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`. --> - - + + - +