User Profile Operations
Operations for working with user profiles and followers.
getProfile
Get a user's profile information by username.
Description
Fetches complete profile information for a Twitter/X user. In hybrid mode, uses web scraping first for cost optimization, with automatic fallback to API. Returns profile details including bio, metrics (followers, following), verification status, and profile media.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
username | string | Yes | Username (with or without @, e.g., 'elonmusk' or '@elonmusk') |
Returns
| Type | Description |
|---|---|
Promise<UserProfile> | User profile object with full details |
UserProfile Interface
| Property | Type | Description |
|---|---|---|
id | string | User ID |
username | string | Username (without @) |
name | string | Display name |
bio | string | undefined | Biography/description |
location | string | undefined | Location text |
website | string | undefined | Website URL |
joinedDate | string | undefined | Account creation date |
verified | boolean | Verification status |
metrics | UserMetrics | Follower/following counts |
profileImageUrl | string | undefined | Profile picture URL |
bannerImageUrl | string | undefined | Banner image URL |
UserMetrics Interface
| Property | Type | Description |
|---|---|---|
followersCount | number | Number of followers |
followingCount | number | Number of accounts following |
tweetCount | number | Total tweets |
listedCount | number | undefined | Number of lists user is on |
Examples
Basic Usage
import { XTwitterClient } from '@blockchain-web-services/bws-x-sdk-node';
const client = new XTwitterClient();
const profile = await client.getProfile('vitalikbuterin');
console.log('Name:', profile.name);
console.log('Bio:', profile.bio);
console.log('Followers:', profile.metrics.followersCount);
console.log('Verified:', profile.verified);Check if User is Influential
const profile = await client.getProfile('elonmusk');
const isInfluential =
profile.metrics.followersCount > 1000000 &&
profile.verified;
if (isInfluential) {
console.log(`${profile.name} is a highly influential account!`);
console.log(`${profile.metrics.followersCount.toLocaleString()} followers`);
}Compare Multiple Profiles
const usernames = ['vitalikbuterin', 'gavinwood', 'ethereum'];
const profiles = await Promise.all(
usernames.map(username => client.getProfile(username))
);
profiles.sort((a, b) => b.metrics.followersCount - a.metrics.followersCount);
console.log('Sorted by followers:');
profiles.forEach((profile, index) => {
console.log(`${index + 1}. ${profile.name}: ${profile.metrics.followersCount.toLocaleString()}`);
});getUserTweets
Get recent tweets from a user's timeline.
Description
Fetches tweets from a specific user's timeline with optional filtering. In hybrid mode, uses web scraping first for cost optimization, with automatic fallback to API. Supports excluding replies and retweets, and time-based filtering.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
username | string | Yes | Twitter username (with or without @) |
options | GetUserTweetsOptions | No | Timeline fetch options |
GetUserTweetsOptions Interface
| Property | Type | Default | Description |
|---|---|---|---|
maxResults | number | 20 | Maximum tweets to return (1-100) |
excludeReplies | boolean | false | Exclude reply tweets |
excludeRetweets | boolean | false | Exclude retweets |
startTime | string | - | ISO 8601 timestamp for earliest tweet |
endTime | string | - | ISO 8601 timestamp for latest tweet |
Returns
| Type | Description |
|---|---|
Promise<Tweet[]> | Array of tweets from user timeline |
Examples
Get Recent Tweets
import { XTwitterClient } from '@blockchain-web-services/bws-x-sdk-node';
const client = XTwitterClient.fromJsonEnv();
const tweets = await client.getUserTweets('vitalikbuterin', {
maxResults: 20,
filter: 'latest'
});
tweets.forEach(tweet => {
console.log(`[${tweet.createdAt}] ${tweet.text}`);
console.log(`Likes: ${tweet.metrics.likes}, Retweets: ${tweet.metrics.retweets}\n`);
});Get Only Original Tweets (No Replies/Retweets)
const originalTweets = await client.getUserTweets('elonmusk', {
maxResults: 50,
excludeReplies: true,
excludeRetweets: true
});
console.log(`Found ${originalTweets.length} original tweets`);Monitor User Activity
// Check for new tweets from a user
const recentTweets = await client.getUserTweets('ethereum', {
maxResults: 10,
excludeRetweets: true
});
const latestTweet = recentTweets[0];
console.log('Latest tweet from @ethereum:');
console.log(latestTweet.text);
console.log(`Posted: ${latestTweet.createdAt}`);Analyze Tweet Performance
const tweets = await client.getUserTweets('balajis', {
maxResults: 100,
excludeReplies: true,
excludeRetweets: true
});
// Calculate average engagement
const totalLikes = tweets.reduce((sum, t) => sum + t.metrics.likes, 0);
const totalRetweets = tweets.reduce((sum, t) => sum + t.metrics.retweets, 0);
console.log(`Average likes per tweet: ${(totalLikes / tweets.length).toFixed(1)}`);
console.log(`Average retweets per tweet: ${(totalRetweets / tweets.length).toFixed(1)}`);
// Find top performing tweet
const topTweet = tweets.reduce((best, t) =>
(t.metrics.likes + t.metrics.retweets) > (best.metrics.likes + best.metrics.retweets) ? t : best
);
console.log('\nTop performing tweet:');
console.log(topTweet.text);
console.log(`Engagement: ${topTweet.metrics.likes} likes, ${topTweet.metrics.retweets} retweets`);Track Content Strategy
// Analyze what type of content performs best
const allTweets = await client.getUserTweets('naval', {
maxResults: 100,
excludeRetweets: true
});
const threadsCount = allTweets.filter(t => t.text.includes('1/')).length;
const questionsCount = allTweets.filter(t => t.text.includes('?')).length;
const repliesCount = allTweets.filter(t => t.inReplyToStatusId).length;
const originalCount = allTweets.length - repliesCount;
console.log('Content breakdown:');
console.log(`- Original tweets: ${originalCount}`);
console.log(`- Replies: ${repliesCount}`);
console.log(`- Threads: ${threadsCount}`);
console.log(`- Questions: ${questionsCount}`);Operating Modes
Crawler Mode (Default)
- Uses web scraping via Playwright
- Scrolls user timeline to load tweets
- No rate limits or API costs
- Returns tweets in chronological order
- Respects excludeReplies/excludeRetweets filters
API Mode
- Uses Twitter API v2
userTimelineendpoint - Returns up to 100 tweets per request
- Rate limits apply (900 requests per 15 minutes)
- More reliable but costs API quota
Hybrid Mode
- Tries crawler mode first
- Falls back to API on failure
- Best for production use
Best Practices
- Filter Efficiently: Use
excludeRepliesandexcludeRetweetsto get only the content you need - Respect Rate Limits: In API mode, be mindful of the 900 requests per 15 minutes limit
- Handle Pagination: For users with many tweets, make multiple requests with time-based filtering
- Cache Results: Store fetched tweets to avoid redundant requests
- Monitor Errors: Handle cases where users have private or protected accounts
Related
- searchTweets - Search for tweets by query
- getProfile - Get user profile information
getFollowing
Get list of users that a specific account follows.
Description
Fetches the "following" list for a user (accounts they follow). In hybrid mode, uses web scraping first for cost optimization, with automatic fallback to API. Useful for network analysis, finding similar accounts, and understanding user interests.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
username | string | Yes | Twitter username (with or without @) |
options | GetFollowingOptions | No | Fetch options |
GetFollowingOptions Interface
| Property | Type | Default | Description |
|---|---|---|---|
maxResults | number | 100 | Maximum users to return (1-1000) |
Returns
| Type | Description |
|---|---|
Promise<UserProfile[]> | Array of user profiles that the account follows |
Examples
Get Following List
import { XTwitterClient } from '@blockchain-web-services/bws-x-sdk-node';
const client = XTwitterClient.fromJsonEnv();
const following = await client.getFollowing('elonmusk', {
maxResults: 100
});
console.log(`@elonmusk follows ${following.length} accounts:`);
following.forEach(user => {
console.log(`- ${user.name} (@${user.username})`);
console.log(` ${user.bio?.substring(0, 100)}...`);
});Find Verified Accounts Someone Follows
const following = await client.getFollowing('vitalikbuterin', {
maxResults: 500
});
const verifiedAccounts = following.filter(user => user.verified);
console.log(`Verified accounts @vitalikbuterin follows: ${verifiedAccounts.length}`);
verifiedAccounts.forEach(user => {
console.log(`✓ ${user.name} (@${user.username}) - ${user.metrics.followersCount.toLocaleString()} followers`);
});Network Analysis - Common Follows
// Find accounts that two users both follow
const user1Following = await client.getFollowing('vitalikbuterin', {
maxResults: 500
});
const user2Following = await client.getFollowing('gavinwood', {
maxResults: 500
});
const user1Usernames = new Set(user1Following.map(u => u.username));
const commonFollows = user2Following.filter(u => user1Usernames.has(u.username));
console.log(`Accounts both @vitalikbuterin and @gavinwood follow: ${commonFollows.length}`);
commonFollows.forEach(user => {
console.log(`- ${user.name} (@${user.username})`);
});Discover Similar Accounts
// Find accounts similar to a target user based on who they follow
const targetFollowing = await client.getFollowing('AndreCronjeTech', {
maxResults: 200
});
// Get category breakdown
const categories = {
defi: targetFollowing.filter(u =>
u.bio?.toLowerCase().includes('defi') ||
u.bio?.toLowerCase().includes('finance')
),
developer: targetFollowing.filter(u =>
u.bio?.toLowerCase().includes('developer') ||
u.bio?.toLowerCase().includes('engineer')
),
founder: targetFollowing.filter(u =>
u.bio?.toLowerCase().includes('founder') ||
u.bio?.toLowerCase().includes('ceo')
)
};
console.log('Interest breakdown:');
console.log(`- DeFi: ${categories.defi.length}`);
console.log(`- Developers: ${categories.developer.length}`);
console.log(`- Founders: ${categories.founder.length}`);Find Influential Accounts in Network
const following = await client.getFollowing('balajis', {
maxResults: 500
});
// Sort by follower count
following.sort((a, b) => b.metrics.followersCount - a.metrics.followersCount);
console.log('Top 10 most influential accounts @balajis follows:\n');
following.slice(0, 10).forEach((user, index) => {
console.log(`${index + 1}. ${user.name} (@${user.username})`);
console.log(` Followers: ${user.metrics.followersCount.toLocaleString()}`);
console.log(` ${user.bio?.substring(0, 80)}...\n`);
});Track Following Changes Over Time
// Store following list and compare later to detect new follows
const currentFollowing = await client.getFollowing('ethereum', {
maxResults: 1000
});
const currentUsernames = currentFollowing.map(u => u.username);
// Later, check for new follows
const newFollowing = await client.getFollowing('ethereum', {
maxResults: 1000
});
const newAccounts = newFollowing.filter(u => !currentUsernames.includes(u.username));
if (newAccounts.length > 0) {
console.log(`@ethereum started following ${newAccounts.length} new accounts:`);
newAccounts.forEach(user => {
console.log(`- ${user.name} (@${user.username})`);
});
}Operating Modes
Crawler Mode (Default)
- Uses web scraping via Playwright
- Scrolls following list to load users
- No rate limits or API costs
- Returns users in order displayed on profile
API Mode
- Uses Twitter API v2
followingendpoint - Returns up to 1000 users per request
- Rate limits apply (15 requests per 15 minutes)
- More reliable but costs API quota
Hybrid Mode
- Tries crawler mode first
- Falls back to API on failure
- Best for production use
Best Practices
- Batch Processing: If analyzing multiple users, implement delays between requests
- Cache Results: Following lists change slowly, cache for hours or days
- Respect Privacy: Some users have protected accounts - handle gracefully
- Network Analysis: Combine with
getFollowersfor full network mapping - Monitor API Quotas: In API mode, track your rate limit usage
Use Cases
- Influencer Discovery: Find who thought leaders follow
- Network Mapping: Build social graphs for Web3 communities
- Content Curation: Discover quality accounts through trusted users
- Competitive Analysis: See who competitors are following
- Community Building: Identify potential collaborators and partners
Related
- getProfile - Get user profile information
- getFollowers - Get user's followers list
getFollowers
Get a list of users following a specific account.
Description
Fetches the followers list for a user. Uses Twitter API (requires API credentials). Returns paginated results with user IDs and optional profile details.
Note: This operation requires Twitter API v2 credentials and counts against your API quota.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
username | string | Yes | Username to get followers for |
maxResults | number | No | Maximum number of results (default: 100, max: 1000) |
paginationToken | string | No | Token for pagination (from previous response) |
Returns
| Type | Description |
|---|---|
Promise<FollowersResponse> | Followers list with pagination data |
FollowersResponse Interface
| Property | Type | Description |
|---|---|---|
data | UserProfile[] | Array of follower profiles |
meta | object | Pagination metadata |
meta.result_count | number | Number of results in this response |
meta.next_token | string | undefined | Token for next page (if more results) |
Examples
Get First 100 Followers
const followers = await client.getFollowers('ethereum');
console.log(`${followers.meta.result_count} followers`);
followers.data.forEach(follower => {
console.log(`- ${follower.name} (@${follower.username})`);
});Paginate Through All Followers
let allFollowers: UserProfile[] = [];
let nextToken: string | undefined = undefined;
do {
const response = await client.getFollowers('vitalikbuterin', {
maxResults: 1000,
paginationToken: nextToken,
});
allFollowers = allFollowers.concat(response.data);
nextToken = response.meta.next_token;
console.log(`Fetched ${allFollowers.length} followers so far...`);
// Rate limit protection
if (nextToken) {
await new Promise(resolve => setTimeout(resolve, 1000));
}
} while (nextToken);
console.log(`Total followers: ${allFollowers.length}`);Find Verified Followers
const response = await client.getFollowers('ethereum', { maxResults: 1000 });
const verifiedFollowers = response.data.filter(user => user.verified);
console.log(`Verified followers: ${verifiedFollowers.length}`);
verifiedFollowers.forEach(user => {
console.log(`✓ ${user.name} (@${user.username}) - ${user.metrics.followersCount} followers`);
});Related
- searchUsers - Search for users
- identifyKOLs - Find influential users
enrichTweetAuthors
Enrich tweet author profiles with complete bio and follower data.
Description
Fetches full profile data (bio, followers, following counts) for tweet authors. This method solves a common limitation where searchTweets() returns tweets with incomplete author data - Twitter's SearchTimeline API doesn't include author bio or follower metrics in search results.
The method automatically:
- Extracts unique usernames from tweets (deduplicates authors)
- Fetches full profiles concurrently in batches
- Enriches tweet author objects with bio, followers, following
- Handles errors gracefully (partial enrichment)
- Uses HTML parsing fallback if GraphQL fails
Use Case: After searching for tweets, you often need complete author profile data to classify, filter, or analyze the accounts. Instead of manually calling getProfile() for each unique author, use enrichTweetAuthors() for efficient batch enrichment.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
tweets | Tweet[] | Yes | Array of tweets to enrich |
options | EnrichOptions | No | Enrichment options |
EnrichOptions Interface
| Property | Type | Default | Description |
|---|---|---|---|
concurrency | number | 3 | Number of profiles to fetch in parallel (1-10) |
Returns
| Type | Description |
|---|---|
Promise<Tweet[]> | Same tweets with enriched author data |
Examples
Basic Usage
import { XTwitterClient } from '@blockchain-web-services/bws-x-sdk-node';
const client = new XTwitterClient({ mode: 'crawler' });
// Search for tweets
const tweets = await client.searchTweets('blockchain credentials', {
maxResults: 20
});
// Enrich with full profile data
const enrichedTweets = await client.enrichTweetAuthors(tweets);
// Now you have bio and follower counts!
for (const tweet of enrichedTweets) {
console.log(`@${tweet.author.username}:`, tweet.author.bio);
console.log(`Followers: ${tweet.author.followers}`);
}Pre-Filter Before Enrichment (Recommended)
// Search for many tweets
const tweets = await client.searchTweets('university blockchain', {
maxResults: 50
});
// Pre-filter by name/username (free - no API calls)
const candidates = tweets.filter(t =>
t.author.name.toLowerCase().match(/university|college|edu/)
);
console.log(`Filtered ${tweets.length} tweets → ${candidates.length} candidates`);
// Only enrich promising candidates (5-10 instead of 50)
const enriched = await client.enrichTweetAuthors(candidates);
// Full classification with bio/followers
const classified = enriched.map(t => ({
username: t.author.username,
type: t.author.bio.includes('university') ? 'institution' : 'personal',
engagement: t.author.followers > 1000 ? 'high' : 'low',
}));Custom Concurrency Control
// Fetch 5 profiles at a time for faster enrichment
const enriched = await client.enrichTweetAuthors(tweets, {
concurrency: 5
});Error Handling & Partial Enrichment
try {
const enriched = await client.enrichTweetAuthors(tweets);
// Check which profiles were successfully enriched
const withBio = enriched.filter(t => t.author.bio !== '');
const withoutBio = enriched.filter(t => t.author.bio === '');
console.log(`Successfully enriched: ${withBio.length}/${enriched.length}`);
if (withoutBio.length > 0) {
console.log('Failed to enrich:', withoutBio.map(t => t.author.username));
}
// Continue with partial results
const classified = withBio.map(t => classifyAccount(t.author));
} catch (error) {
console.error('Enrichment failed:', error);
}Account Classification Example
const tweets = await client.searchTweets('digital credentials', {
maxResults: 30
});
// Pre-filter institutional candidates
const candidates = tweets.filter(t => {
const name = t.author.name.toLowerCase();
return name.includes('university') ||
name.includes('college') ||
name.includes('institute');
});
// Enrich with full data
const enriched = await client.enrichTweetAuthors(candidates);
// Classify based on bio and metrics
const classified = enriched.map(tweet => {
const { author } = tweet;
const bio = author.bio.toLowerCase();
let type = 'unknown';
let confidence = 0.5;
if (bio.includes('university')) {
type = 'university';
confidence = 0.9;
} else if (bio.includes('college')) {
type = 'college';
confidence = 0.85;
} else if (author.followers > 5000) {
type = 'influential_individual';
confidence = 0.7;
}
return {
username: author.username,
type,
confidence,
followers: author.followers,
};
});
console.log('Classification results:', classified);Performance Optimization
// Get unique authors to estimate enrichment cost
const uniqueAuthors = [...new Set(tweets.map(t => t.author.username))];
console.log(`Need to enrich ${uniqueAuthors.length} unique authors`);
// Estimated time: ~2s per profile with concurrency=3
const estimatedTime = (uniqueAuthors.length / 3) * 2;
console.log(`Estimated enrichment time: ${estimatedTime}s`);
// Adjust concurrency based on size
const concurrency = uniqueAuthors.length > 20 ? 5 : 3;
const enriched = await client.enrichTweetAuthors(tweets, {
concurrency
});Performance Characteristics
| Metric | Value |
|---|---|
| Default Concurrency | 3 profiles in parallel |
| Time per Profile | ~2 seconds (with GraphQL) |
| Deduplication | Automatic (unique usernames) |
| Error Handling | Graceful (partial results) |
| Fallback | HTML parsing if GraphQL fails |
Performance Tips
- Pre-filter candidates to reduce the number of profiles to enrich
- Use concurrency control to balance speed vs rate limits
- Handle partial enrichment gracefully (some profiles may fail)
- Cache enriched profiles to avoid re-fetching
- Monitor unique authors - multiple tweets from same author only fetch once
Common Patterns
Discovery Workflow
// 1. Search for relevant tweets
const tweets = await client.searchTweets('blockchain education', {
maxResults: 100
});
// 2. Pre-filter by keywords
const candidates = tweets.filter(t =>
t.author.name.match(/university|professor|researcher/i)
);
// 3. Enrich promising candidates
const enriched = await client.enrichTweetAuthors(candidates);
// 4. Classify and save
const institutions = enriched.filter(t =>
t.author.bio.includes('university') &&
t.author.followers > 500
);
console.log(`Found ${institutions.length} university accounts`);Batch Processing
// Process large tweet sets in chunks
const allTweets = await client.searchTweets('defi', { maxResults: 200 });
const CHUNK_SIZE = 20;
const enrichedChunks: Tweet[] = [];
for (let i = 0; i < allTweets.length; i += CHUNK_SIZE) {
const chunk = allTweets.slice(i, i + CHUNK_SIZE);
const enriched = await client.enrichTweetAuthors(chunk);
enrichedChunks.push(...enriched);
console.log(`Enriched ${enrichedChunks.length}/${allTweets.length} tweets`);
// Rate limit protection
await new Promise(resolve => setTimeout(resolve, 2000));
}Operating Modes
Crawler Mode (Recommended)
- Uses web scraping via Playwright
- No rate limits or API costs
- ~2s per profile with GraphQL interception
- HTML parsing fallback if GraphQL fails
API Mode
- Uses Twitter API v2
getUserByUsernameendpoint - Rate limits apply (900 requests per 15 minutes)
- Faster but costs API quota
- More reliable response times
Hybrid Mode
- Tries crawler mode first
- Falls back to API on failure
- Best for production use
Best Practices
- Always pre-filter before enrichment to minimize profile fetches
- Use appropriate concurrency (3 for small batches, 5-7 for large)
- Handle partial results - some profiles may be suspended/deleted
- Cache enriched data - profiles change slowly
- Monitor performance - log unique authors and timing
- Respect rate limits - add delays between large batches
Limitations & Considerations
- Twitter API Limitation:
searchTweets()doesn't return bio/followers in results - Enrichment Cost: Each unique author requires a profile fetch (~2s)
- Suspended Accounts: Deleted/suspended accounts will fail gracefully
- Protected Accounts: Private accounts may return limited data
- Rate Limits: In API mode, watch for rate limit errors
Use Cases
- Account Discovery: Find and classify institutional accounts
- Influencer Analysis: Identify high-follower accounts in search results
- Content Curation: Filter authors by bio keywords and metrics
- Network Mapping: Understand who's tweeting about specific topics
- Lead Generation: Find relevant business accounts with complete data
- Research: Analyze author demographics from search results
When to Use enrichTweetAuthors
✅ Use when:
- You need bio or follower data for author classification
- You're filtering/scoring accounts based on profile metrics
- You're building account discovery workflows
- You need complete profile data for analysis
❌ Skip when:
- You only need username and display name (already in search results)
- You're processing thousands of tweets (consider sampling)
- You don't need to analyze the authors
- Your classification works without bio/followers
Related
- searchTweets - Search for tweets (returns basic author data)
- getProfile - Get single user profile
- identifyKOLs - Analyze user influence