From a2d1379866a7f26c44513d29ba2844836021f474 Mon Sep 17 00:00:00 2001 From: idranme Date: Mon, 5 Aug 2024 19:09:41 +0800 Subject: [PATCH 01/13] 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 02/13] 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 03/13] 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 04/13] 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 From a9902d910909ed8ab39f45a29a30f0f4da043e85 Mon Sep 17 00:00:00 2001 From: idranme Date: Mon, 5 Aug 2024 22:49:48 +0800 Subject: [PATCH 05/13] sync --- src/ntqqapi/api/user.ts | 80 +++++++++++++++---- .../listeners/NodeIKernelProfileListener.ts | 44 ++++++++++ src/ntqqapi/listeners/index.ts | 1 + src/ntqqapi/types/user.ts | 37 +++++++++ src/onebot11/action/quick-operation.ts | 8 +- 5 files changed, 149 insertions(+), 21 deletions(-) create mode 100644 src/ntqqapi/listeners/NodeIKernelProfileListener.ts create mode 100644 src/ntqqapi/listeners/index.ts diff --git a/src/ntqqapi/api/user.ts b/src/ntqqapi/api/user.ts index e5da023..ab7d1ed 100644 --- a/src/ntqqapi/api/user.ts +++ b/src/ntqqapi/api/user.ts @@ -5,14 +5,18 @@ import { selfInfo, uidMaps } from '../../common/data' import { cacheFunc, isQQ998, log, sleep } from '../../common/utils' import { wrapperApi } from '@/ntqqapi/wrapper' import { RequestUtil } from '@/common/utils/request' +import { NodeIKernelProfileService, UserDetailSource, ProfileBizType } from '../services' +import { NodeIKernelProfileListener } from '../listeners' +import { NTEventDispatch } from '@/common/utils/EventTask' +import { qqPkgInfo } from '@/common/utils/QQBasicInfo' const userInfoCache: Record = {} // uid: User export interface ClientKeyData extends GeneralCallResult { - url: string; - keyIndex: string; - clientKey: string; - expireTime: string; + url: string + keyIndex: string + clientKey: string + expireTime: string } export class NTQQUserApi { @@ -46,8 +50,49 @@ export class NTQQUserApi { return result.profiles.get(uid) } + // 26702 + static async fetchUserDetailInfo(uid: string) { + type EventService = NodeIKernelProfileService['fetchUserDetailInfo'] + type EventListener = NodeIKernelProfileListener['onUserDetailInfoChanged'] + const [_retData, profile] = await NTEventDispatch.CallNormalEvent + + ( + 'NodeIKernelProfileService/fetchUserDetailInfo', + 'NodeIKernelProfileListener/onUserDetailInfoChanged', + 1, + 5000, + (profile) => { + if (profile.uid === uid) { + return true; + } + return false; + }, + 'BuddyProfileStore', + [ + uid + ], + UserDetailSource.KSERVER, + [ + ProfileBizType.KALL + ] + ) + const RetUser: User = { + ...profile.simpleInfo.coreInfo, + ...profile.simpleInfo.status, + ...profile.simpleInfo.vasInfo, + ...profile.commonExt, + ...profile.simpleInfo.baseInfo, + qqLevel: profile.commonExt.qqLevel, + pendantId: '' + } + return RetUser + } + static async getUserDetailInfo(uid: string, getLevel = false, withBizInfo = true) { - // this.getUserInfo(uid); + if (+qqPkgInfo.buildVersion >= 26702) { + return this.fetchUserDetailInfo(uid) + } + // this.getUserInfo(uid) let methodName = !isQQ998 ? NTQQApiMethod.USER_DETAIL_INFO : NTQQApiMethod.USER_DETAIL_INFO_WITH_BIZ_INFO if (!withBizInfo) { methodName = NTQQApiMethod.USER_DETAIL_INFO @@ -80,7 +125,7 @@ export class NTQQUserApi { await fetchInfo() await sleep(1000) } - let userInfo = await fetchInfo() + const userInfo = await fetchInfo() userInfoCache[uid] = userInfo return userInfo } @@ -97,16 +142,17 @@ export class NTQQUserApi { ], }) } + static async getQzoneCookies() { const requestUrl = 'https://ssl.ptlogin2.qq.com/jump?ptlang=1033&clientuin=' + selfInfo.uin + '&clientkey=' + (await this.getClientKey()).clientKey + '&u1=https%3A%2F%2Fuser.qzone.qq.com%2F' + selfInfo.uin + '%2Finfocenter&keyindex=19%27' - let cookies: { [key: string]: string; } = {}; + let cookies: { [key: string]: string } = {} try { - cookies = await RequestUtil.HttpsGetCookies(requestUrl); + cookies = await RequestUtil.HttpsGetCookies(requestUrl) } catch (e: any) { log('获取QZone Cookies失败', e) cookies = {} } - return cookies; + return cookies } static async getSkey(): Promise { const clientKeyData = await this.getClientKey() @@ -115,24 +161,24 @@ export class NTQQUserApi { } const url = 'https://ssl.ptlogin2.qq.com/jump?ptlang=1033&clientuin=' + selfInfo.uin + '&clientkey=' + clientKeyData.clientKey - + '&u1=https%3A%2F%2Fh5.qzone.qq.com%2Fqqnt%2Fqzoneinpcqq%2Ffriend%3Frefresh%3D0%26clientuin%3D0%26darkMode%3D0&keyindex=' + clientKeyData.keyIndex; - return (await RequestUtil.HttpsGetCookies(url))?.skey; + + '&u1=https%3A%2F%2Fh5.qzone.qq.com%2Fqqnt%2Fqzoneinpcqq%2Ffriend%3Frefresh%3D0%26clientuin%3D0%26darkMode%3D0&keyindex=' + clientKeyData.keyIndex + return (await RequestUtil.HttpsGetCookies(url))?.skey } @cacheFunc(60 * 30 * 1000) static async getCookies(domain: string) { if (domain.endsWith("qzone.qq.com")) { - let data = (await NTQQUserApi.getQzoneCookies()); - const CookieValue = 'p_skey=' + data.p_skey + '; skey=' + data.skey + '; p_uin=o' + selfInfo.uin + '; uin=o' + selfInfo.uin; - return { bkn: NTQQUserApi.genBkn(data.p_skey), cookies: CookieValue }; + let data = (await NTQQUserApi.getQzoneCookies()) + const CookieValue = 'p_skey=' + data.p_skey + '; skey=' + data.skey + '; p_uin=o' + selfInfo.uin + '; uin=o' + selfInfo.uin + return { bkn: NTQQUserApi.genBkn(data.p_skey), cookies: CookieValue } } - const skey = await this.getSkey(); - const pskey = (await this.getPSkey([domain])).get(domain); + const skey = await this.getSkey() + const pskey = (await this.getPSkey([domain])).get(domain) if (!pskey || !skey) { throw new Error('获取Cookies失败') } const bkn = NTQQUserApi.genBkn(skey) - const cookies = `p_skey=${pskey}; skey=${skey}; p_uin=o${selfInfo.uin}; uin=o${selfInfo.uin}`; + const cookies = `p_skey=${pskey}; skey=${skey}; p_uin=o${selfInfo.uin}; uin=o${selfInfo.uin}` return { cookies, bkn } } diff --git a/src/ntqqapi/listeners/NodeIKernelProfileListener.ts b/src/ntqqapi/listeners/NodeIKernelProfileListener.ts new file mode 100644 index 0000000..c0a53ad --- /dev/null +++ b/src/ntqqapi/listeners/NodeIKernelProfileListener.ts @@ -0,0 +1,44 @@ +import { User, UserDetailInfoListenerArg } from '@/ntqqapi/types' + +interface IProfileListener { + onProfileSimpleChanged(...args: unknown[]): void + + onUserDetailInfoChanged(arg: UserDetailInfoListenerArg): void + + onProfileDetailInfoChanged(profile: User): void + + onStatusUpdate(...args: unknown[]): void + + onSelfStatusChanged(...args: unknown[]): void + + onStrangerRemarkChanged(...args: unknown[]): void +} + +export interface NodeIKernelProfileListener extends IProfileListener { + new(listener: IProfileListener): NodeIKernelProfileListener +} + +export class ProfileListener implements IProfileListener { + onUserDetailInfoChanged(arg: UserDetailInfoListenerArg): void { + + } + onProfileSimpleChanged(...args: unknown[]) { + + } + + onProfileDetailInfoChanged(profile: User) { + + } + + onStatusUpdate(...args: unknown[]) { + + } + + onSelfStatusChanged(...args: unknown[]) { + + } + + onStrangerRemarkChanged(...args: unknown[]) { + + } +} \ No newline at end of file diff --git a/src/ntqqapi/listeners/index.ts b/src/ntqqapi/listeners/index.ts new file mode 100644 index 0000000..b3932e9 --- /dev/null +++ b/src/ntqqapi/listeners/index.ts @@ -0,0 +1 @@ +export * from './NodeIKernelProfileListener' \ No newline at end of file diff --git a/src/ntqqapi/types/user.ts b/src/ntqqapi/types/user.ts index b879034..8538265 100644 --- a/src/ntqqapi/types/user.ts +++ b/src/ntqqapi/types/user.ts @@ -221,4 +221,41 @@ interface RelationFlags { export interface FriendV2 extends SimpleInfo { categoryId?: number categroyName?: string +} + +interface CommonExt { + constellation: number + shengXiao: number + kBloodType: number + homeTown: string + makeFriendCareer: number + pos: string + college: string + country: string + province: string + city: string + postCode: string + address: string + regTime: number + interest: string + labels: any[] + qqLevel: QQLevel +} + +interface Pic { + picId: string + picTime: number + picUrlMap: Record +} + +interface PhotoWall { + picList: Pic[] +} + +export interface UserDetailInfoListenerArg { + uid: string + uin: string + simpleInfo: SimpleInfo + commonExt: CommonExt + photoWall: PhotoWall } \ No newline at end of file diff --git a/src/onebot11/action/quick-operation.ts b/src/onebot11/action/quick-operation.ts index fa9c1bf..4042cc3 100644 --- a/src/onebot11/action/quick-operation.ts +++ b/src/onebot11/action/quick-operation.ts @@ -5,8 +5,8 @@ import { OB11Message, OB11MessageAt, OB11MessageData, OB11MessageDataType } from import { OB11FriendRequestEvent } from '../event/request/OB11FriendRequest' import { OB11GroupRequestEvent } from '../event/request/OB11GroupRequest' import { dbUtil } from '@/common/db' -import { NTQQFriendApi, NTQQGroupApi, NTQQMsgApi, Peer } from '@/ntqqapi/api' -import { ChatType, Group, GroupRequestOperateTypes } from '@/ntqqapi/types' +import { NTQQFriendApi, NTQQGroupApi, NTQQMsgApi } from '@/ntqqapi/api' +import { ChatType, Group, GroupRequestOperateTypes, Peer } from '@/ntqqapi/types' import { getGroup, getUidByUin } from '@/common/data' import { convertMessage2List, createSendElements, sendMsg } from './msg/SendMsg' import { isNull, log } from '@/common/utils' @@ -129,7 +129,7 @@ async function handleMsg(msg: OB11Message, quickAction: QuickOperationPrivateMes } async function handleFriendRequest(request: OB11FriendRequestEvent, - quickAction: QuickOperationFriendRequest) { + quickAction: QuickOperationFriendRequest) { if (!isNull(quickAction.approve)) { // todo: set remark NTQQFriendApi.handleFriendRequest(request.flag, quickAction.approve).then().catch(log) @@ -138,7 +138,7 @@ async function handleFriendRequest(request: OB11FriendRequestEvent, async function handleGroupRequest(request: OB11GroupRequestEvent, - quickAction: QuickOperationGroupRequest) { + quickAction: QuickOperationGroupRequest) { if (!isNull(quickAction.approve)) { NTQQGroupApi.handleGroupRequest( request.flag, From 897f691d6ce2fb8d030d624eb540d169b89699f4 Mon Sep 17 00:00:00 2001 From: idranme Date: Tue, 6 Aug 2024 01:47:51 +0800 Subject: [PATCH 06/13] make ts happy --- package.json | 1 + src/common/config.ts | 4 +- src/common/data.ts | 2 +- src/common/db.ts | 84 +++++++++---------- src/common/server/http.ts | 10 +-- src/common/server/websocket.ts | 28 +++---- src/common/utils/audio.ts | 4 +- src/common/utils/file.ts | 14 ++-- src/common/utils/helper.ts | 6 +- src/common/utils/upgrade.ts | 2 +- src/common/utils/video.ts | 10 +-- src/main/main.ts | 68 ++++++++------- src/ntqqapi/api/file.ts | 2 +- src/ntqqapi/api/friend.ts | 6 +- src/ntqqapi/api/group.ts | 10 +-- src/ntqqapi/api/msg.ts | 4 +- src/ntqqapi/api/user.ts | 4 +- src/ntqqapi/api/webapi.ts | 16 ++-- src/ntqqapi/api/window.ts | 2 +- src/ntqqapi/constructor.ts | 14 ++-- src/ntqqapi/hook.ts | 44 +++++----- src/ntqqapi/ntcall.ts | 1 - src/onebot11/action/BaseAction.ts | 8 +- src/onebot11/action/file/GetFile.ts | 4 +- src/onebot11/action/file/GetRecord.ts | 2 +- .../action/go-cqhttp/GetForwardMsg.ts | 9 +- .../action/go-cqhttp/GetGroupMsgHistory.ts | 1 - src/onebot11/action/go-cqhttp/UploadFile.ts | 15 ++-- src/onebot11/action/group/GetGroupEssence.ts | 20 ++--- .../action/group/GetGroupHonorInfo.ts | 19 +++-- src/onebot11/action/group/SendGroupMsg.ts | 4 +- src/onebot11/action/msg/ForwardSingleMsg.ts | 17 ++-- src/onebot11/action/msg/SendMsg.ts | 41 ++++----- src/onebot11/action/quick-operation.ts | 16 ++-- src/onebot11/action/system/CleanCache.ts | 11 +-- src/onebot11/action/system/GetStatus.ts | 2 +- src/onebot11/action/user/SendLike.ts | 2 +- src/onebot11/constructor.ts | 84 ++++++++++--------- src/onebot11/event/OB11BaseEvent.ts | 2 +- src/onebot11/event/meta/OB11BaseMetaEvent.ts | 2 +- .../event/notice/OB11GroupAdminNoticeEvent.ts | 11 ++- .../event/notice/OB11GroupBanEvent.ts | 2 + .../event/notice/OB11GroupCardEvent.ts | 2 + .../event/notice/OB11GroupDecreaseEvent.ts | 2 + .../event/notice/OB11GroupEssenceEvent.ts | 18 ++-- .../event/notice/OB11GroupIncreaseEvent.ts | 4 + .../event/notice/OB11GroupNoticeEvent.ts | 5 +- .../notice/OB11GroupRecallNoticeEvent.ts | 2 + .../event/notice/OB11GroupTitleEvent.ts | 2 + .../notice/OB11GroupUploadNoticeEvent.ts | 2 + .../event/notice/OB11MsgEmojiLikeEvent.ts | 7 +- src/onebot11/event/notice/OB11PokeEvent.ts | 8 +- .../event/request/OB11FriendRequest.ts | 10 ++- .../event/request/OB11GroupRequest.ts | 25 ++++-- src/onebot11/server/event-for-http.ts | 53 ++++++------ src/onebot11/server/http.ts | 2 +- src/onebot11/server/post-ob11-event.ts | 5 +- src/onebot11/server/ws/ReverseWebsocket.ts | 28 +++---- src/onebot11/server/ws/WebsocketServer.ts | 12 ++- src/onebot11/server/ws/reply.ts | 2 +- src/renderer/components/item.ts | 2 +- src/renderer/components/select.ts | 15 ++-- src/renderer/index.ts | 76 +++++++++-------- tsconfig.json | 15 +++- 64 files changed, 485 insertions(+), 420 deletions(-) diff --git a/package.json b/package.json index d4f13ae..a571b60 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "ws": "^8.18.0" }, "devDependencies": { + "@types/cors": "^2.8.17", "@types/express": "^4.17.20", "@types/fluent-ffmpeg": "^2.1.24", "@types/node": "^20.11.24", diff --git a/src/common/config.ts b/src/common/config.ts index d9a1eea..d2da049 100644 --- a/src/common/config.ts +++ b/src/common/config.ts @@ -1,7 +1,5 @@ -import fs from 'fs' -import fsPromise from 'fs/promises' +import fs from 'node:fs' import { Config, OB11Config } from './types' - import { mergeNewProperties } from './utils/helper' import path from 'node:path' import { selfInfo } from './data' diff --git a/src/common/data.ts b/src/common/data.ts index 4d8fbb9..4287849 100644 --- a/src/common/data.ts +++ b/src/common/data.ts @@ -45,7 +45,7 @@ export async function getFriend(uinOrUid: string): Promise { if (friend) { friends.push(friend) } - } catch (e) { + } catch (e: any) { log('刷新好友列表失败', e.stack.toString()) } } diff --git a/src/common/db.ts b/src/common/db.ts index 029e979..9cdb597 100644 --- a/src/common/db.ts +++ b/src/common/db.ts @@ -14,9 +14,9 @@ class DBUtil { public readonly DB_KEY_PREFIX_FILE = 'file_' public readonly DB_KEY_PREFIX_GROUP_NOTIFY = 'group_notify_' private readonly DB_KEY_RECEIVED_TEMP_UIN_MAP = 'received_temp_uin_map' - public db: Level + public db: Level | undefined public cache: Record = {} // : RawMessage - private currentShortId: number + private currentShortId: number | undefined /* * 数据库结构 @@ -44,7 +44,7 @@ class DBUtil { this.db = new Level(DB_PATH, { valueEncoding: 'json' }) console.log('llonebot init db success') resolve(null) - } catch (e) { + } catch (e: any) { console.log('init db fail', e.stack.toString()) setTimeout(initDB, 300) } @@ -72,13 +72,13 @@ class DBUtil { public async getReceivedTempUinMap(): Promise { try { - this.cache[this.DB_KEY_RECEIVED_TEMP_UIN_MAP] = JSON.parse(await this.db.get(this.DB_KEY_RECEIVED_TEMP_UIN_MAP)) - } catch (e) {} + this.cache[this.DB_KEY_RECEIVED_TEMP_UIN_MAP] = JSON.parse(await this.db?.get(this.DB_KEY_RECEIVED_TEMP_UIN_MAP)!) + } catch (e) { } return (this.cache[this.DB_KEY_RECEIVED_TEMP_UIN_MAP] || {}) as ReceiveTempUinMap } public setReceivedTempUinMap(data: ReceiveTempUinMap) { this.cache[this.DB_KEY_RECEIVED_TEMP_UIN_MAP] = data - this.db.put(this.DB_KEY_RECEIVED_TEMP_UIN_MAP, JSON.stringify(data)).then() + this.db?.put(this.DB_KEY_RECEIVED_TEMP_UIN_MAP, JSON.stringify(data)).then() } private addCache(msg: RawMessage) { const longIdKey = this.DB_KEY_PREFIX_MSG_ID + msg.msgId @@ -91,30 +91,30 @@ class DBUtil { this.cache = {} } - async getMsgByShortId(shortMsgId: number): Promise { + async getMsgByShortId(shortMsgId: number): Promise { const shortMsgIdKey = this.DB_KEY_PREFIX_MSG_SHORT_ID + shortMsgId if (this.cache[shortMsgIdKey]) { // log("getMsgByShortId cache", shortMsgIdKey, this.cache[shortMsgIdKey]) return this.cache[shortMsgIdKey] as RawMessage } try { - const longId = await this.db.get(shortMsgIdKey) - const msg = await this.getMsgByLongId(longId) - this.addCache(msg) + const longId = await this.db?.get(shortMsgIdKey) + const msg = await this.getMsgByLongId(longId!) + this.addCache(msg!) return msg - } catch (e) { + } catch (e: any) { log('getMsgByShortId db error', e.stack.toString()) } } - async getMsgByLongId(longId: string): Promise { + async getMsgByLongId(longId: string): Promise { const longIdKey = this.DB_KEY_PREFIX_MSG_ID + longId if (this.cache[longIdKey]) { return this.cache[longIdKey] as RawMessage } try { - const data = await this.db.get(longIdKey) - const msg = JSON.parse(data) + const data = await this.db?.get(longIdKey) + const msg = JSON.parse(data!) this.addCache(msg) return msg } catch (e) { @@ -122,17 +122,17 @@ class DBUtil { } } - async getMsgBySeqId(seqId: string): Promise { + async getMsgBySeqId(seqId: string): Promise { const seqIdKey = this.DB_KEY_PREFIX_MSG_SEQ_ID + seqId if (this.cache[seqIdKey]) { return this.cache[seqIdKey] as RawMessage } try { - const longId = await this.db.get(seqIdKey) - const msg = await this.getMsgByLongId(longId) - this.addCache(msg) + const longId = await this.db?.get(seqIdKey) + const msg = await this.getMsgByLongId(longId!) + this.addCache(msg!) return msg - } catch (e) { + } catch (e: any) { log('getMsgBySeqId db error', e.stack.toString()) } } @@ -141,7 +141,7 @@ class DBUtil { // 有则更新,无则添加 // log("addMsg", msg.msgId, msg.msgSeq, msg.msgShortId); const longIdKey = this.DB_KEY_PREFIX_MSG_ID + msg.msgId - let existMsg = this.cache[longIdKey] as RawMessage + let existMsg: RawMessage | undefined = this.cache[longIdKey] as RawMessage if (!existMsg) { try { existMsg = await this.getMsgByLongId(msg.msgId) @@ -161,13 +161,13 @@ class DBUtil { msg.msgShortId = shortMsgId this.addCache(msg) // log("新增消息记录", msg.msgId) - this.db.put(shortIdKey, msg.msgId).then().catch() - this.db.put(longIdKey, JSON.stringify(msg)).then().catch() + this.db?.put(shortIdKey, msg.msgId).then().catch() + this.db?.put(longIdKey, JSON.stringify(msg)).then().catch() try { - await this.db.get(seqIdKey) + await this.db?.get(seqIdKey) } catch (e) { // log("新的seqId", seqIdKey) - this.db.put(seqIdKey, msg.msgId).then().catch() + this.db?.put(seqIdKey, msg.msgId).then().catch() } if (!this.cache[seqIdKey]) { this.cache[seqIdKey] = msg @@ -178,7 +178,7 @@ class DBUtil { async updateMsg(msg: RawMessage) { const longIdKey = this.DB_KEY_PREFIX_MSG_ID + msg.msgId - let existMsg = this.cache[longIdKey] as RawMessage + let existMsg: RawMessage | undefined = this.cache[longIdKey] as RawMessage if (!existMsg) { try { existMsg = await this.getMsgByLongId(msg.msgId) @@ -187,18 +187,18 @@ class DBUtil { } } - Object.assign(existMsg, msg) - this.db.put(longIdKey, JSON.stringify(existMsg)).then().catch() - const shortIdKey = this.DB_KEY_PREFIX_MSG_SHORT_ID + existMsg.msgShortId + Object.assign(existMsg!, msg) + this.db?.put(longIdKey, JSON.stringify(existMsg)).then().catch() + const shortIdKey = this.DB_KEY_PREFIX_MSG_SHORT_ID + existMsg?.msgShortId const seqIdKey = this.DB_KEY_PREFIX_MSG_SEQ_ID + msg.msgSeq if (!this.cache[seqIdKey]) { - this.cache[seqIdKey] = existMsg + this.cache[seqIdKey] = existMsg! } - this.db.put(shortIdKey, msg.msgId).then().catch() + this.db?.put(shortIdKey, msg.msgId).then().catch() try { - await this.db.get(seqIdKey) + await this.db?.get(seqIdKey) } catch (e) { - this.db.put(seqIdKey, msg.msgId).then().catch() + this.db?.put(seqIdKey, msg.msgId).then().catch() // log("更新seqId error", e.stack, seqIdKey); } // log("更新消息", existMsg.msgSeq, existMsg.msgShortId, existMsg.msgId); @@ -208,15 +208,15 @@ class DBUtil { const key = 'msg_current_short_id' if (this.currentShortId === undefined) { try { - let id: string = await this.db.get(key) - this.currentShortId = parseInt(id) + const id = await this.db?.get(key) + this.currentShortId = parseInt(id!) } catch (e) { this.currentShortId = -2147483640 } } this.currentShortId++ - this.db.put(key, this.currentShortId.toString()).then().catch() + this.db?.put(key, this.currentShortId.toString()).then().catch() return this.currentShortId } @@ -229,8 +229,8 @@ class DBUtil { delete cacheDBData['downloadFunc'] this.cache[fileNameOrUuid] = data try { - await this.db.put(key, JSON.stringify(cacheDBData)) - } catch (e) { + await this.db?.put(key, JSON.stringify(cacheDBData)) + } catch (e: any) { log('addFileCache db error', e.stack.toString()) } } @@ -241,8 +241,8 @@ class DBUtil { return this.cache[key] as FileCache } try { - let data = await this.db.get(key) - return JSON.parse(data) + const data = await this.db?.get(key) + return JSON.parse(data!) } catch (e) { // log("getFileCache db error", e.stack.toString()) } @@ -255,7 +255,7 @@ class DBUtil { return } this.cache[key] = notify - this.db.put(key, JSON.stringify(notify)).then().catch() + this.db?.put(key, JSON.stringify(notify)).then().catch() } async getGroupNotify(seq: string): Promise { @@ -264,8 +264,8 @@ class DBUtil { return this.cache[key] as GroupNotify } try { - let data = await this.db.get(key) - return JSON.parse(data) + const data = await this.db?.get(key) + return JSON.parse(data!) } catch (e) { // log("getGroupNotify db error", e.stack.toString()) } diff --git a/src/common/server/http.ts b/src/common/server/http.ts index 1b1898b..f451f52 100644 --- a/src/common/server/http.ts +++ b/src/common/server/http.ts @@ -1,5 +1,5 @@ import express, { Express, Request, Response } from 'express' -import http from 'http' +import http from 'node:http' import cors from 'cors' import { log } from '../utils/log' import { getConfigUtil } from '../config' @@ -10,7 +10,7 @@ type RegisterHandler = (res: Response, payload: any) => Promise export abstract class HttpServerBase { name: string = 'LLOneBot' private readonly expressAPP: Express - private server: http.Server = null + private server: http.Server | null = null constructor() { this.expressAPP = express() @@ -38,7 +38,7 @@ export abstract class HttpServerBase { let clientToken = '' const authHeader = req.get('authorization') if (authHeader) { - clientToken = authHeader.split('Bearer ').pop() + clientToken = authHeader.split('Bearer ').pop()! log('receive http header token', clientToken) } else if (req.query.access_token) { if (Array.isArray(req.query.access_token)) { @@ -62,7 +62,7 @@ export abstract class HttpServerBase { }) this.listen(port) llonebotError.httpServerError = '' - } catch (e) { + } catch (e: any) { log('HTTP服务启动失败', e.toString()) llonebotError.httpServerError = 'HTTP服务启动失败, ' + e.toString() } @@ -103,7 +103,7 @@ export abstract class HttpServerBase { log('收到http请求', url, payload) try { res.send(await handler(res, payload)) - } catch (e) { + } catch (e: any) { this.handleFailed(res, payload, e.stack.toString()) } }) diff --git a/src/common/server/websocket.ts b/src/common/server/websocket.ts index 0ac56cd..dc44980 100644 --- a/src/common/server/websocket.ts +++ b/src/common/server/websocket.ts @@ -6,9 +6,9 @@ import { getConfigUtil } from '../config' import { llonebotError } from '../data' class WebsocketClientBase { - private wsClient: WebSocket + private wsClient: WebSocket | undefined - constructor() {} + constructor() { } send(msg: string) { if (this.wsClient && this.wsClient.readyState == WebSocket.OPEN) { @@ -16,11 +16,11 @@ class WebsocketClientBase { } } - onMessage(msg: string) {} + onMessage(msg: string) { } } export class WebsocketServerBase { - private ws: WebSocketServer = null + private ws: WebSocketServer | null = null constructor() { console.log(`llonebot websocket service started`) @@ -30,22 +30,22 @@ export class WebsocketServerBase { try { this.ws = new WebSocketServer({ port, maxPayload: 1024 * 1024 * 1024 }) llonebotError.wsServerError = '' - } catch (e) { + } catch (e: any) { llonebotError.wsServerError = '正向ws服务启动失败, ' + e.toString() } - this.ws.on('connection', (wsClient, req) => { - const url = req.url.split('?').shift() + this.ws?.on('connection', (wsClient, req) => { + const url = req.url?.split('?').shift() this.authorize(wsClient, req) - this.onConnect(wsClient, url, req) + this.onConnect(wsClient, url!, req) wsClient.on('message', async (msg) => { - this.onMessage(wsClient, url, msg.toString()) + this.onMessage(wsClient, url!, msg.toString()) }) }) } stop() { llonebotError.wsServerError = '' - this.ws.close((err) => { + this.ws?.close((err) => { log('ws server close failed!', err) }) this.ws = null @@ -83,11 +83,11 @@ export class WebsocketServerBase { } } - authorizeFailed(wsClient: WebSocket) {} + authorizeFailed(wsClient: WebSocket) { } - onConnect(wsClient: WebSocket, url: string, req: IncomingMessage) {} + onConnect(wsClient: WebSocket, url: string, req: IncomingMessage) { } - onMessage(wsClient: WebSocket, url: string, msg: string) {} + onMessage(wsClient: WebSocket, url: string, msg: string) { } - sendHeart() {} + sendHeart() { } } diff --git a/src/common/utils/audio.ts b/src/common/utils/audio.ts index f5ca32e..ea31221 100644 --- a/src/common/utils/audio.ts +++ b/src/common/utils/audio.ts @@ -114,7 +114,7 @@ export async function encodeSilk(filePath: string) { let duration = 0 try { duration = getDuration(silk) / 1000 - } catch (e) { + } catch (e: any) { log('获取语音文件时长失败, 使用文件大小推测时长', filePath, e.stack) duration = await guessDuration(filePath) } @@ -125,7 +125,7 @@ export async function encodeSilk(filePath: string) { duration, } } - } catch (error) { + } catch (error: any) { log('convert silk failed', error.stack) return {} } diff --git a/src/common/utils/file.ts b/src/common/utils/file.ts index 2b2aafa..2acd233 100644 --- a/src/common/utils/file.ts +++ b/src/common/utils/file.ts @@ -52,7 +52,7 @@ export async function file2base64(path: string) { const data = await fsPromise.readFile(path) // 转换为Base64编码 result.data = data.toString('base64') - } catch (err) { + } catch (err: any) { result.err = err.toString() } return result @@ -119,7 +119,7 @@ type Uri2LocalRes = { isLocal: boolean } -export async function uri2local(uri: string, fileName: string = null): Promise { +export async function uri2local(uri: string, fileName: string | null = null): Promise { let res = { success: false, errMsg: '', @@ -132,10 +132,10 @@ export async function uri2local(uri: string, fileName: string = null): Promise(); return function (target: any, propertyKey: string, descriptor: PropertyDescriptor): PropertyDescriptor { const originalMethod = descriptor.value; const className = target.constructor.name; // 获取类名 const methodName = propertyKey; // 获取方法名 - descriptor.value = async function (...args: any[]){ + descriptor.value = async function (...args: any[]) { const cacheKey = `${customKey}${className}.${methodName}:${JSON.stringify(args)}`; const cached = cache.get(cacheKey); if (cached && cached.expiry > Date.now()) { diff --git a/src/common/utils/upgrade.ts b/src/common/utils/upgrade.ts index 8005389..989a57d 100644 --- a/src/common/utils/upgrade.ts +++ b/src/common/utils/upgrade.ts @@ -91,7 +91,7 @@ export async function getRemoteVersionByMirror(mirrorGithub: string) { releasePage = (await httpDownload(mirrorGithub + '/LLOneBot/LLOneBot/releases')).toString() // log("releasePage", releasePage); if (releasePage === 'error') return '' - return releasePage.match(new RegExp('(?<=(tag/v)).*?(?=("))'))[0] + return releasePage.match(new RegExp('(?<=(tag/v)).*?(?=("))'))?.[0] } catch {} return '' } diff --git a/src/common/utils/video.ts b/src/common/utils/video.ts index 19a3a22..9ddca67 100644 --- a/src/common/utils/video.ts +++ b/src/common/utils/video.ts @@ -31,10 +31,10 @@ export async function getVideoInfo(filePath: string) { console.log('未找到视频流信息。') } resolve({ - width: videoStream.width, - height: videoStream.height, - time: parseInt(videoStream.duration), - format: metadata.format.format_name, + width: videoStream?.width!, + height: videoStream?.height!, + time: parseInt(videoStream?.duration!), + format: metadata.format.format_name!, size, filePath, }) @@ -67,7 +67,7 @@ export async function encodeMp4(filePath: string) { return videoInfo } -export function checkFfmpeg(newPath: string = null): Promise { +export function checkFfmpeg(newPath: string | null = null): Promise { return new Promise((resolve, reject) => { log('开始检查ffmpeg', newPath) if (newPath) { diff --git a/src/main/main.ts b/src/main/main.ts index 3dc60c8..99a6893 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -123,7 +123,7 @@ function onLoad() { return } dialog - .showMessageBox(mainWindow, { + .showMessageBox(mainWindow!, { type: 'question', buttons: ['确认', '取消'], defaultId: 0, // 默认选中的按钮,0 代表第一个按钮,即 "确认" @@ -210,7 +210,7 @@ function onLoad() { }>([ReceiveCmdS.NEW_MSG, ReceiveCmdS.NEW_ACTIVE_MSG], async (payload) => { try { await postReceiveMsg(payload.msgList) - } catch (e) { + } catch (e: any) { log('report message error: ', e.stack.toString()) } }) @@ -254,7 +254,7 @@ function onLoad() { // log("reportSelfMessage", payload) try { await postReceiveMsg([payload.msgRecord]) - } catch (e) { + } catch (e: any) { log('report self message error: ', e.stack.toString()) } }) @@ -332,7 +332,7 @@ function onLoad() { if (notify.user2.uid) { // 是被踢的 const member2 = await getGroupMember(notify.group.groupCode, notify.user2.uid) - operatorId = member2.uin + operatorId = member2?.uin! subType = 'kick' } let groupDecreaseEvent = new OB11GroupDecreaseEvent( @@ -342,14 +342,12 @@ function onLoad() { subType, ) postOb11Event(groupDecreaseEvent, true) - } catch (e) { + } catch (e: any) { log('获取群通知的成员信息失败', notify, e.stack.toString()) } } else if ([GroupNotifyTypes.JOIN_REQUEST, GroupNotifyTypes.JOIN_REQUEST_BY_INVITED].includes(notify.type)) { log('有加群请求') - let groupRequestEvent = new OB11GroupRequestEvent() - groupRequestEvent.group_id = parseInt(notify.group.groupCode) let requestQQ = uidMaps[notify.user1.uid] if (!requestQQ) { try { @@ -358,40 +356,47 @@ function onLoad() { log('获取加群人QQ号失败', e) } } - groupRequestEvent.user_id = parseInt(requestQQ) || 0 - groupRequestEvent.sub_type = 'add' - groupRequestEvent.comment = notify.postscript - groupRequestEvent.flag = notify.seq + let invitorId: number if (notify.type == GroupNotifyTypes.JOIN_REQUEST_BY_INVITED) { // groupRequestEvent.sub_type = 'invite' let invitorQQ = uidMaps[notify.user2.uid] if (!invitorQQ) { try { let invitor = (await NTQQUserApi.getUserDetailInfo(notify.user2.uid)) - groupRequestEvent.invitor_id = parseInt(invitor.uin) + invitorId = parseInt(invitor.uin) } catch (e) { - groupRequestEvent.invitor_id = 0 + invitorId = 0 log('获取邀请人QQ号失败', e) } } } + const groupRequestEvent = new OB11GroupRequestEvent( + parseInt(notify.group.groupCode), + parseInt(requestQQ) || 0, + notify.seq, + notify.postscript, + invitorId!, + 'add' + ) postOb11Event(groupRequestEvent) } else if (notify.type == GroupNotifyTypes.INVITE_ME) { log('收到邀请我加群通知') - let groupInviteEvent = new OB11GroupRequestEvent() - groupInviteEvent.group_id = parseInt(notify.group.groupCode) - let user_id = uidMaps[notify.user2.uid] - if (!user_id) { - user_id = (await NTQQUserApi.getUserDetailInfo(notify.user2.uid))?.uin + let userId = uidMaps[notify.user2.uid] + if (!userId) { + userId = (await NTQQUserApi.getUserDetailInfo(notify.user2.uid))?.uin } - groupInviteEvent.user_id = parseInt(user_id) - groupInviteEvent.sub_type = 'invite' - // groupInviteEvent.invitor_id = parseInt(user_id) - groupInviteEvent.flag = notify.seq + const groupInviteEvent = new OB11GroupRequestEvent( + parseInt(notify.group.groupCode), + parseInt(userId), + notify.seq, + undefined, + undefined, + 'invite' + ) postOb11Event(groupInviteEvent) } - } catch (e) { + } catch (e: any) { log('解析群通知失败', e.stack.toString()) } } @@ -403,19 +408,18 @@ function onLoad() { registerReceiveHook(ReceiveCmdS.FRIEND_REQUEST, async (payload) => { for (const req of payload.data.buddyReqs) { - let flag = req.friendUid + req.reqTime + const flag = req.friendUid + req.reqTime if (req.isUnread && parseInt(req.reqTime) > startTime / 1000) { friendRequests[flag] = req log('有新的好友请求', req) - let friendRequestEvent = new OB11FriendRequestEvent() + let userId: number try { - let requester = await NTQQUserApi.getUserDetailInfo(req.friendUid) - friendRequestEvent.user_id = parseInt(requester.uin) + const requester = await NTQQUserApi.getUserDetailInfo(req.friendUid) + userId = parseInt(requester.uin) } catch (e) { log('获取加好友者QQ号失败', e) } - friendRequestEvent.flag = flag - friendRequestEvent.comment = req.extWords + const friendRequestEvent = new OB11FriendRequestEvent(userId!, req.extWords, flag) postOb11Event(friendRequestEvent) } } @@ -438,7 +442,7 @@ function onLoad() { uidMaps[value] = key } }) - NTEventDispatch.init({ ListenerMap: wrapperConstructor, WrapperSession: wrapperApi.NodeIQQNTWrapperSession }) + NTEventDispatch.init({ ListenerMap: wrapperConstructor, WrapperSession: wrapperApi.NodeIQQNTWrapperSession! }) try { log('start get groups') const _groups = await NTQQGroupApi.getGroups() @@ -509,7 +513,7 @@ function onLoad() { selfInfo.nick = userInfo.nick return } - } catch (e) { + } catch (e: any) { log('get self nickname failed', e.stack) } if (getSelfNickCount < 10) { @@ -537,7 +541,7 @@ function onBrowserWindowCreated(window: BrowserWindow) { try { hookNTQQApiCall(window) hookNTQQApiReceive(window) - } catch (e) { + } catch (e: any) { log('LLOneBot hook error: ', e.toString()) } } diff --git a/src/ntqqapi/api/file.ts b/src/ntqqapi/api/file.ts index 6a539fd..d38c86e 100644 --- a/src/ntqqapi/api/file.ts +++ b/src/ntqqapi/api/file.ts @@ -22,7 +22,7 @@ import { Peer } from '@/ntqqapi/types/msg' export class NTQQFileApi { static async getVideoUrl(peer: Peer, msgId: string, elementId: string): Promise { const session = wrapperApi.NodeIQQNTWrapperSession - return (await session.getRichMediaService().getVideoPlayUrlV2(peer, + return (await session?.getRichMediaService().getVideoPlayUrlV2(peer, msgId, elementId, 0, diff --git a/src/ntqqapi/api/friend.ts b/src/ntqqapi/api/friend.ts index 2e3a5ec..55384e8 100644 --- a/src/ntqqapi/api/friend.ts +++ b/src/ntqqapi/api/friend.ts @@ -70,9 +70,9 @@ export class NTQQFriendApi { static async getBuddyV2(refresh = false): Promise { const uids: string[] = [] 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 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( 'NodeIKernelProfileService/getCoreAndBaseInfo', 5000, 'nodeStore', uids ) diff --git a/src/ntqqapi/api/group.ts b/src/ntqqapi/api/group.ts index a2cf620..0423c80 100644 --- a/src/ntqqapi/api/group.ts +++ b/src/ntqqapi/api/group.ts @@ -135,7 +135,7 @@ export class NTQQGroupApi { } static async handleGroupRequest(seq: string, operateType: GroupRequestOperateTypes, reason?: string) { - const notify: GroupNotify = await dbUtil.getGroupNotify(seq) + const notify = await dbUtil.getGroupNotify(seq) if (!notify) { throw `${seq}对应的加群通知不存在` } @@ -303,27 +303,27 @@ export class NTQQGroupApi { const session = wrapperApi.NodeIQQNTWrapperSession // 代码没测过 // 需要 ob11msgid->msgId + (peer) -> msgSeq + msgRandom - let MsgData = await session.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 session.getGroupService().removeGroupEssence(param) + return session?.getGroupService().removeGroupEssence(param) } static async addGroupEssence(GroupCode: string, msgId: string) { const session = wrapperApi.NodeIQQNTWrapperSession // 代码没测过 // 需要 ob11msgid->msgId + (peer) -> msgSeq + msgRandom - let MsgData = await session.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 session.getGroupService().addGroupEssence(param) + return session?.getGroupService().addGroupEssence(param) } } diff --git a/src/ntqqapi/api/msg.ts b/src/ntqqapi/api/msg.ts index ae19a0a..9daf31e 100644 --- a/src/ntqqapi/api/msg.ts +++ b/src/ntqqapi/api/msg.ts @@ -33,7 +33,7 @@ async function sendWaiter(peer: Peer, waitComplete = true, timeout: number = 100 } await waitLastSend() - let sentMessage: RawMessage = null + let sentMessage: RawMessage | null = null sendMessagePool[peerUid] = async (rawMessage: RawMessage) => { delete sendMessagePool[peerUid] sentMessage = rawMessage @@ -290,6 +290,6 @@ export class NTQQMsgApi { } static async getMsgsBySeqAndCount(peer: Peer, seq: string, count: number, desc: boolean, z: boolean) { const session = wrapperApi.NodeIQQNTWrapperSession - return await session.getMsgService().getMsgsBySeqAndCount(peer, seq, count, desc, z); + return await session?.getMsgService().getMsgsBySeqAndCount(peer, seq, count, desc, z); } } diff --git a/src/ntqqapi/api/user.ts b/src/ntqqapi/api/user.ts index ab7d1ed..ef093d2 100644 --- a/src/ntqqapi/api/user.ts +++ b/src/ntqqapi/api/user.ts @@ -196,7 +196,7 @@ export class NTQQUserApi { static async getPSkey(domains: string[]): Promise> { const session = wrapperApi.NodeIQQNTWrapperSession - const res = await session.getTipOffService().getPskey(domains, true) + const res = await session?.getTipOffService().getPskey(domains, true) if (res.result !== 0) { throw new Error(`获取Pskey失败: ${res.errMsg}`) } @@ -205,6 +205,6 @@ export class NTQQUserApi { static async getClientKey(): Promise { const session = wrapperApi.NodeIQQNTWrapperSession - return await session.getTicketService().forceFetchClientKey('') + return await session?.getTicketService().forceFetchClientKey('') } } diff --git a/src/ntqqapi/api/webapi.ts b/src/ntqqapi/api/webapi.ts index 8f840ce..44c5063 100644 --- a/src/ntqqapi/api/webapi.ts +++ b/src/ntqqapi/api/webapi.ts @@ -121,20 +121,20 @@ export interface GroupEssenceMsgRet { } export class WebApi { - static async getGroupEssenceMsg(GroupCode: string, page_start: string): Promise { + static async getGroupEssenceMsg(GroupCode: string, page_start: string): Promise { 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; + 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: GroupEssenceMsgRet try { - ret = await RequestUtil.HttpGetJson(url, 'GET', '', { 'Cookie': CookieValue }); + ret = await RequestUtil.HttpGetJson(url, 'GET', '', { 'Cookie': CookieValue }) } catch { - return undefined; + return undefined } - //console.log(url, CookieValue); + //console.log(url, CookieValue) if (ret.retcode !== 0) { - return undefined; + return undefined } - return ret; + return ret } static async getGroupMembers(GroupCode: string, cached: boolean = true): Promise { diff --git a/src/ntqqapi/api/window.ts b/src/ntqqapi/api/window.ts index 767ad95..3d37138 100644 --- a/src/ntqqapi/api/window.ts +++ b/src/ntqqapi/api/window.ts @@ -27,7 +27,7 @@ export class NTQQWindowApi { static async openWindow( ntQQWindow: NTQQWindow, args: any[], - cbCmd: ReceiveCmd = null, + cbCmd: ReceiveCmd | null = null, autoCloseSeconds: number = 2, ) { const result = await callNTQQApi({ diff --git a/src/ntqqapi/constructor.ts b/src/ntqqapi/constructor.ts index 06bb2d2..a03bb04 100644 --- a/src/ntqqapi/constructor.ts +++ b/src/ntqqapi/constructor.ts @@ -283,7 +283,7 @@ export class SendMsgElementConstructor { if (faceId >= 222){ faceType = 2 } - if (face.AniStickerType){ + if (face?.AniStickerType){ faceType = 3; } return { @@ -292,10 +292,10 @@ export class SendMsgElementConstructor { faceElement: { faceIndex: faceId, faceType, - faceText: face.QDes, - stickerId: face.AniStickerId, - stickerType: face.AniStickerType, - packId: face.AniStickerPackId, + faceText: face?.QDes, + stickerId: face?.AniStickerId, + stickerType: face?.AniStickerType, + packId: face?.AniStickerPackId, sourceType: 1, }, } @@ -329,7 +329,7 @@ export class SendMsgElementConstructor { stickerId: '33', sourceType: 1, stickerType: 2, - resultId: resultId.toString(), + resultId: resultId?.toString(), surpriseId: '', // "randomType": 1, }, @@ -351,7 +351,7 @@ export class SendMsgElementConstructor { stickerId: '34', sourceType: 1, stickerType: 2, - resultId: resultId.toString(), + resultId: resultId?.toString(), surpriseId: '', // "randomType": 1, }, diff --git a/src/ntqqapi/hook.ts b/src/ntqqapi/hook.ts index 4f88a32..020c2e6 100644 --- a/src/ntqqapi/hook.ts +++ b/src/ntqqapi/hook.ts @@ -83,7 +83,7 @@ export function hookNTQQApiReceive(window: BrowserWindow) { let isLogger = false try { isLogger = args[0]?.eventName?.startsWith('ns-LoggerApi') - } catch (e) {} + } catch (e) { } if (!isLogger) { try { HOOK_LOG && log(`received ntqq api message: ${channel}`, args) @@ -102,7 +102,7 @@ export function hookNTQQApiReceive(window: BrowserWindow) { try { let _ = hook.hookFunc(receiveData.payload) if (hook.hookFunc.constructor.name === 'AsyncFunction') { - ;(_ as Promise).then() + ; (_ as Promise).then() } } catch (e) { log('hook error', e, receiveData.payload) @@ -123,7 +123,7 @@ export function hookNTQQApiReceive(window: BrowserWindow) { delete hookApiCallbacks[callbackId] } } - } catch (e) { + } catch (e: any) { log('hookNTQQApiReceive error', e.stack.toString(), args) } originalSend.call(window.webContents, channel, ...args) @@ -142,11 +142,11 @@ export function hookNTQQApiCall(window: BrowserWindow) { let isLogger = false try { isLogger = args[3][0].eventName.startsWith('ns-LoggerApi') - } catch (e) {} + } catch (e) { } if (!isLogger) { try { HOOK_LOG && log('call NTQQ api', thisArg, args) - } catch (e) {} + } catch (e) { } try { const _args: unknown[] = args[3][1] const cmdName: NTQQApiMethod = _args[0] as NTQQApiMethod @@ -157,7 +157,7 @@ export function hookNTQQApiCall(window: BrowserWindow) { try { let _ = hook.hookFunc(callParams) if (hook.hookFunc.constructor.name === 'AsyncFunction') { - ;(_ as Promise).then() + (_ as Promise).then() } } catch (e) { log('hook call error', e, _args) @@ -165,7 +165,7 @@ export function hookNTQQApiCall(window: BrowserWindow) { }).then() } }) - } catch (e) {} + } catch (e) { } } return target.apply(thisArg, args) }, @@ -189,7 +189,7 @@ export function hookNTQQApiCall(window: BrowserWindow) { let ret = target.apply(thisArg, args) try { HOOK_LOG && log('call NTQQ invoke api return', ret) - } catch (e) {} + } catch (e) { } return ret }, }) @@ -296,7 +296,7 @@ async function processGroupEvent(payload: { groupList: Group[] }) { // 判断bot是否是管理员,如果是管理员不需要从这里得知有人退群,这里的退群无法得知是主动退群还是被踢 let bot = await getGroupMember(group.groupCode, selfInfo.uin) - if (bot.role == GroupMemberRole.admin || bot.role == GroupMemberRole.owner) { + if (bot?.role == GroupMemberRole.admin || bot?.role == GroupMemberRole.owner) { continue } for (const member of oldMembers) { @@ -320,7 +320,7 @@ async function processGroupEvent(payload: { groupList: Group[] }) { } updateGroups(newGroupList, false).then() - } catch (e) { + } catch (e: any) { updateGroups(payload.groupList).then() log('更新群信息错误', e.stack.toString()) } @@ -328,7 +328,7 @@ async function processGroupEvent(payload: { groupList: Group[] }) { export async function startHook() { -// 群列表变动 + // 群列表变动 registerReceiveHook<{ groupList: Group[]; updateType: number }>(ReceiveCmdS.GROUPS, (payload) => { // updateType 3是群列表变动,2是群成员变动 // log("群列表变动", payload.updateType, payload.groupList) @@ -372,10 +372,11 @@ export async function startHook() { ) } else if (member.role != existMember.role) { log('有管理员变动通知') - let groupAdminNoticeEvent = new OB11GroupAdminNoticeEvent() - groupAdminNoticeEvent.group_id = parseInt(groupCode) - groupAdminNoticeEvent.user_id = parseInt(member.uin) - groupAdminNoticeEvent.sub_type = member.role == GroupMemberRole.admin ? 'set' : 'unset' + const groupAdminNoticeEvent = new OB11GroupAdminNoticeEvent( + member.role == GroupMemberRole.admin ? 'set' : 'unset', + parseInt(groupCode), + parseInt(member.uin) + ) postOb11Event(groupAdminNoticeEvent, true) } Object.assign(existMember, member) @@ -397,7 +398,7 @@ export async function startHook() { // } }) -// 好友列表变动 + // 好友列表变动 registerReceiveHook<{ data: CategoryFriend[] }>(ReceiveCmdS.FRIENDS, (payload) => { @@ -453,7 +454,7 @@ export async function startHook() { const pttPath = msgElement.pttElement?.filePath const filePath = msgElement.fileElement?.filePath const videoPath = msgElement.videoElement?.filePath - const videoThumbPath: string[] = [...msgElement.videoElement?.thumbPath.values()] + const videoThumbPath: string[] = [...msgElement.videoElement.thumbPath?.values()!] const pathList = [picPath, ...picThumbPath, pttPath, filePath, videoPath, ...videoThumbPath] if (msgElement.picElement) { pathList.push(...Object.values(msgElement.picElement.thumbPath)) @@ -471,7 +472,7 @@ export async function startHook() { }) } } - }, getConfigUtil().getConfig().autoDeleteFileSecond * 1000) + }, getConfigUtil().getConfig().autoDeleteFileSecond! * 1000) } } }) @@ -486,7 +487,7 @@ export async function startHook() { if (sendCallback) { try { sendCallback(message) - } catch (e) { + } catch (e: any) { log('receive self msg error', e.stack) } } @@ -518,8 +519,8 @@ export async function startHook() { NTQQMsgApi.getMsgHistory(peer, '', 20).then(({ msgList }) => { let lastTempMsg = msgList.pop() log('激活窗口之前的第一条临时会话消息:', lastTempMsg) - if (Date.now() / 1000 - parseInt(lastTempMsg.msgTime) < 5) { - OB11Constructor.message(lastTempMsg).then((r) => postOb11Event(r)) + if (Date.now() / 1000 - parseInt(lastTempMsg?.msgTime!) < 5) { + OB11Constructor.message(lastTempMsg!).then((r) => postOb11Event(r)) } }) }) @@ -550,5 +551,4 @@ export async function startHook() { log('重新激活聊天窗口', peer, { result: r.result, errMsg: r.errMsg }) }) }) - } \ No newline at end of file diff --git a/src/ntqqapi/ntcall.ts b/src/ntqqapi/ntcall.ts index 12b8441..aed5651 100644 --- a/src/ntqqapi/ntcall.ts +++ b/src/ntqqapi/ntcall.ts @@ -1,7 +1,6 @@ import { ipcMain } from 'electron' import { hookApiCallbacks, ReceiveCmd, ReceiveCmdS, registerReceiveHook, removeReceiveHook } from './hook' import { log } from '../common/utils/log' -import { NTQQWindow, NTQQWindowApi, NTQQWindows } from './api/window' import { HOOK_LOG } from '../common/config' import { randomUUID } from 'node:crypto' diff --git a/src/onebot11/action/BaseAction.ts b/src/onebot11/action/BaseAction.ts index 2290693..390a443 100644 --- a/src/onebot11/action/BaseAction.ts +++ b/src/onebot11/action/BaseAction.ts @@ -4,8 +4,8 @@ import { OB11Return } from '../types' import { log } from '../../common/utils/log' -class BaseAction { - actionName: ActionName +abstract class BaseAction { + abstract actionName: ActionName protected async check(payload: PayloadType): Promise { return { @@ -21,7 +21,7 @@ class BaseAction { try { const resData = await this._handle(payload) return OB11Response.ok(resData) - } catch (e) { + } catch (e: any) { log('发生错误', e) return OB11Response.error(e?.toString() || e?.stack?.toString() || '未知错误,可能操作超时', 200) } @@ -35,7 +35,7 @@ class BaseAction { try { const resData = await this._handle(payload) return OB11Response.ok(resData, echo) - } catch (e) { + } catch (e: any) { log('发生错误', e) return OB11Response.error(e.stack?.toString() || e.toString(), 1200, echo) } diff --git a/src/onebot11/action/file/GetFile.ts b/src/onebot11/action/file/GetFile.ts index a15aa33..a231de0 100644 --- a/src/onebot11/action/file/GetFile.ts +++ b/src/onebot11/action/file/GetFile.ts @@ -20,7 +20,7 @@ export interface GetFileResponse { base64?: string } -export class GetFileBase extends BaseAction { +export abstract class GetFileBase extends BaseAction { private getElement(msg: RawMessage, elementId: string): VideoElement | FileElement { let element = msg.elements.find((e) => e.elementId === elementId) if (!element) { @@ -41,7 +41,7 @@ export class GetFileBase extends BaseAction { // 等待文件下载完成 msg = await dbUtil.getMsgByLongId(cache.msgId) log('下载完成后的msg', msg) - cache.filePath = this.getElement(msg, cache.elementId).filePath + cache.filePath = this.getElement(msg!, cache.elementId).filePath await checkFileReceived(cache.filePath, 10 * 1000) dbUtil.addFileCache(file, cache).then() } diff --git a/src/onebot11/action/file/GetRecord.ts b/src/onebot11/action/file/GetRecord.ts index a73de3c..b2be062 100644 --- a/src/onebot11/action/file/GetRecord.ts +++ b/src/onebot11/action/file/GetRecord.ts @@ -14,7 +14,7 @@ export default class GetRecord extends GetFileBase { protected async _handle(payload: Payload): Promise { let res = await super._handle(payload) - res.file = await decodeSilk(res.file, payload.out_format) + res.file = await decodeSilk(res.file!, payload.out_format) res.file_name = path.basename(res.file) res.file_size = fs.statSync(res.file).size.toString() if (getConfigUtil().getConfig().enableLocalFile2Url){ diff --git a/src/onebot11/action/go-cqhttp/GetForwardMsg.ts b/src/onebot11/action/go-cqhttp/GetForwardMsg.ts index 884cdfb..c2ece3a 100644 --- a/src/onebot11/action/go-cqhttp/GetForwardMsg.ts +++ b/src/onebot11/action/go-cqhttp/GetForwardMsg.ts @@ -1,6 +1,6 @@ import BaseAction from '../BaseAction' import { OB11ForwardMessage, OB11Message, OB11MessageData } from '../../types' -import { NTQQMsgApi, Peer } from '../../../ntqqapi/api' +import { NTQQMsgApi } from '@/ntqqapi/api' import { dbUtil } from '../../../common/db' import { OB11Constructor } from '../../constructor' import { ActionName } from '../types' @@ -37,12 +37,13 @@ export class GoCQHTTGetForwardMsgAction extends BaseAction { let messages = await Promise.all( msgList.map(async (msg) => { let resMsg = await OB11Constructor.message(msg) - resMsg.message_id = await dbUtil.addMsg(msg) + resMsg.message_id = (await dbUtil.addMsg(msg))! return resMsg }), ) - messages.map((msg) => { - ;(msg).content = msg.message + messages.map(v => { + const msg = v as Partial + msg.content = msg.message delete msg.message }) return { messages } diff --git a/src/onebot11/action/go-cqhttp/GetGroupMsgHistory.ts b/src/onebot11/action/go-cqhttp/GetGroupMsgHistory.ts index 5c85074..dc0b469 100644 --- a/src/onebot11/action/go-cqhttp/GetGroupMsgHistory.ts +++ b/src/onebot11/action/go-cqhttp/GetGroupMsgHistory.ts @@ -6,7 +6,6 @@ import { ChatType } from '../../../ntqqapi/types' import { dbUtil } from '../../../common/db' import { NTQQMsgApi } from '../../../ntqqapi/api/msg' import { OB11Constructor } from '../../constructor' -import { log } from '../../../common/utils' interface Payload { group_id: number diff --git a/src/onebot11/action/go-cqhttp/UploadFile.ts b/src/onebot11/action/go-cqhttp/UploadFile.ts index 4428c9b..2a9cc3a 100644 --- a/src/onebot11/action/go-cqhttp/UploadFile.ts +++ b/src/onebot11/action/go-cqhttp/UploadFile.ts @@ -1,11 +1,12 @@ import BaseAction from '../BaseAction' -import { getGroup, getUidByUin } from '../../../common/data' +import { getGroup, getUidByUin } from '@/common/data' import { ActionName } from '../types' -import { SendMsgElementConstructor } from '../../../ntqqapi/constructor' -import { ChatType, SendFileElement } from '../../../ntqqapi/types' +import { SendMsgElementConstructor } from '@/ntqqapi/constructor' +import { ChatType, SendFileElement } from '@/ntqqapi/types' import fs from 'fs' -import { NTQQMsgApi, Peer } from '../../../ntqqapi/api/msg' -import { uri2local } from '../../../common/utils' +import { NTQQMsgApi } from '@/ntqqapi/api/msg' +import { uri2local } from '@/common/utils' +import { Peer } from '@/ntqqapi/types' interface Payload { user_id: number @@ -20,9 +21,9 @@ class GoCQHTTPUploadFileBase extends BaseAction { getPeer(payload: Payload): Peer { if (payload.user_id) { - return { chatType: ChatType.friend, peerUid: getUidByUin(payload.user_id.toString()) } + return { chatType: ChatType.friend, peerUid: getUidByUin(payload.user_id.toString())! } } - return { chatType: ChatType.group, peerUid: payload.group_id.toString() } + return { chatType: ChatType.group, peerUid: payload.group_id?.toString()! } } protected async _handle(payload: Payload): Promise { diff --git a/src/onebot11/action/group/GetGroupEssence.ts b/src/onebot11/action/group/GetGroupEssence.ts index f48b4eb..d87a066 100644 --- a/src/onebot11/action/group/GetGroupEssence.ts +++ b/src/onebot11/action/group/GetGroupEssence.ts @@ -1,24 +1,24 @@ -import { GroupEssenceMsgRet, WebApi } from "@/ntqqapi/api"; -import BaseAction from "../BaseAction"; -import { ActionName } from "../types"; +import { GroupEssenceMsgRet, WebApi } from '@/ntqqapi/api' +import BaseAction from '../BaseAction' +import { ActionName } from '../types' interface PayloadType { - group_id: number; - pages?: number; + group_id: number + pages?: number } -export class GetGroupEssence extends BaseAction { - actionName = ActionName.GoCQHTTP_GetEssenceMsg; +export class GetGroupEssence extends BaseAction { + actionName = ActionName.GoCQHTTP_GetEssenceMsg protected async _handle(payload: PayloadType) { throw '此 api 暂不支持' - const ret = await WebApi.getGroupEssenceMsg(payload.group_id.toString(), payload.pages?.toString() || '0'); + const ret = await WebApi.getGroupEssenceMsg(payload.group_id.toString(), payload.pages?.toString() || '0') if (!ret) { - throw new Error('获取失败'); + throw new Error('获取失败') } // ret.map((item) => { // // }) - return ret; + return ret } } diff --git a/src/onebot11/action/group/GetGroupHonorInfo.ts b/src/onebot11/action/group/GetGroupHonorInfo.ts index 9e72ecd..a216e27 100644 --- a/src/onebot11/action/group/GetGroupHonorInfo.ts +++ b/src/onebot11/action/group/GetGroupHonorInfo.ts @@ -1,22 +1,23 @@ -import { WebApi, WebHonorType } from "@/ntqqapi/api"; -import { ActionName } from "../types"; -import BaseAction from "../BaseAction"; +import { WebApi, WebHonorType } from '@/ntqqapi/api' +import { ActionName } from '../types' +import BaseAction from '../BaseAction' interface Payload { - group_id: number, + group_id: number type?: WebHonorType } + export class GetGroupHonorInfo extends BaseAction> { - actionName = ActionName.GetGroupHonorInfo; + actionName = ActionName.GetGroupHonorInfo protected async _handle(payload: Payload) { - // console.log(await NTQQUserApi.getRobotUinRange()); + // console.log(await NTQQUserApi.getRobotUinRange()) if (!payload.group_id) { - throw '缺少参数group_id'; + throw '缺少参数group_id' } if (!payload.type) { - payload.type = WebHonorType.ALL; + payload.type = WebHonorType.ALL } - return await WebApi.getGroupHonorInfo(payload.group_id.toString(), payload.type); + return await WebApi.getGroupHonorInfo(payload.group_id.toString(), payload.type) } } diff --git a/src/onebot11/action/group/SendGroupMsg.ts b/src/onebot11/action/group/SendGroupMsg.ts index 954c005..6da8813 100644 --- a/src/onebot11/action/group/SendGroupMsg.ts +++ b/src/onebot11/action/group/SendGroupMsg.ts @@ -2,13 +2,11 @@ import SendMsg from '../msg/SendMsg' import { ActionName, BaseCheckResult } from '../types' import { OB11PostSendMsg } from '../../types' -import { log } from '../../../common/utils/log' - class SendGroupMsg extends SendMsg { actionName = ActionName.SendGroupMsg protected async check(payload: OB11PostSendMsg): Promise { - delete payload.user_id + delete (payload as Partial).user_id payload.message_type = 'group' return super.check(payload) } diff --git a/src/onebot11/action/msg/ForwardSingleMsg.ts b/src/onebot11/action/msg/ForwardSingleMsg.ts index 0c86def..01083cf 100644 --- a/src/onebot11/action/msg/ForwardSingleMsg.ts +++ b/src/onebot11/action/msg/ForwardSingleMsg.ts @@ -1,9 +1,10 @@ import BaseAction from '../BaseAction' -import { NTQQMsgApi, Peer } from '../../../ntqqapi/api' -import { ChatType, RawMessage } from '../../../ntqqapi/types' -import { dbUtil } from '../../../common/db' -import { getUidByUin } from '../../../common/data' +import { NTQQMsgApi } from '@/ntqqapi/api' +import { ChatType, RawMessage } from '@/ntqqapi/types' +import { dbUtil } from '@/common/db' +import { getUidByUin } from '@/common/data' import { ActionName } from '../types' +import { Peer } from '@/ntqqapi/types' interface Payload { message_id: number @@ -15,16 +16,16 @@ interface Response { message_id: number } -class ForwardSingleMsg extends BaseAction { +abstract class ForwardSingleMsg extends BaseAction { protected async getTargetPeer(payload: Payload): Promise { if (payload.user_id) { - return { chatType: ChatType.friend, peerUid: getUidByUin(payload.user_id.toString()) } + return { chatType: ChatType.friend, peerUid: getUidByUin(payload.user_id.toString())! } } return { chatType: ChatType.group, peerUid: payload.group_id.toString() } } protected async _handle(payload: Payload): Promise { - const msg = await dbUtil.getMsgByShortId(payload.message_id) + const msg = (await dbUtil.getMsgByShortId(payload.message_id))! const peer = await this.getTargetPeer(payload) const sentMsg = await NTQQMsgApi.forwardMsg( { @@ -35,7 +36,7 @@ class ForwardSingleMsg extends BaseAction { [msg.msgId], ) const ob11MsgId = await dbUtil.addMsg(sentMsg) - return {message_id: ob11MsgId} + return { message_id: ob11MsgId! } } } diff --git a/src/onebot11/action/msg/SendMsg.ts b/src/onebot11/action/msg/SendMsg.ts index b5eb8b2..9f249cb 100644 --- a/src/onebot11/action/msg/SendMsg.ts +++ b/src/onebot11/action/msg/SendMsg.ts @@ -142,7 +142,7 @@ export async function createSendElements( .RemainAtAllCountForUin log(`群${groupCode}剩余at全体次数`, remainAtAllCount) const self = await getGroupMember((target as Group)?.groupCode, selfInfo.uin) - isAdmin = self.role === GroupMemberRole.admin || self.role === GroupMemberRole.owner + isAdmin = self?.role === GroupMemberRole.admin || self?.role === GroupMemberRole.owner } catch (e) { } } @@ -171,8 +171,8 @@ export async function createSendElements( SendMsgElementConstructor.reply( replyMsg.msgSeq, replyMsg.msgId, - replyMsg.senderUin, - replyMsg.senderUin, + replyMsg.senderUin!, + replyMsg.senderUin!, ), ) } @@ -251,7 +251,7 @@ export async function createSendElements( await SendMsgElementConstructor.pic( path, sendMsg.data.summary || '', - parseInt(sendMsg.data?.subType?.toString()) || 0, + parseInt(sendMsg.data?.subType?.toString()!) || 0, ), ) } @@ -266,16 +266,16 @@ export async function createSendElements( case OB11MessageDataType.poke: { let qq = sendMsg.data?.qq || sendMsg.data?.id if (qq) { - if ('groupCode' in target) { + if ('groupCode' in target!) { crychic.sendGroupPoke(target.groupCode, qq.toString()) } else { if (!qq) { - qq = parseInt(target.uin) + qq = parseInt(target?.uin!) } crychic.sendFriendPoke(qq.toString()) } - sendElements.push(SendMsgElementConstructor.poke('', '')) + sendElements.push(SendMsgElementConstructor.poke('', '')!) } } break @@ -387,10 +387,10 @@ export class SendMsg extends BaseAction { let group: Group | undefined = undefined let friend: Friend | undefined = undefined const genGroupPeer = async () => { - group = await getGroup(payload.group_id.toString()) + group = await getGroup(payload.group_id?.toString()!) peer.chatType = ChatType.group // peer.name = group.name - peer.peerUid = group.groupCode + peer.peerUid = group?.groupCode! } const genFriendPeer = () => { @@ -429,8 +429,8 @@ export class SendMsg extends BaseAction { if (this.getSpecialMsgNum(messages, OB11MessageDataType.node)) { try { const returnMsg = await this.handleForwardNode(peer, messages as OB11MessageNode[], group) - return { message_id: returnMsg.msgShortId } - } catch (e) { + return { message_id: returnMsg?.msgShortId! } + } catch (e: any) { throw '发送转发消息失败 ' + e.toString() } } @@ -447,8 +447,9 @@ export class SendMsg extends BaseAction { } const postData: MusicSignPostData = { ...music.data } if (type === 'custom' && music.data.content) { - ;(postData as CustomMusicSignPostData).singer = music.data.content - delete (postData as OB11MessageCustomMusic['data']).content + const data = postData as CustomMusicSignPostData + data.singer = music.data.content + delete (data as OB11MessageCustomMusic['data']).content } if (type === 'custom') { const customMusicData = music.data as CustomMusicSignPostData @@ -493,7 +494,7 @@ export class SendMsg extends BaseAction { const returnMsg = await sendMsg(peer, sendElements, deleteAfterSentFiles) deleteAfterSentFiles.map((f) => fs.unlink(f, () => { })) - return { message_id: returnMsg.msgShortId } + return { message_id: returnMsg.msgShortId! } } private getSpecialMsgNum(message: OB11MessageData[], msgType: OB11MessageDataType): number { @@ -503,7 +504,7 @@ export class SendMsg extends BaseAction { return 0 } - private async cloneMsg(msg: RawMessage): Promise { + private async cloneMsg(msg: RawMessage): Promise { log('克隆的目标消息', msg) let sendElements: SendMessageElement[] = [] for (const ele of msg.elements) { @@ -549,11 +550,11 @@ export class SendMsg extends BaseAction { if (nodeId) { let nodeMsg = await dbUtil.getMsgByShortId(parseInt(nodeId)) if (!needClone) { - nodeMsgIds.push(nodeMsg.msgId) + nodeMsgIds.push(nodeMsg?.msgId!) } else { - if (nodeMsg.peerUid !== selfInfo.uid) { - const cloneMsg = await this.cloneMsg(nodeMsg) + if (nodeMsg?.peerUid !== selfInfo.uid) { + const cloneMsg = await this.cloneMsg(nodeMsg!) if (cloneMsg) { nodeMsgIds.push(cloneMsg.msgId) } @@ -605,7 +606,7 @@ export class SendMsg extends BaseAction { // 检查srcPeer是否一致,不一致则需要克隆成自己的消息, 让所有srcPeer都变成自己的,使其保持一致才能够转发 let nodeMsgArray: Array = [] - let srcPeer: Peer = null + let srcPeer: Peer | null = null let needSendSelf = false for (const [index, msgId] of nodeMsgIds.entries()) { const nodeMsg = await dbUtil.getMsgByLongId(msgId) @@ -648,7 +649,7 @@ export class SendMsg extends BaseAction { } try { log('开发转发', nodeMsgIds) - return await NTQQMsgApi.multiForwardMsg(srcPeer, destPeer, nodeMsgIds) + return await NTQQMsgApi.multiForwardMsg(srcPeer!, destPeer, nodeMsgIds) } catch (e) { log('forward failed', e) return null diff --git a/src/onebot11/action/quick-operation.ts b/src/onebot11/action/quick-operation.ts index 4042cc3..a68f86e 100644 --- a/src/onebot11/action/quick-operation.ts +++ b/src/onebot11/action/quick-operation.ts @@ -71,17 +71,17 @@ async function handleMsg(msg: OB11Message, quickAction: QuickOperationPrivateMes peerUid: msg.user_id.toString(), } if (msg.message_type == 'private') { - peer.peerUid = getUidByUin(msg.user_id.toString()) + peer.peerUid = getUidByUin(msg.user_id.toString())! if (msg.sub_type === 'group') { peer.chatType = ChatType.temp } } else { peer.chatType = ChatType.group - peer.peerUid = msg.group_id.toString() + peer.peerUid = msg.group_id?.toString()! } if (reply) { - let group: Group = null + let group: Group | null = null let replyMessage: OB11MessageData[] = [] if (ob11Config.enableQOAutoQuote) { replyMessage.push({ @@ -93,7 +93,7 @@ async function handleMsg(msg: OB11Message, quickAction: QuickOperationPrivateMes } if (msg.message_type == 'group') { - group = await getGroup(msg.group_id.toString()) + group = (await getGroup(msg.group_id?.toString()!))! if ((quickAction as QuickOperationGroupMessage).at_sender) { replyMessage.push({ type: 'at', @@ -104,7 +104,7 @@ async function handleMsg(msg: OB11Message, quickAction: QuickOperationPrivateMes } } replyMessage = replyMessage.concat(convertMessage2List(reply, quickAction.auto_escape)) - const { sendElements, deleteAfterSentFiles } = await createSendElements(replyMessage, group) + const { sendElements, deleteAfterSentFiles } = await createSendElements(replyMessage, group!) log(`发送消息给`, peer, sendElements) sendMsg(peer, sendElements, deleteAfterSentFiles, false).then().catch(log) } @@ -112,15 +112,15 @@ async function handleMsg(msg: OB11Message, quickAction: QuickOperationPrivateMes const groupMsgQuickAction = quickAction as QuickOperationGroupMessage // handle group msg if (groupMsgQuickAction.delete) { - NTQQMsgApi.recallMsg(peer, [rawMessage.msgId]).then().catch(log) + NTQQMsgApi.recallMsg(peer, [rawMessage?.msgId!]).then().catch(log) } if (groupMsgQuickAction.kick) { - NTQQGroupApi.kickMember(peer.peerUid, [rawMessage.senderUid]).then().catch(log) + NTQQGroupApi.kickMember(peer.peerUid, [rawMessage?.senderUid!]).then().catch(log) } if (groupMsgQuickAction.ban) { NTQQGroupApi.banMember(peer.peerUid, [ { - uid: rawMessage.senderUid, + uid: rawMessage?.senderUid!, timeStamp: groupMsgQuickAction.ban_duration || 60 * 30, }, ]).then().catch(log) diff --git a/src/onebot11/action/system/CleanCache.ts b/src/onebot11/action/system/CleanCache.ts index 9d5d95f..a7b6dc0 100644 --- a/src/onebot11/action/system/CleanCache.ts +++ b/src/onebot11/action/system/CleanCache.ts @@ -1,9 +1,8 @@ import BaseAction from '../BaseAction' import { ActionName } from '../types' -import fs from 'fs' -import Path from 'path' +import fs from 'node:fs' +import Path from 'node:path' import { ChatType, ChatCacheListItemBasic, CacheFileType } from '../../../ntqqapi/types' -import { dbUtil } from '../../../common/db' import { NTQQFileApi, NTQQFileCacheApi } from '../../../ntqqapi/api/file' export default class CleanCache extends BaseAction { @@ -12,14 +11,16 @@ export default class CleanCache extends BaseAction { protected _handle(): Promise { return new Promise(async (res, rej) => { try { - // dbUtil.clearCache(); + // dbUtil.clearCache() const cacheFilePaths: string[] = [] await NTQQFileCacheApi.setCacheSilentScan(false) cacheFilePaths.push(await NTQQFileCacheApi.getHotUpdateCachePath()) cacheFilePaths.push(await NTQQFileCacheApi.getDesktopTmpPath()) - ;(await NTQQFileCacheApi.getCacheSessionPathList()).forEach((e) => cacheFilePaths.push(e.value)) + + const list = await NTQQFileCacheApi.getCacheSessionPathList() + list.forEach((e) => cacheFilePaths.push(e.value)) // await NTQQApi.addCacheScannedPaths(); // XXX: 调用就崩溃,原因目前还未知 const cacheScanResult = await NTQQFileCacheApi.scanCache() diff --git a/src/onebot11/action/system/GetStatus.ts b/src/onebot11/action/system/GetStatus.ts index dd7d77d..cac3008 100644 --- a/src/onebot11/action/system/GetStatus.ts +++ b/src/onebot11/action/system/GetStatus.ts @@ -8,7 +8,7 @@ export default class GetStatus extends BaseAction { protected async _handle(payload: any): Promise { return { - online: selfInfo.online, + online: selfInfo.online!, good: true, } } diff --git a/src/onebot11/action/user/SendLike.ts b/src/onebot11/action/user/SendLike.ts index 9bfa2a4..34694ac 100644 --- a/src/onebot11/action/user/SendLike.ts +++ b/src/onebot11/action/user/SendLike.ts @@ -19,7 +19,7 @@ export default class SendLike extends BaseAction { const friend = await getFriend(qq) let uid: string if (!friend) { - uid = getUidByUin(qq) + uid = getUidByUin(qq)! } else { uid = friend.uid } diff --git a/src/onebot11/constructor.ts b/src/onebot11/constructor.ts index d740355..c688b81 100644 --- a/src/onebot11/constructor.ts +++ b/src/onebot11/constructor.ts @@ -39,7 +39,7 @@ import { NTQQFileApi } from '../ntqqapi/api/file' import { NTQQMsgApi } from '../ntqqapi/api/msg' import { calcQQLevel } from '../common/utils/qqlevel' import { log } from '../common/utils/log' -import { sleep } from '../common/utils/helper' +import { isNull, sleep } from '../common/utils/helper' import { getConfigUtil } from '../common/config' import { OB11GroupTitleEvent } from './event/notice/OB11GroupTitleEvent' import { OB11GroupCardEvent } from './event/notice/OB11GroupCardEvent' @@ -67,14 +67,14 @@ export class OB11Constructor { const message_type = msg.chatType == ChatType.group ? 'group' : 'private' const resMsg: OB11Message = { self_id: parseInt(selfInfo.uin), - user_id: parseInt(msg.senderUin), + user_id: parseInt(msg.senderUin!), time: parseInt(msg.msgTime) || Date.now(), - message_id: msg.msgShortId, - real_id: msg.msgShortId, - message_seq: msg.msgShortId, + message_id: msg.msgShortId!, + real_id: msg.msgShortId!, + message_seq: msg.msgShortId!, message_type: msg.chatType == ChatType.group ? 'group' : 'private', sender: { - user_id: parseInt(msg.senderUin), + user_id: parseInt(msg.senderUin!), nickname: msg.sendNickName, card: msg.sendMemberName || '', }, @@ -91,7 +91,7 @@ export class OB11Constructor { if (msg.chatType == ChatType.group) { resMsg.sub_type = 'normal' resMsg.group_id = parseInt(msg.peerUin) - const member = await getGroupMember(msg.peerUin, msg.senderUin) + const member = await getGroupMember(msg.peerUin, msg.senderUin!) if (member) { resMsg.sender.role = OB11Constructor.groupMemberRole(member.role) resMsg.sender.nickname = member.nick @@ -99,7 +99,7 @@ export class OB11Constructor { } else if (msg.chatType == ChatType.friend) { resMsg.sub_type = 'friend' - const friend = await getFriend(msg.senderUin) + const friend = await getFriend(msg.senderUin!) if (friend) { resMsg.sender.nickname = friend.nick } @@ -140,7 +140,7 @@ export class OB11Constructor { message_data = { type: OB11MessageDataType.at, data: { - qq, + qq: qq!, name } } @@ -160,12 +160,12 @@ export class OB11Constructor { const replyMsg = await dbUtil.getMsgBySeqId(element.replyElement.replayMsgSeq) // log("找到回复消息", replyMsg.msgShortId, replyMsg.msgId) if (replyMsg) { - message_data['data']['id'] = replyMsg.msgShortId.toString() + message_data['data']['id'] = replyMsg.msgShortId?.toString() } else { continue } - } catch (e) { + } catch (e: any) { log('获取不到引用的消息', e.stack, element.replyElement.replayMsgSeq) } } @@ -221,12 +221,12 @@ export class OB11Constructor { ) } dbUtil - .addFileCache(videoOrFileElement.fileUuid, { + .addFileCache(videoOrFileElement.fileUuid!, { msgId: msg.msgId, elementId: element.elementId, fileName: videoOrFileElement.fileName, filePath: videoOrFileElement.filePath, - fileSize: videoOrFileElement.fileSize, + fileSize: videoOrFileElement.fileSize!, downloadFunc: async () => { await NTQQFileApi.downloadMedia( msg.msgId, @@ -234,7 +234,7 @@ export class OB11Constructor { msg.peerUid, element.elementId, ob11MessageDataType == OB11MessageDataType.video - ? (videoOrFileElement as VideoElement).thumbPath.get(0) + ? (videoOrFileElement as VideoElement).thumbPath?.get(0) : null, videoOrFileElement.filePath, ) @@ -297,7 +297,7 @@ export class OB11Constructor { message_data['data']['emoji_id'] = element.marketFaceElement.emojiId message_data['data']['emoji_package_id'] = String(element.marketFaceElement.emojiPackageId) message_data['data']['key'] = element.marketFaceElement.key - mFaceCache.set(md5, element.marketFaceElement.faceName) + mFaceCache.set(md5, element.marketFaceElement.faceName!) } else if (element.markdownElement) { message_data['type'] = OB11MessageDataType.markdown @@ -321,7 +321,7 @@ export class OB11Constructor { return resMsg } - static async PrivateEvent(msg: RawMessage): Promise { + static async PrivateEvent(msg: RawMessage): Promise { if (msg.chatType !== ChatType.friend) { return } @@ -350,7 +350,7 @@ export class OB11Constructor { } } - static async GroupEvent(msg: RawMessage): Promise { + static async GroupEvent(msg: RawMessage): Promise { if (msg.chatType !== ChatType.group) { return } @@ -360,10 +360,10 @@ export class OB11Constructor { const event = new OB11GroupCardEvent( parseInt(msg.peerUid), parseInt(msg.senderUin), - msg.sendMemberName, + msg.sendMemberName!, member.cardName, ) - member.cardName = msg.sendMemberName + member.cardName = msg.sendMemberName! return event } } @@ -393,10 +393,10 @@ export class OB11Constructor { } else if (groupElement.type === TipGroupElementType.ban) { log('收到群群员禁言提示', groupElement) - const memberUid = groupElement.shutUp.member.uid - const adminUid = groupElement.shutUp.admin.uid + const memberUid = groupElement.shutUp?.member.uid + const adminUid = groupElement.shutUp?.admin.uid let memberUin: string = '' - let duration = parseInt(groupElement.shutUp.duration) + let duration = parseInt(groupElement.shutUp?.duration!) let sub_type: 'ban' | 'lift_ban' = duration > 0 ? 'ban' : 'lift_ban' if (memberUid) { memberUin = @@ -410,7 +410,7 @@ export class OB11Constructor { } } const adminUin = - (await getGroupMember(msg.peerUid, adminUid))?.uin || (await NTQQUserApi.getUserDetailInfo(adminUid))?.uin + (await getGroupMember(msg.peerUid, adminUid!))?.uin || (await NTQQUserApi.getUserDetailInfo(adminUid!))?.uin if (memberUin && adminUin) { return new OB11GroupBanEvent( parseInt(msg.peerUid), @@ -443,8 +443,8 @@ export class OB11Constructor { } } else if (element.fileElement) { - return new OB11GroupUploadNoticeEvent(parseInt(msg.peerUid), parseInt(msg.senderUin), { - id: element.fileElement.fileUuid, + return new OB11GroupUploadNoticeEvent(parseInt(msg.peerUid), parseInt(msg.senderUin!), { + id: element.fileElement.fileUuid!, name: element.fileElement.fileName, size: parseInt(element.fileElement.fileSize), busid: element.fileElement.fileBizId || 0, @@ -476,13 +476,13 @@ export class OB11Constructor { if (!msg) { return } - return new OB11GroupMsgEmojiLikeEvent(parseInt(msg.peerUid), parseInt(senderUin), msg.msgShortId, [ + return new OB11GroupMsgEmojiLikeEvent(parseInt(msg.peerUid), parseInt(senderUin), msg.msgShortId!, [ { emoji_id: emojiId, count: 1, }, ]) - } catch (e) { + } catch (e: any) { log('解析表情回应消息失败', e.stack) } } @@ -495,8 +495,8 @@ export class OB11Constructor { if (xmlElement?.content) { const regex = /jp="(\d+)"/g - let matches = [] - let match = null + const matches: string[] = [] + let match: RegExpExecArray | null = null while ((match = regex.exec(xmlElement.content)) !== null) { matches.push(match[1]) @@ -557,20 +557,22 @@ export class OB11Constructor { } 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 + const postMsg = await dbUtil.getMsgBySeqId(origMsg?.msgSeq!) ?? origMsg // 如果 senderUin 为 0,可能是 历史消息 或 自身消息 if (msgList[0].senderUin === '0') { 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) { const memberUin = json.items[1].param[0] const title = json.items[3].txt log('收到群成员新头衔消息', json) - getGroupMember(msg.peerUid, memberUin).then((member) => { - member.memberSpecialTitle = title + getGroupMember(msg.peerUid, memberUin).then(member => { + if (!isNull(member)) { + member.memberSpecialTitle = title + } }) return new OB11GroupTitleEvent(parseInt(msg.peerUid), parseInt(memberUin), title) } @@ -592,16 +594,16 @@ export class OB11Constructor { const revokeElement = msgElement.grayTipElement.revokeElement if (isGroup) { const operator = await getGroupMember(msg.peerUid, revokeElement.operatorUid) - const sender = await getGroupMember(msg.peerUid, revokeElement.origMsgSenderUid) + const sender = await getGroupMember(msg.peerUid, revokeElement.origMsgSenderUid!) return new OB11GroupRecallNoticeEvent( parseInt(msg.peerUid), - parseInt(sender.uin), - parseInt(operator.uin), - msg.msgShortId, + parseInt(sender?.uin!), + parseInt(operator?.uin!), + msg.msgShortId!, ) } else { - return new OB11FriendRecallNoticeEvent(parseInt(msg.senderUin), msg.msgShortId) + return new OB11FriendRecallNoticeEvent(parseInt(msg.senderUin!), msg.msgShortId!) } } @@ -610,7 +612,7 @@ export class OB11Constructor { user_id: parseInt(friend.uin), nickname: friend.nick, remark: friend.remark, - sex: OB11Constructor.sex(friend.sex), + sex: OB11Constructor.sex(friend.sex!), level: (friend.qqLevel && calcQQLevel(friend.qqLevel)) || 0, } } @@ -668,7 +670,7 @@ export class OB11Constructor { user_id: parseInt(member.uin), nickname: member.nick, card: member.cardName, - sex: OB11Constructor.sex(member.sex), + sex: OB11Constructor.sex(member.sex!), age: 0, area: '', level: 0, @@ -690,7 +692,7 @@ export class OB11Constructor { ...user, user_id: parseInt(user.uin), nickname: user.nick, - sex: OB11Constructor.sex(user.sex), + sex: OB11Constructor.sex(user.sex!), age: 0, qid: user.qid, login_days: 0, diff --git a/src/onebot11/event/OB11BaseEvent.ts b/src/onebot11/event/OB11BaseEvent.ts index 27cb8bc..bcdd45b 100644 --- a/src/onebot11/event/OB11BaseEvent.ts +++ b/src/onebot11/event/OB11BaseEvent.ts @@ -11,5 +11,5 @@ export enum EventType { export abstract class OB11BaseEvent { time = Math.floor(Date.now() / 1000) self_id = parseInt(selfInfo.uin) - post_type: EventType + abstract post_type: EventType } diff --git a/src/onebot11/event/meta/OB11BaseMetaEvent.ts b/src/onebot11/event/meta/OB11BaseMetaEvent.ts index c2460a9..94e6ff8 100644 --- a/src/onebot11/event/meta/OB11BaseMetaEvent.ts +++ b/src/onebot11/event/meta/OB11BaseMetaEvent.ts @@ -2,5 +2,5 @@ import { EventType, OB11BaseEvent } from '../OB11BaseEvent' export abstract class OB11BaseMetaEvent extends OB11BaseEvent { post_type = EventType.META - meta_event_type: string + abstract meta_event_type: string } diff --git a/src/onebot11/event/notice/OB11GroupAdminNoticeEvent.ts b/src/onebot11/event/notice/OB11GroupAdminNoticeEvent.ts index 024e818..47349ae 100644 --- a/src/onebot11/event/notice/OB11GroupAdminNoticeEvent.ts +++ b/src/onebot11/event/notice/OB11GroupAdminNoticeEvent.ts @@ -2,5 +2,14 @@ import { OB11GroupNoticeEvent } from './OB11GroupNoticeEvent' export class OB11GroupAdminNoticeEvent extends OB11GroupNoticeEvent { notice_type = 'group_admin' - sub_type: 'set' | 'unset' // "set" | "unset" + sub_type: 'set' | 'unset' + group_id: number + user_id: number + + constructor(subType: 'set' | 'unset', groupId: number, userId: number) { + super() + this.sub_type = subType + this.group_id = groupId + this.user_id = userId + } } diff --git a/src/onebot11/event/notice/OB11GroupBanEvent.ts b/src/onebot11/event/notice/OB11GroupBanEvent.ts index 1dec45b..41b4526 100644 --- a/src/onebot11/event/notice/OB11GroupBanEvent.ts +++ b/src/onebot11/event/notice/OB11GroupBanEvent.ts @@ -5,6 +5,8 @@ export class OB11GroupBanEvent extends OB11GroupNoticeEvent { operator_id: number duration: number sub_type: 'ban' | 'lift_ban' + group_id: number + user_id: number constructor(groupId: number, userId: number, operatorId: number, duration: number, sub_type: 'ban' | 'lift_ban') { super() diff --git a/src/onebot11/event/notice/OB11GroupCardEvent.ts b/src/onebot11/event/notice/OB11GroupCardEvent.ts index 1953741..93b83b8 100644 --- a/src/onebot11/event/notice/OB11GroupCardEvent.ts +++ b/src/onebot11/event/notice/OB11GroupCardEvent.ts @@ -4,6 +4,8 @@ export class OB11GroupCardEvent extends OB11GroupNoticeEvent { notice_type = 'group_card' card_new: string card_old: string + group_id: number + user_id: number constructor(groupId: number, userId: number, cardNew: string, cardOld: string) { super() diff --git a/src/onebot11/event/notice/OB11GroupDecreaseEvent.ts b/src/onebot11/event/notice/OB11GroupDecreaseEvent.ts index 66edb01..8a74c66 100644 --- a/src/onebot11/event/notice/OB11GroupDecreaseEvent.ts +++ b/src/onebot11/event/notice/OB11GroupDecreaseEvent.ts @@ -6,6 +6,8 @@ export class OB11GroupDecreaseEvent extends OB11GroupNoticeEvent { notice_type = 'group_decrease' sub_type: GroupDecreaseSubType = 'leave' // TODO: 实现其他几种子类型的识别 ("leave" | "kick" | "kick_me") operator_id: number + group_id: number + user_id: number constructor(groupId: number, userId: number, operatorId: number, subType: GroupDecreaseSubType = 'leave') { super() diff --git a/src/onebot11/event/notice/OB11GroupEssenceEvent.ts b/src/onebot11/event/notice/OB11GroupEssenceEvent.ts index ae1053e..b6402f0 100644 --- a/src/onebot11/event/notice/OB11GroupEssenceEvent.ts +++ b/src/onebot11/event/notice/OB11GroupEssenceEvent.ts @@ -1,14 +1,16 @@ import { OB11GroupNoticeEvent } from './OB11GroupNoticeEvent'; export class OB11GroupEssenceEvent extends OB11GroupNoticeEvent { - notice_type = 'essence'; - message_id: number; - sender_id: number; - sub_type: 'add' | 'delete' = 'add'; + notice_type = 'essence' + message_id: number + sender_id: number + sub_type: 'add' | 'delete' = 'add' + group_id: number + user_id: number = 0 constructor(groupId: number, message_id: number, sender_id: number) { - super(); - this.group_id = groupId; - this.message_id = message_id; - this.sender_id = sender_id; + super() + this.group_id = groupId + this.message_id = message_id + this.sender_id = sender_id } } diff --git a/src/onebot11/event/notice/OB11GroupIncreaseEvent.ts b/src/onebot11/event/notice/OB11GroupIncreaseEvent.ts index 12c76b4..34ec381 100644 --- a/src/onebot11/event/notice/OB11GroupIncreaseEvent.ts +++ b/src/onebot11/event/notice/OB11GroupIncreaseEvent.ts @@ -1,10 +1,14 @@ import { OB11GroupNoticeEvent } from './OB11GroupNoticeEvent' type GroupIncreaseSubType = 'approve' | 'invite' + export class OB11GroupIncreaseEvent extends OB11GroupNoticeEvent { notice_type = 'group_increase' operator_id: number sub_type: GroupIncreaseSubType + group_id: number + user_id: number + constructor(groupId: number, userId: number, operatorId: number, subType: GroupIncreaseSubType = 'approve') { super() this.group_id = groupId diff --git a/src/onebot11/event/notice/OB11GroupNoticeEvent.ts b/src/onebot11/event/notice/OB11GroupNoticeEvent.ts index 2be3ae6..a768fb4 100644 --- a/src/onebot11/event/notice/OB11GroupNoticeEvent.ts +++ b/src/onebot11/event/notice/OB11GroupNoticeEvent.ts @@ -1,6 +1,7 @@ import { OB11BaseNoticeEvent } from './OB11BaseNoticeEvent' export abstract class OB11GroupNoticeEvent extends OB11BaseNoticeEvent { - group_id: number - user_id: number + abstract group_id: number + abstract user_id: number + abstract notice_type: string } diff --git a/src/onebot11/event/notice/OB11GroupRecallNoticeEvent.ts b/src/onebot11/event/notice/OB11GroupRecallNoticeEvent.ts index b396038..420ef5a 100644 --- a/src/onebot11/event/notice/OB11GroupRecallNoticeEvent.ts +++ b/src/onebot11/event/notice/OB11GroupRecallNoticeEvent.ts @@ -4,6 +4,8 @@ export class OB11GroupRecallNoticeEvent extends OB11GroupNoticeEvent { notice_type = 'group_recall' operator_id: number message_id: number + group_id: number + user_id: number constructor(groupId: number, userId: number, operatorId: number, messageId: number) { super() diff --git a/src/onebot11/event/notice/OB11GroupTitleEvent.ts b/src/onebot11/event/notice/OB11GroupTitleEvent.ts index db83911..dbca65e 100644 --- a/src/onebot11/event/notice/OB11GroupTitleEvent.ts +++ b/src/onebot11/event/notice/OB11GroupTitleEvent.ts @@ -4,6 +4,8 @@ export class OB11GroupTitleEvent extends OB11GroupNoticeEvent { notice_type = 'notify' sub_type = 'title' title: string + group_id: number + user_id: number constructor(groupId: number, userId: number, title: string) { super() diff --git a/src/onebot11/event/notice/OB11GroupUploadNoticeEvent.ts b/src/onebot11/event/notice/OB11GroupUploadNoticeEvent.ts index 2163e80..63944d2 100644 --- a/src/onebot11/event/notice/OB11GroupUploadNoticeEvent.ts +++ b/src/onebot11/event/notice/OB11GroupUploadNoticeEvent.ts @@ -10,6 +10,8 @@ export interface GroupUploadFile { export class OB11GroupUploadNoticeEvent extends OB11GroupNoticeEvent { notice_type = 'group_upload' file: GroupUploadFile + group_id: number + user_id: number constructor(groupId: number, userId: number, file: GroupUploadFile) { super() diff --git a/src/onebot11/event/notice/OB11MsgEmojiLikeEvent.ts b/src/onebot11/event/notice/OB11MsgEmojiLikeEvent.ts index 1a9a662..9173aa0 100644 --- a/src/onebot11/event/notice/OB11MsgEmojiLikeEvent.ts +++ b/src/onebot11/event/notice/OB11MsgEmojiLikeEvent.ts @@ -8,14 +8,17 @@ export interface MsgEmojiLike { export class OB11GroupMsgEmojiLikeEvent extends OB11GroupNoticeEvent { notice_type = 'group_msg_emoji_like' message_id: number - sub_type: 'ban' | 'lift_ban' + sub_type?: 'ban' | 'lift_ban' likes: MsgEmojiLike[] + group_id: number + user_id: number - constructor(groupId: number, userId: number, messageId: number, likes: MsgEmojiLike[]) { + constructor(groupId: number, userId: number, messageId: number, likes: MsgEmojiLike[], sub_type?: 'ban' | 'lift_ban') { super() this.group_id = groupId this.user_id = userId // 可为空,表示是对别人的消息操作,如果是对bot自己的消息则不为空 this.message_id = messageId this.likes = likes + this.sub_type = sub_type } } diff --git a/src/onebot11/event/notice/OB11PokeEvent.ts b/src/onebot11/event/notice/OB11PokeEvent.ts index 0f98ef6..c025b0a 100644 --- a/src/onebot11/event/notice/OB11PokeEvent.ts +++ b/src/onebot11/event/notice/OB11PokeEvent.ts @@ -1,16 +1,15 @@ import { OB11BaseNoticeEvent } from './OB11BaseNoticeEvent' -import { selfInfo } from '../../../common/data' -import { OB11BaseEvent } from '../OB11BaseEvent' -class OB11PokeEvent extends OB11BaseNoticeEvent { +abstract class OB11PokeEvent extends OB11BaseNoticeEvent { notice_type = 'notify' sub_type = 'poke' target_id = 0 - user_id: number + abstract user_id: number raw_message: any } export class OB11FriendPokeEvent extends OB11PokeEvent { + user_id: number constructor(user_id: number, target_id: number, raw_message: any) { super(); @@ -21,6 +20,7 @@ export class OB11FriendPokeEvent extends OB11PokeEvent { } export class OB11GroupPokeEvent extends OB11PokeEvent { + user_id: number group_id: number constructor(group_id: number, user_id: number = 0, target_id: number = 0, raw_message: any) { super() diff --git a/src/onebot11/event/request/OB11FriendRequest.ts b/src/onebot11/event/request/OB11FriendRequest.ts index 66724cf..3f6f021 100644 --- a/src/onebot11/event/request/OB11FriendRequest.ts +++ b/src/onebot11/event/request/OB11FriendRequest.ts @@ -4,7 +4,15 @@ import { EventType } from '../OB11BaseEvent' export class OB11FriendRequestEvent extends OB11BaseNoticeEvent { post_type = EventType.REQUEST user_id: number - request_type: 'friend' = 'friend' + request_type: 'friend' comment: string flag: string + + constructor(userId: number, comment: string, flag: string, requestType: 'friend' = 'friend') { + super() + this.user_id = userId + this.comment = comment + this.flag = flag + this.request_type = requestType + } } diff --git a/src/onebot11/event/request/OB11GroupRequest.ts b/src/onebot11/event/request/OB11GroupRequest.ts index 2f27f02..9f6dfeb 100644 --- a/src/onebot11/event/request/OB11GroupRequest.ts +++ b/src/onebot11/event/request/OB11GroupRequest.ts @@ -1,11 +1,24 @@ -import { OB11GroupNoticeEvent } from '../notice/OB11GroupNoticeEvent' +import { OB11BaseNoticeEvent } from '../notice/OB11BaseNoticeEvent' import { EventType } from '../OB11BaseEvent' -export class OB11GroupRequestEvent extends OB11GroupNoticeEvent { +export class OB11GroupRequestEvent extends OB11BaseNoticeEvent { post_type = EventType.REQUEST - request_type: 'group' = 'group' - sub_type: 'add' | 'invite' = 'add' - invitor_id: number | undefined = undefined - comment: string + request_type: 'group' + sub_type: 'add' | 'invite' + invitor_id: number | undefined + comment?: string flag: string + group_id: number + user_id: number + + constructor(groupId: number, userId: number, flag: string, comment?: string, invitorId?: number, subType: 'add' | 'invite' = 'add', requestType: 'group' = 'group') { + super() + this.group_id = groupId + this.user_id = userId + this.comment = comment + this.flag = flag + this.request_type = requestType + this.sub_type = subType + this.invitor_id = invitorId + } } diff --git a/src/onebot11/server/event-for-http.ts b/src/onebot11/server/event-for-http.ts index 086aa2a..d310a9a 100644 --- a/src/onebot11/server/event-for-http.ts +++ b/src/onebot11/server/event-for-http.ts @@ -1,5 +1,4 @@ -import { PostEventType } from "./post-ob11-event" - +import { PostEventType } from './post-ob11-event' interface HttpEventType { seq: number @@ -11,58 +10,56 @@ interface HttpUserType { userSeq: number } -let curentSeq:number = 0; -let eventList:HttpEventType[] = []; -let httpUser:Record = {}; - +let curentSeq: number = 0 +const eventList: HttpEventType[] = [] +const httpUser: Record = {} export function postHttpEvent(event: PostEventType) { - curentSeq += 1; + curentSeq += 1 eventList.push({ seq: curentSeq, event: event }); - while(eventList.length > 100) { - eventList.shift(); + while (eventList.length > 100) { + eventList.shift() } } - -export async function getHttpEvent(userKey:string,timeout = 0) { - let toRetEvent = []; +export async function getHttpEvent(userKey: string, timeout = 0) { + const toRetEvent: PostEventType[] = [] // 清除过时的user,5分钟没访问过的user将被删除 - let now = Date.now(); - for(let key in httpUser) { - let user = httpUser[key]; - if(now - user.lastAccessTime > 1000 * 60 * 5) { - delete httpUser[key]; + const now = Date.now(); + for (let key in httpUser) { + let user = httpUser[key] + if (now - user.lastAccessTime > 1000 * 60 * 5) { + delete httpUser[key] } } // 增加新的user - if(!httpUser[userKey] ) { + if (!httpUser[userKey]) { httpUser[userKey] = { lastAccessTime: now, userSeq: curentSeq } } - let user = httpUser[userKey]; + const user = httpUser[userKey] // 等待数据到来,暂时先这么写吧...... - while(curentSeq == user.userSeq && Date.now() - now < timeout) { - await new Promise( resolve => setTimeout(resolve, 10) ); + while (curentSeq == user.userSeq && Date.now() - now < timeout) { + await new Promise(resolve => setTimeout(resolve, 10)) } // 取数据 - for(let i = 0; i < eventList.length; i++) { - let evt = eventList[i]; - if(evt.seq > user.userSeq) { - toRetEvent.push(evt.event); + for (let i = 0; i < eventList.length; i++) { + let evt = eventList[i] + if (evt.seq > user.userSeq) { + toRetEvent.push(evt.event) } } // 更新user数据 - user.lastAccessTime = Date.now(); - user.userSeq = curentSeq; - return toRetEvent; + user.lastAccessTime = Date.now() + user.userSeq = curentSeq + return toRetEvent } diff --git a/src/onebot11/server/http.ts b/src/onebot11/server/http.ts index 9c2aab8..79f3a2e 100644 --- a/src/onebot11/server/http.ts +++ b/src/onebot11/server/http.ts @@ -40,7 +40,7 @@ class HTTPHeart { } this.intervalId = setInterval(() => { // ws的心跳是ws自己维护的 - postOb11Event(new OB11HeartbeatEvent(selfInfo.online, true, heartInterval), false, false) + postOb11Event(new OB11HeartbeatEvent(selfInfo.online!, true, heartInterval!), false, false) }, heartInterval) } diff --git a/src/onebot11/server/post-ob11-event.ts b/src/onebot11/server/post-ob11-event.ts index c781ea6..f6d2cb4 100644 --- a/src/onebot11/server/post-ob11-event.ts +++ b/src/onebot11/server/post-ob11-event.ts @@ -43,7 +43,7 @@ export function postOb11Event(msg: PostEventType, reportSelf = false, postWs = t } if (config.ob11.enableHttpPost) { const msgStr = JSON.stringify(msg) - const hmac = crypto.createHmac('sha1', config.ob11.httpSecret) + const hmac = crypto.createHmac('sha1', config.ob11.httpSecret!) hmac.update(msgStr) const sig = hmac.digest('hex') let headers = { @@ -79,9 +79,8 @@ export function postOb11Event(msg: PostEventType, reportSelf = false, postWs = t if (postWs) { postWsEvent(msg) } - if(!(msg.post_type == 'meta_event' && (msg as OB11BaseMetaEvent).meta_event_type == 'heartbeat')) { + if (!(msg.post_type == 'meta_event' && (msg as OB11BaseMetaEvent).meta_event_type == 'heartbeat')) { // 不上报心跳 postHttpEvent(msg) } - } diff --git a/src/onebot11/server/ws/ReverseWebsocket.ts b/src/onebot11/server/ws/ReverseWebsocket.ts index 5bd4618..47ab083 100644 --- a/src/onebot11/server/ws/ReverseWebsocket.ts +++ b/src/onebot11/server/ws/ReverseWebsocket.ts @@ -15,7 +15,7 @@ import { version } from '../../../version' export let rwsList: ReverseWebsocket[] = [] export class ReverseWebsocket { - public websocket: WebSocketClass + public websocket?: WebSocketClass public url: string private running: boolean = false @@ -27,38 +27,38 @@ export class ReverseWebsocket { public stop() { this.running = false - this.websocket.close() + this.websocket?.close() } public onopen() { - wsReply(this.websocket, new OB11LifeCycleEvent(LifeCycleSubType.CONNECT)) + wsReply(this.websocket!, new OB11LifeCycleEvent(LifeCycleSubType.CONNECT)) } public async onmessage(msg: string) { - let receiveData: { action: ActionName; params: any; echo?: any } = { action: null, params: {} } + let receiveData: { action: ActionName | null; params: any; echo?: any } = { action: null, params: {} } let echo = null try { receiveData = JSON.parse(msg.toString()) echo = receiveData.echo log('收到反向Websocket消息', receiveData) } catch (e) { - return wsReply(this.websocket, OB11Response.error('json解析失败,请检查数据格式', 1400, echo)) + return wsReply(this.websocket!, OB11Response.error('json解析失败,请检查数据格式', 1400, echo)) } - const action: BaseAction = actionMap.get(receiveData.action) + const action: BaseAction = actionMap.get(receiveData.action!)! if (!action) { - return wsReply(this.websocket, OB11Response.error('不支持的api ' + receiveData.action, 1404, echo)) + return wsReply(this.websocket!, OB11Response.error('不支持的api ' + receiveData.action, 1404, echo)) } try { let handleResult = await action.websocketHandle(receiveData.params, echo) - wsReply(this.websocket, handleResult) + wsReply(this.websocket!, handleResult) } catch (e) { - wsReply(this.websocket, OB11Response.error(`api处理出错:${e}`, 1200, echo)) + wsReply(this.websocket!, OB11Response.error(`api处理出错:${e}`, 1200, echo)) } } - public onclose = function () { + public onclose = () => { log('反向ws断开', this.url) - unregisterWsEventSender(this.websocket) + unregisterWsEventSender(this.websocket!) if (this.running) { this.reconnect() } @@ -104,7 +104,7 @@ export class ReverseWebsocket { this.websocket.on('error', log) const wsClientInterval = setInterval(() => { - postWsEvent(new OB11HeartbeatEvent(selfInfo.online, true, heartInterval)) + postWsEvent(new OB11HeartbeatEvent(selfInfo.online!, true, heartInterval!)) }, heartInterval) // 心跳包 this.websocket.on('close', () => { clearInterval(wsClientInterval) @@ -121,7 +121,7 @@ class OB11ReverseWebsockets { new Promise(() => { try { rwsList.push(new ReverseWebsocket(url)) - } catch (e) { + } catch (e: any) { log(e.stack) } }).then() @@ -132,7 +132,7 @@ class OB11ReverseWebsockets { for (let rws of rwsList) { try { rws.stop() - } catch (e) { + } catch (e: any) { log('反向ws关闭:', e.stack) } } diff --git a/src/onebot11/server/ws/WebsocketServer.ts b/src/onebot11/server/ws/WebsocketServer.ts index a1235fa..eeb8b9f 100644 --- a/src/onebot11/server/ws/WebsocketServer.ts +++ b/src/onebot11/server/ws/WebsocketServer.ts @@ -13,15 +13,13 @@ import { selfInfo } from '../../../common/data' import { log } from '../../../common/utils/log' import { getConfigUtil } from '../../../common/config' -let heartbeatRunning = false - class OB11WebsocketServer extends WebsocketServerBase { authorizeFailed(wsClient: WebSocket) { wsClient.send(JSON.stringify(OB11Response.res(null, 'failed', 1403, 'token验证失败'))) } async handleAction(wsClient: WebSocket, actionName: string, params: any, echo?: any) { - const action: BaseAction = actionMap.get(actionName) + const action: BaseAction = actionMap.get(actionName)! if (!action) { return wsReply(wsClient, OB11Response.error('不支持的api ' + actionName, 1404, echo)) } @@ -29,7 +27,7 @@ class OB11WebsocketServer extends WebsocketServerBase { let handleResult = await action.websocketHandle(params, echo) handleResult.echo = echo wsReply(wsClient, handleResult) - } catch (e) { + } catch (e: any) { wsReply(wsClient, OB11Response.error(`api处理出错:${e.stack}`, 1200, echo)) } } @@ -37,7 +35,7 @@ class OB11WebsocketServer extends WebsocketServerBase { onConnect(wsClient: WebSocket, url: string, req: IncomingMessage) { if (url == '/api' || url == '/api/' || url == '/') { wsClient.on('message', async (msg) => { - let receiveData: { action: ActionName; params: any; echo?: any } = { action: null, params: {} } + let receiveData: { action: ActionName | null; params: any; echo?: any } = { action: null, params: {} } let echo = null try { receiveData = JSON.parse(msg.toString()) @@ -46,7 +44,7 @@ class OB11WebsocketServer extends WebsocketServerBase { } catch (e) { return wsReply(wsClient, OB11Response.error('json解析失败,请检查数据格式', 1400, echo)) } - this.handleAction(wsClient, receiveData.action, receiveData.params, receiveData.echo).then() + this.handleAction(wsClient, receiveData.action!, receiveData.params, receiveData.echo).then() }) } if (url == '/event' || url == '/event/' || url == '/') { @@ -61,7 +59,7 @@ class OB11WebsocketServer extends WebsocketServerBase { } const { heartInterval } = getConfigUtil().getConfig() const wsClientInterval = setInterval(() => { - postWsEvent(new OB11HeartbeatEvent(selfInfo.online, true, heartInterval)) + postWsEvent(new OB11HeartbeatEvent(selfInfo.online!, true, heartInterval!)) }, heartInterval) // 心跳包 wsClient.on('close', () => { log('event上报ws客户端已断开') diff --git a/src/onebot11/server/ws/reply.ts b/src/onebot11/server/ws/reply.ts index cde3e05..98263e9 100644 --- a/src/onebot11/server/ws/reply.ts +++ b/src/onebot11/server/ws/reply.ts @@ -12,7 +12,7 @@ export function wsReply(wsClient: WebSocketClass, data: OB11Response | PostEvent } wsClient.send(JSON.stringify(packet)) log('ws 消息上报', wsClient.url || '', data) - } catch (e) { + } catch (e: any) { log('websocket 回复失败', e.stack, data) } } diff --git a/src/renderer/components/item.ts b/src/renderer/components/item.ts index a8735e6..4732bc4 100644 --- a/src/renderer/components/item.ts +++ b/src/renderer/components/item.ts @@ -1,6 +1,6 @@ export const SettingItem = ( title: string, - subtitle?: string, + subtitle?: string | null, action?: string, id?: string, visible: boolean = true, diff --git a/src/renderer/components/select.ts b/src/renderer/components/select.ts index 5caf8cd..8562491 100644 --- a/src/renderer/components/select.ts +++ b/src/renderer/components/select.ts @@ -30,11 +30,11 @@ window.customElements.define( super() this.attachShadow({ mode: 'open' }) - this.shadowRoot.append(SelectTemplate.content.cloneNode(true)) + this.shadowRoot?.append(SelectTemplate.content.cloneNode(true)) - this._button = this.shadowRoot.querySelector('div[part="button"]') - this._text = this.shadowRoot.querySelector('input[part="current-text"]') - this._context = this.shadowRoot.querySelector('ul[part="option-list"]') + this._button = this.shadowRoot?.querySelector('div[part="button"]')! + this._text = this.shadowRoot?.querySelector('input[part="current-text"]')! + this._context = this.shadowRoot?.querySelector('ul[part="option-list"]')! const buttonClick = () => { const isHidden = this._context.classList.toggle('hidden') @@ -46,7 +46,8 @@ window.customElements.define( } this._button.addEventListener('click', buttonClick) - this._context.addEventListener('click', ({ target }: MouseEventExtend) => { + this._context.addEventListener('click', e => { + const { target } = e as MouseEventExtend if (target.tagName !== 'SETTING-OPTION') return buttonClick() @@ -55,7 +56,7 @@ window.customElements.define( this.querySelectorAll('setting-option[is-selected]').forEach((dom) => dom.toggleAttribute('is-selected')) target.toggleAttribute('is-selected') - this._text.value = target.textContent + this._text.value = target.textContent! this.dispatchEvent( new CustomEvent('selected', { bubbles: true, @@ -68,7 +69,7 @@ window.customElements.define( ) }) - this._text.value = this.querySelector('setting-option[is-selected]').textContent + this._text.value = this.querySelector('setting-option[is-selected]')?.textContent! } }, ) diff --git a/src/renderer/index.ts b/src/renderer/index.ts index f176c0c..2cdc9a6 100644 --- a/src/renderer/index.ts +++ b/src/renderer/index.ts @@ -12,16 +12,16 @@ function aprilFoolsEgg(node: Element) { let today = new Date() if (today.getDate() === 1) { console.log('超时空猫猫!!!') - node.querySelector('.name').innerHTML = 'ChronoCat' + node.querySelector('.name')!.innerHTML = 'ChronoCat' } } function initSideBar() { document.querySelectorAll('.nav-item.liteloader').forEach((node) => { - if (node.textContent.startsWith('LLOneBot')) { + if (node.textContent?.startsWith('LLOneBot')) { aprilFoolsEgg(node) let iconEle = node.querySelector('.q-icon') - iconEle.innerHTML = iconSvg + iconEle!.innerHTML = iconSvg } }) } @@ -64,11 +64,11 @@ async function onSettingWindowCreated(view: Element) { ), ]), SettingList([ - SettingItem( - '是否启用 LLOneBot, 重启QQ后生效', - null, - SettingSwitch('enableLLOB', config.enableLLOB, { 'control-display-id': 'config-enableLLOB' }), - )] + SettingItem( + '是否启用 LLOneBot, 重启QQ后生效', + null, + SettingSwitch('enableLLOB', config.enableLLOB, { 'control-display-id': 'config-enableLLOB' }), + )] ), SettingList([ SettingItem( @@ -234,29 +234,29 @@ async function onSettingWindowCreated(view: Element) { await new Promise((res) => setTimeout(() => res(true), 1000)) const errDom = document.querySelector('#llonebot-error') || doc.querySelector('#llonebot-error') - const errCodeDom = errDom.querySelector('code') + const errCodeDom = errDom?.querySelector('code') const errMsg = await window.llonebot.getError() if (!errMsg) { - errDom.classList.remove('show') + errDom?.classList.remove('show') } else { - errDom.classList.add('show') + errDom?.classList.add('show') } - errCodeDom.innerHTML = errMsg + errCodeDom!.innerHTML = errMsg } showError().then() // 外链按钮 - doc.querySelector('#open-github').addEventListener('click', () => { + doc.querySelector('#open-github')?.addEventListener('click', () => { window.LiteLoader.api.openExternal('https://github.com/LLOneBot/LLOneBot') }) - doc.querySelector('#open-telegram').addEventListener('click', () => { + doc.querySelector('#open-telegram')?.addEventListener('click', () => { window.LiteLoader.api.openExternal('https://t.me/+nLZEnpne-pQ1OWFl') }) - doc.querySelector('#open-qq-group').addEventListener('click', () => { + doc.querySelector('#open-qq-group')?.addEventListener('click', () => { window.LiteLoader.api.openExternal('https://qm.qq.com/q/bDnHRG38aI') }) - doc.querySelector('#open-docs').addEventListener('click', () => { + doc.querySelector('#open-docs')?.addEventListener('click', () => { window.LiteLoader.api.openExternal('https://llonebot.github.io/') }) // 生成反向地址列表 @@ -303,14 +303,14 @@ async function onSettingWindowCreated(view: Element) { } const addReverseHost = (type: string, doc: Document = document, inputAttr: any = {}) => { const hostContainerDom = doc.body.querySelector(`#config-ob11-${type}-list`) - hostContainerDom.appendChild(buildHostListItem(type, '', ob11Config[type].length, inputAttr)) + hostContainerDom?.appendChild(buildHostListItem(type, '', ob11Config[type].length, inputAttr)) ob11Config[type].push('') } const initReverseHost = (type: string, doc: Document = document) => { const hostContainerDom = doc.body.querySelector(`#config-ob11-${type}-list`) - ;[...hostContainerDom.childNodes].forEach((dom) => dom.remove()) + ;[...hostContainerDom?.childNodes!].forEach((dom) => dom.remove()) buildHostList(ob11Config[type], type).forEach((dom) => { - hostContainerDom.appendChild(dom) + hostContainerDom?.appendChild(dom) }) } initReverseHost('httpHosts', doc) @@ -318,42 +318,43 @@ async function onSettingWindowCreated(view: Element) { doc .querySelector('#config-ob11-httpHosts-add') - .addEventListener('click', () => + ?.addEventListener('click', () => addReverseHost('httpHosts', document, { placeholder: '如:http://127.0.0.1:5140/onebot' }), ) doc .querySelector('#config-ob11-wsHosts-add') - .addEventListener('click', () => + ?.addEventListener('click', () => addReverseHost('wsHosts', document, { placeholder: '如:ws://127.0.0.1:5140/onebot' }), ) - doc.querySelector('#config-ffmpeg-select').addEventListener('click', () => { + doc.querySelector('#config-ffmpeg-select')?.addEventListener('click', () => { window.llonebot.selectFile().then((path) => { if (!isEmpty(path)) { setConfig('ffmpeg', path) - document.querySelector('#config-ffmpeg-path-text').innerHTML = path + document.querySelector('#config-ffmpeg-path-text')!.innerHTML = path } }) }) - doc.querySelector('#config-open-log-path').addEventListener('click', () => { + doc.querySelector('#config-open-log-path')?.addEventListener('click', () => { window.LiteLoader.api.openPath(window.LiteLoader.plugins['LLOneBot'].path.data) }) // 开关 - doc.querySelectorAll('setting-switch[data-config-key]').forEach((dom: HTMLElement) => { + doc.querySelectorAll('setting-switch[data-config-key]').forEach(element => { + const dom = element as HTMLElement dom.addEventListener('click', () => { const active = dom.getAttribute('is-active') === null - setConfig(dom.dataset.configKey, active) + setConfig(dom.dataset.configKey!, active) if (active) dom.setAttribute('is-active', '') else dom.removeAttribute('is-active') if (!isEmpty(dom.dataset.controlDisplayId)) { const displayDom = document.querySelector(`#${dom.dataset.controlDisplayId}`) - if (active) displayDom.removeAttribute('is-hidden') - else displayDom.setAttribute('is-hidden', '') + if (active) displayDom?.removeAttribute('is-hidden') + else displayDom?.setAttribute('is-hidden', '') } }) }) @@ -361,28 +362,31 @@ async function onSettingWindowCreated(view: Element) { // 输入框 doc .querySelectorAll('setting-item .q-input input.q-input__inner[data-config-key]') - .forEach((dom: HTMLInputElement) => { + .forEach(element => { + const dom = element as HTMLInputElement dom.addEventListener('input', () => { const Type = dom.getAttribute('type') const configKey = dom.dataset.configKey const configValue = Type === 'number' ? (parseInt(dom.value) >= 1 ? parseInt(dom.value) : 1) : dom.value - setConfig(configKey, configValue) + setConfig(configKey!, configValue) }) }) // 下拉框 - doc.querySelectorAll('ob-setting-select[data-config-key]').forEach((dom: HTMLElement) => { - dom.addEventListener('selected', (e: CustomEvent) => { + doc?.querySelectorAll('ob-setting-select[data-config-key]').forEach(element => { + const dom = element as HTMLElement + dom?.addEventListener('selected', e => { + const { detail } = e as CustomEvent const configKey = dom.dataset.configKey - const configValue = e.detail.value + const configValue = detail.value - setConfig(configKey, configValue) + setConfig(configKey!, configValue) }) }) // 保存按钮 - doc.querySelector('#config-ob11-save').addEventListener('click', () => { + doc.querySelector('#config-ob11-save')?.addEventListener('click', () => { config.ob11 = ob11Config window.llonebot.setConfig(false, config) @@ -446,7 +450,7 @@ function init() { } if (location.hash === '#/blank') { - ;(window as any).navigation.addEventListener('navigatesuccess', init, { once: true }) + globalThis.navigation.addEventListener('navigatesuccess', init, { once: true }) } else { init() } diff --git a/tsconfig.json b/tsconfig.json index 63fb8a6..f3257f4 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,7 +3,8 @@ "target": "ESNext", "module": "commonjs", "outDir": "./dist", - "strict": false, + "strict": true, + "noImplicitAny": false, "esModuleInterop": true, "allowJs": true, "allowSyntheticDefaultImports": true, @@ -23,6 +24,12 @@ }, "noEmit": true }, - "include": ["src/*", "src/**/*", "scripts/*"], - "exclude": ["node_modules"] -} + "include": [ + "src/*", + "src/**/*", + "scripts/*" + ], + "exclude": [ + "node_modules" + ] +} \ No newline at end of file From 18f01b7f21d8d848b93c6124381257efa90d6f00 Mon Sep 17 00:00:00 2001 From: idranme Date: Tue, 6 Aug 2024 02:08:00 +0800 Subject: [PATCH 07/13] chore: v3.28.0 --- manifest.json | 2 +- src/version.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/manifest.json b/manifest.json index 30ce472..72829cc 100644 --- a/manifest.json +++ b/manifest.json @@ -4,7 +4,7 @@ "name": "LLOneBot", "slug": "LLOneBot", "description": "实现 OneBot 11 协议,用以 QQ 机器人开发", - "version": "3.27.4", + "version": "3.28.0", "icon": "./icon.webp", "authors": [ { diff --git a/src/version.ts b/src/version.ts index de6c1c0..a7c4e06 100644 --- a/src/version.ts +++ b/src/version.ts @@ -1 +1 @@ -export const version = '3.27.4' +export const version = '3.28.0' From e1ff366e10a28671265656a0bec05c46b5982a26 Mon Sep 17 00:00:00 2001 From: idranme Date: Tue, 6 Aug 2024 02:32:28 +0800 Subject: [PATCH 08/13] clean --- src/onebot11/action/msg/SendMsg.ts | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/src/onebot11/action/msg/SendMsg.ts b/src/onebot11/action/msg/SendMsg.ts index 9f249cb..25c25fd 100644 --- a/src/onebot11/action/msg/SendMsg.ts +++ b/src/onebot11/action/msg/SendMsg.ts @@ -34,7 +34,6 @@ import { ALLOW_SEND_TEMP_MSG, getConfigUtil } from '../../../common/config' import { log } from '../../../common/utils/log' import { sleep } from '../../../common/utils/helper' import { uri2local } from '../../../common/utils' -import { crychic } from '../../../ntqqapi/native/crychic' import { NTQQGroupApi } from '../../../ntqqapi/api' import { CustomMusicSignPostData, IdMusicSignPostData, MusicSign, MusicSignPostData } from '../../../common/utils/sign' import { Peer } from '../../../ntqqapi/types/msg' @@ -265,18 +264,6 @@ export async function createSendElements( break case OB11MessageDataType.poke: { let qq = sendMsg.data?.qq || sendMsg.data?.id - if (qq) { - if ('groupCode' in target!) { - crychic.sendGroupPoke(target.groupCode, qq.toString()) - } - else { - if (!qq) { - qq = parseInt(target?.uin!) - } - crychic.sendFriendPoke(qq.toString()) - } - sendElements.push(SendMsgElementConstructor.poke('', '')!) - } } break case OB11MessageDataType.dice: { From 9e57b2c17ec5db5dcb39ea16f36c25723d02ffdf Mon Sep 17 00:00:00 2001 From: idranme Date: Tue, 6 Aug 2024 14:51:17 +0800 Subject: [PATCH 09/13] Update publish.yml --- .github/workflows/publish.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index f5e159a..3216b8e 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -19,15 +19,14 @@ jobs: - name: install dependenies run: | export ELECTRON_SKIP_BINARY_DOWNLOAD=1 - npm install + yarn install - name: build - run: npm run build + run: yarn build - name: zip run: | sudo apt install zip -y - cp manifest.json ./dist/manifest.json cd ./dist/ zip -r ../LLOneBot.zip ./* From a5e3f94228548c7d3be87a4bc66e5193cf2bda99 Mon Sep 17 00:00:00 2001 From: idranme Date: Tue, 6 Aug 2024 22:26:21 +0800 Subject: [PATCH 10/13] chore: deps --- package.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index a571b60..2f3f21a 100644 --- a/package.json +++ b/package.json @@ -18,18 +18,18 @@ "dependencies": { "compressing": "^1.10.1", "cors": "^2.8.5", - "express": "^4.18.2", + "express": "^4.19.2", "fast-xml-parser": "^4.4.1", - "file-type": "^19.0.0", - "fluent-ffmpeg": "^2.1.2", + "file-type": "^19.4.0", + "fluent-ffmpeg": "^2.1.3", "level": "^8.0.1", "silk-wasm": "^3.6.1", "ws": "^8.18.0" }, "devDependencies": { "@types/cors": "^2.8.17", - "@types/express": "^4.17.20", - "@types/fluent-ffmpeg": "^2.1.24", + "@types/express": "^4.17.21", + "@types/fluent-ffmpeg": "^2.1.25", "@types/node": "^20.11.24", "@types/ws": "^8.5.12", "electron": "^29.0.1", From 4958e22770ce6521fac0a753a9ef87c35ed8a05c Mon Sep 17 00:00:00 2001 From: idranme Date: Tue, 6 Aug 2024 22:28:49 +0800 Subject: [PATCH 11/13] Update README.md --- README.md | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/README.md b/README.md index ca1690e..7d234d8 100644 --- a/README.md +++ b/README.md @@ -23,20 +23,6 @@ TG群: -## TODO - -- [x] 重构摆脱LLAPI,目前调用LLAPI只能在renderer进程调用,需重构成在main进程调用 -- [x] 支持正、反向websocket(感谢@disymayufei的PR) -- [x] 转发消息记录 -- [x] 好友点赞api -- [x] 群管理功能,禁言、踢人,改群名片等 -- [x] 视频消息 -- [x] 文件消息 -- [x] 群禁言事件上报 -- [x] 优化加群成功事件上报 -- [x] 清理缓存api -- [ ] 框架对接文档 - ## Stargazers over time [![Stargazers over time](https://starchart.cc/LLOneBot/LLOneBot.svg?variant=adaptive)](https://starchart.cc/LLOneBot/LLOneBot) From d7e40e488c99dfc3e6a0f894cfc676f7d4209b8d Mon Sep 17 00:00:00 2001 From: idranme Date: Tue, 6 Aug 2024 22:31:39 +0800 Subject: [PATCH 12/13] Update README.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit LLAPI 已删库 --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 7d234d8..03825db 100644 --- a/README.md +++ b/README.md @@ -30,8 +30,7 @@ TG群: ## 鸣谢 - [LiteLoaderQQNT](https://liteloaderqqnt.github.io/guide/install.html) -- [LLAPI](https://github.com/Night-stars-1/LiteLoaderQQNT-Plugin-LLAPI) -- [chronocat](https://github.com/chrononeko/chronocat/) +- [chronocat](https://github.com/chrononeko/chronocat) - [koishi-plugin-adapter-onebot](https://github.com/koishijs/koishi-plugin-adapter-onebot) - [silk-wasm](https://github.com/idranme/silk-wasm) From 5005d83ce0d85351c7001ca8544134304cc43572 Mon Sep 17 00:00:00 2001 From: idranme Date: Wed, 7 Aug 2024 04:22:51 +0800 Subject: [PATCH 13/13] opt: audio encoding and decoding --- src/common/utils/audio.ts | 216 ++++++++++++++------------------------ 1 file changed, 81 insertions(+), 135 deletions(-) diff --git a/src/common/utils/audio.ts b/src/common/utils/audio.ts index ea31221..7328a30 100644 --- a/src/common/utils/audio.ts +++ b/src/common/utils/audio.ts @@ -1,124 +1,93 @@ -import fs from 'fs' -import fsPromise from 'fs/promises' -import { decode, encode, getDuration, getWavFileInfo, isWav, isSilk } from 'silk-wasm' -import { log } from './log' import path from 'node:path' +import ffmpeg from 'fluent-ffmpeg' +import fsPromise from 'node:fs/promises' +import { decode, encode, getDuration, getWavFileInfo, isWav, isSilk, EncodeResult } from 'silk-wasm' +import { log } from './log' import { TEMP_DIR } from './index' import { getConfigUtil } from '../config' -import { spawn } from 'node:child_process' import { randomUUID } from 'node:crypto' +import { Readable } from 'node:stream' + +interface FFmpegOptions { + input?: string[] + output?: string[] +} + +type Input = string | Readable + +function convert(input: Input, options: FFmpegOptions): Promise +function convert(input: Input, options: FFmpegOptions, outputPath: string): Promise +function convert(input: Input, options: FFmpegOptions, outputPath?: string): Promise | Promise { + return new Promise((resolve, reject) => { + const chunks: Buffer[] = [] + let command = ffmpeg(input) + .on('error', err => { + log(`FFmpeg处理转换出错: `, err.message) + reject(err) + }) + .on('end', () => { + if (!outputPath) { + resolve(Buffer.concat(chunks)) + } else { + resolve(outputPath) + } + }) + if (options.input) { + command = command.inputOptions(options.input) + } + if (options.output) { + command = command.outputOptions(options.output) + } + const ffmpegPath = getConfigUtil().getConfig().ffmpeg + if (ffmpegPath) { + command = command.setFfmpegPath(ffmpegPath) + } + if (!outputPath) { + const stream = command.pipe() + stream.on('data', chunk => { + chunks.push(chunk) + }) + } else { + command.save(outputPath) + } + }) +} export async function encodeSilk(filePath: string) { - function getFileHeader(filePath: string) { - // 定义要读取的字节数 - const bytesToRead = 7 - try { - const buffer = fs.readFileSync(filePath, { - encoding: null, - flag: 'r', - }) - - const fileHeader = buffer.toString('hex', 0, bytesToRead) - return fileHeader - } catch (err) { - console.error('读取文件错误:', err) - return - } - } - - async function isWavFile(filePath: string) { - return isWav(fs.readFileSync(filePath)) - } - - async function guessDuration(pttPath: string) { - const pttFileInfo = await fsPromise.stat(pttPath) - let duration = pttFileInfo.size / 1024 / 3 // 3kb/s - duration = Math.floor(duration) - duration = Math.max(1, duration) - log(`通过文件大小估算语音的时长:`, duration) - return duration - } - - // function verifyDuration(oriDuration: number, guessDuration: number) { - // // 单位都是秒 - // if (oriDuration - guessDuration > 10) { - // return guessDuration - // } - // oriDuration = Math.max(1, oriDuration) - // return oriDuration - // } - // async function getAudioSampleRate(filePath: string) { - // try { - // const mm = await import('music-metadata'); - // const metadata = await mm.parseFile(filePath); - // log(`${filePath}采样率`, metadata.format.sampleRate); - // return metadata.format.sampleRate; - // } catch (error) { - // log(`${filePath}采样率获取失败`, error.stack); - // // console.error(error); - // } - // } - try { const file = await fsPromise.readFile(filePath) - const pttPath = path.join(TEMP_DIR, randomUUID()) if (!isSilk(file)) { log(`语音文件${filePath}需要转换成silk`) - const _isWav = isWav(file) - const pcmPath = pttPath + '.pcm' - let sampleRate = 0 - const convert = () => { - return new Promise((resolve, reject) => { - const ffmpegPath = getConfigUtil().getConfig().ffmpeg || process.env.FFMPEG_PATH || 'ffmpeg' - const cp = spawn(ffmpegPath, ['-y', '-i', filePath, '-ar', '24000', '-ac', '1', '-f', 's16le', pcmPath]) - cp.on('error', (err) => { - log(`FFmpeg处理转换出错: `, err.message) - return reject(err) - }) - cp.on('exit', (code, signal) => { - const EXIT_CODES = [0, 255] - if (code == null || EXIT_CODES.includes(code)) { - sampleRate = 24000 - const data = fs.readFileSync(pcmPath) - fs.unlink(pcmPath, (err) => { - }) - return resolve(data) - } - log(`FFmpeg exit: code=${code ?? 'unknown'} sig=${signal ?? 'unknown'}`) - reject(Error(`FFmpeg处理转换失败`)) - }) - }) - } - let input: Buffer - if (!_isWav) { - input = await convert() + let result: EncodeResult + const allowSampleRate = [8000, 12000, 16000, 24000, 32000, 44100, 48000] + if (isWav(file) && allowSampleRate.includes(getWavFileInfo(file).fmt.sampleRate)) { + result = await encode(file, 0) } else { - input = file - const allowSampleRate = [8000, 12000, 16000, 24000, 32000, 44100, 48000] - const { fmt } = getWavFileInfo(input) - // log(`wav文件信息`, fmt) - if (!allowSampleRate.includes(fmt.sampleRate)) { - input = await convert() - } + const input = await convert(filePath, { + output: [ + '-ar 24000', + '-ac 1', + '-f s16le' + ] + }) + result = await encode(input, 24000) } - const silk = await encode(input, sampleRate) - fs.writeFileSync(pttPath, silk.data) - log(`语音文件${filePath}转换成功!`, pttPath, `时长:`, silk.duration) + const pttPath = path.join(TEMP_DIR, randomUUID()) + await fsPromise.writeFile(pttPath, result.data) + log(`语音文件${filePath}转换成功!`, pttPath, `时长:`, result.duration) return { converted: true, path: pttPath, - duration: silk.duration / 1000, + duration: result.duration / 1000, } } else { const silk = file - let duration = 0 + let duration = 1 try { duration = getDuration(silk) / 1000 } catch (e: any) { - log('获取语音文件时长失败, 使用文件大小推测时长', filePath, e.stack) - duration = await guessDuration(filePath) + log('获取语音文件时长失败, 默认为1秒', filePath, e.stack) } - return { converted: false, path: filePath, @@ -131,40 +100,17 @@ export async function encodeSilk(filePath: string) { } } -export async function decodeSilk(inputFilePath: string, outFormat: 'mp3' | 'amr' | 'wma' | 'm4a' | 'spx' | 'ogg' | 'wav' | 'flac' = 'mp3') { - const silkArrayBuffer = await fsPromise.readFile(inputFilePath) - const data = (await decode(silkArrayBuffer, 24000)).data - const fileName = path.join(TEMP_DIR, path.basename(inputFilePath)) - const outPCMPath = fileName + '.pcm' - const outFilePath = fileName + '.' + outFormat - await fsPromise.writeFile(outPCMPath, data) - const convert = () => { - return new Promise((resolve, reject) => { - const ffmpegPath = getConfigUtil().getConfig().ffmpeg || process.env.FFMPEG_PATH || 'ffmpeg' - const cp = spawn(ffmpegPath, [ - '-y', - '-f', 's16le', // PCM format - '-ar', '24000', // Sample rate - '-ac', '1', // Number of audio channels - '-i', outPCMPath, - outFilePath, - ]) - cp.on('error', (err) => { - log(`FFmpeg处理转换出错: `, err.message) - return reject(err) - }) - cp.on('exit', (code, signal) => { - const EXIT_CODES = [0, 255] - if (code == null || EXIT_CODES.includes(code)) { - fs.unlink(outPCMPath, (err) => { - }) - return resolve(outFilePath) - } - const exitErr = `FFmpeg exit: code=${code ?? 'unknown'} sig=${signal ?? 'unknown'}` - log(exitErr) - reject(Error(`FFmpeg处理转换失败,${exitErr}`)) - }) - }) - } - return convert() +type OutFormat = 'mp3' | 'amr' | 'wma' | 'm4a' | 'spx' | 'ogg' | 'wav' | 'flac' + +export async function decodeSilk(inputFilePath: string, outFormat: OutFormat = 'mp3') { + const silk = await fsPromise.readFile(inputFilePath) + const { data } = await decode(silk, 24000) + const outFilePath = path.join(TEMP_DIR, path.basename(inputFilePath)) + `.${outFormat}` + return convert(Readable.from(data), { + input: [ + '-f s16le', + '-ar 24000', + '-ac 1' + ] + }, outFilePath) } \ No newline at end of file