-
-
Save dataprolet/b27c2fe1912035da6bfd61ecf18c55b1 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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 | |
| if not response.feed: # Prevent division by zero. | |
| return False | |
| 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