Skip to content

Instantly share code, notes, and snippets.

@PavlikPolivka
Last active December 3, 2024 13:47
Show Gist options
  • Save PavlikPolivka/58070bb2ec5b04eb6f47ad37246b8eff to your computer and use it in GitHub Desktop.
Save PavlikPolivka/58070bb2ec5b04eb6f47ad37246b8eff to your computer and use it in GitHub Desktop.
SkyBlue cleanup script to unfollow inactive accounts. Fill in your handle and app password and run it like python unfollow.py --prod --days 15. It will unfollow all acounts that were not active in last 15 days.
from datetime import timezone
from atproto import Client
import datetime
client = Client()
handle = 'YOUR_HANLDE'
password = 'YOUR_APP_PASSWORD'
client.login(handle, password)
def get_follows(recursive=False):
"""Fetch the list of people you are currently following."""
follows = []
limit = 100 if recursive else 1
response = client.get_follows(handle, None, limit)
follows.extend(response.follows)
while recursive and response.cursor:
response = client.get_follows(handle, response.cursor, 100)
follows.extend(response.follows)
return follows
def get_last_post_date(user):
"""Fetch the date of the last post of a user."""
response = client.get_author_feed(user.handle)
if len(response.feed) > 0:
return datetime.datetime.fromisoformat(response.feed[0].post.indexed_at)
return None
def is_inactive(user, days=15):
fifteen_days_ago = datetime.datetime.now(timezone.utc) - datetime.timedelta(days=days)
last_post_day = get_last_post_date(user)
return last_post_day is None or last_post_day < fifteen_days_ago
def is_reposter(user, ratio=0.8):
"""Check if a user is a reposter."""
response = client.get_author_feed(user.handle)
reposts = 0
for post in response.feed:
if post.post.author.handle != user.handle:
reposts += 1
actual_ration = reposts / len(response.feed)
return actual_ration > ratio
def action_on_users(follows, days, repost, dry=True):
"""Identify users to unfollow based on posting activity."""
inactiveCount = 0
reposterCount = 0
for user in follows:
if is_inactive(user, days):
inactiveCount += 1
print(f"User {user.handle} has not posted in the last {days} days.")
if not dry:
unfollow(user)
if is_reposter(user, repost):
reposterCount += 1
print(f"User {user.handle} is a reposter.")
if not dry:
unfollow(user)
if dry:
print(f"Would unfollow {inactiveCount} inactive users and {reposterCount} reposters.")
else:
print(f"Unfollowed {inactiveCount} inactive users and {reposterCount} reposters.")
def unfollow(user):
"""Unfollow users."""
client.delete_follow(user.viewer.following)
def main(recursive=False, dry=False, prod=False, days=15, repost=0.8):
follows = get_follows(recursive)
action_on_users(follows, days, repost, not prod)
if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser(description="Unfollow inactive users.")
parser.add_argument('--dry', action='store_true', help="Full run, dry unfollow.")
parser.add_argument('--prod', action='store_true', help="Full run. Actually unfollow.")
parser.add_argument('--days', type=int, default=15, help="Number of days of inactivity to consider for unfollowing.")
parser.add_argument('--repost', type=float, default=0.8, help="Ratio of reposts.")
args = parser.parse_args()
recursive = args.prod or args.dry
dry = args.dry
prod = args.prod
main(recursive, dry, prod, args.days)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment