@elizaos/plugin-twitter 1.0.0-beta.41 → 1.0.0-beta.43
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 +430 -45
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
2
|
import {
|
|
3
|
-
ChannelType as
|
|
3
|
+
ChannelType as ChannelType8,
|
|
4
4
|
EventType as EventType4,
|
|
5
5
|
Role,
|
|
6
6
|
Service,
|
|
7
|
-
createUniqueUuid as
|
|
8
|
-
logger as
|
|
7
|
+
createUniqueUuid as createUniqueUuid8,
|
|
8
|
+
logger as logger11
|
|
9
9
|
} from "@elizaos/core";
|
|
10
10
|
|
|
11
11
|
// src/actions/spaceJoin.ts
|
|
@@ -4604,29 +4604,29 @@ async function unmuteSpeaker(params) {
|
|
|
4604
4604
|
throw new Error(`unmuteSpeaker => ${resp.status} ${text}`);
|
|
4605
4605
|
}
|
|
4606
4606
|
}
|
|
4607
|
-
function setupCommonChatEvents(chatClient,
|
|
4607
|
+
function setupCommonChatEvents(chatClient, logger12, emitter) {
|
|
4608
4608
|
chatClient.on("occupancyUpdate", (upd) => {
|
|
4609
|
-
|
|
4609
|
+
logger12.debug("[ChatEvents] occupancyUpdate =>", upd);
|
|
4610
4610
|
emitter.emit("occupancyUpdate", upd);
|
|
4611
4611
|
});
|
|
4612
4612
|
chatClient.on("guestReaction", (reaction) => {
|
|
4613
|
-
|
|
4613
|
+
logger12.debug("[ChatEvents] guestReaction =>", reaction);
|
|
4614
4614
|
emitter.emit("guestReaction", reaction);
|
|
4615
4615
|
});
|
|
4616
4616
|
chatClient.on("muteStateChanged", (evt) => {
|
|
4617
|
-
|
|
4617
|
+
logger12.debug("[ChatEvents] muteStateChanged =>", evt);
|
|
4618
4618
|
emitter.emit("muteStateChanged", evt);
|
|
4619
4619
|
});
|
|
4620
4620
|
chatClient.on("speakerRequest", (req) => {
|
|
4621
|
-
|
|
4621
|
+
logger12.debug("[ChatEvents] speakerRequest =>", req);
|
|
4622
4622
|
emitter.emit("speakerRequest", req);
|
|
4623
4623
|
});
|
|
4624
4624
|
chatClient.on("newSpeakerAccepted", (info) => {
|
|
4625
|
-
|
|
4625
|
+
logger12.debug("[ChatEvents] newSpeakerAccepted =>", info);
|
|
4626
4626
|
emitter.emit("newSpeakerAccepted", info);
|
|
4627
4627
|
});
|
|
4628
4628
|
chatClient.on("newSpeakerRemoved", (info) => {
|
|
4629
|
-
|
|
4629
|
+
logger12.debug("[ChatEvents] newSpeakerRemoved =>", info);
|
|
4630
4630
|
emitter.emit("newSpeakerRemoved", info);
|
|
4631
4631
|
});
|
|
4632
4632
|
}
|
|
@@ -6699,6 +6699,31 @@ async function sendTweet(client, text, mediaData = [], tweetToReplyTo) {
|
|
|
6699
6699
|
return await sendStandardTweet(client, text, tweetToReplyTo, mediaData);
|
|
6700
6700
|
}
|
|
6701
6701
|
}
|
|
6702
|
+
var parseActionResponseFromText = (text) => {
|
|
6703
|
+
const actions = {
|
|
6704
|
+
like: false,
|
|
6705
|
+
retweet: false,
|
|
6706
|
+
quote: false,
|
|
6707
|
+
reply: false
|
|
6708
|
+
};
|
|
6709
|
+
const likePattern = /\[LIKE\]/i;
|
|
6710
|
+
const retweetPattern = /\[RETWEET\]/i;
|
|
6711
|
+
const quotePattern = /\[QUOTE\]/i;
|
|
6712
|
+
const replyPattern = /\[REPLY\]/i;
|
|
6713
|
+
actions.like = likePattern.test(text);
|
|
6714
|
+
actions.retweet = retweetPattern.test(text);
|
|
6715
|
+
actions.quote = quotePattern.test(text);
|
|
6716
|
+
actions.reply = replyPattern.test(text);
|
|
6717
|
+
const lines = text.split("\n");
|
|
6718
|
+
for (const line of lines) {
|
|
6719
|
+
const trimmed = line.trim();
|
|
6720
|
+
if (trimmed === "[LIKE]") actions.like = true;
|
|
6721
|
+
if (trimmed === "[RETWEET]") actions.retweet = true;
|
|
6722
|
+
if (trimmed === "[QUOTE]") actions.quote = true;
|
|
6723
|
+
if (trimmed === "[REPLY]") actions.reply = true;
|
|
6724
|
+
}
|
|
6725
|
+
return { actions };
|
|
6726
|
+
};
|
|
6702
6727
|
async function generateFiller(runtime, fillerType) {
|
|
6703
6728
|
try {
|
|
6704
6729
|
const prompt = composePrompt({
|
|
@@ -8428,8 +8453,7 @@ import {
|
|
|
8428
8453
|
ChannelType as ChannelType6,
|
|
8429
8454
|
EventType as EventType3,
|
|
8430
8455
|
createUniqueUuid as createUniqueUuid6,
|
|
8431
|
-
logger as logger8
|
|
8432
|
-
parseBooleanFromText
|
|
8456
|
+
logger as logger8
|
|
8433
8457
|
} from "@elizaos/core";
|
|
8434
8458
|
var TwitterPostClient = class {
|
|
8435
8459
|
/**
|
|
@@ -8447,12 +8471,6 @@ var TwitterPostClient = class {
|
|
|
8447
8471
|
logger8.log("Twitter Client Configuration:");
|
|
8448
8472
|
logger8.log(`- Username: ${this.twitterUsername}`);
|
|
8449
8473
|
logger8.log(`- Dry Run Mode: ${this.isDryRun ? "Enabled" : "Disabled"}`);
|
|
8450
|
-
this.state.isTwitterEnabled = parseBooleanFromText(
|
|
8451
|
-
String(
|
|
8452
|
-
this.state?.TWITTER_ENABLE_POST_GENERATION || this.runtime.getSetting("TWITTER_ENABLE_POST_GENERATION") || ""
|
|
8453
|
-
)
|
|
8454
|
-
);
|
|
8455
|
-
logger8.log(`- Auto-post: ${this.state.isTwitterEnabled ? "enabled" : "disabled"}`);
|
|
8456
8474
|
logger8.log(
|
|
8457
8475
|
`- Post Interval: ${this.state?.TWITTER_POST_INTERVAL_MIN || this.runtime.getSetting("TWITTER_POST_INTERVAL_MIN") || 90}-${this.state?.TWITTER_POST_INTERVAL_MAX || this.runtime.getSetting("TWITTER_POST_INTERVAL_MAX") || 180} minutes`
|
|
8458
8476
|
);
|
|
@@ -8468,11 +8486,6 @@ var TwitterPostClient = class {
|
|
|
8468
8486
|
*/
|
|
8469
8487
|
async start() {
|
|
8470
8488
|
logger8.log("Starting Twitter post client...");
|
|
8471
|
-
const tweetGeneration = this.state.isTwitterEnabled;
|
|
8472
|
-
if (tweetGeneration === false) {
|
|
8473
|
-
logger8.log("Tweet generation is disabled");
|
|
8474
|
-
return;
|
|
8475
|
-
}
|
|
8476
8489
|
const generateNewTweetLoop = async () => {
|
|
8477
8490
|
const minPostMinutes = this.state?.TWITTER_POST_INTERVAL_MIN || this.runtime.getSetting("TWITTER_POST_INTERVAL_MIN") || 90;
|
|
8478
8491
|
const maxPostMinutes = this.state?.TWITTER_POST_INTERVAL_MAX || this.runtime.getSetting("TWITTER_POST_INTERVAL_MAX") || 180;
|
|
@@ -8599,8 +8612,370 @@ var TwitterPostClient = class {
|
|
|
8599
8612
|
}
|
|
8600
8613
|
};
|
|
8601
8614
|
|
|
8602
|
-
// src/
|
|
8615
|
+
// src/timeline.ts
|
|
8616
|
+
import {
|
|
8617
|
+
ChannelType as ChannelType7,
|
|
8618
|
+
composePromptFromState,
|
|
8619
|
+
createUniqueUuid as createUniqueUuid7,
|
|
8620
|
+
ModelType as ModelType5,
|
|
8621
|
+
parseKeyValueXml
|
|
8622
|
+
} from "@elizaos/core";
|
|
8603
8623
|
import { logger as logger9 } from "@elizaos/core";
|
|
8624
|
+
|
|
8625
|
+
// src/templates.ts
|
|
8626
|
+
var twitterActionTemplate = `
|
|
8627
|
+
# INSTRUCTIONS: Determine actions for {{agentName}} (@{{twitterUserName}}) based on:
|
|
8628
|
+
{{bio}}
|
|
8629
|
+
{{postDirections}}
|
|
8630
|
+
|
|
8631
|
+
Guidelines:
|
|
8632
|
+
- ONLY engage with content that DIRECTLY relates to character's core interests
|
|
8633
|
+
- Direct mentions are priority IF they are on-topic
|
|
8634
|
+
- Skip ALL content that is:
|
|
8635
|
+
- Off-topic or tangentially related
|
|
8636
|
+
- From high-profile accounts unless explicitly relevant
|
|
8637
|
+
- Generic/viral content without specific relevance
|
|
8638
|
+
- Political/controversial unless central to character
|
|
8639
|
+
- Promotional/marketing unless directly relevant
|
|
8640
|
+
|
|
8641
|
+
Actions (respond only with tags):
|
|
8642
|
+
[LIKE] - Perfect topic match AND aligns with character (9.8/10)
|
|
8643
|
+
[RETWEET] - Exceptional content that embodies character's expertise (9.5/10)
|
|
8644
|
+
[QUOTE] - Can add substantial domain expertise (9.5/10)
|
|
8645
|
+
[REPLY] - Can contribute meaningful, expert-level insight (9.5/10)
|
|
8646
|
+
`;
|
|
8647
|
+
var quoteTweetTemplate = `# Task: Write a quote tweet in the voice, style, and perspective of {{agentName}} @{{twitterUserName}}.
|
|
8648
|
+
|
|
8649
|
+
{{bio}}
|
|
8650
|
+
{{postDirections}}
|
|
8651
|
+
|
|
8652
|
+
<response>
|
|
8653
|
+
<thought>Your thought here, explaining why the quote tweet is meaningful or how it connects to what {{agentName}} cares about</thought>
|
|
8654
|
+
<post>The quote tweet content here, under 280 characters, without emojis, no questions</post>
|
|
8655
|
+
</response>
|
|
8656
|
+
|
|
8657
|
+
Your quote tweet should be:
|
|
8658
|
+
- A reaction, agreement, disagreement, or expansion of the original tweet
|
|
8659
|
+
- Personal and unique to {{agentName}}\u2019s style and point of view
|
|
8660
|
+
- 1 to 3 sentences long, chosen at random
|
|
8661
|
+
- No questions, no emojis, concise
|
|
8662
|
+
- Use "\\n\\n" (double spaces) between multiple sentences
|
|
8663
|
+
- Max 280 characters including line breaks
|
|
8664
|
+
|
|
8665
|
+
Your output must ONLY contain the XML block.`;
|
|
8666
|
+
var replyTweetTemplate = `# Task: Write a reply tweet in the voice, style, and perspective of {{agentName}} @{{twitterUserName}}.
|
|
8667
|
+
|
|
8668
|
+
{{bio}}
|
|
8669
|
+
{{postDirections}}
|
|
8670
|
+
|
|
8671
|
+
<response>
|
|
8672
|
+
<thought>Your thought here, explaining why this reply is meaningful or how it connects to what {{agentName}} cares about</thought>
|
|
8673
|
+
<post>The reply tweet content here, under 280 characters, without emojis, no questions</post>
|
|
8674
|
+
</response>
|
|
8675
|
+
|
|
8676
|
+
Your reply should be:
|
|
8677
|
+
- A direct response, agreement, disagreement, or personal take on the original tweet
|
|
8678
|
+
- Reflective of {{agentName}}\u2019s unique voice and values
|
|
8679
|
+
- 1 to 2 sentences long, chosen at random
|
|
8680
|
+
- No questions, no emojis, concise
|
|
8681
|
+
- Use "\\n\\n" (double spaces) between multiple sentences if needed
|
|
8682
|
+
- Max 280 characters including line breaks
|
|
8683
|
+
|
|
8684
|
+
Your output must ONLY contain the XML block.`;
|
|
8685
|
+
|
|
8686
|
+
// src/timeline.ts
|
|
8687
|
+
var TwitterTimelineClient = class {
|
|
8688
|
+
constructor(client, runtime, state) {
|
|
8689
|
+
this.client = client;
|
|
8690
|
+
this.twitterClient = client.twitterClient;
|
|
8691
|
+
this.runtime = runtime;
|
|
8692
|
+
this.state = state;
|
|
8693
|
+
this.timelineType = this.state?.TWITTER_TIMELINE_MODE || this.runtime.getSetting("TWITTER_TIMELINE_MODE");
|
|
8694
|
+
}
|
|
8695
|
+
async start() {
|
|
8696
|
+
const handleTwitterTimelineLoop = () => {
|
|
8697
|
+
const interactionInterval = (this.state?.TWITTER_TIMELINE_POLL_INTERVAL || this.runtime.getSetting("TWITTER_TIMELINE_POLL_INTERVAL") || 120) * 1e3;
|
|
8698
|
+
this.handleTimeline();
|
|
8699
|
+
setTimeout(handleTwitterTimelineLoop, interactionInterval);
|
|
8700
|
+
};
|
|
8701
|
+
handleTwitterTimelineLoop();
|
|
8702
|
+
}
|
|
8703
|
+
async getTimeline(count) {
|
|
8704
|
+
const twitterUsername = this.client.profile?.username;
|
|
8705
|
+
const homeTimeline = this.timelineType === "following" /* Following */ ? await this.twitterClient.fetchFollowingTimeline(count, []) : await this.twitterClient.fetchHomeTimeline(count, []);
|
|
8706
|
+
return homeTimeline.map((tweet) => ({
|
|
8707
|
+
id: tweet.rest_id,
|
|
8708
|
+
name: tweet.core?.user_results?.result?.legacy?.name,
|
|
8709
|
+
username: tweet.core?.user_results?.result?.legacy?.screen_name,
|
|
8710
|
+
text: tweet.legacy?.full_text,
|
|
8711
|
+
inReplyToStatusId: tweet.legacy?.in_reply_to_status_id_str,
|
|
8712
|
+
timestamp: new Date(tweet.legacy?.created_at).getTime() / 1e3,
|
|
8713
|
+
userId: tweet.legacy?.user_id_str,
|
|
8714
|
+
conversationId: tweet.legacy?.conversation_id_str,
|
|
8715
|
+
permanentUrl: `https://twitter.com/${tweet.core?.user_results?.result?.legacy?.screen_name}/status/${tweet.rest_id}`,
|
|
8716
|
+
hashtags: tweet.legacy?.entities?.hashtags || [],
|
|
8717
|
+
mentions: tweet.legacy?.entities?.user_mentions || [],
|
|
8718
|
+
photos: tweet.legacy?.entities?.media?.filter((media) => media.type === "photo").map((media) => ({
|
|
8719
|
+
id: media.id_str,
|
|
8720
|
+
url: media.media_url_https,
|
|
8721
|
+
// Store media_url_https as url
|
|
8722
|
+
alt_text: media.alt_text
|
|
8723
|
+
})) || [],
|
|
8724
|
+
thread: tweet.thread || [],
|
|
8725
|
+
urls: tweet.legacy?.entities?.urls || [],
|
|
8726
|
+
videos: tweet.legacy?.entities?.media?.filter((media) => media.type === "video") || []
|
|
8727
|
+
})).filter((tweet) => tweet.username !== twitterUsername);
|
|
8728
|
+
}
|
|
8729
|
+
createTweetId(runtime, tweet) {
|
|
8730
|
+
return createUniqueUuid7(runtime, tweet.id);
|
|
8731
|
+
}
|
|
8732
|
+
formMessage(runtime, tweet) {
|
|
8733
|
+
return {
|
|
8734
|
+
id: this.createTweetId(runtime, tweet),
|
|
8735
|
+
agentId: runtime.agentId,
|
|
8736
|
+
content: {
|
|
8737
|
+
text: tweet.text,
|
|
8738
|
+
url: tweet.permanentUrl,
|
|
8739
|
+
imageUrls: tweet.photos?.map((photo) => photo.url) || [],
|
|
8740
|
+
inReplyTo: tweet.inReplyToStatusId ? createUniqueUuid7(runtime, tweet.inReplyToStatusId) : void 0,
|
|
8741
|
+
source: "twitter",
|
|
8742
|
+
channelType: ChannelType7.GROUP,
|
|
8743
|
+
tweet
|
|
8744
|
+
},
|
|
8745
|
+
entityId: createUniqueUuid7(runtime, tweet.userId),
|
|
8746
|
+
roomId: createUniqueUuid7(runtime, tweet.conversationId),
|
|
8747
|
+
createdAt: tweet.timestamp * 1e3
|
|
8748
|
+
};
|
|
8749
|
+
}
|
|
8750
|
+
async handleTimeline() {
|
|
8751
|
+
console.log("Start Hanldeling Twitter Timeline");
|
|
8752
|
+
const tweets = await this.getTimeline(20);
|
|
8753
|
+
const maxActionsPerCycle = 20;
|
|
8754
|
+
const tweetDecisions = [];
|
|
8755
|
+
for (const tweet of tweets) {
|
|
8756
|
+
try {
|
|
8757
|
+
const tweetId = this.createTweetId(this.runtime, tweet);
|
|
8758
|
+
const memory = await this.runtime.getMemoryById(tweetId);
|
|
8759
|
+
if (memory) {
|
|
8760
|
+
console.log(`Already processed tweet ID: ${tweet.id}`);
|
|
8761
|
+
continue;
|
|
8762
|
+
}
|
|
8763
|
+
const roomId = createUniqueUuid7(this.runtime, tweet.conversationId);
|
|
8764
|
+
const message = this.formMessage(this.runtime, tweet);
|
|
8765
|
+
let state = await this.runtime.composeState(message);
|
|
8766
|
+
const actionRespondPrompt = composePromptFromState({
|
|
8767
|
+
state,
|
|
8768
|
+
template: this.runtime.character.templates?.twitterActionTemplate || twitterActionTemplate
|
|
8769
|
+
}) + `
|
|
8770
|
+
Tweet:
|
|
8771
|
+
${tweet.text}
|
|
8772
|
+
|
|
8773
|
+
# Respond with qualifying action tags only.
|
|
8774
|
+
|
|
8775
|
+
Choose any combination of [LIKE], [RETWEET], [QUOTE], and [REPLY] that are appropriate. Each action must be on its own line. Your response must only include the chosen actions.`;
|
|
8776
|
+
const actionResponse = await this.runtime.useModel(ModelType5.TEXT_SMALL, {
|
|
8777
|
+
prompt: actionRespondPrompt
|
|
8778
|
+
});
|
|
8779
|
+
if (!actionResponse) {
|
|
8780
|
+
logger9.log(`No valid actions generated for tweet ${tweet.id}`);
|
|
8781
|
+
continue;
|
|
8782
|
+
}
|
|
8783
|
+
const { actions } = parseActionResponseFromText(actionResponse.trim());
|
|
8784
|
+
tweetDecisions.push({
|
|
8785
|
+
tweet,
|
|
8786
|
+
actionResponse: actions,
|
|
8787
|
+
tweetState: state,
|
|
8788
|
+
roomId
|
|
8789
|
+
});
|
|
8790
|
+
} catch (error) {
|
|
8791
|
+
logger9.error(`Error processing tweet ${tweet.id}:`, error);
|
|
8792
|
+
continue;
|
|
8793
|
+
}
|
|
8794
|
+
}
|
|
8795
|
+
const rankByActionRelevance = (arr) => {
|
|
8796
|
+
return arr.sort((a, b) => {
|
|
8797
|
+
const countTrue = (obj) => Object.values(obj).filter(Boolean).length;
|
|
8798
|
+
const countA = countTrue(a.actionResponse);
|
|
8799
|
+
const countB = countTrue(b.actionResponse);
|
|
8800
|
+
if (countA !== countB) {
|
|
8801
|
+
return countB - countA;
|
|
8802
|
+
}
|
|
8803
|
+
if (a.actionResponse.like !== b.actionResponse.like) {
|
|
8804
|
+
return a.actionResponse.like ? -1 : 1;
|
|
8805
|
+
}
|
|
8806
|
+
return 0;
|
|
8807
|
+
});
|
|
8808
|
+
};
|
|
8809
|
+
const prioritizedTweets = rankByActionRelevance(tweetDecisions);
|
|
8810
|
+
this.processTimelineActions(prioritizedTweets);
|
|
8811
|
+
}
|
|
8812
|
+
async processTimelineActions(tweetDecisions) {
|
|
8813
|
+
const results = [];
|
|
8814
|
+
for (const decision of tweetDecisions) {
|
|
8815
|
+
const { actionResponse, tweetState, roomId, tweet } = decision;
|
|
8816
|
+
const entityId = createUniqueUuid7(this.runtime, tweet.userId);
|
|
8817
|
+
const worldId = createUniqueUuid7(this.runtime, tweet.userId);
|
|
8818
|
+
await this.ensureTweetWorldContext(tweet, roomId, worldId, entityId);
|
|
8819
|
+
try {
|
|
8820
|
+
const message = this.formMessage(this.runtime, tweet);
|
|
8821
|
+
await Promise.all([
|
|
8822
|
+
this.runtime.addEmbeddingToMemory(message),
|
|
8823
|
+
this.runtime.createMemory(message, "messages")
|
|
8824
|
+
]);
|
|
8825
|
+
if (actionResponse.like) {
|
|
8826
|
+
this.handleLikeAction(tweet);
|
|
8827
|
+
}
|
|
8828
|
+
if (actionResponse.retweet) {
|
|
8829
|
+
this.handleRetweetAction(tweet);
|
|
8830
|
+
}
|
|
8831
|
+
if (actionResponse.quote) {
|
|
8832
|
+
this.handleQuoteAction(tweet);
|
|
8833
|
+
}
|
|
8834
|
+
if (actionResponse.reply) {
|
|
8835
|
+
this.handleReplyAction(tweet);
|
|
8836
|
+
}
|
|
8837
|
+
} catch (error) {
|
|
8838
|
+
logger9.error(`Error processing tweet ${tweet.id}:`, error);
|
|
8839
|
+
continue;
|
|
8840
|
+
}
|
|
8841
|
+
}
|
|
8842
|
+
return results;
|
|
8843
|
+
}
|
|
8844
|
+
async ensureTweetWorldContext(tweet, roomId, worldId, entityId) {
|
|
8845
|
+
await this.runtime.ensureWorldExists({
|
|
8846
|
+
id: worldId,
|
|
8847
|
+
name: `${tweet.name}'s Twitter`,
|
|
8848
|
+
agentId: this.runtime.agentId,
|
|
8849
|
+
serverId: tweet.userId,
|
|
8850
|
+
metadata: {
|
|
8851
|
+
ownership: { ownerId: tweet.userId },
|
|
8852
|
+
twitter: {
|
|
8853
|
+
username: tweet.username,
|
|
8854
|
+
id: tweet.userId,
|
|
8855
|
+
name: tweet.name
|
|
8856
|
+
}
|
|
8857
|
+
}
|
|
8858
|
+
});
|
|
8859
|
+
await this.runtime.ensureConnection({
|
|
8860
|
+
entityId,
|
|
8861
|
+
roomId,
|
|
8862
|
+
userName: tweet.username,
|
|
8863
|
+
name: tweet.name,
|
|
8864
|
+
source: "twitter",
|
|
8865
|
+
type: ChannelType7.GROUP,
|
|
8866
|
+
channelId: tweet.conversationId,
|
|
8867
|
+
serverId: tweet.userId,
|
|
8868
|
+
worldId
|
|
8869
|
+
});
|
|
8870
|
+
await this.runtime.ensureRoomExists({
|
|
8871
|
+
id: roomId,
|
|
8872
|
+
name: `Conversation with ${tweet.name}`,
|
|
8873
|
+
source: "twitter",
|
|
8874
|
+
type: ChannelType7.GROUP,
|
|
8875
|
+
channelId: tweet.conversationId,
|
|
8876
|
+
serverId: tweet.userId,
|
|
8877
|
+
worldId
|
|
8878
|
+
});
|
|
8879
|
+
}
|
|
8880
|
+
async handleLikeAction(tweet) {
|
|
8881
|
+
try {
|
|
8882
|
+
await this.twitterClient.likeTweet(tweet.id);
|
|
8883
|
+
logger9.log(`Liked tweet ${tweet.id}`);
|
|
8884
|
+
} catch (error) {
|
|
8885
|
+
logger9.error(`Error liking tweet ${tweet.id}:`, error);
|
|
8886
|
+
}
|
|
8887
|
+
}
|
|
8888
|
+
async handleRetweetAction(tweet) {
|
|
8889
|
+
try {
|
|
8890
|
+
await this.twitterClient.retweet(tweet.id);
|
|
8891
|
+
logger9.log(`Retweeted tweet ${tweet.id}`);
|
|
8892
|
+
} catch (error) {
|
|
8893
|
+
logger9.error(`Error retweeting tweet ${tweet.id}:`, error);
|
|
8894
|
+
}
|
|
8895
|
+
}
|
|
8896
|
+
async handleQuoteAction(tweet) {
|
|
8897
|
+
try {
|
|
8898
|
+
const message = this.formMessage(this.runtime, tweet);
|
|
8899
|
+
let state = await this.runtime.composeState(message);
|
|
8900
|
+
const quotePrompt = composePromptFromState({
|
|
8901
|
+
state,
|
|
8902
|
+
template: this.runtime.character.templates?.quoteTweetTemplate || quoteTweetTemplate
|
|
8903
|
+
}) + `
|
|
8904
|
+
You are responding to this tweet:
|
|
8905
|
+
${tweet.text}`;
|
|
8906
|
+
const quoteResponse = await this.runtime.useModel(ModelType5.TEXT_SMALL, {
|
|
8907
|
+
prompt: quotePrompt
|
|
8908
|
+
});
|
|
8909
|
+
const responseObject = parseKeyValueXml(quoteResponse);
|
|
8910
|
+
if (responseObject.post) {
|
|
8911
|
+
const result = await this.client.requestQueue.add(
|
|
8912
|
+
async () => await this.twitterClient.sendQuoteTweet(responseObject.post, tweet.id)
|
|
8913
|
+
);
|
|
8914
|
+
const body = await result.json();
|
|
8915
|
+
if (body?.data?.create_tweet?.tweet_results?.result) {
|
|
8916
|
+
logger9.log("Successfully posted quote tweet");
|
|
8917
|
+
} else {
|
|
8918
|
+
logger9.error("Quote tweet creation failed:", body);
|
|
8919
|
+
}
|
|
8920
|
+
const responseId = createUniqueUuid7(this.runtime, body.rest_id);
|
|
8921
|
+
const responseMemory = {
|
|
8922
|
+
id: responseId,
|
|
8923
|
+
entityId: this.runtime.agentId,
|
|
8924
|
+
agentId: this.runtime.agentId,
|
|
8925
|
+
roomId: message.roomId,
|
|
8926
|
+
content: {
|
|
8927
|
+
...responseObject,
|
|
8928
|
+
inReplyTo: message.id
|
|
8929
|
+
},
|
|
8930
|
+
createdAt: Date.now()
|
|
8931
|
+
};
|
|
8932
|
+
await this.runtime.createMemory(responseMemory, "messages");
|
|
8933
|
+
}
|
|
8934
|
+
} catch (error) {
|
|
8935
|
+
logger9.error("Error in quote tweet generation:", error);
|
|
8936
|
+
}
|
|
8937
|
+
}
|
|
8938
|
+
async handleReplyAction(tweet) {
|
|
8939
|
+
try {
|
|
8940
|
+
const message = this.formMessage(this.runtime, tweet);
|
|
8941
|
+
let state = await this.runtime.composeState(message);
|
|
8942
|
+
const replyPrompt = composePromptFromState({
|
|
8943
|
+
state,
|
|
8944
|
+
template: this.runtime.character.templates?.replyTweetTemplate || replyTweetTemplate
|
|
8945
|
+
}) + `
|
|
8946
|
+
You are responding to this tweet:
|
|
8947
|
+
${tweet.text}`;
|
|
8948
|
+
const replyResponse = await this.runtime.useModel(ModelType5.TEXT_SMALL, {
|
|
8949
|
+
prompt: replyPrompt
|
|
8950
|
+
});
|
|
8951
|
+
const responseObject = parseKeyValueXml(replyResponse);
|
|
8952
|
+
if (responseObject.post) {
|
|
8953
|
+
const tweetResult = await sendTweet(this.client, responseObject.post, [], tweet.id);
|
|
8954
|
+
if (!tweetResult) {
|
|
8955
|
+
throw new Error("Failed to get tweet result from response");
|
|
8956
|
+
}
|
|
8957
|
+
const responseId = createUniqueUuid7(this.runtime, tweetResult.rest_id);
|
|
8958
|
+
const responseMemory = {
|
|
8959
|
+
id: responseId,
|
|
8960
|
+
entityId: this.runtime.agentId,
|
|
8961
|
+
agentId: this.runtime.agentId,
|
|
8962
|
+
roomId: message.roomId,
|
|
8963
|
+
content: {
|
|
8964
|
+
...responseObject,
|
|
8965
|
+
inReplyTo: message.id
|
|
8966
|
+
},
|
|
8967
|
+
createdAt: Date.now()
|
|
8968
|
+
};
|
|
8969
|
+
await this.runtime.createMemory(responseMemory, "messages");
|
|
8970
|
+
}
|
|
8971
|
+
} catch (error) {
|
|
8972
|
+
logger9.error("Error in quote tweet generation:", error);
|
|
8973
|
+
}
|
|
8974
|
+
}
|
|
8975
|
+
};
|
|
8976
|
+
|
|
8977
|
+
// src/tests.ts
|
|
8978
|
+
import { logger as logger10 } from "@elizaos/core";
|
|
8604
8979
|
var ClientBaseTestSuite = class {
|
|
8605
8980
|
constructor() {
|
|
8606
8981
|
this.name = "twitter-client-base";
|
|
@@ -8660,7 +9035,7 @@ var ClientBaseTestSuite = class {
|
|
|
8660
9035
|
if (client.state.TWITTER_DRY_RUN !== true) {
|
|
8661
9036
|
throw new Error("Client state TWITTER_DRY_RUN mismatch.");
|
|
8662
9037
|
}
|
|
8663
|
-
|
|
9038
|
+
logger10.success("ClientBase instance created with correct configuration.");
|
|
8664
9039
|
}
|
|
8665
9040
|
async testPostIntervals() {
|
|
8666
9041
|
const client = new ClientBase(this.mockRuntime, this.mockConfig);
|
|
@@ -8676,7 +9051,7 @@ var ClientBaseTestSuite = class {
|
|
|
8676
9051
|
if (client.state.TWITTER_POST_INTERVAL_MAX !== 180) {
|
|
8677
9052
|
throw new Error("Client state TWITTER_POST_INTERVAL_MAX mismatch.");
|
|
8678
9053
|
}
|
|
8679
|
-
|
|
9054
|
+
logger10.success("ClientBase initialized with correct post intervals.");
|
|
8680
9055
|
}
|
|
8681
9056
|
};
|
|
8682
9057
|
|
|
@@ -8684,8 +9059,15 @@ var ClientBaseTestSuite = class {
|
|
|
8684
9059
|
var TwitterClientInstance = class {
|
|
8685
9060
|
constructor(runtime, state) {
|
|
8686
9061
|
this.client = new ClientBase(runtime, state);
|
|
8687
|
-
|
|
8688
|
-
|
|
9062
|
+
if (runtime.getSetting("TWITTER_ENABLE_POST_GENERATION") === true) {
|
|
9063
|
+
this.post = new TwitterPostClient(this.client, runtime, state);
|
|
9064
|
+
}
|
|
9065
|
+
if (runtime.getSetting("TWITTER_INTERACTION_ENABLE") === true) {
|
|
9066
|
+
this.interaction = new TwitterInteractionClient(this.client, runtime, state);
|
|
9067
|
+
}
|
|
9068
|
+
if (runtime.getSetting("TWITTER_TIMELINE_ENABLE") === true) {
|
|
9069
|
+
this.timeline = new TwitterTimelineClient(this.client, runtime, state);
|
|
9070
|
+
}
|
|
8689
9071
|
if (runtime.getSetting("TWITTER_SPACES_ENABLE") === true) {
|
|
8690
9072
|
this.space = new TwitterSpaceClient(this.client, runtime);
|
|
8691
9073
|
}
|
|
@@ -8711,7 +9093,7 @@ var _TwitterService = class _TwitterService extends Service {
|
|
|
8711
9093
|
try {
|
|
8712
9094
|
const existingClient = this.getClient(clientId, runtime.agentId);
|
|
8713
9095
|
if (existingClient) {
|
|
8714
|
-
|
|
9096
|
+
logger11.info(`Twitter client already exists for ${clientId}`);
|
|
8715
9097
|
return existingClient;
|
|
8716
9098
|
}
|
|
8717
9099
|
const client = new TwitterClientInstance(runtime, state);
|
|
@@ -8725,12 +9107,15 @@ var _TwitterService = class _TwitterService extends Service {
|
|
|
8725
9107
|
if (client.interaction) {
|
|
8726
9108
|
client.interaction.start();
|
|
8727
9109
|
}
|
|
9110
|
+
if (client.timeline) {
|
|
9111
|
+
client.timeline.start();
|
|
9112
|
+
}
|
|
8728
9113
|
this.clients.set(this.getClientKey(clientId, runtime.agentId), client);
|
|
8729
9114
|
await this.emitServerJoinedEvent(runtime, client);
|
|
8730
|
-
|
|
9115
|
+
logger11.info(`Created Twitter client for ${clientId}`);
|
|
8731
9116
|
return client;
|
|
8732
9117
|
} catch (error) {
|
|
8733
|
-
|
|
9118
|
+
logger11.error(`Failed to create Twitter client for ${clientId}:`, error);
|
|
8734
9119
|
throw error;
|
|
8735
9120
|
}
|
|
8736
9121
|
}
|
|
@@ -8742,13 +9127,13 @@ var _TwitterService = class _TwitterService extends Service {
|
|
|
8742
9127
|
async emitServerJoinedEvent(runtime, client) {
|
|
8743
9128
|
try {
|
|
8744
9129
|
if (!client.client.profile) {
|
|
8745
|
-
|
|
9130
|
+
logger11.warn("Twitter profile not available yet, can't emit WORLD_JOINED event");
|
|
8746
9131
|
return;
|
|
8747
9132
|
}
|
|
8748
9133
|
const profile = client.client.profile;
|
|
8749
9134
|
const twitterId = profile.id;
|
|
8750
9135
|
const username = profile.username;
|
|
8751
|
-
const worldId =
|
|
9136
|
+
const worldId = createUniqueUuid8(runtime, twitterId);
|
|
8752
9137
|
const world = {
|
|
8753
9138
|
id: worldId,
|
|
8754
9139
|
name: `${username}'s Twitter`,
|
|
@@ -8765,27 +9150,27 @@ var _TwitterService = class _TwitterService extends Service {
|
|
|
8765
9150
|
}
|
|
8766
9151
|
}
|
|
8767
9152
|
};
|
|
8768
|
-
const homeTimelineRoomId =
|
|
9153
|
+
const homeTimelineRoomId = createUniqueUuid8(runtime, `${twitterId}-home`);
|
|
8769
9154
|
const homeTimelineRoom = {
|
|
8770
9155
|
id: homeTimelineRoomId,
|
|
8771
9156
|
name: `${username}'s Timeline`,
|
|
8772
9157
|
source: "twitter",
|
|
8773
|
-
type:
|
|
9158
|
+
type: ChannelType8.FEED,
|
|
8774
9159
|
channelId: `${twitterId}-home`,
|
|
8775
9160
|
serverId: twitterId,
|
|
8776
9161
|
worldId
|
|
8777
9162
|
};
|
|
8778
|
-
const mentionsRoomId =
|
|
9163
|
+
const mentionsRoomId = createUniqueUuid8(runtime, `${twitterId}-mentions`);
|
|
8779
9164
|
const mentionsRoom = {
|
|
8780
9165
|
id: mentionsRoomId,
|
|
8781
9166
|
name: `${username}'s Mentions`,
|
|
8782
9167
|
source: "twitter",
|
|
8783
|
-
type:
|
|
9168
|
+
type: ChannelType8.GROUP,
|
|
8784
9169
|
channelId: `${twitterId}-mentions`,
|
|
8785
9170
|
serverId: twitterId,
|
|
8786
9171
|
worldId
|
|
8787
9172
|
};
|
|
8788
|
-
const twitterUserId =
|
|
9173
|
+
const twitterUserId = createUniqueUuid8(runtime, twitterId);
|
|
8789
9174
|
const twitterUser = {
|
|
8790
9175
|
id: twitterUserId,
|
|
8791
9176
|
names: [profile.screenName || username],
|
|
@@ -8806,9 +9191,9 @@ var _TwitterService = class _TwitterService extends Service {
|
|
|
8806
9191
|
users: [twitterUser],
|
|
8807
9192
|
source: "twitter"
|
|
8808
9193
|
});
|
|
8809
|
-
|
|
9194
|
+
logger11.info(`Emitted WORLD_JOINED event for Twitter account ${username}`);
|
|
8810
9195
|
} catch (error) {
|
|
8811
|
-
|
|
9196
|
+
logger11.error("Failed to emit WORLD_JOINED event for Twitter:", error);
|
|
8812
9197
|
}
|
|
8813
9198
|
}
|
|
8814
9199
|
getClient(clientId, agentId) {
|
|
@@ -8821,9 +9206,9 @@ var _TwitterService = class _TwitterService extends Service {
|
|
|
8821
9206
|
try {
|
|
8822
9207
|
await client.service.stop();
|
|
8823
9208
|
this.clients.delete(key);
|
|
8824
|
-
|
|
9209
|
+
logger11.info(`Stopped Twitter client for ${clientId}`);
|
|
8825
9210
|
} catch (error) {
|
|
8826
|
-
|
|
9211
|
+
logger11.error(`Error stopping Twitter client for ${clientId}:`, error);
|
|
8827
9212
|
}
|
|
8828
9213
|
}
|
|
8829
9214
|
}
|
|
@@ -8841,11 +9226,11 @@ var _TwitterService = class _TwitterService extends Service {
|
|
|
8841
9226
|
try {
|
|
8842
9227
|
if (config.TWITTER_USERNAME && // Basic auth
|
|
8843
9228
|
config.TWITTER_PASSWORD && config.TWITTER_EMAIL) {
|
|
8844
|
-
|
|
9229
|
+
logger11.info("Creating default Twitter client from character settings");
|
|
8845
9230
|
await twitterClientManager.createClient(runtime, runtime.agentId, config);
|
|
8846
9231
|
}
|
|
8847
9232
|
} catch (error) {
|
|
8848
|
-
|
|
9233
|
+
logger11.error("Failed to create default Twitter client:", error);
|
|
8849
9234
|
}
|
|
8850
9235
|
return twitterClientManager;
|
|
8851
9236
|
}
|
|
@@ -8858,7 +9243,7 @@ var _TwitterService = class _TwitterService extends Service {
|
|
|
8858
9243
|
await client.service.stop();
|
|
8859
9244
|
this.clients.delete(key);
|
|
8860
9245
|
} catch (error) {
|
|
8861
|
-
|
|
9246
|
+
logger11.error(`Error stopping Twitter client ${key}:`, error);
|
|
8862
9247
|
}
|
|
8863
9248
|
}
|
|
8864
9249
|
}
|