UNPKG

@podx/cli

Version:

๐Ÿ’ป Command-line interface for PODx - Advanced Twitter/X scraping and crypto analysis toolkit

292 lines (266 loc) โ€ข 13.2 kB
import { Command } from 'commander'; import { runScraping, runBatchScraping, runStatus, runClean, runConfiguration, runCryptoAnalysis, runAccountAnalysis, runRepliesAnalysis, runPodxAPI, runGenerateToken, runConvexSave, runSearchTweets, runListScrape, runCryptoSearch, runSolanaContracts, runHashtagSearch, runTickerSearch, runReplySearch, runAdvancedAnalysis, runBatchAdvancedAnalysis } from '../handlers'; import { config as appConfig } from '@podx/core'; export function registerCommands(program: Command): void { // ===== SCRAPING COMMANDS ===== program .command('scrape') .description('๐Ÿฆ Scrape tweets from a specific X (Twitter) account') .option('-u, --username <username>', 'Target username to scrape (without @)') .option('-c, --count <number>', 'Number of tweets to scrape (default: 300)', '300') .action(async (options) => { const scraperConfig = { targetUsername: options.username || appConfig.scraper.targetUsername, maxTweets: options.count ? parseInt(options.count, 10) : appConfig.scraper.maxTweets }; await runScraping(scraperConfig); }); program .command('batch') .description('๐Ÿ“ฆ Scrape multiple X accounts concurrently with rate limiting') .option('-u, --usernames <usernames>', 'Comma-separated usernames (e.g., "elonmusk,pmarca,naval")') .option('-f, --file <file>', 'File containing usernames (one per line)') .option('-c, --count <number>', 'Number of tweets to scrape per account', '200') .option('--concurrent <number>', 'Number of accounts to scrape concurrently', '3') .option('--delay <seconds>', 'Delay between account scrapes in seconds', '5') .action(async (options) => { const batchConfig = { usernames: options.usernames, usernamesFile: options.file, maxTweets: options.count ? parseInt(options.count, 10) : appConfig.scraper.maxTweets, concurrent: parseInt(options.concurrent, 10), delaySeconds: parseInt(options.delay, 10) }; await runBatchScraping(batchConfig); }); // ===== SYSTEM COMMANDS ===== program .command('status') .description('๐Ÿ“Š Show current system status, data, and configuration') .action(runStatus); program .command('clean') .description('๐Ÿงน Clean build artifacts and optionally remove data') .option('--all', 'Remove all data including scraped tweets') .action(runClean); program .command('config') .description('โš™๏ธ Setup or modify Twitter credentials and configuration') .action(runConfiguration); // ===== ANALYSIS COMMANDS ===== program .command('analyze') .description('๐Ÿ’Ž Analyze scraped tweets for crypto signals and token mentions') .option('-f, --file <file>', 'Input tweets file', 'scraped_tweets.json') .option('-o, --output <file>', 'Output analysis file', 'crypto_analysis.json') .action(async (options) => { await runCryptoAnalysis(options.file, options.output); }); program .command('accounts') .description('๐Ÿ‘ค Analyze account reputations and detect bots/shillers') .option('-f, --file <file>', 'Input tweets file', 'scraped_tweets.json') .option('-o, --output <file>', 'Output analysis file', 'account_analysis.json') .action(async (options) => { await runAccountAnalysis(options.file, options.output); }); program .command('replies') .description('๐Ÿ’ฌ Scrape and analyze replies/comments for crypto signals') .option('-f, --file <file>', 'Input tweets file', 'scraped_tweets.json') .option('-t, --token <token>', 'Token to analyze replies for (e.g., BTC, SOL)') .option('-o, --output <file>', 'Output analysis file', 'replies_analysis.json') .option('-c, --count <number>', 'Max replies per tweet', '20') .action(async (options) => { await runRepliesAnalysis(options.file, options.token, options.output, parseInt(options.count)); }); // ===== ADVANCED ANALYSIS WITH BOT DETECTION ===== program .command('bot-analysis') .description('๐Ÿค– Advanced analysis with bot detection and token shilling monitoring') .option('-u, --username <username>', 'Target username to analyze (without @)') .option('-c, --count <number>', 'Number of tweets to scrape and analyze', '300') .option('--no-bot-detection', 'Disable bot detection analysis') .option('--no-token-analysis', 'Disable token shilling analysis') .option('--no-replies', 'Disable reply analysis') .option('-o, --output <file>', 'Save analysis to custom file') .action(async (options) => { const username = options.username || appConfig.scraper.targetUsername; const count = options.count ? parseInt(options.count, 10) : appConfig.scraper.maxTweets; await runAdvancedAnalysis(username, count, { includeBotAnalysis: !options.noBotDetection, includeTokenAnalysis: !options.noTokenAnalysis, analyzeReplies: !options.noReplies, outputFile: options.output }); }); program .command('batch-bot-analysis') .description('๐Ÿค–๐Ÿ“ฆ Batch advanced analysis with bot detection for multiple accounts') .option('-u, --usernames <usernames>', 'Comma-separated usernames to analyze') .option('-f, --file <file>', 'File containing usernames (one per line)') .option('-c, --count <number>', 'Number of tweets to analyze per account', '200') .option('--concurrent <number>', 'Number of accounts to analyze concurrently', '2') .option('--delay <seconds>', 'Delay between batches in seconds', '10') .option('--no-bot-detection', 'Disable bot detection analysis') .option('--no-token-analysis', 'Disable token shilling analysis') .option('--no-replies', 'Disable reply analysis') .action(async (options) => { let usernames: string[] = []; if (options.usernames) { usernames = options.usernames.split(',').map((u: string) => u.trim()).filter((u: string) => u); } else if (options.file) { const fs = await import('fs/promises'); const content = await fs.readFile(options.file, 'utf-8'); usernames = content.split('\n').map((u: string) => u.trim()).filter((u: string) => u); } else { console.error('โŒ Either --usernames or --file must be provided'); return; } await runBatchAdvancedAnalysis(usernames, parseInt(options.count, 10), { includeBotAnalysis: !options.noBotDetection, includeTokenAnalysis: !options.noTokenAnalysis, analyzeReplies: !options.noReplies, concurrent: parseInt(options.concurrent, 10), delay: parseInt(options.delay, 10) }); }); // ===== SEARCH COMMANDS ===== program .command('search') .description('๐Ÿ” Search Twitter for specific queries with advanced filters') .option('-q, --query <query>', 'Search query (required)') .option('-c, --count <number>', 'Max tweets to fetch', '100') .option('-m, --mode <mode>', 'Search mode (Latest|Top|People|Photos|Videos)', 'Latest') .option('-o, --output <file>', 'Output file', 'search_results.json') .option('--min-likes <number>', 'Minimum likes filter') .option('--min-retweets <number>', 'Minimum retweets filter') .option('--date-from <date>', 'Start date (YYYY-MM-DD)') .option('--date-to <date>', 'End date (YYYY-MM-DD)') .option('--no-retweets', 'Exclude retweets') .action(async (options) => { if (!options.query) { console.error('โŒ Query is required. Use -q or --query to specify search terms.'); return; } await runSearchTweets(options); }); program .command('crypto-search') .description('๐Ÿ’Ž Search Twitter for crypto-related content with enhanced token detection') .option('-t, --tokens <tokens>', 'Comma-separated token symbols (e.g., BTC,ETH,SOL)') .option('-k, --keywords <keywords>', 'Comma-separated crypto keywords') .option('-c, --count <number>', 'Max tweets to fetch', '200') .option('-m, --mode <mode>', 'Search mode (Latest|Top)', 'Latest') .option('-o, --output <file>', 'Output file', 'crypto_search_results.json') .option('--min-engagement <number>', 'Minimum likes for filtering', '0') .action(async (options) => { await runCryptoSearch(options); }); program .command('hashtags') .description('๐Ÿท๏ธ Search Twitter for specific hashtags') .option('-h, --hashtags <hashtags>', 'Comma-separated hashtags (e.g., bitcoin,crypto,web3)') .option('-c, --count <number>', 'Max tweets to fetch', '100') .option('-o, --output <file>', 'Output file', 'hashtag_search.json') .action(async (options) => { if (!options.hashtags) { console.error('โŒ Hashtags are required. Use -h or --hashtags to specify hashtags.'); return; } await runHashtagSearch(options); }); program .command('tickers') .description('๐Ÿ’ฐ Search Twitter for specific ticker symbols ($BTC, $ETH, etc.)') .option('-t, --tickers <tickers>', 'Comma-separated tickers (e.g., BTC,ETH,SOL)') .option('-c, --count <number>', 'Max tweets to fetch', '100') .option('-o, --output <file>', 'Output file', 'ticker_search.json') .action(async (options) => { if (!options.tickers) { console.error('โŒ Tickers are required. Use -t or --tickers to specify ticker symbols.'); return; } await runTickerSearch(options); }); program .command('list') .description('๐Ÿ“‹ Scrape tweets from a Twitter list (experimental)') .option('-i, --id <listId>', 'Twitter list ID (required)') .option('-c, --count <number>', 'Max tweets to fetch', '100') .option('-o, --output <file>', 'Output file', 'list_results.json') .option('--no-retweets', 'Exclude retweets') .action(async (options) => { if (!options.id) { console.error('โŒ List ID is required. Use -i or --id to specify the Twitter list ID.'); return; } await runListScrape(options); }); program .command('reply-search') .description('๐Ÿ’ฌ Search replies for specific tweet IDs (experimental)') .option('-i, --ids <ids>', 'Comma-separated tweet IDs') .option('-c, --count <number>', 'Max replies per tweet', '50') .option('-o, --output <file>', 'Output file', 'replies_search.json') .action(async (options) => { if (!options.ids) { console.error('โŒ Tweet IDs are required. Use -i or --ids to specify tweet IDs.'); return; } await runReplySearch(options); }); // ===== SPECIALIZED ANALYSIS ===== program .command('solana-contracts') .description('๐Ÿ”— Analyze tweets for Solana contract addresses and smart contracts') .option('-f, --file <file>', 'Input tweets file', 'scraped_tweets.json') .option('-o, --output <file>', 'Output file', 'solana_contracts.json') .action(async (options) => { await runSolanaContracts(options.file, options.output); }); // ===== API & SERVICES ===== program .command('serve') .description('๐ŸŒ Start the PODx REST API server') .option('-p, --port <port>', 'Port to run the API server on', '3000') .action(async (options) => { const port = parseInt(options.port, 10); await runPodxAPI(port); }); program .command('token') .description('๐Ÿ”‘ Generate JWT tokens for PODX API access') .option('-u, --user <userId>', 'User ID for the token', 'podx-user') .option('-t, --tier <tier>', 'Access tier (free|premium|admin)', 'free') .option('-d, --duration <duration>', 'Token duration (1h|1d|7d|30d|unlimited)', 'unlimited') .option('-n, --name <name>', 'Optional name/description for the token') .action(async (options) => { await runGenerateToken(options.user, options.tier, options.duration, options.name); }); program .command('convex') .description('๐Ÿ’พ Save analysis data to Convex database (optional)') .option('-f, --file <file>', 'Input analysis file', 'crypto_analysis.json') .option('-t, --type <type>', 'Data type to save (analysis|tokens|accounts)', 'analysis') .action(async (options) => { await runConvexSave(options.file, options.type); }); program .command('db') .description('๐Ÿ’พ Start the PODx database service (Convex development)') .action(async () => { console.log('๐Ÿ’พ Starting PODx Database Service...'); console.log('๐Ÿ”— Convex development mode'); console.log('๐Ÿ’ก Use "npx convex dev" to start Convex development server'); }); program .command('all') .description('๐ŸŽฏ Start all PODx services (API + Database + CLI)') .action(async () => { console.log('๐ŸŽฏ Starting PODx Full Stack...'); console.log('๐Ÿš€ API Server: http://localhost:3000'); console.log('๐Ÿ’พ Database: Local/Convex mode'); console.log('๐Ÿ’ป CLI: Interactive mode ready'); }); }