generated from nhcarrigan/template
191 lines
5.5 KiB
TypeScript
191 lines
5.5 KiB
TypeScript
/**
|
|
* @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<string>;
|
|
owner?: boolean;
|
|
id: string;
|
|
}
|
|
|
|
interface Stats {
|
|
community: Array<string>;
|
|
moderating: Array<string>;
|
|
owned: Array<string>;
|
|
partnered: Array<string>;
|
|
total: number;
|
|
verified: Array<string>;
|
|
}
|
|
|
|
// 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<string>, 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<Guild>): 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<void> {
|
|
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<Guild> = await response.json();
|
|
analyzeGuilds(guilds);
|
|
}
|
|
|
|
await getGuilds();
|
|
|
|
export { getGuilds };
|