@elizaos/plugin-twitter 1.2.11 → 1.2.13

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
@@ -1948,7 +1948,7 @@ import {
1948
1948
  } from "@elizaos/core";
1949
1949
 
1950
1950
  // src/constants.ts
1951
- var TWEET_MAX_LENGTH = 4e3;
1951
+ var TWEET_MAX_LENGTH = 280;
1952
1952
 
1953
1953
  // src/utils/error-handler.ts
1954
1954
  import { logger } from "@elizaos/core";
@@ -6894,20 +6894,21 @@ Response (YES/NO):`;
6894
6894
 
6895
6895
  // src/post.ts
6896
6896
  import {
6897
- ChannelType as ChannelType2,
6898
6897
  createUniqueUuid as createUniqueUuid3,
6899
6898
  logger as logger4,
6900
6899
  ModelType as ModelType2
6901
6900
  } from "@elizaos/core";
6902
6901
  var TwitterPostClient = class {
6902
+ // Add lock to prevent concurrent posting
6903
6903
  /**
6904
- * Constructor for initializing a new Twitter client with the provided client, runtime, and state
6905
- * @param {ClientBase} client - The client used for interacting with Twitter API
6906
- * @param {IAgentRuntime} runtime - The runtime environment for the agent
6904
+ * Creates an instance of TwitterPostClient.
6905
+ * @param {ClientBase} client - The client instance.
6906
+ * @param {IAgentRuntime} runtime - The runtime instance.
6907
6907
  * @param {any} state - The state object containing configuration settings
6908
6908
  */
6909
6909
  constructor(client, runtime, state) {
6910
6910
  this.isRunning = false;
6911
+ this.isPosting = false;
6911
6912
  this.client = client;
6912
6913
  this.state = state;
6913
6914
  this.runtime = runtime;
@@ -6943,9 +6944,14 @@ var TwitterPostClient = class {
6943
6944
  );
6944
6945
  const interval = postIntervalMinutes * 60 * 1e3;
6945
6946
  logger4.info(`Next tweet scheduled in ${postIntervalMinutes} minutes`);
6947
+ await new Promise((resolve) => setTimeout(resolve, interval));
6948
+ if (!this.isRunning) {
6949
+ logger4.log("Twitter post client stopped during wait, exiting loop");
6950
+ return;
6951
+ }
6946
6952
  await this.generateNewTweet();
6947
6953
  if (this.isRunning) {
6948
- setTimeout(generateNewTweetLoop, interval);
6954
+ generateNewTweetLoop();
6949
6955
  }
6950
6956
  };
6951
6957
  await new Promise((resolve) => setTimeout(resolve, 1e3));
@@ -6964,6 +6970,11 @@ var TwitterPostClient = class {
6964
6970
  */
6965
6971
  async generateNewTweet() {
6966
6972
  logger4.info("Attempting to generate new tweet...");
6973
+ if (this.isPosting) {
6974
+ logger4.info("Already posting a tweet, skipping concurrent attempt");
6975
+ return;
6976
+ }
6977
+ this.isPosting = true;
6967
6978
  try {
6968
6979
  const userId = this.client.profile?.id;
6969
6980
  if (!userId) {
@@ -6981,28 +6992,50 @@ var TwitterPostClient = class {
6981
6992
  roomId,
6982
6993
  content: { text: "", type: "post" },
6983
6994
  createdAt: Date.now()
6995
+ }).catch((error) => {
6996
+ logger4.warn("Error composing state, using minimal state:", error);
6997
+ return {
6998
+ agentId: this.runtime.agentId,
6999
+ recentMemories: [],
7000
+ values: {}
7001
+ };
6984
7002
  });
6985
7003
  const tweetPrompt = `You are ${this.runtime.character.name}.
6986
7004
  ${this.runtime.character.bio}
6987
7005
 
6988
- Generate a tweet that:
6989
- - Reflects your personality and interests
6990
- - Is engaging and authentic
6991
- - Is between 50-280 characters
6992
- - Does NOT include hashtags unless they're essential to the message
6993
- - Does NOT include @mentions unless replying to someone
7006
+ CRITICAL: Generate a tweet that sounds like YOU, not a generic motivational poster or LinkedIn influencer.
7007
+
7008
+ ${this.runtime.character.messageExamples && this.runtime.character.messageExamples.length > 0 ? `
7009
+ Example tweets that capture your voice:
7010
+ ${this.runtime.character.messageExamples.map(
7011
+ (example) => Array.isArray(example) ? example[1]?.content?.text || "" : example
7012
+ ).filter(Boolean).slice(0, 5).join("\n")}
7013
+ ` : ""}
7014
+
7015
+ Style guidelines:
7016
+ - Be authentic, opinionated, and specific - no generic platitudes
7017
+ - Use your unique voice and perspective
7018
+ - Share hot takes, unpopular opinions, or specific insights
7019
+ - Be conversational, not preachy
7020
+ - If you use emojis, use them sparingly and purposefully
7021
+ - Length: 50-280 characters (keep it punchy)
7022
+ - NO hashtags unless absolutely essential
7023
+ - NO generic motivational content
6994
7024
 
6995
- Your topics of interest: ${this.runtime.character.topics?.join(", ") || "general topics"}
7025
+ Your interests: ${this.runtime.character.topics?.join(", ") || "technology, crypto, AI"}
7026
+
7027
+ ${this.runtime.character.style ? `Your style: ${typeof this.runtime.character.style === "object" ? this.runtime.character.style.all?.join(", ") || JSON.stringify(this.runtime.character.style) : this.runtime.character.style}` : ""}
6996
7028
 
6997
7029
  Recent context:
6998
- ${state.recentMemories?.slice(0, 5).map((m) => m.content.text).join("\n") || "No recent context"}
7030
+ ${state.recentMemories?.slice(0, 3).map((m) => m.content.text).join("\n") || "No recent context"}
6999
7031
 
7000
- Generate a single tweet:`;
7032
+ Generate a single tweet that sounds like YOU would actually write it:`;
7001
7033
  const generatedContent = await this.runtime.useModel(
7002
7034
  ModelType2.TEXT_SMALL,
7003
7035
  {
7004
7036
  prompt: tweetPrompt,
7005
- temperature: 0.8,
7037
+ temperature: 0.9,
7038
+ // Increased for more creativity
7006
7039
  maxTokens: 100
7007
7040
  }
7008
7041
  );
@@ -7015,6 +7048,33 @@ Generate a single tweet:`;
7015
7048
  logger4.error("Error in generated content:", tweetText);
7016
7049
  return;
7017
7050
  }
7051
+ if (tweetText.length > 280) {
7052
+ logger4.warn(`Generated tweet too long (${tweetText.length} chars), truncating...`);
7053
+ const sentences = tweetText.match(/[^.!?]+[.!?]+/g) || [tweetText];
7054
+ let truncated = "";
7055
+ for (const sentence of sentences) {
7056
+ if ((truncated + sentence).length <= 280) {
7057
+ truncated += sentence;
7058
+ } else {
7059
+ break;
7060
+ }
7061
+ }
7062
+ const finalTweet = truncated.trim() || tweetText.substring(0, 277) + "...";
7063
+ logger4.info(`Truncated tweet: ${finalTweet}`);
7064
+ if (this.isDryRun) {
7065
+ logger4.info(`[DRY RUN] Would post tweet: ${finalTweet}`);
7066
+ return;
7067
+ }
7068
+ const result2 = await this.postToTwitter(finalTweet, []);
7069
+ if (result2 === null) {
7070
+ logger4.info("Skipped posting duplicate tweet");
7071
+ return;
7072
+ }
7073
+ const tweetId2 = result2.id;
7074
+ logger4.info(`Tweet posted successfully! ID: ${tweetId2}`);
7075
+ logger4.info("Tweet posted successfully (memory saving disabled due to room constraints)");
7076
+ return;
7077
+ }
7018
7078
  logger4.info(`Generated tweet: ${tweetText}`);
7019
7079
  if (this.isDryRun) {
7020
7080
  logger4.info(`[DRY RUN] Would post tweet: ${tweetText}`);
@@ -7029,28 +7089,12 @@ Generate a single tweet:`;
7029
7089
  logger4.info(`Tweet posted successfully! ID: ${tweetId}`);
7030
7090
  if (result) {
7031
7091
  const postedTweetId = createUniqueUuid3(this.runtime, tweetId);
7032
- const postedMemory = {
7033
- id: postedTweetId,
7034
- entityId: this.runtime.agentId,
7035
- agentId: this.runtime.agentId,
7036
- roomId,
7037
- content: {
7038
- text: tweetText,
7039
- source: "twitter",
7040
- channelType: ChannelType2.FEED,
7041
- type: "post",
7042
- metadata: {
7043
- tweetId,
7044
- postedAt: Date.now()
7045
- }
7046
- },
7047
- createdAt: Date.now()
7048
- };
7049
- await this.runtime.createMemory(postedMemory, "messages");
7050
- logger4.info("Tweet posted and saved to memory successfully");
7092
+ logger4.info("Tweet posted successfully (memory saving temporarily disabled)");
7051
7093
  }
7052
7094
  } catch (error) {
7053
7095
  logger4.error("Error generating tweet:", error);
7096
+ } finally {
7097
+ this.isPosting = false;
7054
7098
  }
7055
7099
  }
7056
7100
  /**
@@ -8824,9 +8868,19 @@ var postTweetAction = {
8824
8868
  ],
8825
8869
  validate: async (runtime, message) => {
8826
8870
  logger9.debug("Validating POST_TWEET action");
8871
+ logger9.debug("Message details:", {
8872
+ hasContent: !!message.content,
8873
+ contentType: message.content?.type,
8874
+ hasText: !!message.content?.text,
8875
+ textLength: message.content?.text?.length || 0,
8876
+ source: message.content?.source,
8877
+ roomId: message.roomId,
8878
+ entityId: message.entityId
8879
+ });
8827
8880
  const text = message.content?.text?.trim();
8828
8881
  if (!text || text.length === 0) {
8829
8882
  logger9.error("No text content for tweet");
8883
+ logger9.debug("Stack trace:", new Error().stack);
8830
8884
  return false;
8831
8885
  }
8832
8886
  if (text.length > 280) {
@@ -8842,34 +8896,70 @@ var postTweetAction = {
8842
8896
  if (!client.twitterClient) {
8843
8897
  await client.init();
8844
8898
  }
8899
+ let text = message.content?.text?.trim();
8900
+ if (!text) {
8901
+ logger9.error("No text content for tweet");
8902
+ if (callback) {
8903
+ callback({
8904
+ text: "I need something to tweet! Please provide the text.",
8905
+ action: "POST_TWEET"
8906
+ });
8907
+ }
8908
+ return;
8909
+ }
8910
+ if (text.length > 280) {
8911
+ logger9.info(`Truncating tweet from ${text.length} to 280 characters`);
8912
+ const sentences = text.match(/[^.!?]+[.!?]+/g) || [text];
8913
+ let truncated = "";
8914
+ for (const sentence of sentences) {
8915
+ if ((truncated + sentence).length <= 280) {
8916
+ truncated += sentence;
8917
+ } else {
8918
+ break;
8919
+ }
8920
+ }
8921
+ text = truncated.trim() || text.substring(0, 277) + "...";
8922
+ logger9.info(`Truncated tweet: ${text}`);
8923
+ }
8845
8924
  if (!client.profile) {
8846
8925
  throw new Error(
8847
8926
  "Twitter client not properly initialized - no profile found"
8848
8927
  );
8849
8928
  }
8850
- const tweetText = message.content?.text?.trim() || "";
8851
- let finalTweetText = tweetText;
8852
- if (tweetText.length < 50 || tweetText.toLowerCase().includes("post") || tweetText.toLowerCase().includes("tweet")) {
8853
- const tweetPrompt = `You are ${runtime.character.name}. Create an interesting tweet based on this context:
8929
+ let finalTweetText = text;
8930
+ if (text.length < 50 || text.toLowerCase().includes("post") || text.toLowerCase().includes("tweet")) {
8931
+ const tweetPrompt = `You are ${runtime.character.name}.
8932
+ ${runtime.character.bio || ""}
8854
8933
 
8855
- Context: ${tweetText}
8934
+ CRITICAL: Generate a tweet based on the context that sounds like YOU, not generic corporate speak.
8856
8935
 
8857
- Your interests: ${runtime.character.topics?.join(", ") || "technology, AI, web3"}
8858
- Your style: ${runtime.character.style?.all?.join(", ") || "thoughtful, engaging"}
8936
+ Context: ${text}
8859
8937
 
8860
- Generate a tweet that:
8861
- - Is under 280 characters
8862
- - Reflects your personality and interests
8863
- - Is engaging and conversational
8864
- - Doesn't use hashtags unless truly relevant
8865
- - Doesn't ask questions at the end
8866
- - Is not generic or promotional
8938
+ ${runtime.character.messageExamples && runtime.character.messageExamples.length > 0 ? `
8939
+ Your voice examples:
8940
+ ${runtime.character.messageExamples.map(
8941
+ (example) => Array.isArray(example) ? example[1]?.content?.text || "" : example
8942
+ ).filter(Boolean).slice(0, 3).join("\n")}
8943
+ ` : ""}
8944
+
8945
+ Style rules:
8946
+ - Be specific, opinionated, authentic
8947
+ - No generic motivational content or platitudes
8948
+ - Share actual insights, hot takes, or unique perspectives
8949
+ - Keep it conversational and punchy
8950
+ - Under 280 characters
8951
+ - Skip hashtags unless essential
8952
+ - Don't end with generic questions
8953
+
8954
+ Your interests: ${runtime.character.topics?.join(", ") || "technology, AI, web3"}
8955
+ ${runtime.character.style ? `Your style: ${typeof runtime.character.style === "object" ? runtime.character.style.all?.join(", ") || "" : runtime.character.style}` : ""}
8867
8956
 
8868
8957
  Tweet:`;
8869
8958
  const response = await runtime.useModel(ModelType5.TEXT_SMALL, {
8870
8959
  prompt: tweetPrompt,
8871
8960
  max_tokens: 100,
8872
- temperature: 0.8
8961
+ temperature: 0.9
8962
+ // Higher for more creativity
8873
8963
  });
8874
8964
  finalTweetText = response.trim();
8875
8965
  }
@@ -8910,7 +9000,7 @@ View it here: ${tweetUrl}`,
8910
9000
  }
8911
9001
  });
8912
9002
  }
8913
- return true;
9003
+ return;
8914
9004
  } else {
8915
9005
  throw new Error("Failed to post tweet - no response data");
8916
9006
  }
@@ -8922,7 +9012,7 @@ View it here: ${tweetUrl}`,
8922
9012
  metadata: { error: error.message }
8923
9013
  });
8924
9014
  }
8925
- return false;
9015
+ return;
8926
9016
  }
8927
9017
  },
8928
9018
  examples: [