fedipod 1.7.0 → 1.10.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/README.md +14 -3
- package/gateway.md +16 -0
- package/groups.md +1 -0
- package/lib/client/c2s.mjs +112 -64
- package/lib/client/masto/index.mjs +9 -1
- package/lib/client/masto/render.mjs +5 -3
- package/lib/client/masto/statuses.mjs +7 -4
- package/lib/client/oidc-auth.mjs +10 -3
- package/lib/core/as2.mjs +1 -1
- package/lib/core/contexts/anno.json +126 -0
- package/lib/core/contexts/index.mjs +4 -0
- package/lib/core/contexts/map.json +2 -1
- package/lib/core/deliver.mjs +24 -1
- package/lib/core/intake/activities.mjs +54 -0
- package/lib/core/intake/index.mjs +34 -5
- package/lib/core/intake/notes.mjs +7 -0
- package/lib/core/publisher/collections.mjs +7 -4
- package/lib/core/publisher/index.mjs +49 -27
- package/lib/core/publisher/notes.mjs +154 -7
- package/lib/core/publisher/questions.mjs +11 -7
- package/lib/core/publisher/restore.mjs +27 -2
- package/lib/core/social.mjs +1 -0
- package/lib/core/store.mjs +13 -0
- package/lib/core/wire.mjs +71 -20
- package/lib/device/admin/static.mjs +2 -2
- package/lib/device/admin/surface.mjs +11 -2
- package/lib/gateway/front-core.mjs +76 -6
- package/lib/gateway/gateway-core.mjs +40 -0
- package/lib/gateway/httpsig.mjs +7 -3
- package/lib/pod/actor.mjs +2 -2
- package/lib/pod/transport.mjs +9 -3
- package/package.json +1 -1
- package/run-agent.mjs +11 -0
- package/scripts/refresh-contexts.mjs +5 -0
- package/scripts/stage-site.mjs +1 -0
- package/web/admin/index.html +2 -0
- package/web/admin/upkeep.js +9 -1
- package/web/app/README.md +1 -1
- package/web/app/agent.mjs +12 -0
- package/web/app/boot.mjs +49 -10
- package/web/app/dist/boot.js +47 -7
- package/web/app/dist/boot.js.map +2 -2
- package/web/app/dist/sw.js +1604 -722
- package/web/app/dist/sw.js.map +4 -4
- package/web/app/index.html +16 -6
- package/web/app/site/_redirects +1 -0
- package/web/app/site/admin/index.html +2 -0
- package/web/app/site/admin/upkeep.js +9 -1
- package/web/app/site/boot.js +89 -37
- package/web/app/site/index.html +16 -6
- package/web/app/site/sw.js +1605 -723
- package/web/front/#new-account.html# +0 -43
- package/web/front/new-account.html~ +0 -50
package/web/app/dist/sw.js
CHANGED
|
@@ -9589,6 +9589,7 @@ __export(wire_exports, {
|
|
|
9589
9589
|
AS_CTX: () => AS_CTX,
|
|
9590
9590
|
DEFAULT_ROOT: () => DEFAULT_ROOT,
|
|
9591
9591
|
FOLLOWERS_PAGE_SIZE: () => FOLLOWERS_PAGE_SIZE,
|
|
9592
|
+
HASHTAG_RE: () => HASHTAG_RE,
|
|
9592
9593
|
MENTION_RE: () => MENTION_RE,
|
|
9593
9594
|
OUTBOX_PAGE_SIZE: () => OUTBOX_PAGE_SIZE,
|
|
9594
9595
|
PUBLIC: () => PUBLIC,
|
|
@@ -9596,6 +9597,7 @@ __export(wire_exports, {
|
|
|
9596
9597
|
acceptActivity: () => acceptActivity,
|
|
9597
9598
|
actorDoc: () => actorDoc,
|
|
9598
9599
|
addRemoveActivity: () => addRemoveActivity,
|
|
9600
|
+
addressing: () => addressing,
|
|
9599
9601
|
announceActivity: () => announceActivity,
|
|
9600
9602
|
apUrls: () => apUrls2,
|
|
9601
9603
|
assertionKeyId: () => assertionKeyId,
|
|
@@ -9614,6 +9616,7 @@ __export(wire_exports, {
|
|
|
9614
9616
|
followersPageId: () => followersPageId,
|
|
9615
9617
|
followersPaging: () => followersPaging,
|
|
9616
9618
|
followsNeedApproval: () => followsNeedApproval,
|
|
9619
|
+
hashtagsIn: () => hashtagsIn,
|
|
9617
9620
|
hostMeta: () => hostMeta,
|
|
9618
9621
|
jrd: () => jrd,
|
|
9619
9622
|
likeActivity: () => likeActivity,
|
|
@@ -9626,11 +9629,13 @@ __export(wire_exports, {
|
|
|
9626
9629
|
orderedCollection: () => orderedCollection,
|
|
9627
9630
|
outboxHead: () => outboxHead,
|
|
9628
9631
|
outboxItemId: () => outboxItemId,
|
|
9632
|
+
outboxLocalItem: () => outboxLocalItem,
|
|
9629
9633
|
outboxPage: () => outboxPage,
|
|
9630
9634
|
outboxPageCount: () => outboxPageCount,
|
|
9631
9635
|
outboxPageId: () => outboxPageId,
|
|
9632
9636
|
outboxPages: () => outboxPages,
|
|
9633
9637
|
outboxPaging: () => outboxPaging,
|
|
9638
|
+
outboxWireItem: () => outboxWireItem,
|
|
9634
9639
|
pageItems: () => pageItems,
|
|
9635
9640
|
profilePageHtml: () => profilePageHtml,
|
|
9636
9641
|
publicHandle: () => publicHandle,
|
|
@@ -9638,6 +9643,7 @@ __export(wire_exports, {
|
|
|
9638
9643
|
rejectActivity: () => rejectActivity,
|
|
9639
9644
|
repliesId: () => repliesId,
|
|
9640
9645
|
sanitizeHtml: () => sanitizeHtml,
|
|
9646
|
+
tagHref: () => tagHref,
|
|
9641
9647
|
titledContent: () => titledContent,
|
|
9642
9648
|
tombstoneDoc: () => tombstoneDoc,
|
|
9643
9649
|
undoActivity: () => undoActivity,
|
|
@@ -9658,11 +9664,14 @@ function hostMeta(base) {
|
|
|
9658
9664
|
</XRD>
|
|
9659
9665
|
`;
|
|
9660
9666
|
}
|
|
9661
|
-
function jrd({ handle: handle7, host, actor, aliases = [] }) {
|
|
9667
|
+
function jrd({ handle: handle7, host, actor, aliases = [], page: page2 = null }) {
|
|
9662
9668
|
return {
|
|
9663
9669
|
subject: `acct:${handle7}@${host}`,
|
|
9664
9670
|
...aliases.length ? { aliases } : {},
|
|
9665
|
-
links: [
|
|
9671
|
+
links: [
|
|
9672
|
+
{ rel: "self", type: "application/activity+json", href: actor },
|
|
9673
|
+
...page2 ? [{ rel: "http://webfinger.net/rel/profile-page", type: "text/html", href: page2 }] : []
|
|
9674
|
+
]
|
|
9666
9675
|
};
|
|
9667
9676
|
}
|
|
9668
9677
|
function followsNeedApproval(config = {}) {
|
|
@@ -9925,7 +9934,7 @@ function outboxPage(outboxId, n, items) {
|
|
|
9925
9934
|
id: outboxPageId(outboxId, n),
|
|
9926
9935
|
type: "OrderedCollectionPage",
|
|
9927
9936
|
partOf: outboxId,
|
|
9928
|
-
orderedItems: items,
|
|
9937
|
+
orderedItems: items.map(outboxWireItem),
|
|
9929
9938
|
...n > 1 ? { next: outboxPageId(outboxId, n - 1) } : {}
|
|
9930
9939
|
};
|
|
9931
9940
|
}
|
|
@@ -10045,8 +10054,27 @@ function attachmentsOf(note) {
|
|
|
10045
10054
|
function mentionsIn(text) {
|
|
10046
10055
|
return [...new Set(String(text).match(MENTION_RE) || [])].map((m) => m.slice(1));
|
|
10047
10056
|
}
|
|
10048
|
-
function
|
|
10057
|
+
function hashtagsIn(text) {
|
|
10058
|
+
const seen = /* @__PURE__ */ new Map();
|
|
10059
|
+
for (const m of String(text).matchAll(HASHTAG_RE)) {
|
|
10060
|
+
const key = m[2].toLowerCase();
|
|
10061
|
+
if (!seen.has(key)) seen.set(key, m[2]);
|
|
10062
|
+
}
|
|
10063
|
+
return [...seen.values()];
|
|
10064
|
+
}
|
|
10065
|
+
function tagHref(urls, name) {
|
|
10066
|
+
return urls.profileHtml + "?tag=" + encodeURIComponent(String(name).toLowerCase());
|
|
10067
|
+
}
|
|
10068
|
+
function contentHtml(text, mentions = [], hashtags = []) {
|
|
10049
10069
|
let html = String(text).replace(/[&<>]/g, (c) => HTML_ESCAPES[c]);
|
|
10070
|
+
if (hashtags.length) {
|
|
10071
|
+
const byName = new Map(hashtags.filter((h) => h?.name && h?.href).map((h) => [h.name.toLowerCase(), h.href]));
|
|
10072
|
+
html = html.replace(HASHTAG_RE, (all, lead, name) => {
|
|
10073
|
+
const href = byName.get(name.toLowerCase());
|
|
10074
|
+
if (!href) return all;
|
|
10075
|
+
return `${lead}<a href="${String(href).replace(/[&<>"]/g, (c) => HTML_ESCAPES[c] || """)}" class="mention hashtag" rel="tag">#<span>${name}</span></a>`;
|
|
10076
|
+
});
|
|
10077
|
+
}
|
|
10050
10078
|
for (const m of mentions) {
|
|
10051
10079
|
if (!m?.handle || !m?.actor) continue;
|
|
10052
10080
|
const href = String(m.page || m.actor).replace(/[&<>"]/g, (c) => HTML_ESCAPES[c] || """);
|
|
@@ -10056,6 +10084,14 @@ function contentHtml(text, mentions = []) {
|
|
|
10056
10084
|
}
|
|
10057
10085
|
return "<p>" + html.replace(/\n+/g, "</p><p>") + "</p>";
|
|
10058
10086
|
}
|
|
10087
|
+
function addressing(urls, visibility, who = []) {
|
|
10088
|
+
return {
|
|
10089
|
+
public: { to: [PUBLIC], cc: [urls.followers, ...who] },
|
|
10090
|
+
unlisted: { to: [urls.followers], cc: [PUBLIC, ...who] },
|
|
10091
|
+
private: { to: [urls.followers], cc: who },
|
|
10092
|
+
direct: { to: who, cc: [] }
|
|
10093
|
+
}[visibility] || { to: [PUBLIC], cc: [urls.followers, ...who] };
|
|
10094
|
+
}
|
|
10059
10095
|
function noteDoc({
|
|
10060
10096
|
urls,
|
|
10061
10097
|
slug,
|
|
@@ -10066,33 +10102,35 @@ function noteDoc({
|
|
|
10066
10102
|
mentions = [],
|
|
10067
10103
|
visibility = "public",
|
|
10068
10104
|
summary = null,
|
|
10105
|
+
sensitive = false,
|
|
10069
10106
|
updated = null,
|
|
10070
|
-
container = null
|
|
10107
|
+
container = null,
|
|
10108
|
+
also = []
|
|
10071
10109
|
}) {
|
|
10072
10110
|
const id = (container || urls.notes) + slug;
|
|
10073
10111
|
const who = mentions.map((m) => m.actor);
|
|
10074
|
-
const
|
|
10075
|
-
|
|
10076
|
-
|
|
10077
|
-
private: { to: [urls.followers], cc: who },
|
|
10078
|
-
direct: { to: who, cc: [] }
|
|
10079
|
-
}[visibility] || { to: [PUBLIC], cc: [urls.followers, ...who] };
|
|
10112
|
+
const extra = visibility === "direct" ? [] : also.filter((a) => a && !who.includes(a));
|
|
10113
|
+
const addressed = addressing(urls, visibility, [...who, ...extra]);
|
|
10114
|
+
const hashtags = hashtagsIn(content).map((name) => ({ name, href: tagHref(urls, name) }));
|
|
10080
10115
|
const note = {
|
|
10081
10116
|
"@context": AS_CTX,
|
|
10082
10117
|
id,
|
|
10083
10118
|
type: "Note",
|
|
10084
10119
|
attributedTo: urls.actor,
|
|
10085
|
-
content: contentHtml(content, mentions),
|
|
10120
|
+
content: contentHtml(content, mentions, hashtags),
|
|
10086
10121
|
published,
|
|
10087
|
-
to:
|
|
10088
|
-
cc:
|
|
10122
|
+
to: addressed.to,
|
|
10123
|
+
cc: addressed.cc,
|
|
10089
10124
|
replies: repliesId(id)
|
|
10090
10125
|
};
|
|
10091
10126
|
if (summary) note.summary = summary;
|
|
10127
|
+
if (summary || sensitive) note.sensitive = true;
|
|
10092
10128
|
if (updated) note.updated = updated;
|
|
10093
|
-
|
|
10094
|
-
|
|
10095
|
-
|
|
10129
|
+
const tag = [
|
|
10130
|
+
...mentions.map((m) => ({ type: "Mention", href: m.actor, name: "@" + m.handle })),
|
|
10131
|
+
...hashtags.map((h) => ({ type: "Hashtag", href: h.href, name: "#" + h.name }))
|
|
10132
|
+
];
|
|
10133
|
+
if (tag.length) note.tag = tag;
|
|
10096
10134
|
if (inReplyTo) note.inReplyTo = inReplyTo;
|
|
10097
10135
|
if (attachments?.length) {
|
|
10098
10136
|
note.attachment = attachments.map((a) => ({
|
|
@@ -10270,7 +10308,7 @@ function addRemoveActivity({ urls, type, object, target, serial }) {
|
|
|
10270
10308
|
target
|
|
10271
10309
|
};
|
|
10272
10310
|
}
|
|
10273
|
-
var import_sanitize_html, AS_CTX, SEC_CTX, PUBLIC, DEFAULT_ROOT, assertionKeyId, OUTBOX_PAGE_SIZE, outboxPageId, outboxPageCount, outboxItemId, FOLLOWERS_PAGE_SIZE, followersPageId, followersPageCount, ALLOWED_TAGS, ALLOWED_ATTRS, MAX_ATTACHMENTS, MAX_ATTACHMENT_URL, attachmentUrl, HTML_ESCAPES, MENTION_RE;
|
|
10311
|
+
var import_sanitize_html, AS_CTX, SEC_CTX, PUBLIC, DEFAULT_ROOT, assertionKeyId, OUTBOX_PAGE_SIZE, outboxPageId, outboxPageCount, outboxItemId, outboxWireItem, outboxLocalItem, FOLLOWERS_PAGE_SIZE, followersPageId, followersPageCount, ALLOWED_TAGS, ALLOWED_ATTRS, MAX_ATTACHMENTS, MAX_ATTACHMENT_URL, attachmentUrl, HTML_ESCAPES, MENTION_RE, HASHTAG_RE;
|
|
10274
10312
|
var init_wire = __esm({
|
|
10275
10313
|
"lib/core/wire.mjs"() {
|
|
10276
10314
|
init_urls();
|
|
@@ -10285,6 +10323,8 @@ var init_wire = __esm({
|
|
|
10285
10323
|
outboxPageId = (outboxId, n) => `${outboxId}-${n}`;
|
|
10286
10324
|
outboxPageCount = (total) => Math.max(1, Math.ceil(total / OUTBOX_PAGE_SIZE));
|
|
10287
10325
|
outboxItemId = (i) => typeof i === "string" ? i : i?.id || null;
|
|
10326
|
+
outboxWireItem = (i) => typeof i === "string" ? createActivityId(i) : i;
|
|
10327
|
+
outboxLocalItem = (i) => typeof i === "string" && i.endsWith("-create") ? i.slice(0, -"-create".length) : i;
|
|
10288
10328
|
FOLLOWERS_PAGE_SIZE = 20;
|
|
10289
10329
|
followersPageId = (id, n) => `${id}-${n}`;
|
|
10290
10330
|
followersPageCount = (total) => Math.max(1, Math.ceil(total / FOLLOWERS_PAGE_SIZE));
|
|
@@ -10325,6 +10365,7 @@ var init_wire = __esm({
|
|
|
10325
10365
|
};
|
|
10326
10366
|
HTML_ESCAPES = { "&": "&", "<": "<", ">": ">" };
|
|
10327
10367
|
MENTION_RE = /@([A-Za-z0-9_.-]+)@([A-Za-z0-9.-]+\.[A-Za-z]{2,}(?::\d+)?)/g;
|
|
10368
|
+
HASHTAG_RE = /(^|[^\/\w)])#([\p{L}\p{N}_]*[\p{L}_][\p{L}\p{N}_]*)/gu;
|
|
10328
10369
|
}
|
|
10329
10370
|
});
|
|
10330
10371
|
|
|
@@ -24128,20 +24169,20 @@ var require_yallist = __commonJS({
|
|
|
24128
24169
|
return acc;
|
|
24129
24170
|
};
|
|
24130
24171
|
Yallist.prototype.toArray = function() {
|
|
24131
|
-
var
|
|
24172
|
+
var arr2 = new Array(this.length);
|
|
24132
24173
|
for (var i = 0, walker = this.head; walker !== null; i++) {
|
|
24133
|
-
|
|
24174
|
+
arr2[i] = walker.value;
|
|
24134
24175
|
walker = walker.next;
|
|
24135
24176
|
}
|
|
24136
|
-
return
|
|
24177
|
+
return arr2;
|
|
24137
24178
|
};
|
|
24138
24179
|
Yallist.prototype.toArrayReverse = function() {
|
|
24139
|
-
var
|
|
24180
|
+
var arr2 = new Array(this.length);
|
|
24140
24181
|
for (var i = 0, walker = this.tail; walker !== null; i++) {
|
|
24141
|
-
|
|
24182
|
+
arr2[i] = walker.value;
|
|
24142
24183
|
walker = walker.prev;
|
|
24143
24184
|
}
|
|
24144
|
-
return
|
|
24185
|
+
return arr2;
|
|
24145
24186
|
};
|
|
24146
24187
|
Yallist.prototype.slice = function(from, to) {
|
|
24147
24188
|
to = to || this.length;
|
|
@@ -24469,11 +24510,11 @@ var require_lru_cache = __commonJS({
|
|
|
24469
24510
|
del(key) {
|
|
24470
24511
|
del(this, this[CACHE].get(key));
|
|
24471
24512
|
}
|
|
24472
|
-
load(
|
|
24513
|
+
load(arr2) {
|
|
24473
24514
|
this.reset();
|
|
24474
24515
|
const now = Date.now();
|
|
24475
|
-
for (let l =
|
|
24476
|
-
const hit =
|
|
24516
|
+
for (let l = arr2.length - 1; l >= 0; l--) {
|
|
24517
|
+
const hit = arr2[l];
|
|
24477
24518
|
const expiresAt = hit.e || 0;
|
|
24478
24519
|
if (expiresAt === 0)
|
|
24479
24520
|
this.set(hit.k, hit.v);
|
|
@@ -30207,26 +30248,26 @@ var require_base64_js = __commonJS({
|
|
|
30207
30248
|
var lens = getLens(b642);
|
|
30208
30249
|
var validLen = lens[0];
|
|
30209
30250
|
var placeHoldersLen = lens[1];
|
|
30210
|
-
var
|
|
30251
|
+
var arr2 = new Arr(_byteLength(b642, validLen, placeHoldersLen));
|
|
30211
30252
|
var curByte = 0;
|
|
30212
30253
|
var len2 = placeHoldersLen > 0 ? validLen - 4 : validLen;
|
|
30213
30254
|
var i2;
|
|
30214
30255
|
for (i2 = 0; i2 < len2; i2 += 4) {
|
|
30215
30256
|
tmp = revLookup[b642.charCodeAt(i2)] << 18 | revLookup[b642.charCodeAt(i2 + 1)] << 12 | revLookup[b642.charCodeAt(i2 + 2)] << 6 | revLookup[b642.charCodeAt(i2 + 3)];
|
|
30216
|
-
|
|
30217
|
-
|
|
30218
|
-
|
|
30257
|
+
arr2[curByte++] = tmp >> 16 & 255;
|
|
30258
|
+
arr2[curByte++] = tmp >> 8 & 255;
|
|
30259
|
+
arr2[curByte++] = tmp & 255;
|
|
30219
30260
|
}
|
|
30220
30261
|
if (placeHoldersLen === 2) {
|
|
30221
30262
|
tmp = revLookup[b642.charCodeAt(i2)] << 2 | revLookup[b642.charCodeAt(i2 + 1)] >> 4;
|
|
30222
|
-
|
|
30263
|
+
arr2[curByte++] = tmp & 255;
|
|
30223
30264
|
}
|
|
30224
30265
|
if (placeHoldersLen === 1) {
|
|
30225
30266
|
tmp = revLookup[b642.charCodeAt(i2)] << 10 | revLookup[b642.charCodeAt(i2 + 1)] << 4 | revLookup[b642.charCodeAt(i2 + 2)] >> 2;
|
|
30226
|
-
|
|
30227
|
-
|
|
30267
|
+
arr2[curByte++] = tmp >> 8 & 255;
|
|
30268
|
+
arr2[curByte++] = tmp & 255;
|
|
30228
30269
|
}
|
|
30229
|
-
return
|
|
30270
|
+
return arr2;
|
|
30230
30271
|
}
|
|
30231
30272
|
function tripletToBase64(num) {
|
|
30232
30273
|
return lookup2[num >> 18 & 63] + lookup2[num >> 12 & 63] + lookup2[num >> 6 & 63] + lookup2[num & 63];
|
|
@@ -30368,13 +30409,13 @@ var require_buffer = __commonJS({
|
|
|
30368
30409
|
}
|
|
30369
30410
|
function typedArraySupport() {
|
|
30370
30411
|
try {
|
|
30371
|
-
const
|
|
30412
|
+
const arr2 = new Uint8Array(1);
|
|
30372
30413
|
const proto = { foo: function() {
|
|
30373
30414
|
return 42;
|
|
30374
30415
|
} };
|
|
30375
30416
|
Object.setPrototypeOf(proto, Uint8Array.prototype);
|
|
30376
|
-
Object.setPrototypeOf(
|
|
30377
|
-
return
|
|
30417
|
+
Object.setPrototypeOf(arr2, proto);
|
|
30418
|
+
return arr2.foo() === 42;
|
|
30378
30419
|
} catch (e) {
|
|
30379
30420
|
return false;
|
|
30380
30421
|
}
|
|
@@ -30894,14 +30935,14 @@ var require_buffer = __commonJS({
|
|
|
30894
30935
|
}
|
|
30895
30936
|
throw new TypeError("val must be string, number or Buffer");
|
|
30896
30937
|
}
|
|
30897
|
-
function arrayIndexOf(
|
|
30938
|
+
function arrayIndexOf(arr2, val, byteOffset, encoding, dir) {
|
|
30898
30939
|
let indexSize = 1;
|
|
30899
|
-
let arrLength =
|
|
30940
|
+
let arrLength = arr2.length;
|
|
30900
30941
|
let valLength = val.length;
|
|
30901
30942
|
if (encoding !== void 0) {
|
|
30902
30943
|
encoding = String(encoding).toLowerCase();
|
|
30903
30944
|
if (encoding === "ucs2" || encoding === "ucs-2" || encoding === "utf16le" || encoding === "utf-16le") {
|
|
30904
|
-
if (
|
|
30945
|
+
if (arr2.length < 2 || val.length < 2) {
|
|
30905
30946
|
return -1;
|
|
30906
30947
|
}
|
|
30907
30948
|
indexSize = 2;
|
|
@@ -30921,7 +30962,7 @@ var require_buffer = __commonJS({
|
|
|
30921
30962
|
if (dir) {
|
|
30922
30963
|
let foundIndex = -1;
|
|
30923
30964
|
for (i = byteOffset; i < arrLength; i++) {
|
|
30924
|
-
if (read2(
|
|
30965
|
+
if (read2(arr2, i) === read2(val, foundIndex === -1 ? 0 : i - foundIndex)) {
|
|
30925
30966
|
if (foundIndex === -1) foundIndex = i;
|
|
30926
30967
|
if (i - foundIndex + 1 === valLength) return foundIndex * indexSize;
|
|
30927
30968
|
} else {
|
|
@@ -30934,7 +30975,7 @@ var require_buffer = __commonJS({
|
|
|
30934
30975
|
for (i = byteOffset; i >= 0; i--) {
|
|
30935
30976
|
let found = true;
|
|
30936
30977
|
for (let j = 0; j < valLength; j++) {
|
|
30937
|
-
if (read2(
|
|
30978
|
+
if (read2(arr2, i + j) !== read2(val, j)) {
|
|
30938
30979
|
found = false;
|
|
30939
30980
|
break;
|
|
30940
30981
|
}
|
|
@@ -32646,11 +32687,12 @@ async function verifyHttpSignature(request, { documentLoader, keyCache, timeWind
|
|
|
32646
32687
|
} catch {
|
|
32647
32688
|
result = null;
|
|
32648
32689
|
}
|
|
32690
|
+
const method = request.headers.has("signature-input") ? "rfc9421" : "draft-cavage";
|
|
32649
32691
|
if (result?.verified) {
|
|
32650
32692
|
const key = result.key;
|
|
32651
32693
|
return {
|
|
32652
32694
|
verified: true,
|
|
32653
|
-
method
|
|
32695
|
+
method,
|
|
32654
32696
|
keyId: key.id?.href ?? null,
|
|
32655
32697
|
actor: key.ownerId?.href ?? null,
|
|
32656
32698
|
reason: null,
|
|
@@ -32671,7 +32713,7 @@ async function verifyHttpSignature(request, { documentLoader, keyCache, timeWind
|
|
|
32671
32713
|
if (kind === "keyFetchError") {
|
|
32672
32714
|
return {
|
|
32673
32715
|
verified: false,
|
|
32674
|
-
method
|
|
32716
|
+
method,
|
|
32675
32717
|
keyId: null,
|
|
32676
32718
|
actor: null,
|
|
32677
32719
|
reason: "key-unfetchable",
|
|
@@ -32680,7 +32722,7 @@ async function verifyHttpSignature(request, { documentLoader, keyCache, timeWind
|
|
|
32680
32722
|
}
|
|
32681
32723
|
return {
|
|
32682
32724
|
verified: false,
|
|
32683
|
-
method
|
|
32725
|
+
method,
|
|
32684
32726
|
keyId: null,
|
|
32685
32727
|
actor: null,
|
|
32686
32728
|
reason: "bad-signature",
|
|
@@ -33960,11 +34002,11 @@ var require_src = __commonJS({
|
|
|
33960
34002
|
function serializeFunction(fn) {
|
|
33961
34003
|
return JSON.stringify(fn);
|
|
33962
34004
|
}
|
|
33963
|
-
function serializeArray(
|
|
34005
|
+
function serializeArray(arr2) {
|
|
33964
34006
|
let str = "[";
|
|
33965
|
-
const length =
|
|
34007
|
+
const length = arr2.length;
|
|
33966
34008
|
for (let i = 0; i < length; i++) {
|
|
33967
|
-
const val =
|
|
34009
|
+
const val = arr2[i];
|
|
33968
34010
|
if (i !== 0) str += ",";
|
|
33969
34011
|
str += serialize3(val);
|
|
33970
34012
|
}
|
|
@@ -34119,6 +34161,7 @@ function dropFollower(contacts, actor, why) {
|
|
|
34119
34161
|
}
|
|
34120
34162
|
var PodStore = class {
|
|
34121
34163
|
constructor({ storage = null, log: log2 = console.log } = {}) {
|
|
34164
|
+
this.lastSkipped = [];
|
|
34122
34165
|
this.storage = storage;
|
|
34123
34166
|
this.log = log2;
|
|
34124
34167
|
this.cache = /* @__PURE__ */ new Map();
|
|
@@ -34181,6 +34224,7 @@ var PodStore = class {
|
|
|
34181
34224
|
this.log(`state load ${name}: unparsable (${e.message})`);
|
|
34182
34225
|
}
|
|
34183
34226
|
}
|
|
34227
|
+
this.lastSkipped = skipped;
|
|
34184
34228
|
if (skipped.length) this.log(`state load skipped ${skipped.length}: ${skipped.join(", ")}`);
|
|
34185
34229
|
this.log(`state loaded: ${this.cache.size} doc(s) from ${this.base} (${fetched} re-fetched)`);
|
|
34186
34230
|
}
|
|
@@ -34475,6 +34519,14 @@ var PodStore = class {
|
|
|
34475
34519
|
// — not from the pod, not from the RDF. Unverified entries are dropped first
|
|
34476
34520
|
// now, so a flood can only ever evict itself, and a stranger's genuine
|
|
34477
34521
|
// favourite still shows up, which is what recording them at all is for.
|
|
34522
|
+
// What an Undo unsays: the favourite or boost it names, matched by a
|
|
34523
|
+
// predicate on what was recorded. Returns how many went.
|
|
34524
|
+
removeNotifications(pred) {
|
|
34525
|
+
const all = this.getNotifications();
|
|
34526
|
+
const kept = all.filter((n) => !pred(n));
|
|
34527
|
+
if (kept.length !== all.length) this.write("notifications.json", kept);
|
|
34528
|
+
return all.length - kept.length;
|
|
34529
|
+
}
|
|
34478
34530
|
addNotification(n) {
|
|
34479
34531
|
const all = this.getNotifications();
|
|
34480
34532
|
const id = node_crypto_default.createHash("sha256").update(JSON.stringify(n)).digest("hex").slice(0, 16);
|
|
@@ -36882,11 +36934,11 @@ function arrayToStatements(rdfFactory, subject, data) {
|
|
|
36882
36934
|
}, subject);
|
|
36883
36935
|
return statements;
|
|
36884
36936
|
}
|
|
36885
|
-
function ArrayIndexOf(
|
|
36886
|
-
var length =
|
|
36937
|
+
function ArrayIndexOf(arr2, item, i = 0) {
|
|
36938
|
+
var length = arr2.length;
|
|
36887
36939
|
if (i < 0) i = length + i;
|
|
36888
36940
|
for (; i < length; i++) {
|
|
36889
|
-
if (
|
|
36941
|
+
if (arr2[i] === item) {
|
|
36890
36942
|
return i;
|
|
36891
36943
|
}
|
|
36892
36944
|
}
|
|
@@ -45218,8 +45270,8 @@ async function writeProfilePage(pod, urls, html) {
|
|
|
45218
45270
|
await pod.put(urls.profileHtml, html, "text/html");
|
|
45219
45271
|
await pod.setAcl(urls.profileHtml, PUBLIC_READ2);
|
|
45220
45272
|
}
|
|
45221
|
-
function linkInWebIdProfile(pod, { actorUrl, accountName, kind = "person" }) {
|
|
45222
|
-
return pod.linkAccountInProfile({ actorUrl, accountName, kind });
|
|
45273
|
+
function linkInWebIdProfile(pod, { actorUrl, accountName, kind = "person", outbox = null }) {
|
|
45274
|
+
return pod.linkAccountInProfile({ actorUrl, accountName, kind, outbox });
|
|
45223
45275
|
}
|
|
45224
45276
|
|
|
45225
45277
|
// lib/pod/inbox.mjs
|
|
@@ -45427,8 +45479,9 @@ async function publishOutbox(publisher, outbox, { acls = false, force = false }
|
|
|
45427
45479
|
);
|
|
45428
45480
|
return wrote;
|
|
45429
45481
|
}
|
|
45430
|
-
function readPublishedOutbox(publisher) {
|
|
45431
|
-
|
|
45482
|
+
async function readPublishedOutbox(publisher) {
|
|
45483
|
+
const items = await readPublished(publisher.remote, publisher.urls);
|
|
45484
|
+
return Array.isArray(items) ? items.map(outboxLocalItem) : items;
|
|
45432
45485
|
}
|
|
45433
45486
|
async function publishFollowers(publisher, actors, { acls = false, force = false } = {}) {
|
|
45434
45487
|
const { urls } = publisher;
|
|
@@ -55489,6 +55542,126 @@ var miscellany_default = {
|
|
|
55489
55542
|
}
|
|
55490
55543
|
};
|
|
55491
55544
|
|
|
55545
|
+
// lib/core/contexts/anno.json
|
|
55546
|
+
var anno_default = {
|
|
55547
|
+
"@context": {
|
|
55548
|
+
oa: "http://www.w3.org/ns/oa#",
|
|
55549
|
+
dc: "http://purl.org/dc/elements/1.1/",
|
|
55550
|
+
dcterms: "http://purl.org/dc/terms/",
|
|
55551
|
+
dctypes: "http://purl.org/dc/dcmitype/",
|
|
55552
|
+
foaf: "http://xmlns.com/foaf/0.1/",
|
|
55553
|
+
rdf: "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
|
|
55554
|
+
rdfs: "http://www.w3.org/2000/01/rdf-schema#",
|
|
55555
|
+
skos: "http://www.w3.org/2004/02/skos/core#",
|
|
55556
|
+
xsd: "http://www.w3.org/2001/XMLSchema#",
|
|
55557
|
+
iana: "http://www.iana.org/assignments/relation/",
|
|
55558
|
+
owl: "http://www.w3.org/2002/07/owl#",
|
|
55559
|
+
as: "http://www.w3.org/ns/activitystreams#",
|
|
55560
|
+
schema: "http://schema.org/",
|
|
55561
|
+
id: { "@type": "@id", "@id": "@id" },
|
|
55562
|
+
type: { "@type": "@id", "@id": "@type" },
|
|
55563
|
+
Annotation: "oa:Annotation",
|
|
55564
|
+
Dataset: "dctypes:Dataset",
|
|
55565
|
+
Image: "dctypes:StillImage",
|
|
55566
|
+
Video: "dctypes:MovingImage",
|
|
55567
|
+
Audio: "dctypes:Sound",
|
|
55568
|
+
Text: "dctypes:Text",
|
|
55569
|
+
TextualBody: "oa:TextualBody",
|
|
55570
|
+
ResourceSelection: "oa:ResourceSelection",
|
|
55571
|
+
SpecificResource: "oa:SpecificResource",
|
|
55572
|
+
FragmentSelector: "oa:FragmentSelector",
|
|
55573
|
+
CssSelector: "oa:CssSelector",
|
|
55574
|
+
XPathSelector: "oa:XPathSelector",
|
|
55575
|
+
TextQuoteSelector: "oa:TextQuoteSelector",
|
|
55576
|
+
TextPositionSelector: "oa:TextPositionSelector",
|
|
55577
|
+
DataPositionSelector: "oa:DataPositionSelector",
|
|
55578
|
+
SvgSelector: "oa:SvgSelector",
|
|
55579
|
+
RangeSelector: "oa:RangeSelector",
|
|
55580
|
+
TimeState: "oa:TimeState",
|
|
55581
|
+
HttpRequestState: "oa:HttpRequestState",
|
|
55582
|
+
CssStylesheet: "oa:CssStyle",
|
|
55583
|
+
Choice: "oa:Choice",
|
|
55584
|
+
Person: "foaf:Person",
|
|
55585
|
+
Software: "as:Application",
|
|
55586
|
+
Organization: "foaf:Organization",
|
|
55587
|
+
AnnotationCollection: "as:OrderedCollection",
|
|
55588
|
+
AnnotationPage: "as:OrderedCollectionPage",
|
|
55589
|
+
Audience: "schema:Audience",
|
|
55590
|
+
Motivation: "oa:Motivation",
|
|
55591
|
+
bookmarking: "oa:bookmarking",
|
|
55592
|
+
classifying: "oa:classifying",
|
|
55593
|
+
commenting: "oa:commenting",
|
|
55594
|
+
describing: "oa:describing",
|
|
55595
|
+
editing: "oa:editing",
|
|
55596
|
+
highlighting: "oa:highlighting",
|
|
55597
|
+
identifying: "oa:identifying",
|
|
55598
|
+
linking: "oa:linking",
|
|
55599
|
+
moderating: "oa:moderating",
|
|
55600
|
+
questioning: "oa:questioning",
|
|
55601
|
+
replying: "oa:replying",
|
|
55602
|
+
reviewing: "oa:reviewing",
|
|
55603
|
+
assessing: "oa:assessing",
|
|
55604
|
+
tagging: "oa:tagging",
|
|
55605
|
+
auto: "oa:autoDirection",
|
|
55606
|
+
ltr: "oa:ltrDirection",
|
|
55607
|
+
rtl: "oa:rtlDirection",
|
|
55608
|
+
body: { "@type": "@id", "@id": "oa:hasBody" },
|
|
55609
|
+
target: { "@type": "@id", "@id": "oa:hasTarget" },
|
|
55610
|
+
source: { "@type": "@id", "@id": "oa:hasSource" },
|
|
55611
|
+
selector: { "@type": "@id", "@id": "oa:hasSelector" },
|
|
55612
|
+
state: { "@type": "@id", "@id": "oa:hasState" },
|
|
55613
|
+
scope: { "@type": "@id", "@id": "oa:hasScope" },
|
|
55614
|
+
refinedBy: { "@type": "@id", "@id": "oa:refinedBy" },
|
|
55615
|
+
startSelector: { "@type": "@id", "@id": "oa:hasStartSelector" },
|
|
55616
|
+
endSelector: { "@type": "@id", "@id": "oa:hasEndSelector" },
|
|
55617
|
+
renderedVia: { "@type": "@id", "@id": "oa:renderedVia" },
|
|
55618
|
+
creator: { "@type": "@id", "@id": "dcterms:creator" },
|
|
55619
|
+
generator: { "@type": "@id", "@id": "as:generator" },
|
|
55620
|
+
rights: { "@type": "@id", "@id": "dcterms:rights" },
|
|
55621
|
+
homepage: { "@type": "@id", "@id": "foaf:homepage" },
|
|
55622
|
+
via: { "@type": "@id", "@id": "oa:via" },
|
|
55623
|
+
canonical: { "@type": "@id", "@id": "oa:canonical" },
|
|
55624
|
+
stylesheet: { "@type": "@id", "@id": "oa:styledBy" },
|
|
55625
|
+
cached: { "@type": "@id", "@id": "oa:cachedSource" },
|
|
55626
|
+
conformsTo: { "@type": "@id", "@id": "dcterms:conformsTo" },
|
|
55627
|
+
items: { "@type": "@id", "@id": "as:items", "@container": "@list" },
|
|
55628
|
+
partOf: { "@type": "@id", "@id": "as:partOf" },
|
|
55629
|
+
first: { "@type": "@id", "@id": "as:first" },
|
|
55630
|
+
last: { "@type": "@id", "@id": "as:last" },
|
|
55631
|
+
next: { "@type": "@id", "@id": "as:next" },
|
|
55632
|
+
prev: { "@type": "@id", "@id": "as:prev" },
|
|
55633
|
+
audience: { "@type": "@id", "@id": "schema:audience" },
|
|
55634
|
+
motivation: { "@type": "@vocab", "@id": "oa:motivatedBy" },
|
|
55635
|
+
purpose: { "@type": "@vocab", "@id": "oa:hasPurpose" },
|
|
55636
|
+
textDirection: { "@type": "@vocab", "@id": "oa:textDirection" },
|
|
55637
|
+
accessibility: "schema:accessibilityFeature",
|
|
55638
|
+
bodyValue: "oa:bodyValue",
|
|
55639
|
+
format: "dc:format",
|
|
55640
|
+
language: "dc:language",
|
|
55641
|
+
processingLanguage: "oa:processingLanguage",
|
|
55642
|
+
value: "rdf:value",
|
|
55643
|
+
exact: "oa:exact",
|
|
55644
|
+
prefix: "oa:prefix",
|
|
55645
|
+
suffix: "oa:suffix",
|
|
55646
|
+
styleClass: "oa:styleClass",
|
|
55647
|
+
name: "foaf:name",
|
|
55648
|
+
email: "foaf:mbox",
|
|
55649
|
+
email_sha1: "foaf:mbox_sha1sum",
|
|
55650
|
+
nickname: "foaf:nick",
|
|
55651
|
+
label: "rdfs:label",
|
|
55652
|
+
created: { "@id": "dcterms:created", "@type": "xsd:dateTime" },
|
|
55653
|
+
modified: { "@id": "dcterms:modified", "@type": "xsd:dateTime" },
|
|
55654
|
+
generated: { "@id": "dcterms:issued", "@type": "xsd:dateTime" },
|
|
55655
|
+
sourceDate: { "@id": "oa:sourceDate", "@type": "xsd:dateTime" },
|
|
55656
|
+
sourceDateStart: { "@id": "oa:sourceDateStart", "@type": "xsd:dateTime" },
|
|
55657
|
+
sourceDateEnd: { "@id": "oa:sourceDateEnd", "@type": "xsd:dateTime" },
|
|
55658
|
+
start: { "@id": "oa:start", "@type": "xsd:nonNegativeInteger" },
|
|
55659
|
+
end: { "@id": "oa:end", "@type": "xsd:nonNegativeInteger" },
|
|
55660
|
+
total: { "@id": "as:totalItems", "@type": "xsd:nonNegativeInteger" },
|
|
55661
|
+
startIndex: { "@id": "as:startIndex", "@type": "xsd:nonNegativeInteger" }
|
|
55662
|
+
}
|
|
55663
|
+
};
|
|
55664
|
+
|
|
55492
55665
|
// lib/core/contexts/index.mjs
|
|
55493
55666
|
var CONTEXTS = {
|
|
55494
55667
|
"https://www.w3.org/ns/activitystreams": activitystreams_default,
|
|
@@ -55504,7 +55677,8 @@ var CONTEXTS = {
|
|
|
55504
55677
|
"https://w3id.org/fep/5711": fep_5711_default,
|
|
55505
55678
|
"https://join-lemmy.org/context.json": join_lemmy_default,
|
|
55506
55679
|
"http://joinmastodon.org/ns": joinmastodon_default,
|
|
55507
|
-
"https://purl.archive.org/miscellany": miscellany_default
|
|
55680
|
+
"https://purl.archive.org/miscellany": miscellany_default,
|
|
55681
|
+
"http://www.w3.org/ns/anno.jsonld": anno_default
|
|
55508
55682
|
};
|
|
55509
55683
|
|
|
55510
55684
|
// lib/core/graphview.mjs
|
|
@@ -55762,146 +55936,6 @@ async function readLenient(raw) {
|
|
|
55762
55936
|
}
|
|
55763
55937
|
}
|
|
55764
55938
|
|
|
55765
|
-
// lib/core/publisher/restore.mjs
|
|
55766
|
-
var ACCEPT_AP = 'application/activity+json, application/ld+json; profile="https://www.w3.org/ns/activitystreams"';
|
|
55767
|
-
var REBUILD_MAX_PER_RUN = 200;
|
|
55768
|
-
async function reconcileFollowers(publisher, contacts) {
|
|
55769
|
-
let published = [];
|
|
55770
|
-
try {
|
|
55771
|
-
published = await publisher.readPublishedFollowers() || [];
|
|
55772
|
-
} catch {
|
|
55773
|
-
return 0;
|
|
55774
|
-
}
|
|
55775
|
-
const known2 = new Set(contacts.followers.map((f) => f.actor));
|
|
55776
|
-
const removed = new Set((contacts.removedFollowers || []).map((r) => r.actor));
|
|
55777
|
-
const missing = published.filter((a) => typeof a === "string" && !known2.has(a) && !removed.has(a));
|
|
55778
|
-
if (!missing.length) return 0;
|
|
55779
|
-
let recovered = 0;
|
|
55780
|
-
for (const actor of missing.slice(0, 200)) {
|
|
55781
|
-
try {
|
|
55782
|
-
const res = await publisher.deliverer.signedFetch(actor, { headers: { accept: ACCEPT_AP } });
|
|
55783
|
-
if (!res.ok) continue;
|
|
55784
|
-
const { doc, view, degraded } = await readLenient(await res.json());
|
|
55785
|
-
const actorDoc2 = view ?? doc;
|
|
55786
|
-
if (degraded) publisher.log?.(`actor ${actor} grounded to read: ${degraded}`);
|
|
55787
|
-
if (!actorDoc2?.inbox) continue;
|
|
55788
|
-
contacts.followers.push({
|
|
55789
|
-
actor,
|
|
55790
|
-
inbox: actorDoc2.inbox,
|
|
55791
|
-
sharedInbox: actorDoc2.endpoints?.sharedInbox || null,
|
|
55792
|
-
recovered: true,
|
|
55793
|
-
// Said explicitly, because onUndo reads it: the pod publishes WHO
|
|
55794
|
-
// follows, never the id of the Follow that did it, so a recovered
|
|
55795
|
-
// record has nothing an Undo can be matched against and must not be
|
|
55796
|
-
// evictable by one naming anything at all.
|
|
55797
|
-
followId: null
|
|
55798
|
-
});
|
|
55799
|
-
recovered++;
|
|
55800
|
-
} catch {
|
|
55801
|
-
}
|
|
55802
|
-
}
|
|
55803
|
-
if (recovered) {
|
|
55804
|
-
publisher.store.setContacts(contacts);
|
|
55805
|
-
publisher.log(`reconciled ${recovered} follower(s) the pod knew about and this machine did not \u2014 a restored or copied state was behind`);
|
|
55806
|
-
}
|
|
55807
|
-
return recovered;
|
|
55808
|
-
}
|
|
55809
|
-
async function reconcileOutbox(publisher, outbox) {
|
|
55810
|
-
let published = [];
|
|
55811
|
-
try {
|
|
55812
|
-
published = await publisher.readPublishedOutbox() || [];
|
|
55813
|
-
} catch {
|
|
55814
|
-
return 0;
|
|
55815
|
-
}
|
|
55816
|
-
if (!Array.isArray(published) || !published.length) return 0;
|
|
55817
|
-
const idOf = (i) => typeof i === "string" ? i : i?.id || null;
|
|
55818
|
-
const known2 = new Set(outbox.map(idOf).filter(Boolean));
|
|
55819
|
-
const removed = new Set(publisher.store.read("outbox-removed.json", []).map((r) => r.id));
|
|
55820
|
-
const missing = published.filter((i) => {
|
|
55821
|
-
const id = idOf(i);
|
|
55822
|
-
return id && !known2.has(id) && !removed.has(id);
|
|
55823
|
-
});
|
|
55824
|
-
if (!missing.length) return 0;
|
|
55825
|
-
outbox.push(...missing);
|
|
55826
|
-
publisher.store.write("outbox.json", outbox);
|
|
55827
|
-
publisher.log(`reconciled ${missing.length} outbox entr(ies) the pod carried and this machine did not`);
|
|
55828
|
-
return missing.length;
|
|
55829
|
-
}
|
|
55830
|
-
async function rebuildStatuses(publisher, { fromNotes = false } = {}) {
|
|
55831
|
-
const { urls } = publisher;
|
|
55832
|
-
const ids = /* @__PURE__ */ new Set();
|
|
55833
|
-
const boosts = [];
|
|
55834
|
-
let indexed = false;
|
|
55835
|
-
const published = await publisher.readPublishedOutbox().catch(() => null);
|
|
55836
|
-
if (published) {
|
|
55837
|
-
indexed = true;
|
|
55838
|
-
for (const item of published) {
|
|
55839
|
-
if (typeof item === "string") {
|
|
55840
|
-
if (item.startsWith(urls.notes)) ids.add(item);
|
|
55841
|
-
} else if (item?.type === "Announce") boosts.push(item);
|
|
55842
|
-
}
|
|
55843
|
-
}
|
|
55844
|
-
if (fromNotes) {
|
|
55845
|
-
for (const child of await list2(publisher.remote, urls).catch(() => [])) {
|
|
55846
|
-
ids.add(child.url);
|
|
55847
|
-
}
|
|
55848
|
-
indexed = true;
|
|
55849
|
-
}
|
|
55850
|
-
if (!indexed) return { indexed: 0, recovered: 0, reblogs: 0, landed: false, why: "the pod would not answer for its outbox" };
|
|
55851
|
-
const statuses = publisher.store.getStatuses();
|
|
55852
|
-
const have = new Set(statuses.map((s) => s.noteId));
|
|
55853
|
-
const removed = new Set(publisher.store.read("outbox-removed.json", []).map((r) => r.id));
|
|
55854
|
-
const recovered = [];
|
|
55855
|
-
let budget = REBUILD_MAX_PER_RUN;
|
|
55856
|
-
for (const id of ids) {
|
|
55857
|
-
if (budget <= 0) {
|
|
55858
|
-
publisher.log(`rebuild: stopping at ${REBUILD_MAX_PER_RUN} this run \u2014 run it again for the rest`);
|
|
55859
|
-
break;
|
|
55860
|
-
}
|
|
55861
|
-
if (have.has(id) || removed.has(id)) continue;
|
|
55862
|
-
budget--;
|
|
55863
|
-
const note = await read(publisher.remote, id).catch(() => null);
|
|
55864
|
-
if (note?.type !== "Note" || note.id !== id || note.attributedTo !== urls.actor) continue;
|
|
55865
|
-
const attachments = attachmentsOf(note);
|
|
55866
|
-
const mentions = (Array.isArray(note.tag) ? note.tag : []).filter((t) => t?.type === "Mention" && t.href).map((t) => ({ href: t.href, name: t.name }));
|
|
55867
|
-
recovered.push({
|
|
55868
|
-
noteId: note.id,
|
|
55869
|
-
actor: urls.actor,
|
|
55870
|
-
content: note.content || "",
|
|
55871
|
-
published: note.published || null,
|
|
55872
|
-
...note.inReplyTo ? { inReplyTo: note.inReplyTo } : {},
|
|
55873
|
-
kind: "post",
|
|
55874
|
-
slug: note.id.slice(urls.notes.length),
|
|
55875
|
-
...attachments.length ? { attachments } : {},
|
|
55876
|
-
...mentions.length ? { mentions } : {},
|
|
55877
|
-
recovered: true
|
|
55878
|
-
});
|
|
55879
|
-
}
|
|
55880
|
-
const merged = [...statuses, ...recovered];
|
|
55881
|
-
let reblogs = 0;
|
|
55882
|
-
for (const act of boosts) {
|
|
55883
|
-
const object = typeof act.object === "string" ? act.object : act.object?.id;
|
|
55884
|
-
const s = object && merged.find((x) => x.noteId === object);
|
|
55885
|
-
if (!s || s.reblogged) continue;
|
|
55886
|
-
s.reblogged = true;
|
|
55887
|
-
s.announceActivity = act;
|
|
55888
|
-
reblogs++;
|
|
55889
|
-
}
|
|
55890
|
-
if (!recovered.length && !reblogs) return { indexed: ids.size, recovered: 0, reblogs: 0, landed: true };
|
|
55891
|
-
merged.sort((a, b) => String(b.published || "").localeCompare(String(a.published || "")));
|
|
55892
|
-
const kept = merged.slice(0, 1e3);
|
|
55893
|
-
publisher.store.write("statuses.json", kept);
|
|
55894
|
-
const landed = await publisher.store.commit();
|
|
55895
|
-
publisher.log(`rebuilt ${recovered.length} post(s) and ${reblogs} boost(s) from the pod${landed ? "" : " \u2014 THE STATE WRITE DID NOT LAND"}`);
|
|
55896
|
-
return {
|
|
55897
|
-
indexed: ids.size,
|
|
55898
|
-
recovered: recovered.length,
|
|
55899
|
-
reblogs,
|
|
55900
|
-
landed,
|
|
55901
|
-
dropped: Math.max(0, merged.length - kept.length)
|
|
55902
|
-
};
|
|
55903
|
-
}
|
|
55904
|
-
|
|
55905
55939
|
// lib/core/publisher/notes.mjs
|
|
55906
55940
|
init_node_crypto();
|
|
55907
55941
|
init_wire();
|
|
@@ -55954,6 +55988,35 @@ async function mentionsFor(publisher, content, inReplyTo) {
|
|
|
55954
55988
|
}
|
|
55955
55989
|
return mentions;
|
|
55956
55990
|
}
|
|
55991
|
+
async function replyTarget(publisher, inReplyTo, mentions = [], visibility = "public") {
|
|
55992
|
+
if (!inReplyTo || visibility === "direct" || !publisher.resolveActor) return null;
|
|
55993
|
+
const parent = publisher.store.getStatuses().find((s) => s.noteId === inReplyTo);
|
|
55994
|
+
let actor = parent?.actor || null;
|
|
55995
|
+
if (!parent) {
|
|
55996
|
+
const doc2 = await publisher.resolveActor(inReplyTo).catch(() => null);
|
|
55997
|
+
const by = doc2?.attributedTo;
|
|
55998
|
+
actor = typeof by === "string" ? by : (Array.isArray(by) ? by[0] : by)?.id || null;
|
|
55999
|
+
if (typeof actor === "object") actor = actor?.id || null;
|
|
56000
|
+
}
|
|
56001
|
+
if (!actor || actor === publisher.urls.actor) return null;
|
|
56002
|
+
if (mentions.some((m) => m.actor === actor)) return null;
|
|
56003
|
+
if (publisher.store.isBlocked?.(actor)) return null;
|
|
56004
|
+
const doc = await publisher.resolveActor(actor).catch(() => null);
|
|
56005
|
+
const inbox = doc?.endpoints?.sharedInbox || doc?.inbox || null;
|
|
56006
|
+
if (!inbox) publisher.log(`reply: no inbox found for ${actor} \u2014 they get it only through followers`);
|
|
56007
|
+
return { actor, inbox };
|
|
56008
|
+
}
|
|
56009
|
+
async function inboxesFor(publisher, actorIds = []) {
|
|
56010
|
+
const out = [];
|
|
56011
|
+
for (const id of [...new Set(actorIds.filter((a) => typeof a === "string" && a && a !== publisher.urls.actor))]) {
|
|
56012
|
+
if (!publisher.resolveActor) break;
|
|
56013
|
+
const doc = await publisher.resolveActor(id).catch(() => null);
|
|
56014
|
+
const inbox = doc?.endpoints?.sharedInbox || doc?.inbox;
|
|
56015
|
+
if (inbox) out.push(inbox);
|
|
56016
|
+
else publisher.log(`addressed actor ${id} has no inbox the agent can find \u2014 not delivered to`);
|
|
56017
|
+
}
|
|
56018
|
+
return out;
|
|
56019
|
+
}
|
|
55957
56020
|
function assertDirectAddressed(content, mentions) {
|
|
55958
56021
|
const wanted = [...new Set(mentionsIn(content))];
|
|
55959
56022
|
const missing = wanted.filter((h) => !mentions.some((m) => m.handle === h));
|
|
@@ -55965,7 +56028,21 @@ function assertDirectAddressed(content, mentions) {
|
|
|
55965
56028
|
e.code = "unaddressed";
|
|
55966
56029
|
throw e;
|
|
55967
56030
|
}
|
|
55968
|
-
|
|
56031
|
+
var SLUG_OK = /^[A-Za-z0-9._-]{1,64}$/u;
|
|
56032
|
+
var safeSlug = (s) => typeof s === "string" && SLUG_OK.test(s) && !/^\.+$/u.test(s) ? s : null;
|
|
56033
|
+
async function slugFor(publisher, container, wanted, published) {
|
|
56034
|
+
const name = safeSlug(wanted);
|
|
56035
|
+
if (name && !await read(publisher.remote, container + name).catch(() => null)) return name;
|
|
56036
|
+
return published.slice(0, 10) + "-" + node_crypto_default.randomBytes(4).toString("hex");
|
|
56037
|
+
}
|
|
56038
|
+
function rowContent(obj) {
|
|
56039
|
+
if (typeof obj?.content === "string" && obj.content.trim()) return sanitizeHtml(obj.content);
|
|
56040
|
+
const plain = [obj?.bodyValue, obj?.name, obj?.summary].find((v) => typeof v === "string" && v.trim());
|
|
56041
|
+
if (plain) return contentHtml(plain);
|
|
56042
|
+
const esc = (v) => String(v).replace(/[&<>"]/gu, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[c]);
|
|
56043
|
+
return `<p><a href="${esc(obj?.id || "")}">${esc(obj?.type || "object")}</a></p>`;
|
|
56044
|
+
}
|
|
56045
|
+
async function publishNote(publisher, content, { inReplyTo, attachments, visibility = "public", spoilerText = null, sensitive = false, slug: wanted = null, also = [], deliverTo = [] } = {}) {
|
|
55969
56046
|
const { urls } = publisher;
|
|
55970
56047
|
const priv = visibility === "private" || visibility === "direct";
|
|
55971
56048
|
if (priv) {
|
|
@@ -55973,9 +56050,10 @@ async function publishNote(publisher, content, { inReplyTo, attachments, visibil
|
|
|
55973
56050
|
if (ready !== true) throw new Error(ready);
|
|
55974
56051
|
}
|
|
55975
56052
|
const published = (/* @__PURE__ */ new Date()).toISOString();
|
|
55976
|
-
const slug =
|
|
56053
|
+
const slug = await slugFor(publisher, priv ? urls.privateNotes : urls.notes, wanted, published);
|
|
55977
56054
|
const mentions = await publisher._mentionsFor(content, inReplyTo);
|
|
55978
56055
|
if (visibility === "direct") assertDirectAddressed(content, mentions);
|
|
56056
|
+
const reply = await replyTarget(publisher, inReplyTo, mentions, visibility);
|
|
55979
56057
|
const note = noteDoc({
|
|
55980
56058
|
urls,
|
|
55981
56059
|
slug,
|
|
@@ -55986,8 +56064,11 @@ async function publishNote(publisher, content, { inReplyTo, attachments, visibil
|
|
|
55986
56064
|
mentions,
|
|
55987
56065
|
visibility,
|
|
55988
56066
|
summary: spoilerText,
|
|
55989
|
-
|
|
56067
|
+
sensitive,
|
|
56068
|
+
container: priv ? urls.privateNotes : urls.notes,
|
|
56069
|
+
also: [...also, ...reply ? [reply.actor] : []]
|
|
55990
56070
|
});
|
|
56071
|
+
const addressedInboxes = await inboxesFor(publisher, [...also, ...deliverTo]);
|
|
55991
56072
|
await write4(publisher.remote, note.id, note);
|
|
55992
56073
|
await writeEmptyReplies(
|
|
55993
56074
|
publisher.remote,
|
|
@@ -56006,15 +56087,19 @@ async function publishNote(publisher, content, { inReplyTo, attachments, visibil
|
|
|
56006
56087
|
text: content,
|
|
56007
56088
|
visibility,
|
|
56008
56089
|
...spoilerText ? { spoiler: spoilerText } : {},
|
|
56090
|
+
...note.sensitive ? { sensitive: true } : {},
|
|
56009
56091
|
...attachments?.length ? { attachments } : {},
|
|
56010
|
-
...note
|
|
56092
|
+
...rowTags(note)
|
|
56011
56093
|
});
|
|
56094
|
+
if (await publisher.store.commit?.() === false) publisher.log(`post published but its timeline row was refused: ${note.id}`);
|
|
56012
56095
|
const create = createActivity(note, urls);
|
|
56013
56096
|
await writeCreate(publisher.remote, create.id, create);
|
|
56014
56097
|
const contacts = publisher.store.getContacts();
|
|
56015
56098
|
const inboxes = [...new Set([
|
|
56016
56099
|
...visibility === "direct" ? [] : contacts.followers.map((f) => f.sharedInbox || f.inbox),
|
|
56017
|
-
...mentions.map((m) => m.inbox)
|
|
56100
|
+
...mentions.map((m) => m.inbox),
|
|
56101
|
+
reply?.inbox,
|
|
56102
|
+
...addressedInboxes
|
|
56018
56103
|
].filter(Boolean))];
|
|
56019
56104
|
await publisher.deliverer.deliverToAll(inboxes, create);
|
|
56020
56105
|
publisher.log(`note published: ${note.id} \u2192 ${inboxes.length} inbox(es)`);
|
|
@@ -56033,7 +56118,69 @@ async function publishNote(publisher, content, { inReplyTo, attachments, visibil
|
|
|
56033
56118
|
}
|
|
56034
56119
|
return note;
|
|
56035
56120
|
}
|
|
56036
|
-
async function
|
|
56121
|
+
async function publishObject(publisher, object, { visibility = "public", slug: wanted = null, also = [], deliverTo = [] } = {}) {
|
|
56122
|
+
const { urls } = publisher;
|
|
56123
|
+
const priv = visibility === "private" || visibility === "direct";
|
|
56124
|
+
if (priv) {
|
|
56125
|
+
const ready = await publisher.privateReady();
|
|
56126
|
+
if (ready !== true) throw new Error(ready);
|
|
56127
|
+
}
|
|
56128
|
+
const published = (/* @__PURE__ */ new Date()).toISOString();
|
|
56129
|
+
const container = priv ? urls.privateNotes : urls.notes;
|
|
56130
|
+
const pod = publisher.config?.remotePod || urls.base;
|
|
56131
|
+
const ownId = typeof object.id === "string" && /^https?:\/\//u.test(object.id) && object.id.startsWith(pod) ? object.id : null;
|
|
56132
|
+
const name = await slugFor(publisher, container, wanted, published);
|
|
56133
|
+
const addressed = addressing(urls, visibility, visibility === "direct" ? also : []);
|
|
56134
|
+
if (visibility !== "direct") addressed.cc = [.../* @__PURE__ */ new Set([...addressed.cc, ...also])];
|
|
56135
|
+
let doc = null;
|
|
56136
|
+
const id = ownId || container + name;
|
|
56137
|
+
if (!ownId) {
|
|
56138
|
+
const { bto, bcc, ...sent } = object;
|
|
56139
|
+
doc = { ...sent, id, attributedTo: urls.actor, published: object.published || published, to: addressed.to, cc: addressed.cc };
|
|
56140
|
+
if (!doc["@context"]) doc["@context"] = AS_CTX;
|
|
56141
|
+
if (typeof doc.content === "string") doc.content = sanitizeHtml(doc.content);
|
|
56142
|
+
await write4(publisher.remote, id, doc);
|
|
56143
|
+
}
|
|
56144
|
+
if (!priv) await publisher.recordOutbox(id);
|
|
56145
|
+
const row = doc || object;
|
|
56146
|
+
publisher.store.addStatus({
|
|
56147
|
+
noteId: id,
|
|
56148
|
+
actor: urls.actor,
|
|
56149
|
+
content: rowContent(row),
|
|
56150
|
+
published: row.published || published,
|
|
56151
|
+
kind: "post",
|
|
56152
|
+
slug: name,
|
|
56153
|
+
visibility
|
|
56154
|
+
});
|
|
56155
|
+
if (await publisher.store.commit?.() === false) publisher.log(`object published but its timeline row was refused: ${id}`);
|
|
56156
|
+
const createId = doc ? createActivityId(id) : createActivityId(container + name);
|
|
56157
|
+
const create = {
|
|
56158
|
+
"@context": AS_CTX,
|
|
56159
|
+
id: createId,
|
|
56160
|
+
type: "Create",
|
|
56161
|
+
actor: urls.actor,
|
|
56162
|
+
published: row.published || published,
|
|
56163
|
+
to: addressed.to,
|
|
56164
|
+
cc: addressed.cc,
|
|
56165
|
+
object: doc || id
|
|
56166
|
+
};
|
|
56167
|
+
await writeCreate(publisher.remote, create.id, create);
|
|
56168
|
+
const contacts = publisher.store.getContacts();
|
|
56169
|
+
const inboxes = [...new Set([
|
|
56170
|
+
...visibility === "direct" ? [] : contacts.followers.map((f) => f.sharedInbox || f.inbox),
|
|
56171
|
+
...await inboxesFor(publisher, [...also, ...deliverTo])
|
|
56172
|
+
].filter(Boolean))];
|
|
56173
|
+
await publisher.deliverer.deliverToAll(inboxes, create);
|
|
56174
|
+
publisher.log(`${row.type || "object"} published: ${id} \u2192 ${inboxes.length} inbox(es)`);
|
|
56175
|
+
return { id, createId, copied: !ownId };
|
|
56176
|
+
}
|
|
56177
|
+
function rowTags(note) {
|
|
56178
|
+
const tags = [].concat(note.tag || []);
|
|
56179
|
+
const mentions = tags.filter((t) => t.type === "Mention").map((t) => ({ href: t.href, name: t.name }));
|
|
56180
|
+
const hashtags = tags.filter((t) => t.type === "Hashtag").map((t) => ({ name: String(t.name).replace(/^#/, ""), url: t.href }));
|
|
56181
|
+
return { ...mentions.length ? { mentions } : {}, ...hashtags.length ? { tags: hashtags } : {} };
|
|
56182
|
+
}
|
|
56183
|
+
async function updateNote(publisher, s, { content, spoilerText = null, sensitive = null, attachments = null } = {}) {
|
|
56037
56184
|
const { urls } = publisher;
|
|
56038
56185
|
const updated = (/* @__PURE__ */ new Date()).toISOString();
|
|
56039
56186
|
const inText = new Set(mentionsIn(content));
|
|
@@ -56050,6 +56197,7 @@ async function updateNote(publisher, s, { content, spoilerText = null, attachmen
|
|
|
56050
56197
|
const atts = attachments ?? s.attachments ?? [];
|
|
56051
56198
|
const container = String(s.noteId).startsWith(urls.privateNotes) ? urls.privateNotes : urls.notes;
|
|
56052
56199
|
const slug = s.slug || String(s.noteId).slice(container.length);
|
|
56200
|
+
const reply = await replyTarget(publisher, s.inReplyTo, mentions, s.visibility || "public");
|
|
56053
56201
|
const note = noteDoc({
|
|
56054
56202
|
urls,
|
|
56055
56203
|
slug,
|
|
@@ -56060,8 +56208,10 @@ async function updateNote(publisher, s, { content, spoilerText = null, attachmen
|
|
|
56060
56208
|
mentions,
|
|
56061
56209
|
visibility: s.visibility || "public",
|
|
56062
56210
|
summary: spoilerText,
|
|
56211
|
+
sensitive: sensitive ?? !!s.sensitive,
|
|
56063
56212
|
updated,
|
|
56064
|
-
container
|
|
56213
|
+
container,
|
|
56214
|
+
also: reply ? [reply.actor] : []
|
|
56065
56215
|
});
|
|
56066
56216
|
await write4(publisher.remote, note.id, note);
|
|
56067
56217
|
await writeCreate(publisher.remote, createActivityId(note.id), createActivity(note, urls));
|
|
@@ -56070,20 +56220,175 @@ async function updateNote(publisher, s, { content, spoilerText = null, attachmen
|
|
|
56070
56220
|
text: content,
|
|
56071
56221
|
editedAt: updated,
|
|
56072
56222
|
spoiler: spoilerText || void 0,
|
|
56223
|
+
sensitive: note.sensitive || void 0,
|
|
56073
56224
|
attachments: atts.length ? atts : void 0,
|
|
56074
|
-
mentions: note
|
|
56225
|
+
mentions: rowTags(note).mentions,
|
|
56226
|
+
tags: rowTags(note).tags
|
|
56075
56227
|
});
|
|
56076
56228
|
const update = updateActivity(note, urls);
|
|
56077
56229
|
const contacts = publisher.store.getContacts();
|
|
56078
56230
|
const inboxes = [...new Set([
|
|
56079
56231
|
...s.visibility === "direct" ? [] : contacts.followers.map((f) => f.sharedInbox || f.inbox),
|
|
56080
|
-
...mentions.map((m) => m.inbox)
|
|
56232
|
+
...mentions.map((m) => m.inbox),
|
|
56233
|
+
reply?.inbox
|
|
56081
56234
|
].filter(Boolean))];
|
|
56082
56235
|
await publisher.deliverer.deliverToAll(inboxes, update);
|
|
56083
56236
|
publisher.log(`note edited: ${note.id} \u2192 ${inboxes.length} inbox(es)`);
|
|
56084
56237
|
return patched;
|
|
56085
56238
|
}
|
|
56086
56239
|
|
|
56240
|
+
// lib/core/publisher/restore.mjs
|
|
56241
|
+
var ACCEPT_AP = 'application/activity+json, application/ld+json; profile="https://www.w3.org/ns/activitystreams"';
|
|
56242
|
+
var REBUILD_MAX_PER_RUN = 200;
|
|
56243
|
+
async function reconcileFollowers(publisher, contacts) {
|
|
56244
|
+
let published = [];
|
|
56245
|
+
try {
|
|
56246
|
+
published = await publisher.readPublishedFollowers() || [];
|
|
56247
|
+
} catch {
|
|
56248
|
+
return 0;
|
|
56249
|
+
}
|
|
56250
|
+
const known2 = new Set(contacts.followers.map((f) => f.actor));
|
|
56251
|
+
const removed = new Set((contacts.removedFollowers || []).map((r) => r.actor));
|
|
56252
|
+
const missing = published.filter((a) => typeof a === "string" && !known2.has(a) && !removed.has(a));
|
|
56253
|
+
if (!missing.length) return 0;
|
|
56254
|
+
let recovered = 0;
|
|
56255
|
+
for (const actor of missing.slice(0, 200)) {
|
|
56256
|
+
try {
|
|
56257
|
+
const res = await publisher.deliverer.signedFetch(actor, { headers: { accept: ACCEPT_AP } });
|
|
56258
|
+
if (!res.ok) continue;
|
|
56259
|
+
const { doc, view, degraded } = await readLenient(await res.json());
|
|
56260
|
+
const actorDoc2 = view ?? doc;
|
|
56261
|
+
if (degraded) publisher.log?.(`actor ${actor} grounded to read: ${degraded}`);
|
|
56262
|
+
if (!actorDoc2?.inbox) continue;
|
|
56263
|
+
contacts.followers.push({
|
|
56264
|
+
actor,
|
|
56265
|
+
inbox: actorDoc2.inbox,
|
|
56266
|
+
sharedInbox: actorDoc2.endpoints?.sharedInbox || null,
|
|
56267
|
+
recovered: true,
|
|
56268
|
+
// Said explicitly, because onUndo reads it: the pod publishes WHO
|
|
56269
|
+
// follows, never the id of the Follow that did it, so a recovered
|
|
56270
|
+
// record has nothing an Undo can be matched against and must not be
|
|
56271
|
+
// evictable by one naming anything at all.
|
|
56272
|
+
followId: null
|
|
56273
|
+
});
|
|
56274
|
+
recovered++;
|
|
56275
|
+
} catch {
|
|
56276
|
+
}
|
|
56277
|
+
}
|
|
56278
|
+
if (recovered) {
|
|
56279
|
+
publisher.store.setContacts(contacts);
|
|
56280
|
+
publisher.log(`reconciled ${recovered} follower(s) the pod knew about and this machine did not \u2014 a restored or copied state was behind`);
|
|
56281
|
+
}
|
|
56282
|
+
return recovered;
|
|
56283
|
+
}
|
|
56284
|
+
async function reconcileOutbox(publisher, outbox) {
|
|
56285
|
+
let published = [];
|
|
56286
|
+
try {
|
|
56287
|
+
published = await publisher.readPublishedOutbox() || [];
|
|
56288
|
+
} catch {
|
|
56289
|
+
return 0;
|
|
56290
|
+
}
|
|
56291
|
+
if (!Array.isArray(published) || !published.length) return 0;
|
|
56292
|
+
const idOf2 = (i) => typeof i === "string" ? i : i?.id || null;
|
|
56293
|
+
const known2 = new Set(outbox.map(idOf2).filter(Boolean));
|
|
56294
|
+
const removed = new Set(publisher.store.read("outbox-removed.json", []).map((r) => r.id));
|
|
56295
|
+
const missing = published.filter((i) => {
|
|
56296
|
+
const id = idOf2(i);
|
|
56297
|
+
return id && !known2.has(id) && !removed.has(id);
|
|
56298
|
+
});
|
|
56299
|
+
if (!missing.length) return 0;
|
|
56300
|
+
outbox.push(...missing);
|
|
56301
|
+
publisher.store.write("outbox.json", outbox);
|
|
56302
|
+
publisher.log(`reconciled ${missing.length} outbox entr(ies) the pod carried and this machine did not`);
|
|
56303
|
+
return missing.length;
|
|
56304
|
+
}
|
|
56305
|
+
async function rebuildStatuses(publisher, { fromNotes = false } = {}) {
|
|
56306
|
+
const { urls } = publisher;
|
|
56307
|
+
const ids = /* @__PURE__ */ new Set();
|
|
56308
|
+
const boosts = [];
|
|
56309
|
+
let indexed = false;
|
|
56310
|
+
const published = await publisher.readPublishedOutbox().catch(() => null);
|
|
56311
|
+
if (published) {
|
|
56312
|
+
indexed = true;
|
|
56313
|
+
for (const item of published) {
|
|
56314
|
+
if (typeof item === "string") {
|
|
56315
|
+
if (item.startsWith(urls.notes)) ids.add(item);
|
|
56316
|
+
} else if (item?.type === "Announce") boosts.push(item);
|
|
56317
|
+
}
|
|
56318
|
+
}
|
|
56319
|
+
if (fromNotes) {
|
|
56320
|
+
for (const child of await list2(publisher.remote, urls).catch(() => [])) {
|
|
56321
|
+
ids.add(child.url);
|
|
56322
|
+
}
|
|
56323
|
+
indexed = true;
|
|
56324
|
+
}
|
|
56325
|
+
if (!indexed) return { indexed: 0, recovered: 0, reblogs: 0, landed: false, why: "the pod would not answer for its outbox" };
|
|
56326
|
+
const statuses = publisher.store.getStatuses();
|
|
56327
|
+
const have = new Set(statuses.map((s) => s.noteId));
|
|
56328
|
+
const removed = new Set(publisher.store.read("outbox-removed.json", []).map((r) => r.id));
|
|
56329
|
+
const recovered = [];
|
|
56330
|
+
let budget = REBUILD_MAX_PER_RUN;
|
|
56331
|
+
for (const id of ids) {
|
|
56332
|
+
if (budget <= 0) {
|
|
56333
|
+
publisher.log(`rebuild: stopping at ${REBUILD_MAX_PER_RUN} this run \u2014 run it again for the rest`);
|
|
56334
|
+
break;
|
|
56335
|
+
}
|
|
56336
|
+
if (have.has(id) || removed.has(id)) continue;
|
|
56337
|
+
budget--;
|
|
56338
|
+
const note = await read(publisher.remote, id).catch(() => null);
|
|
56339
|
+
if (!note?.type || note.id !== id || note.attributedTo !== urls.actor) continue;
|
|
56340
|
+
const attachments = attachmentsOf(note);
|
|
56341
|
+
const mentions = (Array.isArray(note.tag) ? note.tag : []).filter((t) => t?.type === "Mention" && t.href).map((t) => ({ href: t.href, name: t.name }));
|
|
56342
|
+
recovered.push({
|
|
56343
|
+
noteId: note.id,
|
|
56344
|
+
actor: urls.actor,
|
|
56345
|
+
content: rowContent(note),
|
|
56346
|
+
published: note.published || null,
|
|
56347
|
+
...note.inReplyTo ? { inReplyTo: note.inReplyTo } : {},
|
|
56348
|
+
kind: "post",
|
|
56349
|
+
slug: note.id.slice(urls.notes.length),
|
|
56350
|
+
...attachments.length ? { attachments } : {},
|
|
56351
|
+
...mentions.length ? { mentions } : {},
|
|
56352
|
+
recovered: true
|
|
56353
|
+
});
|
|
56354
|
+
}
|
|
56355
|
+
const merged = [...statuses, ...recovered];
|
|
56356
|
+
let reblogs = 0;
|
|
56357
|
+
for (const act of boosts) {
|
|
56358
|
+
const object = typeof act.object === "string" ? act.object : act.object?.id;
|
|
56359
|
+
const s = object && merged.find((x) => x.noteId === object);
|
|
56360
|
+
if (!s || s.reblogged) continue;
|
|
56361
|
+
s.reblogged = true;
|
|
56362
|
+
s.announceActivity = act;
|
|
56363
|
+
reblogs++;
|
|
56364
|
+
}
|
|
56365
|
+
if (!recovered.length && !reblogs) return { indexed: ids.size, recovered: 0, reblogs: 0, landed: true };
|
|
56366
|
+
merged.sort((a, b) => String(b.published || "").localeCompare(String(a.published || "")));
|
|
56367
|
+
const kept = merged.slice(0, 1e3);
|
|
56368
|
+
publisher.store.write("statuses.json", kept);
|
|
56369
|
+
const landed = await publisher.store.commit();
|
|
56370
|
+
publisher.log(`rebuilt ${recovered.length} post(s) and ${reblogs} boost(s) from the pod${landed ? "" : " \u2014 THE STATE WRITE DID NOT LAND"}`);
|
|
56371
|
+
return {
|
|
56372
|
+
indexed: ids.size,
|
|
56373
|
+
recovered: recovered.length,
|
|
56374
|
+
reblogs,
|
|
56375
|
+
landed,
|
|
56376
|
+
dropped: Math.max(0, merged.length - kept.length)
|
|
56377
|
+
};
|
|
56378
|
+
}
|
|
56379
|
+
async function healStatuses(publisher) {
|
|
56380
|
+
const { urls, store } = publisher;
|
|
56381
|
+
const own = store.read("outbox.json", []).map((i) => typeof i === "string" ? i : null).filter((id) => id && id.startsWith(urls.notes));
|
|
56382
|
+
if (!own.length) return { missing: 0, recovered: 0 };
|
|
56383
|
+
const have = new Set(store.getStatuses().map((s) => s.noteId));
|
|
56384
|
+
const removed = new Set(store.read("outbox-removed.json", []).map((r2) => r2.id));
|
|
56385
|
+
const missing = own.filter((id) => !have.has(id) && !removed.has(id));
|
|
56386
|
+
if (!missing.length) return { missing: 0, recovered: 0 };
|
|
56387
|
+
const r = await rebuildStatuses(publisher);
|
|
56388
|
+
publisher.log(`timeline index healed: ${missing.length} own post(s) were missing, ${r.recovered} recovered`);
|
|
56389
|
+
return { missing: missing.length, recovered: r.recovered };
|
|
56390
|
+
}
|
|
56391
|
+
|
|
56087
56392
|
// lib/core/publisher/questions.mjs
|
|
56088
56393
|
init_node_crypto();
|
|
56089
56394
|
init_wire();
|
|
@@ -56151,7 +56456,8 @@ async function publishQuestion(publisher, content, {
|
|
|
56151
56456
|
expiresAt = null,
|
|
56152
56457
|
inReplyTo = void 0,
|
|
56153
56458
|
visibility = "public",
|
|
56154
|
-
spoilerText = null
|
|
56459
|
+
spoilerText = null,
|
|
56460
|
+
sensitive = false
|
|
56155
56461
|
} = {}) {
|
|
56156
56462
|
const { urls } = publisher;
|
|
56157
56463
|
const priv = visibility === "private" || visibility === "direct";
|
|
@@ -56178,6 +56484,7 @@ async function publishQuestion(publisher, content, {
|
|
|
56178
56484
|
const slug = published.slice(0, 10) + "-" + node_crypto_default.randomBytes(4).toString("hex");
|
|
56179
56485
|
const mentions = await publisher._mentionsFor(content, inReplyTo);
|
|
56180
56486
|
if (visibility === "direct") assertDirectAddressed(content, mentions);
|
|
56487
|
+
const reply = await replyTarget(publisher, inReplyTo, mentions, visibility);
|
|
56181
56488
|
const poll = {
|
|
56182
56489
|
multiple: !!multiple,
|
|
56183
56490
|
expiresAt: expiresAt || null,
|
|
@@ -56186,7 +56493,7 @@ async function publishQuestion(publisher, content, {
|
|
|
56186
56493
|
votersCount: 0,
|
|
56187
56494
|
// Resolved once, here: a tally rewrite must not cost a webfinger lookup
|
|
56188
56495
|
// per vote for people the poll named.
|
|
56189
|
-
mentionInboxes: [...new Set(mentions.map((m) => m.inbox).filter(Boolean))]
|
|
56496
|
+
mentionInboxes: [...new Set([...mentions.map((m) => m.inbox), reply?.inbox].filter(Boolean))]
|
|
56190
56497
|
};
|
|
56191
56498
|
const question = questionDoc({
|
|
56192
56499
|
urls,
|
|
@@ -56198,6 +56505,8 @@ async function publishQuestion(publisher, content, {
|
|
|
56198
56505
|
mentions,
|
|
56199
56506
|
visibility,
|
|
56200
56507
|
summary: spoilerText,
|
|
56508
|
+
sensitive,
|
|
56509
|
+
also: reply ? [reply.actor] : [],
|
|
56201
56510
|
container: priv ? urls.privateNotes : urls.notes,
|
|
56202
56511
|
options: poll.options,
|
|
56203
56512
|
multiple: poll.multiple,
|
|
@@ -56222,9 +56531,12 @@ async function publishQuestion(publisher, content, {
|
|
|
56222
56531
|
visibility,
|
|
56223
56532
|
poll,
|
|
56224
56533
|
inReplyTo,
|
|
56534
|
+
...reply ? { replyActor: reply.actor } : {},
|
|
56225
56535
|
...spoilerText ? { spoiler: spoilerText } : {},
|
|
56226
|
-
...question.
|
|
56536
|
+
...question.sensitive ? { sensitive: true } : {},
|
|
56537
|
+
...rowTags(question)
|
|
56227
56538
|
});
|
|
56539
|
+
if (await publisher.store.commit?.() === false) publisher.log(`poll published but its timeline row was refused: ${question.id}`);
|
|
56228
56540
|
const create = publisher._pollActivity("Create", question, createActivityId(question.id));
|
|
56229
56541
|
await writeCreate(publisher.remote, create.id, create);
|
|
56230
56542
|
const contacts = publisher.store.getContacts();
|
|
@@ -56301,8 +56613,10 @@ async function republishPoll(publisher, questionId, { closing = null } = {}) {
|
|
|
56301
56613
|
inReplyTo: s.inReplyTo,
|
|
56302
56614
|
attachments: [],
|
|
56303
56615
|
mentions,
|
|
56616
|
+
also: s.replyActor ? [s.replyActor] : [],
|
|
56304
56617
|
visibility: s.visibility || "public",
|
|
56305
56618
|
summary: s.spoiler || null,
|
|
56619
|
+
sensitive: !!s.sensitive,
|
|
56306
56620
|
container,
|
|
56307
56621
|
options: poll.options,
|
|
56308
56622
|
multiple: !!poll.multiple,
|
|
@@ -56374,6 +56688,7 @@ var Publisher = class {
|
|
|
56374
56688
|
log: log2 = console.log,
|
|
56375
56689
|
probeFetch = null,
|
|
56376
56690
|
resolveMention = null,
|
|
56691
|
+
resolveActor = null,
|
|
56377
56692
|
clientOrigin = null
|
|
56378
56693
|
}) {
|
|
56379
56694
|
this.config = config;
|
|
@@ -56388,6 +56703,7 @@ var Publisher = class {
|
|
|
56388
56703
|
if (this.urls.toPod && this.remote?.setUrlMap) this.remote.setUrlMap(this.urls.toPod);
|
|
56389
56704
|
this.probeFetch = probeFetch || ((u, i) => this.remote.probe(u, i));
|
|
56390
56705
|
this.resolveMention = resolveMention;
|
|
56706
|
+
this.resolveActor = resolveActor;
|
|
56391
56707
|
this.pollTimers = /* @__PURE__ */ new Map();
|
|
56392
56708
|
this.log = log2;
|
|
56393
56709
|
}
|
|
@@ -56405,19 +56721,25 @@ var Publisher = class {
|
|
|
56405
56721
|
// republish button. Without it, an actor lost from the pod would match the
|
|
56406
56722
|
// digest, be skipped, and leave the agent reporting success while nobody can
|
|
56407
56723
|
// resolve it.
|
|
56408
|
-
|
|
56409
|
-
|
|
56410
|
-
|
|
56411
|
-
const priv = await this.privateReady() === true;
|
|
56412
|
-
const moderators = (this.config.moderators || []).length ? urls.moderators : null;
|
|
56724
|
+
// The Gateway's outbox door for this account, when a Gateway is attached:
|
|
56725
|
+
// beside its inbox door, or under the fronted actor.
|
|
56726
|
+
gatewayOutbox() {
|
|
56413
56727
|
const gw = this.config.gateway;
|
|
56414
|
-
|
|
56415
|
-
|
|
56728
|
+
if (!(gw && gw.url && gw.mode && gw.mode !== "off")) return null;
|
|
56729
|
+
if (gw.frontActor) return String(gw.frontActor).replace(/ap\/actor\/?$/u, "ap/outbox");
|
|
56730
|
+
return String(gw.url).replace(/ap\/inbox\/?$/u, "ap/outbox");
|
|
56731
|
+
}
|
|
56732
|
+
// Every actor document this identity publishes, from one place: the
|
|
56733
|
+
// profile publish and a Move both write it, and a Move that rebuilt it
|
|
56734
|
+
// by hand dropped the gateway inbox, the outbox door and the collections.
|
|
56735
|
+
actorDocFor({ priv = false, moderators = null, inbox = null, movedTo = this.config.movedTo || null } = {}) {
|
|
56736
|
+
const { urls } = this;
|
|
56737
|
+
return actorDoc({
|
|
56416
56738
|
urls,
|
|
56417
56739
|
handle: publicHandle(this.config),
|
|
56418
56740
|
name: this.config.name,
|
|
56419
56741
|
publicKeyPem: this.publicKeyPem,
|
|
56420
|
-
movedTo
|
|
56742
|
+
movedTo,
|
|
56421
56743
|
kind: this.config.kind,
|
|
56422
56744
|
approveJoins: followsNeedApproval(this.config),
|
|
56423
56745
|
assertionKey: this.assertionKey,
|
|
@@ -56431,15 +56753,25 @@ var Publisher = class {
|
|
|
56431
56753
|
pendingFollowers: priv ? urls.pendingFollowers : null,
|
|
56432
56754
|
pendingFollowing: priv ? urls.pendingFollowing : null,
|
|
56433
56755
|
blocked: priv ? urls.blocked : null,
|
|
56434
|
-
inbox
|
|
56756
|
+
inbox,
|
|
56435
56757
|
// The agent's own outbox endpoint, where it is reachable: a client
|
|
56436
56758
|
// following the actor must arrive somewhere that will take a write.
|
|
56437
|
-
|
|
56759
|
+
// Otherwise the Gateway's outbox door, when one is attached.
|
|
56760
|
+
outbox: this.clientOrigin ? `${this.clientOrigin}ap/outbox` : this.gatewayOutbox(),
|
|
56438
56761
|
// How a client-to-server client finds the way in with nothing configured
|
|
56439
56762
|
// by hand. Advertised only where the surface is publicly reachable.
|
|
56440
56763
|
oauthAuthorize: this.clientOrigin ? `${this.clientOrigin}oauth/authorize` : null,
|
|
56441
56764
|
oauthToken: this.clientOrigin ? `${this.clientOrigin}oauth/token` : null
|
|
56442
56765
|
});
|
|
56766
|
+
}
|
|
56767
|
+
async publishProfile({ force = false } = {}) {
|
|
56768
|
+
const { urls } = this;
|
|
56769
|
+
const host = new URL(urls.base).host;
|
|
56770
|
+
const priv = await this.privateReady() === true;
|
|
56771
|
+
const moderators = (this.config.moderators || []).length ? urls.moderators : null;
|
|
56772
|
+
const gw = this.config.gateway;
|
|
56773
|
+
const gwActive = gw && gw.url && gw.mode && gw.mode !== "off";
|
|
56774
|
+
const actorDoc2 = this.actorDocFor({ priv, moderators, inbox: gwActive ? gw.url : null });
|
|
56443
56775
|
const surface = node_crypto_default.createHash("sha256").update(JSON.stringify({
|
|
56444
56776
|
actor: actorDoc2,
|
|
56445
56777
|
handle: this.config.handle,
|
|
@@ -56455,7 +56787,7 @@ var Publisher = class {
|
|
|
56455
56787
|
await writeWebfinger(
|
|
56456
56788
|
this.remote,
|
|
56457
56789
|
urls,
|
|
56458
|
-
jrd({ handle: this.config.handle, host, actor: urls.actor })
|
|
56790
|
+
jrd({ handle: this.config.handle, host, actor: urls.actor, page: urls.profileHtml })
|
|
56459
56791
|
);
|
|
56460
56792
|
await writeHostMeta(this.remote, urls, hostMeta(urls.base));
|
|
56461
56793
|
const nodeinfoDocUrl = urls.home + "ap/nodeinfo-2.0";
|
|
@@ -56485,7 +56817,9 @@ var Publisher = class {
|
|
|
56485
56817
|
const wrote = await linkInWebIdProfile(this.remote, {
|
|
56486
56818
|
actorUrl: urls.actor,
|
|
56487
56819
|
accountName: `@${this.config.handle}@${host}`,
|
|
56488
|
-
kind: this.config.kind
|
|
56820
|
+
kind: this.config.kind,
|
|
56821
|
+
// Where a Solid client posts: dokieli reads `as:outbox` off the WebID.
|
|
56822
|
+
outbox: this.clientOrigin ? `${this.clientOrigin}ap/outbox` : this.gatewayOutbox()
|
|
56489
56823
|
});
|
|
56490
56824
|
if (wrote) this.log("WebID profile now lists the actor as a foaf:account");
|
|
56491
56825
|
} catch (e) {
|
|
@@ -56648,21 +56982,15 @@ var Publisher = class {
|
|
|
56648
56982
|
await this.deliverer.deliverToAll(inboxes, moveActivity(urls, target, Date.parse(at)));
|
|
56649
56983
|
this.config.movedTo = target;
|
|
56650
56984
|
this.store.setConfig({ ...this.store.getConfig(), movedTo: target, movedAt: at });
|
|
56651
|
-
|
|
56652
|
-
|
|
56653
|
-
|
|
56654
|
-
|
|
56655
|
-
|
|
56656
|
-
|
|
56657
|
-
|
|
56658
|
-
|
|
56659
|
-
|
|
56660
|
-
summary: this.config.summary || null,
|
|
56661
|
-
icon: this.config.icon || null,
|
|
56662
|
-
image: this.config.image || null,
|
|
56663
|
-
fields: this.config.fields || [],
|
|
56664
|
-
webId: this.remote.webId || null,
|
|
56665
|
-
aliases: this.config.aliases || []
|
|
56985
|
+
const gw = this.config.gateway;
|
|
56986
|
+
const gwActive = gw && gw.url && gw.mode && gw.mode !== "off";
|
|
56987
|
+
const priv = await this.privateReady() === true;
|
|
56988
|
+
const moderators = (this.config.moderators || []).length ? urls.moderators : null;
|
|
56989
|
+
await writeMoved(this.remote, urls, this.actorDocFor({
|
|
56990
|
+
priv,
|
|
56991
|
+
moderators,
|
|
56992
|
+
inbox: gwActive ? gw.url : null,
|
|
56993
|
+
movedTo: target
|
|
56666
56994
|
}));
|
|
56667
56995
|
await this.store.flush();
|
|
56668
56996
|
this.log(`moved to ${target}: Move sent to ${inboxes.length} inbox(es), actor now advertises movedTo`);
|
|
@@ -56732,6 +57060,9 @@ var Publisher = class {
|
|
|
56732
57060
|
rebuildStatuses(...a) {
|
|
56733
57061
|
return rebuildStatuses(this, ...a);
|
|
56734
57062
|
}
|
|
57063
|
+
healStatuses(...a) {
|
|
57064
|
+
return healStatuses(this, ...a);
|
|
57065
|
+
}
|
|
56735
57066
|
// notes.mjs
|
|
56736
57067
|
ensureMediaContainer(...a) {
|
|
56737
57068
|
return ensureMediaContainer(this, ...a);
|
|
@@ -56751,6 +57082,9 @@ var Publisher = class {
|
|
|
56751
57082
|
publishNote(...a) {
|
|
56752
57083
|
return publishNote(this, ...a);
|
|
56753
57084
|
}
|
|
57085
|
+
publishObject(...a) {
|
|
57086
|
+
return publishObject(this, ...a);
|
|
57087
|
+
}
|
|
56754
57088
|
updateNote(...a) {
|
|
56755
57089
|
return updateNote(this, ...a);
|
|
56756
57090
|
}
|
|
@@ -56833,13 +57167,13 @@ var MAX_FORWARDS_PER_DRAIN = 20;
|
|
|
56833
57167
|
var ACCEPT_AP2 = 'application/activity+json, application/ld+json; profile="https://www.w3.org/ns/activitystreams"';
|
|
56834
57168
|
function trimActivity(a) {
|
|
56835
57169
|
if (!a || typeof a !== "object") return a ?? null;
|
|
56836
|
-
const
|
|
57170
|
+
const idOf2 = (v) => typeof v === "string" ? v : v?.id ?? null;
|
|
56837
57171
|
const out = {};
|
|
56838
57172
|
for (const k of ["id", "type", "actor", "target"]) {
|
|
56839
|
-
const v =
|
|
57173
|
+
const v = idOf2(a[k]);
|
|
56840
57174
|
if (v) out[k] = String(v).slice(0, 2048);
|
|
56841
57175
|
}
|
|
56842
|
-
const obj =
|
|
57176
|
+
const obj = idOf2(a.object);
|
|
56843
57177
|
if (obj) out.object = String(obj).slice(0, 2048);
|
|
56844
57178
|
else if (a.object && typeof a.object === "object" && a.object.type) {
|
|
56845
57179
|
out.object = { type: String(a.object.type).slice(0, 64) };
|
|
@@ -57360,6 +57694,8 @@ async function onFollow(intake, activity, actor, { trusted = false } = {}) {
|
|
|
57360
57694
|
intake.log(`Accept sent \u2192 ${doc.inbox}`);
|
|
57361
57695
|
}
|
|
57362
57696
|
async function onUndo(intake, activity, actor, { trusted = false } = {}) {
|
|
57697
|
+
const inner = activity.object && typeof activity.object === "object" ? activity.object : null;
|
|
57698
|
+
if (inner?.type === "Like" || inner?.type === "Announce") return onUndoReaction(intake, inner, actor, { trusted });
|
|
57363
57699
|
if (typeof activity.object === "object" && activity.object?.type && activity.object.type !== "Follow") return;
|
|
57364
57700
|
const named = typeof activity.object === "string" ? activity.object : activity.object?.id;
|
|
57365
57701
|
if (!named && !trusted) return;
|
|
@@ -57447,6 +57783,47 @@ async function onAnnounce(intake, activity, actor, objectId) {
|
|
|
57447
57783
|
}
|
|
57448
57784
|
return intake.ingestNote(objectId, actor, { via: actor });
|
|
57449
57785
|
}
|
|
57786
|
+
async function onUndoReaction(intake, inner, actor, { trusted = false } = {}) {
|
|
57787
|
+
const innerActor = typeof inner.actor === "string" ? inner.actor : inner.actor?.id;
|
|
57788
|
+
if (innerActor && innerActor !== actor) return;
|
|
57789
|
+
const wrapped = inner.object;
|
|
57790
|
+
const target = wrapped && typeof wrapped === "object" && (wrapped.type === "Create" || wrapped.type === "Update") ? wrapped.object : wrapped;
|
|
57791
|
+
const objectId = typeof target === "string" ? target : target?.id;
|
|
57792
|
+
if (!objectId) return;
|
|
57793
|
+
if (objectId.startsWith(intake.urls.notes)) {
|
|
57794
|
+
const type = inner.type === "Like" ? "favourite" : "reblog";
|
|
57795
|
+
const gone = intake.store.removeNotifications((n) => n.type === type && n.actor === actor && n.noteId === objectId);
|
|
57796
|
+
if (gone) intake.log(`${inner.type} withdrawn by ${actor} on ${objectId}`);
|
|
57797
|
+
return;
|
|
57798
|
+
}
|
|
57799
|
+
if (inner.type !== "Announce") return;
|
|
57800
|
+
const followed = trusted || intake.store.getContacts().following.some((f) => f.actor === actor && f.accepted);
|
|
57801
|
+
if (!followed) return;
|
|
57802
|
+
const s = intake.store.getStatuses().find((x) => x.noteId === objectId);
|
|
57803
|
+
if (!s || s.kind === "post" || s.via !== actor) return;
|
|
57804
|
+
await intake.forget(s);
|
|
57805
|
+
intake.log(`boost withdrawn by ${actor}: ${objectId} left the timeline`);
|
|
57806
|
+
}
|
|
57807
|
+
async function onBlock(intake, activity, actor, { trusted = false } = {}) {
|
|
57808
|
+
const objectId = typeof activity.object === "string" ? activity.object : activity.object?.id;
|
|
57809
|
+
if (objectId !== intake.urls.actor) return;
|
|
57810
|
+
if (!trusted) {
|
|
57811
|
+
intake.log(`Block from ${actor} arrived unverified \u2014 ignored`);
|
|
57812
|
+
return;
|
|
57813
|
+
}
|
|
57814
|
+
const contacts = intake.store.getContacts();
|
|
57815
|
+
const wasFollower = contacts.followers.some((f) => f.actor === actor);
|
|
57816
|
+
const wasFollowing = contacts.following.some((f) => f.actor === actor);
|
|
57817
|
+
if (wasFollower) dropFollower(contacts, actor, "blocked-us");
|
|
57818
|
+
contacts.following = contacts.following.filter((f) => f.actor !== actor);
|
|
57819
|
+
intake.store.setContacts(contacts);
|
|
57820
|
+
const reqs = intake.store.getRequests();
|
|
57821
|
+
const left = reqs.filter((r) => r.actor !== actor);
|
|
57822
|
+
if (left.length !== reqs.length) intake.store.setRequests(left);
|
|
57823
|
+
await intake.republish({ followers: wasFollower, following: wasFollowing, pending: true });
|
|
57824
|
+
const what = [wasFollower && "their follow of us", wasFollowing && "our follow of them"].filter(Boolean).join(" and ");
|
|
57825
|
+
intake.log(`blocked by ${actor}: ${what || "no relationship"} dropped`);
|
|
57826
|
+
}
|
|
57450
57827
|
async function onDelete(intake, activity, actor) {
|
|
57451
57828
|
const objectId = typeof activity.object === "string" ? activity.object : activity.object?.id;
|
|
57452
57829
|
if (!objectId) return "Delete without object id";
|
|
@@ -57652,6 +58029,7 @@ async function ingestNote(intake, objectId, actor, { via, inline = null } = {})
|
|
|
57652
58029
|
const followed = via || known2 || !known2 && await intake.isCoMember(author);
|
|
57653
58030
|
const kind = followed ? "timeline" : "mention";
|
|
57654
58031
|
const mentions = [].concat(note.tag || []).filter((t) => t?.type === "Mention" && t.href && t.name).slice(0, MAX_MENTIONS).map((t) => ({ href: httpOnly(String(t.href).slice(0, MAX_URL_CHARS)), name: String(t.name).slice(0, 256) })).filter((m) => m.href);
|
|
58032
|
+
const hashtags = [].concat(note.tag || []).filter((t) => t?.type === "Hashtag" && t.name).slice(0, MAX_MENTIONS).map((t) => ({ name: String(t.name).replace(/^#/, "").slice(0, 128), url: httpOnly(String(t.href || "").slice(0, MAX_URL_CHARS)) })).filter((t) => t.name);
|
|
57655
58033
|
const emojis = emojisOf(note);
|
|
57656
58034
|
const poll = pollOf(note);
|
|
57657
58035
|
const audience = [].concat(note.to || [], note.cc || []).map(String);
|
|
@@ -57668,6 +58046,8 @@ async function ingestNote(intake, objectId, actor, { via, inline = null } = {})
|
|
|
57668
58046
|
...nonPublic ? { nonPublic: true } : {},
|
|
57669
58047
|
// The author's content warning, shown as one: plain text only.
|
|
57670
58048
|
...note.summary ? { spoiler: String(note.summary).replace(/<[^>]*>/g, "") } : {},
|
|
58049
|
+
...note.sensitive === true ? { sensitive: true } : {},
|
|
58050
|
+
...hashtags.length ? { tags: hashtags } : {},
|
|
57671
58051
|
...poll ? { poll } : {},
|
|
57672
58052
|
...emojis.length ? { emojis } : {},
|
|
57673
58053
|
...mentions.length ? { mentions } : {},
|
|
@@ -60814,8 +61194,8 @@ function walkPath(graph2, subject, path, visited = new node_set_default()) {
|
|
|
60814
61194
|
pathValues.addAll(deeperValues);
|
|
60815
61195
|
return pathValues;
|
|
60816
61196
|
}
|
|
60817
|
-
function flatMap(
|
|
60818
|
-
return [...
|
|
61197
|
+
function flatMap(arr2, func) {
|
|
61198
|
+
return [...arr2].reduce((acc, x) => acc.concat(func(x)), []);
|
|
60819
61199
|
}
|
|
60820
61200
|
|
|
60821
61201
|
// node_modules/rdf-validate-shacl/src/dataset-utils.js
|
|
@@ -62764,8 +63144,10 @@ var ATTEMPTS_TTL_MS = 7 * 24 * 60 * 6e4;
|
|
|
62764
63144
|
var MAX_ITEM_ATTEMPTS = 5;
|
|
62765
63145
|
var MAX_ITEMS_PER_DRAIN = 50;
|
|
62766
63146
|
var Intake = class {
|
|
62767
|
-
|
|
62768
|
-
|
|
63147
|
+
// `ownerPost(activity, { raw, slug })` is the client-to-server dispatcher,
|
|
63148
|
+
// for an item the Gateway's outbox door took on the owner's behalf.
|
|
63149
|
+
constructor({ config, urls, remote, store, deliverer, publisher, log: log2 = console.log, lease = null, archive = null, push = true, pollSeconds = null, ownerPost = null }) {
|
|
63150
|
+
Object.assign(this, { config, urls, remote, store, deliverer, publisher, log: log2, lease, archive, push, pollSeconds, ownerPost });
|
|
62769
63151
|
this.serial = Date.now();
|
|
62770
63152
|
this.stopped = false;
|
|
62771
63153
|
this.lastDrain = null;
|
|
@@ -63073,9 +63455,10 @@ var Intake = class {
|
|
|
63073
63455
|
}
|
|
63074
63456
|
const receipt = activity ? await this._readReceipt(url) : null;
|
|
63075
63457
|
if (activity && this.gatewaySecret()) this._bumpGatewayStat(!!receipt?.verified);
|
|
63076
|
-
const
|
|
63077
|
-
|
|
63078
|
-
if (!rejection) await this.
|
|
63458
|
+
const owned = activity && this.isOwnerPost(receipt);
|
|
63459
|
+
const rejection = !activity ? "unparsable JSON" : owned ? await this.ownerPostFrom(activity, raw, receipt) : await this.handle(activity, receipt);
|
|
63460
|
+
if (!rejection && raw && !owned) await this._archive(url, raw, activity);
|
|
63461
|
+
if (!rejection && !owned) await this._maybeForward(activity);
|
|
63079
63462
|
if (rejection) {
|
|
63080
63463
|
this.store.addDeadLetter({
|
|
63081
63464
|
inboxUrl: url,
|
|
@@ -63187,6 +63570,8 @@ var Intake = class {
|
|
|
63187
63570
|
case "Add":
|
|
63188
63571
|
case "Remove":
|
|
63189
63572
|
return this.onAddRemove(activity, actor);
|
|
63573
|
+
case "Block":
|
|
63574
|
+
return this.onBlock(activity, actor, { trusted });
|
|
63190
63575
|
default:
|
|
63191
63576
|
this.log(`ignored ${activity.type} from ${actor}`);
|
|
63192
63577
|
}
|
|
@@ -63223,6 +63608,28 @@ var Intake = class {
|
|
|
63223
63608
|
_readReceipt(...a) {
|
|
63224
63609
|
return readReceipt(this, ...a);
|
|
63225
63610
|
}
|
|
63611
|
+
// A receipt the door stamped `c2s` for THIS actor, and only a receipt whose
|
|
63612
|
+
// HMAC verified (readReceipt returns nothing else). A stranger appending an
|
|
63613
|
+
// item that claims to be ours has no such receipt and is read as mail.
|
|
63614
|
+
isOwnerPost(receipt) {
|
|
63615
|
+
return !!(this.ownerPost && receipt?.verified && receipt.method === "c2s" && receipt.actor === this.urls.actor);
|
|
63616
|
+
}
|
|
63617
|
+
// Publish what the owner posted at the door. The bytes as the client wrote
|
|
63618
|
+
// them go to the dispatcher (an object stored as sent must be the client's
|
|
63619
|
+
// document, not our reading of it); the graph is what it decides from.
|
|
63620
|
+
// Returns null when published, else the reason it was not — a dead letter.
|
|
63621
|
+
async ownerPostFrom(activity, raw, receipt) {
|
|
63622
|
+
let asSent = null;
|
|
63623
|
+
try {
|
|
63624
|
+
asSent = raw ? JSON.parse(raw) : null;
|
|
63625
|
+
} catch {
|
|
63626
|
+
asSent = null;
|
|
63627
|
+
}
|
|
63628
|
+
const r = await this.ownerPost(activity, { raw: asSent, slug: receipt.slug || null });
|
|
63629
|
+
if (!r || r.status >= 300) return `owner post refused (${r?.status || "?"}): ${r?.body?.error || ""}`;
|
|
63630
|
+
this.log(`owner post from the outbox door published: ${r.body?.object || r.body?.id || activity.type}`);
|
|
63631
|
+
return null;
|
|
63632
|
+
}
|
|
63226
63633
|
receiptVouchesFor(...a) {
|
|
63227
63634
|
return receiptVouchesFor(this, ...a);
|
|
63228
63635
|
}
|
|
@@ -63258,6 +63665,9 @@ var Intake = class {
|
|
|
63258
63665
|
onUndo(...a) {
|
|
63259
63666
|
return onUndo(this, ...a);
|
|
63260
63667
|
}
|
|
63668
|
+
onBlock(...a) {
|
|
63669
|
+
return onBlock(this, ...a);
|
|
63670
|
+
}
|
|
63261
63671
|
onCreate(...a) {
|
|
63262
63672
|
return onCreate(this, ...a);
|
|
63263
63673
|
}
|
|
@@ -63306,6 +63716,859 @@ var Intake = class {
|
|
|
63306
63716
|
}
|
|
63307
63717
|
};
|
|
63308
63718
|
|
|
63719
|
+
// lib/core/social.mjs
|
|
63720
|
+
var social_exports = {};
|
|
63721
|
+
__export(social_exports, {
|
|
63722
|
+
admitRequest: () => admitRequest,
|
|
63723
|
+
announceModeration: () => announceModeration,
|
|
63724
|
+
applyModeration: () => applyModeration,
|
|
63725
|
+
blockActor: () => blockActor,
|
|
63726
|
+
confirmDelegation: () => confirmDelegation,
|
|
63727
|
+
deleteNote: () => deleteNote,
|
|
63728
|
+
ejectFollower: () => ejectFollower,
|
|
63729
|
+
favourite: () => favourite,
|
|
63730
|
+
followActor: () => followActor,
|
|
63731
|
+
followHandle: () => followHandle,
|
|
63732
|
+
lookupWebFinger: () => lookupWebFinger,
|
|
63733
|
+
pinStatus: () => pinStatus,
|
|
63734
|
+
reblog: () => reblog,
|
|
63735
|
+
refuseRequest: () => refuseRequest,
|
|
63736
|
+
resolveHandle: () => resolveHandle,
|
|
63737
|
+
retractAnnouncement: () => retractAnnouncement,
|
|
63738
|
+
selfLink: () => selfLink,
|
|
63739
|
+
unblockActor: () => unblockActor,
|
|
63740
|
+
unfavourite: () => unfavourite,
|
|
63741
|
+
unfollowActor: () => unfollowActor,
|
|
63742
|
+
unreblog: () => unreblog,
|
|
63743
|
+
votePoll: () => votePoll
|
|
63744
|
+
});
|
|
63745
|
+
init_node_crypto();
|
|
63746
|
+
init_wire();
|
|
63747
|
+
async function lookupWebFinger(acct, { fetchImpl = null } = {}) {
|
|
63748
|
+
const clean = String(acct || "").replace(/^acct:/, "");
|
|
63749
|
+
const at = clean.lastIndexOf("@");
|
|
63750
|
+
if (at < 1) return null;
|
|
63751
|
+
const [user, host] = [clean.slice(0, at), clean.slice(at + 1)];
|
|
63752
|
+
if (!host || /[/\\?#]/.test(host)) return null;
|
|
63753
|
+
const { safeFetch: safeFetch2, readCapped: readCapped3 } = await Promise.resolve().then(() => (init_safefetch(), safefetch_exports));
|
|
63754
|
+
const url = `https://${host}/.well-known/webfinger?resource=${encodeURIComponent(`acct:${clean}`)}`;
|
|
63755
|
+
const headers = { accept: "application/jrd+json, application/json" };
|
|
63756
|
+
const res = await (fetchImpl ? fetchImpl(url, { headers }) : safeFetch2(url, { headers })).catch(() => null);
|
|
63757
|
+
if (!res || res.status >= 400) return null;
|
|
63758
|
+
let jrd2;
|
|
63759
|
+
try {
|
|
63760
|
+
jrd2 = JSON.parse(await readCapped3(res, 256 * 1024));
|
|
63761
|
+
} catch {
|
|
63762
|
+
return null;
|
|
63763
|
+
}
|
|
63764
|
+
const subject = String(jrd2?.subject || "").replace(/^acct:/, "").toLowerCase();
|
|
63765
|
+
if (subject && subject !== clean.toLowerCase()) {
|
|
63766
|
+
return null;
|
|
63767
|
+
}
|
|
63768
|
+
return jrd2;
|
|
63769
|
+
}
|
|
63770
|
+
var selfLink = (jrd2) => jrd2?.links?.find((l) => l.rel === "self" && /application\/(activity\+json|ld\+json)/.test(l.type || ""));
|
|
63771
|
+
async function confirmDelegation(asked, doc, lookup2 = lookupWebFinger) {
|
|
63772
|
+
let home;
|
|
63773
|
+
try {
|
|
63774
|
+
home = new URL(doc.id).host.toLowerCase();
|
|
63775
|
+
} catch {
|
|
63776
|
+
throw new Error(`actor id is not a URL (${doc.id})`);
|
|
63777
|
+
}
|
|
63778
|
+
if (home === asked.toLowerCase()) return;
|
|
63779
|
+
const user = doc.preferredUsername;
|
|
63780
|
+
if (!user) {
|
|
63781
|
+
throw new Error(`${asked} points at ${doc.id} on another host, which names no username to confirm it by`);
|
|
63782
|
+
}
|
|
63783
|
+
const back = await lookup2(`acct:${user}@${home}`);
|
|
63784
|
+
if (selfLink(back)?.href !== doc.id) {
|
|
63785
|
+
throw new Error(`${asked} claims ${doc.id}, but ${home} does not agree that is @${user}@${home} \u2014 refusing to take one host's word for another's actor`);
|
|
63786
|
+
}
|
|
63787
|
+
}
|
|
63788
|
+
async function resolveHandle(agent2, handle7) {
|
|
63789
|
+
const clean = String(handle7 || "").replace(/^@/, "");
|
|
63790
|
+
if (!/^[^@]+@[^@]+$/.test(clean)) throw new Error("handle must look like user@host");
|
|
63791
|
+
if (agent2.store.isBlocked("https://" + clean.split("@")[1] + "/")) throw new Error("domain is blocked");
|
|
63792
|
+
const remote = agent2.intake?.deliverer?.signedFetch ? (u, i) => agent2.intake.deliverer.signedFetch(u, i) : null;
|
|
63793
|
+
const lookup2 = (a) => lookupWebFinger(a, { fetchImpl: remote });
|
|
63794
|
+
const jrd2 = await lookup2("acct:" + clean);
|
|
63795
|
+
const self2 = selfLink(jrd2);
|
|
63796
|
+
if (!self2?.href) throw new Error(`webfinger found no actor for ${clean}`);
|
|
63797
|
+
const doc = await agent2.intake.fetchAP(self2.href);
|
|
63798
|
+
if (!doc?.id) throw new Error(`actor document unusable for ${clean}`);
|
|
63799
|
+
if (agent2.store.isBlocked(doc.id)) throw new Error("actor is blocked");
|
|
63800
|
+
await confirmDelegation(clean.slice(clean.lastIndexOf("@") + 1), doc, lookup2);
|
|
63801
|
+
await cacheCounts(agent2, doc);
|
|
63802
|
+
return doc;
|
|
63803
|
+
}
|
|
63804
|
+
async function cacheCounts(agent2, doc) {
|
|
63805
|
+
const total = async (url) => {
|
|
63806
|
+
if (!url) return null;
|
|
63807
|
+
try {
|
|
63808
|
+
const res = await agent2.intake.fetchAP(url);
|
|
63809
|
+
return Number.isInteger(res?.totalItems) ? res.totalItems : null;
|
|
63810
|
+
} catch {
|
|
63811
|
+
return null;
|
|
63812
|
+
}
|
|
63813
|
+
};
|
|
63814
|
+
const [followers, following] = await Promise.all([total(doc.followers), total(doc.following)]);
|
|
63815
|
+
if (followers === null && following === null) return;
|
|
63816
|
+
const actors = agent2.store.getActors();
|
|
63817
|
+
if (!actors[doc.id]) return;
|
|
63818
|
+
actors[doc.id] = { ...actors[doc.id], counts: { followers, following } };
|
|
63819
|
+
agent2.store.write("actors.json", actors);
|
|
63820
|
+
}
|
|
63821
|
+
async function followActor(agent2, actorUrl, { publish = true, doc: fetched = null } = {}) {
|
|
63822
|
+
const doc = fetched || await agent2.intake.fetchAP(actorUrl);
|
|
63823
|
+
if (!doc?.inbox) throw new Error(`actor document unusable (${actorUrl})`);
|
|
63824
|
+
if (agent2.store.isBlocked(doc.id)) throw new Error("actor is blocked");
|
|
63825
|
+
const contacts = agent2.store.getContacts();
|
|
63826
|
+
let rec = contacts.following.find((f) => f.actor === doc.id);
|
|
63827
|
+
if (!rec) {
|
|
63828
|
+
rec = { actor: doc.id, inbox: doc.inbox, accepted: false };
|
|
63829
|
+
contacts.following.push(rec);
|
|
63830
|
+
}
|
|
63831
|
+
const follow = followActivity({ urls: agent2.publisher.urls, targetActor: doc.id, serial: Date.now() });
|
|
63832
|
+
rec.followActivity = follow;
|
|
63833
|
+
agent2.store.setContacts(contacts);
|
|
63834
|
+
await agent2.deliverer.deliver(doc.inbox, follow);
|
|
63835
|
+
if (publish) await agent2.publisher.publishCollections({ following: true, pending: true });
|
|
63836
|
+
return doc;
|
|
63837
|
+
}
|
|
63838
|
+
async function followHandle(agent2, handle7) {
|
|
63839
|
+
const doc = await resolveHandle(agent2, handle7);
|
|
63840
|
+
await followActor(agent2, doc.id);
|
|
63841
|
+
const clean = String(handle7 || "").replace(/^@/, "");
|
|
63842
|
+
const contacts = agent2.store.getContacts();
|
|
63843
|
+
const rec = contacts.following.find((f) => f.actor === doc.id);
|
|
63844
|
+
if (rec && !rec.handle) {
|
|
63845
|
+
rec.handle = clean;
|
|
63846
|
+
agent2.store.setContacts(contacts);
|
|
63847
|
+
}
|
|
63848
|
+
return { ok: true, actor: doc.id };
|
|
63849
|
+
}
|
|
63850
|
+
async function unfollowActor(agent2, actor) {
|
|
63851
|
+
const contacts = agent2.store.getContacts();
|
|
63852
|
+
const rec = contacts.following.find((f) => f.actor === actor);
|
|
63853
|
+
if (!rec) throw new Error("not following that actor");
|
|
63854
|
+
if (rec.followActivity) {
|
|
63855
|
+
await agent2.deliverer.deliver(
|
|
63856
|
+
rec.inbox,
|
|
63857
|
+
undoActivity({ urls: agent2.publisher.urls, activity: rec.followActivity, serial: Date.now() })
|
|
63858
|
+
);
|
|
63859
|
+
}
|
|
63860
|
+
contacts.following = contacts.following.filter((f) => f.actor !== actor);
|
|
63861
|
+
agent2.store.setContacts(contacts);
|
|
63862
|
+
await agent2.publisher.publishCollections({ following: true, pending: true });
|
|
63863
|
+
return { ok: true };
|
|
63864
|
+
}
|
|
63865
|
+
async function favourite(agent2, s) {
|
|
63866
|
+
if (s.favourited) return s;
|
|
63867
|
+
const doc = await agent2.intake.fetchAP(s.actor);
|
|
63868
|
+
if (!doc?.inbox) throw new Error("author inbox unavailable");
|
|
63869
|
+
const act = likeActivity({ urls: agent2.publisher.urls, noteId: s.noteId, serial: Date.now() });
|
|
63870
|
+
await agent2.deliverer.deliver(doc.endpoints?.sharedInbox || doc.inbox, act);
|
|
63871
|
+
return agent2.store.updateStatus(s.noteId, { favourited: true, likeActivity: act });
|
|
63872
|
+
}
|
|
63873
|
+
async function unfavourite(agent2, s) {
|
|
63874
|
+
if (s.likeActivity) {
|
|
63875
|
+
const doc = await agent2.intake.fetchAP(s.actor).catch(() => null);
|
|
63876
|
+
if (doc?.inbox) {
|
|
63877
|
+
await agent2.deliverer.deliver(
|
|
63878
|
+
doc.endpoints?.sharedInbox || doc.inbox,
|
|
63879
|
+
undoActivity({ urls: agent2.publisher.urls, activity: s.likeActivity, serial: Date.now() })
|
|
63880
|
+
);
|
|
63881
|
+
}
|
|
63882
|
+
}
|
|
63883
|
+
return agent2.store.updateStatus(s.noteId, { favourited: false, likeActivity: void 0 });
|
|
63884
|
+
}
|
|
63885
|
+
async function reblog(agent2, s) {
|
|
63886
|
+
if (s.reblogged) return s;
|
|
63887
|
+
const { urls } = agent2.publisher;
|
|
63888
|
+
const act = announceActivity({ urls, object: s.noteId, serial: Date.now() });
|
|
63889
|
+
const inboxes = new Set(agent2.store.getContacts().followers.map((f) => f.sharedInbox || f.inbox).filter(Boolean));
|
|
63890
|
+
if (s.actor !== urls.actor) {
|
|
63891
|
+
const doc = await agent2.intake.fetchAP(s.actor).catch(() => null);
|
|
63892
|
+
if (doc?.inbox) inboxes.add(doc.endpoints?.sharedInbox || doc.inbox);
|
|
63893
|
+
}
|
|
63894
|
+
await agent2.deliverer.deliverToAll([...inboxes], act);
|
|
63895
|
+
const updated = agent2.store.updateStatus(s.noteId, { reblogged: true, announceActivity: act });
|
|
63896
|
+
await agent2.publisher.recordOutbox(act);
|
|
63897
|
+
if (await agent2.store.commit?.() === false) agent2.log(`boost sent but its timeline row was refused: ${s.noteId}`);
|
|
63898
|
+
return updated;
|
|
63899
|
+
}
|
|
63900
|
+
async function unreblog(agent2, s) {
|
|
63901
|
+
if (s.announceActivity) {
|
|
63902
|
+
const undo = undoActivity({ urls: agent2.publisher.urls, activity: s.announceActivity, serial: Date.now() });
|
|
63903
|
+
const inboxes = new Set(agent2.store.getContacts().followers.map((f) => f.sharedInbox || f.inbox).filter(Boolean));
|
|
63904
|
+
const doc = await agent2.intake.fetchAP(s.actor).catch(() => null);
|
|
63905
|
+
if (doc?.inbox && s.actor !== agent2.publisher.urls.actor) inboxes.add(doc.endpoints?.sharedInbox || doc.inbox);
|
|
63906
|
+
await agent2.deliverer.deliverToAll([...inboxes], undo);
|
|
63907
|
+
await agent2.publisher.unrecordOutbox((i) => i?.id === s.announceActivity.id);
|
|
63908
|
+
}
|
|
63909
|
+
return agent2.store.updateStatus(s.noteId, { reblogged: false, announceActivity: void 0 });
|
|
63910
|
+
}
|
|
63911
|
+
async function ejectFollower(agent2, actor) {
|
|
63912
|
+
const { urls } = agent2.publisher;
|
|
63913
|
+
const contacts = agent2.store.getContacts();
|
|
63914
|
+
const rec = contacts.followers.find((f) => f.actor === actor);
|
|
63915
|
+
if (!rec) throw new Error("not a member");
|
|
63916
|
+
dropFollower(contacts, actor, "ejected");
|
|
63917
|
+
agent2.store.setContacts(contacts);
|
|
63918
|
+
const muted = agent2.store.getMuted();
|
|
63919
|
+
muted.actors = [.../* @__PURE__ */ new Set([...muted.actors, actor])];
|
|
63920
|
+
agent2.store.setMuted(muted);
|
|
63921
|
+
await agent2.publisher.publishCollections({ followers: true });
|
|
63922
|
+
await announceModeration(
|
|
63923
|
+
agent2,
|
|
63924
|
+
blockActivity({ urls, targetActor: actor, serial: Date.now() })
|
|
63925
|
+
).catch((e) => agent2.log?.(`moderation announce failed: ${e.message}`));
|
|
63926
|
+
if (rec.bsky) {
|
|
63927
|
+
await agent2.bskygroup?.blockDid(rec.bsky.did).catch((e) => agent2.log?.(`bluesky block failed for ${rec.bsky.handle}: ${e.message}`));
|
|
63928
|
+
return { ok: true, actor, told: true };
|
|
63929
|
+
}
|
|
63930
|
+
if (rec.inbox) {
|
|
63931
|
+
await agent2.deliverer.deliver(rec.inbox, rejectActivity({
|
|
63932
|
+
urls,
|
|
63933
|
+
followActivity: { id: rec.followId, type: "Follow", actor, object: urls.actor },
|
|
63934
|
+
serial: Date.now()
|
|
63935
|
+
}));
|
|
63936
|
+
}
|
|
63937
|
+
return { ok: true, actor, told: !!rec.inbox };
|
|
63938
|
+
}
|
|
63939
|
+
async function admitRequest(agent2, actor, { publish = true } = {}) {
|
|
63940
|
+
const reqs = agent2.store.getRequests();
|
|
63941
|
+
const req = reqs.find((r) => r.actor === actor);
|
|
63942
|
+
if (!req) throw new Error("no such request");
|
|
63943
|
+
const contacts = agent2.store.getContacts();
|
|
63944
|
+
if (!contacts.followers.some((f) => f.actor === actor)) {
|
|
63945
|
+
const bridgedDid = /^https:\/\/bsky\.brid\.gy\/ap\/(did:[^/]+)$/.exec(actor)?.[1];
|
|
63946
|
+
if (bridgedDid) contacts.followers = contacts.followers.filter((f) => f.bsky?.did !== bridgedDid);
|
|
63947
|
+
contacts.followers.push(req.bsky ? { actor, bsky: req.bsky } : { actor, inbox: req.inbox, sharedInbox: req.sharedInbox, followId: req.activity?.id });
|
|
63948
|
+
agent2.store.setContacts(contacts);
|
|
63949
|
+
agent2.store.addNotification({ type: "follow", actor, ...req.bsky ? { bsky: true } : {} });
|
|
63950
|
+
}
|
|
63951
|
+
agent2.store.setRequests(reqs.filter((r) => r.actor !== actor));
|
|
63952
|
+
if (req.bsky) {
|
|
63953
|
+
await agent2.bskygroup?.nudgeOnce(contacts.followers.find((f) => f.actor === actor));
|
|
63954
|
+
return { ok: true, actor };
|
|
63955
|
+
}
|
|
63956
|
+
if (publish) await agent2.publisher.publishCollections({ followers: true, pending: true });
|
|
63957
|
+
await agent2.deliverer.deliver(req.inbox, acceptActivity({
|
|
63958
|
+
urls: agent2.publisher.urls,
|
|
63959
|
+
followActivity: req.activity,
|
|
63960
|
+
serial: Date.now()
|
|
63961
|
+
}));
|
|
63962
|
+
return { ok: true, actor };
|
|
63963
|
+
}
|
|
63964
|
+
async function refuseRequest(agent2, actor) {
|
|
63965
|
+
const reqs = agent2.store.getRequests();
|
|
63966
|
+
const req = reqs.find((r) => r.actor === actor);
|
|
63967
|
+
if (!req) throw new Error("no such request");
|
|
63968
|
+
agent2.store.setRequests(reqs.filter((r) => r.actor !== actor));
|
|
63969
|
+
await agent2.publisher?.publishCollections({ pending: true }).catch(() => {
|
|
63970
|
+
});
|
|
63971
|
+
if (req.bsky) {
|
|
63972
|
+
await agent2.bskygroup?.blockDid(req.bsky.did).catch((e) => agent2.log?.(`bluesky block failed for ${req.bsky.handle}: ${e.message}`));
|
|
63973
|
+
return { ok: true, actor };
|
|
63974
|
+
}
|
|
63975
|
+
await agent2.deliverer.deliver(req.inbox, rejectActivity({
|
|
63976
|
+
urls: agent2.publisher.urls,
|
|
63977
|
+
followActivity: req.activity,
|
|
63978
|
+
serial: Date.now()
|
|
63979
|
+
}));
|
|
63980
|
+
return { ok: true, actor };
|
|
63981
|
+
}
|
|
63982
|
+
async function retractAnnouncement(agent2, noteId) {
|
|
63983
|
+
return agent2.intake.retract(noteId);
|
|
63984
|
+
}
|
|
63985
|
+
async function votePoll(agent2, s, choices) {
|
|
63986
|
+
const { urls } = agent2.publisher;
|
|
63987
|
+
const poll = s.poll || {};
|
|
63988
|
+
const opts = poll.options || [];
|
|
63989
|
+
if (poll.closed || poll.expiresAt && Date.parse(poll.expiresAt) < Date.now()) {
|
|
63990
|
+
return { ok: false, error: "this poll has closed" };
|
|
63991
|
+
}
|
|
63992
|
+
if (poll.voted) return { ok: false, error: "already voted" };
|
|
63993
|
+
if (s.actor === urls.actor) return { ok: false, error: "this is your own poll" };
|
|
63994
|
+
const picks = [...new Set(choices)].filter((i) => i >= 0 && i < opts.length);
|
|
63995
|
+
if (!picks.length) return { ok: false, error: "invalid choice" };
|
|
63996
|
+
if (!poll.multiple && picks.length > 1) return { ok: false, error: "this poll takes one choice" };
|
|
63997
|
+
const author = await agent2.intake.fetchAP(s.actor).catch(() => null);
|
|
63998
|
+
const inbox = author?.endpoints?.sharedInbox || author?.inbox;
|
|
63999
|
+
if (!inbox) return { ok: false, error: "could not reach the poll author" };
|
|
64000
|
+
const published = (/* @__PURE__ */ new Date()).toISOString();
|
|
64001
|
+
for (const i of picks) {
|
|
64002
|
+
const id = urls.notes + published.slice(0, 10) + "-vote-" + node_crypto_default.randomBytes(4).toString("hex");
|
|
64003
|
+
const note = {
|
|
64004
|
+
"@context": AS_CTX,
|
|
64005
|
+
id,
|
|
64006
|
+
type: "Note",
|
|
64007
|
+
attributedTo: urls.actor,
|
|
64008
|
+
name: opts[i].title,
|
|
64009
|
+
inReplyTo: s.noteId,
|
|
64010
|
+
published,
|
|
64011
|
+
to: [s.actor]
|
|
64012
|
+
};
|
|
64013
|
+
const create = {
|
|
64014
|
+
"@context": AS_CTX,
|
|
64015
|
+
id: createActivityId(id),
|
|
64016
|
+
type: "Create",
|
|
64017
|
+
actor: urls.actor,
|
|
64018
|
+
published,
|
|
64019
|
+
to: [s.actor],
|
|
64020
|
+
object: note
|
|
64021
|
+
};
|
|
64022
|
+
await write4(agent2.remote, id, note);
|
|
64023
|
+
await writeCreate(agent2.remote, create.id, create);
|
|
64024
|
+
await agent2.deliverer.deliverToAll([inbox], create);
|
|
64025
|
+
}
|
|
64026
|
+
agent2.store.updateStatus(s.noteId, {
|
|
64027
|
+
poll: {
|
|
64028
|
+
...poll,
|
|
64029
|
+
voted: true,
|
|
64030
|
+
ownVotes: picks,
|
|
64031
|
+
options: opts.map((o, j) => picks.includes(j) ? { ...o, votes: (o.votes || 0) + 1 } : o)
|
|
64032
|
+
}
|
|
64033
|
+
});
|
|
64034
|
+
return { ok: true };
|
|
64035
|
+
}
|
|
64036
|
+
async function deleteNote(agent2, s) {
|
|
64037
|
+
const { urls } = agent2.publisher;
|
|
64038
|
+
const inboxes = agent2.store.getContacts().followers.map((f) => f.sharedInbox || f.inbox).filter(Boolean);
|
|
64039
|
+
await agent2.deliverer.deliverToAll(inboxes, deleteActivity({ urls, noteId: s.noteId }));
|
|
64040
|
+
const stuck = [];
|
|
64041
|
+
const deletedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
64042
|
+
try {
|
|
64043
|
+
await writeTombstone2(agent2.remote, s.noteId, noteTombstone(s.noteId, deletedAt));
|
|
64044
|
+
} catch {
|
|
64045
|
+
stuck.push(s.noteId);
|
|
64046
|
+
}
|
|
64047
|
+
if (!await dropCreate(agent2.remote, createActivityId(s.noteId))) {
|
|
64048
|
+
stuck.push(createActivityId(s.noteId));
|
|
64049
|
+
}
|
|
64050
|
+
if (stuck.length) {
|
|
64051
|
+
agent2.log?.(`delete ${s.noteId}: the pod kept ${stuck.length} document(s) \u2014 the post is STILL PUBLISHED`);
|
|
64052
|
+
return {
|
|
64053
|
+
ok: false,
|
|
64054
|
+
stillPublished: stuck,
|
|
64055
|
+
error: "the pod would not remove the post \u2014 it is still published, and has been kept here so you can try again"
|
|
64056
|
+
};
|
|
64057
|
+
}
|
|
64058
|
+
await dropReplies(agent2.remote, repliesId(s.noteId));
|
|
64059
|
+
if (s.atproto?.uri && agent2.atproto?.connected()) {
|
|
64060
|
+
await agent2.atproto.deleteCrossPost(s.atproto.uri).then(() => agent2.log?.(`bluesky mirror deleted: ${s.atproto.uri}`)).catch((e) => agent2.log?.(`bluesky mirror not deleted (${e.message}): ${s.atproto.uri}`));
|
|
64061
|
+
}
|
|
64062
|
+
await agent2.publisher.unrecordOutbox((i) => i === s.noteId || i?.id && i.id === s.announceActivity?.id);
|
|
64063
|
+
agent2.store.removeStatus(s.noteId);
|
|
64064
|
+
return { ok: true };
|
|
64065
|
+
}
|
|
64066
|
+
async function announceModeration(agent2, activity) {
|
|
64067
|
+
if (agent2.store.getConfig()?.kind !== "group") return null;
|
|
64068
|
+
const { urls } = agent2.publisher;
|
|
64069
|
+
const act = announceActivity({ urls, object: activity, serial: Date.now(), audience: urls.actor });
|
|
64070
|
+
const inboxes = [...new Set(agent2.store.getContacts().followers.map((f) => f.sharedInbox || f.inbox).filter(Boolean))];
|
|
64071
|
+
await agent2.deliverer.deliverToAll(inboxes, act);
|
|
64072
|
+
await agent2.publisher.recordOutbox(act);
|
|
64073
|
+
return act;
|
|
64074
|
+
}
|
|
64075
|
+
async function blockActor(agent2, actorUrl) {
|
|
64076
|
+
if (!/^https?:\/\/\S+$/.test(String(actorUrl || ""))) throw new Error("actor must be a URL");
|
|
64077
|
+
const b = agent2.store.getBlocklist();
|
|
64078
|
+
if (!b.actors.includes(actorUrl)) b.actors.push(actorUrl);
|
|
64079
|
+
agent2.store.setBlocklist(b);
|
|
64080
|
+
await unfollowActor(agent2, actorUrl).catch(() => {
|
|
64081
|
+
});
|
|
64082
|
+
await agent2.publisher?.publishCollections({ blocked: true }).catch(() => {
|
|
64083
|
+
});
|
|
64084
|
+
await announceModeration(
|
|
64085
|
+
agent2,
|
|
64086
|
+
blockActivity({ urls: agent2.publisher.urls, targetActor: actorUrl, serial: Date.now() })
|
|
64087
|
+
).catch((e) => agent2.log?.(`moderation announce failed: ${e.message}`));
|
|
64088
|
+
return { ok: true, actor: actorUrl };
|
|
64089
|
+
}
|
|
64090
|
+
async function unblockActor(agent2, actorUrl) {
|
|
64091
|
+
const b = agent2.store.getBlocklist();
|
|
64092
|
+
b.actors = b.actors.filter((a) => a !== actorUrl);
|
|
64093
|
+
agent2.store.setBlocklist(b);
|
|
64094
|
+
await agent2.publisher?.publishCollections({ blocked: true }).catch(() => {
|
|
64095
|
+
});
|
|
64096
|
+
await announceModeration(agent2, undoActivity({
|
|
64097
|
+
urls: agent2.publisher.urls,
|
|
64098
|
+
activity: { type: "Block", actor: agent2.publisher.urls.actor, object: actorUrl },
|
|
64099
|
+
serial: Date.now()
|
|
64100
|
+
})).catch((e) => agent2.log?.(`moderation announce failed: ${e.message}`));
|
|
64101
|
+
return { ok: true, actor: actorUrl };
|
|
64102
|
+
}
|
|
64103
|
+
async function applyModeration(agent2, entry) {
|
|
64104
|
+
const { urls } = agent2.publisher;
|
|
64105
|
+
const target = typeof entry.activity?.object === "string" ? entry.activity.object : entry.activity?.object?.id;
|
|
64106
|
+
switch (entry.type) {
|
|
64107
|
+
case "Block": {
|
|
64108
|
+
if (!target) throw new Error("the queued Block names nobody");
|
|
64109
|
+
const member = agent2.store.getContacts().followers.some((f) => f.actor === target);
|
|
64110
|
+
return member ? ejectFollower(agent2, target) : blockActor(agent2, target);
|
|
64111
|
+
}
|
|
64112
|
+
case "Undo": {
|
|
64113
|
+
const inner = entry.activity?.object;
|
|
64114
|
+
const unbanned = typeof inner === "object" && inner?.type === "Block" ? typeof inner.object === "string" ? inner.object : inner.object?.id : null;
|
|
64115
|
+
if (!unbanned) throw new Error("the queued Undo does not name a Block");
|
|
64116
|
+
const muted = agent2.store.getMuted();
|
|
64117
|
+
muted.actors = muted.actors.filter((a) => a !== unbanned);
|
|
64118
|
+
agent2.store.setMuted(muted);
|
|
64119
|
+
return unblockActor(agent2, unbanned);
|
|
64120
|
+
}
|
|
64121
|
+
case "Delete": {
|
|
64122
|
+
if (!target) throw new Error("the queued Delete names nothing");
|
|
64123
|
+
return agent2.intake.retract(target);
|
|
64124
|
+
}
|
|
64125
|
+
case "Add":
|
|
64126
|
+
case "Remove": {
|
|
64127
|
+
if (!target) throw new Error("the queued roster change names nobody");
|
|
64128
|
+
const cfg = { ...agent2.store.getConfig() };
|
|
64129
|
+
const prev = cfg.moderators || [];
|
|
64130
|
+
cfg.moderators = entry.type === "Add" ? [.../* @__PURE__ */ new Set([...prev, target])] : prev.filter((m) => m !== target);
|
|
64131
|
+
agent2.store.setConfig(cfg);
|
|
64132
|
+
if (agent2.publisher) agent2.publisher.config.moderators = cfg.moderators;
|
|
64133
|
+
await agent2.publisher.publishProfile();
|
|
64134
|
+
await announceModeration(agent2, addRemoveActivity({
|
|
64135
|
+
urls,
|
|
64136
|
+
type: entry.type,
|
|
64137
|
+
object: target,
|
|
64138
|
+
target: urls.moderators,
|
|
64139
|
+
serial: Date.now()
|
|
64140
|
+
})).catch((e) => agent2.log?.(`moderation announce failed: ${e.message}`));
|
|
64141
|
+
return { ok: true, moderators: cfg.moderators };
|
|
64142
|
+
}
|
|
64143
|
+
default:
|
|
64144
|
+
throw new Error(`no way to apply a queued ${entry.type}`);
|
|
64145
|
+
}
|
|
64146
|
+
}
|
|
64147
|
+
async function pinStatus(agent2, s, pinned) {
|
|
64148
|
+
if (s.actor !== agent2.publisher.urls.actor) throw new Error("not your status");
|
|
64149
|
+
const updated = agent2.store.updateStatus(s.noteId, { pinned: !!pinned });
|
|
64150
|
+
await agent2.publisher.publishFeatured().catch((e) => agent2.log?.(`featured: ${e.message}`));
|
|
64151
|
+
agent2.publisher.publishProfile?.().catch(() => {
|
|
64152
|
+
});
|
|
64153
|
+
return updated || s;
|
|
64154
|
+
}
|
|
64155
|
+
|
|
64156
|
+
// lib/client/c2s.mjs
|
|
64157
|
+
init_wire();
|
|
64158
|
+
var MAX_BODY = 512 * 1024;
|
|
64159
|
+
var MAX_INBOX_PAGE = 500;
|
|
64160
|
+
var ACTIVITY_TYPES = /* @__PURE__ */ new Set([
|
|
64161
|
+
"Create",
|
|
64162
|
+
"Update",
|
|
64163
|
+
"Delete",
|
|
64164
|
+
"Follow",
|
|
64165
|
+
"Like",
|
|
64166
|
+
"Announce",
|
|
64167
|
+
"Undo",
|
|
64168
|
+
"Block",
|
|
64169
|
+
"Add",
|
|
64170
|
+
"Remove",
|
|
64171
|
+
"Accept",
|
|
64172
|
+
"Reject",
|
|
64173
|
+
"Move"
|
|
64174
|
+
]);
|
|
64175
|
+
var arr = (v) => v === void 0 || v === null ? [] : Array.isArray(v) ? v : [v];
|
|
64176
|
+
var idOf = (v) => typeof v === "string" ? v : v?.id || null;
|
|
64177
|
+
function readBody(req) {
|
|
64178
|
+
return new Promise((resolve2, reject) => {
|
|
64179
|
+
let size = 0;
|
|
64180
|
+
const chunks = [];
|
|
64181
|
+
req.on("data", (c) => {
|
|
64182
|
+
size += c.length;
|
|
64183
|
+
if (size > MAX_BODY) {
|
|
64184
|
+
reject(new Error("body too large"));
|
|
64185
|
+
req.destroy();
|
|
64186
|
+
return;
|
|
64187
|
+
}
|
|
64188
|
+
chunks.push(c);
|
|
64189
|
+
});
|
|
64190
|
+
req.on("end", () => resolve2(Buffer.concat(chunks).toString("utf8")));
|
|
64191
|
+
req.on("error", reject);
|
|
64192
|
+
});
|
|
64193
|
+
}
|
|
64194
|
+
var C2S = class {
|
|
64195
|
+
constructor({ agent: agent2, log: log2 = console.log, auth }) {
|
|
64196
|
+
this.agent = agent2;
|
|
64197
|
+
this.log = log2;
|
|
64198
|
+
this.auth = auth;
|
|
64199
|
+
}
|
|
64200
|
+
get store() {
|
|
64201
|
+
return this.agent.store;
|
|
64202
|
+
}
|
|
64203
|
+
get urls() {
|
|
64204
|
+
return this.agent.publisher?.urls;
|
|
64205
|
+
}
|
|
64206
|
+
send(res, status2, obj, headers = {}) {
|
|
64207
|
+
res.writeHead(status2, {
|
|
64208
|
+
"content-type": "application/json; charset=utf-8",
|
|
64209
|
+
"cache-control": "no-store",
|
|
64210
|
+
...headers
|
|
64211
|
+
});
|
|
64212
|
+
res.end(JSON.stringify(obj));
|
|
64213
|
+
return true;
|
|
64214
|
+
}
|
|
64215
|
+
byIri(iri) {
|
|
64216
|
+
return iri ? this.store.getStatuses().find((s) => s.noteId === iri) : null;
|
|
64217
|
+
}
|
|
64218
|
+
/** The months the archive holds, newest first. One container listing. */
|
|
64219
|
+
async archiveMonths() {
|
|
64220
|
+
const archive = this.agent.intake?.archive;
|
|
64221
|
+
if (!archive) return [];
|
|
64222
|
+
const { names } = await archive.list("");
|
|
64223
|
+
return (names || []).map((n) => n.replace(/\/$/u, "")).filter((n) => /^\d{4}-\d{2}$/u.test(n)).sort().reverse();
|
|
64224
|
+
}
|
|
64225
|
+
/**
|
|
64226
|
+
* The owner's own inbox, §5.2. Paged by month because that is how the
|
|
64227
|
+
* archive is stored, so a page costs one listing and a read per item and no
|
|
64228
|
+
* page is dearer for another month being large.
|
|
64229
|
+
*/
|
|
64230
|
+
async sendInbox(res, url) {
|
|
64231
|
+
const id = `${this.urls.base}ap/inbox`;
|
|
64232
|
+
const page2 = url?.searchParams?.get("page") || null;
|
|
64233
|
+
let months;
|
|
64234
|
+
try {
|
|
64235
|
+
months = await this.archiveMonths();
|
|
64236
|
+
} catch (e) {
|
|
64237
|
+
return this.send(res, 502, { error: `the archive could not be read: ${e.message}` });
|
|
64238
|
+
}
|
|
64239
|
+
if (!page2) {
|
|
64240
|
+
if (!months.length && this.store.getConfig()?.archiveInbox === false) {
|
|
64241
|
+
this.log("inbox read: nothing to show \u2014 this identity does not keep what it receives");
|
|
64242
|
+
}
|
|
64243
|
+
return this.send(res, 200, {
|
|
64244
|
+
"@context": AS_CTX,
|
|
64245
|
+
id,
|
|
64246
|
+
type: "OrderedCollection",
|
|
64247
|
+
...months.length ? { first: `${id}?page=${months[0]}` } : { orderedItems: [] }
|
|
64248
|
+
});
|
|
64249
|
+
}
|
|
64250
|
+
if (!/^\d{4}-\d{2}$/u.test(page2)) {
|
|
64251
|
+
return this.send(res, 400, { error: "page names a month, written 2026-09" });
|
|
64252
|
+
}
|
|
64253
|
+
const archive = this.agent.intake?.archive;
|
|
64254
|
+
let names = [];
|
|
64255
|
+
try {
|
|
64256
|
+
({ names } = await archive.list(`${page2}/`));
|
|
64257
|
+
} catch (e) {
|
|
64258
|
+
return this.send(res, 502, { error: `the archive could not be read: ${e.message}` });
|
|
64259
|
+
}
|
|
64260
|
+
const files = (names || []).filter((n) => n.endsWith(".json")).sort();
|
|
64261
|
+
if (files.length > MAX_INBOX_PAGE) {
|
|
64262
|
+
this.log(`inbox read: ${page2} holds ${files.length} items; serving the first ${MAX_INBOX_PAGE}`);
|
|
64263
|
+
}
|
|
64264
|
+
const kept = [];
|
|
64265
|
+
for (const file of files.slice(0, MAX_INBOX_PAGE)) {
|
|
64266
|
+
const read2 = await archive.read(`${page2}/${file}`, { accept: "*/*" });
|
|
64267
|
+
if (!read2?.ok || !read2.body) continue;
|
|
64268
|
+
try {
|
|
64269
|
+
const record = JSON.parse(read2.body);
|
|
64270
|
+
kept.push({ at: record.receivedAt || "", activity: JSON.parse(record.raw) });
|
|
64271
|
+
} catch {
|
|
64272
|
+
}
|
|
64273
|
+
}
|
|
64274
|
+
kept.sort((a, b) => String(b.at).localeCompare(String(a.at)));
|
|
64275
|
+
const older = months.filter((m) => m < page2)[0] || null;
|
|
64276
|
+
return this.send(res, 200, {
|
|
64277
|
+
"@context": AS_CTX,
|
|
64278
|
+
id: `${id}?page=${page2}`,
|
|
64279
|
+
type: "OrderedCollectionPage",
|
|
64280
|
+
partOf: id,
|
|
64281
|
+
...older ? { next: `${id}?page=${older}` } : {},
|
|
64282
|
+
orderedItems: kept.map((k) => k.activity)
|
|
64283
|
+
});
|
|
64284
|
+
}
|
|
64285
|
+
async handle(req, res, pathname, url) {
|
|
64286
|
+
if (pathname !== "/ap/outbox" && pathname !== "/ap/actor" && pathname !== "/ap/inbox") return false;
|
|
64287
|
+
if (req.method === "OPTIONS") {
|
|
64288
|
+
res.writeHead(204, pathname === "/ap/inbox" ? { allow: "GET, OPTIONS" } : { allow: "GET, POST, OPTIONS", "accept-post": "application/ld+json, application/activity+json" });
|
|
64289
|
+
res.end();
|
|
64290
|
+
return true;
|
|
64291
|
+
}
|
|
64292
|
+
if (!this.agent.configured() || !this.urls) {
|
|
64293
|
+
return this.send(res, 409, { error: "agent not configured" });
|
|
64294
|
+
}
|
|
64295
|
+
if (pathname === "/ap/inbox") {
|
|
64296
|
+
if (req.method !== "GET" && req.method !== "HEAD") {
|
|
64297
|
+
return this.send(res, 405, { error: "deliveries go to this actor's inbox on the pod, which the actor document names; this address is the owner reading their own" });
|
|
64298
|
+
}
|
|
64299
|
+
const reader = await this.auth(req, pathname);
|
|
64300
|
+
if (!reader.ok) return this.send(res, reader.status, { error: reader.error }, reader.headers || {});
|
|
64301
|
+
return this.sendInbox(res, url);
|
|
64302
|
+
}
|
|
64303
|
+
if (req.method === "GET" || req.method === "HEAD") {
|
|
64304
|
+
const target = pathname === "/ap/actor" ? this.urls.actor : this.urls.outbox;
|
|
64305
|
+
res.writeHead(303, { location: target, "cache-control": "no-store" });
|
|
64306
|
+
res.end();
|
|
64307
|
+
return true;
|
|
64308
|
+
}
|
|
64309
|
+
if (pathname !== "/ap/outbox" || req.method !== "POST") {
|
|
64310
|
+
return this.send(res, 405, { error: "POST the outbox; GET redirects to the pod" });
|
|
64311
|
+
}
|
|
64312
|
+
const who = await this.auth(req, pathname);
|
|
64313
|
+
if (!who.ok) return this.send(res, who.status, { error: who.error }, who.headers || {});
|
|
64314
|
+
if (this.agent.viewer) {
|
|
64315
|
+
const took = await this.agent.requestTakeover?.();
|
|
64316
|
+
if (!took) return this.send(res, 503, { error: "another agent is active for this pod \u2014 takeover failed, try again" });
|
|
64317
|
+
}
|
|
64318
|
+
let activity;
|
|
64319
|
+
let raw = null;
|
|
64320
|
+
try {
|
|
64321
|
+
const body = await readBody(req);
|
|
64322
|
+
try {
|
|
64323
|
+
raw = JSON.parse(body);
|
|
64324
|
+
} catch {
|
|
64325
|
+
raw = null;
|
|
64326
|
+
}
|
|
64327
|
+
const read2 = await readLenient(body);
|
|
64328
|
+
activity = read2.view ?? read2.doc;
|
|
64329
|
+
} catch (e) {
|
|
64330
|
+
return this.send(res, 400, { error: `unreadable body: ${e.message}` });
|
|
64331
|
+
}
|
|
64332
|
+
if (!activity || typeof activity !== "object" || Array.isArray(activity) || !activity.type) {
|
|
64333
|
+
return this.send(res, 400, { error: "a typed ActivityStreams object is required" });
|
|
64334
|
+
}
|
|
64335
|
+
const slug = safeSlug(req.headers.slug) || null;
|
|
64336
|
+
const r = await this.dispatch(activity, { slug, raw });
|
|
64337
|
+
return this.send(res, r.status, r.body, r.headers);
|
|
64338
|
+
}
|
|
64339
|
+
// Addressing → the facade's four visibilities, inverting the table the
|
|
64340
|
+
// composer writes (wire.addressing). Nothing stated is a public post — what
|
|
64341
|
+
// every client means by a post with no audience chosen, and what a client
|
|
64342
|
+
// that never addresses (dokieli's annotations) needs.
|
|
64343
|
+
visibilityOf(activity, object) {
|
|
64344
|
+
const to = arr(activity.to ?? object?.to).map(idOf);
|
|
64345
|
+
const cc = arr(activity.cc ?? object?.cc).map(idOf);
|
|
64346
|
+
if (!to.length && !cc.length) return "public";
|
|
64347
|
+
if (to.includes(PUBLIC)) return "public";
|
|
64348
|
+
if (cc.includes(PUBLIC)) return "unlisted";
|
|
64349
|
+
if (to.includes(this.urls.followers)) return "private";
|
|
64350
|
+
return "direct";
|
|
64351
|
+
}
|
|
64352
|
+
async dispatch(activity, { slug = null, raw = null } = {}) {
|
|
64353
|
+
const reply = (status2, body, headers = {}) => ({ status: status2, body, headers });
|
|
64354
|
+
if (!activity || typeof activity !== "object" || Array.isArray(activity) || !activity.type) {
|
|
64355
|
+
return reply(400, { error: "a typed ActivityStreams object is required" });
|
|
64356
|
+
}
|
|
64357
|
+
if (!ACTIVITY_TYPES.has(activity.type)) {
|
|
64358
|
+
activity = { type: "Create", object: activity, to: activity.to, cc: activity.cc };
|
|
64359
|
+
}
|
|
64360
|
+
try {
|
|
64361
|
+
return await this._dispatch(activity, { slug, raw, reply });
|
|
64362
|
+
} catch (e) {
|
|
64363
|
+
this.log(`c2s ${activity?.type}: ${e.message}`);
|
|
64364
|
+
return reply(422, { error: e.message || String(e) });
|
|
64365
|
+
}
|
|
64366
|
+
}
|
|
64367
|
+
// The actors a client addressed by id, beyond the audience words: the ones
|
|
64368
|
+
// in to/cc are listed and delivered to; the ones in bto/bcc are delivered
|
|
64369
|
+
// to and never listed (§6).
|
|
64370
|
+
addressedActors(activity, object) {
|
|
64371
|
+
const pick = (...fields) => [...new Set(fields.flatMap((f) => arr(activity[f] ?? object?.[f]).map(idOf)))].filter((a) => typeof a === "string" && /^https?:\/\//u.test(a) && a !== PUBLIC && a !== this.urls.followers && a !== this.urls.actor);
|
|
64372
|
+
return { also: pick("to", "cc"), deliverTo: pick("bto", "bcc") };
|
|
64373
|
+
}
|
|
64374
|
+
async _dispatch(activity, { slug, raw, reply }) {
|
|
64375
|
+
const agent2 = this.agent;
|
|
64376
|
+
const object = typeof activity.object === "object" && activity.object !== null ? activity.object : null;
|
|
64377
|
+
const objectId = idOf(activity.object);
|
|
64378
|
+
switch (activity.type) {
|
|
64379
|
+
case "Create": {
|
|
64380
|
+
const makes = object?.type || "Note";
|
|
64381
|
+
if (!object) return reply(422, { error: "a Create carries the object it creates" });
|
|
64382
|
+
const visibility = this.visibilityOf(activity, object);
|
|
64383
|
+
const { also, deliverTo } = this.addressedActors(activity, object);
|
|
64384
|
+
if (makes !== "Note" && makes !== "Question") {
|
|
64385
|
+
const asSent = raw && typeof raw === "object" && !Array.isArray(raw) ? ACTIVITY_TYPES.has(raw.type) ? raw.object && typeof raw.object === "object" ? raw.object : null : raw : null;
|
|
64386
|
+
const made = await agent2.publisher.publishObject(asSent || object, { visibility, slug, also, deliverTo });
|
|
64387
|
+
return reply(201, { id: made.createId, object: made.id }, { location: made.createId });
|
|
64388
|
+
}
|
|
64389
|
+
const text = String(object.source?.content ?? object.content ?? "");
|
|
64390
|
+
if (!text.trim()) return reply(422, { error: "the note has no content" });
|
|
64391
|
+
if (makes === "Question") {
|
|
64392
|
+
const one = arr(object.oneOf);
|
|
64393
|
+
const many = arr(object.anyOf);
|
|
64394
|
+
const titles = (one.length ? one : many).map((c) => String(c?.name ?? "").trim()).filter(Boolean);
|
|
64395
|
+
try {
|
|
64396
|
+
const question = await agent2.publisher.publishQuestion(text, {
|
|
64397
|
+
options: titles,
|
|
64398
|
+
multiple: !one.length && many.length > 0,
|
|
64399
|
+
expiresAt: object.endTime || null,
|
|
64400
|
+
inReplyTo: idOf(object.inReplyTo) || void 0,
|
|
64401
|
+
visibility,
|
|
64402
|
+
spoilerText: object.summary || null,
|
|
64403
|
+
sensitive: object.sensitive === true
|
|
64404
|
+
});
|
|
64405
|
+
return reply(
|
|
64406
|
+
201,
|
|
64407
|
+
{ id: createActivityId(question.id), object: question.id },
|
|
64408
|
+
{ location: createActivityId(question.id) }
|
|
64409
|
+
);
|
|
64410
|
+
} catch (e) {
|
|
64411
|
+
return reply(422, { error: e.message });
|
|
64412
|
+
}
|
|
64413
|
+
}
|
|
64414
|
+
const attachments = arr(object.attachment).map((a) => ({
|
|
64415
|
+
url: a?.url,
|
|
64416
|
+
mediaType: a?.mediaType,
|
|
64417
|
+
...a?.name ? { description: a.name } : {}
|
|
64418
|
+
})).filter((a) => a.url);
|
|
64419
|
+
const note = await agent2.publisher.publishNote(text, {
|
|
64420
|
+
inReplyTo: idOf(object.inReplyTo) || void 0,
|
|
64421
|
+
attachments,
|
|
64422
|
+
visibility,
|
|
64423
|
+
spoilerText: object.summary || null,
|
|
64424
|
+
sensitive: object.sensitive === true,
|
|
64425
|
+
slug,
|
|
64426
|
+
also,
|
|
64427
|
+
deliverTo
|
|
64428
|
+
});
|
|
64429
|
+
return reply(
|
|
64430
|
+
201,
|
|
64431
|
+
{ id: createActivityId(note.id), object: note.id },
|
|
64432
|
+
{ location: createActivityId(note.id) }
|
|
64433
|
+
);
|
|
64434
|
+
}
|
|
64435
|
+
case "Update": {
|
|
64436
|
+
if (objectId === this.urls.actor) {
|
|
64437
|
+
return reply(422, { error: "edit the profile on the admin surface; actor updates are not taken here" });
|
|
64438
|
+
}
|
|
64439
|
+
const s = this.byIri(objectId);
|
|
64440
|
+
if (!s) return reply(404, { error: "no such note here" });
|
|
64441
|
+
if (s.actor !== this.urls.actor || s.kind !== "post") {
|
|
64442
|
+
return reply(403, { error: "not your note" });
|
|
64443
|
+
}
|
|
64444
|
+
const text = String(object?.source?.content ?? object?.content ?? "");
|
|
64445
|
+
if (!text.trim()) return reply(422, { error: "the edit has no content" });
|
|
64446
|
+
const attachments = object?.attachment !== void 0 ? arr(object.attachment).map((a) => ({
|
|
64447
|
+
url: a?.url,
|
|
64448
|
+
mediaType: a?.mediaType,
|
|
64449
|
+
...a?.name ? { description: a.name } : {}
|
|
64450
|
+
})).filter((a) => a.url) : null;
|
|
64451
|
+
await agent2.publisher.updateNote(s, {
|
|
64452
|
+
content: text,
|
|
64453
|
+
spoilerText: object?.summary || null,
|
|
64454
|
+
sensitive: object?.sensitive === void 0 ? null : object.sensitive === true,
|
|
64455
|
+
attachments
|
|
64456
|
+
});
|
|
64457
|
+
return reply(200, { ok: true, object: s.noteId });
|
|
64458
|
+
}
|
|
64459
|
+
case "Delete": {
|
|
64460
|
+
if (objectId === this.urls.actor) {
|
|
64461
|
+
return reply(422, { error: "retiring the actor is done on the admin surface, where it asks twice" });
|
|
64462
|
+
}
|
|
64463
|
+
const s = this.byIri(objectId);
|
|
64464
|
+
if (!s) return reply(404, { error: "no such note here" });
|
|
64465
|
+
if (s.actor !== this.urls.actor || s.kind !== "post") {
|
|
64466
|
+
return reply(403, { error: "not your note" });
|
|
64467
|
+
}
|
|
64468
|
+
const r = await deleteNote(agent2, s);
|
|
64469
|
+
if (!r.ok) return reply(502, { error: r.error, stillPublished: r.stillPublished });
|
|
64470
|
+
return reply(200, { ok: true });
|
|
64471
|
+
}
|
|
64472
|
+
case "Follow": {
|
|
64473
|
+
if (!objectId) return reply(400, { error: "whom? object must name an actor" });
|
|
64474
|
+
if (/^acct:|^@|^[^/@]+@[^/@]+$/.test(objectId) && !/^https?:/.test(objectId)) {
|
|
64475
|
+
const r = await followHandle(agent2, objectId.replace(/^acct:/, ""));
|
|
64476
|
+
const rec2 = this.store.getContacts().following.find((f) => f.actor === r.actor);
|
|
64477
|
+
return reply(
|
|
64478
|
+
201,
|
|
64479
|
+
{ id: rec2?.followActivity?.id, object: r.actor },
|
|
64480
|
+
rec2?.followActivity?.id ? { location: rec2.followActivity.id } : {}
|
|
64481
|
+
);
|
|
64482
|
+
}
|
|
64483
|
+
const doc = await followActor(agent2, objectId);
|
|
64484
|
+
const rec = this.store.getContacts().following.find((f) => f.actor === doc.id);
|
|
64485
|
+
return reply(
|
|
64486
|
+
201,
|
|
64487
|
+
{ id: rec?.followActivity?.id, object: doc.id },
|
|
64488
|
+
rec?.followActivity?.id ? { location: rec.followActivity.id } : {}
|
|
64489
|
+
);
|
|
64490
|
+
}
|
|
64491
|
+
case "Like": {
|
|
64492
|
+
const s = this.byIri(objectId);
|
|
64493
|
+
if (!s) return reply(422, { error: "that note is not held here \u2014 like what the timeline holds" });
|
|
64494
|
+
const updated = await favourite(agent2, s);
|
|
64495
|
+
return reply(
|
|
64496
|
+
201,
|
|
64497
|
+
{ id: updated.likeActivity?.id, object: s.noteId },
|
|
64498
|
+
updated.likeActivity?.id ? { location: updated.likeActivity.id } : {}
|
|
64499
|
+
);
|
|
64500
|
+
}
|
|
64501
|
+
case "Announce": {
|
|
64502
|
+
const s = this.byIri(objectId);
|
|
64503
|
+
if (!s) return reply(422, { error: "that note is not held here \u2014 boost what the timeline holds" });
|
|
64504
|
+
const updated = await reblog(agent2, s);
|
|
64505
|
+
return reply(
|
|
64506
|
+
201,
|
|
64507
|
+
{ id: updated.announceActivity?.id, object: s.noteId },
|
|
64508
|
+
updated.announceActivity?.id ? { location: updated.announceActivity.id } : {}
|
|
64509
|
+
);
|
|
64510
|
+
}
|
|
64511
|
+
case "Undo": {
|
|
64512
|
+
const inner = object;
|
|
64513
|
+
const innerId = idOf(activity.object);
|
|
64514
|
+
if (inner?.type === "Block") {
|
|
64515
|
+
const target = idOf(inner.object);
|
|
64516
|
+
if (!target) return reply(400, { error: "unblock whom?" });
|
|
64517
|
+
await unblockActor(agent2, target);
|
|
64518
|
+
return reply(200, { ok: true, object: target });
|
|
64519
|
+
}
|
|
64520
|
+
const statuses = this.store.getStatuses();
|
|
64521
|
+
let s = innerId ? statuses.find((x) => x.likeActivity?.id === innerId) : null;
|
|
64522
|
+
if (!s && inner?.type === "Like") s = this.byIri(idOf(inner.object));
|
|
64523
|
+
if (s?.favourited) {
|
|
64524
|
+
const updated = await unfavourite(agent2, s);
|
|
64525
|
+
return reply(200, { ok: true, object: updated.noteId });
|
|
64526
|
+
}
|
|
64527
|
+
s = innerId ? statuses.find((x) => x.announceActivity?.id === innerId) : null;
|
|
64528
|
+
if (!s && inner?.type === "Announce") s = this.byIri(idOf(inner.object));
|
|
64529
|
+
if (s?.reblogged) {
|
|
64530
|
+
const updated = await unreblog(agent2, s);
|
|
64531
|
+
return reply(200, { ok: true, object: updated.noteId });
|
|
64532
|
+
}
|
|
64533
|
+
const following = this.store.getContacts().following;
|
|
64534
|
+
const rec = following.find((f) => f.followActivity?.id === innerId) || (inner?.type === "Follow" ? following.find((f) => f.actor === idOf(inner.object)) : null);
|
|
64535
|
+
if (rec) {
|
|
64536
|
+
await unfollowActor(agent2, rec.actor);
|
|
64537
|
+
return reply(200, { ok: true, object: rec.actor });
|
|
64538
|
+
}
|
|
64539
|
+
return reply(422, { error: "nothing here matches what that Undo names" });
|
|
64540
|
+
}
|
|
64541
|
+
case "Block": {
|
|
64542
|
+
if (!objectId) return reply(400, { error: "block whom? object must name an actor" });
|
|
64543
|
+
await blockActor(agent2, objectId);
|
|
64544
|
+
return reply(200, { ok: true, object: objectId });
|
|
64545
|
+
}
|
|
64546
|
+
case "Add":
|
|
64547
|
+
case "Remove": {
|
|
64548
|
+
if (idOf(activity.target) !== this.urls.featured) {
|
|
64549
|
+
return reply(422, { error: "the featured collection is the one Add/Remove edits here" });
|
|
64550
|
+
}
|
|
64551
|
+
const s = this.byIri(objectId);
|
|
64552
|
+
if (!s) return reply(404, { error: "no such note here" });
|
|
64553
|
+
const updated = await pinStatus(agent2, s, activity.type === "Add");
|
|
64554
|
+
return reply(200, { ok: true, object: updated.noteId, pinned: !!updated.pinned });
|
|
64555
|
+
}
|
|
64556
|
+
case "Accept":
|
|
64557
|
+
case "Reject": {
|
|
64558
|
+
const requester = object?.actor ? idOf(object.actor) : objectId;
|
|
64559
|
+
if (!requester) return reply(400, { error: "whose request? object must name the Follow or its actor" });
|
|
64560
|
+
const r = activity.type === "Accept" ? await admitRequest(agent2, requester).catch((e) => ({ error: e.message })) : await refuseRequest(agent2, requester).catch((e) => ({ error: e.message }));
|
|
64561
|
+
if (r.error) return reply(404, { error: r.error });
|
|
64562
|
+
return reply(200, { ok: true, object: requester });
|
|
64563
|
+
}
|
|
64564
|
+
case "Move":
|
|
64565
|
+
return reply(422, { error: "moving the account is done on the admin surface, where it asks twice" });
|
|
64566
|
+
default:
|
|
64567
|
+
return reply(422, { error: `no handler for ${activity.type}` });
|
|
64568
|
+
}
|
|
64569
|
+
}
|
|
64570
|
+
};
|
|
64571
|
+
|
|
63309
64572
|
// lib/core/lease.mjs
|
|
63310
64573
|
init_node_crypto();
|
|
63311
64574
|
var TTL_MS = 3e5;
|
|
@@ -63618,7 +64881,7 @@ function pollParams(body) {
|
|
|
63618
64881
|
function htmlToText(html) {
|
|
63619
64882
|
return String(html).replace(/<br\s*\/?>/gi, "\n").replace(/<\/p>\s*<p[^>]*>/gi, "\n\n").replace(/<[^>]+>/g, "").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/'/g, "'").trim();
|
|
63620
64883
|
}
|
|
63621
|
-
function
|
|
64884
|
+
function readBody2(req) {
|
|
63622
64885
|
return new Promise((resolve2, reject) => {
|
|
63623
64886
|
let data = "";
|
|
63624
64887
|
req.on("data", (c) => {
|
|
@@ -63913,7 +65176,7 @@ function rateLimited(api) {
|
|
|
63913
65176
|
async function handle(api, ctx) {
|
|
63914
65177
|
const { req, res, pathname, url, send } = ctx;
|
|
63915
65178
|
if (pathname === "/api/v1/apps" && req.method === "POST") {
|
|
63916
|
-
const body = await
|
|
65179
|
+
const body = await readBody2(req);
|
|
63917
65180
|
const redirectUris = parseRedirects(body.redirect_uris);
|
|
63918
65181
|
const app = api.registerApp({
|
|
63919
65182
|
name: body.client_name,
|
|
@@ -63936,7 +65199,7 @@ async function handle(api, ctx) {
|
|
|
63936
65199
|
let params = url.searchParams;
|
|
63937
65200
|
let body = null;
|
|
63938
65201
|
if (req.method === "POST") {
|
|
63939
|
-
body = await
|
|
65202
|
+
body = await readBody2(req);
|
|
63940
65203
|
params = new URLSearchParams(body);
|
|
63941
65204
|
}
|
|
63942
65205
|
const redirect = params.get("redirect_uri") || "";
|
|
@@ -64011,7 +65274,7 @@ async function handle(api, ctx) {
|
|
|
64011
65274
|
return true;
|
|
64012
65275
|
}
|
|
64013
65276
|
if (pathname === "/oauth/token" && req.method === "POST") {
|
|
64014
|
-
const body = await
|
|
65277
|
+
const body = await readBody2(req);
|
|
64015
65278
|
const app = api.findApp(body.client_id || "");
|
|
64016
65279
|
if (!app && body.code_verifier && /^https:\/\//iu.test(String(body.client_id || ""))) {
|
|
64017
65280
|
const rec = api.consumeCode(body.code || "");
|
|
@@ -64091,7 +65354,7 @@ async function handle(api, ctx) {
|
|
|
64091
65354
|
});
|
|
64092
65355
|
}
|
|
64093
65356
|
if (pathname === "/oauth/revoke" && req.method === "POST") {
|
|
64094
|
-
const body = await
|
|
65357
|
+
const body = await readBody2(req).catch(() => ({}));
|
|
64095
65358
|
const gone = body?.token;
|
|
64096
65359
|
if (gone) {
|
|
64097
65360
|
const kept = api.tokenRecords().filter((r) => r.token !== gone);
|
|
@@ -64103,442 +65366,6 @@ async function handle(api, ctx) {
|
|
|
64103
65366
|
return false;
|
|
64104
65367
|
}
|
|
64105
65368
|
|
|
64106
|
-
// lib/core/social.mjs
|
|
64107
|
-
var social_exports = {};
|
|
64108
|
-
__export(social_exports, {
|
|
64109
|
-
admitRequest: () => admitRequest,
|
|
64110
|
-
announceModeration: () => announceModeration,
|
|
64111
|
-
applyModeration: () => applyModeration,
|
|
64112
|
-
blockActor: () => blockActor,
|
|
64113
|
-
confirmDelegation: () => confirmDelegation,
|
|
64114
|
-
deleteNote: () => deleteNote,
|
|
64115
|
-
ejectFollower: () => ejectFollower,
|
|
64116
|
-
favourite: () => favourite,
|
|
64117
|
-
followActor: () => followActor,
|
|
64118
|
-
followHandle: () => followHandle,
|
|
64119
|
-
lookupWebFinger: () => lookupWebFinger,
|
|
64120
|
-
pinStatus: () => pinStatus,
|
|
64121
|
-
reblog: () => reblog,
|
|
64122
|
-
refuseRequest: () => refuseRequest,
|
|
64123
|
-
resolveHandle: () => resolveHandle,
|
|
64124
|
-
retractAnnouncement: () => retractAnnouncement,
|
|
64125
|
-
selfLink: () => selfLink,
|
|
64126
|
-
unblockActor: () => unblockActor,
|
|
64127
|
-
unfavourite: () => unfavourite,
|
|
64128
|
-
unfollowActor: () => unfollowActor,
|
|
64129
|
-
unreblog: () => unreblog,
|
|
64130
|
-
votePoll: () => votePoll
|
|
64131
|
-
});
|
|
64132
|
-
init_node_crypto();
|
|
64133
|
-
init_wire();
|
|
64134
|
-
async function lookupWebFinger(acct, { fetchImpl = null } = {}) {
|
|
64135
|
-
const clean = String(acct || "").replace(/^acct:/, "");
|
|
64136
|
-
const at = clean.lastIndexOf("@");
|
|
64137
|
-
if (at < 1) return null;
|
|
64138
|
-
const [user, host] = [clean.slice(0, at), clean.slice(at + 1)];
|
|
64139
|
-
if (!host || /[/\\?#]/.test(host)) return null;
|
|
64140
|
-
const { safeFetch: safeFetch2, readCapped: readCapped3 } = await Promise.resolve().then(() => (init_safefetch(), safefetch_exports));
|
|
64141
|
-
const url = `https://${host}/.well-known/webfinger?resource=${encodeURIComponent(`acct:${clean}`)}`;
|
|
64142
|
-
const headers = { accept: "application/jrd+json, application/json" };
|
|
64143
|
-
const res = await (fetchImpl ? fetchImpl(url, { headers }) : safeFetch2(url, { headers })).catch(() => null);
|
|
64144
|
-
if (!res || res.status >= 400) return null;
|
|
64145
|
-
let jrd2;
|
|
64146
|
-
try {
|
|
64147
|
-
jrd2 = JSON.parse(await readCapped3(res, 256 * 1024));
|
|
64148
|
-
} catch {
|
|
64149
|
-
return null;
|
|
64150
|
-
}
|
|
64151
|
-
const subject = String(jrd2?.subject || "").replace(/^acct:/, "").toLowerCase();
|
|
64152
|
-
if (subject && subject !== clean.toLowerCase()) {
|
|
64153
|
-
return null;
|
|
64154
|
-
}
|
|
64155
|
-
return jrd2;
|
|
64156
|
-
}
|
|
64157
|
-
var selfLink = (jrd2) => jrd2?.links?.find((l) => l.rel === "self" && /application\/(activity\+json|ld\+json)/.test(l.type || ""));
|
|
64158
|
-
async function confirmDelegation(asked, doc, lookup2 = lookupWebFinger) {
|
|
64159
|
-
let home;
|
|
64160
|
-
try {
|
|
64161
|
-
home = new URL(doc.id).host.toLowerCase();
|
|
64162
|
-
} catch {
|
|
64163
|
-
throw new Error(`actor id is not a URL (${doc.id})`);
|
|
64164
|
-
}
|
|
64165
|
-
if (home === asked.toLowerCase()) return;
|
|
64166
|
-
const user = doc.preferredUsername;
|
|
64167
|
-
if (!user) {
|
|
64168
|
-
throw new Error(`${asked} points at ${doc.id} on another host, which names no username to confirm it by`);
|
|
64169
|
-
}
|
|
64170
|
-
const back = await lookup2(`acct:${user}@${home}`);
|
|
64171
|
-
if (selfLink(back)?.href !== doc.id) {
|
|
64172
|
-
throw new Error(`${asked} claims ${doc.id}, but ${home} does not agree that is @${user}@${home} \u2014 refusing to take one host's word for another's actor`);
|
|
64173
|
-
}
|
|
64174
|
-
}
|
|
64175
|
-
async function resolveHandle(agent2, handle7) {
|
|
64176
|
-
const clean = String(handle7 || "").replace(/^@/, "");
|
|
64177
|
-
if (!/^[^@]+@[^@]+$/.test(clean)) throw new Error("handle must look like user@host");
|
|
64178
|
-
if (agent2.store.isBlocked("https://" + clean.split("@")[1] + "/")) throw new Error("domain is blocked");
|
|
64179
|
-
const remote = agent2.intake?.deliverer?.signedFetch ? (u, i) => agent2.intake.deliverer.signedFetch(u, i) : null;
|
|
64180
|
-
const lookup2 = (a) => lookupWebFinger(a, { fetchImpl: remote });
|
|
64181
|
-
const jrd2 = await lookup2("acct:" + clean);
|
|
64182
|
-
const self2 = selfLink(jrd2);
|
|
64183
|
-
if (!self2?.href) throw new Error(`webfinger found no actor for ${clean}`);
|
|
64184
|
-
const doc = await agent2.intake.fetchAP(self2.href);
|
|
64185
|
-
if (!doc?.id) throw new Error(`actor document unusable for ${clean}`);
|
|
64186
|
-
if (agent2.store.isBlocked(doc.id)) throw new Error("actor is blocked");
|
|
64187
|
-
await confirmDelegation(clean.slice(clean.lastIndexOf("@") + 1), doc, lookup2);
|
|
64188
|
-
await cacheCounts(agent2, doc);
|
|
64189
|
-
return doc;
|
|
64190
|
-
}
|
|
64191
|
-
async function cacheCounts(agent2, doc) {
|
|
64192
|
-
const total = async (url) => {
|
|
64193
|
-
if (!url) return null;
|
|
64194
|
-
try {
|
|
64195
|
-
const res = await agent2.intake.fetchAP(url);
|
|
64196
|
-
return Number.isInteger(res?.totalItems) ? res.totalItems : null;
|
|
64197
|
-
} catch {
|
|
64198
|
-
return null;
|
|
64199
|
-
}
|
|
64200
|
-
};
|
|
64201
|
-
const [followers, following] = await Promise.all([total(doc.followers), total(doc.following)]);
|
|
64202
|
-
if (followers === null && following === null) return;
|
|
64203
|
-
const actors = agent2.store.getActors();
|
|
64204
|
-
if (!actors[doc.id]) return;
|
|
64205
|
-
actors[doc.id] = { ...actors[doc.id], counts: { followers, following } };
|
|
64206
|
-
agent2.store.write("actors.json", actors);
|
|
64207
|
-
}
|
|
64208
|
-
async function followActor(agent2, actorUrl, { publish = true, doc: fetched = null } = {}) {
|
|
64209
|
-
const doc = fetched || await agent2.intake.fetchAP(actorUrl);
|
|
64210
|
-
if (!doc?.inbox) throw new Error(`actor document unusable (${actorUrl})`);
|
|
64211
|
-
if (agent2.store.isBlocked(doc.id)) throw new Error("actor is blocked");
|
|
64212
|
-
const contacts = agent2.store.getContacts();
|
|
64213
|
-
let rec = contacts.following.find((f) => f.actor === doc.id);
|
|
64214
|
-
if (!rec) {
|
|
64215
|
-
rec = { actor: doc.id, inbox: doc.inbox, accepted: false };
|
|
64216
|
-
contacts.following.push(rec);
|
|
64217
|
-
}
|
|
64218
|
-
const follow = followActivity({ urls: agent2.publisher.urls, targetActor: doc.id, serial: Date.now() });
|
|
64219
|
-
rec.followActivity = follow;
|
|
64220
|
-
agent2.store.setContacts(contacts);
|
|
64221
|
-
await agent2.deliverer.deliver(doc.inbox, follow);
|
|
64222
|
-
if (publish) await agent2.publisher.publishCollections({ following: true, pending: true });
|
|
64223
|
-
return doc;
|
|
64224
|
-
}
|
|
64225
|
-
async function followHandle(agent2, handle7) {
|
|
64226
|
-
const doc = await resolveHandle(agent2, handle7);
|
|
64227
|
-
await followActor(agent2, doc.id);
|
|
64228
|
-
const clean = String(handle7 || "").replace(/^@/, "");
|
|
64229
|
-
const contacts = agent2.store.getContacts();
|
|
64230
|
-
const rec = contacts.following.find((f) => f.actor === doc.id);
|
|
64231
|
-
if (rec && !rec.handle) {
|
|
64232
|
-
rec.handle = clean;
|
|
64233
|
-
agent2.store.setContacts(contacts);
|
|
64234
|
-
}
|
|
64235
|
-
return { ok: true, actor: doc.id };
|
|
64236
|
-
}
|
|
64237
|
-
async function unfollowActor(agent2, actor) {
|
|
64238
|
-
const contacts = agent2.store.getContacts();
|
|
64239
|
-
const rec = contacts.following.find((f) => f.actor === actor);
|
|
64240
|
-
if (!rec) throw new Error("not following that actor");
|
|
64241
|
-
if (rec.followActivity) {
|
|
64242
|
-
await agent2.deliverer.deliver(
|
|
64243
|
-
rec.inbox,
|
|
64244
|
-
undoActivity({ urls: agent2.publisher.urls, activity: rec.followActivity, serial: Date.now() })
|
|
64245
|
-
);
|
|
64246
|
-
}
|
|
64247
|
-
contacts.following = contacts.following.filter((f) => f.actor !== actor);
|
|
64248
|
-
agent2.store.setContacts(contacts);
|
|
64249
|
-
await agent2.publisher.publishCollections({ following: true, pending: true });
|
|
64250
|
-
return { ok: true };
|
|
64251
|
-
}
|
|
64252
|
-
async function favourite(agent2, s) {
|
|
64253
|
-
if (s.favourited) return s;
|
|
64254
|
-
const doc = await agent2.intake.fetchAP(s.actor);
|
|
64255
|
-
if (!doc?.inbox) throw new Error("author inbox unavailable");
|
|
64256
|
-
const act = likeActivity({ urls: agent2.publisher.urls, noteId: s.noteId, serial: Date.now() });
|
|
64257
|
-
await agent2.deliverer.deliver(doc.endpoints?.sharedInbox || doc.inbox, act);
|
|
64258
|
-
return agent2.store.updateStatus(s.noteId, { favourited: true, likeActivity: act });
|
|
64259
|
-
}
|
|
64260
|
-
async function unfavourite(agent2, s) {
|
|
64261
|
-
if (s.likeActivity) {
|
|
64262
|
-
const doc = await agent2.intake.fetchAP(s.actor).catch(() => null);
|
|
64263
|
-
if (doc?.inbox) {
|
|
64264
|
-
await agent2.deliverer.deliver(
|
|
64265
|
-
doc.endpoints?.sharedInbox || doc.inbox,
|
|
64266
|
-
undoActivity({ urls: agent2.publisher.urls, activity: s.likeActivity, serial: Date.now() })
|
|
64267
|
-
);
|
|
64268
|
-
}
|
|
64269
|
-
}
|
|
64270
|
-
return agent2.store.updateStatus(s.noteId, { favourited: false, likeActivity: void 0 });
|
|
64271
|
-
}
|
|
64272
|
-
async function reblog(agent2, s) {
|
|
64273
|
-
if (s.reblogged) return s;
|
|
64274
|
-
const { urls } = agent2.publisher;
|
|
64275
|
-
const act = announceActivity({ urls, object: s.noteId, serial: Date.now() });
|
|
64276
|
-
const inboxes = new Set(agent2.store.getContacts().followers.map((f) => f.sharedInbox || f.inbox).filter(Boolean));
|
|
64277
|
-
if (s.actor !== urls.actor) {
|
|
64278
|
-
const doc = await agent2.intake.fetchAP(s.actor).catch(() => null);
|
|
64279
|
-
if (doc?.inbox) inboxes.add(doc.endpoints?.sharedInbox || doc.inbox);
|
|
64280
|
-
}
|
|
64281
|
-
await agent2.deliverer.deliverToAll([...inboxes], act);
|
|
64282
|
-
const updated = agent2.store.updateStatus(s.noteId, { reblogged: true, announceActivity: act });
|
|
64283
|
-
await agent2.publisher.recordOutbox(act);
|
|
64284
|
-
return updated;
|
|
64285
|
-
}
|
|
64286
|
-
async function unreblog(agent2, s) {
|
|
64287
|
-
if (s.announceActivity) {
|
|
64288
|
-
const undo = undoActivity({ urls: agent2.publisher.urls, activity: s.announceActivity, serial: Date.now() });
|
|
64289
|
-
const inboxes = new Set(agent2.store.getContacts().followers.map((f) => f.sharedInbox || f.inbox).filter(Boolean));
|
|
64290
|
-
const doc = await agent2.intake.fetchAP(s.actor).catch(() => null);
|
|
64291
|
-
if (doc?.inbox && s.actor !== agent2.publisher.urls.actor) inboxes.add(doc.endpoints?.sharedInbox || doc.inbox);
|
|
64292
|
-
await agent2.deliverer.deliverToAll([...inboxes], undo);
|
|
64293
|
-
await agent2.publisher.unrecordOutbox((i) => i?.id === s.announceActivity.id);
|
|
64294
|
-
}
|
|
64295
|
-
return agent2.store.updateStatus(s.noteId, { reblogged: false, announceActivity: void 0 });
|
|
64296
|
-
}
|
|
64297
|
-
async function ejectFollower(agent2, actor) {
|
|
64298
|
-
const { urls } = agent2.publisher;
|
|
64299
|
-
const contacts = agent2.store.getContacts();
|
|
64300
|
-
const rec = contacts.followers.find((f) => f.actor === actor);
|
|
64301
|
-
if (!rec) throw new Error("not a member");
|
|
64302
|
-
dropFollower(contacts, actor, "ejected");
|
|
64303
|
-
agent2.store.setContacts(contacts);
|
|
64304
|
-
const muted = agent2.store.getMuted();
|
|
64305
|
-
muted.actors = [.../* @__PURE__ */ new Set([...muted.actors, actor])];
|
|
64306
|
-
agent2.store.setMuted(muted);
|
|
64307
|
-
await agent2.publisher.publishCollections({ followers: true });
|
|
64308
|
-
await announceModeration(
|
|
64309
|
-
agent2,
|
|
64310
|
-
blockActivity({ urls, targetActor: actor, serial: Date.now() })
|
|
64311
|
-
).catch((e) => agent2.log?.(`moderation announce failed: ${e.message}`));
|
|
64312
|
-
if (rec.bsky) {
|
|
64313
|
-
await agent2.bskygroup?.blockDid(rec.bsky.did).catch((e) => agent2.log?.(`bluesky block failed for ${rec.bsky.handle}: ${e.message}`));
|
|
64314
|
-
return { ok: true, actor, told: true };
|
|
64315
|
-
}
|
|
64316
|
-
if (rec.inbox) {
|
|
64317
|
-
await agent2.deliverer.deliver(rec.inbox, rejectActivity({
|
|
64318
|
-
urls,
|
|
64319
|
-
followActivity: { id: rec.followId, type: "Follow", actor, object: urls.actor },
|
|
64320
|
-
serial: Date.now()
|
|
64321
|
-
}));
|
|
64322
|
-
}
|
|
64323
|
-
return { ok: true, actor, told: !!rec.inbox };
|
|
64324
|
-
}
|
|
64325
|
-
async function admitRequest(agent2, actor, { publish = true } = {}) {
|
|
64326
|
-
const reqs = agent2.store.getRequests();
|
|
64327
|
-
const req = reqs.find((r) => r.actor === actor);
|
|
64328
|
-
if (!req) throw new Error("no such request");
|
|
64329
|
-
const contacts = agent2.store.getContacts();
|
|
64330
|
-
if (!contacts.followers.some((f) => f.actor === actor)) {
|
|
64331
|
-
const bridgedDid = /^https:\/\/bsky\.brid\.gy\/ap\/(did:[^/]+)$/.exec(actor)?.[1];
|
|
64332
|
-
if (bridgedDid) contacts.followers = contacts.followers.filter((f) => f.bsky?.did !== bridgedDid);
|
|
64333
|
-
contacts.followers.push(req.bsky ? { actor, bsky: req.bsky } : { actor, inbox: req.inbox, sharedInbox: req.sharedInbox, followId: req.activity?.id });
|
|
64334
|
-
agent2.store.setContacts(contacts);
|
|
64335
|
-
agent2.store.addNotification({ type: "follow", actor, ...req.bsky ? { bsky: true } : {} });
|
|
64336
|
-
}
|
|
64337
|
-
agent2.store.setRequests(reqs.filter((r) => r.actor !== actor));
|
|
64338
|
-
if (req.bsky) {
|
|
64339
|
-
await agent2.bskygroup?.nudgeOnce(contacts.followers.find((f) => f.actor === actor));
|
|
64340
|
-
return { ok: true, actor };
|
|
64341
|
-
}
|
|
64342
|
-
if (publish) await agent2.publisher.publishCollections({ followers: true, pending: true });
|
|
64343
|
-
await agent2.deliverer.deliver(req.inbox, acceptActivity({
|
|
64344
|
-
urls: agent2.publisher.urls,
|
|
64345
|
-
followActivity: req.activity,
|
|
64346
|
-
serial: Date.now()
|
|
64347
|
-
}));
|
|
64348
|
-
return { ok: true, actor };
|
|
64349
|
-
}
|
|
64350
|
-
async function refuseRequest(agent2, actor) {
|
|
64351
|
-
const reqs = agent2.store.getRequests();
|
|
64352
|
-
const req = reqs.find((r) => r.actor === actor);
|
|
64353
|
-
if (!req) throw new Error("no such request");
|
|
64354
|
-
agent2.store.setRequests(reqs.filter((r) => r.actor !== actor));
|
|
64355
|
-
await agent2.publisher?.publishCollections({ pending: true }).catch(() => {
|
|
64356
|
-
});
|
|
64357
|
-
if (req.bsky) {
|
|
64358
|
-
await agent2.bskygroup?.blockDid(req.bsky.did).catch((e) => agent2.log?.(`bluesky block failed for ${req.bsky.handle}: ${e.message}`));
|
|
64359
|
-
return { ok: true, actor };
|
|
64360
|
-
}
|
|
64361
|
-
await agent2.deliverer.deliver(req.inbox, rejectActivity({
|
|
64362
|
-
urls: agent2.publisher.urls,
|
|
64363
|
-
followActivity: req.activity,
|
|
64364
|
-
serial: Date.now()
|
|
64365
|
-
}));
|
|
64366
|
-
return { ok: true, actor };
|
|
64367
|
-
}
|
|
64368
|
-
async function retractAnnouncement(agent2, noteId) {
|
|
64369
|
-
return agent2.intake.retract(noteId);
|
|
64370
|
-
}
|
|
64371
|
-
async function votePoll(agent2, s, choices) {
|
|
64372
|
-
const { urls } = agent2.publisher;
|
|
64373
|
-
const poll = s.poll || {};
|
|
64374
|
-
const opts = poll.options || [];
|
|
64375
|
-
if (poll.closed || poll.expiresAt && Date.parse(poll.expiresAt) < Date.now()) {
|
|
64376
|
-
return { ok: false, error: "this poll has closed" };
|
|
64377
|
-
}
|
|
64378
|
-
if (poll.voted) return { ok: false, error: "already voted" };
|
|
64379
|
-
if (s.actor === urls.actor) return { ok: false, error: "this is your own poll" };
|
|
64380
|
-
const picks = [...new Set(choices)].filter((i) => i >= 0 && i < opts.length);
|
|
64381
|
-
if (!picks.length) return { ok: false, error: "invalid choice" };
|
|
64382
|
-
if (!poll.multiple && picks.length > 1) return { ok: false, error: "this poll takes one choice" };
|
|
64383
|
-
const author = await agent2.intake.fetchAP(s.actor).catch(() => null);
|
|
64384
|
-
const inbox = author?.endpoints?.sharedInbox || author?.inbox;
|
|
64385
|
-
if (!inbox) return { ok: false, error: "could not reach the poll author" };
|
|
64386
|
-
const published = (/* @__PURE__ */ new Date()).toISOString();
|
|
64387
|
-
for (const i of picks) {
|
|
64388
|
-
const id = urls.notes + published.slice(0, 10) + "-vote-" + node_crypto_default.randomBytes(4).toString("hex");
|
|
64389
|
-
const note = {
|
|
64390
|
-
"@context": AS_CTX,
|
|
64391
|
-
id,
|
|
64392
|
-
type: "Note",
|
|
64393
|
-
attributedTo: urls.actor,
|
|
64394
|
-
name: opts[i].title,
|
|
64395
|
-
inReplyTo: s.noteId,
|
|
64396
|
-
published,
|
|
64397
|
-
to: [s.actor]
|
|
64398
|
-
};
|
|
64399
|
-
const create = {
|
|
64400
|
-
"@context": AS_CTX,
|
|
64401
|
-
id: createActivityId(id),
|
|
64402
|
-
type: "Create",
|
|
64403
|
-
actor: urls.actor,
|
|
64404
|
-
published,
|
|
64405
|
-
to: [s.actor],
|
|
64406
|
-
object: note
|
|
64407
|
-
};
|
|
64408
|
-
await write4(agent2.remote, id, note);
|
|
64409
|
-
await writeCreate(agent2.remote, create.id, create);
|
|
64410
|
-
await agent2.deliverer.deliverToAll([inbox], create);
|
|
64411
|
-
}
|
|
64412
|
-
agent2.store.updateStatus(s.noteId, {
|
|
64413
|
-
poll: {
|
|
64414
|
-
...poll,
|
|
64415
|
-
voted: true,
|
|
64416
|
-
ownVotes: picks,
|
|
64417
|
-
options: opts.map((o, j) => picks.includes(j) ? { ...o, votes: (o.votes || 0) + 1 } : o)
|
|
64418
|
-
}
|
|
64419
|
-
});
|
|
64420
|
-
return { ok: true };
|
|
64421
|
-
}
|
|
64422
|
-
async function deleteNote(agent2, s) {
|
|
64423
|
-
const { urls } = agent2.publisher;
|
|
64424
|
-
const inboxes = agent2.store.getContacts().followers.map((f) => f.sharedInbox || f.inbox).filter(Boolean);
|
|
64425
|
-
await agent2.deliverer.deliverToAll(inboxes, deleteActivity({ urls, noteId: s.noteId }));
|
|
64426
|
-
const stuck = [];
|
|
64427
|
-
const deletedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
64428
|
-
try {
|
|
64429
|
-
await writeTombstone2(agent2.remote, s.noteId, noteTombstone(s.noteId, deletedAt));
|
|
64430
|
-
} catch {
|
|
64431
|
-
stuck.push(s.noteId);
|
|
64432
|
-
}
|
|
64433
|
-
if (!await dropCreate(agent2.remote, createActivityId(s.noteId))) {
|
|
64434
|
-
stuck.push(createActivityId(s.noteId));
|
|
64435
|
-
}
|
|
64436
|
-
if (stuck.length) {
|
|
64437
|
-
agent2.log?.(`delete ${s.noteId}: the pod kept ${stuck.length} document(s) \u2014 the post is STILL PUBLISHED`);
|
|
64438
|
-
return {
|
|
64439
|
-
ok: false,
|
|
64440
|
-
stillPublished: stuck,
|
|
64441
|
-
error: "the pod would not remove the post \u2014 it is still published, and has been kept here so you can try again"
|
|
64442
|
-
};
|
|
64443
|
-
}
|
|
64444
|
-
await dropReplies(agent2.remote, repliesId(s.noteId));
|
|
64445
|
-
if (s.atproto?.uri && agent2.atproto?.connected()) {
|
|
64446
|
-
await agent2.atproto.deleteCrossPost(s.atproto.uri).then(() => agent2.log?.(`bluesky mirror deleted: ${s.atproto.uri}`)).catch((e) => agent2.log?.(`bluesky mirror not deleted (${e.message}): ${s.atproto.uri}`));
|
|
64447
|
-
}
|
|
64448
|
-
await agent2.publisher.unrecordOutbox((i) => i === s.noteId || i?.id && i.id === s.announceActivity?.id);
|
|
64449
|
-
agent2.store.removeStatus(s.noteId);
|
|
64450
|
-
return { ok: true };
|
|
64451
|
-
}
|
|
64452
|
-
async function announceModeration(agent2, activity) {
|
|
64453
|
-
if (agent2.store.getConfig()?.kind !== "group") return null;
|
|
64454
|
-
const { urls } = agent2.publisher;
|
|
64455
|
-
const act = announceActivity({ urls, object: activity, serial: Date.now(), audience: urls.actor });
|
|
64456
|
-
const inboxes = [...new Set(agent2.store.getContacts().followers.map((f) => f.sharedInbox || f.inbox).filter(Boolean))];
|
|
64457
|
-
await agent2.deliverer.deliverToAll(inboxes, act);
|
|
64458
|
-
await agent2.publisher.recordOutbox(act);
|
|
64459
|
-
return act;
|
|
64460
|
-
}
|
|
64461
|
-
async function blockActor(agent2, actorUrl) {
|
|
64462
|
-
if (!/^https?:\/\/\S+$/.test(String(actorUrl || ""))) throw new Error("actor must be a URL");
|
|
64463
|
-
const b = agent2.store.getBlocklist();
|
|
64464
|
-
if (!b.actors.includes(actorUrl)) b.actors.push(actorUrl);
|
|
64465
|
-
agent2.store.setBlocklist(b);
|
|
64466
|
-
await unfollowActor(agent2, actorUrl).catch(() => {
|
|
64467
|
-
});
|
|
64468
|
-
await agent2.publisher?.publishCollections({ blocked: true }).catch(() => {
|
|
64469
|
-
});
|
|
64470
|
-
await announceModeration(
|
|
64471
|
-
agent2,
|
|
64472
|
-
blockActivity({ urls: agent2.publisher.urls, targetActor: actorUrl, serial: Date.now() })
|
|
64473
|
-
).catch((e) => agent2.log?.(`moderation announce failed: ${e.message}`));
|
|
64474
|
-
return { ok: true, actor: actorUrl };
|
|
64475
|
-
}
|
|
64476
|
-
async function unblockActor(agent2, actorUrl) {
|
|
64477
|
-
const b = agent2.store.getBlocklist();
|
|
64478
|
-
b.actors = b.actors.filter((a) => a !== actorUrl);
|
|
64479
|
-
agent2.store.setBlocklist(b);
|
|
64480
|
-
await agent2.publisher?.publishCollections({ blocked: true }).catch(() => {
|
|
64481
|
-
});
|
|
64482
|
-
await announceModeration(agent2, undoActivity({
|
|
64483
|
-
urls: agent2.publisher.urls,
|
|
64484
|
-
activity: { type: "Block", actor: agent2.publisher.urls.actor, object: actorUrl },
|
|
64485
|
-
serial: Date.now()
|
|
64486
|
-
})).catch((e) => agent2.log?.(`moderation announce failed: ${e.message}`));
|
|
64487
|
-
return { ok: true, actor: actorUrl };
|
|
64488
|
-
}
|
|
64489
|
-
async function applyModeration(agent2, entry) {
|
|
64490
|
-
const { urls } = agent2.publisher;
|
|
64491
|
-
const target = typeof entry.activity?.object === "string" ? entry.activity.object : entry.activity?.object?.id;
|
|
64492
|
-
switch (entry.type) {
|
|
64493
|
-
case "Block": {
|
|
64494
|
-
if (!target) throw new Error("the queued Block names nobody");
|
|
64495
|
-
const member = agent2.store.getContacts().followers.some((f) => f.actor === target);
|
|
64496
|
-
return member ? ejectFollower(agent2, target) : blockActor(agent2, target);
|
|
64497
|
-
}
|
|
64498
|
-
case "Undo": {
|
|
64499
|
-
const inner = entry.activity?.object;
|
|
64500
|
-
const unbanned = typeof inner === "object" && inner?.type === "Block" ? typeof inner.object === "string" ? inner.object : inner.object?.id : null;
|
|
64501
|
-
if (!unbanned) throw new Error("the queued Undo does not name a Block");
|
|
64502
|
-
const muted = agent2.store.getMuted();
|
|
64503
|
-
muted.actors = muted.actors.filter((a) => a !== unbanned);
|
|
64504
|
-
agent2.store.setMuted(muted);
|
|
64505
|
-
return unblockActor(agent2, unbanned);
|
|
64506
|
-
}
|
|
64507
|
-
case "Delete": {
|
|
64508
|
-
if (!target) throw new Error("the queued Delete names nothing");
|
|
64509
|
-
return agent2.intake.retract(target);
|
|
64510
|
-
}
|
|
64511
|
-
case "Add":
|
|
64512
|
-
case "Remove": {
|
|
64513
|
-
if (!target) throw new Error("the queued roster change names nobody");
|
|
64514
|
-
const cfg = { ...agent2.store.getConfig() };
|
|
64515
|
-
const prev = cfg.moderators || [];
|
|
64516
|
-
cfg.moderators = entry.type === "Add" ? [.../* @__PURE__ */ new Set([...prev, target])] : prev.filter((m) => m !== target);
|
|
64517
|
-
agent2.store.setConfig(cfg);
|
|
64518
|
-
if (agent2.publisher) agent2.publisher.config.moderators = cfg.moderators;
|
|
64519
|
-
await agent2.publisher.publishProfile();
|
|
64520
|
-
await announceModeration(agent2, addRemoveActivity({
|
|
64521
|
-
urls,
|
|
64522
|
-
type: entry.type,
|
|
64523
|
-
object: target,
|
|
64524
|
-
target: urls.moderators,
|
|
64525
|
-
serial: Date.now()
|
|
64526
|
-
})).catch((e) => agent2.log?.(`moderation announce failed: ${e.message}`));
|
|
64527
|
-
return { ok: true, moderators: cfg.moderators };
|
|
64528
|
-
}
|
|
64529
|
-
default:
|
|
64530
|
-
throw new Error(`no way to apply a queued ${entry.type}`);
|
|
64531
|
-
}
|
|
64532
|
-
}
|
|
64533
|
-
async function pinStatus(agent2, s, pinned) {
|
|
64534
|
-
if (s.actor !== agent2.publisher.urls.actor) throw new Error("not your status");
|
|
64535
|
-
const updated = agent2.store.updateStatus(s.noteId, { pinned: !!pinned });
|
|
64536
|
-
await agent2.publisher.publishFeatured().catch((e) => agent2.log?.(`featured: ${e.message}`));
|
|
64537
|
-
agent2.publisher.publishProfile?.().catch(() => {
|
|
64538
|
-
});
|
|
64539
|
-
return updated || s;
|
|
64540
|
-
}
|
|
64541
|
-
|
|
64542
65369
|
// lib/client/masto/render.mjs
|
|
64543
65370
|
init_wire();
|
|
64544
65371
|
|
|
@@ -64809,11 +65636,11 @@ function account(api, actorUrl, { selfAcct } = {}) {
|
|
|
64809
65636
|
fields: (self2 ? api.store.getConfig()?.fields : cached.fields) || []
|
|
64810
65637
|
};
|
|
64811
65638
|
}
|
|
64812
|
-
function page(api, items, url, { limit = 20, max = 40, idOf = (s) => api.store.idFor(s.noteId) } = {}) {
|
|
65639
|
+
function page(api, items, url, { limit = 20, max = 40, idOf: idOf2 = (s) => api.store.idFor(s.noteId) } = {}) {
|
|
64813
65640
|
const n = Math.min(Number(url.searchParams.get("limit")) || limit, max);
|
|
64814
65641
|
const ids = /* @__PURE__ */ new Map();
|
|
64815
65642
|
const idAt = (s) => {
|
|
64816
|
-
if (!ids.has(s)) ids.set(s,
|
|
65643
|
+
if (!ids.has(s)) ids.set(s, idOf2(s));
|
|
64817
65644
|
return ids.get(s);
|
|
64818
65645
|
};
|
|
64819
65646
|
const cut = (param) => {
|
|
@@ -64830,15 +65657,15 @@ function page(api, items, url, { limit = 20, max = 40, idOf = (s) => api.store.i
|
|
|
64830
65657
|
}
|
|
64831
65658
|
const pageItems2 = items.slice(0, n);
|
|
64832
65659
|
if (!pageItems2.length) return { items: pageItems2, headers: {} };
|
|
64833
|
-
const base =
|
|
65660
|
+
const base = url.origin + url.pathname;
|
|
64834
65661
|
const q = (extra) => {
|
|
64835
65662
|
const u = new URL(base);
|
|
64836
65663
|
for (const [k, v] of url.searchParams) if (!["max_id", "since_id", "min_id"].includes(k)) u.searchParams.set(k, v);
|
|
64837
65664
|
for (const [k, v] of Object.entries(extra)) u.searchParams.set(k, v);
|
|
64838
65665
|
return u.href;
|
|
64839
65666
|
};
|
|
64840
|
-
const links = [`<${q({ max_id:
|
|
64841
|
-
if (pageItems2.length) links.push(`<${q({ min_id:
|
|
65667
|
+
const links = [`<${q({ max_id: idOf2(pageItems2[pageItems2.length - 1]) })}>; rel="next"`];
|
|
65668
|
+
if (pageItems2.length) links.push(`<${q({ min_id: idOf2(pageItems2[0]) })}>; rel="prev"`);
|
|
64842
65669
|
return { items: pageItems2, headers: { link: links.join(", ") } };
|
|
64843
65670
|
}
|
|
64844
65671
|
async function bskyReply(api, send, body, parent, visibility) {
|
|
@@ -64956,7 +65783,7 @@ function status(api, s, { all } = {}) {
|
|
|
64956
65783
|
created_at: s.published || (/* @__PURE__ */ new Date()).toISOString(),
|
|
64957
65784
|
in_reply_to_id: s.inReplyTo ? api.store.idFor(s.inReplyTo) : null,
|
|
64958
65785
|
in_reply_to_account_id: null,
|
|
64959
|
-
sensitive: !!s.spoiler,
|
|
65786
|
+
sensitive: !!(s.spoiler || s.sensitive),
|
|
64960
65787
|
spoiler_text: s.spoiler || "",
|
|
64961
65788
|
visibility: s.visibility || "public",
|
|
64962
65789
|
language: null,
|
|
@@ -64996,7 +65823,7 @@ function status(api, s, { all } = {}) {
|
|
|
64996
65823
|
acct: bare.includes("@") ? bare : host ? `${user}@${host}` : bare
|
|
64997
65824
|
};
|
|
64998
65825
|
}),
|
|
64999
|
-
tags: [],
|
|
65826
|
+
tags: (s.tags || []).map((t) => ({ name: t.name, url: t.url })),
|
|
65000
65827
|
emojis: (s.emojis || []).map((e) => ({
|
|
65001
65828
|
shortcode: e.shortcode,
|
|
65002
65829
|
url: e.url,
|
|
@@ -65425,7 +66252,7 @@ async function handle3(api, ctx) {
|
|
|
65425
66252
|
const entry = api.store.getMedia()[mMedia[1]];
|
|
65426
66253
|
if (!entry) return send(404, { error: "Record not found" });
|
|
65427
66254
|
if (req.method === "PUT") {
|
|
65428
|
-
const body = await
|
|
66255
|
+
const body = await readBody2(req);
|
|
65429
66256
|
if (typeof body.description === "string") {
|
|
65430
66257
|
entry.description = body.description;
|
|
65431
66258
|
api.store.setMedia(mMedia[1], entry);
|
|
@@ -65459,7 +66286,7 @@ async function handle4(api, ctx) {
|
|
|
65459
66286
|
const ct = String(req.headers["content-type"] || "");
|
|
65460
66287
|
let form = {}, files = {};
|
|
65461
66288
|
if (ct.includes("multipart/form-data")) ({ fields: form, files } = await readMultipart(req));
|
|
65462
|
-
else form = await
|
|
66289
|
+
else form = await readBody2(req);
|
|
65463
66290
|
const cfg = { ...api.store.getConfig() };
|
|
65464
66291
|
const putImage = async (f) => {
|
|
65465
66292
|
const ext = (f.filename || "").includes(".") ? f.filename.split(".").pop().replace(/[^\w]/g, "") : "bin";
|
|
@@ -65521,7 +66348,7 @@ async function handle4(api, ctx) {
|
|
|
65521
66348
|
}
|
|
65522
66349
|
if (pathname === "/api/v1/markers") {
|
|
65523
66350
|
if (req.method === "POST") {
|
|
65524
|
-
const body = await
|
|
66351
|
+
const body = await readBody2(req);
|
|
65525
66352
|
const markers = api.store.read("masto-markers.json", {});
|
|
65526
66353
|
for (const [k, v] of Object.entries(body)) {
|
|
65527
66354
|
const lastId = v?.last_read_id || v;
|
|
@@ -65596,7 +66423,7 @@ async function handle4(api, ctx) {
|
|
|
65596
66423
|
return sub ? send(200, api.push.json(token, sub)) : send(404, { error: "Record not found" });
|
|
65597
66424
|
}
|
|
65598
66425
|
if (req.method === "POST") {
|
|
65599
|
-
const body = await
|
|
66426
|
+
const body = await readBody2(req);
|
|
65600
66427
|
const sub = api.push.set(token, {
|
|
65601
66428
|
endpoint: body.subscription?.endpoint,
|
|
65602
66429
|
keys: body.subscription?.keys,
|
|
@@ -65606,7 +66433,7 @@ async function handle4(api, ctx) {
|
|
|
65606
66433
|
return send(200, api.push.json(token, sub));
|
|
65607
66434
|
}
|
|
65608
66435
|
if (req.method === "PUT") {
|
|
65609
|
-
const body = await
|
|
66436
|
+
const body = await readBody2(req);
|
|
65610
66437
|
const sub = api.push.setAlerts(token, body.data?.alerts);
|
|
65611
66438
|
return sub ? send(200, api.push.json(token, sub)) : send(404, { error: "Record not found" });
|
|
65612
66439
|
}
|
|
@@ -65699,7 +66526,7 @@ async function handle5(api, ctx) {
|
|
|
65699
66526
|
return send(200, api.store.getLists().map(listJson));
|
|
65700
66527
|
}
|
|
65701
66528
|
if (pathname === "/api/v1/lists" && req.method === "POST") {
|
|
65702
|
-
const body = await
|
|
66529
|
+
const body = await readBody2(req);
|
|
65703
66530
|
const title = String(body.title || "").trim();
|
|
65704
66531
|
if (!title) return send(422, { error: "a title is required" });
|
|
65705
66532
|
const lists = api.store.getLists();
|
|
@@ -65718,7 +66545,7 @@ async function handle5(api, ctx) {
|
|
|
65718
66545
|
return send(200, {});
|
|
65719
66546
|
}
|
|
65720
66547
|
if (req.method === "PUT") {
|
|
65721
|
-
const body = await
|
|
66548
|
+
const body = await readBody2(req);
|
|
65722
66549
|
if (body.title) l.title = String(body.title);
|
|
65723
66550
|
if (body.replies_policy) l.repliesPolicy = body.replies_policy;
|
|
65724
66551
|
api.store.setLists(lists);
|
|
@@ -65731,7 +66558,7 @@ async function handle5(api, ctx) {
|
|
|
65731
66558
|
const l = lists.find((x) => x.id === mListAcc[1]);
|
|
65732
66559
|
if (!l) return send(404, { error: "Record not found" });
|
|
65733
66560
|
if (req.method === "GET") return send(200, (l.members || []).map((a) => api.account(a)));
|
|
65734
|
-
const body = await
|
|
66561
|
+
const body = await readBody2(req).catch(() => ({}));
|
|
65735
66562
|
const ids = [].concat(body.account_ids || body["account_ids[]"] || url.searchParams.getAll("account_ids[]")).filter(Boolean);
|
|
65736
66563
|
const actors = ids.map((id) => api.store.urlFor(id)).filter(Boolean);
|
|
65737
66564
|
if (req.method === "POST") l.members = [.../* @__PURE__ */ new Set([...l.members || [], ...actors])];
|
|
@@ -65763,7 +66590,7 @@ async function handle5(api, ctx) {
|
|
|
65763
66590
|
return send(200, api.store.getFilters().map(filterJson));
|
|
65764
66591
|
}
|
|
65765
66592
|
if (pathname === "/api/v2/filters" && req.method === "POST") {
|
|
65766
|
-
const body = await
|
|
66593
|
+
const body = await readBody2(req);
|
|
65767
66594
|
const title = String(body.title || "").trim();
|
|
65768
66595
|
if (!title) return send(422, { error: "a title is required" });
|
|
65769
66596
|
const filters = api.store.getFilters();
|
|
@@ -65789,7 +66616,7 @@ async function handle5(api, ctx) {
|
|
|
65789
66616
|
return send(200, {});
|
|
65790
66617
|
}
|
|
65791
66618
|
if (req.method === "PUT") {
|
|
65792
|
-
const body = await
|
|
66619
|
+
const body = await readBody2(req);
|
|
65793
66620
|
if (body.title) f.title = String(body.title);
|
|
65794
66621
|
if (body.context) f.context = [].concat(body.context);
|
|
65795
66622
|
if (body.filter_action) f.action = body.filter_action;
|
|
@@ -65846,7 +66673,7 @@ async function handle5(api, ctx) {
|
|
|
65846
66673
|
if (sinceId) cut(sinceId, true);
|
|
65847
66674
|
const page2 = q.get("min_id") && !q.get("since_id") ? items.slice(Math.max(0, items.length - limit)) : items.slice(0, limit);
|
|
65848
66675
|
if (page2.length) {
|
|
65849
|
-
const base = `${api.scheme || (req.socket?.encrypted ? "https" : "http")}://${req.headers.host}${pathname}`;
|
|
66676
|
+
const base = `${api.scheme || (req.socket?.encrypted ? "https" : "http")}://${req.headers.host}${api.mount || ""}${pathname}`;
|
|
65850
66677
|
const link = (params) => {
|
|
65851
66678
|
const u = new URL(base);
|
|
65852
66679
|
for (const [k, v] of q) if (k !== "max_id" && k !== "since_id" && k !== "min_id") u.searchParams.append(k, v);
|
|
@@ -65912,7 +66739,7 @@ init_node_crypto();
|
|
|
65912
66739
|
async function handle6(api, ctx) {
|
|
65913
66740
|
const { req, res, pathname, url, send } = ctx;
|
|
65914
66741
|
if (pathname === "/api/v1/statuses" && req.method === "POST") {
|
|
65915
|
-
const body = await
|
|
66742
|
+
const body = await readBody2(req);
|
|
65916
66743
|
if (!body.status) return send(422, { error: "status text required" });
|
|
65917
66744
|
const visibility = body.visibility || "public";
|
|
65918
66745
|
if (!["public", "unlisted", "private", "direct"].includes(visibility)) {
|
|
@@ -65928,6 +66755,7 @@ async function handle6(api, ctx) {
|
|
|
65928
66755
|
if (ready !== true) return send(422, { error: ready });
|
|
65929
66756
|
}
|
|
65930
66757
|
const spoilerText = String(body.spoiler_text || "").trim() || null;
|
|
66758
|
+
const sensitive = body.sensitive === true || body.sensitive === "true" || body.sensitive === "1";
|
|
65931
66759
|
const mediaIds = [].concat(body.media_ids || body["media_ids[]"] || []).filter(Boolean);
|
|
65932
66760
|
const media = api.store.getMedia();
|
|
65933
66761
|
const attachments = mediaIds.map((id) => media[id] && { id, ...media[id] }).filter(Boolean);
|
|
@@ -65958,7 +66786,8 @@ async function handle6(api, ctx) {
|
|
|
65958
66786
|
expiresAt: new Date(Date.now() + seconds * 1e3).toISOString(),
|
|
65959
66787
|
inReplyTo,
|
|
65960
66788
|
visibility,
|
|
65961
|
-
spoilerText
|
|
66789
|
+
spoilerText,
|
|
66790
|
+
sensitive
|
|
65962
66791
|
});
|
|
65963
66792
|
return send(200, api.status(api.store.getStatuses().find((x) => x.noteId === q.id)));
|
|
65964
66793
|
} catch (e) {
|
|
@@ -65987,7 +66816,7 @@ async function handle6(api, ctx) {
|
|
|
65987
66816
|
try {
|
|
65988
66817
|
note = await api.agent.publisher.publishNote(
|
|
65989
66818
|
body.status,
|
|
65990
|
-
{ inReplyTo, attachments, visibility, spoilerText }
|
|
66819
|
+
{ inReplyTo, attachments, visibility, spoilerText, sensitive }
|
|
65991
66820
|
);
|
|
65992
66821
|
} catch (e) {
|
|
65993
66822
|
if (e.code === "unaddressed") return send(422, { error: e.message });
|
|
@@ -66011,7 +66840,7 @@ async function handle6(api, ctx) {
|
|
|
66011
66840
|
return send(200, [{
|
|
66012
66841
|
content: s.content || "",
|
|
66013
66842
|
spoiler_text: s.spoiler || "",
|
|
66014
|
-
sensitive: !!s.spoiler,
|
|
66843
|
+
sensitive: !!(s.spoiler || s.sensitive),
|
|
66015
66844
|
created_at: s.editedAt || s.published,
|
|
66016
66845
|
account: api.account(s.actor),
|
|
66017
66846
|
media_attachments: (s.attachments || []).map((a) => api.mediaJson(a)),
|
|
@@ -66030,15 +66859,16 @@ async function handle6(api, ctx) {
|
|
|
66030
66859
|
const s = noteUrl && api.store.getStatuses().find((x) => x.noteId === noteUrl);
|
|
66031
66860
|
if (!s) return send(404, { error: "Record not found" });
|
|
66032
66861
|
if (s.actor !== api.urls.actor) return send(403, { error: "not your status" });
|
|
66033
|
-
const body = await
|
|
66862
|
+
const body = await readBody2(req);
|
|
66034
66863
|
if (!body.status) return send(422, { error: "status text required" });
|
|
66035
66864
|
const mediaIds = [].concat(body.media_ids || body["media_ids[]"] || []).filter(Boolean);
|
|
66036
66865
|
const media = api.store.getMedia();
|
|
66037
66866
|
const attachments = mediaIds.length ? mediaIds.map((id) => media[id] && { id, ...media[id] }).filter(Boolean) : null;
|
|
66038
66867
|
const spoilerText = String(body.spoiler_text || "").trim() || null;
|
|
66868
|
+
const sensitive = body.sensitive === void 0 ? null : body.sensitive === true || body.sensitive === "true" || body.sensitive === "1";
|
|
66039
66869
|
const patched = await api.agent.publisher.updateNote(
|
|
66040
66870
|
s,
|
|
66041
|
-
{ content: body.status, spoilerText, attachments }
|
|
66871
|
+
{ content: body.status, spoilerText, sensitive, attachments }
|
|
66042
66872
|
);
|
|
66043
66873
|
return send(200, api.status(patched || s));
|
|
66044
66874
|
}
|
|
@@ -66148,7 +66978,7 @@ async function handle6(api, ctx) {
|
|
|
66148
66978
|
return send(200, {});
|
|
66149
66979
|
}
|
|
66150
66980
|
if (req.method === "PUT") {
|
|
66151
|
-
const body = await
|
|
66981
|
+
const body = await readBody2(req);
|
|
66152
66982
|
const at = Date.parse(body.scheduled_at || "");
|
|
66153
66983
|
if (!Number.isFinite(at)) return send(422, { error: "scheduled_at required" });
|
|
66154
66984
|
e.scheduledAt = new Date(at).toISOString();
|
|
@@ -66162,7 +66992,7 @@ async function handle6(api, ctx) {
|
|
|
66162
66992
|
const s = noteUrl && api.store.getStatuses().find((x) => x.noteId === noteUrl);
|
|
66163
66993
|
if (!s?.poll) return send(404, { error: "Record not found" });
|
|
66164
66994
|
if (mPoll[2] && req.method === "POST") {
|
|
66165
|
-
const body = await
|
|
66995
|
+
const body = await readBody2(req);
|
|
66166
66996
|
const choices = [].concat(body.choices || body["choices[]"] || []).map(Number).filter(Number.isInteger);
|
|
66167
66997
|
if (!choices.length) return send(422, { error: "choices required" });
|
|
66168
66998
|
const r = await votePoll(api.agent, s, choices);
|
|
@@ -66211,11 +67041,13 @@ var MastoApi = class _MastoApi {
|
|
|
66211
67041
|
allowed = null,
|
|
66212
67042
|
scheme = null,
|
|
66213
67043
|
embedded = false,
|
|
67044
|
+
mount = "",
|
|
66214
67045
|
streaming = true,
|
|
66215
67046
|
webPush = true,
|
|
66216
67047
|
scheduling = true
|
|
66217
67048
|
}) {
|
|
66218
67049
|
this.agent = agent2;
|
|
67050
|
+
this.mount = mount;
|
|
66219
67051
|
this.embedded = embedded;
|
|
66220
67052
|
this.log = log2;
|
|
66221
67053
|
this.allowed = allowed;
|
|
@@ -66275,7 +67107,13 @@ var MastoApi = class _MastoApi {
|
|
|
66275
67107
|
res.end(body);
|
|
66276
67108
|
return true;
|
|
66277
67109
|
};
|
|
66278
|
-
const
|
|
67110
|
+
const scheme = String(this.scheme || (req.headers?.["x-forwarded-proto"] === "https" || req.socket?.encrypted ? "https" : "http")).replace(/:.*$/, "");
|
|
67111
|
+
let self2 = url;
|
|
67112
|
+
try {
|
|
67113
|
+
self2 = new URL(`${scheme}://${req.headers?.host || "localhost"}${this.mount || ""}${pathname}${url.search || ""}`);
|
|
67114
|
+
} catch {
|
|
67115
|
+
}
|
|
67116
|
+
const ctx = { req, res, pathname, url: self2, send };
|
|
66279
67117
|
if (await handle(this, ctx)) return true;
|
|
66280
67118
|
if (!pathname.startsWith("/api/")) return false;
|
|
66281
67119
|
if (await handle2(this, ctx)) return true;
|
|
@@ -66907,7 +67745,7 @@ var PodTransport = class {
|
|
|
66907
67745
|
* says all of it. The parsed graph must mention the WebID before anything is
|
|
66908
67746
|
* written back — an empty or foreign body must never become the new profile.
|
|
66909
67747
|
*/
|
|
66910
|
-
async linkAccountInProfile({ actorUrl, accountName, kind = "person" }) {
|
|
67748
|
+
async linkAccountInProfile({ actorUrl, accountName, kind = "person", outbox = null }) {
|
|
66911
67749
|
const docUrl = this.webId.split("#")[0];
|
|
66912
67750
|
const res = await this.fetch(docUrl, { headers: { accept: "text/turtle" } });
|
|
66913
67751
|
if (res.status >= 400) throw new Error(`[${this.label}] GET ${docUrl} \u2192 ${res.status}`);
|
|
@@ -66923,10 +67761,16 @@ var PodTransport = class {
|
|
|
66923
67761
|
[me, FOAF("account"), actor],
|
|
66924
67762
|
[actor, RDF4("type"), FOAF("OnlineAccount")],
|
|
66925
67763
|
[actor, RDF4("type"), kind === "group" ? AS("Group") : AS("Person")],
|
|
66926
|
-
[actor, FOAF("accountName"), literal2(accountName)]
|
|
67764
|
+
[actor, FOAF("accountName"), literal2(accountName)],
|
|
67765
|
+
// Where a Solid client posts on this person's behalf (`as:outbox` on the
|
|
67766
|
+
// WebID is what dokieli reads); only where a door exists to take it.
|
|
67767
|
+
...outbox ? [[me, AS("outbox"), namedNode2(outbox)]] : []
|
|
66927
67768
|
];
|
|
66928
67769
|
const missing = wanted.filter(([s, p, o]) => !g.holds(s, p, o, doc));
|
|
66929
|
-
const stale =
|
|
67770
|
+
const stale = [
|
|
67771
|
+
...g.statementsMatching(actor, FOAF("accountName"), null, doc).filter((st2) => st2.object.value !== accountName),
|
|
67772
|
+
...outbox ? g.statementsMatching(me, AS("outbox"), null, doc).filter((st2) => st2.object.value !== outbox) : []
|
|
67773
|
+
];
|
|
66930
67774
|
if (!missing.length && !stale.length) return false;
|
|
66931
67775
|
const deletes = stale.map((st2) => [st2.subject, st2.predicate, st2.object]);
|
|
66932
67776
|
if (await this.patchDocument(docUrl, missing, deletes)) return true;
|
|
@@ -67280,6 +68124,7 @@ var hostOf = (inbox) => {
|
|
|
67280
68124
|
}
|
|
67281
68125
|
};
|
|
67282
68126
|
var aboutTheHost = (e) => !e.status || e.status >= 500 || e.status === 429;
|
|
68127
|
+
var unsalvageable = (e) => e.status === 404 || e.status === 410;
|
|
67283
68128
|
var Deliverer = class {
|
|
67284
68129
|
// passive: signing-only (viewer-mode agents) — no queue drain timer, so a
|
|
67285
68130
|
// read-only agent never mutates shared delivery state. startQueue() flips
|
|
@@ -67292,9 +68137,11 @@ var Deliverer = class {
|
|
|
67292
68137
|
edPrivate = null,
|
|
67293
68138
|
proofKeyId = null,
|
|
67294
68139
|
log: log2 = console.log,
|
|
67295
|
-
passive = false
|
|
68140
|
+
passive = false,
|
|
68141
|
+
onGone = null
|
|
67296
68142
|
}) {
|
|
67297
68143
|
this.store = store;
|
|
68144
|
+
this.onGone = onGone;
|
|
67298
68145
|
this.keyId = keyId;
|
|
67299
68146
|
this.rsaPrivate = rsaPrivate;
|
|
67300
68147
|
this.actorId = actorId;
|
|
@@ -67390,6 +68237,10 @@ var Deliverer = class {
|
|
|
67390
68237
|
await this.deliverNow(inbox, signed);
|
|
67391
68238
|
this.log(`delivered ${signed.type} \u2192 ${inbox}`);
|
|
67392
68239
|
} catch (e) {
|
|
68240
|
+
if (unsalvageable(e)) {
|
|
68241
|
+
await this._unsalvageable(inbox, e);
|
|
68242
|
+
return;
|
|
68243
|
+
}
|
|
67393
68244
|
this.log(`delivery failed (${e.message}) \u2014 queued`);
|
|
67394
68245
|
const wait = e.retryAfterMs || 6e4;
|
|
67395
68246
|
if (aboutTheHost(e)) {
|
|
@@ -67399,6 +68250,26 @@ var Deliverer = class {
|
|
|
67399
68250
|
this._enqueue({ inbox, activity: signed, attempts: 1, nextAt: Date.now() + wait });
|
|
67400
68251
|
}
|
|
67401
68252
|
}
|
|
68253
|
+
// Not retried, and on a 410 the followers who received there are dropped,
|
|
68254
|
+
// so the next fan-out stops asking; the followers collection is republished
|
|
68255
|
+
// through onGone. A 404 drops only this delivery.
|
|
68256
|
+
async _unsalvageable(inbox, e) {
|
|
68257
|
+
if (e.status !== 410) {
|
|
68258
|
+
this.log(`delivery dropped (${e.message}) \u2014 nothing answers there`);
|
|
68259
|
+
return;
|
|
68260
|
+
}
|
|
68261
|
+
const contacts = this.store.getContacts?.();
|
|
68262
|
+
const left = (contacts?.followers || []).filter((f) => f.inbox === inbox || f.sharedInbox === inbox);
|
|
68263
|
+
this.log(`${inbox} is gone (410) \u2014 delivery dropped${left.length ? `, ${left.length} follower(s) there forgotten` : ""}`);
|
|
68264
|
+
if (!left.length) return;
|
|
68265
|
+
for (const f of left) dropFollower(contacts, f.actor, "inbox-gone");
|
|
68266
|
+
this.store.setContacts(contacts);
|
|
68267
|
+
try {
|
|
68268
|
+
await this.onGone?.();
|
|
68269
|
+
} catch (err) {
|
|
68270
|
+
this.log(`followers republish after 410: ${err.message}`);
|
|
68271
|
+
}
|
|
68272
|
+
}
|
|
67402
68273
|
// The queue has a ceiling: a fan-out into a sea of down servers must not grow
|
|
67403
68274
|
// queue.json without bound. Overflow keeps the evidence as a dead letter.
|
|
67404
68275
|
_enqueue(item) {
|
|
@@ -67483,6 +68354,11 @@ var Deliverer = class {
|
|
|
67483
68354
|
changed = true;
|
|
67484
68355
|
this.log(`retry ok: ${item.activity.type} \u2192 ${item.inbox}`);
|
|
67485
68356
|
} catch (e) {
|
|
68357
|
+
if (unsalvageable(e)) {
|
|
68358
|
+
changed = true;
|
|
68359
|
+
await this._unsalvageable(item.inbox, e);
|
|
68360
|
+
continue;
|
|
68361
|
+
}
|
|
67486
68362
|
item.attempts += 1;
|
|
67487
68363
|
changed = true;
|
|
67488
68364
|
const wait = e.retryAfterMs || ladderFor(item);
|
|
@@ -69664,6 +70540,7 @@ var BrowserAgent = class _BrowserAgent {
|
|
|
69664
70540
|
this.lease.startRenewal();
|
|
69665
70541
|
try {
|
|
69666
70542
|
await this.store.load({ force: true }).catch((e) => this.log(`re-reading state: ${e.message}`));
|
|
70543
|
+
await this.publisher.healStatuses().catch((e) => this.log(`healing the timeline index: ${e.message}`));
|
|
69667
70544
|
this.deliverer?.startQueue?.();
|
|
69668
70545
|
await this.publisher.publishProfile();
|
|
69669
70546
|
await this.store.flush?.();
|
|
@@ -69775,7 +70652,8 @@ var BrowserAgent = class _BrowserAgent {
|
|
|
69775
70652
|
// The relay finds the account by the front's own key for it, which for a
|
|
69776
70653
|
// mail-door account is the full address, not the bare handle.
|
|
69777
70654
|
handle: doorKeyOf(config.gateway?.url) || config.handle,
|
|
69778
|
-
sessionFetch: session.fetch
|
|
70655
|
+
sessionFetch: session.fetch,
|
|
70656
|
+
onGone: () => this.publisher.publishCollections({ followers: true })
|
|
69779
70657
|
});
|
|
69780
70658
|
this.publisher = new Publisher({
|
|
69781
70659
|
config: this.store.getConfig(),
|
|
@@ -69788,12 +70666,14 @@ var BrowserAgent = class _BrowserAgent {
|
|
|
69788
70666
|
// Who a post names, resolved — the same lookup the DeviceAgent
|
|
69789
70667
|
// gives its publisher. Without it no mention from the browser ever
|
|
69790
70668
|
// resolved: a direct message went to nobody, a mention notified no one.
|
|
69791
|
-
resolveMention: (h) => resolveHandle(this, h)
|
|
70669
|
+
resolveMention: (h) => resolveHandle(this, h),
|
|
70670
|
+
resolveActor: (u) => this.intake.fetchAP(u)
|
|
69792
70671
|
});
|
|
69793
70672
|
this.atproto = new BrowserAtproto({ store: this.store, actorId: this.urls.actor, log: this.log });
|
|
69794
70673
|
this.publisher.atproto = this.atproto;
|
|
69795
70674
|
this.fediaccts = new BrowserFediAccounts({ store: this.store, actorId: this.urls.actor, log: this.log });
|
|
69796
70675
|
this.lease = new Lease({ url: this.urls.state + "lease.json", fetchImpl: podFetch, log: this.log });
|
|
70676
|
+
this.c2s = new C2S({ agent: this, log: this.log });
|
|
69797
70677
|
this.intake = new Intake({
|
|
69798
70678
|
config: this.store.getConfig(),
|
|
69799
70679
|
urls: this.urls,
|
|
@@ -69803,7 +70683,8 @@ var BrowserAgent = class _BrowserAgent {
|
|
|
69803
70683
|
publisher: this.publisher,
|
|
69804
70684
|
log: this.log,
|
|
69805
70685
|
push: true,
|
|
69806
|
-
lease: this.lease
|
|
70686
|
+
lease: this.lease,
|
|
70687
|
+
ownerPost: (a, o) => this.c2s.dispatch(a, o)
|
|
69807
70688
|
});
|
|
69808
70689
|
this.masto = new MastoApi({
|
|
69809
70690
|
agent: this,
|
|
@@ -69863,7 +70744,8 @@ var BrowserAgent = class _BrowserAgent {
|
|
|
69863
70744
|
atproto: this.atproto?.status() || null,
|
|
69864
70745
|
podRequests: this.remote?.stats?.() || null,
|
|
69865
70746
|
update: null,
|
|
69866
|
-
inboxCooldownFor: 0
|
|
70747
|
+
inboxCooldownFor: 0,
|
|
70748
|
+
stateSkipped: this.store?.lastSkipped || []
|
|
69867
70749
|
};
|
|
69868
70750
|
}
|
|
69869
70751
|
// Rotate the signing key (POST /rotate-key). Mint a fresh keypair, replace the
|