Feature 3: Devlog-Archiv — Live-Listener + /devlog-backfill

- Webhook-Nachrichten im Devlog-Kanal werden automatisch in SQLite archiviert
  (devlog.py im EcoGame-Repo bleibt unverändert)
- /devlog-backfill (Admin): scannt die komplette Kanal-Historie in 100er-Blöcken
- Dedupe über Discord-Message-ID, Embeds werden mit Titel+Beschreibung erfasst
- Neue Intents: GuildMessages + MessageContent, neue Env-Var DEVLOG_CHANNEL_ID

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-22 23:51:16 +02:00
co-authored by Claude Opus 4.8
parent 716268b1c1
commit 2c63c62e14
8 changed files with 152 additions and 5 deletions
+22 -2
View File
@@ -1,14 +1,22 @@
// Discord-Client: Commands laden, registrieren und Interactions verarbeiten
import { Client, Collection, Events, GatewayIntentBits, REST, Routes } from 'discord.js';
import { config } from '../config.js';
import { archiveDevlogMessage } from './devlog-archive.js';
import * as ping from './commands/ping.js';
import * as devlogBackfill from './commands/devlog-backfill.js';
// Alle Commands hier eintragen — jedes Modul exportiert { data, execute }
const commandModules = [ping];
const commandModules = [ping, devlogBackfill];
export async function startBot() {
const client = new Client({
intents: [GatewayIntentBits.Guilds],
intents: [
GatewayIntentBits.Guilds,
// Für das Devlog-Archiv: Nachrichten im Devlog-Kanal mitlesen
// (erfordert aktivierten "Message Content Intent" im Developer Portal!)
GatewayIntentBits.GuildMessages,
GatewayIntentBits.MessageContent,
],
});
// Commands in Collection ablegen für schnellen Zugriff im Interaction-Handler
@@ -22,6 +30,18 @@ export async function startBot() {
await registerCommands();
});
// Live-Archivierung: neue Devlogs (Webhook-Posts im Devlog-Kanal) sofort sichern
client.on(Events.MessageCreate, (message) => {
if (message.channelId !== config.devlogChannelId) return;
try {
if (archiveDevlogMessage(message)) {
console.log(`[devlog] Neues Devlog archiviert (${message.id})`);
}
} catch (error) {
console.error('[devlog] Archivierung fehlgeschlagen:', error);
}
});
client.on(Events.InteractionCreate, async (interaction) => {
if (!interaction.isChatInputCommand()) return;
+38
View File
@@ -0,0 +1,38 @@
// /devlog-backfill — komplette Kanal-Historie scannen und alte Devlogs nacharchivieren (Admin only)
import { SlashCommandBuilder, PermissionFlagsBits, MessageFlags } from 'discord.js';
import { config } from '../../config.js';
import { archiveDevlogMessage } from '../devlog-archive.js';
export const data = new SlashCommandBuilder()
.setName('devlog-backfill')
.setDescription('Archiviert alle bisherigen Devlogs aus dem Devlog-Kanal')
.setDefaultMemberPermissions(PermissionFlagsBits.Administrator);
export async function execute(interaction) {
await interaction.deferReply({ flags: MessageFlags.Ephemeral });
const channel = await interaction.client.channels.fetch(config.devlogChannelId);
if (!channel?.isTextBased()) {
await interaction.editReply('❌ Devlog-Kanal nicht gefunden oder kein Textkanal.');
return;
}
// Historie rückwärts in 100er-Blöcken durchlaufen (Discord-API-Maximum pro Fetch)
let scanned = 0;
let saved = 0;
let before;
for (;;) {
const batch = await channel.messages.fetch({ limit: 100, before });
if (batch.size === 0) break;
for (const message of batch.values()) {
scanned++;
if (archiveDevlogMessage(message)) saved++;
}
before = batch.last().id;
}
await interaction.editReply(
`✅ Backfill fertig: ${scanned} Nachrichten gescannt, **${saved} Devlogs neu archiviert**.`
);
}
+38
View File
@@ -0,0 +1,38 @@
// Devlog-Archivierung: Webhook-Nachrichten aus dem Devlog-Kanal in SQLite sichern.
// Wird vom Live-Listener (MessageCreate) und vom /devlog-backfill-Command genutzt.
import { saveDevlog } from '../db.js';
/** Text aus einer Nachricht ziehen — Plain-Content plus Embed-Titel/-Beschreibungen */
function extractContent(message) {
const parts = [];
if (message.content?.trim()) {
parts.push(message.content.trim());
}
for (const embed of message.embeds) {
if (embed.title?.trim()) parts.push(embed.title.trim());
if (embed.description?.trim()) parts.push(embed.description.trim());
}
return parts.join('\n\n');
}
/**
* Nachricht archivieren, falls sie ein Devlog ist (Webhook-Post mit Text).
* @param {import('discord.js').Message} message
* @returns {boolean} true, wenn neu gespeichert
*/
export function archiveDevlogMessage(message) {
// Nur Webhook-Nachrichten — devlog.py postet per Discord-Webhook.
// Filtert nebenbei User-Chatter und Bot-Posts im Kanal aus.
if (!message.webhookId) return false;
const content = extractContent(message);
if (!content) return false;
return saveDevlog({
message_id: message.id,
channel_id: message.channelId,
content,
author_name: message.author?.username ?? null,
posted_at: message.createdAt.toISOString(),
});
}