From a2d1379866a7f26c44513d29ba2844836021f474 Mon Sep 17 00:00:00 2001 From: idranme Date: Mon, 5 Aug 2024 19:09:41 +0800 Subject: [PATCH 1/4] sync --- src/common/utils/EventTask.ts | 231 ++++++++++++++++++ src/main/main.ts | 8 +- src/ntqqapi/api/friend.ts | 18 +- src/ntqqapi/api/group.ts | 2 +- src/ntqqapi/native/wrapper.ts | 19 -- .../services/NodeIKernelBuddyService.ts | 125 ++++++++++ .../services/NodeIKernelProfileService.ts | 106 ++++++++ src/ntqqapi/services/common.ts | 16 ++ src/ntqqapi/services/index.ts | 2 + src/ntqqapi/types/user.ts | 146 ++++++++++- src/ntqqapi/wrapper.ts | 26 ++ src/onebot11/action/user/GetFriendList.ts | 4 + src/onebot11/constructor.ts | 66 +++-- src/onebot11/types.ts | 2 + 14 files changed, 717 insertions(+), 54 deletions(-) create mode 100644 src/common/utils/EventTask.ts delete mode 100644 src/ntqqapi/native/wrapper.ts create mode 100644 src/ntqqapi/services/NodeIKernelBuddyService.ts create mode 100644 src/ntqqapi/services/NodeIKernelProfileService.ts create mode 100644 src/ntqqapi/services/common.ts create mode 100644 src/ntqqapi/services/index.ts create mode 100644 src/ntqqapi/wrapper.ts diff --git a/src/common/utils/EventTask.ts b/src/common/utils/EventTask.ts new file mode 100644 index 0000000..28d9a8e --- /dev/null +++ b/src/common/utils/EventTask.ts @@ -0,0 +1,231 @@ +import { NodeIQQNTWrapperSession } from '@/ntqqapi/wrapper' +import { randomUUID } from 'node:crypto' + +interface Internal_MapKey { + timeout: number + createtime: number + func: (...arg: any[]) => any + checker: ((...args: any[]) => boolean) | undefined +} + +export class ListenerClassBase { + [key: string]: string +} + +export interface ListenerIBase { + new(listener: any): ListenerClassBase +} + +export class NTEventWrapper { + private ListenerMap: { [key: string]: ListenerIBase } | undefined//ListenerName-Unique -> Listener构造函数 + private WrapperSession: NodeIQQNTWrapperSession | undefined//WrapperSession + private ListenerManger: Map = new Map() //ListenerName-Unique -> Listener实例 + private EventTask = new Map>>()//tasks ListenerMainName -> ListenerSubName-> uuid -> {timeout,createtime,func} + + constructor() { + } + + createProxyDispatch(ListenerMainName: string) { + const current = this + return new Proxy({}, { + get(target: any, prop: any, receiver: any) { + // console.log('get', prop, typeof target[prop]) + if (typeof target[prop] === 'undefined') { + // 如果方法不存在,返回一个函数,这个函数调用existentMethod + return (...args: any[]) => { + current.DispatcherListener.apply(current, [ListenerMainName, prop, ...args]).then() + } + } + // 如果方法存在,正常返回 + return Reflect.get(target, prop, receiver) + } + }) + } + + init({ ListenerMap, WrapperSession }: { ListenerMap: { [key: string]: typeof ListenerClassBase }, WrapperSession: NodeIQQNTWrapperSession }) { + this.ListenerMap = ListenerMap + this.WrapperSession = WrapperSession + } + + CreatEventFunction any>(eventName: string): T | undefined { + const eventNameArr = eventName.split('/') + type eventType = { + [key: string]: () => { [key: string]: (...params: Parameters) => Promise> } + } + if (eventNameArr.length > 1) { + const serviceName = 'get' + eventNameArr[0].replace('NodeIKernel', '') + const eventName = eventNameArr[1] + //getNodeIKernelGroupListener,GroupService + //console.log('2', eventName) + const services = (this.WrapperSession as unknown as eventType)[serviceName]() + let event = services[eventName] + //重新绑定this + event = event.bind(services) + if (event) { + return event as T + } + return undefined + } + } + + CreatListenerFunction(listenerMainName: string, uniqueCode: string = ''): T { + const ListenerType = this.ListenerMap![listenerMainName] + let Listener = this.ListenerManger.get(listenerMainName + uniqueCode) + if (!Listener && ListenerType) { + Listener = new ListenerType(this.createProxyDispatch(listenerMainName)) + const ServiceSubName = listenerMainName.match(/^NodeIKernel(.*?)Listener$/)![1] + const Service = 'NodeIKernel' + ServiceSubName + 'Service/addKernel' + ServiceSubName + 'Listener' + const addfunc = this.CreatEventFunction<(listener: T) => number>(Service) + addfunc!(Listener as T) + //console.log(addfunc!(Listener as T)) + this.ListenerManger.set(listenerMainName + uniqueCode, Listener) + } + return Listener as T + } + + //统一回调清理事件 + async DispatcherListener(ListenerMainName: string, ListenerSubName: string, ...args: any[]) { + //console.log("[EventDispatcher]",ListenerMainName, ListenerSubName, ...args) + this.EventTask.get(ListenerMainName)?.get(ListenerSubName)?.forEach((task, uuid) => { + //console.log(task.func, uuid, task.createtime, task.timeout) + if (task.createtime + task.timeout < Date.now()) { + this.EventTask.get(ListenerMainName)?.get(ListenerSubName)?.delete(uuid) + return + } + if (task.checker && task.checker(...args)) { + task.func(...args) + } + }) + } + + async CallNoListenerEvent Promise | any>(EventName = '', timeout: number = 3000, ...args: Parameters) { + return new Promise>>(async (resolve, reject) => { + const EventFunc = this.CreatEventFunction(EventName) + let complete = false + const Timeouter = setTimeout(() => { + if (!complete) { + reject(new Error('NTEvent EventName:' + EventName + ' timeout')) + } + }, timeout) + const retData = await EventFunc!(...args) + complete = true + resolve(retData) + }) + } + + async RegisterListen void>(ListenerName = '', waitTimes = 1, timeout = 5000, checker: (...args: Parameters) => boolean) { + return new Promise>((resolve, reject) => { + const ListenerNameList = ListenerName.split('/') + const ListenerMainName = ListenerNameList[0] + const ListenerSubName = ListenerNameList[1] + const id = randomUUID() + let complete = 0 + let retData: Parameters | undefined = undefined + const databack = () => { + if (complete == 0) { + reject(new Error(' ListenerName:' + ListenerName + ' timeout')) + } else { + resolve(retData!) + } + } + const Timeouter = setTimeout(databack, timeout) + const eventCallbak = { + timeout: timeout, + createtime: Date.now(), + checker: checker, + func: (...args: Parameters) => { + complete++ + retData = args + if (complete >= waitTimes) { + clearTimeout(Timeouter) + databack() + } + } + } + if (!this.EventTask.get(ListenerMainName)) { + this.EventTask.set(ListenerMainName, new Map()) + } + if (!(this.EventTask.get(ListenerMainName)?.get(ListenerSubName))) { + this.EventTask.get(ListenerMainName)?.set(ListenerSubName, new Map()) + } + this.EventTask.get(ListenerMainName)?.get(ListenerSubName)?.set(id, eventCallbak) + this.CreatListenerFunction(ListenerMainName) + }) + } + + async CallNormalEvent Promise, ListenerType extends (...args: any[]) => void> + (EventName = '', ListenerName = '', waitTimes = 1, timeout: number = 3000, checker: (...args: Parameters) => boolean, ...args: Parameters) { + return new Promise<[EventRet: Awaited>, ...Parameters]>(async (resolve, reject) => { + const id = randomUUID() + let complete = 0 + let retData: Parameters | undefined = undefined + let retEvent: any = {} + const databack = () => { + if (complete == 0) { + reject(new Error('Timeout: NTEvent EventName:' + EventName + ' ListenerName:' + ListenerName + ' EventRet:\n' + JSON.stringify(retEvent, null, 4) + '\n')) + } else { + resolve([retEvent as Awaited>, ...retData!]) + } + } + + const ListenerNameList = ListenerName.split('/') + const ListenerMainName = ListenerNameList[0] + const ListenerSubName = ListenerNameList[1] + + const Timeouter = setTimeout(databack, timeout) + + const eventCallbak = { + timeout: timeout, + createtime: Date.now(), + checker: checker, + func: (...args: any[]) => { + complete++ + //console.log('func', ...args) + retData = args as Parameters + if (complete >= waitTimes) { + clearTimeout(Timeouter) + databack() + } + } + } + if (!this.EventTask.get(ListenerMainName)) { + this.EventTask.set(ListenerMainName, new Map()) + } + if (!(this.EventTask.get(ListenerMainName)?.get(ListenerSubName))) { + this.EventTask.get(ListenerMainName)?.set(ListenerSubName, new Map()) + } + this.EventTask.get(ListenerMainName)?.get(ListenerSubName)?.set(id, eventCallbak) + this.CreatListenerFunction(ListenerMainName) + const EventFunc = this.CreatEventFunction(EventName) + retEvent = await EventFunc!(...(args as any[])) + }) + } +} + +export const NTEventDispatch = new NTEventWrapper() + +// 示例代码 快速创建事件 +// let NTEvent = new NTEventWrapper() +// let TestEvent = NTEvent.CreatEventFunction<(force: boolean) => Promise>('NodeIKernelProfileLikeService/GetTest') +// if (TestEvent) { +// TestEvent(true) +// } + +// 示例代码 快速创建监听Listener类 +// let NTEvent = new NTEventWrapper() +// NTEvent.CreatListenerFunction('NodeIKernelMsgListener', 'core') + + +// 调用接口 +//let NTEvent = new NTEventWrapper() +//let ret = await NTEvent.CallNormalEvent<(force: boolean) => Promise, (data1: string, data2: number) => void>('NodeIKernelProfileLikeService/GetTest', 'NodeIKernelMsgListener/onAddSendMsg', 1, 3000, true) + +// 注册监听 解除监听 +// NTEventDispatch.RigisterListener('NodeIKernelMsgListener/onAddSendMsg','core',cb) +// NTEventDispatch.UnRigisterListener('NodeIKernelMsgListener/onAddSendMsg','core') + +// let GetTest = NTEventDispatch.CreatEvent('NodeIKernelProfileLikeService/GetTest','NodeIKernelMsgListener/onAddSendMsg',Mode) +// GetTest('test') + +// always模式 +// NTEventDispatch.CreatEvent('NodeIKernelProfileLikeService/GetTest','NodeIKernelMsgListener/onAddSendMsg',Mode,(...args:any[])=>{ console.log(args) }) \ No newline at end of file diff --git a/src/main/main.ts b/src/main/main.ts index 69ac2fe..2dfdea8 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -36,11 +36,8 @@ import { RawMessage, } from '../ntqqapi/types' import { httpHeart, ob11HTTPServer } from '../onebot11/server/http' -import { OB11FriendRecallNoticeEvent } from '../onebot11/event/notice/OB11FriendRecallNoticeEvent' -import { OB11GroupRecallNoticeEvent } from '../onebot11/event/notice/OB11GroupRecallNoticeEvent' import { postOb11Event } from '../onebot11/server/post-ob11-event' import { ob11ReverseWebsockets } from '../onebot11/server/ws/ReverseWebsocket' -import { OB11GroupAdminNoticeEvent } from '../onebot11/event/notice/OB11GroupAdminNoticeEvent' import { OB11GroupRequestEvent } from '../onebot11/event/request/OB11GroupRequest' import { OB11FriendRequestEvent } from '../onebot11/event/request/OB11FriendRequest' import * as path from 'node:path' @@ -48,17 +45,14 @@ import { dbUtil } from '../common/db' import { setConfig } from './setConfig' import { NTQQUserApi } from '../ntqqapi/api/user' import { NTQQGroupApi } from '../ntqqapi/api/group' -import { OB11FriendPokeEvent, OB11GroupPokeEvent } from '../onebot11/event/notice/OB11PokeEvent' import { checkNewVersion, upgradeLLOneBot } from '../common/utils/upgrade' import { log } from '../common/utils/log' import { getConfigUtil } from '../common/config' import { checkFfmpeg } from '../common/utils/video' import { GroupDecreaseSubType, OB11GroupDecreaseEvent } from '../onebot11/event/notice/OB11GroupDecreaseEvent' -import '../ntqqapi/native/wrapper' +import '../ntqqapi/wrapper' import { sentMessages } from '@/ntqqapi/api' -let running = false - let mainWindow: BrowserWindow | null = null // 加载插件时触发 diff --git a/src/ntqqapi/api/friend.ts b/src/ntqqapi/api/friend.ts index cfa2daf..f592eb8 100644 --- a/src/ntqqapi/api/friend.ts +++ b/src/ntqqapi/api/friend.ts @@ -1,8 +1,11 @@ -import { Friend, FriendRequest } from '../types' +import { Friend, FriendRequest, FriendV2 } from '../types' import { ReceiveCmdS } from '../hook' import { callNTQQApi, GeneralCallResult, NTQQApiMethod } from '../ntcall' import { friendRequests } from '../../common/data' import { log } from '../../common/utils' +import { wrapperApi } from '@/ntqqapi/wrapper' +import { BuddyListReqType, NodeIKernelProfileService } from '../services' +import { NTEventDispatch } from '../../common/utils/EventTask' export class NTQQFriendApi { static async getFriends(forced = false) { @@ -26,6 +29,7 @@ export class NTQQFriendApi { } return _friends } + static async likeFriend(uid: string, count = 1) { return await callNTQQApi({ methodName: NTQQApiMethod.LIKE_FRIEND, @@ -42,6 +46,7 @@ export class NTQQFriendApi { ], }) } + static async handleFriendRequest(flag: string, accept: boolean) { const request: FriendRequest = friendRequests[flag] if (!request) { @@ -62,4 +67,15 @@ export class NTQQFriendApi { delete friendRequests[flag] return result } + + static async getBuddyV2(refresh = false): Promise { + const uids: string[] = [] + const buddyService = wrapperApi.NodeIQQNTWrapperSession.getBuddyService() + const buddyListV2 = refresh ? await buddyService.getBuddyListV2('0', BuddyListReqType.KNOMAL) : await buddyService.getBuddyListV2('0', BuddyListReqType.KNOMAL) + uids.push(...buddyListV2.data.flatMap(item => item.buddyUids)) + const data = await NTEventDispatch.CallNoListenerEvent( + 'NodeIKernelProfileService/getCoreAndBaseInfo', 5000, 'nodeStore', uids + ) + return Array.from(data.values()) + } } diff --git a/src/ntqqapi/api/group.ts b/src/ntqqapi/api/group.ts index 6617dcc..1d2a26e 100644 --- a/src/ntqqapi/api/group.ts +++ b/src/ntqqapi/api/group.ts @@ -5,7 +5,7 @@ import { deleteGroup, uidMaps } from '../../common/data' import { dbUtil } from '../../common/db' import { log } from '../../common/utils/log' import { NTQQWindowApi, NTQQWindows } from './window' -import { wrapperApi } from '../native/wrapper' +import { wrapperApi } from '../wrapper' export class NTQQGroupApi { diff --git a/src/ntqqapi/native/wrapper.ts b/src/ntqqapi/native/wrapper.ts deleted file mode 100644 index 8596de2..0000000 --- a/src/ntqqapi/native/wrapper.ts +++ /dev/null @@ -1,19 +0,0 @@ -let Process = require('process') -let os = require('os') -Process.dlopenOrig = Process.dlopen - -export const wrapperApi: any = {} - -Process.dlopen = function(module, filename, flags = os.constants.dlopen.RTLD_LAZY) { - let dlopenRet = this.dlopenOrig(module, filename, flags) - for (let export_name in module.exports) { - module.exports[export_name] = new Proxy(module.exports[export_name], { - construct: (target, args, _newTarget) => { - let ret = new target(...args) - if (export_name === 'NodeIQQNTWrapperSession') wrapperApi.NodeIQQNTWrapperSession = ret - return ret - }, - }) - } - return dlopenRet -} diff --git a/src/ntqqapi/services/NodeIKernelBuddyService.ts b/src/ntqqapi/services/NodeIKernelBuddyService.ts new file mode 100644 index 0000000..89c5cd6 --- /dev/null +++ b/src/ntqqapi/services/NodeIKernelBuddyService.ts @@ -0,0 +1,125 @@ +import { GeneralCallResult } from './common' + +export enum BuddyListReqType { + KNOMAL, + KLETTER +} + +export interface NodeIKernelBuddyService { + // 26702 以上 + getBuddyListV2(callFrom: string, reqType: BuddyListReqType): Promise + }> + }> + + //26702 以上 + getBuddyListFromCache(callFrom: string): Promise//Uids + }>> + + addKernelBuddyListener(listener: any): number + + getAllBuddyCount(): number + + removeKernelBuddyListener(listener: unknown): void + + getBuddyList(nocache: boolean): Promise + + getBuddyNick(uid: number): string + + getBuddyRemark(uid: number): string + + setBuddyRemark(uid: number, remark: string): void + + getAvatarUrl(uid: number): string + + isBuddy(uid: string): boolean + + getCategoryNameWithUid(uid: number): string + + getTargetBuddySetting(uid: number): unknown + + getTargetBuddySettingByType(uid: number, type: number): unknown + + getBuddyReqUnreadCnt(): number + + getBuddyReq(): unknown + + delBuddyReq(uid: number): void + + clearBuddyReqUnreadCnt(): void + + reqToAddFriends(uid: number, msg: string): void + + setSpacePermission(uid: number, permission: number): void + + approvalFriendRequest(arg: { + friendUid: string + reqTime: string + accept: boolean + }): Promise + + delBuddy(uid: number): void + + delBatchBuddy(uids: number[]): void + + getSmartInfos(uid: number): unknown + + setBuddyCategory(uid: number, category: number): void + + setBatchBuddyCategory(uids: number[], category: number): void + + addCategory(category: string): void + + delCategory(category: string): void + + renameCategory(oldCategory: string, newCategory: string): void + + resortCategory(categorys: string[]): void + + pullCategory(uid: number, category: string): void + + setTop(uid: number, isTop: boolean): void + + SetSpecialCare(uid: number, isSpecialCare: boolean): void + + setMsgNotify(uid: number, isNotify: boolean): void + + hasBuddyList(): boolean + + setBlock(uid: number, isBlock: boolean): void + + isBlocked(uid: number): boolean + + modifyAddMeSetting(setting: unknown): void + + getAddMeSetting(): unknown + + getDoubtBuddyReq(): unknown + + getDoubtBuddyUnreadNum(): number + + approvalDoubtBuddyReq(uid: number, isAgree: boolean): void + + delDoubtBuddyReq(uid: number): void + + delAllDoubtBuddyReq(): void + + reportDoubtBuddyReqUnread(): void + + getBuddyRecommendContactArkJson(uid: string, phoneNumber: string): Promise + + isNull(): boolean +} \ No newline at end of file diff --git a/src/ntqqapi/services/NodeIKernelProfileService.ts b/src/ntqqapi/services/NodeIKernelProfileService.ts new file mode 100644 index 0000000..95a3425 --- /dev/null +++ b/src/ntqqapi/services/NodeIKernelProfileService.ts @@ -0,0 +1,106 @@ +import { AnyCnameRecord } from 'node:dns' +import { SimpleInfo } from '../types' +import { GeneralCallResult } from './common' + +export enum UserDetailSource { + KDB, + KSERVER +} + +export enum ProfileBizType { + KALL, + KBASEEXTEND, + KVAS, + KQZONE, + KOTHER +} + +export interface NodeIKernelProfileService { + getUidByUin(callfrom: string, uin: Array): Promise>//uin->uid + + getUinByUid(callfrom: string, uid: Array): Promise> + + // { + // coreInfo: CoreInfo, + // baseInfo: BaseInfo, + // status: null, + // vasInfo: null, + // relationFlags: null, + // otherFlags: null, + // intimate: null + // } + getCoreAndBaseInfo(callfrom: string, uids: string[]): Promise> + + fetchUserDetailInfo(trace: string, uids: string[], arg2: number, arg3: number[]): Promise + + addKernelProfileListener(listener: any): number + + removeKernelProfileListener(listenerId: number): void + + prepareRegionConfig(...args: unknown[]): unknown + + getLocalStrangerRemark(): Promise + + enumCountryOptions(): Array + + enumProvinceOptions(Country: string): Array + + enumCityOptions(Country: string, Province: string): unknown + + enumAreaOptions(...args: unknown[]): unknown + + //SimpleInfo + // this.uid = "" + // this.uid = str + // this.uin = j2 + // this.isBuddy = z + // this.coreInfo = coreInfo + // this.baseInfo = baseInfo + // this.status = statusInfo + // this.vasInfo = vasInfo + // this.relationFlags = relationFlag + // this.otherFlags = otherFlag + // this.intimate = intimate + + modifySelfProfile(...args: unknown[]): Promise + + modifyDesktopMiniProfile(param: any): Promise + + setNickName(NickName: string): Promise + + setLongNick(longNick: string): Promise + + setBirthday(...args: unknown[]): Promise + + setGander(...args: unknown[]): Promise + + setHeader(arg: string): Promise + + setRecommendImgFlag(...args: unknown[]): Promise + + getUserSimpleInfo(force: boolean, uids: string[],): Promise + + getUserDetailInfo(uid: string): Promise + + getUserDetailInfoWithBizInfo(uid: string, Biz: any[]): Promise + + getUserDetailInfoByUin(uin: string): Promise + + getZplanAvatarInfos(args: string[]): Promise + + getStatus(uid: string): Promise + + startStatusPolling(isForceReset: boolean): Promise + + getSelfStatus(): Promise + + setdisableEmojiShortCuts(...args: unknown[]): unknown + + getProfileQzonePicInfo(uid: string, type: number, force: boolean): Promise + + //profileService.getCoreInfo("UserRemarkServiceImpl::getStrangerRemarkByUid", arrayList) + getCoreInfo(name: string, arg: any[]): unknown + + //m429253e12.getOtherFlag("FriendListInfoCache_getKernelDataAndPutCache", new ArrayList<>()) + isNull(): boolean +} \ No newline at end of file diff --git a/src/ntqqapi/services/common.ts b/src/ntqqapi/services/common.ts new file mode 100644 index 0000000..0e56914 --- /dev/null +++ b/src/ntqqapi/services/common.ts @@ -0,0 +1,16 @@ +export enum GeneralCallResultStatus { + OK = 0 + // ERROR = 1 +} + +export interface GeneralCallResult { + result: GeneralCallResultStatus + errMsg: string +} + +export interface forceFetchClientKeyRetType extends GeneralCallResult { + url: string + keyIndex: string + clientKey: string + expireTime: string +} \ No newline at end of file diff --git a/src/ntqqapi/services/index.ts b/src/ntqqapi/services/index.ts new file mode 100644 index 0000000..3e5969f --- /dev/null +++ b/src/ntqqapi/services/index.ts @@ -0,0 +1,2 @@ +export * from './NodeIKernelBuddyService' +export * from './NodeIKernelProfileService' \ No newline at end of file diff --git a/src/ntqqapi/types/user.ts b/src/ntqqapi/types/user.ts index 5fbe4b8..b879034 100644 --- a/src/ntqqapi/types/user.ts +++ b/src/ntqqapi/types/user.ts @@ -77,8 +77,148 @@ export interface Friend extends User { } export interface CategoryFriend { - categoryId: number; - categroyName: string; - categroyMbCount: number; + categoryId: number + categroyName: string + categroyMbCount: number buddyList: User[] +} + +export interface CoreInfo { + uid: string + uin: string + nick: string + remark: string +} + +export interface BaseInfo { + qid: string + longNick: string + birthday_year: number + birthday_month: number + birthday_day: number + age: number + sex: number + eMail: string + phoneNum: string + categoryId: number + richTime: number + richBuffer: string +} + +interface MusicInfo { + buf: string +} + +interface VideoBizInfo { + cid: string + tvUrl: string + synchType: string +} + +interface VideoInfo { + name: string +} + +interface ExtOnlineBusinessInfo { + buf: string + customStatus: any + videoBizInfo: VideoBizInfo + videoInfo: VideoInfo +} + +interface ExtBuffer { + buf: string +} + +interface UserStatus { + uid: string + uin: string + status: number + extStatus: number + batteryStatus: number + termType: number + netType: number + iconType: number + customStatus: any + setTime: string + specialFlag: number + abiFlag: number + eNetworkType: number + showName: string + termDesc: string + musicInfo: MusicInfo + extOnlineBusinessInfo: ExtOnlineBusinessInfo + extBuffer: ExtBuffer +} + +interface PrivilegeIcon { + jumpUrl: string + openIconList: any[] + closeIconList: any[] +} + +interface VasInfo { + vipFlag: boolean + yearVipFlag: boolean + svipFlag: boolean + vipLevel: number + bigClub: boolean + bigClubLevel: number + nameplateVipType: number + grayNameplateFlag: number + superVipTemplateId: number + diyFontId: number + pendantId: number + pendantDiyId: number + faceId: number + vipFont: number + vipFontType: number + magicFont: number + fontEffect: number + newLoverDiamondFlag: number + extendNameplateId: number + diyNameplateIDs: any[] + vipStartFlag: number + vipDataFlag: number + gameNameplateId: string + gameLastLoginTime: string + gameRank: number + gameIconShowFlag: boolean + gameCardId: string + vipNameColorId: string + privilegeIcon: PrivilegeIcon +} + +export interface SimpleInfo { + uid?: string + uin?: string + coreInfo: CoreInfo + baseInfo: BaseInfo + status: UserStatus | null + vasInfo: VasInfo | null + relationFlags: RelationFlags | null + otherFlags: any | null + intimate: any | null +} + +interface RelationFlags { + topTime: string + isBlock: boolean + isMsgDisturb: boolean + isSpecialCareOpen: boolean + isSpecialCareZone: boolean + ringId: string + isBlocked: boolean + recommendImgFlag: number + disableEmojiShortCuts: number + qidianMasterFlag: number + qidianCrewFlag: number + qidianCrewFlag2: number + isHideQQLevel: number + isHidePrivilegeIcon: number +} + +export interface FriendV2 extends SimpleInfo { + categoryId?: number + categroyName?: string } \ No newline at end of file diff --git a/src/ntqqapi/wrapper.ts b/src/ntqqapi/wrapper.ts new file mode 100644 index 0000000..7ca8ef7 --- /dev/null +++ b/src/ntqqapi/wrapper.ts @@ -0,0 +1,26 @@ +import path from 'node:path' +import fs from 'node:fs' +import { qqPkgInfo } from '../common/utils/QQBasicInfo' +import { NodeIKernelBuddyService } from './services/NodeIKernelBuddyService' + +export interface NodeIQQNTWrapperSession { + [key: string]: any + new(): NodeIQQNTWrapperSession + getBuddyService(): NodeIKernelBuddyService +} + +export interface WrapperNodeApi { + [key: string]: any + NodeIQQNTWrapperSession: NodeIQQNTWrapperSession +} + +let wrapperNodePath = path.resolve(path.dirname(process.execPath), './resources/app/wrapper.node') +if (!fs.existsSync(wrapperNodePath)) { + wrapperNodePath = path.join(path.dirname(process.execPath), `resources/app/versions/${qqPkgInfo.version}/wrapper.node`) +} +const nativemodule: any = { exports: {} } +process.dlopen(nativemodule, wrapperNodePath) +const wrapperApi: WrapperNodeApi = nativemodule.exports + +export { wrapperApi } +export default wrapperApi \ No newline at end of file diff --git a/src/onebot11/action/user/GetFriendList.ts b/src/onebot11/action/user/GetFriendList.ts index deab4b9..bab6ea2 100644 --- a/src/onebot11/action/user/GetFriendList.ts +++ b/src/onebot11/action/user/GetFriendList.ts @@ -5,6 +5,7 @@ import BaseAction from '../BaseAction' import { ActionName } from '../types' import { NTQQFriendApi } from '@/ntqqapi/api' import { CategoryFriend } from '@/ntqqapi/types' +import { qqPkgInfo } from '@/common/utils/QQBasicInfo' interface Payload { no_cache: boolean | string @@ -14,6 +15,9 @@ export class GetFriendList extends BaseAction { actionName = ActionName.GetFriendList protected async _handle(payload: Payload) { + if (+qqPkgInfo.buildVersion >= 26702) { + return OB11Constructor.friendsV2(await NTQQFriendApi.getBuddyV2(payload?.no_cache === true || payload?.no_cache === 'true')) + } if (friends.length === 0 || payload?.no_cache === true || payload?.no_cache === 'true') { const _friends = await NTQQFriendApi.getFriends(true) // log('强制刷新好友列表,结果: ', _friends) diff --git a/src/onebot11/constructor.ts b/src/onebot11/constructor.ts index df39b01..d740355 100644 --- a/src/onebot11/constructor.ts +++ b/src/onebot11/constructor.ts @@ -24,6 +24,7 @@ import { TipGroupElementType, User, VideoElement, + FriendV2 } from '../ntqqapi/types' import { deleteGroup, getFriend, getGroupMember, selfInfo, tempGroupCodeMap, uidMaps } from '../common/data' import { EventType } from './event/OB11BaseEvent' @@ -50,8 +51,8 @@ import { OB11FriendAddNoticeEvent } from './event/notice/OB11FriendAddNoticeEven import { OB11FriendRecallNoticeEvent } from './event/notice/OB11FriendRecallNoticeEvent' import { OB11GroupRecallNoticeEvent } from './event/notice/OB11GroupRecallNoticeEvent' import { OB11FriendPokeEvent, OB11GroupPokeEvent } from './event/notice/OB11PokeEvent' -import { OB11BaseNoticeEvent } from './event/notice/OB11BaseNoticeEvent'; -import { OB11GroupEssenceEvent } from './event/notice/OB11GroupEssenceEvent'; +import { OB11BaseNoticeEvent } from './event/notice/OB11BaseNoticeEvent' +import { OB11GroupEssenceEvent } from './event/notice/OB11GroupEssenceEvent' let lastRKeyUpdateTime = 0 @@ -259,9 +260,9 @@ export class OB11Constructor { // log("收到语音消息", msg) // window.LLAPI.Ptt2Text(message.raw.msgId, message.peer, messages).then(text => { - // console.log("语音转文字结果", text); + // console.log("语音转文字结果", text) // }).catch(err => { - // console.log("语音转文字失败", err); + // console.log("语音转文字失败", err) // }) } else if (element.arkElement) { @@ -322,20 +323,20 @@ export class OB11Constructor { static async PrivateEvent(msg: RawMessage): Promise { if (msg.chatType !== ChatType.friend) { - return; + 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); + const json = JSON.parse(element.grayTipElement.jsonGrayTipElement.jsonStr) if (element.grayTipElement.jsonGrayTipElement.busiId == 1061) { //判断业务类型 //Poke事件 - const pokedetail: any[] = json.items; + const pokedetail: any[] = json.items //筛选item带有uid的元素 - const poke_uid = pokedetail.filter(item => item.uid); + const poke_uid = pokedetail.filter(item => item.uid) if (poke_uid.length == 2) { - return new OB11FriendPokeEvent(parseInt((uidMaps[poke_uid[0].uid])!), parseInt((uidMaps[poke_uid[1].uid])), pokedetail); + return new OB11FriendPokeEvent(parseInt((uidMaps[poke_uid[0].uid])!), parseInt((uidMaps[poke_uid[1].uid])), pokedetail) } } //下面得改 上面也是错的grayTipElement.subElementType == GrayTipElementSubType.MEMBER_NEW_TITLE @@ -366,7 +367,7 @@ export class OB11Constructor { return event } } - // log("group msg", msg); + // log("group msg", msg) for (let element of msg.elements) { const grayTipElement = element.grayTipElement const groupElement = grayTipElement?.groupElement @@ -536,32 +537,32 @@ export class OB11Constructor { if (grayTipElement.jsonGrayTipElement.busiId == 1061) { //判断业务类型 //Poke事件 - const pokedetail: any[] = json.items; + const pokedetail: any[] = json.items //筛选item带有uid的元素 - const poke_uid = pokedetail.filter(item => item.uid); + const poke_uid = pokedetail.filter(item => item.uid) if (poke_uid.length == 2) { - return new OB11GroupPokeEvent(parseInt(msg.peerUid), parseInt((uidMaps[poke_uid[0].uid])!), parseInt((uidMaps[poke_uid[1].uid])), pokedetail); + return new OB11GroupPokeEvent(parseInt(msg.peerUid), parseInt((uidMaps[poke_uid[0].uid])!), parseInt((uidMaps[poke_uid[1].uid])), pokedetail) } } if (grayTipElement.jsonGrayTipElement.busiId == 2401) { log('收到群精华消息', json) - 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 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! - }; - let msgList = (await NTQQMsgApi.getMsgsBySeqAndCount(Peer, msgSeq.toString(), 1, true, true)).msgList; - const origMsg = await dbUtil.getMsgByLongId(msgList[0].msgId); - const postMsg = await dbUtil.getMsgBySeqId(origMsg.msgSeq) ?? origMsg; + } + let msgList = (await NTQQMsgApi.getMsgsBySeqAndCount(Peer, msgSeq.toString(), 1, true, true)).msgList + const origMsg = await dbUtil.getMsgByLongId(msgList[0].msgId) + const postMsg = await dbUtil.getMsgBySeqId(origMsg.msgSeq) ?? origMsg // 如果 senderUin 为 0,可能是 历史消息 或 自身消息 if (msgList[0].senderUin === '0') { - msgList[0].senderUin = postMsg?.senderUin ?? selfInfo.uin; + msgList[0].senderUin = postMsg?.senderUin ?? selfInfo.uin } - return new OB11GroupEssenceEvent(parseInt(msg.peerUid), postMsg.msgShortId, parseInt(msgList[0].senderUin)); + return new OB11GroupEssenceEvent(parseInt(msg.peerUid), postMsg.msgShortId, parseInt(msgList[0].senderUin)) // 获取MsgSeq+Peer可获取具体消息 } if (grayTipElement.jsonGrayTipElement.busiId == 2407) { @@ -625,6 +626,25 @@ export class OB11Constructor { return friends.map(OB11Constructor.friend) } + static friendsV2(friends: FriendV2[]): OB11User[] { + const data: OB11User[] = [] + for (const friend of friends) { + 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 groupMemberRole(role: number): OB11GroupMemberRole | undefined { return { 4: OB11GroupMemberRole.owner, diff --git a/src/onebot11/types.ts b/src/onebot11/types.ts index 90a9f3c..37a1b5b 100644 --- a/src/onebot11/types.ts +++ b/src/onebot11/types.ts @@ -11,6 +11,8 @@ export interface OB11User { age?: number qid?: string login_days?: number + categroyName?: string + categoryId?: number } export enum OB11UserSex { From 808777c04474cd0ef769b2eec8ffa941843e33b5 Mon Sep 17 00:00:00 2001 From: idranme Date: Mon, 5 Aug 2024 19:18:15 +0800 Subject: [PATCH 2/4] fix: import path --- manifest.json | 2 +- src/ntqqapi/api/file.ts | 4 ++-- src/ntqqapi/api/msg.ts | 2 +- src/ntqqapi/api/user.ts | 4 +--- src/ntqqapi/types/msg.ts | 7 +++---- 5 files changed, 8 insertions(+), 11 deletions(-) diff --git a/manifest.json b/manifest.json index f6e8266..30ce472 100644 --- a/manifest.json +++ b/manifest.json @@ -3,7 +3,7 @@ "type": "extension", "name": "LLOneBot", "slug": "LLOneBot", - "description": "实现 OneBot 11 协议,帮助进行 QQ 机器人开发", + "description": "实现 OneBot 11 协议,用以 QQ 机器人开发", "version": "3.27.4", "icon": "./icon.webp", "authors": [ diff --git a/src/ntqqapi/api/file.ts b/src/ntqqapi/api/file.ts index 8776e2c..0a05f22 100644 --- a/src/ntqqapi/api/file.ts +++ b/src/ntqqapi/api/file.ts @@ -16,8 +16,8 @@ import fs from 'fs' import { ReceiveCmdS } from '../hook' import { log } from '@/common/utils' import { rkeyManager } from '@/ntqqapi/api/rkey' -import { wrapperApi } from '@/ntqqapi/native/wrapper' -import { Peer } from '@/ntqqapi/api/msg' +import { wrapperApi } from '@/ntqqapi/wrapper' +import { Peer } from '@/ntqqapi/types/msg' export class NTQQFileApi { static async getVideoUrl(peer: Peer, msgId: string, elementId: string): Promise { diff --git a/src/ntqqapi/api/msg.ts b/src/ntqqapi/api/msg.ts index 1bd2da8..b5619e7 100644 --- a/src/ntqqapi/api/msg.ts +++ b/src/ntqqapi/api/msg.ts @@ -6,7 +6,7 @@ import { ReceiveCmdS, registerReceiveHook } from '../hook' import { log } from '../../common/utils/log' import { sleep } from '../../common/utils/helper' import { isQQ998 } from '../../common/utils' -import { wrapperApi } from '@/ntqqapi/native/wrapper' +import { wrapperApi } from '@/ntqqapi/wrapper' export let sendMessagePool: Record void) | null> = {} // peerUid: callbackFunc diff --git a/src/ntqqapi/api/user.ts b/src/ntqqapi/api/user.ts index 90dc84a..010d15a 100644 --- a/src/ntqqapi/api/user.ts +++ b/src/ntqqapi/api/user.ts @@ -2,10 +2,8 @@ import { callNTQQApi, GeneralCallResult, NTQQApiClass, NTQQApiMethod } from '../ import { Group, SelfInfo, User } from '../types' import { ReceiveCmdS } from '../hook' import { selfInfo, uidMaps } from '../../common/data' -import { NTQQWindowApi, NTQQWindows } from './window' import { cacheFunc, isQQ998, log, sleep } from '../../common/utils' -import { wrapperApi } from '@/ntqqapi/native/wrapper' -import * as https from 'https' +import { wrapperApi } from '@/ntqqapi/wrapper' import { RequestUtil } from '@/common/utils/request' let userInfoCache: Record = {} // uid: User diff --git a/src/ntqqapi/types/msg.ts b/src/ntqqapi/types/msg.ts index e50295f..c4c3c2f 100644 --- a/src/ntqqapi/types/msg.ts +++ b/src/ntqqapi/types/msg.ts @@ -1,5 +1,4 @@ import { GroupMemberRole } from './group' -import exp from 'constants' export enum ElementType { TEXT = 1, @@ -417,7 +416,7 @@ export interface RawMessage { } export interface Peer { - chatType: ChatType; - peerUid: string; // 如果是群聊uid为群号,私聊uid就是加密的字符串 - guildId?: string; + chatType: ChatType + peerUid: string // 如果是群聊uid为群号,私聊uid就是加密的字符串 + guildId?: string } \ No newline at end of file From 72eb01337146e87b5841564b767163b766520c45 Mon Sep 17 00:00:00 2001 From: idranme Date: Mon, 5 Aug 2024 20:44:28 +0800 Subject: [PATCH 3/4] fix --- src/ntqqapi/api/file.ts | 8 ++++-- src/ntqqapi/api/friend.ts | 4 +-- src/ntqqapi/api/group.ts | 59 ++++++++++++++++++++++++++------------- src/ntqqapi/api/msg.ts | 3 +- src/ntqqapi/api/rkey.ts | 53 +++++++++++++++++++---------------- src/ntqqapi/api/user.ts | 9 +++--- src/ntqqapi/api/webapi.ts | 23 +++++++++++---- src/ntqqapi/wrapper.ts | 33 ++++++++++++---------- 8 files changed, 119 insertions(+), 73 deletions(-) diff --git a/src/ntqqapi/api/file.ts b/src/ntqqapi/api/file.ts index 0a05f22..6a539fd 100644 --- a/src/ntqqapi/api/file.ts +++ b/src/ntqqapi/api/file.ts @@ -11,8 +11,8 @@ import { IMAGE_HTTP_HOST, IMAGE_HTTP_HOST_NT, PicElement, } from '../types' -import path from 'path' -import fs from 'fs' +import path from 'node:path' +import fs from 'node:fs' import { ReceiveCmdS } from '../hook' import { log } from '@/common/utils' import { rkeyManager } from '@/ntqqapi/api/rkey' @@ -21,12 +21,14 @@ import { Peer } from '@/ntqqapi/types/msg' export class NTQQFileApi { static async getVideoUrl(peer: Peer, msgId: string, elementId: string): Promise { - return (await wrapperApi.NodeIQQNTWrapperSession.getRichMediaService().getVideoPlayUrlV2(peer, + const session = wrapperApi.NodeIQQNTWrapperSession + return (await session.getRichMediaService().getVideoPlayUrlV2(peer, msgId, elementId, 0, { downSourceType: 1, triggerType: 1 })).urlResult?.domainUrl[0]?.url; } + static async getFileType(filePath: string) { return await callNTQQApi<{ ext: string }>({ className: NTQQApiClass.FS_API, diff --git a/src/ntqqapi/api/friend.ts b/src/ntqqapi/api/friend.ts index f592eb8..2e3a5ec 100644 --- a/src/ntqqapi/api/friend.ts +++ b/src/ntqqapi/api/friend.ts @@ -2,7 +2,6 @@ import { Friend, FriendRequest, FriendV2 } from '../types' import { ReceiveCmdS } from '../hook' import { callNTQQApi, GeneralCallResult, NTQQApiMethod } from '../ntcall' import { friendRequests } from '../../common/data' -import { log } from '../../common/utils' import { wrapperApi } from '@/ntqqapi/wrapper' import { BuddyListReqType, NodeIKernelProfileService } from '../services' import { NTEventDispatch } from '../../common/utils/EventTask' @@ -70,7 +69,8 @@ export class NTQQFriendApi { static async getBuddyV2(refresh = false): Promise { const uids: string[] = [] - const buddyService = wrapperApi.NodeIQQNTWrapperSession.getBuddyService() + const session = wrapperApi.NodeIQQNTWrapperSession + const buddyService = session.getBuddyService() const buddyListV2 = refresh ? await buddyService.getBuddyListV2('0', BuddyListReqType.KNOMAL) : await buddyService.getBuddyListV2('0', BuddyListReqType.KNOMAL) uids.push(...buddyListV2.data.flatMap(item => item.buddyUids)) const data = await NTEventDispatch.CallNoListenerEvent( diff --git a/src/ntqqapi/api/group.ts b/src/ntqqapi/api/group.ts index 1d2a26e..a2cf620 100644 --- a/src/ntqqapi/api/group.ts +++ b/src/ntqqapi/api/group.ts @@ -8,22 +8,23 @@ import { NTQQWindowApi, NTQQWindows } from './window' import { wrapperApi } from '../wrapper' export class NTQQGroupApi { - - static async activateMemberListChange(){ + static async activateMemberListChange() { return await callNTQQApi({ methodName: NTQQApiMethod.ACTIVATE_MEMBER_LIST_CHANGE, classNameIsRegister: true, args: [], }) } - static async activateMemberInfoChange(){ + + static async activateMemberInfoChange() { return await callNTQQApi({ methodName: NTQQApiMethod.ACTIVATE_MEMBER_INFO_CHANGE, classNameIsRegister: true, args: [], }) } - static async getGroupAllInfo(groupCode: string, source: number=4){ + + static async getGroupAllInfo(groupCode: string, source: number = 4) { return await callNTQQApi({ methodName: NTQQApiMethod.GET_GROUP_ALL_INFO, args: [ @@ -35,6 +36,7 @@ export class NTQQGroupApi { ], }) } + static async getGroups(forced = false) { // let cbCmd = ReceiveCmdS.GROUPS // if (process.platform != 'win32') { @@ -52,6 +54,7 @@ export class NTQQGroupApi { log('get groups result', result) return result.groupList } + static async getGroupMembers(groupQQ: string, num = 3000): Promise { const sceneId = await callNTQQApi({ methodName: NTQQApiMethod.GROUP_MEMBER_SCENE, @@ -62,7 +65,7 @@ export class NTQQGroupApi { }, ], }) - // log("get group member sceneId", sceneId); + // log("get group member sceneId", sceneId) try { const result = await callNTQQApi<{ result: { infos: any } @@ -83,8 +86,8 @@ export class NTQQGroupApi { for (const member of members) { uidMaps[member.uid] = member.uin } - // log(uidMaps); - // log("members info", values); + // log(uidMaps) + // log("members info", values) log(`get group ${groupQQ} members success`) return members } catch (e) { @@ -92,7 +95,8 @@ export class NTQQGroupApi { return [] } } - static async getGroupMembersInfo(groupCode: string, uids: string[], forceUpdate: boolean=false) { + + static async getGroupMembersInfo(groupCode: string, uids: string[], forceUpdate: boolean = false) { return await callNTQQApi({ methodName: NTQQApiMethod.GROUP_MEMBERS_INFO, args: [ @@ -105,6 +109,7 @@ export class NTQQGroupApi { ], }) } + static async getGroupNotifies() { // 获取管理员变更 // 加群通知,退出通知,需要管理员权限 @@ -119,6 +124,7 @@ export class NTQQGroupApi { args: [{ doubt: false, startSeq: '', number: 14 }, null], }) } + static async getGroupIgnoreNotifies() { await NTQQGroupApi.getGroupNotifies() return await NTQQWindowApi.openWindow( @@ -127,12 +133,13 @@ export class NTQQGroupApi { ReceiveCmdS.GROUP_NOTIFY, ) } + static async handleGroupRequest(seq: string, operateType: GroupRequestOperateTypes, reason?: string) { const notify: GroupNotify = await dbUtil.getGroupNotify(seq) if (!notify) { throw `${seq}对应的加群通知不存在` } - // delete groupNotifies[seq]; + // delete groupNotifies[seq] return await callNTQQApi({ methodName: NTQQApiMethod.HANDLE_GROUP_REQUEST, args: [ @@ -152,6 +159,7 @@ export class NTQQGroupApi { ], }) } + static async quitGroup(groupQQ: string) { const result = await callNTQQApi({ methodName: NTQQApiMethod.QUIT_GROUP, @@ -162,6 +170,7 @@ export class NTQQGroupApi { } return result } + static async kickMember( groupQQ: string, kickUids: string[], @@ -180,7 +189,8 @@ export class NTQQGroupApi { ], }) } - static async banMember(groupQQ: string, memList: Array<{ uid: string; timeStamp: number }>) { + + static async banMember(groupQQ: string, memList: Array<{ uid: string, timeStamp: number }>) { // timeStamp为秒数, 0为解除禁言 return await callNTQQApi({ methodName: NTQQApiMethod.MUTE_MEMBER, @@ -192,6 +202,7 @@ export class NTQQGroupApi { ], }) } + static async banGroup(groupQQ: string, shutUp: boolean) { return await callNTQQApi({ methodName: NTQQApiMethod.MUTE_GROUP, @@ -204,6 +215,7 @@ export class NTQQGroupApi { ], }) } + static async setMemberCard(groupQQ: string, memberUid: string, cardName: string) { NTQQGroupApi.activateMemberListChange().then().catch(log) const res = await callNTQQApi({ @@ -218,8 +230,9 @@ export class NTQQGroupApi { ], }) NTQQGroupApi.getGroupMembersInfo(groupQQ, [memberUid], true).then().catch(log) - return res; + return res } + static async setMemberRole(groupQQ: string, memberUid: string, role: GroupMemberRole) { return await callNTQQApi({ methodName: NTQQApiMethod.SET_MEMBER_ROLE, @@ -233,6 +246,7 @@ export class NTQQGroupApi { ], }) } + static async setGroupName(groupQQ: string, groupName: string) { return await callNTQQApi({ methodName: NTQQApiMethod.SET_GROUP_NAME, @@ -282,29 +296,34 @@ export class NTQQGroupApi { ], }) } - static publishGroupBulletin(groupQQ: string, title: string, content: string) {} + + static publishGroupBulletin(groupQQ: string, title: string, content: string) { } + static async removeGroupEssence(GroupCode: string, msgId: string) { + const session = wrapperApi.NodeIQQNTWrapperSession // 代码没测过 // 需要 ob11msgid->msgId + (peer) -> msgSeq + msgRandom - let MsgData = await wrapperApi.NodeIQQNTWrapperSession.getMsgService().getMsgsIncludeSelf({ chatType: 2, guildId: '', peerUid: GroupCode }, msgId, 1, false); + let MsgData = await session.getMsgService().getMsgsIncludeSelf({ chatType: 2, guildId: '', peerUid: GroupCode }, msgId, 1, false) let param = { groupCode: GroupCode, msgRandom: parseInt(MsgData.msgList[0].msgRandom), msgSeq: parseInt(MsgData.msgList[0].msgSeq) - }; - // GetMsgByShoretID(ShoretID); -> MsgService.getMsgs(Peer,MsgId,1,false); -> 组出参数 - return wrapperApi.NodeIQQNTWrapperSession.getGroupService().removeGroupEssence(param); + } + // GetMsgByShoretID(ShoretID) -> MsgService.getMsgs(Peer,MsgId,1,false) -> 组出参数 + return session.getGroupService().removeGroupEssence(param) } + static async addGroupEssence(GroupCode: string, msgId: string) { + const session = wrapperApi.NodeIQQNTWrapperSession // 代码没测过 // 需要 ob11msgid->msgId + (peer) -> msgSeq + msgRandom - let MsgData = await wrapperApi.NodeIQQNTWrapperSession.getMsgService().getMsgsIncludeSelf({ chatType: 2, guildId: '', peerUid: GroupCode }, msgId, 1, false); + let MsgData = await session.getMsgService().getMsgsIncludeSelf({ chatType: 2, guildId: '', peerUid: GroupCode }, msgId, 1, false) let param = { groupCode: GroupCode, msgRandom: parseInt(MsgData.msgList[0].msgRandom), msgSeq: parseInt(MsgData.msgList[0].msgSeq) - }; - // GetMsgByShoretID(ShoretID); -> MsgService.getMsgs(Peer,MsgId,1,false); -> 组出参数 - return wrapperApi.NodeIQQNTWrapperSession.getGroupService().addGroupEssence(param); + } + // GetMsgByShoretID(ShoretID) -> MsgService.getMsgs(Peer,MsgId,1,false) -> 组出参数 + return session.getGroupService().addGroupEssence(param) } } diff --git a/src/ntqqapi/api/msg.ts b/src/ntqqapi/api/msg.ts index b5619e7..ae19a0a 100644 --- a/src/ntqqapi/api/msg.ts +++ b/src/ntqqapi/api/msg.ts @@ -289,6 +289,7 @@ export class NTQQMsgApi { }) } static async getMsgsBySeqAndCount(peer: Peer, seq: string, count: number, desc: boolean, z: boolean) { - return await wrapperApi.NodeIQQNTWrapperSession.getMsgService().getMsgsBySeqAndCount(peer, seq, count, desc, z); + const session = wrapperApi.NodeIQQNTWrapperSession + return await session.getMsgService().getMsgsBySeqAndCount(peer, seq, count, desc, z); } } diff --git a/src/ntqqapi/api/rkey.ts b/src/ntqqapi/api/rkey.ts index 25ebbaf..c3a59cf 100644 --- a/src/ntqqapi/api/rkey.ts +++ b/src/ntqqapi/api/rkey.ts @@ -2,58 +2,63 @@ import { log } from '@/common/utils' -interface ServerRkeyData{ - group_rkey: string; - private_rkey: string; - expired_time: number; +interface ServerRkeyData { + group_rkey: string + private_rkey: string + expired_time: number } class RkeyManager { - serverUrl: string = ''; + serverUrl: string = '' private rkeyData: ServerRkeyData = { group_rkey: '', private_rkey: '', expired_time: 0 - }; - constructor(serverUrl: string) { - this.serverUrl = serverUrl; } - async getRkey(){ + + constructor(serverUrl: string) { + this.serverUrl = serverUrl + } + + async getRkey() { if (this.isExpired()) { try { - await this.refreshRkey(); + await this.refreshRkey() } catch (e) { - log('获取rkey失败', e); + log('获取rkey失败', e) } } - return this.rkeyData; + return this.rkeyData } isExpired(): boolean { - const now = new Date().getTime() / 1000; - // console.log(`now: ${now}, expired_time: ${this.rkeyData.expired_time}`); - return now > this.rkeyData.expired_time; + const now = new Date().getTime() / 1000 + // console.log(`now: ${now}, expired_time: ${this.rkeyData.expired_time}`) + return now > this.rkeyData.expired_time } + async refreshRkey(): Promise { //刷新rkey - this.rkeyData = await this.fetchServerRkey(); + this.rkeyData = await this.fetchServerRkey() } - async fetchServerRkey(){ + + async fetchServerRkey() { return new Promise((resolve, reject) => { fetch(this.serverUrl) .then(response => { if (!response.ok) { - return reject(response.statusText); // 请求失败,返回错误信息 + return reject(response.statusText) // 请求失败,返回错误信息 } - return response.json(); // 解析 JSON 格式的响应体 + return response.json() // 解析 JSON 格式的响应体 }) .then(data => { - resolve(data); + resolve(data) }) .catch(error => { - reject(error); - }); - }); + reject(error) + }) + }) } } -export const rkeyManager = new RkeyManager('http://napcat-sign.wumiao.wang:2082/rkey'); + +export const rkeyManager = new RkeyManager('http://napcat-sign.wumiao.wang:2082/rkey') diff --git a/src/ntqqapi/api/user.ts b/src/ntqqapi/api/user.ts index 010d15a..e5da023 100644 --- a/src/ntqqapi/api/user.ts +++ b/src/ntqqapi/api/user.ts @@ -6,7 +6,7 @@ import { cacheFunc, isQQ998, log, sleep } from '../../common/utils' import { wrapperApi } from '@/ntqqapi/wrapper' import { RequestUtil } from '@/common/utils/request' -let userInfoCache: Record = {} // uid: User +const userInfoCache: Record = {} // uid: User export interface ClientKeyData extends GeneralCallResult { url: string; @@ -149,7 +149,8 @@ export class NTQQUserApi { } static async getPSkey(domains: string[]): Promise> { - const res = await wrapperApi.NodeIQQNTWrapperSession.getTipOffService().getPskey(domains, true) + const session = wrapperApi.NodeIQQNTWrapperSession + const res = await session.getTipOffService().getPskey(domains, true) if (res.result !== 0) { throw new Error(`获取Pskey失败: ${res.errMsg}`) } @@ -157,7 +158,7 @@ export class NTQQUserApi { } static async getClientKey(): Promise { - return await wrapperApi.NodeIQQNTWrapperSession.getTicketService().forceFetchClientKey('') + const session = wrapperApi.NodeIQQNTWrapperSession + return await session.getTicketService().forceFetchClientKey('') } - } diff --git a/src/ntqqapi/api/webapi.ts b/src/ntqqapi/api/webapi.ts index cd73440..8f840ce 100644 --- a/src/ntqqapi/api/webapi.ts +++ b/src/ntqqapi/api/webapi.ts @@ -1,7 +1,8 @@ -import { WebGroupData, groups, selfInfo } from '@/common/data'; -import { log } from '@/common/utils/log'; -import { NTQQUserApi } from './user'; -import { RequestUtil } from '@/common/utils/request'; +import { WebGroupData, groups, selfInfo } from '@/common/data' +import { log } from '@/common/utils/log' +import { NTQQUserApi } from './user' +import { RequestUtil } from '@/common/utils/request' + export enum WebHonorType { ALL = 'all', TALKACTIVE = 'talkative', @@ -10,6 +11,7 @@ export enum WebHonorType { STORONGE_NEWBI = 'strong_newbie', EMOTION = 'emotion' } + export interface WebApiGroupMember { uin: number role: number @@ -27,6 +29,7 @@ export interface WebApiGroupMember { qage: number rm: number } + interface WebApiGroupMemberRet { ec: number errcode: number @@ -41,6 +44,7 @@ interface WebApiGroupMemberRet { search_count: number extmode: number } + export interface WebApiGroupNoticeFeed { u: number//发送者 fid: string//fid @@ -69,6 +73,7 @@ export interface WebApiGroupNoticeFeed { is_read: number is_all_confirm: number } + export interface WebApiGroupNoticeRet { ec: number em: string @@ -89,6 +94,7 @@ export interface WebApiGroupNoticeRet { svrt: number ad: number } + interface GroupEssenceMsg { group_code: string msg_seq: number @@ -102,6 +108,7 @@ interface GroupEssenceMsg { msg_content: any[] can_be_removed: true } + export interface GroupEssenceMsgRet { retcode: number retmsg: string @@ -112,9 +119,10 @@ export interface GroupEssenceMsgRet { config_page_url: string } } + export class WebApi { static async getGroupEssenceMsg(GroupCode: string, page_start: string): Promise { - const {cookies: CookieValue, bkn: Bkn} = (await NTQQUserApi.getCookies('qun.qq.com')) + const { cookies: CookieValue, bkn: Bkn } = (await NTQQUserApi.getCookies('qun.qq.com')) const url = 'https://qun.qq.com/cgi-bin/group_digest/digest_list?bkn=' + Bkn + '&group_code=' + GroupCode + '&page_start=' + page_start + '&page_limit=20'; let ret; try { @@ -128,6 +136,7 @@ export class WebApi { } return ret; } + static async getGroupMembers(GroupCode: string, cached: boolean = true): Promise { log('webapi 获取群成员', GroupCode); let MemberData: Array = new Array(); @@ -190,6 +199,7 @@ export class WebApi { // const res = await this.request(url); // return await res.json(); // } + static async setGroupNotice(GroupCode: string, Content: string = '') { //https://web.qun.qq.com/cgi-bin/announce/add_qun_notice?bkn=${bkn} //qid=${群号}&bkn=${bkn}&text=${内容}&pinned=0&type=1&settings={"is_show_edit_card":1,"tip_window_type":1,"confirm_required":1} @@ -213,6 +223,7 @@ export class WebApi { } return undefined; } + static async getGrouptNotice(GroupCode: string): Promise { const _Pskey = (await NTQQUserApi.getPSkey(['qun.qq.com']))['qun.qq.com']; const _Skey = await NTQQUserApi.getSkey(); @@ -236,6 +247,7 @@ export class WebApi { } return undefined; } + static genBkn(sKey: string) { sKey = sKey || ''; let hash = 5381; @@ -247,6 +259,7 @@ export class WebApi { return (hash & 0x7FFFFFFF).toString(); } + //实现未缓存 考虑2h缓存 static async getGroupHonorInfo(groupCode: string, getType: WebHonorType) { async function getDataInternal(Internal_groupCode: string, Internal_type: number) { diff --git a/src/ntqqapi/wrapper.ts b/src/ntqqapi/wrapper.ts index 7ca8ef7..e17beb1 100644 --- a/src/ntqqapi/wrapper.ts +++ b/src/ntqqapi/wrapper.ts @@ -1,26 +1,31 @@ -import path from 'node:path' -import fs from 'node:fs' -import { qqPkgInfo } from '../common/utils/QQBasicInfo' import { NodeIKernelBuddyService } from './services/NodeIKernelBuddyService' +import os from 'node:os' +const Process = require('node:process') export interface NodeIQQNTWrapperSession { [key: string]: any - new(): NodeIQQNTWrapperSession getBuddyService(): NodeIKernelBuddyService } export interface WrapperNodeApi { [key: string]: any - NodeIQQNTWrapperSession: NodeIQQNTWrapperSession + NodeIQQNTWrapperSession?: NodeIQQNTWrapperSession } -let wrapperNodePath = path.resolve(path.dirname(process.execPath), './resources/app/wrapper.node') -if (!fs.existsSync(wrapperNodePath)) { - wrapperNodePath = path.join(path.dirname(process.execPath), `resources/app/versions/${qqPkgInfo.version}/wrapper.node`) -} -const nativemodule: any = { exports: {} } -process.dlopen(nativemodule, wrapperNodePath) -const wrapperApi: WrapperNodeApi = nativemodule.exports +export const wrapperApi: WrapperNodeApi = {} -export { wrapperApi } -export default wrapperApi \ No newline at end of file +Process.dlopenOrig = Process.dlopen + +Process.dlopen = function (module, filename, flags = os.constants.dlopen.RTLD_LAZY) { + const dlopenRet = this.dlopenOrig(module, filename, flags) + for (let export_name in module.exports) { + module.exports[export_name] = new Proxy(module.exports[export_name], { + construct: (target, args, _newTarget) => { + const ret = new target(...args) + if (export_name === 'NodeIQQNTWrapperSession') wrapperApi.NodeIQQNTWrapperSession = ret + return ret + }, + }) + } + return dlopenRet +} \ No newline at end of file From 5d78fdd6a487250f8d7c38b080f1fba96e5c58f7 Mon Sep 17 00:00:00 2001 From: idranme Date: Mon, 5 Aug 2024 22:07:04 +0800 Subject: [PATCH 4/4] fix --- src/main/main.ts | 3 +++ src/ntqqapi/wrapper.ts | 45 ++++++++++++++++++++++++++++++++++++++---- 2 files changed, 44 insertions(+), 4 deletions(-) diff --git a/src/main/main.ts b/src/main/main.ts index 2dfdea8..3dc60c8 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -52,6 +52,8 @@ import { checkFfmpeg } from '../common/utils/video' import { GroupDecreaseSubType, OB11GroupDecreaseEvent } from '../onebot11/event/notice/OB11GroupDecreaseEvent' import '../ntqqapi/wrapper' import { sentMessages } from '@/ntqqapi/api' +import { NTEventDispatch } from '../common/utils/EventTask' +import { wrapperApi, wrapperConstructor } from '../ntqqapi/wrapper' let mainWindow: BrowserWindow | null = null @@ -436,6 +438,7 @@ function onLoad() { uidMaps[value] = key } }) + NTEventDispatch.init({ ListenerMap: wrapperConstructor, WrapperSession: wrapperApi.NodeIQQNTWrapperSession }) try { log('start get groups') const _groups = await NTQQGroupApi.getGroups() diff --git a/src/ntqqapi/wrapper.ts b/src/ntqqapi/wrapper.ts index e17beb1..40ed806 100644 --- a/src/ntqqapi/wrapper.ts +++ b/src/ntqqapi/wrapper.ts @@ -7,12 +7,46 @@ export interface NodeIQQNTWrapperSession { getBuddyService(): NodeIKernelBuddyService } -export interface WrapperNodeApi { - [key: string]: any +export interface WrapperApi { NodeIQQNTWrapperSession?: NodeIQQNTWrapperSession } -export const wrapperApi: WrapperNodeApi = {} +export interface WrapperConstructor { + [key: string]: any + NodeIKernelBuddyListener?: any + NodeIKernelGroupListener?: any + NodeQQNTWrapperUtil?: any + NodeIKernelMsgListener?: any + NodeIQQNTWrapperEngine?: any + NodeIGlobalAdapter?: any + NodeIDependsAdapter?: any + NodeIDispatcherAdapter?: any + NodeIKernelSessionListener?: any + NodeIKernelLoginService?: any + NodeIKernelLoginListener?: any + NodeIKernelProfileService?: any + NodeIKernelProfileListener?: any +} + +export const wrapperApi: WrapperApi = {} + +export const wrapperConstructor: WrapperConstructor = {} + +const constructor = [ + 'NodeIKernelBuddyListener', + 'NodeIKernelGroupListener', + 'NodeQQNTWrapperUtil', + 'NodeIKernelMsgListener', + 'NodeIQQNTWrapperEngine', + 'NodeIGlobalAdapter', + 'NodeIDependsAdapter', + 'NodeIDispatcherAdapter', + 'NodeIKernelSessionListener', + 'NodeIKernelLoginService', + 'NodeIKernelLoginListener', + 'NodeIKernelProfileService', + 'NodeIKernelProfileListener', +] Process.dlopenOrig = Process.dlopen @@ -24,8 +58,11 @@ Process.dlopen = function (module, filename, flags = os.constants.dlopen.RTLD_LA const ret = new target(...args) if (export_name === 'NodeIQQNTWrapperSession') wrapperApi.NodeIQQNTWrapperSession = ret return ret - }, + } }) + if (constructor.includes(export_name)) { + wrapperConstructor[export_name] = module.exports[export_name] + } } return dlopenRet } \ No newline at end of file