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