2023-08-26 22:31:52 +00:00
|
|
|
import { Conf } from '@/config.ts';
|
2023-12-28 04:49:35 +00:00
|
|
|
import { type Event, type Filter, matchFilters, stringifyStable, z } from '@/deps.ts';
|
|
|
|
import { nostrIdSchema } from '@/schemas/nostr.ts';
|
|
|
|
import { type EventData } from '@/types.ts';
|
2023-08-25 18:38:21 +00:00
|
|
|
|
2023-12-06 04:06:27 +00:00
|
|
|
/** Additional properties that may be added by Ditto to events. */
|
2023-12-10 21:54:31 +00:00
|
|
|
type Relation = 'author' | 'author_stats' | 'event_stats';
|
2023-12-06 04:06:27 +00:00
|
|
|
|
2023-08-25 18:38:21 +00:00
|
|
|
/** Custom filter interface that extends Nostr filters with extra options for Ditto. */
|
|
|
|
interface DittoFilter<K extends number = number> extends Filter<K> {
|
2023-12-06 04:06:27 +00:00
|
|
|
/** Whether the event was authored by a local user. */
|
2023-08-25 18:38:21 +00:00
|
|
|
local?: boolean;
|
2023-12-06 04:06:27 +00:00
|
|
|
/** Additional fields to add to the returned event. */
|
|
|
|
relations?: Relation[];
|
2023-08-25 18:38:21 +00:00
|
|
|
}
|
|
|
|
|
2023-12-22 01:10:42 +00:00
|
|
|
/** Filter to get one specific event. */
|
|
|
|
type MicroFilter = { ids: [Event['id']] } | { kinds: [0]; authors: [Event['pubkey']] };
|
|
|
|
|
2023-08-25 18:38:21 +00:00
|
|
|
/** Additional options to apply to the whole subscription. */
|
|
|
|
interface GetFiltersOpts {
|
2023-12-22 16:38:48 +00:00
|
|
|
/** Signal to abort the request. */
|
|
|
|
signal?: AbortSignal;
|
2023-08-25 18:38:21 +00:00
|
|
|
/** Event limit for the whole subscription. */
|
|
|
|
limit?: number;
|
2023-12-21 20:56:21 +00:00
|
|
|
/** Relays to use, if applicable. */
|
|
|
|
relays?: WebSocket['url'][];
|
2023-08-25 18:38:21 +00:00
|
|
|
}
|
2023-08-24 20:28:13 +00:00
|
|
|
|
|
|
|
function matchDittoFilter(filter: DittoFilter, event: Event, data: EventData): boolean {
|
2023-08-26 22:31:52 +00:00
|
|
|
if (filter.local && !(data.user || event.pubkey === Conf.pubkey)) {
|
2023-08-24 20:28:13 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return matchFilters([filter], event);
|
|
|
|
}
|
|
|
|
|
2023-08-24 22:00:08 +00:00
|
|
|
/**
|
|
|
|
* Similar to nostr-tools `matchFilters`, but supports Ditto's custom keys.
|
|
|
|
* Database calls are needed to look up the extra data, so it's passed in as an argument.
|
|
|
|
*/
|
2023-08-24 20:28:13 +00:00
|
|
|
function matchDittoFilters(filters: DittoFilter[], event: Event, data: EventData): boolean {
|
|
|
|
for (const filter of filters) {
|
|
|
|
if (matchDittoFilter(filter, event, data)) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2023-12-22 01:10:42 +00:00
|
|
|
/** Get deterministic ID for a microfilter. */
|
|
|
|
function getFilterId(filter: MicroFilter): string {
|
|
|
|
if ('ids' in filter) {
|
2023-12-28 03:14:45 +00:00
|
|
|
return stringifyStable({ ids: [filter.ids[0]] });
|
2023-12-22 01:10:42 +00:00
|
|
|
} else {
|
|
|
|
return stringifyStable({
|
|
|
|
kinds: [filter.kinds[0]],
|
|
|
|
authors: [filter.authors[0]],
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Get a microfilter from a Nostr event. */
|
|
|
|
function eventToMicroFilter(event: Event): MicroFilter {
|
2023-12-28 05:22:24 +00:00
|
|
|
const [microfilter] = getMicroFilters(event);
|
|
|
|
return microfilter;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Get all the microfilters for an event, in order of priority. */
|
|
|
|
function getMicroFilters(event: Event): MicroFilter[] {
|
|
|
|
const microfilters: MicroFilter[] = [];
|
2023-12-22 01:10:42 +00:00
|
|
|
if (event.kind === 0) {
|
2023-12-28 05:22:24 +00:00
|
|
|
microfilters.push({ kinds: [0], authors: [event.pubkey] });
|
2023-12-22 01:10:42 +00:00
|
|
|
}
|
2023-12-28 05:22:24 +00:00
|
|
|
microfilters.push({ ids: [event.id] });
|
|
|
|
return microfilters;
|
2023-12-22 01:10:42 +00:00
|
|
|
}
|
|
|
|
|
2023-12-28 04:49:35 +00:00
|
|
|
/** Microfilter schema. */
|
|
|
|
const microFilterSchema = z.union([
|
|
|
|
z.object({ ids: z.tuple([nostrIdSchema]) }).strict(),
|
|
|
|
z.object({ kinds: z.tuple([z.literal(0)]), authors: z.tuple([nostrIdSchema]) }).strict(),
|
|
|
|
]);
|
|
|
|
|
|
|
|
/** Checks whether the filter is a microfilter. */
|
|
|
|
function isMicrofilter(filter: Filter): filter is MicroFilter {
|
|
|
|
return microFilterSchema.safeParse(filter).success;
|
|
|
|
}
|
|
|
|
|
2023-12-22 01:10:42 +00:00
|
|
|
export {
|
|
|
|
type DittoFilter,
|
|
|
|
eventToMicroFilter,
|
|
|
|
getFilterId,
|
|
|
|
type GetFiltersOpts,
|
2023-12-28 05:22:24 +00:00
|
|
|
getMicroFilters,
|
2023-12-28 04:49:35 +00:00
|
|
|
isMicrofilter,
|
2023-12-22 01:10:42 +00:00
|
|
|
matchDittoFilters,
|
|
|
|
type MicroFilter,
|
|
|
|
type Relation,
|
|
|
|
};
|