tools/post-devlog.mjs: Devlogs aus diesem Repo posten
devlog.py im EcoGame-Repo taugt für Bot-Devlogs nicht: es zählt EcoGame-Commits, bricht ohne solche ganz ab, betitelt den Post als „Devlog — EcoGame" und löscht die Bilder nach dem Senden. Das Skript hier zählt die Commits von d4rkbot, nimmt einen Bilder-Ordner (max. 4, wie der Endpoint), lässt die Bilder liegen und kann mit --dry vorher zeigen, was rausginge. Die Webhook-Adresse kommt aus der Umgebung oder einer Datei und wird nie ausgegeben — auch nicht im Fehlerfall. Beim Testen gegen einen lokalen Bot ohne Discord gefunden: payload_json als Blob anzuhängen kommt beim Endpoint als Datei-Teil an und wird zusammen mit den Nicht-Bildern verworfen — der Post wäre mit „empty devlog" abgelehnt worden. Muss ein Textfeld sein. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,133 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
// Devlog aus DIESEM Repo posten.
|
||||||
|
//
|
||||||
|
// tools/devlog.py im EcoGame-Repo geht dafür nicht: es zählt EcoGame-Commits,
|
||||||
|
// bricht ohne solche ab, betitelt den Post als „Devlog — EcoGame" und löscht
|
||||||
|
// die Bilder nach dem Senden. Dieses Skript zählt die Commits von d4rkbot,
|
||||||
|
// lässt die Bilder liegen und kann vorher trocken laufen.
|
||||||
|
//
|
||||||
|
// Aufruf:
|
||||||
|
// node tools/post-devlog.mjs --text docs/devlog-hub.md --images A:/eco/devlog-hub-bilder
|
||||||
|
// node tools/post-devlog.mjs --text docs/devlog-hub.md --dry
|
||||||
|
//
|
||||||
|
// Die Webhook-Adresse (enthält das Secret) kommt aus der Umgebungsvariable
|
||||||
|
// DEVLOG_WEBHOOK oder aus einer Datei — Standard ist die vorhandene
|
||||||
|
// A:/eco/EcoGame/tools/.devlog_webhook. Sie wird nie ausgegeben.
|
||||||
|
import { execFileSync } from 'node:child_process';
|
||||||
|
import { readFileSync, existsSync, readdirSync, statSync } from 'node:fs';
|
||||||
|
import { basename, dirname, join, resolve } from 'node:path';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
|
||||||
|
const REPO = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
||||||
|
const MAX_IMAGES = 4;
|
||||||
|
const IMAGE_PATTERN = /\.(png|jpe?g|gif|webp)$/i;
|
||||||
|
|
||||||
|
function arg(name, fallback = null) {
|
||||||
|
const i = process.argv.indexOf(`--${name}`);
|
||||||
|
return i !== -1 && process.argv[i + 1] && !process.argv[i + 1].startsWith('--')
|
||||||
|
? process.argv[i + 1]
|
||||||
|
: fallback;
|
||||||
|
}
|
||||||
|
const has = (name) => process.argv.includes(`--${name}`);
|
||||||
|
|
||||||
|
function webhookUrl() {
|
||||||
|
const fromEnv = String(process.env.DEVLOG_WEBHOOK ?? '').trim();
|
||||||
|
if (fromEnv) return fromEnv;
|
||||||
|
const file = arg('webhook-file', 'A:/eco/EcoGame/tools/.devlog_webhook');
|
||||||
|
if (!existsSync(file)) {
|
||||||
|
console.error(
|
||||||
|
`Keine Webhook-Adresse gefunden.\n` +
|
||||||
|
` DEVLOG_WEBHOOK setzen oder --webhook-file <datei> angeben.\n` +
|
||||||
|
` Gesucht wurde: ${file}`
|
||||||
|
);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
return readFileSync(file, 'utf8').trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Commits dieses Repos seit <since> — nur für die Fußzeile */
|
||||||
|
function commitCount(since) {
|
||||||
|
try {
|
||||||
|
const out = execFileSync('git', ['-C', REPO, 'log', `--since=${since}`, '--oneline', '--no-merges'], {
|
||||||
|
encoding: 'utf8',
|
||||||
|
});
|
||||||
|
return out.split('\n').filter((l) => l.trim()).length;
|
||||||
|
} catch {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function collectImages(dir) {
|
||||||
|
if (!dir) return [];
|
||||||
|
if (!existsSync(dir) || !statSync(dir).isDirectory()) {
|
||||||
|
console.error(`Bilder-Ordner nicht gefunden: ${dir}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
const files = readdirSync(dir).filter((f) => IMAGE_PATTERN.test(f)).sort();
|
||||||
|
if (files.length > MAX_IMAGES) {
|
||||||
|
console.warn(`Hinweis: ${files.length} Bilder gefunden, es werden die ersten ${MAX_IMAGES} gepostet.`);
|
||||||
|
}
|
||||||
|
return files.slice(0, MAX_IMAGES).map((f) => join(dir, f));
|
||||||
|
}
|
||||||
|
|
||||||
|
const textFile = arg('text');
|
||||||
|
if (!textFile) {
|
||||||
|
console.error('Bitte --text <datei> angeben (Markdown mit der Prosa).');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
if (!existsSync(textFile)) {
|
||||||
|
console.error(`Textdatei nicht gefunden: ${textFile}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const prose = readFileSync(textFile, 'utf8').trim();
|
||||||
|
if (!prose) {
|
||||||
|
console.error('Die Textdatei ist leer — nicht gepostet.');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const project = arg('project', 'd4rkbot');
|
||||||
|
const since = arg('since', 'yesterday');
|
||||||
|
const images = collectImages(arg('images'));
|
||||||
|
const n = commitCount(since);
|
||||||
|
|
||||||
|
const description = `${prose}\n\n*${n} Commit${n === 1 ? '' : 's'} heute — Details im [Repo](https://git.d4rkst3r.de/D4rkst3r/${project})*`;
|
||||||
|
const payload = {
|
||||||
|
embeds: [{
|
||||||
|
title: `Devlog — ${project}`,
|
||||||
|
description,
|
||||||
|
url: `https://git.d4rkst3r.de/D4rkst3r/${project}`,
|
||||||
|
}],
|
||||||
|
};
|
||||||
|
|
||||||
|
if (description.length > 3900) {
|
||||||
|
console.warn(`Achtung: Text ist ${description.length} Zeichen — Discord kürzt ab 4096.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Projekt: ${project}`);
|
||||||
|
console.log(`Text: ${textFile} (${prose.length} Zeichen)`);
|
||||||
|
console.log(`Commits: ${n} seit ${since}`);
|
||||||
|
console.log(`Bilder: ${images.length ? images.map((i) => basename(i)).join(', ') : '— keine —'}`);
|
||||||
|
|
||||||
|
if (has('dry')) {
|
||||||
|
console.log('\n--- Vorschau (nichts gesendet) ---\n');
|
||||||
|
console.log(description);
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
const form = new FormData();
|
||||||
|
// Als Textfeld anhängen, nicht als Blob: ein Blob käme als Datei-Teil an und
|
||||||
|
// würde vom Endpoint zusammen mit den Nicht-Bildern verworfen.
|
||||||
|
form.append('payload_json', JSON.stringify(payload));
|
||||||
|
for (const [i, file] of images.entries()) {
|
||||||
|
form.append(`files[${i}]`, new Blob([readFileSync(file)]), basename(file));
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await fetch(webhookUrl(), { method: 'POST', body: form });
|
||||||
|
if (!res.ok) {
|
||||||
|
// Die Adresse enthält das Secret — deshalb nur Status und Antworttext zeigen
|
||||||
|
console.error(`\nFehlgeschlagen: HTTP ${res.status}`);
|
||||||
|
console.error(await res.text().catch(() => ''));
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
console.log(`\nGepostet (HTTP ${res.status}). Die Bilder bleiben liegen.`);
|
||||||
Reference in New Issue
Block a user