Bug-Reports, Devlog-Threads und Watchdog
- /bug (alle Member): erstellt Gitea-Issue inkl. Screenshot-Upload als Asset; bug_reports-Tabelle für den Rückkanal — Issue geschlossen (issues-Webhook) → DM an den Reporter - Auto-Thread '💬 Devlog <Datum>' unter jedem Devlog-Post (Setting, Default an) - Watchdog: prüft Setting watchdog_urls alle 2 min, DM an Admin nach 2 Fails in Folge + Entwarnung mit Downtime-Dauer - Setup-Seite: Bug-Repo, Threads-Toggle, Watchdog-URLs; Env GITEA_API_TOKEN/GITEA_URL Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -48,6 +48,15 @@ ADMIN_DISCORD_ID=
|
||||
# Lokal zum Testen: http://localhost:3080 (auch im Developer Portal eintragen!)
|
||||
PUBLIC_URL=https://bot.d4rkst3r.de
|
||||
|
||||
# --- Bug-Reports (/bug → Gitea-Issue) ---
|
||||
|
||||
# Gitea → Einstellungen → Anwendungen → Token erzeugen (Scope: write:issue)
|
||||
# Ohne Token ist /bug deaktiviert (Bot läuft trotzdem)
|
||||
GITEA_API_TOKEN=
|
||||
|
||||
# Optional: Gitea-Basis-URL (Default: https://git.d4rkst3r.de)
|
||||
# GITEA_URL=https://git.d4rkst3r.de
|
||||
|
||||
# Optional: HTTP-Port des Webservers (Default: 3080)
|
||||
# HTTP_PORT=3080
|
||||
|
||||
|
||||
@@ -142,6 +142,31 @@ Unter **/settings** (Nav: „Setup", nur als Admin sichtbar) lassen sich zur Lau
|
||||
|
||||
---
|
||||
|
||||
## Bug-Reports, Threads & Watchdog
|
||||
|
||||
**`/bug` (alle Member):** Titel + Beschreibung + optional Screenshot → der Bot
|
||||
erstellt ein Gitea-Issue im konfigurierten Repo (Setup-Seite), lädt den
|
||||
Screenshot als Issue-Attachment hoch und merkt sich den Reporter.
|
||||
Wird das Issue in Gitea **geschlossen**, bekommt der Reporter automatisch
|
||||
eine DM („Dein Bug ist behoben ✅").
|
||||
|
||||
Setup:
|
||||
1. Gitea → Einstellungen → Anwendungen → **Token erzeugen** (Scope `write:issue`)
|
||||
→ als `GITEA_API_TOKEN` in die Env
|
||||
2. Im Gitea-Webhook zusätzlich das Trigger-Event **„Issues"** aktivieren
|
||||
(für die Rückkanal-DMs)
|
||||
|
||||
**Auto-Threads:** Unter jedem Devlog-Post öffnet der Bot einen Diskussions-Thread
|
||||
(„💬 Devlog 23.07.", Auto-Archiv nach 3 Tagen). Abschaltbar auf der Setup-Seite.
|
||||
Der Bot braucht im Devlog-Kanal die Berechtigung **„Öffentliche Threads erstellen"**.
|
||||
|
||||
**Watchdog:** Auf der Setup-Seite URLs eintragen (kommagetrennt, z. B.
|
||||
`https://git.d4rkst3r.de, https://bot.d4rkst3r.de/health`) — der Bot prüft sie
|
||||
alle 2 Minuten und schickt dir bei Ausfall (2 Fehlschläge in Folge) eine
|
||||
**Discord-DM**, inkl. Entwarnung mit Downtime-Dauer.
|
||||
|
||||
---
|
||||
|
||||
## Setup: Webinterface
|
||||
|
||||
**Zugriffsmodell:** Devlog-Archiv ist öffentlich (wie der Discord-Kanal),
|
||||
|
||||
@@ -14,6 +14,8 @@ services:
|
||||
COMMIT_CHANNEL_ID: ${COMMIT_CHANNEL_ID}
|
||||
DEVLOG_CHANNEL_ID: ${DEVLOG_CHANNEL_ID}
|
||||
DEVLOG_POST_SECRET: ${DEVLOG_POST_SECRET}
|
||||
GITEA_API_TOKEN: ${GITEA_API_TOKEN:-}
|
||||
GITEA_URL: ${GITEA_URL:-https://git.d4rkst3r.de}
|
||||
GITEA_WEBHOOK_SECRET: ${GITEA_WEBHOOK_SECRET}
|
||||
DISCORD_CLIENT_SECRET: ${DISCORD_CLIENT_SECRET}
|
||||
SESSION_SECRET: ${SESSION_SECRET}
|
||||
|
||||
@@ -178,11 +178,55 @@ export default function Settings({ me }) {
|
||||
<span className="field-hint">Commits pro Tag, Devlogs & Top-Projekte in den Devlog-Kanal</span>
|
||||
</label>
|
||||
</div>
|
||||
<div className="field">
|
||||
<label className="toggle">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={form.devlog_threads_enabled}
|
||||
onChange={(e) => setForm({ ...form, devlog_threads_enabled: e.target.checked })}
|
||||
/>
|
||||
<span>Diskussions-Thread unter jedem Devlog</span>
|
||||
<span className="field-hint">Bot öffnet automatisch „💬 Devlog <Datum>"</span>
|
||||
</label>
|
||||
</div>
|
||||
<div className="field">
|
||||
<button className="btn" onClick={() => sendTest('recap')}>Rückblick jetzt testen</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Bug-Reports */}
|
||||
<div className="settings-section">
|
||||
<h2 className="settings-title">// Bug-Reports (/bug)</h2>
|
||||
{!data.status.giteaTokenConfigured && (
|
||||
<p className="notice" style={{ padding: '0 0 .8rem' }}>
|
||||
⚠️ Kein <code>GITEA_API_TOKEN</code> gesetzt — /bug ist deaktiviert.
|
||||
</p>
|
||||
)}
|
||||
<div className="field">
|
||||
<label>Ziel-Repo <span className="field-hint">hier landen die Issues aus /bug</span></label>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="D4rkst3r/EcoGame"
|
||||
value={form.bug_report_repo}
|
||||
onChange={(e) => setForm({ ...form, bug_report_repo: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Watchdog */}
|
||||
<div className="settings-section">
|
||||
<h2 className="settings-title">// Watchdog</h2>
|
||||
<div className="field">
|
||||
<label>Überwachte URLs <span className="field-hint">kommagetrennt; bei Ausfall gibt's eine DM — leer = aus</span></label>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="https://git.d4rkst3r.de, https://bot.d4rkst3r.de/health"
|
||||
value={form.watchdog_urls}
|
||||
onChange={(e) => setForm({ ...form, watchdog_urls: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Commit-Feed */}
|
||||
<div className="settings-section">
|
||||
<h2 className="settings-title">// Commit-Feed</h2>
|
||||
|
||||
+2
-1
@@ -5,9 +5,10 @@ import { devlogChannelId, devlogPingRoleId } from '../runtime-settings.js';
|
||||
import { archiveDevlogMessage, removeDevlog } from './devlog-archive.js';
|
||||
import * as ping from './commands/ping.js';
|
||||
import * as devlogBackfill from './commands/devlog-backfill.js';
|
||||
import * as bug from './commands/bug.js';
|
||||
|
||||
// Alle Commands hier eintragen — jedes Modul exportiert { data, execute }
|
||||
const commandModules = [ping, devlogBackfill];
|
||||
const commandModules = [ping, devlogBackfill, bug];
|
||||
|
||||
export async function startBot() {
|
||||
const client = new Client({
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
// /bug — Bug-Report aus Discord: erstellt ein Gitea-Issue (inkl. Screenshot-Upload)
|
||||
import { SlashCommandBuilder, MessageFlags } from 'discord.js';
|
||||
import { config } from '../../config.js';
|
||||
import { bugReportRepo } from '../../runtime-settings.js';
|
||||
import { createIssue, uploadIssueAsset } from '../../gitea-api.js';
|
||||
import { saveBugReport } from '../../db.js';
|
||||
|
||||
export const data = new SlashCommandBuilder()
|
||||
.setName('bug')
|
||||
.setDescription('Bug melden — landet direkt beim Entwickler')
|
||||
.addStringOption((o) =>
|
||||
o.setName('titel').setDescription('Kurze Zusammenfassung').setRequired(true).setMaxLength(120)
|
||||
)
|
||||
.addStringOption((o) =>
|
||||
o.setName('beschreibung')
|
||||
.setDescription('Was ist passiert? Was hast du erwartet?')
|
||||
.setRequired(true)
|
||||
.setMaxLength(1500)
|
||||
)
|
||||
.addAttachmentOption((o) =>
|
||||
o.setName('screenshot').setDescription('Optional: Screenshot des Problems')
|
||||
);
|
||||
|
||||
export async function execute(interaction) {
|
||||
await interaction.deferReply({ flags: MessageFlags.Ephemeral });
|
||||
|
||||
if (!config.giteaApiToken) {
|
||||
await interaction.editReply('❌ Bug-Reports sind gerade nicht konfiguriert (kein Gitea-Token).');
|
||||
return;
|
||||
}
|
||||
|
||||
const repo = bugReportRepo();
|
||||
const titel = interaction.options.getString('titel');
|
||||
const beschreibung = interaction.options.getString('beschreibung');
|
||||
const screenshot = interaction.options.getAttachment('screenshot');
|
||||
|
||||
const body = `${beschreibung}\n\n---\n🎮 Gemeldet via Discord von **${interaction.user.tag}**`;
|
||||
|
||||
try {
|
||||
const issue = await createIssue(repo, `[Bug] ${titel}`, body);
|
||||
|
||||
// Screenshot als Issue-Attachment hochladen (Discord-CDN-Links laufen ab)
|
||||
if (screenshot?.contentType?.startsWith('image/')) {
|
||||
try {
|
||||
const res = await fetch(screenshot.url);
|
||||
if (res.ok) {
|
||||
await uploadIssueAsset(
|
||||
repo,
|
||||
issue.number,
|
||||
screenshot.name ?? 'screenshot.png',
|
||||
Buffer.from(await res.arrayBuffer())
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[bug] Screenshot-Upload fehlgeschlagen:', error.message);
|
||||
}
|
||||
}
|
||||
|
||||
// Für den Rückkanal merken: Issue geschlossen → DM an den Reporter
|
||||
saveBugReport({
|
||||
repo,
|
||||
issue_number: issue.number,
|
||||
discord_user_id: interaction.user.id,
|
||||
title: titel,
|
||||
});
|
||||
|
||||
await interaction.editReply(
|
||||
`✅ Danke! Dein Bug ist erfasst als [#${issue.number}](${issue.html_url}) — ` +
|
||||
`du bekommst eine DM, sobald er behoben ist.`
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('[bug] Issue-Erstellung fehlgeschlagen:', error);
|
||||
await interaction.editReply('❌ Konnte das Issue nicht anlegen — bitte später nochmal versuchen.');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
// Watchdog: prüft konfigurierte URLs und schickt dem Admin eine DM bei Ausfall.
|
||||
// URLs kommen aus dem Setting watchdog_urls (Setup-Seite), leer = deaktiviert.
|
||||
import { getSetting } from '../db.js';
|
||||
import { config } from '../config.js';
|
||||
|
||||
const CHECK_INTERVAL_MS = 2 * 60 * 1000;
|
||||
const FAILS_BEFORE_ALERT = 2; // 2 Fehlschläge in Folge → Alarm (gegen Flattern)
|
||||
const TIMEOUT_MS = 10_000;
|
||||
|
||||
// URL → { fails, down, since }
|
||||
const state = new Map();
|
||||
|
||||
function watchedUrls() {
|
||||
return (getSetting('watchdog_urls') ?? '')
|
||||
.split(',')
|
||||
.map((s) => s.trim())
|
||||
.filter((s) => /^https?:\/\//.test(s));
|
||||
}
|
||||
|
||||
async function dmAdmin(client, content) {
|
||||
const user = await client.users.fetch(config.adminDiscordId);
|
||||
await user.send(content);
|
||||
}
|
||||
|
||||
/** Ein Prüfdurchlauf — exportiert für Tests und den Intervall-Timer */
|
||||
export async function watchdogTick(client, { failsBeforeAlert = FAILS_BEFORE_ALERT } = {}) {
|
||||
for (const url of watchedUrls()) {
|
||||
const s = state.get(url) ?? { fails: 0, down: false, since: null };
|
||||
|
||||
let ok = false;
|
||||
let detail = '';
|
||||
try {
|
||||
const res = await fetch(url, {
|
||||
method: 'GET',
|
||||
redirect: 'follow',
|
||||
signal: AbortSignal.timeout(TIMEOUT_MS),
|
||||
headers: { 'User-Agent': 'ecobot-watchdog/1.0' },
|
||||
});
|
||||
ok = res.status < 500;
|
||||
detail = `HTTP ${res.status}`;
|
||||
} catch (error) {
|
||||
detail = error.cause?.code ?? error.name ?? 'Fehler';
|
||||
}
|
||||
|
||||
if (ok) {
|
||||
if (s.down) {
|
||||
const minutes = Math.max(1, Math.round((Date.now() - s.since) / 60000));
|
||||
await dmAdmin(client, `✅ **${url}** ist wieder erreichbar (Downtime: ~${minutes} min).`).catch(() => {});
|
||||
console.log(`[watchdog] ${url} wieder ok`);
|
||||
}
|
||||
state.set(url, { fails: 0, down: false, since: null });
|
||||
} else {
|
||||
const fails = s.fails + 1;
|
||||
if (!s.down && fails >= failsBeforeAlert) {
|
||||
await dmAdmin(client, `🚨 **${url}** ist DOWN (${detail}) — seit ${failsBeforeAlert} Checks nicht erreichbar!`).catch(() => {});
|
||||
console.warn(`[watchdog] ${url} down (${detail})`);
|
||||
state.set(url, { fails, down: true, since: Date.now() });
|
||||
} else {
|
||||
state.set(url, { ...s, fails });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function startWatchdog(client) {
|
||||
setInterval(() => watchdogTick(client), CHECK_INTERVAL_MS);
|
||||
}
|
||||
@@ -47,4 +47,8 @@ export const config = {
|
||||
|
||||
// SQLite-Datei (im Container: /app/data → ecobot_data-Volume)
|
||||
dbPath: process.env.DB_PATH || './data/ecobot.db',
|
||||
|
||||
// Gitea-API (für /bug → Issues). Ohne Token ist /bug deaktiviert.
|
||||
giteaUrl: (process.env.GITEA_URL || 'https://git.d4rkst3r.de').replace(/\/$/, ''),
|
||||
giteaApiToken: process.env.GITEA_API_TOKEN || null,
|
||||
};
|
||||
|
||||
@@ -79,6 +79,40 @@ db.exec(`
|
||||
}
|
||||
}
|
||||
|
||||
// Bug-Reports aus Discord (/bug) — für den Rückkanal (Issue geschlossen → DM)
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS bug_reports (
|
||||
repo TEXT NOT NULL,
|
||||
issue_number INTEGER NOT NULL,
|
||||
discord_user_id TEXT NOT NULL,
|
||||
title TEXT,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
PRIMARY KEY (repo, issue_number)
|
||||
);
|
||||
`);
|
||||
|
||||
const insertBugReport = db.prepare(`
|
||||
INSERT OR REPLACE INTO bug_reports (repo, issue_number, discord_user_id, title)
|
||||
VALUES (@repo, @issue_number, @discord_user_id, @title)
|
||||
`);
|
||||
const getBugReportStmt = db.prepare(
|
||||
'SELECT * FROM bug_reports WHERE repo = ? AND issue_number = ?'
|
||||
);
|
||||
const deleteBugReportStmt = db.prepare(
|
||||
'DELETE FROM bug_reports WHERE repo = ? AND issue_number = ?'
|
||||
);
|
||||
|
||||
export function saveBugReport(report) {
|
||||
insertBugReport.run(report);
|
||||
}
|
||||
|
||||
/** Report holen und direkt entfernen (einmalige Benachrichtigung) */
|
||||
export function takeBugReport(repo, issueNumber) {
|
||||
const row = getBugReportStmt.get(repo, issueNumber);
|
||||
if (row) deleteBugReportStmt.run(repo, issueNumber);
|
||||
return row ?? null;
|
||||
}
|
||||
|
||||
// Laufzeit-Einstellungen (Settings-Seite im Webinterface) — überschreiben Env-Defaults
|
||||
db.exec('CREATE TABLE IF NOT EXISTS settings (key TEXT PRIMARY KEY, value TEXT NOT NULL)');
|
||||
const getSettingStmt = db.prepare('SELECT value FROM settings WHERE key = ?');
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
// Minimaler Gitea-API-Client (Token-Auth) — für /bug → Issues
|
||||
import { config } from './config.js';
|
||||
|
||||
function headers(extra = {}) {
|
||||
return {
|
||||
Authorization: `token ${config.giteaApiToken}`,
|
||||
Accept: 'application/json',
|
||||
...extra,
|
||||
};
|
||||
}
|
||||
|
||||
/** Issue anlegen; gibt { number, html_url } zurück */
|
||||
export async function createIssue(repo, title, body) {
|
||||
const res = await fetch(`${config.giteaUrl}/api/v1/repos/${repo}/issues`, {
|
||||
method: 'POST',
|
||||
headers: headers({ 'Content-Type': 'application/json' }),
|
||||
body: JSON.stringify({ title, body }),
|
||||
});
|
||||
if (!res.ok) {
|
||||
throw new Error(`Gitea createIssue ${res.status}: ${(await res.text()).slice(0, 200)}`);
|
||||
}
|
||||
const issue = await res.json();
|
||||
return { number: issue.number, html_url: issue.html_url };
|
||||
}
|
||||
|
||||
/** Datei (z. B. Screenshot) als Issue-Attachment hochladen */
|
||||
export async function uploadIssueAsset(repo, issueNumber, filename, buffer) {
|
||||
const form = new FormData();
|
||||
form.append('attachment', new Blob([buffer]), filename);
|
||||
const res = await fetch(
|
||||
`${config.giteaUrl}/api/v1/repos/${repo}/issues/${issueNumber}/assets?name=${encodeURIComponent(filename)}`,
|
||||
{ method: 'POST', headers: headers(), body: form }
|
||||
);
|
||||
if (!res.ok) {
|
||||
throw new Error(`Gitea uploadIssueAsset ${res.status}`);
|
||||
}
|
||||
return res.json();
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
import { startBot } from './bot/client.js';
|
||||
import { startWebServer } from './web/server.js';
|
||||
import { scheduleWeeklyRecap } from './bot/weekly-recap.js';
|
||||
import { startWatchdog } from './bot/watchdog.js';
|
||||
|
||||
process.on('unhandledRejection', (error) => {
|
||||
console.error('[main] Unhandled Rejection:', error);
|
||||
@@ -11,6 +12,7 @@ try {
|
||||
const client = await startBot();
|
||||
await startWebServer(client);
|
||||
scheduleWeeklyRecap(client);
|
||||
startWatchdog(client);
|
||||
} catch (error) {
|
||||
console.error('[main] Start fehlgeschlagen:', error);
|
||||
process.exit(1);
|
||||
|
||||
@@ -16,6 +16,16 @@ export function releaseChannelId() {
|
||||
return getSetting('release_channel_id') || null;
|
||||
}
|
||||
|
||||
/** Diskussions-Thread unter jedem Devlog-Post (Default: an) */
|
||||
export function devlogThreadsEnabled() {
|
||||
return getSetting('devlog_threads_enabled') !== '0';
|
||||
}
|
||||
|
||||
/** Ziel-Repo für /bug-Reports (Default: EcoGame) */
|
||||
export function bugReportRepo() {
|
||||
return getSetting('bug_report_repo') || 'D4rkst3r/EcoGame';
|
||||
}
|
||||
|
||||
/** Rolle, die bei Devlog-Posts gepingt wird — leer = kein Ping */
|
||||
export function devlogPingRoleId() {
|
||||
return getSetting('devlog_ping_role_id') || null;
|
||||
|
||||
+8
-1
@@ -139,6 +139,9 @@ ${rssItems}
|
||||
commit_branch_filter: getSetting('commit_branch_filter') ?? '',
|
||||
ignored_repos: getSetting('ignored_repos') ?? '',
|
||||
weekly_recap_enabled: getSetting('weekly_recap_enabled') !== '0',
|
||||
devlog_threads_enabled: getSetting('devlog_threads_enabled') !== '0',
|
||||
bug_report_repo: getSetting('bug_report_repo') ?? 'D4rkst3r/EcoGame',
|
||||
watchdog_urls: getSetting('watchdog_urls') ?? '',
|
||||
};
|
||||
}
|
||||
|
||||
@@ -154,6 +157,7 @@ ${rssItems}
|
||||
botTag: client.user?.tag ?? null,
|
||||
uptimeSeconds: Math.floor(process.uptime()),
|
||||
guilds: client.guilds.cache.size,
|
||||
giteaTokenConfigured: Boolean(config.giteaApiToken),
|
||||
...stats,
|
||||
},
|
||||
};
|
||||
@@ -197,7 +201,10 @@ ${rssItems}
|
||||
if (body.weekly_recap_enabled !== undefined) {
|
||||
setSetting('weekly_recap_enabled', body.weekly_recap_enabled ? '1' : '0');
|
||||
}
|
||||
for (const key of ['commit_branch_filter', 'ignored_repos']) {
|
||||
if (body.devlog_threads_enabled !== undefined) {
|
||||
setSetting('devlog_threads_enabled', body.devlog_threads_enabled ? '1' : '0');
|
||||
}
|
||||
for (const key of ['commit_branch_filter', 'ignored_repos', 'bug_report_repo', 'watchdog_urls']) {
|
||||
if (body[key] !== undefined) {
|
||||
setSetting(key, String(body[key]).trim());
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import { join } from 'node:path';
|
||||
import { ActionRowBuilder, AttachmentBuilder, ButtonBuilder, ButtonStyle, EmbedBuilder } from 'discord.js';
|
||||
import { config } from '../config.js';
|
||||
import { saveDevlog } from '../db.js';
|
||||
import { devlogChannelId, devlogPingRoleId } from '../runtime-settings.js';
|
||||
import { devlogChannelId, devlogPingRoleId, devlogThreadsEnabled } from '../runtime-settings.js';
|
||||
import { imagesDir } from '../bot/devlog-archive.js';
|
||||
|
||||
const BRAND_YELLOW = 0xf5c518;
|
||||
@@ -154,6 +154,21 @@ export function registerDevlogEndpoint(app, client) {
|
||||
: {}),
|
||||
});
|
||||
|
||||
// Diskussions-Thread unterm Devlog (Fehler nicht fatal — z. B. fehlende Rechte)
|
||||
if (devlogThreadsEnabled() && typeof message.startThread === 'function') {
|
||||
try {
|
||||
const threadDate = new Intl.DateTimeFormat('de-DE', {
|
||||
day: '2-digit', month: '2-digit',
|
||||
}).format(new Date());
|
||||
await message.startThread({
|
||||
name: `💬 Devlog ${threadDate}`,
|
||||
autoArchiveDuration: 4320, // 3 Tage
|
||||
});
|
||||
} catch (error) {
|
||||
request.log.warn(`Devlog-Thread fehlgeschlagen: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Direkt archivieren (der Live-Listener ignoriert eigene Bot-Posts bewusst)
|
||||
const imageFiles = [];
|
||||
for (const [i, f] of files.entries()) {
|
||||
|
||||
+23
-1
@@ -8,7 +8,7 @@ import { existsSync } from 'node:fs';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { dirname, join } from 'node:path';
|
||||
import { config } from '../config.js';
|
||||
import { saveCommits, saveRelease } from '../db.js';
|
||||
import { saveCommits, saveRelease, takeBugReport } from '../db.js';
|
||||
import { commitFeedEnabled, branchAllowed, repoIgnored } from '../runtime-settings.js';
|
||||
import { postPushEmbed } from '../bot/commit-feed.js';
|
||||
import { postReleaseEmbed } from '../bot/release-feed.js';
|
||||
@@ -140,6 +140,28 @@ export async function startWebServer(client) {
|
||||
return { ok: true, release: release.tag, posted };
|
||||
}
|
||||
|
||||
// Issue geschlossen → Rückkanal-DM an den Discord-Reporter (/bug)
|
||||
if (event === 'issues') {
|
||||
const payload = request.body;
|
||||
if (payload.action === 'closed' && payload.issue?.number) {
|
||||
const repo = payload.repository?.full_name ?? '';
|
||||
const report = takeBugReport(repo, payload.issue.number);
|
||||
if (report) {
|
||||
try {
|
||||
const user = await client.users.fetch(report.discord_user_id);
|
||||
await user.send(
|
||||
`✅ Dein gemeldeter Bug **„${report.title}"** wurde behoben!\n` +
|
||||
`Issue [#${payload.issue.number}](${payload.issue.html_url ?? ''}) ist geschlossen — danke für den Report! 🙌`
|
||||
);
|
||||
request.log.info(`Bug-Report-DM an ${report.discord_user_id} (Issue #${payload.issue.number})`);
|
||||
} catch (error) {
|
||||
request.log.warn(`Bug-Report-DM fehlgeschlagen: ${error.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
return { ok: true, event: 'issues' };
|
||||
}
|
||||
|
||||
// Andere Events (Tag, Issue, …) freundlich ignorieren
|
||||
if (event !== 'push') {
|
||||
return { ok: true, ignored: event };
|
||||
|
||||
Reference in New Issue
Block a user