/** * @copyright NHCarrigan * @license Naomi's Public License * @author Naomi Carrigan */ /* eslint-disable complexity, max-lines-per-function -- This is a chonky boi script. */ import { password, confirm } from "@inquirer/prompts"; interface Guild { name?: string; permissions?: string; features?: Array; owner?: boolean; id: string; } interface Stats { community: Array; moderating: Array; owned: Array; partnered: Array; total: number; verified: Array; } // Permission Flags (BigInt) const permissions = { administrator: 0x00_00_00_00_00_00_00_08n, banMembers: 0x00_00_00_00_00_00_00_04n, kickMembers: 0x00_00_00_00_00_00_00_02n, manageGuild: 0x00_00_00_00_00_00_00_20n, manageMessages: 0x00_00_00_00_00_00_20_00n, moderateMembers: 0x00_00_01_00_00_00_00_00n, }; const printList = (title: string, list: Array, icon: string): void => { console.log(`${icon} ${title}: ${list.length.toString()}`); if (list.length > 0) { for (const name of list) { console.log(` - ${name}`); } } console.log("\n"); }; const printReport = (stats: Stats): void => { console.log("\n====== DISCORD SERVER BREAKDOWN ======"); console.log(`Total Servers Joined: ${stats.total.toString()}\n`); printList("Owned Servers", stats.owned, "👑"); printList("Moderating (Non-Owned)", stats.moderating, "🛡️ "); printList("Partnered Servers", stats.partnered, "🤝"); printList("Verified Servers", stats.verified, "✅"); printList("Community/Public Servers", stats.community, "🌍"); console.log(`======================================\n`); }; const checkForPermission = (perms: bigint, permission: bigint): boolean => { // eslint-disable-next-line no-bitwise -- Since Discord uses bit flags... return (perms & permission) === permission; }; const analyzeGuilds = (guilds: Array): void => { // Arrays to store names instead of just counts const stats: Stats = { community: [], moderating: [], owned: [], partnered: [], total: guilds.length, verified: [], }; for (const guild of guilds) { const perms = BigInt(guild.permissions ?? 0); const features = guild.features ?? []; const { name, owner, id } = guild; // 1. Ownership if (owner === true) { stats.owned.push(name ?? "Unknown"); } /* * 2. Moderation * We consider you a "Moderator" if you have specific mod permissions, * Even if you don't own the server. */ const isModerator = checkForPermission(perms, permissions.administrator) || checkForPermission(perms, permissions.manageGuild) || checkForPermission(perms, permissions.banMembers) || checkForPermission(perms, permissions.kickMembers) || checkForPermission(perms, permissions.moderateMembers); if (isModerator && owner !== true) { stats.moderating.push(name ?? id); } // 3. Partnered if (features.includes("PARTNERED")) { stats.partnered.push(name ?? id); } // 4. Verified if (features.includes("VERIFIED")) { stats.verified.push(name ?? id); } /* * 5. Community / Public * "COMMUNITY" feature enables public facing screens (Welcome Screen, Rules, etc) * "DISCOVERABLE" means it appears in Server Discovery */ if (features.includes("COMMUNITY") || features.includes("DISCOVERABLE")) { stats.community.push(name ?? id); } } printReport(stats); }; /** * Fetches the user's guilds and analyses them. * For safety, we require the user to confirm our terms to continue. * Token is provided as an obscured password input to avoid accidental disclosure. */ async function getGuilds(): Promise { console.log( `WARNING! This script requires your user token. Because of this, you MUST take these into consideration:`, ); console.log( `1. DO NOT SHARE YOUR TOKEN WITH ANYONE. Your token can be used to impersonate your account, and can only be changed by rotating your account password.`, ); console.log( `2. THIS SCRIPT IS CONSIDERED SELF BOTTING. Running this is a violation of Discord's Terms of Service. DO SO AT YOUR OWN RISK!`, ); console.log( `3. Naomi Carrigan, NHCarrigan, its associates, and its affiliates are not responsible for any actions taken by you using this script.`, ); const confirmed = await confirm({ message: "I understand these risks, agree to the terms, and want to continue.", }); if (!confirmed) { throw new Error("User did not confirm the terms."); } const token = await password({ mask: true, message: "Please enter your user token:", validate: (value) => { if (value === "") { return "Token is required"; } return true; }, }); if (token === "") { throw new Error("Missing TOKEN"); } console.log("Fetching servers..."); // Discord allows max 200 servers per user (with Nitro), so limit=200 catches all. const response = await fetch( "https://discord.com/api/v10/users/@me/guilds?limit=200", { headers: { authorization: token, }, }, ); if (!response.ok) { if (response.status === 401) { console.error("Error: Invalid Token. Please check your TOKEN."); } else { console.error(`Error: API returned status ${response.status.toString()}`); } return; } const guilds: Array = await response.json(); analyzeGuilds(guilds); } await getGuilds(); export { getGuilds };