Ignore:
File:
1 edited

Legend:

Unmodified
Added
Removed
  • src/services/ActivityPubService.js

    r8bb3b09 rb4af6f3  
    496496}
    497497
     498// fedi_open tracks → real AS2 Audio attachments (the actual file URL, served ungated) so
     499// EVERY client incl. the Mastodon apps plays them inline natively. Gated tracks (default)
     500// stay link/card-only — the file is never exposed for them. Resolve from post.content so a
     501// later body mutation can't affect it.
     502//
     503// Staat apart en niet meer midden in buildNote, omdat een BETAALDE post hem ook
     504// nodig heeft: daar staat de muur om de TEKST en niet om de muziek.
     505function openAudioAttachments(base, site, post) {
     506  const openAudio = [];
     507  if (!/\[\[(track|album|playlist):/i.test(post.content || '')) return openAudio;
     508  const abs = (u) => !u ? null : (/^https?:/i.test(u) ? u : `${base}${u.startsWith('/') ? '' : '/'}${u}`);
     509  const seenA = new Set();
     510  const addRow = (r) => {
     511    const fn = r.filename || (r.storage_path || '').split('/').pop();
     512    if (!fn || seenA.has(fn)) return; seenA.add(fn);
     513    const a = { type: 'Audio', mediaType: r.mime_type || 'audio/mpeg', url: `${base}/audio/stream/${encodeURIComponent(fn)}`, name: r.title || 'Audio' };
     514    // Cover art on the Audio attachment (AS2 `icon`): track cover, else the post cover.
     515    // Mastodon renders it as the artwork thumbnail on its native audio player.
     516    const art = abs(r.cover_url || post.cover_image_url || null);
     517    if (art) a.icon = { type: 'Image', mediaType: guessMediaType(art), url: art };
     518    openAudio.push(a);
     519  };
     520  const SEL = 'SELECT t.title, t.cover_url, m.filename, m.storage_path, m.mime_type FROM audio_tracks t JOIN media m ON m.id = t.media_id WHERE t.fedi_open = 1 AND ';
     521  try {
     522    for (const mm of (post.content || '').matchAll(/\[\[track:([A-Za-z0-9_-]+)\]\]/g)) { const r = db.prepare(SEL + 't.id = ?').get(mm[1]); if (r) addRow(r); }
     523    for (const mm of (post.content || '').matchAll(/\[\[album:([^\]]+)\]\]/g)) for (const r of db.prepare(SEL + 't.site_id = ? AND t.album = ? ORDER BY t.rowid').all(site.id, mm[1].trim())) addRow(r);
     524    for (const mm of (post.content || '').matchAll(/\[\[playlist:([A-Za-z0-9_-]+)\]\]/g)) for (const r of db.prepare('SELECT t.title, t.cover_url, m.filename, m.storage_path, m.mime_type FROM playlist_tracks pt JOIN audio_tracks t ON t.id = pt.track_id JOIN media m ON m.id = t.media_id WHERE t.fedi_open = 1 AND pt.playlist_id = ? ORDER BY pt.position').all(mm[1])) addRow(r);
     525  } catch { /* non-fatal */ }
     526  return openAudio;
     527}
     528
     529// HET BANDJE OP DE DRAAD. Zonder dit stuk bestaat `Mixtape` alleen in onze
     530// eigen code: de playlist-collectie blijft namelijk een OrderedCollection
     531// (dat moet, anders verliest een lezer die `type` als tekst uitpakt het hele
     532// object), en dan zegt niets naar buiten toe ooit dat dit een cassette is.
     533// Gemeten op 21-8: in de Note van een mixtape-post kwam het woord Mixtape
     534// niet voor, en de hub gooide zo'n bandje daarom stil weg.
     535//
     536// Als bijlage en niet als het object zelf: de post blijft een Note, zodat
     537// Mastodon en alles wat `Mixtape` niet kent gewoon een bericht met audio
     538// ziet. Wie het type wel kent, vindt het bandje als geheel.
     539//
     540// Het bandje draagt alleen wat al open staat: playlistOpenTracks filtert op
     541// fedi_open. Daarom is het veilig om hem ook aan een betaalde teaser te hangen.
     542function mixtapeAttachment(base, site, post) {
     543  try {
     544    const soort = postMusicType(post.content || '', site.id);
     545    if (!soort || soort.type !== 'mixtape' || !soort.collectie || !soort.collectie.id) return null;
     546    const pl = db.prepare('SELECT * FROM playlists WHERE id = ? AND site_id = ?')
     547      .get(soort.collectie.id, site.id);
     548    if (!pl) return null;
     549    return buildMixtapeObject(base, site, { ...pl, _post: post }, playlistOpenTracks(pl.id)) || null;
     550  } catch { return null; /* een bandje minder is geen kapotte post */ }
     551}
     552
    498553// A single post as an AS2 Note (the object), and as a Create activity (for outbox/delivery).
    499554export function buildNote(base, site, post, opts = {}) {
     
    565620
    566621  // Paid post (klonkt-demo-aki): federate a PUBLIC teaser + link, never the full
    567   // content, so nothing leaks past the paywall. No media attachments either.
     622  // content, so nothing leaks past the paywall. Images stay home too.
     623  //
     624  // MAAR DE OPENGEZETTE AUDIO REIST WEL MEE (Robin, 24-8, naar aanleiding van
     625  // boiert.eu/introducing-this-machine). De muur staat om de TEKST. `fedi_open`
     626  // is een aparte, eenrichtings, per nummer bewust gezette vlag van de eigenaar,
     627  // en die nummers federeren toch al los als eigen Audio-objecten met hun
     628  // `context` naar deze post. Hield deze tak het bandje tegen, dan hield hij
     629  // niets geheim -- alleen de VOLGORDE en het feit dat het een cassette is. In
     630  // de hub viel het bandje daardoor uiteen in vier losse nummers onder een kale
     631  // teaserkaart. Een cassette die terugwijst naar "lees verder (supporters)"
     632  // dient de betaalde post beter dan vier weesnummers.
    568633  if (post.paid) {
    569634    const esc = (x) => String(x || '').replace(/[<>&]/g, (c) => ({ '<': '&lt;', '>': '&gt;', '&': '&amp;' }[c]));
     
    571636    const rawTeaser = String(post.excerpt || '').trim()
    572637      || _firstP.replace(/<[^>]+>/g, ' ').replace(/&[a-z#0-9]+;/gi, ' ').replace(/\s+/g, ' ').trim().slice(0, 280);
     638    const openBijlagen = openAudioAttachments(base, site, post);
     639    const band = mixtapeAttachment(base, site, post);
     640    // Het bandje alleen als er ook echt iets open in zit: een cassette waarvan
     641    // elk nummer gesloten is, is een lege doos met een titel erop.
     642    if (band && openBijlagen.length) openBijlagen.push(band);
    573643    return {
    574644      '@context': AP_CONTEXT,
     
    579649      url: human,
    580650      published: toISO(post.published_at || post.created_at || Date.now()),
     651      ...(openBijlagen.length ? { attachment: openBijlagen } : {}),
    581652      to: [PUBLIC],
    582653      cc: [`${aId}/followers`],
     
    705776    }
    706777  } catch { /* non-fatal */ }
    707   // fedi_open tracks → real AS2 Audio attachments (the actual file URL, served ungated) so
    708   // EVERY client incl. the Mastodon apps plays them inline natively. Gated tracks (default)
    709   // stay link/card-only — the file is never exposed for them. Resolve from post.content so a
    710   // later body mutation can't affect it.
    711   const openAudio = [];
    712   if (hadAudio) {
    713     const seenA = new Set();
    714     const addRow = (r) => {
    715       const fn = r.filename || (r.storage_path || '').split('/').pop();
    716       if (!fn || seenA.has(fn)) return; seenA.add(fn);
    717       const a = { type: 'Audio', mediaType: r.mime_type || 'audio/mpeg', url: `${base}/audio/stream/${encodeURIComponent(fn)}`, name: r.title || 'Audio' };
    718       // Cover art on the Audio attachment (AS2 `icon`): track cover, else the post cover.
    719       // Mastodon renders it as the artwork thumbnail on its native audio player.
    720       const art = abs(r.cover_url || post.cover_image_url || null);
    721       if (art) a.icon = { type: 'Image', mediaType: guessMediaType(art), url: art };
    722       openAudio.push(a);
    723     };
    724     const SEL = 'SELECT t.title, t.cover_url, m.filename, m.storage_path, m.mime_type FROM audio_tracks t JOIN media m ON m.id = t.media_id WHERE t.fedi_open = 1 AND ';
    725     try {
    726       for (const mm of (post.content || '').matchAll(/\[\[track:([A-Za-z0-9_-]+)\]\]/g)) { const r = db.prepare(SEL + 't.id = ?').get(mm[1]); if (r) addRow(r); }
    727       for (const mm of (post.content || '').matchAll(/\[\[album:([^\]]+)\]\]/g)) for (const r of db.prepare(SEL + 't.site_id = ? AND t.album = ? ORDER BY t.rowid').all(site.id, mm[1].trim())) addRow(r);
    728       for (const mm of (post.content || '').matchAll(/\[\[playlist:([A-Za-z0-9_-]+)\]\]/g)) for (const r of db.prepare('SELECT t.title, t.cover_url, m.filename, m.storage_path, m.mime_type FROM playlist_tracks pt JOIN audio_tracks t ON t.id = pt.track_id JOIN media m ON m.id = t.media_id WHERE t.fedi_open = 1 AND pt.playlist_id = ? ORDER BY pt.position').all(mm[1])) addRow(r);
    729     } catch { /* non-fatal */ }
    730   }
     778  const openAudio = openAudioAttachments(base, site, post);
    731779  // ONVERTAALD voor een verhuizing (FEP-1580). De doelinstantie IS een Klonkt:
    732780  // die rendert [[track:]], [[album:]] en [[playlist:]] zelf en maakt er een
     
    795843  for (const a of openAudio) attachment.push(a); // fedi_open tracks → native Audio players
    796844
    797   // HET BANDJE OP DE DRAAD. Zonder dit stuk bestaat `Mixtape` alleen in onze
    798   // eigen code: de playlist-collectie blijft namelijk een OrderedCollection
    799   // (dat moet, anders verliest een lezer die `type` als tekst uitpakt het hele
    800   // object), en dan zegt niets naar buiten toe ooit dat dit een cassette is.
    801   // Gemeten op 21-8: in de Note van een mixtape-post kwam het woord Mixtape
    802   // niet voor, en de hub gooide zo'n bandje daarom stil weg.
    803   //
    804   // Als bijlage en niet als het object zelf: de post blijft een Note, zodat
    805   // Mastodon en alles wat `Mixtape` niet kent gewoon een bericht met audio
    806   // ziet. Wie het type wel kent, vindt het bandje als geheel.
    807   try {
    808     const soort = postMusicType(post.content || '', site.id);
    809     if (soort && soort.type === 'mixtape' && soort.collectie && soort.collectie.id) {
    810       const pl = db.prepare('SELECT * FROM playlists WHERE id = ? AND site_id = ?')
    811         .get(soort.collectie.id, site.id);
    812       if (pl) {
    813         const tape = buildMixtapeObject(base, site, { ...pl, _post: post }, playlistOpenTracks(pl.id));
    814         if (tape) attachment.push(tape);
    815       }
    816     }
    817   } catch { /* een bandje minder is geen kapotte post */ }
     845  // Het bandje als bijlage — zie mixtapeAttachment() voor het waarom.
     846  const tape = mixtapeAttachment(base, site, post);
     847  if (tape) attachment.push(tape);
    818848
    819849  // Inline @user@host mentions: the Mention tag objects + the mentioned actor URIs. Only
Note: See TracChangeset for help on using the changeset viewer.