diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fa6a73d..35f05f6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -10,13 +10,13 @@ importers: devDependencies: '@cloudflare/workers-types': specifier: ^4.20260617.1 - version: 4.20260617.1 + version: 4.20260625.1 typescript: specifier: ^6.0.3 version: 6.0.3 wrangler: specifier: ^4.105.0 - version: 4.105.0(@cloudflare/workers-types@4.20260617.1) + version: 4.105.0(@cloudflare/workers-types@4.20260625.1) packages: @@ -63,8 +63,8 @@ packages: cpu: [x64] os: [win32] - '@cloudflare/workers-types@4.20260617.1': - resolution: {integrity: sha512-HdbP3CNcdMZBwegitFDjWvzv+6wPkFXvV9gBXMnf6RjV2Cy3W8TJL3IhSEGul0S6F1DHjnucP7lrpIsvkzNEjA==} + '@cloudflare/workers-types@4.20260625.1': + resolution: {integrity: sha512-asH0RhPHiNu/IUSssyiOJYAcGqysy0DJpO9fihC6KATaayD9CE1E9bgNQozTLUraxrCT2qkM4CBOIcV0M5NPJw==} '@cspotcode/source-map-support@0.8.1': resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} @@ -447,8 +447,8 @@ packages: pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} - semver@7.8.4: - resolution: {integrity: sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA==} + semver@7.8.5: + resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==} engines: {node: '>=10'} hasBin: true @@ -533,7 +533,7 @@ snapshots: '@cloudflare/workerd-windows-64@1.20260625.1': optional: true - '@cloudflare/workers-types@4.20260617.1': {} + '@cloudflare/workers-types@4.20260625.1': {} '@cspotcode/source-map-support@0.8.1': dependencies: @@ -801,13 +801,13 @@ snapshots: pathe@2.0.3: {} - semver@7.8.4: {} + semver@7.8.5: {} sharp@0.34.5: dependencies: '@img/colour': 1.1.0 detect-libc: 2.1.2 - semver: 7.8.4 + semver: 7.8.5 optionalDependencies: '@img/sharp-darwin-arm64': 0.34.5 '@img/sharp-darwin-x64': 0.34.5 @@ -855,7 +855,7 @@ snapshots: '@cloudflare/workerd-linux-arm64': 1.20260625.1 '@cloudflare/workerd-windows-64': 1.20260625.1 - wrangler@4.105.0(@cloudflare/workers-types@4.20260617.1): + wrangler@4.105.0(@cloudflare/workers-types@4.20260625.1): dependencies: '@cloudflare/kv-asset-handler': 0.5.0 '@cloudflare/unenv-preset': 2.16.1(unenv@2.0.0-rc.24)(workerd@1.20260625.1) @@ -866,7 +866,7 @@ snapshots: unenv: 2.0.0-rc.24 workerd: 1.20260625.1 optionalDependencies: - '@cloudflare/workers-types': 4.20260617.1 + '@cloudflare/workers-types': 4.20260625.1 fsevents: 2.3.3 transitivePeerDependencies: - bufferutil diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index e193f9f..26e6ee4 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -5,5 +5,5 @@ allowBuilds: sharp: true workerd: true minimumReleaseAgeExclude: - - miniflare@4.20260625.1 - - wrangler@4.105.0 \ No newline at end of file + - miniflare@4.20260625.0 || 4.20260625.1 + - wrangler@4.105.0 diff --git a/src/discord/clientBadges.ts b/src/discord/clientBadges.ts index 682013a..e20c998 100644 --- a/src/discord/clientBadges.ts +++ b/src/discord/clientBadges.ts @@ -112,18 +112,43 @@ async function fetchClientBadges(id: string): Promise display-name table. The equicord endpoint aggregates several + * underlying services, and the only thing in the response that tells us + * which one a given badge actually came from is the hostname of its icon + * URL. Add new services here as we spot them in the wild — anything not + * listed falls back to "Equicord" (the aggregator's own native badges, + * e.g. /public/badges/equicord.png, live on that host too). + * + * Still TBD / not yet seen in a sample response: ReviewDB, Aero, Aliucord, + * Raincord, Velocity, Enmity, Replugged, Paicord. + */ +const SOURCE_BY_HOST: Record = { + "gb.obamabot.me": "BadgeVault", + "nekocord.dev": "Neokcord", + "badges.vencord.dev": "Vencord", + "badges.equicord.org": "Equicord", +}; + +/** Falls back to "Equicord" for unknown/malformed hosts. */ +function sourceForIconUrl(iconUrl: string): string { + try { + const host = new URL(iconUrl).hostname; + return SOURCE_BY_HOST[host] ?? "Equicord"; + } catch { + return "Equicord"; + } +} + +/** + * Human-readable 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. + * build one from the originating service + tooltip, e.g. + * "GlobalBadges - Moffman" or "Equicord - Equicord Contributor". + * + * Note: this is NOT guaranteed unique — two badges from the same service + * with the same tooltip text collide. Fine for display/grouping; if exact + * per-badge identity is ever needed, icon_url is the only unique field. */ 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"); + return `${sourceForIconUrl(iconUrl)} - ${tooltip}`; } \ No newline at end of file