fix: resolve and linkify @mentions in ap_timeline content

When the syndicator adds own posts to ap_timeline, it now:
1. Linkifies @user@domain patterns using WebFinger-resolved profile URLs
   (matching the federated AS2 content from jf2-to-as2.js)
2. Stores resolved mentions with actorUrl for proper Mastodon API
   serialization (deterministic account IDs via remoteActorId)

serializeStatus now parses mention handles into proper username/acct
fields with real account IDs instead of placeholder "0".
This commit is contained in:
Ricardo
2026-03-31 12:07:40 +02:00
parent 933ae98483
commit 32bf01e7bb
3 changed files with 41 additions and 8 deletions
+12 -7
View File
@@ -15,6 +15,7 @@
*/ */
import { serializeAccount } from "./account.js"; import { serializeAccount } from "./account.js";
import { sanitizeHtml } from "./sanitize.js"; import { sanitizeHtml } from "./sanitize.js";
import { remoteActorId } from "../helpers/id-mapping.js";
// Module-level defaults set once at startup via setLocalIdentity() // Module-level defaults set once at startup via setLocalIdentity()
let _localPublicationUrl = ""; let _localPublicationUrl = "";
@@ -178,13 +179,17 @@ export function serializeStatus(item, { baseUrl, favouritedIds, rebloggedIds, bo
url: `${baseUrl}/tags/${encodeURIComponent(tag)}`, url: `${baseUrl}/tags/${encodeURIComponent(tag)}`,
})); }));
// Mentions // Mentions — use actorUrl for deterministic ID, parse acct from handle
const mentions = (item.mentions || []).map((m) => ({ const mentions = (item.mentions || []).map((m) => {
id: "0", // We don't have stable IDs for mentioned accounts const handle = (m.name || "").replace(/^@/, "");
username: m.name || "", const parts = handle.split("@");
url: m.url || "", return {
acct: m.name || "", id: m.actorUrl ? remoteActorId(m.actorUrl) : "0",
})); username: parts[0] || handle,
url: m.url || m.actorUrl || "",
acct: handle,
};
});
// Custom emojis // Custom emojis
const emojis = (item.emojis || []).map((e) => ({ const emojis = (item.emojis || []).map((e) => ({
+28
View File
@@ -227,11 +227,39 @@ export function createSyndicator(plugin) {
const content = buildTimelineContent(properties); const content = buildTimelineContent(properties);
// Permalink is appended at read time by serializeStatus, not here. // Permalink is appended at read time by serializeStatus, not here.
// Linkify @mentions in content using resolved WebFinger data.
// This ensures the ap_timeline HTML has proper <a> links for
// mentions, matching what the federated AS2 activity contains.
if (resolvedMentions.length > 0 && content.html) {
const { default: jf2Mod } = await import("./jf2-to-as2.js");
// Import linkifyMentions — it's not exported, so inline the logic
for (const { handle, profileUrl, actorUrl } of resolvedMentions) {
const escaped = handle.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
const pattern = new RegExp(`(?<!["\\/\\w])@${escaped}(?![\\w])`, "gi");
const parts = handle.split("@");
const url = profileUrl || (actorUrl ? actorUrl : `https://${parts[1]}/@${parts[0]}`);
content.html = content.html.replace(
pattern,
`<a href="${url}" class="mention" rel="nofollow noopener" target="_blank">@${handle}</a>`,
);
}
}
// Store resolved mentions for Mastodon API serialization
const timelineMentions = resolvedMentions
.filter(m => m.actorUrl)
.map(m => ({
name: `@${m.handle}`,
url: m.profileUrl || m.actorUrl,
actorUrl: m.actorUrl,
}));
const timelineItem = { const timelineItem = {
uid: properties.url, uid: properties.url,
url: properties.url, url: properties.url,
type: mapPostType(properties["post-type"]), type: mapPostType(properties["post-type"]),
content, content,
mentions: timelineMentions,
author: { author: {
name: profile?.name || handle, name: profile?.name || handle,
url: profile?.url || plugin._publicationUrl, url: profile?.url || plugin._publicationUrl,
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@rmdes/indiekit-endpoint-activitypub", "name": "@rmdes/indiekit-endpoint-activitypub",
"version": "3.12.2", "version": "3.12.3",
"description": "ActivityPub federation endpoint for Indiekit via Fedify. Adds full fediverse support: actor, inbox, outbox, followers, following, syndication, and Mastodon migration.", "description": "ActivityPub federation endpoint for Indiekit via Fedify. Adds full fediverse support: actor, inbox, outbox, followers, following, syndication, and Mastodon migration.",
"keywords": [ "keywords": [
"indiekit", "indiekit",