Files
cv-cat dc078b248b catbus:一个命令操作小红书、抖音、TikTok、B 站、快手、微博、闲鱼、淘宝、京东、X
10 个平台的 web 端全部用 TypeScript 移植自 cv-cat 的开源项目,请求与上游逐字节对拍。
统一的命令语法与 JSON 输出、按 平台 × 端 × 账号 隔离的登录态、给 AI Agent 用的技能与 llms.txt。
2026-10-06 19:24:55 +08:00

145 lines
4.8 KiB
TypeScript

import { z } from 'zod'
/** 归一化类型(AGENTS 6.2)的结构校验,在线测试用。字段总是存在:缺失时为 null 或 []。 */
const str = z.string()
const nstr = z.string().nullable()
const nint = z.number().int().nullable()
const nnum = z.number().nullable()
/** 带时区的 ISO 8601。 */
const time = z.string().regex(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})$/, '不是带时区的 ISO 8601')
const ntime = time.nullable()
const UserRef = z.strictObject({ id: str, name: nstr, url: nstr })
const Price = z.strictObject({ amount: z.number(), currency: str })
const Media = z.strictObject({
id: nstr,
type: z.enum(['image', 'video', 'audio']),
url: str,
width: nint,
height: nint,
duration: nnum,
})
const User = z.strictObject({
id: str,
name: nstr,
handle: nstr,
avatar: nstr,
url: nstr,
bio: nstr,
stats: z.strictObject({ followers: nint, following: nint, items: nint, likes: nint }),
})
const Item = z.strictObject({
id: str,
kind: z.enum(['video', 'image', 'text', 'article', 'goods']),
url: nstr,
title: nstr,
text: nstr,
author: UserRef.nullable(),
created_at: ntime,
cover: nstr,
media: z.array(Media),
stats: z.strictObject({ views: nint, likes: nint, comments: nint, collects: nint, shares: nint }),
price: Price.nullable(),
status: z.enum(['published', 'reviewing', 'rejected', 'private', 'draft', 'on_sale', 'sold', 'off_shelf']).nullable(),
})
const Comment = z.strictObject({
id: str,
item_id: str,
parent_id: nstr,
author: UserRef.nullable(),
text: str,
created_at: ntime,
stats: z.strictObject({ likes: nint, replies: nint }),
})
const Folder = z.strictObject({ id: str, name: str, count: nint, url: nstr })
const Category = z.strictObject({ id: str, name: str, parent_id: nstr })
const Keyword = z.strictObject({ text: str, heat: nint })
const Topic = z.strictObject({ id: str, name: str, url: nstr, stats: z.strictObject({ views: nint, items: nint }) })
const Poi = z.strictObject({ id: str, name: str, address: nstr })
const Live = z.strictObject({
id: str,
url: nstr,
title: nstr,
status: z.enum(['live', 'offline']),
host: UserRef.nullable(),
cover: nstr,
stats: z.strictObject({ viewers: nint }),
})
const Event = z.strictObject({
type: z.enum(['chat', 'gift', 'like', 'enter', 'follow', 'other']),
time,
user: UserRef.nullable(),
text: nstr,
gift: z.strictObject({ name: str, count: z.number().int() }).nullable(),
})
const Gift = z.strictObject({ id: str, name: str, price: Price.nullable() })
const Rank = z.strictObject({ rank: z.number().int(), user: UserRef, score: nnum })
const Conversation = z.strictObject({ id: str, peer: UserRef.nullable(), unread: nint, last_message: nstr, updated_at: ntime })
const Message = z.strictObject({
id: str,
conversation_id: str,
from: UserRef.nullable(),
type: z.enum(['text', 'image', 'video', 'card', 'other']),
text: nstr,
media: z.array(Media),
created_at: ntime,
})
const Notice = z.strictObject({
id: str,
type: z.enum(['comment', 'mention', 'like', 'follow', 'system']),
user: UserRef.nullable(),
target: z.strictObject({ id: str, url: nstr }).nullable(),
text: nstr,
created_at: ntime,
})
const NoticeCount = z.strictObject({ total: z.number().int(), comment: nint, mention: nint, like: nint, follow: nint, system: nint })
const AuthStatus = z.strictObject({ logged_in: z.boolean(), user: UserRef.nullable(), method: nstr, expires_at: ntime })
const Account = z.strictObject({
platform: str,
endpoint: str,
account: str,
current: z.boolean(),
user: UserRef.nullable(),
method: nstr,
updated_at: ntime,
})
const Subtitle = z.strictObject({
lang: str,
name: nstr,
url: nstr,
lines: z.array(z.strictObject({ from: z.number(), to: z.number(), text: str })),
})
const Danmaku = z.strictObject({ id: str, item_id: str, offset: z.number(), text: str, created_at: ntime })
const Order = z.strictObject({ id: str, status: nstr, total: Price.nullable(), items: z.array(Item), created_at: ntime })
const Coupon = z.strictObject({
id: str,
title: nstr,
discount: Price.nullable(),
threshold: Price.nullable(),
start_at: ntime,
end_at: ntime,
})
export const SHAPES: Record<string, z.ZodType> = {
User, Item, Comment, Folder, Series: Folder, Category, Keyword, Topic, Poi, Live, Event, Gift, Rank,
Conversation, Message, Notice, NoticeCount, Media, AuthStatus, Account, Subtitle, Danmaku, Order, Coupon,
}
/** 按注册表的 output(如 `Item[]`、`{count}`)取校验器;内联结构只校验是对象。 */
export function shapeOf(output: string): z.ZodType {
const list = output.endsWith('[]')
const name = output.replace(/\[\]$/, '')
const one = name.startsWith('{') ? z.record(z.string(), z.unknown()) : SHAPES[name]
if (!one) throw new Error(`没有 ${output} 的校验器`)
return list ? z.array(one) : one
}