From 529e61be6d286e6d64ad24ee9d9cd78b98b0ee68 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Wed, 7 Aug 2024 17:27:22 -0500 Subject: [PATCH] Return properly formatted mentions in Status API --- src/utils/note.ts | 66 +++++++++++++++++----------------- src/views/mastodon/statuses.ts | 38 ++++++++------------ 2 files changed, 48 insertions(+), 56 deletions(-) diff --git a/src/utils/note.ts b/src/utils/note.ts index 6e0d8d41..00be4b1a 100644 --- a/src/utils/note.ts +++ b/src/utils/note.ts @@ -4,40 +4,12 @@ import linkify from 'linkifyjs'; import { nip19, nip21, nip27 } from 'nostr-tools'; import { Conf } from '@/config.ts'; +import { MastodonMention } from '@/entities/MastodonMention.ts'; import { getUrlMediaType, isPermittedMediaType } from '@/utils/media.ts'; linkify.registerCustomProtocol('nostr', true); linkify.registerCustomProtocol('wss'); -const linkifyOpts: linkify.Opts = { - render: { - hashtag: ({ content }) => { - const tag = content.replace(/^#/, ''); - const href = Conf.local(`/tags/${tag}`); - return `#${tag}`; - }, - url: ({ attributes, content }) => { - try { - const { decoded } = nip21.parse(content); - const pubkey = getDecodedPubkey(decoded); - if (pubkey) { - const name = pubkey.substring(0, 8); - const href = Conf.local(`/users/${pubkey}`); - return `@${name}`; - } else { - return ''; - } - } catch { - const attr = Object.entries(attributes) - .map(([name, value]) => `${name}="${value}"`) - .join(' '); - - return `${content}`; - } - }, - }, -}; - type Link = ReturnType[0]; interface ParsedNoteContent { @@ -48,12 +20,42 @@ interface ParsedNoteContent { } /** Convert Nostr content to Mastodon API HTML. Also return parsed data. */ -function parseNoteContent(content: string): ParsedNoteContent { - // Parsing twice is ineffecient, but I don't know how to do only once. - const html = linkifyStr(content, linkifyOpts).replace(/\n+$/, ''); +function parseNoteContent(content: string, mentions: MastodonMention[]): ParsedNoteContent { const links = linkify.find(content).filter(isLinkURL); const firstUrl = links.find(isNonMediaLink)?.href; + const html = linkifyStr(content, { + render: { + hashtag: ({ content }) => { + const tag = content.replace(/^#/, ''); + const href = Conf.local(`/tags/${tag}`); + return `#${tag}`; + }, + url: ({ attributes, content }) => { + try { + const { decoded } = nip21.parse(content); + const pubkey = getDecodedPubkey(decoded); + if (pubkey) { + const mention = mentions.find((m) => m.id === pubkey); + const npub = nip19.npubEncode(pubkey); + const acct = mention?.acct ?? npub; + const name = mention?.acct ?? npub.substring(0, 8); + const href = mention?.url ?? Conf.local(`/@${acct}`); + return `@${name}`; + } else { + return ''; + } + } catch { + const attr = Object.entries(attributes) + .map(([name, value]) => `${name}="${value}"`) + .join(' '); + + return `${content}`; + } + }, + }, + }).replace(/\n+$/, ''); + return { html, links, diff --git a/src/views/mastodon/statuses.ts b/src/views/mastodon/statuses.ts index 27bffdfb..7583ea95 100644 --- a/src/views/mastodon/statuses.ts +++ b/src/views/mastodon/statuses.ts @@ -46,13 +46,14 @@ async function renderStatus(event: DittoEvent, opts: RenderStatusOpts): Promise< [{ kinds: [0], authors: mentionedPubkeys, limit: mentionedPubkeys.length }], ); - const { html, links, firstUrl } = parseNoteContent(stripimeta(event.content, event.tags)); + const mentions = await Promise.all( + mentionedPubkeys.map((pubkey) => renderMention(pubkey, mentionedProfiles.find((event) => event.pubkey === pubkey))), + ); - const [mentions, card, relatedEvents] = await Promise + const { html, links, firstUrl } = parseNoteContent(stripimeta(event.content, event.tags), mentions); + + const [card, relatedEvents] = await Promise .all([ - Promise.all( - mentionedPubkeys.map((pubkey) => toMention(pubkey, mentionedProfiles.find((event) => event.pubkey === pubkey))), - ), firstUrl ? unfurlCardCached(firstUrl) : null, viewerPubkey ? await store.query([ @@ -152,25 +153,14 @@ async function renderReblog(event: DittoEvent, opts: RenderStatusOpts): Promise< }; } -async function toMention(pubkey: string, event?: NostrEvent): Promise { - const account = event ? await renderAccount(event) : undefined; - - if (account) { - return { - id: account.id, - acct: account.acct, - username: account.username, - url: account.url, - }; - } else { - const npub = nip19.npubEncode(pubkey); - return { - id: pubkey, - acct: npub, - username: npub.substring(0, 8), - url: Conf.local(`/users/${pubkey}`), - }; - } +async function renderMention(pubkey: string, event?: NostrEvent): Promise { + const account = event ? await renderAccount(event) : await accountFromPubkey(pubkey); + return { + id: account.id, + acct: account.acct, + username: account.username, + url: account.url, + }; } function buildInlineRecipients(mentions: MastodonMention[]): string {