@elizaos/plugin-twitter 1.0.0-alpha.20 → 1.0.0-alpha.21

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 CHANGED
@@ -1,6 +1,10 @@
1
1
  // src/index.ts
2
2
  import {
3
+ ChannelType as ChannelType6,
4
+ EventTypes as EventTypes3,
5
+ Role,
3
6
  Service,
7
+ createUniqueUuid as createUniqueUuid7,
4
8
  logger as logger9
5
9
  } from "@elizaos/core";
6
10
 
@@ -279,12 +283,6 @@ var TwitterGuestAuth = class {
279
283
  this.jar = new CookieJar();
280
284
  this.v2Client = null;
281
285
  }
282
- bearerToken;
283
- jar;
284
- guestToken;
285
- guestCreatedAt;
286
- v2Client;
287
- fetch;
288
286
  cookieJar() {
289
287
  return this.jar;
290
288
  }
@@ -607,7 +605,6 @@ var TwitterUserAuthSubtask = Type.Object({
607
605
  enter_text: Type.Optional(Type.Object({}))
608
606
  });
609
607
  var TwitterUserAuth = class extends TwitterGuestAuth {
610
- userProfile;
611
608
  async isLoggedIn() {
612
609
  const res = await requestApi(
613
610
  "https://api.twitter.com/1.1/account/verify_credentials.json",
@@ -2477,10 +2474,6 @@ var endpoints = {
2477
2474
  ListTweets: "https://twitter.com/i/api/graphql/whF0_KH1fCkdLLoyNPMoEw/ListLatestTweetsTimeline?variables=%7B%22listId%22%3A%221736495155002106192%22%2C%22count%22%3A20%7D&features=%7B%22responsive_web_graphql_exclude_directive_enabled%22%3Atrue%2C%22verified_phone_label_enabled%22%3Afalse%2C%22creator_subscriptions_tweet_preview_api_enabled%22%3Atrue%2C%22responsive_web_graphql_timeline_navigation_enabled%22%3Atrue%2C%22responsive_web_graphql_skip_user_profile_image_extensions_enabled%22%3Afalse%2C%22c9s_tweet_anatomy_moderator_badge_enabled%22%3Atrue%2C%22tweetypie_unmention_optimization_enabled%22%3Atrue%2C%22responsive_web_edit_tweet_api_enabled%22%3Atrue%2C%22graphql_is_translatable_rweb_tweet_is_translatable_enabled%22%3Atrue%2C%22view_counts_everywhere_api_enabled%22%3Atrue%2C%22longform_notetweets_consumption_enabled%22%3Atrue%2C%22responsive_web_twitter_article_tweet_consumption_enabled%22%3Afalse%2C%22tweet_awards_web_tipping_enabled%22%3Afalse%2C%22freedom_of_speech_not_reach_fetch_enabled%22%3Atrue%2C%22standardized_nudges_misinfo%22%3Atrue%2C%22tweet_with_visibility_results_prefer_gql_limited_actions_policy_enabled%22%3Atrue%2C%22rweb_video_timestamps_enabled%22%3Atrue%2C%22longform_notetweets_rich_text_read_enabled%22%3Atrue%2C%22longform_notetweets_inline_media_enabled%22%3Atrue%2C%22responsive_web_media_download_video_enabled%22%3Afalse%2C%22responsive_web_enhance_cards_enabled%22%3Afalse%7D"
2478
2475
  };
2479
2476
  var ApiRequest = class {
2480
- url;
2481
- variables;
2482
- features;
2483
- fieldToggles;
2484
2477
  constructor(info) {
2485
2478
  this.url = info.url;
2486
2479
  this.variables = info.variables;
@@ -3659,9 +3652,6 @@ var Client = class {
3659
3652
  this.token = bearerToken;
3660
3653
  this.useGuestAuth();
3661
3654
  }
3662
- auth;
3663
- authTrends;
3664
- token;
3665
3655
  /**
3666
3656
  * Initializes auth properties using a guest token.
3667
3657
  * Used when creating a new instance of this class, and when logging out.
@@ -4398,7 +4388,6 @@ import { EventEmitter as EventEmitter4 } from "node:events";
4398
4388
 
4399
4389
  // src/client/spaces/logger.ts
4400
4390
  var Logger = class {
4401
- debugEnabled;
4402
4391
  /**
4403
4392
  * Constructor for initializing a new instance of the class.
4404
4393
  *
@@ -4788,14 +4777,9 @@ function setupCommonChatEvents(chatClient, logger10, emitter) {
4788
4777
  import { EventEmitter } from "node:events";
4789
4778
  import WebSocket from "ws";
4790
4779
  var ChatClient = class extends EventEmitter {
4791
- ws;
4792
- connected = false;
4793
- logger;
4794
- spaceId;
4795
- accessToken;
4796
- endpoint;
4797
4780
  constructor(config) {
4798
4781
  super();
4782
+ this.connected = false;
4799
4783
  this.spaceId = config.spaceId;
4800
4784
  this.accessToken = config.accessToken;
4801
4785
  this.endpoint = config.endpoint;
@@ -4994,9 +4978,6 @@ import wrtc from "@roamhq/wrtc";
4994
4978
  var { nonstandard } = wrtc;
4995
4979
  var { RTCAudioSource, RTCAudioSink } = nonstandard;
4996
4980
  var JanusAudioSource = class extends EventEmitter2 {
4997
- source;
4998
- track;
4999
- logger;
5000
4981
  constructor(options) {
5001
4982
  super();
5002
4983
  this.logger = options?.logger;
@@ -5031,11 +5012,9 @@ var JanusAudioSource = class extends EventEmitter2 {
5031
5012
  }
5032
5013
  };
5033
5014
  var JanusAudioSink = class extends EventEmitter2 {
5034
- sink;
5035
- active = true;
5036
- logger;
5037
5015
  constructor(track, options) {
5038
5016
  super();
5017
+ this.active = true;
5039
5018
  this.logger = options?.logger;
5040
5019
  if (track.kind !== "audio") {
5041
5020
  throw new Error("[JanusAudioSink] Provided track is not an audio track");
@@ -5069,19 +5048,13 @@ var JanusClient = class extends EventEmitter3 {
5069
5048
  constructor(config) {
5070
5049
  super();
5071
5050
  this.config = config;
5051
+ this.pollActive = false;
5052
+ // Tracks promises waiting for specific Janus events
5053
+ this.eventWaiters = [];
5054
+ // Tracks subscriber handle+pc for each userId we subscribe to
5055
+ this.subscribers = /* @__PURE__ */ new Map();
5072
5056
  this.logger = config.logger;
5073
5057
  }
5074
- logger;
5075
- sessionId;
5076
- handleId;
5077
- publisherId;
5078
- pc;
5079
- localAudioSource;
5080
- pollActive = false;
5081
- // Tracks promises waiting for specific Janus events
5082
- eventWaiters = [];
5083
- // Tracks subscriber handle+pc for each userId we subscribe to
5084
- subscribers = /* @__PURE__ */ new Map();
5085
5058
  /**
5086
5059
  * Initializes this JanusClient for the host scenario:
5087
5060
  * 1) createSession()
@@ -5729,18 +5702,12 @@ var Space = class extends EventEmitter4 {
5729
5702
  constructor(client, options) {
5730
5703
  super();
5731
5704
  this.client = client;
5705
+ this.isInitialized = false;
5706
+ this.plugins = /* @__PURE__ */ new Set();
5707
+ this.speakers = /* @__PURE__ */ new Map();
5732
5708
  this.debug = options?.debug ?? false;
5733
5709
  this.logger = new Logger(this.debug);
5734
5710
  }
5735
- debug;
5736
- logger;
5737
- janusClient;
5738
- chatClient;
5739
- authToken;
5740
- broadcastInfo;
5741
- isInitialized = false;
5742
- plugins = /* @__PURE__ */ new Set();
5743
- speakers = /* @__PURE__ */ new Map();
5744
5711
  /**
5745
5712
  * Registers a plugin and calls its onAttach(...).
5746
5713
  * init(...) will be invoked once initialization is complete.
@@ -6178,32 +6145,12 @@ var SpaceParticipant = class extends EventEmitter5 {
6178
6145
  constructor(client, config) {
6179
6146
  super();
6180
6147
  this.client = client;
6148
+ // Plugin management
6149
+ this.plugins = /* @__PURE__ */ new Set();
6181
6150
  this.spaceId = config.spaceId;
6182
6151
  this.debug = config.debug ?? false;
6183
6152
  this.logger = new Logger(this.debug);
6184
6153
  }
6185
- spaceId;
6186
- debug;
6187
- logger;
6188
- // Basic auth/cookie data
6189
- cookie;
6190
- authToken;
6191
- // Chat
6192
- chatJwtToken;
6193
- chatToken;
6194
- chatClient;
6195
- // Watch session
6196
- lifecycleToken;
6197
- watchSession;
6198
- // HLS stream
6199
- hlsUrl;
6200
- // Speaker request + Janus
6201
- sessionUUID;
6202
- janusJwt;
6203
- webrtcGwUrl;
6204
- janusClient;
6205
- // Plugin management
6206
- plugins = /* @__PURE__ */ new Set();
6207
6154
  /**
6208
6155
  * Adds a plugin and calls its onAttach immediately.
6209
6156
  * init() or onJanusReady() will be invoked later at the appropriate time.
@@ -6501,12 +6448,9 @@ var IdleMonitorPlugin = class {
6501
6448
  constructor(idleTimeoutMs = 6e4, checkEveryMs = 1e4) {
6502
6449
  this.idleTimeoutMs = idleTimeoutMs;
6503
6450
  this.checkEveryMs = checkEveryMs;
6451
+ this.lastSpeakerAudioMs = Date.now();
6452
+ this.lastLocalAudioMs = Date.now();
6504
6453
  }
6505
- space;
6506
- logger;
6507
- lastSpeakerAudioMs = Date.now();
6508
- lastLocalAudioMs = Date.now();
6509
- checkInterval;
6510
6454
  /**
6511
6455
  * Called immediately after .use(plugin).
6512
6456
  * Allows for minimal setup, including obtaining a debug logger if desired.
@@ -6576,6 +6520,7 @@ var IdleMonitorPlugin = class {
6576
6520
  import { spawn } from "node:child_process";
6577
6521
  import {
6578
6522
  ChannelType,
6523
+ EventTypes,
6579
6524
  ModelTypes,
6580
6525
  createUniqueUuid,
6581
6526
  logger
@@ -6584,23 +6529,20 @@ var VOLUME_WINDOW_SIZE = 100;
6584
6529
  var SPEAKING_THRESHOLD = 0.05;
6585
6530
  var SILENCE_DETECTION_THRESHOLD_MS = 1e3;
6586
6531
  var SttTtsPlugin2 = class {
6587
- name = "SttTtsPlugin";
6588
- description = "Speech-to-text (OpenAI) + conversation + TTS (ElevenLabs)";
6589
- runtime;
6590
- spaceId;
6591
- space;
6592
- janus;
6593
- /**
6594
- * userId => arrayOfChunks (PCM Int16)
6595
- */
6596
- pcmBuffers = /* @__PURE__ */ new Map();
6597
- // TTS queue for sequentially speaking
6598
- ttsQueue = [];
6599
- isSpeaking = false;
6600
- isProcessingAudio = false;
6601
- userSpeakingTimer = null;
6602
- volumeBuffers;
6603
- ttsAbortController = null;
6532
+ constructor() {
6533
+ this.name = "SttTtsPlugin";
6534
+ this.description = "Speech-to-text (OpenAI) + conversation + TTS (ElevenLabs)";
6535
+ /**
6536
+ * userId => arrayOfChunks (PCM Int16)
6537
+ */
6538
+ this.pcmBuffers = /* @__PURE__ */ new Map();
6539
+ // TTS queue for sequentially speaking
6540
+ this.ttsQueue = [];
6541
+ this.isSpeaking = false;
6542
+ this.isProcessingAudio = false;
6543
+ this.userSpeakingTimer = null;
6544
+ this.ttsAbortController = null;
6545
+ }
6604
6546
  onAttach(_space) {
6605
6547
  logger.log("[SttTtsPlugin] onAttach => space was attached");
6606
6548
  }
@@ -6811,9 +6753,9 @@ var SttTtsPlugin2 = class {
6811
6753
  `twitter_generate_room-${this.spaceId}`
6812
6754
  );
6813
6755
  const userUuid = createUniqueUuid(this.runtime, numericId);
6814
- const entity = await this.runtime.getDatabaseAdapter().getEntityById(userUuid);
6756
+ const entity = await this.runtime.getEntityById(userUuid);
6815
6757
  if (!entity) {
6816
- await this.runtime.getDatabaseAdapter().createEntity({
6758
+ await this.runtime.createEntity({
6817
6759
  id: userUuid,
6818
6760
  names: [userId],
6819
6761
  agentId: this.runtime.agentId
@@ -6872,7 +6814,7 @@ var SttTtsPlugin2 = class {
6872
6814
  return [];
6873
6815
  }
6874
6816
  };
6875
- this.runtime.emitEvent(["VOICE_MESSAGE_RECEIVED"], {
6817
+ this.runtime.emitEvent(EventTypes.VOICE_MESSAGE_RECEIVED, {
6876
6818
  runtime: this.runtime,
6877
6819
  message: memory,
6878
6820
  callback
@@ -6985,10 +6927,6 @@ import {
6985
6927
  createUniqueUuid as createUniqueUuid2,
6986
6928
  logger as logger2
6987
6929
  } from "@elizaos/core";
6988
- var wait = (minTime = 1e3, maxTime = 3e3) => {
6989
- const waitTime = Math.floor(Math.random() * (maxTime - minTime + 1)) + minTime;
6990
- return new Promise((resolve) => setTimeout(resolve, waitTime));
6991
- };
6992
6930
  async function buildConversationThread(tweet, client, maxReplies = 10) {
6993
6931
  const thread = [];
6994
6932
  const visited = /* @__PURE__ */ new Set();
@@ -7110,190 +7048,6 @@ async function fetchMediaData(attachments) {
7110
7048
  })
7111
7049
  );
7112
7050
  }
7113
- async function sendTweet(client, content, roomId, twitterUsername, inReplyTo) {
7114
- const isLongTweet = content.text.length > 280 - 1;
7115
- const tweetChunks = splitTweetContent(content.text, 280 - 1);
7116
- const sentTweets = [];
7117
- let previousTweetId = inReplyTo;
7118
- for (const chunk of tweetChunks) {
7119
- let mediaData = null;
7120
- if (content.attachments && content.attachments.length > 0) {
7121
- mediaData = await fetchMediaData(content.attachments);
7122
- }
7123
- const cleanChunk = deduplicateMentions(chunk.trim());
7124
- const result = await client.requestQueue.add(
7125
- async () => isLongTweet ? client.twitterClient.sendLongTweet(
7126
- cleanChunk,
7127
- previousTweetId,
7128
- mediaData
7129
- ) : client.twitterClient.sendTweet(
7130
- cleanChunk,
7131
- previousTweetId,
7132
- mediaData
7133
- )
7134
- );
7135
- const body = await result.json();
7136
- const tweetResult = isLongTweet ? body?.data?.notetweet_create?.tweet_results?.result : body?.data?.create_tweet?.tweet_results?.result;
7137
- if (tweetResult) {
7138
- const finalTweet = {
7139
- id: tweetResult.rest_id,
7140
- text: tweetResult.legacy.full_text,
7141
- conversationId: tweetResult.legacy.conversation_id_str,
7142
- timestamp: new Date(tweetResult.legacy.created_at).getTime() / 1e3,
7143
- userId: tweetResult.legacy.user_id_str,
7144
- inReplyToStatusId: tweetResult.legacy.in_reply_to_status_id_str,
7145
- permanentUrl: `https://twitter.com/${twitterUsername}/status/${tweetResult.rest_id}`,
7146
- hashtags: [],
7147
- mentions: [],
7148
- photos: [],
7149
- thread: [],
7150
- urls: [],
7151
- videos: []
7152
- };
7153
- sentTweets.push(finalTweet);
7154
- previousTweetId = finalTweet.id;
7155
- } else {
7156
- logger2.error("Error sending tweet chunk:", {
7157
- chunk,
7158
- response: body
7159
- });
7160
- }
7161
- await wait(1e3, 2e3);
7162
- }
7163
- const memories = sentTweets.map((tweet) => ({
7164
- id: createUniqueUuid2(client.runtime, tweet.id),
7165
- agentId: client.runtime.agentId,
7166
- entityId: client.runtime.agentId,
7167
- content: {
7168
- tweetId: tweet.id,
7169
- text: tweet.text,
7170
- source: "twitter",
7171
- url: tweet.permanentUrl,
7172
- imageUrls: tweet.photos.map((p) => p.url) || [],
7173
- inReplyTo: tweet.inReplyToStatusId ? createUniqueUuid2(client.runtime, tweet.inReplyToStatusId) : void 0
7174
- },
7175
- roomId,
7176
- createdAt: tweet.timestamp * 1e3
7177
- }));
7178
- return memories;
7179
- }
7180
- function splitTweetContent(content, maxLength) {
7181
- const paragraphs = content.split("\n\n").map((p) => p.trim());
7182
- const tweets = [];
7183
- let currentTweet = "";
7184
- for (const paragraph of paragraphs) {
7185
- if (!paragraph) continue;
7186
- if (`${currentTweet}
7187
-
7188
- ${paragraph}`.trim().length <= maxLength) {
7189
- if (currentTweet) {
7190
- currentTweet += `
7191
-
7192
- ${paragraph}`;
7193
- } else {
7194
- currentTweet = paragraph;
7195
- }
7196
- } else {
7197
- if (currentTweet) {
7198
- tweets.push(currentTweet.trim());
7199
- }
7200
- if (paragraph.length <= maxLength) {
7201
- currentTweet = paragraph;
7202
- } else {
7203
- const chunks = splitParagraph(paragraph, maxLength);
7204
- tweets.push(...chunks.slice(0, -1));
7205
- currentTweet = chunks[chunks.length - 1];
7206
- }
7207
- }
7208
- }
7209
- if (currentTweet) {
7210
- tweets.push(currentTweet.trim());
7211
- }
7212
- return tweets;
7213
- }
7214
- function extractUrls(paragraph) {
7215
- const urlRegex = /https?:\/\/[^\s]+/g;
7216
- const placeholderMap = /* @__PURE__ */ new Map();
7217
- let urlIndex = 0;
7218
- const textWithPlaceholders = paragraph.replace(urlRegex, (match) => {
7219
- const placeholder = `<<URL_CONSIDERER_23_${urlIndex}>>`;
7220
- placeholderMap.set(placeholder, match);
7221
- urlIndex++;
7222
- return placeholder;
7223
- });
7224
- return { textWithPlaceholders, placeholderMap };
7225
- }
7226
- function splitSentencesAndWords(text, maxLength) {
7227
- const sentences = text.match(/[^.!?]+[.!?]+|[^.!?]+$/g) || [text];
7228
- const chunks = [];
7229
- let currentChunk = "";
7230
- for (const sentence of sentences) {
7231
- if (`${currentChunk} ${sentence}`.trim().length <= maxLength) {
7232
- if (currentChunk) {
7233
- currentChunk += ` ${sentence}`;
7234
- } else {
7235
- currentChunk = sentence;
7236
- }
7237
- } else {
7238
- if (currentChunk) {
7239
- chunks.push(currentChunk.trim());
7240
- }
7241
- if (sentence.length <= maxLength) {
7242
- currentChunk = sentence;
7243
- } else {
7244
- const words = sentence.split(" ");
7245
- currentChunk = "";
7246
- for (const word of words) {
7247
- if (`${currentChunk} ${word}`.trim().length <= maxLength) {
7248
- if (currentChunk) {
7249
- currentChunk += ` ${word}`;
7250
- } else {
7251
- currentChunk = word;
7252
- }
7253
- } else {
7254
- if (currentChunk) {
7255
- chunks.push(currentChunk.trim());
7256
- }
7257
- currentChunk = word;
7258
- }
7259
- }
7260
- }
7261
- }
7262
- }
7263
- if (currentChunk) {
7264
- chunks.push(currentChunk.trim());
7265
- }
7266
- return chunks;
7267
- }
7268
- function deduplicateMentions(paragraph) {
7269
- const mentionRegex = /^@(\w+)(?:\s+@(\w+))*(\s+|$)/;
7270
- const matches = paragraph.match(mentionRegex);
7271
- if (!matches) {
7272
- return paragraph;
7273
- }
7274
- let mentions = matches.slice(0, 1)[0].trim().split(" ");
7275
- mentions = Array.from(new Set(mentions));
7276
- const uniqueMentionsString = mentions.join(" ");
7277
- const endOfMentions = paragraph.indexOf(matches[0]) + matches[0].length;
7278
- return `${uniqueMentionsString} ${paragraph.slice(endOfMentions)}`;
7279
- }
7280
- function restoreUrls(chunks, placeholderMap) {
7281
- return chunks.map((chunk) => {
7282
- return chunk.replace(/<<URL_CONSIDERER_23_(\d+)>>/g, (match) => {
7283
- const original = placeholderMap.get(match);
7284
- return original || match;
7285
- });
7286
- });
7287
- }
7288
- function splitParagraph(paragraph, maxLength) {
7289
- const { textWithPlaceholders, placeholderMap } = extractUrls(paragraph);
7290
- const splittedChunks = splitSentencesAndWords(
7291
- textWithPlaceholders,
7292
- maxLength
7293
- );
7294
- const restoredChunks = restoreUrls(splittedChunks, placeholderMap);
7295
- return restoredChunks;
7296
- }
7297
7051
  async function generateFiller(runtime, fillerType) {
7298
7052
  try {
7299
7053
  const prompt = composePrompt({
@@ -7366,25 +7120,15 @@ async function isAgentInSpace(client, spaceId) {
7366
7120
 
7367
7121
  // src/spaces.ts
7368
7122
  var TwitterSpaceClient = class {
7369
- runtime;
7370
- client;
7371
- twitterClient;
7372
- currentSpace;
7373
- spaceId;
7374
- startedAt;
7375
- checkInterval;
7376
- lastSpaceEndedAt;
7377
- sttTtsPlugin;
7378
- spaceStatus = "idle" /* IDLE */;
7379
- spaceParticipant = null;
7380
- participantStatus = "listener" /* LISTENER */;
7381
- /**
7382
- * We now store an array of active speakers, not just 1
7383
- */
7384
- activeSpeakers = [];
7385
- speakerQueue = [];
7386
- decisionOptions;
7387
7123
  constructor(client, runtime) {
7124
+ this.spaceStatus = "idle" /* IDLE */;
7125
+ this.spaceParticipant = null;
7126
+ this.participantStatus = "listener" /* LISTENER */;
7127
+ /**
7128
+ * We now store an array of active speakers, not just 1
7129
+ */
7130
+ this.activeSpeakers = [];
7131
+ this.speakerQueue = [];
7388
7132
  this.client = client;
7389
7133
  this.twitterClient = client.twitterClient;
7390
7134
  this.runtime = runtime;
@@ -7934,8 +7678,10 @@ import {
7934
7678
  logger as logger5
7935
7679
  } from "@elizaos/core";
7936
7680
  var RequestQueue = class {
7937
- queue = [];
7938
- processing = false;
7681
+ constructor() {
7682
+ this.queue = [];
7683
+ this.processing = false;
7684
+ }
7939
7685
  /**
7940
7686
  * Asynchronously adds a request to the queue, then processes the queue.
7941
7687
  *
@@ -7998,14 +7744,23 @@ var RequestQueue = class {
7998
7744
  await new Promise((resolve) => setTimeout(resolve, delay));
7999
7745
  }
8000
7746
  };
8001
- var ClientBase = class _ClientBase extends EventEmitter6 {
8002
- static _twitterClients = {};
8003
- twitterClient;
8004
- runtime;
8005
- lastCheckedTweetId = null;
8006
- temperature = 0.5;
8007
- requestQueue = new RequestQueue();
8008
- profile;
7747
+ var _ClientBase = class _ClientBase extends EventEmitter6 {
7748
+ constructor(runtime, state) {
7749
+ super();
7750
+ this.lastCheckedTweetId = null;
7751
+ this.temperature = 0.5;
7752
+ this.requestQueue = new RequestQueue();
7753
+ this.callback = null;
7754
+ this.runtime = runtime;
7755
+ this.state = state;
7756
+ const username = state?.TWITTER_USERNAME || this.runtime.getSetting("TWITTER_USERNAME");
7757
+ if (_ClientBase._twitterClients[username]) {
7758
+ this.twitterClient = _ClientBase._twitterClients[username];
7759
+ } else {
7760
+ this.twitterClient = new Client();
7761
+ _ClientBase._twitterClients[username] = this.twitterClient;
7762
+ }
7763
+ }
8009
7764
  /**
8010
7765
  * Caches a tweet in the database.
8011
7766
  *
@@ -8017,7 +7772,7 @@ var ClientBase = class _ClientBase extends EventEmitter6 {
8017
7772
  console.warn("Tweet is undefined, skipping cache");
8018
7773
  return;
8019
7774
  }
8020
- this.runtime.getDatabaseAdapter().setCache(`twitter/tweets/${tweet.id}`, tweet);
7775
+ this.runtime.setCache(`twitter/tweets/${tweet.id}`, tweet);
8021
7776
  }
8022
7777
  /**
8023
7778
  * Retrieves a cached tweet by its ID.
@@ -8025,7 +7780,9 @@ var ClientBase = class _ClientBase extends EventEmitter6 {
8025
7780
  * @returns {Promise<Tweet | undefined>} A Promise that resolves to the cached tweet, or undefined if the tweet is not found in the cache.
8026
7781
  */
8027
7782
  async getCachedTweet(tweetId) {
8028
- const cached = await this.runtime.getDatabaseAdapter().getCache(`twitter/tweets/${tweetId}`);
7783
+ const cached = await this.runtime.getCache(
7784
+ `twitter/tweets/${tweetId}`
7785
+ );
8029
7786
  if (!cached) {
8030
7787
  return void 0;
8031
7788
  }
@@ -8049,7 +7806,6 @@ var ClientBase = class _ClientBase extends EventEmitter6 {
8049
7806
  await this.cacheTweet(tweet);
8050
7807
  return tweet;
8051
7808
  }
8052
- callback = null;
8053
7809
  /**
8054
7810
  * This method is called when the application is ready.
8055
7811
  * It throws an error indicating that it is not implemented in the base class
@@ -8124,19 +7880,6 @@ var ClientBase = class _ClientBase extends EventEmitter6 {
8124
7880
  };
8125
7881
  return t;
8126
7882
  }
8127
- state;
8128
- constructor(runtime, state) {
8129
- super();
8130
- this.runtime = runtime;
8131
- this.state = state;
8132
- const username = state?.TWITTER_USERNAME || this.runtime.getSetting("TWITTER_USERNAME");
8133
- if (_ClientBase._twitterClients[username]) {
8134
- this.twitterClient = _ClientBase._twitterClients[username];
8135
- } else {
8136
- this.twitterClient = new Client();
8137
- _ClientBase._twitterClients[username] = this.twitterClient;
8138
- }
8139
- }
8140
7883
  async init() {
8141
7884
  const username = this.state?.TWITTER_USERNAME || this.runtime.getSetting("TWITTER_USERNAME");
8142
7885
  const password = this.state?.TWITTER_PASSWORD || this.runtime.getSetting("TWITTER_PASSWORD");
@@ -8151,28 +7894,25 @@ var ClientBase = class _ClientBase extends EventEmitter6 {
8151
7894
  `Missing required Twitter credentials: ${missing.join(", ")}`
8152
7895
  );
8153
7896
  }
8154
- let retries = (this.state?.TWITTER_RETRY_LIMIT || this.runtime.getSetting(
8155
- "TWITTER_RETRY_LIMIT"
8156
- )) ?? 3;
8157
- if (!username) {
8158
- throw new Error("Twitter username not configured");
8159
- }
8160
- const authToken = this.state?.TWITTER_COOKIES_AUTH_TOKEN || this.runtime.getSetting("TWITTER_COOKIES_AUTH_TOKEN");
8161
- const ct0 = this.state?.TWITTER_COOKIES_CT0 || this.runtime.getSetting("TWITTER_COOKIES_CT0");
8162
- const guestId = this.state?.TWITTER_COOKIES_GUEST_ID || this.runtime.getSetting("TWITTER_COOKIES_GUEST_ID");
8163
- const createTwitterCookies = (authToken2, ct02, guestId2) => authToken2 && ct02 && guestId2 ? [
8164
- { key: "auth_token", value: authToken2, domain: ".twitter.com" },
8165
- { key: "ct0", value: ct02, domain: ".twitter.com" },
8166
- { key: "guest_id", value: guestId2, domain: ".twitter.com" }
8167
- ] : null;
8168
- const cachedCookies = await this.getCachedCookies(username) || createTwitterCookies(authToken, ct0, guestId);
8169
- if (cachedCookies) {
8170
- logger5.info("Using cached cookies");
8171
- await this.setCookiesFromArray(cachedCookies);
8172
- }
8173
- logger5.log("Waiting for Twitter login");
8174
- while (retries > 0) {
7897
+ const maxRetries = 3;
7898
+ let retryCount = 0;
7899
+ let lastError = null;
7900
+ while (retryCount < maxRetries) {
8175
7901
  try {
7902
+ const authToken = this.state?.TWITTER_COOKIES_AUTH_TOKEN || this.runtime.getSetting("TWITTER_COOKIES_AUTH_TOKEN");
7903
+ const ct0 = this.state?.TWITTER_COOKIES_CT0 || this.runtime.getSetting("TWITTER_COOKIES_CT0");
7904
+ const guestId = this.state?.TWITTER_COOKIES_GUEST_ID || this.runtime.getSetting("TWITTER_COOKIES_GUEST_ID");
7905
+ const createTwitterCookies = (authToken2, ct02, guestId2) => authToken2 && ct02 && guestId2 ? [
7906
+ { key: "auth_token", value: authToken2, domain: ".twitter.com" },
7907
+ { key: "ct0", value: ct02, domain: ".twitter.com" },
7908
+ { key: "guest_id", value: guestId2, domain: ".twitter.com" }
7909
+ ] : null;
7910
+ const cachedCookies = await this.getCachedCookies(username) || createTwitterCookies(authToken, ct0, guestId);
7911
+ if (cachedCookies) {
7912
+ logger5.info("Using cached cookies");
7913
+ await this.setCookiesFromArray(cachedCookies);
7914
+ }
7915
+ logger5.log("Waiting for Twitter login");
8176
7916
  if (await this.twitterClient.isLoggedIn()) {
8177
7917
  logger5.info("Successfully logged in.");
8178
7918
  break;
@@ -8193,17 +7933,22 @@ var ClientBase = class _ClientBase extends EventEmitter6 {
8193
7933
  break;
8194
7934
  }
8195
7935
  } catch (error) {
8196
- logger5.error(`Login attempt failed: ${error.message}`);
7936
+ lastError = error instanceof Error ? error : new Error(String(error));
7937
+ logger5.error(
7938
+ `Login attempt ${retryCount + 1} failed: ${lastError.message}`
7939
+ );
7940
+ retryCount++;
7941
+ if (retryCount < maxRetries) {
7942
+ const delay = 2 ** retryCount * 1e3;
7943
+ logger5.info(`Retrying in ${delay / 1e3} seconds...`);
7944
+ await new Promise((resolve) => setTimeout(resolve, delay));
7945
+ }
8197
7946
  }
8198
- retries--;
8199
- logger5.error(
8200
- `Failed to login to Twitter. Retrying... (${retries} attempts left)`
7947
+ }
7948
+ if (retryCount === maxRetries) {
7949
+ throw new Error(
7950
+ `Twitter login failed after ${maxRetries} attempts. Last error: ${lastError?.message}`
8201
7951
  );
8202
- if (retries === 0) {
8203
- logger5.error("Max retries reached. Exiting login process.");
8204
- throw new Error("Twitter login failed after maximum retries.");
8205
- }
8206
- await new Promise((resolve) => setTimeout(resolve, 2e3));
8207
7952
  }
8208
7953
  this.profile = await this.fetchProfile(username);
8209
7954
  if (this.profile) {
@@ -8417,7 +8162,7 @@ var ClientBase = class _ClientBase extends EventEmitter6 {
8417
8162
  }
8418
8163
  }
8419
8164
  async loadLatestCheckedTweetId() {
8420
- const latestCheckedTweetId = await this.runtime.getDatabaseAdapter().getCache(
8165
+ const latestCheckedTweetId = await this.runtime.getCache(
8421
8166
  `twitter/${this.profile.username}/latest_checked_tweet_id`
8422
8167
  );
8423
8168
  if (latestCheckedTweetId) {
@@ -8426,34 +8171,44 @@ var ClientBase = class _ClientBase extends EventEmitter6 {
8426
8171
  }
8427
8172
  async cacheLatestCheckedTweetId() {
8428
8173
  if (this.lastCheckedTweetId) {
8429
- await this.runtime.getDatabaseAdapter().setCache(
8174
+ await this.runtime.setCache(
8430
8175
  `twitter/${this.profile.username}/latest_checked_tweet_id`,
8431
8176
  this.lastCheckedTweetId.toString()
8432
8177
  );
8433
8178
  }
8434
8179
  }
8435
8180
  async getCachedTimeline() {
8436
- const cached = await this.runtime.getDatabaseAdapter().getCache(`twitter/${this.profile.username}/timeline`);
8181
+ const cached = await this.runtime.getCache(
8182
+ `twitter/${this.profile.username}/timeline`
8183
+ );
8437
8184
  if (!cached) {
8438
8185
  return void 0;
8439
8186
  }
8440
8187
  return cached;
8441
8188
  }
8442
8189
  async cacheTimeline(timeline) {
8443
- await this.runtime.getDatabaseAdapter().setCache(`twitter/${this.profile.username}/timeline`, timeline);
8190
+ await this.runtime.setCache(
8191
+ `twitter/${this.profile.username}/timeline`,
8192
+ timeline
8193
+ );
8444
8194
  }
8445
8195
  async cacheMentions(mentions) {
8446
- await this.runtime.getDatabaseAdapter().setCache(`twitter/${this.profile.username}/mentions`, mentions);
8196
+ await this.runtime.setCache(
8197
+ `twitter/${this.profile.username}/mentions`,
8198
+ mentions
8199
+ );
8447
8200
  }
8448
8201
  async getCachedCookies(username) {
8449
- const cached = await this.runtime.getDatabaseAdapter().getCache(`twitter/${username}/cookies`);
8202
+ const cached = await this.runtime.getCache(
8203
+ `twitter/${username}/cookies`
8204
+ );
8450
8205
  if (!cached) {
8451
8206
  return void 0;
8452
8207
  }
8453
8208
  return cached;
8454
8209
  }
8455
8210
  async cacheCookies(username, cookies) {
8456
- await this.runtime.getDatabaseAdapter().setCache(`twitter/${username}/cookies`, cookies);
8211
+ await this.runtime.setCache(`twitter/${username}/cookies`, cookies);
8457
8212
  }
8458
8213
  async fetchProfile(username) {
8459
8214
  try {
@@ -8473,7 +8228,110 @@ var ClientBase = class _ClientBase extends EventEmitter6 {
8473
8228
  throw error;
8474
8229
  }
8475
8230
  }
8231
+ /**
8232
+ * Fetches recent interactions (likes, retweets, quotes) for the authenticated user's tweets
8233
+ */
8234
+ async fetchInteractions() {
8235
+ try {
8236
+ const interactions = await this.requestQueue.add(
8237
+ () => this.twitterClient.get("statuses/mentions_timeline", {
8238
+ count: 100,
8239
+ include_entities: true
8240
+ })
8241
+ );
8242
+ return interactions.map((interaction) => ({
8243
+ id: interaction.id_str,
8244
+ type: this.getInteractionType(interaction),
8245
+ userId: interaction.user.id_str,
8246
+ username: interaction.user.screen_name,
8247
+ name: interaction.user.name,
8248
+ targetTweetId: interaction.in_reply_to_status_id_str || interaction.quoted_status_id_str,
8249
+ targetTweet: interaction.quoted_status || interaction,
8250
+ quoteTweet: interaction.is_quote_status ? interaction : void 0,
8251
+ retweetId: interaction.retweeted_status?.id_str
8252
+ }));
8253
+ } catch (error) {
8254
+ logger5.error("Error fetching Twitter interactions:", error);
8255
+ return [];
8256
+ }
8257
+ }
8258
+ /**
8259
+ * Determines the type of interaction from a Twitter API response
8260
+ */
8261
+ getInteractionType(interaction) {
8262
+ if (interaction.retweeted_status) {
8263
+ return "retweet";
8264
+ }
8265
+ if (interaction.is_quote_status) {
8266
+ return "quote";
8267
+ }
8268
+ return "like";
8269
+ }
8270
+ /**
8271
+ * Fetches recent follower changes (new followers and unfollowers)
8272
+ */
8273
+ async fetchFollowerChanges() {
8274
+ try {
8275
+ const currentFollowers = await this.requestQueue.add(
8276
+ () => this.twitterClient.get("followers/list", {
8277
+ count: 200,
8278
+ include_user_entities: false
8279
+ })
8280
+ );
8281
+ const cachedFollowers = await this.getCachedFollowers();
8282
+ const changes = [];
8283
+ for (const follower of currentFollowers.users) {
8284
+ if (!cachedFollowers.some(
8285
+ (f) => f.id_str === follower.id_str
8286
+ )) {
8287
+ changes.push({
8288
+ type: "followed",
8289
+ userId: follower.id_str,
8290
+ username: follower.screen_name,
8291
+ name: follower.name,
8292
+ user: follower
8293
+ });
8294
+ }
8295
+ }
8296
+ for (const cached of cachedFollowers) {
8297
+ if (!currentFollowers.users.some((f) => f.id_str === cached.id_str)) {
8298
+ changes.push({
8299
+ type: "unfollowed",
8300
+ userId: cached.id_str,
8301
+ username: cached.screen_name,
8302
+ name: cached.name,
8303
+ user: cached
8304
+ });
8305
+ }
8306
+ }
8307
+ await this.cacheFollowers(currentFollowers.users);
8308
+ return changes;
8309
+ } catch (error) {
8310
+ logger5.error("Error fetching Twitter follower changes:", error);
8311
+ return [];
8312
+ }
8313
+ }
8314
+ /**
8315
+ * Gets cached followers from the database
8316
+ */
8317
+ async getCachedFollowers() {
8318
+ const cached = await this.runtime.getCache(
8319
+ `twitter/${this.profile.username}/followers`
8320
+ );
8321
+ return cached || [];
8322
+ }
8323
+ /**
8324
+ * Caches current followers in the database
8325
+ */
8326
+ async cacheFollowers(followers) {
8327
+ await this.runtime.setCache(
8328
+ `twitter/${this.profile.username}/followers`,
8329
+ followers
8330
+ );
8331
+ }
8476
8332
  };
8333
+ _ClientBase._twitterClients = {};
8334
+ var ClientBase = _ClientBase;
8477
8335
 
8478
8336
  // src/constants.ts
8479
8337
  var TWITTER_SERVICE_NAME = "twitter";
@@ -8481,58 +8339,38 @@ var TWITTER_SERVICE_NAME = "twitter";
8481
8339
  // src/interactions.ts
8482
8340
  import {
8483
8341
  ChannelType as ChannelType4,
8342
+ EventTypes as EventTypes2,
8484
8343
  ModelTypes as ModelTypes4,
8485
8344
  composePrompt as composePrompt2,
8486
8345
  createUniqueUuid as createUniqueUuid4,
8487
- logger as logger6,
8488
- parseJSONObjectFromText
8346
+ logger as logger6
8489
8347
  } from "@elizaos/core";
8490
- var twitterMessageHandlerTemplate = `# Task: Generate dialog and actions for {{agentName}}.
8491
- {{providers}}
8492
- Here is the current post text again. Remember to include an action if the current post text includes a prompt that asks for one of the available actions mentioned above (does not need to be exact)
8493
- {{currentPost}}
8494
- {{imageDescriptions}}
8495
-
8496
- # Instructions: Write the next message for {{agentName}}. Include the appropriate action from the list: {{actionNames}}
8497
- Response format should be formatted in a valid JSON block like this:
8498
- \`\`\`json
8499
- { "thought": "<string>", "name": "{{agentName}}", "text": "<string>", "action": "<string>" }
8500
- \`\`\`
8501
-
8502
- The "action" field should be one of the options in [Available Actions] and the "text" field should be the response you want to send. Do not including any thinking or internal reflection in the "text" field. "thought" should be a short description of what the agent is thinking about before responding, inlcuding a brief justification for the response.`;
8503
- var twitterShouldRespondTemplate = `# INSTRUCTIONS: Determine if {{agentName}} (@{{twitterUserName}}) should respond to the message and participate in the conversation. Do not comment. Just respond with "true" or "false".
8504
-
8505
- Response options are RESPOND, IGNORE and STOP.
8506
-
8507
- For other users:
8508
- - {{agentName}} should RESPOND to messages directed at them
8509
- - {{agentName}} should RESPOND to conversations relevant to their background
8510
- - {{agentName}} should IGNORE irrelevant messages
8511
- - {{agentName}} should IGNORE very short messages unless directly addressed
8512
- - {{agentName}} should STOP if asked to stop
8513
- - {{agentName}} should STOP if conversation is concluded
8514
- - {{agentName}} is in a room with other users and wants to be conversational, but not annoying.
8515
8348
 
8516
- IMPORTANT:
8517
- - {{agentName}} (aka @{{twitterUserName}}) is particularly sensitive about being annoying, so if there is any doubt, it is better to IGNORE than to RESPOND.
8518
- - For users not in the priority list, {{agentName}} (@{{twitterUserName}}) should err on the side of IGNORE rather than RESPOND if in doubt.
8519
-
8520
- Recent Posts:
8521
- {{recentPosts}}
8522
-
8523
- Current Post:
8524
- {{currentPost}}
8525
-
8526
- Thread of Tweets You Are Replying To:
8527
- {{formattedConversation}}
8349
+ // src/types.ts
8350
+ var ServiceTypes2 = {
8351
+ TWITTER: "twitter"
8352
+ };
8528
8353
 
8529
- # INSTRUCTIONS: Respond with RESPOND if {{agentName}} should respond, or IGNORE if {{agentName}} should not respond to the last message and STOP if {{agentName}} should stop participating in the conversation.
8530
- The available options are RESPOND, IGNORE, or STOP. Choose the most appropriate option.`;
8354
+ // src/interactions.ts
8355
+ var convertToCoreTweet = (tweet) => ({
8356
+ id: tweet.id,
8357
+ text: tweet.text,
8358
+ conversationId: tweet.conversationId,
8359
+ timestamp: tweet.timestamp,
8360
+ userId: tweet.userId,
8361
+ username: tweet.username,
8362
+ name: tweet.name,
8363
+ inReplyToStatusId: tweet.inReplyToStatusId,
8364
+ permanentUrl: tweet.permanentUrl,
8365
+ photos: tweet.photos,
8366
+ hashtags: tweet.hashtags,
8367
+ mentions: tweet.mentions.map((mention) => mention.username),
8368
+ urls: tweet.urls,
8369
+ videos: tweet.videos,
8370
+ thread: tweet.thread
8371
+ });
8372
+ var convertToCoreTweets = (tweets) => tweets.map(convertToCoreTweet);
8531
8373
  var TwitterInteractionClient = class {
8532
- client;
8533
- runtime;
8534
- isDryRun;
8535
- state;
8536
8374
  /**
8537
8375
  * Constructor for setting up a new instance with the provided client, runtime, and state.
8538
8376
  * @param {ClientBase} client - The client being used for communication.
@@ -8611,6 +8449,68 @@ var TwitterInteractionClient = class {
8611
8449
  entityId,
8612
8450
  roomId
8613
8451
  };
8452
+ if (tweet.text.includes(`@${twitterUsername}`)) {
8453
+ const messagePayload = {
8454
+ runtime: this.runtime,
8455
+ message: {
8456
+ ...message,
8457
+ content: {
8458
+ ...message.content,
8459
+ source: "twitter"
8460
+ },
8461
+ roomId: message.roomId
8462
+ },
8463
+ source: "twitter",
8464
+ callback: async (response) => {
8465
+ logger6.info("Received message response:", response);
8466
+ return [];
8467
+ }
8468
+ };
8469
+ this.runtime.emitEvent(EventTypes2.MESSAGE_RECEIVED, messagePayload);
8470
+ const mentionPayload = {
8471
+ runtime: this.runtime,
8472
+ message: {
8473
+ ...message,
8474
+ content: {
8475
+ ...message.content,
8476
+ source: "twitter"
8477
+ },
8478
+ roomId: message.roomId
8479
+ },
8480
+ tweet: convertToCoreTweet(tweet),
8481
+ user: {
8482
+ id: tweet.userId,
8483
+ username: tweet.username,
8484
+ name: tweet.name
8485
+ },
8486
+ source: "twitter",
8487
+ callback: async (response) => {
8488
+ logger6.info("Received mention response:", response);
8489
+ return [];
8490
+ }
8491
+ };
8492
+ this.runtime.emitEvent("TWITTER_MENTION_RECEIVED" /* MENTION_RECEIVED */, mentionPayload);
8493
+ }
8494
+ if (thread.length > 1) {
8495
+ const threadPayload = {
8496
+ runtime: this.runtime,
8497
+ tweets: convertToCoreTweets(thread),
8498
+ user: {
8499
+ id: tweet.userId,
8500
+ username: tweet.username,
8501
+ name: tweet.name
8502
+ },
8503
+ source: "twitter"
8504
+ };
8505
+ if (thread[thread.length - 1].id === tweet.id) {
8506
+ this.runtime.emitEvent("TWITTER_THREAD_UPDATED" /* THREAD_UPDATED */, {
8507
+ ...threadPayload,
8508
+ newTweet: convertToCoreTweet(tweet)
8509
+ });
8510
+ } else if (thread[0].id === tweet.id) {
8511
+ this.runtime.emitEvent("TWITTER_THREAD_CREATED" /* THREAD_CREATED */, threadPayload);
8512
+ }
8513
+ }
8614
8514
  await this.handleTweet({
8615
8515
  tweet,
8616
8516
  message,
@@ -8619,6 +8519,156 @@ var TwitterInteractionClient = class {
8619
8519
  this.client.lastCheckedTweetId = BigInt(tweet.id);
8620
8520
  }
8621
8521
  }
8522
+ const interactions = await this.client.fetchInteractions();
8523
+ const handleInteraction = async (interaction) => {
8524
+ if (interaction?.targetTweet?.conversationId) {
8525
+ const memory = this.createMemoryObject(
8526
+ interaction.type,
8527
+ `${interaction.id}-${interaction.type}`,
8528
+ interaction.userId,
8529
+ interaction.targetTweet.conversationId
8530
+ );
8531
+ await this.runtime.getMemoryManager("interactions").createMemory(memory);
8532
+ const reactionMessage = {
8533
+ id: createUniqueUuid4(this.runtime, interaction.targetTweetId),
8534
+ content: {
8535
+ text: interaction.targetTweet.text,
8536
+ source: "twitter"
8537
+ },
8538
+ entityId: createUniqueUuid4(this.runtime, interaction.targetTweet.userId),
8539
+ roomId: createUniqueUuid4(this.runtime, interaction.targetTweet.conversationId),
8540
+ agentId: this.runtime.agentId
8541
+ };
8542
+ const basePayload = {
8543
+ runtime: this.runtime,
8544
+ user: {
8545
+ id: interaction.userId,
8546
+ username: interaction.username,
8547
+ name: interaction.name
8548
+ },
8549
+ source: "twitter"
8550
+ };
8551
+ switch (interaction.type) {
8552
+ case "like": {
8553
+ const likePayload = {
8554
+ ...basePayload,
8555
+ tweet: interaction.targetTweet
8556
+ };
8557
+ this.runtime.emitEvent("TWITTER_LIKE_RECEIVED" /* LIKE_RECEIVED */, likePayload);
8558
+ this.runtime.emitEvent(EventTypes2.REACTION_RECEIVED, {
8559
+ ...basePayload,
8560
+ reaction: {
8561
+ type: "like",
8562
+ entityId: createUniqueUuid4(this.runtime, interaction.userId)
8563
+ },
8564
+ message: reactionMessage,
8565
+ callback: async () => {
8566
+ return [];
8567
+ }
8568
+ });
8569
+ break;
8570
+ }
8571
+ case "retweet": {
8572
+ const retweetPayload = {
8573
+ ...basePayload,
8574
+ tweet: interaction.targetTweet,
8575
+ retweetId: interaction.retweetId
8576
+ };
8577
+ this.runtime.emitEvent("TWITTER_RETWEET_RECEIVED" /* RETWEET_RECEIVED */, retweetPayload);
8578
+ this.runtime.emitEvent(EventTypes2.REACTION_RECEIVED, {
8579
+ ...basePayload,
8580
+ reaction: {
8581
+ type: "retweet",
8582
+ entityId: createUniqueUuid4(this.runtime, interaction.userId)
8583
+ },
8584
+ message: reactionMessage,
8585
+ callback: async () => {
8586
+ return [];
8587
+ }
8588
+ });
8589
+ break;
8590
+ }
8591
+ case "quote": {
8592
+ const quotePayload = {
8593
+ ...basePayload,
8594
+ message: reactionMessage,
8595
+ quotedTweet: interaction.targetTweet,
8596
+ quoteTweet: interaction.quoteTweet || interaction.targetTweet,
8597
+ callback: async () => [],
8598
+ reaction: {
8599
+ type: "quote",
8600
+ entityId: createUniqueUuid4(this.runtime, interaction.userId)
8601
+ }
8602
+ };
8603
+ this.runtime.emitEvent("TWITTER_QUOTE_RECEIVED" /* QUOTE_RECEIVED */, quotePayload);
8604
+ this.runtime.emitEvent(EventTypes2.REACTION_RECEIVED, {
8605
+ ...basePayload,
8606
+ reaction: {
8607
+ type: "quote",
8608
+ entityId: createUniqueUuid4(this.runtime, interaction.userId)
8609
+ },
8610
+ message: reactionMessage,
8611
+ callback: async () => {
8612
+ return [];
8613
+ }
8614
+ });
8615
+ break;
8616
+ }
8617
+ }
8618
+ }
8619
+ };
8620
+ const processInteractions = async (interactions2) => {
8621
+ for (const interaction of interactions2) {
8622
+ if (interaction?.targetTweet?.conversationId) {
8623
+ await handleInteraction(interaction);
8624
+ }
8625
+ }
8626
+ };
8627
+ const processFollowerChange = async (change, profileId) => {
8628
+ if (change?.type && change?.userId && profileId) {
8629
+ const followerMemory = this.createMemoryObject(
8630
+ change.type,
8631
+ `${change.type}-${change.userId}`,
8632
+ change.userId,
8633
+ profileId
8634
+ );
8635
+ await this.runtime.getMemoryManager("follower-changes").createMemory(followerMemory);
8636
+ }
8637
+ };
8638
+ const followerChanges = await this.client.fetchFollowerChanges();
8639
+ for (const change of followerChanges) {
8640
+ const changeId = createUniqueUuid4(this.runtime, `${change.type}-${change.userId}`);
8641
+ const existingChange = await this.runtime.getMemoryManager("follower-changes").getMemoryById(changeId);
8642
+ if (existingChange) continue;
8643
+ const entityId = createUniqueUuid4(this.runtime, change.userId);
8644
+ const basePayload = {
8645
+ runtime: this.runtime,
8646
+ user: {
8647
+ id: change.userId,
8648
+ username: change.username,
8649
+ name: change.name
8650
+ },
8651
+ source: "twitter"
8652
+ };
8653
+ if (change.type === "followed") {
8654
+ const userFollowedPayload = {
8655
+ ...basePayload,
8656
+ follower: change.user,
8657
+ entityId: createUniqueUuid4(this.runtime, change.userId),
8658
+ roomId: createUniqueUuid4(this.runtime, this.client.profile.id)
8659
+ };
8660
+ this.runtime.emitEvent("TWITTER_USER_FOLLOWED" /* USER_FOLLOWED */, userFollowedPayload);
8661
+ } else if (change.type === "unfollowed") {
8662
+ const userUnfollowedPayload = {
8663
+ ...basePayload,
8664
+ unfollower: change.user,
8665
+ entityId: createUniqueUuid4(this.runtime, change.userId),
8666
+ roomId: createUniqueUuid4(this.runtime, this.client.profile.id)
8667
+ };
8668
+ this.runtime.emitEvent("TWITTER_USER_UNFOLLOWED" /* USER_UNFOLLOWED */, userUnfollowedPayload);
8669
+ }
8670
+ await processFollowerChange(change, this.client.profile.id);
8671
+ }
8622
8672
  await this.client.cacheLatestCheckedTweetId();
8623
8673
  logger6.log("Finished checking Twitter interactions");
8624
8674
  } catch (error) {
@@ -8676,12 +8726,11 @@ var TwitterInteractionClient = class {
8676
8726
  } catch (error) {
8677
8727
  logger6.error("Error Occured during describing image: ", error);
8678
8728
  }
8679
- let state = await this.runtime.composeState(message);
8729
+ const state = await this.runtime.composeState(message);
8680
8730
  state.values = {
8681
8731
  ...state.values,
8682
8732
  twitterUserName: this.state?.TWITTER_USERNAME || this.runtime.getSetting("TWITTER_USERNAME"),
8683
8733
  currentPost,
8684
- // TODO: move to recentMessages provider
8685
8734
  formattedConversation
8686
8735
  };
8687
8736
  const tweetId = createUniqueUuid4(this.runtime, tweet.id);
@@ -8698,117 +8747,96 @@ var TwitterInteractionClient = class {
8698
8747
  source: "twitter",
8699
8748
  type: ChannelType4.GROUP
8700
8749
  });
8701
- const message2 = {
8750
+ await this.runtime.ensureRoomExists({
8751
+ id: roomId,
8752
+ name: `Conversation with ${tweet.name}`,
8753
+ source: "twitter",
8754
+ type: ChannelType4.GROUP,
8755
+ channelId: tweet.conversationId,
8756
+ worldId: createUniqueUuid4(this.runtime, tweet.userId)
8757
+ });
8758
+ const memory = {
8702
8759
  id: tweetId,
8703
8760
  agentId: this.runtime.agentId,
8704
8761
  content: {
8705
8762
  text: tweet.text,
8706
8763
  url: tweet.permanentUrl,
8707
8764
  imageUrls: tweet.photos?.map((photo) => photo.url) || [],
8708
- inReplyTo: tweet.inReplyToStatusId ? createUniqueUuid4(this.runtime, tweet.inReplyToStatusId) : void 0
8765
+ inReplyTo: tweet.inReplyToStatusId ? createUniqueUuid4(this.runtime, tweet.inReplyToStatusId) : void 0,
8766
+ source: "twitter",
8767
+ channelType: ChannelType4.GROUP
8709
8768
  },
8710
8769
  entityId,
8711
8770
  roomId,
8712
8771
  createdAt: tweet.timestamp * 1e3
8713
8772
  };
8714
- this.client.saveRequestMessage(message2, state);
8773
+ this.client.saveRequestMessage(memory, state);
8715
8774
  }
8716
8775
  const shouldRespondPrompt = composePrompt2({
8717
8776
  state,
8718
- template: this.runtime.character.templates?.twitterShouldRespondTemplate || this.runtime.character?.templates?.shouldRespondTemplate || twitterShouldRespondTemplate
8777
+ template: this.runtime.character.templates?.shouldRespondTemplate || ""
8719
8778
  });
8720
- const shouldRespond = await this.runtime.useModel(ModelTypes4.TEXT_SMALL, {
8779
+ const response = await this.runtime.useModel(ModelTypes4.TEXT_SMALL, {
8721
8780
  prompt: shouldRespondPrompt
8722
8781
  });
8723
- if (!shouldRespond.includes("RESPOND")) {
8724
- logger6.log("Not responding to message");
8725
- return { text: "Response Decision:", action: shouldRespond };
8782
+ const responseActions = (response.match(/(?:RESPOND|IGNORE|STOP)/g) || [
8783
+ "IGNORE"
8784
+ ])[0];
8785
+ if (responseActions !== "RESPOND") {
8786
+ logger6.log(
8787
+ `Not responding to tweet based on shouldRespond decision: ${responseActions}`
8788
+ );
8789
+ return { text: "", actions: [responseActions] };
8726
8790
  }
8727
- const prompt = composePrompt2({
8728
- state: {
8729
- ...state,
8730
- values: {
8731
- ...state.values,
8732
- // Convert actionNames array to string
8733
- actionNames: Array.isArray(state.actionNames) ? state.actionNames.join(", ") : state.actionNames || "",
8734
- actions: Array.isArray(state.actions) ? state.actions.join("\n") : state.actions || "",
8735
- // Ensure character examples are included
8736
- characterPostExamples: this.runtime.character.messageExamples ? this.runtime.character.messageExamples.map(
8737
- (example) => example.map(
8738
- (msg) => `${msg.name}: ${msg.content.text}${msg.content.actions ? ` (Actions: ${msg.content.actions.join(", ")})` : ""}`
8739
- ).join("\n")
8740
- ).join("\n\n") : ""
8791
+ const callback = async (response2, tweetId2) => {
8792
+ try {
8793
+ const tweetToReplyTo = tweetId2 || tweet.id;
8794
+ if (this.isDryRun) {
8795
+ logger6.info(
8796
+ `[DRY RUN] Would have replied to ${tweet.username} with: ${response2.text}`
8797
+ );
8798
+ return [];
8741
8799
  }
8742
- },
8743
- template: this.runtime.character.templates?.twitterMessageHandlerTemplate || this.runtime.character?.templates?.messageHandlerTemplate || twitterMessageHandlerTemplate
8744
- });
8745
- const responseText = await this.runtime.useModel(ModelTypes4.TEXT_LARGE, {
8746
- prompt
8747
- });
8748
- const response = parseJSONObjectFromText(responseText);
8749
- const removeQuotes = (str) => str.replace(/^['"](.*)['"]$/, "$1");
8750
- const replyToId = createUniqueUuid4(this.runtime, tweet.id);
8751
- response.inReplyTo = replyToId;
8752
- response.text = removeQuotes(response.text);
8753
- if (response.text) {
8754
- if (this.isDryRun) {
8755
- logger6.info(
8756
- `Dry run: Selected Post: ${tweet.id} - ${tweet.username}: ${tweet.text}
8757
- Agent's Output:
8758
- ${response.text}`
8800
+ logger6.info(`Replying to tweet ${tweetToReplyTo}`);
8801
+ const replyTweetResult = await this.client.requestQueue.add(
8802
+ () => this.client.twitterClient.post("statuses/update", {
8803
+ status: response2.text.substring(0, 280),
8804
+ in_reply_to_status_id: tweetToReplyTo,
8805
+ auto_populate_reply_metadata: true
8806
+ })
8759
8807
  );
8760
- } else {
8761
- try {
8762
- const callback = async (response2, tweetId2) => {
8763
- const memories = await sendTweet(
8764
- this.client,
8765
- response2,
8766
- message.roomId,
8767
- this.state?.TWITTER_USERNAME || this.runtime.getSetting("TWITTER_USERNAME"),
8768
- tweetId2 || tweet.id
8769
- );
8770
- return memories;
8771
- };
8772
- const responseMessages = [
8773
- {
8774
- id: createUniqueUuid4(this.runtime, tweet.id),
8775
- entityId: this.runtime.agentId,
8776
- agentId: this.runtime.agentId,
8777
- content: response,
8778
- roomId: message.roomId,
8779
- createdAt: Date.now()
8780
- }
8781
- ];
8782
- state = await this.runtime.composeState(message, ["RECENT_MESSAGES"]);
8783
- for (const responseMessage of responseMessages) {
8784
- await this.runtime.getMemoryManager("messages").createMemory(responseMessage);
8785
- }
8786
- const responseTweetId = responseMessages[responseMessages.length - 1]?.content?.tweetId;
8787
- await this.runtime.processActions(
8788
- message,
8789
- responseMessages,
8790
- state,
8791
- (response2) => {
8792
- return callback(response2, responseTweetId);
8793
- }
8794
- );
8795
- const responseInfo = `Context:
8796
-
8797
- ${prompt}
8798
-
8799
- Selected Post: ${tweet.id} - ${tweet.username}: ${tweet.text}
8800
- Agent's Output:
8801
- ${response.text}`;
8802
- await this.runtime.getDatabaseAdapter().setCache(
8803
- `twitter/tweet_generation_${tweet.id}.txt`,
8804
- responseInfo
8805
- );
8806
- await wait();
8807
- } catch (error) {
8808
- logger6.error(`Error sending response tweet: ${error}`);
8808
+ if (!replyTweetResult) {
8809
+ throw new Error("Failed to create tweet response");
8809
8810
  }
8811
+ const responseId = createUniqueUuid4(
8812
+ this.runtime,
8813
+ replyTweetResult.id_str
8814
+ );
8815
+ const responseMemory = {
8816
+ id: responseId,
8817
+ entityId: this.runtime.agentId,
8818
+ agentId: this.runtime.agentId,
8819
+ roomId: message.roomId,
8820
+ content: {
8821
+ ...response2,
8822
+ inReplyTo: message.id
8823
+ },
8824
+ createdAt: Date.now()
8825
+ };
8826
+ await this.runtime.getMemoryManager("messages").createMemory(responseMemory);
8827
+ return [responseMemory];
8828
+ } catch (error) {
8829
+ logger6.error("Error replying to tweet:", error);
8830
+ return [];
8810
8831
  }
8811
- }
8832
+ };
8833
+ this.runtime.emitEvent(EventTypes2.MESSAGE_RECEIVED, {
8834
+ runtime: this.runtime,
8835
+ message,
8836
+ callback,
8837
+ source: "twitter"
8838
+ });
8839
+ return { text: "", actions: ["RESPOND"] };
8812
8840
  }
8813
8841
  /**
8814
8842
  * Build a conversation thread based on a given tweet.
@@ -8898,18 +8926,29 @@ ${response.text}`;
8898
8926
  await processThread.bind(this)(tweet, 0);
8899
8927
  return thread;
8900
8928
  }
8929
+ createMemoryObject(type, id, userId, roomId) {
8930
+ return {
8931
+ id: createUniqueUuid4(this.runtime, id),
8932
+ agentId: this.runtime.agentId,
8933
+ entityId: createUniqueUuid4(this.runtime, userId),
8934
+ roomId: createUniqueUuid4(this.runtime, roomId),
8935
+ content: {
8936
+ type,
8937
+ source: "twitter"
8938
+ },
8939
+ createdAt: Date.now()
8940
+ };
8941
+ }
8901
8942
  };
8902
8943
 
8903
8944
  // src/post.ts
8904
8945
  import {
8905
8946
  ChannelType as ChannelType5,
8906
8947
  ModelTypes as ModelTypes5,
8907
- cleanJsonResponse,
8908
8948
  composePrompt as composePrompt3,
8909
8949
  createUniqueUuid as createUniqueUuid5,
8910
- extractAttributes,
8911
8950
  logger as logger7,
8912
- parseJSONObjectFromText as parseJSONObjectFromText2,
8951
+ parseJSONObjectFromText,
8913
8952
  truncateToCompleteSentence
8914
8953
  } from "@elizaos/core";
8915
8954
 
@@ -8922,11 +8961,6 @@ Your response should not contain any questions. Brief, concise statements only.
8922
8961
 
8923
8962
  // src/post.ts
8924
8963
  var TwitterPostClient = class {
8925
- client;
8926
- runtime;
8927
- twitterUsername;
8928
- isDryRun;
8929
- state;
8930
8964
  /**
8931
8965
  * Constructor for initializing a new Twitter client with the provided client, runtime, and state
8932
8966
  * @param {ClientBase} client - The client used for interacting with Twitter API
@@ -8941,7 +8975,7 @@ var TwitterPostClient = class {
8941
8975
  this.isDryRun = this.state?.TWITTER_DRY_RUN || this.runtime.getSetting("TWITTER_DRY_RUN");
8942
8976
  logger7.log("Twitter Client Configuration:");
8943
8977
  logger7.log(`- Username: ${this.twitterUsername}`);
8944
- logger7.log(`- Dry Run Mode: ${this.isDryRun ? "enabled" : "disabled"}`);
8978
+ logger7.log(`- Dry Run Mode: ${this.isDryRun ? "Enabled" : "Disabled"}`);
8945
8979
  logger7.log(
8946
8980
  `- Disable Post: ${this.state?.TWITTER_ENABLE_POST_GENERATION || this.runtime.getSetting("TWITTER_ENABLE_POST_GENERATION") ? "disabled" : "enabled"}`
8947
8981
  );
@@ -8958,44 +8992,21 @@ var TwitterPostClient = class {
8958
8992
  }
8959
8993
  }
8960
8994
  /**
8961
- * Asynchronously starts the tweet generation loop.
8962
- * If the client's profile is not available, it initializes the client first.
8963
- * It generates a new tweet at random intervals specified by the TWITTER_POST_INTERVAL_MIN and TWITTER_POST_INTERVAL_MAX settings or state properties.
8964
- * Optionally, it can immediately generate a tweet if TWITTER_POST_IMMEDIATELY is set to true in the state or settings.
8995
+ * Starts the Twitter post client, setting up a loop to periodically generate new tweets.
8965
8996
  */
8966
8997
  async start() {
8967
- if (!this.client.profile) {
8968
- await this.client.init();
8998
+ logger7.log("Starting Twitter post client...");
8999
+ const tweetGeneration = this.runtime.getSetting("TWITTER_ENABLE_TWEET_GENERATION");
9000
+ if (tweetGeneration === false) {
9001
+ logger7.log("Tweet generation is disabled");
9002
+ return;
8969
9003
  }
8970
9004
  const generateNewTweetLoop = async () => {
8971
- let lastPost = await this.runtime.getDatabaseAdapter().getCache(`twitter/${this.twitterUsername}/lastPost`);
8972
- if (!lastPost) {
8973
- lastPost = JSON.stringify({
8974
- timestamp: 0
8975
- });
8976
- }
8977
- const lastPostTimestamp = lastPost.timestamp ?? 0;
8978
- const minMinutes = (this.state?.TWITTER_POST_INTERVAL_MIN || this.runtime.getSetting("TWITTER_POST_INTERVAL_MIN")) ?? 90;
8979
- const maxMinutes = (this.state?.TWITTER_POST_INTERVAL_MAX || this.runtime.getSetting("TWITTER_POST_INTERVAL_MAX")) ?? 180;
8980
- const randomMinutes = Math.floor(Math.random() * (maxMinutes - minMinutes + 1)) + minMinutes;
8981
- const delay = randomMinutes * 60 * 1e3;
8982
- if (Date.now() > lastPostTimestamp + delay) {
8983
- await this.generateNewTweet();
8984
- }
8985
- setTimeout(() => {
8986
- generateNewTweetLoop();
8987
- }, delay);
8988
- logger7.log(`Next tweet scheduled in ${randomMinutes} minutes`);
9005
+ const interval = (this.state?.TWITTER_POST_INTERVAL || this.runtime.getSetting("TWITTER_POST_INTERVAL") || 30) * 60 * 1e3;
9006
+ this.generateNewTweet();
9007
+ setTimeout(generateNewTweetLoop, interval);
8989
9008
  };
8990
- if (this.state?.TWITTER_ENABLE_POST_GENERATION || this.runtime.getSetting("TWITTER_ENABLE_POST_GENERATION")) {
8991
- if (this.state?.TWITTER_POST_IMMEDIATELY || this.runtime.getSetting("TWITTER_POST_IMMEDIATELY")) {
8992
- setTimeout(() => {
8993
- this.generateNewTweet();
8994
- }, 5e3);
8995
- }
8996
- generateNewTweetLoop();
8997
- logger7.log("Tweet generation loop started");
8998
- }
9009
+ setTimeout(generateNewTweetLoop, 60 * 1e3);
8999
9010
  }
9000
9011
  /**
9001
9012
  * Creates a Tweet object based on the tweet result, client information, and Twitter username.
@@ -9035,7 +9046,7 @@ var TwitterPostClient = class {
9035
9046
  * @param {string} rawTweetContent - The raw content of the tweet.
9036
9047
  */
9037
9048
  async processAndCacheTweet(runtime, client, tweet, roomId, rawTweetContent) {
9038
- await runtime.getDatabaseAdapter().setCache(`twitter/${client.profile.username}/lastPost`, {
9049
+ await runtime.setCache(`twitter/${client.profile.username}/lastPost`, {
9039
9050
  id: tweet.id,
9040
9051
  timestamp: Date.now()
9041
9052
  });
@@ -9156,95 +9167,169 @@ var TwitterPostClient = class {
9156
9167
  }
9157
9168
  }
9158
9169
  /**
9159
- * Generates and posts a new tweet. If isDryRun is true, only logs what would have been posted.
9160
- */
9161
- /**
9162
- * Asynchronously generates a new tweet for the Twitter account associated with the agent.
9163
- * This method retrieves random topics of interest from the character's topics list and prompts the user to compose a tweet based on those topics.
9164
- * The tweet is then processed and posted on Twitter with optional media attachments.
9165
- * @returns {void}
9170
+ * Handles the creation and posting of a tweet by emitting standardized events.
9171
+ * This approach aligns with our platform-independent architecture.
9166
9172
  */
9167
9173
  async generateNewTweet() {
9168
- logger7.log("Generating new tweet");
9169
9174
  try {
9170
- const roomId = createUniqueUuid5(this.runtime, "twitter_generate");
9171
- const topics = this.runtime.character.topics.sort(() => 0.5 - Math.random()).slice(0, 10).join(", ");
9172
- const state = await this.runtime.composeState({
9173
- entityId: this.runtime.agentId,
9174
- roomId,
9175
- agentId: this.runtime.agentId,
9176
- content: {
9177
- text: topics || "",
9178
- actions: ["TWEET"]
9179
- }
9180
- });
9181
- state.values = {
9182
- ...state.values,
9183
- twitterUserName: this.client.profile.username
9184
- };
9185
- const prompt = composePrompt3({
9175
+ logger7.log("Generating new tweet...");
9176
+ const userId = this.client.profile?.id;
9177
+ if (!userId) {
9178
+ logger7.error("Cannot generate tweet: Twitter profile not available");
9179
+ return;
9180
+ }
9181
+ const worldId = createUniqueUuid5(this.runtime, userId);
9182
+ const timelineRoomId = createUniqueUuid5(this.runtime, `${userId}-home`);
9183
+ const state = await this.runtime.composeState(null, [
9184
+ "CHARACTER",
9185
+ "RECENT_MESSAGES",
9186
+ "TIME"
9187
+ ]);
9188
+ const tweetPrompt = composePrompt3({
9186
9189
  state,
9187
9190
  template: this.runtime.character.templates?.twitterPostTemplate || twitterPostTemplate
9188
9191
  });
9189
- logger7.debug(`generate post prompt:
9190
- ${prompt}`);
9191
- const response = await this.runtime.useModel(ModelTypes5.TEXT_SMALL, {
9192
- prompt
9192
+ const response = await this.runtime.useModel(ModelTypes5.TEXT_LARGE, {
9193
+ prompt: tweetPrompt
9193
9194
  });
9194
- const rawTweetContent = cleanJsonResponse(response);
9195
- let tweetTextForPosting = null;
9196
- let mediaData = null;
9197
- const parsedResponse = parseJSONObjectFromText2(rawTweetContent);
9198
- if (parsedResponse?.text) {
9199
- tweetTextForPosting = parsedResponse.text;
9200
- } else {
9201
- tweetTextForPosting = rawTweetContent.trim();
9195
+ const jsonResponse = parseJSONObjectFromText(response);
9196
+ if (!jsonResponse || !jsonResponse.text) {
9197
+ logger7.error("Failed to generate valid tweet content");
9198
+ return;
9202
9199
  }
9203
- if (parsedResponse?.attachments && parsedResponse?.attachments.length > 0) {
9204
- mediaData = await fetchMediaData(parsedResponse.attachments);
9200
+ const cleanedText = this.cleanupTweetText(jsonResponse.text);
9201
+ const mediaData = [];
9202
+ if (jsonResponse.imagePrompt) {
9203
+ try {
9204
+ const imagePromptMedia = Array.isArray(jsonResponse.imagePrompt) ? jsonResponse.imagePrompt.map((prompt) => ({
9205
+ url: prompt,
9206
+ contentType: "image/png"
9207
+ })) : [{
9208
+ url: jsonResponse.imagePrompt,
9209
+ contentType: "image/png"
9210
+ }];
9211
+ const fetchedMedia = await fetchMediaData(imagePromptMedia);
9212
+ mediaData.push(...fetchedMedia);
9213
+ } catch (error) {
9214
+ logger7.error("Error fetching media for tweet:", error);
9215
+ }
9205
9216
  }
9206
- if (!tweetTextForPosting) {
9207
- const parsingText = extractAttributes(rawTweetContent, ["text"]).text;
9208
- if (parsingText) {
9209
- tweetTextForPosting = truncateToCompleteSentence(
9210
- extractAttributes(rawTweetContent, ["text"]).text,
9211
- 280 - 1
9212
- );
9217
+ const tweetId = createUniqueUuid5(this.runtime, `tweet-${Date.now()}`);
9218
+ const memory = {
9219
+ id: tweetId,
9220
+ entityId: this.runtime.agentId,
9221
+ agentId: this.runtime.agentId,
9222
+ roomId: timelineRoomId,
9223
+ content: {
9224
+ text: cleanedText,
9225
+ source: "twitter",
9226
+ channelType: ChannelType5.FEED,
9227
+ thought: jsonResponse.thought || "",
9228
+ plan: jsonResponse.plan || "",
9229
+ type: "post"
9230
+ },
9231
+ createdAt: Date.now()
9232
+ };
9233
+ const callback = async (content) => {
9234
+ try {
9235
+ if (this.isDryRun) {
9236
+ logger7.info(`[DRY RUN] Would post tweet: ${content.text}`);
9237
+ return [];
9238
+ }
9239
+ const result = await this.postToTwitter(content.text, mediaData);
9240
+ if (result) {
9241
+ const postedTweetId = createUniqueUuid5(
9242
+ this.runtime,
9243
+ result.id_str
9244
+ );
9245
+ const postedMemory = {
9246
+ id: postedTweetId,
9247
+ entityId: this.runtime.agentId,
9248
+ agentId: this.runtime.agentId,
9249
+ roomId: timelineRoomId,
9250
+ content: {
9251
+ ...content,
9252
+ source: "twitter",
9253
+ channelType: ChannelType5.FEED,
9254
+ type: "post",
9255
+ metadata: {
9256
+ tweetId: result.id_str,
9257
+ postedAt: Date.now()
9258
+ }
9259
+ },
9260
+ createdAt: Date.now()
9261
+ };
9262
+ await this.runtime.getMemoryManager("messages").createMemory(postedMemory);
9263
+ return [postedMemory];
9264
+ }
9265
+ return [];
9266
+ } catch (error) {
9267
+ logger7.error("Error posting tweet:", error);
9268
+ return [];
9269
+ }
9270
+ };
9271
+ this.runtime.emitEvent(["TWITTER_POST_GENERATED", "POST_GENERATED"], {
9272
+ runtime: this.runtime,
9273
+ message: memory,
9274
+ callback,
9275
+ source: "twitter"
9276
+ });
9277
+ } catch (error) {
9278
+ logger7.error("Error generating tweet:", error);
9279
+ }
9280
+ }
9281
+ /**
9282
+ * Posts content to Twitter
9283
+ * @param {string} text The tweet text to post
9284
+ * @param {MediaData[]} mediaData Optional media to attach to the tweet
9285
+ * @returns {Promise<any>} The result from the Twitter API
9286
+ */
9287
+ async postToTwitter(text, mediaData = []) {
9288
+ try {
9289
+ const mediaIds = [];
9290
+ if (mediaData && mediaData.length > 0) {
9291
+ for (const media of mediaData) {
9292
+ try {
9293
+ const uploadResult = await this.client.requestQueue.add(
9294
+ () => this.client.twitterClient.post("media/upload", {
9295
+ media_data: Buffer.isBuffer(media.data) ? media.data : Buffer.from(String(media.data).split(",")[1], "base64")
9296
+ })
9297
+ );
9298
+ if (uploadResult && uploadResult.media_id_string) {
9299
+ mediaIds.push(uploadResult.media_id_string);
9300
+ }
9301
+ } catch (error) {
9302
+ logger7.error("Error uploading media:", error);
9303
+ }
9213
9304
  }
9214
9305
  }
9215
- if (!tweetTextForPosting) {
9216
- tweetTextForPosting = rawTweetContent;
9306
+ const tweetParams = {
9307
+ status: text.substring(0, 280)
9308
+ // Twitter's character limit
9309
+ };
9310
+ if (mediaIds.length > 0) {
9311
+ tweetParams.media_ids = mediaIds.join(",");
9217
9312
  }
9218
- tweetTextForPosting = truncateToCompleteSentence(
9219
- tweetTextForPosting,
9220
- 280 - 1
9313
+ const result = await this.client.requestQueue.add(
9314
+ () => this.client.twitterClient.post("statuses/update", tweetParams)
9221
9315
  );
9222
- const removeQuotes = (str) => str.replace(/^['"](.*)['"]$/, "$1");
9223
- const fixNewLines = (str) => str.replaceAll(/\\n/g, "\n\n");
9224
- tweetTextForPosting = removeQuotes(fixNewLines(tweetTextForPosting));
9225
- if (this.isDryRun) {
9226
- logger7.info(`Dry run: would have posted tweet: ${tweetTextForPosting}`);
9227
- return;
9228
- }
9229
- try {
9230
- logger7.log(`Posting new tweet:
9231
- ${tweetTextForPosting}`);
9232
- this.postTweet(
9233
- this.runtime,
9234
- this.client,
9235
- tweetTextForPosting,
9236
- roomId,
9237
- rawTweetContent,
9238
- this.twitterUsername,
9239
- mediaData
9240
- );
9241
- } catch (error) {
9242
- logger7.error("Error sending tweet:", error);
9243
- }
9316
+ return result;
9244
9317
  } catch (error) {
9245
- logger7.error("Error generating new tweet:", error);
9318
+ logger7.error("Error posting to Twitter:", error);
9319
+ throw error;
9246
9320
  }
9247
9321
  }
9322
+ /**
9323
+ * Cleans up a tweet text by removing quotes and fixing newlines
9324
+ */
9325
+ cleanupTweetText(text) {
9326
+ let cleanedText = text.replace(/^['"](.*)['"]$/, "$1");
9327
+ cleanedText = cleanedText.replaceAll(/\\n/g, "\n\n");
9328
+ if (cleanedText.length > 280) {
9329
+ cleanedText = truncateToCompleteSentence(cleanedText, 280);
9330
+ }
9331
+ return cleanedText;
9332
+ }
9248
9333
  async stop() {
9249
9334
  }
9250
9335
  };
@@ -9256,13 +9341,6 @@ import {
9256
9341
  logger as logger8,
9257
9342
  stringToUuid as stringToUuid2
9258
9343
  } from "@elizaos/core";
9259
-
9260
- // src/types.ts
9261
- var ServiceTypes2 = {
9262
- TWITTER: "twitter"
9263
- };
9264
-
9265
- // src/tests.ts
9266
9344
  var TEST_IMAGE_URL = "https://github.com/elizaOS/awesome-eliza/blob/main/assets/eliza-logo.jpg?raw=true";
9267
9345
  var TEST_IMAGE = {
9268
9346
  id: "mock-image-id",
@@ -9275,14 +9353,13 @@ var TEST_IMAGE = {
9275
9353
  alt_text: "mock image"
9276
9354
  };
9277
9355
  var TwitterTestSuite = class {
9278
- name = "twitter";
9279
- twitterClient = null;
9280
- tests;
9281
9356
  /**
9282
9357
  * Constructor for TestSuite class.
9283
9358
  * Initializes an array of test functions to be executed.
9284
9359
  */
9285
9360
  constructor() {
9361
+ this.name = "twitter";
9362
+ this.twitterClient = null;
9286
9363
  this.tests = [
9287
9364
  {
9288
9365
  name: "Initialize Twitter Client",
@@ -9553,11 +9630,6 @@ var TwitterTestSuite = class {
9553
9630
 
9554
9631
  // src/index.ts
9555
9632
  var TwitterClientInstance = class {
9556
- client;
9557
- post;
9558
- interaction;
9559
- space;
9560
- service;
9561
9633
  constructor(runtime, state) {
9562
9634
  this.client = new ClientBase(runtime, state);
9563
9635
  this.post = new TwitterPostClient(this.client, runtime, state);
@@ -9572,11 +9644,12 @@ var TwitterClientInstance = class {
9572
9644
  this.service = TwitterService.getInstance();
9573
9645
  }
9574
9646
  };
9575
- var TwitterService = class _TwitterService extends Service {
9576
- static serviceType = TWITTER_SERVICE_NAME;
9577
- capabilityDescription = "The agent is able to send and receive messages on twitter";
9578
- static instance;
9579
- clients = /* @__PURE__ */ new Map();
9647
+ var _TwitterService = class _TwitterService extends Service {
9648
+ constructor() {
9649
+ super(...arguments);
9650
+ this.capabilityDescription = "The agent is able to send and receive messages on twitter";
9651
+ this.clients = /* @__PURE__ */ new Map();
9652
+ }
9580
9653
  static getInstance() {
9581
9654
  if (!_TwitterService.instance) {
9582
9655
  _TwitterService.instance = new _TwitterService();
@@ -9606,6 +9679,7 @@ var TwitterService = class _TwitterService extends Service {
9606
9679
  client.interaction.start();
9607
9680
  }
9608
9681
  this.clients.set(this.getClientKey(clientId, runtime.agentId), client);
9682
+ await this.emitServerJoinedEvent(runtime, client);
9609
9683
  logger9.info(`Created Twitter client for ${clientId}`);
9610
9684
  return client;
9611
9685
  } catch (error) {
@@ -9613,6 +9687,83 @@ var TwitterService = class _TwitterService extends Service {
9613
9687
  throw error;
9614
9688
  }
9615
9689
  }
9690
+ /**
9691
+ * Emits a standardized WORLD_JOINED event for Twitter
9692
+ * @param runtime The agent runtime
9693
+ * @param client The Twitter client instance
9694
+ */
9695
+ async emitServerJoinedEvent(runtime, client) {
9696
+ try {
9697
+ if (!client.client.profile) {
9698
+ logger9.warn("Twitter profile not available yet, can't emit WORLD_JOINED event");
9699
+ return;
9700
+ }
9701
+ const profile = client.client.profile;
9702
+ const twitterId = profile.id;
9703
+ const username = profile.username;
9704
+ const worldId = createUniqueUuid7(runtime, twitterId);
9705
+ const world = {
9706
+ id: worldId,
9707
+ name: `${username}'s Twitter`,
9708
+ agentId: runtime.agentId,
9709
+ serverId: twitterId,
9710
+ metadata: {
9711
+ ownership: { ownerId: twitterId },
9712
+ roles: {
9713
+ [twitterId]: Role.OWNER
9714
+ },
9715
+ twitter: {
9716
+ username,
9717
+ id: twitterId
9718
+ }
9719
+ }
9720
+ };
9721
+ const homeTimelineRoomId = createUniqueUuid7(runtime, `${twitterId}-home`);
9722
+ const homeTimelineRoom = {
9723
+ id: homeTimelineRoomId,
9724
+ name: `${username}'s Timeline`,
9725
+ source: "twitter",
9726
+ type: ChannelType6.FEED,
9727
+ channelId: `${twitterId}-home`,
9728
+ serverId: twitterId,
9729
+ worldId
9730
+ };
9731
+ const mentionsRoomId = createUniqueUuid7(runtime, `${twitterId}-mentions`);
9732
+ const mentionsRoom = {
9733
+ id: mentionsRoomId,
9734
+ name: `${username}'s Mentions`,
9735
+ source: "twitter",
9736
+ type: ChannelType6.GROUP,
9737
+ channelId: `${twitterId}-mentions`,
9738
+ serverId: twitterId,
9739
+ worldId
9740
+ };
9741
+ const twitterUserId = createUniqueUuid7(runtime, twitterId);
9742
+ const twitterUser = {
9743
+ id: twitterUserId,
9744
+ names: [profile.screenName || username],
9745
+ agentId: runtime.agentId,
9746
+ metadata: {
9747
+ twitter: {
9748
+ id: twitterId,
9749
+ username,
9750
+ screenName: profile.screenName || username,
9751
+ name: profile.screenName || username
9752
+ }
9753
+ }
9754
+ };
9755
+ runtime.emitEvent(["TWITTER_WORLD_JOINED" /* WORLD_JOINED */, EventTypes3.WORLD_JOINED], {
9756
+ runtime,
9757
+ world,
9758
+ rooms: [homeTimelineRoom, mentionsRoom],
9759
+ users: [twitterUser],
9760
+ source: "twitter"
9761
+ });
9762
+ logger9.info(`Emitted WORLD_JOINED event for Twitter account ${username}`);
9763
+ } catch (error) {
9764
+ logger9.error("Failed to emit WORLD_JOINED event for Twitter:", error);
9765
+ }
9766
+ }
9616
9767
  getClient(clientId, agentId) {
9617
9768
  return this.clients.get(this.getClientKey(clientId, agentId));
9618
9769
  }
@@ -9672,6 +9823,8 @@ var TwitterService = class _TwitterService extends Service {
9672
9823
  return `${clientId}-${agentId}`;
9673
9824
  }
9674
9825
  };
9826
+ _TwitterService.serviceType = TWITTER_SERVICE_NAME;
9827
+ var TwitterService = _TwitterService;
9675
9828
  var twitterPlugin = {
9676
9829
  name: TWITTER_SERVICE_NAME,
9677
9830
  description: "Twitter client with per-server instance management",