@rmdes/indiekit-endpoint-activitypub 2.0.8 → 2.0.10
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/assets/reader.css +34 -0
- package/index.js +7 -0
- package/lib/controllers/compose.js +26 -23
- package/lib/controllers/note-object.js +51 -0
- package/lib/inbox-listeners.js +3 -1
- package/package.json +1 -1
- package/views/activitypub-public-profile.njk +11 -0
package/assets/reader.css
CHANGED
|
@@ -993,6 +993,40 @@
|
|
|
993
993
|
color: var(--color-primary);
|
|
994
994
|
}
|
|
995
995
|
|
|
996
|
+
/* Override upstream .mention { display: grid } for bio content */
|
|
997
|
+
.ap-profile__bio .h-card {
|
|
998
|
+
display: inline;
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
.ap-profile__bio .h-card a,
|
|
1002
|
+
.ap-profile__bio a.u-url.mention {
|
|
1003
|
+
display: inline;
|
|
1004
|
+
white-space: nowrap;
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
.ap-profile__bio .h-card a span,
|
|
1008
|
+
.ap-profile__bio a.u-url.mention span {
|
|
1009
|
+
display: inline;
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
.ap-profile__bio a.mention.hashtag {
|
|
1013
|
+
display: inline;
|
|
1014
|
+
white-space: nowrap;
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
.ap-profile__bio a.mention.hashtag span {
|
|
1018
|
+
display: inline;
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
/* Mastodon invisible/ellipsis spans for long URLs in bios */
|
|
1022
|
+
.ap-profile__bio .invisible {
|
|
1023
|
+
display: none;
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
.ap-profile__bio .ellipsis::after {
|
|
1027
|
+
content: "…";
|
|
1028
|
+
}
|
|
1029
|
+
|
|
996
1030
|
.ap-profile__actions {
|
|
997
1031
|
display: flex;
|
|
998
1032
|
flex-wrap: wrap;
|
package/index.js
CHANGED
|
@@ -59,6 +59,7 @@ import {
|
|
|
59
59
|
} from "./lib/controllers/featured-tags.js";
|
|
60
60
|
import { resolveController } from "./lib/controllers/resolve.js";
|
|
61
61
|
import { publicProfileController } from "./lib/controllers/public-profile.js";
|
|
62
|
+
import { noteObjectController } from "./lib/controllers/note-object.js";
|
|
62
63
|
import {
|
|
63
64
|
refollowPauseController,
|
|
64
65
|
refollowResumeController,
|
|
@@ -161,6 +162,10 @@ export default class ActivityPubEndpoint {
|
|
|
161
162
|
return self._fedifyMiddleware(req, res, next);
|
|
162
163
|
});
|
|
163
164
|
|
|
165
|
+
// Serve stored quick reply Notes as JSON-LD so remote servers can
|
|
166
|
+
// dereference the Note ID during Create activity verification.
|
|
167
|
+
router.get("/quick-replies/:id", noteObjectController(self));
|
|
168
|
+
|
|
164
169
|
// HTML fallback for actor URL — serve a public profile page.
|
|
165
170
|
// Fedify only serves JSON-LD; browsers get 406 and fall through here.
|
|
166
171
|
router.get("/users/:identifier", publicProfileController(self));
|
|
@@ -835,6 +840,7 @@ export default class ActivityPubEndpoint {
|
|
|
835
840
|
Indiekit.addCollection("ap_muted");
|
|
836
841
|
Indiekit.addCollection("ap_blocked");
|
|
837
842
|
Indiekit.addCollection("ap_interactions");
|
|
843
|
+
Indiekit.addCollection("ap_notes");
|
|
838
844
|
|
|
839
845
|
// Store collection references (posts resolved lazily)
|
|
840
846
|
const indiekitCollections = Indiekit.collections;
|
|
@@ -853,6 +859,7 @@ export default class ActivityPubEndpoint {
|
|
|
853
859
|
ap_muted: indiekitCollections.get("ap_muted"),
|
|
854
860
|
ap_blocked: indiekitCollections.get("ap_blocked"),
|
|
855
861
|
ap_interactions: indiekitCollections.get("ap_interactions"),
|
|
862
|
+
ap_notes: indiekitCollections.get("ap_notes"),
|
|
856
863
|
get posts() {
|
|
857
864
|
return indiekitCollections.get("posts");
|
|
858
865
|
},
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import { Temporal } from "@js-temporal/polyfill";
|
|
6
6
|
import { getToken, validateToken } from "../csrf.js";
|
|
7
7
|
import { sanitizeContent } from "../timeline-store.js";
|
|
8
|
+
import { resolveAuthor } from "../resolve-author.js";
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* Fetch syndication targets from the Micropub config endpoint.
|
|
@@ -205,33 +206,20 @@ export function submitComposeController(mountPath, plugin) {
|
|
|
205
206
|
);
|
|
206
207
|
const followersUri = ctx.getFollowersUri(handle);
|
|
207
208
|
|
|
209
|
+
const documentLoader = await ctx.getDocumentLoader({
|
|
210
|
+
identifier: handle,
|
|
211
|
+
});
|
|
212
|
+
|
|
208
213
|
// Resolve the original author BEFORE constructing the Note,
|
|
209
214
|
// so we can include them in cc (required for threading/notification)
|
|
210
215
|
let recipient = null;
|
|
211
216
|
if (inReplyTo) {
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
});
|
|
219
|
-
|
|
220
|
-
if (
|
|
221
|
-
remoteObject &&
|
|
222
|
-
typeof remoteObject.getAttributedTo === "function"
|
|
223
|
-
) {
|
|
224
|
-
const author = await remoteObject.getAttributedTo({
|
|
225
|
-
documentLoader,
|
|
226
|
-
});
|
|
227
|
-
recipient = Array.isArray(author) ? author[0] : author;
|
|
228
|
-
}
|
|
229
|
-
} catch (error) {
|
|
230
|
-
console.warn(
|
|
231
|
-
`[ActivityPub] lookupObject failed for ${inReplyTo} (quick reply):`,
|
|
232
|
-
error.message,
|
|
233
|
-
);
|
|
234
|
-
}
|
|
217
|
+
recipient = await resolveAuthor(
|
|
218
|
+
inReplyTo,
|
|
219
|
+
ctx,
|
|
220
|
+
documentLoader,
|
|
221
|
+
application?.collections,
|
|
222
|
+
);
|
|
235
223
|
}
|
|
236
224
|
|
|
237
225
|
// Build cc list: always include followers, add original author for replies
|
|
@@ -258,6 +246,21 @@ export function submitComposeController(mountPath, plugin) {
|
|
|
258
246
|
ccs: ccList,
|
|
259
247
|
});
|
|
260
248
|
|
|
249
|
+
// Store the Note so remote servers can dereference its ID
|
|
250
|
+
const ap_notes = application?.collections?.get("ap_notes");
|
|
251
|
+
if (ap_notes) {
|
|
252
|
+
await ap_notes.insertOne({
|
|
253
|
+
_id: uuid,
|
|
254
|
+
noteId,
|
|
255
|
+
actorUrl: actorUri.href,
|
|
256
|
+
content: content.trim(),
|
|
257
|
+
inReplyTo: inReplyTo || null,
|
|
258
|
+
published: new Date().toISOString(),
|
|
259
|
+
to: ["https://www.w3.org/ns/activitystreams#Public"],
|
|
260
|
+
cc: ccList.map((u) => (u instanceof URL ? u.href : u.href || u)),
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
|
|
261
264
|
// Send to followers
|
|
262
265
|
await ctx.sendActivity({ identifier: handle }, "followers", create, {
|
|
263
266
|
preferSharedInbox: true,
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public route handler for serving quick reply Notes as ActivityPub JSON-LD.
|
|
3
|
+
*
|
|
4
|
+
* Remote servers dereference Note IDs to verify Create activities.
|
|
5
|
+
* Without this, quick replies are rejected by servers that validate
|
|
6
|
+
* the Note's ID URL (Mastodon with Authorized Fetch, Bonfire, etc.).
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* GET /quick-replies/:id — serve a stored Note as JSON-LD.
|
|
11
|
+
* @param {object} plugin - ActivityPub plugin instance
|
|
12
|
+
*/
|
|
13
|
+
export function noteObjectController(plugin) {
|
|
14
|
+
return async (request, response) => {
|
|
15
|
+
const { id } = request.params;
|
|
16
|
+
|
|
17
|
+
const { application } = request.app.locals;
|
|
18
|
+
const ap_notes = application?.collections?.get("ap_notes");
|
|
19
|
+
|
|
20
|
+
if (!ap_notes) {
|
|
21
|
+
return response.status(404).json({ error: "Not Found" });
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const note = await ap_notes.findOne({ _id: id });
|
|
25
|
+
|
|
26
|
+
if (!note) {
|
|
27
|
+
return response.status(404).json({ error: "Not Found" });
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const noteJson = {
|
|
31
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
32
|
+
id: note.noteId,
|
|
33
|
+
type: "Note",
|
|
34
|
+
attributedTo: note.actorUrl,
|
|
35
|
+
content: note.content,
|
|
36
|
+
published: note.published,
|
|
37
|
+
to: note.to,
|
|
38
|
+
cc: note.cc,
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
if (note.inReplyTo) {
|
|
42
|
+
noteJson.inReplyTo = note.inReplyTo;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
response
|
|
46
|
+
.status(200)
|
|
47
|
+
.set("Content-Type", "application/activity+json; charset=utf-8")
|
|
48
|
+
.set("Cache-Control", "public, max-age=3600")
|
|
49
|
+
.json(noteJson);
|
|
50
|
+
};
|
|
51
|
+
}
|
package/lib/inbox-listeners.js
CHANGED
|
@@ -340,7 +340,9 @@ export function registerInboxListeners(inboxChain, options) {
|
|
|
340
340
|
|
|
341
341
|
await addTimelineItem(collections, timelineItem);
|
|
342
342
|
} catch (error) {
|
|
343
|
-
|
|
343
|
+
// Remote object unreachable (timeout, Authorized Fetch, deleted, etc.) — skip
|
|
344
|
+
const cause = error?.cause?.code || error?.message || "unknown";
|
|
345
|
+
console.warn(`[AP] Skipped boost from ${actorUrl}: ${cause}`);
|
|
344
346
|
}
|
|
345
347
|
}
|
|
346
348
|
})
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rmdes/indiekit-endpoint-activitypub",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.10",
|
|
4
4
|
"description": "ActivityPub federation endpoint for Indiekit via Fedify. Adds full fediverse support: actor, inbox, outbox, followers, following, syndication, and Mastodon migration.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"indiekit",
|
|
@@ -177,6 +177,17 @@
|
|
|
177
177
|
.ap-pub__bio p { margin: 0 0 var(--space-s); }
|
|
178
178
|
.ap-pub__bio p:last-child { margin-bottom: 0; }
|
|
179
179
|
|
|
180
|
+
/* Mastodon mention/hashtag formatting */
|
|
181
|
+
.ap-pub__bio .h-card { display: inline; }
|
|
182
|
+
.ap-pub__bio .h-card a,
|
|
183
|
+
.ap-pub__bio a.u-url.mention { display: inline; white-space: nowrap; }
|
|
184
|
+
.ap-pub__bio .h-card a span,
|
|
185
|
+
.ap-pub__bio a.u-url.mention span { display: inline; }
|
|
186
|
+
.ap-pub__bio a.mention.hashtag { display: inline; white-space: nowrap; }
|
|
187
|
+
.ap-pub__bio a.mention.hashtag span { display: inline; }
|
|
188
|
+
.ap-pub__bio .invisible { display: none; }
|
|
189
|
+
.ap-pub__bio .ellipsis::after { content: "…"; }
|
|
190
|
+
|
|
180
191
|
/* ================================================================
|
|
181
192
|
Profile fields
|
|
182
193
|
================================================================ */
|