Notifications: render notifications for kinds 1, 6, and 7 events
This commit is contained in:
parent
3d70edfb39
commit
220f16feb8
|
@ -1,20 +1,40 @@
|
||||||
import { type AppController } from '@/app.ts';
|
import { NostrFilter } from '@nostrify/nostrify';
|
||||||
import { Storages } from '@/storages.ts';
|
|
||||||
|
import { AppContext, AppController } from '@/app.ts';
|
||||||
|
import { hydrateEvents } from '@/storages/hydrate.ts';
|
||||||
import { paginated, paginationSchema } from '@/utils/api.ts';
|
import { paginated, paginationSchema } from '@/utils/api.ts';
|
||||||
import { renderNotification } from '@/views/mastodon/notifications.ts';
|
import { renderNotification } from '@/views/mastodon/notifications.ts';
|
||||||
|
|
||||||
const notificationsController: AppController = async (c) => {
|
const notificationsController: AppController = (c) => {
|
||||||
const pubkey = c.get('pubkey')!;
|
const pubkey = c.get('pubkey')!;
|
||||||
const { since, until } = paginationSchema.parse(c.req.query());
|
const { since, until } = paginationSchema.parse(c.req.query());
|
||||||
const { signal } = c.req.raw;
|
|
||||||
|
|
||||||
const events = await Storages.db.query(
|
return renderNotifications(c, [{ kinds: [1, 6, 7], '#p': [pubkey], since, until }]);
|
||||||
[{ kinds: [1], '#p': [pubkey], since, until }],
|
|
||||||
{ signal },
|
|
||||||
);
|
|
||||||
|
|
||||||
const statuses = await Promise.all(events.map((event) => renderNotification(event, pubkey)));
|
|
||||||
return paginated(c, events, statuses);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
async function renderNotifications(c: AppContext, filters: NostrFilter[]) {
|
||||||
|
const store = c.get('store');
|
||||||
|
const pubkey = c.get('pubkey')!;
|
||||||
|
const { signal } = c.req.raw;
|
||||||
|
|
||||||
|
const events = await store
|
||||||
|
.query(filters, { signal })
|
||||||
|
.then((events) => events.filter((event) => event.pubkey !== pubkey))
|
||||||
|
.then((events) => hydrateEvents({ events, storage: store, signal }));
|
||||||
|
|
||||||
|
if (!events.length) {
|
||||||
|
return c.json([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const notifications = (await Promise
|
||||||
|
.all(events.map((event) => renderNotification(event, { viewerPubkey: pubkey }))))
|
||||||
|
.filter(Boolean);
|
||||||
|
|
||||||
|
if (!notifications.length) {
|
||||||
|
return c.json([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return paginated(c, events, notifications);
|
||||||
|
}
|
||||||
|
|
||||||
export { notificationsController };
|
export { notificationsController };
|
||||||
|
|
|
@ -24,4 +24,5 @@ export interface DittoEvent extends NostrEvent {
|
||||||
user?: DittoEvent;
|
user?: DittoEvent;
|
||||||
repost?: DittoEvent;
|
repost?: DittoEvent;
|
||||||
quote_repost?: DittoEvent;
|
quote_repost?: DittoEvent;
|
||||||
|
reacted?: DittoEvent;
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,10 @@ async function hydrateEvents(opts: HydrateOpts): Promise<DittoEvent[]> {
|
||||||
cache.push(event);
|
cache.push(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const event of await gatherReacted({ events: cache, storage, signal })) {
|
||||||
|
cache.push(event);
|
||||||
|
}
|
||||||
|
|
||||||
for (const event of await gatherQuotes({ events: cache, storage, signal })) {
|
for (const event of await gatherQuotes({ events: cache, storage, signal })) {
|
||||||
cache.push(event);
|
cache.push(event);
|
||||||
}
|
}
|
||||||
|
@ -105,6 +109,25 @@ function gatherReposts({ events, storage, signal }: HydrateOpts): Promise<DittoE
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Collect events being reacted to by the events. */
|
||||||
|
function gatherReacted({ events, storage, signal }: HydrateOpts): Promise<DittoEvent[]> {
|
||||||
|
const ids = new Set<string>();
|
||||||
|
|
||||||
|
for (const event of events) {
|
||||||
|
if (event.kind === 7) {
|
||||||
|
const id = event.tags.find(([name]) => name === 'e')?.[1];
|
||||||
|
if (id) {
|
||||||
|
ids.add(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return storage.query(
|
||||||
|
[{ ids: [...ids], limit: ids.size }],
|
||||||
|
{ signal },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/** Collect quotes from the events. */
|
/** Collect quotes from the events. */
|
||||||
function gatherQuotes({ events, storage, signal }: HydrateOpts): Promise<DittoEvent[]> {
|
function gatherQuotes({ events, storage, signal }: HydrateOpts): Promise<DittoEvent[]> {
|
||||||
const ids = new Set<string>();
|
const ids = new Set<string>();
|
||||||
|
|
|
@ -1,19 +1,34 @@
|
||||||
import { NostrEvent } from '@nostrify/nostrify';
|
import { DittoEvent } from '@/interfaces/DittoEvent.ts';
|
||||||
import { getAuthor } from '@/queries.ts';
|
|
||||||
import { nostrDate } from '@/utils.ts';
|
import { nostrDate } from '@/utils.ts';
|
||||||
import { accountFromPubkey } from '@/views/mastodon/accounts.ts';
|
import { accountFromPubkey, renderAccount } from '@/views/mastodon/accounts.ts';
|
||||||
import { renderStatus } from '@/views/mastodon/statuses.ts';
|
import { renderStatus } from '@/views/mastodon/statuses.ts';
|
||||||
|
|
||||||
function renderNotification(event: NostrEvent, viewerPubkey?: string) {
|
interface RenderNotificationOpts {
|
||||||
switch (event.kind) {
|
viewerPubkey: string;
|
||||||
case 1:
|
}
|
||||||
return renderNotificationMention(event, viewerPubkey);
|
|
||||||
|
function renderNotification(event: DittoEvent, opts: RenderNotificationOpts) {
|
||||||
|
const mentioned = !!event.tags.find(([name, value]) => name === 'p' && value === opts.viewerPubkey);
|
||||||
|
|
||||||
|
if (event.kind === 1 && mentioned) {
|
||||||
|
return renderMention(event, opts);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.kind === 6) {
|
||||||
|
return renderReblog(event, opts);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.kind === 7 && event.content === '+') {
|
||||||
|
return renderFavourite(event, opts);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.kind === 7) {
|
||||||
|
return renderReaction(event, opts);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function renderNotificationMention(event: NostrEvent, viewerPubkey?: string) {
|
async function renderMention(event: DittoEvent, opts: RenderNotificationOpts) {
|
||||||
const author = await getAuthor(event.pubkey);
|
const status = await renderStatus(event, opts);
|
||||||
const status = await renderStatus({ ...event, author }, { viewerPubkey: viewerPubkey });
|
|
||||||
if (!status) return;
|
if (!status) return;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -25,4 +40,50 @@ async function renderNotificationMention(event: NostrEvent, viewerPubkey?: strin
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export { accountFromPubkey, renderNotification };
|
async function renderReblog(event: DittoEvent, opts: RenderNotificationOpts) {
|
||||||
|
if (event.repost?.kind !== 1) return;
|
||||||
|
const status = await renderStatus(event.repost, opts);
|
||||||
|
if (!status) return;
|
||||||
|
const account = event.author ? await renderAccount(event.author) : accountFromPubkey(event.pubkey);
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: event.id,
|
||||||
|
type: 'reblog',
|
||||||
|
created_at: nostrDate(event.created_at).toISOString(),
|
||||||
|
account,
|
||||||
|
status,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function renderFavourite(event: DittoEvent, opts: RenderNotificationOpts) {
|
||||||
|
if (event.reacted?.kind !== 1) return;
|
||||||
|
const status = await renderStatus(event.reacted, opts);
|
||||||
|
if (!status) return;
|
||||||
|
const account = event.author ? await renderAccount(event.author) : accountFromPubkey(event.pubkey);
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: event.id,
|
||||||
|
type: 'favourite',
|
||||||
|
created_at: nostrDate(event.created_at).toISOString(),
|
||||||
|
account,
|
||||||
|
status,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function renderReaction(event: DittoEvent, opts: RenderNotificationOpts) {
|
||||||
|
if (event.reacted?.kind !== 1) return;
|
||||||
|
const status = await renderStatus(event.reacted, opts);
|
||||||
|
if (!status) return;
|
||||||
|
const account = event.author ? await renderAccount(event.author) : accountFromPubkey(event.pubkey);
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: event.id,
|
||||||
|
type: 'pleroma:emoji_reaction',
|
||||||
|
emoji: event.content,
|
||||||
|
created_at: nostrDate(event.created_at).toISOString(),
|
||||||
|
account,
|
||||||
|
status,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export { renderNotification };
|
||||||
|
|
|
@ -14,12 +14,12 @@ import { DittoAttachment, renderAttachment } from '@/views/mastodon/attachments.
|
||||||
import { renderEmojis } from '@/views/mastodon/emojis.ts';
|
import { renderEmojis } from '@/views/mastodon/emojis.ts';
|
||||||
import { mediaDataSchema } from '@/schemas/nostr.ts';
|
import { mediaDataSchema } from '@/schemas/nostr.ts';
|
||||||
|
|
||||||
interface statusOpts {
|
interface RenderStatusOpts {
|
||||||
viewerPubkey?: string;
|
viewerPubkey?: string;
|
||||||
depth?: number;
|
depth?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function renderStatus(event: DittoEvent, opts: statusOpts): Promise<any> {
|
async function renderStatus(event: DittoEvent, opts: RenderStatusOpts): Promise<any> {
|
||||||
const { viewerPubkey, depth = 1 } = opts;
|
const { viewerPubkey, depth = 1 } = opts;
|
||||||
|
|
||||||
if (depth > 2 || depth < 0) return null;
|
if (depth > 2 || depth < 0) return null;
|
||||||
|
@ -117,7 +117,7 @@ async function renderStatus(event: DittoEvent, opts: statusOpts): Promise<any> {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async function renderReblog(event: DittoEvent, opts: statusOpts) {
|
async function renderReblog(event: DittoEvent, opts: RenderStatusOpts) {
|
||||||
const { viewerPubkey } = opts;
|
const { viewerPubkey } = opts;
|
||||||
|
|
||||||
if (!event.author) return;
|
if (!event.author) return;
|
||||||
|
|
Loading…
Reference in New Issue