mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2024-11-21 09:36:35 +00:00
508 lines
19 KiB
TypeScript
508 lines
19 KiB
TypeScript
import {
|
|
GeneralCallResult,
|
|
Group,
|
|
GroupMember,
|
|
GroupMemberRole,
|
|
GroupRequestOperateTypes,
|
|
InstanceContext,
|
|
KickMemberV2Req,
|
|
MemberExtSourceType,
|
|
NapCatCore,
|
|
} from '@/core';
|
|
import { isNumeric, sleep, solveAsyncProblem } from '@/common/helper';
|
|
import { LimitedHashTable } from '@/common/message-unique';
|
|
import { NTEventWrapper } from '@/common/event';
|
|
import { NapProtoMsg } from '../proto/NapProto';
|
|
import { OidbSvcTrpcTcpBase } from '../proto/oidb/OidbBase';
|
|
import { OidbSvcTrpcTcp0XED3_1 } from '../proto/oidb/Oidb.ed3_1';
|
|
interface recvPacket {
|
|
type: string,//仅recv
|
|
trace_id_md5?: string,
|
|
data: {
|
|
seq: number,
|
|
hex_data: string,
|
|
cmd: string
|
|
}
|
|
}
|
|
export class NTQQGroupApi {
|
|
context: InstanceContext;
|
|
core: NapCatCore;
|
|
groupCache: Map<string, Group> = new Map<string, Group>();
|
|
groupMemberCache: Map<string, Map<string, GroupMember>> = new Map<string, Map<string, GroupMember>>();
|
|
groups: Group[] = [];
|
|
essenceLRU = new LimitedHashTable<number, string>(1000);
|
|
session: any;
|
|
|
|
constructor(context: InstanceContext, core: NapCatCore) {
|
|
this.context = context;
|
|
this.core = core;
|
|
this.initCache().then().catch(context.logger.logError.bind(context.logger));
|
|
}
|
|
|
|
async initCache() {
|
|
this.groups = await this.getGroups();
|
|
for (const group of this.groups) {
|
|
this.groupCache.set(group.groupCode, group);
|
|
}
|
|
this.context.logger.logDebug(`加载${this.groups.length}个群组缓存完成`);
|
|
// process.pid 调试点
|
|
}
|
|
async getCoreAndBaseInfo(uids: string[]) {
|
|
return await this.core.eventWrapper.callNoListenerEvent(
|
|
'NodeIKernelProfileService/getCoreAndBaseInfo',
|
|
'nodeStore',
|
|
uids,
|
|
);
|
|
}
|
|
async sendPocketRkey() {
|
|
let hex = '08E7A00210CA01221D0A130A05080110CA011206A80602B006011A0208022206080A081408022A006001';
|
|
let ret = await this.core.apis.PacketApi.sendPacket('OidbSvcTrpcTcp.0x9067_202', hex, true);
|
|
//console.log('ret: ', ret);
|
|
}
|
|
async sendPacketPoke(group: number, peer: number) {
|
|
let oidb_0xed3 = new NapProtoMsg(OidbSvcTrpcTcp0XED3_1).encode({
|
|
uin: peer,
|
|
groupUin: group,
|
|
friendUin: group,
|
|
ext: 0
|
|
});
|
|
let oidb_packet = new NapProtoMsg(OidbSvcTrpcTcpBase).encode({
|
|
command: 0xed3,
|
|
subCommand: 1,
|
|
body: oidb_0xed3
|
|
});
|
|
let hex = Buffer.from(oidb_packet).toString('hex');
|
|
let retdata = await this.core.apis.PacketApi.sendPacket('OidbSvcTrpcTcp.0xed3_1', hex, false);
|
|
}
|
|
async fetchGroupEssenceList(groupCode: string) {
|
|
const pskey = (await this.core.apis.UserApi.getPSkey(['qun.qq.com'])).domainPskeyMap.get('qun.qq.com')!;
|
|
return this.context.session.getGroupService().fetchGroupEssenceList({
|
|
groupCode: groupCode,
|
|
pageStart: 0,
|
|
pageLimit: 300,
|
|
}, pskey);
|
|
}
|
|
|
|
async clearGroupNotifiesUnreadCount(uk: boolean) {
|
|
return this.context.session.getGroupService().clearGroupNotifiesUnreadCount(uk);
|
|
}
|
|
|
|
async setGroupAvatar(gc: string, filePath: string) {
|
|
return this.context.session.getGroupService().setHeader(gc, filePath);
|
|
}
|
|
|
|
async getGroups(forced = false) {
|
|
const [, , groupList] = await this.core.eventWrapper.callNormalEventV2(
|
|
'NodeIKernelGroupService/getGroupList',
|
|
'NodeIKernelGroupListener/onGroupListUpdate',
|
|
[forced],
|
|
);
|
|
return groupList;
|
|
}
|
|
|
|
async getGroupExtFE0Info(groupCode: string[], forced = true) {
|
|
return this.context.session.getGroupService().getGroupExt0xEF0Info(
|
|
groupCode,
|
|
[],
|
|
{
|
|
bindGuildId: 1,
|
|
blacklistExpireTime: 1,
|
|
companyId: 1,
|
|
essentialMsgPrivilege: 1,
|
|
essentialMsgSwitch: 1,
|
|
fullGroupExpansionSeq: 1,
|
|
fullGroupExpansionSwitch: 1,
|
|
gangUpId: 1,
|
|
groupAioBindGuildId: 1,
|
|
groupBindGuildIds: 1,
|
|
groupBindGuildSwitch: 1,
|
|
groupExcludeGuildIds: 1,
|
|
groupExtFlameData: 1,
|
|
groupFlagPro1: 1,
|
|
groupInfoExtSeq: 1,
|
|
groupOwnerId: 1,
|
|
groupSquareSwitch: 1,
|
|
hasGroupCustomPortrait: 1,
|
|
inviteRobotMemberExamine: 1,
|
|
inviteRobotMemberSwitch: 1,
|
|
inviteRobotSwitch: 1,
|
|
isLimitGroupRtc: 1,
|
|
lightCharNum: 1,
|
|
luckyWord: 1,
|
|
luckyWordId: 1,
|
|
msgEventSeq: 1,
|
|
qqMusicMedalSwitch: 1,
|
|
reserve: 1,
|
|
showPlayTogetherSwitch: 1,
|
|
starId: 1,
|
|
todoSeq: 1,
|
|
viewedMsgDisappearTime: 1,
|
|
},
|
|
forced,
|
|
);
|
|
}
|
|
|
|
async getGroup(groupCode: string, forced = false) {
|
|
let group = this.groupCache.get(groupCode.toString());
|
|
if (!group) {
|
|
try {
|
|
const groupList = await this.getGroups(forced);
|
|
if (groupList.length) {
|
|
groupList.forEach(g => {
|
|
this.groupCache.set(g.groupCode, g);
|
|
});
|
|
}
|
|
} catch (e) {
|
|
return undefined;
|
|
}
|
|
}
|
|
group = this.groupCache.get(groupCode.toString());
|
|
return group;
|
|
}
|
|
|
|
async getGroupMemberAll(groupCode: string, forced = false) {
|
|
return this.context.session.getGroupService().getAllMemberList(groupCode, forced);
|
|
}
|
|
|
|
async getGroupMember(groupCode: string | number, memberUinOrUid: string | number) {
|
|
const groupCodeStr = groupCode.toString();
|
|
const memberUinOrUidStr = memberUinOrUid.toString();
|
|
let members = this.groupMemberCache.get(groupCodeStr);
|
|
if (!members) {
|
|
try {
|
|
members = await this.getGroupMembersV2(groupCodeStr);
|
|
// 更新群成员列表
|
|
this.groupMemberCache.set(groupCodeStr, members);
|
|
} catch (e) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
// log('getGroupMember', members);
|
|
function getMember() {
|
|
let member: GroupMember | undefined;
|
|
if (isNumeric(memberUinOrUidStr)) {
|
|
member = Array.from(members!.values()).find(member => member.uin === memberUinOrUidStr);
|
|
} else {
|
|
member = members!.get(memberUinOrUidStr);
|
|
}
|
|
return member;
|
|
}
|
|
|
|
let member = getMember();
|
|
if (!member) {
|
|
members = await this.getGroupMembersV2(groupCodeStr);
|
|
member = getMember();
|
|
}
|
|
return member;
|
|
}
|
|
async getGroupRecommendContactArkJson(groupCode: string) {
|
|
return this.context.session.getGroupService().getGroupRecommendContactArkJson(groupCode);
|
|
}
|
|
|
|
async CreatGroupFileFolder(groupCode: string, folderName: string) {
|
|
return this.context.session.getRichMediaService().createGroupFolder(groupCode, folderName);
|
|
}
|
|
|
|
async DelGroupFile(groupCode: string, files: string[]) {
|
|
return this.context.session.getRichMediaService().deleteGroupFile(groupCode, [102], files);
|
|
}
|
|
|
|
async DelGroupFileFolder(groupCode: string, folderId: string) {
|
|
return this.context.session.getRichMediaService().deleteGroupFolder(groupCode, folderId);
|
|
}
|
|
|
|
async addGroupEssence(GroupCode: string, msgId: string) {
|
|
const MsgData = await this.context.session.getMsgService().getMsgsIncludeSelf({
|
|
chatType: 2,
|
|
guildId: '',
|
|
peerUid: GroupCode,
|
|
}, msgId, 1, false);
|
|
const param = {
|
|
groupCode: GroupCode,
|
|
msgRandom: parseInt(MsgData.msgList[0].msgRandom),
|
|
msgSeq: parseInt(MsgData.msgList[0].msgSeq),
|
|
};
|
|
return this.context.session.getGroupService().addGroupEssence(param);
|
|
}
|
|
|
|
async kickMemberV2Inner(param: KickMemberV2Req) {
|
|
return this.context.session.getGroupService().kickMemberV2(param);
|
|
}
|
|
|
|
async deleteGroupBulletin(GroupCode: string, noticeId: string) {
|
|
const psKey = (await this.core.apis.UserApi.getPSkey(['qun.qq.com'])).domainPskeyMap.get('qun.qq.com')!;
|
|
return this.context.session.getGroupService().deleteGroupBulletin(GroupCode, psKey, noticeId);
|
|
}
|
|
|
|
async quitGroupV2(GroupCode: string, needDeleteLocalMsg: boolean) {
|
|
const param = {
|
|
groupCode: GroupCode,
|
|
needDeleteLocalMsg: needDeleteLocalMsg,
|
|
};
|
|
return this.context.session.getGroupService().quitGroupV2(param);
|
|
}
|
|
|
|
async removeGroupEssenceBySeq(GroupCode: string, msgRandom: string, msgSeq: string) {
|
|
const param = {
|
|
groupCode: GroupCode,
|
|
msgRandom: parseInt(msgRandom),
|
|
msgSeq: parseInt(msgSeq),
|
|
};
|
|
return this.context.session.getGroupService().removeGroupEssence(param);
|
|
}
|
|
|
|
async removeGroupEssence(GroupCode: string, msgId: string) {
|
|
const MsgData = await this.context.session.getMsgService().getMsgsIncludeSelf({
|
|
chatType: 2,
|
|
guildId: '',
|
|
peerUid: GroupCode,
|
|
}, msgId, 1, false);
|
|
const param = {
|
|
groupCode: GroupCode,
|
|
msgRandom: parseInt(MsgData.msgList[0].msgRandom),
|
|
msgSeq: parseInt(MsgData.msgList[0].msgSeq),
|
|
};
|
|
return this.context.session.getGroupService().removeGroupEssence(param);
|
|
}
|
|
|
|
async getSingleScreenNotifies(doubt: boolean, num: number) {
|
|
const [, , , notifies] = await this.core.eventWrapper.callNormalEventV2(
|
|
'NodeIKernelGroupService/getSingleScreenNotifies',
|
|
'NodeIKernelGroupListener/onGroupSingleScreenNotifies',
|
|
[
|
|
doubt,
|
|
'',
|
|
num,
|
|
],
|
|
);
|
|
return notifies;
|
|
}
|
|
|
|
async getGroupMemberV2(GroupCode: string, uid: string, forced = false) {
|
|
const Listener = this.core.eventWrapper.registerListen(
|
|
'NodeIKernelGroupListener/onMemberInfoChange',
|
|
1,
|
|
forced ? 5000 : 250,
|
|
(params, _, members) => params === GroupCode && members.size > 0,
|
|
);
|
|
const retData = await (
|
|
this.core.eventWrapper
|
|
.createEventFunction('NodeIKernelGroupService/getMemberInfo')
|
|
)!(GroupCode, [uid], forced);
|
|
if (retData.result !== 0) {
|
|
throw new Error(`${retData.errMsg}`);
|
|
}
|
|
const result = await Listener as unknown;
|
|
let member: GroupMember | undefined;
|
|
if (Array.isArray(result) && result?.[2] instanceof Map) {
|
|
const members = result[2] as Map<string, GroupMember>;
|
|
member = members.get(uid);
|
|
}
|
|
return member;
|
|
}
|
|
async searchGroup(groupCode: string) {
|
|
const [, ret] = await this.core.eventWrapper.callNormalEventV2(
|
|
'NodeIKernelSearchService/searchGroup',
|
|
'NodeIKernelSearchListener/onSearchGroupResult',
|
|
[{
|
|
keyWords: groupCode,
|
|
groupNum: 25,
|
|
exactSearch: false,
|
|
penetrate: ''
|
|
}],
|
|
(ret) => ret.result === 0,
|
|
(params) => !!params.groupInfos.find(g => g.groupCode === groupCode),
|
|
1,
|
|
5000
|
|
);
|
|
return ret.groupInfos.find(g => g.groupCode === groupCode);
|
|
}
|
|
async getGroupMemberEx(GroupCode: string, uid: string, forced = false, retry = 2) {
|
|
const data = await solveAsyncProblem((eventWrapper: NTEventWrapper, GroupCode: string, uid: string, forced = false) => {
|
|
return eventWrapper.callNormalEventV2(
|
|
'NodeIKernelGroupService/getMemberInfo',
|
|
'NodeIKernelGroupListener/onMemberInfoChange',
|
|
[GroupCode, [uid], forced],
|
|
(ret) => ret.result === 0,
|
|
(params, _, members) => params === GroupCode && members.size > 0 && members.has(uid),
|
|
1,
|
|
forced ? 2500 : 250
|
|
);
|
|
}, this.core.eventWrapper, GroupCode, uid, forced);
|
|
if (data && data[3] instanceof Map && data[3].has(uid)) {
|
|
return data[3].get(uid);
|
|
}
|
|
if (retry > 0) {
|
|
const trydata = await this.getGroupMemberEx(GroupCode, uid, true, retry - 1) as GroupMember | undefined;
|
|
if (trydata) return trydata;
|
|
}
|
|
return undefined;
|
|
}
|
|
async getGroupMembersV2(groupQQ: string, num = 3000): Promise<Map<string, GroupMember>> {
|
|
const groupService = this.context.session.getGroupService();
|
|
const sceneId = groupService.createMemberListScene(groupQQ, 'groupMemberList_MainWindow');
|
|
const listener = this.core.eventWrapper.registerListen(
|
|
'NodeIKernelGroupListener/onMemberListChange',
|
|
1,
|
|
5000,
|
|
(params) => params.sceneId === sceneId,
|
|
);
|
|
try {
|
|
const [membersFromFunc, membersFromListener] = await Promise.allSettled([
|
|
groupService.getNextMemberList(sceneId, undefined, num),
|
|
listener,
|
|
]);
|
|
if (membersFromFunc.status === 'fulfilled' && membersFromListener.status === 'fulfilled') {
|
|
return new Map([
|
|
...membersFromFunc.value.result.infos,
|
|
...membersFromListener.value[0].infos,
|
|
]);
|
|
}
|
|
if (membersFromFunc.status === 'fulfilled') {
|
|
return membersFromFunc.value.result.infos;
|
|
}
|
|
if (membersFromListener.status === 'fulfilled') {
|
|
return membersFromListener.value[0].infos;
|
|
}
|
|
throw new Error('获取群成员列表失败');
|
|
} finally {
|
|
groupService.destroyMemberListScene(sceneId);
|
|
}
|
|
}
|
|
|
|
async getGroupMembers(groupQQ: string, num = 3000): Promise<Map<string, GroupMember>> {
|
|
const groupService = this.context.session.getGroupService();
|
|
const sceneId = groupService.createMemberListScene(groupQQ, 'groupMemberList_MainWindow');
|
|
const result = await groupService.getNextMemberList(sceneId!, undefined, num);
|
|
if (result.errCode !== 0) {
|
|
throw new Error('获取群成员列表出错,' + result.errMsg);
|
|
}
|
|
this.context.logger.logDebug(`获取群(${groupQQ})成员列表结果:`, `members: ${result.result.infos.size}`);
|
|
return result.result.infos;
|
|
}
|
|
|
|
async getGroupFileCount(Gids: Array<string>) {
|
|
return this.context.session.getRichMediaService().batchGetGroupFileCount(Gids);
|
|
}
|
|
|
|
async getArkJsonGroupShare(GroupCode: string) {
|
|
const ret = await this.core.eventWrapper.callNoListenerEvent(
|
|
'NodeIKernelGroupService/getGroupRecommendContactArkJson',
|
|
GroupCode,
|
|
) as GeneralCallResult & { arkJson: string };
|
|
return ret.arkJson;
|
|
}
|
|
|
|
//需要异常处理
|
|
async uploadGroupBulletinPic(GroupCode: string, imageurl: string) {
|
|
const _Pskey = (await this.core.apis.UserApi.getPSkey(['qun.qq.com'])).domainPskeyMap.get('qun.qq.com')!;
|
|
return this.context.session.getGroupService().uploadGroupBulletinPic(GroupCode, _Pskey, imageurl);
|
|
}
|
|
|
|
async handleGroupRequest(flag: string, operateType: GroupRequestOperateTypes, reason?: string) {
|
|
const flagitem = flag.split('|');
|
|
const groupCode = flagitem[0];
|
|
const seq = flagitem[1];
|
|
const type = parseInt(flagitem[2]);
|
|
|
|
return this.context.session.getGroupService().operateSysNotify(
|
|
false,
|
|
{
|
|
operateType: operateType, // 2 拒绝
|
|
targetMsg: {
|
|
seq: seq, // 通知序列号
|
|
type: type,
|
|
groupCode: groupCode,
|
|
postscript: reason ?? ' ', // 仅传空值可能导致处理失败,故默认给个空格
|
|
},
|
|
});
|
|
}
|
|
|
|
async quitGroup(groupQQ: string) {
|
|
return this.context.session.getGroupService().quitGroup(groupQQ);
|
|
}
|
|
|
|
async kickMember(groupQQ: string, kickUids: string[], refuseForever: boolean = false, kickReason: string = '') {
|
|
return this.context.session.getGroupService().kickMember(groupQQ, kickUids, refuseForever, kickReason);
|
|
}
|
|
|
|
async banMember(groupQQ: string, memList: Array<{ uid: string, timeStamp: number }>) {
|
|
// timeStamp为秒数, 0为解除禁言
|
|
return this.context.session.getGroupService().setMemberShutUp(groupQQ, memList);
|
|
}
|
|
|
|
async banGroup(groupQQ: string, shutUp: boolean) {
|
|
return this.context.session.getGroupService().setGroupShutUp(groupQQ, shutUp);
|
|
}
|
|
|
|
async setMemberCard(groupQQ: string, memberUid: string, cardName: string) {
|
|
return this.context.session.getGroupService().modifyMemberCardName(groupQQ, memberUid, cardName);
|
|
}
|
|
|
|
async setMemberRole(groupQQ: string, memberUid: string, role: GroupMemberRole) {
|
|
return this.context.session.getGroupService().modifyMemberRole(groupQQ, memberUid, role);
|
|
}
|
|
|
|
async setGroupName(groupQQ: string, groupName: string) {
|
|
return this.context.session.getGroupService().modifyGroupName(groupQQ, groupName, false);
|
|
}
|
|
|
|
async publishGroupBulletin(groupQQ: string, content: string, picInfo: {
|
|
id: string,
|
|
width: number,
|
|
height: number
|
|
} | undefined = undefined, pinned: number = 0, confirmRequired: number = 0) {
|
|
const psKey = (await this.core.apis.UserApi.getPSkey(['qun.qq.com'])).domainPskeyMap.get('qun.qq.com');
|
|
//text是content内容url编码
|
|
const data = {
|
|
text: encodeURI(content),
|
|
picInfo: picInfo,
|
|
oldFeedsId: '',
|
|
pinned: pinned,
|
|
confirmRequired: confirmRequired,
|
|
};
|
|
return this.context.session.getGroupService().publishGroupBulletin(groupQQ, psKey!, data);
|
|
}
|
|
|
|
async getGroupRemainAtTimes(GroupCode: string) {
|
|
this.context.session.getGroupService().getGroupRemainAtTimes(GroupCode);
|
|
}
|
|
|
|
async getMemberExtInfo(groupCode: string, uin: string) {
|
|
return this.context.session.getGroupService().getMemberExtInfo(
|
|
{
|
|
groupCode: groupCode,
|
|
sourceType: MemberExtSourceType.TITLETYPE,
|
|
beginUin: '0',
|
|
dataTime: '0',
|
|
uinList: [uin],
|
|
uinNum: '',
|
|
seq: '',
|
|
groupType: '',
|
|
richCardNameVer: '',
|
|
memberExtFilter: {
|
|
memberLevelInfoUin: 1,
|
|
memberLevelInfoPoint: 1,
|
|
memberLevelInfoActiveDay: 1,
|
|
memberLevelInfoLevel: 1,
|
|
memberLevelInfoName: 1,
|
|
levelName: 1,
|
|
dataTime: 1,
|
|
userShowFlag: 1,
|
|
sysShowFlag: 1,
|
|
timeToUpdate: 1,
|
|
nickName: 1,
|
|
specialTitle: 1,
|
|
levelNameNew: 1,
|
|
userShowFlagNew: 1,
|
|
msgNeedField: 1,
|
|
cmdUinFlagExt3Grocery: 1,
|
|
memberIcon: 1,
|
|
memberInfoSeq: 1,
|
|
},
|
|
},
|
|
);
|
|
}
|
|
}
|