Changeset 9f9b45f in Klonkt


Ignore:
Timestamp:
08/14/2026 01:36:52 AM (4 weeks ago)
Author:
Robin <roboburr@…>
Branches:
main
Children:
861c228
Parents:
f894be5
Message:

De omslag zat in image, en daar keek de ingest niet

Robin: nog steeds niet alle plaatjes. Gemeten op soundfabrics.nl: 35 posts
binnen (de paginatiefix werkt), maar 9 covers van de 36.

DE OORZAAK STAAT IN ONS EIGEN COMMENTAAR. buildNote onderdrukt de beeldbijlage
zodra een post een speler of embed heeft (noImages), zodat Mastodon zijn
spelerkaart toont in plaats van de cover, en zet de omslag dan in image. Bij
dat stuk staat letterlijk "a Klonkt receiver reads it (handleInbox o.image)".
Alleen: de ingest is ook een Klonkt receiver, en die keek uitsluitend naar
attachment. Op pagina 1 hadden 18 van de 20 berichten daardoor geen enkele
bijlage en toch een cover.

Een Audio-bijlage draagt bovendien zijn eigen hoes in icon, en ook die viel
buiten de boot.

coverKandidaten() haalt nu alle drie de plekken op: o.image, o.icon, en de icon
van elke bijlage. Zowel bij een vers bericht als bij het repareren van een
bericht dat er al staat.

WAT GEEN FOUT WAS: de 23 berichten die nog naar sound-fabrics.com wijzen. Dat
zijn geen plaatjes maar de gebakken tracklinks (/tiktik#track-... en ?fc=2),
die bewust naar de bronpost verwijzen. Die laat ik met rust; ze verdwijnen pas
als de oude site weggaat, en dan is een dode link eerlijker dan een link naar
een pagina die hier niet bestaat.

Changed files:
src/services/MigrationService.js

  • coverKandidaten(): image, icon, en de icon van een bijlage
  • beide takken gebruiken hem: verse posts en de reparatieronde

test/fep1580-migration.test.js

  • een speler-post zonder bijlage met omslag in image, plus een Audio-bijlage met hoes in icon

remarks: in de mediamap van soundfabrics.nl staan 308 bestanden onder migrated/
uit de rondes van voor de padbehoud-fix. Die zijn dubbel: dezelfde plaatjes
staan nu ook onder hun eigen pad. Opruimen kan, maar niet blind: eerst
nagaan welke rijen er nog naar wijzen. Suite 998 groen, tegenproef gedaan.

-robo
Co-Authored-By: Claude Opus 4.8 <noreply@…>

Files:
2 edited

Legend:

Unmodified
Added
Removed
  • src/services/MigrationService.js

    rf894be5 r9f9b45f  
    239239
    240240const AFBEELDING = /^image\//i;
     241
     242/**
     243 * Waar kan de omslag van een bericht zitten?
     244 *
     245 * Niet alleen in `attachment`. Klonkt onderdrukt de beeldbijlage met opzet
     246 * zodra een post een speler of embed heeft (zie noImages in buildNote), en zet
     247 * de cover dan in `image` zodat Mastodon zijn spelerkaart toont en een Klonkt
     248 * hem alsnog vindt. Dat is precies wat er bij Robin misging: 18 van de 20
     249 * berichten op pagina 1 hadden geen enkele bijlage, en toch een cover, en die
     250 * viel er stil tussenuit.
     251 *
     252 * En een Audio-bijlage draagt zijn eigen hoes in `icon`; die telt ook mee.
     253 */
     254function coverKandidaten(o) {
     255  const uit = [];
     256  const pak = (v) => {
     257    if (!v) return;
     258    const u = typeof v === 'string' ? v : (v.url && (typeof v.url === 'string' ? v.url : v.url.href)) || v.href;
     259    if (u && /^https?:\/\//i.test(String(u))) uit.push(String(u));
     260  };
     261  pak(o.image);
     262  pak(o.icon);
     263  for (const a of (Array.isArray(o.attachment) ? o.attachment : [])) pak(a && a.icon);
     264  return [...new Set(uit)];
     265}
    241266
    242267/**
     
    521546          }
    522547          if (!bestaand.cover_image_url && safeFetch && fs && path && mediaRoot) {
    523             // De cover alsnog: hij zit als bijlage op de Note.
    524             for (const a of (Array.isArray(o.attachment) ? o.attachment : []).slice(0, 20)) {
    525               const u = a && (typeof a === 'string' ? a : (a.url && (typeof a.url === 'string' ? a.url : a.url.href)));
    526               if (!u || !AFBEELDING.test(String((a && a.mediaType) || ''))) continue;
     548            // De omslag alsnog. Uit de beeldbijlage als die er is, anders uit
     549            // image/icon: bij een post met een speler staat hij daar.
     550            const uit = [
     551              ...(Array.isArray(o.attachment) ? o.attachment : [])
     552                .filter((a) => AFBEELDING.test(String((a && a.mediaType) || '')))
     553                .map((a) => (typeof a.url === 'string' ? a.url : (a.url && a.url.href)))
     554                .filter(Boolean),
     555              ...coverKandidaten(o),
     556            ];
     557            for (const u of uit) {
    527558              const doel = bronMediaPad(u, bronOrigin, { mediaRoot, path });
    528559              const g = await haalBijlage(String(u), { safeFetch, mediaRoot, fs, path, maxBytes, doel }).catch(() => null);
    529               if (g) {
    530                 db.prepare('UPDATE posts SET cover_image_url = ? WHERE id = ?').run(g.url, bestaand.id);
    531                 rapport.media++;
    532                 rapport.postsBijgewerkt++;
    533               }
     560              if (!g) continue;
     561              db.prepare('UPDATE posts SET cover_image_url = ? WHERE id = ?').run(g.url, bestaand.id);
     562              rapport.media++;
     563              rapport.postsBijgewerkt++;
    534564              break;
    535565            }
     
    569599          const r2 = await inhoudMediaBinnen(inhoud, bronOrigin, site, rapport, { safeFetch, mediaRoot, fs, path, maxBytes });
    570600          inhoud = r2.inhoud;
     601          // De omslag zit lang niet altijd in attachment (zie coverKandidaten).
     602          if (!binnen.some((b) => AFBEELDING.test(b.type || ''))) {
     603            for (const u of coverKandidaten(o)) {
     604              const doel = bronMediaPad(u, bronOrigin, { mediaRoot, path });
     605              const g = await haalBijlage(u, { safeFetch, mediaRoot, fs, path, maxBytes, doel }).catch(() => null);
     606              if (!g) { rapport.mediaMislukt++; rapport.waarschuwingen.push(`omslag niet opgehaald: ${u}`); continue; }
     607              binnen.unshift({ ...g, naam: null, type: g.mediaType });
     608              rapport.media++;
     609              try {
     610                db.prepare('INSERT INTO media (id, site_id, filename, mime_type, size, storage_path) VALUES (?, ?, ?, ?, ?, ?)')
     611                  .run(crypto.randomUUID(), site.id, g.filename, g.mediaType, g.size, g.storage_path);
     612              } catch { /* administratie */ }
     613              break;                       // een omslag is genoeg
     614            }
     615          }
    571616        }
    572617
  • test/fep1580-migration.test.js

    rf894be5 r9f9b45f  
    536536});
    537537
     538test('de omslag komt ook mee als hij in `image` zit in plaats van in attachment', async () => {
     539  // Robins 9 covers van de 36. Klonkt onderdrukt de beeldbijlage met opzet
     540  // zodra een post een speler of embed heeft (noImages in buildNote), en zet
     541  // de omslag dan in `image` zodat Mastodon zijn spelerkaart toont. 18 van de
     542  // 20 berichten op pagina 1 hadden daardoor geen enkele bijlage, en toch een
     543  // cover. Mijn ingest keek alleen naar attachment.
     544  const s = site({ aliases: [BRON] });
     545  const OMSLAG = 'https://oud.example/media/post-images/omslag.png';
     546  const HOES = 'https://oud.example/media/audio-covers/hoes.webp';
     547  const kaart = new Map([
     548    [BRON, { id: BRON, type: 'Person', movedTo: IK, outbox: `${BRON}/outbox` }],
     549    [`${BRON}/outbox`, { type: 'OrderedCollection', totalItems: 2, first: `${BRON}/outbox?page=1` }],
     550    [`${BRON}/outbox?page=1`, { type: 'OrderedCollectionPage', orderedItems: [
     551      // een speler-post: GEEN attachment, omslag in image
     552      note(`${BRON}/notes/speler`, { content: '<p>muziek</p>', image: { type: 'Image', url: OMSLAG } }),
     553      // een audio-bijlage draagt zijn eigen hoes in icon
     554      note(`${BRON}/notes/audio`, { content: '<p>track</p>', attachment: [
     555        { type: 'Audio', mediaType: 'audio/mpeg', url: 'https://oud.example/audio/stream/x.mp3',
     556          icon: { type: 'Image', mediaType: 'image/webp', url: HOES } },
     557      ] }),
     558    ] }],
     559  ]);
     560  const opgehaald = [];
     561  const r = await stil(() => Mig.ingestFromSource(s, { deps: {
     562    getJson: async (_s, url) => kaart.get(url) || null,
     563    noteId: (b, id) => `${b}/ap/notes/${id}`,
     564    noteVisibility: AP.noteVisibility,
     565    mediaRoot: '/nep/media', audioRoot: '/nep/audio',
     566    signHeaders: () => ({ Signature: 'nep' }),
     567    safeFetch: async (url) => { opgehaald.push(url); return { ok: true, arrayBuffer: async () => Buffer.from('x'), headers: { get: () => 'image/png' } }; },
     568    fs: { mkdirSync() {}, writeFileSync() {}, statSync() { throw new Error('ENOENT'); } },
     569    path,
     570  } }));
     571  assert.equal(r.posts, 2);
     572  assert.ok(opgehaald.includes(OMSLAG), 'de omslag uit `image` hoort opgehaald te worden');
     573  assert.ok(opgehaald.includes(HOES), 'en de hoes uit de icon van een Audio-bijlage ook');
     574  const covers = db.prepare('SELECT cover_image_url c FROM posts WHERE site_id = ?').all('s1').map((x) => x.c);
     575  assert.equal(covers.filter(Boolean).length, 2, 'beide berichten krijgen hun omslag');
     576  assert.ok(covers.includes('/media/post-images/omslag.png'), 'op het pad van de bron');
     577});
     578
    538579test('een bericht dat je zelf hebt verwijderd komt bij een tweede ronde terug', async () => {
    539580  // Robin: "ik kan handmatig deze keer de posts verwijderen en opnieuw ophalen."
Note: See TracChangeset for help on using the changeset viewer.