From c1502516dbe52305ff4c59874512a8cea7cf520c Mon Sep 17 00:00:00 2001 From: Ting Chien Meng Date: Sun, 5 Jan 2025 08:35:23 -0500 Subject: [PATCH] clean code and add comment --- packages/client-twitter/src/post.ts | 469 +++++++++++++++------------- 1 file changed, 246 insertions(+), 223 deletions(-) diff --git a/packages/client-twitter/src/post.ts b/packages/client-twitter/src/post.ts index 8fe3f5d7c51..535b1765189 100644 --- a/packages/client-twitter/src/post.ts +++ b/packages/client-twitter/src/post.ts @@ -16,6 +16,8 @@ import { IImageDescriptionService, ServiceType } from "@elizaos/core"; import { buildConversationThread } from "./utils.ts"; import { twitterMessageHandlerTemplate } from "./interactions.ts"; import { DEFAULT_MAX_TWEET_LENGTH } from "./environment.ts"; +import { State } from "@elizaos/core"; +import { ActionResponse } from "@elizaos/core"; const twitterPostTemplate = ` # Areas of Expertise @@ -625,8 +627,10 @@ export class TwitterPostClient { "twitter" ); + // TODO: Once the 'count' parameter is fixed in the 'fetchTimeline' method of the 'agent-twitter-client', + // we should enable the ability to control the number of items fetched here. + // Related issue: https://github.com/elizaOS/agent-twitter-client/issues/43 const homeTimeline = await this.client.fetchTimelineForActions(); - const results = []; const maxActionsProcessing = this.client.twitterConfig.MAX_ACTIONS_PROCESSING; const processedTimelines = []; @@ -720,267 +724,286 @@ export class TwitterPostClient { return 0; }); }; + // Sort the timeline based on the action decision score, + // then slice the results according to the environment variable to limit the number of actions per cycle. const sortedTimelines = sortProcessedTimeline( processedTimelines ).slice(0, maxActionsProcessing); - for (const timeline of sortedTimelines) { - const { actionResponse, tweetState, roomId, tweet } = timeline; - try { - const executedActions: string[] = []; - // Execute actions - if (actionResponse.like) { - try { - if (this.isDryRun) { - elizaLogger.info( - `Dry run: would have liked tweet ${tweet.id}` - ); - executedActions.push("like (dry run)"); - } else { - await this.client.twitterClient.likeTweet( - tweet.id - ); - executedActions.push("like"); - elizaLogger.log(`Liked tweet ${tweet.id}`); - } - } catch (error) { - elizaLogger.error( - `Error liking tweet ${tweet.id}:`, - error + return this.processTimelineActions(sortedTimelines); // Return results array to indicate completion + } catch (error) { + elizaLogger.error("Error in processTweetActions:", error); + throw error; + } finally { + this.isProcessing = false; + } + } + + /** + * Processes a list of timelines by executing the corresponding tweet actions. + * Each timeline includes the tweet, action response, tweet state, and room context. + * Results are returned for tracking completed actions. + * + * @param timelines - Array of objects containing tweet details, action responses, and state information. + * @returns A promise that resolves to an array of results with details of executed actions. + */ + private async processTimelineActions( + timelines: { + tweet: Tweet; + actionResponse: ActionResponse; + tweetState: State; + roomId: UUID; + }[] + ): Promise< + { + tweetId: string; + actionResponse: ActionResponse; + executedActions: string[]; + }[] + > { + const results = []; + for (const timeline of timelines) { + const { actionResponse, tweetState, roomId, tweet } = timeline; + try { + const executedActions: string[] = []; + // Execute actions + if (actionResponse.like) { + try { + if (this.isDryRun) { + elizaLogger.info( + `Dry run: would have liked tweet ${tweet.id}` ); + executedActions.push("like (dry run)"); + } else { + await this.client.twitterClient.likeTweet(tweet.id); + executedActions.push("like"); + elizaLogger.log(`Liked tweet ${tweet.id}`); } + } catch (error) { + elizaLogger.error( + `Error liking tweet ${tweet.id}:`, + error + ); } + } - if (actionResponse.retweet) { - try { - if (this.isDryRun) { - elizaLogger.info( - `Dry run: would have retweeted tweet ${tweet.id}` - ); - executedActions.push("retweet (dry run)"); - } else { - await this.client.twitterClient.retweet( - tweet.id - ); - executedActions.push("retweet"); - elizaLogger.log(`Retweeted tweet ${tweet.id}`); - } - } catch (error) { - elizaLogger.error( - `Error retweeting tweet ${tweet.id}:`, - error + if (actionResponse.retweet) { + try { + if (this.isDryRun) { + elizaLogger.info( + `Dry run: would have retweeted tweet ${tweet.id}` ); + executedActions.push("retweet (dry run)"); + } else { + await this.client.twitterClient.retweet(tweet.id); + executedActions.push("retweet"); + elizaLogger.log(`Retweeted tweet ${tweet.id}`); } + } catch (error) { + elizaLogger.error( + `Error retweeting tweet ${tweet.id}:`, + error + ); } + } - if (actionResponse.quote) { - try { - // Check for dry run mode - if (this.isDryRun) { - elizaLogger.info( - `Dry run: would have posted quote tweet for ${tweet.id}` - ); - executedActions.push("quote (dry run)"); - continue; - } - - // Build conversation thread for context - const thread = await buildConversationThread( - tweet, - this.client + if (actionResponse.quote) { + try { + // Check for dry run mode + if (this.isDryRun) { + elizaLogger.info( + `Dry run: would have posted quote tweet for ${tweet.id}` ); - const formattedConversation = thread - .map( - (t) => - `@${t.username} (${new Date(t.timestamp * 1000).toLocaleString()}): ${t.text}` - ) - .join("\n\n"); + executedActions.push("quote (dry run)"); + continue; + } - // Generate image descriptions if present - const imageDescriptions = []; - if (tweet.photos?.length > 0) { - elizaLogger.log( - "Processing images in tweet for context" - ); - for (const photo of tweet.photos) { - const description = await this.runtime - .getService( - ServiceType.IMAGE_DESCRIPTION - ) - .describeImage(photo.url); - imageDescriptions.push(description); - } + // Build conversation thread for context + const thread = await buildConversationThread( + tweet, + this.client + ); + const formattedConversation = thread + .map( + (t) => + `@${t.username} (${new Date(t.timestamp * 1000).toLocaleString()}): ${t.text}` + ) + .join("\n\n"); + + // Generate image descriptions if present + const imageDescriptions = []; + if (tweet.photos?.length > 0) { + elizaLogger.log( + "Processing images in tweet for context" + ); + for (const photo of tweet.photos) { + const description = await this.runtime + .getService( + ServiceType.IMAGE_DESCRIPTION + ) + .describeImage(photo.url); + imageDescriptions.push(description); } + } - // Handle quoted tweet if present - let quotedContent = ""; - if (tweet.quotedStatusId) { - try { - const quotedTweet = - await this.client.twitterClient.getTweet( - tweet.quotedStatusId - ); - if (quotedTweet) { - quotedContent = `\nQuoted Tweet from @${quotedTweet.username}:\n${quotedTweet.text}`; - } - } catch (error) { - elizaLogger.error( - "Error fetching quoted tweet:", - error + // Handle quoted tweet if present + let quotedContent = ""; + if (tweet.quotedStatusId) { + try { + const quotedTweet = + await this.client.twitterClient.getTweet( + tweet.quotedStatusId ); + if (quotedTweet) { + quotedContent = `\nQuoted Tweet from @${quotedTweet.username}:\n${quotedTweet.text}`; } - } - - // Compose rich state with all context - const enrichedState = - await this.runtime.composeState( - { - userId: this.runtime.agentId, - roomId: stringToUuid( - tweet.conversationId + - "-" + - this.runtime.agentId - ), - agentId: this.runtime.agentId, - content: { - text: tweet.text, - action: "QUOTE", - }, - }, - { - twitterUserName: this.twitterUsername, - currentPost: `From @${tweet.username}: ${tweet.text}`, - formattedConversation, - imageContext: - imageDescriptions.length > 0 - ? `\nImages in Tweet:\n${imageDescriptions.map((desc, i) => `Image ${i + 1}: ${desc}`).join("\n")}` - : "", - quotedContent, - } + } catch (error) { + elizaLogger.error( + "Error fetching quoted tweet:", + error ); + } + } - const quoteContent = - await this.generateTweetContent(enrichedState, { - template: - this.runtime.character.templates - ?.twitterMessageHandlerTemplate || - twitterMessageHandlerTemplate, - }); + // Compose rich state with all context + const enrichedState = await this.runtime.composeState( + { + userId: this.runtime.agentId, + roomId: stringToUuid( + tweet.conversationId + + "-" + + this.runtime.agentId + ), + agentId: this.runtime.agentId, + content: { + text: tweet.text, + action: "QUOTE", + }, + }, + { + twitterUserName: this.twitterUsername, + currentPost: `From @${tweet.username}: ${tweet.text}`, + formattedConversation, + imageContext: + imageDescriptions.length > 0 + ? `\nImages in Tweet:\n${imageDescriptions.map((desc, i) => `Image ${i + 1}: ${desc}`).join("\n")}` + : "", + quotedContent, + } + ); - if (!quoteContent) { - elizaLogger.error( - "Failed to generate valid quote tweet content" - ); - return; + const quoteContent = await this.generateTweetContent( + enrichedState, + { + template: + this.runtime.character.templates + ?.twitterMessageHandlerTemplate || + twitterMessageHandlerTemplate, } + ); - elizaLogger.log( - "Generated quote tweet content:", - quoteContent + if (!quoteContent) { + elizaLogger.error( + "Failed to generate valid quote tweet content" ); + return; + } - // Send the tweet through request queue - const result = await this.client.requestQueue.add( - async () => - await this.client.twitterClient.sendQuoteTweet( - quoteContent, - tweet.id - ) - ); + elizaLogger.log( + "Generated quote tweet content:", + quoteContent + ); - const body = await result.json(); + // Send the tweet through request queue + const result = await this.client.requestQueue.add( + async () => + await this.client.twitterClient.sendQuoteTweet( + quoteContent, + tweet.id + ) + ); - if ( - body?.data?.create_tweet?.tweet_results?.result - ) { - elizaLogger.log( - "Successfully posted quote tweet" - ); - executedActions.push("quote"); + const body = await result.json(); - // Cache generation context for debugging - await this.runtime.cacheManager.set( - `twitter/quote_generation_${tweet.id}.txt`, - `Context:\n${enrichedState}\n\nGenerated Quote:\n${quoteContent}` - ); - } else { - elizaLogger.error( - "Quote tweet creation failed:", - body - ); - } - } catch (error) { - elizaLogger.error( - "Error in quote tweet generation:", - error - ); - } - } + if (body?.data?.create_tweet?.tweet_results?.result) { + elizaLogger.log("Successfully posted quote tweet"); + executedActions.push("quote"); - if (actionResponse.reply) { - try { - await this.handleTextOnlyReply( - tweet, - tweetState, - executedActions + // Cache generation context for debugging + await this.runtime.cacheManager.set( + `twitter/quote_generation_${tweet.id}.txt`, + `Context:\n${enrichedState}\n\nGenerated Quote:\n${quoteContent}` ); - } catch (error) { + } else { elizaLogger.error( - `Error replying to tweet ${tweet.id}:`, - error + "Quote tweet creation failed:", + body ); } + } catch (error) { + elizaLogger.error( + "Error in quote tweet generation:", + error + ); } + } - // Add these checks before creating memory - await this.runtime.ensureRoomExists(roomId); - await this.runtime.ensureUserExists( - stringToUuid(tweet.userId), - tweet.username, - tweet.name, - "twitter" - ); - await this.runtime.ensureParticipantInRoom( - this.runtime.agentId, - roomId - ); + if (actionResponse.reply) { + try { + await this.handleTextOnlyReply( + tweet, + tweetState, + executedActions + ); + } catch (error) { + elizaLogger.error( + `Error replying to tweet ${tweet.id}:`, + error + ); + } + } - // Then create the memory - await this.runtime.messageManager.createMemory({ - id: stringToUuid(tweet.id + "-" + this.runtime.agentId), - userId: stringToUuid(tweet.userId), - content: { - text: tweet.text, - url: tweet.permanentUrl, - source: "twitter", - action: executedActions.join(","), - }, - agentId: this.runtime.agentId, - roomId, - embedding: getEmbeddingZeroVector(), - createdAt: tweet.timestamp * 1000, - }); + // Add these checks before creating memory + await this.runtime.ensureRoomExists(roomId); + await this.runtime.ensureUserExists( + stringToUuid(tweet.userId), + tweet.username, + tweet.name, + "twitter" + ); + await this.runtime.ensureParticipantInRoom( + this.runtime.agentId, + roomId + ); - results.push({ - tweetId: tweet.id, - actionResponse: actionResponse, - executedActions, - }); - } catch (error) { - elizaLogger.error( - `Error processing tweet ${tweet.id}:`, - error - ); - continue; - } + // Then create the memory + await this.runtime.messageManager.createMemory({ + id: stringToUuid(tweet.id + "-" + this.runtime.agentId), + userId: stringToUuid(tweet.userId), + content: { + text: tweet.text, + url: tweet.permanentUrl, + source: "twitter", + action: executedActions.join(","), + }, + agentId: this.runtime.agentId, + roomId, + embedding: getEmbeddingZeroVector(), + createdAt: tweet.timestamp * 1000, + }); + + results.push({ + tweetId: tweet.id, + actionResponse: actionResponse, + executedActions, + }); + } catch (error) { + elizaLogger.error(`Error processing tweet ${tweet.id}:`, error); + continue; } - return results; // Return results array to indicate completion - } catch (error) { - elizaLogger.error("Error in processTweetActions:", error); - throw error; - } finally { - this.isProcessing = false; } + + return results; } /**