Twitter Integration

Configuration

Configure your Twitter client using environment variables and the twitterConfigSchema:

// Environment variables
TWITTER_USERNAME="your-username"
TWITTER_PASSWORD="your-password"
TWITTER_EMAIL="your-email"
TWITTER_2FA_SECRET="optional-2fa-secret"
TWITTER_POST_INTERVAL_HOURS=4
TWITTER_POLLING_INTERVAL=5 # minutes
TWITTER_DRY_RUN=true # For testing

// Configuration schema
const twitterConfigSchema = z.object({
  username: z.string().min(1, "Twitter username is required"),
  password: z.string().min(1, "Twitter password is required"),
  email: z.string().email("Valid email is required"),
  twoFactorSecret: z.string().optional(),
  retryLimit: z.number().int().min(1).default(5),
  postIntervalHours: z.number().int().min(1).default(4),
  enableActions: z.boolean().default(false)
});

Setting Up the Client

Initialize and start the Twitter client with your agent:

import { TwitterClient } from "@liz/twitter-client";

const config = {
  username: process.env.TWITTER_USERNAME,
  password: process.env.TWITTER_PASSWORD,
  email: process.env.TWITTER_EMAIL,
  twoFactorSecret: process.env.TWITTER_2FA_SECRET,
  retryLimit: 3,
  postIntervalHours: 4,
  pollingInterval: 5,
  dryRun: process.env.TWITTER_DRY_RUN === "true"
};

const twitter = new TwitterClient(agent, config);
await twitter.start(); // Starts posting & monitoring intervals

Automated Posting

The client can automatically generate and post tweets at regular intervals:

// Automatic posting loop
async generateAndPost() {
  const responseText = await this.fetchTweetContent({
    agentId: this.agent.getAgentId(),
    userId: "twitter_client",
    roomId: "twitter",
    text: "<SYSTEM> Generate a new tweet to post on your timeline </SYSTEM>",
    type: "text"
  });

  const tweets = await sendThreadedTweet(this, responseText);
  
  // Store tweets in memory
  for (const tweet of tweets) {
    await storeTweetIfNotExists({
      id: tweet.id,
      text: tweet.text,
      userId: this.config.username,
      username: this.config.username,
      conversationId: tweet.conversationId,
      permanentUrl: tweet.permanentUrl
    });
  }
}

Mention Monitoring

Monitor and respond to mentions automatically:

// Check for new mentions
async checkInteractions() {
  const mentions = await this.getMentions();
  for (const mention of mentions) {
    if (mention.id <= this.lastCheckedTweetId) continue;
    await this.handleMention(mention);
    this.lastCheckedTweetId = mention.id;
  }
}

// Handle mention with agent
async handleMention(tweet) {
  const responseText = await this.fetchTweetContent({
    agentId: this.agent.getAgentId(),
    userId: `tw_user_${tweet.userId}`,
    roomId: tweet.conversationId || "twitter",
    text: `@${tweet.username}: ${tweet.text}`,
    type: "text"
  });

  const replies = await sendThreadedTweet(this, responseText, tweet.id);
}

Thread Management

Handle tweet threads and conversations:

// Split long content into tweets
function splitTweetContent(text, maxLength = 280) {
  if (text.length <= maxLength) return [text];
  
  const tweets = [];
  const sentences = text.match(/[^.!?]+[.!?]+/g) || [text];
  
  let currentTweet = '';
  for (const sentence of sentences) {
    if ((currentTweet + sentence).length <= maxLength) {
      currentTweet += sentence;
    } else {
      tweets.push(currentTweet.trim());
      currentTweet = sentence;
    }
  }
  
  if (currentTweet) tweets.push(currentTweet.trim());
  return tweets;
}

// Send threaded tweets
async function sendThreadedTweet(client, content, replyToId) {
  const tweets = [];
  const parts = splitTweetContent(content);
  let lastTweetId = replyToId;

  for (const part of parts) {
    const tweet = await client.sendTweet(part, lastTweetId);
    tweets.push(tweet);
    lastTweetId = tweet.id;
    await new Promise(resolve => setTimeout(resolve, 1000));
  }

  return tweets;
}

Memory Integration

Store tweets and maintain conversation context:

// Store tweet in database
async function storeTweetIfNotExists(tweet) {
  const exists = await prisma.tweet.count({
    where: { id: tweet.id }
  });

  if (!exists) {
    await prisma.tweet.create({
      data: {
        id: tweet.id,
        text: tweet.text,
        userId: tweet.userId,
        username: tweet.username,
        conversationId: tweet.conversationId,
        inReplyToId: tweet.inReplyToId,
        permanentUrl: tweet.permanentUrl
      }
    });
    return true;
  }
  return false;
}

// Get conversation thread
async function getTweetThread(conversationId) {
  return prisma.tweet.findMany({
    where: { conversationId },
    orderBy: { createdAt: "asc" }
  });
}

Best Practices

Rate Limiting

  • Use RequestQueue for API calls
  • Add delays between tweets
  • Handle API errors gracefully
  • Implement exponential backoff

Testing

  • Use dryRun mode for testing
  • Monitor tweet content
  • Test thread splitting
  • Verify mention handling