import { z } from 'zod'; import { PROTOCOLS } from './protocols.js'; export const nodeInputSchema = z .object({ name: z.string().min(1).max(64), protocol: z.enum(PROTOCOLS), server: z.string().min(1), port: z.number().int().min(1).max(65535), network: z.enum(['tcp', 'ws']).default('tcp'), wsPath: z.string().startsWith('/').optional().nullable(), wsHost: z.string().optional().nullable(), tls: z.boolean().default(false), sni: z.string().optional().nullable(), skipCertVerify: z.boolean().default(false), uuid: z.string().uuid().optional().nullable(), alterId: z.number().int().min(0).default(0), security: z.string().default('auto'), password: z.string().optional().nullable(), method: z.string().optional().nullable(), obfsType: z.enum(['salamander']).optional().nullable(), obfsPassword: z.string().optional().nullable(), upMbps: z.number().int().positive().optional().nullable(), downMbps: z.number().int().positive().optional().nullable(), region: z.string().max(8).optional().nullable(), tags: z.array(z.string()).default([]), enabled: z.boolean().default(true), sortOrder: z.number().int().default(0), }) .superRefine((v, ctx) => { if (v.protocol === 'vmess' && !v.uuid) { ctx.addIssue({ code: z.ZodIssueCode.custom, path: ['uuid'], message: 'VMess 需要 UUID' }); } if ((v.protocol === 'trojan' || v.protocol === 'shadowsocks' || v.protocol === 'hysteria2') && !v.password) { ctx.addIssue({ code: z.ZodIssueCode.custom, path: ['password'], message: '该协议需要密码' }); } if (v.protocol === 'shadowsocks' && !v.method) { ctx.addIssue({ code: z.ZodIssueCode.custom, path: ['method'], message: 'Shadowsocks 需要加密方式' }); } if (v.network === 'ws' && !v.wsPath) { ctx.addIssue({ code: z.ZodIssueCode.custom, path: ['wsPath'], message: 'WebSocket 传输需要路径' }); } }); export type NodeInput = z.input; export type NodeParsed = z.output; export const groupInputSchema = z.object({ name: z.string().min(1).max(64), type: z.enum(['select', 'url-test', 'fallback']), testUrl: z.string().url().optional().nullable(), interval: z.number().int().positive().optional().nullable(), tolerance: z.number().int().positive().optional().nullable(), filterRegex: z.string().optional().nullable(), includeAllNodes: z.boolean().default(false), sortOrder: z.number().int().default(0), members: z .array( z.object({ kind: z.enum(['node', 'group', 'builtin']), nodeId: z.string().optional().nullable(), refName: z.string().optional().nullable(), }) ) .default([]), }); export type GroupInput = z.input; export const RULE_TYPES = [ 'RULE-SET', 'DOMAIN-SET', 'DOMAIN', 'DOMAIN-SUFFIX', 'DOMAIN-KEYWORD', 'IP-CIDR', 'IP-CIDR6', 'IP-ASN', 'GEOIP', 'PROCESS-NAME', 'USER-AGENT', 'URL-REGEX', 'AND', 'OR', 'NOT', 'FINAL', ] as const; export const ruleInputSchema = z.object({ type: z.enum(RULE_TYPES), value: z.string().optional().nullable(), policy: z.string().min(1), params: z.array(z.string()).default([]), enabled: z.boolean().default(true), }); export type RuleInput = z.input; export const tokenInputSchema = z.object({ name: z.string().min(1).max(64), });