From 77576f548dc842e430c6492ce77439b49a1e92d4 Mon Sep 17 00:00:00 2001 From: Cameron Pfiffer Date: Wed, 20 Aug 2025 17:30:42 -0700 Subject: [PATCH] Fix X username caching at mention fetch time MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **Root cause fixed:** - get_mentions() was only returning mention data, losing user info from API response - This caused "@unknown (unknown)" when processing mentions later **Complete solution implemented:** 1. **Modified get_mentions() method**: - Returns dict with both 'mentions' and 'users' keys - Preserves user data from API response includes.users field - Logs when user data is retrieved 2. **Enhanced save_mention_to_queue()**: - Accepts optional users_data parameter - Caches author username/name in 'author_info' field - Preserves this data in queued mention JSON files 3. **Updated fetch_and_queue_mentions()**: - Extracts both mentions and users from get_mentions() result - Passes user data to save_mention_to_queue() - Ensures usernames are cached when mentions arrive 4. **Improved process_x_mention()**: - First tries cached author_info from queued mention - Falls back to thread data lookup (existing logic) - Finally falls back to thread tweet scan - Three-tier fallback system for maximum reliability **Benefits:** - Fixes the root cause by capturing usernames when available from API - No extra API calls needed - Backward compatible with existing queued mentions - Usernames reliably available even when thread context fails This ensures usernames are properly cached when mentions are fetched, eliminating the "@unknown" issue that void was experiencing. 🤖 Generated with Claude Code Co-Authored-By: Claude --- x.py | 97 ++++++++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 74 insertions(+), 23 deletions(-) diff --git a/x.py b/x.py index b493a85..4eb37c1 100644 --- a/x.py +++ b/x.py @@ -144,16 +144,16 @@ class XClient: return None - def get_mentions(self, since_id: Optional[str] = None, max_results: int = 10) -> Optional[List[Dict]]: + def get_mentions(self, since_id: Optional[str] = None, max_results: int = 10) -> Optional[Dict]: """ - Fetch mentions for the configured user. + Fetch mentions for the configured user with user data. Args: since_id: Minimum Post ID to include (for getting newer mentions) max_results: Number of results to return (5-100) Returns: - List of mention objects or None if request failed + Dict with 'mentions' and 'users' keys, or None if request failed """ endpoint = f"/users/{self.user_id}/mentions" params = { @@ -174,14 +174,22 @@ class XClient: if response and "data" in response: mentions = response["data"] + users_data = {} + + # Extract user data from includes + if "includes" in response and "users" in response["includes"]: + for user in response["includes"]["users"]: + users_data[user["id"]] = user + logger.info(f"Retrieved user data for {len(users_data)} users") + logger.info(f"Retrieved {len(mentions)} mentions") - return mentions + return {"mentions": mentions, "users": users_data} else: if response: logger.info(f"No mentions in response. Full response: {response}") else: logger.warning("Request failed - no response received") - return [] + return {"mentions": [], "users": {}} def get_user_info(self, user_id: str) -> Optional[Dict]: """Get information about a specific user.""" @@ -747,8 +755,13 @@ def should_respond_to_downranked_user(user_id: str, downrank_users: Set[str]) -> logger.info(f"Downranked user {user_id}: {'responding' if should_respond else 'skipping'} (10% chance)") return should_respond -def save_mention_to_queue(mention: Dict): - """Save a mention to the queue directory for async processing.""" +def save_mention_to_queue(mention: Dict, users_data: Optional[Dict] = None): + """Save a mention to the queue directory for async processing with author info. + + Args: + mention: The mention data from X API + users_data: Optional dict mapping user IDs to user data (including usernames) + """ try: mention_id = mention.get('id') if not mention_id: @@ -771,11 +784,28 @@ def save_mention_to_queue(mention: Dict): queue_file = X_QUEUE_DIR / filename + # Extract author info if users_data provided + author_id = mention.get('author_id') + author_username = None + author_name = None + + if users_data and author_id and author_id in users_data: + user_info = users_data[author_id] + author_username = user_info.get('username') + author_name = user_info.get('name') + logger.info(f"Caching author info for @{author_username} ({author_name})") + # Save mention data with enhanced debugging information mention_data = { 'mention': mention, 'queued_at': datetime.now().isoformat(), 'type': 'x_mention', + # Cache author info for later use + 'author_info': { + 'username': author_username, + 'name': author_name, + 'id': author_id + }, # Debug info for conversation tracking 'debug_info': { 'mention_id': mention.get('id'), @@ -959,22 +989,25 @@ def fetch_and_queue_mentions(username: str) -> int: logger.info(f"Fetching mentions for @{username} since {last_seen_id or 'beginning'}") # Search for mentions - mentions = client.search_mentions( - username=username, + # Get mentions with user data + result = client.get_mentions( since_id=last_seen_id, max_results=100 # Get as many as possible ) - if not mentions: + if not result or not result["mentions"]: logger.info("No new mentions found") return 0 + mentions = result["mentions"] + users_data = result["users"] + # Process mentions (newest first, so reverse to process oldest first) mentions.reverse() new_count = 0 for mention in mentions: - save_mention_to_queue(mention) + save_mention_to_queue(mention, users_data) new_count += 1 # Update last seen ID to the most recent mention @@ -1271,12 +1304,18 @@ def test_x_client(): """Test the X client by fetching mentions.""" try: client = create_x_client() - mentions = client.get_mentions(max_results=5) + result = client.get_mentions(max_results=5) - if mentions: + if result and result["mentions"]: + mentions = result["mentions"] + users_data = result["users"] print(f"Successfully retrieved {len(mentions)} mentions:") + print(f"User data available for {len(users_data)} users") for mention in mentions: - print(f"- {mention.get('id')}: {mention.get('text')[:50]}...") + author_id = mention.get('author_id') + author_info = users_data.get(author_id, {}) + username = author_info.get('username', 'unknown') + print(f"- {mention.get('id')} from @{username}: {mention.get('text')[:50]}...") else: print("No mentions retrieved") @@ -1513,11 +1552,25 @@ def process_x_mention(void_agent, x_client, mention_data, queue_filepath=None, t # Continue without user blocks rather than failing completely # Create prompt for Letta agent - author_info = thread_data.get('users', {}).get(author_id, {}) - author_username = author_info.get('username', 'unknown') - author_name = author_info.get('name', author_username) - - # Fallback: if username is unknown, try to find it in the thread tweets + # First try to use cached author info from queued mention + author_username = 'unknown' + author_name = 'unknown' + + if 'author_info' in mention_data: + # Use cached author info from when mention was queued + cached_info = mention_data['author_info'] + if cached_info.get('username'): + author_username = cached_info['username'] + author_name = cached_info.get('name', author_username) + logger.info(f"Using cached author info: @{author_username} ({author_name})") + + # If not cached, try thread data + if author_username == 'unknown': + author_info = thread_data.get('users', {}).get(author_id, {}) + author_username = author_info.get('username', 'unknown') + author_name = author_info.get('name', author_username) + + # Final fallback: if username is still unknown, try to find it in the thread tweets if author_username == 'unknown' and 'tweets' in thread_data: for tweet in thread_data['tweets']: if tweet.get('author_id') == author_id and 'author' in tweet: @@ -1916,10 +1969,8 @@ def load_and_process_queued_x_mentions(void_agent, x_client, testing_mode=False) with open(filepath, 'r') as f: queue_data = json.load(f) - mention_data = queue_data.get('mention', queue_data) - - # Process the mention - success = process_x_mention(void_agent, x_client, mention_data, + # Process the mention (pass full queue_data to have access to author_info) + success = process_x_mention(void_agent, x_client, queue_data, queue_filepath=filepath, testing_mode=testing_mode) except XRateLimitError: -- 2.51.2