mirror of
https://github.com/LLOneBot/LLOneBot.git
synced 2024-11-22 01:56:33 +00:00
Compare commits
201 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
41c04faa05 | ||
![]() |
6ad4492f01 | ||
![]() |
d52f16bc88 | ||
![]() |
2b0179acd1 | ||
![]() |
f540f324a1 | ||
![]() |
128f40a51d | ||
![]() |
c815e0ca6b | ||
![]() |
1da720e0a7 | ||
![]() |
1472c9c949 | ||
![]() |
4678253815 | ||
![]() |
e1176e18cd | ||
![]() |
107f02f21f | ||
![]() |
51f8db3a83 | ||
![]() |
25691a4124 | ||
![]() |
40f03e6401 | ||
![]() |
9f89094978 | ||
![]() |
04f837145c | ||
![]() |
6126920830 | ||
![]() |
5c219aa003 | ||
![]() |
ce5cf82339 | ||
![]() |
6931277e33 | ||
![]() |
be1b9c21c1 | ||
![]() |
b02cd3af00 | ||
![]() |
22dcbac16f | ||
![]() |
44faedd6c0 | ||
![]() |
fb3b673e63 | ||
![]() |
4e377f86d1 | ||
![]() |
e8bd98020b | ||
![]() |
c520034934 | ||
![]() |
5d5fd403b8 | ||
![]() |
1fc02229df | ||
![]() |
6c8d3db3a4 | ||
![]() |
c5b69561af | ||
![]() |
b5bffff941 | ||
![]() |
1a2cdc8c0e | ||
![]() |
50ab62f103 | ||
![]() |
5005d83ce0 | ||
![]() |
d7e40e488c | ||
![]() |
4958e22770 | ||
![]() |
a5e3f94228 | ||
![]() |
9e57b2c17e | ||
![]() |
e1ff366e10 | ||
![]() |
6b03b01a24 | ||
![]() |
18f01b7f21 | ||
![]() |
897f691d6c | ||
![]() |
a9902d9109 | ||
![]() |
5d78fdd6a4 | ||
![]() |
72eb013371 | ||
![]() |
808777c044 | ||
![]() |
a2d1379866 | ||
![]() |
c41a8556fa | ||
![]() |
fa2df2a3cd | ||
![]() |
b28dd3a723 | ||
![]() |
6ffa41e0d6 | ||
![]() |
85df3794e8 | ||
![]() |
4bee2ba062 | ||
![]() |
4bf992c4a9 | ||
![]() |
898e856150 | ||
![]() |
c86797afc8 | ||
![]() |
799593b788 | ||
![]() |
74d9a083aa | ||
![]() |
cae525429a | ||
![]() |
cc0d1e2a9b | ||
![]() |
34ecfcfa16 | ||
![]() |
79c5041216 | ||
![]() |
8fb53260ab | ||
![]() |
07d9ac823a | ||
![]() |
b571ef434c | ||
![]() |
c1f4dcd6a6 | ||
![]() |
4c5befbe44 | ||
![]() |
296cd4d0a3 | ||
![]() |
e77a2ca34a | ||
![]() |
f3af0d18bc | ||
![]() |
406e3c7e6b | ||
![]() |
3f5ca8ebfa | ||
![]() |
6e8389e833 | ||
![]() |
71aedca4c6 | ||
![]() |
6410689549 | ||
![]() |
6d0e2269cc | ||
![]() |
2e28fc678c | ||
![]() |
8204f4407f | ||
![]() |
9f1d4c4db2 | ||
![]() |
8ba47635d3 | ||
![]() |
5fa2427c51 | ||
![]() |
aa8739d016 | ||
![]() |
79f0329da7 | ||
![]() |
7a33a36f44 | ||
![]() |
808424d08e | ||
![]() |
d0967785de | ||
![]() |
eccabb8189 | ||
![]() |
c9374ff515 | ||
![]() |
92c4889924 | ||
![]() |
f9454039a1 | ||
![]() |
bc4511e175 | ||
![]() |
f191103f99 | ||
![]() |
408463f63b | ||
![]() |
fb96c4272e | ||
![]() |
c6b302d5a8 | ||
![]() |
1dd468e2ff | ||
![]() |
2a1aa8c649 | ||
![]() |
1633734e08 | ||
![]() |
dff92e6f27 | ||
![]() |
dba5e30d5d | ||
![]() |
2d04ab2e72 | ||
![]() |
1a015ac8d3 | ||
![]() |
6390620ddd | ||
![]() |
0d19005dc3 | ||
![]() |
c6479dd2c4 | ||
![]() |
8871331b7c | ||
![]() |
e01148b86a | ||
![]() |
2f87e3818e | ||
![]() |
2c8a594c38 | ||
![]() |
1508dab7fe | ||
![]() |
958b21e47e | ||
![]() |
781c3311ae | ||
![]() |
52850d172e | ||
![]() |
52a065542e | ||
![]() |
fd10469685 | ||
![]() |
a2ee75b113 | ||
![]() |
0f7f243b98 | ||
![]() |
97d7996a50 | ||
![]() |
b658d164f9 | ||
![]() |
f150ae478b | ||
![]() |
d1f68553f1 | ||
![]() |
f47f0800de | ||
![]() |
b7ddefc950 | ||
![]() |
25b3325a44 | ||
![]() |
c281b87bab | ||
![]() |
c0946ddda2 | ||
![]() |
1128cf679c | ||
![]() |
ff65a42350 | ||
![]() |
c459587dcd | ||
![]() |
6f8ea9677f | ||
![]() |
38197527fa | ||
![]() |
21b2bd2c8e | ||
![]() |
25158eee55 | ||
![]() |
1aa804f255 | ||
![]() |
fbe101339d | ||
![]() |
a4aeb8171d | ||
![]() |
27f98a459c | ||
![]() |
e6b0eaa46d | ||
![]() |
f336317a33 | ||
![]() |
17b44cc0fa | ||
![]() |
debe3a8597 | ||
![]() |
f36c5e849f | ||
![]() |
abbd6797c4 | ||
![]() |
fdb7784a7d | ||
![]() |
92b49015b0 | ||
![]() |
1765ffff7b | ||
![]() |
3024316b5b | ||
![]() |
9a0d89bfbf | ||
![]() |
807ef3b700 | ||
![]() |
948f10d4e3 | ||
![]() |
0f99b5cb87 | ||
![]() |
6413b0ff82 | ||
![]() |
39713d8e11 | ||
![]() |
739a497af6 | ||
![]() |
de2fe9b0aa | ||
![]() |
44448895a0 | ||
![]() |
cfd9097769 | ||
![]() |
627042fd25 | ||
![]() |
b51ce24d0c | ||
![]() |
fc0881eccc | ||
![]() |
6b8509d2b2 | ||
![]() |
cf1d67a5cf | ||
![]() |
473ebd25b8 | ||
![]() |
d4427cfff4 | ||
![]() |
9d2e9786cc | ||
![]() |
9968f714c7 | ||
![]() |
bd212c4bf3 | ||
![]() |
32c7f904db | ||
![]() |
2ef017282f | ||
![]() |
9672f67a23 | ||
![]() |
6e5cfd827c | ||
![]() |
5402bef4a9 | ||
![]() |
4194512cce | ||
![]() |
b3aad8b0d9 | ||
![]() |
1489c6df25 | ||
![]() |
2e225045e6 | ||
![]() |
11ed06148c | ||
![]() |
a3fc018186 | ||
![]() |
9692bf6ec6 | ||
![]() |
9b3916307a | ||
![]() |
fdf96b479c | ||
![]() |
25c7a6096d | ||
![]() |
627955e7fd | ||
![]() |
43e9b070a9 | ||
![]() |
78bb36a2bb | ||
![]() |
58e6e3cbda | ||
![]() |
1da086ce0a | ||
![]() |
e9d43a9449 | ||
![]() |
ce31052661 | ||
![]() |
3fd9b0a183 | ||
![]() |
7e1dee8e07 | ||
![]() |
f2854fdf00 | ||
![]() |
1fad95a55b | ||
![]() |
5342e1521c | ||
![]() |
c0bb7def20 | ||
![]() |
3c532526df | ||
![]() |
05c6cae86f | ||
![]() |
24a49f035e |
9
.editorconfig
Normal file
9
.editorconfig
Normal file
@@ -0,0 +1,9 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
insert_final_newline = true
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
5
.github/workflows/publish.yml
vendored
5
.github/workflows/publish.yml
vendored
@@ -9,10 +9,10 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: setup node
|
||||
uses: actions/setup-node@v2
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
|
||||
@@ -27,7 +27,6 @@ jobs:
|
||||
- name: zip
|
||||
run: |
|
||||
sudo apt install zip -y
|
||||
cp manifest.json ./dist/manifest.json
|
||||
cd ./dist/
|
||||
zip -r ../LLOneBot.zip ./*
|
||||
|
||||
|
17
.gitignore
vendored
17
.gitignore
vendored
@@ -1,6 +1,15 @@
|
||||
node_modules/
|
||||
.yarn/*
|
||||
!.yarn/patches
|
||||
!.yarn/plugins
|
||||
!.yarn/releases
|
||||
!.yarn/sdks
|
||||
!.yarn/versions
|
||||
|
||||
package-lock.json
|
||||
dist/
|
||||
out/
|
||||
yarn.lock
|
||||
node_modules
|
||||
dist
|
||||
out
|
||||
|
||||
.idea/
|
||||
.DS_Store
|
||||
.DS_Store
|
||||
|
1
.yarnrc.yml
Normal file
1
.yarnrc.yml
Normal file
@@ -0,0 +1 @@
|
||||
nodeLinker: node-modules
|
34
README.md
34
README.md
@@ -1,9 +1,9 @@
|
||||
# LLOneBot API
|
||||
# LLOneBot
|
||||
|
||||
LiteLoaderQQNT插件,使你的NTQQ支持OneBot11协议进行QQ机器人开发
|
||||
LiteLoaderQQNT 插件,实现 OneBot 11 协议,用以 QQ 机器人开发
|
||||
|
||||
> [!CAUTION]\
|
||||
> **请不要在 QQ 官方群聊和任何影响力较大的简中互联网平台(包括但不限于:B站,微博,知乎,抖音等)发布和讨论*任何*与本插件存在相关性的信息**
|
||||
> **请不要在 QQ 官方群聊和任何影响力较大的简中互联网平台(包括但不限于: B站,微博,知乎,抖音等)发布和讨论*任何*与本插件存在相关性的信息**
|
||||
|
||||
TG群:<https://t.me/+nLZEnpne-pQ1OWFl>
|
||||
|
||||
@@ -13,35 +13,16 @@ TG群:<https://t.me/+nLZEnpne-pQ1OWFl>
|
||||
|
||||
## 设置界面
|
||||
|
||||
<img src="./doc/image/setting.png" width="500px" alt="图片名称"/>
|
||||
<img src="./doc/image/setting.png" width="400px" alt="设置界面"/>
|
||||
|
||||
## HTTP 调用示例
|
||||
|
||||

|
||||
<img src="./doc/image/example.jpg" width="500px" alt="HTTP调用示例"/>
|
||||
|
||||
## 支持的 api 和功能详情
|
||||
## 支持的 API
|
||||
|
||||
见 <https://llonebot.github.io/zh-CN/develop/api>
|
||||
|
||||
## TODO
|
||||
|
||||
- [x] 重构摆脱LLAPI,目前调用LLAPI只能在renderer进程调用,需重构成在main进程调用
|
||||
- [x] 支持正、反向websocket(感谢@disymayufei的PR)
|
||||
- [x] 转发消息记录
|
||||
- [x] 好友点赞api
|
||||
- [x] 群管理功能,禁言、踢人,改群名片等
|
||||
- [x] 视频消息
|
||||
- [x] 文件消息
|
||||
- [x] 群禁言事件上报
|
||||
- [x] 优化加群成功事件上报
|
||||
- [x] 清理缓存api
|
||||
- [ ] 无头模式
|
||||
- [ ] 框架对接文档
|
||||
|
||||
## onebot11文档
|
||||
|
||||
<https://11.onebot.dev/>
|
||||
|
||||
## Stargazers over time
|
||||
|
||||
[](https://starchart.cc/LLOneBot/LLOneBot)
|
||||
@@ -49,8 +30,7 @@ TG群:<https://t.me/+nLZEnpne-pQ1OWFl>
|
||||
## 鸣谢
|
||||
|
||||
- [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)
|
||||
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import cp from 'vite-plugin-cp'
|
||||
import './scripts/gen-version'
|
||||
import path from 'node:path'
|
||||
import './scripts/gen-manifest'
|
||||
|
||||
const external = [
|
||||
'silk-wasm',
|
||||
@@ -31,9 +32,11 @@ let config = {
|
||||
external,
|
||||
input: 'src/main/main.ts',
|
||||
},
|
||||
minify: true,
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': path.resolve(__dirname, './src'),
|
||||
'./lib-cov/fluent-ffmpeg': './lib/fluent-ffmpeg',
|
||||
},
|
||||
},
|
||||
@@ -42,10 +45,10 @@ let config = {
|
||||
targets: [
|
||||
...external.map(genCpModule),
|
||||
{ src: './manifest.json', dest: 'dist' },
|
||||
{ src: './icon.jpg', dest: 'dist' },
|
||||
{ src: './src/ntqqapi/external/crychic/crychic-win32-x64.node', dest: 'dist/main/' },
|
||||
{ src: './src/ntqqapi/external/moehook/MoeHoo-win32-x64.node', dest: 'dist/main/' },
|
||||
{ src: './src/ntqqapi/external/moehook/MoeHoo-linux-x64.node', dest: 'dist/main/' },
|
||||
{ src: './icon.webp', dest: 'dist' },
|
||||
// { src: './src/ntqqapi/native/crychic/crychic-win32-x64.node', dest: 'dist/main/' },
|
||||
// { src: './src/ntqqapi/native/moehook/MoeHoo-win32-x64.node', dest: 'dist/main/' },
|
||||
// { src: './src/ntqqapi/native/moehook/MoeHoo-linux-x64.node', dest: 'dist/main/' },
|
||||
],
|
||||
}),
|
||||
],
|
||||
|
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"manifest_version": 4,
|
||||
"type": "extension",
|
||||
"name": "LLOneBot v3.24.0",
|
||||
"name": "LLOneBot",
|
||||
"slug": "LLOneBot",
|
||||
"description": "使你的NTQQ支持OneBot11协议进行QQ机器人开发, 不支持商店在线更新",
|
||||
"version": "3.24.0",
|
||||
"icon": "./icon.jpg",
|
||||
"description": "实现 OneBot 11 协议,用以 QQ 机器人开发",
|
||||
"version": "3.28.5",
|
||||
"icon": "./icon.webp",
|
||||
"authors": [
|
||||
{
|
||||
"name": "linyuchen",
|
||||
@@ -13,7 +13,7 @@
|
||||
}
|
||||
],
|
||||
"repository": {
|
||||
"repo": "linyuchen/LiteLoaderQQNT-OneBotApi",
|
||||
"repo": "LLOneBot/LLOneBot",
|
||||
"branch": "main",
|
||||
"release": {
|
||||
"tag": "latest",
|
||||
|
46
package.json
46
package.json
@@ -10,39 +10,33 @@
|
||||
"deploy-mac": "cp -r dist/* ~/Library/Containers/com.tencent.qq/Data/LiteLoaderQQNT/plugins/LLOneBot/",
|
||||
"build-win": "npm run build && npm run deploy-win",
|
||||
"deploy-win": "cmd /c \"xcopy /C /S /Y dist\\* %USERPROFILE%\\documents\\LiteLoaderQQNT\\plugins\\LLOneBot\\\"",
|
||||
"format": "prettier -cw ."
|
||||
"format": "prettier -cw .",
|
||||
"check": "tsc"
|
||||
},
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"compressing": "^1.10.0",
|
||||
"compressing": "^1.10.1",
|
||||
"cors": "^2.8.5",
|
||||
"express": "^4.18.2",
|
||||
"fast-xml-parser": "^4.3.6",
|
||||
"file-type": "^19.0.0",
|
||||
"fluent-ffmpeg": "^2.1.2",
|
||||
"express": "^4.19.2",
|
||||
"fast-xml-parser": "^4.4.1",
|
||||
"file-type": "^19.4.0",
|
||||
"fluent-ffmpeg": "^2.1.3",
|
||||
"level": "^8.0.1",
|
||||
"silk-wasm": "^3.3.4",
|
||||
"utf-8-validate": "^6.0.3",
|
||||
"uuid": "^9.0.1",
|
||||
"ws": "^8.16.0"
|
||||
"silk-wasm": "^3.6.1",
|
||||
"ws": "^8.18.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/express": "^4.17.20",
|
||||
"@types/fluent-ffmpeg": "^2.1.24",
|
||||
"@types/node": "^20.11.24",
|
||||
"@types/uuid": "^9.0.8",
|
||||
"@types/ws": "^8.5.10",
|
||||
"@typescript-eslint/eslint-plugin": "^6.4.0",
|
||||
"electron": "^29.0.1",
|
||||
"electron-vite": "^2.0.0",
|
||||
"eslint": "^8.0.1",
|
||||
"eslint-plugin-import": "^2.25.2",
|
||||
"eslint-plugin-n": "^15.0.0 || ^16.0.0",
|
||||
"eslint-plugin-promise": "^6.0.0",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "*",
|
||||
"vite": "^5.1.4",
|
||||
"@types/cors": "^2.8.17",
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/fluent-ffmpeg": "^2.1.25",
|
||||
"@types/node": "^20.14.15",
|
||||
"@types/ws": "^8.5.12",
|
||||
"electron": "^29.1.4",
|
||||
"electron-vite": "^2.3.0",
|
||||
"typescript": "^5.5.4",
|
||||
"vite": "^5.4.0",
|
||||
"vite-plugin-cp": "^4.0.8"
|
||||
}
|
||||
},
|
||||
"packageManager": "yarn@4.4.0"
|
||||
}
|
||||
|
38
scripts/gen-manifest.ts
Normal file
38
scripts/gen-manifest.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { version } from '../src/version'
|
||||
import { writeFileSync } from 'node:fs'
|
||||
|
||||
const manifest = {
|
||||
manifest_version: 4,
|
||||
type: 'extension',
|
||||
name: 'LLOneBot',
|
||||
slug: 'LLOneBot',
|
||||
description: '实现 OneBot 11 协议,用以 QQ 机器人开发',
|
||||
version,
|
||||
icon: './icon.webp',
|
||||
authors: [
|
||||
{
|
||||
name: 'linyuchen',
|
||||
link: 'https://github.com/linyuchen'
|
||||
}
|
||||
],
|
||||
repository: {
|
||||
repo: 'LLOneBot/LLOneBot',
|
||||
branch: 'main',
|
||||
release: {
|
||||
tag: 'latest',
|
||||
name: 'LLOneBot.zip'
|
||||
}
|
||||
},
|
||||
platform: [
|
||||
'win32',
|
||||
'linux',
|
||||
'darwin'
|
||||
],
|
||||
injects: {
|
||||
renderer: './renderer/index.js',
|
||||
main: './main/main.cjs',
|
||||
preload: './preload/preload.cjs'
|
||||
}
|
||||
}
|
||||
|
||||
writeFileSync('manifest.json', JSON.stringify(manifest, null, 2))
|
@@ -1,22 +0,0 @@
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import { version } from '../src/version'
|
||||
|
||||
const manifestPath = path.join(__dirname, '../manifest.json')
|
||||
|
||||
function readManifest(): any {
|
||||
if (fs.existsSync(manifestPath)) {
|
||||
return JSON.parse(fs.readFileSync(manifestPath, 'utf-8'))
|
||||
}
|
||||
}
|
||||
|
||||
function writeManifest(manifest: any) {
|
||||
fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2))
|
||||
}
|
||||
|
||||
const manifest = readManifest()
|
||||
if (version !== manifest.version) {
|
||||
manifest.version = version
|
||||
manifest.name = `LLOneBot v${version}`
|
||||
writeManifest(manifest)
|
||||
}
|
@@ -1,10 +1,10 @@
|
||||
import { Level } from 'level'
|
||||
|
||||
const db = new Level(process.env['level_db_path'], { valueEncoding: 'json' })
|
||||
const db = new Level(process.env['level_db_path'] as string, { valueEncoding: 'json' })
|
||||
|
||||
async function getGroupNotify() {
|
||||
let keys = await db.keys().all()
|
||||
let result = []
|
||||
let result: string[] = []
|
||||
for (const key of keys) {
|
||||
// console.log(key)
|
||||
if (key.startsWith('group_notify_')) {
|
||||
|
@@ -1,10 +1,8 @@
|
||||
import fs from 'fs'
|
||||
import fsPromise from 'fs/promises'
|
||||
import fs from 'node:fs'
|
||||
import { Config, OB11Config } from './types'
|
||||
|
||||
import { mergeNewProperties } from './utils/helper'
|
||||
import path from 'node:path'
|
||||
import { selfInfo } from './data'
|
||||
import { getSelfUin } from './data'
|
||||
import { DATA_DIR } from './utils'
|
||||
|
||||
export const HOOK_LOG = false
|
||||
@@ -40,8 +38,10 @@ export class ConfigUtil {
|
||||
enableWsReverse: false,
|
||||
messagePostFormat: 'array',
|
||||
enableHttpHeart: false,
|
||||
enableQOAutoQuote: false
|
||||
}
|
||||
let defaultConfig: Config = {
|
||||
enableLLOB: true,
|
||||
ob11: ob11Default,
|
||||
heartInterval: 60000,
|
||||
token: '',
|
||||
@@ -51,7 +51,6 @@ export class ConfigUtil {
|
||||
reportSelfMessage: false,
|
||||
autoDeleteFile: false,
|
||||
autoDeleteFileSecond: 60,
|
||||
enablePoke: false,
|
||||
musicSignUrl: '',
|
||||
}
|
||||
|
||||
@@ -82,9 +81,12 @@ export class ConfigUtil {
|
||||
fs.writeFileSync(this.configPath, JSON.stringify(config, null, 2), 'utf-8')
|
||||
}
|
||||
|
||||
private checkOldConfig(currentConfig: Config | OB11Config,
|
||||
oldConfig: Config | OB11Config,
|
||||
currentKey: string, oldKey: string) {
|
||||
private checkOldConfig(
|
||||
currentConfig: Config | OB11Config,
|
||||
oldConfig: Config | OB11Config,
|
||||
currentKey: string,
|
||||
oldKey: string,
|
||||
) {
|
||||
// 迁移旧的配置到新配置,避免用户重新填写配置
|
||||
const oldValue = oldConfig[oldKey]
|
||||
if (oldValue) {
|
||||
@@ -95,6 +97,6 @@ export class ConfigUtil {
|
||||
}
|
||||
|
||||
export function getConfigUtil() {
|
||||
const configFilePath = path.join(DATA_DIR, `config_${selfInfo.uin}.json`)
|
||||
const configFilePath = path.join(DATA_DIR, `config_${getSelfUin()}.json`)
|
||||
return new ConfigUtil(configFilePath)
|
||||
}
|
||||
|
@@ -1,25 +1,25 @@
|
||||
import { type Friend, type FriendRequest, type Group, type GroupMember, type SelfInfo } from '../ntqqapi/types'
|
||||
import { type FileCache, type LLOneBotError } from './types'
|
||||
import {
|
||||
type Friend,
|
||||
type Group,
|
||||
type GroupMember,
|
||||
type SelfInfo,
|
||||
} from '../ntqqapi/types'
|
||||
import { type LLOneBotError } from './types'
|
||||
import { NTQQGroupApi } from '../ntqqapi/api/group'
|
||||
import { log } from './utils/log'
|
||||
import { isNumeric } from './utils/helper'
|
||||
import { NTQQFriendApi } from '../ntqqapi/api'
|
||||
import { NTQQFriendApi, NTQQUserApi } from '../ntqqapi/api'
|
||||
|
||||
export const selfInfo: SelfInfo = {
|
||||
uid: '',
|
||||
uin: '',
|
||||
nick: '',
|
||||
online: true,
|
||||
}
|
||||
export let groups: Group[] = []
|
||||
export let friends: Friend[] = []
|
||||
export let friendRequests: Map<number, FriendRequest> = new Map<number, FriendRequest>()
|
||||
export const llonebotError: LLOneBotError = {
|
||||
ffmpegError: '',
|
||||
httpServerError: '',
|
||||
wsServerError: '',
|
||||
otherError: 'LLOnebot未能正常启动,请检查日志查看错误',
|
||||
}
|
||||
// 群号 -> 群成员map(uid=>GroupMember)
|
||||
export const groupMembers: Map<string, Map<string, GroupMember>> = new Map<string, Map<string, GroupMember>>()
|
||||
|
||||
export async function getFriend(uinOrUid: string): Promise<Friend | undefined> {
|
||||
let filterKey = isNumeric(uinOrUid.toString()) ? 'uin' : 'uid'
|
||||
@@ -27,13 +27,13 @@ export async function getFriend(uinOrUid: string): Promise<Friend | undefined> {
|
||||
let friend = friends.find((friend) => friend[filterKey] === filterValue.toString())
|
||||
if (!friend) {
|
||||
try {
|
||||
const _friends = (await NTQQFriendApi.getFriends(true))
|
||||
friend = _friends.find(friend => friend[filterKey] === filterValue.toString())
|
||||
if (friend){
|
||||
const _friends = await NTQQFriendApi.getFriends(true)
|
||||
friend = _friends.find((friend) => friend[filterKey] === filterValue.toString())
|
||||
if (friend) {
|
||||
friends.push(friend)
|
||||
}
|
||||
} catch (e) {
|
||||
log("刷新好友列表失败", e.stack.toString())
|
||||
} catch (e: any) {
|
||||
log('刷新好友列表失败', e.stack.toString())
|
||||
}
|
||||
}
|
||||
return friend
|
||||
@@ -66,44 +66,65 @@ 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<string, string> = {} // 一串加密的字符串(uid) -> qq号
|
||||
const selfInfo: SelfInfo = {
|
||||
uid: '',
|
||||
uin: '',
|
||||
nick: '',
|
||||
online: true,
|
||||
}
|
||||
|
||||
export function getUidByUin(uin: string) {
|
||||
for (const uid in uidMaps) {
|
||||
if (uidMaps[uid] === uin) {
|
||||
return uid
|
||||
export async function getSelfNick(force = false): Promise<string> {
|
||||
if (!selfInfo.nick || force) {
|
||||
const userInfo = await NTQQUserApi.getUserDetailInfo(selfInfo.uid)
|
||||
if (userInfo) {
|
||||
selfInfo.nick = userInfo.nick
|
||||
return userInfo.nick
|
||||
}
|
||||
}
|
||||
|
||||
return selfInfo.nick
|
||||
}
|
||||
|
||||
export let tempGroupCodeMap: Record<string, string> = {} // peerUid => 群号
|
||||
export function getSelfInfo() {
|
||||
return selfInfo
|
||||
}
|
||||
|
||||
export function setSelfInfo(data: Partial<SelfInfo>) {
|
||||
Object.assign(selfInfo, data)
|
||||
}
|
||||
|
||||
export function getSelfUid() {
|
||||
return selfInfo['uid']
|
||||
}
|
||||
|
||||
export function getSelfUin() {
|
||||
return selfInfo['uin']
|
||||
}
|
114
src/common/db.ts
114
src/common/db.ts
@@ -1,7 +1,6 @@
|
||||
import { Level } from 'level'
|
||||
import { type GroupNotify, RawMessage } from '../ntqqapi/types'
|
||||
import { DATA_DIR } from './utils'
|
||||
import { selfInfo } from './data'
|
||||
import { FileCache } from './types'
|
||||
import { log } from './utils/log'
|
||||
|
||||
@@ -14,9 +13,9 @@ class DBUtil {
|
||||
public readonly DB_KEY_PREFIX_FILE = 'file_'
|
||||
public readonly DB_KEY_PREFIX_GROUP_NOTIFY = 'group_notify_'
|
||||
private readonly DB_KEY_RECEIVED_TEMP_UIN_MAP = 'received_temp_uin_map'
|
||||
public db: Level
|
||||
public db: Level | undefined
|
||||
public cache: Record<string, RawMessage | string | FileCache | GroupNotify | ReceiveTempUinMap> = {} // <msg_id_ | msg_short_id_ | msg_seq_id_><id>: RawMessage
|
||||
private currentShortId: number
|
||||
private currentShortId: number | undefined
|
||||
|
||||
/*
|
||||
* 数据库结构
|
||||
@@ -27,33 +26,12 @@ class DBUtil {
|
||||
* */
|
||||
|
||||
constructor() {
|
||||
let initCount = 0
|
||||
new Promise((resolve, reject) => {
|
||||
const initDB = () => {
|
||||
initCount++
|
||||
// if (initCount > 50) {
|
||||
// return reject("init db fail")
|
||||
// }
|
||||
|
||||
try {
|
||||
if (!selfInfo.uin) {
|
||||
setTimeout(initDB, 300)
|
||||
return
|
||||
}
|
||||
const DB_PATH = DATA_DIR + `/msg_${selfInfo.uin}`
|
||||
this.db = new Level(DB_PATH, { valueEncoding: 'json' })
|
||||
console.log('llonebot init db success')
|
||||
resolve(null)
|
||||
} catch (e) {
|
||||
console.log('init db fail', e.stack.toString())
|
||||
setTimeout(initDB, 300)
|
||||
}
|
||||
}
|
||||
initDB()
|
||||
}).then()
|
||||
}
|
||||
|
||||
init(uin: string) {
|
||||
const DB_PATH = DATA_DIR + `/msg_${uin}`
|
||||
this.db = new Level(DB_PATH, { valueEncoding: 'json' })
|
||||
const expiredMilliSecond = 1000 * 60 * 60
|
||||
|
||||
setInterval(() => {
|
||||
// this.cache = {}
|
||||
// 清理时间较久的缓存
|
||||
@@ -72,13 +50,13 @@ class DBUtil {
|
||||
|
||||
public async getReceivedTempUinMap(): Promise<ReceiveTempUinMap> {
|
||||
try {
|
||||
this.cache[this.DB_KEY_RECEIVED_TEMP_UIN_MAP] = JSON.parse(await this.db.get(this.DB_KEY_RECEIVED_TEMP_UIN_MAP))
|
||||
} catch (e) {}
|
||||
this.cache[this.DB_KEY_RECEIVED_TEMP_UIN_MAP] = JSON.parse(await this.db?.get(this.DB_KEY_RECEIVED_TEMP_UIN_MAP)!)
|
||||
} catch (e) { }
|
||||
return (this.cache[this.DB_KEY_RECEIVED_TEMP_UIN_MAP] || {}) as ReceiveTempUinMap
|
||||
}
|
||||
public setReceivedTempUinMap(data: ReceiveTempUinMap) {
|
||||
this.cache[this.DB_KEY_RECEIVED_TEMP_UIN_MAP] = data
|
||||
this.db.put(this.DB_KEY_RECEIVED_TEMP_UIN_MAP, JSON.stringify(data)).then()
|
||||
this.db?.put(this.DB_KEY_RECEIVED_TEMP_UIN_MAP, JSON.stringify(data)).then()
|
||||
}
|
||||
private addCache(msg: RawMessage) {
|
||||
const longIdKey = this.DB_KEY_PREFIX_MSG_ID + msg.msgId
|
||||
@@ -91,30 +69,30 @@ class DBUtil {
|
||||
this.cache = {}
|
||||
}
|
||||
|
||||
async getMsgByShortId(shortMsgId: number): Promise<RawMessage> {
|
||||
async getMsgByShortId(shortMsgId: number): Promise<RawMessage | undefined> {
|
||||
const shortMsgIdKey = this.DB_KEY_PREFIX_MSG_SHORT_ID + shortMsgId
|
||||
if (this.cache[shortMsgIdKey]) {
|
||||
// log("getMsgByShortId cache", shortMsgIdKey, this.cache[shortMsgIdKey])
|
||||
return this.cache[shortMsgIdKey] as RawMessage
|
||||
}
|
||||
try {
|
||||
const longId = await this.db.get(shortMsgIdKey)
|
||||
const msg = await this.getMsgByLongId(longId)
|
||||
this.addCache(msg)
|
||||
const longId = await this.db?.get(shortMsgIdKey)
|
||||
const msg = await this.getMsgByLongId(longId!)
|
||||
this.addCache(msg!)
|
||||
return msg
|
||||
} catch (e) {
|
||||
} catch (e: any) {
|
||||
log('getMsgByShortId db error', e.stack.toString())
|
||||
}
|
||||
}
|
||||
|
||||
async getMsgByLongId(longId: string): Promise<RawMessage> {
|
||||
async getMsgByLongId(longId: string): Promise<RawMessage | undefined> {
|
||||
const longIdKey = this.DB_KEY_PREFIX_MSG_ID + longId
|
||||
if (this.cache[longIdKey]) {
|
||||
return this.cache[longIdKey] as RawMessage
|
||||
}
|
||||
try {
|
||||
const data = await this.db.get(longIdKey)
|
||||
const msg = JSON.parse(data)
|
||||
const data = await this.db?.get(longIdKey)
|
||||
const msg = JSON.parse(data!)
|
||||
this.addCache(msg)
|
||||
return msg
|
||||
} catch (e) {
|
||||
@@ -122,17 +100,17 @@ class DBUtil {
|
||||
}
|
||||
}
|
||||
|
||||
async getMsgBySeqId(seqId: string): Promise<RawMessage> {
|
||||
async getMsgBySeqId(seqId: string): Promise<RawMessage | undefined> {
|
||||
const seqIdKey = this.DB_KEY_PREFIX_MSG_SEQ_ID + seqId
|
||||
if (this.cache[seqIdKey]) {
|
||||
return this.cache[seqIdKey] as RawMessage
|
||||
}
|
||||
try {
|
||||
const longId = await this.db.get(seqIdKey)
|
||||
const msg = await this.getMsgByLongId(longId)
|
||||
this.addCache(msg)
|
||||
const longId = await this.db?.get(seqIdKey)
|
||||
const msg = await this.getMsgByLongId(longId!)
|
||||
this.addCache(msg!)
|
||||
return msg
|
||||
} catch (e) {
|
||||
} catch (e: any) {
|
||||
log('getMsgBySeqId db error', e.stack.toString())
|
||||
}
|
||||
}
|
||||
@@ -141,7 +119,7 @@ class DBUtil {
|
||||
// 有则更新,无则添加
|
||||
// log("addMsg", msg.msgId, msg.msgSeq, msg.msgShortId);
|
||||
const longIdKey = this.DB_KEY_PREFIX_MSG_ID + msg.msgId
|
||||
let existMsg = this.cache[longIdKey] as RawMessage
|
||||
let existMsg: RawMessage | undefined = this.cache[longIdKey] as RawMessage
|
||||
if (!existMsg) {
|
||||
try {
|
||||
existMsg = await this.getMsgByLongId(msg.msgId)
|
||||
@@ -154,20 +132,20 @@ class DBUtil {
|
||||
this.updateMsg(msg).then()
|
||||
return existMsg.msgShortId
|
||||
}
|
||||
this.addCache(msg)
|
||||
|
||||
const shortMsgId = await this.genMsgShortId()
|
||||
const shortIdKey = this.DB_KEY_PREFIX_MSG_SHORT_ID + shortMsgId
|
||||
const seqIdKey = this.DB_KEY_PREFIX_MSG_SEQ_ID + msg.msgSeq
|
||||
msg.msgShortId = shortMsgId
|
||||
this.addCache(msg)
|
||||
// log("新增消息记录", msg.msgId)
|
||||
this.db.put(shortIdKey, msg.msgId).then().catch()
|
||||
this.db.put(longIdKey, JSON.stringify(msg)).then().catch()
|
||||
this.db?.put(shortIdKey, msg.msgId).then().catch()
|
||||
this.db?.put(longIdKey, JSON.stringify(msg)).then().catch()
|
||||
try {
|
||||
await this.db.get(seqIdKey)
|
||||
await this.db?.get(seqIdKey)
|
||||
} catch (e) {
|
||||
// log("新的seqId", seqIdKey)
|
||||
this.db.put(seqIdKey, msg.msgId).then().catch()
|
||||
this.db?.put(seqIdKey, msg.msgId).then().catch()
|
||||
}
|
||||
if (!this.cache[seqIdKey]) {
|
||||
this.cache[seqIdKey] = msg
|
||||
@@ -178,7 +156,7 @@ class DBUtil {
|
||||
|
||||
async updateMsg(msg: RawMessage) {
|
||||
const longIdKey = this.DB_KEY_PREFIX_MSG_ID + msg.msgId
|
||||
let existMsg = this.cache[longIdKey] as RawMessage
|
||||
let existMsg: RawMessage | undefined = this.cache[longIdKey] as RawMessage
|
||||
if (!existMsg) {
|
||||
try {
|
||||
existMsg = await this.getMsgByLongId(msg.msgId)
|
||||
@@ -187,18 +165,18 @@ class DBUtil {
|
||||
}
|
||||
}
|
||||
|
||||
Object.assign(existMsg, msg)
|
||||
this.db.put(longIdKey, JSON.stringify(existMsg)).then().catch()
|
||||
const shortIdKey = this.DB_KEY_PREFIX_MSG_SHORT_ID + existMsg.msgShortId
|
||||
Object.assign(existMsg!, msg)
|
||||
this.db?.put(longIdKey, JSON.stringify(existMsg)).then().catch()
|
||||
const shortIdKey = this.DB_KEY_PREFIX_MSG_SHORT_ID + existMsg?.msgShortId
|
||||
const seqIdKey = this.DB_KEY_PREFIX_MSG_SEQ_ID + msg.msgSeq
|
||||
if (!this.cache[seqIdKey]) {
|
||||
this.cache[seqIdKey] = existMsg
|
||||
this.cache[seqIdKey] = existMsg!
|
||||
}
|
||||
this.db.put(shortIdKey, msg.msgId).then().catch()
|
||||
this.db?.put(shortIdKey, msg.msgId).then().catch()
|
||||
try {
|
||||
await this.db.get(seqIdKey)
|
||||
await this.db?.get(seqIdKey)
|
||||
} catch (e) {
|
||||
this.db.put(seqIdKey, msg.msgId).then().catch()
|
||||
this.db?.put(seqIdKey, msg.msgId).then().catch()
|
||||
// log("更新seqId error", e.stack, seqIdKey);
|
||||
}
|
||||
// log("更新消息", existMsg.msgSeq, existMsg.msgShortId, existMsg.msgId);
|
||||
@@ -208,15 +186,15 @@ class DBUtil {
|
||||
const key = 'msg_current_short_id'
|
||||
if (this.currentShortId === undefined) {
|
||||
try {
|
||||
let id: string = await this.db.get(key)
|
||||
this.currentShortId = parseInt(id)
|
||||
const id = await this.db?.get(key)
|
||||
this.currentShortId = parseInt(id!)
|
||||
} catch (e) {
|
||||
this.currentShortId = -2147483640
|
||||
}
|
||||
}
|
||||
|
||||
this.currentShortId++
|
||||
this.db.put(key, this.currentShortId.toString()).then().catch()
|
||||
this.db?.put(key, this.currentShortId.toString()).then().catch()
|
||||
return this.currentShortId
|
||||
}
|
||||
|
||||
@@ -229,8 +207,8 @@ class DBUtil {
|
||||
delete cacheDBData['downloadFunc']
|
||||
this.cache[fileNameOrUuid] = data
|
||||
try {
|
||||
await this.db.put(key, JSON.stringify(cacheDBData))
|
||||
} catch (e) {
|
||||
await this.db?.put(key, JSON.stringify(cacheDBData))
|
||||
} catch (e: any) {
|
||||
log('addFileCache db error', e.stack.toString())
|
||||
}
|
||||
}
|
||||
@@ -241,8 +219,8 @@ class DBUtil {
|
||||
return this.cache[key] as FileCache
|
||||
}
|
||||
try {
|
||||
let data = await this.db.get(key)
|
||||
return JSON.parse(data)
|
||||
const data = await this.db?.get(key)
|
||||
return JSON.parse(data!)
|
||||
} catch (e) {
|
||||
// log("getFileCache db error", e.stack.toString())
|
||||
}
|
||||
@@ -255,7 +233,7 @@ class DBUtil {
|
||||
return
|
||||
}
|
||||
this.cache[key] = notify
|
||||
this.db.put(key, JSON.stringify(notify)).then().catch()
|
||||
this.db?.put(key, JSON.stringify(notify)).then().catch()
|
||||
}
|
||||
|
||||
async getGroupNotify(seq: string): Promise<GroupNotify | undefined> {
|
||||
@@ -264,8 +242,8 @@ class DBUtil {
|
||||
return this.cache[key] as GroupNotify
|
||||
}
|
||||
try {
|
||||
let data = await this.db.get(key)
|
||||
return JSON.parse(data)
|
||||
const data = await this.db?.get(key)
|
||||
return JSON.parse(data!)
|
||||
} catch (e) {
|
||||
// log("getGroupNotify db error", e.stack.toString())
|
||||
}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import express, { Express, Request, Response } from 'express'
|
||||
import http from 'http'
|
||||
import http from 'node:http'
|
||||
import cors from 'cors'
|
||||
import { log } from '../utils/log'
|
||||
import { getConfigUtil } from '../config'
|
||||
@@ -10,7 +10,7 @@ type RegisterHandler = (res: Response, payload: any) => Promise<any>
|
||||
export abstract class HttpServerBase {
|
||||
name: string = 'LLOneBot'
|
||||
private readonly expressAPP: Express
|
||||
private server: http.Server = null
|
||||
private server: http.Server | null = null
|
||||
|
||||
constructor() {
|
||||
this.expressAPP = express()
|
||||
@@ -38,7 +38,7 @@ export abstract class HttpServerBase {
|
||||
let clientToken = ''
|
||||
const authHeader = req.get('authorization')
|
||||
if (authHeader) {
|
||||
clientToken = authHeader.split('Bearer ').pop()
|
||||
clientToken = authHeader.split('Bearer ').pop()!
|
||||
log('receive http header token', clientToken)
|
||||
} else if (req.query.access_token) {
|
||||
if (Array.isArray(req.query.access_token)) {
|
||||
@@ -58,11 +58,11 @@ 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 = ''
|
||||
} catch (e) {
|
||||
} catch (e: any) {
|
||||
log('HTTP服务启动失败', e.toString())
|
||||
llonebotError.httpServerError = 'HTTP服务启动失败, ' + e.toString()
|
||||
}
|
||||
@@ -103,7 +103,7 @@ export abstract class HttpServerBase {
|
||||
log('收到http请求', url, payload)
|
||||
try {
|
||||
res.send(await handler(res, payload))
|
||||
} catch (e) {
|
||||
} catch (e: any) {
|
||||
this.handleFailed(res, payload, e.stack.toString())
|
||||
}
|
||||
})
|
||||
|
@@ -6,9 +6,9 @@ import { getConfigUtil } from '../config'
|
||||
import { llonebotError } from '../data'
|
||||
|
||||
class WebsocketClientBase {
|
||||
private wsClient: WebSocket
|
||||
private wsClient: WebSocket | undefined
|
||||
|
||||
constructor() {}
|
||||
constructor() { }
|
||||
|
||||
send(msg: string) {
|
||||
if (this.wsClient && this.wsClient.readyState == WebSocket.OPEN) {
|
||||
@@ -16,11 +16,11 @@ class WebsocketClientBase {
|
||||
}
|
||||
}
|
||||
|
||||
onMessage(msg: string) {}
|
||||
onMessage(msg: string) { }
|
||||
}
|
||||
|
||||
export class WebsocketServerBase {
|
||||
private ws: WebSocketServer = null
|
||||
private ws: WebSocketServer | null = null
|
||||
|
||||
constructor() {
|
||||
console.log(`llonebot websocket service started`)
|
||||
@@ -30,22 +30,22 @@ export class WebsocketServerBase {
|
||||
try {
|
||||
this.ws = new WebSocketServer({ port, maxPayload: 1024 * 1024 * 1024 })
|
||||
llonebotError.wsServerError = ''
|
||||
} catch (e) {
|
||||
} catch (e: any) {
|
||||
llonebotError.wsServerError = '正向ws服务启动失败, ' + e.toString()
|
||||
}
|
||||
this.ws.on('connection', (wsClient, req) => {
|
||||
const url = req.url.split('?').shift()
|
||||
this.ws?.on('connection', (wsClient, req) => {
|
||||
const url = req.url?.split('?').shift()
|
||||
this.authorize(wsClient, req)
|
||||
this.onConnect(wsClient, url, req)
|
||||
this.onConnect(wsClient, url!, req)
|
||||
wsClient.on('message', async (msg) => {
|
||||
this.onMessage(wsClient, url, msg.toString())
|
||||
this.onMessage(wsClient, url!, msg.toString())
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
stop() {
|
||||
llonebotError.wsServerError = ''
|
||||
this.ws.close((err) => {
|
||||
this.ws?.close((err) => {
|
||||
log('ws server close failed!', err)
|
||||
})
|
||||
this.ws = null
|
||||
@@ -83,11 +83,11 @@ export class WebsocketServerBase {
|
||||
}
|
||||
}
|
||||
|
||||
authorizeFailed(wsClient: WebSocket) {}
|
||||
authorizeFailed(wsClient: WebSocket) { }
|
||||
|
||||
onConnect(wsClient: WebSocket, url: string, req: IncomingMessage) {}
|
||||
onConnect(wsClient: WebSocket, url: string, req: IncomingMessage) { }
|
||||
|
||||
onMessage(wsClient: WebSocket, url: string, msg: string) {}
|
||||
onMessage(wsClient: WebSocket, url: string, msg: string) { }
|
||||
|
||||
sendHeart() {}
|
||||
sendHeart() { }
|
||||
}
|
||||
|
@@ -10,12 +10,14 @@ export interface OB11Config {
|
||||
enableWsReverse?: boolean
|
||||
messagePostFormat?: 'array' | 'string'
|
||||
enableHttpHeart?: boolean
|
||||
enableQOAutoQuote: boolean // 快速操作回复自动引用原消息
|
||||
}
|
||||
export interface CheckVersion {
|
||||
result: boolean,
|
||||
result: boolean
|
||||
version: string
|
||||
}
|
||||
export interface Config {
|
||||
enableLLOB: boolean
|
||||
ob11: OB11Config
|
||||
token?: string
|
||||
heartInterval?: number // ms
|
||||
@@ -26,7 +28,6 @@ export interface Config {
|
||||
autoDeleteFile?: boolean
|
||||
autoDeleteFileSecond?: number
|
||||
ffmpeg?: string // ffmpeg路径
|
||||
enablePoke?: boolean
|
||||
musicSignUrl?: string
|
||||
ignoreBeforeLoginMsg?: boolean
|
||||
}
|
||||
@@ -45,5 +46,6 @@ export interface FileCache {
|
||||
fileUuid?: string
|
||||
url?: string
|
||||
msgId?: string
|
||||
elementId: string
|
||||
downloadFunc?: () => Promise<void>
|
||||
}
|
||||
|
231
src/common/utils/EventTask.ts
Normal file
231
src/common/utils/EventTask.ts
Normal file
@@ -0,0 +1,231 @@
|
||||
import { NodeIQQNTWrapperSession } from '@/ntqqapi/wrapper'
|
||||
import { randomUUID } from 'node:crypto'
|
||||
|
||||
interface Internal_MapKey {
|
||||
timeout: number
|
||||
createtime: number
|
||||
func: (...arg: any[]) => any
|
||||
checker: ((...args: any[]) => boolean) | undefined
|
||||
}
|
||||
|
||||
export class ListenerClassBase {
|
||||
[key: string]: string
|
||||
}
|
||||
|
||||
export interface ListenerIBase {
|
||||
new(listener: any): ListenerClassBase
|
||||
}
|
||||
|
||||
export class NTEventWrapper {
|
||||
private ListenerMap: { [key: string]: ListenerIBase } | undefined//ListenerName-Unique -> Listener构造函数
|
||||
private WrapperSession: NodeIQQNTWrapperSession | undefined//WrapperSession
|
||||
private ListenerManger: Map<string, ListenerClassBase> = new Map<string, ListenerClassBase>() //ListenerName-Unique -> Listener实例
|
||||
private EventTask = new Map<string, Map<string, Map<string, Internal_MapKey>>>()//tasks ListenerMainName -> ListenerSubName-> uuid -> {timeout,createtime,func}
|
||||
|
||||
constructor() {
|
||||
}
|
||||
|
||||
createProxyDispatch(ListenerMainName: string) {
|
||||
const current = this
|
||||
return new Proxy({}, {
|
||||
get(target: any, prop: any, receiver: any) {
|
||||
// console.log('get', prop, typeof target[prop])
|
||||
if (typeof target[prop] === 'undefined') {
|
||||
// 如果方法不存在,返回一个函数,这个函数调用existentMethod
|
||||
return (...args: any[]) => {
|
||||
current.DispatcherListener.apply(current, [ListenerMainName, prop, ...args]).then()
|
||||
}
|
||||
}
|
||||
// 如果方法存在,正常返回
|
||||
return Reflect.get(target, prop, receiver)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
init({ ListenerMap, WrapperSession }: { ListenerMap: { [key: string]: typeof ListenerClassBase }, WrapperSession: NodeIQQNTWrapperSession }) {
|
||||
this.ListenerMap = ListenerMap
|
||||
this.WrapperSession = WrapperSession
|
||||
}
|
||||
|
||||
CreatEventFunction<T extends (...args: any) => any>(eventName: string): T | undefined {
|
||||
const eventNameArr = eventName.split('/')
|
||||
type eventType = {
|
||||
[key: string]: () => { [key: string]: (...params: Parameters<T>) => Promise<ReturnType<T>> }
|
||||
}
|
||||
if (eventNameArr.length > 1) {
|
||||
const serviceName = 'get' + eventNameArr[0].replace('NodeIKernel', '')
|
||||
const eventName = eventNameArr[1]
|
||||
//getNodeIKernelGroupListener,GroupService
|
||||
//console.log('2', eventName)
|
||||
const services = (this.WrapperSession as unknown as eventType)[serviceName]()
|
||||
let event = services[eventName]
|
||||
//重新绑定this
|
||||
event = event.bind(services)
|
||||
if (event) {
|
||||
return event as T
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
CreatListenerFunction<T>(listenerMainName: string, uniqueCode: string = ''): T {
|
||||
const ListenerType = this.ListenerMap![listenerMainName]
|
||||
let Listener = this.ListenerManger.get(listenerMainName + uniqueCode)
|
||||
if (!Listener && ListenerType) {
|
||||
Listener = new ListenerType(this.createProxyDispatch(listenerMainName))
|
||||
const ServiceSubName = listenerMainName.match(/^NodeIKernel(.*?)Listener$/)![1]
|
||||
const Service = 'NodeIKernel' + ServiceSubName + 'Service/addKernel' + ServiceSubName + 'Listener'
|
||||
const addfunc = this.CreatEventFunction<(listener: T) => number>(Service)
|
||||
addfunc!(Listener as T)
|
||||
//console.log(addfunc!(Listener as T))
|
||||
this.ListenerManger.set(listenerMainName + uniqueCode, Listener)
|
||||
}
|
||||
return Listener as T
|
||||
}
|
||||
|
||||
//统一回调清理事件
|
||||
async DispatcherListener(ListenerMainName: string, ListenerSubName: string, ...args: any[]) {
|
||||
//console.log("[EventDispatcher]",ListenerMainName, ListenerSubName, ...args)
|
||||
this.EventTask.get(ListenerMainName)?.get(ListenerSubName)?.forEach((task, uuid) => {
|
||||
//console.log(task.func, uuid, task.createtime, task.timeout)
|
||||
if (task.createtime + task.timeout < Date.now()) {
|
||||
this.EventTask.get(ListenerMainName)?.get(ListenerSubName)?.delete(uuid)
|
||||
return
|
||||
}
|
||||
if (task.checker && task.checker(...args)) {
|
||||
task.func(...args)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async CallNoListenerEvent<EventType extends (...args: any[]) => Promise<any> | any>(EventName = '', timeout: number = 3000, ...args: Parameters<EventType>) {
|
||||
return new Promise<Awaited<ReturnType<EventType>>>(async (resolve, reject) => {
|
||||
const EventFunc = this.CreatEventFunction<EventType>(EventName)
|
||||
let complete = false
|
||||
const Timeouter = setTimeout(() => {
|
||||
if (!complete) {
|
||||
reject(new Error('NTEvent EventName:' + EventName + ' timeout'))
|
||||
}
|
||||
}, timeout)
|
||||
const retData = await EventFunc!(...args)
|
||||
complete = true
|
||||
resolve(retData)
|
||||
})
|
||||
}
|
||||
|
||||
async RegisterListen<ListenerType extends (...args: any[]) => void>(ListenerName = '', waitTimes = 1, timeout = 5000, checker: (...args: Parameters<ListenerType>) => boolean) {
|
||||
return new Promise<Parameters<ListenerType>>((resolve, reject) => {
|
||||
const ListenerNameList = ListenerName.split('/')
|
||||
const ListenerMainName = ListenerNameList[0]
|
||||
const ListenerSubName = ListenerNameList[1]
|
||||
const id = randomUUID()
|
||||
let complete = 0
|
||||
let retData: Parameters<ListenerType> | undefined = undefined
|
||||
const databack = () => {
|
||||
if (complete == 0) {
|
||||
reject(new Error(' ListenerName:' + ListenerName + ' timeout'))
|
||||
} else {
|
||||
resolve(retData!)
|
||||
}
|
||||
}
|
||||
const Timeouter = setTimeout(databack, timeout)
|
||||
const eventCallbak = {
|
||||
timeout: timeout,
|
||||
createtime: Date.now(),
|
||||
checker: checker,
|
||||
func: (...args: Parameters<ListenerType>) => {
|
||||
complete++
|
||||
retData = args
|
||||
if (complete >= waitTimes) {
|
||||
clearTimeout(Timeouter)
|
||||
databack()
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!this.EventTask.get(ListenerMainName)) {
|
||||
this.EventTask.set(ListenerMainName, new Map())
|
||||
}
|
||||
if (!(this.EventTask.get(ListenerMainName)?.get(ListenerSubName))) {
|
||||
this.EventTask.get(ListenerMainName)?.set(ListenerSubName, new Map())
|
||||
}
|
||||
this.EventTask.get(ListenerMainName)?.get(ListenerSubName)?.set(id, eventCallbak)
|
||||
this.CreatListenerFunction(ListenerMainName)
|
||||
})
|
||||
}
|
||||
|
||||
async CallNormalEvent<EventType extends (...args: any[]) => Promise<any>, ListenerType extends (...args: any[]) => void>
|
||||
(EventName = '', ListenerName = '', waitTimes = 1, timeout: number = 3000, checker: (...args: Parameters<ListenerType>) => boolean, ...args: Parameters<EventType>) {
|
||||
return new Promise<[EventRet: Awaited<ReturnType<EventType>>, ...Parameters<ListenerType>]>(async (resolve, reject) => {
|
||||
const id = randomUUID()
|
||||
let complete = 0
|
||||
let retData: Parameters<ListenerType> | undefined = undefined
|
||||
let retEvent: any = {}
|
||||
const databack = () => {
|
||||
if (complete == 0) {
|
||||
reject(new Error('Timeout: NTEvent EventName:' + EventName + ' ListenerName:' + ListenerName + ' EventRet:\n' + JSON.stringify(retEvent, null, 4) + '\n'))
|
||||
} else {
|
||||
resolve([retEvent as Awaited<ReturnType<EventType>>, ...retData!])
|
||||
}
|
||||
}
|
||||
|
||||
const ListenerNameList = ListenerName.split('/')
|
||||
const ListenerMainName = ListenerNameList[0]
|
||||
const ListenerSubName = ListenerNameList[1]
|
||||
|
||||
const Timeouter = setTimeout(databack, timeout)
|
||||
|
||||
const eventCallbak = {
|
||||
timeout: timeout,
|
||||
createtime: Date.now(),
|
||||
checker: checker,
|
||||
func: (...args: any[]) => {
|
||||
complete++
|
||||
//console.log('func', ...args)
|
||||
retData = args as Parameters<ListenerType>
|
||||
if (complete >= waitTimes) {
|
||||
clearTimeout(Timeouter)
|
||||
databack()
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!this.EventTask.get(ListenerMainName)) {
|
||||
this.EventTask.set(ListenerMainName, new Map())
|
||||
}
|
||||
if (!(this.EventTask.get(ListenerMainName)?.get(ListenerSubName))) {
|
||||
this.EventTask.get(ListenerMainName)?.set(ListenerSubName, new Map())
|
||||
}
|
||||
this.EventTask.get(ListenerMainName)?.get(ListenerSubName)?.set(id, eventCallbak)
|
||||
this.CreatListenerFunction(ListenerMainName)
|
||||
const EventFunc = this.CreatEventFunction<EventType>(EventName)
|
||||
retEvent = await EventFunc!(...(args as any[]))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export const NTEventDispatch = new NTEventWrapper()
|
||||
|
||||
// 示例代码 快速创建事件
|
||||
// let NTEvent = new NTEventWrapper()
|
||||
// let TestEvent = NTEvent.CreatEventFunction<(force: boolean) => Promise<Number>>('NodeIKernelProfileLikeService/GetTest')
|
||||
// if (TestEvent) {
|
||||
// TestEvent(true)
|
||||
// }
|
||||
|
||||
// 示例代码 快速创建监听Listener类
|
||||
// let NTEvent = new NTEventWrapper()
|
||||
// NTEvent.CreatListenerFunction<NodeIKernelMsgListener>('NodeIKernelMsgListener', 'core')
|
||||
|
||||
|
||||
// 调用接口
|
||||
//let NTEvent = new NTEventWrapper()
|
||||
//let ret = await NTEvent.CallNormalEvent<(force: boolean) => Promise<Number>, (data1: string, data2: number) => void>('NodeIKernelProfileLikeService/GetTest', 'NodeIKernelMsgListener/onAddSendMsg', 1, 3000, true)
|
||||
|
||||
// 注册监听 解除监听
|
||||
// NTEventDispatch.RigisterListener('NodeIKernelMsgListener/onAddSendMsg','core',cb)
|
||||
// NTEventDispatch.UnRigisterListener('NodeIKernelMsgListener/onAddSendMsg','core')
|
||||
|
||||
// let GetTest = NTEventDispatch.CreatEvent('NodeIKernelProfileLikeService/GetTest','NodeIKernelMsgListener/onAddSendMsg',Mode)
|
||||
// GetTest('test')
|
||||
|
||||
// always模式
|
||||
// NTEventDispatch.CreatEvent('NodeIKernelProfileLikeService/GetTest','NodeIKernelMsgListener/onAddSendMsg',Mode,(...args:any[])=>{ console.log(args) })
|
52
src/common/utils/QQBasicInfo.ts
Normal file
52
src/common/utils/QQBasicInfo.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import path from 'node:path'
|
||||
import os from 'node:os'
|
||||
import { systemPlatform } from './system'
|
||||
|
||||
export const exePath = process.execPath
|
||||
|
||||
function getPKGPath() {
|
||||
let p = path.join(path.dirname(exePath), 'resources', 'app', 'package.json')
|
||||
if (systemPlatform === 'darwin') {
|
||||
p = path.join(path.dirname(path.dirname(exePath)), 'Resources', 'app', 'package.json')
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
export const pkgInfoPath = getPKGPath()
|
||||
let configVersionInfoPath: string
|
||||
|
||||
|
||||
if (os.platform() !== 'linux') {
|
||||
configVersionInfoPath = path.join(path.dirname(exePath), 'resources', 'app', 'versions', 'config.json')
|
||||
}
|
||||
else {
|
||||
const userPath = os.homedir()
|
||||
const appDataPath = path.resolve(userPath, './.config/QQ')
|
||||
configVersionInfoPath = path.resolve(appDataPath, './versions/config.json')
|
||||
}
|
||||
|
||||
if (typeof configVersionInfoPath !== 'string') {
|
||||
throw new Error('Something went wrong when load QQ info path')
|
||||
}
|
||||
|
||||
export { configVersionInfoPath }
|
||||
|
||||
type QQPkgInfo = {
|
||||
version: string
|
||||
buildVersion: string
|
||||
platform: string
|
||||
eleArch: string
|
||||
}
|
||||
|
||||
export const qqPkgInfo: QQPkgInfo = require(pkgInfoPath)
|
||||
// platform_type: 3,
|
||||
// app_type: 4,
|
||||
// app_version: '9.9.9-23159',
|
||||
// qua: 'V1_WIN_NQ_9.9.9_23159_GW_B',
|
||||
// appid: '537213764',
|
||||
// platVer: '10.0.26100',
|
||||
// clientVer: '9.9.9-23159',
|
||||
|
||||
export function getBuildVersion(): number {
|
||||
return +qqPkgInfo.buildVersion
|
||||
}
|
@@ -1,130 +1,119 @@
|
||||
import fs from 'fs'
|
||||
import { encode, getDuration, getWavFileInfo, isWav } from 'silk-wasm'
|
||||
import fsPromise from 'fs/promises'
|
||||
import { log } from './log'
|
||||
import path from 'node:path'
|
||||
import { DATA_DIR, TEMP_DIR } from './index'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
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<Buffer>
|
||||
function convert(input: Input, options: FFmpegOptions, outputPath: string): Promise<string>
|
||||
function convert(input: Input, options: FFmpegOptions, outputPath?: string): Promise<Buffer> | Promise<string> {
|
||||
return new Promise<any>((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 pttPath = path.join(TEMP_DIR, uuidv4())
|
||||
if (getFileHeader(filePath) !== '02232153494c4b') {
|
||||
const file = await fsPromise.readFile(filePath)
|
||||
if (!isSilk(file)) {
|
||||
log(`语音文件${filePath}需要转换成silk`)
|
||||
const _isWav = await isWavFile(filePath)
|
||||
const pcmPath = pttPath + '.pcm'
|
||||
let sampleRate = 0
|
||||
const convert = () => {
|
||||
return new Promise<Buffer>((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 = fs.readFileSync(filePath)
|
||||
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 = fs.readFileSync(filePath)
|
||||
let duration = 0
|
||||
const silk = file
|
||||
let duration = 1
|
||||
try {
|
||||
duration = getDuration(silk) / 1000
|
||||
} catch (e) {
|
||||
log('获取语音文件时长失败, 使用文件大小推测时长', filePath, e.stack)
|
||||
duration = await guessDuration(filePath)
|
||||
} catch (e: any) {
|
||||
log('获取语音文件时长失败, 默认为1秒', filePath, e.stack)
|
||||
}
|
||||
|
||||
return {
|
||||
converted: false,
|
||||
path: filePath,
|
||||
duration,
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
} catch (error: any) {
|
||||
log('convert silk failed', error.stack)
|
||||
return {}
|
||||
}
|
||||
}
|
||||
|
||||
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 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',
|
||||
'-ac 1'
|
||||
]
|
||||
}, outFilePath)
|
||||
}
|
@@ -1,13 +1,10 @@
|
||||
import fs from 'fs'
|
||||
import fsPromise from 'fs/promises'
|
||||
import crypto from 'crypto'
|
||||
import util from 'util'
|
||||
import fs from 'node:fs'
|
||||
import fsPromise from 'node:fs/promises'
|
||||
import path from 'node:path'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { log, TEMP_DIR } from './index'
|
||||
import { dbUtil } from '../db'
|
||||
import * as fileType from 'file-type'
|
||||
import { net } from 'electron'
|
||||
import { randomUUID, createHash } from 'node:crypto'
|
||||
|
||||
export function isGIF(path: string) {
|
||||
const buffer = Buffer.alloc(4)
|
||||
@@ -28,7 +25,7 @@ export function checkFileReceived(path: string, timeout: number = 3000): Promise
|
||||
} else if (Date.now() - startTime > timeout) {
|
||||
reject(new Error(`文件不存在: ${path}`))
|
||||
} else {
|
||||
setTimeout(check, 100)
|
||||
setTimeout(check, 200)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +34,6 @@ export function checkFileReceived(path: string, timeout: number = 3000): Promise
|
||||
}
|
||||
|
||||
export async function file2base64(path: string) {
|
||||
const readFile = util.promisify(fs.readFile)
|
||||
let result = {
|
||||
err: '',
|
||||
data: '',
|
||||
@@ -53,10 +49,10 @@ export async function file2base64(path: string) {
|
||||
result.err = e.toString()
|
||||
return result
|
||||
}
|
||||
const data = await readFile(path)
|
||||
const data = await fsPromise.readFile(path)
|
||||
// 转换为Base64编码
|
||||
result.data = data.toString('base64')
|
||||
} catch (err) {
|
||||
} catch (err: any) {
|
||||
result.err = err.toString()
|
||||
}
|
||||
return result
|
||||
@@ -66,7 +62,7 @@ export function calculateFileMD5(filePath: string): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
// 创建一个流式读取器
|
||||
const stream = fs.createReadStream(filePath)
|
||||
const hash = crypto.createHash('md5')
|
||||
const hash = createHash('md5')
|
||||
|
||||
stream.on('data', (data: Buffer) => {
|
||||
// 当读取到数据时,更新哈希对象的状态
|
||||
@@ -91,7 +87,6 @@ export interface HttpDownloadOptions {
|
||||
headers?: Record<string, string> | string
|
||||
}
|
||||
export async function httpDownload(options: string | HttpDownloadOptions): Promise<Buffer> {
|
||||
let chunks: Buffer[] = []
|
||||
let url: string
|
||||
let headers: Record<string, string> = {
|
||||
'User-Agent':
|
||||
@@ -109,12 +104,10 @@ export async function httpDownload(options: string | HttpDownloadOptions): Promi
|
||||
}
|
||||
}
|
||||
}
|
||||
const fetchRes = await net.fetch(url, headers)
|
||||
const fetchRes = await fetch(url, { headers })
|
||||
if (!fetchRes.ok) throw new Error(`下载文件失败: ${fetchRes.statusText}`)
|
||||
|
||||
const blob = await fetchRes.blob()
|
||||
let buffer = await blob.arrayBuffer()
|
||||
return Buffer.from(buffer)
|
||||
return Buffer.from(await fetchRes.arrayBuffer())
|
||||
}
|
||||
|
||||
type Uri2LocalRes = {
|
||||
@@ -126,7 +119,7 @@ type Uri2LocalRes = {
|
||||
isLocal: boolean
|
||||
}
|
||||
|
||||
export async function uri2local(uri: string, fileName: string = null): Promise<Uri2LocalRes> {
|
||||
export async function uri2local(uri: string, fileName: string | null = null): Promise<Uri2LocalRes> {
|
||||
let res = {
|
||||
success: false,
|
||||
errMsg: '',
|
||||
@@ -136,13 +129,13 @@ export async function uri2local(uri: string, fileName: string = null): Promise<U
|
||||
isLocal: false,
|
||||
}
|
||||
if (!fileName) {
|
||||
fileName = uuidv4()
|
||||
fileName = randomUUID()
|
||||
}
|
||||
let filePath = path.join(TEMP_DIR, fileName)
|
||||
let url = null
|
||||
let url: URL | null = null
|
||||
try {
|
||||
url = new URL(uri)
|
||||
} catch (e) {
|
||||
} catch (e: any) {
|
||||
res.errMsg = `uri ${uri} 解析失败,` + e.toString() + ` 可能${uri}不存在`
|
||||
return res
|
||||
}
|
||||
@@ -153,17 +146,17 @@ export async function uri2local(uri: string, fileName: string = null): Promise<U
|
||||
let base64Data = uri.split('base64://')[1]
|
||||
try {
|
||||
const buffer = Buffer.from(base64Data, 'base64')
|
||||
fs.writeFileSync(filePath, buffer)
|
||||
await fsPromise.writeFile(filePath, buffer)
|
||||
} catch (e: any) {
|
||||
res.errMsg = `base64文件下载失败,` + e.toString()
|
||||
return res
|
||||
}
|
||||
} else if (url.protocol == 'http:' || url.protocol == 'https:') {
|
||||
// 下载文件
|
||||
let buffer: Buffer = null
|
||||
let buffer: Buffer | null = null
|
||||
try {
|
||||
buffer = await httpDownload(uri)
|
||||
} catch (e) {
|
||||
} catch (e: any) {
|
||||
res.errMsg = `${url}下载失败,` + e.toString()
|
||||
return res
|
||||
}
|
||||
@@ -176,9 +169,10 @@ export async function uri2local(uri: string, fileName: string = null): Promise<U
|
||||
// res.ext = pathInfo.ext
|
||||
}
|
||||
}
|
||||
fileName = fileName.replace(/[/\\:*?"<>|]/g, '_')
|
||||
res.fileName = fileName
|
||||
filePath = path.join(TEMP_DIR, uuidv4() + fileName)
|
||||
fs.writeFileSync(filePath, buffer)
|
||||
filePath = path.join(TEMP_DIR, randomUUID() + fileName)
|
||||
await fsPromise.writeFile(filePath, buffer)
|
||||
} catch (e: any) {
|
||||
res.errMsg = `${url}下载失败,` + e.toString()
|
||||
return res
|
||||
@@ -214,10 +208,10 @@ export async function uri2local(uri: string, fileName: string = null): Promise<U
|
||||
// }
|
||||
if (!res.isLocal && !res.ext) {
|
||||
try {
|
||||
let ext: string = (await fileType.fileTypeFromFile(filePath)).ext
|
||||
const ext = (await fileType.fileTypeFromFile(filePath))?.ext
|
||||
if (ext) {
|
||||
log('获取文件类型', ext, filePath)
|
||||
fs.renameSync(filePath, filePath + `.${ext}`)
|
||||
await fsPromise.rename(filePath, filePath + `.${ext}`)
|
||||
filePath += `.${ext}`
|
||||
res.fileName += `.${ext}`
|
||||
res.ext = ext
|
||||
|
@@ -41,7 +41,7 @@ export function mergeNewProperties(newObj: any, oldObj: any) {
|
||||
})
|
||||
}
|
||||
|
||||
export function isNull(value: any) {
|
||||
export function isNull(value: unknown) {
|
||||
return value === undefined || value === null
|
||||
}
|
||||
|
||||
@@ -65,3 +65,82 @@ export function wrapText(str: string, maxLength: number): string {
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 函数缓存装饰器,根据方法名、参数、自定义key生成缓存键,在一定时间内返回缓存结果
|
||||
* @param ttl 超时时间,单位毫秒
|
||||
* @param customKey 自定义缓存键前缀,可为空,防止方法名参数名一致时导致缓存键冲突
|
||||
* @returns 处理后缓存或调用原方法的结果
|
||||
*/
|
||||
export function cacheFunc(ttl: number, customKey: string = '') {
|
||||
const cache = new Map<string, { expiry: number; value: any }>()
|
||||
|
||||
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor): PropertyDescriptor {
|
||||
const originalMethod = descriptor.value
|
||||
const className = target.constructor.name // 获取类名
|
||||
const methodName = propertyKey // 获取方法名
|
||||
descriptor.value = async function (...args: any[]) {
|
||||
const cacheKey = `${customKey}${className}.${methodName}:${JSON.stringify(args)}`
|
||||
const cached = cache.get(cacheKey)
|
||||
if (cached && cached.expiry > Date.now()) {
|
||||
return cached.value
|
||||
} else {
|
||||
const result = await originalMethod.apply(this, args)
|
||||
cache.set(cacheKey, { value: result, expiry: Date.now() + ttl })
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
return descriptor
|
||||
}
|
||||
}
|
||||
|
||||
export function CacheClassFuncAsync(ttl = 3600 * 1000, customKey = '') {
|
||||
function logExecutionTime(target: any, methodName: string, descriptor: PropertyDescriptor) {
|
||||
const cache = new Map<string, { expiry: number; value: any }>()
|
||||
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)
|
||||
cache.set(key, { expiry: Date.now() + ttl, value: result })
|
||||
return result
|
||||
}
|
||||
}
|
||||
return logExecutionTime
|
||||
}
|
||||
|
||||
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<string, { expiry: number; value: any }>()
|
||||
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
|
||||
}
|
@@ -5,7 +5,7 @@ export * from './file'
|
||||
export * from './helper'
|
||||
export * from './log'
|
||||
export * from './qqlevel'
|
||||
export * from './qqpkg'
|
||||
export * from './QQBasicInfo'
|
||||
export * from './upgrade'
|
||||
export const DATA_DIR = global.LiteLoader.plugins['LLOneBot'].path.data
|
||||
export const TEMP_DIR = path.join(DATA_DIR, 'temp')
|
||||
@@ -15,4 +15,4 @@ if (!fs.existsSync(TEMP_DIR)) {
|
||||
}
|
||||
export { getVideoInfo } from './video'
|
||||
export { checkFfmpeg } from './video'
|
||||
export { encodeSilk } from './audio'
|
||||
export { encodeSilk } from './audio'
|
@@ -1,4 +1,4 @@
|
||||
import { selfInfo } from '../data'
|
||||
import { getSelfInfo } from '../data'
|
||||
import fs from 'fs'
|
||||
import path from 'node:path'
|
||||
import { DATA_DIR, truncateString } from './index'
|
||||
@@ -15,7 +15,7 @@ export function log(...msg: any[]) {
|
||||
if (!getConfigUtil().getConfig().log) {
|
||||
return //console.log(...msg);
|
||||
}
|
||||
|
||||
const selfInfo = getSelfInfo()
|
||||
const userInfo = selfInfo.uin ? `${selfInfo.nick}(${selfInfo.uin})` : ''
|
||||
let logMsg = ''
|
||||
for (let msgItem of msg) {
|
||||
@@ -31,5 +31,5 @@ export function log(...msg: any[]) {
|
||||
logMsg = `${currentDateTime} ${userInfo}: ${logMsg}\n\n`
|
||||
// sendLog(...msg);
|
||||
// console.log(msg)
|
||||
fs.appendFile(path.join(logDir, logFileName), logMsg, (err: any) => {})
|
||||
fs.appendFile(path.join(logDir, logFileName), logMsg, () => {})
|
||||
}
|
||||
|
@@ -1,12 +0,0 @@
|
||||
import path from 'path'
|
||||
|
||||
type QQPkgInfo = {
|
||||
version: string
|
||||
buildVersion: string
|
||||
platform: string
|
||||
eleArch: string
|
||||
}
|
||||
|
||||
export const qqPkgInfo: QQPkgInfo = require(path.join(process.resourcesPath, 'app/package.json'))
|
||||
|
||||
export const isQQ998: boolean = qqPkgInfo.buildVersion >= '22106'
|
107
src/common/utils/request.ts
Normal file
107
src/common/utils/request.ts
Normal file
@@ -0,0 +1,107 @@
|
||||
import https from 'node:https';
|
||||
import http from 'node:http';
|
||||
import { log } from '@/common/utils/log'
|
||||
|
||||
export class RequestUtil {
|
||||
// 适用于获取服务器下发cookies时获取,仅GET
|
||||
static async HttpsGetCookies(url: string): Promise<{ [key: string]: string }> {
|
||||
const client = url.startsWith('https') ? https : http;
|
||||
return new Promise((resolve, reject) => {
|
||||
client.get(url, (res) => {
|
||||
let cookies: { [key: string]: string } = {};
|
||||
const handleRedirect = (res: http.IncomingMessage) => {
|
||||
//console.log(res.headers.location);
|
||||
if (res.statusCode === 301 || res.statusCode === 302) {
|
||||
if (res.headers.location) {
|
||||
const redirectUrl = new URL(res.headers.location, url);
|
||||
RequestUtil.HttpsGetCookies(redirectUrl.href).then((redirectCookies) => {
|
||||
// 合并重定向过程中的cookies
|
||||
log('redirectCookies', redirectCookies)
|
||||
cookies = { ...cookies, ...redirectCookies };
|
||||
resolve(cookies);
|
||||
});
|
||||
} else {
|
||||
resolve(cookies);
|
||||
}
|
||||
} else {
|
||||
resolve(cookies);
|
||||
}
|
||||
};
|
||||
res.on('data', () => { }); // Necessary to consume the stream
|
||||
res.on('end', () => {
|
||||
handleRedirect(res);
|
||||
});
|
||||
if (res.headers['set-cookie']) {
|
||||
// console.log(res.headers['set-cookie']);
|
||||
log('set-cookie', url, res.headers['set-cookie']);
|
||||
res.headers['set-cookie'].forEach((cookie) => {
|
||||
const parts = cookie.split(';')[0].split('=');
|
||||
const key = parts[0];
|
||||
const value = parts[1];
|
||||
if (key && value && key.length > 0 && value.length > 0) {
|
||||
cookies[key] = value;
|
||||
}
|
||||
});
|
||||
}
|
||||
}).on('error', (err) => {
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 请求和回复都是JSON data传原始内容 自动编码json
|
||||
static async HttpGetJson<T>(url: string, method: string = 'GET', data?: any, headers: Record<string, string> = {}, isJsonRet: boolean = true, isArgJson: boolean = true): Promise<T> {
|
||||
let option = new URL(url);
|
||||
const protocol = url.startsWith('https://') ? https : http;
|
||||
const options = {
|
||||
hostname: option.hostname,
|
||||
port: option.port,
|
||||
path: option.href,
|
||||
method: method,
|
||||
headers: headers
|
||||
};
|
||||
return new Promise((resolve, reject) => {
|
||||
const req = protocol.request(options, (res: any) => {
|
||||
let responseBody = '';
|
||||
res.on('data', (chunk: string | Buffer) => {
|
||||
responseBody += chunk.toString();
|
||||
});
|
||||
|
||||
res.on('end', () => {
|
||||
try {
|
||||
if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
|
||||
if (isJsonRet) {
|
||||
const responseJson = JSON.parse(responseBody);
|
||||
resolve(responseJson as T);
|
||||
} else {
|
||||
resolve(responseBody as T);
|
||||
}
|
||||
} else {
|
||||
reject(new Error(`Unexpected status code: ${res.statusCode}`));
|
||||
}
|
||||
} catch (parseError) {
|
||||
reject(parseError);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
req.on('error', (error: any) => {
|
||||
reject(error);
|
||||
});
|
||||
if (method === 'POST' || method === 'PUT' || method === 'PATCH') {
|
||||
if (isArgJson) {
|
||||
req.write(JSON.stringify(data));
|
||||
} else {
|
||||
req.write(data);
|
||||
}
|
||||
|
||||
}
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
// 请求返回都是原始内容
|
||||
static async HttpGetText(url: string, method: string = 'GET', data?: any, headers: Record<string, string> = {}) {
|
||||
return this.HttpGetJson<string>(url, method, data, headers, false, false);
|
||||
}
|
||||
}
|
10
src/common/utils/system.ts
Normal file
10
src/common/utils/system.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
|
||||
export const systemPlatform = os.platform();
|
||||
export const cpuArch = os.arch();
|
||||
export const systemVersion = os.release();
|
||||
// export const hostname = os.hostname(); // win7不支持
|
||||
const homeDir = os.homedir();
|
||||
export const downloadsPath = path.join(homeDir, 'Downloads');
|
||||
export const systemName = os.type();
|
71
src/common/utils/table.ts
Normal file
71
src/common/utils/table.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
export class LimitedHashTable<K, V> {
|
||||
private keyToValue: Map<K, V> = new Map()
|
||||
private valueToKey: Map<V, K> = 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
|
||||
}
|
||||
}
|
@@ -5,7 +5,7 @@ import { copyFolder, httpDownload, log, PLUGIN_DIR, TEMP_DIR } from '.'
|
||||
import compressing from 'compressing'
|
||||
|
||||
const downloadMirrorHosts = ['https://mirror.ghproxy.com/']
|
||||
const checkVersionMirrorHosts = ['https://521github.com']
|
||||
const checkVersionMirrorHosts = ['https://kkgithub.com']
|
||||
|
||||
export async function checkNewVersion() {
|
||||
const latestVersionText = await getRemoteVersion()
|
||||
@@ -91,7 +91,7 @@ export async function getRemoteVersionByMirror(mirrorGithub: string) {
|
||||
releasePage = (await httpDownload(mirrorGithub + '/LLOneBot/LLOneBot/releases')).toString()
|
||||
// log("releasePage", releasePage);
|
||||
if (releasePage === 'error') return ''
|
||||
return releasePage.match(new RegExp('(?<=(tag/v)).*?(?=("))'))[0]
|
||||
return releasePage.match(new RegExp('(?<=(tag/v)).*?(?=("))'))?.[0]
|
||||
} catch {}
|
||||
return ''
|
||||
}
|
||||
|
@@ -31,10 +31,10 @@ export async function getVideoInfo(filePath: string) {
|
||||
console.log('未找到视频流信息。')
|
||||
}
|
||||
resolve({
|
||||
width: videoStream.width,
|
||||
height: videoStream.height,
|
||||
time: parseInt(videoStream.duration),
|
||||
format: metadata.format.format_name,
|
||||
width: videoStream?.width!,
|
||||
height: videoStream?.height!,
|
||||
time: parseInt(videoStream?.duration!),
|
||||
format: metadata.format.format_name!,
|
||||
size,
|
||||
filePath,
|
||||
})
|
||||
@@ -67,7 +67,7 @@ export async function encodeMp4(filePath: string) {
|
||||
return videoInfo
|
||||
}
|
||||
|
||||
export function checkFfmpeg(newPath: string = null): Promise<boolean> {
|
||||
export function checkFfmpeg(newPath: string | null = null): Promise<boolean> {
|
||||
return new Promise((resolve, reject) => {
|
||||
log('开始检查ffmpeg', newPath)
|
||||
if (newPath) {
|
||||
|
2
src/global.d.ts
vendored
2
src/global.d.ts
vendored
@@ -3,6 +3,6 @@ import { type LLOneBot } from './preload'
|
||||
declare global {
|
||||
interface Window {
|
||||
llonebot: LLOneBot
|
||||
LiteLoader: any
|
||||
LiteLoader: Record<string, any>
|
||||
}
|
||||
}
|
||||
|
326
src/main/main.ts
326
src/main/main.ts
@@ -1,7 +1,7 @@
|
||||
// 运行在 Electron 主进程 下的插件入口
|
||||
|
||||
import { BrowserWindow, dialog, ipcMain } from 'electron'
|
||||
import * as fs from 'node:fs'
|
||||
import fs from 'node:fs'
|
||||
import { Config } from '../common/types'
|
||||
import {
|
||||
CHANNEL_CHECK_VERSION,
|
||||
@@ -15,54 +15,44 @@ import {
|
||||
import { ob11WebsocketServer } from '../onebot11/server/ws/WebsocketServer'
|
||||
import { DATA_DIR } from '../common/utils'
|
||||
import {
|
||||
friendRequests,
|
||||
getFriend,
|
||||
getGroup,
|
||||
getGroupMember,
|
||||
groups,
|
||||
llonebotError,
|
||||
refreshGroupMembers,
|
||||
selfInfo,
|
||||
uidMaps,
|
||||
setSelfInfo,
|
||||
getSelfInfo,
|
||||
getSelfUid,
|
||||
getSelfUin
|
||||
} from '../common/data'
|
||||
import { hookNTQQApiCall, hookNTQQApiReceive, ReceiveCmdS, registerReceiveHook } from '../ntqqapi/hook'
|
||||
import { hookNTQQApiCall, hookNTQQApiReceive, ReceiveCmdS, registerReceiveHook, startHook } from '../ntqqapi/hook'
|
||||
import { OB11Constructor } from '../onebot11/constructor'
|
||||
import {
|
||||
ChatType,
|
||||
FriendRequestNotify,
|
||||
GroupMemberRole,
|
||||
GroupNotifies,
|
||||
GroupNotifyTypes,
|
||||
RawMessage,
|
||||
BuddyReqType,
|
||||
} from '../ntqqapi/types'
|
||||
import { httpHeart, ob11HTTPServer } from '../onebot11/server/http'
|
||||
import { OB11FriendRecallNoticeEvent } from '../onebot11/event/notice/OB11FriendRecallNoticeEvent'
|
||||
import { OB11GroupRecallNoticeEvent } from '../onebot11/event/notice/OB11GroupRecallNoticeEvent'
|
||||
import { postOB11Event } from '../onebot11/server/postOB11Event'
|
||||
import { postOb11Event } from '../onebot11/server/post-ob11-event'
|
||||
import { ob11ReverseWebsockets } from '../onebot11/server/ws/ReverseWebsocket'
|
||||
import { OB11GroupAdminNoticeEvent } from '../onebot11/event/notice/OB11GroupAdminNoticeEvent'
|
||||
import { OB11GroupRequestEvent } from '../onebot11/event/request/OB11GroupRequest'
|
||||
import { OB11FriendRequestEvent } from '../onebot11/event/request/OB11FriendRequest'
|
||||
import * as path from 'node:path'
|
||||
import path from 'node:path'
|
||||
import { dbUtil } from '../common/db'
|
||||
import { setConfig } from './setConfig'
|
||||
import { NTQQUserApi } from '../ntqqapi/api/user'
|
||||
import { NTQQGroupApi } from '../ntqqapi/api/group'
|
||||
import { crychic } from '../ntqqapi/external/crychic'
|
||||
import { OB11FriendPokeEvent, OB11GroupPokeEvent } from '../onebot11/event/notice/OB11PokeEvent'
|
||||
import { NTQQUserApi, NTQQGroupApi } from '../ntqqapi/api'
|
||||
import { checkNewVersion, upgradeLLOneBot } from '../common/utils/upgrade'
|
||||
import { log } from '../common/utils/log'
|
||||
import { getConfigUtil } from '../common/config'
|
||||
import { checkFfmpeg } from '../common/utils/video'
|
||||
import { GroupDecreaseSubType, OB11GroupDecreaseEvent } from '../onebot11/event/notice/OB11GroupDecreaseEvent'
|
||||
|
||||
let running = false
|
||||
import '../ntqqapi/wrapper'
|
||||
import { NTEventDispatch } from '../common/utils/EventTask'
|
||||
import { wrapperConstructor, getSession } from '../ntqqapi/wrapper'
|
||||
|
||||
let mainWindow: BrowserWindow | null = null
|
||||
|
||||
// 加载插件时触发
|
||||
function onLoad() {
|
||||
log('llonebot main onLoad')
|
||||
ipcMain.handle(CHANNEL_CHECK_VERSION, async (event, arg) => {
|
||||
return checkNewVersion()
|
||||
})
|
||||
@@ -126,7 +116,7 @@ function onLoad() {
|
||||
return
|
||||
}
|
||||
dialog
|
||||
.showMessageBox(mainWindow, {
|
||||
.showMessageBox(mainWindow!, {
|
||||
type: 'question',
|
||||
buttons: ['确认', '取消'],
|
||||
defaultId: 0, // 默认选中的按钮,0 代表第一个按钮,即 "确认"
|
||||
@@ -141,7 +131,8 @@ function onLoad() {
|
||||
.catch((e) => {
|
||||
log('保存设置失败', e.stack)
|
||||
})
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
@@ -168,95 +159,76 @@ function onLoad() {
|
||||
|
||||
OB11Constructor.message(message)
|
||||
.then((msg) => {
|
||||
if (debug) {
|
||||
msg.raw = message
|
||||
} else {
|
||||
if (msg.message.length === 0) {
|
||||
return
|
||||
}
|
||||
if (!debug && msg.message.length === 0) {
|
||||
return
|
||||
}
|
||||
const isSelfMsg = msg.user_id.toString() == selfInfo.uin
|
||||
const isSelfMsg = msg.user_id.toString() === getSelfUin()
|
||||
if (isSelfMsg && !reportSelfMessage) {
|
||||
return
|
||||
}
|
||||
if (isSelfMsg) {
|
||||
msg.target_id = parseInt(message.peerUin)
|
||||
}
|
||||
postOB11Event(msg)
|
||||
postOb11Event(msg)
|
||||
// log("post msg", msg)
|
||||
})
|
||||
.catch((e) => log('constructMessage error: ', e.stack.toString()))
|
||||
OB11Constructor.GroupEvent(message).then((groupEvent) => {
|
||||
if (groupEvent) {
|
||||
// log("post group event", groupEvent);
|
||||
postOB11Event(groupEvent)
|
||||
postOb11Event(groupEvent)
|
||||
}
|
||||
})
|
||||
OB11Constructor.FriendAddEvent(message).then((friendAddEvent) => {
|
||||
if (friendAddEvent) {
|
||||
// log("post friend add event", friendAddEvent);
|
||||
postOB11Event(friendAddEvent)
|
||||
OB11Constructor.PrivateEvent(message).then((privateEvent) => {
|
||||
log(message)
|
||||
if (privateEvent) {
|
||||
// log("post private event", privateEvent);
|
||||
postOb11Event(privateEvent)
|
||||
}
|
||||
})
|
||||
// OB11Constructor.FriendAddEvent(message).then((friendAddEvent) => {
|
||||
// log(message)
|
||||
// if (friendAddEvent) {
|
||||
// // log("post friend add event", friendAddEvent);
|
||||
// postOb11Event(friendAddEvent)
|
||||
// }
|
||||
// })
|
||||
}
|
||||
}
|
||||
|
||||
async function startReceiveHook() {
|
||||
if (getConfigUtil().getConfig().enablePoke) {
|
||||
crychic.loadNode()
|
||||
crychic.registerPokeHandler((id, isGroup) => {
|
||||
log(`收到戳一戳消息了!是否群聊:${isGroup},id:${id}`)
|
||||
let pokeEvent: OB11FriendPokeEvent | OB11GroupPokeEvent
|
||||
if (isGroup) {
|
||||
pokeEvent = new OB11GroupPokeEvent(parseInt(id))
|
||||
} else {
|
||||
pokeEvent = new OB11FriendPokeEvent(parseInt(id))
|
||||
}
|
||||
postOB11Event(pokeEvent)
|
||||
})
|
||||
}
|
||||
startHook()
|
||||
registerReceiveHook<{
|
||||
msgList: Array<RawMessage>
|
||||
}>([ReceiveCmdS.NEW_MSG, ReceiveCmdS.NEW_ACTIVE_MSG], async (payload) => {
|
||||
try {
|
||||
await postReceiveMsg(payload.msgList)
|
||||
} catch (e) {
|
||||
} catch (e: any) {
|
||||
log('report message error: ', e.stack.toString())
|
||||
}
|
||||
})
|
||||
const recallMsgIds: string[] = [] // 避免重复上报
|
||||
registerReceiveHook<{ msgList: Array<RawMessage> }>([ReceiveCmdS.UPDATE_MSG], async (payload) => {
|
||||
for (const message of payload.msgList) {
|
||||
// log("message update", message)
|
||||
log('message update', message.msgId, message)
|
||||
if (message.recallTime != '0') {
|
||||
//todo: 这个判断方法不太好,应该使用灰色消息元素来判断
|
||||
// 撤回消息上报
|
||||
if (recallMsgIds.includes(message.msgId)) {
|
||||
continue
|
||||
}
|
||||
recallMsgIds.push(message.msgId)
|
||||
const oriMessage = await dbUtil.getMsgByLongId(message.msgId)
|
||||
if (!oriMessage) {
|
||||
continue
|
||||
}
|
||||
oriMessage.recallTime = message.recallTime
|
||||
dbUtil.updateMsg(oriMessage).then()
|
||||
if (message.chatType == ChatType.friend) {
|
||||
const friendRecallEvent = new OB11FriendRecallNoticeEvent(
|
||||
parseInt(message.senderUin),
|
||||
oriMessage.msgShortId,
|
||||
)
|
||||
postOB11Event(friendRecallEvent)
|
||||
} else if (message.chatType == ChatType.group) {
|
||||
let operatorId = message.senderUin
|
||||
for (const element of message.elements) {
|
||||
const operatorUid = element.grayTipElement?.revokeElement.operatorUid
|
||||
const operator = await getGroupMember(message.peerUin, operatorUid)
|
||||
operatorId = operator.uin
|
||||
message.msgShortId = oriMessage.msgShortId
|
||||
OB11Constructor.RecallEvent(message).then((recallEvent) => {
|
||||
if (recallEvent) {
|
||||
log('post recall event', recallEvent)
|
||||
postOb11Event(recallEvent)
|
||||
}
|
||||
const groupRecallEvent = new OB11GroupRecallNoticeEvent(
|
||||
parseInt(message.peerUin),
|
||||
parseInt(message.senderUin),
|
||||
parseInt(operatorId),
|
||||
oriMessage.msgShortId,
|
||||
)
|
||||
postOB11Event(groupRecallEvent)
|
||||
}
|
||||
})
|
||||
// 不让入库覆盖原来消息,不然就获取不到撤回的消息内容了
|
||||
continue
|
||||
}
|
||||
@@ -271,7 +243,7 @@ function onLoad() {
|
||||
// log("reportSelfMessage", payload)
|
||||
try {
|
||||
await postReceiveMsg([payload.msgRecord])
|
||||
} catch (e) {
|
||||
} catch (e: any) {
|
||||
log('report self message error: ', e.stack.toString())
|
||||
}
|
||||
})
|
||||
@@ -307,27 +279,8 @@ function onLoad() {
|
||||
}
|
||||
log('收到群通知', notify)
|
||||
await dbUtil.addGroupNotify(notify)
|
||||
// let member2: GroupMember;
|
||||
// if (notify.user2.uid) {
|
||||
// member2 = await getGroupMember(notify.group.groupCode, null, notify.user2.uid);
|
||||
// }
|
||||
if ([GroupNotifyTypes.ADMIN_SET, GroupNotifyTypes.ADMIN_UNSET, GroupNotifyTypes.ADMIN_UNSET_OTHER].includes(notify.type)) {
|
||||
const member1 = await getGroupMember(notify.group.groupCode, notify.user1.uid)
|
||||
log('有管理员变动通知')
|
||||
refreshGroupMembers(notify.group.groupCode).then()
|
||||
let groupAdminNoticeEvent = new OB11GroupAdminNoticeEvent()
|
||||
groupAdminNoticeEvent.group_id = parseInt(notify.group.groupCode)
|
||||
log('开始获取变动的管理员')
|
||||
if (member1) {
|
||||
log('变动管理员获取成功')
|
||||
groupAdminNoticeEvent.user_id = parseInt(member1.uin)
|
||||
groupAdminNoticeEvent.sub_type = [GroupNotifyTypes.ADMIN_UNSET, GroupNotifyTypes.ADMIN_UNSET_OTHER].includes(notify.type) ? 'unset' : 'set'
|
||||
// member1.role = notify.type == GroupNotifyTypes.ADMIN_SET ? GroupMemberRole.admin : GroupMemberRole.normal;
|
||||
postOB11Event(groupAdminNoticeEvent, true)
|
||||
} else {
|
||||
log('获取群通知的成员信息失败', notify, getGroup(notify.group.groupCode))
|
||||
}
|
||||
} else if (notify.type == GroupNotifyTypes.MEMBER_EXIT || notify.type == GroupNotifyTypes.KICK_MEMBER) {
|
||||
const flag = notify.group.groupCode + '|' + notify.seq + '|' + notify.type
|
||||
if (notify.type == GroupNotifyTypes.MEMBER_EXIT || notify.type == GroupNotifyTypes.KICK_MEMBER) {
|
||||
log('有成员退出通知', notify)
|
||||
try {
|
||||
const member1 = await NTQQUserApi.getUserDetailInfo(notify.user1.uid)
|
||||
@@ -336,7 +289,7 @@ function onLoad() {
|
||||
if (notify.user2.uid) {
|
||||
// 是被踢的
|
||||
const member2 = await getGroupMember(notify.group.groupCode, notify.user2.uid)
|
||||
operatorId = member2.uin
|
||||
operatorId = member2?.uin!
|
||||
subType = 'kick'
|
||||
}
|
||||
let groupDecreaseEvent = new OB11GroupDecreaseEvent(
|
||||
@@ -345,82 +298,113 @@ function onLoad() {
|
||||
parseInt(operatorId),
|
||||
subType,
|
||||
)
|
||||
postOB11Event(groupDecreaseEvent, true)
|
||||
} catch (e) {
|
||||
postOb11Event(groupDecreaseEvent, true)
|
||||
} catch (e: any) {
|
||||
log('获取群通知的成员信息失败', notify, e.stack.toString())
|
||||
}
|
||||
} else if ([GroupNotifyTypes.JOIN_REQUEST].includes(notify.type)) {
|
||||
}
|
||||
else if ([GroupNotifyTypes.JOIN_REQUEST, GroupNotifyTypes.JOIN_REQUEST_BY_INVITED].includes(notify.type)) {
|
||||
log('有加群请求')
|
||||
let groupRequestEvent = new OB11GroupRequestEvent()
|
||||
groupRequestEvent.group_id = parseInt(notify.group.groupCode)
|
||||
let requestQQ = ''
|
||||
try {
|
||||
requestQQ = (await NTQQUserApi.getUserDetailInfo(notify.user1.uid)).uin
|
||||
// uid-->uin
|
||||
requestQQ = (await NTQQUserApi.getUinByUid(notify.user1.uid))
|
||||
if (isNaN(parseInt(requestQQ))) {
|
||||
requestQQ = (await NTQQUserApi.getUserDetailInfo(notify.user1.uid)).uin
|
||||
}
|
||||
} catch (e) {
|
||||
log('获取加群人QQ号失败', e)
|
||||
log('获取加群人QQ号失败 Uid:', notify.user1.uid, e)
|
||||
}
|
||||
groupRequestEvent.user_id = parseInt(requestQQ) || 0
|
||||
groupRequestEvent.sub_type = 'add'
|
||||
groupRequestEvent.comment = notify.postscript
|
||||
groupRequestEvent.flag = notify.seq
|
||||
postOB11Event(groupRequestEvent)
|
||||
} else if (notify.type == GroupNotifyTypes.INVITE_ME) {
|
||||
log('收到邀请我加群通知')
|
||||
let groupInviteEvent = new OB11GroupRequestEvent()
|
||||
groupInviteEvent.group_id = parseInt(notify.group.groupCode)
|
||||
let user_id = (await getFriend(notify.user2.uid))?.uin
|
||||
if (!user_id) {
|
||||
user_id = (await NTQQUserApi.getUserDetailInfo(notify.user2.uid))?.uin
|
||||
let invitorId: string
|
||||
if (notify.type == GroupNotifyTypes.JOIN_REQUEST_BY_INVITED) {
|
||||
// groupRequestEvent.sub_type = 'invite'
|
||||
try {
|
||||
// uid-->uin
|
||||
invitorId = (await NTQQUserApi.getUinByUid(notify.user2.uid))
|
||||
if (isNaN(parseInt(invitorId))) {
|
||||
invitorId = (await NTQQUserApi.getUserDetailInfo(notify.user2.uid)).uin
|
||||
}
|
||||
} catch (e) {
|
||||
invitorId = ''
|
||||
log('获取邀请人QQ号失败 Uid:', notify.user2.uid, e)
|
||||
}
|
||||
}
|
||||
groupInviteEvent.user_id = parseInt(user_id)
|
||||
groupInviteEvent.sub_type = 'invite'
|
||||
groupInviteEvent.flag = notify.seq
|
||||
postOB11Event(groupInviteEvent)
|
||||
const groupRequestEvent = new OB11GroupRequestEvent(
|
||||
parseInt(notify.group.groupCode),
|
||||
parseInt(requestQQ) || 0,
|
||||
flag,
|
||||
notify.postscript,
|
||||
invitorId! === undefined ? undefined : +invitorId,
|
||||
'add'
|
||||
)
|
||||
postOb11Event(groupRequestEvent)
|
||||
}
|
||||
} catch (e) {
|
||||
else if (notify.type == GroupNotifyTypes.INVITE_ME) {
|
||||
log('收到邀请我加群通知')
|
||||
const userId = (await NTQQUserApi.getUinByUid(notify.user2.uid)) || ''
|
||||
const groupInviteEvent = new OB11GroupRequestEvent(
|
||||
parseInt(notify.group.groupCode),
|
||||
parseInt(userId),
|
||||
flag,
|
||||
undefined,
|
||||
undefined,
|
||||
'invite'
|
||||
)
|
||||
postOb11Event(groupInviteEvent)
|
||||
}
|
||||
} catch (e: any) {
|
||||
log('解析群通知失败', e.stack.toString())
|
||||
}
|
||||
}
|
||||
} else if (payload.doubt) {
|
||||
}
|
||||
else if (payload.doubt) {
|
||||
// 可能有群管理员变动
|
||||
}
|
||||
})
|
||||
|
||||
registerReceiveHook<FriendRequestNotify>(ReceiveCmdS.FRIEND_REQUEST, async (payload) => {
|
||||
for (const req of payload.data.buddyReqs) {
|
||||
let flag = req.friendUid + req.reqTime
|
||||
if (req.isUnread && parseInt(req.reqTime) > startTime / 1000) {
|
||||
friendRequests[flag] = req
|
||||
log('有新的好友请求', req)
|
||||
let friendRequestEvent = new OB11FriendRequestEvent()
|
||||
try {
|
||||
let requester = await NTQQUserApi.getUserDetailInfo(req.friendUid)
|
||||
friendRequestEvent.user_id = parseInt(requester.uin)
|
||||
} catch (e) {
|
||||
log('获取加好友者QQ号失败', e)
|
||||
}
|
||||
friendRequestEvent.flag = flag
|
||||
friendRequestEvent.comment = req.extWords
|
||||
postOB11Event(friendRequestEvent)
|
||||
if (!!req.isInitiator || (req.isDecide && req.reqType !== BuddyReqType.KMEINITIATORWAITPEERCONFIRM)) {
|
||||
continue
|
||||
}
|
||||
let userId = 0
|
||||
try {
|
||||
const requesterUin = await NTQQUserApi.getUinByUid(req.friendUid)
|
||||
userId = parseInt(requesterUin!)
|
||||
} catch (e) {
|
||||
log('获取加好友者QQ号失败', e)
|
||||
}
|
||||
const flag = req.friendUid + '|' + req.reqTime
|
||||
const comment = req.extWords
|
||||
const friendRequestEvent = new OB11FriendRequestEvent(
|
||||
userId,
|
||||
comment,
|
||||
flag
|
||||
)
|
||||
postOb11Event(friendRequestEvent)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
let startTime = 0 // 毫秒
|
||||
|
||||
async function start() {
|
||||
async function start(uid: string, uin: string) {
|
||||
log('llonebot pid', process.pid)
|
||||
const config = getConfigUtil().getConfig()
|
||||
if (!config.enableLLOB) {
|
||||
log('LLOneBot 开关设置为关闭,不启动LLOneBot')
|
||||
return
|
||||
}
|
||||
llonebotError.otherError = ''
|
||||
startTime = Date.now()
|
||||
dbUtil.getReceivedTempUinMap().then((m) => {
|
||||
for (const [key, value] of Object.entries(m)) {
|
||||
uidMaps[value] = key
|
||||
}
|
||||
})
|
||||
NTEventDispatch.init({ ListenerMap: wrapperConstructor, WrapperSession: getSession()! })
|
||||
dbUtil.init(uin)
|
||||
|
||||
log('start activate group member info')
|
||||
NTQQGroupApi.activateMemberInfoChange().then().catch(log)
|
||||
NTQQGroupApi.activateMemberListChange().then().catch(log)
|
||||
startReceiveHook().then()
|
||||
NTQQGroupApi.getGroups(true).then()
|
||||
const config = getConfigUtil().getConfig()
|
||||
|
||||
if (config.ob11.enableHttp) {
|
||||
ob11HTTPServer.start(config.ob11.httpPort)
|
||||
}
|
||||
@@ -437,53 +421,29 @@ function onLoad() {
|
||||
log('LLOneBot start')
|
||||
}
|
||||
|
||||
let getSelfNickCount = 0
|
||||
const init = async () => {
|
||||
try {
|
||||
log('start get self info')
|
||||
const _ = await NTQQUserApi.getSelfInfo()
|
||||
log('get self info api result:', _)
|
||||
Object.assign(selfInfo, _)
|
||||
selfInfo.nick = selfInfo.uin
|
||||
} catch (e) {
|
||||
log('retry get self info', e)
|
||||
const current = getSelfInfo()
|
||||
if (!current.uin) {
|
||||
setSelfInfo({
|
||||
uin: globalThis.authData?.uin,
|
||||
uid: globalThis.authData?.uid,
|
||||
nick: current.uin,
|
||||
})
|
||||
}
|
||||
if (!selfInfo.uin) {
|
||||
selfInfo.uin = globalThis.authData?.uin
|
||||
selfInfo.uid = globalThis.authData?.uid
|
||||
selfInfo.nick = selfInfo.uin
|
||||
//log('self info', selfInfo, globalThis.authData)
|
||||
if (current.uin) {
|
||||
start(current.uid, current.uin)
|
||||
}
|
||||
log('self info', selfInfo, globalThis.authData)
|
||||
if (selfInfo.uin) {
|
||||
async function getUserNick() {
|
||||
try {
|
||||
getSelfNickCount++
|
||||
const userInfo = await NTQQUserApi.getUserDetailInfo(selfInfo.uid)
|
||||
log('self info', userInfo)
|
||||
if (userInfo) {
|
||||
selfInfo.nick = userInfo.nick
|
||||
return
|
||||
}
|
||||
} catch (e) {
|
||||
log('get self nickname failed', e.stack)
|
||||
}
|
||||
if (getSelfNickCount < 10) {
|
||||
return setTimeout(getUserNick, 1000)
|
||||
}
|
||||
}
|
||||
|
||||
getUserNick().then()
|
||||
start().then()
|
||||
} else {
|
||||
else {
|
||||
setTimeout(init, 1000)
|
||||
}
|
||||
}
|
||||
setTimeout(init, 1000)
|
||||
init()
|
||||
}
|
||||
|
||||
// 创建窗口时触发
|
||||
function onBrowserWindowCreated(window: BrowserWindow) {
|
||||
if (selfInfo.uid) {
|
||||
if (getSelfUid()) {
|
||||
return
|
||||
}
|
||||
mainWindow = window
|
||||
@@ -491,7 +451,7 @@ function onBrowserWindowCreated(window: BrowserWindow) {
|
||||
try {
|
||||
hookNTQQApiCall(window)
|
||||
hookNTQQApiReceive(window)
|
||||
} catch (e) {
|
||||
} catch (e: any) {
|
||||
log('LLOneBot hook error: ', e.toString())
|
||||
}
|
||||
}
|
||||
|
@@ -7,220 +7,191 @@ import {
|
||||
ChatCacheList,
|
||||
ChatCacheListItemBasic,
|
||||
ChatType,
|
||||
ElementType, IMAGE_HTTP_HOST, IMAGE_HTTP_HOST_NT, RawMessage,
|
||||
ElementType,
|
||||
IMAGE_HTTP_HOST,
|
||||
IMAGE_HTTP_HOST_NT,
|
||||
PicElement,
|
||||
} from '../types'
|
||||
import path from 'path'
|
||||
import fs from 'fs'
|
||||
import path from 'node:path'
|
||||
import fs from 'node:fs'
|
||||
import { ReceiveCmdS } from '../hook'
|
||||
import { log } from '../../common/utils'
|
||||
import https from 'https'
|
||||
import { sleep } from '../../common/utils'
|
||||
import { hookApi } from '../external/moehook/hook'
|
||||
|
||||
let privateImageRKey = ''
|
||||
let groupImageRKey = ''
|
||||
let lastGetPrivateRKeyTime = 0
|
||||
let lastGetGroupRKeyTime = 0
|
||||
const rkeyExpireTime = 1000 * 60 * 30
|
||||
import { log, TEMP_DIR } from '@/common/utils'
|
||||
import { rkeyManager } from '@/ntqqapi/api/rkey'
|
||||
import { getSession } from '@/ntqqapi/wrapper'
|
||||
import { Peer } from '@/ntqqapi/types/msg'
|
||||
import { calculateFileMD5 } from '@/common/utils/file'
|
||||
import { fileTypeFromFile } from 'file-type'
|
||||
import fsPromise from 'node:fs/promises'
|
||||
import { NTEventDispatch } from '@/common/utils/EventTask'
|
||||
import { OnRichMediaDownloadCompleteParams } from '@/ntqqapi/listeners'
|
||||
|
||||
export class NTQQFileApi {
|
||||
static async getFileType(filePath: string) {
|
||||
return await callNTQQApi<{ ext: string }>({
|
||||
className: NTQQApiClass.FS_API, methodName: NTQQApiMethod.FILE_TYPE, args: [filePath],
|
||||
})
|
||||
static async getVideoUrl(peer: Peer, msgId: string, elementId: string): Promise<string> {
|
||||
const session = getSession()
|
||||
return (await session?.getRichMediaService().getVideoPlayUrlV2(peer,
|
||||
msgId,
|
||||
elementId,
|
||||
0,
|
||||
{ downSourceType: 1, triggerType: 1 }))?.urlResult?.domainUrl[0]?.url!
|
||||
}
|
||||
|
||||
static async getFileMd5(filePath: string) {
|
||||
return await callNTQQApi<string>({
|
||||
className: NTQQApiClass.FS_API,
|
||||
methodName: NTQQApiMethod.FILE_MD5,
|
||||
args: [filePath],
|
||||
})
|
||||
static async getFileType(filePath: string) {
|
||||
return fileTypeFromFile(filePath)
|
||||
}
|
||||
|
||||
static async copyFile(filePath: string, destPath: string) {
|
||||
return await callNTQQApi<string>({
|
||||
className: NTQQApiClass.FS_API,
|
||||
methodName: NTQQApiMethod.FILE_COPY,
|
||||
args: [{
|
||||
fromPath: filePath,
|
||||
toPath: destPath,
|
||||
}],
|
||||
args: [
|
||||
{
|
||||
fromPath: filePath,
|
||||
toPath: destPath,
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
static async getFileSize(filePath: string) {
|
||||
return await callNTQQApi<number>({
|
||||
className: NTQQApiClass.FS_API, methodName: NTQQApiMethod.FILE_SIZE, args: [filePath],
|
||||
className: NTQQApiClass.FS_API,
|
||||
methodName: NTQQApiMethod.FILE_SIZE,
|
||||
args: [filePath],
|
||||
})
|
||||
}
|
||||
|
||||
// 上传文件到QQ的文件夹
|
||||
static async uploadFile(filePath: string, elementType: ElementType = ElementType.PIC, elementSubType: number = 0) {
|
||||
const md5 = await NTQQFileApi.getFileMd5(filePath)
|
||||
let ext = (await NTQQFileApi.getFileType(filePath))?.ext
|
||||
static async uploadFile(filePath: string, elementType: ElementType = ElementType.PIC, elementSubType = 0) {
|
||||
const fileMd5 = await calculateFileMD5(filePath)
|
||||
let ext = (await NTQQFileApi.getFileType(filePath))?.ext || ''
|
||||
if (ext) {
|
||||
ext = '.' + ext
|
||||
} else {
|
||||
ext = ''
|
||||
}
|
||||
let fileName = `${path.basename(filePath)}`
|
||||
if (fileName.indexOf('.') === -1) {
|
||||
fileName += ext
|
||||
}
|
||||
const mediaPath = await callNTQQApi<string>({
|
||||
methodName: NTQQApiMethod.MEDIA_FILE_PATH,
|
||||
args: [{
|
||||
path_info: {
|
||||
md5HexStr: md5,
|
||||
fileName: fileName,
|
||||
elementType: elementType,
|
||||
elementSubType,
|
||||
thumbSize: 0,
|
||||
needCreate: true,
|
||||
downloadType: 1,
|
||||
file_uuid: '',
|
||||
},
|
||||
}],
|
||||
const session = getSession()
|
||||
const mediaPath = session?.getMsgService().getRichMediaFilePathForGuild({
|
||||
md5HexStr: fileMd5,
|
||||
fileName: fileName,
|
||||
elementType: elementType,
|
||||
elementSubType,
|
||||
thumbSize: 0,
|
||||
needCreate: true,
|
||||
downloadType: 1,
|
||||
file_uuid: ''
|
||||
})
|
||||
log('media path', mediaPath)
|
||||
await NTQQFileApi.copyFile(filePath, mediaPath)
|
||||
const fileSize = await NTQQFileApi.getFileSize(filePath)
|
||||
await fsPromise.copyFile(filePath, mediaPath!)
|
||||
const fileSize = (await fsPromise.stat(filePath)).size
|
||||
return {
|
||||
md5,
|
||||
md5: fileMd5,
|
||||
fileName,
|
||||
path: mediaPath,
|
||||
path: mediaPath!,
|
||||
fileSize,
|
||||
ext,
|
||||
ext
|
||||
}
|
||||
}
|
||||
|
||||
static async downloadMedia(msgId: string, chatType: ChatType, peerUid: string, elementId: string, thumbPath: string, sourcePath: string, force: boolean = false) {
|
||||
static async downloadMedia(
|
||||
msgId: string,
|
||||
chatType: ChatType,
|
||||
peerUid: string,
|
||||
elementId: string,
|
||||
thumbPath: string,
|
||||
sourcePath: string,
|
||||
timeout = 1000 * 60 * 2,
|
||||
force = false
|
||||
) {
|
||||
// 用于下载收到的消息中的图片等
|
||||
if (sourcePath && fs.existsSync(sourcePath)) {
|
||||
if (force) {
|
||||
fs.unlinkSync(sourcePath)
|
||||
try {
|
||||
await fsPromise.unlink(sourcePath)
|
||||
} catch (e) {
|
||||
//
|
||||
}
|
||||
} else {
|
||||
return sourcePath
|
||||
}
|
||||
}
|
||||
const apiParams = [
|
||||
const data = await NTEventDispatch.CallNormalEvent<
|
||||
(
|
||||
params: {
|
||||
fileModelId: string,
|
||||
downloadSourceType: number,
|
||||
triggerType: number,
|
||||
msgId: string,
|
||||
chatType: ChatType,
|
||||
peerUid: string,
|
||||
elementId: string,
|
||||
thumbSize: number,
|
||||
downloadType: number,
|
||||
filePath: string
|
||||
}) => Promise<unknown>,
|
||||
(fileTransNotifyInfo: OnRichMediaDownloadCompleteParams) => void
|
||||
>(
|
||||
'NodeIKernelMsgService/downloadRichMedia',
|
||||
'NodeIKernelMsgListener/onRichMediaDownloadComplete',
|
||||
1,
|
||||
timeout,
|
||||
(arg: OnRichMediaDownloadCompleteParams) => {
|
||||
if (arg.msgId === msgId) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
},
|
||||
{
|
||||
getReq: {
|
||||
fileModelId: '0',
|
||||
downloadSourceType: 0,
|
||||
triggerType: 1,
|
||||
msgId: msgId,
|
||||
chatType: chatType,
|
||||
peerUid: peerUid,
|
||||
elementId: elementId,
|
||||
thumbSize: 0,
|
||||
downloadType: 1,
|
||||
filePath: thumbPath,
|
||||
},
|
||||
},
|
||||
null,
|
||||
]
|
||||
// log("需要下载media", sourcePath);
|
||||
await callNTQQApi({
|
||||
methodName: NTQQApiMethod.DOWNLOAD_MEDIA,
|
||||
args: apiParams,
|
||||
cbCmd: ReceiveCmdS.MEDIA_DOWNLOAD_COMPLETE,
|
||||
cmdCB: (payload: { notifyInfo: { filePath: string, msgId: string } }) => {
|
||||
log('media 下载完成判断', payload.notifyInfo.msgId, msgId)
|
||||
return payload.notifyInfo.msgId == msgId
|
||||
},
|
||||
})
|
||||
return sourcePath
|
||||
fileModelId: '0',
|
||||
downloadSourceType: 0,
|
||||
triggerType: 1,
|
||||
msgId: msgId,
|
||||
chatType: chatType,
|
||||
peerUid: peerUid,
|
||||
elementId: elementId,
|
||||
thumbSize: 0,
|
||||
downloadType: 1,
|
||||
filePath: thumbPath
|
||||
}
|
||||
)
|
||||
let filePath = data[1].filePath
|
||||
if (filePath.startsWith('\\')) {
|
||||
const downloadPath = TEMP_DIR
|
||||
filePath = path.join(downloadPath, filePath)
|
||||
// 下载路径是下载文件夹的相对路径
|
||||
}
|
||||
return filePath
|
||||
}
|
||||
|
||||
static async getImageSize(filePath: string) {
|
||||
return await callNTQQApi<{ width: number, height: number }>({
|
||||
className: NTQQApiClass.FS_API, methodName: NTQQApiMethod.IMAGE_SIZE, args: [filePath],
|
||||
return await callNTQQApi<{ width: number; height: number }>({
|
||||
className: NTQQApiClass.FS_API,
|
||||
methodName: NTQQApiMethod.IMAGE_SIZE,
|
||||
args: [filePath],
|
||||
})
|
||||
}
|
||||
|
||||
static async getImageUrl(msg: RawMessage) {
|
||||
const isPrivateImage = msg.chatType !== ChatType.group
|
||||
const msgElement = msg.elements.find(e => !!e.picElement)
|
||||
if (!msgElement) {
|
||||
static async getImageUrl(element: PicElement) {
|
||||
if (!element) {
|
||||
return ''
|
||||
}
|
||||
const url = msgElement.picElement.originImageUrl // 没有域名
|
||||
const md5HexStr = msgElement.picElement.md5HexStr
|
||||
const fileMd5 = msgElement.picElement.md5HexStr
|
||||
const fileUuid = msgElement.picElement.fileUuid
|
||||
const url: string = element.originImageUrl! // 没有域名
|
||||
const md5HexStr = element.md5HexStr
|
||||
const fileMd5 = element.md5HexStr
|
||||
const fileUuid = element.fileUuid
|
||||
|
||||
if (url) {
|
||||
if (url.startsWith('/download')) {
|
||||
// console.log('rkey', rkey);
|
||||
if (url.includes('&rkey=')) {
|
||||
const UrlParse = new URL(IMAGE_HTTP_HOST + url) //临时解析拼接
|
||||
const imageAppid = UrlParse.searchParams.get('appid')
|
||||
const isNewPic = imageAppid && ['1406', '1407'].includes(imageAppid)
|
||||
if (isNewPic) {
|
||||
let UrlRkey = UrlParse.searchParams.get('rkey')
|
||||
if (UrlRkey) {
|
||||
return IMAGE_HTTP_HOST_NT + url
|
||||
}
|
||||
|
||||
if (!hookApi.isAvailable()) {
|
||||
log('hookApi is not available')
|
||||
return ''
|
||||
}
|
||||
|
||||
const saveRKey = (rkey: string) => {
|
||||
if (isPrivateImage) {
|
||||
privateImageRKey = rkey
|
||||
lastGetPrivateRKeyTime = Date.now()
|
||||
} else {
|
||||
groupImageRKey = rkey
|
||||
lastGetGroupRKeyTime = Date.now()
|
||||
}
|
||||
}
|
||||
|
||||
const refreshRKey = async () => {
|
||||
log('获取图片rkey...')
|
||||
NTQQFileApi.downloadMedia(msg.msgId, msg.chatType, msg.peerUid, msgElement.elementId, '', msgElement.picElement.sourcePath, false).then().catch(() => {
|
||||
})
|
||||
await sleep(1000)
|
||||
const _rkey = hookApi.getRKey()
|
||||
if (_rkey) {
|
||||
const imageUrl = IMAGE_HTTP_HOST_NT + url + _rkey
|
||||
// 验证_rkey是否有效
|
||||
try {
|
||||
await new Promise((res, rej) => {
|
||||
https.get(imageUrl, response => {
|
||||
if (response.statusCode !== 200) {
|
||||
rej('图片rkey获取失败')
|
||||
} else {
|
||||
res(response)
|
||||
}
|
||||
}).on('error', e => {
|
||||
rej(e)
|
||||
})
|
||||
})
|
||||
log('图片rkey获取成功', _rkey)
|
||||
saveRKey(_rkey)
|
||||
return _rkey
|
||||
}catch (e) {
|
||||
log('图片rkey有误', imageUrl)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const existsRKey = isPrivateImage ? privateImageRKey : groupImageRKey
|
||||
const lastGetRKeyTime = isPrivateImage ? lastGetPrivateRKeyTime : lastGetGroupRKeyTime
|
||||
if ((Date.now() - lastGetRKeyTime > rkeyExpireTime)) {
|
||||
// rkey过期
|
||||
const newRKey = await refreshRKey()
|
||||
if (newRKey) {
|
||||
return IMAGE_HTTP_HOST_NT + url + `${newRKey}`
|
||||
} else {
|
||||
log('图片rkey获取失败', url)
|
||||
if(existsRKey){
|
||||
return IMAGE_HTTP_HOST_NT + url + `${existsRKey}`
|
||||
}
|
||||
return ''
|
||||
}
|
||||
}
|
||||
// 使用未过期的rkey
|
||||
if (existsRKey) {
|
||||
return IMAGE_HTTP_HOST_NT + url + `${existsRKey}`
|
||||
}
|
||||
const rkeyData = await rkeyManager.getRkey()
|
||||
UrlRkey = imageAppid === '1406' ? rkeyData.private_rkey : rkeyData.group_rkey
|
||||
return IMAGE_HTTP_HOST_NT + url + `${UrlRkey}`
|
||||
} else {
|
||||
// 老的图片url,不需要rkey
|
||||
return IMAGE_HTTP_HOST + url
|
||||
@@ -229,47 +200,58 @@ export class NTQQFileApi {
|
||||
// 没有url,需要自己拼接
|
||||
return `${IMAGE_HTTP_HOST}/gchatpic_new/0/0-0-${(fileMd5 || md5HexStr)!.toUpperCase()}/0`
|
||||
}
|
||||
log('图片url获取失败', msg)
|
||||
log('图片url获取失败', element)
|
||||
return ''
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class NTQQFileCacheApi {
|
||||
static async setCacheSilentScan(isSilent: boolean = true) {
|
||||
return await callNTQQApi<GeneralCallResult>({
|
||||
methodName: NTQQApiMethod.CACHE_SET_SILENCE,
|
||||
args: [{
|
||||
isSilent,
|
||||
}, null],
|
||||
args: [
|
||||
{
|
||||
isSilent,
|
||||
},
|
||||
null,
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
static getCacheSessionPathList() {
|
||||
return callNTQQApi<{
|
||||
key: string,
|
||||
value: string
|
||||
}[]>({
|
||||
return callNTQQApi<
|
||||
{
|
||||
key: string
|
||||
value: string
|
||||
}[]
|
||||
>({
|
||||
className: NTQQApiClass.OS_API,
|
||||
methodName: NTQQApiMethod.CACHE_PATH_SESSION,
|
||||
})
|
||||
}
|
||||
|
||||
static clearCache(cacheKeys: Array<string> = ['tmp', 'hotUpdate']) {
|
||||
return callNTQQApi<any>({ // TODO: 目前还不知道真正的返回值是什么
|
||||
return callNTQQApi<any>({
|
||||
// TODO: 目前还不知道真正的返回值是什么
|
||||
methodName: NTQQApiMethod.CACHE_CLEAR,
|
||||
args: [{
|
||||
keys: cacheKeys,
|
||||
}, null],
|
||||
args: [
|
||||
{
|
||||
keys: cacheKeys,
|
||||
},
|
||||
null,
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
static addCacheScannedPaths(pathMap: object = {}) {
|
||||
return callNTQQApi<GeneralCallResult>({
|
||||
methodName: NTQQApiMethod.CACHE_ADD_SCANNED_PATH,
|
||||
args: [{
|
||||
pathMap: { ...pathMap },
|
||||
}, null],
|
||||
args: [
|
||||
{
|
||||
pathMap: { ...pathMap },
|
||||
},
|
||||
null,
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
@@ -303,14 +285,18 @@ export class NTQQFileCacheApi {
|
||||
return new Promise<ChatCacheList>((res, rej) => {
|
||||
callNTQQApi<ChatCacheList>({
|
||||
methodName: NTQQApiMethod.CACHE_CHAT_GET,
|
||||
args: [{
|
||||
chatType: type,
|
||||
pageSize,
|
||||
order: 1,
|
||||
pageIndex,
|
||||
}, null],
|
||||
}).then(list => res(list))
|
||||
.catch(e => rej(e))
|
||||
args: [
|
||||
{
|
||||
chatType: type,
|
||||
pageSize,
|
||||
order: 1,
|
||||
pageIndex,
|
||||
},
|
||||
null,
|
||||
],
|
||||
})
|
||||
.then((list) => res(list))
|
||||
.catch((e) => rej(e))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -319,24 +305,29 @@ export class NTQQFileCacheApi {
|
||||
|
||||
return callNTQQApi<CacheFileList>({
|
||||
methodName: NTQQApiMethod.CACHE_FILE_GET,
|
||||
args: [{
|
||||
fileType: fileType,
|
||||
restart: true,
|
||||
pageSize: pageSize,
|
||||
order: 1,
|
||||
lastRecord: _lastRecord,
|
||||
}, null],
|
||||
args: [
|
||||
{
|
||||
fileType: fileType,
|
||||
restart: true,
|
||||
pageSize: pageSize,
|
||||
order: 1,
|
||||
lastRecord: _lastRecord,
|
||||
},
|
||||
null,
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
static async clearChatCache(chats: ChatCacheListItemBasic[] = [], fileKeys: string[] = []) {
|
||||
return await callNTQQApi<GeneralCallResult>({
|
||||
methodName: NTQQApiMethod.CACHE_CHAT_CLEAR,
|
||||
args: [{
|
||||
chats,
|
||||
fileKeys,
|
||||
}, null],
|
||||
args: [
|
||||
{
|
||||
chats,
|
||||
fileKeys,
|
||||
},
|
||||
null,
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,8 +1,11 @@
|
||||
import { Friend, FriendRequest } from '../types'
|
||||
import { Friend, FriendV2 } from '../types'
|
||||
import { ReceiveCmdS } from '../hook'
|
||||
import { callNTQQApi, GeneralCallResult, NTQQApiMethod } from '../ntcall'
|
||||
import { friendRequests } from '../../common/data'
|
||||
import { log } from '../../common/utils'
|
||||
import { getSession } from '@/ntqqapi/wrapper'
|
||||
import { BuddyListReqType, NodeIKernelProfileService } from '../services'
|
||||
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) {
|
||||
@@ -26,6 +29,7 @@ export class NTQQFriendApi {
|
||||
}
|
||||
return _friends
|
||||
}
|
||||
|
||||
static async likeFriend(uid: string, count = 1) {
|
||||
return await callNTQQApi<GeneralCallResult>({
|
||||
methodName: NTQQApiMethod.LIKE_FRIEND,
|
||||
@@ -42,24 +46,80 @@ export class NTQQFriendApi {
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
static async handleFriendRequest(flag: string, accept: boolean) {
|
||||
const request: FriendRequest = friendRequests[flag]
|
||||
if (!request) {
|
||||
throw `flat: ${flag}, 对应的好友请求不存在`
|
||||
const data = flag.split('|')
|
||||
if (data.length < 2) {
|
||||
return
|
||||
}
|
||||
const result = await callNTQQApi<GeneralCallResult>({
|
||||
methodName: NTQQApiMethod.HANDLE_FRIEND_REQUEST,
|
||||
args: [
|
||||
{
|
||||
approvalInfo: {
|
||||
friendUid: request.friendUid,
|
||||
reqTime: request.reqTime,
|
||||
accept,
|
||||
},
|
||||
},
|
||||
],
|
||||
const friendUid = data[0]
|
||||
const reqTime = data[1]
|
||||
const session = getSession()
|
||||
return session?.getBuddyService().approvalFriendRequest({
|
||||
friendUid,
|
||||
reqTime,
|
||||
accept
|
||||
})
|
||||
delete friendRequests[flag]
|
||||
return result
|
||||
}
|
||||
|
||||
static async getBuddyV2(refresh = false): Promise<FriendV2[]> {
|
||||
const uids: string[] = []
|
||||
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']>(
|
||||
'NodeIKernelProfileService/getCoreAndBaseInfo', 5000, 'nodeStore', uids
|
||||
)
|
||||
return Array.from(data.values())
|
||||
}
|
||||
|
||||
@CacheClassFuncAsyncExtend(3600 * 1000, 'getBuddyIdMap', () => true)
|
||||
static async getBuddyIdMapCache(refresh = false): Promise<LimitedHashTable<string, string>> {
|
||||
return await NTQQFriendApi.getBuddyIdMap(refresh)
|
||||
}
|
||||
|
||||
static async getBuddyIdMap(refresh = false): Promise<LimitedHashTable<string, string>> {
|
||||
const uids: string[] = []
|
||||
const retMap: LimitedHashTable<string, string> = new LimitedHashTable<string, string>(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']>(
|
||||
'NodeIKernelProfileService/getCoreAndBaseInfo', 5000, 'nodeStore', uids
|
||||
);
|
||||
data.forEach((value, key) => {
|
||||
retMap.set(value.uin!, value.uid!)
|
||||
})
|
||||
//console.log('getBuddyIdMap', retMap.getValue)
|
||||
return retMap
|
||||
}
|
||||
|
||||
static async getBuddyV2ExWithCate(refresh = false) {
|
||||
const uids: string[] = []
|
||||
const categoryMap: Map<string, any> = new Map()
|
||||
const session = getSession()
|
||||
const buddyService = session?.getBuddyService()
|
||||
const buddyListV2 = refresh ? (await buddyService?.getBuddyListV2('0', BuddyListReqType.KNOMAL))?.data : (await buddyService?.getBuddyListV2('0', BuddyListReqType.KNOMAL))?.data
|
||||
uids.push(
|
||||
...buddyListV2?.flatMap(item => {
|
||||
item.buddyUids.forEach(uid => {
|
||||
categoryMap.set(uid, { categoryId: item.categoryId, categroyName: item.categroyName })
|
||||
})
|
||||
return item.buddyUids
|
||||
})!)
|
||||
const data = await NTEventDispatch.CallNoListenerEvent<NodeIKernelProfileService['getCoreAndBaseInfo']>(
|
||||
'NodeIKernelProfileService/getCoreAndBaseInfo', 5000, 'nodeStore', uids
|
||||
)
|
||||
return Array.from(data).map(([key, value]) => {
|
||||
const category = categoryMap.get(key)
|
||||
return category ? { ...value, categoryId: category.categoryId, categroyName: category.categroyName } : value
|
||||
})
|
||||
}
|
||||
|
||||
static async isBuddy(uid: string): Promise<boolean> {
|
||||
const session = getSession()
|
||||
return session?.getBuddyService().isBuddy(uid)!
|
||||
}
|
||||
}
|
||||
|
@@ -1,63 +1,81 @@
|
||||
import { ReceiveCmdS } from '../hook'
|
||||
import { Group, GroupMember, GroupMemberRole, GroupNotifies, GroupNotify, GroupRequestOperateTypes } from '../types'
|
||||
import { callNTQQApi, GeneralCallResult, NTQQApiClass, NTQQApiMethod } from '../ntcall'
|
||||
import { deleteGroup, uidMaps } from '../../common/data'
|
||||
import { dbUtil } from '../../common/db'
|
||||
import { log } from '../../common/utils/log'
|
||||
import { Group, GroupMember, GroupMemberRole, GroupNotifies, GroupRequestOperateTypes } from '../types'
|
||||
import { callNTQQApi, GeneralCallResult, NTQQApiMethod } from '../ntcall'
|
||||
import { NTQQWindowApi, NTQQWindows } from './window'
|
||||
import { getSession } from '../wrapper'
|
||||
import { NTEventDispatch } from '@/common/utils/EventTask'
|
||||
import { NodeIKernelGroupListener } from '../listeners'
|
||||
|
||||
export class NTQQGroupApi {
|
||||
static async getGroups(forced = false) {
|
||||
let cbCmd = ReceiveCmdS.GROUPS
|
||||
if (process.platform != 'win32') {
|
||||
cbCmd = ReceiveCmdS.GROUPS_STORE
|
||||
}
|
||||
const result = await callNTQQApi<{
|
||||
updateType: number
|
||||
groupList: Group[]
|
||||
}>({ methodName: NTQQApiMethod.GROUPS, args: [{ force_update: forced }, undefined], cbCmd })
|
||||
return result.groupList
|
||||
static async activateMemberListChange() {
|
||||
return await callNTQQApi<GeneralCallResult>({
|
||||
methodName: NTQQApiMethod.ACTIVATE_MEMBER_LIST_CHANGE,
|
||||
classNameIsRegister: true,
|
||||
args: [],
|
||||
})
|
||||
}
|
||||
static async getGroupMembers(groupQQ: string, num = 3000): Promise<GroupMember[]> {
|
||||
const sceneId = await callNTQQApi({
|
||||
methodName: NTQQApiMethod.GROUP_MEMBER_SCENE,
|
||||
|
||||
static async activateMemberInfoChange() {
|
||||
return await callNTQQApi<GeneralCallResult>({
|
||||
methodName: NTQQApiMethod.ACTIVATE_MEMBER_INFO_CHANGE,
|
||||
classNameIsRegister: true,
|
||||
args: [],
|
||||
})
|
||||
}
|
||||
|
||||
static async getGroupAllInfo(groupCode: string, source: number = 4) {
|
||||
return await callNTQQApi<GeneralCallResult & Group>({
|
||||
methodName: NTQQApiMethod.GET_GROUP_ALL_INFO,
|
||||
args: [
|
||||
{
|
||||
groupCode: groupQQ,
|
||||
scene: 'groupMemberList_MainWindow',
|
||||
groupCode,
|
||||
source
|
||||
},
|
||||
null,
|
||||
],
|
||||
})
|
||||
// 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 getGroups(forced = false): Promise<Group[]> {
|
||||
type ListenerType = NodeIKernelGroupListener['onGroupListUpdate']
|
||||
const [, , groupList] = await NTEventDispatch.CallNormalEvent
|
||||
<(force: boolean) => Promise<any>, ListenerType>
|
||||
(
|
||||
'NodeIKernelGroupService/getGroupList',
|
||||
'NodeIKernelGroupListener/onGroupListUpdate',
|
||||
1,
|
||||
5000,
|
||||
(updateType) => true,
|
||||
forced
|
||||
)
|
||||
return groupList
|
||||
}
|
||||
|
||||
static async getGroupMembers(groupQQ: string, num = 3000): Promise<Map<string, GroupMember>> {
|
||||
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) {
|
||||
return await callNTQQApi<GeneralCallResult>({
|
||||
methodName: NTQQApiMethod.GROUP_MEMBERS_INFO,
|
||||
args: [
|
||||
{
|
||||
forceUpdate,
|
||||
groupCode,
|
||||
uids
|
||||
},
|
||||
null,
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
static async getGroupNotifies() {
|
||||
// 获取管理员变更
|
||||
// 加群通知,退出通知,需要管理员权限
|
||||
@@ -72,124 +90,74 @@ export class NTQQGroupApi {
|
||||
args: [{ doubt: false, startSeq: '', number: 14 }, null],
|
||||
})
|
||||
}
|
||||
|
||||
static async getGroupIgnoreNotifies() {
|
||||
await NTQQGroupApi.getGroupNotifies()
|
||||
return await NTQQWindowApi.openWindow(NTQQWindows.GroupNotifyFilterWindow, [], ReceiveCmdS.GROUP_NOTIFY)
|
||||
return await NTQQWindowApi.openWindow<GeneralCallResult & GroupNotifies>(
|
||||
NTQQWindows.GroupNotifyFilterWindow,
|
||||
[],
|
||||
ReceiveCmdS.GROUP_NOTIFY,
|
||||
)
|
||||
}
|
||||
static async handleGroupRequest(seq: string, operateType: GroupRequestOperateTypes, reason?: string) {
|
||||
const notify: GroupNotify = await dbUtil.getGroupNotify(seq)
|
||||
if (!notify) {
|
||||
throw `${seq}对应的加群通知不存在`
|
||||
}
|
||||
// delete groupNotifies[seq];
|
||||
return await callNTQQApi<GeneralCallResult>({
|
||||
methodName: NTQQApiMethod.HANDLE_GROUP_REQUEST,
|
||||
args: [
|
||||
{
|
||||
doubt: false,
|
||||
operateMsg: {
|
||||
operateType: operateType, // 2 拒绝
|
||||
targetMsg: {
|
||||
seq: seq, // 通知序列号
|
||||
type: notify.type,
|
||||
groupCode: notify.group.groupCode,
|
||||
postscript: reason,
|
||||
},
|
||||
},
|
||||
},
|
||||
null,
|
||||
],
|
||||
})
|
||||
|
||||
static async handleGroupRequest(flag: string, operateType: GroupRequestOperateTypes, reason?: string) {
|
||||
const flagitem = flag.split('|')
|
||||
const groupCode = flagitem[0]
|
||||
const seq = flagitem[1]
|
||||
const type = parseInt(flagitem[2])
|
||||
const session = getSession()
|
||||
return session?.getGroupService().operateSysNotify(
|
||||
false,
|
||||
{
|
||||
'operateType': operateType, // 2 拒绝
|
||||
'targetMsg': {
|
||||
'seq': seq, // 通知序列号
|
||||
'type': type,
|
||||
'groupCode': groupCode,
|
||||
'postscript': reason || ' ' // 仅传空值可能导致处理失败,故默认给个空格
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
static async quitGroup(groupQQ: string) {
|
||||
const result = await callNTQQApi<GeneralCallResult>({
|
||||
methodName: NTQQApiMethod.QUIT_GROUP,
|
||||
args: [{ groupCode: groupQQ }, null],
|
||||
})
|
||||
if (result.result === 0) {
|
||||
deleteGroup(groupQQ)
|
||||
}
|
||||
return result
|
||||
const session = getSession()
|
||||
return session?.getGroupService().quitGroup(groupQQ)
|
||||
}
|
||||
|
||||
static async kickMember(
|
||||
groupQQ: string,
|
||||
kickUids: string[],
|
||||
refuseForever: boolean = false,
|
||||
kickReason: string = '',
|
||||
refuseForever = false,
|
||||
kickReason = '',
|
||||
) {
|
||||
return await callNTQQApi<GeneralCallResult>({
|
||||
methodName: NTQQApiMethod.KICK_MEMBER,
|
||||
args: [
|
||||
{
|
||||
groupCode: groupQQ,
|
||||
kickUids,
|
||||
refuseForever,
|
||||
kickReason,
|
||||
},
|
||||
],
|
||||
})
|
||||
const session = getSession()
|
||||
return session?.getGroupService().kickMember(groupQQ, kickUids, refuseForever, kickReason)
|
||||
}
|
||||
static async banMember(groupQQ: string, memList: Array<{ uid: string; timeStamp: number }>) {
|
||||
|
||||
static async banMember(groupQQ: string, memList: Array<{ uid: string, timeStamp: number }>) {
|
||||
// timeStamp为秒数, 0为解除禁言
|
||||
return await callNTQQApi<GeneralCallResult>({
|
||||
methodName: NTQQApiMethod.MUTE_MEMBER,
|
||||
args: [
|
||||
{
|
||||
groupCode: groupQQ,
|
||||
memList,
|
||||
},
|
||||
],
|
||||
})
|
||||
const session = getSession()
|
||||
return session?.getGroupService().setMemberShutUp(groupQQ, memList)
|
||||
}
|
||||
|
||||
static async banGroup(groupQQ: string, shutUp: boolean) {
|
||||
return await callNTQQApi<GeneralCallResult>({
|
||||
methodName: NTQQApiMethod.MUTE_GROUP,
|
||||
args: [
|
||||
{
|
||||
groupCode: groupQQ,
|
||||
shutUp,
|
||||
},
|
||||
null,
|
||||
],
|
||||
})
|
||||
const session = getSession()
|
||||
return session?.getGroupService().setGroupShutUp(groupQQ, shutUp)
|
||||
}
|
||||
|
||||
static async setMemberCard(groupQQ: string, memberUid: string, cardName: string) {
|
||||
return await callNTQQApi<GeneralCallResult>({
|
||||
methodName: NTQQApiMethod.SET_MEMBER_CARD,
|
||||
args: [
|
||||
{
|
||||
groupCode: groupQQ,
|
||||
uid: memberUid,
|
||||
cardName,
|
||||
},
|
||||
null,
|
||||
],
|
||||
})
|
||||
const session = getSession()
|
||||
return session?.getGroupService().modifyMemberCardName(groupQQ, memberUid, cardName)
|
||||
}
|
||||
|
||||
static async setMemberRole(groupQQ: string, memberUid: string, role: GroupMemberRole) {
|
||||
return await callNTQQApi<GeneralCallResult>({
|
||||
methodName: NTQQApiMethod.SET_MEMBER_ROLE,
|
||||
args: [
|
||||
{
|
||||
groupCode: groupQQ,
|
||||
uid: memberUid,
|
||||
role,
|
||||
},
|
||||
null,
|
||||
],
|
||||
})
|
||||
const session = getSession()
|
||||
return session?.getGroupService().modifyMemberRole(groupQQ, memberUid, role)
|
||||
}
|
||||
|
||||
static async setGroupName(groupQQ: string, groupName: string) {
|
||||
return await callNTQQApi<GeneralCallResult>({
|
||||
methodName: NTQQApiMethod.SET_GROUP_NAME,
|
||||
args: [
|
||||
{
|
||||
groupCode: groupQQ,
|
||||
groupName,
|
||||
},
|
||||
null,
|
||||
],
|
||||
})
|
||||
const session = getSession()
|
||||
return session?.getGroupService().modifyGroupName(groupQQ, groupName, false)
|
||||
}
|
||||
|
||||
static async getGroupAtAllRemainCount(groupCode: string) {
|
||||
@@ -214,19 +182,42 @@ export class NTQQGroupApi {
|
||||
})
|
||||
}
|
||||
|
||||
static async getGroupRemainAtTimes(GroupCode: string) {
|
||||
const session = getSession()
|
||||
return session?.getGroupService().getGroupRemainAtTimes(GroupCode)!
|
||||
}
|
||||
|
||||
// 头衔不可用
|
||||
static async setGroupTitle(groupQQ: string, uid: string, title: string) {
|
||||
return await callNTQQApi<GeneralCallResult>({
|
||||
methodName: NTQQApiMethod.SET_GROUP_TITLE,
|
||||
args: [
|
||||
{
|
||||
groupCode: groupQQ,
|
||||
uid,
|
||||
title,
|
||||
},
|
||||
null,
|
||||
],
|
||||
})
|
||||
}
|
||||
static publishGroupBulletin(groupQQ: string, title: string, content: string) {}
|
||||
|
||||
static publishGroupBulletin(groupQQ: string, title: string, content: string) { }
|
||||
|
||||
static async removeGroupEssence(GroupCode: string, msgId: string) {
|
||||
const session = getSession()
|
||||
// 代码没测过
|
||||
// 需要 ob11msgid->msgId + (peer) -> msgSeq + msgRandom
|
||||
let MsgData = await session?.getMsgService().getMsgsIncludeSelf({ chatType: 2, guildId: '', peerUid: GroupCode }, msgId, 1, false)
|
||||
let param = {
|
||||
groupCode: GroupCode,
|
||||
msgRandom: parseInt(MsgData?.msgList[0].msgRandom!),
|
||||
msgSeq: parseInt(MsgData?.msgList[0].msgSeq!)
|
||||
}
|
||||
// GetMsgByShoretID(ShoretID) -> MsgService.getMsgs(Peer,MsgId,1,false) -> 组出参数
|
||||
return session?.getGroupService().removeGroupEssence(param)
|
||||
}
|
||||
|
||||
static async addGroupEssence(GroupCode: string, msgId: string) {
|
||||
const session = getSession()
|
||||
// 代码没测过
|
||||
// 需要 ob11msgid->msgId + (peer) -> msgSeq + msgRandom
|
||||
let MsgData = await session?.getMsgService().getMsgsIncludeSelf({ chatType: 2, guildId: '', peerUid: GroupCode }, msgId, 1, false)
|
||||
let param = {
|
||||
groupCode: GroupCode,
|
||||
msgRandom: parseInt(MsgData?.msgList[0].msgRandom!),
|
||||
msgSeq: parseInt(MsgData?.msgList[0].msgSeq!)
|
||||
}
|
||||
// GetMsgByShoretID(ShoretID) -> MsgService.getMsgs(Peer,MsgId,1,false) -> 组出参数
|
||||
return session?.getGroupService().addGroupEssence(param)
|
||||
}
|
||||
}
|
||||
|
@@ -1,51 +1,50 @@
|
||||
import { callNTQQApi, GeneralCallResult, NTQQApiMethod } from '../ntcall'
|
||||
import { ChatType, RawMessage, SendMessageElement } from '../types'
|
||||
import { dbUtil } from '../../common/db'
|
||||
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'
|
||||
|
||||
export let sendMessagePool: Record<string, ((sendSuccessMsg: RawMessage) => void) | null> = {} // peerUid: callbackFunnc
|
||||
|
||||
export interface Peer {
|
||||
chatType: ChatType
|
||||
peerUid: string // 如果是群聊uid为群号,私聊uid就是加密的字符串
|
||||
guildId?: ''
|
||||
}
|
||||
import { RawMessage, SendMessageElement, Peer, ChatType2 } from '../types'
|
||||
import { getSelfNick, getSelfUid } from '../../common/data'
|
||||
import { getBuildVersion } from '../../common/utils'
|
||||
import { getSession } from '@/ntqqapi/wrapper'
|
||||
import { NTEventDispatch } from '@/common/utils/EventTask'
|
||||
|
||||
export class NTQQMsgApi {
|
||||
static async getTempChatInfo(chatType: ChatType2, peerUid: string) {
|
||||
const session = getSession()
|
||||
return session?.getMsgService().getTempChatInfo(chatType, peerUid)!
|
||||
}
|
||||
|
||||
static async prepareTempChat(toUserUid: string, GroupCode: string, nickname: string) {
|
||||
//By Jadx/Ida Mlikiowa
|
||||
let TempGameSession = {
|
||||
nickname: '',
|
||||
gameAppId: '',
|
||||
selfTinyId: '',
|
||||
peerRoleId: '',
|
||||
peerOpenId: '',
|
||||
}
|
||||
const session = getSession()
|
||||
return session?.getMsgService().prepareTempChat({
|
||||
chatType: ChatType2.KCHATTYPETEMPC2CFROMGROUP,
|
||||
peerUid: toUserUid,
|
||||
peerNickname: nickname,
|
||||
fromGroupCode: GroupCode,
|
||||
sig: '',
|
||||
selfPhone: '',
|
||||
selfUid: getSelfUid(),
|
||||
gameSession: TempGameSession
|
||||
})
|
||||
}
|
||||
|
||||
static async setEmojiLike(peer: Peer, msgSeq: string, emojiId: string, set: boolean = true) {
|
||||
// nt_qq//global//nt_data//Emoji//emoji-resource//sysface_res/apng/ 下可以看到所有QQ表情预览
|
||||
// nt_qq\global\nt_data\Emoji\emoji-resource\face_config.json 里面有所有表情的id, 自带表情id是QSid, 标准emoji表情id是QCid
|
||||
// 其实以官方文档为准是最好的,https://bot.q.qq.com/wiki/develop/api-v2/openapi/emoji/model.html#EmojiType
|
||||
return await callNTQQApi<GeneralCallResult>({
|
||||
methodName: NTQQApiMethod.EMOJI_LIKE,
|
||||
args: [
|
||||
{
|
||||
peer,
|
||||
msgSeq,
|
||||
emojiId,
|
||||
emojiType: emojiId.length > 3 ? '2' : '1',
|
||||
setEmoji: set,
|
||||
},
|
||||
null,
|
||||
],
|
||||
})
|
||||
emojiId = emojiId.toString()
|
||||
const session = getSession()
|
||||
return session?.getMsgService().setMsgEmojiLikes(peer, msgSeq, emojiId, emojiId.length > 3 ? '2' : '1', set)
|
||||
}
|
||||
|
||||
static async getMultiMsg(peer: Peer, rootMsgId: string, parentMsgId: string) {
|
||||
return await callNTQQApi<GeneralCallResult & { msgList: RawMessage[] }>({
|
||||
methodName: NTQQApiMethod.GET_MULTI_MSG,
|
||||
args: [
|
||||
{
|
||||
peer,
|
||||
rootMsgId,
|
||||
parentMsgId,
|
||||
},
|
||||
null,
|
||||
],
|
||||
})
|
||||
const session = getSession()
|
||||
return session?.getMsgService().getMultiMsg(peer, rootMsgId, parentMsgId)!
|
||||
}
|
||||
|
||||
static async activateChat(peer: Peer) {
|
||||
@@ -56,6 +55,7 @@ export class NTQQMsgApi {
|
||||
args: [{ peer, cnt: 20 }, null],
|
||||
})
|
||||
}
|
||||
|
||||
static async activateChatAndGetHistory(peer: Peer) {
|
||||
// await this.fetchRecentContact();
|
||||
// await sleep(500);
|
||||
@@ -65,183 +65,208 @@ export class NTQQMsgApi {
|
||||
args: [{ peer, cnt: 20 }, null],
|
||||
})
|
||||
}
|
||||
static async getMsgHistory(peer: Peer, msgId: string, count: number) {
|
||||
// 消息时间从旧到新
|
||||
return await callNTQQApi<GeneralCallResult & { msgList: RawMessage[] }>({
|
||||
methodName: isQQ998 ? NTQQApiMethod.ACTIVE_CHAT_HISTORY : NTQQApiMethod.HISTORY_MSG,
|
||||
args: [
|
||||
{
|
||||
peer,
|
||||
msgId,
|
||||
cnt: count,
|
||||
queryOrder: true,
|
||||
},
|
||||
null,
|
||||
],
|
||||
})
|
||||
|
||||
static async getMsgsByMsgId(peer: Peer | undefined, msgIds: string[] | undefined) {
|
||||
if (!peer) throw new Error('peer is not allowed')
|
||||
if (!msgIds) throw new Error('msgIds is not allowed')
|
||||
const session = getSession()
|
||||
//Mlikiowa: 参数不合规会导致NC异常崩溃 原因是TX未对进入参数判断 对应Android标记@NotNull AndroidJADX分析可得
|
||||
return await session?.getMsgService().getMsgsByMsgId(peer, msgIds)!
|
||||
}
|
||||
static async fetchRecentContact() {
|
||||
await callNTQQApi({
|
||||
methodName: NTQQApiMethod.RECENT_CONTACT,
|
||||
args: [
|
||||
{
|
||||
fetchParam: {
|
||||
anchorPointContact: {
|
||||
contactId: '',
|
||||
sortField: '',
|
||||
pos: 0,
|
||||
},
|
||||
relativeMoveCount: 0,
|
||||
listType: 2, // 1普通消息,2群助手内的消息
|
||||
count: 200,
|
||||
fetchOld: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
static async getMsgHistory(peer: Peer, msgId: string, count: number, isReverseOrder: boolean = false) {
|
||||
const session = getSession()
|
||||
// 消息时间从旧到新
|
||||
return session?.getMsgService().getMsgsIncludeSelf(peer, msgId, count, isReverseOrder)!
|
||||
}
|
||||
|
||||
static async recallMsg(peer: Peer, msgIds: string[]) {
|
||||
return await callNTQQApi({
|
||||
methodName: NTQQApiMethod.RECALL_MSG,
|
||||
args: [
|
||||
{
|
||||
peer,
|
||||
msgIds,
|
||||
},
|
||||
null,
|
||||
],
|
||||
})
|
||||
const session = getSession()
|
||||
return await session?.getMsgService().recallMsg({
|
||||
chatType: peer.chatType,
|
||||
peerUid: peer.peerUid
|
||||
}, msgIds)
|
||||
}
|
||||
|
||||
static async sendMsg(peer: Peer, msgElements: SendMessageElement[], waitComplete = true, timeout = 10000) {
|
||||
const peerUid = peer.peerUid
|
||||
|
||||
// 等待上一个相同的peer发送完
|
||||
let checkLastSendUsingTime = 0
|
||||
const waitLastSend = async () => {
|
||||
if (checkLastSendUsingTime > timeout) {
|
||||
throw '发送超时'
|
||||
}
|
||||
let lastSending = sendMessagePool[peer.peerUid]
|
||||
if (lastSending) {
|
||||
// log("有正在发送的消息,等待中...")
|
||||
await sleep(500)
|
||||
checkLastSendUsingTime += 500
|
||||
return await waitLastSend()
|
||||
} else {
|
||||
return
|
||||
}
|
||||
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
|
||||
}
|
||||
await waitLastSend()
|
||||
|
||||
let sentMessage: RawMessage = null
|
||||
sendMessagePool[peerUid] = async (rawMessage: RawMessage) => {
|
||||
delete sendMessagePool[peerUid]
|
||||
sentMessage = rawMessage
|
||||
// 此处有采用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()
|
||||
}
|
||||
|
||||
let checkSendCompleteUsingTime = 0
|
||||
const checkSendComplete = async (): Promise<RawMessage> => {
|
||||
if (sentMessage) {
|
||||
if (waitComplete) {
|
||||
if ((await dbUtil.getMsgByLongId(sentMessage.msgId)).sendStatus == 2) {
|
||||
return sentMessage
|
||||
peer.guildId = msgId
|
||||
const data = await NTEventDispatch.CallNormalEvent<
|
||||
(msgId: string, peer: Peer, msgElements: SendMessageElement[], map: Map<any, any>) => Promise<unknown>,
|
||||
(msgList: RawMessage[]) => void
|
||||
>(
|
||||
'NodeIKernelMsgService/sendMsg',
|
||||
'NodeIKernelMsgListener/onMsgInfoListUpdate',
|
||||
1,
|
||||
timeout,
|
||||
(msgRecords: RawMessage[]) => {
|
||||
for (let msgRecord of msgRecords) {
|
||||
if (msgRecord.guildId === msgId && msgRecord.sendStatus === 2) {
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
return sentMessage
|
||||
}
|
||||
// log(`给${peerUid}发送消息成功`)
|
||||
return false
|
||||
},
|
||||
'0',
|
||||
peer,
|
||||
msgElements,
|
||||
new Map()
|
||||
)
|
||||
const retMsg = data[1].find(msgRecord => {
|
||||
if (msgRecord.guildId === msgId) {
|
||||
return true
|
||||
}
|
||||
checkSendCompleteUsingTime += 500
|
||||
if (checkSendCompleteUsingTime > timeout) {
|
||||
throw '发送超时'
|
||||
}
|
||||
await sleep(500)
|
||||
return await checkSendComplete()
|
||||
}
|
||||
})
|
||||
return retMsg!
|
||||
}
|
||||
|
||||
callNTQQApi({
|
||||
methodName: NTQQApiMethod.SEND_MSG,
|
||||
args: [
|
||||
{
|
||||
msgId: '0',
|
||||
peer,
|
||||
msgElements,
|
||||
msgAttributeInfos: new Map(),
|
||||
},
|
||||
null,
|
||||
],
|
||||
}).then()
|
||||
return await checkSendComplete()
|
||||
static async sendMsgV2(peer: Peer, msgElements: SendMessageElement[], waitComplete = true, timeout = 10000) {
|
||||
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<any, any>) => Promise<unknown>,
|
||||
(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[]) {
|
||||
return await callNTQQApi<GeneralCallResult>({
|
||||
methodName: NTQQApiMethod.FORWARD_MSG,
|
||||
args: [
|
||||
{
|
||||
msgIds: msgIds,
|
||||
srcContact: srcPeer,
|
||||
dstContacts: [destPeer],
|
||||
commentElements: [],
|
||||
msgAttributeInfos: new Map(),
|
||||
},
|
||||
null,
|
||||
],
|
||||
})
|
||||
const session = getSession()
|
||||
return session?.getMsgService().forwardMsg(msgIds, srcPeer, [destPeer], [])!
|
||||
}
|
||||
|
||||
static async multiForwardMsg(srcPeer: Peer, destPeer: Peer, msgIds: string[]) {
|
||||
const msgInfos = msgIds.map((id) => {
|
||||
return { msgId: id, senderShowName: selfInfo.nick }
|
||||
static async multiForwardMsg(srcPeer: Peer, destPeer: Peer, msgIds: string[]): Promise<RawMessage> {
|
||||
const msgInfos = msgIds.map(async id => {
|
||||
return { msgId: id, senderShowName: await getSelfNick() }
|
||||
})
|
||||
const apiArgs = [
|
||||
{
|
||||
msgInfos,
|
||||
srcContact: srcPeer,
|
||||
dstContact: destPeer,
|
||||
commentElements: [],
|
||||
msgAttributeInfos: new Map(),
|
||||
const selfUid = getSelfUid()
|
||||
let data = await NTEventDispatch.CallNormalEvent<
|
||||
(msgInfo: typeof msgInfos, srcPeer: Peer, destPeer: Peer, comment: Array<any>, attr: Map<any, any>,) => Promise<unknown>,
|
||||
(msgList: RawMessage[]) => void
|
||||
>(
|
||||
'NodeIKernelMsgService/multiForwardMsgWithComment',
|
||||
'NodeIKernelMsgListener/onMsgInfoListUpdate',
|
||||
1,
|
||||
5000,
|
||||
(msgRecords: RawMessage[]) => {
|
||||
for (let msgRecord of msgRecords) {
|
||||
if (msgRecord.peerUid == destPeer.peerUid && msgRecord.senderUid == selfUid) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
},
|
||||
null,
|
||||
]
|
||||
return await new Promise<RawMessage>((resolve, reject) => {
|
||||
let complete = false
|
||||
setTimeout(() => {
|
||||
if (!complete) {
|
||||
reject('转发消息超时')
|
||||
}
|
||||
}, 5000)
|
||||
registerReceiveHook(ReceiveCmdS.SELF_SEND_MSG, async (payload: { msgRecord: RawMessage }) => {
|
||||
const msg = payload.msgRecord
|
||||
// 需要判断它是转发的消息,并且识别到是当前转发的这一条
|
||||
const arkElement = msg.elements.find((ele) => ele.arkElement)
|
||||
if (!arkElement) {
|
||||
// log("收到的不是转发消息")
|
||||
return
|
||||
}
|
||||
const forwardData: any = JSON.parse(arkElement.arkElement.bytesData)
|
||||
if (forwardData.app != 'com.tencent.multimsg') {
|
||||
return
|
||||
}
|
||||
if (msg.peerUid == destPeer.peerUid && msg.senderUid == selfInfo.uid) {
|
||||
complete = true
|
||||
await dbUtil.addMsg(msg)
|
||||
resolve(msg)
|
||||
log('转发消息成功:', payload)
|
||||
}
|
||||
})
|
||||
callNTQQApi<GeneralCallResult>({
|
||||
methodName: NTQQApiMethod.MULTI_FORWARD_MSG,
|
||||
args: apiArgs,
|
||||
}).then((result) => {
|
||||
log('转发消息结果:', result, apiArgs)
|
||||
if (result.result !== 0) {
|
||||
complete = true
|
||||
reject('转发消息失败,' + JSON.stringify(result))
|
||||
}
|
||||
})
|
||||
msgInfos,
|
||||
srcPeer,
|
||||
destPeer,
|
||||
[],
|
||||
new Map()
|
||||
)
|
||||
for (let msg of data[1]) {
|
||||
const arkElement = msg.elements.find(ele => ele.arkElement)
|
||||
if (!arkElement) {
|
||||
continue
|
||||
}
|
||||
const forwardData: any = JSON.parse(arkElement.arkElement.bytesData)
|
||||
if (forwardData.app != 'com.tencent.multimsg') {
|
||||
continue
|
||||
}
|
||||
if (msg.peerUid == destPeer.peerUid && msg.senderUid == selfUid) {
|
||||
return msg
|
||||
}
|
||||
}
|
||||
throw new Error('转发消息超时')
|
||||
}
|
||||
|
||||
static async queryMsgsWithFilterExWithSeq(peer: Peer, msgSeq: string) {
|
||||
const session = getSession()
|
||||
const ret = await session?.getMsgService().queryMsgsWithFilterEx('0', '0', msgSeq, {
|
||||
chatInfo: peer,//此处为Peer 为关键查询参数 没有啥也没有 by mlik iowa
|
||||
filterMsgType: [],
|
||||
filterSendersUid: [],
|
||||
filterMsgToTime: '0',
|
||||
filterMsgFromTime: '0',
|
||||
isReverseOrder: false,
|
||||
isIncludeCurrent: true,
|
||||
pageLimit: 1,
|
||||
})
|
||||
return ret!
|
||||
}
|
||||
|
||||
static async getMsgsBySeqAndCount(peer: Peer, seq: string, count: number, desc: boolean, z: boolean) {
|
||||
const session = getSession()
|
||||
return await session?.getMsgService().getMsgsBySeqAndCount(peer, seq, count, desc, z)!
|
||||
}
|
||||
}
|
||||
|
64
src/ntqqapi/api/rkey.ts
Normal file
64
src/ntqqapi/api/rkey.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
//远端rkey获取
|
||||
|
||||
import { log } from '@/common/utils'
|
||||
|
||||
interface ServerRkeyData {
|
||||
group_rkey: string
|
||||
private_rkey: string
|
||||
expired_time: number
|
||||
}
|
||||
|
||||
class RkeyManager {
|
||||
serverUrl: string = ''
|
||||
private rkeyData: ServerRkeyData = {
|
||||
group_rkey: '',
|
||||
private_rkey: '',
|
||||
expired_time: 0
|
||||
}
|
||||
|
||||
constructor(serverUrl: string) {
|
||||
this.serverUrl = serverUrl
|
||||
}
|
||||
|
||||
async getRkey() {
|
||||
if (this.isExpired()) {
|
||||
try {
|
||||
await this.refreshRkey()
|
||||
} catch (e) {
|
||||
log('获取rkey失败', e)
|
||||
}
|
||||
}
|
||||
return this.rkeyData
|
||||
}
|
||||
|
||||
isExpired(): boolean {
|
||||
const now = new Date().getTime() / 1000
|
||||
// console.log(`now: ${now}, expired_time: ${this.rkeyData.expired_time}`)
|
||||
return now > this.rkeyData.expired_time
|
||||
}
|
||||
|
||||
async refreshRkey(): Promise<any> {
|
||||
//刷新rkey
|
||||
this.rkeyData = await this.fetchServerRkey()
|
||||
}
|
||||
|
||||
async fetchServerRkey() {
|
||||
return new Promise<ServerRkeyData>((resolve, reject) => {
|
||||
fetch(this.serverUrl)
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
return reject(response.statusText) // 请求失败,返回错误信息
|
||||
}
|
||||
return response.json() // 解析 JSON 格式的响应体
|
||||
})
|
||||
.then(data => {
|
||||
resolve(data)
|
||||
})
|
||||
.catch(error => {
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export const rkeyManager = new RkeyManager('http://napcat-sign.wumiao.wang:2082/rkey')
|
@@ -1,11 +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 { NTQQWindowApi, NTQQWindows } from './window'
|
||||
import { isQQ998, log, sleep } from '../../common/utils'
|
||||
|
||||
let userInfoCache: Record<string, User> = {} // uid: User
|
||||
import { friends, groupMembers, getSelfUin } from '@/common/data'
|
||||
import { CacheClassFuncAsync, log, 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 { NTQQFriendApi } from './friend'
|
||||
|
||||
export class NTQQUserApi {
|
||||
static async setQQAvatar(filePath: string) {
|
||||
@@ -28,6 +31,7 @@ export class NTQQUserApi {
|
||||
timeoutSecond: 2,
|
||||
})
|
||||
}
|
||||
|
||||
static async getUserInfo(uid: string) {
|
||||
const result = await callNTQQApi<{ profiles: Map<string, User> }>({
|
||||
methodName: NTQQApiMethod.USER_INFO,
|
||||
@@ -36,40 +40,68 @@ export class NTQQUserApi {
|
||||
})
|
||||
return result.profiles.get(uid)
|
||||
}
|
||||
static async getUserDetailInfo(uid: string, getLevel = false) {
|
||||
// this.getUserInfo(uid);
|
||||
let methodName = !isQQ998 ? NTQQApiMethod.USER_DETAIL_INFO : NTQQApiMethod.USER_DETAIL_INFO_WITH_BIZ_INFO
|
||||
const fetchInfo = async () => {
|
||||
const result = await callNTQQApi<{ info: User }>({
|
||||
methodName,
|
||||
cbCmd: ReceiveCmdS.USER_DETAIL_INFO,
|
||||
afterFirstCmd: false,
|
||||
cmdCB: (payload) => {
|
||||
const success = payload.info.uid == uid
|
||||
// log("get user detail info", success, uid, payload)
|
||||
return success
|
||||
|
||||
/** 26702 */
|
||||
static async fetchUserDetailInfo(uid: string) {
|
||||
type EventService = NodeIKernelProfileService['fetchUserDetailInfo']
|
||||
type EventListener = NodeIKernelProfileListener['onUserDetailInfoChanged']
|
||||
const [_retData, profile] = await NTEventDispatch.CallNormalEvent
|
||||
<EventService, EventListener>
|
||||
(
|
||||
'NodeIKernelProfileService/fetchUserDetailInfo',
|
||||
'NodeIKernelProfileListener/onUserDetailInfoChanged',
|
||||
1,
|
||||
5000,
|
||||
(profile) => {
|
||||
if (profile.uid === uid) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
},
|
||||
args: [
|
||||
{
|
||||
uid,
|
||||
},
|
||||
null,
|
||||
'BuddyProfileStore',
|
||||
[
|
||||
uid
|
||||
],
|
||||
})
|
||||
const info = result.info
|
||||
if (info?.uin) {
|
||||
uidMaps[info.uid] = info.uin
|
||||
}
|
||||
return info
|
||||
UserDetailSource.KSERVER,
|
||||
[
|
||||
ProfileBizType.KALL
|
||||
]
|
||||
)
|
||||
const RetUser: User = {
|
||||
...profile.simpleInfo.coreInfo,
|
||||
...profile.simpleInfo.status,
|
||||
...profile.simpleInfo.vasInfo,
|
||||
...profile.commonExt,
|
||||
...profile.simpleInfo.baseInfo,
|
||||
qqLevel: profile.commonExt.qqLevel,
|
||||
pendantId: ''
|
||||
}
|
||||
// 首次请求两次才能拿到的等级信息
|
||||
if (!userInfoCache[uid] && getLevel) {
|
||||
await fetchInfo()
|
||||
await sleep(1000)
|
||||
return RetUser
|
||||
}
|
||||
|
||||
static async getUserDetailInfo(uid: string, getLevel = false, withBizInfo = true) {
|
||||
if (getBuildVersion() >= 26702) {
|
||||
return this.fetchUserDetailInfo(uid)
|
||||
}
|
||||
let userInfo = await fetchInfo()
|
||||
userInfoCache[uid] = userInfo
|
||||
return userInfo
|
||||
type EventService = NodeIKernelProfileService['getUserDetailInfoWithBizInfo']
|
||||
type EventListener = NodeIKernelProfileListener['onProfileDetailInfoChanged']
|
||||
const [_retData, profile] = await NTEventDispatch.CallNormalEvent
|
||||
<EventService, EventListener>
|
||||
(
|
||||
'NodeIKernelProfileService/getUserDetailInfoWithBizInfo',
|
||||
'NodeIKernelProfileListener/onProfileDetailInfoChanged',
|
||||
2,
|
||||
5000,
|
||||
(profile: User) => {
|
||||
if (profile.uid === uid) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
},
|
||||
uid,
|
||||
[0]
|
||||
)
|
||||
return profile
|
||||
}
|
||||
|
||||
// return 'p_uin=o0xxx; p_skey=orXDssiGF8axxxxxxxxxxxxxx_; skey='
|
||||
@@ -84,65 +116,37 @@ export class NTQQUserApi {
|
||||
],
|
||||
})
|
||||
}
|
||||
static async getSkey(groupName: string, groupCode: string): Promise<{ data: string }> {
|
||||
return await NTQQWindowApi.openWindow<{ data: string }>(
|
||||
NTQQWindows.GroupHomeWorkWindow,
|
||||
[
|
||||
{
|
||||
groupName,
|
||||
groupCode,
|
||||
source: 'funcbar',
|
||||
},
|
||||
],
|
||||
ReceiveCmdS.SKEY_UPDATE,
|
||||
1,
|
||||
)
|
||||
// return await callNTQQApi<string>({
|
||||
// className: NTQQApiClass.GROUP_HOME_WORK,
|
||||
// methodName: NTQQApiMethod.UPDATE_SKEY,
|
||||
// args: [
|
||||
// {
|
||||
// domain: "qun.qq.com"
|
||||
// }
|
||||
// ]
|
||||
// })
|
||||
// return await callNTQQApi<GeneralCallResult>({
|
||||
// methodName: NTQQApiMethod.GET_SKEY,
|
||||
// args: [
|
||||
// {
|
||||
// "domains": [
|
||||
// "qzone.qq.com",
|
||||
// "qlive.qq.com",
|
||||
// "qun.qq.com",
|
||||
// "gamecenter.qq.com",
|
||||
// "vip.qq.com",
|
||||
// "qianbao.qq.com",
|
||||
// "qidian.qq.com"
|
||||
// ],
|
||||
// "isForNewPCQQ": false
|
||||
// },
|
||||
// null
|
||||
// ]
|
||||
// })
|
||||
|
||||
static async getQzoneCookies() {
|
||||
const uin = getSelfUin()
|
||||
const requestUrl = 'https://ssl.ptlogin2.qq.com/jump?ptlang=1033&clientuin=' + uin + '&clientkey=' + (await this.getClientKey()).clientKey + '&u1=https%3A%2F%2Fuser.qzone.qq.com%2F' + uin + '%2Finfocenter&keyindex=19%27'
|
||||
let cookies: { [key: string]: string } = {}
|
||||
try {
|
||||
cookies = await RequestUtil.HttpsGetCookies(requestUrl)
|
||||
} catch (e: any) {
|
||||
log('获取QZone Cookies失败', e)
|
||||
cookies = {}
|
||||
}
|
||||
return cookies
|
||||
}
|
||||
static async getSkey(): Promise<string> {
|
||||
const clientKeyData = await this.getClientKey()
|
||||
if (clientKeyData.result !== 0) {
|
||||
throw new Error('获取clientKey失败')
|
||||
}
|
||||
const url = 'https://ssl.ptlogin2.qq.com/jump?ptlang=1033&clientuin=' + getSelfUin()
|
||||
+ '&clientkey=' + clientKeyData.clientKey
|
||||
+ '&u1=https%3A%2F%2Fh5.qzone.qq.com%2Fqqnt%2Fqzoneinpcqq%2Ffriend%3Frefresh%3D0%26clientuin%3D0%26darkMode%3D0&keyindex=' + clientKeyData.keyIndex
|
||||
return (await RequestUtil.HttpsGetCookies(url))?.skey
|
||||
}
|
||||
|
||||
static async getCookie(group: Group) {
|
||||
let cookies = await this.getCookieWithoutSkey()
|
||||
let skey = ''
|
||||
for (let i = 0; i < 2; i++) {
|
||||
skey = (await this.getSkey(group.groupName, group.groupCode)).data
|
||||
skey = skey.trim()
|
||||
if (skey) {
|
||||
break
|
||||
}
|
||||
await sleep(1000)
|
||||
}
|
||||
if (!skey) {
|
||||
throw new Error('获取skey失败')
|
||||
}
|
||||
const bkn = NTQQUserApi.genBkn(skey)
|
||||
cookies = cookies.replace('skey=;', `skey=${skey};`)
|
||||
return { cookies, bkn }
|
||||
@CacheClassFuncAsync(1800 * 1000)
|
||||
static async getCookies(domain: string) {
|
||||
const ClientKeyData = await NTQQUserApi.forceFetchClientKey()
|
||||
const uin = getSelfUin()
|
||||
const requestUrl = 'https://ssl.ptlogin2.qq.com/jump?ptlang=1033&clientuin=' + uin + '&clientkey=' + ClientKeyData.clientKey + '&u1=https%3A%2F%2F' + domain + '%2F' + uin + '%2Finfocenter&keyindex=19%27'
|
||||
const cookies: { [key: string]: string; } = await RequestUtil.HttpsGetCookies(requestUrl)
|
||||
return cookies
|
||||
}
|
||||
|
||||
static genBkn(sKey: string) {
|
||||
@@ -156,4 +160,153 @@ export class NTQQUserApi {
|
||||
|
||||
return (hash & 0x7fffffff).toString()
|
||||
}
|
||||
|
||||
static async getPSkey(domains: string[]): Promise<Map<string, string>> {
|
||||
const session = getSession()
|
||||
const res = await session?.getTipOffService().getPskey(domains, true)
|
||||
if (res?.result !== 0) {
|
||||
throw new Error(`获取Pskey失败: ${res?.errMsg}`)
|
||||
}
|
||||
return res.domainPskeyMap
|
||||
}
|
||||
|
||||
static async getClientKey() {
|
||||
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<UserDetailInfoByUinV2>>(
|
||||
'NodeIKernelProfileService/getUserDetailInfoByUin',
|
||||
5000,
|
||||
Uin
|
||||
)
|
||||
}
|
||||
static async getUserDetailInfoByUin(Uin: string) {
|
||||
return NTEventDispatch.CallNoListenerEvent
|
||||
<(Uin: string) => Promise<UserDetailInfoByUin>>(
|
||||
'NodeIKernelProfileService/getUserDetailInfoByUin',
|
||||
5000,
|
||||
Uin
|
||||
)
|
||||
}
|
||||
|
||||
static async getUinByUidV1(Uid: string) {
|
||||
const ret = await NTEventDispatch.CallNoListenerEvent
|
||||
<(Uin: string[]) => Promise<{ uinInfo: Map<string, string> }>>(
|
||||
'NodeIKernelUixConvertService/getUin',
|
||||
5000,
|
||||
[Uid]
|
||||
)
|
||||
let uin = ret.uinInfo.get(Uid)
|
||||
if (!uin) {
|
||||
//从Buddy缓存获取Uin
|
||||
friends.forEach((t) => {
|
||||
if (t.uid == Uid) {
|
||||
uin = t.uin
|
||||
}
|
||||
})
|
||||
}
|
||||
if (!uin) {
|
||||
uin = (await NTQQUserApi.getUserDetailInfo(Uid)).uin //从QQ Native 转换
|
||||
}
|
||||
return uin
|
||||
}
|
||||
|
||||
static async getUinByUidV2(Uid: string) {
|
||||
const session = getSession()
|
||||
let uin = (await session?.getProfileService().getUinByUid('FriendsServiceImpl', [Uid]))?.get(Uid)
|
||||
if (uin) return uin
|
||||
uin = (await session?.getGroupService().getUinByUids([Uid]))?.uins.get(Uid)
|
||||
if (uin) return uin
|
||||
uin = (await session?.getUixConvertService().getUin([Uid]))?.uinInfo.get(Uid)
|
||||
if (uin) return uin
|
||||
uin = (await NTQQFriendApi.getBuddyIdMapCache(true)).getKey(Uid) //从Buddy缓存获取Uin
|
||||
if (uin) return uin
|
||||
uin = (await NTQQFriendApi.getBuddyIdMap(true)).getKey(Uid)
|
||||
if (uin) return uin
|
||||
uin = (await NTQQUserApi.getUserDetailInfo(Uid)).uin //从QQ Native 转换
|
||||
return uin
|
||||
}
|
||||
|
||||
static async getUinByUid(Uid: string) {
|
||||
if (getBuildVersion() >= 26702) {
|
||||
return await NTQQUserApi.getUinByUidV2(Uid)
|
||||
}
|
||||
return await NTQQUserApi.getUinByUidV1(Uid)
|
||||
}
|
||||
|
||||
@CacheClassFuncAsync(3600 * 1000, 'ClientKey')
|
||||
static async forceFetchClientKey() {
|
||||
const session = getSession()
|
||||
return await session?.getTicketService().forceFetchClientKey('')!
|
||||
}
|
||||
}
|
||||
|
@@ -1,76 +1,366 @@
|
||||
import { groups } from '../../common/data'
|
||||
import { log } from '../../common/utils'
|
||||
import { getSelfUin } from '@/common/data'
|
||||
import { log } from '@/common/utils/log'
|
||||
import { NTQQUserApi } from './user'
|
||||
import { RequestUtil } from '@/common/utils/request'
|
||||
import { CacheClassFuncAsync } from '@/common/utils/helper'
|
||||
|
||||
export class WebApi {
|
||||
private static bkn: string
|
||||
private static skey: string
|
||||
private static pskey: string
|
||||
private static cookie: string
|
||||
private defaultHeaders: Record<string, string> = {
|
||||
'User-Agent': 'QQ/8.9.28.635 CFNetwork/1312 Darwin/21.0.0',
|
||||
export enum WebHonorType {
|
||||
ALL = 'all',
|
||||
TALKACTIVE = 'talkative',
|
||||
PERFROMER = 'performer',
|
||||
LEGEND = 'legend',
|
||||
STORONGE_NEWBI = 'strong_newbie',
|
||||
EMOTION = 'emotion'
|
||||
}
|
||||
|
||||
export interface WebApiGroupMember {
|
||||
uin: number
|
||||
role: number
|
||||
g: number
|
||||
join_time: number
|
||||
last_speak_time: number
|
||||
lv: {
|
||||
point: number
|
||||
level: number
|
||||
}
|
||||
card: string
|
||||
tags: string
|
||||
flag: number
|
||||
nick: string
|
||||
qage: number
|
||||
rm: number
|
||||
}
|
||||
|
||||
constructor() {}
|
||||
interface WebApiGroupMemberRet {
|
||||
ec: number
|
||||
errcode: number
|
||||
em: string
|
||||
cache: number
|
||||
adm_num: number
|
||||
levelname: any
|
||||
mems: WebApiGroupMember[]
|
||||
count: number
|
||||
svr_time: number
|
||||
max_count: number
|
||||
search_count: number
|
||||
extmode: number
|
||||
}
|
||||
|
||||
public async addGroupDigest(groupCode: string, msgSeq: string) {
|
||||
const url = `https://qun.qq.com/cgi-bin/group_digest/cancel_digest?random=665&X-CROSS-ORIGIN=fetch&group_code=${groupCode}&msg_seq=${msgSeq}&msg_random=444021292`
|
||||
const res = await this.request(url)
|
||||
return await res.json()
|
||||
export interface WebApiGroupNoticeFeed {
|
||||
u: number//发送者
|
||||
fid: string//fid
|
||||
pubt: number//时间
|
||||
msg: {
|
||||
text: string
|
||||
text_face: string
|
||||
title: string,
|
||||
pics?: {
|
||||
id: string,
|
||||
w: string,
|
||||
h: string
|
||||
}[]
|
||||
}
|
||||
|
||||
public async getGroupDigest(groupCode: string) {
|
||||
const url = `https://qun.qq.com/cgi-bin/group_digest/digest_list?random=665&X-CROSS-ORIGIN=fetch&group_code=${groupCode}&page_start=0&page_limit=20`
|
||||
const res = await this.request(url)
|
||||
log(res.headers)
|
||||
return await res.json()
|
||||
type: number
|
||||
fn: number
|
||||
cn: number
|
||||
vn: number
|
||||
settings: {
|
||||
is_show_edit_card: number
|
||||
remind_ts: number
|
||||
tip_window_type: number
|
||||
confirm_required: number
|
||||
}
|
||||
read_num: number
|
||||
is_read: number
|
||||
is_all_confirm: number
|
||||
}
|
||||
|
||||
private genBkn(sKey: string) {
|
||||
return NTQQUserApi.genBkn(sKey)
|
||||
}
|
||||
private async init() {
|
||||
if (!WebApi.bkn) {
|
||||
const group = groups[0]
|
||||
WebApi.skey = (await NTQQUserApi.getSkey(group.groupName, group.groupCode)).data
|
||||
WebApi.bkn = this.genBkn(WebApi.skey)
|
||||
let cookie = await NTQQUserApi.getCookieWithoutSkey()
|
||||
const pskeyRegex = /p_skey=([^;]+)/
|
||||
const match = cookie.match(pskeyRegex)
|
||||
const pskeyValue = match ? match[1] : null
|
||||
WebApi.pskey = pskeyValue
|
||||
if (cookie.indexOf('skey=;') !== -1) {
|
||||
cookie = cookie.replace('skey=;', `skey=${WebApi.skey};`)
|
||||
}
|
||||
WebApi.cookie = cookie
|
||||
// for(const kv of WebApi.cookie.split(";")){
|
||||
// const [key, value] = kv.split("=");
|
||||
// }
|
||||
// log("set cookie", key, value)
|
||||
// await session.defaultSession.cookies.set({
|
||||
// url: 'https://qun.qq.com', // 你要请求的域名
|
||||
// name: key.trim(),
|
||||
// value: value.trim(),
|
||||
// expirationDate: Date.now() / 1000 + 300000, // Cookie 过期时间,例如设置为当前时间之后的300秒
|
||||
// });
|
||||
// }
|
||||
}
|
||||
export interface WebApiGroupNoticeRet {
|
||||
ec: number
|
||||
em: string
|
||||
ltsm: number
|
||||
srv_code: number
|
||||
read_only: number
|
||||
role: number
|
||||
feeds: WebApiGroupNoticeFeed[]
|
||||
group: {
|
||||
group_id: number
|
||||
class_ext: number
|
||||
}
|
||||
sta: number,
|
||||
gln: number
|
||||
tst: number,
|
||||
ui: any
|
||||
server_time: number
|
||||
svrt: number
|
||||
ad: number
|
||||
}
|
||||
|
||||
private async request(url: string, method: 'GET' | 'POST' = 'GET', headers: Record<string, string> = {}) {
|
||||
await this.init()
|
||||
url += '&bkn=' + WebApi.bkn
|
||||
let _headers: Record<string, string> = {
|
||||
...this.defaultHeaders,
|
||||
...headers,
|
||||
Cookie: WebApi.cookie,
|
||||
credentials: 'include',
|
||||
}
|
||||
log('request', url, _headers)
|
||||
const options = {
|
||||
method: method,
|
||||
headers: _headers,
|
||||
}
|
||||
return fetch(url, options)
|
||||
interface GroupEssenceMsg {
|
||||
group_code: string
|
||||
msg_seq: number
|
||||
msg_random: number
|
||||
sender_uin: string
|
||||
sender_nick: string
|
||||
sender_time: number
|
||||
add_digest_uin: string
|
||||
add_digest_nick: string
|
||||
add_digest_time: number
|
||||
msg_content: any[]
|
||||
can_be_removed: true
|
||||
}
|
||||
|
||||
export interface GroupEssenceMsgRet {
|
||||
retcode: number
|
||||
retmsg: string
|
||||
data: {
|
||||
msg_list: GroupEssenceMsg[]
|
||||
is_end: boolean
|
||||
group_role: number
|
||||
config_page_url: string
|
||||
}
|
||||
}
|
||||
|
||||
export class WebApi {
|
||||
static async getGroupEssenceMsg(GroupCode: string, page_start: string): Promise<GroupEssenceMsgRet | undefined> {
|
||||
const { cookies: CookieValue, bkn: Bkn } = (await NTQQUserApi.getCookies('qun.qq.com'))
|
||||
const url = 'https://qun.qq.com/cgi-bin/group_digest/digest_list?bkn=' + Bkn + '&group_code=' + GroupCode + '&page_start=' + page_start + '&page_limit=20'
|
||||
let ret: GroupEssenceMsgRet
|
||||
try {
|
||||
ret = await RequestUtil.HttpGetJson<GroupEssenceMsgRet>(url, 'GET', '', { 'Cookie': CookieValue })
|
||||
} catch {
|
||||
return undefined
|
||||
}
|
||||
//console.log(url, CookieValue)
|
||||
if (ret.retcode !== 0) {
|
||||
return undefined
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
@CacheClassFuncAsync(3600 * 1000, 'webapi_get_group_members')
|
||||
static async getGroupMembers(GroupCode: string, cached: boolean = true): Promise<WebApiGroupMember[]> {
|
||||
//logDebug('webapi 获取群成员', GroupCode)
|
||||
let MemberData: Array<WebApiGroupMember> = new Array<WebApiGroupMember>()
|
||||
try {
|
||||
const CookiesObject = await NTQQUserApi.getCookies('qun.qq.com')
|
||||
const CookieValue = Object.entries(CookiesObject).map(([key, value]) => `${key}=${value}`).join('; ')
|
||||
const Bkn = WebApi.genBkn(CookiesObject.skey)
|
||||
const retList: Promise<WebApiGroupMemberRet>[] = []
|
||||
const fastRet = await RequestUtil.HttpGetJson<WebApiGroupMemberRet>('https://qun.qq.com/cgi-bin/qun_mgr/search_group_members?st=0&end=40&sort=1&gc=' + GroupCode + '&bkn=' + Bkn, 'POST', '', { 'Cookie': CookieValue });
|
||||
if (!fastRet?.count || fastRet?.errcode !== 0 || !fastRet?.mems) {
|
||||
return []
|
||||
} else {
|
||||
for (const key in fastRet.mems) {
|
||||
MemberData.push(fastRet.mems[key])
|
||||
}
|
||||
}
|
||||
//初始化获取PageNum
|
||||
const PageNum = Math.ceil(fastRet.count / 40)
|
||||
//遍历批量请求
|
||||
for (let i = 2; i <= PageNum; i++) {
|
||||
const ret: Promise<WebApiGroupMemberRet> = RequestUtil.HttpGetJson<WebApiGroupMemberRet>('https://qun.qq.com/cgi-bin/qun_mgr/search_group_members?st=' + (i - 1) * 40 + '&end=' + i * 40 + '&sort=1&gc=' + GroupCode + '&bkn=' + Bkn, 'POST', '', { 'Cookie': CookieValue });
|
||||
retList.push(ret)
|
||||
}
|
||||
//批量等待
|
||||
for (let i = 1; i <= PageNum; i++) {
|
||||
const ret = await (retList[i])
|
||||
if (!ret?.count || ret?.errcode !== 0 || !ret?.mems) {
|
||||
continue
|
||||
}
|
||||
for (const key in ret.mems) {
|
||||
MemberData.push(ret.mems[key])
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
return MemberData
|
||||
}
|
||||
return MemberData
|
||||
}
|
||||
// public static async addGroupDigest(groupCode: string, msgSeq: string) {
|
||||
// const url = `https://qun.qq.com/cgi-bin/group_digest/cancel_digest?random=665&X-CROSS-ORIGIN=fetch&group_code=${groupCode}&msg_seq=${msgSeq}&msg_random=444021292`;
|
||||
// const res = await this.request(url);
|
||||
// return await res.json();
|
||||
// }
|
||||
|
||||
// public async getGroupDigest(groupCode: string) {
|
||||
// const url = `https://qun.qq.com/cgi-bin/group_digest/digest_list?random=665&X-CROSS-ORIGIN=fetch&group_code=${groupCode}&page_start=0&page_limit=20`;
|
||||
// const res = await this.request(url);
|
||||
// return await res.json();
|
||||
// }
|
||||
|
||||
static async setGroupNotice(GroupCode: string, Content: string = '') {
|
||||
//https://web.qun.qq.com/cgi-bin/announce/add_qun_notice?bkn=${bkn}
|
||||
//qid=${群号}&bkn=${bkn}&text=${内容}&pinned=0&type=1&settings={"is_show_edit_card":1,"tip_window_type":1,"confirm_required":1}
|
||||
const _Pskey = (await NTQQUserApi.getPSkey(['qun.qq.com']))['qun.qq.com']
|
||||
const _Skey = await NTQQUserApi.getSkey()
|
||||
const CookieValue = 'p_skey=' + _Pskey + '; skey=' + _Skey + '; p_uin=o' + getSelfUin()
|
||||
let ret: any = undefined
|
||||
//console.log(CookieValue)
|
||||
if (!_Skey || !_Pskey) {
|
||||
//获取Cookies失败
|
||||
return undefined
|
||||
}
|
||||
const Bkn = WebApi.genBkn(_Skey)
|
||||
const data = 'qid=' + GroupCode + '&bkn=' + Bkn + '&text=' + Content + '&pinned=0&type=1&settings={"is_show_edit_card":1,"tip_window_type":1,"confirm_required":1}'
|
||||
const url = 'https://web.qun.qq.com/cgi-bin/announce/add_qun_notice?bkn=' + Bkn
|
||||
try {
|
||||
ret = await RequestUtil.HttpGetJson<any>(url, 'GET', '', { 'Cookie': CookieValue })
|
||||
return ret
|
||||
} catch (e) {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
static async getGrouptNotice(GroupCode: string): Promise<undefined | WebApiGroupNoticeRet> {
|
||||
const _Pskey = (await NTQQUserApi.getPSkey(['qun.qq.com']))['qun.qq.com']
|
||||
const _Skey = await NTQQUserApi.getSkey()
|
||||
const CookieValue = 'p_skey=' + _Pskey + '; skey=' + _Skey + '; p_uin=o' + getSelfUin()
|
||||
let ret: WebApiGroupNoticeRet | undefined = undefined
|
||||
//console.log(CookieValue)
|
||||
if (!_Skey || !_Pskey) {
|
||||
//获取Cookies失败
|
||||
return undefined
|
||||
}
|
||||
const Bkn = WebApi.genBkn(_Skey)
|
||||
const url = 'https://web.qun.qq.com/cgi-bin/announce/get_t_list?bkn=' + Bkn + '&qid=' + GroupCode + '&ft=23&ni=1&n=1&i=1&log_read=1&platform=1&s=-1&n=20'
|
||||
try {
|
||||
ret = await RequestUtil.HttpGetJson<WebApiGroupNoticeRet>(url, 'GET', '', { 'Cookie': CookieValue })
|
||||
if (ret?.ec !== 0) {
|
||||
return undefined
|
||||
}
|
||||
return ret
|
||||
} catch (e) {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
static genBkn(sKey: string) {
|
||||
sKey = sKey || '';
|
||||
let hash = 5381;
|
||||
|
||||
for (let i = 0; i < sKey.length; i++) {
|
||||
const code = sKey.charCodeAt(i);
|
||||
hash = hash + (hash << 5) + code;
|
||||
}
|
||||
|
||||
return (hash & 0x7FFFFFFF).toString();
|
||||
}
|
||||
|
||||
//实现未缓存 考虑2h缓存
|
||||
static async getGroupHonorInfo(groupCode: string, getType: WebHonorType) {
|
||||
async function getDataInternal(Internal_groupCode: string, Internal_type: number) {
|
||||
let url = 'https://qun.qq.com/interactive/honorlist?gc=' + Internal_groupCode + '&type=' + Internal_type.toString();
|
||||
let res = '';
|
||||
let resJson;
|
||||
try {
|
||||
res = await RequestUtil.HttpGetText(url, 'GET', '', { 'Cookie': CookieValue });
|
||||
const match = res.match(/window\.__INITIAL_STATE__=(.*?);/);
|
||||
if (match) {
|
||||
resJson = JSON.parse(match[1].trim());
|
||||
}
|
||||
if (Internal_type === 1) {
|
||||
return resJson?.talkativeList;
|
||||
} else {
|
||||
return resJson?.actorList;
|
||||
}
|
||||
} catch (e) {
|
||||
log('获取当前群荣耀失败', url, e);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let HonorInfo: any = { group_id: groupCode };
|
||||
const CookieValue = (await NTQQUserApi.getCookies('qun.qq.com')).cookies;
|
||||
|
||||
if (getType === WebHonorType.TALKACTIVE || getType === WebHonorType.ALL) {
|
||||
try {
|
||||
let RetInternal = await getDataInternal(groupCode, 1);
|
||||
if (!RetInternal) {
|
||||
throw new Error('获取龙王信息失败');
|
||||
}
|
||||
HonorInfo.current_talkative = {
|
||||
user_id: RetInternal[0]?.uin,
|
||||
avatar: RetInternal[0]?.avatar,
|
||||
nickname: RetInternal[0]?.name,
|
||||
day_count: 0,
|
||||
description: RetInternal[0]?.desc
|
||||
}
|
||||
HonorInfo.talkative_list = [];
|
||||
for (const talkative_ele of RetInternal) {
|
||||
HonorInfo.talkative_list.push({
|
||||
user_id: talkative_ele?.uin,
|
||||
avatar: talkative_ele?.avatar,
|
||||
description: talkative_ele?.desc,
|
||||
day_count: 0,
|
||||
nickname: talkative_ele?.name
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
log(e);
|
||||
}
|
||||
}
|
||||
if (getType === WebHonorType.PERFROMER || getType === WebHonorType.ALL) {
|
||||
try {
|
||||
let RetInternal = await getDataInternal(groupCode, 2);
|
||||
if (!RetInternal) {
|
||||
throw new Error('获取群聊之火失败');
|
||||
}
|
||||
HonorInfo.performer_list = [];
|
||||
for (const performer_ele of RetInternal) {
|
||||
HonorInfo.performer_list.push({
|
||||
user_id: performer_ele?.uin,
|
||||
nickname: performer_ele?.name,
|
||||
avatar: performer_ele?.avatar,
|
||||
description: performer_ele?.desc
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
log(e);
|
||||
}
|
||||
}
|
||||
if (getType === WebHonorType.PERFROMER || getType === WebHonorType.ALL) {
|
||||
try {
|
||||
let RetInternal = await getDataInternal(groupCode, 3);
|
||||
if (!RetInternal) {
|
||||
throw new Error('获取群聊炽焰失败');
|
||||
}
|
||||
HonorInfo.legend_list = [];
|
||||
for (const legend_ele of RetInternal) {
|
||||
HonorInfo.legend_list.push({
|
||||
user_id: legend_ele?.uin,
|
||||
nickname: legend_ele?.name,
|
||||
avatar: legend_ele?.avatar,
|
||||
desc: legend_ele?.description
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
log('获取群聊炽焰失败', e);
|
||||
}
|
||||
}
|
||||
if (getType === WebHonorType.EMOTION || getType === WebHonorType.ALL) {
|
||||
try {
|
||||
let RetInternal = await getDataInternal(groupCode, 6);
|
||||
if (!RetInternal) {
|
||||
throw new Error('获取快乐源泉失败');
|
||||
}
|
||||
HonorInfo.emotion_list = [];
|
||||
for (const emotion_ele of RetInternal) {
|
||||
HonorInfo.emotion_list.push({
|
||||
user_id: emotion_ele?.uin,
|
||||
nickname: emotion_ele?.name,
|
||||
avatar: emotion_ele?.avatar,
|
||||
desc: emotion_ele?.description
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
log('获取快乐源泉失败', e);
|
||||
}
|
||||
}
|
||||
//冒尖小春笋好像已经被tx扬了
|
||||
if (getType === WebHonorType.EMOTION || getType === WebHonorType.ALL) {
|
||||
HonorInfo.strong_newbie_list = [];
|
||||
}
|
||||
return HonorInfo;
|
||||
}
|
||||
}
|
||||
|
@@ -27,7 +27,7 @@ export class NTQQWindowApi {
|
||||
static async openWindow<R = GeneralCallResult>(
|
||||
ntQQWindow: NTQQWindow,
|
||||
args: any[],
|
||||
cbCmd: ReceiveCmd = null,
|
||||
cbCmd: ReceiveCmd | null = null,
|
||||
autoCloseSeconds: number = 2,
|
||||
) {
|
||||
const result = await callNTQQApi<R>({
|
||||
|
@@ -2,7 +2,6 @@ import {
|
||||
AtType,
|
||||
ElementType,
|
||||
FaceIndex,
|
||||
FaceType,
|
||||
PicType,
|
||||
SendArkElement,
|
||||
SendFaceElement,
|
||||
@@ -22,8 +21,9 @@ 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'
|
||||
|
||||
export const mFaceCache = new Map<string, string>(); // emojiId -> faceName
|
||||
export const mFaceCache = new Map<string, string>() // emojiId -> faceName
|
||||
|
||||
export class SendMsgElementConstructor {
|
||||
static poke(groupCode: string, uin: string) {
|
||||
@@ -44,12 +44,12 @@ export class SendMsgElementConstructor {
|
||||
}
|
||||
}
|
||||
|
||||
static at(atUid: string, atNtUid: string, atType: AtType, atName: string): SendTextElement {
|
||||
static at(atUid: string, atNtUid: string, atType: AtType, display: string): SendTextElement {
|
||||
return {
|
||||
elementType: ElementType.TEXT,
|
||||
elementId: '',
|
||||
textElement: {
|
||||
content: `@${atName}`,
|
||||
content: display,
|
||||
atType,
|
||||
atUid,
|
||||
atTinyId: '',
|
||||
@@ -76,6 +76,10 @@ export class SendMsgElementConstructor {
|
||||
if (fileSize === 0) {
|
||||
throw '文件异常,大小为0'
|
||||
}
|
||||
const maxMB = 30;
|
||||
if (fileSize > 1024 * 1024 * 30) {
|
||||
throw `图片过大,最大支持${maxMB}MB,当前文件大小${fileSize}B`
|
||||
}
|
||||
const imageSize = await NTQQFileApi.getImageSize(picPath)
|
||||
const picElement = {
|
||||
md5HexStr: md5,
|
||||
@@ -100,37 +104,41 @@ export class SendMsgElementConstructor {
|
||||
}
|
||||
}
|
||||
|
||||
static async file(filePath: string, fileName: string = ''): Promise<SendFileElement> {
|
||||
const { md5, fileName: _fileName, path, fileSize } = await NTQQFileApi.uploadFile(filePath, ElementType.FILE)
|
||||
static async file(filePath: string, fileName: string = '', folderId: string = ''): Promise<SendFileElement> {
|
||||
const { fileName: _fileName, path, fileSize } = await NTQQFileApi.uploadFile(filePath, ElementType.FILE)
|
||||
if (fileSize === 0) {
|
||||
throw '文件异常,大小为0'
|
||||
throw '文件异常,大小为 0'
|
||||
}
|
||||
let element: SendFileElement = {
|
||||
const element: SendFileElement = {
|
||||
elementType: ElementType.FILE,
|
||||
elementId: '',
|
||||
fileElement: {
|
||||
fileName: fileName || _fileName,
|
||||
filePath: path,
|
||||
folderId: folderId,
|
||||
filePath: path!,
|
||||
fileSize: fileSize.toString(),
|
||||
},
|
||||
}
|
||||
|
||||
return element
|
||||
}
|
||||
|
||||
static async video(filePath: string, fileName: string = '', diyThumbPath: string = ''): Promise<SendVideoElement> {
|
||||
try{
|
||||
try {
|
||||
await fs.stat(filePath)
|
||||
}catch (e) {
|
||||
} catch (e) {
|
||||
throw `文件${filePath}异常,不存在`
|
||||
}
|
||||
log("复制视频到QQ目录", filePath)
|
||||
log('复制视频到QQ目录', filePath)
|
||||
let { fileName: _fileName, path, fileSize, md5 } = await NTQQFileApi.uploadFile(filePath, ElementType.VIDEO)
|
||||
|
||||
log("复制视频到QQ目录完成", path)
|
||||
log('复制视频到QQ目录完成', path)
|
||||
if (fileSize === 0) {
|
||||
throw '文件异常,大小为0'
|
||||
}
|
||||
const maxMB = 100;
|
||||
if (fileSize > 1024 * 1024 * maxMB) {
|
||||
throw `视频过大,最大支持${maxMB}MB,当前文件大小${fileSize}B`
|
||||
}
|
||||
const pathLib = require('path')
|
||||
let thumbDir = path.replace(`${pathLib.sep}Ori${pathLib.sep}`, `${pathLib.sep}Thumb${pathLib.sep}`)
|
||||
thumbDir = pathLib.dirname(thumbDir)
|
||||
@@ -167,7 +175,6 @@ export class SendMsgElementConstructor {
|
||||
|
||||
setTimeout(useDefaultThumb, 5000)
|
||||
ffmpeg(filePath)
|
||||
.on('end', () => {})
|
||||
.on('error', (err) => {
|
||||
if (diyThumbPath) {
|
||||
fs.copyFile(diyThumbPath, thumbPath)
|
||||
@@ -265,13 +272,30 @@ export class SendMsgElementConstructor {
|
||||
}
|
||||
|
||||
static face(faceId: number): SendFaceElement {
|
||||
// 从face_config.json中获取表情名称
|
||||
const sysFaces = faceConfig.sysface
|
||||
const emojiFaces = faceConfig.emoji
|
||||
const face = sysFaces.find((face) => face.QSid === faceId.toString())
|
||||
faceId = parseInt(faceId.toString())
|
||||
// let faceType = parseInt(faceId.toString().substring(0, 1));
|
||||
let faceType = 1
|
||||
if (faceId >= 222) {
|
||||
faceType = 2
|
||||
}
|
||||
if (face?.AniStickerType) {
|
||||
faceType = 3;
|
||||
}
|
||||
return {
|
||||
elementType: ElementType.FACE,
|
||||
elementId: '',
|
||||
faceElement: {
|
||||
faceIndex: faceId,
|
||||
faceType: faceId < 222 ? FaceType.normal : FaceType.normal2,
|
||||
faceType,
|
||||
faceText: face?.QDes,
|
||||
stickerId: face?.AniStickerId,
|
||||
stickerType: face?.AniStickerType,
|
||||
packId: face?.AniStickerPackId,
|
||||
sourceType: 1,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -298,13 +322,13 @@ export class SendMsgElementConstructor {
|
||||
elementId: '',
|
||||
faceElement: {
|
||||
faceIndex: FaceIndex.dice,
|
||||
faceType: FaceType.dice,
|
||||
faceType: 3,
|
||||
faceText: '[骰子]',
|
||||
packId: '1',
|
||||
stickerId: '33',
|
||||
sourceType: 1,
|
||||
stickerType: 2,
|
||||
resultId: resultId.toString(),
|
||||
resultId: resultId?.toString(),
|
||||
surpriseId: '',
|
||||
// "randomType": 1,
|
||||
},
|
||||
@@ -326,7 +350,7 @@ export class SendMsgElementConstructor {
|
||||
stickerId: '34',
|
||||
sourceType: 1,
|
||||
stickerType: 2,
|
||||
resultId: resultId.toString(),
|
||||
resultId: resultId?.toString(),
|
||||
surpriseId: '',
|
||||
// "randomType": 1,
|
||||
},
|
||||
|
19
src/ntqqapi/external/cpmodule.ts
vendored
19
src/ntqqapi/external/cpmodule.ts
vendored
@@ -1,19 +0,0 @@
|
||||
import * as os from "os";
|
||||
import path from "node:path";
|
||||
import fs from "fs";
|
||||
|
||||
export function getModuleWithArchName(moduleName: string) {
|
||||
const systemPlatform = os.platform()
|
||||
const cpuArch = os.arch()
|
||||
return `${moduleName}-${systemPlatform}-${cpuArch}.node`
|
||||
}
|
||||
|
||||
export function cpModule(moduleName: string) {
|
||||
const currentDir = path.resolve(__dirname);
|
||||
const fileName = `./${getModuleWithArchName(moduleName)}`
|
||||
try {
|
||||
fs.copyFileSync(path.join(currentDir, fileName), path.join(currentDir, `${moduleName}.node`));
|
||||
} catch (e) {
|
||||
|
||||
}
|
||||
}
|
BIN
src/ntqqapi/external/crychic/crychic-win32-x64.node
vendored
BIN
src/ntqqapi/external/crychic/crychic-win32-x64.node
vendored
Binary file not shown.
54
src/ntqqapi/external/crychic/index.ts
vendored
54
src/ntqqapi/external/crychic/index.ts
vendored
@@ -1,54 +0,0 @@
|
||||
import {log} from "../../../common/utils";
|
||||
import {NTQQApi} from "../../ntcall";
|
||||
import {cpModule} from "../cpmodule";
|
||||
|
||||
type PokeHandler = (id: string, isGroup: boolean) => void
|
||||
type CrychicHandler = (event: string, id: string, isGroup: boolean) => void
|
||||
|
||||
let pokeRecords: Record<string, number> = {}
|
||||
|
||||
class Crychic{
|
||||
private crychic: any = undefined
|
||||
|
||||
loadNode(){
|
||||
if (!this.crychic){
|
||||
try {
|
||||
cpModule('crychic');
|
||||
this.crychic = require("./crychic.node")
|
||||
this.crychic.init()
|
||||
}catch (e) {
|
||||
log("crychic加载失败", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
registerPokeHandler(fn: PokeHandler){
|
||||
this.registerHandler((event, id, isGroup)=>{
|
||||
if (event === "poke"){
|
||||
let existTime = pokeRecords[id]
|
||||
if (existTime) {
|
||||
if (Date.now() - existTime < 1500) {
|
||||
return
|
||||
}
|
||||
}
|
||||
pokeRecords[id] = Date.now()
|
||||
fn(id, isGroup);
|
||||
}
|
||||
})
|
||||
}
|
||||
registerHandler(fn: CrychicHandler){
|
||||
if (!this.crychic) return;
|
||||
this.crychic.setCryHandler(fn)
|
||||
}
|
||||
sendFriendPoke(friendUid: string){
|
||||
if (!this.crychic) return;
|
||||
this.crychic.sendFriendPoke(parseInt(friendUid))
|
||||
NTQQApi.fetchUnitedCommendConfig().then()
|
||||
}
|
||||
sendGroupPoke(groupCode: string, memberUin: string){
|
||||
if (!this.crychic) return;
|
||||
this.crychic.sendGroupPoke(parseInt(memberUin), parseInt(groupCode))
|
||||
NTQQApi.fetchUnitedCommendConfig().then()
|
||||
}
|
||||
}
|
||||
|
||||
export const crychic = new Crychic()
|
BIN
src/ntqqapi/external/moehook/MoeHoo-linux-x64.node
vendored
BIN
src/ntqqapi/external/moehook/MoeHoo-linux-x64.node
vendored
Binary file not shown.
BIN
src/ntqqapi/external/moehook/MoeHoo-win32-x64.node
vendored
BIN
src/ntqqapi/external/moehook/MoeHoo-win32-x64.node
vendored
Binary file not shown.
34
src/ntqqapi/external/moehook/hook.ts
vendored
34
src/ntqqapi/external/moehook/hook.ts
vendored
@@ -1,34 +0,0 @@
|
||||
import * as os from "os";
|
||||
import fs from "fs";
|
||||
import path from "node:path";
|
||||
import {cpModule} from "../cpmodule";
|
||||
|
||||
interface MoeHook {
|
||||
GetRkey: () => string, // Return '&rkey=xxx'
|
||||
HookRkey: () => string
|
||||
}
|
||||
|
||||
|
||||
class HookApi {
|
||||
private readonly moeHook: MoeHook | null = null;
|
||||
|
||||
constructor() {
|
||||
cpModule('MoeHoo');
|
||||
try {
|
||||
this.moeHook = require('./MoeHoo.node');
|
||||
console.log("hook rkey地址", this.moeHook!.HookRkey());
|
||||
} catch (e) {
|
||||
console.log('加载 moehoo 失败', e);
|
||||
}
|
||||
}
|
||||
|
||||
getRKey(): string {
|
||||
return this.moeHook?.GetRkey() || '';
|
||||
}
|
||||
|
||||
isAvailable() {
|
||||
return !!this.moeHook;
|
||||
}
|
||||
}
|
||||
|
||||
export const hookApi = new HookApi();
|
3665
src/ntqqapi/face_config.json
Normal file
3665
src/ntqqapi/face_config.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,27 +1,28 @@
|
||||
import { BrowserWindow } from 'electron'
|
||||
import type { BrowserWindow } from 'electron'
|
||||
import { NTQQApiClass, NTQQApiMethod } from './ntcall'
|
||||
import { NTQQMsgApi, sendMessagePool } from './api/msg'
|
||||
import { ChatType, Group, GroupMember, GroupMemberRole, RawMessage, User } from './types'
|
||||
import { NTQQMsgApi } from './api/msg'
|
||||
import { CategoryFriend, ChatType, Group, GroupMember, GroupMemberRole, RawMessage } from './types'
|
||||
import {
|
||||
deleteGroup,
|
||||
friends,
|
||||
getFriend,
|
||||
getGroupMember,
|
||||
groups,
|
||||
selfInfo,
|
||||
tempGroupCodeMap,
|
||||
uidMaps,
|
||||
} from '../common/data'
|
||||
getSelfUin,
|
||||
setSelfInfo
|
||||
} from '@/common/data'
|
||||
import { OB11GroupDecreaseEvent } from '../onebot11/event/notice/OB11GroupDecreaseEvent'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { postOB11Event } from '../onebot11/server/postOB11Event'
|
||||
import { getConfigUtil, HOOK_LOG } from '../common/config'
|
||||
import { postOb11Event } from '../onebot11/server/post-ob11-event'
|
||||
import { getConfigUtil, HOOK_LOG } from '@/common/config'
|
||||
import fs from 'fs'
|
||||
import { dbUtil } from '../common/db'
|
||||
import { dbUtil } from '@/common/db'
|
||||
import { NTQQGroupApi } from './api/group'
|
||||
import { log } from '../common/utils/log'
|
||||
import { isNumeric, sleep } from '../common/utils/helper'
|
||||
import { log } from '@/common/utils'
|
||||
import { isNumeric, sleep } from '@/common/utils'
|
||||
import { OB11Constructor } from '../onebot11/constructor'
|
||||
import { OB11GroupCardEvent } from '../onebot11/event/notice/OB11GroupCardEvent'
|
||||
import { OB11GroupAdminNoticeEvent } from '../onebot11/event/notice/OB11GroupAdminNoticeEvent'
|
||||
import { randomUUID } from 'node:crypto'
|
||||
|
||||
export let hookApiCallbacks: Record<string, (apiReturn: any) => void> = {}
|
||||
|
||||
@@ -81,7 +82,7 @@ export function hookNTQQApiReceive(window: BrowserWindow) {
|
||||
let isLogger = false
|
||||
try {
|
||||
isLogger = args[0]?.eventName?.startsWith('ns-LoggerApi')
|
||||
} catch (e) {}
|
||||
} catch (e) { }
|
||||
if (!isLogger) {
|
||||
try {
|
||||
HOOK_LOG && log(`received ntqq api message: ${channel}`, args)
|
||||
@@ -100,7 +101,7 @@ export function hookNTQQApiReceive(window: BrowserWindow) {
|
||||
try {
|
||||
let _ = hook.hookFunc(receiveData.payload)
|
||||
if (hook.hookFunc.constructor.name === 'AsyncFunction') {
|
||||
;(_ as Promise<void>).then()
|
||||
; (_ as Promise<void>).then()
|
||||
}
|
||||
} catch (e) {
|
||||
log('hook error', e, receiveData.payload)
|
||||
@@ -121,7 +122,7 @@ export function hookNTQQApiReceive(window: BrowserWindow) {
|
||||
delete hookApiCallbacks[callbackId]
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
} catch (e: any) {
|
||||
log('hookNTQQApiReceive error', e.stack.toString(), args)
|
||||
}
|
||||
originalSend.call(window.webContents, channel, ...args)
|
||||
@@ -140,11 +141,11 @@ export function hookNTQQApiCall(window: BrowserWindow) {
|
||||
let isLogger = false
|
||||
try {
|
||||
isLogger = args[3][0].eventName.startsWith('ns-LoggerApi')
|
||||
} catch (e) {}
|
||||
} catch (e) { }
|
||||
if (!isLogger) {
|
||||
try {
|
||||
HOOK_LOG && log('call NTQQ api', thisArg, args)
|
||||
} catch (e) {}
|
||||
} catch (e) { }
|
||||
try {
|
||||
const _args: unknown[] = args[3][1]
|
||||
const cmdName: NTQQApiMethod = _args[0] as NTQQApiMethod
|
||||
@@ -155,7 +156,7 @@ export function hookNTQQApiCall(window: BrowserWindow) {
|
||||
try {
|
||||
let _ = hook.hookFunc(callParams)
|
||||
if (hook.hookFunc.constructor.name === 'AsyncFunction') {
|
||||
;(_ as Promise<void>).then()
|
||||
(_ as Promise<void>).then()
|
||||
}
|
||||
} catch (e) {
|
||||
log('hook call error', e, _args)
|
||||
@@ -163,7 +164,7 @@ export function hookNTQQApiCall(window: BrowserWindow) {
|
||||
}).then()
|
||||
}
|
||||
})
|
||||
} catch (e) {}
|
||||
} catch (e) { }
|
||||
}
|
||||
return target.apply(thisArg, args)
|
||||
},
|
||||
@@ -187,7 +188,7 @@ export function hookNTQQApiCall(window: BrowserWindow) {
|
||||
let ret = target.apply(thisArg, args)
|
||||
try {
|
||||
HOOK_LOG && log('call NTQQ invoke api return', ret)
|
||||
} catch (e) {}
|
||||
} catch (e) { }
|
||||
return ret
|
||||
},
|
||||
})
|
||||
@@ -202,7 +203,7 @@ export function registerReceiveHook<PayloadType>(
|
||||
method: ReceiveCmd | ReceiveCmd[],
|
||||
hookFunc: (payload: PayloadType) => void,
|
||||
): string {
|
||||
const id = uuidv4()
|
||||
const id = randomUUID()
|
||||
if (!Array.isArray(method)) {
|
||||
method = [method]
|
||||
}
|
||||
@@ -242,18 +243,7 @@ async function updateGroups(_groups: Group[], needUpdate: boolean = true) {
|
||||
continue
|
||||
}
|
||||
log('update group', group)
|
||||
// if (!activatedGroups.includes(group.groupCode)) {
|
||||
NTQQMsgApi.activateChat({ peerUid: group.groupCode, chatType: ChatType.group })
|
||||
.then((r) => {
|
||||
// activatedGroups.push(group.groupCode);
|
||||
// log(`激活群聊天窗口${group.groupName}(${group.groupCode})`, r)
|
||||
// if (r.result !== 0) {
|
||||
// setTimeout(() => NTQQMsgApi.activateGroupChat(group.groupCode).then(r => log(`再次激活群聊天窗口${group.groupName}(${group.groupCode})`, r)), 500);
|
||||
// }else {
|
||||
// }
|
||||
})
|
||||
.catch(log)
|
||||
// }
|
||||
NTQQMsgApi.activateChat({ peerUid: group.groupCode, chatType: ChatType.group }).then().catch(log)
|
||||
let existGroup = groups.find((g) => g.groupCode == group.groupCode)
|
||||
if (existGroup) {
|
||||
Object.assign(existGroup, group)
|
||||
@@ -266,7 +256,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())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -285,21 +275,22 @@ 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<string>() // 建立索引降低时间复杂度
|
||||
|
||||
for (const member of newMembers) {
|
||||
newMembersSet.add(member.uin)
|
||||
newMembersSet.add(member[1].uin)
|
||||
}
|
||||
|
||||
// 判断bot是否是管理员,如果是管理员不需要从这里得知有人退群,这里的退群无法得知是主动退群还是被踢
|
||||
let bot = await getGroupMember(group.groupCode, selfInfo.uin)
|
||||
if (bot.role == GroupMemberRole.admin || bot.role == GroupMemberRole.owner) {
|
||||
const selfUin = getSelfUin()
|
||||
const bot = await getGroupMember(group.groupCode, selfUin)
|
||||
if (bot?.role == GroupMemberRole.admin || bot?.role == GroupMemberRole.owner) {
|
||||
continue
|
||||
}
|
||||
for (const member of oldMembers) {
|
||||
if (!newMembersSet.has(member.uin) && member.uin != selfInfo.uin) {
|
||||
postOB11Event(
|
||||
if (!newMembersSet.has(member.uin) && member.uin != selfUin) {
|
||||
postOb11Event(
|
||||
new OB11GroupDecreaseEvent(
|
||||
parseInt(group.groupCode),
|
||||
parseInt(member.uin),
|
||||
@@ -318,211 +309,203 @@ async function processGroupEvent(payload: { groupList: Group[] }) {
|
||||
}
|
||||
|
||||
updateGroups(newGroupList, false).then()
|
||||
} catch (e) {
|
||||
} catch (e: any) {
|
||||
updateGroups(payload.groupList).then()
|
||||
log('更新群信息错误', e.stack.toString())
|
||||
}
|
||||
}
|
||||
|
||||
// 群列表变动
|
||||
registerReceiveHook<{ groupList: Group[]; updateType: number }>(ReceiveCmdS.GROUPS, (payload) => {
|
||||
// updateType 3是群列表变动,2是群成员变动
|
||||
// log("群列表变动", payload.updateType, payload.groupList)
|
||||
if (payload.updateType != 2) {
|
||||
updateGroups(payload.groupList).then()
|
||||
} else {
|
||||
if (process.platform == 'win32') {
|
||||
processGroupEvent(payload).then()
|
||||
}
|
||||
}
|
||||
})
|
||||
registerReceiveHook<{ groupList: Group[]; updateType: number }>(ReceiveCmdS.GROUPS_STORE, (payload) => {
|
||||
// updateType 3是群列表变动,2是群成员变动
|
||||
// log("群列表变动", payload.updateType, payload.groupList)
|
||||
if (payload.updateType != 2) {
|
||||
updateGroups(payload.groupList).then()
|
||||
} else {
|
||||
if (process.platform != 'win32') {
|
||||
processGroupEvent(payload).then()
|
||||
}
|
||||
}
|
||||
})
|
||||
export async function startHook() {
|
||||
|
||||
registerReceiveHook<{
|
||||
groupCode: string
|
||||
dataSource: number
|
||||
members: Set<GroupMember>
|
||||
}>(ReceiveCmdS.GROUP_MEMBER_INFO_UPDATE, async (payload) => {
|
||||
const groupCode = payload.groupCode
|
||||
const members = Array.from(payload.members.values())
|
||||
// log("群成员信息变动", groupCode, members)
|
||||
for (const member of members) {
|
||||
const existMember = await getGroupMember(groupCode, member.uin)
|
||||
if (existMember) {
|
||||
Object.assign(existMember, member)
|
||||
// 群列表变动
|
||||
registerReceiveHook<{ groupList: Group[]; updateType: number }>(ReceiveCmdS.GROUPS, (payload) => {
|
||||
// updateType 3是群列表变动,2是群成员变动
|
||||
// log("群列表变动", payload.updateType, payload.groupList)
|
||||
if (payload.updateType != 2) {
|
||||
updateGroups(payload.groupList).then()
|
||||
}
|
||||
}
|
||||
// const existGroup = groups.find(g => g.groupCode == groupCode);
|
||||
// if (existGroup) {
|
||||
// log("对比群成员", existGroup.members, members)
|
||||
// for (const member of members) {
|
||||
// const existMember = existGroup.members.find(m => m.uin == member.uin);
|
||||
// if (existMember) {
|
||||
// log("对比群名片", existMember.cardName, member.cardName)
|
||||
// if (existMember.cardName != member.cardName) {
|
||||
// postOB11Event(new OB11GroupCardEvent(parseInt(existGroup.groupCode), parseInt(member.uin), member.cardName, existMember.cardName));
|
||||
// }
|
||||
// Object.assign(existMember, member);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
})
|
||||
|
||||
// 好友列表变动
|
||||
registerReceiveHook<{
|
||||
data: { categoryId: number; categroyName: string; categroyMbCount: number; buddyList: User[] }[]
|
||||
}>(ReceiveCmdS.FRIENDS, (payload) => {
|
||||
for (const fData of payload.data) {
|
||||
const _friends = fData.buddyList
|
||||
for (let friend of _friends) {
|
||||
NTQQMsgApi.activateChat({ peerUid: friend.uid, chatType: ChatType.friend }).then()
|
||||
let existFriend = friends.find((f) => f.uin == friend.uin)
|
||||
if (!existFriend) {
|
||||
friends.push(friend)
|
||||
} else {
|
||||
Object.assign(existFriend, friend)
|
||||
else {
|
||||
if (process.platform == 'win32') {
|
||||
processGroupEvent(payload).then()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
registerReceiveHook<{ msgList: Array<RawMessage> }>([ReceiveCmdS.NEW_MSG, ReceiveCmdS.NEW_ACTIVE_MSG], (payload) => {
|
||||
// 保存一下uid
|
||||
for (const message of payload.msgList) {
|
||||
const uid = message.senderUid
|
||||
const uin = message.senderUin
|
||||
if (uid && uin) {
|
||||
if (message.chatType === ChatType.temp) {
|
||||
dbUtil.getReceivedTempUinMap().then((receivedTempUinMap) => {
|
||||
if (!receivedTempUinMap[uin]) {
|
||||
receivedTempUinMap[uin] = uid
|
||||
dbUtil.setReceivedTempUinMap(receivedTempUinMap)
|
||||
}
|
||||
})
|
||||
}
|
||||
uidMaps[uid] = uin
|
||||
}
|
||||
}
|
||||
|
||||
// 自动清理新消息文件
|
||||
const { autoDeleteFile } = getConfigUtil().getConfig()
|
||||
if (!autoDeleteFile) {
|
||||
return
|
||||
}
|
||||
for (const message of payload.msgList) {
|
||||
// log("收到新消息,push到历史记录", message.msgId)
|
||||
// dbUtil.addMsg(message).then()
|
||||
// 清理文件
|
||||
|
||||
for (const msgElement of message.elements) {
|
||||
setTimeout(() => {
|
||||
const picPath = msgElement.picElement?.sourcePath
|
||||
const picThumbPath = [...msgElement.picElement?.thumbPath.values()]
|
||||
const pttPath = msgElement.pttElement?.filePath
|
||||
const filePath = msgElement.fileElement?.filePath
|
||||
const videoPath = msgElement.videoElement?.filePath
|
||||
const videoThumbPath: string[] = [...msgElement.videoElement?.thumbPath.values()]
|
||||
const pathList = [picPath, ...picThumbPath, pttPath, filePath, videoPath, ...videoThumbPath]
|
||||
if (msgElement.picElement) {
|
||||
pathList.push(...Object.values(msgElement.picElement.thumbPath))
|
||||
}
|
||||
const aioOpGrayTipElement = msgElement.grayTipElement?.aioOpGrayTipElement
|
||||
if (aioOpGrayTipElement) {
|
||||
tempGroupCodeMap[aioOpGrayTipElement.peerUid] = aioOpGrayTipElement.fromGrpCodeOfTmpChat
|
||||
}
|
||||
|
||||
// log("需要清理的文件", pathList);
|
||||
for (const path of pathList) {
|
||||
if (path) {
|
||||
fs.unlink(picPath, () => {
|
||||
log('删除文件成功', path)
|
||||
})
|
||||
}
|
||||
}
|
||||
}, getConfigUtil().getConfig().autoDeleteFileSecond * 1000)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
registerReceiveHook<{ msgRecord: RawMessage }>(ReceiveCmdS.SELF_SEND_MSG, ({ msgRecord }) => {
|
||||
const message = msgRecord
|
||||
const peerUid = message.peerUid
|
||||
// log("收到自己发送成功的消息", Object.keys(sendMessagePool), message);
|
||||
// log("收到自己发送成功的消息", message.msgId, message.msgSeq);
|
||||
dbUtil.addMsg(message).then()
|
||||
const sendCallback = sendMessagePool[peerUid]
|
||||
if (sendCallback) {
|
||||
try {
|
||||
sendCallback(message)
|
||||
} catch (e) {
|
||||
log('receive self msg error', e.stack)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
registerReceiveHook<{ info: { status: number } }>(ReceiveCmdS.SELF_STATUS, (info) => {
|
||||
selfInfo.online = info.info.status !== 20
|
||||
})
|
||||
|
||||
let activatedPeerUids: string[] = []
|
||||
registerReceiveHook<{
|
||||
changedRecentContactLists: {
|
||||
listType: number
|
||||
sortedContactList: string[]
|
||||
changedList: {
|
||||
id: string // peerUid
|
||||
chatType: ChatType
|
||||
}[]
|
||||
}[]
|
||||
}>(ReceiveCmdS.RECENT_CONTACT, async (payload) => {
|
||||
for (const recentContact of payload.changedRecentContactLists) {
|
||||
for (const changedContact of recentContact.changedList) {
|
||||
if (activatedPeerUids.includes(changedContact.id)) continue
|
||||
activatedPeerUids.push(changedContact.id)
|
||||
const peer = { peerUid: changedContact.id, chatType: changedContact.chatType }
|
||||
if (changedContact.chatType === ChatType.temp) {
|
||||
log('收到临时会话消息', peer)
|
||||
NTQQMsgApi.activateChatAndGetHistory(peer).then(() => {
|
||||
NTQQMsgApi.getMsgHistory(peer, '', 20).then(({ msgList }) => {
|
||||
let lastTempMsg = msgList.pop()
|
||||
log('激活窗口之前的第一条临时会话消息:', lastTempMsg)
|
||||
if (Date.now() / 1000 - parseInt(lastTempMsg.msgTime) < 5) {
|
||||
OB11Constructor.message(lastTempMsg).then((r) => postOB11Event(r))
|
||||
}
|
||||
})
|
||||
})
|
||||
} else {
|
||||
NTQQMsgApi.activateChat(peer).then()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
registerCallHook(NTQQApiMethod.DELETE_ACTIVE_CHAT, async (payload) => {
|
||||
const peerUid = payload[0] as string
|
||||
log('激活的聊天窗口被删除,准备重新激活', peerUid)
|
||||
let chatType = ChatType.friend
|
||||
if (isNumeric(peerUid)) {
|
||||
chatType = ChatType.group
|
||||
} else {
|
||||
// 检查是否好友
|
||||
if (!(await getFriend(peerUid))) {
|
||||
chatType = ChatType.temp
|
||||
}
|
||||
}
|
||||
const peer = { peerUid, chatType }
|
||||
await sleep(1000)
|
||||
NTQQMsgApi.activateChat(peer).then((r) => {
|
||||
log('重新激活聊天窗口', peer, { result: r.result, errMsg: r.errMsg })
|
||||
})
|
||||
})
|
||||
registerReceiveHook<{ groupList: Group[]; updateType: number }>(ReceiveCmdS.GROUPS_STORE, (payload) => {
|
||||
// updateType 3是群列表变动,2是群成员变动
|
||||
// log("群列表变动", payload.updateType, payload.groupList)
|
||||
if (payload.updateType != 2) {
|
||||
updateGroups(payload.groupList).then()
|
||||
}
|
||||
else {
|
||||
if (process.platform != 'win32') {
|
||||
processGroupEvent(payload).then()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
registerReceiveHook<{
|
||||
groupCode: string
|
||||
dataSource: number
|
||||
members: Set<GroupMember>
|
||||
}>(ReceiveCmdS.GROUP_MEMBER_INFO_UPDATE, async (payload) => {
|
||||
const groupCode = payload.groupCode
|
||||
const members = Array.from(payload.members.values())
|
||||
// log("群成员信息变动", groupCode, members)
|
||||
for (const member of members) {
|
||||
const existMember = await getGroupMember(groupCode, member.uin)
|
||||
if (existMember) {
|
||||
if (member.cardName != existMember.cardName) {
|
||||
log('群成员名片变动', `${groupCode}: ${existMember.uin}`, existMember.cardName, '->', member.cardName)
|
||||
postOb11Event(
|
||||
new OB11GroupCardEvent(parseInt(groupCode), parseInt(member.uin), member.cardName, existMember.cardName),
|
||||
)
|
||||
} else if (member.role != existMember.role) {
|
||||
log('有管理员变动通知')
|
||||
const groupAdminNoticeEvent = new OB11GroupAdminNoticeEvent(
|
||||
member.role == GroupMemberRole.admin ? 'set' : 'unset',
|
||||
parseInt(groupCode),
|
||||
parseInt(member.uin)
|
||||
)
|
||||
postOb11Event(groupAdminNoticeEvent, true)
|
||||
}
|
||||
Object.assign(existMember, member)
|
||||
}
|
||||
}
|
||||
// const existGroup = groups.find(g => g.groupCode == groupCode);
|
||||
// if (existGroup) {
|
||||
// log("对比群成员", existGroup.members, members)
|
||||
// for (const member of members) {
|
||||
// const existMember = existGroup.members.find(m => m.uin == member.uin);
|
||||
// if (existMember) {
|
||||
// log("对比群名片", existMember.cardName, member.cardName)
|
||||
// if (existMember.cardName != member.cardName) {
|
||||
// postOB11Event(new OB11GroupCardEvent(parseInt(existGroup.groupCode), parseInt(member.uin), member.cardName, existMember.cardName));
|
||||
// }
|
||||
// Object.assign(existMember, member);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
})
|
||||
|
||||
// 好友列表变动
|
||||
registerReceiveHook<{
|
||||
data: CategoryFriend[]
|
||||
}>(ReceiveCmdS.FRIENDS, (payload) => {
|
||||
for (const fData of payload.data) {
|
||||
const _friends = fData.buddyList
|
||||
for (let friend of _friends) {
|
||||
NTQQMsgApi.activateChat({ peerUid: friend.uid, chatType: ChatType.friend }).then()
|
||||
let existFriend = friends.find((f) => f.uin == friend.uin)
|
||||
if (!existFriend) {
|
||||
friends.push(friend)
|
||||
}
|
||||
else {
|
||||
Object.assign(existFriend, friend)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
registerReceiveHook<{ msgList: Array<RawMessage> }>([ReceiveCmdS.NEW_MSG, ReceiveCmdS.NEW_ACTIVE_MSG], (payload) => {
|
||||
// 自动清理新消息文件
|
||||
const { autoDeleteFile } = getConfigUtil().getConfig()
|
||||
if (!autoDeleteFile) {
|
||||
return
|
||||
}
|
||||
for (const message of payload.msgList) {
|
||||
// log("收到新消息,push到历史记录", message.msgId)
|
||||
// dbUtil.addMsg(message).then()
|
||||
// 清理文件
|
||||
|
||||
for (const msgElement of message.elements) {
|
||||
setTimeout(() => {
|
||||
const picPath = msgElement.picElement?.sourcePath
|
||||
const picThumbPath = [...msgElement.picElement?.thumbPath.values()]
|
||||
const pttPath = msgElement.pttElement?.filePath
|
||||
const filePath = msgElement.fileElement?.filePath
|
||||
const videoPath = msgElement.videoElement?.filePath
|
||||
const videoThumbPath: string[] = [...msgElement.videoElement.thumbPath?.values()!]
|
||||
const pathList = [picPath, ...picThumbPath, pttPath, filePath, videoPath, ...videoThumbPath]
|
||||
if (msgElement.picElement) {
|
||||
pathList.push(...Object.values(msgElement.picElement.thumbPath))
|
||||
}
|
||||
|
||||
// log("需要清理的文件", pathList);
|
||||
for (const path of pathList) {
|
||||
if (path) {
|
||||
fs.unlink(picPath, () => {
|
||||
log('删除文件成功', path)
|
||||
})
|
||||
}
|
||||
}
|
||||
}, getConfigUtil().getConfig().autoDeleteFileSecond! * 1000)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
registerReceiveHook<{ msgRecord: RawMessage }>(ReceiveCmdS.SELF_SEND_MSG, ({ msgRecord }) => {
|
||||
const message = msgRecord
|
||||
dbUtil.addMsg(message).then()
|
||||
})
|
||||
|
||||
registerReceiveHook<{ info: { status: number } }>(ReceiveCmdS.SELF_STATUS, (info) => {
|
||||
setSelfInfo({
|
||||
online: info.info.status !== 20
|
||||
})
|
||||
})
|
||||
|
||||
let activatedPeerUids: string[] = []
|
||||
registerReceiveHook<{
|
||||
changedRecentContactLists: {
|
||||
listType: number
|
||||
sortedContactList: string[]
|
||||
changedList: {
|
||||
id: string // peerUid
|
||||
chatType: ChatType
|
||||
}[]
|
||||
}[]
|
||||
}>(ReceiveCmdS.RECENT_CONTACT, async (payload) => {
|
||||
for (const recentContact of payload.changedRecentContactLists) {
|
||||
for (const changedContact of recentContact.changedList) {
|
||||
if (activatedPeerUids.includes(changedContact.id)) continue
|
||||
activatedPeerUids.push(changedContact.id)
|
||||
const peer = { peerUid: changedContact.id, chatType: changedContact.chatType }
|
||||
if (changedContact.chatType === ChatType.temp) {
|
||||
log('收到临时会话消息', peer)
|
||||
NTQQMsgApi.activateChatAndGetHistory(peer).then(() => {
|
||||
NTQQMsgApi.getMsgHistory(peer, '', 20).then(({ msgList }) => {
|
||||
let lastTempMsg = msgList.pop()
|
||||
log('激活窗口之前的第一条临时会话消息:', lastTempMsg)
|
||||
if (Date.now() / 1000 - parseInt(lastTempMsg?.msgTime!) < 5) {
|
||||
OB11Constructor.message(lastTempMsg!).then((r) => postOb11Event(r))
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
else {
|
||||
NTQQMsgApi.activateChat(peer).then()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
registerCallHook(NTQQApiMethod.DELETE_ACTIVE_CHAT, async (payload) => {
|
||||
const peerUid = payload[0] as string
|
||||
log('激活的聊天窗口被删除,准备重新激活', peerUid)
|
||||
let chatType = ChatType.friend
|
||||
if (isNumeric(peerUid)) {
|
||||
chatType = ChatType.group
|
||||
}
|
||||
else {
|
||||
// 检查是否好友
|
||||
if (!(await getFriend(peerUid))) {
|
||||
chatType = ChatType.temp
|
||||
}
|
||||
}
|
||||
const peer = { peerUid, chatType }
|
||||
await sleep(1000)
|
||||
NTQQMsgApi.activateChat(peer).then((r) => {
|
||||
log('重新激活聊天窗口', peer, { result: r.result, errMsg: r.errMsg })
|
||||
})
|
||||
})
|
||||
}
|
240
src/ntqqapi/listeners/NodeIKernelGroupListener.ts
Normal file
240
src/ntqqapi/listeners/NodeIKernelGroupListener.ts
Normal file
@@ -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<string, GroupMember>,
|
||||
finish: boolean,
|
||||
hasRobot: boolean
|
||||
}): void
|
||||
|
||||
onMemberInfoChange(groupCode: string, changeType: number, members: Map<string, GroupMember>): 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<string, GroupMember>) {
|
||||
}
|
||||
|
||||
onMemberListChange(arg: {
|
||||
sceneId: string,
|
||||
ids: string[],
|
||||
infos: Map<string, GroupMember>, // 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<string, GroupMember>) {
|
||||
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)
|
||||
}
|
||||
}
|
514
src/ntqqapi/listeners/NodeIKernelMsgListener.ts
Normal file
514
src/ntqqapi/listeners/NodeIKernelMsgListener.ts
Normal file
@@ -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<any>
|
||||
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<unknown>,
|
||||
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[]) {
|
||||
|
||||
}
|
||||
}
|
44
src/ntqqapi/listeners/NodeIKernelProfileListener.ts
Normal file
44
src/ntqqapi/listeners/NodeIKernelProfileListener.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import { User, UserDetailInfoListenerArg } from '@/ntqqapi/types'
|
||||
|
||||
interface IProfileListener {
|
||||
onProfileSimpleChanged(...args: unknown[]): void
|
||||
|
||||
onUserDetailInfoChanged(arg: UserDetailInfoListenerArg): void
|
||||
|
||||
onProfileDetailInfoChanged(profile: User): void
|
||||
|
||||
onStatusUpdate(...args: unknown[]): void
|
||||
|
||||
onSelfStatusChanged(...args: unknown[]): void
|
||||
|
||||
onStrangerRemarkChanged(...args: unknown[]): void
|
||||
}
|
||||
|
||||
export interface NodeIKernelProfileListener extends IProfileListener {
|
||||
new(listener: IProfileListener): NodeIKernelProfileListener
|
||||
}
|
||||
|
||||
export class ProfileListener implements IProfileListener {
|
||||
onUserDetailInfoChanged(arg: UserDetailInfoListenerArg): void {
|
||||
|
||||
}
|
||||
onProfileSimpleChanged(...args: unknown[]) {
|
||||
|
||||
}
|
||||
|
||||
onProfileDetailInfoChanged(profile: User) {
|
||||
|
||||
}
|
||||
|
||||
onStatusUpdate(...args: unknown[]) {
|
||||
|
||||
}
|
||||
|
||||
onSelfStatusChanged(...args: unknown[]) {
|
||||
|
||||
}
|
||||
|
||||
onStrangerRemarkChanged(...args: unknown[]) {
|
||||
|
||||
}
|
||||
}
|
3
src/ntqqapi/listeners/index.ts
Normal file
3
src/ntqqapi/listeners/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export * from './NodeIKernelProfileListener'
|
||||
export * from './NodeIKernelGroupListener'
|
||||
export * from './NodeIKernelMsgListener'
|
@@ -1,11 +1,8 @@
|
||||
import { ipcMain } from 'electron'
|
||||
import { hookApiCallbacks, ReceiveCmd, ReceiveCmdS, registerReceiveHook, removeReceiveHook } from './hook'
|
||||
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { log } from '../common/utils/log'
|
||||
import { NTQQWindow, NTQQWindowApi, NTQQWindows } from './api/window'
|
||||
import { WebApi } from './api/webapi'
|
||||
import { HOOK_LOG } from '../common/config'
|
||||
import { randomUUID } from 'node:crypto'
|
||||
|
||||
export enum NTQQApiClass {
|
||||
NT_API = 'ns-ntApi',
|
||||
@@ -21,19 +18,24 @@ export enum NTQQApiClass {
|
||||
}
|
||||
|
||||
export enum NTQQApiMethod {
|
||||
TEST = 'NodeIKernelTipOffService/getPskey',
|
||||
RECENT_CONTACT = 'nodeIKernelRecentContactService/fetchAndSubscribeABatchOfRecentContact',
|
||||
ACTIVE_CHAT_PREVIEW = 'nodeIKernelMsgService/getAioFirstViewLatestMsgsAndAddActiveChat', // 激活聊天窗口,有时候必须这样才能收到消息, 并返回最新预览消息
|
||||
ACTIVE_CHAT_HISTORY = 'nodeIKernelMsgService/getMsgsIncludeSelfAndAddActiveChat', // 激活聊天窗口,有时候必须这样才能收到消息, 并返回历史消息
|
||||
HISTORY_MSG = 'nodeIKernelMsgService/getMsgsIncludeSelf',
|
||||
GET_MULTI_MSG = 'nodeIKernelMsgService/getMultiMsg',
|
||||
DELETE_ACTIVE_CHAT = 'nodeIKernelMsgService/deleteActiveChatByUid',
|
||||
ENTER_OR_EXIT_AIO = 'nodeIKernelMsgService/enterOrExitAio',
|
||||
|
||||
LIKE_FRIEND = 'nodeIKernelProfileLikeService/setBuddyProfileLike',
|
||||
SELF_INFO = 'fetchAuthData',
|
||||
FRIENDS = 'nodeIKernelBuddyService/getBuddyList',
|
||||
|
||||
GROUPS = 'nodeIKernelGroupService/getGroupList',
|
||||
GROUP_MEMBER_SCENE = 'nodeIKernelGroupService/createMemberListScene',
|
||||
GROUP_MEMBERS = 'nodeIKernelGroupService/getNextMemberList',
|
||||
GROUP_MEMBERS_INFO = 'nodeIKernelGroupService/getMemberInfo',
|
||||
|
||||
USER_INFO = 'nodeIKernelProfileService/getUserSimpleInfo',
|
||||
USER_DETAIL_INFO = 'nodeIKernelProfileService/getUserDetailInfo',
|
||||
USER_DETAIL_INFO_WITH_BIZ_INFO = 'nodeIKernelProfileService/getUserDetailInfoWithBizInfo',
|
||||
@@ -65,6 +67,10 @@ export enum NTQQApiMethod {
|
||||
PUBLISH_GROUP_BULLETIN = 'nodeIKernelGroupService/publishGroupBulletinBulletin',
|
||||
SET_GROUP_NAME = 'nodeIKernelGroupService/modifyGroupName',
|
||||
SET_GROUP_TITLE = 'nodeIKernelGroupService/modifyMemberSpecialTitle',
|
||||
ACTIVATE_MEMBER_LIST_CHANGE = 'nodeIKernelGroupListener/onMemberListChange',
|
||||
ACTIVATE_MEMBER_INFO_CHANGE = 'nodeIKernelGroupListener/onMemberInfoChange',
|
||||
GET_MSG_BOX_INFO = 'nodeIKernelMsgService/getABatchOfContactMsgBoxInfo',
|
||||
GET_GROUP_ALL_INFO = 'nodeIKernelGroupService/getGroupAllInfo',
|
||||
|
||||
CACHE_SET_SILENCE = 'nodeIKernelStorageCleanService/setSilentScan',
|
||||
CACHE_ADD_SCANNED_PATH = 'nodeIKernelStorageCleanService/addCacheScanedPaths',
|
||||
@@ -81,7 +87,7 @@ export enum NTQQApiMethod {
|
||||
OPEN_EXTRA_WINDOW = 'openExternalWindow',
|
||||
|
||||
SET_QQ_AVATAR = 'nodeIKernelProfileService/setHeader',
|
||||
GET_SKEY = 'nodeIKernelTipOffService/getPskey',
|
||||
GET_PSKEY = 'nodeIKernelTipOffService/getPskey',
|
||||
UPDATE_SKEY = 'updatePskey',
|
||||
|
||||
FETCH_UNITED_COMMEND_CONFIG = 'nodeIKernelUnitedConfigService/fetchUnitedCommendConfig', // 发包需要调用的
|
||||
@@ -99,7 +105,7 @@ interface NTQQApiParams {
|
||||
channel?: NTQQApiChannel
|
||||
classNameIsRegister?: boolean
|
||||
args?: unknown[]
|
||||
cbCmd?: ReceiveCmd | null
|
||||
cbCmd?: ReceiveCmd | ReceiveCmd[] | null
|
||||
cmdCB?: (payload: any) => boolean
|
||||
afterFirstCmd?: boolean // 是否在methodName调用完之后再去hook cbCmd
|
||||
timeoutSecond?: number
|
||||
@@ -122,7 +128,7 @@ export function callNTQQApi<ReturnType>(params: NTQQApiParams) {
|
||||
args = args ?? []
|
||||
timeout = timeout ?? 5
|
||||
afterFirstCmd = afterFirstCmd ?? true
|
||||
const uuid = uuidv4()
|
||||
const uuid = randomUUID()
|
||||
HOOK_LOG && log('callNTQQApi', channel, className, methodName, args, uuid)
|
||||
return new Promise((resolve: (data: ReturnType) => void, reject) => {
|
||||
// log("callNTQQApiPromise", channel, className, methodName, args, uuid)
|
||||
@@ -139,7 +145,8 @@ export function callNTQQApi<ReturnType>(params: NTQQApiParams) {
|
||||
success = true
|
||||
resolve(r)
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
// 这里的callback比较特殊,QQ后端先返回是否调用成功,再返回一条结果数据
|
||||
const secondCallback = () => {
|
||||
const hookId = registerReceiveHook<ReturnType>(cbCmd, (payload) => {
|
||||
@@ -150,7 +157,8 @@ export function callNTQQApi<ReturnType>(params: NTQQApiParams) {
|
||||
success = true
|
||||
resolve(payload)
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
removeReceiveHook(hookId)
|
||||
success = true
|
||||
resolve(payload)
|
||||
@@ -162,7 +170,8 @@ export function callNTQQApi<ReturnType>(params: NTQQApiParams) {
|
||||
log(`${methodName} callback`, result)
|
||||
if (result?.result == 0 || result === undefined) {
|
||||
afterFirstCmd && secondCallback()
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
success = true
|
||||
reject(`ntqq api call failed, ${result.errMsg}`)
|
||||
}
|
||||
@@ -180,7 +189,8 @@ export function callNTQQApi<ReturnType>(params: NTQQApiParams) {
|
||||
channel,
|
||||
{
|
||||
sender: {
|
||||
send: (..._args: unknown[]) => {},
|
||||
send: (..._args: unknown[]) => {
|
||||
},
|
||||
},
|
||||
},
|
||||
{ type: 'request', callbackId: uuid, eventName },
|
||||
|
125
src/ntqqapi/services/NodeIKernelBuddyService.ts
Normal file
125
src/ntqqapi/services/NodeIKernelBuddyService.ts
Normal file
@@ -0,0 +1,125 @@
|
||||
import { GeneralCallResult } from './common'
|
||||
|
||||
export enum BuddyListReqType {
|
||||
KNOMAL,
|
||||
KLETTER
|
||||
}
|
||||
|
||||
export interface NodeIKernelBuddyService {
|
||||
// 26702 以上
|
||||
getBuddyListV2(callFrom: string, reqType: BuddyListReqType): Promise<GeneralCallResult & {
|
||||
data: Array<{
|
||||
categoryId: number,
|
||||
categorySortId: number,
|
||||
categroyName: string,
|
||||
categroyMbCount: number,
|
||||
onlineCount: number,
|
||||
buddyUids: Array<string>
|
||||
}>
|
||||
}>
|
||||
|
||||
//26702 以上
|
||||
getBuddyListFromCache(callFrom: string): Promise<Array<
|
||||
{
|
||||
categoryId: number,//9999应该跳过 那是兜底数据吧
|
||||
categorySortId: number,//排序方式
|
||||
categroyName: string,//分类名
|
||||
categroyMbCount: number,//不懂
|
||||
onlineCount: number,//在线数目
|
||||
buddyUids: Array<string>//Uids
|
||||
}>>
|
||||
|
||||
addKernelBuddyListener(listener: any): number
|
||||
|
||||
getAllBuddyCount(): number
|
||||
|
||||
removeKernelBuddyListener(listener: unknown): void
|
||||
|
||||
getBuddyList(nocache: boolean): Promise<GeneralCallResult>
|
||||
|
||||
getBuddyNick(uid: number): string
|
||||
|
||||
getBuddyRemark(uid: number): string
|
||||
|
||||
setBuddyRemark(uid: number, remark: string): void
|
||||
|
||||
getAvatarUrl(uid: number): string
|
||||
|
||||
isBuddy(uid: string): boolean
|
||||
|
||||
getCategoryNameWithUid(uid: number): string
|
||||
|
||||
getTargetBuddySetting(uid: number): unknown
|
||||
|
||||
getTargetBuddySettingByType(uid: number, type: number): unknown
|
||||
|
||||
getBuddyReqUnreadCnt(): number
|
||||
|
||||
getBuddyReq(): unknown
|
||||
|
||||
delBuddyReq(uid: number): void
|
||||
|
||||
clearBuddyReqUnreadCnt(): void
|
||||
|
||||
reqToAddFriends(uid: number, msg: string): void
|
||||
|
||||
setSpacePermission(uid: number, permission: number): void
|
||||
|
||||
approvalFriendRequest(arg: {
|
||||
friendUid: string
|
||||
reqTime: string
|
||||
accept: boolean
|
||||
}): Promise<void>
|
||||
|
||||
delBuddy(uid: number): void
|
||||
|
||||
delBatchBuddy(uids: number[]): void
|
||||
|
||||
getSmartInfos(uid: number): unknown
|
||||
|
||||
setBuddyCategory(uid: number, category: number): void
|
||||
|
||||
setBatchBuddyCategory(uids: number[], category: number): void
|
||||
|
||||
addCategory(category: string): void
|
||||
|
||||
delCategory(category: string): void
|
||||
|
||||
renameCategory(oldCategory: string, newCategory: string): void
|
||||
|
||||
resortCategory(categorys: string[]): void
|
||||
|
||||
pullCategory(uid: number, category: string): void
|
||||
|
||||
setTop(uid: number, isTop: boolean): void
|
||||
|
||||
SetSpecialCare(uid: number, isSpecialCare: boolean): void
|
||||
|
||||
setMsgNotify(uid: number, isNotify: boolean): void
|
||||
|
||||
hasBuddyList(): boolean
|
||||
|
||||
setBlock(uid: number, isBlock: boolean): void
|
||||
|
||||
isBlocked(uid: number): boolean
|
||||
|
||||
modifyAddMeSetting(setting: unknown): void
|
||||
|
||||
getAddMeSetting(): unknown
|
||||
|
||||
getDoubtBuddyReq(): unknown
|
||||
|
||||
getDoubtBuddyUnreadNum(): number
|
||||
|
||||
approvalDoubtBuddyReq(uid: number, isAgree: boolean): void
|
||||
|
||||
delDoubtBuddyReq(uid: number): void
|
||||
|
||||
delAllDoubtBuddyReq(): void
|
||||
|
||||
reportDoubtBuddyReqUnread(): void
|
||||
|
||||
getBuddyRecommendContactArkJson(uid: string, phoneNumber: string): Promise<unknown>
|
||||
|
||||
isNull(): boolean
|
||||
}
|
249
src/ntqqapi/services/NodeIKernelGroupService.ts
Normal file
249
src/ntqqapi/services/NodeIKernelGroupService.ts
Normal file
@@ -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<unknown>
|
||||
//26702
|
||||
getGroupMemberLevelInfo(groupCode: string): Promise<unknown>
|
||||
//26702
|
||||
getGroupHonorList(groupCodes: Array<string>): unknown
|
||||
|
||||
getUinByUids(uins: string[]): Promise<{
|
||||
errCode: number,
|
||||
errMsg: string,
|
||||
uins: Map<string, string>
|
||||
}>
|
||||
|
||||
getUidByUins(uins: string[]): Promise<{
|
||||
errCode: number,
|
||||
errMsg: string,
|
||||
uids: Map<string, string>
|
||||
}>
|
||||
//26702(其实更早 但是我不知道)
|
||||
checkGroupMemberCache(arrayList: Array<string>): Promise<unknown>
|
||||
|
||||
//26702(其实更早 但是我不知道)
|
||||
getGroupLatestEssenceList(groupCode: string): Promise<unknown>
|
||||
|
||||
//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<unknown>
|
||||
//26702(其实更早 但是我不知道)
|
||||
isEssenceMsg(Req: { groupCode: string, msgRandom: number, msgSeq: number }): Promise<unknown>
|
||||
//26702(其实更早 但是我不知道)
|
||||
queryCachedEssenceMsg(Req: { groupCode: string, msgRandom: number, msgSeq: number }): Promise<unknown>
|
||||
//26702(其实更早 但是我不知道)
|
||||
fetchGroupEssenceList(Req: { groupCode: string, pageStart: number, pageLimit: number }, Arg: unknown): Promise<unknown>
|
||||
//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<string, GroupMember>, finish: boolean, hasRobot: boolean }
|
||||
}>
|
||||
|
||||
getPrevMemberList(): unknown
|
||||
|
||||
monitorMemberList(): unknown
|
||||
|
||||
searchMember(sceneId: string, keywords: string[]): unknown
|
||||
|
||||
getMemberInfo(group_id: string, uids: string[], forceFetch: boolean): Promise<GeneralCallResult>
|
||||
//getMemberInfo [ '56729xxxx', [ 'u_4Nj08cwW5Hxxxxx' ], true ]
|
||||
|
||||
kickMember(groupCode: string, memberUids: string[], refuseForever: boolean, kickReason: string): Promise<void>
|
||||
|
||||
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<GeneralCallResult>
|
||||
|
||||
getGroupExtList(force: boolean): Promise<GeneralCallResult>
|
||||
|
||||
getGroupDetailInfo(groupCode: string): unknown
|
||||
|
||||
getMemberExtInfo(param: GroupExtParam): Promise<unknown>//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<GeneralCallResult>
|
||||
|
||||
clearGroupNotifies(groupCode: string): void
|
||||
|
||||
getGroupNotifiesUnreadCount(unknown: Boolean): Promise<GeneralCallResult>
|
||||
|
||||
clearGroupNotifiesUnreadCount(groupCode: string): void
|
||||
|
||||
operateSysNotify(
|
||||
doubt: boolean,
|
||||
operateMsg: {
|
||||
operateType: GroupRequestOperateTypes, // 2 拒绝
|
||||
targetMsg: {
|
||||
seq: string, // 通知序列号
|
||||
type: GroupNotifyTypes,
|
||||
groupCode: string,
|
||||
postscript: string
|
||||
}
|
||||
}): Promise<void>
|
||||
|
||||
setTop(groupCode: string, isTop: boolean): void
|
||||
|
||||
getGroupBulletin(groupCode: string): unknown
|
||||
|
||||
deleteGroupBulletin(groupCode: string, seq: string): void
|
||||
|
||||
publishGroupBulletin(groupCode: string, pskey: string, data: any): Promise<GeneralCallResult>
|
||||
|
||||
publishInstructionForNewcomers(groupCode: string, arg: unknown): void
|
||||
|
||||
uploadGroupBulletinPic(groupCode: string, pskey: string, imagePath: string): Promise<GeneralCallResult & {
|
||||
errCode: number
|
||||
picInfo?: {
|
||||
id: string,
|
||||
width: number,
|
||||
height: number
|
||||
}
|
||||
}>
|
||||
|
||||
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<void>
|
||||
|
||||
getGroupRecommendContactArkJson(groupCode: string): unknown
|
||||
|
||||
getJoinGroupLink(groupCode: string): unknown
|
||||
|
||||
modifyGroupExtInfo(groupCode: string, arg: unknown): void
|
||||
|
||||
//需要提前判断是否存在 高版本新增
|
||||
addGroupEssence(param: {
|
||||
groupCode: string
|
||||
msgRandom: number,
|
||||
msgSeq: number
|
||||
}): Promise<unknown>
|
||||
//需要提前判断是否存在 高版本新增
|
||||
removeGroupEssence(param: {
|
||||
groupCode: string
|
||||
msgRandom: number,
|
||||
msgSeq: number
|
||||
}): Promise<unknown>
|
||||
|
||||
isNull(): boolean
|
||||
}
|
3
src/ntqqapi/services/NodeIKernelMSFService.ts
Normal file
3
src/ntqqapi/services/NodeIKernelMSFService.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export interface NodeIKernelMSFService {
|
||||
getServerTime(): string
|
||||
}
|
744
src/ntqqapi/services/NodeIKernelMsgService.ts
Normal file
744
src/ntqqapi/services/NodeIKernelMsgService.ts
Normal file
@@ -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<any, any>): Promise<GeneralCallResult>
|
||||
|
||||
recallMsg(peer: Peer, msgIds: string[]): Promise<GeneralCallResult>
|
||||
|
||||
addKernelMsgImportToolListener(arg: Object): unknown
|
||||
|
||||
removeKernelMsgListener(args: unknown): unknown
|
||||
|
||||
addKernelTempChatSigListener(...args: unknown[]): unknown
|
||||
|
||||
removeKernelTempChatSigListener(...args: unknown[]): unknown
|
||||
|
||||
setAutoReplyTextList(AutoReplyText: Array<unknown>, i2: number): unknown
|
||||
|
||||
getAutoReplyTextList(...args: unknown[]): unknown
|
||||
|
||||
getOnLineDev(): void
|
||||
|
||||
kickOffLine(DevInfo: Object): unknown
|
||||
|
||||
setStatus(args: { status: number, extStatus: number, batteryStatus: number }): Promise<GeneralCallResult>
|
||||
|
||||
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<GeneralCallResult>
|
||||
|
||||
forwardMsgWithComment(...args: unknown[]): unknown
|
||||
|
||||
forwardSubMsgWithComment(...args: unknown[]): unknown
|
||||
|
||||
forwardRichMsgInVist(...args: unknown[]): unknown
|
||||
|
||||
forwardFile(...args: unknown[]): unknown
|
||||
//Array<Msg>, 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<any> | number, front: boolean): Promise<unknown>
|
||||
|
||||
deleteMsg(Peer: Peer, msgIds: Array<string>): Promise<any>
|
||||
|
||||
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<unknown>
|
||||
|
||||
getLastMessageList(peer: Peer[]): Promise<unknown>
|
||||
|
||||
getAioFirstViewLatestMsgs(peer: Peer, num: number): unknown
|
||||
|
||||
//deprecated 从9.9.15-26702版本开始,该接口已经废弃,请使用getMsgsEx
|
||||
getMsgs(peer: Peer, msgId: string, count: unknown, queryOrder: boolean): Promise<unknown>
|
||||
|
||||
getMsgsIncludeSelf(peer: Peer, msgId: string, count: number, queryOrder: boolean): Promise<GeneralCallResult & {
|
||||
msgList: RawMessage[]
|
||||
}>
|
||||
|
||||
// this.$peer = contact
|
||||
// this.$msgTime = j2
|
||||
// this.$clientSeq = j3
|
||||
// this.$cnt = i2
|
||||
|
||||
getMsgsWithMsgTimeAndClientSeqForC2C(...args: unknown[]): Promise<GeneralCallResult & { msgList: RawMessage[] }>
|
||||
|
||||
getMsgsWithStatus(params: {
|
||||
peer: Peer
|
||||
msgId: string
|
||||
msgTime: unknown
|
||||
cnt: unknown
|
||||
queryOrder: boolean
|
||||
isIncludeSelf: boolean
|
||||
appid: unknown
|
||||
}): Promise<GeneralCallResult & { msgList: RawMessage[] }>
|
||||
|
||||
getMsgsBySeqRange(peer: Peer, startSeq: string, endSeq: string): Promise<GeneralCallResult & { msgList: RawMessage[] }>
|
||||
|
||||
getMsgsBySeqAndCount(peer: Peer, seq: string, count: number, desc: boolean, unknownArg: boolean): Promise<GeneralCallResult & { msgList: RawMessage[] }>
|
||||
|
||||
getMsgsByMsgId(peer: Peer, ids: string[]): Promise<GeneralCallResult & { msgList: RawMessage[] }>
|
||||
|
||||
getRecallMsgsByMsgId(peer: Peer, MsgId: string[]): Promise<unknown>
|
||||
|
||||
getMsgsBySeqList(peer: Peer, seqList: string[]): Promise<GeneralCallResult & { msgList: RawMessage[] }>
|
||||
|
||||
getSingleMsg(Peer: Peer, msgSeq: string): Promise<GeneralCallResult & { msgList: RawMessage[] }>
|
||||
|
||||
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<number> }): unknown
|
||||
|
||||
getMsgsByTypeFilters(peer: Peer, msgId: string, cnt: unknown, queryOrder: boolean, typeFilters: Array<{ type: number, subtype: Array<number> }>): 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<unknown>
|
||||
|
||||
// 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<GeneralCallResult & {
|
||||
msgList: RawMessage[]
|
||||
}>
|
||||
//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<unknown>
|
||||
|
||||
queryPicOrVideoMsgsDesktop(...args: unknown[]): unknown
|
||||
|
||||
queryEmoticonMsgs(msgId: string, msgTime: string, msgSeq: string, Params: QueryMsgsParams): Promise<unknown>
|
||||
|
||||
queryTroopEmoticonMsgs(msgId: string, msgTime: string, msgSeq: string, Params: QueryMsgsParams): Promise<unknown>
|
||||
|
||||
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<GeneralCallResult>
|
||||
|
||||
setAllC2CAndGroupMsgRead(): Promise<unknown>
|
||||
|
||||
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<GeneralCallResult & {
|
||||
emojiInfoList: Array<{
|
||||
uin: string,
|
||||
emoId: number,
|
||||
emoPath: string,
|
||||
isExist: boolean,
|
||||
resId: string,
|
||||
url: string,
|
||||
md5: string,
|
||||
emoOriginalPath: string,
|
||||
thumbPath: string,
|
||||
RomaingType: string,
|
||||
isAPNG: false,
|
||||
isMarkFace: false,
|
||||
eId: string,
|
||||
epId: string,
|
||||
ocrWord: string,
|
||||
modifyWord: string,
|
||||
exposeNum: number,
|
||||
clickNum: number,
|
||||
desc: string
|
||||
}>
|
||||
}>
|
||||
|
||||
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<unknown>
|
||||
|
||||
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<GeneralCallResult & {
|
||||
msgList: RawMessage[]
|
||||
}>
|
||||
|
||||
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<unknown>
|
||||
|
||||
//chattype,uid->Promise<any>
|
||||
getTempChatInfo(ChatType: number, Uid: string): Promise<TmpChatInfoApi>
|
||||
|
||||
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<unknown>//频道的东西
|
||||
|
||||
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<unknown>//表情推荐?
|
||||
|
||||
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//这是嘛啊
|
||||
|
||||
}
|
22
src/ntqqapi/services/NodeIKernelProfileLikeService.ts
Normal file
22
src/ntqqapi/services/NodeIKernelProfileLikeService.ts
Normal file
@@ -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<GeneralCallResult & {
|
||||
'info': {
|
||||
'userLikeInfos': Array<any>,
|
||||
'friendMaxVotes': number,
|
||||
'start': number
|
||||
}
|
||||
}>
|
||||
|
||||
getProfileLikeScidResourceInfo(...args: unknown[]): void
|
||||
|
||||
isNull(): boolean
|
||||
}
|
106
src/ntqqapi/services/NodeIKernelProfileService.ts
Normal file
106
src/ntqqapi/services/NodeIKernelProfileService.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
import { AnyCnameRecord } from 'node:dns'
|
||||
import { SimpleInfo } from '../types'
|
||||
import { GeneralCallResult } from './common'
|
||||
|
||||
export enum UserDetailSource {
|
||||
KDB,
|
||||
KSERVER
|
||||
}
|
||||
|
||||
export enum ProfileBizType {
|
||||
KALL,
|
||||
KBASEEXTEND,
|
||||
KVAS,
|
||||
KQZONE,
|
||||
KOTHER
|
||||
}
|
||||
|
||||
export interface NodeIKernelProfileService {
|
||||
getUidByUin(callfrom: string, uin: Array<string>): Promise<Map<string,string>>//uin->uid
|
||||
|
||||
getUinByUid(callfrom: string, uid: Array<string>): Promise<Map<string,string>>
|
||||
|
||||
// {
|
||||
// coreInfo: CoreInfo,
|
||||
// baseInfo: BaseInfo,
|
||||
// status: null,
|
||||
// vasInfo: null,
|
||||
// relationFlags: null,
|
||||
// otherFlags: null,
|
||||
// intimate: null
|
||||
// }
|
||||
getCoreAndBaseInfo(callfrom: string, uids: string[]): Promise<Map<string, SimpleInfo>>
|
||||
|
||||
fetchUserDetailInfo(trace: string, uids: string[], arg2: number, arg3: number[]): Promise<unknown>
|
||||
|
||||
addKernelProfileListener(listener: any): number
|
||||
|
||||
removeKernelProfileListener(listenerId: number): void
|
||||
|
||||
prepareRegionConfig(...args: unknown[]): unknown
|
||||
|
||||
getLocalStrangerRemark(): Promise<AnyCnameRecord>
|
||||
|
||||
enumCountryOptions(): Array<string>
|
||||
|
||||
enumProvinceOptions(Country: string): Array<string>
|
||||
|
||||
enumCityOptions(Country: string, Province: string): unknown
|
||||
|
||||
enumAreaOptions(...args: unknown[]): unknown
|
||||
|
||||
//SimpleInfo
|
||||
// this.uid = ""
|
||||
// this.uid = str
|
||||
// this.uin = j2
|
||||
// this.isBuddy = z
|
||||
// this.coreInfo = coreInfo
|
||||
// this.baseInfo = baseInfo
|
||||
// this.status = statusInfo
|
||||
// this.vasInfo = vasInfo
|
||||
// this.relationFlags = relationFlag
|
||||
// this.otherFlags = otherFlag
|
||||
// this.intimate = intimate
|
||||
|
||||
modifySelfProfile(...args: unknown[]): Promise<unknown>
|
||||
|
||||
modifyDesktopMiniProfile(param: any): Promise<GeneralCallResult>
|
||||
|
||||
setNickName(NickName: string): Promise<unknown>
|
||||
|
||||
setLongNick(longNick: string): Promise<unknown>
|
||||
|
||||
setBirthday(...args: unknown[]): Promise<unknown>
|
||||
|
||||
setGander(...args: unknown[]): Promise<unknown>
|
||||
|
||||
setHeader(arg: string): Promise<unknown>
|
||||
|
||||
setRecommendImgFlag(...args: unknown[]): Promise<unknown>
|
||||
|
||||
getUserSimpleInfo(force: boolean, uids: string[],): Promise<unknown>
|
||||
|
||||
getUserDetailInfo(uid: string): Promise<unknown>
|
||||
|
||||
getUserDetailInfoWithBizInfo(uid: string, Biz: any[]): Promise<GeneralCallResult>
|
||||
|
||||
getUserDetailInfoByUin(uin: string): Promise<any>
|
||||
|
||||
getZplanAvatarInfos(args: string[]): Promise<unknown>
|
||||
|
||||
getStatus(uid: string): Promise<unknown>
|
||||
|
||||
startStatusPolling(isForceReset: boolean): Promise<unknown>
|
||||
|
||||
getSelfStatus(): Promise<unknown>
|
||||
|
||||
setdisableEmojiShortCuts(...args: unknown[]): unknown
|
||||
|
||||
getProfileQzonePicInfo(uid: string, type: number, force: boolean): Promise<unknown>
|
||||
|
||||
//profileService.getCoreInfo("UserRemarkServiceImpl::getStrangerRemarkByUid", arrayList)
|
||||
getCoreInfo(name: string, arg: any[]): unknown
|
||||
|
||||
//m429253e12.getOtherFlag("FriendListInfoCache_getKernelDataAndPutCache", new ArrayList<>())
|
||||
isNull(): boolean
|
||||
}
|
270
src/ntqqapi/services/NodeIKernelRichMediaService.ts
Normal file
270
src/ntqqapi/services/NodeIKernelRichMediaService.ts
Normal file
@@ -0,0 +1,270 @@
|
||||
import { GetFileListParam, MessageElement, Peer } from '../types'
|
||||
import { GeneralCallResult } from './common'
|
||||
|
||||
export enum UrlFileDownloadType {
|
||||
KUNKNOWN,
|
||||
KURLFILEDOWNLOADPRIVILEGEICON,
|
||||
KURLFILEDOWNLOADPHOTOWALL,
|
||||
KURLFILEDOWNLOADQZONE,
|
||||
KURLFILEDOWNLOADCOMMON,
|
||||
KURLFILEDOWNLOADINSTALLAPP
|
||||
}
|
||||
|
||||
export enum RMBizTypeEnum {
|
||||
KUNKNOWN,
|
||||
KC2CFILE,
|
||||
KGROUPFILE,
|
||||
KC2CPIC,
|
||||
KGROUPPIC,
|
||||
KDISCPIC,
|
||||
KC2CVIDEO,
|
||||
KGROUPVIDEO,
|
||||
KC2CPTT,
|
||||
KGROUPPTT,
|
||||
KFEEDCOMMENTPIC,
|
||||
KGUILDFILE,
|
||||
KGUILDPIC,
|
||||
KGUILDPTT,
|
||||
KGUILDVIDEO
|
||||
}
|
||||
|
||||
export interface CommonFileInfo {
|
||||
bizType: number
|
||||
chatType: number
|
||||
elemId: string
|
||||
favId: string
|
||||
fileModelId: string
|
||||
fileName: string
|
||||
fileSize: string
|
||||
md5: string
|
||||
md510m: string
|
||||
msgId: string
|
||||
msgTime: string
|
||||
parent: string
|
||||
peerUid: string
|
||||
picThumbPath: Array<string>
|
||||
sha: string
|
||||
sha3: string
|
||||
subId: string
|
||||
uuid: string
|
||||
}
|
||||
|
||||
export interface NodeIKernelRichMediaService {
|
||||
//getVideoPlayUrl(peer, msgId, elemId, videoCodecFormat, VideoRequestWay.KHAND, cb)
|
||||
// public enum VideoCodecFormatType {
|
||||
// KCODECFORMATH264,
|
||||
// KCODECFORMATH265,
|
||||
// KCODECFORMATH266,
|
||||
// KCODECFORMATAV1
|
||||
// }
|
||||
// public enum VideoRequestWay {
|
||||
// KUNKNOW,
|
||||
// KHAND,
|
||||
// KAUTO
|
||||
// }
|
||||
getVideoPlayUrl(peer: Peer, msgId: string, elemId: string, videoCodecFormat: number, VideoRequestWay: number): Promise<unknown>
|
||||
|
||||
//exParams (RMReqExParams)
|
||||
// this.downSourceType = i2
|
||||
// this.triggerType = i3
|
||||
//peer, msgId, elemId, videoCodecFormat, exParams
|
||||
// 1 0 频道在用
|
||||
// 1 1
|
||||
// 0 2
|
||||
|
||||
// public static final int KCOMMONREDENVELOPEMSGTYPEINMSGBOX = 1007
|
||||
// public static final int KDOWNSOURCETYPEAIOINNER = 1
|
||||
// public static final int KDOWNSOURCETYPEBIGSCREEN = 2
|
||||
// public static final int KDOWNSOURCETYPEHISTORY = 3
|
||||
// public static final int KDOWNSOURCETYPEUNKNOWN = 0
|
||||
|
||||
// public static final int KTRIGGERTYPEAUTO = 1
|
||||
// public static final int KTRIGGERTYPEMANUAL = 0
|
||||
|
||||
getVideoPlayUrlV2(peer: Peer, msgId: string, elemId: string, videoCodecFormat: number, exParams: { downSourceType: number, triggerType: number }): Promise<GeneralCallResult & {
|
||||
urlResult: {
|
||||
v4IpUrl: [],
|
||||
v6IpUrl: [],
|
||||
domainUrl: Array<{
|
||||
url: string,
|
||||
isHttps: boolean,
|
||||
httpsDomain: string
|
||||
}>,
|
||||
videoCodecFormat: number
|
||||
}
|
||||
}>
|
||||
|
||||
getRichMediaFileDir(elementType: number, downType: number, isTemp: boolean): unknown
|
||||
|
||||
// this.senderUid = ""
|
||||
// this.peerUid = ""
|
||||
// this.guildId = ""
|
||||
// this.elem = new MsgElement()
|
||||
// this.downloadType = i2
|
||||
// this.thumbSize = i3
|
||||
// this.msgId = j2
|
||||
// this.msgRandom = j3
|
||||
// this.msgSeq = j4
|
||||
// this.msgTime = j5
|
||||
// this.chatType = i4
|
||||
// this.senderUid = str
|
||||
// this.peerUid = str2
|
||||
// this.guildId = str3
|
||||
// this.elem = msgElement
|
||||
// this.useHttps = num
|
||||
|
||||
getVideoPlayUrlInVisit(arg: {
|
||||
downloadType: number,
|
||||
thumbSize: number,
|
||||
msgId: string,
|
||||
msgRandom: string,
|
||||
msgSeq: string,
|
||||
msgTime: string,
|
||||
chatType: number,
|
||||
senderUid: string,
|
||||
peerUid: string,
|
||||
guildId: string,
|
||||
ele: MessageElement,
|
||||
useHttps: boolean
|
||||
}): Promise<unknown>
|
||||
|
||||
//arg双端number
|
||||
isFileExpired(arg: number): unknown
|
||||
|
||||
deleteGroupFolder(GroupCode: string, FolderId: string): Promise<GeneralCallResult & { groupFileCommonResult: { retCode: number, retMsg: string, clientWording: string } }>
|
||||
|
||||
//参数与getVideoPlayUrlInVisit一样
|
||||
downloadRichMediaInVisit(arg: {
|
||||
downloadType: number,
|
||||
thumbSize: number,
|
||||
msgId: string,
|
||||
msgRandom: string,
|
||||
msgSeq: string,
|
||||
msgTime: string,
|
||||
chatType: number,
|
||||
senderUid: string,
|
||||
peerUid: string,
|
||||
guildId: string,
|
||||
ele: MessageElement,
|
||||
useHttps: boolean
|
||||
}): unknown
|
||||
//arg3为“”
|
||||
downloadFileForModelId(peer: Peer, ModelId: string[], arg3: string): unknown
|
||||
//第三个参数 Array<Type>
|
||||
// this.fileId = ""
|
||||
// this.fileName = ""
|
||||
// this.fileId = str
|
||||
// this.fileName = str2
|
||||
// this.fileSize = j2
|
||||
// this.fileModelId = j3
|
||||
|
||||
downloadFileForFileUuid(peer: Peer, uuid: string, arg3: {
|
||||
fileId: string,
|
||||
fileName: string,
|
||||
fileSize: string,
|
||||
fileModelId: string
|
||||
}[]): Promise<unknown>
|
||||
|
||||
downloadFileByUrlList(fileDownloadTyp: UrlFileDownloadType, urlList: Array<string>): unknown
|
||||
|
||||
downloadFileForFileInfo(fileInfo: CommonFileInfo[], savePath: string): unknown
|
||||
|
||||
createGroupFolder(GroupCode: string, FolderName: string): Promise<GeneralCallResult & { resultWithGroupItem: { result: any, groupItem: Array<any> } }>
|
||||
|
||||
downloadFile(commonFile: CommonFileInfo, arg2: unknown, arg3: unknown, savePath: string): unknown
|
||||
|
||||
createGroupFolder(arg1: unknown, arg2: unknown): unknown
|
||||
|
||||
downloadGroupFolder(arg1: unknown, arg2: unknown, arg3: unknown): unknown
|
||||
|
||||
renameGroupFolder(arg1: unknown, arg2: unknown, arg3: unknown): unknown
|
||||
|
||||
deleteGroupFolder(arg1: unknown, arg2: unknown): unknown
|
||||
|
||||
deleteTransferInfo(arg1: unknown, arg2: unknown): unknown
|
||||
|
||||
cancelTransferTask(arg1: unknown, arg2: unknown, arg3: unknown): unknown
|
||||
|
||||
cancelUrlDownload(arg: unknown): unknown
|
||||
|
||||
updateOnlineVideoElemStatus(arg: unknown): unknown
|
||||
|
||||
getGroupSpace(arg: unknown): unknown
|
||||
|
||||
getGroupFileList(groupCode: string, params: GetFileListParam): Promise<GeneralCallResult & {
|
||||
groupSpaceResult: {
|
||||
retCode: number
|
||||
retMsg: string
|
||||
clientWording: string
|
||||
totalSpace: number
|
||||
usedSpace: number
|
||||
allUpload: boolean
|
||||
}
|
||||
}>
|
||||
|
||||
getGroupFileInfo(arg1: unknown, arg2: unknown): unknown
|
||||
|
||||
getGroupTransferList(arg1: unknown, arg2: unknown): unknown
|
||||
|
||||
renameGroupFile(arg1: unknown, arg2: unknown, arg3: unknown, arg4: unknown, arg5: unknown): unknown
|
||||
|
||||
moveGroupFile(arg1: unknown, arg2: unknown, arg3: unknown, arg4: unknown, arg5: unknown): unknown
|
||||
|
||||
transGroupFile(arg1: unknown, arg2: unknown): unknown
|
||||
|
||||
searchGroupFile(
|
||||
keywords: Array<string>,
|
||||
param: {
|
||||
groupIds: Array<string>,
|
||||
fileType: number,
|
||||
context: string,
|
||||
count: number,
|
||||
sortType: number,
|
||||
groupNames: Array<string>
|
||||
}): Promise<unknown>
|
||||
searchGroupFileByWord(arg1: unknown, arg2: unknown, arg3: unknown, arg4: unknown, arg5: unknown): unknown
|
||||
|
||||
deleteGroupFile(GroupCode: string, params: Array<number>, Files: Array<string>): Promise<GeneralCallResult & {
|
||||
transGroupFileResult: {
|
||||
result: any
|
||||
successFileIdList: Array<any>
|
||||
failFileIdList: Array<any>
|
||||
}
|
||||
}>
|
||||
|
||||
translateEnWordToZn(words: string[]): Promise<GeneralCallResult & { words: string[] }>
|
||||
|
||||
getScreenOCR(path: string): Promise<unknown>
|
||||
|
||||
batchGetGroupFileCount(Gids: Array<string>): Promise<GeneralCallResult & { groupCodes: Array<string>, groupFileCounts: Array<number> }>
|
||||
|
||||
queryPicDownloadSize(arg: unknown): unknown
|
||||
|
||||
searchGroupFile(arg1: unknown, arg2: unknown): unknown
|
||||
|
||||
searchMoreGroupFile(arg: unknown): unknown
|
||||
|
||||
cancelSearcheGroupFile(arg1: unknown, arg2: unknown, arg3: unknown): unknown
|
||||
|
||||
onlyDownloadFile(peer: Peer, arg2: unknown, arg3: Array<{
|
||||
fileId: string,
|
||||
fileName: string,
|
||||
fileSize: string,
|
||||
fileModelId: string
|
||||
}
|
||||
>): unknown
|
||||
|
||||
onlyUploadFile(arg1: unknown, arg2: unknown): unknown
|
||||
|
||||
isExtraLargePic(arg1: unknown, arg2: unknown, arg3: unknown): unknown
|
||||
|
||||
uploadRMFileWithoutMsg(arg: {
|
||||
bizType: RMBizTypeEnum,
|
||||
filePath: string,
|
||||
peerUid: string,
|
||||
transferId: string
|
||||
useNTV2: string
|
||||
}): Promise<unknown>
|
||||
|
||||
isNull(): boolean
|
||||
}
|
11
src/ntqqapi/services/NodeIKernelTicketService.ts
Normal file
11
src/ntqqapi/services/NodeIKernelTicketService.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { forceFetchClientKeyRetType } from './common'
|
||||
|
||||
export interface NodeIKernelTicketService {
|
||||
addKernelTicketListener(listener: unknown): void
|
||||
|
||||
removeKernelTicketListener(listenerId: unknown): void
|
||||
|
||||
forceFetchClientKey(arg: string): Promise<forceFetchClientKeyRetType>
|
||||
|
||||
isNull(): boolean
|
||||
}
|
19
src/ntqqapi/services/NodeIKernelTipOffService.ts
Normal file
19
src/ntqqapi/services/NodeIKernelTipOffService.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { GeneralCallResult } from './common'
|
||||
|
||||
export interface NodeIKernelTipOffService {
|
||||
addKernelTipOffListener(listener: unknown): void
|
||||
|
||||
removeKernelTipOffListener(listenerId: unknown): void
|
||||
|
||||
tipOffSendJsData(args: unknown[]): Promise<unknown> //2
|
||||
|
||||
getPskey(domainList: string[], nocache: boolean): Promise<GeneralCallResult & { domainPskeyMap: Map<string, string> }> //2
|
||||
|
||||
tipOffSendJsData(args: unknown[]): Promise<unknown> //2
|
||||
|
||||
tipOffMsgs(args: unknown[]): Promise<unknown> //1
|
||||
|
||||
encodeUinAesInfo(args: unknown[]): Promise<unknown> //2
|
||||
|
||||
isNull(): boolean
|
||||
}
|
5
src/ntqqapi/services/NodeIKernelUixConvertService.ts
Normal file
5
src/ntqqapi/services/NodeIKernelUixConvertService.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export interface NodeIKernelUixConvertService {
|
||||
getUin(uid: string[]): Promise<{ uinInfo: Map<string, string> }>
|
||||
|
||||
getUid(uin: string[]): Promise<{ uidInfo: Map<string, string> }>
|
||||
}
|
16
src/ntqqapi/services/common.ts
Normal file
16
src/ntqqapi/services/common.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
export enum GeneralCallResultStatus {
|
||||
OK = 0
|
||||
// ERROR = 1
|
||||
}
|
||||
|
||||
export interface GeneralCallResult {
|
||||
result: GeneralCallResultStatus
|
||||
errMsg: string
|
||||
}
|
||||
|
||||
export interface forceFetchClientKeyRetType extends GeneralCallResult {
|
||||
url: string
|
||||
keyIndex: string
|
||||
clientKey: string
|
||||
expireTime: string
|
||||
}
|
10
src/ntqqapi/services/index.ts
Normal file
10
src/ntqqapi/services/index.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
export * from './NodeIKernelBuddyService'
|
||||
export * from './NodeIKernelProfileService'
|
||||
export * from './NodeIKernelGroupService'
|
||||
export * from './NodeIKernelProfileLikeService'
|
||||
export * from './NodeIKernelMsgService'
|
||||
export * from './NodeIKernelMSFService'
|
||||
export * from './NodeIKernelUixConvertService'
|
||||
export * from './NodeIKernelRichMediaService'
|
||||
export * from './NodeIKernelTicketService'
|
||||
export * from './NodeIKernelTipOffService'
|
@@ -1,5 +1,12 @@
|
||||
import { QQLevel, Sex } from './user'
|
||||
|
||||
export enum GroupListUpdateType {
|
||||
REFRESHALL,
|
||||
GETALL,
|
||||
MODIFIED,
|
||||
REMOVE
|
||||
}
|
||||
|
||||
export interface Group {
|
||||
groupCode: string
|
||||
maxMember: number
|
||||
|
@@ -1,7 +1,15 @@
|
||||
import { GroupMemberRole } from './group'
|
||||
import exp from 'constants'
|
||||
|
||||
export interface GetFileListParam {
|
||||
sortType: number
|
||||
fileCount: number
|
||||
startIndex: number
|
||||
sortOrder: number
|
||||
showOnlinedocFolder: number
|
||||
}
|
||||
|
||||
export enum ElementType {
|
||||
UNKNOWN = 0,
|
||||
TEXT = 1,
|
||||
PIC = 2,
|
||||
FILE = 3,
|
||||
@@ -9,20 +17,36 @@ export enum ElementType {
|
||||
VIDEO = 5,
|
||||
FACE = 6,
|
||||
REPLY = 7,
|
||||
WALLET = 9,
|
||||
GreyTip = 8, //Poke别叫戳一搓了 官方名字拍一拍 戳一戳是另一个名字
|
||||
ARK = 10,
|
||||
MFACE = 11,
|
||||
LIVEGIFT = 12,
|
||||
STRUCTLONGMSG = 13,
|
||||
MARKDOWN = 14,
|
||||
GIPHY = 15,
|
||||
MULTIFORWARD = 16,
|
||||
INLINEKEYBOARD = 17,
|
||||
INTEXTGIFT = 18,
|
||||
CALENDAR = 19,
|
||||
YOLOGAMERESULT = 20,
|
||||
AVRECORD = 21,
|
||||
FEED = 22,
|
||||
TOFURECORD = 23,
|
||||
ACEBUBBLE = 24,
|
||||
ACTIVITY = 25,
|
||||
TOFU = 26,
|
||||
FACEBUBBLE = 27,
|
||||
SHARELOCATION = 28,
|
||||
TASKTOPMSG = 29,
|
||||
RECOMMENDEDMSG = 43,
|
||||
ACTIONBAR = 44
|
||||
}
|
||||
|
||||
export interface SendTextElement {
|
||||
elementType: ElementType.TEXT
|
||||
elementId: ''
|
||||
textElement: {
|
||||
content: string
|
||||
atType: number
|
||||
atUid: string
|
||||
atTinyId: string
|
||||
atNtUid: string
|
||||
}
|
||||
textElement: TextElement
|
||||
}
|
||||
|
||||
export interface SendPttElement {
|
||||
@@ -78,12 +102,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 {
|
||||
@@ -97,19 +116,35 @@ 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?: ''
|
||||
fileMd5?: string
|
||||
fileName: string
|
||||
filePath: string
|
||||
fileSize: string
|
||||
picHeight?: number
|
||||
picWidth?: number
|
||||
picThumbPath?: {}
|
||||
file10MMd5?: ''
|
||||
fileSha?: ''
|
||||
fileSha3?: ''
|
||||
fileUuid?: ''
|
||||
fileSubId?: ''
|
||||
folderId?: string
|
||||
picThumbPath?: Map<number, string>
|
||||
file10MMd5?: string
|
||||
fileSha?: string
|
||||
fileSha3?: string
|
||||
fileUuid?: string
|
||||
fileSubId?: string
|
||||
thumbFileSize?: number
|
||||
fileBizId?: number
|
||||
}
|
||||
@@ -155,6 +190,50 @@ export enum ChatType {
|
||||
temp = 100,
|
||||
}
|
||||
|
||||
// 来自Android分析
|
||||
export enum ChatType2 {
|
||||
KCHATTYPEADELIE = 42,
|
||||
KCHATTYPEBUDDYNOTIFY = 5,
|
||||
KCHATTYPEC2C = 1,
|
||||
KCHATTYPECIRCLE = 113,
|
||||
KCHATTYPEDATALINE = 8,
|
||||
KCHATTYPEDATALINEMQQ = 134,
|
||||
KCHATTYPEDISC = 3,
|
||||
KCHATTYPEFAV = 41,
|
||||
KCHATTYPEGAMEMESSAGE = 105,
|
||||
KCHATTYPEGAMEMESSAGEFOLDER = 116,
|
||||
KCHATTYPEGROUP = 2,
|
||||
KCHATTYPEGROUPBLESS = 133,
|
||||
KCHATTYPEGROUPGUILD = 9,
|
||||
KCHATTYPEGROUPHELPER = 7,
|
||||
KCHATTYPEGROUPNOTIFY = 6,
|
||||
KCHATTYPEGUILD = 4,
|
||||
KCHATTYPEGUILDMETA = 16,
|
||||
KCHATTYPEMATCHFRIEND = 104,
|
||||
KCHATTYPEMATCHFRIENDFOLDER = 109,
|
||||
KCHATTYPENEARBY = 106,
|
||||
KCHATTYPENEARBYASSISTANT = 107,
|
||||
KCHATTYPENEARBYFOLDER = 110,
|
||||
KCHATTYPENEARBYHELLOFOLDER = 112,
|
||||
KCHATTYPENEARBYINTERACT = 108,
|
||||
KCHATTYPEQQNOTIFY = 132,
|
||||
KCHATTYPERELATEACCOUNT = 131,
|
||||
KCHATTYPESERVICEASSISTANT = 118,
|
||||
KCHATTYPESERVICEASSISTANTSUB = 201,
|
||||
KCHATTYPESQUAREPUBLIC = 115,
|
||||
KCHATTYPESUBSCRIBEFOLDER = 30,
|
||||
KCHATTYPETEMPADDRESSBOOK = 111,
|
||||
KCHATTYPETEMPBUSSINESSCRM = 102,
|
||||
KCHATTYPETEMPC2CFROMGROUP = 100,
|
||||
KCHATTYPETEMPC2CFROMUNKNOWN = 99,
|
||||
KCHATTYPETEMPFRIENDVERIFY = 101,
|
||||
KCHATTYPETEMPNEARBYPRO = 119,
|
||||
KCHATTYPETEMPPUBLICACCOUNT = 103,
|
||||
KCHATTYPETEMPWPA = 117,
|
||||
KCHATTYPEUNKNOWN = 0,
|
||||
KCHATTYPEWEIYUN = 40,
|
||||
}
|
||||
|
||||
export interface PttElement {
|
||||
canConvert2Text: boolean
|
||||
duration: number // 秒数
|
||||
@@ -188,6 +267,8 @@ export const IMAGE_HTTP_HOST = 'https://gchat.qpic.cn'
|
||||
export const IMAGE_HTTP_HOST_NT = 'https://multimedia.nt.qq.com.cn'
|
||||
|
||||
export interface PicElement {
|
||||
picSubType: PicSubType
|
||||
picType: PicType // 有这玩意儿吗
|
||||
originImageUrl: string // http url, 没有host,host是https://gchat.qpic.cn/, 带download参数的是https://multimedia.nt.qq.com.cn
|
||||
originImageMd5?: string
|
||||
sourcePath: string // 图片本地路径
|
||||
@@ -201,6 +282,7 @@ export interface PicElement {
|
||||
}
|
||||
|
||||
export enum GrayTipElementSubType {
|
||||
RECALL = 1,
|
||||
INVITE_NEW_MEMBER = 12,
|
||||
MEMBER_NEW_TITLE = 17,
|
||||
}
|
||||
@@ -213,6 +295,8 @@ export interface GrayTipElement {
|
||||
operatorNick: string
|
||||
operatorRemark: string
|
||||
operatorMemRemark?: string
|
||||
origMsgSenderUid?: string
|
||||
isSelfOperate?: boolean
|
||||
wording: string // 自定义的撤回提示语
|
||||
}
|
||||
aioOpGrayTipElement: TipAioOpGrayTipElement
|
||||
@@ -222,15 +306,11 @@ export interface GrayTipElement {
|
||||
content: string
|
||||
}
|
||||
jsonGrayTipElement: {
|
||||
busiId: number
|
||||
jsonStr: string
|
||||
}
|
||||
}
|
||||
|
||||
export enum FaceType {
|
||||
normal = 1, // 小黄脸
|
||||
normal2 = 2, // 新小黄脸, 从faceIndex 222开始?
|
||||
dice = 3, // 骰子
|
||||
}
|
||||
|
||||
export enum FaceIndex {
|
||||
dice = 358,
|
||||
@@ -239,7 +319,7 @@ export enum FaceIndex {
|
||||
|
||||
export interface FaceElement {
|
||||
faceIndex: number
|
||||
faceType: FaceType
|
||||
faceType: number
|
||||
faceText?: string
|
||||
packId?: string
|
||||
stickerId?: string
|
||||
@@ -377,19 +457,23 @@ export interface RawMessage {
|
||||
msgShortId?: number // 自己维护的消息id
|
||||
msgTime: string // 时间戳,秒
|
||||
msgSeq: string
|
||||
msgRandom: string
|
||||
senderUid: string
|
||||
senderUin?: string // 发送者QQ号
|
||||
peerUid: string // 群号 或者 QQ uid
|
||||
peerUin: string // 群号 或者 发送者QQ号
|
||||
guildId: string
|
||||
sendNickName: string
|
||||
sendMemberName?: string // 发送者群名片
|
||||
chatType: ChatType
|
||||
sendStatus?: number // 消息状态,别人发的2是已撤回,自己发的2是已发送
|
||||
recallTime: string // 撤回时间, "0"是没有撤回
|
||||
records: RawMessage[]
|
||||
elements: {
|
||||
elementId: string
|
||||
elementType: ElementType
|
||||
replyElement: {
|
||||
sourceMsgIdInRecords: string
|
||||
senderUid: string // 原消息发送者QQ号
|
||||
sourceMsgIsIncPic: boolean // 原消息是否有图片
|
||||
sourceMsgText: string
|
||||
@@ -414,3 +498,43 @@ export interface RawMessage {
|
||||
multiForwardMsgElement: MultiForwardMsgElement
|
||||
}[]
|
||||
}
|
||||
|
||||
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
|
||||
}
|
@@ -1,12 +1,13 @@
|
||||
export enum GroupNotifyTypes {
|
||||
INVITE_ME = 1,
|
||||
INVITED_JOIN = 4, // 有人接受了邀请入群
|
||||
JOIN_REQUEST_BY_INVITED = 5, // 有人邀请了别人入群
|
||||
JOIN_REQUEST = 7,
|
||||
ADMIN_SET = 8,
|
||||
KICK_MEMBER = 9,
|
||||
MEMBER_EXIT = 11, // 主动退出
|
||||
ADMIN_UNSET = 12, // 我被取消管理员
|
||||
ADMIN_UNSET_OTHER = 13, // 其他人取消管理员
|
||||
ADMIN_UNSET = 12, // 我被取消管理员
|
||||
ADMIN_UNSET_OTHER = 13, // 其他人取消管理员
|
||||
}
|
||||
|
||||
export interface GroupNotifies {
|
||||
@@ -47,8 +48,28 @@ export enum GroupRequestOperateTypes {
|
||||
reject = 2,
|
||||
}
|
||||
|
||||
export enum BuddyReqType {
|
||||
KMEINITIATOR,
|
||||
KPEERINITIATOR,
|
||||
KMEAGREED,
|
||||
KMEAGREEDANDADDED,
|
||||
KPEERAGREED,
|
||||
KPEERAGREEDANDADDED,
|
||||
KPEERREFUSED,
|
||||
KMEREFUSED,
|
||||
KMEIGNORED,
|
||||
KMEAGREEANYONE,
|
||||
KMESETQUESTION,
|
||||
KMEAGREEANDADDFAILED,
|
||||
KMSGINFO,
|
||||
KMEINITIATORWAITPEERCONFIRM
|
||||
}
|
||||
|
||||
export interface FriendRequest {
|
||||
isInitiator?: boolean
|
||||
isDecide: boolean
|
||||
friendUid: string
|
||||
reqType: BuddyReqType
|
||||
reqTime: string // 时间戳,秒
|
||||
extWords: string // 申请人填写的验证消息
|
||||
isUnread: boolean
|
||||
@@ -63,3 +84,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<string>
|
||||
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
|
||||
}
|
||||
}
|
@@ -10,6 +10,7 @@ export interface QQLevel {
|
||||
moonNum: number
|
||||
starNum: number
|
||||
}
|
||||
|
||||
export interface User {
|
||||
uid: string // 加密的字符串
|
||||
uin: string // QQ号
|
||||
@@ -72,4 +73,271 @@ export interface SelfInfo extends User {
|
||||
online?: boolean
|
||||
}
|
||||
|
||||
export interface Friend extends User {}
|
||||
export interface Friend extends User {
|
||||
}
|
||||
|
||||
export interface CategoryFriend {
|
||||
categoryId: number
|
||||
categroyName: string
|
||||
categroyMbCount: number
|
||||
buddyList: User[]
|
||||
}
|
||||
|
||||
export interface CoreInfo {
|
||||
uid: string
|
||||
uin: string
|
||||
nick: string
|
||||
remark: string
|
||||
}
|
||||
|
||||
export interface BaseInfo {
|
||||
qid: string
|
||||
longNick: string
|
||||
birthday_year: number
|
||||
birthday_month: number
|
||||
birthday_day: number
|
||||
age: number
|
||||
sex: number
|
||||
eMail: string
|
||||
phoneNum: string
|
||||
categoryId: number
|
||||
richTime: number
|
||||
richBuffer: string
|
||||
}
|
||||
|
||||
interface MusicInfo {
|
||||
buf: string
|
||||
}
|
||||
|
||||
interface VideoBizInfo {
|
||||
cid: string
|
||||
tvUrl: string
|
||||
synchType: string
|
||||
}
|
||||
|
||||
interface VideoInfo {
|
||||
name: string
|
||||
}
|
||||
|
||||
interface ExtOnlineBusinessInfo {
|
||||
buf: string
|
||||
customStatus: any
|
||||
videoBizInfo: VideoBizInfo
|
||||
videoInfo: VideoInfo
|
||||
}
|
||||
|
||||
interface ExtBuffer {
|
||||
buf: string
|
||||
}
|
||||
|
||||
interface UserStatus {
|
||||
uid: string
|
||||
uin: string
|
||||
status: number
|
||||
extStatus: number
|
||||
batteryStatus: number
|
||||
termType: number
|
||||
netType: number
|
||||
iconType: number
|
||||
customStatus: any
|
||||
setTime: string
|
||||
specialFlag: number
|
||||
abiFlag: number
|
||||
eNetworkType: number
|
||||
showName: string
|
||||
termDesc: string
|
||||
musicInfo: MusicInfo
|
||||
extOnlineBusinessInfo: ExtOnlineBusinessInfo
|
||||
extBuffer: ExtBuffer
|
||||
}
|
||||
|
||||
interface PrivilegeIcon {
|
||||
jumpUrl: string
|
||||
openIconList: any[]
|
||||
closeIconList: any[]
|
||||
}
|
||||
|
||||
interface VasInfo {
|
||||
vipFlag: boolean
|
||||
yearVipFlag: boolean
|
||||
svipFlag: boolean
|
||||
vipLevel: number
|
||||
bigClub: boolean
|
||||
bigClubLevel: number
|
||||
nameplateVipType: number
|
||||
grayNameplateFlag: number
|
||||
superVipTemplateId: number
|
||||
diyFontId: number
|
||||
pendantId: number
|
||||
pendantDiyId: number
|
||||
faceId: number
|
||||
vipFont: number
|
||||
vipFontType: number
|
||||
magicFont: number
|
||||
fontEffect: number
|
||||
newLoverDiamondFlag: number
|
||||
extendNameplateId: number
|
||||
diyNameplateIDs: any[]
|
||||
vipStartFlag: number
|
||||
vipDataFlag: number
|
||||
gameNameplateId: string
|
||||
gameLastLoginTime: string
|
||||
gameRank: number
|
||||
gameIconShowFlag: boolean
|
||||
gameCardId: string
|
||||
vipNameColorId: string
|
||||
privilegeIcon: PrivilegeIcon
|
||||
}
|
||||
|
||||
export interface SimpleInfo {
|
||||
uid?: string
|
||||
uin?: string
|
||||
coreInfo: CoreInfo
|
||||
baseInfo: BaseInfo
|
||||
status: UserStatus | null
|
||||
vasInfo: VasInfo | null
|
||||
relationFlags: RelationFlags | null
|
||||
otherFlags: any | null
|
||||
intimate: any | null
|
||||
}
|
||||
|
||||
interface RelationFlags {
|
||||
topTime: string
|
||||
isBlock: boolean
|
||||
isMsgDisturb: boolean
|
||||
isSpecialCareOpen: boolean
|
||||
isSpecialCareZone: boolean
|
||||
ringId: string
|
||||
isBlocked: boolean
|
||||
recommendImgFlag: number
|
||||
disableEmojiShortCuts: number
|
||||
qidianMasterFlag: number
|
||||
qidianCrewFlag: number
|
||||
qidianCrewFlag2: number
|
||||
isHideQQLevel: number
|
||||
isHidePrivilegeIcon: number
|
||||
}
|
||||
|
||||
export interface FriendV2 extends SimpleInfo {
|
||||
categoryId?: number
|
||||
categroyName?: string
|
||||
}
|
||||
|
||||
interface CommonExt {
|
||||
constellation: number
|
||||
shengXiao: number
|
||||
kBloodType: number
|
||||
homeTown: string
|
||||
makeFriendCareer: number
|
||||
pos: string
|
||||
college: string
|
||||
country: string
|
||||
province: string
|
||||
city: string
|
||||
postCode: string
|
||||
address: string
|
||||
regTime: number
|
||||
interest: string
|
||||
labels: any[]
|
||||
qqLevel: QQLevel
|
||||
}
|
||||
|
||||
interface Pic {
|
||||
picId: string
|
||||
picTime: number
|
||||
picUrlMap: Record<string, string>
|
||||
}
|
||||
|
||||
interface PhotoWall {
|
||||
picList: Pic[]
|
||||
}
|
||||
|
||||
export interface UserDetailInfoListenerArg {
|
||||
uid: string
|
||||
uin: string
|
||||
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
|
||||
}
|
||||
}
|
92
src/ntqqapi/wrapper.ts
Normal file
92
src/ntqqapi/wrapper.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
import {
|
||||
NodeIKernelBuddyService,
|
||||
NodeIKernelGroupService,
|
||||
NodeIKernelProfileService,
|
||||
NodeIKernelProfileLikeService,
|
||||
NodeIKernelMSFService,
|
||||
NodeIKernelMsgService,
|
||||
NodeIKernelUixConvertService,
|
||||
NodeIKernelRichMediaService,
|
||||
NodeIKernelTicketService,
|
||||
NodeIKernelTipOffService
|
||||
} 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
|
||||
getMsgService(): NodeIKernelMsgService
|
||||
getMSFService(): NodeIKernelMSFService
|
||||
getUixConvertService(): NodeIKernelUixConvertService
|
||||
getRichMediaService(): NodeIKernelRichMediaService
|
||||
getTicketService(): NodeIKernelTicketService
|
||||
getTipOffService(): NodeIKernelTipOffService
|
||||
}
|
||||
|
||||
export interface WrapperApi {
|
||||
NodeIQQNTWrapperSession?: NodeIQQNTWrapperSession
|
||||
}
|
||||
|
||||
export interface WrapperConstructor {
|
||||
[key: string]: any
|
||||
NodeIKernelBuddyListener?: any
|
||||
NodeIKernelGroupListener?: any
|
||||
NodeQQNTWrapperUtil?: any
|
||||
NodeIKernelMsgListener?: any
|
||||
NodeIQQNTWrapperEngine?: any
|
||||
NodeIGlobalAdapter?: any
|
||||
NodeIDependsAdapter?: any
|
||||
NodeIDispatcherAdapter?: any
|
||||
NodeIKernelSessionListener?: any
|
||||
NodeIKernelLoginService?: any
|
||||
NodeIKernelLoginListener?: any
|
||||
NodeIKernelProfileService?: any
|
||||
NodeIKernelProfileListener?: any
|
||||
}
|
||||
|
||||
const wrapperApi: WrapperApi = {}
|
||||
|
||||
export const wrapperConstructor: WrapperConstructor = {}
|
||||
|
||||
const constructor = [
|
||||
'NodeIKernelBuddyListener',
|
||||
'NodeIKernelGroupListener',
|
||||
'NodeQQNTWrapperUtil',
|
||||
'NodeIKernelMsgListener',
|
||||
'NodeIQQNTWrapperEngine',
|
||||
'NodeIGlobalAdapter',
|
||||
'NodeIDependsAdapter',
|
||||
'NodeIDispatcherAdapter',
|
||||
'NodeIKernelSessionListener',
|
||||
'NodeIKernelLoginService',
|
||||
'NodeIKernelLoginListener',
|
||||
'NodeIKernelProfileService',
|
||||
'NodeIKernelProfileListener',
|
||||
]
|
||||
|
||||
Process.dlopenOrig = Process.dlopen
|
||||
|
||||
Process.dlopen = function (module, filename, flags = os.constants.dlopen.RTLD_LAZY) {
|
||||
const dlopenRet = this.dlopenOrig(module, filename, flags)
|
||||
for (let export_name in module.exports) {
|
||||
module.exports[export_name] = new Proxy(module.exports[export_name], {
|
||||
construct: (target, args, _newTarget) => {
|
||||
const ret = new target(...args)
|
||||
if (export_name === 'NodeIQQNTWrapperSession') wrapperApi.NodeIQQNTWrapperSession = ret
|
||||
return ret
|
||||
}
|
||||
})
|
||||
if (constructor.includes(export_name)) {
|
||||
wrapperConstructor[export_name] = module.exports[export_name]
|
||||
}
|
||||
}
|
||||
return dlopenRet
|
||||
}
|
||||
|
||||
export function getSession() {
|
||||
return wrapperApi['NodeIQQNTWrapperSession']
|
||||
}
|
@@ -4,8 +4,8 @@ import { OB11Return } from '../types'
|
||||
|
||||
import { log } from '../../common/utils/log'
|
||||
|
||||
class BaseAction<PayloadType, ReturnDataType> {
|
||||
actionName: ActionName
|
||||
abstract class BaseAction<PayloadType, ReturnDataType> {
|
||||
abstract actionName: ActionName
|
||||
|
||||
protected async check(payload: PayloadType): Promise<BaseCheckResult> {
|
||||
return {
|
||||
@@ -21,7 +21,7 @@ class BaseAction<PayloadType, ReturnDataType> {
|
||||
try {
|
||||
const resData = await this._handle(payload)
|
||||
return OB11Response.ok(resData)
|
||||
} catch (e) {
|
||||
} catch (e: any) {
|
||||
log('发生错误', e)
|
||||
return OB11Response.error(e?.toString() || e?.stack?.toString() || '未知错误,可能操作超时', 200)
|
||||
}
|
||||
@@ -35,7 +35,7 @@ class BaseAction<PayloadType, ReturnDataType> {
|
||||
try {
|
||||
const resData = await this._handle(payload)
|
||||
return OB11Response.ok(resData, echo)
|
||||
} catch (e) {
|
||||
} catch (e: any) {
|
||||
log('发生错误', e)
|
||||
return OB11Response.error(e.stack?.toString() || e.toString(), 1200, echo)
|
||||
}
|
||||
|
@@ -1,12 +1,12 @@
|
||||
import BaseAction from '../BaseAction'
|
||||
import fs from 'fs/promises'
|
||||
import { dbUtil } from '../../../common/db'
|
||||
import { getConfigUtil } from '../../../common/config'
|
||||
import { log, sleep, uri2local } from '../../../common/utils'
|
||||
import { NTQQFileApi } from '../../../ntqqapi/api/file'
|
||||
import { dbUtil } from '@/common/db'
|
||||
import { getConfigUtil } from '@/common/config'
|
||||
import { checkFileReceived, log, sleep, uri2local } from '@/common/utils'
|
||||
import { NTQQFileApi } from '@/ntqqapi/api'
|
||||
import { ActionName } from '../types'
|
||||
import { FileElement, RawMessage, VideoElement } from '../../../ntqqapi/types'
|
||||
import { FileCache } from '../../../common/types'
|
||||
import { FileElement, RawMessage, VideoElement } from '@/ntqqapi/types'
|
||||
import { FileCache } from '@/common/types'
|
||||
|
||||
export interface GetFilePayload {
|
||||
file: string // 文件名或者fileUuid
|
||||
@@ -20,14 +20,13 @@ export interface GetFileResponse {
|
||||
base64?: string
|
||||
}
|
||||
|
||||
export class GetFileBase extends BaseAction<GetFilePayload, GetFileResponse> {
|
||||
private getElement(msg: RawMessage): { id: string; element: VideoElement | FileElement } {
|
||||
let element = msg.elements.find((e) => e.fileElement)
|
||||
export abstract class GetFileBase extends BaseAction<GetFilePayload, GetFileResponse> {
|
||||
private getElement(msg: RawMessage, elementId: string): VideoElement | FileElement {
|
||||
let element = msg.elements.find((e) => e.elementId === elementId)
|
||||
if (!element) {
|
||||
element = msg.elements.find((e) => e.videoElement)
|
||||
return { id: element.elementId, element: element.videoElement }
|
||||
throw new Error('element not found')
|
||||
}
|
||||
return { id: element.elementId, element: element.fileElement }
|
||||
return element.fileElement
|
||||
}
|
||||
private async download(cache: FileCache, file: string) {
|
||||
log('需要调用 NTQQ 下载文件api')
|
||||
@@ -35,24 +34,25 @@ export class GetFileBase extends BaseAction<GetFilePayload, GetFileResponse> {
|
||||
let msg = await dbUtil.getMsgByLongId(cache.msgId)
|
||||
if (msg) {
|
||||
log('找到了文件 msg', msg)
|
||||
let element = this.getElement(msg)
|
||||
let element = this.getElement(msg, cache.elementId)
|
||||
log('找到了文件 element', element)
|
||||
// 构建下载函数
|
||||
await NTQQFileApi.downloadMedia(msg.msgId, msg.chatType, msg.peerUid, element.id, '', '', true)
|
||||
await sleep(1000)
|
||||
await NTQQFileApi.downloadMedia(msg.msgId, msg.chatType, msg.peerUid, cache.elementId, '', '')
|
||||
// 等待文件下载完成
|
||||
msg = await dbUtil.getMsgByLongId(cache.msgId)
|
||||
log('下载完成后的msg', msg)
|
||||
cache.filePath = this.getElement(msg).element.filePath
|
||||
cache.filePath = this.getElement(msg!, cache.elementId).filePath
|
||||
await checkFileReceived(cache.filePath, 10 * 1000)
|
||||
dbUtil.addFileCache(file, cache).then()
|
||||
}
|
||||
}
|
||||
}
|
||||
protected async _handle(payload: GetFilePayload): Promise<GetFileResponse> {
|
||||
const cache = await dbUtil.getFileCache(payload.file)
|
||||
const { autoDeleteFile, enableLocalFile2Url, autoDeleteFileSecond } = getConfigUtil().getConfig()
|
||||
let cache = await dbUtil.getFileCache(payload.file)
|
||||
if (!cache) {
|
||||
throw new Error('file not found')
|
||||
}
|
||||
const { autoDeleteFile, enableLocalFile2Url, autoDeleteFileSecond } = getConfigUtil().getConfig()
|
||||
if (cache.downloadFunc) {
|
||||
await cache.downloadFunc()
|
||||
}
|
||||
|
@@ -1,5 +1,9 @@
|
||||
import { GetFileBase, GetFilePayload, GetFileResponse } from './GetFile'
|
||||
import { ActionName } from '../types'
|
||||
import {decodeSilk} from "@/common/utils/audio";
|
||||
import { getConfigUtil } from '@/common/config'
|
||||
import path from 'node:path'
|
||||
import fs from 'node:fs'
|
||||
|
||||
interface Payload extends GetFilePayload {
|
||||
out_format: 'mp3' | 'amr' | 'wma' | 'm4a' | 'spx' | 'ogg' | 'wav' | 'flac'
|
||||
@@ -9,7 +13,13 @@ export default class GetRecord extends GetFileBase {
|
||||
actionName = ActionName.GetRecord
|
||||
|
||||
protected async _handle(payload: Payload): Promise<GetFileResponse> {
|
||||
let res = super._handle(payload)
|
||||
let res = await super._handle(payload)
|
||||
res.file = await decodeSilk(res.file!, payload.out_format)
|
||||
res.file_name = path.basename(res.file)
|
||||
res.file_size = fs.statSync(res.file).size.toString()
|
||||
if (getConfigUtil().getConfig().enableLocalFile2Url){
|
||||
res.base64 = fs.readFileSync(res.file, 'base64')
|
||||
}
|
||||
return res
|
||||
}
|
||||
}
|
||||
|
24
src/onebot11/action/go-cqhttp/DelEssenceMsg.ts
Normal file
24
src/onebot11/action/go-cqhttp/DelEssenceMsg.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
|
||||
import BaseAction from '../BaseAction';
|
||||
import { ActionName } from '../types';
|
||||
import { NTQQGroupApi } from '../../../ntqqapi/api/group'
|
||||
import { dbUtil } from '@/common/db';
|
||||
|
||||
interface Payload {
|
||||
message_id: number | string;
|
||||
}
|
||||
|
||||
export default class GoCQHTTPDelEssenceMsg extends BaseAction<Payload, any> {
|
||||
actionName = ActionName.GoCQHTTP_DelEssenceMsg;
|
||||
|
||||
protected async _handle(payload: Payload): Promise<any> {
|
||||
const msg = await dbUtil.getMsgByShortId(parseInt(payload.message_id.toString()));
|
||||
if (!msg) {
|
||||
throw new Error('msg not found');
|
||||
}
|
||||
return await NTQQGroupApi.removeGroupEssence(
|
||||
msg.peerUid,
|
||||
msg.msgId
|
||||
);
|
||||
}
|
||||
}
|
@@ -3,7 +3,7 @@ import { ActionName } from '../types'
|
||||
import fs from 'fs'
|
||||
import { join as joinPath } from 'node:path'
|
||||
import { calculateFileMD5, httpDownload, TEMP_DIR } from '../../../common/utils'
|
||||
import { v4 as uuid4 } from 'uuid'
|
||||
import { randomUUID } from 'node:crypto'
|
||||
|
||||
interface Payload {
|
||||
thread_count?: number
|
||||
@@ -22,7 +22,7 @@ export default class GoCQHTTPDownloadFile extends BaseAction<Payload, FileRespon
|
||||
|
||||
protected async _handle(payload: Payload): Promise<FileResponse> {
|
||||
const isRandomName = !payload.name
|
||||
let name = payload.name || uuid4()
|
||||
let name = payload.name || randomUUID()
|
||||
const filePath = joinPath(TEMP_DIR, name)
|
||||
|
||||
if (payload.base64) {
|
||||
|
@@ -1,12 +1,13 @@
|
||||
import BaseAction from '../BaseAction'
|
||||
import { OB11ForwardMessage, OB11Message, OB11MessageData } from '../../types'
|
||||
import { NTQQMsgApi, Peer } from '../../../ntqqapi/api'
|
||||
import { NTQQMsgApi } from '@/ntqqapi/api'
|
||||
import { dbUtil } from '../../../common/db'
|
||||
import { OB11Constructor } from '../../constructor'
|
||||
import { ActionName } from '../types'
|
||||
|
||||
interface Payload {
|
||||
message_id: string // long msg id
|
||||
message_id: string // long msg id,gocq
|
||||
id?: string // long msg id, onebot11
|
||||
}
|
||||
|
||||
interface Response {
|
||||
@@ -16,7 +17,11 @@ interface Response {
|
||||
export class GoCQHTTGetForwardMsgAction extends BaseAction<Payload, any> {
|
||||
actionName = ActionName.GoCQHTTP_GetForwardMsg
|
||||
protected async _handle(payload: Payload): Promise<any> {
|
||||
const rootMsg = await dbUtil.getMsgByLongId(payload.message_id)
|
||||
const message_id = payload.id || payload.message_id
|
||||
if (!message_id) {
|
||||
throw Error('message_id不能为空')
|
||||
}
|
||||
const rootMsg = await dbUtil.getMsgByLongId(message_id)
|
||||
if (!rootMsg) {
|
||||
throw Error('msg not found')
|
||||
}
|
||||
@@ -32,12 +37,13 @@ export class GoCQHTTGetForwardMsgAction extends BaseAction<Payload, any> {
|
||||
let messages = await Promise.all(
|
||||
msgList.map(async (msg) => {
|
||||
let resMsg = await OB11Constructor.message(msg)
|
||||
resMsg.message_id = await dbUtil.addMsg(msg)
|
||||
resMsg.message_id = (await dbUtil.addMsg(msg))!
|
||||
return resMsg
|
||||
}),
|
||||
)
|
||||
messages.map((msg) => {
|
||||
;(<OB11ForwardMessage>msg).content = msg.message
|
||||
messages.map(v => {
|
||||
const msg = v as Partial<OB11ForwardMessage>
|
||||
msg.content = msg.message
|
||||
delete msg.message
|
||||
})
|
||||
return { messages }
|
||||
|
@@ -6,7 +6,6 @@ import { ChatType } from '../../../ntqqapi/types'
|
||||
import { dbUtil } from '../../../common/db'
|
||||
import { NTQQMsgApi } from '../../../ntqqapi/api/msg'
|
||||
import { OB11Constructor } from '../../constructor'
|
||||
import { log } from '../../../common/utils'
|
||||
|
||||
interface Payload {
|
||||
group_id: number
|
||||
|
@@ -1,19 +1,59 @@
|
||||
import BaseAction from '../BaseAction'
|
||||
import { OB11User } from '../../types'
|
||||
import { getUidByUin, uidMaps } from '../../../common/data'
|
||||
import { OB11Constructor } from '../../constructor'
|
||||
import { ActionName } from '../types'
|
||||
import { NTQQUserApi } from '../../../ntqqapi/api/user'
|
||||
import { getBuildVersion } from '@/common/utils/QQBasicInfo'
|
||||
import { OB11UserSex } from '../../types'
|
||||
import { calcQQLevel } from '@/common/utils/qqlevel'
|
||||
|
||||
export default class GoCQHTTPGetStrangerInfo extends BaseAction<{ user_id: number }, OB11User> {
|
||||
interface Payload {
|
||||
user_id: number | string
|
||||
}
|
||||
|
||||
export default class GoCQHTTPGetStrangerInfo extends BaseAction<Payload, OB11User> {
|
||||
actionName = ActionName.GoCQHTTP_GetStrangerInfo
|
||||
|
||||
protected async _handle(payload: { user_id: number }): Promise<OB11User> {
|
||||
const user_id = payload.user_id.toString()
|
||||
const uid = getUidByUin(user_id)
|
||||
if (!uid) {
|
||||
throw new Error('查无此人')
|
||||
protected async _handle(payload: Payload): Promise<OB11User> {
|
||||
if (!(getBuildVersion() >= 26702)) {
|
||||
const user_id = payload.user_id.toString()
|
||||
const extendData = await NTQQUserApi.getUserDetailInfoByUin(user_id)
|
||||
const uid = (await NTQQUserApi.getUidByUin(user_id))!
|
||||
if (!uid || uid.indexOf('*') != -1) {
|
||||
const ret = {
|
||||
...extendData,
|
||||
user_id: parseInt(extendData.info.uin) || 0,
|
||||
nickname: extendData.info.nick,
|
||||
sex: OB11UserSex.unknown,
|
||||
age: (extendData.info.birthday_year == 0) ? 0 : new Date().getFullYear() - extendData.info.birthday_year,
|
||||
qid: extendData.info.qid,
|
||||
level: extendData.info.qqLevel && calcQQLevel(extendData.info.qqLevel) || 0,
|
||||
login_days: 0,
|
||||
uid: ''
|
||||
}
|
||||
return ret
|
||||
}
|
||||
const data = { ...extendData, ...(await NTQQUserApi.getUserDetailInfo(uid)) }
|
||||
return OB11Constructor.stranger(data)
|
||||
} else {
|
||||
const user_id = payload.user_id.toString()
|
||||
const extendData = await NTQQUserApi.getUserDetailInfoByUinV2(user_id)
|
||||
const uid = (await NTQQUserApi.getUidByUin(user_id))!
|
||||
if (!uid || uid.indexOf('*') != -1) {
|
||||
const ret = {
|
||||
...extendData,
|
||||
user_id: parseInt(extendData.detail.uin) || 0,
|
||||
nickname: extendData.detail.simpleInfo.coreInfo.nick,
|
||||
sex: OB11UserSex.unknown,
|
||||
age: 0,
|
||||
level: extendData.detail.commonExt.qqLevel && calcQQLevel(extendData.detail.commonExt.qqLevel) || 0,
|
||||
login_days: 0,
|
||||
uid: ''
|
||||
}
|
||||
return ret
|
||||
}
|
||||
const data = { ...extendData, ...(await NTQQUserApi.getUserDetailInfo(uid)) }
|
||||
return OB11Constructor.stranger(data)
|
||||
}
|
||||
return OB11Constructor.stranger(await NTQQUserApi.getUserDetailInfo(uid, true))
|
||||
}
|
||||
}
|
||||
|
17
src/onebot11/action/go-cqhttp/QuickOperation.ts
Normal file
17
src/onebot11/action/go-cqhttp/QuickOperation.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import BaseAction from '../BaseAction'
|
||||
import { handleQuickOperation, QuickOperation, QuickOperationEvent } from '../quick-operation'
|
||||
import { log } from '@/common/utils'
|
||||
import { ActionName } from '../types'
|
||||
|
||||
interface Payload{
|
||||
context: QuickOperationEvent,
|
||||
operation: QuickOperation
|
||||
}
|
||||
|
||||
export class GoCQHTTHandleQuickOperation extends BaseAction<Payload, null>{
|
||||
actionName = ActionName.GoCQHTTP_HandleQuickOperation
|
||||
protected async _handle(payload: Payload): Promise<null> {
|
||||
handleQuickOperation(payload.context, payload.operation).then().catch(log);
|
||||
return null
|
||||
}
|
||||
}
|
23
src/onebot11/action/go-cqhttp/SetEssenceMsg.ts
Normal file
23
src/onebot11/action/go-cqhttp/SetEssenceMsg.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import BaseAction from '../BaseAction';
|
||||
import { ActionName } from '../types';
|
||||
import { NTQQGroupApi } from '../../../ntqqapi/api/group'
|
||||
import { dbUtil } from '@/common/db';
|
||||
|
||||
interface Payload {
|
||||
message_id: number | string;
|
||||
}
|
||||
|
||||
export default class GoCQHTTPSetEssenceMsg extends BaseAction<Payload, any> {
|
||||
actionName = ActionName.GoCQHTTP_SetEssenceMsg;
|
||||
|
||||
protected async _handle(payload: Payload): Promise<any> {
|
||||
const msg = await dbUtil.getMsgByShortId(parseInt(payload.message_id.toString()));
|
||||
if (!msg) {
|
||||
throw new Error('msg not found');
|
||||
}
|
||||
return await NTQQGroupApi.addGroupEssence(
|
||||
msg.peerUid,
|
||||
msg.msgId
|
||||
);
|
||||
}
|
||||
}
|
@@ -1,49 +1,72 @@
|
||||
import fs from 'node:fs'
|
||||
import BaseAction from '../BaseAction'
|
||||
import { getGroup, getUidByUin } from '../../../common/data'
|
||||
import { getGroup } from '@/common/data'
|
||||
import { ActionName } from '../types'
|
||||
import { SendMsgElementConstructor } from '../../../ntqqapi/constructor'
|
||||
import { ChatType, SendFileElement } from '../../../ntqqapi/types'
|
||||
import fs from 'fs'
|
||||
import { NTQQMsgApi, Peer } from '../../../ntqqapi/api/msg'
|
||||
import { uri2local } from '../../../common/utils'
|
||||
import { SendMsgElementConstructor } from '@/ntqqapi/constructor'
|
||||
import { ChatType, SendFileElement } from '@/ntqqapi/types'
|
||||
import { uri2local } from '@/common/utils'
|
||||
import { Peer } from '@/ntqqapi/types'
|
||||
import { sendMsg } from '../msg/SendMsg'
|
||||
import { NTQQUserApi, NTQQFriendApi } from '@/ntqqapi/api'
|
||||
|
||||
interface Payload {
|
||||
user_id: number
|
||||
group_id?: number
|
||||
user_id: number | string
|
||||
group_id?: number | string
|
||||
file: string
|
||||
name: string
|
||||
folder: string
|
||||
folder?: string
|
||||
folder_id?: string
|
||||
}
|
||||
|
||||
class GoCQHTTPUploadFileBase extends BaseAction<Payload, null> {
|
||||
export class GoCQHTTPUploadGroupFile extends BaseAction<Payload, null> {
|
||||
actionName = ActionName.GoCQHTTP_UploadGroupFile
|
||||
|
||||
getPeer(payload: Payload): Peer {
|
||||
if (payload.user_id) {
|
||||
return { chatType: ChatType.friend, peerUid: getUidByUin(payload.user_id.toString()) }
|
||||
}
|
||||
return { chatType: ChatType.group, peerUid: payload.group_id.toString() }
|
||||
}
|
||||
|
||||
protected async _handle(payload: Payload): Promise<null> {
|
||||
const group = await getGroup(payload.group_id?.toString()!)
|
||||
if (!group) {
|
||||
throw new Error(`群组${payload.group_id}不存在`)
|
||||
}
|
||||
let file = payload.file
|
||||
if (fs.existsSync(file)) {
|
||||
file = `file://${file}`
|
||||
}
|
||||
const downloadResult = await uri2local(file)
|
||||
if (downloadResult.errMsg) {
|
||||
if (!downloadResult.success) {
|
||||
throw new Error(downloadResult.errMsg)
|
||||
}
|
||||
let sendFileEle: SendFileElement = await SendMsgElementConstructor.file(downloadResult.path, payload.name)
|
||||
await NTQQMsgApi.sendMsg(this.getPeer(payload), [sendFileEle])
|
||||
const sendFileEle: SendFileElement = await SendMsgElementConstructor.file(downloadResult.path, payload.name, payload.folder_id)
|
||||
await sendMsg({ chatType: ChatType.group, peerUid: group.groupCode }, [sendFileEle], [], true)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
export class GoCQHTTPUploadGroupFile extends GoCQHTTPUploadFileBase {
|
||||
actionName = ActionName.GoCQHTTP_UploadGroupFile
|
||||
}
|
||||
|
||||
export class GoCQHTTPUploadPrivateFile extends GoCQHTTPUploadFileBase {
|
||||
export class GoCQHTTPUploadPrivateFile extends BaseAction<Payload, null> {
|
||||
actionName = ActionName.GoCQHTTP_UploadPrivateFile
|
||||
|
||||
async getPeer(payload: Payload): Promise<Peer> {
|
||||
if (payload.user_id) {
|
||||
const peerUid = await NTQQUserApi.getUidByUin(payload.user_id.toString())
|
||||
if (!peerUid) {
|
||||
throw `私聊${payload.user_id}不存在`
|
||||
}
|
||||
const isBuddy = await NTQQFriendApi.isBuddy(peerUid)
|
||||
return { chatType: isBuddy ? ChatType.friend : ChatType.temp, peerUid }
|
||||
}
|
||||
throw '缺少参数 user_id'
|
||||
}
|
||||
|
||||
protected async _handle(payload: Payload): Promise<null> {
|
||||
const peer = await this.getPeer(payload)
|
||||
let file = payload.file
|
||||
if (fs.existsSync(file)) {
|
||||
file = `file://${file}`
|
||||
}
|
||||
const downloadResult = await uri2local(file)
|
||||
if (!downloadResult.success) {
|
||||
throw new Error(downloadResult.errMsg)
|
||||
}
|
||||
const sendFileEle: SendFileElement = await SendMsgElementConstructor.file(downloadResult.path, payload.name)
|
||||
await sendMsg(peer, [sendFileEle], [], true)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
24
src/onebot11/action/group/GetGroupEssence.ts
Normal file
24
src/onebot11/action/group/GetGroupEssence.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { GroupEssenceMsgRet, WebApi } from '@/ntqqapi/api'
|
||||
import BaseAction from '../BaseAction'
|
||||
import { ActionName } from '../types'
|
||||
|
||||
interface PayloadType {
|
||||
group_id: number
|
||||
pages?: number
|
||||
}
|
||||
|
||||
export class GetGroupEssence extends BaseAction<PayloadType, GroupEssenceMsgRet | void> {
|
||||
actionName = ActionName.GoCQHTTP_GetEssenceMsg
|
||||
|
||||
protected async _handle(payload: PayloadType) {
|
||||
throw '此 api 暂不支持'
|
||||
const ret = await WebApi.getGroupEssenceMsg(payload.group_id.toString(), payload.pages?.toString() || '0')
|
||||
if (!ret) {
|
||||
throw new Error('获取失败')
|
||||
}
|
||||
// ret.map((item) => {
|
||||
//
|
||||
// })
|
||||
return ret
|
||||
}
|
||||
}
|
23
src/onebot11/action/group/GetGroupHonorInfo.ts
Normal file
23
src/onebot11/action/group/GetGroupHonorInfo.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { WebApi, WebHonorType } from '@/ntqqapi/api'
|
||||
import { ActionName } from '../types'
|
||||
import BaseAction from '../BaseAction'
|
||||
|
||||
interface Payload {
|
||||
group_id: number
|
||||
type?: WebHonorType
|
||||
}
|
||||
|
||||
export class GetGroupHonorInfo extends BaseAction<Payload, Array<any>> {
|
||||
actionName = ActionName.GetGroupHonorInfo
|
||||
|
||||
protected async _handle(payload: Payload) {
|
||||
// console.log(await NTQQUserApi.getRobotUinRange())
|
||||
if (!payload.group_id) {
|
||||
throw '缺少参数group_id'
|
||||
}
|
||||
if (!payload.type) {
|
||||
payload.type = WebHonorType.ALL
|
||||
}
|
||||
return await WebApi.getGroupHonorInfo(payload.group_id.toString(), payload.type)
|
||||
}
|
||||
}
|
@@ -14,13 +14,10 @@ class GetGroupList extends BaseAction<Payload, OB11Group[]> {
|
||||
actionName = ActionName.GetGroupList
|
||||
|
||||
protected async _handle(payload: Payload) {
|
||||
if (
|
||||
groups.length === 0
|
||||
|| payload?.no_cache === true || payload?.no_cache === 'true'
|
||||
) {
|
||||
if (groups.length === 0 || payload?.no_cache === true || payload?.no_cache === 'true') {
|
||||
try {
|
||||
const groups = await NTQQGroupApi.getGroups(true)
|
||||
log("强制刷新群列表, 数量:", groups.length)
|
||||
log('强制刷新群列表, 数量:', groups.length)
|
||||
return OB11Constructor.groups(groups)
|
||||
} catch (e) {}
|
||||
}
|
||||
|
@@ -7,7 +7,7 @@ import { NTQQGroupApi } from '../../../ntqqapi/api/group'
|
||||
import { log } from '../../../common/utils'
|
||||
|
||||
export interface PayloadType {
|
||||
group_id: number,
|
||||
group_id: number
|
||||
no_cache: boolean | string
|
||||
}
|
||||
|
||||
@@ -18,7 +18,8 @@ class GetGroupMemberList extends BaseAction<PayloadType, OB11GroupMember[]> {
|
||||
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)
|
||||
|
@@ -2,13 +2,11 @@ import SendMsg from '../msg/SendMsg'
|
||||
import { ActionName, BaseCheckResult } from '../types'
|
||||
import { OB11PostSendMsg } from '../../types'
|
||||
|
||||
import { log } from '../../../common/utils/log'
|
||||
|
||||
class SendGroupMsg extends SendMsg {
|
||||
actionName = ActionName.SendGroupMsg
|
||||
|
||||
protected async check(payload: OB11PostSendMsg): Promise<BaseCheckResult> {
|
||||
delete payload.user_id
|
||||
delete (payload as Partial<OB11PostSendMsg>).user_id
|
||||
payload.message_type = 'group'
|
||||
return super.check(payload)
|
||||
}
|
||||
|
@@ -5,22 +5,19 @@ import { NTQQGroupApi } from '../../../ntqqapi/api/group'
|
||||
|
||||
interface Payload {
|
||||
flag: string
|
||||
// sub_type: "add" | "invite",
|
||||
// type: "add" | "invite"
|
||||
approve: boolean
|
||||
reason: string
|
||||
approve?: boolean | string
|
||||
reason?: string
|
||||
}
|
||||
|
||||
export default class SetGroupAddRequest extends BaseAction<Payload, null> {
|
||||
actionName = ActionName.SetGroupAddRequest
|
||||
|
||||
protected async _handle(payload: Payload): Promise<null> {
|
||||
const seq = payload.flag.toString()
|
||||
const approve = payload.approve.toString() === 'true'
|
||||
await NTQQGroupApi.handleGroupRequest(
|
||||
seq,
|
||||
const flag = payload.flag.toString()
|
||||
const approve = payload.approve?.toString() !== 'false'
|
||||
await NTQQGroupApi.handleGroupRequest(flag,
|
||||
approve ? GroupRequestOperateTypes.approve : GroupRequestOperateTypes.reject,
|
||||
payload.reason,
|
||||
payload.reason || ''
|
||||
)
|
||||
return null
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import GetMsg from './msg/GetMsg'
|
||||
import GetLoginInfo from './system/GetLoginInfo'
|
||||
import GetFriendList from './user/GetFriendList'
|
||||
import { GetFriendList, GetFriendWithCategory} from './user/GetFriendList'
|
||||
import GetGroupList from './group/GetGroupList'
|
||||
import GetGroupInfo from './group/GetGroupInfo'
|
||||
import GetGroupMemberList from './group/GetGroupMemberList'
|
||||
@@ -46,7 +46,14 @@ import GetFile from './file/GetFile'
|
||||
import { GoCQHTTGetForwardMsgAction } from './go-cqhttp/GetForwardMsg'
|
||||
import { GetCookies } from './user/GetCookie'
|
||||
import { SetMsgEmojiLike } from './msg/SetMsgEmojiLike'
|
||||
import { ForwardFriendSingleMsg, ForwardSingleGroupMsg } from './msg/ForwardSingleMsg'
|
||||
import { ForwardFriendSingleMsg, ForwardGroupSingleMsg } from './msg/ForwardSingleMsg'
|
||||
import { GetGroupEssence } from './group/GetGroupEssence'
|
||||
import { GetGroupHonorInfo } from './group/GetGroupHonorInfo'
|
||||
import { GoCQHTTHandleQuickOperation } from './go-cqhttp/QuickOperation'
|
||||
import GoCQHTTPSetEssenceMsg from './go-cqhttp/SetEssenceMsg'
|
||||
import GoCQHTTPDelEssenceMsg from './go-cqhttp/DelEssenceMsg'
|
||||
import GetEvent from './llonebot/GetEvent'
|
||||
|
||||
|
||||
export const actionHandlers = [
|
||||
new GetFile(),
|
||||
@@ -55,6 +62,8 @@ export const actionHandlers = [
|
||||
new SetConfigAction(),
|
||||
new GetGroupAddRequest(),
|
||||
new SetQQAvatar(),
|
||||
new GetFriendWithCategory(),
|
||||
new GetEvent(),
|
||||
// onebot11
|
||||
new SendLike(),
|
||||
new GetMsg(),
|
||||
@@ -87,8 +96,10 @@ export const actionHandlers = [
|
||||
new GetCookies(),
|
||||
new SetMsgEmojiLike(),
|
||||
new ForwardFriendSingleMsg(),
|
||||
new ForwardSingleGroupMsg(),
|
||||
new ForwardGroupSingleMsg(),
|
||||
//以下为go-cqhttp api
|
||||
new GetGroupEssence(),
|
||||
new GetGroupHonorInfo(),
|
||||
new GoCQHTTPSendForwardMsg(),
|
||||
new GoCQHTTPSendGroupForwardMsg(),
|
||||
new GoCQHTTPSendPrivateForwardMsg(),
|
||||
@@ -100,6 +111,9 @@ export const actionHandlers = [
|
||||
new GoCQHTTPUploadPrivateFile(),
|
||||
new GoCQHTTPGetGroupMsgHistory(),
|
||||
new GoCQHTTGetForwardMsgAction(),
|
||||
new GoCQHTTHandleQuickOperation(),
|
||||
new GoCQHTTPSetEssenceMsg(),
|
||||
new GoCQHTTPDelEssenceMsg()
|
||||
]
|
||||
|
||||
function initActionMap() {
|
||||
|
23
src/onebot11/action/llonebot/GetEvent.ts
Normal file
23
src/onebot11/action/llonebot/GetEvent.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import BaseAction from '../BaseAction'
|
||||
import { ActionName } from '../types'
|
||||
import { getHttpEvent } from '../../server/event-for-http'
|
||||
import { PostEventType } from '../../server/post-ob11-event'
|
||||
// import { log } from "../../../common/utils";
|
||||
|
||||
interface Payload {
|
||||
key: string
|
||||
timeout: number
|
||||
}
|
||||
|
||||
export default class GetEvent extends BaseAction<Payload, PostEventType[]> {
|
||||
actionName = ActionName.GetEvent
|
||||
protected async _handle(payload: Payload): Promise<PostEventType[]> {
|
||||
let key = ''
|
||||
if (payload.key) {
|
||||
key = payload.key;
|
||||
}
|
||||
let timeout = parseInt(payload.timeout?.toString()) || 0;
|
||||
let evts = await getHttpEvent(key,timeout);
|
||||
return evts;
|
||||
}
|
||||
}
|
@@ -1,10 +1,8 @@
|
||||
import { GroupNotify, GroupNotifyStatus } from '../../../ntqqapi/types'
|
||||
import BaseAction from '../BaseAction'
|
||||
import { ActionName } from '../types'
|
||||
import { uidMaps } from '../../../common/data'
|
||||
import { NTQQUserApi } from '../../../ntqqapi/api/user'
|
||||
import { NTQQGroupApi } from '../../../ntqqapi/api/group'
|
||||
import { log } from '../../../common/utils/log'
|
||||
|
||||
interface OB11GroupRequestNotify {
|
||||
group_id: number
|
||||
@@ -17,11 +15,10 @@ export default class GetGroupAddRequest extends BaseAction<null, OB11GroupReques
|
||||
|
||||
protected async _handle(payload: null): Promise<OB11GroupRequestNotify[]> {
|
||||
const data = await NTQQGroupApi.getGroupIgnoreNotifies()
|
||||
log(data)
|
||||
let notifies: GroupNotify[] = data.notifies.filter((notify) => notify.status === GroupNotifyStatus.WAIT_HANDLE)
|
||||
let returnData: OB11GroupRequestNotify[] = []
|
||||
const notifies: GroupNotify[] = data.notifies.filter((notify) => notify.status === GroupNotifyStatus.WAIT_HANDLE)
|
||||
const returnData: OB11GroupRequestNotify[] = []
|
||||
for (const notify of notifies) {
|
||||
const uin = uidMaps[notify.user1.uid] || (await NTQQUserApi.getUserDetailInfo(notify.user1.uid))?.uin
|
||||
const uin = await NTQQUserApi.getUinByUid(notify.user1.uid)
|
||||
returnData.push({
|
||||
group_id: parseInt(notify.group.groupCode),
|
||||
user_id: parseInt(uin),
|
||||
|
@@ -1,28 +1,35 @@
|
||||
import BaseAction from '../BaseAction'
|
||||
import { NTQQMsgApi, Peer } from '../../../ntqqapi/api'
|
||||
import { ChatType, RawMessage } from '../../../ntqqapi/types'
|
||||
import { dbUtil } from '../../../common/db'
|
||||
import { getUidByUin } from '../../../common/data'
|
||||
import { NTQQMsgApi, NTQQUserApi } from '@/ntqqapi/api'
|
||||
import { ChatType } from '@/ntqqapi/types'
|
||||
import { dbUtil } from '@/common/db'
|
||||
import { ActionName } from '../types'
|
||||
import { Peer } from '@/ntqqapi/types'
|
||||
|
||||
interface Payload {
|
||||
message_id: number
|
||||
group_id: number
|
||||
user_id?: number
|
||||
group_id: number | string
|
||||
user_id?: number | string
|
||||
}
|
||||
|
||||
class ForwardSingleMsg extends BaseAction<Payload, null> {
|
||||
abstract class ForwardSingleMsg extends BaseAction<Payload, null> {
|
||||
protected async getTargetPeer(payload: Payload): Promise<Peer> {
|
||||
if (payload.user_id) {
|
||||
return { chatType: ChatType.friend, peerUid: getUidByUin(payload.user_id.toString()) }
|
||||
const peerUid = await NTQQUserApi.getUidByUin(payload.user_id.toString())
|
||||
if (!peerUid) {
|
||||
throw new Error(`无法找到私聊对象${payload.user_id}`)
|
||||
}
|
||||
return { chatType: ChatType.friend, peerUid }
|
||||
}
|
||||
return { chatType: ChatType.group, peerUid: payload.group_id.toString() }
|
||||
return { chatType: ChatType.group, peerUid: payload.group_id!.toString() }
|
||||
}
|
||||
|
||||
protected async _handle(payload: Payload): Promise<null> {
|
||||
const msg = await dbUtil.getMsgByShortId(payload.message_id)
|
||||
if (!msg) {
|
||||
throw new Error(`无法找到消息${payload.message_id}`)
|
||||
}
|
||||
const peer = await this.getTargetPeer(payload)
|
||||
await NTQQMsgApi.forwardMsg(
|
||||
const ret = await NTQQMsgApi.forwardMsg(
|
||||
{
|
||||
chatType: msg.chatType,
|
||||
peerUid: msg.peerUid,
|
||||
@@ -30,6 +37,9 @@ class ForwardSingleMsg extends BaseAction<Payload, null> {
|
||||
peer,
|
||||
[msg.msgId],
|
||||
)
|
||||
if (ret.result !== 0) {
|
||||
throw new Error(`转发消息失败 ${ret.errMsg}`)
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
@@ -38,6 +48,6 @@ export class ForwardFriendSingleMsg extends ForwardSingleMsg {
|
||||
actionName = ActionName.ForwardFriendSingleMsg
|
||||
}
|
||||
|
||||
export class ForwardSingleGroupMsg extends ForwardSingleMsg {
|
||||
export class ForwardGroupSingleMsg extends ForwardSingleMsg {
|
||||
actionName = ActionName.ForwardGroupSingleMsg
|
||||
}
|
||||
|
@@ -2,78 +2,47 @@ import {
|
||||
AtType,
|
||||
ChatType,
|
||||
ElementType,
|
||||
Friend,
|
||||
Group,
|
||||
GroupMemberRole,
|
||||
PicSubType,
|
||||
RawMessage,
|
||||
SendArkElement,
|
||||
SendMessageElement,
|
||||
} from '../../../ntqqapi/types'
|
||||
import { friends, getFriend, getGroup, getGroupMember, getUidByUin, selfInfo } from '../../../common/data'
|
||||
import { getGroup, getGroupMember, getSelfUid, getSelfUin } from '../../../common/data'
|
||||
import {
|
||||
OB11MessageCustomMusic,
|
||||
OB11MessageData,
|
||||
OB11MessageDataType, OB11MessageFile,
|
||||
OB11MessageDataType,
|
||||
OB11MessageFile,
|
||||
OB11MessageJson,
|
||||
OB11MessageMixType,
|
||||
OB11MessageMusic,
|
||||
OB11MessageNode, OB11MessageVideo,
|
||||
OB11MessageNode,
|
||||
OB11PostSendMsg,
|
||||
} from '../../types'
|
||||
import { NTQQMsgApi, Peer } 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 { 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/external/crychic'
|
||||
import { NTQQGroupApi } from '../../../ntqqapi/api'
|
||||
import { CustomMusicSignPostData, MusicSign, MusicSignPostData } from '../../../common/utils/sign'
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
export enum ContextMode {
|
||||
Normal = 0,
|
||||
Private = 1,
|
||||
Group = 2
|
||||
}
|
||||
|
||||
export function convertMessage2List(message: OB11MessageMixType, autoEscape = false) {
|
||||
if (typeof message === 'string') {
|
||||
if (autoEscape === true) {
|
||||
@@ -85,10 +54,12 @@ export function convertMessage2List(message: OB11MessageMixType, autoEscape = fa
|
||||
},
|
||||
},
|
||||
]
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
message = decodeCQCode(message.toString())
|
||||
}
|
||||
} else if (!Array.isArray(message)) {
|
||||
}
|
||||
else if (!Array.isArray(message)) {
|
||||
message = [message]
|
||||
}
|
||||
return message
|
||||
@@ -96,7 +67,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[] = []
|
||||
@@ -106,172 +77,172 @@ export async function createSendElements(
|
||||
continue
|
||||
}
|
||||
switch (sendMsg.type) {
|
||||
case OB11MessageDataType.text:
|
||||
{
|
||||
const text = sendMsg.data?.text
|
||||
if (text) {
|
||||
sendElements.push(SendMsgElementConstructor.text(sendMsg.data!.text))
|
||||
}
|
||||
case OB11MessageDataType.text: {
|
||||
const text = sendMsg.data?.text
|
||||
if (text) {
|
||||
sendElements.push(SendMsgElementConstructor.text(sendMsg.data!.text))
|
||||
}
|
||||
}
|
||||
break
|
||||
case OB11MessageDataType.at:
|
||||
{
|
||||
if (!target) {
|
||||
continue
|
||||
case OB11MessageDataType.at: {
|
||||
if (!peer) {
|
||||
continue
|
||||
}
|
||||
let atQQ = sendMsg.data?.qq
|
||||
if (atQQ) {
|
||||
atQQ = atQQ.toString()
|
||||
if (atQQ === 'all') {
|
||||
// todo:查询剩余的at全体次数
|
||||
const groupCode = peer.peerUid
|
||||
let remainAtAllCount = 1
|
||||
let isAdmin: boolean = true
|
||||
if (groupCode) {
|
||||
try {
|
||||
remainAtAllCount = (await NTQQGroupApi.getGroupAtAllRemainCount(groupCode)).atInfo
|
||||
.RemainAtAllCountForUin
|
||||
log(`群${groupCode}剩余at全体次数`, remainAtAllCount)
|
||||
const self = await getGroupMember(groupCode, getSelfUin())
|
||||
isAdmin = self?.role === GroupMemberRole.admin || self?.role === GroupMemberRole.owner
|
||||
} catch (e) {
|
||||
}
|
||||
}
|
||||
if (isAdmin && remainAtAllCount > 0) {
|
||||
sendElements.push(SendMsgElementConstructor.at(atQQ, atQQ, AtType.atAll, '@全体成员'))
|
||||
}
|
||||
}
|
||||
let atQQ = sendMsg.data?.qq
|
||||
if (atQQ) {
|
||||
atQQ = atQQ.toString()
|
||||
if (atQQ === 'all') {
|
||||
// todo:查询剩余的at全体次数
|
||||
const groupCode = (target as Group)?.groupCode
|
||||
let remainAtAllCount = 1
|
||||
let isAdmin: boolean = true
|
||||
if (groupCode) {
|
||||
try {
|
||||
remainAtAllCount = (await NTQQGroupApi.getGroupAtAllRemainCount(groupCode)).atInfo
|
||||
.RemainAtAllCountForUin
|
||||
log(`群${groupCode}剩余at全体次数`, remainAtAllCount)
|
||||
const self = await getGroupMember((target as Group)?.groupCode, selfInfo.uin)
|
||||
isAdmin = self.role === GroupMemberRole.admin || self.role === GroupMemberRole.owner
|
||||
} catch (e) {}
|
||||
}
|
||||
if (isAdmin && remainAtAllCount > 0) {
|
||||
sendElements.push(SendMsgElementConstructor.at(atQQ, atQQ, AtType.atAll, '全体成员'))
|
||||
}
|
||||
else if (peer.chatType === ChatType.group) {
|
||||
const atMember = await getGroupMember(peer.peerUid, atQQ)
|
||||
if (atMember) {
|
||||
const display = `@${atMember.cardName || atMember.nick}`
|
||||
sendElements.push(
|
||||
SendMsgElementConstructor.at(atQQ, atMember.uid, AtType.atUser, display),
|
||||
)
|
||||
} else {
|
||||
// const atMember = group?.members.find(m => m.uin == atQQ)
|
||||
const atMember = await getGroupMember((target as Group)?.groupCode, atQQ)
|
||||
if (atMember) {
|
||||
sendElements.push(
|
||||
SendMsgElementConstructor.at(atQQ, atMember.uid, AtType.atUser, atMember.cardName || atMember.nick),
|
||||
)
|
||||
}
|
||||
const atNmae = sendMsg.data?.name
|
||||
const uid = await NTQQUserApi.getUidByUin(atQQ) || ''
|
||||
const display = atNmae ? `@${atNmae}` : ''
|
||||
sendElements.push(
|
||||
SendMsgElementConstructor.at(atQQ, uid, AtType.atUser, display),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break
|
||||
case OB11MessageDataType.reply:
|
||||
{
|
||||
let replyMsgId = sendMsg.data.id
|
||||
if (replyMsgId) {
|
||||
const replyMsg = await dbUtil.getMsgByShortId(parseInt(replyMsgId))
|
||||
if (replyMsg) {
|
||||
case OB11MessageDataType.reply: {
|
||||
let replyMsgId = sendMsg.data.id
|
||||
if (replyMsgId) {
|
||||
const replyMsg = await dbUtil.getMsgByShortId(parseInt(replyMsgId))
|
||||
if (replyMsg) {
|
||||
sendElements.push(
|
||||
SendMsgElementConstructor.reply(
|
||||
replyMsg.msgSeq,
|
||||
replyMsg.msgId,
|
||||
replyMsg.senderUin!,
|
||||
replyMsg.senderUin!,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
break
|
||||
case OB11MessageDataType.face: {
|
||||
const faceId = sendMsg.data?.id
|
||||
if (faceId) {
|
||||
sendElements.push(SendMsgElementConstructor.face(parseInt(faceId)))
|
||||
}
|
||||
}
|
||||
break
|
||||
case OB11MessageDataType.mface: {
|
||||
sendElements.push(
|
||||
SendMsgElementConstructor.mface(
|
||||
sendMsg.data.emoji_package_id,
|
||||
sendMsg.data.emoji_id,
|
||||
sendMsg.data.key,
|
||||
sendMsg.data.summary,
|
||||
),
|
||||
)
|
||||
}
|
||||
break
|
||||
case OB11MessageDataType.image:
|
||||
case OB11MessageDataType.file:
|
||||
case OB11MessageDataType.video:
|
||||
case OB11MessageDataType.voice: {
|
||||
const data = (sendMsg as OB11MessageFile).data
|
||||
let file = data.file
|
||||
const payloadFileName = data?.name
|
||||
if (file) {
|
||||
const cache = await dbUtil.getFileCache(file)
|
||||
if (cache) {
|
||||
if (fs.existsSync(cache.filePath)) {
|
||||
file = 'file://' + cache.filePath
|
||||
}
|
||||
else if (cache.downloadFunc) {
|
||||
await cache.downloadFunc()
|
||||
file = cache.filePath
|
||||
}
|
||||
else if (cache.url) {
|
||||
file = cache.url
|
||||
}
|
||||
log('找到文件缓存', file)
|
||||
}
|
||||
const { path, isLocal, fileName, errMsg } = await uri2local(file)
|
||||
if (errMsg) {
|
||||
throw errMsg
|
||||
}
|
||||
if (path) {
|
||||
if (!isLocal) {
|
||||
// 只删除http和base64转过来的文件
|
||||
deleteAfterSentFiles.push(path)
|
||||
}
|
||||
if (sendMsg.type === OB11MessageDataType.file) {
|
||||
log('发送文件', path, payloadFileName || fileName)
|
||||
sendElements.push(await SendMsgElementConstructor.file(path, payloadFileName || fileName))
|
||||
}
|
||||
else if (sendMsg.type === OB11MessageDataType.video) {
|
||||
log('发送视频', path, payloadFileName || fileName)
|
||||
let thumb = sendMsg.data?.thumb
|
||||
if (thumb) {
|
||||
let uri2LocalRes = await uri2local(thumb)
|
||||
if (uri2LocalRes.success) {
|
||||
thumb = uri2LocalRes.path
|
||||
}
|
||||
}
|
||||
sendElements.push(await SendMsgElementConstructor.video(path, payloadFileName || fileName, thumb))
|
||||
}
|
||||
else if (sendMsg.type === OB11MessageDataType.voice) {
|
||||
sendElements.push(await SendMsgElementConstructor.ptt(path))
|
||||
}
|
||||
else if (sendMsg.type === OB11MessageDataType.image) {
|
||||
sendElements.push(
|
||||
SendMsgElementConstructor.reply(
|
||||
replyMsg.msgSeq,
|
||||
replyMsg.msgId,
|
||||
replyMsg.senderUin,
|
||||
replyMsg.senderUin,
|
||||
await SendMsgElementConstructor.pic(
|
||||
path,
|
||||
sendMsg.data.summary || '',
|
||||
<PicSubType>parseInt(sendMsg.data?.subType?.toString()!) || 0,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
break
|
||||
case OB11MessageDataType.face:
|
||||
{
|
||||
const faceId = sendMsg.data?.id
|
||||
if (faceId) {
|
||||
sendElements.push(SendMsgElementConstructor.face(parseInt(faceId)))
|
||||
}
|
||||
}
|
||||
break
|
||||
case OB11MessageDataType.mface: {
|
||||
sendElements.push(
|
||||
SendMsgElementConstructor.mface(sendMsg.data.emoji_package_id, sendMsg.data.emoji_id, sendMsg.data.key, sendMsg.data.summary),
|
||||
)
|
||||
}
|
||||
case OB11MessageDataType.image:
|
||||
case OB11MessageDataType.file:
|
||||
case OB11MessageDataType.video:
|
||||
case OB11MessageDataType.voice:
|
||||
{
|
||||
const data = (sendMsg as OB11MessageFile).data
|
||||
let file = data.file
|
||||
const payloadFileName = data?.name
|
||||
if (file) {
|
||||
const cache = await dbUtil.getFileCache(file)
|
||||
if (cache) {
|
||||
if (fs.existsSync(cache.filePath)) {
|
||||
file = 'file://' + cache.filePath
|
||||
} else if (cache.downloadFunc) {
|
||||
await cache.downloadFunc()
|
||||
file = cache.filePath
|
||||
} else if (cache.url) {
|
||||
file = cache.url
|
||||
}
|
||||
log('找到文件缓存', file)
|
||||
}
|
||||
const { path, isLocal, fileName, errMsg } = await uri2local(file)
|
||||
if (errMsg) {
|
||||
throw errMsg
|
||||
}
|
||||
if (path) {
|
||||
if (!isLocal) {
|
||||
// 只删除http和base64转过来的文件
|
||||
deleteAfterSentFiles.push(path)
|
||||
}
|
||||
if (sendMsg.type === OB11MessageDataType.file) {
|
||||
log('发送文件', path, payloadFileName || fileName)
|
||||
sendElements.push(await SendMsgElementConstructor.file(path, payloadFileName || fileName))
|
||||
} else if (sendMsg.type === OB11MessageDataType.video) {
|
||||
log('发送视频', path, payloadFileName || fileName)
|
||||
let thumb = sendMsg.data?.thumb
|
||||
if (thumb) {
|
||||
let uri2LocalRes = await uri2local(thumb)
|
||||
if (uri2LocalRes.success) {
|
||||
thumb = uri2LocalRes.path
|
||||
}
|
||||
}
|
||||
sendElements.push(await SendMsgElementConstructor.video(path, payloadFileName || fileName, thumb))
|
||||
} else if (sendMsg.type === OB11MessageDataType.voice) {
|
||||
sendElements.push(await SendMsgElementConstructor.ptt(path))
|
||||
} else if (sendMsg.type === OB11MessageDataType.image) {
|
||||
sendElements.push(
|
||||
await SendMsgElementConstructor.pic(
|
||||
path,
|
||||
sendMsg.data.summary || '',
|
||||
<PicSubType>parseInt(sendMsg.data?.subType?.toString()) || 0,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break
|
||||
case OB11MessageDataType.json:
|
||||
{
|
||||
sendElements.push(SendMsgElementConstructor.ark(sendMsg.data.data))
|
||||
}
|
||||
case OB11MessageDataType.json: {
|
||||
sendElements.push(SendMsgElementConstructor.ark(sendMsg.data.data))
|
||||
}
|
||||
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('', ''))
|
||||
}
|
||||
}
|
||||
case OB11MessageDataType.poke: {
|
||||
let qq = sendMsg.data?.qq || sendMsg.data?.id
|
||||
}
|
||||
break
|
||||
case OB11MessageDataType.dice:
|
||||
{
|
||||
const resultId = sendMsg.data?.result
|
||||
sendElements.push(SendMsgElementConstructor.dice(resultId))
|
||||
}
|
||||
case OB11MessageDataType.dice: {
|
||||
const resultId = sendMsg.data?.result
|
||||
sendElements.push(SendMsgElementConstructor.dice(resultId))
|
||||
}
|
||||
break
|
||||
case OB11MessageDataType.RPS:
|
||||
{
|
||||
const resultId = sendMsg.data?.result
|
||||
sendElements.push(SendMsgElementConstructor.rps(resultId))
|
||||
}
|
||||
case OB11MessageDataType.RPS: {
|
||||
const resultId = sendMsg.data?.result
|
||||
sendElements.push(SendMsgElementConstructor.rps(resultId))
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -291,26 +262,75 @@ export async function sendMsg(
|
||||
if (!sendElements.length) {
|
||||
throw '消息体无法解析,请检查是否发送了不支持的消息类型'
|
||||
}
|
||||
const returnMsg = await NTQQMsgApi.sendMsg(peer, sendElements, waitComplete, 20000)
|
||||
// 计算发送的文件大小
|
||||
let totalSize = 0
|
||||
for (const fileElement of sendElements) {
|
||||
try {
|
||||
if (fileElement.elementType === ElementType.PTT) {
|
||||
totalSize += fs.statSync(fileElement.pttElement.filePath).size
|
||||
}
|
||||
if (fileElement.elementType === ElementType.FILE) {
|
||||
totalSize += fs.statSync(fileElement.fileElement.filePath).size
|
||||
}
|
||||
if (fileElement.elementType === ElementType.VIDEO) {
|
||||
totalSize += fs.statSync(fileElement.videoElement.filePath).size
|
||||
}
|
||||
if (fileElement.elementType === ElementType.PIC) {
|
||||
totalSize += fs.statSync(fileElement.picElement.sourcePath).size
|
||||
}
|
||||
} catch (e) {
|
||||
log('文件大小计算失败', e, fileElement)
|
||||
}
|
||||
}
|
||||
log('发送消息总大小', totalSize, 'bytes')
|
||||
let timeout = ((totalSize / 1024 / 100) * 1000) + 5000 // 100kb/s
|
||||
log('设置消息超时时间', timeout)
|
||||
const returnMsg = await NTQQMsgApi.sendMsg(peer, sendElements, waitComplete, timeout)
|
||||
log('消息发送结果', returnMsg)
|
||||
returnMsg.msgShortId = await dbUtil.addMsg(returnMsg)
|
||||
deleteAfterSentFiles.map((f) => fs.unlink(f, () => {}))
|
||||
deleteAfterSentFiles.map((f) => fs.unlink(f, () => {
|
||||
}))
|
||||
return returnMsg
|
||||
}
|
||||
|
||||
async function createContext(payload: OB11PostSendMsg, contextMode: ContextMode): Promise<Peer> {
|
||||
// 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<OB11PostSendMsg, ReturnDataType> {
|
||||
actionName = ActionName.SendMsg
|
||||
|
||||
protected async check(payload: OB11PostSendMsg): Promise<BaseCheckResult> {
|
||||
const messages = convertMessage2List(payload.message)
|
||||
const fmNum = this.getSpecialMsgNum(payload, OB11MessageDataType.node)
|
||||
const fmNum = this.getSpecialMsgNum(messages, OB11MessageDataType.node)
|
||||
if (fmNum && fmNum != messages.length) {
|
||||
return {
|
||||
valid: false,
|
||||
message: '转发消息不能和普通消息混在一起发送,转发需要保证message只有type为node的元素',
|
||||
}
|
||||
}
|
||||
const musicNum = this.getSpecialMsgNum(payload, OB11MessageDataType.music)
|
||||
const musicNum = this.getSpecialMsgNum(messages, OB11MessageDataType.music)
|
||||
if (musicNum && messages.length > 1) {
|
||||
return {
|
||||
valid: false,
|
||||
@@ -324,13 +344,11 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
|
||||
}
|
||||
}
|
||||
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 {
|
||||
@@ -339,57 +357,20 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
|
||||
}
|
||||
|
||||
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(payload, OB11MessageDataType.node)) {
|
||||
if (this.getSpecialMsgNum(messages, OB11MessageDataType.node)) {
|
||||
try {
|
||||
const returnMsg = await this.handleForwardNode(peer, messages as OB11MessageNode[], group)
|
||||
return { message_id: returnMsg.msgShortId }
|
||||
} catch (e) {
|
||||
const returnMsg = await this.handleForwardNode(peer, messages as OB11MessageNode[])
|
||||
return { message_id: returnMsg?.msgShortId! }
|
||||
} catch (e: any) {
|
||||
throw '发送转发消息失败 ' + e.toString()
|
||||
}
|
||||
} else if (this.getSpecialMsgNum(payload, OB11MessageDataType.music)) {
|
||||
}
|
||||
else if (this.getSpecialMsgNum(messages, OB11MessageDataType.music)) {
|
||||
const music = messages[0] as OB11MessageMusic
|
||||
if (music) {
|
||||
const { musicSignUrl } = getConfigUtil().getConfig()
|
||||
@@ -402,12 +383,34 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
|
||||
}
|
||||
const postData: MusicSignPostData = { ...music.data }
|
||||
if (type === 'custom' && music.data.content) {
|
||||
;(postData as CustomMusicSignPostData).singer = music.data.content
|
||||
delete (postData as OB11MessageCustomMusic['data']).content
|
||||
const data = postData as CustomMusicSignPostData
|
||||
data.singer = music.data.content
|
||||
delete (data as OB11MessageCustomMusic['data']).content
|
||||
}
|
||||
if (type === 'custom') {
|
||||
const customMusicData = music.data as CustomMusicSignPostData
|
||||
if (!customMusicData.url) {
|
||||
throw '自定义音卡缺少参数url'
|
||||
}
|
||||
if (!customMusicData.audio) {
|
||||
throw '自定义音卡缺少参数audio'
|
||||
}
|
||||
if (!customMusicData.title) {
|
||||
throw '自定义音卡缺少参数title'
|
||||
}
|
||||
}
|
||||
if (type === 'qq' || type === '163') {
|
||||
const idMusicData = music.data as IdMusicSignPostData
|
||||
if (!idMusicData.id) {
|
||||
throw '音乐卡片缺少id参数'
|
||||
}
|
||||
}
|
||||
let jsonContent: string
|
||||
try {
|
||||
jsonContent = await new MusicSign(musicSignUrl).sign(postData)
|
||||
if (!jsonContent) {
|
||||
throw '音乐消息生成失败,提交内容有误或者签名服务器签名失败'
|
||||
}
|
||||
} catch (e) {
|
||||
throw `签名音乐消息失败:${e}`
|
||||
}
|
||||
@@ -418,25 +421,26 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
|
||||
}
|
||||
}
|
||||
// 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 }
|
||||
}
|
||||
}
|
||||
const returnMsg = await sendMsg(peer, sendElements, deleteAfterSentFiles)
|
||||
deleteAfterSentFiles.map((f) => fs.unlink(f, () => {}))
|
||||
return { message_id: returnMsg.msgShortId }
|
||||
deleteAfterSentFiles.map((f) => fs.unlink(f, () => {
|
||||
}))
|
||||
return { message_id: returnMsg.msgShortId! }
|
||||
}
|
||||
|
||||
private getSpecialMsgNum(payload: OB11PostSendMsg, msgType: OB11MessageDataType): number {
|
||||
if (Array.isArray(payload.message)) {
|
||||
return payload.message.filter((msg) => msg.type == msgType).length
|
||||
private getSpecialMsgNum(message: OB11MessageData[], msgType: OB11MessageDataType): number {
|
||||
if (Array.isArray(message)) {
|
||||
return message.filter((msg) => msg.type == msgType).length
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
private async cloneMsg(msg: RawMessage): Promise<RawMessage> {
|
||||
private async cloneMsg(msg: RawMessage): Promise<RawMessage | undefined> {
|
||||
log('克隆的目标消息', msg)
|
||||
let sendElements: SendMessageElement[] = []
|
||||
for (const ele of msg.elements) {
|
||||
@@ -453,7 +457,7 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
|
||||
const nodeMsg = await NTQQMsgApi.sendMsg(
|
||||
{
|
||||
chatType: ChatType.friend,
|
||||
peerUid: selfInfo.uid,
|
||||
peerUid: getSelfUid(),
|
||||
},
|
||||
sendElements,
|
||||
true,
|
||||
@@ -466,10 +470,10 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
|
||||
}
|
||||
|
||||
// 返回一个合并转发的消息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,
|
||||
peerUid: getSelfUid(),
|
||||
}
|
||||
let nodeMsgIds: string[] = []
|
||||
// 先判断一遍是不是id和自定义混用
|
||||
@@ -482,22 +486,24 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
|
||||
if (nodeId) {
|
||||
let nodeMsg = await dbUtil.getMsgByShortId(parseInt(nodeId))
|
||||
if (!needClone) {
|
||||
nodeMsgIds.push(nodeMsg.msgId)
|
||||
} else {
|
||||
if (nodeMsg.peerUid !== selfInfo.uid) {
|
||||
const cloneMsg = await this.cloneMsg(nodeMsg)
|
||||
nodeMsgIds.push(nodeMsg?.msgId!)
|
||||
}
|
||||
else {
|
||||
if (nodeMsg?.peerUid !== selfPeer.peerUid) {
|
||||
const cloneMsg = await this.cloneMsg(nodeMsg!)
|
||||
if (cloneMsg) {
|
||||
nodeMsgIds.push(cloneMsg.msgId)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
// 自定义的消息
|
||||
// 提取消息段,发给自己生成消息id
|
||||
try {
|
||||
const { sendElements, deleteAfterSentFiles } = await createSendElements(
|
||||
convertMessage2List(messageNode.data.content),
|
||||
group,
|
||||
destPeer
|
||||
)
|
||||
log('开始生成转发节点', sendElements)
|
||||
let sendElementsSplit: SendMessageElement[][] = []
|
||||
@@ -513,7 +519,8 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
|
||||
}
|
||||
sendElementsSplit[splitIndex] = [ele]
|
||||
splitIndex++
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
sendElementsSplit[splitIndex].push(ele)
|
||||
}
|
||||
log(sendElementsSplit)
|
||||
@@ -525,7 +532,8 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
|
||||
await sleep(500)
|
||||
log('转发节点生成成功', nodeMsg.msgId)
|
||||
}
|
||||
deleteAfterSentFiles.map((f) => fs.unlink(f, () => {}))
|
||||
deleteAfterSentFiles.map((f) => fs.unlink(f, () => {
|
||||
}))
|
||||
} catch (e) {
|
||||
log('生成转发消息节点失败', e)
|
||||
}
|
||||
@@ -534,7 +542,7 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
|
||||
|
||||
// 检查srcPeer是否一致,不一致则需要克隆成自己的消息, 让所有srcPeer都变成自己的,使其保持一致才能够转发
|
||||
let nodeMsgArray: Array<RawMessage> = []
|
||||
let srcPeer: Peer = null
|
||||
let srcPeer: Peer | null = null
|
||||
let needSendSelf = false
|
||||
for (const [index, msgId] of nodeMsgIds.entries()) {
|
||||
const nodeMsg = await dbUtil.getMsgByLongId(msgId)
|
||||
@@ -542,7 +550,8 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
|
||||
nodeMsgArray.push(nodeMsg)
|
||||
if (!srcPeer) {
|
||||
srcPeer = { chatType: nodeMsg.chatType, peerUid: nodeMsg.peerUid }
|
||||
} else if (srcPeer.peerUid !== nodeMsg.peerUid) {
|
||||
}
|
||||
else if (srcPeer.peerUid !== nodeMsg.peerUid) {
|
||||
needSendSelf = true
|
||||
srcPeer = selfPeer
|
||||
}
|
||||
@@ -553,7 +562,7 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
|
||||
if (needSendSelf) {
|
||||
log('需要克隆转发消息')
|
||||
for (const [index, msg] of nodeMsgArray.entries()) {
|
||||
if (msg.peerUid !== selfInfo.uid) {
|
||||
if (msg.peerUid !== selfPeer.peerUid) {
|
||||
const cloneMsg = await this.cloneMsg(msg)
|
||||
if (cloneMsg) {
|
||||
nodeMsgIds[index] = cloneMsg.msgId
|
||||
@@ -574,50 +583,10 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
|
||||
if (nodeMsgIds.length === 0) {
|
||||
throw Error('转发消息失败,节点为空')
|
||||
}
|
||||
try {
|
||||
log('开发转发', nodeMsgIds)
|
||||
return await NTQQMsgApi.multiForwardMsg(srcPeer, destPeer, nodeMsgIds)
|
||||
} catch (e) {
|
||||
log('forward failed', e)
|
||||
return null
|
||||
}
|
||||
const returnMsg = await NTQQMsgApi.multiForwardMsg(srcPeer!, destPeer, nodeMsgIds)
|
||||
returnMsg.msgShortId = await dbUtil.addMsg(returnMsg)
|
||||
return returnMsg
|
||||
}
|
||||
|
||||
// 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
|
||||
|
144
src/onebot11/action/quick-operation.ts
Normal file
144
src/onebot11/action/quick-operation.ts
Normal file
@@ -0,0 +1,144 @@
|
||||
// handle quick action, create at 2024-5-18 10:54:39 by linyuchen
|
||||
|
||||
|
||||
import { OB11Message, OB11MessageAt, OB11MessageData, OB11MessageDataType } from '../types'
|
||||
import { OB11FriendRequestEvent } from '../event/request/OB11FriendRequest'
|
||||
import { OB11GroupRequestEvent } from '../event/request/OB11GroupRequest'
|
||||
import { dbUtil } from '@/common/db'
|
||||
import { NTQQFriendApi, NTQQGroupApi, NTQQMsgApi, NTQQUserApi } from '@/ntqqapi/api'
|
||||
import { ChatType, GroupRequestOperateTypes, Peer } from '@/ntqqapi/types'
|
||||
import { convertMessage2List, createSendElements, sendMsg } from './msg/SendMsg'
|
||||
import { isNull, log } from '@/common/utils'
|
||||
import { getConfigUtil } from '@/common/config'
|
||||
|
||||
|
||||
interface QuickOperationPrivateMessage {
|
||||
reply?: string
|
||||
auto_escape?: boolean
|
||||
}
|
||||
|
||||
interface QuickOperationGroupMessage extends QuickOperationPrivateMessage {
|
||||
// 回复群消息
|
||||
at_sender?: boolean
|
||||
delete?: boolean
|
||||
kick?: boolean
|
||||
ban?: boolean
|
||||
ban_duration?: number
|
||||
//
|
||||
}
|
||||
|
||||
interface QuickOperationFriendRequest {
|
||||
approve?: boolean
|
||||
remark?: string
|
||||
}
|
||||
|
||||
interface QuickOperationGroupRequest {
|
||||
approve?: boolean
|
||||
reason?: string
|
||||
}
|
||||
|
||||
export type QuickOperation = QuickOperationPrivateMessage &
|
||||
QuickOperationGroupMessage &
|
||||
QuickOperationFriendRequest &
|
||||
QuickOperationGroupRequest
|
||||
|
||||
export type QuickOperationEvent = OB11Message | OB11FriendRequestEvent | OB11GroupRequestEvent;
|
||||
|
||||
export async function handleQuickOperation(context: QuickOperationEvent, quickAction: QuickOperation) {
|
||||
if (context.post_type === 'message') {
|
||||
handleMsg(context as OB11Message, quickAction).then().catch(log)
|
||||
}
|
||||
if (context.post_type === 'request') {
|
||||
const friendRequest = context as OB11FriendRequestEvent
|
||||
const groupRequest = context as OB11GroupRequestEvent
|
||||
if ((friendRequest).request_type === 'friend') {
|
||||
handleFriendRequest(friendRequest, quickAction).then().catch(log)
|
||||
}
|
||||
else if (groupRequest.request_type === 'group') {
|
||||
handleGroupRequest(groupRequest, quickAction).then().catch(log)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function handleMsg(msg: OB11Message, quickAction: QuickOperationPrivateMessage | QuickOperationGroupMessage) {
|
||||
const rawMessage = await dbUtil.getMsgByShortId(msg.message_id)
|
||||
const reply = quickAction.reply
|
||||
const ob11Config = getConfigUtil().getConfig().ob11
|
||||
const peer: Peer = {
|
||||
chatType: ChatType.friend,
|
||||
peerUid: msg.user_id.toString(),
|
||||
}
|
||||
if (msg.message_type == 'private') {
|
||||
peer.peerUid = (await NTQQUserApi.getUidByUin(msg.user_id.toString()))!
|
||||
if (msg.sub_type === 'group') {
|
||||
peer.chatType = ChatType.temp
|
||||
}
|
||||
}
|
||||
else {
|
||||
peer.chatType = ChatType.group
|
||||
peer.peerUid = msg.group_id?.toString()!
|
||||
}
|
||||
if (reply) {
|
||||
let replyMessage: OB11MessageData[] = []
|
||||
if (ob11Config.enableQOAutoQuote) {
|
||||
replyMessage.push({
|
||||
type: OB11MessageDataType.reply,
|
||||
data: {
|
||||
id: msg.message_id.toString(),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
if (msg.message_type == 'group') {
|
||||
if ((quickAction as QuickOperationGroupMessage).at_sender) {
|
||||
replyMessage.push({
|
||||
type: 'at',
|
||||
data: {
|
||||
qq: msg.user_id.toString(),
|
||||
},
|
||||
} as OB11MessageAt)
|
||||
}
|
||||
}
|
||||
replyMessage = replyMessage.concat(convertMessage2List(reply, quickAction.auto_escape))
|
||||
const { sendElements, deleteAfterSentFiles } = await createSendElements(replyMessage, peer)
|
||||
sendMsg(peer, sendElements, deleteAfterSentFiles, false).then().catch(log)
|
||||
}
|
||||
if (msg.message_type === 'group') {
|
||||
const groupMsgQuickAction = quickAction as QuickOperationGroupMessage
|
||||
// handle group msg
|
||||
if (groupMsgQuickAction.delete) {
|
||||
NTQQMsgApi.recallMsg(peer, [rawMessage?.msgId!]).then().catch(log)
|
||||
}
|
||||
if (groupMsgQuickAction.kick) {
|
||||
NTQQGroupApi.kickMember(peer.peerUid, [rawMessage?.senderUid!]).then().catch(log)
|
||||
}
|
||||
if (groupMsgQuickAction.ban) {
|
||||
NTQQGroupApi.banMember(peer.peerUid, [
|
||||
{
|
||||
uid: rawMessage?.senderUid!,
|
||||
timeStamp: groupMsgQuickAction.ban_duration || 60 * 30,
|
||||
},
|
||||
]).then().catch(log)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function handleFriendRequest(request: OB11FriendRequestEvent,
|
||||
quickAction: QuickOperationFriendRequest) {
|
||||
if (!isNull(quickAction.approve)) {
|
||||
// todo: set remark
|
||||
NTQQFriendApi.handleFriendRequest(request.flag, quickAction.approve).then().catch(log)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async function handleGroupRequest(request: OB11GroupRequestEvent,
|
||||
quickAction: QuickOperationGroupRequest) {
|
||||
if (!isNull(quickAction.approve)) {
|
||||
NTQQGroupApi.handleGroupRequest(
|
||||
request.flag,
|
||||
quickAction.approve ? GroupRequestOperateTypes.approve : GroupRequestOperateTypes.reject,
|
||||
quickAction.reason,
|
||||
).then().catch(log)
|
||||
}
|
||||
}
|
@@ -1,9 +1,8 @@
|
||||
import BaseAction from '../BaseAction'
|
||||
import { ActionName } from '../types'
|
||||
import fs from 'fs'
|
||||
import Path from 'path'
|
||||
import fs from 'node:fs'
|
||||
import Path from 'node:path'
|
||||
import { ChatType, ChatCacheListItemBasic, CacheFileType } from '../../../ntqqapi/types'
|
||||
import { dbUtil } from '../../../common/db'
|
||||
import { NTQQFileApi, NTQQFileCacheApi } from '../../../ntqqapi/api/file'
|
||||
|
||||
export default class CleanCache extends BaseAction<void, void> {
|
||||
@@ -12,14 +11,16 @@ export default class CleanCache extends BaseAction<void, void> {
|
||||
protected _handle(): Promise<void> {
|
||||
return new Promise<void>(async (res, rej) => {
|
||||
try {
|
||||
// dbUtil.clearCache();
|
||||
// dbUtil.clearCache()
|
||||
const cacheFilePaths: string[] = []
|
||||
|
||||
await NTQQFileCacheApi.setCacheSilentScan(false)
|
||||
|
||||
cacheFilePaths.push(await NTQQFileCacheApi.getHotUpdateCachePath())
|
||||
cacheFilePaths.push(await NTQQFileCacheApi.getDesktopTmpPath())
|
||||
;(await NTQQFileCacheApi.getCacheSessionPathList()).forEach((e) => cacheFilePaths.push(e.value))
|
||||
|
||||
const list = await NTQQFileCacheApi.getCacheSessionPathList()
|
||||
list.forEach((e) => cacheFilePaths.push(e.value))
|
||||
|
||||
// await NTQQApi.addCacheScannedPaths(); // XXX: 调用就崩溃,原因目前还未知
|
||||
const cacheScanResult = await NTQQFileCacheApi.scanCache()
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { OB11User } from '../../types'
|
||||
import { OB11Constructor } from '../../constructor'
|
||||
import { selfInfo } from '../../../common/data'
|
||||
import { getSelfInfo, getSelfNick } from '../../../common/data'
|
||||
import BaseAction from '../BaseAction'
|
||||
import { ActionName } from '../types'
|
||||
|
||||
@@ -8,7 +8,10 @@ class GetLoginInfo extends BaseAction<null, OB11User> {
|
||||
actionName = ActionName.GetLoginInfo
|
||||
|
||||
protected async _handle(payload: null) {
|
||||
return OB11Constructor.selfInfo(selfInfo)
|
||||
return OB11Constructor.selfInfo({
|
||||
...getSelfInfo(),
|
||||
nick: await getSelfNick(true)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,14 +1,14 @@
|
||||
import BaseAction from '../BaseAction'
|
||||
import { OB11Status } from '../../types'
|
||||
import { ActionName } from '../types'
|
||||
import { selfInfo } from '../../../common/data'
|
||||
import { getSelfInfo } from '../../../common/data'
|
||||
|
||||
export default class GetStatus extends BaseAction<any, OB11Status> {
|
||||
actionName = ActionName.GetStatus
|
||||
|
||||
protected async _handle(payload: any): Promise<OB11Status> {
|
||||
return {
|
||||
online: selfInfo.online,
|
||||
online: getSelfInfo().online!,
|
||||
good: true,
|
||||
}
|
||||
}
|
||||
|
@@ -21,6 +21,8 @@ export enum ActionName {
|
||||
SetConfig = 'set_config',
|
||||
Debug = 'llonebot_debug',
|
||||
GetFile = 'get_file',
|
||||
GetFriendsWithCategory = 'get_friends_with_category',
|
||||
GetEvent = 'get_event',
|
||||
// onebot 11
|
||||
SendLike = 'send_like',
|
||||
GetLoginInfo = 'get_login_info',
|
||||
@@ -66,4 +68,9 @@ export enum ActionName {
|
||||
GoCQHTTP_DownloadFile = 'download_file',
|
||||
GoCQHTTP_GetGroupMsgHistory = 'get_group_msg_history',
|
||||
GoCQHTTP_GetForwardMsg = 'get_forward_msg',
|
||||
GoCQHTTP_GetEssenceMsg = "get_essence_msg_list",
|
||||
GoCQHTTP_HandleQuickOperation = ".handle_quick_operation",
|
||||
GetGroupHonorInfo = "get_group_honor_info",
|
||||
GoCQHTTP_SetEssenceMsg = 'set_essence_msg',
|
||||
GoCQHTTP_DelEssenceMsg = 'delete_essence_msg',
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user