Commander-Wünsche vom 02.09.2026 vor der Freigabe von 5.4.0 (die deshalb nie veröffentlicht wurde): Kern: kern/index.ts ist nur noch Bootstrap und Router; Dienste in kern/dienste/ (Einstellungen, Werkzeuge, Laufwerke, Erkennung, Vorgänge, Speicher, Benachrichtigung, Automatik), Kontext in kern/kontext.ts, Protokoll-Datei in gemeinsam/protokoll.ts, Platz-Rechnung in gemeinsam/platz.ts. Oberfläche: fenster/App.tsx ist nur noch der Rahmen; bausteine.tsx, dialoge/, uebersicht/ (LaufwerkKachel, KompressionsKarte, SpeicherKarte, VerlaufKarte), einstellungen/ (Karten je Funktion, SprachAuswahl), einrichtung/ (ErstEinrichtung, PosterHintergrund). Neu: Bibliothek weg, dafür Verlauf; Speicher-und-Bilanz-Karte (statfs); LED-Fußleiste; MakeMKV-Kopf in die Einstellungen; Sprach-Chips mit Dropdown statt Allgemeinem Preset; Karten Automatik/Benachrichtigung/Allgemein/Update; Durchsuchen bei Werkzeugen; Arbeitsordner lokal (Encode dort, dann in die Ablage); Platz-Prüfung vor dem Rip (Dialog und Kern); Automatik mit Countdown; Serien-Merker über Discs; Rettungs-Abbild mit Nullen nur nach gesehenen Lesefehlern (kern/rip/rettung.ts, Phase rettet, iso:-Quelle); Warteschlange pausieren; Discord/Telegram-Benachrichtigung; Protokoll-Datei je Tag; Fensterlage 80 % mittig, gemerkt; Erst-Einrichtung in acht Schritten vor dem Poster-Grid. Beweise: 413 Tests (neu: speicher, benachrichtigung, automatik, rettung, fensterlage, protokoll, kleinkram-550), drei Typprüfungen, Klick-Beweis mit 29 Schritten inklusive Erst-Einrichtung (zweiter Start mit RIPPY_START_BEREICH=einrichtung), Bilder beweise/klick/01…09. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
95 lines
4.0 KiB
TypeScript
95 lines
4.0 KiB
TypeScript
// Benachrichtigung aufs Handy (5.5.0, aus ARM: Apprise/Pushover/…) — zwei
|
|
// Wege, beide ohne Bibliothek, beide dokumentiert (Regel D):
|
|
// Discord-Webhook: POST <webhook-url> mit JSON { content } —
|
|
// discord.com/developers/docs/resources/webhook („Execute Webhook":
|
|
// content ist eine Zeichenkette bis 2000 Zeichen).
|
|
// Telegram-Bot: POST https://api.telegram.org/bot<TOKEN>/sendMessage mit
|
|
// JSON { chat_id, text } — core.telegram.org/bots/api#sendmessage.
|
|
// Ein Fehlschlag wird GESAGT (Ereignis-Zeile), kippt aber nie den Vorgang,
|
|
// der ihn ausgelöst hat.
|
|
import type { KernKontext } from '../kontext'
|
|
import type { EinstellungsDienst } from './einstellungen'
|
|
|
|
export interface Kanaele {
|
|
discordWebhook: string
|
|
telegramToken: string
|
|
telegramChatId: string
|
|
}
|
|
|
|
/** Was an Discord geht (pur, testbar). */
|
|
export function discordAnfrage(webhook: string, titel: string, text: string): { url: string; body: string } {
|
|
const inhalt = `**${titel}**\n${text}`.slice(0, 1990)
|
|
return { url: webhook, body: JSON.stringify({ content: inhalt, username: 'Rippy' }) }
|
|
}
|
|
|
|
/** Was an Telegram geht (pur, testbar). */
|
|
export function telegramAnfrage(token: string, chatId: string, titel: string, text: string): { url: string; body: string } {
|
|
return {
|
|
url: `https://api.telegram.org/bot${token}/sendMessage`,
|
|
body: JSON.stringify({ chat_id: chatId, text: `${titel}\n${text}`.slice(0, 4000) }),
|
|
}
|
|
}
|
|
|
|
export function kanaeleAus(lesen: (schluessel: string) => string): Kanaele {
|
|
return {
|
|
discordWebhook: lesen('discordWebhook').trim(),
|
|
telegramToken: lesen('telegramToken').trim(),
|
|
telegramChatId: lesen('telegramChatId').trim(),
|
|
}
|
|
}
|
|
|
|
export function kanaeleEingerichtet(k: Kanaele): boolean {
|
|
return k.discordWebhook.length > 0 || (k.telegramToken.length > 0 && k.telegramChatId.length > 0)
|
|
}
|
|
|
|
export class BenachrichtigungsDienst {
|
|
constructor(
|
|
private readonly kontext: KernKontext,
|
|
private readonly einstellungen: EinstellungsDienst,
|
|
private readonly holen: typeof fetch = fetch,
|
|
) {}
|
|
|
|
eingeschaltet(): boolean {
|
|
return this.einstellungen.lesen('benachrichtigungen', 'an') !== 'aus'
|
|
}
|
|
|
|
/** Schickt über alle eingerichteten Kanäle. Liefert die Fehler (leer = alles gut). */
|
|
async senden(titel: string, text: string, erzwingen = false): Promise<string[]> {
|
|
if (!erzwingen && !this.eingeschaltet()) return []
|
|
const k = kanaeleAus((s) => this.einstellungen.lesen(s))
|
|
const fehler: string[] = []
|
|
const anfragen: Array<{ name: string; url: string; body: string }> = []
|
|
if (k.discordWebhook.length > 0) anfragen.push({ name: 'Discord', ...discordAnfrage(k.discordWebhook, titel, text) })
|
|
if (k.telegramToken.length > 0 && k.telegramChatId.length > 0) {
|
|
anfragen.push({ name: 'Telegram', ...telegramAnfrage(k.telegramToken, k.telegramChatId, titel, text) })
|
|
}
|
|
for (const a of anfragen) {
|
|
try {
|
|
const antwort = await this.holen(a.url, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: a.body,
|
|
signal: AbortSignal.timeout(15_000),
|
|
})
|
|
if (antwort.status >= 300) fehler.push(`${a.name}: HTTP ${antwort.status}`)
|
|
} catch (e) {
|
|
fehler.push(`${a.name}: ${String((e as Error).message ?? e).slice(0, 120)}`)
|
|
}
|
|
}
|
|
for (const f of fehler) this.kontext.fehler(`Benachrichtigung ${f}`)
|
|
if (anfragen.length > 0 && fehler.length === 0) this.kontext.protokoll.schreiben(`Benachrichtigt (${anfragen.map((a) => a.name).join(', ')}): ${titel}`)
|
|
return fehler
|
|
}
|
|
|
|
/** Der Knopf „Testnachricht" — antwortet IMMER etwas. */
|
|
async testen(): Promise<void> {
|
|
const k = kanaeleAus((s) => this.einstellungen.lesen(s))
|
|
if (!kanaeleEingerichtet(k)) {
|
|
this.kontext.fehler('Benachrichtigung: kein Kanal eingerichtet — Discord-Webhook oder Telegram-Bot eintragen.')
|
|
return
|
|
}
|
|
const fehler = await this.senden('Rippy meldet sich', 'Testnachricht — die Verbindung steht.', true)
|
|
if (fehler.length === 0) this.kontext.hinweis('Testnachricht verschickt.')
|
|
}
|
|
}
|