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
| Parameter | Type | Required | Description |
|---|---|---|---|
tweetId | string | Yes | The numeric ID of the tweet to fetch |
Returns
| Type | Description |
|---|---|
Promise<Tweet> | Tweet object with full details |
Tweet Interface
| Property | Type | Description |
|---|---|---|
id | string | Tweet ID |
text | string | Tweet text content |
authorId | string | Author's user ID |
authorUsername | string | Author's username (without @) |
authorName | string | Author's display name |
createdAt | string | ISO 8601 timestamp |
metrics | TweetMetrics | Engagement metrics |
conversationId | string | undefined | Thread conversation ID |
inReplyToUserId | string | undefined | Reply target user ID |
referencedTweets | ReferencedTweet[] | undefined | Quoted/replied tweets |
entities | TweetEntities | undefined | Hashtags, mentions, URLs |
media | Media[] | undefined | Attached media (photos, videos) |
TweetMetrics Interface
| Property | Type | Description |
|---|---|---|
likeCount | number | Number of likes |
retweetCount | number | Number of retweets |
replyCount | number | Number of replies |
quoteCount | number | Number of quote tweets |
viewCount | number | undefined | Number of views (if available) |
bookmarkCount | number | undefined | Number of bookmarks (if available) |
Errors
| Error Type | Description |
|---|---|
NotFoundError | Tweet not found or deleted |
RateLimitError | Rate limit exceeded |
CrawlerError | Scraping failed (check account status) |
APIError | Twitter API error |
Examples
Basic Usage
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
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
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
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
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
| Parameter | Type | Required | Description |
|---|---|---|---|
query | string | Yes | Search query (supports Twitter search operators) |
options | SearchTweetsOptions | No | Search configuration options |
SearchTweetsOptions
| Property | Type | Required | Default | Description |
|---|---|---|---|---|
maxResults | number | No | 20 | Maximum number of tweets to return (1-100) |
filter | 'top' | 'latest' | 'people' | 'photos' | 'videos' | No | 'latest' | Filter type for results |
startTime | string | No | - | ISO 8601 timestamp for oldest tweet |
endTime | string | No | - | ISO 8601 timestamp for newest tweet |
Returns
| Type | Description |
|---|---|
Promise<Tweet[]> | Array of tweet objects matching the query |
Search Operators
Twitter search syntax supported:
blockchain- Search for keyword"exact phrase"- Search for exact phrasefrom:username- Tweets from specific userto:username- Tweets mentioning specific user#hashtag- Tweets with hashtagmin_faves:100- Tweets with at least 100 likesmin_retweets:50- Tweets with at least 50 retweetslang:en- Tweets in specific language-word- Exclude keyword
Errors
| Error Type | Description |
|---|---|
ValidationError | Invalid query or options |
RateLimitError | Rate limit exceeded |
CrawlerError | Scraping failed |
APIError | Twitter API error |
Examples
Basic Search
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
// 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
// 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
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
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
// 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
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
- Use Specific Queries - More specific queries return better results
- Filter by Engagement - Use
min_favesandmin_retweetsoperators - Limit Results - Request only what you need to reduce latency
- Handle Empty Results - Not all searches will return results
- Use Crawler Mode - Saves API quota for posting operations
- Combine with getUserTweets - Get full context for interesting authors
Related
- getUserTweets - Get tweets from specific user
- postReply - Reply to found tweets
- Operating Modes - Understand crawler vs API modes
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
| Parameter | Type | Required | Description |
|---|---|---|---|
tweetId | string | Yes | The ID of the tweet to reply to |
text | string | Yes | Reply text content (max 280 characters) |
Returns
| Type | Description |
|---|---|
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 Type | Description |
|---|---|
ValidationError | Text exceeds 280 characters or is empty |
NotFoundError | Parent tweet not found |
RateLimitError | API rate limit exceeded |
APIError | Twitter API error (auth failure, permissions, etc.) |
ConfigurationError | API credentials not configured |
Examples
Basic Reply
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
// 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
const reply = await client.postReply(
'1234567890123456789',
'Totally agree! #Bitcoin #Crypto #Web3 🔥'
);Validate Before Posting
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)
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
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
// 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
- Validate Input - Check text length before posting
- Handle Errors - Always wrap in try/catch
- Rate Limit Protection - Add delays between posts
- Avoid Spam - Don't auto-reply to everything
- Use Webhooks - Monitor API rate limits
- Test First - Test with test accounts before production
Related
- searchTweets - Find tweets to reply to
- Webhook Notifications - Monitor rate limits