// 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), })); }