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:
+22
-2
@@ -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;
|
||||
|
||||
|
||||
@@ -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**.`
|
||||
);
|
||||
}
|
||||
@@ -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(),
|
||||
});
|
||||
}
|
||||
@@ -18,6 +18,10 @@ export const config = {
|
||||
// Optional: Guild-ID für sofortige Slash-Command-Registrierung (global dauert bis zu 1h)
|
||||
discordGuildId: process.env.DISCORD_GUILD_ID || null,
|
||||
|
||||
// Devlog-Archiv (Feature 3)
|
||||
// Kanal, in den tools/devlog.py (EcoGame-Repo) per Discord-Webhook postet
|
||||
devlogChannelId: required('DEVLOG_CHANNEL_ID'),
|
||||
|
||||
// Commit-Feed (Feature 2)
|
||||
// Kanal, in den Push-Embeds gepostet werden
|
||||
commitChannelId: required('COMMIT_CHANNEL_ID'),
|
||||
|
||||
@@ -23,6 +23,16 @@ db.exec(`
|
||||
received_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_commits_repo_time ON commits (repo, committed_at DESC);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS devlogs (
|
||||
message_id TEXT PRIMARY KEY,
|
||||
channel_id TEXT NOT NULL,
|
||||
content TEXT NOT NULL,
|
||||
author_name TEXT,
|
||||
posted_at TEXT NOT NULL,
|
||||
archived_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_devlogs_posted ON devlogs (posted_at DESC);
|
||||
`);
|
||||
|
||||
const insertCommit = db.prepare(`
|
||||
@@ -38,3 +48,13 @@ export const saveCommits = db.transaction((commits) => {
|
||||
}
|
||||
return inserted;
|
||||
});
|
||||
|
||||
const insertDevlog = db.prepare(`
|
||||
INSERT OR IGNORE INTO devlogs (message_id, channel_id, content, author_name, posted_at)
|
||||
VALUES (@message_id, @channel_id, @content, @author_name, @posted_at)
|
||||
`);
|
||||
|
||||
/** Devlog speichern — bereits bekannte Message-IDs werden ignoriert. Gibt true zurück, wenn neu. */
|
||||
export function saveDevlog(devlog) {
|
||||
return insertDevlog.run(devlog).changes > 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user