From e1ff366e10a28671265656a0bec05c46b5982a26 Mon Sep 17 00:00:00 2001 From: idranme Date: Tue, 6 Aug 2024 02:32:28 +0800 Subject: [PATCH 01/14] 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 02/14] 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 03/14] 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 04/14] 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 05/14] 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 06/14] 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 From 50ab62f1032aa90fdb30ee34cc0acd1ad7d1109f Mon Sep 17 00:00:00 2001 From: idranme Date: Wed, 7 Aug 2024 21:39:26 +0800 Subject: [PATCH 07/14] opt: config --- src/renderer/index.ts | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/renderer/index.ts b/src/renderer/index.ts index 2cdc9a6..9cf9b97 100644 --- a/src/renderer/index.ts +++ b/src/renderer/index.ts @@ -1,4 +1,3 @@ -/// import { CheckVersion } from '../common/types' import { SettingButton, SettingItem, SettingList, SettingSwitch, SettingSelect } from './components' // @ts-ignore @@ -26,22 +25,24 @@ function initSideBar() { }) } +function isEmpty(value: unknown) { + return value === undefined || value === null || value === '' +} + async function onSettingWindowCreated(view: Element) { window.llonebot.log('setting window created') initSideBar() - const isEmpty = (value: any) => value === undefined || value === null || value === '' let config = await window.llonebot.getConfig() let ob11Config = { ...config.ob11 } + const setConfig = (key: string, value: any) => { const configKey = key.split('.') - if (key.indexOf('ob11') === 0) { if (configKey.length === 2) ob11Config[configKey[1]] = value else ob11Config[key] = value } else { if (configKey.length === 2) config[configKey[0]][configKey[1]] = value else config[key] = value - if (!['heartInterval', 'token', 'ffmpeg'].includes(key)) { window.llonebot.setConfig(false, config) } @@ -65,7 +66,7 @@ async function onSettingWindowCreated(view: Element) { ]), SettingList([ SettingItem( - '是否启用 LLOneBot, 重启QQ后生效', + '是否启用 LLOneBot, 重启 QQ 后生效', null, SettingSwitch('enableLLOB', config.enableLLOB, { 'control-display-id': 'config-enableLLOB' }), )] @@ -160,21 +161,21 @@ async function onSettingWindowCreated(view: Element) { SettingSelect( [ { text: '消息段', value: 'array' }, - { text: 'CQ码', value: 'string' }, + { text: 'CQ 码', value: 'string' }, ], 'ob11.messagePostFormat', config.ob11.messagePostFormat, ), ), SettingItem( - 'ffmpeg 路径,发送语音、视频需要,同时保证ffprobe和ffmpeg在一起', - ` 下载地址 , 路径:${ + 'FFmpeg 路径,发送语音、视频需要', + `可点此下载, 路径: ${ !isEmpty(config.ffmpeg) ? config.ffmpeg : '未指定' - }`, - SettingButton('选择ffmpeg', 'config-ffmpeg-select'), + }, 需保证 FFprobe 和 FFmpeg 在一起`, + SettingButton('选择 FFmpeg', 'config-ffmpeg-select'), ), SettingItem( - '音乐卡片签名地址', + '音乐卡片签名 URL 地址', null, `
`, 'config-musicSignUrl', From 1a2cdc8c0ef2d37ba2f3db0b1997bbb52f01e283 Mon Sep 17 00:00:00 2001 From: idranme Date: Wed, 7 Aug 2024 22:08:47 +0800 Subject: [PATCH 08/14] opt --- src/common/server/http.ts | 2 +- src/common/utils/helper.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/common/server/http.ts b/src/common/server/http.ts index f451f52..550522f 100644 --- a/src/common/server/http.ts +++ b/src/common/server/http.ts @@ -58,7 +58,7 @@ export abstract class HttpServerBase { start(port: number) { try { this.expressAPP.get('/', (req: Request, res: Response) => { - res.send(`${this.name}已启动`) + res.send(`${this.name} 已启动`) }) this.listen(port) llonebotError.httpServerError = '' diff --git a/src/common/utils/helper.ts b/src/common/utils/helper.ts index 03f7501..275d786 100644 --- a/src/common/utils/helper.ts +++ b/src/common/utils/helper.ts @@ -41,7 +41,7 @@ export function mergeNewProperties(newObj: any, oldObj: any) { }) } -export function isNull(value: any): value is null | undefined | void { +export function isNull(value: unknown) { return value === undefined || value === null } From b5bffff94148659f89240ffd924ebda4cb5b0693 Mon Sep 17 00:00:00 2001 From: idranme Date: Wed, 7 Aug 2024 23:17:13 +0800 Subject: [PATCH 09/14] fix --- src/common/utils/audio.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/common/utils/audio.ts b/src/common/utils/audio.ts index 7328a30..a19996f 100644 --- a/src/common/utils/audio.ts +++ b/src/common/utils/audio.ts @@ -105,8 +105,11 @@ 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), { + const tmpPath = path.join(TEMP_DIR, path.basename(inputFilePath)) + const outFilePath = tmpPath + `.${outFormat}` + const pcmFilePath = tmpPath + '.pcm' + await fsPromise.writeFile(pcmFilePath, data) + return convert(pcmFilePath, { input: [ '-f s16le', '-ar 24000', From c5b69561af69fd4b472f314575558bddfaf19384 Mon Sep 17 00:00:00 2001 From: idranme Date: Fri, 9 Aug 2024 14:20:59 +0800 Subject: [PATCH 10/14] sync --- src/common/data.ts | 48 ++-- src/common/utils/QQBasicInfo.ts | 24 +- src/common/utils/helper.ts | 52 +++- src/common/utils/table.ts | 71 +++++ src/main/main.ts | 31 +-- src/ntqqapi/api/friend.ts | 37 ++- src/ntqqapi/api/group.ts | 52 +--- src/ntqqapi/api/user.ts | 106 +++++++- src/ntqqapi/constructor.ts | 2 +- src/ntqqapi/hook.ts | 6 +- .../listeners/NodeIKernelGroupListener.ts | 240 +++++++++++++++++ src/ntqqapi/listeners/index.ts | 3 +- .../services/NodeIKernelGroupService.ts | 249 ++++++++++++++++++ .../services/NodeIKernelProfileLikeService.ts | 22 ++ src/ntqqapi/services/index.ts | 4 +- src/ntqqapi/types/group.ts | 7 + src/ntqqapi/types/notify.ts | 38 +++ src/ntqqapi/types/user.ts | 82 ++++++ src/ntqqapi/wrapper.ts | 14 +- .../action/group/GetGroupMemberList.ts | 3 +- src/onebot11/action/msg/SendMsg.ts | 65 +---- src/onebot11/action/user/SendLike.ts | 17 +- src/onebot11/constructor.ts | 14 +- 23 files changed, 970 insertions(+), 217 deletions(-) create mode 100644 src/common/utils/table.ts create mode 100644 src/ntqqapi/listeners/NodeIKernelGroupListener.ts create mode 100644 src/ntqqapi/services/NodeIKernelGroupService.ts create mode 100644 src/ntqqapi/services/NodeIKernelProfileLikeService.ts diff --git a/src/common/data.ts b/src/common/data.ts index 4287849..6ef4a1d 100644 --- a/src/common/data.ts +++ b/src/common/data.ts @@ -33,6 +33,8 @@ export const llonebotError: LLOneBotError = { wsServerError: '', otherError: 'LLOnebot未能正常启动,请检查日志查看错误', } +// 群号 -> 群成员map(uid=>GroupMember) +export const groupMembers: Map> = new Map>() export async function getFriend(uinOrUid: string): Promise { let filterKey = isNumeric(uinOrUid.toString()) ? 'uin' : 'uid' @@ -79,34 +81,32 @@ export function deleteGroup(groupCode: string) { export async function getGroupMember(groupQQ: string | number, memberUinOrUid: string | number) { groupQQ = groupQQ.toString() memberUinOrUid = memberUinOrUid.toString() - const group = await getGroup(groupQQ) - if (group) { - const filterKey = isNumeric(memberUinOrUid) ? 'uin' : 'uid' - const filterValue = memberUinOrUid - let filterFunc: (member: GroupMember) => boolean = (member) => member[filterKey] === filterValue - let member = group.members?.find(filterFunc) - if (!member) { - try { - const _members = await NTQQGroupApi.getGroupMembers(groupQQ) - if (_members.length > 0) { - group.members = _members - } - } catch (e) { - // log("刷新群成员列表失败", e.stack.toString()) - } - - member = group.members?.find(filterFunc) + let members = groupMembers.get(groupQQ) + if (!members) { + try { + members = await NTQQGroupApi.getGroupMembers(groupQQ) + // 更新群成员列表 + groupMembers.set(groupQQ, members) + } + catch (e) { + return null + } + } + const getMember = () => { + let member: GroupMember | undefined = undefined + if (isNumeric(memberUinOrUid)) { + member = Array.from(members!.values()).find(member => member.uin === memberUinOrUid) + } else { + member = members!.get(memberUinOrUid) } return member } - return null -} - -export async function refreshGroupMembers(groupQQ: string) { - const group = groups.find((group) => group.groupCode === groupQQ) - if (group) { - group.members = await NTQQGroupApi.getGroupMembers(groupQQ) + let member = getMember() + if (!member) { + members = await NTQQGroupApi.getGroupMembers(groupQQ) + member = getMember() } + return member } export const uidMaps: Record = {} // 一串加密的字符串(uid) -> qq号 diff --git a/src/common/utils/QQBasicInfo.ts b/src/common/utils/QQBasicInfo.ts index 29c0ceb..2e7c5e1 100644 --- a/src/common/utils/QQBasicInfo.ts +++ b/src/common/utils/QQBasicInfo.ts @@ -33,17 +33,17 @@ if (typeof configVersionInfoPath !== 'string') { export { configVersionInfoPath } type QQPkgInfo = { - version: string; - buildVersion: string; - platform: string; - eleArch: string; + version: string + buildVersion: string + platform: string + eleArch: string } type QQVersionConfigInfo = { - baseVersion: string; - curVersion: string; - prevVersion: string; - onErrorVersions: Array; - buildId: string; + baseVersion: string + curVersion: string + prevVersion: string + onErrorVersions: Array + buildId: string } let _qqVersionConfigInfo: QQVersionConfigInfo = { @@ -80,4 +80,8 @@ if (systemPlatform === 'linux') { } // todo: mac 平台的 appid export const appid = _appid -export const isQQ998: boolean = qqPkgInfo.buildVersion >= '22106' \ No newline at end of file +export const isQQ998: boolean = qqPkgInfo.buildVersion >= '22106' + +export function getBuildVersion(): number { + return +qqPkgInfo.buildVersion +} \ No newline at end of file diff --git a/src/common/utils/helper.ts b/src/common/utils/helper.ts index 275d786..1dbbd0a 100644 --- a/src/common/utils/helper.ts +++ b/src/common/utils/helper.ts @@ -74,24 +74,50 @@ export function wrapText(str: string, maxLength: number): string { * @returns 处理后缓存或调用原方法的结果 */ export function cacheFunc(ttl: number, customKey: string = '') { - const cache = new Map(); + const cache = new Map() return function (target: any, propertyKey: string, descriptor: PropertyDescriptor): PropertyDescriptor { - const originalMethod = descriptor.value; - const className = target.constructor.name; // 获取类名 - const methodName = propertyKey; // 获取方法名 + const originalMethod = descriptor.value + const className = target.constructor.name // 获取类名 + const methodName = propertyKey // 获取方法名 descriptor.value = async function (...args: any[]) { - const cacheKey = `${customKey}${className}.${methodName}:${JSON.stringify(args)}`; - const cached = cache.get(cacheKey); + const cacheKey = `${customKey}${className}.${methodName}:${JSON.stringify(args)}` + const cached = cache.get(cacheKey) if (cached && cached.expiry > Date.now()) { - return cached.value; + return cached.value } else { - const result = await originalMethod.apply(this, args); - cache.set(cacheKey, { value: result, expiry: Date.now() + ttl }); - return result; + const result = await originalMethod.apply(this, args) + cache.set(cacheKey, { value: result, expiry: Date.now() + ttl }) + return result } - }; + } - return descriptor; - }; + return descriptor + } +} + +export function CacheClassFuncAsyncExtend(ttl: number = 3600 * 1000, customKey: string = '', checker: any = (...data: any[]) => { return true }) { + function logExecutionTime(target: any, methodName: string, descriptor: PropertyDescriptor) { + const cache = new Map() + const originalMethod = descriptor.value + descriptor.value = async function (...args: any[]) { + const key = `${customKey}${String(methodName)}.(${args.map(arg => JSON.stringify(arg)).join(', ')})` + cache.forEach((value, key) => { + if (value.expiry < Date.now()) { + cache.delete(key) + } + }) + const cachedValue = cache.get(key) + if (cachedValue && cachedValue.expiry > Date.now()) { + return cachedValue.value + } + const result = await originalMethod.apply(this, args) + if (!checker(...args, result)) { + return result //丢弃缓存 + } + cache.set(key, { expiry: Date.now() + ttl, value: result }) + return result + } + } + return logExecutionTime } \ No newline at end of file diff --git a/src/common/utils/table.ts b/src/common/utils/table.ts new file mode 100644 index 0000000..bdb9e02 --- /dev/null +++ b/src/common/utils/table.ts @@ -0,0 +1,71 @@ +export class LimitedHashTable { + private keyToValue: Map = new Map() + private valueToKey: Map = new Map() + private maxSize: number + + constructor(maxSize: number) { + this.maxSize = maxSize + } + + resize(count: number) { + this.maxSize = count + } + + set(key: K, value: V): void { + this.keyToValue.set(key, value) + this.valueToKey.set(value, key) + while (this.keyToValue.size !== this.valueToKey.size) { + console.log('keyToValue.size !== valueToKey.size Error Atom') + this.keyToValue.clear() + this.valueToKey.clear() + } + while (this.keyToValue.size > this.maxSize || this.valueToKey.size > this.maxSize) { + const oldestKey = this.keyToValue.keys().next().value + this.valueToKey.delete(this.keyToValue.get(oldestKey)!) + this.keyToValue.delete(oldestKey) + } + } + + getValue(key: K): V | undefined { + return this.keyToValue.get(key) + } + + getKey(value: V): K | undefined { + return this.valueToKey.get(value) + } + + deleteByValue(value: V): void { + const key = this.valueToKey.get(value) + if (key !== undefined) { + this.keyToValue.delete(key) + this.valueToKey.delete(value) + } + } + + deleteByKey(key: K): void { + const value = this.keyToValue.get(key) + if (value !== undefined) { + this.keyToValue.delete(key) + this.valueToKey.delete(value) + } + } + + getKeyList(): K[] { + return Array.from(this.keyToValue.keys()) + } + + //获取最近刚写入的几个值 + getHeads(size: number): { key: K; value: V }[] | undefined { + const keyList = this.getKeyList() + if (keyList.length === 0) { + return undefined + } + const result: { key: K; value: V }[] = [] + const listSize = Math.min(size, keyList.length) + for (let i = 0; i < listSize; i++) { + const key = keyList[listSize - i] + result.push({ key, value: this.keyToValue.get(key)! }) + } + return result + } +} \ No newline at end of file diff --git a/src/main/main.ts b/src/main/main.ts index 99a6893..dc74a4b 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -21,7 +21,6 @@ import { getGroupMember, groups, llonebotError, - refreshGroupMembers, selfInfo, uidMaps, } from '../common/data' @@ -443,32 +442,10 @@ function onLoad() { } }) NTEventDispatch.init({ ListenerMap: wrapperConstructor, WrapperSession: wrapperApi.NodeIQQNTWrapperSession! }) - try { - log('start get groups') - const _groups = await NTQQGroupApi.getGroups() - log('_groups', _groups) - await Promise.all( - _groups.map(async (group) => { - try { - const members = await NTQQGroupApi.getGroupMembers(group.groupCode) - group.members = members - groups.push(group) - } catch (e) { - log('获取群成员失败', e) - } - }) - ) - } - catch (e) { - log('获取群列表失败', e) - } - finally { - log('start activate group member info') - NTQQGroupApi.activateMemberInfoChange().then().catch(log) - NTQQGroupApi.activateMemberListChange().then().catch(log) - startReceiveHook().then() - } - + log('start activate group member info') + NTQQGroupApi.activateMemberInfoChange().then().catch(log) + NTQQGroupApi.activateMemberListChange().then().catch(log) + startReceiveHook().then() if (config.ob11.enableHttp) { ob11HTTPServer.start(config.ob11.httpPort) diff --git a/src/ntqqapi/api/friend.ts b/src/ntqqapi/api/friend.ts index 55384e8..56efd3a 100644 --- a/src/ntqqapi/api/friend.ts +++ b/src/ntqqapi/api/friend.ts @@ -1,10 +1,12 @@ import { Friend, FriendRequest, FriendV2 } from '../types' import { ReceiveCmdS } from '../hook' import { callNTQQApi, GeneralCallResult, NTQQApiMethod } from '../ntcall' -import { friendRequests } from '../../common/data' -import { wrapperApi } from '@/ntqqapi/wrapper' +import { friendRequests } from '@/common/data' +import { getSession } from '@/ntqqapi/wrapper' import { BuddyListReqType, NodeIKernelProfileService } from '../services' -import { NTEventDispatch } from '../../common/utils/EventTask' +import { NTEventDispatch } from '@/common/utils/EventTask' +import { CacheClassFuncAsyncExtend } from '@/common/utils/helper' +import { LimitedHashTable } from '@/common/utils/table' export class NTQQFriendApi { static async getFriends(forced = false) { @@ -69,7 +71,7 @@ export class NTQQFriendApi { static async getBuddyV2(refresh = false): Promise { const uids: string[] = [] - const session = wrapperApi.NodeIQQNTWrapperSession + const session = getSession() 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)!) @@ -78,4 +80,31 @@ export class NTQQFriendApi { ) return Array.from(data.values()) } + + @CacheClassFuncAsyncExtend(3600 * 1000, 'getBuddyIdMap', () => true) + static async getBuddyIdMapCache(refresh = false): Promise> { + return await NTQQFriendApi.getBuddyIdMap(refresh) + } + + static async getBuddyIdMap(refresh = false): Promise> { + const uids: string[] = [] + const retMap: LimitedHashTable = new LimitedHashTable(5000) + const session = getSession() + 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 + ); + data.forEach((value, key) => { + retMap.set(value.uin!, value.uid!) + }) + //console.log('getBuddyIdMap', retMap.getValue) + return retMap + } + + static async isBuddy(uid: string): Promise { + const session = getSession() + return session?.getBuddyService().isBuddy(uid)! + } } diff --git a/src/ntqqapi/api/group.ts b/src/ntqqapi/api/group.ts index 0423c80..21dc68f 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 '../wrapper' +import { getSession } from '../wrapper' export class NTQQGroupApi { static async activateMemberListChange() { @@ -55,45 +55,15 @@ export class NTQQGroupApi { return result.groupList } - static async getGroupMembers(groupQQ: string, num = 3000): Promise { - const sceneId = await callNTQQApi({ - methodName: NTQQApiMethod.GROUP_MEMBER_SCENE, - args: [ - { - groupCode: groupQQ, - scene: 'groupMemberList_MainWindow', - }, - ], - }) - // log("get group member sceneId", sceneId) - try { - const result = await callNTQQApi<{ - result: { infos: any } - }>({ - methodName: NTQQApiMethod.GROUP_MEMBERS, - args: [ - { - sceneId: sceneId, - num: num, - }, - null, - ], - }) - // log("members info", typeof result.result.infos, Object.keys(result.result.infos)) - const values = result.result.infos.values() - - const members: GroupMember[] = Array.from(values) - for (const member of members) { - uidMaps[member.uid] = member.uin - } - // log(uidMaps) - // log("members info", values) - log(`get group ${groupQQ} members success`) - return members - } catch (e) { - log(`get group ${groupQQ} members failed`, e) - return [] + static async getGroupMembers(groupQQ: string, num = 3000): Promise> { + const session = getSession() + const groupService = session?.getGroupService() + const sceneId = groupService?.createMemberListScene(groupQQ, 'groupMemberList_MainWindow') + const result = await groupService?.getNextMemberList(sceneId!, undefined, num) + if (result?.errCode !== 0) { + throw ('获取群成员列表出错,' + result?.errMsg) } + return result.result.infos } static async getGroupMembersInfo(groupCode: string, uids: string[], forceUpdate: boolean = false) { @@ -300,7 +270,7 @@ export class NTQQGroupApi { static publishGroupBulletin(groupQQ: string, title: string, content: string) { } static async removeGroupEssence(GroupCode: string, msgId: string) { - const session = wrapperApi.NodeIQQNTWrapperSession + const session = getSession() // 代码没测过 // 需要 ob11msgid->msgId + (peer) -> msgSeq + msgRandom let MsgData = await session?.getMsgService().getMsgsIncludeSelf({ chatType: 2, guildId: '', peerUid: GroupCode }, msgId, 1, false) @@ -314,7 +284,7 @@ export class NTQQGroupApi { } static async addGroupEssence(GroupCode: string, msgId: string) { - const session = wrapperApi.NodeIQQNTWrapperSession + const session = getSession() // 代码没测过 // 需要 ob11msgid->msgId + (peer) -> msgSeq + msgRandom let MsgData = await session?.getMsgService().getMsgsIncludeSelf({ chatType: 2, guildId: '', peerUid: GroupCode }, msgId, 1, false) diff --git a/src/ntqqapi/api/user.ts b/src/ntqqapi/api/user.ts index ef093d2..a3803a8 100644 --- a/src/ntqqapi/api/user.ts +++ b/src/ntqqapi/api/user.ts @@ -1,14 +1,14 @@ import { callNTQQApi, GeneralCallResult, NTQQApiClass, NTQQApiMethod } from '../ntcall' -import { Group, SelfInfo, User } from '../types' +import { SelfInfo, User, UserDetailInfoByUin, UserDetailInfoByUinV2 } from '../types' import { ReceiveCmdS } from '../hook' -import { selfInfo, uidMaps } from '../../common/data' -import { cacheFunc, isQQ998, log, sleep } from '../../common/utils' -import { wrapperApi } from '@/ntqqapi/wrapper' +import { selfInfo, uidMaps, friends, groupMembers } from '@/common/data' +import { cacheFunc, isQQ998, log, sleep, getBuildVersion } from '@/common/utils' +import { getSession } 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' +import { NTQQFriendApi } from './friend' const userInfoCache: Record = {} // uid: User @@ -50,7 +50,7 @@ export class NTQQUserApi { return result.profiles.get(uid) } - // 26702 + /** 26702 */ static async fetchUserDetailInfo(uid: string) { type EventService = NodeIKernelProfileService['fetchUserDetailInfo'] type EventListener = NodeIKernelProfileListener['onUserDetailInfoChanged'] @@ -63,9 +63,9 @@ export class NTQQUserApi { 5000, (profile) => { if (profile.uid === uid) { - return true; + return true } - return false; + return false }, 'BuddyProfileStore', [ @@ -89,7 +89,7 @@ export class NTQQUserApi { } static async getUserDetailInfo(uid: string, getLevel = false, withBizInfo = true) { - if (+qqPkgInfo.buildVersion >= 26702) { + if (getBuildVersion() >= 26702) { return this.fetchUserDetailInfo(uid) } // this.getUserInfo(uid) @@ -195,7 +195,7 @@ export class NTQQUserApi { } static async getPSkey(domains: string[]): Promise> { - const session = wrapperApi.NodeIQQNTWrapperSession + const session = getSession() const res = await session?.getTipOffService().getPskey(domains, true) if (res.result !== 0) { throw new Error(`获取Pskey失败: ${res.errMsg}`) @@ -204,7 +204,91 @@ export class NTQQUserApi { } static async getClientKey(): Promise { - const session = wrapperApi.NodeIQQNTWrapperSession + const session = getSession() return await session?.getTicketService().forceFetchClientKey('') } + + static async like(uid: string, count = 1): Promise<{ result: number, errMsg: string, succCounts: number }> { + const session = getSession() + return session?.getProfileLikeService().setBuddyProfileLike({ + friendUid: uid, + sourceId: 71, + doLikeCount: count, + doLikeTollCount: 0 + })! + } + + static async getUidByUinV1(Uin: string) { + const session = getSession() + // 通用转换开始尝试 + let uid = (await session?.getUixConvertService().getUid([Uin])).uidInfo.get(Uin); + // Uid 好友转 + if (!uid) { + friends.forEach((t) => { + if (t.uin == Uin) { + uid = t.uid + } + }) + } + //Uid 群友列表转 + if (!uid) { + for (let groupMembersList of groupMembers.values()) { + for (let GroupMember of groupMembersList.values()) { + if (GroupMember.uin == Uin) { + uid = GroupMember.uid + } + } + } + } + if (!uid) { + let unveifyUid = (await NTQQUserApi.getUserDetailInfoByUin(Uin)).info.uid;//从QQ Native 特殊转换 方法三 + if (unveifyUid.indexOf('*') == -1) { + uid = unveifyUid + } + } + return uid + } + + static async getUidByUinV2(Uin: string) { + const session = getSession() + let uid = (await session?.getProfileService().getUidByUin('FriendsServiceImpl', [Uin])!).get(Uin) + if (uid) return uid + uid = (await session?.getGroupService().getUidByUins([Uin])!).uids.get(Uin) + if (uid) return uid + uid = (await session?.getUixConvertService().getUid([Uin])).uidInfo.get(Uin) + if (uid) return uid + console.log((await NTQQFriendApi.getBuddyIdMapCache(true))) + uid = (await NTQQFriendApi.getBuddyIdMapCache(true)).getValue(Uin)//从Buddy缓存获取Uid + if (uid) return uid + uid = (await NTQQFriendApi.getBuddyIdMap(true)).getValue(Uin) + if (uid) return uid + let unveifyUid = (await NTQQUserApi.getUserDetailInfoByUinV2(Uin)).detail.uid//从QQ Native 特殊转换 + if (unveifyUid.indexOf("*") == -1) uid = unveifyUid + //if (uid) return uid + return uid + } + + static async getUidByUin(Uin: string) { + if (getBuildVersion() >= 26702) { + return await NTQQUserApi.getUidByUinV2(Uin) + } + return await NTQQUserApi.getUidByUinV1(Uin) + } + + static async getUserDetailInfoByUinV2(Uin: string) { + return await NTEventDispatch.CallNoListenerEvent + <(Uin: string) => Promise>( + 'NodeIKernelProfileService/getUserDetailInfoByUin', + 5000, + Uin + ) + } + static async getUserDetailInfoByUin(Uin: string) { + return NTEventDispatch.CallNoListenerEvent + <(Uin: string) => Promise>( + 'NodeIKernelProfileService/getUserDetailInfoByUin', + 5000, + Uin + ) + } } diff --git a/src/ntqqapi/constructor.ts b/src/ntqqapi/constructor.ts index a03bb04..f99f18a 100644 --- a/src/ntqqapi/constructor.ts +++ b/src/ntqqapi/constructor.ts @@ -21,7 +21,7 @@ import { log } from '../common/utils/log' import { defaultVideoThumb, getVideoInfo } from '../common/utils/video' import { encodeSilk } from '../common/utils/audio' import { isNull } from '../common/utils' -import faceConfig from './face_config.json'; +import faceConfig from './face_config.json' export const mFaceCache = new Map() // emojiId -> faceName diff --git a/src/ntqqapi/hook.ts b/src/ntqqapi/hook.ts index 020c2e6..b8d651d 100644 --- a/src/ntqqapi/hook.ts +++ b/src/ntqqapi/hook.ts @@ -268,7 +268,7 @@ async function updateGroups(_groups: Group[], needUpdate: boolean = true) { const members = await NTQQGroupApi.getGroupMembers(group.groupCode) if (members) { - existGroup.members = members + existGroup.members = Array.from(members.values()) } } } @@ -287,11 +287,11 @@ async function processGroupEvent(payload: { groupList: Group[] }) { await sleep(200) // 如果请求QQ API的速度过快,通常无法正确拉取到最新的群信息,因此这里人为引入一个延时 const newMembers = await NTQQGroupApi.getGroupMembers(group.groupCode) - group.members = newMembers + group.members = Array.from(newMembers.values()) const newMembersSet = new Set() // 建立索引降低时间复杂度 for (const member of newMembers) { - newMembersSet.add(member.uin) + newMembersSet.add(member[1].uin) } // 判断bot是否是管理员,如果是管理员不需要从这里得知有人退群,这里的退群无法得知是主动退群还是被踢 diff --git a/src/ntqqapi/listeners/NodeIKernelGroupListener.ts b/src/ntqqapi/listeners/NodeIKernelGroupListener.ts new file mode 100644 index 0000000..ae4a4f9 --- /dev/null +++ b/src/ntqqapi/listeners/NodeIKernelGroupListener.ts @@ -0,0 +1,240 @@ +import { Group, GroupListUpdateType, GroupMember, GroupNotify } from '@/ntqqapi/types' + +interface IGroupListener { + onGroupListUpdate(updateType: GroupListUpdateType, groupList: Group[]): void + + onGroupExtListUpdate(...args: unknown[]): void + + onGroupSingleScreenNotifies(doubt: boolean, seq: string, notifies: GroupNotify[]): void + + onGroupNotifiesUpdated(dboubt: boolean, notifies: GroupNotify[]): void + + onGroupNotifiesUnreadCountUpdated(...args: unknown[]): void + + onGroupDetailInfoChange(...args: unknown[]): void + + onGroupAllInfoChange(...args: unknown[]): void + + onGroupsMsgMaskResult(...args: unknown[]): void + + onGroupConfMemberChange(...args: unknown[]): void + + onGroupBulletinChange(...args: unknown[]): void + + onGetGroupBulletinListResult(...args: unknown[]): void + + onMemberListChange(arg: { + sceneId: string, + ids: string[], + infos: Map, + finish: boolean, + hasRobot: boolean + }): void + + onMemberInfoChange(groupCode: string, changeType: number, members: Map): void + + onSearchMemberChange(...args: unknown[]): void + + onGroupBulletinRichMediaDownloadComplete(...args: unknown[]): void + + onGroupBulletinRichMediaProgressUpdate(...args: unknown[]): void + + onGroupStatisticInfoChange(...args: unknown[]): void + + onJoinGroupNotify(...args: unknown[]): void + + onShutUpMemberListChanged(...args: unknown[]): void + + onGroupBulletinRemindNotify(...args: unknown[]): void + + onGroupFirstBulletinNotify(...args: unknown[]): void + + onJoinGroupNoVerifyFlag(...args: unknown[]): void + + onGroupArkInviteStateResult(...args: unknown[]): void + // 发现于Win 9.9.9 23159 + onGroupMemberLevelInfoChange(...args: unknown[]): void +} + +export interface NodeIKernelGroupListener extends IGroupListener { + // eslint-disable-next-line @typescript-eslint/no-misused-new + new(listener: IGroupListener): NodeIKernelGroupListener +} + +export class GroupListener implements IGroupListener { + // 发现于Win 9.9.9 23159 + onGroupMemberLevelInfoChange(...args: unknown[]): void { + + } + onGetGroupBulletinListResult(...args: unknown[]) { + } + + onGroupAllInfoChange(...args: unknown[]) { + } + + onGroupBulletinChange(...args: unknown[]) { + } + + onGroupBulletinRemindNotify(...args: unknown[]) { + } + + onGroupArkInviteStateResult(...args: unknown[]) { + } + + onGroupBulletinRichMediaDownloadComplete(...args: unknown[]) { + } + + onGroupConfMemberChange(...args: unknown[]) { + } + + onGroupDetailInfoChange(...args: unknown[]) { + } + + onGroupExtListUpdate(...args: unknown[]) { + } + + onGroupFirstBulletinNotify(...args: unknown[]) { + } + + onGroupListUpdate(updateType: GroupListUpdateType, groupList: Group[]) { + } + + onGroupNotifiesUpdated(dboubt: boolean, notifies: GroupNotify[]) { + } + + onGroupBulletinRichMediaProgressUpdate(...args: unknown[]) { + } + + onGroupNotifiesUnreadCountUpdated(...args: unknown[]) { + } + + onGroupSingleScreenNotifies(doubt: boolean, seq: string, notifies: GroupNotify[]) { + } + + onGroupsMsgMaskResult(...args: unknown[]) { + } + + onGroupStatisticInfoChange(...args: unknown[]) { + } + + onJoinGroupNotify(...args: unknown[]) { + } + + onJoinGroupNoVerifyFlag(...args: unknown[]) { + } + + onMemberInfoChange(groupCode: string, changeType: number, members: Map) { + } + + onMemberListChange(arg: { + sceneId: string, + ids: string[], + infos: Map, // uid -> GroupMember + finish: boolean, + hasRobot: boolean + }) { + } + + onSearchMemberChange(...args: unknown[]) { + } + + onShutUpMemberListChanged(...args: unknown[]) { + } +} + +export class DebugGroupListener implements IGroupListener { + onGroupMemberLevelInfoChange(...args: unknown[]): void { + console.log('onGroupMemberLevelInfoChange:', ...args) + } + onGetGroupBulletinListResult(...args: unknown[]) { + console.log('onGetGroupBulletinListResult:', ...args) + } + + onGroupAllInfoChange(...args: unknown[]) { + console.log('onGroupAllInfoChange:', ...args) + } + + onGroupBulletinChange(...args: unknown[]) { + console.log('onGroupBulletinChange:', ...args) + } + + onGroupBulletinRemindNotify(...args: unknown[]) { + console.log('onGroupBulletinRemindNotify:', ...args) + } + + onGroupArkInviteStateResult(...args: unknown[]) { + console.log('onGroupArkInviteStateResult:', ...args) + } + + onGroupBulletinRichMediaDownloadComplete(...args: unknown[]) { + console.log('onGroupBulletinRichMediaDownloadComplete:', ...args) + } + + onGroupConfMemberChange(...args: unknown[]) { + console.log('onGroupConfMemberChange:', ...args) + } + + onGroupDetailInfoChange(...args: unknown[]) { + console.log('onGroupDetailInfoChange:', ...args) + } + + onGroupExtListUpdate(...args: unknown[]) { + console.log('onGroupExtListUpdate:', ...args) + } + + onGroupFirstBulletinNotify(...args: unknown[]) { + console.log('onGroupFirstBulletinNotify:', ...args) + } + + onGroupListUpdate(...args: unknown[]) { + console.log('onGroupListUpdate:', ...args) + } + + onGroupNotifiesUpdated(...args: unknown[]) { + console.log('onGroupNotifiesUpdated:', ...args) + } + + onGroupBulletinRichMediaProgressUpdate(...args: unknown[]) { + console.log('onGroupBulletinRichMediaProgressUpdate:', ...args) + } + + onGroupNotifiesUnreadCountUpdated(...args: unknown[]) { + console.log('onGroupNotifiesUnreadCountUpdated:', ...args) + } + + onGroupSingleScreenNotifies(doubt: boolean, seq: string, notifies: GroupNotify[]) { + console.log('onGroupSingleScreenNotifies:') + } + + onGroupsMsgMaskResult(...args: unknown[]) { + console.log('onGroupsMsgMaskResult:', ...args) + } + + onGroupStatisticInfoChange(...args: unknown[]) { + console.log('onGroupStatisticInfoChange:', ...args) + } + + onJoinGroupNotify(...args: unknown[]) { + console.log('onJoinGroupNotify:', ...args) + } + + onJoinGroupNoVerifyFlag(...args: unknown[]) { + console.log('onJoinGroupNoVerifyFlag:', ...args) + } + + onMemberInfoChange(groupCode: string, changeType: number, members: Map) { + console.log('onMemberInfoChange:', groupCode, changeType, members) + } + + onMemberListChange(...args: unknown[]) { + console.log('onMemberListChange:', ...args) + } + + onSearchMemberChange(...args: unknown[]) { + console.log('onSearchMemberChange:', ...args) + } + + onShutUpMemberListChanged(...args: unknown[]) { + console.log('onShutUpMemberListChanged:', ...args) + } +} \ No newline at end of file diff --git a/src/ntqqapi/listeners/index.ts b/src/ntqqapi/listeners/index.ts index b3932e9..66fb556 100644 --- a/src/ntqqapi/listeners/index.ts +++ b/src/ntqqapi/listeners/index.ts @@ -1 +1,2 @@ -export * from './NodeIKernelProfileListener' \ No newline at end of file +export * from './NodeIKernelProfileListener' +export * from './NodeIKernelGroupListener' \ No newline at end of file diff --git a/src/ntqqapi/services/NodeIKernelGroupService.ts b/src/ntqqapi/services/NodeIKernelGroupService.ts new file mode 100644 index 0000000..860e53a --- /dev/null +++ b/src/ntqqapi/services/NodeIKernelGroupService.ts @@ -0,0 +1,249 @@ +import { NodeIKernelGroupListener } from '@/ntqqapi/listeners' +import { + GroupExtParam, + GroupMember, + GroupMemberRole, + GroupNotifyTypes, + GroupRequestOperateTypes, +} from '@/ntqqapi/types' +import { GeneralCallResult } from './common' + +//高版本的接口不应该随意使用 使用应该严格进行pr审核 同时部分ipc中未出现的接口不要过于依赖 应该做好数据兜底 + +export interface NodeIKernelGroupService { + getMemberCommonInfo(Req: { + groupCode: string, + startUin: string, + identifyFlag: string, + uinList: string[], + memberCommonFilter: { + memberUin: number, + uinFlag: number, + uinFlagExt: number, + uinMobileFlag: number, + shutUpTime: number, + privilege: number, + }, + memberNum: number, + filterMethod: string, + onlineFlag: string, + realSpecialTitleFlag: number + }): Promise + //26702 + getGroupMemberLevelInfo(groupCode: string): Promise + //26702 + getGroupHonorList(groupCodes: Array): unknown + + getUinByUids(uins: string[]): Promise<{ + errCode: number, + errMsg: string, + uins: Map + }> + + getUidByUins(uins: string[]): Promise<{ + errCode: number, + errMsg: string, + uids: Map + }> + //26702(其实更早 但是我不知道) + checkGroupMemberCache(arrayList: Array): Promise + + //26702(其实更早 但是我不知道) + getGroupLatestEssenceList(groupCode: string): Promise + + //26702(其实更早 但是我不知道) + shareDigest(Req: { + appId: string, + appType: number, + msgStyle: number, + recvUin: string, + sendType: number, + clientInfo: { + platform: number + }, + richMsg: { + usingArk: boolean, + title: string, + summary: string, + url: string, + pictureUrl: string, + brief: string + } + }): Promise + //26702(其实更早 但是我不知道) + isEssenceMsg(Req: { groupCode: string, msgRandom: number, msgSeq: number }): Promise + //26702(其实更早 但是我不知道) + queryCachedEssenceMsg(Req: { groupCode: string, msgRandom: number, msgSeq: number }): Promise + //26702(其实更早 但是我不知道) + fetchGroupEssenceList(Req: { groupCode: string, pageStart: number, pageLimit: number }, Arg: unknown): Promise + //26702 + getAllMemberList(groupCode: string, forceFetch: boolean): Promise<{ + errCode: number, + errMsg: string, + result: { + ids: Array<{ + uid: string, + index: number//0 + }>, + infos: {}, + finish: true, + hasRobot: false + } + }> + + setHeader(uid: string, path: string): unknown + + addKernelGroupListener(listener: NodeIKernelGroupListener): number + + removeKernelGroupListener(listenerId: unknown): void + + createMemberListScene(groupCode: string, scene: string): string + + destroyMemberListScene(SceneId: string): void + //About Arg (a) name: lastId 根据手Q来看为object {index:?(number),uid:string} + getNextMemberList(sceneId: string, a: undefined, num: number): Promise<{ + errCode: number, errMsg: string, + result: { ids: string[], infos: Map, finish: boolean, hasRobot: boolean } + }> + + getPrevMemberList(): unknown + + monitorMemberList(): unknown + + searchMember(sceneId: string, keywords: string[]): unknown + + getMemberInfo(group_id: string, uids: string[], forceFetch: boolean): Promise + //getMemberInfo [ '56729xxxx', [ 'u_4Nj08cwW5Hxxxxx' ], true ] + + kickMember(groupCode: string, memberUids: string[], refuseForever: boolean, kickReason: string): Promise + + modifyMemberRole(groupCode: string, uid: string, role: GroupMemberRole): void + + modifyMemberCardName(groupCode: string, uid: string, cardName: string): void + + getTransferableMemberInfo(groupCode: string): unknown//获取整个群的 + + transferGroup(uid: string): void + + getGroupList(force: boolean): Promise + + getGroupExtList(force: boolean): Promise + + getGroupDetailInfo(groupCode: string): unknown + + getMemberExtInfo(param: GroupExtParam): Promise//req + + getGroupAllInfo(): unknown + + getDiscussExistInfo(): unknown + + getGroupConfMember(): unknown + + getGroupMsgMask(): unknown + + getGroupPortrait(): void + + modifyGroupName(groupCode: string, groupName: string, arg: false): void + + modifyGroupRemark(groupCode: string, remark: string): void + + modifyGroupDetailInfo(groupCode: string, arg: unknown): void + + setGroupMsgMask(groupCode: string, arg: unknown): void + + changeGroupShieldSettingTemp(groupCode: string, arg: unknown): void + + inviteToGroup(arg: unknown): void + + inviteMembersToGroup(args: unknown[]): void + + inviteMembersToGroupWithMsg(args: unknown): void + + createGroup(arg: unknown): void + + createGroupWithMembers(arg: unknown): void + + quitGroup(groupCode: string): void + + destroyGroup(groupCode: string): void + //获取单屏群通知列表 + getSingleScreenNotifies(force: boolean, start_seq: string, num: number): Promise + + clearGroupNotifies(groupCode: string): void + + getGroupNotifiesUnreadCount(unknown: Boolean): Promise + + clearGroupNotifiesUnreadCount(groupCode: string): void + + operateSysNotify( + doubt: boolean, + operateMsg: { + operateType: GroupRequestOperateTypes, // 2 拒绝 + targetMsg: { + seq: string, // 通知序列号 + type: GroupNotifyTypes, + groupCode: string, + postscript: string + } + }): Promise + + setTop(groupCode: string, isTop: boolean): void + + getGroupBulletin(groupCode: string): unknown + + deleteGroupBulletin(groupCode: string, seq: string): void + + publishGroupBulletin(groupCode: string, pskey: string, data: any): Promise + + publishInstructionForNewcomers(groupCode: string, arg: unknown): void + + uploadGroupBulletinPic(groupCode: string, pskey: string, imagePath: string): Promise + + downloadGroupBulletinRichMedia(groupCode: string): unknown + + getGroupBulletinList(groupCode: string): unknown + + getGroupStatisticInfo(groupCode: string): unknown + + getGroupRemainAtTimes(groupCode: string): number + + getJoinGroupNoVerifyFlag(groupCode: string): unknown + + getGroupArkInviteState(groupCode: string): unknown + + reqToJoinGroup(groupCode: string, arg: unknown): void + + setGroupShutUp(groupCode: string, shutUp: boolean): void + + getGroupShutUpMemberList(groupCode: string): unknown[] + + setMemberShutUp(groupCode: string, memberTimes: { uid: string, timeStamp: number }[]): Promise + + getGroupRecommendContactArkJson(groupCode: string): unknown + + getJoinGroupLink(groupCode: string): unknown + + modifyGroupExtInfo(groupCode: string, arg: unknown): void + + //需要提前判断是否存在 高版本新增 + addGroupEssence(param: { + groupCode: string + msgRandom: number, + msgSeq: number + }): Promise + //需要提前判断是否存在 高版本新增 + removeGroupEssence(param: { + groupCode: string + msgRandom: number, + msgSeq: number + }): Promise + + isNull(): boolean +} \ No newline at end of file diff --git a/src/ntqqapi/services/NodeIKernelProfileLikeService.ts b/src/ntqqapi/services/NodeIKernelProfileLikeService.ts new file mode 100644 index 0000000..dc44a31 --- /dev/null +++ b/src/ntqqapi/services/NodeIKernelProfileLikeService.ts @@ -0,0 +1,22 @@ +import { BuddyProfileLikeReq } from '../types' +import { GeneralCallResult } from './common' + +export interface NodeIKernelProfileLikeService { + addKernelProfileLikeListener(listener: NodeIKernelProfileLikeService): void + + removeKernelProfileLikeListener(listener: unknown): void + + setBuddyProfileLike(...args: unknown[]): { result: number, errMsg: string, succCounts: number } + + getBuddyProfileLike(req: BuddyProfileLikeReq): Promise, + 'friendMaxVotes': number, + 'start': number + } + }> + + getProfileLikeScidResourceInfo(...args: unknown[]): void + + isNull(): boolean +} \ No newline at end of file diff --git a/src/ntqqapi/services/index.ts b/src/ntqqapi/services/index.ts index 3e5969f..052026d 100644 --- a/src/ntqqapi/services/index.ts +++ b/src/ntqqapi/services/index.ts @@ -1,2 +1,4 @@ export * from './NodeIKernelBuddyService' -export * from './NodeIKernelProfileService' \ No newline at end of file +export * from './NodeIKernelProfileService' +export * from './NodeIKernelGroupService' +export * from './NodeIKernelProfileLikeService' \ No newline at end of file diff --git a/src/ntqqapi/types/group.ts b/src/ntqqapi/types/group.ts index fc17b72..4ba3e07 100644 --- a/src/ntqqapi/types/group.ts +++ b/src/ntqqapi/types/group.ts @@ -1,5 +1,12 @@ import { QQLevel, Sex } from './user' +export enum GroupListUpdateType { + REFRESHALL, + GETALL, + MODIFIED, + REMOVE +} + export interface Group { groupCode: string maxMember: number diff --git a/src/ntqqapi/types/notify.ts b/src/ntqqapi/types/notify.ts index cf91bc3..0b8c05b 100644 --- a/src/ntqqapi/types/notify.ts +++ b/src/ntqqapi/types/notify.ts @@ -64,3 +64,41 @@ export interface FriendRequestNotify { buddyReqs: FriendRequest[] } } + +export enum MemberExtSourceType { + DEFAULTTYPE = 0, + TITLETYPE = 1, + NEWGROUPTYPE = 2, +} + +export interface GroupExtParam { + groupCode: string + seq: string + beginUin: string + dataTime: string + uinList: Array + uinNum: string + groupType: string + richCardNameVer: string + sourceType: MemberExtSourceType + memberExtFilter: { + memberLevelInfoUin: number + memberLevelInfoPoint: number + memberLevelInfoActiveDay: number + memberLevelInfoLevel: number + memberLevelInfoName: number + levelName: number + dataTime: number + userShowFlag: number + sysShowFlag: number + timeToUpdate: number + nickName: number + specialTitle: number + levelNameNew: number + userShowFlagNew: number + msgNeedField: number + cmdUinFlagExt3Grocery: number + memberIcon: number + memberInfoSeq: number + } +} \ No newline at end of file diff --git a/src/ntqqapi/types/user.ts b/src/ntqqapi/types/user.ts index 8538265..e109794 100644 --- a/src/ntqqapi/types/user.ts +++ b/src/ntqqapi/types/user.ts @@ -258,4 +258,86 @@ export interface UserDetailInfoListenerArg { simpleInfo: SimpleInfo commonExt: CommonExt photoWall: PhotoWall +} + +export interface BuddyProfileLikeReq { + friendUids: string[] + basic: number + vote: number + favorite: number + userProfile: number + type: number + start: number + limit: number +} + +export interface UserDetailInfoByUinV2 { + result: number + errMsg: string + detail: { + uid: string + uin: string + simpleInfo: SimpleInfo + commonExt: CommonExt + photoWall: null + } +} + +export interface UserDetailInfoByUin { + result: number + errMsg: string + info: { + uid: string //这个没办法用 + qid: string + uin: string + nick: string + remark: string + longNick: string + avatarUrl: string + birthday_year: number + birthday_month: number + birthday_day: number + sex: number //0 + topTime: string + constellation: number + shengXiao: number + kBloodType: number + homeTown: string + makeFriendCareer: number + pos: string + eMail: string + phoneNum: string + college: string + country: string + province: string + city: string + postCode: string + address: string + isBlock: boolean + isSpecialCareOpen: boolean + isSpecialCareZone: boolean + ringId: string + regTime: number + interest: string + termType: number + labels: any[] + qqLevel: { crownNum: number, sunNum: number, moonNum: number, starNum: number } + isHideQQLevel: number + privilegeIcon: { jumpUrl: string, openIconList: any[], closeIconList: any[] } + isHidePrivilegeIcon: number + photoWall: { picList: any[] } + vipFlag: boolean + yearVipFlag: boolean + svipFlag: boolean + vipLevel: number + status: number + qidianMasterFlag: number + qidianCrewFlag: number + qidianCrewFlag2: number + extStatus: number + recommendImgFlag: number + disableEmojiShortCuts: number + pendantId: string + vipNameColorId: string + } } \ No newline at end of file diff --git a/src/ntqqapi/wrapper.ts b/src/ntqqapi/wrapper.ts index 40ed806..31e7f1c 100644 --- a/src/ntqqapi/wrapper.ts +++ b/src/ntqqapi/wrapper.ts @@ -1,10 +1,18 @@ -import { NodeIKernelBuddyService } from './services/NodeIKernelBuddyService' +import { + NodeIKernelBuddyService, + NodeIKernelGroupService, + NodeIKernelProfileService, + NodeIKernelProfileLikeService +} from './services' import os from 'node:os' const Process = require('node:process') export interface NodeIQQNTWrapperSession { [key: string]: any getBuddyService(): NodeIKernelBuddyService + getGroupService(): NodeIKernelGroupService + getProfileService(): NodeIKernelProfileService + getProfileLikeService(): NodeIKernelProfileLikeService } export interface WrapperApi { @@ -65,4 +73,8 @@ Process.dlopen = function (module, filename, flags = os.constants.dlopen.RTLD_LA } } return dlopenRet +} + +export function getSession() { + return wrapperApi.NodeIQQNTWrapperSession } \ No newline at end of file diff --git a/src/onebot11/action/group/GetGroupMemberList.ts b/src/onebot11/action/group/GetGroupMemberList.ts index 150426b..86e4456 100644 --- a/src/onebot11/action/group/GetGroupMemberList.ts +++ b/src/onebot11/action/group/GetGroupMemberList.ts @@ -18,7 +18,8 @@ class GetGroupMemberList extends BaseAction { const group = await getGroup(payload.group_id.toString()) if (group) { if (!group.members?.length || payload.no_cache === true || payload.no_cache === 'true') { - group.members = await NTQQGroupApi.getGroupMembers(payload.group_id.toString()) + const members = await NTQQGroupApi.getGroupMembers(payload.group_id.toString()) + group.members = Array.from(members.values()) log('强制刷新群成员列表, 数量: ', group.members.length) } return OB11Constructor.groupMembers(group) diff --git a/src/onebot11/action/msg/SendMsg.ts b/src/onebot11/action/msg/SendMsg.ts index 25c25fd..7df510c 100644 --- a/src/onebot11/action/msg/SendMsg.ts +++ b/src/onebot11/action/msg/SendMsg.ts @@ -7,10 +7,9 @@ import { GroupMemberRole, PicSubType, RawMessage, - SendArkElement, SendMessageElement, } from '../../../ntqqapi/types' -import { friends, getFriend, getGroup, getGroupMember, getUidByUin, selfInfo } from '../../../common/data' +import { friends, getGroup, getGroupMember, getUidByUin, selfInfo } from '../../../common/data' import { OB11MessageCustomMusic, OB11MessageData, @@ -20,63 +19,21 @@ import { OB11MessageMixType, OB11MessageMusic, OB11MessageNode, - OB11MessageVideo, OB11PostSendMsg, } from '../../types' -import { NTQQMsgApi } from '../../../ntqqapi/api/msg' import { SendMsgElementConstructor } from '../../../ntqqapi/constructor' import BaseAction from '../BaseAction' import { ActionName, BaseCheckResult } from '../types' -import * as fs from 'node:fs' +import fs from 'node:fs' import { decodeCQCode } from '../../cqcode' import { dbUtil } from '../../../common/db' 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 { NTQQGroupApi } from '../../../ntqqapi/api' -import { CustomMusicSignPostData, IdMusicSignPostData, MusicSign, MusicSignPostData } from '../../../common/utils/sign' -import { Peer } from '../../../ntqqapi/types/msg' - -function checkSendMessage(sendMsgList: OB11MessageData[]) { - function checkUri(uri: string): boolean { - const pattern = /^(file:\/\/|http:\/\/|https:\/\/|base64:\/\/)/ - return pattern.test(uri) - } - - for (let msg of sendMsgList) { - if (msg['type'] && msg['data']) { - let type = msg['type'] - let data = msg['data'] - if (type === 'text' && !data['text']) { - return 400 - } - else if (['image', 'voice', 'record'].includes(type)) { - if (!data['file']) { - return 400 - } - else { - if (checkUri(data['file'])) { - return 200 - } - else { - return 400 - } - } - } - else if (type === 'at' && !data['qq']) { - return 400 - } - else if (type === 'reply' && !data['id']) { - return 400 - } - } - else { - return 400 - } - } - return 200 -} +import { NTQQGroupApi, NTQQMsgApi, NTQQUserApi, NTQQFriendApi } from '@/ntqqapi/api' +import { CustomMusicSignPostData, IdMusicSignPostData, MusicSign, MusicSignPostData } from '@/common/utils/sign' +import { Peer } from '@/ntqqapi/types/msg' export interface ReturnDataType { message_id: number @@ -351,13 +308,11 @@ export class SendMsg extends BaseAction { } } if (payload.user_id && payload.message_type !== 'group') { - if (!(await getFriend(payload.user_id))) { - if (!ALLOW_SEND_TEMP_MSG && !(await dbUtil.getReceivedTempUinMap())[payload.user_id.toString()]) { - return { - valid: false, - message: `不能发送临时消息`, - } - } + const uid = await NTQQUserApi.getUidByUin(payload.user_id.toString()) + const isBuddy = await NTQQFriendApi.isBuddy(uid!) + // 此处有问题 + if (!isBuddy) { + //return { valid: false, message: '异常消息' } } } return { diff --git a/src/onebot11/action/user/SendLike.ts b/src/onebot11/action/user/SendLike.ts index 34694ac..4048814 100644 --- a/src/onebot11/action/user/SendLike.ts +++ b/src/onebot11/action/user/SendLike.ts @@ -1,8 +1,6 @@ import BaseAction from '../BaseAction' -import { getFriend, getUidByUin, uidMaps } from '../../../common/data' import { ActionName } from '../types' -import { NTQQFriendApi } from '../../../ntqqapi/api/friend' -import { log } from '../../../common/utils/log' +import { NTQQUserApi } from '@/ntqqapi/api' interface Payload { user_id: number @@ -13,19 +11,12 @@ export default class SendLike extends BaseAction { actionName = ActionName.SendLike protected async _handle(payload: Payload): Promise { - log('点赞参数', payload) try { const qq = payload.user_id.toString() - const friend = await getFriend(qq) - let uid: string - if (!friend) { - uid = getUidByUin(qq)! - } else { - uid = friend.uid - } - let result = await NTQQFriendApi.likeFriend(uid, parseInt(payload.times?.toString()) || 1) + const uid: string = await NTQQUserApi.getUidByUin(qq) || '' + const result = await NTQQUserApi.like(uid, parseInt(payload.times?.toString()) || 1) if (result.result !== 0) { - throw result.errMsg + throw Error(result.errMsg) } } catch (e) { throw `点赞失败 ${e}` diff --git a/src/onebot11/constructor.ts b/src/onebot11/constructor.ts index c688b81..d45f818 100644 --- a/src/onebot11/constructor.ts +++ b/src/onebot11/constructor.ts @@ -26,7 +26,7 @@ import { VideoElement, FriendV2 } from '../ntqqapi/types' -import { deleteGroup, getFriend, getGroupMember, selfInfo, tempGroupCodeMap, uidMaps } from '../common/data' +import { deleteGroup, getGroupMember, selfInfo, tempGroupCodeMap, uidMaps } from '../common/data' import { EventType } from './event/OB11BaseEvent' import { encodeCQCode } from './cqcode' import { dbUtil } from '../common/db' @@ -34,9 +34,6 @@ import { OB11GroupIncreaseEvent } from './event/notice/OB11GroupIncreaseEvent' import { OB11GroupBanEvent } from './event/notice/OB11GroupBanEvent' import { OB11GroupUploadNoticeEvent } from './event/notice/OB11GroupUploadNoticeEvent' import { OB11GroupNoticeEvent } from './event/notice/OB11GroupNoticeEvent' -import { NTQQUserApi } from '../ntqqapi/api/user' -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 { isNull, sleep } from '../common/utils/helper' @@ -44,7 +41,7 @@ import { getConfigUtil } from '../common/config' import { OB11GroupTitleEvent } from './event/notice/OB11GroupTitleEvent' import { OB11GroupCardEvent } from './event/notice/OB11GroupCardEvent' import { OB11GroupDecreaseEvent } from './event/notice/OB11GroupDecreaseEvent' -import { NTQQGroupApi } from '../ntqqapi/api' +import { NTQQGroupApi, NTQQUserApi, NTQQFileApi, NTQQMsgApi } from '../ntqqapi/api' import { OB11GroupMsgEmojiLikeEvent } from './event/notice/OB11MsgEmojiLikeEvent' import { mFaceCache } from '../ntqqapi/constructor' import { OB11FriendAddNoticeEvent } from './event/notice/OB11FriendAddNoticeEvent' @@ -54,8 +51,6 @@ import { OB11FriendPokeEvent, OB11GroupPokeEvent } from './event/notice/OB11Poke import { OB11BaseNoticeEvent } from './event/notice/OB11BaseNoticeEvent' import { OB11GroupEssenceEvent } from './event/notice/OB11GroupEssenceEvent' -let lastRKeyUpdateTime = 0 - export class OB11Constructor { static async message(msg: RawMessage): Promise { let config = getConfigUtil().getConfig() @@ -99,10 +94,7 @@ export class OB11Constructor { } else if (msg.chatType == ChatType.friend) { resMsg.sub_type = 'friend' - const friend = await getFriend(msg.senderUin!) - if (friend) { - resMsg.sender.nickname = friend.nick - } + resMsg.sender.nickname = (await NTQQUserApi.getUserDetailInfo(msg.senderUid)).nick } else if (msg.chatType == ChatType.temp) { resMsg.sub_type = 'group' From 6c8d3db3a4e8dab59012daf3b11940974ee1b409 Mon Sep 17 00:00:00 2001 From: idranme Date: Fri, 9 Aug 2024 14:26:30 +0800 Subject: [PATCH 11/14] opt --- src/main/main.ts | 4 ++-- src/ntqqapi/api/file.ts | 4 ++-- src/ntqqapi/api/msg.ts | 4 ++-- src/ntqqapi/wrapper.ts | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/main/main.ts b/src/main/main.ts index dc74a4b..76ffee8 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -52,7 +52,7 @@ import { GroupDecreaseSubType, OB11GroupDecreaseEvent } from '../onebot11/event/ import '../ntqqapi/wrapper' import { sentMessages } from '@/ntqqapi/api' import { NTEventDispatch } from '../common/utils/EventTask' -import { wrapperApi, wrapperConstructor } from '../ntqqapi/wrapper' +import { wrapperConstructor, getSession } from '../ntqqapi/wrapper' let mainWindow: BrowserWindow | null = null @@ -441,7 +441,7 @@ function onLoad() { uidMaps[value] = key } }) - NTEventDispatch.init({ ListenerMap: wrapperConstructor, WrapperSession: wrapperApi.NodeIQQNTWrapperSession! }) + NTEventDispatch.init({ ListenerMap: wrapperConstructor, WrapperSession: getSession()! }) log('start activate group member info') NTQQGroupApi.activateMemberInfoChange().then().catch(log) NTQQGroupApi.activateMemberListChange().then().catch(log) diff --git a/src/ntqqapi/api/file.ts b/src/ntqqapi/api/file.ts index d38c86e..19bc1b0 100644 --- a/src/ntqqapi/api/file.ts +++ b/src/ntqqapi/api/file.ts @@ -16,12 +16,12 @@ import fs from 'node:fs' import { ReceiveCmdS } from '../hook' import { log } from '@/common/utils' import { rkeyManager } from '@/ntqqapi/api/rkey' -import { wrapperApi } from '@/ntqqapi/wrapper' +import { getSession } from '@/ntqqapi/wrapper' import { Peer } from '@/ntqqapi/types/msg' export class NTQQFileApi { static async getVideoUrl(peer: Peer, msgId: string, elementId: string): Promise { - const session = wrapperApi.NodeIQQNTWrapperSession + const session = getSession() return (await session?.getRichMediaService().getVideoPlayUrlV2(peer, msgId, elementId, diff --git a/src/ntqqapi/api/msg.ts b/src/ntqqapi/api/msg.ts index 9daf31e..159c158 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/wrapper' +import { getSession } from '@/ntqqapi/wrapper' export let sendMessagePool: Record void) | null> = {} // peerUid: callbackFunc @@ -289,7 +289,7 @@ export class NTQQMsgApi { }) } static async getMsgsBySeqAndCount(peer: Peer, seq: string, count: number, desc: boolean, z: boolean) { - const session = wrapperApi.NodeIQQNTWrapperSession + const session = getSession() return await session?.getMsgService().getMsgsBySeqAndCount(peer, seq, count, desc, z); } } diff --git a/src/ntqqapi/wrapper.ts b/src/ntqqapi/wrapper.ts index 31e7f1c..91567bf 100644 --- a/src/ntqqapi/wrapper.ts +++ b/src/ntqqapi/wrapper.ts @@ -36,7 +36,7 @@ export interface WrapperConstructor { NodeIKernelProfileListener?: any } -export const wrapperApi: WrapperApi = {} +const wrapperApi: WrapperApi = {} export const wrapperConstructor: WrapperConstructor = {} @@ -76,5 +76,5 @@ Process.dlopen = function (module, filename, flags = os.constants.dlopen.RTLD_LA } export function getSession() { - return wrapperApi.NodeIQQNTWrapperSession + return wrapperApi['NodeIQQNTWrapperSession'] } \ No newline at end of file From 1fc02229df33da68b075d71b27db65d3f2db4d30 Mon Sep 17 00:00:00 2001 From: idranme Date: Fri, 9 Aug 2024 15:40:08 +0800 Subject: [PATCH 12/14] sync --- package.json | 2 +- src/ntqqapi/api/group.ts | 8 +- src/ntqqapi/api/msg.ts | 72 +- src/ntqqapi/api/user.ts | 4 +- .../listeners/NodeIKernelMsgListener.ts | 514 ++++++++++++ src/ntqqapi/listeners/index.ts | 3 +- src/ntqqapi/services/NodeIKernelMSFService.ts | 3 + src/ntqqapi/services/NodeIKernelMsgService.ts | 744 ++++++++++++++++++ .../services/NodeIKernelUixConvertService.ts | 5 + src/ntqqapi/services/index.ts | 5 +- src/ntqqapi/types/msg.ts | 65 +- src/ntqqapi/wrapper.ts | 8 +- src/onebot11/action/msg/SendMsg.ts | 129 +-- 13 files changed, 1450 insertions(+), 112 deletions(-) create mode 100644 src/ntqqapi/listeners/NodeIKernelMsgListener.ts create mode 100644 src/ntqqapi/services/NodeIKernelMSFService.ts create mode 100644 src/ntqqapi/services/NodeIKernelMsgService.ts create mode 100644 src/ntqqapi/services/NodeIKernelUixConvertService.ts diff --git a/package.json b/package.json index 2f3f21a..ab8026b 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "electron": "^29.0.1", "electron-vite": "^2.3.0", "typescript": "^5.5.4", - "vite": "^5.3.5", + "vite": "^5.4.0", "vite-plugin-cp": "^4.0.8" }, "packageManager": "yarn@4.4.0" diff --git a/src/ntqqapi/api/group.ts b/src/ntqqapi/api/group.ts index 21dc68f..5663862 100644 --- a/src/ntqqapi/api/group.ts +++ b/src/ntqqapi/api/group.ts @@ -276,8 +276,8 @@ export class NTQQGroupApi { 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) + 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) @@ -290,8 +290,8 @@ export class NTQQGroupApi { 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) + 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) diff --git a/src/ntqqapi/api/msg.ts b/src/ntqqapi/api/msg.ts index 159c158..615eda5 100644 --- a/src/ntqqapi/api/msg.ts +++ b/src/ntqqapi/api/msg.ts @@ -5,8 +5,9 @@ import { selfInfo } from '../../common/data' import { ReceiveCmdS, registerReceiveHook } from '../hook' import { log } from '../../common/utils/log' import { sleep } from '../../common/utils/helper' -import { isQQ998 } from '../../common/utils' +import { isQQ998, getBuildVersion } from '../../common/utils' import { getSession } from '@/ntqqapi/wrapper' +import { NTEventDispatch } from '@/common/utils/EventTask' export let sendMessagePool: Record void) | null> = {} // peerUid: callbackFunc @@ -202,6 +203,9 @@ export class NTQQMsgApi { } static async sendMsg(peer: Peer, msgElements: SendMessageElement[], waitComplete = true, timeout = 10000) { + if (getBuildVersion() >= 26702) { + return NTQQMsgApi.sendMsgV2(peer, msgElements, waitComplete, timeout) + } const waiter = sendWaiter(peer, waitComplete, timeout) callNTQQApi({ methodName: NTQQApiMethod.SEND_MSG, @@ -218,6 +222,72 @@ export class NTQQMsgApi { return await waiter } + static async sendMsgV2(peer: Peer, msgElements: SendMessageElement[], waitComplete = true, timeout = 10000) { + if (peer.chatType === ChatType.temp) { + //await NTQQMsgApi.PrepareTempChat().then().catch() + } + function generateMsgId() { + const timestamp = Math.floor(Date.now() / 1000) + const random = Math.floor(Math.random() * Math.pow(2, 32)) + const buffer = Buffer.alloc(8) + buffer.writeUInt32BE(timestamp, 0) + buffer.writeUInt32BE(random, 4) + const msgId = BigInt('0x' + buffer.toString('hex')).toString() + return msgId + } + // 此处有采用Hack方法 利用数据返回正确得到对应消息 + // 与之前 Peer队列 MsgSeq队列 真正的MsgId并发不同 + // 谨慎采用 目前测试暂无问题 Developer.Mlikiowa + let msgId: string + try { + msgId = await NTQQMsgApi.getMsgUnique(peer.chatType, await NTQQMsgApi.getServerTime()) + } catch (error) { + //if (!napCatCore.session.getMsgService()['generateMsgUniqueId']) + //兜底识别策略V2 + msgId = generateMsgId().toString() + } + let data = await NTEventDispatch.CallNormalEvent< + (msgId: string, peer: Peer, msgElements: SendMessageElement[], map: Map) => Promise, + (msgList: RawMessage[]) => void + >( + 'NodeIKernelMsgService/sendMsg', + 'NodeIKernelMsgListener/onMsgInfoListUpdate', + 1, + timeout, + (msgRecords: RawMessage[]) => { + for (let msgRecord of msgRecords) { + if (msgRecord.msgId === msgId && msgRecord.sendStatus === 2) { + return true + } + } + return false + }, + msgId, + peer, + msgElements, + new Map() + ) + const retMsg = data[1].find(msgRecord => { + if (msgRecord.msgId === msgId) { + return true + } + }) + return retMsg! + } + + static async getMsgUnique(chatType: number, time: string) { + const session = getSession() + if (getBuildVersion() >= 26702) { + return session?.getMsgService().generateMsgUniqueId(chatType, time)! + } + return session?.getMsgService().getMsgUniqueId(time)! + } + + static async getServerTime() { + const session = getSession() + return session?.getMSFService().getServerTime()! + } + static async forwardMsg(srcPeer: Peer, destPeer: Peer, msgIds: string[]) { const waiter = sendWaiter(destPeer, true, 10000) callNTQQApi({ diff --git a/src/ntqqapi/api/user.ts b/src/ntqqapi/api/user.ts index a3803a8..bd8e9d0 100644 --- a/src/ntqqapi/api/user.ts +++ b/src/ntqqapi/api/user.ts @@ -221,7 +221,7 @@ export class NTQQUserApi { static async getUidByUinV1(Uin: string) { const session = getSession() // 通用转换开始尝试 - let uid = (await session?.getUixConvertService().getUid([Uin])).uidInfo.get(Uin); + let uid = (await session?.getUixConvertService().getUid([Uin])!).uidInfo.get(Uin); // Uid 好友转 if (!uid) { friends.forEach((t) => { @@ -255,7 +255,7 @@ export class NTQQUserApi { if (uid) return uid uid = (await session?.getGroupService().getUidByUins([Uin])!).uids.get(Uin) if (uid) return uid - uid = (await session?.getUixConvertService().getUid([Uin])).uidInfo.get(Uin) + uid = (await session?.getUixConvertService().getUid([Uin])!).uidInfo.get(Uin) if (uid) return uid console.log((await NTQQFriendApi.getBuddyIdMapCache(true))) uid = (await NTQQFriendApi.getBuddyIdMapCache(true)).getValue(Uin)//从Buddy缓存获取Uid diff --git a/src/ntqqapi/listeners/NodeIKernelMsgListener.ts b/src/ntqqapi/listeners/NodeIKernelMsgListener.ts new file mode 100644 index 0000000..140714d --- /dev/null +++ b/src/ntqqapi/listeners/NodeIKernelMsgListener.ts @@ -0,0 +1,514 @@ +import { ChatType, RawMessage } from '@/ntqqapi/types' + +export interface OnRichMediaDownloadCompleteParams { + fileModelId: string, + msgElementId: string, + msgId: string, + fileId: string, + fileProgress: string, // '0' + fileSpeed: string, // '0' + fileErrCode: string, // '0' + fileErrMsg: string, + fileDownType: number, // 暂时未知 + thumbSize: number, + filePath: string, + totalSize: string, + trasferStatus: number, + step: number, + commonFileInfo: unknown | null, + fileSrvErrCode: string, + clientMsg: string, + businessId: number, + userTotalSpacePerDay: unknown | null, + userUsedSpacePerDay: unknown | null +} + +export interface onGroupFileInfoUpdateParamType { + retCode: number + retMsg: string + clientWording: string + isEnd: boolean + item: Array + allFileCount: string + nextIndex: string + reqId: string +} + +// { +// sessionType: 1, +// chatType: 100, +// peerUid: 'u_PVQ3tl6K78xxxx', +// groupCode: '809079648', +// fromNick: '拾xxxx, +// sig: '0x' +// } +export interface TempOnRecvParams { + sessionType: number,//1 + chatType: ChatType,//100 + peerUid: string,//uid + groupCode: string,//gc + fromNick: string,//gc name + sig: string, +} + +export interface IKernelMsgListener { + onAddSendMsg(msgRecord: RawMessage): void + + onBroadcastHelperDownloadComplete(broadcastHelperTransNotifyInfo: unknown): void + + onBroadcastHelperProgressUpdate(broadcastHelperTransNotifyInfo: unknown): void + + onChannelFreqLimitInfoUpdate(contact: unknown, z: unknown, freqLimitInfo: unknown): void + + onContactUnreadCntUpdate(hashMap: unknown): void + + onCustomWithdrawConfigUpdate(customWithdrawConfig: unknown): void + + onDraftUpdate(contact: unknown, arrayList: unknown, j2: unknown): void + + onEmojiDownloadComplete(emojiNotifyInfo: unknown): void + + onEmojiResourceUpdate(emojiResourceInfo: unknown): void + + onFeedEventUpdate(firstViewDirectMsgNotifyInfo: unknown): void + + onFileMsgCome(arrayList: unknown): void + + onFirstViewDirectMsgUpdate(firstViewDirectMsgNotifyInfo: unknown): void + + onFirstViewGroupGuildMapping(arrayList: unknown): void + + onGrabPasswordRedBag(i2: unknown, str: unknown, i3: unknown, recvdOrder: unknown, msgRecord: unknown): void + + onGroupFileInfoAdd(groupItem: unknown): void + + onGroupFileInfoUpdate(groupFileListResult: onGroupFileInfoUpdateParamType): void + + onGroupGuildUpdate(groupGuildNotifyInfo: unknown): void + + onGroupTransferInfoAdd(groupItem: unknown): void + + onGroupTransferInfoUpdate(groupFileListResult: unknown): void + + onGuildInteractiveUpdate(guildInteractiveNotificationItem: unknown): void + + onGuildMsgAbFlagChanged(guildMsgAbFlag: unknown): void + + onGuildNotificationAbstractUpdate(guildNotificationAbstractInfo: unknown): void + + onHitCsRelatedEmojiResult(downloadRelateEmojiResultInfo: unknown): void + + onHitEmojiKeywordResult(hitRelatedEmojiWordsResult: unknown): void + + onHitRelatedEmojiResult(relatedWordEmojiInfo: unknown): void + + onImportOldDbProgressUpdate(importOldDbMsgNotifyInfo: unknown): void + + onInputStatusPush(inputStatusInfo: unknown): void + + onKickedOffLine(kickedInfo: unknown): void + + onLineDev(arrayList: unknown): void + + onLogLevelChanged(j2: unknown): void + + onMsgAbstractUpdate(arrayList: unknown): void + + onMsgBoxChanged(arrayList: unknown): void + + onMsgDelete(contact: unknown, arrayList: unknown): void + + onMsgEventListUpdate(hashMap: unknown): void + + onMsgInfoListAdd(arrayList: unknown): void + + onMsgInfoListUpdate(msgList: RawMessage[]): void + + onMsgQRCodeStatusChanged(i2: unknown): void + + onMsgRecall(i2: unknown, str: unknown, j2: unknown): void + + onMsgSecurityNotify(msgRecord: unknown): void + + onMsgSettingUpdate(msgSetting: unknown): void + + onNtFirstViewMsgSyncEnd(): void + + onNtMsgSyncEnd(): void + + onNtMsgSyncStart(): void + + onReadFeedEventUpdate(firstViewDirectMsgNotifyInfo: unknown): void + + onRecvGroupGuildFlag(i2: unknown): void + + onRecvMsg(...arrayList: unknown[]): void + + onRecvMsgSvrRspTransInfo(j2: unknown, contact: unknown, i2: unknown, i3: unknown, str: unknown, bArr: unknown): void + + onRecvOnlineFileMsg(arrayList: unknown): void + + onRecvS2CMsg(arrayList: unknown): void + + onRecvSysMsg(arrayList: unknown): void + + onRecvUDCFlag(i2: unknown): void + + onRichMediaDownloadComplete(fileTransNotifyInfo: OnRichMediaDownloadCompleteParams): void + + onRichMediaProgerssUpdate(fileTransNotifyInfo: unknown): void + + onRichMediaUploadComplete(fileTransNotifyInfo: unknown): void + + onSearchGroupFileInfoUpdate(searchGroupFileResult: + { + result: { + retCode: number, + retMsg: string, + clientWording: string + }, + syncCookie: string, + totalMatchCount: number, + ownerMatchCount: number, + isEnd: boolean, + reqId: number, + item: Array<{ + groupCode: string, + groupName: string, + uploaderUin: string, + uploaderName: string, + matchUin: string, + matchWords: Array, + fileNameHits: Array<{ + start: number, + end: number + }>, + fileModelId: string, + fileId: string, + fileName: string, + fileSize: string, + busId: number, + uploadTime: number, + modifyTime: number, + deadTime: number, + downloadTimes: number, + localPath: string + }> + }): void + + onSendMsgError(j2: unknown, contact: unknown, i2: unknown, str: unknown): void + + onSysMsgNotification(i2: unknown, j2: unknown, j3: unknown, arrayList: unknown): void + + onTempChatInfoUpdate(tempChatInfo: TempOnRecvParams): void + + onUnreadCntAfterFirstView(hashMap: unknown): void + + onUnreadCntUpdate(hashMap: unknown): void + + onUserChannelTabStatusChanged(z: unknown): void + + onUserOnlineStatusChanged(z: unknown): void + + onUserTabStatusChanged(arrayList: unknown): void + + onlineStatusBigIconDownloadPush(i2: unknown, j2: unknown, str: unknown): void + + onlineStatusSmallIconDownloadPush(i2: unknown, j2: unknown, str: unknown): void + + // 第一次发现于Linux + onUserSecQualityChanged(...args: unknown[]): void + + onMsgWithRichLinkInfoUpdate(...args: unknown[]): void + + onRedTouchChanged(...args: unknown[]): void + + // 第一次发现于Win 9.9.9 23159 + onBroadcastHelperProgerssUpdate(...args: unknown[]): void + +} + +export interface NodeIKernelMsgListener extends IKernelMsgListener { + // eslint-disable-next-line @typescript-eslint/no-misused-new + new(listener: IKernelMsgListener): NodeIKernelMsgListener +} + + +export class MsgListener implements IKernelMsgListener { + onAddSendMsg(msgRecord: RawMessage) { + + } + + onBroadcastHelperDownloadComplete(broadcastHelperTransNotifyInfo: unknown) { + + } + + onBroadcastHelperProgressUpdate(broadcastHelperTransNotifyInfo: unknown) { + + } + + onChannelFreqLimitInfoUpdate(contact: unknown, z: unknown, freqLimitInfo: unknown) { + + } + + onContactUnreadCntUpdate(hashMap: unknown) { + + } + + onCustomWithdrawConfigUpdate(customWithdrawConfig: unknown) { + + } + + onDraftUpdate(contact: unknown, arrayList: unknown, j2: unknown) { + + } + + onEmojiDownloadComplete(emojiNotifyInfo: unknown) { + + } + + onEmojiResourceUpdate(emojiResourceInfo: unknown) { + + } + + onFeedEventUpdate(firstViewDirectMsgNotifyInfo: unknown) { + + } + + onFileMsgCome(arrayList: unknown) { + + } + + onFirstViewDirectMsgUpdate(firstViewDirectMsgNotifyInfo: unknown) { + + } + + onFirstViewGroupGuildMapping(arrayList: unknown) { + + } + + onGrabPasswordRedBag(i2: unknown, str: unknown, i3: unknown, recvdOrder: unknown, msgRecord: unknown) { + + } + + onGroupFileInfoAdd(groupItem: unknown) { + + } + + onGroupFileInfoUpdate(groupFileListResult: onGroupFileInfoUpdateParamType) { + + } + + onGroupGuildUpdate(groupGuildNotifyInfo: unknown) { + + } + + + onGroupTransferInfoAdd(groupItem: unknown) { + + } + + onGroupTransferInfoUpdate(groupFileListResult: unknown) { + + } + + onGuildInteractiveUpdate(guildInteractiveNotificationItem: unknown) { + + } + + onGuildMsgAbFlagChanged(guildMsgAbFlag: unknown) { + + } + + onGuildNotificationAbstractUpdate(guildNotificationAbstractInfo: unknown) { + + } + + onHitCsRelatedEmojiResult(downloadRelateEmojiResultInfo: unknown) { + + } + + onHitEmojiKeywordResult(hitRelatedEmojiWordsResult: unknown) { + + } + + onHitRelatedEmojiResult(relatedWordEmojiInfo: unknown) { + + } + + onImportOldDbProgressUpdate(importOldDbMsgNotifyInfo: unknown) { + + } + + onInputStatusPush(inputStatusInfo: unknown) { + + } + + onKickedOffLine(kickedInfo: unknown) { + + } + + onLineDev(arrayList: unknown) { + + } + + onLogLevelChanged(j2: unknown) { + + } + + onMsgAbstractUpdate(arrayList: unknown) { + + } + + onMsgBoxChanged(arrayList: unknown) { + + } + + onMsgDelete(contact: unknown, arrayList: unknown) { + + } + + onMsgEventListUpdate(hashMap: unknown) { + + } + + onMsgInfoListAdd(arrayList: unknown) { + + } + + onMsgInfoListUpdate(msgList: RawMessage[]) { + + } + + onMsgQRCodeStatusChanged(i2: unknown) { + + } + + onMsgRecall(i2: unknown, str: unknown, j2: unknown) { + + } + + onMsgSecurityNotify(msgRecord: unknown) { + + } + + onMsgSettingUpdate(msgSetting: unknown) { + + } + + onNtFirstViewMsgSyncEnd() { + + } + + onNtMsgSyncEnd() { + + } + + onNtMsgSyncStart() { + + } + + onReadFeedEventUpdate(firstViewDirectMsgNotifyInfo: unknown) { + + } + + onRecvGroupGuildFlag(i2: unknown) { + + } + + onRecvMsg(arrayList: RawMessage[]) { + + } + + onRecvMsgSvrRspTransInfo(j2: unknown, contact: unknown, i2: unknown, i3: unknown, str: unknown, bArr: unknown) { + + } + + onRecvOnlineFileMsg(arrayList: unknown) { + + } + + onRecvS2CMsg(arrayList: unknown) { + + } + + onRecvSysMsg(arrayList: unknown) { + + } + + onRecvUDCFlag(i2: unknown) { + + } + + onRichMediaDownloadComplete(fileTransNotifyInfo: OnRichMediaDownloadCompleteParams) { + } + + onRichMediaProgerssUpdate(fileTransNotifyInfo: unknown) { + + } + + onRichMediaUploadComplete(fileTransNotifyInfo: unknown) { + + } + + onSearchGroupFileInfoUpdate(searchGroupFileResult: unknown) { + + } + + onSendMsgError(j2: unknown, contact: unknown, i2: unknown, str: unknown) { + + } + + onSysMsgNotification(i2: unknown, j2: unknown, j3: unknown, arrayList: unknown) { + + } + + onTempChatInfoUpdate(tempChatInfo: TempOnRecvParams) { + + } + + onUnreadCntAfterFirstView(hashMap: unknown) { + + } + + onUnreadCntUpdate(hashMap: unknown) { + + } + + onUserChannelTabStatusChanged(z: unknown) { + + } + + onUserOnlineStatusChanged(z: unknown) { + + } + + onUserTabStatusChanged(arrayList: unknown) { + + } + + onlineStatusBigIconDownloadPush(i2: unknown, j2: unknown, str: unknown) { + + } + + onlineStatusSmallIconDownloadPush(i2: unknown, j2: unknown, str: unknown) { + + } + + // 第一次发现于Linux + onUserSecQualityChanged(...args: unknown[]) { + + } + + onMsgWithRichLinkInfoUpdate(...args: unknown[]) { + + } + + onRedTouchChanged(...args: unknown[]) { + + } + // 第一次发现于Win 9.9.9-23159 + onBroadcastHelperProgerssUpdate(...args: unknown[]) { + + } +} \ No newline at end of file diff --git a/src/ntqqapi/listeners/index.ts b/src/ntqqapi/listeners/index.ts index 66fb556..e0589aa 100644 --- a/src/ntqqapi/listeners/index.ts +++ b/src/ntqqapi/listeners/index.ts @@ -1,2 +1,3 @@ export * from './NodeIKernelProfileListener' -export * from './NodeIKernelGroupListener' \ No newline at end of file +export * from './NodeIKernelGroupListener' +export * from './NodeIKernelMsgListener' \ No newline at end of file diff --git a/src/ntqqapi/services/NodeIKernelMSFService.ts b/src/ntqqapi/services/NodeIKernelMSFService.ts new file mode 100644 index 0000000..f0bff51 --- /dev/null +++ b/src/ntqqapi/services/NodeIKernelMSFService.ts @@ -0,0 +1,3 @@ +export interface NodeIKernelMSFService { + getServerTime(): string +} \ No newline at end of file diff --git a/src/ntqqapi/services/NodeIKernelMsgService.ts b/src/ntqqapi/services/NodeIKernelMsgService.ts new file mode 100644 index 0000000..f9a85a9 --- /dev/null +++ b/src/ntqqapi/services/NodeIKernelMsgService.ts @@ -0,0 +1,744 @@ +import { ElementType, MessageElement, Peer, RawMessage, SendMessageElement } from '@/ntqqapi/types' +import { NodeIKernelMsgListener } from '@/ntqqapi/listeners/NodeIKernelMsgListener' +import { GeneralCallResult } from './common' + +export interface QueryMsgsParams { + chatInfo: Peer, + filterMsgType: [], + filterSendersUid: string[], + filterMsgFromTime: string, + filterMsgToTime: string, + pageLimit: number, + isReverseOrder: boolean, + isIncludeCurrent: boolean +} + +export interface TmpChatInfoApi { + errMsg: string + result: number + tmpChatInfo?: TmpChatInfo +} + +export interface TmpChatInfo { + chatType: number + fromNick: string + groupCode: string + peerUid: string + sessionType: number + sig: string +} + +export interface NodeIKernelMsgService { + + generateMsgUniqueId(chatType: number, time: string): string + + addKernelMsgListener(nodeIKernelMsgListener: NodeIKernelMsgListener): number + + sendMsg(msgId: string, peer: Peer, msgElements: SendMessageElement[], map: Map): Promise + + recallMsg(peer: Peer, msgIds: string[]): Promise + + addKernelMsgImportToolListener(arg: Object): unknown + + removeKernelMsgListener(args: unknown): unknown + + addKernelTempChatSigListener(...args: unknown[]): unknown + + removeKernelTempChatSigListener(...args: unknown[]): unknown + + setAutoReplyTextList(AutoReplyText: Array, i2: number): unknown + + getAutoReplyTextList(...args: unknown[]): unknown + + getOnLineDev(): void + + kickOffLine(DevInfo: Object): unknown + + setStatus(args: { status: number, extStatus: number, batteryStatus: number }): Promise + + fetchStatusMgrInfo(): unknown + + fetchStatusUnitedConfigInfo(): unknown + + getOnlineStatusSmallIconBasePath(): unknown + + getOnlineStatusSmallIconFileNameByUrl(Url: string): unknown + + downloadOnlineStatusSmallIconByUrl(arg0: number, arg1: string): unknown + + getOnlineStatusBigIconBasePath(): unknown + + downloadOnlineStatusBigIconByUrl(arg0: number, arg1: string): unknown + + getOnlineStatusCommonPath(arg: string): unknown + + getOnlineStatusCommonFileNameByUrl(Url: string): unknown + + downloadOnlineStatusCommonByUrl(arg0: string, arg1: string): unknown + + // this.tokenType = i2 + // this.apnsToken = bArr + // this.voipToken = bArr2 + // this.profileId = str + + setToken(arg: Object): unknown + + switchForeGround(): unknown + + switchBackGround(arg: Object): unknown + + //hex + setTokenForMqq(token: string): unknown + + switchForeGroundForMqq(...args: unknown[]): unknown + + switchBackGroundForMqq(...args: unknown[]): unknown + + getMsgSetting(...args: unknown[]): unknown + + setMsgSetting(...args: unknown[]): unknown + + addSendMsg(...args: unknown[]): unknown + + cancelSendMsg(...args: unknown[]): unknown + + switchToOfflineSendMsg(peer: Peer, MsgId: string): unknown + + reqToOfflineSendMsg(...args: unknown[]): unknown + + refuseReceiveOnlineFileMsg(peer: Peer, MsgId: string): unknown + + resendMsg(...args: unknown[]): unknown + + recallMsg(...args: unknown[]): unknown + + reeditRecallMsg(...args: unknown[]): unknown + //调用请检查除开commentElements其余参数不能为null + forwardMsg(msgIds: string[], srcContact: Peer, dstContacts: Peer[], commentElements: MessageElement[]): Promise + + forwardMsgWithComment(...args: unknown[]): unknown + + forwardSubMsgWithComment(...args: unknown[]): unknown + + forwardRichMsgInVist(...args: unknown[]): unknown + + forwardFile(...args: unknown[]): unknown + //Array, Peer from, Peer to + multiForwardMsg(...args: unknown[]): unknown + + multiForwardMsgWithComment(...args: unknown[]): unknown + + deleteRecallMsg(...args: unknown[]): unknown + + deleteRecallMsgForLocal(...args: unknown[]): unknown + + addLocalGrayTipMsg(...args: unknown[]): unknown + + addLocalJsonGrayTipMsg(...args: unknown[]): unknown + + addLocalJsonGrayTipMsgExt(...args: unknown[]): unknown + + IsLocalJsonTipValid(...args: unknown[]): unknown + + addLocalAVRecordMsg(...args: unknown[]): unknown + + addLocalTofuRecordMsg(...args: unknown[]): unknown + + addLocalRecordMsg(Peer: Peer, msgId: string, ele: MessageElement, attr: Array | number, front: boolean): Promise + + deleteMsg(Peer: Peer, msgIds: Array): Promise + + updateElementExtBufForUI(...args: unknown[]): unknown + + updateMsgRecordExtPbBufForUI(...args: unknown[]): unknown + + startMsgSync(...args: unknown[]): unknown + + startGuildMsgSync(...args: unknown[]): unknown + + isGuildChannelSync(...args: unknown[]): unknown + + getMsgUniqueId(UniqueId: string): string + + isMsgMatched(...args: unknown[]): unknown + + getOnlineFileMsgs(...args: unknown[]): unknown + + getAllOnlineFileMsgs(...args: unknown[]): unknown + + getLatestDbMsgs(peer: Peer, cnt: number): Promise + + getLastMessageList(peer: Peer[]): Promise + + getAioFirstViewLatestMsgs(peer: Peer, num: number): unknown + + //deprecated 从9.9.15-26702版本开始,该接口已经废弃,请使用getMsgsEx + getMsgs(peer: Peer, msgId: string, count: unknown, queryOrder: boolean): Promise + + getMsgsIncludeSelf(peer: Peer, msgId: string, count: number, queryOrder: boolean): Promise + + // this.$peer = contact + // this.$msgTime = j2 + // this.$clientSeq = j3 + // this.$cnt = i2 + + getMsgsWithMsgTimeAndClientSeqForC2C(...args: unknown[]): Promise + + getMsgsWithStatus(params: { + peer: Peer + msgId: string + msgTime: unknown + cnt: unknown + queryOrder: boolean + isIncludeSelf: boolean + appid: unknown + }): Promise + + getMsgsBySeqRange(peer: Peer, startSeq: string, endSeq: string): Promise + + getMsgsBySeqAndCount(peer: Peer, seq: string, count: number, desc: boolean, unknownArg: boolean): Promise + + getMsgsByMsgId(peer: Peer, ids: string[]): Promise + + getRecallMsgsByMsgId(peer: Peer, MsgId: string[]): Promise + + getMsgsBySeqList(peer: Peer, seqList: string[]): Promise + + getSingleMsg(Peer: Peer, msgSeq: string): Promise + + getSourceOfReplyMsg(peer: Peer, MsgId: string, SourceSeq: string): unknown + + getSourceOfReplyMsgV2(peer: Peer, RootMsgId: string, ReplyMsgId: string): unknown + + getMsgByClientSeqAndTime(peer: Peer, clientSeq: string, time: string): unknown + + getSourceOfReplyMsgByClientSeqAndTime(peer: Peer, clientSeq: string, time: string): unknown + //cnt clientSeq?并不是吧 + getMsgsByTypeFilter(peer: Peer, msgId: string, cnt: unknown, queryOrder: boolean, typeFilter: { type: number, subtype: Array }): unknown + + getMsgsByTypeFilters(peer: Peer, msgId: string, cnt: unknown, queryOrder: boolean, typeFilters: Array<{ type: number, subtype: Array }>): unknown + + getMsgWithAbstractByFilterParam(...args: unknown[]): unknown + + queryMsgsWithFilter(...args: unknown[]): unknown + + /** + * @deprecated 该函数已被标记为废弃,请使用新的替代方法。 + * 使用过滤条件查询消息列表的版本2接口。 + * + * 该函数通过一系列过滤条件来查询特定聊天中的消息列表。这些条件包括消息类型、发送者、时间范围等。 + * 函数返回一个Promise,解析为查询结果的未知类型对象。 + * + * @param MsgId 消息ID,用于特定消息的查询。 + * @param MsgTime 消息时间,用于指定消息的时间范围。 + * @param param 查询参数对象,包含详细的过滤条件和分页信息。 + * @param param.chatInfo 聊天信息,包括聊天类型和对方用户ID。 + * @param param.filterMsgType 需要过滤的消息类型数组,留空表示不过滤。 + * @param param.filterSendersUid 需要过滤的发送者用户ID数组。 + * @param param.filterMsgFromTime 查询消息的起始时间。 + * @param param.filterMsgToTime 查询消息的结束时间。 + * @param param.pageLimit 每页的消息数量限制。 + * @param param.isReverseOrder 是否按时间顺序倒序返回消息。 + * @param param.isIncludeCurrent 是否包含当前页码。 + * @returns 返回一个Promise,解析为查询结果的未知类型对象。 + */ + queryMsgsWithFilterVer2(MsgId: string, MsgTime: string, param: QueryMsgsParams): Promise + + // this.chatType = i2 + // this.peerUid = str + + // this.chatInfo = new ChatInfo() + // this.filterMsgType = new ArrayList<>() + // this.filterSendersUid = new ArrayList<>() + // this.chatInfo = chatInfo + // this.filterMsgType = arrayList + // this.filterSendersUid = arrayList2 + // this.filterMsgFromTime = j2 + // this.filterMsgToTime = j3 + // this.pageLimit = i2 + // this.isReverseOrder = z + // this.isIncludeCurrent = z2 + //queryMsgsWithFilterEx(0L, 0L, 0L, new QueryMsgsParams(new ChatInfo(2, str), new ArrayList(), new ArrayList(), 0L, 0L, 250, false, true)) + queryMsgsWithFilterEx(msgId: string, msgTime: string, megSeq: string, param: QueryMsgsParams): Promise + //queryMsgsWithFilterEx(this.$msgId, this.$msgTime, this.$msgSeq, this.$param) + queryFileMsgsDesktop(...args: unknown[]): unknown + + setMsgRichInfoFlag(...args: unknown[]): unknown + + queryPicOrVideoMsgs(msgId: string, msgTime: string, megSeq: string, param: QueryMsgsParams): Promise + + queryPicOrVideoMsgsDesktop(...args: unknown[]): unknown + + queryEmoticonMsgs(msgId: string, msgTime: string, msgSeq: string, Params: QueryMsgsParams): Promise + + queryTroopEmoticonMsgs(msgId: string, msgTime: string, msgSeq: string, Params: QueryMsgsParams): Promise + + queryMsgsAndAbstractsWithFilter(msgId: string, msgTime: string, megSeq: string, param: QueryMsgsParams): unknown + + setFocusOnGuild(...args: unknown[]): unknown + + setFocusSession(...args: unknown[]): unknown + + enableFilterUnreadInfoNotify(...args: unknown[]): unknown + + enableFilterMsgAbstractNotify(...args: unknown[]): unknown + + onScenesChangeForSilenceMode(...args: unknown[]): unknown + + getContactUnreadCnt(...args: unknown[]): unknown + + getUnreadCntInfo(...args: unknown[]): unknown + + getGuildUnreadCntInfo(...args: unknown[]): unknown + + getGuildUnreadCntTabInfo(...args: unknown[]): unknown + + getAllGuildUnreadCntInfo(...args: unknown[]): unknown + + getAllJoinGuildCnt(...args: unknown[]): unknown + + getAllDirectSessionUnreadCntInfo(...args: unknown[]): unknown + + getCategoryUnreadCntInfo(...args: unknown[]): unknown + + getGuildFeedsUnreadCntInfo(...args: unknown[]): unknown + + setUnVisibleChannelCntInfo(...args: unknown[]): unknown + + setUnVisibleChannelTypeCntInfo(...args: unknown[]): unknown + + setVisibleGuildCntInfo(...args: unknown[]): unknown + + setMsgRead(peer: Peer): Promise + + setAllC2CAndGroupMsgRead(): Promise + + setGuildMsgRead(...args: unknown[]): unknown + + setAllGuildMsgRead(...args: unknown[]): unknown + + setMsgReadAndReport(...args: unknown[]): unknown + + setSpecificMsgReadAndReport(...args: unknown[]): unknown + + setLocalMsgRead(...args: unknown[]): unknown + + setGroupGuildMsgRead(...args: unknown[]): unknown + + getGuildGroupTransData(...args: unknown[]): unknown + + setGroupGuildBubbleRead(...args: unknown[]): unknown + + getGuildGroupBubble(...args: unknown[]): unknown + + fetchGroupGuildUnread(...args: unknown[]): unknown + + setGroupGuildFlag(...args: unknown[]): unknown + + setGuildUDCFlag(...args: unknown[]): unknown + + setGuildTabUserFlag(...args: unknown[]): unknown + + setBuildMode(flag: number/*0 1 3*/): unknown + + setConfigurationServiceData(...args: unknown[]): unknown + + setMarkUnreadFlag(...args: unknown[]): unknown + + getChannelEventFlow(...args: unknown[]): unknown + + getMsgEventFlow(...args: unknown[]): unknown + + getRichMediaFilePathForMobileQQSend(...args: unknown[]): unknown + + getRichMediaFilePathForGuild(arg: { + md5HexStr: string, + fileName: string, + elementType: ElementType, + elementSubType: number, + thumbSize: 0, + needCreate: true, + downloadType: 1, + file_uuid: '' + }): string + + assembleMobileQQRichMediaFilePath(...args: unknown[]): unknown + + getFileThumbSavePathForSend(...args: unknown[]): unknown + + getFileThumbSavePath(...args: unknown[]): unknown + //猜测居多 + translatePtt2Text(MsgId: string, Peer: {}, MsgElement: {}): unknown + + setPttPlayedState(...args: unknown[]): unknown + // NodeIQQNTWrapperSession fetchFavEmojiList [ + // "", + // 48, + // true, + // true + // ] + fetchFavEmojiList(str: string, num: number, uk1: boolean, uk2: boolean): Promise + }> + + addFavEmoji(...args: unknown[]): unknown + + fetchMarketEmoticonList(...args: unknown[]): unknown + + fetchMarketEmoticonShowImage(...args: unknown[]): unknown + + fetchMarketEmoticonAioImage(...args: unknown[]): unknown + + fetchMarketEmotionJsonFile(...args: unknown[]): unknown + + getMarketEmoticonPath(...args: unknown[]): unknown + + getMarketEmoticonPathBySync(...args: unknown[]): unknown + + fetchMarketEmoticonFaceImages(...args: unknown[]): unknown + + fetchMarketEmoticonAuthDetail(...args: unknown[]): unknown + + getFavMarketEmoticonInfo(...args: unknown[]): unknown + + addRecentUsedFace(...args: unknown[]): unknown + + getRecentUsedFaceList(...args: unknown[]): unknown + + getMarketEmoticonEncryptKeys(...args: unknown[]): unknown + + downloadEmojiPic(...args: unknown[]): unknown + + deleteFavEmoji(...args: unknown[]): unknown + + modifyFavEmojiDesc(...args: unknown[]): unknown + + queryFavEmojiByDesc(...args: unknown[]): unknown + + getHotPicInfoListSearchString(...args: unknown[]): unknown + + getHotPicSearchResult(...args: unknown[]): unknown + + getHotPicHotWords(...args: unknown[]): unknown + + getHotPicJumpInfo(...args: unknown[]): unknown + + getEmojiResourcePath(...args: unknown[]): unknown + + JoinDragonGroupEmoji(JoinDragonGroupEmojiReq: any/*joinDragonGroupEmojiReq*/): unknown + + getMsgAbstracts(...args: unknown[]): unknown + + getMsgAbstract(...args: unknown[]): unknown + + getMsgAbstractList(...args: unknown[]): unknown + + getMsgAbstractListBySeqRange(...args: unknown[]): unknown + + refreshMsgAbstracts(...args: unknown[]): unknown + + refreshMsgAbstractsByGuildIds(...args: unknown[]): unknown + + getRichMediaElement(...args: unknown[]): unknown + + cancelGetRichMediaElement(...args: unknown[]): unknown + + refuseGetRichMediaElement(...args: unknown[]): unknown + + switchToOfflineGetRichMediaElement(...args: unknown[]): unknown + + downloadRichMedia(...args: unknown[]): unknown + + getFirstUnreadMsgSeq(args: { + peerUid: string + guildId: string + }): unknown + + getFirstUnreadCommonMsg(...args: unknown[]): unknown + + getFirstUnreadAtmeMsg(...args: unknown[]): unknown + + getFirstUnreadAtallMsg(...args: unknown[]): unknown + + getNavigateInfo(...args: unknown[]): unknown + + getChannelFreqLimitInfo(...args: unknown[]): unknown + + getRecentUseEmojiList(...args: unknown[]): unknown + + getRecentEmojiList(...args: unknown[]): unknown + + setMsgEmojiLikes(...args: unknown[]): unknown + + getMsgEmojiLikesList(peer: Peer, msgSeq: string, emojiId: string, emojiType: string, cookie: string, bForward: boolean, number: number): Promise<{ + result: number, + errMsg: string, + emojiLikesList: + Array<{ + tinyId: string, + nickName: string, + headUrl: string + }>, + cookie: string, + isLastPage: boolean, + isFirstPage: boolean + }> + + setMsgEmojiLikesForRole(...args: unknown[]): unknown + + clickInlineKeyboardButton(...args: unknown[]): unknown + + setCurOnScreenMsg(...args: unknown[]): unknown + + setCurOnScreenMsgForMsgEvent(...args: unknown[]): unknown + + getMiscData(key: string): unknown + + setMiscData(key: string, value: string): unknown + + getBookmarkData(...args: unknown[]): unknown + + setBookmarkData(...args: unknown[]): unknown + + sendShowInputStatusReq(ChatType: number, EventType: number, toUid: string): Promise + + queryCalendar(...args: unknown[]): unknown + + queryFirstMsgSeq(peer: Peer, ...args: unknown[]): unknown + + queryRoamCalendar(...args: unknown[]): unknown + + queryFirstRoamMsg(...args: unknown[]): unknown + + fetchLongMsg(peer: Peer, msgId: string): unknown + + fetchLongMsgWithCb(...args: unknown[]): unknown + + setIsStopKernelFetchLongMsg(...args: unknown[]): unknown + + insertGameResultAsMsgToDb(...args: unknown[]): unknown + + getMultiMsg(...args: unknown[]): Promise + + setDraft(...args: unknown[]): unknown + + getDraft(...args: unknown[]): unknown + + deleteDraft(...args: unknown[]): unknown + + getRecentHiddenSesionList(...args: unknown[]): unknown + + setRecentHiddenSession(...args: unknown[]): unknown + + delRecentHiddenSession(...args: unknown[]): unknown + + getCurHiddenSession(...args: unknown[]): unknown + + setCurHiddenSession(...args: unknown[]): unknown + + setReplyDraft(...args: unknown[]): unknown + + getReplyDraft(...args: unknown[]): unknown + + deleteReplyDraft(...args: unknown[]): unknown + + getFirstUnreadAtMsg(peer: Peer): unknown + + clearMsgRecords(...args: unknown[]): unknown//设置已读后调用我觉得比较好 清理记录 现在别了 + + IsExistOldDb(...args: unknown[]): unknown + + canImportOldDbMsg(...args: unknown[]): unknown + + setPowerStatus(z: boolean): unknown + + canProcessDataMigration(...args: unknown[]): unknown + + importOldDbMsg(...args: unknown[]): unknown + + stopImportOldDbMsgAndroid(...args: unknown[]): unknown + + isMqqDataImportFinished(...args: unknown[]): unknown + + getMqqDataImportTableNames(...args: unknown[]): unknown + + getCurChatImportStatusByUin(...args: unknown[]): unknown + + getDataImportUserLevel(): unknown + + getMsgQRCode(...args: unknown[]): unknown + + getGuestMsgAbstracts(...args: unknown[]): unknown + + getGuestMsgByRange(...args: unknown[]): unknown + + getGuestMsgAbstractByRange(...args: unknown[]): unknown + + registerSysMsgNotification(...args: unknown[]): unknown + + unregisterSysMsgNotification(...args: unknown[]): unknown + + enterOrExitAio(...args: unknown[]): unknown + + // this.peerUid = "" + // this.peerNickname = "" + // this.fromGroupCode = "" + // this.sig = new byte[0] + // this.selfUid = "" + // this.selfPhone = "" + // this.chatType = i2 + // this.peerUid = str + // this.peerNickname = str2 + // this.fromGroupCode = str3 + // this.sig = bArr + // this.selfUid = str4 + // this.selfPhone = str5 + // this.gameSession = tempChatGameSession + prepareTempChat(args: unknown): unknown//主动临时消息 不做 + + sendSsoCmdReqByContend(cmd: string, param: string): Promise + + //chattype,uid->Promise + getTempChatInfo(ChatType: number, Uid: string): Promise + + setContactLocalTop(...args: unknown[]): unknown + + switchAnonymousChat(...args: unknown[]): unknown + + renameAnonyChatNick(...args: unknown[]): unknown + + getAnonymousInfo(...args: unknown[]): unknown + + updateAnonymousInfo(...args: unknown[]): unknown + + sendSummonMsg(peer: Peer, MsgElement: unknown, MsgAttributeInfo: unknown): Promise//频道的东西 + + outputGuildUnreadInfo(...args: unknown[]): unknown + + checkMsgWithUrl(...args: unknown[]): unknown + + checkTabListStatus(...args: unknown[]): unknown + + getABatchOfContactMsgBoxInfo(...args: unknown[]): unknown + + insertMsgToMsgBox(peer: Peer, msgId: string, arg: 2006): unknown + + isHitEmojiKeyword(...args: unknown[]): unknown + + getKeyWordRelatedEmoji(...args: unknown[]): unknown + + recordEmoji(...args: unknown[]): unknown + + fetchGetHitEmotionsByWord(args: Object): Promise//表情推荐? + + deleteAllRoamMsgs(...args: unknown[]): unknown//漫游消息? + + packRedBag(...args: unknown[]): unknown + + grabRedBag(...args: unknown[]): unknown + + pullDetail(...args: unknown[]): unknown + + selectPasswordRedBag(...args: unknown[]): unknown + + pullRedBagPasswordList(...args: unknown[]): unknown + + requestTianshuAdv(...args: unknown[]): unknown + + tianshuReport(...args: unknown[]): unknown + + tianshuMultiReport(...args: unknown[]): unknown + + GetMsgSubType(a0: number, a1: number): unknown + + setIKernelPublicAccountAdapter(...args: unknown[]): unknown + //tempChatGameSession有关 + createUidFromTinyId(fromTinyId: string, toTinyId: string): unknown + + dataMigrationGetDataAvaiableContactList(...args: unknown[]): unknown + + dataMigrationGetMsgList(...args: unknown[]): unknown + + dataMigrationStopOperation(...args: unknown[]): unknown + + //新的希望 + dataMigrationImportMsgPbRecord(DataMigrationMsgInfo: Array<{ + extensionData: string//"Hex" + extraData: string //"" + chatType: number + chatUin: string + msgType: number + msgTime: string + msgSeq: string + msgRandom: string + }>, DataMigrationResourceInfo: { + extraData: string + filePath: string + fileSize: string + msgRandom: string + msgSeq: string + msgSubType: number + msgType: number + }): unknown + + dataMigrationGetResourceLocalDestinyPath(...args: unknown[]): unknown + + dataMigrationSetIOSPathPrefix(...args: unknown[]): unknown + + getServiceAssistantSwitch(...args: unknown[]): unknown + + setServiceAssistantSwitch(...args: unknown[]): unknown + + setSubscribeFolderUsingSmallRedPoint(...args: unknown[]): unknown + + clearGuildNoticeRedPoint(...args: unknown[]): unknown + + clearFeedNoticeRedPoint(...args: unknown[]): unknown + + clearFeedSquareRead(...args: unknown[]): unknown + + IsC2CStyleChatType(...args: unknown[]): unknown + + IsTempChatType(uin: number): unknown//猜的 + + getGuildInteractiveNotification(...args: unknown[]): unknown + + getGuildNotificationAbstract(...args: unknown[]): unknown + + setFocusOnBase(...args: unknown[]): unknown + + queryArkInfo(...args: unknown[]): unknown + + queryUserSecQuality(...args: unknown[]): unknown + + getGuildMsgAbFlag(...args: unknown[]): unknown + + getGroupMsgStorageTime(): unknown//这是嘛啊 + +} \ No newline at end of file diff --git a/src/ntqqapi/services/NodeIKernelUixConvertService.ts b/src/ntqqapi/services/NodeIKernelUixConvertService.ts new file mode 100644 index 0000000..e98749d --- /dev/null +++ b/src/ntqqapi/services/NodeIKernelUixConvertService.ts @@ -0,0 +1,5 @@ +export interface NodeIKernelUixConvertService { + getUin(uid: string[]): Promise<{ uinInfo: Map }> + + getUid(uin: string[]): Promise<{ uidInfo: Map }> +} \ No newline at end of file diff --git a/src/ntqqapi/services/index.ts b/src/ntqqapi/services/index.ts index 052026d..88079a8 100644 --- a/src/ntqqapi/services/index.ts +++ b/src/ntqqapi/services/index.ts @@ -1,4 +1,7 @@ export * from './NodeIKernelBuddyService' export * from './NodeIKernelProfileService' export * from './NodeIKernelGroupService' -export * from './NodeIKernelProfileLikeService' \ No newline at end of file +export * from './NodeIKernelProfileLikeService' +export * from './NodeIKernelMsgService' +export * from './NodeIKernelMSFService' +export * from './NodeIKernelUixConvertService' \ No newline at end of file diff --git a/src/ntqqapi/types/msg.ts b/src/ntqqapi/types/msg.ts index c4c3c2f..c7c7a02 100644 --- a/src/ntqqapi/types/msg.ts +++ b/src/ntqqapi/types/msg.ts @@ -15,13 +15,7 @@ export enum ElementType { export interface SendTextElement { elementType: ElementType.TEXT elementId: '' - textElement: { - content: string - atType: number - atUid: string - atTinyId: string - atNtUid: string - } + textElement: TextElement } export interface SendPttElement { @@ -77,12 +71,7 @@ export interface SendPicElement { export interface SendReplyElement { elementType: ElementType.REPLY elementId: '' - replyElement: { - replayMsgSeq: string - replayMsgId: string - senderUin: string - senderUinStr: string - } + replyElement: ReplyElement } export interface SendFaceElement { @@ -96,6 +85,21 @@ export interface SendMarketFaceElement { marketFaceElement: MarketFaceElement } +export interface TextElement { + content: string + atType: number + atUid: string + atTinyId: string + atNtUid: string +} + +export interface ReplyElement { + replayMsgSeq: string + replayMsgId: string + senderUin: string + senderUinStr: string +} + export interface FileElement { fileMd5?: '' fileName: string @@ -377,6 +381,7 @@ export interface RawMessage { msgShortId?: number // 自己维护的消息id msgTime: string // 时间戳,秒 msgSeq: string + msgRandom: string senderUid: string senderUin?: string // 发送者QQ号 peerUid: string // 群号 或者 QQ uid @@ -419,4 +424,38 @@ export interface Peer { chatType: ChatType peerUid: string // 如果是群聊uid为群号,私聊uid就是加密的字符串 guildId?: string +} + +export interface MessageElement { + elementType: ElementType + elementId: string + extBufForUI: string //"0x" + textElement?: TextElement + faceElement?: FaceElement + marketFaceElement?: MarkdownElement + replyElement?: ReplyElement + picElement?: PicElement + pttElement?: PttElement + videoElement?: VideoElement + grayTipElement?: GrayTipElement + arkElement?: ArkElement + fileElement?: FileElement + liveGiftElement?: null + markdownElement?: MarkdownElement + structLongMsgElement?: any + multiForwardMsgElement?: MultiForwardMsgElement + giphyElement?: any + walletElement?: null + inlineKeyboardElement?: InlineKeyboardElement + textGiftElement?: null //???? + calendarElement?: any + yoloGameResultElement?: any + avRecordElement?: any + structMsgElement?: null + faceBubbleElement?: any + shareLocationElement?: any + tofuRecordElement?: any + taskTopMsgElement?: any + recommendedMsgElement?: any + actionBarElement?: any } \ No newline at end of file diff --git a/src/ntqqapi/wrapper.ts b/src/ntqqapi/wrapper.ts index 91567bf..40bfc8f 100644 --- a/src/ntqqapi/wrapper.ts +++ b/src/ntqqapi/wrapper.ts @@ -2,7 +2,10 @@ import { NodeIKernelBuddyService, NodeIKernelGroupService, NodeIKernelProfileService, - NodeIKernelProfileLikeService + NodeIKernelProfileLikeService, + NodeIKernelMSFService, + NodeIKernelMsgService, + NodeIKernelUixConvertService } from './services' import os from 'node:os' const Process = require('node:process') @@ -13,6 +16,9 @@ export interface NodeIQQNTWrapperSession { getGroupService(): NodeIKernelGroupService getProfileService(): NodeIKernelProfileService getProfileLikeService(): NodeIKernelProfileLikeService + getMsgService(): NodeIKernelMsgService + getMSFService(): NodeIKernelMSFService + getUixConvertService(): NodeIKernelUixConvertService } export interface WrapperApi { diff --git a/src/onebot11/action/msg/SendMsg.ts b/src/onebot11/action/msg/SendMsg.ts index 7df510c..45ce161 100644 --- a/src/onebot11/action/msg/SendMsg.ts +++ b/src/onebot11/action/msg/SendMsg.ts @@ -39,6 +39,12 @@ export interface ReturnDataType { message_id: number } +export enum ContextMode { + Normal = 0, + Private = 1, + Group = 2 +} + export function convertMessage2List(message: OB11MessageMixType, autoEscape = false) { if (typeof message === 'string') { if (autoEscape === true) { @@ -63,7 +69,7 @@ export function convertMessage2List(message: OB11MessageMixType, autoEscape = fa export async function createSendElements( messageData: OB11MessageData[], - target: Group | Friend | undefined, + peer: Peer, ignoreTypes: OB11MessageDataType[] = [], ) { let sendElements: SendMessageElement[] = [] @@ -81,7 +87,7 @@ export async function createSendElements( } break case OB11MessageDataType.at: { - if (!target) { + if (!peer) { continue } let atQQ = sendMsg.data?.qq @@ -89,7 +95,7 @@ export async function createSendElements( atQQ = atQQ.toString() if (atQQ === 'all') { // todo:查询剩余的at全体次数 - const groupCode = (target as Group)?.groupCode + const groupCode = peer.peerUid let remainAtAllCount = 1 let isAdmin: boolean = true if (groupCode) { @@ -97,7 +103,7 @@ export async function createSendElements( remainAtAllCount = (await NTQQGroupApi.getGroupAtAllRemainCount(groupCode)).atInfo .RemainAtAllCountForUin log(`群${groupCode}剩余at全体次数`, remainAtAllCount) - const self = await getGroupMember((target as Group)?.groupCode, selfInfo.uin) + const self = await getGroupMember(groupCode, selfInfo.uin) isAdmin = self?.role === GroupMemberRole.admin || self?.role === GroupMemberRole.owner } catch (e) { } @@ -108,7 +114,7 @@ export async function createSendElements( } else { // const atMember = group?.members.find(m => m.uin == atQQ) - const atMember = await getGroupMember((target as Group)?.groupCode, atQQ) + const atMember = await getGroupMember(peer.peerUid, atQQ) if (atMember) { sendElements.push( SendMsgElementConstructor.at(atQQ, atMember.uid, AtType.atUser, atMember.cardName || atMember.nick), @@ -282,6 +288,31 @@ export async function sendMsg( return returnMsg } +async function createContext(payload: OB11PostSendMsg, contextMode: ContextMode): Promise { + // This function determines the type of message by the existence of user_id / group_id, + // not message_type. + // This redundant design of Ob11 here should be blamed. + + if ((contextMode === ContextMode.Group || contextMode === ContextMode.Normal) && payload.group_id) { + const group = (await getGroup(payload.group_id))! // checked before + return { + chatType: ChatType.group, + peerUid: group.groupCode + } + } + if ((contextMode === ContextMode.Private || contextMode === ContextMode.Normal) && payload.user_id) { + const Uid = await NTQQUserApi.getUidByUin(payload.user_id.toString()) + const isBuddy = await NTQQFriendApi.isBuddy(Uid!) + //console.log("[调试代码] UIN:", payload.user_id, " UID:", Uid, " IsBuddy:", isBuddy) + return { + chatType: isBuddy ? ChatType.friend : ChatType.temp, + peerUid: Uid!, + guildId: payload.group_id || ''//临时主动发起时需要传入群号 + } + } + throw '请指定 group_id 或 user_id' +} + export class SendMsg extends BaseAction { actionName = ActionName.SendMsg @@ -321,56 +352,14 @@ export class SendMsg extends BaseAction { } protected async _handle(payload: OB11PostSendMsg) { - const peer: Peer = { - chatType: ChatType.friend, - peerUid: '', - } - let isTempMsg = false - let group: Group | undefined = undefined - let friend: Friend | undefined = undefined - const genGroupPeer = async () => { - group = await getGroup(payload.group_id?.toString()!) - peer.chatType = ChatType.group - // peer.name = group.name - peer.peerUid = group?.groupCode! - } - - const genFriendPeer = () => { - friend = friends.find((f) => f.uin == payload.user_id.toString()) - if (friend) { - // peer.name = friend.nickName - peer.peerUid = friend.uid - } - else { - peer.chatType = ChatType.temp - const tempUserUid = getUidByUin(payload.user_id.toString()) - if (!tempUserUid) { - throw `找不到私聊对象${payload.user_id}` - } - // peer.name = tempUser.nickName - isTempMsg = true - peer.peerUid = tempUserUid - } - } - if (payload?.group_id && payload.message_type === 'group') { - await genGroupPeer() - } - else if (payload?.user_id) { - genFriendPeer() - } - else if (payload.group_id) { - await genGroupPeer() - } - else { - throw '发送消息参数错误, 请指定group_id或user_id' - } + const peer = await createContext(payload, ContextMode.Normal) const messages = convertMessage2List( payload.message, payload.auto_escape === true || payload.auto_escape === 'true', ) if (this.getSpecialMsgNum(messages, OB11MessageDataType.node)) { try { - const returnMsg = await this.handleForwardNode(peer, messages as OB11MessageNode[], group) + const returnMsg = await this.handleForwardNode(peer, messages as OB11MessageNode[]) return { message_id: returnMsg?.msgShortId! } } catch (e: any) { throw '发送转发消息失败 ' + e.toString() @@ -427,7 +416,7 @@ export class SendMsg extends BaseAction { } } // log("send msg:", peer, sendElements) - const { sendElements, deleteAfterSentFiles } = await createSendElements(messages, group || friend) + const { sendElements, deleteAfterSentFiles } = await createSendElements(messages, peer) if (sendElements.length === 1) { if (sendElements[0] === null) { return { message_id: 0 } @@ -476,7 +465,7 @@ export class SendMsg extends BaseAction { } // 返回一个合并转发的消息id - private async handleForwardNode(destPeer: Peer, messageNodes: OB11MessageNode[], group: Group | undefined) { + private async handleForwardNode(destPeer: Peer, messageNodes: OB11MessageNode[]) { const selfPeer = { chatType: ChatType.friend, peerUid: selfInfo.uid, @@ -509,7 +498,7 @@ export class SendMsg extends BaseAction { try { const { sendElements, deleteAfterSentFiles } = await createSendElements( convertMessage2List(messageNode.data.content), - group, + destPeer ) log('开始生成转发节点', sendElements) let sendElementsSplit: SendMessageElement[][] = [] @@ -597,42 +586,6 @@ export class SendMsg extends BaseAction { return null } } - - // private genMusicElement(url: string, audio: string, title: string, content: string, image: string): SendArkElement { - // const musicJson = { - // app: 'com.tencent.structmsg', - // config: { - // ctime: 1709689928, - // forward: 1, - // token: '5c1e4905f926dd3a64a4bd3841460351', - // type: 'normal', - // }, - // extra: { app_type: 1, appid: 100497308, uin: selfInfo.uin }, - // meta: { - // news: { - // action: '', - // android_pkg_name: '', - // app_type: 1, - // appid: 100497308, - // ctime: 1709689928, - // desc: content || title, - // jumpUrl: url, - // musicUrl: audio, - // preview: image, - // source_icon: 'https://p.qpic.cn/qqconnect/0/app_100497308_1626060999/100?max-age=2592000&t=0', - // source_url: '', - // tag: 'QQ音乐', - // title: title, - // uin: selfInfo.uin, - // }, - // }, - // prompt: content || title, - // ver: '0.0.0.1', - // view: 'news', - // } - - // return SendMsgElementConstructor.ark(musicJson) - // } } export default SendMsg From 5d5fd403b8259d7c85f2bb06f1d61f4d365d6013 Mon Sep 17 00:00:00 2001 From: idranme Date: Fri, 9 Aug 2024 15:44:18 +0800 Subject: [PATCH 13/14] fix: filtering `at` segments when sending private chat messages --- src/onebot11/action/msg/SendMsg.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/onebot11/action/msg/SendMsg.ts b/src/onebot11/action/msg/SendMsg.ts index 45ce161..85b27cb 100644 --- a/src/onebot11/action/msg/SendMsg.ts +++ b/src/onebot11/action/msg/SendMsg.ts @@ -112,7 +112,7 @@ export async function createSendElements( sendElements.push(SendMsgElementConstructor.at(atQQ, atQQ, AtType.atAll, '全体成员')) } } - else { + else if (peer.chatType === ChatType.group) { // const atMember = group?.members.find(m => m.uin == atQQ) const atMember = await getGroupMember(peer.peerUid, atQQ) if (atMember) { From c520034934ac451eec1e72a8b8764b887695f207 Mon Sep 17 00:00:00 2001 From: idranme Date: Fri, 9 Aug 2024 15:47:57 +0800 Subject: [PATCH 14/14] chore: v3.28.1 --- manifest.json | 2 +- src/version.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/manifest.json b/manifest.json index 72829cc..3ce5014 100644 --- a/manifest.json +++ b/manifest.json @@ -4,7 +4,7 @@ "name": "LLOneBot", "slug": "LLOneBot", "description": "实现 OneBot 11 协议,用以 QQ 机器人开发", - "version": "3.28.0", + "version": "3.28.1", "icon": "./icon.webp", "authors": [ { diff --git a/src/version.ts b/src/version.ts index a7c4e06..a706e91 100644 --- a/src/version.ts +++ b/src/version.ts @@ -1 +1 @@ -export const version = '3.28.0' +export const version = '3.28.1'