Skip to content

Commit 2fcca62

Browse files
authored
Merge pull request #913 from tharak123455/twitterClient-enhancements
Twitter client enhancements
2 parents 06c1c41 + d89a879 commit 2fcca62

File tree

2 files changed

+113
-31
lines changed

2 files changed

+113
-31
lines changed

.env.example

+1
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ TWITTER_EMAIL= # Account email
5353
TWITTER_2FA_SECRET=
5454
TWITTER_COOKIES= # Account cookies
5555
TWITTER_POLL_INTERVAL=120 # How often (in seconds) the bot should check for interactions
56+
TWITTER_TARGET_USERS= # Comma separated list of Twitter user names to interact with
5657
X_SERVER_URL=
5758
XAI_API_KEY=
5859
XAI_MODEL=

packages/client-twitter/src/interactions.ts

+112-31
Original file line numberDiff line numberDiff line change
@@ -47,27 +47,31 @@ Thread of Tweets You Are Replying To:
4747
{{formattedConversation}}
4848
4949
{{actions}}
50-
5150
# Task: Generate a post in the voice, style and perspective of {{agentName}} (@{{twitterUserName}}). You MUST include an action if the current post text includes a prompt that is similar to one of the available actions mentioned here:
5251
{{actionNames}}
53-
54-
Here is the current post text again. Remember to include an action if the current post text includes a prompt that asks for one of the available actions mentioned above (does not need to be exact):
52+
Here is the current post text again. Remember to include an action if the current post text includes a prompt that asks for one of the available actions mentioned above (does not need to be exact)
5553
{{currentPost}}
5654
` + messageCompletionFooter;
5755

58-
export const twitterShouldRespondTemplate =
56+
export const twitterShouldRespondTemplate = (targetUsersStr: string) =>
5957
`# INSTRUCTIONS: Determine if {{agentName}} (@{{twitterUserName}}) should respond to the message and participate in the conversation. Do not comment. Just respond with "true" or "false".
6058
61-
Response options are RESPOND, IGNORE and STOP .
59+
Response options are RESPOND, IGNORE and STOP.
60+
61+
PRIORITY RULE: ALWAYS RESPOND to these users regardless of topic or message content: ${targetUsersStr}. Topic relevance should be ignored for these users.
6262
63-
{{agentName}} should respond to messages that are directed at them, or participate in conversations that are interesting or relevant to their background, IGNORE messages that are irrelevant to them, and should STOP if the conversation is concluded.
63+
For other users:
64+
- {{agentName}} should RESPOND to messages directed at them
65+
- {{agentName}} should RESPOND to conversations relevant to their background
66+
- {{agentName}} should IGNORE irrelevant messages
67+
- {{agentName}} should IGNORE very short messages unless directly addressed
68+
- {{agentName}} should STOP if asked to stop
69+
- {{agentName}} should STOP if conversation is concluded
70+
- {{agentName}} is in a room with other users and wants to be conversational, but not annoying.
6471
65-
{{agentName}} is in a room with other users and wants to be conversational, but not annoying.
66-
{{agentName}} must RESPOND to messages that are directed at them, a command towards them, or participate in conversations that are interesting or relevant to their background.
67-
If a message is not interesting or relevant, {{agentName}} should IGNORE.
68-
Unless directly RESPONDing to a user, {{agentName}} should IGNORE messages that are very short or do not contain much information.
69-
If a user asks {{agentName}} to stop talking, {{agentName}} should STOP.
70-
If {{agentName}} concludes a conversation and isn't part of the conversation anymore, {{agentName}} should STOP.
72+
{{recentPosts}}
73+
74+
IMPORTANT: For users not in the priority list, {{agentName}} (@{{twitterUserName}}) should err on the side of IGNORE rather than RESPOND if in doubt.
7175
7276
{{recentPosts}}
7377
@@ -106,20 +110,89 @@ export class TwitterInteractionClient {
106110

107111
async handleTwitterInteractions() {
108112
elizaLogger.log("Checking Twitter interactions");
113+
// Read from environment variable, fallback to default list if not set
114+
const targetUsersStr = this.runtime.getSetting("TWITTER_TARGET_USERS");
115+
116+
const twitterUsername = this.client.profile.username;
117+
try {
118+
// Check for mentions
119+
const mentionCandidates = (
120+
await this.client.fetchSearchTweets(
121+
`@${twitterUsername}`,
122+
20,
123+
SearchMode.Latest
124+
)
125+
).tweets;
126+
127+
elizaLogger.log("Completed checking mentioned tweets:", mentionCandidates.length);
128+
let uniqueTweetCandidates = [...mentionCandidates];
129+
// Only process target users if configured
130+
if (targetUsersStr && targetUsersStr.trim()) {
131+
const TARGET_USERS = targetUsersStr.split(',')
132+
.map(u => u.trim())
133+
.filter(u => u.length > 0); // Filter out empty strings after split
134+
135+
elizaLogger.log("Processing target users:", TARGET_USERS);
136+
137+
if (TARGET_USERS.length > 0) {
138+
// Create a map to store tweets by user
139+
const tweetsByUser = new Map<string, Tweet[]>();
140+
141+
// Fetch tweets from all target users
142+
for (const username of TARGET_USERS) {
143+
try {
144+
const userTweets = (await this.client.twitterClient.fetchSearchTweets(
145+
`from:${username}`,
146+
3,
147+
SearchMode.Latest
148+
)).tweets;
149+
150+
// Filter for unprocessed, non-reply, recent tweets
151+
const validTweets = userTweets.filter(tweet => {
152+
const isUnprocessed = !this.client.lastCheckedTweetId ||
153+
parseInt(tweet.id) > this.client.lastCheckedTweetId;
154+
const isRecent = (Date.now() - (tweet.timestamp * 1000)) < 2 * 60 * 60 * 1000;
155+
156+
elizaLogger.log(`Tweet ${tweet.id} checks:`, {
157+
isUnprocessed,
158+
isRecent,
159+
isReply: tweet.isReply,
160+
isRetweet: tweet.isRetweet
161+
});
162+
163+
return isUnprocessed && !tweet.isReply && !tweet.isRetweet && isRecent;
164+
});
165+
166+
if (validTweets.length > 0) {
167+
tweetsByUser.set(username, validTweets);
168+
elizaLogger.log(`Found ${validTweets.length} valid tweets from ${username}`);
169+
}
170+
} catch (error) {
171+
elizaLogger.error(`Error fetching tweets for ${username}:`, error);
172+
continue;
173+
}
174+
}
175+
176+
// Select one tweet from each user that has tweets
177+
const selectedTweets: Tweet[] = [];
178+
for (const [username, tweets] of tweetsByUser) {
179+
if (tweets.length > 0) {
180+
// Randomly select one tweet from this user
181+
const randomTweet = tweets[Math.floor(Math.random() * tweets.length)];
182+
selectedTweets.push(randomTweet);
183+
elizaLogger.log(`Selected tweet from ${username}: ${randomTweet.text?.substring(0, 100)}`);
184+
}
185+
}
186+
187+
// Add selected tweets to candidates
188+
uniqueTweetCandidates = [...mentionCandidates, ...selectedTweets];
189+
}
190+
} else {
191+
elizaLogger.log("No target users configured, processing only mentions");
192+
}
193+
194+
109195

110-
const twitterUsername = this.client.profile.username;
111-
try {
112-
// Check for mentions
113-
const tweetCandidates = (
114-
await this.client.fetchSearchTweets(
115-
`@${twitterUsername}`,
116-
20,
117-
SearchMode.Latest
118-
)
119-
).tweets;
120-
121-
// de-duplicate tweetCandidates with a set
122-
const uniqueTweetCandidates = [...new Set(tweetCandidates)];
123196
// Sort tweet candidates by ID in ascending order
124197
uniqueTweetCandidates
125198
.sort((a, b) => a.id.localeCompare(b.id))
@@ -282,13 +355,22 @@ export class TwitterInteractionClient {
282355
this.client.saveRequestMessage(message, state);
283356
}
284357

358+
// 1. Get the raw target users string from settings
359+
const targetUsersStr = this.runtime.getSetting("TWITTER_TARGET_USERS");
360+
361+
// 2. Process the string to get valid usernames
362+
const validTargetUsersStr = targetUsersStr && targetUsersStr.trim()
363+
? targetUsersStr.split(',') // Split by commas: "user1,user2" -> ["user1", "user2"]
364+
.map(u => u.trim()) // Remove whitespace: [" user1 ", "user2 "] -> ["user1", "user2"]
365+
.filter(u => u.length > 0)
366+
.join(',')
367+
: '';
368+
285369
const shouldRespondContext = composeContext({
286370
state,
287-
template:
288-
this.runtime.character.templates
289-
?.twitterShouldRespondTemplate ||
290-
this.runtime.character?.templates?.shouldRespondTemplate ||
291-
twitterShouldRespondTemplate,
371+
template: this.runtime.character.templates?.twitterShouldRespondTemplate?.(validTargetUsersStr) ||
372+
this.runtime.character?.templates?.shouldRespondTemplate ||
373+
twitterShouldRespondTemplate(validTargetUsersStr),
292374
});
293375

294376
const shouldRespond = await generateShouldRespond({
@@ -362,7 +444,6 @@ export class TwitterInteractionClient {
362444
);
363445
}
364446

365-
await this.runtime.evaluate(message, state);
366447

367448
await this.runtime.processActions(
368449
message,

0 commit comments

Comments
 (0)