diff --git a/packages/napcat-onebot/action/msg/SendMsg.ts b/packages/napcat-onebot/action/msg/SendMsg.ts index 185f5505..255a581d 100644 --- a/packages/napcat-onebot/action/msg/SendMsg.ts +++ b/packages/napcat-onebot/action/msg/SendMsg.ts @@ -16,12 +16,13 @@ import { PacketMsg } from 'napcat-core/packet/message/message'; import { rawMsgWithSendMsg } from 'napcat-core/packet/message/converter'; import { Static, Type } from '@sinclair/typebox'; import { MsgActionsExamples } from '@/napcat-onebot/action/msg/examples'; +import { OB11MessageMixTypeSchema } from '@/napcat-onebot/types/message-segment-schema'; export const SendMsgPayloadSchema = Type.Object({ message_type: Type.Optional(Type.Union([Type.Literal('private'), Type.Literal('group')], { description: '消息类型 (private/group)' })), user_id: Type.Optional(Type.String({ description: '用户QQ' })), group_id: Type.Optional(Type.String({ description: '群号' })), - message: Type.Unknown({ description: '消息内容' }), + message: OB11MessageMixTypeSchema, auto_escape: Type.Optional(Type.Union([Type.Boolean(), Type.String()], { description: '是否作为纯文本发送' })), // 以下为扩展字段 source: Type.Optional(Type.String({ description: '合并转发来源' })), @@ -30,9 +31,7 @@ export const SendMsgPayloadSchema = Type.Object({ prompt: Type.Optional(Type.String({ description: '合并转发提示' })), }); -export type SendMsgPayload = Static & { - message: OB11MessageMixType; -}; +export type SendMsgPayload = Static; export const SendMsgReturnSchema = Type.Object({ message_id: Type.Number({ description: '消息ID' }), diff --git a/packages/napcat-onebot/types/message-segment-schema.ts b/packages/napcat-onebot/types/message-segment-schema.ts new file mode 100644 index 00000000..d74223f7 --- /dev/null +++ b/packages/napcat-onebot/types/message-segment-schema.ts @@ -0,0 +1,412 @@ +import { Type, Static } from '@sinclair/typebox'; +import { OB11MessageDataType } from './message'; + +// ==================== 基础消息段类型 ==================== + +// 纯文本消息段 +export const OB11MessageTextSchema = Type.Object({ + type: Type.Literal(OB11MessageDataType.text), + data: Type.Object({ + text: Type.String({ description: '纯文本内容' }), + }), +}, { $id: 'OB11MessageText', description: '纯文本消息段' }); + +// 表情消息段 +export const OB11MessageFaceSchema = Type.Object({ + type: Type.Literal(OB11MessageDataType.face), + data: Type.Object({ + id: Type.String({ description: '表情ID' }), + resultId: Type.Optional(Type.String({ description: '结果ID' })), + chainCount: Type.Optional(Type.Number({ description: '连击数' })), + }), +}, { $id: 'OB11MessageFace', description: 'QQ表情消息段' }); + +// 商城表情消息段 +export const OB11MessageMFaceSchema = Type.Object({ + type: Type.Literal(OB11MessageDataType.mface), + data: Type.Object({ + emoji_package_id: Type.Number({ description: '表情包ID' }), + emoji_id: Type.String({ description: '表情ID' }), + key: Type.String({ description: '表情key' }), + summary: Type.String({ description: '表情摘要' }), + }), +}, { $id: 'OB11MessageMFace', description: '商城表情消息段' }); + +// @消息段 +export const OB11MessageAtSchema = Type.Object({ + type: Type.Literal(OB11MessageDataType.at), + data: Type.Object({ + qq: Type.String({ description: 'QQ号或all' }), + name: Type.Optional(Type.String({ description: '显示名称' })), + }), +}, { $id: 'OB11MessageAt', description: '@消息段' }); + +// 回复消息段 +export const OB11MessageReplySchema = Type.Object({ + type: Type.Literal(OB11MessageDataType.reply), + data: Type.Object({ + id: Type.Optional(Type.String({ description: '消息ID的短ID映射' })), + seq: Type.Optional(Type.Number({ description: '消息序列号,优先使用' })), + }), +}, { $id: 'OB11MessageReply', description: '回复消息段' }); + +// ==================== 文件类消息段 ==================== + +// 文件消息段基础数据 +const FileBaseDataSchema = Type.Object({ + file: Type.String({ description: '文件路径/URL/file:///' }), + path: Type.Optional(Type.String({ description: '文件路径' })), + url: Type.Optional(Type.String({ description: '文件URL' })), + name: Type.Optional(Type.String({ description: '文件名' })), + thumb: Type.Optional(Type.String({ description: '缩略图' })), +}); + +// 图片消息段 +export const OB11MessageImageSchema = Type.Object({ + type: Type.Literal(OB11MessageDataType.image), + data: Type.Intersect([ + FileBaseDataSchema, + Type.Object({ + summary: Type.Optional(Type.String({ description: '图片摘要' })), + sub_type: Type.Optional(Type.Number({ description: '图片子类型' })), + }), + ]), +}, { $id: 'OB11MessageImage', description: '图片消息段' }); + +// 语音消息段 +export const OB11MessageRecordSchema = Type.Object({ + type: Type.Literal(OB11MessageDataType.voice), + data: FileBaseDataSchema, +}, { $id: 'OB11MessageRecord', description: '语音消息段' }); + +// 视频消息段 +export const OB11MessageVideoSchema = Type.Object({ + type: Type.Literal(OB11MessageDataType.video), + data: FileBaseDataSchema, +}, { $id: 'OB11MessageVideo', description: '视频消息段' }); + +// 文件消息段 +export const OB11MessageFileSchema = Type.Object({ + type: Type.Literal(OB11MessageDataType.file), + data: FileBaseDataSchema, +}, { $id: 'OB11MessageFile', description: '文件消息段' }); + +// ==================== 音乐消息段 ==================== + +// ID音乐消息段数据 +const IdMusicDataSchema = Type.Object({ + type: Type.Union([ + Type.Literal('qq'), + Type.Literal('163'), + Type.Literal('kugou'), + Type.Literal('migu'), + Type.Literal('kuwo'), + ], { description: '音乐平台类型' }), + id: Type.Union([Type.String(), Type.Number()], { description: '音乐ID' }), +}); + +// 自定义音乐消息段数据 +const CustomMusicDataSchema = Type.Object({ + type: Type.Union([ + Type.Literal('qq'), + Type.Literal('163'), + Type.Literal('kugou'), + Type.Literal('migu'), + Type.Literal('kuwo'), + Type.Literal('custom'), + ], { description: '音乐平台类型' }), + id: Type.Undefined(), + url: Type.String({ description: '点击后跳转URL' }), + audio: Type.Optional(Type.String({ description: '音频URL' })), + title: Type.Optional(Type.String({ description: '音乐标题' })), + image: Type.String({ description: '封面图片URL' }), + content: Type.Optional(Type.String({ description: '音乐简介' })), +}); + +// ID音乐消息段 +export const OB11MessageIdMusicSchema = Type.Object({ + type: Type.Literal(OB11MessageDataType.music), + data: IdMusicDataSchema, +}, { $id: 'OB11MessageIdMusic', description: 'ID音乐消息段' }); + +// 自定义音乐消息段 +export const OB11MessageCustomMusicSchema = Type.Object({ + type: Type.Literal(OB11MessageDataType.music), + data: CustomMusicDataSchema, +}, { $id: 'OB11MessageCustomMusic', description: '自定义音乐消息段' }); + +// ==================== 特殊消息段 ==================== + +// 戳一戳消息段 +export const OB11MessagePokeSchema = Type.Object({ + type: Type.Literal(OB11MessageDataType.poke), + data: Type.Object({ + type: Type.String({ description: '戳一戳类型' }), + id: Type.String({ description: '戳一戳ID' }), + }), +}, { $id: 'OB11MessagePoke', description: '戳一戳消息段' }); + +// 骰子消息段 +export const OB11MessageDiceSchema = Type.Object({ + type: Type.Literal(OB11MessageDataType.dice), + data: Type.Object({ + result: Type.Union([Type.Number(), Type.String()], { description: '骰子结果' }), + }), +}, { $id: 'OB11MessageDice', description: '骰子消息段' }); + +// 猜拳消息段 +export const OB11MessageRPSSchema = Type.Object({ + type: Type.Literal(OB11MessageDataType.rps), + data: Type.Object({ + result: Type.Union([Type.Number(), Type.String()], { description: '猜拳结果' }), + }), +}, { $id: 'OB11MessageRPS', description: '猜拳消息段' }); + +// 联系人消息段 +export const OB11MessageContactSchema = Type.Object({ + type: Type.Literal(OB11MessageDataType.contact), + data: Type.Object({ + type: Type.Union([Type.Literal('qq'), Type.Literal('group')], { description: '联系人类型' }), + id: Type.String({ description: '联系人ID' }), + }), +}, { $id: 'OB11MessageContact', description: '联系人消息段' }); + +// 位置消息段 +export const OB11MessageLocationSchema = Type.Object({ + type: Type.Literal(OB11MessageDataType.location), + data: Type.Object({ + lat: Type.Union([Type.String(), Type.Number()], { description: '纬度' }), + lon: Type.Union([Type.String(), Type.Number()], { description: '经度' }), + title: Type.Optional(Type.String({ description: '标题' })), + content: Type.Optional(Type.String({ description: '内容' })), + }), +}, { $id: 'OB11MessageLocation', description: '位置消息段' }); + +// ==================== 富文本消息段 ==================== + +// JSON消息段 +export const OB11MessageJsonSchema = Type.Object({ + type: Type.Literal(OB11MessageDataType.json), + data: Type.Object({ + data: Type.Union([Type.String(), Type.Object({})], { description: 'JSON数据' }), + config: Type.Optional(Type.Object({ + token: Type.String({ description: 'token' }), + })), + }), +}, { $id: 'OB11MessageJson', description: 'JSON消息段' }); + +// XML消息段 +export const OB11MessageXmlSchema = Type.Object({ + type: Type.Literal(OB11MessageDataType.xml), + data: Type.Object({ + data: Type.String({ description: 'XML数据' }), + }), +}, { $id: 'OB11MessageXml', description: 'XML消息段' }); + +// Markdown消息段 +export const OB11MessageMarkdownSchema = Type.Object({ + type: Type.Literal(OB11MessageDataType.markdown), + data: Type.Object({ + content: Type.String({ description: 'Markdown内容' }), + }), +}, { $id: 'OB11MessageMarkdown', description: 'Markdown消息段' }); + +// 小程序消息段 +export const OB11MessageMiniAppSchema = Type.Object({ + type: Type.Literal(OB11MessageDataType.miniapp), + data: Type.Object({ + data: Type.String({ description: '小程序数据' }), + }), +}, { $id: 'OB11MessageMiniApp', description: '小程序消息段' }); + +// ==================== 在线文件消息段 ==================== + +// 在线文件消息段 +export const OB11MessageOnlineFileSchema = Type.Object({ + type: Type.Literal(OB11MessageDataType.onlinefile), + data: Type.Object({ + msgId: Type.String({ description: '消息ID' }), + elementId: Type.String({ description: '元素ID' }), + fileName: Type.String({ description: '文件名' }), + fileSize: Type.String({ description: '文件大小' }), + isDir: Type.Boolean({ description: '是否为目录' }), + }), +}, { $id: 'OB11MessageOnlineFile', description: '在线文件消息段' }); + +// 闪传消息段 +export const OB11MessageFlashTransferSchema = Type.Object({ + type: Type.Literal(OB11MessageDataType.flashtransfer), + data: Type.Object({ + fileSetId: Type.String({ description: '文件集ID' }), + }), +}, { $id: 'OB11MessageFlashTransfer', description: 'QQ闪传消息段' }); + +// ==================== 合并转发消息段(递归类型)==================== + +// 由于 TypeBox 的递归类型限制,我们需要使用 Type.Recursive +// 但为了与原始类型完全兼容,我们使用 Type.Unknown 来表示递归部分 + +// 合并转发消息节点 +export const OB11MessageNodeSchema = Type.Object({ + type: Type.Literal(OB11MessageDataType.node), + data: Type.Object({ + id: Type.Optional(Type.String({ description: '转发消息ID' })), + user_id: Type.Optional(Type.Union([Type.Number(), Type.String()], { description: '发送者QQ号' })), + uin: Type.Optional(Type.Union([Type.Number(), Type.String()], { description: '发送者QQ号(兼容go-cqhttp)' })), + nickname: Type.String({ description: '发送者昵称' }), + name: Type.Optional(Type.String({ description: '发送者昵称(兼容go-cqhttp)' })), + // content 使用 Any 以支持循环引用,实际类型是 OB11MessageMixType + content: Type.Any({ description: '消息内容 (OB11MessageMixType)' }), + source: Type.Optional(Type.String({ description: '消息来源' })), + news: Type.Optional(Type.Array(Type.Object({ + text: Type.String({ description: '新闻文本' }), + }))), + summary: Type.Optional(Type.String({ description: '摘要' })), + prompt: Type.Optional(Type.String({ description: '提示' })), + time: Type.Optional(Type.String({ description: '时间' })), + }), +}, { $id: 'OB11MessageNode', description: '合并转发消息节点' }); + +// 合并转发消息段 +export const OB11MessageForwardSchema = Type.Object({ + type: Type.Literal(OB11MessageDataType.forward), + data: Type.Object({ + id: Type.String({ description: '合并转发ID' }), + // content 使用 Any 以支持类型兼容,实际类型是 OB11Message[] + content: Type.Optional(Type.Any({ description: '消息内容 (OB11Message[])' })), + }), +}, { $id: 'OB11MessageForward', description: '合并转发消息段' }); + +// ==================== 消息段联合类型 ==================== + +// 所有消息段的联合类型(与原始 OB11MessageData 完全一致) +export const OB11MessageDataSchema = Type.Union([ + OB11MessageTextSchema, + OB11MessageFaceSchema, + OB11MessageMFaceSchema, + OB11MessageAtSchema, + OB11MessageReplySchema, + OB11MessageImageSchema, + OB11MessageRecordSchema, + OB11MessageVideoSchema, + OB11MessageFileSchema, + OB11MessageIdMusicSchema, + OB11MessageCustomMusicSchema, + OB11MessagePokeSchema, + OB11MessageDiceSchema, + OB11MessageRPSSchema, + OB11MessageContactSchema, + OB11MessageJsonSchema, + OB11MessageMarkdownSchema, + OB11MessageNodeSchema, + OB11MessageForwardSchema, + OB11MessageOnlineFileSchema, + OB11MessageFlashTransferSchema, +], { $id: 'OB11MessageData', description: 'OneBot 11 消息段' }); + +// 消息混合类型(数组、字符串或单个消息段) +export const OB11MessageMixTypeSchema = Type.Union([ + Type.Array(OB11MessageDataSchema), + Type.String(), + OB11MessageDataSchema, +], { $id: 'OB11MessageMixType', description: 'OneBot 11 消息混合类型' }); + +// ==================== 发送消息请求 ==================== + +// 发送消息请求 +export const OB11PostSendMsgSchema = Type.Object({ + message_type: Type.Optional(Type.Union([Type.Literal('private'), Type.Literal('group')], { description: '消息类型' })), + user_id: Type.Optional(Type.String({ description: '用户QQ号' })), + group_id: Type.Optional(Type.String({ description: '群号' })), + message: OB11MessageMixTypeSchema, + messages: Type.Optional(OB11MessageMixTypeSchema), + auto_escape: Type.Optional(Type.Union([Type.Boolean(), Type.String()], { description: '是否作为纯文本发送' })), + source: Type.Optional(Type.String({ description: '消息来源' })), + news: Type.Optional(Type.Array(Type.Object({ + text: Type.String({ description: '文本' }), + }))), + summary: Type.Optional(Type.String({ description: '摘要' })), + prompt: Type.Optional(Type.String({ description: '提示' })), + time: Type.Optional(Type.String({ description: '时间' })), +}, { $id: 'OB11PostSendMsg', description: 'OneBot 11 发送消息请求' }); + +// ==================== 完整消息对象 ==================== + +// 发送者信息 +export const OB11SenderSchema = Type.Object({ + user_id: Type.Union([Type.Number(), Type.String()], { description: '发送者QQ号' }), + nickname: Type.String({ description: '发送者昵称' }), + card: Type.Optional(Type.String({ description: '群名片' })), + role: Type.Optional(Type.String({ description: '角色' })), + sex: Type.Optional(Type.String({ description: '性别' })), + age: Type.Optional(Type.Number({ description: '年龄' })), + area: Type.Optional(Type.String({ description: '地区' })), + level: Type.Optional(Type.String({ description: '等级' })), + title: Type.Optional(Type.String({ description: '头衔' })), +}, { $id: 'OB11Sender', description: 'OneBot 11 发送者信息' }); + +// 完整消息对象 +export const OB11MessageSchema = Type.Object({ + real_seq: Type.Optional(Type.String({ description: '真实序列号' })), + temp_source: Type.Optional(Type.Number({ description: '临时会话来源' })), + message_sent_type: Type.Optional(Type.String({ description: '消息发送类型' })), + target_id: Type.Optional(Type.Number({ description: '目标ID' })), + self_id: Type.Optional(Type.Number({ description: '机器人QQ号' })), + time: Type.Number({ description: '消息时间戳' }), + message_id: Type.Number({ description: '消息ID' }), + message_seq: Type.Number({ description: '消息序列号' }), + real_id: Type.Number({ description: '真实ID' }), + user_id: Type.Union([Type.Number(), Type.String()], { description: '发送者QQ号' }), + group_id: Type.Optional(Type.Union([Type.Number(), Type.String()], { description: '群号' })), + group_name: Type.Optional(Type.String({ description: '群名称' })), + message_type: Type.Union([Type.Literal('private'), Type.Literal('group')], { description: '消息类型' }), + sub_type: Type.Optional(Type.Union([ + Type.Literal('friend'), + Type.Literal('group'), + Type.Literal('normal'), + ], { description: '消息子类型' })), + sender: OB11SenderSchema, + message: Type.Union([Type.Array(OB11MessageDataSchema), Type.String()], { description: '消息内容' }), + message_format: Type.Union([Type.Literal('array'), Type.Literal('string')], { description: '消息格式' }), + raw_message: Type.String({ description: '原始消息' }), + font: Type.Number({ description: '字体' }), + post_type: Type.Optional(Type.String({ description: '上报类型' })), + raw: Type.Optional(Type.Unknown({ description: '原始消息对象' })), + emoji_likes_list: Type.Optional(Type.Array(Type.Object({ + emoji_id: Type.String({ description: '表情ID' }), + emoji_type: Type.String({ description: '表情类型' }), + likes_cnt: Type.String({ description: '点赞数' }), + }), { description: '表情点赞列表' })), +}, { $id: 'OB11Message', description: 'OneBot 11 完整消息对象' }); + +// ==================== TypeScript 类型导出 ==================== + +export type OB11MessageText = Static; +export type OB11MessageFace = Static; +export type OB11MessageMFace = Static; +export type OB11MessageAt = Static; +export type OB11MessageReply = Static; +export type OB11MessageImage = Static; +export type OB11MessageRecord = Static; +export type OB11MessageVideo = Static; +export type OB11MessageFile = Static; +export type OB11MessageIdMusic = Static; +export type OB11MessageCustomMusic = Static; +export type OB11MessagePoke = Static; +export type OB11MessageDice = Static; +export type OB11MessageRPS = Static; +export type OB11MessageContact = Static; +export type OB11MessageLocation = Static; +export type OB11MessageJson = Static; +export type OB11MessageXml = Static; +export type OB11MessageMarkdown = Static; +export type OB11MessageMiniApp = Static; +export type OB11MessageNode = Static; +export type OB11MessageForward = Static; +export type OB11MessageOnlineFile = Static; +export type OB11MessageFlashTransfer = Static; +export type OB11MessageData = Static; +export type OB11MessageMixType = Static; +export type OB11PostSendMsg = Static; +export type OB11Sender = Static; +export type OB11Message = Static; diff --git a/packages/napcat-onebot/types/message.ts b/packages/napcat-onebot/types/message.ts index bf566b81..c7077fe3 100644 --- a/packages/napcat-onebot/types/message.ts +++ b/packages/napcat-onebot/types/message.ts @@ -264,14 +264,14 @@ export interface OB11MessageOnlineFile { fileName: string; fileSize: string; isDir: boolean; - } + }; } export interface OB11MessageFlashTransfer { type: OB11MessageDataType.flashtransfer; data: { fileSetId: string; - } + }; } // 消息数据类型定义