generated from nhcarrigan/template
This commit is contained in:
+233
-3
@@ -32,6 +32,7 @@ interface Guild {
|
|||||||
interface Stats {
|
interface Stats {
|
||||||
admin: Array<string>;
|
admin: Array<string>;
|
||||||
community: Array<string>;
|
community: Array<string>;
|
||||||
|
discoverable: Array<string>;
|
||||||
moderating: Array<string>;
|
moderating: Array<string>;
|
||||||
owned: Array<string>;
|
owned: Array<string>;
|
||||||
partnered: Array<string>;
|
partnered: Array<string>;
|
||||||
@@ -69,20 +70,244 @@ const printReport = (stats: Stats): void => {
|
|||||||
printList("Partnered Servers", stats.partnered, "🤝");
|
printList("Partnered Servers", stats.partnered, "🤝");
|
||||||
printList("Verified Servers", stats.verified, "✅");
|
printList("Verified Servers", stats.verified, "✅");
|
||||||
printList("Community/Public Servers", stats.community, "🌍");
|
printList("Community/Public Servers", stats.community, "🌍");
|
||||||
|
printList("Discovery Enabled Servers", stats.discoverable, "🔍");
|
||||||
|
|
||||||
console.log(`======================================\n`);
|
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 => {
|
const checkForPermission = (perms: bigint, permission: bigint): boolean => {
|
||||||
// eslint-disable-next-line no-bitwise -- Since Discord uses bit flags...
|
// eslint-disable-next-line no-bitwise -- Since Discord uses bit flags...
|
||||||
return (perms & permission) === permission;
|
return (perms & permission) === permission;
|
||||||
};
|
};
|
||||||
|
|
||||||
const analyzeGuilds = (guilds: Array<Guild>): void => {
|
const analyzeGuilds = (guilds: Array<Guild>): Stats => {
|
||||||
// Arrays to store names instead of just counts
|
// Arrays to store names instead of just counts
|
||||||
const stats: Stats = {
|
const stats: Stats = {
|
||||||
admin: [],
|
admin: [],
|
||||||
community: [],
|
community: [],
|
||||||
|
discoverable: [],
|
||||||
moderating: [],
|
moderating: [],
|
||||||
owned: [],
|
owned: [],
|
||||||
partnered: [],
|
partnered: [],
|
||||||
@@ -129,12 +354,16 @@ const analyzeGuilds = (guilds: Array<Guild>): void => {
|
|||||||
* "COMMUNITY" feature enables public facing screens (Welcome Screen, Rules, etc)
|
* "COMMUNITY" feature enables public facing screens (Welcome Screen, Rules, etc)
|
||||||
* "DISCOVERABLE" means it appears in Server Discovery
|
* "DISCOVERABLE" means it appears in Server Discovery
|
||||||
*/
|
*/
|
||||||
if (features.includes("COMMUNITY") || features.includes("DISCOVERABLE")) {
|
if (features.includes("COMMUNITY")) {
|
||||||
stats.community.push(name ?? id);
|
stats.community.push(name ?? id);
|
||||||
}
|
}
|
||||||
|
if (features.includes("DISCOVERABLE")) {
|
||||||
|
stats.discoverable.push(name ?? id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
printReport(stats);
|
printReport(stats);
|
||||||
|
return stats;
|
||||||
};
|
};
|
||||||
|
|
||||||
const defaultRedirectUri = "http://127.0.0.1:8721/callback";
|
const defaultRedirectUri = "http://127.0.0.1:8721/callback";
|
||||||
@@ -434,7 +663,8 @@ async function getGuilds(): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const guilds: Array<Guild> = await response.json();
|
const guilds: Array<Guild> = await response.json();
|
||||||
analyzeGuilds(guilds);
|
const stats = analyzeGuilds(guilds);
|
||||||
|
await serveStatsDashboard(stats);
|
||||||
}
|
}
|
||||||
|
|
||||||
await getGuilds();
|
await getGuilds();
|
||||||
|
|||||||
Reference in New Issue
Block a user