generated from nhcarrigan/template
Compare commits
3 Commits
262cdcb702
...
64e2b4419c
| Author | SHA1 | Date | |
|---|---|---|---|
| 64e2b4419c | |||
| 43e8b4e6c6 | |||
| bd3f2c6388 |
+6
-5
@@ -19,12 +19,13 @@
|
||||
"@inquirer/prompts": "7.8.6",
|
||||
"@nhcarrigan/eslint-config": "5.2.0",
|
||||
"@nhcarrigan/typescript-config": "4.0.0",
|
||||
"@types/node": "24.3.0",
|
||||
"eslint": "9.34.0",
|
||||
"tsx": "4.20.5",
|
||||
"typescript": "5.9.2",
|
||||
"@octokit/rest": "22.0.0",
|
||||
"@types/cli-progress": "3.11.6",
|
||||
"cli-progress": "3.12.0"
|
||||
"@types/node": "24.3.0",
|
||||
"cli-progress": "3.12.0",
|
||||
"eslint": "9.34.0",
|
||||
"open": "11.0.0",
|
||||
"tsx": "4.20.5",
|
||||
"typescript": "5.9.2"
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+96
@@ -35,6 +35,9 @@ importers:
|
||||
eslint:
|
||||
specifier: 9.34.0
|
||||
version: 9.34.0
|
||||
open:
|
||||
specifier: 11.0.0
|
||||
version: 11.0.0
|
||||
tsx:
|
||||
specifier: 4.20.5
|
||||
version: 4.20.5
|
||||
@@ -1284,6 +1287,10 @@ packages:
|
||||
resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
bundle-name@4.1.0:
|
||||
resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
cac@6.7.14:
|
||||
resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -1395,10 +1402,22 @@ packages:
|
||||
deep-is@0.1.4:
|
||||
resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
|
||||
|
||||
default-browser-id@5.0.1:
|
||||
resolution: {integrity: sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
default-browser@5.4.0:
|
||||
resolution: {integrity: sha512-XDuvSq38Hr1MdN47EDvYtx3U0MTqpCEn+F6ft8z2vYDzMrvQhVp0ui9oQdqW3MvK3vqUETglt1tVGgjLuJ5izg==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
define-data-property@1.1.4:
|
||||
resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
define-lazy-prop@3.0.0:
|
||||
resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
define-properties@1.2.1:
|
||||
resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -1823,6 +1842,11 @@ packages:
|
||||
resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
is-docker@3.0.0:
|
||||
resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==}
|
||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||
hasBin: true
|
||||
|
||||
is-extglob@2.1.1:
|
||||
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@@ -1843,6 +1867,15 @@ packages:
|
||||
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
is-in-ssh@1.0.0:
|
||||
resolution: {integrity: sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw==}
|
||||
engines: {node: '>=20'}
|
||||
|
||||
is-inside-container@1.0.0:
|
||||
resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==}
|
||||
engines: {node: '>=14.16'}
|
||||
hasBin: true
|
||||
|
||||
is-map@2.0.3:
|
||||
resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -1895,6 +1928,10 @@ packages:
|
||||
resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
is-wsl@3.1.0:
|
||||
resolution: {integrity: sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==}
|
||||
engines: {node: '>=16'}
|
||||
|
||||
isarray@2.0.5:
|
||||
resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==}
|
||||
|
||||
@@ -2058,6 +2095,10 @@ packages:
|
||||
resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
open@11.0.0:
|
||||
resolution: {integrity: sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw==}
|
||||
engines: {node: '>=20'}
|
||||
|
||||
optionator@0.9.4:
|
||||
resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
@@ -2153,6 +2194,10 @@ packages:
|
||||
resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==}
|
||||
engines: {node: ^10 || ^12 || >=14}
|
||||
|
||||
powershell-utils@0.1.0:
|
||||
resolution: {integrity: sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==}
|
||||
engines: {node: '>=20'}
|
||||
|
||||
prelude-ls@1.2.1:
|
||||
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
@@ -2227,6 +2272,10 @@ packages:
|
||||
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
|
||||
hasBin: true
|
||||
|
||||
run-applescript@7.1.0:
|
||||
resolution: {integrity: sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
run-parallel@1.2.0:
|
||||
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
|
||||
|
||||
@@ -2609,6 +2658,10 @@ packages:
|
||||
resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
wsl-utils@0.3.0:
|
||||
resolution: {integrity: sha512-3sFIGLiaDP7rTO4xh3g+b3AzhYDIUGGywE/WsmqzJWDxus5aJXVnPTNC/6L+r2WzrwXqVOdD262OaO+cEyPMSQ==}
|
||||
engines: {node: '>=20'}
|
||||
|
||||
yocto-queue@0.1.0:
|
||||
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
|
||||
engines: {node: '>=10'}
|
||||
@@ -4294,6 +4347,10 @@ snapshots:
|
||||
|
||||
builtin-modules@3.3.0: {}
|
||||
|
||||
bundle-name@4.1.0:
|
||||
dependencies:
|
||||
run-applescript: 7.1.0
|
||||
|
||||
cac@6.7.14: {}
|
||||
|
||||
call-bind-apply-helpers@1.0.2:
|
||||
@@ -4396,12 +4453,21 @@ snapshots:
|
||||
|
||||
deep-is@0.1.4: {}
|
||||
|
||||
default-browser-id@5.0.1: {}
|
||||
|
||||
default-browser@5.4.0:
|
||||
dependencies:
|
||||
bundle-name: 4.1.0
|
||||
default-browser-id: 5.0.1
|
||||
|
||||
define-data-property@1.1.4:
|
||||
dependencies:
|
||||
es-define-property: 1.0.1
|
||||
es-errors: 1.3.0
|
||||
gopd: 1.2.0
|
||||
|
||||
define-lazy-prop@3.0.0: {}
|
||||
|
||||
define-properties@1.2.1:
|
||||
dependencies:
|
||||
define-data-property: 1.1.4
|
||||
@@ -5006,6 +5072,8 @@ snapshots:
|
||||
call-bound: 1.0.4
|
||||
has-tostringtag: 1.0.2
|
||||
|
||||
is-docker@3.0.0: {}
|
||||
|
||||
is-extglob@2.1.1: {}
|
||||
|
||||
is-finalizationregistry@1.1.1:
|
||||
@@ -5025,6 +5093,12 @@ snapshots:
|
||||
dependencies:
|
||||
is-extglob: 2.1.1
|
||||
|
||||
is-in-ssh@1.0.0: {}
|
||||
|
||||
is-inside-container@1.0.0:
|
||||
dependencies:
|
||||
is-docker: 3.0.0
|
||||
|
||||
is-map@2.0.3: {}
|
||||
|
||||
is-negative-zero@2.0.3: {}
|
||||
@@ -5075,6 +5149,10 @@ snapshots:
|
||||
call-bound: 1.0.4
|
||||
get-intrinsic: 1.3.0
|
||||
|
||||
is-wsl@3.1.0:
|
||||
dependencies:
|
||||
is-inside-container: 1.0.0
|
||||
|
||||
isarray@2.0.5: {}
|
||||
|
||||
isexe@2.0.0: {}
|
||||
@@ -5232,6 +5310,15 @@ snapshots:
|
||||
define-properties: 1.2.1
|
||||
es-object-atoms: 1.1.1
|
||||
|
||||
open@11.0.0:
|
||||
dependencies:
|
||||
default-browser: 5.4.0
|
||||
define-lazy-prop: 3.0.0
|
||||
is-in-ssh: 1.0.0
|
||||
is-inside-container: 1.0.0
|
||||
powershell-utils: 0.1.0
|
||||
wsl-utils: 0.3.0
|
||||
|
||||
optionator@0.9.4:
|
||||
dependencies:
|
||||
deep-is: 0.1.4
|
||||
@@ -5317,6 +5404,8 @@ snapshots:
|
||||
picocolors: 1.1.1
|
||||
source-map-js: 1.2.1
|
||||
|
||||
powershell-utils@0.1.0: {}
|
||||
|
||||
prelude-ls@1.2.1: {}
|
||||
|
||||
prop-types@15.8.1:
|
||||
@@ -5418,6 +5507,8 @@ snapshots:
|
||||
'@rollup/rollup-win32-x64-msvc': 4.49.0
|
||||
fsevents: 2.3.3
|
||||
|
||||
run-applescript@7.1.0: {}
|
||||
|
||||
run-parallel@1.2.0:
|
||||
dependencies:
|
||||
queue-microtask: 1.2.3
|
||||
@@ -5869,6 +5960,11 @@ snapshots:
|
||||
string-width: 4.2.3
|
||||
strip-ansi: 6.0.1
|
||||
|
||||
wsl-utils@0.3.0:
|
||||
dependencies:
|
||||
is-wsl: 3.1.0
|
||||
powershell-utils: 0.1.0
|
||||
|
||||
yocto-queue@0.1.0: {}
|
||||
|
||||
yoctocolors-cjs@2.1.3: {}
|
||||
|
||||
+255
-13
@@ -19,6 +19,7 @@ import crypto from "node:crypto";
|
||||
import http from "node:http";
|
||||
import process from "node:process";
|
||||
import { confirm } from "@inquirer/prompts";
|
||||
import open from "open";
|
||||
|
||||
interface Guild {
|
||||
name?: string;
|
||||
@@ -29,7 +30,9 @@ interface Guild {
|
||||
}
|
||||
|
||||
interface Stats {
|
||||
admin: Array<string>;
|
||||
community: Array<string>;
|
||||
discoverable: Array<string>;
|
||||
moderating: Array<string>;
|
||||
owned: Array<string>;
|
||||
partnered: Array<string>;
|
||||
@@ -62,23 +65,249 @@ const printReport = (stats: Stats): void => {
|
||||
console.log(`Total Servers Joined: ${stats.total.toString()}\n`);
|
||||
|
||||
printList("Owned Servers", stats.owned, "đź‘‘");
|
||||
printList("Moderating (Non-Owned)", stats.moderating, "🛡️ ");
|
||||
printList("Admin (Non-Owned)", stats.admin, "⚙️ ");
|
||||
printList("Moderating (Non-Admin/Non-Owned)", stats.moderating, "🛡️ ");
|
||||
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<string>; 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
|
||||
? "<li class=\"empty\">No servers in this category.</li>"
|
||||
: tile.items.map((name) => {
|
||||
return `<li>${escapeHtml(name)}</li>`;
|
||||
}).join("");
|
||||
return `
|
||||
<div class="card">
|
||||
<p class="label">${escapeHtml(tile.label)}</p>
|
||||
<p class="count">${tile.value.toString()}</p>
|
||||
<ul class="server-list">
|
||||
${listItems}
|
||||
</ul>
|
||||
</div>
|
||||
`;
|
||||
}).join("");
|
||||
|
||||
return `<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Discord Server Counter</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<style>
|
||||
:root {
|
||||
font-family: "Inter", system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
|
||||
background: #0f0f16;
|
||||
color: #111;
|
||||
}
|
||||
body {
|
||||
margin: 0;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 2rem;
|
||||
background: linear-gradient(135deg, #ff4ecd, #7873f5, #4ac7fa, #43e97b);
|
||||
}
|
||||
.wrapper {
|
||||
width: min(1100px, 100%);
|
||||
border-radius: 1.5rem;
|
||||
padding: 0.25rem;
|
||||
background: linear-gradient(120deg, #ff4ecd, #b388ff, #4ac7fa, #43e97b);
|
||||
box-shadow: 0 20px 50px rgba(15, 23, 42, 0.35);
|
||||
}
|
||||
.content {
|
||||
background: #fff;
|
||||
border-radius: 1.25rem;
|
||||
padding: 2rem 2.5rem 3rem;
|
||||
}
|
||||
h1 {
|
||||
margin: 0;
|
||||
font-size: clamp(1.75rem, 3vw, 2.5rem);
|
||||
}
|
||||
.subtitle {
|
||||
color: #6b7280;
|
||||
margin-bottom: 2rem;
|
||||
font-size: 1rem;
|
||||
}
|
||||
.total {
|
||||
text-align: center;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
.total span {
|
||||
display: block;
|
||||
color: #6b7280;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
}
|
||||
.total strong {
|
||||
font-size: clamp(2.5rem, 5vw, 3.5rem);
|
||||
color: #4c1d95;
|
||||
}
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
||||
gap: 1.25rem;
|
||||
}
|
||||
.card {
|
||||
background: #f9fafb;
|
||||
border-radius: 1rem;
|
||||
padding: 1.25rem;
|
||||
border: 1px solid #e5e7eb;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
.label {
|
||||
color: #7c3aed;
|
||||
font-weight: 600;
|
||||
margin-bottom: 0.75rem;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
.count {
|
||||
font-size: 2.25rem;
|
||||
font-weight: 700;
|
||||
margin-bottom: 1rem;
|
||||
color: #111827;
|
||||
}
|
||||
.server-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
max-height: 180px;
|
||||
}
|
||||
.server-list li {
|
||||
padding: 0.35rem 0;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
font-size: 0.95rem;
|
||||
color: #374151;
|
||||
}
|
||||
.server-list li:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
.server-list .empty {
|
||||
color: #9ca3af;
|
||||
font-style: italic;
|
||||
border-bottom: none;
|
||||
}
|
||||
@media (max-width: 640px) {
|
||||
body {
|
||||
padding: 1rem;
|
||||
}
|
||||
.content {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="wrapper">
|
||||
<div class="content">
|
||||
<h1>Server Counter</h1>
|
||||
<p class="subtitle">View counts of servers you are in along with quick insights.</p>
|
||||
<div class="total">
|
||||
<span>You are in</span>
|
||||
<strong>${stats.total.toString()} servers</strong>
|
||||
</div>
|
||||
<div class="grid">
|
||||
${cards}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>`;
|
||||
};
|
||||
|
||||
const serveStatsDashboard = async(stats: Stats): Promise<void> => {
|
||||
const html = buildDashboardHtml(stats);
|
||||
await new Promise<void>((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<Guild>): void => {
|
||||
const analyzeGuilds = (guilds: Array<Guild>): Stats => {
|
||||
// Arrays to store names instead of just counts
|
||||
const stats: Stats = {
|
||||
admin: [],
|
||||
community: [],
|
||||
discoverable: [],
|
||||
moderating: [],
|
||||
owned: [],
|
||||
partnered: [],
|
||||
@@ -96,18 +325,17 @@ const analyzeGuilds = (guilds: Array<Guild>): void => {
|
||||
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)
|
||||
const isAdmin = checkForPermission(perms, permissions.administrator);
|
||||
const hasModeratorPermissions
|
||||
= isAdmin
|
||||
|| checkForPermission(perms, permissions.manageGuild)
|
||||
|| checkForPermission(perms, permissions.banMembers)
|
||||
|| checkForPermission(perms, permissions.kickMembers)
|
||||
|| checkForPermission(perms, permissions.moderateMembers);
|
||||
if (isModerator && owner !== true) {
|
||||
|
||||
if (isAdmin && owner !== true) {
|
||||
stats.admin.push(name ?? id);
|
||||
} else if (hasModeratorPermissions && owner !== true && !isAdmin) {
|
||||
stats.moderating.push(name ?? id);
|
||||
}
|
||||
|
||||
@@ -126,12 +354,16 @@ const analyzeGuilds = (guilds: Array<Guild>): 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";
|
||||
@@ -346,13 +578,22 @@ async function startOAuthFlow(): Promise<string> {
|
||||
authUrl.searchParams.set("code_challenge_method", "S256");
|
||||
|
||||
console.log("\n====== Discord OAuth ======");
|
||||
console.log("1. Open the following URL in your browser:");
|
||||
console.log(
|
||||
`1. Open the following URL in your browser (we will try to automatically open it for you):`,
|
||||
);
|
||||
console.log(authUrl.toString());
|
||||
console.log("2. Approve access for your application.");
|
||||
console.log(
|
||||
"3. Return here; the script will continue once approval is complete.\n",
|
||||
);
|
||||
|
||||
void open(authUrl.toString(), { wait: false }).catch(() => {
|
||||
console.log(
|
||||
"Failed to open browser. Please open the following URL in your browser:",
|
||||
);
|
||||
console.log(authUrl.toString());
|
||||
});
|
||||
|
||||
const code = await waitForOAuthCode(redirectUri, state);
|
||||
const tokenRequest: TokenExchangeOptions = {
|
||||
clientId,
|
||||
@@ -422,7 +663,8 @@ async function getGuilds(): Promise<void> {
|
||||
}
|
||||
|
||||
const guilds: Array<Guild> = await response.json();
|
||||
analyzeGuilds(guilds);
|
||||
const stats = analyzeGuilds(guilds);
|
||||
await serveStatsDashboard(stats);
|
||||
}
|
||||
|
||||
await getGuilds();
|
||||
|
||||
Reference in New Issue
Block a user