This commit is contained in:
Clove 2026-06-26 02:50:30 +01:00
parent 22774e2ab8
commit 825e6ec5c7
2 changed files with 60 additions and 4 deletions

View File

@ -118,6 +118,41 @@ export function emojiUrl(id: string, animated: boolean): string {
return `${CDN}/emojis/${id}.${animated ? "gif" : "png"}?size=32`;
}
/**
* Resolve a Rich Presence activity asset (`activity.assets.large_image` /
* `small_image`) to an actual image URL. Discord prefixes these with a
* scheme for "external" assets (streaming previews, proxied media); a bare
* hash with no prefix is a normal app-asset on the CDN.
*/
// asset URL scheme handling adapted from pxseu/lanyard-ui, MPL-2.0
export function activityAssetUrl(raw: string | undefined | null, applicationId?: string | null): string | null {
if (!raw) return null;
const split = raw.split(":");
if (split.length < 2) {
// Plain hash — standard Rich Presence app-asset, needs the app id.
return applicationId ? `${CDN}/app-assets/${applicationId}/${raw}.png` : null;
}
switch (split[0]) {
case "mp":
// External Discord-proxied asset (e.g. attachment, embed image).
return `https://media.discordapp.net/${split.slice(1).join(":")}`;
case "twitch":
// Twitch stream preview.
return `https://static-cdn.jtvnw.net/previews-ttv/live_user_${split[1]}.png`;
case "youtube":
// YouTube live-stream thumbnail.
return `https://i.ytimg.com/vi/${split[1]}/hqdefault_live.jpg`;
case "spotify":
// Spotify album/cover art.
return `https://i.scdn.co/image/${split[1]}`;
default:
return applicationId ? `${CDN}/app-assets/${applicationId}/${raw}.png` : null;
}
}
// ---- collectibles (Shop wishlist) ---------------------------------------
// Discord product type ids -> our human-readable kind. See Userdoccers
// "Collectible Product Type". 1000/2000/3000 are bundle/variants/external.

View File

@ -3,7 +3,7 @@
* ===================================================================== */
import type { DiscordStatus, UnifiedCustomStatus, UnifiedPresence, UnifiedSpotify } from "./types";
import { emojiUrl } from "./discord/constants";
import { activityAssetUrl, emojiUrl } from "./discord/constants";
export interface RawPresence {
user: { id: string };
@ -54,8 +54,29 @@ function extractCustomStatus(activities: any[]): UnifiedCustomStatus | null {
};
}
/**
* Resolve an activity's `assets.large_image`/`small_image` (which can be a
* bare app-asset hash, or a scheme-prefixed external reference like
* "twitch:username" for streams) into actual, directly-loadable URLs.
* Leaves the original asset fields untouched and just adds the resolved
* `*_url` companions, so this is purely additive.
*/
function enrichActivityAssets(a: any): any {
if (!a || !a.assets) return a;
const appId = a.application_id ?? null;
return {
...a,
assets: {
...a.assets,
large_image_url: activityAssetUrl(a.assets.large_image, appId),
small_image_url: activityAssetUrl(a.assets.small_image, appId),
},
};
}
export function buildPresence(raw: RawPresence): UnifiedPresence {
const activities = Array.isArray(raw.activities) ? raw.activities : [];
const rawActivities = Array.isArray(raw.activities) ? raw.activities : [];
const activities = rawActivities.map(enrichActivityAssets);
const status: DiscordStatus = raw.status ?? "offline";
const cs = raw.client_status || {};
const spotify = extractSpotify(activities);