@rmdes/indiekit-endpoint-activitypub 3.7.1 → 3.7.3
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 +8 -2
- package/lib/mastodon/routes/statuses.js +71 -2
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -721,13 +721,19 @@ export default class ActivityPubEndpoint {
|
|
|
721
721
|
);
|
|
722
722
|
|
|
723
723
|
// Resolve the remote actor to get their inbox
|
|
724
|
-
//
|
|
724
|
+
// Try authenticated document loader first (for Authorized Fetch servers),
|
|
725
|
+
// fall back to unsigned if that fails (some servers reject signed GETs)
|
|
725
726
|
const documentLoader = await ctx.getDocumentLoader({
|
|
726
727
|
identifier: handle,
|
|
727
728
|
});
|
|
728
|
-
|
|
729
|
+
let remoteActor = await lookupWithSecurity(ctx, actorUrl, {
|
|
729
730
|
documentLoader,
|
|
730
731
|
});
|
|
732
|
+
if (!remoteActor) {
|
|
733
|
+
// Retry without authentication — some servers (e.g., tags.pub)
|
|
734
|
+
// may reject or mishandle signed GET requests
|
|
735
|
+
remoteActor = await lookupWithSecurity(ctx, actorUrl);
|
|
736
|
+
}
|
|
731
737
|
if (!remoteActor) {
|
|
732
738
|
return { ok: false, error: "Could not resolve remote actor" };
|
|
733
739
|
}
|
|
@@ -247,12 +247,17 @@ router.post("/api/v1/statuses", async (req, res, next) => {
|
|
|
247
247
|
});
|
|
248
248
|
};
|
|
249
249
|
|
|
250
|
+
// Process content: linkify URLs and extract @mentions
|
|
251
|
+
const rawContent = data.properties.content || { text: statusText || "", html: "" };
|
|
252
|
+
const processedContent = processStatusContent(rawContent, statusText || "");
|
|
253
|
+
const mentions = extractMentions(statusText || "");
|
|
254
|
+
|
|
250
255
|
const now = new Date().toISOString();
|
|
251
256
|
const timelineItem = await addTimelineItem(collections, {
|
|
252
257
|
uid: postUrl,
|
|
253
258
|
url: postUrl,
|
|
254
259
|
type: data.properties["post-type"] || "note",
|
|
255
|
-
content:
|
|
260
|
+
content: processedContent,
|
|
256
261
|
summary: spoilerText || "",
|
|
257
262
|
sensitive: sensitive === true || sensitive === "true",
|
|
258
263
|
visibility: visibility || "public",
|
|
@@ -274,7 +279,7 @@ router.post("/api/v1/statuses", async (req, res, next) => {
|
|
|
274
279
|
category: categories,
|
|
275
280
|
counts: { replies: 0, boosts: 0, likes: 0 },
|
|
276
281
|
linkPreviews: [],
|
|
277
|
-
mentions
|
|
282
|
+
mentions,
|
|
278
283
|
emojis: [],
|
|
279
284
|
});
|
|
280
285
|
|
|
@@ -636,4 +641,68 @@ async function loadItemInteractions(collections, item) {
|
|
|
636
641
|
return { favouritedIds, rebloggedIds, bookmarkedIds };
|
|
637
642
|
}
|
|
638
643
|
|
|
644
|
+
/**
|
|
645
|
+
* Process status content: linkify bare URLs and convert @mentions to links.
|
|
646
|
+
*
|
|
647
|
+
* Mastodon clients send plain text — the server is responsible for
|
|
648
|
+
* converting URLs and mentions into HTML links.
|
|
649
|
+
*
|
|
650
|
+
* @param {object} content - { text, html } from Micropub pipeline
|
|
651
|
+
* @param {string} rawText - Original status text from client
|
|
652
|
+
* @returns {object} { text, html } with linkified content
|
|
653
|
+
*/
|
|
654
|
+
function processStatusContent(content, rawText) {
|
|
655
|
+
let html = content.html || content.text || rawText || "";
|
|
656
|
+
|
|
657
|
+
// If the HTML is just plain text wrapped in <p>, process it
|
|
658
|
+
// Don't touch HTML that already has links (from Micropub rendering)
|
|
659
|
+
if (!html.includes("<a ")) {
|
|
660
|
+
// Linkify bare URLs (http/https)
|
|
661
|
+
html = html.replace(
|
|
662
|
+
/(https?:\/\/[^\s<>"')\]]+)/g,
|
|
663
|
+
'<a href="$1" rel="nofollow noopener noreferrer" target="_blank">$1</a>',
|
|
664
|
+
);
|
|
665
|
+
|
|
666
|
+
// Convert @user@domain mentions to profile links
|
|
667
|
+
html = html.replace(
|
|
668
|
+
/(?:^|\s)(@([a-zA-Z0-9_]+)@([a-zA-Z0-9.-]+\.[a-zA-Z]{2,}))/g,
|
|
669
|
+
(match, full, username, domain) =>
|
|
670
|
+
match.replace(
|
|
671
|
+
full,
|
|
672
|
+
`<span class="h-card"><a href="https://${domain}/@${username}" class="u-url mention" rel="nofollow noopener noreferrer" target="_blank">@${username}@${domain}</a></span>`,
|
|
673
|
+
),
|
|
674
|
+
);
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
return {
|
|
678
|
+
text: content.text || rawText || "",
|
|
679
|
+
html,
|
|
680
|
+
};
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
/**
|
|
684
|
+
* Extract @user@domain mentions from text into mention objects.
|
|
685
|
+
*
|
|
686
|
+
* @param {string} text - Status text
|
|
687
|
+
* @returns {Array<{name: string, url: string}>} Mention objects
|
|
688
|
+
*/
|
|
689
|
+
function extractMentions(text) {
|
|
690
|
+
if (!text) return [];
|
|
691
|
+
const mentionRegex = /@([a-zA-Z0-9_]+)@([a-zA-Z0-9.-]+\.[a-zA-Z]{2,})/g;
|
|
692
|
+
const mentions = [];
|
|
693
|
+
const seen = new Set();
|
|
694
|
+
let match;
|
|
695
|
+
while ((match = mentionRegex.exec(text)) !== null) {
|
|
696
|
+
const [, username, domain] = match;
|
|
697
|
+
const key = `${username}@${domain}`.toLowerCase();
|
|
698
|
+
if (seen.has(key)) continue;
|
|
699
|
+
seen.add(key);
|
|
700
|
+
mentions.push({
|
|
701
|
+
name: `@${username}@${domain}`,
|
|
702
|
+
url: `https://${domain}/@${username}`,
|
|
703
|
+
});
|
|
704
|
+
}
|
|
705
|
+
return mentions;
|
|
706
|
+
}
|
|
707
|
+
|
|
639
708
|
export default router;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rmdes/indiekit-endpoint-activitypub",
|
|
3
|
-
"version": "3.7.
|
|
3
|
+
"version": "3.7.3",
|
|
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",
|