Ignore:
Timestamp:
06/27/2026 10:27:33 AM (2 months ago)
Author:
Robin Genis <roboburr@…>
Branches:
main
Children:
7eddb3b
Parents:
f3551ec
Message:

fix(fedi): resolve PeerTube videos (array attributedTo) as a reply target

  • services/ActivityPubService.js — resolveRemoteNote returned null ("Could not fetch that post") for objects whose attributedTo is an ARRAY (PeerTube Video = [Person, Group]). New actorUriOf() handles string/object/array and prefers the Person; used for the post and each thread ancestor. Non-Note objects (Video/Article…) now prepend their name (title) to the shown content so you see what you're replying to.
File:
1 edited

Legend:

Unmodified
Added
Removed
  • src/services/ActivityPubService.js

    rf3551ec rc6bd87e  
    965965}
    966966
     967// attributedTo may be a string, an object {id}, or an ARRAY — e.g. a PeerTube Video is
     968// attributed to [Person (account), Group (channel)]. Pick a usable actor URI (prefer Person).
     969function actorUriOf(att) {
     970  if (!att) return null;
     971  if (typeof att === 'string') return att;
     972  if (Array.isArray(att)) {
     973    const person = att.find((a) => a && typeof a === 'object' && a.type === 'Person' && a.id);
     974    if (person) return person.id;
     975    for (const a of att) { if (typeof a === 'string') return a; if (a && a.id) return a.id; }
     976    return null;
     977  }
     978  return att.id || null;
     979}
     980
    967981// Resolve a remote post URL (any fediverse/Klonkt post) into a reply target.
    968982// Returns a parent-shaped object usable by deliverReply(), or null.
     
    972986  if (!note || !note.id) return null;
    973987  const att = note.attributedTo;
    974   const actorUri = typeof att === 'string' ? att : (att && att.id);
     988  const actorUri = actorUriOf(att);
    975989  if (!actorUri) return null;
    976990  const actor = await fetchActor(actorUri).catch(() => null);
     
    9901004    const pn = await fetchActor(url).catch(() => null);
    9911005    if (!pn) break;
    992     const pa = typeof pn.attributedTo === 'string' ? pn.attributedTo : (pn.attributedTo && pn.attributedTo.id);
     1006    const pa = actorUriOf(pn.attributedTo);
    9931007    if (pa && pa !== actorUri) {
    9941008      const paDoc = await fetchActor(pa).catch(() => null);
     
    9981012    cursor = pn.inReplyTo; // climb to the next ancestor
    9991013  }
    1000   const rawHtml = String(note.content || '').replace(/\[\[(track|album|playlist):[^\]]+\]\]/gi, '');
     1014  // For non-Note objects (PeerTube Video, Article, …) the meaningful label is `name` (the
     1015  // title); prepend it so the reply page shows what you're replying to (sanitize cleans it).
     1016  let rawHtml = String(note.content || '').replace(/\[\[(track|album|playlist):[^\]]+\]\]/gi, '');
     1017  if (note.name && note.type && note.type !== 'Note') rawHtml = `<p><strong>${note.name}</strong></p>` + rawHtml;
    10011018  const images = (Array.isArray(note.attachment) ? note.attachment : [])
    10021019    .filter((a) => a && a.url && (!a.mediaType || /^image\//i.test(a.mediaType)))
Note: See TracChangeset for help on using the changeset viewer.