From 387b9d100b6786860b2e78cbc1ada00b2abaa33e Mon Sep 17 00:00:00 2001 From: Ting Chien Meng Date: Fri, 10 Jan 2025 11:39:57 -0500 Subject: [PATCH 1/8] fetch user settings from runtime --- packages/plugin-twitter/src/actions/post.ts | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/packages/plugin-twitter/src/actions/post.ts b/packages/plugin-twitter/src/actions/post.ts index f99af1e9c8e..34fb3fd5792 100644 --- a/packages/plugin-twitter/src/actions/post.ts +++ b/packages/plugin-twitter/src/actions/post.ts @@ -59,13 +59,16 @@ async function composeTweet( } } -async function postTweet(content: string): Promise { +async function postTweet( + runtime: IAgentRuntime, + content: string +): Promise { try { const scraper = new Scraper(); - const username = process.env.TWITTER_USERNAME; - const password = process.env.TWITTER_PASSWORD; - const email = process.env.TWITTER_EMAIL; - const twitter2faSecret = process.env.TWITTER_2FA_SECRET; + const username = runtime.getSetting("TWITTER_USERNAME"); + const password = runtime.getSetting("TWITTER_PASSWORD"); + const email = runtime.getSetting("TWITTER_EMAIL"); + const twitter2faSecret = runtime.getSetting("TWITTER_2FA_SECRET"); if (!username || !password) { elizaLogger.error( @@ -127,8 +130,10 @@ export const postAction: Action = { message: Memory, state?: State ) => { - const hasCredentials = - !!process.env.TWITTER_USERNAME && !!process.env.TWITTER_PASSWORD; + const username = runtime.getSetting("TWITTER_USERNAME"); + const password = runtime.getSetting("TWITTER_PASSWORD"); + const email = runtime.getSetting("TWITTER_EMAIL"); + const hasCredentials = !!username && !!password && !!email; elizaLogger.log(`Has credentials: ${hasCredentials}`); return hasCredentials; @@ -160,7 +165,7 @@ export const postAction: Action = { return true; } - return await postTweet(tweetContent); + return await postTweet(runtime, tweetContent); } catch (error) { elizaLogger.error("Error in post action:", error); return false; From c7a3cafbdea81e9264299c84a9901fc070803e37 Mon Sep 17 00:00:00 2001 From: Ting Chien Meng Date: Fri, 10 Jan 2025 13:13:06 -0500 Subject: [PATCH 2/8] move truncate to core --- packages/client-twitter/src/post.ts | 35 +---------------------------- packages/core/src/parsing.ts | 34 ++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 34 deletions(-) diff --git a/packages/client-twitter/src/post.ts b/packages/client-twitter/src/post.ts index e0aff4b3a61..80129173c51 100644 --- a/packages/client-twitter/src/post.ts +++ b/packages/client-twitter/src/post.ts @@ -8,6 +8,7 @@ import { stringToUuid, TemplateType, UUID, + truncateToCompleteSentence, } from "@elizaos/core"; import { elizaLogger } from "@elizaos/core"; import { ClientBase } from "./base.ts"; @@ -77,40 +78,6 @@ Tweet: # Respond with qualifying action tags only. Default to NO action unless extremely confident of relevance.` + postActionResponseFooter; -/** - * Truncate text to fit within the Twitter character limit, ensuring it ends at a complete sentence. - */ -function truncateToCompleteSentence( - text: string, - maxTweetLength: number -): string { - if (text.length <= maxTweetLength) { - return text; - } - - // Attempt to truncate at the last period within the limit - const lastPeriodIndex = text.lastIndexOf(".", maxTweetLength - 1); - if (lastPeriodIndex !== -1) { - const truncatedAtPeriod = text.slice(0, lastPeriodIndex + 1).trim(); - if (truncatedAtPeriod.length > 0) { - return truncatedAtPeriod; - } - } - - // If no period, truncate to the nearest whitespace within the limit - const lastSpaceIndex = text.lastIndexOf(" ", maxTweetLength - 1); - if (lastSpaceIndex !== -1) { - const truncatedAtSpace = text.slice(0, lastSpaceIndex).trim(); - if (truncatedAtSpace.length > 0) { - return truncatedAtSpace + "..."; - } - } - - // Fallback: Hard truncate and add ellipsis - const hardTruncated = text.slice(0, maxTweetLength - 3).trim(); - return hardTruncated + "..."; -} - interface PendingTweet { cleanedContent: string; roomId: UUID; diff --git a/packages/core/src/parsing.ts b/packages/core/src/parsing.ts index 107ce8ea0bd..331cd30a13b 100644 --- a/packages/core/src/parsing.ts +++ b/packages/core/src/parsing.ts @@ -205,3 +205,37 @@ export const parseActionResponseFromText = ( return { actions }; }; + +/** + * Truncate text to fit within the character limit, ensuring it ends at a complete sentence. + */ +export function truncateToCompleteSentence( + text: string, + maxLength: number +): string { + if (text.length <= maxLength) { + return text; + } + + // Attempt to truncate at the last period within the limit + const lastPeriodIndex = text.lastIndexOf(".", maxLength - 1); + if (lastPeriodIndex !== -1) { + const truncatedAtPeriod = text.slice(0, lastPeriodIndex + 1).trim(); + if (truncatedAtPeriod.length > 0) { + return truncatedAtPeriod; + } + } + + // If no period, truncate to the nearest whitespace within the limit + const lastSpaceIndex = text.lastIndexOf(" ", maxLength - 1); + if (lastSpaceIndex !== -1) { + const truncatedAtSpace = text.slice(0, lastSpaceIndex).trim(); + if (truncatedAtSpace.length > 0) { + return truncatedAtSpace + "..."; + } + } + + // Fallback: Hard truncate and add ellipsis + const hardTruncated = text.slice(0, maxLength - 3).trim(); + return hardTruncated + "..."; +} From a812a496f0d313f44128ff711667827095409fad Mon Sep 17 00:00:00 2001 From: Ting Chien Meng Date: Fri, 10 Jan 2025 13:18:00 -0500 Subject: [PATCH 3/8] remove unuse parameter --- packages/client-twitter/src/post.ts | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/packages/client-twitter/src/post.ts b/packages/client-twitter/src/post.ts index 80129173c51..93d89930259 100644 --- a/packages/client-twitter/src/post.ts +++ b/packages/client-twitter/src/post.ts @@ -366,7 +366,6 @@ export class TwitterPostClient { async handleNoteTweet( client: ClientBase, - runtime: IAgentRuntime, content: string, tweetId?: string ) { @@ -432,11 +431,7 @@ export class TwitterPostClient { let result; if (cleanedContent.length > DEFAULT_MAX_TWEET_LENGTH) { - result = await this.handleNoteTweet( - client, - runtime, - cleanedContent - ); + result = await this.handleNoteTweet(client, cleanedContent); } else { result = await this.sendStandardTweet(client, cleanedContent); } @@ -1171,7 +1166,6 @@ export class TwitterPostClient { if (replyText.length > DEFAULT_MAX_TWEET_LENGTH) { result = await this.handleNoteTweet( this.client, - this.runtime, replyText, tweet.id ); From af0b397b2caee62f22dc861730212a138b414948 Mon Sep 17 00:00:00 2001 From: Ting Chien Meng Date: Fri, 10 Jan 2025 14:35:30 -0500 Subject: [PATCH 4/8] prevent repeated login --- packages/plugin-twitter/src/actions/post.ts | 109 +++++++++++++------- 1 file changed, 71 insertions(+), 38 deletions(-) diff --git a/packages/plugin-twitter/src/actions/post.ts b/packages/plugin-twitter/src/actions/post.ts index 34fb3fd5792..3d9a99e3e69 100644 --- a/packages/plugin-twitter/src/actions/post.ts +++ b/packages/plugin-twitter/src/actions/post.ts @@ -7,11 +7,14 @@ import { elizaLogger, ModelClass, generateObject, + truncateToCompleteSentence, } from "@elizaos/core"; import { Scraper } from "agent-twitter-client"; import { tweetTemplate } from "../templates"; import { isTweetContent, TweetSchema } from "../types"; +export const DEFAULT_MAX_TWEET_LENGTH = 280; + async function composeTweet( runtime: IAgentRuntime, _message: Memory, @@ -39,7 +42,16 @@ async function composeTweet( return; } - const trimmedContent = tweetContentObject.object.text.trim(); + let trimmedContent = tweetContentObject.object.text.trim(); + + // Truncate the content to the maximum tweet length specified in the environment settings, ensuring the truncation respects sentence boundaries. + const maxTweetLength = this.client.twitterConfig.MAX_TWEET_LENGTH; + if (maxTweetLength) { + trimmedContent = truncateToCompleteSentence( + trimmedContent, + maxTweetLength + ); + } // Skip truncation if TWITTER_PREMIUM is true if ( @@ -59,56 +71,77 @@ async function composeTweet( } } +async function sendTweet(twitterClient: Scraper, content: string) { + const result = await twitterClient.sendTweet(content); + + const body = await result.json(); + elizaLogger.log("Tweet response:", body); + + // Check for Twitter API errors + if (body.errors) { + const error = body.errors[0]; + elizaLogger.error( + `Twitter API error (${error.code}): ${error.message}` + ); + return false; + } + + // Check for successful tweet creation + if (!body?.data?.create_tweet?.tweet_results?.result) { + elizaLogger.error("Failed to post tweet: No tweet result in response"); + return false; + } +} + async function postTweet( runtime: IAgentRuntime, content: string ): Promise { try { - const scraper = new Scraper(); - const username = runtime.getSetting("TWITTER_USERNAME"); - const password = runtime.getSetting("TWITTER_PASSWORD"); - const email = runtime.getSetting("TWITTER_EMAIL"); - const twitter2faSecret = runtime.getSetting("TWITTER_2FA_SECRET"); + const twitterClient = runtime.clients.twitter.client.twitterClient; + const scraper = twitterClient || new Scraper(); - if (!username || !password) { - elizaLogger.error( - "Twitter credentials not configured in environment" - ); - return false; - } + if (!twitterClient) { + const username = runtime.getSetting("TWITTER_USERNAME"); + const password = runtime.getSetting("TWITTER_PASSWORD"); + const email = runtime.getSetting("TWITTER_EMAIL"); + const twitter2faSecret = runtime.getSetting("TWITTER_2FA_SECRET"); - // Login with credentials - await scraper.login(username, password, email, twitter2faSecret); - if (!(await scraper.isLoggedIn())) { - elizaLogger.error("Failed to login to Twitter"); - return false; + if (!username || !password) { + elizaLogger.error( + "Twitter credentials not configured in environment" + ); + return false; + } + // Login with credentials + await scraper.login(username, password, email, twitter2faSecret); + if (!(await scraper.isLoggedIn())) { + elizaLogger.error("Failed to login to Twitter"); + return false; + } } // Send the tweet elizaLogger.log("Attempting to send tweet:", content); - const result = await scraper.sendTweet(content); - - const body = await result.json(); - elizaLogger.log("Tweet response:", body); - // Check for Twitter API errors - if (body.errors) { - const error = body.errors[0]; - elizaLogger.error( - `Twitter API error (${error.code}): ${error.message}` - ); - return false; - } - - // Check for successful tweet creation - if (!body?.data?.create_tweet?.tweet_results?.result) { - elizaLogger.error( - "Failed to post tweet: No tweet result in response" - ); - return false; + try { + if (content.length > DEFAULT_MAX_TWEET_LENGTH) { + const noteTweetResult = await scraper.sendNoteTweet(content); + if ( + noteTweetResult.errors && + noteTweetResult.errors.length > 0 + ) { + // Note Tweet failed due to authorization. Falling back to standard Tweet. + return await sendTweet(scraper, content); + } else { + return true; + } + } else { + return await sendTweet(scraper, content); + } + } catch (error) { + throw new Error(`Note Tweet failed: ${error}`); } - - return true; } catch (error) { // Log the full error details elizaLogger.error("Error posting tweet:", { From 12a3299d0a4c4dbfcad548cd329f77b4bf23927a Mon Sep 17 00:00:00 2001 From: Ting Chien Meng Date: Fri, 10 Jan 2025 14:37:52 -0500 Subject: [PATCH 5/8] clean code --- packages/plugin-twitter/src/actions/post.ts | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/packages/plugin-twitter/src/actions/post.ts b/packages/plugin-twitter/src/actions/post.ts index 3d9a99e3e69..a05ede3e6b9 100644 --- a/packages/plugin-twitter/src/actions/post.ts +++ b/packages/plugin-twitter/src/actions/post.ts @@ -53,17 +53,6 @@ async function composeTweet( ); } - // Skip truncation if TWITTER_PREMIUM is true - if ( - process.env.TWITTER_PREMIUM?.toLowerCase() !== "true" && - trimmedContent.length > 180 - ) { - elizaLogger.warn( - `Tweet too long (${trimmedContent.length} chars), truncating...` - ); - return trimmedContent.substring(0, 177) + "..."; - } - return trimmedContent; } catch (error) { elizaLogger.error("Error composing tweet:", error); From 2c0f5447596feb43df1bd37f89d419bc5fbcdfb2 Mon Sep 17 00:00:00 2001 From: Ting Chien Meng Date: Fri, 10 Jan 2025 14:40:11 -0500 Subject: [PATCH 6/8] modify comment --- packages/plugin-twitter/src/actions/post.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/plugin-twitter/src/actions/post.ts b/packages/plugin-twitter/src/actions/post.ts index a05ede3e6b9..57976370f6a 100644 --- a/packages/plugin-twitter/src/actions/post.ts +++ b/packages/plugin-twitter/src/actions/post.ts @@ -44,7 +44,7 @@ async function composeTweet( let trimmedContent = tweetContentObject.object.text.trim(); - // Truncate the content to the maximum tweet length specified in the environment settings, ensuring the truncation respects sentence boundaries. + // Truncate the content to the maximum tweet length specified in the environment settings. const maxTweetLength = this.client.twitterConfig.MAX_TWEET_LENGTH; if (maxTweetLength) { trimmedContent = truncateToCompleteSentence( From b4964688079d0462c5250c6fcca10107dc85f95c Mon Sep 17 00:00:00 2001 From: Ting Chien Meng Date: Fri, 10 Jan 2025 14:41:37 -0500 Subject: [PATCH 7/8] return value --- packages/plugin-twitter/src/actions/post.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/plugin-twitter/src/actions/post.ts b/packages/plugin-twitter/src/actions/post.ts index 57976370f6a..af6577b555c 100644 --- a/packages/plugin-twitter/src/actions/post.ts +++ b/packages/plugin-twitter/src/actions/post.ts @@ -80,6 +80,8 @@ async function sendTweet(twitterClient: Scraper, content: string) { elizaLogger.error("Failed to post tweet: No tweet result in response"); return false; } + + return true; } async function postTweet( From d52e0109e184b5f433a0d1bceec67b23a9e5ee14 Mon Sep 17 00:00:00 2001 From: Ting Chien Meng Date: Fri, 10 Jan 2025 15:05:07 -0500 Subject: [PATCH 8/8] get settings from runtime --- packages/plugin-twitter/src/actions/post.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/plugin-twitter/src/actions/post.ts b/packages/plugin-twitter/src/actions/post.ts index af6577b555c..4bd3e86e80f 100644 --- a/packages/plugin-twitter/src/actions/post.ts +++ b/packages/plugin-twitter/src/actions/post.ts @@ -45,11 +45,11 @@ async function composeTweet( let trimmedContent = tweetContentObject.object.text.trim(); // Truncate the content to the maximum tweet length specified in the environment settings. - const maxTweetLength = this.client.twitterConfig.MAX_TWEET_LENGTH; + const maxTweetLength = runtime.getSetting("MAX_TWEET_LENGTH"); if (maxTweetLength) { trimmedContent = truncateToCompleteSentence( trimmedContent, - maxTweetLength + Number(maxTweetLength) ); } @@ -89,7 +89,7 @@ async function postTweet( content: string ): Promise { try { - const twitterClient = runtime.clients.twitter.client.twitterClient; + const twitterClient = runtime.clients.twitter?.client?.twitterClient; const scraper = twitterClient || new Scraper(); if (!twitterClient) {