Link einfuegen, Video steht in der App -- Stufe A des Video-Plans
Filipe: "damit meine videos auch da auf der app rein kommen ... sobald
ich ein video poste erscheint es sofort in der app fuer die ganze
community ... dass man das Coverbild und den Text sieht ... fuer meinen
Account, dann DogFather Clips und HasiDog."
Der Weg, der HEUTE funktioniert -- ohne Freigabe, ohne Geheimnis, ohne
Wartezeit: Adresse einfuegen, alles andere geht von allein.
Link -> oEmbed (Titel, Cover-Adresse, Account)
-> Cover HERUNTERLADEN und bei uns ablegen
-> Eintrag mit Bild, Text und Knopf zum Video
DAS COVER WIRD KOPIERT, NICHT VERLINKT -- der wichtigste Satz.
TikToks Bildadresse ist signiert und laeuft ab. Heute an einem echten
Video nachgemessen: x-expires = 1789812000, also in 48 Stunden. Wer sie
nur speichert, hat uebermorgen schwarze Kacheln. Beim Bauen merkt man
das nicht und beim Testen auch nicht -- es faellt erst am uebernaechsten
Tag auf, und dann sieht die ganze App kaputt aus.
Deshalb wandert das Bild in dieselbe Ablage wie jeder Anhang und geht
durch dieselbe Bytepruefung. Ein heruntergeladenes Cover ist eine
FREMDE Datei; dass der Typ hier von einem fremden Server behauptet wird
statt vom eigenen Browser, macht ihn nicht glaubwuerdiger.
NUR SEINE EIGENEN KANAELE. Der Account aus der Antwort wird gegen
KANAELE geprueft (kanalVonHandle). Genommen wird author_url (.../@name),
nicht author_name -- der Anzeigename aendert sich, wenn jemand ihn
umstellt. Ein fremdes Video in Filipes Highlights waere nicht bloss
falsch einsortiert, es waere fremder Inhalt unter seinem Namen: 403.
DIE PRUEFUNG SCHALTET TIKTOK AB (server/pruef-video.mjs, 37 Pruefungen)
Ein nachgebauter Dienst auf 127.0.0.1 antwortet wie TikTok, mitsamt
Ablaufstempel. Im letzten Abschnitt wird er ABGESCHALTET. Danach muss
die alte Cover-Adresse ins Leere laufen und unsere weiterhin ein Bild
liefern -- beides wird gemessen, nebeneinander. Waere das Bild nur
verlinkt, waeren beide tot, genau wie uebermorgen im Betrieb.
Die Adresse des Dienstes kommt aus TIKTOK_OEMBED_BASIS. Steht sie nicht
da, gilt TikTok -- kein Verhalten, das sich still aendert. Eine
Pruefung, die das echte TikTok braucht, misst fremde Verfuegbarkeit
statt unseren Code und wird irgendwann rot, ohne dass etwas kaputt ist.
VIER GEGENPROBEN, weil 37 von 37 im ersten Lauf kein Beweis ist:
a) Cover nicht mehr kopieren -> 9 Pruefungen rot
b) author_name statt author_url -> 22 rot
c) Bytepruefung entfernen -> 2 rot (genau Abschnitt 5)
d) Dublettenpruefung entfernen -> 4 rot (genau Abschnitt 4)
Jede schlaegt dort an, wo sie soll. Und eine fuenfte Gegenprobe ist ins
Leere gelaufen: Der Patch lief ueber python3, das es hier nicht gibt --
die Datei blieb unveraendert und der Lauf war gruen. Haette ich die
Ausgabe nicht gelesen, haette ich behauptet, die Pruefung koenne rot
werden, ohne sie je rot gesehen zu haben. Der dritte Ausgang, an mir
selbst.
DAZU:
- eintraege.quelle_url / quelle_kanal / quelle_geholt_am (Umstellung)
- KANAELE bekommt handle, dazu kanalVonHandle()
- videoRouter steht VOR bereicheRouter, sonst schluckt /:bereich ihn
- Eingabefeld und Kanal-Knopf in bereich.html/.js, Farben wie bei den
Aufgaben-Kanaelen
- Stempel 202609171249
Nicht darin: die Kanal-Filterzeile in der Galerie, die Handy-Verknuepfung
(Teilen -> Workspace) und der Zustand "nicht mehr da" fuer geloeschte
Videos. Stufe B (Display API) braucht Filipes Entwickler-Freigabe.
Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
@@ -56,6 +56,12 @@
|
||||
/* Wie ein Brett in einem Satz heisst. Kurz, weil es mitten in einer
|
||||
Zeile steht: „Aus Wunschliste: …" liest sich, „Aus dem Bereich
|
||||
Wunschliste (wunsch): …" nicht. */
|
||||
/* Wie ein Kanal auf der Karte heisst. Kurz -- er steht neben einem
|
||||
Knopf, nicht in einer Ueberschrift. */
|
||||
const KANAL_WORT = {
|
||||
dogfather: 'DogFather', clips: 'DogFather Clips', hasidog: 'HasiDog',
|
||||
};
|
||||
|
||||
const BRETT_WORT = {
|
||||
anschlag: 'Anschlagbrett', ansteht: 'Was ansteht', treff: 'Treff',
|
||||
wunsch: 'Wunschliste', highlight: 'Highlights', regeln: 'Regeln',
|
||||
@@ -388,6 +394,29 @@
|
||||
|
||||
k.append(kopf);
|
||||
|
||||
/* ---- DAS VIDEO (17.09.2026), Stufe A des Video-Plans ----------
|
||||
Der Knopf ist die Antwort auf "und dann liken": Er fuehrt
|
||||
dorthin, wo das Liken zaehlt. Ein Herz bei uns, das bei TikTok
|
||||
nichts bewirkt, waere eine Attrappe.
|
||||
|
||||
`rel="noopener"` ist Pflicht: Ohne das kann die geoeffnete Seite
|
||||
ueber `window.opener` auf unsere zugreifen. */
|
||||
if (e.quelle_url) {
|
||||
const z = el('div', 'videozeile');
|
||||
if (e.quelle_kanal) {
|
||||
const k = el('span', 'videozeile__kanal',
|
||||
KANAL_WORT[e.quelle_kanal] || e.quelle_kanal);
|
||||
k.dataset.kanal = e.quelle_kanal;
|
||||
z.append(k);
|
||||
}
|
||||
const a = el('a', 'schritt videozeile__weg', 'Auf TikTok ansehen →');
|
||||
a.href = e.quelle_url;
|
||||
a.target = '_blank';
|
||||
a.rel = 'noopener noreferrer';
|
||||
z.append(a);
|
||||
k.append(z);
|
||||
}
|
||||
|
||||
/* ---- DIE KETTE (17.09.2026), Stufe 8 des Plans -----------------
|
||||
Woher kam das hier, und was ist daraus geworden? Beides steht
|
||||
auf der Karte, nicht in einem Detail: Der Zusammenhang ist die
|
||||
@@ -914,6 +943,53 @@
|
||||
} catch { uebernahmen = {}; uebernahmeMoeglich = false; }
|
||||
}
|
||||
|
||||
/* ---- EIN VIDEO EINLESEN ---------------------------------------
|
||||
Link einfuegen, den Rest macht der Server. Nur bei den Highlights:
|
||||
Dort gehoeren Clips hin, und ein Feld an jedem anderen Brett waere
|
||||
eine Einladung, sie ueberall hinzuschreiben. */
|
||||
function videoFeldZeigen() {
|
||||
const ziel = $('videoholen');
|
||||
if (!ziel) return;
|
||||
ziel.hidden = !(bereich === 'highlight' && darfEintragen());
|
||||
}
|
||||
|
||||
function videoFeldBedienen() {
|
||||
const form = $('video-form');
|
||||
if (!form || form.dataset.bereit === 'ja') return;
|
||||
form.dataset.bereit = 'ja';
|
||||
form.addEventListener('submit', async (ev) => {
|
||||
ev.preventDefault();
|
||||
const feld = $('video-url');
|
||||
const knopf = $('video-holen');
|
||||
const url = feld.value.trim();
|
||||
if (!url) return;
|
||||
knopf.disabled = true;
|
||||
/* "Wird geholt" statt eines stillen Wartens: Der Server fragt
|
||||
TikTok, und das dauert einen Moment. Ein Knopf, der nichts tut
|
||||
und nichts sagt, wird ein zweites Mal gedrueckt. */
|
||||
const hinweis = $('video-hinweis');
|
||||
const alterText = hinweis?.textContent || '';
|
||||
if (hinweis) hinweis.textContent = 'Wird geholt …';
|
||||
try {
|
||||
const a = await hole(`/workspace/api/bereich/${bereich}/video`, {
|
||||
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ url }),
|
||||
});
|
||||
const r = await a.json().catch(() => ({}));
|
||||
if (!a.ok) {
|
||||
melde(r.fehler || 'Ging nicht.');
|
||||
if (hinweis) hinweis.textContent = alterText;
|
||||
knopf.disabled = false;
|
||||
return;
|
||||
}
|
||||
feld.value = '';
|
||||
if (hinweis) hinweis.textContent = r.schon
|
||||
? 'Das Video war schon da.' : alterText;
|
||||
await laden();
|
||||
} finally { knopf.disabled = false; }
|
||||
});
|
||||
}
|
||||
|
||||
/* ---- DER STARTKATALOG ------------------------------------------------
|
||||
Fertige Texte, die jemand aus dem Team uebernimmt. Nicht
|
||||
automatisch: Ein Text, den niemand gelesen hat, stuende sonst als
|
||||
@@ -1017,6 +1093,8 @@
|
||||
alles uebernommen ist -- und das haengt an dem, was gerade
|
||||
geladen wurde. */
|
||||
startkatalogZeigen();
|
||||
videoFeldZeigen();
|
||||
videoFeldBedienen();
|
||||
maxStimmen = (daten.eintraege || [])
|
||||
.reduce((n, e) => Math.max(n, Number(e.stimmen) || 0), 0);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user