forked from
j4ck.xyz/tweets2bsky
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.
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();