Zwei Fragen, zwei Antworten — eine davon ein Nein mit Umweg. ## Bilder im Eingabefenster: geht nicht Discord-Modals koennen nur Textfelder. Kein Datei-Anhang, keine Umgehung — das ist eine Grenze der Plattform, nicht unseres Codes. Der Umweg ist besser als er klingt: Wer nach dem Einreichen einen Screenshot in den Thread haengt, sieht ihn auf der Ideen-Seite. Die Bestaetigung nach dem Absenden sagt das jetzt auch. Damit kann man auch nachtraeglich noch Bilder dazulegen, was im Eingabefenster gar nicht ginge. ## Warum nicht einfach der Discord-Link Discord haengt seit einiger Zeit eine Signatur an die Anhang-Links (?ex=&is=&hm=), die nach etwa einem Tag ablaeuft. Ein gespeicherter Link zeigt morgen ein kaputtes Bild. Deshalb laden Devlogs und Galerie seit dem ersten Tag herunter — im Kopf von devlog-archive.js steht der Grund. Wuensche machen es jetzt genauso, ausgeliefert unter /wish-assets/. Beim Loeschen eines Beitrags oder eines ganzen Wunsches gehen die Dateien mit; sonst waechst der Ordner mit Bildern, auf die nichts mehr zeigt. Das Herunterladen stand vorher zweimal fast wortgleich im Code. Mit den Wuenschen waeren es drei Kopien geworden — also einmal nach bot/bilder.js, und Devlogs und Galerie ziehen mit um. Ein Fehler steckt jetzt an einer Stelle statt an dreien. ## Forum-Tags per Knopf Bisher musstest du sie von Hand anlegen. Jetzt macht es das Panel: die Bereiche von dort plus die fuenf Staende, mit ihren Emojis. Vorhandene bleiben, auch fremde — die Liste wird ergaenzt und nicht ersetzt. Discords Grenze von 20 Tags wird eingehalten, was nicht mehr reinpasst, wird gemeldet statt still verschluckt. Geprueft: forumTagsAnlegen gegen eine Kanal-Attrappe — leeres Forum, teilweise vorhandene, Gross/Kleinschreibung, fremde Tags, nichts zu tun (dann wird auch nicht geschrieben), volles Forum, kein Forum, kein Kanal. Im Browser die Ideen-Seite mit Bildern in einem Beitrag, einem Beitrag nur aus Bild ohne Text, und der Lightbox darueber. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
291 lines
12 KiB
JavaScript
291 lines
12 KiB
JavaScript
// Text-Vorlagen: eine Stelle, an der alle Nachrichten stehen, die der Bot
|
|
// nach außen schreibt — Begrüßungen, Bestätigungen, Direktnachrichten.
|
|
//
|
|
// Jede Vorlage hat einen Standardtext. Wird sie im Webinterface geändert,
|
|
// landet die Fassung als Einstellung `tpl_<id>` in der Datenbank; ein leerer
|
|
// Wert bedeutet „wieder der Standard". So bleibt der Code die Quelle für den
|
|
// Standard und die Datenbank enthält nur echte Abweichungen.
|
|
//
|
|
// Platzhalter sind `{name}`. Unbekannte Namen bleiben unverändert stehen,
|
|
// damit ein Tippfehler in der Nachricht sichtbar wird statt still zu
|
|
// verschwinden.
|
|
import { getSetting, setSetting } from './db.js';
|
|
|
|
export const TEMPLATE_GROUPS = [
|
|
{ id: 'willkommen', label: 'Ankommen & Community' },
|
|
{ id: 'support', label: 'Support & Bewerbung' },
|
|
{ id: 'spass', label: 'Verlosung, Geburtstag & Level' },
|
|
{ id: 'sonstiges', label: 'Sonstiges' },
|
|
];
|
|
|
|
/** Platzhalter, die es in fast jeder Vorlage gibt */
|
|
const WHO = [
|
|
{ key: 'user', desc: 'Anzeigename' },
|
|
{ key: 'mention', desc: 'Erwähnung (@Name)' },
|
|
];
|
|
|
|
export const TEMPLATES = [
|
|
/* ── Ankommen & Community ─────────────────────────── */
|
|
{
|
|
id: 'welcome.title', group: 'willkommen', module: 'welcome',
|
|
name: 'Willkommen — Überschrift',
|
|
vars: [...WHO, { key: 'server', desc: 'Servername' }, { key: 'count', desc: 'Mitglieder-Nummer' }],
|
|
default: '👋 Willkommen, {user}!',
|
|
},
|
|
{
|
|
id: 'welcome.text', group: 'willkommen', module: 'welcome',
|
|
name: 'Willkommen — Text', long: true,
|
|
vars: [...WHO, { key: 'server', desc: 'Servername' }, { key: 'count', desc: 'Mitglieder-Nummer' }, { key: 'url', desc: 'Adresse der Webseite' }],
|
|
default:
|
|
'Schön, dass du da bist — du bist Mitglied **#{count}**.\n\n' +
|
|
'📔 Devlogs & Roadmap: {url}\n' +
|
|
'🧪 Playtester werden? Schau nach dem Bewerbungs-Post!\n' +
|
|
'🐛 Bug gefunden? Einfach `/bug` benutzen.',
|
|
},
|
|
{
|
|
id: 'playtester.joined', group: 'willkommen', module: 'playtester',
|
|
name: 'Playtester — angemeldet', long: true,
|
|
vars: WHO,
|
|
default: '🧪 **Willkommen im Playtest-Team!** Du stehst auf der Liste — sobald es losgeht, meldet sich der Bot per DM.',
|
|
},
|
|
{
|
|
id: 'playtester.left', group: 'willkommen', module: 'playtester',
|
|
name: 'Playtester — abgemeldet',
|
|
vars: WHO,
|
|
default: '🚪 Von der Playtester-Liste **abgemeldet**.',
|
|
},
|
|
{
|
|
id: 'tempvoice.title', group: 'willkommen', module: 'temp_voice',
|
|
name: 'Sprachkanal — Überschrift',
|
|
vars: WHO,
|
|
default: '🔊 Dein Kanal',
|
|
},
|
|
{
|
|
id: 'tempvoice.text', group: 'willkommen', module: 'temp_voice',
|
|
name: 'Sprachkanal — Text', long: true,
|
|
vars: WHO,
|
|
default: '{mention}, das hier ist dein temporärer Voice-Kanal — er verschwindet automatisch, wenn alle raus sind. Steuern kannst du ihn hier:',
|
|
},
|
|
|
|
/* ── Support & Bewerbung ──────────────────────────── */
|
|
{
|
|
id: 'ticket.panel_title', group: 'support', module: 'tickets',
|
|
name: 'Ticket-Post — Überschrift',
|
|
vars: [],
|
|
default: '🎫 Support-Ticket',
|
|
},
|
|
{
|
|
id: 'ticket.panel_text', group: 'support', module: 'tickets',
|
|
name: 'Ticket-Post — Text', long: true,
|
|
vars: [],
|
|
default:
|
|
'Du hast eine Frage, ein Problem oder ein privates Anliegen?\n\n' +
|
|
'Klick auf den Button — es öffnet sich ein **privater Thread**, den nur du und das Team sehen können.',
|
|
},
|
|
{
|
|
id: 'ticket.opened', group: 'support', module: 'tickets',
|
|
name: 'Ticket — Begrüßung im Thread', long: true,
|
|
vars: WHO,
|
|
default:
|
|
'Willkommen in deinem Ticket! Beschreib dein Anliegen — das Team meldet sich.\n' +
|
|
'Wenn alles geklärt ist, kann das Ticket hier geschlossen werden.',
|
|
},
|
|
{
|
|
id: 'ticket.closed_dm', group: 'support', module: 'tickets',
|
|
name: 'Ticket — Direktnachricht beim Schließen', long: true,
|
|
vars: [{ key: 'ticket', desc: 'Name des Tickets' }, { key: 'closedBy', desc: 'wer geschlossen hat' }],
|
|
default: '🔒 Dein Ticket **{ticket}** wurde geschlossen — hier dein Transcript. Danke für deine Anfrage!',
|
|
},
|
|
{
|
|
id: 'modmail.thread_intro', group: 'support', module: 'modmail',
|
|
name: 'Modmail — Kopf des Staff-Threads', long: true,
|
|
vars: WHO,
|
|
default:
|
|
'Neue Modmail-Konversation mit **{user}** ({mention}).\n' +
|
|
'Antworten in diesem Thread gehen als DM an den User.',
|
|
},
|
|
{
|
|
id: 'application.submitted', group: 'support', module: 'applications',
|
|
name: 'Bewerbung — Bestätigung', long: true,
|
|
vars: [...WHO, { key: 'form', desc: 'Name des Formulars' }],
|
|
default: '✅ Bewerbung eingereicht — danke! Du bekommst eine DM, sobald sie geprüft wurde.',
|
|
},
|
|
{
|
|
id: 'application.approved', group: 'support', module: 'applications',
|
|
name: 'Bewerbung — angenommen', long: true,
|
|
vars: [...WHO, { key: 'form', desc: 'Name des Formulars' }],
|
|
default: '✅ Deine Bewerbung **„{form}"** wurde angenommen — willkommen! 🎉',
|
|
},
|
|
{
|
|
id: 'application.denied', group: 'support', module: 'applications',
|
|
name: 'Bewerbung — abgelehnt', long: true,
|
|
vars: [...WHO, { key: 'form', desc: 'Name des Formulars' }],
|
|
default: '❌ Deine Bewerbung **„{form}"** wurde leider abgelehnt.',
|
|
},
|
|
|
|
/* ── Verlosung, Geburtstag & Level ────────────────── */
|
|
{
|
|
id: 'giveaway.winner', group: 'spass', module: 'giveaways',
|
|
name: 'Verlosung — Gewinner', long: true,
|
|
vars: [
|
|
{ key: 'prize', desc: 'Gewinn' },
|
|
{ key: 'winners', desc: 'Erwähnungen der Gewinner' },
|
|
{ key: 'entries', desc: 'Anzahl Teilnahmen' },
|
|
],
|
|
default: '🎉 **{prize}** geht an: {winners} — Glückwunsch! 🏆',
|
|
},
|
|
{
|
|
id: 'giveaway.empty', group: 'spass', module: 'giveaways',
|
|
name: 'Verlosung — keine Teilnahmen',
|
|
vars: [{ key: 'prize', desc: 'Gewinn' }],
|
|
default: 'Keine Teilnahmen — niemand hat gewonnen. 😢',
|
|
},
|
|
{
|
|
id: 'giveaway.reroll', group: 'spass', module: 'giveaways',
|
|
name: 'Verlosung — neu ausgelost', long: true,
|
|
vars: [{ key: 'prize', desc: 'Gewinn' }, { key: 'winners', desc: 'Erwähnung des Gewinners' }],
|
|
default: '🔁 Neu ausgelost: **{prize}** geht jetzt an {winners} — Glückwunsch! 🏆',
|
|
},
|
|
{
|
|
id: 'birthday.title', group: 'spass', module: 'birthdays',
|
|
name: 'Geburtstag — Überschrift',
|
|
vars: [{ key: 'count', desc: 'Anzahl Geburtstagskinder' }],
|
|
default: '🎂 Alles Gute zum Geburtstag!',
|
|
},
|
|
{
|
|
id: 'birthday.title_multi', group: 'spass', module: 'birthdays',
|
|
name: 'Geburtstag — Überschrift (mehrere)',
|
|
vars: [{ key: 'count', desc: 'Anzahl Geburtstagskinder' }],
|
|
default: '🎂 Heute wird gleich mehrfach gefeiert!',
|
|
},
|
|
{
|
|
id: 'birthday.text', group: 'spass', module: 'birthdays',
|
|
name: 'Geburtstag — Text', long: true,
|
|
vars: [{ key: 'mentions', desc: 'Erwähnungen' }, { key: 'count', desc: 'Anzahl' }],
|
|
default: '{mentions} hat heute Geburtstag — lasst mal ordentlich 🎉 da!',
|
|
},
|
|
{
|
|
id: 'birthday.text_multi', group: 'spass', module: 'birthdays',
|
|
name: 'Geburtstag — Text (mehrere)', long: true,
|
|
vars: [{ key: 'mentions', desc: 'Erwähnungen' }, { key: 'count', desc: 'Anzahl' }],
|
|
default: '{mentions} haben heute Geburtstag — lasst mal ordentlich 🎉 da!',
|
|
},
|
|
{
|
|
id: 'level.up', group: 'spass', module: 'levels',
|
|
name: 'Level-Aufstieg', long: true,
|
|
vars: [...WHO, { key: 'level', desc: 'neues Level' }, { key: 'xp', desc: 'Gesamt-XP' }],
|
|
default: '🎉 **{user}** ist jetzt **Level {level}**!',
|
|
},
|
|
|
|
/* ── Sonstiges ────────────────────────────────────── */
|
|
{
|
|
id: 'reminder.dm', group: 'sonstiges', module: 'reminders',
|
|
name: 'Erinnerung', long: true,
|
|
vars: [...WHO, { key: 'text', desc: 'der erinnerte Text' }],
|
|
default: '⏰ **Erinnerung:** {text}',
|
|
},
|
|
{
|
|
id: 'wish.submitted', group: 'sonstiges', module: 'voting',
|
|
name: 'Feature-Wunsch — Bestätigung', long: true,
|
|
vars: [...WHO, { key: 'url', desc: 'Link zum Voting-Post' }],
|
|
default: '✅ Wunsch eingereicht — [zur Abstimmung]({url})!\n'
|
|
+ 'Screenshot dazu? Häng ihn einfach in den Thread — er landet mit auf der Ideen-Seite.',
|
|
},
|
|
{
|
|
id: 'wish.panel_title', group: 'sonstiges', module: 'voting',
|
|
name: 'Ideen-Aufruf — Überschrift',
|
|
vars: [],
|
|
default: '💡 Deine Idee für EcoGame',
|
|
},
|
|
{
|
|
id: 'wish.panel_text', group: 'sonstiges', module: 'voting',
|
|
name: 'Ideen-Aufruf — Text', long: true,
|
|
vars: [],
|
|
default:
|
|
'Dir fehlt etwas, oder du hättest gern, dass etwas anders läuft?\n\n' +
|
|
'Such dir unten den passenden Bereich, schreib deine Idee auf — sie landet als '
|
|
+ 'Post hier im Kanal. Mit 👍 stimmt die Community ab, und im Thread darunter '
|
|
+ 'wird darüber geredet. Auf der Roadmap siehst du, was daraus geworden ist.',
|
|
},
|
|
{
|
|
id: 'bug.submitted', group: 'sonstiges', module: 'bug_reports',
|
|
name: 'Fehlermeldung — Bestätigung', long: true,
|
|
vars: [
|
|
...WHO,
|
|
{ key: 'number', desc: 'Nummer des Issues' },
|
|
{ key: 'url', desc: 'Link zum Issue' },
|
|
{ key: 'title', desc: 'Titel der Meldung' },
|
|
],
|
|
default: '✅ Danke! Dein Bug ist erfasst als [#{number}]({url}) — du bekommst eine DM, sobald er behoben ist.',
|
|
},
|
|
{
|
|
id: 'playtester.panel_title', group: 'sonstiges', module: 'playtester',
|
|
name: 'Playtester-Post — Überschrift',
|
|
vars: [],
|
|
default: '🧪 Playtester werden',
|
|
},
|
|
{
|
|
id: 'playtester.panel_text', group: 'sonstiges', module: 'playtester',
|
|
name: 'Playtester-Post — Text', long: true,
|
|
vars: [],
|
|
default:
|
|
'Du willst **EcoGame** vor allen anderen spielen und mit deinem Feedback die Entwicklung mitgestalten?\n\n' +
|
|
'Klick auf den Button und füll das kurze Formular aus. Wir schauen es uns an und ' +
|
|
'melden uns per DM — bei einer Zusage bekommst du Rolle und Zugang.',
|
|
},
|
|
];
|
|
|
|
const byId = new Map(TEMPLATES.map((t) => [t.id, t]));
|
|
|
|
/** Standardtext einer Vorlage */
|
|
export function templateDefault(id) {
|
|
return byId.get(id)?.default ?? '';
|
|
}
|
|
|
|
/** Aktueller Rohtext — eigene Fassung, sonst der Standard */
|
|
export function getTemplate(id) {
|
|
const custom = getSetting(`tpl_${id}`);
|
|
return custom?.trim() ? custom : templateDefault(id);
|
|
}
|
|
|
|
/** Eigene Fassung setzen. Leerer Text stellt den Standard wieder her. */
|
|
export function setTemplate(id, text) {
|
|
if (!byId.has(id)) return false;
|
|
setSetting(`tpl_${id}`, String(text ?? '').trim());
|
|
return true;
|
|
}
|
|
|
|
/** Platzhalter ersetzen — Unbekanntes bleibt stehen */
|
|
export function fillPlaceholders(text, vars = {}) {
|
|
return String(text).replace(/\{(\w+)\}/g, (match, key) =>
|
|
Object.hasOwn(vars, key) ? String(vars[key] ?? '') : match
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Vorlage rendern.
|
|
* @param {string} id
|
|
* @param {Record<string, unknown>} vars
|
|
*/
|
|
export function renderTemplate(id, vars = {}) {
|
|
return fillPlaceholders(getTemplate(id), vars);
|
|
}
|
|
|
|
/** Alle Vorlagen mit Zustand — fürs Webinterface */
|
|
export function templateStates() {
|
|
return TEMPLATES.map((t) => {
|
|
const custom = getSetting(`tpl_${t.id}`);
|
|
return {
|
|
id: t.id,
|
|
name: t.name,
|
|
group: t.group,
|
|
module: t.module ?? null,
|
|
long: Boolean(t.long),
|
|
vars: t.vars ?? [],
|
|
default: t.default,
|
|
text: custom?.trim() ? custom : t.default,
|
|
customized: Boolean(custom?.trim()),
|
|
};
|
|
});
|
|
}
|