generated from nhcarrigan/template
This commit is contained in:
+3
-5
@@ -5,7 +5,7 @@
|
||||
"main": "index.js",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"build": "tsc --noEmit",
|
||||
"lint": "eslint src --max-warnings 0",
|
||||
"start": "op run --env-file=prod.env --no-masking -- tsx",
|
||||
"test": "echo \"Error: no test specified\" && exit 0"
|
||||
@@ -14,7 +14,7 @@
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"packageManager": "pnpm@10.15.0",
|
||||
"devDependencies": {
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "3.940.0",
|
||||
"@inquirer/prompts": "7.8.6",
|
||||
"@nhcarrigan/eslint-config": "5.2.0",
|
||||
@@ -22,9 +22,7 @@
|
||||
"@types/node": "24.3.0",
|
||||
"eslint": "9.34.0",
|
||||
"tsx": "4.20.5",
|
||||
"typescript": "5.9.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"typescript": "5.9.2",
|
||||
"@octokit/rest": "22.0.0",
|
||||
"@types/cli-progress": "3.11.6",
|
||||
"cli-progress": "3.12.0"
|
||||
|
||||
Generated
+9
-10
@@ -8,16 +8,6 @@ importers:
|
||||
|
||||
.:
|
||||
dependencies:
|
||||
'@octokit/rest':
|
||||
specifier: 22.0.0
|
||||
version: 22.0.0
|
||||
'@types/cli-progress':
|
||||
specifier: 3.11.6
|
||||
version: 3.11.6
|
||||
cli-progress:
|
||||
specifier: 3.12.0
|
||||
version: 3.12.0
|
||||
devDependencies:
|
||||
'@aws-sdk/client-s3':
|
||||
specifier: 3.940.0
|
||||
version: 3.940.0
|
||||
@@ -30,9 +20,18 @@ importers:
|
||||
'@nhcarrigan/typescript-config':
|
||||
specifier: 4.0.0
|
||||
version: 4.0.0(typescript@5.9.2)
|
||||
'@octokit/rest':
|
||||
specifier: 22.0.0
|
||||
version: 22.0.0
|
||||
'@types/cli-progress':
|
||||
specifier: 3.11.6
|
||||
version: 3.11.6
|
||||
'@types/node':
|
||||
specifier: 24.3.0
|
||||
version: 24.3.0
|
||||
cli-progress:
|
||||
specifier: 3.12.0
|
||||
version: 3.12.0
|
||||
eslint:
|
||||
specifier: 9.34.0
|
||||
version: 9.34.0
|
||||
|
||||
@@ -0,0 +1,165 @@
|
||||
/**
|
||||
* @copyright NHCarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
/* eslint-disable complexity, max-lines-per-function -- This is a chonky boi script. */
|
||||
/**
|
||||
* DISCORD SERVER STATS ANALYZER
|
||||
* * Instructions:
|
||||
* 1. Get your User Token (Open Discord in Browser -> F12 -> Network Tab -> Type a message -> Look for "authorization" in request headers).
|
||||
* 2. Set it in the .env file as TOKEN.
|
||||
* 3. Run with: node discord_stats.js.
|
||||
*/
|
||||
|
||||
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);
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
async function getGuilds(): Promise<void> {
|
||||
const token = process.env.TOKEN;
|
||||
|
||||
if (token === undefined) {
|
||||
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 };
|
||||
Reference in New Issue
Block a user