@rmdes/indiekit-endpoint-activitypub 1.0.19 → 1.0.20
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/index.js +67 -8
- package/lib/jf2-to-as2.js +30 -4
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -261,10 +261,50 @@ export default class ActivityPubEndpoint {
|
|
|
261
261
|
|
|
262
262
|
try {
|
|
263
263
|
const actorUrl = self._getActorUrl();
|
|
264
|
+
const handle = self.options.actor.handle;
|
|
265
|
+
|
|
266
|
+
const ctx = self._federation.createContext(
|
|
267
|
+
new URL(self._publicationUrl),
|
|
268
|
+
{},
|
|
269
|
+
);
|
|
270
|
+
|
|
271
|
+
// For replies, resolve the original post author for proper
|
|
272
|
+
// addressing (CC) and direct inbox delivery
|
|
273
|
+
let replyToActor = null;
|
|
274
|
+
if (properties["in-reply-to"]) {
|
|
275
|
+
try {
|
|
276
|
+
const remoteObject = await ctx.lookupObject(
|
|
277
|
+
new URL(properties["in-reply-to"]),
|
|
278
|
+
);
|
|
279
|
+
if (remoteObject && typeof remoteObject.getAttributedTo === "function") {
|
|
280
|
+
const author = await remoteObject.getAttributedTo();
|
|
281
|
+
const authorActor = Array.isArray(author) ? author[0] : author;
|
|
282
|
+
if (authorActor?.id) {
|
|
283
|
+
replyToActor = {
|
|
284
|
+
url: authorActor.id.href,
|
|
285
|
+
handle: authorActor.preferredUsername || null,
|
|
286
|
+
recipient: authorActor,
|
|
287
|
+
};
|
|
288
|
+
console.info(
|
|
289
|
+
`[ActivityPub] Reply to ${properties["in-reply-to"]} — resolved author: ${replyToActor.url}`,
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
} catch (error) {
|
|
294
|
+
console.warn(
|
|
295
|
+
`[ActivityPub] Could not resolve reply-to author for ${properties["in-reply-to"]}: ${error.message}`,
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
264
300
|
const activity = jf2ToAS2Activity(
|
|
265
301
|
properties,
|
|
266
302
|
actorUrl,
|
|
267
303
|
self._publicationUrl,
|
|
304
|
+
{
|
|
305
|
+
replyToActorUrl: replyToActor?.url,
|
|
306
|
+
replyToActorHandle: replyToActor?.handle,
|
|
307
|
+
},
|
|
268
308
|
);
|
|
269
309
|
|
|
270
310
|
if (!activity) {
|
|
@@ -278,11 +318,6 @@ export default class ActivityPubEndpoint {
|
|
|
278
318
|
return undefined;
|
|
279
319
|
}
|
|
280
320
|
|
|
281
|
-
const ctx = self._federation.createContext(
|
|
282
|
-
new URL(self._publicationUrl),
|
|
283
|
-
{},
|
|
284
|
-
);
|
|
285
|
-
|
|
286
321
|
// Count followers for logging
|
|
287
322
|
const followerCount =
|
|
288
323
|
await self._collections.ap_followers.countDocuments();
|
|
@@ -291,26 +326,50 @@ export default class ActivityPubEndpoint {
|
|
|
291
326
|
`[ActivityPub] Sending ${activity.constructor?.name || "activity"} for ${properties.url} to ${followerCount} followers`,
|
|
292
327
|
);
|
|
293
328
|
|
|
329
|
+
// Send to followers
|
|
294
330
|
await ctx.sendActivity(
|
|
295
|
-
{ identifier:
|
|
331
|
+
{ identifier: handle },
|
|
296
332
|
"followers",
|
|
297
333
|
activity,
|
|
298
334
|
);
|
|
299
335
|
|
|
336
|
+
// For replies, also deliver to the original post author's inbox
|
|
337
|
+
// so their server can thread the reply under the original post
|
|
338
|
+
if (replyToActor?.recipient) {
|
|
339
|
+
try {
|
|
340
|
+
await ctx.sendActivity(
|
|
341
|
+
{ identifier: handle },
|
|
342
|
+
replyToActor.recipient,
|
|
343
|
+
activity,
|
|
344
|
+
);
|
|
345
|
+
console.info(
|
|
346
|
+
`[ActivityPub] Reply delivered to author: ${replyToActor.url}`,
|
|
347
|
+
);
|
|
348
|
+
} catch (error) {
|
|
349
|
+
console.warn(
|
|
350
|
+
`[ActivityPub] Failed to deliver reply to ${replyToActor.url}: ${error.message}`,
|
|
351
|
+
);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
300
355
|
// Determine activity type name
|
|
301
356
|
const typeName =
|
|
302
357
|
activity.constructor?.name || "Create";
|
|
358
|
+
const replyNote = replyToActor
|
|
359
|
+
? ` (reply to ${replyToActor.url})`
|
|
360
|
+
: "";
|
|
303
361
|
|
|
304
362
|
await logActivity(self._collections.ap_activities, {
|
|
305
363
|
direction: "outbound",
|
|
306
364
|
type: typeName,
|
|
307
365
|
actorUrl: self._publicationUrl,
|
|
308
366
|
objectUrl: properties.url,
|
|
309
|
-
|
|
367
|
+
targetUrl: replyToActor?.url || undefined,
|
|
368
|
+
summary: `Sent ${typeName} for ${properties.url} to ${followerCount} followers${replyNote}`,
|
|
310
369
|
});
|
|
311
370
|
|
|
312
371
|
console.info(
|
|
313
|
-
`[ActivityPub] Syndication queued: ${typeName} for ${properties.url}`,
|
|
372
|
+
`[ActivityPub] Syndication queued: ${typeName} for ${properties.url}${replyNote}`,
|
|
314
373
|
);
|
|
315
374
|
|
|
316
375
|
return properties.url || undefined;
|
package/lib/jf2-to-as2.js
CHANGED
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
Hashtag,
|
|
16
16
|
Image,
|
|
17
17
|
Like,
|
|
18
|
+
Mention,
|
|
18
19
|
Note,
|
|
19
20
|
Video,
|
|
20
21
|
} from "@fedify/fedify";
|
|
@@ -126,9 +127,12 @@ export function jf2ToActivityStreams(properties, actorUrl, publicationUrl) {
|
|
|
126
127
|
* @param {object} properties - JF2 post properties
|
|
127
128
|
* @param {string} actorUrl - Actor URL (e.g. "https://example.com/activitypub/users/rick")
|
|
128
129
|
* @param {string} publicationUrl - Publication base URL with trailing slash
|
|
130
|
+
* @param {object} [options] - Optional settings
|
|
131
|
+
* @param {string} [options.replyToActorUrl] - Original post author's actor URL (for reply addressing)
|
|
132
|
+
* @param {string} [options.replyToActorHandle] - Original post author's handle (for Mention tag)
|
|
129
133
|
* @returns {import("@fedify/fedify").Activity | null}
|
|
130
134
|
*/
|
|
131
|
-
export function jf2ToAS2Activity(properties, actorUrl, publicationUrl) {
|
|
135
|
+
export function jf2ToAS2Activity(properties, actorUrl, publicationUrl, options = {}) {
|
|
132
136
|
const postType = properties["post-type"];
|
|
133
137
|
const actorUri = new URL(actorUrl);
|
|
134
138
|
|
|
@@ -154,13 +158,25 @@ export function jf2ToAS2Activity(properties, actorUrl, publicationUrl) {
|
|
|
154
158
|
const isArticle = postType === "article" && properties.name;
|
|
155
159
|
const postUrl = resolvePostUrl(properties.url, publicationUrl);
|
|
156
160
|
const followersUrl = `${actorUrl.replace(/\/$/, "")}/followers`;
|
|
161
|
+
const { replyToActorUrl, replyToActorHandle } = options;
|
|
157
162
|
|
|
158
163
|
const noteOptions = {
|
|
159
164
|
attributedTo: actorUri,
|
|
160
|
-
to: new URL("https://www.w3.org/ns/activitystreams#Public"),
|
|
161
|
-
cc: new URL(followersUrl),
|
|
162
165
|
};
|
|
163
166
|
|
|
167
|
+
// Addressing: for replies, include original author in CC so their server
|
|
168
|
+
// threads the reply and notifies them
|
|
169
|
+
if (replyToActorUrl && properties["in-reply-to"]) {
|
|
170
|
+
noteOptions.to = new URL("https://www.w3.org/ns/activitystreams#Public");
|
|
171
|
+
noteOptions.ccs = [
|
|
172
|
+
new URL(followersUrl),
|
|
173
|
+
new URL(replyToActorUrl),
|
|
174
|
+
];
|
|
175
|
+
} else {
|
|
176
|
+
noteOptions.to = new URL("https://www.w3.org/ns/activitystreams#Public");
|
|
177
|
+
noteOptions.cc = new URL(followersUrl);
|
|
178
|
+
}
|
|
179
|
+
|
|
164
180
|
if (postUrl) {
|
|
165
181
|
noteOptions.id = new URL(postUrl);
|
|
166
182
|
noteOptions.url = new URL(postUrl);
|
|
@@ -208,8 +224,18 @@ export function jf2ToAS2Activity(properties, actorUrl, publicationUrl) {
|
|
|
208
224
|
noteOptions.attachments = fedifyAttachments;
|
|
209
225
|
}
|
|
210
226
|
|
|
211
|
-
//
|
|
227
|
+
// Tags: hashtags + Mention for reply addressing
|
|
212
228
|
const fedifyTags = buildFedifyTags(properties, publicationUrl, postType);
|
|
229
|
+
|
|
230
|
+
if (replyToActorUrl) {
|
|
231
|
+
fedifyTags.push(
|
|
232
|
+
new Mention({
|
|
233
|
+
href: new URL(replyToActorUrl),
|
|
234
|
+
name: replyToActorHandle ? `@${replyToActorHandle}` : undefined,
|
|
235
|
+
}),
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
|
|
213
239
|
if (fedifyTags.length > 0) {
|
|
214
240
|
noteOptions.tags = fedifyTags;
|
|
215
241
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rmdes/indiekit-endpoint-activitypub",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.20",
|
|
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",
|