@rmdes/indiekit-endpoint-activitypub 3.10.0 → 3.10.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -6
- package/lib/syndicator.js +119 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -110,12 +110,33 @@ ActivityPub federation endpoint for [Indiekit](https://getindiekit.com), built o
|
|
|
110
110
|
- Federation management page with moderation overview (blocked servers, blocked accounts, muted)
|
|
111
111
|
|
|
112
112
|
**Standards Compliance**
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
113
|
+
|
|
114
|
+
Core protocols and Fediverse Enhancement Proposals (FEPs) supported:
|
|
115
|
+
|
|
116
|
+
| Standard | Name | Status | Provider |
|
|
117
|
+
|----------|------|--------|----------|
|
|
118
|
+
| [ActivityPub](https://www.w3.org/TR/activitypub/) | W3C ActivityPub | Full (server-to-server) | Fedify 2.1 |
|
|
119
|
+
| [ActivityStreams 2.0](https://www.w3.org/TR/activitystreams-core/) | W3C Activity Streams | Full | Fedify 2.1 |
|
|
120
|
+
| [HTTP Signatures](https://datatracker.ietf.org/doc/html/draft-cavage-http-signatures) | draft-cavage HTTP Signatures | Full | Fedify 2.1 |
|
|
121
|
+
| [RFC 9421](https://www.rfc-editor.org/rfc/rfc9421) | HTTP Message Signatures | Full (with Accept-Signature negotiation) | Fedify 2.1 |
|
|
122
|
+
| [WebFinger](https://www.rfc-editor.org/rfc/rfc7033) | RFC 7033 WebFinger | Full | Fedify 2.1 |
|
|
123
|
+
| [NodeInfo 2.1](https://nodeinfo.diaspora.software/) | Server metadata discovery | Full (enriched) | Plugin |
|
|
124
|
+
| [FEP-8b32](https://w3id.org/fep/8b32) | Object Integrity Proofs (Ed25519) | Full | Fedify 2.1 |
|
|
125
|
+
| [FEP-521a](https://w3id.org/fep/521a) | Multiple key pairs (Multikey) | Full | Fedify 2.1 |
|
|
126
|
+
| [FEP-fe34](https://w3id.org/fep/fe34) | Origin-based security model | Full | Fedify 2.1 + Plugin |
|
|
127
|
+
| [FEP-8fcf](https://w3id.org/fep/8fcf) | Followers collection synchronization | Outbound only | Fedify 2.1 |
|
|
128
|
+
| [FEP-5feb](https://w3id.org/fep/5feb) | Search indexing consent | Full (`indexable`, `discoverable`) | Plugin |
|
|
129
|
+
| [FEP-f1d5](https://w3id.org/fep/f1d5) | Enhanced NodeInfo 2.1 | Full (metadata, staff accounts) | Plugin |
|
|
130
|
+
| [FEP-4f05](https://w3id.org/fep/4f05) | Soft delete with Tombstone | Full (410 + Tombstone JSON-LD) | Plugin |
|
|
131
|
+
| [FEP-3b86](https://w3id.org/fep/3b86) | Activity Intents | Full (Follow, Create, Like, Announce) | Plugin |
|
|
132
|
+
| [FEP-044f](https://w3id.org/fep/044f) | Quote posts | Full (Mastodon, Misskey, Fedibird formats) | Fedify 2.1 + Plugin |
|
|
133
|
+
| [FEP-c0e0](https://w3id.org/fep/c0e0) | Emoji reactions (EmojiReact) | Vocab support (no UI) | Fedify 2.1 |
|
|
134
|
+
| [FEP-5711](https://w3id.org/fep/5711) | Conversation threads | Vocab support | Fedify 2.1 |
|
|
135
|
+
| [Linked Data Signatures](https://w3c-dvcg.github.io/ld-signatures/) | RsaSignature2017 (legacy) | Full (outbound signing) | Fedify 2.1 |
|
|
136
|
+
|
|
137
|
+
**Status key:** *Full* = complete implementation, *Outbound only* = sending side only, *Vocab support* = types available but no dedicated UI/logic.
|
|
138
|
+
|
|
139
|
+
**Provider key:** *Fedify 2.1* = handled by the Fedify framework, *Plugin* = implemented in this plugin, *Fedify 2.1 + Plugin* = framework provides primitives, plugin wires them together.
|
|
119
140
|
|
|
120
141
|
## Requirements
|
|
121
142
|
|
package/lib/syndicator.js
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
} from "./jf2-to-as2.js";
|
|
9
9
|
import { lookupWithSecurity } from "./lookup-helpers.js";
|
|
10
10
|
import { logActivity } from "./activity-log.js";
|
|
11
|
+
import { addTimelineItem } from "./storage/timeline.js";
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* Create the ActivityPub syndicator object.
|
|
@@ -219,6 +220,54 @@ export function createSyndicator(plugin) {
|
|
|
219
220
|
`[ActivityPub] Syndication queued: ${typeName} for ${properties.url}${replyNote}`,
|
|
220
221
|
);
|
|
221
222
|
|
|
223
|
+
// Add own post to ap_timeline so it appears in Mastodon Client API
|
|
224
|
+
// timelines (Phanpy/Moshidon). Uses $setOnInsert — idempotent.
|
|
225
|
+
try {
|
|
226
|
+
const profile = await plugin._collections.ap_profile?.findOne({});
|
|
227
|
+
const content = buildTimelineContent(properties);
|
|
228
|
+
const timelineItem = {
|
|
229
|
+
uid: properties.url,
|
|
230
|
+
url: properties.url,
|
|
231
|
+
type: mapPostType(properties["post-type"]),
|
|
232
|
+
content,
|
|
233
|
+
author: {
|
|
234
|
+
name: profile?.name || handle,
|
|
235
|
+
url: profile?.url || plugin._publicationUrl,
|
|
236
|
+
photo: profile?.icon || "",
|
|
237
|
+
handle: `@${handle}`,
|
|
238
|
+
emojis: [],
|
|
239
|
+
bot: false,
|
|
240
|
+
},
|
|
241
|
+
published: properties.published || new Date().toISOString(),
|
|
242
|
+
createdAt: new Date().toISOString(),
|
|
243
|
+
visibility: properties.visibility || "public",
|
|
244
|
+
sensitive: properties.sensitive === "true",
|
|
245
|
+
category: Array.isArray(properties.category)
|
|
246
|
+
? properties.category
|
|
247
|
+
: properties.category ? [properties.category] : [],
|
|
248
|
+
photo: normalizeMedia(properties.photo, plugin._publicationUrl),
|
|
249
|
+
video: normalizeMedia(properties.video, plugin._publicationUrl),
|
|
250
|
+
audio: normalizeMedia(properties.audio, plugin._publicationUrl),
|
|
251
|
+
counts: { replies: 0, boosts: 0, likes: 0 },
|
|
252
|
+
};
|
|
253
|
+
if (properties.name) timelineItem.name = properties.name;
|
|
254
|
+
if (properties.summary) timelineItem.summary = properties.summary;
|
|
255
|
+
if (properties["content-warning"]) {
|
|
256
|
+
timelineItem.summary = properties["content-warning"];
|
|
257
|
+
timelineItem.sensitive = true;
|
|
258
|
+
}
|
|
259
|
+
if (properties["in-reply-to"]) {
|
|
260
|
+
timelineItem.inReplyTo = Array.isArray(properties["in-reply-to"])
|
|
261
|
+
? properties["in-reply-to"][0]
|
|
262
|
+
: properties["in-reply-to"];
|
|
263
|
+
}
|
|
264
|
+
await addTimelineItem(plugin._collections, timelineItem);
|
|
265
|
+
} catch (tlError) {
|
|
266
|
+
console.warn(
|
|
267
|
+
`[ActivityPub] Failed to add own post to timeline: ${tlError.message}`,
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
|
|
222
271
|
return properties.url || undefined;
|
|
223
272
|
} catch (error) {
|
|
224
273
|
console.error("[ActivityPub] Syndication failed:", error.message);
|
|
@@ -237,3 +286,73 @@ export function createSyndicator(plugin) {
|
|
|
237
286
|
update: async (properties) => plugin.update(properties),
|
|
238
287
|
};
|
|
239
288
|
}
|
|
289
|
+
|
|
290
|
+
// ─── Timeline helpers ───────────────────────────────────────────────────────
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Build content from JF2 properties. Synthesizes content for interaction
|
|
294
|
+
* types (likes, bookmarks, reposts) that have no body text.
|
|
295
|
+
*/
|
|
296
|
+
function buildTimelineContent(properties) {
|
|
297
|
+
const content = properties.content;
|
|
298
|
+
if (content) {
|
|
299
|
+
if (typeof content === "string") return { text: content, html: `<p>${content}</p>` };
|
|
300
|
+
if (content.text || content.html) {
|
|
301
|
+
return {
|
|
302
|
+
text: content.text || content.value || "",
|
|
303
|
+
html: content.html || content.text || content.value || "",
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Synthesize for interaction types
|
|
309
|
+
const esc = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
310
|
+
const likeOf = properties["like-of"];
|
|
311
|
+
if (likeOf) {
|
|
312
|
+
return {
|
|
313
|
+
text: `Liked: ${likeOf}`,
|
|
314
|
+
html: `<p>Liked: <a href="${esc(likeOf)}">${esc(likeOf)}</a></p>`,
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
const bookmarkOf = properties["bookmark-of"];
|
|
318
|
+
if (bookmarkOf) {
|
|
319
|
+
const label = properties.name || bookmarkOf;
|
|
320
|
+
return {
|
|
321
|
+
text: `Bookmarked: ${label}`,
|
|
322
|
+
html: `<p>Bookmarked: <a href="${esc(bookmarkOf)}">${esc(label)}</a></p>`,
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
const repostOf = properties["repost-of"];
|
|
326
|
+
if (repostOf) {
|
|
327
|
+
const label = properties.name || repostOf;
|
|
328
|
+
return {
|
|
329
|
+
text: `Reposted: ${label}`,
|
|
330
|
+
html: `<p>Reposted: <a href="${esc(repostOf)}">${esc(label)}</a></p>`,
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
if (properties.name) {
|
|
334
|
+
return { text: properties.name, html: `<p>${esc(properties.name)}</p>` };
|
|
335
|
+
}
|
|
336
|
+
return { text: "", html: "" };
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function mapPostType(postType) {
|
|
340
|
+
if (postType === "article") return "article";
|
|
341
|
+
if (postType === "repost") return "boost";
|
|
342
|
+
return "note";
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function normalizeMedia(value, siteUrl) {
|
|
346
|
+
if (!value) return [];
|
|
347
|
+
const base = siteUrl?.replace(/\/$/, "") || "";
|
|
348
|
+
const arr = Array.isArray(value) ? value : [value];
|
|
349
|
+
return arr.map((item) => {
|
|
350
|
+
if (typeof item === "string") {
|
|
351
|
+
return item.startsWith("http") ? item : `${base}/${item.replace(/^\//, "")}`;
|
|
352
|
+
}
|
|
353
|
+
if (item?.url && !item.url.startsWith("http")) {
|
|
354
|
+
return { ...item, url: `${base}/${item.url.replace(/^\//, "")}` };
|
|
355
|
+
}
|
|
356
|
+
return item;
|
|
357
|
+
}).filter(Boolean);
|
|
358
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rmdes/indiekit-endpoint-activitypub",
|
|
3
|
-
"version": "3.10.
|
|
3
|
+
"version": "3.10.2",
|
|
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",
|