Bisher waren die Schalter über die Oberfläche verstreut: fünf Funktionen
hatten einen Haken, 22 weitere gingen nur über den Umweg „Kanal leeren".
Für jeden außer dem Owner war das nicht auffindbar.
Jetzt steht in src/modules.js ein zentrales Register aller Funktionen mit
Beschreibung, Standard-Zustand und den Einstellungen, die sie brauchen.
Daraus entsteht ein neuer Setup-Tab mit Karten: Schiebeschalter, kurze
Erklärung, Hinweis was noch fehlt ("Fehlt noch: Release-Kanal") und ein
Sprung zu den Einstellungen.
Damit die Schalter keine Attrappen sind, prüfen die Bot-Module jetzt an
18 Stellen zentral, ob sie laufen dürfen — Starboard, Galerie, Modmail,
Willkommen, Protokoll, Auto-Antworten, Sprachkanäle, Erinnerungen,
Events, Twitch/YouTube, Monitor, Wächter, Releases, Rückblick,
Geburtstage, Verlosungen und geplante Beiträge.
Funktionen mit vorhandenem Schalter (Level-System, Backups, Member-Gate …)
nutzen weiterhin dieselbe Einstellung, damit keine zweite Wahrheit
entsteht und bestehende Konfigurationen unverändert weiterlaufen.
Nebenbei gefunden und behoben: Beim Anlegen eines Modmail-Threads wurde
`member.client` benutzt, obwohl es an der Stelle kein `member` gibt — der
erste Modmail-Thread wäre mit einem Fehler abgebrochen.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
266 lines
11 KiB
JavaScript
266 lines
11 KiB
JavaScript
// Modul-Register: eine Stelle, an der alle Funktionen des Bots stehen.
|
|
//
|
|
// Jedes Modul hat einen Schalter im Webinterface. Damit nichts kaputtgeht,
|
|
// nutzen Funktionen mit einem bereits vorhandenen Schalter (etwa das
|
|
// Level-System) weiterhin denselben Einstellungs-Namen — es entsteht also
|
|
// keine zweite Wahrheit.
|
|
//
|
|
// `requires` beschreibt, was gesetzt sein muss, damit ein Modul wirklich
|
|
// arbeiten kann. Fehlt etwas, zeigt das Panel „noch nicht einsatzbereit"
|
|
// statt das Modul stillschweigend nichts tun zu lassen.
|
|
import { getSetting, setSetting } from './db.js';
|
|
|
|
export const MODULE_GROUPS = [
|
|
{ id: 'inhalte', label: 'Inhalte & Feeds' },
|
|
{ id: 'community', label: 'Community' },
|
|
{ id: 'moderation', label: 'Moderation & Support' },
|
|
{ id: 'server', label: 'Server & Technik' },
|
|
];
|
|
|
|
export const MODULES = [
|
|
/* ── Inhalte & Feeds ──────────────────────────── */
|
|
{
|
|
id: 'devlogs', group: 'inhalte', name: 'Devlogs',
|
|
desc: 'Entwicklungs-Berichte als gebrandetes Embed posten und durchsuchbar archivieren.',
|
|
tab: 'feeds', default: true,
|
|
requires: [{ setting: 'devlog_channel_id', label: 'Devlog-Kanal' }],
|
|
},
|
|
{
|
|
id: 'commit_feed', group: 'inhalte', name: 'Commit-Feed',
|
|
desc: 'Pushes aus Gitea landen als Embed im Kanal. Archiviert wird unabhängig davon immer.',
|
|
tab: 'feeds', setting: 'commit_feed_enabled', defaultOn: true,
|
|
requires: [{ setting: 'commit_channel_id', label: 'Commit-Kanal' }],
|
|
},
|
|
{
|
|
id: 'releases', group: 'inhalte', name: 'Release-Ankündigungen',
|
|
desc: 'Neue Releases werden angekündigt und landen im öffentlichen Changelog.',
|
|
tab: 'feeds', default: true,
|
|
requires: [{ setting: 'release_channel_id', label: 'Release-Kanal' }],
|
|
},
|
|
{
|
|
id: 'weekly_recap', group: 'inhalte', name: 'Wochen-Rückblick',
|
|
desc: 'Sonntags um 20 Uhr eine Zusammenfassung der Woche.',
|
|
tab: 'feeds', setting: 'weekly_recap_enabled', defaultOn: true,
|
|
requires: [{ setting: 'devlog_channel_id', label: 'Devlog-Kanal' }],
|
|
},
|
|
{
|
|
id: 'devlog_threads', group: 'inhalte', name: 'Devlog-Threads',
|
|
desc: 'Unter jedem Devlog entsteht ein Diskussions-Thread.',
|
|
tab: 'feeds', setting: 'devlog_threads_enabled', defaultOn: true,
|
|
},
|
|
{
|
|
id: 'social', group: 'inhalte', name: 'Twitch & YouTube',
|
|
desc: 'Meldet Livestreams und neue Videos.',
|
|
tab: 'system', default: true,
|
|
requires: [{ setting: 'social_announce_channel_id', label: 'Social-Kanal' }],
|
|
},
|
|
|
|
/* ── Community ────────────────────────────────── */
|
|
{
|
|
id: 'levels', group: 'community', name: 'Level-System',
|
|
desc: 'XP fürs Mitreden, Rollen-Belohnungen und öffentliche Bestenliste.',
|
|
tab: 'community', setting: 'levels_enabled', defaultOn: false,
|
|
},
|
|
{
|
|
id: 'starboard', group: 'community', name: 'Starboard',
|
|
desc: 'Nachrichten mit genug Sternen landen in einem Best-of-Kanal.',
|
|
tab: 'community', default: true,
|
|
requires: [{ setting: 'starboard_channel_id', label: 'Starboard-Kanal' }],
|
|
},
|
|
{
|
|
id: 'gallery', group: 'community', name: 'Screenshot-Galerie',
|
|
desc: 'Bilder aus einem Kanal erscheinen in der öffentlichen Galerie.',
|
|
tab: 'community', default: true,
|
|
requires: [{ setting: 'screenshot_channel_id', label: 'Screenshot-Kanal' }],
|
|
},
|
|
{
|
|
id: 'voting', group: 'community', name: 'Feature-Wünsche',
|
|
desc: 'Wünsche einreichen und abstimmen — im Discord und auf der Roadmap.',
|
|
tab: 'community', default: true,
|
|
requires: [{ setting: 'voting_channel_id', label: 'Voting-Kanal' }],
|
|
},
|
|
{
|
|
id: 'giveaways', group: 'community', name: 'Verlosungen',
|
|
desc: 'Teilnahme per Knopf, automatische Ziehung, Neuauslosung möglich.',
|
|
tab: 'community', default: true,
|
|
},
|
|
{
|
|
id: 'birthdays', group: 'community', name: 'Geburtstage',
|
|
desc: 'Gratulation am großen Tag, optional mit Tages-Rolle.',
|
|
tab: 'community', default: true,
|
|
requires: [{ setting: 'birthday_channel_id', label: 'Geburtstags-Kanal' }],
|
|
},
|
|
{
|
|
id: 'events', group: 'community', name: 'Event-Ankündigungen',
|
|
desc: 'Neue Discord-Events werden angekündigt und erscheinen auf der Events-Seite.',
|
|
tab: 'system', default: true,
|
|
requires: [{ setting: 'events_announce_channel_id', label: 'Event-Kanal' }],
|
|
},
|
|
{
|
|
id: 'playtester', group: 'community', name: 'Playtester-Programm',
|
|
desc: 'Bewerbungs-Knopf, Rolle und Teilnehmerliste.',
|
|
tab: 'community', default: true,
|
|
requires: [{ setting: 'playtester_role_id', label: 'Playtester-Rolle' }],
|
|
},
|
|
{
|
|
id: 'alpha_keys', group: 'community', name: 'Alpha-Keys',
|
|
desc: 'Schlüssel-Pool mit Verteilung per Direktnachricht.',
|
|
tab: 'community', default: true,
|
|
},
|
|
{
|
|
id: 'role_menus', group: 'community', name: 'Rollen-Menüs',
|
|
desc: 'Selbstbedienungs-Rollen per Knopf — im Discord und im Profil.',
|
|
tab: 'rollen', default: true,
|
|
},
|
|
{
|
|
id: 'applications', group: 'community', name: 'Bewerbungen',
|
|
desc: 'Formulare als Eingabefenster, Prüfung mit Annehmen/Ablehnen.',
|
|
tab: 'bewerbungen', default: true,
|
|
},
|
|
|
|
/* ── Moderation & Support ─────────────────────── */
|
|
{
|
|
id: 'moderation', group: 'moderation', name: 'Moderation',
|
|
desc: 'Verwarnen, Auszeiten, Aufräumen — mit Verlauf und Protokoll.',
|
|
tab: 'support', default: true,
|
|
},
|
|
{
|
|
id: 'modlog', group: 'moderation', name: 'Protokoll',
|
|
desc: 'Gelöschte und bearbeitete Nachrichten, Beitritte, Namens- und Rollenwechsel.',
|
|
tab: 'support', default: true,
|
|
requires: [{ setting: 'modlog_channel_id', label: 'Protokoll-Kanal' }],
|
|
},
|
|
{
|
|
id: 'modmail', group: 'moderation', name: 'Modmail',
|
|
desc: 'Direktnachrichten an den Bot landen als Thread beim Team.',
|
|
tab: 'support', default: true,
|
|
requires: [{ setting: 'modmail_channel_id', label: 'Modmail-Kanal' }],
|
|
},
|
|
{
|
|
id: 'tickets', group: 'moderation', name: 'Tickets',
|
|
desc: 'Private Threads per Knopf, beim Schließen ein Gesprächsprotokoll.',
|
|
tab: 'support', default: true,
|
|
requires: [{ setting: 'ticket_channel_id', label: 'Ticket-Kanal' }],
|
|
},
|
|
{
|
|
id: 'welcome', group: 'moderation', name: 'Willkommens-Karten',
|
|
desc: 'Begrüßung neuer Mitglieder mit gerendertem Bild.',
|
|
tab: 'support', default: true,
|
|
requires: [{ setting: 'welcome_channel_id', label: 'Willkommens-Kanal' }],
|
|
},
|
|
{
|
|
id: 'autorole', group: 'moderation', name: 'Auto-Rolle',
|
|
desc: 'Neue Mitglieder bekommen automatisch eine Startrolle.',
|
|
tab: 'community', default: true,
|
|
requires: [{ setting: 'autorole_id', label: 'Auto-Rolle' }],
|
|
},
|
|
{
|
|
id: 'sticky_roles', group: 'moderation', name: 'Rollen merken',
|
|
desc: 'Wer den Server verlässt und zurückkommt, bekommt seine Rollen wieder.',
|
|
tab: 'community', setting: 'sticky_roles_enabled', defaultOn: false,
|
|
},
|
|
{
|
|
id: 'bug_reports', group: 'moderation', name: 'Fehlermeldungen',
|
|
desc: 'Meldungen werden zu Issues im Repository, mit Rückmeldung beim Schließen.',
|
|
tab: 'system', default: true,
|
|
requires: [{ setting: 'gitea_api_token', label: 'Gitea-Token' }],
|
|
},
|
|
|
|
/* ── Server & Technik ─────────────────────────── */
|
|
{
|
|
id: 'server_monitor', group: 'server', name: 'Server-Monitor',
|
|
desc: 'Live-Status der Game-Server mit Verlauf und Ausfall-Alarm.',
|
|
tab: 'server', default: true,
|
|
},
|
|
{
|
|
id: 'watchdog', group: 'server', name: 'Erreichbarkeits-Wächter',
|
|
desc: 'Prüft hinterlegte Adressen und meldet Ausfälle per Direktnachricht.',
|
|
tab: 'system', default: true,
|
|
requires: [{ setting: 'watchdog_urls', label: 'Adressen' }],
|
|
},
|
|
{
|
|
id: 'temp_voice', group: 'server', name: 'Temporäre Sprachkanäle',
|
|
desc: 'Beitritt zum Hub erzeugt einen eigenen Kanal mit Bedienfeld.',
|
|
tab: 'system', default: true,
|
|
requires: [{ setting: 'tempvoice_channel_id', label: 'Hub-Kanal' }],
|
|
},
|
|
{
|
|
id: 'triggers', group: 'server', name: 'Auto-Antworten',
|
|
desc: 'Der Bot antwortet auf hinterlegte Schlüsselwörter.',
|
|
tab: 'composer', default: true,
|
|
},
|
|
{
|
|
id: 'reminders', group: 'server', name: 'Erinnerungen',
|
|
desc: 'Mitglieder lassen sich per Direktnachricht erinnern.',
|
|
tab: 'system', default: true,
|
|
},
|
|
{
|
|
id: 'scheduled_posts', group: 'server', name: 'Geplante Beiträge',
|
|
desc: 'Nachrichten zu festen Zeiten, einmalig oder wiederkehrend.',
|
|
tab: 'composer', default: true,
|
|
},
|
|
{
|
|
id: 'tags', group: 'server', name: 'Textbausteine',
|
|
desc: 'Gespeicherte Texte per Befehl abrufen.',
|
|
tab: 'composer', default: true,
|
|
},
|
|
{
|
|
id: 'backups', group: 'server', name: 'Datenbank-Sicherung',
|
|
desc: 'Nächtliche Sicherung mit Aufbewahrung über 14 Tage.',
|
|
tab: 'system', setting: 'backup_enabled', defaultOn: true,
|
|
},
|
|
{
|
|
id: 'repo_backups', group: 'server', name: 'Repository-Sicherung',
|
|
desc: 'Alle Gitea-Repositories als Bundle sichern.',
|
|
tab: 'system', setting: 'repo_backup_enabled', defaultOn: false,
|
|
},
|
|
{
|
|
id: 'member_gate', group: 'server', name: 'Login nur für Mitglieder',
|
|
desc: 'Nur wer auf dem Discord-Server ist, kann sich im Web anmelden.',
|
|
tab: 'system', setting: 'member_gate_enabled', defaultOn: true,
|
|
},
|
|
];
|
|
|
|
/** Einstellungs-Name des Schalters für ein Modul */
|
|
function toggleKey(mod) {
|
|
return mod.setting ?? `module_${mod.id}`;
|
|
}
|
|
|
|
/** Läuft dieses Modul? (unabhängig davon, ob alles Nötige konfiguriert ist) */
|
|
export function moduleEnabled(id) {
|
|
const mod = MODULES.find((m) => m.id === id);
|
|
if (!mod) return true; // unbekannt → nicht blockieren
|
|
const value = getSetting(toggleKey(mod));
|
|
if (value === '1') return true;
|
|
if (value === '0') return false;
|
|
return mod.setting ? Boolean(mod.defaultOn) : mod.default !== false;
|
|
}
|
|
|
|
/** Schalter setzen */
|
|
export function setModuleEnabled(id, on) {
|
|
const mod = MODULES.find((m) => m.id === id);
|
|
if (!mod) return false;
|
|
setSetting(toggleKey(mod), on ? '1' : '0');
|
|
return true;
|
|
}
|
|
|
|
/** Was fehlt diesem Modul noch? (leere Liste = einsatzbereit) */
|
|
export function moduleMissing(mod) {
|
|
return (mod.requires ?? [])
|
|
.filter((r) => !String(getSetting(r.setting) ?? '').trim())
|
|
.map((r) => r.label);
|
|
}
|
|
|
|
/** Alle Module mit Zustand — für das Webinterface */
|
|
export function moduleStates() {
|
|
return MODULES.map((mod) => ({
|
|
id: mod.id,
|
|
name: mod.name,
|
|
desc: mod.desc,
|
|
group: mod.group,
|
|
tab: mod.tab ?? null,
|
|
enabled: moduleEnabled(mod.id),
|
|
missing: moduleMissing(mod),
|
|
}));
|
|
}
|