Skip to content

Tweet Operations

Operations for working with tweets (get, post, reply).

getTweet

Get a single tweet by ID with metrics and author information.

Description

Fetches a tweet by its ID. In hybrid mode, uses web scraping first for cost optimization, with automatic fallback to API if scraping fails. Returns full tweet details including text, metrics (likes, retweets, views), author information, and media.

Parameters

ParameterTypeRequiredDescription
tweetIdstringYesThe numeric ID of the tweet to fetch

Returns

TypeDescription
Promise<Tweet>Tweet object with full details

Tweet Interface

PropertyTypeDescription
idstringTweet ID
textstringTweet text content
authorIdstringAuthor's user ID
authorUsernamestringAuthor's username (without @)
authorNamestringAuthor's display name
createdAtstringISO 8601 timestamp
metricsTweetMetricsEngagement metrics
conversationIdstring | undefinedThread conversation ID
inReplyToUserIdstring | undefinedReply target user ID
referencedTweetsReferencedTweet[] | undefinedQuoted/replied tweets
entitiesTweetEntities | undefinedHashtags, mentions, URLs
mediaMedia[] | undefinedAttached media (photos, videos)

TweetMetrics Interface

PropertyTypeDescription
likeCountnumberNumber of likes
retweetCountnumberNumber of retweets
replyCountnumberNumber of replies
quoteCountnumberNumber of quote tweets
viewCountnumber | undefinedNumber of views (if available)
bookmarkCountnumber | undefinedNumber of bookmarks (if available)

Errors

Error TypeDescription
NotFoundErrorTweet not found or deleted
RateLimitErrorRate limit exceeded
CrawlerErrorScraping failed (check account status)
APIErrorTwitter API error

Examples

Basic Usage

typescript
import { XTwitterClient } from '@blockchain-web-services/bws-x-sdk-node';

const client = new XTwitterClient();

try {
  const tweet = await client.getTweet('1234567890123456789');

  console.log('Tweet:', tweet.text);
  console.log('Author:', tweet.authorUsername);
  console.log('Likes:', tweet.metrics.likeCount);
  console.log('Retweets:', tweet.metrics.retweetCount);
  console.log('Views:', tweet.metrics.viewCount);
} catch (error) {
  if (error instanceof NotFoundError) {
    console.error('Tweet not found');
  } else {
    console.error('Error fetching tweet:', error);
  }
}

Extract Hashtags and Mentions

typescript
const tweet = await client.getTweet('1234567890123456789');

// Extract hashtags
const hashtags = tweet.entities?.hashtags?.map(h => h.tag) || [];
console.log('Hashtags:', hashtags); // ['bitcoin', 'crypto']

// Extract mentions
const mentions = tweet.entities?.mentions?.map(m => m.username) || [];
console.log('Mentions:', mentions); // ['vitalikbuterin', 'elonmusk']

// Extract URLs
const urls = tweet.entities?.urls?.map(u => u.expanded_url) || [];
console.log('URLs:', urls);

Check if Tweet is Viral

typescript
const tweet = await client.getTweet('1234567890123456789');

const isViral =
  tweet.metrics.likeCount > 10000 ||
  tweet.metrics.retweetCount > 1000 ||
  tweet.metrics.viewCount! > 100000;

if (isViral) {
  console.log('🔥 Viral tweet detected!');
  console.log(`Engagement: ${tweet.metrics.likeCount} likes, ${tweet.metrics.retweetCount} RTs`);
}

Handle Media Attachments

typescript
const tweet = await client.getTweet('1234567890123456789');

if (tweet.media && tweet.media.length > 0) {
  tweet.media.forEach((media, index) => {
    console.log(`Media ${index + 1}:`);
    console.log(`  Type: ${media.type}`);
    console.log(`  URL: ${media.url}`);

    if (media.type === 'photo') {
      console.log(`  Photo URL: ${media.url}`);
    } else if (media.type === 'video') {
      console.log(`  Video URL: ${media.variants?.[0]?.url}`);
      console.log(`  Duration: ${media.duration_ms}ms`);
    }
  });
}

Check if Tweet is a Reply

typescript
const tweet = await client.getTweet('1234567890123456789');

if (tweet.inReplyToUserId) {
  console.log(`This is a reply to user: ${tweet.inReplyToUserId}`);

  // Find parent tweet reference
  const parentRef = tweet.referencedTweets?.find(ref => ref.type === 'replied_to');
  if (parentRef) {
    console.log(`Parent tweet ID: ${parentRef.id}`);

    // Fetch parent tweet
    const parentTweet = await client.getTweet(parentRef.id);
    console.log(`Original tweet: ${parentTweet.text}`);
  }
}

searchTweets

Search for tweets matching a query with filters.

Description

Searches for tweets matching a query string. Supports Twitter's search syntax and various filters. In hybrid mode, uses web scraping first for cost optimization, with automatic fallback to API if scraping fails. Returns an array of tweets matching the search criteria.

Parameters

ParameterTypeRequiredDescription
querystringYesSearch query (supports Twitter search operators)
optionsSearchTweetsOptionsNoSearch configuration options

SearchTweetsOptions

PropertyTypeRequiredDefaultDescription
maxResultsnumberNo20Maximum number of tweets to return (1-100)
filter'top' | 'latest' | 'people' | 'photos' | 'videos'No'latest'Filter type for results
startTimestringNo-ISO 8601 timestamp for oldest tweet
endTimestringNo-ISO 8601 timestamp for newest tweet

Returns

TypeDescription
Promise<Tweet[]>Array of tweet objects matching the query

Search Operators

Twitter search syntax supported:

  • blockchain - Search for keyword
  • "exact phrase" - Search for exact phrase
  • from:username - Tweets from specific user
  • to:username - Tweets mentioning specific user
  • #hashtag - Tweets with hashtag
  • min_faves:100 - Tweets with at least 100 likes
  • min_retweets:50 - Tweets with at least 50 retweets
  • lang:en - Tweets in specific language
  • -word - Exclude keyword

Errors

Error TypeDescription
ValidationErrorInvalid query or options
RateLimitErrorRate limit exceeded
CrawlerErrorScraping failed
APIErrorTwitter API error

Examples

typescript
import { XTwitterClient } from '@blockchain-web-services/bws-x-sdk-node';

const client = XTwitterClient.fromJsonEnv();

// Search for tweets about blockchain
const tweets = await client.searchTweets('blockchain', {
  maxResults: 20,
  filter: 'latest'
});

console.log(`Found ${tweets.length} tweets`);
tweets.forEach(tweet => {
  console.log(`@${tweet.author.username}: ${tweet.text}`);
  console.log(`  Likes: ${tweet.metrics.likes}, RTs: ${tweet.metrics.retweets}`);
});

Search with Filters

typescript
// Find top tweets about Web3
const topTweets = await client.searchTweets('Web3', {
  maxResults: 10,
  filter: 'top'
});

// Find latest tweets with photos
const photoTweets = await client.searchTweets('crypto', {
  maxResults: 15,
  filter: 'photos'
});

// Find tweets from specific time range
const recentTweets = await client.searchTweets('bitcoin', {
  maxResults: 50,
  startTime: '2024-01-01T00:00:00Z',
  endTime: '2024-01-31T23:59:59Z'
});

Search with Twitter Operators

typescript
// Tweets from specific user
const userTweets = await client.searchTweets('from:vitalikbuterin ethereum');

// Tweets with minimum engagement
const viralTweets = await client.searchTweets('AI min_faves:1000 min_retweets:100');

// Exact phrase search
const exactPhrases = await client.searchTweets('"Web3 development"');

// Exclude keywords
const filtered = await client.searchTweets('crypto -scam -spam');

// Hashtag search
const hashtagTweets = await client.searchTweets('#Bitcoin OR #BTC');

Find High-Engagement Tweets

typescript
const query = 'blockchain min_faves:500';
const tweets = await client.searchTweets(query, { maxResults: 20 });

const highEngagement = tweets.filter(t =>
  t.metrics.likes > 1000 || t.metrics.retweets > 200
);

console.log(`Found ${highEngagement.length} high-engagement tweets`);
highEngagement.forEach(tweet => {
  const score = tweet.metrics.likes + (tweet.metrics.retweets * 2);
  console.log(`Score: ${score} - ${tweet.text.substring(0, 50)}...`);
});

Monitor Brand Mentions

typescript
async function monitorBrand(brandName: string) {
  const tweets = await client.searchTweets(brandName, {
    maxResults: 50,
    filter: 'latest'
  });

  // Analyze sentiment (simple example)
  const positive = tweets.filter(t =>
    t.text.toLowerCase().includes('love') ||
    t.text.toLowerCase().includes('great') ||
    t.text.toLowerCase().includes('awesome')
  );

  const negative = tweets.filter(t =>
    t.text.toLowerCase().includes('hate') ||
    t.text.toLowerCase().includes('bad') ||
    t.text.toLowerCase().includes('terrible')
  );

  console.log(`Brand: ${brandName}`);
  console.log(`Total mentions: ${tweets.length}`);
  console.log(`Positive: ${positive.length} (${(positive.length / tweets.length * 100).toFixed(1)}%)`);
  console.log(`Negative: ${negative.length} (${(negative.length / tweets.length * 100).toFixed(1)}%)`);

  return { tweets, positive, negative };
}

await monitorBrand('YourBrand');

Find Tweets to Reply To

typescript
// Find relevant tweets to engage with
const query = 'Web3 OR blockchain OR crypto';
const tweets = await client.searchTweets(query, {
  maxResults: 30,
  filter: 'latest'
});

// Filter for quality opportunities
const opportunities = tweets.filter(tweet => {
  const hasGoodEngagement = tweet.metrics.likes > 10;
  const notTooPopular = tweet.metrics.likes < 1000; // Less competition
  const isRecent = (Date.now() - tweet.createdAt.getTime()) < 3600000; // Last hour
  const hasQuestions = tweet.text.includes('?');

  return hasGoodEngagement && notTooPopular && isRecent && hasQuestions;
});

console.log(`Found ${opportunities.length} opportunities to engage`);

for (const tweet of opportunities) {
  console.log(`\nOpportunity:`);
  console.log(`  @${tweet.author.username}: ${tweet.text}`);
  console.log(`  Engagement: ${tweet.metrics.likes} likes, ${tweet.metrics.retweets} RTs`);
  console.log(`  URL: https://x.com/${tweet.author.username}/status/${tweet.id}`);
}

Track Competitor Activity

typescript
async function trackCompetitors(competitors: string[]) {
  const allTweets: Tweet[] = [];

  for (const competitor of competitors) {
    const tweets = await client.searchTweets(`from:${competitor}`, {
      maxResults: 10,
      filter: 'latest'
    });
    allTweets.push(...tweets);
  }

  // Sort by engagement
  allTweets.sort((a, b) => {
    const scoreA = a.metrics.likes + a.metrics.retweets * 2;
    const scoreB = b.metrics.likes + b.metrics.retweets * 2;
    return scoreB - scoreA;
  });

  console.log('Top competitor tweets:');
  allTweets.slice(0, 10).forEach((tweet, i) => {
    const score = tweet.metrics.likes + tweet.metrics.retweets * 2;
    console.log(`${i + 1}. [@${tweet.author.username}] Score: ${score}`);
    console.log(`   ${tweet.text.substring(0, 80)}...`);
  });
}

await trackCompetitors(['competitor1', 'competitor2', 'competitor3']);

Rate Limits

  • Crawler mode: No rate limits (uses web scraping)
  • API mode: 450 requests per 15 minutes (standard Twitter API v2 limit)
  • Hybrid mode: Falls back to API when crawler fails

Best Practices

  1. Use Specific Queries - More specific queries return better results
  2. Filter by Engagement - Use min_faves and min_retweets operators
  3. Limit Results - Request only what you need to reduce latency
  4. Handle Empty Results - Not all searches will return results
  5. Use Crawler Mode - Saves API quota for posting operations
  6. Combine with getUserTweets - Get full context for interesting authors

postReply

Post a reply to an existing tweet.

Description

Posts a reply to a specified tweet using the Twitter API. Requires API credentials. The reply will appear in the tweet's conversation thread and will notify the original author.

Note: This operation always uses the Twitter API, regardless of the configured mode, as posting requires write permissions.

Parameters

ParameterTypeRequiredDescription
tweetIdstringYesThe ID of the tweet to reply to
textstringYesReply text content (max 280 characters)

Returns

TypeDescription
Promise<Tweet>The newly created reply tweet object

Constraints

  • Text length: 1-280 characters
  • Media attachments: Not currently supported
  • Polls: Not supported
  • Rate limits: Twitter API v2 rate limits apply

Errors

Error TypeDescription
ValidationErrorText exceeds 280 characters or is empty
NotFoundErrorParent tweet not found
RateLimitErrorAPI rate limit exceeded
APIErrorTwitter API error (auth failure, permissions, etc.)
ConfigurationErrorAPI credentials not configured

Examples

Basic Reply

typescript
import { XTwitterClient } from '@blockchain-web-services/bws-x-sdk-node';

const client = new XTwitterClient();

try {
  const reply = await client.postReply(
    '1234567890123456789',
    'Great insight! Thanks for sharing 🚀'
  );

  console.log('Reply posted successfully!');
  console.log('Reply ID:', reply.id);
  console.log('Reply URL:', `https://x.com/${reply.authorUsername}/status/${reply.id}`);
} catch (error) {
  console.error('Error posting reply:', error.message);
}

Reply with Mentions

typescript
// Mention users in the reply
const reply = await client.postReply(
  '1234567890123456789',
  '@vitalikbuterin @elonmusk What do you think about this?'
);

console.log('Reply with mentions posted!');

Reply with Hashtags

typescript
const reply = await client.postReply(
  '1234567890123456789',
  'Totally agree! #Bitcoin #Crypto #Web3 🔥'
);

Validate Before Posting

typescript
const replyText = 'This is my reply text...';

// Validate text length
if (replyText.length === 0) {
  console.error('Reply text cannot be empty');
} else if (replyText.length > 280) {
  console.error(`Reply too long: ${replyText.length}/280 characters`);
} else {
  const reply = await client.postReply('1234567890123456789', replyText);
  console.log('Reply posted successfully!');
}

Auto-Reply to Mentions (Bot Example)

typescript
import { XTwitterClient } from '@blockchain-web-services/bws-x-sdk-node';

const client = new XTwitterClient();

async function autoReplyBot() {
  // Search for tweets mentioning your bot
  const mentions = await client.searchTweets('@yourbotname', {
    maxResults: 10,
  });

  for (const tweet of mentions) {
    // Skip if already replied
    if (await hasAlreadyReplied(tweet.id)) {
      continue;
    }

    try {
      // Generate reply
      const replyText = generateReply(tweet.text);

      // Post reply
      const reply = await client.postReply(tweet.id, replyText);
      console.log(`Replied to ${tweet.authorUsername}: ${reply.id}`);

      // Mark as replied
      await markAsReplied(tweet.id);

      // Rate limit protection - wait 5 seconds between replies
      await new Promise(resolve => setTimeout(resolve, 5000));
    } catch (error) {
      console.error(`Failed to reply to ${tweet.id}:`, error.message);
    }
  }
}

function generateReply(tweetText: string): string {
  // Simple example - you'd want more sophisticated logic
  if (tweetText.toLowerCase().includes('help')) {
    return 'Hi! Here are some helpful resources: https://docs.example.com';
  }
  return 'Thanks for reaching out! 👋';
}

// Run bot every 5 minutes
setInterval(autoReplyBot, 5 * 60 * 1000);

Error Handling

typescript
try {
  const reply = await client.postReply(
    '1234567890123456789',
    'My reply'
  );
  console.log('Success:', reply.id);
} catch (error) {
  if (error instanceof RateLimitError) {
    console.error('Rate limited! Wait before retrying.');
    console.error('Reset at:', error.rateLimit.reset);
  } else if (error instanceof NotFoundError) {
    console.error('Tweet not found or deleted');
  } else if (error instanceof ConfigurationError) {
    console.error('API credentials not configured');
  } else {
    console.error('Unexpected error:', error);
  }
}

Thread Multiple Replies

typescript
// Reply to a tweet
const reply1 = await client.postReply(
  '1234567890123456789',
  'First, let me address point A...'
);

// Wait a moment
await new Promise(resolve => setTimeout(resolve, 2000));

// Reply to your own reply to create a thread
const reply2 = await client.postReply(
  reply1.id,
  'Second, regarding point B...'
);

await new Promise(resolve => setTimeout(resolve, 2000));

// Continue the thread
const reply3 = await client.postReply(
  reply2.id,
  'Finally, my conclusion is...'
);

console.log('Thread created!');
console.log('View thread:', `https://x.com/${reply1.authorUsername}/status/${reply1.id}`);

Rate Limits

Twitter API v2 rate limits for posting:

  • User context: 50 requests per 24 hours per user
  • App context: 300 requests per 3 hours

The SDK does not automatically handle post rate limits. You should implement your own rate limiting logic for posting operations.

Best Practices

  1. Validate Input - Check text length before posting
  2. Handle Errors - Always wrap in try/catch
  3. Rate Limit Protection - Add delays between posts
  4. Avoid Spam - Don't auto-reply to everything
  5. Use Webhooks - Monitor API rate limits
  6. Test First - Test with test accounts before production

Released under the MIT License.