Skip to content

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

ParameterTypeRequiredDescription
usernamestringYesUsername (with or without @, e.g., 'elonmusk' or '@elonmusk')

Returns

TypeDescription
Promise<UserProfile>User profile object with full details

UserProfile Interface

PropertyTypeDescription
idstringUser ID
usernamestringUsername (without @)
namestringDisplay name
biostring | undefinedBiography/description
locationstring | undefinedLocation text
websitestring | undefinedWebsite URL
joinedDatestring | undefinedAccount creation date
verifiedbooleanVerification status
metricsUserMetricsFollower/following counts
profileImageUrlstring | undefinedProfile picture URL
bannerImageUrlstring | undefinedBanner image URL

UserMetrics Interface

PropertyTypeDescription
followersCountnumberNumber of followers
followingCountnumberNumber of accounts following
tweetCountnumberTotal tweets
listedCountnumber | undefinedNumber of lists user is on

Examples

Basic Usage

typescript
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

typescript
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

typescript
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

ParameterTypeRequiredDescription
usernamestringYesTwitter username (with or without @)
optionsGetUserTweetsOptionsNoTimeline fetch options

GetUserTweetsOptions Interface

PropertyTypeDefaultDescription
maxResultsnumber20Maximum tweets to return (1-100)
excludeRepliesbooleanfalseExclude reply tweets
excludeRetweetsbooleanfalseExclude retweets
startTimestring-ISO 8601 timestamp for earliest tweet
endTimestring-ISO 8601 timestamp for latest tweet

Returns

TypeDescription
Promise<Tweet[]>Array of tweets from user timeline

Examples

Get Recent Tweets

typescript
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)

typescript
const originalTweets = await client.getUserTweets('elonmusk', {
  maxResults: 50,
  excludeReplies: true,
  excludeRetweets: true
});

console.log(`Found ${originalTweets.length} original tweets`);

Monitor User Activity

typescript
// 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

typescript
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

typescript
// 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 userTimeline endpoint
  • 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

  1. Filter Efficiently: Use excludeReplies and excludeRetweets to get only the content you need
  2. Respect Rate Limits: In API mode, be mindful of the 900 requests per 15 minutes limit
  3. Handle Pagination: For users with many tweets, make multiple requests with time-based filtering
  4. Cache Results: Store fetched tweets to avoid redundant requests
  5. Monitor Errors: Handle cases where users have private or protected accounts

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

ParameterTypeRequiredDescription
usernamestringYesTwitter username (with or without @)
optionsGetFollowingOptionsNoFetch options

GetFollowingOptions Interface

PropertyTypeDefaultDescription
maxResultsnumber100Maximum users to return (1-1000)

Returns

TypeDescription
Promise<UserProfile[]>Array of user profiles that the account follows

Examples

Get Following List

typescript
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

typescript
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

typescript
// 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

typescript
// 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

typescript
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

typescript
// 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 following endpoint
  • 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

  1. Batch Processing: If analyzing multiple users, implement delays between requests
  2. Cache Results: Following lists change slowly, cache for hours or days
  3. Respect Privacy: Some users have protected accounts - handle gracefully
  4. Network Analysis: Combine with getFollowers for full network mapping
  5. 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

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

ParameterTypeRequiredDescription
usernamestringYesUsername to get followers for
maxResultsnumberNoMaximum number of results (default: 100, max: 1000)
paginationTokenstringNoToken for pagination (from previous response)

Returns

TypeDescription
Promise<FollowersResponse>Followers list with pagination data

FollowersResponse Interface

PropertyTypeDescription
dataUserProfile[]Array of follower profiles
metaobjectPagination metadata
meta.result_countnumberNumber of results in this response
meta.next_tokenstring | undefinedToken for next page (if more results)

Examples

Get First 100 Followers

typescript
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

typescript
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

typescript
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`);
});

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

ParameterTypeRequiredDescription
tweetsTweet[]YesArray of tweets to enrich
optionsEnrichOptionsNoEnrichment options

EnrichOptions Interface

PropertyTypeDefaultDescription
concurrencynumber3Number of profiles to fetch in parallel (1-10)

Returns

TypeDescription
Promise<Tweet[]>Same tweets with enriched author data

Examples

Basic Usage

typescript
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}`);
}
typescript
// 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

typescript
// Fetch 5 profiles at a time for faster enrichment
const enriched = await client.enrichTweetAuthors(tweets, {
  concurrency: 5
});

Error Handling & Partial Enrichment

typescript
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

typescript
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

typescript
// 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

MetricValue
Default Concurrency3 profiles in parallel
Time per Profile~2 seconds (with GraphQL)
DeduplicationAutomatic (unique usernames)
Error HandlingGraceful (partial results)
FallbackHTML parsing if GraphQL fails

Performance Tips

  1. Pre-filter candidates to reduce the number of profiles to enrich
  2. Use concurrency control to balance speed vs rate limits
  3. Handle partial enrichment gracefully (some profiles may fail)
  4. Cache enriched profiles to avoid re-fetching
  5. Monitor unique authors - multiple tweets from same author only fetch once

Common Patterns

Discovery Workflow

typescript
// 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

typescript
// 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

  • 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 getUserByUsername endpoint
  • 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

  1. Always pre-filter before enrichment to minimize profile fetches
  2. Use appropriate concurrency (3 for small batches, 5-7 for large)
  3. Handle partial results - some profiles may be suspended/deleted
  4. Cache enriched data - profiles change slowly
  5. Monitor performance - log unique authors and timing
  6. 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

Released under the MIT License.