import { ChatType, FileElement, GrayTipElement, InstanceContext, JsonGrayBusiId, MessageElement, NapCatCore, NTGrayTipElementSubTypeV2, RawMessage, TipGroupElement, TipGroupElementType, } from 'napcat-core'; import { OB11GroupBanEvent } from '@/napcat-onebot/event/notice/OB11GroupBanEvent'; import fastXmlParser from 'fast-xml-parser'; import { OB11GroupMsgEmojiLikeEvent } from '@/napcat-onebot/event/notice/OB11MsgEmojiLikeEvent'; import { MessageUnique } from 'napcat-common/src/message-unique'; import { OB11GroupCardEvent } from '@/napcat-onebot/event/notice/OB11GroupCardEvent'; import { OB11GroupPokeEvent } from '@/napcat-onebot/event/notice/OB11PokeEvent'; import { OB11GroupEssenceEvent } from '@/napcat-onebot/event/notice/OB11GroupEssenceEvent'; import { OB11GroupTitleEvent } from '@/napcat-onebot/event/notice/OB11GroupTitleEvent'; import { OB11GroupUploadNoticeEvent } from '../event/notice/OB11GroupUploadNoticeEvent'; import { OB11GroupNameEvent } from '../event/notice/OB11GroupNameEvent'; import { FileNapCatOneBotUUID } from 'napcat-common/src/file-uuid'; import { OB11GroupIncreaseEvent } from '../event/notice/OB11GroupIncreaseEvent'; import { NapProtoMsg } from 'napcat-protobuf'; import { GroupReactNotify, PushMsg } from 'napcat-core/packet/transformer/proto'; import { NapCatOneBot11Adapter } from '..'; export class OneBotGroupApi { obContext: NapCatOneBot11Adapter; core: NapCatCore; constructor (obContext: NapCatOneBot11Adapter, core: NapCatCore) { this.obContext = obContext; this.core = core; } async parseGroupBanEvent (GroupCode: string, grayTipElement: GrayTipElement) { const groupElement = grayTipElement?.groupElement; if (!groupElement?.shutUp) return undefined; const memberUid = groupElement.shutUp.member.uid; const adminUid = groupElement.shutUp.admin.uid; let memberUin: string; let duration = parseInt(groupElement.shutUp.duration); const subType: 'ban' | 'lift_ban' = duration > 0 ? 'ban' : 'lift_ban'; if (memberUid) { memberUin = (await this.core.apis.GroupApi.getGroupMember(GroupCode, memberUid))?.uin ?? ''; } else { memberUin = '0'; // 0表示全员禁言 if (duration > 0) { duration = -1; } } await this.core.apis.GroupApi.refreshGroupMemberCachePartial(GroupCode, memberUid); const adminUin = (await this.core.apis.GroupApi.getGroupMember(GroupCode, adminUid))?.uin; if (memberUin && adminUin) { return new OB11GroupBanEvent( this.core, parseInt(GroupCode), parseInt(memberUin), parseInt(adminUin), duration, subType ); } return undefined; } async parseGroupEmojiLikeEventByGrayTip ( groupCode: string, grayTipElement: GrayTipElement ) { if (this.core.apis.PacketApi.packetStatus === true) { return; // Raw包解析支持时禁用NT解析 } const emojiLikeData = new fastXmlParser.XMLParser({ ignoreAttributes: false, attributeNamePrefix: '', }).parse(grayTipElement.xmlElement.content); this.core.context.logger.logDebug('收到表情回应我的消息', emojiLikeData); const senderUin = emojiLikeData.gtip.qq.jp; const msgSeq = emojiLikeData.gtip.url.msgseq; const emojiId = emojiLikeData.gtip.face.id; return await this.createGroupEmojiLikeEvent(groupCode, senderUin, msgSeq, emojiId, true, 1); } async createGroupEmojiLikeEvent ( groupCode: string, senderUin: string, msgSeq: string, emojiId: string, isAdd: boolean = true, count: number = 1 ) { const peer = { chatType: ChatType.KCHATTYPEGROUP, guildId: '', peerUid: groupCode, }; const replyMsgList = (await this.core.apis.MsgApi.queryFirstMsgBySeq(peer, msgSeq)).msgList; if (replyMsgList.length < 1) { return; } const replyMsg = replyMsgList[0]; if (!replyMsg) { this.core.context.logger.logError('解析表情回应消息失败: 未找到回应消息'); return undefined; } return new OB11GroupMsgEmojiLikeEvent( this.core, parseInt(groupCode), parseInt(senderUin), MessageUnique.createUniqueMsgId({ chatType: ChatType.KCHATTYPEGROUP, guildId: '', peerUid: groupCode }, replyMsg.msgId), [{ emoji_id: emojiId, count, }], isAdd, msgSeq ); } async parseCardChangedEvent (msg: RawMessage) { if (msg.senderUin && msg.senderUin !== '0') { const member = await this.core.apis.GroupApi.getGroupMember(msg.peerUid, msg.senderUin); if (member && member.cardName !== msg.sendMemberName) { const newCardName = msg.sendMemberName ?? ''; const event = new OB11GroupCardEvent(this.core, parseInt(msg.peerUid), parseInt(msg.senderUin), newCardName, member.cardName); member.cardName = newCardName; return event; } if (member && member.nick !== msg.sendNickName) { await this.core.apis.GroupApi.refreshGroupMemberCachePartial(msg.peerUid, msg.senderUid); } } return undefined; } async registerParseGroupReactEvent () { this.obContext.core.context.packetHandler.onCmd('trpc.msg.olpush.OlPushService.MsgPush', async (packet) => { const data = new NapProtoMsg(PushMsg).decode(Buffer.from(packet.hex_data, 'hex')); if (data.message.contentHead.type === 732 && data.message.contentHead.subType === 16) { const pbNotify = data.message.body?.msgContent?.slice(7); if (!pbNotify) { return; } // 开始解析Notify const notify = new NapProtoMsg(GroupReactNotify).decode(pbNotify); if ((notify.field13 ?? 0) === 35) { // Group React Notify const groupCode = notify.groupUin?.toString() ?? ''; const operatorUid = notify.groupReactionData?.data?.data?.groupReactionDataContent?.operatorUid ?? ''; const type = notify.groupReactionData?.data?.data?.groupReactionDataContent?.type ?? 0; const seq = notify.groupReactionData?.data?.data?.groupReactionTarget?.seq?.toString() ?? ''; const code = notify.groupReactionData?.data?.data?.groupReactionDataContent?.code ?? ''; // const count = notify.groupReactionData?.data?.data?.groupReactionDataContent?.count ?? 0; const senderUin = await this.core.apis.UserApi.getUinByUidV2(operatorUid); const event = await this.createGroupEmojiLikeEvent( groupCode, senderUin, seq, code, type === 1, 1 ); if (event) { this.obContext.networkManager.emitEvent(event); } } } }); } async registerParseGroupReactEventByCore () { this.core.event.on('event:emoji_like', async (data) => { console.log('Received emoji_like event from core:', data); const event = await this.createGroupEmojiLikeEvent( data.groupId, data.senderUin, data.msgSeq, data.emojiId, data.isAdd, data.count ); if (event) { this.obContext.networkManager.emitEvent(event); } }); } async parsePaiYiPai (msg: RawMessage, jsonStr: string) { const json = JSON.parse(jsonStr); // 判断业务类型 // Poke事件 const pokedetail: Array<{ uid: string; }> = json.items; // 筛选item带有uid的元素 const poke_uid = pokedetail.filter(item => item.uid); if (poke_uid.length === 2 && poke_uid[0]?.uid && poke_uid[1]?.uid) { return new OB11GroupPokeEvent( this.core, parseInt(msg.peerUid), +await this.core.apis.UserApi.getUinByUidV2(poke_uid[0].uid), +await this.core.apis.UserApi.getUinByUidV2(poke_uid[1].uid), pokedetail ); } return undefined; } async parseOtherJsonEvent (msg: RawMessage, jsonStr: string, context: InstanceContext) { const json = JSON.parse(jsonStr); const type = json.items[json.items.length - 1]?.txt; await this.core.apis.GroupApi.refreshGroupMemberCachePartial(msg.peerUid, msg.senderUid); if (type === '头衔') { const memberUin = json.items[1].param[0]; const title = json.items[3].txt; context.logger.logDebug('收到群成员新头衔消息', json); return new OB11GroupTitleEvent( this.core, +msg.peerUid, +memberUin, title ); } else if (type === '移出') { context.logger.logDebug('收到机器人被踢消息', json); } else { context.logger.logWarn('收到未知的灰条消息', json); } return undefined; } async parseEssenceMsg (msg: RawMessage, jsonStr: string) { const json = JSON.parse(jsonStr); const searchParams = new URL(json.items[0].jp).searchParams; const msgSeq = searchParams.get('msgSeq')!; const Group = searchParams.get('groupCode'); if (!Group) return undefined; // const businessId = searchParams.get('businessid'); const Peer = { guildId: '', chatType: ChatType.KCHATTYPEGROUP, peerUid: Group, }; const msgData = await this.core.apis.MsgApi.getMsgsBySeqAndCount(Peer, msgSeq.toString(), 1, true, true); const msgList = (await this.core.apis.WebApi.getGroupEssenceMsgAll(Group)).flatMap((e) => e.data.msg_list); const realMsg = msgList.find((e) => e.msg_seq.toString() === msgSeq); if (msgData.msgList[0]) { return new OB11GroupEssenceEvent( this.core, parseInt(msg.peerUid), MessageUnique.getShortIdByMsgId(msgData.msgList[0].msgId)!, parseInt(msgData.msgList[0].senderUin ?? await this.core.apis.UserApi.getUinByUidV2(msgData.msgList[0].senderUid)), parseInt(realMsg?.add_digest_uin ?? '0') ); } return undefined; // 获取MsgSeq+Peer可获取具体消息 } async parseGroupUploadFileEvene (msg: RawMessage, element: FileElement, elementWrapper: MessageElement) { return new OB11GroupUploadNoticeEvent( this.core, parseInt(msg.peerUid), parseInt(msg.senderUin || ''), { id: FileNapCatOneBotUUID.encode({ chatType: ChatType.KCHATTYPEGROUP, peerUid: msg.peerUid, }, msg.msgId, elementWrapper.elementId, elementWrapper?.fileElement?.fileUuid, element.fileMd5 ?? element.fileUuid), name: element.fileName, size: parseInt(element.fileSize), busid: element.fileBizId ?? 0, } ); } async parseGroupElement (msg: RawMessage, element: TipGroupElement, elementWrapper: GrayTipElement) { if (element.type === TipGroupElementType.KGROUPNAMEMODIFIED) { this.core.context.logger.logDebug('收到群名称变更事件', element); return new OB11GroupNameEvent( this.core, parseInt(msg.peerUid), parseInt(await this.core.apis.UserApi.getUinByUidV2(element.memberUid)), element.groupName ); } else if (element.type === TipGroupElementType.KSHUTUP) { const event = await this.parseGroupBanEvent(msg.peerUid, elementWrapper); return event; } else if (element.type === TipGroupElementType.KMEMBERADD) { // 自己的通知 协议推送为type->85 在这里实现为了避免邀请出现问题 if (element.memberUid === this.core.selfInfo.uid) { await this.core.apis.GroupApi.refreshGroupMemberCache(msg.peerUid, true); return new OB11GroupIncreaseEvent( this.core, parseInt(msg.peerUid), +this.core.selfInfo.uin, element.adminUid ? +await this.core.apis.UserApi.getUinByUidV2(element.adminUid) : 0, 'approve' ); } } return undefined; } async parseSelfInviteEvent (msg: RawMessage, inviterUin: string, inviteeUin: string) { return new OB11GroupIncreaseEvent( this.core, parseInt(msg.peerUid), +inviteeUin, +inviterUin, 'invite' ); } async parse51TypeEvent (msg: RawMessage, grayTipElement: GrayTipElement) { // 神经腾讯 没了妈妈想出来的 // Warn 下面存在高并发危险 if (grayTipElement.jsonGrayTipElement.jsonStr) { const json: { align: string, items: Array<{ txt: string, type: string; }>; } = JSON.parse(grayTipElement.jsonGrayTipElement.jsonStr); if (json.items.length === 1 && json.items[0]?.txt.endsWith('加入群')) { const old_members = structuredClone(this.core.apis.GroupApi.groupMemberCache.get(msg.peerUid)); if (!old_members) return; const new_members_map = await this.core.apis.GroupApi.refreshGroupMemberCache(msg.peerUid, true); if (!new_members_map) return; const new_members = Array.from(new_members_map.values()); // 对比members查找新成员 const new_member = new_members.find((member) => old_members.get(member.uid) === undefined); if (!new_member) return; return new OB11GroupIncreaseEvent( this.core, +msg.peerUid, +new_member.uin, 0, 'invite' ); } } return undefined; } async parseGrayTipElement (msg: RawMessage, grayTipElement: GrayTipElement) { if (grayTipElement.subElementType === NTGrayTipElementSubTypeV2.GRAYTIP_ELEMENT_SUBTYPE_GROUP) { // 解析群组事件 由sysmsg解析 return await this.parseGroupElement(msg, grayTipElement.groupElement, grayTipElement); } else if (grayTipElement.subElementType === NTGrayTipElementSubTypeV2.GRAYTIP_ELEMENT_SUBTYPE_XMLMSG) { // 筛选自身入群情况 // if (grayTipElement.xmlElement.busiId === '10145') { // const inviteData = new fastXmlParser.XMLParser({ // ignoreAttributes: false, // attributeNamePrefix: '', // }).parse(grayTipElement.xmlElement.content); // const inviterUin: string = inviteData.gtip.qq[0].jp; // const inviteeUin: string = inviteData.gtip.qq[1].jp; // //刷新群缓存 // if (inviteeUin === this.core.selfInfo.uin) { // this.core.apis.GroupApi.refreshGroupMemberCache(msg.peerUid).then().catch(); // return this.parseSelfInviteEvent(msg, inviterUin, inviteeUin); // } // } else if (grayTipElement.xmlElement?.templId === '10382') { return await this.obContext.apis.GroupApi.parseGroupEmojiLikeEventByGrayTip(msg.peerUid, grayTipElement); } else { // return await this.obContext.apis.GroupApi.parseGroupIncreaseEvent(msg.peerUid, grayTipElement); } } else if (grayTipElement.subElementType === NTGrayTipElementSubTypeV2.GRAYTIP_ELEMENT_SUBTYPE_JSON) { // 解析json事件 busiId好像是string类型的? if (grayTipElement.jsonGrayTipElement.busiId === '1061') { return await this.parsePaiYiPai(msg, grayTipElement.jsonGrayTipElement.jsonStr); } else if (grayTipElement.jsonGrayTipElement.busiId === JsonGrayBusiId.AIO_GROUP_ESSENCE_MSG_TIP.toString()) { return await this.parseEssenceMsg(msg, grayTipElement.jsonGrayTipElement.jsonStr); } else if (grayTipElement.jsonGrayTipElement.busiId === '51') { // 51是什么?{"align":"center","items":[{"txt":"下一秒起床通过王者荣耀加入群","type":"nor"}] return await this.parse51TypeEvent(msg, grayTipElement); } else { console.log('Unknown JSON event:', grayTipElement.jsonGrayTipElement, JSON.stringify(grayTipElement)); return await this.parseOtherJsonEvent(msg, grayTipElement.jsonGrayTipElement.jsonStr, this.core.context); } } return undefined; } }