Der Weg ueber die TikTok-Datei des Creators ist entfernt

Filipe, mit den drei Knoepfen im Bild: "ich will nur dass wir manager
scouts, dogfather oder spicy sachen eintragen koennen und nicht die
creator. ich will keine daten von denen kriegen, nur wir."

Gebaut am Vormittag, am Nachmittag wieder ausgebaut. Der Weg war
technisch in Ordnung -- und er war der einzige, bei dem ein CREATOR uns
etwas gibt: seine eigene Datenkopie aus der TikTok-App. Genau das soll
nicht sein. Es bleiben die beiden Wir-Wege: die eigene Datei und die
Backstage-Tabelle der Agentur.

VOLLSTAENDIG ENTFERNT, NICHT VERSTECKT:
- beide Schnittstellen (/tiktok-datei und /tiktok-datei/vorschau)
- der Leser workspace-tiktok-datei.js (263 Zeilen)
- Knopf, Fenster, Dateiauswahl, Vorschau, Stilregeln
- der fertige Text, der einen Creator um seine Daten bittet
- der Erklaersatz unter der Knopfreihe

Ein Weg, der nur unsichtbar ist, ist weiterhin ein Weg -- wer die
Adresse kennt, benutzt ihn. Und eine Bitte an einen Creator um seine
Daten soll in diesem Haus nirgends mehr stehen, auch nicht in einem
Fenster, das niemand oeffnet.

GEPRUEFT WIRD JETZT DIE ABWESENHEIT, an drei Stellen: Knopf weg,
Fenster weg, und die Schnittstelle antwortet DOGFATHER mit 404 -- dem
staerksten Zugang, den es gibt. Bekommt er 404, bekommt ihn jeder.
Daneben die Gegenprobe, dass der Backstage-Weg weiterhin mit 200
antwortet; sonst bewiese das 404 nur einen Tippfehler.

pruef-tiktok-datei.mjs (47 Pruefungen) faellt mit dem Weg weg. Die
Zahl sinkt dadurch, und das ist hier richtig: Sie pruefte etwas, das
es nicht mehr gibt. Was bleibt, sind sechs Pruefungen, die das
Fehlen sichern -- pruef-backstage-import 63 -> 66.

NEBENBEI GEDREHT, NICHT GELOESCHT: pruef-leistung-optik behauptete
noch die Regel vom 07.09. ("beim Manager fehlt die Kachel", "die Seite
weist den Creator ab"). Beide Aussagen sind jetzt umgekehrt und messen
zusaetzlich, was vorher niemand gemessen hat: dass die Creatorin auf
der Zahlen-Seite ihre EIGENEN Zahlen sieht und trotzdem keinen
einzigen Knopf zum Eintragen hat -- beides zusammen, denn "keine
Knoepfe" waere auch auf einer leeren Seite wahr. 56 -> 59.

Nicht von mir, nachgemessen gegen den Stand ohne diese Aenderungen:
pruef-struktur meldet dieselben 6 Fehler (crew-index/teamlage nicht
verlinkt, start.css 348 KB, totes CSS .nase, UTC-Datum).

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-09-11 15:02:21 +02:00
co-authored by Claude Opus 5
parent b34586be91
commit 14ed467928
31 changed files with 427 additions and 1262 deletions
+12 -135
View File
@@ -197,24 +197,24 @@
hier geht es darum, niemandem einen Knopf hinzustellen, der
eine Absage bringt. */
['netz-oeffnen', LEITUNG.has(ich?.rolle)],
/* Die Datei gehoert EINEM Creator -- deshalb sichtbar fuer alle,
die fuer ihn eintragen duerfen, also auch fuer seinen Scout. */
['tt-oeffnen', darfEintragen()],
];
for (const [knopf, darf] of KNOEPFE) $(knopf).hidden = !darf;
/* Jeder Erklaersatz haengt an SEINEM Knopf, nicht am Block. Ein
Scout sieht nur die TikTok-Datei -- ihm die Backstage-Tabelle zu
erklaeren, an die er gar nicht kommt, ist keine Hilfe, sondern
eine Absage, die er nicht gesucht hat. Und ein Creator sieht
keinen von beiden, also auch keinen Satz. Abgeleitet statt
zweimal aufgeschrieben: kommt ein dritter Weg dazu, stimmt es
weiter. */
for (const [satz, knopf] of [['quelle-netz', 'netz-oeffnen'],
['quelle-tt', 'tt-oeffnen']]) {
Scout, der die Backstage-Tabelle nicht hat, soll sie auch nicht
erklaert bekommen -- das waere keine Hilfe, sondern eine Absage,
die er nicht gesucht hat. Und ein Creator sieht keinen Knopf,
also auch keinen Satz.
Die Liste hat seit dem 11.09.2026 nur noch einen Eintrag (der
Weg ueber die TikTok-Datei des Creators ist entfernt). Sie
bleibt trotzdem eine Liste: Der naechste Weg haengt sich dann
daneben, statt dass jemand die Regel ein zweites Mal
aufschreibt. */
for (const [satz, knopf] of [['quelle-netz', 'netz-oeffnen']]) {
$(satz).hidden = $(knopf).hidden;
}
$('quellen-hinweis').hidden = $('quelle-netz').hidden && $('quelle-tt').hidden;
$('quellen-hinweis').hidden = $('quelle-netz').hidden;
if (!z || (!z.diamanten && !z.tage && !z.verweildauer)) {
ziel.append(el('p', 'leistung__leer',
@@ -582,109 +582,6 @@
$('tag-form').addEventListener('submit', tagSpeichern);
$('tag-zu').addEventListener('click', () => $('tag-dialog').close());
$('tag-loeschen').addEventListener('click', tagLoeschen);
/* =====================================================================
DIE TIKTOK-DATEI DES CREATORS
Das Format ist nicht dokumentiert. Deshalb wird hier nichts
geraten: Der Server durchsucht die Datei nach Listen mit Datum und
schickt sie zur Auswahl zurück -- mit Anzahl, Zeitraum und dem,
was dabei herauskäme. Bestätigt wird von Hand.
===================================================================== */
let ttInhalt = null; /* die gelesene Datei -- bleibt im Browser */
let ttWahl = null; /* der bestätigte Kandidat */
async function ttDateiGewaehlt(ereignis) {
const datei = ereignis.target.files?.[0];
$('tt-fehler').textContent = '';
$('tt-vorschau').hidden = true;
$('tt-los').disabled = true;
ttInhalt = null; ttWahl = null;
if (!datei) return;
/* Ein Vollarchiv kann zweistellige Megabyte haben. Das Lesen
passiert im Browser; auf den Server geht es erst danach -- und
nur einmal, nicht bei jedem Klick. */
$('tt-fehler').textContent = 'Datei wird gelesen …';
let text;
try { text = await datei.text(); } catch { $('tt-fehler').textContent = 'Die Datei ließ sich nicht lesen.'; return; }
try { ttInhalt = JSON.parse(text); } catch {
$('tt-fehler').textContent = 'Das ist keine JSON-Datei. Beim Herunterladen bei TikTok '
+ 'muss als Format JSON gewählt werden, nicht TXT.';
return;
}
$('tt-fehler').textContent = 'Datei wird durchsucht …';
const a = await hole(`/workspace/api/leistung/${offenerCreator}/tiktok-datei/vorschau`, {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ inhalt: ttInhalt }),
}).catch(() => null);
if (!a) { $('tt-fehler').textContent = 'Die Datei konnte nicht geprüft werden.'; return; }
const d = await a.json().catch(() => ({}));
if (!a.ok) { $('tt-fehler').textContent = d.fehler || 'Die Datei wurde nicht verstanden.'; return; }
$('tt-fehler').textContent = '';
const v = $('tt-vorschau');
v.textContent = ''; v.hidden = false;
const zeile = (text2, art) => {
const p = el('p', 'import-vorschau__zeile');
if (art) p.dataset.art = art;
p.textContent = text2;
v.append(p);
return p;
};
zeile(`${d.kandidaten.length} Liste(n) mit Datum gefunden. Welche ist der LIVE-Verlauf?`);
/* Die Auswahl als echte Radioknöpfe: Es ist eine Entscheidung, und
eine Entscheidung gehört nicht in ein Aufklappfeld, in dem man
die erste Zeile für gesetzt hält. */
d.kandidaten.forEach((k, i) => {
const box = el('label', 'tt-wahl');
const r = document.createElement('input');
r.type = 'radio'; r.name = 'tt-kandidat'; r.value = String(i);
if (i === 0) { r.checked = true; ttWahl = k; }
r.addEventListener('change', () => { ttWahl = k; $('tt-los').disabled = false; });
const t = el('span', 'tt-wahl__text');
t.append(el('strong', null, k.pfad || '(oberste Ebene)'));
t.append(el('span', 'tt-wahl__zahl',
` ${k.anzahl} Einträge · ${k.tage_gesamt} Tage`
+ (k.von ? ` · ${k.von} bis ${k.bis}` : '')));
const felder = ['tag', 'dauer', 'diamanten', 'zuschauer']
.filter((f) => k.vorschlag[f])
.map((f) => `${f}: ${k.vorschlag[f]}`).join(' · ');
t.append(el('span', 'tt-wahl__felder', felder || 'nur ein Datum erkannt'));
box.append(r, t);
v.append(box);
});
if (ttWahl) $('tt-los').disabled = false;
}
async function ttUebernehmen(ereignis) {
ereignis.preventDefault();
if (!ttInhalt || !ttWahl) return;
$('tt-los').disabled = true;
const a = await hole(`/workspace/api/leistung/${offenerCreator}/tiktok-datei`, {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ inhalt: ttInhalt, pfad: ttWahl.pfad, zuordnung: ttWahl.vorschlag }),
}).catch(() => null);
if (!a) { $('tt-los').disabled = false; return; }
const d = await a.json().catch(() => ({}));
if (!a.ok) {
$('tt-fehler').textContent = d.fehler === 'keine_brauchbaren_tage'
? 'In dieser Liste stehen keine Tage mit Zahlen.'
: (d.fehler || 'Übernehmen ging nicht.');
$('tt-los').disabled = false;
return;
}
/* Die Datei loslassen, sobald sie nicht mehr gebraucht wird. */
ttInhalt = null; ttWahl = null;
$('tt-dialog').close();
melde(`${d.geschrieben} Tage neu, ${d.ergaenzt} ergänzt`);
setTimeout(() => melde(''), 6000);
await laden();
}
/* =====================================================================
DIE BACKSTAGE-TABELLE — Vorschau und Übernahme
===================================================================== */
@@ -797,26 +694,6 @@
$('netz-text').focus();
});
$('netz-zu').addEventListener('click', () => $('netz-dialog').close());
$('tt-oeffnen').addEventListener('click', () => {
$('tt-vorschau').hidden = true; $('tt-los').disabled = true;
$('tt-fehler').textContent = ''; $('tt-datei').value = '';
$('tt-dialog').showModal();
});
$('tt-zu').addEventListener('click', () => $('tt-dialog').close());
$('tt-datei').addEventListener('change', ttDateiGewaehlt);
/* Den Text fuer den Creator in die Zwischenablage. Kein
Erfolgshaken ohne Beleg: Geht es schief (kein Recht, alter
Browser), steht das auch da -- sonst glaubt man, es sei kopiert,
und schickt eine leere Nachricht. */
$('tt-text-kopieren').addEventListener('click', async (e) => {
const knopf = e.currentTarget;
try {
await navigator.clipboard.writeText($('tt-text').textContent);
knopf.textContent = 'Kopiert';
} catch { knopf.textContent = 'Ging nicht - bitte von Hand markieren'; }
setTimeout(() => { knopf.textContent = 'Text kopieren'; }, 2600);
});
$('tt-form').addEventListener('submit', ttUebernehmen);
/* Beim EINFÜGEN sofort nachsehen -- nicht erst auf einen Knopf warten.
Wer eine Tabelle einfügt, will wissen, ob sie erkannt wurde; ein
zweiter Klick dazwischen ist nur eine Gelegenheit, es nicht zu tun. */