Support kind 20 "Picture" events (NIP-68)

This commit is contained in:
Alex Gleason 2025-01-05 11:23:18 -06:00
parent 5a32964eb5
commit 7a60b4b8d8
No known key found for this signature in database
GPG key ID: 7211D1F99744FBB7
13 changed files with 31 additions and 31 deletions

View file

@ -47,7 +47,7 @@ const importUsers = async (
if (!profilesOnly) { if (!profilesOnly) {
matched.push( matched.push(
...await conn.query( ...await conn.query(
authors.map((author) => ({ kinds: [1], authors: [author], limit: 200 })), authors.map((author) => ({ kinds: [1, 20], authors: [author], limit: 200 })),
), ),
); );
} }

View file

@ -252,7 +252,7 @@ class Conf {
} }
/** Nostr event kinds of events to listen for on the firehose. */ /** Nostr event kinds of events to listen for on the firehose. */
static get firehoseKinds(): number[] { static get firehoseKinds(): number[] {
return (Deno.env.get('FIREHOSE_KINDS') ?? '0, 1, 3, 5, 6, 7, 9735, 10002') return (Deno.env.get('FIREHOSE_KINDS') ?? '0, 1, 3, 5, 6, 7, 20, 9735, 10002')
.split(/[, ]+/g) .split(/[, ]+/g)
.map(Number); .map(Number);
} }

View file

@ -234,7 +234,7 @@ const accountStatusesController: AppController = async (c) => {
const filter: NostrFilter = { const filter: NostrFilter = {
authors: [pubkey], authors: [pubkey],
kinds: [1, 6], kinds: [1, 6, 20],
since, since,
until, until,
limit, limit,
@ -473,7 +473,7 @@ const favouritesController: AppController = async (c) => {
.map((event) => event.tags.find((tag) => tag[0] === 'e')?.[1]) .map((event) => event.tags.find((tag) => tag[0] === 'e')?.[1])
.filter((id): id is string => !!id); .filter((id): id is string => !!id);
const events1 = await store.query([{ kinds: [1], ids }], { signal }) const events1 = await store.query([{ kinds: [1, 20], ids }], { signal })
.then((events) => hydrateEvents({ events, store, signal })); .then((events) => hydrateEvents({ events, store, signal }));
const viewerPubkey = await c.get('signer')?.getPublicKey(); const viewerPubkey = await c.get('signer')?.getPublicKey();

View file

@ -260,7 +260,7 @@ export const statusZapSplitsController: AppController = async (c) => {
const id = c.req.param('id'); const id = c.req.param('id');
const { signal } = c.req.raw; const { signal } = c.req.raw;
const [event] = await store.query([{ kinds: [1], ids: [id], limit: 1 }], { signal }); const [event] = await store.query([{ kinds: [1, 20], ids: [id], limit: 1 }], { signal });
if (!event) { if (!event) {
return c.json({ error: 'Event not found' }, 404); return c.json({ error: 'Event not found' }, 404);
} }

View file

@ -20,7 +20,7 @@ const reactionController: AppController = async (c) => {
} }
const store = await Storages.db(); const store = await Storages.db();
const [event] = await store.query([{ kinds: [1], ids: [id], limit: 1 }]); const [event] = await store.query([{ kinds: [1, 20], ids: [id], limit: 1 }]);
if (!event) { if (!event) {
return c.json({ error: 'Status not found' }, 404); return c.json({ error: 'Status not found' }, 404);
@ -56,7 +56,7 @@ const deleteReactionController: AppController = async (c) => {
} }
const [event] = await store.query([ const [event] = await store.query([
{ kinds: [1], ids: [id], limit: 1 }, { kinds: [1, 20], ids: [id], limit: 1 },
]); ]);
if (!event) { if (!event) {

View file

@ -166,7 +166,7 @@ async function getLookupFilters({ q, type, resolve }: SearchQuery, signal: Abort
if (n.id().safeParse(q).success) { if (n.id().safeParse(q).success) {
const filters: NostrFilter[] = []; const filters: NostrFilter[] = [];
if (accounts) filters.push({ kinds: [0], authors: [q] }); if (accounts) filters.push({ kinds: [0], authors: [q] });
if (statuses) filters.push({ kinds: [1], ids: [q] }); if (statuses) filters.push({ kinds: [1, 20], ids: [q] });
return filters; return filters;
} }
@ -184,10 +184,10 @@ async function getLookupFilters({ q, type, resolve }: SearchQuery, signal: Abort
if (accounts) filters.push({ kinds: [0], authors: [result.data.pubkey] }); if (accounts) filters.push({ kinds: [0], authors: [result.data.pubkey] });
break; break;
case 'note': case 'note':
if (statuses) filters.push({ kinds: [1], ids: [result.data] }); if (statuses) filters.push({ kinds: [1, 20], ids: [result.data] });
break; break;
case 'nevent': case 'nevent':
if (statuses) filters.push({ kinds: [1], ids: [result.data.id] }); if (statuses) filters.push({ kinds: [1, 20], ids: [result.data.id] });
break; break;
} }
return filters; return filters;

View file

@ -397,7 +397,7 @@ const unreblogStatusController: AppController = async (c) => {
const pubkey = await c.get('signer')?.getPublicKey()!; const pubkey = await c.get('signer')?.getPublicKey()!;
const store = await Storages.db(); const store = await Storages.db();
const [event] = await store.query([{ ids: [eventId], kinds: [1] }]); const [event] = await store.query([{ ids: [eventId], kinds: [1, 20] }]);
if (!event) { if (!event) {
return c.json({ error: 'Record not found' }, 404); return c.json({ error: 'Record not found' }, 404);
} }
@ -429,13 +429,13 @@ const quotesController: AppController = async (c) => {
const params = c.get('pagination'); const params = c.get('pagination');
const store = await Storages.db(); const store = await Storages.db();
const [event] = await store.query([{ ids: [id], kinds: [1] }]); const [event] = await store.query([{ ids: [id], kinds: [1, 20] }]);
if (!event) { if (!event) {
return c.json({ error: 'Event not found.' }, 404); return c.json({ error: 'Event not found.' }, 404);
} }
const quotes = await store const quotes = await store
.query([{ kinds: [1], '#q': [event.id], ...params }]) .query([{ kinds: [1, 20], '#q': [event.id], ...params }])
.then((events) => hydrateEvents({ events, store })); .then((events) => hydrateEvents({ events, store }));
const viewerPubkey = await c.get('signer')?.getPublicKey(); const viewerPubkey = await c.get('signer')?.getPublicKey();

View file

@ -214,20 +214,20 @@ async function topicToFilter(
switch (topic) { switch (topic) {
case 'public': case 'public':
return { kinds: [1, 6] }; return { kinds: [1, 6, 20] };
case 'public:local': case 'public:local':
return { kinds: [1, 6], search: `domain:${host}` }; return { kinds: [1, 6, 20], search: `domain:${host}` };
case 'hashtag': case 'hashtag':
if (query.tag) return { kinds: [1, 6], '#t': [query.tag] }; if (query.tag) return { kinds: [1, 6, 20], '#t': [query.tag] };
break; break;
case 'hashtag:local': case 'hashtag:local':
if (query.tag) return { kinds: [1, 6], '#t': [query.tag], search: `domain:${host}` }; if (query.tag) return { kinds: [1, 6, 20], '#t': [query.tag], search: `domain:${host}` };
break; break;
case 'user': case 'user':
// HACK: this puts the user's entire contacts list into RAM, // HACK: this puts the user's entire contacts list into RAM,
// and then calls `matchFilters` over it. Refreshing the page // and then calls `matchFilters` over it. Refreshing the page
// is required after following a new user. // is required after following a new user.
return pubkey ? { kinds: [1, 6], authors: [...await getFeedPubkeys(pubkey)] } : undefined; return pubkey ? { kinds: [1, 6, 20], authors: [...await getFeedPubkeys(pubkey)] } : undefined;
} }
} }

View file

@ -14,7 +14,7 @@ const homeTimelineController: AppController = async (c) => {
const params = c.get('pagination'); const params = c.get('pagination');
const pubkey = await c.get('signer')?.getPublicKey()!; const pubkey = await c.get('signer')?.getPublicKey()!;
const authors = [...await getFeedPubkeys(pubkey)]; const authors = [...await getFeedPubkeys(pubkey)];
return renderStatuses(c, [{ authors, kinds: [1, 6], ...params }]); return renderStatuses(c, [{ authors, kinds: [1, 6, 20], ...params }]);
}; };
const publicQuerySchema = z.object({ const publicQuerySchema = z.object({
@ -33,7 +33,7 @@ const publicTimelineController: AppController = (c) => {
const { local, instance, language } = result.data; const { local, instance, language } = result.data;
const filter: NostrFilter = { kinds: [1], ...params }; const filter: NostrFilter = { kinds: [1, 20], ...params };
const search: `${string}:${string}`[] = []; const search: `${string}:${string}`[] = [];
@ -57,7 +57,7 @@ const publicTimelineController: AppController = (c) => {
const hashtagTimelineController: AppController = (c) => { const hashtagTimelineController: AppController = (c) => {
const hashtag = c.req.param('hashtag')!.toLowerCase(); const hashtag = c.req.param('hashtag')!.toLowerCase();
const params = c.get('pagination'); const params = c.get('pagination');
return renderStatuses(c, [{ kinds: [1], '#t': [hashtag], ...params }]); return renderStatuses(c, [{ kinds: [1, 20], '#t': [hashtag], ...params }]);
}; };
const suggestedTimelineController: AppController = async (c) => { const suggestedTimelineController: AppController = async (c) => {
@ -70,7 +70,7 @@ const suggestedTimelineController: AppController = async (c) => {
const authors = [...getTagSet(follows?.tags ?? [], 'p')]; const authors = [...getTagSet(follows?.tags ?? [], 'p')];
return renderStatuses(c, [{ authors, kinds: [1], ...params }]); return renderStatuses(c, [{ authors, kinds: [1, 20], ...params }]);
}; };
/** Render statuses for timelines. */ /** Render statuses for timelines. */

View file

@ -134,7 +134,7 @@ const trendingStatusesController: AppController = async (c) => {
return c.json([]); return c.json([]);
} }
const results = await store.query([{ kinds: [1], ids }]) const results = await store.query([{ kinds: [1, 20], ids }])
.then((events) => hydrateEvents({ events, store })); .then((events) => hydrateEvents({ events, store }));
// Sort events in the order they appear in the label. // Sort events in the order they appear in the label.

View file

@ -102,21 +102,21 @@ export function assembleEvents(
if (event.kind === 1) { if (event.kind === 1) {
const id = findQuoteTag(event.tags)?.[1] || findQuoteInContent(event.content); const id = findQuoteTag(event.tags)?.[1] || findQuoteInContent(event.content);
if (id) { if (id) {
event.quote = b.find((e) => matchFilter({ kinds: [1], ids: [id] }, e)); event.quote = b.find((e) => matchFilter({ kinds: [1, 20], ids: [id] }, e));
} }
} }
if (event.kind === 6) { if (event.kind === 6) {
const id = event.tags.find(([name]) => name === 'e')?.[1]; const id = event.tags.find(([name]) => name === 'e')?.[1];
if (id) { if (id) {
event.repost = b.find((e) => matchFilter({ kinds: [1], ids: [id] }, e)); event.repost = b.find((e) => matchFilter({ kinds: [1, 20], ids: [id] }, e));
} }
} }
if (event.kind === 7) { if (event.kind === 7) {
const id = event.tags.findLast(([name]) => name === 'e')?.[1]; const id = event.tags.findLast(([name]) => name === 'e')?.[1];
if (id) { if (id) {
event.reacted = b.find((e) => matchFilter({ kinds: [1], ids: [id] }, e)); event.reacted = b.find((e) => matchFilter({ kinds: [1, 20], ids: [id] }, e));
} }
} }
@ -130,7 +130,7 @@ export function assembleEvents(
const ids = event.tags.filter(([name]) => name === 'e').map(([_name, value]) => value); const ids = event.tags.filter(([name]) => name === 'e').map(([_name, value]) => value);
for (const id of ids) { for (const id of ids) {
const reported = b.find((e) => matchFilter({ kinds: [1], ids: [id] }, e)); const reported = b.find((e) => matchFilter({ kinds: [1, 20], ids: [id] }, e));
if (reported) { if (reported) {
reportedEvents.push(reported); reportedEvents.push(reported);
} }
@ -146,7 +146,7 @@ export function assembleEvents(
const id = event.tags.find(([name]) => name === 'e')?.[1]; const id = event.tags.find(([name]) => name === 'e')?.[1];
if (id) { if (id) {
event.zapped = b.find((e) => matchFilter({ kinds: [1], ids: [id] }, e)); event.zapped = b.find((e) => matchFilter({ kinds: [1, 20], ids: [id] }, e));
} }
const zapRequestString = event?.tags?.find(([name]) => name === 'description')?.[1]; const zapRequestString = event?.tags?.find(([name]) => name === 'description')?.[1];
@ -313,7 +313,7 @@ function gatherReportedNotes({ events, store, signal }: HydrateOpts): Promise<Di
} }
return store.query( return store.query(
[{ kinds: [1], ids: [...ids], limit: ids.size }], [{ kinds: [1, 20], ids: [...ids], limit: ids.size }],
{ signal }, { signal },
); );
} }

View file

@ -273,7 +273,7 @@ export async function countAuthorStats(
): Promise<DittoTables['author_stats']> { ): Promise<DittoTables['author_stats']> {
const [{ count: followers_count }, { count: notes_count }, [followList], [kind0]] = await Promise.all([ const [{ count: followers_count }, { count: notes_count }, [followList], [kind0]] = await Promise.all([
store.count([{ kinds: [3], '#p': [pubkey] }]), store.count([{ kinds: [3], '#p': [pubkey] }]),
store.count([{ kinds: [1], authors: [pubkey] }]), store.count([{ kinds: [1, 20], authors: [pubkey] }]),
store.query([{ kinds: [3], authors: [pubkey], limit: 1 }]), store.query([{ kinds: [3], authors: [pubkey], limit: 1 }]),
store.query([{ kinds: [0], authors: [pubkey], limit: 1 }]), store.query([{ kinds: [0], authors: [pubkey], limit: 1 }]),
]); ]);

View file

@ -75,7 +75,7 @@ async function renderStatuses(c: AppContext, ids: string[], signal = AbortSignal
const store = await Storages.db(); const store = await Storages.db();
const { limit } = c.get('pagination'); const { limit } = c.get('pagination');
const events = await store.query([{ kinds: [1], ids, limit }], { signal }) const events = await store.query([{ kinds: [1, 20], ids, limit }], { signal })
.then((events) => hydrateEvents({ events, store, signal })); .then((events) => hydrateEvents({ events, store, signal }));
if (!events.length) { if (!events.length) {