fedipod-server 0.13.2 → 0.14.0
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.
- package/lib/client/masto/statuses.mjs +16 -2
- package/lib/connections/bskyfeed.mjs +79 -8
- package/lib/core/intake/notes.mjs +7 -1
- package/lib/core/publisher/notes.mjs +17 -0
- package/lib/core/publisher/questions.mjs +2 -0
- package/lib/gateway/front-core.mjs +3 -1
- package/package.json +1 -1
- package/web/app/dist/sw.js +106 -19
- package/web/app/dist/sw.js.map +3 -3
- package/web/app/site/sw.js +106 -19
package/web/app/site/sw.js
CHANGED
|
@@ -55946,6 +55946,17 @@ async function mentionsFor(publisher, content, inReplyTo) {
|
|
|
55946
55946
|
}
|
|
55947
55947
|
return mentions;
|
|
55948
55948
|
}
|
|
55949
|
+
function assertDirectAddressed(content, mentions) {
|
|
55950
|
+
const wanted = [...new Set(mentionsIn(content))];
|
|
55951
|
+
const missing = wanted.filter((h) => !mentions.some((m) => m.handle === h));
|
|
55952
|
+
let why = null;
|
|
55953
|
+
if (!wanted.length) why = "a direct message names who it is for \u2014 mention them as @name@host";
|
|
55954
|
+
else if (missing.length) why = `could not find ${missing.map((h) => "@" + h).join(", ")} \u2014 the direct message was not sent`;
|
|
55955
|
+
if (!why) return;
|
|
55956
|
+
const e = new Error(why);
|
|
55957
|
+
e.code = "unaddressed";
|
|
55958
|
+
throw e;
|
|
55959
|
+
}
|
|
55949
55960
|
async function publishNote(publisher, content, { inReplyTo, attachments, visibility = "public", spoilerText = null } = {}) {
|
|
55950
55961
|
const { urls } = publisher;
|
|
55951
55962
|
const priv = visibility === "private" || visibility === "direct";
|
|
@@ -55956,6 +55967,7 @@ async function publishNote(publisher, content, { inReplyTo, attachments, visibil
|
|
|
55956
55967
|
const published = (/* @__PURE__ */ new Date()).toISOString();
|
|
55957
55968
|
const slug = published.slice(0, 10) + "-" + node_crypto_default.randomBytes(4).toString("hex");
|
|
55958
55969
|
const mentions = await publisher._mentionsFor(content, inReplyTo);
|
|
55970
|
+
if (visibility === "direct") assertDirectAddressed(content, mentions);
|
|
55959
55971
|
const note = noteDoc({
|
|
55960
55972
|
urls,
|
|
55961
55973
|
slug,
|
|
@@ -56157,6 +56169,7 @@ async function publishQuestion(publisher, content, {
|
|
|
56157
56169
|
const published = (/* @__PURE__ */ new Date()).toISOString();
|
|
56158
56170
|
const slug = published.slice(0, 10) + "-" + node_crypto_default.randomBytes(4).toString("hex");
|
|
56159
56171
|
const mentions = await publisher._mentionsFor(content, inReplyTo);
|
|
56172
|
+
if (visibility === "direct") assertDirectAddressed(content, mentions);
|
|
56160
56173
|
const poll = {
|
|
56161
56174
|
multiple: !!multiple,
|
|
56162
56175
|
expiresAt: expiresAt || null,
|
|
@@ -57471,9 +57484,9 @@ async function onUpdate(intake, activity, actor) {
|
|
|
57471
57484
|
const note = await intake.fetchAP(objectId);
|
|
57472
57485
|
if (!note) throw new Error(`cannot refetch ${objectId} \u2014 will retry`);
|
|
57473
57486
|
if (note.id !== objectId || !isContentType(note.type)) return `object not verifiable content (${objectId}, ${note.type})`;
|
|
57474
|
-
const { attachmentsOf:
|
|
57487
|
+
const { attachmentsOf: attachmentsOf3, titledContent: titledContent2 } = await Promise.resolve().then(() => (init_wire(), wire_exports));
|
|
57475
57488
|
const content = titledContent2(note);
|
|
57476
|
-
const attachments =
|
|
57489
|
+
const attachments = attachmentsOf3(note);
|
|
57477
57490
|
const freshPoll = pollOf(note);
|
|
57478
57491
|
const freshEmojis = emojisOf(note);
|
|
57479
57492
|
intake.store.updateStatus(objectId, {
|
|
@@ -57607,8 +57620,8 @@ async function ingestNote(intake, objectId, actor, { via } = {}) {
|
|
|
57607
57620
|
const note = await intake.fetchAP(objectId);
|
|
57608
57621
|
if (!note) return `object fetch failed (${objectId})`;
|
|
57609
57622
|
if (note.id !== objectId || !isContentType(note.type)) return `object not verifiable content (${objectId}, ${note.type})`;
|
|
57610
|
-
const { attachmentsOf:
|
|
57611
|
-
const attachments =
|
|
57623
|
+
const { attachmentsOf: attachmentsOf3, titledContent: titledContent2 } = await Promise.resolve().then(() => (init_wire(), wire_exports));
|
|
57624
|
+
const attachments = attachmentsOf3(note);
|
|
57612
57625
|
const content = titledContent2(note);
|
|
57613
57626
|
const author = authorOf(note, actor);
|
|
57614
57627
|
if (!author) return `object names an author its origin does not vouch for (${objectId})`;
|
|
@@ -57652,7 +57665,9 @@ async function ingestNote(intake, objectId, actor, { via } = {}) {
|
|
|
57652
57665
|
...attachments.length ? { attachments } : {},
|
|
57653
57666
|
...via ? { via } : {}
|
|
57654
57667
|
});
|
|
57655
|
-
|
|
57668
|
+
const namesUs = mentions.some((m) => m.href === intake.urls.actor);
|
|
57669
|
+
const replyToOurs = !!note.inReplyTo && String(note.inReplyTo).startsWith(intake.urls.notes);
|
|
57670
|
+
if (!followed || replyToOurs || direct || namesUs) {
|
|
57656
57671
|
intake.store.addNotification({ type: "mention", actor: author, noteId: note.id });
|
|
57657
57672
|
}
|
|
57658
57673
|
if (note.inReplyTo && String(note.inReplyTo).startsWith(intake.urls.notes)) {
|
|
@@ -64528,6 +64543,16 @@ var postUrl = (uri) => {
|
|
|
64528
64543
|
const m = String(uri).match(/^at:\/\/([^/]+)\/[^/]+\/(.+)$/);
|
|
64529
64544
|
return m ? `https://bsky.app/profile/${m[1]}/post/${m[2]}` : null;
|
|
64530
64545
|
};
|
|
64546
|
+
function attachmentsOf2(post) {
|
|
64547
|
+
const e = post?.embed || {};
|
|
64548
|
+
const images = [...e.images || [], ...e.media?.images || []].filter((i) => i?.fullsize).map((i) => ({ url: i.fullsize, mediaType: "image/jpeg", description: i.alt || "" }));
|
|
64549
|
+
const card = e.external?.thumb || e.media?.external?.thumb;
|
|
64550
|
+
if (card) {
|
|
64551
|
+
const ext = e.external || e.media.external;
|
|
64552
|
+
images.push({ url: card, mediaType: "image/jpeg", description: ext.title || ext.uri || "" });
|
|
64553
|
+
}
|
|
64554
|
+
return images;
|
|
64555
|
+
}
|
|
64531
64556
|
var BskyFeed = class {
|
|
64532
64557
|
constructor({ store, atproto, log: log2 = console.log, onNotification = null }) {
|
|
64533
64558
|
Object.assign(this, { store, atproto, log: log2, onNotification });
|
|
@@ -64583,14 +64608,27 @@ var BskyFeed = class {
|
|
|
64583
64608
|
return url;
|
|
64584
64609
|
}
|
|
64585
64610
|
// One Bluesky post into the statuses index. Returns its noteId, new or not.
|
|
64611
|
+
// A post seen before is completed, not duplicated: a reply first met as a
|
|
64612
|
+
// bare notification record learns its parent and its pictures from the
|
|
64613
|
+
// full view when that arrives.
|
|
64586
64614
|
_mirrorPost(post, { via = null } = {}) {
|
|
64587
64615
|
const noteId = post.uri;
|
|
64588
|
-
const
|
|
64589
|
-
|
|
64616
|
+
const inReplyTo = post.record?.reply?.parent?.uri || null;
|
|
64617
|
+
const attachments = attachmentsOf2(post);
|
|
64618
|
+
const existing = this.store.getStatuses().find((s) => s.noteId === noteId);
|
|
64619
|
+
if (existing) {
|
|
64620
|
+
const patch = {
|
|
64621
|
+
...inReplyTo && !existing.inReplyTo ? { inReplyTo } : {},
|
|
64622
|
+
...attachments.length && !existing.attachments?.length ? { attachments } : {}
|
|
64623
|
+
};
|
|
64624
|
+
if (Object.keys(patch).length && typeof this.store.updateStatus === "function") {
|
|
64625
|
+
this.store.updateStatus(noteId, patch);
|
|
64626
|
+
}
|
|
64627
|
+
return { noteId, added: false };
|
|
64628
|
+
}
|
|
64590
64629
|
const actor = this._rememberAuthor(post.author);
|
|
64591
64630
|
if (this.store.isBlocked(actor)) return { noteId, added: false };
|
|
64592
64631
|
const text = post.record?.text || "";
|
|
64593
|
-
const images = post.embed?.images || [];
|
|
64594
64632
|
this.store.addStatus({
|
|
64595
64633
|
noteId,
|
|
64596
64634
|
actor,
|
|
@@ -64600,12 +64638,46 @@ var BskyFeed = class {
|
|
|
64600
64638
|
...post.cid ? { cid: post.cid } : {},
|
|
64601
64639
|
link: postUrl(noteId),
|
|
64602
64640
|
...via ? { via } : {},
|
|
64603
|
-
...
|
|
64604
|
-
|
|
64605
|
-
} : {}
|
|
64641
|
+
...inReplyTo ? { inReplyTo } : {},
|
|
64642
|
+
...attachments.length ? { attachments } : {}
|
|
64606
64643
|
});
|
|
64607
64644
|
return { noteId, added: true };
|
|
64608
64645
|
}
|
|
64646
|
+
// The conversation under one post, asked for when a post is opened: the
|
|
64647
|
+
// ancestors it hangs from and the replies beneath it, each mirrored with
|
|
64648
|
+
// the reply link the thread view is built from. View cache only, like the
|
|
64649
|
+
// timeline. Returns how many posts were new; a thread Bluesky will not
|
|
64650
|
+
// give (gone, blocked, rate-limited) adds none and is logged, not thrown.
|
|
64651
|
+
async mirrorThread(uri, { depth = 6 } = {}) {
|
|
64652
|
+
if (!this.atproto?.connected()) return 0;
|
|
64653
|
+
if (this.quietUntil && Date.now() < this.quietUntil) return 0;
|
|
64654
|
+
let out;
|
|
64655
|
+
try {
|
|
64656
|
+
out = await this.atproto.xrpc("app.bsky.feed.getPostThread", { params: { uri, depth, parentHeight: 40 } });
|
|
64657
|
+
} catch (e) {
|
|
64658
|
+
if (e.status === 429 || e.status >= 500) this._backOff(e.status, null);
|
|
64659
|
+
else this.log(`bskyfeed: thread ${uri}: ${e.message}`);
|
|
64660
|
+
return 0;
|
|
64661
|
+
}
|
|
64662
|
+
const self2 = this.atproto.read()?.did;
|
|
64663
|
+
const ownMirrors = new Set(this.store.getStatuses().map((s) => s.atproto?.uri).filter(Boolean));
|
|
64664
|
+
let added = 0;
|
|
64665
|
+
const take2 = (node) => {
|
|
64666
|
+
const post = node?.post;
|
|
64667
|
+
if (!post?.uri) return;
|
|
64668
|
+
if (post.author?.did === self2 && ownMirrors.has(post.uri)) return;
|
|
64669
|
+
if (this._mirrorPost(post).added) added++;
|
|
64670
|
+
};
|
|
64671
|
+
for (let up = out?.thread?.parent; up; up = up.parent) take2(up);
|
|
64672
|
+
const walk = (node, left) => {
|
|
64673
|
+
take2(node);
|
|
64674
|
+
if (left <= 0) return;
|
|
64675
|
+
for (const r of node?.replies || []) walk(r, left - 1);
|
|
64676
|
+
};
|
|
64677
|
+
walk(out?.thread, depth);
|
|
64678
|
+
if (added) this.log(`bskyfeed: +${added} from the thread under ${uri}`);
|
|
64679
|
+
return added;
|
|
64680
|
+
}
|
|
64609
64681
|
async sweep() {
|
|
64610
64682
|
if (!this.atproto?.connected()) return;
|
|
64611
64683
|
if (this.quietUntil && Date.now() < this.quietUntil) return;
|
|
@@ -64644,8 +64716,13 @@ var BskyFeed = class {
|
|
|
64644
64716
|
this.store.addNotification({ type: "follow", actor, bsky: true });
|
|
64645
64717
|
await this.onNotification?.(n, { actor });
|
|
64646
64718
|
} else if (n.reason === "mention" || n.reason === "reply") {
|
|
64647
|
-
|
|
64648
|
-
|
|
64719
|
+
let view = null;
|
|
64720
|
+
try {
|
|
64721
|
+
const got = await this.atproto.xrpc("app.bsky.feed.getPosts", { params: { uris: n.uri } });
|
|
64722
|
+
view = (got?.posts || [])[0] || null;
|
|
64723
|
+
} catch {
|
|
64724
|
+
}
|
|
64725
|
+
this._mirrorPost(view || { uri: n.uri, cid: n.cid, author: n.author, record: n.record, indexedAt: n.indexedAt });
|
|
64649
64726
|
this.store.addNotification({ type: "mention", actor, noteId: n.uri, bsky: true });
|
|
64650
64727
|
await this.onNotification?.(n, { actor });
|
|
64651
64728
|
}
|
|
@@ -65896,10 +65973,16 @@ async function handle6(api, ctx) {
|
|
|
65896
65973
|
api.store.setScheduled(sched);
|
|
65897
65974
|
return send(200, api.scheduledJson(entry));
|
|
65898
65975
|
}
|
|
65899
|
-
|
|
65900
|
-
|
|
65901
|
-
|
|
65902
|
-
|
|
65976
|
+
let note;
|
|
65977
|
+
try {
|
|
65978
|
+
note = await api.agent.publisher.publishNote(
|
|
65979
|
+
body.status,
|
|
65980
|
+
{ inReplyTo, attachments, visibility, spoilerText }
|
|
65981
|
+
);
|
|
65982
|
+
} catch (e) {
|
|
65983
|
+
if (e.code === "unaddressed") return send(422, { error: e.message });
|
|
65984
|
+
throw e;
|
|
65985
|
+
}
|
|
65903
65986
|
const s = api.store.getStatuses().find((x) => x.noteId === note.id);
|
|
65904
65987
|
return send(200, api.status(s));
|
|
65905
65988
|
}
|
|
@@ -65978,6 +66061,10 @@ async function handle6(api, ctx) {
|
|
|
65978
66061
|
const mContext = /^\/api\/v1\/statuses\/([a-f0-9]+)\/context$/.exec(pathname);
|
|
65979
66062
|
if (mContext) {
|
|
65980
66063
|
const noteUrl = api.lookup(mContext[1]).s?.noteId;
|
|
66064
|
+
const opened = noteUrl && api.store.getStatuses().find((x) => x.noteId === noteUrl);
|
|
66065
|
+
if (opened?.kind === "bsky" && typeof api.agent?.bskyfeed?.mirrorThread === "function") {
|
|
66066
|
+
await api.agent.bskyfeed.mirrorThread(noteUrl);
|
|
66067
|
+
}
|
|
65981
66068
|
const all = api.store.getStatuses();
|
|
65982
66069
|
const byId = new Map(all.map((s) => [s.noteId, s]));
|
|
65983
66070
|
const ancestors = [];
|
|
@@ -66413,8 +66500,8 @@ var TagFeed = class {
|
|
|
66413
66500
|
await this.intake.fetchAP(author).catch(() => {
|
|
66414
66501
|
});
|
|
66415
66502
|
}
|
|
66416
|
-
const { attachmentsOf:
|
|
66417
|
-
const attachments =
|
|
66503
|
+
const { attachmentsOf: attachmentsOf3, titledContent: titledContent2 } = await Promise.resolve().then(() => (init_wire(), wire_exports));
|
|
66504
|
+
const attachments = attachmentsOf3(note);
|
|
66418
66505
|
this.store.addStatus({
|
|
66419
66506
|
noteId,
|
|
66420
66507
|
actor: author,
|