feat: add quote repost

This commit is contained in:
P. Reis 2024-04-15 17:10:25 -03:00
parent b54d044550
commit 6248272243
10 changed files with 84 additions and 22 deletions

View File

@ -163,7 +163,7 @@ const accountStatusesController: AppController = async (c) => {
return events; return events;
}); });
const statuses = await Promise.all(events.map((event) => renderStatus(event, c.get('pubkey')))); const statuses = await Promise.all(events.map((event) => renderStatus(event, { viewerPubkey: c.get('pubkey') })));
return paginated(c, events, statuses); return paginated(c, events, statuses);
}; };
@ -310,7 +310,7 @@ const favouritesController: AppController = async (c) => {
hydrateEvents({ events, relations: ['author', 'event_stats', 'author_stats'], storage: eventsDB, signal }) hydrateEvents({ events, relations: ['author', 'event_stats', 'author_stats'], storage: eventsDB, signal })
); );
const statuses = await Promise.all(events1.map((event) => renderStatus(event, c.get('pubkey')))); const statuses = await Promise.all(events1.map((event) => renderStatus(event, { viewerPubkey: c.get('pubkey') })));
return paginated(c, events1, statuses); return paginated(c, events1, statuses);
}; };

View File

@ -52,7 +52,7 @@ const searchController: AppController = async (c) => {
Promise.all( Promise.all(
results results
.filter((event) => event.kind === 1) .filter((event) => event.kind === 1)
.map((event) => renderStatus(event, c.get('pubkey'))), .map((event) => renderStatus(event, { viewerPubkey: c.get('pubkey') })),
), ),
]); ]);

View File

@ -40,12 +40,12 @@ const statusController: AppController = async (c) => {
const event = await getEvent(id, { const event = await getEvent(id, {
kind: 1, kind: 1,
relations: ['author', 'event_stats', 'author_stats'], relations: ['author', 'event_stats', 'author_stats', 'quote_repost'],
signal: AbortSignal.timeout(1500), signal: AbortSignal.timeout(1500),
}); });
if (event) { if (event) {
return c.json(await renderStatus(event, c.get('pubkey'))); return c.json(await renderStatus(event, { viewerPubkey: c.get('pubkey') }));
} }
return c.json({ error: 'Event not found.' }, 404); return c.json({ error: 'Event not found.' }, 404);
@ -130,7 +130,7 @@ const createStatusController: AppController = async (c) => {
}, c); }, c);
const author = await getAuthor(event.pubkey); const author = await getAuthor(event.pubkey);
return c.json(await renderStatus({ ...event, author }, c.get('pubkey'))); return c.json(await renderStatus({ ...event, author }, { viewerPubkey: c.get('pubkey') }));
}; };
const deleteStatusController: AppController = async (c) => { const deleteStatusController: AppController = async (c) => {
@ -147,7 +147,7 @@ const deleteStatusController: AppController = async (c) => {
}, c); }, c);
const author = await getAuthor(event.pubkey); const author = await getAuthor(event.pubkey);
return c.json(await renderStatus({ ...event, author }, pubkey)); return c.json(await renderStatus({ ...event, author }, { viewerPubkey: pubkey }));
} else { } else {
return c.json({ error: 'Unauthorized' }, 403); return c.json({ error: 'Unauthorized' }, 403);
} }
@ -161,7 +161,7 @@ const contextController: AppController = async (c) => {
const event = await getEvent(id, { kind: 1, relations: ['author', 'event_stats', 'author_stats'] }); const event = await getEvent(id, { kind: 1, relations: ['author', 'event_stats', 'author_stats'] });
async function renderStatuses(events: NostrEvent[]) { async function renderStatuses(events: NostrEvent[]) {
const statuses = await Promise.all(events.map((event) => renderStatus(event, c.get('pubkey')))); const statuses = await Promise.all(events.map((event) => renderStatus(event, { viewerPubkey: c.get('pubkey') })));
return statuses.filter(Boolean); return statuses.filter(Boolean);
} }
@ -191,7 +191,7 @@ const favouriteController: AppController = async (c) => {
], ],
}, c); }, c);
const status = await renderStatus(target, c.get('pubkey')); const status = await renderStatus(target, { viewerPubkey: c.get('pubkey') });
if (status) { if (status) {
status.favourited = true; status.favourited = true;
@ -259,7 +259,7 @@ const unreblogStatusController: AppController = async (c) => {
tags: [['e', repostedEvent.id]], tags: [['e', repostedEvent.id]],
}, c); }, c);
return c.json(await renderStatus(event)); return c.json(await renderStatus(event, {}));
}; };
const rebloggedByController: AppController = (c) => { const rebloggedByController: AppController = (c) => {
@ -285,7 +285,7 @@ const bookmarkController: AppController = async (c) => {
c, c,
); );
const status = await renderStatus(event, pubkey); const status = await renderStatus(event, { viewerPubkey: pubkey });
if (status) { if (status) {
status.bookmarked = true; status.bookmarked = true;
} }
@ -312,7 +312,7 @@ const unbookmarkController: AppController = async (c) => {
c, c,
); );
const status = await renderStatus(event, pubkey); const status = await renderStatus(event, { viewerPubkey: pubkey });
if (status) { if (status) {
status.bookmarked = false; status.bookmarked = false;
} }
@ -339,7 +339,7 @@ const pinController: AppController = async (c) => {
c, c,
); );
const status = await renderStatus(event, pubkey); const status = await renderStatus(event, { viewerPubkey: pubkey });
if (status) { if (status) {
status.pinned = true; status.pinned = true;
} }
@ -368,7 +368,7 @@ const unpinController: AppController = async (c) => {
c, c,
); );
const status = await renderStatus(event, pubkey); const status = await renderStatus(event, { viewerPubkey: pubkey });
if (status) { if (status) {
status.pinned = false; status.pinned = false;
} }
@ -411,7 +411,7 @@ const zapController: AppController = async (c) => {
], ],
}, c); }, c);
const status = await renderStatus(target, c.get('pubkey')); const status = await renderStatus(target, { viewerPubkey: c.get('pubkey') });
status.zapped = true; status.zapped = true;
return c.json(status); return c.json(status);

View File

@ -79,7 +79,7 @@ const streamingController: AppController = (c) => {
} }
continue; continue;
} }
const status = await renderStatus(event, pubkey); const status = await renderStatus(event, { viewerPubkey: pubkey });
if (status) { if (status) {
send('update', status); send('update', status);
} }

View File

@ -51,7 +51,7 @@ async function renderStatuses(c: AppContext, filters: NostrFilter[]) {
.then((events) => .then((events) =>
hydrateEvents({ hydrateEvents({
events, events,
relations: ['author', 'author_stats', 'event_stats', 'repost'], relations: ['author', 'author_stats', 'event_stats', 'repost', 'quote_repost'],
storage: eventsDB, storage: eventsDB,
signal, signal,
}) })
@ -65,7 +65,7 @@ async function renderStatuses(c: AppContext, filters: NostrFilter[]) {
if (event.kind === 6) { if (event.kind === 6) {
return renderReblog(event); return renderReblog(event);
} }
return renderStatus(event, c.get('pubkey')); return renderStatus(event, { viewerPubkey: c.get('pubkey') });
}))).filter((boolean) => boolean); }))).filter((boolean) => boolean);
if (!statuses.length) { if (!statuses.length) {

View File

@ -23,4 +23,5 @@ export interface DittoEvent extends NostrEvent {
d_author?: DittoEvent; d_author?: DittoEvent;
user?: DittoEvent; user?: DittoEvent;
repost?: NostrEvent; repost?: NostrEvent;
quote_repost?: NostrEvent;
} }

View File

@ -36,6 +36,9 @@ async function hydrateEvents(opts: HydrateEventOpts): Promise<DittoEvent[]> {
case 'repost': case 'repost':
await hydrateRepostEvents({ events, storage, signal }); await hydrateRepostEvents({ events, storage, signal });
break; break;
case 'quote_repost':
await hydrateQuoteRepostEvents({ events, storage, signal });
break;
} }
} }
@ -144,6 +147,51 @@ async function hydrateRepostEvents(opts: Omit<HydrateEventOpts, 'relations'>): P
return events; return events;
} }
async function hydrateQuoteRepostEvents(opts: Omit<HydrateEventOpts, 'relations'>): Promise<DittoEvent[]> {
const { events, storage, signal } = opts;
const results = await storage.query([{
kinds: [1],
ids: events.map((event) => {
if (event.kind === 1) {
const originalPostId = event.tags.find(([name]) => name === 'q')?.[1];
if (!originalPostId) return event.id;
else return originalPostId;
}
return event.id;
}),
}], { signal });
for (const event of events) {
if (event.kind === 1) {
const originalPostId = event.tags.find(([name]) => name === 'q')?.[1];
if (!originalPostId) continue;
const originalPostEvent = events.find((event) => event.id === originalPostId);
if (!originalPostEvent) {
const originalPostEvent = results.find((event) => event.id === originalPostId);
if (!originalPostEvent) continue;
await hydrateEvents({ events: [originalPostEvent], storage: storage, signal: signal, relations: ['author'] });
event.quote_repost = originalPostEvent;
continue;
}
if (!originalPostEvent.author) {
await hydrateEvents({ events: [originalPostEvent], storage: storage, signal: signal, relations: ['author'] });
event.quote_repost = originalPostEvent;
continue;
}
event.quote_repost = originalPostEvent;
}
}
return events;
}
/** Return a normalized event without any non-standard keys. */ /** Return a normalized event without any non-standard keys. */
function purifyEvent(event: NostrEvent): NostrEvent { function purifyEvent(event: NostrEvent): NostrEvent {
return { return {

View File

@ -62,7 +62,7 @@ async function renderStatuses(c: AppContext, ids: string[], signal = AbortSignal
const sortedEvents = [...events].sort((a, b) => ids.indexOf(a.id) - ids.indexOf(b.id)); const sortedEvents = [...events].sort((a, b) => ids.indexOf(a.id) - ids.indexOf(b.id));
const statuses = await Promise.all( const statuses = await Promise.all(
sortedEvents.map((event) => renderStatus(event, c.get('pubkey'))), sortedEvents.map((event) => renderStatus(event, { viewerPubkey: c.get('pubkey') })),
); );
// TODO: pagination with min_id and max_id based on the order of `ids`. // TODO: pagination with min_id and max_id based on the order of `ids`.

View File

@ -13,7 +13,7 @@ function renderNotification(event: NostrEvent, viewerPubkey?: string) {
async function renderNotificationMention(event: NostrEvent, viewerPubkey?: string) { async function renderNotificationMention(event: NostrEvent, viewerPubkey?: string) {
const author = await getAuthor(event.pubkey); const author = await getAuthor(event.pubkey);
const status = await renderStatus({ ...event, author }, viewerPubkey); const status = await renderStatus({ ...event, author }, { viewerPubkey: viewerPubkey });
if (!status) return; if (!status) return;
return { return {

View File

@ -14,7 +14,16 @@ import { accountFromPubkey, renderAccount } from '@/views/mastodon/accounts.ts';
import { DittoAttachment, renderAttachment } from '@/views/mastodon/attachments.ts'; import { DittoAttachment, renderAttachment } from '@/views/mastodon/attachments.ts';
import { renderEmojis } from '@/views/mastodon/emojis.ts'; import { renderEmojis } from '@/views/mastodon/emojis.ts';
async function renderStatus(event: DittoEvent, viewerPubkey?: string) { interface statusOpts {
viewerPubkey?: string;
depth?: number;
}
async function renderStatus(event: DittoEvent, opts: statusOpts): Promise<any> {
const { viewerPubkey, depth = 1 } = opts;
if (depth > 2 || depth < 0) return null;
const note = nip19.noteEncode(event.id); const note = nip19.noteEncode(event.id);
const account = event.author const account = event.author
@ -67,6 +76,8 @@ async function renderStatus(event: DittoEvent, viewerPubkey?: string) {
const media = [...mediaLinks, ...mediaTags]; const media = [...mediaLinks, ...mediaTags];
const quoteStatus = !event.quote_repost ? null : await renderStatus(event.quote_repost, { depth: depth + 1 });
return { return {
id: event.id, id: event.id,
account, account,
@ -94,6 +105,8 @@ async function renderStatus(event: DittoEvent, viewerPubkey?: string) {
tags: [], tags: [],
emojis: renderEmojis(event), emojis: renderEmojis(event),
poll: null, poll: null,
quote: quoteStatus,
quote_id: quoteStatus ? quoteStatus.id : null,
uri: Conf.external(note), uri: Conf.external(note),
url: Conf.external(note), url: Conf.external(note),
zapped: Boolean(zapEvent), zapped: Boolean(zapEvent),
@ -108,7 +121,7 @@ async function renderReblog(event: DittoEvent) {
if (!event.repost) return; if (!event.repost) return;
const reblog = await renderStatus(event.repost); const reblog = await renderStatus(event.repost, {});
reblog.reblogged = true; reblog.reblogged = true;
return { return {