Videos: Stufe 5 -- fuehrt der Knopf noch irgendwohin?

Ein Video kann bei TikTok geloescht oder auf privat gestellt werden.
Bei uns blieb der Eintrag stehen -- mit Cover, Text und einem Knopf
auf eine Fehlerseite. Das faellt beim Bauen nicht auf und beim Testen
nicht; es faellt dem auf, der klickt, und das ist die Community.

DREI ANTWORTEN, NICHT ZWEI. Die naheliegende Fassung fragt TikTok und
setzt bei einem Fehlschlag "weg". Die waere an einem einzigen
schlechten Nachmittag von TikTok in der Lage, die halbe Galerie als
geloescht zu markieren -- und wer das hinterher sucht, sucht lange,
denn im Protokoll stuende sauber, dass gefragt wurde. Also:

  Daten kommen an       -> da. Zaehler zurueck, eine frueher gesetzte
                           Markierung faellt weg (privat gestellte
                           Videos kommen wieder).
  "gibt es nicht" (404) -> ein Fehlversuch. Erst der DRITTE an drei
                           verschiedenen Tagen markiert.
  niemand antwortet     -> weiss nicht. Aendert gar nichts, nicht
                           einmal den Zeitstempel: Wer ihn setzte,
                           verschoebe die naechste Nachfrage um sieben
                           Tage, obwohl nichts gemessen wurde.

Drei Geschwindigkeiten beim Nachsehen: unauffaellig woechentlich,
verdaechtig taeglich (sonst dauerte eine Loeschung drei Wochen bis zur
Anzeige), schon markiert wieder woechentlich -- taeglich nachzufragen
waere Hammern fuer eine Auskunft, die wir schon haben. Gefunden hat
das eine rote Pruefung, nicht das Nachdenken davor.

Der Knopf verschwindet nicht, er sagt "Bei TikTok nicht mehr da".
Bewusst kein Rot: Es ist kein Fehler, sondern eine Auskunft -- Titel,
Text und Cover stimmen weiter, die liegen bei uns.

Geprueft: pruef-video 37 -> 51. Gegenprobe (Stoerung als "weg" werten
plus Markieren ab dem ersten Versuch) macht 8 rot, darunter woertlich
"eine Stoerung erhoeht den Zaehler NICHT (0 -> 1)".

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-09-18 16:08:15 +02:00
co-authored by Claude Opus 5
parent 88a8712638
commit fb55377e19
40 changed files with 766 additions and 410 deletions
+118
View File
@@ -63,6 +63,10 @@ const PNG = Buffer.from(
"base64");
let coverAbrufe = 0;
let oembedAbrufe = 0;
/* Welche Videos gerade „geloescht" sind und welche „stoeren".
Zwei getrennte Mengen, weil es zwei getrennte Faelle sind. */
const verschwunden = new Set();
const stoerung = new Set();
const coverAdresse = (was) => `http://127.0.0.1:${TIKTOK}/${was}`
+ `?x-expires=${Math.floor(Date.now() / 1000) + 172800}&x-signature=abc`;
@@ -75,6 +79,11 @@ const tiktok = createServer((q, a) => {
Dienst ist es genauso. */
const wer = (ziel.match(/@([^/?]+)/) || [])[1] || "";
if (!wer) { a.writeHead(404); a.end("{}"); return; }
/* UMSCHALTBAR FUER DIE WACHE (Abschnitt 5b): Ein Video kann
verschwinden und wiederkommen, und TikTok kann stoeren. Beides
sind verschiedene Dinge, und genau das muss messbar sein. */
if (verschwunden.has(ziel)) { a.writeHead(404); a.end("{}"); return; }
if (stoerung.has(ziel)) { a.writeHead(503); a.end("{}"); return; }
/* Welches Cover? Am Videonamen. So lassen sich die Sonderfälle
auslösen, ohne den Dienst umzubauen. */
let bild = coverAdresse("cover.jpg");
@@ -303,6 +312,115 @@ melde("=== 5. Die Byteprüfung ===");
ok(b.status === 201, `ein Video ohne Coverbild geht auch (${b.status})`);
}
/* =======================================================================
5b. FUEHRT DER KNOPF NOCH IRGENDWOHIN? (Stufe 5)
Der wichtigste Satz dieses Abschnitts ist nicht „geloeschte Videos
werden erkannt", sondern: EINE STOERUNG BEI TIKTOK AENDERT NICHTS.
Die naheliegende Fassung setzt bei jedem misslungenen Abruf „weg" --
und markiert an einem schlechten Nachmittag die halbe Galerie als
geloescht. Wer das hinterher sucht, sucht lange, denn im Protokoll
steht dann sauber, dass TikTok gefragt wurde.
======================================================================= */
melde("");
melde("=== 5b. Wenn ein Video bei TikTok verschwindet ===");
{
const video = await import("./workspace-video.js");
const db2 = new DatabaseSync(process.env.WORKSPACE_DB);
const nimm = (id) => db2.prepare(
"SELECT quelle_geprueft_am, quelle_fehlt_seit,"
+ " COALESCE(quelle_fehlversuche,0) AS fehl FROM eintraege WHERE id = ?").get(id);
/* Stellt den naechsten Tag dar. Die Wache sieht verdaechtige Videos
TAEGLICH nach -- ohne Zurueckdatieren waeren alle drei Laeufe am
selben Tag, und nur der erste taete etwas. Genau so ist es
gewollt; die Pruefung muss deshalb die Uhr weiterstellen, nicht
die Regel umgehen. */
const morgen = (id, stunden = 36) => db2.prepare(
"UPDATE eintraege SET quelle_geprueft_am = ? WHERE id = ?")
.run(new Date(Date.now() - stunden * 3600_000).toISOString(), id);
/* Derselbe Account wie in Abschnitt 1 -- ein ERFUNDENER wird von der
Route zu Recht abgewiesen, und die Pruefung haette dann gemessen,
dass sie sich selbst vertippt hat. */
const adresse = videoVon("dogfather0804", "5b1");
const ein = await holen(adresse);
const id5b = ein.daten?.id;
ok(ein.status === 201 && !!id5b, `ein Video zum Beobachten ist da (#${id5b})`);
/* ---- Die drei Antworten einzeln ---------------------------------- */
ok((await video.videoNochDa(adresse)).da === true,
"ein vorhandenes Video meldet: da");
stoerung.add(adresse);
const beiStoerung = await video.videoNochDa(adresse);
ok(beiStoerung.da === null,
`eine Stoerung meldet WEISS NICHT, nicht „weg" (${beiStoerung.grund})`);
stoerung.delete(adresse);
verschwunden.add(adresse);
const beiWeg = await video.videoNochDa(adresse);
ok(beiWeg.da === false, `ein geloeschtes meldet: weg (${beiWeg.grund})`);
/* ---- Ein einzelner Fehlversuch markiert NICHT --------------------- */
const l1 = await video.videoWacheLauf();
const n1 = nimm(id5b);
ok(n1.fehl === 1 && !n1.quelle_fehlt_seit,
`nach dem ersten Fehlversuch ist es noch NICHT „weg" (Zaehler ${n1.fehl})`);
morgen(id5b);
await video.videoWacheLauf();
const n2 = nimm(id5b);
ok(n2.fehl === 2 && !n2.quelle_fehlt_seit,
`nach dem zweiten auch nicht (Zaehler ${n2.fehl})`);
morgen(id5b);
const l3 = await video.videoWacheLauf();
const n3 = nimm(id5b);
ok(n3.fehl === 3 && !!n3.quelle_fehlt_seit,
`erst der dritte markiert es (Zaehler ${n3.fehl}, seit ${String(n3.quelle_fehlt_seit).slice(0, 10)})`);
ok(l3.weg === 1, `und der Lauf sagt es auch (weg: ${l3.weg})`);
/* ---- Die Oberflaeche erfaehrt davon ------------------------------- */
const e5b = (await liste()).find((x) => x.id === id5b);
ok(!!e5b?.quelle_fehlt_seit,
"die Liste liefert den Zustand mit -- sonst zeigte die Kachel weiter den Knopf");
/* ---- DER KERN: eine Stoerung aendert NICHTS ----------------------- */
verschwunden.delete(adresse);
/* ACHT TAGE, nicht 36 Stunden: Ein bereits markiertes Video wird
absichtlich nur noch woechentlich nachgefragt (siehe die drei
Geschwindigkeiten in workspace-video.js). Die Pruefung muss die
echte Regel abbilden, nicht die bequemere. */
morgen(id5b, 8 * 24);
await video.videoWacheLauf(); /* wieder da -> Zaehler zurueck */
const zurueck = nimm(id5b);
ok(zurueck.fehl === 0 && !zurueck.quelle_fehlt_seit,
"kommt es zurueck (privat gestellt und wieder oeffentlich), faellt die Markierung weg");
/* Ebenfalls acht Tage: Nach der Rueckkehr steht der Zaehler auf
null, das Video gilt also wieder als unauffaellig -- und
unauffaellige werden nur woechentlich gefragt. */
morgen(id5b, 8 * 24);
stoerung.add(adresse);
const vorher5b = nimm(id5b);
const lS = await video.videoWacheLauf();
const nachher5b = nimm(id5b);
stoerung.delete(adresse);
ok(nachher5b.fehl === vorher5b.fehl && !nachher5b.quelle_fehlt_seit,
`eine Stoerung erhoeht den Zaehler NICHT (${vorher5b.fehl} -> ${nachher5b.fehl})`);
ok(nachher5b.quelle_geprueft_am === vorher5b.quelle_geprueft_am,
"und verschiebt auch den Zeitpunkt nicht -- sonst waere sieben Tage Ruhe,"
+ " obwohl nichts gemessen wurde");
ok(lS.unklar >= 1 && lS.geprueft === 0,
`der Lauf zaehlt sie als „weiss nicht" (unklar ${lS.unklar}, geprueft ${lS.geprueft})`);
/* ---- Und ein gesundes Video bleibt unberuehrt --------------------- */
const heil = (await liste()).find((x) => x.id === eintragId);
ok(!heil?.quelle_fehlt_seit,
"ein Video, das es noch gibt, wird nicht angefasst");
db2.close();
void l1;
}
/* =======================================================================
6. UND ES ÜBERLEBT, WENN TIKTOK WEG IST ← der eigentliche Punkt
======================================================================= */