diff --git a/src/common/utils/qqlevel.ts b/src/common/utils/qqlevel.ts new file mode 100644 index 00000000..7e957ddb --- /dev/null +++ b/src/common/utils/qqlevel.ts @@ -0,0 +1,7 @@ +// QQ等级换算 +import { QQLevel } from '@/core/entities'; + +export function calcQQLevel(level: QQLevel) { + const { crownNum, sunNum, moonNum, starNum } = level; + return crownNum * 64 + sunNum * 16 + moonNum * 4 + starNum; +} diff --git a/src/onebot/action/extends/GetFriendWithCategory.ts b/src/onebot/action/extends/GetFriendWithCategory.ts index 9f3dae9c..5431fb1e 100644 --- a/src/onebot/action/extends/GetFriendWithCategory.ts +++ b/src/onebot/action/extends/GetFriendWithCategory.ts @@ -1,17 +1,15 @@ -import { requireMinNTQQBuild } from '@/common/utils/QQBasicInfo'; + import BaseAction from '../BaseAction'; import { ActionName } from '../types'; -import { BuddyCategoryType } from '@/core/entities/'; -import { NTQQFriendApi } from '@/core'; -import { OB11Constructor } from '@/onebot11/constructor'; +import { OB11Constructor } from '@/onebot/helper/constructor'; export class GetFriendWithCategory extends BaseAction { actionName = ActionName.GetFriendsWithCategory; protected async _handle(payload: void) { - if (requireMinNTQQBuild('26702')) { + if (this.CoreContext.context.basicInfoWrapper.requireMinNTQQBuild('26702')) { //全新逻辑 - return OB11Constructor.friendsV2(await NTQQFriendApi.getBuddyV2ExWithCate(true)); + return OB11Constructor.friendsV2(await this.CoreContext.getApiContext().FriendApi.getBuddyV2ExWithCate(true)); } else { throw new Error('this ntqq version not support, must be 26702 or later'); } diff --git a/src/onebot/action/extends/GetGroupAddRequest.ts b/src/onebot/action/extends/GetGroupAddRequest.ts index 1d6ebe08..f4b9391f 100644 --- a/src/onebot/action/extends/GetGroupAddRequest.ts +++ b/src/onebot/action/extends/GetGroupAddRequest.ts @@ -1,7 +1,5 @@ -import { GroupNotify, GroupNotifyStatus } from '@/core/entities'; import BaseAction from '../BaseAction'; import { ActionName } from '../types'; -import { NTQQUserApi } from '@/core/apis/user'; import { NTQQGroupApi } from '@/core/apis/group'; interface OB11GroupRequestNotify { @@ -14,7 +12,7 @@ export default class GetGroupAddRequest extends BaseAction { - const data = await NTQQGroupApi.getGroupIgnoreNotifies(); + const data = await this.CoreContext.getApiContext().GroupApi.getGroupIgnoreNotifies(); // log(data); // const notifies: GroupNotify[] = data.notifies.filter(notify => notify.status === GroupNotifyStatus.WAIT_HANDLE); // const returnData: OB11GroupRequestNotify[] = []; diff --git a/src/onebot/action/extends/GetProfileLike.ts b/src/onebot/action/extends/GetProfileLike.ts index 80690adc..aff7df11 100644 --- a/src/onebot/action/extends/GetProfileLike.ts +++ b/src/onebot/action/extends/GetProfileLike.ts @@ -1,12 +1,10 @@ -import { selfInfo } from '@/core/data'; import BaseAction from '../BaseAction'; import { ActionName } from '../types'; -import { NTQQUserApi } from '@/core/apis'; - export class GetProfileLike extends BaseAction { actionName = ActionName.GetProfileLike; protected async _handle(payload: void) { - const ret = await NTQQUserApi.getProfileLike(selfInfo.uid); + const NTQQUserApi = this.CoreContext.getApiContext().UserApi; + const ret = await NTQQUserApi.getProfileLike(this.CoreContext.selfInfo.uid); const listdata: any[] = ret.info.userLikeInfos[0].favoriteInfo.userInfos; for (let i = 0; i < listdata.length; i++) { listdata[i].uin = parseInt((await NTQQUserApi.getUinByUid(listdata[i].uid)) || ''); diff --git a/src/onebot/action/extends/GetRobotUinRange.ts b/src/onebot/action/extends/GetRobotUinRange.ts index d5b85aba..89398a59 100644 --- a/src/onebot/action/extends/GetRobotUinRange.ts +++ b/src/onebot/action/extends/GetRobotUinRange.ts @@ -6,6 +6,7 @@ export class GetRobotUinRange extends BaseAction> { protected async _handle(payload: void) { // console.log(await NTQQUserApi.getRobotUinRange()); + const NTQQUserApi = this.CoreContext.getApiContext().UserApi; return await NTQQUserApi.getRobotUinRange(); } } diff --git a/src/onebot/action/extends/SetGroupHeader.ts b/src/onebot/action/extends/SetGroupHeader.ts index 1998301b..4f1cfe9d 100644 --- a/src/onebot/action/extends/SetGroupHeader.ts +++ b/src/onebot/action/extends/SetGroupHeader.ts @@ -3,7 +3,6 @@ import { ActionName, BaseCheckResult } from '../types'; import * as fs from 'node:fs'; import { NTQQUserApi } from '@/core/apis/user'; import { checkFileReceived, uri2local } from '@/common/utils/file'; -import { NTQQGroupApi } from '@/core'; // import { log } from "../../../common/utils"; interface Payload { @@ -26,6 +25,7 @@ export default class SetGroupHeader extends BaseAction { }; } protected async _handle(payload: Payload): Promise { + const NTQQGroupApi = this.CoreContext.getApiContext().GroupApi; const { path, isLocal, errMsg,success } = (await uri2local(payload.file)); if (!success) { throw `头像${payload.file}设置失败,file字段可能格式不正确`; diff --git a/src/onebot/action/extends/SetLongNick.ts b/src/onebot/action/extends/SetLongNick.ts index 2f1f8c5d..e339671a 100644 --- a/src/onebot/action/extends/SetLongNick.ts +++ b/src/onebot/action/extends/SetLongNick.ts @@ -18,6 +18,7 @@ export class SetLongNick extends BaseAction { actionName = ActionName.SetLongNick; PayloadSchema = SchemaData; protected async _handle(payload: Payload) { + const NTQQUserApi = this.CoreContext.getApiContext().UserApi; const ret = await NTQQUserApi.setLongNick(payload.longNick); return ret; } diff --git a/src/onebot/action/extends/SetOnlineStatus.ts b/src/onebot/action/extends/SetOnlineStatus.ts index c06a2292..53a76e78 100644 --- a/src/onebot/action/extends/SetOnlineStatus.ts +++ b/src/onebot/action/extends/SetOnlineStatus.ts @@ -26,6 +26,7 @@ export class SetOnlineStatus extends BaseAction { // { status: 50, extStatus: 0, batteryStatus: 0 } // { status: 60, extStatus: 0, batteryStatus: 0 } // { status: 70, extStatus: 0, batteryStatus: 0 } + const NTQQUserApi = this.CoreContext.getApiContext().UserApi; const ret = await NTQQUserApi.setSelfOnlineStatus(payload.status, payload.extStatus, payload.batteryStatus); if (ret.result !== 0) { throw new Error('设置在线状态失败'); diff --git a/src/onebot/action/extends/SetQQAvatar.ts b/src/onebot/action/extends/SetQQAvatar.ts index f8006344..abcf9652 100644 --- a/src/onebot/action/extends/SetQQAvatar.ts +++ b/src/onebot/action/extends/SetQQAvatar.ts @@ -24,6 +24,7 @@ export default class SetAvatar extends BaseAction { }; } protected async _handle(payload: Payload): Promise { + const NTQQUserApi = this.CoreContext.getApiContext().UserApi; const { path, isLocal, errMsg,success } = (await uri2local(payload.file)); if (!success) { throw `头像${payload.file}设置失败,file字段可能格式不正确`; diff --git a/src/onebot/action/extends/SetSelfProfile.ts b/src/onebot/action/extends/SetSelfProfile.ts index 5c85e9eb..4a9ada91 100644 --- a/src/onebot/action/extends/SetSelfProfile.ts +++ b/src/onebot/action/extends/SetSelfProfile.ts @@ -20,6 +20,7 @@ export class SetSelfProfile extends BaseAction { actionName = ActionName.SetSelfProfile; PayloadSchema = SchemaData; protected async _handle(payload: Payload) { + const NTQQUserApi = this.CoreContext.getApiContext().UserApi; const ret = await NTQQUserApi.modifySelfProfile({ nick: payload.nick, longNick: payload.longNick, diff --git a/src/onebot/action/extends/TestApi01.ts b/src/onebot/action/extends/TestApi01.ts deleted file mode 100644 index 566d63d2..00000000 --- a/src/onebot/action/extends/TestApi01.ts +++ /dev/null @@ -1,28 +0,0 @@ -import BaseAction from '../BaseAction'; -import { ActionName, BaseCheckResult } from '../types'; -import { napCatCore, NTQQGroupApi } from '@/core'; -import { FromSchema, JSONSchema } from 'json-schema-to-ts'; - -const SchemaData = { - type: 'object', - properties: { - cmd: { type: 'string' }, - param: { type: 'string' } - }, - required: ['cmd', 'param'], -} as const satisfies JSONSchema; - -type Payload = FromSchema; - -export default class TestApi01 extends BaseAction { - actionName = ActionName.TestApi01; - // 用不着复杂检测 - protected async check(payload: Payload): Promise { - return { - valid: true, - }; - } - protected async _handle(payload: Payload): Promise { - return await napCatCore.session.getMsgService().sendSsoCmdReqByContend(payload.cmd, payload.param); - } -} diff --git a/src/onebot/action/extends/TranslateEnWordToZn.ts b/src/onebot/action/extends/TranslateEnWordToZn.ts index 8088ab6d..4c3a5f94 100644 --- a/src/onebot/action/extends/TranslateEnWordToZn.ts +++ b/src/onebot/action/extends/TranslateEnWordToZn.ts @@ -1,9 +1,7 @@ import BaseAction from '../BaseAction'; -import { ActionName, BaseCheckResult } from '../types'; -import { NTQQSystemApi, NTQQUserApi } from '@/core/apis'; +import { ActionName } from '../types'; +import { NTQQSystemApi } from '@/core/apis'; import { FromSchema, JSONSchema } from 'json-schema-to-ts'; -import Ajv from 'ajv'; -// 设置在线状态 const SchemaData = { type: 'object', diff --git a/src/onebot/action/extends/sharePeer.ts b/src/onebot/action/extends/sharePeer.ts index abb63bff..e98cddd1 100644 --- a/src/onebot/action/extends/sharePeer.ts +++ b/src/onebot/action/extends/sharePeer.ts @@ -1,7 +1,5 @@ -import { NTQQGroupApi, NTQQUserApi } from '@/core'; import BaseAction from '../BaseAction'; import { ActionName } from '../types'; -import { BuddyCategoryType } from '@/core/entities/'; import { FromSchema, JSONSchema } from 'json-schema-to-ts'; const SchemaData = { @@ -20,6 +18,8 @@ export class sharePeer extends BaseAction { actionName = ActionName.SharePeer; PayloadSchema = SchemaData; protected async _handle(payload: Payload) { + const NTQQUserApi = this.CoreContext.getApiContext().UserApi; + const NTQQGroupApi = this.CoreContext.getApiContext().GroupApi; if (payload.group_id) { return await NTQQGroupApi.getGroupRecommendContactArkJson(payload.group_id); } else if (payload.user_id) { @@ -40,6 +40,8 @@ export class shareGroupEx extends BaseAction { actionName = ActionName.ShareGroupEx; PayloadSchema = SchemaDataGroupEx; protected async _handle(payload: PayloadGroupEx) { + const NTQQUserApi = this.CoreContext.getApiContext().UserApi; + const NTQQGroupApi = this.CoreContext.getApiContext().GroupApi; return await NTQQGroupApi.getArkJsonGroupShare(payload.group_id); } } \ No newline at end of file diff --git a/src/onebot/action/file/DelGroupFile.ts b/src/onebot/action/file/DelGroupFile.ts index 498cfe13..a7016259 100644 --- a/src/onebot/action/file/DelGroupFile.ts +++ b/src/onebot/action/file/DelGroupFile.ts @@ -1,8 +1,6 @@ import { FromSchema, JSONSchema } from 'json-schema-to-ts'; import BaseAction from '../BaseAction'; import { ActionName } from '../types'; -import { NTQQGroupApi, NTQQMsgApi, NTQQUserApi } from '@/core/apis'; - const SchemaData = { type: 'object', properties: { @@ -18,6 +16,7 @@ export class DelGroupFile extends BaseAction { actionName = ActionName.DelGroupFile; PayloadSchema = SchemaData; protected async _handle(payload: Payload) { + const NTQQGroupApi = this.CoreContext.getApiContext().GroupApi; return await NTQQGroupApi.DelGroupFile(payload.group_id.toString(), [payload.file_id]); } } diff --git a/src/onebot/action/go-cqhttp/GetForwardMsg.ts b/src/onebot/action/go-cqhttp/GetForwardMsg.ts index 375aefd1..4c8a9fdc 100644 --- a/src/onebot/action/go-cqhttp/GetForwardMsg.ts +++ b/src/onebot/action/go-cqhttp/GetForwardMsg.ts @@ -1,7 +1,7 @@ import BaseAction from '../BaseAction'; import { OB11ForwardMessage, OB11Message, OB11MessageData } from '../../types'; import { NTQQMsgApi } from '@/core/apis'; -import { OB11Constructor } from '../../constructor'; +import { OB11Constructor } from '../../helper/constructor'; import { ActionName, BaseCheckResult } from '../types'; import { FromSchema, JSONSchema } from 'json-schema-to-ts'; import { MessageUnique } from '@/common/utils/MessageUnique'; @@ -24,6 +24,7 @@ export class GoCQHTTPGetForwardMsgAction extends BaseAction { actionName = ActionName.GoCQHTTP_GetForwardMsg; PayloadSchema = SchemaData; protected async _handle(payload: Payload): Promise { + const NTQQMsgApi = this.CoreContext.getApiContext().MsgApi; const msgId = payload.message_id || payload.id; if (!msgId) { throw Error('message_id is required'); diff --git a/src/onebot/action/go-cqhttp/GetFriendMsgHistory.ts b/src/onebot/action/go-cqhttp/GetFriendMsgHistory.ts index 3ca15764..ef98bf46 100644 --- a/src/onebot/action/go-cqhttp/GetFriendMsgHistory.ts +++ b/src/onebot/action/go-cqhttp/GetFriendMsgHistory.ts @@ -3,9 +3,8 @@ import { OB11Message, OB11User } from '../../types'; import { ActionName } from '../types'; import { ChatType, RawMessage } from '@/core/entities'; import { NTQQMsgApi } from '@/core/apis/msg'; -import { OB11Constructor } from '../../constructor'; +import { OB11Constructor } from '../../helper/constructor'; import { FromSchema, JSONSchema } from 'json-schema-to-ts'; -import { NTQQFriendApi, NTQQUserApi } from '@/core'; import { MessageUnique } from '@/common/utils/MessageUnique'; interface Response { @@ -29,6 +28,9 @@ export default class GetFriendMsgHistory extends BaseAction { actionName = ActionName.GetFriendMsgHistory; PayloadSchema = SchemaData; protected async _handle(payload: Payload): Promise { + const NTQQUserApi = this.CoreContext.getApiContext().UserApi; + const NTQQMsgApi = this.CoreContext.getApiContext().MsgApi; + const NTQQFriendApi = this.CoreContext.getApiContext().FriendApi; //处理参数 const uid = await NTQQUserApi.getUidByUin(payload.user_id.toString()); const MsgCount = payload.count || 20; diff --git a/src/onebot/action/go-cqhttp/GetGroupHonorInfo.ts b/src/onebot/action/go-cqhttp/GetGroupHonorInfo.ts index 2d84f476..bd60031f 100644 --- a/src/onebot/action/go-cqhttp/GetGroupHonorInfo.ts +++ b/src/onebot/action/go-cqhttp/GetGroupHonorInfo.ts @@ -1,7 +1,7 @@ import BaseAction from '../BaseAction'; import { ActionName } from '../types'; -import { WebApi, WebHonorType } from '@/core/apis'; +import { WebHonorType } from '@/core/entities'; import { FromSchema, JSONSchema } from 'json-schema-to-ts'; const SchemaData = { type: 'object', @@ -21,6 +21,7 @@ export class GetGroupHonorInfo extends BaseAction> { if (!payload.type) { payload.type = WebHonorType.ALL; } - return await WebApi.getGroupHonorInfo(payload.group_id.toString(), payload.type); + const NTQQWebApi = this.CoreContext.getApiContext().WebApi; + return await NTQQWebApi.getGroupHonorInfo(payload.group_id.toString(), payload.type); } } diff --git a/src/onebot/action/go-cqhttp/GetGroupMsgHistory.ts b/src/onebot/action/go-cqhttp/GetGroupMsgHistory.ts index de518621..bb7e9b7a 100644 --- a/src/onebot/action/go-cqhttp/GetGroupMsgHistory.ts +++ b/src/onebot/action/go-cqhttp/GetGroupMsgHistory.ts @@ -1,10 +1,9 @@ import BaseAction from '../BaseAction'; import { OB11Message, OB11User } from '../../types'; -import { getGroup, groups } from '@/core/data'; import { ActionName } from '../types'; import { ChatType, Peer, RawMessage } from '@/core/entities'; import { NTQQMsgApi } from '@/core/apis/msg'; -import { OB11Constructor } from '../../constructor'; +import { OB11Constructor } from '../../helper/constructor'; import { FromSchema, JSONSchema } from 'json-schema-to-ts'; import { MessageUnique } from '@/common/utils/MessageUnique'; interface Response { @@ -28,13 +27,11 @@ export default class GoCQHTTPGetGroupMsgHistory extends BaseAction { + const NTQQMsgApi = this.CoreContext.getApiContext().MsgApi; //处理参数 - const group = await getGroup(payload.group_id.toString()); const isReverseOrder = payload.reverseOrder || true; const MsgCount = payload.count || 20; const peer: Peer = { chatType: ChatType.group, peerUid: payload.group_id.toString() }; - if (!group) throw `群${payload.group_id}不存在`; - //拉取消息 let msgList: RawMessage[]; if (!payload.message_seq || payload.message_seq == 0) { diff --git a/src/onebot/action/go-cqhttp/GetStrangerInfo.ts b/src/onebot/action/go-cqhttp/GetStrangerInfo.ts index b7f1c223..3848fc97 100644 --- a/src/onebot/action/go-cqhttp/GetStrangerInfo.ts +++ b/src/onebot/action/go-cqhttp/GetStrangerInfo.ts @@ -1,12 +1,10 @@ import BaseAction from '../BaseAction'; import { OB11User, OB11UserSex } from '../../types'; -import { OB11Constructor } from '../../constructor'; +import { OB11Constructor } from '../../helper/constructor'; import { ActionName } from '../types'; import { NTQQUserApi } from '@/core/apis/user'; import { FromSchema, JSONSchema } from 'json-schema-to-ts'; import { calcQQLevel } from '@/common/utils/qqlevel'; -import { requireMinNTQQBuild } from '@/common/utils/QQBasicInfo'; - const SchemaData = { type: 'object', properties: { @@ -21,7 +19,7 @@ export default class GoCQHTTPGetStrangerInfo extends BaseAction { - if (!requireMinNTQQBuild('26702')) { + const NTQQUserApi = this.CoreContext.getApiContext().UserApi; const user_id = payload.user_id.toString(); const extendData = await NTQQUserApi.getUserDetailInfoByUin(user_id); const uid = (await NTQQUserApi.getUidByUin(user_id))!; @@ -41,26 +39,5 @@ export default class GoCQHTTPGetStrangerInfo extends BaseAction { + const { messagePostFormat } = ob11Config; + const message_type = msg.chatType == ChatType.group ? 'group' : 'private'; + const resMsg: OB11Message = { + self_id: parseInt(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.group ? '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: selfInfo.uin == msg.senderUin ? EventType.MESSAGE_SENT : EventType.MESSAGE, + }; + if (msg.chatType == ChatType.group) { + resMsg.sub_type = 'normal'; // 这里go-cqhttp是group,而onebot11标准是normal, 蛋疼 + resMsg.group_id = parseInt(msg.peerUin); + let member = await getGroupMember(msg.peerUin, msg.senderUin!); + if (!member) { + //直接去QQNative取 + const memberList = await NTQQGroupApi.getGroupMembers(msg.peerUin); + member = memberList.get(msg.senderUin!); + } + if (member) { + resMsg.sender.role = OB11Constructor.groupMemberRole(member.role); + resMsg.sender.nickname = member.nick; + } + } + else if (msg.chatType == ChatType.friend) { + resMsg.sub_type = 'friend'; + resMsg.sender.nickname = (await NTQQUserApi.getUserDetailInfo(msg.senderUid)).nick; + //const user = await NTQQUserApi.getUserDetailInfoByUin(msg.senderUin!); + //resMsg.sender.nickname = user.info.nick; + } + else if (msg.chatType == ChatType.temp) { + resMsg.sub_type = 'group'; + const tempGroupCode = tempGroupCodeMap[msg.peerUin]; + if (tempGroupCode) { + resMsg.group_id = parseInt(tempGroupCode); + } + } + for (const element of msg.elements) { + let message_data: OB11MessageData = { + data: {} as any, + type: 'unknown' as any + }; + if (element.textElement && element.textElement?.atType !== AtType.notAt) { + let qq: `${number}` | 'all'; + let name: string | undefined; + if (element.textElement.atType == AtType.atAll) { + qq = 'all'; + } + else { + const { atNtUid, content } = element.textElement; + let atQQ = element.textElement.atUid; + if (!atQQ || atQQ === '0') { + const atMember = await getGroupMember(msg.peerUin, atNtUid); + if (atMember) { + atQQ = atMember.uin; + } + } + if (atQQ) { + qq = atQQ as `${number}`; + name = content.replace('@', ''); + } + } + message_data = { + type: OB11MessageDataType.at, + data: { + qq: qq!, + name + } + }; + } + else if (element.textElement) { + message_data['type'] = OB11MessageDataType.text; + + let text = element.textElement.content; + if (!text.trim()) { + continue; + } + // 兼容 9.7.x 换行符 + if (text.indexOf('\n') === -1 && text.indexOf('\r\n') === -1) { + text = text.replace(/\r/g, '\n'); + } + message_data['data']['text'] = text; + } + else if (element.replyElement) { + message_data['type'] = OB11MessageDataType.reply; + //log("收到回复消息", element.replyElement); + try { + const records = msg.records.find(msgRecord => msgRecord.msgId === element.replyElement.sourceMsgIdInRecords); + const peer = { + chatType: msg.chatType, + peerUid: msg.peerUid, + guildId: '', + }; + let replyMsg: RawMessage | undefined; + if (!records) throw new Error('找不到回复消息'); + replyMsg = (await NTQQMsgApi.getMsgsBySeqAndCount({ peerUid: msg.peerUid, guildId: '', chatType: msg.chatType }, element.replyElement.replayMsgSeq, 1, true, true)).msgList[0]; + if (!replyMsg || records.msgRandom !== replyMsg.msgRandom) { + replyMsg = (await NTQQMsgApi.getSingleMsg(peer, element.replyElement.replayMsgSeq)).msgList[0]; + } + if (msg.peerUin == '284840486') { + //合并消息内侧 消息具体定位不到 + } + if ((!replyMsg || records.msgRandom !== replyMsg.msgRandom) && msg.peerUin !== '284840486') { + throw new Error('回复消息消息验证失败'); + } + message_data['data']['id'] = MessageUnique.createMsg({ peerUid: msg.peerUid, guildId: '', chatType: msg.chatType }, replyMsg.msgId)?.toString(); + //log("找到回复消息", message_data['data']['id'], replyMsg.msgList[0].msgId) + } catch (e: any) { + message_data['type'] = 'unknown' as any; + message_data['data'] = undefined; + logError('获取不到引用的消息', e.stack, element.replyElement.replayMsgSeq); + } + + } + else if (element.picElement) { + message_data['type'] = OB11MessageDataType.image; + // message_data["data"]["file"] = element.picElement.sourcePath + message_data['data']['file'] = element.picElement.fileName; + message_data['data']['subType'] = element.picElement.picSubType; + message_data['data']['file_id'] = UUIDConverter.encode(msg.peerUin, msg.msgId); + // message_data["data"]["path"] = element.picElement.sourcePath + + try { + message_data['data']['url'] = await NTQQFileApi.getImageUrl(element.picElement); + } catch (e: any) { + logError('获取图片url失败', e.stack); + } + //console.log(message_data['data']['url']) + // message_data["data"]["file_id"] = element.picElement.fileUuid + message_data['data']['file_size'] = element.picElement.fileSize; + } + else if (element.fileElement) { + const FileElement = element.fileElement; + message_data['type'] = OB11MessageDataType.file; + message_data['data']['file'] = FileElement.fileName; + message_data['data']['path'] = FileElement.filePath; + message_data['data']['url'] = FileElement.filePath; + message_data['data']['file_id'] = UUIDConverter.encode(msg.peerUin, msg.msgId); + message_data['data']['file_size'] = FileElement.fileSize; + await NTQQFileApi.addFileCache({ + peerUid: msg.peerUid, + chatType: msg.chatType, + guildId: '', + }, + msg.msgId, + msg.msgSeq, + msg.senderUid, + element.elementId, + element.elementType.toString(), + FileElement.fileSize, + FileElement.fileName + ); + } + else if (element.videoElement) { + const videoElement: VideoElement = element.videoElement; + //读取视频链接并兜底 + let videoUrl;//Array + if (msg.peerUin = '284840486') { + //合并消息内部 应该进行特殊处理 可能需要重写peer 待测试与研究 Mlikiowa Taged TODO + } + try { + + videoUrl = await NTQQFileApi.getVideoUrl({ + chatType: msg.chatType, + peerUid: msg.peerUid, + guildId: '0' + }, msg.msgId, element.elementId); + } catch (error) { + videoUrl = undefined; + } + //读取在线URL + let videoDownUrl = undefined; + + if (videoUrl) { + const videoDownUrlTemp = videoUrl.find((url) => { if (url.url) { return true; } return false; }); + if (videoDownUrlTemp) { + videoDownUrl = videoDownUrlTemp.url; + } + } + //开始兜底 + if (!videoDownUrl) { + videoDownUrl = videoElement.filePath; + } + message_data['type'] = OB11MessageDataType.video; + message_data['data']['file'] = videoElement.fileName; + message_data['data']['path'] = videoDownUrl; + message_data['data']['url'] = videoDownUrl; + message_data['data']['file_id'] = UUIDConverter.encode(msg.peerUin, msg.msgId); + message_data['data']['file_size'] = videoElement.fileSize; + + await NTQQFileApi.addFileCache({ + peerUid: msg.peerUid, + chatType: msg.chatType, + guildId: '', + }, + msg.msgId, + msg.msgSeq, + msg.senderUid, + element.elementId, + element.elementType.toString(), + videoElement.fileSize || '0', + videoElement.fileName + ); + } + else if (element.pttElement) { + message_data['type'] = OB11MessageDataType.voice; + message_data['data']['file'] = element.pttElement.fileName; + message_data['data']['path'] = element.pttElement.filePath; + //message_data['data']['file_id'] = element.pttElement.fileUuid; + message_data['data']['file_id'] = UUIDConverter.encode(msg.peerUin, msg.msgId); + message_data['data']['file_size'] = element.pttElement.fileSize; + await NTQQFileApi.addFileCache({ + peerUid: msg.peerUid, + chatType: msg.chatType, + guildId: '', + }, + msg.msgId, + msg.msgSeq, + msg.senderUid, + element.elementId, + element.elementType.toString(), + element.pttElement.fileSize || '0', + element.pttElement.fileUuid || '' + ); + //以uuid作为文件名 + } + else if (element.arkElement) { + message_data['type'] = OB11MessageDataType.json; + message_data['data']['data'] = element.arkElement.bytesData; + } + else if (element.faceElement) { + const faceId = element.faceElement.faceIndex; + if (faceId === FaceIndex.dice) { + message_data['type'] = OB11MessageDataType.dice; + message_data['data']['result'] = element.faceElement.resultId; + } + else if (faceId === FaceIndex.RPS) { + message_data['type'] = OB11MessageDataType.RPS; + message_data['data']['result'] = element.faceElement.resultId; + } + else { + message_data['type'] = OB11MessageDataType.face; + message_data['data']['id'] = element.faceElement.faceIndex.toString(); + } + } + else if (element.marketFaceElement) { + message_data['type'] = OB11MessageDataType.mface; + message_data['data']['summary'] = element.marketFaceElement.faceName; + const md5 = element.marketFaceElement.emojiId; + // 取md5的前两位 + const dir = md5.substring(0, 2); + // 获取组装url + // const url = `https://p.qpic.cn/CDN_STATIC/0/data/imgcache/htdocs/club/item/parcel/item/${dir}/${md5}/300x300.gif?max_age=31536000`; + const url = `https://gxh.vip.qq.com/club/item/parcel/item/${dir}/${md5}/raw300.gif`; + message_data['data']['url'] = url; + message_data['data']['emoji_id'] = element.marketFaceElement.emojiId; + message_data['data']['emoji_package_id'] = String(element.marketFaceElement.emojiPackageId); + message_data['data']['key'] = element.marketFaceElement.key; + mFaceCache.set(md5, element.marketFaceElement.faceName); + } + else if (element.markdownElement) { + message_data['type'] = OB11MessageDataType.markdown; + message_data['data']['data'] = element.markdownElement.content; + } + else if (element.multiForwardMsgElement) { + 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 + let MultiMsgs = (await NTQQMsgApi.getMultiMsg(ParentMsgPeer, msg.parentMsgIdList[0], msg.msgId))?.msgList; + //拉取下级消息 + if (!MultiMsgs) continue; + //拉取失败则跳过 + message_data['data']['content'] = []; + for (let MultiMsg of MultiMsgs) { + //对每条拉取的消息传递ParentMsgPeer修正Peer + MultiMsg.parentMsgPeer = ParentMsgPeer; + MultiMsg.parentMsgIdList = msg.parentMsgIdList; + MultiMsg.id = MessageUnique.createMsg(ParentMsgPeer, MultiMsg.msgId);//该ID仅用查看 无法调用 + let msgList = await OB11Constructor.message(MultiMsg); + message_data['data']['content'].push(msgList); + //console.log("合并消息", msgList); + } + } + if ((message_data.type as string) !== 'unknown' && message_data.data) { + const cqCode = encodeCQCode(message_data); + + if (messagePostFormat === 'string') { + (resMsg.message as string) += cqCode; + } + else (resMsg.message as OB11MessageData[]).push(message_data); + resMsg.raw_message += cqCode; + } + + } + resMsg.raw_message = resMsg.raw_message.trim(); + return resMsg; + } + static async PrivateEvent(msg: RawMessage): Promise { + if (msg.chatType !== ChatType.friend) { + return; + } + for (const element of msg.elements) { + if (element.grayTipElement) { + if (element.grayTipElement.subElementType == GrayTipElementSubType.MEMBER_NEW_TITLE) { + const json = JSON.parse(element.grayTipElement.jsonGrayTipElement.jsonStr); + + if (element.grayTipElement.jsonGrayTipElement.busiId == 1061) { + //判断业务类型 + //Poke事件 + let pokedetail: any[] = json.items; + //筛选item带有uid的元素 + pokedetail = pokedetail.filter(item => item.uid); + //console.log("[NapCat] 群拍一拍 群:", pokedetail, parseInt(msg.peerUid), " ", await NTQQUserApi.getUinByUid(pokedetail[0].uid), "拍了拍", await NTQQUserApi.getUinByUid(pokedetail[1].uid)); + if (pokedetail.length == 2) { + return new OB11FriendPokeEvent(parseInt((await NTQQUserApi.getUinByUid(pokedetail[0].uid))!), parseInt((await NTQQUserApi.getUinByUid(pokedetail[1].uid))!), pokedetail); + } + } + //下面得改 上面也是错的grayTipElement.subElementType == GrayTipElementSubType.MEMBER_NEW_TITLE + } + if (element.grayTipElement.subElementType == GrayTipElementSubType.INVITE_NEW_MEMBER) { + //好友添加成功事件 + if (element.grayTipElement.xmlElement.templId === '10229' && msg.peerUin !== '') { + return new OB11FriendAddNoticeEvent(parseInt(msg.peerUin)); + } + } + } + } + } + static async GroupEvent(msg: RawMessage): Promise { + if (msg.chatType !== ChatType.group) { + return; + } + //log("group msg", msg); + if (msg.senderUin && msg.senderUin !== '0') { + const member = await getGroupMember(msg.peerUid, msg.senderUin); + if (member && member.cardName !== msg.sendMemberName) { + const newCardName = msg.sendMemberName || ''; + const event = new OB11GroupCardEvent(parseInt(msg.peerUid), parseInt(msg.senderUin), newCardName, member.cardName); + member.cardName = newCardName; + return event; + } + } + + for (const element of msg.elements) { + const grayTipElement = element.grayTipElement; + const groupElement = grayTipElement?.groupElement; + if (groupElement) { + // log("收到群提示消息", groupElement) + if (groupElement.type == TipGroupElementType.memberIncrease) { + logDebug('收到群成员增加消息', groupElement); + await sleep(1000); + const member = await getGroupMember(msg.peerUid, groupElement.memberUid); + const memberUin = member?.uin; + // if (!memberUin) { + // memberUin = (await NTQQUserApi.getUserDetailInfo(groupElement.memberUid)).uin + // } + // log("获取新群成员QQ", memberUin) + const adminMember = await getGroupMember(msg.peerUid, groupElement.adminUid); + // log("获取同意新成员入群的管理员", adminMember) + if (memberUin) { + const operatorUin = adminMember?.uin || memberUin; + const event = new OB11GroupIncreaseEvent(parseInt(msg.peerUid), parseInt(memberUin), parseInt(operatorUin)); + // log("构造群增加事件", event) + return event; + } + } + else if (groupElement.type === TipGroupElementType.ban) { + logDebug('收到群群员禁言提示', groupElement); + const memberUid = groupElement.shutUp!.member.uid; + const adminUid = groupElement.shutUp!.admin.uid; + let memberUin: string = ''; + let duration = parseInt(groupElement.shutUp!.duration); + const sub_type: 'ban' | 'lift_ban' = duration > 0 ? 'ban' : 'lift_ban'; + // log('OB11被禁言事件', adminUid); + if (memberUid) { + memberUin = (await getGroupMember(msg.peerUid, memberUid))?.uin || ''; // || (await NTQQUserApi.getUserDetailInfo(memberUid))?.uin + } + else { + memberUin = '0'; // 0表示全员禁言 + if (duration > 0) { + duration = -1; + } + } + const adminUin = (await getGroupMember(msg.peerUid, adminUid))?.uin; // || (await NTQQUserApi.getUserDetailInfo(adminUid))?.uin + // log('OB11被禁言事件', memberUin, adminUin, duration, sub_type); + if (memberUin && adminUin) { + const event = new OB11GroupBanEvent(parseInt(msg.peerUid), parseInt(memberUin), parseInt(adminUin), duration, sub_type); + return event; + } + } + else if (groupElement.type == TipGroupElementType.kicked) { + logDebug(`收到我被踢出或退群提示, 群${msg.peerUid}`, groupElement); + deleteGroup(msg.peerUid); + NTQQGroupApi.quitGroup(msg.peerUid).then(); + try { + const adminUin = (await getGroupMember(msg.peerUid, groupElement.adminUid))?.uin || (await NTQQUserApi.getUidByUin(groupElement.adminUid)); + if (adminUin) { + return new OB11GroupDecreaseEvent(parseInt(msg.peerUid), parseInt(selfInfo.uin), parseInt(adminUin), 'kick_me'); + } + } catch (e) { + return new OB11GroupDecreaseEvent(parseInt(msg.peerUid), parseInt(selfInfo.uin), 0, 'leave'); + } + } + } + else if (element.fileElement) { + return new OB11GroupUploadNoticeEvent(parseInt(msg.peerUid), parseInt(msg.senderUin || ''), { + id: element.fileElement.fileUuid!, + name: element.fileElement.fileName, + size: parseInt(element.fileElement.fileSize), + busid: element.fileElement.fileBizId || 0 + }); + } + if (grayTipElement) { + //console.log('收到群提示消息', grayTipElement); + if (grayTipElement.xmlElement?.templId === '10382') { + const emojiLikeData = new fastXmlParser.XMLParser({ + ignoreAttributes: false, + attributeNamePrefix: '' + }).parse(grayTipElement.xmlElement.content); + logDebug('收到表情回应我的消息', emojiLikeData); + try { + const senderUin = emojiLikeData.gtip.qq.jp; + const msgSeq = emojiLikeData.gtip.url.msgseq; + const emojiId = emojiLikeData.gtip.face.id; + + const replyMsgList = (await NTQQMsgApi.getMsgsBySeqAndCount({ chatType: ChatType.group, guildId: '', peerUid: msg.peerUid }, msgSeq, 1, true, true)).msgList; + if (replyMsgList.length < 1) { + return; + } + console.log('表情回应消息', msgSeq, " 结算ID", replyMsgList[0].msgId); + const replyMsg = replyMsgList[0]; + return new OB11GroupMsgEmojiLikeEvent(parseInt(msg.peerUid), parseInt(senderUin), MessageUnique.getShortIdByMsgId(replyMsg?.msgId!)!, [{ + emoji_id: emojiId, + count: 1 + }]); + } catch (e: any) { + logError('解析表情回应消息失败', e.stack); + } + } + if (grayTipElement.subElementType == GrayTipElementSubType.INVITE_NEW_MEMBER) { + logDebug('收到新人被邀请进群消息', grayTipElement); + const xmlElement = grayTipElement.xmlElement; + if (xmlElement?.content) { + const regex = /jp="(\d+)"/g; + + const matches = []; + let match = null; + + while ((match = regex.exec(xmlElement.content)) !== null) { + matches.push(match[1]); + } + // log("新人进群匹配到的QQ号", matches) + if (matches.length === 2) { + const [inviter, invitee] = matches; + return new OB11GroupIncreaseEvent(parseInt(msg.peerUid), parseInt(invitee), parseInt(inviter), 'invite'); + } + } + } + //代码歧义 GrayTipElementSubType.MEMBER_NEW_TITLE + else if (grayTipElement.subElementType == GrayTipElementSubType.MEMBER_NEW_TITLE) { + const json = JSON.parse(grayTipElement.jsonGrayTipElement.jsonStr); + if (grayTipElement.jsonGrayTipElement.busiId == 1061) { + //判断业务类型 + //Poke事件 + const pokedetail: any[] = json.items; + //筛选item带有uid的元素 + const poke_uid = pokedetail.filter(item => item.uid); + if (poke_uid.length == 2) { + return new OB11GroupPokeEvent(parseInt(msg.peerUid), parseInt((await NTQQUserApi.getUinByUid(poke_uid[0].uid))!), parseInt((await NTQQUserApi.getUinByUid(poke_uid[1].uid))!), pokedetail); + } + } + if (grayTipElement.jsonGrayTipElement.busiId == 2401) { + const searchParams = new URL(json.items[0].jp).searchParams; + const msgSeq = searchParams.get('msgSeq')!; + const Group = searchParams.get('groupCode'); + const Businessid = searchParams.get('businessid'); + const Peer: Peer = { + guildId: '', + chatType: ChatType.group, + peerUid: Group! + }; + const msgData = await NTQQMsgApi.getMsgsBySeqAndCount(Peer, msgSeq.toString(), 1, true, true); + return new OB11GroupEssenceEvent(parseInt(msg.peerUid), MessageUnique.getShortIdByMsgId(msgData.msgList[0].msgId)!, parseInt(msgData.msgList[0].senderUin)); + // 获取MsgSeq+Peer可获取具体消息 + } + if (grayTipElement.jsonGrayTipElement.busiId == 2407) { + //下面得改 上面也是错的grayTipElement.subElementType == GrayTipElementSubType.MEMBER_NEW_TITLE + const memberUin = json.items[1].param[0]; + const title = json.items[3].txt; + logDebug('收到群成员新头衔消息', json); + return new OB11GroupTitleEvent(parseInt(msg.peerUid), parseInt(memberUin), title); + } + } + } + } + } + static friend(friend: User): OB11User { + return { + user_id: parseInt(friend.uin), + nickname: friend.nick, + remark: friend.remark, + sex: OB11Constructor.sex(friend.sex!), + level: friend.qqLevel && calcQQLevel(friend.qqLevel) || 0 + }; + } + + static selfInfo(selfInfo: SelfInfo): OB11User { + return { + user_id: parseInt(selfInfo.uin), + nickname: selfInfo.nick, + }; + } + static friendsV2(friends: FriendV2[]): OB11User[] { + const data: OB11User[] = []; + friends.forEach(friend => { + const sexValue = this.sex(friend.baseInfo.sex!); + data.push({ + ...friend.baseInfo, + ...friend.coreInfo, + user_id: parseInt(friend.coreInfo.uin), + nickname: friend.coreInfo.nick, + remark: friend.coreInfo.nick, + sex: sexValue, + level: 0, + categroyName: friend.categroyName, + categoryId: friend.categoryId + }); + }); + return data; + } + static friends(friends: Friend[]): OB11User[] { + const data: OB11User[] = []; + friends.forEach(friend => { + const sexValue = this.sex(friend.sex!); + data.push({ user_id: parseInt(friend.uin), nickname: friend.nick, remark: friend.remark, sex: sexValue, level: 0 }); + }); + return data; + } + + static groupMemberRole(role: number): OB11GroupMemberRole | undefined { + return { + 4: OB11GroupMemberRole.owner, + 3: OB11GroupMemberRole.admin, + 2: OB11GroupMemberRole.member + }[role]; + } + + static sex(sex: Sex): OB11UserSex { + const sexMap = { + [Sex.male]: OB11UserSex.male, + [Sex.female]: OB11UserSex.female, + [Sex.unknown]: OB11UserSex.unknown + }; + return sexMap[sex] || OB11UserSex.unknown; + } + + static groupMember(group_id: string, member: GroupMember): OB11GroupMember { + return { + group_id: parseInt(group_id), + user_id: parseInt(member.uin), + nickname: member.nick, + card: member.cardName, + sex: OB11Constructor.sex(member.sex!), + age: 0, + area: '', + level: '0', + qq_level: member.qqLevel && calcQQLevel(member.qqLevel) || 0, + join_time: 0, // 暂时没法获取 + last_sent_time: 0, // 暂时没法获取 + title_expire_time: 0, + unfriendly: false, + card_changeable: true, + is_robot: member.isRobot, + shut_up_timestamp: member.shutUpTime, + role: OB11Constructor.groupMemberRole(member.role), + title: member.memberSpecialTitle || '', + }; + } + + static stranger(user: User): OB11User { + //logDebug('construct ob11 stranger', user); + return { + ...user, + user_id: parseInt(user.uin), + nickname: user.nick, + sex: OB11Constructor.sex(user.sex!), + age: 0, + qid: user.qid, + login_days: 0, + level: user.qqLevel && calcQQLevel(user.qqLevel) || 0, + }; + } + + static groupMembers(group: Group): OB11GroupMember[] { + //logDebug('construct ob11 group members', group); + return Array.from(groupMembers.get(group.groupCode)?.values() || []).map(m => OB11Constructor.groupMember(group.groupCode, m)); + } + + static group(group: Group): OB11Group { + return { + group_id: parseInt(group.groupCode), + group_name: group.groupName, + member_count: group.memberCount, + max_member_count: group.maxMember + }; + } + + static groups(groups: Group[]): OB11Group[] { + return groups.map(OB11Constructor.group); + } +} diff --git a/src/onebot/helper/cqcode.ts b/src/onebot/helper/cqcode.ts new file mode 100644 index 00000000..5c20dcc3 --- /dev/null +++ b/src/onebot/helper/cqcode.ts @@ -0,0 +1,86 @@ +import { OB11MessageData } from '../types'; + +const pattern = /\[CQ:(\w+)((,\w+=[^,\]]*)*)\]/; + +function unescape(source: string) { + return String(source) + .replace(/[/g, '[') + .replace(/]/g, ']') + .replace(/,/g, ',') + .replace(/&/g, '&'); +} + +function from(source: string) { + const capture = pattern.exec(source); + if (!capture) return null; + const [, type, attrs] = capture; + const data: Record = {}; + attrs && attrs.slice(1).split(',').forEach((str) => { + const index = str.indexOf('='); + data[str.slice(0, index)] = unescape(str.slice(index + 1)); + }); + return { type, data, capture }; +} + +function h(type: string, data: any) { + return { + type, + data, + }; +} + +export function decodeCQCode(source: string): OB11MessageData[] { + const elements: any[] = []; + let result: ReturnType; + while ((result = from(source))) { + const { type, data, capture } = result; + if (capture.index) { + elements.push(h('text', { text: unescape(source.slice(0, capture.index)) })); + } + elements.push(h(type, data)); + source = source.slice(capture.index + capture[0].length); + } + if (source) elements.push(h('text', { text: unescape(source) })); + return elements; +} + + +export function encodeCQCode(data: OB11MessageData) { + const CQCodeEscapeText = (text: string) => { + return text.replace(/&/g, '&') + .replace(/\[/g, '[') + .replace(/\]/g, ']'); + + }; + + const CQCodeEscape = (text: string) => { + return text.replace(/&/g, '&') + .replace(/\[/g, '[') + .replace(/\]/g, ']') + .replace(/,/g, ','); + }; + + if (data.type === 'text') { + return CQCodeEscapeText(data.data.text); + } + + let result = '[CQ:' + data.type; + for (const name in data.data) { + const value = data.data[name]; + if (value === undefined) { + continue; + } + try { + const text = value.toString(); + result += `,${name}=${CQCodeEscape(text)}`; + } catch (error) { + // If it can't be converted, skip this name-value pair + } + } + result += ']'; + return result; +} + +// const result = parseCQCode("[CQ:at,qq=114514]早上好啊[CQ:image,file=http://baidu.com/1.jpg,type=show,id=40004]") +// const result = parseCQCode("好好好") +// console.log(JSON.stringify(result))