A simple tool which lets you scrape twitter accounts and crosspost them to bluesky accounts! Comes with a CLI and a webapp for managing profiles! Works with images/videos/link embeds/threads.
at 336c1506cee87f0b593a2bb090bb66c8e9f87feb 157 lines 4.2 kB view raw
1import { Command } from 'commander'; 2import inquirer from 'inquirer'; 3import { addMapping, getConfig, removeMapping, saveConfig, updateTwitterConfig } from './config-manager.js'; 4 5const program = new Command(); 6 7program 8 .name('tweets-2-bsky-cli') 9 .description('CLI to manage Twitter to Bluesky crossposting mappings') 10 .version('1.0.0'); 11 12program 13 .command('setup-twitter') 14 .description('Setup Twitter auth cookies') 15 .action(async () => { 16 const config = getConfig(); 17 const answers = await inquirer.prompt([ 18 { 19 type: 'input', 20 name: 'authToken', 21 message: 'Enter Twitter auth_token:', 22 default: config.twitter.authToken, 23 }, 24 { 25 type: 'input', 26 name: 'ct0', 27 message: 'Enter Twitter ct0:', 28 default: config.twitter.ct0, 29 }, 30 ]); 31 updateTwitterConfig(answers); 32 console.log('Twitter config updated!'); 33 }); 34 35program 36 .command('add-mapping') 37 .description('Add a new Twitter to Bluesky mapping') 38 .action(async () => { 39 const answers = await inquirer.prompt([ 40 { 41 type: 'input', 42 name: 'twitterUsername', 43 message: 'Twitter username to watch (without @):', 44 }, 45 { 46 type: 'input', 47 name: 'bskyIdentifier', 48 message: 'Bluesky identifier (handle or email):', 49 }, 50 { 51 type: 'password', 52 name: 'bskyPassword', 53 message: 'Bluesky app password:', 54 }, 55 { 56 type: 'input', 57 name: 'bskyServiceUrl', 58 message: 'Bluesky service URL:', 59 default: 'https://bsky.social', 60 }, 61 ]); 62 addMapping(answers); 63 console.log('Mapping added successfully!'); 64 }); 65 66program 67 .command('list') 68 .description('List all mappings') 69 .action(() => { 70 const config = getConfig(); 71 if (config.mappings.length === 0) { 72 console.log('No mappings found.'); 73 return; 74 } 75 console.table( 76 config.mappings.map((m) => ({ 77 id: m.id, 78 twitter: m.twitterUsername, 79 bsky: m.bskyIdentifier, 80 enabled: m.enabled, 81 })), 82 ); 83 }); 84 85program 86 .command('remove') 87 .description('Remove a mapping') 88 .action(async () => { 89 const config = getConfig(); 90 if (config.mappings.length === 0) { 91 console.log('No mappings to remove.'); 92 return; 93 } 94 const { id } = await inquirer.prompt([ 95 { 96 type: 'list', 97 name: 'id', 98 message: 'Select a mapping to remove:', 99 choices: config.mappings.map((m) => ({ 100 name: `${m.twitterUsername} -> ${m.bskyIdentifier}`, 101 value: m.id, 102 })), 103 }, 104 ]); 105 removeMapping(id); 106 console.log('Mapping removed.'); 107 }); 108 109program 110 .command('import-history') 111 .description('Import history for a specific mapping') 112 .action(async () => { 113 const config = getConfig(); 114 if (config.mappings.length === 0) { 115 console.log('No mappings found.'); 116 return; 117 } 118 const { id } = await inquirer.prompt([ 119 { 120 type: 'list', 121 name: 'id', 122 message: 'Select a mapping to import history for:', 123 choices: config.mappings.map((m) => ({ 124 name: `${m.twitterUsername} -> ${m.bskyIdentifier}`, 125 value: m.id, 126 })), 127 }, 128 ]); 129 130 const mapping = config.mappings.find((m) => m.id === id); 131 if (!mapping) return; 132 133 console.log(` 134To import history for ${mapping.twitterUsername}, run:`); 135 console.log(` npm run import -- --username ${mapping.twitterUsername}`); 136 console.log(` 137You can also use additional flags:`); 138 console.log(' --limit <number> Limit the number of tweets to import'); 139 console.log(' --dry-run Fetch and show tweets without posting'); 140 console.log(` 141Example:`); 142 console.log(` npm run import -- --username ${mapping.twitterUsername} --limit 10 --dry-run 143`); 144 }); 145 146program 147 .command('set-interval') 148 .description('Set check interval in minutes') 149 .argument('<minutes>', 'Interval in minutes') 150 .action((minutes) => { 151 const config = getConfig(); 152 config.checkIntervalMinutes = Number.parseInt(minutes, 10); 153 saveConfig(config); 154 console.log(`Interval set to ${minutes} minutes.`); 155 }); 156 157program.parse();