From 015ac7738e4e492ba9775cdede61a1f3add99bd9 Mon Sep 17 00:00:00 2001 From: Dogfather Date: Wed, 7 Oct 2026 18:02:02 +0200 Subject: [PATCH] TikTok liefert die Kampagnenseite nicht mehr aus -- und der Kasten sagt es jetzt MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Filipe hat die Kachel benutzt und bekam "Ging nicht." Im Protokoll des Servers stand der Grund; im Kasten stand er nicht. Beides ist jetzt behoben -- und beim Nachmessen kam ein groesserer Befund heraus. DER BEFUND: DIE SEITE KOMMT LEER AN Gemessen am 07.10.2026, von zwei Leitungen und auf fuenf Wegen: ehrliche Kennung, accept text/html 17 KB, keine Daten ehrliche Kennung + Browser-Accept 17 KB, keine Daten Browser-Kennung 17 KB, keine Daten Browser-Kennung + Accept + Sprache 17 KB, keine Daten ganz ohne eigene Koepfe 17 KB, keine Daten zweiter Abruf MIT Keksen 17 KB, keine Daten echtes Chromium (headless) 17 KB, LEERE Seite Titel "Campaign", leerer Koerper, zwei Skripte (tiktok-environment, gfdatav1). Dasselbe fuer Filipes neue Kampagne UND fuer "Gipfelstuermer", von meinem Rechner wie vom Server. Damit traegt eine Annahme des Bauplans nicht: "HTML 1.100.298 Zeichen, serverseitig gerendert". Die 1,1-MB-Seiten, an denen der Bauplan entwickelt wurde, stammen aus einer echten Browsersitzung. Auf einen gewoehnlichen Abruf baut TikTok die Seite nicht mehr auf. Ob das voruebergehend ist (Modern.js kann SSR abstufen -- die gespeicherten Seiten tragen "renderLevel":2) oder bleibt, laesst sich an einem Abend nicht sagen. Der Weg bleibt deshalb eingebaut: Kommt die Seite wieder mit Daten, laeuft alles sofort. WAS DER MENSCH DAVOR JETZT SIEHT Der Leser unterscheidet HUELLE von UMBAU. Das sind zwei sehr verschiedene Lagen: Bei einem Umbau ist etwas zu reparieren, bei einer Huelle kann niemand etwas machen -- und soll das hoeren statt zu suchen. Die Meldung sagt die Groesse, sagt "das liegt nicht an dir und nicht am Link" und nennt den naechsten Schritt. "umgebaut" steht dort ausdruecklich NICHT mehr: Dieser Satz haette ihn auf die falsche Suche geschickt. UND NIE WIEDER "Ging nicht." Der Verlust der Meldung liess sich NICHT nachstellen -- 403 und 502 kommen beide woertlich im Kasten an, jetzt in bild-kampagne gemessen (25 Messungen). Ein Ersatztext, der nichts sagt, ist aber auch ohne bekannte Ursache falsch: Er laesst raten. Er nennt jetzt die Nummer der Antwort und sagt, wo mehr steht. GEPRUEFT pruef-kampagne-lesen 108 -> 112 (Huelle und Umbau getrennt, mit Gegenprobe: eine fremde Seite gilt NICHT als Huelle) · pruef-kampagne 99 -> 102 (fuenf verschiedene Saetze fuer fuenf Faelle) · bild-kampagne 20 -> 25 (zeigt der Kasten den Grund?) · pruef-struktur, pruef-eventkarte 94, pruef-agentur 62, pruef-css-klassen 39, pruef-zeichen 8, pruef-deutsche-texte 12 Co-Authored-By: Claude Opus 5 --- server/bild-kampagne.mjs | 83 +++++++++++++++++++++++++++++++++ server/kampagne-lesen.mjs | 27 ++++++++++- server/pruef-kampagne-lesen.mjs | 34 ++++++++++++++ server/pruef-kampagne.mjs | 31 +++++++++++- server/workspace-kampagne.js | 10 +++- workspace/anleitung.html | 28 +++++------ workspace/anruf-probe.html | 10 ++-- workspace/app.webmanifest | 8 ++-- workspace/assets/js/bereich.js | 16 ++++++- workspace/aufgaben.html | 38 +++++++-------- workspace/automation.html | 32 ++++++------- workspace/befinden.html | 34 +++++++------- workspace/bereich.html | 48 +++++++++---------- workspace/bewerben.html | 38 +++++++-------- workspace/bewerbungen.html | 36 +++++++------- workspace/buehne.html | 6 +-- workspace/calls.html | 34 +++++++------- workspace/chat.html | 42 ++++++++--------- workspace/content.html | 34 +++++++------- workspace/crew-index.html | 12 ++--- workspace/crew.webmanifest | 8 ++-- workspace/dateien.html | 34 +++++++------- workspace/entwicklung.html | 36 +++++++------- workspace/hilfe.html | 30 ++++++------ workspace/index.html | 10 ++-- workspace/kalender.html | 34 +++++++------- workspace/leistung.html | 32 ++++++------- workspace/manager-ziele.html | 28 +++++------ workspace/material.html | 32 ++++++------- workspace/notizen.html | 28 +++++------ workspace/personen.html | 32 ++++++------- workspace/profil.html | 36 +++++++------- workspace/reaktion.html | 38 +++++++-------- workspace/rechte.html | 34 +++++++------- workspace/report.html | 36 +++++++------- workspace/scouting.html | 32 ++++++------- workspace/start.html | 38 +++++++-------- workspace/startcheck.html | 34 +++++++------- workspace/steckbrief.html | 36 +++++++------- workspace/support.html | 28 +++++------ workspace/tafel.html | 6 +-- workspace/talente.html | 36 +++++++------- workspace/team.html | 32 ++++++------- workspace/teamlage.html | 34 +++++++------- workspace/teilen.html | 30 ++++++------ workspace/treff-moderation.html | 32 ++++++------- workspace/treff-regeln.html | 32 ++++++------- workspace/uebersicht.html | 32 ++++++------- workspace/unsere-seiten.html | 30 ++++++------ workspace/unterstuetzen.html | 32 ++++++------- workspace/werdegang.html | 32 ++++++------- workspace/willkommen.html | 2 +- workspace/wissen.html | 34 +++++++------- 53 files changed, 884 insertions(+), 697 deletions(-) diff --git a/server/bild-kampagne.mjs b/server/bild-kampagne.mjs index 277b9c3e..b9329767 100644 --- a/server/bild-kampagne.mjs +++ b/server/bild-kampagne.mjs @@ -254,6 +254,89 @@ await messen(412, 915, "Auf dem Handy, haeufigste Android-Breite"); `keine Bewegung bei prefers-reduced-motion (${ruht.animation}, ${ruht.dauer})`); } +/* ---- SAGT DER KASTEN, WARUM ES NICHT GING? ------------------------- + + Am 07.10.2026 stand bei Filipe im Kasten "Ging nicht." -- der + Ersatztext. Im Protokoll des Servers stand dagegen der genaue + Grund. Zwischen beiden liegt die ganze Frage, ob jemand weiss, was + zu tun ist. + + GEMESSEN WIRD HIER DER ECHTE WEG: Auf einer Pruefadresse hat die + Sitzung kein Haus, die Route lehnt deshalb mit 403 und einem + ganzen deutschen Satz ab. Genau dieser Satz muss im Kasten + stehen -- nicht "Ging nicht.". + + Das ist kein Umweg: Jede Fehlermeldung dieser Route geht + denselben Weg (res.status(...).json({ fehler })). Wer hier + ankommt, kommt ueberall an. */ +{ + const ctx = await browser.newContext({ viewport: { width: 1280, height: 900 } }); + const seite = await ctx.newPage(); + await anmelden(seite, "admin", "CODE-DOGI-0001"); + await seite.goto(BASIS + "/workspace/bereich.html?b=agentur", { waitUntil: "networkidle" }); + await seite.waitForTimeout(1400); + + await seite.fill("#kampagne-text", + "Dein Creator Network lädt dich ein: https://www.tiktok.com/t/ZSbqPsFa8/"); + await seite.$eval("#kampagne-holen", (b) => b.click()); + /* Auf eine ANTWORT warten, nicht auf eine feste Zeit: Ein fester + Wert waere hier dieselbe Rechnung von gestern wie ueberall. */ + await seite.waitForFunction( + () => document.getElementById("kampagne-sagt").textContent.trim() !== "Wird geholt …", + { timeout: 25000 }).catch(() => {}); + + const gezeigt = await seite.evaluate(() => ({ + text: document.getElementById("kampagne-sagt").textContent.trim(), + knopfFrei: !document.getElementById("kampagne-holen").disabled, + })); + console.log("\n=== Was der Kasten nach einem Fehlschlag sagt ==="); + console.log(" gezeigt: " + JSON.stringify(gezeigt.text)); + + ok(gezeigt.text !== "Ging nicht.", + "der Kasten zeigt NICHT den Ersatztext"); + ok(gezeigt.text.length > 25 && /[.!]$/.test(gezeigt.text), + `sondern einen ganzen Satz (${gezeigt.text.length} Zeichen)`); + ok(/Agentur|workspace\.dogfather/i.test(gezeigt.text), + "der den Grund nennt und sagt, was zu tun ist"); + ok(gezeigt.knopfFrei, "und der Knopf ist wieder frei"); + + /* UND DER FALL, DER FILIPE GETROFFEN HAT: HTTP 502. + + Der 403 oben beweist, dass der Weg grundsaetzlich traegt. Die + Meldung, die bei ihm verschwand, war aber ein 502 -- und ein + 502 kommt in diesem Haus durch einen anderen Flaschenhals + (Vorbau, Dienst). Deshalb wird er hier EINZELN nachgestellt: + window.holen gibt eine fertige 502-Antwort zurueck, genau so, + wie der Server sie schickt. Zeigt der Kasten dann den Satz, + liegt es nicht am Browsercode. + + Nachgestellt wird die ANTWORT, nicht der Fehler -- damit misst + diese Zeile genau eine Sache und nicht nebenbei TikTok. */ + await seite.evaluate(() => { + const echt = window.holen; + window.holen = async (pfad, o) => { + if (!String(pfad).includes("/kampagne/einlesen")) return echt(pfad, o); + return new Response(JSON.stringify({ + fehler: "Die Seite konnte nicht gelesen werden: " + + "__MODERN_ROUTER_DATA__ fehlt in der Seite. " + + "Vermutlich hat TikTok die Seite umgebaut.", + }), { status: 502, headers: { "Content-Type": "application/json" } }); + }; + }); + await seite.fill("#kampagne-text", "https://www.tiktok.com/t/ZSbqPsFa8/"); + await seite.$eval("#kampagne-holen", (b) => b.click()); + await seite.waitForFunction( + () => document.getElementById("kampagne-sagt").textContent.trim() !== "Wird geholt …", + { timeout: 20000 }).catch(() => {}); + const beiFuenfNullZwei = (await seite.evaluate( + () => document.getElementById("kampagne-sagt").textContent.trim())); + console.log(" bei HTTP 502: " + JSON.stringify(beiFuenfNullZwei)); + ok(beiFuenfNullZwei !== "Ging nicht." + && /MODERN_ROUTER_DATA/.test(beiFuenfNullZwei), + "auch ein 502 zeigt den Satz des Servers, nicht den Ersatztext"); + await ctx.close(); +} + /* ---- Und ein Creator sieht den Kasten gar nicht --------------------- */ { const ctx = await browser.newContext({ viewport: { width: 1280, height: 900 } }); diff --git a/server/kampagne-lesen.mjs b/server/kampagne-lesen.mjs index 4e1b2db8..dd1509df 100644 --- a/server/kampagne-lesen.mjs +++ b/server/kampagne-lesen.mjs @@ -166,8 +166,33 @@ export function datenAusSeite(html) { const text = String(html || ""); const treffer = DATENBLOCK.exec(text); if (!treffer) { + /* ZWEI SEHR VERSCHIEDENE FAELLE, EINE FEHLENDE MARKE + (nachgetragen 07.10.2026, nachdem genau das passiert ist). + + (a) TikTok hat die Seite umgebaut -- dann kommt eine volle + Seite an, nur anders gebaut. Das muss jemand ansehen. + (b) TikTok schickt nur eine HUELLE, die sich erst im Browser + aufbaut. Gemessen am 07.10.2026: 17 KB statt rund 1 MB, + Titel "Campaign", leerer Koerper, zwei Skripte + (tiktok-environment, gfdatav1). Von jedem Abrufweg aus + dasselbe -- mit ehrlicher Kennung, mit Browser-Kennung, + mit Keksen, sogar aus einem echten Chromium. + + Fuer den Menschen davor ist der Unterschied alles: Bei (a) + ist etwas zu reparieren, bei (b) kann er gar nichts machen + und soll das auch hoeren. Eine Meldung, die beides + zusammenwirft, schickt ihn auf die falsche Suche. */ + const huelle = text.length < 200_000 + && (text.includes("gfdatav1") || text.includes("tiktok-environment")); throw new KampagneUnlesbar( - "__MODERN_ROUTER_DATA__ fehlt in der Seite", "aufbau"); + huelle + ? `TikTok hat nur eine leere Hülle geschickt ` + + `(${text.length < 1024 ? text.length + " Bytes" + : Math.round(text.length / 1024) + " KB"} statt rund 1 MB). ` + + `Die Seite baut sich erst im Browser auf -- daran kann ` + + `von hier aus niemand etwas ändern` + : "__MODERN_ROUTER_DATA__ fehlt in der Seite", + "aufbau"); } const anfang = treffer.index + treffer[0].length; const ende = text.indexOf("", anfang); diff --git a/server/pruef-kampagne-lesen.mjs b/server/pruef-kampagne-lesen.mjs index 45eab624..165dfe99 100644 --- a/server/pruef-kampagne-lesen.mjs +++ b/server/pruef-kampagne-lesen.mjs @@ -542,6 +542,40 @@ function faengt(tun) { ok(faengt(() => leseKampagne(seite.gipfel)) === null, "Gegenprobe: die vollständige Seite wirft nicht"); + /* DIE LEERE HUELLE IST ETWAS ANDERES ALS EIN UMBAU + (nachgetragen 07.10.2026, nachdem genau das passiert ist). + + Gemessen an diesem Tag: TikTok schickt auf einen gewoehnlichen + Abruf nur noch 17 KB -- Titel "Campaign", leerer Koerper, zwei + Skripte. Von jedem Weg aus dasselbe: ehrliche Kennung, + Browser-Kennung, mit Keksen, sogar aus einem echten Chromium. + Die Seite baut sich erst im Browser auf. + + Fuer den Menschen davor ist der Unterschied alles: Bei einem + Umbau ist etwas zu reparieren, bei einer Huelle kann er nichts + machen -- und soll das hoeren statt zu suchen. */ + { + const huelle = [ + "", + "Campaign", + '', + '', + "
", + ].join(""); + const fh = faengt(() => leseKampagne(huelle)); + ok(fh?.art === "aufbau", `eine Hülle wirft ebenfalls (${fh?.art})`); + ok(/Hülle/.test(fh?.grund || "") && /(KB|Bytes)/.test(fh?.grund || ""), + `und nennt sie beim Namen: „${fh?.grund}“`); + ok(!/__MODERN_ROUTER_DATA__/.test(fh?.grund || ""), + " ohne mit einer Marke um sich zu werfen, die hier niemandem hilft"); + + /* GEGENPROBE: Eine Seite OHNE TikToks Kennzeichen bleibt beim + alten Satz -- sonst hiesse jeder Unsinn "Hülle". */ + const fremd = faengt(() => leseKampagne("nichts")); + ok(/__MODERN_ROUTER_DATA__/.test(fremd?.grund || ""), + "Gegenprobe: irgendeine fremde Seite gilt nicht als Hülle"); + } + /* Die kaputte Seite enthält bewusst ein ANDERES JSON-Skript und eine Zeile mit der Kampagnennummer. Wer „das erste Skript“ nimmt oder „irgendwo die Nummer“, wird hier grün und im Betrieb falsch. */ diff --git a/server/pruef-kampagne.mjs b/server/pruef-kampagne.mjs index 0386a2c2..a32c7434 100644 --- a/server/pruef-kampagne.mjs +++ b/server/pruef-kampagne.mjs @@ -158,6 +158,18 @@ const SEITEN = { const ROH_GIPFEL = seiteLesen("kampagne-gipfelstuermer.html.br"); const SEITE_KAPUTT = seiteLesen("kampagne-kaputt.html"); +/* DIE LEERE HUELLE, wie TikTok sie am 07.10.2026 wirklich schickte: + 17 KB, Titel "Campaign", leerer Koerper, die Skripte + tiktok-environment und gfdatav1. Hier nur so gross wie noetig -- + gemessen wird die Unterscheidung, nicht die Dateigroesse. */ +const HUELLE = [ + "", + "Campaign", + '', + '', + "
", +].join(""); + /* Eine Kampagne, deren Banner eine TEXTDATEI ist. Für die Gegenprobe zu `bildArtVon`: Was von draußen kommt, ist von draußen -- und der fremde Server behauptet den Typ. */ @@ -199,6 +211,7 @@ const tikDienst = createServer((anfrage, antwort) => { if (u.pathname === "/t/fremd") return leite("https://beispiel.de/ganz-woanders"); if (u.pathname === "/t/kreis") return leite("/t/kreis"); if (u.pathname === "/t/kaputt") return leite("/falcon/kaputt"); + if (u.pathname === "/t/huelle") return leite("/falcon/huelle"); if (u.pathname === "/t/ohneende") return leite("/falcon/ohneende"); if (u.pathname === "/t/stumm") return leite("/falcon/stumm"); if (u.pathname === "/t/riesig") return leite("/falcon/riesig"); @@ -237,6 +250,12 @@ const tikDienst = createServer((anfrage, antwort) => { antwort.writeHead(200, { "content-type": "text/html" }); return antwort.end(SEITE_KAPUTT); } + if (u.pathname === "/falcon/huelle") { + /* Genau das, was TikTok am 07.10.2026 wirklich schickte: + eine kleine Seite mit Titel und zwei Skripten, ohne Inhalt. */ + antwort.writeHead(200, { "content-type": "text/html; charset=utf-8" }); + return antwort.end(HUELLE); + } if (u.pathname === "/falcon/ohneende") { antwort.writeHead(200, { "content-type": "text/html" }); return antwort.end(SEITE_OHNE_ENDE); @@ -675,6 +694,14 @@ melde("=== 9. Was schiefgehen kann, wird verständlich gesagt ==="); ok(fremd.code === 400 && /beispiel\.de/.test(fremd.j.fehler || ""), `fremdes Ziel: HTTP ${fremd.code} — „${fremd.j.fehler}“`); + const huelle = await einlesen(`Klick: ${TIK}/t/huelle`); + ok(huelle.code === 502 && /Hülle/.test(huelle.j.fehler || ""), + `leere Hülle: HTTP ${huelle.code} — „${(huelle.j.fehler || "").slice(0, 95)}…“`); + ok(/nicht an dir/.test(huelle.j.fehler || ""), + " und sagt ausdrücklich, dass es nicht am Menschen davor liegt"); + ok(!/umgebaut/.test(huelle.j.fehler || ""), + " ohne ihn auf die falsche Suche zu schicken"); + const kaputt = await einlesen(`Klick: ${TIK}/t/kaputt`); ok(kaputt.code === 502 && /__MODERN_ROUTER_DATA__/.test(kaputt.j.fehler || ""), `umgebaute Seite: HTTP ${kaputt.code} — „${(kaputt.j.fehler || "").slice(0, 90)}…“`); @@ -687,8 +714,8 @@ melde("=== 9. Was schiefgehen kann, wird verständlich gesagt ==="); nur „es kam ein Fehler“ sagt, wäre auch grün, wenn überall dasselbe stünde. */ const saetze = new Set([ohneLink.j.fehler, fremd.j.fehler, - kaputt.j.fehler, ohneEnde.j.fehler]); - ok(saetze.size === 4, `${saetze.size} verschiedene Sätze für vier Fälle`); + kaputt.j.fehler, ohneEnde.j.fehler, huelle.j.fehler]); + ok(saetze.size === 5, `${saetze.size} verschiedene Sätze für fünf Fälle`); ok([...saetze].every((s) => s && !/\b50\d\b/.test(s) && s.length > 25), "keiner davon sagt „Fehler 502“, alle sind ganze Sätze"); diff --git a/server/workspace-kampagne.js b/server/workspace-kampagne.js index 1e280d38..c6e84288 100644 --- a/server/workspace-kampagne.js +++ b/server/workspace-kampagne.js @@ -404,10 +404,16 @@ kampagneRouter.post("/workspace/api/kampagne/einlesen", async (req, res) => { beide mit 502 und beide mit dem GRUND im Klartext. Eine Kachel ohne Zeitraum wäre eine Falschauskunft; sie behauptet einen Termin, den sie nicht kennt. */ + /* DREI SAETZE STATT ZWEI (07.10.2026). Die leere Huelle + ist kein Umbau und kein Fehler des Menschen davor -- und + "vermutlich hat TikTok umgebaut" schickt ihn suchen. */ const satz = fehler.art === "pflicht" ? `Die Seite wurde gelesen, aber ${fehler.grund}. Bitte von Hand anlegen.` - : `Die Seite konnte nicht gelesen werden: ${fehler.grund}. ` - + "Vermutlich hat TikTok die Seite umgebaut."; + : (/Hülle/.test(fehler.grund) + ? `${fehler.grund}. Das liegt nicht an dir und nicht am Link. ` + + "Bitte die Kachel vorerst von Hand anlegen." + : `Die Seite konnte nicht gelesen werden: ${fehler.grund}. ` + + "Vermutlich hat TikTok die Seite umgebaut."); return schiefgegangen(502, satz, `${fehler.art}: ${fehler.grund}`); } throw fehler; diff --git a/workspace/anleitung.html b/workspace/anleitung.html index 58105806..7ce9aacb 100644 --- a/workspace/anleitung.html +++ b/workspace/anleitung.html @@ -7,17 +7,17 @@ - - - - - + + + + + - - + + @@ -289,16 +289,16 @@ Und anleitung.js braucht es doppelt: Symbol, Farbe, Name und Adresse jeder Karte kommen von dort, nicht vom Server. --> - + - - - - - - + + + + + + diff --git a/workspace/anruf-probe.html b/workspace/anruf-probe.html index e03810a6..528f490b 100644 --- a/workspace/anruf-probe.html +++ b/workspace/anruf-probe.html @@ -16,8 +16,8 @@ verdeckt. Gefunden hat es pruef-struktur. Richtig sind `gate.css` (die Farbwerte des Hauses) und `haus.css`. --> - - + + - +