@rmdes/indiekit-endpoint-activitypub 3.3.0 → 3.5.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/inbox-handlers.js
CHANGED
|
@@ -984,6 +984,24 @@ export async function handleUpdate(item, collections, ctx, handle) {
|
|
|
984
984
|
|
|
985
985
|
const existing = await collections.ap_followers.findOne({ actorUrl });
|
|
986
986
|
if (existing) {
|
|
987
|
+
let updatedAvatar = "";
|
|
988
|
+
try {
|
|
989
|
+
updatedAvatar = actorObj.icon
|
|
990
|
+
? (await actorObj.icon)?.url?.href || ""
|
|
991
|
+
: "";
|
|
992
|
+
} catch {
|
|
993
|
+
// Icon fetch failed
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
let updatedBanner = "";
|
|
997
|
+
try {
|
|
998
|
+
updatedBanner = actorObj.image
|
|
999
|
+
? (await actorObj.image)?.url?.href || ""
|
|
1000
|
+
: "";
|
|
1001
|
+
} catch {
|
|
1002
|
+
// Banner fetch failed
|
|
1003
|
+
}
|
|
1004
|
+
|
|
987
1005
|
await collections.ap_followers.updateOne(
|
|
988
1006
|
{ actorUrl },
|
|
989
1007
|
{
|
|
@@ -993,9 +1011,8 @@ export async function handleUpdate(item, collections, ctx, handle) {
|
|
|
993
1011
|
actorObj.preferredUsername?.toString() ||
|
|
994
1012
|
actorUrl,
|
|
995
1013
|
handle: actorObj.preferredUsername?.toString() || "",
|
|
996
|
-
avatar:
|
|
997
|
-
|
|
998
|
-
: "",
|
|
1014
|
+
avatar: updatedAvatar,
|
|
1015
|
+
banner: updatedBanner,
|
|
999
1016
|
updatedAt: new Date().toISOString(),
|
|
1000
1017
|
},
|
|
1001
1018
|
},
|
package/lib/inbox-listeners.js
CHANGED
|
@@ -66,13 +66,30 @@ export function registerInboxListeners(inboxChain, options) {
|
|
|
66
66
|
followerActor.preferredUsername?.toString() ||
|
|
67
67
|
followerUrl;
|
|
68
68
|
|
|
69
|
+
let followerAvatar = "";
|
|
70
|
+
try {
|
|
71
|
+
followerAvatar = followerActor.icon
|
|
72
|
+
? (await followerActor.icon)?.url?.href || ""
|
|
73
|
+
: "";
|
|
74
|
+
} catch {
|
|
75
|
+
// Icon fetch failed
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
let followerBanner = "";
|
|
79
|
+
try {
|
|
80
|
+
followerBanner = followerActor.image
|
|
81
|
+
? (await followerActor.image)?.url?.href || ""
|
|
82
|
+
: "";
|
|
83
|
+
} catch {
|
|
84
|
+
// Banner fetch failed
|
|
85
|
+
}
|
|
86
|
+
|
|
69
87
|
const followerData = {
|
|
70
88
|
actorUrl: followerUrl,
|
|
71
89
|
handle: followerActor.preferredUsername?.toString() || "",
|
|
72
90
|
name: followerName,
|
|
73
|
-
avatar:
|
|
74
|
-
|
|
75
|
-
: "",
|
|
91
|
+
avatar: followerAvatar,
|
|
92
|
+
banner: followerBanner,
|
|
76
93
|
inbox: followerActor.inbox?.id?.href || "",
|
|
77
94
|
sharedInbox: followerActor.endpoints?.sharedInbox?.href || "",
|
|
78
95
|
};
|
|
@@ -55,17 +55,26 @@ export function serializeNotification(notif, { baseUrl, statusMap, interactionSt
|
|
|
55
55
|
);
|
|
56
56
|
|
|
57
57
|
// Resolve the associated status (for favourite, reblog, mention types)
|
|
58
|
+
// For mention types, prefer the triggering post (notif.url) over the target post (notif.targetUrl)
|
|
59
|
+
// because targetUrl for replies points to the user's OWN post being replied to
|
|
58
60
|
let status = null;
|
|
59
|
-
if (
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
61
|
+
if (statusMap) {
|
|
62
|
+
const isMentionType = mastodonType === "mention";
|
|
63
|
+
const lookupUrl = isMentionType
|
|
64
|
+
? (notif.url || notif.targetUrl)
|
|
65
|
+
: (notif.targetUrl || notif.url);
|
|
66
|
+
|
|
67
|
+
if (lookupUrl) {
|
|
68
|
+
const timelineItem = statusMap.get(lookupUrl);
|
|
69
|
+
if (timelineItem) {
|
|
70
|
+
status = serializeStatus(timelineItem, {
|
|
71
|
+
baseUrl,
|
|
72
|
+
favouritedIds: interactionState?.favouritedIds || new Set(),
|
|
73
|
+
rebloggedIds: interactionState?.rebloggedIds || new Set(),
|
|
74
|
+
bookmarkedIds: interactionState?.bookmarkedIds || new Set(),
|
|
75
|
+
pinnedIds: new Set(),
|
|
76
|
+
});
|
|
77
|
+
}
|
|
69
78
|
}
|
|
70
79
|
}
|
|
71
80
|
|
|
@@ -82,7 +91,7 @@ export function serializeNotification(notif, { baseUrl, statusMap, interactionSt
|
|
|
82
91
|
visibility: notif.type === "dm" ? "direct" : "public",
|
|
83
92
|
language: null,
|
|
84
93
|
uri: notif.uid || "",
|
|
85
|
-
url: notif.targetUrl || notif.uid || "",
|
|
94
|
+
url: notif.url || notif.targetUrl || notif.uid || "",
|
|
86
95
|
replies_count: 0,
|
|
87
96
|
reblogs_count: 0,
|
|
88
97
|
favourites_count: 0,
|
|
@@ -110,7 +110,7 @@ router.get("/api/v1/accounts/lookup", async (req, res, next) => {
|
|
|
110
110
|
if (follower) {
|
|
111
111
|
return res.json(
|
|
112
112
|
serializeAccount(
|
|
113
|
-
{ name: follower.name, url: follower.actorUrl, photo: follower.avatar, handle: follower.handle },
|
|
113
|
+
{ name: follower.name, url: follower.actorUrl, photo: follower.avatar, handle: follower.handle, bannerUrl: follower.banner || "" },
|
|
114
114
|
{ baseUrl },
|
|
115
115
|
),
|
|
116
116
|
);
|
|
@@ -277,7 +277,7 @@ router.get("/api/v1/accounts/:id/followers", async (req, res, next) => {
|
|
|
277
277
|
|
|
278
278
|
const accounts = followers.map((f) =>
|
|
279
279
|
serializeAccount(
|
|
280
|
-
{ name: f.name, url: f.actorUrl, photo: f.avatar, handle: f.handle },
|
|
280
|
+
{ name: f.name, url: f.actorUrl, photo: f.avatar, handle: f.handle, bannerUrl: f.banner || "" },
|
|
281
281
|
{ baseUrl },
|
|
282
282
|
),
|
|
283
283
|
);
|
|
@@ -310,7 +310,7 @@ router.get("/api/v1/accounts/:id/following", async (req, res, next) => {
|
|
|
310
310
|
|
|
311
311
|
const accounts = following.map((f) =>
|
|
312
312
|
serializeAccount(
|
|
313
|
-
{ name: f.name, url: f.actorUrl, photo: f.avatar, handle: f.handle },
|
|
313
|
+
{ name: f.name, url: f.actorUrl, photo: f.avatar, handle: f.handle, bannerUrl: f.banner || "" },
|
|
314
314
|
{ baseUrl },
|
|
315
315
|
),
|
|
316
316
|
);
|
|
@@ -712,23 +712,35 @@ async function resolveActorUrl(id, collections) {
|
|
|
712
712
|
* Returns { actor, actorUrl } or { actor: null, actorUrl: null }.
|
|
713
713
|
*/
|
|
714
714
|
async function resolveActorData(id, collections) {
|
|
715
|
-
// Check followers
|
|
715
|
+
// Check followers — pass through all stored fields for richer serialization
|
|
716
716
|
const followers = await collections.ap_followers.find({}).toArray();
|
|
717
717
|
for (const f of followers) {
|
|
718
718
|
if (remoteActorId(f.actorUrl) === id) {
|
|
719
719
|
return {
|
|
720
|
-
actor: {
|
|
720
|
+
actor: {
|
|
721
|
+
name: f.name,
|
|
722
|
+
url: f.actorUrl,
|
|
723
|
+
photo: f.avatar,
|
|
724
|
+
handle: f.handle,
|
|
725
|
+
bannerUrl: f.banner || "",
|
|
726
|
+
},
|
|
721
727
|
actorUrl: f.actorUrl,
|
|
722
728
|
};
|
|
723
729
|
}
|
|
724
730
|
}
|
|
725
731
|
|
|
726
|
-
// Check following
|
|
732
|
+
// Check following — pass through all stored fields
|
|
727
733
|
const following = await collections.ap_following.find({}).toArray();
|
|
728
734
|
for (const f of following) {
|
|
729
735
|
if (remoteActorId(f.actorUrl) === id) {
|
|
730
736
|
return {
|
|
731
|
-
actor: {
|
|
737
|
+
actor: {
|
|
738
|
+
name: f.name,
|
|
739
|
+
url: f.actorUrl,
|
|
740
|
+
photo: f.avatar,
|
|
741
|
+
handle: f.handle,
|
|
742
|
+
bannerUrl: f.banner || "",
|
|
743
|
+
},
|
|
732
744
|
actorUrl: f.actorUrl,
|
|
733
745
|
};
|
|
734
746
|
}
|
|
@@ -225,23 +225,25 @@ function resolveInternalTypes(mastodonTypes) {
|
|
|
225
225
|
async function batchFetchStatuses(collections, notifications) {
|
|
226
226
|
const statusMap = new Map();
|
|
227
227
|
|
|
228
|
-
|
|
228
|
+
// Collect both targetUrl (the post being acted on) and url (the triggering post)
|
|
229
|
+
// For mention/reply notifications, the url is the actual mention/reply post
|
|
230
|
+
const allUrls = [
|
|
229
231
|
...new Set(
|
|
230
232
|
notifications
|
|
231
|
-
.
|
|
233
|
+
.flatMap((n) => [n.targetUrl, n.url])
|
|
232
234
|
.filter(Boolean),
|
|
233
235
|
),
|
|
234
236
|
];
|
|
235
237
|
|
|
236
|
-
if (
|
|
238
|
+
if (allUrls.length === 0 || !collections.ap_timeline) {
|
|
237
239
|
return statusMap;
|
|
238
240
|
}
|
|
239
241
|
|
|
240
242
|
const items = await collections.ap_timeline
|
|
241
243
|
.find({
|
|
242
244
|
$or: [
|
|
243
|
-
{ uid: { $in:
|
|
244
|
-
{ url: { $in:
|
|
245
|
+
{ uid: { $in: allUrls } },
|
|
246
|
+
{ url: { $in: allUrls } },
|
|
245
247
|
],
|
|
246
248
|
})
|
|
247
249
|
.toArray();
|
|
@@ -214,9 +214,15 @@ router.post("/api/v1/statuses", async (req, res, next) => {
|
|
|
214
214
|
jf2["mp-language"] = language;
|
|
215
215
|
}
|
|
216
216
|
|
|
217
|
+
// Syndicate to AP only — posts from Mastodon clients belong to the fediverse.
|
|
218
|
+
// Never cross-post to Bluesky (conversations stay in their protocol).
|
|
219
|
+
// The publication URL is the AP syndicator's uid.
|
|
220
|
+
const publicationUrl = pluginOptions.publicationUrl || baseUrl;
|
|
221
|
+
jf2["mp-syndicate-to"] = [publicationUrl.replace(/\/$/, "") + "/"];
|
|
222
|
+
|
|
217
223
|
// Create post via Micropub pipeline (same functions the Micropub endpoint uses)
|
|
218
224
|
// postData.create() handles: normalization, post type detection, path rendering,
|
|
219
|
-
// mp-syndicate-to
|
|
225
|
+
// mp-syndicate-to validated against configured syndicators, MongoDB posts collection
|
|
220
226
|
const { postData } = await import("@indiekit/endpoint-micropub/lib/post-data.js");
|
|
221
227
|
const { postContent } = await import("@indiekit/endpoint-micropub/lib/post-content.js");
|
|
222
228
|
|
|
@@ -230,7 +236,6 @@ router.post("/api/v1/statuses", async (req, res, next) => {
|
|
|
230
236
|
// Add to ap_timeline so the post is visible in the Mastodon Client API
|
|
231
237
|
const profile = await collections.ap_profile.findOne({});
|
|
232
238
|
const handle = pluginOptions.handle || "user";
|
|
233
|
-
const publicationUrl = pluginOptions.publicationUrl || baseUrl;
|
|
234
239
|
const actorUrl = profile?.url || `${publicationUrl}/users/${handle}`;
|
|
235
240
|
|
|
236
241
|
const now = new Date().toISOString();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rmdes/indiekit-endpoint-activitypub",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.5.0",
|
|
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",
|