Feature-Voting, Giveaways und Contribution-Heatmap

- /wunsch: Voting-Embed mit 👍, Reaction-Tracking (add/remove) in SQLite,
  Top-20-Rangliste öffentlich auf der Roadmap-Seite
- /giveaway (Admin): Preis/Dauer/Gewinnerzahl, 🎉-Teilnahme-Button (toggle),
  Minuten-Scheduler zieht Gewinner, schließt das Embed ab und pingt sie
- Contribution-Heatmap: 52-Wochen-Grid in Brand-Gelb aus dem Commit-Archiv
  (/api/heatmap) auf der Roadmap-Seite
- Setting voting_channel_id auf der Setup-Seite

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-23 13:20:44 +02:00
co-authored by Claude Opus 4.8
parent 075ea36bd2
commit bc000aaa9f
12 changed files with 391 additions and 4 deletions
+7 -2
View File
@@ -3,7 +3,7 @@ import { EmbedBuilder } from 'discord.js';
import {
listDevlogs, searchDevlogs, listCommits, listReleases, archiveStats,
getSetting, setSetting, createApiKey, listApiKeys, deleteApiKey,
listGallery, listPlaytesters,
listGallery, listPlaytesters, topWishes, commitHeatmap,
} from '../db.js';
import { config } from '../config.js';
import { removeDevlog } from '../bot/devlog-archive.js';
@@ -71,6 +71,10 @@ export function registerApiRoutes(app, client) {
return { playtesters: listPlaytesters() };
});
// Community-Wünsche (Top 20) + Commit-Heatmap — öffentlich
app.get('/api/wishes', async () => ({ wishes: topWishes(20) }));
app.get('/api/heatmap', async () => ({ days: commitHeatmap() }));
// Roadmap — öffentlich, aus Gitea-Milestones (5-Minuten-Cache gegen API-Hammering)
let roadmapCache = { at: 0, repo: null, data: null };
app.get('/api/roadmap', async (request, reply) => {
@@ -207,6 +211,7 @@ ${rssItems}
welcome_channel_id: getSetting('welcome_channel_id') ?? '',
modlog_channel_id: getSetting('modlog_channel_id') ?? '',
status_channel_id: getSetting('status_channel_id') ?? '',
voting_channel_id: getSetting('voting_channel_id') ?? '',
gameservers: getSetting('gameservers') ?? '',
};
}
@@ -239,7 +244,7 @@ ${rssItems}
const OPTIONAL_CHANNELS = [
'release_channel_id', 'backup_channel_id', 'starboard_channel_id',
'screenshot_channel_id', 'modmail_channel_id', 'welcome_channel_id',
'modlog_channel_id', 'status_channel_id',
'modlog_channel_id', 'status_channel_id', 'voting_channel_id',
];
for (const key of ['commit_channel_id', 'devlog_channel_id', ...OPTIONAL_CHANNELS]) {
if (body[key] === undefined) continue;