fedipod-server 0.13.0 → 0.13.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -55555,10 +55555,16 @@ function indexQuads(quads) {
55555
55555
  }
55556
55556
  return { bySubject, objects, blanks };
55557
55557
  }
55558
- function findRoot({ bySubject, objects }) {
55559
- for (const s of bySubject.keys()) if (!objects.has(s)) return s;
55560
- for (const [s, preds] of bySubject) if (preds.has(RDF_TYPE)) return s;
55561
- return bySubject.keys().next().value ?? null;
55558
+ function findRoot({ bySubject, objects, blanks }) {
55559
+ const all = [...bySubject.keys()];
55560
+ const named = all.filter((s) => !blanks.has(s));
55561
+ const free = all.filter((s) => !objects.has(s));
55562
+ const freeNamed = free.find((s) => !blanks.has(s));
55563
+ if (freeNamed) return freeNamed;
55564
+ if (free.length) return free[0];
55565
+ for (const s of named) if (bySubject.get(s).has(RDF_TYPE)) return s;
55566
+ for (const s of all) if (bySubject.get(s).has(RDF_TYPE)) return s;
55567
+ return named[0] ?? all[0] ?? null;
55562
55568
  }
55563
55569
  function readList(head, ctx, depth, path) {
55564
55570
  const out = [];
@@ -55636,7 +55642,7 @@ function makeView(subject, ctx, depth = 0, seen = /* @__PURE__ */ new Set()) {
55636
55642
  function graphView(quads, { root = null } = {}) {
55637
55643
  if (!quads || !quads.length) return null;
55638
55644
  const ctx = indexQuads(quads);
55639
- const subject = root ?? findRoot(ctx);
55645
+ const subject = root && ctx.bySubject.has(root) ? root : findRoot(ctx);
55640
55646
  if (!subject) return null;
55641
55647
  const view = makeView(subject, ctx);
55642
55648
  return view && typeof view === "object" ? view : null;
@@ -55695,19 +55701,21 @@ function groundContext(input) {
55695
55701
  return { ...input, "@context": kept.length === 1 ? kept[0] : kept };
55696
55702
  }
55697
55703
  async function readLenient(raw) {
55704
+ let input = null;
55705
+ try {
55706
+ input = typeof raw === "string" ? JSON.parse(raw) : raw ?? null;
55707
+ } catch {
55708
+ }
55709
+ const own = input && typeof input === "object" ? input.id ?? input["@id"] : null;
55710
+ const root = typeof own === "string" && /^https?:\/\//u.test(own) ? own : null;
55698
55711
  try {
55699
55712
  const { doc, graph: graph2 } = await parseAS2(raw);
55700
- return { doc, graph: graph2, view: graphView(graph2), degraded: null };
55713
+ return { doc, graph: graph2, view: graphView(graph2, { root }), degraded: null };
55701
55714
  } catch (e) {
55702
- let input = null;
55703
- try {
55704
- input = typeof raw === "string" ? JSON.parse(raw) : raw ?? null;
55705
- } catch {
55706
- }
55707
55715
  if (!input || typeof input !== "object") return { doc: null, graph: null, view: null, degraded: e.message };
55708
55716
  try {
55709
55717
  const { graph: graph2 } = await parseAS2(groundContext(input));
55710
- return { doc: input, graph: graph2, view: graphView(graph2), degraded: e.message };
55718
+ return { doc: input, graph: graph2, view: graphView(graph2, { root }), degraded: e.message };
55711
55719
  } catch (inner) {
55712
55720
  return { doc: input, graph: null, view: null, degraded: `${e.message}; grounded read also failed: ${inner.message}` };
55713
55721
  }
@@ -57431,9 +57439,9 @@ async function onUpdate(intake, activity, actor) {
57431
57439
  const note = await intake.fetchAP(objectId);
57432
57440
  if (!note) throw new Error(`cannot refetch ${objectId} \u2014 will retry`);
57433
57441
  if (note.id !== objectId || !isContentType(note.type)) return `object not verifiable content (${objectId}, ${note.type})`;
57434
- const { attachmentsOf: attachmentsOf2, titledContent: titledContent2 } = await Promise.resolve().then(() => (init_wire(), wire_exports));
57442
+ const { attachmentsOf: attachmentsOf3, titledContent: titledContent2 } = await Promise.resolve().then(() => (init_wire(), wire_exports));
57435
57443
  const content = titledContent2(note);
57436
- const attachments = attachmentsOf2(note);
57444
+ const attachments = attachmentsOf3(note);
57437
57445
  const freshPoll = pollOf(note);
57438
57446
  const freshEmojis = emojisOf(note);
57439
57447
  intake.store.updateStatus(objectId, {
@@ -57567,8 +57575,8 @@ async function ingestNote(intake, objectId, actor, { via } = {}) {
57567
57575
  const note = await intake.fetchAP(objectId);
57568
57576
  if (!note) return `object fetch failed (${objectId})`;
57569
57577
  if (note.id !== objectId || !isContentType(note.type)) return `object not verifiable content (${objectId}, ${note.type})`;
57570
- const { attachmentsOf: attachmentsOf2, titledContent: titledContent2 } = await Promise.resolve().then(() => (init_wire(), wire_exports));
57571
- const attachments = attachmentsOf2(note);
57578
+ const { attachmentsOf: attachmentsOf3, titledContent: titledContent2 } = await Promise.resolve().then(() => (init_wire(), wire_exports));
57579
+ const attachments = attachmentsOf3(note);
57572
57580
  const content = titledContent2(note);
57573
57581
  const author = authorOf(note, actor);
57574
57582
  if (!author) return `object names an author its origin does not vouch for (${objectId})`;
@@ -64488,6 +64496,16 @@ var postUrl = (uri) => {
64488
64496
  const m = String(uri).match(/^at:\/\/([^/]+)\/[^/]+\/(.+)$/);
64489
64497
  return m ? `https://bsky.app/profile/${m[1]}/post/${m[2]}` : null;
64490
64498
  };
64499
+ function attachmentsOf2(post) {
64500
+ const e = post?.embed || {};
64501
+ const images = [...e.images || [], ...e.media?.images || []].filter((i) => i?.fullsize).map((i) => ({ url: i.fullsize, mediaType: "image/jpeg", description: i.alt || "" }));
64502
+ const card = e.external?.thumb || e.media?.external?.thumb;
64503
+ if (card) {
64504
+ const ext = e.external || e.media.external;
64505
+ images.push({ url: card, mediaType: "image/jpeg", description: ext.title || ext.uri || "" });
64506
+ }
64507
+ return images;
64508
+ }
64491
64509
  var BskyFeed = class {
64492
64510
  constructor({ store, atproto, log: log2 = console.log, onNotification = null }) {
64493
64511
  Object.assign(this, { store, atproto, log: log2, onNotification });
@@ -64543,14 +64561,27 @@ var BskyFeed = class {
64543
64561
  return url;
64544
64562
  }
64545
64563
  // One Bluesky post into the statuses index. Returns its noteId, new or not.
64564
+ // A post seen before is completed, not duplicated: a reply first met as a
64565
+ // bare notification record learns its parent and its pictures from the
64566
+ // full view when that arrives.
64546
64567
  _mirrorPost(post, { via = null } = {}) {
64547
64568
  const noteId = post.uri;
64548
- const existing = this.store.getStatuses().some((s) => s.noteId === noteId);
64549
- if (existing) return { noteId, added: false };
64569
+ const inReplyTo = post.record?.reply?.parent?.uri || null;
64570
+ const attachments = attachmentsOf2(post);
64571
+ const existing = this.store.getStatuses().find((s) => s.noteId === noteId);
64572
+ if (existing) {
64573
+ const patch = {
64574
+ ...inReplyTo && !existing.inReplyTo ? { inReplyTo } : {},
64575
+ ...attachments.length && !existing.attachments?.length ? { attachments } : {}
64576
+ };
64577
+ if (Object.keys(patch).length && typeof this.store.updateStatus === "function") {
64578
+ this.store.updateStatus(noteId, patch);
64579
+ }
64580
+ return { noteId, added: false };
64581
+ }
64550
64582
  const actor = this._rememberAuthor(post.author);
64551
64583
  if (this.store.isBlocked(actor)) return { noteId, added: false };
64552
64584
  const text = post.record?.text || "";
64553
- const images = post.embed?.images || [];
64554
64585
  this.store.addStatus({
64555
64586
  noteId,
64556
64587
  actor,
@@ -64560,12 +64591,46 @@ var BskyFeed = class {
64560
64591
  ...post.cid ? { cid: post.cid } : {},
64561
64592
  link: postUrl(noteId),
64562
64593
  ...via ? { via } : {},
64563
- ...images.length ? {
64564
- attachments: images.map((i) => ({ url: i.fullsize, mediaType: "image/jpeg", description: i.alt || "" }))
64565
- } : {}
64594
+ ...inReplyTo ? { inReplyTo } : {},
64595
+ ...attachments.length ? { attachments } : {}
64566
64596
  });
64567
64597
  return { noteId, added: true };
64568
64598
  }
64599
+ // The conversation under one post, asked for when a post is opened: the
64600
+ // ancestors it hangs from and the replies beneath it, each mirrored with
64601
+ // the reply link the thread view is built from. View cache only, like the
64602
+ // timeline. Returns how many posts were new; a thread Bluesky will not
64603
+ // give (gone, blocked, rate-limited) adds none and is logged, not thrown.
64604
+ async mirrorThread(uri, { depth = 6 } = {}) {
64605
+ if (!this.atproto?.connected()) return 0;
64606
+ if (this.quietUntil && Date.now() < this.quietUntil) return 0;
64607
+ let out;
64608
+ try {
64609
+ out = await this.atproto.xrpc("app.bsky.feed.getPostThread", { params: { uri, depth, parentHeight: 40 } });
64610
+ } catch (e) {
64611
+ if (e.status === 429 || e.status >= 500) this._backOff(e.status, null);
64612
+ else this.log(`bskyfeed: thread ${uri}: ${e.message}`);
64613
+ return 0;
64614
+ }
64615
+ const self2 = this.atproto.read()?.did;
64616
+ const ownMirrors = new Set(this.store.getStatuses().map((s) => s.atproto?.uri).filter(Boolean));
64617
+ let added = 0;
64618
+ const take2 = (node) => {
64619
+ const post = node?.post;
64620
+ if (!post?.uri) return;
64621
+ if (post.author?.did === self2 && ownMirrors.has(post.uri)) return;
64622
+ if (this._mirrorPost(post).added) added++;
64623
+ };
64624
+ for (let up = out?.thread?.parent; up; up = up.parent) take2(up);
64625
+ const walk = (node, left) => {
64626
+ take2(node);
64627
+ if (left <= 0) return;
64628
+ for (const r of node?.replies || []) walk(r, left - 1);
64629
+ };
64630
+ walk(out?.thread, depth);
64631
+ if (added) this.log(`bskyfeed: +${added} from the thread under ${uri}`);
64632
+ return added;
64633
+ }
64569
64634
  async sweep() {
64570
64635
  if (!this.atproto?.connected()) return;
64571
64636
  if (this.quietUntil && Date.now() < this.quietUntil) return;
@@ -64604,8 +64669,13 @@ var BskyFeed = class {
64604
64669
  this.store.addNotification({ type: "follow", actor, bsky: true });
64605
64670
  await this.onNotification?.(n, { actor });
64606
64671
  } else if (n.reason === "mention" || n.reason === "reply") {
64607
- const record = { uri: n.uri, cid: n.cid, author: n.author, record: n.record, indexedAt: n.indexedAt };
64608
- this._mirrorPost(record);
64672
+ let view = null;
64673
+ try {
64674
+ const got = await this.atproto.xrpc("app.bsky.feed.getPosts", { params: { uris: n.uri } });
64675
+ view = (got?.posts || [])[0] || null;
64676
+ } catch {
64677
+ }
64678
+ this._mirrorPost(view || { uri: n.uri, cid: n.cid, author: n.author, record: n.record, indexedAt: n.indexedAt });
64609
64679
  this.store.addNotification({ type: "mention", actor, noteId: n.uri, bsky: true });
64610
64680
  await this.onNotification?.(n, { actor });
64611
64681
  }
@@ -65937,6 +66007,10 @@ async function handle6(api, ctx) {
65937
66007
  const mContext = /^\/api\/v1\/statuses\/([a-f0-9]+)\/context$/.exec(pathname);
65938
66008
  if (mContext) {
65939
66009
  const noteUrl = api.lookup(mContext[1]).s?.noteId;
66010
+ const opened = noteUrl && api.store.getStatuses().find((x) => x.noteId === noteUrl);
66011
+ if (opened?.kind === "bsky" && typeof api.agent?.bskyfeed?.mirrorThread === "function") {
66012
+ await api.agent.bskyfeed.mirrorThread(noteUrl);
66013
+ }
65940
66014
  const all = api.store.getStatuses();
65941
66015
  const byId = new Map(all.map((s) => [s.noteId, s]));
65942
66016
  const ancestors = [];
@@ -66372,8 +66446,8 @@ var TagFeed = class {
66372
66446
  await this.intake.fetchAP(author).catch(() => {
66373
66447
  });
66374
66448
  }
66375
- const { attachmentsOf: attachmentsOf2, titledContent: titledContent2 } = await Promise.resolve().then(() => (init_wire(), wire_exports));
66376
- const attachments = attachmentsOf2(note);
66449
+ const { attachmentsOf: attachmentsOf3, titledContent: titledContent2 } = await Promise.resolve().then(() => (init_wire(), wire_exports));
66450
+ const attachments = attachmentsOf3(note);
66377
66451
  this.store.addStatus({
66378
66452
  noteId,
66379
66453
  actor: author,
@@ -67364,6 +67438,14 @@ var Deliverer = class {
67364
67438
 
67365
67439
  // web/app/deliver-relay.mjs
67366
67440
  init_fedify_sig();
67441
+ function doorKeyOf(doorInboxUrl) {
67442
+ try {
67443
+ const seg2 = new URL(doorInboxUrl).pathname.split("/");
67444
+ return seg2[1] === "u" && seg2[2] ? decodeURIComponent(seg2[2]) : null;
67445
+ } catch {
67446
+ return null;
67447
+ }
67448
+ }
67367
67449
  var RelayDeliverer = class extends Deliverer {
67368
67450
  constructor(opts) {
67369
67451
  super(opts);
@@ -67388,6 +67470,7 @@ var RelayDeliverer = class extends Deliverer {
67388
67470
  headers: {
67389
67471
  date: s.headers.date,
67390
67472
  digest: s.headers.digest,
67473
+ accept: s.headers.accept,
67391
67474
  "content-type": s.headers["content-type"],
67392
67475
  signature: s.headers.signature
67393
67476
  }
@@ -69615,7 +69698,9 @@ var BrowserAgent = class _BrowserAgent {
69615
69698
  actorId: this.urls.actor,
69616
69699
  log: this.log,
69617
69700
  relayUrl: `${frontOrigin.replace(/\/$/, "")}/api/relay`,
69618
- handle: config.handle,
69701
+ // The relay finds the account by the front's own key for it, which for a
69702
+ // mail-door account is the full address, not the bare handle.
69703
+ handle: doorKeyOf(config.gateway?.url) || config.handle,
69619
69704
  sessionFetch: session.fetch
69620
69705
  });
69621
69706
  this.publisher = new Publisher({