Add 3rd party badges
This commit is contained in:
parent
a6a8b8a437
commit
17a67b5892
|
|
@ -14,6 +14,6 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@cloudflare/workers-types": "^4.20260617.1",
|
"@cloudflare/workers-types": "^4.20260617.1",
|
||||||
"typescript": "^6.0.3",
|
"typescript": "^6.0.3",
|
||||||
"wrangler": "^4.103.0"
|
"wrangler": "^4.105.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,8 @@ importers:
|
||||||
specifier: ^6.0.3
|
specifier: ^6.0.3
|
||||||
version: 6.0.3
|
version: 6.0.3
|
||||||
wrangler:
|
wrangler:
|
||||||
specifier: ^4.103.0
|
specifier: ^4.105.0
|
||||||
version: 4.103.0(@cloudflare/workers-types@4.20260617.1)
|
version: 4.105.0(@cloudflare/workers-types@4.20260617.1)
|
||||||
|
|
||||||
packages:
|
packages:
|
||||||
|
|
||||||
|
|
@ -33,32 +33,32 @@ packages:
|
||||||
workerd:
|
workerd:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@cloudflare/workerd-darwin-64@1.20260617.1':
|
'@cloudflare/workerd-darwin-64@1.20260625.1':
|
||||||
resolution: {integrity: sha512-jWwmgEVVWbsHNrLSNXzwjJaH90VzRxq1cWkQFUidxyeUPnMxemeNE8I9qFAfrpzGgE11e9sKDcE3ettJW08swQ==}
|
resolution: {integrity: sha512-naCfBv0WnnTQIQPTniqMoUlklOIFjrAcSn1X+IAOhY8aFLF/xGYtFjs1eEE8sFib3ZuChGGpU23FFORVczqr0A==}
|
||||||
engines: {node: '>=16'}
|
engines: {node: '>=16'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
|
|
||||||
'@cloudflare/workerd-darwin-arm64@1.20260617.1':
|
'@cloudflare/workerd-darwin-arm64@1.20260625.1':
|
||||||
resolution: {integrity: sha512-LHH7b565g9znfCUOkwbec6FG2rmRbsgCy6aJiU9KN662mNheWl5sw/iKleiFSiljPKQQP3HkjnC/NSkdgi/aSA==}
|
resolution: {integrity: sha512-jmH6zjp6Wrux46+qtFwDwrj+vd7s5bdwEqeGvdnwE0a4IEeAhKs0L42HQOyID+g5lkrHq9m55+AbhtmRAm63Pw==}
|
||||||
engines: {node: '>=16'}
|
engines: {node: '>=16'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
|
|
||||||
'@cloudflare/workerd-linux-64@1.20260617.1':
|
'@cloudflare/workerd-linux-64@1.20260625.1':
|
||||||
resolution: {integrity: sha512-FMnaAKXe4Cfd8TQurCVd9fs2XQVBFRCsP+Id/SRdUv89MlwYu9zXfoyx6BxM+brPTIUK38SHbo8iaxiwzLi9JQ==}
|
resolution: {integrity: sha512-MiQkpA/dX8d83Zp64pzHUKfd6ca4cvwxnNobSP6CnXvfESvnNI9pfa+nfwnParla36sPmnYntNkjR7NjRuDeKQ==}
|
||||||
engines: {node: '>=16'}
|
engines: {node: '>=16'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
'@cloudflare/workerd-linux-arm64@1.20260617.1':
|
'@cloudflare/workerd-linux-arm64@1.20260625.1':
|
||||||
resolution: {integrity: sha512-MRoifFYcqbxxIIQy7PqO5tFY/qPFSnjXzakWl0sO93l+HLyG35jRAgOi6jfqa4kBxc7gKKtH861DcewjxUfkjA==}
|
resolution: {integrity: sha512-LxxW7Qv60Xvv37+w6gUSDpYZziyqMy+cZWd9IvSA5ehVgKAxmzEaYPMiSZlxk32nbIWL9u/tfjXYCOKJ4Lo+XQ==}
|
||||||
engines: {node: '>=16'}
|
engines: {node: '>=16'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
'@cloudflare/workerd-windows-64@1.20260617.1':
|
'@cloudflare/workerd-windows-64@1.20260625.1':
|
||||||
resolution: {integrity: sha512-rgBV9wQrv0OSKgCTTbhFUFY3sLGNANZ88aqaLvtmEn2gmbFVb1J4PDGochVUdB7NSEp4D/ghHva6/8SZmbONpw==}
|
resolution: {integrity: sha512-LH6iIX1HHaTwVKV5VokDxxUErXJzQoNZFRwVm7Vx/3fB/ApcTcRCUaMqcxI4as94jEUqg+pmX5czOndiveohow==}
|
||||||
engines: {node: '>=16'}
|
engines: {node: '>=16'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
|
|
@ -436,8 +436,8 @@ packages:
|
||||||
resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==}
|
resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
|
|
||||||
miniflare@4.20260617.1:
|
miniflare@4.20260625.0:
|
||||||
resolution: {integrity: sha512-Go3/gzStm99QHptsSgU+q1S+xDfLoRgwjJNY80kaTVi0ENhTyqKq+sc4xZiWBSbM7uUcJwmzm8+QFKtcYLJ9nw==}
|
resolution: {integrity: sha512-3kKXwRUObJsnBYPBgR0NiNZYKF/yv8GFyha1cx2EeAEraxNODgRVcyeRo+F1ok1tg5Mg7iUpOWSkknQTHuFhwA==}
|
||||||
engines: {node: '>=22.0.0'}
|
engines: {node: '>=22.0.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
|
@ -475,17 +475,17 @@ packages:
|
||||||
unenv@2.0.0-rc.24:
|
unenv@2.0.0-rc.24:
|
||||||
resolution: {integrity: sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw==}
|
resolution: {integrity: sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw==}
|
||||||
|
|
||||||
workerd@1.20260617.1:
|
workerd@1.20260625.1:
|
||||||
resolution: {integrity: sha512-Re5pl6pdowt3ZmWUzGlOuB7jbRIIPetgKalmo4cYmucQnVhpo7/3e4MfpekbhLi2EhZZz5EY9NWRu8zFzuEZew==}
|
resolution: {integrity: sha512-GApQvFX52SDM6L4u0+RRnUDB1wJOnEwoXjinkmOPtIyofWBxrlZckdegJSYc1leg++lLZ3+DQ4zMVmBqYVtzfA==}
|
||||||
engines: {node: '>=16'}
|
engines: {node: '>=16'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
wrangler@4.103.0:
|
wrangler@4.105.0:
|
||||||
resolution: {integrity: sha512-3Lv1P5t2xcSEkSTKtG+Lz+3JFryuU7YPLkaCUj7gNe+CJsjZJLtUwqsh1x595QBxkIbCE0GAvDx2DCJUU4+oqw==}
|
resolution: {integrity: sha512-7dXFH6OLj1Fv0y6ZeRPUxFTkp+duWD7/xxVi/1c0vfOeEYwIFKWB7cdqnY05DvY1Ta3BnqAwRkXfLs8PDj538g==}
|
||||||
engines: {node: '>=22.0.0'}
|
engines: {node: '>=22.0.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@cloudflare/workers-types': ^4.20260617.1
|
'@cloudflare/workers-types': ^4.20260625.1
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
'@cloudflare/workers-types':
|
'@cloudflare/workers-types':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
@ -512,25 +512,25 @@ snapshots:
|
||||||
|
|
||||||
'@cloudflare/kv-asset-handler@0.5.0': {}
|
'@cloudflare/kv-asset-handler@0.5.0': {}
|
||||||
|
|
||||||
'@cloudflare/unenv-preset@2.16.1(unenv@2.0.0-rc.24)(workerd@1.20260617.1)':
|
'@cloudflare/unenv-preset@2.16.1(unenv@2.0.0-rc.24)(workerd@1.20260625.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
unenv: 2.0.0-rc.24
|
unenv: 2.0.0-rc.24
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
workerd: 1.20260617.1
|
workerd: 1.20260625.1
|
||||||
|
|
||||||
'@cloudflare/workerd-darwin-64@1.20260617.1':
|
'@cloudflare/workerd-darwin-64@1.20260625.1':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@cloudflare/workerd-darwin-arm64@1.20260617.1':
|
'@cloudflare/workerd-darwin-arm64@1.20260625.1':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@cloudflare/workerd-linux-64@1.20260617.1':
|
'@cloudflare/workerd-linux-64@1.20260625.1':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@cloudflare/workerd-linux-arm64@1.20260617.1':
|
'@cloudflare/workerd-linux-arm64@1.20260625.1':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@cloudflare/workerd-windows-64@1.20260617.1':
|
'@cloudflare/workerd-windows-64@1.20260625.1':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@cloudflare/workers-types@4.20260617.1': {}
|
'@cloudflare/workers-types@4.20260617.1': {}
|
||||||
|
|
@ -785,12 +785,12 @@ snapshots:
|
||||||
|
|
||||||
kleur@4.1.5: {}
|
kleur@4.1.5: {}
|
||||||
|
|
||||||
miniflare@4.20260617.1:
|
miniflare@4.20260625.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@cspotcode/source-map-support': 0.8.1
|
'@cspotcode/source-map-support': 0.8.1
|
||||||
sharp: 0.34.5
|
sharp: 0.34.5
|
||||||
undici: 7.28.0
|
undici: 7.28.0
|
||||||
workerd: 1.20260617.1
|
workerd: 1.20260625.1
|
||||||
ws: 8.21.0
|
ws: 8.21.0
|
||||||
youch: 4.1.0-beta.10
|
youch: 4.1.0-beta.10
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
|
|
@ -847,24 +847,24 @@ snapshots:
|
||||||
dependencies:
|
dependencies:
|
||||||
pathe: 2.0.3
|
pathe: 2.0.3
|
||||||
|
|
||||||
workerd@1.20260617.1:
|
workerd@1.20260625.1:
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@cloudflare/workerd-darwin-64': 1.20260617.1
|
'@cloudflare/workerd-darwin-64': 1.20260625.1
|
||||||
'@cloudflare/workerd-darwin-arm64': 1.20260617.1
|
'@cloudflare/workerd-darwin-arm64': 1.20260625.1
|
||||||
'@cloudflare/workerd-linux-64': 1.20260617.1
|
'@cloudflare/workerd-linux-64': 1.20260625.1
|
||||||
'@cloudflare/workerd-linux-arm64': 1.20260617.1
|
'@cloudflare/workerd-linux-arm64': 1.20260625.1
|
||||||
'@cloudflare/workerd-windows-64': 1.20260617.1
|
'@cloudflare/workerd-windows-64': 1.20260625.1
|
||||||
|
|
||||||
wrangler@4.103.0(@cloudflare/workers-types@4.20260617.1):
|
wrangler@4.105.0(@cloudflare/workers-types@4.20260617.1):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@cloudflare/kv-asset-handler': 0.5.0
|
'@cloudflare/kv-asset-handler': 0.5.0
|
||||||
'@cloudflare/unenv-preset': 2.16.1(unenv@2.0.0-rc.24)(workerd@1.20260617.1)
|
'@cloudflare/unenv-preset': 2.16.1(unenv@2.0.0-rc.24)(workerd@1.20260625.1)
|
||||||
blake3-wasm: 2.1.5
|
blake3-wasm: 2.1.5
|
||||||
esbuild: 0.28.1
|
esbuild: 0.28.1
|
||||||
miniflare: 4.20260617.1
|
miniflare: 4.20260625.0
|
||||||
path-to-regexp: 6.3.0
|
path-to-regexp: 6.3.0
|
||||||
unenv: 2.0.0-rc.24
|
unenv: 2.0.0-rc.24
|
||||||
workerd: 1.20260617.1
|
workerd: 1.20260625.1
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@cloudflare/workers-types': 4.20260617.1
|
'@cloudflare/workers-types': 4.20260617.1
|
||||||
fsevents: 2.3.3
|
fsevents: 2.3.3
|
||||||
|
|
|
||||||
|
|
@ -3,5 +3,11 @@ allowBuilds:
|
||||||
sharp: true
|
sharp: true
|
||||||
workerd: true
|
workerd: true
|
||||||
minimumReleaseAgeExclude:
|
minimumReleaseAgeExclude:
|
||||||
- miniflare@4.20260617.1
|
- miniflare@4.20260617.1 || 4.20260625.0
|
||||||
- wrangler@4.103.0
|
- wrangler@4.103.0 || 4.105.0
|
||||||
|
- '@cloudflare/workerd-darwin-64@1.20260625.1'
|
||||||
|
- '@cloudflare/workerd-darwin-arm64@1.20260625.1'
|
||||||
|
- '@cloudflare/workerd-linux-64@1.20260625.1'
|
||||||
|
- '@cloudflare/workerd-linux-arm64@1.20260625.1'
|
||||||
|
- '@cloudflare/workerd-windows-64@1.20260625.1'
|
||||||
|
- workerd@1.20260625.1
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,93 @@
|
||||||
|
import type { Env, UnifiedClientBadge } from "../types";
|
||||||
|
|
||||||
|
const API_BASE = "https://badges.equicord.org";
|
||||||
|
|
||||||
|
interface EquibadgesResponse {
|
||||||
|
status: number;
|
||||||
|
/** Flat array (no `separated` query) — one list across all services. */
|
||||||
|
badges: { tooltip: string; badge: string }[];
|
||||||
|
}
|
||||||
|
|
||||||
|
function cacheKey(id: string): string {
|
||||||
|
return `clientbadges:${id}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Cache freshness window — these change rarely, so an hour is plenty. */
|
||||||
|
const TTL_SECONDS = 3600;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch a user's third-party client-mod badges, cache-first.
|
||||||
|
* Returns [] when the user has none, null when the aggregator couldn't be
|
||||||
|
* reached and there's nothing usable cached either.
|
||||||
|
*/
|
||||||
|
export async function getClientBadges(
|
||||||
|
env: Env,
|
||||||
|
id: string,
|
||||||
|
ctx?: ExecutionContext,
|
||||||
|
force = false
|
||||||
|
): Promise<UnifiedClientBadge[] | null> {
|
||||||
|
if (!force) {
|
||||||
|
const cached = (await env.PROFILE_CACHE.get(cacheKey(id), "json")) as
|
||||||
|
| UnifiedClientBadge[]
|
||||||
|
| null;
|
||||||
|
if (cached) return cached;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fetched = await fetchClientBadges(id);
|
||||||
|
|
||||||
|
// 404 ("no badges found") is a valid, cacheable empty result — only a
|
||||||
|
// genuine fetch failure (network error, 5xx, etc.) should fall through.
|
||||||
|
if (fetched === undefined) {
|
||||||
|
if (force) {
|
||||||
|
// Caller explicitly asked for a fresh fetch; fall back to whatever's
|
||||||
|
// cached (even if stale) rather than returning null outright.
|
||||||
|
const stale = (await env.PROFILE_CACHE.get(cacheKey(id), "json")) as
|
||||||
|
| UnifiedClientBadge[]
|
||||||
|
| null;
|
||||||
|
return stale ?? null;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const write = env.PROFILE_CACHE.put(cacheKey(id), JSON.stringify(fetched), {
|
||||||
|
expirationTtl: TTL_SECONDS,
|
||||||
|
});
|
||||||
|
if (ctx) ctx.waitUntil(write);
|
||||||
|
else await write;
|
||||||
|
|
||||||
|
return fetched;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns the badge list, [] for none, or undefined on a fetch failure. */
|
||||||
|
async function fetchClientBadges(id: string): Promise<UnifiedClientBadge[] | undefined> {
|
||||||
|
let res: Response;
|
||||||
|
try {
|
||||||
|
res = await fetch(`${API_BASE}/${id}`);
|
||||||
|
} catch {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (res.status === 404) return [];
|
||||||
|
if (!res.ok) return undefined;
|
||||||
|
|
||||||
|
let data: EquibadgesResponse;
|
||||||
|
try {
|
||||||
|
data = (await res.json()) as EquibadgesResponse;
|
||||||
|
} catch {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Array.isArray(data.badges)) return [];
|
||||||
|
|
||||||
|
return data.badges
|
||||||
|
.filter((b) => b && typeof b.badge === "string")
|
||||||
|
// The aggregator also throws in official Discord badges (HypeSquad,
|
||||||
|
// Nitro, etc) under /public/badges/discord/. Those already live in
|
||||||
|
// `data.badges` via Discord's own flags, so drop them here to avoid
|
||||||
|
// duplicating them under clientBadges.
|
||||||
|
.filter((b) => !/\/public\/badges\/discord\//i.test(b.badge))
|
||||||
|
.map((b) => ({
|
||||||
|
tooltip: typeof b.tooltip === "string" ? b.tooltip : "",
|
||||||
|
icon_url: b.badge,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
@ -115,6 +115,7 @@ export default {
|
||||||
user: profile.user,
|
user: profile.user,
|
||||||
presence,
|
presence,
|
||||||
badges: profile.badges,
|
badges: profile.badges,
|
||||||
|
clientBadges: profile.clientBadges,
|
||||||
connected_accounts: profile.connected_accounts,
|
connected_accounts: profile.connected_accounts,
|
||||||
wishlist: profile.wishlist ?? null,
|
wishlist: profile.wishlist ?? null,
|
||||||
updated_at: Date.now(),
|
updated_at: Date.now(),
|
||||||
|
|
|
||||||
|
|
@ -9,10 +9,12 @@
|
||||||
import type {
|
import type {
|
||||||
Env,
|
Env,
|
||||||
UnifiedBadge,
|
UnifiedBadge,
|
||||||
|
UnifiedClientBadge,
|
||||||
UnifiedConnectedAccount,
|
UnifiedConnectedAccount,
|
||||||
UnifiedUser,
|
UnifiedUser,
|
||||||
UnifiedWishlistItem,
|
UnifiedWishlistItem,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
|
import { getClientBadges } from "./discord/clientBadges";
|
||||||
import {
|
import {
|
||||||
avatarDecorationImageUrl,
|
avatarDecorationImageUrl,
|
||||||
avatarUrl,
|
avatarUrl,
|
||||||
|
|
@ -37,6 +39,8 @@ import {
|
||||||
export interface ProfileResult {
|
export interface ProfileResult {
|
||||||
user: UnifiedUser;
|
user: UnifiedUser;
|
||||||
badges: UnifiedBadge[];
|
badges: UnifiedBadge[];
|
||||||
|
/** Third-party client-mod badges (Vencord/Equicord/Aliucord/etc). */
|
||||||
|
clientBadges: UnifiedClientBadge[] | null;
|
||||||
connected_accounts: UnifiedConnectedAccount[];
|
connected_accounts: UnifiedConnectedAccount[];
|
||||||
/** Shop collectibles saved to the profile; null when unavailable. */
|
/** Shop collectibles saved to the profile; null when unavailable. */
|
||||||
wishlist: UnifiedWishlistItem[] | null;
|
wishlist: UnifiedWishlistItem[] | null;
|
||||||
|
|
@ -393,6 +397,7 @@ function mergeRichOverBot(cached: CachedProfile, bot: ProfileResult): CachedProf
|
||||||
display_name_styles: cached.user.display_name_styles,
|
display_name_styles: cached.user.display_name_styles,
|
||||||
},
|
},
|
||||||
badges: cached.badges.length ? cached.badges : bot.badges,
|
badges: cached.badges.length ? cached.badges : bot.badges,
|
||||||
|
clientBadges: bot.clientBadges != null ? bot.clientBadges : cached.clientBadges,
|
||||||
connected_accounts: cached.connected_accounts.length
|
connected_accounts: cached.connected_accounts.length
|
||||||
? cached.connected_accounts
|
? cached.connected_accounts
|
||||||
: bot.connected_accounts,
|
: bot.connected_accounts,
|
||||||
|
|
@ -411,6 +416,7 @@ async function writeCache(env: Env, id: string, result: ProfileResult): Promise<
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
user: result.user,
|
user: result.user,
|
||||||
badges: result.badges,
|
badges: result.badges,
|
||||||
|
clientBadges: result.clientBadges,
|
||||||
connected_accounts: result.connected_accounts,
|
connected_accounts: result.connected_accounts,
|
||||||
wishlist: result.wishlist,
|
wishlist: result.wishlist,
|
||||||
}),
|
}),
|
||||||
|
|
@ -481,9 +487,17 @@ async function buildFreshProfile(
|
||||||
// Wishlist rides on the rich profile (`wishlist_settings`); resolve its
|
// Wishlist rides on the rich profile (`wishlist_settings`); resolve its
|
||||||
// SKUs to names + images (cache-first). null if the field is absent.
|
// SKUs to names + images (cache-first). null if the field is absent.
|
||||||
const wishlist = await buildWishlist(env, profile, ctx, force);
|
const wishlist = await buildWishlist(env, profile, ctx, force);
|
||||||
|
const clientBadges = await getClientBadges(env, id, ctx, force);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
result: { user: buildUser(u, bio, pronouns, themeColors), badges, connected_accounts: connected, wishlist, source: "user" },
|
result: {
|
||||||
|
user: buildUser(u, bio, pronouns, themeColors),
|
||||||
|
badges,
|
||||||
|
clientBadges,
|
||||||
|
connected_accounts: connected,
|
||||||
|
wishlist,
|
||||||
|
source: "user",
|
||||||
|
},
|
||||||
richStatus,
|
richStatus,
|
||||||
retryAfter,
|
retryAfter,
|
||||||
};
|
};
|
||||||
|
|
@ -493,10 +507,12 @@ async function buildFreshProfile(
|
||||||
// have here, so it's null and the cache-merge keeps any previously cached one.
|
// have here, so it's null and the cache-merge keeps any previously cached one.
|
||||||
const u = await fetchBotUser(env, id);
|
const u = await fetchBotUser(env, id);
|
||||||
if (!u) return { result: null, richStatus, retryAfter };
|
if (!u) return { result: null, richStatus, retryAfter };
|
||||||
|
const clientBadges = await getClientBadges(env, id, ctx, force);
|
||||||
return {
|
return {
|
||||||
result: {
|
result: {
|
||||||
user: buildUser(u, null, null, null),
|
user: buildUser(u, null, null, null),
|
||||||
badges: flagBadges(u.public_flags ?? u.flags ?? 0),
|
badges: flagBadges(u.public_flags ?? u.flags ?? 0),
|
||||||
|
clientBadges,
|
||||||
connected_accounts: [],
|
connected_accounts: [],
|
||||||
wishlist: null,
|
wishlist: null,
|
||||||
source: "bot",
|
source: "bot",
|
||||||
|
|
|
||||||
18
src/types.ts
18
src/types.ts
|
|
@ -52,6 +52,20 @@ export interface UnifiedBadge {
|
||||||
source: "flags" | "profile";
|
source: "flags" | "profile";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A badge sourced from a third-party client-mod badge aggregator
|
||||||
|
* (badges.equicord.org), covering Vencord/Equicord/Aliucord/etc + the
|
||||||
|
* "global badges" set it aggregates. Deliberately separate from
|
||||||
|
* `badges` (Discord's own flag/profile badges) since these come from an
|
||||||
|
* unofficial third-party service.
|
||||||
|
*/
|
||||||
|
export interface UnifiedClientBadge {
|
||||||
|
/** Tooltip text the client mod shows for this badge. */
|
||||||
|
tooltip: string;
|
||||||
|
/** Absolute URL to the badge icon (png/gif/webp/svg). */
|
||||||
|
icon_url: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface UnifiedConnectedAccount {
|
export interface UnifiedConnectedAccount {
|
||||||
type: string;
|
type: string;
|
||||||
id: string;
|
id: string;
|
||||||
|
|
@ -182,6 +196,10 @@ export interface UnifiedRecord {
|
||||||
/** null when the user shares no monitored guild with the bot. */
|
/** null when the user shares no monitored guild with the bot. */
|
||||||
presence: UnifiedPresence | null;
|
presence: UnifiedPresence | null;
|
||||||
badges: UnifiedBadge[];
|
badges: UnifiedBadge[];
|
||||||
|
/** Third-party client-mod badges (Vencord/Equicord/Aliucord/etc, via
|
||||||
|
* badges.equicord.org's "global badges" aggregation). [] if none found,
|
||||||
|
* null if the aggregator couldn't be reached. */
|
||||||
|
clientBadges: UnifiedClientBadge[] | null;
|
||||||
connected_accounts: UnifiedConnectedAccount[];
|
connected_accounts: UnifiedConnectedAccount[];
|
||||||
/** Discord Shop collectibles the user saved to their profile wishlist.
|
/** Discord Shop collectibles the user saved to their profile wishlist.
|
||||||
* null when unavailable (no user token / proxy, or the source was blocked);
|
* null when unavailable (no user token / proxy, or the source was blocked);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue