Linked Roles: Rollen, die an geprueften Werten haengen
Deploy / check (push) Has been cancelled
Deploy / deploy (push) Has been cancelled

Neues Modul, standardmaessig aus. Ein Mitglied klickt in Discord auf "Rolle
holen", landet auf /linked-roles, bestaetigt den OAuth-Dialog — und der Bot
schiebt ihm drei Kennzahlen zu, die Discord dann gegen die Rollen-Einstellung
prueft:

  level        Zahl >= eingestelltem Wert
  dabei_seit   Datum <= "vor X Tagen", also Mitglied seit mindestens X Tagen
  playtester   Wahrheitswert

Der Bot vergibt dabei keine Rollen. Er liefert nur die Zahlen; was daraus
wird, entscheidet die Rollen-Einstellung im Server. Die Daten kommen aus dem,
was ohnehin da ist: Level-Tabelle, Playtester-Liste, Discords Beitrittsdatum.

Eigener OAuth-Weg statt /auth/login, weil die Berechtigungen andere sind —
role_connections.write hat der normale Login nicht.

Die Tokens werden gespeichert, und das ist keine Bequemlichkeit: ohne
Refresh-Token bliebe jede Rolle auf dem Stand des Verknuepfungs-Tages stehen,
ein Level-Up kaeme nie an. Discords eigene Anleitung sagt dasselbe. Der
Umfang ist eng (nur identify + role_connections.write), beim Widerruf oder
Entkoppeln fliegt der Datensatz sofort raus. Steht so auch als Kommentar an
der Tabelle.

Abgeglichen wird alle 6 Stunden, einstellbar. Dafuer kann everyTuned jetzt
auch Stunden — und wirft bei einer unbekannten Einheit, statt den Abstand
still um Faktor 60 oder 3600 danebenzulegen.

Beim Bauen gefunden: moduleEnabled fehlte im Import von client.js. Der Bot
waere beim ClientReady mit ReferenceError gestorben.

Geprueft: alle drei Kennzahlen gegen Discords Formatgrenzen (Schluessel-Regex,
Laengen, gueltige Typ-Codes), Modul- und Stellwert-Register, die
Stunden-Umrechnung (6 h = 21600000 ms), Speichern und Loeschen einer
Verknuepfung, dazu 186 SQL-Abfragen und alle Routen auf Rechtepruefung.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-01 05:57:57 +02:00
co-authored by Claude Fable 5
parent 55e9ef57e2
commit 137452da11
9 changed files with 358 additions and 2 deletions
+36
View File
@@ -1074,6 +1074,42 @@ export function setSetting(key, value) {
setSettingStmt.run(key, value);
}
// Linked Roles: OAuth-Tokens der Mitglieder, die ihr Konto verknüpft haben.
//
// Das Speichern ist nicht optional — ohne Refresh-Token bliebe die verknüpfte
// Rolle für immer auf dem Stand des Verknüpfungs-Tages stehen, ein Level-Up
// käme nie an. Discords eigene Anleitung sagt dasselbe.
//
// Die Tokens gelten NUR für role_connections.write und identify. Damit kann
// man weder Nachrichten lesen noch im Namen des Users handeln. Trotzdem:
// wer die Datenbank hat, hat diese Tokens — sie liegen im selben Volume wie
// alles andere und werden beim Entkoppeln sofort gelöscht.
db.exec(`
CREATE TABLE IF NOT EXISTS role_connections (
user_id TEXT PRIMARY KEY,
username TEXT,
access_token TEXT NOT NULL,
refresh_token TEXT NOT NULL,
expires_at INTEGER NOT NULL,
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
);
`);
const upsertRoleConnStmt = db.prepare(`
INSERT INTO role_connections (user_id, username, access_token, refresh_token, expires_at, updated_at)
VALUES (@user_id, @username, @access_token, @refresh_token, @expires_at, datetime('now'))
ON CONFLICT(user_id) DO UPDATE SET
username = excluded.username, access_token = excluded.access_token,
refresh_token = excluded.refresh_token, expires_at = excluded.expires_at,
updated_at = datetime('now')
`);
const getRoleConnStmt = db.prepare('SELECT * FROM role_connections WHERE user_id = ?');
const allRoleConnStmt = db.prepare('SELECT * FROM role_connections');
const deleteRoleConnStmt = db.prepare('DELETE FROM role_connections WHERE user_id = ?');
export const saveRoleConnection = (r) => upsertRoleConnStmt.run(r);
export const getRoleConnection = (userId) => getRoleConnStmt.get(userId) ?? null;
export const allRoleConnections = () => allRoleConnStmt.all();
export const deleteRoleConnection = (userId) => deleteRoleConnStmt.run(userId).changes > 0;
// Einmalige Übernahme der alten kommagetrennten Watchdog-Liste in die
// Dienst-Tabelle. Muss hier unten stehen, weil sie getSetting() braucht.
//