@@ -7,11 +7,14 @@ import {
7
7
elizaLogger ,
8
8
ModelClass ,
9
9
generateObject ,
10
+ truncateToCompleteSentence ,
10
11
} from "@elizaos/core" ;
11
12
import { Scraper } from "agent-twitter-client" ;
12
13
import { tweetTemplate } from "../templates" ;
13
14
import { isTweetContent , TweetSchema } from "../types" ;
14
15
16
+ export const DEFAULT_MAX_TWEET_LENGTH = 280 ;
17
+
15
18
async function composeTweet (
16
19
runtime : IAgentRuntime ,
17
20
_message : Memory ,
@@ -39,17 +42,15 @@ async function composeTweet(
39
42
return ;
40
43
}
41
44
42
- const trimmedContent = tweetContentObject . object . text . trim ( ) ;
45
+ let trimmedContent = tweetContentObject . object . text . trim ( ) ;
43
46
44
- // Skip truncation if TWITTER_PREMIUM is true
45
- if (
46
- process . env . TWITTER_PREMIUM ?. toLowerCase ( ) !== "true" &&
47
- trimmedContent . length > 180
48
- ) {
49
- elizaLogger . warn (
50
- `Tweet too long (${ trimmedContent . length } chars), truncating...`
47
+ // Truncate the content to the maximum tweet length specified in the environment settings.
48
+ const maxTweetLength = runtime . getSetting ( "MAX_TWEET_LENGTH" ) ;
49
+ if ( maxTweetLength ) {
50
+ trimmedContent = truncateToCompleteSentence (
51
+ trimmedContent ,
52
+ Number ( maxTweetLength )
51
53
) ;
52
- return trimmedContent . substring ( 0 , 177 ) + "..." ;
53
54
}
54
55
55
56
return trimmedContent ;
@@ -59,53 +60,79 @@ async function composeTweet(
59
60
}
60
61
}
61
62
62
- async function postTweet ( content : string ) : Promise < boolean > {
63
+ async function sendTweet ( twitterClient : Scraper , content : string ) {
64
+ const result = await twitterClient . sendTweet ( content ) ;
65
+
66
+ const body = await result . json ( ) ;
67
+ elizaLogger . log ( "Tweet response:" , body ) ;
68
+
69
+ // Check for Twitter API errors
70
+ if ( body . errors ) {
71
+ const error = body . errors [ 0 ] ;
72
+ elizaLogger . error (
73
+ `Twitter API error (${ error . code } ): ${ error . message } `
74
+ ) ;
75
+ return false ;
76
+ }
77
+
78
+ // Check for successful tweet creation
79
+ if ( ! body ?. data ?. create_tweet ?. tweet_results ?. result ) {
80
+ elizaLogger . error ( "Failed to post tweet: No tweet result in response" ) ;
81
+ return false ;
82
+ }
83
+
84
+ return true ;
85
+ }
86
+
87
+ async function postTweet (
88
+ runtime : IAgentRuntime ,
89
+ content : string
90
+ ) : Promise < boolean > {
63
91
try {
64
- const scraper = new Scraper ( ) ;
65
- const username = process . env . TWITTER_USERNAME ;
66
- const password = process . env . TWITTER_PASSWORD ;
67
- const email = process . env . TWITTER_EMAIL ;
68
- const twitter2faSecret = process . env . TWITTER_2FA_SECRET ;
92
+ const twitterClient = runtime . clients . twitter ?. client ?. twitterClient ;
93
+ const scraper = twitterClient || new Scraper ( ) ;
69
94
70
- if ( ! username || ! password ) {
71
- elizaLogger . error (
72
- "Twitter credentials not configured in environment"
73
- ) ;
74
- return false ;
75
- }
95
+ if ( ! twitterClient ) {
96
+ const username = runtime . getSetting ( "TWITTER_USERNAME" ) ;
97
+ const password = runtime . getSetting ( "TWITTER_PASSWORD" ) ;
98
+ const email = runtime . getSetting ( "TWITTER_EMAIL" ) ;
99
+ const twitter2faSecret = runtime . getSetting ( "TWITTER_2FA_SECRET" ) ;
76
100
77
- // Login with credentials
78
- await scraper . login ( username , password , email , twitter2faSecret ) ;
79
- if ( ! ( await scraper . isLoggedIn ( ) ) ) {
80
- elizaLogger . error ( "Failed to login to Twitter" ) ;
81
- return false ;
101
+ if ( ! username || ! password ) {
102
+ elizaLogger . error (
103
+ "Twitter credentials not configured in environment"
104
+ ) ;
105
+ return false ;
106
+ }
107
+ // Login with credentials
108
+ await scraper . login ( username , password , email , twitter2faSecret ) ;
109
+ if ( ! ( await scraper . isLoggedIn ( ) ) ) {
110
+ elizaLogger . error ( "Failed to login to Twitter" ) ;
111
+ return false ;
112
+ }
82
113
}
83
114
84
115
// Send the tweet
85
116
elizaLogger . log ( "Attempting to send tweet:" , content ) ;
86
- const result = await scraper . sendTweet ( content ) ;
87
-
88
- const body = await result . json ( ) ;
89
- elizaLogger . log ( "Tweet response:" , body ) ;
90
117
91
- // Check for Twitter API errors
92
- if ( body . errors ) {
93
- const error = body . errors [ 0 ] ;
94
- elizaLogger . error (
95
- `Twitter API error (${ error . code } ): ${ error . message } `
96
- ) ;
97
- return false ;
98
- }
99
-
100
- // Check for successful tweet creation
101
- if ( ! body ?. data ?. create_tweet ?. tweet_results ?. result ) {
102
- elizaLogger . error (
103
- "Failed to post tweet: No tweet result in response"
104
- ) ;
105
- return false ;
118
+ try {
119
+ if ( content . length > DEFAULT_MAX_TWEET_LENGTH ) {
120
+ const noteTweetResult = await scraper . sendNoteTweet ( content ) ;
121
+ if (
122
+ noteTweetResult . errors &&
123
+ noteTweetResult . errors . length > 0
124
+ ) {
125
+ // Note Tweet failed due to authorization. Falling back to standard Tweet.
126
+ return await sendTweet ( scraper , content ) ;
127
+ } else {
128
+ return true ;
129
+ }
130
+ } else {
131
+ return await sendTweet ( scraper , content ) ;
132
+ }
133
+ } catch ( error ) {
134
+ throw new Error ( `Note Tweet failed: ${ error } ` ) ;
106
135
}
107
-
108
- return true ;
109
136
} catch ( error ) {
110
137
// Log the full error details
111
138
elizaLogger . error ( "Error posting tweet:" , {
@@ -127,8 +154,10 @@ export const postAction: Action = {
127
154
message : Memory ,
128
155
state ?: State
129
156
) => {
130
- const hasCredentials =
131
- ! ! process . env . TWITTER_USERNAME && ! ! process . env . TWITTER_PASSWORD ;
157
+ const username = runtime . getSetting ( "TWITTER_USERNAME" ) ;
158
+ const password = runtime . getSetting ( "TWITTER_PASSWORD" ) ;
159
+ const email = runtime . getSetting ( "TWITTER_EMAIL" ) ;
160
+ const hasCredentials = ! ! username && ! ! password && ! ! email ;
132
161
elizaLogger . log ( `Has credentials: ${ hasCredentials } ` ) ;
133
162
134
163
return hasCredentials ;
@@ -160,7 +189,7 @@ export const postAction: Action = {
160
189
return true ;
161
190
}
162
191
163
- return await postTweet ( tweetContent ) ;
192
+ return await postTweet ( runtime , tweetContent ) ;
164
193
} catch ( error ) {
165
194
elizaLogger . error ( "Error in post action:" , error ) ;
166
195
return false ;
0 commit comments