Fix parsing bech32 with apostrophe

This commit is contained in:
Alex Gleason 2024-08-28 19:33:22 +02:00
parent 8d8211de09
commit c2ea4cbfd5
No known key found for this signature in database
GPG key ID: 7211D1F99744FBB7
2 changed files with 43 additions and 17 deletions

View file

@ -10,6 +10,27 @@ Deno.test('parseNoteContent', () => {
assertEquals(firstUrl, undefined); assertEquals(firstUrl, undefined);
}); });
Deno.test('parseNoteContent handles apostrophes', () => {
const { html } = parseNoteContent(
`did you see nostr:nprofile1qqsqgc0uhmxycvm5gwvn944c7yfxnnxm0nyh8tt62zhrvtd3xkj8fhgprdmhxue69uhkwmr9v9ek7mnpw3hhytnyv4mz7un9d3shjqgcwaehxw309ahx7umywf5hvefwv9c8qtmjv4kxz7gpzemhxue69uhhyetvv9ujumt0wd68ytnsw43z7s3al0v's speech?`,
[{
id: '0461fcbecc4c3374439932d6b8f11269ccdb7cc973ad7a50ae362db135a474dd',
username: 'alex',
acct: 'alex@gleasonator.dev',
url: 'https://gleasonator.dev/@alex',
}],
);
assertEquals(
html,
`did you see <span class="h-card"><a class="u-url mention" href="https://gleasonator.dev/@alex" rel="ugc">@<span>alex@gleasonator.dev</span></a></span>&apos;s speech?`,
);
});
Deno.test("parseNoteContent doesn't parse invalid nostr URIs", () => {
const { html } = parseNoteContent(`nip19 has URIs like nostr:npub and nostr:nevent, etc.`, []);
assertEquals(html, 'nip19 has URIs like nostr:npub and nostr:nevent, etc.');
});
Deno.test('getMediaLinks', () => { Deno.test('getMediaLinks', () => {
const links = [ const links = [
{ href: 'https://example.com/image.png' }, { href: 'https://example.com/image.png' },

View file

@ -1,10 +1,11 @@
import 'linkify-plugin-hashtag'; import 'linkify-plugin-hashtag';
import linkifyStr from 'linkify-string'; import linkifyStr from 'linkify-string';
import linkify from 'linkifyjs'; import linkify from 'linkifyjs';
import { nip19, nip21, nip27 } from 'nostr-tools'; import { nip19, nip27 } from 'nostr-tools';
import { Conf } from '@/config.ts'; import { Conf } from '@/config.ts';
import { MastodonMention } from '@/entities/MastodonMention.ts'; import { MastodonMention } from '@/entities/MastodonMention.ts';
import { html } from '@/utils/html.ts';
import { getUrlMediaType, isPermittedMediaType } from '@/utils/media.ts'; import { getUrlMediaType, isPermittedMediaType } from '@/utils/media.ts';
linkify.registerCustomProtocol('nostr', true); linkify.registerCustomProtocol('nostr', true);
@ -23,18 +24,22 @@ interface ParsedNoteContent {
function parseNoteContent(content: string, mentions: MastodonMention[]): ParsedNoteContent { function parseNoteContent(content: string, mentions: MastodonMention[]): ParsedNoteContent {
const links = linkify.find(content).filter(isLinkURL); const links = linkify.find(content).filter(isLinkURL);
const firstUrl = links.find(isNonMediaLink)?.href; const firstUrl = links.find(isNonMediaLink)?.href;
const html = linkifyStr(content, {
const result = linkifyStr(content, {
render: { render: {
hashtag: ({ content }) => { hashtag: ({ content }) => {
const tag = content.replace(/^#/, ''); const tag = content.replace(/^#/, '');
const href = Conf.local(`/tags/${tag}`); const href = Conf.local(`/tags/${tag}`);
return `<a class=\"mention hashtag\" href=\"${href}\" rel=\"tag\"><span>#</span>${tag}</a>`; return html`<a class=\"mention hashtag\" href=\"${href}\" rel=\"tag\"><span>#</span>${tag}</a>`;
}, },
url: ({ attributes, content }) => { url: ({ attributes, content }) => {
const extra = content.slice(69)
content = content.slice(0,69)
try { try {
const { decoded } = nip21.parse(content); const { pathname } = new URL(content);
const match = pathname.match(new RegExp(`^${nip19.BECH32_REGEX.source}`));
if (match) {
const bech32 = match[0];
const extra = pathname.slice(bech32.length);
const decoded = nip19.decode(bech32);
const pubkey = getDecodedPubkey(decoded); const pubkey = getDecodedPubkey(decoded);
if (pubkey) { if (pubkey) {
const mention = mentions.find((m) => m.id === pubkey); const mention = mentions.find((m) => m.id === pubkey);
@ -42,10 +47,10 @@ function parseNoteContent(content: string, mentions: MastodonMention[]): ParsedN
const acct = mention?.acct ?? npub; const acct = mention?.acct ?? npub;
const name = mention?.acct ?? npub.substring(0, 8); const name = mention?.acct ?? npub.substring(0, 8);
const href = mention?.url ?? Conf.local(`/@${acct}`); const href = mention?.url ?? Conf.local(`/@${acct}`);
return `<span class="h-card"><a class="u-url mention" href="${href}" rel="ugc">@<span>${name}</span></a></span>${extra}`; return html`<span class="h-card"><a class="u-url mention" href="${href}" rel="ugc">@<span>${name}</span></a></span>${extra}`;
} else {
return '';
} }
}
return content;
} catch { } catch {
const attr = Object.entries(attributes) const attr = Object.entries(attributes)
.map(([name, value]) => `${name}="${value}"`) .map(([name, value]) => `${name}="${value}"`)
@ -58,7 +63,7 @@ function parseNoteContent(content: string, mentions: MastodonMention[]): ParsedN
}).replace(/\n+$/, ''); }).replace(/\n+$/, '');
return { return {
html, html: result,
links, links,
firstUrl, firstUrl,
}; };