Files
d4rkbot/src/runtime-settings.js
T
D4rkst3randClaude Fable 5 bea35234c6
Deploy / check (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
„Einrichten" zeigt jetzt, was fehlt — und Env-Werte gelten als gesetzt
Zwei Fehler, die zusammen für Ratlosigkeit gesorgt haben.

In der Liste stand „Fehlermeldungen — Gitea-Token", obwohl das Token über
GITEA_API_TOKEN längst gesetzt war und /bug lief. Grund: die
Voraussetzungs-Prüfung las die Einstellung roh statt den wirksamen Wert,
sah also nur in die Datenbank und nicht in die Umgebung. Betraf außerdem
Devlog-Kanal, Commit-Kanal und Guild-ID. runtime-settings hat dafür jetzt
effectiveSetting(), das beides zusammenführt.

Und wer dann auf „Einrichten" drückte, landete im System-Bereich — dort
gibt es gar kein Token-Feld, das steht bei Brand. Das Modul zeigte auf den
falschen Bereich.

Jetzt springt „Einrichten" auf das konkrete Feld und hebt es kurz hervor,
statt nur den Bereich zu wechseln. Der Bereich kommt dabei aus dem
Einstellungs-Register, das ohnehin weiß, wo jedes Feld liegt — damit kann
die Zuordnung am Modul nicht mehr auseinanderlaufen. Dieselbe Logik hängt
an „Einstellungen →" auf den Modul-Karten.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-31 21:43:34 +02:00

328 lines
11 KiB
JavaScript

// Effektive Laufzeit-Konfiguration: DB-Setting (Webinterface) vor Env-Variable.
// Wird bei jedem Zugriff frisch aufgelöst — Änderungen greifen ohne Neustart.
import { getSetting } from './db.js';
import { config } from './config.js';
export function commitChannelId() {
return getSetting('commit_channel_id') || config.commitChannelId;
}
export function devlogChannelId() {
return getSetting('devlog_channel_id') || config.devlogChannelId;
}
/** Öffentliche Basis-URL (Buttons, RSS, OAuth-Redirect) */
export function publicUrl() {
return (getSetting('public_url') || config.publicUrl).replace(/\/$/, '');
}
/**
* Community-Hub — Ziel für Devlog-Buttons, RSS, Vorschaubilder und die
* geteilte Navigation. Ohne eigene Einstellung bleibt alles wie bisher.
*/
export function hubUrl() {
return (getSetting('hub_url') || publicUrl()).replace(/\/$/, '');
}
/** Bot-Produktseite mit Dashboard */
export function botUrl() {
return (getSetting('bot_url') || publicUrl()).replace(/\/$/, '');
}
/** Cookie-Domain für den Login über mehrere Subdomains (leer = nur diese) */
export function cookieDomain() {
return getSetting('cookie_domain') || null;
}
/** Gitea-Basis-URL (API für /bug) */
export function giteaUrl() {
return (getSetting('gitea_url') || config.giteaUrl).replace(/\/$/, '');
}
/** Release-Ankündigungs-Kanal — nur per Settings-Seite, leer = Feature aus */
export function releaseChannelId() {
return getSetting('release_channel_id') || null;
}
/** Diskussions-Thread unter jedem Devlog-Post (Default: an) */
export function devlogThreadsEnabled() {
return getSetting('devlog_threads_enabled') !== '0';
}
/** Playtester-Rolle (Bewerbungs-Button) — leer = Feature aus */
export function playtesterRoleId() {
return getSetting('playtester_role_id') || null;
}
/** Starboard-Kanal — leer = Feature aus */
export function starboardChannelId() {
return getSetting('starboard_channel_id') || null;
}
/** Ab wie vielen ⭐ landet eine Nachricht im Starboard (Default: 3) */
export function starboardThreshold() {
const n = Number(getSetting('starboard_threshold'));
return Number.isInteger(n) && n >= 1 ? n : 3;
}
/** Gitea-API-Token (für /bug) — Setting hat Vorrang vor Env */
export function giteaApiToken() {
return getSetting('gitea_api_token') || config.giteaApiToken || null;
}
/** Guild-ID für die Slash-Command-Registrierung (greift beim nächsten Start) */
export function discordGuildId() {
return getSetting('discord_guild_id') || config.discordGuildId || null;
}
/* ── Branding (Config → Brand-Tab) ── */
/** Brand-Name für Footer/Embeds (Default: D4RKST3R) */
export function brandName() {
return getSetting('brand_name') || 'D4RKST3R';
}
/** Primärfarbe als Discord-Int (Default: #f5c518) */
export function brandColor() {
const hex = (getSetting('brand_color') || '#f5c518').replace('#', '');
const n = parseInt(hex, 16);
return Number.isNaN(n) ? 0xf5c518 : n;
}
/** Sekundärfarbe (Default: #ff4d00) */
export function brandColor2() {
const hex = (getSetting('brand_color2') || '#ff4d00').replace('#', '');
const n = parseInt(hex, 16);
return Number.isNaN(n) ? 0xff4d00 : n;
}
/** Embed-Footer im Brand-Stil: "D4RKST3R // DEVLOG" */
export function brandFooter(tag) {
return `${brandName()} // ${tag}`;
}
/** Bot-Status: Text (leer = keiner; Server-Monitor darf dann die Presence nutzen) */
export function botStatusText() {
return getSetting('bot_status_text') || '';
}
/** Bot-Status: Typ (playing|watching|listening|custom) */
export function botStatusType() {
return getSetting('bot_status_type') || 'custom';
}
/** Bot-Presence-Status: online | idle | dnd */
export function botPresenceStatus() {
return getSetting('bot_presence_status') || 'online';
}
/** Ankündigungs-Kanal für neue Discord-Events — leer = aus */
export function eventsAnnounceChannelId() {
return getSetting('events_announce_channel_id') || null;
}
/** Announce-Kanal für Twitch/YouTube — leer = aus */
export function socialAnnounceChannelId() {
return getSetting('social_announce_channel_id') || null;
}
/** YouTube-Kanal-ID (UC…) für neue-Video-Benachrichtigungen */
export function youtubeChannelId() {
return getSetting('youtube_channel_id') || null;
}
/** Twitch-Login-Name + App-Credentials (Client-Credentials-Flow) */
export function twitchChannel() {
return getSetting('twitch_channel') || null;
}
export function twitchClientId() {
return getSetting('twitch_client_id') || null;
}
export function twitchClientSecret() {
return getSetting('twitch_client_secret') || null;
}
/** "Join to Create"-Voice-Kanal — leer = Temp-Voice aus */
export function tempVoiceChannelId() {
return getSetting('tempvoice_channel_id') || null;
}
/** Auto-Rolle für neue Member — leer = aus */
export function autoroleId() {
return getSetting('autorole_id') || null;
}
/** Sticky-Roles: Rollen bei Rejoin wiederherstellen (Default: aus) */
export function stickyRolesEnabled() {
return getSetting('sticky_roles_enabled') === '1';
}
/** Level-System an/aus (Default: aus — bewusst opt-in) */
export function levelsEnabled() {
return getSetting('levels_enabled') === '1';
}
/** Level-Up-Nachricht im Kanal (Default: an, wenn Levels an) */
export function levelsAnnounce() {
return getSetting('levels_announce') !== '0';
}
/** Rollen-Belohnungen: "5=roleId, 10=roleId" → [{level, roleId}] aufsteigend */
export function levelRewards() {
return (getSetting('level_rewards') ?? '')
.split(',')
.map((entry) => {
const [lvl, roleId] = entry.split('=').map((s) => s.trim());
return Number(lvl) > 0 && roleId ? { level: Number(lvl), roleId } : null;
})
.filter(Boolean)
.sort((a, b) => a.level - b.level);
}
/** Betreiber-Angaben fürs Impressum (§ 5 DDG) — leer = Seite zeigt Hinweis */
export function legalInfo() {
return {
name: getSetting('legal_name') ?? '',
address: getSetting('legal_address') ?? '',
email: getSetting('legal_email') ?? '',
extra: getSetting('legal_extra') ?? '',
};
}
/** Member-Gate: Web-Login nur für Discord-Member (Default: an, wenn Guild gesetzt) */
export function memberGateEnabled() {
return getSetting('member_gate_enabled') !== '0';
}
/** Discord-Invite-Link für die „Tritt erst dem Server bei"-Seite — leer = keiner */
export function discordInviteUrl() {
return getSetting('discord_invite_url') || null;
}
/** Geburtstags-Kanal (morgendliche Gratulation) — leer = Feature aus */
export function birthdayChannelId() {
return getSetting('birthday_channel_id') || null;
}
/** Geburtstags-Rolle für den Tag — leer = keine Rolle */
export function birthdayRoleId() {
return getSetting('birthday_role_id') || null;
}
/** Kanal für Feature-Wünsche (/wunsch) — leer = Feature aus */
export function votingChannelId() {
return getSetting('voting_channel_id') || null;
}
/** Ticket-Kanal (dort entstehen die privaten Ticket-Threads) — leer = Feature aus */
export function ticketChannelId() {
return getSetting('ticket_channel_id') || null;
}
/** Modmail-Staff-Kanal (privat!) — leer = Feature aus */
export function modmailChannelId() {
return getSetting('modmail_channel_id') || null;
}
/** Willkommens-Kanal — leer = Feature aus */
export function welcomeChannelId() {
return getSetting('welcome_channel_id') || null;
}
/**
* Wert einer Einstellung so, wie er tatsächlich wirkt — also inklusive der
* Env-Fallbacks aus config.js.
*
* Ohne das meldet die Config Sachen als „fehlt", die über die Umgebung längst
* gesetzt sind: Wer sein Gitea-Token per GITEA_API_TOKEN übergibt, sieht in
* der Liste „Fehlermeldungen — Gitea-Token", obwohl /bug einwandfrei läuft.
*
* @param {string} key
* @returns {string}
*/
export function effectiveSetting(key) {
const gesetzt = String(getSetting(key) ?? '').trim();
if (gesetzt) return gesetzt;
const ausDerEnv = {
devlog_channel_id: config.devlogChannelId,
commit_channel_id: config.commitChannelId,
gitea_api_token: config.giteaApiToken,
discord_guild_id: config.discordGuildId,
}[key];
return String(ausDerEnv ?? '').trim();
}
/**
* Aussehen der Willkommens-Karte. Die Standardwerte sind der Marken-Look;
* die Marken-Farben ziehen automatisch nach, solange nichts eigenes gesetzt ist.
*/
export function welcomeCard() {
const hex = (key, fallback) => {
const value = String(getSetting(key) ?? '').trim();
return /^#[0-9a-fA-F]{6}$/.test(value) ? value : fallback;
};
const brand = (n) => `#${n.toString(16).padStart(6, '0')}`;
return {
enabled: getSetting('welcome_card_enabled') !== '0',
background: hex('welcome_card_bg', '#0a0a0a'),
accent: hex('welcome_card_accent', brand(brandColor())),
accent2: hex('welcome_card_accent2', brand(brandColor2())),
text: hex('welcome_card_text', '#e8e0d0'),
kicker: (getSetting('welcome_card_kicker') ?? 'WILLKOMMEN').slice(0, 24),
sub: getSetting('welcome_card_sub') ?? 'Member #{count}',
image: String(getSetting('welcome_card_image') ?? '').trim(),
watermark: getSetting('welcome_card_watermark') !== '0',
};
}
/** Mod-Log-Kanal (privat!) — leer = Feature aus */
export function modlogChannelId() {
return getSetting('modlog_channel_id') || null;
}
/** Screenshot-Kanal für die Galerie — leer = Feature aus */
export function screenshotChannelId() {
return getSetting('screenshot_channel_id') || null;
}
/** Ziel-Repo für /bug-Reports (Default: EcoGame) */
export function bugReportRepo() {
return getSetting('bug_report_repo') || 'D4rkst3r/EcoGame';
}
/** Repo, dessen Milestones die öffentliche Roadmap speisen */
export function roadmapRepo() {
return getSetting('roadmap_repo') || 'D4rkst3r/EcoGame';
}
/** Rolle, die bei Devlog-Posts gepingt wird — leer = kein Ping */
export function devlogPingRoleId() {
return getSetting('devlog_ping_role_id') || null;
}
/** Commit-Feed global an/aus (Default: an). Aus = weiter archivieren, nur nicht posten. */
export function commitFeedEnabled() {
return getSetting('commit_feed_enabled') !== '0';
}
/** Kommagetrennte Liste in Array übersetzen ('' → leer) */
function csv(value) {
return (value ?? '')
.split(',')
.map((s) => s.trim())
.filter(Boolean);
}
/** Branch-Filter: leer = alle Branches werden gepostet */
export function branchAllowed(branch) {
const allowed = csv(getSetting('commit_branch_filter'));
return allowed.length === 0 || allowed.includes(branch);
}
/** Ignorierte Repos (z. B. "D4rkst3r/ecobot") — werden komplett übersprungen */
export function repoIgnored(repo) {
return csv(getSetting('ignored_repos')).some(
(r) => r.toLowerCase() === repo.toLowerCase()
);
}