← back to Goodquestion Ai

scripts/x-create-lists.cjs

67 lines

/**
 * Create X/Twitter Lists for @goodquestionai
 * Handles rate limiting with exponential backoff
 * Usage: node x-create-lists.cjs
 */
require('dotenv').config({ path: require('path').join(__dirname, '..', '.env') });
const { TwitterApi } = require('twitter-api-v2');

const client = new TwitterApi({
  appKey: process.env.TWITTER_API_KEY,
  appSecret: process.env.TWITTER_API_SECRET,
  accessToken: process.env.TWITTER_ACCESS_TOKEN,
  accessSecret: process.env.TWITTER_ACCESS_TOKEN_SECRET,
});

const LISTS = [
  { name: 'Claude & Anthropic', description: 'Anthropic team, Claude ecosystem builders, and Claude Code power users.' },
  { name: 'Dev Tools & Infra', description: 'Developer tools, infrastructure, DevOps, and the people making developers more productive.' },
  { name: 'Open Source Leaders', description: 'Open source maintainers, contributors, and advocates building in public.' },
  { name: 'Build In Public', description: 'Founders and devs sharing their journey — code, revenue, mistakes, and wins.' },
];

async function sleep(ms) {
  return new Promise(r => setTimeout(r, ms));
}

async function run() {
  // Check existing lists first
  const me = await client.v2.me();
  console.log(`Account: @${me.data.username}`);
  const existing = await client.v2.listsOwned(me.data.id);
  const existingNames = (existing.data || []).map(l => l.name);
  console.log(`Existing lists: ${existingNames.join(', ') || 'none'}`);

  for (const list of LISTS) {
    if (existingNames.includes(list.name)) {
      console.log(`Skipping "${list.name}" — already exists`);
      continue;
    }
    for (let attempt = 1; attempt <= 5; attempt++) {
      try {
        const result = await client.v2.createList({ name: list.name, description: list.description, private: false });
        console.log(`Created: "${list.name}" → ID: ${result.data.id}`);
        break;
      } catch (e) {
        if (e.message.includes('429') && attempt < 5) {
          const delay = attempt * 60;
          console.log(`Rate limited on "${list.name}", waiting ${delay}s (attempt ${attempt}/5)...`);
          await sleep(delay * 1000);
        } else {
          console.error(`Failed: "${list.name}" → ${e.message}`);
          break;
        }
      }
    }
  }

  // List all owned lists at the end
  const final = await client.v2.listsOwned(me.data.id);
  console.log('\n=== Final Lists ===');
  for (const l of (final.data || [])) {
    console.log(`  ${l.name} (ID: ${l.id})`);
  }
}

run().catch(e => console.error('Fatal:', e.message));