@elizaos/plugin-twitter 1.2.0 → 1.2.1

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 CHANGED
@@ -177,11 +177,6 @@ TWITTER_POST_INTERVAL_VARIANCE=0.2 # Random variance factor for posting interval
177
177
 
178
178
  # Interaction Settings
179
179
  TWITTER_SEARCH_ENABLE=true # Enable timeline monitoring and interactions
180
- TWITTER_INTERACTION_INTERVAL_MIN=15 # Minimum interval between interactions (minutes)
181
- TWITTER_INTERACTION_INTERVAL_MAX=30 # Maximum interval between interactions (minutes)
182
- TWITTER_INTERACTION_INTERVAL_VARIANCE=0.3 # Random variance for interaction intervals
183
- TWITTER_AUTO_RESPOND_MENTIONS=true # Automatically respond to mentions
184
- TWITTER_AUTO_RESPOND_REPLIES=true # Automatically respond to replies
185
180
  TWITTER_MAX_INTERACTIONS_PER_RUN=10 # Maximum interactions processed per cycle
186
181
 
187
182
  # Timeline Algorithm Configuration
@@ -193,8 +188,8 @@ TWITTER_TIMELINE_RELEVANCE_WEIGHT=5 # Weight for relevance scoring
193
188
  # Advanced Settings
194
189
  TWITTER_MAX_TWEET_LENGTH=4000 # Maximum tweet length (for threads)
195
190
  TWITTER_DM_ONLY=false # Only interact via direct messages
196
- TWITTER_ENABLE_ACTION_PROCESSING=false # Enable timeline action processing
197
- TWITTER_ACTION_INTERVAL=240 # Action processing interval (minutes)
191
+ TWITTER_ENABLE_ACTION_PROCESSING=false # Enable timeline action processing (likes, retweets, replies)
192
+ TWITTER_ACTION_INTERVAL=30 # Timeline action processing interval in MINUTES (default: 30 minutes)
198
193
  ```
199
194
 
200
195
  ## 🎯 Common Use Cases
package/dist/index.d.ts CHANGED
@@ -769,6 +769,7 @@ declare class TwitterInteractionClient {
769
769
  runtime: IAgentRuntime;
770
770
  private isDryRun;
771
771
  private state;
772
+ private isRunning;
772
773
  /**
773
774
  * Constructor for setting up a new instance with the provided client, runtime, and state.
774
775
  * @param {ClientBase} client - The client being used for communication.
@@ -833,6 +834,7 @@ declare class TwitterInteractionClient {
833
834
  */
834
835
  buildConversationThread(tweet: Tweet$1, maxReplies?: number): Promise<Tweet$1[]>;
835
836
  private createMemoryObject;
837
+ stop(): Promise<void>;
836
838
  }
837
839
 
838
840
  /**
@@ -844,6 +846,7 @@ declare class TwitterPostClient {
844
846
  twitterUsername: string;
845
847
  private isDryRun;
846
848
  private state;
849
+ private isRunning;
847
850
  /**
848
851
  * Constructor for initializing a new Twitter client with the provided client, runtime, and state
849
852
  * @param {ClientBase} client - The client used for interacting with Twitter API
@@ -943,6 +946,8 @@ type TwitterProfile = {
943
946
  declare class RequestQueue {
944
947
  private queue;
945
948
  private processing;
949
+ private maxRetries;
950
+ private retryAttempts;
946
951
  /**
947
952
  * Asynchronously adds a request to the queue, then processes the queue.
948
953
  *
@@ -1058,6 +1063,7 @@ declare class TwitterTimelineClient {
1058
1063
  isDryRun: boolean;
1059
1064
  timelineType: TIMELINE_TYPE;
1060
1065
  private state;
1066
+ private isRunning;
1061
1067
  constructor(client: ClientBase, runtime: IAgentRuntime, state: any);
1062
1068
  start(): Promise<void>;
1063
1069
  getTimeline(count: number): Promise<Tweet$1[]>;
@@ -1085,6 +1091,7 @@ declare class TwitterTimelineClient {
1085
1091
  handleRetweetAction(tweet: Tweet$1): Promise<void>;
1086
1092
  handleQuoteAction(tweet: Tweet$1): Promise<void>;
1087
1093
  handleReplyAction(tweet: Tweet$1): Promise<void>;
1094
+ stop(): Promise<void>;
1088
1095
  }
1089
1096
 
1090
1097
  /**
package/dist/index.js CHANGED
@@ -504,9 +504,7 @@ async function* searchTweets(query, maxTweets, searchMode, auth) {
504
504
  const convertedTweet = {
505
505
  id: tweet.id,
506
506
  text: tweet.text || "",
507
- // Twitter API returns created_at as ISO string, convert to seconds
508
- // to match the expected Tweet.timestamp format used throughout the plugin
509
- timestamp: tweet.created_at ? new Date(tweet.created_at).getTime() / 1e3 : Date.now() / 1e3,
507
+ timestamp: tweet.created_at ? new Date(tweet.created_at).getTime() : Date.now(),
510
508
  timeParsed: tweet.created_at ? new Date(tweet.created_at) : /* @__PURE__ */ new Date(),
511
509
  userId: tweet.author_id || "",
512
510
  name: searchIterator.includes?.users?.find((u) => u.id === tweet.author_id)?.name || "",
@@ -1919,6 +1917,8 @@ var RequestQueue = class {
1919
1917
  constructor() {
1920
1918
  this.queue = [];
1921
1919
  this.processing = false;
1920
+ this.maxRetries = 3;
1921
+ this.retryAttempts = /* @__PURE__ */ new Map();
1922
1922
  }
1923
1923
  /**
1924
1924
  * Asynchronously adds a request to the queue, then processes the queue.
@@ -1954,14 +1954,26 @@ var RequestQueue = class {
1954
1954
  const request = this.queue.shift();
1955
1955
  try {
1956
1956
  await request();
1957
+ this.retryAttempts.delete(request);
1957
1958
  } catch (error) {
1958
- console.error("Error processing request:", error);
1959
- this.queue.unshift(request);
1960
- await this.exponentialBackoff(this.queue.length);
1959
+ logger.error("Error processing request:", error);
1960
+ const retryCount = (this.retryAttempts.get(request) || 0) + 1;
1961
+ if (retryCount < this.maxRetries) {
1962
+ this.retryAttempts.set(request, retryCount);
1963
+ this.queue.unshift(request);
1964
+ await this.exponentialBackoff(retryCount);
1965
+ break;
1966
+ } else {
1967
+ logger.error(`Max retries (${this.maxRetries}) exceeded for request, skipping`);
1968
+ this.retryAttempts.delete(request);
1969
+ }
1961
1970
  }
1962
1971
  await this.randomDelay();
1963
1972
  }
1964
1973
  this.processing = false;
1974
+ if (this.queue.length > 0) {
1975
+ this.processQueue();
1976
+ }
1965
1977
  }
1966
1978
  /**
1967
1979
  * Implements an exponential backoff strategy for retrying a task.
@@ -2008,7 +2020,7 @@ var _ClientBase = class _ClientBase {
2008
2020
  */
2009
2021
  async cacheTweet(tweet) {
2010
2022
  if (!tweet) {
2011
- console.warn("Tweet is undefined, skipping cache");
2023
+ logger.warn("Tweet is undefined, skipping cache");
2012
2024
  return;
2013
2025
  }
2014
2026
  this.runtime.setCache(`twitter/tweets/${tweet.id}`, tweet);
@@ -2422,7 +2434,7 @@ var _ClientBase = class _ClientBase {
2422
2434
  });
2423
2435
  return profile;
2424
2436
  } catch (error) {
2425
- console.error("Error fetching Twitter profile:", error);
2437
+ logger.error("Error fetching Twitter profile:", error);
2426
2438
  throw error;
2427
2439
  }
2428
2440
  }
@@ -6668,6 +6680,7 @@ var TwitterInteractionClient = class {
6668
6680
  * @param {any} state - The initial state of the agent.
6669
6681
  */
6670
6682
  constructor(client, runtime, state) {
6683
+ this.isRunning = false;
6671
6684
  this.client = client;
6672
6685
  this.runtime = runtime;
6673
6686
  this.state = state;
@@ -6678,12 +6691,19 @@ var TwitterInteractionClient = class {
6678
6691
  * Uses an interval based on the 'TWITTER_POLL_INTERVAL' setting, or defaults to 2 minutes if not set.
6679
6692
  */
6680
6693
  async start() {
6694
+ this.isRunning = true;
6681
6695
  const handleTwitterInteractionsLoop = () => {
6696
+ if (!this.isRunning) {
6697
+ logger4.info("Twitter interaction client stopped, exiting loop");
6698
+ return;
6699
+ }
6682
6700
  const interactionInterval = (this.state?.TWITTER_POLL_INTERVAL || this.runtime.getSetting(
6683
6701
  "TWITTER_POLL_INTERVAL"
6684
6702
  ) || 120) * 1e3;
6685
6703
  this.handleTwitterInteractions();
6686
- setTimeout(handleTwitterInteractionsLoop, interactionInterval);
6704
+ if (this.isRunning) {
6705
+ setTimeout(handleTwitterInteractionsLoop, interactionInterval);
6706
+ }
6687
6707
  };
6688
6708
  handleTwitterInteractionsLoop();
6689
6709
  }
@@ -6747,7 +6767,12 @@ var TwitterInteractionClient = class {
6747
6767
  return shouldTarget;
6748
6768
  });
6749
6769
  }
6750
- for (const tweet of uniqueTweetCandidates) {
6770
+ const maxInteractionsPerRun = parseInt(
6771
+ this.runtime.getSetting("TWITTER_MAX_INTERACTIONS_PER_RUN") || "10"
6772
+ );
6773
+ const tweetsToProcess = uniqueTweetCandidates.slice(0, maxInteractionsPerRun);
6774
+ logger4.info(`Processing ${tweetsToProcess.length} of ${uniqueTweetCandidates.length} mention tweets (max: ${maxInteractionsPerRun})`);
6775
+ for (const tweet of tweetsToProcess) {
6751
6776
  if (!this.client.lastCheckedTweetId || BigInt(tweet.id) > this.client.lastCheckedTweetId) {
6752
6777
  const tweetId = createUniqueUuid3(this.runtime, tweet.id);
6753
6778
  const existingResponse = await this.runtime.getMemoryById(tweetId);
@@ -7151,6 +7176,10 @@ var TwitterInteractionClient = class {
7151
7176
  createdAt: Date.now()
7152
7177
  };
7153
7178
  }
7179
+ async stop() {
7180
+ logger4.info("Stopping Twitter interaction client...");
7181
+ this.isRunning = false;
7182
+ }
7154
7183
  };
7155
7184
 
7156
7185
  // src/post.ts
@@ -7168,6 +7197,7 @@ var TwitterPostClient = class {
7168
7197
  * @param {any} state - The state object containing configuration settings
7169
7198
  */
7170
7199
  constructor(client, runtime, state) {
7200
+ this.isRunning = false;
7171
7201
  this.client = client;
7172
7202
  this.state = state;
7173
7203
  this.runtime = runtime;
@@ -7194,13 +7224,20 @@ var TwitterPostClient = class {
7194
7224
  */
7195
7225
  async start() {
7196
7226
  logger5.log("Starting Twitter post client...");
7227
+ this.isRunning = true;
7197
7228
  const generateNewTweetLoop = async () => {
7229
+ if (!this.isRunning) {
7230
+ logger5.log("Twitter post client stopped, exiting loop");
7231
+ return;
7232
+ }
7198
7233
  const minPostMinutes = this.state?.TWITTER_POST_INTERVAL_MIN || this.runtime.getSetting("TWITTER_POST_INTERVAL_MIN") || 90;
7199
7234
  const maxPostMinutes = this.state?.TWITTER_POST_INTERVAL_MAX || this.runtime.getSetting("TWITTER_POST_INTERVAL_MAX") || 180;
7200
7235
  const randomMinutes = Math.floor(Math.random() * (maxPostMinutes - minPostMinutes + 1)) + minPostMinutes;
7201
7236
  const interval = randomMinutes * 60 * 1e3;
7202
7237
  await this.generateNewTweet();
7203
- setTimeout(generateNewTweetLoop, interval);
7238
+ if (this.isRunning) {
7239
+ setTimeout(generateNewTweetLoop, interval);
7240
+ }
7204
7241
  };
7205
7242
  setTimeout(generateNewTweetLoop, 60 * 1e3);
7206
7243
  const postImmediately = this.state?.TWITTER_POST_IMMEDIATELY ?? this.runtime.getSetting("TWITTER_POST_IMMEDIATELY");
@@ -7336,6 +7373,8 @@ var TwitterPostClient = class {
7336
7373
  }
7337
7374
  }
7338
7375
  async stop() {
7376
+ logger5.log("Stopping Twitter post client...");
7377
+ this.isRunning = false;
7339
7378
  }
7340
7379
  };
7341
7380
 
@@ -7413,19 +7452,31 @@ Your output must ONLY contain the XML block.`;
7413
7452
  // src/timeline.ts
7414
7453
  var TwitterTimelineClient = class {
7415
7454
  constructor(client, runtime, state) {
7455
+ this.isRunning = false;
7416
7456
  this.client = client;
7417
7457
  this.twitterClient = client.twitterClient;
7418
7458
  this.runtime = runtime;
7419
7459
  this.state = state;
7420
- this.timelineType = this.state?.TWITTER_TIMELINE_MODE || this.runtime.getSetting("TWITTER_TIMELINE_MODE");
7460
+ const dryRunSetting = this.state?.TWITTER_DRY_RUN ?? this.runtime.getSetting("TWITTER_DRY_RUN");
7461
+ this.isDryRun = dryRunSetting === true || dryRunSetting === "true" || typeof dryRunSetting === "string" && dryRunSetting.toLowerCase() === "true";
7462
+ const timelineMode = this.state?.TWITTER_TIMELINE_MODE || this.runtime.getSetting("TWITTER_TIMELINE_MODE") || "foryou";
7463
+ this.timelineType = timelineMode.toLowerCase() === "following" ? "following" /* Following */ : "foryou" /* ForYou */;
7421
7464
  }
7422
7465
  async start() {
7466
+ logger6.info("Starting Twitter timeline client...");
7467
+ this.isRunning = true;
7423
7468
  const handleTwitterTimelineLoop = () => {
7424
- const interactionInterval = (this.state?.TWITTER_TIMELINE_POLL_INTERVAL || this.runtime.getSetting(
7425
- "TWITTER_TIMELINE_POLL_INTERVAL"
7426
- ) || 120) * 1e3;
7469
+ if (!this.isRunning) {
7470
+ logger6.info("Twitter timeline client stopped, exiting loop");
7471
+ return;
7472
+ }
7473
+ const actionIntervalMinutes = this.state?.TWITTER_ACTION_INTERVAL || this.runtime.getSetting("TWITTER_ACTION_INTERVAL") || 240;
7474
+ const actionInterval = actionIntervalMinutes * 60 * 1e3;
7475
+ logger6.info(`Timeline client will check every ${actionIntervalMinutes} minutes`);
7427
7476
  this.handleTimeline();
7428
- setTimeout(handleTwitterTimelineLoop, interactionInterval);
7477
+ if (this.isRunning) {
7478
+ setTimeout(handleTwitterTimelineLoop, actionInterval);
7479
+ }
7429
7480
  };
7430
7481
  handleTwitterTimelineLoop();
7431
7482
  }
@@ -7456,8 +7507,9 @@ var TwitterTimelineClient = class {
7456
7507
  };
7457
7508
  }
7458
7509
  async handleTimeline() {
7459
- console.log("Start Hanldeling Twitter Timeline");
7510
+ logger6.info("Starting Twitter timeline processing...");
7460
7511
  const tweets = await this.getTimeline(20);
7512
+ logger6.info(`Fetched ${tweets.length} tweets from timeline`);
7461
7513
  const maxActionsPerCycle = 20;
7462
7514
  const tweetDecisions = [];
7463
7515
  for (const tweet of tweets) {
@@ -7465,7 +7517,7 @@ var TwitterTimelineClient = class {
7465
7517
  const tweetId = this.createTweetId(this.runtime, tweet);
7466
7518
  const memory = await this.runtime.getMemoryById(tweetId);
7467
7519
  if (memory) {
7468
- console.log(`Already processed tweet ID: ${tweet.id}`);
7520
+ logger6.log(`Already processed tweet ID: ${tweet.id}`);
7469
7521
  continue;
7470
7522
  }
7471
7523
  const roomId = createUniqueUuid5(this.runtime, tweet.conversationId);
@@ -7518,7 +7570,21 @@ Choose any combination of [LIKE], [RETWEET], [QUOTE], and [REPLY] that are appro
7518
7570
  });
7519
7571
  };
7520
7572
  const prioritizedTweets = rankByActionRelevance(tweetDecisions);
7521
- this.processTimelineActions(prioritizedTweets);
7573
+ logger6.info(`Processing ${prioritizedTweets.length} tweets with actions`);
7574
+ if (prioritizedTweets.length > 0) {
7575
+ const actionSummary = prioritizedTweets.map((td) => {
7576
+ const actions = [];
7577
+ if (td.actionResponse.like) actions.push("LIKE");
7578
+ if (td.actionResponse.retweet) actions.push("RETWEET");
7579
+ if (td.actionResponse.quote) actions.push("QUOTE");
7580
+ if (td.actionResponse.reply) actions.push("REPLY");
7581
+ return `Tweet ${td.tweet.id}: ${actions.join(", ")}`;
7582
+ });
7583
+ logger6.info(`Actions to execute:
7584
+ ${actionSummary.join("\n")}`);
7585
+ }
7586
+ await this.processTimelineActions(prioritizedTweets);
7587
+ logger6.info("Timeline processing complete");
7522
7588
  }
7523
7589
  async processTimelineActions(tweetDecisions) {
7524
7590
  const results = [];
@@ -7576,6 +7642,10 @@ Choose any combination of [LIKE], [RETWEET], [QUOTE], and [REPLY] that are appro
7576
7642
  }
7577
7643
  async handleLikeAction(tweet) {
7578
7644
  try {
7645
+ if (this.isDryRun) {
7646
+ logger6.log(`[DRY RUN] Would have liked tweet ${tweet.id}`);
7647
+ return;
7648
+ }
7579
7649
  await this.twitterClient.likeTweet(tweet.id);
7580
7650
  logger6.log(`Liked tweet ${tweet.id}`);
7581
7651
  } catch (error) {
@@ -7584,6 +7654,10 @@ Choose any combination of [LIKE], [RETWEET], [QUOTE], and [REPLY] that are appro
7584
7654
  }
7585
7655
  async handleRetweetAction(tweet) {
7586
7656
  try {
7657
+ if (this.isDryRun) {
7658
+ logger6.log(`[DRY RUN] Would have retweeted tweet ${tweet.id}`);
7659
+ return;
7660
+ }
7587
7661
  await this.twitterClient.retweet(tweet.id);
7588
7662
  logger6.log(`Retweeted tweet ${tweet.id}`);
7589
7663
  } catch (error) {
@@ -7605,6 +7679,10 @@ ${tweet.text}`;
7605
7679
  });
7606
7680
  const responseObject = parseKeyValueXml(quoteResponse);
7607
7681
  if (responseObject.post) {
7682
+ if (this.isDryRun) {
7683
+ logger6.log(`[DRY RUN] Would have quoted tweet ${tweet.id} with: ${responseObject.post}`);
7684
+ return;
7685
+ }
7608
7686
  const result = await this.client.requestQueue.add(
7609
7687
  async () => await this.twitterClient.sendQuoteTweet(
7610
7688
  responseObject.post,
@@ -7652,6 +7730,10 @@ ${tweet.text}`;
7652
7730
  });
7653
7731
  const responseObject = parseKeyValueXml(replyResponse);
7654
7732
  if (responseObject.post) {
7733
+ if (this.isDryRun) {
7734
+ logger6.log(`[DRY RUN] Would have replied to tweet ${tweet.id} with: ${responseObject.post}`);
7735
+ return;
7736
+ }
7655
7737
  const tweetResult = await sendTweet(
7656
7738
  this.client,
7657
7739
  responseObject.post,
@@ -7679,6 +7761,10 @@ ${tweet.text}`;
7679
7761
  logger6.error("Error in quote tweet generation:", error);
7680
7762
  }
7681
7763
  }
7764
+ async stop() {
7765
+ logger6.info("Stopping Twitter timeline client...");
7766
+ this.isRunning = false;
7767
+ }
7682
7768
  };
7683
7769
 
7684
7770
  // src/tests.ts
@@ -7785,7 +7871,7 @@ var ClientBaseTestSuite = class {
7785
7871
  };
7786
7872
 
7787
7873
  // src/index.ts
7788
- console.log(`Twitter plugin loaded with service name: ${TWITTER_SERVICE_NAME}`);
7874
+ logger7.info(`Twitter plugin loaded with service name: ${TWITTER_SERVICE_NAME}`);
7789
7875
  var TwitterClientInstance = class {
7790
7876
  constructor(runtime, state) {
7791
7877
  this.client = new ClientBase(runtime, state);
@@ -7844,13 +7930,13 @@ var _TwitterService = class _TwitterService extends Service {
7844
7930
  const client = new TwitterClientInstance(runtime, state);
7845
7931
  await client.client.init();
7846
7932
  if (client.post) {
7847
- client.post.start();
7933
+ await client.post.start();
7848
7934
  }
7849
7935
  if (client.interaction) {
7850
- client.interaction.start();
7936
+ await client.interaction.start();
7851
7937
  }
7852
7938
  if (client.timeline) {
7853
- client.timeline.start();
7939
+ await client.timeline.start();
7854
7940
  }
7855
7941
  this.clients.set(this.getClientKey(clientId, runtime.agentId), client);
7856
7942
  await this.emitServerJoinedEvent(runtime, client);
@@ -7957,7 +8043,15 @@ var _TwitterService = class _TwitterService extends Service {
7957
8043
  const client = this.clients.get(key);
7958
8044
  if (client) {
7959
8045
  try {
7960
- await client.service.stop();
8046
+ if (client.post) {
8047
+ await client.post.stop();
8048
+ }
8049
+ if (client.interaction) {
8050
+ await client.interaction.stop();
8051
+ }
8052
+ if (client.timeline) {
8053
+ await client.timeline.stop();
8054
+ }
7961
8055
  this.clients.delete(key);
7962
8056
  logger7.info(`Stopped Twitter client for ${clientId}`);
7963
8057
  } catch (error) {
@@ -7997,7 +8091,15 @@ var _TwitterService = class _TwitterService extends Service {
7997
8091
  async stopAllClients() {
7998
8092
  for (const [key, client] of this.clients.entries()) {
7999
8093
  try {
8000
- await client.service.stop();
8094
+ if (client.post) {
8095
+ await client.post.stop();
8096
+ }
8097
+ if (client.interaction) {
8098
+ await client.interaction.stop();
8099
+ }
8100
+ if (client.timeline) {
8101
+ await client.timeline.stop();
8102
+ }
8001
8103
  this.clients.delete(key);
8002
8104
  } catch (error) {
8003
8105
  logger7.error(`Error stopping Twitter client ${key}:`, error);