@elizaos/plugin-twitter 1.2.14 → 1.2.15
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/README.md +20 -4
- package/dist/index.js +359 -230
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -5,10 +5,10 @@ var __export = (target, all) => {
|
|
|
5
5
|
};
|
|
6
6
|
|
|
7
7
|
// src/index.ts
|
|
8
|
-
import { logger as
|
|
8
|
+
import { logger as logger11 } from "@elizaos/core";
|
|
9
9
|
|
|
10
10
|
// src/services/twitter.service.ts
|
|
11
|
-
import { Service, logger as
|
|
11
|
+
import { Service, logger as logger9 } from "@elizaos/core";
|
|
12
12
|
|
|
13
13
|
// src/interactions.ts
|
|
14
14
|
import {
|
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
EventType,
|
|
17
17
|
ModelType,
|
|
18
18
|
createUniqueUuid as createUniqueUuid2,
|
|
19
|
-
logger as
|
|
19
|
+
logger as logger4
|
|
20
20
|
} from "@elizaos/core";
|
|
21
21
|
|
|
22
22
|
// src/client/auth.ts
|
|
@@ -2014,6 +2014,9 @@ var parseActionResponseFromText = (text) => {
|
|
|
2014
2014
|
return { actions };
|
|
2015
2015
|
};
|
|
2016
2016
|
|
|
2017
|
+
// src/environment.ts
|
|
2018
|
+
import { logger as logger3 } from "@elizaos/core";
|
|
2019
|
+
|
|
2017
2020
|
// src/utils/settings.ts
|
|
2018
2021
|
function getSetting(runtime, key, defaultValue) {
|
|
2019
2022
|
if (runtime && typeof runtime.getSetting === "function") {
|
|
@@ -6079,11 +6082,24 @@ var twitterEnvSchema = external_exports.object({
|
|
|
6079
6082
|
// likes, retweets, quotes
|
|
6080
6083
|
// Timing configuration (all in minutes)
|
|
6081
6084
|
TWITTER_POST_INTERVAL: external_exports.string().default("120"),
|
|
6082
|
-
// minutes between posts
|
|
6085
|
+
// minutes between posts (deprecated, kept for backwards compatibility)
|
|
6086
|
+
TWITTER_POST_INTERVAL_MIN: external_exports.string().default("90"),
|
|
6087
|
+
// minimum minutes between posts
|
|
6088
|
+
TWITTER_POST_INTERVAL_MAX: external_exports.string().default("150"),
|
|
6089
|
+
// maximum minutes between posts
|
|
6083
6090
|
TWITTER_ENGAGEMENT_INTERVAL: external_exports.string().default("30"),
|
|
6084
|
-
// minutes between all interactions
|
|
6091
|
+
// minutes between all interactions (deprecated, kept for backwards compatibility)
|
|
6092
|
+
TWITTER_ENGAGEMENT_INTERVAL_MIN: external_exports.string().default("20"),
|
|
6093
|
+
// minimum minutes between engagements
|
|
6094
|
+
TWITTER_ENGAGEMENT_INTERVAL_MAX: external_exports.string().default("40"),
|
|
6095
|
+
// maximum minutes between engagements
|
|
6096
|
+
TWITTER_DISCOVERY_INTERVAL_MIN: external_exports.string().default("15"),
|
|
6097
|
+
// minimum minutes between discovery cycles
|
|
6098
|
+
TWITTER_DISCOVERY_INTERVAL_MAX: external_exports.string().default("30"),
|
|
6099
|
+
// maximum minutes between discovery cycles
|
|
6085
6100
|
// Limits
|
|
6086
|
-
TWITTER_MAX_ENGAGEMENTS_PER_RUN: external_exports.string().default("
|
|
6101
|
+
TWITTER_MAX_ENGAGEMENTS_PER_RUN: external_exports.string().default("5"),
|
|
6102
|
+
// Reduced from 10 to be less aggressive
|
|
6087
6103
|
TWITTER_MAX_TWEET_LENGTH: external_exports.string().default("280"),
|
|
6088
6104
|
// standard tweet length
|
|
6089
6105
|
// Advanced
|
|
@@ -6145,16 +6161,52 @@ async function validateTwitterConfig(runtime, config = {}) {
|
|
|
6145
6161
|
120
|
|
6146
6162
|
)
|
|
6147
6163
|
),
|
|
6164
|
+
TWITTER_POST_INTERVAL_MIN: String(
|
|
6165
|
+
safeParseInt(
|
|
6166
|
+
config.TWITTER_POST_INTERVAL_MIN ?? getSetting(runtime, "TWITTER_POST_INTERVAL_MIN"),
|
|
6167
|
+
90
|
|
6168
|
+
)
|
|
6169
|
+
),
|
|
6170
|
+
TWITTER_POST_INTERVAL_MAX: String(
|
|
6171
|
+
safeParseInt(
|
|
6172
|
+
config.TWITTER_POST_INTERVAL_MAX ?? getSetting(runtime, "TWITTER_POST_INTERVAL_MAX"),
|
|
6173
|
+
150
|
|
6174
|
+
)
|
|
6175
|
+
),
|
|
6148
6176
|
TWITTER_ENGAGEMENT_INTERVAL: String(
|
|
6149
6177
|
safeParseInt(
|
|
6150
6178
|
config.TWITTER_ENGAGEMENT_INTERVAL ?? getSetting(runtime, "TWITTER_ENGAGEMENT_INTERVAL"),
|
|
6151
6179
|
30
|
|
6152
6180
|
)
|
|
6153
6181
|
),
|
|
6182
|
+
TWITTER_ENGAGEMENT_INTERVAL_MIN: String(
|
|
6183
|
+
safeParseInt(
|
|
6184
|
+
config.TWITTER_ENGAGEMENT_INTERVAL_MIN ?? getSetting(runtime, "TWITTER_ENGAGEMENT_INTERVAL_MIN"),
|
|
6185
|
+
20
|
|
6186
|
+
)
|
|
6187
|
+
),
|
|
6188
|
+
TWITTER_ENGAGEMENT_INTERVAL_MAX: String(
|
|
6189
|
+
safeParseInt(
|
|
6190
|
+
config.TWITTER_ENGAGEMENT_INTERVAL_MAX ?? getSetting(runtime, "TWITTER_ENGAGEMENT_INTERVAL_MAX"),
|
|
6191
|
+
40
|
|
6192
|
+
)
|
|
6193
|
+
),
|
|
6194
|
+
TWITTER_DISCOVERY_INTERVAL_MIN: String(
|
|
6195
|
+
safeParseInt(
|
|
6196
|
+
config.TWITTER_DISCOVERY_INTERVAL_MIN ?? getSetting(runtime, "TWITTER_DISCOVERY_INTERVAL_MIN"),
|
|
6197
|
+
15
|
|
6198
|
+
)
|
|
6199
|
+
),
|
|
6200
|
+
TWITTER_DISCOVERY_INTERVAL_MAX: String(
|
|
6201
|
+
safeParseInt(
|
|
6202
|
+
config.TWITTER_DISCOVERY_INTERVAL_MAX ?? getSetting(runtime, "TWITTER_DISCOVERY_INTERVAL_MAX"),
|
|
6203
|
+
30
|
|
6204
|
+
)
|
|
6205
|
+
),
|
|
6154
6206
|
TWITTER_MAX_ENGAGEMENTS_PER_RUN: String(
|
|
6155
6207
|
safeParseInt(
|
|
6156
6208
|
config.TWITTER_MAX_ENGAGEMENTS_PER_RUN ?? getSetting(runtime, "TWITTER_MAX_ENGAGEMENTS_PER_RUN"),
|
|
6157
|
-
|
|
6209
|
+
5
|
|
6158
6210
|
)
|
|
6159
6211
|
),
|
|
6160
6212
|
TWITTER_MAX_TWEET_LENGTH: String(
|
|
@@ -6186,6 +6238,49 @@ async function validateTwitterConfig(runtime, config = {}) {
|
|
|
6186
6238
|
throw error;
|
|
6187
6239
|
}
|
|
6188
6240
|
}
|
|
6241
|
+
function getRandomInterval(runtime, type) {
|
|
6242
|
+
let minInterval;
|
|
6243
|
+
let maxInterval;
|
|
6244
|
+
let fallbackInterval;
|
|
6245
|
+
switch (type) {
|
|
6246
|
+
case "post":
|
|
6247
|
+
const postMin = getSetting(runtime, "TWITTER_POST_INTERVAL_MIN");
|
|
6248
|
+
const postMax = getSetting(runtime, "TWITTER_POST_INTERVAL_MAX");
|
|
6249
|
+
minInterval = postMin ? safeParseInt(postMin, 0) : void 0;
|
|
6250
|
+
maxInterval = postMax ? safeParseInt(postMax, 0) : void 0;
|
|
6251
|
+
fallbackInterval = safeParseInt(
|
|
6252
|
+
getSetting(runtime, "TWITTER_POST_INTERVAL"),
|
|
6253
|
+
120
|
|
6254
|
+
);
|
|
6255
|
+
break;
|
|
6256
|
+
case "engagement":
|
|
6257
|
+
const engagementMin = getSetting(runtime, "TWITTER_ENGAGEMENT_INTERVAL_MIN");
|
|
6258
|
+
const engagementMax = getSetting(runtime, "TWITTER_ENGAGEMENT_INTERVAL_MAX");
|
|
6259
|
+
minInterval = engagementMin ? safeParseInt(engagementMin, 0) : void 0;
|
|
6260
|
+
maxInterval = engagementMax ? safeParseInt(engagementMax, 0) : void 0;
|
|
6261
|
+
fallbackInterval = safeParseInt(
|
|
6262
|
+
getSetting(runtime, "TWITTER_ENGAGEMENT_INTERVAL"),
|
|
6263
|
+
30
|
|
6264
|
+
);
|
|
6265
|
+
break;
|
|
6266
|
+
case "discovery":
|
|
6267
|
+
const discoveryMin = getSetting(runtime, "TWITTER_DISCOVERY_INTERVAL_MIN");
|
|
6268
|
+
const discoveryMax = getSetting(runtime, "TWITTER_DISCOVERY_INTERVAL_MAX");
|
|
6269
|
+
minInterval = discoveryMin ? safeParseInt(discoveryMin, 0) : void 0;
|
|
6270
|
+
maxInterval = discoveryMax ? safeParseInt(discoveryMax, 0) : void 0;
|
|
6271
|
+
fallbackInterval = 20;
|
|
6272
|
+
break;
|
|
6273
|
+
default:
|
|
6274
|
+
throw new Error(`Unknown interval type: ${type}`);
|
|
6275
|
+
}
|
|
6276
|
+
if (minInterval !== void 0 && maxInterval !== void 0 && minInterval < maxInterval) {
|
|
6277
|
+
const randomInterval = Math.random() * (maxInterval - minInterval) + minInterval;
|
|
6278
|
+
logger3.debug(`Random ${type} interval: ${randomInterval.toFixed(1)} minutes (between ${minInterval}-${maxInterval})`);
|
|
6279
|
+
return randomInterval;
|
|
6280
|
+
}
|
|
6281
|
+
logger3.debug(`Using fixed ${type} interval: ${fallbackInterval} minutes`);
|
|
6282
|
+
return fallbackInterval;
|
|
6283
|
+
}
|
|
6189
6284
|
|
|
6190
6285
|
// src/interactions.ts
|
|
6191
6286
|
var TwitterInteractionClient = class {
|
|
@@ -6213,15 +6308,13 @@ var TwitterInteractionClient = class {
|
|
|
6213
6308
|
this.isRunning = true;
|
|
6214
6309
|
const handleTwitterInteractionsLoop = () => {
|
|
6215
6310
|
if (!this.isRunning) {
|
|
6216
|
-
|
|
6311
|
+
logger4.info("Twitter interaction client stopped, exiting loop");
|
|
6217
6312
|
return;
|
|
6218
6313
|
}
|
|
6219
|
-
const engagementIntervalMinutes =
|
|
6220
|
-
this.state?.TWITTER_ENGAGEMENT_INTERVAL || getSetting(this.runtime, "TWITTER_ENGAGEMENT_INTERVAL") || process.env.TWITTER_ENGAGEMENT_INTERVAL || "30"
|
|
6221
|
-
);
|
|
6314
|
+
const engagementIntervalMinutes = getRandomInterval(this.runtime, "engagement");
|
|
6222
6315
|
const interactionInterval = engagementIntervalMinutes * 60 * 1e3;
|
|
6223
|
-
|
|
6224
|
-
`Twitter interaction client will check
|
|
6316
|
+
logger4.info(
|
|
6317
|
+
`Twitter interaction client will check in ${engagementIntervalMinutes.toFixed(1)} minutes`
|
|
6225
6318
|
);
|
|
6226
6319
|
this.handleTwitterInteractions();
|
|
6227
6320
|
if (this.isRunning) {
|
|
@@ -6234,14 +6327,14 @@ var TwitterInteractionClient = class {
|
|
|
6234
6327
|
* Stops the Twitter interaction client
|
|
6235
6328
|
*/
|
|
6236
6329
|
async stop() {
|
|
6237
|
-
|
|
6330
|
+
logger4.log("Stopping Twitter interaction client...");
|
|
6238
6331
|
this.isRunning = false;
|
|
6239
6332
|
}
|
|
6240
6333
|
/**
|
|
6241
6334
|
* Asynchronously handles Twitter interactions by checking for mentions and target user posts.
|
|
6242
6335
|
*/
|
|
6243
6336
|
async handleTwitterInteractions() {
|
|
6244
|
-
|
|
6337
|
+
logger4.log("Checking Twitter interactions");
|
|
6245
6338
|
const twitterUsername = this.client.profile?.username;
|
|
6246
6339
|
try {
|
|
6247
6340
|
const repliesEnabled = (getSetting(this.runtime, "TWITTER_ENABLE_REPLIES") ?? process.env.TWITTER_ENABLE_REPLIES) !== "false";
|
|
@@ -6253,9 +6346,9 @@ var TwitterInteractionClient = class {
|
|
|
6253
6346
|
await this.handleTargetUserPosts(targetUsersConfig);
|
|
6254
6347
|
}
|
|
6255
6348
|
await this.client.cacheLatestCheckedTweetId();
|
|
6256
|
-
|
|
6349
|
+
logger4.log("Finished checking Twitter interactions");
|
|
6257
6350
|
} catch (error) {
|
|
6258
|
-
|
|
6351
|
+
logger4.error("Error handling Twitter interactions:", error);
|
|
6259
6352
|
}
|
|
6260
6353
|
}
|
|
6261
6354
|
/**
|
|
@@ -6279,7 +6372,7 @@ var TwitterInteractionClient = class {
|
|
|
6279
6372
|
}
|
|
6280
6373
|
await this.processMentionTweets(mentionCandidates);
|
|
6281
6374
|
} catch (error) {
|
|
6282
|
-
|
|
6375
|
+
logger4.error("Error handling mentions:", error);
|
|
6283
6376
|
}
|
|
6284
6377
|
}
|
|
6285
6378
|
/**
|
|
@@ -6291,7 +6384,7 @@ var TwitterInteractionClient = class {
|
|
|
6291
6384
|
if (targetUsers.length === 0 && !targetUsersConfig.includes("*")) {
|
|
6292
6385
|
return;
|
|
6293
6386
|
}
|
|
6294
|
-
|
|
6387
|
+
logger4.info(
|
|
6295
6388
|
`Checking posts from target users: ${targetUsers.join(", ") || "everyone (*)"}`
|
|
6296
6389
|
);
|
|
6297
6390
|
for (const targetUser of targetUsers) {
|
|
@@ -6305,7 +6398,7 @@ var TwitterInteractionClient = class {
|
|
|
6305
6398
|
1 /* Latest */
|
|
6306
6399
|
);
|
|
6307
6400
|
if (searchResult.tweets.length > 0) {
|
|
6308
|
-
|
|
6401
|
+
logger4.info(
|
|
6309
6402
|
`Found ${searchResult.tweets.length} posts from @${normalizedUsername}`
|
|
6310
6403
|
);
|
|
6311
6404
|
await this.processTargetUserTweets(
|
|
@@ -6314,14 +6407,14 @@ var TwitterInteractionClient = class {
|
|
|
6314
6407
|
);
|
|
6315
6408
|
}
|
|
6316
6409
|
} catch (error) {
|
|
6317
|
-
|
|
6410
|
+
logger4.error(`Error searching posts from @${targetUser}:`, error);
|
|
6318
6411
|
}
|
|
6319
6412
|
}
|
|
6320
6413
|
if (targetUsersConfig.includes("*")) {
|
|
6321
6414
|
await this.processTimelineForEngagement();
|
|
6322
6415
|
}
|
|
6323
6416
|
} catch (error) {
|
|
6324
|
-
|
|
6417
|
+
logger4.error("Error handling target user posts:", error);
|
|
6325
6418
|
}
|
|
6326
6419
|
}
|
|
6327
6420
|
/**
|
|
@@ -6334,7 +6427,7 @@ var TwitterInteractionClient = class {
|
|
|
6334
6427
|
let engagementCount = 0;
|
|
6335
6428
|
for (const tweet of tweets) {
|
|
6336
6429
|
if (engagementCount >= maxEngagementsPerRun) {
|
|
6337
|
-
|
|
6430
|
+
logger4.info(`Reached max engagements limit (${maxEngagementsPerRun})`);
|
|
6338
6431
|
break;
|
|
6339
6432
|
}
|
|
6340
6433
|
const tweetId = createUniqueUuid2(this.runtime, tweet.id);
|
|
@@ -6349,7 +6442,7 @@ var TwitterInteractionClient = class {
|
|
|
6349
6442
|
}
|
|
6350
6443
|
const shouldEngage = await this.shouldEngageWithTweet(tweet);
|
|
6351
6444
|
if (shouldEngage) {
|
|
6352
|
-
|
|
6445
|
+
logger4.info(
|
|
6353
6446
|
`Engaging with tweet from @${username}: ${tweet.text.substring(0, 50)}...`
|
|
6354
6447
|
);
|
|
6355
6448
|
await this.ensureTweetContext(tweet);
|
|
@@ -6375,13 +6468,13 @@ var TwitterInteractionClient = class {
|
|
|
6375
6468
|
return tweetAge < 12 * 60 * 60 * 1e3;
|
|
6376
6469
|
});
|
|
6377
6470
|
if (relevantTweets.length > 0) {
|
|
6378
|
-
|
|
6471
|
+
logger4.info(
|
|
6379
6472
|
`Found ${relevantTweets.length} relevant tweets from timeline`
|
|
6380
6473
|
);
|
|
6381
6474
|
await this.processTargetUserTweets(relevantTweets, "timeline");
|
|
6382
6475
|
}
|
|
6383
6476
|
} catch (error) {
|
|
6384
|
-
|
|
6477
|
+
logger4.error("Error processing timeline for engagement:", error);
|
|
6385
6478
|
}
|
|
6386
6479
|
}
|
|
6387
6480
|
/**
|
|
@@ -6434,7 +6527,7 @@ Response (YES/NO):`;
|
|
|
6434
6527
|
});
|
|
6435
6528
|
return response.trim().toUpperCase().includes("YES");
|
|
6436
6529
|
} catch (error) {
|
|
6437
|
-
|
|
6530
|
+
logger4.error("Error determining engagement:", error);
|
|
6438
6531
|
return false;
|
|
6439
6532
|
}
|
|
6440
6533
|
}
|
|
@@ -6518,7 +6611,7 @@ Response (YES/NO):`;
|
|
|
6518
6611
|
});
|
|
6519
6612
|
return result.text && result.text.length > 0;
|
|
6520
6613
|
} catch (error) {
|
|
6521
|
-
|
|
6614
|
+
logger4.error("Error engaging with tweet:", error);
|
|
6522
6615
|
return false;
|
|
6523
6616
|
}
|
|
6524
6617
|
}
|
|
@@ -6533,7 +6626,7 @@ Response (YES/NO):`;
|
|
|
6533
6626
|
* Note: MENTION_RECEIVED is currently disabled (see TODO below)
|
|
6534
6627
|
*/
|
|
6535
6628
|
async processMentionTweets(mentionCandidates) {
|
|
6536
|
-
|
|
6629
|
+
logger4.log(
|
|
6537
6630
|
"Completed checking mentioned tweets:",
|
|
6538
6631
|
mentionCandidates.length
|
|
6539
6632
|
);
|
|
@@ -6547,7 +6640,7 @@ Response (YES/NO):`;
|
|
|
6547
6640
|
targetUsersConfig
|
|
6548
6641
|
);
|
|
6549
6642
|
if (!shouldTarget) {
|
|
6550
|
-
|
|
6643
|
+
logger4.log(
|
|
6551
6644
|
`Skipping tweet from @${tweet.username} - not in target users list`
|
|
6552
6645
|
);
|
|
6553
6646
|
}
|
|
@@ -6561,7 +6654,7 @@ Response (YES/NO):`;
|
|
|
6561
6654
|
0,
|
|
6562
6655
|
maxInteractionsPerRun
|
|
6563
6656
|
);
|
|
6564
|
-
|
|
6657
|
+
logger4.info(
|
|
6565
6658
|
`Processing ${tweetsToProcess.length} of ${uniqueTweetCandidates.length} mention tweets (max: ${maxInteractionsPerRun})`
|
|
6566
6659
|
);
|
|
6567
6660
|
for (const tweet of tweetsToProcess) {
|
|
@@ -6569,7 +6662,7 @@ Response (YES/NO):`;
|
|
|
6569
6662
|
const tweetId = createUniqueUuid2(this.runtime, tweet.id);
|
|
6570
6663
|
const existingResponse = await this.runtime.getMemoryById(tweetId);
|
|
6571
6664
|
if (existingResponse) {
|
|
6572
|
-
|
|
6665
|
+
logger4.log(`Already responded to tweet ${tweet.id}, skipping`);
|
|
6573
6666
|
continue;
|
|
6574
6667
|
}
|
|
6575
6668
|
const conversationRoomId = createUniqueUuid2(
|
|
@@ -6586,22 +6679,22 @@ Response (YES/NO):`;
|
|
|
6586
6679
|
(memory2) => memory2.content.inReplyTo === tweetId || memory2.content.inReplyTo === tweet.id
|
|
6587
6680
|
);
|
|
6588
6681
|
if (hasExistingReply) {
|
|
6589
|
-
|
|
6682
|
+
logger4.log(
|
|
6590
6683
|
`Already responded to tweet ${tweet.id} (found in conversation history), skipping`
|
|
6591
6684
|
);
|
|
6592
6685
|
continue;
|
|
6593
6686
|
}
|
|
6594
|
-
|
|
6687
|
+
logger4.log("New Tweet found", tweet.id);
|
|
6595
6688
|
const userId = tweet.userId;
|
|
6596
6689
|
const conversationId = tweet.conversationId || tweet.id;
|
|
6597
6690
|
const roomId = createUniqueUuid2(this.runtime, conversationId);
|
|
6598
6691
|
const username = tweet.username;
|
|
6599
|
-
|
|
6600
|
-
|
|
6601
|
-
|
|
6602
|
-
|
|
6603
|
-
|
|
6604
|
-
|
|
6692
|
+
logger4.log("----");
|
|
6693
|
+
logger4.log(`User: ${username} (${userId})`);
|
|
6694
|
+
logger4.log(`Tweet: ${tweet.id}`);
|
|
6695
|
+
logger4.log(`Conversation: ${conversationId}`);
|
|
6696
|
+
logger4.log(`Room: ${roomId}`);
|
|
6697
|
+
logger4.log("----");
|
|
6605
6698
|
const worldId = createUniqueUuid2(this.runtime, userId);
|
|
6606
6699
|
await this.runtime.ensureWorldExists({
|
|
6607
6700
|
id: worldId,
|
|
@@ -6648,7 +6741,7 @@ Response (YES/NO):`;
|
|
|
6648
6741
|
roomId,
|
|
6649
6742
|
createdAt: tweet.timestamp * 1e3
|
|
6650
6743
|
};
|
|
6651
|
-
|
|
6744
|
+
logger4.log("Saving tweet memory...");
|
|
6652
6745
|
await this.runtime.createMemory(memory, "messages");
|
|
6653
6746
|
if (tweet.thread && tweet.thread.length > 0) {
|
|
6654
6747
|
const threadStartId = tweet.thread[0].id;
|
|
@@ -6831,23 +6924,23 @@ Response (YES/NO):`;
|
|
|
6831
6924
|
thread
|
|
6832
6925
|
}) {
|
|
6833
6926
|
if (!message.content.text) {
|
|
6834
|
-
|
|
6927
|
+
logger4.log("Skipping Tweet with no text", tweet.id);
|
|
6835
6928
|
return { text: "", actions: ["IGNORE"] };
|
|
6836
6929
|
}
|
|
6837
6930
|
const callback = async (response2, tweetId) => {
|
|
6838
6931
|
try {
|
|
6839
6932
|
if (!response2.text) {
|
|
6840
|
-
|
|
6933
|
+
logger4.warn("No text content in response, skipping tweet reply");
|
|
6841
6934
|
return [];
|
|
6842
6935
|
}
|
|
6843
6936
|
const tweetToReplyTo = tweetId || tweet.id;
|
|
6844
6937
|
if (this.isDryRun) {
|
|
6845
|
-
|
|
6938
|
+
logger4.info(
|
|
6846
6939
|
`[DRY RUN] Would have replied to ${tweet.username} with: ${response2.text}`
|
|
6847
6940
|
);
|
|
6848
6941
|
return [];
|
|
6849
6942
|
}
|
|
6850
|
-
|
|
6943
|
+
logger4.info(`Replying to tweet ${tweetToReplyTo}`);
|
|
6851
6944
|
const tweetResult = await sendTweet(
|
|
6852
6945
|
this.client,
|
|
6853
6946
|
response2.text,
|
|
@@ -6873,7 +6966,7 @@ Response (YES/NO):`;
|
|
|
6873
6966
|
await this.runtime.createMemory(responseMemory, "messages");
|
|
6874
6967
|
return [responseMemory];
|
|
6875
6968
|
} catch (error) {
|
|
6876
|
-
|
|
6969
|
+
logger4.error("Error in tweet reply callback:", error);
|
|
6877
6970
|
return [];
|
|
6878
6971
|
}
|
|
6879
6972
|
};
|
|
@@ -6914,7 +7007,7 @@ Response (YES/NO):`;
|
|
|
6914
7007
|
import {
|
|
6915
7008
|
ChannelType as ChannelType2,
|
|
6916
7009
|
createUniqueUuid as createUniqueUuid3,
|
|
6917
|
-
logger as
|
|
7010
|
+
logger as logger5,
|
|
6918
7011
|
ModelType as ModelType2
|
|
6919
7012
|
} from "@elizaos/core";
|
|
6920
7013
|
var TwitterPostClient = class {
|
|
@@ -6933,39 +7026,40 @@ var TwitterPostClient = class {
|
|
|
6933
7026
|
this.runtime = runtime;
|
|
6934
7027
|
const dryRunSetting = this.state?.TWITTER_DRY_RUN ?? getSetting(this.runtime, "TWITTER_DRY_RUN") ?? process.env.TWITTER_DRY_RUN;
|
|
6935
7028
|
this.isDryRun = dryRunSetting === true || dryRunSetting === "true" || typeof dryRunSetting === "string" && dryRunSetting.toLowerCase() === "true";
|
|
6936
|
-
|
|
6937
|
-
|
|
6938
|
-
const
|
|
6939
|
-
this.state?.
|
|
7029
|
+
logger5.log("Twitter Post Client Configuration:");
|
|
7030
|
+
logger5.log(`- Dry Run Mode: ${this.isDryRun ? "Enabled" : "Disabled"}`);
|
|
7031
|
+
const postIntervalMin = parseInt(
|
|
7032
|
+
this.state?.TWITTER_POST_INTERVAL_MIN || getSetting(this.runtime, "TWITTER_POST_INTERVAL_MIN") || process.env.TWITTER_POST_INTERVAL_MIN || "90"
|
|
6940
7033
|
);
|
|
6941
|
-
|
|
7034
|
+
const postIntervalMax = parseInt(
|
|
7035
|
+
this.state?.TWITTER_POST_INTERVAL_MAX || getSetting(this.runtime, "TWITTER_POST_INTERVAL_MAX") || process.env.TWITTER_POST_INTERVAL_MAX || "150"
|
|
7036
|
+
);
|
|
7037
|
+
logger5.log(`- Post Interval: ${postIntervalMin}-${postIntervalMax} minutes (randomized)`);
|
|
6942
7038
|
}
|
|
6943
7039
|
/**
|
|
6944
7040
|
* Stops the Twitter post client
|
|
6945
7041
|
*/
|
|
6946
7042
|
async stop() {
|
|
6947
|
-
|
|
7043
|
+
logger5.log("Stopping Twitter post client...");
|
|
6948
7044
|
this.isRunning = false;
|
|
6949
7045
|
}
|
|
6950
7046
|
/**
|
|
6951
7047
|
* Starts the Twitter post client, setting up a loop to periodically generate new tweets.
|
|
6952
7048
|
*/
|
|
6953
7049
|
async start() {
|
|
6954
|
-
|
|
7050
|
+
logger5.log("Starting Twitter post client...");
|
|
6955
7051
|
this.isRunning = true;
|
|
6956
7052
|
const generateNewTweetLoop = async () => {
|
|
6957
7053
|
if (!this.isRunning) {
|
|
6958
|
-
|
|
7054
|
+
logger5.log("Twitter post client stopped, exiting loop");
|
|
6959
7055
|
return;
|
|
6960
7056
|
}
|
|
6961
|
-
const postIntervalMinutes =
|
|
6962
|
-
this.state?.TWITTER_POST_INTERVAL || getSetting(this.runtime, "TWITTER_POST_INTERVAL") || process.env.TWITTER_POST_INTERVAL || "120"
|
|
6963
|
-
);
|
|
7057
|
+
const postIntervalMinutes = getRandomInterval(this.runtime, "post");
|
|
6964
7058
|
const interval = postIntervalMinutes * 60 * 1e3;
|
|
6965
|
-
|
|
7059
|
+
logger5.info(`Next tweet scheduled in ${postIntervalMinutes.toFixed(1)} minutes`);
|
|
6966
7060
|
await new Promise((resolve) => setTimeout(resolve, interval));
|
|
6967
7061
|
if (!this.isRunning) {
|
|
6968
|
-
|
|
7062
|
+
logger5.log("Twitter post client stopped during wait, exiting loop");
|
|
6969
7063
|
return;
|
|
6970
7064
|
}
|
|
6971
7065
|
await this.generateNewTweet();
|
|
@@ -6976,7 +7070,7 @@ var TwitterPostClient = class {
|
|
|
6976
7070
|
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
6977
7071
|
const postImmediately = this.state?.TWITTER_POST_IMMEDIATELY || getSetting(this.runtime, "TWITTER_POST_IMMEDIATELY") || process.env.TWITTER_POST_IMMEDIATELY;
|
|
6978
7072
|
if (postImmediately === "true" || postImmediately === true) {
|
|
6979
|
-
|
|
7073
|
+
logger5.info(
|
|
6980
7074
|
"TWITTER_POST_IMMEDIATELY is true, generating initial tweet now"
|
|
6981
7075
|
);
|
|
6982
7076
|
await this.generateNewTweet();
|
|
@@ -6988,19 +7082,19 @@ var TwitterPostClient = class {
|
|
|
6988
7082
|
* This approach aligns with our platform-independent architecture.
|
|
6989
7083
|
*/
|
|
6990
7084
|
async generateNewTweet() {
|
|
6991
|
-
|
|
7085
|
+
logger5.info("Attempting to generate new tweet...");
|
|
6992
7086
|
if (this.isPosting) {
|
|
6993
|
-
|
|
7087
|
+
logger5.info("Already posting a tweet, skipping concurrent attempt");
|
|
6994
7088
|
return;
|
|
6995
7089
|
}
|
|
6996
7090
|
this.isPosting = true;
|
|
6997
7091
|
try {
|
|
6998
7092
|
const userId = this.client.profile?.id;
|
|
6999
7093
|
if (!userId) {
|
|
7000
|
-
|
|
7094
|
+
logger5.error("Cannot generate tweet: Twitter profile not available");
|
|
7001
7095
|
return;
|
|
7002
7096
|
}
|
|
7003
|
-
|
|
7097
|
+
logger5.info(
|
|
7004
7098
|
`Generating tweet for user: ${this.client.profile?.username} (${userId})`
|
|
7005
7099
|
);
|
|
7006
7100
|
const worldId = createUniqueUuid3(this.runtime, userId);
|
|
@@ -7012,7 +7106,7 @@ var TwitterPostClient = class {
|
|
|
7012
7106
|
content: { text: "", type: "post" },
|
|
7013
7107
|
createdAt: Date.now()
|
|
7014
7108
|
}).catch((error) => {
|
|
7015
|
-
|
|
7109
|
+
logger5.warn("Error composing state, using minimal state:", error);
|
|
7016
7110
|
return {
|
|
7017
7111
|
agentId: this.runtime.agentId,
|
|
7018
7112
|
recentMemories: [],
|
|
@@ -7060,15 +7154,15 @@ Generate a single tweet that sounds like YOU would actually write it:`;
|
|
|
7060
7154
|
);
|
|
7061
7155
|
const tweetText = generatedContent.trim();
|
|
7062
7156
|
if (!tweetText || tweetText.length === 0) {
|
|
7063
|
-
|
|
7157
|
+
logger5.error("Generated empty tweet content");
|
|
7064
7158
|
return;
|
|
7065
7159
|
}
|
|
7066
7160
|
if (tweetText.includes("Error: Missing")) {
|
|
7067
|
-
|
|
7161
|
+
logger5.error("Error in generated content:", tweetText);
|
|
7068
7162
|
return;
|
|
7069
7163
|
}
|
|
7070
7164
|
if (tweetText.length > 280) {
|
|
7071
|
-
|
|
7165
|
+
logger5.warn(`Generated tweet too long (${tweetText.length} chars), truncating...`);
|
|
7072
7166
|
const sentences = tweetText.match(/[^.!?]+[.!?]+/g) || [tweetText];
|
|
7073
7167
|
let truncated = "";
|
|
7074
7168
|
for (const sentence of sentences) {
|
|
@@ -7079,33 +7173,33 @@ Generate a single tweet that sounds like YOU would actually write it:`;
|
|
|
7079
7173
|
}
|
|
7080
7174
|
}
|
|
7081
7175
|
const finalTweet = truncated.trim() || tweetText.substring(0, 277) + "...";
|
|
7082
|
-
|
|
7176
|
+
logger5.info(`Truncated tweet: ${finalTweet}`);
|
|
7083
7177
|
if (this.isDryRun) {
|
|
7084
|
-
|
|
7178
|
+
logger5.info(`[DRY RUN] Would post tweet: ${finalTweet}`);
|
|
7085
7179
|
return;
|
|
7086
7180
|
}
|
|
7087
7181
|
const result2 = await this.postToTwitter(finalTweet, []);
|
|
7088
7182
|
if (result2 === null) {
|
|
7089
|
-
|
|
7183
|
+
logger5.info("Skipped posting duplicate tweet");
|
|
7090
7184
|
return;
|
|
7091
7185
|
}
|
|
7092
7186
|
const tweetId2 = result2.id;
|
|
7093
|
-
|
|
7094
|
-
|
|
7187
|
+
logger5.info(`Tweet posted successfully! ID: ${tweetId2}`);
|
|
7188
|
+
logger5.info("Tweet posted successfully (memory saving disabled due to room constraints)");
|
|
7095
7189
|
return;
|
|
7096
7190
|
}
|
|
7097
|
-
|
|
7191
|
+
logger5.info(`Generated tweet: ${tweetText}`);
|
|
7098
7192
|
if (this.isDryRun) {
|
|
7099
|
-
|
|
7193
|
+
logger5.info(`[DRY RUN] Would post tweet: ${tweetText}`);
|
|
7100
7194
|
return;
|
|
7101
7195
|
}
|
|
7102
7196
|
const result = await this.postToTwitter(tweetText, []);
|
|
7103
7197
|
if (result === null) {
|
|
7104
|
-
|
|
7198
|
+
logger5.info("Skipped posting duplicate tweet");
|
|
7105
7199
|
return;
|
|
7106
7200
|
}
|
|
7107
7201
|
const tweetId = result.id;
|
|
7108
|
-
|
|
7202
|
+
logger5.info(`Tweet posted successfully! ID: ${tweetId}`);
|
|
7109
7203
|
if (result) {
|
|
7110
7204
|
const postedTweetId = createUniqueUuid3(this.runtime, tweetId);
|
|
7111
7205
|
await this.runtime.ensureWorldExists({
|
|
@@ -7141,10 +7235,10 @@ Generate a single tweet that sounds like YOU would actually write it:`;
|
|
|
7141
7235
|
createdAt: Date.now()
|
|
7142
7236
|
};
|
|
7143
7237
|
await this.runtime.createMemory(postedMemory, "messages");
|
|
7144
|
-
|
|
7238
|
+
logger5.info("Tweet posted and saved to memory successfully");
|
|
7145
7239
|
}
|
|
7146
7240
|
} catch (error) {
|
|
7147
|
-
|
|
7241
|
+
logger5.error("Error generating tweet:", error);
|
|
7148
7242
|
} finally {
|
|
7149
7243
|
this.isPosting = false;
|
|
7150
7244
|
}
|
|
@@ -7163,7 +7257,7 @@ Generate a single tweet that sounds like YOU would actually write it:`;
|
|
|
7163
7257
|
if (lastPost) {
|
|
7164
7258
|
const lastTweet = await this.client.getTweet(lastPost.id);
|
|
7165
7259
|
if (lastTweet && lastTweet.text === text) {
|
|
7166
|
-
|
|
7260
|
+
logger5.warn(
|
|
7167
7261
|
"Tweet is a duplicate of the last post. Skipping to avoid duplicate."
|
|
7168
7262
|
);
|
|
7169
7263
|
return null;
|
|
@@ -7173,11 +7267,11 @@ Generate a single tweet that sounds like YOU would actually write it:`;
|
|
|
7173
7267
|
if (mediaData && mediaData.length > 0) {
|
|
7174
7268
|
for (const media of mediaData) {
|
|
7175
7269
|
try {
|
|
7176
|
-
|
|
7270
|
+
logger5.warn(
|
|
7177
7271
|
"Media upload not currently supported with the modern Twitter API"
|
|
7178
7272
|
);
|
|
7179
7273
|
} catch (error) {
|
|
7180
|
-
|
|
7274
|
+
logger5.error("Error uploading media:", error);
|
|
7181
7275
|
}
|
|
7182
7276
|
}
|
|
7183
7277
|
}
|
|
@@ -7192,7 +7286,7 @@ Generate a single tweet that sounds like YOU would actually write it:`;
|
|
|
7192
7286
|
);
|
|
7193
7287
|
return result;
|
|
7194
7288
|
} catch (error) {
|
|
7195
|
-
|
|
7289
|
+
logger5.error("Error posting to Twitter:", error);
|
|
7196
7290
|
throw error;
|
|
7197
7291
|
}
|
|
7198
7292
|
}
|
|
@@ -7206,7 +7300,7 @@ import {
|
|
|
7206
7300
|
ModelType as ModelType3,
|
|
7207
7301
|
parseKeyValueXml
|
|
7208
7302
|
} from "@elizaos/core";
|
|
7209
|
-
import { logger as
|
|
7303
|
+
import { logger as logger6 } from "@elizaos/core";
|
|
7210
7304
|
|
|
7211
7305
|
// src/templates.ts
|
|
7212
7306
|
var twitterActionTemplate = `
|
|
@@ -7286,18 +7380,18 @@ var TwitterTimelineClient = class {
|
|
|
7286
7380
|
this.timelineType = timelineMode === "following" /* Following */ ? "following" /* Following */ : "foryou" /* ForYou */;
|
|
7287
7381
|
}
|
|
7288
7382
|
async start() {
|
|
7289
|
-
|
|
7383
|
+
logger6.info("Starting Twitter timeline client...");
|
|
7290
7384
|
this.isRunning = true;
|
|
7291
7385
|
const handleTwitterTimelineLoop = () => {
|
|
7292
7386
|
if (!this.isRunning) {
|
|
7293
|
-
|
|
7387
|
+
logger6.info("Twitter timeline client stopped, exiting loop");
|
|
7294
7388
|
return;
|
|
7295
7389
|
}
|
|
7296
7390
|
const engagementIntervalMinutes = parseInt(
|
|
7297
7391
|
this.state?.TWITTER_ENGAGEMENT_INTERVAL || getSetting(this.runtime, "TWITTER_ENGAGEMENT_INTERVAL") || process.env.TWITTER_ENGAGEMENT_INTERVAL || "30"
|
|
7298
7392
|
);
|
|
7299
7393
|
const actionInterval = engagementIntervalMinutes * 60 * 1e3;
|
|
7300
|
-
|
|
7394
|
+
logger6.info(
|
|
7301
7395
|
`Timeline client will check every ${engagementIntervalMinutes} minutes`
|
|
7302
7396
|
);
|
|
7303
7397
|
this.handleTimeline();
|
|
@@ -7308,7 +7402,7 @@ var TwitterTimelineClient = class {
|
|
|
7308
7402
|
handleTwitterTimelineLoop();
|
|
7309
7403
|
}
|
|
7310
7404
|
async stop() {
|
|
7311
|
-
|
|
7405
|
+
logger6.info("Stopping Twitter timeline client...");
|
|
7312
7406
|
this.isRunning = false;
|
|
7313
7407
|
}
|
|
7314
7408
|
async getTimeline(count) {
|
|
@@ -7338,9 +7432,9 @@ var TwitterTimelineClient = class {
|
|
|
7338
7432
|
};
|
|
7339
7433
|
}
|
|
7340
7434
|
async handleTimeline() {
|
|
7341
|
-
|
|
7435
|
+
logger6.info("Starting Twitter timeline processing...");
|
|
7342
7436
|
const tweets = await this.getTimeline(20);
|
|
7343
|
-
|
|
7437
|
+
logger6.info(`Fetched ${tweets.length} tweets from timeline`);
|
|
7344
7438
|
const maxActionsPerCycle = parseInt(
|
|
7345
7439
|
getSetting(this.runtime, "TWITTER_MAX_ENGAGEMENTS_PER_RUN") || process.env.TWITTER_MAX_ENGAGEMENTS_PER_RUN || "10"
|
|
7346
7440
|
);
|
|
@@ -7350,7 +7444,7 @@ var TwitterTimelineClient = class {
|
|
|
7350
7444
|
const tweetId = this.createTweetId(this.runtime, tweet);
|
|
7351
7445
|
const memory = await this.runtime.getMemoryById(tweetId);
|
|
7352
7446
|
if (memory) {
|
|
7353
|
-
|
|
7447
|
+
logger6.log(`Already processed tweet ID: ${tweet.id}`);
|
|
7354
7448
|
continue;
|
|
7355
7449
|
}
|
|
7356
7450
|
const roomId = createUniqueUuid4(this.runtime, tweet.conversationId);
|
|
@@ -7374,7 +7468,7 @@ Choose any combination of [LIKE], [RETWEET], [QUOTE], and [REPLY] that are appro
|
|
|
7374
7468
|
);
|
|
7375
7469
|
const parsedResponse = parseActionResponseFromText(actionResponse);
|
|
7376
7470
|
if (!parsedResponse) {
|
|
7377
|
-
|
|
7471
|
+
logger6.debug(`No action response generated for tweet ${tweet.id}`);
|
|
7378
7472
|
continue;
|
|
7379
7473
|
}
|
|
7380
7474
|
tweetDecisions.push({
|
|
@@ -7385,7 +7479,7 @@ Choose any combination of [LIKE], [RETWEET], [QUOTE], and [REPLY] that are appro
|
|
|
7385
7479
|
});
|
|
7386
7480
|
if (tweetDecisions.length >= maxActionsPerCycle) break;
|
|
7387
7481
|
} catch (error) {
|
|
7388
|
-
|
|
7482
|
+
logger6.error(`Error processing tweet ${tweet.id}:`, error);
|
|
7389
7483
|
}
|
|
7390
7484
|
}
|
|
7391
7485
|
const rankByActionRelevance = (arr) => {
|
|
@@ -7403,7 +7497,7 @@ Choose any combination of [LIKE], [RETWEET], [QUOTE], and [REPLY] that are appro
|
|
|
7403
7497
|
});
|
|
7404
7498
|
};
|
|
7405
7499
|
const prioritizedTweets = rankByActionRelevance(tweetDecisions);
|
|
7406
|
-
|
|
7500
|
+
logger6.info(`Processing ${prioritizedTweets.length} tweets with actions`);
|
|
7407
7501
|
if (prioritizedTweets.length > 0) {
|
|
7408
7502
|
const actionSummary = prioritizedTweets.map((td) => {
|
|
7409
7503
|
const actions = [];
|
|
@@ -7413,11 +7507,11 @@ Choose any combination of [LIKE], [RETWEET], [QUOTE], and [REPLY] that are appro
|
|
|
7413
7507
|
if (td.actionResponse.reply) actions.push("REPLY");
|
|
7414
7508
|
return `Tweet ${td.tweet.id}: ${actions.join(", ")}`;
|
|
7415
7509
|
});
|
|
7416
|
-
|
|
7510
|
+
logger6.info(`Actions to execute:
|
|
7417
7511
|
${actionSummary.join("\n")}`);
|
|
7418
7512
|
}
|
|
7419
7513
|
await this.processTimelineActions(prioritizedTweets);
|
|
7420
|
-
|
|
7514
|
+
logger6.info("Timeline processing complete");
|
|
7421
7515
|
}
|
|
7422
7516
|
async processTimelineActions(tweetDecisions) {
|
|
7423
7517
|
const results = [];
|
|
@@ -7478,7 +7572,7 @@ ${actionSummary.join("\n")}`);
|
|
|
7478
7572
|
}
|
|
7479
7573
|
results.push({ tweetId: tweet.id, actionResponse, executedActions });
|
|
7480
7574
|
} catch (error) {
|
|
7481
|
-
|
|
7575
|
+
logger6.error(`Error processing actions for tweet ${tweet.id}:`, error);
|
|
7482
7576
|
}
|
|
7483
7577
|
}
|
|
7484
7578
|
return results;
|
|
@@ -7522,25 +7616,25 @@ ${actionSummary.join("\n")}`);
|
|
|
7522
7616
|
async handleLikeAction(tweet) {
|
|
7523
7617
|
try {
|
|
7524
7618
|
if (this.isDryRun) {
|
|
7525
|
-
|
|
7619
|
+
logger6.log(`[DRY RUN] Would have liked tweet ${tweet.id}`);
|
|
7526
7620
|
return;
|
|
7527
7621
|
}
|
|
7528
7622
|
await this.twitterClient.likeTweet(tweet.id);
|
|
7529
|
-
|
|
7623
|
+
logger6.log(`Liked tweet ${tweet.id}`);
|
|
7530
7624
|
} catch (error) {
|
|
7531
|
-
|
|
7625
|
+
logger6.error(`Error liking tweet ${tweet.id}:`, error);
|
|
7532
7626
|
}
|
|
7533
7627
|
}
|
|
7534
7628
|
async handleRetweetAction(tweet) {
|
|
7535
7629
|
try {
|
|
7536
7630
|
if (this.isDryRun) {
|
|
7537
|
-
|
|
7631
|
+
logger6.log(`[DRY RUN] Would have retweeted tweet ${tweet.id}`);
|
|
7538
7632
|
return;
|
|
7539
7633
|
}
|
|
7540
7634
|
await this.twitterClient.retweet(tweet.id);
|
|
7541
|
-
|
|
7635
|
+
logger6.log(`Retweeted tweet ${tweet.id}`);
|
|
7542
7636
|
} catch (error) {
|
|
7543
|
-
|
|
7637
|
+
logger6.error(`Error retweeting tweet ${tweet.id}:`, error);
|
|
7544
7638
|
}
|
|
7545
7639
|
}
|
|
7546
7640
|
async handleQuoteAction(tweet) {
|
|
@@ -7559,7 +7653,7 @@ ${tweet.text}`;
|
|
|
7559
7653
|
const responseObject = parseKeyValueXml(quoteResponse);
|
|
7560
7654
|
if (responseObject.post) {
|
|
7561
7655
|
if (this.isDryRun) {
|
|
7562
|
-
|
|
7656
|
+
logger6.log(
|
|
7563
7657
|
`[DRY RUN] Would have quoted tweet ${tweet.id} with: ${responseObject.post}`
|
|
7564
7658
|
);
|
|
7565
7659
|
return;
|
|
@@ -7573,9 +7667,9 @@ ${tweet.text}`;
|
|
|
7573
7667
|
const body = await result.json();
|
|
7574
7668
|
const tweetResult = body?.data?.create_tweet?.tweet_results?.result || body?.data || body;
|
|
7575
7669
|
if (tweetResult) {
|
|
7576
|
-
|
|
7670
|
+
logger6.log("Successfully posted quote tweet");
|
|
7577
7671
|
} else {
|
|
7578
|
-
|
|
7672
|
+
logger6.error("Quote tweet creation failed:", body);
|
|
7579
7673
|
}
|
|
7580
7674
|
const tweetId = tweetResult?.id || Date.now().toString();
|
|
7581
7675
|
const responseId = createUniqueUuid4(this.runtime, tweetId);
|
|
@@ -7593,7 +7687,7 @@ ${tweet.text}`;
|
|
|
7593
7687
|
await this.runtime.createMemory(responseMemory, "messages");
|
|
7594
7688
|
}
|
|
7595
7689
|
} catch (error) {
|
|
7596
|
-
|
|
7690
|
+
logger6.error("Error in quote tweet generation:", error);
|
|
7597
7691
|
}
|
|
7598
7692
|
}
|
|
7599
7693
|
async handleReplyAction(tweet) {
|
|
@@ -7612,7 +7706,7 @@ ${tweet.text}`;
|
|
|
7612
7706
|
const responseObject = parseKeyValueXml(replyResponse);
|
|
7613
7707
|
if (responseObject.post) {
|
|
7614
7708
|
if (this.isDryRun) {
|
|
7615
|
-
|
|
7709
|
+
logger6.log(
|
|
7616
7710
|
`[DRY RUN] Would have replied to tweet ${tweet.id} with: ${responseObject.post}`
|
|
7617
7711
|
);
|
|
7618
7712
|
return;
|
|
@@ -7624,7 +7718,7 @@ ${tweet.text}`;
|
|
|
7624
7718
|
tweet.id
|
|
7625
7719
|
);
|
|
7626
7720
|
if (result) {
|
|
7627
|
-
|
|
7721
|
+
logger6.log("Successfully posted reply tweet");
|
|
7628
7722
|
const responseId = createUniqueUuid4(this.runtime, result.id);
|
|
7629
7723
|
const responseMemory = {
|
|
7630
7724
|
id: responseId,
|
|
@@ -7641,7 +7735,7 @@ ${tweet.text}`;
|
|
|
7641
7735
|
}
|
|
7642
7736
|
}
|
|
7643
7737
|
} catch (error) {
|
|
7644
|
-
|
|
7738
|
+
logger6.error("Error in reply tweet generation:", error);
|
|
7645
7739
|
}
|
|
7646
7740
|
}
|
|
7647
7741
|
};
|
|
@@ -7649,7 +7743,7 @@ ${tweet.text}`;
|
|
|
7649
7743
|
// src/discovery.ts
|
|
7650
7744
|
import {
|
|
7651
7745
|
createUniqueUuid as createUniqueUuid5,
|
|
7652
|
-
logger as
|
|
7746
|
+
logger as logger7,
|
|
7653
7747
|
ModelType as ModelType4
|
|
7654
7748
|
} from "@elizaos/core";
|
|
7655
7749
|
var TwitterDiscoveryClient = class {
|
|
@@ -7663,7 +7757,7 @@ var TwitterDiscoveryClient = class {
|
|
|
7663
7757
|
const dryRunSetting = state?.TWITTER_DRY_RUN ?? getSetting(this.runtime, "TWITTER_DRY_RUN") ?? process.env.TWITTER_DRY_RUN;
|
|
7664
7758
|
this.isDryRun = dryRunSetting === true || dryRunSetting === "true" || typeof dryRunSetting === "string" && dryRunSetting.toLowerCase() === "true";
|
|
7665
7759
|
this.config = this.buildDiscoveryConfig();
|
|
7666
|
-
|
|
7760
|
+
logger7.info("Twitter Discovery Config:", {
|
|
7667
7761
|
topics: this.config.topics,
|
|
7668
7762
|
isDryRun: this.isDryRun,
|
|
7669
7763
|
minFollowerCount: this.config.minFollowerCount,
|
|
@@ -7701,7 +7795,7 @@ var TwitterDiscoveryClient = class {
|
|
|
7701
7795
|
topics = this.extractTopicsFromBio(character.bio);
|
|
7702
7796
|
}
|
|
7703
7797
|
} else {
|
|
7704
|
-
|
|
7798
|
+
logger7.warn(
|
|
7705
7799
|
"Character not available in runtime, using default topics for discovery"
|
|
7706
7800
|
);
|
|
7707
7801
|
}
|
|
@@ -7717,14 +7811,15 @@ var TwitterDiscoveryClient = class {
|
|
|
7717
7811
|
getSetting(
|
|
7718
7812
|
this.runtime,
|
|
7719
7813
|
"TWITTER_MAX_ENGAGEMENTS_PER_RUN"
|
|
7720
|
-
) || process.env.TWITTER_MAX_ENGAGEMENTS_PER_RUN || "
|
|
7814
|
+
) || process.env.TWITTER_MAX_ENGAGEMENTS_PER_RUN || "5"
|
|
7815
|
+
// Reduced from 10 to 5
|
|
7721
7816
|
),
|
|
7722
|
-
likeThreshold: 0.
|
|
7723
|
-
//
|
|
7724
|
-
replyThreshold: 0.
|
|
7725
|
-
//
|
|
7726
|
-
quoteThreshold: 0.
|
|
7727
|
-
//
|
|
7817
|
+
likeThreshold: 0.5,
|
|
7818
|
+
// Increased from 0.3 (be more selective)
|
|
7819
|
+
replyThreshold: 0.7,
|
|
7820
|
+
// Increased from 0.5 (be more selective)
|
|
7821
|
+
quoteThreshold: 0.85
|
|
7822
|
+
// Increased from 0.7 (be more selective)
|
|
7728
7823
|
};
|
|
7729
7824
|
}
|
|
7730
7825
|
extractTopicsFromBio(bio) {
|
|
@@ -7745,46 +7840,41 @@ var TwitterDiscoveryClient = class {
|
|
|
7745
7840
|
return [...new Set(words)].slice(0, 5);
|
|
7746
7841
|
}
|
|
7747
7842
|
async start() {
|
|
7748
|
-
|
|
7843
|
+
logger7.info("Starting Twitter Discovery Client...");
|
|
7749
7844
|
this.isRunning = true;
|
|
7750
7845
|
const discoveryLoop = async () => {
|
|
7751
7846
|
if (!this.isRunning) {
|
|
7752
|
-
|
|
7847
|
+
logger7.info("Discovery client stopped, exiting loop");
|
|
7753
7848
|
return;
|
|
7754
7849
|
}
|
|
7755
7850
|
try {
|
|
7756
7851
|
await this.runDiscoveryCycle();
|
|
7757
7852
|
} catch (error) {
|
|
7758
|
-
|
|
7853
|
+
logger7.error("Discovery cycle error:", error);
|
|
7759
7854
|
}
|
|
7760
|
-
const
|
|
7761
|
-
|
|
7762
|
-
|
|
7763
|
-
|
|
7764
|
-
const nextInterval = (baseInterval + variance) * 60 * 1e3;
|
|
7765
|
-
logger6.info(
|
|
7766
|
-
`Next discovery cycle in ${(baseInterval + variance).toFixed(1)} minutes`
|
|
7855
|
+
const discoveryIntervalMinutes = getRandomInterval(this.runtime, "discovery");
|
|
7856
|
+
const nextInterval = discoveryIntervalMinutes * 60 * 1e3;
|
|
7857
|
+
logger7.log(
|
|
7858
|
+
`Next discovery cycle in ${discoveryIntervalMinutes.toFixed(1)} minutes`
|
|
7767
7859
|
);
|
|
7768
|
-
|
|
7769
|
-
setTimeout(discoveryLoop, nextInterval);
|
|
7770
|
-
}
|
|
7860
|
+
setTimeout(discoveryLoop, nextInterval);
|
|
7771
7861
|
};
|
|
7772
7862
|
setTimeout(discoveryLoop, 5e3);
|
|
7773
7863
|
}
|
|
7774
7864
|
async stop() {
|
|
7775
|
-
|
|
7865
|
+
logger7.info("Stopping Twitter Discovery Client...");
|
|
7776
7866
|
this.isRunning = false;
|
|
7777
7867
|
}
|
|
7778
7868
|
async runDiscoveryCycle() {
|
|
7779
|
-
|
|
7869
|
+
logger7.info("Starting Twitter discovery cycle...");
|
|
7780
7870
|
const discoveries = await this.discoverContent();
|
|
7781
7871
|
const { tweets, accounts } = discoveries;
|
|
7782
|
-
|
|
7872
|
+
logger7.info(
|
|
7783
7873
|
`Discovered ${tweets.length} tweets and ${accounts.length} accounts`
|
|
7784
7874
|
);
|
|
7785
7875
|
const followedCount = await this.processAccounts(accounts);
|
|
7786
7876
|
const engagementCount = await this.processTweets(tweets);
|
|
7787
|
-
|
|
7877
|
+
logger7.info(
|
|
7788
7878
|
`Discovery cycle complete: ${followedCount} follows, ${engagementCount} engagements`
|
|
7789
7879
|
);
|
|
7790
7880
|
}
|
|
@@ -7796,7 +7886,7 @@ var TwitterDiscoveryClient = class {
|
|
|
7796
7886
|
allTweets.push(...topicContent.tweets);
|
|
7797
7887
|
topicContent.accounts.forEach((acc) => allAccounts.set(acc.user.id, acc));
|
|
7798
7888
|
} catch (error) {
|
|
7799
|
-
|
|
7889
|
+
logger7.error("Failed to discover from topics:", error);
|
|
7800
7890
|
}
|
|
7801
7891
|
try {
|
|
7802
7892
|
const threadContent = await this.discoverFromThreads();
|
|
@@ -7805,7 +7895,7 @@ var TwitterDiscoveryClient = class {
|
|
|
7805
7895
|
(acc) => allAccounts.set(acc.user.id, acc)
|
|
7806
7896
|
);
|
|
7807
7897
|
} catch (error) {
|
|
7808
|
-
|
|
7898
|
+
logger7.error("Failed to discover from threads:", error);
|
|
7809
7899
|
}
|
|
7810
7900
|
try {
|
|
7811
7901
|
const popularContent = await this.discoverFromPopularAccounts();
|
|
@@ -7814,7 +7904,7 @@ var TwitterDiscoveryClient = class {
|
|
|
7814
7904
|
(acc) => allAccounts.set(acc.user.id, acc)
|
|
7815
7905
|
);
|
|
7816
7906
|
} catch (error) {
|
|
7817
|
-
|
|
7907
|
+
logger7.error("Failed to discover from popular accounts:", error);
|
|
7818
7908
|
}
|
|
7819
7909
|
const sortedTweets = allTweets.sort((a, b) => b.relevanceScore - a.relevanceScore).slice(0, 50);
|
|
7820
7910
|
const sortedAccounts = Array.from(allAccounts.values()).sort(
|
|
@@ -7823,14 +7913,14 @@ var TwitterDiscoveryClient = class {
|
|
|
7823
7913
|
return { tweets: sortedTweets, accounts: sortedAccounts };
|
|
7824
7914
|
}
|
|
7825
7915
|
async discoverFromTopics() {
|
|
7826
|
-
|
|
7916
|
+
logger7.debug("Discovering from character topics...");
|
|
7827
7917
|
const tweets = [];
|
|
7828
7918
|
const accounts = /* @__PURE__ */ new Map();
|
|
7829
7919
|
for (const topic of this.config.topics.slice(0, 5)) {
|
|
7830
7920
|
try {
|
|
7831
7921
|
const searchTopic = this.sanitizeTopic(topic);
|
|
7832
7922
|
const popularQuery = `${searchTopic} -is:retweet -is:reply lang:en`;
|
|
7833
|
-
|
|
7923
|
+
logger7.debug(`Searching popular tweets for topic: ${topic}`);
|
|
7834
7924
|
const popularResults = await this.twitterClient.fetchSearchTweets(
|
|
7835
7925
|
popularQuery,
|
|
7836
7926
|
20,
|
|
@@ -7840,44 +7930,66 @@ var TwitterDiscoveryClient = class {
|
|
|
7840
7930
|
if ((tweet.likes || 0) < 10) continue;
|
|
7841
7931
|
const scored = this.scoreTweet(tweet, "topic");
|
|
7842
7932
|
tweets.push(scored);
|
|
7933
|
+
const authorUsername = tweet.username;
|
|
7934
|
+
const authorName = tweet.name || tweet.username;
|
|
7935
|
+
const estimatedFollowers = Math.max(
|
|
7936
|
+
1e3,
|
|
7937
|
+
// minimum estimate
|
|
7938
|
+
(tweet.likes || 0) * 100
|
|
7939
|
+
// rough estimate: 100 followers per like
|
|
7940
|
+
);
|
|
7941
|
+
const account = this.scoreAccount({
|
|
7942
|
+
id: tweet.userId,
|
|
7943
|
+
username: authorUsername,
|
|
7944
|
+
name: authorName,
|
|
7945
|
+
followersCount: estimatedFollowers
|
|
7946
|
+
});
|
|
7947
|
+
if (account.qualityScore > 0.3) {
|
|
7948
|
+
accounts.set(tweet.userId, account);
|
|
7949
|
+
}
|
|
7843
7950
|
}
|
|
7844
|
-
const
|
|
7845
|
-
|
|
7846
|
-
const
|
|
7847
|
-
|
|
7848
|
-
|
|
7951
|
+
const engagedQuery = `${searchTopic} -is:retweet lang:en`;
|
|
7952
|
+
logger7.debug(`Searching engaged tweets for topic: ${topic}`);
|
|
7953
|
+
const engagedResults = await this.twitterClient.fetchSearchTweets(
|
|
7954
|
+
engagedQuery,
|
|
7955
|
+
15,
|
|
7849
7956
|
1 /* Latest */
|
|
7850
7957
|
);
|
|
7851
|
-
for (const tweet of
|
|
7958
|
+
for (const tweet of engagedResults.tweets) {
|
|
7959
|
+
if ((tweet.likes || 0) < 5) continue;
|
|
7852
7960
|
const scored = this.scoreTweet(tweet, "topic");
|
|
7853
7961
|
tweets.push(scored);
|
|
7854
7962
|
const authorUsername = tweet.username;
|
|
7855
7963
|
const authorName = tweet.name || tweet.username;
|
|
7964
|
+
const estimatedFollowers = Math.max(
|
|
7965
|
+
500,
|
|
7966
|
+
// minimum for engaged tweets
|
|
7967
|
+
(tweet.likes || 0) * 50
|
|
7968
|
+
);
|
|
7856
7969
|
const account = this.scoreAccount({
|
|
7857
7970
|
id: tweet.userId,
|
|
7858
7971
|
username: authorUsername,
|
|
7859
7972
|
name: authorName,
|
|
7860
|
-
followersCount:
|
|
7861
|
-
// Default estimate for verified accounts
|
|
7973
|
+
followersCount: estimatedFollowers
|
|
7862
7974
|
});
|
|
7863
|
-
if (account.qualityScore > 0.
|
|
7975
|
+
if (account.qualityScore > 0.2) {
|
|
7864
7976
|
accounts.set(tweet.userId, account);
|
|
7865
7977
|
}
|
|
7866
7978
|
}
|
|
7867
7979
|
} catch (error) {
|
|
7868
|
-
|
|
7980
|
+
logger7.error(`Failed to search topic ${topic}:`, error);
|
|
7869
7981
|
}
|
|
7870
7982
|
}
|
|
7871
7983
|
return { tweets, accounts: Array.from(accounts.values()) };
|
|
7872
7984
|
}
|
|
7873
7985
|
async discoverFromThreads() {
|
|
7874
|
-
|
|
7986
|
+
logger7.debug("Discovering from conversation threads...");
|
|
7875
7987
|
const tweets = [];
|
|
7876
7988
|
const accounts = /* @__PURE__ */ new Map();
|
|
7877
7989
|
const topicQuery = this.config.topics.slice(0, 3).map((t) => this.sanitizeTopic(t)).join(" OR ");
|
|
7878
7990
|
try {
|
|
7879
7991
|
const viralQuery = `(${topicQuery}) -is:retweet has:mentions`;
|
|
7880
|
-
|
|
7992
|
+
logger7.debug(`Searching viral threads with query: ${viralQuery}`);
|
|
7881
7993
|
const searchResults = await this.twitterClient.fetchSearchTweets(
|
|
7882
7994
|
viralQuery,
|
|
7883
7995
|
15,
|
|
@@ -7900,19 +8012,19 @@ var TwitterDiscoveryClient = class {
|
|
|
7900
8012
|
}
|
|
7901
8013
|
}
|
|
7902
8014
|
} catch (error) {
|
|
7903
|
-
|
|
8015
|
+
logger7.error("Failed to discover threads:", error);
|
|
7904
8016
|
}
|
|
7905
8017
|
return { tweets, accounts: Array.from(accounts.values()) };
|
|
7906
8018
|
}
|
|
7907
8019
|
async discoverFromPopularAccounts() {
|
|
7908
|
-
|
|
8020
|
+
logger7.debug("Discovering from popular accounts in topics...");
|
|
7909
8021
|
const tweets = [];
|
|
7910
8022
|
const accounts = /* @__PURE__ */ new Map();
|
|
7911
8023
|
for (const topic of this.config.topics.slice(0, 3)) {
|
|
7912
8024
|
try {
|
|
7913
8025
|
const searchTopic = this.sanitizeTopic(topic);
|
|
7914
8026
|
const influencerQuery = `${searchTopic} -is:retweet lang:en`;
|
|
7915
|
-
|
|
8027
|
+
logger7.debug(`Searching for influencers in topic: ${topic}`);
|
|
7916
8028
|
const results = await this.twitterClient.fetchSearchTweets(
|
|
7917
8029
|
influencerQuery,
|
|
7918
8030
|
10,
|
|
@@ -7939,7 +8051,7 @@ var TwitterDiscoveryClient = class {
|
|
|
7939
8051
|
}
|
|
7940
8052
|
}
|
|
7941
8053
|
} catch (error) {
|
|
7942
|
-
|
|
8054
|
+
logger7.error(
|
|
7943
8055
|
`Failed to discover popular accounts for ${topic}:`,
|
|
7944
8056
|
error
|
|
7945
8057
|
);
|
|
@@ -8003,18 +8115,35 @@ var TwitterDiscoveryClient = class {
|
|
|
8003
8115
|
}
|
|
8004
8116
|
async processAccounts(accounts) {
|
|
8005
8117
|
let followedCount = 0;
|
|
8006
|
-
|
|
8118
|
+
const sortedAccounts = accounts.sort((a, b) => {
|
|
8119
|
+
const scoreA = a.qualityScore + a.relevanceScore;
|
|
8120
|
+
const scoreB = b.qualityScore + b.relevanceScore;
|
|
8121
|
+
return scoreB - scoreA;
|
|
8122
|
+
});
|
|
8123
|
+
for (const scoredAccount of sortedAccounts) {
|
|
8007
8124
|
if (followedCount >= this.config.maxFollowsPerCycle) break;
|
|
8125
|
+
if (scoredAccount.user.followersCount < this.config.minFollowerCount) {
|
|
8126
|
+
logger7.debug(
|
|
8127
|
+
`Skipping @${scoredAccount.user.username} - below minimum follower count (${scoredAccount.user.followersCount} < ${this.config.minFollowerCount})`
|
|
8128
|
+
);
|
|
8129
|
+
continue;
|
|
8130
|
+
}
|
|
8131
|
+
if (scoredAccount.qualityScore < 0.2) {
|
|
8132
|
+
logger7.debug(
|
|
8133
|
+
`Skipping @${scoredAccount.user.username} - quality score too low (${scoredAccount.qualityScore.toFixed(2)})`
|
|
8134
|
+
);
|
|
8135
|
+
continue;
|
|
8136
|
+
}
|
|
8008
8137
|
try {
|
|
8009
8138
|
const isFollowing = await this.checkIfFollowing(scoredAccount.user.id);
|
|
8010
8139
|
if (isFollowing) continue;
|
|
8011
8140
|
if (this.isDryRun) {
|
|
8012
|
-
|
|
8141
|
+
logger7.info(
|
|
8013
8142
|
`[DRY RUN] Would follow @${scoredAccount.user.username} (quality: ${scoredAccount.qualityScore.toFixed(2)}, relevance: ${scoredAccount.relevanceScore.toFixed(2)})`
|
|
8014
8143
|
);
|
|
8015
8144
|
} else {
|
|
8016
8145
|
await this.twitterClient.followUser(scoredAccount.user.id);
|
|
8017
|
-
|
|
8146
|
+
logger7.info(
|
|
8018
8147
|
`Followed @${scoredAccount.user.username} (quality: ${scoredAccount.qualityScore.toFixed(2)}, relevance: ${scoredAccount.relevanceScore.toFixed(2)})`
|
|
8019
8148
|
);
|
|
8020
8149
|
await this.saveFollowMemory(scoredAccount.user);
|
|
@@ -8022,7 +8151,7 @@ var TwitterDiscoveryClient = class {
|
|
|
8022
8151
|
followedCount++;
|
|
8023
8152
|
await this.delay(2e3 + Math.random() * 3e3);
|
|
8024
8153
|
} catch (error) {
|
|
8025
|
-
|
|
8154
|
+
logger7.error(
|
|
8026
8155
|
`Failed to follow @${scoredAccount.user.username}:`,
|
|
8027
8156
|
error
|
|
8028
8157
|
);
|
|
@@ -8042,7 +8171,7 @@ var TwitterDiscoveryClient = class {
|
|
|
8042
8171
|
);
|
|
8043
8172
|
const existingMemory = await this.runtime.getMemoryById(tweetMemoryId);
|
|
8044
8173
|
if (existingMemory) {
|
|
8045
|
-
|
|
8174
|
+
logger7.debug(
|
|
8046
8175
|
`Already engaged with tweet ${scoredTweet.tweet.id}, skipping`
|
|
8047
8176
|
);
|
|
8048
8177
|
continue;
|
|
@@ -8050,12 +8179,12 @@ var TwitterDiscoveryClient = class {
|
|
|
8050
8179
|
switch (scoredTweet.engagementType) {
|
|
8051
8180
|
case "like":
|
|
8052
8181
|
if (this.isDryRun) {
|
|
8053
|
-
|
|
8182
|
+
logger7.info(
|
|
8054
8183
|
`[DRY RUN] Would like tweet: ${scoredTweet.tweet.id} (score: ${scoredTweet.relevanceScore.toFixed(2)})`
|
|
8055
8184
|
);
|
|
8056
8185
|
} else {
|
|
8057
8186
|
await this.twitterClient.likeTweet(scoredTweet.tweet.id);
|
|
8058
|
-
|
|
8187
|
+
logger7.info(
|
|
8059
8188
|
`Liked tweet: ${scoredTweet.tweet.id} (score: ${scoredTweet.relevanceScore.toFixed(2)})`
|
|
8060
8189
|
);
|
|
8061
8190
|
}
|
|
@@ -8063,7 +8192,7 @@ var TwitterDiscoveryClient = class {
|
|
|
8063
8192
|
case "reply":
|
|
8064
8193
|
const replyText = await this.generateReply(scoredTweet.tweet);
|
|
8065
8194
|
if (this.isDryRun) {
|
|
8066
|
-
|
|
8195
|
+
logger7.info(
|
|
8067
8196
|
`[DRY RUN] Would reply to tweet ${scoredTweet.tweet.id} with: "${replyText}"`
|
|
8068
8197
|
);
|
|
8069
8198
|
} else {
|
|
@@ -8071,13 +8200,13 @@ var TwitterDiscoveryClient = class {
|
|
|
8071
8200
|
replyText,
|
|
8072
8201
|
scoredTweet.tweet.id
|
|
8073
8202
|
);
|
|
8074
|
-
|
|
8203
|
+
logger7.info(`Replied to tweet: ${scoredTweet.tweet.id}`);
|
|
8075
8204
|
}
|
|
8076
8205
|
break;
|
|
8077
8206
|
case "quote":
|
|
8078
8207
|
const quoteText = await this.generateQuote(scoredTweet.tweet);
|
|
8079
8208
|
if (this.isDryRun) {
|
|
8080
|
-
|
|
8209
|
+
logger7.info(
|
|
8081
8210
|
`[DRY RUN] Would quote tweet ${scoredTweet.tweet.id} with: "${quoteText}"`
|
|
8082
8211
|
);
|
|
8083
8212
|
} else {
|
|
@@ -8085,7 +8214,7 @@ var TwitterDiscoveryClient = class {
|
|
|
8085
8214
|
quoteText,
|
|
8086
8215
|
scoredTweet.tweet.id
|
|
8087
8216
|
);
|
|
8088
|
-
|
|
8217
|
+
logger7.info(`Quoted tweet: ${scoredTweet.tweet.id}`);
|
|
8089
8218
|
}
|
|
8090
8219
|
break;
|
|
8091
8220
|
}
|
|
@@ -8096,7 +8225,7 @@ var TwitterDiscoveryClient = class {
|
|
|
8096
8225
|
engagementCount++;
|
|
8097
8226
|
await this.delay(3e3 + Math.random() * 5e3);
|
|
8098
8227
|
} catch (error) {
|
|
8099
|
-
|
|
8228
|
+
logger7.error(
|
|
8100
8229
|
`Failed to engage with tweet ${scoredTweet.tweet.id}:`,
|
|
8101
8230
|
error
|
|
8102
8231
|
);
|
|
@@ -8181,11 +8310,11 @@ Quote tweet:`;
|
|
|
8181
8310
|
return response.trim();
|
|
8182
8311
|
}
|
|
8183
8312
|
async saveEngagementMemory(tweet, engagementType) {
|
|
8184
|
-
|
|
8313
|
+
logger7.debug(`[Discovery] Would save engagement memory for ${engagementType} on tweet ${tweet.id}`);
|
|
8185
8314
|
return;
|
|
8186
8315
|
}
|
|
8187
8316
|
async saveFollowMemory(user) {
|
|
8188
|
-
|
|
8317
|
+
logger7.debug(`[Discovery] Would save follow memory for @${user.username}`);
|
|
8189
8318
|
return;
|
|
8190
8319
|
}
|
|
8191
8320
|
delay(ms) {
|
|
@@ -8197,7 +8326,7 @@ Quote tweet:`;
|
|
|
8197
8326
|
import {
|
|
8198
8327
|
ChannelType as ChannelType4,
|
|
8199
8328
|
createUniqueUuid as createUniqueUuid6,
|
|
8200
|
-
logger as
|
|
8329
|
+
logger as logger8
|
|
8201
8330
|
} from "@elizaos/core";
|
|
8202
8331
|
var RequestQueue = class {
|
|
8203
8332
|
constructor() {
|
|
@@ -8242,7 +8371,7 @@ var RequestQueue = class {
|
|
|
8242
8371
|
await request();
|
|
8243
8372
|
this.retryAttempts.delete(request);
|
|
8244
8373
|
} catch (error) {
|
|
8245
|
-
|
|
8374
|
+
logger8.error("Error processing request:", error);
|
|
8246
8375
|
const retryCount = (this.retryAttempts.get(request) || 0) + 1;
|
|
8247
8376
|
if (retryCount < this.maxRetries) {
|
|
8248
8377
|
this.retryAttempts.set(request, retryCount);
|
|
@@ -8250,7 +8379,7 @@ var RequestQueue = class {
|
|
|
8250
8379
|
await this.exponentialBackoff(retryCount);
|
|
8251
8380
|
break;
|
|
8252
8381
|
} else {
|
|
8253
|
-
|
|
8382
|
+
logger8.error(
|
|
8254
8383
|
`Max retries (${this.maxRetries}) exceeded for request, skipping`
|
|
8255
8384
|
);
|
|
8256
8385
|
this.retryAttempts.delete(request);
|
|
@@ -8308,7 +8437,7 @@ var _ClientBase = class _ClientBase {
|
|
|
8308
8437
|
*/
|
|
8309
8438
|
async cacheTweet(tweet) {
|
|
8310
8439
|
if (!tweet) {
|
|
8311
|
-
|
|
8440
|
+
logger8.warn("Tweet is undefined, skipping cache");
|
|
8312
8441
|
return;
|
|
8313
8442
|
}
|
|
8314
8443
|
this.runtime.setCache(`twitter/tweets/${tweet.id}`, tweet);
|
|
@@ -8373,7 +8502,7 @@ var _ClientBase = class _ClientBase {
|
|
|
8373
8502
|
let lastError = null;
|
|
8374
8503
|
while (retryCount < maxRetries) {
|
|
8375
8504
|
try {
|
|
8376
|
-
|
|
8505
|
+
logger8.log("Initializing Twitter API v2 client");
|
|
8377
8506
|
await this.twitterClient.login(
|
|
8378
8507
|
"",
|
|
8379
8508
|
// username not needed for API v2
|
|
@@ -8389,18 +8518,18 @@ var _ClientBase = class _ClientBase {
|
|
|
8389
8518
|
accessTokenSecret
|
|
8390
8519
|
);
|
|
8391
8520
|
if (await this.twitterClient.isLoggedIn()) {
|
|
8392
|
-
|
|
8521
|
+
logger8.info("Successfully authenticated with Twitter API v2");
|
|
8393
8522
|
break;
|
|
8394
8523
|
}
|
|
8395
8524
|
} catch (error) {
|
|
8396
8525
|
lastError = error instanceof Error ? error : new Error(String(error));
|
|
8397
|
-
|
|
8526
|
+
logger8.error(
|
|
8398
8527
|
`Authentication attempt ${retryCount + 1} failed: ${lastError.message}`
|
|
8399
8528
|
);
|
|
8400
8529
|
retryCount++;
|
|
8401
8530
|
if (retryCount < maxRetries) {
|
|
8402
8531
|
const delay = 2 ** retryCount * 1e3;
|
|
8403
|
-
|
|
8532
|
+
logger8.info(`Retrying in ${delay / 1e3} seconds...`);
|
|
8404
8533
|
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
8405
8534
|
}
|
|
8406
8535
|
}
|
|
@@ -8412,13 +8541,13 @@ var _ClientBase = class _ClientBase {
|
|
|
8412
8541
|
}
|
|
8413
8542
|
const profile = await this.twitterClient.me();
|
|
8414
8543
|
if (profile) {
|
|
8415
|
-
|
|
8416
|
-
|
|
8544
|
+
logger8.log("Twitter user ID:", profile.userId);
|
|
8545
|
+
logger8.log("Twitter loaded:", JSON.stringify(profile, null, 10));
|
|
8417
8546
|
const agentId = this.runtime.agentId;
|
|
8418
8547
|
const entity = await this.runtime.getEntityById(agentId);
|
|
8419
8548
|
const entityMetadata = entity?.metadata;
|
|
8420
8549
|
if (entityMetadata?.twitter?.userName !== profile.username) {
|
|
8421
|
-
|
|
8550
|
+
logger8.log(
|
|
8422
8551
|
"Updating Agents known X/twitter handle",
|
|
8423
8552
|
profile.username,
|
|
8424
8553
|
"was",
|
|
@@ -8457,7 +8586,7 @@ var _ClientBase = class _ClientBase {
|
|
|
8457
8586
|
await this.populateTimeline();
|
|
8458
8587
|
}
|
|
8459
8588
|
async fetchOwnPosts(count) {
|
|
8460
|
-
|
|
8589
|
+
logger8.debug("fetching own posts");
|
|
8461
8590
|
const homeTimeline = await this.twitterClient.getUserTweets(
|
|
8462
8591
|
this.profile.id,
|
|
8463
8592
|
count
|
|
@@ -8468,7 +8597,7 @@ var _ClientBase = class _ClientBase {
|
|
|
8468
8597
|
* Fetch timeline for twitter account, optionally only from followed accounts
|
|
8469
8598
|
*/
|
|
8470
8599
|
async fetchHomeTimeline(count, following) {
|
|
8471
|
-
|
|
8600
|
+
logger8.debug("fetching home timeline");
|
|
8472
8601
|
const homeTimeline = following ? await this.twitterClient.fetchFollowingTimeline(count, []) : await this.twitterClient.fetchHomeTimeline(count, []);
|
|
8473
8602
|
return homeTimeline;
|
|
8474
8603
|
}
|
|
@@ -8491,16 +8620,16 @@ var _ClientBase = class _ClientBase {
|
|
|
8491
8620
|
);
|
|
8492
8621
|
return result ?? { tweets: [] };
|
|
8493
8622
|
} catch (error) {
|
|
8494
|
-
|
|
8623
|
+
logger8.error("Error fetching search tweets:", error);
|
|
8495
8624
|
return { tweets: [] };
|
|
8496
8625
|
}
|
|
8497
8626
|
} catch (error) {
|
|
8498
|
-
|
|
8627
|
+
logger8.error("Error fetching search tweets:", error);
|
|
8499
8628
|
return { tweets: [] };
|
|
8500
8629
|
}
|
|
8501
8630
|
}
|
|
8502
8631
|
async populateTimeline() {
|
|
8503
|
-
|
|
8632
|
+
logger8.debug("populating timeline...");
|
|
8504
8633
|
const cachedTimeline = await this.getCachedTimeline();
|
|
8505
8634
|
if (cachedTimeline) {
|
|
8506
8635
|
const existingMemories2 = await this.runtime.getMemoriesByRoomIds({
|
|
@@ -8520,7 +8649,7 @@ var _ClientBase = class _ClientBase {
|
|
|
8520
8649
|
(tweet) => tweet.userId !== this.profile.id && !existingMemoryIds2.has(createUniqueUuid6(this.runtime, tweet.id))
|
|
8521
8650
|
);
|
|
8522
8651
|
for (const tweet of tweetsToSave2) {
|
|
8523
|
-
|
|
8652
|
+
logger8.log("Saving Tweet", tweet.id);
|
|
8524
8653
|
if (tweet.userId === this.profile.id) {
|
|
8525
8654
|
continue;
|
|
8526
8655
|
}
|
|
@@ -8568,7 +8697,7 @@ var _ClientBase = class _ClientBase {
|
|
|
8568
8697
|
);
|
|
8569
8698
|
await this.cacheTweet(tweet);
|
|
8570
8699
|
}
|
|
8571
|
-
|
|
8700
|
+
logger8.log(
|
|
8572
8701
|
`Populated ${tweetsToSave2.length} missing tweets from the cache.`
|
|
8573
8702
|
);
|
|
8574
8703
|
return;
|
|
@@ -8597,11 +8726,11 @@ var _ClientBase = class _ClientBase {
|
|
|
8597
8726
|
const tweetsToSave = allTweets.filter(
|
|
8598
8727
|
(tweet) => tweet.userId !== this.profile.id && !existingMemoryIds.has(createUniqueUuid6(this.runtime, tweet.id))
|
|
8599
8728
|
);
|
|
8600
|
-
|
|
8729
|
+
logger8.debug({
|
|
8601
8730
|
processingTweets: tweetsToSave.map((tweet) => tweet.id).join(",")
|
|
8602
8731
|
});
|
|
8603
8732
|
for (const tweet of tweetsToSave) {
|
|
8604
|
-
|
|
8733
|
+
logger8.log("Saving Tweet", tweet.id);
|
|
8605
8734
|
if (tweet.userId === this.profile.id) {
|
|
8606
8735
|
continue;
|
|
8607
8736
|
}
|
|
@@ -8661,7 +8790,7 @@ var _ClientBase = class _ClientBase {
|
|
|
8661
8790
|
unique: false
|
|
8662
8791
|
});
|
|
8663
8792
|
if (recentMessage.length > 0 && recentMessage[0].content === message.content) {
|
|
8664
|
-
|
|
8793
|
+
logger8.debug("Message already saved", recentMessage[0].id);
|
|
8665
8794
|
} else {
|
|
8666
8795
|
await this.runtime.createMemory(message, "messages");
|
|
8667
8796
|
}
|
|
@@ -8734,7 +8863,7 @@ var _ClientBase = class _ClientBase {
|
|
|
8734
8863
|
});
|
|
8735
8864
|
return profile;
|
|
8736
8865
|
} catch (error) {
|
|
8737
|
-
|
|
8866
|
+
logger8.error("Error fetching Twitter profile:", error);
|
|
8738
8867
|
throw error;
|
|
8739
8868
|
}
|
|
8740
8869
|
}
|
|
@@ -8755,7 +8884,7 @@ var _ClientBase = class _ClientBase {
|
|
|
8755
8884
|
(tweet) => this.formatTweetToInteraction(tweet)
|
|
8756
8885
|
);
|
|
8757
8886
|
} catch (error) {
|
|
8758
|
-
|
|
8887
|
+
logger8.error("Error fetching Twitter interactions:", error);
|
|
8759
8888
|
return [];
|
|
8760
8889
|
}
|
|
8761
8890
|
}
|
|
@@ -8785,42 +8914,42 @@ var TwitterClientInstance = class {
|
|
|
8785
8914
|
constructor(runtime, state) {
|
|
8786
8915
|
this.client = new ClientBase(runtime, state);
|
|
8787
8916
|
const postEnabledSetting = getSetting(runtime, "TWITTER_ENABLE_POST") ?? process.env.TWITTER_ENABLE_POST;
|
|
8788
|
-
|
|
8917
|
+
logger9.debug(
|
|
8789
8918
|
`TWITTER_ENABLE_POST setting value: ${JSON.stringify(postEnabledSetting)}, type: ${typeof postEnabledSetting}`
|
|
8790
8919
|
);
|
|
8791
8920
|
const postEnabled = postEnabledSetting === "true" || postEnabledSetting === true;
|
|
8792
8921
|
if (postEnabled) {
|
|
8793
|
-
|
|
8922
|
+
logger9.info("Twitter posting is ENABLED - creating post client");
|
|
8794
8923
|
this.post = new TwitterPostClient(this.client, runtime, state);
|
|
8795
8924
|
} else {
|
|
8796
|
-
|
|
8925
|
+
logger9.info(
|
|
8797
8926
|
"Twitter posting is DISABLED - set TWITTER_ENABLE_POST=true to enable automatic posting"
|
|
8798
8927
|
);
|
|
8799
8928
|
}
|
|
8800
8929
|
const repliesEnabled = (getSetting(runtime, "TWITTER_ENABLE_REPLIES") ?? process.env.TWITTER_ENABLE_REPLIES) !== "false";
|
|
8801
8930
|
if (repliesEnabled) {
|
|
8802
|
-
|
|
8931
|
+
logger9.info("Twitter replies/interactions are ENABLED");
|
|
8803
8932
|
this.interaction = new TwitterInteractionClient(
|
|
8804
8933
|
this.client,
|
|
8805
8934
|
runtime,
|
|
8806
8935
|
state
|
|
8807
8936
|
);
|
|
8808
8937
|
} else {
|
|
8809
|
-
|
|
8938
|
+
logger9.info("Twitter replies/interactions are DISABLED");
|
|
8810
8939
|
}
|
|
8811
8940
|
const actionsEnabled = (getSetting(runtime, "TWITTER_ENABLE_ACTIONS") ?? process.env.TWITTER_ENABLE_ACTIONS) === "true";
|
|
8812
8941
|
if (actionsEnabled) {
|
|
8813
|
-
|
|
8942
|
+
logger9.info("Twitter timeline actions are ENABLED");
|
|
8814
8943
|
this.timeline = new TwitterTimelineClient(this.client, runtime, state);
|
|
8815
8944
|
} else {
|
|
8816
|
-
|
|
8945
|
+
logger9.info("Twitter timeline actions are DISABLED");
|
|
8817
8946
|
}
|
|
8818
8947
|
const discoveryEnabled = (getSetting(runtime, "TWITTER_ENABLE_DISCOVERY") ?? process.env.TWITTER_ENABLE_DISCOVERY) === "true" || actionsEnabled && (getSetting(runtime, "TWITTER_ENABLE_DISCOVERY") ?? process.env.TWITTER_ENABLE_DISCOVERY) !== "false";
|
|
8819
8948
|
if (discoveryEnabled) {
|
|
8820
|
-
|
|
8949
|
+
logger9.info("Twitter discovery service is ENABLED");
|
|
8821
8950
|
this.discovery = new TwitterDiscoveryClient(this.client, runtime, state);
|
|
8822
8951
|
} else {
|
|
8823
|
-
|
|
8952
|
+
logger9.info(
|
|
8824
8953
|
"Twitter discovery service is DISABLED - set TWITTER_ENABLE_DISCOVERY=true to enable"
|
|
8825
8954
|
);
|
|
8826
8955
|
}
|
|
@@ -8837,28 +8966,28 @@ var _TwitterService = class _TwitterService extends Service {
|
|
|
8837
8966
|
service.runtime = runtime;
|
|
8838
8967
|
try {
|
|
8839
8968
|
await validateTwitterConfig(runtime);
|
|
8840
|
-
|
|
8969
|
+
logger9.log("\u2705 Twitter configuration validated successfully");
|
|
8841
8970
|
service.twitterClient = new TwitterClientInstance(runtime, {});
|
|
8842
8971
|
await service.twitterClient.client.init();
|
|
8843
8972
|
if (service.twitterClient.post) {
|
|
8844
|
-
|
|
8973
|
+
logger9.log("\u{1F4EE} Starting Twitter post client...");
|
|
8845
8974
|
await service.twitterClient.post.start();
|
|
8846
8975
|
}
|
|
8847
8976
|
if (service.twitterClient.interaction) {
|
|
8848
|
-
|
|
8977
|
+
logger9.log("\u{1F4AC} Starting Twitter interaction client...");
|
|
8849
8978
|
await service.twitterClient.interaction.start();
|
|
8850
8979
|
}
|
|
8851
8980
|
if (service.twitterClient.timeline) {
|
|
8852
|
-
|
|
8981
|
+
logger9.log("\u{1F4CA} Starting Twitter timeline client...");
|
|
8853
8982
|
await service.twitterClient.timeline.start();
|
|
8854
8983
|
}
|
|
8855
8984
|
if (service.twitterClient.discovery) {
|
|
8856
|
-
|
|
8985
|
+
logger9.log("\u{1F50D} Starting Twitter discovery client...");
|
|
8857
8986
|
await service.twitterClient.discovery.start();
|
|
8858
8987
|
}
|
|
8859
|
-
|
|
8988
|
+
logger9.log("\u2705 Twitter service started successfully");
|
|
8860
8989
|
} catch (error) {
|
|
8861
|
-
|
|
8990
|
+
logger9.error("\u{1F6A8} Failed to start Twitter service:", error);
|
|
8862
8991
|
throw error;
|
|
8863
8992
|
}
|
|
8864
8993
|
return service;
|
|
@@ -8876,7 +9005,7 @@ var _TwitterService = class _TwitterService extends Service {
|
|
|
8876
9005
|
if (this.twitterClient?.discovery) {
|
|
8877
9006
|
await this.twitterClient.discovery.stop();
|
|
8878
9007
|
}
|
|
8879
|
-
|
|
9008
|
+
logger9.log("Twitter service stopped");
|
|
8880
9009
|
}
|
|
8881
9010
|
};
|
|
8882
9011
|
_TwitterService.serviceType = "twitter";
|
|
@@ -8884,7 +9013,7 @@ var TwitterService = _TwitterService;
|
|
|
8884
9013
|
|
|
8885
9014
|
// src/actions/postTweet.ts
|
|
8886
9015
|
import {
|
|
8887
|
-
logger as
|
|
9016
|
+
logger as logger10,
|
|
8888
9017
|
ModelType as ModelType5
|
|
8889
9018
|
} from "@elizaos/core";
|
|
8890
9019
|
var postTweetAction = {
|
|
@@ -8897,8 +9026,8 @@ var postTweetAction = {
|
|
|
8897
9026
|
"SHARE_ON_TWITTER"
|
|
8898
9027
|
],
|
|
8899
9028
|
validate: async (runtime, message) => {
|
|
8900
|
-
|
|
8901
|
-
|
|
9029
|
+
logger10.debug("Validating POST_TWEET action");
|
|
9030
|
+
logger10.debug("Message details:", {
|
|
8902
9031
|
hasContent: !!message.content,
|
|
8903
9032
|
contentType: message.content?.type,
|
|
8904
9033
|
hasText: !!message.content?.text,
|
|
@@ -8908,23 +9037,23 @@ var postTweetAction = {
|
|
|
8908
9037
|
entityId: message.entityId
|
|
8909
9038
|
});
|
|
8910
9039
|
if (message.content?.type === "post" && !message.content?.text) {
|
|
8911
|
-
|
|
9040
|
+
logger10.debug("Skipping validation for provider context (empty post type)");
|
|
8912
9041
|
return false;
|
|
8913
9042
|
}
|
|
8914
9043
|
const text = message.content?.text?.trim();
|
|
8915
9044
|
if (!text || text.length === 0) {
|
|
8916
|
-
|
|
8917
|
-
|
|
9045
|
+
logger10.error("No text content for tweet");
|
|
9046
|
+
logger10.debug("Stack trace:", new Error().stack);
|
|
8918
9047
|
return false;
|
|
8919
9048
|
}
|
|
8920
9049
|
if (text.length > 280) {
|
|
8921
|
-
|
|
9050
|
+
logger10.warn(`Tweet too long: ${text.length} characters`);
|
|
8922
9051
|
}
|
|
8923
9052
|
return true;
|
|
8924
9053
|
},
|
|
8925
9054
|
description: "Post a tweet on Twitter",
|
|
8926
9055
|
handler: async (runtime, message, state, _options, callback) => {
|
|
8927
|
-
|
|
9056
|
+
logger10.info("Executing POST_TWEET action");
|
|
8928
9057
|
try {
|
|
8929
9058
|
const client = new ClientBase(runtime, {});
|
|
8930
9059
|
if (!client.twitterClient) {
|
|
@@ -8932,7 +9061,7 @@ var postTweetAction = {
|
|
|
8932
9061
|
}
|
|
8933
9062
|
let text = message.content?.text?.trim();
|
|
8934
9063
|
if (!text) {
|
|
8935
|
-
|
|
9064
|
+
logger10.error("No text content for tweet");
|
|
8936
9065
|
if (callback) {
|
|
8937
9066
|
callback({
|
|
8938
9067
|
text: "I need something to tweet! Please provide the text.",
|
|
@@ -8942,7 +9071,7 @@ var postTweetAction = {
|
|
|
8942
9071
|
return;
|
|
8943
9072
|
}
|
|
8944
9073
|
if (text.length > 280) {
|
|
8945
|
-
|
|
9074
|
+
logger10.info(`Truncating tweet from ${text.length} to 280 characters`);
|
|
8946
9075
|
const sentences = text.match(/[^.!?]+[.!?]+/g) || [text];
|
|
8947
9076
|
let truncated = "";
|
|
8948
9077
|
for (const sentence of sentences) {
|
|
@@ -8953,7 +9082,7 @@ var postTweetAction = {
|
|
|
8953
9082
|
}
|
|
8954
9083
|
}
|
|
8955
9084
|
text = truncated.trim() || text.substring(0, 277) + "...";
|
|
8956
|
-
|
|
9085
|
+
logger10.info(`Truncated tweet: ${text}`);
|
|
8957
9086
|
}
|
|
8958
9087
|
if (!client.profile) {
|
|
8959
9088
|
throw new Error(
|
|
@@ -9009,7 +9138,7 @@ Tweet:`;
|
|
|
9009
9138
|
tweetId = Date.now().toString();
|
|
9010
9139
|
}
|
|
9011
9140
|
const tweetUrl = `https://twitter.com/${client.profile.username}/status/${tweetId}`;
|
|
9012
|
-
|
|
9141
|
+
logger10.info(`Successfully posted tweet: ${tweetId}`);
|
|
9013
9142
|
await runtime.createMemory(
|
|
9014
9143
|
{
|
|
9015
9144
|
entityId: runtime.agentId,
|
|
@@ -9039,7 +9168,7 @@ View it here: ${tweetUrl}`,
|
|
|
9039
9168
|
throw new Error("Failed to post tweet - no response data");
|
|
9040
9169
|
}
|
|
9041
9170
|
} catch (error) {
|
|
9042
|
-
|
|
9171
|
+
logger10.error("Error posting tweet:", error);
|
|
9043
9172
|
if (callback) {
|
|
9044
9173
|
await callback({
|
|
9045
9174
|
text: `Sorry, I couldn't post the tweet. Error: ${error.message}`,
|
|
@@ -9105,7 +9234,7 @@ var TwitterPlugin = {
|
|
|
9105
9234
|
actions: [postTweetAction],
|
|
9106
9235
|
services: [TwitterService],
|
|
9107
9236
|
init: async (_config, runtime) => {
|
|
9108
|
-
|
|
9237
|
+
logger11.log("\u{1F527} Initializing Twitter plugin...");
|
|
9109
9238
|
const hasGetSetting = runtime && typeof runtime.getSetting === "function";
|
|
9110
9239
|
const apiKey = hasGetSetting ? runtime.getSetting("TWITTER_API_KEY") : process.env.TWITTER_API_KEY;
|
|
9111
9240
|
const apiSecretKey = hasGetSetting ? runtime.getSetting("TWITTER_API_SECRET_KEY") : process.env.TWITTER_API_SECRET_KEY;
|
|
@@ -9117,14 +9246,14 @@ var TwitterPlugin = {
|
|
|
9117
9246
|
if (!apiSecretKey) missing.push("TWITTER_API_SECRET_KEY");
|
|
9118
9247
|
if (!accessToken) missing.push("TWITTER_ACCESS_TOKEN");
|
|
9119
9248
|
if (!accessTokenSecret) missing.push("TWITTER_ACCESS_TOKEN_SECRET");
|
|
9120
|
-
|
|
9249
|
+
logger11.warn(
|
|
9121
9250
|
`Twitter API credentials not configured - Twitter functionality will be limited. Missing: ${missing.join(", ")}`
|
|
9122
9251
|
);
|
|
9123
|
-
|
|
9252
|
+
logger11.warn(
|
|
9124
9253
|
"To enable Twitter functionality, please provide the missing credentials in your .env file"
|
|
9125
9254
|
);
|
|
9126
9255
|
} else {
|
|
9127
|
-
|
|
9256
|
+
logger11.log("\u2705 Twitter credentials found");
|
|
9128
9257
|
}
|
|
9129
9258
|
}
|
|
9130
9259
|
};
|