Files
dogfather-universe/server-internal/lib/live-status.js
T
DogFatherGit e87aca3019 Postfach/Team-Verwaltung/Supporter-Abo als Node.js/Express-Server portiert
Vollständiger 1:1-Port des cloudflare-worker/ (dogfather-universe-postfach)
auf server-internal/: Bewerbungs-Postfach mit Rollen/Rechten, Team-
Zugangsverwaltung (verschlüsselte Owner-only-Codes), DogiCrew-Supporter-Abo
(PayPal Subscriptions, Google-Login, Resend-E-Mail, 30-Monats-Prämienzyklus)
und automatische TikTok-Live-Erkennung. D1 -> better-sqlite3, Cloudflare-
Cron -> node-cron. Läuft ohne gesetzte Secrets weiter (PayPal/Google/Resend/
Ticketanizer/Discord bleiben "nicht konfiguriert" statt kaputt), bis die
echten Zugangsdaten eingetragen werden.
2026-08-05 18:57:19 +02:00

134 lines
4.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* =====================================================================
lib/live-status.js — Automatische TikTok-Live-Erkennung für den grünen
Punkt auf der Startseite. 1:1 portiert aus dem Live-Status-Teil von
cloudflare-worker/src/worker.js, D1 → better-sqlite3.
Ein Cron-Auftrag (node-cron in index.js, ersetzt Cloudflares
`scheduled()`) fragt jede Minute bei TikTok nach, ob @dogfather0804
gerade sendet, und schreibt das Ergebnis in die Tabelle app_settings.
Zwei Schlüssel:
live_auto letzter ERFOLGREICH ermittelter Stand (wird bei einem
fehlgeschlagenen Abruf bewusst NICHT überschrieben,
damit ein TikTok-Aussetzer den Punkt nicht fälschlich
auf offline zieht)
live_override manueller Vorrang aus dem Postfach, mit Ablaufzeit.
Solange er gilt, schlägt er die Automatik.
===================================================================== */
import { db } from "../db.js";
import { json } from "./http.js";
import { logAction } from "./audit.js";
import { checkTikTokLive } from "./tiktok-live.js";
const DEFAULT_OVERRIDE_MINUTES = 180;
function readSetting(key) {
const row = db.prepare(`SELECT value FROM app_settings WHERE key = ?`).get(key);
if (!row || !row.value) return null;
try {
return JSON.parse(row.value);
} catch {
return null;
}
}
function writeSetting(key, value) {
db.prepare(
`INSERT INTO app_settings (key, value, updated_at) VALUES (?, ?, ?)
ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at`
).run(key, JSON.stringify(value), new Date().toISOString());
}
/** Ermittelt den Stand, der nach außen gilt (Vorrang: manuell vor Automatik). */
function resolveLiveStatus() {
const auto = readSetting("live_auto") || {};
const override = readSetting("live_override");
const overrideActive = override && override.until && new Date(override.until).getTime() > Date.now();
if (overrideActive) {
return {
live: !!override.live,
source: "manuell",
updatedAt: override.setAt || null,
overrideUntil: override.until,
autoLive: auto.live ?? null,
autoCheckedAt: auto.checkedAt || null,
autoHealthy: auto.ok !== false,
};
}
return {
live: !!auto.live,
source: "automatisch",
updatedAt: auto.changedAt || auto.checkedAt || null,
title: auto.title || null,
autoLive: auto.live ?? null,
autoCheckedAt: auto.checkedAt || null,
autoHealthy: auto.ok !== false,
};
}
export async function handleLiveStatusGet(req, res) {
return json(res, { ok: true, ...resolveLiveStatus() });
}
/**
* Manueller Eingriff aus dem Postfach.
* { live: true|false, minutes?: number } → Vorrang setzen
* { auto: true } → Vorrang aufheben, Automatik übernimmt
*/
export async function handleLiveStatusSet(req, res, session) {
if (!session?.isOwner) return json(res, { ok: false, error: "Nur Dogi darf den Live-Status ändern." }, 403);
const body = req.body || {};
if (body.auto === true) {
db.prepare(`DELETE FROM app_settings WHERE key = 'live_override'`).run();
logAction("owner", "live.override_cleared", null, null);
return json(res, { ok: true, ...resolveLiveStatus() });
}
const minutes = Number.isFinite(body.minutes) ? Math.min(Math.max(body.minutes, 5), 1440) : DEFAULT_OVERRIDE_MINUTES;
const now = new Date();
writeSetting("live_override", {
live: !!body.live,
setAt: now.toISOString(),
until: new Date(now.getTime() + minutes * 60_000).toISOString(),
});
logAction("owner", "live.override_set", null, { live: !!body.live, minutes });
return json(res, { ok: true, ...resolveLiveStatus() });
}
/** Wird vom Cron-Auftrag aufgerufen (index.js) — fragt TikTok ab und schreibt das Ergebnis. */
export async function refreshLiveFromTikTok() {
const username = process.env.TIKTOK_USERNAME || "dogfather0804";
const result = await checkTikTokLive(username);
const now = new Date().toISOString();
if (!result.ok) {
// Fehlgeschlagen: letzten guten Stand behalten, nur den Fehler vermerken.
const previous = readSetting("live_auto") || {};
writeSetting("live_auto", { ...previous, ok: false, lastError: result.reason, checkedAt: now });
return { ok: false, reason: result.reason };
}
const previous = readSetting("live_auto") || {};
const changed = previous.live !== result.live;
writeSetting("live_auto", {
ok: true,
live: result.live,
title: result.title,
roomId: result.roomId,
rawStatus: result.rawStatus,
checkedAt: now,
changedAt: changed ? now : previous.changedAt || now,
lastError: null,
});
if (changed) {
logAction("system", result.live ? "live.started" : "live.ended", null, { title: result.title });
}
return { ok: true, live: result.live, changed };
}