NapCatQQ/src/onebot/api/msg.ts
2024-08-25 18:20:58 +08:00

443 lines
17 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { UUIDConverter } from '@/common/utils/helper';
import { MessageUnique } from '@/common/utils/MessageUnique';
import { AtType, ChatType, FaceIndex, MessageElement, NapCatCore, RawMessage } from '@/core';
import { NapCatOneBot11Adapter, OB11Message, OB11MessageData, OB11MessageDataType } from '@/onebot';
import { OB11Constructor } from '../helper';
import { EventType } from '@/onebot/event/OB11BaseEvent';
import { encodeCQCode } from '@/onebot/helper/cqcode';
type RawToOb11Converters = {
[Key in keyof MessageElement as Key extends `${string}Element` ? Key : never]: (
element: Exclude<MessageElement[Key], null | undefined>,
msg: RawMessage,
elementWrapper: MessageElement
) => PromiseLike<OB11MessageData | null>
}
function keyCanBeParsed(key: string, parser: RawToOb11Converters): key is keyof RawToOb11Converters {
return key in parser;
}
export class OneBotMsgApi {
obContext: NapCatOneBot11Adapter;
coreContext: NapCatCore;
rawToOb11Converters: RawToOb11Converters = {
textElement: async element => {
if (element.atType === AtType.notAt) {
let text = element.content;
if (!text.trim()) {
return null;
}
// 兼容 9.7.x 换行符
if (text.indexOf('\n') === -1 && text.indexOf('\r\n') === -1) {
text = text.replace(/\r/g, '\n');
}
return {
type: OB11MessageDataType.text,
data: { text }
};
} else {
let qq: string = 'all';
if (element.atType !== AtType.atAll) {
const { atNtUid, /* content */ } = element;
let atQQ = element.atUid;
if (!atQQ || atQQ === '0') {
atQQ = await this.coreContext.apis.UserApi.getUinByUidV2(atNtUid);
}
if (atQQ) {
qq = atQQ as `${number}`;
}
}
return {
type: OB11MessageDataType.at,
data: {
qq: qq,
// name: content.slice(1);
},
};
}
},
picElement: async (element, msg) => {
try {
return {
type: OB11MessageDataType.image,
data: {
file: element.fileName,
sub_type: element.picSubType,
file_id: UUIDConverter.encode(msg.peerUin, msg.msgId),
url: await this.coreContext.apis.FileApi.getImageUrl(element),
file_size: element.fileSize,
},
};
} catch (e: any) {
this.coreContext.context.logger.logError('获取图片url失败', e.stack);
return null;
}
},
fileElement: async (element, msg, elementWrapper) => {
await this.coreContext.apis.FileApi.addFileCache(
{
peerUid: msg.peerUid,
chatType: msg.chatType,
guildId: '',
},
msg.msgId,
msg.msgSeq,
msg.senderUid,
elementWrapper.elementId,
elementWrapper.elementType.toString(),
element.fileSize,
element.fileName,
);
return {
type: OB11MessageDataType.file,
data: {
file: element.fileName,
path: element.filePath,
url: element.filePath,
file_id: UUIDConverter.encode(msg.peerUin, msg.msgId),
file_size: element.fileSize,
}
};
},
faceElement: async element => {
const faceIndex = element.faceIndex;
if (faceIndex === FaceIndex.dice) {
return {
type: OB11MessageDataType.dice,
data: {
result: element.resultId!,
}
};
} else if (faceIndex === FaceIndex.RPS) {
return {
type: OB11MessageDataType.RPS,
data: {
result: element.resultId!,
}
};
} else {
return {
type: OB11MessageDataType.face,
data: {
id: element.faceIndex.toString()
}
};
}
},
marketFaceElement: async (_, msg, elementWrapper) => {
await this.coreContext.apis.FileApi.addFileCache(
{
peerUid: msg.peerUid,
chatType: msg.chatType,
guildId: '',
},
msg.msgId,
msg.msgSeq,
msg.senderUid,
elementWrapper.elementId,
elementWrapper.elementType.toString(),
'0',
'marketface',
);
return {
type: OB11MessageDataType.image,
data: {
file: 'marketface',
file_id: UUIDConverter.encode(msg.peerUin, msg.msgId),
path: elementWrapper.elementId,
url: elementWrapper.elementId,
}
};
},
replyElement: async (element, msg) => {
const NTQQMsgApi = this.coreContext.apis.MsgApi;
const records = msg.records.find(msgRecord => msgRecord.msgId === element?.sourceMsgIdInRecords);
const peer = {
chatType: msg.chatType,
peerUid: msg.peerUid,
guildId: '',
};
if (!records) {
this.coreContext.context.logger.logError('获取不到引用的消息', element.replayMsgSeq);
return null;
}
let replyMsg: RawMessage | undefined;
// Attempt 1
replyMsg = (await NTQQMsgApi.getMsgsBySeqAndCount({
peerUid: msg.peerUid,
guildId: '',
chatType: msg.chatType,
}, element.replayMsgSeq, 1, true, true))
.msgList
.find(msg => msg.msgRandom === records.msgRandom);
if (!replyMsg || records.msgRandom !== replyMsg.msgRandom) {
// Attempt 2
replyMsg = (await NTQQMsgApi.getSingleMsg(peer, element.replayMsgSeq)).msgList[0];
if ((!replyMsg || records.msgRandom !== replyMsg.msgRandom) && msg.peerUin !== '284840486') {
// Attempt 3
const replyMsgList = (await NTQQMsgApi.getMsgExBySeq(peer, records.msgSeq)).msgList;
if (replyMsgList.length < 1) {
this.coreContext.context.logger.logError('回复消息消息验证失败', element.replayMsgSeq);
return null;
}
replyMsg = replyMsgList.filter(e => e.msgSeq == records.msgSeq)
.sort((a, b) => parseInt(a.msgTime) - parseInt(b.msgTime))[0];
}
}
return {
type: OB11MessageDataType.reply,
data: {
id: MessageUnique.createMsg({
peerUid: msg.peerUid,
guildId: '',
chatType: msg.chatType,
}, replyMsg.msgId).toString()
}
};
},
videoElement: async (element, msg, elementWrapper) => {
const NTQQFileApi = this.coreContext.apis.FileApi;
//读取视频链接并兜底
let videoUrlWrappers: Awaited<ReturnType<typeof NTQQFileApi.getVideoUrl>> | undefined;
if (msg.peerUin === '284840486') {
//TODO: 合并消息内部 应该进行特殊处理 可能需要重写peer 待测试与研究 Mlikiowa Tagged
}
try {
videoUrlWrappers = await NTQQFileApi.getVideoUrl({
chatType: msg.chatType,
peerUid: msg.peerUid,
guildId: '0',
}, msg.msgId, elementWrapper.elementId);
} catch (error) {
this.coreContext.context.logger.logWarn('获取视频 URL 失败');
}
//读取在线URL
let videoDownUrl: string | undefined;
if (videoUrlWrappers) {
const videoDownUrlTemp = videoUrlWrappers.find((urlWrapper) => {
return !!(urlWrapper.url);
});
if (videoDownUrlTemp) {
videoDownUrl = videoDownUrlTemp.url;
}
}
//开始兜底
if (!videoDownUrl) {
videoDownUrl = element.filePath;
}
await NTQQFileApi.addFileCache(
{
peerUid: msg.peerUid,
chatType: msg.chatType,
guildId: '',
},
msg.msgId,
msg.msgSeq,
msg.senderUid,
elementWrapper.elementId,
elementWrapper.elementType.toString(),
element.fileSize ?? '0',
element.fileName,
);
return {
type: OB11MessageDataType.video,
data: {
file: element.fileName,
path: videoDownUrl,
url: videoDownUrl,
file_id: UUIDConverter.encode(msg.peerUin, msg.msgId),
file_size: element.fileSize,
}
};
},
pttElement: async (element, msg, elementWrapper) => {
await this.coreContext.apis.FileApi.addFileCache(
{
peerUid: msg.peerUid,
chatType: msg.chatType,
guildId: '',
},
msg.msgId,
msg.msgSeq,
msg.senderUid,
elementWrapper.elementId,
elementWrapper.elementType.toString(),
element.fileSize || '0',
element.fileUuid || '',
);
return {
type: OB11MessageDataType.voice,
data: {
file: element.fileName,
path: element.filePath,
file_id: UUIDConverter.encode(msg.peerUin, msg.msgId),
file_size: element.fileSize,
}
};
},
multiForwardMsgElement: async (_, msg) => {
const NTQQMsgApi = this.coreContext.apis.MsgApi;
const message_data: OB11MessageData = {
data: {} as any,
type: 'unknown' as any,
};
message_data['type'] = OB11MessageDataType.forward;
message_data['data']['id'] = msg.msgId;
const parentMsgPeer = msg.parentMsgPeer ?? {
chatType: msg.chatType,
guildId: '',
peerUid: msg.peerUid,
};
//判断是否在合并消息内
msg.parentMsgIdList = msg.parentMsgIdList ?? [];
//首次列表不存在则开始创建
msg.parentMsgIdList.push(msg.msgId);
//let parentMsgId = msg.parentMsgIdList[msg.parentMsgIdList.length - 2 < 0 ? 0 : msg.parentMsgIdList.length - 2];
//加入自身MsgId
const multiMsgs = (await NTQQMsgApi.getMultiMsg(parentMsgPeer, msg.parentMsgIdList[0], msg.msgId))?.msgList;
//拉取下级消息
if (!multiMsgs) return null;
//拉取失败则跳过
return {
type: OB11MessageDataType.forward,
data: {
id: msg.msgId,
content: (await Promise.all(multiMsgs.map(
async multiMsgItem => {
multiMsgItem.parentMsgPeer = parentMsgPeer;
multiMsgItem.parentMsgIdList = msg.parentMsgIdList;
multiMsgItem.id = MessageUnique.createMsg(parentMsgPeer, multiMsgItem.msgId); //该ID仅用查看 无法调用
return await this.parseMessage(multiMsgItem);
}
))).filter(item => item !== undefined),
}
};
},
arkElement: async (element) => {
return {
type: OB11MessageDataType.json,
data: {
data: element.bytesData
}
};
},
markdownElement: async (element) => {
return {
type: OB11MessageDataType.markdown,
data: {
content: element.content
}
};
}
};
constructor(obContext: NapCatOneBot11Adapter, coreContext: NapCatCore) {
this.obContext = obContext;
this.coreContext = coreContext;
}
async parseMessage(
msg: RawMessage,
messagePostFormat: string = this.obContext.configLoader.configData.messagePostFormat,
) {
if (msg.senderUin == '0' || msg.senderUin == '') return;
if (msg.peerUin == '0' || msg.peerUin == '') return;
//跳过空消息
const NTQQGroupApi = this.coreContext.apis.GroupApi;
const NTQQUserApi = this.coreContext.apis.UserApi;
const NTQQMsgApi = this.coreContext.apis.MsgApi;
const resMsg: OB11Message = {
self_id: parseInt(this.coreContext.selfInfo.uin),
user_id: parseInt(msg.senderUin!),
time: parseInt(msg.msgTime) || Date.now(),
message_id: msg.id!,
message_seq: msg.id!,
real_id: msg.id!,
message_type: msg.chatType == ChatType.KCHATTYPEGROUP ? 'group' : 'private',
sender: {
user_id: parseInt(msg.senderUin || '0'),
nickname: msg.sendNickName,
card: msg.sendMemberName || '',
},
raw_message: '',
font: 14,
sub_type: 'friend',
message: messagePostFormat === 'string' ? '' : [],
message_format: messagePostFormat === 'string' ? 'string' : 'array',
post_type: this.coreContext.selfInfo.uin == msg.senderUin ? EventType.MESSAGE_SENT : EventType.MESSAGE,
};
if (msg.chatType == ChatType.KCHATTYPEGROUP) {
resMsg.sub_type = 'normal'; // 这里go-cqhttp是group而onebot11标准是normal, 蛋疼
resMsg.group_id = parseInt(msg.peerUin);
let member = await NTQQGroupApi.getGroupMember(msg.peerUin, msg.senderUin);
if (!member) member = await NTQQGroupApi.getGroupMember(msg.peerUin, msg.senderUin);
if (member) {
resMsg.sender.role = OB11Constructor.groupMemberRole(member.role);
resMsg.sender.nickname = member.nick;
}
} else if (msg.chatType == ChatType.KCHATTYPEC2C) {
resMsg.sub_type = 'friend';
resMsg.sender.nickname = (await NTQQUserApi.getUserDetailInfo(msg.senderUid)).nick;
} else if (msg.chatType == ChatType.KCHATTYPETEMPC2CFROMGROUP) {
resMsg.sub_type = 'group';
const ret = await NTQQMsgApi.getTempChatInfo(ChatType.KCHATTYPETEMPC2CFROMGROUP, msg.senderUid);
if (ret.result === 0) {
resMsg.group_id = parseInt(ret.tmpChatInfo!.groupCode);
resMsg.sender.nickname = ret.tmpChatInfo!.fromNick;
} else {
resMsg.group_id = 284840486; //兜底数据
resMsg.sender.nickname = '临时会话';
}
}
const msgSegments = (await Promise.all(msg.elements.map(
async (element) => {
for (const key in element) {
if (keyCanBeParsed(key, this.rawToOb11Converters)) {
return await this.rawToOb11Converters[key]?.(
// eslint-disable-next-line
// @ts-ignore
element[key],
msg,
element
);
}
}
}
))).filter(entry => !!entry);
const msgAsCQCode = msgSegments.map(msg => encodeCQCode(msg)).join('').trim();
if (messagePostFormat === 'string') {
resMsg.message = msgAsCQCode;
} else {
resMsg.message = msgSegments;
resMsg.raw_message = msgAsCQCode;
}
return resMsg;
}
}