Changeset 0db3c72 in Klonkt for src/routes/activitypub.js


Ignore:
Timestamp:
08/10/2026 06:54:54 AM (4 weeks ago)
Author:
Robin <roboburr@…>
Branches:
main
Children:
75f2897
Parents:
7e53594
Message:

Standaardvormen naast het dialect: AS2 en FEP waar die bestaan

shaer-nmw. De client-legs spraken Klonkt-dialect waar de standaard het al
regelt. Nu dragen ze de standaardvorm ERNAAST:

  • attributedTo als ingesloten Person (naam, preferredUsername, icon, url, FEP-9098 emoji in tag) in plaats van alleen shaer:author;
  • een boost is een echte Announce met zijn actor, niet shaer:booster;
  • FEP-044f quote als de geciteerde Note zelf, met eigen attributedTo;
  • AS2 preview (Page met url, name, image) voor de linkkaart.

Weten we niets van de persoon, dan blijft attributedTo de kale URI: een leeg
object zou beweren dat we hem kennen.

De shaer:-velden blijven voorlopig staan. Ze weghalen is de tweede helft van
de bead en kan pas als de apps in het veld zijn bijgewerkt.

De toets is niet "onze app snapt het" maar of een GENERIEKE AP-lezer er iets
aan heeft; test/standard-shapes.test.js kijkt daarom naar geen enkel
shaer:-veld.

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

File:
1 edited

Legend:

Unmodified
Added
Removed
  • src/routes/activitypub.js

    r7e53594 r0db3c72  
    214214      if (it && it.object && typeof it.object === 'object') {
    215215        it.object['shaer:author'] = me;
     216        // En de standaardvorm ernaast (shaer-nmw): de ingesloten actor, zodat
     217        // ook een generieke lezer de byline heeft.
     218        it.object.attributedTo = AP.actorObject(
     219          (typeof it.object.attributedTo === 'string' ? it.object.attributedTo : undefined) || AP.actorId(baseUrl(req), site.slug),
     220          me,
     221        );
    216222        const row = byNote.get(it.object.id);
    217223        if (row) {
    218224          it.object['shaer:quote'] = AP.timelineQuote(row.quote_json);
    219           if (bearerEmbeds) it.object['shaer:embed'] = AP.timelineEmbed(row.embed_json, { playback: bearerEmbeds.playback });
     225          it.object.quote = AP.quoteObject(row.quote_json);
     226          if (bearerEmbeds) {
     227            it.object['shaer:embed'] = AP.timelineEmbed(row.embed_json, { playback: bearerEmbeds.playback });
     228            it.object.preview = AP.previewObject(row.embed_json, { playback: bearerEmbeds.playback });
     229          }
    220230        }
    221231      }
     
    438448  // goed van een vreemde server. De naam zelf blijft, met :shortcode: als tekst.
    439449  const gateAuthor = (a) => (a && !emojiAllowed ? { ...a, emojis: undefined } : a);
     450  // ── Standaardvormen naast het dialect (shaer-nmw) ────────────────
     451  //
     452  // Een lezer die AS2 kent heeft nu genoeg aan attributedTo (ingesloten
     453  // actor), quote (FEP-044f als object), preview (AS2 core) en de
     454  // Announce-wrapper. De shaer:-velden blijven er nog naast staan voor apps
     455  // in het veld; die gaan eruit als de clients om zijn.
     456  const authorInfo = (r, p) => {
     457    const info = {
     458      name: r[`${p}name`] || undefined, handle: r[`${p}handle`] || undefined,
     459      icon: r[`${p}icon`] || undefined, url: r[`${p}url`] || undefined,
     460      emojis: (() => { try { return r[`${p}emoji_json`] ? JSON.parse(r[`${p}emoji_json`]) : undefined; } catch { return undefined; } })(),
     461    };
     462    return (info.name || info.handle || info.icon) ? gateAuthor(info) : undefined;
     463  };
    440464  const rows = AP.getTimeline(auth.site.slug, 60);
    441465  // Eén query voor de hele pagina (shaer-9e9 fase 2): shaer:liked komt uit de
     
    443467  // ap_timeline. Per rij vragen zou hier een N+1 opleveren.
    444468  const reacties = AP.getReactionsFor(auth.site.slug, rows.map((t) => t.id));
    445   const posts = rows.map((t) => ({
     469  const posts = rows.map((t) => {
     470    const auteur = authorInfo(t, 'author_');
     471    const booster = authorInfo(t, 'reblog_');
     472    const boosterUri = t.reblog_url || t.reblog_handle || undefined;
     473    return {
    446474    id: `${t.id}#create`,
    447     type: 'Create',
    448     actor: t.author_uri,
     475    // EEN BOOST IS EEN ANNOUNCE (shaer-nmw): een Create met een
     476    // zijkanaal-property was onze uitvinding; de wrapper is de standaard, en
     477    // elke AP-client leest hem al.
     478    type: booster ? 'Announce' : 'Create',
     479    actor: booster ? (AP.actorObject(boosterUri || t.author_uri, booster)) : t.author_uri,
    449480    published: t.published || t.created_at || undefined,
    450481    object: {
    451482      id: t.id,
    452483      type: 'Note',
    453       attributedTo: t.author_uri,
     484      // AS2 staat een INGESLOTEN actor toe; dan heeft elke client de byline,
     485      // niet alleen de onze (shaer-nmw).
     486      attributedTo: AP.actorObject(t.author_uri, auteur),
    454487      content: t.content,
    455488      url: t.url || undefined,
     
    497530      // Carries shaer:playerUrl only when the playback gate is open too.
    498531      'shaer:embed': embedsAllowed ? AP.timelineEmbed(t.embed_json, { playback: playbackAllowed }) : undefined,
     532      // De standaardvormen (shaer-nmw): FEP-044f quote als object, AS2 preview
     533      // als kaart. Dezelfde poorten als hun shaer:-tegenhangers hierboven.
     534      quote: quotesAllowed ? AP.quoteObject(t.quote_json) : undefined,
     535      preview: embedsAllowed ? AP.previewObject(t.embed_json, { playback: playbackAllowed }) : undefined,
    499536    },
    500   }));
     537  };
     538  });
    501539  // The direct notes addressed to this account: a plain DM, a guardian's wave
    502540  // (§5), a ward's 🛟 help request (§5.2.1). Those are messages, not posts, so
     
    520558      id: m.object_uri,
    521559      type: 'Note',
    522       attributedTo: m.actor_uri,
     560      attributedTo: AP.actorObject(m.actor_uri, (m.actor_name || m.actor_handle || m.actor_icon) ? gateAuthor({
     561        name: m.actor_name || undefined, handle: m.actor_handle || undefined,
     562        icon: m.actor_icon || undefined, url: m.actor_url || undefined,
     563        emojis: (() => { try { return m.actor_emoji_json ? JSON.parse(m.actor_emoji_json) : undefined; } catch { return undefined; } })(),
     564      }) : undefined),
    523565      content: AP.stripLeadingMentions(m.content),
    524566      url: m.note_url || undefined,
     
    543585      } : undefined),
    544586      'shaer:embed': embedsAllowed ? AP.timelineEmbed(m.embed_json, { playback: playbackAllowed }) : undefined,
     587      quote: quotesAllowed ? AP.quoteObject(m.quote_json) : undefined,
     588      preview: embedsAllowed ? AP.previewObject(m.embed_json, { playback: playbackAllowed }) : undefined,
    545589    },
    546590  }));
     
    557601      id: m.object_uri,
    558602      type: 'Note',
    559       attributedTo: m.actor_uri,
     603      attributedTo: AP.actorObject(m.actor_uri, (m.actor_name || m.actor_handle || m.actor_icon) ? gateAuthor({
     604        name: m.actor_name || undefined, handle: m.actor_handle || undefined,
     605        icon: m.actor_icon || undefined, url: m.actor_url || undefined,
     606        emojis: (() => { try { return m.actor_emoji_json ? JSON.parse(m.actor_emoji_json) : undefined; } catch { return undefined; } })(),
     607      }) : undefined),
    560608      content: AP.stripLeadingMentions(m.content),
    561609      inReplyTo: m.parent_uri || `${base}/ap/notes/${m.post_id}`,
     
    571619      } : undefined,
    572620      'shaer:embed': embedsAllowed ? AP.timelineEmbed(m.embed_json, { playback: playbackAllowed }) : undefined,
     621      quote: quotesAllowed ? AP.quoteObject(m.quote_json) : undefined,
     622      preview: embedsAllowed ? AP.previewObject(m.embed_json, { playback: playbackAllowed }) : undefined,
    573623    },
    574624  }));
     
    585635    // The leading mention anchor is addressing, not prose (the DM leg strips
    586636    // it the same way); the Mention tags built from the full content stay.
    587     object: { ...n, content: AP.stripLeadingMentions(n.content), 'shaer:author': mine },
     637    object: {
     638      ...n, content: AP.stripLeadingMentions(n.content),
     639      'shaer:author': mine,
     640      attributedTo: AP.actorObject(typeof n.attributedTo === 'string' ? n.attributedTo : me, mine),
     641    },
    588642  }));
    589643  // Newest first over all legs, so the app can keep treating this as one feed.
     
    905959    'shaer:quote': AP.timelineQuote(uit.quoteJson),
    906960    'shaer:embed': embedsAllowed ? AP.timelineEmbed(uit.embedJson, { playback }) : undefined,
     961    quote: AP.quoteObject(uit.quoteJson),
     962    preview: embedsAllowed ? AP.previewObject(uit.embedJson, { playback }) : undefined,
    907963  }, 'private, no-store');
    908964});
Note: See TracChangeset for help on using the changeset viewer.