@elizaos/plugin-twitter 1.2.8 → 1.2.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +402 -200
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -452,7 +452,10 @@ async function followUser(username, auth) {
|
|
|
452
452
|
if (!meResponse.data) {
|
|
453
453
|
throw new Error("Failed to get authenticated user");
|
|
454
454
|
}
|
|
455
|
-
const result = await client.v2.follow(
|
|
455
|
+
const result = await client.v2.follow(
|
|
456
|
+
meResponse.data.id,
|
|
457
|
+
userResponse.data.id
|
|
458
|
+
);
|
|
456
459
|
return new Response(JSON.stringify(result), {
|
|
457
460
|
status: result.data?.following ? 200 : 400,
|
|
458
461
|
headers: new Headers({ "Content-Type": "application/json" })
|
|
@@ -748,7 +751,9 @@ async function fetchTweetsAndReplies(userId, maxTweets, cursor, auth) {
|
|
|
748
751
|
next: response.meta.next_token
|
|
749
752
|
};
|
|
750
753
|
} catch (error) {
|
|
751
|
-
throw new Error(
|
|
754
|
+
throw new Error(
|
|
755
|
+
`Failed to fetch tweets and replies: ${error?.message || error}`
|
|
756
|
+
);
|
|
752
757
|
}
|
|
753
758
|
}
|
|
754
759
|
async function createCreateTweetRequestV2(text, auth, tweetId, options) {
|
|
@@ -822,9 +827,15 @@ function parseTweetV2ToV1(tweetV2, includes) {
|
|
|
822
827
|
isReply: tweetV2.referenced_tweets?.some((ref) => ref.type === "replied_to") ?? false,
|
|
823
828
|
isRetweet: tweetV2.referenced_tweets?.some((ref) => ref.type === "retweeted") ?? false,
|
|
824
829
|
isQuoted: tweetV2.referenced_tweets?.some((ref) => ref.type === "quoted") ?? false,
|
|
825
|
-
inReplyToStatusId: tweetV2.referenced_tweets?.find(
|
|
826
|
-
|
|
827
|
-
|
|
830
|
+
inReplyToStatusId: tweetV2.referenced_tweets?.find(
|
|
831
|
+
(ref) => ref.type === "replied_to"
|
|
832
|
+
)?.id,
|
|
833
|
+
quotedStatusId: tweetV2.referenced_tweets?.find(
|
|
834
|
+
(ref) => ref.type === "quoted"
|
|
835
|
+
)?.id,
|
|
836
|
+
retweetedStatusId: tweetV2.referenced_tweets?.find(
|
|
837
|
+
(ref) => ref.type === "retweeted"
|
|
838
|
+
)?.id
|
|
828
839
|
};
|
|
829
840
|
if (includes?.polls?.length) {
|
|
830
841
|
const poll = includes.polls[0];
|
|
@@ -976,7 +987,12 @@ async function* getTweets(user, maxTweets, auth) {
|
|
|
976
987
|
let cursor;
|
|
977
988
|
let totalFetched = 0;
|
|
978
989
|
while (totalFetched < maxTweets) {
|
|
979
|
-
const response = await fetchTweets(
|
|
990
|
+
const response = await fetchTweets(
|
|
991
|
+
userId,
|
|
992
|
+
maxTweets - totalFetched,
|
|
993
|
+
cursor,
|
|
994
|
+
auth
|
|
995
|
+
);
|
|
980
996
|
for (const tweet of response.tweets) {
|
|
981
997
|
yield tweet;
|
|
982
998
|
totalFetched++;
|
|
@@ -990,7 +1006,12 @@ async function* getTweetsByUserId(userId, maxTweets, auth) {
|
|
|
990
1006
|
let cursor;
|
|
991
1007
|
let totalFetched = 0;
|
|
992
1008
|
while (totalFetched < maxTweets) {
|
|
993
|
-
const response = await fetchTweets(
|
|
1009
|
+
const response = await fetchTweets(
|
|
1010
|
+
userId,
|
|
1011
|
+
maxTweets - totalFetched,
|
|
1012
|
+
cursor,
|
|
1013
|
+
auth
|
|
1014
|
+
);
|
|
994
1015
|
for (const tweet of response.tweets) {
|
|
995
1016
|
yield tweet;
|
|
996
1017
|
totalFetched++;
|
|
@@ -1009,7 +1030,12 @@ async function* getTweetsAndReplies(user, maxTweets, auth) {
|
|
|
1009
1030
|
let cursor;
|
|
1010
1031
|
let totalFetched = 0;
|
|
1011
1032
|
while (totalFetched < maxTweets) {
|
|
1012
|
-
const response = await fetchTweetsAndReplies(
|
|
1033
|
+
const response = await fetchTweetsAndReplies(
|
|
1034
|
+
userId,
|
|
1035
|
+
maxTweets - totalFetched,
|
|
1036
|
+
cursor,
|
|
1037
|
+
auth
|
|
1038
|
+
);
|
|
1013
1039
|
for (const tweet of response.tweets) {
|
|
1014
1040
|
yield tweet;
|
|
1015
1041
|
totalFetched++;
|
|
@@ -1023,7 +1049,12 @@ async function* getTweetsAndRepliesByUserId(userId, maxTweets, auth) {
|
|
|
1023
1049
|
let cursor;
|
|
1024
1050
|
let totalFetched = 0;
|
|
1025
1051
|
while (totalFetched < maxTweets) {
|
|
1026
|
-
const response = await fetchTweetsAndReplies(
|
|
1052
|
+
const response = await fetchTweetsAndReplies(
|
|
1053
|
+
userId,
|
|
1054
|
+
maxTweets - totalFetched,
|
|
1055
|
+
cursor,
|
|
1056
|
+
auth
|
|
1057
|
+
);
|
|
1027
1058
|
for (const tweet of response.tweets) {
|
|
1028
1059
|
yield tweet;
|
|
1029
1060
|
totalFetched++;
|
|
@@ -1115,10 +1146,7 @@ async function getTweetV2(id, auth, options = defaultOptions) {
|
|
|
1115
1146
|
console.warn(`Tweet data not found for ID: ${id}`);
|
|
1116
1147
|
return null;
|
|
1117
1148
|
}
|
|
1118
|
-
const parsedTweet = parseTweetV2ToV1(
|
|
1119
|
-
tweetData.data,
|
|
1120
|
-
tweetData?.includes
|
|
1121
|
-
);
|
|
1149
|
+
const parsedTweet = parseTweetV2ToV1(tweetData.data, tweetData?.includes);
|
|
1122
1150
|
return parsedTweet;
|
|
1123
1151
|
} catch (error) {
|
|
1124
1152
|
console.error(`Error fetching tweet ${id}:`, error);
|
|
@@ -6098,18 +6126,49 @@ async function validateTwitterConfig(runtime, config = {}) {
|
|
|
6098
6126
|
TWITTER_API_SECRET_KEY: config.TWITTER_API_SECRET_KEY ?? getSetting(runtime, "TWITTER_API_SECRET_KEY") ?? "",
|
|
6099
6127
|
TWITTER_ACCESS_TOKEN: config.TWITTER_ACCESS_TOKEN ?? getSetting(runtime, "TWITTER_ACCESS_TOKEN") ?? "",
|
|
6100
6128
|
TWITTER_ACCESS_TOKEN_SECRET: config.TWITTER_ACCESS_TOKEN_SECRET ?? getSetting(runtime, "TWITTER_ACCESS_TOKEN_SECRET") ?? "",
|
|
6101
|
-
TWITTER_DRY_RUN: String(
|
|
6129
|
+
TWITTER_DRY_RUN: String(
|
|
6130
|
+
(config.TWITTER_DRY_RUN ?? getSetting(runtime, "TWITTER_DRY_RUN") ?? "false").toLowerCase() === "true"
|
|
6131
|
+
),
|
|
6102
6132
|
TWITTER_TARGET_USERS: config.TWITTER_TARGET_USERS ?? getSetting(runtime, "TWITTER_TARGET_USERS") ?? "",
|
|
6103
|
-
TWITTER_ENABLE_POST: String(
|
|
6133
|
+
TWITTER_ENABLE_POST: String(
|
|
6134
|
+
(config.TWITTER_ENABLE_POST ?? getSetting(runtime, "TWITTER_ENABLE_POST") ?? "false").toLowerCase() === "true"
|
|
6135
|
+
),
|
|
6104
6136
|
TWITTER_ENABLE_REPLIES: String(
|
|
6105
6137
|
config.TWITTER_ENABLE_REPLIES !== void 0 ? config.TWITTER_ENABLE_REPLIES.toLowerCase() === "true" : (getSetting(runtime, "TWITTER_ENABLE_REPLIES") ?? "true").toLowerCase() === "true"
|
|
6106
6138
|
),
|
|
6107
|
-
TWITTER_ENABLE_ACTIONS: String(
|
|
6108
|
-
|
|
6109
|
-
|
|
6110
|
-
|
|
6111
|
-
|
|
6112
|
-
|
|
6139
|
+
TWITTER_ENABLE_ACTIONS: String(
|
|
6140
|
+
(config.TWITTER_ENABLE_ACTIONS ?? getSetting(runtime, "TWITTER_ENABLE_ACTIONS") ?? "false").toLowerCase() === "true"
|
|
6141
|
+
),
|
|
6142
|
+
TWITTER_POST_INTERVAL: String(
|
|
6143
|
+
safeParseInt(
|
|
6144
|
+
config.TWITTER_POST_INTERVAL ?? getSetting(runtime, "TWITTER_POST_INTERVAL"),
|
|
6145
|
+
120
|
|
6146
|
+
)
|
|
6147
|
+
),
|
|
6148
|
+
TWITTER_ENGAGEMENT_INTERVAL: String(
|
|
6149
|
+
safeParseInt(
|
|
6150
|
+
config.TWITTER_ENGAGEMENT_INTERVAL ?? getSetting(runtime, "TWITTER_ENGAGEMENT_INTERVAL"),
|
|
6151
|
+
30
|
|
6152
|
+
)
|
|
6153
|
+
),
|
|
6154
|
+
TWITTER_MAX_ENGAGEMENTS_PER_RUN: String(
|
|
6155
|
+
safeParseInt(
|
|
6156
|
+
config.TWITTER_MAX_ENGAGEMENTS_PER_RUN ?? getSetting(runtime, "TWITTER_MAX_ENGAGEMENTS_PER_RUN"),
|
|
6157
|
+
10
|
|
6158
|
+
)
|
|
6159
|
+
),
|
|
6160
|
+
TWITTER_MAX_TWEET_LENGTH: String(
|
|
6161
|
+
safeParseInt(
|
|
6162
|
+
config.TWITTER_MAX_TWEET_LENGTH ?? getSetting(runtime, "TWITTER_MAX_TWEET_LENGTH"),
|
|
6163
|
+
280
|
|
6164
|
+
)
|
|
6165
|
+
),
|
|
6166
|
+
TWITTER_RETRY_LIMIT: String(
|
|
6167
|
+
safeParseInt(
|
|
6168
|
+
config.TWITTER_RETRY_LIMIT ?? getSetting(runtime, "TWITTER_RETRY_LIMIT"),
|
|
6169
|
+
5
|
|
6170
|
+
)
|
|
6171
|
+
)
|
|
6113
6172
|
};
|
|
6114
6173
|
if (!validatedConfig.TWITTER_API_KEY || !validatedConfig.TWITTER_API_SECRET_KEY || !validatedConfig.TWITTER_ACCESS_TOKEN || !validatedConfig.TWITTER_ACCESS_TOKEN_SECRET) {
|
|
6115
6174
|
throw new Error(
|
|
@@ -6161,7 +6220,9 @@ var TwitterInteractionClient = class {
|
|
|
6161
6220
|
this.state?.TWITTER_ENGAGEMENT_INTERVAL || getSetting(this.runtime, "TWITTER_ENGAGEMENT_INTERVAL") || process.env.TWITTER_ENGAGEMENT_INTERVAL || "30"
|
|
6162
6221
|
);
|
|
6163
6222
|
const interactionInterval = engagementIntervalMinutes * 60 * 1e3;
|
|
6164
|
-
logger3.info(
|
|
6223
|
+
logger3.info(
|
|
6224
|
+
`Twitter interaction client will check every ${engagementIntervalMinutes} minutes`
|
|
6225
|
+
);
|
|
6165
6226
|
this.handleTwitterInteractions();
|
|
6166
6227
|
if (this.isRunning) {
|
|
6167
6228
|
setTimeout(handleTwitterInteractionsLoop, interactionInterval);
|
|
@@ -6230,7 +6291,9 @@ var TwitterInteractionClient = class {
|
|
|
6230
6291
|
if (targetUsers.length === 0 && !targetUsersConfig.includes("*")) {
|
|
6231
6292
|
return;
|
|
6232
6293
|
}
|
|
6233
|
-
logger3.info(
|
|
6294
|
+
logger3.info(
|
|
6295
|
+
`Checking posts from target users: ${targetUsers.join(", ") || "everyone (*)"}`
|
|
6296
|
+
);
|
|
6234
6297
|
for (const targetUser of targetUsers) {
|
|
6235
6298
|
try {
|
|
6236
6299
|
const normalizedUsername = targetUser.replace(/^@/, "");
|
|
@@ -6242,8 +6305,13 @@ var TwitterInteractionClient = class {
|
|
|
6242
6305
|
1 /* Latest */
|
|
6243
6306
|
);
|
|
6244
6307
|
if (searchResult.tweets.length > 0) {
|
|
6245
|
-
logger3.info(
|
|
6246
|
-
|
|
6308
|
+
logger3.info(
|
|
6309
|
+
`Found ${searchResult.tweets.length} posts from @${normalizedUsername}`
|
|
6310
|
+
);
|
|
6311
|
+
await this.processTargetUserTweets(
|
|
6312
|
+
searchResult.tweets,
|
|
6313
|
+
normalizedUsername
|
|
6314
|
+
);
|
|
6247
6315
|
}
|
|
6248
6316
|
} catch (error) {
|
|
6249
6317
|
logger3.error(`Error searching posts from @${targetUser}:`, error);
|
|
@@ -6281,7 +6349,9 @@ var TwitterInteractionClient = class {
|
|
|
6281
6349
|
}
|
|
6282
6350
|
const shouldEngage = await this.shouldEngageWithTweet(tweet);
|
|
6283
6351
|
if (shouldEngage) {
|
|
6284
|
-
logger3.info(
|
|
6352
|
+
logger3.info(
|
|
6353
|
+
`Engaging with tweet from @${username}: ${tweet.text.substring(0, 50)}...`
|
|
6354
|
+
);
|
|
6285
6355
|
await this.ensureTweetContext(tweet);
|
|
6286
6356
|
const engaged = await this.engageWithTweet(tweet);
|
|
6287
6357
|
if (engaged) {
|
|
@@ -6305,7 +6375,9 @@ var TwitterInteractionClient = class {
|
|
|
6305
6375
|
return tweetAge < 12 * 60 * 60 * 1e3;
|
|
6306
6376
|
});
|
|
6307
6377
|
if (relevantTweets.length > 0) {
|
|
6308
|
-
logger3.info(
|
|
6378
|
+
logger3.info(
|
|
6379
|
+
`Found ${relevantTweets.length} relevant tweets from timeline`
|
|
6380
|
+
);
|
|
6309
6381
|
await this.processTargetUserTweets(relevantTweets, "timeline");
|
|
6310
6382
|
}
|
|
6311
6383
|
} catch (error) {
|
|
@@ -6476,8 +6548,13 @@ Response (YES/NO):`;
|
|
|
6476
6548
|
const maxInteractionsPerRun = parseInt(
|
|
6477
6549
|
getSetting(this.runtime, "TWITTER_MAX_ENGAGEMENTS_PER_RUN") || process.env.TWITTER_MAX_ENGAGEMENTS_PER_RUN || "10"
|
|
6478
6550
|
);
|
|
6479
|
-
const tweetsToProcess = uniqueTweetCandidates.slice(
|
|
6480
|
-
|
|
6551
|
+
const tweetsToProcess = uniqueTweetCandidates.slice(
|
|
6552
|
+
0,
|
|
6553
|
+
maxInteractionsPerRun
|
|
6554
|
+
);
|
|
6555
|
+
logger3.info(
|
|
6556
|
+
`Processing ${tweetsToProcess.length} of ${uniqueTweetCandidates.length} mention tweets (max: ${maxInteractionsPerRun})`
|
|
6557
|
+
);
|
|
6481
6558
|
for (const tweet of tweetsToProcess) {
|
|
6482
6559
|
if (!this.client.lastCheckedTweetId || BigInt(tweet.id) > this.client.lastCheckedTweetId) {
|
|
6483
6560
|
const tweetId = createUniqueUuid2(this.runtime, tweet.id);
|
|
@@ -6819,7 +6896,8 @@ Response (YES/NO):`;
|
|
|
6819
6896
|
import {
|
|
6820
6897
|
ChannelType as ChannelType2,
|
|
6821
6898
|
createUniqueUuid as createUniqueUuid3,
|
|
6822
|
-
logger as logger4
|
|
6899
|
+
logger as logger4,
|
|
6900
|
+
ModelType as ModelType2
|
|
6823
6901
|
} from "@elizaos/core";
|
|
6824
6902
|
var TwitterPostClient = class {
|
|
6825
6903
|
/**
|
|
@@ -6871,11 +6949,11 @@ var TwitterPostClient = class {
|
|
|
6871
6949
|
}
|
|
6872
6950
|
};
|
|
6873
6951
|
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
6874
|
-
const postImmediately =
|
|
6875
|
-
|
|
6876
|
-
|
|
6877
|
-
|
|
6878
|
-
|
|
6952
|
+
const postImmediately = this.state?.TWITTER_POST_IMMEDIATELY || getSetting(this.runtime, "TWITTER_POST_IMMEDIATELY") || process.env.TWITTER_POST_IMMEDIATELY;
|
|
6953
|
+
if (postImmediately === "true" || postImmediately === true) {
|
|
6954
|
+
logger4.info(
|
|
6955
|
+
"TWITTER_POST_IMMEDIATELY is true, generating initial tweet now"
|
|
6956
|
+
);
|
|
6879
6957
|
await this.generateNewTweet();
|
|
6880
6958
|
}
|
|
6881
6959
|
generateNewTweetLoop();
|
|
@@ -6892,70 +6970,85 @@ var TwitterPostClient = class {
|
|
|
6892
6970
|
logger4.error("Cannot generate tweet: Twitter profile not available");
|
|
6893
6971
|
return;
|
|
6894
6972
|
}
|
|
6895
|
-
logger4.info(
|
|
6973
|
+
logger4.info(
|
|
6974
|
+
`Generating tweet for user: ${this.client.profile?.username} (${userId})`
|
|
6975
|
+
);
|
|
6896
6976
|
const worldId = createUniqueUuid3(this.runtime, userId);
|
|
6897
6977
|
const roomId = createUniqueUuid3(this.runtime, `${userId}-home`);
|
|
6898
|
-
const
|
|
6899
|
-
|
|
6900
|
-
|
|
6901
|
-
|
|
6902
|
-
|
|
6903
|
-
|
|
6904
|
-
|
|
6905
|
-
|
|
6906
|
-
|
|
6907
|
-
|
|
6908
|
-
|
|
6909
|
-
|
|
6910
|
-
|
|
6911
|
-
|
|
6912
|
-
|
|
6913
|
-
|
|
6914
|
-
|
|
6915
|
-
|
|
6916
|
-
|
|
6917
|
-
|
|
6918
|
-
|
|
6919
|
-
|
|
6920
|
-
|
|
6921
|
-
|
|
6922
|
-
|
|
6923
|
-
id: postedTweetId,
|
|
6924
|
-
entityId: this.runtime.agentId,
|
|
6925
|
-
agentId: this.runtime.agentId,
|
|
6926
|
-
roomId,
|
|
6927
|
-
content: {
|
|
6928
|
-
...content,
|
|
6929
|
-
source: "twitter",
|
|
6930
|
-
channelType: ChannelType2.FEED,
|
|
6931
|
-
type: "post",
|
|
6932
|
-
metadata: {
|
|
6933
|
-
tweetId,
|
|
6934
|
-
postedAt: Date.now()
|
|
6935
|
-
}
|
|
6936
|
-
},
|
|
6937
|
-
createdAt: Date.now()
|
|
6938
|
-
};
|
|
6939
|
-
await this.runtime.createMemory(postedMemory, "messages");
|
|
6940
|
-
return [postedMemory];
|
|
6941
|
-
}
|
|
6942
|
-
return [];
|
|
6943
|
-
} catch (error) {
|
|
6944
|
-
logger4.error("Error in tweet generation callback:", error);
|
|
6945
|
-
return [];
|
|
6946
|
-
}
|
|
6947
|
-
};
|
|
6948
|
-
this.runtime.emitEvent(
|
|
6949
|
-
"TWITTER_POST_GENERATED" /* POST_GENERATED */,
|
|
6978
|
+
const state = await this.runtime.composeState({
|
|
6979
|
+
agentId: this.runtime.agentId,
|
|
6980
|
+
entityId: this.runtime.agentId,
|
|
6981
|
+
roomId,
|
|
6982
|
+
content: { text: "", type: "post" },
|
|
6983
|
+
createdAt: Date.now()
|
|
6984
|
+
});
|
|
6985
|
+
const tweetPrompt = `You are ${this.runtime.character.name}.
|
|
6986
|
+
${this.runtime.character.bio}
|
|
6987
|
+
|
|
6988
|
+
Generate a tweet that:
|
|
6989
|
+
- Reflects your personality and interests
|
|
6990
|
+
- Is engaging and authentic
|
|
6991
|
+
- Is between 50-280 characters
|
|
6992
|
+
- Does NOT include hashtags unless they're essential to the message
|
|
6993
|
+
- Does NOT include @mentions unless replying to someone
|
|
6994
|
+
|
|
6995
|
+
Your topics of interest: ${this.runtime.character.topics?.join(", ") || "general topics"}
|
|
6996
|
+
|
|
6997
|
+
Recent context:
|
|
6998
|
+
${state.recentMemories?.slice(0, 5).map((m) => m.content.text).join("\n") || "No recent context"}
|
|
6999
|
+
|
|
7000
|
+
Generate a single tweet:`;
|
|
7001
|
+
const generatedContent = await this.runtime.useModel(
|
|
7002
|
+
ModelType2.TEXT_SMALL,
|
|
6950
7003
|
{
|
|
6951
|
-
|
|
6952
|
-
|
|
6953
|
-
|
|
6954
|
-
roomId,
|
|
6955
|
-
source: "twitter"
|
|
7004
|
+
prompt: tweetPrompt,
|
|
7005
|
+
temperature: 0.8,
|
|
7006
|
+
maxTokens: 100
|
|
6956
7007
|
}
|
|
6957
7008
|
);
|
|
6958
|
-
|
|
7009
|
+
const tweetText = generatedContent.trim();
|
|
7010
|
+
if (!tweetText || tweetText.length === 0) {
|
|
7011
|
+
logger4.error("Generated empty tweet content");
|
|
7012
|
+
return;
|
|
7013
|
+
}
|
|
7014
|
+
if (tweetText.includes("Error: Missing")) {
|
|
7015
|
+
logger4.error("Error in generated content:", tweetText);
|
|
7016
|
+
return;
|
|
7017
|
+
}
|
|
7018
|
+
logger4.info(`Generated tweet: ${tweetText}`);
|
|
7019
|
+
if (this.isDryRun) {
|
|
7020
|
+
logger4.info(`[DRY RUN] Would post tweet: ${tweetText}`);
|
|
7021
|
+
return;
|
|
7022
|
+
}
|
|
7023
|
+
const result = await this.postToTwitter(tweetText, []);
|
|
7024
|
+
if (result === null) {
|
|
7025
|
+
logger4.info("Skipped posting duplicate tweet");
|
|
7026
|
+
return;
|
|
7027
|
+
}
|
|
7028
|
+
const tweetId = result.id;
|
|
7029
|
+
logger4.info(`Tweet posted successfully! ID: ${tweetId}`);
|
|
7030
|
+
if (result) {
|
|
7031
|
+
const postedTweetId = createUniqueUuid3(this.runtime, tweetId);
|
|
7032
|
+
const postedMemory = {
|
|
7033
|
+
id: postedTweetId,
|
|
7034
|
+
entityId: this.runtime.agentId,
|
|
7035
|
+
agentId: this.runtime.agentId,
|
|
7036
|
+
roomId,
|
|
7037
|
+
content: {
|
|
7038
|
+
text: tweetText,
|
|
7039
|
+
source: "twitter",
|
|
7040
|
+
channelType: ChannelType2.FEED,
|
|
7041
|
+
type: "post",
|
|
7042
|
+
metadata: {
|
|
7043
|
+
tweetId,
|
|
7044
|
+
postedAt: Date.now()
|
|
7045
|
+
}
|
|
7046
|
+
},
|
|
7047
|
+
createdAt: Date.now()
|
|
7048
|
+
};
|
|
7049
|
+
await this.runtime.createMemory(postedMemory, "messages");
|
|
7050
|
+
logger4.info("Tweet posted and saved to memory successfully");
|
|
7051
|
+
}
|
|
6959
7052
|
} catch (error) {
|
|
6960
7053
|
logger4.error("Error generating tweet:", error);
|
|
6961
7054
|
}
|
|
@@ -7014,7 +7107,7 @@ import {
|
|
|
7014
7107
|
ChannelType as ChannelType3,
|
|
7015
7108
|
composePromptFromState,
|
|
7016
7109
|
createUniqueUuid as createUniqueUuid4,
|
|
7017
|
-
ModelType as
|
|
7110
|
+
ModelType as ModelType3,
|
|
7018
7111
|
parseKeyValueXml
|
|
7019
7112
|
} from "@elizaos/core";
|
|
7020
7113
|
import { logger as logger5 } from "@elizaos/core";
|
|
@@ -7026,20 +7119,23 @@ var twitterActionTemplate = `
|
|
|
7026
7119
|
{{postDirections}}
|
|
7027
7120
|
|
|
7028
7121
|
Guidelines:
|
|
7029
|
-
-
|
|
7030
|
-
- Direct mentions
|
|
7031
|
-
-
|
|
7032
|
-
-
|
|
7033
|
-
-
|
|
7034
|
-
-
|
|
7035
|
-
-
|
|
7036
|
-
|
|
7122
|
+
- Engage with content that relates to character's interests and expertise
|
|
7123
|
+
- Direct mentions should be prioritized when relevant
|
|
7124
|
+
- Consider engaging with:
|
|
7125
|
+
- Content directly related to your topics
|
|
7126
|
+
- Interesting discussions you can contribute to
|
|
7127
|
+
- Questions you can help answer
|
|
7128
|
+
- Content from users you've interacted with before
|
|
7129
|
+
- Skip content that is:
|
|
7130
|
+
- Completely off-topic or spam
|
|
7131
|
+
- Inflammatory or highly controversial (unless it's your area)
|
|
7132
|
+
- Pure marketing/promotional with no value
|
|
7037
7133
|
|
|
7038
7134
|
Actions (respond only with tags):
|
|
7039
|
-
[LIKE] -
|
|
7040
|
-
[RETWEET] -
|
|
7041
|
-
[QUOTE] -
|
|
7042
|
-
[REPLY] -
|
|
7135
|
+
[LIKE] - Content is relevant and interesting (7/10 or higher)
|
|
7136
|
+
[RETWEET] - Content is valuable and worth sharing (8/10 or higher)
|
|
7137
|
+
[QUOTE] - You can add meaningful commentary (7.5/10 or higher)
|
|
7138
|
+
[REPLY] - You can contribute helpful insights (7/10 or higher)
|
|
7043
7139
|
`;
|
|
7044
7140
|
var quoteTweetTemplate = `# Task: Write a quote tweet in the voice, style, and perspective of {{agentName}} @{{twitterUserName}}.
|
|
7045
7141
|
|
|
@@ -7105,7 +7201,9 @@ var TwitterTimelineClient = class {
|
|
|
7105
7201
|
this.state?.TWITTER_ENGAGEMENT_INTERVAL || getSetting(this.runtime, "TWITTER_ENGAGEMENT_INTERVAL") || process.env.TWITTER_ENGAGEMENT_INTERVAL || "30"
|
|
7106
7202
|
);
|
|
7107
7203
|
const actionInterval = engagementIntervalMinutes * 60 * 1e3;
|
|
7108
|
-
logger5.info(
|
|
7204
|
+
logger5.info(
|
|
7205
|
+
`Timeline client will check every ${engagementIntervalMinutes} minutes`
|
|
7206
|
+
);
|
|
7109
7207
|
this.handleTimeline();
|
|
7110
7208
|
if (this.isRunning) {
|
|
7111
7209
|
setTimeout(handleTwitterTimelineLoop, actionInterval);
|
|
@@ -7173,7 +7271,7 @@ ${tweet.text}
|
|
|
7173
7271
|
|
|
7174
7272
|
Choose any combination of [LIKE], [RETWEET], [QUOTE], and [REPLY] that are appropriate. Each action must be on its own line. Your response must only include the chosen actions.`;
|
|
7175
7273
|
const actionResponse = await this.runtime.useModel(
|
|
7176
|
-
|
|
7274
|
+
ModelType3.TEXT_SMALL,
|
|
7177
7275
|
{
|
|
7178
7276
|
prompt: actionRespondPrompt
|
|
7179
7277
|
}
|
|
@@ -7227,7 +7325,12 @@ ${actionSummary.join("\n")}`);
|
|
|
7227
7325
|
}
|
|
7228
7326
|
async processTimelineActions(tweetDecisions) {
|
|
7229
7327
|
const results = [];
|
|
7230
|
-
for (const {
|
|
7328
|
+
for (const {
|
|
7329
|
+
tweet,
|
|
7330
|
+
actionResponse,
|
|
7331
|
+
tweetState,
|
|
7332
|
+
roomId
|
|
7333
|
+
} of tweetDecisions) {
|
|
7231
7334
|
const tweetId = this.createTweetId(this.runtime, tweet);
|
|
7232
7335
|
const executedActions = [];
|
|
7233
7336
|
await this.runtime.createMemory(
|
|
@@ -7345,13 +7448,15 @@ ${actionSummary.join("\n")}`);
|
|
|
7345
7448
|
}) + `
|
|
7346
7449
|
You are responding to this tweet:
|
|
7347
7450
|
${tweet.text}`;
|
|
7348
|
-
const quoteResponse = await this.runtime.useModel(
|
|
7451
|
+
const quoteResponse = await this.runtime.useModel(ModelType3.TEXT_SMALL, {
|
|
7349
7452
|
prompt: quotePrompt
|
|
7350
7453
|
});
|
|
7351
7454
|
const responseObject = parseKeyValueXml(quoteResponse);
|
|
7352
7455
|
if (responseObject.post) {
|
|
7353
7456
|
if (this.isDryRun) {
|
|
7354
|
-
logger5.log(
|
|
7457
|
+
logger5.log(
|
|
7458
|
+
`[DRY RUN] Would have quoted tweet ${tweet.id} with: ${responseObject.post}`
|
|
7459
|
+
);
|
|
7355
7460
|
return;
|
|
7356
7461
|
}
|
|
7357
7462
|
const result = await this.client.requestQueue.add(
|
|
@@ -7396,13 +7501,15 @@ ${tweet.text}`;
|
|
|
7396
7501
|
}) + `
|
|
7397
7502
|
You are replying to this tweet:
|
|
7398
7503
|
${tweet.text}`;
|
|
7399
|
-
const replyResponse = await this.runtime.useModel(
|
|
7504
|
+
const replyResponse = await this.runtime.useModel(ModelType3.TEXT_SMALL, {
|
|
7400
7505
|
prompt: replyPrompt
|
|
7401
7506
|
});
|
|
7402
7507
|
const responseObject = parseKeyValueXml(replyResponse);
|
|
7403
7508
|
if (responseObject.post) {
|
|
7404
7509
|
if (this.isDryRun) {
|
|
7405
|
-
logger5.log(
|
|
7510
|
+
logger5.log(
|
|
7511
|
+
`[DRY RUN] Would have replied to tweet ${tweet.id} with: ${responseObject.post}`
|
|
7512
|
+
);
|
|
7406
7513
|
return;
|
|
7407
7514
|
}
|
|
7408
7515
|
const result = await sendTweet(
|
|
@@ -7438,14 +7545,16 @@ ${tweet.text}`;
|
|
|
7438
7545
|
import {
|
|
7439
7546
|
createUniqueUuid as createUniqueUuid5,
|
|
7440
7547
|
logger as logger6,
|
|
7441
|
-
ModelType as
|
|
7548
|
+
ModelType as ModelType4
|
|
7442
7549
|
} from "@elizaos/core";
|
|
7443
7550
|
var TwitterDiscoveryClient = class {
|
|
7444
7551
|
constructor(client, runtime, state) {
|
|
7445
7552
|
this.isRunning = false;
|
|
7553
|
+
this.lastDiscoveryTime = 0;
|
|
7446
7554
|
this.client = client;
|
|
7447
7555
|
this.twitterClient = client.twitterClient;
|
|
7448
7556
|
this.runtime = runtime;
|
|
7557
|
+
this.state = state;
|
|
7449
7558
|
const dryRunSetting = state?.TWITTER_DRY_RUN ?? getSetting(this.runtime, "TWITTER_DRY_RUN") ?? process.env.TWITTER_DRY_RUN;
|
|
7450
7559
|
this.isDryRun = dryRunSetting === true || dryRunSetting === "true" || typeof dryRunSetting === "string" && dryRunSetting.toLowerCase() === "true";
|
|
7451
7560
|
this.config = this.buildDiscoveryConfig();
|
|
@@ -7457,6 +7566,17 @@ var TwitterDiscoveryClient = class {
|
|
|
7457
7566
|
maxEngagementsPerCycle: this.config.maxEngagementsPerCycle
|
|
7458
7567
|
});
|
|
7459
7568
|
}
|
|
7569
|
+
/**
|
|
7570
|
+
* Sanitizes a topic for use in Twitter search queries
|
|
7571
|
+
* - Removes common stop words that might be interpreted as operators
|
|
7572
|
+
* - Handles special characters
|
|
7573
|
+
* - Simplifies complex phrases
|
|
7574
|
+
*/
|
|
7575
|
+
sanitizeTopic(topic) {
|
|
7576
|
+
let sanitized = topic.replace(/\band\b/gi, " ").replace(/\bor\b/gi, " ").replace(/\bnot\b/gi, " ").trim();
|
|
7577
|
+
sanitized = sanitized.replace(/\s+/g, " ");
|
|
7578
|
+
return sanitized.includes(" ") ? `"${sanitized}"` : sanitized;
|
|
7579
|
+
}
|
|
7460
7580
|
buildDiscoveryConfig() {
|
|
7461
7581
|
const character = this.runtime?.character;
|
|
7462
7582
|
const defaultTopics = [
|
|
@@ -7476,7 +7596,9 @@ var TwitterDiscoveryClient = class {
|
|
|
7476
7596
|
topics = this.extractTopicsFromBio(character.bio);
|
|
7477
7597
|
}
|
|
7478
7598
|
} else {
|
|
7479
|
-
logger6.warn(
|
|
7599
|
+
logger6.warn(
|
|
7600
|
+
"Character not available in runtime, using default topics for discovery"
|
|
7601
|
+
);
|
|
7480
7602
|
}
|
|
7481
7603
|
return {
|
|
7482
7604
|
topics,
|
|
@@ -7487,11 +7609,17 @@ var TwitterDiscoveryClient = class {
|
|
|
7487
7609
|
getSetting(this.runtime, "TWITTER_MAX_FOLLOWS_PER_CYCLE") || process.env.TWITTER_MAX_FOLLOWS_PER_CYCLE || "5"
|
|
7488
7610
|
),
|
|
7489
7611
|
maxEngagementsPerCycle: parseInt(
|
|
7490
|
-
getSetting(
|
|
7612
|
+
getSetting(
|
|
7613
|
+
this.runtime,
|
|
7614
|
+
"TWITTER_MAX_ENGAGEMENTS_PER_RUN"
|
|
7615
|
+
) || process.env.TWITTER_MAX_ENGAGEMENTS_PER_RUN || "10"
|
|
7491
7616
|
),
|
|
7492
|
-
likeThreshold: 0.
|
|
7493
|
-
|
|
7494
|
-
|
|
7617
|
+
likeThreshold: 0.3,
|
|
7618
|
+
// Lowered from 0.6
|
|
7619
|
+
replyThreshold: 0.5,
|
|
7620
|
+
// Lowered from 0.8
|
|
7621
|
+
quoteThreshold: 0.7
|
|
7622
|
+
// Lowered from 0.85
|
|
7495
7623
|
};
|
|
7496
7624
|
}
|
|
7497
7625
|
extractTopicsFromBio(bio) {
|
|
@@ -7499,7 +7627,16 @@ var TwitterDiscoveryClient = class {
|
|
|
7499
7627
|
return [];
|
|
7500
7628
|
}
|
|
7501
7629
|
const bioText = Array.isArray(bio) ? bio.join(" ") : bio;
|
|
7502
|
-
const words = bioText.toLowerCase().split(/\s+/).filter((word) => word.length > 4).filter(
|
|
7630
|
+
const words = bioText.toLowerCase().split(/\s+/).filter((word) => word.length > 4).filter(
|
|
7631
|
+
(word) => ![
|
|
7632
|
+
"about",
|
|
7633
|
+
"helping",
|
|
7634
|
+
"working",
|
|
7635
|
+
"people",
|
|
7636
|
+
"making",
|
|
7637
|
+
"building"
|
|
7638
|
+
].includes(word)
|
|
7639
|
+
);
|
|
7503
7640
|
return [...new Set(words)].slice(0, 5);
|
|
7504
7641
|
}
|
|
7505
7642
|
async start() {
|
|
@@ -7520,7 +7657,9 @@ var TwitterDiscoveryClient = class {
|
|
|
7520
7657
|
);
|
|
7521
7658
|
const variance = Math.random() * 20 - 10;
|
|
7522
7659
|
const nextInterval = (baseInterval + variance) * 60 * 1e3;
|
|
7523
|
-
logger6.info(
|
|
7660
|
+
logger6.info(
|
|
7661
|
+
`Next discovery cycle in ${(baseInterval + variance).toFixed(1)} minutes`
|
|
7662
|
+
);
|
|
7524
7663
|
if (this.isRunning) {
|
|
7525
7664
|
setTimeout(discoveryLoop, nextInterval);
|
|
7526
7665
|
}
|
|
@@ -7535,7 +7674,9 @@ var TwitterDiscoveryClient = class {
|
|
|
7535
7674
|
logger6.info("Starting Twitter discovery cycle...");
|
|
7536
7675
|
const discoveries = await this.discoverContent();
|
|
7537
7676
|
const { tweets, accounts } = discoveries;
|
|
7538
|
-
logger6.info(
|
|
7677
|
+
logger6.info(
|
|
7678
|
+
`Discovered ${tweets.length} tweets and ${accounts.length} accounts`
|
|
7679
|
+
);
|
|
7539
7680
|
const followedCount = await this.processAccounts(accounts);
|
|
7540
7681
|
const engagementCount = await this.processTweets(tweets);
|
|
7541
7682
|
logger6.info(
|
|
@@ -7548,9 +7689,7 @@ var TwitterDiscoveryClient = class {
|
|
|
7548
7689
|
try {
|
|
7549
7690
|
const topicContent = await this.discoverFromTopics();
|
|
7550
7691
|
allTweets.push(...topicContent.tweets);
|
|
7551
|
-
topicContent.accounts.forEach(
|
|
7552
|
-
(acc) => allAccounts.set(acc.user.id, acc)
|
|
7553
|
-
);
|
|
7692
|
+
topicContent.accounts.forEach((acc) => allAccounts.set(acc.user.id, acc));
|
|
7554
7693
|
} catch (error) {
|
|
7555
7694
|
logger6.error("Failed to discover from topics:", error);
|
|
7556
7695
|
}
|
|
@@ -7573,7 +7712,9 @@ var TwitterDiscoveryClient = class {
|
|
|
7573
7712
|
logger6.error("Failed to discover from popular accounts:", error);
|
|
7574
7713
|
}
|
|
7575
7714
|
const sortedTweets = allTweets.sort((a, b) => b.relevanceScore - a.relevanceScore).slice(0, 50);
|
|
7576
|
-
const sortedAccounts = Array.from(allAccounts.values()).sort(
|
|
7715
|
+
const sortedAccounts = Array.from(allAccounts.values()).sort(
|
|
7716
|
+
(a, b) => b.qualityScore * b.relevanceScore - a.qualityScore * a.relevanceScore
|
|
7717
|
+
).slice(0, 20);
|
|
7577
7718
|
return { tweets: sortedTweets, accounts: sortedAccounts };
|
|
7578
7719
|
}
|
|
7579
7720
|
async discoverFromTopics() {
|
|
@@ -7582,7 +7723,8 @@ var TwitterDiscoveryClient = class {
|
|
|
7582
7723
|
const accounts = /* @__PURE__ */ new Map();
|
|
7583
7724
|
for (const topic of this.config.topics.slice(0, 5)) {
|
|
7584
7725
|
try {
|
|
7585
|
-
const
|
|
7726
|
+
const searchTopic = this.sanitizeTopic(topic);
|
|
7727
|
+
const popularQuery = `${searchTopic} -is:retweet -is:reply lang:en`;
|
|
7586
7728
|
logger6.debug(`Searching popular tweets for topic: ${topic}`);
|
|
7587
7729
|
const popularResults = await this.twitterClient.fetchSearchTweets(
|
|
7588
7730
|
popularQuery,
|
|
@@ -7594,7 +7736,7 @@ var TwitterDiscoveryClient = class {
|
|
|
7594
7736
|
const scored = this.scoreTweet(tweet, "topic");
|
|
7595
7737
|
tweets.push(scored);
|
|
7596
7738
|
}
|
|
7597
|
-
const verifiedQuery = `${
|
|
7739
|
+
const verifiedQuery = `${searchTopic} -is:retweet lang:en is:verified`;
|
|
7598
7740
|
logger6.debug(`Searching verified accounts for topic: ${topic}`);
|
|
7599
7741
|
const verifiedResults = await this.twitterClient.fetchSearchTweets(
|
|
7600
7742
|
verifiedQuery,
|
|
@@ -7627,9 +7769,9 @@ var TwitterDiscoveryClient = class {
|
|
|
7627
7769
|
logger6.debug("Discovering from conversation threads...");
|
|
7628
7770
|
const tweets = [];
|
|
7629
7771
|
const accounts = /* @__PURE__ */ new Map();
|
|
7630
|
-
const topicQuery = this.config.topics.slice(0, 3).map((t) =>
|
|
7772
|
+
const topicQuery = this.config.topics.slice(0, 3).map((t) => this.sanitizeTopic(t)).join(" OR ");
|
|
7631
7773
|
try {
|
|
7632
|
-
const viralQuery =
|
|
7774
|
+
const viralQuery = `(${topicQuery}) -is:retweet has:mentions`;
|
|
7633
7775
|
logger6.debug(`Searching viral threads with query: ${viralQuery}`);
|
|
7634
7776
|
const searchResults = await this.twitterClient.fetchSearchTweets(
|
|
7635
7777
|
viralQuery,
|
|
@@ -7638,17 +7780,17 @@ var TwitterDiscoveryClient = class {
|
|
|
7638
7780
|
);
|
|
7639
7781
|
for (const tweet of searchResults.tweets) {
|
|
7640
7782
|
const engagementScore = (tweet.likes || 0) + (tweet.retweets || 0) * 2;
|
|
7641
|
-
if (engagementScore <
|
|
7783
|
+
if (engagementScore < 10) continue;
|
|
7642
7784
|
const scored = this.scoreTweet(tweet, "thread");
|
|
7643
7785
|
tweets.push(scored);
|
|
7644
7786
|
const account = this.scoreAccount({
|
|
7645
7787
|
id: tweet.userId,
|
|
7646
7788
|
username: tweet.username,
|
|
7647
7789
|
name: tweet.name || tweet.username,
|
|
7648
|
-
followersCount:
|
|
7649
|
-
//
|
|
7790
|
+
followersCount: 1e3
|
|
7791
|
+
// Reasonable estimate for engaged users
|
|
7650
7792
|
});
|
|
7651
|
-
if (account.qualityScore > 0.
|
|
7793
|
+
if (account.qualityScore > 0.5) {
|
|
7652
7794
|
accounts.set(tweet.userId, account);
|
|
7653
7795
|
}
|
|
7654
7796
|
}
|
|
@@ -7663,7 +7805,8 @@ var TwitterDiscoveryClient = class {
|
|
|
7663
7805
|
const accounts = /* @__PURE__ */ new Map();
|
|
7664
7806
|
for (const topic of this.config.topics.slice(0, 3)) {
|
|
7665
7807
|
try {
|
|
7666
|
-
const
|
|
7808
|
+
const searchTopic = this.sanitizeTopic(topic);
|
|
7809
|
+
const influencerQuery = `${searchTopic} -is:retweet lang:en`;
|
|
7667
7810
|
logger6.debug(`Searching for influencers in topic: ${topic}`);
|
|
7668
7811
|
const results = await this.twitterClient.fetchSearchTweets(
|
|
7669
7812
|
influencerQuery,
|
|
@@ -7672,7 +7815,7 @@ var TwitterDiscoveryClient = class {
|
|
|
7672
7815
|
);
|
|
7673
7816
|
for (const tweet of results.tweets) {
|
|
7674
7817
|
const engagement = (tweet.likes || 0) + (tweet.retweets || 0) * 2;
|
|
7675
|
-
if (engagement <
|
|
7818
|
+
if (engagement < 5) continue;
|
|
7676
7819
|
const scored = this.scoreTweet(tweet, "topic");
|
|
7677
7820
|
tweets.push(scored);
|
|
7678
7821
|
const estimatedFollowers = Math.max(
|
|
@@ -7691,7 +7834,10 @@ var TwitterDiscoveryClient = class {
|
|
|
7691
7834
|
}
|
|
7692
7835
|
}
|
|
7693
7836
|
} catch (error) {
|
|
7694
|
-
logger6.error(
|
|
7837
|
+
logger6.error(
|
|
7838
|
+
`Failed to discover popular accounts for ${topic}:`,
|
|
7839
|
+
error
|
|
7840
|
+
);
|
|
7695
7841
|
}
|
|
7696
7842
|
}
|
|
7697
7843
|
return { tweets, accounts: Array.from(accounts.values()) };
|
|
@@ -7701,12 +7847,15 @@ var TwitterDiscoveryClient = class {
|
|
|
7701
7847
|
scoreTweet(tweet, source) {
|
|
7702
7848
|
let relevanceScore = 0;
|
|
7703
7849
|
const sourceScores = {
|
|
7704
|
-
topic: 0.
|
|
7705
|
-
thread: 0.
|
|
7850
|
+
topic: 0.4,
|
|
7851
|
+
thread: 0.35
|
|
7706
7852
|
};
|
|
7707
7853
|
relevanceScore += sourceScores[source];
|
|
7708
7854
|
const engagementScore = Math.min(
|
|
7709
|
-
(tweet.likes || 0) /
|
|
7855
|
+
(tweet.likes || 0) / 100 + // 100 likes = 0.1 points (was 1000)
|
|
7856
|
+
(tweet.retweets || 0) / 50 + // 50 retweets = 0.1 points (was 500)
|
|
7857
|
+
(tweet.replies || 0) / 20,
|
|
7858
|
+
// 20 replies = 0.1 points (was 100)
|
|
7710
7859
|
0.3
|
|
7711
7860
|
);
|
|
7712
7861
|
relevanceScore += engagementScore;
|
|
@@ -7714,7 +7863,7 @@ var TwitterDiscoveryClient = class {
|
|
|
7714
7863
|
const topicMatches = this.config.topics.filter(
|
|
7715
7864
|
(topic) => textLower.includes(topic.toLowerCase())
|
|
7716
7865
|
).length;
|
|
7717
|
-
relevanceScore += Math.min(topicMatches * 0.
|
|
7866
|
+
relevanceScore += Math.min(topicMatches * 0.15, 0.3);
|
|
7718
7867
|
relevanceScore = Math.min(relevanceScore, 1);
|
|
7719
7868
|
let engagementType = "skip";
|
|
7720
7869
|
if (relevanceScore >= this.config.quoteThreshold) {
|
|
@@ -7768,7 +7917,10 @@ var TwitterDiscoveryClient = class {
|
|
|
7768
7917
|
followedCount++;
|
|
7769
7918
|
await this.delay(2e3 + Math.random() * 3e3);
|
|
7770
7919
|
} catch (error) {
|
|
7771
|
-
logger6.error(
|
|
7920
|
+
logger6.error(
|
|
7921
|
+
`Failed to follow @${scoredAccount.user.username}:`,
|
|
7922
|
+
error
|
|
7923
|
+
);
|
|
7772
7924
|
}
|
|
7773
7925
|
}
|
|
7774
7926
|
return followedCount;
|
|
@@ -7779,51 +7931,76 @@ var TwitterDiscoveryClient = class {
|
|
|
7779
7931
|
if (engagementCount >= this.config.maxEngagementsPerCycle) break;
|
|
7780
7932
|
if (scoredTweet.engagementType === "skip") continue;
|
|
7781
7933
|
try {
|
|
7782
|
-
const tweetMemoryId = createUniqueUuid5(
|
|
7934
|
+
const tweetMemoryId = createUniqueUuid5(
|
|
7935
|
+
this.runtime,
|
|
7936
|
+
scoredTweet.tweet.id
|
|
7937
|
+
);
|
|
7783
7938
|
const existingMemory = await this.runtime.getMemoryById(tweetMemoryId);
|
|
7784
7939
|
if (existingMemory) {
|
|
7785
|
-
logger6.debug(
|
|
7940
|
+
logger6.debug(
|
|
7941
|
+
`Already engaged with tweet ${scoredTweet.tweet.id}, skipping`
|
|
7942
|
+
);
|
|
7786
7943
|
continue;
|
|
7787
7944
|
}
|
|
7788
7945
|
switch (scoredTweet.engagementType) {
|
|
7789
7946
|
case "like":
|
|
7790
7947
|
if (this.isDryRun) {
|
|
7791
|
-
logger6.info(
|
|
7948
|
+
logger6.info(
|
|
7949
|
+
`[DRY RUN] Would like tweet: ${scoredTweet.tweet.id} (score: ${scoredTweet.relevanceScore.toFixed(2)})`
|
|
7950
|
+
);
|
|
7792
7951
|
} else {
|
|
7793
7952
|
await this.twitterClient.likeTweet(scoredTweet.tweet.id);
|
|
7794
|
-
logger6.info(
|
|
7953
|
+
logger6.info(
|
|
7954
|
+
`Liked tweet: ${scoredTweet.tweet.id} (score: ${scoredTweet.relevanceScore.toFixed(2)})`
|
|
7955
|
+
);
|
|
7795
7956
|
}
|
|
7796
7957
|
break;
|
|
7797
7958
|
case "reply":
|
|
7798
7959
|
const replyText = await this.generateReply(scoredTweet.tweet);
|
|
7799
7960
|
if (this.isDryRun) {
|
|
7800
|
-
logger6.info(
|
|
7961
|
+
logger6.info(
|
|
7962
|
+
`[DRY RUN] Would reply to tweet ${scoredTweet.tweet.id} with: "${replyText}"`
|
|
7963
|
+
);
|
|
7801
7964
|
} else {
|
|
7802
|
-
await this.twitterClient.sendTweet(
|
|
7965
|
+
await this.twitterClient.sendTweet(
|
|
7966
|
+
replyText,
|
|
7967
|
+
scoredTweet.tweet.id
|
|
7968
|
+
);
|
|
7803
7969
|
logger6.info(`Replied to tweet: ${scoredTweet.tweet.id}`);
|
|
7804
7970
|
}
|
|
7805
7971
|
break;
|
|
7806
7972
|
case "quote":
|
|
7807
7973
|
const quoteText = await this.generateQuote(scoredTweet.tweet);
|
|
7808
7974
|
if (this.isDryRun) {
|
|
7809
|
-
logger6.info(
|
|
7975
|
+
logger6.info(
|
|
7976
|
+
`[DRY RUN] Would quote tweet ${scoredTweet.tweet.id} with: "${quoteText}"`
|
|
7977
|
+
);
|
|
7810
7978
|
} else {
|
|
7811
|
-
await this.twitterClient.sendQuoteTweet(
|
|
7979
|
+
await this.twitterClient.sendQuoteTweet(
|
|
7980
|
+
quoteText,
|
|
7981
|
+
scoredTweet.tweet.id
|
|
7982
|
+
);
|
|
7812
7983
|
logger6.info(`Quoted tweet: ${scoredTweet.tweet.id}`);
|
|
7813
7984
|
}
|
|
7814
7985
|
break;
|
|
7815
7986
|
}
|
|
7816
|
-
await this.saveEngagementMemory(
|
|
7987
|
+
await this.saveEngagementMemory(
|
|
7988
|
+
scoredTweet.tweet,
|
|
7989
|
+
scoredTweet.engagementType
|
|
7990
|
+
);
|
|
7817
7991
|
engagementCount++;
|
|
7818
7992
|
await this.delay(3e3 + Math.random() * 5e3);
|
|
7819
7993
|
} catch (error) {
|
|
7820
|
-
logger6.error(
|
|
7994
|
+
logger6.error(
|
|
7995
|
+
`Failed to engage with tweet ${scoredTweet.tweet.id}:`,
|
|
7996
|
+
error
|
|
7997
|
+
);
|
|
7821
7998
|
}
|
|
7822
7999
|
}
|
|
7823
8000
|
return engagementCount;
|
|
7824
8001
|
}
|
|
7825
8002
|
async checkIfFollowing(userId) {
|
|
7826
|
-
const embedding = await this.runtime.useModel(
|
|
8003
|
+
const embedding = await this.runtime.useModel(ModelType4.TEXT_EMBEDDING, {
|
|
7827
8004
|
text: `followed twitter user ${userId}`
|
|
7828
8005
|
});
|
|
7829
8006
|
const followMemories = await this.runtime.searchMemories({
|
|
@@ -7859,7 +8036,7 @@ Keep the reply:
|
|
|
7859
8036
|
- Respectful and constructive
|
|
7860
8037
|
|
|
7861
8038
|
Reply:`;
|
|
7862
|
-
const response = await this.runtime.useModel(
|
|
8039
|
+
const response = await this.runtime.useModel(ModelType4.TEXT_SMALL, {
|
|
7863
8040
|
prompt,
|
|
7864
8041
|
max_tokens: 100,
|
|
7865
8042
|
temperature: 0.8
|
|
@@ -7891,7 +8068,7 @@ Create a quote tweet that:
|
|
|
7891
8068
|
- Encourages further discussion
|
|
7892
8069
|
|
|
7893
8070
|
Quote tweet:`;
|
|
7894
|
-
const response = await this.runtime.useModel(
|
|
8071
|
+
const response = await this.runtime.useModel(ModelType4.TEXT_SMALL, {
|
|
7895
8072
|
prompt,
|
|
7896
8073
|
max_tokens: 100,
|
|
7897
8074
|
temperature: 0.8
|
|
@@ -7899,37 +8076,43 @@ Quote tweet:`;
|
|
|
7899
8076
|
return response.trim();
|
|
7900
8077
|
}
|
|
7901
8078
|
async saveEngagementMemory(tweet, engagementType) {
|
|
7902
|
-
const memoryId = await this.runtime.createMemory(
|
|
7903
|
-
|
|
7904
|
-
|
|
7905
|
-
|
|
7906
|
-
|
|
7907
|
-
|
|
7908
|
-
|
|
7909
|
-
|
|
7910
|
-
|
|
7911
|
-
|
|
7912
|
-
|
|
8079
|
+
const memoryId = await this.runtime.createMemory(
|
|
8080
|
+
{
|
|
8081
|
+
id: createUniqueUuid5(this.runtime, tweet.id),
|
|
8082
|
+
entityId: createUniqueUuid5(this.runtime, tweet.userId),
|
|
8083
|
+
content: {
|
|
8084
|
+
text: `${engagementType} tweet from @${tweet.username}: ${tweet.text}`,
|
|
8085
|
+
metadata: {
|
|
8086
|
+
tweetId: tweet.id,
|
|
8087
|
+
engagementType,
|
|
8088
|
+
source: "discovery",
|
|
8089
|
+
isDryRun: this.isDryRun
|
|
8090
|
+
}
|
|
8091
|
+
},
|
|
8092
|
+
roomId: createUniqueUuid5(this.runtime, tweet.conversationId)
|
|
7913
8093
|
},
|
|
7914
|
-
|
|
7915
|
-
|
|
8094
|
+
"messages"
|
|
8095
|
+
);
|
|
7916
8096
|
}
|
|
7917
8097
|
async saveFollowMemory(user) {
|
|
7918
|
-
const memoryId = await this.runtime.createMemory(
|
|
7919
|
-
|
|
7920
|
-
|
|
7921
|
-
|
|
7922
|
-
|
|
7923
|
-
|
|
7924
|
-
|
|
7925
|
-
|
|
7926
|
-
|
|
7927
|
-
|
|
7928
|
-
|
|
7929
|
-
|
|
8098
|
+
const memoryId = await this.runtime.createMemory(
|
|
8099
|
+
{
|
|
8100
|
+
entityId: createUniqueUuid5(this.runtime, user.id),
|
|
8101
|
+
content: {
|
|
8102
|
+
text: `followed twitter user ${user.id} @${user.username}`,
|
|
8103
|
+
metadata: {
|
|
8104
|
+
userId: user.id,
|
|
8105
|
+
username: user.username,
|
|
8106
|
+
name: user.name,
|
|
8107
|
+
followersCount: user.followersCount,
|
|
8108
|
+
source: "discovery",
|
|
8109
|
+
isDryRun: this.isDryRun
|
|
8110
|
+
}
|
|
8111
|
+
},
|
|
8112
|
+
roomId: createUniqueUuid5(this.runtime, `twitter-follows`)
|
|
7930
8113
|
},
|
|
7931
|
-
|
|
7932
|
-
|
|
8114
|
+
"messages"
|
|
8115
|
+
);
|
|
7933
8116
|
}
|
|
7934
8117
|
delay(ms) {
|
|
7935
8118
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
@@ -7993,7 +8176,9 @@ var RequestQueue = class {
|
|
|
7993
8176
|
await this.exponentialBackoff(retryCount);
|
|
7994
8177
|
break;
|
|
7995
8178
|
} else {
|
|
7996
|
-
logger7.error(
|
|
8179
|
+
logger7.error(
|
|
8180
|
+
`Max retries (${this.maxRetries}) exceeded for request, skipping`
|
|
8181
|
+
);
|
|
7997
8182
|
this.retryAttempts.delete(request);
|
|
7998
8183
|
}
|
|
7999
8184
|
}
|
|
@@ -8526,13 +8711,17 @@ var TwitterClientInstance = class {
|
|
|
8526
8711
|
constructor(runtime, state) {
|
|
8527
8712
|
this.client = new ClientBase(runtime, state);
|
|
8528
8713
|
const postEnabledSetting = getSetting(runtime, "TWITTER_ENABLE_POST") ?? process.env.TWITTER_ENABLE_POST;
|
|
8529
|
-
logger8.debug(
|
|
8714
|
+
logger8.debug(
|
|
8715
|
+
`TWITTER_ENABLE_POST setting value: ${JSON.stringify(postEnabledSetting)}, type: ${typeof postEnabledSetting}`
|
|
8716
|
+
);
|
|
8530
8717
|
const postEnabled = postEnabledSetting === "true" || postEnabledSetting === true;
|
|
8531
8718
|
if (postEnabled) {
|
|
8532
8719
|
logger8.info("Twitter posting is ENABLED - creating post client");
|
|
8533
8720
|
this.post = new TwitterPostClient(this.client, runtime, state);
|
|
8534
8721
|
} else {
|
|
8535
|
-
logger8.info(
|
|
8722
|
+
logger8.info(
|
|
8723
|
+
"Twitter posting is DISABLED - set TWITTER_ENABLE_POST=true to enable automatic posting"
|
|
8724
|
+
);
|
|
8536
8725
|
}
|
|
8537
8726
|
const repliesEnabled = (getSetting(runtime, "TWITTER_ENABLE_REPLIES") ?? process.env.TWITTER_ENABLE_REPLIES) !== "false";
|
|
8538
8727
|
if (repliesEnabled) {
|
|
@@ -8557,7 +8746,9 @@ var TwitterClientInstance = class {
|
|
|
8557
8746
|
logger8.info("Twitter discovery service is ENABLED");
|
|
8558
8747
|
this.discovery = new TwitterDiscoveryClient(this.client, runtime, state);
|
|
8559
8748
|
} else {
|
|
8560
|
-
logger8.info(
|
|
8749
|
+
logger8.info(
|
|
8750
|
+
"Twitter discovery service is DISABLED - set TWITTER_ENABLE_DISCOVERY=true to enable"
|
|
8751
|
+
);
|
|
8561
8752
|
}
|
|
8562
8753
|
}
|
|
8563
8754
|
};
|
|
@@ -8620,11 +8811,17 @@ var TwitterService = _TwitterService;
|
|
|
8620
8811
|
// src/actions/postTweet.ts
|
|
8621
8812
|
import {
|
|
8622
8813
|
logger as logger9,
|
|
8623
|
-
ModelType as
|
|
8814
|
+
ModelType as ModelType5
|
|
8624
8815
|
} from "@elizaos/core";
|
|
8625
8816
|
var postTweetAction = {
|
|
8626
8817
|
name: "POST_TWEET",
|
|
8627
|
-
similes: [
|
|
8818
|
+
similes: [
|
|
8819
|
+
"TWEET",
|
|
8820
|
+
"SEND_TWEET",
|
|
8821
|
+
"TWITTER_POST",
|
|
8822
|
+
"POST_ON_TWITTER",
|
|
8823
|
+
"SHARE_ON_TWITTER"
|
|
8824
|
+
],
|
|
8628
8825
|
validate: async (runtime, message) => {
|
|
8629
8826
|
logger9.debug("Validating POST_TWEET action");
|
|
8630
8827
|
const text = message.content?.text?.trim();
|
|
@@ -8646,7 +8843,9 @@ var postTweetAction = {
|
|
|
8646
8843
|
await client.init();
|
|
8647
8844
|
}
|
|
8648
8845
|
if (!client.profile) {
|
|
8649
|
-
throw new Error(
|
|
8846
|
+
throw new Error(
|
|
8847
|
+
"Twitter client not properly initialized - no profile found"
|
|
8848
|
+
);
|
|
8650
8849
|
}
|
|
8651
8850
|
const tweetText = message.content?.text?.trim() || "";
|
|
8652
8851
|
let finalTweetText = tweetText;
|
|
@@ -8667,7 +8866,7 @@ Generate a tweet that:
|
|
|
8667
8866
|
- Is not generic or promotional
|
|
8668
8867
|
|
|
8669
8868
|
Tweet:`;
|
|
8670
|
-
const response = await runtime.useModel(
|
|
8869
|
+
const response = await runtime.useModel(ModelType5.TEXT_SMALL, {
|
|
8671
8870
|
prompt: tweetPrompt,
|
|
8672
8871
|
max_tokens: 100,
|
|
8673
8872
|
temperature: 0.8
|
|
@@ -8687,16 +8886,19 @@ Tweet:`;
|
|
|
8687
8886
|
}
|
|
8688
8887
|
const tweetUrl = `https://twitter.com/${client.profile.username}/status/${tweetId}`;
|
|
8689
8888
|
logger9.info(`Successfully posted tweet: ${tweetId}`);
|
|
8690
|
-
await runtime.createMemory(
|
|
8691
|
-
|
|
8692
|
-
|
|
8693
|
-
|
|
8694
|
-
|
|
8695
|
-
|
|
8696
|
-
|
|
8889
|
+
await runtime.createMemory(
|
|
8890
|
+
{
|
|
8891
|
+
entityId: runtime.agentId,
|
|
8892
|
+
content: {
|
|
8893
|
+
text: finalTweetText,
|
|
8894
|
+
url: tweetUrl,
|
|
8895
|
+
source: "twitter",
|
|
8896
|
+
action: "POST_TWEET"
|
|
8897
|
+
},
|
|
8898
|
+
roomId: message.roomId
|
|
8697
8899
|
},
|
|
8698
|
-
|
|
8699
|
-
|
|
8900
|
+
"messages"
|
|
8901
|
+
);
|
|
8700
8902
|
if (callback) {
|
|
8701
8903
|
await callback({
|
|
8702
8904
|
text: `I've posted a tweet: "${finalTweetText}"
|