feat: button-driven Telegram menu (reply keyboard + commands), reachability diagnostic, How-To update
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNQ8ghPfzAfsyVYd6HgFb6
This commit is contained in:
+22
-11
@@ -38,23 +38,33 @@ import Base from '../layouts/Base.astro';
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>4 · Bibliothek, Ordner & Alternativen</h2>
|
||||
<h2>4 · Bibliothek, Ordner, Alternativen & Weiterbearbeiten</h2>
|
||||
<p>Alle Ergebnisse landen in der <b>Bibliothek</b>. Über die Ordner-Leiste oben lässt sich nach Ordner
|
||||
filtern; jedem Ordner kann eine eigene Picdrop-Galerie zugeordnet werden. Mit <b>+ Alternative</b>
|
||||
entsteht eine weitere Fassung desselben Bildes — die Fassungen liegen gruppiert beieinander und lassen
|
||||
sich per Miniaturleiste umschalten. <b>Vorher/Nachher</b> zeigt den Vergleich zum Original.</p>
|
||||
filtern; jedem Ordner kann eine eigene Picdrop-Galerie zugeordnet werden (Pfeil am Ordner = eigene
|
||||
Galerie hinterlegt).</p>
|
||||
<ul>
|
||||
<li><b>+ Alternative</b>: erzeugt eine weitere Fassung desselben Bildes. Die Fassungen liegen gruppiert
|
||||
beieinander und lassen sich per Miniaturleiste umschalten (Badge „Fassung 2/3").</li>
|
||||
<li><b>Weiterbearbeiten</b>: übernimmt ein fertiges Bild als neue Vorlage ins Studio — dort erneut
|
||||
bearbeiten, ins nächste Format bringen oder im Modus Kombinieren mit weiteren Bildern mischen.</li>
|
||||
<li><b>Vorher/Nachher</b>: zeigt den Vergleich Vorlage ↔ Ergebnis (bei reinen Text-Bildern entfällt das).</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>5 · Alles per Telegram</h2>
|
||||
<h2>5 · Alles per Telegram — ohne Befehle</h2>
|
||||
<p>Einmalig koppeln: im Admin einen Code erzeugen und im Chat mit dem Bot eingeben. Danach läuft alles über
|
||||
die Knöpfe unten im Chat — kein Tippen von Befehlen nötig.</p>
|
||||
<ul>
|
||||
<li>Einmalig koppeln: im Admin einen Code erzeugen und im Chat mit dem Bot eingeben.</li>
|
||||
<li><b>Bearbeiten:</b> Bilder weiterleiten (am besten als <i>Datei</i> für volle Qualität) → Rezept wählen.</li>
|
||||
<li><b>Kombinieren:</b> zwei oder mehr Bilder mit einer Bildunterschrift senden (z. B. „mit unserer Hündin
|
||||
Frieda") → Knopf „🔀 Kombinieren".</li>
|
||||
<li><b>Neu erzeugen:</b> <code>/neu <Beschreibung></code> — z. B. <code>/neu ein Olivenzweig auf Sand</code>.</li>
|
||||
<li>Der Bot meldet sich einmal, wenn alles fertig ist — mit den Ergebnissen und Links.</li>
|
||||
<li><b>Bearbeiten:</b> Bild(er) schicken (am besten als <i>Datei</i> für volle Qualität) → unten das
|
||||
Rezept antippen.</li>
|
||||
<li><b>Kombinieren:</b> zwei oder mehr Bilder schicken, ins Bild-Textfeld eine Beschreibung wie
|
||||
„mit unserer Hündin Frieda" — dann „🔀 Kombinieren" antippen. (Ohne Beschreibung fragt der Bot nach.)</li>
|
||||
<li><b>Neu erzeugen:</b> unten auf „✨ Neues Bild" tippen und beschreiben, was entstehen soll.</li>
|
||||
<li><b>Weiterbearbeiten:</b> ein fertiges Bild vom Bot einfach wieder zurückschicken.</li>
|
||||
<li>Über „❓ Hilfe" gibt es die Kurzanleitung, über „📁 Rezept wählen" das Standard-Rezept.</li>
|
||||
</ul>
|
||||
<p class="mini">Für Profis gibt es zusätzlich das „/"-Befehlsmenü (/neu, /rezepte, /status) — nötig ist es nicht.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
@@ -78,6 +88,7 @@ import Base from '../layouts/Base.astro';
|
||||
.howto ol,.howto ul{margin:0;padding-left:20px;line-height:1.65;}
|
||||
.howto li{margin-bottom:6px;}
|
||||
.howto code{font-family:var(--font-mono);font-size:.85em;background:var(--paper);border:1px solid var(--line);border-radius:3px;padding:1px 5px;}
|
||||
.howto .mini{font-size:.82rem;color:var(--soft);margin:8px 0 0;}
|
||||
@media print{.topbar,.tabbar,.druck{display:none !important;}.howto section{break-inside:avoid;}}
|
||||
</style>
|
||||
</Base>
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
import type { APIRoute } from 'astro';
|
||||
import net from 'node:net';
|
||||
import dns from 'node:dns/promises';
|
||||
import { one } from '../../../lib/db';
|
||||
|
||||
export const prerender = false;
|
||||
const json = (b: unknown, s = 200) =>
|
||||
new Response(JSON.stringify(b), { status: s, headers: { 'Content-Type': 'application/json' } });
|
||||
|
||||
/** TCP-Verbindungstest aus dem Container heraus (mit Timeout). */
|
||||
function tcpProbe(host: string, port: number, timeoutMs = 6000): Promise<{ ok: boolean; ms: number; error?: string }> {
|
||||
return new Promise((resolve) => {
|
||||
const start = Date.now();
|
||||
const sock = new net.Socket();
|
||||
let done = false;
|
||||
const finish = (ok: boolean, error?: string) => {
|
||||
if (done) return; done = true;
|
||||
try { sock.destroy(); } catch { /* egal */ }
|
||||
resolve({ ok, ms: Date.now() - start, error });
|
||||
};
|
||||
sock.setTimeout(timeoutMs);
|
||||
sock.once('connect', () => finish(true));
|
||||
sock.once('timeout', () => finish(false, 'Timeout — keine Antwort (Firewall/Route?)'));
|
||||
sock.once('error', (e: any) => finish(false, e?.code || e?.message || 'Verbindungsfehler'));
|
||||
sock.connect(port, host);
|
||||
});
|
||||
}
|
||||
|
||||
/** Prüft, ob ein Host:Port aus dem Container erreichbar ist. Body {host,port} oder
|
||||
* ohne Angabe der konfigurierte NAS-Host. Testet zusätzlich DNS-Auflösung. */
|
||||
export const POST: APIRoute = async ({ request }) => {
|
||||
const b = await request.json().catch(() => ({} as any));
|
||||
let host = (b.host || '').trim();
|
||||
let port = Number(b.port) || 0;
|
||||
if (!host) {
|
||||
const s = await one<{ nas_host: string | null; nas_port: number | null; nas_protocol: string | null }>(
|
||||
'SELECT nas_host, nas_port, nas_protocol FROM settings WHERE id=1');
|
||||
host = (s?.nas_host || '').trim();
|
||||
port = s?.nas_port || (s?.nas_protocol === 'ftps' ? 21 : 22);
|
||||
}
|
||||
if (!host) return json({ error: 'Kein Host angegeben.' }, 400);
|
||||
if (!port) port = 22;
|
||||
|
||||
const out: any = { host, port };
|
||||
// DNS: nur wenn kein reines IP.
|
||||
const isIp = net.isIP(host) !== 0;
|
||||
if (!isIp) {
|
||||
try { const a = await dns.lookup(host); out.resolve = { ok: true, address: a.address }; }
|
||||
catch (e: any) { out.resolve = { ok: false, error: e?.code || 'DNS fehlgeschlagen' }; }
|
||||
} else {
|
||||
out.resolve = { ok: true, address: host, note: 'IP-Adresse' };
|
||||
}
|
||||
out.tcp = await tcpProbe(host, port);
|
||||
out.reachable = out.tcp.ok;
|
||||
return json(out);
|
||||
};
|
||||
Reference in New Issue
Block a user