mirror of
https://gitlab.com/soapbox-pub/ditto.git
synced 2025-12-06 11:29:46 +00:00
Merge branch 'zap-notification-streaming' into 'main'
feat: zap notification in streaming Closes #204 See merge request soapbox-pub/ditto!490
This commit is contained in:
commit
3ff3ba81b7
10 changed files with 141 additions and 59 deletions
1
fixtures/events/kind-0-jack.json
Normal file
1
fixtures/events/kind-0-jack.json
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
{"kind":0,"id":"f7b1a3ca3fa77bffded2024568da939e8cd3ed2403004e1ecb56d556f299ad2a","pubkey":"82341f882b6eabcd2ba7f1ef90aad961cf074af15b9ef44a09f9d2a8fbfbe6a2","created_at":1715441226,"tags":[],"content":"{\"banner\":\"https:\\/\\/m.primal.net\\/IBZO.jpg\",\"website\":\"\",\"picture\":\"https:\\/\\/image.nostr.build\\/26867ce34e4b11f0a1d083114919a9f4eca699f3b007454c396ef48c43628315.jpg\",\"lud06\":\"\",\"display_name\":\"\",\"lud16\":\"jack@primal.net\",\"nip05\":\"\",\"name\":\"jack\",\"about\":\"bitcoin \u0026 chill\"}","sig":"9792ceb1e9c73a6c2140540ddbac4279361cae4cc41888019d9dd47d09c1e7cee55948f6e1af824fa0f856d892686352bc757ad157f766f0da656d5e80b38bc7"}
|
||||||
1
fixtures/events/kind-0-patrick.json
Normal file
1
fixtures/events/kind-0-patrick.json
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
{"kind":0,"id":"34bc588a4ff5ca8570a1ad4114485239f83c135b09636dbc16df338f73079e42","pubkey":"47259076c85f9240e852420d7213c95e95102f1de929fb60f33a2c32570c98c4","created_at":1726076335,"tags":[],"content":"{\"about\":\"Coding with nature's inspiration, embracing solitude's wisdom. Team Soapbox.\",\"bot\":false,\"lud16\":\"patrickreiis@getalby.com\",\"name\":\"patrickReiis\",\"nip05\":\"patrick@patrickdosreis.com\",\"picture\":\"https://image.nostr.build/2177817a323ed8a58d508fb25160e1c2f38f60256125b764c82c988869916e84.jpg\",\"website\":\"https://patrickdosreis.com/\",\"pubkey\":\"47259076c85f9240e852420d7213c95e95102f1de929fb60f33a2c32570c98c4\",\"npub\":\"npub1gujeqakgt7fyp6zjggxhyy7ft623qtcaay5lkc8n8gkry4cvnrzqd3f67z\",\"created_at\":1717600965}","sig":"2780887e58d6e59cc9c03cca8a583bc121d2c74d98cc434d22e65c1f56da1bb09d79fc7cc3c4ee5b829773c17d6f482b114dc951c1683c3908cedff783d785ad"}
|
||||||
1
fixtures/events/kind-1-being-zapped.json
Normal file
1
fixtures/events/kind-1-being-zapped.json
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
{"kind":1,"id":"02e52f80e2e6a3ad0993e9c4a7b4e6afc79d067c6ff9c6df3fb2896342dee2df","pubkey":"47259076c85f9240e852420d7213c95e95102f1de929fb60f33a2c32570c98c4","created_at":1724609131,"tags":[["e","677c701036eae20632a7677ee6eece0c62e259d5c72864d78a3bbe419c0d2d2c","wss://gleasonator.dev/relay","root"],["e","677c701036eae20632a7677ee6eece0c62e259d5c72864d78a3bbe419c0d2d2c","wss://gleasonator.dev/relay","reply"],["p","82341f882b6eabcd2ba7f1ef90aad961cf074af15b9ef44a09f9d2a8fbfbe6a2"]],"content":"Please I don't want to go back to the shoe factory","sig":"ce6ca329701eec5db0b182bd52c48777b9eccaac298180a6601d8c5156060d944768d71376e7d24c24cefb6619d1467f6a30e0ca574d68f748b38c784e4ced59"}
|
||||||
1
fixtures/events/kind-9735-jack-zap-patrick.json
Normal file
1
fixtures/events/kind-9735-jack-zap-patrick.json
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
{"kind":9735,"id":"a57d30d59e7442f9a2ad329400a6cbf29c2b34b1e69e4cdce8bc2fe751d9268f","pubkey":"79f00d3f5a19ec806189fcab03c1be4ff81d18ee4f653c88fac41fe03570f432","created_at":1724610766,"tags":[["p","47259076c85f9240e852420d7213c95e95102f1de929fb60f33a2c32570c98c4"],["e","02e52f80e2e6a3ad0993e9c4a7b4e6afc79d067c6ff9c6df3fb2896342dee2df"],["P","82341f882b6eabcd2ba7f1ef90aad961cf074af15b9ef44a09f9d2a8fbfbe6a2"],["bolt11","lnbc52250n1pnvk7xvpp5l776w7354zz9mh7sf3dlq8znkfjhysse9dwda9c7se7jwpglng0qhp5jp5cqy7n7wz9jlvd0aa40ws0d3e78l4ug2pzfen2m56mwg0qahrscqzzsxqyz5vqsp5v30pn2u86h3mz69wlvmu9vam9wudlnt4fv9wcxn24s6vrkj842gq9qxpqysgqw9mfxpyce3fhfue8p88exx8g6gn5ut9c2tz8awnw377dmhqymszrsjg49waxprkd6ggdzn90dwpgjwhdtx45052ukylkwvu5q05w5lspyjpg37"],["preimage","18264e7cce0b91bfd2016362e8a239591674c0f51ffa152acf5d73edac675432"],["description","{\"id\":\"092cd6341b42604b8e908f5bed45cbd60d98bff33258ab4f83f24a7fad445065\",\"pubkey\":\"82341f882b6eabcd2ba7f1ef90aad961cf074af15b9ef44a09f9d2a8fbfbe6a2\",\"created_at\":1724610762,\"kind\":9734,\"tags\":[[\"p\",\"47259076c85f9240e852420d7213c95e95102f1de929fb60f33a2c32570c98c4\"],[\"e\",\"02e52f80e2e6a3ad0993e9c4a7b4e6afc79d067c6ff9c6df3fb2896342dee2df\"],[\"amount\",\"5225000\"],[\"relays\",\"wss://relay.exit.pub\",\"wss://relay.damus.io\",\"wss://nos.lol\",\"wss://relay.mostr.pub\",\"wss://relay.primal.net\"]],\"content\":\"🫂\",\"sig\":\"84a36873000d5003c85c56996be856c598e91f66bf2cae9ee9d984892a11774310acf81eae2b40e9fbf25040b91239e840f856c44b68be2d23e4451fa6c5762a\"}"]],"content":"🫂","sig":"087adfe3c5831e2d760678b2929f35340c35662929acb8050f0956a2a95ba2917bf610f921e3d3fc0c08a123c6f721574eb80ca469fe7e33b6581e976844bfcc"}
|
||||||
|
|
@ -4,10 +4,9 @@ import { z } from 'zod';
|
||||||
import { AppContext, AppController } from '@/app.ts';
|
import { AppContext, AppController } from '@/app.ts';
|
||||||
import { Conf } from '@/config.ts';
|
import { Conf } from '@/config.ts';
|
||||||
import { DittoPagination } from '@/interfaces/DittoPagination.ts';
|
import { DittoPagination } from '@/interfaces/DittoPagination.ts';
|
||||||
import { getAmount } from '@/utils/bolt11.ts';
|
|
||||||
import { hydrateEvents } from '@/storages/hydrate.ts';
|
import { hydrateEvents } from '@/storages/hydrate.ts';
|
||||||
import { paginated } from '@/utils/api.ts';
|
import { paginated } from '@/utils/api.ts';
|
||||||
import { renderNotification, RenderNotificationOpts } from '@/views/mastodon/notifications.ts';
|
import { renderNotification } from '@/views/mastodon/notifications.ts';
|
||||||
|
|
||||||
/** Set of known notification types across backends. */
|
/** Set of known notification types across backends. */
|
||||||
const notificationTypes = new Set([
|
const notificationTypes = new Set([
|
||||||
|
|
@ -86,54 +85,17 @@ async function renderNotifications(
|
||||||
const { signal } = c.req.raw;
|
const { signal } = c.req.raw;
|
||||||
const opts = { signal, limit: params.limit, timeout: Conf.db.timeouts.timelines };
|
const opts = { signal, limit: params.limit, timeout: Conf.db.timeouts.timelines };
|
||||||
|
|
||||||
const zapsRelatedFilter: NostrFilter[] = [];
|
|
||||||
|
|
||||||
const events = await store
|
const events = await store
|
||||||
.query(filters, opts)
|
.query(filters, opts)
|
||||||
.then((events) =>
|
.then((events) => events.filter((event) => event.pubkey !== pubkey))
|
||||||
events.filter((event) => {
|
|
||||||
if (event.kind === 9735) {
|
|
||||||
const zappedEventId = event.tags.find(([name]) => name === 'e')?.[1];
|
|
||||||
if (zappedEventId) zapsRelatedFilter.push({ kinds: [1], ids: [zappedEventId] });
|
|
||||||
const zapSender = event.tags.find(([name]) => name === 'P')?.[1];
|
|
||||||
if (zapSender) zapsRelatedFilter.push({ kinds: [0], authors: [zapSender] });
|
|
||||||
}
|
|
||||||
|
|
||||||
return event.pubkey !== pubkey;
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.then((events) => hydrateEvents({ events, store, signal }));
|
.then((events) => hydrateEvents({ events, store, signal }));
|
||||||
|
|
||||||
if (!events.length) {
|
if (!events.length) {
|
||||||
return c.json([]);
|
return c.json([]);
|
||||||
}
|
}
|
||||||
|
|
||||||
const zapSendersAndPosts = await store
|
|
||||||
.query(zapsRelatedFilter, opts)
|
|
||||||
.then((events) => hydrateEvents({ events, store, signal }));
|
|
||||||
|
|
||||||
const notifications = (await Promise.all(events.map((event) => {
|
const notifications = (await Promise.all(events.map((event) => {
|
||||||
const opts: RenderNotificationOpts = { viewerPubkey: pubkey };
|
return renderNotification(event, { viewerPubkey: pubkey });
|
||||||
if (event.kind === 9735) {
|
|
||||||
const zapRequestString = event?.tags?.find(([name]) => name === 'description')?.[1];
|
|
||||||
const zapRequest = n.json().pipe(n.event()).optional().catch(undefined).parse(zapRequestString);
|
|
||||||
// By getting the pubkey from the zap request we guarantee who is the sender
|
|
||||||
// some clients don't put the P tag in the zap receipt...
|
|
||||||
const zapSender = zapRequest?.pubkey;
|
|
||||||
const zappedPost = event.tags.find(([name]) => name === 'e')?.[1];
|
|
||||||
|
|
||||||
const amountSchema = z.coerce.number().int().nonnegative().catch(0);
|
|
||||||
// amount in millisats
|
|
||||||
const amount = amountSchema.parse(getAmount(event?.tags.find(([name]) => name === 'bolt11')?.[1]));
|
|
||||||
|
|
||||||
opts['zap'] = {
|
|
||||||
zapSender: zapSendersAndPosts.find(({ pubkey, kind }) => kind === 0 && pubkey === zapSender) ?? zapSender,
|
|
||||||
zappedPost: zapSendersAndPosts.find(({ id }) => id === zappedPost),
|
|
||||||
amount,
|
|
||||||
message: zapRequest?.content,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return renderNotification(event, opts);
|
|
||||||
})))
|
})))
|
||||||
.filter((notification) => notification && types.has(notification.type));
|
.filter((notification) => notification && types.has(notification.type));
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import TTLCache from '@isaacs/ttlcache';
|
||||||
import { NostrEvent, NostrFilter } from '@nostrify/nostrify';
|
import { NostrEvent, NostrFilter } from '@nostrify/nostrify';
|
||||||
import Debug from '@soapbox/stickynotes/debug';
|
import Debug from '@soapbox/stickynotes/debug';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
@ -16,7 +17,6 @@ import { Storages } from '@/storages.ts';
|
||||||
import { bech32ToPubkey, Time } from '@/utils.ts';
|
import { bech32ToPubkey, Time } from '@/utils.ts';
|
||||||
import { renderReblog, renderStatus } from '@/views/mastodon/statuses.ts';
|
import { renderReblog, renderStatus } from '@/views/mastodon/statuses.ts';
|
||||||
import { renderNotification } from '@/views/mastodon/notifications.ts';
|
import { renderNotification } from '@/views/mastodon/notifications.ts';
|
||||||
import TTLCache from '@isaacs/ttlcache';
|
|
||||||
|
|
||||||
const debug = Debug('ditto:streaming');
|
const debug = Debug('ditto:streaming');
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -37,4 +37,10 @@ export interface DittoEvent extends NostrEvent {
|
||||||
reported_notes?: DittoEvent[];
|
reported_notes?: DittoEvent[];
|
||||||
/** Admin event relationship. */
|
/** Admin event relationship. */
|
||||||
info?: DittoEvent;
|
info?: DittoEvent;
|
||||||
|
/** Kind 1 being zapped */
|
||||||
|
zapped?: DittoEvent;
|
||||||
|
/** Kind 0 or pubkey that zapped */
|
||||||
|
zap_sender?: DittoEvent | string;
|
||||||
|
zap_amount?: number;
|
||||||
|
zap_message?: string;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -143,3 +143,37 @@ Deno.test('hydrateEvents(): report pubkey and post // kind 1984 --- WITHOUT stat
|
||||||
};
|
};
|
||||||
assertEquals(reportEvent, expectedEvent);
|
assertEquals(reportEvent, expectedEvent);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Deno.test('hydrateEvents(): zap sender, zap amount, zapped post // kind 9735 --- WITHOUT stats', async () => {
|
||||||
|
const relay = new MockRelay();
|
||||||
|
await using db = await createTestDB();
|
||||||
|
|
||||||
|
const zapSender = await eventFixture('kind-0-jack');
|
||||||
|
const zapReceipt = await eventFixture('kind-9735-jack-zap-patrick');
|
||||||
|
const zappedPost = await eventFixture('kind-1-being-zapped');
|
||||||
|
const zapReceiver = await eventFixture('kind-0-patrick');
|
||||||
|
|
||||||
|
// Save events to database
|
||||||
|
await relay.event(zapSender);
|
||||||
|
await relay.event(zapReceipt);
|
||||||
|
await relay.event(zappedPost);
|
||||||
|
await relay.event(zapReceiver);
|
||||||
|
|
||||||
|
await hydrateEvents({
|
||||||
|
events: [zapReceipt],
|
||||||
|
store: relay,
|
||||||
|
kysely: db.kysely,
|
||||||
|
});
|
||||||
|
|
||||||
|
const expectedEvent: DittoEvent = {
|
||||||
|
...zapReceipt,
|
||||||
|
zap_sender: zapSender,
|
||||||
|
zapped: {
|
||||||
|
...zappedPost,
|
||||||
|
author: zapReceiver,
|
||||||
|
},
|
||||||
|
zap_amount: 5225000, // millisats
|
||||||
|
zap_message: '🫂',
|
||||||
|
};
|
||||||
|
assertEquals(zapReceipt, expectedEvent);
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,15 @@
|
||||||
import { NStore } from '@nostrify/nostrify';
|
import { NStore } from '@nostrify/nostrify';
|
||||||
import { Kysely } from 'kysely';
|
import { Kysely } from 'kysely';
|
||||||
import { matchFilter } from 'nostr-tools';
|
import { matchFilter } from 'nostr-tools';
|
||||||
|
import { NSchema as n } from '@nostrify/nostrify';
|
||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
import { DittoTables } from '@/db/DittoTables.ts';
|
import { DittoTables } from '@/db/DittoTables.ts';
|
||||||
import { Conf } from '@/config.ts';
|
import { Conf } from '@/config.ts';
|
||||||
import { type DittoEvent } from '@/interfaces/DittoEvent.ts';
|
import { type DittoEvent } from '@/interfaces/DittoEvent.ts';
|
||||||
import { findQuoteTag } from '@/utils/tags.ts';
|
import { findQuoteTag } from '@/utils/tags.ts';
|
||||||
import { findQuoteInContent } from '@/utils/note.ts';
|
import { findQuoteInContent } from '@/utils/note.ts';
|
||||||
|
import { getAmount } from '@/utils/bolt11.ts';
|
||||||
import { Storages } from '@/storages.ts';
|
import { Storages } from '@/storages.ts';
|
||||||
|
|
||||||
interface HydrateOpts {
|
interface HydrateOpts {
|
||||||
|
|
@ -58,6 +61,14 @@ async function hydrateEvents(opts: HydrateOpts): Promise<DittoEvent[]> {
|
||||||
cache.push(event);
|
cache.push(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const event of await gatherZapped({ events: cache, store, signal })) {
|
||||||
|
cache.push(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const event of await gatherZapSender({ events: cache, store, signal })) {
|
||||||
|
cache.push(event);
|
||||||
|
}
|
||||||
|
|
||||||
const stats = {
|
const stats = {
|
||||||
authors: await gatherAuthorStats(cache, kysely as Kysely<DittoTables>),
|
authors: await gatherAuthorStats(cache, kysely as Kysely<DittoTables>),
|
||||||
events: await gatherEventStats(cache, kysely as Kysely<DittoTables>),
|
events: await gatherEventStats(cache, kysely as Kysely<DittoTables>),
|
||||||
|
|
@ -130,6 +141,29 @@ export function assembleEvents(
|
||||||
event.reported_notes = reportedEvents;
|
event.reported_notes = reportedEvents;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (event.kind === 9735) {
|
||||||
|
const amountSchema = z.coerce.number().int().nonnegative().catch(0);
|
||||||
|
// amount in millisats
|
||||||
|
const amount = amountSchema.parse(getAmount(event?.tags.find(([name]) => name === 'bolt11')?.[1]));
|
||||||
|
event.zap_amount = amount;
|
||||||
|
|
||||||
|
const id = event.tags.find(([name]) => name === 'e')?.[1];
|
||||||
|
if (id) {
|
||||||
|
event.zapped = b.find((e) => matchFilter({ kinds: [1], ids: [id] }, e));
|
||||||
|
}
|
||||||
|
|
||||||
|
const zapRequestString = event?.tags?.find(([name]) => name === 'description')?.[1];
|
||||||
|
const zapRequest = n.json().pipe(n.event()).optional().catch(undefined).parse(zapRequestString);
|
||||||
|
// By getting the pubkey from the zap request we guarantee who is the sender
|
||||||
|
// some clients don't put the P tag in the zap receipt...
|
||||||
|
const zapSender = zapRequest?.pubkey;
|
||||||
|
if (zapSender) {
|
||||||
|
event.zap_sender = b.find((e) => matchFilter({ kinds: [0], authors: [zapSender] }, e)) ?? zapSender;
|
||||||
|
}
|
||||||
|
|
||||||
|
event.zap_message = zapRequest?.content ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
event.author_stats = stats.authors.find((stats) => stats.pubkey === event.pubkey);
|
event.author_stats = stats.authors.find((stats) => stats.pubkey === event.pubkey);
|
||||||
event.event_stats = eventStats.find((stats) => stats.event_id === event.id);
|
event.event_stats = eventStats.find((stats) => stats.event_id === event.id);
|
||||||
}
|
}
|
||||||
|
|
@ -196,7 +230,13 @@ function gatherQuotes({ events, store, signal }: HydrateOpts): Promise<DittoEven
|
||||||
|
|
||||||
/** Collect authors from the events. */
|
/** Collect authors from the events. */
|
||||||
function gatherAuthors({ events, store, signal }: HydrateOpts): Promise<DittoEvent[]> {
|
function gatherAuthors({ events, store, signal }: HydrateOpts): Promise<DittoEvent[]> {
|
||||||
const pubkeys = new Set(events.map((event) => event.pubkey));
|
const pubkeys = new Set(events.map((event) => {
|
||||||
|
if (event.kind === 9735) {
|
||||||
|
const pubkey = event.tags.find(([name]) => name === 'p')?.[1];
|
||||||
|
if (pubkey) return pubkey;
|
||||||
|
}
|
||||||
|
return event.pubkey;
|
||||||
|
}));
|
||||||
|
|
||||||
return store.query(
|
return store.query(
|
||||||
[{ kinds: [0], authors: [...pubkeys], limit: pubkeys.size }],
|
[{ kinds: [0], authors: [...pubkeys], limit: pubkeys.size }],
|
||||||
|
|
@ -277,6 +317,48 @@ function gatherReportedProfiles({ events, store, signal }: HydrateOpts): Promise
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Collect events being zapped. */
|
||||||
|
function gatherZapped({ events, store, signal }: HydrateOpts): Promise<DittoEvent[]> {
|
||||||
|
const ids = new Set<string>();
|
||||||
|
|
||||||
|
for (const event of events) {
|
||||||
|
if (event.kind === 9735) {
|
||||||
|
const id = event.tags.find(([name]) => name === 'e')?.[1];
|
||||||
|
if (id) {
|
||||||
|
ids.add(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return store.query(
|
||||||
|
[{ ids: [...ids], limit: ids.size }],
|
||||||
|
{ signal },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Collect author that zapped. */
|
||||||
|
function gatherZapSender({ events, store, signal }: HydrateOpts): Promise<DittoEvent[]> {
|
||||||
|
const pubkeys = new Set<string>();
|
||||||
|
|
||||||
|
for (const event of events) {
|
||||||
|
if (event.kind === 9735) {
|
||||||
|
const zapRequestString = event?.tags?.find(([name]) => name === 'description')?.[1];
|
||||||
|
const zapRequest = n.json().pipe(n.event()).optional().catch(undefined).parse(zapRequestString);
|
||||||
|
// By getting the pubkey from the zap request we guarantee who is the sender
|
||||||
|
// some clients don't put the P tag in the zap receipt...
|
||||||
|
const zapSender = zapRequest?.pubkey;
|
||||||
|
if (zapSender) {
|
||||||
|
pubkeys.add(zapSender);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return store.query(
|
||||||
|
[{ kinds: [0], limit: pubkeys.size }],
|
||||||
|
{ signal },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/** Collect author stats from the events. */
|
/** Collect author stats from the events. */
|
||||||
async function gatherAuthorStats(
|
async function gatherAuthorStats(
|
||||||
events: DittoEvent[],
|
events: DittoEvent[],
|
||||||
|
|
|
||||||
|
|
@ -6,14 +6,8 @@ import { DittoEvent } from '@/interfaces/DittoEvent.ts';
|
||||||
import { nostrDate } from '@/utils.ts';
|
import { nostrDate } from '@/utils.ts';
|
||||||
import { renderStatus } from '@/views/mastodon/statuses.ts';
|
import { renderStatus } from '@/views/mastodon/statuses.ts';
|
||||||
|
|
||||||
export interface RenderNotificationOpts {
|
interface RenderNotificationOpts {
|
||||||
viewerPubkey: string;
|
viewerPubkey: string;
|
||||||
zap?: {
|
|
||||||
zapSender?: NostrEvent | NostrEvent['pubkey']; // kind 0 or pubkey
|
|
||||||
zappedPost?: NostrEvent;
|
|
||||||
amount?: number;
|
|
||||||
message?: string;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderNotification(event: DittoEvent, opts: RenderNotificationOpts) {
|
function renderNotification(event: DittoEvent, opts: RenderNotificationOpts) {
|
||||||
|
|
@ -120,23 +114,23 @@ async function renderNameGrant(event: DittoEvent) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function renderZap(event: DittoEvent, opts: RenderNotificationOpts) {
|
async function renderZap(event: DittoEvent, opts: RenderNotificationOpts) {
|
||||||
if (!opts.zap?.zapSender) return;
|
if (!event.zap_sender) return;
|
||||||
|
|
||||||
const { amount = 0, message = '' } = opts.zap;
|
const { zap_amount = 0, zap_message = '' } = event;
|
||||||
if (amount < 1) return;
|
if (zap_amount < 1) return;
|
||||||
|
|
||||||
const account = typeof opts.zap.zapSender !== 'string'
|
const account = typeof event.zap_sender !== 'string'
|
||||||
? await renderAccount(opts.zap.zapSender)
|
? await renderAccount(event.zap_sender)
|
||||||
: await accountFromPubkey(opts.zap.zapSender);
|
: await accountFromPubkey(event.zap_sender);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: notificationId(event),
|
id: notificationId(event),
|
||||||
type: 'ditto:zap',
|
type: 'ditto:zap',
|
||||||
amount,
|
amount: zap_amount,
|
||||||
message,
|
message: zap_message,
|
||||||
created_at: nostrDate(event.created_at).toISOString(),
|
created_at: nostrDate(event.created_at).toISOString(),
|
||||||
account,
|
account,
|
||||||
...(opts.zap?.zappedPost ? { status: await renderStatus(opts.zap?.zappedPost, opts) } : {}),
|
...(event.zapped ? { status: await renderStatus(event.zapped, opts) } : {}),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue