diff --git a/src/discord/clientBadges.ts b/src/discord/clientBadges.ts index 666d49a..682013a 100644 --- a/src/discord/clientBadges.ts +++ b/src/discord/clientBadges.ts @@ -1,3 +1,18 @@ +/* ===================================================================== + * discord/clientBadges.ts — third-party client-mod badges. + * + * badges.equicord.org aggregates badges from Vencord, Equicord, Aliucord, + * and a bunch of other client mods (Nekocord, ReviewDB, BadgeVault, Aero, + * Raincord, Velocity, Enmity, Replugged, Paicord) into one "global badges" + * style response. We just hit the plain GET /:userId (no query params) — + * that's already the combined view across every service — and cache it, + * since none of this is ours to rate-limit. + * + * This is intentionally kept separate from `badges` (Discord's own + * flags/profile badges): different source, different trust level, and the + * caller asked for it to live at `data.clientBadges` instead. + * ===================================================================== */ + import type { Env, UnifiedClientBadge } from "../types"; const API_BASE = "https://badges.equicord.org"; @@ -86,8 +101,29 @@ async function fetchClientBadges(id: string): Promise !/\/public\/badges\/discord\//i.test(b.badge)) - .map((b) => ({ - tooltip: typeof b.tooltip === "string" ? b.tooltip : "", - icon_url: b.badge, - })); + .map((b) => { + const tooltip = typeof b.tooltip === "string" ? b.tooltip : ""; + return { + id: badgeId(tooltip, b.badge), + tooltip, + icon_url: b.badge, + }; + }); +} + +/** + * Deterministic id for a badge — the upstream API has no id field of its + * own (these are arbitrary per-user badges, not a fixed catalog), so we + * derive a stable short hash from tooltip+icon_url. Same badge -> same id + * every time, which is all that's needed for React keys / dedup / lookups. + */ +function badgeId(tooltip: string, iconUrl: string): string { + const input = `${tooltip}\u0000${iconUrl}`; + // FNV-1a 32-bit — fast, sync, good enough distribution for this purpose. + let hash = 0x811c9dc5; + for (let i = 0; i < input.length; i++) { + hash ^= input.charCodeAt(i); + hash = Math.imul(hash, 0x01000193); + } + return (hash >>> 0).toString(16).padStart(8, "0"); } \ No newline at end of file diff --git a/src/types.ts b/src/types.ts index a95dea7..3678aaa 100644 --- a/src/types.ts +++ b/src/types.ts @@ -60,6 +60,9 @@ export interface UnifiedBadge { * unofficial third-party service. */ export interface UnifiedClientBadge { + /** Stable id derived from tooltip+icon_url (the upstream API has no id of + * its own — these are arbitrary per-user badges, not a fixed catalog). */ + id: string; /** Tooltip text the client mod shows for this badge. */ tooltip: string; /** Absolute URL to the badge icon (png/gif/webp/svg). */