From 99ce783b96dd9cba3ba508cc8ce560dcdfbf44bd Mon Sep 17 00:00:00 2001 From: Ally Founder Date: Mon, 10 Aug 2026 14:07:00 -0400 Subject: [PATCH] typing-avatars: add avatar URL resolution helper --- plugins/typing-avatars/src/lib/avatarUrl.ts | 34 +++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 plugins/typing-avatars/src/lib/avatarUrl.ts diff --git a/plugins/typing-avatars/src/lib/avatarUrl.ts b/plugins/typing-avatars/src/lib/avatarUrl.ts new file mode 100644 index 0000000..a043fe7 --- /dev/null +++ b/plugins/typing-avatars/src/lib/avatarUrl.ts @@ -0,0 +1,34 @@ +import { findByStoreName } from "@vendetta/metro"; + +const UserStore = findByStoreName("UserStore"); +const GuildMemberStore = findByStoreName("GuildMemberStore"); + +function avatarExtension(hash: string): string { + return hash.startsWith("a_") ? "gif" : "png"; +} + +// member.avatar / user.avatar are the property names Discord's client uses for a per-server / +// global avatar hash respectively - GuildMemberStore.getMember and UserStore.getUser are both +// confirmed-working lookups elsewhere in this repo (typingWrapper.ts, getTag.ts), but the avatar +// hash field itself hasn't been live-verified yet. If avatars come back broken, check these two +// property reads first via devtools eval against a real member/user record. +export function getTypingAvatarURL(guildId: string | undefined, userId: string, size = 32): string | null { + if (!userId) return null; + + const member = guildId ? GuildMemberStore?.getMember?.(guildId, userId) : null; + const guildAvatarHash = member?.avatar; + if (guildId && guildAvatarHash) { + return `https://cdn.discordapp.com/guilds/${guildId}/users/${userId}/avatars/${guildAvatarHash}.${avatarExtension(guildAvatarHash)}?size=${size * 2}`; + } + + const user = UserStore?.getUser?.(userId); + const globalAvatarHash = user?.avatar; + if (globalAvatarHash) { + return `https://cdn.discordapp.com/avatars/${userId}/${globalAvatarHash}.${avatarExtension(globalAvatarHash)}?size=${size * 2}`; + } + + // Discord's default avatar for users on the modern username system (no discriminator): + // index = (snowflake >> 22) % 6. BigInt is required - the shift loses precision as a Number. + const defaultIndex = Number((BigInt(userId) >> 22n) % 6n); + return `https://cdn.discordapp.com/embed/avatars/${defaultIndex}.png`; +}