Move statuses view into its own file

This commit is contained in:
Alex Gleason 2023-10-06 15:37:31 -05:00
parent 0b77e7d888
commit d49c63bb1a
No known key found for this signature in database
GPG Key ID: 7211D1F99744FBB7
7 changed files with 147 additions and 139 deletions

View File

@ -12,7 +12,8 @@ import { paginated, paginationSchema, parseBody } from '@/utils/web.ts';
import { createEvent } from '@/utils/web.ts'; import { createEvent } from '@/utils/web.ts';
import { renderEventAccounts } from '@/views.ts'; import { renderEventAccounts } from '@/views.ts';
import { renderAccount } from '@/views/mastodon/accounts.ts'; import { renderAccount } from '@/views/mastodon/accounts.ts';
import { accountFromPubkey, toRelationship, toStatus } from '@/views/nostr-to-mastoapi.ts'; import { renderStatus } from '@/views/mastodon/statuses.ts';
import { accountFromPubkey, toRelationship } from '@/views/nostr-to-mastoapi.ts';
const usernameSchema = z const usernameSchema = z
.string().min(1).max(30) .string().min(1).max(30)
@ -149,7 +150,7 @@ const accountStatusesController: AppController = async (c) => {
events = events.filter((event) => !findReplyTag(event)); events = events.filter((event) => !findReplyTag(event));
} }
const statuses = await Promise.all(events.map((event) => toStatus(event, c.get('pubkey')))); const statuses = await Promise.all(events.map((event) => renderStatus(event, c.get('pubkey'))));
return paginated(c, events, statuses); return paginated(c, events, statuses);
}; };
@ -259,7 +260,7 @@ const favouritesController: AppController = async (c) => {
const events1 = await mixer.getFilters([{ kinds: [1], ids }], { timeout: Time.seconds(1) }); const events1 = await mixer.getFilters([{ kinds: [1], ids }], { timeout: Time.seconds(1) });
const statuses = await Promise.all(events1.map((event) => toStatus(event, c.get('pubkey')))); const statuses = await Promise.all(events1.map((event) => renderStatus(event, c.get('pubkey'))));
return paginated(c, events1, statuses); return paginated(c, events1, statuses);
}; };

View File

@ -7,7 +7,7 @@ import { nostrIdSchema } from '@/schemas/nostr.ts';
import { dedupeEvents, Time } from '@/utils.ts'; import { dedupeEvents, Time } from '@/utils.ts';
import { lookupNip05Cached } from '@/utils/nip05.ts'; import { lookupNip05Cached } from '@/utils/nip05.ts';
import { renderAccount } from '@/views/mastodon/accounts.ts'; import { renderAccount } from '@/views/mastodon/accounts.ts';
import { toStatus } from '@/views/nostr-to-mastoapi.ts'; import { renderStatus } from '@/views/mastodon/statuses.ts';
/** Matches NIP-05 names with or without an @ in front. */ /** Matches NIP-05 names with or without an @ in front. */
const ACCT_REGEX = /^@?(?:([\w.+-]+)@)?([\w.-]+)$/; const ACCT_REGEX = /^@?(?:([\w.+-]+)@)?([\w.-]+)$/;
@ -50,7 +50,7 @@ const searchController: AppController = async (c) => {
Promise.all( Promise.all(
results results
.filter((event): event is Event<1> => event.kind === 1) .filter((event): event is Event<1> => event.kind === 1)
.map((event) => toStatus(event, c.get('pubkey'))), .map((event) => renderStatus(event, c.get('pubkey'))),
), ),
]); ]);

View File

@ -4,7 +4,7 @@ import { type Event, ISO6391, z } from '@/deps.ts';
import { getAncestors, getDescendants, getEvent } from '@/queries.ts'; import { getAncestors, getDescendants, getEvent } from '@/queries.ts';
import { createEvent, paginationSchema, parseBody } from '@/utils/web.ts'; import { createEvent, paginationSchema, parseBody } from '@/utils/web.ts';
import { renderEventAccounts } from '@/views.ts'; import { renderEventAccounts } from '@/views.ts';
import { toStatus } from '@/views/nostr-to-mastoapi.ts'; import { renderStatus } from '@/views/mastodon/statuses.ts';
const createStatusSchema = z.object({ const createStatusSchema = z.object({
in_reply_to_id: z.string().regex(/[0-9a-f]{64}/).nullish(), in_reply_to_id: z.string().regex(/[0-9a-f]{64}/).nullish(),
@ -31,7 +31,7 @@ const statusController: AppController = async (c) => {
const event = await getEvent(id, { kind: 1 }); const event = await getEvent(id, { kind: 1 });
if (event) { if (event) {
return c.json(await toStatus(event, c.get('pubkey'))); return c.json(await renderStatus(event, c.get('pubkey')));
} }
return c.json({ error: 'Event not found.' }, 404); return c.json({ error: 'Event not found.' }, 404);
@ -83,7 +83,7 @@ const createStatusController: AppController = async (c) => {
tags, tags,
}, c); }, c);
return c.json(await toStatus(event, c.get('pubkey'))); return c.json(await renderStatus(event, c.get('pubkey')));
}; };
const contextController: AppController = async (c) => { const contextController: AppController = async (c) => {
@ -91,7 +91,7 @@ const contextController: AppController = async (c) => {
const event = await getEvent(id, { kind: 1 }); const event = await getEvent(id, { kind: 1 });
async function renderStatuses(events: Event<1>[]) { async function renderStatuses(events: Event<1>[]) {
const statuses = await Promise.all(events.map((event) => toStatus(event, c.get('pubkey')))); const statuses = await Promise.all(events.map((event) => renderStatus(event, c.get('pubkey'))));
return statuses.filter(Boolean); return statuses.filter(Boolean);
} }
@ -121,7 +121,7 @@ const favouriteController: AppController = async (c) => {
], ],
}, c); }, c);
const status = await toStatus(target, c.get('pubkey')); const status = await renderStatus(target, c.get('pubkey'));
if (status) { if (status) {
status.favourited = true; status.favourited = true;

View File

@ -4,7 +4,7 @@ import { type DittoFilter } from '@/filter.ts';
import { getFeedPubkeys } from '@/queries.ts'; import { getFeedPubkeys } from '@/queries.ts';
import { Sub } from '@/subs.ts'; import { Sub } from '@/subs.ts';
import { bech32ToPubkey } from '@/utils.ts'; import { bech32ToPubkey } from '@/utils.ts';
import { toStatus } from '@/views/nostr-to-mastoapi.ts'; import { renderStatus } from '@/views/mastodon/statuses.ts';
/** /**
* Streaming timelines/categories. * Streaming timelines/categories.
@ -63,7 +63,7 @@ const streamingController: AppController = (c) => {
if (filter) { if (filter) {
for await (const event of Sub.sub(socket, '1', [filter])) { for await (const event of Sub.sub(socket, '1', [filter])) {
const status = await toStatus(event, pubkey); const status = await renderStatus(event, pubkey);
if (status) { if (status) {
send('update', status); send('update', status);
} }

View File

@ -5,7 +5,7 @@ import { getFeedPubkeys } from '@/queries.ts';
import { booleanParamSchema } from '@/schema.ts'; import { booleanParamSchema } from '@/schema.ts';
import { Time } from '@/utils.ts'; import { Time } from '@/utils.ts';
import { paginated, paginationSchema } from '@/utils/web.ts'; import { paginated, paginationSchema } from '@/utils/web.ts';
import { toStatus } from '@/views/nostr-to-mastoapi.ts'; import { renderStatus } from '@/views/mastodon/statuses.ts';
import type { AppContext, AppController } from '@/app.ts'; import type { AppContext, AppController } from '@/app.ts';
@ -40,7 +40,7 @@ async function renderStatuses(c: AppContext, filters: DittoFilter<1>[]) {
return c.json([]); return c.json([]);
} }
const statuses = await Promise.all(events.map((event) => toStatus(event, c.get('pubkey')))); const statuses = await Promise.all(events.map((event) => renderStatus(event, c.get('pubkey'))));
return paginated(c, events, statuses); return paginated(c, events, statuses);
} }

View File

@ -0,0 +1,126 @@
import { isCWTag } from 'https://gitlab.com/soapbox-pub/mostr/-/raw/c67064aee5ade5e01597c6d23e22e53c628ef0e2/src/nostr/tags.ts';
import { Conf } from '@/config.ts';
import * as eventsDB from '@/db/events.ts';
import { type Event, findReplyTag, nip19 } from '@/deps.ts';
import { getMediaLinks, parseNoteContent } from '@/note.ts';
import { getAuthor } from '@/queries.ts';
import { jsonMediaDataSchema } from '@/schemas/nostr.ts';
import { nostrDate } from '@/utils.ts';
import { unfurlCardCached } from '@/utils/unfurl.ts';
import { accountFromPubkey, renderAccount } from '@/views/mastodon/accounts.ts';
import { DittoAttachment, renderAttachment } from '@/views/mastodon/attachments.ts';
import { renderEmojis } from '@/views/mastodon/emojis.ts';
async function renderStatus(event: Event<1>, viewerPubkey?: string) {
const profile = await getAuthor(event.pubkey);
const account = profile ? await renderAccount(profile) : await accountFromPubkey(event.pubkey);
const replyTag = findReplyTag(event);
const mentionedPubkeys = [
...new Set(
event.tags
.filter((tag) => tag[0] === 'p')
.map((tag) => tag[1]),
),
];
const { html, links, firstUrl } = parseNoteContent(event.content);
const [mentions, card, repliesCount, reblogsCount, favouritesCount, [repostEvent], [reactionEvent]] = await Promise
.all([
Promise.all(mentionedPubkeys.map(toMention)),
firstUrl ? unfurlCardCached(firstUrl) : null,
eventsDB.countFilters([{ kinds: [1], '#e': [event.id] }]),
eventsDB.countFilters([{ kinds: [6], '#e': [event.id] }]),
eventsDB.countFilters([{ kinds: [7], '#e': [event.id] }]),
viewerPubkey
? eventsDB.getFilters([{ kinds: [6], '#e': [event.id], authors: [viewerPubkey] }], { limit: 1 })
: [],
viewerPubkey
? eventsDB.getFilters([{ kinds: [7], '#e': [event.id], authors: [viewerPubkey] }], { limit: 1 })
: [],
]);
const content = buildInlineRecipients(mentions) + html;
const cw = event.tags.find(isCWTag);
const subject = event.tags.find((tag) => tag[0] === 'subject');
const mediaLinks = getMediaLinks(links);
const mediaTags: DittoAttachment[] = event.tags
.filter((tag) => tag[0] === 'media')
.map(([_, url, json]) => ({ url, data: jsonMediaDataSchema.parse(json) }));
const media = [...mediaLinks, ...mediaTags];
return {
id: event.id,
account,
card,
content,
created_at: nostrDate(event.created_at).toISOString(),
in_reply_to_id: replyTag ? replyTag[1] : null,
in_reply_to_account_id: null,
sensitive: !!cw,
spoiler_text: (cw ? cw[1] : subject?.[1]) || '',
visibility: 'public',
language: event.tags.find((tag) => tag[0] === 'lang')?.[1] || null,
replies_count: repliesCount,
reblogs_count: reblogsCount,
favourites_count: favouritesCount,
favourited: reactionEvent?.content === '+',
reblogged: Boolean(repostEvent),
muted: false,
bookmarked: false,
reblog: null,
application: null,
media_attachments: media.map(renderAttachment),
mentions,
tags: [],
emojis: renderEmojis(event),
poll: null,
uri: Conf.local(`/posts/${event.id}`),
url: Conf.local(`/posts/${event.id}`),
};
}
async function toMention(pubkey: string) {
const profile = await getAuthor(pubkey);
const account = profile ? await renderAccount(profile) : 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}`),
};
}
}
type Mention = Awaited<ReturnType<typeof toMention>>;
function buildInlineRecipients(mentions: Mention[]): string {
if (!mentions.length) return '';
const elements = mentions.reduce<string[]>((acc, { url, username }) => {
const name = nip19.BECH32_REGEX.test(username) ? username.substring(0, 8) : username;
acc.push(`<a href="${url}" class="u-url mention" rel="ugc">@<span>${name}</span></a>`);
return acc;
}, []);
return `<span class="recipients-inline">${elements.join(' ')} </span>`;
}
export { renderStatus };

View File

@ -1,127 +1,8 @@
import { isCWTag } from 'https://gitlab.com/soapbox-pub/mostr/-/raw/c67064aee5ade5e01597c6d23e22e53c628ef0e2/src/nostr/tags.ts'; import { type Event } from '@/deps.ts';
import { getFollows } from '@/queries.ts';
import { Conf } from '@/config.ts';
import * as eventsDB from '@/db/events.ts';
import { type Event, findReplyTag, nip19 } from '@/deps.ts';
import { getMediaLinks, parseNoteContent } from '@/note.ts';
import { getAuthor, getFollows } from '@/queries.ts';
import { jsonMediaDataSchema } from '@/schemas/nostr.ts';
import { isFollowing, nostrDate } from '@/utils.ts'; import { isFollowing, nostrDate } from '@/utils.ts';
import { unfurlCardCached } from '@/utils/unfurl.ts'; import { accountFromPubkey } from '@/views/mastodon/accounts.ts';
import { accountFromPubkey, renderAccount } from '@/views/mastodon/accounts.ts'; import { renderStatus } from '@/views/mastodon/statuses.ts';
import { DittoAttachment, renderAttachment } from '@/views/mastodon/attachments.ts';
import { renderEmojis } from '@/views/mastodon/emojis.ts';
async function toMention(pubkey: string) {
const profile = await getAuthor(pubkey);
const account = profile ? await renderAccount(profile) : 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 toStatus(event: Event<1>, viewerPubkey?: string) {
const profile = await getAuthor(event.pubkey);
const account = profile ? await renderAccount(profile) : await accountFromPubkey(event.pubkey);
const replyTag = findReplyTag(event);
const mentionedPubkeys = [
...new Set(
event.tags
.filter((tag) => tag[0] === 'p')
.map((tag) => tag[1]),
),
];
const { html, links, firstUrl } = parseNoteContent(event.content);
const [mentions, card, repliesCount, reblogsCount, favouritesCount, [repostEvent], [reactionEvent]] = await Promise
.all([
Promise.all(mentionedPubkeys.map(toMention)),
firstUrl ? unfurlCardCached(firstUrl) : null,
eventsDB.countFilters([{ kinds: [1], '#e': [event.id] }]),
eventsDB.countFilters([{ kinds: [6], '#e': [event.id] }]),
eventsDB.countFilters([{ kinds: [7], '#e': [event.id] }]),
viewerPubkey
? eventsDB.getFilters([{ kinds: [6], '#e': [event.id], authors: [viewerPubkey] }], { limit: 1 })
: [],
viewerPubkey
? eventsDB.getFilters([{ kinds: [7], '#e': [event.id], authors: [viewerPubkey] }], { limit: 1 })
: [],
]);
const content = buildInlineRecipients(mentions) + html;
const cw = event.tags.find(isCWTag);
const subject = event.tags.find((tag) => tag[0] === 'subject');
const mediaLinks = getMediaLinks(links);
const mediaTags: DittoAttachment[] = event.tags
.filter((tag) => tag[0] === 'media')
.map(([_, url, json]) => ({ url, data: jsonMediaDataSchema.parse(json) }));
const media = [...mediaLinks, ...mediaTags];
return {
id: event.id,
account,
card,
content,
created_at: nostrDate(event.created_at).toISOString(),
in_reply_to_id: replyTag ? replyTag[1] : null,
in_reply_to_account_id: null,
sensitive: !!cw,
spoiler_text: (cw ? cw[1] : subject?.[1]) || '',
visibility: 'public',
language: event.tags.find((tag) => tag[0] === 'lang')?.[1] || null,
replies_count: repliesCount,
reblogs_count: reblogsCount,
favourites_count: favouritesCount,
favourited: reactionEvent?.content === '+',
reblogged: Boolean(repostEvent),
muted: false,
bookmarked: false,
reblog: null,
application: null,
media_attachments: media.map(renderAttachment),
mentions,
tags: [],
emojis: renderEmojis(event),
poll: null,
uri: Conf.local(`/posts/${event.id}`),
url: Conf.local(`/posts/${event.id}`),
};
}
type Mention = Awaited<ReturnType<typeof toMention>>;
function buildInlineRecipients(mentions: Mention[]): string {
if (!mentions.length) return '';
const elements = mentions.reduce<string[]>((acc, { url, username }) => {
const name = nip19.BECH32_REGEX.test(username) ? username.substring(0, 8) : username;
acc.push(`<a href="${url}" class="u-url mention" rel="ugc">@<span>${name}</span></a>`);
return acc;
}, []);
return `<span class="recipients-inline">${elements.join(' ')} </span>`;
}
async function toRelationship(sourcePubkey: string, targetPubkey: string) { async function toRelationship(sourcePubkey: string, targetPubkey: string) {
const [source, target] = await Promise.all([ const [source, target] = await Promise.all([
@ -153,7 +34,7 @@ function toNotification(event: Event, viewerPubkey?: string) {
} }
async function toNotificationMention(event: Event<1>, viewerPubkey?: string) { async function toNotificationMention(event: Event<1>, viewerPubkey?: string) {
const status = await toStatus(event, viewerPubkey); const status = await renderStatus(event, viewerPubkey);
if (!status) return; if (!status) return;
return { return {
@ -165,4 +46,4 @@ async function toNotificationMention(event: Event<1>, viewerPubkey?: string) {
}; };
} }
export { accountFromPubkey, toNotification, toRelationship, toStatus }; export { accountFromPubkey, toNotification, toRelationship };