feat: Discord-Bild und -Name im Dashboard

Wer ueber Discord hereinkommt, sieht sein Bild in der Kopfzeile, auf der
Kontoseite und neben seinen Eintraegen im Verlauf.

DIE ADRESSE BAUT DER DIENST, nicht die Oberflaeche. Gespeichert wird nur der
Bildstempel; das URL-Schema von Discord steht an EINER Stelle (avatarUrl in
db.ts) und nicht in jeder Ansicht, die ein Bild zeigt. Es hat drei Faelle, und
alle drei stehen dort:

  - Stempel faengt mit "a_" an  -> bewegtes Bild, muss als .gif angefragt werden
  - Stempel vorhanden           -> .png mit gewuenschter Groesse
  - kein Stempel                -> Discords Ersatzbild, dessen Nummer sich aus
                                   der ID ergibt: (id >> 22) % 6. Das ist die
                                   NEUE Rechnung; die alte ueber den
                                   Diskriminator gilt seit den Pseudonymen
                                   nicht mehr. Gegengerechnet an drei IDs.

Bild und Anzeigename werden BEI JEDER ANMELDUNG nachgezogen: wer sein Bild
wechselt, saehe sonst monatelang das alte und fragte sich, ob er im richtigen
Konto sitzt. Der Benutzername im Dashboard bleibt dagegen, wie er ist -- an ihm
haengen die Eintraege im Verlauf.

Der Avatar-Baustein kennt drei Faelle und keinen kaputten: Bild, sonst die
Initialen auf einer Farbe, die sich aus dem Namen ergibt (damit sie beim
Neuladen nicht springt), und fuer einen Token einen Schluessel -- ein Token hat
kein Gesicht, ein Platzhalterbild daneben waere eine Behauptung. Laedt das Bild
nicht (CDN nicht erreichbar, Stempel veraltet), faellt es auf die Initialen
zurueck statt ein kaputtes Bildsymbol zu zeigen.

Nachgemessen: cdn.discordapp.com liefert das Ersatzbild (200, image/png), die
Form der Bildadresse stimmt, und ein Passwort-Konto bekommt sauber avatar:null.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-11 18:59:42 +02:00
co-authored by Claude Opus 5
parent 23405d68d3
commit 4f149e365f
8 changed files with 260 additions and 36 deletions
+36
View File
@@ -116,6 +116,15 @@ CREATE TABLE IF NOT EXISTS settings (
db.exec('CREATE UNIQUE INDEX IF NOT EXISTS users_discord ON users(discord_id)')
console.log('[db] Spalte users.discord_id angelegt')
}
// Der Bildstempel, NICHT die fertige Adresse: Discord baut die URL aus ID
// und Stempel zusammen, und das Schema gehoert an eine Stelle (siehe
// avatarUrl unten). Eine gespeicherte URL waere beim naechsten Wechsel des
// Schemas eine Baustelle in jeder Zeile.
if (!spalten.some((s) => s.name === 'discord_avatar')) {
db.exec('ALTER TABLE users ADD COLUMN discord_avatar TEXT')
db.exec('ALTER TABLE users ADD COLUMN discord_name TEXT')
console.log('[db] Spalten users.discord_avatar und users.discord_name angelegt')
}
}
// Den Verlauf aus dem vorhandenen Bestand nachtragen -- einmalig, und nur
@@ -151,6 +160,33 @@ export type User = {
/** Gesetzt, wenn dieses Konto ueber Discord hereinkommt. Ein solches Konto
* hat kein brauchbares Passwort — verifyPassword scheitert daran sauber. */
discord_id: string | null
/** Der Bildstempel von Discord, nicht die Adresse. Null heisst: kein
* eigenes Bild, dann gilt Discords Ersatzbild. */
discord_avatar: string | null
discord_name: string | null
}
/** Die Adresse des Discord-Bildes — an EINER Stelle gebaut.
*
* Faengt der Stempel mit "a_" an, ist es ein bewegtes Bild und muss als .gif
* angefragt werden; sonst kommt ein Standbild. Ohne eigenen Stempel gibt es
* Discords Ersatzbild, dessen Nummer sich aus der ID ergibt (die neue
* Rechnung mit >> 22, nicht die alte ueber den Diskriminator).
*
* Null nur, wenn gar keine Discord-ID da ist — dann zeigt die Oberflaeche
* Initialen. */
export function avatarUrl(
discordId: string | null,
stempel: string | null,
groesse = 64,
): string | null {
if (!discordId) return null
if (stempel) {
const endung = stempel.startsWith('a_') ? 'gif' : 'png'
return `https://cdn.discordapp.com/avatars/${discordId}/${stempel}.${endung}?size=${groesse}`
}
const nummer = Number((BigInt(discordId) >> 22n) % 6n)
return `https://cdn.discordapp.com/embed/avatars/${nummer}.png`
}
export type Token = {