@elizaos/plugin-twitter 1.2.10 → 1.2.12
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 +472 -217
- 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);
|
|
@@ -1920,7 +1948,7 @@ import {
|
|
|
1920
1948
|
} from "@elizaos/core";
|
|
1921
1949
|
|
|
1922
1950
|
// src/constants.ts
|
|
1923
|
-
var TWEET_MAX_LENGTH =
|
|
1951
|
+
var TWEET_MAX_LENGTH = 280;
|
|
1924
1952
|
|
|
1925
1953
|
// src/utils/error-handler.ts
|
|
1926
1954
|
import { logger } from "@elizaos/core";
|
|
@@ -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);
|
|
@@ -6817,19 +6894,21 @@ Response (YES/NO):`;
|
|
|
6817
6894
|
|
|
6818
6895
|
// src/post.ts
|
|
6819
6896
|
import {
|
|
6820
|
-
ChannelType as ChannelType2,
|
|
6821
6897
|
createUniqueUuid as createUniqueUuid3,
|
|
6822
|
-
logger as logger4
|
|
6898
|
+
logger as logger4,
|
|
6899
|
+
ModelType as ModelType2
|
|
6823
6900
|
} from "@elizaos/core";
|
|
6824
6901
|
var TwitterPostClient = class {
|
|
6902
|
+
// Add lock to prevent concurrent posting
|
|
6825
6903
|
/**
|
|
6826
|
-
*
|
|
6827
|
-
* @param {ClientBase} client - The client
|
|
6828
|
-
* @param {IAgentRuntime} runtime - The runtime
|
|
6904
|
+
* Creates an instance of TwitterPostClient.
|
|
6905
|
+
* @param {ClientBase} client - The client instance.
|
|
6906
|
+
* @param {IAgentRuntime} runtime - The runtime instance.
|
|
6829
6907
|
* @param {any} state - The state object containing configuration settings
|
|
6830
6908
|
*/
|
|
6831
6909
|
constructor(client, runtime, state) {
|
|
6832
6910
|
this.isRunning = false;
|
|
6911
|
+
this.isPosting = false;
|
|
6833
6912
|
this.client = client;
|
|
6834
6913
|
this.state = state;
|
|
6835
6914
|
this.runtime = runtime;
|
|
@@ -6871,11 +6950,11 @@ var TwitterPostClient = class {
|
|
|
6871
6950
|
}
|
|
6872
6951
|
};
|
|
6873
6952
|
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
6874
|
-
const postImmediately =
|
|
6875
|
-
|
|
6876
|
-
|
|
6877
|
-
|
|
6878
|
-
|
|
6953
|
+
const postImmediately = this.state?.TWITTER_POST_IMMEDIATELY || getSetting(this.runtime, "TWITTER_POST_IMMEDIATELY") || process.env.TWITTER_POST_IMMEDIATELY;
|
|
6954
|
+
if (postImmediately === "true" || postImmediately === true) {
|
|
6955
|
+
logger4.info(
|
|
6956
|
+
"TWITTER_POST_IMMEDIATELY is true, generating initial tweet now"
|
|
6957
|
+
);
|
|
6879
6958
|
await this.generateNewTweet();
|
|
6880
6959
|
}
|
|
6881
6960
|
generateNewTweetLoop();
|
|
@@ -6886,78 +6965,124 @@ var TwitterPostClient = class {
|
|
|
6886
6965
|
*/
|
|
6887
6966
|
async generateNewTweet() {
|
|
6888
6967
|
logger4.info("Attempting to generate new tweet...");
|
|
6968
|
+
if (this.isPosting) {
|
|
6969
|
+
logger4.info("Already posting a tweet, skipping concurrent attempt");
|
|
6970
|
+
return;
|
|
6971
|
+
}
|
|
6972
|
+
this.isPosting = true;
|
|
6889
6973
|
try {
|
|
6890
6974
|
const userId = this.client.profile?.id;
|
|
6891
6975
|
if (!userId) {
|
|
6892
6976
|
logger4.error("Cannot generate tweet: Twitter profile not available");
|
|
6893
6977
|
return;
|
|
6894
6978
|
}
|
|
6895
|
-
logger4.info(
|
|
6979
|
+
logger4.info(
|
|
6980
|
+
`Generating tweet for user: ${this.client.profile?.username} (${userId})`
|
|
6981
|
+
);
|
|
6896
6982
|
const worldId = createUniqueUuid3(this.runtime, userId);
|
|
6897
6983
|
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
|
-
|
|
6924
|
-
|
|
6925
|
-
|
|
6926
|
-
|
|
6927
|
-
|
|
6928
|
-
|
|
6929
|
-
|
|
6930
|
-
|
|
6931
|
-
|
|
6932
|
-
|
|
6933
|
-
|
|
6934
|
-
|
|
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 */,
|
|
6984
|
+
const state = await this.runtime.composeState({
|
|
6985
|
+
agentId: this.runtime.agentId,
|
|
6986
|
+
entityId: this.runtime.agentId,
|
|
6987
|
+
roomId,
|
|
6988
|
+
content: { text: "", type: "post" },
|
|
6989
|
+
createdAt: Date.now()
|
|
6990
|
+
});
|
|
6991
|
+
const tweetPrompt = `You are ${this.runtime.character.name}.
|
|
6992
|
+
${this.runtime.character.bio}
|
|
6993
|
+
|
|
6994
|
+
CRITICAL: Generate a tweet that sounds like YOU, not a generic motivational poster or LinkedIn influencer.
|
|
6995
|
+
|
|
6996
|
+
${this.runtime.character.messageExamples && this.runtime.character.messageExamples.length > 0 ? `
|
|
6997
|
+
Example tweets that capture your voice:
|
|
6998
|
+
${this.runtime.character.messageExamples.map(
|
|
6999
|
+
(example) => Array.isArray(example) ? example[1]?.content?.text || "" : example
|
|
7000
|
+
).filter(Boolean).slice(0, 5).join("\n")}
|
|
7001
|
+
` : ""}
|
|
7002
|
+
|
|
7003
|
+
Style guidelines:
|
|
7004
|
+
- Be authentic, opinionated, and specific - no generic platitudes
|
|
7005
|
+
- Use your unique voice and perspective
|
|
7006
|
+
- Share hot takes, unpopular opinions, or specific insights
|
|
7007
|
+
- Be conversational, not preachy
|
|
7008
|
+
- If you use emojis, use them sparingly and purposefully
|
|
7009
|
+
- Length: 50-280 characters (keep it punchy)
|
|
7010
|
+
- NO hashtags unless absolutely essential
|
|
7011
|
+
- NO generic motivational content
|
|
7012
|
+
|
|
7013
|
+
Your interests: ${this.runtime.character.topics?.join(", ") || "technology, crypto, AI"}
|
|
7014
|
+
|
|
7015
|
+
${this.runtime.character.style ? `Your style: ${typeof this.runtime.character.style === "object" ? this.runtime.character.style.all?.join(", ") || JSON.stringify(this.runtime.character.style) : this.runtime.character.style}` : ""}
|
|
7016
|
+
|
|
7017
|
+
Recent context:
|
|
7018
|
+
${state.recentMemories?.slice(0, 3).map((m) => m.content.text).join("\n") || "No recent context"}
|
|
7019
|
+
|
|
7020
|
+
Generate a single tweet that sounds like YOU would actually write it:`;
|
|
7021
|
+
const generatedContent = await this.runtime.useModel(
|
|
7022
|
+
ModelType2.TEXT_SMALL,
|
|
6950
7023
|
{
|
|
6951
|
-
|
|
6952
|
-
|
|
6953
|
-
|
|
6954
|
-
|
|
6955
|
-
source: "twitter"
|
|
7024
|
+
prompt: tweetPrompt,
|
|
7025
|
+
temperature: 0.9,
|
|
7026
|
+
// Increased for more creativity
|
|
7027
|
+
maxTokens: 100
|
|
6956
7028
|
}
|
|
6957
7029
|
);
|
|
6958
|
-
|
|
7030
|
+
const tweetText = generatedContent.trim();
|
|
7031
|
+
if (!tweetText || tweetText.length === 0) {
|
|
7032
|
+
logger4.error("Generated empty tweet content");
|
|
7033
|
+
return;
|
|
7034
|
+
}
|
|
7035
|
+
if (tweetText.includes("Error: Missing")) {
|
|
7036
|
+
logger4.error("Error in generated content:", tweetText);
|
|
7037
|
+
return;
|
|
7038
|
+
}
|
|
7039
|
+
if (tweetText.length > 280) {
|
|
7040
|
+
logger4.warn(`Generated tweet too long (${tweetText.length} chars), truncating...`);
|
|
7041
|
+
const sentences = tweetText.match(/[^.!?]+[.!?]+/g) || [tweetText];
|
|
7042
|
+
let truncated = "";
|
|
7043
|
+
for (const sentence of sentences) {
|
|
7044
|
+
if ((truncated + sentence).length <= 280) {
|
|
7045
|
+
truncated += sentence;
|
|
7046
|
+
} else {
|
|
7047
|
+
break;
|
|
7048
|
+
}
|
|
7049
|
+
}
|
|
7050
|
+
const finalTweet = truncated.trim() || tweetText.substring(0, 277) + "...";
|
|
7051
|
+
logger4.info(`Truncated tweet: ${finalTweet}`);
|
|
7052
|
+
if (this.isDryRun) {
|
|
7053
|
+
logger4.info(`[DRY RUN] Would post tweet: ${finalTweet}`);
|
|
7054
|
+
return;
|
|
7055
|
+
}
|
|
7056
|
+
const result2 = await this.postToTwitter(finalTweet, []);
|
|
7057
|
+
if (result2 === null) {
|
|
7058
|
+
logger4.info("Skipped posting duplicate tweet");
|
|
7059
|
+
return;
|
|
7060
|
+
}
|
|
7061
|
+
const tweetId2 = result2.id;
|
|
7062
|
+
logger4.info(`Tweet posted successfully! ID: ${tweetId2}`);
|
|
7063
|
+
logger4.info("Tweet posted successfully (memory saving disabled due to room constraints)");
|
|
7064
|
+
return;
|
|
7065
|
+
}
|
|
7066
|
+
logger4.info(`Generated tweet: ${tweetText}`);
|
|
7067
|
+
if (this.isDryRun) {
|
|
7068
|
+
logger4.info(`[DRY RUN] Would post tweet: ${tweetText}`);
|
|
7069
|
+
return;
|
|
7070
|
+
}
|
|
7071
|
+
const result = await this.postToTwitter(tweetText, []);
|
|
7072
|
+
if (result === null) {
|
|
7073
|
+
logger4.info("Skipped posting duplicate tweet");
|
|
7074
|
+
return;
|
|
7075
|
+
}
|
|
7076
|
+
const tweetId = result.id;
|
|
7077
|
+
logger4.info(`Tweet posted successfully! ID: ${tweetId}`);
|
|
7078
|
+
if (result) {
|
|
7079
|
+
const postedTweetId = createUniqueUuid3(this.runtime, tweetId);
|
|
7080
|
+
logger4.info("Tweet posted successfully (memory saving temporarily disabled)");
|
|
7081
|
+
}
|
|
6959
7082
|
} catch (error) {
|
|
6960
7083
|
logger4.error("Error generating tweet:", error);
|
|
7084
|
+
} finally {
|
|
7085
|
+
this.isPosting = false;
|
|
6961
7086
|
}
|
|
6962
7087
|
}
|
|
6963
7088
|
/**
|
|
@@ -7014,7 +7139,7 @@ import {
|
|
|
7014
7139
|
ChannelType as ChannelType3,
|
|
7015
7140
|
composePromptFromState,
|
|
7016
7141
|
createUniqueUuid as createUniqueUuid4,
|
|
7017
|
-
ModelType as
|
|
7142
|
+
ModelType as ModelType3,
|
|
7018
7143
|
parseKeyValueXml
|
|
7019
7144
|
} from "@elizaos/core";
|
|
7020
7145
|
import { logger as logger5 } from "@elizaos/core";
|
|
@@ -7026,20 +7151,23 @@ var twitterActionTemplate = `
|
|
|
7026
7151
|
{{postDirections}}
|
|
7027
7152
|
|
|
7028
7153
|
Guidelines:
|
|
7029
|
-
-
|
|
7030
|
-
- Direct mentions
|
|
7031
|
-
-
|
|
7032
|
-
-
|
|
7033
|
-
-
|
|
7034
|
-
-
|
|
7035
|
-
-
|
|
7036
|
-
|
|
7154
|
+
- Engage with content that relates to character's interests and expertise
|
|
7155
|
+
- Direct mentions should be prioritized when relevant
|
|
7156
|
+
- Consider engaging with:
|
|
7157
|
+
- Content directly related to your topics
|
|
7158
|
+
- Interesting discussions you can contribute to
|
|
7159
|
+
- Questions you can help answer
|
|
7160
|
+
- Content from users you've interacted with before
|
|
7161
|
+
- Skip content that is:
|
|
7162
|
+
- Completely off-topic or spam
|
|
7163
|
+
- Inflammatory or highly controversial (unless it's your area)
|
|
7164
|
+
- Pure marketing/promotional with no value
|
|
7037
7165
|
|
|
7038
7166
|
Actions (respond only with tags):
|
|
7039
|
-
[LIKE] -
|
|
7040
|
-
[RETWEET] -
|
|
7041
|
-
[QUOTE] -
|
|
7042
|
-
[REPLY] -
|
|
7167
|
+
[LIKE] - Content is relevant and interesting (7/10 or higher)
|
|
7168
|
+
[RETWEET] - Content is valuable and worth sharing (8/10 or higher)
|
|
7169
|
+
[QUOTE] - You can add meaningful commentary (7.5/10 or higher)
|
|
7170
|
+
[REPLY] - You can contribute helpful insights (7/10 or higher)
|
|
7043
7171
|
`;
|
|
7044
7172
|
var quoteTweetTemplate = `# Task: Write a quote tweet in the voice, style, and perspective of {{agentName}} @{{twitterUserName}}.
|
|
7045
7173
|
|
|
@@ -7105,7 +7233,9 @@ var TwitterTimelineClient = class {
|
|
|
7105
7233
|
this.state?.TWITTER_ENGAGEMENT_INTERVAL || getSetting(this.runtime, "TWITTER_ENGAGEMENT_INTERVAL") || process.env.TWITTER_ENGAGEMENT_INTERVAL || "30"
|
|
7106
7234
|
);
|
|
7107
7235
|
const actionInterval = engagementIntervalMinutes * 60 * 1e3;
|
|
7108
|
-
logger5.info(
|
|
7236
|
+
logger5.info(
|
|
7237
|
+
`Timeline client will check every ${engagementIntervalMinutes} minutes`
|
|
7238
|
+
);
|
|
7109
7239
|
this.handleTimeline();
|
|
7110
7240
|
if (this.isRunning) {
|
|
7111
7241
|
setTimeout(handleTwitterTimelineLoop, actionInterval);
|
|
@@ -7173,7 +7303,7 @@ ${tweet.text}
|
|
|
7173
7303
|
|
|
7174
7304
|
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
7305
|
const actionResponse = await this.runtime.useModel(
|
|
7176
|
-
|
|
7306
|
+
ModelType3.TEXT_SMALL,
|
|
7177
7307
|
{
|
|
7178
7308
|
prompt: actionRespondPrompt
|
|
7179
7309
|
}
|
|
@@ -7227,7 +7357,12 @@ ${actionSummary.join("\n")}`);
|
|
|
7227
7357
|
}
|
|
7228
7358
|
async processTimelineActions(tweetDecisions) {
|
|
7229
7359
|
const results = [];
|
|
7230
|
-
for (const {
|
|
7360
|
+
for (const {
|
|
7361
|
+
tweet,
|
|
7362
|
+
actionResponse,
|
|
7363
|
+
tweetState,
|
|
7364
|
+
roomId
|
|
7365
|
+
} of tweetDecisions) {
|
|
7231
7366
|
const tweetId = this.createTweetId(this.runtime, tweet);
|
|
7232
7367
|
const executedActions = [];
|
|
7233
7368
|
await this.runtime.createMemory(
|
|
@@ -7345,13 +7480,15 @@ ${actionSummary.join("\n")}`);
|
|
|
7345
7480
|
}) + `
|
|
7346
7481
|
You are responding to this tweet:
|
|
7347
7482
|
${tweet.text}`;
|
|
7348
|
-
const quoteResponse = await this.runtime.useModel(
|
|
7483
|
+
const quoteResponse = await this.runtime.useModel(ModelType3.TEXT_SMALL, {
|
|
7349
7484
|
prompt: quotePrompt
|
|
7350
7485
|
});
|
|
7351
7486
|
const responseObject = parseKeyValueXml(quoteResponse);
|
|
7352
7487
|
if (responseObject.post) {
|
|
7353
7488
|
if (this.isDryRun) {
|
|
7354
|
-
logger5.log(
|
|
7489
|
+
logger5.log(
|
|
7490
|
+
`[DRY RUN] Would have quoted tweet ${tweet.id} with: ${responseObject.post}`
|
|
7491
|
+
);
|
|
7355
7492
|
return;
|
|
7356
7493
|
}
|
|
7357
7494
|
const result = await this.client.requestQueue.add(
|
|
@@ -7396,13 +7533,15 @@ ${tweet.text}`;
|
|
|
7396
7533
|
}) + `
|
|
7397
7534
|
You are replying to this tweet:
|
|
7398
7535
|
${tweet.text}`;
|
|
7399
|
-
const replyResponse = await this.runtime.useModel(
|
|
7536
|
+
const replyResponse = await this.runtime.useModel(ModelType3.TEXT_SMALL, {
|
|
7400
7537
|
prompt: replyPrompt
|
|
7401
7538
|
});
|
|
7402
7539
|
const responseObject = parseKeyValueXml(replyResponse);
|
|
7403
7540
|
if (responseObject.post) {
|
|
7404
7541
|
if (this.isDryRun) {
|
|
7405
|
-
logger5.log(
|
|
7542
|
+
logger5.log(
|
|
7543
|
+
`[DRY RUN] Would have replied to tweet ${tweet.id} with: ${responseObject.post}`
|
|
7544
|
+
);
|
|
7406
7545
|
return;
|
|
7407
7546
|
}
|
|
7408
7547
|
const result = await sendTweet(
|
|
@@ -7438,7 +7577,7 @@ ${tweet.text}`;
|
|
|
7438
7577
|
import {
|
|
7439
7578
|
createUniqueUuid as createUniqueUuid5,
|
|
7440
7579
|
logger as logger6,
|
|
7441
|
-
ModelType as
|
|
7580
|
+
ModelType as ModelType4
|
|
7442
7581
|
} from "@elizaos/core";
|
|
7443
7582
|
var TwitterDiscoveryClient = class {
|
|
7444
7583
|
constructor(client, runtime, state) {
|
|
@@ -7489,7 +7628,9 @@ var TwitterDiscoveryClient = class {
|
|
|
7489
7628
|
topics = this.extractTopicsFromBio(character.bio);
|
|
7490
7629
|
}
|
|
7491
7630
|
} else {
|
|
7492
|
-
logger6.warn(
|
|
7631
|
+
logger6.warn(
|
|
7632
|
+
"Character not available in runtime, using default topics for discovery"
|
|
7633
|
+
);
|
|
7493
7634
|
}
|
|
7494
7635
|
return {
|
|
7495
7636
|
topics,
|
|
@@ -7500,11 +7641,17 @@ var TwitterDiscoveryClient = class {
|
|
|
7500
7641
|
getSetting(this.runtime, "TWITTER_MAX_FOLLOWS_PER_CYCLE") || process.env.TWITTER_MAX_FOLLOWS_PER_CYCLE || "5"
|
|
7501
7642
|
),
|
|
7502
7643
|
maxEngagementsPerCycle: parseInt(
|
|
7503
|
-
getSetting(
|
|
7644
|
+
getSetting(
|
|
7645
|
+
this.runtime,
|
|
7646
|
+
"TWITTER_MAX_ENGAGEMENTS_PER_RUN"
|
|
7647
|
+
) || process.env.TWITTER_MAX_ENGAGEMENTS_PER_RUN || "10"
|
|
7504
7648
|
),
|
|
7505
|
-
likeThreshold: 0.
|
|
7506
|
-
|
|
7507
|
-
|
|
7649
|
+
likeThreshold: 0.3,
|
|
7650
|
+
// Lowered from 0.6
|
|
7651
|
+
replyThreshold: 0.5,
|
|
7652
|
+
// Lowered from 0.8
|
|
7653
|
+
quoteThreshold: 0.7
|
|
7654
|
+
// Lowered from 0.85
|
|
7508
7655
|
};
|
|
7509
7656
|
}
|
|
7510
7657
|
extractTopicsFromBio(bio) {
|
|
@@ -7512,7 +7659,16 @@ var TwitterDiscoveryClient = class {
|
|
|
7512
7659
|
return [];
|
|
7513
7660
|
}
|
|
7514
7661
|
const bioText = Array.isArray(bio) ? bio.join(" ") : bio;
|
|
7515
|
-
const words = bioText.toLowerCase().split(/\s+/).filter((word) => word.length > 4).filter(
|
|
7662
|
+
const words = bioText.toLowerCase().split(/\s+/).filter((word) => word.length > 4).filter(
|
|
7663
|
+
(word) => ![
|
|
7664
|
+
"about",
|
|
7665
|
+
"helping",
|
|
7666
|
+
"working",
|
|
7667
|
+
"people",
|
|
7668
|
+
"making",
|
|
7669
|
+
"building"
|
|
7670
|
+
].includes(word)
|
|
7671
|
+
);
|
|
7516
7672
|
return [...new Set(words)].slice(0, 5);
|
|
7517
7673
|
}
|
|
7518
7674
|
async start() {
|
|
@@ -7533,7 +7689,9 @@ var TwitterDiscoveryClient = class {
|
|
|
7533
7689
|
);
|
|
7534
7690
|
const variance = Math.random() * 20 - 10;
|
|
7535
7691
|
const nextInterval = (baseInterval + variance) * 60 * 1e3;
|
|
7536
|
-
logger6.info(
|
|
7692
|
+
logger6.info(
|
|
7693
|
+
`Next discovery cycle in ${(baseInterval + variance).toFixed(1)} minutes`
|
|
7694
|
+
);
|
|
7537
7695
|
if (this.isRunning) {
|
|
7538
7696
|
setTimeout(discoveryLoop, nextInterval);
|
|
7539
7697
|
}
|
|
@@ -7548,7 +7706,9 @@ var TwitterDiscoveryClient = class {
|
|
|
7548
7706
|
logger6.info("Starting Twitter discovery cycle...");
|
|
7549
7707
|
const discoveries = await this.discoverContent();
|
|
7550
7708
|
const { tweets, accounts } = discoveries;
|
|
7551
|
-
logger6.info(
|
|
7709
|
+
logger6.info(
|
|
7710
|
+
`Discovered ${tweets.length} tweets and ${accounts.length} accounts`
|
|
7711
|
+
);
|
|
7552
7712
|
const followedCount = await this.processAccounts(accounts);
|
|
7553
7713
|
const engagementCount = await this.processTweets(tweets);
|
|
7554
7714
|
logger6.info(
|
|
@@ -7561,9 +7721,7 @@ var TwitterDiscoveryClient = class {
|
|
|
7561
7721
|
try {
|
|
7562
7722
|
const topicContent = await this.discoverFromTopics();
|
|
7563
7723
|
allTweets.push(...topicContent.tweets);
|
|
7564
|
-
topicContent.accounts.forEach(
|
|
7565
|
-
(acc) => allAccounts.set(acc.user.id, acc)
|
|
7566
|
-
);
|
|
7724
|
+
topicContent.accounts.forEach((acc) => allAccounts.set(acc.user.id, acc));
|
|
7567
7725
|
} catch (error) {
|
|
7568
7726
|
logger6.error("Failed to discover from topics:", error);
|
|
7569
7727
|
}
|
|
@@ -7586,7 +7744,9 @@ var TwitterDiscoveryClient = class {
|
|
|
7586
7744
|
logger6.error("Failed to discover from popular accounts:", error);
|
|
7587
7745
|
}
|
|
7588
7746
|
const sortedTweets = allTweets.sort((a, b) => b.relevanceScore - a.relevanceScore).slice(0, 50);
|
|
7589
|
-
const sortedAccounts = Array.from(allAccounts.values()).sort(
|
|
7747
|
+
const sortedAccounts = Array.from(allAccounts.values()).sort(
|
|
7748
|
+
(a, b) => b.qualityScore * b.relevanceScore - a.qualityScore * a.relevanceScore
|
|
7749
|
+
).slice(0, 20);
|
|
7590
7750
|
return { tweets: sortedTweets, accounts: sortedAccounts };
|
|
7591
7751
|
}
|
|
7592
7752
|
async discoverFromTopics() {
|
|
@@ -7652,17 +7812,17 @@ var TwitterDiscoveryClient = class {
|
|
|
7652
7812
|
);
|
|
7653
7813
|
for (const tweet of searchResults.tweets) {
|
|
7654
7814
|
const engagementScore = (tweet.likes || 0) + (tweet.retweets || 0) * 2;
|
|
7655
|
-
if (engagementScore <
|
|
7815
|
+
if (engagementScore < 10) continue;
|
|
7656
7816
|
const scored = this.scoreTweet(tweet, "thread");
|
|
7657
7817
|
tweets.push(scored);
|
|
7658
7818
|
const account = this.scoreAccount({
|
|
7659
7819
|
id: tweet.userId,
|
|
7660
7820
|
username: tweet.username,
|
|
7661
7821
|
name: tweet.name || tweet.username,
|
|
7662
|
-
followersCount:
|
|
7663
|
-
//
|
|
7822
|
+
followersCount: 1e3
|
|
7823
|
+
// Reasonable estimate for engaged users
|
|
7664
7824
|
});
|
|
7665
|
-
if (account.qualityScore > 0.
|
|
7825
|
+
if (account.qualityScore > 0.5) {
|
|
7666
7826
|
accounts.set(tweet.userId, account);
|
|
7667
7827
|
}
|
|
7668
7828
|
}
|
|
@@ -7687,7 +7847,7 @@ var TwitterDiscoveryClient = class {
|
|
|
7687
7847
|
);
|
|
7688
7848
|
for (const tweet of results.tweets) {
|
|
7689
7849
|
const engagement = (tweet.likes || 0) + (tweet.retweets || 0) * 2;
|
|
7690
|
-
if (engagement <
|
|
7850
|
+
if (engagement < 5) continue;
|
|
7691
7851
|
const scored = this.scoreTweet(tweet, "topic");
|
|
7692
7852
|
tweets.push(scored);
|
|
7693
7853
|
const estimatedFollowers = Math.max(
|
|
@@ -7706,7 +7866,10 @@ var TwitterDiscoveryClient = class {
|
|
|
7706
7866
|
}
|
|
7707
7867
|
}
|
|
7708
7868
|
} catch (error) {
|
|
7709
|
-
logger6.error(
|
|
7869
|
+
logger6.error(
|
|
7870
|
+
`Failed to discover popular accounts for ${topic}:`,
|
|
7871
|
+
error
|
|
7872
|
+
);
|
|
7710
7873
|
}
|
|
7711
7874
|
}
|
|
7712
7875
|
return { tweets, accounts: Array.from(accounts.values()) };
|
|
@@ -7716,12 +7879,15 @@ var TwitterDiscoveryClient = class {
|
|
|
7716
7879
|
scoreTweet(tweet, source) {
|
|
7717
7880
|
let relevanceScore = 0;
|
|
7718
7881
|
const sourceScores = {
|
|
7719
|
-
topic: 0.
|
|
7720
|
-
thread: 0.
|
|
7882
|
+
topic: 0.4,
|
|
7883
|
+
thread: 0.35
|
|
7721
7884
|
};
|
|
7722
7885
|
relevanceScore += sourceScores[source];
|
|
7723
7886
|
const engagementScore = Math.min(
|
|
7724
|
-
(tweet.likes || 0) /
|
|
7887
|
+
(tweet.likes || 0) / 100 + // 100 likes = 0.1 points (was 1000)
|
|
7888
|
+
(tweet.retweets || 0) / 50 + // 50 retweets = 0.1 points (was 500)
|
|
7889
|
+
(tweet.replies || 0) / 20,
|
|
7890
|
+
// 20 replies = 0.1 points (was 100)
|
|
7725
7891
|
0.3
|
|
7726
7892
|
);
|
|
7727
7893
|
relevanceScore += engagementScore;
|
|
@@ -7729,7 +7895,7 @@ var TwitterDiscoveryClient = class {
|
|
|
7729
7895
|
const topicMatches = this.config.topics.filter(
|
|
7730
7896
|
(topic) => textLower.includes(topic.toLowerCase())
|
|
7731
7897
|
).length;
|
|
7732
|
-
relevanceScore += Math.min(topicMatches * 0.
|
|
7898
|
+
relevanceScore += Math.min(topicMatches * 0.15, 0.3);
|
|
7733
7899
|
relevanceScore = Math.min(relevanceScore, 1);
|
|
7734
7900
|
let engagementType = "skip";
|
|
7735
7901
|
if (relevanceScore >= this.config.quoteThreshold) {
|
|
@@ -7783,7 +7949,10 @@ var TwitterDiscoveryClient = class {
|
|
|
7783
7949
|
followedCount++;
|
|
7784
7950
|
await this.delay(2e3 + Math.random() * 3e3);
|
|
7785
7951
|
} catch (error) {
|
|
7786
|
-
logger6.error(
|
|
7952
|
+
logger6.error(
|
|
7953
|
+
`Failed to follow @${scoredAccount.user.username}:`,
|
|
7954
|
+
error
|
|
7955
|
+
);
|
|
7787
7956
|
}
|
|
7788
7957
|
}
|
|
7789
7958
|
return followedCount;
|
|
@@ -7794,51 +7963,76 @@ var TwitterDiscoveryClient = class {
|
|
|
7794
7963
|
if (engagementCount >= this.config.maxEngagementsPerCycle) break;
|
|
7795
7964
|
if (scoredTweet.engagementType === "skip") continue;
|
|
7796
7965
|
try {
|
|
7797
|
-
const tweetMemoryId = createUniqueUuid5(
|
|
7966
|
+
const tweetMemoryId = createUniqueUuid5(
|
|
7967
|
+
this.runtime,
|
|
7968
|
+
scoredTweet.tweet.id
|
|
7969
|
+
);
|
|
7798
7970
|
const existingMemory = await this.runtime.getMemoryById(tweetMemoryId);
|
|
7799
7971
|
if (existingMemory) {
|
|
7800
|
-
logger6.debug(
|
|
7972
|
+
logger6.debug(
|
|
7973
|
+
`Already engaged with tweet ${scoredTweet.tweet.id}, skipping`
|
|
7974
|
+
);
|
|
7801
7975
|
continue;
|
|
7802
7976
|
}
|
|
7803
7977
|
switch (scoredTweet.engagementType) {
|
|
7804
7978
|
case "like":
|
|
7805
7979
|
if (this.isDryRun) {
|
|
7806
|
-
logger6.info(
|
|
7980
|
+
logger6.info(
|
|
7981
|
+
`[DRY RUN] Would like tweet: ${scoredTweet.tweet.id} (score: ${scoredTweet.relevanceScore.toFixed(2)})`
|
|
7982
|
+
);
|
|
7807
7983
|
} else {
|
|
7808
7984
|
await this.twitterClient.likeTweet(scoredTweet.tweet.id);
|
|
7809
|
-
logger6.info(
|
|
7985
|
+
logger6.info(
|
|
7986
|
+
`Liked tweet: ${scoredTweet.tweet.id} (score: ${scoredTweet.relevanceScore.toFixed(2)})`
|
|
7987
|
+
);
|
|
7810
7988
|
}
|
|
7811
7989
|
break;
|
|
7812
7990
|
case "reply":
|
|
7813
7991
|
const replyText = await this.generateReply(scoredTweet.tweet);
|
|
7814
7992
|
if (this.isDryRun) {
|
|
7815
|
-
logger6.info(
|
|
7993
|
+
logger6.info(
|
|
7994
|
+
`[DRY RUN] Would reply to tweet ${scoredTweet.tweet.id} with: "${replyText}"`
|
|
7995
|
+
);
|
|
7816
7996
|
} else {
|
|
7817
|
-
await this.twitterClient.sendTweet(
|
|
7997
|
+
await this.twitterClient.sendTweet(
|
|
7998
|
+
replyText,
|
|
7999
|
+
scoredTweet.tweet.id
|
|
8000
|
+
);
|
|
7818
8001
|
logger6.info(`Replied to tweet: ${scoredTweet.tweet.id}`);
|
|
7819
8002
|
}
|
|
7820
8003
|
break;
|
|
7821
8004
|
case "quote":
|
|
7822
8005
|
const quoteText = await this.generateQuote(scoredTweet.tweet);
|
|
7823
8006
|
if (this.isDryRun) {
|
|
7824
|
-
logger6.info(
|
|
8007
|
+
logger6.info(
|
|
8008
|
+
`[DRY RUN] Would quote tweet ${scoredTweet.tweet.id} with: "${quoteText}"`
|
|
8009
|
+
);
|
|
7825
8010
|
} else {
|
|
7826
|
-
await this.twitterClient.sendQuoteTweet(
|
|
8011
|
+
await this.twitterClient.sendQuoteTweet(
|
|
8012
|
+
quoteText,
|
|
8013
|
+
scoredTweet.tweet.id
|
|
8014
|
+
);
|
|
7827
8015
|
logger6.info(`Quoted tweet: ${scoredTweet.tweet.id}`);
|
|
7828
8016
|
}
|
|
7829
8017
|
break;
|
|
7830
8018
|
}
|
|
7831
|
-
await this.saveEngagementMemory(
|
|
8019
|
+
await this.saveEngagementMemory(
|
|
8020
|
+
scoredTweet.tweet,
|
|
8021
|
+
scoredTweet.engagementType
|
|
8022
|
+
);
|
|
7832
8023
|
engagementCount++;
|
|
7833
8024
|
await this.delay(3e3 + Math.random() * 5e3);
|
|
7834
8025
|
} catch (error) {
|
|
7835
|
-
logger6.error(
|
|
8026
|
+
logger6.error(
|
|
8027
|
+
`Failed to engage with tweet ${scoredTweet.tweet.id}:`,
|
|
8028
|
+
error
|
|
8029
|
+
);
|
|
7836
8030
|
}
|
|
7837
8031
|
}
|
|
7838
8032
|
return engagementCount;
|
|
7839
8033
|
}
|
|
7840
8034
|
async checkIfFollowing(userId) {
|
|
7841
|
-
const embedding = await this.runtime.useModel(
|
|
8035
|
+
const embedding = await this.runtime.useModel(ModelType4.TEXT_EMBEDDING, {
|
|
7842
8036
|
text: `followed twitter user ${userId}`
|
|
7843
8037
|
});
|
|
7844
8038
|
const followMemories = await this.runtime.searchMemories({
|
|
@@ -7874,7 +8068,7 @@ Keep the reply:
|
|
|
7874
8068
|
- Respectful and constructive
|
|
7875
8069
|
|
|
7876
8070
|
Reply:`;
|
|
7877
|
-
const response = await this.runtime.useModel(
|
|
8071
|
+
const response = await this.runtime.useModel(ModelType4.TEXT_SMALL, {
|
|
7878
8072
|
prompt,
|
|
7879
8073
|
max_tokens: 100,
|
|
7880
8074
|
temperature: 0.8
|
|
@@ -7906,7 +8100,7 @@ Create a quote tweet that:
|
|
|
7906
8100
|
- Encourages further discussion
|
|
7907
8101
|
|
|
7908
8102
|
Quote tweet:`;
|
|
7909
|
-
const response = await this.runtime.useModel(
|
|
8103
|
+
const response = await this.runtime.useModel(ModelType4.TEXT_SMALL, {
|
|
7910
8104
|
prompt,
|
|
7911
8105
|
max_tokens: 100,
|
|
7912
8106
|
temperature: 0.8
|
|
@@ -7914,37 +8108,43 @@ Quote tweet:`;
|
|
|
7914
8108
|
return response.trim();
|
|
7915
8109
|
}
|
|
7916
8110
|
async saveEngagementMemory(tweet, engagementType) {
|
|
7917
|
-
const memoryId = await this.runtime.createMemory(
|
|
7918
|
-
|
|
7919
|
-
|
|
7920
|
-
|
|
7921
|
-
|
|
7922
|
-
|
|
7923
|
-
|
|
7924
|
-
|
|
7925
|
-
|
|
7926
|
-
|
|
7927
|
-
|
|
8111
|
+
const memoryId = await this.runtime.createMemory(
|
|
8112
|
+
{
|
|
8113
|
+
id: createUniqueUuid5(this.runtime, tweet.id),
|
|
8114
|
+
entityId: createUniqueUuid5(this.runtime, tweet.userId),
|
|
8115
|
+
content: {
|
|
8116
|
+
text: `${engagementType} tweet from @${tweet.username}: ${tweet.text}`,
|
|
8117
|
+
metadata: {
|
|
8118
|
+
tweetId: tweet.id,
|
|
8119
|
+
engagementType,
|
|
8120
|
+
source: "discovery",
|
|
8121
|
+
isDryRun: this.isDryRun
|
|
8122
|
+
}
|
|
8123
|
+
},
|
|
8124
|
+
roomId: createUniqueUuid5(this.runtime, tweet.conversationId)
|
|
7928
8125
|
},
|
|
7929
|
-
|
|
7930
|
-
|
|
8126
|
+
"messages"
|
|
8127
|
+
);
|
|
7931
8128
|
}
|
|
7932
8129
|
async saveFollowMemory(user) {
|
|
7933
|
-
const memoryId = await this.runtime.createMemory(
|
|
7934
|
-
|
|
7935
|
-
|
|
7936
|
-
|
|
7937
|
-
|
|
7938
|
-
|
|
7939
|
-
|
|
7940
|
-
|
|
7941
|
-
|
|
7942
|
-
|
|
7943
|
-
|
|
7944
|
-
|
|
8130
|
+
const memoryId = await this.runtime.createMemory(
|
|
8131
|
+
{
|
|
8132
|
+
entityId: createUniqueUuid5(this.runtime, user.id),
|
|
8133
|
+
content: {
|
|
8134
|
+
text: `followed twitter user ${user.id} @${user.username}`,
|
|
8135
|
+
metadata: {
|
|
8136
|
+
userId: user.id,
|
|
8137
|
+
username: user.username,
|
|
8138
|
+
name: user.name,
|
|
8139
|
+
followersCount: user.followersCount,
|
|
8140
|
+
source: "discovery",
|
|
8141
|
+
isDryRun: this.isDryRun
|
|
8142
|
+
}
|
|
8143
|
+
},
|
|
8144
|
+
roomId: createUniqueUuid5(this.runtime, `twitter-follows`)
|
|
7945
8145
|
},
|
|
7946
|
-
|
|
7947
|
-
|
|
8146
|
+
"messages"
|
|
8147
|
+
);
|
|
7948
8148
|
}
|
|
7949
8149
|
delay(ms) {
|
|
7950
8150
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
@@ -8008,7 +8208,9 @@ var RequestQueue = class {
|
|
|
8008
8208
|
await this.exponentialBackoff(retryCount);
|
|
8009
8209
|
break;
|
|
8010
8210
|
} else {
|
|
8011
|
-
logger7.error(
|
|
8211
|
+
logger7.error(
|
|
8212
|
+
`Max retries (${this.maxRetries}) exceeded for request, skipping`
|
|
8213
|
+
);
|
|
8012
8214
|
this.retryAttempts.delete(request);
|
|
8013
8215
|
}
|
|
8014
8216
|
}
|
|
@@ -8541,13 +8743,17 @@ var TwitterClientInstance = class {
|
|
|
8541
8743
|
constructor(runtime, state) {
|
|
8542
8744
|
this.client = new ClientBase(runtime, state);
|
|
8543
8745
|
const postEnabledSetting = getSetting(runtime, "TWITTER_ENABLE_POST") ?? process.env.TWITTER_ENABLE_POST;
|
|
8544
|
-
logger8.debug(
|
|
8746
|
+
logger8.debug(
|
|
8747
|
+
`TWITTER_ENABLE_POST setting value: ${JSON.stringify(postEnabledSetting)}, type: ${typeof postEnabledSetting}`
|
|
8748
|
+
);
|
|
8545
8749
|
const postEnabled = postEnabledSetting === "true" || postEnabledSetting === true;
|
|
8546
8750
|
if (postEnabled) {
|
|
8547
8751
|
logger8.info("Twitter posting is ENABLED - creating post client");
|
|
8548
8752
|
this.post = new TwitterPostClient(this.client, runtime, state);
|
|
8549
8753
|
} else {
|
|
8550
|
-
logger8.info(
|
|
8754
|
+
logger8.info(
|
|
8755
|
+
"Twitter posting is DISABLED - set TWITTER_ENABLE_POST=true to enable automatic posting"
|
|
8756
|
+
);
|
|
8551
8757
|
}
|
|
8552
8758
|
const repliesEnabled = (getSetting(runtime, "TWITTER_ENABLE_REPLIES") ?? process.env.TWITTER_ENABLE_REPLIES) !== "false";
|
|
8553
8759
|
if (repliesEnabled) {
|
|
@@ -8572,7 +8778,9 @@ var TwitterClientInstance = class {
|
|
|
8572
8778
|
logger8.info("Twitter discovery service is ENABLED");
|
|
8573
8779
|
this.discovery = new TwitterDiscoveryClient(this.client, runtime, state);
|
|
8574
8780
|
} else {
|
|
8575
|
-
logger8.info(
|
|
8781
|
+
logger8.info(
|
|
8782
|
+
"Twitter discovery service is DISABLED - set TWITTER_ENABLE_DISCOVERY=true to enable"
|
|
8783
|
+
);
|
|
8576
8784
|
}
|
|
8577
8785
|
}
|
|
8578
8786
|
};
|
|
@@ -8635,11 +8843,17 @@ var TwitterService = _TwitterService;
|
|
|
8635
8843
|
// src/actions/postTweet.ts
|
|
8636
8844
|
import {
|
|
8637
8845
|
logger as logger9,
|
|
8638
|
-
ModelType as
|
|
8846
|
+
ModelType as ModelType5
|
|
8639
8847
|
} from "@elizaos/core";
|
|
8640
8848
|
var postTweetAction = {
|
|
8641
8849
|
name: "POST_TWEET",
|
|
8642
|
-
similes: [
|
|
8850
|
+
similes: [
|
|
8851
|
+
"TWEET",
|
|
8852
|
+
"SEND_TWEET",
|
|
8853
|
+
"TWITTER_POST",
|
|
8854
|
+
"POST_ON_TWITTER",
|
|
8855
|
+
"SHARE_ON_TWITTER"
|
|
8856
|
+
],
|
|
8643
8857
|
validate: async (runtime, message) => {
|
|
8644
8858
|
logger9.debug("Validating POST_TWEET action");
|
|
8645
8859
|
const text = message.content?.text?.trim();
|
|
@@ -8660,32 +8874,70 @@ var postTweetAction = {
|
|
|
8660
8874
|
if (!client.twitterClient) {
|
|
8661
8875
|
await client.init();
|
|
8662
8876
|
}
|
|
8877
|
+
let text = message.content?.text?.trim();
|
|
8878
|
+
if (!text) {
|
|
8879
|
+
logger9.error("No text content for tweet");
|
|
8880
|
+
if (callback) {
|
|
8881
|
+
callback({
|
|
8882
|
+
text: "I need something to tweet! Please provide the text.",
|
|
8883
|
+
action: "POST_TWEET"
|
|
8884
|
+
});
|
|
8885
|
+
}
|
|
8886
|
+
return;
|
|
8887
|
+
}
|
|
8888
|
+
if (text.length > 280) {
|
|
8889
|
+
logger9.info(`Truncating tweet from ${text.length} to 280 characters`);
|
|
8890
|
+
const sentences = text.match(/[^.!?]+[.!?]+/g) || [text];
|
|
8891
|
+
let truncated = "";
|
|
8892
|
+
for (const sentence of sentences) {
|
|
8893
|
+
if ((truncated + sentence).length <= 280) {
|
|
8894
|
+
truncated += sentence;
|
|
8895
|
+
} else {
|
|
8896
|
+
break;
|
|
8897
|
+
}
|
|
8898
|
+
}
|
|
8899
|
+
text = truncated.trim() || text.substring(0, 277) + "...";
|
|
8900
|
+
logger9.info(`Truncated tweet: ${text}`);
|
|
8901
|
+
}
|
|
8663
8902
|
if (!client.profile) {
|
|
8664
|
-
throw new Error(
|
|
8903
|
+
throw new Error(
|
|
8904
|
+
"Twitter client not properly initialized - no profile found"
|
|
8905
|
+
);
|
|
8665
8906
|
}
|
|
8666
|
-
|
|
8667
|
-
|
|
8668
|
-
|
|
8669
|
-
|
|
8907
|
+
let finalTweetText = text;
|
|
8908
|
+
if (text.length < 50 || text.toLowerCase().includes("post") || text.toLowerCase().includes("tweet")) {
|
|
8909
|
+
const tweetPrompt = `You are ${runtime.character.name}.
|
|
8910
|
+
${runtime.character.bio || ""}
|
|
8670
8911
|
|
|
8671
|
-
|
|
8912
|
+
CRITICAL: Generate a tweet based on the context that sounds like YOU, not generic corporate speak.
|
|
8672
8913
|
|
|
8673
|
-
|
|
8674
|
-
Your style: ${runtime.character.style?.all?.join(", ") || "thoughtful, engaging"}
|
|
8914
|
+
Context: ${text}
|
|
8675
8915
|
|
|
8676
|
-
|
|
8677
|
-
|
|
8678
|
-
|
|
8679
|
-
|
|
8680
|
-
|
|
8681
|
-
|
|
8682
|
-
|
|
8916
|
+
${runtime.character.messageExamples && runtime.character.messageExamples.length > 0 ? `
|
|
8917
|
+
Your voice examples:
|
|
8918
|
+
${runtime.character.messageExamples.map(
|
|
8919
|
+
(example) => Array.isArray(example) ? example[1]?.content?.text || "" : example
|
|
8920
|
+
).filter(Boolean).slice(0, 3).join("\n")}
|
|
8921
|
+
` : ""}
|
|
8922
|
+
|
|
8923
|
+
Style rules:
|
|
8924
|
+
- Be specific, opinionated, authentic
|
|
8925
|
+
- No generic motivational content or platitudes
|
|
8926
|
+
- Share actual insights, hot takes, or unique perspectives
|
|
8927
|
+
- Keep it conversational and punchy
|
|
8928
|
+
- Under 280 characters
|
|
8929
|
+
- Skip hashtags unless essential
|
|
8930
|
+
- Don't end with generic questions
|
|
8931
|
+
|
|
8932
|
+
Your interests: ${runtime.character.topics?.join(", ") || "technology, AI, web3"}
|
|
8933
|
+
${runtime.character.style ? `Your style: ${typeof runtime.character.style === "object" ? runtime.character.style.all?.join(", ") || "" : runtime.character.style}` : ""}
|
|
8683
8934
|
|
|
8684
8935
|
Tweet:`;
|
|
8685
|
-
const response = await runtime.useModel(
|
|
8936
|
+
const response = await runtime.useModel(ModelType5.TEXT_SMALL, {
|
|
8686
8937
|
prompt: tweetPrompt,
|
|
8687
8938
|
max_tokens: 100,
|
|
8688
|
-
temperature: 0.
|
|
8939
|
+
temperature: 0.9
|
|
8940
|
+
// Higher for more creativity
|
|
8689
8941
|
});
|
|
8690
8942
|
finalTweetText = response.trim();
|
|
8691
8943
|
}
|
|
@@ -8702,16 +8954,19 @@ Tweet:`;
|
|
|
8702
8954
|
}
|
|
8703
8955
|
const tweetUrl = `https://twitter.com/${client.profile.username}/status/${tweetId}`;
|
|
8704
8956
|
logger9.info(`Successfully posted tweet: ${tweetId}`);
|
|
8705
|
-
await runtime.createMemory(
|
|
8706
|
-
|
|
8707
|
-
|
|
8708
|
-
|
|
8709
|
-
|
|
8710
|
-
|
|
8711
|
-
|
|
8957
|
+
await runtime.createMemory(
|
|
8958
|
+
{
|
|
8959
|
+
entityId: runtime.agentId,
|
|
8960
|
+
content: {
|
|
8961
|
+
text: finalTweetText,
|
|
8962
|
+
url: tweetUrl,
|
|
8963
|
+
source: "twitter",
|
|
8964
|
+
action: "POST_TWEET"
|
|
8965
|
+
},
|
|
8966
|
+
roomId: message.roomId
|
|
8712
8967
|
},
|
|
8713
|
-
|
|
8714
|
-
|
|
8968
|
+
"messages"
|
|
8969
|
+
);
|
|
8715
8970
|
if (callback) {
|
|
8716
8971
|
await callback({
|
|
8717
8972
|
text: `I've posted a tweet: "${finalTweetText}"
|
|
@@ -8723,7 +8978,7 @@ View it here: ${tweetUrl}`,
|
|
|
8723
8978
|
}
|
|
8724
8979
|
});
|
|
8725
8980
|
}
|
|
8726
|
-
return
|
|
8981
|
+
return;
|
|
8727
8982
|
} else {
|
|
8728
8983
|
throw new Error("Failed to post tweet - no response data");
|
|
8729
8984
|
}
|
|
@@ -8735,7 +8990,7 @@ View it here: ${tweetUrl}`,
|
|
|
8735
8990
|
metadata: { error: error.message }
|
|
8736
8991
|
});
|
|
8737
8992
|
}
|
|
8738
|
-
return
|
|
8993
|
+
return;
|
|
8739
8994
|
}
|
|
8740
8995
|
},
|
|
8741
8996
|
examples: [
|