diff --git a/packages/backend/src/mfm/to-html.ts b/packages/backend/src/mfm/to-html.ts index 8d06b2a95..5ae4cda2a 100644 --- a/packages/backend/src/mfm/to-html.ts +++ b/packages/backend/src/mfm/to-html.ts @@ -113,21 +113,21 @@ export async function toHtml( return a; }, - mention(node) { + async mention(node) { const { username, host, acct } = node.props; - const href = resolveMentionFromCache(username, host, objectHost, mentionedRemoteUsers); + const resolved = await resolveMentionFromCache(username, host, objectHost, mentionedRemoteUsers); const el = doc.createElement("span"); - if (href === null) { + if (resolved === null) { el.textContent = acct; } else { el.setAttribute("class", "h-card"); el.setAttribute("translate", "no"); const a = doc.createElement("a"); - a.href = href; + a.href = resolved.href; a.className = "u-url mention"; const span = doc.createElement("span"); - span.textContent = username; + span.textContent = resolved.username; a.textContent = '@'; a.appendChild(span); el.appendChild(a); diff --git a/packages/backend/src/remote/resolve-user.ts b/packages/backend/src/remote/resolve-user.ts index 13cddc2d8..b2fa71432 100644 --- a/packages/backend/src/remote/resolve-user.ts +++ b/packages/backend/src/remote/resolve-user.ts @@ -13,6 +13,7 @@ import { IMentionedRemoteUsers } from "@/models/entities/note.js"; const logger = remoteLogger.createSubLogger("resolve-user"); const uriHostCache = new Cache("resolveUserUriHost", 60 * 60 * 24); +const localUsernameCache = new Cache("localUserNameCapitalization", 60 * 60 * 24); export async function resolveUser( username: string, @@ -184,7 +185,6 @@ export async function resolveUser( export async function resolveMentionToUserAndProfile(username: string, host: string | null, objectHost: string | null) { try { - //const fallback = getMentionFallbackUri(username, host, objectHost); const user = await resolveUser(username, host ?? objectHost, false); const profile = await UserProfiles.findOneBy({ userId: user.id }); const data = { username, host: host ?? objectHost }; @@ -206,12 +206,30 @@ export function getMentionFallbackUri(username: string, host: string | null, obj return fallback; } -export function resolveMentionFromCache(username: string, host: string | null, objectHost: string | null, cache: IMentionedRemoteUsers): string | null { - const fallback = getMentionFallbackUri(username, host, objectHost); +async function getLocalUsernameCached(username: string): Promise { + return localUsernameCache.fetch(username.toLowerCase(), () => + Users.findOneBy({ usernameLower: username.toLowerCase(), host: IsNull() }) + .then(p => p ? p.username : null)); +} +export async function resolveMentionFromCache(username: string, host: string | null, objectHost: string | null, cache: IMentionedRemoteUsers): Promise<{ username: string, href: string } | null> { + const isLocal = (host === null && objectHost === null) || host === config.domain; + if (isLocal) { + const finalUsername = await getLocalUsernameCached(username); + if (finalUsername === null) return null; + username = finalUsername; + } + try { + if (isLocal) username = await resolveUser(username, null).then(p => p?.username ?? username); + } catch { + return null; + } + + const fallback = getMentionFallbackUri(username, host, objectHost); const cached = cache.find(r => r.username.toLowerCase() === username.toLowerCase() && r.host === host); - if (cached) return cached.url ?? cached.uri ?? fallback; - if ((host === null && objectHost === null) || host === config.domain) return fallback; + const href = cached?.url ?? cached?.uri; + if (cached && href != null) return { username: cached.username, href: href }; + if (isLocal) return { username: username, href: fallback }; return null; } diff --git a/packages/backend/src/server/api/mastodon/helpers/mfm.ts b/packages/backend/src/server/api/mastodon/helpers/mfm.ts index 055ac9110..c1f69e3af 100644 --- a/packages/backend/src/server/api/mastodon/helpers/mfm.ts +++ b/packages/backend/src/server/api/mastodon/helpers/mfm.ts @@ -135,21 +135,21 @@ export class MfmHelpers { return a; }, - mention(node) { + async mention(node) { const { username, host, acct } = node.props; - const href = resolveMentionFromCache(username, host, objectHost, mentionedRemoteUsers); + const resolved = await resolveMentionFromCache(username, host, objectHost, mentionedRemoteUsers); const el = doc.createElement("span"); - if (href === null) { + if (resolved === null) { el.textContent = acct; } else { el.setAttribute("class", "h-card"); el.setAttribute("translate", "no"); const a = doc.createElement("a"); - a.href = href; + a.href = resolved.href; a.className = "u-url mention"; const span = doc.createElement("span"); - span.textContent = username; + span.textContent = resolved.username; a.textContent = '@'; a.appendChild(span); el.appendChild(a);