Community-Endgame: Alpha-Keys, Bewerbungen, Events, Stats, Triggers, /remind, Social, Temp-Voice + MEE6-Bot-Karte
- Alpha-Keys: Pool im Community-Tab, Ein-Klick-Verteilung per DM an alle Playtester (Key bleibt frei, wenn die DM geblockt wird) - Bewerbungs-Formulare: Builder im neuen Bewerbungen-Tab (bis 5 Fragen), Button → Discord-Modal → Review-Embed mit ✅/❌, Rolle + DM bei Entscheidung - Events: GuildScheduledEventCreate → Announce-Embed; öffentliche /events-Seite aus den Discord-Events (5-min-Cache) - Server-Stats: activity_daily (Nachrichten/Joins/Leaves) → Balken-Chart auf /level - Triggers (Auto-Antworten, 30s-Cooldown), /remind (DM-Scheduler), Twitch-Live (Helix, App-Creds write-only) + YouTube-RSS-Announcements, Temp-Voice (Join to Create, Cleanup bei Leerstand + Start) - Brand-Tab: MEE6-Style Bot-Identity-Karte (Avatar-Vorschau mit Status-Dot, Bot-Name via setUsername, Presence online/idle/dnd, Aktivität) - Neue Intents: GuildVoiceStates, GuildScheduledEvents; alles smoke-getestet Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -252,6 +252,158 @@ export function commitHeatmap() {
|
||||
.all();
|
||||
}
|
||||
|
||||
// Alpha-Keys: Pool + Zuweisung an Playtester
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS alpha_keys (
|
||||
key TEXT PRIMARY KEY,
|
||||
assigned_to TEXT,
|
||||
assigned_at TEXT
|
||||
);
|
||||
`);
|
||||
const insertAlphaKey = db.prepare('INSERT OR IGNORE INTO alpha_keys (key) VALUES (?)');
|
||||
const freeKeyCountStmt = db.prepare('SELECT count(*) AS n FROM alpha_keys WHERE assigned_to IS NULL');
|
||||
const assignedKeysStmt = db.prepare(
|
||||
`SELECT key, assigned_to, assigned_at FROM alpha_keys WHERE assigned_to IS NOT NULL ORDER BY assigned_at`
|
||||
);
|
||||
const hasKeyForUserStmt = db.prepare('SELECT key FROM alpha_keys WHERE assigned_to = ?');
|
||||
const reserveKeyStmt = db.prepare(`
|
||||
UPDATE alpha_keys SET assigned_to = ?, assigned_at = datetime('now')
|
||||
WHERE key = (SELECT key FROM alpha_keys WHERE assigned_to IS NULL LIMIT 1)
|
||||
RETURNING key
|
||||
`);
|
||||
const unreserveKeyStmt = db.prepare('UPDATE alpha_keys SET assigned_to = NULL, assigned_at = NULL WHERE key = ?');
|
||||
export const addAlphaKeys = db.transaction((keys) => {
|
||||
let added = 0;
|
||||
for (const k of keys) added += insertAlphaKey.run(k).changes;
|
||||
return added;
|
||||
});
|
||||
export const freeAlphaKeyCount = () => freeKeyCountStmt.get().n;
|
||||
export const assignedAlphaKeys = () => assignedKeysStmt.all();
|
||||
export const alphaKeyOf = (userId) => hasKeyForUserStmt.get(userId)?.key ?? null;
|
||||
export const reserveAlphaKey = (userId) => reserveKeyStmt.get(userId)?.key ?? null;
|
||||
export const unreserveAlphaKey = (key) => unreserveKeyStmt.run(key);
|
||||
|
||||
// Bewerbungs-Formulare (Modals) + eingereichte Bewerbungen
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS app_forms (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
title TEXT NOT NULL,
|
||||
description TEXT NOT NULL DEFAULT '',
|
||||
review_channel_id TEXT,
|
||||
approve_role_id TEXT,
|
||||
post_channel_id TEXT,
|
||||
message_id TEXT,
|
||||
questions TEXT NOT NULL DEFAULT '[]'
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS applications (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
form_id INTEGER NOT NULL,
|
||||
user_id TEXT NOT NULL,
|
||||
username TEXT,
|
||||
answers TEXT NOT NULL DEFAULT '[]',
|
||||
status TEXT NOT NULL DEFAULT 'open',
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
`);
|
||||
const insertAppForm = db.prepare(`
|
||||
INSERT INTO app_forms (title, description, review_channel_id, approve_role_id, post_channel_id, questions)
|
||||
VALUES (@title, @description, @review_channel_id, @approve_role_id, @post_channel_id, @questions)
|
||||
`);
|
||||
const updateAppFormStmt = db.prepare(`
|
||||
UPDATE app_forms SET title = @title, description = @description, review_channel_id = @review_channel_id,
|
||||
approve_role_id = @approve_role_id, post_channel_id = @post_channel_id, questions = @questions
|
||||
WHERE id = @id
|
||||
`);
|
||||
const setAppFormMessageStmt = db.prepare('UPDATE app_forms SET message_id = ? WHERE id = ?');
|
||||
const getAppFormStmt = db.prepare('SELECT * FROM app_forms WHERE id = ?');
|
||||
const listAppFormsStmt = db.prepare('SELECT * FROM app_forms ORDER BY id');
|
||||
const deleteAppFormStmt = db.prepare('DELETE FROM app_forms WHERE id = ?');
|
||||
export const createAppForm = (f) => insertAppForm.run(f).lastInsertRowid;
|
||||
export const updateAppForm = (f) => updateAppFormStmt.run(f).changes > 0;
|
||||
export const setAppFormMessage = (id, messageId) => setAppFormMessageStmt.run(messageId, id);
|
||||
export const getAppForm = (id) => getAppFormStmt.get(id) ?? null;
|
||||
export const listAppForms = () => listAppFormsStmt.all();
|
||||
export const deleteAppForm = (id) => deleteAppFormStmt.run(id).changes > 0;
|
||||
|
||||
const insertApplication = db.prepare(`
|
||||
INSERT INTO applications (form_id, user_id, username, answers) VALUES (?, ?, ?, ?)
|
||||
`);
|
||||
const getApplicationStmt = db.prepare('SELECT * FROM applications WHERE id = ?');
|
||||
const openApplicationStmt = db.prepare(
|
||||
`SELECT * FROM applications WHERE form_id = ? AND user_id = ? AND status = 'open'`
|
||||
);
|
||||
const setApplicationStatusStmt = db.prepare('UPDATE applications SET status = ? WHERE id = ?');
|
||||
export const createApplication = (formId, userId, username, answers) =>
|
||||
insertApplication.run(formId, userId, username, JSON.stringify(answers)).lastInsertRowid;
|
||||
export const getApplication = (id) => getApplicationStmt.get(id) ?? null;
|
||||
export const openApplicationOf = (formId, userId) => openApplicationStmt.get(formId, userId) ?? null;
|
||||
export const setApplicationStatus = (id, status) => setApplicationStatusStmt.run(status, id);
|
||||
|
||||
// Triggers (Auto-Antworten), Reminders, Server-Aktivität, Temp-Voice
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS triggers (
|
||||
keyword TEXT PRIMARY KEY,
|
||||
reply TEXT NOT NULL
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS reminders (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id TEXT NOT NULL,
|
||||
text TEXT NOT NULL,
|
||||
remind_at TEXT NOT NULL,
|
||||
sent INTEGER NOT NULL DEFAULT 0
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS activity_daily (
|
||||
day TEXT PRIMARY KEY,
|
||||
messages INTEGER NOT NULL DEFAULT 0,
|
||||
joins INTEGER NOT NULL DEFAULT 0,
|
||||
leaves INTEGER NOT NULL DEFAULT 0
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS temp_voice (
|
||||
channel_id TEXT PRIMARY KEY
|
||||
);
|
||||
`);
|
||||
const upsertTrigger = db.prepare(`
|
||||
INSERT INTO triggers (keyword, reply) VALUES (?, ?)
|
||||
ON CONFLICT(keyword) DO UPDATE SET reply = excluded.reply
|
||||
`);
|
||||
const listTriggersStmt = db.prepare('SELECT keyword, reply FROM triggers ORDER BY keyword');
|
||||
const deleteTriggerStmt = db.prepare('DELETE FROM triggers WHERE keyword = ?');
|
||||
export const saveTrigger = (keyword, reply) => upsertTrigger.run(keyword, reply);
|
||||
export const listTriggers = () => listTriggersStmt.all();
|
||||
export const deleteTrigger = (keyword) => deleteTriggerStmt.run(keyword).changes > 0;
|
||||
|
||||
const insertReminder = db.prepare('INSERT INTO reminders (user_id, text, remind_at) VALUES (?, ?, ?)');
|
||||
const dueRemindersStmt = db.prepare(
|
||||
`SELECT * FROM reminders WHERE sent = 0 AND remind_at <= datetime('now')`
|
||||
);
|
||||
const markReminderSentStmt = db.prepare('UPDATE reminders SET sent = 1 WHERE id = ?');
|
||||
export const createReminder = (userId, text, remindAt) => insertReminder.run(userId, text, remindAt).lastInsertRowid;
|
||||
export const dueReminders = () => dueRemindersStmt.all();
|
||||
export const markReminderSent = (id) => markReminderSentStmt.run(id);
|
||||
|
||||
const bumpActivityStmt = (col) => db.prepare(`
|
||||
INSERT INTO activity_daily (day, ${col}) VALUES (date('now'), 1)
|
||||
ON CONFLICT(day) DO UPDATE SET ${col} = ${col} + 1
|
||||
`);
|
||||
const bumpMessages = bumpActivityStmt('messages');
|
||||
const bumpJoins = bumpActivityStmt('joins');
|
||||
const bumpLeaves = bumpActivityStmt('leaves');
|
||||
export const countActivity = (kind) =>
|
||||
(kind === 'join' ? bumpJoins : kind === 'leave' ? bumpLeaves : bumpMessages).run();
|
||||
const activityRangeStmt = db.prepare(
|
||||
`SELECT day, messages, joins, leaves FROM activity_daily WHERE day >= date('now', ?) ORDER BY day`
|
||||
);
|
||||
export const activityRange = (days = 30) => activityRangeStmt.all(`-${days} days`);
|
||||
|
||||
const insertTempVoice = db.prepare('INSERT OR IGNORE INTO temp_voice (channel_id) VALUES (?)');
|
||||
const deleteTempVoiceStmt = db.prepare('DELETE FROM temp_voice WHERE channel_id = ?');
|
||||
const listTempVoiceStmt = db.prepare('SELECT channel_id FROM temp_voice');
|
||||
export const trackTempVoice = (id) => insertTempVoice.run(id);
|
||||
export const untrackTempVoice = (id) => deleteTempVoiceStmt.run(id);
|
||||
export const listTempVoice = () => listTempVoiceStmt.all().map((r) => r.channel_id);
|
||||
const isTempVoiceStmt = db.prepare('SELECT 1 FROM temp_voice WHERE channel_id = ?');
|
||||
export const isTempVoice = (id) => Boolean(isTempVoiceStmt.get(id));
|
||||
|
||||
// Moderation: Verwarnungen + Sticky-Roles
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS warns (
|
||||
|
||||
Reference in New Issue
Block a user