Remove zod schemas that we can get from NSchema
This commit is contained in:
parent
79177cd6c3
commit
fc7ed8bf24
|
@ -1,4 +1,4 @@
|
|||
import { NostrFilter } from '@nostrify/nostrify';
|
||||
import { NostrFilter, NSchema as n } from '@nostrify/nostrify';
|
||||
import { nip19 } from 'nostr-tools';
|
||||
import { z } from 'zod';
|
||||
|
||||
|
@ -6,7 +6,6 @@ import { type AppController } from '@/app.ts';
|
|||
import { Conf } from '@/config.ts';
|
||||
import { getAuthor, getFollowedPubkeys } from '@/queries.ts';
|
||||
import { booleanParamSchema, fileSchema } from '@/schema.ts';
|
||||
import { jsonMetaContentSchema } from '@/schemas/nostr.ts';
|
||||
import { Storages } from '@/storages.ts';
|
||||
import { addTag, deleteTag, findReplyTag, getTagSet } from '@/tags.ts';
|
||||
import { uploadFile } from '@/upload.ts';
|
||||
|
@ -198,7 +197,7 @@ const updateCredentialsController: AppController = async (c) => {
|
|||
}
|
||||
|
||||
const author = await getAuthor(pubkey);
|
||||
const meta = author ? jsonMetaContentSchema.parse(author.content) : {};
|
||||
const meta = author ? n.json().pipe(n.metadata()).parse(author.content) : {};
|
||||
|
||||
const {
|
||||
avatar: avatarFile,
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import { NSchema as n } from '@nostrify/nostrify';
|
||||
|
||||
import { type AppController } from '@/app.ts';
|
||||
import { Conf } from '@/config.ts';
|
||||
import { jsonServerMetaSchema } from '@/schemas/nostr.ts';
|
||||
import { serverMetaSchema } from '@/schemas/nostr.ts';
|
||||
import { Storages } from '@/storages.ts';
|
||||
|
||||
const instanceController: AppController = async (c) => {
|
||||
|
@ -8,7 +10,7 @@ const instanceController: AppController = async (c) => {
|
|||
const { signal } = c.req.raw;
|
||||
|
||||
const [event] = await Storages.db.query([{ kinds: [0], authors: [Conf.pubkey], limit: 1 }], { signal });
|
||||
const meta = jsonServerMetaSchema.parse(event?.content);
|
||||
const meta = n.json().pipe(serverMetaSchema).parse(event?.content);
|
||||
|
||||
/** Protocol to use for WebSocket URLs, depending on the protocol of the `LOCAL_DOMAIN`. */
|
||||
const wsProtocol = protocol === 'http:' ? 'ws:' : 'wss:';
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { NSchema as n } from '@nostrify/nostrify';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { type AppController } from '@/app.ts';
|
||||
|
@ -6,7 +7,6 @@ import { configSchema, elixirTupleSchema, type PleromaConfig } from '@/schemas/p
|
|||
import { AdminSigner } from '@/signers/AdminSigner.ts';
|
||||
import { Storages } from '@/storages.ts';
|
||||
import { createAdminEvent } from '@/utils/api.ts';
|
||||
import { jsonSchema } from '@/schema.ts';
|
||||
|
||||
const frontendConfigController: AppController = async (c) => {
|
||||
const configs = await getConfigs(c.req.raw.signal);
|
||||
|
@ -75,7 +75,7 @@ async function getConfigs(signal: AbortSignal): Promise<PleromaConfig[]> {
|
|||
|
||||
try {
|
||||
const decrypted = await new AdminSigner().nip44.decrypt(Conf.pubkey, event.content);
|
||||
return jsonSchema.pipe(configSchema.array()).catch([]).parse(decrypted);
|
||||
return n.json().pipe(configSchema.array()).catch([]).parse(decrypted);
|
||||
} catch (_e) {
|
||||
return [];
|
||||
}
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
import { NostrEvent, NostrFilter } from '@nostrify/nostrify';
|
||||
import { NostrEvent, NostrFilter, NSchema as n } from '@nostrify/nostrify';
|
||||
import { nip19 } from 'nostr-tools';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { AppController } from '@/app.ts';
|
||||
import { booleanParamSchema } from '@/schema.ts';
|
||||
import { nostrIdSchema } from '@/schemas/nostr.ts';
|
||||
import { Storages } from '@/storages.ts';
|
||||
import { dedupeEvents } from '@/utils.ts';
|
||||
import { nip05Cache } from '@/utils/nip05.ts';
|
||||
|
@ -20,7 +19,7 @@ const searchQuerySchema = z.object({
|
|||
type: z.enum(['accounts', 'statuses', 'hashtags']).optional(),
|
||||
resolve: booleanParamSchema.optional().transform(Boolean),
|
||||
following: z.boolean().default(false),
|
||||
account_id: nostrIdSchema.optional(),
|
||||
account_id: n.id().optional(),
|
||||
limit: z.coerce.number().catch(20).transform((value) => Math.min(Math.max(value, 0), 40)),
|
||||
});
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { NIP05, NostrEvent, NostrFilter } from '@nostrify/nostrify';
|
||||
import { NIP05, NostrEvent, NostrFilter, NSchema as n } from '@nostrify/nostrify';
|
||||
import ISO6391 from 'iso-639-1';
|
||||
import { nip19 } from 'nostr-tools';
|
||||
import { z } from 'zod';
|
||||
|
@ -7,7 +7,6 @@ import { type AppController } from '@/app.ts';
|
|||
import { Conf } from '@/config.ts';
|
||||
import { getUnattachedMediaByIds } from '@/db/unattached-media.ts';
|
||||
import { getAncestors, getAuthor, getDescendants, getEvent } from '@/queries.ts';
|
||||
import { jsonMetaContentSchema } from '@/schemas/nostr.ts';
|
||||
import { addTag, deleteTag } from '@/tags.ts';
|
||||
import { createEvent, paginationSchema, parseBody, updateListEvent } from '@/utils/api.ts';
|
||||
import { renderEventAccounts } from '@/views.ts';
|
||||
|
@ -406,7 +405,7 @@ const zapController: AppController = async (c) => {
|
|||
|
||||
const target = await getEvent(id, { kind: 1, relations: ['author', 'event_stats', 'author_stats'], signal });
|
||||
const author = target?.author;
|
||||
const meta = jsonMetaContentSchema.parse(author?.content);
|
||||
const meta = n.json().pipe(n.metadata()).parse(author?.content);
|
||||
const lnurl = getLnurl(meta);
|
||||
|
||||
if (target && lnurl) {
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
import { NSchema as n } from '@nostrify/nostrify';
|
||||
|
||||
import { AppController } from '@/app.ts';
|
||||
import { Conf } from '@/config.ts';
|
||||
import { jsonServerMetaSchema } from '@/schemas/nostr.ts';
|
||||
import { serverMetaSchema } from '@/schemas/nostr.ts';
|
||||
import { Storages } from '@/storages.ts';
|
||||
|
||||
const relayInfoController: AppController = async (c) => {
|
||||
const { signal } = c.req.raw;
|
||||
const [event] = await Storages.db.query([{ kinds: [0], authors: [Conf.pubkey], limit: 1 }], { signal });
|
||||
const meta = jsonServerMetaSchema.parse(event?.content);
|
||||
const meta = n.json().pipe(serverMetaSchema).parse(event?.content);
|
||||
|
||||
return c.json({
|
||||
name: meta.name ?? 'Ditto',
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
import { NostrEvent, NostrFilter, NSchema as n } from '@nostrify/nostrify';
|
||||
import {
|
||||
NostrClientCLOSE,
|
||||
NostrClientCOUNT,
|
||||
NostrClientEVENT,
|
||||
NostrClientMsg,
|
||||
NostrClientREQ,
|
||||
NostrEvent,
|
||||
NostrFilter,
|
||||
NSchema as n,
|
||||
} from '@nostrify/nostrify';
|
||||
import { relayInfoController } from '@/controllers/nostr/relay-info.ts';
|
||||
import * as pipeline from '@/pipeline.ts';
|
||||
import {
|
||||
type ClientCLOSE,
|
||||
type ClientCOUNT,
|
||||
type ClientEVENT,
|
||||
type ClientMsg,
|
||||
clientMsgSchema,
|
||||
type ClientREQ,
|
||||
} from '@/schemas/nostr.ts';
|
||||
import { Storages } from '@/storages.ts';
|
||||
|
||||
import type { AppController } from '@/app.ts';
|
||||
|
@ -30,7 +31,7 @@ function connectStream(socket: WebSocket) {
|
|||
const controllers = new Map<string, AbortController>();
|
||||
|
||||
socket.onmessage = (e) => {
|
||||
const result = n.json().pipe(clientMsgSchema).safeParse(e.data);
|
||||
const result = n.json().pipe(n.clientMsg()).safeParse(e.data);
|
||||
if (result.success) {
|
||||
handleMsg(result.data);
|
||||
} else {
|
||||
|
@ -45,7 +46,7 @@ function connectStream(socket: WebSocket) {
|
|||
};
|
||||
|
||||
/** Handle client message. */
|
||||
function handleMsg(msg: ClientMsg) {
|
||||
function handleMsg(msg: NostrClientMsg) {
|
||||
switch (msg[0]) {
|
||||
case 'REQ':
|
||||
handleReq(msg);
|
||||
|
@ -63,7 +64,7 @@ function connectStream(socket: WebSocket) {
|
|||
}
|
||||
|
||||
/** Handle REQ. Start a subscription. */
|
||||
async function handleReq([_, subId, ...rest]: ClientREQ): Promise<void> {
|
||||
async function handleReq([_, subId, ...rest]: NostrClientREQ): Promise<void> {
|
||||
const filters = prepareFilters(rest);
|
||||
|
||||
const controller = new AbortController();
|
||||
|
@ -88,7 +89,7 @@ function connectStream(socket: WebSocket) {
|
|||
}
|
||||
|
||||
/** Handle EVENT. Store the event. */
|
||||
async function handleEvent([_, event]: ClientEVENT): Promise<void> {
|
||||
async function handleEvent([_, event]: NostrClientEVENT): Promise<void> {
|
||||
try {
|
||||
// This will store it (if eligible) and run other side-effects.
|
||||
await pipeline.handleEvent(event, AbortSignal.timeout(1000));
|
||||
|
@ -104,7 +105,7 @@ function connectStream(socket: WebSocket) {
|
|||
}
|
||||
|
||||
/** Handle CLOSE. Close the subscription. */
|
||||
function handleClose([_, subId]: ClientCLOSE): void {
|
||||
function handleClose([_, subId]: NostrClientCLOSE): void {
|
||||
const controller = controllers.get(subId);
|
||||
if (controller) {
|
||||
controller.abort();
|
||||
|
@ -113,7 +114,7 @@ function connectStream(socket: WebSocket) {
|
|||
}
|
||||
|
||||
/** Handle COUNT. Return the number of events matching the filters. */
|
||||
async function handleCount([_, subId, ...rest]: ClientCOUNT): Promise<void> {
|
||||
async function handleCount([_, subId, ...rest]: NostrClientCOUNT): Promise<void> {
|
||||
const { count } = await Storages.db.count(prepareFilters(rest));
|
||||
send(['COUNT', subId, { count, approximate: false }]);
|
||||
}
|
||||
|
@ -127,7 +128,7 @@ function connectStream(socket: WebSocket) {
|
|||
}
|
||||
|
||||
/** Enforce the filters with certain criteria. */
|
||||
function prepareFilters(filters: ClientREQ[2][]): NostrFilter[] {
|
||||
function prepareFilters(filters: NostrClientREQ[2][]): NostrFilter[] {
|
||||
return filters.map((filter) => {
|
||||
const narrow = Boolean(filter.ids?.length || filter.authors?.length);
|
||||
const search = narrow ? filter.search : `domain:${Conf.url.host} ${filter.search ?? ''}`;
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
import { NostrEvent, NostrFilter } from '@nostrify/nostrify';
|
||||
import { NostrEvent, NostrFilter, NSchema as n } from '@nostrify/nostrify';
|
||||
import stringifyStable from 'fast-stable-stringify';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { isReplaceableKind } from '@/kinds.ts';
|
||||
import { nostrIdSchema } from '@/schemas/nostr.ts';
|
||||
|
||||
/** Microfilter to get one specific event by ID. */
|
||||
type IdMicrofilter = { ids: [NostrEvent['id']] };
|
||||
|
@ -42,8 +41,8 @@ function getMicroFilters(event: NostrEvent): MicroFilter[] {
|
|||
|
||||
/** 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(),
|
||||
z.object({ ids: z.tuple([n.id()]) }).strict(),
|
||||
z.object({ kinds: z.tuple([z.literal(0)]), authors: z.tuple([n.id()]) }).strict(),
|
||||
]);
|
||||
|
||||
/** Checks whether the filter is a microfilter. */
|
||||
|
|
|
@ -11,16 +11,6 @@ function filteredArray<T extends z.ZodTypeAny>(schema: T) {
|
|||
));
|
||||
}
|
||||
|
||||
/** Parses a JSON string into its native type. */
|
||||
const jsonSchema = z.string().transform((value, ctx) => {
|
||||
try {
|
||||
return JSON.parse(value) as unknown;
|
||||
} catch (_e) {
|
||||
ctx.addIssue({ code: z.ZodIssueCode.custom, message: 'Invalid JSON' });
|
||||
return z.NEVER;
|
||||
}
|
||||
});
|
||||
|
||||
/** https://developer.mozilla.org/en-US/docs/Glossary/Base64#the_unicode_problem */
|
||||
const decode64Schema = z.string().transform((value, ctx) => {
|
||||
try {
|
||||
|
@ -48,4 +38,4 @@ const booleanParamSchema = z.enum(['true', 'false']).transform((value) => value
|
|||
/** Schema for `File` objects. */
|
||||
const fileSchema = z.custom<File>((value) => value instanceof File);
|
||||
|
||||
export { booleanParamSchema, decode64Schema, fileSchema, filteredArray, hashtagSchema, jsonSchema, safeUrlSchema };
|
||||
export { booleanParamSchema, decode64Schema, fileSchema, filteredArray, hashtagSchema, safeUrlSchema };
|
||||
|
|
|
@ -1,80 +1,14 @@
|
|||
import { NSchema as n } from '@nostrify/nostrify';
|
||||
import { getEventHash, verifyEvent } from 'nostr-tools';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { jsonSchema, safeUrlSchema } from '@/schema.ts';
|
||||
|
||||
/** Schema to validate Nostr hex IDs such as event IDs and pubkeys. */
|
||||
const nostrIdSchema = z.string().regex(/^[0-9a-f]{64}$/);
|
||||
/** Nostr kinds are positive integers. */
|
||||
const kindSchema = z.number().int().nonnegative();
|
||||
|
||||
/** Nostr event schema. */
|
||||
const eventSchema = z.object({
|
||||
id: nostrIdSchema,
|
||||
kind: kindSchema,
|
||||
tags: z.array(z.array(z.string())),
|
||||
content: z.string(),
|
||||
created_at: z.number(),
|
||||
pubkey: nostrIdSchema,
|
||||
sig: z.string(),
|
||||
});
|
||||
import { safeUrlSchema } from '@/schema.ts';
|
||||
|
||||
/** Nostr event schema that also verifies the event's signature. */
|
||||
const signedEventSchema = eventSchema
|
||||
const signedEventSchema = n.event()
|
||||
.refine((event) => event.id === getEventHash(event), 'Event ID does not match hash')
|
||||
.refine(verifyEvent, 'Event signature is invalid');
|
||||
|
||||
/** Nostr relay filter schema. */
|
||||
const filterSchema = z.object({
|
||||
kinds: kindSchema.array().optional(),
|
||||
ids: nostrIdSchema.array().optional(),
|
||||
authors: nostrIdSchema.array().optional(),
|
||||
since: z.number().int().nonnegative().optional(),
|
||||
until: z.number().int().nonnegative().optional(),
|
||||
limit: z.number().int().nonnegative().optional(),
|
||||
search: z.string().optional(),
|
||||
}).passthrough().and(
|
||||
z.record(
|
||||
z.custom<`#${string}`>((val) => typeof val === 'string' && val.startsWith('#')),
|
||||
z.string().array(),
|
||||
).catch({}),
|
||||
);
|
||||
|
||||
const clientReqSchema = z.tuple([z.literal('REQ'), z.string().min(1)]).rest(filterSchema);
|
||||
const clientEventSchema = z.tuple([z.literal('EVENT'), signedEventSchema]);
|
||||
const clientCloseSchema = z.tuple([z.literal('CLOSE'), z.string().min(1)]);
|
||||
const clientCountSchema = z.tuple([z.literal('COUNT'), z.string().min(1)]).rest(filterSchema);
|
||||
|
||||
/** Client message to a Nostr relay. */
|
||||
const clientMsgSchema = z.union([
|
||||
clientReqSchema,
|
||||
clientEventSchema,
|
||||
clientCloseSchema,
|
||||
clientCountSchema,
|
||||
]);
|
||||
|
||||
/** REQ message from client to relay. */
|
||||
type ClientREQ = z.infer<typeof clientReqSchema>;
|
||||
/** EVENT message from client to relay. */
|
||||
type ClientEVENT = z.infer<typeof clientEventSchema>;
|
||||
/** CLOSE message from client to relay. */
|
||||
type ClientCLOSE = z.infer<typeof clientCloseSchema>;
|
||||
/** COUNT message from client to relay. */
|
||||
type ClientCOUNT = z.infer<typeof clientCountSchema>;
|
||||
/** Client message to a Nostr relay. */
|
||||
type ClientMsg = z.infer<typeof clientMsgSchema>;
|
||||
|
||||
/** Kind 0 content schema. */
|
||||
const metaContentSchema = z.object({
|
||||
name: z.string().optional().catch(undefined),
|
||||
about: z.string().optional().catch(undefined),
|
||||
picture: z.string().optional().catch(undefined),
|
||||
banner: z.string().optional().catch(undefined),
|
||||
nip05: z.string().optional().catch(undefined),
|
||||
lud06: z.string().optional().catch(undefined),
|
||||
lud16: z.string().optional().catch(undefined),
|
||||
}).partial().passthrough();
|
||||
|
||||
/** Media data schema from `"media"` tags. */
|
||||
const mediaDataSchema = z.object({
|
||||
blurhash: z.string().optional().catch(undefined),
|
||||
|
@ -88,40 +22,25 @@ const mediaDataSchema = z.object({
|
|||
});
|
||||
|
||||
/** Kind 0 content schema for the Ditto server admin user. */
|
||||
const serverMetaSchema = metaContentSchema.extend({
|
||||
const serverMetaSchema = n.metadata().and(z.object({
|
||||
tagline: z.string().optional().catch(undefined),
|
||||
email: z.string().optional().catch(undefined),
|
||||
});
|
||||
}));
|
||||
|
||||
/** Media data from `"media"` tags. */
|
||||
type MediaData = z.infer<typeof mediaDataSchema>;
|
||||
|
||||
/** Parses kind 0 content from a JSON string. */
|
||||
const jsonMetaContentSchema = jsonSchema.pipe(metaContentSchema).catch({});
|
||||
|
||||
/** Parses media data from a JSON string. */
|
||||
const jsonMediaDataSchema = jsonSchema.pipe(mediaDataSchema).catch({});
|
||||
|
||||
/** Parses server admin meta from a JSON string. */
|
||||
const jsonServerMetaSchema = jsonSchema.pipe(serverMetaSchema).catch({});
|
||||
|
||||
/** NIP-11 Relay Information Document. */
|
||||
const relayInfoDocSchema = z.object({
|
||||
name: z.string().transform((val) => val.slice(0, 30)).optional().catch(undefined),
|
||||
description: z.string().transform((val) => val.slice(0, 3000)).optional().catch(undefined),
|
||||
pubkey: nostrIdSchema.optional().catch(undefined),
|
||||
pubkey: n.id().optional().catch(undefined),
|
||||
contact: safeUrlSchema.optional().catch(undefined),
|
||||
supported_nips: z.number().int().nonnegative().array().optional().catch(undefined),
|
||||
software: safeUrlSchema.optional().catch(undefined),
|
||||
icon: safeUrlSchema.optional().catch(undefined),
|
||||
});
|
||||
|
||||
/** NIP-46 signer response. */
|
||||
const connectResponseSchema = z.object({
|
||||
id: z.string(),
|
||||
result: signedEventSchema,
|
||||
});
|
||||
|
||||
/** Parses a Nostr emoji tag. */
|
||||
const emojiTagSchema = z.tuple([z.literal('emoji'), z.string(), z.string().url()]);
|
||||
|
||||
|
@ -129,23 +48,11 @@ const emojiTagSchema = z.tuple([z.literal('emoji'), z.string(), z.string().url()
|
|||
type EmojiTag = z.infer<typeof emojiTagSchema>;
|
||||
|
||||
export {
|
||||
type ClientCLOSE,
|
||||
type ClientCOUNT,
|
||||
type ClientEVENT,
|
||||
type ClientMsg,
|
||||
clientMsgSchema,
|
||||
type ClientREQ,
|
||||
connectResponseSchema,
|
||||
type EmojiTag,
|
||||
emojiTagSchema,
|
||||
filterSchema,
|
||||
jsonMediaDataSchema,
|
||||
jsonMetaContentSchema,
|
||||
jsonServerMetaSchema,
|
||||
type MediaData,
|
||||
mediaDataSchema,
|
||||
metaContentSchema,
|
||||
nostrIdSchema,
|
||||
relayInfoDocSchema,
|
||||
serverMetaSchema,
|
||||
signedEventSchema,
|
||||
};
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { NIP50, NostrEvent, NostrFilter, NStore } from '@nostrify/nostrify';
|
||||
import { NIP50, NostrEvent, NostrFilter, NSchema as n, NStore } from '@nostrify/nostrify';
|
||||
import Debug from '@soapbox/stickynotes/debug';
|
||||
import { Kysely, type SelectQueryBuilder } from 'kysely';
|
||||
|
||||
|
@ -7,7 +7,6 @@ import { DittoTables } from '@/db/DittoTables.ts';
|
|||
import { normalizeFilters } from '@/filter.ts';
|
||||
import { DittoEvent } from '@/interfaces/DittoEvent.ts';
|
||||
import { isDittoInternalKind, isParameterizedReplaceableKind, isReplaceableKind } from '@/kinds.ts';
|
||||
import { jsonMetaContentSchema } from '@/schemas/nostr.ts';
|
||||
import { purifyEvent } from '@/storages/hydrate.ts';
|
||||
import { isNostrId, isURL } from '@/utils.ts';
|
||||
import { abortError } from '@/utils/abort.ts';
|
||||
|
@ -412,7 +411,7 @@ function buildSearchContent(event: NostrEvent): string {
|
|||
|
||||
/** Build search content for a user. */
|
||||
function buildUserSearchContent(event: NostrEvent): string {
|
||||
const { name, nip05, about } = jsonMetaContentSchema.parse(event.content);
|
||||
const { name, nip05, about } = n.json().pipe(n.metadata()).parse(event.content);
|
||||
return [name, nip05, about].filter(Boolean).join('\n');
|
||||
}
|
||||
|
||||
|
|
49
src/utils.ts
49
src/utils.ts
|
@ -1,16 +1,13 @@
|
|||
import { NostrEvent } from '@nostrify/nostrify';
|
||||
import { EventTemplate, getEventHash, nip19 } from 'nostr-tools';
|
||||
|
||||
import { nostrIdSchema } from '@/schemas/nostr.ts';
|
||||
import { NostrEvent, NSchema as n } from '@nostrify/nostrify';
|
||||
import { nip19 } from 'nostr-tools';
|
||||
import { z } from 'zod';
|
||||
|
||||
/** Get the current time in Nostr format. */
|
||||
const nostrNow = (): number => Math.floor(Date.now() / 1000);
|
||||
|
||||
/** Convenience function to convert Nostr dates into native Date objects. */
|
||||
const nostrDate = (seconds: number): Date => new Date(seconds * 1000);
|
||||
|
||||
/** Pass to sort() to sort events by date. */
|
||||
const eventDateComparator = (a: NostrEvent, b: NostrEvent): number => b.created_at - a.created_at;
|
||||
|
||||
/** Get pubkey from bech32 string, if applicable. */
|
||||
function bech32ToPubkey(bech32: string): string | undefined {
|
||||
try {
|
||||
|
@ -86,54 +83,20 @@ function dedupeEvents(events: NostrEvent[]): NostrEvent[] {
|
|||
return [...new Map(events.map((event) => [event.id, event])).values()];
|
||||
}
|
||||
|
||||
/** Return a copy of the event with the given tags removed. */
|
||||
function stripTags<E extends EventTemplate>(event: E, tags: string[] = []): E {
|
||||
if (!tags.length) return event;
|
||||
return {
|
||||
...event,
|
||||
tags: event.tags.filter(([name]) => !tags.includes(name)),
|
||||
};
|
||||
}
|
||||
|
||||
/** Ensure the template and event match on their shared keys. */
|
||||
function eventMatchesTemplate(event: NostrEvent, template: EventTemplate): boolean {
|
||||
const whitelist = ['nonce'];
|
||||
|
||||
event = stripTags(event, whitelist);
|
||||
template = stripTags(template, whitelist);
|
||||
|
||||
if (template.created_at > event.created_at) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return getEventHash(event) === getEventHash({
|
||||
pubkey: event.pubkey,
|
||||
...template,
|
||||
created_at: event.created_at,
|
||||
});
|
||||
}
|
||||
|
||||
/** Test whether the value is a Nostr ID. */
|
||||
function isNostrId(value: unknown): boolean {
|
||||
return nostrIdSchema.safeParse(value).success;
|
||||
return n.id().safeParse(value).success;
|
||||
}
|
||||
|
||||
/** Test whether the value is a URL. */
|
||||
function isURL(value: unknown): boolean {
|
||||
try {
|
||||
new URL(value as string);
|
||||
return true;
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
return z.string().url().safeParse(value).success;
|
||||
}
|
||||
|
||||
export {
|
||||
bech32ToPubkey,
|
||||
dedupeEvents,
|
||||
eventAge,
|
||||
eventDateComparator,
|
||||
eventMatchesTemplate,
|
||||
findTag,
|
||||
isNostrId,
|
||||
isURL,
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
import { NostrEvent } from '@nostrify/nostrify';
|
||||
import { NostrEvent, NSchema as n } from '@nostrify/nostrify';
|
||||
import { EventTemplate, nip13 } from 'nostr-tools';
|
||||
|
||||
import { decode64Schema, jsonSchema } from '@/schema.ts';
|
||||
import { decode64Schema } from '@/schema.ts';
|
||||
import { signedEventSchema } from '@/schemas/nostr.ts';
|
||||
import { eventAge, findTag, nostrNow, sha256 } from '@/utils.ts';
|
||||
import { Time } from '@/utils/time.ts';
|
||||
|
||||
/** Decode a Nostr event from a base64 encoded string. */
|
||||
const decode64EventSchema = decode64Schema.pipe(jsonSchema).pipe(signedEventSchema);
|
||||
const decode64EventSchema = decode64Schema.pipe(n.json()).pipe(signedEventSchema);
|
||||
|
||||
interface ParseAuthRequestOpts {
|
||||
/** Max event age (in ms). */
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { NSchema as n } from '@nostrify/nostrify';
|
||||
|
||||
import { Conf } from '@/config.ts';
|
||||
import { jsonMetaContentSchema } from '@/schemas/nostr.ts';
|
||||
import { getPublicKeyPem } from '@/utils/rsa.ts';
|
||||
|
||||
import type { NostrEvent } from '@nostrify/nostrify';
|
||||
|
@ -7,7 +8,7 @@ import type { Actor } from '@/schemas/activitypub.ts';
|
|||
|
||||
/** Nostr metadata event to ActivityPub actor. */
|
||||
async function renderActor(event: NostrEvent, username: string): Promise<Actor | undefined> {
|
||||
const content = jsonMetaContentSchema.parse(event.content);
|
||||
const content = n.json().pipe(n.metadata()).parse(event.content);
|
||||
|
||||
return {
|
||||
type: 'Person',
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { NSchema as n } from '@nostrify/nostrify';
|
||||
import { nip19, UnsignedEvent } from 'nostr-tools';
|
||||
|
||||
import { Conf } from '@/config.ts';
|
||||
import { lodash } from '@/deps.ts';
|
||||
import { type DittoEvent } from '@/interfaces/DittoEvent.ts';
|
||||
import { jsonMetaContentSchema } from '@/schemas/nostr.ts';
|
||||
import { getLnurl } from '@/utils/lnurl.ts';
|
||||
import { nip05Cache } from '@/utils/nip05.ts';
|
||||
import { Nip05, nostrDate, nostrNow, parseNip05 } from '@/utils.ts';
|
||||
|
@ -28,7 +28,7 @@ async function renderAccount(
|
|||
about,
|
||||
lud06,
|
||||
lud16,
|
||||
} = jsonMetaContentSchema.parse(event.content);
|
||||
} = n.json().pipe(n.metadata()).parse(event.content);
|
||||
|
||||
const npub = nip19.npubEncode(pubkey);
|
||||
const parsed05 = await parseAndVerifyNip05(nip05, pubkey);
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
import { NostrEvent } from '@nostrify/nostrify';
|
||||
import { NostrEvent, NSchema as n } from '@nostrify/nostrify';
|
||||
import { isCWTag } from 'https://gitlab.com/soapbox-pub/mostr/-/raw/c67064aee5ade5e01597c6d23e22e53c628ef0e2/src/nostr/tags.ts';
|
||||
import { nip19 } from 'nostr-tools';
|
||||
|
||||
import { Conf } from '@/config.ts';
|
||||
import { type DittoEvent } from '@/interfaces/DittoEvent.ts';
|
||||
import { getMediaLinks, parseNoteContent } from '@/note.ts';
|
||||
import { jsonMediaDataSchema } from '@/schemas/nostr.ts';
|
||||
import { Storages } from '@/storages.ts';
|
||||
import { findReplyTag } from '@/tags.ts';
|
||||
import { nostrDate } from '@/utils.ts';
|
||||
|
@ -13,6 +12,7 @@ import { unfurlCardCached } from '@/utils/unfurl.ts';
|
|||
import { accountFromPubkey, renderAccount } from '@/views/mastodon/accounts.ts';
|
||||
import { DittoAttachment, renderAttachment } from '@/views/mastodon/attachments.ts';
|
||||
import { renderEmojis } from '@/views/mastodon/emojis.ts';
|
||||
import { mediaDataSchema } from '@/schemas/nostr.ts';
|
||||
|
||||
interface statusOpts {
|
||||
viewerPubkey?: string;
|
||||
|
@ -78,7 +78,7 @@ async function renderStatus(event: DittoEvent, opts: statusOpts): Promise<any> {
|
|||
|
||||
const mediaTags: DittoAttachment[] = event.tags
|
||||
.filter((tag) => tag[0] === 'media')
|
||||
.map(([_, url, json]) => ({ url, data: jsonMediaDataSchema.parse(json) }));
|
||||
.map(([_, url, json]) => ({ url, data: n.json().pipe(mediaDataSchema).parse(json) }));
|
||||
|
||||
const media = [...mediaLinks, ...mediaTags];
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { NSchema } from '@nostrify/nostrify';
|
||||
import * as Comlink from 'comlink';
|
||||
|
||||
import { Sqlite } from '@/deps.ts';
|
||||
import { hashtagSchema } from '@/schema.ts';
|
||||
import { nostrIdSchema } from '@/schemas/nostr.ts';
|
||||
import { generateDateRange, Time } from '@/utils/time.ts';
|
||||
|
||||
interface GetTrendingTagsOpts {
|
||||
|
@ -102,7 +102,7 @@ export const TrendsWorker = {
|
|||
},
|
||||
|
||||
addTagUsages(pubkey: string, hashtags: string[], date = new Date()): void {
|
||||
const pubkey8 = nostrIdSchema.parse(pubkey).substring(0, 8);
|
||||
const pubkey8 = NSchema.id().parse(pubkey).substring(0, 8);
|
||||
const tags = hashtagSchema.array().min(1).parse(hashtags);
|
||||
|
||||
db.query(
|
||||
|
|
Loading…
Reference in New Issue