fedipod-server 0.17.0 → 0.18.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 +95 -61
- 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/intake/index.mjs +32 -5
- package/lib/core/publisher/index.mjs +15 -1
- package/lib/core/publisher/notes.mjs +84 -2
- package/lib/core/publisher/questions.mjs +1 -0
- package/lib/core/publisher/restore.mjs +27 -2
- package/lib/core/social.mjs +1 -0
- package/lib/core/store.mjs +4 -0
- package/lib/core/wire.mjs +16 -12
- package/lib/gateway/front-core.mjs +43 -1
- package/lib/gateway/gateway-core.mjs +40 -0
- package/lib/pod/actor.mjs +2 -2
- package/lib/pod/transport.mjs +9 -3
- package/package.json +1 -1
- package/run-agent.mjs +9 -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 +10 -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 +1325 -661
- 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/site/sw.js
CHANGED
|
@@ -9596,6 +9596,7 @@ __export(wire_exports, {
|
|
|
9596
9596
|
acceptActivity: () => acceptActivity,
|
|
9597
9597
|
actorDoc: () => actorDoc,
|
|
9598
9598
|
addRemoveActivity: () => addRemoveActivity,
|
|
9599
|
+
addressing: () => addressing,
|
|
9599
9600
|
announceActivity: () => announceActivity,
|
|
9600
9601
|
apUrls: () => apUrls2,
|
|
9601
9602
|
assertionKeyId: () => assertionKeyId,
|
|
@@ -10056,6 +10057,14 @@ function contentHtml(text, mentions = []) {
|
|
|
10056
10057
|
}
|
|
10057
10058
|
return "<p>" + html.replace(/\n+/g, "</p><p>") + "</p>";
|
|
10058
10059
|
}
|
|
10060
|
+
function addressing(urls, visibility, who = []) {
|
|
10061
|
+
return {
|
|
10062
|
+
public: { to: [PUBLIC], cc: [urls.followers, ...who] },
|
|
10063
|
+
unlisted: { to: [urls.followers], cc: [PUBLIC, ...who] },
|
|
10064
|
+
private: { to: [urls.followers], cc: who },
|
|
10065
|
+
direct: { to: who, cc: [] }
|
|
10066
|
+
}[visibility] || { to: [PUBLIC], cc: [urls.followers, ...who] };
|
|
10067
|
+
}
|
|
10059
10068
|
function noteDoc({
|
|
10060
10069
|
urls,
|
|
10061
10070
|
slug,
|
|
@@ -10071,12 +10080,7 @@ function noteDoc({
|
|
|
10071
10080
|
}) {
|
|
10072
10081
|
const id = (container || urls.notes) + slug;
|
|
10073
10082
|
const who = mentions.map((m) => m.actor);
|
|
10074
|
-
const
|
|
10075
|
-
public: { to: [PUBLIC], cc: [urls.followers, ...who] },
|
|
10076
|
-
unlisted: { to: [urls.followers], cc: [PUBLIC, ...who] },
|
|
10077
|
-
private: { to: [urls.followers], cc: who },
|
|
10078
|
-
direct: { to: who, cc: [] }
|
|
10079
|
-
}[visibility] || { to: [PUBLIC], cc: [urls.followers, ...who] };
|
|
10083
|
+
const addressed = addressing(urls, visibility, who);
|
|
10080
10084
|
const note = {
|
|
10081
10085
|
"@context": AS_CTX,
|
|
10082
10086
|
id,
|
|
@@ -10084,8 +10088,8 @@ function noteDoc({
|
|
|
10084
10088
|
attributedTo: urls.actor,
|
|
10085
10089
|
content: contentHtml(content, mentions),
|
|
10086
10090
|
published,
|
|
10087
|
-
to:
|
|
10088
|
-
cc:
|
|
10091
|
+
to: addressed.to,
|
|
10092
|
+
cc: addressed.cc,
|
|
10089
10093
|
replies: repliesId(id)
|
|
10090
10094
|
};
|
|
10091
10095
|
if (summary) note.summary = summary;
|
|
@@ -10279,7 +10283,7 @@ var init_wire = __esm({
|
|
|
10279
10283
|
AS_CTX = "https://www.w3.org/ns/activitystreams";
|
|
10280
10284
|
SEC_CTX = "https://w3id.org/security/v1";
|
|
10281
10285
|
PUBLIC = "https://www.w3.org/ns/activitystreams#Public";
|
|
10282
|
-
DEFAULT_ROOT = "
|
|
10286
|
+
DEFAULT_ROOT = "fedipod/";
|
|
10283
10287
|
assertionKeyId = (urls) => urls.actor + "#ed25519-key";
|
|
10284
10288
|
OUTBOX_PAGE_SIZE = 20;
|
|
10285
10289
|
outboxPageId = (outboxId, n) => `${outboxId}-${n}`;
|
|
@@ -24128,20 +24132,20 @@ var require_yallist = __commonJS({
|
|
|
24128
24132
|
return acc;
|
|
24129
24133
|
};
|
|
24130
24134
|
Yallist.prototype.toArray = function() {
|
|
24131
|
-
var
|
|
24135
|
+
var arr2 = new Array(this.length);
|
|
24132
24136
|
for (var i = 0, walker = this.head; walker !== null; i++) {
|
|
24133
|
-
|
|
24137
|
+
arr2[i] = walker.value;
|
|
24134
24138
|
walker = walker.next;
|
|
24135
24139
|
}
|
|
24136
|
-
return
|
|
24140
|
+
return arr2;
|
|
24137
24141
|
};
|
|
24138
24142
|
Yallist.prototype.toArrayReverse = function() {
|
|
24139
|
-
var
|
|
24143
|
+
var arr2 = new Array(this.length);
|
|
24140
24144
|
for (var i = 0, walker = this.tail; walker !== null; i++) {
|
|
24141
|
-
|
|
24145
|
+
arr2[i] = walker.value;
|
|
24142
24146
|
walker = walker.prev;
|
|
24143
24147
|
}
|
|
24144
|
-
return
|
|
24148
|
+
return arr2;
|
|
24145
24149
|
};
|
|
24146
24150
|
Yallist.prototype.slice = function(from, to) {
|
|
24147
24151
|
to = to || this.length;
|
|
@@ -24469,11 +24473,11 @@ var require_lru_cache = __commonJS({
|
|
|
24469
24473
|
del(key) {
|
|
24470
24474
|
del(this, this[CACHE].get(key));
|
|
24471
24475
|
}
|
|
24472
|
-
load(
|
|
24476
|
+
load(arr2) {
|
|
24473
24477
|
this.reset();
|
|
24474
24478
|
const now = Date.now();
|
|
24475
|
-
for (let l =
|
|
24476
|
-
const hit =
|
|
24479
|
+
for (let l = arr2.length - 1; l >= 0; l--) {
|
|
24480
|
+
const hit = arr2[l];
|
|
24477
24481
|
const expiresAt = hit.e || 0;
|
|
24478
24482
|
if (expiresAt === 0)
|
|
24479
24483
|
this.set(hit.k, hit.v);
|
|
@@ -30207,26 +30211,26 @@ var require_base64_js = __commonJS({
|
|
|
30207
30211
|
var lens = getLens(b642);
|
|
30208
30212
|
var validLen = lens[0];
|
|
30209
30213
|
var placeHoldersLen = lens[1];
|
|
30210
|
-
var
|
|
30214
|
+
var arr2 = new Arr(_byteLength(b642, validLen, placeHoldersLen));
|
|
30211
30215
|
var curByte = 0;
|
|
30212
30216
|
var len2 = placeHoldersLen > 0 ? validLen - 4 : validLen;
|
|
30213
30217
|
var i2;
|
|
30214
30218
|
for (i2 = 0; i2 < len2; i2 += 4) {
|
|
30215
30219
|
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
|
-
|
|
30220
|
+
arr2[curByte++] = tmp >> 16 & 255;
|
|
30221
|
+
arr2[curByte++] = tmp >> 8 & 255;
|
|
30222
|
+
arr2[curByte++] = tmp & 255;
|
|
30219
30223
|
}
|
|
30220
30224
|
if (placeHoldersLen === 2) {
|
|
30221
30225
|
tmp = revLookup[b642.charCodeAt(i2)] << 2 | revLookup[b642.charCodeAt(i2 + 1)] >> 4;
|
|
30222
|
-
|
|
30226
|
+
arr2[curByte++] = tmp & 255;
|
|
30223
30227
|
}
|
|
30224
30228
|
if (placeHoldersLen === 1) {
|
|
30225
30229
|
tmp = revLookup[b642.charCodeAt(i2)] << 10 | revLookup[b642.charCodeAt(i2 + 1)] << 4 | revLookup[b642.charCodeAt(i2 + 2)] >> 2;
|
|
30226
|
-
|
|
30227
|
-
|
|
30230
|
+
arr2[curByte++] = tmp >> 8 & 255;
|
|
30231
|
+
arr2[curByte++] = tmp & 255;
|
|
30228
30232
|
}
|
|
30229
|
-
return
|
|
30233
|
+
return arr2;
|
|
30230
30234
|
}
|
|
30231
30235
|
function tripletToBase64(num) {
|
|
30232
30236
|
return lookup2[num >> 18 & 63] + lookup2[num >> 12 & 63] + lookup2[num >> 6 & 63] + lookup2[num & 63];
|
|
@@ -30368,13 +30372,13 @@ var require_buffer = __commonJS({
|
|
|
30368
30372
|
}
|
|
30369
30373
|
function typedArraySupport() {
|
|
30370
30374
|
try {
|
|
30371
|
-
const
|
|
30375
|
+
const arr2 = new Uint8Array(1);
|
|
30372
30376
|
const proto = { foo: function() {
|
|
30373
30377
|
return 42;
|
|
30374
30378
|
} };
|
|
30375
30379
|
Object.setPrototypeOf(proto, Uint8Array.prototype);
|
|
30376
|
-
Object.setPrototypeOf(
|
|
30377
|
-
return
|
|
30380
|
+
Object.setPrototypeOf(arr2, proto);
|
|
30381
|
+
return arr2.foo() === 42;
|
|
30378
30382
|
} catch (e) {
|
|
30379
30383
|
return false;
|
|
30380
30384
|
}
|
|
@@ -30894,14 +30898,14 @@ var require_buffer = __commonJS({
|
|
|
30894
30898
|
}
|
|
30895
30899
|
throw new TypeError("val must be string, number or Buffer");
|
|
30896
30900
|
}
|
|
30897
|
-
function arrayIndexOf(
|
|
30901
|
+
function arrayIndexOf(arr2, val, byteOffset, encoding, dir) {
|
|
30898
30902
|
let indexSize = 1;
|
|
30899
|
-
let arrLength =
|
|
30903
|
+
let arrLength = arr2.length;
|
|
30900
30904
|
let valLength = val.length;
|
|
30901
30905
|
if (encoding !== void 0) {
|
|
30902
30906
|
encoding = String(encoding).toLowerCase();
|
|
30903
30907
|
if (encoding === "ucs2" || encoding === "ucs-2" || encoding === "utf16le" || encoding === "utf-16le") {
|
|
30904
|
-
if (
|
|
30908
|
+
if (arr2.length < 2 || val.length < 2) {
|
|
30905
30909
|
return -1;
|
|
30906
30910
|
}
|
|
30907
30911
|
indexSize = 2;
|
|
@@ -30921,7 +30925,7 @@ var require_buffer = __commonJS({
|
|
|
30921
30925
|
if (dir) {
|
|
30922
30926
|
let foundIndex = -1;
|
|
30923
30927
|
for (i = byteOffset; i < arrLength; i++) {
|
|
30924
|
-
if (read2(
|
|
30928
|
+
if (read2(arr2, i) === read2(val, foundIndex === -1 ? 0 : i - foundIndex)) {
|
|
30925
30929
|
if (foundIndex === -1) foundIndex = i;
|
|
30926
30930
|
if (i - foundIndex + 1 === valLength) return foundIndex * indexSize;
|
|
30927
30931
|
} else {
|
|
@@ -30934,7 +30938,7 @@ var require_buffer = __commonJS({
|
|
|
30934
30938
|
for (i = byteOffset; i >= 0; i--) {
|
|
30935
30939
|
let found = true;
|
|
30936
30940
|
for (let j = 0; j < valLength; j++) {
|
|
30937
|
-
if (read2(
|
|
30941
|
+
if (read2(arr2, i + j) !== read2(val, j)) {
|
|
30938
30942
|
found = false;
|
|
30939
30943
|
break;
|
|
30940
30944
|
}
|
|
@@ -33960,11 +33964,11 @@ var require_src = __commonJS({
|
|
|
33960
33964
|
function serializeFunction(fn) {
|
|
33961
33965
|
return JSON.stringify(fn);
|
|
33962
33966
|
}
|
|
33963
|
-
function serializeArray(
|
|
33967
|
+
function serializeArray(arr2) {
|
|
33964
33968
|
let str = "[";
|
|
33965
|
-
const length =
|
|
33969
|
+
const length = arr2.length;
|
|
33966
33970
|
for (let i = 0; i < length; i++) {
|
|
33967
|
-
const val =
|
|
33971
|
+
const val = arr2[i];
|
|
33968
33972
|
if (i !== 0) str += ",";
|
|
33969
33973
|
str += serialize3(val);
|
|
33970
33974
|
}
|
|
@@ -34119,6 +34123,7 @@ function dropFollower(contacts, actor, why) {
|
|
|
34119
34123
|
}
|
|
34120
34124
|
var PodStore = class {
|
|
34121
34125
|
constructor({ storage = null, log: log2 = console.log } = {}) {
|
|
34126
|
+
this.lastSkipped = [];
|
|
34122
34127
|
this.storage = storage;
|
|
34123
34128
|
this.log = log2;
|
|
34124
34129
|
this.cache = /* @__PURE__ */ new Map();
|
|
@@ -34181,6 +34186,7 @@ var PodStore = class {
|
|
|
34181
34186
|
this.log(`state load ${name}: unparsable (${e.message})`);
|
|
34182
34187
|
}
|
|
34183
34188
|
}
|
|
34189
|
+
this.lastSkipped = skipped;
|
|
34184
34190
|
if (skipped.length) this.log(`state load skipped ${skipped.length}: ${skipped.join(", ")}`);
|
|
34185
34191
|
this.log(`state loaded: ${this.cache.size} doc(s) from ${this.base} (${fetched} re-fetched)`);
|
|
34186
34192
|
}
|
|
@@ -36882,11 +36888,11 @@ function arrayToStatements(rdfFactory, subject, data) {
|
|
|
36882
36888
|
}, subject);
|
|
36883
36889
|
return statements;
|
|
36884
36890
|
}
|
|
36885
|
-
function ArrayIndexOf(
|
|
36886
|
-
var length =
|
|
36891
|
+
function ArrayIndexOf(arr2, item, i = 0) {
|
|
36892
|
+
var length = arr2.length;
|
|
36887
36893
|
if (i < 0) i = length + i;
|
|
36888
36894
|
for (; i < length; i++) {
|
|
36889
|
-
if (
|
|
36895
|
+
if (arr2[i] === item) {
|
|
36890
36896
|
return i;
|
|
36891
36897
|
}
|
|
36892
36898
|
}
|
|
@@ -45218,8 +45224,8 @@ async function writeProfilePage(pod, urls, html) {
|
|
|
45218
45224
|
await pod.put(urls.profileHtml, html, "text/html");
|
|
45219
45225
|
await pod.setAcl(urls.profileHtml, PUBLIC_READ2);
|
|
45220
45226
|
}
|
|
45221
|
-
function linkInWebIdProfile(pod, { actorUrl, accountName, kind = "person" }) {
|
|
45222
|
-
return pod.linkAccountInProfile({ actorUrl, accountName, kind });
|
|
45227
|
+
function linkInWebIdProfile(pod, { actorUrl, accountName, kind = "person", outbox = null }) {
|
|
45228
|
+
return pod.linkAccountInProfile({ actorUrl, accountName, kind, outbox });
|
|
45223
45229
|
}
|
|
45224
45230
|
|
|
45225
45231
|
// lib/pod/inbox.mjs
|
|
@@ -55489,6 +55495,126 @@ var miscellany_default = {
|
|
|
55489
55495
|
}
|
|
55490
55496
|
};
|
|
55491
55497
|
|
|
55498
|
+
// lib/core/contexts/anno.json
|
|
55499
|
+
var anno_default = {
|
|
55500
|
+
"@context": {
|
|
55501
|
+
oa: "http://www.w3.org/ns/oa#",
|
|
55502
|
+
dc: "http://purl.org/dc/elements/1.1/",
|
|
55503
|
+
dcterms: "http://purl.org/dc/terms/",
|
|
55504
|
+
dctypes: "http://purl.org/dc/dcmitype/",
|
|
55505
|
+
foaf: "http://xmlns.com/foaf/0.1/",
|
|
55506
|
+
rdf: "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
|
|
55507
|
+
rdfs: "http://www.w3.org/2000/01/rdf-schema#",
|
|
55508
|
+
skos: "http://www.w3.org/2004/02/skos/core#",
|
|
55509
|
+
xsd: "http://www.w3.org/2001/XMLSchema#",
|
|
55510
|
+
iana: "http://www.iana.org/assignments/relation/",
|
|
55511
|
+
owl: "http://www.w3.org/2002/07/owl#",
|
|
55512
|
+
as: "http://www.w3.org/ns/activitystreams#",
|
|
55513
|
+
schema: "http://schema.org/",
|
|
55514
|
+
id: { "@type": "@id", "@id": "@id" },
|
|
55515
|
+
type: { "@type": "@id", "@id": "@type" },
|
|
55516
|
+
Annotation: "oa:Annotation",
|
|
55517
|
+
Dataset: "dctypes:Dataset",
|
|
55518
|
+
Image: "dctypes:StillImage",
|
|
55519
|
+
Video: "dctypes:MovingImage",
|
|
55520
|
+
Audio: "dctypes:Sound",
|
|
55521
|
+
Text: "dctypes:Text",
|
|
55522
|
+
TextualBody: "oa:TextualBody",
|
|
55523
|
+
ResourceSelection: "oa:ResourceSelection",
|
|
55524
|
+
SpecificResource: "oa:SpecificResource",
|
|
55525
|
+
FragmentSelector: "oa:FragmentSelector",
|
|
55526
|
+
CssSelector: "oa:CssSelector",
|
|
55527
|
+
XPathSelector: "oa:XPathSelector",
|
|
55528
|
+
TextQuoteSelector: "oa:TextQuoteSelector",
|
|
55529
|
+
TextPositionSelector: "oa:TextPositionSelector",
|
|
55530
|
+
DataPositionSelector: "oa:DataPositionSelector",
|
|
55531
|
+
SvgSelector: "oa:SvgSelector",
|
|
55532
|
+
RangeSelector: "oa:RangeSelector",
|
|
55533
|
+
TimeState: "oa:TimeState",
|
|
55534
|
+
HttpRequestState: "oa:HttpRequestState",
|
|
55535
|
+
CssStylesheet: "oa:CssStyle",
|
|
55536
|
+
Choice: "oa:Choice",
|
|
55537
|
+
Person: "foaf:Person",
|
|
55538
|
+
Software: "as:Application",
|
|
55539
|
+
Organization: "foaf:Organization",
|
|
55540
|
+
AnnotationCollection: "as:OrderedCollection",
|
|
55541
|
+
AnnotationPage: "as:OrderedCollectionPage",
|
|
55542
|
+
Audience: "schema:Audience",
|
|
55543
|
+
Motivation: "oa:Motivation",
|
|
55544
|
+
bookmarking: "oa:bookmarking",
|
|
55545
|
+
classifying: "oa:classifying",
|
|
55546
|
+
commenting: "oa:commenting",
|
|
55547
|
+
describing: "oa:describing",
|
|
55548
|
+
editing: "oa:editing",
|
|
55549
|
+
highlighting: "oa:highlighting",
|
|
55550
|
+
identifying: "oa:identifying",
|
|
55551
|
+
linking: "oa:linking",
|
|
55552
|
+
moderating: "oa:moderating",
|
|
55553
|
+
questioning: "oa:questioning",
|
|
55554
|
+
replying: "oa:replying",
|
|
55555
|
+
reviewing: "oa:reviewing",
|
|
55556
|
+
assessing: "oa:assessing",
|
|
55557
|
+
tagging: "oa:tagging",
|
|
55558
|
+
auto: "oa:autoDirection",
|
|
55559
|
+
ltr: "oa:ltrDirection",
|
|
55560
|
+
rtl: "oa:rtlDirection",
|
|
55561
|
+
body: { "@type": "@id", "@id": "oa:hasBody" },
|
|
55562
|
+
target: { "@type": "@id", "@id": "oa:hasTarget" },
|
|
55563
|
+
source: { "@type": "@id", "@id": "oa:hasSource" },
|
|
55564
|
+
selector: { "@type": "@id", "@id": "oa:hasSelector" },
|
|
55565
|
+
state: { "@type": "@id", "@id": "oa:hasState" },
|
|
55566
|
+
scope: { "@type": "@id", "@id": "oa:hasScope" },
|
|
55567
|
+
refinedBy: { "@type": "@id", "@id": "oa:refinedBy" },
|
|
55568
|
+
startSelector: { "@type": "@id", "@id": "oa:hasStartSelector" },
|
|
55569
|
+
endSelector: { "@type": "@id", "@id": "oa:hasEndSelector" },
|
|
55570
|
+
renderedVia: { "@type": "@id", "@id": "oa:renderedVia" },
|
|
55571
|
+
creator: { "@type": "@id", "@id": "dcterms:creator" },
|
|
55572
|
+
generator: { "@type": "@id", "@id": "as:generator" },
|
|
55573
|
+
rights: { "@type": "@id", "@id": "dcterms:rights" },
|
|
55574
|
+
homepage: { "@type": "@id", "@id": "foaf:homepage" },
|
|
55575
|
+
via: { "@type": "@id", "@id": "oa:via" },
|
|
55576
|
+
canonical: { "@type": "@id", "@id": "oa:canonical" },
|
|
55577
|
+
stylesheet: { "@type": "@id", "@id": "oa:styledBy" },
|
|
55578
|
+
cached: { "@type": "@id", "@id": "oa:cachedSource" },
|
|
55579
|
+
conformsTo: { "@type": "@id", "@id": "dcterms:conformsTo" },
|
|
55580
|
+
items: { "@type": "@id", "@id": "as:items", "@container": "@list" },
|
|
55581
|
+
partOf: { "@type": "@id", "@id": "as:partOf" },
|
|
55582
|
+
first: { "@type": "@id", "@id": "as:first" },
|
|
55583
|
+
last: { "@type": "@id", "@id": "as:last" },
|
|
55584
|
+
next: { "@type": "@id", "@id": "as:next" },
|
|
55585
|
+
prev: { "@type": "@id", "@id": "as:prev" },
|
|
55586
|
+
audience: { "@type": "@id", "@id": "schema:audience" },
|
|
55587
|
+
motivation: { "@type": "@vocab", "@id": "oa:motivatedBy" },
|
|
55588
|
+
purpose: { "@type": "@vocab", "@id": "oa:hasPurpose" },
|
|
55589
|
+
textDirection: { "@type": "@vocab", "@id": "oa:textDirection" },
|
|
55590
|
+
accessibility: "schema:accessibilityFeature",
|
|
55591
|
+
bodyValue: "oa:bodyValue",
|
|
55592
|
+
format: "dc:format",
|
|
55593
|
+
language: "dc:language",
|
|
55594
|
+
processingLanguage: "oa:processingLanguage",
|
|
55595
|
+
value: "rdf:value",
|
|
55596
|
+
exact: "oa:exact",
|
|
55597
|
+
prefix: "oa:prefix",
|
|
55598
|
+
suffix: "oa:suffix",
|
|
55599
|
+
styleClass: "oa:styleClass",
|
|
55600
|
+
name: "foaf:name",
|
|
55601
|
+
email: "foaf:mbox",
|
|
55602
|
+
email_sha1: "foaf:mbox_sha1sum",
|
|
55603
|
+
nickname: "foaf:nick",
|
|
55604
|
+
label: "rdfs:label",
|
|
55605
|
+
created: { "@id": "dcterms:created", "@type": "xsd:dateTime" },
|
|
55606
|
+
modified: { "@id": "dcterms:modified", "@type": "xsd:dateTime" },
|
|
55607
|
+
generated: { "@id": "dcterms:issued", "@type": "xsd:dateTime" },
|
|
55608
|
+
sourceDate: { "@id": "oa:sourceDate", "@type": "xsd:dateTime" },
|
|
55609
|
+
sourceDateStart: { "@id": "oa:sourceDateStart", "@type": "xsd:dateTime" },
|
|
55610
|
+
sourceDateEnd: { "@id": "oa:sourceDateEnd", "@type": "xsd:dateTime" },
|
|
55611
|
+
start: { "@id": "oa:start", "@type": "xsd:nonNegativeInteger" },
|
|
55612
|
+
end: { "@id": "oa:end", "@type": "xsd:nonNegativeInteger" },
|
|
55613
|
+
total: { "@id": "as:totalItems", "@type": "xsd:nonNegativeInteger" },
|
|
55614
|
+
startIndex: { "@id": "as:startIndex", "@type": "xsd:nonNegativeInteger" }
|
|
55615
|
+
}
|
|
55616
|
+
};
|
|
55617
|
+
|
|
55492
55618
|
// lib/core/contexts/index.mjs
|
|
55493
55619
|
var CONTEXTS = {
|
|
55494
55620
|
"https://www.w3.org/ns/activitystreams": activitystreams_default,
|
|
@@ -55504,7 +55630,8 @@ var CONTEXTS = {
|
|
|
55504
55630
|
"https://w3id.org/fep/5711": fep_5711_default,
|
|
55505
55631
|
"https://join-lemmy.org/context.json": join_lemmy_default,
|
|
55506
55632
|
"http://joinmastodon.org/ns": joinmastodon_default,
|
|
55507
|
-
"https://purl.archive.org/miscellany": miscellany_default
|
|
55633
|
+
"https://purl.archive.org/miscellany": miscellany_default,
|
|
55634
|
+
"http://www.w3.org/ns/anno.jsonld": anno_default
|
|
55508
55635
|
};
|
|
55509
55636
|
|
|
55510
55637
|
// lib/core/graphview.mjs
|
|
@@ -55762,146 +55889,6 @@ async function readLenient(raw) {
|
|
|
55762
55889
|
}
|
|
55763
55890
|
}
|
|
55764
55891
|
|
|
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
55892
|
// lib/core/publisher/notes.mjs
|
|
55906
55893
|
init_node_crypto();
|
|
55907
55894
|
init_wire();
|
|
@@ -55965,7 +55952,21 @@ function assertDirectAddressed(content, mentions) {
|
|
|
55965
55952
|
e.code = "unaddressed";
|
|
55966
55953
|
throw e;
|
|
55967
55954
|
}
|
|
55968
|
-
|
|
55955
|
+
var SLUG_OK = /^[A-Za-z0-9._-]{1,64}$/u;
|
|
55956
|
+
var safeSlug = (s) => typeof s === "string" && SLUG_OK.test(s) && !/^\.+$/u.test(s) ? s : null;
|
|
55957
|
+
async function slugFor(publisher, container, wanted, published) {
|
|
55958
|
+
const name = safeSlug(wanted);
|
|
55959
|
+
if (name && !await read(publisher.remote, container + name).catch(() => null)) return name;
|
|
55960
|
+
return published.slice(0, 10) + "-" + node_crypto_default.randomBytes(4).toString("hex");
|
|
55961
|
+
}
|
|
55962
|
+
function rowContent(obj) {
|
|
55963
|
+
if (typeof obj?.content === "string" && obj.content.trim()) return sanitizeHtml(obj.content);
|
|
55964
|
+
const plain = [obj?.bodyValue, obj?.name, obj?.summary].find((v) => typeof v === "string" && v.trim());
|
|
55965
|
+
if (plain) return contentHtml(plain);
|
|
55966
|
+
const esc = (v) => String(v).replace(/[&<>"]/gu, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[c]);
|
|
55967
|
+
return `<p><a href="${esc(obj?.id || "")}">${esc(obj?.type || "object")}</a></p>`;
|
|
55968
|
+
}
|
|
55969
|
+
async function publishNote(publisher, content, { inReplyTo, attachments, visibility = "public", spoilerText = null, slug: wanted = null } = {}) {
|
|
55969
55970
|
const { urls } = publisher;
|
|
55970
55971
|
const priv = visibility === "private" || visibility === "direct";
|
|
55971
55972
|
if (priv) {
|
|
@@ -55973,7 +55974,7 @@ async function publishNote(publisher, content, { inReplyTo, attachments, visibil
|
|
|
55973
55974
|
if (ready !== true) throw new Error(ready);
|
|
55974
55975
|
}
|
|
55975
55976
|
const published = (/* @__PURE__ */ new Date()).toISOString();
|
|
55976
|
-
const slug =
|
|
55977
|
+
const slug = await slugFor(publisher, priv ? urls.privateNotes : urls.notes, wanted, published);
|
|
55977
55978
|
const mentions = await publisher._mentionsFor(content, inReplyTo);
|
|
55978
55979
|
if (visibility === "direct") assertDirectAddressed(content, mentions);
|
|
55979
55980
|
const note = noteDoc({
|
|
@@ -56009,6 +56010,7 @@ async function publishNote(publisher, content, { inReplyTo, attachments, visibil
|
|
|
56009
56010
|
...attachments?.length ? { attachments } : {},
|
|
56010
56011
|
...note.tag?.length ? { mentions: note.tag.map((t) => ({ href: t.href, name: t.name })) } : {}
|
|
56011
56012
|
});
|
|
56013
|
+
if (await publisher.store.commit?.() === false) publisher.log(`post published but its timeline row was refused: ${note.id}`);
|
|
56012
56014
|
const create = createActivity(note, urls);
|
|
56013
56015
|
await writeCreate(publisher.remote, create.id, create);
|
|
56014
56016
|
const contacts = publisher.store.getContacts();
|
|
@@ -56033,6 +56035,57 @@ async function publishNote(publisher, content, { inReplyTo, attachments, visibil
|
|
|
56033
56035
|
}
|
|
56034
56036
|
return note;
|
|
56035
56037
|
}
|
|
56038
|
+
async function publishObject(publisher, object, { visibility = "public", slug: wanted = null } = {}) {
|
|
56039
|
+
const { urls } = publisher;
|
|
56040
|
+
const priv = visibility === "private" || visibility === "direct";
|
|
56041
|
+
if (priv) {
|
|
56042
|
+
const ready = await publisher.privateReady();
|
|
56043
|
+
if (ready !== true) throw new Error(ready);
|
|
56044
|
+
}
|
|
56045
|
+
const published = (/* @__PURE__ */ new Date()).toISOString();
|
|
56046
|
+
const container = priv ? urls.privateNotes : urls.notes;
|
|
56047
|
+
const pod = publisher.config?.remotePod || urls.base;
|
|
56048
|
+
const ownId = typeof object.id === "string" && /^https?:\/\//u.test(object.id) && object.id.startsWith(pod) ? object.id : null;
|
|
56049
|
+
const name = await slugFor(publisher, container, wanted, published);
|
|
56050
|
+
const addressed = addressing(urls, visibility);
|
|
56051
|
+
let doc = null;
|
|
56052
|
+
const id = ownId || container + name;
|
|
56053
|
+
if (!ownId) {
|
|
56054
|
+
doc = { ...object, id, attributedTo: urls.actor, published: object.published || published, to: addressed.to, cc: addressed.cc };
|
|
56055
|
+
if (!doc["@context"]) doc["@context"] = AS_CTX;
|
|
56056
|
+
if (typeof doc.content === "string") doc.content = sanitizeHtml(doc.content);
|
|
56057
|
+
await write4(publisher.remote, id, doc);
|
|
56058
|
+
}
|
|
56059
|
+
if (!priv) await publisher.recordOutbox(id);
|
|
56060
|
+
const row = doc || object;
|
|
56061
|
+
publisher.store.addStatus({
|
|
56062
|
+
noteId: id,
|
|
56063
|
+
actor: urls.actor,
|
|
56064
|
+
content: rowContent(row),
|
|
56065
|
+
published: row.published || published,
|
|
56066
|
+
kind: "post",
|
|
56067
|
+
slug: name,
|
|
56068
|
+
visibility
|
|
56069
|
+
});
|
|
56070
|
+
if (await publisher.store.commit?.() === false) publisher.log(`object published but its timeline row was refused: ${id}`);
|
|
56071
|
+
const createId = doc ? createActivityId(id) : createActivityId(container + name);
|
|
56072
|
+
const create = {
|
|
56073
|
+
"@context": AS_CTX,
|
|
56074
|
+
id: createId,
|
|
56075
|
+
type: "Create",
|
|
56076
|
+
actor: urls.actor,
|
|
56077
|
+
published: row.published || published,
|
|
56078
|
+
to: addressed.to,
|
|
56079
|
+
cc: addressed.cc,
|
|
56080
|
+
object: doc || id
|
|
56081
|
+
};
|
|
56082
|
+
await writeCreate(publisher.remote, create.id, create);
|
|
56083
|
+
const contacts = publisher.store.getContacts();
|
|
56084
|
+
const inboxes = visibility === "direct" ? [] : [...new Set(contacts.followers.map((f) => f.sharedInbox || f.inbox).filter(Boolean))];
|
|
56085
|
+
await publisher.deliverer.deliverToAll(inboxes, create);
|
|
56086
|
+
publisher.log(`${row.type || "object"} published: ${id} \u2192 ${inboxes.length} inbox(es)`);
|
|
56087
|
+
return { id, createId, copied: !ownId };
|
|
56088
|
+
}
|
|
56036
56089
|
async function updateNote(publisher, s, { content, spoilerText = null, attachments = null } = {}) {
|
|
56037
56090
|
const { urls } = publisher;
|
|
56038
56091
|
const updated = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -56084,6 +56137,158 @@ async function updateNote(publisher, s, { content, spoilerText = null, attachmen
|
|
|
56084
56137
|
return patched;
|
|
56085
56138
|
}
|
|
56086
56139
|
|
|
56140
|
+
// lib/core/publisher/restore.mjs
|
|
56141
|
+
var ACCEPT_AP = 'application/activity+json, application/ld+json; profile="https://www.w3.org/ns/activitystreams"';
|
|
56142
|
+
var REBUILD_MAX_PER_RUN = 200;
|
|
56143
|
+
async function reconcileFollowers(publisher, contacts) {
|
|
56144
|
+
let published = [];
|
|
56145
|
+
try {
|
|
56146
|
+
published = await publisher.readPublishedFollowers() || [];
|
|
56147
|
+
} catch {
|
|
56148
|
+
return 0;
|
|
56149
|
+
}
|
|
56150
|
+
const known2 = new Set(contacts.followers.map((f) => f.actor));
|
|
56151
|
+
const removed = new Set((contacts.removedFollowers || []).map((r) => r.actor));
|
|
56152
|
+
const missing = published.filter((a) => typeof a === "string" && !known2.has(a) && !removed.has(a));
|
|
56153
|
+
if (!missing.length) return 0;
|
|
56154
|
+
let recovered = 0;
|
|
56155
|
+
for (const actor of missing.slice(0, 200)) {
|
|
56156
|
+
try {
|
|
56157
|
+
const res = await publisher.deliverer.signedFetch(actor, { headers: { accept: ACCEPT_AP } });
|
|
56158
|
+
if (!res.ok) continue;
|
|
56159
|
+
const { doc, view, degraded } = await readLenient(await res.json());
|
|
56160
|
+
const actorDoc2 = view ?? doc;
|
|
56161
|
+
if (degraded) publisher.log?.(`actor ${actor} grounded to read: ${degraded}`);
|
|
56162
|
+
if (!actorDoc2?.inbox) continue;
|
|
56163
|
+
contacts.followers.push({
|
|
56164
|
+
actor,
|
|
56165
|
+
inbox: actorDoc2.inbox,
|
|
56166
|
+
sharedInbox: actorDoc2.endpoints?.sharedInbox || null,
|
|
56167
|
+
recovered: true,
|
|
56168
|
+
// Said explicitly, because onUndo reads it: the pod publishes WHO
|
|
56169
|
+
// follows, never the id of the Follow that did it, so a recovered
|
|
56170
|
+
// record has nothing an Undo can be matched against and must not be
|
|
56171
|
+
// evictable by one naming anything at all.
|
|
56172
|
+
followId: null
|
|
56173
|
+
});
|
|
56174
|
+
recovered++;
|
|
56175
|
+
} catch {
|
|
56176
|
+
}
|
|
56177
|
+
}
|
|
56178
|
+
if (recovered) {
|
|
56179
|
+
publisher.store.setContacts(contacts);
|
|
56180
|
+
publisher.log(`reconciled ${recovered} follower(s) the pod knew about and this machine did not \u2014 a restored or copied state was behind`);
|
|
56181
|
+
}
|
|
56182
|
+
return recovered;
|
|
56183
|
+
}
|
|
56184
|
+
async function reconcileOutbox(publisher, outbox) {
|
|
56185
|
+
let published = [];
|
|
56186
|
+
try {
|
|
56187
|
+
published = await publisher.readPublishedOutbox() || [];
|
|
56188
|
+
} catch {
|
|
56189
|
+
return 0;
|
|
56190
|
+
}
|
|
56191
|
+
if (!Array.isArray(published) || !published.length) return 0;
|
|
56192
|
+
const idOf2 = (i) => typeof i === "string" ? i : i?.id || null;
|
|
56193
|
+
const known2 = new Set(outbox.map(idOf2).filter(Boolean));
|
|
56194
|
+
const removed = new Set(publisher.store.read("outbox-removed.json", []).map((r) => r.id));
|
|
56195
|
+
const missing = published.filter((i) => {
|
|
56196
|
+
const id = idOf2(i);
|
|
56197
|
+
return id && !known2.has(id) && !removed.has(id);
|
|
56198
|
+
});
|
|
56199
|
+
if (!missing.length) return 0;
|
|
56200
|
+
outbox.push(...missing);
|
|
56201
|
+
publisher.store.write("outbox.json", outbox);
|
|
56202
|
+
publisher.log(`reconciled ${missing.length} outbox entr(ies) the pod carried and this machine did not`);
|
|
56203
|
+
return missing.length;
|
|
56204
|
+
}
|
|
56205
|
+
async function rebuildStatuses(publisher, { fromNotes = false } = {}) {
|
|
56206
|
+
const { urls } = publisher;
|
|
56207
|
+
const ids = /* @__PURE__ */ new Set();
|
|
56208
|
+
const boosts = [];
|
|
56209
|
+
let indexed = false;
|
|
56210
|
+
const published = await publisher.readPublishedOutbox().catch(() => null);
|
|
56211
|
+
if (published) {
|
|
56212
|
+
indexed = true;
|
|
56213
|
+
for (const item of published) {
|
|
56214
|
+
if (typeof item === "string") {
|
|
56215
|
+
if (item.startsWith(urls.notes)) ids.add(item);
|
|
56216
|
+
} else if (item?.type === "Announce") boosts.push(item);
|
|
56217
|
+
}
|
|
56218
|
+
}
|
|
56219
|
+
if (fromNotes) {
|
|
56220
|
+
for (const child of await list2(publisher.remote, urls).catch(() => [])) {
|
|
56221
|
+
ids.add(child.url);
|
|
56222
|
+
}
|
|
56223
|
+
indexed = true;
|
|
56224
|
+
}
|
|
56225
|
+
if (!indexed) return { indexed: 0, recovered: 0, reblogs: 0, landed: false, why: "the pod would not answer for its outbox" };
|
|
56226
|
+
const statuses = publisher.store.getStatuses();
|
|
56227
|
+
const have = new Set(statuses.map((s) => s.noteId));
|
|
56228
|
+
const removed = new Set(publisher.store.read("outbox-removed.json", []).map((r) => r.id));
|
|
56229
|
+
const recovered = [];
|
|
56230
|
+
let budget = REBUILD_MAX_PER_RUN;
|
|
56231
|
+
for (const id of ids) {
|
|
56232
|
+
if (budget <= 0) {
|
|
56233
|
+
publisher.log(`rebuild: stopping at ${REBUILD_MAX_PER_RUN} this run \u2014 run it again for the rest`);
|
|
56234
|
+
break;
|
|
56235
|
+
}
|
|
56236
|
+
if (have.has(id) || removed.has(id)) continue;
|
|
56237
|
+
budget--;
|
|
56238
|
+
const note = await read(publisher.remote, id).catch(() => null);
|
|
56239
|
+
if (!note?.type || note.id !== id || note.attributedTo !== urls.actor) continue;
|
|
56240
|
+
const attachments = attachmentsOf(note);
|
|
56241
|
+
const mentions = (Array.isArray(note.tag) ? note.tag : []).filter((t) => t?.type === "Mention" && t.href).map((t) => ({ href: t.href, name: t.name }));
|
|
56242
|
+
recovered.push({
|
|
56243
|
+
noteId: note.id,
|
|
56244
|
+
actor: urls.actor,
|
|
56245
|
+
content: rowContent(note),
|
|
56246
|
+
published: note.published || null,
|
|
56247
|
+
...note.inReplyTo ? { inReplyTo: note.inReplyTo } : {},
|
|
56248
|
+
kind: "post",
|
|
56249
|
+
slug: note.id.slice(urls.notes.length),
|
|
56250
|
+
...attachments.length ? { attachments } : {},
|
|
56251
|
+
...mentions.length ? { mentions } : {},
|
|
56252
|
+
recovered: true
|
|
56253
|
+
});
|
|
56254
|
+
}
|
|
56255
|
+
const merged = [...statuses, ...recovered];
|
|
56256
|
+
let reblogs = 0;
|
|
56257
|
+
for (const act of boosts) {
|
|
56258
|
+
const object = typeof act.object === "string" ? act.object : act.object?.id;
|
|
56259
|
+
const s = object && merged.find((x) => x.noteId === object);
|
|
56260
|
+
if (!s || s.reblogged) continue;
|
|
56261
|
+
s.reblogged = true;
|
|
56262
|
+
s.announceActivity = act;
|
|
56263
|
+
reblogs++;
|
|
56264
|
+
}
|
|
56265
|
+
if (!recovered.length && !reblogs) return { indexed: ids.size, recovered: 0, reblogs: 0, landed: true };
|
|
56266
|
+
merged.sort((a, b) => String(b.published || "").localeCompare(String(a.published || "")));
|
|
56267
|
+
const kept = merged.slice(0, 1e3);
|
|
56268
|
+
publisher.store.write("statuses.json", kept);
|
|
56269
|
+
const landed = await publisher.store.commit();
|
|
56270
|
+
publisher.log(`rebuilt ${recovered.length} post(s) and ${reblogs} boost(s) from the pod${landed ? "" : " \u2014 THE STATE WRITE DID NOT LAND"}`);
|
|
56271
|
+
return {
|
|
56272
|
+
indexed: ids.size,
|
|
56273
|
+
recovered: recovered.length,
|
|
56274
|
+
reblogs,
|
|
56275
|
+
landed,
|
|
56276
|
+
dropped: Math.max(0, merged.length - kept.length)
|
|
56277
|
+
};
|
|
56278
|
+
}
|
|
56279
|
+
async function healStatuses(publisher) {
|
|
56280
|
+
const { urls, store } = publisher;
|
|
56281
|
+
const own = store.read("outbox.json", []).map((i) => typeof i === "string" ? i : null).filter((id) => id && id.startsWith(urls.notes));
|
|
56282
|
+
if (!own.length) return { missing: 0, recovered: 0 };
|
|
56283
|
+
const have = new Set(store.getStatuses().map((s) => s.noteId));
|
|
56284
|
+
const removed = new Set(store.read("outbox-removed.json", []).map((r2) => r2.id));
|
|
56285
|
+
const missing = own.filter((id) => !have.has(id) && !removed.has(id));
|
|
56286
|
+
if (!missing.length) return { missing: 0, recovered: 0 };
|
|
56287
|
+
const r = await rebuildStatuses(publisher);
|
|
56288
|
+
publisher.log(`timeline index healed: ${missing.length} own post(s) were missing, ${r.recovered} recovered`);
|
|
56289
|
+
return { missing: missing.length, recovered: r.recovered };
|
|
56290
|
+
}
|
|
56291
|
+
|
|
56087
56292
|
// lib/core/publisher/questions.mjs
|
|
56088
56293
|
init_node_crypto();
|
|
56089
56294
|
init_wire();
|
|
@@ -56225,6 +56430,7 @@ async function publishQuestion(publisher, content, {
|
|
|
56225
56430
|
...spoilerText ? { spoiler: spoilerText } : {},
|
|
56226
56431
|
...question.tag?.length ? { mentions: question.tag.map((t) => ({ href: t.href, name: t.name })) } : {}
|
|
56227
56432
|
});
|
|
56433
|
+
if (await publisher.store.commit?.() === false) publisher.log(`poll published but its timeline row was refused: ${question.id}`);
|
|
56228
56434
|
const create = publisher._pollActivity("Create", question, createActivityId(question.id));
|
|
56229
56435
|
await writeCreate(publisher.remote, create.id, create);
|
|
56230
56436
|
const contacts = publisher.store.getContacts();
|
|
@@ -56405,6 +56611,14 @@ var Publisher = class {
|
|
|
56405
56611
|
// republish button. Without it, an actor lost from the pod would match the
|
|
56406
56612
|
// digest, be skipped, and leave the agent reporting success while nobody can
|
|
56407
56613
|
// resolve it.
|
|
56614
|
+
// The Gateway's outbox door for this account, when a Gateway is attached:
|
|
56615
|
+
// beside its inbox door, or under the fronted actor.
|
|
56616
|
+
gatewayOutbox() {
|
|
56617
|
+
const gw = this.config.gateway;
|
|
56618
|
+
if (!(gw && gw.url && gw.mode && gw.mode !== "off")) return null;
|
|
56619
|
+
if (gw.frontActor) return String(gw.frontActor).replace(/ap\/actor\/?$/u, "ap/outbox");
|
|
56620
|
+
return String(gw.url).replace(/ap\/inbox\/?$/u, "ap/outbox");
|
|
56621
|
+
}
|
|
56408
56622
|
async publishProfile({ force = false } = {}) {
|
|
56409
56623
|
const { urls } = this;
|
|
56410
56624
|
const host = new URL(urls.base).host;
|
|
@@ -56434,7 +56648,8 @@ var Publisher = class {
|
|
|
56434
56648
|
inbox: gwActive ? gw.url : null,
|
|
56435
56649
|
// The agent's own outbox endpoint, where it is reachable: a client
|
|
56436
56650
|
// following the actor must arrive somewhere that will take a write.
|
|
56437
|
-
|
|
56651
|
+
// Otherwise the Gateway's outbox door, when one is attached.
|
|
56652
|
+
outbox: this.clientOrigin ? `${this.clientOrigin}ap/outbox` : this.gatewayOutbox(),
|
|
56438
56653
|
// How a client-to-server client finds the way in with nothing configured
|
|
56439
56654
|
// by hand. Advertised only where the surface is publicly reachable.
|
|
56440
56655
|
oauthAuthorize: this.clientOrigin ? `${this.clientOrigin}oauth/authorize` : null,
|
|
@@ -56485,7 +56700,9 @@ var Publisher = class {
|
|
|
56485
56700
|
const wrote = await linkInWebIdProfile(this.remote, {
|
|
56486
56701
|
actorUrl: urls.actor,
|
|
56487
56702
|
accountName: `@${this.config.handle}@${host}`,
|
|
56488
|
-
kind: this.config.kind
|
|
56703
|
+
kind: this.config.kind,
|
|
56704
|
+
// Where a Solid client posts: dokieli reads `as:outbox` off the WebID.
|
|
56705
|
+
outbox: this.clientOrigin ? `${this.clientOrigin}ap/outbox` : this.gatewayOutbox()
|
|
56489
56706
|
});
|
|
56490
56707
|
if (wrote) this.log("WebID profile now lists the actor as a foaf:account");
|
|
56491
56708
|
} catch (e) {
|
|
@@ -56732,6 +56949,9 @@ var Publisher = class {
|
|
|
56732
56949
|
rebuildStatuses(...a) {
|
|
56733
56950
|
return rebuildStatuses(this, ...a);
|
|
56734
56951
|
}
|
|
56952
|
+
healStatuses(...a) {
|
|
56953
|
+
return healStatuses(this, ...a);
|
|
56954
|
+
}
|
|
56735
56955
|
// notes.mjs
|
|
56736
56956
|
ensureMediaContainer(...a) {
|
|
56737
56957
|
return ensureMediaContainer(this, ...a);
|
|
@@ -56751,6 +56971,9 @@ var Publisher = class {
|
|
|
56751
56971
|
publishNote(...a) {
|
|
56752
56972
|
return publishNote(this, ...a);
|
|
56753
56973
|
}
|
|
56974
|
+
publishObject(...a) {
|
|
56975
|
+
return publishObject(this, ...a);
|
|
56976
|
+
}
|
|
56754
56977
|
updateNote(...a) {
|
|
56755
56978
|
return updateNote(this, ...a);
|
|
56756
56979
|
}
|
|
@@ -56833,13 +57056,13 @@ var MAX_FORWARDS_PER_DRAIN = 20;
|
|
|
56833
57056
|
var ACCEPT_AP2 = 'application/activity+json, application/ld+json; profile="https://www.w3.org/ns/activitystreams"';
|
|
56834
57057
|
function trimActivity(a) {
|
|
56835
57058
|
if (!a || typeof a !== "object") return a ?? null;
|
|
56836
|
-
const
|
|
57059
|
+
const idOf2 = (v) => typeof v === "string" ? v : v?.id ?? null;
|
|
56837
57060
|
const out = {};
|
|
56838
57061
|
for (const k of ["id", "type", "actor", "target"]) {
|
|
56839
|
-
const v =
|
|
57062
|
+
const v = idOf2(a[k]);
|
|
56840
57063
|
if (v) out[k] = String(v).slice(0, 2048);
|
|
56841
57064
|
}
|
|
56842
|
-
const obj =
|
|
57065
|
+
const obj = idOf2(a.object);
|
|
56843
57066
|
if (obj) out.object = String(obj).slice(0, 2048);
|
|
56844
57067
|
else if (a.object && typeof a.object === "object" && a.object.type) {
|
|
56845
57068
|
out.object = { type: String(a.object.type).slice(0, 64) };
|
|
@@ -60814,8 +61037,8 @@ function walkPath(graph2, subject, path, visited = new node_set_default()) {
|
|
|
60814
61037
|
pathValues.addAll(deeperValues);
|
|
60815
61038
|
return pathValues;
|
|
60816
61039
|
}
|
|
60817
|
-
function flatMap(
|
|
60818
|
-
return [...
|
|
61040
|
+
function flatMap(arr2, func) {
|
|
61041
|
+
return [...arr2].reduce((acc, x) => acc.concat(func(x)), []);
|
|
60819
61042
|
}
|
|
60820
61043
|
|
|
60821
61044
|
// node_modules/rdf-validate-shacl/src/dataset-utils.js
|
|
@@ -62764,8 +62987,10 @@ var ATTEMPTS_TTL_MS = 7 * 24 * 60 * 6e4;
|
|
|
62764
62987
|
var MAX_ITEM_ATTEMPTS = 5;
|
|
62765
62988
|
var MAX_ITEMS_PER_DRAIN = 50;
|
|
62766
62989
|
var Intake = class {
|
|
62767
|
-
|
|
62768
|
-
|
|
62990
|
+
// `ownerPost(activity, { raw, slug })` is the client-to-server dispatcher,
|
|
62991
|
+
// for an item the Gateway's outbox door took on the owner's behalf.
|
|
62992
|
+
constructor({ config, urls, remote, store, deliverer, publisher, log: log2 = console.log, lease = null, archive = null, push = true, pollSeconds = null, ownerPost = null }) {
|
|
62993
|
+
Object.assign(this, { config, urls, remote, store, deliverer, publisher, log: log2, lease, archive, push, pollSeconds, ownerPost });
|
|
62769
62994
|
this.serial = Date.now();
|
|
62770
62995
|
this.stopped = false;
|
|
62771
62996
|
this.lastDrain = null;
|
|
@@ -63073,9 +63298,10 @@ var Intake = class {
|
|
|
63073
63298
|
}
|
|
63074
63299
|
const receipt = activity ? await this._readReceipt(url) : null;
|
|
63075
63300
|
if (activity && this.gatewaySecret()) this._bumpGatewayStat(!!receipt?.verified);
|
|
63076
|
-
const
|
|
63077
|
-
|
|
63078
|
-
if (!rejection) await this.
|
|
63301
|
+
const owned = activity && this.isOwnerPost(receipt);
|
|
63302
|
+
const rejection = !activity ? "unparsable JSON" : owned ? await this.ownerPostFrom(activity, raw, receipt) : await this.handle(activity, receipt);
|
|
63303
|
+
if (!rejection && raw && !owned) await this._archive(url, raw, activity);
|
|
63304
|
+
if (!rejection && !owned) await this._maybeForward(activity);
|
|
63079
63305
|
if (rejection) {
|
|
63080
63306
|
this.store.addDeadLetter({
|
|
63081
63307
|
inboxUrl: url,
|
|
@@ -63223,6 +63449,28 @@ var Intake = class {
|
|
|
63223
63449
|
_readReceipt(...a) {
|
|
63224
63450
|
return readReceipt(this, ...a);
|
|
63225
63451
|
}
|
|
63452
|
+
// A receipt the door stamped `c2s` for THIS actor, and only a receipt whose
|
|
63453
|
+
// HMAC verified (readReceipt returns nothing else). A stranger appending an
|
|
63454
|
+
// item that claims to be ours has no such receipt and is read as mail.
|
|
63455
|
+
isOwnerPost(receipt) {
|
|
63456
|
+
return !!(this.ownerPost && receipt?.verified && receipt.method === "c2s" && receipt.actor === this.urls.actor);
|
|
63457
|
+
}
|
|
63458
|
+
// Publish what the owner posted at the door. The bytes as the client wrote
|
|
63459
|
+
// them go to the dispatcher (an object stored as sent must be the client's
|
|
63460
|
+
// document, not our reading of it); the graph is what it decides from.
|
|
63461
|
+
// Returns null when published, else the reason it was not — a dead letter.
|
|
63462
|
+
async ownerPostFrom(activity, raw, receipt) {
|
|
63463
|
+
let asSent = null;
|
|
63464
|
+
try {
|
|
63465
|
+
asSent = raw ? JSON.parse(raw) : null;
|
|
63466
|
+
} catch {
|
|
63467
|
+
asSent = null;
|
|
63468
|
+
}
|
|
63469
|
+
const r = await this.ownerPost(activity, { raw: asSent, slug: receipt.slug || null });
|
|
63470
|
+
if (!r || r.status >= 300) return `owner post refused (${r?.status || "?"}): ${r?.body?.error || ""}`;
|
|
63471
|
+
this.log(`owner post from the outbox door published: ${r.body?.object || r.body?.id || activity.type}`);
|
|
63472
|
+
return null;
|
|
63473
|
+
}
|
|
63226
63474
|
receiptVouchesFor(...a) {
|
|
63227
63475
|
return receiptVouchesFor(this, ...a);
|
|
63228
63476
|
}
|
|
@@ -63306,6 +63554,846 @@ var Intake = class {
|
|
|
63306
63554
|
}
|
|
63307
63555
|
};
|
|
63308
63556
|
|
|
63557
|
+
// lib/core/social.mjs
|
|
63558
|
+
var social_exports = {};
|
|
63559
|
+
__export(social_exports, {
|
|
63560
|
+
admitRequest: () => admitRequest,
|
|
63561
|
+
announceModeration: () => announceModeration,
|
|
63562
|
+
applyModeration: () => applyModeration,
|
|
63563
|
+
blockActor: () => blockActor,
|
|
63564
|
+
confirmDelegation: () => confirmDelegation,
|
|
63565
|
+
deleteNote: () => deleteNote,
|
|
63566
|
+
ejectFollower: () => ejectFollower,
|
|
63567
|
+
favourite: () => favourite,
|
|
63568
|
+
followActor: () => followActor,
|
|
63569
|
+
followHandle: () => followHandle,
|
|
63570
|
+
lookupWebFinger: () => lookupWebFinger,
|
|
63571
|
+
pinStatus: () => pinStatus,
|
|
63572
|
+
reblog: () => reblog,
|
|
63573
|
+
refuseRequest: () => refuseRequest,
|
|
63574
|
+
resolveHandle: () => resolveHandle,
|
|
63575
|
+
retractAnnouncement: () => retractAnnouncement,
|
|
63576
|
+
selfLink: () => selfLink,
|
|
63577
|
+
unblockActor: () => unblockActor,
|
|
63578
|
+
unfavourite: () => unfavourite,
|
|
63579
|
+
unfollowActor: () => unfollowActor,
|
|
63580
|
+
unreblog: () => unreblog,
|
|
63581
|
+
votePoll: () => votePoll
|
|
63582
|
+
});
|
|
63583
|
+
init_node_crypto();
|
|
63584
|
+
init_wire();
|
|
63585
|
+
async function lookupWebFinger(acct, { fetchImpl = null } = {}) {
|
|
63586
|
+
const clean = String(acct || "").replace(/^acct:/, "");
|
|
63587
|
+
const at = clean.lastIndexOf("@");
|
|
63588
|
+
if (at < 1) return null;
|
|
63589
|
+
const [user, host] = [clean.slice(0, at), clean.slice(at + 1)];
|
|
63590
|
+
if (!host || /[/\\?#]/.test(host)) return null;
|
|
63591
|
+
const { safeFetch: safeFetch2, readCapped: readCapped3 } = await Promise.resolve().then(() => (init_safefetch(), safefetch_exports));
|
|
63592
|
+
const url = `https://${host}/.well-known/webfinger?resource=${encodeURIComponent(`acct:${clean}`)}`;
|
|
63593
|
+
const headers = { accept: "application/jrd+json, application/json" };
|
|
63594
|
+
const res = await (fetchImpl ? fetchImpl(url, { headers }) : safeFetch2(url, { headers })).catch(() => null);
|
|
63595
|
+
if (!res || res.status >= 400) return null;
|
|
63596
|
+
let jrd2;
|
|
63597
|
+
try {
|
|
63598
|
+
jrd2 = JSON.parse(await readCapped3(res, 256 * 1024));
|
|
63599
|
+
} catch {
|
|
63600
|
+
return null;
|
|
63601
|
+
}
|
|
63602
|
+
const subject = String(jrd2?.subject || "").replace(/^acct:/, "").toLowerCase();
|
|
63603
|
+
if (subject && subject !== clean.toLowerCase()) {
|
|
63604
|
+
return null;
|
|
63605
|
+
}
|
|
63606
|
+
return jrd2;
|
|
63607
|
+
}
|
|
63608
|
+
var selfLink = (jrd2) => jrd2?.links?.find((l) => l.rel === "self" && /application\/(activity\+json|ld\+json)/.test(l.type || ""));
|
|
63609
|
+
async function confirmDelegation(asked, doc, lookup2 = lookupWebFinger) {
|
|
63610
|
+
let home;
|
|
63611
|
+
try {
|
|
63612
|
+
home = new URL(doc.id).host.toLowerCase();
|
|
63613
|
+
} catch {
|
|
63614
|
+
throw new Error(`actor id is not a URL (${doc.id})`);
|
|
63615
|
+
}
|
|
63616
|
+
if (home === asked.toLowerCase()) return;
|
|
63617
|
+
const user = doc.preferredUsername;
|
|
63618
|
+
if (!user) {
|
|
63619
|
+
throw new Error(`${asked} points at ${doc.id} on another host, which names no username to confirm it by`);
|
|
63620
|
+
}
|
|
63621
|
+
const back = await lookup2(`acct:${user}@${home}`);
|
|
63622
|
+
if (selfLink(back)?.href !== doc.id) {
|
|
63623
|
+
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`);
|
|
63624
|
+
}
|
|
63625
|
+
}
|
|
63626
|
+
async function resolveHandle(agent2, handle7) {
|
|
63627
|
+
const clean = String(handle7 || "").replace(/^@/, "");
|
|
63628
|
+
if (!/^[^@]+@[^@]+$/.test(clean)) throw new Error("handle must look like user@host");
|
|
63629
|
+
if (agent2.store.isBlocked("https://" + clean.split("@")[1] + "/")) throw new Error("domain is blocked");
|
|
63630
|
+
const remote = agent2.intake?.deliverer?.signedFetch ? (u, i) => agent2.intake.deliverer.signedFetch(u, i) : null;
|
|
63631
|
+
const lookup2 = (a) => lookupWebFinger(a, { fetchImpl: remote });
|
|
63632
|
+
const jrd2 = await lookup2("acct:" + clean);
|
|
63633
|
+
const self2 = selfLink(jrd2);
|
|
63634
|
+
if (!self2?.href) throw new Error(`webfinger found no actor for ${clean}`);
|
|
63635
|
+
const doc = await agent2.intake.fetchAP(self2.href);
|
|
63636
|
+
if (!doc?.id) throw new Error(`actor document unusable for ${clean}`);
|
|
63637
|
+
if (agent2.store.isBlocked(doc.id)) throw new Error("actor is blocked");
|
|
63638
|
+
await confirmDelegation(clean.slice(clean.lastIndexOf("@") + 1), doc, lookup2);
|
|
63639
|
+
await cacheCounts(agent2, doc);
|
|
63640
|
+
return doc;
|
|
63641
|
+
}
|
|
63642
|
+
async function cacheCounts(agent2, doc) {
|
|
63643
|
+
const total = async (url) => {
|
|
63644
|
+
if (!url) return null;
|
|
63645
|
+
try {
|
|
63646
|
+
const res = await agent2.intake.fetchAP(url);
|
|
63647
|
+
return Number.isInteger(res?.totalItems) ? res.totalItems : null;
|
|
63648
|
+
} catch {
|
|
63649
|
+
return null;
|
|
63650
|
+
}
|
|
63651
|
+
};
|
|
63652
|
+
const [followers, following] = await Promise.all([total(doc.followers), total(doc.following)]);
|
|
63653
|
+
if (followers === null && following === null) return;
|
|
63654
|
+
const actors = agent2.store.getActors();
|
|
63655
|
+
if (!actors[doc.id]) return;
|
|
63656
|
+
actors[doc.id] = { ...actors[doc.id], counts: { followers, following } };
|
|
63657
|
+
agent2.store.write("actors.json", actors);
|
|
63658
|
+
}
|
|
63659
|
+
async function followActor(agent2, actorUrl, { publish = true, doc: fetched = null } = {}) {
|
|
63660
|
+
const doc = fetched || await agent2.intake.fetchAP(actorUrl);
|
|
63661
|
+
if (!doc?.inbox) throw new Error(`actor document unusable (${actorUrl})`);
|
|
63662
|
+
if (agent2.store.isBlocked(doc.id)) throw new Error("actor is blocked");
|
|
63663
|
+
const contacts = agent2.store.getContacts();
|
|
63664
|
+
let rec = contacts.following.find((f) => f.actor === doc.id);
|
|
63665
|
+
if (!rec) {
|
|
63666
|
+
rec = { actor: doc.id, inbox: doc.inbox, accepted: false };
|
|
63667
|
+
contacts.following.push(rec);
|
|
63668
|
+
}
|
|
63669
|
+
const follow = followActivity({ urls: agent2.publisher.urls, targetActor: doc.id, serial: Date.now() });
|
|
63670
|
+
rec.followActivity = follow;
|
|
63671
|
+
agent2.store.setContacts(contacts);
|
|
63672
|
+
await agent2.deliverer.deliver(doc.inbox, follow);
|
|
63673
|
+
if (publish) await agent2.publisher.publishCollections({ following: true, pending: true });
|
|
63674
|
+
return doc;
|
|
63675
|
+
}
|
|
63676
|
+
async function followHandle(agent2, handle7) {
|
|
63677
|
+
const doc = await resolveHandle(agent2, handle7);
|
|
63678
|
+
await followActor(agent2, doc.id);
|
|
63679
|
+
const clean = String(handle7 || "").replace(/^@/, "");
|
|
63680
|
+
const contacts = agent2.store.getContacts();
|
|
63681
|
+
const rec = contacts.following.find((f) => f.actor === doc.id);
|
|
63682
|
+
if (rec && !rec.handle) {
|
|
63683
|
+
rec.handle = clean;
|
|
63684
|
+
agent2.store.setContacts(contacts);
|
|
63685
|
+
}
|
|
63686
|
+
return { ok: true, actor: doc.id };
|
|
63687
|
+
}
|
|
63688
|
+
async function unfollowActor(agent2, actor) {
|
|
63689
|
+
const contacts = agent2.store.getContacts();
|
|
63690
|
+
const rec = contacts.following.find((f) => f.actor === actor);
|
|
63691
|
+
if (!rec) throw new Error("not following that actor");
|
|
63692
|
+
if (rec.followActivity) {
|
|
63693
|
+
await agent2.deliverer.deliver(
|
|
63694
|
+
rec.inbox,
|
|
63695
|
+
undoActivity({ urls: agent2.publisher.urls, activity: rec.followActivity, serial: Date.now() })
|
|
63696
|
+
);
|
|
63697
|
+
}
|
|
63698
|
+
contacts.following = contacts.following.filter((f) => f.actor !== actor);
|
|
63699
|
+
agent2.store.setContacts(contacts);
|
|
63700
|
+
await agent2.publisher.publishCollections({ following: true, pending: true });
|
|
63701
|
+
return { ok: true };
|
|
63702
|
+
}
|
|
63703
|
+
async function favourite(agent2, s) {
|
|
63704
|
+
if (s.favourited) return s;
|
|
63705
|
+
const doc = await agent2.intake.fetchAP(s.actor);
|
|
63706
|
+
if (!doc?.inbox) throw new Error("author inbox unavailable");
|
|
63707
|
+
const act = likeActivity({ urls: agent2.publisher.urls, noteId: s.noteId, serial: Date.now() });
|
|
63708
|
+
await agent2.deliverer.deliver(doc.endpoints?.sharedInbox || doc.inbox, act);
|
|
63709
|
+
return agent2.store.updateStatus(s.noteId, { favourited: true, likeActivity: act });
|
|
63710
|
+
}
|
|
63711
|
+
async function unfavourite(agent2, s) {
|
|
63712
|
+
if (s.likeActivity) {
|
|
63713
|
+
const doc = await agent2.intake.fetchAP(s.actor).catch(() => null);
|
|
63714
|
+
if (doc?.inbox) {
|
|
63715
|
+
await agent2.deliverer.deliver(
|
|
63716
|
+
doc.endpoints?.sharedInbox || doc.inbox,
|
|
63717
|
+
undoActivity({ urls: agent2.publisher.urls, activity: s.likeActivity, serial: Date.now() })
|
|
63718
|
+
);
|
|
63719
|
+
}
|
|
63720
|
+
}
|
|
63721
|
+
return agent2.store.updateStatus(s.noteId, { favourited: false, likeActivity: void 0 });
|
|
63722
|
+
}
|
|
63723
|
+
async function reblog(agent2, s) {
|
|
63724
|
+
if (s.reblogged) return s;
|
|
63725
|
+
const { urls } = agent2.publisher;
|
|
63726
|
+
const act = announceActivity({ urls, object: s.noteId, serial: Date.now() });
|
|
63727
|
+
const inboxes = new Set(agent2.store.getContacts().followers.map((f) => f.sharedInbox || f.inbox).filter(Boolean));
|
|
63728
|
+
if (s.actor !== urls.actor) {
|
|
63729
|
+
const doc = await agent2.intake.fetchAP(s.actor).catch(() => null);
|
|
63730
|
+
if (doc?.inbox) inboxes.add(doc.endpoints?.sharedInbox || doc.inbox);
|
|
63731
|
+
}
|
|
63732
|
+
await agent2.deliverer.deliverToAll([...inboxes], act);
|
|
63733
|
+
const updated = agent2.store.updateStatus(s.noteId, { reblogged: true, announceActivity: act });
|
|
63734
|
+
await agent2.publisher.recordOutbox(act);
|
|
63735
|
+
if (await agent2.store.commit?.() === false) agent2.log(`boost sent but its timeline row was refused: ${s.noteId}`);
|
|
63736
|
+
return updated;
|
|
63737
|
+
}
|
|
63738
|
+
async function unreblog(agent2, s) {
|
|
63739
|
+
if (s.announceActivity) {
|
|
63740
|
+
const undo = undoActivity({ urls: agent2.publisher.urls, activity: s.announceActivity, serial: Date.now() });
|
|
63741
|
+
const inboxes = new Set(agent2.store.getContacts().followers.map((f) => f.sharedInbox || f.inbox).filter(Boolean));
|
|
63742
|
+
const doc = await agent2.intake.fetchAP(s.actor).catch(() => null);
|
|
63743
|
+
if (doc?.inbox && s.actor !== agent2.publisher.urls.actor) inboxes.add(doc.endpoints?.sharedInbox || doc.inbox);
|
|
63744
|
+
await agent2.deliverer.deliverToAll([...inboxes], undo);
|
|
63745
|
+
await agent2.publisher.unrecordOutbox((i) => i?.id === s.announceActivity.id);
|
|
63746
|
+
}
|
|
63747
|
+
return agent2.store.updateStatus(s.noteId, { reblogged: false, announceActivity: void 0 });
|
|
63748
|
+
}
|
|
63749
|
+
async function ejectFollower(agent2, actor) {
|
|
63750
|
+
const { urls } = agent2.publisher;
|
|
63751
|
+
const contacts = agent2.store.getContacts();
|
|
63752
|
+
const rec = contacts.followers.find((f) => f.actor === actor);
|
|
63753
|
+
if (!rec) throw new Error("not a member");
|
|
63754
|
+
dropFollower(contacts, actor, "ejected");
|
|
63755
|
+
agent2.store.setContacts(contacts);
|
|
63756
|
+
const muted = agent2.store.getMuted();
|
|
63757
|
+
muted.actors = [.../* @__PURE__ */ new Set([...muted.actors, actor])];
|
|
63758
|
+
agent2.store.setMuted(muted);
|
|
63759
|
+
await agent2.publisher.publishCollections({ followers: true });
|
|
63760
|
+
await announceModeration(
|
|
63761
|
+
agent2,
|
|
63762
|
+
blockActivity({ urls, targetActor: actor, serial: Date.now() })
|
|
63763
|
+
).catch((e) => agent2.log?.(`moderation announce failed: ${e.message}`));
|
|
63764
|
+
if (rec.bsky) {
|
|
63765
|
+
await agent2.bskygroup?.blockDid(rec.bsky.did).catch((e) => agent2.log?.(`bluesky block failed for ${rec.bsky.handle}: ${e.message}`));
|
|
63766
|
+
return { ok: true, actor, told: true };
|
|
63767
|
+
}
|
|
63768
|
+
if (rec.inbox) {
|
|
63769
|
+
await agent2.deliverer.deliver(rec.inbox, rejectActivity({
|
|
63770
|
+
urls,
|
|
63771
|
+
followActivity: { id: rec.followId, type: "Follow", actor, object: urls.actor },
|
|
63772
|
+
serial: Date.now()
|
|
63773
|
+
}));
|
|
63774
|
+
}
|
|
63775
|
+
return { ok: true, actor, told: !!rec.inbox };
|
|
63776
|
+
}
|
|
63777
|
+
async function admitRequest(agent2, actor, { publish = true } = {}) {
|
|
63778
|
+
const reqs = agent2.store.getRequests();
|
|
63779
|
+
const req = reqs.find((r) => r.actor === actor);
|
|
63780
|
+
if (!req) throw new Error("no such request");
|
|
63781
|
+
const contacts = agent2.store.getContacts();
|
|
63782
|
+
if (!contacts.followers.some((f) => f.actor === actor)) {
|
|
63783
|
+
const bridgedDid = /^https:\/\/bsky\.brid\.gy\/ap\/(did:[^/]+)$/.exec(actor)?.[1];
|
|
63784
|
+
if (bridgedDid) contacts.followers = contacts.followers.filter((f) => f.bsky?.did !== bridgedDid);
|
|
63785
|
+
contacts.followers.push(req.bsky ? { actor, bsky: req.bsky } : { actor, inbox: req.inbox, sharedInbox: req.sharedInbox, followId: req.activity?.id });
|
|
63786
|
+
agent2.store.setContacts(contacts);
|
|
63787
|
+
agent2.store.addNotification({ type: "follow", actor, ...req.bsky ? { bsky: true } : {} });
|
|
63788
|
+
}
|
|
63789
|
+
agent2.store.setRequests(reqs.filter((r) => r.actor !== actor));
|
|
63790
|
+
if (req.bsky) {
|
|
63791
|
+
await agent2.bskygroup?.nudgeOnce(contacts.followers.find((f) => f.actor === actor));
|
|
63792
|
+
return { ok: true, actor };
|
|
63793
|
+
}
|
|
63794
|
+
if (publish) await agent2.publisher.publishCollections({ followers: true, pending: true });
|
|
63795
|
+
await agent2.deliverer.deliver(req.inbox, acceptActivity({
|
|
63796
|
+
urls: agent2.publisher.urls,
|
|
63797
|
+
followActivity: req.activity,
|
|
63798
|
+
serial: Date.now()
|
|
63799
|
+
}));
|
|
63800
|
+
return { ok: true, actor };
|
|
63801
|
+
}
|
|
63802
|
+
async function refuseRequest(agent2, actor) {
|
|
63803
|
+
const reqs = agent2.store.getRequests();
|
|
63804
|
+
const req = reqs.find((r) => r.actor === actor);
|
|
63805
|
+
if (!req) throw new Error("no such request");
|
|
63806
|
+
agent2.store.setRequests(reqs.filter((r) => r.actor !== actor));
|
|
63807
|
+
await agent2.publisher?.publishCollections({ pending: true }).catch(() => {
|
|
63808
|
+
});
|
|
63809
|
+
if (req.bsky) {
|
|
63810
|
+
await agent2.bskygroup?.blockDid(req.bsky.did).catch((e) => agent2.log?.(`bluesky block failed for ${req.bsky.handle}: ${e.message}`));
|
|
63811
|
+
return { ok: true, actor };
|
|
63812
|
+
}
|
|
63813
|
+
await agent2.deliverer.deliver(req.inbox, rejectActivity({
|
|
63814
|
+
urls: agent2.publisher.urls,
|
|
63815
|
+
followActivity: req.activity,
|
|
63816
|
+
serial: Date.now()
|
|
63817
|
+
}));
|
|
63818
|
+
return { ok: true, actor };
|
|
63819
|
+
}
|
|
63820
|
+
async function retractAnnouncement(agent2, noteId) {
|
|
63821
|
+
return agent2.intake.retract(noteId);
|
|
63822
|
+
}
|
|
63823
|
+
async function votePoll(agent2, s, choices) {
|
|
63824
|
+
const { urls } = agent2.publisher;
|
|
63825
|
+
const poll = s.poll || {};
|
|
63826
|
+
const opts = poll.options || [];
|
|
63827
|
+
if (poll.closed || poll.expiresAt && Date.parse(poll.expiresAt) < Date.now()) {
|
|
63828
|
+
return { ok: false, error: "this poll has closed" };
|
|
63829
|
+
}
|
|
63830
|
+
if (poll.voted) return { ok: false, error: "already voted" };
|
|
63831
|
+
if (s.actor === urls.actor) return { ok: false, error: "this is your own poll" };
|
|
63832
|
+
const picks = [...new Set(choices)].filter((i) => i >= 0 && i < opts.length);
|
|
63833
|
+
if (!picks.length) return { ok: false, error: "invalid choice" };
|
|
63834
|
+
if (!poll.multiple && picks.length > 1) return { ok: false, error: "this poll takes one choice" };
|
|
63835
|
+
const author = await agent2.intake.fetchAP(s.actor).catch(() => null);
|
|
63836
|
+
const inbox = author?.endpoints?.sharedInbox || author?.inbox;
|
|
63837
|
+
if (!inbox) return { ok: false, error: "could not reach the poll author" };
|
|
63838
|
+
const published = (/* @__PURE__ */ new Date()).toISOString();
|
|
63839
|
+
for (const i of picks) {
|
|
63840
|
+
const id = urls.notes + published.slice(0, 10) + "-vote-" + node_crypto_default.randomBytes(4).toString("hex");
|
|
63841
|
+
const note = {
|
|
63842
|
+
"@context": AS_CTX,
|
|
63843
|
+
id,
|
|
63844
|
+
type: "Note",
|
|
63845
|
+
attributedTo: urls.actor,
|
|
63846
|
+
name: opts[i].title,
|
|
63847
|
+
inReplyTo: s.noteId,
|
|
63848
|
+
published,
|
|
63849
|
+
to: [s.actor]
|
|
63850
|
+
};
|
|
63851
|
+
const create = {
|
|
63852
|
+
"@context": AS_CTX,
|
|
63853
|
+
id: createActivityId(id),
|
|
63854
|
+
type: "Create",
|
|
63855
|
+
actor: urls.actor,
|
|
63856
|
+
published,
|
|
63857
|
+
to: [s.actor],
|
|
63858
|
+
object: note
|
|
63859
|
+
};
|
|
63860
|
+
await write4(agent2.remote, id, note);
|
|
63861
|
+
await writeCreate(agent2.remote, create.id, create);
|
|
63862
|
+
await agent2.deliverer.deliverToAll([inbox], create);
|
|
63863
|
+
}
|
|
63864
|
+
agent2.store.updateStatus(s.noteId, {
|
|
63865
|
+
poll: {
|
|
63866
|
+
...poll,
|
|
63867
|
+
voted: true,
|
|
63868
|
+
ownVotes: picks,
|
|
63869
|
+
options: opts.map((o, j) => picks.includes(j) ? { ...o, votes: (o.votes || 0) + 1 } : o)
|
|
63870
|
+
}
|
|
63871
|
+
});
|
|
63872
|
+
return { ok: true };
|
|
63873
|
+
}
|
|
63874
|
+
async function deleteNote(agent2, s) {
|
|
63875
|
+
const { urls } = agent2.publisher;
|
|
63876
|
+
const inboxes = agent2.store.getContacts().followers.map((f) => f.sharedInbox || f.inbox).filter(Boolean);
|
|
63877
|
+
await agent2.deliverer.deliverToAll(inboxes, deleteActivity({ urls, noteId: s.noteId }));
|
|
63878
|
+
const stuck = [];
|
|
63879
|
+
const deletedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
63880
|
+
try {
|
|
63881
|
+
await writeTombstone2(agent2.remote, s.noteId, noteTombstone(s.noteId, deletedAt));
|
|
63882
|
+
} catch {
|
|
63883
|
+
stuck.push(s.noteId);
|
|
63884
|
+
}
|
|
63885
|
+
if (!await dropCreate(agent2.remote, createActivityId(s.noteId))) {
|
|
63886
|
+
stuck.push(createActivityId(s.noteId));
|
|
63887
|
+
}
|
|
63888
|
+
if (stuck.length) {
|
|
63889
|
+
agent2.log?.(`delete ${s.noteId}: the pod kept ${stuck.length} document(s) \u2014 the post is STILL PUBLISHED`);
|
|
63890
|
+
return {
|
|
63891
|
+
ok: false,
|
|
63892
|
+
stillPublished: stuck,
|
|
63893
|
+
error: "the pod would not remove the post \u2014 it is still published, and has been kept here so you can try again"
|
|
63894
|
+
};
|
|
63895
|
+
}
|
|
63896
|
+
await dropReplies(agent2.remote, repliesId(s.noteId));
|
|
63897
|
+
if (s.atproto?.uri && agent2.atproto?.connected()) {
|
|
63898
|
+
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}`));
|
|
63899
|
+
}
|
|
63900
|
+
await agent2.publisher.unrecordOutbox((i) => i === s.noteId || i?.id && i.id === s.announceActivity?.id);
|
|
63901
|
+
agent2.store.removeStatus(s.noteId);
|
|
63902
|
+
return { ok: true };
|
|
63903
|
+
}
|
|
63904
|
+
async function announceModeration(agent2, activity) {
|
|
63905
|
+
if (agent2.store.getConfig()?.kind !== "group") return null;
|
|
63906
|
+
const { urls } = agent2.publisher;
|
|
63907
|
+
const act = announceActivity({ urls, object: activity, serial: Date.now(), audience: urls.actor });
|
|
63908
|
+
const inboxes = [...new Set(agent2.store.getContacts().followers.map((f) => f.sharedInbox || f.inbox).filter(Boolean))];
|
|
63909
|
+
await agent2.deliverer.deliverToAll(inboxes, act);
|
|
63910
|
+
await agent2.publisher.recordOutbox(act);
|
|
63911
|
+
return act;
|
|
63912
|
+
}
|
|
63913
|
+
async function blockActor(agent2, actorUrl) {
|
|
63914
|
+
if (!/^https?:\/\/\S+$/.test(String(actorUrl || ""))) throw new Error("actor must be a URL");
|
|
63915
|
+
const b = agent2.store.getBlocklist();
|
|
63916
|
+
if (!b.actors.includes(actorUrl)) b.actors.push(actorUrl);
|
|
63917
|
+
agent2.store.setBlocklist(b);
|
|
63918
|
+
await unfollowActor(agent2, actorUrl).catch(() => {
|
|
63919
|
+
});
|
|
63920
|
+
await agent2.publisher?.publishCollections({ blocked: true }).catch(() => {
|
|
63921
|
+
});
|
|
63922
|
+
await announceModeration(
|
|
63923
|
+
agent2,
|
|
63924
|
+
blockActivity({ urls: agent2.publisher.urls, targetActor: actorUrl, serial: Date.now() })
|
|
63925
|
+
).catch((e) => agent2.log?.(`moderation announce failed: ${e.message}`));
|
|
63926
|
+
return { ok: true, actor: actorUrl };
|
|
63927
|
+
}
|
|
63928
|
+
async function unblockActor(agent2, actorUrl) {
|
|
63929
|
+
const b = agent2.store.getBlocklist();
|
|
63930
|
+
b.actors = b.actors.filter((a) => a !== actorUrl);
|
|
63931
|
+
agent2.store.setBlocklist(b);
|
|
63932
|
+
await agent2.publisher?.publishCollections({ blocked: true }).catch(() => {
|
|
63933
|
+
});
|
|
63934
|
+
await announceModeration(agent2, undoActivity({
|
|
63935
|
+
urls: agent2.publisher.urls,
|
|
63936
|
+
activity: { type: "Block", actor: agent2.publisher.urls.actor, object: actorUrl },
|
|
63937
|
+
serial: Date.now()
|
|
63938
|
+
})).catch((e) => agent2.log?.(`moderation announce failed: ${e.message}`));
|
|
63939
|
+
return { ok: true, actor: actorUrl };
|
|
63940
|
+
}
|
|
63941
|
+
async function applyModeration(agent2, entry) {
|
|
63942
|
+
const { urls } = agent2.publisher;
|
|
63943
|
+
const target = typeof entry.activity?.object === "string" ? entry.activity.object : entry.activity?.object?.id;
|
|
63944
|
+
switch (entry.type) {
|
|
63945
|
+
case "Block": {
|
|
63946
|
+
if (!target) throw new Error("the queued Block names nobody");
|
|
63947
|
+
const member = agent2.store.getContacts().followers.some((f) => f.actor === target);
|
|
63948
|
+
return member ? ejectFollower(agent2, target) : blockActor(agent2, target);
|
|
63949
|
+
}
|
|
63950
|
+
case "Undo": {
|
|
63951
|
+
const inner = entry.activity?.object;
|
|
63952
|
+
const unbanned = typeof inner === "object" && inner?.type === "Block" ? typeof inner.object === "string" ? inner.object : inner.object?.id : null;
|
|
63953
|
+
if (!unbanned) throw new Error("the queued Undo does not name a Block");
|
|
63954
|
+
const muted = agent2.store.getMuted();
|
|
63955
|
+
muted.actors = muted.actors.filter((a) => a !== unbanned);
|
|
63956
|
+
agent2.store.setMuted(muted);
|
|
63957
|
+
return unblockActor(agent2, unbanned);
|
|
63958
|
+
}
|
|
63959
|
+
case "Delete": {
|
|
63960
|
+
if (!target) throw new Error("the queued Delete names nothing");
|
|
63961
|
+
return agent2.intake.retract(target);
|
|
63962
|
+
}
|
|
63963
|
+
case "Add":
|
|
63964
|
+
case "Remove": {
|
|
63965
|
+
if (!target) throw new Error("the queued roster change names nobody");
|
|
63966
|
+
const cfg = { ...agent2.store.getConfig() };
|
|
63967
|
+
const prev = cfg.moderators || [];
|
|
63968
|
+
cfg.moderators = entry.type === "Add" ? [.../* @__PURE__ */ new Set([...prev, target])] : prev.filter((m) => m !== target);
|
|
63969
|
+
agent2.store.setConfig(cfg);
|
|
63970
|
+
if (agent2.publisher) agent2.publisher.config.moderators = cfg.moderators;
|
|
63971
|
+
await agent2.publisher.publishProfile();
|
|
63972
|
+
await announceModeration(agent2, addRemoveActivity({
|
|
63973
|
+
urls,
|
|
63974
|
+
type: entry.type,
|
|
63975
|
+
object: target,
|
|
63976
|
+
target: urls.moderators,
|
|
63977
|
+
serial: Date.now()
|
|
63978
|
+
})).catch((e) => agent2.log?.(`moderation announce failed: ${e.message}`));
|
|
63979
|
+
return { ok: true, moderators: cfg.moderators };
|
|
63980
|
+
}
|
|
63981
|
+
default:
|
|
63982
|
+
throw new Error(`no way to apply a queued ${entry.type}`);
|
|
63983
|
+
}
|
|
63984
|
+
}
|
|
63985
|
+
async function pinStatus(agent2, s, pinned) {
|
|
63986
|
+
if (s.actor !== agent2.publisher.urls.actor) throw new Error("not your status");
|
|
63987
|
+
const updated = agent2.store.updateStatus(s.noteId, { pinned: !!pinned });
|
|
63988
|
+
await agent2.publisher.publishFeatured().catch((e) => agent2.log?.(`featured: ${e.message}`));
|
|
63989
|
+
agent2.publisher.publishProfile?.().catch(() => {
|
|
63990
|
+
});
|
|
63991
|
+
return updated || s;
|
|
63992
|
+
}
|
|
63993
|
+
|
|
63994
|
+
// lib/client/c2s.mjs
|
|
63995
|
+
init_wire();
|
|
63996
|
+
var MAX_BODY = 512 * 1024;
|
|
63997
|
+
var MAX_INBOX_PAGE = 500;
|
|
63998
|
+
var ACTIVITY_TYPES = /* @__PURE__ */ new Set([
|
|
63999
|
+
"Create",
|
|
64000
|
+
"Update",
|
|
64001
|
+
"Delete",
|
|
64002
|
+
"Follow",
|
|
64003
|
+
"Like",
|
|
64004
|
+
"Announce",
|
|
64005
|
+
"Undo",
|
|
64006
|
+
"Block",
|
|
64007
|
+
"Add",
|
|
64008
|
+
"Remove",
|
|
64009
|
+
"Accept",
|
|
64010
|
+
"Reject",
|
|
64011
|
+
"Move"
|
|
64012
|
+
]);
|
|
64013
|
+
var arr = (v) => v === void 0 || v === null ? [] : Array.isArray(v) ? v : [v];
|
|
64014
|
+
var idOf = (v) => typeof v === "string" ? v : v?.id || null;
|
|
64015
|
+
function readBody(req) {
|
|
64016
|
+
return new Promise((resolve2, reject) => {
|
|
64017
|
+
let size = 0;
|
|
64018
|
+
const chunks = [];
|
|
64019
|
+
req.on("data", (c) => {
|
|
64020
|
+
size += c.length;
|
|
64021
|
+
if (size > MAX_BODY) {
|
|
64022
|
+
reject(new Error("body too large"));
|
|
64023
|
+
req.destroy();
|
|
64024
|
+
return;
|
|
64025
|
+
}
|
|
64026
|
+
chunks.push(c);
|
|
64027
|
+
});
|
|
64028
|
+
req.on("end", () => resolve2(Buffer.concat(chunks).toString("utf8")));
|
|
64029
|
+
req.on("error", reject);
|
|
64030
|
+
});
|
|
64031
|
+
}
|
|
64032
|
+
var C2S = class {
|
|
64033
|
+
constructor({ agent: agent2, log: log2 = console.log, auth }) {
|
|
64034
|
+
this.agent = agent2;
|
|
64035
|
+
this.log = log2;
|
|
64036
|
+
this.auth = auth;
|
|
64037
|
+
}
|
|
64038
|
+
get store() {
|
|
64039
|
+
return this.agent.store;
|
|
64040
|
+
}
|
|
64041
|
+
get urls() {
|
|
64042
|
+
return this.agent.publisher?.urls;
|
|
64043
|
+
}
|
|
64044
|
+
send(res, status2, obj, headers = {}) {
|
|
64045
|
+
res.writeHead(status2, {
|
|
64046
|
+
"content-type": "application/json; charset=utf-8",
|
|
64047
|
+
"cache-control": "no-store",
|
|
64048
|
+
...headers
|
|
64049
|
+
});
|
|
64050
|
+
res.end(JSON.stringify(obj));
|
|
64051
|
+
return true;
|
|
64052
|
+
}
|
|
64053
|
+
byIri(iri) {
|
|
64054
|
+
return iri ? this.store.getStatuses().find((s) => s.noteId === iri) : null;
|
|
64055
|
+
}
|
|
64056
|
+
/** The months the archive holds, newest first. One container listing. */
|
|
64057
|
+
async archiveMonths() {
|
|
64058
|
+
const archive = this.agent.intake?.archive;
|
|
64059
|
+
if (!archive) return [];
|
|
64060
|
+
const { names } = await archive.list("");
|
|
64061
|
+
return (names || []).map((n) => n.replace(/\/$/u, "")).filter((n) => /^\d{4}-\d{2}$/u.test(n)).sort().reverse();
|
|
64062
|
+
}
|
|
64063
|
+
/**
|
|
64064
|
+
* The owner's own inbox, §5.2. Paged by month because that is how the
|
|
64065
|
+
* archive is stored, so a page costs one listing and a read per item and no
|
|
64066
|
+
* page is dearer for another month being large.
|
|
64067
|
+
*/
|
|
64068
|
+
async sendInbox(res, url) {
|
|
64069
|
+
const id = `${this.urls.base}ap/inbox`;
|
|
64070
|
+
const page2 = url?.searchParams?.get("page") || null;
|
|
64071
|
+
let months;
|
|
64072
|
+
try {
|
|
64073
|
+
months = await this.archiveMonths();
|
|
64074
|
+
} catch (e) {
|
|
64075
|
+
return this.send(res, 502, { error: `the archive could not be read: ${e.message}` });
|
|
64076
|
+
}
|
|
64077
|
+
if (!page2) {
|
|
64078
|
+
if (!months.length && this.store.getConfig()?.archiveInbox === false) {
|
|
64079
|
+
this.log("inbox read: nothing to show \u2014 this identity does not keep what it receives");
|
|
64080
|
+
}
|
|
64081
|
+
return this.send(res, 200, {
|
|
64082
|
+
"@context": AS_CTX,
|
|
64083
|
+
id,
|
|
64084
|
+
type: "OrderedCollection",
|
|
64085
|
+
...months.length ? { first: `${id}?page=${months[0]}` } : { orderedItems: [] }
|
|
64086
|
+
});
|
|
64087
|
+
}
|
|
64088
|
+
if (!/^\d{4}-\d{2}$/u.test(page2)) {
|
|
64089
|
+
return this.send(res, 400, { error: "page names a month, written 2026-09" });
|
|
64090
|
+
}
|
|
64091
|
+
const archive = this.agent.intake?.archive;
|
|
64092
|
+
let names = [];
|
|
64093
|
+
try {
|
|
64094
|
+
({ names } = await archive.list(`${page2}/`));
|
|
64095
|
+
} catch (e) {
|
|
64096
|
+
return this.send(res, 502, { error: `the archive could not be read: ${e.message}` });
|
|
64097
|
+
}
|
|
64098
|
+
const files = (names || []).filter((n) => n.endsWith(".json")).sort();
|
|
64099
|
+
if (files.length > MAX_INBOX_PAGE) {
|
|
64100
|
+
this.log(`inbox read: ${page2} holds ${files.length} items; serving the first ${MAX_INBOX_PAGE}`);
|
|
64101
|
+
}
|
|
64102
|
+
const kept = [];
|
|
64103
|
+
for (const file of files.slice(0, MAX_INBOX_PAGE)) {
|
|
64104
|
+
const read2 = await archive.read(`${page2}/${file}`, { accept: "*/*" });
|
|
64105
|
+
if (!read2?.ok || !read2.body) continue;
|
|
64106
|
+
try {
|
|
64107
|
+
const record = JSON.parse(read2.body);
|
|
64108
|
+
kept.push({ at: record.receivedAt || "", activity: JSON.parse(record.raw) });
|
|
64109
|
+
} catch {
|
|
64110
|
+
}
|
|
64111
|
+
}
|
|
64112
|
+
kept.sort((a, b) => String(b.at).localeCompare(String(a.at)));
|
|
64113
|
+
const older = months.filter((m) => m < page2)[0] || null;
|
|
64114
|
+
return this.send(res, 200, {
|
|
64115
|
+
"@context": AS_CTX,
|
|
64116
|
+
id: `${id}?page=${page2}`,
|
|
64117
|
+
type: "OrderedCollectionPage",
|
|
64118
|
+
partOf: id,
|
|
64119
|
+
...older ? { next: `${id}?page=${older}` } : {},
|
|
64120
|
+
orderedItems: kept.map((k) => k.activity)
|
|
64121
|
+
});
|
|
64122
|
+
}
|
|
64123
|
+
async handle(req, res, pathname, url) {
|
|
64124
|
+
if (pathname !== "/ap/outbox" && pathname !== "/ap/actor" && pathname !== "/ap/inbox") return false;
|
|
64125
|
+
if (req.method === "OPTIONS") {
|
|
64126
|
+
res.writeHead(204, pathname === "/ap/inbox" ? { allow: "GET, OPTIONS" } : { allow: "GET, POST, OPTIONS", "accept-post": "application/ld+json, application/activity+json" });
|
|
64127
|
+
res.end();
|
|
64128
|
+
return true;
|
|
64129
|
+
}
|
|
64130
|
+
if (!this.agent.configured() || !this.urls) {
|
|
64131
|
+
return this.send(res, 409, { error: "agent not configured" });
|
|
64132
|
+
}
|
|
64133
|
+
if (pathname === "/ap/inbox") {
|
|
64134
|
+
if (req.method !== "GET" && req.method !== "HEAD") {
|
|
64135
|
+
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" });
|
|
64136
|
+
}
|
|
64137
|
+
const reader = await this.auth(req, pathname);
|
|
64138
|
+
if (!reader.ok) return this.send(res, reader.status, { error: reader.error });
|
|
64139
|
+
return this.sendInbox(res, url);
|
|
64140
|
+
}
|
|
64141
|
+
if (req.method === "GET" || req.method === "HEAD") {
|
|
64142
|
+
const target = pathname === "/ap/actor" ? this.urls.actor : this.urls.outbox;
|
|
64143
|
+
res.writeHead(303, { location: target, "cache-control": "no-store" });
|
|
64144
|
+
res.end();
|
|
64145
|
+
return true;
|
|
64146
|
+
}
|
|
64147
|
+
if (pathname !== "/ap/outbox" || req.method !== "POST") {
|
|
64148
|
+
return this.send(res, 405, { error: "POST the outbox; GET redirects to the pod" });
|
|
64149
|
+
}
|
|
64150
|
+
const who = await this.auth(req, pathname);
|
|
64151
|
+
if (!who.ok) return this.send(res, who.status, { error: who.error });
|
|
64152
|
+
if (this.agent.viewer) {
|
|
64153
|
+
const took = await this.agent.requestTakeover?.();
|
|
64154
|
+
if (!took) return this.send(res, 503, { error: "another agent is active for this pod \u2014 takeover failed, try again" });
|
|
64155
|
+
}
|
|
64156
|
+
let activity;
|
|
64157
|
+
let raw = null;
|
|
64158
|
+
try {
|
|
64159
|
+
const body = await readBody(req);
|
|
64160
|
+
try {
|
|
64161
|
+
raw = JSON.parse(body);
|
|
64162
|
+
} catch {
|
|
64163
|
+
raw = null;
|
|
64164
|
+
}
|
|
64165
|
+
const read2 = await readLenient(body);
|
|
64166
|
+
activity = read2.view ?? read2.doc;
|
|
64167
|
+
} catch (e) {
|
|
64168
|
+
return this.send(res, 400, { error: `unreadable body: ${e.message}` });
|
|
64169
|
+
}
|
|
64170
|
+
if (!activity || typeof activity !== "object" || Array.isArray(activity) || !activity.type) {
|
|
64171
|
+
return this.send(res, 400, { error: "a typed ActivityStreams object is required" });
|
|
64172
|
+
}
|
|
64173
|
+
const slug = safeSlug(req.headers.slug) || null;
|
|
64174
|
+
const r = await this.dispatch(activity, { slug, raw });
|
|
64175
|
+
return this.send(res, r.status, r.body, r.headers);
|
|
64176
|
+
}
|
|
64177
|
+
// Addressing → the facade's four visibilities, inverting the table the
|
|
64178
|
+
// composer writes (wire.addressing). Nothing stated is a public post — what
|
|
64179
|
+
// every client means by a post with no audience chosen, and what a client
|
|
64180
|
+
// that never addresses (dokieli's annotations) needs.
|
|
64181
|
+
visibilityOf(activity, object) {
|
|
64182
|
+
const to = arr(activity.to ?? object?.to).map(idOf);
|
|
64183
|
+
const cc = arr(activity.cc ?? object?.cc).map(idOf);
|
|
64184
|
+
if (!to.length && !cc.length) return "public";
|
|
64185
|
+
if (to.includes(PUBLIC)) return "public";
|
|
64186
|
+
if (cc.includes(PUBLIC)) return "unlisted";
|
|
64187
|
+
if (to.includes(this.urls.followers)) return "private";
|
|
64188
|
+
return "direct";
|
|
64189
|
+
}
|
|
64190
|
+
async dispatch(activity, { slug = null, raw = null } = {}) {
|
|
64191
|
+
const reply = (status2, body, headers = {}) => ({ status: status2, body, headers });
|
|
64192
|
+
if (!activity || typeof activity !== "object" || Array.isArray(activity) || !activity.type) {
|
|
64193
|
+
return reply(400, { error: "a typed ActivityStreams object is required" });
|
|
64194
|
+
}
|
|
64195
|
+
if (!ACTIVITY_TYPES.has(activity.type)) {
|
|
64196
|
+
activity = { type: "Create", object: activity, to: activity.to, cc: activity.cc };
|
|
64197
|
+
}
|
|
64198
|
+
try {
|
|
64199
|
+
return await this._dispatch(activity, { slug, raw, reply });
|
|
64200
|
+
} catch (e) {
|
|
64201
|
+
this.log(`c2s ${activity?.type}: ${e.message}`);
|
|
64202
|
+
return reply(422, { error: e.message || String(e) });
|
|
64203
|
+
}
|
|
64204
|
+
}
|
|
64205
|
+
async _dispatch(activity, { slug, raw, reply }) {
|
|
64206
|
+
const agent2 = this.agent;
|
|
64207
|
+
const object = typeof activity.object === "object" && activity.object !== null ? activity.object : null;
|
|
64208
|
+
const objectId = idOf(activity.object);
|
|
64209
|
+
switch (activity.type) {
|
|
64210
|
+
case "Create": {
|
|
64211
|
+
const makes = object?.type || "Note";
|
|
64212
|
+
if (!object) return reply(422, { error: "a Create carries the object it creates" });
|
|
64213
|
+
const visibility = this.visibilityOf(activity, object);
|
|
64214
|
+
if (makes !== "Note" && makes !== "Question") {
|
|
64215
|
+
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;
|
|
64216
|
+
const made = await agent2.publisher.publishObject(asSent || object, { visibility, slug });
|
|
64217
|
+
return reply(201, { id: made.createId, object: made.id }, { location: made.createId });
|
|
64218
|
+
}
|
|
64219
|
+
const text = String(object.source?.content ?? object.content ?? "");
|
|
64220
|
+
if (!text.trim()) return reply(422, { error: "the note has no content" });
|
|
64221
|
+
if (makes === "Question") {
|
|
64222
|
+
const one = arr(object.oneOf);
|
|
64223
|
+
const many = arr(object.anyOf);
|
|
64224
|
+
const titles = (one.length ? one : many).map((c) => String(c?.name ?? "").trim()).filter(Boolean);
|
|
64225
|
+
try {
|
|
64226
|
+
const question = await agent2.publisher.publishQuestion(text, {
|
|
64227
|
+
options: titles,
|
|
64228
|
+
multiple: !one.length && many.length > 0,
|
|
64229
|
+
expiresAt: object.endTime || null,
|
|
64230
|
+
inReplyTo: idOf(object.inReplyTo) || void 0,
|
|
64231
|
+
visibility,
|
|
64232
|
+
spoilerText: object.summary || null
|
|
64233
|
+
});
|
|
64234
|
+
return reply(
|
|
64235
|
+
201,
|
|
64236
|
+
{ id: createActivityId(question.id), object: question.id },
|
|
64237
|
+
{ location: createActivityId(question.id) }
|
|
64238
|
+
);
|
|
64239
|
+
} catch (e) {
|
|
64240
|
+
return reply(422, { error: e.message });
|
|
64241
|
+
}
|
|
64242
|
+
}
|
|
64243
|
+
const attachments = arr(object.attachment).map((a) => ({
|
|
64244
|
+
url: a?.url,
|
|
64245
|
+
mediaType: a?.mediaType,
|
|
64246
|
+
...a?.name ? { description: a.name } : {}
|
|
64247
|
+
})).filter((a) => a.url);
|
|
64248
|
+
const note = await agent2.publisher.publishNote(text, {
|
|
64249
|
+
inReplyTo: idOf(object.inReplyTo) || void 0,
|
|
64250
|
+
attachments,
|
|
64251
|
+
visibility,
|
|
64252
|
+
spoilerText: object.summary || null,
|
|
64253
|
+
slug
|
|
64254
|
+
});
|
|
64255
|
+
return reply(
|
|
64256
|
+
201,
|
|
64257
|
+
{ id: createActivityId(note.id), object: note.id },
|
|
64258
|
+
{ location: createActivityId(note.id) }
|
|
64259
|
+
);
|
|
64260
|
+
}
|
|
64261
|
+
case "Update": {
|
|
64262
|
+
if (objectId === this.urls.actor) {
|
|
64263
|
+
return reply(422, { error: "edit the profile on the admin surface; actor updates are not taken here" });
|
|
64264
|
+
}
|
|
64265
|
+
const s = this.byIri(objectId);
|
|
64266
|
+
if (!s) return reply(404, { error: "no such note here" });
|
|
64267
|
+
if (s.actor !== this.urls.actor || s.kind !== "post") {
|
|
64268
|
+
return reply(403, { error: "not your note" });
|
|
64269
|
+
}
|
|
64270
|
+
const text = String(object?.source?.content ?? object?.content ?? "");
|
|
64271
|
+
if (!text.trim()) return reply(422, { error: "the edit has no content" });
|
|
64272
|
+
const attachments = object?.attachment !== void 0 ? arr(object.attachment).map((a) => ({
|
|
64273
|
+
url: a?.url,
|
|
64274
|
+
mediaType: a?.mediaType,
|
|
64275
|
+
...a?.name ? { description: a.name } : {}
|
|
64276
|
+
})).filter((a) => a.url) : null;
|
|
64277
|
+
await agent2.publisher.updateNote(s, {
|
|
64278
|
+
content: text,
|
|
64279
|
+
spoilerText: object?.summary || null,
|
|
64280
|
+
attachments
|
|
64281
|
+
});
|
|
64282
|
+
return reply(200, { ok: true, object: s.noteId });
|
|
64283
|
+
}
|
|
64284
|
+
case "Delete": {
|
|
64285
|
+
if (objectId === this.urls.actor) {
|
|
64286
|
+
return reply(422, { error: "retiring the actor is done on the admin surface, where it asks twice" });
|
|
64287
|
+
}
|
|
64288
|
+
const s = this.byIri(objectId);
|
|
64289
|
+
if (!s) return reply(404, { error: "no such note here" });
|
|
64290
|
+
if (s.actor !== this.urls.actor || s.kind !== "post") {
|
|
64291
|
+
return reply(403, { error: "not your note" });
|
|
64292
|
+
}
|
|
64293
|
+
const r = await deleteNote(agent2, s);
|
|
64294
|
+
if (!r.ok) return reply(502, { error: r.error, stillPublished: r.stillPublished });
|
|
64295
|
+
return reply(200, { ok: true });
|
|
64296
|
+
}
|
|
64297
|
+
case "Follow": {
|
|
64298
|
+
if (!objectId) return reply(400, { error: "whom? object must name an actor" });
|
|
64299
|
+
if (/^acct:|^@|^[^/@]+@[^/@]+$/.test(objectId) && !/^https?:/.test(objectId)) {
|
|
64300
|
+
const r = await followHandle(agent2, objectId.replace(/^acct:/, ""));
|
|
64301
|
+
const rec2 = this.store.getContacts().following.find((f) => f.actor === r.actor);
|
|
64302
|
+
return reply(
|
|
64303
|
+
201,
|
|
64304
|
+
{ id: rec2?.followActivity?.id, object: r.actor },
|
|
64305
|
+
rec2?.followActivity?.id ? { location: rec2.followActivity.id } : {}
|
|
64306
|
+
);
|
|
64307
|
+
}
|
|
64308
|
+
const doc = await followActor(agent2, objectId);
|
|
64309
|
+
const rec = this.store.getContacts().following.find((f) => f.actor === doc.id);
|
|
64310
|
+
return reply(
|
|
64311
|
+
201,
|
|
64312
|
+
{ id: rec?.followActivity?.id, object: doc.id },
|
|
64313
|
+
rec?.followActivity?.id ? { location: rec.followActivity.id } : {}
|
|
64314
|
+
);
|
|
64315
|
+
}
|
|
64316
|
+
case "Like": {
|
|
64317
|
+
const s = this.byIri(objectId);
|
|
64318
|
+
if (!s) return reply(422, { error: "that note is not held here \u2014 like what the timeline holds" });
|
|
64319
|
+
const updated = await favourite(agent2, s);
|
|
64320
|
+
return reply(
|
|
64321
|
+
201,
|
|
64322
|
+
{ id: updated.likeActivity?.id, object: s.noteId },
|
|
64323
|
+
updated.likeActivity?.id ? { location: updated.likeActivity.id } : {}
|
|
64324
|
+
);
|
|
64325
|
+
}
|
|
64326
|
+
case "Announce": {
|
|
64327
|
+
const s = this.byIri(objectId);
|
|
64328
|
+
if (!s) return reply(422, { error: "that note is not held here \u2014 boost what the timeline holds" });
|
|
64329
|
+
const updated = await reblog(agent2, s);
|
|
64330
|
+
return reply(
|
|
64331
|
+
201,
|
|
64332
|
+
{ id: updated.announceActivity?.id, object: s.noteId },
|
|
64333
|
+
updated.announceActivity?.id ? { location: updated.announceActivity.id } : {}
|
|
64334
|
+
);
|
|
64335
|
+
}
|
|
64336
|
+
case "Undo": {
|
|
64337
|
+
const inner = object;
|
|
64338
|
+
const innerId = idOf(activity.object);
|
|
64339
|
+
if (inner?.type === "Block") {
|
|
64340
|
+
const target = idOf(inner.object);
|
|
64341
|
+
if (!target) return reply(400, { error: "unblock whom?" });
|
|
64342
|
+
await unblockActor(agent2, target);
|
|
64343
|
+
return reply(200, { ok: true, object: target });
|
|
64344
|
+
}
|
|
64345
|
+
const statuses = this.store.getStatuses();
|
|
64346
|
+
let s = innerId ? statuses.find((x) => x.likeActivity?.id === innerId) : null;
|
|
64347
|
+
if (!s && inner?.type === "Like") s = this.byIri(idOf(inner.object));
|
|
64348
|
+
if (s?.favourited) {
|
|
64349
|
+
const updated = await unfavourite(agent2, s);
|
|
64350
|
+
return reply(200, { ok: true, object: updated.noteId });
|
|
64351
|
+
}
|
|
64352
|
+
s = innerId ? statuses.find((x) => x.announceActivity?.id === innerId) : null;
|
|
64353
|
+
if (!s && inner?.type === "Announce") s = this.byIri(idOf(inner.object));
|
|
64354
|
+
if (s?.reblogged) {
|
|
64355
|
+
const updated = await unreblog(agent2, s);
|
|
64356
|
+
return reply(200, { ok: true, object: updated.noteId });
|
|
64357
|
+
}
|
|
64358
|
+
const following = this.store.getContacts().following;
|
|
64359
|
+
const rec = following.find((f) => f.followActivity?.id === innerId) || (inner?.type === "Follow" ? following.find((f) => f.actor === idOf(inner.object)) : null);
|
|
64360
|
+
if (rec) {
|
|
64361
|
+
await unfollowActor(agent2, rec.actor);
|
|
64362
|
+
return reply(200, { ok: true, object: rec.actor });
|
|
64363
|
+
}
|
|
64364
|
+
return reply(422, { error: "nothing here matches what that Undo names" });
|
|
64365
|
+
}
|
|
64366
|
+
case "Block": {
|
|
64367
|
+
if (!objectId) return reply(400, { error: "block whom? object must name an actor" });
|
|
64368
|
+
await blockActor(agent2, objectId);
|
|
64369
|
+
return reply(200, { ok: true, object: objectId });
|
|
64370
|
+
}
|
|
64371
|
+
case "Add":
|
|
64372
|
+
case "Remove": {
|
|
64373
|
+
if (idOf(activity.target) !== this.urls.featured) {
|
|
64374
|
+
return reply(422, { error: "the featured collection is the one Add/Remove edits here" });
|
|
64375
|
+
}
|
|
64376
|
+
const s = this.byIri(objectId);
|
|
64377
|
+
if (!s) return reply(404, { error: "no such note here" });
|
|
64378
|
+
const updated = await pinStatus(agent2, s, activity.type === "Add");
|
|
64379
|
+
return reply(200, { ok: true, object: updated.noteId, pinned: !!updated.pinned });
|
|
64380
|
+
}
|
|
64381
|
+
case "Accept":
|
|
64382
|
+
case "Reject": {
|
|
64383
|
+
const requester = object?.actor ? idOf(object.actor) : objectId;
|
|
64384
|
+
if (!requester) return reply(400, { error: "whose request? object must name the Follow or its actor" });
|
|
64385
|
+
const r = activity.type === "Accept" ? await admitRequest(agent2, requester).catch((e) => ({ error: e.message })) : await refuseRequest(agent2, requester).catch((e) => ({ error: e.message }));
|
|
64386
|
+
if (r.error) return reply(404, { error: r.error });
|
|
64387
|
+
return reply(200, { ok: true, object: requester });
|
|
64388
|
+
}
|
|
64389
|
+
case "Move":
|
|
64390
|
+
return reply(422, { error: "moving the account is done on the admin surface, where it asks twice" });
|
|
64391
|
+
default:
|
|
64392
|
+
return reply(422, { error: `no handler for ${activity.type}` });
|
|
64393
|
+
}
|
|
64394
|
+
}
|
|
64395
|
+
};
|
|
64396
|
+
|
|
63309
64397
|
// lib/core/lease.mjs
|
|
63310
64398
|
init_node_crypto();
|
|
63311
64399
|
var TTL_MS = 3e5;
|
|
@@ -63618,7 +64706,7 @@ function pollParams(body) {
|
|
|
63618
64706
|
function htmlToText(html) {
|
|
63619
64707
|
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
64708
|
}
|
|
63621
|
-
function
|
|
64709
|
+
function readBody2(req) {
|
|
63622
64710
|
return new Promise((resolve2, reject) => {
|
|
63623
64711
|
let data = "";
|
|
63624
64712
|
req.on("data", (c) => {
|
|
@@ -63913,7 +65001,7 @@ function rateLimited(api) {
|
|
|
63913
65001
|
async function handle(api, ctx) {
|
|
63914
65002
|
const { req, res, pathname, url, send } = ctx;
|
|
63915
65003
|
if (pathname === "/api/v1/apps" && req.method === "POST") {
|
|
63916
|
-
const body = await
|
|
65004
|
+
const body = await readBody2(req);
|
|
63917
65005
|
const redirectUris = parseRedirects(body.redirect_uris);
|
|
63918
65006
|
const app = api.registerApp({
|
|
63919
65007
|
name: body.client_name,
|
|
@@ -63936,7 +65024,7 @@ async function handle(api, ctx) {
|
|
|
63936
65024
|
let params = url.searchParams;
|
|
63937
65025
|
let body = null;
|
|
63938
65026
|
if (req.method === "POST") {
|
|
63939
|
-
body = await
|
|
65027
|
+
body = await readBody2(req);
|
|
63940
65028
|
params = new URLSearchParams(body);
|
|
63941
65029
|
}
|
|
63942
65030
|
const redirect = params.get("redirect_uri") || "";
|
|
@@ -64011,7 +65099,7 @@ async function handle(api, ctx) {
|
|
|
64011
65099
|
return true;
|
|
64012
65100
|
}
|
|
64013
65101
|
if (pathname === "/oauth/token" && req.method === "POST") {
|
|
64014
|
-
const body = await
|
|
65102
|
+
const body = await readBody2(req);
|
|
64015
65103
|
const app = api.findApp(body.client_id || "");
|
|
64016
65104
|
if (!app && body.code_verifier && /^https:\/\//iu.test(String(body.client_id || ""))) {
|
|
64017
65105
|
const rec = api.consumeCode(body.code || "");
|
|
@@ -64091,7 +65179,7 @@ async function handle(api, ctx) {
|
|
|
64091
65179
|
});
|
|
64092
65180
|
}
|
|
64093
65181
|
if (pathname === "/oauth/revoke" && req.method === "POST") {
|
|
64094
|
-
const body = await
|
|
65182
|
+
const body = await readBody2(req).catch(() => ({}));
|
|
64095
65183
|
const gone = body?.token;
|
|
64096
65184
|
if (gone) {
|
|
64097
65185
|
const kept = api.tokenRecords().filter((r) => r.token !== gone);
|
|
@@ -64103,442 +65191,6 @@ async function handle(api, ctx) {
|
|
|
64103
65191
|
return false;
|
|
64104
65192
|
}
|
|
64105
65193
|
|
|
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
65194
|
// lib/client/masto/render.mjs
|
|
64543
65195
|
init_wire();
|
|
64544
65196
|
|
|
@@ -64809,11 +65461,11 @@ function account(api, actorUrl, { selfAcct } = {}) {
|
|
|
64809
65461
|
fields: (self2 ? api.store.getConfig()?.fields : cached.fields) || []
|
|
64810
65462
|
};
|
|
64811
65463
|
}
|
|
64812
|
-
function page(api, items, url, { limit = 20, max = 40, idOf = (s) => api.store.idFor(s.noteId) } = {}) {
|
|
65464
|
+
function page(api, items, url, { limit = 20, max = 40, idOf: idOf2 = (s) => api.store.idFor(s.noteId) } = {}) {
|
|
64813
65465
|
const n = Math.min(Number(url.searchParams.get("limit")) || limit, max);
|
|
64814
65466
|
const ids = /* @__PURE__ */ new Map();
|
|
64815
65467
|
const idAt = (s) => {
|
|
64816
|
-
if (!ids.has(s)) ids.set(s,
|
|
65468
|
+
if (!ids.has(s)) ids.set(s, idOf2(s));
|
|
64817
65469
|
return ids.get(s);
|
|
64818
65470
|
};
|
|
64819
65471
|
const cut = (param) => {
|
|
@@ -64837,8 +65489,8 @@ function page(api, items, url, { limit = 20, max = 40, idOf = (s) => api.store.i
|
|
|
64837
65489
|
for (const [k, v] of Object.entries(extra)) u.searchParams.set(k, v);
|
|
64838
65490
|
return u.href;
|
|
64839
65491
|
};
|
|
64840
|
-
const links = [`<${q({ max_id:
|
|
64841
|
-
if (pageItems2.length) links.push(`<${q({ min_id:
|
|
65492
|
+
const links = [`<${q({ max_id: idOf2(pageItems2[pageItems2.length - 1]) })}>; rel="next"`];
|
|
65493
|
+
if (pageItems2.length) links.push(`<${q({ min_id: idOf2(pageItems2[0]) })}>; rel="prev"`);
|
|
64842
65494
|
return { items: pageItems2, headers: { link: links.join(", ") } };
|
|
64843
65495
|
}
|
|
64844
65496
|
async function bskyReply(api, send, body, parent, visibility) {
|
|
@@ -65425,7 +66077,7 @@ async function handle3(api, ctx) {
|
|
|
65425
66077
|
const entry = api.store.getMedia()[mMedia[1]];
|
|
65426
66078
|
if (!entry) return send(404, { error: "Record not found" });
|
|
65427
66079
|
if (req.method === "PUT") {
|
|
65428
|
-
const body = await
|
|
66080
|
+
const body = await readBody2(req);
|
|
65429
66081
|
if (typeof body.description === "string") {
|
|
65430
66082
|
entry.description = body.description;
|
|
65431
66083
|
api.store.setMedia(mMedia[1], entry);
|
|
@@ -65459,7 +66111,7 @@ async function handle4(api, ctx) {
|
|
|
65459
66111
|
const ct = String(req.headers["content-type"] || "");
|
|
65460
66112
|
let form = {}, files = {};
|
|
65461
66113
|
if (ct.includes("multipart/form-data")) ({ fields: form, files } = await readMultipart(req));
|
|
65462
|
-
else form = await
|
|
66114
|
+
else form = await readBody2(req);
|
|
65463
66115
|
const cfg = { ...api.store.getConfig() };
|
|
65464
66116
|
const putImage = async (f) => {
|
|
65465
66117
|
const ext = (f.filename || "").includes(".") ? f.filename.split(".").pop().replace(/[^\w]/g, "") : "bin";
|
|
@@ -65521,7 +66173,7 @@ async function handle4(api, ctx) {
|
|
|
65521
66173
|
}
|
|
65522
66174
|
if (pathname === "/api/v1/markers") {
|
|
65523
66175
|
if (req.method === "POST") {
|
|
65524
|
-
const body = await
|
|
66176
|
+
const body = await readBody2(req);
|
|
65525
66177
|
const markers = api.store.read("masto-markers.json", {});
|
|
65526
66178
|
for (const [k, v] of Object.entries(body)) {
|
|
65527
66179
|
const lastId = v?.last_read_id || v;
|
|
@@ -65596,7 +66248,7 @@ async function handle4(api, ctx) {
|
|
|
65596
66248
|
return sub ? send(200, api.push.json(token, sub)) : send(404, { error: "Record not found" });
|
|
65597
66249
|
}
|
|
65598
66250
|
if (req.method === "POST") {
|
|
65599
|
-
const body = await
|
|
66251
|
+
const body = await readBody2(req);
|
|
65600
66252
|
const sub = api.push.set(token, {
|
|
65601
66253
|
endpoint: body.subscription?.endpoint,
|
|
65602
66254
|
keys: body.subscription?.keys,
|
|
@@ -65606,7 +66258,7 @@ async function handle4(api, ctx) {
|
|
|
65606
66258
|
return send(200, api.push.json(token, sub));
|
|
65607
66259
|
}
|
|
65608
66260
|
if (req.method === "PUT") {
|
|
65609
|
-
const body = await
|
|
66261
|
+
const body = await readBody2(req);
|
|
65610
66262
|
const sub = api.push.setAlerts(token, body.data?.alerts);
|
|
65611
66263
|
return sub ? send(200, api.push.json(token, sub)) : send(404, { error: "Record not found" });
|
|
65612
66264
|
}
|
|
@@ -65699,7 +66351,7 @@ async function handle5(api, ctx) {
|
|
|
65699
66351
|
return send(200, api.store.getLists().map(listJson));
|
|
65700
66352
|
}
|
|
65701
66353
|
if (pathname === "/api/v1/lists" && req.method === "POST") {
|
|
65702
|
-
const body = await
|
|
66354
|
+
const body = await readBody2(req);
|
|
65703
66355
|
const title = String(body.title || "").trim();
|
|
65704
66356
|
if (!title) return send(422, { error: "a title is required" });
|
|
65705
66357
|
const lists = api.store.getLists();
|
|
@@ -65718,7 +66370,7 @@ async function handle5(api, ctx) {
|
|
|
65718
66370
|
return send(200, {});
|
|
65719
66371
|
}
|
|
65720
66372
|
if (req.method === "PUT") {
|
|
65721
|
-
const body = await
|
|
66373
|
+
const body = await readBody2(req);
|
|
65722
66374
|
if (body.title) l.title = String(body.title);
|
|
65723
66375
|
if (body.replies_policy) l.repliesPolicy = body.replies_policy;
|
|
65724
66376
|
api.store.setLists(lists);
|
|
@@ -65731,7 +66383,7 @@ async function handle5(api, ctx) {
|
|
|
65731
66383
|
const l = lists.find((x) => x.id === mListAcc[1]);
|
|
65732
66384
|
if (!l) return send(404, { error: "Record not found" });
|
|
65733
66385
|
if (req.method === "GET") return send(200, (l.members || []).map((a) => api.account(a)));
|
|
65734
|
-
const body = await
|
|
66386
|
+
const body = await readBody2(req).catch(() => ({}));
|
|
65735
66387
|
const ids = [].concat(body.account_ids || body["account_ids[]"] || url.searchParams.getAll("account_ids[]")).filter(Boolean);
|
|
65736
66388
|
const actors = ids.map((id) => api.store.urlFor(id)).filter(Boolean);
|
|
65737
66389
|
if (req.method === "POST") l.members = [.../* @__PURE__ */ new Set([...l.members || [], ...actors])];
|
|
@@ -65763,7 +66415,7 @@ async function handle5(api, ctx) {
|
|
|
65763
66415
|
return send(200, api.store.getFilters().map(filterJson));
|
|
65764
66416
|
}
|
|
65765
66417
|
if (pathname === "/api/v2/filters" && req.method === "POST") {
|
|
65766
|
-
const body = await
|
|
66418
|
+
const body = await readBody2(req);
|
|
65767
66419
|
const title = String(body.title || "").trim();
|
|
65768
66420
|
if (!title) return send(422, { error: "a title is required" });
|
|
65769
66421
|
const filters = api.store.getFilters();
|
|
@@ -65789,7 +66441,7 @@ async function handle5(api, ctx) {
|
|
|
65789
66441
|
return send(200, {});
|
|
65790
66442
|
}
|
|
65791
66443
|
if (req.method === "PUT") {
|
|
65792
|
-
const body = await
|
|
66444
|
+
const body = await readBody2(req);
|
|
65793
66445
|
if (body.title) f.title = String(body.title);
|
|
65794
66446
|
if (body.context) f.context = [].concat(body.context);
|
|
65795
66447
|
if (body.filter_action) f.action = body.filter_action;
|
|
@@ -65846,7 +66498,7 @@ async function handle5(api, ctx) {
|
|
|
65846
66498
|
if (sinceId) cut(sinceId, true);
|
|
65847
66499
|
const page2 = q.get("min_id") && !q.get("since_id") ? items.slice(Math.max(0, items.length - limit)) : items.slice(0, limit);
|
|
65848
66500
|
if (page2.length) {
|
|
65849
|
-
const base = `${api.scheme || (req.socket?.encrypted ? "https" : "http")}://${req.headers.host}${pathname}`;
|
|
66501
|
+
const base = `${api.scheme || (req.socket?.encrypted ? "https" : "http")}://${req.headers.host}${api.mount || ""}${pathname}`;
|
|
65850
66502
|
const link = (params) => {
|
|
65851
66503
|
const u = new URL(base);
|
|
65852
66504
|
for (const [k, v] of q) if (k !== "max_id" && k !== "since_id" && k !== "min_id") u.searchParams.append(k, v);
|
|
@@ -65912,7 +66564,7 @@ init_node_crypto();
|
|
|
65912
66564
|
async function handle6(api, ctx) {
|
|
65913
66565
|
const { req, res, pathname, url, send } = ctx;
|
|
65914
66566
|
if (pathname === "/api/v1/statuses" && req.method === "POST") {
|
|
65915
|
-
const body = await
|
|
66567
|
+
const body = await readBody2(req);
|
|
65916
66568
|
if (!body.status) return send(422, { error: "status text required" });
|
|
65917
66569
|
const visibility = body.visibility || "public";
|
|
65918
66570
|
if (!["public", "unlisted", "private", "direct"].includes(visibility)) {
|
|
@@ -66030,7 +66682,7 @@ async function handle6(api, ctx) {
|
|
|
66030
66682
|
const s = noteUrl && api.store.getStatuses().find((x) => x.noteId === noteUrl);
|
|
66031
66683
|
if (!s) return send(404, { error: "Record not found" });
|
|
66032
66684
|
if (s.actor !== api.urls.actor) return send(403, { error: "not your status" });
|
|
66033
|
-
const body = await
|
|
66685
|
+
const body = await readBody2(req);
|
|
66034
66686
|
if (!body.status) return send(422, { error: "status text required" });
|
|
66035
66687
|
const mediaIds = [].concat(body.media_ids || body["media_ids[]"] || []).filter(Boolean);
|
|
66036
66688
|
const media = api.store.getMedia();
|
|
@@ -66148,7 +66800,7 @@ async function handle6(api, ctx) {
|
|
|
66148
66800
|
return send(200, {});
|
|
66149
66801
|
}
|
|
66150
66802
|
if (req.method === "PUT") {
|
|
66151
|
-
const body = await
|
|
66803
|
+
const body = await readBody2(req);
|
|
66152
66804
|
const at = Date.parse(body.scheduled_at || "");
|
|
66153
66805
|
if (!Number.isFinite(at)) return send(422, { error: "scheduled_at required" });
|
|
66154
66806
|
e.scheduledAt = new Date(at).toISOString();
|
|
@@ -66162,7 +66814,7 @@ async function handle6(api, ctx) {
|
|
|
66162
66814
|
const s = noteUrl && api.store.getStatuses().find((x) => x.noteId === noteUrl);
|
|
66163
66815
|
if (!s?.poll) return send(404, { error: "Record not found" });
|
|
66164
66816
|
if (mPoll[2] && req.method === "POST") {
|
|
66165
|
-
const body = await
|
|
66817
|
+
const body = await readBody2(req);
|
|
66166
66818
|
const choices = [].concat(body.choices || body["choices[]"] || []).map(Number).filter(Number.isInteger);
|
|
66167
66819
|
if (!choices.length) return send(422, { error: "choices required" });
|
|
66168
66820
|
const r = await votePoll(api.agent, s, choices);
|
|
@@ -66211,11 +66863,13 @@ var MastoApi = class _MastoApi {
|
|
|
66211
66863
|
allowed = null,
|
|
66212
66864
|
scheme = null,
|
|
66213
66865
|
embedded = false,
|
|
66866
|
+
mount = "",
|
|
66214
66867
|
streaming = true,
|
|
66215
66868
|
webPush = true,
|
|
66216
66869
|
scheduling = true
|
|
66217
66870
|
}) {
|
|
66218
66871
|
this.agent = agent2;
|
|
66872
|
+
this.mount = mount;
|
|
66219
66873
|
this.embedded = embedded;
|
|
66220
66874
|
this.log = log2;
|
|
66221
66875
|
this.allowed = allowed;
|
|
@@ -66907,7 +67561,7 @@ var PodTransport = class {
|
|
|
66907
67561
|
* says all of it. The parsed graph must mention the WebID before anything is
|
|
66908
67562
|
* written back — an empty or foreign body must never become the new profile.
|
|
66909
67563
|
*/
|
|
66910
|
-
async linkAccountInProfile({ actorUrl, accountName, kind = "person" }) {
|
|
67564
|
+
async linkAccountInProfile({ actorUrl, accountName, kind = "person", outbox = null }) {
|
|
66911
67565
|
const docUrl = this.webId.split("#")[0];
|
|
66912
67566
|
const res = await this.fetch(docUrl, { headers: { accept: "text/turtle" } });
|
|
66913
67567
|
if (res.status >= 400) throw new Error(`[${this.label}] GET ${docUrl} \u2192 ${res.status}`);
|
|
@@ -66923,10 +67577,16 @@ var PodTransport = class {
|
|
|
66923
67577
|
[me, FOAF("account"), actor],
|
|
66924
67578
|
[actor, RDF4("type"), FOAF("OnlineAccount")],
|
|
66925
67579
|
[actor, RDF4("type"), kind === "group" ? AS("Group") : AS("Person")],
|
|
66926
|
-
[actor, FOAF("accountName"), literal2(accountName)]
|
|
67580
|
+
[actor, FOAF("accountName"), literal2(accountName)],
|
|
67581
|
+
// Where a Solid client posts on this person's behalf (`as:outbox` on the
|
|
67582
|
+
// WebID is what dokieli reads); only where a door exists to take it.
|
|
67583
|
+
...outbox ? [[me, AS("outbox"), namedNode2(outbox)]] : []
|
|
66927
67584
|
];
|
|
66928
67585
|
const missing = wanted.filter(([s, p, o]) => !g.holds(s, p, o, doc));
|
|
66929
|
-
const stale =
|
|
67586
|
+
const stale = [
|
|
67587
|
+
...g.statementsMatching(actor, FOAF("accountName"), null, doc).filter((st2) => st2.object.value !== accountName),
|
|
67588
|
+
...outbox ? g.statementsMatching(me, AS("outbox"), null, doc).filter((st2) => st2.object.value !== outbox) : []
|
|
67589
|
+
];
|
|
66930
67590
|
if (!missing.length && !stale.length) return false;
|
|
66931
67591
|
const deletes = stale.map((st2) => [st2.subject, st2.predicate, st2.object]);
|
|
66932
67592
|
if (await this.patchDocument(docUrl, missing, deletes)) return true;
|
|
@@ -69664,6 +70324,7 @@ var BrowserAgent = class _BrowserAgent {
|
|
|
69664
70324
|
this.lease.startRenewal();
|
|
69665
70325
|
try {
|
|
69666
70326
|
await this.store.load({ force: true }).catch((e) => this.log(`re-reading state: ${e.message}`));
|
|
70327
|
+
await this.publisher.healStatuses().catch((e) => this.log(`healing the timeline index: ${e.message}`));
|
|
69667
70328
|
this.deliverer?.startQueue?.();
|
|
69668
70329
|
await this.publisher.publishProfile();
|
|
69669
70330
|
await this.store.flush?.();
|
|
@@ -69794,6 +70455,7 @@ var BrowserAgent = class _BrowserAgent {
|
|
|
69794
70455
|
this.publisher.atproto = this.atproto;
|
|
69795
70456
|
this.fediaccts = new BrowserFediAccounts({ store: this.store, actorId: this.urls.actor, log: this.log });
|
|
69796
70457
|
this.lease = new Lease({ url: this.urls.state + "lease.json", fetchImpl: podFetch, log: this.log });
|
|
70458
|
+
this.c2s = new C2S({ agent: this, log: this.log });
|
|
69797
70459
|
this.intake = new Intake({
|
|
69798
70460
|
config: this.store.getConfig(),
|
|
69799
70461
|
urls: this.urls,
|
|
@@ -69803,7 +70465,8 @@ var BrowserAgent = class _BrowserAgent {
|
|
|
69803
70465
|
publisher: this.publisher,
|
|
69804
70466
|
log: this.log,
|
|
69805
70467
|
push: true,
|
|
69806
|
-
lease: this.lease
|
|
70468
|
+
lease: this.lease,
|
|
70469
|
+
ownerPost: (a, o) => this.c2s.dispatch(a, o)
|
|
69807
70470
|
});
|
|
69808
70471
|
this.masto = new MastoApi({
|
|
69809
70472
|
agent: this,
|
|
@@ -69863,7 +70526,8 @@ var BrowserAgent = class _BrowserAgent {
|
|
|
69863
70526
|
atproto: this.atproto?.status() || null,
|
|
69864
70527
|
podRequests: this.remote?.stats?.() || null,
|
|
69865
70528
|
update: null,
|
|
69866
|
-
inboxCooldownFor: 0
|
|
70529
|
+
inboxCooldownFor: 0,
|
|
70530
|
+
stateSkipped: this.store?.lastSkipped || []
|
|
69867
70531
|
};
|
|
69868
70532
|
}
|
|
69869
70533
|
// Rotate the signing key (POST /rotate-key). Mint a fresh keypair, replace the
|