diff --git a/packages/napcat-onebot/action/extends/SetGroupSearch.ts b/packages/napcat-onebot/action/extends/SetGroupSearch.ts index f3732ddf..2ab61e81 100644 --- a/packages/napcat-onebot/action/extends/SetGroupSearch.ts +++ b/packages/napcat-onebot/action/extends/SetGroupSearch.ts @@ -4,8 +4,8 @@ import { Static, Type } from '@sinclair/typebox'; const PayloadSchema = Type.Object({ group_id: Type.String({ description: '群号' }), - no_code_finger_open: Type.Optional(Type.Number({ description: '是否开启无码指纹' })), - no_finger_open: Type.Optional(Type.Number({ description: '是否开启无指纹' })), + no_code_finger_open: Type.Optional(Type.Number({ description: '未知' })), + no_finger_open: Type.Optional(Type.Number({ description: '未知' })), }); type PayloadType = Static; diff --git a/packages/napcat-onebot/action/file/flash/GetFilesetIdByCode.ts b/packages/napcat-onebot/action/file/flash/GetFilesetIdByCode.ts index f0079a3a..ebd8a722 100644 --- a/packages/napcat-onebot/action/file/flash/GetFilesetIdByCode.ts +++ b/packages/napcat-onebot/action/file/flash/GetFilesetIdByCode.ts @@ -8,10 +8,12 @@ export const GetFilesetIdPayloadSchema = Type.Object({ export type GetFilesetIdPayload = Static; -export class GetFilesetId extends OneBotAction { +export class GetFilesetId extends OneBotAction { override actionName = ActionName.GetFilesetId; override payloadSchema = GetFilesetIdPayloadSchema; - override returnSchema = Type.Any({ description: '文件集 ID' }); + override returnSchema = Type.Object({ + fileset_id: Type.String({ description: '文件集 ID' }) + }); override actionSummary = '获取文件集 ID'; override actionTags = ['文件扩展']; override payloadExample = { @@ -24,6 +26,7 @@ export class GetFilesetId extends OneBotAction { async _handle (payload: GetFilesetIdPayload) { // 适配share_link 防止被传 Link无法解析 const code = payload.share_code.includes('=') ? payload.share_code.split('=').slice(1).join('=') : payload.share_code; - return await this.core.apis.FlashApi.fromShareLinkFindSetId(code); + const result = await this.core.apis.FlashApi.fromShareLinkFindSetId(code); + return { fileset_id: result.fileSetId }; } } diff --git a/packages/napcat-onebot/action/go-cqhttp/GoCQHTTPGetModelShow.ts b/packages/napcat-onebot/action/go-cqhttp/GoCQHTTPGetModelShow.ts index 62ca9d88..3cdf1cfb 100644 --- a/packages/napcat-onebot/action/go-cqhttp/GoCQHTTPGetModelShow.ts +++ b/packages/napcat-onebot/action/go-cqhttp/GoCQHTTPGetModelShow.ts @@ -14,7 +14,7 @@ export const GoCQHTTPGetModelShowReturnSchema = Type.Array(Type.Object({ model_show: Type.String({ description: '显示名称' }), need_pay: Type.Boolean({ description: '是否需要付费' }), }), -}), { description: '模型显示列表' }); +}), { description: '机型显示列表' }); export type GoCQHTTPGetModelShowReturn = Static; @@ -22,8 +22,8 @@ export class GoCQHTTPGetModelShow extends OneBotAction { override actionName = ActionName.GoCQHTTP_SetModelShow; override payloadSchema = Type.Object({}); override returnSchema = Type.Null(); - override actionSummary = '设置模型显示'; - override actionDescription = '设置当前账号的设备模型显示名称'; + override actionSummary = '设置机型'; + override actionDescription = '设置当前账号的设备机型名称'; override actionTags = ['Go-CQHTTP']; override payloadExample = GoCQHTTPActionsExamples.GoCQHTTPSetModelShow.payload; override returnExample = GoCQHTTPActionsExamples.GoCQHTTPSetModelShow.response; diff --git a/packages/napcat-onebot/action/go-cqhttp/UploadGroupFile.ts b/packages/napcat-onebot/action/go-cqhttp/UploadGroupFile.ts index 72169932..58f3c3f9 100644 --- a/packages/napcat-onebot/action/go-cqhttp/UploadGroupFile.ts +++ b/packages/napcat-onebot/action/go-cqhttp/UploadGroupFile.ts @@ -9,7 +9,7 @@ import { GoCQHTTPActionsExamples } from './examples'; export const GoCQHTTPUploadGroupFilePayloadSchema = Type.Object({ group_id: Type.String({ description: '群号' }), - file: Type.String({ description: '本地文件路径' }), + file: Type.String({ description: '资源路径或URL' }), name: Type.String({ description: '文件名' }), folder: Type.Optional(Type.String({ description: '父目录 ID' })), folder_id: Type.Optional(Type.String({ description: '父目录 ID (兼容性字段)' })), // 临时扩展 @@ -29,7 +29,7 @@ export default class GoCQHTTPUploadGroupFile extends OneBotAction; diff --git a/packages/napcat-onebot/action/msg/SendMsg.ts b/packages/napcat-onebot/action/msg/SendMsg.ts index 6cfe8c7a..cf07d2f0 100644 --- a/packages/napcat-onebot/action/msg/SendMsg.ts +++ b/packages/napcat-onebot/action/msg/SendMsg.ts @@ -195,7 +195,7 @@ export class SendMsgBase extends OneBotAction { // const music: OB11MessageCustomMusic = messages[0] as OB11MessageCustomMusic; // if (music) { // } - // }\r + // } } // log("send msg:", peer, sendElements) diff --git a/packages/napcat-onebot/action/packet/SendPoke.ts b/packages/napcat-onebot/action/packet/SendPoke.ts index b906a3c6..48682174 100644 --- a/packages/napcat-onebot/action/packet/SendPoke.ts +++ b/packages/napcat-onebot/action/packet/SendPoke.ts @@ -5,9 +5,9 @@ import { Static, Type } from '@sinclair/typebox'; import { PacketActionsExamples } from './examples'; export const SendPokePayloadSchema = Type.Object({ - group_id: Type.Optional(Type.Union([Type.String(), Type.Number()], { description: '群号' })), - user_id: Type.Optional(Type.Union([Type.String(), Type.Number()], { description: '用户QQ' })), - target_id: Type.Optional(Type.Union([Type.String(), Type.Number()], { description: '目标QQ' })), + group_id: Type.Optional(Type.String({ description: '群号' })), + user_id: Type.Optional(Type.String({ description: '用户QQ' })), + target_id: Type.Optional(Type.String({ description: '目标QQ' })), }); export type SendPokePayload = Static; diff --git a/packages/napcat-schema/index.ts b/packages/napcat-schema/index.ts index fa9f5ddd..2d458b13 100644 --- a/packages/napcat-schema/index.ts +++ b/packages/napcat-schema/index.ts @@ -1,6 +1,6 @@ import { getAllHandlers } from '@/napcat-onebot/action/index'; import { AutoRegisterRouter } from '@/napcat-onebot/action/auto-register'; -import { writeFileSync, mkdirSync, existsSync } from 'node:fs'; +import { writeFileSync, existsSync } from 'node:fs'; import { resolve, dirname } from 'node:path'; import { TSchema } from '@sinclair/typebox'; import { fileURLToPath } from 'node:url'; @@ -17,7 +17,7 @@ interface ActionSchemaInfo { tags?: string[]; payloadExample?: unknown; returnExample?: unknown; - errorExamples?: Array<{ code: number, description: string }>; + errorExamples?: Array<{ code: number, description: string; }>; } export const actionSchemas: Record = {}; @@ -191,7 +191,7 @@ export function generateOpenAPI () { generateMissingReport(); } -function generateMissingReport() { +function generateMissingReport () { const missingReport: string[] = []; for (const [actionName, schemas] of Object.entries(actionSchemas)) { const missing: string[] = []; diff --git a/packages/napcat-webui-backend/src/api/Debug.ts b/packages/napcat-webui-backend/src/api/Debug.ts index 95683c17..3a35f9f4 100644 --- a/packages/napcat-webui-backend/src/api/Debug.ts +++ b/packages/napcat-webui-backend/src/api/Debug.ts @@ -12,6 +12,7 @@ import { ActionMap } from '@/napcat-onebot/action'; import { NapCatCore } from '@/napcat-core/index'; import { NapCatOneBot11Adapter } from '@/napcat-onebot/index'; import { OB11EmitEventContent, OB11NetworkReloadType } from '@/napcat-onebot/network/index'; +import { OneBotAction } from '@/napcat-onebot/action/OneBotAction'; import json5 from 'json5'; type ActionNameType = typeof ActionName[keyof typeof ActionName]; @@ -19,6 +20,40 @@ type ActionNameType = typeof ActionName[keyof typeof ActionName]; const router: Router = Router(); const DEFAULT_ADAPTER_NAME = 'debug-primary'; +/** + * 获取所有 Action 的 Schema 信息 + */ +router.get('/schemas', async (_req: Request, res: Response) => { + try { + const obContext = WebUiDataRuntime.getOneBotContext(); + if (!obContext) { + return sendError(res, 'OneBot 未初始化'); + } + const schemas: Record = {}; + + // 遍历 ActionName 中定义的所有路由 + for (const key in ActionName) { + const actionName = (ActionName as any)[key]; + if (actionName === ActionName.Unknown) continue; + + const handler = obContext.actions.get(actionName); + if (handler) { + const action = handler as OneBotAction; + schemas[actionName] = { + description: action.actionSummary || action.actionDescription, + payload: action.payloadSchema, + response: action.returnSchema, + payloadExample: action.payloadExample, + }; + } + } + + sendSuccess(res, schemas); + } catch (error: unknown) { + sendError(res, (error as Error).message); + } +}); + /** * 统一的调试适配器 * 用于注入到 OneBot NetworkManager,接收所有事件并转发给 WebSocket 客户端 diff --git a/packages/napcat-webui-frontend/package.json b/packages/napcat-webui-frontend/package.json index 503f1734..b6a24714 100644 --- a/packages/napcat-webui-frontend/package.json +++ b/packages/napcat-webui-frontend/package.json @@ -55,6 +55,7 @@ "@monaco-editor/loader": "^1.4.0", "@react-aria/visually-hidden": "^3.8.19", "@reduxjs/toolkit": "^2.5.1", + "@sinclair/typebox": "^0.34.41", "@uidotdev/usehooks": "^2.4.1", "@uiw/react-codemirror": "^4.25.4", "@xterm/addon-canvas": "^0.7.0", diff --git a/packages/napcat-webui-frontend/src/components/onebot/api/debug.tsx b/packages/napcat-webui-frontend/src/components/onebot/api/debug.tsx index 36930f9a..aa5b416f 100644 --- a/packages/napcat-webui-frontend/src/components/onebot/api/debug.tsx +++ b/packages/napcat-webui-frontend/src/components/onebot/api/debug.tsx @@ -21,7 +21,8 @@ import PageLoading from '@/components/page_loading'; import { request } from '@/utils/request'; -import { generateDefaultJson, parse } from '@/utils/zod'; +import { BaseResponseSchema, parseTypeBox, generateDefaultFromTypeBox } from '@/utils/typebox'; +import { Type } from '@sinclair/typebox'; import DisplayStruct from './display_struct'; @@ -58,8 +59,16 @@ const OneBotApiDebug = forwardRef((props const [responseHeight, setResponseHeight] = useState(240); const [storedHeight, setStoredHeight] = useLocalStorage('napcat_debug_response_height', 240); - const parsedRequest = parse(data.request); - const parsedResponse = parse(data.response); + const parsedRequest = parseTypeBox(data?.payload); + + // 将返回值的 data 结构包装进 BaseResponseSchema 进行展示 + // 使用解构属性的方式重新构建对象,确保 parseTypeBox 能够识别为 object 类型 + const wrappedResponseSchema = Type.Object({ + ...BaseResponseSchema.properties, + data: data?.response || Type.Any({ description: '数据' }) + }); + + const parsedResponse = parseTypeBox(wrappedResponseSchema); const [backgroundImage] = useLocalStorage(key.backgroundImage, ''); const hasBackground = !!backgroundImage; @@ -75,7 +84,7 @@ const OneBotApiDebug = forwardRef((props // 如果有 adapterName,走后端转发 if (adapterName) { request.post(`/api/Debug/call/${adapterName}`, { - action: path.replace(/^\//, ''), // 去掉开头的 / + action: path, params: parsedRequestBody }, { headers: { @@ -154,7 +163,11 @@ const OneBotApiDebug = forwardRef((props })); useEffect(() => { - setRequestBody(generateDefaultJson(data.request)); + if (data?.payloadExample) { + setRequestBody(JSON.stringify(data.payloadExample, null, 2)); + } else { + setRequestBody(JSON.stringify(generateDefaultFromTypeBox(data?.payload), null, 2)); + } setResponseContent(''); setResponseStatus(null); }, [path]); diff --git a/packages/napcat-webui-frontend/src/const/ob_api/group.ts b/packages/napcat-webui-frontend/src/const/ob_api/group.ts deleted file mode 100644 index 39d69fe0..00000000 --- a/packages/napcat-webui-frontend/src/const/ob_api/group.ts +++ /dev/null @@ -1,744 +0,0 @@ -import { z } from 'zod'; - -import messageNodeSchema from './message/node'; -import { baseResponseSchema, commonResponseDataSchema } from './response'; - -const oneBotHttpApiGroup = { - '/set_group_kick': { - description: '群踢人', - request: z.object({ - group_id: z.union([z.string(), z.number()]).describe('群号'), - user_id: z.union([z.string(), z.number()]).describe('QQ 号'), - reject_add_request: z.boolean().describe('拒绝此人的加群请求'), - }), - response: baseResponseSchema, - }, - '/set_group_ban': { - description: '群禁言', - request: z.object({ - group_id: z.union([z.string(), z.number()]).describe('群号'), - user_id: z.union([z.string(), z.number()]).describe('QQ 号'), - duration: z.number(), - }), - response: baseResponseSchema, - }, - '/get_group_system_msg': { - description: '获取群系统消息', - request: z.object({}), - response: baseResponseSchema.extend({ - data: z.object({ - InvitedRequest: z - .array( - z - .object({ - request_id: z.string().describe('请求 ID'), - invitor_uin: z.string().describe('邀请人 QQ 号'), - invitor_nick: z.string().describe('邀请人昵称'), - group_id: z.string().describe('群号'), - message: z.string().describe('入群回答'), - group_name: z.string().describe('群名称'), - checked: z.boolean().describe('是否已处理'), - actor: z.string().describe('处理人 QQ 号'), - }) - .describe('邀请入群请求') - ) - .describe('邀请入群请求列表'), - join_requests: z.array( - z.object({ - request_id: z.string().describe('请求 ID'), - requester_uin: z.string().describe('请求人 QQ 号'), - requester_nick: z.string().describe('请求人昵称'), - group_id: z.string().describe('群号'), - message: z.string().describe('入群回答'), - group_name: z.string().describe('群名称'), - checked: z.boolean().describe('是否已处理'), - actor: z.string().describe('处理人 QQ 号'), - }) - ), - }), - }), - }, - '/get_essence_msg_list': { - description: '获取精华消息', - request: z.object({ - group_id: z.union([z.string(), z.number()]).describe('群号'), - }), - response: baseResponseSchema.extend({ - data: z - .array( - z - .object({ - msg_seq: z.number().describe('消息序号'), - msg_random: z.number().describe('消息随机数'), - sender_id: z.number().describe('发送人 QQ 号'), - sender_nick: z.string().describe('发送人昵称'), - operator_id: z.number().describe('操作人 QQ 号'), - operator_nick: z.string().describe('操作人昵称'), - message_id: z.string().describe('消息 ID'), - operator_time: z.string().describe('操作时间'), - content: z.array(messageNodeSchema), - }) - .describe('精华消息') - ) - .describe('精华消息列表'), - }), - }, - '/set_group_whole_ban': { - description: '全员禁言', - request: z.object({ - group_id: z.union([z.string(), z.number()]).describe('群号'), - enable: z.boolean().describe('是否开启'), - }), - response: baseResponseSchema, - }, - '/set_group_portrait': { - description: '设置群头像', - request: z.object({ - group_id: z.union([z.string(), z.number()]).describe('群号'), - file: z.string().describe('图片文件路径,服务器本地路径或远程 URL'), - }), - response: baseResponseSchema.extend({ - data: commonResponseDataSchema, - }), - }, - '/set_group_admin': { - description: '设置群管理', - request: z.object({ - group_id: z.union([z.string(), z.number()]).describe('群号'), - user_id: z.union([z.string(), z.number()]).describe('QQ 号'), - enable: z.boolean().describe('是否设置为管理员'), - }), - response: baseResponseSchema, - }, - '/set_essence_msg': { - description: '设置群精华消息', - request: z.object({ - message_id: z.union([z.string(), z.number()]).describe('消息 ID'), - }), - response: baseResponseSchema.extend({ - data: z.object({ - errCode: z.number().describe('错误码'), - errMsg: z.string().describe('错误信息'), - result: z - .object({ - wording: z.string().describe('?'), - digestUin: z.string().describe('?QQ号'), - digestTime: z.number().describe('设置时间?'), - msg: z - .object({ - groupCode: z.string().describe('群号'), - msgSeq: z.number().describe('消息序号'), - msgRandom: z.number().describe('消息随机数'), - msgContent: z.array(messageNodeSchema).describe('消息内容'), - textSize: z.string().describe('文本大小'), - picSize: z.string().describe('图片大小'), - videoSize: z.string().describe('视频大小'), - senderUin: z.string().describe('发送人 QQ 号'), - senderTime: z.number().describe('发送时间'), - addDigestUin: z.string().describe('添加精华消息人 QQ 号'), - addDigestTime: z.number().describe('添加精华消息时间'), - startTime: z.number().describe('开始时间'), - latestMsgSeq: z.number().describe('最新消息序号'), - opType: z.number().describe('操作类型'), - }) - .describe('消息内容'), - errorCode: z.number().describe('错误码'), - }) - .describe('结果'), - }), - }), - }, - '/set_group_card': { - description: '设置群成员名片', - request: z.object({ - group_id: z.union([z.string(), z.number()]).describe('群号'), - user_id: z.union([z.string(), z.number()]).describe('QQ 号'), - card: z.string().describe('名片'), - }), - response: baseResponseSchema, - }, - '/delete_essence_msg': { - description: '删除群精华消息', - request: z.object({ - message_id: z.union([z.string(), z.number()]).describe('消息 ID'), - }), - - response: baseResponseSchema.extend({ - data: z.object({ - errCode: z.number().describe('错误码'), - errMsg: z.string().describe('错误信息'), - result: z - .object({ - wording: z.string().describe('?'), - digestUin: z.string().describe('?QQ号'), - digestTime: z.number().describe('设置时间?'), - msg: z.object({ - groupCode: z.string().describe('群号'), - msgSeq: z.number().describe('消息序号'), - msgRandom: z.number().describe('消息随机数'), - msgContent: z.array(messageNodeSchema).describe('消息内容'), - textSize: z.string().describe('文本大小'), - picSize: z.string().describe('图片大小'), - videoSize: z.string().describe('视频大小'), - senderUin: z.string().describe('发送人 QQ 号'), - senderTime: z.number().describe('发送时间'), - addDigestUin: z.string().describe('添加精华消息人 QQ 号'), - addDigestTime: z.number().describe('添加精华消息时间'), - startTime: z.number().describe('开始时间'), - latestMsgSeq: z.number().describe('最新消息序号'), - opType: z.number().describe('操作类型'), - }), - errorCode: z.number().describe('错误码'), - }) - .describe('结果'), - }), - }), - }, - '/set_group_name': { - description: '设置群名称', - request: z.object({ - group_id: z.union([z.string(), z.number()]).describe('群号'), - group_name: z.string().describe('群名称'), - }), - response: baseResponseSchema, - }, - '/set_group_leave': { - description: '退出群聊', - request: z.object({ - group_id: z.union([z.string(), z.number()]).describe('群号'), - }), - response: baseResponseSchema, - }, - '/_send_group_notice': { - description: '发送群公告', - request: z.object({ - group_id: z.union([z.string(), z.number()]).describe('群号'), - content: z.string().describe('公告内容'), - image: z.string().optional().describe('图片地址'), - }), - response: baseResponseSchema, - }, - '/_get_group_notice': { - description: '获取群公告', - request: z.object({ - group_id: z.union([z.string(), z.number()]).describe('群号'), - }), - response: baseResponseSchema.extend({ - data: z.array( - z.object({ - notice_id: z.string().describe('公告 ID'), - sender_id: z.number().describe('发送人 QQ 号'), - publish_time: z.number().describe('发布时间'), - message: z.object({ - text: z.string().describe('文本内容'), - image: z - .array( - z - .object({ - id: z.string().describe('图片 ID'), - height: z.string().describe('高度'), - width: z.string().describe('宽度'), - }) - .describe('图片信息') - ) - .describe('图片内容列表'), - }), - }) - ), - }), - }, - '/set_group_special_title': { - description: '设置群成员专属头衔', - request: z.object({ - group_id: z.union([z.string(), z.number()]).describe('群号'), - user_id: z.union([z.string(), z.number()]).describe('QQ 号'), - special_title: z.string().describe('专属头衔内容'), - }), - response: baseResponseSchema, - }, - '/upload_group_file': { - description: '上传群文件', - request: z.object({ - group_id: z.union([z.string(), z.number()]).describe('群号'), - file: z.string().describe('文件路径'), - name: z.string().describe('文件名'), - folder_id: z.string().describe('文件夹 ID'), - }), - response: baseResponseSchema.extend({ - data: commonResponseDataSchema, - }), - }, - '/set_group_add_request': { - description: '处理加群请求', - request: z.object({ - flag: z.string().describe('请求ID'), - approve: z.boolean().describe('是否同意'), - reason: z.string().optional().describe('拒绝理由'), - }), - response: baseResponseSchema, - }, - '/get_group_info': { - description: '获取群信息', - request: z.object({ - group_id: z.union([z.string(), z.number()]).describe('群号'), - }), - response: baseResponseSchema.extend({ - data: z.object({}), - }), - }, - '/get_group_info_ex': { - description: '获取群信息扩展', - request: z.object({ - group_id: z.union([z.string(), z.number()]).describe('群号'), - }), - response: baseResponseSchema.extend({ - data: z - .object({ - groupCode: z.string().describe('群号'), - resultCode: z.number().describe('结果码'), - extInfo: z - .object({ - groupInfoExtSeq: z.number().describe('群信息序列号'), - reserve: z.number().describe('?'), - luckyWordId: z.string().describe('幸运字符ID'), - lightCharNum: z.number().describe('?'), - luckyWord: z.string().describe('幸运字符'), - starId: z.number().describe('?'), - essentialMsgSwitch: z.number().describe('精华消息开关'), - todoSeq: z.number().describe('?'), - blacklistExpireTime: z.number().describe('黑名单过期时间'), - isLimitGroupRtc: z.number().describe('是否限制群视频通话'), - companyId: z.number().describe('公司ID'), - hasGroupCustomPortrait: z.number().describe('是否有群自定义头像'), - bindGuildId: z.string().describe('绑定频道ID?'), - groupOwnerId: z - .object({ - memberUin: z.string().describe('群主QQ号'), - memberUid: z.string().describe('群主ID'), - memberQid: z.string().describe('群主QID'), - }) - .describe('群主信息'), - essentialMsgPrivilege: z.number().describe('精华消息权限'), - msgEventSeq: z.string().describe('消息事件序列号'), - inviteRobotSwitch: z.number().describe('邀请机器人开关'), - gangUpId: z.string().describe('?'), - qqMusicMedalSwitch: z.number().describe('QQ音乐勋章开关'), - showPlayTogetherSwitch: z.number().describe('显示一起玩开关'), - groupFlagPro1: z.string()?.describe('群标识1'), - groupBindGuildIds: z - .object({ - guildIds: z.array(z.string()), - }) - .describe('绑定频道ID列表?'), - viewedMsgDisappearTime: z.string().describe('消息消失时间'), - groupExtFlameData: z.object({ - switchState: z.number().describe('开关状态'), - state: z.number().describe('状态'), - dayNums: z.array(z.number()).describe('天数列表'), - version: z.number().describe('版本号'), - updateTime: z.string().describe('更新时间'), - isDisplayDayNum: z.boolean().describe('是否显示天数'), - }), - groupBindGuildSwitch: z.number().describe('绑定频道开关'), - groupAioBindGuildId: z.string().describe('AIO绑定频道ID'), - groupExcludeGuildIds: z - .object({ - guildIds: z.array(z.string()).describe('排除频道ID'), - }) - .describe('排除频道ID列表?'), - fullGroupExpansionSwitch: z.number().describe('全员群扩容开关'), - fullGroupExpansionSeq: z.string().describe('全员群扩容序列号'), - inviteRobotMemberSwitch: z - .number() - .describe('邀请机器人成员开关'), - inviteRobotMemberExamine: z - .number() - .describe('邀请机器人成员审核'), - groupSquareSwitch: z.number().describe('群广场开关'), - }) - .describe('扩展信息'), - }) - .describe('结果'), - }), - }, - '/create_group_file_folder': { - description: '创建群文件夹', - request: z.object({ - group_id: z.union([z.string(), z.number()]).describe('群号'), - folder_name: z.string().describe('文件夹名称'), - }), - response: baseResponseSchema.extend({ - data: z - .object({ - result: z - .object({ - retCode: z.number().describe('结果码'), - retMsg: z.string().describe('结果信息'), - clientWording: z.string().describe('客户端提示'), - }) - .describe('结果'), - groupItem: z - .object({ - peerId: z.string().describe('?'), - type: z.string().describe('类型'), - folderInfo: z - .object({ - folderId: z.string().describe('文件夹 ID'), - parentFolderId: z.string().describe('父文件夹 ID'), - folderName: z.string().describe('文件夹名称'), - createTime: z.number().describe('创建时间'), - modifyTime: z.number().describe('修改时间'), - createUin: z.string().describe('创建人 QQ 号'), - creatorName: z.string().describe('创建人昵称'), - totalFileCount: z.string().describe('文件总数'), - modifyUin: z.string().describe('修改人 QQ 号'), - modifyName: z.string().describe('修改人昵称'), - usedSpace: z.string().describe('已使用空间'), - }) - .describe('文件夹信息'), - }) - .describe('群文件夹信息'), - }) - .describe('数据'), - }), - }, - '/delete_group_file': { - description: '删除群文件', - request: z.object({ - group_id: z.union([z.string(), z.number()]).describe('群号'), - file_id: z.string().describe('文件 ID'), - }), - response: baseResponseSchema.extend({ - data: z - .object({ - result: z.number().describe('结果码'), - errMsg: z.string().describe('错误信息'), - transGroupFileResult: z - .object({ - result: z - .object({ - retCode: z.number().describe('结果码'), - retMsg: z.string().describe('结果信息'), - clientWording: z.string().describe('客户端提示'), - }) - .describe('结果'), - successFileIdList: z - .array(z.string()) - .describe('成功文件 ID 列表'), - failFileIdList: z.array(z.string()).describe('失败文件 ID 列表'), - }) - .describe('删除群文件结果'), - }) - .describe('结果'), - }), - }, - '/delete_group_folder': { - description: '删除群文件夹', - request: z.object({ - group_id: z.union([z.string(), z.number()]).describe('群号'), - folder_id: z.string().describe('文件夹 ID'), - }), - response: baseResponseSchema.extend({ - data: z.object({ - retCode: z.number().describe('结果码'), - retMsg: z.string().describe('结果信息'), - clientWording: z.string().describe('客户端提示'), - }), - }), - }, - '/get_group_file_system_info': { - description: '获取群文件系统信息', - request: z.object({ - group_id: z.union([z.string(), z.number()]).describe('群号'), - }), - response: baseResponseSchema.extend({ - data: z.object({ - file_count: z.number().describe('文件总数'), - limit_count: z.number().describe('文件总数限制'), - used_space: z.number().describe('已使用空间'), - total_space: z.number().describe('总空间'), - }), - }), - }, - '/get_group_root_files': { - description: '获取群根目录文件列表', - request: z.object({ - group_id: z.union([z.string(), z.number()]).describe('群号'), - }), - response: baseResponseSchema.extend({ - data: z.array( - z.object({ - files: z - .array( - z - .object({ - group_id: z.number().describe('群号'), - file_id: z.string().describe('文件 ID'), - file_name: z.string().describe('文件名'), - busid: z.number().describe('?'), - size: z.number().describe('文件大小'), - upload_time: z.number().describe('上传时间'), - dead_time: z.number().describe('过期时间'), - modify_time: z.number().describe('修改时间'), - download_times: z.number().describe('下载次数'), - uploader: z.number().describe('上传人 QQ 号'), - uploader_name: z.string().describe('上传人昵称'), - }) - .describe('文件信息') - ) - .describe('文件列表'), - folders: z - .array( - z - .object({ - group_id: z.number().describe('群号'), - folder_id: z.string().describe('文件夹 ID'), - folder: z.string().describe('文件夹?'), - folder_name: z.string().describe('文件夹名称'), - create_time: z.string().describe('创建时间'), - creator: z.string().describe('创建人 QQ 号'), - creator_name: z.string().describe('创建人昵称'), - total_file_count: z.string().describe('文件总数'), - }) - .describe('文件夹信息') - ) - .describe('文件夹列表'), - }) - ), - }), - }, - '/get_group_files_by_folder': { - description: '获取群子目录文件列表', - request: z.object({ - group_id: z.union([z.string(), z.number()]).describe('群号'), - folder_id: z.string().describe('文件夹 ID'), - file_count: z.number().describe('文件数量'), - }), - response: baseResponseSchema.extend({ - data: z.object({ - files: z - .array( - z - .object({ - group_id: z.number().describe('群号'), - file_id: z.string().describe('文件 ID'), - file_name: z.string().describe('文件名'), - busid: z.number().describe('?'), - size: z.number().describe('文件大小'), - upload_time: z.number().describe('上传时间'), - dead_time: z.number().describe('过期时间'), - modify_time: z.number().describe('修改时间'), - download_times: z.number().describe('下载次数'), - uploader: z.number().describe('上传人 QQ 号'), - uploader_name: z.string().describe('上传人昵称'), - }) - .describe('文件信息') - ) - .describe('文件列表'), - folders: z - .array( - z - .object({ - group_id: z.number().describe('群号'), - folder_id: z.string().describe('文件夹 ID'), - folder: z.string().describe('文件夹?'), - folder_name: z.string().describe('文件夹名称'), - create_time: z.string().describe('创建时间'), - creator: z.string().describe('创建人 QQ 号'), - creator_name: z.string().describe('创建人昵称'), - total_file_count: z.string().describe('文件总数'), - }) - .describe('文件夹信息') - ) - .describe('文件夹列表'), - }), - }), - }, - '/get_group_file_url': { - description: '获取群文件下载链接', - request: z.object({ - group_id: z.union([z.string(), z.number()]).describe('群号'), - file_id: z.string().describe('文件 ID'), - }), - response: baseResponseSchema.extend({ - data: z.object({ - url: z.string().describe('下载链接'), - }), - }), - }, - '/get_group_list': { - description: '获取群列表', - request: z.object({ - next_token: z.string().optional().describe('下一页标识'), - }), - response: baseResponseSchema.extend({ - data: z.array(z.object({})), - }), - }, - '/get_group_member_info': { - description: '获取群成员信息', - request: z.object({ - group_id: z.union([z.string(), z.number()]).describe('群号'), - user_id: z.union([z.string(), z.number()]).describe('QQ 号'), - no_cache: z.boolean().describe('是否不使用缓存'), - }), - response: baseResponseSchema.extend({ - data: z.object({}), - }), - }, - '/get_group_member_list': { - description: '获取群成员列表', - request: z.object({ - group_id: z.union([z.string(), z.number()]).describe('群号'), - no_cache: z.boolean().describe('是否不使用缓存'), - }), - response: baseResponseSchema.extend({ - data: z.array(z.object({})), - }), - }, - '/get_group_honor_info': { - description: '获取群荣誉', - request: z.object({ - group_id: z.union([z.string(), z.number()]).describe('群号'), - }), - response: baseResponseSchema.extend({ - data: z - .object({ - group_id: z.number().describe('群号'), - current_talkative: z - .object({ - user_id: z.number().describe('QQ 号'), - avatar: z.string().describe('头像 URL'), - nickname: z.string().describe('昵称'), - day_count: z.number().describe('天数'), - description: z.string().describe('描述'), - }) - .describe('当前龙王'), - talkative_list: z - .array( - z.object({ - user_id: z.number().describe('QQ 号'), - avatar: z.string().describe('头像 URL'), - nickname: z.string().describe('昵称'), - day_count: z.number().describe('天数'), - description: z.string().describe('描述'), - }) - ) - .describe('龙王榜'), - performer_list: z - .array( - z.object({ - user_id: z.number().describe('QQ 号'), - avatar: z.string().describe('头像 URL'), - nickname: z.string().describe('昵称'), - description: z.string().describe('描述'), - }) - ) - .describe('?'), - legend_list: z.array(z.string()).describe('?'), - emotion_list: z.array(z.string()).describe('?'), - strong_newbie_list: z.array(z.string()).describe('?'), - }) - .describe('群荣誉信息'), - }), - }, - '/get_group_at_all_remain': { - description: '获取群 @全体成员 剩余次数', - request: z.object({ - group_id: z.union([z.string(), z.number()]).describe('群号'), - }), - response: baseResponseSchema.extend({ - data: z.object({ - can_at_all: z.boolean().describe('是否可以 @全体成员'), - remain_at_all_count_for_group: z.number().describe('剩余次数(group?)'), - remain_at_all_count_for_uin: z.number().describe('剩余次数(qq?)'), - }), - }), - }, - '/get_group_ignored_notifies': { - description: '获取群过滤系统消息', - request: z.object({ - group_id: z.union([z.string(), z.number()]).describe('群号'), - }), - response: baseResponseSchema.extend({ - data: z.object({ - join_requests: z - .array( - z.object({ - request_id: z.string().describe('请求 ID'), - requester_uin: z.string().describe('请求人 QQ 号'), - requester_nick: z.string().describe('请求人昵称'), - group_id: z.string().describe('群号'), - group_name: z.string().describe('群名称'), - checked: z.boolean().describe('是否已处理'), - actor: z.string().describe('处理人 QQ 号'), - }) - ) - .describe('入群请求列表'), - }), - }), - }, - '/set_group_sign': { - description: '设置群打卡', - request: z.object({ - group_id: z.union([z.string(), z.number()]).describe('群号'), - }), - response: baseResponseSchema, - }, - '/send_group_sign': { - description: '发送群打卡', - request: z.object({ - group_id: z.union([z.string(), z.number()]).describe('群号'), - }), - response: baseResponseSchema, - }, - '/get_ai_characters': { - description: '获取AI语音人物', - request: z.object({ - group_id: z.union([z.string(), z.number()]).describe('群号'), - chat_type: z.union([z.string(), z.number()]).describe('聊天类型'), - }), - response: baseResponseSchema.extend({ - data: z.array( - z.object({ - type: z.string().describe('类型'), - characters: z.array( - z - .object({ - character_id: z.string().describe('人物 ID'), - character_name: z.string().describe('人物名称'), - preview_url: z.string().describe('预览音频地址'), - }) - .describe('人物信息') - ), - }) - ), - }), - }, - '/send_group_ai_record': { - description: '发送群AI语音', - request: z.object({ - group_id: z.union([z.string(), z.number()]).describe('群号'), - character: z.string().describe('人物ID'), - text: z.string().describe('文本内容'), - }), - response: baseResponseSchema.extend({ - data: z.object({ - message_id: z.string().describe('消息 ID'), - }), - }), - }, - '/get_ai_record': { - description: '获取AI语音', - request: z.object({ - group_id: z.string().describe('群号'), - character: z.string().describe('人物ID'), - text: z.string().describe('文本内容'), - }), - response: baseResponseSchema.extend({ - data: z.string(), - }), - }, -} as const; - -export default oneBotHttpApiGroup; diff --git a/packages/napcat-webui-frontend/src/const/ob_api/index.ts b/packages/napcat-webui-frontend/src/const/ob_api/index.ts index ab075756..58218c84 100644 --- a/packages/napcat-webui-frontend/src/const/ob_api/index.ts +++ b/packages/napcat-webui-frontend/src/const/ob_api/index.ts @@ -1,34 +1,39 @@ -import { ZodSchema } from 'zod'; +import { TSchema } from '@sinclair/typebox'; -import oneBotHttpApiGroup from './group'; -import oneBotHttpApiMessage from './message'; -import oneBotHttpApiSystem from './system'; -import oneBotHttpApiUser from './user'; +export interface OneBotHttpApiContent { + description?: string; + payload: TSchema; + response: TSchema; + payloadExample?: any; +} -type AllKey = - | keyof typeof oneBotHttpApiUser - | keyof typeof oneBotHttpApiMessage - | keyof typeof oneBotHttpApiGroup - | keyof typeof oneBotHttpApiSystem; +export type OneBotHttpApi = Record; -export type OneBotHttpApi = Record< - AllKey, - { - description?: string - request: ZodSchema - response: ZodSchema +let oneBotHttpApi: OneBotHttpApi = {}; + +export async function fetchOneBotHttpApi (): Promise { + try { + const response = await fetch('/api/Debug/schemas', { + headers: { + 'Authorization': `Bearer ${localStorage.getItem('token')}` + } + }); + const data = await response.json(); + if (data.code === 0) { + oneBotHttpApi = data.data; + return oneBotHttpApi; + } + } catch (error) { + console.error('Failed to fetch OneBot HTTP API schemas:', error); } ->; + return {}; +} -const oneBotHttpApi: OneBotHttpApi = { - ...oneBotHttpApiUser, - ...oneBotHttpApiMessage, - ...oneBotHttpApiGroup, - ...oneBotHttpApiSystem, -} as const; +export function getOneBotHttpApi () { + return oneBotHttpApi; +} -export type OneBotHttpApiPath = keyof OneBotHttpApi; - -export type OneBotHttpApiContent = OneBotHttpApi[OneBotHttpApiPath]; +export type OneBotHttpApiPath = string; export default oneBotHttpApi; + diff --git a/packages/napcat-webui-frontend/src/const/ob_api/message/group.ts b/packages/napcat-webui-frontend/src/const/ob_api/message/group.ts deleted file mode 100644 index f5546e63..00000000 --- a/packages/napcat-webui-frontend/src/const/ob_api/message/group.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { z } from 'zod'; -import type { ZodSchema } from 'zod'; - -import { baseResponseSchema, commonResponseDataSchema } from '../response'; -import messageNodeSchema, { nodeMessage } from './node'; - -const oneBotHttpApiMessageGroup: Record< - string, - { - description?: string - request: ZodSchema - response: ZodSchema - } -> = { - '/send_group_msg': { - description: '发送群消息', - request: z - .object({ - group_id: z - .union([z.string(), z.number()]) - .describe('群号') - .describe('群号'), - message: z.array(messageNodeSchema).describe('消息内容'), - }) - .refine( - (data) => { - const hasReply = data.message.some((item) => item.type === 'reply'); - - if (hasReply) { - return data.message[0].type === 'reply'; - } - - return true; - }, - { - message: - '如果 message 包含 reply 类型的消息,那么只能包含一个,而且排在最前面', - } - ), - response: baseResponseSchema.extend({ - data: commonResponseDataSchema, - }), - }, - '/send_group_forward_msg': { - description: '发送群合并转发消息', - request: z.object({ - group_id: z.union([z.string(), z.number()]).describe('群号'), - messages: z.array(nodeMessage).describe('消息内容'), - news: z - .array( - z.object({ - text: z.string(), - }) - ) - .describe('?'), - prompt: z.string().describe('外显'), - summary: z.string().describe('底下文本'), - source: z.string().describe('内容'), - }), - response: baseResponseSchema.extend({ - data: commonResponseDataSchema, - }), - }, - '/forward_group_single_msg': { - description: '消息转发到群', - request: z.object({ - group_id: z.union([z.string(), z.number()]).describe('群号'), - message_id: z.union([z.string(), z.number()]).describe('消息 ID'), - }), - response: baseResponseSchema.extend({ - data: commonResponseDataSchema, - }), - }, - '/group_poke': { - description: '发送戳一戳', - request: z.object({ - group_id: z.union([z.string(), z.number()]).describe('群号'), - user_id: z.union([z.string(), z.number()]).describe('对方QQ号'), - }), - response: baseResponseSchema, - }, -}; - -export default oneBotHttpApiMessageGroup; diff --git a/packages/napcat-webui-frontend/src/const/ob_api/message/index.ts b/packages/napcat-webui-frontend/src/const/ob_api/message/index.ts deleted file mode 100644 index 9e624ea8..00000000 --- a/packages/napcat-webui-frontend/src/const/ob_api/message/index.ts +++ /dev/null @@ -1,290 +0,0 @@ -import { z } from 'zod'; - -import { baseResponseSchema, commonResponseDataSchema } from '../response'; -import oneBotHttpApiMessageGroup from './group'; -import messageNodeSchema from './node'; -import oneBotHttpApiMessagePrivate from './private'; - -const fileSchema = z - .object({ - file: z.string().describe('路径或链接'), - url: z.string().describe('路径或链接'), - file_size: z.string().describe('文件大小'), - file_name: z.string().describe('文件名'), - base64: z.string().describe('文件base64编码'), - }) - .describe('文件'); -const messageSchema = z - .object({ - self_id: z.number().describe('自己QQ号'), - user_id: z.number().describe('发送人QQ号'), - time: z.number().describe('发送时间'), - message_id: z.number().describe('消息ID'), - message_seq: z.number().describe('消息序号'), - real_id: z.number().describe('?ID'), - message_type: z.string().describe('消息类型'), - sender: z - .object({ - user_id: z.number().describe('发送人QQ号'), - nickname: z.string().describe('昵称'), - sex: z.enum(['male', 'female', 'unknown']).describe('性别'), - age: z.number().describe('年龄'), - card: z.string().describe('名片'), - role: z.enum(['owner', 'admin', 'member']).describe('角色'), - }) - .describe('发送人信息'), - raw_message: z.string().describe('原始消息'), - font: z.number().describe('字体'), - sub_type: z.string().describe('子类型'), - message: z.array(messageNodeSchema).describe('消息内容'), - message_format: z.string().describe('消息格式'), - post_type: z.string().describe('?'), - message_sent_type: z.string().describe('消息发送类型'), - group_id: z.number().describe('群号'), - }) - .describe('消息'); - -const oneBotHttpApiMessage = { - ...oneBotHttpApiMessagePrivate, - ...oneBotHttpApiMessageGroup, - '/mark_msg_as_read': { - description: '标记消息已读', - request: z - .object({ - group_id: z - .union([z.string(), z.number()]) - .optional() - .describe('群号,与 user_id 二选一'), - user_id: z - .union([z.string(), z.number()]) - .optional() - .describe('用户QQ号,与 group_id 二选一'), - }) - .refine( - (data) => - (data.group_id && !data.user_id) || (!data.group_id && data.user_id), - { - message: 'group_id 和 user_id 必须二选一,且不能同时存在或同时为空', - path: ['group_id', 'user_id'], - } - ), - response: baseResponseSchema, - }, - '/mark_group_msg_as_read': { - description: '标记群消息已读', - request: z.object({ - group_id: z.union([z.string(), z.number()]).describe('群号'), - }), - response: baseResponseSchema, - }, - '/mark_private_msg_as_read': { - description: '标记私聊消息已读', - request: z.object({ - user_id: z.union([z.string(), z.number()]).describe('用户QQ号'), - }), - response: baseResponseSchema, - }, - '/_mark_all_as_read': { - description: '标记所有消息已读', - request: z.object({}), - response: baseResponseSchema, - }, - '/delete_msg': { - description: '撤回消息', - request: z.object({ - message_id: z.union([z.string(), z.number()]).describe('消息ID'), - }), - response: baseResponseSchema, - }, - '/get_msg': { - description: '获取消息', - request: z.object({ - message_id: z.union([z.string(), z.number()]).describe('消息ID'), - }), - response: baseResponseSchema.extend({ - data: z.object({}), - }), - }, - '/get_image': { - description: '获取图片', - request: z.object({ - file_id: z.string().describe('文件ID'), - }), - response: baseResponseSchema.extend({ - data: fileSchema, - }), - }, - '/get_record': { - description: '获取语音', - request: z.object({ - file_id: z.string().describe('文件ID'), - out_format: z - .enum(['mp3', 'amr', 'wma', 'm4a', 'spx', 'ogg', 'wav', 'flac']) - .describe('输出格式'), - }), - response: baseResponseSchema.extend({ - data: fileSchema, - }), - }, - '/get_file': { - description: '获取文件', - request: z.object({ - file_id: z.string().describe('文件ID'), - }), - response: baseResponseSchema.extend({ - data: fileSchema, - }), - }, - '/get_group_msg_history': { - description: '获取群消息历史', - request: z.object({ - group_id: z.union([z.string(), z.number()]).describe('群号'), - message_seq: z.union([z.string(), z.number()]).describe('消息序号'), - count: z.number().int().positive().describe('获取数量'), - reverse_order: z.boolean().describe('是否倒序'), - }), - response: baseResponseSchema.extend({ - data: z.object({ - messages: z.array(messageSchema).describe('消息列表'), - }), - }), - }, - '/set_msg_emoji_like': { - description: '贴表情', - request: z.object({ - message_id: z.union([z.string(), z.number()]).describe('消息ID'), - emoji_id: z.number().describe('表情ID'), - set: z.boolean().describe('?'), - }), - response: baseResponseSchema.extend({ - data: commonResponseDataSchema, - }), - }, - '/get_friend_msg_history': { - description: '获取好友消息历史', - request: z.object({ - user_id: z.union([z.string(), z.number()]).describe('用户QQ号'), - message_seq: z.union([z.string(), z.number()]).describe('消息序号'), - count: z.number().int().positive().describe('获取数量'), - reverse_order: z.boolean().describe('是否倒序'), - }), - response: baseResponseSchema.extend({ - data: z.object({ - messages: z.array(messageSchema), - }), - }), - }, - '/get_recent_contact': { - description: '最近消息列表', - request: z.object({ - count: z.number().int().positive().describe('获取数量'), - }), - response: baseResponseSchema.extend({ - data: z.array( - z.object({ - lastestMsg: messageSchema, - peerUin: z.string().describe('对方QQ号'), - remark: z.string().describe('备注'), - msgTime: z.string().describe('消息时间'), - chatType: z.number().describe('聊天类型'), - msgId: z.string().describe('消息ID'), - sendNickName: z.string().describe('发送人昵称'), - sendMemberName: z.string().describe('发送人?昵称'), - peerName: z.string().describe('对方昵称'), - }) - ), - }), - }, - '/fetch_emoji_like': { - description: '获取贴表情详情', - request: z.object({ - message_id: z.union([z.string(), z.number()]).describe('消息ID'), - emojiId: z.string().describe('表情ID'), - emojiType: z.string().describe('表情类型'), - count: z.number().int().positive().optional().describe('获取数量'), - cookie: z.string().describe('cookie,首次为空,后续为上次返回'), - }), - response: baseResponseSchema.extend({ - data: z.object({ - result: z.number().describe('结果'), - errMsg: z.string().describe('错误信息'), - emojiLikesList: z - .array( - z - .object({ - tinyId: z.string().describe('点击者QQ号'), - nickName: z.string().describe('昵称?'), - headUrl: z.string().describe('头像?'), - }) - .describe('表情点击列表') - ) - .describe('表情点击列表'), - cookie: z.string().describe('cookie'), - isLastPage: z.boolean().describe('是否最后一页'), - isFirstPage: z.boolean().describe('是否第一页'), - }), - }), - }, - '/get_emoji_likes': { - description: '获取贴表情详情列表', - request: z.object({ - message_id: z.union([z.string(), z.number()]).describe('消息ID'), - emojiId: z.string().describe('表情ID'), - emojiType: z.string().describe('表情类型'), - }), - response: baseResponseSchema.extend({ - data: z.object({ - result: z.number().describe('结果'), - errMsg: z.string().describe('错误信息'), - emojiLikesList: z - .array( - z - .object({ - tinyId: z.string().describe('点击者QQ号'), - nickName: z.string().describe('昵称?'), - headUrl: z.string().describe('头像?'), - }) - .describe('表情点击列表') - ) - .describe('表情点击列表'), - }), - }), - }, - '/get_forward_msg': { - description: '获取合并转发消息', - request: z.object({ - message_id: z.union([z.string(), z.number()]).describe('消息ID'), - }), - response: baseResponseSchema.extend({ - data: z.object({}), - }), - }, - '/send_forward_msg': { - description: '发送合并转发消息', - request: z.object({ - group_id: z.union([z.string(), z.number()]).optional().describe('群号'), - user_id: z - .union([z.string(), z.number()]) - .optional() - .describe('用户QQ号'), - messages: z.array(messageNodeSchema).describe('消息内容'), - news: z - .array( - z.object({ - text: z.string(), - }) - ) - .describe('?'), - prompt: z.string().describe('外显'), - summary: z.string().describe('底下文字'), - source: z.string().describe('内容'), - }), - response: baseResponseSchema.extend({ - data: commonResponseDataSchema.extend({ - data: z.object({}), - }), - }), - }, -} as const; - -export default oneBotHttpApiMessage; diff --git a/packages/napcat-webui-frontend/src/const/ob_api/message/node.ts b/packages/napcat-webui-frontend/src/const/ob_api/message/node.ts deleted file mode 100644 index c1b93c90..00000000 --- a/packages/napcat-webui-frontend/src/const/ob_api/message/node.ts +++ /dev/null @@ -1,120 +0,0 @@ -import { z } from 'zod'; - -const messageNode = z.union([ - z - .object({ - type: z.literal('text'), - data: z.object({ - text: z.string(), - }), - }) - .describe('文本消息'), - z - .object({ - type: z.literal('at'), - data: z.object({ - qq: z.string(), - }), - }) - .describe('@某人'), - z - .object({ - type: z.literal('image'), - data: z.object({ - file: z.string(), - }), - }) - .describe('图片消息'), - z - .object({ - type: z.literal('face'), - data: z.object({ - id: z.number(), - }), - }) - .describe('表情消息'), - z - .object({ - type: z.literal('json'), - data: z.object({ - data: z.string(), - }), - }) - .describe('json 卡片消息'), - z - .object({ - type: z.literal('record'), - data: z.object({ - file: z.string(), - }), - }) - .describe('语音消息'), - z - .object({ - type: z.literal('video'), - data: z.object({ - file: z.string(), - }), - }) - .describe('视频消息'), - z - .object({ - type: z.literal('reply'), - data: z.object({ - id: z.number().optional(), - seq: z.number().optional(), - }), - }) - .describe('回复消息'), - z - .object({ - type: z.literal('music'), - data: z.union([ - z.object({ - type: z.enum(['qq', '163']), - id: z.string(), - }), - z.object({ - type: z.literal('custom'), - url: z.string(), - audio: z.string(), - title: z.string(), - image: z.string(), - }), - ]), - }) - .describe('音乐消息'), - z - .object({ - type: z.literal('dice'), - }) - .describe('掷骰子'), - z - .object({ - type: z.literal('rps'), - }) - .describe('猜拳'), - z - .object({ - type: z.literal('file'), - data: z.object({ - file: z.string().describe('文件路径,服务器本地或者网络文件均可'), - }), - }) - .describe('发送消息'), -]); - -export const nodeMessage = z - .object({ - type: z.literal('node'), - data: z.object({ - user_id: z.string(), - nickname: z.string(), - content: z.array(messageNode), - }), - }) - .describe('消息节点'); - -const messageNodeSchema = z.union([messageNode, nodeMessage]); - -export default messageNodeSchema; diff --git a/packages/napcat-webui-frontend/src/const/ob_api/message/private.ts b/packages/napcat-webui-frontend/src/const/ob_api/message/private.ts deleted file mode 100644 index 87ff7e11..00000000 --- a/packages/napcat-webui-frontend/src/const/ob_api/message/private.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { z } from 'zod'; -import type { ZodSchema } from 'zod'; - -import { baseResponseSchema, commonResponseDataSchema } from '../response'; -import messageNodeSchema, { nodeMessage } from './node'; - -const oneBotHttpApiMessagePrivate: Record< - string, - { - description?: string - request: ZodSchema - response: ZodSchema - } -> = { - '/send_private_msg': { - description: '发送私聊消息', - request: z - .object({ - user_id: z.union([z.string(), z.number()]).describe('对方QQ号'), - message: z.array(messageNodeSchema).describe('消息内容'), - }) - .refine( - (data) => { - const hasReply = data.message.some((item) => item.type === 'reply'); - - if (hasReply) { - return data.message[0].type === 'reply'; - } - - return true; - }, - { - message: - '如果 message 包含 reply 类型的消息,那么只能包含一个,而且排在最前面', - } - ), - response: baseResponseSchema.extend({ - data: commonResponseDataSchema, - }), - }, - '/send_private_forward_msg': { - description: '发送私聊合并转发消息', - request: z.object({ - user_id: z.union([z.string(), z.number()]).describe('对方QQ号'), - messages: z.array(nodeMessage).describe('消息内容'), - news: z - .array( - z.object({ - text: z.string(), - }) - ) - .describe('?'), - prompt: z.string().describe('外显'), - summary: z.string().describe('底下文本'), - source: z.string().describe('内容'), - }), - response: baseResponseSchema.extend({ - data: commonResponseDataSchema, - }), - }, - '/forward_friend_single_msg': { - description: '消息转发到私聊', - request: z.object({ - user_id: z.union([z.string(), z.number()]).describe('对方QQ号'), - message_id: z.union([z.string(), z.number()]).describe('消息ID'), - }), - response: baseResponseSchema.extend({ - data: commonResponseDataSchema, - }), - }, - '/group_poke': { - description: '发送私聊戳一戳', - request: z.object({ - user_id: z.union([z.string(), z.number()]).describe('对方QQ号'), - }), - response: baseResponseSchema, - }, -}; - -export default oneBotHttpApiMessagePrivate; diff --git a/packages/napcat-webui-frontend/src/const/ob_api/online_status.ts b/packages/napcat-webui-frontend/src/const/ob_api/online_status.ts deleted file mode 100644 index e1caaa7b..00000000 --- a/packages/napcat-webui-frontend/src/const/ob_api/online_status.ts +++ /dev/null @@ -1,335 +0,0 @@ -import { z } from 'zod'; - -// 定义 set_online_status 的 data 格式 -const onlineStatusDataSchema = z.union([ - // 在线 - z - .object({ - status: z.literal(10), - ext_status: z.literal(0), - battery_status: z.literal(0), - }) - .describe('在线'), - // Q我吧 - z - .object({ - status: z.literal(60), - ext_status: z.literal(0), - battery_status: z.literal(0), - }) - .describe('Q我吧'), - // 离开 - z - .object({ - status: z.literal(30), - ext_status: z.literal(0), - battery_status: z.literal(0), - }) - .describe('离开'), - // 忙碌 - z - .object({ - status: z.literal(50), - ext_status: z.literal(0), - battery_status: z.literal(0), - }) - .describe('忙碌'), - // 请勿打扰 - z - .object({ - status: z.literal(70), - ext_status: z.literal(0), - battery_status: z.literal(0), - }) - .describe('请勿打扰'), - // 隐身 - z - .object({ - status: z.literal(40), - ext_status: z.literal(0), - battery_status: z.literal(0), - }) - .describe('隐身'), - // 听歌中 - z - .object({ - status: z.literal(10), - ext_status: z.literal(1028), - battery_status: z.literal(0), - }) - .describe('听歌中'), - // 春日限定 - z - .object({ - status: z.literal(10), - ext_status: z.literal(2037), - battery_status: z.literal(0), - }) - .describe('春日限定'), - // 一起元梦 - z - .object({ - status: z.literal(10), - ext_status: z.literal(2025), - battery_status: z.literal(0), - }) - .describe('一起元梦'), - // 求星搭子 - z - .object({ - status: z.literal(10), - ext_status: z.literal(2026), - battery_status: z.literal(0), - }) - .describe('求星搭子'), - // 被掏空 - z - .object({ - status: z.literal(10), - ext_status: z.literal(2014), - battery_status: z.literal(0), - }) - .describe('被掏空'), - // 今日天气 - z - .object({ - status: z.literal(10), - ext_status: z.literal(1030), - battery_status: z.literal(0), - }) - .describe('今日天气'), - // 我crash了 - z - .object({ - status: z.literal(10), - ext_status: z.literal(2019), - battery_status: z.literal(0), - }) - .describe('我crash了'), - // 爱你 - z - .object({ - status: z.literal(10), - ext_status: z.literal(2006), - battery_status: z.literal(0), - }) - .describe('爱你'), - // 恋爱中 - z - .object({ - status: z.literal(10), - ext_status: z.literal(1051), - battery_status: z.literal(0), - }) - .describe('恋爱中'), - // 好运锦鲤 - z - .object({ - status: z.literal(10), - ext_status: z.literal(1071), - battery_status: z.literal(0), - }) - .describe('好运锦鲤'), - // 水逆退散 - z - .object({ - status: z.literal(10), - ext_status: z.literal(1201), - battery_status: z.literal(0), - }) - .describe('水逆退散'), - // 嗨到飞起 - z - .object({ - status: z.literal(10), - ext_status: z.literal(1056), - battery_status: z.literal(0), - }) - .describe('嗨到飞起'), - // 元气满满 - z - .object({ - status: z.literal(10), - ext_status: z.literal(1058), - battery_status: z.literal(0), - }) - .describe('元气满满'), - // 宝宝认证 - z - .object({ - status: z.literal(10), - ext_status: z.literal(1070), - battery_status: z.literal(0), - }) - .describe('宝宝认证'), - // 一言难尽 - z - .object({ - status: z.literal(10), - ext_status: z.literal(1063), - battery_status: z.literal(0), - }) - .describe('一言难尽'), - // 难得糊涂 - z - .object({ - status: z.literal(10), - ext_status: z.literal(2001), - battery_status: z.literal(0), - }) - .describe('难得糊涂'), - // emo中 - z - .object({ - status: z.literal(10), - ext_status: z.literal(1401), - battery_status: z.literal(0), - }) - .describe('emo中'), - // 我太难了 - z - .object({ - status: z.literal(10), - ext_status: z.literal(1062), - battery_status: z.literal(0), - }) - .describe('我太难了'), - // 我想开了 - z - .object({ - status: z.literal(10), - ext_status: z.literal(2013), - battery_status: z.literal(0), - }) - .describe('我想开了'), - // 我没事 - z - .object({ - status: z.literal(10), - ext_status: z.literal(1052), - battery_status: z.literal(0), - }) - .describe('我没事'), - // 想静静 - z - .object({ - status: z.literal(10), - ext_status: z.literal(1061), - battery_status: z.literal(0), - }) - .describe('想静静'), - // 悠哉哉 - z - .object({ - status: z.literal(10), - ext_status: z.literal(1059), - battery_status: z.literal(0), - }) - .describe('悠哉哉'), - // 去旅行 - z - .object({ - status: z.literal(10), - ext_status: z.literal(2015), - battery_status: z.literal(0), - }) - .describe('去旅行'), - // 信号弱 - z - .object({ - status: z.literal(10), - ext_status: z.literal(1011), - battery_status: z.literal(0), - }) - .describe('信号弱'), - // 出去浪 - z - .object({ - status: z.literal(10), - ext_status: z.literal(2003), - battery_status: z.literal(0), - }) - .describe('出去浪'), - // 肝作业 - z - .object({ - status: z.literal(10), - ext_status: z.literal(2012), - battery_status: z.literal(0), - }) - .describe('肝作业'), - // 学习中 - z - .object({ - status: z.literal(10), - ext_status: z.literal(1018), - battery_status: z.literal(0), - }) - .describe('学习中'), - // 搬砖中 - z - .object({ - status: z.literal(10), - ext_status: z.literal(2023), - battery_status: z.literal(0), - }) - .describe('搬砖中'), - // 摸鱼中 - z - .object({ - status: z.literal(10), - ext_status: z.literal(1300), - battery_status: z.literal(0), - }) - .describe('摸鱼中'), - // 无聊中 - z - .object({ - status: z.literal(10), - ext_status: z.literal(1060), - battery_status: z.literal(0), - }) - .describe('无聊中'), - // timi中 - z - .object({ - status: z.literal(10), - ext_status: z.literal(1027), - battery_status: z.literal(0), - }) - .describe('timi中'), - // 睡觉中 - z - .object({ - status: z.literal(10), - ext_status: z.literal(1016), - battery_status: z.literal(0), - }) - .describe('睡觉中'), - // 熬夜中 - z - .object({ - status: z.literal(10), - ext_status: z.literal(1032), - battery_status: z.literal(0), - }) - .describe('熬夜中'), - // 追剧中 - z - .object({ - status: z.literal(10), - ext_status: z.literal(1021), - battery_status: z.literal(0), - }) - .describe('追剧中'), - // 我的电量 - z - .object({ - status: z.literal(10), - ext_status: z.literal(1000), - battery_status: z.literal(0), - }) - .describe('我的电量'), -]); - -export default onlineStatusDataSchema; diff --git a/packages/napcat-webui-frontend/src/const/ob_api/response.ts b/packages/napcat-webui-frontend/src/const/ob_api/response.ts deleted file mode 100644 index 7162b99a..00000000 --- a/packages/napcat-webui-frontend/src/const/ob_api/response.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { z } from 'zod'; - -// 通用响应格式 -export const baseResponseSchema = z.object({ - status: z.enum(['ok', 'error']).describe('请求状态'), // 状态 - retcode: z.number().describe('响应🐎'), // 返回码 - data: z.null(), - message: z.string().describe('提示信息'), // 提示信息 - wording: z.string().describe('提示信息(人性化)'), // 人性化提示 - echo: z.string().describe('回显'), // 请求回显内容 -}); - -export const commonResponseDataSchema = z.object({ - result: z.number(), - errMsg: z.string(), -}); diff --git a/packages/napcat-webui-frontend/src/const/ob_api/system.ts b/packages/napcat-webui-frontend/src/const/ob_api/system.ts deleted file mode 100644 index 36a53e34..00000000 --- a/packages/napcat-webui-frontend/src/const/ob_api/system.ts +++ /dev/null @@ -1,366 +0,0 @@ -import { z } from 'zod'; - -import { baseResponseSchema, commonResponseDataSchema } from './response'; - -const oneBotHttpApiSystem = { - '/get_online_clients': { - description: '获取当前账号在线客户端列表', - request: z.object({ - no_cache: z.boolean().optional().describe('是否不使用缓存'), - }), - response: baseResponseSchema.extend({ - data: z.object({ - clients: z.object({}), - }), - }), - }, - '/get_robot_uin_range': { - description: '获取机器人账号范围', - request: z.object({}), - response: baseResponseSchema.extend({ - data: z.array( - z.object({ - minUin: z.string(), - maxUin: z.string(), - }) - ), - }), - }, - '/ocr_image': { - description: 'OCR图片识别', - request: z.object({ - image: z.string(), - }), - response: baseResponseSchema.extend({ - data: z.array( - z.object({ - text: z.string(), - pt1: z.object({ - x: z.string(), - y: z.string(), - }), - pt2: z.object({ - x: z.string(), - y: z.string(), - }), - pt3: z.object({ - x: z.string(), - y: z.string(), - }), - pt4: z.object({ - x: z.string(), - y: z.string(), - }), - charBox: z.array( - z.object({ - charText: z.string(), - charBox: z.object({ - pt1: z.object({ - x: z.string(), - y: z.string(), - }), - pt2: z.object({ - x: z.string(), - y: z.string(), - }), - pt3: z.object({ - x: z.string(), - y: z.string(), - }), - pt4: z.object({ - x: z.string(), - y: z.string(), - }), - }), - }) - ), - score: z.string(), - }) - ), - }), - }, - - '/.ocr_image': { - description: '.OCR图片识别', - request: z.object({ - image: z.string(), - }), - response: baseResponseSchema.extend({ - data: z.array( - z.object({ - text: z.string(), - pt1: z.object({ - x: z.string(), - y: z.string(), - }), - pt2: z.object({ - x: z.string(), - y: z.string(), - }), - pt3: z.object({ - x: z.string(), - y: z.string(), - }), - pt4: z.object({ - x: z.string(), - y: z.string(), - }), - charBox: z.array( - z.object({ - charText: z.string(), - charBox: z.object({ - pt1: z.object({ - x: z.string(), - y: z.string(), - }), - pt2: z.object({ - x: z.string(), - y: z.string(), - }), - pt3: z.object({ - x: z.string(), - y: z.string(), - }), - pt4: z.object({ - x: z.string(), - y: z.string(), - }), - }), - }) - ), - score: z.string(), - }) - ), - }), - }, - '/translate_en2zh': { - description: '英文翻译为中文', - request: z.object({ - words: z.array(z.string()), - }), - response: baseResponseSchema.extend({ - data: z.array(z.string()), - }), - }, - '/get_login_info': { - description: '获取登录号信息', - request: z.object({}), - response: baseResponseSchema.extend({ - data: z.object({ - user_id: z.number(), - nickname: z.string(), - }), - }), - }, - '/set_input_status': { - description: '设置输入状态', - request: z.object({ - eventType: z.union([z.literal(0), z.literal(1)]), - user_id: z.union([z.number(), z.string()]), - }), - response: baseResponseSchema.extend({ - data: commonResponseDataSchema, - }), - }, - '/download_file': { - description: '下载文件到缓存目录', - request: z - .object({ - base64: z.string().optional(), - url: z.string().optional(), - thread_count: z.number(), - headers: z.union([z.string(), z.array(z.string())]), - name: z.string().optional(), - }) - .refine( - (data) => (data.base64 && !data.url) || (!data.base64 && data.url), - { - message: 'base64 和 url 必须二选一,且不能同时存在或同时为空', - path: ['base64', 'url'], - } - ), - response: baseResponseSchema.extend({ - data: z.object({ - file: z.string(), - }), - }), - }, - '/get_cookies': { - description: '获取cookies', - request: z.object({ - domain: z.string(), - }), - response: baseResponseSchema.extend({ - data: z.object({ - cookies: z.string(), - bkn: z.string(), - }), - }), - }, - '/.handle_quick_operation': { - description: '.对事件执行快速操作', - request: z.object({ - context: z.object({}), - operation: z.object({}), - }), - response: baseResponseSchema, - }, - '/get_csrf_token': { - description: '获取CSRF Token', - request: z.object({}), - response: baseResponseSchema.extend({ - data: z.object({ - token: z.number(), - }), - }), - }, - '/_del_group_notice': { - description: '_删除群公告', - request: z.object({ - group_id: z.union([z.number(), z.string()]), - notice_id: z.number(), - }), - response: baseResponseSchema, - }, - '/get_credentials': { - description: '获取 QQ 相关接口凭证', - request: z.object({ - domain: z.string(), - }), - response: baseResponseSchema.extend({ - data: z.object({ - cookies: z.string(), - token: z.number(), - }), - }), - }, - '/_get_model_show': { - description: '_获取在线机型', - request: z.object({ - model: z.string(), - }), - response: baseResponseSchema.extend({ - data: z.array( - z.object({ - variants: z.object({ - model_show: z.string(), - need_pay: z.boolean(), - }), - }) - ), - }), - }, - '/_set_model_show': { - description: '_设置在线机型', - request: z.object({ - model: z.string(), - model_show: z.string(), - }), - response: baseResponseSchema, - }, - '/can_send_image': { - description: '检查是否可以发送图片', - request: z.object({}), - response: baseResponseSchema.extend({ - yes: z.boolean(), - }), - }, - '/nc_get_packet_status': { - description: '获取packet状态', - request: z.object({}), - response: baseResponseSchema, - }, - '/can_send_record': { - description: '检查是否可以发送语音', - request: z.object({}), - response: baseResponseSchema.extend({ - yes: z.boolean(), - }), - }, - '/get_status': { - description: '获取状态', - request: z.object({}), - response: baseResponseSchema.extend({ - data: z.object({ - online: z.boolean(), - good: z.boolean(), - stat: z.object({}), - }), - }), - }, - '/nc_get_rkey': { - description: '获取rkey', - request: z.object({}), - response: baseResponseSchema.extend({ - data: z.array( - z.object({ - rkey: z.string(), - ttl: z.string(), - time: z.number(), - type: z.number(), - }) - ), - }), - }, - '/get_version_info': { - description: '获取版本信息', - request: z.object({}), - response: baseResponseSchema.extend({ - data: z.object({ - app_name: z.string(), - protocol_version: z.string(), - app_version: z.string(), - }), - }), - }, - '/get_group_shut_list': { - description: '获取群禁言列表', - request: z.object({ - group_id: z.union([z.number(), z.string()]), - }), - response: baseResponseSchema.extend({ - data: z.object({ - uid: z.string(), - qid: z.string(), - uin: z.string(), - nick: z.string(), - remark: z.string(), - cardType: z.number(), - cardName: z.string(), - role: z.number(), - avatarPath: z.string(), - shutUpTime: z.number(), - isDelete: z.boolean(), - isSpecialConcerned: z.boolean(), - isSpecialShield: z.boolean(), - isRobot: z.boolean(), - groupHonor: z.record(z.number()), - memberRealLevel: z.number(), - memberLevel: z.number(), - globalGroupLevel: z.number(), - globalGroupPoint: z.number(), - memberTitleId: z.number(), - memberSpecialTitle: z.string(), - specialTitleExpireTime: z.string(), - userShowFlag: z.number(), - userShowFlagNew: z.number(), - richFlag: z.number(), - mssVipType: z.number(), - bigClubLevel: z.number(), - bigClubFlag: z.number(), - autoRemark: z.string(), - creditLevel: z.number(), - joinTime: z.number(), - lastSpeakTime: z.number(), - memberFlag: z.number(), - memberFlagExt: z.number(), - memberMobileFlag: z.number(), - memberFlagExt2: z.number(), - isSpecialShielded: z.boolean(), - cardNameId: z.number(), - }), - }), - }, -} as const; - -export default oneBotHttpApiSystem; diff --git a/packages/napcat-webui-frontend/src/const/ob_api/user.ts b/packages/napcat-webui-frontend/src/const/ob_api/user.ts deleted file mode 100644 index ba66bcc8..00000000 --- a/packages/napcat-webui-frontend/src/const/ob_api/user.ts +++ /dev/null @@ -1,278 +0,0 @@ -import { z } from 'zod'; - -import onlineStatusDataSchema from './online_status'; -import { baseResponseSchema, commonResponseDataSchema } from './response'; - -const oneBotHttpApiUser = { - '/set_qq_profile': { - description: '设置账号信息', - request: z.object({ - nickname: z.string().describe('昵称'), - personal_note: z.string().describe('个性签名'), - sex: z.string().optional().describe('性别'), - }), - response: baseResponseSchema.extend({ - data: commonResponseDataSchema, - }), - }, - '/send_ark_share': { - description: '获取推荐好友/群聊卡片', - request: z - .object({ - group_id: z - .union([z.string(), z.number()]) - .optional() - .describe('群聊ID,与 user_id 二选一'), - user_id: z - .union([z.string(), z.number()]) - .optional() - .describe('用户ID,与 group_id 二选一'), - phone_number: z.string().optional().describe('对方手机号码'), - }) - .refine( - (data) => - (data.group_id && !data.user_id) || (!data.group_id && data.user_id), - { - message: 'group_id 和 user_id 必须二选一,且不能同时存在或同时为空', - path: ['group_id', 'user_id'], // 错误路径 - } - ), - response: baseResponseSchema.extend({ - data: z.object({ - errCode: z.number(), - errMsg: z.string(), - arkJson: z.string(), - }), - }), - }, - '/send_group_ark_share': { - description: '获取推荐群聊卡片', - request: z.object({ - group_id: z.union([z.string(), z.number()]).describe('群聊ID'), - }), - response: baseResponseSchema.extend({ - data: z.string(), - }), - }, - '/set_online_status': { - description: '设置在线状态', - request: z.object({ - data: onlineStatusDataSchema, - }), - response: baseResponseSchema, - }, - '/get_friends_with_category': { - description: '获取好友分组列表', - request: z.object({}), - response: baseResponseSchema.extend({ - data: z.array( - z.object({ - categoryId: z.number(), - categorySortId: z.number(), - categoryName: z.string(), - categoryMbCount: z.number(), - onlineCount: z.number(), - buddyList: z.array( - z.object({ - qid: z.string(), - longNick: z.string(), - birthday_year: z.number(), - birthday_month: z.number(), - birthday_day: z.number(), - age: z.number(), - sex: z.string(), - eMail: z.string(), - phoneNum: z.string(), - categoryId: z.number(), - richTime: z.number(), - richBuffer: z.object({}), - uid: z.string(), - uin: z.string(), - nick: z.string(), - remark: z.string(), - user_id: z.number(), - nickname: z.string(), - level: z.number(), - }) - ), - }) - ), - }), - }, - '/set_qq_avatar': { - description: '设置头像', - request: z.object({ - file: z.string().describe('图片文件路径(服务器本地或者远程均可)'), - }), - response: baseResponseSchema, - }, - '/send_like': { - description: '点赞', - request: z.object({ - user_id: z.union([z.string(), z.number()]).describe('对方QQ号'), - times: z.number().describe('点赞次数'), - }), - response: baseResponseSchema, - }, - '/create_collection': { - description: '创建收藏', - request: z.object({ - rawData: z.string().describe('收藏内容'), - brief: z.string().describe('收藏简介'), - }), - response: baseResponseSchema.extend({ - data: commonResponseDataSchema, - }), - }, - '/set_friend_add_request': { - description: '处理好友请求', - request: z.object({ - flag: z.string().describe('请求ID'), - approve: z.boolean().describe('是否同意'), - remark: z.string().describe('好友备注'), - }), - response: baseResponseSchema, - }, - '/set_self_longnick': { - description: '设置个性签名', - request: z.object({ - longNick: z.string().describe('签名内容'), - }), - response: baseResponseSchema.extend({ - data: commonResponseDataSchema, - }), - }, - '/get_stranger_info': { - description: '获取账号信息', - request: z.object({ - user_id: z.union([z.string(), z.number()]).describe('对方QQ号'), - }), - response: baseResponseSchema.extend({ - data: z.object({ - user_id: z.number(), - uid: z.string(), - uin: z.string(), - nickname: z.string(), - age: z.number(), - qid: z.string(), - qqLevel: z.number(), - sex: z.string(), - long_nick: z.string(), - reg_time: z.number(), - is_vip: z.boolean(), - is_years_vip: z.boolean(), - vip_level: z.number(), - remark: z.string(), - status: z.number(), - login_days: z.number(), - }), - }), - }, - '/get_friend_list': { - description: '获取好友列表', - request: z.object({ - no_cache: z.boolean().describe('是否不使用缓存'), - }), - response: baseResponseSchema.extend({ - data: z.array( - z.object({ - qid: z.string(), - longNick: z.string(), - birthday_year: z.number(), - birthday_month: z.number(), - birthday_day: z.number(), - age: z.number(), - sex: z.string(), - eMail: z.string(), - phoneNum: z.string(), - categoryId: z.number(), - richTime: z.number(), - richBuffer: z.object({}), - uid: z.string(), - uin: z.string(), - nick: z.string(), - remark: z.string(), - user_id: z.number(), - nickname: z.string(), - level: z.number(), - }) - ), - }), - }, - '/get_profile_like': { - description: '获取点赞列表', - request: z.object({}), - response: baseResponseSchema.extend({ - data: z.object({ - total_count: z.number(), - new_count: z.number(), - new_nearby_count: z.number(), - last_visit_time: z.number(), - userInfos: z.array( - z.object({ - uid: z.string(), - src: z.number(), - latestTime: z.number(), - count: z.number(), - giftCount: z.number(), - customId: z.number(), - lastCharged: z.number(), - bAvailableCnt: z.number(), - bTodayVotedCnt: z.number(), - nick: z.string(), - gender: z.number(), - age: z.number(), - isFriend: z.boolean(), - isvip: z.boolean(), - isSvip: z.boolean(), - uin: z.number(), - }) - ), - }), - }), - }, - '/fetch_custom_face': { - description: '获取收藏表情', - request: z.object({ - count: z.number().optional().describe('获取数量'), - }), - response: baseResponseSchema.extend({ - data: z.array(z.string()), - }), - }, - '/upload_private_file': { - description: '上传私聊文件', - request: z.object({ - user_id: z.union([z.string(), z.number()]), - file: z.string(), - name: z.string(), - }), - response: baseResponseSchema, - }, - '/delete_friend': { - description: '删除好友', - request: z.object({ - user_id: z.union([z.string(), z.number()]).describe('自己QQ号?'), - friend_id: z.union([z.string(), z.number()]).describe('好友QQ号'), - temp_block: z.boolean().describe('是否加入黑名单'), - temp_both_del: z.boolean().describe('是否双向删除'), - }), - response: baseResponseSchema.extend({ - data: commonResponseDataSchema, - }), - }, - '/nc_get_user_status': { - description: '获取用户在线状态', - request: z.object({ - user_id: z.union([z.string(), z.number()]).describe('对方QQ号'), - }), - response: baseResponseSchema.extend({ - data: z.object({ - status: z.number(), - ext_status: z.number(), - }), - }), - }, -} as const; - -export default oneBotHttpApiUser; diff --git a/packages/napcat-webui-frontend/src/pages/dashboard/debug/http/index.tsx b/packages/napcat-webui-frontend/src/pages/dashboard/debug/http/index.tsx index 0637ed08..589deeaa 100644 --- a/packages/napcat-webui-frontend/src/pages/dashboard/debug/http/index.tsx +++ b/packages/napcat-webui-frontend/src/pages/dashboard/debug/http/index.tsx @@ -6,19 +6,20 @@ import { IoClose } from 'react-icons/io5'; import { TbSearch } from 'react-icons/tb'; import key from '@/const/key'; -import oneBotHttpApi from '@/const/ob_api'; -import type { OneBotHttpApiPath } from '@/const/ob_api'; +import { fetchOneBotHttpApi, OneBotHttpApiPath } from '@/const/ob_api'; +import type { OneBotHttpApi } from '@/const/ob_api'; import OneBotApiDebug from '@/components/onebot/api/debug'; import CommandPalette from '@/components/command_palette'; import type { CommandPaletteCommand, CommandPaletteExecuteMode } from '@/components/command_palette'; -import { generateDefaultJson } from '@/utils/zod'; +import { generateDefaultFromTypeBox } from '@/utils/typebox'; import type { OneBotApiDebugRef } from '@/components/onebot/api/debug'; export default function HttpDebug () { const [activeApi, setActiveApi] = useState(null); const [openApis, setOpenApis] = useState([]); + const [oneBotHttpApi, setOneBotHttpApi] = useState({}); const [backgroundImage] = useLocalStorage(key.backgroundImage, ''); const hasBackground = !!backgroundImage; @@ -40,30 +41,34 @@ export default function HttpDebug () { return () => window.removeEventListener('keydown', handler); }, []); - // Initialize Debug Adapter + // Initialize Debug Adapter and fetch schemas useEffect(() => { let currentAdapterName = ''; - const initAdapter = async () => { + const init = async () => { try { - const response = await fetch('/api/Debug/create', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${localStorage.getItem('token')}` - } - }); - const data = await response.json(); - if (data.code === 0) { - currentAdapterName = data.data.adapterName; - setAdapterName(currentAdapterName); - } + const [apiData] = await Promise.all([ + fetchOneBotHttpApi(), + fetch('/api/Debug/create', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${localStorage.getItem('token')}` + } + }).then(res => res.json()).then(data => { + if (data.code === 0) { + currentAdapterName = data.data.adapterName; + setAdapterName(currentAdapterName); + } + }) + ]); + setOneBotHttpApi(apiData); } catch (error) { - console.error('Failed to create debug adapter:', error); + console.error('Failed to initialize debug:', error); } }; - initAdapter(); + init(); return () => { // 不再主动关闭 adapter,由后端自动管理活跃状态 @@ -92,21 +97,24 @@ export default function HttpDebug () { return Object.keys(oneBotHttpApi).map((p) => { const path = p as OneBotHttpApiPath; const item = oneBotHttpApi[path]; + const displayPath = '/' + path; // 简单分组:按描述里已有分类不可靠,这里只用 path 前缀推断 - const group = path.startsWith('/get_') ? 'GET' : (path.startsWith('/set_') ? 'SET' : 'API'); + const group = path.startsWith('get_') ? 'GET' : (path.startsWith('set_') ? 'SET' : 'API'); return { id: path, - title: item?.description || path, - subtitle: item?.request ? '回车发送 · Shift+Enter 仅打开' : undefined, + title: item?.description || displayPath, + subtitle: item?.payload ? '回车发送 · Shift+Enter 仅打开' : undefined, group, }; }); - }, []); + }, [oneBotHttpApi]); const executeCommand = (commandId: string, mode: CommandPaletteExecuteMode) => { const api = commandId as OneBotHttpApiPath; const item = oneBotHttpApi[api]; - const body = item?.request ? generateDefaultJson(item.request) : '{}'; + const body = item?.payloadExample + ? JSON.stringify(item.payloadExample, null, 2) + : (item?.payload ? JSON.stringify(generateDefaultFromTypeBox(item.payload), null, 2) : '{}'); handleSelectApi(api); // 确保请求参数可见 diff --git a/packages/napcat-webui-frontend/src/utils/typebox.ts b/packages/napcat-webui-frontend/src/utils/typebox.ts new file mode 100644 index 00000000..92f1f048 --- /dev/null +++ b/packages/napcat-webui-frontend/src/utils/typebox.ts @@ -0,0 +1,123 @@ +import { TSchema, Type } from '@sinclair/typebox'; + +export type ParsedSchema = { + name?: string; + type: string | string[]; + optional: boolean; + value?: any; + enum?: any[]; + children?: ParsedSchema[]; + description?: string; +}; + +// 定义基础响应结构 (TypeBox 格式) +export const BaseResponseSchema = Type.Object({ + status: Type.Union([Type.Literal('ok'), Type.Literal('failed')], { description: '状态 (ok/failed)' }), + retcode: Type.Number({ description: '返回码' }), + data: Type.Any({ description: '数据' }), + message: Type.String({ description: '消息' }), + wording: Type.String({ description: '提示' }), + echo: Type.Optional(Type.String({ description: '回显' })), +}); + +export function parseTypeBox (schema: TSchema | undefined, name?: string, isRoot = true): ParsedSchema | ParsedSchema[] { + if (!schema) { + return isRoot ? [] : { name, type: 'unknown', optional: false }; + } + + // 如果是根节点解析,且我们需要将其包装在 BaseResponse 中(通常用于 response) + // 但这里我们根据传入的 schema 决定 + + const description = schema.description; + const optional = false; // TypeBox schema doesn't store optionality in the same way Zod does, usually handled by parent object + + // Handle specific types + const type = schema.type; + + if (schema.const !== undefined) { + return { name, type: 'value', value: schema.const, optional, description }; + } + + if (schema.enum) { + return { name, type: 'enum', enum: schema.enum, optional, description }; + } + + if (schema.anyOf || schema.oneOf) { + const options = (schema.anyOf || schema.oneOf) as TSchema[]; + const children = options.map(opt => parseTypeBox(opt, undefined, false) as ParsedSchema); + return { name, type: 'union', children, optional, description }; + } + + if (schema.allOf) { + const parts = schema.allOf as TSchema[]; + // 如果全是对象,尝试合并属性 + const allProperties: Record = {}; + const allRequired: string[] = []; + let canMerge = true; + parts.forEach(part => { + if (part.type === 'object' && part.properties) { + Object.assign(allProperties, part.properties); + if (part.required) allRequired.push(...part.required); + } else { + canMerge = false; + } + }); + + if (canMerge) { + return parseTypeBox({ ...schema, type: 'object', properties: allProperties, required: allRequired }, name, isRoot); + } + // 无法简单合并,当作联合展示 + const children = parts.map(part => parseTypeBox(part, undefined, false) as ParsedSchema); + return { name, type: 'intersection', children, optional, description }; + } + + if (type === 'object') { + const properties = schema.properties || {}; + const required = schema.required || []; + const children = Object.keys(properties).map(key => { + const child = parseTypeBox(properties[key], key, false) as ParsedSchema; + child.optional = !required.includes(key); + return child; + }); + if (isRoot) return children; + return { name, type: 'object', children, optional, description }; + } + + if (type === 'array') { + const items = schema.items as TSchema; + const child = parseTypeBox(items, undefined, false) as ParsedSchema; + return { name, type: 'array', children: [child], optional, description }; + } + + if (type === 'string') return { name, type: 'string', optional, description }; + if (type === 'number' || type === 'integer') return { name, type: 'number', optional, description }; + if (type === 'boolean') return { name, type: 'boolean', optional, description }; + if (type === 'null') return { name, type: 'null', optional, description }; + + return { name, type: type || 'unknown', optional, description }; +} + +export function generateDefaultFromTypeBox (schema: TSchema | undefined): any { + if (!schema) return {}; + if (schema.const !== undefined) return schema.const; + if (schema.default !== undefined) return schema.default; + if (schema.enum) return schema.enum[0]; + if (schema.anyOf || schema.oneOf) return generateDefaultFromTypeBox((schema.anyOf || schema.oneOf)[0]); + + const type = schema.type; + if (type === 'object') { + const obj: any = {}; + const props = schema.properties || {}; + for (const key in props) { + // Only generate defaults for required properties or if we want a full example + obj[key] = generateDefaultFromTypeBox(props[key]); + } + return obj; + } + if (type === 'array') return []; + if (type === 'string') return ''; + if (type === 'number' || type === 'integer') return 0; + if (type === 'boolean') return false; + if (type === 'null') return null; + return null; +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 24017d0e..a618952b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -509,6 +509,9 @@ importers: '@reduxjs/toolkit': specifier: ^2.5.1 version: 2.10.1(react-redux@9.2.0(@types/react@19.2.4)(react@19.2.0)(redux@5.0.1))(react@19.2.0) + '@sinclair/typebox': + specifier: ^0.34.41 + version: 0.34.41 '@uidotdev/usehooks': specifier: ^2.4.1 version: 2.4.1(react-dom@19.2.0(react@19.2.0))(react@19.2.0)