@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 +142 -52
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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 =
|
|
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
|
-
*
|
|
6905
|
-
* @param {ClientBase} client - The client
|
|
6906
|
-
* @param {IAgentRuntime} runtime - The runtime
|
|
6904
|
+
* Creates an instance of TwitterPostClient.
|
|
6905
|
+
* @param {ClientBase} client - The client instance.
|
|
6906
|
+
* @param {IAgentRuntime} runtime - The runtime instance.
|
|
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
|
-
|
|
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
|
-
|
|
6990
|
-
|
|
6991
|
-
|
|
6992
|
-
|
|
6993
|
-
|
|
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
|
|
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,
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
8851
|
-
|
|
8852
|
-
|
|
8853
|
-
|
|
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
|
-
|
|
8934
|
+
CRITICAL: Generate a tweet based on the context that sounds like YOU, not generic corporate speak.
|
|
8856
8935
|
|
|
8857
|
-
|
|
8858
|
-
Your style: ${runtime.character.style?.all?.join(", ") || "thoughtful, engaging"}
|
|
8936
|
+
Context: ${text}
|
|
8859
8937
|
|
|
8860
|
-
|
|
8861
|
-
|
|
8862
|
-
|
|
8863
|
-
|
|
8864
|
-
|
|
8865
|
-
|
|
8866
|
-
|
|
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.
|
|
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
|
|
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
|
|
9015
|
+
return;
|
|
8926
9016
|
}
|
|
8927
9017
|
},
|
|
8928
9018
|
examples: [
|