diff --git a/src/discord/guildCount.ts b/src/discord/guildCount.ts index ea92108..668dd6b 100644 --- a/src/discord/guildCount.ts +++ b/src/discord/guildCount.ts @@ -30,13 +30,14 @@ interface Guild { } interface Stats { - admin: Array; - community: Array; - moderating: Array; - owned: Array; - partnered: Array; - total: number; - verified: Array; + admin: Array; + community: Array; + discoverable: Array; + moderating: Array; + owned: Array; + partnered: Array; + total: number; + verified: Array; } // Permission Flags (BigInt) @@ -69,25 +70,249 @@ const printReport = (stats: Stats): void => { printList("Partnered Servers", stats.partnered, "🤝"); printList("Verified Servers", stats.verified, "✅"); printList("Community/Public Servers", stats.community, "🌍"); + printList("Discovery Enabled Servers", stats.discoverable, "🔍"); console.log(`======================================\n`); }; +const escapeHtml = (value: string): string => { + return value. + replaceAll("&", "&"). + replaceAll("<", "<"). + replaceAll(">", ">"). + replaceAll("\"", """). + replaceAll("'", "'"); +}; + +const buildDashboardHtml = (stats: Stats): string => { + const tiles: Array<{ items: Array; label: string; value: number }> = [ + { items: stats.owned, label: "You Own", value: stats.owned.length }, + { items: stats.admin, label: "You Admin", value: stats.admin.length }, + { + items: stats.moderating, + label: "You Moderate", + value: stats.moderating.length, + }, + { + items: stats.community, + label: "Community Enabled", + value: stats.community.length, + }, + { + items: stats.discoverable, + label: "Discovery Enabled", + value: stats.discoverable.length, + }, + { + items: stats.partnered, + label: "Partnered", + value: stats.partnered.length, + }, + { items: stats.verified, label: "Verified", value: stats.verified.length }, + ]; + + const cards = tiles.map((tile) => { + const listItems + = tile.items.length === 0 + ? "
  • No servers in this category.
  • " + : tile.items.map((name) => { + return `
  • ${escapeHtml(name)}
  • `; + }).join(""); + return ` +
    +

    ${escapeHtml(tile.label)}

    +

    ${tile.value.toString()}

    +
      + ${listItems} +
    +
    + `; + }).join(""); + + return ` + + + + Discord Server Counter + + + + +
    +
    +

    Server Counter

    +

    View counts of servers you are in along with quick insights.

    +
    + You are in + ${stats.total.toString()} servers +
    +
    + ${cards} +
    +
    +
    + +`; +}; + +const serveStatsDashboard = async(stats: Stats): Promise => { + const html = buildDashboardHtml(stats); + await new Promise((resolve, reject) => { + const server = http.createServer((request, response) => { + if (request.method !== "GET") { + response.statusCode = 405; + response.end("Method Not Allowed"); + return; + } + response.statusCode = 200; + response.setHeader("Content-Type", "text/html; charset=utf-8"); + response.end(html); + server.close(); + resolve(); + }); + + server.on("error", (error) => { + reject(error); + }); + + server.listen(0, "127.0.0.1", () => { + const address = server.address(); + if (address !== null && typeof address === "object") { + const url = `http://${address.address}:${address.port.toString()}/`; + console.log(`Opening dashboard at ${url}`); + void open(url, { wait: false }).catch(() => { + console.log( + `Failed to open browser automatically. Please visit ${url} manually.`, + ); + }); + } + }); + }); +}; + 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 => { +const analyzeGuilds = (guilds: Array): Stats => { // Arrays to store names instead of just counts const stats: Stats = { - admin: [], - community: [], - moderating: [], - owned: [], - partnered: [], - total: guilds.length, - verified: [], + admin: [], + community: [], + discoverable: [], + moderating: [], + owned: [], + partnered: [], + total: guilds.length, + verified: [], }; for (const guild of guilds) { @@ -129,12 +354,16 @@ const analyzeGuilds = (guilds: Array): void => { * "COMMUNITY" feature enables public facing screens (Welcome Screen, Rules, etc) * "DISCOVERABLE" means it appears in Server Discovery */ - if (features.includes("COMMUNITY") || features.includes("DISCOVERABLE")) { + if (features.includes("COMMUNITY")) { stats.community.push(name ?? id); } + if (features.includes("DISCOVERABLE")) { + stats.discoverable.push(name ?? id); + } } printReport(stats); + return stats; }; const defaultRedirectUri = "http://127.0.0.1:8721/callback"; @@ -434,7 +663,8 @@ async function getGuilds(): Promise { } const guilds: Array = await response.json(); - analyzeGuilds(guilds); + const stats = analyzeGuilds(guilds); + await serveStatsDashboard(stats); } await getGuilds();