Kampagnen: auswaehlen, wo die Kachel hingehoert -- TikTok oder Agentur

Filipe, mit einem Bild des Einlesekastens: "das sind die von der
agentur selbst. die krieg ich so, perfektioniere das und auch das ich
auswaehlen kann wo es hingehoert, zu tiktok oder der agentur." Dazu
sein echter Link: https://vm.tiktok.com/ZSbACwAnK/

ZWEI FRAGEN, DIE BISHER EINE WAREN. "Woher kam der Link" und "wem
gehoert das Event" wurden beide aus `kampagne_id` beantwortet: Wer
ueber eine Einladung eingelesen wurde, stand links. Die Agentur
schickt ihre eigenen Events aber ebenfalls als TikTok-Link -- damit
war die Zuordnung zwangslaeufig falsch.

Neue Spalte `kampagne_seite` mit DREI Zustaenden, und NULL ist einer
davon: niemand hat gewaehlt -> wie bisher nach der Herkunft. Alle
bestehenden Kacheln behalten dadurch genau ihren Platz, ohne dass
etwas umgestellt werden musste. Gewaehlt wird im Einlesekasten
(Automatisch / TikTok / Agentur, "Automatisch" vorbelegt) und
nachtraeglich an der Kachel ("zur Agentur" / "zu TikTok"). Nur die
Leitung; bei einem Creator wird das Feld stillschweigend entfernt.

GEMESSEN AN SEINEM ECHTEN LINK, mit Gegenprobe:
  vm.tiktok.com/ZSbACwAnK/
    -> www.tiktok.com/tcn/activity?activity_id=7691301069758087175
    -> 233 KB reine Huelle, kein __MODERN_ROUTER_DATA__
  dieselbe Nummer auf dem Weg der oeffentlichen Kampagnenseite
    -> 42 KB, ebenfalls ohne Datenpaket
  eine echte Kampagne auf demselben Weg
    -> 967 KB MIT Datenpaket
Die Kampagne existiert also nicht als oeffentliche Seite: Das Creator
Network baut seine Seiten erst im Browser auf, angemeldet. Von aussen
kann das niemand lesen -- auch kein anderes Programm.

Daraus zwei Dinge:
  - `nummerAusAdresse` liest jetzt BEIDE Schreibweisen (`activityId`
    auf der Kampagnenseite, `activity_id` im Creator Network).
  - `istCreatorNetwork` erkennt solche Links VOR dem Abruf. Statt 502
    "TikTok hat nicht geantwortet" (eine Ursache, die es nicht gibt)
    kommt 422 mit dem, was wirklich los ist -- und mit dem Weg
    daneben: das Event von Hand anlegen, Link in die Beschreibung.

BERICHTIGT: Der PATCH-Weg stand als '/workspace/api/bereich/agentur/'
+ e.id im Browser und war fuer pruef-struktur nicht mehr zuzuordnen
-- jetzt `${bereich}` wie bei den Nachbarn, was nebenbei den zweiten
Ort mit "agentur" im Text wegnimmt. Und "Automatisch" hatte keinen
eigenen Ton und fiel auf `--akzent` zurueck: auf dem Agenturbrett
genau das Gelb der rechten Spalte, die vorbelegte Moeglichkeit sah
also aus wie "Agentur".

GEPRUEFT: 169 (Route, +22), 170 (Kachel, +12), 67 (Agentur), 67
Messungen (Bild), 107 (Haus-Trennung, unveraendert), dazu struktur,
css-klassen, zeichen, deutsche-texte, formulare, tippziele. Drei
Gegenproben: Auswahl verwerfen, Creator-Network durchlassen, Kachel
wieder nur nach Herkunft -- alle drei werden bemerkt, alle Dateien
byte-gleich wiederhergestellt.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-10-07 23:46:41 +02:00
co-authored by Claude Opus 5
parent cadab24a5f
commit 2764aea61c
54 changed files with 1333 additions and 702 deletions
+151
View File
@@ -149,11 +149,22 @@ const ID_GIPFEL = "7691493175936699143";
const ID_BEENDET = "7657456323453405959";
const ID_OHNEPREISE = "7670899782609996552";
const ID_TEXTBANNER = "7600000000000000001";
/* Die vierte gespeicherte Seite -- bis zum 07.10.2026 benutzte sie
nur pruef-kampagne-lesen.mjs. Hier wird sie gebraucht, weil die
Seitenwahl eine Kampagne braucht, die es noch NICHT gibt: Bei
einer schon eingelesenen kaeme „schon da", und dann entstuende
gar keine neue Kachel, an der sich etwas messen liesse. */
const ID_GLOWUP = "7690927903777262344";
/* Die Nummer aus Filipes echtem Agenturlink -- sie steht hier,
damit die Pruefung dieselbe Form misst, die wirklich ankommt
(snake_case `activity_id`, nicht `activityId`). */
const ID_TCN = "7691301069758087175";
const SEITEN = {
[ID_GIPFEL]: bannerHierher(seiteLesen("kampagne-gipfelstuermer.html.br")),
[ID_BEENDET]: bannerHierher(seiteLesen("kampagne-beendet.html.br")),
[ID_OHNEPREISE]: bannerHierher(seiteLesen("kampagne-ohne-preise.html.br")),
[ID_GLOWUP]: bannerHierher(seiteLesen("kampagne-aufgabengruppe.html.br")),
};
const ROH_GIPFEL = seiteLesen("kampagne-gipfelstuermer.html.br");
const SEITE_KAPUTT = seiteLesen("kampagne-kaputt.html");
@@ -207,7 +218,21 @@ const tikDienst = createServer((anfrage, antwort) => {
if (u.pathname === "/t/gipfel-zweiter-versand") return leite(`/falcon/home?activityId=${ID_GIPFEL}&campaign_traffic_id=2`);
if (u.pathname === "/t/beendet") return leite(`/falcon/home?activityId=${ID_BEENDET}`);
if (u.pathname === "/t/ohnepreise") return leite(`/falcon/home?activityId=${ID_OHNEPREISE}`);
if (u.pathname === "/t/glowup") return leite(`/falcon/home?activityId=${ID_GLOWUP}`);
if (u.pathname === "/t/textbanner") return leite(`/falcon/home?activityId=${ID_TEXTBANNER}`);
/* DAS CREATOR NETWORK (07.10.2026). Nachgemessen am echten
Agenturlink von Filipe: `vm.tiktok.com/ZSbACwAnK/` leitet auf
`www.tiktok.com/tcn/activity?activity_id=...` -- eine Seite,
die TikTok erst im Browser aufbaut, angemeldet. Von aussen
steht dort nichts.
Der Weg wird hier nachgebaut, damit die Pruefung nicht das
echte TikTok braucht: Sie misst dann fremde Verfuegbarkeit
statt unseren Code. Die ANTWORT auf diesen Weg ist
absichtlich eine leere Huelle -- genau wie draussen. */
if (u.pathname === "/t/agentur") {
return leite(`/tcn/activity?activity_id=${ID_TCN}&activity_type=ranklist`);
}
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");
@@ -819,6 +844,132 @@ melde("=== 9. Was schiefgehen kann, wird verständlich gesagt ===");
"und die Kachel selbst sagt, dass etwas nachzutragen ist");
}
/* =====================================================================
9b. DER AGENTURLINK — UND WOHIN DIE KACHEL GEHÖRT (07.10.2026)
Filipe: „das sind die von der agentur selbst. die krieg ich so,
perfektioniere das und auch das ich auswählen kann wo es
hingehört, zu tiktok oder der agentur.“
===================================================================== */
melde("");
melde("=== 9b. Agenturlink und Seitenwahl ===");
{
/* AUS DEM ECHTEN MODUL, nicht nachgebaut: Geprüft wird die
Funktion, die im Betrieb läuft. Eine Abschrift hier wäre eine
zweite Fassung derselben Regel -- und die läuft auseinander. */
const { nummerAusAdresse, istCreatorNetwork } = await import("./workspace-kampagne.js");
/* ---- Die Nummer in BEIDEN Schreibweisen ------------------- */
ok(nummerAusAdresse(`${TIK}/falcon/home?activityId=${ID_GIPFEL}`) === ID_GIPFEL,
"activityId wird gelesen (die Kampagnenseite)");
ok(nummerAusAdresse(`${TIK}/tcn/activity?activity_id=${ID_TCN}`) === ID_TCN,
"activity_id ebenso (das Creator Network)");
/* GEGENPROBE: Etwas, das keine Nummer ist, gibt auch keine --
sonst wäre „beide Schreibweisen“ auch bei einer Funktion
grün, die immer irgendetwas zurückgibt. */
ok(nummerAusAdresse(`${TIK}/tcn/activity?activity_id=hallo`) === null,
" und Buchstaben sind keine Nummer");
ok(nummerAusAdresse(`${TIK}/tcn/activity`) === null,
" und ohne Angabe kommt nichts");
/* ---- Die Erkennung selbst, mit beiden Richtungen ---------- */
ok(istCreatorNetwork(`${TIK}/tcn/activity?activity_id=${ID_TCN}`),
"eine /tcn/-Adresse wird als Creator Network erkannt");
/* GEGENPROBE: Die echte Kampagnenseite NICHT -- sonst wäre jede
Einladung ein Agenturlink und nichts ließe sich mehr einlesen. */
ok(!istCreatorNetwork(`${TIK}/falcon/campaign/fusion_cosmic_runtime/home?activityId=${ID_GIPFEL}`),
" die echte Kampagnenseite dagegen nicht");
ok(!istCreatorNetwork(`${TIK}/tcnx/activity`),
" und ein Pfad, der nur so anfängt, auch nicht");
/* ---- Der Agenturlink wird ERKANNT, nicht blind geholt ----- */
const vorher = abrufe;
const tcn = await einlesen(`Hier die Einladung: ${TIK}/t/agentur`);
ok(tcn.code === 422,
`ein Creator-Network-Link gibt 422, nicht 502 (HTTP ${tcn.code})`);
ok(/Creator Network/.test(tcn.j.fehler || ""),
`und sagt, was für eine Seite das ist: „${tcn.j.fehler}“`);
/* DIE MELDUNG SAGT, WAS ZU TUN IST. Eine Ursache ohne Ausweg
lässt den Menschen davor stehen. */
ok(/von Hand/.test(tcn.j.fehler || ""),
" und was man stattdessen tun kann");
/* UND DIE SEITE WIRD GAR NICHT ERST GEHOLT. 233 KB für eine
Hülle, von der wir schon wissen, dass nichts drinsteht --
und danach eine falsche Ursache melden. Gezählt werden die
Abrufe beim nachgebauten Dienst: die Weiterleitung ja, die
Seite selbst nicht. */
const geholt = abrufe - vorher;
ok(geholt <= 2,
`nur der Weg wurde verfolgt, die Hülle nicht geholt (${geholt} Abrufe)`);
/* ---- DIE SEITENWAHL BEIM EINLESEN ------------------------- */
const mitSeite = await roh("/workspace/api/kampagne/einlesen", {
methode: "POST", keks: keksDogi, wirt: HOST,
rumpf: { text: `Klick: ${TIK}/t/glowup`, seite: "agentur" },
});
const neuId = JSON.parse(mitSeite.text || "{}").id;
ok(mitSeite.code === 201 && Number.isInteger(neuId),
`eine Kampagne mit Seitenwahl entsteht (HTTP ${mitSeite.code}, #${neuId})`);
const gz = dbLesen("SELECT kampagne_id, kampagne_seite FROM eintraege WHERE id = ?", neuId)[0];
ok(gz?.kampagne_seite === "agentur",
`und steht auf der gewählten Seite: ${JSON.stringify(gz?.kampagne_seite)}`);
/* GEGENPROBE: Sie kommt trotzdem von TikTok -- die Herkunft wird
nicht verfälscht, nur die Zuordnung gewählt. Das ist der
ganze Unterschied zwischen beiden Angaben. */
ok(gz?.kampagne_id === ID_GLOWUP,
`die Herkunft bleibt unberührt (${gz?.kampagne_id})`);
/* OHNE WAHL BLEIBT ES NULL -- „niemand hat gewählt“ ist etwas
anderes als „links“. Die zuerst eingelesene Kachel hatte keine
Wahl dabei. */
const alt = dbLesen("SELECT kampagne_seite FROM eintraege WHERE id = ?", kachelId)[0];
ok(alt?.kampagne_seite === null,
`ohne Wahl bleibt die Seite offen (${JSON.stringify(alt?.kampagne_seite)})`);
/* Ein erfundener Wert wird NICHT stillschweigend übernommen. */
const quatsch = await roh(`/workspace/api/bereich/agentur/${kachelId}`, {
methode: "PATCH", keks: keksDogi, wirt: HOST,
rumpf: { kampagne_seite: "mond" },
});
ok(quatsch.code === 400,
`ein erfundener Wert wird abgelehnt (HTTP ${quatsch.code})`);
const unberuehrt = dbLesen("SELECT kampagne_seite FROM eintraege WHERE id = ?", kachelId)[0];
ok(unberuehrt?.kampagne_seite === null,
" und die Kachel bleibt, wie sie war");
/* ---- NACHTRÄGLICH VERSCHIEBEN ----------------------------- */
const schieben = await roh(`/workspace/api/bereich/agentur/${kachelId}`, {
methode: "PATCH", keks: keksDogi, wirt: HOST,
rumpf: { kampagne_seite: "agentur" },
});
ok(schieben.code === 200, `die Seite lässt sich ändern (HTTP ${schieben.code})`);
const nachher = dbLesen("SELECT kampagne_seite FROM eintraege WHERE id = ?", kachelId)[0];
ok(nachher?.kampagne_seite === "agentur",
`und steht jetzt rechts (${nachher?.kampagne_seite})`);
/* ZURÜCKNEHMEN GEHT AUCH. Eine Wahl ohne Weg zurück ist beim
ersten Versehen ein Dauerzustand. */
const zurueck = await roh(`/workspace/api/bereich/agentur/${kachelId}`, {
methode: "PATCH", keks: keksDogi, wirt: HOST,
rumpf: { kampagne_seite: "" },
});
const offen = dbLesen("SELECT kampagne_seite FROM eintraege WHERE id = ?", kachelId)[0];
ok(zurueck.code === 200 && offen?.kampagne_seite === null,
`und wieder auf automatisch (${JSON.stringify(offen?.kampagne_seite)})`);
/* ---- EIN CREATOR DARF DAS NICHT --------------------------- */
/* Wo eine Kachel steht, ist eine Entscheidung über das Brett,
nicht über den eigenen Eintrag. */
const alsCreator = await roh(`/workspace/api/bereich/agentur/${kachelId}`, {
methode: "PATCH", keks: keksLuna, wirt: HOST,
rumpf: { kampagne_seite: "tiktok" },
});
const danach = dbLesen("SELECT kampagne_seite FROM eintraege WHERE id = ?", kachelId)[0];
ok(danach?.kampagne_seite === null,
`ein Creator verschiebt nichts (HTTP ${alsCreator.code}, `
+ `Seite ${JSON.stringify(danach?.kampagne_seite)})`);
}
/* =====================================================================
10. EIN STUMMER SERVER HÄNGT DIE ROUTE NICHT AUF
===================================================================== */