Eine Anwendung, zwei Gesichter — der Server erkennt an der Domain, welche Seite gefragt ist, und schreibt das als data-site an den <body>. Das Frontend rendert daraufhin entweder den Community-Hub wie bisher oder die neue Produktseite. - Bot-Produktseite (BotApp): Landing mit Feature-Übersicht, Befehls- referenz, Dashboard unter /dashboard, Orange als Leitfarbe - Weiterleitungen: Community-Routen auf der Bot-Domain und umgekehrt werden dauerhaft (301) auf die richtige Adresse geschickt — geteilte Devlog-Permalinks laufen also nicht ins Leere. Rechtstexte bleiben auf beiden erreichbar. - Open-Graph-Tags je Domain, inklusive eigener Vorschau für frei angelegte Seiten (Entwürfe bekommen bewusst keine) - Login: neue Einstellung für die Cookie-Domain, damit die Anmeldung auf beiden Seiten gilt; nach dem Discord-Login landet man wieder auf der Seite, von der man gestartet ist (vorher immer auf der Hauptadresse) - Alles greift erst, wenn beide Adressen im Setup eingetragen sind — bis dahin verhält sich die Anwendung unverändert Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
282 lines
8.9 KiB
JavaScript
282 lines
8.9 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 (Setup → 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;
|
|
}
|
|
|
|
/** 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()
|
|
);
|
|
}
|