// 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);
}
})();