Changeset c1df3e9 in Klonkt


Ignore:
Timestamp:
08/07/2026 06:16:10 PM (5 weeks ago)
Author:
Robin <roboburr@…>
Branches:
main
Children:
4b5db37
Parents:
c25eff7
git-author:
Robin <roboburr@…> (08/07/2026 06:14:30 PM)
git-committer:
Robin <roboburr@…> (08/07/2026 06:16:10 PM)
Message:

Playlist-collecties zijn vindbaar (shaer-ayc, stap 2)

Drie wegen, drie vragen:

streams op de actor 'wat heeft deze site?' -- AS2-kern, letterlijk

'supplementary Collections which may be of
interest', dus geen eigen vocabulaire nodig

GET .../playlists de lijst: kaal URI's, verrijkt stubs op verzoek

(FEP-9876, zelfde conventie als followers)

Link-tag op de Note 'wat zit er in deze post?' -- Mastodon parseert

alleen Mention/Hashtag/Emoji en negeert een Link

De poortregel loopt overal door: ook een stub in de verrijkte lijst telt
alleen het open deel, en de gated titel komt nergens in de JSON voor.

De Link-tag checkt de site: playlist-ids zijn een globale primary key, dus
zonder die check zou een post van site A naar de collectie van site B wijzen.

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

Files:
1 added
3 edited

Legend:

Unmodified
Added
Removed
  • src/routes/activitypub.js

    rc25eff7 rc1df3e9  
    636636// zonder open tracks bestaat wel maar is leeg, want de playlist zelf is niet
    637637// geheim, alleen de bestanden erachter.
     638// De lijst van alle playlist-collecties (shaer-ayc, stap 2). De actor wijst
     639// hierheen via AS2 `streams`. Kaal standaard; verrijkte stubs op verzoek
     640// (FEP-9876), dezelfde conventie als followers/following.
     641router.get('/ap/users/:slug/playlists', (req, res) => {
     642  const site = publicSite(req.params.slug);
     643  if (!site) return res.status(404).end();
     644  AP.sendAP(res, AP.listPlaylistsAP(baseUrl(req), site, wantsEnriched(req, res)));
     645});
     646
    638647router.get('/ap/users/:slug/playlists/:id', (req, res) => {
    639648  const site = publicSite(req.params.slug);
  • src/services/ActivityPubService.js

    rc25eff7 rc1df3e9  
    195195    following: `${id}/following`,
    196196    featured: `${id}/featured`,
     197    // AS2-kern `streams`: "supplementary Collections which may be of
     198    // interest" -- precies wat de playlist-lijst is (shaer-ayc, stap 2).
     199    // Geen eigen vocabulaire nodig, en wie het niet kent negeert het.
     200    streams: [`${id}/playlists`],
    197201    // AP §5.6: the private blocked collection (owner-only GET). The server
    198202    // list is the source of truth for Shaer's "in Orbit"; clients keep no
     
    548552      ...((post.fan_only || post.ap_visibility === 'quiet') ? [] : [`${aId}/followers`]),
    549553      ..._mentionCc])],
    550     tag: [...buildHashtagList(base, post.tags, body), ..._mentionTags],
     554    tag: [...buildHashtagList(base, post.tags, body), ..._mentionTags, ...playlistLinkTags(base, site, post.content)],
    551555    replies: `${id}/replies`,
    552556    // NSFW → Mastodon-style content warning: sensitive (blurs media) + a summary/spoiler
     
    892896     ORDER BY pt.position`
    893897  ).all(playlistId);
     898}
     899
     900// Een post die een playlist insluit wijst in zijn AS2 ook naar de collectie
     901// (shaer-ayc, stap 2): een Link-tag per ingesloten playlist. Mastodon
     902// parseert alleen Mention/Hashtag/Emoji en negeert een Link geruisloos; een
     903// client die hem kent haalt de collectie op. Opgelost uit post.content en
     904// ALLEEN binnen de eigen site: playlist-ids zijn een globale primary key, dus
     905// zonder site-check zou een post van site A naar de collectie van site B
     906// kunnen wijzen.
     907export function playlistLinkTags(base, site, content) {
     908  const out = [];
     909  const seen = new Set();
     910  try {
     911    for (const m of (content || '').matchAll(/\[\[playlist:([A-Za-z0-9_-]+)\]\]/g)) {
     912      if (seen.has(m[1])) continue;
     913      seen.add(m[1]);
     914      const pl = db.prepare('SELECT id, title FROM playlists WHERE id = ? AND site_id = ?').get(m[1], site.id);
     915      if (!pl) continue;
     916      out.push({ type: 'Link', href: `${actorId(base, site.slug)}/playlists/${pl.id}`, mediaType: 'application/activity+json', name: pl.title });
     917    }
     918  } catch { /* niet-fataal: een tag minder, geen kapotte Note */ }
     919  return out;
     920}
     921
     922// De lijst van alle playlist-collecties van een site (shaer-ayc, stap 2).
     923// Kaal standaard (URI's), verrijkt op verzoek (FEP-9876, zelfde conventie als
     924// followers/following): een stub per playlist met naam, hoes en de EERLIJKE
     925// telling -- totalItems van de stub telt het open deel, dezelfde regel als de
     926// collectie zelf, want ook een lijst mag niet verklappen wat er achter de
     927// poort staat.
     928export function listPlaylistsAP(base, site, enriched) {
     929  const rows = db.prepare(
     930    'SELECT id, title, artist, year, cover_url FROM playlists WHERE site_id = ? ORDER BY created_at, id'
     931  ).all(site.id);
     932  const colId = `${actorId(base, site.slug)}/playlists`;
     933  const items = rows.map((p) => {
     934    const uri = `${actorId(base, site.slug)}/playlists/${p.id}`;
     935    if (!enriched) return uri;
     936    const stub = buildPlaylistCollection(base, site, p, playlistOpenTracks(p.id));
     937    delete stub['@context'];       // genest object draagt de context van zijn omhulsel
     938    delete stub.orderedItems;      // stub: wie de tracks wil, haalt de collectie op
     939    return stub;
     940  });
     941  return {
     942    '@context': AP_CONTEXT,
     943    id: colId,
     944    type: 'OrderedCollection',
     945    attributedTo: actorId(base, site.slug),
     946    totalItems: items.length,
     947    orderedItems: items,
     948  };
    894949}
    895950
     
    54825537  AP_CONTEXT, getOrCreateKeys, apWants, sendAP, actorId, noteId, stripLeadingMentions,
    54835538  buildActor, buildNote, buildCreate, buildOutbox, buildFollowers, buildFollowing, buildFeatured,
    5484   buildPlaylistCollection, playlistOpenTracks,
     5539  buildPlaylistCollection, playlistOpenTracks, listPlaylistsAP, playlistLinkTags,
    54855540  followerCount, deliver, fetchActor, verifyRequest, handleInbox, deliverCreate, deliverDelete, deliverUpdate, deliverActorUpdate, resyncFeaturedPins,
    54865541  feedCursor, feedChangesSince, waitForFeedChange,
  • test/activitypub-as2.test.js

    rc25eff7 rc1df3e9  
    3232  // ActivityPub §5.6: the private blocked collection (owner-only GET).
    3333  'blocked',
     34  // ActivityPub §4.1: supplementary collections on the actor — Klonkt wijst
     35  // ermee naar de playlist-lijst (shaer-ayc).
     36  'streams',
    3437  // FEP-633c (Guardians): the owner-only dashboard queues on the actor; the
    3538  // sub-keys are the daemon-contract collection names the Shaer clients read.
Note: See TracChangeset for help on using the changeset viewer.