Skip to content

Commit

Permalink
Merge branch 'twitter-refactor' of https://github.com/bmgalego/eliza
Browse files Browse the repository at this point in the history
…into bmgalego-twitter-refactor
  • Loading branch information
ponderingdemocritus committed Nov 21, 2024
2 parents 47fc913 + 83885f8 commit c56fc01
Show file tree
Hide file tree
Showing 9 changed files with 260 additions and 211 deletions.
249 changes: 118 additions & 131 deletions packages/client-twitter/src/base.ts

Large diffs are not rendered by default.

18 changes: 14 additions & 4 deletions packages/client-twitter/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,35 @@ import { TwitterPostClient } from "./post.ts";
import { TwitterSearchClient } from "./search.ts";
import { TwitterInteractionClient } from "./interactions.ts";
import { IAgentRuntime, Client, elizaLogger } from "@ai16z/eliza";
import { ClientBase } from "./base.ts";

class TwitterAllClient {
class TwitterManager {
client: ClientBase;
post: TwitterPostClient;
search: TwitterSearchClient;
interaction: TwitterInteractionClient;
constructor(runtime: IAgentRuntime) {
this.post = new TwitterPostClient(runtime);
this.client = new ClientBase(runtime);
this.post = new TwitterPostClient(this.client, runtime);
// this.search = new TwitterSearchClient(runtime); // don't start the search client by default
// this searches topics from character file, but kind of violates consent of random users
// burns your rate limit and can get your account banned
// use at your own risk
this.interaction = new TwitterInteractionClient(runtime);
this.interaction = new TwitterInteractionClient(this.client, runtime);
}
}

export const TwitterClientInterface: Client = {
async start(runtime: IAgentRuntime) {
elizaLogger.log("Twitter client started");
return new TwitterAllClient(runtime);
const manager = new TwitterManager(runtime);

await manager.client.init();

await manager.post.start();
await manager.interaction.start();

return manager;
},
async stop(runtime: IAgentRuntime) {
elizaLogger.warn("Twitter client does not support stopping yet");
Expand Down
93 changes: 53 additions & 40 deletions packages/client-twitter/src/interactions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
stringToUuid,
elizaLogger,
} from "@ai16z/eliza";
import { ClientBase } from "./base.ts";
import { ClientBase } from "./base";
import { buildConversationThread, sendTweet, wait } from "./utils.ts";
import { embeddingZeroVector } from "@ai16z/eliza";

Expand Down Expand Up @@ -82,8 +82,16 @@ Thread of Tweets You Are Replying To:
# 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.
` + shouldRespondFooter;

export class TwitterInteractionClient extends ClientBase {
onReady() {
export class TwitterInteractionClient {
client: ClientBase;
runtime: IAgentRuntime;

constructor(client: ClientBase, runtime: IAgentRuntime) {
this.client = client;
this.runtime = runtime;
}

async start() {
const handleTwitterInteractionsLoop = () => {
this.handleTwitterInteractions();
setTimeout(
Expand All @@ -94,45 +102,43 @@ export class TwitterInteractionClient extends ClientBase {
handleTwitterInteractionsLoop();
}

constructor(runtime: IAgentRuntime) {
super({
runtime,
});
}

async handleTwitterInteractions() {
elizaLogger.log("Checking Twitter interactions");

const twitterUsername = this.client.profile.username;
try {
// Check for mentions
const tweetCandidates = (
await this.fetchSearchTweets(
`@${this.runtime.getSetting("TWITTER_USERNAME")}`,
await this.client.fetchSearchTweets(
`@${twitterUsername}`,
20,
SearchMode.Latest
)
).tweets;

// de-duplicate tweetCandidates with a set
const uniqueTweetCandidates = [...new Set(tweetCandidates)];

// Sort tweet candidates by ID in ascending order
uniqueTweetCandidates
.sort((a, b) => a.id.localeCompare(b.id))
.filter((tweet) => tweet.userId !== this.twitterUserId);
.filter((tweet) => tweet.userId !== this.client.profile.id);

// for each tweet candidate, handle the tweet
for (const tweet of uniqueTweetCandidates) {
// console.log("tweet:", tweet);
if (
!this.lastCheckedTweetId ||
parseInt(tweet.id) > this.lastCheckedTweetId
!this.client.lastCheckedTweetId ||
parseInt(tweet.id) > this.client.lastCheckedTweetId
) {
const conversationId =
tweet.conversationId + "-" + this.runtime.agentId;
elizaLogger.log("New Tweet found", tweet.permanentUrl);

const roomId = stringToUuid(conversationId);
const roomId = stringToUuid(
tweet.conversationId + "-" + this.runtime.agentId
);

const userIdUUID = stringToUuid(tweet.userId as string);
const userIdUUID =
tweet.userId === this.client.profile.id
? this.runtime.agentId
: stringToUuid(tweet.userId!);

await this.runtime.ensureConnection(
userIdUUID,
Expand All @@ -142,7 +148,10 @@ export class TwitterInteractionClient extends ClientBase {
"twitter"
);

const thread = await buildConversationThread(tweet, this);
const thread = await buildConversationThread(
tweet,
this.client
);

const message = {
content: { text: tweet.text },
Expand All @@ -158,12 +167,12 @@ export class TwitterInteractionClient extends ClientBase {
});

// Update the last checked tweet ID after processing each tweet
this.lastCheckedTweetId = parseInt(tweet.id);
this.client.lastCheckedTweetId = parseInt(tweet.id);
}
}

// Save the latest checked tweet ID to the file
await this.cacheLatestCheckedTweetId();
await this.client.cacheLatestCheckedTweetId();

elizaLogger.log("Finished checking Twitter interactions");
} catch (error) {
Expand All @@ -180,17 +189,18 @@ export class TwitterInteractionClient extends ClientBase {
message: Memory;
thread: Tweet[];
}) {
if (tweet.username === this.runtime.getSetting("TWITTER_USERNAME")) {
if (tweet.userId === this.client.profile.id) {
// console.log("skipping tweet from bot itself", tweet.id);
// Skip processing if the tweet is from the bot itself
return;
}

if (!message.content.text) {
elizaLogger.log("skipping tweet with no text", tweet.id);
elizaLogger.log("Skipping Tweet with no text", tweet.id);
return { text: "", action: "IGNORE" };
}
elizaLogger.log("handling tweet", tweet.id);

elizaLogger.log("Processing Tweet: ", tweet.id);
const formatTweet = (tweet: Tweet) => {
return ` ID: ${tweet.id}
From: ${tweet.name} (@${tweet.username})
Expand All @@ -201,12 +211,12 @@ export class TwitterInteractionClient extends ClientBase {
let homeTimeline: Tweet[] = [];
// read the file if it exists

const cachedTimeline = await this.getCachedTimeline();
const cachedTimeline = await this.client.getCachedTimeline();
if (cachedTimeline) {
homeTimeline = cachedTimeline;
} else {
homeTimeline = await this.fetchHomeTimeline(50);
await this.cacheTimeline(homeTimeline);
homeTimeline = await this.client.fetchHomeTimeline(50);
await this.client.cacheTimeline(homeTimeline);
}

elizaLogger.debug("Thread: ", thread);
Expand Down Expand Up @@ -235,7 +245,7 @@ export class TwitterInteractionClient extends ClientBase {
.join("\n");

let state = await this.runtime.composeState(message, {
twitterClient: this.twitterClient,
twitterClient: this.client.twitterClient,
twitterUserName: this.runtime.getSetting("TWITTER_USERNAME"),
currentPost,
formattedConversation,
Expand Down Expand Up @@ -270,7 +280,7 @@ export class TwitterInteractionClient extends ClientBase {
roomId,
createdAt: tweet.timestamp * 1000,
};
this.saveRequestMessage(message, state);
this.client.saveRequestMessage(message, state);
}

const shouldRespondContext = composeContext({
Expand Down Expand Up @@ -322,7 +332,7 @@ export class TwitterInteractionClient extends ClientBase {
try {
const callback: HandlerCallback = async (response: Content) => {
const memories = await sendTweet(
this,
this.client,
response,
message.roomId,
this.runtime.getSetting("TWITTER_USERNAME"),
Expand Down Expand Up @@ -380,19 +390,19 @@ export class TwitterInteractionClient extends ClientBase {
const visited: Set<string> = new Set();

async function processThread(currentTweet: Tweet, depth: number = 0) {
console.log("Processing tweet:", {
elizaLogger.log("Processing tweet:", {
id: currentTweet.id,
inReplyToStatusId: currentTweet.inReplyToStatusId,
depth: depth,
});

if (!currentTweet) {
console.log("No current tweet found for thread building");
elizaLogger.log("No current tweet found for thread building");
return;
}

if (depth >= maxReplies) {
console.log("Reached maximum reply depth", depth);
elizaLogger.log("Reached maximum reply depth", depth);
return;
}

Expand Down Expand Up @@ -456,7 +466,7 @@ export class TwitterInteractionClient extends ClientBase {
});

if (currentTweet.inReplyToStatusId) {
console.log(
elizaLogger.log(
"Fetching parent tweet:",
currentTweet.inReplyToStatusId
);
Expand All @@ -466,25 +476,28 @@ export class TwitterInteractionClient extends ClientBase {
);

if (parentTweet) {
console.log("Found parent tweet:", {
elizaLogger.log("Found parent tweet:", {
id: parentTweet.id,
text: parentTweet.text?.slice(0, 50),
});
await processThread(parentTweet, depth + 1);
} else {
console.log(
elizaLogger.log(
"No parent tweet found for:",
currentTweet.inReplyToStatusId
);
}
} catch (error) {
console.log("Error fetching parent tweet:", {
elizaLogger.log("Error fetching parent tweet:", {
tweetId: currentTweet.inReplyToStatusId,
error,
});
}
} else {
console.log("Reached end of reply chain at:", currentTweet.id);
elizaLogger.log(
"Reached end of reply chain at:",
currentTweet.id
);
}
}

Expand Down
Loading

0 comments on commit c56fc01

Please sign in to comment.