Remove @/config.ts import from utils/note.ts

This commit is contained in:
Alex Gleason 2025-02-27 14:15:02 -06:00
parent c07c88f2f5
commit 0e667995c1
No known key found for this signature in database
GPG key ID: 7211D1F99744FBB7
5 changed files with 44 additions and 13 deletions

View file

@ -324,7 +324,8 @@ export class DittoRelayStore implements NRelay {
} }
private async prewarmLinkPreview(event: NostrEvent, signal?: AbortSignal): Promise<void> { private async prewarmLinkPreview(event: NostrEvent, signal?: AbortSignal): Promise<void> {
const { firstUrl } = parseNoteContent(stripimeta(event.content, event.tags), []); const { firstUrl } = parseNoteContent(stripimeta(event.content, event.tags), [], this.opts);
if (firstUrl) { if (firstUrl) {
await unfurlCardCached(firstUrl, signal); await unfurlCardCached(firstUrl, signal);
} }

View file

@ -1,26 +1,35 @@
import { DittoConf } from '@ditto/conf';
import { assertEquals } from '@std/assert'; import { assertEquals } from '@std/assert';
import { eventFixture } from '@/test.ts'; import { eventFixture } from '@/test.ts';
import { getMediaLinks, parseNoteContent, stripimeta } from '@/utils/note.ts'; import { getMediaLinks, parseNoteContent, stripimeta } from '@/utils/note.ts';
Deno.test('parseNoteContent', () => { Deno.test('parseNoteContent', () => {
const { html, links, firstUrl } = parseNoteContent('Hello, world!', []); const conf = new DittoConf(new Map());
const { html, links, firstUrl } = parseNoteContent('Hello, world!', [], { conf });
assertEquals(html, 'Hello, world!'); assertEquals(html, 'Hello, world!');
assertEquals(links, []); assertEquals(links, []);
assertEquals(firstUrl, undefined); assertEquals(firstUrl, undefined);
}); });
Deno.test('parseNoteContent parses URLs', () => { Deno.test('parseNoteContent parses URLs', () => {
const { html } = parseNoteContent('check out my website: https://alexgleason.me', []); const conf = new DittoConf(new Map());
const { html } = parseNoteContent('check out my website: https://alexgleason.me', [], { conf });
assertEquals(html, 'check out my website: <a href="https://alexgleason.me">https://alexgleason.me</a>'); assertEquals(html, 'check out my website: <a href="https://alexgleason.me">https://alexgleason.me</a>');
}); });
Deno.test('parseNoteContent parses bare URLs', () => { Deno.test('parseNoteContent parses bare URLs', () => {
const { html } = parseNoteContent('have you seen ditto.pub?', []); const conf = new DittoConf(new Map());
const { html } = parseNoteContent('have you seen ditto.pub?', [], { conf });
assertEquals(html, 'have you seen <a href="http://ditto.pub">ditto.pub</a>?'); assertEquals(html, 'have you seen <a href="http://ditto.pub">ditto.pub</a>?');
}); });
Deno.test('parseNoteContent parses mentions with apostrophes', () => { Deno.test('parseNoteContent parses mentions with apostrophes', () => {
const conf = new DittoConf(new Map());
const { html } = parseNoteContent( const { html } = parseNoteContent(
`did you see nostr:nprofile1qqsqgc0uhmxycvm5gwvn944c7yfxnnxm0nyh8tt62zhrvtd3xkj8fhgprdmhxue69uhkwmr9v9ek7mnpw3hhytnyv4mz7un9d3shjqgcwaehxw309ahx7umywf5hvefwv9c8qtmjv4kxz7gpzemhxue69uhhyetvv9ujumt0wd68ytnsw43z7s3al0v's speech?`, `did you see nostr:nprofile1qqsqgc0uhmxycvm5gwvn944c7yfxnnxm0nyh8tt62zhrvtd3xkj8fhgprdmhxue69uhkwmr9v9ek7mnpw3hhytnyv4mz7un9d3shjqgcwaehxw309ahx7umywf5hvefwv9c8qtmjv4kxz7gpzemhxue69uhhyetvv9ujumt0wd68ytnsw43z7s3al0v's speech?`,
[{ [{
@ -29,7 +38,9 @@ Deno.test('parseNoteContent parses mentions with apostrophes', () => {
acct: 'alex@gleasonator.dev', acct: 'alex@gleasonator.dev',
url: 'https://gleasonator.dev/@alex', url: 'https://gleasonator.dev/@alex',
}], }],
{ conf },
); );
assertEquals( assertEquals(
html, 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?', '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?',
@ -37,6 +48,8 @@ Deno.test('parseNoteContent parses mentions with apostrophes', () => {
}); });
Deno.test('parseNoteContent parses mentions with commas', () => { Deno.test('parseNoteContent parses mentions with commas', () => {
const conf = new DittoConf(new Map());
const { html } = parseNoteContent( const { html } = parseNoteContent(
`Sim. Hi nostr:npub1q3sle0kvfsehgsuexttt3ugjd8xdklxfwwkh559wxckmzddywnws6cd26p and nostr:npub1gujeqakgt7fyp6zjggxhyy7ft623qtcaay5lkc8n8gkry4cvnrzqd3f67z, any chance to have Cobrafuma as PWA?`, `Sim. Hi nostr:npub1q3sle0kvfsehgsuexttt3ugjd8xdklxfwwkh559wxckmzddywnws6cd26p and nostr:npub1gujeqakgt7fyp6zjggxhyy7ft623qtcaay5lkc8n8gkry4cvnrzqd3f67z, any chance to have Cobrafuma as PWA?`,
[{ [{
@ -50,7 +63,9 @@ Deno.test('parseNoteContent parses mentions with commas', () => {
acct: 'patrick@patrickdosreis.com', acct: 'patrick@patrickdosreis.com',
url: 'https://gleasonator.dev/@patrick@patrickdosreis.com', url: 'https://gleasonator.dev/@patrick@patrickdosreis.com',
}], }],
{ conf },
); );
assertEquals( assertEquals(
html, html,
'Sim. Hi <span class="h-card"><a class="u-url mention" href="https://gleasonator.dev/@alex" rel="ugc">@<span>alex@gleasonator.dev</span></a></span> and <span class="h-card"><a class="u-url mention" href="https://gleasonator.dev/@patrick@patrickdosreis.com" rel="ugc">@<span>patrick@patrickdosreis.com</span></a></span>, any chance to have Cobrafuma as PWA?', 'Sim. Hi <span class="h-card"><a class="u-url mention" href="https://gleasonator.dev/@alex" rel="ugc">@<span>alex@gleasonator.dev</span></a></span> and <span class="h-card"><a class="u-url mention" href="https://gleasonator.dev/@patrick@patrickdosreis.com" rel="ugc">@<span>patrick@patrickdosreis.com</span></a></span>, any chance to have Cobrafuma as PWA?',
@ -58,19 +73,26 @@ Deno.test('parseNoteContent parses mentions with commas', () => {
}); });
Deno.test("parseNoteContent doesn't parse invalid nostr URIs", () => { Deno.test("parseNoteContent doesn't parse invalid nostr URIs", () => {
const { html } = parseNoteContent('nip19 has URIs like nostr:npub and nostr:nevent, etc.', []); const conf = new DittoConf(new Map());
const { html } = parseNoteContent('nip19 has URIs like nostr:npub and nostr:nevent, etc.', [], { conf });
assertEquals(html, 'nip19 has URIs like nostr:npub and nostr:nevent, etc.'); assertEquals(html, 'nip19 has URIs like nostr:npub and nostr:nevent, etc.');
}); });
Deno.test('parseNoteContent renders empty for non-profile nostr URIs', () => { Deno.test('parseNoteContent renders empty for non-profile nostr URIs', () => {
const conf = new DittoConf(new Map());
const { html } = parseNoteContent( const { html } = parseNoteContent(
'nostr:nevent1qgsr9cvzwc652r4m83d86ykplrnm9dg5gwdvzzn8ameanlvut35wy3gpz3mhxue69uhhztnnwashymtnw3ezucm0d5qzqru8mkz2q4gzsxg99q7pdneyx7n8p5u0afe3ntapj4sryxxmg4gpcdvgce', 'nostr:nevent1qgsr9cvzwc652r4m83d86ykplrnm9dg5gwdvzzn8ameanlvut35wy3gpz3mhxue69uhhztnnwashymtnw3ezucm0d5qzqru8mkz2q4gzsxg99q7pdneyx7n8p5u0afe3ntapj4sryxxmg4gpcdvgce',
[], [],
{ conf },
); );
assertEquals(html, ''); assertEquals(html, '');
}); });
Deno.test("parseNoteContent doesn't fuck up links to my own post", () => { Deno.test("parseNoteContent doesn't fuck up links to my own post", () => {
const conf = new DittoConf(new Map());
const { html } = parseNoteContent( const { html } = parseNoteContent(
'Check this post: https://gleasonator.dev/@alex@gleasonator.dev/posts/a8badb480d88f9e7b6a090342279ef47ed0e0a3989ed85f898dfedc6be94225f', 'Check this post: https://gleasonator.dev/@alex@gleasonator.dev/posts/a8badb480d88f9e7b6a090342279ef47ed0e0a3989ed85f898dfedc6be94225f',
[{ [{
@ -79,7 +101,9 @@ Deno.test("parseNoteContent doesn't fuck up links to my own post", () => {
acct: 'alex@gleasonator.dev', acct: 'alex@gleasonator.dev',
url: 'https://gleasonator.dev/@alex', url: 'https://gleasonator.dev/@alex',
}], }],
{ conf },
); );
assertEquals( assertEquals(
html, html,
'Check this post: <a href="https://gleasonator.dev/@alex@gleasonator.dev/posts/a8badb480d88f9e7b6a090342279ef47ed0e0a3989ed85f898dfedc6be94225f">https://gleasonator.dev/@alex@gleasonator.dev/posts/a8badb480d88f9e7b6a090342279ef47ed0e0a3989ed85f898dfedc6be94225f</a>', 'Check this post: <a href="https://gleasonator.dev/@alex@gleasonator.dev/posts/a8badb480d88f9e7b6a090342279ef47ed0e0a3989ed85f898dfedc6be94225f">https://gleasonator.dev/@alex@gleasonator.dev/posts/a8badb480d88f9e7b6a090342279ef47ed0e0a3989ed85f898dfedc6be94225f</a>',

View file

@ -3,11 +3,11 @@ import linkifyStr from 'linkify-string';
import linkify from 'linkifyjs'; import linkify from 'linkifyjs';
import { nip19, nip27 } from 'nostr-tools'; import { nip19, nip27 } from 'nostr-tools';
import { Conf } from '@/config.ts';
import { html } from '@/utils/html.ts'; import { html } from '@/utils/html.ts';
import { getUrlMediaType, isPermittedMediaType } from '@/utils/media.ts'; import { getUrlMediaType, isPermittedMediaType } from '@/utils/media.ts';
import { MastodonMention } from '@ditto/mastoapi/types'; import type { DittoConf } from '@ditto/conf';
import type { MastodonMention } from '@ditto/mastoapi/types';
linkify.registerCustomProtocol('nostr', true); linkify.registerCustomProtocol('nostr', true);
linkify.registerCustomProtocol('wss'); linkify.registerCustomProtocol('wss');
@ -21,8 +21,14 @@ interface ParsedNoteContent {
firstUrl: string | undefined; firstUrl: string | undefined;
} }
interface ParseNoteContentOpts {
conf: DittoConf;
}
/** Convert Nostr content to Mastodon API HTML. Also return parsed data. */ /** Convert Nostr content to Mastodon API HTML. Also return parsed data. */
function parseNoteContent(content: string, mentions: MastodonMention[]): ParsedNoteContent { function parseNoteContent(content: string, mentions: MastodonMention[], opts: ParseNoteContentOpts): ParsedNoteContent {
const { conf } = opts;
const links = linkify.find(content).filter(({ type }) => type === 'url'); const links = linkify.find(content).filter(({ type }) => type === 'url');
const firstUrl = links.find(isNonMediaLink)?.href; const firstUrl = links.find(isNonMediaLink)?.href;
@ -30,7 +36,7 @@ function parseNoteContent(content: string, mentions: MastodonMention[]): ParsedN
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 html`<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 }) => {
@ -49,7 +55,7 @@ function parseNoteContent(content: string, mentions: MastodonMention[]): ParsedN
const npub = nip19.npubEncode(pubkey); const npub = nip19.npubEncode(pubkey);
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 html`<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 { } else {
return ''; return '';

View file

@ -48,7 +48,7 @@ function renderAccount(event: Omit<DittoEvent, 'id' | 'sig'>, opts: ToAccountOpt
const parsed05 = stats?.nip05 ? parseNip05(stats.nip05) : undefined; const parsed05 = stats?.nip05 ? parseNip05(stats.nip05) : undefined;
const acct = parsed05?.handle || npub; const acct = parsed05?.handle || npub;
const { html } = parseNoteContent(about || '', []); const { html } = parseNoteContent(about || '', [], { conf: Conf });
const fields = _fields const fields = _fields
?.slice(0, Conf.profileFields.maxFields) ?.slice(0, Conf.profileFields.maxFields)
@ -84,7 +84,7 @@ function renderAccount(event: Omit<DittoEvent, 'id' | 'sig'>, opts: ToAccountOpt
discoverable: true, discoverable: true,
display_name: name ?? '', display_name: name ?? '',
emojis: renderEmojis(event), emojis: renderEmojis(event),
fields: fields.map((field) => ({ ...field, value: parseNoteContent(field.value, []).html })), fields: fields.map((field) => ({ ...field, value: parseNoteContent(field.value, [], { conf: Conf }).html })),
follow_requests_count: 0, follow_requests_count: 0,
followers_count: stats?.followers_count ?? 0, followers_count: stats?.followers_count ?? 0,
following_count: stats?.following_count ?? 0, following_count: stats?.following_count ?? 0,

View file

@ -42,7 +42,7 @@ async function renderStatus(
const mentions = event.mentions?.map((event) => renderMention(event)) ?? []; const mentions = event.mentions?.map((event) => renderMention(event)) ?? [];
const { html, links, firstUrl } = parseNoteContent(stripimeta(event.content, event.tags), mentions); const { html, links, firstUrl } = parseNoteContent(stripimeta(event.content, event.tags), mentions, { conf: Conf });
const [card, relatedEvents] = await Promise const [card, relatedEvents] = await Promise
.all([ .all([