mirror of
https://github.com/LLOneBot/LLOneBot.git
synced 2024-11-22 01:56:33 +00:00
Merge remote-tracking branch 'upstream/dev' into dev
This commit is contained in:
commit
cdb34ffe61
@ -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": [
|
||||
|
231
src/common/utils/EventTask.ts
Normal file
231
src/common/utils/EventTask.ts
Normal file
@ -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<string, ListenerClassBase> = new Map<string, ListenerClassBase>() //ListenerName-Unique -> Listener实例
|
||||
private EventTask = new Map<string, Map<string, Map<string, Internal_MapKey>>>()//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<T extends (...args: any) => any>(eventName: string): T | undefined {
|
||||
const eventNameArr = eventName.split('/')
|
||||
type eventType = {
|
||||
[key: string]: () => { [key: string]: (...params: Parameters<T>) => Promise<ReturnType<T>> }
|
||||
}
|
||||
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<T>(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<EventType extends (...args: any[]) => Promise<any> | any>(EventName = '', timeout: number = 3000, ...args: Parameters<EventType>) {
|
||||
return new Promise<Awaited<ReturnType<EventType>>>(async (resolve, reject) => {
|
||||
const EventFunc = this.CreatEventFunction<EventType>(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<ListenerType extends (...args: any[]) => void>(ListenerName = '', waitTimes = 1, timeout = 5000, checker: (...args: Parameters<ListenerType>) => boolean) {
|
||||
return new Promise<Parameters<ListenerType>>((resolve, reject) => {
|
||||
const ListenerNameList = ListenerName.split('/')
|
||||
const ListenerMainName = ListenerNameList[0]
|
||||
const ListenerSubName = ListenerNameList[1]
|
||||
const id = randomUUID()
|
||||
let complete = 0
|
||||
let retData: Parameters<ListenerType> | 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<ListenerType>) => {
|
||||
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<EventType extends (...args: any[]) => Promise<any>, ListenerType extends (...args: any[]) => void>
|
||||
(EventName = '', ListenerName = '', waitTimes = 1, timeout: number = 3000, checker: (...args: Parameters<ListenerType>) => boolean, ...args: Parameters<EventType>) {
|
||||
return new Promise<[EventRet: Awaited<ReturnType<EventType>>, ...Parameters<ListenerType>]>(async (resolve, reject) => {
|
||||
const id = randomUUID()
|
||||
let complete = 0
|
||||
let retData: Parameters<ListenerType> | 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<ReturnType<EventType>>, ...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<ListenerType>
|
||||
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<EventType>(EventName)
|
||||
retEvent = await EventFunc!(...(args as any[]))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export const NTEventDispatch = new NTEventWrapper()
|
||||
|
||||
// 示例代码 快速创建事件
|
||||
// let NTEvent = new NTEventWrapper()
|
||||
// let TestEvent = NTEvent.CreatEventFunction<(force: boolean) => Promise<Number>>('NodeIKernelProfileLikeService/GetTest')
|
||||
// if (TestEvent) {
|
||||
// TestEvent(true)
|
||||
// }
|
||||
|
||||
// 示例代码 快速创建监听Listener类
|
||||
// let NTEvent = new NTEventWrapper()
|
||||
// NTEvent.CreatListenerFunction<NodeIKernelMsgListener>('NodeIKernelMsgListener', 'core')
|
||||
|
||||
|
||||
// 调用接口
|
||||
//let NTEvent = new NTEventWrapper()
|
||||
//let ret = await NTEvent.CallNormalEvent<(force: boolean) => Promise<Number>, (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) })
|
@ -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,16 +45,15 @@ 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
|
||||
import { NTEventDispatch } from '../common/utils/EventTask'
|
||||
import { wrapperApi, wrapperConstructor } from '../ntqqapi/wrapper'
|
||||
|
||||
let mainWindow: BrowserWindow | null = null
|
||||
|
||||
@ -442,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()
|
||||
|
@ -11,22 +11,24 @@ 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'
|
||||
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<string> {
|
||||
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,
|
||||
|
@ -1,8 +1,10 @@
|
||||
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 +28,7 @@ export class NTQQFriendApi {
|
||||
}
|
||||
return _friends
|
||||
}
|
||||
|
||||
static async likeFriend(uid: string, count = 1) {
|
||||
return await callNTQQApi<GeneralCallResult>({
|
||||
methodName: NTQQApiMethod.LIKE_FRIEND,
|
||||
@ -42,6 +45,7 @@ export class NTQQFriendApi {
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
static async handleFriendRequest(flag: string, accept: boolean) {
|
||||
const request: FriendRequest = friendRequests[flag]
|
||||
if (!request) {
|
||||
@ -62,4 +66,16 @@ export class NTQQFriendApi {
|
||||
delete friendRequests[flag]
|
||||
return result
|
||||
}
|
||||
|
||||
static async getBuddyV2(refresh = false): Promise<FriendV2[]> {
|
||||
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 data = await NTEventDispatch.CallNoListenerEvent<NodeIKernelProfileService['getCoreAndBaseInfo']>(
|
||||
'NodeIKernelProfileService/getCoreAndBaseInfo', 5000, 'nodeStore', uids
|
||||
)
|
||||
return Array.from(data.values())
|
||||
}
|
||||
}
|
||||
|
@ -5,25 +5,26 @@ 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 {
|
||||
|
||||
static async activateMemberListChange(){
|
||||
static async activateMemberListChange() {
|
||||
return await callNTQQApi<GeneralCallResult>({
|
||||
methodName: NTQQApiMethod.ACTIVATE_MEMBER_LIST_CHANGE,
|
||||
classNameIsRegister: true,
|
||||
args: [],
|
||||
})
|
||||
}
|
||||
static async activateMemberInfoChange(){
|
||||
|
||||
static async activateMemberInfoChange() {
|
||||
return await callNTQQApi<GeneralCallResult>({
|
||||
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<GeneralCallResult & Group>({
|
||||
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<GroupMember[]> {
|
||||
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<GeneralCallResult>({
|
||||
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<GeneralCallResult & GroupNotifies>(
|
||||
@ -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<GeneralCallResult>({
|
||||
methodName: NTQQApiMethod.HANDLE_GROUP_REQUEST,
|
||||
args: [
|
||||
@ -152,6 +159,7 @@ export class NTQQGroupApi {
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
static async quitGroup(groupQQ: string) {
|
||||
const result = await callNTQQApi<GeneralCallResult>({
|
||||
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<GeneralCallResult>({
|
||||
methodName: NTQQApiMethod.MUTE_MEMBER,
|
||||
@ -192,6 +202,7 @@ export class NTQQGroupApi {
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
static async banGroup(groupQQ: string, shutUp: boolean) {
|
||||
return await callNTQQApi<GeneralCallResult>({
|
||||
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<GeneralCallResult>({
|
||||
@ -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<GeneralCallResult>({
|
||||
methodName: NTQQApiMethod.SET_MEMBER_ROLE,
|
||||
@ -233,6 +246,7 @@ export class NTQQGroupApi {
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
static async setGroupName(groupQQ: string, groupName: string) {
|
||||
return await callNTQQApi<GeneralCallResult>({
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@ -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<string, ((sendSuccessMsg: RawMessage) => void) | null> = {} // peerUid: callbackFunc
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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<any> {
|
||||
//刷新rkey
|
||||
this.rkeyData = await this.fetchServerRkey();
|
||||
this.rkeyData = await this.fetchServerRkey()
|
||||
}
|
||||
async fetchServerRkey(){
|
||||
|
||||
async fetchServerRkey() {
|
||||
return new Promise<ServerRkeyData>((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')
|
||||
|
@ -2,13 +2,11 @@ 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<string, User> = {} // uid: User
|
||||
const userInfoCache: Record<string, User> = {} // uid: User
|
||||
|
||||
export interface ClientKeyData extends GeneralCallResult {
|
||||
url: string;
|
||||
@ -151,7 +149,8 @@ export class NTQQUserApi {
|
||||
}
|
||||
|
||||
static async getPSkey(domains: string[]): Promise<Map<string, string>> {
|
||||
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}`)
|
||||
}
|
||||
@ -159,7 +158,7 @@ export class NTQQUserApi {
|
||||
}
|
||||
|
||||
static async getClientKey(): Promise<ClientKeyData> {
|
||||
return await wrapperApi.NodeIQQNTWrapperSession.getTicketService().forceFetchClientKey('')
|
||||
const session = wrapperApi.NodeIQQNTWrapperSession
|
||||
return await session.getTicketService().forceFetchClientKey('')
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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<GroupEssenceMsgRet> {
|
||||
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<WebApiGroupMember[]> {
|
||||
log('webapi 获取群成员', GroupCode);
|
||||
let MemberData: Array<WebApiGroupMember> = new Array<WebApiGroupMember>();
|
||||
@ -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<undefined | WebApiGroupNoticeRet> {
|
||||
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) {
|
||||
|
@ -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
|
||||
}
|
125
src/ntqqapi/services/NodeIKernelBuddyService.ts
Normal file
125
src/ntqqapi/services/NodeIKernelBuddyService.ts
Normal file
@ -0,0 +1,125 @@
|
||||
import { GeneralCallResult } from './common'
|
||||
|
||||
export enum BuddyListReqType {
|
||||
KNOMAL,
|
||||
KLETTER
|
||||
}
|
||||
|
||||
export interface NodeIKernelBuddyService {
|
||||
// 26702 以上
|
||||
getBuddyListV2(callFrom: string, reqType: BuddyListReqType): Promise<GeneralCallResult & {
|
||||
data: Array<{
|
||||
categoryId: number,
|
||||
categorySortId: number,
|
||||
categroyName: string,
|
||||
categroyMbCount: number,
|
||||
onlineCount: number,
|
||||
buddyUids: Array<string>
|
||||
}>
|
||||
}>
|
||||
|
||||
//26702 以上
|
||||
getBuddyListFromCache(callFrom: string): Promise<Array<
|
||||
{
|
||||
categoryId: number,//9999应该跳过 那是兜底数据吧
|
||||
categorySortId: number,//排序方式
|
||||
categroyName: string,//分类名
|
||||
categroyMbCount: number,//不懂
|
||||
onlineCount: number,//在线数目
|
||||
buddyUids: Array<string>//Uids
|
||||
}>>
|
||||
|
||||
addKernelBuddyListener(listener: any): number
|
||||
|
||||
getAllBuddyCount(): number
|
||||
|
||||
removeKernelBuddyListener(listener: unknown): void
|
||||
|
||||
getBuddyList(nocache: boolean): Promise<GeneralCallResult>
|
||||
|
||||
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<void>
|
||||
|
||||
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<unknown>
|
||||
|
||||
isNull(): boolean
|
||||
}
|
106
src/ntqqapi/services/NodeIKernelProfileService.ts
Normal file
106
src/ntqqapi/services/NodeIKernelProfileService.ts
Normal file
@ -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<string>): Promise<Map<string,string>>//uin->uid
|
||||
|
||||
getUinByUid(callfrom: string, uid: Array<string>): Promise<Map<string,string>>
|
||||
|
||||
// {
|
||||
// coreInfo: CoreInfo,
|
||||
// baseInfo: BaseInfo,
|
||||
// status: null,
|
||||
// vasInfo: null,
|
||||
// relationFlags: null,
|
||||
// otherFlags: null,
|
||||
// intimate: null
|
||||
// }
|
||||
getCoreAndBaseInfo(callfrom: string, uids: string[]): Promise<Map<string, SimpleInfo>>
|
||||
|
||||
fetchUserDetailInfo(trace: string, uids: string[], arg2: number, arg3: number[]): Promise<unknown>
|
||||
|
||||
addKernelProfileListener(listener: any): number
|
||||
|
||||
removeKernelProfileListener(listenerId: number): void
|
||||
|
||||
prepareRegionConfig(...args: unknown[]): unknown
|
||||
|
||||
getLocalStrangerRemark(): Promise<AnyCnameRecord>
|
||||
|
||||
enumCountryOptions(): Array<string>
|
||||
|
||||
enumProvinceOptions(Country: string): Array<string>
|
||||
|
||||
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<unknown>
|
||||
|
||||
modifyDesktopMiniProfile(param: any): Promise<GeneralCallResult>
|
||||
|
||||
setNickName(NickName: string): Promise<unknown>
|
||||
|
||||
setLongNick(longNick: string): Promise<unknown>
|
||||
|
||||
setBirthday(...args: unknown[]): Promise<unknown>
|
||||
|
||||
setGander(...args: unknown[]): Promise<unknown>
|
||||
|
||||
setHeader(arg: string): Promise<unknown>
|
||||
|
||||
setRecommendImgFlag(...args: unknown[]): Promise<unknown>
|
||||
|
||||
getUserSimpleInfo(force: boolean, uids: string[],): Promise<unknown>
|
||||
|
||||
getUserDetailInfo(uid: string): Promise<unknown>
|
||||
|
||||
getUserDetailInfoWithBizInfo(uid: string, Biz: any[]): Promise<GeneralCallResult>
|
||||
|
||||
getUserDetailInfoByUin(uin: string): Promise<any>
|
||||
|
||||
getZplanAvatarInfos(args: string[]): Promise<unknown>
|
||||
|
||||
getStatus(uid: string): Promise<unknown>
|
||||
|
||||
startStatusPolling(isForceReset: boolean): Promise<unknown>
|
||||
|
||||
getSelfStatus(): Promise<unknown>
|
||||
|
||||
setdisableEmojiShortCuts(...args: unknown[]): unknown
|
||||
|
||||
getProfileQzonePicInfo(uid: string, type: number, force: boolean): Promise<unknown>
|
||||
|
||||
//profileService.getCoreInfo("UserRemarkServiceImpl::getStrangerRemarkByUid", arrayList)
|
||||
getCoreInfo(name: string, arg: any[]): unknown
|
||||
|
||||
//m429253e12.getOtherFlag("FriendListInfoCache_getKernelDataAndPutCache", new ArrayList<>())
|
||||
isNull(): boolean
|
||||
}
|
16
src/ntqqapi/services/common.ts
Normal file
16
src/ntqqapi/services/common.ts
Normal file
@ -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
|
||||
}
|
2
src/ntqqapi/services/index.ts
Normal file
2
src/ntqqapi/services/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './NodeIKernelBuddyService'
|
||||
export * from './NodeIKernelProfileService'
|
@ -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
|
||||
}
|
@ -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
|
||||
}
|
68
src/ntqqapi/wrapper.ts
Normal file
68
src/ntqqapi/wrapper.ts
Normal file
@ -0,0 +1,68 @@
|
||||
import { NodeIKernelBuddyService } from './services/NodeIKernelBuddyService'
|
||||
import os from 'node:os'
|
||||
const Process = require('node:process')
|
||||
|
||||
export interface NodeIQQNTWrapperSession {
|
||||
[key: string]: any
|
||||
getBuddyService(): NodeIKernelBuddyService
|
||||
}
|
||||
|
||||
export interface WrapperApi {
|
||||
NodeIQQNTWrapperSession?: NodeIQQNTWrapperSession
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
}
|
||||
})
|
||||
if (constructor.includes(export_name)) {
|
||||
wrapperConstructor[export_name] = module.exports[export_name]
|
||||
}
|
||||
}
|
||||
return dlopenRet
|
||||
}
|
@ -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<Payload, OB11User[]> {
|
||||
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)
|
||||
|
@ -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<OB11BaseNoticeEvent> {
|
||||
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,
|
||||
|
@ -11,6 +11,8 @@ export interface OB11User {
|
||||
age?: number
|
||||
qid?: string
|
||||
login_days?: number
|
||||
categroyName?: string
|
||||
categoryId?: number
|
||||
}
|
||||
|
||||
export enum OB11UserSex {
|
||||
|
Loading…
x
Reference in New Issue
Block a user