mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2025-12-19 05:05:44 +08:00
Moved various helper, event, and utility files from napcat-common to napcat-core/helper for better modularity and separation of concerns. Updated imports across packages to reflect new file locations. Removed unused dependencies from napcat-common and added them to napcat-core where needed. Also consolidated type definitions and cleaned up tsconfig settings for improved compatibility.
530 lines
18 KiB
TypeScript
530 lines
18 KiB
TypeScript
import {
|
||
GeneralCallResult,
|
||
GroupMember,
|
||
NTGroupMemberRole,
|
||
NTGroupRequestOperateTypes,
|
||
InstanceContext,
|
||
KickMemberV2Req,
|
||
MemberExtSourceType,
|
||
NapCatCore,
|
||
GroupNotify,
|
||
GroupInfoSource,
|
||
ShutUpGroupMember,
|
||
Peer,
|
||
ChatType,
|
||
} from '@/napcat-core/index';
|
||
import { isNumeric, solveAsyncProblem } from 'napcat-common/src/helper';
|
||
import { LimitedHashTable } from 'napcat-common/src/message-unique';
|
||
import { CancelableTask, TaskExecutor } from 'napcat-common/src/cancel-task';
|
||
import { createGroupDetailInfoV2Param, createGroupExtFilter, createGroupExtInfo } from '../data';
|
||
import { NTEventWrapper } from '../helper/event';
|
||
|
||
export class NTQQGroupApi {
|
||
context: InstanceContext;
|
||
core: NapCatCore;
|
||
groupMemberCache: Map<string, Map<string, GroupMember>> = new Map<string, Map<string, GroupMember>>();
|
||
essenceLRU = new LimitedHashTable<number, string>(1000);
|
||
|
||
constructor (context: InstanceContext, core: NapCatCore) {
|
||
this.context = context;
|
||
this.core = core;
|
||
}
|
||
|
||
async setGroupRemark (groupCode: string, remark: string) {
|
||
return this.context.session.getGroupService().modifyGroupRemark(groupCode, remark);
|
||
}
|
||
|
||
async fetchGroupDetail (groupCode: string) {
|
||
const [, detailInfo] = await this.core.eventWrapper.callNormalEventV2(
|
||
'NodeIKernelGroupService/getGroupDetailInfo',
|
||
'NodeIKernelGroupListener/onGroupDetailInfoChange',
|
||
[groupCode, GroupInfoSource.KDATACARD],
|
||
(ret) => ret.result === 0,
|
||
(detailInfo) => detailInfo.groupCode === groupCode,
|
||
1,
|
||
5000
|
||
);
|
||
return detailInfo;
|
||
}
|
||
|
||
async initApi () {
|
||
this.initCache().then().catch(e => this.context.logger.logError(e));
|
||
}
|
||
|
||
async createGrayTip (groupCode: string, tip: string) {
|
||
return this.context.session.getMsgService().addLocalJsonGrayTipMsg(
|
||
{
|
||
chatType: ChatType.KCHATTYPEGROUP,
|
||
peerUid: groupCode,
|
||
} as Peer,
|
||
{
|
||
busiId: 2201,
|
||
jsonStr: JSON.stringify({ align: 'center', items: [{ txt: tip, type: 'nor' }] }),
|
||
recentAbstract: tip,
|
||
isServer: false,
|
||
},
|
||
true,
|
||
true
|
||
);
|
||
}
|
||
|
||
async initCache () {
|
||
for (const group of await this.getGroups(true)) {
|
||
this.refreshGroupMemberCache(group.groupCode, false).then().catch(e => this.context.logger.logError(e));
|
||
}
|
||
}
|
||
|
||
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,
|
||
pageStart: 0,
|
||
pageLimit: 300,
|
||
}, pskey);
|
||
}
|
||
|
||
async getGroupShutUpMemberList (groupCode: string): Promise<ShutUpGroupMember[]> {
|
||
const executor: TaskExecutor<ShutUpGroupMember[]> = async (resolve, reject, onCancel) => {
|
||
this.core.eventWrapper.registerListen(
|
||
'NodeIKernelGroupListener/onShutUpMemberListChanged',
|
||
(group_id) => group_id === groupCode,
|
||
1,
|
||
1000
|
||
).then((data) => {
|
||
resolve(data[1]);
|
||
}).catch(reject);
|
||
|
||
onCancel(() => {
|
||
reject(new Error('Task was canceled'));
|
||
});
|
||
};
|
||
|
||
const task = new CancelableTask(executor);
|
||
this.context.session.getGroupService().getGroupShutUpMemberList(groupCode).then(e => {
|
||
if (e.result !== 0) {
|
||
task.cancel();
|
||
}
|
||
});
|
||
return await task.catch(() => []);
|
||
}
|
||
|
||
async clearGroupNotifiesUnreadCount (doubt: boolean) {
|
||
return this.context.session.getGroupService().clearGroupNotifiesUnreadCount(doubt);
|
||
}
|
||
|
||
async setGroupAvatar (groupCode: string, filePath: string) {
|
||
return this.context.session.getGroupService().setHeader(groupCode, filePath);
|
||
}
|
||
|
||
// 0 0 无需管理员审核
|
||
// 0 2 需要管理员审核
|
||
// 1 2 禁止Bot入群( 最好只传一个1 ?)
|
||
async setGroupRobotAddOption (groupCode: string, robotMemberSwitch?: number, robotMemberExamine?: number) {
|
||
const extInfo = createGroupExtInfo(groupCode);
|
||
const groupExtFilter = createGroupExtFilter();
|
||
if (robotMemberSwitch !== undefined) {
|
||
extInfo.extInfo.inviteRobotMemberSwitch = robotMemberSwitch;
|
||
groupExtFilter.inviteRobotMemberSwitch = 1;
|
||
}
|
||
if (robotMemberExamine !== undefined) {
|
||
extInfo.extInfo.inviteRobotMemberExamine = robotMemberExamine;
|
||
groupExtFilter.inviteRobotMemberExamine = 1;
|
||
}
|
||
return this.context.session.getGroupService().modifyGroupExtInfoV2(extInfo, groupExtFilter);
|
||
}
|
||
|
||
async setGroupAddOption (groupCode: string, option: {
|
||
addOption: number;
|
||
groupQuestion?: string;
|
||
groupAnswer?: string;
|
||
}) {
|
||
const param = createGroupDetailInfoV2Param(groupCode);
|
||
// 设置要修改的目标
|
||
param.filter.addOption = 1;
|
||
if (option.addOption === 4 || option.addOption === 5) {
|
||
// 4 问题进入答案 5 问题管理员批准
|
||
param.filter.groupQuestion = 1;
|
||
param.filter.groupAnswer = option.addOption === 4 ? 1 : 0;
|
||
param.modifyInfo.groupQuestion = option.groupQuestion || '';
|
||
param.modifyInfo.groupAnswer = option.addOption === 4 ? option.groupAnswer || '' : '';
|
||
}
|
||
param.modifyInfo.addOption = option.addOption;
|
||
return this.context.session.getGroupService().modifyGroupDetailInfoV2(param, 0);
|
||
}
|
||
|
||
async setGroupSearch (groupCode: string, option: {
|
||
noCodeFingerOpenFlag?: number;
|
||
noFingerOpenFlag?: number;
|
||
}) {
|
||
const param = createGroupDetailInfoV2Param(groupCode);
|
||
if (option.noCodeFingerOpenFlag) {
|
||
param.filter.noCodeFingerOpenFlag = 1;
|
||
param.modifyInfo.noCodeFingerOpenFlag = option.noCodeFingerOpenFlag;
|
||
}
|
||
if (option.noFingerOpenFlag) {
|
||
param.filter.noFingerOpenFlag = 1;
|
||
param.modifyInfo.noFingerOpenFlag = option.noFingerOpenFlag;
|
||
}
|
||
return this.context.session.getGroupService().modifyGroupDetailInfoV2(param, 0);
|
||
}
|
||
|
||
async getGroups (forced: boolean = false) {
|
||
const [, , groupList] = await this.core.eventWrapper.callNormalEventV2(
|
||
'NodeIKernelGroupService/getGroupList',
|
||
'NodeIKernelGroupListener/onGroupListUpdate',
|
||
[forced]
|
||
);
|
||
return groupList;
|
||
}
|
||
|
||
async getGroupExtFE0Info (groupCodes: Array<string>, forced = true) {
|
||
return this.context.session.getGroupService().getGroupExt0xEF0Info(
|
||
groupCodes,
|
||
[],
|
||
{
|
||
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 getGroupMemberAll (groupCode: string, forced = false) {
|
||
return this.context.session.getGroupService().getAllMemberList(groupCode, forced);
|
||
}
|
||
|
||
async refreshGroupMemberCache (groupCode: string, isWait = true) {
|
||
const updateCache = async () => {
|
||
try {
|
||
const members = await this.getGroupMemberAll(groupCode, true);
|
||
this.groupMemberCache.set(groupCode, members.result.infos);
|
||
} catch (e) {
|
||
this.context.logger.logError(`刷新群成员缓存失败, 群号: ${groupCode}, 错误: ${e}`);
|
||
}
|
||
};
|
||
|
||
if (isWait) {
|
||
await updateCache();
|
||
} else {
|
||
updateCache();
|
||
}
|
||
|
||
return this.groupMemberCache.get(groupCode);
|
||
}
|
||
|
||
async refreshGroupMemberCachePartial (groupCode: string, uid: string) {
|
||
const member = await this.getGroupMemberEx(groupCode, uid, true);
|
||
if (member) {
|
||
this.groupMemberCache.get(groupCode)?.set(uid, member);
|
||
}
|
||
return member;
|
||
}
|
||
|
||
async getGroupMember (groupCode: string | number, memberUinOrUid: string | number) {
|
||
const groupCodeStr = groupCode.toString();
|
||
const memberUinOrUidStr = memberUinOrUid.toString();
|
||
|
||
// 获取群成员缓存
|
||
let members = this.groupMemberCache.get(groupCodeStr);
|
||
if (!members) {
|
||
members = (await this.refreshGroupMemberCache(groupCodeStr, true));
|
||
}
|
||
|
||
const getMember = () => {
|
||
if (isNumeric(memberUinOrUidStr)) {
|
||
return Array.from(members!.values()).find(member => member.uin === memberUinOrUidStr);
|
||
} else {
|
||
return members!.get(memberUinOrUidStr);
|
||
}
|
||
};
|
||
|
||
let member = getMember();
|
||
// 如果缓存中不存在该成员,尝试刷新缓存
|
||
if (!member) {
|
||
members = (await this.refreshGroupMemberCache(groupCodeStr, true));
|
||
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: Array<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 transGroupFile (groupCode: string, fileId: string) {
|
||
return this.context.session.getRichMediaService().transGroupFile(groupCode, fileId);
|
||
}
|
||
|
||
async addGroupEssence (groupCode: string, msgId: string) {
|
||
const MsgData = await this.context.session.getMsgService().getMsgsIncludeSelf({
|
||
chatType: 2,
|
||
guildId: '',
|
||
peerUid: groupCode,
|
||
}, msgId, 1, false);
|
||
if (!MsgData.msgList[0]) {
|
||
throw new Error('消息不存在');
|
||
}
|
||
const param = {
|
||
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,
|
||
};
|
||
return this.context.session.getGroupService().quitGroupV2(param);
|
||
}
|
||
|
||
async removeGroupEssenceBySeq (groupCode: string, msgRandom: string, msgSeq: string) {
|
||
const param = {
|
||
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);
|
||
if (!MsgData.msgList[0]) {
|
||
throw new Error('消息不存在');
|
||
}
|
||
const param = {
|
||
groupCode,
|
||
msgRandom: parseInt(MsgData.msgList[0].msgRandom),
|
||
msgSeq: parseInt(MsgData.msgList[0].msgSeq),
|
||
};
|
||
return this.context.session.getGroupService().removeGroupEssence(param);
|
||
}
|
||
|
||
async getSingleScreenNotifies (doubt: boolean, count: number) {
|
||
const [, , , notifies] = await this.core.eventWrapper.callNormalEventV2(
|
||
'NodeIKernelGroupService/getSingleScreenNotifies',
|
||
'NodeIKernelGroupListener/onGroupSingleScreenNotifies',
|
||
[
|
||
doubt,
|
||
'',
|
||
count,
|
||
]
|
||
);
|
||
return notifies;
|
||
}
|
||
|
||
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: boolean = false, retry: number = 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: string, _: any, members: Map<string, GroupMember>) => 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 getGroupFileCount (groupCodes: Array<string>) {
|
||
return this.context.session.getRichMediaService().batchGetGroupFileCount(groupCodes);
|
||
}
|
||
|
||
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 (doubt: boolean, notify: GroupNotify, operateType: NTGroupRequestOperateTypes, reason?: string) {
|
||
return this.context.session.getGroupService().operateSysNotify(
|
||
doubt,
|
||
{
|
||
operateType,
|
||
targetMsg: {
|
||
seq: notify.seq, // 通知序列号
|
||
type: notify.type,
|
||
groupCode: notify.group.groupCode,
|
||
postscript: reason ?? ' ', // 仅传空值可能导致处理失败,故默认给个空格
|
||
},
|
||
});
|
||
}
|
||
|
||
async quitGroup (groupCode: string) {
|
||
return this.context.session.getGroupService().quitGroup(groupCode);
|
||
}
|
||
|
||
async kickMember (groupCode: string, kickUids: string[], refuseForever: boolean = false, kickReason: string = '') {
|
||
return this.context.session.getGroupService().kickMember(groupCode, kickUids, refuseForever, kickReason);
|
||
}
|
||
|
||
async banMember (groupCode: string, memList: Array<{ uid: string, timeStamp: number; }>) {
|
||
// timeStamp为秒数, 0为解除禁言
|
||
return this.context.session.getGroupService().setMemberShutUp(groupCode, memList);
|
||
}
|
||
|
||
async banGroup (groupCode: string, shutUp: boolean) {
|
||
return this.context.session.getGroupService().setGroupShutUp(groupCode, shutUp);
|
||
}
|
||
|
||
async setMemberCard (groupCode: string, memberUid: string, cardName: string) {
|
||
return this.context.session.getGroupService().modifyMemberCardName(groupCode, memberUid, cardName);
|
||
}
|
||
|
||
async setMemberRole (groupCode: string, memberUid: string, role: NTGroupMemberRole) {
|
||
return this.context.session.getGroupService().modifyMemberRole(groupCode, memberUid, role);
|
||
}
|
||
|
||
async setGroupName (groupCode: string, groupName: string) {
|
||
return this.context.session.getGroupService().modifyGroupName(groupCode, groupName, false);
|
||
}
|
||
|
||
async publishGroupBulletin (groupCode: 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,
|
||
oldFeedsId: '',
|
||
pinned,
|
||
confirmRequired,
|
||
};
|
||
return this.context.session.getGroupService().publishGroupBulletin(groupCode, psKey!, data);
|
||
}
|
||
|
||
async getGroupRemainAtTimes (groupCode: string) {
|
||
return this.context.session.getGroupService().getGroupRemainAtTimes(groupCode);
|
||
}
|
||
|
||
async getMemberExtInfo (groupCode: string, uin: string) {
|
||
return this.context.session.getGroupService().getMemberExtInfo(
|
||
{
|
||
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,
|
||
},
|
||
}
|
||
);
|
||
}
|
||
}
|