diff --git a/packages/backend/src/mfm/to-html.ts b/packages/backend/src/mfm/to-html.ts index 54e93964b..818a37d83 100644 --- a/packages/backend/src/mfm/to-html.ts +++ b/packages/backend/src/mfm/to-html.ts @@ -8,6 +8,7 @@ import { resolveMentionWithFallback } from "@/remote/resolve-user.js"; export async function toHtml( nodes: mfm.MfmNode[] | null, mentionedRemoteUsers: IMentionedRemoteUsers = [], + objectHost: string | null ) { if (nodes == null) { return null; @@ -118,7 +119,7 @@ export async function toHtml( el.setAttribute("translate", "no"); const a = doc.createElement("a"); const { username, host, acct } = node.props; - a.href = await resolveMentionWithFallback(username, host, acct, mentionedRemoteUsers); + a.href = await resolveMentionWithFallback(username, host, objectHost, mentionedRemoteUsers); a.className = "u-url mention"; const span = doc.createElement("span"); span.textContent = username; diff --git a/packages/backend/src/remote/activitypub/misc/get-note-html.ts b/packages/backend/src/remote/activitypub/misc/get-note-html.ts index 046d60d1a..162a2dbd6 100644 --- a/packages/backend/src/remote/activitypub/misc/get-note-html.ts +++ b/packages/backend/src/remote/activitypub/misc/get-note-html.ts @@ -4,5 +4,5 @@ import { toHtml } from "../../../mfm/to-html.js"; export default async function (note: Note) { if (!note.text) return ""; - return toHtml(mfm.parse(note.text), JSON.parse(note.mentionedRemoteUsers)); + return toHtml(mfm.parse(note.text), JSON.parse(note.mentionedRemoteUsers), note.userHost); } diff --git a/packages/backend/src/remote/activitypub/renderer/person.ts b/packages/backend/src/remote/activitypub/renderer/person.ts index 5e010d733..f3beb789e 100644 --- a/packages/backend/src/remote/activitypub/renderer/person.ts +++ b/packages/backend/src/remote/activitypub/renderer/person.ts @@ -73,7 +73,7 @@ export async function renderPerson(user: ILocalUser) { preferredUsername: user.username, name: user.name, summary: profile.description - ? await toHtml(mfm.parse(profile.description)) + ? await toHtml(mfm.parse(profile.description), [], profile.userHost) : null, icon: avatar ? renderImage(avatar) : null, image: banner ? renderImage(banner) : null, diff --git a/packages/backend/src/remote/resolve-user.ts b/packages/backend/src/remote/resolve-user.ts index e97ea3ab6..576b89c91 100644 --- a/packages/backend/src/remote/resolve-user.ts +++ b/packages/backend/src/remote/resolve-user.ts @@ -177,11 +177,15 @@ export async function resolveUser( return user; } -export async function resolveMentionWithFallback(username: string, host: string | null, acct: string, cache: IMentionedRemoteUsers): Promise { - const fallback = `${config.url}/${acct}`; +export async function resolveMentionWithFallback(username: string, host: string | null, objectHost: string | null, cache: IMentionedRemoteUsers): Promise { + let fallback = `${config.url}/@${username}`; + if (host !== null) fallback += `@${host}`; + else if (objectHost !== null) fallback += `@${objectHost}`; + const cached = cache.find(r => r.username.toLowerCase() === username.toLowerCase() && r.host === host); if (cached) return cached.url ?? cached.uri; if (host === null || host === config.domain) return fallback; + try { const user = await resolveUser(username, host, false); const profile = await UserProfiles.findOneBy({ userId: user.id }); diff --git a/packages/backend/src/server/api/mastodon/converters/announcement.ts b/packages/backend/src/server/api/mastodon/converters/announcement.ts index a1adaa466..afadf071e 100644 --- a/packages/backend/src/server/api/mastodon/converters/announcement.ts +++ b/packages/backend/src/server/api/mastodon/converters/announcement.ts @@ -6,7 +6,7 @@ export class AnnouncementConverter { public static async encode(announcement: Announcement, isRead: boolean): Promise { return { id: announcement.id, - content: `

${await MfmHelpers.toHtml(mfm.parse(announcement.title), []) ?? 'Announcement'}

${await MfmHelpers.toHtml(mfm.parse(announcement.text), []) ?? ''}`, + content: `

${await MfmHelpers.toHtml(mfm.parse(announcement.title), [], null) ?? 'Announcement'}

${await MfmHelpers.toHtml(mfm.parse(announcement.text), [], null) ?? ''}`, starts_at: null, ends_at: null, published: true, diff --git a/packages/backend/src/server/api/mastodon/converters/note.ts b/packages/backend/src/server/api/mastodon/converters/note.ts index 45b109c4f..fc1cfd4be 100644 --- a/packages/backend/src/server/api/mastodon/converters/note.ts +++ b/packages/backend/src/server/api/mastodon/converters/note.ts @@ -117,7 +117,7 @@ export class NoteConverter { in_reply_to_id: note.replyId, in_reply_to_account_id: note.replyUserId, reblog: reblog.then(reblog => note.text === null ? reblog : null), - content: text.then(async text => text !== null ? MfmHelpers.toHtml(mfm.parse(text), JSON.parse(note.mentionedRemoteUsers)).then(p => p ?? escapeMFM(text)) : ""), + content: text.then(async text => text !== null ? MfmHelpers.toHtml(mfm.parse(text), JSON.parse(note.mentionedRemoteUsers), note.userHost).then(p => p ?? escapeMFM(text)) : ""), text: text, created_at: note.createdAt.toISOString(), emojis: noteEmoji, diff --git a/packages/backend/src/server/api/mastodon/converters/user.ts b/packages/backend/src/server/api/mastodon/converters/user.ts index 4bc2150c3..01f6e4467 100644 --- a/packages/backend/src/server/api/mastodon/converters/user.ts +++ b/packages/backend/src/server/api/mastodon/converters/user.ts @@ -31,7 +31,7 @@ export class UserConverter { acctUrl = `https://${u.host}/@${u.username}`; } const profile = UserProfiles.findOneBy({ userId: u.id }); - const bio = profile.then(profile => MfmHelpers.toHtml(mfm.parse(profile?.description ?? ""), []).then(p => p ?? escapeMFM(profile?.description ?? ""))); + const bio = profile.then(profile => MfmHelpers.toHtml(mfm.parse(profile?.description ?? ""), [], u.host).then(p => p ?? escapeMFM(profile?.description ?? ""))); const avatar = u.avatarId ? (DriveFiles.findOneBy({ id: u.avatarId })) .then(p => p?.url ?? Users.getIdenticonUrl(u.id)) @@ -110,7 +110,7 @@ export class UserConverter { private static async encodeField(f: Field, host: string | null): Promise { return { name: f.name, - value: await MfmHelpers.toHtml(mfm.parse(f.value), [], true) ?? escapeMFM(f.value), + value: await MfmHelpers.toHtml(mfm.parse(f.value), [], host, true) ?? escapeMFM(f.value), verified_at: f.verified ? (new Date()).toISOString() : null, } } diff --git a/packages/backend/src/server/api/mastodon/helpers/mfm.ts b/packages/backend/src/server/api/mastodon/helpers/mfm.ts index 390f5ff7a..a2f59ac42 100644 --- a/packages/backend/src/server/api/mastodon/helpers/mfm.ts +++ b/packages/backend/src/server/api/mastodon/helpers/mfm.ts @@ -9,7 +9,8 @@ export class MfmHelpers { public static async toHtml( nodes: mfm.MfmNode[] | null, mentionedRemoteUsers: IMentionedRemoteUsers = [], - inline: boolean = false + objectHost: string | null, + inline: boolean = false, ) { if (nodes == null) { return null; @@ -138,7 +139,7 @@ export class MfmHelpers { el.setAttribute("translate", "no"); const a = doc.createElement("a"); const { username, host, acct } = node.props; - a.href = await resolveMentionWithFallback(username, host, acct, mentionedRemoteUsers); + a.href = await resolveMentionWithFallback(username, host, objectHost, mentionedRemoteUsers); a.className = "u-url mention"; const span = doc.createElement("span"); span.textContent = username; diff --git a/packages/backend/src/server/api/mastodon/helpers/note.ts b/packages/backend/src/server/api/mastodon/helpers/note.ts index 5580f92d1..75df26f92 100644 --- a/packages/backend/src/server/api/mastodon/helpers/note.ts +++ b/packages/backend/src/server/api/mastodon/helpers/note.ts @@ -201,7 +201,7 @@ export class NoteHelpers { const files = DriveFiles.packMany(edit.fileIds); const item = { account: account, - content: MfmHelpers.toHtml(mfm.parse(edit.text ?? ''), JSON.parse(note.mentionedRemoteUsers)).then(p => p ?? ''), + content: MfmHelpers.toHtml(mfm.parse(edit.text ?? ''), JSON.parse(note.mentionedRemoteUsers), note.userHost).then(p => p ?? ''), created_at: lastDate.toISOString(), emojis: [], sensitive: files.then(files => files.length > 0 ? files.some((f) => f.isSensitive) : false), diff --git a/packages/backend/test/mfm.ts b/packages/backend/test/mfm.ts index a4f54c550..642403875 100644 --- a/packages/backend/test/mfm.ts +++ b/packages/backend/test/mfm.ts @@ -8,13 +8,13 @@ describe("toHtml", () => { it("br", async () => { const input = "foo\nbar\nbaz"; const output = "

foo
bar
baz

"; - assert.equal(await toHtml(mfm.parse(input)), output); + assert.equal(await toHtml(mfm.parse(input), [], null), output); }); it("br alt", async () => { const input = "foo\r\nbar\rbaz"; const output = "

foo
bar
baz

"; - assert.equal(await toHtml(mfm.parse(input)), output); + assert.equal(await toHtml(mfm.parse(input), [], null), output); }); });