This commit is contained in:
Siddharth Singh 2025-04-03 18:06:07 +05:30
parent e533ec22b6
commit 3ace715498
No known key found for this signature in database
4 changed files with 153 additions and 153 deletions

View file

@ -9,42 +9,42 @@ type ParamValue = string | number | boolean;
export { PolicyRegistry }; export { PolicyRegistry };
export const policyRegistry = new PolicyRegistry({ export const policyRegistry = new PolicyRegistry({
antiDuplicationPolicyStore: { antiDuplicationPolicyStore: {
get: (key: Deno.KvKey) => Promise.resolve({ key, value: null, versionstamp: null }), get: (key: Deno.KvKey) => Promise.resolve({ key, value: null, versionstamp: null }),
set: () => Promise.resolve({ ok: true, versionstamp: '00000000000000000000' }), set: () => Promise.resolve({ ok: true, versionstamp: '00000000000000000000' }),
}, },
store: new MockRelay(), store: new MockRelay(),
}); });
interface PolicySpecItem { interface PolicySpecItem {
name: keyof typeof policyRegistry.available; name: keyof typeof policyRegistry.available;
params?: Record<string, ParamValue | (number | string)[]>; params?: Record<string, ParamValue | (number | string)[]>;
} }
export interface PolicySpec { export interface PolicySpec {
policies: PolicySpecItem[]; policies: PolicySpecItem[];
} }
export const normalizeNpub = (itm: string) => { export const normalizeNpub = (itm: string) => {
if (!itm.startsWith('npub1')) return itm; if (!itm.startsWith('npub1')) return itm;
return nip19.decode(itm as `npub1${string}`).data; return nip19.decode(itm as `npub1${string}`).data;
}; };
export const DEFAULT_POLICY_SPEC: PolicySpec = { export const DEFAULT_POLICY_SPEC: PolicySpec = {
policies: [ policies: [
{ 'name': 'AntiDuplicationPolicy' }, { 'name': 'AntiDuplicationPolicy' },
{ 'name': 'HellthreadPolicy' }, { 'name': 'HellthreadPolicy' },
{ 'name': 'ReplyBotPolicy' }, { 'name': 'ReplyBotPolicy' },
{ 'name': 'SizePolicy' }, { 'name': 'SizePolicy' },
{ 'name': 'HashtagPolicy', 'params': { 'hashtags': ['NSFW', 'explicit', 'violence', 'cp', 'porn'] } }, { 'name': 'HashtagPolicy', 'params': { 'hashtags': ['NSFW', 'explicit', 'violence', 'cp', 'porn'] } },
], ],
}; };
export const createPolicyEvent = async (conf: DittoConf, policies: PolicySpec) => { export const createPolicyEvent = async (conf: DittoConf, policies: PolicySpec) => {
return await conf.signer.signEvent({ return await conf.signer.signEvent({
kind: 11984, kind: 11984,
content: JSON.stringify(policies), content: JSON.stringify(policies),
created_at: nostrNow(), created_at: nostrNow(),
tags: [], tags: [],
}); });
}; };

View file

@ -3,103 +3,103 @@ import { z } from 'zod';
import { zodSchemaToFields } from './parameters.ts'; import { zodSchemaToFields } from './parameters.ts';
Deno.test('zodSchemaToFields - basic types', () => { Deno.test('zodSchemaToFields - basic types', () => {
const schema = z.object({ const schema = z.object({
name: z.string(), name: z.string(),
age: z.number(), age: z.number(),
}); });
const result = zodSchemaToFields(schema); const result = zodSchemaToFields(schema);
assertEquals(result, { assertEquals(result, {
name: { type: 'string' }, name: { type: 'string' },
age: { type: 'number' }, age: { type: 'number' },
}); });
}); });
Deno.test('zodSchemaToFields - array types', () => { Deno.test('zodSchemaToFields - array types', () => {
const schema = z.object({ const schema = z.object({
tags: z.array(z.string()), tags: z.array(z.string()),
scores: z.array(z.number()), scores: z.array(z.number()),
}); });
const result = zodSchemaToFields(schema); const result = zodSchemaToFields(schema);
assertEquals(result, { assertEquals(result, {
tags: { type: 'multi_string' }, tags: { type: 'multi_string' },
scores: { type: 'multi_number' }, scores: { type: 'multi_number' },
}); });
}); });
Deno.test('zodSchemaToFields - special-case NIP-01 filters', () => { Deno.test('zodSchemaToFields - special-case NIP-01 filters', () => {
const schema = z.object({ const schema = z.object({
filters: z.array(z.string()), filters: z.array(z.string()),
keywords: z.array(z.string()), keywords: z.array(z.string()),
}); });
const result = zodSchemaToFields(schema); const result = zodSchemaToFields(schema);
assertEquals(result, { assertEquals(result, {
filters: { type: 'multi_string' }, filters: { type: 'multi_string' },
keywords: { type: 'multi_string' }, keywords: { type: 'multi_string' },
}); });
}); });
Deno.test('zodSchemaToFields - mixed types', () => { Deno.test('zodSchemaToFields - mixed types', () => {
const schema = z.object({ const schema = z.object({
id: z.string(), id: z.string(),
values: z.array(z.number()), values: z.array(z.number()),
flags: z.array(z.string()).describe('Test description'), flags: z.array(z.string()).describe('Test description'),
}); });
const result = zodSchemaToFields(schema); const result = zodSchemaToFields(schema);
assertEquals(result, { assertEquals(result, {
id: { type: 'string' }, id: { type: 'string' },
values: { type: 'multi_number' }, values: { type: 'multi_number' },
flags: { type: 'multi_string', description: 'Test description' }, flags: { type: 'multi_string', description: 'Test description' },
}); });
}); });
Deno.test('zodSchemaToFields - optional fields', () => { Deno.test('zodSchemaToFields - optional fields', () => {
const schema = z.object({ const schema = z.object({
name: z.string().optional(), name: z.string().optional(),
age: z.number().optional(), age: z.number().optional(),
}); });
const result = zodSchemaToFields(schema); const result = zodSchemaToFields(schema);
assertEquals(result, { assertEquals(result, {
name: { type: 'string', optional: true }, name: { type: 'string', optional: true },
age: { type: 'number', optional: true }, age: { type: 'number', optional: true },
}); });
}); });
Deno.test('zodSchemaToFields - default values', () => { Deno.test('zodSchemaToFields - default values', () => {
const schema = z.object({ const schema = z.object({
name: z.string().default('John Doe'), name: z.string().default('John Doe'),
age: z.number().default(30), age: z.number().default(30),
}); });
const result = zodSchemaToFields(schema); const result = zodSchemaToFields(schema);
assertEquals(result, { assertEquals(result, {
name: { type: 'string', default: 'John Doe' }, name: { type: 'string', default: 'John Doe' },
age: { type: 'number', default: 30 }, age: { type: 'number', default: 30 },
}); });
}); });
Deno.test('zodSchemaToFields - boolean fields', () => { Deno.test('zodSchemaToFields - boolean fields', () => {
const schema = z.object({ const schema = z.object({
active: z.boolean(), active: z.boolean(),
}); });
const result = zodSchemaToFields(schema); const result = zodSchemaToFields(schema);
assertEquals(result, { assertEquals(result, {
active: { type: 'boolean' }, active: { type: 'boolean' },
}); });
}); });
Deno.test('zodSchemaToFields - invalid schema', () => { Deno.test('zodSchemaToFields - invalid schema', () => {
const schema = z.object({ const schema = z.object({
invalid: z.any(), invalid: z.any(),
}); });
const result = zodSchemaToFields(schema); const result = zodSchemaToFields(schema);
assertEquals(result, { assertEquals(result, {
invalid: { type: 'unknown' }, invalid: { type: 'unknown' },
}); });
}); });

View file

@ -3,52 +3,52 @@ import { z } from 'zod';
type FieldType = 'string' | 'multi_string' | 'number' | 'multi_number' | 'boolean' | 'unknown'; type FieldType = 'string' | 'multi_string' | 'number' | 'multi_number' | 'boolean' | 'unknown';
export interface FieldItem { export interface FieldItem {
type: FieldType; type: FieldType;
description?: string; description?: string;
optional?: boolean; optional?: boolean;
default?: any; default?: any;
} }
interface UnwrappedZodType { interface UnwrappedZodType {
baseType: z.ZodTypeAny; baseType: z.ZodTypeAny;
optional?: boolean; optional?: boolean;
defaultValue?: any; defaultValue?: any;
description?: string; description?: string;
} }
/** /**
* Extracts the base type from wrapped Zod types like ZodOptional and ZodDefault. * Extracts the base type from wrapped Zod types like ZodOptional and ZodDefault.
*/ */
function unwrapZodType(field: z.ZodTypeAny): UnwrappedZodType { function unwrapZodType(field: z.ZodTypeAny): UnwrappedZodType {
let optional = false; let optional = false;
let defaultValue: any = undefined; let defaultValue: any = undefined;
let description: string | undefined = undefined; let description: string | undefined = undefined;
description = field.description; description = field.description;
while (field instanceof z.ZodOptional || field instanceof z.ZodDefault) { while (field instanceof z.ZodOptional || field instanceof z.ZodDefault) {
if (field instanceof z.ZodOptional) optional = true; if (field instanceof z.ZodOptional) optional = true;
if (field instanceof z.ZodDefault) defaultValue = field._def.defaultValue(); if (field instanceof z.ZodDefault) defaultValue = field._def.defaultValue();
if (!description) description = field.description; if (!description) description = field.description;
field = field._def.innerType; field = field._def.innerType;
}
const result: UnwrappedZodType = { baseType: field };
if (optional) result.optional = true;
if (typeof defaultValue !== 'undefined') {
if (
typeof defaultValue === 'string' && defaultValue.length > 0 ||
(typeof defaultValue === 'number') ||
(typeof defaultValue === 'object' && Object.keys(defaultValue).length > 0) ||
(Array.isArray(defaultValue) && defaultValue.length > 0)
) {
result.defaultValue = defaultValue;
} }
}
const result: UnwrappedZodType = { baseType: field }; if (description) result.description = description;
return result;
if (optional) result.optional = true;
if (typeof defaultValue !== 'undefined') {
if (
typeof defaultValue === 'string' && defaultValue.length > 0 ||
(typeof defaultValue === 'number') ||
(typeof defaultValue === 'object' && Object.keys(defaultValue).length > 0) ||
(Array.isArray(defaultValue) && defaultValue.length > 0)
) {
result.defaultValue = defaultValue;
}
}
if (description) result.description = description;
return result;
} }
/** /**
@ -64,27 +64,27 @@ function unwrapZodType(field: z.ZodTypeAny): UnwrappedZodType {
* Special-cases NIP-01 filters as `multi_string`. * Special-cases NIP-01 filters as `multi_string`.
*/ */
export function zodSchemaToFields(schema: z.ZodObject<any>): Record<string, FieldItem> { export function zodSchemaToFields(schema: z.ZodObject<any>): Record<string, FieldItem> {
const result: Record<string, FieldItem> = {}; const result: Record<string, FieldItem> = {};
for (const [key, field] of Object.entries(schema.shape) as [string, z.ZodTypeAny][]) { for (const [key, field] of Object.entries(schema.shape) as [string, z.ZodTypeAny][]) {
const { baseType, optional, defaultValue, description } = unwrapZodType(field); const { baseType, optional, defaultValue, description } = unwrapZodType(field);
result[key] = { type: 'unknown' }; result[key] = { type: 'unknown' };
if (optional) result[key].optional = optional; if (optional) result[key].optional = optional;
if (defaultValue) result[key].default = defaultValue; if (defaultValue) result[key].default = defaultValue;
if (description) result[key].description = description; if (description) result[key].description = description;
if (key === 'filters') { if (key === 'filters') {
result[key].type = 'multi_string'; result[key].type = 'multi_string';
} else if (baseType instanceof z.ZodArray) { } else if (baseType instanceof z.ZodArray) {
const elementType = unwrapZodType(baseType._def.type).baseType._def.typeName; const elementType = unwrapZodType(baseType._def.type).baseType._def.typeName;
if (elementType === 'ZodNumber') result[key].type = 'multi_number'; if (elementType === 'ZodNumber') result[key].type = 'multi_number';
else result[key].type = 'multi_string'; else result[key].type = 'multi_string';
} else if (baseType instanceof z.ZodNumber) result[key].type = 'number'; } else if (baseType instanceof z.ZodNumber) result[key].type = 'number';
else if (baseType instanceof z.ZodBoolean) result[key].type = 'boolean'; else if (baseType instanceof z.ZodBoolean) result[key].type = 'boolean';
else if (baseType instanceof z.ZodString) result[key].type = 'string'; else if (baseType instanceof z.ZodString) result[key].type = 'string';
if (baseType.description && !result[key].description) result[key].description = baseType.description; if (baseType.description && !result[key].description) result[key].description = baseType.description;
} }
return result; return result;
} }

View file

@ -17,21 +17,21 @@ export const FiltersPolicyOptsSchema = z.object({
filters: z.array(n.filter()), filters: z.array(n.filter()),
}); });
interface FiltersPolicyOpts extends z.TypeOf<typeof FiltersPolicyOptsSchema> { } interface FiltersPolicyOpts extends z.TypeOf<typeof FiltersPolicyOptsSchema> {}
/** Options for `HashtagPolicy`. */ /** Options for `HashtagPolicy`. */
export const HashtagPolicyOptsSchema = z.object({ export const HashtagPolicyOptsSchema = z.object({
hashtags: z.array(z.string()).describe('Banned hashtags (case-insensitive)'), hashtags: z.array(z.string()).describe('Banned hashtags (case-insensitive)'),
}); });
export interface HashtagPolicyOpts extends z.TypeOf<typeof HashtagPolicyOptsSchema> { } export interface HashtagPolicyOpts extends z.TypeOf<typeof HashtagPolicyOptsSchema> {}
/** Options for `WhitelistPolicy`. */ /** Options for `WhitelistPolicy`. */
export const WhitelistPolicyOptsSchema = z.object({ export const WhitelistPolicyOptsSchema = z.object({
pubkeys: z.array(z.string()).describe('Allowed pubkeys'), pubkeys: z.array(z.string()).describe('Allowed pubkeys'),
}); });
export interface WhitelistPolicyOpts extends z.TypeOf<typeof WhitelistPolicyOptsSchema> { } export interface WhitelistPolicyOpts extends z.TypeOf<typeof WhitelistPolicyOptsSchema> {}
/** Options for `RegexPolicy`. */ /** Options for `RegexPolicy`. */
export const RegexPolicyOptsSchema = z.object({ export const RegexPolicyOptsSchema = z.object({
@ -48,7 +48,7 @@ export const KeywordPolicyOptsSchema = z.object({
keywords: z.array(z.string()).describe('Banned keywords (case-insensitive)'), keywords: z.array(z.string()).describe('Banned keywords (case-insensitive)'),
}); });
export interface KeywordPolicyOpts extends z.TypeOf<typeof KeywordPolicyOptsSchema> { } export interface KeywordPolicyOpts extends z.TypeOf<typeof KeywordPolicyOptsSchema> {}
/** Options for `DomainPolicy`. */ /** Options for `DomainPolicy`. */
export const DomainPolicyOptsSchema = z.object({ export const DomainPolicyOptsSchema = z.object({
@ -66,7 +66,7 @@ export const PubkeyBanPolicyOptsSchema = z.object({
pubkeys: z.array(z.string()).describe('Banned pubkeys'), pubkeys: z.array(z.string()).describe('Banned pubkeys'),
}); });
export interface PubkeyBanPolicyOpts extends z.TypeOf<typeof PubkeyBanPolicyOptsSchema> { } export interface PubkeyBanPolicyOpts extends z.TypeOf<typeof PubkeyBanPolicyOptsSchema> {}
/** Options for `OpenAIPolicy`. */ /** Options for `OpenAIPolicy`. */
export const OpenAIPolicyOptsSchema = z.object({ export const OpenAIPolicyOptsSchema = z.object({
@ -122,7 +122,7 @@ export const SizePolicyOptsSchema = z.object({
maxBytes: z.number().default(8 * 1024).describe('Max allowed message size in bytes'), maxBytes: z.number().default(8 * 1024).describe('Max allowed message size in bytes'),
}); });
export interface SizePolicyOpts extends Partial<z.TypeOf<typeof SizePolicyOptsSchema>> { } export interface SizePolicyOpts extends Partial<z.TypeOf<typeof SizePolicyOptsSchema>> {}
/** Options for `AntiDuplicationPolicy`. */ /** Options for `AntiDuplicationPolicy`. */
export const AntiDuplicationPolicyOptsSchema = z.object({ export const AntiDuplicationPolicyOptsSchema = z.object({
@ -140,11 +140,11 @@ export const HellthreadPolicyOptsSchema = z.object({
limit: z.number().default(100).describe('Maximum number of mentions to allow per post'), limit: z.number().default(100).describe('Maximum number of mentions to allow per post'),
}); });
export interface HellthreadPolicyOpts extends Partial<z.TypeOf<typeof HellthreadPolicyOptsSchema>> { } export interface HellthreadPolicyOpts extends Partial<z.TypeOf<typeof HellthreadPolicyOptsSchema>> {}
/** Options for `PowPolicy`. */ /** Options for `PowPolicy`. */
export const PowPolicyOptsSchema = z.object({ export const PowPolicyOptsSchema = z.object({
difficulty: z.number().default(1).describe('Number of bits of proof-of-work to require'), difficulty: z.number().default(1).describe('Number of bits of proof-of-work to require'),
}); });
export interface PowPolicyOpts extends Partial<z.TypeOf<typeof PowPolicyOptsSchema>> { } export interface PowPolicyOpts extends Partial<z.TypeOf<typeof PowPolicyOptsSchema>> {}