CCCPaste
// findChannels.js
// Parallelized server scanning to find chattable channels.
// Uses servers.json (auto-located).
// WARNING: Selfbots violate Discord TOS. Use at your own risk.
const { Client, Intents } = require('discord.js-selfbot-v13');
const fs = require('fs');
const path = require('path');
/////////// CONFIG ///////////
const TOKEN = ''; // <-- put your token here
// Concurrency settings (tune to be slower to avoid rate limits/bans).
const CONCURRENCY = 1; // Set to 1 to process servers one by one (safer)
const CHANNEL_TEST_DELAY_MS = 2000; // Delay between testing each channel (in ms, e.g., 2000 = 2 seconds)
const RETRY_ATTEMPTS = 3; // retry attempts on transient errors / 429
const RETRY_BASE_MS = 1000; // base backoff in ms
const TEST_SEND = true; // Enabled to actually test sending "X" and deleting it to verify chattable (RISKY: can trigger anti-spam, rate limits, or bans! Run on small server lists first.)
const LOG_CHANNELS = true; // Enabled to log each found/verified channel's name and ID to console
const INTENTS = [Intents.FLAGS.GUILDS, Intents.FLAGS.GUILD_MEMBERS];
/////////// HELPERS ///////////
function sleep(ms) { return new Promise(res => setTimeout(res, ms)); }
/**
 * Locate servers.json in several sensible places so doubled folder problems won't block you.
 */
function locateServersJson() {
  const candidates = [
    path.resolve(process.cwd(), 'servers.json'),
    path.join(__dirname, 'servers.json'),
  ];
  for (const p of candidates) if (fs.existsSync(p)) return p;
  return { notFound: true, attempted: candidates };
}
function loadServerIdsOrExit() {
  const located = locateServersJson();
  if (located && located.notFound) {
    console.error('[error] Required file not found. Attempted the following paths:');
    for (const p of located.attempted) console.error(' ' + p);
    console.error('\nCreate a servers.json in one of those locations using this format (array of guild ID strings):');
    console.error(`[\n "349051257702580224",\n "587355301083349002",\n "1141515589924429869"\n]`);
    process.exit(1);
  }
  console.log(`[info] Using servers.json at: ${located}`);
  let parsed;
  try { parsed = JSON.parse(fs.readFileSync(located, 'utf8')); }
  catch (e) { console.error(`[error] Failed to parse ${located}: ${e.message}`); process.exit(1); }
  if (!Array.isArray(parsed)) { console.error(`[error] ${located} must contain a JSON array of guild ID strings.`); process.exit(1); }
  const ids = Array.from(new Set(parsed.map(String).map(s => s.trim()).filter(Boolean).filter(s => /^\d+$/.test(s))));
  if (!ids.length) { console.error(`[error] ${located} contains no valid numeric guild IDs.`); process.exit(1); }
  return ids;
}
/**
 * Generic limited-concurrency runner.
 * items: array
 * limit: number of parallel workers
 * worker: async function(item, index) -> result
 */
async function runLimited(items, limit, worker) {
  let i = 0;
  const results = new Array(items.length);
  const workers = Array.from({ length: Math.min(limit, items.length) }, async () => {
    while (true) {
      const idx = i++;
      if (idx >= items.length) break;
      try { results[idx] = await worker(items[idx], idx); }
      catch (err) { results[idx] = { __error: err }; }
    }
  });
  await Promise.all(workers);
  return results;
}
/**
 * Robust channel scan inside a guild.
 * Tries to fetch channels and check permissions with retries on errors.
 * If TEST_SEND is true, actually tries to send and delete a message to verify.
 * Skips threads as per user request.
 */
async function findChattableChannelsInGuildWithRetries(guild, client) {
  for (let attempt = 0; attempt < RETRY_ATTEMPTS; attempt++) {
    try {
      // Fetch self member
      const me = await guild.members.fetch(client.user.id);
      // Fetch all channels (cache should populate) - only gets channels visible to the user
      await guild.channels.fetch();
      const channels = guild.channels.cache;
      const chattable = [];
      for (const channel of channels.values()) {
        if (!channel.isText() || channel.isThread()) continue; // Skip non-text and threads
        // Check permission first
        if (!channel.permissionsFor(me).has('SEND_MESSAGES')) continue;
        if (TEST_SEND) {
          // Actually test send "X" and delete
          try {
            const msg = await channel.send('X');
            await msg.delete();
            chattable.push(channel.id);
            if (LOG_CHANNELS) console.log(`  - Verified by send: #${channel.name} (${channel.id})`);
          } catch (sendErr) {
            console.warn(`[warn] Cannot send in channel ${channel.id} (${channel.name}): ${sendErr.message || sendErr}`);
            continue;
          }
        } else {
          chattable.push(channel.id);
          if (LOG_CHANNELS) console.log(`  - Found (perm check): #${channel.name} (${channel.id})`);
        }
        // Delay between channel tests to avoid rate limits
        await sleep(CHANNEL_TEST_DELAY_MS);
      }
      return chattable;
    } catch (err) {
      // Detect 429-like behavior if possible
      const is429 = (err && (err.status === 429 || /429|Too Many Requests/i.test(err.message || '')));
      const waitMs = RETRY_BASE_MS * Math.pow(2, attempt);
      if (is429) {
        console.warn(`[warn] Rate limited in guild ${guild.id}. Backing off ${waitMs}ms (attempt ${attempt+1}/${RETRY_ATTEMPTS})`);
      } else {
        console.warn(`[warn] Error in guild ${guild.id}: ${err.message || err}. Retrying ${waitMs}ms (attempt ${attempt+1}/${RETRY_ATTEMPTS})`);
      }
      await sleep(waitMs);
    }
  }
  // All attempts failed -> treat as no channels for this guild
  return [];
}
/////////// MAIN ///////////
(async () => {
  if (!TOKEN || TOKEN === 'PASTE_YOUR_TOKEN_HERE') {
    console.error('No token provided. Paste your token into the TOKEN constant at top of the script.');
    process.exit(1);
  }
  const serverIds = loadServerIdsOrExit();
  console.log(`Loaded ${serverIds.length} server ID(s) from servers.json.`);
  console.log(`Using concurrency: CONCURRENCY=${CONCURRENCY} (lowered for safety)`);
  console.log(`TEST_SEND mode: ${TEST_SEND ? 'ENABLED (will try sending "X" in each channel - risky, but verifies truly chattable)' : 'DISABLED (permission check only)'}`);
  console.log(`CHANNEL_TEST_DELAY_MS: ${CHANNEL_TEST_DELAY_MS}ms between each channel test`);
  console.log(`LOG_CHANNELS: ${LOG_CHANNELS ? 'ENABLED' : 'DISABLED'}`);
  const client = new Client({ intents: INTENTS });
  client.once('ready', async () => {
    console.log(`\nLogged in as ${client.user.tag}`);
    const allChattableChannels = new Set(); // Use set to avoid duplicates
    // Process servers in parallel with CONCURRENCY
    await runLimited(serverIds, CONCURRENCY, async (sid, idx) => {
      console.log(`\n[${idx+1}/${serverIds.length}] Checking server ${sid} ...`);
      let guild;
      try {
        guild = await client.guilds.fetch(sid);
      } catch (err) {
        console.warn(`- Skipping guild ${sid}: cannot fetch (not in guild / invalid id).`);
        return;
      }
      try {
        const channels = await findChattableChannelsInGuildWithRetries(guild, client);
        if (channels.length) {
          console.log(`- Found ${channels.length} chattable channel(s) in ${guild.name} (${guild.id})`);
          channels.forEach(id => allChattableChannels.add(id));
        } else {
          console.log(`- No chattable channels found in ${guild.name} (${guild.id})`);
        }
      } catch (err) {
        console.warn(`- Error scanning guild ${sid}: ${err.message || err}`);
      }
    });
    // Write to channels.json
    const outputPath = path.resolve(process.cwd(), 'channels.json');
    const channelArray = Array.from(allChattableChannels).sort(); // Sort for consistency
    const jsonContent = '[\n' + channelArray.map(id => `  "${id}"`).join(',\n') + '\n]';
    fs.writeFileSync(outputPath, jsonContent, 'utf8');
    console.log(`\n--- scan complete — summary ---`);
    console.log(`Found ${channelArray.length} chattable channel(s) across all servers.`);
    console.log(`Saved to: ${outputPath}`);
    client.destroy();
    process.exit(0);
  });
  client.on('error', (err) => {
    console.error('Client error:', err && err.message ? err.message : err);
  });
  try {
    await client.login(TOKEN);
  } catch (err) {
    console.error('Failed to login:', err && err.message ? err.message : err);
    process.exit(1);
  }
})();