mirror of
https://github.com/LLOneBot/LLOneBot.git
synced 2024-11-22 01:56:33 +00:00
Compare commits
157 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
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 | ||
![]() |
3c532526df | ||
![]() |
05c6cae86f |
4
.github/workflows/publish.yml
vendored
4
.github/workflows/publish.yml
vendored
@@ -9,10 +9,10 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: checkout
|
- name: checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: setup node
|
- name: setup node
|
||||||
uses: actions/setup-node@v2
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 18
|
node-version: 18
|
||||||
|
|
||||||
|
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
|
package-lock.json
|
||||||
dist/
|
yarn.lock
|
||||||
out/
|
node_modules
|
||||||
|
dist
|
||||||
|
out
|
||||||
|
|
||||||
.idea/
|
.idea/
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
1
.yarnrc.yml
Normal file
1
.yarnrc.yml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
nodeLinker: node-modules
|
17
README.md
17
README.md
@@ -1,9 +1,9 @@
|
|||||||
# LLOneBot API
|
# LLOneBot
|
||||||
|
|
||||||
LiteLoaderQQNT插件,使你的NTQQ支持OneBot11协议进行QQ机器人开发
|
LiteLoaderQQNT 插件,实现 OneBot 11 协议,用以 QQ 机器人开发
|
||||||
|
|
||||||
> [!CAUTION]\
|
> [!CAUTION]\
|
||||||
> **请不要在 QQ 官方群聊和任何影响力较大的简中互联网平台(包括但不限于:B站,微博,知乎,抖音等)发布和讨论*任何*与本插件存在相关性的信息**
|
> **请不要在 QQ 官方群聊和任何影响力较大的简中互联网平台(包括但不限于: B站,微博,知乎,抖音等)发布和讨论*任何*与本插件存在相关性的信息**
|
||||||
|
|
||||||
TG群:<https://t.me/+nLZEnpne-pQ1OWFl>
|
TG群:<https://t.me/+nLZEnpne-pQ1OWFl>
|
||||||
|
|
||||||
@@ -13,13 +13,13 @@ 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 调用示例
|
## HTTP 调用示例
|
||||||
|
|
||||||

|
<img src="./doc/image/example.jpg" width="500px" alt="HTTP调用示例"/>
|
||||||
|
|
||||||
## 支持的 api 和功能详情
|
## 支持的 API
|
||||||
|
|
||||||
见 <https://llonebot.github.io/zh-CN/develop/api>
|
见 <https://llonebot.github.io/zh-CN/develop/api>
|
||||||
|
|
||||||
@@ -35,13 +35,8 @@ TG群:<https://t.me/+nLZEnpne-pQ1OWFl>
|
|||||||
- [x] 群禁言事件上报
|
- [x] 群禁言事件上报
|
||||||
- [x] 优化加群成功事件上报
|
- [x] 优化加群成功事件上报
|
||||||
- [x] 清理缓存api
|
- [x] 清理缓存api
|
||||||
- [ ] 无头模式
|
|
||||||
- [ ] 框架对接文档
|
- [ ] 框架对接文档
|
||||||
|
|
||||||
## onebot11文档
|
|
||||||
|
|
||||||
<https://11.onebot.dev/>
|
|
||||||
|
|
||||||
## Stargazers over time
|
## Stargazers over time
|
||||||
|
|
||||||
[](https://starchart.cc/LLOneBot/LLOneBot)
|
[](https://starchart.cc/LLOneBot/LLOneBot)
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
import cp from 'vite-plugin-cp'
|
import cp from 'vite-plugin-cp'
|
||||||
import './scripts/gen-version'
|
import path from 'node:path'
|
||||||
|
import './scripts/gen-manifest'
|
||||||
|
|
||||||
const external = [
|
const external = [
|
||||||
'silk-wasm',
|
'silk-wasm',
|
||||||
@@ -31,9 +32,11 @@ let config = {
|
|||||||
external,
|
external,
|
||||||
input: 'src/main/main.ts',
|
input: 'src/main/main.ts',
|
||||||
},
|
},
|
||||||
|
minify: true,
|
||||||
},
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
|
'@': path.resolve(__dirname, './src'),
|
||||||
'./lib-cov/fluent-ffmpeg': './lib/fluent-ffmpeg',
|
'./lib-cov/fluent-ffmpeg': './lib/fluent-ffmpeg',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -42,10 +45,10 @@ let config = {
|
|||||||
targets: [
|
targets: [
|
||||||
...external.map(genCpModule),
|
...external.map(genCpModule),
|
||||||
{ src: './manifest.json', dest: 'dist' },
|
{ src: './manifest.json', dest: 'dist' },
|
||||||
{ src: './icon.jpg', dest: 'dist' },
|
{ src: './icon.webp', dest: 'dist' },
|
||||||
{ src: './src/ntqqapi/external/crychic/crychic-win32-x64.node', dest: 'dist/main/' },
|
// { src: './src/ntqqapi/native/crychic/crychic-win32-x64.node', dest: 'dist/main/' },
|
||||||
{ src: './src/ntqqapi/external/moehook/MoeHoo-win32-x64.node', dest: 'dist/main/' },
|
// { src: './src/ntqqapi/native/moehook/MoeHoo-win32-x64.node', dest: 'dist/main/' },
|
||||||
{ src: './src/ntqqapi/external/moehook/MoeHoo-linux-x64.node', dest: 'dist/main/' },
|
// { src: './src/ntqqapi/native/moehook/MoeHoo-linux-x64.node', dest: 'dist/main/' },
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
|
@@ -1,11 +1,11 @@
|
|||||||
{
|
{
|
||||||
"manifest_version": 4,
|
"manifest_version": 4,
|
||||||
"type": "extension",
|
"type": "extension",
|
||||||
"name": "LLOneBot v3.24.0",
|
"name": "LLOneBot",
|
||||||
"slug": "LLOneBot",
|
"slug": "LLOneBot",
|
||||||
"description": "使你的NTQQ支持OneBot11协议进行QQ机器人开发, 不支持商店在线更新",
|
"description": "实现 OneBot 11 协议,用以 QQ 机器人开发",
|
||||||
"version": "3.24.0",
|
"version": "3.28.0",
|
||||||
"icon": "./icon.jpg",
|
"icon": "./icon.webp",
|
||||||
"authors": [
|
"authors": [
|
||||||
{
|
{
|
||||||
"name": "linyuchen",
|
"name": "linyuchen",
|
||||||
|
32
package.json
32
package.json
@@ -10,39 +10,33 @@
|
|||||||
"deploy-mac": "cp -r dist/* ~/Library/Containers/com.tencent.qq/Data/LiteLoaderQQNT/plugins/LLOneBot/",
|
"deploy-mac": "cp -r dist/* ~/Library/Containers/com.tencent.qq/Data/LiteLoaderQQNT/plugins/LLOneBot/",
|
||||||
"build-win": "npm run build && npm run deploy-win",
|
"build-win": "npm run build && npm run deploy-win",
|
||||||
"deploy-win": "cmd /c \"xcopy /C /S /Y dist\\* %USERPROFILE%\\documents\\LiteLoaderQQNT\\plugins\\LLOneBot\\\"",
|
"deploy-win": "cmd /c \"xcopy /C /S /Y dist\\* %USERPROFILE%\\documents\\LiteLoaderQQNT\\plugins\\LLOneBot\\\"",
|
||||||
"format": "prettier -cw ."
|
"format": "prettier -cw .",
|
||||||
|
"check": "tsc"
|
||||||
},
|
},
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"compressing": "^1.10.0",
|
"compressing": "^1.10.1",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
"fast-xml-parser": "^4.3.6",
|
"fast-xml-parser": "^4.4.1",
|
||||||
"file-type": "^19.0.0",
|
"file-type": "^19.0.0",
|
||||||
"fluent-ffmpeg": "^2.1.2",
|
"fluent-ffmpeg": "^2.1.2",
|
||||||
"level": "^8.0.1",
|
"level": "^8.0.1",
|
||||||
"silk-wasm": "^3.3.4",
|
"silk-wasm": "^3.6.1",
|
||||||
"utf-8-validate": "^6.0.3",
|
"ws": "^8.18.0"
|
||||||
"uuid": "^9.0.1",
|
|
||||||
"ws": "^8.16.0"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/cors": "^2.8.17",
|
||||||
"@types/express": "^4.17.20",
|
"@types/express": "^4.17.20",
|
||||||
"@types/fluent-ffmpeg": "^2.1.24",
|
"@types/fluent-ffmpeg": "^2.1.24",
|
||||||
"@types/node": "^20.11.24",
|
"@types/node": "^20.11.24",
|
||||||
"@types/uuid": "^9.0.8",
|
"@types/ws": "^8.5.12",
|
||||||
"@types/ws": "^8.5.10",
|
|
||||||
"@typescript-eslint/eslint-plugin": "^6.4.0",
|
|
||||||
"electron": "^29.0.1",
|
"electron": "^29.0.1",
|
||||||
"electron-vite": "^2.0.0",
|
"electron-vite": "^2.3.0",
|
||||||
"eslint": "^8.0.1",
|
"typescript": "^5.5.4",
|
||||||
"eslint-plugin-import": "^2.25.2",
|
"vite": "^5.3.5",
|
||||||
"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",
|
|
||||||
"vite-plugin-cp": "^4.0.8"
|
"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: 'linyuchen/LiteLoaderQQNT-OneBotApi',
|
||||||
|
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'
|
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() {
|
async function getGroupNotify() {
|
||||||
let keys = await db.keys().all()
|
let keys = await db.keys().all()
|
||||||
let result = []
|
let result: string[] = []
|
||||||
for (const key of keys) {
|
for (const key of keys) {
|
||||||
// console.log(key)
|
// console.log(key)
|
||||||
if (key.startsWith('group_notify_')) {
|
if (key.startsWith('group_notify_')) {
|
||||||
|
@@ -1,7 +1,5 @@
|
|||||||
import fs from 'fs'
|
import fs from 'node:fs'
|
||||||
import fsPromise from 'fs/promises'
|
|
||||||
import { Config, OB11Config } from './types'
|
import { Config, OB11Config } from './types'
|
||||||
|
|
||||||
import { mergeNewProperties } from './utils/helper'
|
import { mergeNewProperties } from './utils/helper'
|
||||||
import path from 'node:path'
|
import path from 'node:path'
|
||||||
import { selfInfo } from './data'
|
import { selfInfo } from './data'
|
||||||
@@ -40,8 +38,10 @@ export class ConfigUtil {
|
|||||||
enableWsReverse: false,
|
enableWsReverse: false,
|
||||||
messagePostFormat: 'array',
|
messagePostFormat: 'array',
|
||||||
enableHttpHeart: false,
|
enableHttpHeart: false,
|
||||||
|
enableQOAutoQuote: false
|
||||||
}
|
}
|
||||||
let defaultConfig: Config = {
|
let defaultConfig: Config = {
|
||||||
|
enableLLOB: true,
|
||||||
ob11: ob11Default,
|
ob11: ob11Default,
|
||||||
heartInterval: 60000,
|
heartInterval: 60000,
|
||||||
token: '',
|
token: '',
|
||||||
@@ -51,7 +51,6 @@ export class ConfigUtil {
|
|||||||
reportSelfMessage: false,
|
reportSelfMessage: false,
|
||||||
autoDeleteFile: false,
|
autoDeleteFile: false,
|
||||||
autoDeleteFileSecond: 60,
|
autoDeleteFileSecond: 60,
|
||||||
enablePoke: false,
|
|
||||||
musicSignUrl: '',
|
musicSignUrl: '',
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,9 +81,12 @@ export class ConfigUtil {
|
|||||||
fs.writeFileSync(this.configPath, JSON.stringify(config, null, 2), 'utf-8')
|
fs.writeFileSync(this.configPath, JSON.stringify(config, null, 2), 'utf-8')
|
||||||
}
|
}
|
||||||
|
|
||||||
private checkOldConfig(currentConfig: Config | OB11Config,
|
private checkOldConfig(
|
||||||
oldConfig: Config | OB11Config,
|
currentConfig: Config | OB11Config,
|
||||||
currentKey: string, oldKey: string) {
|
oldConfig: Config | OB11Config,
|
||||||
|
currentKey: string,
|
||||||
|
oldKey: string,
|
||||||
|
) {
|
||||||
// 迁移旧的配置到新配置,避免用户重新填写配置
|
// 迁移旧的配置到新配置,避免用户重新填写配置
|
||||||
const oldValue = oldConfig[oldKey]
|
const oldValue = oldConfig[oldKey]
|
||||||
if (oldValue) {
|
if (oldValue) {
|
||||||
|
@@ -1,9 +1,18 @@
|
|||||||
import { type Friend, type FriendRequest, type Group, type GroupMember, type SelfInfo } from '../ntqqapi/types'
|
import {
|
||||||
|
CategoryFriend,
|
||||||
|
type Friend,
|
||||||
|
type FriendRequest,
|
||||||
|
type Group,
|
||||||
|
type GroupMember,
|
||||||
|
type SelfInfo,
|
||||||
|
User,
|
||||||
|
} from '../ntqqapi/types'
|
||||||
import { type FileCache, type LLOneBotError } from './types'
|
import { type FileCache, type LLOneBotError } from './types'
|
||||||
import { NTQQGroupApi } from '../ntqqapi/api/group'
|
import { NTQQGroupApi } from '../ntqqapi/api/group'
|
||||||
import { log } from './utils/log'
|
import { log } from './utils/log'
|
||||||
import { isNumeric } from './utils/helper'
|
import { isNumeric } from './utils/helper'
|
||||||
import { NTQQFriendApi } from '../ntqqapi/api'
|
import { NTQQFriendApi } from '../ntqqapi/api'
|
||||||
|
import { WebApiGroupMember } from '@/ntqqapi/api/webapi'
|
||||||
|
|
||||||
export const selfInfo: SelfInfo = {
|
export const selfInfo: SelfInfo = {
|
||||||
uid: '',
|
uid: '',
|
||||||
@@ -11,6 +20,10 @@ export const selfInfo: SelfInfo = {
|
|||||||
nick: '',
|
nick: '',
|
||||||
online: true,
|
online: true,
|
||||||
}
|
}
|
||||||
|
export const WebGroupData = {
|
||||||
|
GroupData: new Map<string, Array<WebApiGroupMember>>(),
|
||||||
|
GroupTime: new Map<string, number>(),
|
||||||
|
}
|
||||||
export let groups: Group[] = []
|
export let groups: Group[] = []
|
||||||
export let friends: Friend[] = []
|
export let friends: Friend[] = []
|
||||||
export let friendRequests: Map<number, FriendRequest> = new Map<number, FriendRequest>()
|
export let friendRequests: Map<number, FriendRequest> = new Map<number, FriendRequest>()
|
||||||
@@ -27,13 +40,13 @@ export async function getFriend(uinOrUid: string): Promise<Friend | undefined> {
|
|||||||
let friend = friends.find((friend) => friend[filterKey] === filterValue.toString())
|
let friend = friends.find((friend) => friend[filterKey] === filterValue.toString())
|
||||||
if (!friend) {
|
if (!friend) {
|
||||||
try {
|
try {
|
||||||
const _friends = (await NTQQFriendApi.getFriends(true))
|
const _friends = await NTQQFriendApi.getFriends(true)
|
||||||
friend = _friends.find(friend => friend[filterKey] === filterValue.toString())
|
friend = _friends.find((friend) => friend[filterKey] === filterValue.toString())
|
||||||
if (friend){
|
if (friend) {
|
||||||
friends.push(friend)
|
friends.push(friend)
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e: any) {
|
||||||
log("刷新好友列表失败", e.stack.toString())
|
log('刷新好友列表失败', e.stack.toString())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return friend
|
return friend
|
||||||
@@ -107,3 +120,5 @@ export function getUidByUin(uin: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export let tempGroupCodeMap: Record<string, string> = {} // peerUid => 群号
|
export let tempGroupCodeMap: Record<string, string> = {} // peerUid => 群号
|
||||||
|
|
||||||
|
export let rawFriends: CategoryFriend[] = []
|
@@ -14,9 +14,9 @@ class DBUtil {
|
|||||||
public readonly DB_KEY_PREFIX_FILE = 'file_'
|
public readonly DB_KEY_PREFIX_FILE = 'file_'
|
||||||
public readonly DB_KEY_PREFIX_GROUP_NOTIFY = 'group_notify_'
|
public readonly DB_KEY_PREFIX_GROUP_NOTIFY = 'group_notify_'
|
||||||
private readonly DB_KEY_RECEIVED_TEMP_UIN_MAP = 'received_temp_uin_map'
|
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
|
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
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* 数据库结构
|
* 数据库结构
|
||||||
@@ -44,12 +44,12 @@ class DBUtil {
|
|||||||
this.db = new Level(DB_PATH, { valueEncoding: 'json' })
|
this.db = new Level(DB_PATH, { valueEncoding: 'json' })
|
||||||
console.log('llonebot init db success')
|
console.log('llonebot init db success')
|
||||||
resolve(null)
|
resolve(null)
|
||||||
} catch (e) {
|
} catch (e: any) {
|
||||||
console.log('init db fail', e.stack.toString())
|
console.log('init db fail', e.stack.toString())
|
||||||
setTimeout(initDB, 300)
|
setTimeout(initDB, 300)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
initDB()
|
setTimeout(initDB)
|
||||||
}).then()
|
}).then()
|
||||||
|
|
||||||
const expiredMilliSecond = 1000 * 60 * 60
|
const expiredMilliSecond = 1000 * 60 * 60
|
||||||
@@ -72,13 +72,13 @@ class DBUtil {
|
|||||||
|
|
||||||
public async getReceivedTempUinMap(): Promise<ReceiveTempUinMap> {
|
public async getReceivedTempUinMap(): Promise<ReceiveTempUinMap> {
|
||||||
try {
|
try {
|
||||||
this.cache[this.DB_KEY_RECEIVED_TEMP_UIN_MAP] = JSON.parse(await this.db.get(this.DB_KEY_RECEIVED_TEMP_UIN_MAP))
|
this.cache[this.DB_KEY_RECEIVED_TEMP_UIN_MAP] = JSON.parse(await this.db?.get(this.DB_KEY_RECEIVED_TEMP_UIN_MAP)!)
|
||||||
} catch (e) {}
|
} catch (e) { }
|
||||||
return (this.cache[this.DB_KEY_RECEIVED_TEMP_UIN_MAP] || {}) as ReceiveTempUinMap
|
return (this.cache[this.DB_KEY_RECEIVED_TEMP_UIN_MAP] || {}) as ReceiveTempUinMap
|
||||||
}
|
}
|
||||||
public setReceivedTempUinMap(data: ReceiveTempUinMap) {
|
public setReceivedTempUinMap(data: ReceiveTempUinMap) {
|
||||||
this.cache[this.DB_KEY_RECEIVED_TEMP_UIN_MAP] = data
|
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) {
|
private addCache(msg: RawMessage) {
|
||||||
const longIdKey = this.DB_KEY_PREFIX_MSG_ID + msg.msgId
|
const longIdKey = this.DB_KEY_PREFIX_MSG_ID + msg.msgId
|
||||||
@@ -91,30 +91,30 @@ class DBUtil {
|
|||||||
this.cache = {}
|
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
|
const shortMsgIdKey = this.DB_KEY_PREFIX_MSG_SHORT_ID + shortMsgId
|
||||||
if (this.cache[shortMsgIdKey]) {
|
if (this.cache[shortMsgIdKey]) {
|
||||||
// log("getMsgByShortId cache", shortMsgIdKey, this.cache[shortMsgIdKey])
|
// log("getMsgByShortId cache", shortMsgIdKey, this.cache[shortMsgIdKey])
|
||||||
return this.cache[shortMsgIdKey] as RawMessage
|
return this.cache[shortMsgIdKey] as RawMessage
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const longId = await this.db.get(shortMsgIdKey)
|
const longId = await this.db?.get(shortMsgIdKey)
|
||||||
const msg = await this.getMsgByLongId(longId)
|
const msg = await this.getMsgByLongId(longId!)
|
||||||
this.addCache(msg)
|
this.addCache(msg!)
|
||||||
return msg
|
return msg
|
||||||
} catch (e) {
|
} catch (e: any) {
|
||||||
log('getMsgByShortId db error', e.stack.toString())
|
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
|
const longIdKey = this.DB_KEY_PREFIX_MSG_ID + longId
|
||||||
if (this.cache[longIdKey]) {
|
if (this.cache[longIdKey]) {
|
||||||
return this.cache[longIdKey] as RawMessage
|
return this.cache[longIdKey] as RawMessage
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const data = await this.db.get(longIdKey)
|
const data = await this.db?.get(longIdKey)
|
||||||
const msg = JSON.parse(data)
|
const msg = JSON.parse(data!)
|
||||||
this.addCache(msg)
|
this.addCache(msg)
|
||||||
return msg
|
return msg
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -122,17 +122,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
|
const seqIdKey = this.DB_KEY_PREFIX_MSG_SEQ_ID + seqId
|
||||||
if (this.cache[seqIdKey]) {
|
if (this.cache[seqIdKey]) {
|
||||||
return this.cache[seqIdKey] as RawMessage
|
return this.cache[seqIdKey] as RawMessage
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const longId = await this.db.get(seqIdKey)
|
const longId = await this.db?.get(seqIdKey)
|
||||||
const msg = await this.getMsgByLongId(longId)
|
const msg = await this.getMsgByLongId(longId!)
|
||||||
this.addCache(msg)
|
this.addCache(msg!)
|
||||||
return msg
|
return msg
|
||||||
} catch (e) {
|
} catch (e: any) {
|
||||||
log('getMsgBySeqId db error', e.stack.toString())
|
log('getMsgBySeqId db error', e.stack.toString())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -141,7 +141,7 @@ class DBUtil {
|
|||||||
// 有则更新,无则添加
|
// 有则更新,无则添加
|
||||||
// log("addMsg", msg.msgId, msg.msgSeq, msg.msgShortId);
|
// log("addMsg", msg.msgId, msg.msgSeq, msg.msgShortId);
|
||||||
const longIdKey = this.DB_KEY_PREFIX_MSG_ID + msg.msgId
|
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) {
|
if (!existMsg) {
|
||||||
try {
|
try {
|
||||||
existMsg = await this.getMsgByLongId(msg.msgId)
|
existMsg = await this.getMsgByLongId(msg.msgId)
|
||||||
@@ -154,20 +154,20 @@ class DBUtil {
|
|||||||
this.updateMsg(msg).then()
|
this.updateMsg(msg).then()
|
||||||
return existMsg.msgShortId
|
return existMsg.msgShortId
|
||||||
}
|
}
|
||||||
this.addCache(msg)
|
|
||||||
|
|
||||||
const shortMsgId = await this.genMsgShortId()
|
const shortMsgId = await this.genMsgShortId()
|
||||||
const shortIdKey = this.DB_KEY_PREFIX_MSG_SHORT_ID + shortMsgId
|
const shortIdKey = this.DB_KEY_PREFIX_MSG_SHORT_ID + shortMsgId
|
||||||
const seqIdKey = this.DB_KEY_PREFIX_MSG_SEQ_ID + msg.msgSeq
|
const seqIdKey = this.DB_KEY_PREFIX_MSG_SEQ_ID + msg.msgSeq
|
||||||
msg.msgShortId = shortMsgId
|
msg.msgShortId = shortMsgId
|
||||||
|
this.addCache(msg)
|
||||||
// log("新增消息记录", msg.msgId)
|
// log("新增消息记录", msg.msgId)
|
||||||
this.db.put(shortIdKey, msg.msgId).then().catch()
|
this.db?.put(shortIdKey, msg.msgId).then().catch()
|
||||||
this.db.put(longIdKey, JSON.stringify(msg)).then().catch()
|
this.db?.put(longIdKey, JSON.stringify(msg)).then().catch()
|
||||||
try {
|
try {
|
||||||
await this.db.get(seqIdKey)
|
await this.db?.get(seqIdKey)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// log("新的seqId", seqIdKey)
|
// log("新的seqId", seqIdKey)
|
||||||
this.db.put(seqIdKey, msg.msgId).then().catch()
|
this.db?.put(seqIdKey, msg.msgId).then().catch()
|
||||||
}
|
}
|
||||||
if (!this.cache[seqIdKey]) {
|
if (!this.cache[seqIdKey]) {
|
||||||
this.cache[seqIdKey] = msg
|
this.cache[seqIdKey] = msg
|
||||||
@@ -178,7 +178,7 @@ class DBUtil {
|
|||||||
|
|
||||||
async updateMsg(msg: RawMessage) {
|
async updateMsg(msg: RawMessage) {
|
||||||
const longIdKey = this.DB_KEY_PREFIX_MSG_ID + msg.msgId
|
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) {
|
if (!existMsg) {
|
||||||
try {
|
try {
|
||||||
existMsg = await this.getMsgByLongId(msg.msgId)
|
existMsg = await this.getMsgByLongId(msg.msgId)
|
||||||
@@ -187,18 +187,18 @@ class DBUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Object.assign(existMsg, msg)
|
Object.assign(existMsg!, msg)
|
||||||
this.db.put(longIdKey, JSON.stringify(existMsg)).then().catch()
|
this.db?.put(longIdKey, JSON.stringify(existMsg)).then().catch()
|
||||||
const shortIdKey = this.DB_KEY_PREFIX_MSG_SHORT_ID + existMsg.msgShortId
|
const shortIdKey = this.DB_KEY_PREFIX_MSG_SHORT_ID + existMsg?.msgShortId
|
||||||
const seqIdKey = this.DB_KEY_PREFIX_MSG_SEQ_ID + msg.msgSeq
|
const seqIdKey = this.DB_KEY_PREFIX_MSG_SEQ_ID + msg.msgSeq
|
||||||
if (!this.cache[seqIdKey]) {
|
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 {
|
try {
|
||||||
await this.db.get(seqIdKey)
|
await this.db?.get(seqIdKey)
|
||||||
} catch (e) {
|
} 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("更新seqId error", e.stack, seqIdKey);
|
||||||
}
|
}
|
||||||
// log("更新消息", existMsg.msgSeq, existMsg.msgShortId, existMsg.msgId);
|
// log("更新消息", existMsg.msgSeq, existMsg.msgShortId, existMsg.msgId);
|
||||||
@@ -208,15 +208,15 @@ class DBUtil {
|
|||||||
const key = 'msg_current_short_id'
|
const key = 'msg_current_short_id'
|
||||||
if (this.currentShortId === undefined) {
|
if (this.currentShortId === undefined) {
|
||||||
try {
|
try {
|
||||||
let id: string = await this.db.get(key)
|
const id = await this.db?.get(key)
|
||||||
this.currentShortId = parseInt(id)
|
this.currentShortId = parseInt(id!)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.currentShortId = -2147483640
|
this.currentShortId = -2147483640
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.currentShortId++
|
this.currentShortId++
|
||||||
this.db.put(key, this.currentShortId.toString()).then().catch()
|
this.db?.put(key, this.currentShortId.toString()).then().catch()
|
||||||
return this.currentShortId
|
return this.currentShortId
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -229,8 +229,8 @@ class DBUtil {
|
|||||||
delete cacheDBData['downloadFunc']
|
delete cacheDBData['downloadFunc']
|
||||||
this.cache[fileNameOrUuid] = data
|
this.cache[fileNameOrUuid] = data
|
||||||
try {
|
try {
|
||||||
await this.db.put(key, JSON.stringify(cacheDBData))
|
await this.db?.put(key, JSON.stringify(cacheDBData))
|
||||||
} catch (e) {
|
} catch (e: any) {
|
||||||
log('addFileCache db error', e.stack.toString())
|
log('addFileCache db error', e.stack.toString())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -241,8 +241,8 @@ class DBUtil {
|
|||||||
return this.cache[key] as FileCache
|
return this.cache[key] as FileCache
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
let data = await this.db.get(key)
|
const data = await this.db?.get(key)
|
||||||
return JSON.parse(data)
|
return JSON.parse(data!)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// log("getFileCache db error", e.stack.toString())
|
// log("getFileCache db error", e.stack.toString())
|
||||||
}
|
}
|
||||||
@@ -255,7 +255,7 @@ class DBUtil {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
this.cache[key] = notify
|
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> {
|
async getGroupNotify(seq: string): Promise<GroupNotify | undefined> {
|
||||||
@@ -264,8 +264,8 @@ class DBUtil {
|
|||||||
return this.cache[key] as GroupNotify
|
return this.cache[key] as GroupNotify
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
let data = await this.db.get(key)
|
const data = await this.db?.get(key)
|
||||||
return JSON.parse(data)
|
return JSON.parse(data!)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// log("getGroupNotify db error", e.stack.toString())
|
// log("getGroupNotify db error", e.stack.toString())
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import express, { Express, Request, Response } from 'express'
|
import express, { Express, Request, Response } from 'express'
|
||||||
import http from 'http'
|
import http from 'node:http'
|
||||||
import cors from 'cors'
|
import cors from 'cors'
|
||||||
import { log } from '../utils/log'
|
import { log } from '../utils/log'
|
||||||
import { getConfigUtil } from '../config'
|
import { getConfigUtil } from '../config'
|
||||||
@@ -10,7 +10,7 @@ type RegisterHandler = (res: Response, payload: any) => Promise<any>
|
|||||||
export abstract class HttpServerBase {
|
export abstract class HttpServerBase {
|
||||||
name: string = 'LLOneBot'
|
name: string = 'LLOneBot'
|
||||||
private readonly expressAPP: Express
|
private readonly expressAPP: Express
|
||||||
private server: http.Server = null
|
private server: http.Server | null = null
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.expressAPP = express()
|
this.expressAPP = express()
|
||||||
@@ -38,7 +38,7 @@ export abstract class HttpServerBase {
|
|||||||
let clientToken = ''
|
let clientToken = ''
|
||||||
const authHeader = req.get('authorization')
|
const authHeader = req.get('authorization')
|
||||||
if (authHeader) {
|
if (authHeader) {
|
||||||
clientToken = authHeader.split('Bearer ').pop()
|
clientToken = authHeader.split('Bearer ').pop()!
|
||||||
log('receive http header token', clientToken)
|
log('receive http header token', clientToken)
|
||||||
} else if (req.query.access_token) {
|
} else if (req.query.access_token) {
|
||||||
if (Array.isArray(req.query.access_token)) {
|
if (Array.isArray(req.query.access_token)) {
|
||||||
@@ -62,7 +62,7 @@ export abstract class HttpServerBase {
|
|||||||
})
|
})
|
||||||
this.listen(port)
|
this.listen(port)
|
||||||
llonebotError.httpServerError = ''
|
llonebotError.httpServerError = ''
|
||||||
} catch (e) {
|
} catch (e: any) {
|
||||||
log('HTTP服务启动失败', e.toString())
|
log('HTTP服务启动失败', e.toString())
|
||||||
llonebotError.httpServerError = 'HTTP服务启动失败, ' + e.toString()
|
llonebotError.httpServerError = 'HTTP服务启动失败, ' + e.toString()
|
||||||
}
|
}
|
||||||
@@ -103,7 +103,7 @@ export abstract class HttpServerBase {
|
|||||||
log('收到http请求', url, payload)
|
log('收到http请求', url, payload)
|
||||||
try {
|
try {
|
||||||
res.send(await handler(res, payload))
|
res.send(await handler(res, payload))
|
||||||
} catch (e) {
|
} catch (e: any) {
|
||||||
this.handleFailed(res, payload, e.stack.toString())
|
this.handleFailed(res, payload, e.stack.toString())
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@@ -6,9 +6,9 @@ import { getConfigUtil } from '../config'
|
|||||||
import { llonebotError } from '../data'
|
import { llonebotError } from '../data'
|
||||||
|
|
||||||
class WebsocketClientBase {
|
class WebsocketClientBase {
|
||||||
private wsClient: WebSocket
|
private wsClient: WebSocket | undefined
|
||||||
|
|
||||||
constructor() {}
|
constructor() { }
|
||||||
|
|
||||||
send(msg: string) {
|
send(msg: string) {
|
||||||
if (this.wsClient && this.wsClient.readyState == WebSocket.OPEN) {
|
if (this.wsClient && this.wsClient.readyState == WebSocket.OPEN) {
|
||||||
@@ -16,11 +16,11 @@ class WebsocketClientBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onMessage(msg: string) {}
|
onMessage(msg: string) { }
|
||||||
}
|
}
|
||||||
|
|
||||||
export class WebsocketServerBase {
|
export class WebsocketServerBase {
|
||||||
private ws: WebSocketServer = null
|
private ws: WebSocketServer | null = null
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
console.log(`llonebot websocket service started`)
|
console.log(`llonebot websocket service started`)
|
||||||
@@ -30,22 +30,22 @@ export class WebsocketServerBase {
|
|||||||
try {
|
try {
|
||||||
this.ws = new WebSocketServer({ port, maxPayload: 1024 * 1024 * 1024 })
|
this.ws = new WebSocketServer({ port, maxPayload: 1024 * 1024 * 1024 })
|
||||||
llonebotError.wsServerError = ''
|
llonebotError.wsServerError = ''
|
||||||
} catch (e) {
|
} catch (e: any) {
|
||||||
llonebotError.wsServerError = '正向ws服务启动失败, ' + e.toString()
|
llonebotError.wsServerError = '正向ws服务启动失败, ' + e.toString()
|
||||||
}
|
}
|
||||||
this.ws.on('connection', (wsClient, req) => {
|
this.ws?.on('connection', (wsClient, req) => {
|
||||||
const url = req.url.split('?').shift()
|
const url = req.url?.split('?').shift()
|
||||||
this.authorize(wsClient, req)
|
this.authorize(wsClient, req)
|
||||||
this.onConnect(wsClient, url, req)
|
this.onConnect(wsClient, url!, req)
|
||||||
wsClient.on('message', async (msg) => {
|
wsClient.on('message', async (msg) => {
|
||||||
this.onMessage(wsClient, url, msg.toString())
|
this.onMessage(wsClient, url!, msg.toString())
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
stop() {
|
stop() {
|
||||||
llonebotError.wsServerError = ''
|
llonebotError.wsServerError = ''
|
||||||
this.ws.close((err) => {
|
this.ws?.close((err) => {
|
||||||
log('ws server close failed!', err)
|
log('ws server close failed!', err)
|
||||||
})
|
})
|
||||||
this.ws = null
|
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
|
enableWsReverse?: boolean
|
||||||
messagePostFormat?: 'array' | 'string'
|
messagePostFormat?: 'array' | 'string'
|
||||||
enableHttpHeart?: boolean
|
enableHttpHeart?: boolean
|
||||||
|
enableQOAutoQuote: boolean // 快速操作回复自动引用原消息
|
||||||
}
|
}
|
||||||
export interface CheckVersion {
|
export interface CheckVersion {
|
||||||
result: boolean,
|
result: boolean
|
||||||
version: string
|
version: string
|
||||||
}
|
}
|
||||||
export interface Config {
|
export interface Config {
|
||||||
|
enableLLOB: boolean
|
||||||
ob11: OB11Config
|
ob11: OB11Config
|
||||||
token?: string
|
token?: string
|
||||||
heartInterval?: number // ms
|
heartInterval?: number // ms
|
||||||
@@ -26,7 +28,6 @@ export interface Config {
|
|||||||
autoDeleteFile?: boolean
|
autoDeleteFile?: boolean
|
||||||
autoDeleteFileSecond?: number
|
autoDeleteFileSecond?: number
|
||||||
ffmpeg?: string // ffmpeg路径
|
ffmpeg?: string // ffmpeg路径
|
||||||
enablePoke?: boolean
|
|
||||||
musicSignUrl?: string
|
musicSignUrl?: string
|
||||||
ignoreBeforeLoginMsg?: boolean
|
ignoreBeforeLoginMsg?: boolean
|
||||||
}
|
}
|
||||||
@@ -45,5 +46,6 @@ export interface FileCache {
|
|||||||
fileUuid?: string
|
fileUuid?: string
|
||||||
url?: string
|
url?: string
|
||||||
msgId?: string
|
msgId?: string
|
||||||
|
elementId: string
|
||||||
downloadFunc?: () => Promise<void>
|
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) })
|
83
src/common/utils/QQBasicInfo.ts
Normal file
83
src/common/utils/QQBasicInfo.ts
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
import path from 'node:path'
|
||||||
|
import fs from 'node:fs'
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
type QQVersionConfigInfo = {
|
||||||
|
baseVersion: string;
|
||||||
|
curVersion: string;
|
||||||
|
prevVersion: string;
|
||||||
|
onErrorVersions: Array<any>;
|
||||||
|
buildId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
let _qqVersionConfigInfo: QQVersionConfigInfo = {
|
||||||
|
'baseVersion': '9.9.9-23361',
|
||||||
|
'curVersion': '9.9.9-23361',
|
||||||
|
'prevVersion': '',
|
||||||
|
'onErrorVersions': [],
|
||||||
|
'buildId': '23361',
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fs.existsSync(configVersionInfoPath)) {
|
||||||
|
try {
|
||||||
|
const _ = JSON.parse(fs.readFileSync(configVersionInfoPath).toString())
|
||||||
|
_qqVersionConfigInfo = Object.assign(_qqVersionConfigInfo, _)
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Load QQ version config info failed, Use default version', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const qqVersionConfigInfo: QQVersionConfigInfo = _qqVersionConfigInfo
|
||||||
|
|
||||||
|
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',
|
||||||
|
|
||||||
|
let _appid: string = '537213803' // 默认为 Windows 平台的 appid
|
||||||
|
if (systemPlatform === 'linux') {
|
||||||
|
_appid = '537213827'
|
||||||
|
}
|
||||||
|
// todo: mac 平台的 appid
|
||||||
|
export const appid = _appid
|
||||||
|
export const isQQ998: boolean = qqPkgInfo.buildVersion >= '22106'
|
@@ -1,12 +1,12 @@
|
|||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
import { encode, getDuration, getWavFileInfo, isWav } from 'silk-wasm'
|
|
||||||
import fsPromise from 'fs/promises'
|
import fsPromise from 'fs/promises'
|
||||||
|
import { decode, encode, getDuration, getWavFileInfo, isWav, isSilk } from 'silk-wasm'
|
||||||
import { log } from './log'
|
import { log } from './log'
|
||||||
import path from 'node:path'
|
import path from 'node:path'
|
||||||
import { DATA_DIR, TEMP_DIR } from './index'
|
import { TEMP_DIR } from './index'
|
||||||
import { v4 as uuidv4 } from 'uuid'
|
|
||||||
import { getConfigUtil } from '../config'
|
import { getConfigUtil } from '../config'
|
||||||
import { spawn } from 'node:child_process'
|
import { spawn } from 'node:child_process'
|
||||||
|
import { randomUUID } from 'node:crypto'
|
||||||
|
|
||||||
export async function encodeSilk(filePath: string) {
|
export async function encodeSilk(filePath: string) {
|
||||||
function getFileHeader(filePath: string) {
|
function getFileHeader(filePath: string) {
|
||||||
@@ -60,10 +60,11 @@ export async function encodeSilk(filePath: string) {
|
|||||||
// }
|
// }
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const pttPath = path.join(TEMP_DIR, uuidv4())
|
const file = await fsPromise.readFile(filePath)
|
||||||
if (getFileHeader(filePath) !== '02232153494c4b') {
|
const pttPath = path.join(TEMP_DIR, randomUUID())
|
||||||
|
if (!isSilk(file)) {
|
||||||
log(`语音文件${filePath}需要转换成silk`)
|
log(`语音文件${filePath}需要转换成silk`)
|
||||||
const _isWav = await isWavFile(filePath)
|
const _isWav = isWav(file)
|
||||||
const pcmPath = pttPath + '.pcm'
|
const pcmPath = pttPath + '.pcm'
|
||||||
let sampleRate = 0
|
let sampleRate = 0
|
||||||
const convert = () => {
|
const convert = () => {
|
||||||
@@ -79,7 +80,8 @@ export async function encodeSilk(filePath: string) {
|
|||||||
if (code == null || EXIT_CODES.includes(code)) {
|
if (code == null || EXIT_CODES.includes(code)) {
|
||||||
sampleRate = 24000
|
sampleRate = 24000
|
||||||
const data = fs.readFileSync(pcmPath)
|
const data = fs.readFileSync(pcmPath)
|
||||||
fs.unlink(pcmPath, (err) => {})
|
fs.unlink(pcmPath, (err) => {
|
||||||
|
})
|
||||||
return resolve(data)
|
return resolve(data)
|
||||||
}
|
}
|
||||||
log(`FFmpeg exit: code=${code ?? 'unknown'} sig=${signal ?? 'unknown'}`)
|
log(`FFmpeg exit: code=${code ?? 'unknown'} sig=${signal ?? 'unknown'}`)
|
||||||
@@ -91,7 +93,7 @@ export async function encodeSilk(filePath: string) {
|
|||||||
if (!_isWav) {
|
if (!_isWav) {
|
||||||
input = await convert()
|
input = await convert()
|
||||||
} else {
|
} else {
|
||||||
input = fs.readFileSync(filePath)
|
input = file
|
||||||
const allowSampleRate = [8000, 12000, 16000, 24000, 32000, 44100, 48000]
|
const allowSampleRate = [8000, 12000, 16000, 24000, 32000, 44100, 48000]
|
||||||
const { fmt } = getWavFileInfo(input)
|
const { fmt } = getWavFileInfo(input)
|
||||||
// log(`wav文件信息`, fmt)
|
// log(`wav文件信息`, fmt)
|
||||||
@@ -108,11 +110,11 @@ export async function encodeSilk(filePath: string) {
|
|||||||
duration: silk.duration / 1000,
|
duration: silk.duration / 1000,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const silk = fs.readFileSync(filePath)
|
const silk = file
|
||||||
let duration = 0
|
let duration = 0
|
||||||
try {
|
try {
|
||||||
duration = getDuration(silk) / 1000
|
duration = getDuration(silk) / 1000
|
||||||
} catch (e) {
|
} catch (e: any) {
|
||||||
log('获取语音文件时长失败, 使用文件大小推测时长', filePath, e.stack)
|
log('获取语音文件时长失败, 使用文件大小推测时长', filePath, e.stack)
|
||||||
duration = await guessDuration(filePath)
|
duration = await guessDuration(filePath)
|
||||||
}
|
}
|
||||||
@@ -123,8 +125,46 @@ export async function encodeSilk(filePath: string) {
|
|||||||
duration,
|
duration,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error: any) {
|
||||||
log('convert silk failed', error.stack)
|
log('convert silk failed', error.stack)
|
||||||
return {}
|
return {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function decodeSilk(inputFilePath: string, outFormat: 'mp3' | 'amr' | 'wma' | 'm4a' | 'spx' | 'ogg' | 'wav' | 'flac' = 'mp3') {
|
||||||
|
const silkArrayBuffer = await fsPromise.readFile(inputFilePath)
|
||||||
|
const data = (await decode(silkArrayBuffer, 24000)).data
|
||||||
|
const fileName = path.join(TEMP_DIR, path.basename(inputFilePath))
|
||||||
|
const outPCMPath = fileName + '.pcm'
|
||||||
|
const outFilePath = fileName + '.' + outFormat
|
||||||
|
await fsPromise.writeFile(outPCMPath, data)
|
||||||
|
const convert = () => {
|
||||||
|
return new Promise<string>((resolve, reject) => {
|
||||||
|
const ffmpegPath = getConfigUtil().getConfig().ffmpeg || process.env.FFMPEG_PATH || 'ffmpeg'
|
||||||
|
const cp = spawn(ffmpegPath, [
|
||||||
|
'-y',
|
||||||
|
'-f', 's16le', // PCM format
|
||||||
|
'-ar', '24000', // Sample rate
|
||||||
|
'-ac', '1', // Number of audio channels
|
||||||
|
'-i', outPCMPath,
|
||||||
|
outFilePath,
|
||||||
|
])
|
||||||
|
cp.on('error', (err) => {
|
||||||
|
log(`FFmpeg处理转换出错: `, err.message)
|
||||||
|
return reject(err)
|
||||||
|
})
|
||||||
|
cp.on('exit', (code, signal) => {
|
||||||
|
const EXIT_CODES = [0, 255]
|
||||||
|
if (code == null || EXIT_CODES.includes(code)) {
|
||||||
|
fs.unlink(outPCMPath, (err) => {
|
||||||
|
})
|
||||||
|
return resolve(outFilePath)
|
||||||
|
}
|
||||||
|
const exitErr = `FFmpeg exit: code=${code ?? 'unknown'} sig=${signal ?? 'unknown'}`
|
||||||
|
log(exitErr)
|
||||||
|
reject(Error(`FFmpeg处理转换失败,${exitErr}`))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return convert()
|
||||||
|
}
|
@@ -1,13 +1,10 @@
|
|||||||
import fs from 'fs'
|
import fs from 'node:fs'
|
||||||
import fsPromise from 'fs/promises'
|
import fsPromise from 'node:fs/promises'
|
||||||
import crypto from 'crypto'
|
|
||||||
import util from 'util'
|
|
||||||
import path from 'node:path'
|
import path from 'node:path'
|
||||||
import { v4 as uuidv4 } from 'uuid'
|
|
||||||
import { log, TEMP_DIR } from './index'
|
import { log, TEMP_DIR } from './index'
|
||||||
import { dbUtil } from '../db'
|
import { dbUtil } from '../db'
|
||||||
import * as fileType from 'file-type'
|
import * as fileType from 'file-type'
|
||||||
import { net } from 'electron'
|
import { randomUUID, createHash } from 'node:crypto'
|
||||||
|
|
||||||
export function isGIF(path: string) {
|
export function isGIF(path: string) {
|
||||||
const buffer = Buffer.alloc(4)
|
const buffer = Buffer.alloc(4)
|
||||||
@@ -37,7 +34,6 @@ export function checkFileReceived(path: string, timeout: number = 3000): Promise
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function file2base64(path: string) {
|
export async function file2base64(path: string) {
|
||||||
const readFile = util.promisify(fs.readFile)
|
|
||||||
let result = {
|
let result = {
|
||||||
err: '',
|
err: '',
|
||||||
data: '',
|
data: '',
|
||||||
@@ -53,10 +49,10 @@ export async function file2base64(path: string) {
|
|||||||
result.err = e.toString()
|
result.err = e.toString()
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
const data = await readFile(path)
|
const data = await fsPromise.readFile(path)
|
||||||
// 转换为Base64编码
|
// 转换为Base64编码
|
||||||
result.data = data.toString('base64')
|
result.data = data.toString('base64')
|
||||||
} catch (err) {
|
} catch (err: any) {
|
||||||
result.err = err.toString()
|
result.err = err.toString()
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
@@ -66,7 +62,7 @@ export function calculateFileMD5(filePath: string): Promise<string> {
|
|||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
// 创建一个流式读取器
|
// 创建一个流式读取器
|
||||||
const stream = fs.createReadStream(filePath)
|
const stream = fs.createReadStream(filePath)
|
||||||
const hash = crypto.createHash('md5')
|
const hash = createHash('md5')
|
||||||
|
|
||||||
stream.on('data', (data: Buffer) => {
|
stream.on('data', (data: Buffer) => {
|
||||||
// 当读取到数据时,更新哈希对象的状态
|
// 当读取到数据时,更新哈希对象的状态
|
||||||
@@ -91,7 +87,6 @@ export interface HttpDownloadOptions {
|
|||||||
headers?: Record<string, string> | string
|
headers?: Record<string, string> | string
|
||||||
}
|
}
|
||||||
export async function httpDownload(options: string | HttpDownloadOptions): Promise<Buffer> {
|
export async function httpDownload(options: string | HttpDownloadOptions): Promise<Buffer> {
|
||||||
let chunks: Buffer[] = []
|
|
||||||
let url: string
|
let url: string
|
||||||
let headers: Record<string, string> = {
|
let headers: Record<string, string> = {
|
||||||
'User-Agent':
|
'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}`)
|
if (!fetchRes.ok) throw new Error(`下载文件失败: ${fetchRes.statusText}`)
|
||||||
|
|
||||||
const blob = await fetchRes.blob()
|
return Buffer.from(await fetchRes.arrayBuffer())
|
||||||
let buffer = await blob.arrayBuffer()
|
|
||||||
return Buffer.from(buffer)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Uri2LocalRes = {
|
type Uri2LocalRes = {
|
||||||
@@ -126,7 +119,7 @@ type Uri2LocalRes = {
|
|||||||
isLocal: boolean
|
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 = {
|
let res = {
|
||||||
success: false,
|
success: false,
|
||||||
errMsg: '',
|
errMsg: '',
|
||||||
@@ -136,13 +129,13 @@ export async function uri2local(uri: string, fileName: string = null): Promise<U
|
|||||||
isLocal: false,
|
isLocal: false,
|
||||||
}
|
}
|
||||||
if (!fileName) {
|
if (!fileName) {
|
||||||
fileName = uuidv4()
|
fileName = randomUUID()
|
||||||
}
|
}
|
||||||
let filePath = path.join(TEMP_DIR, fileName)
|
let filePath = path.join(TEMP_DIR, fileName)
|
||||||
let url = null
|
let url: URL | null = null
|
||||||
try {
|
try {
|
||||||
url = new URL(uri)
|
url = new URL(uri)
|
||||||
} catch (e) {
|
} catch (e: any) {
|
||||||
res.errMsg = `uri ${uri} 解析失败,` + e.toString() + ` 可能${uri}不存在`
|
res.errMsg = `uri ${uri} 解析失败,` + e.toString() + ` 可能${uri}不存在`
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
@@ -153,17 +146,17 @@ export async function uri2local(uri: string, fileName: string = null): Promise<U
|
|||||||
let base64Data = uri.split('base64://')[1]
|
let base64Data = uri.split('base64://')[1]
|
||||||
try {
|
try {
|
||||||
const buffer = Buffer.from(base64Data, 'base64')
|
const buffer = Buffer.from(base64Data, 'base64')
|
||||||
fs.writeFileSync(filePath, buffer)
|
await fsPromise.writeFile(filePath, buffer)
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
res.errMsg = `base64文件下载失败,` + e.toString()
|
res.errMsg = `base64文件下载失败,` + e.toString()
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
} else if (url.protocol == 'http:' || url.protocol == 'https:') {
|
} else if (url.protocol == 'http:' || url.protocol == 'https:') {
|
||||||
// 下载文件
|
// 下载文件
|
||||||
let buffer: Buffer = null
|
let buffer: Buffer | null = null
|
||||||
try {
|
try {
|
||||||
buffer = await httpDownload(uri)
|
buffer = await httpDownload(uri)
|
||||||
} catch (e) {
|
} catch (e: any) {
|
||||||
res.errMsg = `${url}下载失败,` + e.toString()
|
res.errMsg = `${url}下载失败,` + e.toString()
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
@@ -176,9 +169,10 @@ export async function uri2local(uri: string, fileName: string = null): Promise<U
|
|||||||
// res.ext = pathInfo.ext
|
// res.ext = pathInfo.ext
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
fileName = fileName.replace(/[/\\:*?"<>|]/g, '_')
|
||||||
res.fileName = fileName
|
res.fileName = fileName
|
||||||
filePath = path.join(TEMP_DIR, uuidv4() + fileName)
|
filePath = path.join(TEMP_DIR, randomUUID() + fileName)
|
||||||
fs.writeFileSync(filePath, buffer)
|
await fsPromise.writeFile(filePath, buffer)
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
res.errMsg = `${url}下载失败,` + e.toString()
|
res.errMsg = `${url}下载失败,` + e.toString()
|
||||||
return res
|
return res
|
||||||
@@ -214,10 +208,10 @@ export async function uri2local(uri: string, fileName: string = null): Promise<U
|
|||||||
// }
|
// }
|
||||||
if (!res.isLocal && !res.ext) {
|
if (!res.isLocal && !res.ext) {
|
||||||
try {
|
try {
|
||||||
let ext: string = (await fileType.fileTypeFromFile(filePath)).ext
|
const ext = (await fileType.fileTypeFromFile(filePath))?.ext
|
||||||
if (ext) {
|
if (ext) {
|
||||||
log('获取文件类型', ext, filePath)
|
log('获取文件类型', ext, filePath)
|
||||||
fs.renameSync(filePath, filePath + `.${ext}`)
|
await fsPromise.rename(filePath, filePath + `.${ext}`)
|
||||||
filePath += `.${ext}`
|
filePath += `.${ext}`
|
||||||
res.fileName += `.${ext}`
|
res.fileName += `.${ext}`
|
||||||
res.ext = ext
|
res.ext = ext
|
||||||
|
@@ -41,7 +41,7 @@ export function mergeNewProperties(newObj: any, oldObj: any) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isNull(value: any) {
|
export function isNull(value: any): value is null | undefined | void {
|
||||||
return value === undefined || value === null
|
return value === undefined || value === null
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,3 +65,33 @@ export function wrapText(str: string, maxLength: number): string {
|
|||||||
|
|
||||||
return result
|
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;
|
||||||
|
};
|
||||||
|
}
|
@@ -5,7 +5,7 @@ export * from './file'
|
|||||||
export * from './helper'
|
export * from './helper'
|
||||||
export * from './log'
|
export * from './log'
|
||||||
export * from './qqlevel'
|
export * from './qqlevel'
|
||||||
export * from './qqpkg'
|
export * from './QQBasicInfo'
|
||||||
export * from './upgrade'
|
export * from './upgrade'
|
||||||
export const DATA_DIR = global.LiteLoader.plugins['LLOneBot'].path.data
|
export const DATA_DIR = global.LiteLoader.plugins['LLOneBot'].path.data
|
||||||
export const TEMP_DIR = path.join(DATA_DIR, 'temp')
|
export const TEMP_DIR = path.join(DATA_DIR, 'temp')
|
||||||
@@ -16,3 +16,4 @@ if (!fs.existsSync(TEMP_DIR)) {
|
|||||||
export { getVideoInfo } from './video'
|
export { getVideoInfo } from './video'
|
||||||
export { checkFfmpeg } from './video'
|
export { checkFfmpeg } from './video'
|
||||||
export { encodeSilk } from './audio'
|
export { encodeSilk } from './audio'
|
||||||
|
export { isQQ998 } from './QQBasicInfo'
|
@@ -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();
|
@@ -5,7 +5,7 @@ import { copyFolder, httpDownload, log, PLUGIN_DIR, TEMP_DIR } from '.'
|
|||||||
import compressing from 'compressing'
|
import compressing from 'compressing'
|
||||||
|
|
||||||
const downloadMirrorHosts = ['https://mirror.ghproxy.com/']
|
const downloadMirrorHosts = ['https://mirror.ghproxy.com/']
|
||||||
const checkVersionMirrorHosts = ['https://521github.com']
|
const checkVersionMirrorHosts = ['https://kkgithub.com']
|
||||||
|
|
||||||
export async function checkNewVersion() {
|
export async function checkNewVersion() {
|
||||||
const latestVersionText = await getRemoteVersion()
|
const latestVersionText = await getRemoteVersion()
|
||||||
@@ -91,7 +91,7 @@ export async function getRemoteVersionByMirror(mirrorGithub: string) {
|
|||||||
releasePage = (await httpDownload(mirrorGithub + '/LLOneBot/LLOneBot/releases')).toString()
|
releasePage = (await httpDownload(mirrorGithub + '/LLOneBot/LLOneBot/releases')).toString()
|
||||||
// log("releasePage", releasePage);
|
// log("releasePage", releasePage);
|
||||||
if (releasePage === 'error') return ''
|
if (releasePage === 'error') return ''
|
||||||
return releasePage.match(new RegExp('(?<=(tag/v)).*?(?=("))'))[0]
|
return releasePage.match(new RegExp('(?<=(tag/v)).*?(?=("))'))?.[0]
|
||||||
} catch {}
|
} catch {}
|
||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
|
@@ -31,10 +31,10 @@ export async function getVideoInfo(filePath: string) {
|
|||||||
console.log('未找到视频流信息。')
|
console.log('未找到视频流信息。')
|
||||||
}
|
}
|
||||||
resolve({
|
resolve({
|
||||||
width: videoStream.width,
|
width: videoStream?.width!,
|
||||||
height: videoStream.height,
|
height: videoStream?.height!,
|
||||||
time: parseInt(videoStream.duration),
|
time: parseInt(videoStream?.duration!),
|
||||||
format: metadata.format.format_name,
|
format: metadata.format.format_name!,
|
||||||
size,
|
size,
|
||||||
filePath,
|
filePath,
|
||||||
})
|
})
|
||||||
@@ -67,7 +67,7 @@ export async function encodeMp4(filePath: string) {
|
|||||||
return videoInfo
|
return videoInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
export function checkFfmpeg(newPath: string = null): Promise<boolean> {
|
export function checkFfmpeg(newPath: string | null = null): Promise<boolean> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
log('开始检查ffmpeg', newPath)
|
log('开始检查ffmpeg', newPath)
|
||||||
if (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 {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
llonebot: LLOneBot
|
llonebot: LLOneBot
|
||||||
LiteLoader: any
|
LiteLoader: Record<string, any>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
298
src/main/main.ts
298
src/main/main.ts
@@ -13,7 +13,7 @@ import {
|
|||||||
CHANNEL_UPDATE,
|
CHANNEL_UPDATE,
|
||||||
} from '../common/channels'
|
} from '../common/channels'
|
||||||
import { ob11WebsocketServer } from '../onebot11/server/ws/WebsocketServer'
|
import { ob11WebsocketServer } from '../onebot11/server/ws/WebsocketServer'
|
||||||
import { DATA_DIR } from '../common/utils'
|
import { DATA_DIR, qqPkgInfo } from '../common/utils'
|
||||||
import {
|
import {
|
||||||
friendRequests,
|
friendRequests,
|
||||||
getFriend,
|
getFriend,
|
||||||
@@ -25,7 +25,7 @@ import {
|
|||||||
selfInfo,
|
selfInfo,
|
||||||
uidMaps,
|
uidMaps,
|
||||||
} from '../common/data'
|
} 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 { OB11Constructor } from '../onebot11/constructor'
|
||||||
import {
|
import {
|
||||||
ChatType,
|
ChatType,
|
||||||
@@ -36,11 +36,8 @@ import {
|
|||||||
RawMessage,
|
RawMessage,
|
||||||
} from '../ntqqapi/types'
|
} from '../ntqqapi/types'
|
||||||
import { httpHeart, ob11HTTPServer } from '../onebot11/server/http'
|
import { httpHeart, ob11HTTPServer } from '../onebot11/server/http'
|
||||||
import { OB11FriendRecallNoticeEvent } from '../onebot11/event/notice/OB11FriendRecallNoticeEvent'
|
import { postOb11Event } from '../onebot11/server/post-ob11-event'
|
||||||
import { OB11GroupRecallNoticeEvent } from '../onebot11/event/notice/OB11GroupRecallNoticeEvent'
|
|
||||||
import { postOB11Event } from '../onebot11/server/postOB11Event'
|
|
||||||
import { ob11ReverseWebsockets } from '../onebot11/server/ws/ReverseWebsocket'
|
import { ob11ReverseWebsockets } from '../onebot11/server/ws/ReverseWebsocket'
|
||||||
import { OB11GroupAdminNoticeEvent } from '../onebot11/event/notice/OB11GroupAdminNoticeEvent'
|
|
||||||
import { OB11GroupRequestEvent } from '../onebot11/event/request/OB11GroupRequest'
|
import { OB11GroupRequestEvent } from '../onebot11/event/request/OB11GroupRequest'
|
||||||
import { OB11FriendRequestEvent } from '../onebot11/event/request/OB11FriendRequest'
|
import { OB11FriendRequestEvent } from '../onebot11/event/request/OB11FriendRequest'
|
||||||
import * as path from 'node:path'
|
import * as path from 'node:path'
|
||||||
@@ -48,15 +45,15 @@ import { dbUtil } from '../common/db'
|
|||||||
import { setConfig } from './setConfig'
|
import { setConfig } from './setConfig'
|
||||||
import { NTQQUserApi } from '../ntqqapi/api/user'
|
import { NTQQUserApi } from '../ntqqapi/api/user'
|
||||||
import { NTQQGroupApi } from '../ntqqapi/api/group'
|
import { NTQQGroupApi } from '../ntqqapi/api/group'
|
||||||
import { crychic } from '../ntqqapi/external/crychic'
|
|
||||||
import { OB11FriendPokeEvent, OB11GroupPokeEvent } from '../onebot11/event/notice/OB11PokeEvent'
|
|
||||||
import { checkNewVersion, upgradeLLOneBot } from '../common/utils/upgrade'
|
import { checkNewVersion, upgradeLLOneBot } from '../common/utils/upgrade'
|
||||||
import { log } from '../common/utils/log'
|
import { log } from '../common/utils/log'
|
||||||
import { getConfigUtil } from '../common/config'
|
import { getConfigUtil } from '../common/config'
|
||||||
import { checkFfmpeg } from '../common/utils/video'
|
import { checkFfmpeg } from '../common/utils/video'
|
||||||
import { GroupDecreaseSubType, OB11GroupDecreaseEvent } from '../onebot11/event/notice/OB11GroupDecreaseEvent'
|
import { GroupDecreaseSubType, OB11GroupDecreaseEvent } from '../onebot11/event/notice/OB11GroupDecreaseEvent'
|
||||||
|
import '../ntqqapi/wrapper'
|
||||||
let running = false
|
import { sentMessages } from '@/ntqqapi/api'
|
||||||
|
import { NTEventDispatch } from '../common/utils/EventTask'
|
||||||
|
import { wrapperApi, wrapperConstructor } from '../ntqqapi/wrapper'
|
||||||
|
|
||||||
let mainWindow: BrowserWindow | null = null
|
let mainWindow: BrowserWindow | null = null
|
||||||
|
|
||||||
@@ -126,7 +123,7 @@ function onLoad() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
dialog
|
dialog
|
||||||
.showMessageBox(mainWindow, {
|
.showMessageBox(mainWindow!, {
|
||||||
type: 'question',
|
type: 'question',
|
||||||
buttons: ['确认', '取消'],
|
buttons: ['确认', '取消'],
|
||||||
defaultId: 0, // 默认选中的按钮,0 代表第一个按钮,即 "确认"
|
defaultId: 0, // 默认选中的按钮,0 代表第一个按钮,即 "确认"
|
||||||
@@ -141,7 +138,8 @@ function onLoad() {
|
|||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
log('保存设置失败', e.stack)
|
log('保存设置失败', e.stack)
|
||||||
})
|
})
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
@@ -168,12 +166,8 @@ function onLoad() {
|
|||||||
|
|
||||||
OB11Constructor.message(message)
|
OB11Constructor.message(message)
|
||||||
.then((msg) => {
|
.then((msg) => {
|
||||||
if (debug) {
|
if (!debug && msg.message.length === 0) {
|
||||||
msg.raw = message
|
return
|
||||||
} else {
|
|
||||||
if (msg.message.length === 0) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
const isSelfMsg = msg.user_id.toString() == selfInfo.uin
|
const isSelfMsg = msg.user_id.toString() == selfInfo.uin
|
||||||
if (isSelfMsg && !reportSelfMessage) {
|
if (isSelfMsg && !reportSelfMessage) {
|
||||||
@@ -182,81 +176,70 @@ function onLoad() {
|
|||||||
if (isSelfMsg) {
|
if (isSelfMsg) {
|
||||||
msg.target_id = parseInt(message.peerUin)
|
msg.target_id = parseInt(message.peerUin)
|
||||||
}
|
}
|
||||||
postOB11Event(msg)
|
postOb11Event(msg)
|
||||||
// log("post msg", msg)
|
// log("post msg", msg)
|
||||||
})
|
})
|
||||||
.catch((e) => log('constructMessage error: ', e.stack.toString()))
|
.catch((e) => log('constructMessage error: ', e.stack.toString()))
|
||||||
OB11Constructor.GroupEvent(message).then((groupEvent) => {
|
OB11Constructor.GroupEvent(message).then((groupEvent) => {
|
||||||
if (groupEvent) {
|
if (groupEvent) {
|
||||||
// log("post group event", groupEvent);
|
// log("post group event", groupEvent);
|
||||||
postOB11Event(groupEvent)
|
postOb11Event(groupEvent)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
OB11Constructor.FriendAddEvent(message).then((friendAddEvent) => {
|
OB11Constructor.PrivateEvent(message).then((privateEvent) => {
|
||||||
if (friendAddEvent) {
|
log(message)
|
||||||
// log("post friend add event", friendAddEvent);
|
if (privateEvent) {
|
||||||
postOB11Event(friendAddEvent)
|
// 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() {
|
async function startReceiveHook() {
|
||||||
if (getConfigUtil().getConfig().enablePoke) {
|
startHook()
|
||||||
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)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
registerReceiveHook<{
|
registerReceiveHook<{
|
||||||
msgList: Array<RawMessage>
|
msgList: Array<RawMessage>
|
||||||
}>([ReceiveCmdS.NEW_MSG, ReceiveCmdS.NEW_ACTIVE_MSG], async (payload) => {
|
}>([ReceiveCmdS.NEW_MSG, ReceiveCmdS.NEW_ACTIVE_MSG], async (payload) => {
|
||||||
try {
|
try {
|
||||||
await postReceiveMsg(payload.msgList)
|
await postReceiveMsg(payload.msgList)
|
||||||
} catch (e) {
|
} catch (e: any) {
|
||||||
log('report message error: ', e.stack.toString())
|
log('report message error: ', e.stack.toString())
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
const recallMsgIds: string[] = [] // 避免重复上报
|
||||||
registerReceiveHook<{ msgList: Array<RawMessage> }>([ReceiveCmdS.UPDATE_MSG], async (payload) => {
|
registerReceiveHook<{ msgList: Array<RawMessage> }>([ReceiveCmdS.UPDATE_MSG], async (payload) => {
|
||||||
for (const message of payload.msgList) {
|
for (const message of payload.msgList) {
|
||||||
// log("message update", message)
|
const sentMessage = sentMessages[message.msgId]
|
||||||
|
if (sentMessage) {
|
||||||
|
Object.assign(sentMessage, message)
|
||||||
|
}
|
||||||
|
log('message update', message.msgId, message)
|
||||||
if (message.recallTime != '0') {
|
if (message.recallTime != '0') {
|
||||||
//todo: 这个判断方法不太好,应该使用灰色消息元素来判断
|
if (recallMsgIds.includes(message.msgId)) {
|
||||||
// 撤回消息上报
|
continue
|
||||||
|
}
|
||||||
|
recallMsgIds.push(message.msgId)
|
||||||
const oriMessage = await dbUtil.getMsgByLongId(message.msgId)
|
const oriMessage = await dbUtil.getMsgByLongId(message.msgId)
|
||||||
if (!oriMessage) {
|
if (!oriMessage) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
oriMessage.recallTime = message.recallTime
|
oriMessage.recallTime = message.recallTime
|
||||||
dbUtil.updateMsg(oriMessage).then()
|
dbUtil.updateMsg(oriMessage).then()
|
||||||
if (message.chatType == ChatType.friend) {
|
message.msgShortId = oriMessage.msgShortId
|
||||||
const friendRecallEvent = new OB11FriendRecallNoticeEvent(
|
OB11Constructor.RecallEvent(message).then((recallEvent) => {
|
||||||
parseInt(message.senderUin),
|
if (recallEvent) {
|
||||||
oriMessage.msgShortId,
|
log('post recall event', recallEvent)
|
||||||
)
|
postOb11Event(recallEvent)
|
||||||
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
|
|
||||||
}
|
}
|
||||||
const groupRecallEvent = new OB11GroupRecallNoticeEvent(
|
})
|
||||||
parseInt(message.peerUin),
|
|
||||||
parseInt(message.senderUin),
|
|
||||||
parseInt(operatorId),
|
|
||||||
oriMessage.msgShortId,
|
|
||||||
)
|
|
||||||
postOB11Event(groupRecallEvent)
|
|
||||||
}
|
|
||||||
// 不让入库覆盖原来消息,不然就获取不到撤回的消息内容了
|
// 不让入库覆盖原来消息,不然就获取不到撤回的消息内容了
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -271,7 +254,7 @@ function onLoad() {
|
|||||||
// log("reportSelfMessage", payload)
|
// log("reportSelfMessage", payload)
|
||||||
try {
|
try {
|
||||||
await postReceiveMsg([payload.msgRecord])
|
await postReceiveMsg([payload.msgRecord])
|
||||||
} catch (e) {
|
} catch (e: any) {
|
||||||
log('report self message error: ', e.stack.toString())
|
log('report self message error: ', e.stack.toString())
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -311,23 +294,36 @@ function onLoad() {
|
|||||||
// if (notify.user2.uid) {
|
// if (notify.user2.uid) {
|
||||||
// member2 = await getGroupMember(notify.group.groupCode, null, 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)
|
// if (
|
||||||
log('有管理员变动通知')
|
// [GroupNotifyTypes.ADMIN_SET, GroupNotifyTypes.ADMIN_UNSET, GroupNotifyTypes.ADMIN_UNSET_OTHER].includes(
|
||||||
refreshGroupMembers(notify.group.groupCode).then()
|
// notify.type,
|
||||||
let groupAdminNoticeEvent = new OB11GroupAdminNoticeEvent()
|
// )
|
||||||
groupAdminNoticeEvent.group_id = parseInt(notify.group.groupCode)
|
// ) {
|
||||||
log('开始获取变动的管理员')
|
// const member1 = await getGroupMember(notify.group.groupCode, notify.user1.uid)
|
||||||
if (member1) {
|
// log('有管理员变动通知')
|
||||||
log('变动管理员获取成功')
|
// refreshGroupMembers(notify.group.groupCode).then()
|
||||||
groupAdminNoticeEvent.user_id = parseInt(member1.uin)
|
// let groupAdminNoticeEvent = new OB11GroupAdminNoticeEvent()
|
||||||
groupAdminNoticeEvent.sub_type = [GroupNotifyTypes.ADMIN_UNSET, GroupNotifyTypes.ADMIN_UNSET_OTHER].includes(notify.type) ? 'unset' : 'set'
|
// groupAdminNoticeEvent.group_id = parseInt(notify.group.groupCode)
|
||||||
// member1.role = notify.type == GroupNotifyTypes.ADMIN_SET ? GroupMemberRole.admin : GroupMemberRole.normal;
|
// log('开始获取变动的管理员')
|
||||||
postOB11Event(groupAdminNoticeEvent, true)
|
// if (member1) {
|
||||||
} else {
|
// log('变动管理员获取成功')
|
||||||
log('获取群通知的成员信息失败', notify, getGroup(notify.group.groupCode))
|
// groupAdminNoticeEvent.user_id = parseInt(member1.uin)
|
||||||
}
|
// groupAdminNoticeEvent.sub_type = [
|
||||||
} else if (notify.type == GroupNotifyTypes.MEMBER_EXIT || notify.type == GroupNotifyTypes.KICK_MEMBER) {
|
// 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) {
|
||||||
log('有成员退出通知', notify)
|
log('有成员退出通知', notify)
|
||||||
try {
|
try {
|
||||||
const member1 = await NTQQUserApi.getUserDetailInfo(notify.user1.uid)
|
const member1 = await NTQQUserApi.getUserDetailInfo(notify.user1.uid)
|
||||||
@@ -336,7 +332,7 @@ function onLoad() {
|
|||||||
if (notify.user2.uid) {
|
if (notify.user2.uid) {
|
||||||
// 是被踢的
|
// 是被踢的
|
||||||
const member2 = await getGroupMember(notify.group.groupCode, notify.user2.uid)
|
const member2 = await getGroupMember(notify.group.groupCode, notify.user2.uid)
|
||||||
operatorId = member2.uin
|
operatorId = member2?.uin!
|
||||||
subType = 'kick'
|
subType = 'kick'
|
||||||
}
|
}
|
||||||
let groupDecreaseEvent = new OB11GroupDecreaseEvent(
|
let groupDecreaseEvent = new OB11GroupDecreaseEvent(
|
||||||
@@ -345,63 +341,86 @@ function onLoad() {
|
|||||||
parseInt(operatorId),
|
parseInt(operatorId),
|
||||||
subType,
|
subType,
|
||||||
)
|
)
|
||||||
postOB11Event(groupDecreaseEvent, true)
|
postOb11Event(groupDecreaseEvent, true)
|
||||||
} catch (e) {
|
} catch (e: any) {
|
||||||
log('获取群通知的成员信息失败', notify, e.stack.toString())
|
log('获取群通知的成员信息失败', notify, e.stack.toString())
|
||||||
}
|
}
|
||||||
} else if ([GroupNotifyTypes.JOIN_REQUEST].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
|
|
||||||
} catch (e) {
|
|
||||||
log('获取加群人QQ号失败', 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
|
|
||||||
}
|
|
||||||
groupInviteEvent.user_id = parseInt(user_id)
|
|
||||||
groupInviteEvent.sub_type = 'invite'
|
|
||||||
groupInviteEvent.flag = notify.seq
|
|
||||||
postOB11Event(groupInviteEvent)
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
else if ([GroupNotifyTypes.JOIN_REQUEST, GroupNotifyTypes.JOIN_REQUEST_BY_INVITED].includes(notify.type)) {
|
||||||
|
log('有加群请求')
|
||||||
|
let requestQQ = uidMaps[notify.user1.uid]
|
||||||
|
if (!requestQQ) {
|
||||||
|
try {
|
||||||
|
requestQQ = (await NTQQUserApi.getUserDetailInfo(notify.user1.uid)).uin
|
||||||
|
} catch (e) {
|
||||||
|
log('获取加群人QQ号失败', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let invitorId: number
|
||||||
|
if (notify.type == GroupNotifyTypes.JOIN_REQUEST_BY_INVITED) {
|
||||||
|
// groupRequestEvent.sub_type = 'invite'
|
||||||
|
let invitorQQ = uidMaps[notify.user2.uid]
|
||||||
|
if (!invitorQQ) {
|
||||||
|
try {
|
||||||
|
let invitor = (await NTQQUserApi.getUserDetailInfo(notify.user2.uid))
|
||||||
|
invitorId = parseInt(invitor.uin)
|
||||||
|
} catch (e) {
|
||||||
|
invitorId = 0
|
||||||
|
log('获取邀请人QQ号失败', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const groupRequestEvent = new OB11GroupRequestEvent(
|
||||||
|
parseInt(notify.group.groupCode),
|
||||||
|
parseInt(requestQQ) || 0,
|
||||||
|
notify.seq,
|
||||||
|
notify.postscript,
|
||||||
|
invitorId!,
|
||||||
|
'add'
|
||||||
|
)
|
||||||
|
postOb11Event(groupRequestEvent)
|
||||||
|
}
|
||||||
|
else if (notify.type == GroupNotifyTypes.INVITE_ME) {
|
||||||
|
log('收到邀请我加群通知')
|
||||||
|
let userId = uidMaps[notify.user2.uid]
|
||||||
|
if (!userId) {
|
||||||
|
userId = (await NTQQUserApi.getUserDetailInfo(notify.user2.uid))?.uin
|
||||||
|
}
|
||||||
|
const groupInviteEvent = new OB11GroupRequestEvent(
|
||||||
|
parseInt(notify.group.groupCode),
|
||||||
|
parseInt(userId),
|
||||||
|
notify.seq,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
'invite'
|
||||||
|
)
|
||||||
|
postOb11Event(groupInviteEvent)
|
||||||
|
}
|
||||||
|
} catch (e: any) {
|
||||||
log('解析群通知失败', e.stack.toString())
|
log('解析群通知失败', e.stack.toString())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (payload.doubt) {
|
}
|
||||||
|
else if (payload.doubt) {
|
||||||
// 可能有群管理员变动
|
// 可能有群管理员变动
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
registerReceiveHook<FriendRequestNotify>(ReceiveCmdS.FRIEND_REQUEST, async (payload) => {
|
registerReceiveHook<FriendRequestNotify>(ReceiveCmdS.FRIEND_REQUEST, async (payload) => {
|
||||||
for (const req of payload.data.buddyReqs) {
|
for (const req of payload.data.buddyReqs) {
|
||||||
let flag = req.friendUid + req.reqTime
|
const flag = req.friendUid + req.reqTime
|
||||||
if (req.isUnread && parseInt(req.reqTime) > startTime / 1000) {
|
if (req.isUnread && parseInt(req.reqTime) > startTime / 1000) {
|
||||||
friendRequests[flag] = req
|
friendRequests[flag] = req
|
||||||
log('有新的好友请求', req)
|
log('有新的好友请求', req)
|
||||||
let friendRequestEvent = new OB11FriendRequestEvent()
|
let userId: number
|
||||||
try {
|
try {
|
||||||
let requester = await NTQQUserApi.getUserDetailInfo(req.friendUid)
|
const requester = await NTQQUserApi.getUserDetailInfo(req.friendUid)
|
||||||
friendRequestEvent.user_id = parseInt(requester.uin)
|
userId = parseInt(requester.uin)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('获取加好友者QQ号失败', e)
|
log('获取加好友者QQ号失败', e)
|
||||||
}
|
}
|
||||||
friendRequestEvent.flag = flag
|
const friendRequestEvent = new OB11FriendRequestEvent(userId!, req.extWords, flag)
|
||||||
friendRequestEvent.comment = req.extWords
|
postOb11Event(friendRequestEvent)
|
||||||
postOB11Event(friendRequestEvent)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -411,6 +430,11 @@ function onLoad() {
|
|||||||
|
|
||||||
async function start() {
|
async function start() {
|
||||||
log('llonebot pid', process.pid)
|
log('llonebot pid', process.pid)
|
||||||
|
const config = getConfigUtil().getConfig()
|
||||||
|
if (!config.enableLLOB) {
|
||||||
|
log('LLOneBot 开关设置为关闭,不启动LLOneBot')
|
||||||
|
return
|
||||||
|
}
|
||||||
llonebotError.otherError = ''
|
llonebotError.otherError = ''
|
||||||
startTime = Date.now()
|
startTime = Date.now()
|
||||||
dbUtil.getReceivedTempUinMap().then((m) => {
|
dbUtil.getReceivedTempUinMap().then((m) => {
|
||||||
@@ -418,9 +442,34 @@ function onLoad() {
|
|||||||
uidMaps[value] = key
|
uidMaps[value] = key
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
startReceiveHook().then()
|
NTEventDispatch.init({ ListenerMap: wrapperConstructor, WrapperSession: wrapperApi.NodeIQQNTWrapperSession! })
|
||||||
NTQQGroupApi.getGroups(true).then()
|
try {
|
||||||
const config = getConfigUtil().getConfig()
|
log('start get groups')
|
||||||
|
const _groups = await NTQQGroupApi.getGroups()
|
||||||
|
log('_groups', _groups)
|
||||||
|
await Promise.all(
|
||||||
|
_groups.map(async (group) => {
|
||||||
|
try {
|
||||||
|
const members = await NTQQGroupApi.getGroupMembers(group.groupCode)
|
||||||
|
group.members = members
|
||||||
|
groups.push(group)
|
||||||
|
} catch (e) {
|
||||||
|
log('获取群成员失败', e)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
log('获取群列表失败', e)
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
log('start activate group member info')
|
||||||
|
NTQQGroupApi.activateMemberInfoChange().then().catch(log)
|
||||||
|
NTQQGroupApi.activateMemberListChange().then().catch(log)
|
||||||
|
startReceiveHook().then()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
if (config.ob11.enableHttp) {
|
if (config.ob11.enableHttp) {
|
||||||
ob11HTTPServer.start(config.ob11.httpPort)
|
ob11HTTPServer.start(config.ob11.httpPort)
|
||||||
}
|
}
|
||||||
@@ -464,7 +513,7 @@ function onLoad() {
|
|||||||
selfInfo.nick = userInfo.nick
|
selfInfo.nick = userInfo.nick
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e: any) {
|
||||||
log('get self nickname failed', e.stack)
|
log('get self nickname failed', e.stack)
|
||||||
}
|
}
|
||||||
if (getSelfNickCount < 10) {
|
if (getSelfNickCount < 10) {
|
||||||
@@ -474,7 +523,8 @@ function onLoad() {
|
|||||||
|
|
||||||
getUserNick().then()
|
getUserNick().then()
|
||||||
start().then()
|
start().then()
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
setTimeout(init, 1000)
|
setTimeout(init, 1000)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -491,7 +541,7 @@ function onBrowserWindowCreated(window: BrowserWindow) {
|
|||||||
try {
|
try {
|
||||||
hookNTQQApiCall(window)
|
hookNTQQApiCall(window)
|
||||||
hookNTQQApiReceive(window)
|
hookNTQQApiReceive(window)
|
||||||
} catch (e) {
|
} catch (e: any) {
|
||||||
log('LLOneBot hook error: ', e.toString())
|
log('LLOneBot hook error: ', e.toString())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -7,26 +7,33 @@ import {
|
|||||||
ChatCacheList,
|
ChatCacheList,
|
||||||
ChatCacheListItemBasic,
|
ChatCacheListItemBasic,
|
||||||
ChatType,
|
ChatType,
|
||||||
ElementType, IMAGE_HTTP_HOST, IMAGE_HTTP_HOST_NT, RawMessage,
|
ElementType,
|
||||||
|
IMAGE_HTTP_HOST,
|
||||||
|
IMAGE_HTTP_HOST_NT, PicElement,
|
||||||
} from '../types'
|
} from '../types'
|
||||||
import path from 'path'
|
import path from 'node:path'
|
||||||
import fs from 'fs'
|
import fs from 'node:fs'
|
||||||
import { ReceiveCmdS } from '../hook'
|
import { ReceiveCmdS } from '../hook'
|
||||||
import { log } from '../../common/utils'
|
import { log } from '@/common/utils'
|
||||||
import https from 'https'
|
import { rkeyManager } from '@/ntqqapi/api/rkey'
|
||||||
import { sleep } from '../../common/utils'
|
import { wrapperApi } from '@/ntqqapi/wrapper'
|
||||||
import { hookApi } from '../external/moehook/hook'
|
import { Peer } from '@/ntqqapi/types/msg'
|
||||||
|
|
||||||
let privateImageRKey = ''
|
|
||||||
let groupImageRKey = ''
|
|
||||||
let lastGetPrivateRKeyTime = 0
|
|
||||||
let lastGetGroupRKeyTime = 0
|
|
||||||
const rkeyExpireTime = 1000 * 60 * 30
|
|
||||||
|
|
||||||
export class NTQQFileApi {
|
export class NTQQFileApi {
|
||||||
|
static async getVideoUrl(peer: Peer, msgId: string, elementId: string): Promise<string> {
|
||||||
|
const session = wrapperApi.NodeIQQNTWrapperSession
|
||||||
|
return (await session?.getRichMediaService().getVideoPlayUrlV2(peer,
|
||||||
|
msgId,
|
||||||
|
elementId,
|
||||||
|
0,
|
||||||
|
{ downSourceType: 1, triggerType: 1 })).urlResult?.domainUrl[0]?.url;
|
||||||
|
}
|
||||||
|
|
||||||
static async getFileType(filePath: string) {
|
static async getFileType(filePath: string) {
|
||||||
return await callNTQQApi<{ ext: string }>({
|
return await callNTQQApi<{ ext: string }>({
|
||||||
className: NTQQApiClass.FS_API, methodName: NTQQApiMethod.FILE_TYPE, args: [filePath],
|
className: NTQQApiClass.FS_API,
|
||||||
|
methodName: NTQQApiMethod.FILE_TYPE,
|
||||||
|
args: [filePath],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,16 +49,20 @@ export class NTQQFileApi {
|
|||||||
return await callNTQQApi<string>({
|
return await callNTQQApi<string>({
|
||||||
className: NTQQApiClass.FS_API,
|
className: NTQQApiClass.FS_API,
|
||||||
methodName: NTQQApiMethod.FILE_COPY,
|
methodName: NTQQApiMethod.FILE_COPY,
|
||||||
args: [{
|
args: [
|
||||||
fromPath: filePath,
|
{
|
||||||
toPath: destPath,
|
fromPath: filePath,
|
||||||
}],
|
toPath: destPath,
|
||||||
|
},
|
||||||
|
],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
static async getFileSize(filePath: string) {
|
static async getFileSize(filePath: string) {
|
||||||
return await callNTQQApi<number>({
|
return await callNTQQApi<number>({
|
||||||
className: NTQQApiClass.FS_API, methodName: NTQQApiMethod.FILE_SIZE, args: [filePath],
|
className: NTQQApiClass.FS_API,
|
||||||
|
methodName: NTQQApiMethod.FILE_SIZE,
|
||||||
|
args: [filePath],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,18 +81,20 @@ export class NTQQFileApi {
|
|||||||
}
|
}
|
||||||
const mediaPath = await callNTQQApi<string>({
|
const mediaPath = await callNTQQApi<string>({
|
||||||
methodName: NTQQApiMethod.MEDIA_FILE_PATH,
|
methodName: NTQQApiMethod.MEDIA_FILE_PATH,
|
||||||
args: [{
|
args: [
|
||||||
path_info: {
|
{
|
||||||
md5HexStr: md5,
|
path_info: {
|
||||||
fileName: fileName,
|
md5HexStr: md5,
|
||||||
elementType: elementType,
|
fileName: fileName,
|
||||||
elementSubType,
|
elementType: elementType,
|
||||||
thumbSize: 0,
|
elementSubType,
|
||||||
needCreate: true,
|
thumbSize: 0,
|
||||||
downloadType: 1,
|
needCreate: true,
|
||||||
file_uuid: '',
|
downloadType: 1,
|
||||||
|
file_uuid: '',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}],
|
],
|
||||||
})
|
})
|
||||||
log('media path', mediaPath)
|
log('media path', mediaPath)
|
||||||
await NTQQFileApi.copyFile(filePath, mediaPath)
|
await NTQQFileApi.copyFile(filePath, mediaPath)
|
||||||
@@ -95,7 +108,15 @@ export class NTQQFileApi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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,
|
||||||
|
force: boolean = false,
|
||||||
|
) {
|
||||||
// 用于下载收到的消息中的图片等
|
// 用于下载收到的消息中的图片等
|
||||||
if (sourcePath && fs.existsSync(sourcePath)) {
|
if (sourcePath && fs.existsSync(sourcePath)) {
|
||||||
if (force) {
|
if (force) {
|
||||||
@@ -126,7 +147,7 @@ export class NTQQFileApi {
|
|||||||
methodName: NTQQApiMethod.DOWNLOAD_MEDIA,
|
methodName: NTQQApiMethod.DOWNLOAD_MEDIA,
|
||||||
args: apiParams,
|
args: apiParams,
|
||||||
cbCmd: ReceiveCmdS.MEDIA_DOWNLOAD_COMPLETE,
|
cbCmd: ReceiveCmdS.MEDIA_DOWNLOAD_COMPLETE,
|
||||||
cmdCB: (payload: { notifyInfo: { filePath: string, msgId: string } }) => {
|
cmdCB: (payload: { notifyInfo: { filePath: string; msgId: string } }) => {
|
||||||
log('media 下载完成判断', payload.notifyInfo.msgId, msgId)
|
log('media 下载完成判断', payload.notifyInfo.msgId, msgId)
|
||||||
return payload.notifyInfo.msgId == msgId
|
return payload.notifyInfo.msgId == msgId
|
||||||
},
|
},
|
||||||
@@ -135,21 +156,19 @@ export class NTQQFileApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static async getImageSize(filePath: string) {
|
static async getImageSize(filePath: string) {
|
||||||
return await callNTQQApi<{ width: number, height: number }>({
|
return await callNTQQApi<{ width: number; height: number }>({
|
||||||
className: NTQQApiClass.FS_API, methodName: NTQQApiMethod.IMAGE_SIZE, args: [filePath],
|
className: NTQQApiClass.FS_API,
|
||||||
|
methodName: NTQQApiMethod.IMAGE_SIZE,
|
||||||
|
args: [filePath],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
static async getImageUrl(msg: RawMessage) {
|
static async getImageUrl(picElement: PicElement, chatType: ChatType) {
|
||||||
const isPrivateImage = msg.chatType !== ChatType.group
|
const isPrivateImage = chatType !== ChatType.group
|
||||||
const msgElement = msg.elements.find(e => !!e.picElement)
|
const url = picElement.originImageUrl // 没有域名
|
||||||
if (!msgElement) {
|
const md5HexStr = picElement.md5HexStr
|
||||||
return ''
|
const fileMd5 = picElement.md5HexStr
|
||||||
}
|
const fileUuid = picElement.fileUuid
|
||||||
const url = msgElement.picElement.originImageUrl // 没有域名
|
|
||||||
const md5HexStr = msgElement.picElement.md5HexStr
|
|
||||||
const fileMd5 = msgElement.picElement.md5HexStr
|
|
||||||
const fileUuid = msgElement.picElement.fileUuid
|
|
||||||
if (url) {
|
if (url) {
|
||||||
if (url.startsWith('/download')) {
|
if (url.startsWith('/download')) {
|
||||||
// console.log('rkey', rkey);
|
// console.log('rkey', rkey);
|
||||||
@@ -157,70 +176,9 @@ export class NTQQFileApi {
|
|||||||
return IMAGE_HTTP_HOST_NT + url
|
return IMAGE_HTTP_HOST_NT + url
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!hookApi.isAvailable()) {
|
const rkeyData = await rkeyManager.getRkey();
|
||||||
log('hookApi is not available')
|
const existsRKey = isPrivateImage ? rkeyData.private_rkey : rkeyData.group_rkey;
|
||||||
return ''
|
return IMAGE_HTTP_HOST_NT + url + `${existsRKey}`
|
||||||
}
|
|
||||||
|
|
||||||
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}`
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// 老的图片url,不需要rkey
|
// 老的图片url,不需要rkey
|
||||||
return IMAGE_HTTP_HOST + url
|
return IMAGE_HTTP_HOST + url
|
||||||
@@ -229,47 +187,58 @@ export class NTQQFileApi {
|
|||||||
// 没有url,需要自己拼接
|
// 没有url,需要自己拼接
|
||||||
return `${IMAGE_HTTP_HOST}/gchatpic_new/0/0-0-${(fileMd5 || md5HexStr)!.toUpperCase()}/0`
|
return `${IMAGE_HTTP_HOST}/gchatpic_new/0/0-0-${(fileMd5 || md5HexStr)!.toUpperCase()}/0`
|
||||||
}
|
}
|
||||||
log('图片url获取失败', msg)
|
log('图片url获取失败', picElement)
|
||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class NTQQFileCacheApi {
|
export class NTQQFileCacheApi {
|
||||||
static async setCacheSilentScan(isSilent: boolean = true) {
|
static async setCacheSilentScan(isSilent: boolean = true) {
|
||||||
return await callNTQQApi<GeneralCallResult>({
|
return await callNTQQApi<GeneralCallResult>({
|
||||||
methodName: NTQQApiMethod.CACHE_SET_SILENCE,
|
methodName: NTQQApiMethod.CACHE_SET_SILENCE,
|
||||||
args: [{
|
args: [
|
||||||
isSilent,
|
{
|
||||||
}, null],
|
isSilent,
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
static getCacheSessionPathList() {
|
static getCacheSessionPathList() {
|
||||||
return callNTQQApi<{
|
return callNTQQApi<
|
||||||
key: string,
|
{
|
||||||
value: string
|
key: string
|
||||||
}[]>({
|
value: string
|
||||||
|
}[]
|
||||||
|
>({
|
||||||
className: NTQQApiClass.OS_API,
|
className: NTQQApiClass.OS_API,
|
||||||
methodName: NTQQApiMethod.CACHE_PATH_SESSION,
|
methodName: NTQQApiMethod.CACHE_PATH_SESSION,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
static clearCache(cacheKeys: Array<string> = ['tmp', 'hotUpdate']) {
|
static clearCache(cacheKeys: Array<string> = ['tmp', 'hotUpdate']) {
|
||||||
return callNTQQApi<any>({ // TODO: 目前还不知道真正的返回值是什么
|
return callNTQQApi<any>({
|
||||||
|
// TODO: 目前还不知道真正的返回值是什么
|
||||||
methodName: NTQQApiMethod.CACHE_CLEAR,
|
methodName: NTQQApiMethod.CACHE_CLEAR,
|
||||||
args: [{
|
args: [
|
||||||
keys: cacheKeys,
|
{
|
||||||
}, null],
|
keys: cacheKeys,
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
static addCacheScannedPaths(pathMap: object = {}) {
|
static addCacheScannedPaths(pathMap: object = {}) {
|
||||||
return callNTQQApi<GeneralCallResult>({
|
return callNTQQApi<GeneralCallResult>({
|
||||||
methodName: NTQQApiMethod.CACHE_ADD_SCANNED_PATH,
|
methodName: NTQQApiMethod.CACHE_ADD_SCANNED_PATH,
|
||||||
args: [{
|
args: [
|
||||||
pathMap: { ...pathMap },
|
{
|
||||||
}, null],
|
pathMap: { ...pathMap },
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -303,14 +272,18 @@ export class NTQQFileCacheApi {
|
|||||||
return new Promise<ChatCacheList>((res, rej) => {
|
return new Promise<ChatCacheList>((res, rej) => {
|
||||||
callNTQQApi<ChatCacheList>({
|
callNTQQApi<ChatCacheList>({
|
||||||
methodName: NTQQApiMethod.CACHE_CHAT_GET,
|
methodName: NTQQApiMethod.CACHE_CHAT_GET,
|
||||||
args: [{
|
args: [
|
||||||
chatType: type,
|
{
|
||||||
pageSize,
|
chatType: type,
|
||||||
order: 1,
|
pageSize,
|
||||||
pageIndex,
|
order: 1,
|
||||||
}, null],
|
pageIndex,
|
||||||
}).then(list => res(list))
|
},
|
||||||
.catch(e => rej(e))
|
null,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
.then((list) => res(list))
|
||||||
|
.catch((e) => rej(e))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -319,24 +292,29 @@ export class NTQQFileCacheApi {
|
|||||||
|
|
||||||
return callNTQQApi<CacheFileList>({
|
return callNTQQApi<CacheFileList>({
|
||||||
methodName: NTQQApiMethod.CACHE_FILE_GET,
|
methodName: NTQQApiMethod.CACHE_FILE_GET,
|
||||||
args: [{
|
args: [
|
||||||
fileType: fileType,
|
{
|
||||||
restart: true,
|
fileType: fileType,
|
||||||
pageSize: pageSize,
|
restart: true,
|
||||||
order: 1,
|
pageSize: pageSize,
|
||||||
lastRecord: _lastRecord,
|
order: 1,
|
||||||
}, null],
|
lastRecord: _lastRecord,
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
static async clearChatCache(chats: ChatCacheListItemBasic[] = [], fileKeys: string[] = []) {
|
static async clearChatCache(chats: ChatCacheListItemBasic[] = [], fileKeys: string[] = []) {
|
||||||
return await callNTQQApi<GeneralCallResult>({
|
return await callNTQQApi<GeneralCallResult>({
|
||||||
methodName: NTQQApiMethod.CACHE_CHAT_CLEAR,
|
methodName: NTQQApiMethod.CACHE_CHAT_CLEAR,
|
||||||
args: [{
|
args: [
|
||||||
chats,
|
{
|
||||||
fileKeys,
|
chats,
|
||||||
}, null],
|
fileKeys,
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -1,8 +1,10 @@
|
|||||||
import { Friend, FriendRequest } from '../types'
|
import { Friend, FriendRequest, FriendV2 } from '../types'
|
||||||
import { ReceiveCmdS } from '../hook'
|
import { ReceiveCmdS } from '../hook'
|
||||||
import { callNTQQApi, GeneralCallResult, NTQQApiMethod } from '../ntcall'
|
import { callNTQQApi, GeneralCallResult, NTQQApiMethod } from '../ntcall'
|
||||||
import { friendRequests } from '../../common/data'
|
import { friendRequests } from '../../common/data'
|
||||||
import { log } from '../../common/utils'
|
import { wrapperApi } from '@/ntqqapi/wrapper'
|
||||||
|
import { BuddyListReqType, NodeIKernelProfileService } from '../services'
|
||||||
|
import { NTEventDispatch } from '../../common/utils/EventTask'
|
||||||
|
|
||||||
export class NTQQFriendApi {
|
export class NTQQFriendApi {
|
||||||
static async getFriends(forced = false) {
|
static async getFriends(forced = false) {
|
||||||
@@ -26,6 +28,7 @@ export class NTQQFriendApi {
|
|||||||
}
|
}
|
||||||
return _friends
|
return _friends
|
||||||
}
|
}
|
||||||
|
|
||||||
static async likeFriend(uid: string, count = 1) {
|
static async likeFriend(uid: string, count = 1) {
|
||||||
return await callNTQQApi<GeneralCallResult>({
|
return await callNTQQApi<GeneralCallResult>({
|
||||||
methodName: NTQQApiMethod.LIKE_FRIEND,
|
methodName: NTQQApiMethod.LIKE_FRIEND,
|
||||||
@@ -42,6 +45,7 @@ export class NTQQFriendApi {
|
|||||||
],
|
],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
static async handleFriendRequest(flag: string, accept: boolean) {
|
static async handleFriendRequest(flag: string, accept: boolean) {
|
||||||
const request: FriendRequest = friendRequests[flag]
|
const request: FriendRequest = friendRequests[flag]
|
||||||
if (!request) {
|
if (!request) {
|
||||||
@@ -62,4 +66,16 @@ export class NTQQFriendApi {
|
|||||||
delete friendRequests[flag]
|
delete friendRequests[flag]
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async getBuddyV2(refresh = false): Promise<FriendV2[]> {
|
||||||
|
const uids: string[] = []
|
||||||
|
const session = wrapperApi.NodeIQQNTWrapperSession
|
||||||
|
const buddyService = session?.getBuddyService()
|
||||||
|
const buddyListV2 = refresh ? await buddyService?.getBuddyListV2('0', BuddyListReqType.KNOMAL) : await buddyService?.getBuddyListV2('0', BuddyListReqType.KNOMAL)
|
||||||
|
uids.push(...buddyListV2?.data.flatMap(item => item.buddyUids)!)
|
||||||
|
const data = await NTEventDispatch.CallNoListenerEvent<NodeIKernelProfileService['getCoreAndBaseInfo']>(
|
||||||
|
'NodeIKernelProfileService/getCoreAndBaseInfo', 5000, 'nodeStore', uids
|
||||||
|
)
|
||||||
|
return Array.from(data.values())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -5,19 +5,56 @@ import { deleteGroup, uidMaps } from '../../common/data'
|
|||||||
import { dbUtil } from '../../common/db'
|
import { dbUtil } from '../../common/db'
|
||||||
import { log } from '../../common/utils/log'
|
import { log } from '../../common/utils/log'
|
||||||
import { NTQQWindowApi, NTQQWindows } from './window'
|
import { NTQQWindowApi, NTQQWindows } from './window'
|
||||||
|
import { wrapperApi } from '../wrapper'
|
||||||
|
|
||||||
export class NTQQGroupApi {
|
export class NTQQGroupApi {
|
||||||
|
static async activateMemberListChange() {
|
||||||
|
return await callNTQQApi<GeneralCallResult>({
|
||||||
|
methodName: NTQQApiMethod.ACTIVATE_MEMBER_LIST_CHANGE,
|
||||||
|
classNameIsRegister: true,
|
||||||
|
args: [],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
source
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
static async getGroups(forced = false) {
|
static async getGroups(forced = false) {
|
||||||
let cbCmd = ReceiveCmdS.GROUPS
|
// let cbCmd = ReceiveCmdS.GROUPS
|
||||||
if (process.platform != 'win32') {
|
// if (process.platform != 'win32') {
|
||||||
cbCmd = ReceiveCmdS.GROUPS_STORE
|
// cbCmd = ReceiveCmdS.GROUPS_STORE
|
||||||
}
|
// }
|
||||||
const result = await callNTQQApi<{
|
const result = await callNTQQApi<{
|
||||||
updateType: number
|
updateType: number
|
||||||
groupList: Group[]
|
groupList: Group[]
|
||||||
}>({ methodName: NTQQApiMethod.GROUPS, args: [{ force_update: forced }, undefined], cbCmd })
|
}>({
|
||||||
|
methodName: NTQQApiMethod.GROUPS,
|
||||||
|
args: [{ force_update: forced }, undefined],
|
||||||
|
cbCmd: [ReceiveCmdS.GROUPS, ReceiveCmdS.GROUPS_STORE],
|
||||||
|
afterFirstCmd: false,
|
||||||
|
})
|
||||||
|
log('get groups result', result)
|
||||||
return result.groupList
|
return result.groupList
|
||||||
}
|
}
|
||||||
|
|
||||||
static async getGroupMembers(groupQQ: string, num = 3000): Promise<GroupMember[]> {
|
static async getGroupMembers(groupQQ: string, num = 3000): Promise<GroupMember[]> {
|
||||||
const sceneId = await callNTQQApi({
|
const sceneId = await callNTQQApi({
|
||||||
methodName: NTQQApiMethod.GROUP_MEMBER_SCENE,
|
methodName: NTQQApiMethod.GROUP_MEMBER_SCENE,
|
||||||
@@ -28,7 +65,7 @@ export class NTQQGroupApi {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
// log("get group member sceneId", sceneId);
|
// log("get group member sceneId", sceneId)
|
||||||
try {
|
try {
|
||||||
const result = await callNTQQApi<{
|
const result = await callNTQQApi<{
|
||||||
result: { infos: any }
|
result: { infos: any }
|
||||||
@@ -49,8 +86,8 @@ export class NTQQGroupApi {
|
|||||||
for (const member of members) {
|
for (const member of members) {
|
||||||
uidMaps[member.uid] = member.uin
|
uidMaps[member.uid] = member.uin
|
||||||
}
|
}
|
||||||
// log(uidMaps);
|
// log(uidMaps)
|
||||||
// log("members info", values);
|
// log("members info", values)
|
||||||
log(`get group ${groupQQ} members success`)
|
log(`get group ${groupQQ} members success`)
|
||||||
return members
|
return members
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -58,6 +95,21 @@ export class NTQQGroupApi {
|
|||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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() {
|
static async getGroupNotifies() {
|
||||||
// 获取管理员变更
|
// 获取管理员变更
|
||||||
// 加群通知,退出通知,需要管理员权限
|
// 加群通知,退出通知,需要管理员权限
|
||||||
@@ -72,16 +124,22 @@ export class NTQQGroupApi {
|
|||||||
args: [{ doubt: false, startSeq: '', number: 14 }, null],
|
args: [{ doubt: false, startSeq: '', number: 14 }, null],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
static async getGroupIgnoreNotifies() {
|
static async getGroupIgnoreNotifies() {
|
||||||
await NTQQGroupApi.getGroupNotifies()
|
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) {
|
static async handleGroupRequest(seq: string, operateType: GroupRequestOperateTypes, reason?: string) {
|
||||||
const notify: GroupNotify = await dbUtil.getGroupNotify(seq)
|
const notify = await dbUtil.getGroupNotify(seq)
|
||||||
if (!notify) {
|
if (!notify) {
|
||||||
throw `${seq}对应的加群通知不存在`
|
throw `${seq}对应的加群通知不存在`
|
||||||
}
|
}
|
||||||
// delete groupNotifies[seq];
|
// delete groupNotifies[seq]
|
||||||
return await callNTQQApi<GeneralCallResult>({
|
return await callNTQQApi<GeneralCallResult>({
|
||||||
methodName: NTQQApiMethod.HANDLE_GROUP_REQUEST,
|
methodName: NTQQApiMethod.HANDLE_GROUP_REQUEST,
|
||||||
args: [
|
args: [
|
||||||
@@ -101,6 +159,7 @@ export class NTQQGroupApi {
|
|||||||
],
|
],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
static async quitGroup(groupQQ: string) {
|
static async quitGroup(groupQQ: string) {
|
||||||
const result = await callNTQQApi<GeneralCallResult>({
|
const result = await callNTQQApi<GeneralCallResult>({
|
||||||
methodName: NTQQApiMethod.QUIT_GROUP,
|
methodName: NTQQApiMethod.QUIT_GROUP,
|
||||||
@@ -111,6 +170,7 @@ export class NTQQGroupApi {
|
|||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
static async kickMember(
|
static async kickMember(
|
||||||
groupQQ: string,
|
groupQQ: string,
|
||||||
kickUids: string[],
|
kickUids: string[],
|
||||||
@@ -129,7 +189,8 @@ export class NTQQGroupApi {
|
|||||||
],
|
],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
static async banMember(groupQQ: string, memList: Array<{ uid: string; timeStamp: number }>) {
|
|
||||||
|
static async banMember(groupQQ: string, memList: Array<{ uid: string, timeStamp: number }>) {
|
||||||
// timeStamp为秒数, 0为解除禁言
|
// timeStamp为秒数, 0为解除禁言
|
||||||
return await callNTQQApi<GeneralCallResult>({
|
return await callNTQQApi<GeneralCallResult>({
|
||||||
methodName: NTQQApiMethod.MUTE_MEMBER,
|
methodName: NTQQApiMethod.MUTE_MEMBER,
|
||||||
@@ -141,6 +202,7 @@ export class NTQQGroupApi {
|
|||||||
],
|
],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
static async banGroup(groupQQ: string, shutUp: boolean) {
|
static async banGroup(groupQQ: string, shutUp: boolean) {
|
||||||
return await callNTQQApi<GeneralCallResult>({
|
return await callNTQQApi<GeneralCallResult>({
|
||||||
methodName: NTQQApiMethod.MUTE_GROUP,
|
methodName: NTQQApiMethod.MUTE_GROUP,
|
||||||
@@ -153,8 +215,10 @@ export class NTQQGroupApi {
|
|||||||
],
|
],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
static async setMemberCard(groupQQ: string, memberUid: string, cardName: string) {
|
static async setMemberCard(groupQQ: string, memberUid: string, cardName: string) {
|
||||||
return await callNTQQApi<GeneralCallResult>({
|
NTQQGroupApi.activateMemberListChange().then().catch(log)
|
||||||
|
const res = await callNTQQApi<GeneralCallResult>({
|
||||||
methodName: NTQQApiMethod.SET_MEMBER_CARD,
|
methodName: NTQQApiMethod.SET_MEMBER_CARD,
|
||||||
args: [
|
args: [
|
||||||
{
|
{
|
||||||
@@ -165,7 +229,10 @@ export class NTQQGroupApi {
|
|||||||
null,
|
null,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
NTQQGroupApi.getGroupMembersInfo(groupQQ, [memberUid], true).then().catch(log)
|
||||||
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
static async setMemberRole(groupQQ: string, memberUid: string, role: GroupMemberRole) {
|
static async setMemberRole(groupQQ: string, memberUid: string, role: GroupMemberRole) {
|
||||||
return await callNTQQApi<GeneralCallResult>({
|
return await callNTQQApi<GeneralCallResult>({
|
||||||
methodName: NTQQApiMethod.SET_MEMBER_ROLE,
|
methodName: NTQQApiMethod.SET_MEMBER_ROLE,
|
||||||
@@ -179,6 +246,7 @@ export class NTQQGroupApi {
|
|||||||
],
|
],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
static async setGroupName(groupQQ: string, groupName: string) {
|
static async setGroupName(groupQQ: string, groupName: string) {
|
||||||
return await callNTQQApi<GeneralCallResult>({
|
return await callNTQQApi<GeneralCallResult>({
|
||||||
methodName: NTQQApiMethod.SET_GROUP_NAME,
|
methodName: NTQQApiMethod.SET_GROUP_NAME,
|
||||||
@@ -228,5 +296,34 @@ export class NTQQGroupApi {
|
|||||||
],
|
],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
static publishGroupBulletin(groupQQ: string, title: string, content: string) {}
|
|
||||||
|
static publishGroupBulletin(groupQQ: string, title: string, content: string) { }
|
||||||
|
|
||||||
|
static async removeGroupEssence(GroupCode: string, msgId: string) {
|
||||||
|
const session = wrapperApi.NodeIQQNTWrapperSession
|
||||||
|
// 代码没测过
|
||||||
|
// 需要 ob11msgid->msgId + (peer) -> msgSeq + msgRandom
|
||||||
|
let MsgData = await 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 = wrapperApi.NodeIQQNTWrapperSession
|
||||||
|
// 代码没测过
|
||||||
|
// 需要 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,25 +1,94 @@
|
|||||||
import { callNTQQApi, GeneralCallResult, NTQQApiMethod } from '../ntcall'
|
import { callNTQQApi, GeneralCallResult, NTQQApiMethod } from '../ntcall'
|
||||||
import { ChatType, RawMessage, SendMessageElement } from '../types'
|
import { ChatType, RawMessage, SendMessageElement, Peer } from '../types'
|
||||||
import { dbUtil } from '../../common/db'
|
import { dbUtil } from '../../common/db'
|
||||||
import { selfInfo } from '../../common/data'
|
import { selfInfo } from '../../common/data'
|
||||||
import { ReceiveCmdS, registerReceiveHook } from '../hook'
|
import { ReceiveCmdS, registerReceiveHook } from '../hook'
|
||||||
import { log } from '../../common/utils/log'
|
import { log } from '../../common/utils/log'
|
||||||
import { sleep } from '../../common/utils/helper'
|
import { sleep } from '../../common/utils/helper'
|
||||||
import { isQQ998 } from '../../common/utils'
|
import { isQQ998 } from '../../common/utils'
|
||||||
|
import { wrapperApi } from '@/ntqqapi/wrapper'
|
||||||
|
|
||||||
export let sendMessagePool: Record<string, ((sendSuccessMsg: RawMessage) => void) | null> = {} // peerUid: callbackFunnc
|
export let sendMessagePool: Record<string, ((sendSuccessMsg: RawMessage) => void) | null> = {} // peerUid: callbackFunc
|
||||||
|
|
||||||
export interface Peer {
|
export let sentMessages: Record<string, RawMessage> = {} // msgId: RawMessage
|
||||||
chatType: ChatType
|
|
||||||
peerUid: string // 如果是群聊uid为群号,私聊uid就是加密的字符串
|
async function sendWaiter(peer: Peer, waitComplete = true, timeout: number = 10000) {
|
||||||
guildId?: ''
|
// 等待上一个相同的peer发送完
|
||||||
|
const peerUid = peer.peerUid
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await waitLastSend()
|
||||||
|
|
||||||
|
let sentMessage: RawMessage | null = null
|
||||||
|
sendMessagePool[peerUid] = async (rawMessage: RawMessage) => {
|
||||||
|
delete sendMessagePool[peerUid]
|
||||||
|
sentMessage = rawMessage
|
||||||
|
sentMessages[rawMessage.msgId] = rawMessage
|
||||||
|
}
|
||||||
|
|
||||||
|
let checkSendCompleteUsingTime = 0
|
||||||
|
const checkSendComplete = async (): Promise<RawMessage> => {
|
||||||
|
if (sentMessage) {
|
||||||
|
if (waitComplete) {
|
||||||
|
if (sentMessage.sendStatus == 2) {
|
||||||
|
delete sentMessages[sentMessage.msgId]
|
||||||
|
return sentMessage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
delete sentMessages[sentMessage.msgId]
|
||||||
|
return sentMessage
|
||||||
|
}
|
||||||
|
// log(`给${peerUid}发送消息成功`)
|
||||||
|
}
|
||||||
|
checkSendCompleteUsingTime += 500
|
||||||
|
if (checkSendCompleteUsingTime > timeout) {
|
||||||
|
throw '发送超时'
|
||||||
|
}
|
||||||
|
await sleep(500)
|
||||||
|
return await checkSendComplete()
|
||||||
|
}
|
||||||
|
return checkSendComplete()
|
||||||
}
|
}
|
||||||
|
|
||||||
export class NTQQMsgApi {
|
export class NTQQMsgApi {
|
||||||
|
static enterOrExitAIO(peer: Peer, enter: boolean) {
|
||||||
|
return callNTQQApi<GeneralCallResult>({
|
||||||
|
methodName: NTQQApiMethod.ENTER_OR_EXIT_AIO,
|
||||||
|
args: [
|
||||||
|
{
|
||||||
|
"info_list": [
|
||||||
|
{
|
||||||
|
peer,
|
||||||
|
"option": enter ? 1 : 2
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"send": true
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
}
|
||||||
static async setEmojiLike(peer: Peer, msgSeq: string, emojiId: string, set: boolean = true) {
|
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//sysface_res/apng/ 下可以看到所有QQ表情预览
|
||||||
// nt_qq\global\nt_data\Emoji\emoji-resource\face_config.json 里面有所有表情的id, 自带表情id是QSid, 标准emoji表情id是QCid
|
// 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
|
// 其实以官方文档为准是最好的,https://bot.q.qq.com/wiki/develop/api-v2/openapi/emoji/model.html#EmojiType
|
||||||
|
emojiId = emojiId.toString()
|
||||||
return await callNTQQApi<GeneralCallResult>({
|
return await callNTQQApi<GeneralCallResult>({
|
||||||
methodName: NTQQApiMethod.EMOJI_LIKE,
|
methodName: NTQQApiMethod.EMOJI_LIKE,
|
||||||
args: [
|
args: [
|
||||||
@@ -34,6 +103,7 @@ export class NTQQMsgApi {
|
|||||||
],
|
],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
static async getMultiMsg(peer: Peer, rootMsgId: string, parentMsgId: string) {
|
static async getMultiMsg(peer: Peer, rootMsgId: string, parentMsgId: string) {
|
||||||
return await callNTQQApi<GeneralCallResult & { msgList: RawMessage[] }>({
|
return await callNTQQApi<GeneralCallResult & { msgList: RawMessage[] }>({
|
||||||
methodName: NTQQApiMethod.GET_MULTI_MSG,
|
methodName: NTQQApiMethod.GET_MULTI_MSG,
|
||||||
@@ -48,6 +118,20 @@ export class NTQQMsgApi {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async getMsgBoxInfo(peer: Peer) {
|
||||||
|
return await callNTQQApi<GeneralCallResult>({
|
||||||
|
methodName: NTQQApiMethod.GET_MSG_BOX_INFO,
|
||||||
|
args: [
|
||||||
|
{
|
||||||
|
contacts: [
|
||||||
|
peer
|
||||||
|
],
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
static async activateChat(peer: Peer) {
|
static async activateChat(peer: Peer) {
|
||||||
// await this.fetchRecentContact();
|
// await this.fetchRecentContact();
|
||||||
// await sleep(500);
|
// await sleep(500);
|
||||||
@@ -56,6 +140,7 @@ export class NTQQMsgApi {
|
|||||||
args: [{ peer, cnt: 20 }, null],
|
args: [{ peer, cnt: 20 }, null],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
static async activateChatAndGetHistory(peer: Peer) {
|
static async activateChatAndGetHistory(peer: Peer) {
|
||||||
// await this.fetchRecentContact();
|
// await this.fetchRecentContact();
|
||||||
// await sleep(500);
|
// await sleep(500);
|
||||||
@@ -65,6 +150,7 @@ export class NTQQMsgApi {
|
|||||||
args: [{ peer, cnt: 20 }, null],
|
args: [{ peer, cnt: 20 }, null],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
static async getMsgHistory(peer: Peer, msgId: string, count: number) {
|
static async getMsgHistory(peer: Peer, msgId: string, count: number) {
|
||||||
// 消息时间从旧到新
|
// 消息时间从旧到新
|
||||||
return await callNTQQApi<GeneralCallResult & { msgList: RawMessage[] }>({
|
return await callNTQQApi<GeneralCallResult & { msgList: RawMessage[] }>({
|
||||||
@@ -80,6 +166,7 @@ export class NTQQMsgApi {
|
|||||||
],
|
],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
static async fetchRecentContact() {
|
static async fetchRecentContact() {
|
||||||
await callNTQQApi({
|
await callNTQQApi({
|
||||||
methodName: NTQQApiMethod.RECENT_CONTACT,
|
methodName: NTQQApiMethod.RECENT_CONTACT,
|
||||||
@@ -115,52 +202,7 @@ export class NTQQMsgApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static async sendMsg(peer: Peer, msgElements: SendMessageElement[], waitComplete = true, timeout = 10000) {
|
static async sendMsg(peer: Peer, msgElements: SendMessageElement[], waitComplete = true, timeout = 10000) {
|
||||||
const peerUid = peer.peerUid
|
const waiter = sendWaiter(peer, waitComplete, timeout)
|
||||||
|
|
||||||
// 等待上一个相同的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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
await waitLastSend()
|
|
||||||
|
|
||||||
let sentMessage: RawMessage = null
|
|
||||||
sendMessagePool[peerUid] = async (rawMessage: RawMessage) => {
|
|
||||||
delete sendMessagePool[peerUid]
|
|
||||||
sentMessage = rawMessage
|
|
||||||
}
|
|
||||||
|
|
||||||
let checkSendCompleteUsingTime = 0
|
|
||||||
const checkSendComplete = async (): Promise<RawMessage> => {
|
|
||||||
if (sentMessage) {
|
|
||||||
if (waitComplete) {
|
|
||||||
if ((await dbUtil.getMsgByLongId(sentMessage.msgId)).sendStatus == 2) {
|
|
||||||
return sentMessage
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return sentMessage
|
|
||||||
}
|
|
||||||
// log(`给${peerUid}发送消息成功`)
|
|
||||||
}
|
|
||||||
checkSendCompleteUsingTime += 500
|
|
||||||
if (checkSendCompleteUsingTime > timeout) {
|
|
||||||
throw '发送超时'
|
|
||||||
}
|
|
||||||
await sleep(500)
|
|
||||||
return await checkSendComplete()
|
|
||||||
}
|
|
||||||
|
|
||||||
callNTQQApi({
|
callNTQQApi({
|
||||||
methodName: NTQQApiMethod.SEND_MSG,
|
methodName: NTQQApiMethod.SEND_MSG,
|
||||||
args: [
|
args: [
|
||||||
@@ -173,11 +215,12 @@ export class NTQQMsgApi {
|
|||||||
null,
|
null,
|
||||||
],
|
],
|
||||||
}).then()
|
}).then()
|
||||||
return await checkSendComplete()
|
return await waiter
|
||||||
}
|
}
|
||||||
|
|
||||||
static async forwardMsg(srcPeer: Peer, destPeer: Peer, msgIds: string[]) {
|
static async forwardMsg(srcPeer: Peer, destPeer: Peer, msgIds: string[]) {
|
||||||
return await callNTQQApi<GeneralCallResult>({
|
const waiter = sendWaiter(destPeer, true, 10000)
|
||||||
|
callNTQQApi<GeneralCallResult>({
|
||||||
methodName: NTQQApiMethod.FORWARD_MSG,
|
methodName: NTQQApiMethod.FORWARD_MSG,
|
||||||
args: [
|
args: [
|
||||||
{
|
{
|
||||||
@@ -189,7 +232,8 @@ export class NTQQMsgApi {
|
|||||||
},
|
},
|
||||||
null,
|
null,
|
||||||
],
|
],
|
||||||
})
|
}).then().catch(log)
|
||||||
|
return await waiter
|
||||||
}
|
}
|
||||||
|
|
||||||
static async multiForwardMsg(srcPeer: Peer, destPeer: Peer, msgIds: string[]) {
|
static async multiForwardMsg(srcPeer: Peer, destPeer: Peer, msgIds: string[]) {
|
||||||
@@ -244,4 +288,8 @@ export class NTQQMsgApi {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
static async getMsgsBySeqAndCount(peer: Peer, seq: string, count: number, desc: boolean, z: boolean) {
|
||||||
|
const session = wrapperApi.NodeIQQNTWrapperSession
|
||||||
|
return await session?.getMsgService().getMsgsBySeqAndCount(peer, seq, count, desc, z);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
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')
|
@@ -2,10 +2,22 @@ import { callNTQQApi, GeneralCallResult, NTQQApiClass, NTQQApiMethod } from '../
|
|||||||
import { Group, SelfInfo, User } from '../types'
|
import { Group, SelfInfo, User } from '../types'
|
||||||
import { ReceiveCmdS } from '../hook'
|
import { ReceiveCmdS } from '../hook'
|
||||||
import { selfInfo, uidMaps } from '../../common/data'
|
import { selfInfo, uidMaps } from '../../common/data'
|
||||||
import { NTQQWindowApi, NTQQWindows } from './window'
|
import { cacheFunc, isQQ998, log, sleep } from '../../common/utils'
|
||||||
import { isQQ998, log, sleep } from '../../common/utils'
|
import { wrapperApi } from '@/ntqqapi/wrapper'
|
||||||
|
import { RequestUtil } from '@/common/utils/request'
|
||||||
|
import { NodeIKernelProfileService, UserDetailSource, ProfileBizType } from '../services'
|
||||||
|
import { NodeIKernelProfileListener } from '../listeners'
|
||||||
|
import { NTEventDispatch } from '@/common/utils/EventTask'
|
||||||
|
import { qqPkgInfo } from '@/common/utils/QQBasicInfo'
|
||||||
|
|
||||||
let userInfoCache: Record<string, User> = {} // uid: User
|
const userInfoCache: Record<string, User> = {} // uid: User
|
||||||
|
|
||||||
|
export interface ClientKeyData extends GeneralCallResult {
|
||||||
|
url: string
|
||||||
|
keyIndex: string
|
||||||
|
clientKey: string
|
||||||
|
expireTime: string
|
||||||
|
}
|
||||||
|
|
||||||
export class NTQQUserApi {
|
export class NTQQUserApi {
|
||||||
static async setQQAvatar(filePath: string) {
|
static async setQQAvatar(filePath: string) {
|
||||||
@@ -28,6 +40,7 @@ export class NTQQUserApi {
|
|||||||
timeoutSecond: 2,
|
timeoutSecond: 2,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
static async getUserInfo(uid: string) {
|
static async getUserInfo(uid: string) {
|
||||||
const result = await callNTQQApi<{ profiles: Map<string, User> }>({
|
const result = await callNTQQApi<{ profiles: Map<string, User> }>({
|
||||||
methodName: NTQQApiMethod.USER_INFO,
|
methodName: NTQQApiMethod.USER_INFO,
|
||||||
@@ -36,9 +49,54 @@ export class NTQQUserApi {
|
|||||||
})
|
})
|
||||||
return result.profiles.get(uid)
|
return result.profiles.get(uid)
|
||||||
}
|
}
|
||||||
static async getUserDetailInfo(uid: string, getLevel = false) {
|
|
||||||
// this.getUserInfo(uid);
|
// 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;
|
||||||
|
},
|
||||||
|
'BuddyProfileStore',
|
||||||
|
[
|
||||||
|
uid
|
||||||
|
],
|
||||||
|
UserDetailSource.KSERVER,
|
||||||
|
[
|
||||||
|
ProfileBizType.KALL
|
||||||
|
]
|
||||||
|
)
|
||||||
|
const RetUser: User = {
|
||||||
|
...profile.simpleInfo.coreInfo,
|
||||||
|
...profile.simpleInfo.status,
|
||||||
|
...profile.simpleInfo.vasInfo,
|
||||||
|
...profile.commonExt,
|
||||||
|
...profile.simpleInfo.baseInfo,
|
||||||
|
qqLevel: profile.commonExt.qqLevel,
|
||||||
|
pendantId: ''
|
||||||
|
}
|
||||||
|
return RetUser
|
||||||
|
}
|
||||||
|
|
||||||
|
static async getUserDetailInfo(uid: string, getLevel = false, withBizInfo = true) {
|
||||||
|
if (+qqPkgInfo.buildVersion >= 26702) {
|
||||||
|
return this.fetchUserDetailInfo(uid)
|
||||||
|
}
|
||||||
|
// this.getUserInfo(uid)
|
||||||
let methodName = !isQQ998 ? NTQQApiMethod.USER_DETAIL_INFO : NTQQApiMethod.USER_DETAIL_INFO_WITH_BIZ_INFO
|
let methodName = !isQQ998 ? NTQQApiMethod.USER_DETAIL_INFO : NTQQApiMethod.USER_DETAIL_INFO_WITH_BIZ_INFO
|
||||||
|
if (!withBizInfo) {
|
||||||
|
methodName = NTQQApiMethod.USER_DETAIL_INFO
|
||||||
|
}
|
||||||
const fetchInfo = async () => {
|
const fetchInfo = async () => {
|
||||||
const result = await callNTQQApi<{ info: User }>({
|
const result = await callNTQQApi<{ info: User }>({
|
||||||
methodName,
|
methodName,
|
||||||
@@ -67,7 +125,7 @@ export class NTQQUserApi {
|
|||||||
await fetchInfo()
|
await fetchInfo()
|
||||||
await sleep(1000)
|
await sleep(1000)
|
||||||
}
|
}
|
||||||
let userInfo = await fetchInfo()
|
const userInfo = await fetchInfo()
|
||||||
userInfoCache[uid] = userInfo
|
userInfoCache[uid] = userInfo
|
||||||
return userInfo
|
return userInfo
|
||||||
}
|
}
|
||||||
@@ -84,64 +142,43 @@ export class NTQQUserApi {
|
|||||||
],
|
],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
static async getSkey(groupName: string, groupCode: string): Promise<{ data: string }> {
|
|
||||||
return await NTQQWindowApi.openWindow<{ data: string }>(
|
static async getQzoneCookies() {
|
||||||
NTQQWindows.GroupHomeWorkWindow,
|
const requestUrl = 'https://ssl.ptlogin2.qq.com/jump?ptlang=1033&clientuin=' + selfInfo.uin + '&clientkey=' + (await this.getClientKey()).clientKey + '&u1=https%3A%2F%2Fuser.qzone.qq.com%2F' + selfInfo.uin + '%2Finfocenter&keyindex=19%27'
|
||||||
[
|
let cookies: { [key: string]: string } = {}
|
||||||
{
|
try {
|
||||||
groupName,
|
cookies = await RequestUtil.HttpsGetCookies(requestUrl)
|
||||||
groupCode,
|
} catch (e: any) {
|
||||||
source: 'funcbar',
|
log('获取QZone Cookies失败', e)
|
||||||
},
|
cookies = {}
|
||||||
],
|
}
|
||||||
ReceiveCmdS.SKEY_UPDATE,
|
return cookies
|
||||||
1,
|
}
|
||||||
)
|
static async getSkey(): Promise<string> {
|
||||||
// return await callNTQQApi<string>({
|
const clientKeyData = await this.getClientKey()
|
||||||
// className: NTQQApiClass.GROUP_HOME_WORK,
|
if (clientKeyData.result !== 0) {
|
||||||
// methodName: NTQQApiMethod.UPDATE_SKEY,
|
throw new Error('获取clientKey失败')
|
||||||
// args: [
|
}
|
||||||
// {
|
const url = 'https://ssl.ptlogin2.qq.com/jump?ptlang=1033&clientuin=' + selfInfo.uin
|
||||||
// domain: "qun.qq.com"
|
+ '&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
|
||||||
// })
|
|
||||||
// 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 getCookie(group: Group) {
|
@cacheFunc(60 * 30 * 1000)
|
||||||
let cookies = await this.getCookieWithoutSkey()
|
static async getCookies(domain: string) {
|
||||||
let skey = ''
|
if (domain.endsWith("qzone.qq.com")) {
|
||||||
for (let i = 0; i < 2; i++) {
|
let data = (await NTQQUserApi.getQzoneCookies())
|
||||||
skey = (await this.getSkey(group.groupName, group.groupCode)).data
|
const CookieValue = 'p_skey=' + data.p_skey + '; skey=' + data.skey + '; p_uin=o' + selfInfo.uin + '; uin=o' + selfInfo.uin
|
||||||
skey = skey.trim()
|
return { bkn: NTQQUserApi.genBkn(data.p_skey), cookies: CookieValue }
|
||||||
if (skey) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
await sleep(1000)
|
|
||||||
}
|
}
|
||||||
if (!skey) {
|
const skey = await this.getSkey()
|
||||||
throw new Error('获取skey失败')
|
const pskey = (await this.getPSkey([domain])).get(domain)
|
||||||
|
if (!pskey || !skey) {
|
||||||
|
throw new Error('获取Cookies失败')
|
||||||
}
|
}
|
||||||
const bkn = NTQQUserApi.genBkn(skey)
|
const bkn = NTQQUserApi.genBkn(skey)
|
||||||
cookies = cookies.replace('skey=;', `skey=${skey};`)
|
const cookies = `p_skey=${pskey}; skey=${skey}; p_uin=o${selfInfo.uin}; uin=o${selfInfo.uin}`
|
||||||
return { cookies, bkn }
|
return { cookies, bkn }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,4 +193,18 @@ export class NTQQUserApi {
|
|||||||
|
|
||||||
return (hash & 0x7fffffff).toString()
|
return (hash & 0x7fffffff).toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async getPSkey(domains: string[]): Promise<Map<string, string>> {
|
||||||
|
const session = wrapperApi.NodeIQQNTWrapperSession
|
||||||
|
const res = await session?.getTipOffService().getPskey(domains, true)
|
||||||
|
if (res.result !== 0) {
|
||||||
|
throw new Error(`获取Pskey失败: ${res.errMsg}`)
|
||||||
|
}
|
||||||
|
return res.domainPskeyMap
|
||||||
|
}
|
||||||
|
|
||||||
|
static async getClientKey(): Promise<ClientKeyData> {
|
||||||
|
const session = wrapperApi.NodeIQQNTWrapperSession
|
||||||
|
return await session?.getTicketService().forceFetchClientKey('')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,76 +1,379 @@
|
|||||||
import { groups } from '../../common/data'
|
import { WebGroupData, groups, selfInfo } from '@/common/data'
|
||||||
import { log } from '../../common/utils'
|
import { log } from '@/common/utils/log'
|
||||||
import { NTQQUserApi } from './user'
|
import { NTQQUserApi } from './user'
|
||||||
|
import { RequestUtil } from '@/common/utils/request'
|
||||||
|
|
||||||
export class WebApi {
|
export enum WebHonorType {
|
||||||
private static bkn: string
|
ALL = 'all',
|
||||||
private static skey: string
|
TALKACTIVE = 'talkative',
|
||||||
private static pskey: string
|
PERFROMER = 'performer',
|
||||||
private static cookie: string
|
LEGEND = 'legend',
|
||||||
private defaultHeaders: Record<string, string> = {
|
STORONGE_NEWBI = 'strong_newbie',
|
||||||
'User-Agent': 'QQ/8.9.28.635 CFNetwork/1312 Darwin/21.0.0',
|
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) {
|
export interface WebApiGroupNoticeFeed {
|
||||||
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`
|
u: number//发送者
|
||||||
const res = await this.request(url)
|
fid: string//fid
|
||||||
return await res.json()
|
pubt: number//时间
|
||||||
|
msg: {
|
||||||
|
text: string
|
||||||
|
text_face: string
|
||||||
|
title: string,
|
||||||
|
pics?: {
|
||||||
|
id: string,
|
||||||
|
w: string,
|
||||||
|
h: string
|
||||||
|
}[]
|
||||||
}
|
}
|
||||||
|
type: number
|
||||||
public async getGroupDigest(groupCode: string) {
|
fn: number
|
||||||
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`
|
cn: number
|
||||||
const res = await this.request(url)
|
vn: number
|
||||||
log(res.headers)
|
settings: {
|
||||||
return await res.json()
|
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) {
|
export interface WebApiGroupNoticeRet {
|
||||||
return NTQQUserApi.genBkn(sKey)
|
ec: number
|
||||||
}
|
em: string
|
||||||
private async init() {
|
ltsm: number
|
||||||
if (!WebApi.bkn) {
|
srv_code: number
|
||||||
const group = groups[0]
|
read_only: number
|
||||||
WebApi.skey = (await NTQQUserApi.getSkey(group.groupName, group.groupCode)).data
|
role: number
|
||||||
WebApi.bkn = this.genBkn(WebApi.skey)
|
feeds: WebApiGroupNoticeFeed[]
|
||||||
let cookie = await NTQQUserApi.getCookieWithoutSkey()
|
group: {
|
||||||
const pskeyRegex = /p_skey=([^;]+)/
|
group_id: number
|
||||||
const match = cookie.match(pskeyRegex)
|
class_ext: number
|
||||||
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秒
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
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> = {}) {
|
interface GroupEssenceMsg {
|
||||||
await this.init()
|
group_code: string
|
||||||
url += '&bkn=' + WebApi.bkn
|
msg_seq: number
|
||||||
let _headers: Record<string, string> = {
|
msg_random: number
|
||||||
...this.defaultHeaders,
|
sender_uin: string
|
||||||
...headers,
|
sender_nick: string
|
||||||
Cookie: WebApi.cookie,
|
sender_time: number
|
||||||
credentials: 'include',
|
add_digest_uin: string
|
||||||
}
|
add_digest_nick: string
|
||||||
log('request', url, _headers)
|
add_digest_time: number
|
||||||
const options = {
|
msg_content: any[]
|
||||||
method: method,
|
can_be_removed: true
|
||||||
headers: _headers,
|
}
|
||||||
}
|
|
||||||
return fetch(url, options)
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
static async getGroupMembers(GroupCode: string, cached: boolean = true): Promise<WebApiGroupMember[]> {
|
||||||
|
log('webapi 获取群成员', GroupCode);
|
||||||
|
let MemberData: Array<WebApiGroupMember> = new Array<WebApiGroupMember>();
|
||||||
|
try {
|
||||||
|
let cachedData = WebGroupData.GroupData.get(GroupCode);
|
||||||
|
let cachedTime = WebGroupData.GroupTime.get(GroupCode);
|
||||||
|
|
||||||
|
if (!cachedTime || Date.now() - cachedTime > 1800 * 1000 || !cached) {
|
||||||
|
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' + selfInfo.uin;
|
||||||
|
if (!_Skey || !_Pskey) {
|
||||||
|
return MemberData;
|
||||||
|
}
|
||||||
|
const Bkn = WebApi.genBkn(_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]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
WebGroupData.GroupData.set(GroupCode, MemberData);
|
||||||
|
WebGroupData.GroupTime.set(GroupCode, Date.now());
|
||||||
|
} else {
|
||||||
|
MemberData = cachedData as Array<WebApiGroupMember>;
|
||||||
|
}
|
||||||
|
} 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' + selfInfo.uin;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
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' + selfInfo.uin;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
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>(
|
static async openWindow<R = GeneralCallResult>(
|
||||||
ntQQWindow: NTQQWindow,
|
ntQQWindow: NTQQWindow,
|
||||||
args: any[],
|
args: any[],
|
||||||
cbCmd: ReceiveCmd = null,
|
cbCmd: ReceiveCmd | null = null,
|
||||||
autoCloseSeconds: number = 2,
|
autoCloseSeconds: number = 2,
|
||||||
) {
|
) {
|
||||||
const result = await callNTQQApi<R>({
|
const result = await callNTQQApi<R>({
|
||||||
|
@@ -2,7 +2,6 @@ import {
|
|||||||
AtType,
|
AtType,
|
||||||
ElementType,
|
ElementType,
|
||||||
FaceIndex,
|
FaceIndex,
|
||||||
FaceType,
|
|
||||||
PicType,
|
PicType,
|
||||||
SendArkElement,
|
SendArkElement,
|
||||||
SendFaceElement,
|
SendFaceElement,
|
||||||
@@ -22,8 +21,9 @@ import { log } from '../common/utils/log'
|
|||||||
import { defaultVideoThumb, getVideoInfo } from '../common/utils/video'
|
import { defaultVideoThumb, getVideoInfo } from '../common/utils/video'
|
||||||
import { encodeSilk } from '../common/utils/audio'
|
import { encodeSilk } from '../common/utils/audio'
|
||||||
import { isNull } from '../common/utils'
|
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 {
|
export class SendMsgElementConstructor {
|
||||||
static poke(groupCode: string, uin: string) {
|
static poke(groupCode: string, uin: string) {
|
||||||
@@ -76,6 +76,10 @@ export class SendMsgElementConstructor {
|
|||||||
if (fileSize === 0) {
|
if (fileSize === 0) {
|
||||||
throw '文件异常,大小为0'
|
throw '文件异常,大小为0'
|
||||||
}
|
}
|
||||||
|
const maxMB = 30;
|
||||||
|
if (fileSize > 1024 * 1024 * 30){
|
||||||
|
throw `图片过大,最大支持${maxMB}MB,当前文件大小${fileSize}B`
|
||||||
|
}
|
||||||
const imageSize = await NTQQFileApi.getImageSize(picPath)
|
const imageSize = await NTQQFileApi.getImageSize(picPath)
|
||||||
const picElement = {
|
const picElement = {
|
||||||
md5HexStr: md5,
|
md5HexStr: md5,
|
||||||
@@ -119,18 +123,22 @@ export class SendMsgElementConstructor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static async video(filePath: string, fileName: string = '', diyThumbPath: string = ''): Promise<SendVideoElement> {
|
static async video(filePath: string, fileName: string = '', diyThumbPath: string = ''): Promise<SendVideoElement> {
|
||||||
try{
|
try {
|
||||||
await fs.stat(filePath)
|
await fs.stat(filePath)
|
||||||
}catch (e) {
|
} catch (e) {
|
||||||
throw `文件${filePath}异常,不存在`
|
throw `文件${filePath}异常,不存在`
|
||||||
}
|
}
|
||||||
log("复制视频到QQ目录", filePath)
|
log('复制视频到QQ目录', filePath)
|
||||||
let { fileName: _fileName, path, fileSize, md5 } = await NTQQFileApi.uploadFile(filePath, ElementType.VIDEO)
|
let { fileName: _fileName, path, fileSize, md5 } = await NTQQFileApi.uploadFile(filePath, ElementType.VIDEO)
|
||||||
|
|
||||||
log("复制视频到QQ目录完成", path)
|
log('复制视频到QQ目录完成', path)
|
||||||
if (fileSize === 0) {
|
if (fileSize === 0) {
|
||||||
throw '文件异常,大小为0'
|
throw '文件异常,大小为0'
|
||||||
}
|
}
|
||||||
|
const maxMB = 100;
|
||||||
|
if (fileSize > 1024 * 1024 * maxMB) {
|
||||||
|
throw `视频过大,最大支持${maxMB}MB,当前文件大小${fileSize}B`
|
||||||
|
}
|
||||||
const pathLib = require('path')
|
const pathLib = require('path')
|
||||||
let thumbDir = path.replace(`${pathLib.sep}Ori${pathLib.sep}`, `${pathLib.sep}Thumb${pathLib.sep}`)
|
let thumbDir = path.replace(`${pathLib.sep}Ori${pathLib.sep}`, `${pathLib.sep}Thumb${pathLib.sep}`)
|
||||||
thumbDir = pathLib.dirname(thumbDir)
|
thumbDir = pathLib.dirname(thumbDir)
|
||||||
@@ -265,13 +273,30 @@ export class SendMsgElementConstructor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static face(faceId: number): SendFaceElement {
|
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())
|
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 {
|
return {
|
||||||
elementType: ElementType.FACE,
|
elementType: ElementType.FACE,
|
||||||
elementId: '',
|
elementId: '',
|
||||||
faceElement: {
|
faceElement: {
|
||||||
faceIndex: faceId,
|
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 +323,13 @@ export class SendMsgElementConstructor {
|
|||||||
elementId: '',
|
elementId: '',
|
||||||
faceElement: {
|
faceElement: {
|
||||||
faceIndex: FaceIndex.dice,
|
faceIndex: FaceIndex.dice,
|
||||||
faceType: FaceType.dice,
|
faceType: 3,
|
||||||
faceText: '[骰子]',
|
faceText: '[骰子]',
|
||||||
packId: '1',
|
packId: '1',
|
||||||
stickerId: '33',
|
stickerId: '33',
|
||||||
sourceType: 1,
|
sourceType: 1,
|
||||||
stickerType: 2,
|
stickerType: 2,
|
||||||
resultId: resultId.toString(),
|
resultId: resultId?.toString(),
|
||||||
surpriseId: '',
|
surpriseId: '',
|
||||||
// "randomType": 1,
|
// "randomType": 1,
|
||||||
},
|
},
|
||||||
@@ -326,7 +351,7 @@ export class SendMsgElementConstructor {
|
|||||||
stickerId: '34',
|
stickerId: '34',
|
||||||
sourceType: 1,
|
sourceType: 1,
|
||||||
stickerType: 2,
|
stickerType: 2,
|
||||||
resultId: resultId.toString(),
|
resultId: resultId?.toString(),
|
||||||
surpriseId: '',
|
surpriseId: '',
|
||||||
// "randomType": 1,
|
// "randomType": 1,
|
||||||
},
|
},
|
||||||
|
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.
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,29 @@
|
|||||||
import { BrowserWindow } from 'electron'
|
import type { BrowserWindow } from 'electron'
|
||||||
import { NTQQApiClass, NTQQApiMethod } from './ntcall'
|
import { NTQQApiClass, NTQQApiMethod } from './ntcall'
|
||||||
import { NTQQMsgApi, sendMessagePool } from './api/msg'
|
import { NTQQMsgApi, sendMessagePool } from './api/msg'
|
||||||
import { ChatType, Group, GroupMember, GroupMemberRole, RawMessage, User } from './types'
|
import { CategoryFriend, ChatType, Group, GroupMember, GroupMemberRole, RawMessage, User } from './types'
|
||||||
import {
|
import {
|
||||||
deleteGroup,
|
deleteGroup,
|
||||||
friends,
|
friends,
|
||||||
getFriend,
|
getFriend,
|
||||||
getGroupMember,
|
getGroupMember,
|
||||||
groups,
|
groups, rawFriends,
|
||||||
selfInfo,
|
selfInfo,
|
||||||
tempGroupCodeMap,
|
tempGroupCodeMap,
|
||||||
uidMaps,
|
uidMaps,
|
||||||
} from '../common/data'
|
} from '@/common/data'
|
||||||
import { OB11GroupDecreaseEvent } from '../onebot11/event/notice/OB11GroupDecreaseEvent'
|
import { OB11GroupDecreaseEvent } from '../onebot11/event/notice/OB11GroupDecreaseEvent'
|
||||||
import { v4 as uuidv4 } from 'uuid'
|
import { postOb11Event } from '../onebot11/server/post-ob11-event'
|
||||||
import { postOB11Event } from '../onebot11/server/postOB11Event'
|
import { getConfigUtil, HOOK_LOG } from '@/common/config'
|
||||||
import { getConfigUtil, HOOK_LOG } from '../common/config'
|
|
||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
import { dbUtil } from '../common/db'
|
import { dbUtil } from '@/common/db'
|
||||||
import { NTQQGroupApi } from './api/group'
|
import { NTQQGroupApi } from './api/group'
|
||||||
import { log } from '../common/utils/log'
|
import { log } from '@/common/utils'
|
||||||
import { isNumeric, sleep } from '../common/utils/helper'
|
import { isNumeric, sleep } from '@/common/utils'
|
||||||
import { OB11Constructor } from '../onebot11/constructor'
|
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> = {}
|
export let hookApiCallbacks: Record<string, (apiReturn: any) => void> = {}
|
||||||
|
|
||||||
@@ -81,7 +83,7 @@ export function hookNTQQApiReceive(window: BrowserWindow) {
|
|||||||
let isLogger = false
|
let isLogger = false
|
||||||
try {
|
try {
|
||||||
isLogger = args[0]?.eventName?.startsWith('ns-LoggerApi')
|
isLogger = args[0]?.eventName?.startsWith('ns-LoggerApi')
|
||||||
} catch (e) {}
|
} catch (e) { }
|
||||||
if (!isLogger) {
|
if (!isLogger) {
|
||||||
try {
|
try {
|
||||||
HOOK_LOG && log(`received ntqq api message: ${channel}`, args)
|
HOOK_LOG && log(`received ntqq api message: ${channel}`, args)
|
||||||
@@ -100,7 +102,7 @@ export function hookNTQQApiReceive(window: BrowserWindow) {
|
|||||||
try {
|
try {
|
||||||
let _ = hook.hookFunc(receiveData.payload)
|
let _ = hook.hookFunc(receiveData.payload)
|
||||||
if (hook.hookFunc.constructor.name === 'AsyncFunction') {
|
if (hook.hookFunc.constructor.name === 'AsyncFunction') {
|
||||||
;(_ as Promise<void>).then()
|
; (_ as Promise<void>).then()
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('hook error', e, receiveData.payload)
|
log('hook error', e, receiveData.payload)
|
||||||
@@ -121,7 +123,7 @@ export function hookNTQQApiReceive(window: BrowserWindow) {
|
|||||||
delete hookApiCallbacks[callbackId]
|
delete hookApiCallbacks[callbackId]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e: any) {
|
||||||
log('hookNTQQApiReceive error', e.stack.toString(), args)
|
log('hookNTQQApiReceive error', e.stack.toString(), args)
|
||||||
}
|
}
|
||||||
originalSend.call(window.webContents, channel, ...args)
|
originalSend.call(window.webContents, channel, ...args)
|
||||||
@@ -140,11 +142,11 @@ export function hookNTQQApiCall(window: BrowserWindow) {
|
|||||||
let isLogger = false
|
let isLogger = false
|
||||||
try {
|
try {
|
||||||
isLogger = args[3][0].eventName.startsWith('ns-LoggerApi')
|
isLogger = args[3][0].eventName.startsWith('ns-LoggerApi')
|
||||||
} catch (e) {}
|
} catch (e) { }
|
||||||
if (!isLogger) {
|
if (!isLogger) {
|
||||||
try {
|
try {
|
||||||
HOOK_LOG && log('call NTQQ api', thisArg, args)
|
HOOK_LOG && log('call NTQQ api', thisArg, args)
|
||||||
} catch (e) {}
|
} catch (e) { }
|
||||||
try {
|
try {
|
||||||
const _args: unknown[] = args[3][1]
|
const _args: unknown[] = args[3][1]
|
||||||
const cmdName: NTQQApiMethod = _args[0] as NTQQApiMethod
|
const cmdName: NTQQApiMethod = _args[0] as NTQQApiMethod
|
||||||
@@ -155,7 +157,7 @@ export function hookNTQQApiCall(window: BrowserWindow) {
|
|||||||
try {
|
try {
|
||||||
let _ = hook.hookFunc(callParams)
|
let _ = hook.hookFunc(callParams)
|
||||||
if (hook.hookFunc.constructor.name === 'AsyncFunction') {
|
if (hook.hookFunc.constructor.name === 'AsyncFunction') {
|
||||||
;(_ as Promise<void>).then()
|
(_ as Promise<void>).then()
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('hook call error', e, _args)
|
log('hook call error', e, _args)
|
||||||
@@ -163,7 +165,7 @@ export function hookNTQQApiCall(window: BrowserWindow) {
|
|||||||
}).then()
|
}).then()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
} catch (e) {}
|
} catch (e) { }
|
||||||
}
|
}
|
||||||
return target.apply(thisArg, args)
|
return target.apply(thisArg, args)
|
||||||
},
|
},
|
||||||
@@ -187,7 +189,7 @@ export function hookNTQQApiCall(window: BrowserWindow) {
|
|||||||
let ret = target.apply(thisArg, args)
|
let ret = target.apply(thisArg, args)
|
||||||
try {
|
try {
|
||||||
HOOK_LOG && log('call NTQQ invoke api return', ret)
|
HOOK_LOG && log('call NTQQ invoke api return', ret)
|
||||||
} catch (e) {}
|
} catch (e) { }
|
||||||
return ret
|
return ret
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -202,7 +204,7 @@ export function registerReceiveHook<PayloadType>(
|
|||||||
method: ReceiveCmd | ReceiveCmd[],
|
method: ReceiveCmd | ReceiveCmd[],
|
||||||
hookFunc: (payload: PayloadType) => void,
|
hookFunc: (payload: PayloadType) => void,
|
||||||
): string {
|
): string {
|
||||||
const id = uuidv4()
|
const id = randomUUID()
|
||||||
if (!Array.isArray(method)) {
|
if (!Array.isArray(method)) {
|
||||||
method = [method]
|
method = [method]
|
||||||
}
|
}
|
||||||
@@ -294,12 +296,12 @@ async function processGroupEvent(payload: { groupList: Group[] }) {
|
|||||||
|
|
||||||
// 判断bot是否是管理员,如果是管理员不需要从这里得知有人退群,这里的退群无法得知是主动退群还是被踢
|
// 判断bot是否是管理员,如果是管理员不需要从这里得知有人退群,这里的退群无法得知是主动退群还是被踢
|
||||||
let bot = await getGroupMember(group.groupCode, selfInfo.uin)
|
let bot = await getGroupMember(group.groupCode, selfInfo.uin)
|
||||||
if (bot.role == GroupMemberRole.admin || bot.role == GroupMemberRole.owner) {
|
if (bot?.role == GroupMemberRole.admin || bot?.role == GroupMemberRole.owner) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
for (const member of oldMembers) {
|
for (const member of oldMembers) {
|
||||||
if (!newMembersSet.has(member.uin) && member.uin != selfInfo.uin) {
|
if (!newMembersSet.has(member.uin) && member.uin != selfInfo.uin) {
|
||||||
postOB11Event(
|
postOb11Event(
|
||||||
new OB11GroupDecreaseEvent(
|
new OB11GroupDecreaseEvent(
|
||||||
parseInt(group.groupCode),
|
parseInt(group.groupCode),
|
||||||
parseInt(member.uin),
|
parseInt(member.uin),
|
||||||
@@ -318,211 +320,235 @@ async function processGroupEvent(payload: { groupList: Group[] }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
updateGroups(newGroupList, false).then()
|
updateGroups(newGroupList, false).then()
|
||||||
} catch (e) {
|
} catch (e: any) {
|
||||||
updateGroups(payload.groupList).then()
|
updateGroups(payload.groupList).then()
|
||||||
log('更新群信息错误', e.stack.toString())
|
log('更新群信息错误', e.stack.toString())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 群列表变动
|
export async function startHook() {
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
registerReceiveHook<{
|
// 群列表变动
|
||||||
groupCode: string
|
registerReceiveHook<{ groupList: Group[]; updateType: number }>(ReceiveCmdS.GROUPS, (payload) => {
|
||||||
dataSource: number
|
// updateType 3是群列表变动,2是群成员变动
|
||||||
members: Set<GroupMember>
|
// log("群列表变动", payload.updateType, payload.groupList)
|
||||||
}>(ReceiveCmdS.GROUP_MEMBER_INFO_UPDATE, async (payload) => {
|
if (payload.updateType != 2) {
|
||||||
const groupCode = payload.groupCode
|
updateGroups(payload.groupList).then()
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
}
|
else {
|
||||||
// const existGroup = groups.find(g => g.groupCode == groupCode);
|
if (process.platform == 'win32') {
|
||||||
// if (existGroup) {
|
processGroupEvent(payload).then()
|
||||||
// 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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
})
|
registerReceiveHook<{ groupList: Group[]; updateType: number }>(ReceiveCmdS.GROUPS_STORE, (payload) => {
|
||||||
|
// updateType 3是群列表变动,2是群成员变动
|
||||||
registerReceiveHook<{ msgList: Array<RawMessage> }>([ReceiveCmdS.NEW_MSG, ReceiveCmdS.NEW_ACTIVE_MSG], (payload) => {
|
// log("群列表变动", payload.updateType, payload.groupList)
|
||||||
// 保存一下uid
|
if (payload.updateType != 2) {
|
||||||
for (const message of payload.msgList) {
|
updateGroups(payload.groupList).then()
|
||||||
const uid = message.senderUid
|
}
|
||||||
const uin = message.senderUin
|
else {
|
||||||
if (uid && uin) {
|
if (process.platform != 'win32') {
|
||||||
if (message.chatType === ChatType.temp) {
|
processGroupEvent(payload).then()
|
||||||
dbUtil.getReceivedTempUinMap().then((receivedTempUinMap) => {
|
|
||||||
if (!receivedTempUinMap[uin]) {
|
|
||||||
receivedTempUinMap[uin] = uid
|
|
||||||
dbUtil.setReceivedTempUinMap(receivedTempUinMap)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
uidMaps[uid] = uin
|
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
|
|
||||||
// 自动清理新消息文件
|
registerReceiveHook<{
|
||||||
const { autoDeleteFile } = getConfigUtil().getConfig()
|
groupCode: string
|
||||||
if (!autoDeleteFile) {
|
dataSource: number
|
||||||
return
|
members: Set<GroupMember>
|
||||||
}
|
}>(ReceiveCmdS.GROUP_MEMBER_INFO_UPDATE, async (payload) => {
|
||||||
for (const message of payload.msgList) {
|
const groupCode = payload.groupCode
|
||||||
// log("收到新消息,push到历史记录", message.msgId)
|
const members = Array.from(payload.members.values())
|
||||||
// dbUtil.addMsg(message).then()
|
// log("群成员信息变动", groupCode, members)
|
||||||
// 清理文件
|
for (const member of members) {
|
||||||
|
const existMember = await getGroupMember(groupCode, member.uin)
|
||||||
for (const msgElement of message.elements) {
|
if (existMember) {
|
||||||
setTimeout(() => {
|
if (member.cardName != existMember.cardName) {
|
||||||
const picPath = msgElement.picElement?.sourcePath
|
log('群成员名片变动', `${groupCode}: ${existMember.uin}`, existMember.cardName, '->', member.cardName)
|
||||||
const picThumbPath = [...msgElement.picElement?.thumbPath.values()]
|
postOb11Event(
|
||||||
const pttPath = msgElement.pttElement?.filePath
|
new OB11GroupCardEvent(parseInt(groupCode), parseInt(member.uin), member.cardName, existMember.cardName),
|
||||||
const filePath = msgElement.fileElement?.filePath
|
)
|
||||||
const videoPath = msgElement.videoElement?.filePath
|
} else if (member.role != existMember.role) {
|
||||||
const videoThumbPath: string[] = [...msgElement.videoElement?.thumbPath.values()]
|
log('有管理员变动通知')
|
||||||
const pathList = [picPath, ...picThumbPath, pttPath, filePath, videoPath, ...videoThumbPath]
|
const groupAdminNoticeEvent = new OB11GroupAdminNoticeEvent(
|
||||||
if (msgElement.picElement) {
|
member.role == GroupMemberRole.admin ? 'set' : 'unset',
|
||||||
pathList.push(...Object.values(msgElement.picElement.thumbPath))
|
parseInt(groupCode),
|
||||||
|
parseInt(member.uin)
|
||||||
|
)
|
||||||
|
postOb11Event(groupAdminNoticeEvent, true)
|
||||||
}
|
}
|
||||||
const aioOpGrayTipElement = msgElement.grayTipElement?.aioOpGrayTipElement
|
Object.assign(existMember, member)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
}
|
// 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<{ msgRecord: RawMessage }>(ReceiveCmdS.SELF_SEND_MSG, ({ msgRecord }) => {
|
// 好友列表变动
|
||||||
const message = msgRecord
|
registerReceiveHook<{
|
||||||
const peerUid = message.peerUid
|
data: CategoryFriend[]
|
||||||
// log("收到自己发送成功的消息", Object.keys(sendMessagePool), message);
|
}>(ReceiveCmdS.FRIENDS, (payload) => {
|
||||||
// log("收到自己发送成功的消息", message.msgId, message.msgSeq);
|
rawFriends.length = 0;
|
||||||
dbUtil.addMsg(message).then()
|
rawFriends.push(...payload.data);
|
||||||
const sendCallback = sendMessagePool[peerUid]
|
for (const fData of payload.data) {
|
||||||
if (sendCallback) {
|
const _friends = fData.buddyList
|
||||||
try {
|
for (let friend of _friends) {
|
||||||
sendCallback(message)
|
NTQQMsgApi.activateChat({ peerUid: friend.uid, chatType: ChatType.friend }).then()
|
||||||
} catch (e) {
|
let existFriend = friends.find((f) => f.uin == friend.uin)
|
||||||
log('receive self msg error', e.stack)
|
if (!existFriend) {
|
||||||
|
friends.push(friend)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Object.assign(existFriend, friend)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
})
|
|
||||||
|
|
||||||
registerReceiveHook<{ info: { status: number } }>(ReceiveCmdS.SELF_STATUS, (info) => {
|
registerReceiveHook<{ msgList: Array<RawMessage> }>([ReceiveCmdS.NEW_MSG, ReceiveCmdS.NEW_ACTIVE_MSG], (payload) => {
|
||||||
selfInfo.online = info.info.status !== 20
|
// 保存一下uid
|
||||||
})
|
for (const message of payload.msgList) {
|
||||||
|
const uid = message.senderUid
|
||||||
let activatedPeerUids: string[] = []
|
const uin = message.senderUin
|
||||||
registerReceiveHook<{
|
if (uid && uin) {
|
||||||
changedRecentContactLists: {
|
if (message.chatType === ChatType.temp) {
|
||||||
listType: number
|
dbUtil.getReceivedTempUinMap().then((receivedTempUinMap) => {
|
||||||
sortedContactList: string[]
|
if (!receivedTempUinMap[uin]) {
|
||||||
changedList: {
|
receivedTempUinMap[uin] = uid
|
||||||
id: string // peerUid
|
dbUtil.setReceivedTempUinMap(receivedTempUinMap)
|
||||||
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 {
|
uidMaps[uid] = uin
|
||||||
NTQQMsgApi.activateChat(peer).then()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
registerCallHook(NTQQApiMethod.DELETE_ACTIVE_CHAT, async (payload) => {
|
// 自动清理新消息文件
|
||||||
const peerUid = payload[0] as string
|
const { autoDeleteFile } = getConfigUtil().getConfig()
|
||||||
log('激活的聊天窗口被删除,准备重新激活', peerUid)
|
if (!autoDeleteFile) {
|
||||||
let chatType = ChatType.friend
|
return
|
||||||
if (isNumeric(peerUid)) {
|
}
|
||||||
chatType = ChatType.group
|
for (const message of payload.msgList) {
|
||||||
} else {
|
// log("收到新消息,push到历史记录", message.msgId)
|
||||||
// 检查是否好友
|
// dbUtil.addMsg(message).then()
|
||||||
if (!(await getFriend(peerUid))) {
|
// 清理文件
|
||||||
chatType = ChatType.temp
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
const peer = { peerUid, chatType }
|
|
||||||
await sleep(1000)
|
|
||||||
NTQQMsgApi.activateChat(peer).then((r) => {
|
|
||||||
log('重新激活聊天窗口', peer, { result: r.result, errMsg: r.errMsg })
|
|
||||||
})
|
})
|
||||||
})
|
|
||||||
|
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: any) {
|
||||||
|
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 })
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
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[]) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
1
src/ntqqapi/listeners/index.ts
Normal file
1
src/ntqqapi/listeners/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from './NodeIKernelProfileListener'
|
58
src/ntqqapi/native/crychic/index.ts
Normal file
58
src/ntqqapi/native/crychic/index.ts
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
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()
|
@@ -1,11 +1,9 @@
|
|||||||
import * as os from "os";
|
|
||||||
import fs from "fs";
|
|
||||||
import path from "node:path";
|
|
||||||
import {cpModule} from "../cpmodule";
|
import {cpModule} from "../cpmodule";
|
||||||
|
import { qqPkgInfo } from '@/common/utils/QQBasicInfo'
|
||||||
|
|
||||||
interface MoeHook {
|
interface MoeHook {
|
||||||
GetRkey: () => string, // Return '&rkey=xxx'
|
GetRkey: () => string, // Return '&rkey=xxx'
|
||||||
HookRkey: () => string
|
HookRkey: (version: string) => string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -16,7 +14,8 @@ class HookApi {
|
|||||||
cpModule('MoeHoo');
|
cpModule('MoeHoo');
|
||||||
try {
|
try {
|
||||||
this.moeHook = require('./MoeHoo.node');
|
this.moeHook = require('./MoeHoo.node');
|
||||||
console.log("hook rkey地址", this.moeHook!.HookRkey());
|
console.log("hook rkey qq version", this.moeHook!.HookRkey(qqPkgInfo.version));
|
||||||
|
console.log("hook rkey地址", this.moeHook!.HookRkey(qqPkgInfo.version));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log('加载 moehoo 失败', e);
|
console.log('加载 moehoo 失败', e);
|
||||||
}
|
}
|
||||||
@@ -31,4 +30,4 @@ class HookApi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const hookApi = new HookApi();
|
// export const hookApi = new HookApi();
|
@@ -1,11 +1,8 @@
|
|||||||
import { ipcMain } from 'electron'
|
import { ipcMain } from 'electron'
|
||||||
import { hookApiCallbacks, ReceiveCmd, ReceiveCmdS, registerReceiveHook, removeReceiveHook } from './hook'
|
import { hookApiCallbacks, ReceiveCmd, ReceiveCmdS, registerReceiveHook, removeReceiveHook } from './hook'
|
||||||
|
|
||||||
import { v4 as uuidv4 } from 'uuid'
|
|
||||||
import { log } from '../common/utils/log'
|
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 { HOOK_LOG } from '../common/config'
|
||||||
|
import { randomUUID } from 'node:crypto'
|
||||||
|
|
||||||
export enum NTQQApiClass {
|
export enum NTQQApiClass {
|
||||||
NT_API = 'ns-ntApi',
|
NT_API = 'ns-ntApi',
|
||||||
@@ -21,19 +18,24 @@ export enum NTQQApiClass {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export enum NTQQApiMethod {
|
export enum NTQQApiMethod {
|
||||||
|
TEST = 'NodeIKernelTipOffService/getPskey',
|
||||||
RECENT_CONTACT = 'nodeIKernelRecentContactService/fetchAndSubscribeABatchOfRecentContact',
|
RECENT_CONTACT = 'nodeIKernelRecentContactService/fetchAndSubscribeABatchOfRecentContact',
|
||||||
ACTIVE_CHAT_PREVIEW = 'nodeIKernelMsgService/getAioFirstViewLatestMsgsAndAddActiveChat', // 激活聊天窗口,有时候必须这样才能收到消息, 并返回最新预览消息
|
ACTIVE_CHAT_PREVIEW = 'nodeIKernelMsgService/getAioFirstViewLatestMsgsAndAddActiveChat', // 激活聊天窗口,有时候必须这样才能收到消息, 并返回最新预览消息
|
||||||
ACTIVE_CHAT_HISTORY = 'nodeIKernelMsgService/getMsgsIncludeSelfAndAddActiveChat', // 激活聊天窗口,有时候必须这样才能收到消息, 并返回历史消息
|
ACTIVE_CHAT_HISTORY = 'nodeIKernelMsgService/getMsgsIncludeSelfAndAddActiveChat', // 激活聊天窗口,有时候必须这样才能收到消息, 并返回历史消息
|
||||||
HISTORY_MSG = 'nodeIKernelMsgService/getMsgsIncludeSelf',
|
HISTORY_MSG = 'nodeIKernelMsgService/getMsgsIncludeSelf',
|
||||||
GET_MULTI_MSG = 'nodeIKernelMsgService/getMultiMsg',
|
GET_MULTI_MSG = 'nodeIKernelMsgService/getMultiMsg',
|
||||||
DELETE_ACTIVE_CHAT = 'nodeIKernelMsgService/deleteActiveChatByUid',
|
DELETE_ACTIVE_CHAT = 'nodeIKernelMsgService/deleteActiveChatByUid',
|
||||||
|
ENTER_OR_EXIT_AIO = 'nodeIKernelMsgService/enterOrExitAio',
|
||||||
|
|
||||||
LIKE_FRIEND = 'nodeIKernelProfileLikeService/setBuddyProfileLike',
|
LIKE_FRIEND = 'nodeIKernelProfileLikeService/setBuddyProfileLike',
|
||||||
SELF_INFO = 'fetchAuthData',
|
SELF_INFO = 'fetchAuthData',
|
||||||
FRIENDS = 'nodeIKernelBuddyService/getBuddyList',
|
FRIENDS = 'nodeIKernelBuddyService/getBuddyList',
|
||||||
|
|
||||||
GROUPS = 'nodeIKernelGroupService/getGroupList',
|
GROUPS = 'nodeIKernelGroupService/getGroupList',
|
||||||
GROUP_MEMBER_SCENE = 'nodeIKernelGroupService/createMemberListScene',
|
GROUP_MEMBER_SCENE = 'nodeIKernelGroupService/createMemberListScene',
|
||||||
GROUP_MEMBERS = 'nodeIKernelGroupService/getNextMemberList',
|
GROUP_MEMBERS = 'nodeIKernelGroupService/getNextMemberList',
|
||||||
|
GROUP_MEMBERS_INFO = 'nodeIKernelGroupService/getMemberInfo',
|
||||||
|
|
||||||
USER_INFO = 'nodeIKernelProfileService/getUserSimpleInfo',
|
USER_INFO = 'nodeIKernelProfileService/getUserSimpleInfo',
|
||||||
USER_DETAIL_INFO = 'nodeIKernelProfileService/getUserDetailInfo',
|
USER_DETAIL_INFO = 'nodeIKernelProfileService/getUserDetailInfo',
|
||||||
USER_DETAIL_INFO_WITH_BIZ_INFO = 'nodeIKernelProfileService/getUserDetailInfoWithBizInfo',
|
USER_DETAIL_INFO_WITH_BIZ_INFO = 'nodeIKernelProfileService/getUserDetailInfoWithBizInfo',
|
||||||
@@ -65,6 +67,10 @@ export enum NTQQApiMethod {
|
|||||||
PUBLISH_GROUP_BULLETIN = 'nodeIKernelGroupService/publishGroupBulletinBulletin',
|
PUBLISH_GROUP_BULLETIN = 'nodeIKernelGroupService/publishGroupBulletinBulletin',
|
||||||
SET_GROUP_NAME = 'nodeIKernelGroupService/modifyGroupName',
|
SET_GROUP_NAME = 'nodeIKernelGroupService/modifyGroupName',
|
||||||
SET_GROUP_TITLE = 'nodeIKernelGroupService/modifyMemberSpecialTitle',
|
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_SET_SILENCE = 'nodeIKernelStorageCleanService/setSilentScan',
|
||||||
CACHE_ADD_SCANNED_PATH = 'nodeIKernelStorageCleanService/addCacheScanedPaths',
|
CACHE_ADD_SCANNED_PATH = 'nodeIKernelStorageCleanService/addCacheScanedPaths',
|
||||||
@@ -81,7 +87,7 @@ export enum NTQQApiMethod {
|
|||||||
OPEN_EXTRA_WINDOW = 'openExternalWindow',
|
OPEN_EXTRA_WINDOW = 'openExternalWindow',
|
||||||
|
|
||||||
SET_QQ_AVATAR = 'nodeIKernelProfileService/setHeader',
|
SET_QQ_AVATAR = 'nodeIKernelProfileService/setHeader',
|
||||||
GET_SKEY = 'nodeIKernelTipOffService/getPskey',
|
GET_PSKEY = 'nodeIKernelTipOffService/getPskey',
|
||||||
UPDATE_SKEY = 'updatePskey',
|
UPDATE_SKEY = 'updatePskey',
|
||||||
|
|
||||||
FETCH_UNITED_COMMEND_CONFIG = 'nodeIKernelUnitedConfigService/fetchUnitedCommendConfig', // 发包需要调用的
|
FETCH_UNITED_COMMEND_CONFIG = 'nodeIKernelUnitedConfigService/fetchUnitedCommendConfig', // 发包需要调用的
|
||||||
@@ -99,7 +105,7 @@ interface NTQQApiParams {
|
|||||||
channel?: NTQQApiChannel
|
channel?: NTQQApiChannel
|
||||||
classNameIsRegister?: boolean
|
classNameIsRegister?: boolean
|
||||||
args?: unknown[]
|
args?: unknown[]
|
||||||
cbCmd?: ReceiveCmd | null
|
cbCmd?: ReceiveCmd | ReceiveCmd[] | null
|
||||||
cmdCB?: (payload: any) => boolean
|
cmdCB?: (payload: any) => boolean
|
||||||
afterFirstCmd?: boolean // 是否在methodName调用完之后再去hook cbCmd
|
afterFirstCmd?: boolean // 是否在methodName调用完之后再去hook cbCmd
|
||||||
timeoutSecond?: number
|
timeoutSecond?: number
|
||||||
@@ -122,7 +128,7 @@ export function callNTQQApi<ReturnType>(params: NTQQApiParams) {
|
|||||||
args = args ?? []
|
args = args ?? []
|
||||||
timeout = timeout ?? 5
|
timeout = timeout ?? 5
|
||||||
afterFirstCmd = afterFirstCmd ?? true
|
afterFirstCmd = afterFirstCmd ?? true
|
||||||
const uuid = uuidv4()
|
const uuid = randomUUID()
|
||||||
HOOK_LOG && log('callNTQQApi', channel, className, methodName, args, uuid)
|
HOOK_LOG && log('callNTQQApi', channel, className, methodName, args, uuid)
|
||||||
return new Promise((resolve: (data: ReturnType) => void, reject) => {
|
return new Promise((resolve: (data: ReturnType) => void, reject) => {
|
||||||
// log("callNTQQApiPromise", channel, className, methodName, args, uuid)
|
// log("callNTQQApiPromise", channel, className, methodName, args, uuid)
|
||||||
@@ -139,7 +145,8 @@ export function callNTQQApi<ReturnType>(params: NTQQApiParams) {
|
|||||||
success = true
|
success = true
|
||||||
resolve(r)
|
resolve(r)
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
// 这里的callback比较特殊,QQ后端先返回是否调用成功,再返回一条结果数据
|
// 这里的callback比较特殊,QQ后端先返回是否调用成功,再返回一条结果数据
|
||||||
const secondCallback = () => {
|
const secondCallback = () => {
|
||||||
const hookId = registerReceiveHook<ReturnType>(cbCmd, (payload) => {
|
const hookId = registerReceiveHook<ReturnType>(cbCmd, (payload) => {
|
||||||
@@ -150,7 +157,8 @@ export function callNTQQApi<ReturnType>(params: NTQQApiParams) {
|
|||||||
success = true
|
success = true
|
||||||
resolve(payload)
|
resolve(payload)
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
removeReceiveHook(hookId)
|
removeReceiveHook(hookId)
|
||||||
success = true
|
success = true
|
||||||
resolve(payload)
|
resolve(payload)
|
||||||
@@ -162,7 +170,8 @@ export function callNTQQApi<ReturnType>(params: NTQQApiParams) {
|
|||||||
log(`${methodName} callback`, result)
|
log(`${methodName} callback`, result)
|
||||||
if (result?.result == 0 || result === undefined) {
|
if (result?.result == 0 || result === undefined) {
|
||||||
afterFirstCmd && secondCallback()
|
afterFirstCmd && secondCallback()
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
success = true
|
success = true
|
||||||
reject(`ntqq api call failed, ${result.errMsg}`)
|
reject(`ntqq api call failed, ${result.errMsg}`)
|
||||||
}
|
}
|
||||||
@@ -180,7 +189,8 @@ export function callNTQQApi<ReturnType>(params: NTQQApiParams) {
|
|||||||
channel,
|
channel,
|
||||||
{
|
{
|
||||||
sender: {
|
sender: {
|
||||||
send: (..._args: unknown[]) => {},
|
send: (..._args: unknown[]) => {
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{ type: 'request', callbackId: uuid, eventName },
|
{ 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
|
||||||
|
}
|
106
src/ntqqapi/services/NodeIKernelProfileService.ts
Normal file
106
src/ntqqapi/services/NodeIKernelProfileService.ts
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
import { AnyCnameRecord } from 'node:dns'
|
||||||
|
import { SimpleInfo } from '../types'
|
||||||
|
import { GeneralCallResult } from './common'
|
||||||
|
|
||||||
|
export enum UserDetailSource {
|
||||||
|
KDB,
|
||||||
|
KSERVER
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum ProfileBizType {
|
||||||
|
KALL,
|
||||||
|
KBASEEXTEND,
|
||||||
|
KVAS,
|
||||||
|
KQZONE,
|
||||||
|
KOTHER
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NodeIKernelProfileService {
|
||||||
|
getUidByUin(callfrom: string, uin: Array<string>): Promise<Map<string,string>>//uin->uid
|
||||||
|
|
||||||
|
getUinByUid(callfrom: string, uid: Array<string>): Promise<Map<string,string>>
|
||||||
|
|
||||||
|
// {
|
||||||
|
// coreInfo: CoreInfo,
|
||||||
|
// baseInfo: BaseInfo,
|
||||||
|
// status: null,
|
||||||
|
// vasInfo: null,
|
||||||
|
// relationFlags: null,
|
||||||
|
// otherFlags: null,
|
||||||
|
// intimate: null
|
||||||
|
// }
|
||||||
|
getCoreAndBaseInfo(callfrom: string, uids: string[]): Promise<Map<string, SimpleInfo>>
|
||||||
|
|
||||||
|
fetchUserDetailInfo(trace: string, uids: string[], arg2: number, arg3: number[]): Promise<unknown>
|
||||||
|
|
||||||
|
addKernelProfileListener(listener: any): number
|
||||||
|
|
||||||
|
removeKernelProfileListener(listenerId: number): void
|
||||||
|
|
||||||
|
prepareRegionConfig(...args: unknown[]): unknown
|
||||||
|
|
||||||
|
getLocalStrangerRemark(): Promise<AnyCnameRecord>
|
||||||
|
|
||||||
|
enumCountryOptions(): Array<string>
|
||||||
|
|
||||||
|
enumProvinceOptions(Country: string): Array<string>
|
||||||
|
|
||||||
|
enumCityOptions(Country: string, Province: string): unknown
|
||||||
|
|
||||||
|
enumAreaOptions(...args: unknown[]): unknown
|
||||||
|
|
||||||
|
//SimpleInfo
|
||||||
|
// this.uid = ""
|
||||||
|
// this.uid = str
|
||||||
|
// this.uin = j2
|
||||||
|
// this.isBuddy = z
|
||||||
|
// this.coreInfo = coreInfo
|
||||||
|
// this.baseInfo = baseInfo
|
||||||
|
// this.status = statusInfo
|
||||||
|
// this.vasInfo = vasInfo
|
||||||
|
// this.relationFlags = relationFlag
|
||||||
|
// this.otherFlags = otherFlag
|
||||||
|
// this.intimate = intimate
|
||||||
|
|
||||||
|
modifySelfProfile(...args: unknown[]): Promise<unknown>
|
||||||
|
|
||||||
|
modifyDesktopMiniProfile(param: any): Promise<GeneralCallResult>
|
||||||
|
|
||||||
|
setNickName(NickName: string): Promise<unknown>
|
||||||
|
|
||||||
|
setLongNick(longNick: string): Promise<unknown>
|
||||||
|
|
||||||
|
setBirthday(...args: unknown[]): Promise<unknown>
|
||||||
|
|
||||||
|
setGander(...args: unknown[]): Promise<unknown>
|
||||||
|
|
||||||
|
setHeader(arg: string): Promise<unknown>
|
||||||
|
|
||||||
|
setRecommendImgFlag(...args: unknown[]): Promise<unknown>
|
||||||
|
|
||||||
|
getUserSimpleInfo(force: boolean, uids: string[],): Promise<unknown>
|
||||||
|
|
||||||
|
getUserDetailInfo(uid: string): Promise<unknown>
|
||||||
|
|
||||||
|
getUserDetailInfoWithBizInfo(uid: string, Biz: any[]): Promise<GeneralCallResult>
|
||||||
|
|
||||||
|
getUserDetailInfoByUin(uin: string): Promise<any>
|
||||||
|
|
||||||
|
getZplanAvatarInfos(args: string[]): Promise<unknown>
|
||||||
|
|
||||||
|
getStatus(uid: string): Promise<unknown>
|
||||||
|
|
||||||
|
startStatusPolling(isForceReset: boolean): Promise<unknown>
|
||||||
|
|
||||||
|
getSelfStatus(): Promise<unknown>
|
||||||
|
|
||||||
|
setdisableEmojiShortCuts(...args: unknown[]): unknown
|
||||||
|
|
||||||
|
getProfileQzonePicInfo(uid: string, type: number, force: boolean): Promise<unknown>
|
||||||
|
|
||||||
|
//profileService.getCoreInfo("UserRemarkServiceImpl::getStrangerRemarkByUid", arrayList)
|
||||||
|
getCoreInfo(name: string, arg: any[]): unknown
|
||||||
|
|
||||||
|
//m429253e12.getOtherFlag("FriendListInfoCache_getKernelDataAndPutCache", new ArrayList<>())
|
||||||
|
isNull(): boolean
|
||||||
|
}
|
16
src/ntqqapi/services/common.ts
Normal file
16
src/ntqqapi/services/common.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
export enum GeneralCallResultStatus {
|
||||||
|
OK = 0
|
||||||
|
// ERROR = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GeneralCallResult {
|
||||||
|
result: GeneralCallResultStatus
|
||||||
|
errMsg: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface forceFetchClientKeyRetType extends GeneralCallResult {
|
||||||
|
url: string
|
||||||
|
keyIndex: string
|
||||||
|
clientKey: string
|
||||||
|
expireTime: string
|
||||||
|
}
|
2
src/ntqqapi/services/index.ts
Normal file
2
src/ntqqapi/services/index.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export * from './NodeIKernelBuddyService'
|
||||||
|
export * from './NodeIKernelProfileService'
|
@@ -1,5 +1,4 @@
|
|||||||
import { GroupMemberRole } from './group'
|
import { GroupMemberRole } from './group'
|
||||||
import exp from 'constants'
|
|
||||||
|
|
||||||
export enum ElementType {
|
export enum ElementType {
|
||||||
TEXT = 1,
|
TEXT = 1,
|
||||||
@@ -188,6 +187,8 @@ export const IMAGE_HTTP_HOST = 'https://gchat.qpic.cn'
|
|||||||
export const IMAGE_HTTP_HOST_NT = 'https://multimedia.nt.qq.com.cn'
|
export const IMAGE_HTTP_HOST_NT = 'https://multimedia.nt.qq.com.cn'
|
||||||
|
|
||||||
export interface PicElement {
|
export interface PicElement {
|
||||||
|
picSubType: PicSubType
|
||||||
|
picType: PicType // 有这玩意儿吗
|
||||||
originImageUrl: string // http url, 没有host,host是https://gchat.qpic.cn/, 带download参数的是https://multimedia.nt.qq.com.cn
|
originImageUrl: string // http url, 没有host,host是https://gchat.qpic.cn/, 带download参数的是https://multimedia.nt.qq.com.cn
|
||||||
originImageMd5?: string
|
originImageMd5?: string
|
||||||
sourcePath: string // 图片本地路径
|
sourcePath: string // 图片本地路径
|
||||||
@@ -201,6 +202,7 @@ export interface PicElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export enum GrayTipElementSubType {
|
export enum GrayTipElementSubType {
|
||||||
|
RECALL = 1,
|
||||||
INVITE_NEW_MEMBER = 12,
|
INVITE_NEW_MEMBER = 12,
|
||||||
MEMBER_NEW_TITLE = 17,
|
MEMBER_NEW_TITLE = 17,
|
||||||
}
|
}
|
||||||
@@ -213,6 +215,8 @@ export interface GrayTipElement {
|
|||||||
operatorNick: string
|
operatorNick: string
|
||||||
operatorRemark: string
|
operatorRemark: string
|
||||||
operatorMemRemark?: string
|
operatorMemRemark?: string
|
||||||
|
origMsgSenderUid?: string
|
||||||
|
isSelfOperate?: boolean
|
||||||
wording: string // 自定义的撤回提示语
|
wording: string // 自定义的撤回提示语
|
||||||
}
|
}
|
||||||
aioOpGrayTipElement: TipAioOpGrayTipElement
|
aioOpGrayTipElement: TipAioOpGrayTipElement
|
||||||
@@ -222,15 +226,11 @@ export interface GrayTipElement {
|
|||||||
content: string
|
content: string
|
||||||
}
|
}
|
||||||
jsonGrayTipElement: {
|
jsonGrayTipElement: {
|
||||||
|
busiId: number
|
||||||
jsonStr: string
|
jsonStr: string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum FaceType {
|
|
||||||
normal = 1, // 小黄脸
|
|
||||||
normal2 = 2, // 新小黄脸, 从faceIndex 222开始?
|
|
||||||
dice = 3, // 骰子
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum FaceIndex {
|
export enum FaceIndex {
|
||||||
dice = 358,
|
dice = 358,
|
||||||
@@ -239,7 +239,7 @@ export enum FaceIndex {
|
|||||||
|
|
||||||
export interface FaceElement {
|
export interface FaceElement {
|
||||||
faceIndex: number
|
faceIndex: number
|
||||||
faceType: FaceType
|
faceType: number
|
||||||
faceText?: string
|
faceText?: string
|
||||||
packId?: string
|
packId?: string
|
||||||
stickerId?: string
|
stickerId?: string
|
||||||
@@ -414,3 +414,9 @@ export interface RawMessage {
|
|||||||
multiForwardMsgElement: MultiForwardMsgElement
|
multiForwardMsgElement: MultiForwardMsgElement
|
||||||
}[]
|
}[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Peer {
|
||||||
|
chatType: ChatType
|
||||||
|
peerUid: string // 如果是群聊uid为群号,私聊uid就是加密的字符串
|
||||||
|
guildId?: string
|
||||||
|
}
|
@@ -1,12 +1,13 @@
|
|||||||
export enum GroupNotifyTypes {
|
export enum GroupNotifyTypes {
|
||||||
INVITE_ME = 1,
|
INVITE_ME = 1,
|
||||||
INVITED_JOIN = 4, // 有人接受了邀请入群
|
INVITED_JOIN = 4, // 有人接受了邀请入群
|
||||||
|
JOIN_REQUEST_BY_INVITED = 5, // 有人邀请了别人入群
|
||||||
JOIN_REQUEST = 7,
|
JOIN_REQUEST = 7,
|
||||||
ADMIN_SET = 8,
|
ADMIN_SET = 8,
|
||||||
KICK_MEMBER = 9,
|
KICK_MEMBER = 9,
|
||||||
MEMBER_EXIT = 11, // 主动退出
|
MEMBER_EXIT = 11, // 主动退出
|
||||||
ADMIN_UNSET = 12, // 我被取消管理员
|
ADMIN_UNSET = 12, // 我被取消管理员
|
||||||
ADMIN_UNSET_OTHER = 13, // 其他人取消管理员
|
ADMIN_UNSET_OTHER = 13, // 其他人取消管理员
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GroupNotifies {
|
export interface GroupNotifies {
|
||||||
|
@@ -10,6 +10,7 @@ export interface QQLevel {
|
|||||||
moonNum: number
|
moonNum: number
|
||||||
starNum: number
|
starNum: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface User {
|
export interface User {
|
||||||
uid: string // 加密的字符串
|
uid: string // 加密的字符串
|
||||||
uin: string // QQ号
|
uin: string // QQ号
|
||||||
@@ -72,4 +73,189 @@ export interface SelfInfo extends User {
|
|||||||
online?: boolean
|
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
|
||||||
|
}
|
68
src/ntqqapi/wrapper.ts
Normal file
68
src/ntqqapi/wrapper.ts
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
import { NodeIKernelBuddyService } from './services/NodeIKernelBuddyService'
|
||||||
|
import os from 'node:os'
|
||||||
|
const Process = require('node:process')
|
||||||
|
|
||||||
|
export interface NodeIQQNTWrapperSession {
|
||||||
|
[key: string]: any
|
||||||
|
getBuddyService(): NodeIKernelBuddyService
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WrapperApi {
|
||||||
|
NodeIQQNTWrapperSession?: NodeIQQNTWrapperSession
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WrapperConstructor {
|
||||||
|
[key: string]: any
|
||||||
|
NodeIKernelBuddyListener?: any
|
||||||
|
NodeIKernelGroupListener?: any
|
||||||
|
NodeQQNTWrapperUtil?: any
|
||||||
|
NodeIKernelMsgListener?: any
|
||||||
|
NodeIQQNTWrapperEngine?: any
|
||||||
|
NodeIGlobalAdapter?: any
|
||||||
|
NodeIDependsAdapter?: any
|
||||||
|
NodeIDispatcherAdapter?: any
|
||||||
|
NodeIKernelSessionListener?: any
|
||||||
|
NodeIKernelLoginService?: any
|
||||||
|
NodeIKernelLoginListener?: any
|
||||||
|
NodeIKernelProfileService?: any
|
||||||
|
NodeIKernelProfileListener?: any
|
||||||
|
}
|
||||||
|
|
||||||
|
export const wrapperApi: WrapperApi = {}
|
||||||
|
|
||||||
|
export const wrapperConstructor: WrapperConstructor = {}
|
||||||
|
|
||||||
|
const constructor = [
|
||||||
|
'NodeIKernelBuddyListener',
|
||||||
|
'NodeIKernelGroupListener',
|
||||||
|
'NodeQQNTWrapperUtil',
|
||||||
|
'NodeIKernelMsgListener',
|
||||||
|
'NodeIQQNTWrapperEngine',
|
||||||
|
'NodeIGlobalAdapter',
|
||||||
|
'NodeIDependsAdapter',
|
||||||
|
'NodeIDispatcherAdapter',
|
||||||
|
'NodeIKernelSessionListener',
|
||||||
|
'NodeIKernelLoginService',
|
||||||
|
'NodeIKernelLoginListener',
|
||||||
|
'NodeIKernelProfileService',
|
||||||
|
'NodeIKernelProfileListener',
|
||||||
|
]
|
||||||
|
|
||||||
|
Process.dlopenOrig = Process.dlopen
|
||||||
|
|
||||||
|
Process.dlopen = function (module, filename, flags = os.constants.dlopen.RTLD_LAZY) {
|
||||||
|
const dlopenRet = this.dlopenOrig(module, filename, flags)
|
||||||
|
for (let export_name in module.exports) {
|
||||||
|
module.exports[export_name] = new Proxy(module.exports[export_name], {
|
||||||
|
construct: (target, args, _newTarget) => {
|
||||||
|
const ret = new target(...args)
|
||||||
|
if (export_name === 'NodeIQQNTWrapperSession') wrapperApi.NodeIQQNTWrapperSession = ret
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (constructor.includes(export_name)) {
|
||||||
|
wrapperConstructor[export_name] = module.exports[export_name]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dlopenRet
|
||||||
|
}
|
@@ -4,8 +4,8 @@ import { OB11Return } from '../types'
|
|||||||
|
|
||||||
import { log } from '../../common/utils/log'
|
import { log } from '../../common/utils/log'
|
||||||
|
|
||||||
class BaseAction<PayloadType, ReturnDataType> {
|
abstract class BaseAction<PayloadType, ReturnDataType> {
|
||||||
actionName: ActionName
|
abstract actionName: ActionName
|
||||||
|
|
||||||
protected async check(payload: PayloadType): Promise<BaseCheckResult> {
|
protected async check(payload: PayloadType): Promise<BaseCheckResult> {
|
||||||
return {
|
return {
|
||||||
@@ -21,7 +21,7 @@ class BaseAction<PayloadType, ReturnDataType> {
|
|||||||
try {
|
try {
|
||||||
const resData = await this._handle(payload)
|
const resData = await this._handle(payload)
|
||||||
return OB11Response.ok(resData)
|
return OB11Response.ok(resData)
|
||||||
} catch (e) {
|
} catch (e: any) {
|
||||||
log('发生错误', e)
|
log('发生错误', e)
|
||||||
return OB11Response.error(e?.toString() || e?.stack?.toString() || '未知错误,可能操作超时', 200)
|
return OB11Response.error(e?.toString() || e?.stack?.toString() || '未知错误,可能操作超时', 200)
|
||||||
}
|
}
|
||||||
@@ -35,7 +35,7 @@ class BaseAction<PayloadType, ReturnDataType> {
|
|||||||
try {
|
try {
|
||||||
const resData = await this._handle(payload)
|
const resData = await this._handle(payload)
|
||||||
return OB11Response.ok(resData, echo)
|
return OB11Response.ok(resData, echo)
|
||||||
} catch (e) {
|
} catch (e: any) {
|
||||||
log('发生错误', e)
|
log('发生错误', e)
|
||||||
return OB11Response.error(e.stack?.toString() || e.toString(), 1200, echo)
|
return OB11Response.error(e.stack?.toString() || e.toString(), 1200, echo)
|
||||||
}
|
}
|
||||||
|
@@ -1,12 +1,12 @@
|
|||||||
import BaseAction from '../BaseAction'
|
import BaseAction from '../BaseAction'
|
||||||
import fs from 'fs/promises'
|
import fs from 'fs/promises'
|
||||||
import { dbUtil } from '../../../common/db'
|
import { dbUtil } from '@/common/db'
|
||||||
import { getConfigUtil } from '../../../common/config'
|
import { getConfigUtil } from '@/common/config'
|
||||||
import { log, sleep, uri2local } from '../../../common/utils'
|
import { checkFileReceived, log, sleep, uri2local } from '@/common/utils'
|
||||||
import { NTQQFileApi } from '../../../ntqqapi/api/file'
|
import { NTQQFileApi } from '@/ntqqapi/api'
|
||||||
import { ActionName } from '../types'
|
import { ActionName } from '../types'
|
||||||
import { FileElement, RawMessage, VideoElement } from '../../../ntqqapi/types'
|
import { FileElement, RawMessage, VideoElement } from '@/ntqqapi/types'
|
||||||
import { FileCache } from '../../../common/types'
|
import { FileCache } from '@/common/types'
|
||||||
|
|
||||||
export interface GetFilePayload {
|
export interface GetFilePayload {
|
||||||
file: string // 文件名或者fileUuid
|
file: string // 文件名或者fileUuid
|
||||||
@@ -20,14 +20,13 @@ export interface GetFileResponse {
|
|||||||
base64?: string
|
base64?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export class GetFileBase extends BaseAction<GetFilePayload, GetFileResponse> {
|
export abstract class GetFileBase extends BaseAction<GetFilePayload, GetFileResponse> {
|
||||||
private getElement(msg: RawMessage): { id: string; element: VideoElement | FileElement } {
|
private getElement(msg: RawMessage, elementId: string): VideoElement | FileElement {
|
||||||
let element = msg.elements.find((e) => e.fileElement)
|
let element = msg.elements.find((e) => e.elementId === elementId)
|
||||||
if (!element) {
|
if (!element) {
|
||||||
element = msg.elements.find((e) => e.videoElement)
|
throw new Error('element not found')
|
||||||
return { id: element.elementId, element: element.videoElement }
|
|
||||||
}
|
}
|
||||||
return { id: element.elementId, element: element.fileElement }
|
return element.fileElement
|
||||||
}
|
}
|
||||||
private async download(cache: FileCache, file: string) {
|
private async download(cache: FileCache, file: string) {
|
||||||
log('需要调用 NTQQ 下载文件api')
|
log('需要调用 NTQQ 下载文件api')
|
||||||
@@ -35,24 +34,25 @@ export class GetFileBase extends BaseAction<GetFilePayload, GetFileResponse> {
|
|||||||
let msg = await dbUtil.getMsgByLongId(cache.msgId)
|
let msg = await dbUtil.getMsgByLongId(cache.msgId)
|
||||||
if (msg) {
|
if (msg) {
|
||||||
log('找到了文件 msg', msg)
|
log('找到了文件 msg', msg)
|
||||||
let element = this.getElement(msg)
|
let element = this.getElement(msg, cache.elementId)
|
||||||
log('找到了文件 element', element)
|
log('找到了文件 element', element)
|
||||||
// 构建下载函数
|
// 构建下载函数
|
||||||
await NTQQFileApi.downloadMedia(msg.msgId, msg.chatType, msg.peerUid, element.id, '', '', true)
|
await NTQQFileApi.downloadMedia(msg.msgId, msg.chatType, msg.peerUid, cache.elementId, '', '', true)
|
||||||
await sleep(1000)
|
// 等待文件下载完成
|
||||||
msg = await dbUtil.getMsgByLongId(cache.msgId)
|
msg = await dbUtil.getMsgByLongId(cache.msgId)
|
||||||
log('下载完成后的msg', msg)
|
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()
|
dbUtil.addFileCache(file, cache).then()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
protected async _handle(payload: GetFilePayload): Promise<GetFileResponse> {
|
protected async _handle(payload: GetFilePayload): Promise<GetFileResponse> {
|
||||||
const cache = await dbUtil.getFileCache(payload.file)
|
let cache = await dbUtil.getFileCache(payload.file)
|
||||||
const { autoDeleteFile, enableLocalFile2Url, autoDeleteFileSecond } = getConfigUtil().getConfig()
|
|
||||||
if (!cache) {
|
if (!cache) {
|
||||||
throw new Error('file not found')
|
throw new Error('file not found')
|
||||||
}
|
}
|
||||||
|
const { autoDeleteFile, enableLocalFile2Url, autoDeleteFileSecond } = getConfigUtil().getConfig()
|
||||||
if (cache.downloadFunc) {
|
if (cache.downloadFunc) {
|
||||||
await cache.downloadFunc()
|
await cache.downloadFunc()
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,9 @@
|
|||||||
import { GetFileBase, GetFilePayload, GetFileResponse } from './GetFile'
|
import { GetFileBase, GetFilePayload, GetFileResponse } from './GetFile'
|
||||||
import { ActionName } from '../types'
|
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 {
|
interface Payload extends GetFilePayload {
|
||||||
out_format: 'mp3' | 'amr' | 'wma' | 'm4a' | 'spx' | 'ogg' | 'wav' | 'flac'
|
out_format: 'mp3' | 'amr' | 'wma' | 'm4a' | 'spx' | 'ogg' | 'wav' | 'flac'
|
||||||
@@ -9,7 +13,13 @@ export default class GetRecord extends GetFileBase {
|
|||||||
actionName = ActionName.GetRecord
|
actionName = ActionName.GetRecord
|
||||||
|
|
||||||
protected async _handle(payload: Payload): Promise<GetFileResponse> {
|
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
|
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 fs from 'fs'
|
||||||
import { join as joinPath } from 'node:path'
|
import { join as joinPath } from 'node:path'
|
||||||
import { calculateFileMD5, httpDownload, TEMP_DIR } from '../../../common/utils'
|
import { calculateFileMD5, httpDownload, TEMP_DIR } from '../../../common/utils'
|
||||||
import { v4 as uuid4 } from 'uuid'
|
import { randomUUID } from 'node:crypto'
|
||||||
|
|
||||||
interface Payload {
|
interface Payload {
|
||||||
thread_count?: number
|
thread_count?: number
|
||||||
@@ -22,7 +22,7 @@ export default class GoCQHTTPDownloadFile extends BaseAction<Payload, FileRespon
|
|||||||
|
|
||||||
protected async _handle(payload: Payload): Promise<FileResponse> {
|
protected async _handle(payload: Payload): Promise<FileResponse> {
|
||||||
const isRandomName = !payload.name
|
const isRandomName = !payload.name
|
||||||
let name = payload.name || uuid4()
|
let name = payload.name || randomUUID()
|
||||||
const filePath = joinPath(TEMP_DIR, name)
|
const filePath = joinPath(TEMP_DIR, name)
|
||||||
|
|
||||||
if (payload.base64) {
|
if (payload.base64) {
|
||||||
|
@@ -1,12 +1,13 @@
|
|||||||
import BaseAction from '../BaseAction'
|
import BaseAction from '../BaseAction'
|
||||||
import { OB11ForwardMessage, OB11Message, OB11MessageData } from '../../types'
|
import { OB11ForwardMessage, OB11Message, OB11MessageData } from '../../types'
|
||||||
import { NTQQMsgApi, Peer } from '../../../ntqqapi/api'
|
import { NTQQMsgApi } from '@/ntqqapi/api'
|
||||||
import { dbUtil } from '../../../common/db'
|
import { dbUtil } from '../../../common/db'
|
||||||
import { OB11Constructor } from '../../constructor'
|
import { OB11Constructor } from '../../constructor'
|
||||||
import { ActionName } from '../types'
|
import { ActionName } from '../types'
|
||||||
|
|
||||||
interface Payload {
|
interface Payload {
|
||||||
message_id: string // long msg id
|
message_id: string // long msg id,gocq
|
||||||
|
id?: string // long msg id, onebot11
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Response {
|
interface Response {
|
||||||
@@ -16,7 +17,11 @@ interface Response {
|
|||||||
export class GoCQHTTGetForwardMsgAction extends BaseAction<Payload, any> {
|
export class GoCQHTTGetForwardMsgAction extends BaseAction<Payload, any> {
|
||||||
actionName = ActionName.GoCQHTTP_GetForwardMsg
|
actionName = ActionName.GoCQHTTP_GetForwardMsg
|
||||||
protected async _handle(payload: Payload): Promise<any> {
|
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) {
|
if (!rootMsg) {
|
||||||
throw Error('msg not found')
|
throw Error('msg not found')
|
||||||
}
|
}
|
||||||
@@ -32,12 +37,13 @@ export class GoCQHTTGetForwardMsgAction extends BaseAction<Payload, any> {
|
|||||||
let messages = await Promise.all(
|
let messages = await Promise.all(
|
||||||
msgList.map(async (msg) => {
|
msgList.map(async (msg) => {
|
||||||
let resMsg = await OB11Constructor.message(msg)
|
let resMsg = await OB11Constructor.message(msg)
|
||||||
resMsg.message_id = await dbUtil.addMsg(msg)
|
resMsg.message_id = (await dbUtil.addMsg(msg))!
|
||||||
return resMsg
|
return resMsg
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
messages.map((msg) => {
|
messages.map(v => {
|
||||||
;(<OB11ForwardMessage>msg).content = msg.message
|
const msg = v as Partial<OB11ForwardMessage>
|
||||||
|
msg.content = msg.message
|
||||||
delete msg.message
|
delete msg.message
|
||||||
})
|
})
|
||||||
return { messages }
|
return { messages }
|
||||||
|
@@ -6,7 +6,6 @@ import { ChatType } from '../../../ntqqapi/types'
|
|||||||
import { dbUtil } from '../../../common/db'
|
import { dbUtil } from '../../../common/db'
|
||||||
import { NTQQMsgApi } from '../../../ntqqapi/api/msg'
|
import { NTQQMsgApi } from '../../../ntqqapi/api/msg'
|
||||||
import { OB11Constructor } from '../../constructor'
|
import { OB11Constructor } from '../../constructor'
|
||||||
import { log } from '../../../common/utils'
|
|
||||||
|
|
||||||
interface Payload {
|
interface Payload {
|
||||||
group_id: number
|
group_id: number
|
||||||
|
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,11 +1,12 @@
|
|||||||
import BaseAction from '../BaseAction'
|
import BaseAction from '../BaseAction'
|
||||||
import { getGroup, getUidByUin } from '../../../common/data'
|
import { getGroup, getUidByUin } from '@/common/data'
|
||||||
import { ActionName } from '../types'
|
import { ActionName } from '../types'
|
||||||
import { SendMsgElementConstructor } from '../../../ntqqapi/constructor'
|
import { SendMsgElementConstructor } from '@/ntqqapi/constructor'
|
||||||
import { ChatType, SendFileElement } from '../../../ntqqapi/types'
|
import { ChatType, SendFileElement } from '@/ntqqapi/types'
|
||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
import { NTQQMsgApi, Peer } from '../../../ntqqapi/api/msg'
|
import { NTQQMsgApi } from '@/ntqqapi/api/msg'
|
||||||
import { uri2local } from '../../../common/utils'
|
import { uri2local } from '@/common/utils'
|
||||||
|
import { Peer } from '@/ntqqapi/types'
|
||||||
|
|
||||||
interface Payload {
|
interface Payload {
|
||||||
user_id: number
|
user_id: number
|
||||||
@@ -20,9 +21,9 @@ class GoCQHTTPUploadFileBase extends BaseAction<Payload, null> {
|
|||||||
|
|
||||||
getPeer(payload: Payload): Peer {
|
getPeer(payload: Payload): Peer {
|
||||||
if (payload.user_id) {
|
if (payload.user_id) {
|
||||||
return { chatType: ChatType.friend, peerUid: getUidByUin(payload.user_id.toString()) }
|
return { chatType: ChatType.friend, peerUid: getUidByUin(payload.user_id.toString())! }
|
||||||
}
|
}
|
||||||
return { chatType: ChatType.group, peerUid: payload.group_id.toString() }
|
return { chatType: ChatType.group, peerUid: payload.group_id?.toString()! }
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async _handle(payload: Payload): Promise<null> {
|
protected async _handle(payload: Payload): Promise<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
|
actionName = ActionName.GetGroupList
|
||||||
|
|
||||||
protected async _handle(payload: Payload) {
|
protected async _handle(payload: Payload) {
|
||||||
if (
|
if (groups.length === 0 || payload?.no_cache === true || payload?.no_cache === 'true') {
|
||||||
groups.length === 0
|
|
||||||
|| payload?.no_cache === true || payload?.no_cache === 'true'
|
|
||||||
) {
|
|
||||||
try {
|
try {
|
||||||
const groups = await NTQQGroupApi.getGroups(true)
|
const groups = await NTQQGroupApi.getGroups(true)
|
||||||
log("强制刷新群列表, 数量:", groups.length)
|
log('强制刷新群列表, 数量:', groups.length)
|
||||||
return OB11Constructor.groups(groups)
|
return OB11Constructor.groups(groups)
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
}
|
}
|
||||||
|
@@ -7,7 +7,7 @@ import { NTQQGroupApi } from '../../../ntqqapi/api/group'
|
|||||||
import { log } from '../../../common/utils'
|
import { log } from '../../../common/utils'
|
||||||
|
|
||||||
export interface PayloadType {
|
export interface PayloadType {
|
||||||
group_id: number,
|
group_id: number
|
||||||
no_cache: boolean | string
|
no_cache: boolean | string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -2,13 +2,11 @@ import SendMsg from '../msg/SendMsg'
|
|||||||
import { ActionName, BaseCheckResult } from '../types'
|
import { ActionName, BaseCheckResult } from '../types'
|
||||||
import { OB11PostSendMsg } from '../../types'
|
import { OB11PostSendMsg } from '../../types'
|
||||||
|
|
||||||
import { log } from '../../../common/utils/log'
|
|
||||||
|
|
||||||
class SendGroupMsg extends SendMsg {
|
class SendGroupMsg extends SendMsg {
|
||||||
actionName = ActionName.SendGroupMsg
|
actionName = ActionName.SendGroupMsg
|
||||||
|
|
||||||
protected async check(payload: OB11PostSendMsg): Promise<BaseCheckResult> {
|
protected async check(payload: OB11PostSendMsg): Promise<BaseCheckResult> {
|
||||||
delete payload.user_id
|
delete (payload as Partial<OB11PostSendMsg>).user_id
|
||||||
payload.message_type = 'group'
|
payload.message_type = 'group'
|
||||||
return super.check(payload)
|
return super.check(payload)
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import GetMsg from './msg/GetMsg'
|
import GetMsg from './msg/GetMsg'
|
||||||
import GetLoginInfo from './system/GetLoginInfo'
|
import GetLoginInfo from './system/GetLoginInfo'
|
||||||
import GetFriendList from './user/GetFriendList'
|
import { GetFriendList, GetFriendWithCategory} from './user/GetFriendList'
|
||||||
import GetGroupList from './group/GetGroupList'
|
import GetGroupList from './group/GetGroupList'
|
||||||
import GetGroupInfo from './group/GetGroupInfo'
|
import GetGroupInfo from './group/GetGroupInfo'
|
||||||
import GetGroupMemberList from './group/GetGroupMemberList'
|
import GetGroupMemberList from './group/GetGroupMemberList'
|
||||||
@@ -46,7 +46,14 @@ import GetFile from './file/GetFile'
|
|||||||
import { GoCQHTTGetForwardMsgAction } from './go-cqhttp/GetForwardMsg'
|
import { GoCQHTTGetForwardMsgAction } from './go-cqhttp/GetForwardMsg'
|
||||||
import { GetCookies } from './user/GetCookie'
|
import { GetCookies } from './user/GetCookie'
|
||||||
import { SetMsgEmojiLike } from './msg/SetMsgEmojiLike'
|
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 = [
|
export const actionHandlers = [
|
||||||
new GetFile(),
|
new GetFile(),
|
||||||
@@ -55,6 +62,8 @@ export const actionHandlers = [
|
|||||||
new SetConfigAction(),
|
new SetConfigAction(),
|
||||||
new GetGroupAddRequest(),
|
new GetGroupAddRequest(),
|
||||||
new SetQQAvatar(),
|
new SetQQAvatar(),
|
||||||
|
new GetFriendWithCategory(),
|
||||||
|
new GetEvent(),
|
||||||
// onebot11
|
// onebot11
|
||||||
new SendLike(),
|
new SendLike(),
|
||||||
new GetMsg(),
|
new GetMsg(),
|
||||||
@@ -87,8 +96,10 @@ export const actionHandlers = [
|
|||||||
new GetCookies(),
|
new GetCookies(),
|
||||||
new SetMsgEmojiLike(),
|
new SetMsgEmojiLike(),
|
||||||
new ForwardFriendSingleMsg(),
|
new ForwardFriendSingleMsg(),
|
||||||
new ForwardSingleGroupMsg(),
|
new ForwardGroupSingleMsg(),
|
||||||
//以下为go-cqhttp api
|
//以下为go-cqhttp api
|
||||||
|
new GetGroupEssence(),
|
||||||
|
new GetGroupHonorInfo(),
|
||||||
new GoCQHTTPSendForwardMsg(),
|
new GoCQHTTPSendForwardMsg(),
|
||||||
new GoCQHTTPSendGroupForwardMsg(),
|
new GoCQHTTPSendGroupForwardMsg(),
|
||||||
new GoCQHTTPSendPrivateForwardMsg(),
|
new GoCQHTTPSendPrivateForwardMsg(),
|
||||||
@@ -100,6 +111,9 @@ export const actionHandlers = [
|
|||||||
new GoCQHTTPUploadPrivateFile(),
|
new GoCQHTTPUploadPrivateFile(),
|
||||||
new GoCQHTTPGetGroupMsgHistory(),
|
new GoCQHTTPGetGroupMsgHistory(),
|
||||||
new GoCQHTTGetForwardMsgAction(),
|
new GoCQHTTGetForwardMsgAction(),
|
||||||
|
new GoCQHTTHandleQuickOperation(),
|
||||||
|
new GoCQHTTPSetEssenceMsg(),
|
||||||
|
new GoCQHTTPDelEssenceMsg()
|
||||||
]
|
]
|
||||||
|
|
||||||
function initActionMap() {
|
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,9 +1,10 @@
|
|||||||
import BaseAction from '../BaseAction'
|
import BaseAction from '../BaseAction'
|
||||||
import { NTQQMsgApi, Peer } from '../../../ntqqapi/api'
|
import { NTQQMsgApi } from '@/ntqqapi/api'
|
||||||
import { ChatType, RawMessage } from '../../../ntqqapi/types'
|
import { ChatType, RawMessage } from '@/ntqqapi/types'
|
||||||
import { dbUtil } from '../../../common/db'
|
import { dbUtil } from '@/common/db'
|
||||||
import { getUidByUin } from '../../../common/data'
|
import { getUidByUin } from '@/common/data'
|
||||||
import { ActionName } from '../types'
|
import { ActionName } from '../types'
|
||||||
|
import { Peer } from '@/ntqqapi/types'
|
||||||
|
|
||||||
interface Payload {
|
interface Payload {
|
||||||
message_id: number
|
message_id: number
|
||||||
@@ -11,18 +12,22 @@ interface Payload {
|
|||||||
user_id?: number
|
user_id?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
class ForwardSingleMsg extends BaseAction<Payload, null> {
|
interface Response {
|
||||||
|
message_id: number
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class ForwardSingleMsg extends BaseAction<Payload, Response> {
|
||||||
protected async getTargetPeer(payload: Payload): Promise<Peer> {
|
protected async getTargetPeer(payload: Payload): Promise<Peer> {
|
||||||
if (payload.user_id) {
|
if (payload.user_id) {
|
||||||
return { chatType: ChatType.friend, peerUid: getUidByUin(payload.user_id.toString()) }
|
return { chatType: ChatType.friend, peerUid: getUidByUin(payload.user_id.toString())! }
|
||||||
}
|
}
|
||||||
return { chatType: ChatType.group, peerUid: payload.group_id.toString() }
|
return { chatType: ChatType.group, peerUid: payload.group_id.toString() }
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async _handle(payload: Payload): Promise<null> {
|
protected async _handle(payload: Payload): Promise<Response> {
|
||||||
const msg = await dbUtil.getMsgByShortId(payload.message_id)
|
const msg = (await dbUtil.getMsgByShortId(payload.message_id))!
|
||||||
const peer = await this.getTargetPeer(payload)
|
const peer = await this.getTargetPeer(payload)
|
||||||
await NTQQMsgApi.forwardMsg(
|
const sentMsg = await NTQQMsgApi.forwardMsg(
|
||||||
{
|
{
|
||||||
chatType: msg.chatType,
|
chatType: msg.chatType,
|
||||||
peerUid: msg.peerUid,
|
peerUid: msg.peerUid,
|
||||||
@@ -30,7 +35,8 @@ class ForwardSingleMsg extends BaseAction<Payload, null> {
|
|||||||
peer,
|
peer,
|
||||||
[msg.msgId],
|
[msg.msgId],
|
||||||
)
|
)
|
||||||
return null
|
const ob11MsgId = await dbUtil.addMsg(sentMsg)
|
||||||
|
return { message_id: ob11MsgId! }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,6 +44,6 @@ export class ForwardFriendSingleMsg extends ForwardSingleMsg {
|
|||||||
actionName = ActionName.ForwardFriendSingleMsg
|
actionName = ActionName.ForwardFriendSingleMsg
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ForwardSingleGroupMsg extends ForwardSingleMsg {
|
export class ForwardGroupSingleMsg extends ForwardSingleMsg {
|
||||||
actionName = ActionName.ForwardGroupSingleMsg
|
actionName = ActionName.ForwardGroupSingleMsg
|
||||||
}
|
}
|
||||||
|
@@ -14,14 +14,16 @@ import { friends, getFriend, getGroup, getGroupMember, getUidByUin, selfInfo } f
|
|||||||
import {
|
import {
|
||||||
OB11MessageCustomMusic,
|
OB11MessageCustomMusic,
|
||||||
OB11MessageData,
|
OB11MessageData,
|
||||||
OB11MessageDataType, OB11MessageFile,
|
OB11MessageDataType,
|
||||||
|
OB11MessageFile,
|
||||||
OB11MessageJson,
|
OB11MessageJson,
|
||||||
OB11MessageMixType,
|
OB11MessageMixType,
|
||||||
OB11MessageMusic,
|
OB11MessageMusic,
|
||||||
OB11MessageNode, OB11MessageVideo,
|
OB11MessageNode,
|
||||||
|
OB11MessageVideo,
|
||||||
OB11PostSendMsg,
|
OB11PostSendMsg,
|
||||||
} from '../../types'
|
} from '../../types'
|
||||||
import { NTQQMsgApi, Peer } from '../../../ntqqapi/api/msg'
|
import { NTQQMsgApi } from '../../../ntqqapi/api/msg'
|
||||||
import { SendMsgElementConstructor } from '../../../ntqqapi/constructor'
|
import { SendMsgElementConstructor } from '../../../ntqqapi/constructor'
|
||||||
import BaseAction from '../BaseAction'
|
import BaseAction from '../BaseAction'
|
||||||
import { ActionName, BaseCheckResult } from '../types'
|
import { ActionName, BaseCheckResult } from '../types'
|
||||||
@@ -32,9 +34,10 @@ import { ALLOW_SEND_TEMP_MSG, getConfigUtil } from '../../../common/config'
|
|||||||
import { log } from '../../../common/utils/log'
|
import { log } from '../../../common/utils/log'
|
||||||
import { sleep } from '../../../common/utils/helper'
|
import { sleep } from '../../../common/utils/helper'
|
||||||
import { uri2local } from '../../../common/utils'
|
import { uri2local } from '../../../common/utils'
|
||||||
import { crychic } from '../../../ntqqapi/external/crychic'
|
import { crychic } from '../../../ntqqapi/native/crychic'
|
||||||
import { NTQQGroupApi } from '../../../ntqqapi/api'
|
import { NTQQGroupApi } from '../../../ntqqapi/api'
|
||||||
import { CustomMusicSignPostData, IdMusicSignPostData, MusicSign, MusicSignPostData } from '../../../common/utils/sign'
|
import { CustomMusicSignPostData, IdMusicSignPostData, MusicSign, MusicSignPostData } from '../../../common/utils/sign'
|
||||||
|
import { Peer } from '../../../ntqqapi/types/msg'
|
||||||
|
|
||||||
function checkSendMessage(sendMsgList: OB11MessageData[]) {
|
function checkSendMessage(sendMsgList: OB11MessageData[]) {
|
||||||
function checkUri(uri: string): boolean {
|
function checkUri(uri: string): boolean {
|
||||||
@@ -48,22 +51,28 @@ function checkSendMessage(sendMsgList: OB11MessageData[]) {
|
|||||||
let data = msg['data']
|
let data = msg['data']
|
||||||
if (type === 'text' && !data['text']) {
|
if (type === 'text' && !data['text']) {
|
||||||
return 400
|
return 400
|
||||||
} else if (['image', 'voice', 'record'].includes(type)) {
|
}
|
||||||
|
else if (['image', 'voice', 'record'].includes(type)) {
|
||||||
if (!data['file']) {
|
if (!data['file']) {
|
||||||
return 400
|
return 400
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
if (checkUri(data['file'])) {
|
if (checkUri(data['file'])) {
|
||||||
return 200
|
return 200
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
return 400
|
return 400
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (type === 'at' && !data['qq']) {
|
}
|
||||||
return 400
|
else if (type === 'at' && !data['qq']) {
|
||||||
} else if (type === 'reply' && !data['id']) {
|
|
||||||
return 400
|
return 400
|
||||||
}
|
}
|
||||||
} else {
|
else if (type === 'reply' && !data['id']) {
|
||||||
|
return 400
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
return 400
|
return 400
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -85,10 +94,12 @@ export function convertMessage2List(message: OB11MessageMixType, autoEscape = fa
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
message = decodeCQCode(message.toString())
|
message = decodeCQCode(message.toString())
|
||||||
}
|
}
|
||||||
} else if (!Array.isArray(message)) {
|
}
|
||||||
|
else if (!Array.isArray(message)) {
|
||||||
message = [message]
|
message = [message]
|
||||||
}
|
}
|
||||||
return message
|
return message
|
||||||
@@ -106,172 +117,177 @@ export async function createSendElements(
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
switch (sendMsg.type) {
|
switch (sendMsg.type) {
|
||||||
case OB11MessageDataType.text:
|
case OB11MessageDataType.text: {
|
||||||
{
|
const text = sendMsg.data?.text
|
||||||
const text = sendMsg.data?.text
|
if (text) {
|
||||||
if (text) {
|
sendElements.push(SendMsgElementConstructor.text(sendMsg.data!.text))
|
||||||
sendElements.push(SendMsgElementConstructor.text(sendMsg.data!.text))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
break
|
break
|
||||||
case OB11MessageDataType.at:
|
case OB11MessageDataType.at: {
|
||||||
{
|
if (!target) {
|
||||||
if (!target) {
|
continue
|
||||||
continue
|
}
|
||||||
|
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, '全体成员'))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
let atQQ = sendMsg.data?.qq
|
else {
|
||||||
if (atQQ) {
|
// const atMember = group?.members.find(m => m.uin == atQQ)
|
||||||
atQQ = atQQ.toString()
|
const atMember = await getGroupMember((target as Group)?.groupCode, atQQ)
|
||||||
if (atQQ === 'all') {
|
if (atMember) {
|
||||||
// todo:查询剩余的at全体次数
|
sendElements.push(
|
||||||
const groupCode = (target as Group)?.groupCode
|
SendMsgElementConstructor.at(atQQ, atMember.uid, AtType.atUser, atMember.cardName || atMember.nick),
|
||||||
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 {
|
|
||||||
// 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),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
break
|
break
|
||||||
case OB11MessageDataType.reply:
|
case OB11MessageDataType.reply: {
|
||||||
{
|
let replyMsgId = sendMsg.data.id
|
||||||
let replyMsgId = sendMsg.data.id
|
if (replyMsgId) {
|
||||||
if (replyMsgId) {
|
const replyMsg = await dbUtil.getMsgByShortId(parseInt(replyMsgId))
|
||||||
const replyMsg = await dbUtil.getMsgByShortId(parseInt(replyMsgId))
|
if (replyMsg) {
|
||||||
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(
|
sendElements.push(
|
||||||
SendMsgElementConstructor.reply(
|
await SendMsgElementConstructor.pic(
|
||||||
replyMsg.msgSeq,
|
path,
|
||||||
replyMsg.msgId,
|
sendMsg.data.summary || '',
|
||||||
replyMsg.senderUin,
|
<PicSubType>parseInt(sendMsg.data?.subType?.toString()!) || 0,
|
||||||
replyMsg.senderUin,
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
break
|
break
|
||||||
case OB11MessageDataType.face:
|
case OB11MessageDataType.json: {
|
||||||
{
|
sendElements.push(SendMsgElementConstructor.ark(sendMsg.data.data))
|
||||||
const faceId = sendMsg.data?.id
|
}
|
||||||
if (faceId) {
|
break
|
||||||
sendElements.push(SendMsgElementConstructor.face(parseInt(faceId)))
|
case OB11MessageDataType.poke: {
|
||||||
|
let qq = sendMsg.data?.qq || sendMsg.data?.id
|
||||||
|
if (qq) {
|
||||||
|
if ('groupCode' in target!) {
|
||||||
|
crychic.sendGroupPoke(target.groupCode, qq.toString())
|
||||||
}
|
}
|
||||||
}
|
else {
|
||||||
break
|
if (!qq) {
|
||||||
case OB11MessageDataType.mface: {
|
qq = parseInt(target?.uin!)
|
||||||
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(
|
|
||||||
await SendMsgElementConstructor.pic(
|
|
||||||
path,
|
|
||||||
sendMsg.data.summary || '',
|
|
||||||
<PicSubType>parseInt(sendMsg.data?.subType?.toString()) || 0,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
crychic.sendFriendPoke(qq.toString())
|
||||||
}
|
}
|
||||||
|
sendElements.push(SendMsgElementConstructor.poke('', '')!)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
break
|
break
|
||||||
case OB11MessageDataType.json:
|
case OB11MessageDataType.dice: {
|
||||||
{
|
const resultId = sendMsg.data?.result
|
||||||
sendElements.push(SendMsgElementConstructor.ark(sendMsg.data.data))
|
sendElements.push(SendMsgElementConstructor.dice(resultId))
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
case OB11MessageDataType.poke:
|
case OB11MessageDataType.RPS: {
|
||||||
{
|
const resultId = sendMsg.data?.result
|
||||||
let qq = sendMsg.data?.qq || sendMsg.data?.id
|
sendElements.push(SendMsgElementConstructor.rps(resultId))
|
||||||
if (qq) {
|
}
|
||||||
if ('groupCode' in target) {
|
|
||||||
crychic.sendGroupPoke(target.groupCode, qq.toString())
|
|
||||||
} else {
|
|
||||||
if (!qq) {
|
|
||||||
qq = parseInt(target.uin)
|
|
||||||
}
|
|
||||||
crychic.sendFriendPoke(qq.toString())
|
|
||||||
}
|
|
||||||
sendElements.push(SendMsgElementConstructor.poke('', ''))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case OB11MessageDataType.dice:
|
|
||||||
{
|
|
||||||
const resultId = sendMsg.data?.result
|
|
||||||
sendElements.push(SendMsgElementConstructor.dice(resultId))
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case OB11MessageDataType.RPS:
|
|
||||||
{
|
|
||||||
const resultId = sendMsg.data?.result
|
|
||||||
sendElements.push(SendMsgElementConstructor.rps(resultId))
|
|
||||||
}
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -291,10 +307,34 @@ export async function sendMsg(
|
|||||||
if (!sendElements.length) {
|
if (!sendElements.length) {
|
||||||
throw '消息体无法解析,请检查是否发送了不支持的消息类型'
|
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)
|
log('消息发送结果', returnMsg)
|
||||||
returnMsg.msgShortId = await dbUtil.addMsg(returnMsg)
|
returnMsg.msgShortId = await dbUtil.addMsg(returnMsg)
|
||||||
deleteAfterSentFiles.map((f) => fs.unlink(f, () => {}))
|
deleteAfterSentFiles.map((f) => fs.unlink(f, () => {
|
||||||
|
}))
|
||||||
return returnMsg
|
return returnMsg
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -303,14 +343,14 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
|
|||||||
|
|
||||||
protected async check(payload: OB11PostSendMsg): Promise<BaseCheckResult> {
|
protected async check(payload: OB11PostSendMsg): Promise<BaseCheckResult> {
|
||||||
const messages = convertMessage2List(payload.message)
|
const messages = convertMessage2List(payload.message)
|
||||||
const fmNum = this.getSpecialMsgNum(payload, OB11MessageDataType.node)
|
const fmNum = this.getSpecialMsgNum(messages, OB11MessageDataType.node)
|
||||||
if (fmNum && fmNum != messages.length) {
|
if (fmNum && fmNum != messages.length) {
|
||||||
return {
|
return {
|
||||||
valid: false,
|
valid: false,
|
||||||
message: '转发消息不能和普通消息混在一起发送,转发需要保证message只有type为node的元素',
|
message: '转发消息不能和普通消息混在一起发送,转发需要保证message只有type为node的元素',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const musicNum = this.getSpecialMsgNum(payload, OB11MessageDataType.music)
|
const musicNum = this.getSpecialMsgNum(messages, OB11MessageDataType.music)
|
||||||
if (musicNum && messages.length > 1) {
|
if (musicNum && messages.length > 1) {
|
||||||
return {
|
return {
|
||||||
valid: false,
|
valid: false,
|
||||||
@@ -347,10 +387,10 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
|
|||||||
let group: Group | undefined = undefined
|
let group: Group | undefined = undefined
|
||||||
let friend: Friend | undefined = undefined
|
let friend: Friend | undefined = undefined
|
||||||
const genGroupPeer = async () => {
|
const genGroupPeer = async () => {
|
||||||
group = await getGroup(payload.group_id.toString())
|
group = await getGroup(payload.group_id?.toString()!)
|
||||||
peer.chatType = ChatType.group
|
peer.chatType = ChatType.group
|
||||||
// peer.name = group.name
|
// peer.name = group.name
|
||||||
peer.peerUid = group.groupCode
|
peer.peerUid = group?.groupCode!
|
||||||
}
|
}
|
||||||
|
|
||||||
const genFriendPeer = () => {
|
const genFriendPeer = () => {
|
||||||
@@ -358,7 +398,8 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
|
|||||||
if (friend) {
|
if (friend) {
|
||||||
// peer.name = friend.nickName
|
// peer.name = friend.nickName
|
||||||
peer.peerUid = friend.uid
|
peer.peerUid = friend.uid
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
peer.chatType = ChatType.temp
|
peer.chatType = ChatType.temp
|
||||||
const tempUserUid = getUidByUin(payload.user_id.toString())
|
const tempUserUid = getUidByUin(payload.user_id.toString())
|
||||||
if (!tempUserUid) {
|
if (!tempUserUid) {
|
||||||
@@ -371,25 +412,29 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
|
|||||||
}
|
}
|
||||||
if (payload?.group_id && payload.message_type === 'group') {
|
if (payload?.group_id && payload.message_type === 'group') {
|
||||||
await genGroupPeer()
|
await genGroupPeer()
|
||||||
} else if (payload?.user_id) {
|
}
|
||||||
|
else if (payload?.user_id) {
|
||||||
genFriendPeer()
|
genFriendPeer()
|
||||||
} else if (payload.group_id) {
|
}
|
||||||
|
else if (payload.group_id) {
|
||||||
await genGroupPeer()
|
await genGroupPeer()
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
throw '发送消息参数错误, 请指定group_id或user_id'
|
throw '发送消息参数错误, 请指定group_id或user_id'
|
||||||
}
|
}
|
||||||
const messages = convertMessage2List(
|
const messages = convertMessage2List(
|
||||||
payload.message,
|
payload.message,
|
||||||
payload.auto_escape === true || payload.auto_escape === 'true',
|
payload.auto_escape === true || payload.auto_escape === 'true',
|
||||||
)
|
)
|
||||||
if (this.getSpecialMsgNum(payload, OB11MessageDataType.node)) {
|
if (this.getSpecialMsgNum(messages, OB11MessageDataType.node)) {
|
||||||
try {
|
try {
|
||||||
const returnMsg = await this.handleForwardNode(peer, messages as OB11MessageNode[], group)
|
const returnMsg = await this.handleForwardNode(peer, messages as OB11MessageNode[], group)
|
||||||
return { message_id: returnMsg.msgShortId }
|
return { message_id: returnMsg?.msgShortId! }
|
||||||
} catch (e) {
|
} catch (e: any) {
|
||||||
throw '发送转发消息失败 ' + e.toString()
|
throw '发送转发消息失败 ' + e.toString()
|
||||||
}
|
}
|
||||||
} else if (this.getSpecialMsgNum(payload, OB11MessageDataType.music)) {
|
}
|
||||||
|
else if (this.getSpecialMsgNum(messages, OB11MessageDataType.music)) {
|
||||||
const music = messages[0] as OB11MessageMusic
|
const music = messages[0] as OB11MessageMusic
|
||||||
if (music) {
|
if (music) {
|
||||||
const { musicSignUrl } = getConfigUtil().getConfig()
|
const { musicSignUrl } = getConfigUtil().getConfig()
|
||||||
@@ -402,24 +447,24 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
|
|||||||
}
|
}
|
||||||
const postData: MusicSignPostData = { ...music.data }
|
const postData: MusicSignPostData = { ...music.data }
|
||||||
if (type === 'custom' && music.data.content) {
|
if (type === 'custom' && music.data.content) {
|
||||||
|
const data = postData as CustomMusicSignPostData
|
||||||
;(postData as CustomMusicSignPostData).singer = music.data.content
|
data.singer = music.data.content
|
||||||
delete (postData as OB11MessageCustomMusic['data']).content
|
delete (data as OB11MessageCustomMusic['data']).content
|
||||||
}
|
}
|
||||||
if (type === 'custom'){
|
if (type === 'custom') {
|
||||||
const customMusicData = music.data as CustomMusicSignPostData
|
const customMusicData = music.data as CustomMusicSignPostData
|
||||||
if (!customMusicData.url){
|
if (!customMusicData.url) {
|
||||||
throw ('自定义音卡缺少参数url');
|
throw '自定义音卡缺少参数url'
|
||||||
}
|
}
|
||||||
if (!customMusicData.audio){
|
if (!customMusicData.audio) {
|
||||||
throw('自定义音卡缺少参数audio');
|
throw '自定义音卡缺少参数audio'
|
||||||
}
|
}
|
||||||
if (!customMusicData.title){
|
if (!customMusicData.title) {
|
||||||
throw('自定义音卡缺少参数title');
|
throw '自定义音卡缺少参数title'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (type === 'qq' || type === '163') {
|
if (type === 'qq' || type === '163') {
|
||||||
const idMusicData = music.data as IdMusicSignPostData;
|
const idMusicData = music.data as IdMusicSignPostData
|
||||||
if (!idMusicData.id) {
|
if (!idMusicData.id) {
|
||||||
throw '音乐卡片缺少id参数'
|
throw '音乐卡片缺少id参数'
|
||||||
}
|
}
|
||||||
@@ -427,6 +472,9 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
|
|||||||
let jsonContent: string
|
let jsonContent: string
|
||||||
try {
|
try {
|
||||||
jsonContent = await new MusicSign(musicSignUrl).sign(postData)
|
jsonContent = await new MusicSign(musicSignUrl).sign(postData)
|
||||||
|
if (!jsonContent) {
|
||||||
|
throw '音乐消息生成失败,提交内容有误或者签名服务器签名失败'
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw `签名音乐消息失败:${e}`
|
throw `签名音乐消息失败:${e}`
|
||||||
}
|
}
|
||||||
@@ -444,18 +492,19 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
const returnMsg = await sendMsg(peer, sendElements, deleteAfterSentFiles)
|
const returnMsg = await sendMsg(peer, sendElements, deleteAfterSentFiles)
|
||||||
deleteAfterSentFiles.map((f) => fs.unlink(f, () => {}))
|
deleteAfterSentFiles.map((f) => fs.unlink(f, () => {
|
||||||
return { message_id: returnMsg.msgShortId }
|
}))
|
||||||
|
return { message_id: returnMsg.msgShortId! }
|
||||||
}
|
}
|
||||||
|
|
||||||
private getSpecialMsgNum(payload: OB11PostSendMsg, msgType: OB11MessageDataType): number {
|
private getSpecialMsgNum(message: OB11MessageData[], msgType: OB11MessageDataType): number {
|
||||||
if (Array.isArray(payload.message)) {
|
if (Array.isArray(message)) {
|
||||||
return payload.message.filter((msg) => msg.type == msgType).length
|
return message.filter((msg) => msg.type == msgType).length
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
private async cloneMsg(msg: RawMessage): Promise<RawMessage> {
|
private async cloneMsg(msg: RawMessage): Promise<RawMessage | undefined> {
|
||||||
log('克隆的目标消息', msg)
|
log('克隆的目标消息', msg)
|
||||||
let sendElements: SendMessageElement[] = []
|
let sendElements: SendMessageElement[] = []
|
||||||
for (const ele of msg.elements) {
|
for (const ele of msg.elements) {
|
||||||
@@ -501,16 +550,18 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
|
|||||||
if (nodeId) {
|
if (nodeId) {
|
||||||
let nodeMsg = await dbUtil.getMsgByShortId(parseInt(nodeId))
|
let nodeMsg = await dbUtil.getMsgByShortId(parseInt(nodeId))
|
||||||
if (!needClone) {
|
if (!needClone) {
|
||||||
nodeMsgIds.push(nodeMsg.msgId)
|
nodeMsgIds.push(nodeMsg?.msgId!)
|
||||||
} else {
|
}
|
||||||
if (nodeMsg.peerUid !== selfInfo.uid) {
|
else {
|
||||||
const cloneMsg = await this.cloneMsg(nodeMsg)
|
if (nodeMsg?.peerUid !== selfInfo.uid) {
|
||||||
|
const cloneMsg = await this.cloneMsg(nodeMsg!)
|
||||||
if (cloneMsg) {
|
if (cloneMsg) {
|
||||||
nodeMsgIds.push(cloneMsg.msgId)
|
nodeMsgIds.push(cloneMsg.msgId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
// 自定义的消息
|
// 自定义的消息
|
||||||
// 提取消息段,发给自己生成消息id
|
// 提取消息段,发给自己生成消息id
|
||||||
try {
|
try {
|
||||||
@@ -532,7 +583,8 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
|
|||||||
}
|
}
|
||||||
sendElementsSplit[splitIndex] = [ele]
|
sendElementsSplit[splitIndex] = [ele]
|
||||||
splitIndex++
|
splitIndex++
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
sendElementsSplit[splitIndex].push(ele)
|
sendElementsSplit[splitIndex].push(ele)
|
||||||
}
|
}
|
||||||
log(sendElementsSplit)
|
log(sendElementsSplit)
|
||||||
@@ -544,7 +596,8 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
|
|||||||
await sleep(500)
|
await sleep(500)
|
||||||
log('转发节点生成成功', nodeMsg.msgId)
|
log('转发节点生成成功', nodeMsg.msgId)
|
||||||
}
|
}
|
||||||
deleteAfterSentFiles.map((f) => fs.unlink(f, () => {}))
|
deleteAfterSentFiles.map((f) => fs.unlink(f, () => {
|
||||||
|
}))
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('生成转发消息节点失败', e)
|
log('生成转发消息节点失败', e)
|
||||||
}
|
}
|
||||||
@@ -553,7 +606,7 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
|
|||||||
|
|
||||||
// 检查srcPeer是否一致,不一致则需要克隆成自己的消息, 让所有srcPeer都变成自己的,使其保持一致才能够转发
|
// 检查srcPeer是否一致,不一致则需要克隆成自己的消息, 让所有srcPeer都变成自己的,使其保持一致才能够转发
|
||||||
let nodeMsgArray: Array<RawMessage> = []
|
let nodeMsgArray: Array<RawMessage> = []
|
||||||
let srcPeer: Peer = null
|
let srcPeer: Peer | null = null
|
||||||
let needSendSelf = false
|
let needSendSelf = false
|
||||||
for (const [index, msgId] of nodeMsgIds.entries()) {
|
for (const [index, msgId] of nodeMsgIds.entries()) {
|
||||||
const nodeMsg = await dbUtil.getMsgByLongId(msgId)
|
const nodeMsg = await dbUtil.getMsgByLongId(msgId)
|
||||||
@@ -561,7 +614,8 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
|
|||||||
nodeMsgArray.push(nodeMsg)
|
nodeMsgArray.push(nodeMsg)
|
||||||
if (!srcPeer) {
|
if (!srcPeer) {
|
||||||
srcPeer = { chatType: nodeMsg.chatType, peerUid: nodeMsg.peerUid }
|
srcPeer = { chatType: nodeMsg.chatType, peerUid: nodeMsg.peerUid }
|
||||||
} else if (srcPeer.peerUid !== nodeMsg.peerUid) {
|
}
|
||||||
|
else if (srcPeer.peerUid !== nodeMsg.peerUid) {
|
||||||
needSendSelf = true
|
needSendSelf = true
|
||||||
srcPeer = selfPeer
|
srcPeer = selfPeer
|
||||||
}
|
}
|
||||||
@@ -595,7 +649,7 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
log('开发转发', nodeMsgIds)
|
log('开发转发', nodeMsgIds)
|
||||||
return await NTQQMsgApi.multiForwardMsg(srcPeer, destPeer, nodeMsgIds)
|
return await NTQQMsgApi.multiForwardMsg(srcPeer!, destPeer, nodeMsgIds)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('forward failed', e)
|
log('forward failed', e)
|
||||||
return null
|
return null
|
||||||
|
149
src/onebot11/action/quick-operation.ts
Normal file
149
src/onebot11/action/quick-operation.ts
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
// 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 } from '@/ntqqapi/api'
|
||||||
|
import { ChatType, Group, GroupRequestOperateTypes, Peer } from '@/ntqqapi/types'
|
||||||
|
import { getGroup, getUidByUin } from '@/common/data'
|
||||||
|
import { convertMessage2List, createSendElements, sendMsg } from './msg/SendMsg'
|
||||||
|
import { isNull, log } from '@/common/utils'
|
||||||
|
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) {
|
||||||
|
msg = msg as OB11Message
|
||||||
|
const rawMessage = await dbUtil.getMsgByShortId(msg.message_id)
|
||||||
|
const reply = quickAction.reply
|
||||||
|
const ob11Config = getConfigUtil().getConfig().ob11
|
||||||
|
let peer: Peer = {
|
||||||
|
chatType: ChatType.friend,
|
||||||
|
peerUid: msg.user_id.toString(),
|
||||||
|
}
|
||||||
|
if (msg.message_type == 'private') {
|
||||||
|
peer.peerUid = getUidByUin(msg.user_id.toString())!
|
||||||
|
if (msg.sub_type === 'group') {
|
||||||
|
peer.chatType = ChatType.temp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
peer.chatType = ChatType.group
|
||||||
|
peer.peerUid = msg.group_id?.toString()!
|
||||||
|
}
|
||||||
|
if (reply) {
|
||||||
|
let group: Group | null = null
|
||||||
|
let replyMessage: OB11MessageData[] = []
|
||||||
|
if (ob11Config.enableQOAutoQuote) {
|
||||||
|
replyMessage.push({
|
||||||
|
type: OB11MessageDataType.reply,
|
||||||
|
data: {
|
||||||
|
id: msg.message_id.toString(),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (msg.message_type == 'group') {
|
||||||
|
group = (await getGroup(msg.group_id?.toString()!))!
|
||||||
|
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, group!)
|
||||||
|
log(`发送消息给`, peer, sendElements)
|
||||||
|
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 BaseAction from '../BaseAction'
|
||||||
import { ActionName } from '../types'
|
import { ActionName } from '../types'
|
||||||
import fs from 'fs'
|
import fs from 'node:fs'
|
||||||
import Path from 'path'
|
import Path from 'node:path'
|
||||||
import { ChatType, ChatCacheListItemBasic, CacheFileType } from '../../../ntqqapi/types'
|
import { ChatType, ChatCacheListItemBasic, CacheFileType } from '../../../ntqqapi/types'
|
||||||
import { dbUtil } from '../../../common/db'
|
|
||||||
import { NTQQFileApi, NTQQFileCacheApi } from '../../../ntqqapi/api/file'
|
import { NTQQFileApi, NTQQFileCacheApi } from '../../../ntqqapi/api/file'
|
||||||
|
|
||||||
export default class CleanCache extends BaseAction<void, void> {
|
export default class CleanCache extends BaseAction<void, void> {
|
||||||
@@ -12,14 +11,16 @@ export default class CleanCache extends BaseAction<void, void> {
|
|||||||
protected _handle(): Promise<void> {
|
protected _handle(): Promise<void> {
|
||||||
return new Promise<void>(async (res, rej) => {
|
return new Promise<void>(async (res, rej) => {
|
||||||
try {
|
try {
|
||||||
// dbUtil.clearCache();
|
// dbUtil.clearCache()
|
||||||
const cacheFilePaths: string[] = []
|
const cacheFilePaths: string[] = []
|
||||||
|
|
||||||
await NTQQFileCacheApi.setCacheSilentScan(false)
|
await NTQQFileCacheApi.setCacheSilentScan(false)
|
||||||
|
|
||||||
cacheFilePaths.push(await NTQQFileCacheApi.getHotUpdateCachePath())
|
cacheFilePaths.push(await NTQQFileCacheApi.getHotUpdateCachePath())
|
||||||
cacheFilePaths.push(await NTQQFileCacheApi.getDesktopTmpPath())
|
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: 调用就崩溃,原因目前还未知
|
// await NTQQApi.addCacheScannedPaths(); // XXX: 调用就崩溃,原因目前还未知
|
||||||
const cacheScanResult = await NTQQFileCacheApi.scanCache()
|
const cacheScanResult = await NTQQFileCacheApi.scanCache()
|
||||||
|
@@ -8,7 +8,7 @@ export default class GetStatus extends BaseAction<any, OB11Status> {
|
|||||||
|
|
||||||
protected async _handle(payload: any): Promise<OB11Status> {
|
protected async _handle(payload: any): Promise<OB11Status> {
|
||||||
return {
|
return {
|
||||||
online: selfInfo.online,
|
online: selfInfo.online!,
|
||||||
good: true,
|
good: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -21,6 +21,8 @@ export enum ActionName {
|
|||||||
SetConfig = 'set_config',
|
SetConfig = 'set_config',
|
||||||
Debug = 'llonebot_debug',
|
Debug = 'llonebot_debug',
|
||||||
GetFile = 'get_file',
|
GetFile = 'get_file',
|
||||||
|
GetFriendsWithCategory = 'get_friends_with_category',
|
||||||
|
GetEvent = 'get_event',
|
||||||
// onebot 11
|
// onebot 11
|
||||||
SendLike = 'send_like',
|
SendLike = 'send_like',
|
||||||
GetLoginInfo = 'get_login_info',
|
GetLoginInfo = 'get_login_info',
|
||||||
@@ -66,4 +68,9 @@ export enum ActionName {
|
|||||||
GoCQHTTP_DownloadFile = 'download_file',
|
GoCQHTTP_DownloadFile = 'download_file',
|
||||||
GoCQHTTP_GetGroupMsgHistory = 'get_group_msg_history',
|
GoCQHTTP_GetGroupMsgHistory = 'get_group_msg_history',
|
||||||
GoCQHTTP_GetForwardMsg = 'get_forward_msg',
|
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',
|
||||||
}
|
}
|
||||||
|
@@ -1,12 +1,16 @@
|
|||||||
import BaseAction from '../BaseAction'
|
import BaseAction from '../BaseAction'
|
||||||
import { NTQQUserApi } from '../../../ntqqapi/api'
|
import { NTQQUserApi } from '@/ntqqapi/api'
|
||||||
import { groups } from '../../../common/data'
|
import { ActionName } from '../types'
|
||||||
import { ActionName } from '../types'
|
|
||||||
|
interface Payload {
|
||||||
export class GetCookies extends BaseAction<null, { cookies: string; bkn: string }> {
|
domain: string
|
||||||
actionName = ActionName.GetCookies
|
}
|
||||||
|
|
||||||
protected async _handle() {
|
export class GetCookies extends BaseAction<Payload, { cookies: string; bkn: string }> {
|
||||||
return NTQQUserApi.getCookie(groups[0])
|
actionName = ActionName.GetCookies
|
||||||
}
|
|
||||||
}
|
protected async _handle(payload: Payload) {
|
||||||
|
const domain = payload.domain || 'qun.qq.com'
|
||||||
|
return NTQQUserApi.getCookies(domain);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -1,19 +1,23 @@
|
|||||||
import { OB11User } from '../../types'
|
import { OB11User } from '../../types'
|
||||||
import { OB11Constructor } from '../../constructor'
|
import { OB11Constructor } from '../../constructor'
|
||||||
import { friends } from '../../../common/data'
|
import { friends, rawFriends } from '@/common/data'
|
||||||
import BaseAction from '../BaseAction'
|
import BaseAction from '../BaseAction'
|
||||||
import { ActionName } from '../types'
|
import { ActionName } from '../types'
|
||||||
import { NTQQFriendApi } from '../../../ntqqapi/api'
|
import { NTQQFriendApi } from '@/ntqqapi/api'
|
||||||
import { log } from '../../../common/utils'
|
import { CategoryFriend } from '@/ntqqapi/types'
|
||||||
|
import { qqPkgInfo } from '@/common/utils/QQBasicInfo'
|
||||||
|
|
||||||
interface Payload{
|
interface Payload {
|
||||||
no_cache: boolean | string
|
no_cache: boolean | string
|
||||||
}
|
}
|
||||||
|
|
||||||
class GetFriendList extends BaseAction<Payload, OB11User[]> {
|
export class GetFriendList extends BaseAction<Payload, OB11User[]> {
|
||||||
actionName = ActionName.GetFriendList
|
actionName = ActionName.GetFriendList
|
||||||
|
|
||||||
protected async _handle(payload: Payload) {
|
protected async _handle(payload: Payload) {
|
||||||
|
if (+qqPkgInfo.buildVersion >= 26702) {
|
||||||
|
return OB11Constructor.friendsV2(await NTQQFriendApi.getBuddyV2(payload?.no_cache === true || payload?.no_cache === 'true'))
|
||||||
|
}
|
||||||
if (friends.length === 0 || payload?.no_cache === true || payload?.no_cache === 'true') {
|
if (friends.length === 0 || payload?.no_cache === true || payload?.no_cache === 'true') {
|
||||||
const _friends = await NTQQFriendApi.getFriends(true)
|
const _friends = await NTQQFriendApi.getFriends(true)
|
||||||
// log('强制刷新好友列表,结果: ', _friends)
|
// log('强制刷新好友列表,结果: ', _friends)
|
||||||
@@ -26,4 +30,11 @@ class GetFriendList extends BaseAction<Payload, OB11User[]> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default GetFriendList
|
|
||||||
|
export class GetFriendWithCategory extends BaseAction<void, Array<CategoryFriend>> {
|
||||||
|
actionName = ActionName.GetFriendsWithCategory;
|
||||||
|
|
||||||
|
protected async _handle(payload: void) {
|
||||||
|
return rawFriends;
|
||||||
|
}
|
||||||
|
}
|
@@ -19,7 +19,7 @@ export default class SendLike extends BaseAction<Payload, null> {
|
|||||||
const friend = await getFriend(qq)
|
const friend = await getFriend(qq)
|
||||||
let uid: string
|
let uid: string
|
||||||
if (!friend) {
|
if (!friend) {
|
||||||
uid = getUidByUin(qq)
|
uid = getUidByUin(qq)!
|
||||||
} else {
|
} else {
|
||||||
uid = friend.uid
|
uid = friend.uid
|
||||||
}
|
}
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import fastXmlParser, { XMLParser } from 'fast-xml-parser'
|
import fastXmlParser from 'fast-xml-parser'
|
||||||
import {
|
import {
|
||||||
OB11Group,
|
OB11Group,
|
||||||
OB11GroupMember,
|
OB11GroupMember,
|
||||||
@@ -15,17 +15,18 @@ import {
|
|||||||
FaceIndex,
|
FaceIndex,
|
||||||
GrayTipElementSubType,
|
GrayTipElementSubType,
|
||||||
Group,
|
Group,
|
||||||
|
Peer,
|
||||||
GroupMember,
|
GroupMember,
|
||||||
IMAGE_HTTP_HOST,
|
PicType,
|
||||||
IMAGE_HTTP_HOST_NT,
|
|
||||||
RawMessage,
|
RawMessage,
|
||||||
SelfInfo,
|
SelfInfo,
|
||||||
Sex,
|
Sex,
|
||||||
TipGroupElementType,
|
TipGroupElementType,
|
||||||
User,
|
User,
|
||||||
VideoElement,
|
VideoElement,
|
||||||
|
FriendV2
|
||||||
} from '../ntqqapi/types'
|
} from '../ntqqapi/types'
|
||||||
import { deleteGroup, getFriend, getGroupMember, groups, selfInfo, tempGroupCodeMap } from '../common/data'
|
import { deleteGroup, getFriend, getGroupMember, selfInfo, tempGroupCodeMap, uidMaps } from '../common/data'
|
||||||
import { EventType } from './event/OB11BaseEvent'
|
import { EventType } from './event/OB11BaseEvent'
|
||||||
import { encodeCQCode } from './cqcode'
|
import { encodeCQCode } from './cqcode'
|
||||||
import { dbUtil } from '../common/db'
|
import { dbUtil } from '../common/db'
|
||||||
@@ -35,9 +36,10 @@ import { OB11GroupUploadNoticeEvent } from './event/notice/OB11GroupUploadNotice
|
|||||||
import { OB11GroupNoticeEvent } from './event/notice/OB11GroupNoticeEvent'
|
import { OB11GroupNoticeEvent } from './event/notice/OB11GroupNoticeEvent'
|
||||||
import { NTQQUserApi } from '../ntqqapi/api/user'
|
import { NTQQUserApi } from '../ntqqapi/api/user'
|
||||||
import { NTQQFileApi } from '../ntqqapi/api/file'
|
import { NTQQFileApi } from '../ntqqapi/api/file'
|
||||||
|
import { NTQQMsgApi } from '../ntqqapi/api/msg'
|
||||||
import { calcQQLevel } from '../common/utils/qqlevel'
|
import { calcQQLevel } from '../common/utils/qqlevel'
|
||||||
import { log } from '../common/utils/log'
|
import { log } from '../common/utils/log'
|
||||||
import { sleep } from '../common/utils/helper'
|
import { isNull, sleep } from '../common/utils/helper'
|
||||||
import { getConfigUtil } from '../common/config'
|
import { getConfigUtil } from '../common/config'
|
||||||
import { OB11GroupTitleEvent } from './event/notice/OB11GroupTitleEvent'
|
import { OB11GroupTitleEvent } from './event/notice/OB11GroupTitleEvent'
|
||||||
import { OB11GroupCardEvent } from './event/notice/OB11GroupCardEvent'
|
import { OB11GroupCardEvent } from './event/notice/OB11GroupCardEvent'
|
||||||
@@ -46,6 +48,11 @@ import { NTQQGroupApi } from '../ntqqapi/api'
|
|||||||
import { OB11GroupMsgEmojiLikeEvent } from './event/notice/OB11MsgEmojiLikeEvent'
|
import { OB11GroupMsgEmojiLikeEvent } from './event/notice/OB11MsgEmojiLikeEvent'
|
||||||
import { mFaceCache } from '../ntqqapi/constructor'
|
import { mFaceCache } from '../ntqqapi/constructor'
|
||||||
import { OB11FriendAddNoticeEvent } from './event/notice/OB11FriendAddNoticeEvent'
|
import { OB11FriendAddNoticeEvent } from './event/notice/OB11FriendAddNoticeEvent'
|
||||||
|
import { OB11FriendRecallNoticeEvent } from './event/notice/OB11FriendRecallNoticeEvent'
|
||||||
|
import { OB11GroupRecallNoticeEvent } from './event/notice/OB11GroupRecallNoticeEvent'
|
||||||
|
import { OB11FriendPokeEvent, OB11GroupPokeEvent } from './event/notice/OB11PokeEvent'
|
||||||
|
import { OB11BaseNoticeEvent } from './event/notice/OB11BaseNoticeEvent'
|
||||||
|
import { OB11GroupEssenceEvent } from './event/notice/OB11GroupEssenceEvent'
|
||||||
|
|
||||||
let lastRKeyUpdateTime = 0
|
let lastRKeyUpdateTime = 0
|
||||||
|
|
||||||
@@ -54,18 +61,20 @@ export class OB11Constructor {
|
|||||||
let config = getConfigUtil().getConfig()
|
let config = getConfigUtil().getConfig()
|
||||||
const {
|
const {
|
||||||
enableLocalFile2Url,
|
enableLocalFile2Url,
|
||||||
|
debug,
|
||||||
ob11: { messagePostFormat },
|
ob11: { messagePostFormat },
|
||||||
} = config
|
} = config
|
||||||
const message_type = msg.chatType == ChatType.group ? 'group' : 'private'
|
const message_type = msg.chatType == ChatType.group ? 'group' : 'private'
|
||||||
const resMsg: OB11Message = {
|
const resMsg: OB11Message = {
|
||||||
self_id: parseInt(selfInfo.uin),
|
self_id: parseInt(selfInfo.uin),
|
||||||
user_id: parseInt(msg.senderUin),
|
user_id: parseInt(msg.senderUin!),
|
||||||
time: parseInt(msg.msgTime) || Date.now(),
|
time: parseInt(msg.msgTime) || Date.now(),
|
||||||
message_id: msg.msgShortId,
|
message_id: msg.msgShortId!,
|
||||||
real_id: msg.msgShortId,
|
real_id: msg.msgShortId!,
|
||||||
|
message_seq: msg.msgShortId!,
|
||||||
message_type: msg.chatType == ChatType.group ? 'group' : 'private',
|
message_type: msg.chatType == ChatType.group ? 'group' : 'private',
|
||||||
sender: {
|
sender: {
|
||||||
user_id: parseInt(msg.senderUin),
|
user_id: parseInt(msg.senderUin!),
|
||||||
nickname: msg.sendNickName,
|
nickname: msg.sendNickName,
|
||||||
card: msg.sendMemberName || '',
|
card: msg.sendMemberName || '',
|
||||||
},
|
},
|
||||||
@@ -76,21 +85,26 @@ export class OB11Constructor {
|
|||||||
message_format: messagePostFormat === 'string' ? 'string' : 'array',
|
message_format: messagePostFormat === 'string' ? 'string' : 'array',
|
||||||
post_type: selfInfo.uin == msg.senderUin ? EventType.MESSAGE_SENT : EventType.MESSAGE,
|
post_type: selfInfo.uin == msg.senderUin ? EventType.MESSAGE_SENT : EventType.MESSAGE,
|
||||||
}
|
}
|
||||||
|
if (debug) {
|
||||||
|
resMsg.raw = msg
|
||||||
|
}
|
||||||
if (msg.chatType == ChatType.group) {
|
if (msg.chatType == ChatType.group) {
|
||||||
resMsg.sub_type = 'normal' // 这里go-cqhttp是group,而onebot11标准是normal, 蛋疼
|
resMsg.sub_type = 'normal'
|
||||||
resMsg.group_id = parseInt(msg.peerUin)
|
resMsg.group_id = parseInt(msg.peerUin)
|
||||||
const member = await getGroupMember(msg.peerUin, msg.senderUin)
|
const member = await getGroupMember(msg.peerUin, msg.senderUin!)
|
||||||
if (member) {
|
if (member) {
|
||||||
resMsg.sender.role = OB11Constructor.groupMemberRole(member.role)
|
resMsg.sender.role = OB11Constructor.groupMemberRole(member.role)
|
||||||
resMsg.sender.nickname = member.nick
|
resMsg.sender.nickname = member.nick
|
||||||
}
|
}
|
||||||
} else if (msg.chatType == ChatType.friend) {
|
}
|
||||||
|
else if (msg.chatType == ChatType.friend) {
|
||||||
resMsg.sub_type = 'friend'
|
resMsg.sub_type = 'friend'
|
||||||
const friend = await getFriend(msg.senderUin)
|
const friend = await getFriend(msg.senderUin!)
|
||||||
if (friend) {
|
if (friend) {
|
||||||
resMsg.sender.nickname = friend.nick
|
resMsg.sender.nickname = friend.nick
|
||||||
}
|
}
|
||||||
} else if (msg.chatType == ChatType.temp) {
|
}
|
||||||
|
else if (msg.chatType == ChatType.temp) {
|
||||||
resMsg.sub_type = 'group'
|
resMsg.sub_type = 'group'
|
||||||
const tempGroupCode = tempGroupCodeMap[msg.peerUin]
|
const tempGroupCode = tempGroupCodeMap[msg.peerUin]
|
||||||
if (tempGroupCode) {
|
if (tempGroupCode) {
|
||||||
@@ -99,64 +113,84 @@ export class OB11Constructor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (let element of msg.elements) {
|
for (let element of msg.elements) {
|
||||||
let message_data: OB11MessageData | any = {
|
let message_data: OB11MessageData = {
|
||||||
data: {},
|
data: {} as any,
|
||||||
type: 'unknown',
|
type: 'unknown' as any,
|
||||||
}
|
}
|
||||||
if (element.textElement && element.textElement?.atType !== AtType.notAt) {
|
if (element.textElement && element.textElement?.atType !== AtType.notAt) {
|
||||||
message_data['type'] = OB11MessageDataType.at
|
let qq: string
|
||||||
|
let name: string | undefined
|
||||||
if (element.textElement.atType == AtType.atAll) {
|
if (element.textElement.atType == AtType.atAll) {
|
||||||
// message_data["data"]["mention"] = "all"
|
qq = 'all'
|
||||||
message_data['data']['qq'] = 'all'
|
}
|
||||||
} else {
|
else {
|
||||||
let atUid = element.textElement.atNtUid
|
const { atNtUid, content } = element.textElement
|
||||||
let atQQ = element.textElement.atUid
|
let atQQ = element.textElement.atUid
|
||||||
if (!atQQ || atQQ === '0') {
|
if (!atQQ || atQQ === '0') {
|
||||||
const atMember = await getGroupMember(msg.peerUin, atUid)
|
const atMember = await getGroupMember(msg.peerUin, atNtUid)
|
||||||
if (atMember) {
|
if (atMember) {
|
||||||
atQQ = atMember.uin
|
atQQ = atMember.uin
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (atQQ) {
|
if (atQQ) {
|
||||||
// message_data["data"]["mention"] = atQQ
|
qq = atQQ
|
||||||
message_data['data']['qq'] = atQQ
|
name = content.replace('@', '')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (element.textElement) {
|
message_data = {
|
||||||
message_data['type'] = 'text'
|
type: OB11MessageDataType.at,
|
||||||
|
data: {
|
||||||
|
qq: qq!,
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (element.textElement) {
|
||||||
|
message_data['type'] = OB11MessageDataType.text
|
||||||
let text = element.textElement.content
|
let text = element.textElement.content
|
||||||
if (!text.trim()) {
|
if (!text.trim()) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
message_data['data']['text'] = text
|
message_data['data']['text'] = text
|
||||||
} else if (element.replyElement) {
|
}
|
||||||
message_data['type'] = 'reply'
|
else if (element.replyElement) {
|
||||||
|
message_data['type'] = OB11MessageDataType.reply
|
||||||
// log("收到回复消息", element.replyElement.replayMsgSeq)
|
// log("收到回复消息", element.replyElement.replayMsgSeq)
|
||||||
try {
|
try {
|
||||||
const replyMsg = await dbUtil.getMsgBySeqId(element.replyElement.replayMsgSeq)
|
const replyMsg = await dbUtil.getMsgBySeqId(element.replyElement.replayMsgSeq)
|
||||||
// log("找到回复消息", replyMsg.msgShortId, replyMsg.msgId)
|
// log("找到回复消息", replyMsg.msgShortId, replyMsg.msgId)
|
||||||
if (replyMsg) {
|
if (replyMsg) {
|
||||||
message_data['data']['id'] = replyMsg.msgShortId.toString()
|
message_data['data']['id'] = replyMsg.msgShortId?.toString()
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e: any) {
|
||||||
log('获取不到引用的消息', e.stack, element.replyElement.replayMsgSeq)
|
log('获取不到引用的消息', e.stack, element.replyElement.replayMsgSeq)
|
||||||
}
|
}
|
||||||
} else if (element.picElement) {
|
}
|
||||||
message_data['type'] = 'image'
|
else if (element.picElement) {
|
||||||
|
message_data['type'] = OB11MessageDataType.image
|
||||||
// message_data["data"]["file"] = element.picElement.sourcePath
|
// message_data["data"]["file"] = element.picElement.sourcePath
|
||||||
message_data['data']['file'] = element.picElement.fileName
|
let fileName = element.picElement.fileName
|
||||||
|
const sourcePath = element.picElement.sourcePath
|
||||||
|
const isGif = element.picElement.picType === PicType.gif
|
||||||
|
if (isGif && !fileName.endsWith('.gif')) {
|
||||||
|
fileName += '.gif'
|
||||||
|
}
|
||||||
|
message_data['data']['file'] = fileName
|
||||||
|
message_data['data']['subType'] = element.picElement.picSubType
|
||||||
// message_data["data"]["path"] = element.picElement.sourcePath
|
// message_data["data"]["path"] = element.picElement.sourcePath
|
||||||
// let currentRKey = "CAQSKAB6JWENi5LMk0kc62l8Pm3Jn1dsLZHyRLAnNmHGoZ3y_gDZPqZt-64"
|
// let currentRKey = "CAQSKAB6JWENi5LMk0kc62l8Pm3Jn1dsLZHyRLAnNmHGoZ3y_gDZPqZt-64"
|
||||||
|
|
||||||
message_data['data']['url'] = await NTQQFileApi.getImageUrl(msg)
|
message_data['data']['url'] = await NTQQFileApi.getImageUrl(element.picElement, msg.chatType)
|
||||||
// message_data["data"]["file_id"] = element.picElement.fileUuid
|
// message_data["data"]["file_id"] = element.picElement.fileUuid
|
||||||
message_data['data']['file_size'] = element.picElement.fileSize
|
message_data['data']['file_size'] = element.picElement.fileSize
|
||||||
dbUtil
|
dbUtil
|
||||||
.addFileCache(element.picElement.fileName, {
|
.addFileCache(fileName, {
|
||||||
fileName: element.picElement.fileName,
|
fileName,
|
||||||
filePath: element.picElement.sourcePath,
|
elementId: element.elementId,
|
||||||
|
filePath: sourcePath,
|
||||||
fileSize: element.picElement.fileSize.toString(),
|
fileSize: element.picElement.fileSize.toString(),
|
||||||
url: message_data['data']['url'],
|
url: message_data['data']['url'],
|
||||||
downloadFunc: async () => {
|
downloadFunc: async () => {
|
||||||
@@ -169,10 +203,9 @@ export class OB11Constructor {
|
|||||||
element.picElement.sourcePath,
|
element.picElement.sourcePath,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
})
|
}).then()
|
||||||
.then()
|
}
|
||||||
// 不在自动下载图片
|
else if (element.videoElement || element.fileElement) {
|
||||||
} else if (element.videoElement || element.fileElement) {
|
|
||||||
const videoOrFileElement = element.videoElement || element.fileElement
|
const videoOrFileElement = element.videoElement || element.fileElement
|
||||||
const ob11MessageDataType = element.videoElement ? OB11MessageDataType.video : OB11MessageDataType.file
|
const ob11MessageDataType = element.videoElement ? OB11MessageDataType.video : OB11MessageDataType.file
|
||||||
message_data['type'] = ob11MessageDataType
|
message_data['type'] = ob11MessageDataType
|
||||||
@@ -180,12 +213,20 @@ export class OB11Constructor {
|
|||||||
message_data['data']['path'] = videoOrFileElement.filePath
|
message_data['data']['path'] = videoOrFileElement.filePath
|
||||||
message_data['data']['file_id'] = videoOrFileElement.fileUuid
|
message_data['data']['file_id'] = videoOrFileElement.fileUuid
|
||||||
message_data['data']['file_size'] = videoOrFileElement.fileSize
|
message_data['data']['file_size'] = videoOrFileElement.fileSize
|
||||||
|
if (element.videoElement) {
|
||||||
|
message_data['data']['url'] = await NTQQFileApi.getVideoUrl({
|
||||||
|
chatType: msg.chatType,
|
||||||
|
peerUid: msg.peerUid,
|
||||||
|
}, msg.msgId, element.elementId,
|
||||||
|
)
|
||||||
|
}
|
||||||
dbUtil
|
dbUtil
|
||||||
.addFileCache(videoOrFileElement.fileUuid, {
|
.addFileCache(videoOrFileElement.fileUuid!, {
|
||||||
msgId: msg.msgId,
|
msgId: msg.msgId,
|
||||||
|
elementId: element.elementId,
|
||||||
fileName: videoOrFileElement.fileName,
|
fileName: videoOrFileElement.fileName,
|
||||||
filePath: videoOrFileElement.filePath,
|
filePath: videoOrFileElement.filePath,
|
||||||
fileSize: videoOrFileElement.fileSize,
|
fileSize: videoOrFileElement.fileSize!,
|
||||||
downloadFunc: async () => {
|
downloadFunc: async () => {
|
||||||
await NTQQFileApi.downloadMedia(
|
await NTQQFileApi.downloadMedia(
|
||||||
msg.msgId,
|
msg.msgId,
|
||||||
@@ -193,7 +234,7 @@ export class OB11Constructor {
|
|||||||
msg.peerUid,
|
msg.peerUid,
|
||||||
element.elementId,
|
element.elementId,
|
||||||
ob11MessageDataType == OB11MessageDataType.video
|
ob11MessageDataType == OB11MessageDataType.video
|
||||||
? (videoOrFileElement as VideoElement).thumbPath.get(0)
|
? (videoOrFileElement as VideoElement).thumbPath?.get(0)
|
||||||
: null,
|
: null,
|
||||||
videoOrFileElement.filePath,
|
videoOrFileElement.filePath,
|
||||||
)
|
)
|
||||||
@@ -201,7 +242,8 @@ export class OB11Constructor {
|
|||||||
})
|
})
|
||||||
.then()
|
.then()
|
||||||
// 怎么拿到url呢
|
// 怎么拿到url呢
|
||||||
} else if (element.pttElement) {
|
}
|
||||||
|
else if (element.pttElement) {
|
||||||
message_data['type'] = OB11MessageDataType.voice
|
message_data['type'] = OB11MessageDataType.voice
|
||||||
message_data['data']['file'] = element.pttElement.fileName
|
message_data['data']['file'] = element.pttElement.fileName
|
||||||
message_data['data']['path'] = element.pttElement.filePath
|
message_data['data']['path'] = element.pttElement.filePath
|
||||||
@@ -209,6 +251,7 @@ export class OB11Constructor {
|
|||||||
message_data['data']['file_size'] = element.pttElement.fileSize
|
message_data['data']['file_size'] = element.pttElement.fileSize
|
||||||
dbUtil
|
dbUtil
|
||||||
.addFileCache(element.pttElement.fileName, {
|
.addFileCache(element.pttElement.fileName, {
|
||||||
|
elementId: element.elementId,
|
||||||
fileName: element.pttElement.fileName,
|
fileName: element.pttElement.fileName,
|
||||||
filePath: element.pttElement.filePath,
|
filePath: element.pttElement.filePath,
|
||||||
fileSize: element.pttElement.fileSize,
|
fileSize: element.pttElement.fileSize,
|
||||||
@@ -217,26 +260,31 @@ export class OB11Constructor {
|
|||||||
|
|
||||||
// log("收到语音消息", msg)
|
// log("收到语音消息", msg)
|
||||||
// window.LLAPI.Ptt2Text(message.raw.msgId, message.peer, messages).then(text => {
|
// window.LLAPI.Ptt2Text(message.raw.msgId, message.peer, messages).then(text => {
|
||||||
// console.log("语音转文字结果", text);
|
// console.log("语音转文字结果", text)
|
||||||
// }).catch(err => {
|
// }).catch(err => {
|
||||||
// console.log("语音转文字失败", err);
|
// console.log("语音转文字失败", err)
|
||||||
// })
|
// })
|
||||||
} else if (element.arkElement) {
|
}
|
||||||
|
else if (element.arkElement) {
|
||||||
message_data['type'] = OB11MessageDataType.json
|
message_data['type'] = OB11MessageDataType.json
|
||||||
message_data['data']['data'] = element.arkElement.bytesData
|
message_data['data']['data'] = element.arkElement.bytesData
|
||||||
} else if (element.faceElement) {
|
}
|
||||||
|
else if (element.faceElement) {
|
||||||
const faceId = element.faceElement.faceIndex
|
const faceId = element.faceElement.faceIndex
|
||||||
if (faceId === FaceIndex.dice) {
|
if (faceId === FaceIndex.dice) {
|
||||||
message_data['type'] = OB11MessageDataType.dice
|
message_data['type'] = OB11MessageDataType.dice
|
||||||
message_data['data']['result'] = element.faceElement.resultId
|
message_data['data']['result'] = element.faceElement.resultId
|
||||||
} else if (faceId === FaceIndex.RPS) {
|
}
|
||||||
|
else if (faceId === FaceIndex.RPS) {
|
||||||
message_data['type'] = OB11MessageDataType.RPS
|
message_data['type'] = OB11MessageDataType.RPS
|
||||||
message_data['data']['result'] = element.faceElement.resultId
|
message_data['data']['result'] = element.faceElement.resultId
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
message_data['type'] = OB11MessageDataType.face
|
message_data['type'] = OB11MessageDataType.face
|
||||||
message_data['data']['id'] = element.faceElement.faceIndex.toString()
|
message_data['data']['id'] = element.faceElement.faceIndex.toString()
|
||||||
}
|
}
|
||||||
} else if (element.marketFaceElement) {
|
}
|
||||||
|
else if (element.marketFaceElement) {
|
||||||
message_data['type'] = OB11MessageDataType.mface
|
message_data['type'] = OB11MessageDataType.mface
|
||||||
message_data['data']['summary'] = element.marketFaceElement.faceName
|
message_data['data']['summary'] = element.marketFaceElement.faceName
|
||||||
const md5 = element.marketFaceElement.emojiId
|
const md5 = element.marketFaceElement.emojiId
|
||||||
@@ -249,19 +297,22 @@ export class OB11Constructor {
|
|||||||
message_data['data']['emoji_id'] = element.marketFaceElement.emojiId
|
message_data['data']['emoji_id'] = element.marketFaceElement.emojiId
|
||||||
message_data['data']['emoji_package_id'] = String(element.marketFaceElement.emojiPackageId)
|
message_data['data']['emoji_package_id'] = String(element.marketFaceElement.emojiPackageId)
|
||||||
message_data['data']['key'] = element.marketFaceElement.key
|
message_data['data']['key'] = element.marketFaceElement.key
|
||||||
mFaceCache.set(md5, element.marketFaceElement.faceName);
|
mFaceCache.set(md5, element.marketFaceElement.faceName!)
|
||||||
} else if (element.markdownElement) {
|
}
|
||||||
|
else if (element.markdownElement) {
|
||||||
message_data['type'] = OB11MessageDataType.markdown
|
message_data['type'] = OB11MessageDataType.markdown
|
||||||
message_data['data']['data'] = element.markdownElement.content
|
message_data['data']['data'] = element.markdownElement.content
|
||||||
} else if (element.multiForwardMsgElement) {
|
}
|
||||||
|
else if (element.multiForwardMsgElement) {
|
||||||
message_data['type'] = OB11MessageDataType.forward
|
message_data['type'] = OB11MessageDataType.forward
|
||||||
message_data['data']['id'] = msg.msgId
|
message_data['data']['id'] = msg.msgId
|
||||||
}
|
}
|
||||||
if (message_data.type !== 'unknown' && message_data.data) {
|
if ((message_data.type as string) !== 'unknown' && message_data.data) {
|
||||||
const cqCode = encodeCQCode(message_data)
|
const cqCode = encodeCQCode(message_data)
|
||||||
if (messagePostFormat === 'string') {
|
if (messagePostFormat === 'string') {
|
||||||
;(resMsg.message as string) += cqCode
|
(resMsg.message as string) += cqCode
|
||||||
} else (resMsg.message as OB11MessageData[]).push(message_data)
|
}
|
||||||
|
else (resMsg.message as OB11MessageData[]).push(message_data)
|
||||||
|
|
||||||
resMsg.raw_message += cqCode
|
resMsg.raw_message += cqCode
|
||||||
}
|
}
|
||||||
@@ -270,7 +321,36 @@ export class OB11Constructor {
|
|||||||
return resMsg
|
return resMsg
|
||||||
}
|
}
|
||||||
|
|
||||||
static async GroupEvent(msg: RawMessage): Promise<OB11GroupNoticeEvent> {
|
static async PrivateEvent(msg: RawMessage): Promise<OB11BaseNoticeEvent | void> {
|
||||||
|
if (msg.chatType !== ChatType.friend) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for (const element of msg.elements) {
|
||||||
|
if (element.grayTipElement) {
|
||||||
|
if (element.grayTipElement.subElementType == GrayTipElementSubType.MEMBER_NEW_TITLE) {
|
||||||
|
const json = JSON.parse(element.grayTipElement.jsonGrayTipElement.jsonStr)
|
||||||
|
if (element.grayTipElement.jsonGrayTipElement.busiId == 1061) {
|
||||||
|
//判断业务类型
|
||||||
|
//Poke事件
|
||||||
|
const pokedetail: any[] = json.items
|
||||||
|
//筛选item带有uid的元素
|
||||||
|
const poke_uid = pokedetail.filter(item => item.uid)
|
||||||
|
if (poke_uid.length == 2) {
|
||||||
|
return new OB11FriendPokeEvent(parseInt((uidMaps[poke_uid[0].uid])!), parseInt((uidMaps[poke_uid[1].uid])), pokedetail)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//下面得改 上面也是错的grayTipElement.subElementType == GrayTipElementSubType.MEMBER_NEW_TITLE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 好友增加事件
|
||||||
|
if (msg.msgType === 5 && msg.subMsgType === 12) {
|
||||||
|
const event = new OB11FriendAddNoticeEvent(parseInt(msg.peerUin))
|
||||||
|
return event
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static async GroupEvent(msg: RawMessage): Promise<OB11GroupNoticeEvent | void> {
|
||||||
if (msg.chatType !== ChatType.group) {
|
if (msg.chatType !== ChatType.group) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -280,14 +360,14 @@ export class OB11Constructor {
|
|||||||
const event = new OB11GroupCardEvent(
|
const event = new OB11GroupCardEvent(
|
||||||
parseInt(msg.peerUid),
|
parseInt(msg.peerUid),
|
||||||
parseInt(msg.senderUin),
|
parseInt(msg.senderUin),
|
||||||
msg.sendMemberName,
|
msg.sendMemberName!,
|
||||||
member.cardName,
|
member.cardName,
|
||||||
)
|
)
|
||||||
member.cardName = msg.sendMemberName
|
member.cardName = msg.sendMemberName!
|
||||||
return event
|
return event
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// log("group msg", msg);
|
// log("group msg", msg)
|
||||||
for (let element of msg.elements) {
|
for (let element of msg.elements) {
|
||||||
const grayTipElement = element.grayTipElement
|
const grayTipElement = element.grayTipElement
|
||||||
const groupElement = grayTipElement?.groupElement
|
const groupElement = grayTipElement?.groupElement
|
||||||
@@ -310,25 +390,27 @@ export class OB11Constructor {
|
|||||||
// log("构造群增加事件", event)
|
// log("构造群增加事件", event)
|
||||||
return event
|
return event
|
||||||
}
|
}
|
||||||
} else if (groupElement.type === TipGroupElementType.ban) {
|
}
|
||||||
|
else if (groupElement.type === TipGroupElementType.ban) {
|
||||||
log('收到群群员禁言提示', groupElement)
|
log('收到群群员禁言提示', groupElement)
|
||||||
const memberUid = groupElement.shutUp.member.uid
|
const memberUid = groupElement.shutUp?.member.uid
|
||||||
const adminUid = groupElement.shutUp.admin.uid
|
const adminUid = groupElement.shutUp?.admin.uid
|
||||||
let memberUin: string = ''
|
let memberUin: string = ''
|
||||||
let duration = parseInt(groupElement.shutUp.duration)
|
let duration = parseInt(groupElement.shutUp?.duration!)
|
||||||
let sub_type: 'ban' | 'lift_ban' = duration > 0 ? 'ban' : 'lift_ban'
|
let sub_type: 'ban' | 'lift_ban' = duration > 0 ? 'ban' : 'lift_ban'
|
||||||
if (memberUid) {
|
if (memberUid) {
|
||||||
memberUin =
|
memberUin =
|
||||||
(await getGroupMember(msg.peerUid, memberUid))?.uin ||
|
(await getGroupMember(msg.peerUid, memberUid))?.uin ||
|
||||||
(await NTQQUserApi.getUserDetailInfo(memberUid))?.uin
|
(await NTQQUserApi.getUserDetailInfo(memberUid))?.uin
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
memberUin = '0' // 0表示全员禁言
|
memberUin = '0' // 0表示全员禁言
|
||||||
if (duration > 0) {
|
if (duration > 0) {
|
||||||
duration = -1
|
duration = -1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const adminUin =
|
const adminUin =
|
||||||
(await getGroupMember(msg.peerUid, adminUid))?.uin || (await NTQQUserApi.getUserDetailInfo(adminUid))?.uin
|
(await getGroupMember(msg.peerUid, adminUid!))?.uin || (await NTQQUserApi.getUserDetailInfo(adminUid!))?.uin
|
||||||
if (memberUin && adminUin) {
|
if (memberUin && adminUin) {
|
||||||
return new OB11GroupBanEvent(
|
return new OB11GroupBanEvent(
|
||||||
parseInt(msg.peerUid),
|
parseInt(msg.peerUid),
|
||||||
@@ -338,7 +420,8 @@ export class OB11Constructor {
|
|||||||
sub_type,
|
sub_type,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else if (groupElement.type == TipGroupElementType.kicked) {
|
}
|
||||||
|
else if (groupElement.type == TipGroupElementType.kicked) {
|
||||||
log(`收到我被踢出或退群提示, 群${msg.peerUid}`, groupElement)
|
log(`收到我被踢出或退群提示, 群${msg.peerUid}`, groupElement)
|
||||||
deleteGroup(msg.peerUid)
|
deleteGroup(msg.peerUid)
|
||||||
NTQQGroupApi.quitGroup(msg.peerUid).then()
|
NTQQGroupApi.quitGroup(msg.peerUid).then()
|
||||||
@@ -358,9 +441,10 @@ export class OB11Constructor {
|
|||||||
return new OB11GroupDecreaseEvent(parseInt(msg.peerUid), parseInt(selfInfo.uin), 0, 'leave')
|
return new OB11GroupDecreaseEvent(parseInt(msg.peerUid), parseInt(selfInfo.uin), 0, 'leave')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (element.fileElement) {
|
}
|
||||||
return new OB11GroupUploadNoticeEvent(parseInt(msg.peerUid), parseInt(msg.senderUin), {
|
else if (element.fileElement) {
|
||||||
id: element.fileElement.fileUuid,
|
return new OB11GroupUploadNoticeEvent(parseInt(msg.peerUid), parseInt(msg.senderUin!), {
|
||||||
|
id: element.fileElement.fileUuid!,
|
||||||
name: element.fileElement.fileName,
|
name: element.fileElement.fileName,
|
||||||
size: parseInt(element.fileElement.fileSize),
|
size: parseInt(element.fileElement.fileSize),
|
||||||
busid: element.fileElement.fileBizId || 0,
|
busid: element.fileElement.fileBizId || 0,
|
||||||
@@ -392,13 +476,13 @@ export class OB11Constructor {
|
|||||||
if (!msg) {
|
if (!msg) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
return new OB11GroupMsgEmojiLikeEvent(parseInt(msg.peerUid), parseInt(senderUin), msg.msgShortId, [
|
return new OB11GroupMsgEmojiLikeEvent(parseInt(msg.peerUid), parseInt(senderUin), msg.msgShortId!, [
|
||||||
{
|
{
|
||||||
emoji_id: emojiId,
|
emoji_id: emojiId,
|
||||||
count: 1,
|
count: 1,
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
} catch (e) {
|
} catch (e: any) {
|
||||||
log('解析表情回应消息失败', e.stack)
|
log('解析表情回应消息失败', e.stack)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -411,8 +495,8 @@ export class OB11Constructor {
|
|||||||
if (xmlElement?.content) {
|
if (xmlElement?.content) {
|
||||||
const regex = /jp="(\d+)"/g
|
const regex = /jp="(\d+)"/g
|
||||||
|
|
||||||
let matches = []
|
const matches: string[] = []
|
||||||
let match = null
|
let match: RegExpExecArray | null = null
|
||||||
|
|
||||||
while ((match = regex.exec(xmlElement.content)) !== null) {
|
while ((match = regex.exec(xmlElement.content)) !== null) {
|
||||||
matches.push(match[1])
|
matches.push(match[1])
|
||||||
@@ -423,7 +507,8 @@ export class OB11Constructor {
|
|||||||
return new OB11GroupIncreaseEvent(parseInt(msg.peerUid), parseInt(invitee), parseInt(inviter), 'invite')
|
return new OB11GroupIncreaseEvent(parseInt(msg.peerUid), parseInt(invitee), parseInt(inviter), 'invite')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (grayTipElement.subElementType == GrayTipElementSubType.MEMBER_NEW_TITLE) {
|
}
|
||||||
|
else if (grayTipElement.subElementType == GrayTipElementSubType.MEMBER_NEW_TITLE) {
|
||||||
const json = JSON.parse(grayTipElement.jsonGrayTipElement.jsonStr)
|
const json = JSON.parse(grayTipElement.jsonGrayTipElement.jsonStr)
|
||||||
/*
|
/*
|
||||||
{
|
{
|
||||||
@@ -449,34 +534,85 @@ export class OB11Constructor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
* */
|
* */
|
||||||
const memberUin = json.items[1].param[0]
|
if (grayTipElement.jsonGrayTipElement.busiId == 1061) {
|
||||||
const title = json.items[3].txt
|
//判断业务类型
|
||||||
log('收到群成员新头衔消息', json)
|
//Poke事件
|
||||||
getGroupMember(msg.peerUid, memberUin).then((member) => {
|
const pokedetail: any[] = json.items
|
||||||
member.memberSpecialTitle = title
|
//筛选item带有uid的元素
|
||||||
})
|
const poke_uid = pokedetail.filter(item => item.uid)
|
||||||
return new OB11GroupTitleEvent(parseInt(msg.peerUid), parseInt(memberUin), title)
|
if (poke_uid.length == 2) {
|
||||||
|
return new OB11GroupPokeEvent(parseInt(msg.peerUid), parseInt((uidMaps[poke_uid[0].uid])!), parseInt((uidMaps[poke_uid[1].uid])), pokedetail)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (grayTipElement.jsonGrayTipElement.busiId == 2401) {
|
||||||
|
log('收到群精华消息', json)
|
||||||
|
const searchParams = new URL(json.items[0].jp).searchParams
|
||||||
|
const msgSeq = searchParams.get('msgSeq')!
|
||||||
|
const Group = searchParams.get('groupCode')
|
||||||
|
const Businessid = searchParams.get('businessid')
|
||||||
|
const Peer: Peer = {
|
||||||
|
guildId: '',
|
||||||
|
chatType: ChatType.group,
|
||||||
|
peerUid: Group!
|
||||||
|
}
|
||||||
|
let msgList = (await NTQQMsgApi.getMsgsBySeqAndCount(Peer, msgSeq.toString(), 1, true, true)).msgList
|
||||||
|
const origMsg = await dbUtil.getMsgByLongId(msgList[0].msgId)
|
||||||
|
const postMsg = await dbUtil.getMsgBySeqId(origMsg?.msgSeq!) ?? origMsg
|
||||||
|
// 如果 senderUin 为 0,可能是 历史消息 或 自身消息
|
||||||
|
if (msgList[0].senderUin === '0') {
|
||||||
|
msgList[0].senderUin = postMsg?.senderUin ?? selfInfo.uin
|
||||||
|
}
|
||||||
|
return new OB11GroupEssenceEvent(parseInt(msg.peerUid), postMsg?.msgShortId!, parseInt(msgList[0].senderUin))
|
||||||
|
// 获取MsgSeq+Peer可获取具体消息
|
||||||
|
}
|
||||||
|
if (grayTipElement.jsonGrayTipElement.busiId == 2407) {
|
||||||
|
const memberUin = json.items[1].param[0]
|
||||||
|
const title = json.items[3].txt
|
||||||
|
log('收到群成员新头衔消息', json)
|
||||||
|
getGroupMember(msg.peerUid, memberUin).then(member => {
|
||||||
|
if (!isNull(member)) {
|
||||||
|
member.memberSpecialTitle = title
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return new OB11GroupTitleEvent(parseInt(msg.peerUid), parseInt(memberUin), title)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static async FriendAddEvent(msg: RawMessage): Promise<OB11FriendAddNoticeEvent | undefined> {
|
static async RecallEvent(
|
||||||
if (msg.chatType !== ChatType.friend) {
|
msg: RawMessage,
|
||||||
return;
|
): Promise<OB11FriendRecallNoticeEvent | OB11GroupRecallNoticeEvent | undefined> {
|
||||||
|
let msgElement = msg.elements.find(
|
||||||
|
(element) => element.grayTipElement?.subElementType === GrayTipElementSubType.RECALL,
|
||||||
|
)
|
||||||
|
if (!msgElement) {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
if (msg.msgType === 5 && msg.subMsgType === 12) {
|
const isGroup = msg.chatType === ChatType.group
|
||||||
const event = new OB11FriendAddNoticeEvent(parseInt(msg.peerUin));
|
const revokeElement = msgElement.grayTipElement.revokeElement
|
||||||
return event;
|
if (isGroup) {
|
||||||
|
const operator = await getGroupMember(msg.peerUid, revokeElement.operatorUid)
|
||||||
|
const sender = await getGroupMember(msg.peerUid, revokeElement.origMsgSenderUid!)
|
||||||
|
return new OB11GroupRecallNoticeEvent(
|
||||||
|
parseInt(msg.peerUid),
|
||||||
|
parseInt(sender?.uin!),
|
||||||
|
parseInt(operator?.uin!),
|
||||||
|
msg.msgShortId!,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return new OB11FriendRecallNoticeEvent(parseInt(msg.senderUin!), msg.msgShortId!)
|
||||||
}
|
}
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static friend(friend: User): OB11User {
|
static friend(friend: User): OB11User {
|
||||||
return {
|
return {
|
||||||
user_id: parseInt(friend.uin),
|
user_id: parseInt(friend.uin),
|
||||||
nickname: friend.nick,
|
nickname: friend.nick,
|
||||||
remark: friend.remark,
|
remark: friend.remark,
|
||||||
sex: OB11Constructor.sex(friend.sex),
|
sex: OB11Constructor.sex(friend.sex!),
|
||||||
level: (friend.qqLevel && calcQQLevel(friend.qqLevel)) || 0,
|
level: (friend.qqLevel && calcQQLevel(friend.qqLevel)) || 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -492,6 +628,25 @@ export class OB11Constructor {
|
|||||||
return friends.map(OB11Constructor.friend)
|
return friends.map(OB11Constructor.friend)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static friendsV2(friends: FriendV2[]): OB11User[] {
|
||||||
|
const data: OB11User[] = []
|
||||||
|
for (const friend of friends) {
|
||||||
|
const sexValue = this.sex(friend.baseInfo.sex!)
|
||||||
|
data.push({
|
||||||
|
...friend.baseInfo,
|
||||||
|
...friend.coreInfo,
|
||||||
|
user_id: parseInt(friend.coreInfo.uin),
|
||||||
|
nickname: friend.coreInfo.nick,
|
||||||
|
remark: friend.coreInfo.nick,
|
||||||
|
sex: sexValue,
|
||||||
|
level: 0,
|
||||||
|
categroyName: friend.categroyName,
|
||||||
|
categoryId: friend.categoryId
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
static groupMemberRole(role: number): OB11GroupMemberRole | undefined {
|
static groupMemberRole(role: number): OB11GroupMemberRole | undefined {
|
||||||
return {
|
return {
|
||||||
4: OB11GroupMemberRole.owner,
|
4: OB11GroupMemberRole.owner,
|
||||||
@@ -515,7 +670,7 @@ export class OB11Constructor {
|
|||||||
user_id: parseInt(member.uin),
|
user_id: parseInt(member.uin),
|
||||||
nickname: member.nick,
|
nickname: member.nick,
|
||||||
card: member.cardName,
|
card: member.cardName,
|
||||||
sex: OB11Constructor.sex(member.sex),
|
sex: OB11Constructor.sex(member.sex!),
|
||||||
age: 0,
|
age: 0,
|
||||||
area: '',
|
area: '',
|
||||||
level: 0,
|
level: 0,
|
||||||
@@ -537,7 +692,7 @@ export class OB11Constructor {
|
|||||||
...user,
|
...user,
|
||||||
user_id: parseInt(user.uin),
|
user_id: parseInt(user.uin),
|
||||||
nickname: user.nick,
|
nickname: user.nick,
|
||||||
sex: OB11Constructor.sex(user.sex),
|
sex: OB11Constructor.sex(user.sex!),
|
||||||
age: 0,
|
age: 0,
|
||||||
qid: user.qid,
|
qid: user.qid,
|
||||||
login_days: 0,
|
login_days: 0,
|
||||||
|
@@ -60,7 +60,15 @@ export function encodeCQCode(data: OB11MessageData) {
|
|||||||
let result = '[CQ:' + data.type
|
let result = '[CQ:' + data.type
|
||||||
for (const name in data.data) {
|
for (const name in data.data) {
|
||||||
const value = data.data[name]
|
const value = data.data[name]
|
||||||
result += `,${name}=${CQCodeEscape(value)}`
|
if (value === undefined) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const text = value.toString()
|
||||||
|
result += `,${name}=${CQCodeEscape(text)}`
|
||||||
|
} catch (error) {
|
||||||
|
// If it can't be converted, skip this name-value pair
|
||||||
|
}
|
||||||
}
|
}
|
||||||
result += ']'
|
result += ']'
|
||||||
return result
|
return result
|
||||||
|
@@ -11,5 +11,5 @@ export enum EventType {
|
|||||||
export abstract class OB11BaseEvent {
|
export abstract class OB11BaseEvent {
|
||||||
time = Math.floor(Date.now() / 1000)
|
time = Math.floor(Date.now() / 1000)
|
||||||
self_id = parseInt(selfInfo.uin)
|
self_id = parseInt(selfInfo.uin)
|
||||||
post_type: EventType
|
abstract post_type: EventType
|
||||||
}
|
}
|
||||||
|
@@ -2,5 +2,5 @@ import { EventType, OB11BaseEvent } from '../OB11BaseEvent'
|
|||||||
|
|
||||||
export abstract class OB11BaseMetaEvent extends OB11BaseEvent {
|
export abstract class OB11BaseMetaEvent extends OB11BaseEvent {
|
||||||
post_type = EventType.META
|
post_type = EventType.META
|
||||||
meta_event_type: string
|
abstract meta_event_type: string
|
||||||
}
|
}
|
||||||
|
@@ -1,11 +1,11 @@
|
|||||||
import { OB11BaseNoticeEvent } from './OB11BaseNoticeEvent';
|
import { OB11BaseNoticeEvent } from './OB11BaseNoticeEvent'
|
||||||
|
|
||||||
export class OB11FriendAddNoticeEvent extends OB11BaseNoticeEvent {
|
export class OB11FriendAddNoticeEvent extends OB11BaseNoticeEvent {
|
||||||
notice_type = 'friend_add';
|
notice_type = 'friend_add'
|
||||||
user_id: number;
|
user_id: number
|
||||||
|
|
||||||
public constructor(userId: number) {
|
public constructor(userId: number) {
|
||||||
super();
|
super()
|
||||||
this.user_id = userId;
|
this.user_id = userId
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -2,5 +2,14 @@ import { OB11GroupNoticeEvent } from './OB11GroupNoticeEvent'
|
|||||||
|
|
||||||
export class OB11GroupAdminNoticeEvent extends OB11GroupNoticeEvent {
|
export class OB11GroupAdminNoticeEvent extends OB11GroupNoticeEvent {
|
||||||
notice_type = 'group_admin'
|
notice_type = 'group_admin'
|
||||||
sub_type: 'set' | 'unset' // "set" | "unset"
|
sub_type: 'set' | 'unset'
|
||||||
|
group_id: number
|
||||||
|
user_id: number
|
||||||
|
|
||||||
|
constructor(subType: 'set' | 'unset', groupId: number, userId: number) {
|
||||||
|
super()
|
||||||
|
this.sub_type = subType
|
||||||
|
this.group_id = groupId
|
||||||
|
this.user_id = userId
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -5,6 +5,8 @@ export class OB11GroupBanEvent extends OB11GroupNoticeEvent {
|
|||||||
operator_id: number
|
operator_id: number
|
||||||
duration: number
|
duration: number
|
||||||
sub_type: 'ban' | 'lift_ban'
|
sub_type: 'ban' | 'lift_ban'
|
||||||
|
group_id: number
|
||||||
|
user_id: number
|
||||||
|
|
||||||
constructor(groupId: number, userId: number, operatorId: number, duration: number, sub_type: 'ban' | 'lift_ban') {
|
constructor(groupId: number, userId: number, operatorId: number, duration: number, sub_type: 'ban' | 'lift_ban') {
|
||||||
super()
|
super()
|
||||||
|
@@ -4,6 +4,8 @@ export class OB11GroupCardEvent extends OB11GroupNoticeEvent {
|
|||||||
notice_type = 'group_card'
|
notice_type = 'group_card'
|
||||||
card_new: string
|
card_new: string
|
||||||
card_old: string
|
card_old: string
|
||||||
|
group_id: number
|
||||||
|
user_id: number
|
||||||
|
|
||||||
constructor(groupId: number, userId: number, cardNew: string, cardOld: string) {
|
constructor(groupId: number, userId: number, cardNew: string, cardOld: string) {
|
||||||
super()
|
super()
|
||||||
|
@@ -6,6 +6,8 @@ export class OB11GroupDecreaseEvent extends OB11GroupNoticeEvent {
|
|||||||
notice_type = 'group_decrease'
|
notice_type = 'group_decrease'
|
||||||
sub_type: GroupDecreaseSubType = 'leave' // TODO: 实现其他几种子类型的识别 ("leave" | "kick" | "kick_me")
|
sub_type: GroupDecreaseSubType = 'leave' // TODO: 实现其他几种子类型的识别 ("leave" | "kick" | "kick_me")
|
||||||
operator_id: number
|
operator_id: number
|
||||||
|
group_id: number
|
||||||
|
user_id: number
|
||||||
|
|
||||||
constructor(groupId: number, userId: number, operatorId: number, subType: GroupDecreaseSubType = 'leave') {
|
constructor(groupId: number, userId: number, operatorId: number, subType: GroupDecreaseSubType = 'leave') {
|
||||||
super()
|
super()
|
||||||
|
16
src/onebot11/event/notice/OB11GroupEssenceEvent.ts
Normal file
16
src/onebot11/event/notice/OB11GroupEssenceEvent.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { OB11GroupNoticeEvent } from './OB11GroupNoticeEvent';
|
||||||
|
export class OB11GroupEssenceEvent extends OB11GroupNoticeEvent {
|
||||||
|
notice_type = 'essence'
|
||||||
|
message_id: number
|
||||||
|
sender_id: number
|
||||||
|
sub_type: 'add' | 'delete' = 'add'
|
||||||
|
group_id: number
|
||||||
|
user_id: number = 0
|
||||||
|
|
||||||
|
constructor(groupId: number, message_id: number, sender_id: number) {
|
||||||
|
super()
|
||||||
|
this.group_id = groupId
|
||||||
|
this.message_id = message_id
|
||||||
|
this.sender_id = sender_id
|
||||||
|
}
|
||||||
|
}
|
@@ -1,10 +1,14 @@
|
|||||||
import { OB11GroupNoticeEvent } from './OB11GroupNoticeEvent'
|
import { OB11GroupNoticeEvent } from './OB11GroupNoticeEvent'
|
||||||
|
|
||||||
type GroupIncreaseSubType = 'approve' | 'invite'
|
type GroupIncreaseSubType = 'approve' | 'invite'
|
||||||
|
|
||||||
export class OB11GroupIncreaseEvent extends OB11GroupNoticeEvent {
|
export class OB11GroupIncreaseEvent extends OB11GroupNoticeEvent {
|
||||||
notice_type = 'group_increase'
|
notice_type = 'group_increase'
|
||||||
operator_id: number
|
operator_id: number
|
||||||
sub_type: GroupIncreaseSubType
|
sub_type: GroupIncreaseSubType
|
||||||
|
group_id: number
|
||||||
|
user_id: number
|
||||||
|
|
||||||
constructor(groupId: number, userId: number, operatorId: number, subType: GroupIncreaseSubType = 'approve') {
|
constructor(groupId: number, userId: number, operatorId: number, subType: GroupIncreaseSubType = 'approve') {
|
||||||
super()
|
super()
|
||||||
this.group_id = groupId
|
this.group_id = groupId
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
import { OB11BaseNoticeEvent } from './OB11BaseNoticeEvent'
|
import { OB11BaseNoticeEvent } from './OB11BaseNoticeEvent'
|
||||||
|
|
||||||
export abstract class OB11GroupNoticeEvent extends OB11BaseNoticeEvent {
|
export abstract class OB11GroupNoticeEvent extends OB11BaseNoticeEvent {
|
||||||
group_id: number
|
abstract group_id: number
|
||||||
user_id: number
|
abstract user_id: number
|
||||||
|
abstract notice_type: string
|
||||||
}
|
}
|
||||||
|
@@ -4,6 +4,8 @@ export class OB11GroupRecallNoticeEvent extends OB11GroupNoticeEvent {
|
|||||||
notice_type = 'group_recall'
|
notice_type = 'group_recall'
|
||||||
operator_id: number
|
operator_id: number
|
||||||
message_id: number
|
message_id: number
|
||||||
|
group_id: number
|
||||||
|
user_id: number
|
||||||
|
|
||||||
constructor(groupId: number, userId: number, operatorId: number, messageId: number) {
|
constructor(groupId: number, userId: number, operatorId: number, messageId: number) {
|
||||||
super()
|
super()
|
||||||
|
@@ -4,6 +4,8 @@ export class OB11GroupTitleEvent extends OB11GroupNoticeEvent {
|
|||||||
notice_type = 'notify'
|
notice_type = 'notify'
|
||||||
sub_type = 'title'
|
sub_type = 'title'
|
||||||
title: string
|
title: string
|
||||||
|
group_id: number
|
||||||
|
user_id: number
|
||||||
|
|
||||||
constructor(groupId: number, userId: number, title: string) {
|
constructor(groupId: number, userId: number, title: string) {
|
||||||
super()
|
super()
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user