diff --git a/data/liked_posts.txt b/data/liked_posts.txt new file mode 100644 index 0000000..df634ea --- /dev/null +++ b/data/liked_posts.txt @@ -0,0 +1,10 @@ +at://did:plc:27ivzcszryxp6mehutodmcxo/app.bsky.feed.post/3mdmtq5u4xk2h +at://did:plc:gfrmhdmjvxn2sjedzboeudef/app.bsky.feed.post/3mdmctanaak2a +at://did:plc:gfrmhdmjvxn2sjedzboeudef/app.bsky.feed.post/3mdmmwi45a22q +at://did:plc:gfrmhdmjvxn2sjedzboeudef/app.bsky.feed.post/3mdmnkhonws23 +at://did:plc:gfrmhdmjvxn2sjedzboeudef/app.bsky.feed.post/3mdmpdqnous2n +at://did:plc:gfrmhdmjvxn2sjedzboeudef/app.bsky.feed.post/3mdmq3ng5222n +at://did:plc:gfrmhdmjvxn2sjedzboeudef/app.bsky.feed.post/3mdmqfur2ck2n +at://did:plc:gfrmhdmjvxn2sjedzboeudef/app.bsky.feed.post/3mdmqvcougs2n +at://did:plc:gfrmhdmjvxn2sjedzboeudef/app.bsky.feed.post/3mdmrb4ogzs2m +at://did:plc:gfrmhdmjvxn2sjedzboeudef/app.bsky.feed.post/3mdmsqawsyk27 \ No newline at end of file diff --git a/tools/bluesky_bulk.py b/tools/bluesky_bulk.py index 1de5e9a..b60ad32 100644 --- a/tools/bluesky_bulk.py +++ b/tools/bluesky_bulk.py @@ -30,6 +30,22 @@ from rich.table import Table load_dotenv() console = Console() +# Track liked posts to prevent re-liking +LIKED_CACHE_PATH = Path(__file__).parent.parent / 'data' / 'liked_posts.txt' + + +def load_liked_cache() -> set: + """Load set of already-liked post URIs.""" + if LIKED_CACHE_PATH.exists(): + return set(LIKED_CACHE_PATH.read_text().strip().split('\n')) + return set() + + +def save_liked_cache(liked: set): + """Save liked posts to cache file.""" + LIKED_CACHE_PATH.parent.mkdir(exist_ok=True) + LIKED_CACHE_PATH.write_text('\n'.join(sorted(liked))) + def get_client(): """Get authenticated ATProto client.""" @@ -41,9 +57,13 @@ def get_client(): def bulk_like(uris: list[str]) -> dict: """Like multiple posts in parallel.""" client = get_client() - results = {'success': [], 'failed': []} + liked_cache = load_liked_cache() + results = {'success': [], 'failed': [], 'skipped': []} for uri in uris: + if uri in liked_cache: + results['skipped'].append(uri) + continue try: # Get post CID parts = uri.replace('at://', '').split('/') @@ -57,10 +77,20 @@ def bulk_like(uris: list[str]) -> dict: # Like it client.like(uri, cid) results['success'].append(uri) + liked_cache.add(uri) except Exception as e: - results['failed'].append((uri, str(e))) + err = str(e).lower() + if 'already' in err: + results['skipped'].append(uri) + liked_cache.add(uri) + else: + results['failed'].append((uri, str(e))) + + save_liked_cache(liked_cache) console.print(f"[green]Liked {len(results['success'])} posts[/green]") + if results['skipped']: + console.print(f"[dim]Skipped {len(results['skipped'])} (already liked)[/dim]") if results['failed']: console.print(f"[yellow]Failed: {len(results['failed'])}[/yellow]") @@ -71,6 +101,9 @@ def process_notifications(like_replies: bool = True, limit: int = 50) -> dict: """Process notification backlog efficiently.""" client = get_client() + # Load cache of already-liked posts + liked_cache = load_liked_cache() + notifs = client.app.bsky.notification.list_notifications({'limit': limit}) stats = { @@ -78,7 +111,8 @@ def process_notifications(like_replies: bool = True, limit: int = 50) -> dict: 'replies': 0, 'follows': 0, 'mentions': 0, - 'liked_back': 0 + 'liked_back': 0, + 'skipped': 0 # Already liked } to_like = [] @@ -99,14 +133,24 @@ def process_notifications(like_replies: bool = True, limit: int = 50) -> dict: if notif.uri: to_like.append((notif.uri, notif.cid)) - # Bulk like replies/mentions + # Bulk like replies/mentions (skip already-liked) if to_like: for uri, cid in to_like: + if uri in liked_cache: + stats['skipped'] += 1 + continue try: client.like(uri, cid) stats['liked_back'] += 1 - except: - pass + liked_cache.add(uri) + except Exception as e: + # "already liked" errors are fine, still add to cache + if 'already' in str(e).lower(): + liked_cache.add(uri) + stats['skipped'] += 1 + + # Save updated cache + save_liked_cache(liked_cache) # Mark as read try: