SnowCat 雪猫底层全源码:NapCat 4.18.28 底座 + SnowLuma 能力移植 + V8 通道适配 9.9.37
26 包 monorepo(TS 全源码): - snowcat-core: NT 内核 API 封装 + listener(含 9.9.37 六回调适配)+ Packet 发包 - snowcat-onebot: OneBot v11 全动作 + MCP Streamable HTTP 适配器 + 稳定线 - snowcat-webui-frontend/backend: 雪猫控制台(React+HeroUI) - snowcat-shell: 单文件 bundle 出口(vite inlineDynamicImports) - snowcat-native: napi2native / MoeHoo / ffmpeg / pty 运行时 - 56 项 SnowLuma 独门动作移植(qzone 全家桶/群相册/装扮/闪照/收藏) - 工具件: 解包器/启动器/便携制作器/doctor GitHub 关联已全量指向 DandanLLab/SnowCatQQ
This commit is contained in:
commit
b9655a5ab1
1048 files changed
+130851
No files matched your search
@@ -0,0 +1,43 @@
|
||||
import { ActionName } from '@/snowcat-onebot/action/router';
|
||||
import { OneBotAction } from '@/snowcat-onebot/action/OneBotAction';
|
||||
import { MessageUnique } from 'snowcat-common/src/message-unique';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
const PayloadSchema = Type.Object({
|
||||
message_id: Type.Union([Type.Number(), Type.String()], { description: '消息ID' }),
|
||||
});
|
||||
|
||||
type PayloadType = Static<typeof PayloadSchema>;
|
||||
|
||||
const ReturnSchema = Type.Null({ description: '操作结果' });
|
||||
|
||||
type ReturnType = Static<typeof ReturnSchema>;
|
||||
|
||||
class DeleteMsg extends OneBotAction<PayloadType, ReturnType> {
|
||||
override actionName = ActionName.DeleteMsg;
|
||||
override payloadSchema = PayloadSchema;
|
||||
override returnSchema = ReturnSchema;
|
||||
override actionSummary = '撤回消息';
|
||||
override actionDescription = '撤回已发送的消息';
|
||||
override actionTags = ['消息接口'];
|
||||
override payloadExample = {
|
||||
message_id: 12345,
|
||||
};
|
||||
|
||||
override returnExample = null;
|
||||
|
||||
async _handle (payload: PayloadType) {
|
||||
const msg = MessageUnique.getMsgIdAndPeerByShortId(Number(payload.message_id));
|
||||
if (msg) {
|
||||
this.obContext.recallEventCache.set(msg.MsgId, setTimeout(() => {
|
||||
this.obContext.recallEventCache.delete(msg.MsgId);
|
||||
}, 5000));
|
||||
await this.core.apis.MsgApi.recallMsg(msg.Peer, msg.MsgId);
|
||||
return null;
|
||||
} else {
|
||||
throw new Error('Recall failed');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default DeleteMsg;
|
||||
@@ -0,0 +1,87 @@
|
||||
import { ActionName } from '@/snowcat-onebot/action/router';
|
||||
import { OneBotAction } from '@/snowcat-onebot/action/OneBotAction';
|
||||
import { MessageUnique } from 'snowcat-common/src/message-unique';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
import { PromiseTimer } from '@/snowcat-common/src/helper';
|
||||
import { ElementType } from '@/snowcat-core';
|
||||
import { MsgActionsExamples } from '@/snowcat-onebot/action/msg/examples';
|
||||
|
||||
const PayloadSchema = Type.Object({
|
||||
message_id: Type.Union([Type.Number(), Type.String()], { description: '消息ID' }),
|
||||
});
|
||||
|
||||
type PayloadType = Static<typeof PayloadSchema>;
|
||||
|
||||
const ReturnSchema = Type.Object({ text: Type.String({ description: '得到的文本' }) });
|
||||
|
||||
type ReturnType = Static<typeof ReturnSchema>;
|
||||
|
||||
export class FetchPttText extends OneBotAction<PayloadType, ReturnType> {
|
||||
override actionName = ActionName.FetchPttText;
|
||||
override actionSummary = '获取语音转文字结果';
|
||||
override actionTags = ['消息扩展'];
|
||||
override payloadExample = MsgActionsExamples.FetchPttText.payload;
|
||||
override returnExample = MsgActionsExamples.FetchPttText.response;
|
||||
|
||||
override payloadSchema = PayloadSchema;
|
||||
override returnSchema = ReturnSchema;
|
||||
|
||||
async _handle (payload: PayloadType) {
|
||||
if (!payload.message_id) {
|
||||
throw Error('参数message_id不能为空');
|
||||
}
|
||||
const MsgShortId = MessageUnique.getShortIdByMsgId(payload.message_id.toString());
|
||||
const msgIdWithPeer = MessageUnique.getMsgIdAndPeerByShortId(MsgShortId ?? +payload.message_id);
|
||||
if (!msgIdWithPeer) {
|
||||
throw new Error('消息不存在');
|
||||
}
|
||||
const peer = { guildId: '', peerUid: msgIdWithPeer?.Peer.peerUid, chatType: msgIdWithPeer.Peer.chatType };
|
||||
const getMsgTimeout = this.obContext.configLoader.configData.timeout.baseTimeout;
|
||||
const msgRes = await PromiseTimer(
|
||||
this.core.apis.MsgApi.getMsgsByMsgId(peer, [msgIdWithPeer?.MsgId || payload.message_id.toString()]),
|
||||
getMsgTimeout
|
||||
).catch((e: unknown) => {
|
||||
if (e instanceof Error && e.message.startsWith('PromiseTimer:')) {
|
||||
throw new Error(`消息不存在或已被撤回: ${e.message || String(e)}`);
|
||||
}
|
||||
throw e;
|
||||
});
|
||||
const msg = msgRes.msgList[0];
|
||||
if (!msg) throw Error('消息不存在或已被撤回');
|
||||
const pttSegment = msg.elements.find((s) =>
|
||||
s.elementType === ElementType.PTT
|
||||
);
|
||||
if (!pttSegment) {
|
||||
throw Error('消息中不包含语音');
|
||||
}
|
||||
const getPttTextTimeout = this.obContext.configLoader.configData.timeout.baseTimeout;
|
||||
await PromiseTimer(
|
||||
this.core.context.session.getMsgService().translatePtt2Text(msgIdWithPeer.MsgId, msgIdWithPeer.Peer, pttSegment),
|
||||
getPttTextTimeout
|
||||
).catch((e: unknown) => {
|
||||
if (e instanceof Error && e.message.startsWith('PromiseTimer:')) {
|
||||
throw new Error(`获取语音转文字结果失败: ${e.message || String(e)}`);
|
||||
}
|
||||
throw e;
|
||||
});
|
||||
const msgRes2 = await PromiseTimer(
|
||||
this.core.apis.MsgApi.getMsgsByMsgId(peer, [msgIdWithPeer?.MsgId || payload.message_id.toString()]),
|
||||
getMsgTimeout
|
||||
).catch((e: unknown) => {
|
||||
if (e instanceof Error && e.message.startsWith('PromiseTimer:')) {
|
||||
throw new Error(`消息不存在或已被撤回: ${e.message || String(e)}`);
|
||||
}
|
||||
throw e;
|
||||
});
|
||||
const msg2 = msgRes2.msgList[0];
|
||||
if (!msg2) throw Error('消息不存在或已被撤回');
|
||||
for (const s of msg2.elements) {
|
||||
if (s.elementType === ElementType.PTT) {
|
||||
if (s.pttElement?.text) {
|
||||
return { text: s.pttElement?.text };
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new Error('获取语音转文字结果失败');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
import { OneBotAction } from '@/snowcat-onebot/action/OneBotAction';
|
||||
import { ChatType, Peer } from 'snowcat-core/types';
|
||||
import { ActionName } from '@/snowcat-onebot/action/router';
|
||||
import { MessageUnique } from 'snowcat-common/src/message-unique';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
const PayloadSchema = Type.Object({
|
||||
message_id: Type.Union([Type.Number(), Type.String()], { description: '消息ID' }),
|
||||
group_id: Type.Optional(Type.String({ description: '目标群号' })),
|
||||
user_id: Type.Optional(Type.String({ description: '目标用户QQ' })),
|
||||
});
|
||||
|
||||
type PayloadType = Static<typeof PayloadSchema>;
|
||||
|
||||
const ReturnSchema = Type.Null({ description: '操作结果' });
|
||||
|
||||
type ReturnType = Static<typeof ReturnSchema>;
|
||||
|
||||
class ForwardSingleMsg extends OneBotAction<PayloadType, ReturnType> {
|
||||
override actionSummary = '转发单条消息';
|
||||
override actionDescription = '转发单条消息';
|
||||
override actionTags = ['消息接口'];
|
||||
override payloadExample = {
|
||||
message_id: 12345,
|
||||
group_id: '123456',
|
||||
};
|
||||
|
||||
override returnExample = null;
|
||||
|
||||
protected async getTargetPeer (payload: PayloadType): Promise<Peer> {
|
||||
if (payload.user_id) {
|
||||
const peerUid = await this.core.apis.UserApi.getUidByUinV2(payload.user_id.toString());
|
||||
if (!peerUid) {
|
||||
throw new Error(`无法找到私聊对象${payload.user_id}`);
|
||||
}
|
||||
return { chatType: ChatType.KCHATTYPEC2C, peerUid };
|
||||
}
|
||||
return { chatType: ChatType.KCHATTYPEGROUP, peerUid: payload.group_id!.toString() };
|
||||
}
|
||||
|
||||
async _handle (payload: PayloadType): Promise<null> {
|
||||
const msg = MessageUnique.getMsgIdAndPeerByShortId(+payload.message_id);
|
||||
if (!msg) {
|
||||
throw new Error(`无法找到消息${payload.message_id}`);
|
||||
}
|
||||
const peer = await this.getTargetPeer(payload);
|
||||
const ret = await this.core.apis.MsgApi.forwardMsg(msg.Peer,
|
||||
peer,
|
||||
[msg.MsgId]
|
||||
);
|
||||
if (ret.result !== 0) {
|
||||
throw new Error(`转发消息失败 ${ret.errMsg}`);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export class ForwardFriendSingleMsg extends ForwardSingleMsg {
|
||||
override payloadSchema = PayloadSchema;
|
||||
override returnSchema = ReturnSchema;
|
||||
override actionName = ActionName.ForwardFriendSingleMsg;
|
||||
}
|
||||
|
||||
export class ForwardGroupSingleMsg extends ForwardSingleMsg {
|
||||
override payloadSchema = PayloadSchema;
|
||||
override returnSchema = ReturnSchema;
|
||||
override actionName = ActionName.ForwardGroupSingleMsg;
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
import type { MessageFormat } from '@/snowcat-onebot/config/config';
|
||||
import { OneBotAction } from '@/snowcat-onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/snowcat-onebot/action/router';
|
||||
import { MessageUnique } from 'snowcat-common/src/message-unique';
|
||||
import { PromiseTimer } from 'snowcat-common/src/helper';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
import { NetworkAdapterConfig } from '@/snowcat-onebot/config/config';
|
||||
|
||||
import { MsgActionsExamples } from './examples';
|
||||
|
||||
const PayloadSchema = Type.Object({
|
||||
message_id: Type.Union([Type.Number(), Type.String()], { description: '消息ID' }),
|
||||
});
|
||||
|
||||
type PayloadType = Static<typeof PayloadSchema>;
|
||||
|
||||
const ReturnSchema = Type.Object({
|
||||
time: Type.Number({ description: '发送时间' }),
|
||||
message_type: Type.String({ description: '消息类型' }),
|
||||
message_id: Type.Number({ description: '消息ID' }),
|
||||
real_id: Type.Number({ description: '真实ID' }),
|
||||
message_seq: Type.Number({ description: '消息序号' }),
|
||||
sender: Type.Any({ description: '发送者' }),
|
||||
message: Type.Any({ description: '消息内容' }),
|
||||
raw_message: Type.String({ description: '原始消息内容' }),
|
||||
font: Type.Number({ description: '字体' }),
|
||||
group_id: Type.Optional(Type.Union([Type.Number(), Type.String()], { description: '群号' })),
|
||||
user_id: Type.Union([Type.Number(), Type.String()], { description: '发送者QQ号' }),
|
||||
emoji_likes_list: Type.Optional(Type.Array(Type.Any(), { description: '表情回应列表' })),
|
||||
}, { description: 'OneBot 11 消息' });
|
||||
|
||||
type ReturnType = Static<typeof ReturnSchema>;
|
||||
|
||||
class GetMsg extends OneBotAction<PayloadType, ReturnType> {
|
||||
override actionName = ActionName.GetMsg;
|
||||
override payloadSchema = PayloadSchema;
|
||||
override returnSchema = ReturnSchema;
|
||||
override actionSummary = '获取消息';
|
||||
override actionDescription = '根据消息 ID 获取消息详细信息';
|
||||
override actionTags = ['消息接口'];
|
||||
override payloadExample = MsgActionsExamples.GetMsg.payload;
|
||||
override returnExample = MsgActionsExamples.GetMsg.response;
|
||||
|
||||
async _handle (payload: PayloadType, _adapter: string, config: NetworkAdapterConfig) {
|
||||
if (!payload.message_id) {
|
||||
throw Error('参数message_id不能为空');
|
||||
}
|
||||
const MsgShortId = MessageUnique.getShortIdByMsgId(payload.message_id.toString());
|
||||
const msgIdWithPeer = MessageUnique.getMsgIdAndPeerByShortId(MsgShortId ?? +payload.message_id);
|
||||
if (!msgIdWithPeer) {
|
||||
throw new Error('消息不存在');
|
||||
}
|
||||
const peer = { guildId: '', peerUid: msgIdWithPeer?.Peer.peerUid, chatType: msgIdWithPeer.Peer.chatType };
|
||||
const getMsgTimeout = this.obContext.configLoader.configData.timeout.baseTimeout;
|
||||
const msgRes = await PromiseTimer(
|
||||
this.core.apis.MsgApi.getMsgsByMsgId(peer, [msgIdWithPeer?.MsgId || payload.message_id.toString()]),
|
||||
getMsgTimeout
|
||||
).catch((e: unknown) => {
|
||||
if (e instanceof Error && e.message.startsWith('PromiseTimer:')) {
|
||||
throw new Error(`消息不存在或已被撤回: ${e.message || String(e)}`);
|
||||
}
|
||||
throw e;
|
||||
});
|
||||
const msg = msgRes.msgList[0];
|
||||
if (!msg) throw Error('消息不存在或已被撤回');
|
||||
const retMsg = await this.obContext.apis.MsgApi.parseMessage(msg, config['messagePostFormat'] as MessageFormat | undefined ?? 'array');
|
||||
if (!retMsg) throw Error('消息为空');
|
||||
retMsg.emoji_likes_list = [];
|
||||
msg.emojiLikesList?.forEach(emoji => {
|
||||
retMsg.emoji_likes_list?.push({
|
||||
emoji_id: emoji.emojiId,
|
||||
emoji_type: emoji.emojiType,
|
||||
likes_cnt: emoji.likesCnt,
|
||||
});
|
||||
});
|
||||
try {
|
||||
retMsg.message_id = MessageUnique.createUniqueMsgId(peer, msg.msgId)!;
|
||||
retMsg.message_seq = retMsg.message_id;
|
||||
retMsg.real_id = retMsg.message_id;
|
||||
} catch {
|
||||
// ignored
|
||||
}
|
||||
return retMsg;
|
||||
}
|
||||
}
|
||||
|
||||
export default GetMsg;
|
||||
@@ -0,0 +1,96 @@
|
||||
import { ChatType, Peer } from 'snowcat-core/types';
|
||||
import { OneBotAction } from '@/snowcat-onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/snowcat-onebot/action/router';
|
||||
import { MessageUnique } from 'snowcat-common/src/message-unique';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
const PayloadSchema = Type.Object({
|
||||
user_id: Type.Optional(Type.Union([Type.String(), Type.Number()], { description: '用户QQ' })),
|
||||
group_id: Type.Optional(Type.String({ description: '群号' })),
|
||||
message_id: Type.Optional(Type.String({ description: '消息ID' })),
|
||||
});
|
||||
|
||||
type PayloadType = Static<typeof PayloadSchema>;
|
||||
|
||||
const ReturnSchema = Type.Null({ description: '操作结果' });
|
||||
|
||||
type ReturnType = Static<typeof ReturnSchema>;
|
||||
|
||||
class MarkMsgAsRead extends OneBotAction<PayloadType, ReturnType> {
|
||||
override actionSummary = '标记消息已读';
|
||||
override actionDescription = '标记指定渠道的消息为已读';
|
||||
override actionTags = ['消息接口'];
|
||||
override payloadExample = {
|
||||
message_id: 12345,
|
||||
};
|
||||
|
||||
override returnExample = null;
|
||||
|
||||
async getPeer (payload: PayloadType): Promise<Peer> {
|
||||
if (payload.message_id) {
|
||||
const s_peer = MessageUnique.getMsgIdAndPeerByShortId(+payload.message_id)?.Peer;
|
||||
if (s_peer) {
|
||||
return s_peer;
|
||||
}
|
||||
const l_peer = MessageUnique.getPeerByMsgId(payload.message_id.toString())?.Peer;
|
||||
if (l_peer) {
|
||||
return l_peer;
|
||||
}
|
||||
}
|
||||
if (payload.user_id) {
|
||||
const peerUid = await this.core.apis.UserApi.getUidByUinV2(payload.user_id.toString());
|
||||
if (!peerUid) {
|
||||
throw new Error(`私聊${payload.user_id}不存在`);
|
||||
}
|
||||
const isBuddy = await this.core.apis.FriendApi.isBuddy(peerUid);
|
||||
return { chatType: isBuddy ? ChatType.KCHATTYPEC2C : ChatType.KCHATTYPETEMPC2CFROMGROUP, peerUid };
|
||||
}
|
||||
if (!payload.group_id) {
|
||||
throw new Error('缺少参数 group_id 或 user_id');
|
||||
}
|
||||
return { chatType: ChatType.KCHATTYPEGROUP, peerUid: payload.group_id.toString() };
|
||||
}
|
||||
|
||||
async _handle (payload: PayloadType): Promise<null> {
|
||||
const ret = await this.core.apis.MsgApi.setMsgRead(await this.getPeer(payload));
|
||||
if (ret.result !== 0) {
|
||||
throw new Error('设置已读失败,' + ret.errMsg);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// 以下为非标准实现
|
||||
export class MarkPrivateMsgAsRead extends MarkMsgAsRead {
|
||||
override payloadSchema = PayloadSchema;
|
||||
override returnSchema = ReturnSchema;
|
||||
override actionName = ActionName.MarkPrivateMsgAsRead;
|
||||
override actionSummary = '标记私聊已读';
|
||||
}
|
||||
|
||||
export class MarkGroupMsgAsRead extends MarkMsgAsRead {
|
||||
override payloadSchema = PayloadSchema;
|
||||
override returnSchema = ReturnSchema;
|
||||
override actionName = ActionName.MarkGroupMsgAsRead;
|
||||
override actionSummary = '标记群聊已读';
|
||||
}
|
||||
|
||||
export class GoCQHTTPMarkMsgAsRead extends MarkMsgAsRead {
|
||||
override payloadSchema = PayloadSchema;
|
||||
override returnSchema = ReturnSchema;
|
||||
override actionName = ActionName.GoCQHTTP_MarkMsgAsRead;
|
||||
override actionSummary = '标记消息已读 (Go-CQHTTP)';
|
||||
}
|
||||
|
||||
export class MarkAllMsgAsRead extends OneBotAction<void, null> {
|
||||
override actionName = ActionName._MarkAllMsgAsRead;
|
||||
override actionSummary = '标记所有消息已读';
|
||||
override actionTags = ['消息接口'];
|
||||
override payloadExample = {};
|
||||
override returnExample = null;
|
||||
|
||||
async _handle (): Promise<null> {
|
||||
await this.core.apis.MsgApi.markAllMsgAsRead();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,517 @@
|
||||
import {
|
||||
OB11MessageData,
|
||||
OB11MessageDataType,
|
||||
OB11MessageMixType,
|
||||
OB11MessageNode,
|
||||
OB11PostContext,
|
||||
} from '@/snowcat-onebot/types';
|
||||
import { ActionName, BaseCheckResult } from '@/snowcat-onebot/action/router';
|
||||
import { decodeCQCode } from '@/snowcat-onebot/helper/cqcode';
|
||||
import { MessageUnique } from 'snowcat-common/src/message-unique';
|
||||
import { ChatType, ElementType, SnowCatCore, Peer, RawMessage, SendArkElement, SendMessageElement } from 'snowcat-core';
|
||||
import { OneBotAction } from '@/snowcat-onebot/action/OneBotAction';
|
||||
import { snowCatStable } from '@/snowcat-onebot/stable-line';
|
||||
import { ForwardMsgBuilder } from '@/snowcat-core/helper/forward-msg-builder';
|
||||
import { stringifyWithBigInt } from 'snowcat-common/src/helper';
|
||||
import { PacketMsg } from 'snowcat-core/packet/message/message';
|
||||
import { rawMsgWithSendMsg } from 'snowcat-core/packet/message/converter';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
import { MsgActionsExamples } from '@/snowcat-onebot/action/msg/examples';
|
||||
import { OB11MessageMixTypeSchema } from '@/snowcat-onebot/types/message';
|
||||
import { UploadForwardMsgParams } from '@/snowcat-core/packet/transformer/message/UploadForwardMsgV2';
|
||||
|
||||
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: 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: '合并转发提示' })),
|
||||
timeout: Type.Optional(Type.Number({ description: '自定义发送超时(毫秒),覆盖自动计算值' })),
|
||||
});
|
||||
|
||||
export type SendMsgPayload = Static<typeof SendMsgPayloadSchema>;
|
||||
|
||||
export const SendMsgReturnSchema = Type.Object({
|
||||
message_id: Type.Number({ description: '消息ID' }),
|
||||
res_id: Type.Optional(Type.String({ description: '转发消息的 res_id' })),
|
||||
forward_id: Type.Optional(Type.String({ description: '转发消息的 forward_id' })),
|
||||
});
|
||||
|
||||
export type ReturnDataType = Static<typeof SendMsgReturnSchema>;
|
||||
|
||||
export enum ContextMode {
|
||||
Normal = 0,
|
||||
Private = 1,
|
||||
Group = 2,
|
||||
}
|
||||
|
||||
// Normalizes a mixed type (CQCode/a single segment/segment array) into a segment array.
|
||||
export function normalize (message: OB11MessageMixType, autoEscape = false): OB11MessageData[] {
|
||||
return typeof message === 'string'
|
||||
? (
|
||||
autoEscape
|
||||
? [{ type: OB11MessageDataType.text, data: { text: message } }]
|
||||
: decodeCQCode(message)
|
||||
)
|
||||
: Array.isArray(message) ? message : [message];
|
||||
}
|
||||
|
||||
export async function createContext (core: SnowCatCore, payload: OB11PostContext | undefined, contextMode: ContextMode = ContextMode.Normal): Promise<Peer> {
|
||||
if (!payload) {
|
||||
throw new Error('请传递请求内容');
|
||||
}
|
||||
if ((contextMode === ContextMode.Group || contextMode === ContextMode.Normal) && payload.group_id) {
|
||||
return {
|
||||
chatType: ChatType.KCHATTYPEGROUP,
|
||||
peerUid: payload.group_id.toString(),
|
||||
};
|
||||
}
|
||||
if ((contextMode === ContextMode.Private || contextMode === ContextMode.Normal) && payload.user_id) {
|
||||
const Uid = await core.apis.UserApi.getUidByUinV2(payload.user_id.toString());
|
||||
if (!Uid) {
|
||||
if (payload.group_id) {
|
||||
return {
|
||||
chatType: ChatType.KCHATTYPEGROUP,
|
||||
peerUid: payload.group_id.toString(),
|
||||
guildId: '',
|
||||
};
|
||||
}
|
||||
throw new Error('无法获取用户信息');
|
||||
}
|
||||
const isBuddy = await core.apis.FriendApi.isBuddy(Uid);
|
||||
if (!isBuddy) {
|
||||
if (payload.group_id) {
|
||||
return {
|
||||
chatType: ChatType.KCHATTYPETEMPC2CFROMGROUP,
|
||||
peerUid: Uid,
|
||||
guildId: payload.group_id.toString(),
|
||||
};
|
||||
}
|
||||
const ret = await core.apis.MsgApi.getTempChatInfo(ChatType.KCHATTYPETEMPC2CFROMGROUP, Uid);
|
||||
if (ret.tmpChatInfo?.groupCode) {
|
||||
return {
|
||||
chatType: ChatType.KCHATTYPETEMPC2CFROMGROUP,
|
||||
peerUid: Uid,
|
||||
guildId: '',
|
||||
};
|
||||
}
|
||||
return {
|
||||
chatType: ChatType.KCHATTYPEC2C,
|
||||
peerUid: Uid,
|
||||
guildId: '',
|
||||
};
|
||||
}
|
||||
return {
|
||||
chatType: ChatType.KCHATTYPEC2C,
|
||||
peerUid: Uid,
|
||||
guildId: '',
|
||||
};
|
||||
}
|
||||
if (contextMode === ContextMode.Private && payload.group_id) {
|
||||
throw new Error('当前私聊发送,请指定 user_id 而不是 group_id');
|
||||
}
|
||||
if (contextMode === ContextMode.Group && payload.user_id) {
|
||||
throw new Error('当前群聊发送,请指定 group_id 而不是 user_id');
|
||||
}
|
||||
throw new Error('请指定正确的 group_id 或 user_id');
|
||||
}
|
||||
|
||||
function getSpecialMsgNum (messages: OB11MessageData[], msgType: OB11MessageDataType): number {
|
||||
return messages.filter(msg => msg.type === msgType).length;
|
||||
}
|
||||
|
||||
function isNode (msg: OB11MessageData): msg is OB11MessageNode {
|
||||
return msg.type === OB11MessageDataType.node;
|
||||
}
|
||||
|
||||
export class SendMsgBase extends OneBotAction<SendMsgPayload, ReturnDataType> {
|
||||
override payloadSchema = SendMsgPayloadSchema;
|
||||
override returnSchema = SendMsgReturnSchema;
|
||||
override actionTags = ['消息接口'];
|
||||
|
||||
protected override async check (payload: SendMsgPayload): Promise<BaseCheckResult> {
|
||||
const messages = normalize(payload.message);
|
||||
const nodeElementLength = getSpecialMsgNum(messages, OB11MessageDataType.node);
|
||||
if (nodeElementLength > 0 && nodeElementLength !== messages.length) {
|
||||
return {
|
||||
valid: false,
|
||||
message: '转发消息不能和普通消息混在一起发送,转发需要保证message只有type为node的元素',
|
||||
};
|
||||
}
|
||||
return { valid: true };
|
||||
}
|
||||
|
||||
async _handle (payload: SendMsgPayload): Promise<ReturnDataType> {
|
||||
// SnowCat 稳定线:主动发言前过真人抖动+账号预算+熔断(被动回复不受预算惩罚)
|
||||
await snowCatStable.stableDelay();
|
||||
return this.base_handle(payload);
|
||||
}
|
||||
|
||||
async base_handle (payload: SendMsgPayload, contextMode: ContextMode = ContextMode.Normal): Promise<ReturnDataType> {
|
||||
if (payload.message_type === 'group') contextMode = ContextMode.Group;
|
||||
if (payload.message_type === 'private') contextMode = ContextMode.Private;
|
||||
const peer = await createContext(this.core, {
|
||||
message_type: payload.message_type,
|
||||
user_id: payload.user_id?.toString(),
|
||||
group_id: payload.group_id?.toString(),
|
||||
}, contextMode);
|
||||
|
||||
const messages = normalize(
|
||||
payload.message,
|
||||
typeof payload.auto_escape === 'string' ? payload.auto_escape === 'true' : !!payload.auto_escape
|
||||
);
|
||||
|
||||
const nodeMessages = messages.filter(isNode);
|
||||
if (nodeMessages.length > 0) {
|
||||
const packetMode = this.core.apis.PacketApi.packetStatus;
|
||||
let returnMsgAndResId: { message: RawMessage | null, res_id?: string; } | null;
|
||||
try {
|
||||
returnMsgAndResId = packetMode
|
||||
? await this.handleForwardedNodesPacket(peer, nodeMessages, payload.source, payload.news, payload.summary, payload.prompt)
|
||||
: await this.handleForwardedNodes(peer, nodeMessages);
|
||||
} catch (e: unknown) {
|
||||
throw Error(packetMode ? `发送伪造合并转发消息失败: ${(e as Error)?.stack}` : `发送合并转发消息失败: ${(e as Error)?.stack}`);
|
||||
}
|
||||
if (!returnMsgAndResId) {
|
||||
throw Error('发送合并转发消息失败:returnMsgAndResId 为空!');
|
||||
}
|
||||
if (returnMsgAndResId.message) {
|
||||
const msgShortId = MessageUnique.createUniqueMsgId({
|
||||
guildId: '',
|
||||
peerUid: peer.peerUid,
|
||||
chatType: peer.chatType,
|
||||
}, (returnMsgAndResId.message).msgId);
|
||||
|
||||
// 对gocq的forward_id进行兼容
|
||||
const resId = returnMsgAndResId.res_id!;
|
||||
return { message_id: msgShortId!, res_id: resId, forward_id: resId };
|
||||
} else if (returnMsgAndResId.res_id && !returnMsgAndResId.message) {
|
||||
throw Error(`发送转发消息(res_id:${returnMsgAndResId.res_id} 失败`);
|
||||
}
|
||||
} else {
|
||||
// if (getSpecialMsgNum(payload, OB11MessageDataType.music)) {
|
||||
// const music: OB11MessageCustomMusic = messages[0] as OB11MessageCustomMusic;
|
||||
// if (music) {
|
||||
// }
|
||||
// }
|
||||
}
|
||||
// log("send msg:", peer, sendElements)
|
||||
|
||||
const { sendElements, deleteAfterSentFiles } = await this.obContext.apis.MsgApi
|
||||
.createSendElements(messages, peer);
|
||||
const returnMsg = await this.obContext.apis.MsgApi.sendMsgWithOb11UniqueId(peer, sendElements, deleteAfterSentFiles, payload.timeout);
|
||||
return { message_id: returnMsg.id! };
|
||||
}
|
||||
|
||||
private async uploadForwardedNodesPacket (msgPeer: Peer, messageNodes: OB11MessageNode[], source?: string, news?: {
|
||||
text: string;
|
||||
}[], summary?: string, prompt?: string, parentMeta?: {
|
||||
user_id: string,
|
||||
nickname: string,
|
||||
}, dp: number = 0): Promise<{
|
||||
finallySendElements: SendArkElement,
|
||||
res_id?: string,
|
||||
uuid?: string,
|
||||
packetMsg: PacketMsg[],
|
||||
deleteAfterSentFiles: string[],
|
||||
innerPacketMsg?: Array<{ uuid: string, packetMsg?: PacketMsg[], actionMsgBody?: UploadForwardMsgParams['actionMsgBody']; }>;
|
||||
} | null> {
|
||||
const packetMsg: PacketMsg[] = [];
|
||||
const delFiles: string[] = [];
|
||||
const innerMsg: Array<{ uuid: string, packetMsg?: PacketMsg[], actionMsgBody?: UploadForwardMsgParams['actionMsgBody']; }> = [];
|
||||
for (const node of messageNodes) {
|
||||
if (dp >= 3) {
|
||||
this.core.context.logger.logWarn('转发消息深度超过3层,将停止解析!');
|
||||
break;
|
||||
}
|
||||
if (!node.data.id) {
|
||||
const OB11Data = normalize(node.type === OB11MessageDataType.node ? node.data.content : node);
|
||||
let sendElements: SendMessageElement[];
|
||||
|
||||
const subNodeMessages = OB11Data.filter(isNode);
|
||||
if (subNodeMessages.length > 0) {
|
||||
const uploadReturnData = await this.uploadForwardedNodesPacket(msgPeer, subNodeMessages, node.data.source, node.data.news, node.data.summary, node.data.prompt, {
|
||||
user_id: (node.data.user_id ?? node.data.uin)?.toString() ?? parentMeta?.user_id ?? this.core.selfInfo.uin,
|
||||
nickname: (node.data.nickname || node.data.name) ?? parentMeta?.nickname ?? 'QQ用户',
|
||||
}, dp + 1);
|
||||
sendElements = uploadReturnData?.finallySendElements ? [uploadReturnData.finallySendElements] : [];
|
||||
delFiles.push(...(uploadReturnData?.deleteAfterSentFiles || []));
|
||||
if (uploadReturnData?.uuid) {
|
||||
innerMsg.push({ uuid: uploadReturnData.uuid, packetMsg: uploadReturnData.packetMsg });
|
||||
uploadReturnData.innerPacketMsg?.forEach(m => {
|
||||
innerMsg.push(m);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
const sendElementsCreateReturn = await this.obContext.apis.MsgApi.createSendElements(
|
||||
OB11Data,
|
||||
msgPeer,
|
||||
[],
|
||||
{ usePacketVideoMetadata: true }
|
||||
);
|
||||
sendElements = sendElementsCreateReturn.sendElements;
|
||||
delFiles.push(...sendElementsCreateReturn.deleteAfterSentFiles);
|
||||
}
|
||||
|
||||
const packetMsgElements: rawMsgWithSendMsg = {
|
||||
senderUin: Number((node.data.user_id ?? node.data.uin) ?? parentMeta?.user_id) || +this.core.selfInfo.uin,
|
||||
senderName: (node.data.nickname || node.data.name) ?? parentMeta?.nickname ?? 'QQ用户',
|
||||
groupId: msgPeer.chatType === ChatType.KCHATTYPEGROUP ? +msgPeer.peerUid : 0,
|
||||
time: Number(node.data.time) || Date.now(),
|
||||
msg: sendElements,
|
||||
};
|
||||
this.core.context.logger.logDebug(`handleForwardedNodesPacket[SendRaw] 开始转换 ${stringifyWithBigInt(packetMsgElements)}`);
|
||||
const transformedMsg = this.core.apis.PacketApi.pkt.msgConverter.rawMsgWithSendMsgToPacketMsg(packetMsgElements);
|
||||
this.core.context.logger.logDebug(`handleForwardedNodesPacket[SendRaw] 转换为 ${stringifyWithBigInt(transformedMsg)}`);
|
||||
packetMsg.push(transformedMsg);
|
||||
} else if (node.data.id) {
|
||||
const id = node.data.id;
|
||||
const nodeMsg = MessageUnique.getMsgIdAndPeerByShortId(+id) || MessageUnique.getPeerByMsgId(id);
|
||||
if (!nodeMsg) {
|
||||
this.core.context.logger.logError('转发消息失败,未找到消息', id);
|
||||
continue;
|
||||
}
|
||||
const msg = (await this.core.apis.MsgApi.getMsgsByMsgId(nodeMsg.Peer, [nodeMsg.MsgId])).msgList[0];
|
||||
this.core.context.logger.logDebug(`handleForwardedNodesPacket[PureRaw] 开始转换 ${stringifyWithBigInt(msg)}`);
|
||||
if (msg) {
|
||||
const msgCache = await this.core.apis.FileApi.downloadRawMsgMedia([msg]);
|
||||
delFiles.push(...msgCache);
|
||||
const transformedMsg = this.core.apis.PacketApi.pkt.msgConverter.rawMsgToPacketMsg(msg, msgPeer);
|
||||
// 检测嵌套合并转发消息,拉取内层protobuf数据
|
||||
for (const element of msg.elements) {
|
||||
let resId: string | undefined;
|
||||
let uuid: string | undefined;
|
||||
if (element.multiForwardMsgElement?.resId) {
|
||||
resId = element.multiForwardMsgElement.resId;
|
||||
uuid = element.multiForwardMsgElement.fileName;
|
||||
} else if (element.arkElement?.bytesData) {
|
||||
try {
|
||||
const json = JSON.parse(element.arkElement.bytesData);
|
||||
if (json.app === 'com.tencent.multimsg') {
|
||||
resId = json.meta?.detail?.resid;
|
||||
uuid = json.meta?.detail?.uniseq || json.extra?.filename;
|
||||
}
|
||||
} catch { /* not a valid json */ }
|
||||
}
|
||||
if (resId && uuid) {
|
||||
try {
|
||||
const rawActions = await this.core.apis.PacketApi.pkt.operation.FetchForwardMsgRaw(resId);
|
||||
for (const action of rawActions) {
|
||||
if (action.actionCommand === 'MultiMsg') {
|
||||
innerMsg.push({ uuid, actionMsgBody: action.actionData.msgBody });
|
||||
} else {
|
||||
innerMsg.push({ uuid: action.actionCommand, actionMsgBody: action.actionData.msgBody });
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
this.core.context.logger.logError(`获取合并转发内层消息失败: ${(e as Error)?.stack}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
this.core.context.logger.logDebug(`handleForwardedNodesPacket[PureRaw] 转换为 ${stringifyWithBigInt(transformedMsg)}`);
|
||||
packetMsg.push(transformedMsg);
|
||||
}
|
||||
} else {
|
||||
this.core.context.logger.logDebug(`handleForwardedNodesPacket 跳过元素 ${stringifyWithBigInt(node)}`);
|
||||
}
|
||||
}
|
||||
if (packetMsg.length === 0) {
|
||||
this.core.context.logger.logWarn('handleForwardedNodesPacket 元素为空!');
|
||||
return null;
|
||||
}
|
||||
const uploadMsgData: UploadForwardMsgParams[] = [{
|
||||
actionCommand: 'MultiMsg',
|
||||
actionMsg: packetMsg,
|
||||
}];
|
||||
innerMsg.forEach(({ uuid, packetMsg: msg, actionMsgBody: raw }) => {
|
||||
uploadMsgData.push({
|
||||
actionCommand: uuid,
|
||||
actionMsg: msg,
|
||||
actionMsgBody: raw,
|
||||
});
|
||||
});
|
||||
const resid = await this.core.apis.PacketApi.pkt.operation.UploadForwardMsgV2(uploadMsgData, msgPeer.chatType === ChatType.KCHATTYPEGROUP ? +msgPeer.peerUid : 0);
|
||||
const uuid = crypto.randomUUID();
|
||||
const forwardJson = ForwardMsgBuilder.fromPacketMsg(resid, packetMsg, source, news, summary, prompt, uuid);
|
||||
return {
|
||||
deleteAfterSentFiles: delFiles,
|
||||
finallySendElements: {
|
||||
elementType: ElementType.ARK,
|
||||
elementId: '',
|
||||
arkElement: {
|
||||
bytesData: JSON.stringify(forwardJson),
|
||||
},
|
||||
} as SendArkElement,
|
||||
res_id: resid,
|
||||
uuid,
|
||||
packetMsg,
|
||||
innerPacketMsg: innerMsg,
|
||||
};
|
||||
}
|
||||
|
||||
private async handleForwardedNodesPacket (msgPeer: Peer, messageNodes: OB11MessageNode[], source?: string, news?: {
|
||||
text: string;
|
||||
}[], summary?: string, prompt?: string): Promise<{
|
||||
message: RawMessage | null,
|
||||
res_id?: string;
|
||||
}> {
|
||||
const uploadReturnData = await this.uploadForwardedNodesPacket(msgPeer, messageNodes, source, news, summary, prompt);
|
||||
const res_id = uploadReturnData?.res_id;
|
||||
const finallySendElements = uploadReturnData?.finallySendElements;
|
||||
if (!finallySendElements) throw Error('转发消息失败,生成节点为空');
|
||||
const returnMsg = await this.obContext.apis.MsgApi.sendMsgWithOb11UniqueId(msgPeer, [finallySendElements], uploadReturnData.deleteAfterSentFiles || []).catch(() => undefined);
|
||||
return { message: returnMsg ?? null, res_id: res_id! };
|
||||
}
|
||||
|
||||
private async handleForwardedNodes (destPeer: Peer, messageNodes: OB11MessageNode[]): Promise<{
|
||||
message: RawMessage | null,
|
||||
res_id?: string;
|
||||
}> {
|
||||
const selfPeer = {
|
||||
chatType: ChatType.KCHATTYPEC2C,
|
||||
peerUid: this.core.selfInfo.uid,
|
||||
};
|
||||
let nodeMsgIds: string[] = [];
|
||||
for (const messageNode of messageNodes) {
|
||||
const nodeId = messageNode.data.id;
|
||||
if (nodeId) {
|
||||
// 对Msgid和OB11ID混用情况兜底
|
||||
const nodeMsg = MessageUnique.getMsgIdAndPeerByShortId(parseInt(nodeId)) || MessageUnique.getPeerByMsgId(nodeId);
|
||||
if (!nodeMsg) {
|
||||
this.core.context.logger.logError('转发消息失败,未找到消息', nodeId);
|
||||
continue;
|
||||
}
|
||||
nodeMsgIds.push(nodeMsg.MsgId);
|
||||
} else {
|
||||
// 自定义的消息
|
||||
try {
|
||||
const OB11Data = normalize(messageNode.data.content);
|
||||
// 筛选node消息
|
||||
const subNodeMessages = OB11Data.filter(isNode);
|
||||
if (subNodeMessages.length !== 0) {
|
||||
if (subNodeMessages.length !== OB11Data.length) {
|
||||
this.core.context.logger.logError('子消息中包含非node消息 跳过不合法部分');
|
||||
continue;
|
||||
}
|
||||
const nodeMsg = await this.handleForwardedNodes(selfPeer, subNodeMessages);
|
||||
if (nodeMsg) {
|
||||
nodeMsgIds.push(nodeMsg.message!.msgId);
|
||||
MessageUnique.createUniqueMsgId(selfPeer, nodeMsg.message!.msgId);
|
||||
}
|
||||
// 完成子卡片生成跳过后续
|
||||
continue;
|
||||
}
|
||||
const { sendElements } = await this.obContext.apis.MsgApi
|
||||
.createSendElements(OB11Data, destPeer);
|
||||
|
||||
// 拆分消息
|
||||
|
||||
const MixElement = sendElements.filter(
|
||||
element =>
|
||||
element.elementType !== ElementType.FILE && element.elementType !== ElementType.VIDEO && element.elementType !== ElementType.ARK && element.elementType !== ElementType.PTT
|
||||
);
|
||||
const SingleElement = sendElements.filter(
|
||||
element =>
|
||||
element.elementType === ElementType.FILE || element.elementType === ElementType.VIDEO || element.elementType === ElementType.ARK || element.elementType === ElementType.PTT
|
||||
).map(e => [e]);
|
||||
|
||||
const AllElement: SendMessageElement[][] = [MixElement, ...SingleElement].filter(e => e !== undefined && e.length !== 0);
|
||||
const MsgNodeList: Promise<RawMessage | undefined>[] = [];
|
||||
for (const sendElementsSplitElement of AllElement) {
|
||||
MsgNodeList.push(this.obContext.apis.MsgApi.sendMsgWithOb11UniqueId(selfPeer, sendElementsSplitElement, []).catch(() => undefined));
|
||||
}
|
||||
(await Promise.allSettled(MsgNodeList)).map((result) => {
|
||||
if (result.status === 'fulfilled' && result.value) {
|
||||
nodeMsgIds.push(result.value.msgId);
|
||||
MessageUnique.createUniqueMsgId(selfPeer, result.value.msgId);
|
||||
}
|
||||
return result;
|
||||
});
|
||||
} catch (e: unknown) {
|
||||
this.core.context.logger.logDebug('生成转发消息节点失败', (e as Error).stack);
|
||||
}
|
||||
}
|
||||
}
|
||||
const nodeMsgArray: Array<RawMessage> = [];
|
||||
let srcPeer: Peer | undefined;
|
||||
let needSendSelf = false;
|
||||
// 检测是否处于同一个Peer 不在同一个peer则全部消息由自身发送
|
||||
for (const msgId of nodeMsgIds) {
|
||||
const nodeMsgPeer = MessageUnique.getPeerByMsgId(msgId);
|
||||
if (!nodeMsgPeer) {
|
||||
this.core.context.logger.logError('转发消息失败,未找到消息', msgId);
|
||||
continue;
|
||||
}
|
||||
const nodeMsg = (await this.core.apis.MsgApi.getMsgsByMsgId(nodeMsgPeer.Peer, [msgId])).msgList[0];
|
||||
if (nodeMsg) {
|
||||
srcPeer = srcPeer ?? { chatType: nodeMsg.chatType, peerUid: nodeMsg.peerUid };
|
||||
if (srcPeer.peerUid !== nodeMsg.peerUid) {
|
||||
needSendSelf = true;
|
||||
}
|
||||
nodeMsgArray.push(nodeMsg);
|
||||
}
|
||||
}
|
||||
nodeMsgIds = nodeMsgArray.map(msg => msg.msgId);
|
||||
let retMsgIds: string[] = [];
|
||||
if (needSendSelf) {
|
||||
for (const [, msg] of nodeMsgArray.entries()) {
|
||||
if (msg.peerUid === this.core.selfInfo.uid) {
|
||||
retMsgIds.push(msg.msgId);
|
||||
continue;
|
||||
}
|
||||
const ClonedMsg = await this.cloneMsg(msg);
|
||||
if (ClonedMsg) retMsgIds.push(ClonedMsg.msgId);
|
||||
}
|
||||
} else {
|
||||
retMsgIds = nodeMsgIds;
|
||||
}
|
||||
if (retMsgIds.length === 0) throw Error('转发消息失败,生成节点为空');
|
||||
try {
|
||||
this.core.context.logger.logDebug('开发转发', srcPeer, destPeer, retMsgIds);
|
||||
return {
|
||||
message: await this.core.apis.MsgApi.multiForwardMsg(srcPeer!, destPeer, retMsgIds),
|
||||
};
|
||||
} catch (e: unknown) {
|
||||
this.core.context.logger.logError('forward failed', (e as Error)?.stack);
|
||||
return {
|
||||
message: null,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async cloneMsg (msg: RawMessage): Promise<RawMessage | undefined> {
|
||||
const selfPeer = {
|
||||
chatType: ChatType.KCHATTYPEC2C,
|
||||
peerUid: this.core.selfInfo.uid,
|
||||
};
|
||||
// msg 为待克隆消息
|
||||
const sendElements: SendMessageElement[] = [];
|
||||
|
||||
for (const element of msg.elements) {
|
||||
sendElements.push(element as SendMessageElement);
|
||||
}
|
||||
|
||||
if (sendElements.length === 0) {
|
||||
this.core.context.logger.logDebug('需要clone的消息无法解析,将会忽略掉', msg);
|
||||
}
|
||||
try {
|
||||
return await this.core.apis.MsgApi.sendMsg(selfPeer, sendElements);
|
||||
} catch (e: unknown) {
|
||||
this.core.context.logger.logError((e as Error)?.stack, '克隆转发消息失败,将忽略本条消息', msg);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
export default class SendMsg extends SendMsgBase {
|
||||
override actionName = ActionName.SendMsg;
|
||||
override actionSummary = '发送消息';
|
||||
override actionDescription = '发送私聊或群聊消息';
|
||||
override actionTags = ['消息接口'];
|
||||
override payloadExample = MsgActionsExamples.SendMsg.payload;
|
||||
override returnExample = MsgActionsExamples.SendMsg.response;
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import { ContextMode, ReturnDataType, SendMsgBase, SendMsgPayload } from './SendMsg';
|
||||
import { ActionName, BaseCheckResult } from '@/snowcat-onebot/action/router';
|
||||
import { snowCatStable } from '@/snowcat-onebot/stable-line';
|
||||
|
||||
// 未检测参数
|
||||
class SendPrivateMsg extends SendMsgBase {
|
||||
override actionName = ActionName.SendPrivateMsg;
|
||||
override actionSummary = '发送私聊消息';
|
||||
override actionDescription = '发送私聊消息';
|
||||
override actionTags = ['消息接口'];
|
||||
override payloadExample = {
|
||||
user_id: '123456789',
|
||||
message: 'hello',
|
||||
};
|
||||
|
||||
override returnExample = {
|
||||
message_id: 123456,
|
||||
};
|
||||
|
||||
protected override async check (payload: SendMsgPayload): Promise<BaseCheckResult> {
|
||||
payload.message_type = 'private';
|
||||
return super.check(payload);
|
||||
}
|
||||
|
||||
override async _handle (payload: SendMsgPayload): Promise<ReturnDataType> {
|
||||
// SnowCat 稳定线:真人抖动+账号预算+熔断
|
||||
await snowCatStable.stableDelay();
|
||||
return this.base_handle(payload, ContextMode.Private);
|
||||
}
|
||||
}
|
||||
|
||||
export default SendPrivateMsg;
|
||||
@@ -0,0 +1,57 @@
|
||||
import { ActionName } from '@/snowcat-onebot/action/router';
|
||||
import { OneBotAction } from '@/snowcat-onebot/action/OneBotAction';
|
||||
import { MessageUnique } from 'snowcat-common/src/message-unique';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
const PayloadSchema = Type.Object({
|
||||
message_id: Type.Union([Type.Number(), Type.String()], { description: '消息ID' }),
|
||||
emoji_id: Type.Union([Type.Number(), Type.String()], { description: '表情ID' }),
|
||||
set: Type.Optional(Type.Union([Type.Boolean(), Type.String()], { description: '是否设置' })),
|
||||
});
|
||||
|
||||
type PayloadType = Static<typeof PayloadSchema>;
|
||||
|
||||
const ReturnSchema = Type.Any({ description: '操作结果' });
|
||||
|
||||
type ReturnType = Static<typeof ReturnSchema>;
|
||||
|
||||
export class SetMsgEmojiLike extends OneBotAction<PayloadType, ReturnType> {
|
||||
override actionName = ActionName.SetMsgEmojiLike;
|
||||
override actionSummary = '设置消息表情点赞';
|
||||
override actionTags = ['消息扩展'];
|
||||
override payloadExample = {
|
||||
message_id: 12345,
|
||||
emoji_id: '123',
|
||||
set: true,
|
||||
};
|
||||
|
||||
override returnExample = {
|
||||
result: true,
|
||||
};
|
||||
|
||||
override payloadSchema = PayloadSchema;
|
||||
override returnSchema = ReturnSchema;
|
||||
|
||||
async _handle (payload: PayloadType) {
|
||||
const msg = MessageUnique.getMsgIdAndPeerByShortId(+payload.message_id);
|
||||
if (!msg) {
|
||||
throw new Error('msg not found');
|
||||
}
|
||||
if (!payload.emoji_id) {
|
||||
throw new Error('emojiId not found');
|
||||
}
|
||||
payload.set = payload.set ?? true;
|
||||
|
||||
const msgData = (await this.core.apis.MsgApi.getMsgsByMsgId(msg.Peer, [msg.MsgId])).msgList;
|
||||
if (!msgData || msgData.length === 0 || !msgData[0]?.msgSeq) {
|
||||
throw new Error('find msg by msgid error');
|
||||
}
|
||||
|
||||
return await this.core.apis.MsgApi.setEmojiLike(
|
||||
msg.Peer,
|
||||
msgData[0].msgSeq,
|
||||
payload.emoji_id.toString(),
|
||||
typeof payload.set === 'string' ? payload.set === 'true' : !!payload.set
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
export const MsgActionsExamples = {
|
||||
DeleteMsg: {
|
||||
payload: { message_id: 123456 },
|
||||
response: {},
|
||||
},
|
||||
GetMsg: {
|
||||
payload: { message_id: 123456 },
|
||||
response: {
|
||||
time: 1710000000,
|
||||
message_type: 'group',
|
||||
message_id: 123456,
|
||||
real_id: 123456,
|
||||
sender: { user_id: 123456789, nickname: '昵称' },
|
||||
message: 'hello',
|
||||
},
|
||||
},
|
||||
FetchPttText: {
|
||||
payload: { message_id: 123456 },
|
||||
response: {
|
||||
text: 'hello',
|
||||
},
|
||||
},
|
||||
MarkMsgAsRead: {
|
||||
payload: { group_id: '123456' },
|
||||
response: {},
|
||||
},
|
||||
SendMsg: {
|
||||
payload: { message_type: 'group', group_id: '123456', message: 'hello' },
|
||||
response: { message_id: 123456 },
|
||||
},
|
||||
SendPrivateMsg: {
|
||||
payload: { user_id: '123456789', message: 'hello' },
|
||||
response: { message_id: 123456 },
|
||||
},
|
||||
SetMsgEmojiLike: {
|
||||
payload: { message_id: 123456, emoji_id: '12345' },
|
||||
response: {},
|
||||
},
|
||||
};
|
||||
Reference in new issue
Block a user