XP-Beträge, Wartezeiten, Prüf-Intervalle, Obergrenzen und Postzeiten
standen als Konstanten im Code. Wer den Server-Monitor seltener prüfen
lassen wollte oder das Level-System anders austarieren, musste die
Quelldateien anfassen.
src/tuning.js hält sie jetzt an einer Stelle, mit Standard, erlaubtem
Bereich und Erklärung. Der neue Config-Bereich „Werte" zeigt sie nach
Themen gruppiert; neben jedem Feld steht der erlaubte Bereich und der
Standard, angepasste Werte lassen sich einzeln zurücksetzen.
Die Grenzen sind nicht Kosmetik: ein Vertipper beim Prüf-Intervall würde
sonst den Bot in eine Schleife im Sekundentakt schicken. Gespeichert wird
ganz oder gar nicht — ein ungültiger Wert im Formular lässt auch die
gültigen daneben unverändert, statt die Hälfte zu schreiben.
Intervalle laufen nicht mehr über setInterval mit festem Abstand, sondern
über everyTuned: der Wert wird vor jeder Runde neu gelesen. Ein geändertes
Intervall greift damit ab dem nächsten Durchlauf, ohne Neustart. Ein
Fehler in einer Runde beendet die Schleife nicht.
Beim Testen aufgefallen und behoben: Number('') ist 0 und nicht NaN — ein
nie gesetzter Wert wäre dadurch auf sein Minimum gefallen statt auf den
Standard. Das Level-System hätte also 1 XP pro Nachricht vergeben und der
Monitor jede Minute geprüft.
Der Fetch-Wrapper im Frontend hat die Fehlermeldung des Servers verworfen;
jetzt steht im Panel „XP pro Nachricht: 1–500 XP" statt „fehlgeschlagen".
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
91 lines
3.6 KiB
JavaScript
91 lines
3.6 KiB
JavaScript
// Wochen-Rückblick: sonntags 20:00 (Container-TZ, siehe compose) postet der Bot
|
|
// eine Zusammenfassung der Woche in den Devlog-Kanal.
|
|
import { EmbedBuilder } from 'discord.js';
|
|
import { weeklyStats, getSetting, setSetting } from '../db.js';
|
|
import { devlogChannelId, publicUrl, brandColor, brandFooter } from '../runtime-settings.js';
|
|
import { config } from '../config.js';
|
|
import { moduleEnabled } from '../modules.js';
|
|
import { tuning } from '../tuning.js';
|
|
|
|
const CHECK_INTERVAL_MS = 5 * 60 * 1000;
|
|
|
|
/** Balken-Zeile: `Mo 21.07 ████████ 12` */
|
|
function buildBars(perDay) {
|
|
const byDay = new Map(perDay.map((r) => [r.day, r.n]));
|
|
const rows = [];
|
|
let max = 1;
|
|
// Letzte 7 Tage, älteste zuerst
|
|
for (let i = 6; i >= 0; i--) {
|
|
const date = new Date(Date.now() - i * 86400000);
|
|
const key = date.toISOString().slice(0, 10);
|
|
const n = byDay.get(key) ?? 0;
|
|
max = Math.max(max, n);
|
|
rows.push({ date, n });
|
|
}
|
|
const dayFmt = new Intl.DateTimeFormat('de-DE', { weekday: 'short' });
|
|
const dateFmt = new Intl.DateTimeFormat('de-DE', { day: '2-digit', month: '2-digit' });
|
|
return rows
|
|
.map((r) => {
|
|
const bar = '█'.repeat(Math.round((r.n / max) * 14)) || '·';
|
|
return `${dayFmt.format(r.date).padEnd(3)}${dateFmt.format(r.date)} ${bar} ${r.n}`;
|
|
})
|
|
.join('\n');
|
|
}
|
|
|
|
/** Rückblick-Embed bauen und in den Devlog-Kanal posten */
|
|
export async function postWeeklyRecap(client) {
|
|
if (!moduleEnabled('weekly_recap')) return;
|
|
const channelId = devlogChannelId();
|
|
if (!channelId) throw new Error('Kein Devlog-Kanal konfiguriert');
|
|
|
|
const channel = await client.channels.fetch(channelId);
|
|
if (!channel?.isTextBased()) throw new Error('Devlog-Kanal nicht gefunden');
|
|
|
|
const stats = weeklyStats();
|
|
const totalCommits = stats.perDay.reduce((sum, r) => sum + r.n, 0);
|
|
const repoLine = stats.topRepos
|
|
.map((r) => `**${r.repo.split('/').pop()}** (${r.n})`)
|
|
.join(' · ');
|
|
|
|
const embed = new EmbedBuilder()
|
|
.setColor(brandColor())
|
|
.setTitle('📊 Wochen-Rückblick')
|
|
.setURL(`${publicUrl()}/devlogs`)
|
|
.setDescription(
|
|
`\`\`\`\n${buildBars(stats.perDay)}\n\`\`\``
|
|
)
|
|
.addFields(
|
|
{ name: 'Commits', value: String(totalCommits), inline: true },
|
|
{ name: 'Devlogs', value: String(stats.devlogs), inline: true },
|
|
...(repoLine ? [{ name: 'Aktive Projekte', value: repoLine, inline: false }] : [])
|
|
)
|
|
.setFooter({ text: brandFooter('WEEKLY'), iconURL: client?.user?.displayAvatarURL?.({ size: 64 }) })
|
|
.setTimestamp();
|
|
|
|
await channel.send({ embeds: [embed] });
|
|
}
|
|
|
|
/** Scheduler starten: prüft alle 5 Minuten, ob Sonntag ≥ 20:00 und noch nicht gepostet */
|
|
export function scheduleWeeklyRecap(client) {
|
|
const check = async () => {
|
|
if (getSetting('weekly_recap_enabled') === '0') return;
|
|
|
|
const now = new Date();
|
|
// Wochentag und Uhrzeit kommen aus den Stellwerten (lokale TZ des Containers)
|
|
if (now.getDay() !== tuning('recap_weekday') || now.getHours() < tuning('recap_hour')) return;
|
|
|
|
const today = now.toISOString().slice(0, 10);
|
|
if (getSetting('last_weekly_recap') === today) return;
|
|
|
|
try {
|
|
await postWeeklyRecap(client);
|
|
setSetting('last_weekly_recap', today);
|
|
console.log('[weekly] Wochen-Rückblick gepostet');
|
|
} catch (error) {
|
|
console.error('[weekly] Rückblick fehlgeschlagen:', error);
|
|
}
|
|
};
|
|
setInterval(check, CHECK_INTERVAL_MS);
|
|
check();
|
|
}
|