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:
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
import { serializeAccount } from "./account.js";
|
||||
import { sanitizeHtml } from "./sanitize.js";
|
||||
import { remoteActorId } from "../helpers/id-mapping.js";
|
||||
|
||||
// Module-level defaults set once at startup via setLocalIdentity()
|
||||
let _localPublicationUrl = "";
|
||||
@@ -178,13 +179,17 @@ export function serializeStatus(item, { baseUrl, favouritedIds, rebloggedIds, bo
|
||||
url: `${baseUrl}/tags/${encodeURIComponent(tag)}`,
|
||||
}));
|
||||
|
||||
// Mentions
|
||||
const mentions = (item.mentions || []).map((m) => ({
|
||||
id: "0", // We don't have stable IDs for mentioned accounts
|
||||
username: m.name || "",
|
||||
url: m.url || "",
|
||||
acct: m.name || "",
|
||||
}));
|
||||
// Mentions — use actorUrl for deterministic ID, parse acct from handle
|
||||
const mentions = (item.mentions || []).map((m) => {
|
||||
const handle = (m.name || "").replace(/^@/, "");
|
||||
const parts = handle.split("@");
|
||||
return {
|
||||
id: m.actorUrl ? remoteActorId(m.actorUrl) : "0",
|
||||
username: parts[0] || handle,
|
||||
url: m.url || m.actorUrl || "",
|
||||
acct: handle,
|
||||
};
|
||||
});
|
||||
|
||||
// Custom emojis
|
||||
const emojis = (item.emojis || []).map((e) => ({
|
||||
|
||||
@@ -227,11 +227,39 @@ export function createSyndicator(plugin) {
|
||||
const content = buildTimelineContent(properties);
|
||||
// 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 = {
|
||||
uid: properties.url,
|
||||
url: properties.url,
|
||||
type: mapPostType(properties["post-type"]),
|
||||
content,
|
||||
mentions: timelineMentions,
|
||||
author: {
|
||||
name: profile?.name || handle,
|
||||
url: profile?.url || plugin._publicationUrl,
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"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.",
|
||||
"keywords": [
|
||||
"indiekit",
|
||||
|
||||
Reference in New Issue
Block a user