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 { 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) => ({
|
||||||
|
|||||||
@@ -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
@@ -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",
|
||||||
|
|||||||
Reference in New Issue
Block a user