mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2025-07-19 12:03:37 +00:00
Compare commits
216 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
48d5cb53bd | ||
![]() |
fd7d2dbf53 | ||
![]() |
6609697752 | ||
![]() |
dcd6e1973e | ||
![]() |
3614a6e932 | ||
![]() |
931a0210e5 | ||
![]() |
f9e7de4b42 | ||
![]() |
8e0b79594e | ||
![]() |
17122c4360 | ||
![]() |
154f7b6a30 | ||
![]() |
52e5543d0b | ||
![]() |
3c304bd2ae | ||
![]() |
26609bb8fd | ||
![]() |
de3fa9aaa4 | ||
![]() |
788665f84c | ||
![]() |
3943782971 | ||
![]() |
8f899c40f2 | ||
![]() |
a1f582399e | ||
![]() |
440b63f662 | ||
![]() |
7d2cc3b56b | ||
![]() |
5fe3422469 | ||
![]() |
6c02cedb1e | ||
![]() |
3cc2f1dcad | ||
![]() |
773cdc5877 | ||
![]() |
361a7329d7 | ||
![]() |
29910f1236 | ||
![]() |
4a164016f5 | ||
![]() |
cebd3e62a4 | ||
![]() |
2562a38fa1 | ||
![]() |
d46c922bbf | ||
![]() |
66b59982f7 | ||
![]() |
ad397ccf7f | ||
![]() |
bdef80ede7 | ||
![]() |
385dcbc75a | ||
![]() |
74cf501c8f | ||
![]() |
0c200d6748 | ||
![]() |
e65a36c517 | ||
![]() |
126b54ad40 | ||
![]() |
78637751af | ||
![]() |
f96526ee3a | ||
![]() |
b3c7a91f3d | ||
![]() |
b8daeef0c4 | ||
![]() |
2b662944cf | ||
![]() |
3d516df01e | ||
![]() |
26b4a9b15b | ||
![]() |
0f8af273ae | ||
![]() |
fa29a31da9 | ||
![]() |
9e0d2606d8 | ||
![]() |
338dedd6e0 | ||
![]() |
1f893b1393 | ||
![]() |
b783d6f928 | ||
![]() |
8ca0d40f05 | ||
![]() |
2f40a80434 | ||
![]() |
812d8eb5bb | ||
![]() |
bf5f548349 | ||
![]() |
ebf90e72b9 | ||
![]() |
31d7d42edf | ||
![]() |
833875b42f | ||
![]() |
b901c10f3c | ||
![]() |
8ab678bd97 | ||
![]() |
55d5072f46 | ||
![]() |
a379ffd0f2 | ||
![]() |
4501d73134 | ||
![]() |
5f15774ec7 | ||
![]() |
c9e057599e | ||
![]() |
66851f5625 | ||
![]() |
b33c235b4d | ||
![]() |
d6693b6114 | ||
![]() |
36b4d26c78 | ||
![]() |
617592d90a | ||
![]() |
15a77b8070 | ||
![]() |
606eccd22b | ||
![]() |
5613450313 | ||
![]() |
c59b5564af | ||
![]() |
330b086b8b | ||
![]() |
9837ef4f36 | ||
![]() |
add46b3251 | ||
![]() |
e169199107 | ||
![]() |
92fe654850 | ||
![]() |
b257486404 | ||
![]() |
bdf2e33f40 | ||
![]() |
224d361923 | ||
![]() |
3452fa56df | ||
![]() |
cd256235da | ||
![]() |
361a164f2a | ||
![]() |
0e60d4b198 | ||
![]() |
67b47e39b4 | ||
![]() |
8f54310f63 | ||
![]() |
c7a7494d7e | ||
![]() |
af88b3166d | ||
![]() |
b7837b2a14 | ||
![]() |
950ddc749e | ||
![]() |
df081ef0cf | ||
![]() |
7b24f90d9f | ||
![]() |
f2e4579fd8 | ||
![]() |
97cb351827 | ||
![]() |
c1ec53fdbb | ||
![]() |
98214aa429 | ||
![]() |
ce7deac2dd | ||
![]() |
612092b867 | ||
![]() |
92579d5949 | ||
![]() |
9ab07060ae | ||
![]() |
0d45125d79 | ||
![]() |
9ced152778 | ||
![]() |
3685ab2e3e | ||
![]() |
be605f11f2 | ||
![]() |
8cca8df976 | ||
![]() |
990a31e961 | ||
![]() |
5db201c342 | ||
![]() |
a625e30dd4 | ||
![]() |
b236cdd060 | ||
![]() |
2db9899184 | ||
![]() |
fe5d6db986 | ||
![]() |
7c7bf8fecf | ||
![]() |
76e3a46378 | ||
![]() |
16f3897fec | ||
![]() |
045e120854 | ||
![]() |
2b7fcce9b2 | ||
![]() |
9685931694 | ||
![]() |
1dc844435a | ||
![]() |
18892379de | ||
![]() |
620d61c8dc | ||
![]() |
9f91398875 | ||
![]() |
a29b1154a9 | ||
![]() |
34d19a471a | ||
![]() |
2ef6477d7c | ||
![]() |
26e6800836 | ||
![]() |
9dbbcf3872 | ||
![]() |
6b3343e1e4 | ||
![]() |
ef48f754a5 | ||
![]() |
3be1ede847 | ||
![]() |
7bff1b61e8 | ||
![]() |
6affd0eb68 | ||
![]() |
0a112d15e0 | ||
![]() |
4e03f582bb | ||
![]() |
8f186c1c5e | ||
![]() |
cd1bae9a1f | ||
![]() |
60796c26ca | ||
![]() |
28927f950d | ||
![]() |
95f16ebc8c | ||
![]() |
25bca8385d | ||
![]() |
965c7f23b4 | ||
![]() |
33082af9cc | ||
![]() |
1f7f3565b0 | ||
![]() |
f784363696 | ||
![]() |
37bd51e138 | ||
![]() |
c367728c43 | ||
![]() |
61f0f5d884 | ||
![]() |
5f2ebeead7 | ||
![]() |
7646037fc7 | ||
![]() |
eac6d285ff | ||
![]() |
b921d5e734 | ||
![]() |
831d808e63 | ||
![]() |
451b88d7e3 | ||
![]() |
f916682a71 | ||
![]() |
2d76bcf0cf | ||
![]() |
8db294efe6 | ||
![]() |
5cc5149aed | ||
![]() |
7ecb01dc9f | ||
![]() |
8bf1a545d9 | ||
![]() |
efb2be2f94 | ||
![]() |
bd2edda494 | ||
![]() |
991172eae4 | ||
![]() |
fc7631f9aa | ||
![]() |
a21efb7d2f | ||
![]() |
03bc844ad0 | ||
![]() |
f7bdc35ed6 | ||
![]() |
0efdffd857 | ||
![]() |
6a16a42d0c | ||
![]() |
e9c00c72b1 | ||
![]() |
ab22f36b8a | ||
![]() |
c57497cd91 | ||
![]() |
ba123236e5 | ||
![]() |
338b6e4607 | ||
![]() |
f88c717560 | ||
![]() |
f8ffc92db5 | ||
![]() |
98c23c172c | ||
![]() |
781c107d8c | ||
![]() |
186668c075 | ||
![]() |
cf9f785193 | ||
![]() |
72d2d3f224 | ||
![]() |
087c76b394 | ||
![]() |
4f9fb2c8c3 | ||
![]() |
334e43e764 | ||
![]() |
7843256402 | ||
![]() |
0522ba35fe | ||
![]() |
24d3b52e0b | ||
![]() |
3177110f0f | ||
![]() |
e1b8243a67 | ||
![]() |
b1c6ce3885 | ||
![]() |
0b4b25a11e | ||
![]() |
1176fe984a | ||
![]() |
6ca768c3ee | ||
![]() |
3da1659c8d | ||
![]() |
9aa4cd319c | ||
![]() |
5af866cdca | ||
![]() |
2b421fa447 | ||
![]() |
30ec964325 | ||
![]() |
714d7d72eb | ||
![]() |
687aa0f363 | ||
![]() |
8363ab07a7 | ||
![]() |
c46a757339 | ||
![]() |
557864395b | ||
![]() |
3f7a85d80b | ||
![]() |
8d18d2ce1f | ||
![]() |
7141ba1587 | ||
![]() |
44d350a225 | ||
![]() |
239b8e72d9 | ||
![]() |
279bdb6fb1 | ||
![]() |
a0cea819da | ||
![]() |
9ab7f60544 | ||
![]() |
aaf7191bf3 | ||
![]() |
628c9be0c8 | ||
![]() |
733052720c | ||
![]() |
a10f007194 | ||
![]() |
6fa50c58d3 |
11
docs/changelogs/CHANGELOG.v1.7.6.md
Normal file
11
docs/changelogs/CHANGELOG.v1.7.6.md
Normal file
@@ -0,0 +1,11 @@
|
||||
# v1.7.6
|
||||
|
||||
QQ Version: Windows 9.9.15-26702 / Linux 3.2.12-26702
|
||||
|
||||
## 启动的方式
|
||||
Way03/Way05
|
||||
|
||||
## 新增与调整
|
||||
1. 支持 9.9.15/3.2.12 版本QQ
|
||||
|
||||
新增的 API 详细见[API文档](https://napneko.github.io/zh-CN/develop/extends_api)
|
@@ -1,4 +1,4 @@
|
||||
# v1.6.5
|
||||
# v1.6.6
|
||||
|
||||
QQ Version: Windows 9.9.12-26000 / Linux 3.2.9-26000
|
||||
## 使用前警告
|
@@ -1,3 +1,4 @@
|
||||
|
||||
public static final int C2C_PIC_DOWNLOAD = 1004;
|
||||
public static final String C2C_PIC_DOWNLOAD_DOMAIN = "c2cpicdw.qpic.cn";
|
||||
public static final String C2C_PIC_DOWNLOAD_QUIC_DOMAIN = "c2cpicdw.quic.qpic.cn";
|
||||
|
@@ -1 +1,8 @@
|
||||
# Api方向
|
||||
## getMsgUniqueId √ 已应用
|
||||
getMsgUniqueId 传入时间 产出一个唯一ID 发送消息作为一个参数
|
||||
|
||||
# Native方向
|
||||
## magic_load
|
||||
## api_caller
|
||||
## NodeMain
|
||||
|
12
package.json
12
package.json
@@ -2,7 +2,7 @@
|
||||
"name": "napcat",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"version": "1.6.6",
|
||||
"version": "1.7.6",
|
||||
"scripts": {
|
||||
"watch:dev": "vite --mode development",
|
||||
"watch:prod": "vite --mode production",
|
||||
@@ -19,10 +19,9 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.24.7",
|
||||
"@babel/preset-typescript": "^7.24.7",
|
||||
"vite-plugin-babel": "^1.2.0",
|
||||
"@babel/plugin-proposal-class-properties": "^7.18.6",
|
||||
"@babel/plugin-proposal-decorators": "^7.24.7",
|
||||
"@babel/preset-typescript": "^7.24.7",
|
||||
"@log4js-node/log4js-api": "^1.0.2",
|
||||
"@protobuf-ts/plugin": "^2.9.4",
|
||||
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||
@@ -31,9 +30,9 @@
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/figlet": "^1.5.8",
|
||||
"@types/fluent-ffmpeg": "^2.1.24",
|
||||
"@types/node": "^20.11.30",
|
||||
"@types/jest": "^29.5.12",
|
||||
"@types/node": "^22.0.0",
|
||||
"@types/qrcode-terminal": "^0.12.2",
|
||||
"@types/uuid": "^10.0.0",
|
||||
"@types/ws": "^8.5.10",
|
||||
"@typescript-eslint/eslint-plugin": "^7.4.0",
|
||||
"@typescript-eslint/parser": "^7.4.0",
|
||||
@@ -47,6 +46,7 @@
|
||||
"rollup-plugin-obfuscator": "^1.1.0",
|
||||
"typescript": "^5.3.3",
|
||||
"vite": "^5.2.6",
|
||||
"vite-plugin-babel": "^1.2.0",
|
||||
"vite-plugin-cp": "^4.0.8",
|
||||
"vite-plugin-dts": "^3.8.2",
|
||||
"vite-tsconfig-paths": "^4.3.2"
|
||||
@@ -65,8 +65,6 @@
|
||||
"log4js": "^6.9.1",
|
||||
"qrcode-terminal": "^0.12.0",
|
||||
"silk-wasm": "^3.6.1",
|
||||
"sqlite3": "^5.1.7",
|
||||
"uuid": "^10.0.0",
|
||||
"ws": "^8.16.0"
|
||||
}
|
||||
}
|
||||
|
@@ -42,4 +42,4 @@ if (!(Test-Path $QQpath)) {
|
||||
$Bootfile = Join-Path $PSScriptRoot "napcat.mjs"
|
||||
$env:ELECTRON_RUN_AS_NODE = 1
|
||||
$commandInfo = Get-Command $QQpath -ErrorAction Stop
|
||||
Start-Process powershell -ArgumentList "-noexit", "-noprofile", "-command &{& chcp 65001;& '$($commandInfo.Path)' --enable-logging $params}"
|
||||
Start-Process powershell -ArgumentList "-noexit", "-noprofile", "-command &{& chcp 65001;& '$($commandInfo.Path)' --enable-logging }"
|
123
script/BootWay05.ps1
Normal file
123
script/BootWay05.ps1
Normal file
@@ -0,0 +1,123 @@
|
||||
# 检查当前会话是否具有管理员权限
|
||||
function Test-Administrator {
|
||||
$user = [Security.Principal.WindowsIdentity]::GetCurrent()
|
||||
(New-Object Security.Principal.WindowsPrincipal $user).IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)
|
||||
}
|
||||
|
||||
if (-not (Test-Administrator)) {
|
||||
# 如果不是管理员,则重新启动脚本以管理员模式运行
|
||||
$scriptPath = $myInvocation.MyCommand.Path
|
||||
if (-not $scriptPath) {
|
||||
$scriptPath = $PSCommandPath
|
||||
}
|
||||
$newProcess = New-Object System.Diagnostics.ProcessStartInfo "powershell";
|
||||
$newProcess.Arguments = "-File `"$scriptPath`" $args"
|
||||
$newProcess.Verb = "runas";
|
||||
[System.Diagnostics.Process]::Start($newProcess);
|
||||
exit
|
||||
}
|
||||
|
||||
function Get-QQpath {
|
||||
try {
|
||||
$key = Get-ItemProperty -Path "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\QQ"
|
||||
$uninstallString = $key.UninstallString
|
||||
return [System.IO.Path]::GetDirectoryName($uninstallString) + "\QQ.exe"
|
||||
}
|
||||
catch {
|
||||
throw "get QQ path error: $_"
|
||||
}
|
||||
}
|
||||
function Select-QQPath {
|
||||
Add-Type -AssemblyName System.Windows.Forms
|
||||
[System.Windows.Forms.Application]::EnableVisualStyles()
|
||||
|
||||
$dialogTitle = "Select QQ.exe"
|
||||
|
||||
$filePicker = New-Object System.Windows.Forms.OpenFileDialog
|
||||
$filePicker.Title = $dialogTitle
|
||||
$filePicker.Filter = "Executable Files (*.exe)|*.exe|All Files (*.*)|*.*"
|
||||
$filePicker.FilterIndex = 1
|
||||
$null = $filePicker.ShowDialog()
|
||||
if (-not ($filePicker.FileName)) {
|
||||
throw "User did not select an .exe file."
|
||||
}
|
||||
return $filePicker.FileName
|
||||
}
|
||||
|
||||
# 设置当前工作目录
|
||||
$scriptDirectory = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent
|
||||
Set-Location $scriptDirectory
|
||||
|
||||
# 获取当前目录路径
|
||||
$currentPath = Get-Location
|
||||
|
||||
# 替换\为/
|
||||
$currentPath = $currentPath -replace '\\', '/'
|
||||
|
||||
# 生成JavaScript代码
|
||||
$jsCode = @"
|
||||
(async () => {
|
||||
await import('file:///$currentPath/napcat.mjs');
|
||||
})();
|
||||
"@
|
||||
|
||||
# 将JavaScript代码保存到文件中
|
||||
$jsFilePath = Join-Path $currentPath "loadScript.js"
|
||||
$jsCode | Out-File -FilePath $jsFilePath -Encoding UTF8
|
||||
|
||||
Write-Output "JavaScript code has been generated and saved to $jsFilePath"
|
||||
# 设置NAPCAT_PATH环境变量为 当前目录的loadScript.js地址
|
||||
$env:NAPCAT_PATH = $jsFilePath
|
||||
|
||||
$params = $args -join " "
|
||||
Try {
|
||||
$QQpath = Get-QQpath
|
||||
}
|
||||
Catch {
|
||||
$QQpath = Select-QQPath
|
||||
}
|
||||
# 拿不到QQ路径则退出
|
||||
if (!(Test-Path $QQpath)) {
|
||||
Write-Output "provided QQ path is invalid: $QQpath"
|
||||
Read-Host "Press any key to continue..."
|
||||
exit
|
||||
}
|
||||
|
||||
$commandInfo = Get-Command $QQpath -ErrorAction Stop
|
||||
|
||||
# 收集dbghelp.dll路径和HASH信息
|
||||
$QQpath = Split-Path $QQpath
|
||||
$oldDllPath = Join-Path $QQpath "dbghelp.dll"
|
||||
$oldDllHash = Get-FileHash $oldDllPath -Algorithm MD5
|
||||
$newDllPath = Join-Path $currentPath "dbghelp.dll"
|
||||
$newDllHash = Get-FileHash $newDllPath -Algorithm MD5
|
||||
# 如果文件一致则跳过
|
||||
if ($oldDllHash.Hash -ne $newDllHash.Hash) {
|
||||
$processes = Get-Process -Name QQ -ErrorAction SilentlyContinue
|
||||
if ($processes) {
|
||||
# 文件占用则退出
|
||||
Write-Output "dbghelp.dll is in use by the following processes:"
|
||||
$processes | ForEach-Object { Write-Output "$($_.Id) $($_.Name) $($_.Path)" }
|
||||
Write-Output "dbghelp.dll is in use, cannot continue."
|
||||
Read-Host "Press any key to continue..."
|
||||
exit
|
||||
} else {
|
||||
# 文件未占用则尝试覆盖
|
||||
try {
|
||||
Copy-Item -Path "$newDllPath" -Destination "$oldDllPath" -Force
|
||||
Write-Output "dbghelp.dll has been copied to $QQpath"
|
||||
} catch {
|
||||
Write-Output "Failed to copy dbghelp.dll: $_"
|
||||
Read-Host "Press any key to continue..."
|
||||
exit
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# 带参数启动QQ
|
||||
try {
|
||||
Start-Process powershell -ArgumentList '-noexit', '-noprofile', "-command &{& chcp 65001;& '$($commandInfo.Path)' --enable-logging $params}" -NoNewWindow -ErrorAction Stop
|
||||
} catch {
|
||||
Write-Output "Failed to start process as administrator: $_"
|
||||
Read-Host "Press any key to continue..."
|
||||
}
|
BIN
script/dbghelp.dll
Normal file
BIN
script/dbghelp.dll
Normal file
Binary file not shown.
@@ -9,7 +9,15 @@ type RegisterHandler = (res: Response, payload: any) => Promise<any>
|
||||
export abstract class HttpServerBase {
|
||||
name: string = 'NapCatQQ';
|
||||
private readonly expressAPP: Express;
|
||||
private server: http.Server | null = null;
|
||||
private _server: http.Server | null = null;
|
||||
|
||||
public get server(): http.Server | null {
|
||||
return this._server;
|
||||
}
|
||||
|
||||
private set server(value: http.Server | null) {
|
||||
this._server = value;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
this.expressAPP = express();
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import { WebSocket, WebSocketServer } from 'ws';
|
||||
import http from 'http';
|
||||
import urlParse from 'url';
|
||||
import { IncomingMessage } from 'node:http';
|
||||
import { log } from '@/common/utils/log';
|
||||
@@ -27,17 +28,36 @@ export class WebsocketServerBase {
|
||||
constructor() {
|
||||
}
|
||||
|
||||
start(port: number, host: string = '') {
|
||||
try {
|
||||
this.ws = new WebSocketServer({
|
||||
port,
|
||||
host: '',
|
||||
maxPayload: 1024 * 1024 * 1024
|
||||
}).on('error', () => {
|
||||
});
|
||||
log(`ws服务启动成功, ${host}:${port}`);
|
||||
} catch (e: any) {
|
||||
throw Error('ws服务启动失败, 请检查监听的ip和端口' + e.toString());
|
||||
start(port: number | http.Server, host: string = '') {
|
||||
if (port instanceof http.Server) {
|
||||
try {
|
||||
const wss = new WebSocketServer({
|
||||
noServer: true,
|
||||
maxPayload: 1024 * 1024 * 1024
|
||||
}).on('error', () => {
|
||||
});
|
||||
this.ws = wss;
|
||||
port.on('upgrade', function upgrade(request, socket, head) {
|
||||
wss.handleUpgrade(request, socket, head, function done(ws) {
|
||||
wss.emit('connection', ws, request);
|
||||
});
|
||||
});
|
||||
log('ws服务启动成功, 绑定到HTTP服务');
|
||||
} catch (e: any) {
|
||||
throw Error('ws服务启动失败, 可能是绑定的HTTP服务异常' + e.toString());
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
this.ws = new WebSocketServer({
|
||||
port,
|
||||
host: '',
|
||||
maxPayload: 1024 * 1024 * 1024
|
||||
}).on('error', () => {
|
||||
});
|
||||
log(`ws服务启动成功, ${host}:${port}`);
|
||||
} catch (e: any) {
|
||||
throw Error('ws服务启动失败, 请检查监听的ip和端口' + e.toString());
|
||||
}
|
||||
}
|
||||
this.ws.on('connection', (wsClient, req) => {
|
||||
const url: string = req.url!.split('?').shift() || '/';
|
||||
@@ -50,10 +70,12 @@ export class WebsocketServerBase {
|
||||
}
|
||||
|
||||
stop() {
|
||||
this.ws && this.ws.close((err) => {
|
||||
log('ws server close failed!', err);
|
||||
});
|
||||
this.ws = null;
|
||||
if (this.ws) {
|
||||
this.ws.close((err) => {
|
||||
if (err) log('ws server close failed!', err);
|
||||
});
|
||||
this.ws = null;
|
||||
}
|
||||
}
|
||||
|
||||
restart(port: number) {
|
||||
|
@@ -1,36 +0,0 @@
|
||||
import { sleep } from '@/common/utils/helper';
|
||||
import { logError } from './log';
|
||||
type AsyncQueueTask = (() => void) | (()=>Promise<void>);
|
||||
// 2024.7.13 废弃
|
||||
|
||||
export class AsyncQueue {
|
||||
private tasks: (AsyncQueueTask)[] = [];
|
||||
|
||||
public addTask(task: AsyncQueueTask) {
|
||||
this.tasks.push(task);
|
||||
// console.log('addTask', this.tasks.length);
|
||||
if (this.tasks.length === 1) {
|
||||
this.runQueue().then().catch(()=>{});
|
||||
}
|
||||
}
|
||||
|
||||
private async runQueue() {
|
||||
// console.log('runQueue', this.tasks.length);
|
||||
while (this.tasks.length > 0) {
|
||||
const task = this.tasks[0];
|
||||
// console.log('typeof task', typeof task);
|
||||
try {
|
||||
const taskRet = task();
|
||||
// console.log('type of taskRet', typeof taskRet, taskRet);
|
||||
if (taskRet instanceof Promise) {
|
||||
await taskRet;
|
||||
}
|
||||
} catch (e) {
|
||||
// console.error(e);
|
||||
logError(e);
|
||||
}
|
||||
this.tasks.shift();
|
||||
await sleep(100);
|
||||
}
|
||||
}
|
||||
}
|
@@ -3,6 +3,7 @@ import fs from 'node:fs';
|
||||
import { log, logDebug, logError } from '@/common/utils/log';
|
||||
import { dirname } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { selfInfo } from '@/core/data';
|
||||
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
@@ -12,8 +13,9 @@ const configDir = path.resolve(__dirname, 'config');
|
||||
fs.mkdirSync(configDir, { recursive: true });
|
||||
|
||||
|
||||
export class ConfigBase<T>{
|
||||
|
||||
export class ConfigBase<T> {
|
||||
public name: string = 'default_config';
|
||||
private pathName: string | null = null; // 本次读取的文件路径
|
||||
constructor() {
|
||||
}
|
||||
|
||||
@@ -22,19 +24,28 @@ export class ConfigBase<T>{
|
||||
return null;
|
||||
}
|
||||
|
||||
getConfigDir(){
|
||||
getConfigDir() {
|
||||
const configDir = path.resolve(__dirname, 'config');
|
||||
fs.mkdirSync(configDir, { recursive: true });
|
||||
return configDir;
|
||||
}
|
||||
getConfigPath(): string {
|
||||
throw new Error('Method not implemented.');
|
||||
getConfigPath(pathName: string | null): string {
|
||||
const suffix = pathName ? `_${pathName}` : '';
|
||||
const filename = `${this.name}${suffix}.json`;
|
||||
return path.join(this.getConfigDir(), filename);
|
||||
}
|
||||
|
||||
read() {
|
||||
const configPath = this.getConfigPath();
|
||||
// 尝试加载当前账号配置
|
||||
if (this.read_from_file(selfInfo.uin, false)) return this;
|
||||
// 尝试加载默认配置
|
||||
return this.read_from_file('', true);
|
||||
}
|
||||
read_from_file(pathName: string, createIfNotExist: boolean) {
|
||||
const configPath = this.getConfigPath(pathName);
|
||||
if (!fs.existsSync(configPath)) {
|
||||
try{
|
||||
if (!createIfNotExist) return null;
|
||||
this.pathName = pathName; // 记录有效的设置文件
|
||||
try {
|
||||
fs.writeFileSync(configPath, JSON.stringify(this, this.getKeys(), 2));
|
||||
log(`配置文件${configPath}已创建\n如果修改此文件后需要重启 NapCat 生效`);
|
||||
}
|
||||
@@ -43,6 +54,7 @@ export class ConfigBase<T>{
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
try {
|
||||
const data = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
||||
logDebug(`配置文件${configPath}已加载`, data);
|
||||
@@ -61,9 +73,13 @@ export class ConfigBase<T>{
|
||||
}
|
||||
}
|
||||
|
||||
save(config: T) {
|
||||
save(config: T, overwrite: boolean = false) {
|
||||
Object.assign(this, config);
|
||||
const configPath = this.getConfigPath();
|
||||
if (overwrite) {
|
||||
// 用户要求强制写入,则变更当前文件为目标文件
|
||||
this.pathName = `${selfInfo.uin}`;
|
||||
}
|
||||
const configPath = this.getConfigPath(this.pathName);
|
||||
try {
|
||||
fs.writeFileSync(configPath, JSON.stringify(this, this.getKeys(), 2));
|
||||
} catch (e: any) {
|
||||
|
@@ -5,6 +5,7 @@ interface Internal_MapKey {
|
||||
timeout: number,
|
||||
createtime: number,
|
||||
func: (...arg: any[]) => any,
|
||||
checker: ((...args: any[]) => boolean) | undefined,
|
||||
}
|
||||
|
||||
export class ListenerClassBase {
|
||||
@@ -83,17 +84,19 @@ export class NTEventWrapper {
|
||||
}
|
||||
//统一回调清理事件
|
||||
async DispatcherListener(ListenerMainName: string, ListenerSubName: string, ...args: any[]) {
|
||||
//console.log(ListenerMainName, this.EventTask.get(ListenerMainName), ListenerSubName, this.EventTask.get(ListenerMainName)?.get(ListenerSubName));
|
||||
//console.log(ListenerMainName, ListenerSubName, ...args,this.EventTask.get(ListenerMainName)?.get(ListenerSubName));
|
||||
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;
|
||||
}
|
||||
task.func(...args);
|
||||
if (task.checker && task.checker(...args)) {
|
||||
task.func(...args);
|
||||
}
|
||||
});
|
||||
}
|
||||
async CallNoListenerEvent<EventType extends (...args: any[]) => Promise<any>,>(EventName = '', timeout: number = 3000, ...args: Parameters<EventType>) {
|
||||
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;
|
||||
@@ -107,27 +110,71 @@ export class NTEventWrapper {
|
||||
resolve(retData);
|
||||
});
|
||||
}
|
||||
async CallNormalEvent<EventType extends (...args: any[]) => Promise<any>, ListenerType extends (...args: any[]) => void>(EventName = '', ListenerName = '', waitTimes = 1, timeout: number = 3000, ...args: Parameters<EventType>) {
|
||||
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 < waitTimes) {
|
||||
reject(new Error('NTEvent EventName:' + EventName + ' ListenerName:' + ListenerName + ' timeout'));
|
||||
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 Timeouter = setTimeout(databack, timeout);
|
||||
|
||||
|
||||
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);
|
||||
@@ -147,7 +194,6 @@ export class NTEventWrapper {
|
||||
this.EventTask.get(ListenerMainName)?.get(ListenerSubName)?.set(id, eventCallbak);
|
||||
this.CreatListenerFunction(ListenerMainName);
|
||||
const EventFunc = this.CreatEventFunction<EventType>(EventName);
|
||||
//console.log("测试打点", args);
|
||||
retEvent = await EventFunc!(...(args as any[]));
|
||||
});
|
||||
}
|
||||
|
@@ -1,161 +0,0 @@
|
||||
import { logError, logDebug } from '@/common/utils/log';
|
||||
|
||||
type group_id = number;
|
||||
type user_id = number;
|
||||
|
||||
class cacheNode<T> {
|
||||
value: T;
|
||||
groupId: group_id;
|
||||
userId: user_id;
|
||||
prev: cacheNode<T> | null;
|
||||
next: cacheNode<T> | null;
|
||||
timestamp: number;
|
||||
|
||||
constructor(groupId: group_id, userId: user_id, value: T) {
|
||||
this.groupId = groupId;
|
||||
this.userId = userId;
|
||||
this.value = value;
|
||||
this.prev = null;
|
||||
this.next = null;
|
||||
this.timestamp = Date.now();
|
||||
}
|
||||
}
|
||||
|
||||
type cache<T, K = { [key: user_id]: cacheNode<T> }> = { [key: group_id]: K };
|
||||
type removeObject<T> = cache<T, { userId: user_id, value: T }[]>
|
||||
class LRU<T> {
|
||||
private maxAge: number;
|
||||
private maxSize: number;
|
||||
private currentSize: number;
|
||||
private cache: cache<T>;
|
||||
private head: cacheNode<T> | null = null;
|
||||
private tail: cacheNode<T> | null = null;
|
||||
private onFuncs: ((node: removeObject<T>) => void)[] = [];
|
||||
|
||||
constructor(maxAge: number = 6e4 * 3, maxSize: number = 1e4) {
|
||||
this.maxAge = maxAge;
|
||||
this.maxSize = maxSize;
|
||||
this.cache = Object.create(null);
|
||||
this.currentSize = 0;
|
||||
|
||||
if (maxSize == 0) return;
|
||||
setInterval(() => this.removeExpired(), this.maxAge);
|
||||
}
|
||||
|
||||
// 移除LRU节点
|
||||
private removeLRUNode(node: cacheNode<T>) {
|
||||
logDebug(
|
||||
'removeLRUNode',
|
||||
node.groupId,
|
||||
node.userId,
|
||||
node.value,
|
||||
this.currentSize
|
||||
);
|
||||
node.prev = node.next = null;
|
||||
delete this.cache[node.groupId][node.userId];
|
||||
this.removeNode(node);
|
||||
this.onFuncs.forEach((func) => func({ [node.groupId]: [node] }));
|
||||
this.currentSize--;
|
||||
}
|
||||
|
||||
public on(func: (node: removeObject<T>) => void) {
|
||||
this.onFuncs.push(func);
|
||||
}
|
||||
|
||||
private removeExpired() {
|
||||
const now = Date.now();
|
||||
let current = this.tail;
|
||||
let totalNodeNum = 0;
|
||||
|
||||
const removeObject: cache<T, { userId: user_id, value: T }[]> = {};
|
||||
|
||||
while (current && now - current.timestamp > this.maxAge) {
|
||||
// 收集节点
|
||||
if (!removeObject[current.groupId]) removeObject[current.groupId] = [];
|
||||
removeObject[current.groupId].push({ userId: current.userId, value: current.value });
|
||||
// 删除LRU节点
|
||||
delete this.cache[current.groupId][current.userId];
|
||||
current = current.prev;
|
||||
totalNodeNum++;
|
||||
this.currentSize--;
|
||||
}
|
||||
|
||||
if (!totalNodeNum) return;
|
||||
|
||||
// 跟新链表指向
|
||||
if (current) { current.next = null; } else { this.head = null; }
|
||||
this.tail = current;
|
||||
|
||||
this.onFuncs.forEach(func => func(removeObject));
|
||||
}
|
||||
|
||||
private addNode(node: cacheNode<T>) {
|
||||
node.next = this.head;
|
||||
if (this.head) this.head.prev = node;
|
||||
if (!this.tail) this.tail = node;
|
||||
this.head = node;
|
||||
}
|
||||
|
||||
private removeNode(node: cacheNode<T>) {
|
||||
if (node.prev) node.prev.next = node.next;
|
||||
if (node.next) node.next.prev = node.prev;
|
||||
if (node === this.head) this.head = node.next;
|
||||
if (node === this.tail) this.tail = node.prev;
|
||||
}
|
||||
|
||||
private moveToHead(node: cacheNode<T>) {
|
||||
if (this.head === node) return;
|
||||
|
||||
this.removeNode(node);
|
||||
this.addNode(node);
|
||||
node.prev = null;
|
||||
}
|
||||
|
||||
public set(groupId: group_id, userId: user_id, value: T) {
|
||||
if (!this.cache[groupId]) {
|
||||
this.cache[groupId] = Object.create(null);
|
||||
}
|
||||
|
||||
const groupObject = this.cache[groupId];
|
||||
|
||||
if (groupObject[userId]) {
|
||||
const node = groupObject[userId];
|
||||
node.value = value;
|
||||
node.timestamp = Date.now();
|
||||
this.moveToHead(node);
|
||||
} else {
|
||||
const node = new cacheNode(groupId, userId, value);
|
||||
groupObject[userId] = node;
|
||||
this.currentSize++;
|
||||
this.addNode(node);
|
||||
if (this.currentSize > this.maxSize) {
|
||||
const tail = this.tail!;
|
||||
this.removeLRUNode(tail);
|
||||
}
|
||||
}
|
||||
}
|
||||
public get(groupId: group_id): { userId: user_id; value: T }[];
|
||||
public get(groupId: group_id, userId: user_id): null | { userId: user_id; value: T };
|
||||
public get(groupId: group_id, userId?: user_id): any {
|
||||
const groupObject = this.cache[groupId];
|
||||
if (!groupObject) return userId === undefined ? [] : null;
|
||||
|
||||
if (userId === undefined) {
|
||||
return Object.entries(groupObject).map(([userId, { value }]) => ({
|
||||
userId: Number(userId),
|
||||
value,
|
||||
}));
|
||||
}
|
||||
|
||||
if (groupObject[userId]) {
|
||||
return { userId, value: groupObject[userId].value };
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
export default LRU;
|
@@ -1,20 +1,40 @@
|
||||
import crypto from 'crypto';
|
||||
import { Peer } from '@/core';
|
||||
import crypto, { randomInt, randomUUID } from 'crypto';
|
||||
import { logError } from './log';
|
||||
|
||||
class LimitedHashTable<K, V> {
|
||||
private keyToValue: Map<K, V> = new Map();
|
||||
private valueToKey: Map<V, K> = new Map();
|
||||
private maxSize: number;
|
||||
private KeyQueneList: K[] = [];
|
||||
private ValueQueneList: V[] = [];
|
||||
|
||||
constructor(maxSize: number) {
|
||||
this.maxSize = maxSize;
|
||||
}
|
||||
resize(count: number) {
|
||||
this.maxSize = count;
|
||||
}
|
||||
|
||||
set(key: K, value: V): void {
|
||||
// const isExist = this.keyToValue.get(key);
|
||||
// if (isExist && isExist === value) {
|
||||
// return;
|
||||
// }
|
||||
this.keyToValue.set(key, value);
|
||||
this.valueToKey.set(value, key);
|
||||
if (this.KeyQueneList.length >= this.maxSize || this.ValueQueneList.length >= this.maxSize) {
|
||||
this.KeyQueneList.shift();
|
||||
this.ValueQueneList.shift();
|
||||
while (this.keyToValue.size !== this.valueToKey.size) {
|
||||
console.log('keyToValue.size !== valueToKey.size Error Atom');
|
||||
this.keyToValue.clear();
|
||||
this.valueToKey.clear();
|
||||
}
|
||||
// console.log('---------------');
|
||||
// console.log(this.keyToValue);
|
||||
// console.log(this.valueToKey);
|
||||
// console.log('---------------');
|
||||
while (this.keyToValue.size > this.maxSize || this.valueToKey.size > this.maxSize) {
|
||||
//console.log(this.keyToValue.size > this.maxSize, this.valueToKey.size > this.maxSize);
|
||||
const oldestKey = this.keyToValue.keys().next().value;
|
||||
this.valueToKey.delete(this.keyToValue.get(oldestKey)!);
|
||||
this.keyToValue.delete(oldestKey);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,28 +46,97 @@ class LimitedHashTable<K, V> {
|
||||
return this.valueToKey.get(value);
|
||||
}
|
||||
|
||||
delete(key: K): void {
|
||||
deleteByValue(value: V): void {
|
||||
const key = this.valueToKey.get(value);
|
||||
if (key !== undefined) {
|
||||
this.keyToValue.delete(key);
|
||||
this.valueToKey.delete(value);
|
||||
}
|
||||
}
|
||||
|
||||
deleteByKey(key: K): void {
|
||||
const value = this.keyToValue.get(key);
|
||||
if (value !== undefined) {
|
||||
this.keyToValue.delete(key);
|
||||
this.valueToKey.delete(value);
|
||||
}
|
||||
}
|
||||
|
||||
getKeyList(): K[] {
|
||||
return Array.from(this.keyToValue.keys());
|
||||
}
|
||||
//获取最近刚写入的几个值
|
||||
getHeads(size: number): { key: K; value: V }[] | undefined {
|
||||
const keyList = this.getKeyList();
|
||||
if (keyList.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
const result: { key: K; value: V }[] = [];
|
||||
const listSize = Math.min(size, keyList.length);
|
||||
for (let i = 0; i < listSize; i++) {
|
||||
const key = keyList[listSize - i];
|
||||
result.push({ key, value: this.keyToValue.get(key)! });
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
class MessageUniqueWrapper {
|
||||
private msgIdMap: LimitedHashTable<number, string> = new LimitedHashTable(1000);
|
||||
createMsg(MsgId: string) {
|
||||
const ShortId = parseInt(crypto.createHash('sha1').update('2345').digest('hex').slice(0, 8), 16);
|
||||
this.msgIdMap.set(ShortId, MsgId);
|
||||
return ShortId;
|
||||
private msgDataMap: LimitedHashTable<string, number>;
|
||||
private msgIdMap: LimitedHashTable<string, number>;
|
||||
constructor(maxMap: number = 1000) {
|
||||
this.msgIdMap = new LimitedHashTable<string, number>(maxMap);
|
||||
this.msgDataMap = new LimitedHashTable<string, number>(maxMap);
|
||||
}
|
||||
getMsgIdByShortId(ShortId: number) {
|
||||
return this.msgIdMap.getValue(ShortId);
|
||||
getRecentMsgIds(Peer: Peer, size: number): string[] {
|
||||
const heads = this.msgIdMap.getHeads(size);
|
||||
if (!heads) {
|
||||
return [];
|
||||
}
|
||||
const data = heads.map((t) => MessageUnique.getMsgIdAndPeerByShortId(t.value));
|
||||
const ret = data.filter((t) => t?.Peer.chatType === Peer.chatType && t?.Peer.peerUid === Peer.peerUid);
|
||||
return ret.map((t) => t?.MsgId).filter((t) => t !== undefined);
|
||||
}
|
||||
getShortIdByMsgId(MsgId: string) {
|
||||
return this.msgIdMap.getKey(MsgId);
|
||||
createMsg(peer: Peer, msgId: string): number | undefined {
|
||||
const key = `${msgId}|${peer.chatType}|${peer.peerUid}`;
|
||||
const hash = crypto.createHash('md5').update(key);
|
||||
const shortId = hash.digest().readInt32BE(0);
|
||||
//减少性能损耗
|
||||
// const isExist = this.msgIdMap.getKey(shortId);
|
||||
// if (isExist && isExist === msgId) {
|
||||
// return shortId;
|
||||
// }
|
||||
this.msgIdMap.set(msgId, shortId);
|
||||
this.msgDataMap.set(key, shortId);
|
||||
return shortId;
|
||||
}
|
||||
|
||||
getMsgIdAndPeerByShortId(shortId: number): { MsgId: string; Peer: Peer } | undefined {
|
||||
const data = this.msgDataMap.getKey(shortId);
|
||||
if (data) {
|
||||
const [msgId, chatTypeStr, peerUid] = data.split('|');
|
||||
const peer: Peer = {
|
||||
chatType: parseInt(chatTypeStr),
|
||||
peerUid,
|
||||
guildId: '',
|
||||
};
|
||||
return { MsgId: msgId, Peer: peer };
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
getShortIdByMsgId(msgId: string): number | undefined {
|
||||
return this.msgIdMap.getValue(msgId);
|
||||
}
|
||||
getPeerByMsgId(msgId: string) {
|
||||
const shortId = this.msgIdMap.getValue(msgId);
|
||||
if (!shortId) return undefined;
|
||||
return this.getMsgIdAndPeerByShortId(shortId);
|
||||
}
|
||||
resize(maxSize: number): void {
|
||||
this.msgIdMap.resize(maxSize);
|
||||
this.msgDataMap.resize(maxSize);
|
||||
}
|
||||
}
|
||||
|
||||
export const MessageUnique = new MessageUniqueWrapper();
|
||||
export const MessageUnique: MessageUniqueWrapper = new MessageUniqueWrapper();
|
@@ -3,7 +3,6 @@ import fs from 'node:fs';
|
||||
import os from 'node:os';
|
||||
import { systemPlatform } from '@/common/utils/system';
|
||||
import { logError } from '@/common/utils/log';
|
||||
|
||||
export const exePath = process.execPath;
|
||||
|
||||
export const pkgInfoPath = path.join(path.dirname(exePath), 'resources', 'app', 'package.json');
|
||||
@@ -38,16 +37,16 @@ type QQVersionConfigInfo = {
|
||||
}
|
||||
|
||||
let _qqVersionConfigInfo: QQVersionConfigInfo = {
|
||||
'baseVersion': '9.9.12-25765',
|
||||
'curVersion': '9.9.12-25765',
|
||||
'baseVersion': '9.9.15-26702',
|
||||
'curVersion': '9.9.15-26702',
|
||||
'prevVersion': '',
|
||||
'onErrorVersions': [],
|
||||
'buildId': '25765'
|
||||
'buildId': '26702'
|
||||
};
|
||||
|
||||
if (fs.existsSync(configVersionInfoPath)) {
|
||||
try {
|
||||
const _ =JSON.parse(fs.readFileSync(configVersionInfoPath).toString());
|
||||
const _ = JSON.parse(fs.readFileSync(configVersionInfoPath).toString());
|
||||
_qqVersionConfigInfo = Object.assign(_qqVersionConfigInfo, _);
|
||||
} catch (e) {
|
||||
logError('Load QQ version config info failed, Use default version', e);
|
||||
@@ -68,10 +67,12 @@ export const qqPkgInfo: QQPkgInfo = JSON.parse(fs.readFileSync(pkgInfoPath).toSt
|
||||
// Linux
|
||||
// app_version: '3.2.9-25765',
|
||||
// qua: 'V1_LNX_NQ_3.2.10_25765_GW_B',
|
||||
|
||||
let _appid: string = '537234702'; // 默认为 Windows 平台的 appid
|
||||
export function requireMinNTQBuild(buildStr: string) {
|
||||
return parseInt(qqVersionConfigInfo.buildId) >= parseInt(buildStr);
|
||||
}
|
||||
let _appid: string = '537237765'; // 默认为 Windows 平台的 appid
|
||||
if (systemPlatform === 'linux') {
|
||||
_appid = '537234773';
|
||||
_appid = '537237950';
|
||||
}
|
||||
// todo: mac 平台的 appid
|
||||
export const appid = _appid;
|
@@ -3,7 +3,7 @@ import { encode, getDuration, getWavFileInfo, isWav, isSilk } from 'silk-wasm';
|
||||
import fsPromise from 'fs/promises';
|
||||
import { log, logError } from './log';
|
||||
import path from 'node:path';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { randomUUID } from 'crypto';
|
||||
import { spawn } from 'node:child_process';
|
||||
import { getTempDir } from '@/common/utils/file';
|
||||
|
||||
@@ -64,7 +64,7 @@ export async function encodeSilk(filePath: string) {
|
||||
|
||||
try {
|
||||
const file = await fsPromise.readFile(filePath);
|
||||
const pttPath = path.join(TEMP_DIR, uuidv4());
|
||||
const pttPath = path.join(TEMP_DIR, randomUUID());
|
||||
if (!isSilk(file)) {
|
||||
log(`语音文件${filePath}需要转换成silk`);
|
||||
const _isWav = isWav(file);
|
||||
|
@@ -1,477 +0,0 @@
|
||||
import { ElementType, FileElement, PicElement, PttElement, RawMessage, VideoElement } from '../../core/src/entities';
|
||||
|
||||
import sqlite3 from 'sqlite3';
|
||||
import { log, logDebug, logError } from '@/common/utils/log';
|
||||
import { NTQQMsgApi } from '@/core';
|
||||
import LRU from '@/common/utils/LRUCache';
|
||||
|
||||
export interface IRember {
|
||||
last_sent_time: number;
|
||||
join_time: number;
|
||||
user_id: number;
|
||||
}
|
||||
|
||||
|
||||
type DBMsg = {
|
||||
id: number,
|
||||
shortId: number,
|
||||
longId: string,
|
||||
seq: number,
|
||||
peerUid: string,
|
||||
chatType: number,
|
||||
}
|
||||
|
||||
type DBFile = {
|
||||
name: string; // 文件名
|
||||
path: string;
|
||||
url: string;
|
||||
size: number;
|
||||
uuid: string;
|
||||
msgId: string;
|
||||
elementId: string;
|
||||
element: PicElement | VideoElement | FileElement | PttElement;
|
||||
elementType: ElementType.PIC | ElementType.VIDEO | ElementType.FILE | ElementType.PTT;
|
||||
}
|
||||
|
||||
|
||||
class DBUtilBase {
|
||||
protected db: sqlite3.Database | undefined;
|
||||
|
||||
async init(dbPath: string) {
|
||||
if (this.db) {
|
||||
return;
|
||||
}
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
this.db = new sqlite3.Database(dbPath, sqlite3.OPEN_READWRITE | sqlite3.OPEN_CREATE, (err) => {
|
||||
if (err) {
|
||||
logError('Could not connect to database', err);
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
this.createTable();
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
protected createTable() {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
close() {
|
||||
this.db?.close();
|
||||
}
|
||||
}
|
||||
|
||||
class DBUtil extends DBUtilBase {
|
||||
private msgCache: Map<string | number, RawMessage> = new Map<string | number, RawMessage>();
|
||||
private globalMsgShortId = -2147483640;
|
||||
private groupIds: number[] = [];
|
||||
private LURCache = new LRU<number>();
|
||||
private LastSentCache = new (class {
|
||||
private cache: { gid: number; uid: number }[] = [];
|
||||
private maxSize: number;
|
||||
|
||||
constructor(maxSize: number = 50000) {
|
||||
this.maxSize = maxSize;
|
||||
}
|
||||
|
||||
get(gid: number, uid: number): boolean {
|
||||
const exists = this.cache.some(
|
||||
(entry) => entry.gid === gid && entry.uid === uid
|
||||
);
|
||||
if (!exists) {
|
||||
this.cache.push({ gid, uid });
|
||||
if (this.cache.length > this.maxSize) {
|
||||
this.cache.shift();
|
||||
}
|
||||
}
|
||||
|
||||
return exists;
|
||||
}
|
||||
})();
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
const interval = 1000 * 60 * 10; // 10分钟清理一次缓存
|
||||
setInterval(() => {
|
||||
logDebug('清理消息缓存');
|
||||
this.msgCache.forEach((msg, key) => {
|
||||
if ((Date.now() - parseInt(msg.msgTime) * 1000) > interval) {
|
||||
this.msgCache.delete(key);
|
||||
}
|
||||
});
|
||||
}, interval);
|
||||
}
|
||||
|
||||
async init(dbPath: string) {
|
||||
await super.init(dbPath);
|
||||
this.globalMsgShortId = await this.getCurrentMaxShortId();
|
||||
|
||||
|
||||
// 初始化群缓存列表
|
||||
this.db!.serialize(() => {
|
||||
const sql = 'SELECT * FROM sqlite_master WHERE type=\'table\'';
|
||||
this.db!.all(sql, [], (err, rows: { name: string }[]) => {
|
||||
if (err) return logError(err);
|
||||
rows.forEach((row) => this.groupIds.push(parseInt(row.name)));
|
||||
//logDebug(`已加载 ${groupIds.length} 个群`);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
this.LURCache.on(async (nodeObject) => {
|
||||
|
||||
Object.entries(nodeObject).forEach(async ([_groupId, datas]) => {
|
||||
const userIds = datas.map(v => v.userId);
|
||||
const groupId = Number(_groupId);
|
||||
logDebug('插入发言时间', _groupId);
|
||||
|
||||
await this.createGroupInfoTimeTableIfNotExist(groupId);
|
||||
|
||||
const needCreatUsers = await this.getNeedCreatList(groupId, userIds);
|
||||
const updateList = needCreatUsers.length > 0 ? datas.filter(user => !needCreatUsers.includes(user.userId)) : datas;
|
||||
const insertList = needCreatUsers.map(userId => datas.find(e => userId == e.userId)!);
|
||||
|
||||
logDebug('updateList', updateList);
|
||||
logDebug('insertList', insertList);
|
||||
|
||||
if (insertList.length) {
|
||||
const insertSql = `INSERT INTO "${groupId}" (last_sent_time, user_id) VALUES ${insertList.map(() => '(?, ?)').join(', ')};`;
|
||||
|
||||
this.db!.all(insertSql, insertList.map(v => [v.value, v.userId]).flat(), err => {
|
||||
if (err) {
|
||||
logError(`群 ${groupId} 插入失败`);
|
||||
logError(`更新Sql : ${insertSql}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (updateList.length) {
|
||||
const updateSql =
|
||||
`UPDATE "${groupId}" SET last_sent_time = CASE ` +
|
||||
updateList.map(v => `WHEN user_id = ${v.userId} THEN ${v.value}`).join(' ') +
|
||||
' ELSE last_sent_time END WHERE user_id IN ' +
|
||||
`(${updateList.map(v => v.userId).join(', ')});`;
|
||||
|
||||
this.db!.all(updateSql, [], err => {
|
||||
if (err) {
|
||||
logError(`群 ${groupId} 跟新失败`);
|
||||
logError(`更新Sql : ${updateSql}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
}
|
||||
async getNeedCreatList(groupId: number, userIds: number[]) {
|
||||
|
||||
// 获取缓存中没有的
|
||||
const unhas = userIds.filter(userId => !this.LastSentCache.get(groupId, userId));
|
||||
|
||||
if (unhas.length == 0) {
|
||||
logDebug('缓存全部命中');
|
||||
return [];
|
||||
}
|
||||
|
||||
logDebug('缓存未全部命中');
|
||||
|
||||
const sql = `SELECT * FROM "${groupId}" WHERE user_id IN (${unhas.map(() => '?').join(',')})`;
|
||||
|
||||
return new Promise<number[]>((resolve) => {
|
||||
this.db!.all(sql, unhas, (err, rows: { user_id: number }[]) => {
|
||||
const has = rows.map(v => v.user_id);
|
||||
const needCreatUsers = unhas.filter(userId => !has.includes(userId));
|
||||
|
||||
if (needCreatUsers.length == 0) {
|
||||
logDebug('数据库全部命中');
|
||||
} else {
|
||||
logDebug('数据库未全部命中');
|
||||
}
|
||||
|
||||
resolve(needCreatUsers);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
async createGroupInfoTimeTableIfNotExist(groupId: number) {
|
||||
const createTableSQL = (groupId: number) =>
|
||||
`CREATE TABLE IF NOT EXISTS "${groupId}" (
|
||||
user_id INTEGER,
|
||||
last_sent_time INTEGER,
|
||||
join_time INTEGER,
|
||||
PRIMARY KEY (user_id)
|
||||
);`;
|
||||
|
||||
if (this.groupIds.includes(groupId)) {
|
||||
return;
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
const sql = createTableSQL(groupId);
|
||||
this.db!.all(sql, (err) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
this.groupIds.push(groupId);
|
||||
resolve(true);
|
||||
});
|
||||
});
|
||||
}
|
||||
protected createTable() {
|
||||
// 消息记录
|
||||
const createTableSQL = `
|
||||
CREATE TABLE IF NOT EXISTS msgs (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
shortId INTEGER NOT NULL UNIQUE,
|
||||
longId TEXT NOT NULL UNIQUE,
|
||||
seq INTEGER NOT NULL,
|
||||
peerUid TEXT NOT NULL,
|
||||
chatType INTEGER NOT NULL
|
||||
)`;
|
||||
this.db!.run(createTableSQL, function (err) {
|
||||
if (err) {
|
||||
logError('Could not create table msgs', err.stack);
|
||||
}
|
||||
});
|
||||
|
||||
// 文件缓存
|
||||
const createFileTableSQL = `
|
||||
CREATE TABLE IF NOT EXISTS files (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
path TEXT NOT NULL,
|
||||
url TEXT,
|
||||
size INTEGER NOT NULL,
|
||||
uuid TEXT,
|
||||
elementType INTEGER,
|
||||
element TEXT NOT NULL,
|
||||
elementId TEXT NOT NULL,
|
||||
msgId TEXT NOT NULL
|
||||
)`;
|
||||
this.db!.run(createFileTableSQL, function (err) {
|
||||
if (err) {
|
||||
logError('Could not create table files', err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private async getCurrentMaxShortId() {
|
||||
return new Promise<number>((resolve, reject) => {
|
||||
this.db!.get('SELECT MAX(shortId) as maxId FROM msgs', (err, row: { maxId: number }) => {
|
||||
if (err) {
|
||||
logDebug('Could not get max short id, Use default -2147483640', err);
|
||||
return resolve(-2147483640);
|
||||
}
|
||||
logDebug('数据库中消息最大短id', row?.maxId);
|
||||
resolve(row?.maxId ?? -2147483640);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private async getMsg(query: string, params: any[]) {
|
||||
const stmt = this.db!.prepare(query);
|
||||
return new Promise<RawMessage | null>((resolve, reject) => {
|
||||
stmt.get(...params, (err: any, row: DBMsg) => {
|
||||
// log("getMsg", row, err);
|
||||
if (err) {
|
||||
logError('Could not get msg', err, query, params);
|
||||
return resolve(null);
|
||||
}
|
||||
if (!row) {
|
||||
// logDebug('不存在数据库中的消息,不进行处理', query, params);
|
||||
resolve(null);
|
||||
return;
|
||||
}
|
||||
const msgId = row.longId;
|
||||
NTQQMsgApi.getMsgsByMsgId({ peerUid: row.peerUid, chatType: row.chatType }, [msgId]).then(res => {
|
||||
const msg = res.msgList[0];
|
||||
if (!msg) {
|
||||
resolve(null);
|
||||
return;
|
||||
}
|
||||
msg.id = row.shortId;
|
||||
resolve(msg);
|
||||
}).catch(e => {
|
||||
resolve(null);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async getMsgByShortId(shortId: number): Promise<RawMessage | null> {
|
||||
if (this.msgCache.has(shortId)) {
|
||||
return this.msgCache.get(shortId)!;
|
||||
}
|
||||
const getStmt = 'SELECT * FROM msgs WHERE shortId = ?';
|
||||
return this.getMsg(getStmt, [shortId]);
|
||||
}
|
||||
|
||||
async getMsgByLongId(longId: string): Promise<RawMessage | null> {
|
||||
if (this.msgCache.has(longId)) {
|
||||
return this.msgCache.get(longId)!;
|
||||
}
|
||||
return this.getMsg('SELECT * FROM msgs WHERE longId = ?', [longId]);
|
||||
}
|
||||
|
||||
async getMsgBySeq(peerUid: string, seq: string): Promise<RawMessage | null> {
|
||||
const stmt = 'SELECT * FROM msgs WHERE peerUid = ? AND seq = ?';
|
||||
return this.getMsg(stmt, [peerUid, seq]);
|
||||
}
|
||||
|
||||
async addMsg(msg: RawMessage, update = true): Promise<number> {
|
||||
const existMsg = await this.getMsgByLongId(msg.msgId);
|
||||
if (existMsg) {
|
||||
// logDebug('消息已存在,更新数据库', msg.msgId);
|
||||
if (update) this.updateMsg(msg).then();
|
||||
return existMsg.id!;
|
||||
}
|
||||
const stmt = this.db!.prepare('INSERT INTO msgs (shortId, longId, seq, peerUid, chatType) VALUES (?, ?, ?, ?, ?)');
|
||||
// const runAsync = promisify(stmt.run.bind(stmt));
|
||||
const shortId = ++this.globalMsgShortId;
|
||||
msg.id = shortId;
|
||||
//logDebug(`记录消息到数据库, 消息长id: ${msg.msgId}, 短id: ${msg.id}`);
|
||||
this.msgCache.set(shortId, msg);
|
||||
this.msgCache.set(msg.msgId, msg);
|
||||
stmt.run(this.globalMsgShortId, msg.msgId, msg.msgSeq.toString(), msg.peerUid, msg.chatType, (err: any) => {
|
||||
if (err) {
|
||||
if (err.errno === 19) {
|
||||
this.getMsgByLongId(msg.msgId).then((msg: RawMessage | null) => {
|
||||
if (msg) {
|
||||
this.msgCache.set(shortId, msg);
|
||||
this.msgCache.set(msg.msgId, msg);
|
||||
// logDebug('获取消息短id成功', msg.id);
|
||||
} else {
|
||||
logError('db could not get msg by long id', err);
|
||||
}
|
||||
}).catch(e => logError('db getMsgByLongId error', e));
|
||||
} else {
|
||||
logError('db could not add msg', err);
|
||||
}
|
||||
}
|
||||
});
|
||||
return shortId;
|
||||
}
|
||||
|
||||
async updateMsg(msg: RawMessage) {
|
||||
const existMsg = this.msgCache.get(msg.msgId);
|
||||
if (existMsg) {
|
||||
Object.assign(existMsg, msg);
|
||||
}
|
||||
//logDebug(`更新消息, shortId:${msg.id}, seq: ${msg.msgSeq}, msgId: ${msg.msgId}`);
|
||||
const stmt = this.db!.prepare('UPDATE msgs SET seq=? WHERE longId=?');
|
||||
stmt.run(msg.msgSeq, msg.msgId, (err: any) => {
|
||||
if (err) {
|
||||
logError('updateMsg db error', err);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
async addFileCache(file: DBFile) {
|
||||
const stmt = this.db!.prepare('INSERT INTO files (name, path, url, size, uuid, elementType ,element, elementId, msgId) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)');
|
||||
return new Promise((resolve, reject) => {
|
||||
stmt.run(file.name, file.path, file.url, file.size, file.uuid,
|
||||
file.elementType,
|
||||
JSON.stringify(file.element),
|
||||
file.elementId,
|
||||
file.msgId,
|
||||
function (err: any) {
|
||||
if (err) {
|
||||
logError('db could not add file', err);
|
||||
reject(err);
|
||||
}
|
||||
resolve(null);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private async getFileCache(query: string, params: any[]) {
|
||||
const stmt = this.db!.prepare(query);
|
||||
return new Promise<DBFile | null>((resolve, reject) => {
|
||||
stmt.get(...params, (err: any, row: DBFile & { element: string }) => {
|
||||
if (err) {
|
||||
logError('db could not get file cache', err);
|
||||
reject(err);
|
||||
}
|
||||
if (row) {
|
||||
row.element = JSON.parse(row.element);
|
||||
}
|
||||
resolve(row);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async getFileCacheByName(name: string): Promise<DBFile | null> {
|
||||
return this.getFileCache('SELECT * FROM files WHERE name = ?', [name]);
|
||||
}
|
||||
|
||||
async getFileCacheByUuid(uuid: string): Promise<DBFile | null> {
|
||||
return this.getFileCache('SELECT * FROM files WHERE uuid = ?', [uuid]);
|
||||
}
|
||||
|
||||
// todo: 是否所有的文件都有uuid?语音消息有没有uuid?
|
||||
async updateFileCache(file: DBFile) {
|
||||
const stmt = this.db!.prepare('UPDATE files SET path = ?, url = ? WHERE uuid = ?');
|
||||
return new Promise((resolve, reject) => {
|
||||
stmt.run(file.path, file.url, file.uuid, function (err: any) {
|
||||
if (err) {
|
||||
logError('db could not update file cache', err);
|
||||
reject(err);
|
||||
}
|
||||
resolve(null);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async getLastSentTimeAndJoinTime(
|
||||
groupId: number
|
||||
): Promise<IRember[]> {
|
||||
logDebug('读取发言时间', groupId);
|
||||
return new Promise<IRember[]>((resolve, reject) => {
|
||||
this.db!.all(`SELECT * FROM "${groupId}" `, (err, rows: IRember[]) => {
|
||||
const cache = this.LURCache.get(groupId).map(e => ({ user_id: e.userId, last_sent_time: e.value }));
|
||||
if (err) {
|
||||
logError('查询发言时间失败', groupId);
|
||||
return resolve(cache.map(e => ({ ...e, join_time: 0 })));
|
||||
}
|
||||
Object.assign(rows, cache);
|
||||
logDebug('查询发言时间成功', groupId, rows);
|
||||
resolve(rows);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
insertLastSentTime(
|
||||
groupId: number,
|
||||
userId: number,
|
||||
time: number
|
||||
) {
|
||||
this.LURCache.set(groupId, userId, time);
|
||||
}
|
||||
async insertJoinTime(
|
||||
groupId: number,
|
||||
userId: number,
|
||||
time: number
|
||||
) {
|
||||
await this.createGroupInfoTimeTableIfNotExist(groupId);
|
||||
this.db!.all(
|
||||
`INSERT OR REPLACE INTO "${groupId}" (user_id, last_sent_time, join_time) VALUES (?,?,?)`,
|
||||
[userId, time, time],
|
||||
(err) => {
|
||||
if (err)
|
||||
logError(err),
|
||||
Promise.reject(),
|
||||
logError('插入入群时间失败', userId, groupId);
|
||||
}
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export const dbUtil = new DBUtil();
|
@@ -4,9 +4,8 @@ import crypto from 'crypto';
|
||||
import util from 'util';
|
||||
import path from 'node:path';
|
||||
import { log, logError } from './log';
|
||||
import { dbUtil } from '@/common/utils/db';
|
||||
import * as fileType from 'file-type';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { randomUUID } from 'crypto';
|
||||
import { napCatCore } from '@/core';
|
||||
|
||||
export const getNapCatDir = () => {
|
||||
@@ -192,7 +191,7 @@ export async function uri2local(uri: string, fileName: string | null = null): Pr
|
||||
isLocal: false
|
||||
};
|
||||
if (!fileName) {
|
||||
fileName = uuidv4();
|
||||
fileName = randomUUID();
|
||||
}
|
||||
let filePath = path.join(getTempDir(), fileName);
|
||||
let url = null;
|
||||
@@ -235,7 +234,7 @@ export async function uri2local(uri: string, fileName: string | null = null): Pr
|
||||
}
|
||||
fileName = fileName.replace(/[/\\:*?"<>|]/g, '_');
|
||||
res.fileName = fileName;
|
||||
filePath = path.join(getTempDir(), uuidv4() + fileName);
|
||||
filePath = path.join(getTempDir(), randomUUID() + fileName);
|
||||
fs.writeFileSync(filePath, buffer);
|
||||
} catch (e: any) {
|
||||
res.errMsg = `${url}下载失败,` + e.toString();
|
||||
@@ -251,13 +250,14 @@ export async function uri2local(uri: string, fileName: string | null = null): Pr
|
||||
} else {
|
||||
filePath = pathname;
|
||||
}
|
||||
} else {
|
||||
const cache = await dbUtil.getFileCacheByName(uri);
|
||||
if (cache) {
|
||||
filePath = cache.path;
|
||||
} else {
|
||||
filePath = uri;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// const cache = await dbUtil.getFileCacheByName(uri);
|
||||
// if (cache) {
|
||||
// filePath = cache.path;
|
||||
// } else {
|
||||
// filePath = uri;
|
||||
// }
|
||||
}
|
||||
|
||||
res.isLocal = true;
|
||||
|
@@ -8,10 +8,50 @@ import * as fsPromise from 'node:fs/promises';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
//下面这个类是用于将uid+msgid合并的类
|
||||
export class UUIDConverter {
|
||||
static encode(highStr: string, lowStr: string): string {
|
||||
const high = BigInt(highStr);
|
||||
const low = BigInt(lowStr);
|
||||
const highHex = high.toString(16).padStart(16, '0');
|
||||
const lowHex = low.toString(16).padStart(16, '0');
|
||||
const combinedHex = highHex + lowHex;
|
||||
const uuid = `${combinedHex.substring(0, 8)}-${combinedHex.substring(8, 12)}-${combinedHex.substring(12, 16)}-${combinedHex.substring(16, 20)}-${combinedHex.substring(20)}`;
|
||||
return uuid;
|
||||
}
|
||||
static decode(uuid: string): { high: string, low: string } {
|
||||
const hex = uuid.replace(/-/g, '');
|
||||
const high = BigInt('0x' + hex.substring(0, 16));
|
||||
const low = BigInt('0x' + hex.substring(16));
|
||||
return { high: high.toString(), low: low.toString() };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export function sleep(ms: number): Promise<void> {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
export function PromiseTimer<T>(promise: Promise<T>, ms: number): Promise<T> {
|
||||
const timeoutPromise = new Promise<T>((_, reject) =>
|
||||
setTimeout(() => reject(new Error('PromiseTimer: Operation timed out')), ms)
|
||||
);
|
||||
return Promise.race([promise, timeoutPromise]);
|
||||
}
|
||||
export async function runAllWithTimeout<T>(tasks: Promise<T>[], timeout: number): Promise<T[]> {
|
||||
const wrappedTasks = tasks.map(task =>
|
||||
PromiseTimer(task, timeout).then(
|
||||
result => ({ status: 'fulfilled', value: result }),
|
||||
error => ({ status: 'rejected', reason: error })
|
||||
)
|
||||
);
|
||||
const results = await Promise.all(wrappedTasks);
|
||||
return results
|
||||
.filter(result => result.status === 'fulfilled')
|
||||
.map(result => (result as { status: 'fulfilled'; value: T }).value);
|
||||
}
|
||||
|
||||
export function getMd5(s: string) {
|
||||
|
||||
const h = crypto.createHash('md5');
|
||||
@@ -89,7 +129,36 @@ export function CacheClassFuncAsync(ttl: number = 3600 * 1000, customKey: string
|
||||
}
|
||||
return logExecutionTime;
|
||||
}
|
||||
|
||||
export function CacheClassFuncAsyncExtend(ttl: number = 3600 * 1000, customKey: string = '', checker: any = (...data: any[]) => { return true; }) {
|
||||
//console.log('CacheClassFuncAsync', ttl, customKey);
|
||||
function logExecutionTime(target: any, methodName: string, descriptor: PropertyDescriptor) {
|
||||
//console.log('logExecutionTime', target, methodName, descriptor);
|
||||
const cache = new Map<string, { expiry: number; value: any }>();
|
||||
const originalMethod = descriptor.value;
|
||||
descriptor.value = async function (...args: any[]) {
|
||||
const key = `${customKey}${String(methodName)}.(${args.map(arg => JSON.stringify(arg)).join(', ')})`;
|
||||
cache.forEach((value, key) => {
|
||||
if (value.expiry < Date.now()) {
|
||||
cache.delete(key);
|
||||
}
|
||||
});
|
||||
const cachedValue = cache.get(key);
|
||||
if (cachedValue && cachedValue.expiry > Date.now()) {
|
||||
return cachedValue.value;
|
||||
}
|
||||
// const start = Date.now();
|
||||
const result = await originalMethod.apply(this, args);
|
||||
if (!checker(...args, result)) {
|
||||
return result;//丢弃缓存
|
||||
}
|
||||
// const end = Date.now();
|
||||
// console.log(`Method ${methodName} executed in ${end - start} ms.`);
|
||||
cache.set(key, { expiry: Date.now() + ttl, value: result });
|
||||
return result;
|
||||
};
|
||||
}
|
||||
return logExecutionTime;
|
||||
}
|
||||
// export function CacheClassFuncAsync(ttl: number = 3600 * 1000, customKey: string = ''): any {
|
||||
// const cache = new Map<string, { expiry: number; value: any }>();
|
||||
|
||||
|
@@ -91,8 +91,10 @@ export function enableConsoleLog(enable: boolean) {
|
||||
function formatMsg(msg: any[]) {
|
||||
let logMsg = '';
|
||||
for (const msgItem of msg) {
|
||||
// 判断是否是对象
|
||||
if (typeof msgItem === 'object') {
|
||||
if (msgItem instanceof Error) { // 判断是否是错误
|
||||
logMsg += msgItem.stack + ' ';
|
||||
continue;
|
||||
} else if (typeof msgItem === 'object') { // 判断是否是对象
|
||||
const obj = JSON.parse(JSON.stringify(msgItem, null, 2));
|
||||
logMsg += JSON.stringify(truncateString(obj)) + ' ';
|
||||
continue;
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
import { networkInterfaces } from 'os';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { randomUUID } from 'crypto';
|
||||
|
||||
// 缓解Win7设备兼容性问题
|
||||
let osName: string;
|
||||
@@ -30,7 +30,7 @@ export async function getMachineId(): Promise<string> {
|
||||
if (!machineId) {
|
||||
machineId = (async () => {
|
||||
const id = await getMacMachineId();
|
||||
return id || uuidv4(); // fallback, generate a UUID
|
||||
return id || randomUUID(); // fallback, generate a UUID
|
||||
})();
|
||||
}
|
||||
|
||||
|
@@ -4,10 +4,10 @@ export async function checkVersion(): Promise<string> {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const MirrorList =
|
||||
[
|
||||
'https://jsd.cdn.zzko.cn/gh/NapNeko/NapCatQQ@main/package.json',
|
||||
'https://fastly.jsdelivr.net/gh/NapNeko/NapCatQQ@main/package.json',
|
||||
'https://gcore.jsdelivr.net/gh/NapNeko/NapCatQQ@main/package.json',
|
||||
'https://cdn.jsdelivr.us/gh/NapNeko/NapCatQQ@main/package.json',
|
||||
'https://jsd.cdn.zzko.cn/gh/NapNeko/NapCatQQ@main/package.json'
|
||||
'https://cdn.jsdelivr.net/gh/NapNeko/NapCatQQ@main/package.json'
|
||||
];
|
||||
let version = undefined;
|
||||
for (const url of MirrorList) {
|
||||
|
File diff suppressed because one or more lines are too long
@@ -1,12 +1,9 @@
|
||||
import {
|
||||
CacheFileList,
|
||||
CacheFileListItem,
|
||||
CacheFileType,
|
||||
CacheScanResult,
|
||||
ChatCacheList,
|
||||
ChatCacheListItemBasic,
|
||||
ChatType,
|
||||
ElementType, IMAGE_HTTP_HOST, IMAGE_HTTP_HOST_NT, RawMessage
|
||||
ElementType, IMAGE_HTTP_HOST, IMAGE_HTTP_HOST_NT, Peer, PicElement, RawMessage
|
||||
} from '@/core/entities';
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
@@ -15,28 +12,15 @@ import { log, logDebug, logError } from '@/common/utils/log';
|
||||
import { GeneralCallResult, napCatCore, OnRichMediaDownloadCompleteParams } from '@/core';
|
||||
import { calculateFileMD5 } from '@/common/utils/file';
|
||||
import * as fileType from 'file-type';
|
||||
import { MsgListener } from '@/core/listeners';
|
||||
import imageSize from 'image-size';
|
||||
import { ISizeCalculationResult } from 'image-size/dist/types/interface';
|
||||
import { sessionConfig } from '@/core/sessionConfig';
|
||||
import { randomUUID } from 'crypto';
|
||||
import { rkeyManager } from '../utils/rkey';
|
||||
import { NTEventDispatch } from '@/common/utils/EventTask';
|
||||
import { NodeIKernelSearchService } from '../services/NodeIKernelSearchService';
|
||||
import { selfInfo } from '../data';
|
||||
|
||||
|
||||
const downloadMediaTasks: Map<string, (arg: OnRichMediaDownloadCompleteParams) => void> = new Map<string, (arg: OnRichMediaDownloadCompleteParams) => void>();
|
||||
|
||||
const downloadMediaListener = new MsgListener();
|
||||
downloadMediaListener.onRichMediaDownloadComplete = arg => {
|
||||
for (const [uuid, cb] of downloadMediaTasks) {
|
||||
cb(arg);
|
||||
downloadMediaTasks.delete(uuid);
|
||||
}
|
||||
};
|
||||
setTimeout(() => {
|
||||
napCatCore.onLoginSuccess(() => {
|
||||
napCatCore.addListener(downloadMediaListener);
|
||||
});
|
||||
}, 100);
|
||||
export class NTQQFileApi {
|
||||
static async getFileType(filePath: string) {
|
||||
return fileType.fileTypeFromFile(filePath);
|
||||
@@ -49,12 +33,8 @@ export class NTQQFileApi {
|
||||
static async getFileSize(filePath: string): Promise<number> {
|
||||
return await napCatCore.util.getFileSize(filePath);
|
||||
}
|
||||
static async getVideoUrl(msg: RawMessage, element: any) {
|
||||
return (await napCatCore.session.getRichMediaService().getVideoPlayUrlV2({
|
||||
chatType: msg.chatType,
|
||||
peerUid: msg.peerUid,
|
||||
guildId: '0'
|
||||
}, msg.msgId, element.elementId, 0, { downSourceType: 1, triggerType: 1 })).urlResult.domainUrl[0].url;
|
||||
static async getVideoUrl(peer: Peer, msgId: string, elementId: string) {
|
||||
return (await napCatCore.session.getRichMediaService().getVideoPlayUrlV2(peer, msgId, elementId, 0, { downSourceType: 1, triggerType: 1 })).urlResult.domainUrl;
|
||||
}
|
||||
// 上传文件到QQ的文件夹
|
||||
static async uploadFile(filePath: string, elementType: ElementType = ElementType.PIC, elementSubType: number = 0) {
|
||||
@@ -88,7 +68,9 @@ export class NTQQFileApi {
|
||||
ext
|
||||
};
|
||||
}
|
||||
|
||||
static async downloadMediaByUuid() {
|
||||
//napCatCore.session.getRichMediaService().downloadFileForFileUuid();
|
||||
}
|
||||
static async downloadMedia(msgId: string, chatType: ChatType, peerUid: string, elementId: string, thumbPath: string, sourcePath: string, timeout = 1000 * 60 * 2, force: boolean = false) {
|
||||
//logDebug('receive downloadMedia task', msgId, chatType, peerUid, elementId, thumbPath, sourcePath, timeout, force);
|
||||
// 用于下载收到的消息中的图片等
|
||||
@@ -103,31 +85,33 @@ export class NTQQFileApi {
|
||||
return sourcePath;
|
||||
}
|
||||
}
|
||||
//logDebug('start downloadMedia', msgId, chatType, peerUid, elementId, thumbPath, sourcePath, timeout, force);
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
let completed = false;
|
||||
const cb = (arg: OnRichMediaDownloadCompleteParams) => {
|
||||
//logDebug('downloadMedia complete', arg, msgId);
|
||||
let data = await NTEventDispatch.CallNormalEvent<
|
||||
(
|
||||
params: {
|
||||
fileModelId: string,
|
||||
downloadSourceType: number,
|
||||
triggerType: number,
|
||||
msgId: string,
|
||||
chatType: ChatType,
|
||||
peerUid: string,
|
||||
elementId: string,
|
||||
thumbSize: number,
|
||||
downloadType: number,
|
||||
filePath: string
|
||||
}) => Promise<unknown>,
|
||||
(fileTransNotifyInfo: OnRichMediaDownloadCompleteParams) => void
|
||||
>(
|
||||
'NodeIKernelMsgService/downloadRichMedia',
|
||||
'NodeIKernelMsgListener/onRichMediaDownloadComplete',
|
||||
1,
|
||||
timeout,
|
||||
(arg: OnRichMediaDownloadCompleteParams) => {
|
||||
if (arg.msgId === msgId) {
|
||||
completed = true;
|
||||
let filePath = arg.filePath;
|
||||
if (filePath.startsWith('\\')) {
|
||||
// log('filePath start with \\');
|
||||
const downloadPath = sessionConfig.defaultFileDownloadPath;
|
||||
//logDebug('downloadPath', downloadPath);
|
||||
filePath = path.join(downloadPath, filePath);
|
||||
// 下载路径是下载文件夹的相对路径
|
||||
}
|
||||
resolve(filePath);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
downloadMediaTasks.set(randomUUID(), cb);
|
||||
setTimeout(() => {
|
||||
if (!completed) {
|
||||
reject('下载超时');
|
||||
}
|
||||
}, timeout);
|
||||
napCatCore.session.getMsgService().downloadRichMedia({
|
||||
return false;
|
||||
},
|
||||
{
|
||||
fileModelId: '0',
|
||||
downloadSourceType: 0,
|
||||
triggerType: 1,
|
||||
@@ -137,9 +121,18 @@ export class NTQQFileApi {
|
||||
elementId: elementId,
|
||||
thumbSize: 0,
|
||||
downloadType: 1,
|
||||
filePath: thumbPath,
|
||||
});
|
||||
});
|
||||
filePath: thumbPath
|
||||
}
|
||||
);
|
||||
let filePath = data[1].filePath;
|
||||
if (filePath.startsWith('\\')) {
|
||||
// log('filePath start with \\');
|
||||
const downloadPath = sessionConfig.defaultFileDownloadPath;
|
||||
//logDebug('downloadPath', downloadPath);
|
||||
filePath = path.join(downloadPath, filePath);
|
||||
// 下载路径是下载文件夹的相对路径
|
||||
}
|
||||
return filePath;
|
||||
}
|
||||
|
||||
static async getImageSize(filePath: string): Promise<ISizeCalculationResult | undefined> {
|
||||
@@ -153,27 +146,137 @@ export class NTQQFileApi {
|
||||
});
|
||||
});
|
||||
}
|
||||
static async addFileCache(peer: Peer, msgId: string, msgSeq: string, senderUid: string, elemId: string, elemType: string, fileSize: string, fileName: string) {
|
||||
let GroupData;
|
||||
let BuddyData;
|
||||
if (peer.chatType === ChatType.group) {
|
||||
GroupData =
|
||||
[{
|
||||
groupCode: peer.peerUid,
|
||||
isConf: false,
|
||||
hasModifyConfGroupFace: true,
|
||||
hasModifyConfGroupName: true,
|
||||
groupName: "NapCat.Cached",
|
||||
remark: "NapCat.Cached"
|
||||
}];
|
||||
} else if (peer.chatType === ChatType.friend) {
|
||||
BuddyData = [{
|
||||
category_name: 'NapCat.Cached',
|
||||
peerUid: peer.peerUid,
|
||||
peerUin: peer.peerUid,
|
||||
remark: 'NapCat.Cached'
|
||||
}];
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
static async getImageUrl(element: { originImageUrl: any; md5HexStr?: any; fileUuid: any; }, isPrivateImage: boolean) {
|
||||
|
||||
return napCatCore.session.getSearchService().addSearchHistory({
|
||||
type: 4,
|
||||
contactList: [],
|
||||
id: -1,
|
||||
groupInfos: [],
|
||||
msgs: [],
|
||||
fileInfos: [
|
||||
{
|
||||
chatType: peer.chatType,
|
||||
buddyChatInfo: BuddyData || [],
|
||||
discussChatInfo: [],
|
||||
groupChatInfo: GroupData || [],
|
||||
dataLineChatInfo: [],
|
||||
tmpChatInfo: [],
|
||||
msgId: msgId,
|
||||
msgSeq: msgSeq,
|
||||
msgTime: Math.floor(Date.now() / 1000).toString(),
|
||||
senderUid: senderUid,
|
||||
senderNick: 'NapCat.Cached',
|
||||
senderRemark: 'NapCat.Cached',
|
||||
senderCard: 'NapCat.Cached',
|
||||
elemId: elemId,
|
||||
elemType: elemType,
|
||||
fileSize: fileSize,
|
||||
filePath: '',
|
||||
fileName: fileName,
|
||||
hits: [{
|
||||
start: 12,
|
||||
end: 14
|
||||
}]
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
static async searchfile(keys: string[]) {
|
||||
type EventType = NodeIKernelSearchService['searchFileWithKeywords'];
|
||||
interface OnListener {
|
||||
searchId: string,
|
||||
hasMore: boolean,
|
||||
resultItems: {
|
||||
chatType: ChatType,
|
||||
buddyChatInfo: any[],
|
||||
discussChatInfo: any[],
|
||||
groupChatInfo:
|
||||
{
|
||||
groupCode: string,
|
||||
isConf: boolean,
|
||||
hasModifyConfGroupFace: boolean,
|
||||
hasModifyConfGroupName: boolean,
|
||||
groupName: string,
|
||||
remark: string
|
||||
}[]
|
||||
,
|
||||
dataLineChatInfo: any[],
|
||||
tmpChatInfo: any[],
|
||||
msgId: string,
|
||||
msgSeq: string,
|
||||
msgTime: string,
|
||||
senderUid: string,
|
||||
senderNick: string,
|
||||
senderRemark: string,
|
||||
senderCard: string,
|
||||
elemId: string,
|
||||
elemType: number,
|
||||
fileSize: string,
|
||||
filePath: string,
|
||||
fileName: string,
|
||||
hits:
|
||||
{
|
||||
start: number,
|
||||
end: number
|
||||
}[]
|
||||
}[]
|
||||
};
|
||||
const Event = await NTEventDispatch.CreatEventFunction<EventType>('NodeIKernelSearchService/searchFileWithKeywords');
|
||||
let id = '';
|
||||
const Listener = NTEventDispatch.RegisterListen<(params: OnListener) => void>('NodeIKernelSearchListener/onSearchFileKeywordsResult', 1, 20000, (params) => {
|
||||
if (id !== '' && params.searchId == id) {
|
||||
return true
|
||||
}
|
||||
return false;
|
||||
});
|
||||
id = await Event!(keys, 12);
|
||||
let [ret] = (await Listener);
|
||||
return ret;
|
||||
}
|
||||
static async getImageUrl(element: PicElement) {
|
||||
if (!element) {
|
||||
return '';
|
||||
}
|
||||
const url = element.originImageUrl; // 没有域名
|
||||
const url: string = element.originImageUrl!; // 没有域名
|
||||
const md5HexStr = element.md5HexStr;
|
||||
const fileMd5 = element.md5HexStr;
|
||||
const fileUuid = element.fileUuid;
|
||||
|
||||
if (url) {
|
||||
if (url.startsWith('/download')) {
|
||||
if (url.includes('&rkey=')) {
|
||||
let UrlParse = new URL(IMAGE_HTTP_HOST + url);//临时解析拼接
|
||||
let imageAppid = UrlParse.searchParams.get('appid');
|
||||
let isNewPic = imageAppid && ['1406', '1407'].includes(imageAppid);
|
||||
if (isNewPic) {
|
||||
let UrlRkey = UrlParse.searchParams.get('rkey');
|
||||
if (UrlRkey) {
|
||||
return IMAGE_HTTP_HOST_NT + url;
|
||||
}
|
||||
|
||||
const rkeyData = await rkeyManager.getRkey();
|
||||
|
||||
const existsRKey = isPrivateImage ? rkeyData.private_rkey : rkeyData.group_rkey;
|
||||
return IMAGE_HTTP_HOST_NT + url + `${existsRKey}`;
|
||||
UrlRkey = imageAppid === '1406' ? rkeyData.private_rkey : rkeyData.group_rkey;
|
||||
return IMAGE_HTTP_HOST_NT + url + `${UrlRkey}`;
|
||||
} else {
|
||||
// 老的图片url,不需要rkey
|
||||
return IMAGE_HTTP_HOST + url;
|
||||
|
@@ -14,6 +14,7 @@ export class NTQQFriendApi {
|
||||
'NodeIKernelBuddyListener/onBuddyListChange',
|
||||
1,
|
||||
5000,
|
||||
() => true,
|
||||
forced
|
||||
);
|
||||
const friends: User[] = [];
|
||||
|
@@ -1,12 +1,13 @@
|
||||
import { GroupMember, GroupRequestOperateTypes, GroupMemberRole, GroupNotify, Group, MemberExtSourceType, GroupNotifyTypes } from '../entities';
|
||||
import { GroupMember, GroupRequestOperateTypes, GroupMemberRole, GroupNotify, Group, MemberExtSourceType, GroupNotifyTypes, ChatType, Peer } from '../entities';
|
||||
import { GeneralCallResult, NTQQUserApi, napCatCore } from '@/core';
|
||||
import { NTEventDispatch } from '@/common/utils/EventTask';
|
||||
import { logDebug } from '@/common/utils/log';
|
||||
// console.log(process.pid);
|
||||
// setTimeout(async () => {
|
||||
// console.log(JSON.stringify(await NTQQGroupApi.getMemberExtInfo(), null, 2));
|
||||
// }, 20000);
|
||||
import { log } from '@/common/utils/log';
|
||||
import { groupMembers } from '../data';
|
||||
import { CacheClassFuncAsyncExtend, runAllWithTimeout } from '@/common/utils/helper';
|
||||
export class NTQQGroupApi {
|
||||
static async setGroupAvatar(gc: string, filePath: string) {
|
||||
return napCatCore.session.getGroupService().setHeader(gc, filePath);
|
||||
}
|
||||
static async getGroups(forced = false) {
|
||||
let [_retData, _updateType, groupList] = await NTEventDispatch.CallNormalEvent
|
||||
<(force: boolean) => Promise<any>, (updateType: number, groupList: Group[]) => void>
|
||||
@@ -15,10 +16,93 @@ export class NTQQGroupApi {
|
||||
'NodeIKernelGroupListener/onGroupListUpdate',
|
||||
1,
|
||||
5000,
|
||||
() => true,
|
||||
forced
|
||||
);
|
||||
return groupList;
|
||||
}
|
||||
|
||||
@CacheClassFuncAsyncExtend(600, "LastestSendTime", () => true)
|
||||
static async getGroupMemberLastestSendTimeCache(GroupCode: string) {
|
||||
return NTQQGroupApi.getGroupMemberLastestSendTime(GroupCode);
|
||||
}
|
||||
/**
|
||||
* 通过QQ自带数据库获取群成员最后发言时间(仅返回有效数据 且消耗延迟大 需要进行缓存)
|
||||
* @param GroupCode 群号
|
||||
* @returns Map<string, string> key: uin value: sendTime
|
||||
* @example
|
||||
* let ret = await NTQQGroupApi.getGroupMemberLastestSendTime('123456');
|
||||
* for (let [uin, sendTime] of ret) {
|
||||
* console.log(uin, sendTime);
|
||||
* }
|
||||
*/
|
||||
static async getGroupMemberLastestSendTime(GroupCode: string) {
|
||||
async function getdata(uid: string) {
|
||||
let NTRet = await NTQQGroupApi.getLastestMsgByUids(GroupCode, [uid]);
|
||||
if (NTRet.result != 0 && NTRet.msgList.length < 1) {
|
||||
return undefined;
|
||||
}
|
||||
return { sendUin: NTRet.msgList[0].senderUin, sendTime: NTRet.msgList[0].msgTime }
|
||||
}
|
||||
let currentGroupMembers = groupMembers.get(GroupCode);
|
||||
let PromiseData: Promise<({
|
||||
sendUin: string;
|
||||
sendTime: string;
|
||||
} | undefined)>[] = [];
|
||||
let ret: Map<string, string> = new Map();
|
||||
if (!currentGroupMembers) {
|
||||
return ret;
|
||||
}
|
||||
for (let member of currentGroupMembers.values()) {
|
||||
PromiseData.push(getdata(member.uid).catch(() => undefined));
|
||||
}
|
||||
let allRet = await runAllWithTimeout(PromiseData, 2500);
|
||||
for (let PromiseDo of allRet) {
|
||||
if (PromiseDo) {
|
||||
ret.set(PromiseDo.sendUin, PromiseDo.sendTime);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
static async getLastestMsgByUids(GroupCode: string, uids: string[]) {
|
||||
let ret = await napCatCore.session.getMsgService().queryMsgsWithFilterEx('0', '0', '0', {
|
||||
chatInfo: {
|
||||
peerUid: GroupCode,
|
||||
chatType: ChatType.group,
|
||||
},
|
||||
filterMsgType: [],
|
||||
filterSendersUid: uids,
|
||||
filterMsgToTime: '0',
|
||||
filterMsgFromTime: '0',
|
||||
isReverseOrder: false,
|
||||
isIncludeCurrent: true,
|
||||
pageLimit: 1,
|
||||
});
|
||||
return ret;
|
||||
}
|
||||
static async getLastestMsg(GroupCode: string, uins: string[]) {
|
||||
let uids: Array<string> = [];
|
||||
for (let uin of uins) {
|
||||
let uid = await NTQQUserApi.getUidByUin(uin)
|
||||
if (uid) {
|
||||
uids.push(uid);
|
||||
}
|
||||
}
|
||||
let ret = await napCatCore.session.getMsgService().queryMsgsWithFilterEx('0', '0', '0', {
|
||||
chatInfo: {
|
||||
peerUid: GroupCode,
|
||||
chatType: ChatType.group,
|
||||
},
|
||||
filterMsgType: [],
|
||||
filterSendersUid: uids,
|
||||
filterMsgToTime: '0',
|
||||
filterMsgFromTime: '0',
|
||||
isReverseOrder: false,
|
||||
isIncludeCurrent: true,
|
||||
pageLimit: 1,
|
||||
});
|
||||
return ret;
|
||||
}
|
||||
static async getGroupRecommendContactArkJson(GroupCode: string) {
|
||||
return napCatCore.session.getGroupService().getGroupRecommendContactArkJson(GroupCode);
|
||||
}
|
||||
@@ -63,6 +147,7 @@ export class NTQQGroupApi {
|
||||
'NodeIKernelGroupListener/onGroupSingleScreenNotifies',
|
||||
1,
|
||||
5000,
|
||||
() => true,
|
||||
false,
|
||||
'',
|
||||
num
|
||||
@@ -125,7 +210,7 @@ export class NTQQGroupApi {
|
||||
'seq': seq, // 通知序列号
|
||||
'type': type,
|
||||
'groupCode': groupCode,
|
||||
'postscript': reason || ''
|
||||
'postscript': reason || ' ' // 仅传空值可能导致处理失败,故默认给个空格
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@@ -1,83 +1,80 @@
|
||||
import { GetFileListParam, Peer, RawMessage, SendMessageElement } from '@/core/entities';
|
||||
import { selfInfo } from '@/core/data';
|
||||
import { log, logError } from '@/common/utils/log';
|
||||
import { GetFileListParam, Peer, RawMessage, SendMessageElement, SendMsgElementConstructor } from '@/core/entities';
|
||||
import { friends, groups, selfInfo } from '@/core/data';
|
||||
import { log, logWarn } from '@/common/utils/log';
|
||||
import { sleep } from '@/common/utils/helper';
|
||||
import { napCatCore } from '@/core';
|
||||
import { MsgListener, onGroupFileInfoUpdateParamType } from '@/core/listeners';
|
||||
import { napCatCore, NTQQUserApi } from '@/core';
|
||||
import { onGroupFileInfoUpdateParamType } from '@/core/listeners';
|
||||
import { GeneralCallResult } from '@/core/services/common';
|
||||
import { randomUUID } from 'crypto';
|
||||
import { MessageUnique } from '../../../common/utils/MessageUnique';
|
||||
import { NTEventDispatch } from '@/common/utils/EventTask';
|
||||
import { requireMinNTQBuild } from '@/common/utils/QQBasicInfo';
|
||||
|
||||
|
||||
const sendMessagePool: Record<string, ((sendSuccessMsg: RawMessage) => void | Promise<void>) | null> = {};// peerUid: callbackFunc
|
||||
|
||||
const sendSuccessCBMap: Record<string, ((sendSuccessMsg: RawMessage) => boolean | Promise<boolean>) | null> = {};// uuid: callbackFunc
|
||||
|
||||
const GroupFileInfoUpdateTasks: Map<string, ((groupFileListResult: onGroupFileInfoUpdateParamType) => void)> = new Map();
|
||||
|
||||
const sentMsgTasks: Map<string, (msg: RawMessage) => void> = new Map();
|
||||
|
||||
const msgListener = new MsgListener();
|
||||
|
||||
msgListener.onGroupFileInfoUpdate = (groupFileListResult: onGroupFileInfoUpdateParamType) => {
|
||||
for (const [uuid, cb] of GroupFileInfoUpdateTasks) {
|
||||
cb(groupFileListResult);
|
||||
GroupFileInfoUpdateTasks.delete(uuid);
|
||||
async function LoadMessageIdList(Peer: Peer, msgId: string) {
|
||||
let msgList = await NTQQMsgApi.getMsgHistory(Peer, msgId, 50);
|
||||
for (let j = 0; j < msgList.msgList.length; j++) {
|
||||
let shortId = MessageUnique.createMsg(Peer, msgList.msgList[j].msgId);
|
||||
}
|
||||
};
|
||||
|
||||
msgListener.onAddSendMsg = (msgRecord: RawMessage) => {
|
||||
// console.log("sent msg", msgRecord, sendMessagePool);
|
||||
for (const [uuid, cb] of sentMsgTasks) {
|
||||
cb(msgRecord);
|
||||
sentMsgTasks.delete(uuid);
|
||||
}
|
||||
async function loadMessageUnique() {
|
||||
if (groups.size > 100) {
|
||||
logWarn('群数量大于100,可能会导致性能问题');
|
||||
}
|
||||
if (sendMessagePool[msgRecord.peerUid]) {
|
||||
const r = sendMessagePool[msgRecord.peerUid]?.(msgRecord);
|
||||
if (r instanceof Promise) {
|
||||
r.then().catch(logError);
|
||||
let predict = (groups.size + friends.size / 2) / 5;
|
||||
predict = predict < 20 ? 20 : predict;
|
||||
predict = predict > 50 ? 50 : predict;
|
||||
//let waitpromise: Array<Promise<{ msgList: RawMessage[]; }>> = [];
|
||||
predict = Math.floor(predict * 50);
|
||||
MessageUnique.resize(predict);
|
||||
let RecentContact = await NTQQUserApi.getRecentContactListSnapShot(predict);
|
||||
let LoadMessageIdDo: Array<Promise<void>> = new Array<Promise<void>>();
|
||||
if (RecentContact?.info?.changedList && RecentContact?.info?.changedList?.length > 0) {
|
||||
for (let i = 0; i < RecentContact.info.changedList.length; i++) {
|
||||
let Peer: Peer = { chatType: RecentContact.info.changedList[i].chatType, peerUid: RecentContact.info.changedList[i].peerUid, guildId: '' };
|
||||
LoadMessageIdDo.push(LoadMessageIdList(Peer, RecentContact.info.changedList[i].msgId));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
msgListener.onMsgInfoListUpdate = (msgInfoList: RawMessage[]) => {
|
||||
msgInfoList.forEach(msg => {
|
||||
new Promise((resolve, reject) => {
|
||||
for (const cbId in sendSuccessCBMap) {
|
||||
const cb = sendSuccessCBMap[cbId]!;
|
||||
const cbResult = cb(msg);
|
||||
const checkResult = (result: boolean) => {
|
||||
if (result) {
|
||||
delete sendSuccessCBMap[cbId];
|
||||
}
|
||||
};
|
||||
if (cbResult instanceof Promise) {
|
||||
cbResult.then(checkResult);
|
||||
} else {
|
||||
checkResult(cbResult);
|
||||
}
|
||||
}
|
||||
}).then().catch(log);
|
||||
await Promise.all(LoadMessageIdDo).then(() => {
|
||||
log(`[消息序列] 加载 ${predict} 条历史消息记录完成`);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
napCatCore.onLoginSuccess(() => {
|
||||
napCatCore.addListener(msgListener);
|
||||
napCatCore.onLoginSuccess(async () => {
|
||||
await sleep(100);
|
||||
// NTQQMsgApi.CheckSendMode().then().catch();
|
||||
loadMessageUnique().then().catch();
|
||||
//let data = await napCatCore.session.getMsgService().sendSsoCmdReqByContend("LightAppSvc.mini_app_growguard.ReportExecute","1124343");
|
||||
//console.log(data);
|
||||
});
|
||||
}, 100);
|
||||
|
||||
//歇菜LocalMsg压根不写Db
|
||||
// setTimeout(async () => {
|
||||
// let ele: MessageElement = { extBufForUI: '0x', ...SendMsgElementConstructor.text('测试消息') };
|
||||
// let MsgId = await NTQQMsgApi.getMsgUniqueEx();
|
||||
// let peer = { chatType: 2, peerUid: '', guildId: '' };
|
||||
// console.log(await napCatCore.session.getTestPerformanceService().insertMsg(
|
||||
// {
|
||||
// peer: peer,
|
||||
// msgTime: Math.floor(Date.now() / 1000).toString(),
|
||||
// msgId: MsgId,
|
||||
// msgSeq: '56564',
|
||||
// batchNums: 1,
|
||||
// timesPerBatch: 1,
|
||||
// numPerTime: 1
|
||||
// }, [ele]
|
||||
// ));
|
||||
// console.log(await NTQQMsgApi.multiForwardMsg(peer, peer, [MsgId]));
|
||||
// }, 25000)
|
||||
|
||||
export class NTQQMsgApi {
|
||||
// static napCatCore: NapCatCore | null = null;
|
||||
// enum BaseEmojiType {
|
||||
// NORMAL_EMOJI,
|
||||
// SUPER_EMOJI,
|
||||
// RANDOM_SUPER_EMOJI,
|
||||
// CHAIN_SUPER_EMOJI,
|
||||
// EMOJI_EMOJI
|
||||
// }
|
||||
// enum BaseEmojiType {
|
||||
// NORMAL_EMOJI,
|
||||
// SUPER_EMOJI,
|
||||
// RANDOM_SUPER_EMOJI,
|
||||
// CHAIN_SUPER_EMOJI,
|
||||
// EMOJI_EMOJI
|
||||
// }
|
||||
static async setEmojiLike(peer: Peer, msgSeq: string, emojiId: string, set: boolean = true) {
|
||||
// nt_qq//global//nt_data//Emoji//emoji-resource//sysface_res/apng/ 下可以看到所有QQ表情预览
|
||||
// nt_qq\global\nt_data\Emoji\emoji-resource\face_config.json 里面有所有表情的id, 自带表情id是QSid, 标准emoji表情id是QCid
|
||||
@@ -90,142 +87,206 @@ export class NTQQMsgApi {
|
||||
} | undefined> {
|
||||
return napCatCore.session.getMsgService().getMultiMsg(peer, rootMsgId, parentMsgId);
|
||||
}
|
||||
|
||||
static async getLastestMsgByUids(peer: Peer, count: number = 20) {
|
||||
let ret = await napCatCore.session.getMsgService().queryMsgsWithFilterEx('0', '0', '0', {
|
||||
chatInfo: peer,
|
||||
filterMsgType: [],
|
||||
filterSendersUid: [],
|
||||
filterMsgToTime: '0',
|
||||
filterMsgFromTime: '0',
|
||||
isReverseOrder: false,
|
||||
isIncludeCurrent: true,
|
||||
pageLimit: 1,
|
||||
});
|
||||
return ret;
|
||||
}
|
||||
static async getMsgsByMsgId(peer: Peer, msgIds: string[]) {
|
||||
return await napCatCore.session.getMsgService().getMsgsByMsgId(peer, msgIds);
|
||||
}
|
||||
static async getSingleMsg(peer: Peer, seq: string) {
|
||||
return await napCatCore.session.getMsgService().getSingleMsg(peer, seq);
|
||||
}
|
||||
static async fetchFavEmojiList(num: number) {
|
||||
return napCatCore.session.getMsgService().fetchFavEmojiList("", num, true, true)
|
||||
}
|
||||
static async queryMsgsWithFilterExWithSeq(peer: Peer, msgSeq: string) {
|
||||
let ret = await napCatCore.session.getMsgService().queryMsgsWithFilterEx('0', '0', msgSeq, {
|
||||
chatInfo: peer,
|
||||
filterMsgType: [],
|
||||
filterSendersUid: [],
|
||||
filterMsgToTime: '0',
|
||||
filterMsgFromTime: '0',
|
||||
isReverseOrder: false,
|
||||
isIncludeCurrent: true,
|
||||
pageLimit: 1,
|
||||
});
|
||||
return ret;
|
||||
}
|
||||
static async getMsgsBySeqAndCount(peer: Peer, seq: string, count: number, desc: boolean, z: boolean) {
|
||||
return await napCatCore.session.getMsgService().getMsgsBySeqAndCount(peer, seq, count, desc, z);
|
||||
}
|
||||
|
||||
static async activateChat(peer: Peer) {
|
||||
// await this.fetchRecentContact();
|
||||
// await sleep(500);
|
||||
}
|
||||
|
||||
static async activateChatAndGetHistory(peer: Peer) {
|
||||
|
||||
}
|
||||
static async setMsgRead(peer: Peer) {
|
||||
return napCatCore.session.getMsgService().setMsgRead(peer);
|
||||
}
|
||||
static async getGroupFileList(GroupCode: string, params: GetFileListParam) {
|
||||
return new Promise<Array<any>>(async (resolve, reject) => {
|
||||
let complete = false;
|
||||
setTimeout(() => {
|
||||
if (!complete) {
|
||||
reject('获取群文件列表超时');
|
||||
}
|
||||
}, 5000);
|
||||
const GroupFileInfoUpdateCB = (groupFileListResult: onGroupFileInfoUpdateParamType) => {
|
||||
complete = true;
|
||||
resolve(groupFileListResult.item);
|
||||
};
|
||||
GroupFileInfoUpdateTasks.set(randomUUID(), GroupFileInfoUpdateCB);
|
||||
await napCatCore.session.getRichMediaService().getGroupFileList(GroupCode, params);
|
||||
});
|
||||
let data = await NTEventDispatch.CallNormalEvent<
|
||||
(GroupCode: string, params: GetFileListParam) => Promise<unknown>,
|
||||
(groupFileListResult: onGroupFileInfoUpdateParamType) => void
|
||||
>(
|
||||
'NodeIKernelRichMediaService/getGroupFileList',
|
||||
'NodeIKernelMsgListener/onGroupFileInfoUpdate',
|
||||
1,
|
||||
5000,
|
||||
(groupFileListResult: onGroupFileInfoUpdateParamType) => {
|
||||
return true;
|
||||
},
|
||||
GroupCode,
|
||||
params
|
||||
);
|
||||
return data[1].item;
|
||||
}
|
||||
static async getMsgHistory(peer: Peer, msgId: string, count: number) {
|
||||
// 消息时间从旧到新
|
||||
return napCatCore.session.getMsgService().getMsgsIncludeSelf(peer, msgId, count, true);
|
||||
}
|
||||
|
||||
static async fetchRecentContact() {
|
||||
|
||||
}
|
||||
|
||||
static async recallMsg(peer: Peer, msgIds: string[]) {
|
||||
await napCatCore.session.getMsgService().recallMsg({
|
||||
chatType: peer.chatType,
|
||||
peerUid: peer.peerUid
|
||||
}, msgIds);
|
||||
}
|
||||
static async sendMsgV2(peer: Peer, msgElements: SendMessageElement[], waitComplete = true, timeout = 10000) {
|
||||
// function generateMsgId() {
|
||||
// const timestamp = Math.floor(Date.now() / 1000);
|
||||
// const random = Math.floor(Math.random() * Math.pow(2, 32));
|
||||
// const buffer = Buffer.alloc(8);
|
||||
// buffer.writeUInt32BE(timestamp, 0);
|
||||
// buffer.writeUInt32BE(random, 4);
|
||||
// const msgId = BigInt("0x" + buffer.toString('hex')).toString();
|
||||
// return msgId;
|
||||
// }
|
||||
|
||||
static async sendMsg(peer: Peer, msgElements: SendMessageElement[], waitComplete = true, timeout = 10000): Promise<RawMessage> {
|
||||
const peerUid = peer.peerUid;
|
||||
// 等待上一个相同的peer发送完
|
||||
let checkLastSendUsingTime = 0;
|
||||
const waitLastSend: () => Promise<void> = async () => {
|
||||
if (checkLastSendUsingTime > timeout) {
|
||||
throw ('发送超时');
|
||||
}
|
||||
const lastSending = sendMessagePool[peer.peerUid];
|
||||
if (lastSending) {
|
||||
// log("有正在发送的消息,等待中...")
|
||||
await sleep(500);
|
||||
checkLastSendUsingTime += 500;
|
||||
return await waitLastSend();
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
};
|
||||
await waitLastSend();
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
let completed = false;
|
||||
let sentMessage: RawMessage | null = null;
|
||||
const sendSuccessCBId = randomUUID() as string;
|
||||
sendSuccessCBMap[sendSuccessCBId] = (msgRecord: RawMessage) => {
|
||||
if (msgRecord.msgId === sentMessage?.msgId) {
|
||||
if (msgRecord.sendStatus === 2) {
|
||||
delete sendSuccessCBMap[sendSuccessCBId];
|
||||
completed = true;
|
||||
resolve(msgRecord);
|
||||
let msgId = await NTQQMsgApi.getMsgUnique(peer.chatType, await NTQQMsgApi.getServerTime());
|
||||
let data = await NTEventDispatch.CallNormalEvent<
|
||||
(msgId: string, peer: Peer, msgElements: SendMessageElement[], map: Map<any, any>) => Promise<unknown>,
|
||||
(msgList: RawMessage[]) => void
|
||||
>(
|
||||
'NodeIKernelMsgService/sendMsg',
|
||||
'NodeIKernelMsgListener/onMsgInfoListUpdate',
|
||||
1,
|
||||
timeout,
|
||||
(msgRecords: RawMessage[]) => {
|
||||
for (let msgRecord of msgRecords) {
|
||||
if (msgRecord.msgId === msgId && msgRecord.sendStatus === 2) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
sendMessagePool[peerUid] = async (rawMessage: RawMessage) => {
|
||||
// console.log('收到sent 消息', rawMessage.msgId);
|
||||
delete sendMessagePool[peerUid];
|
||||
sentMessage = rawMessage;
|
||||
};
|
||||
setTimeout(() => {
|
||||
if (completed) return;
|
||||
delete sendMessagePool[peerUid];
|
||||
delete sendSuccessCBMap[sendSuccessCBId];
|
||||
reject('发送超时');
|
||||
}, timeout);
|
||||
const result = napCatCore.session.getMsgService().sendMsg('0', peer, msgElements, new Map());
|
||||
},
|
||||
msgId,
|
||||
peer,
|
||||
msgElements,
|
||||
new Map()
|
||||
);
|
||||
let retMsg = data[1].find(msgRecord => {
|
||||
if (msgRecord.msgId === msgId) {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
return retMsg;
|
||||
}
|
||||
static sendMsgEx(peer: Peer, msgElements: SendMessageElement[], waitComplete = true, timeout = 10000) {
|
||||
//return NTQQMsgApi.sendMsgV1(peer, msgElements, waitComplete, timeout);
|
||||
}
|
||||
static async sendMsg(peer: Peer, msgElements: SendMessageElement[], waitComplete = true, timeout = 10000) {
|
||||
//唉? !我有个想法
|
||||
let msgId = await NTQQMsgApi.getMsgUnique(peer.chatType, await NTQQMsgApi.getServerTime());
|
||||
peer.guildId = msgId;
|
||||
let data = await NTEventDispatch.CallNormalEvent<
|
||||
(msgId: string, peer: Peer, msgElements: SendMessageElement[], map: Map<any, any>) => Promise<unknown>,
|
||||
(msgList: RawMessage[]) => void
|
||||
>(
|
||||
'NodeIKernelMsgService/sendMsg',
|
||||
'NodeIKernelMsgListener/onMsgInfoListUpdate',
|
||||
1,
|
||||
timeout,
|
||||
(msgRecords: RawMessage[]) => {
|
||||
for (let msgRecord of msgRecords) {
|
||||
if (msgRecord.guildId === msgId && msgRecord.sendStatus === 2) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
"0",
|
||||
peer,
|
||||
msgElements,
|
||||
new Map()
|
||||
);
|
||||
let retMsg = data[1].find(msgRecord => {
|
||||
if (msgRecord.guildId === msgId) {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
return retMsg;
|
||||
}
|
||||
static async getMsgUnique(chatType: number, time: string) {
|
||||
if (requireMinNTQBuild('26702')) {
|
||||
return napCatCore.session.getMsgService().generateMsgUniqueId(chatType, time);
|
||||
}
|
||||
return napCatCore.session.getMsgService().getMsgUniqueId(time);
|
||||
}
|
||||
static async getServerTime() {
|
||||
return napCatCore.session.getMSFService().getServerTime();
|
||||
}
|
||||
static async getServerTimeV2() {
|
||||
return NTEventDispatch.CallNoListenerEvent<() => string>('NodeIKernelMsgService/getServerTime', 5000);
|
||||
}
|
||||
|
||||
static async forwardMsg(srcPeer: Peer, destPeer: Peer, msgIds: string[]) {
|
||||
return napCatCore.session.getMsgService().forwardMsg(msgIds, srcPeer, [destPeer], new Map());
|
||||
}
|
||||
|
||||
static async multiForwardMsg(srcPeer: Peer, destPeer: Peer, msgIds: string[]): Promise<RawMessage> {
|
||||
const msgInfos = msgIds.map(id => {
|
||||
return { msgId: id, senderShowName: selfInfo.nick };
|
||||
});
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
let complete = false;
|
||||
const onSentCB = (msg: RawMessage) => {
|
||||
const arkElement = msg.elements.find(ele => ele.arkElement);
|
||||
if (!arkElement) {
|
||||
// log("收到的不是转发消息")
|
||||
return;
|
||||
let data = await NTEventDispatch.CallNormalEvent<
|
||||
(msgInfo: typeof msgInfos, srcPeer: Peer, destPeer: Peer, comment: Array<any>, attr: Map<any, any>,) => Promise<unknown>,
|
||||
(msgList: RawMessage[]) => void
|
||||
>(
|
||||
'NodeIKernelMsgService/multiForwardMsgWithComment',
|
||||
'NodeIKernelMsgListener/onMsgInfoListUpdate',
|
||||
1,
|
||||
5000,
|
||||
(msgRecords: RawMessage[]) => {
|
||||
for (let msgRecord of msgRecords) {
|
||||
if (msgRecord.peerUid == destPeer.peerUid && msgRecord.senderUid == selfInfo.uid) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
const forwardData: any = JSON.parse(arkElement.arkElement.bytesData);
|
||||
if (forwardData.app != 'com.tencent.multimsg') {
|
||||
return;
|
||||
}
|
||||
if (msg.peerUid == destPeer.peerUid && msg.senderUid == selfInfo.uid) {
|
||||
complete = true;
|
||||
resolve(msg);
|
||||
}
|
||||
};
|
||||
sentMsgTasks.set(randomUUID(), onSentCB);
|
||||
setTimeout(() => {
|
||||
if (!complete) {
|
||||
reject('转发消息超时');
|
||||
}
|
||||
}, 5000);
|
||||
napCatCore.session.getMsgService().multiForwardMsgWithComment(msgInfos, srcPeer, destPeer, [], new Map());
|
||||
}
|
||||
return false;
|
||||
},
|
||||
msgInfos,
|
||||
srcPeer,
|
||||
destPeer,
|
||||
[],
|
||||
new Map()
|
||||
);
|
||||
for (let msg of data[1]) {
|
||||
const arkElement = msg.elements.find(ele => ele.arkElement);
|
||||
if (!arkElement) {
|
||||
continue;
|
||||
}
|
||||
const forwardData: any = JSON.parse(arkElement.arkElement.bytesData);
|
||||
if (forwardData.app != 'com.tencent.multimsg') {
|
||||
continue;
|
||||
}
|
||||
if (msg.peerUid == destPeer.peerUid && msg.senderUid == selfInfo.uid) {
|
||||
return msg;
|
||||
}
|
||||
}
|
||||
throw new Error('转发消息超时');
|
||||
}
|
||||
static async markallMsgAsRead() {
|
||||
return napCatCore.session.getMsgService().setAllC2CAndGroupMsgRead();
|
||||
}
|
||||
}
|
||||
|
@@ -1,11 +1,7 @@
|
||||
|
||||
|
||||
import { NTEventDispatch } from '@/common/utils/EventTask';
|
||||
import { GeneralCallResult, NTQQUserApi, napCatCore } from '@/core';
|
||||
// setTimeout(async () => {
|
||||
// let ret = await NTQQSystemApi.getArkJsonCollection('1-2-162b9b42-65b9-4405-a8ed-2e256ec8aa50');
|
||||
// console.log(ret);
|
||||
// }, 20000)
|
||||
import { GeneralCallResult, NTQQFileApi, NTQQUserApi, napCatCore } from '@/core';
|
||||
export class NTQQSystemApi {
|
||||
static async hasOtherRunningQQProcess() {
|
||||
return napCatCore.util.hasOtherRunningQQProcess();
|
||||
@@ -16,7 +12,7 @@ export class NTQQSystemApi {
|
||||
static async translateEnWordToZn(words: string[]) {
|
||||
return napCatCore.session.getRichMediaService().translateEnWordToZn(words);
|
||||
}
|
||||
//调用会超时 没灯用
|
||||
//调用会超时 没灯用 (好像是通知listener的) onLineDev
|
||||
static async getOnlineDev() {
|
||||
return napCatCore.session.getMsgService().getOnLineDev();
|
||||
}
|
||||
@@ -33,7 +29,7 @@ export class NTQQSystemApi {
|
||||
static async BootMiniApp(appfile: string, params: string) {
|
||||
await napCatCore.session.getNodeMiscService().setMiniAppVersion('2.16.4');
|
||||
let c = await napCatCore.session.getNodeMiscService().getMiniAppPath();
|
||||
console.log(c);
|
||||
|
||||
return napCatCore.session.getNodeMiscService().startNewMiniApp(appfile, params);
|
||||
}
|
||||
}
|
@@ -1,40 +1,29 @@
|
||||
import { ModifyProfileParams, SelfInfo, User, UserDetailInfoByUin } from '@/core/entities';
|
||||
import { friends, selfInfo } from '@/core/data';
|
||||
import { CacheClassFuncAsync } from '@/common/utils/helper';
|
||||
import { GeneralCallResult, napCatCore, NTQQFriendApi } from '@/core';
|
||||
import { ProfileListener } from '@/core/listeners';
|
||||
import { rejects } from 'assert';
|
||||
import { randomUUID } from 'crypto';
|
||||
import { friends, groupMembers, selfInfo } from '@/core/data';
|
||||
import { CacheClassFuncAsync, CacheClassFuncAsyncExtend } from '@/common/utils/helper';
|
||||
import { napCatCore } from '@/core';
|
||||
import { NodeIKernelProfileListener, ProfileListener } from '@/core/listeners';
|
||||
import { RequestUtil } from '@/common/utils/request';
|
||||
import { logDebug, logError } from '@/common/utils/log';
|
||||
import { logWarn } from '@/common/utils/log';
|
||||
import { NTEventDispatch } from '@/common/utils/EventTask';
|
||||
const userInfoCache: Record<string, User> = {}; // uid: User
|
||||
import { NodeIKernelProfileService, ProfileBizType, UserDetailSource } from '@/core/services';
|
||||
import { requireMinNTQBuild } from '@/common/utils/QQBasicInfo';
|
||||
|
||||
const profileListener = new ProfileListener();
|
||||
|
||||
const userDetailHandlers: Map<string, ((profile: User) => void)> = new Map();
|
||||
profileListener.onProfileDetailInfoChanged = (profile) => {
|
||||
userInfoCache[profile.uid] = profile;
|
||||
userDetailHandlers.forEach(handler => handler(profile));
|
||||
};
|
||||
setTimeout(() => {
|
||||
napCatCore.onLoginSuccess(() => {
|
||||
napCatCore.addListener(profileListener);
|
||||
});
|
||||
}, 100);
|
||||
// 老版本逻辑现已移除
|
||||
// console.log('onProfileDetailInfoChanged', profile);
|
||||
// recevCount++;
|
||||
// firstProfile = profile;
|
||||
// if (recevCount === 2) {
|
||||
// profileService.removeKernelProfileListener(listenerId);
|
||||
// // if (!completed) {
|
||||
// completed = true;
|
||||
// resolve(profile);
|
||||
// // }
|
||||
// }
|
||||
// };
|
||||
export class NTQQUserApi {
|
||||
static async getProfileLike(uid: string) {
|
||||
return napCatCore.session.getProfileLikeService().getBuddyProfileLike({
|
||||
friendUids: [
|
||||
uid
|
||||
],
|
||||
basic: 1,
|
||||
vote: 1,
|
||||
favorite: 0,
|
||||
userProfile: 1,
|
||||
type: 2,
|
||||
start: 0,
|
||||
limit: 20
|
||||
});
|
||||
}
|
||||
static async setLongNick(longNick: string) {
|
||||
return napCatCore.session.getProfileService().setLongNick(longNick);
|
||||
}
|
||||
@@ -58,6 +47,9 @@ export class NTQQUserApi {
|
||||
const ret = await napCatCore.session.getProfileService().setHeader(filePath) as setQQAvatarRet;
|
||||
return { result: ret?.result, errMsg: ret?.errMsg };
|
||||
}
|
||||
static async setGroupAvatar(gc: string, filePath: string) {
|
||||
return napCatCore.session.getGroupService().setHeader(gc, filePath);
|
||||
}
|
||||
|
||||
static async getSelfInfo() {
|
||||
|
||||
@@ -73,45 +65,68 @@ export class NTQQUserApi {
|
||||
// KQZONE,
|
||||
// KOTHER
|
||||
// }
|
||||
static async getUserDetailInfo(uid: string): Promise<User> {
|
||||
// const existUser = userInfoCache[uid];
|
||||
// if (existUser) {
|
||||
// return existUser;
|
||||
// }
|
||||
const profileService = napCatCore.session.getProfileService();
|
||||
// console.log('getUserDetailInfo', result);
|
||||
return new Promise((resolve, reject) => {
|
||||
const uuid = randomUUID();
|
||||
let completed = false;
|
||||
let retData: User | undefined = undefined;
|
||||
let isFirst = true;
|
||||
// 不管返回几次 超时有数据就该返回 兼容就好了
|
||||
setTimeout(() => {
|
||||
if (!completed) {
|
||||
if (retData) {
|
||||
resolve(retData);
|
||||
} else {
|
||||
reject('getUserDetailInfo timeout');
|
||||
static async fetchUserDetailInfo(uid: string) {
|
||||
type EventService = NodeIKernelProfileService['fetchUserDetailInfo'];
|
||||
type EventListener = NodeIKernelProfileListener['onUserDetailInfoChanged'];
|
||||
let [_retData, profile] = await NTEventDispatch.CallNormalEvent
|
||||
<EventService, EventListener>
|
||||
(
|
||||
'NodeIKernelProfileService/fetchUserDetailInfo',
|
||||
'NodeIKernelProfileListener/onUserDetailInfoChanged',
|
||||
1,
|
||||
5000,
|
||||
(profile) => {
|
||||
if (profile.uid === uid) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
userDetailHandlers.delete(uuid);
|
||||
}, 5000);
|
||||
userDetailHandlers.set(uuid, (profile) => {
|
||||
if (profile.uid === uid) {
|
||||
if (isFirst) {
|
||||
retData = profile;
|
||||
isFirst = false;
|
||||
// console.log('getUserDetailInfo', profile);
|
||||
} else {
|
||||
completed = true;
|
||||
resolve(profile);
|
||||
return false;
|
||||
},
|
||||
"BuddyProfileStore",
|
||||
[
|
||||
uid
|
||||
],
|
||||
UserDetailSource.KSERVER,
|
||||
[
|
||||
ProfileBizType.KALL
|
||||
]
|
||||
);
|
||||
let 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) {
|
||||
if (requireMinNTQBuild('26702')) {
|
||||
return this.fetchUserDetailInfo(uid);
|
||||
}
|
||||
return this.getUserDetailInfoOld(uid);
|
||||
}
|
||||
static async getUserDetailInfoOld(uid: string) {
|
||||
type EventService = NodeIKernelProfileService['getUserDetailInfoWithBizInfo'];
|
||||
type EventListener = NodeIKernelProfileListener['onProfileDetailInfoChanged'];
|
||||
let [_retData, profile] = await NTEventDispatch.CallNormalEvent
|
||||
<EventService, EventListener>
|
||||
(
|
||||
'NodeIKernelProfileService/getUserDetailInfoWithBizInfo',
|
||||
'NodeIKernelProfileListener/onProfileDetailInfoChanged',
|
||||
2,
|
||||
5000,
|
||||
(profile: User) => {
|
||||
if (profile.uid === uid) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
});
|
||||
profileService.getUserDetailInfoWithBizInfo(uid, [0]).then(result => {
|
||||
// console.log('getUserDetailInfo', result);
|
||||
});
|
||||
});
|
||||
return false;
|
||||
},
|
||||
uid,
|
||||
[0]
|
||||
);
|
||||
return profile;
|
||||
}
|
||||
static async modifySelfProfile(param: ModifyProfileParams) {
|
||||
return napCatCore.session.getProfileService().modifyDesktopMiniProfile(param);
|
||||
@@ -163,30 +178,40 @@ export class NTQQUserApi {
|
||||
}
|
||||
return skey;
|
||||
}
|
||||
@CacheClassFuncAsyncExtend(3600, 'Uin2Uid', (Uin: string, Uid: string | undefined) => {
|
||||
if (Uid && Uid.indexOf('u_') != -1) {
|
||||
return true
|
||||
}
|
||||
logWarn("uin转换到uid时异常", Uin);
|
||||
return false;
|
||||
})
|
||||
static async getUidByUin(Uin: string) {
|
||||
//Uid 接口转
|
||||
let ret = await NTEventDispatch.CallNoListenerEvent
|
||||
<(Uin: string[]) => Promise<{ uidInfo: Map<string, string> }>>(
|
||||
'NodeIKernelUixConvertService/getUid',
|
||||
5000,
|
||||
[Uin]
|
||||
);
|
||||
let uid = ret.uidInfo.get(Uin); //通过QQ默认方式转换
|
||||
let uid = ret.uidInfo.get(Uin);
|
||||
// Uid 好友转
|
||||
if (!uid) {
|
||||
Array.from(friends.values()).forEach((t) => {
|
||||
if (t.uin == Uin) {
|
||||
//logDebug('getUidByUin', t.uid, t.uin, Uin);
|
||||
uid = t.uid;
|
||||
}
|
||||
//console.log(t.uid, t.uin, Uin);
|
||||
});
|
||||
//uid = Array.from(friends.values()).find((t) => { t.uin == Uin })?.uid; // 从NC维护的QQ Buddy缓存 转换
|
||||
}
|
||||
// if (!uid) {
|
||||
// uid = (await NTQQFriendApi.getFriends(false)).find((t) => { t.uin == Uin })?.uid; //从QQ Native 缓存转换 方法一
|
||||
// }
|
||||
// if (!uid) {
|
||||
// uid = (await NTQQFriendApi.getFriends(true)).find((t) => { t.uin == Uin })?.uid; //从QQ Native 非缓存转换 方法二
|
||||
// }
|
||||
//Uid 群友列表转
|
||||
if (!uid) {
|
||||
for (let groupMembersList of groupMembers.values()) {
|
||||
for (let GroupMember of groupMembersList.values()) {
|
||||
if (GroupMember.uin == Uin) {
|
||||
uid = GroupMember.uid;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!uid) {
|
||||
let unveifyUid = (await NTQQUserApi.getUserDetailInfoByUin(Uin)).info.uid;//从QQ Native 特殊转换 方法三
|
||||
if (unveifyUid.indexOf("*") == -1) {
|
||||
@@ -195,6 +220,13 @@ export class NTQQUserApi {
|
||||
}
|
||||
return uid;
|
||||
}
|
||||
@CacheClassFuncAsyncExtend(3600, 'Uid2Uin', (Uid: string | undefined, Uin: number | undefined) => {
|
||||
if (Uin && Uin != 0 && !isNaN(Uin)) {
|
||||
return true
|
||||
}
|
||||
logWarn("uid转换到uin时异常", Uid);
|
||||
return false;
|
||||
})
|
||||
static async getUinByUid(Uid: string | undefined) {
|
||||
if (!Uid) {
|
||||
return '';
|
||||
@@ -217,7 +249,7 @@ export class NTQQUserApi {
|
||||
if (!uin) {
|
||||
uin = (await NTQQUserApi.getUserDetailInfo(Uid)).uin; //从QQ Native 转换
|
||||
}
|
||||
|
||||
|
||||
// if (!uin) {
|
||||
// uin = (await NTQQFriendApi.getFriends(false)).find((t) => { t.uid == Uid })?.uin; //从QQ Native 缓存转换
|
||||
// }
|
||||
@@ -226,6 +258,18 @@ export class NTQQUserApi {
|
||||
// }
|
||||
return uin;
|
||||
}
|
||||
static async getRecentContactListSnapShot(count: number) {
|
||||
return await napCatCore.session.getRecentContactService().getRecentContactListSnapShot(count);
|
||||
}
|
||||
static async getRecentContactListSyncLimit(count: number) {
|
||||
return await napCatCore.session.getRecentContactService().getRecentContactListSyncLimit(count);
|
||||
}
|
||||
static async getRecentContactListSync() {
|
||||
return await napCatCore.session.getRecentContactService().getRecentContactListSync();
|
||||
}
|
||||
static async getRecentContactList() {
|
||||
return await napCatCore.session.getRecentContactService().getRecentContactList();
|
||||
}
|
||||
static async getUserDetailInfoByUin(Uin: string) {
|
||||
return NTEventDispatch.CallNoListenerEvent
|
||||
<(Uin: string) => Promise<UserDetailInfoByUin>>(
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import QQWrapper, { NodeIQQNTWrapperEngine, NodeIQQNTWrapperSession, NodeQQNTWrapperUtil } from '@/core/wrapper';
|
||||
import { DeviceList } from '@/onebot11/main';
|
||||
import {
|
||||
NodeIKernelLoginService,
|
||||
NodeIKernelBuddyService,
|
||||
@@ -17,11 +18,10 @@ import fs from 'node:fs';
|
||||
import { appid, qqVersionConfigInfo } from '@/common/utils/QQBasicInfo';
|
||||
import { hostname, systemVersion } from '@/common/utils/system';
|
||||
import { genSessionConfig } from '@/core/sessionConfig';
|
||||
import { dbUtil } from '@/common/utils/db';
|
||||
import { sleep } from '@/common/utils/helper';
|
||||
import crypto from 'node:crypto';
|
||||
import { rawFriends, friends, groupMembers, groups, selfInfo, stat } from '@/core/data';
|
||||
import { RawMessage } from '@/core/entities';
|
||||
import { GroupMember, RawMessage } from '@/core/entities';
|
||||
import { NTEventDispatch } from '@/common/utils/EventTask';
|
||||
import {
|
||||
enableConsoleLog,
|
||||
@@ -84,20 +84,21 @@ export class NapCatCore {
|
||||
const dataPath = path.resolve(this.dataPath, './NapCat/data');
|
||||
fs.mkdirSync(dataPath, { recursive: true });
|
||||
logDebug('本账号数据/缓存目录:', dataPath);
|
||||
dbUtil.init(path.resolve(dataPath, `./${arg.uin}-v2.db`)).then(() => {
|
||||
this.initDataListener();
|
||||
this.onLoginSuccessFuncList.map(cb => {
|
||||
new Promise((resolve, reject) => {
|
||||
const result = cb(arg.uin, arg.uid);
|
||||
if (result instanceof Promise) {
|
||||
result.then(resolve).catch(reject);
|
||||
}
|
||||
}).then();
|
||||
});
|
||||
}).catch((e) => {
|
||||
logError('数据库初始化失败', e);
|
||||
// dbUtil.init(path.resolve(dataPath, `./${arg.uin}-v2.db`)).then(() => {
|
||||
this.initDataListener();
|
||||
this.onLoginSuccessFuncList.map(cb => {
|
||||
new Promise((resolve, reject) => {
|
||||
const result = cb(arg.uin, arg.uid);
|
||||
if (result instanceof Promise) {
|
||||
result.then(resolve).catch(reject);
|
||||
}
|
||||
}).then();
|
||||
});
|
||||
// }).catch((e) => {
|
||||
// logError('数据库初始化失败', e);
|
||||
// });
|
||||
// this.initDataListener();
|
||||
|
||||
}).catch((e) => {
|
||||
logError('initSession failed', e);
|
||||
throw new Error(`启动失败: ${JSON.stringify(e)}`);
|
||||
@@ -108,8 +109,8 @@ export class NapCatCore {
|
||||
logError('登录失败(onQRCodeSessionFailed)', errMsg);
|
||||
if (errType == 1 && errCode == 3) {
|
||||
// 二维码过期刷新
|
||||
this.loginService.getQRCodePicture();
|
||||
}
|
||||
this.loginService.getQRCodePicture();
|
||||
};
|
||||
this.loginListener.onLoginFailed = (args) => {
|
||||
logError('登录失败(onLoginFailed)', args);
|
||||
@@ -189,7 +190,6 @@ export class NapCatCore {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private initDataListener() {
|
||||
// 消息相关
|
||||
interface LineDevice {
|
||||
@@ -208,10 +208,17 @@ export class NapCatCore {
|
||||
}
|
||||
const msgListener = new MsgListener();
|
||||
msgListener.onLineDev = (Devices: LineDevice[]) => {
|
||||
DeviceList.splice(0, DeviceList.length);
|
||||
Devices.map((Device: LineDevice) => {
|
||||
if (Device.clientType === 2) {
|
||||
log('账号设备(' + Device.devUid + ') 在线状态变更');
|
||||
}
|
||||
let DeviceData = {
|
||||
app_id: Device.devUid,
|
||||
device_name: Device.clientType.toString(),
|
||||
device_kind: Device.clientType.toString(),
|
||||
};
|
||||
DeviceList.push(DeviceData);
|
||||
// if (Device.clientType === 2) {
|
||||
// log('账号设备(' + Device.devUid + ') 在线状态变更');
|
||||
// }
|
||||
});
|
||||
};
|
||||
msgListener.onKickedOffLine = (Info: KickedOffLineInfo) => {
|
||||
@@ -242,6 +249,7 @@ export class NapCatCore {
|
||||
stat.last_message_time = Math.floor(Date.now() / 1000);
|
||||
};
|
||||
msgListener.onRecvMsg = (msgList: RawMessage[]) => {
|
||||
// console.log(JSON.stringify(msgList[0],null,2));
|
||||
stat.packet_received += 1;
|
||||
stat.message_received += msgList.length;
|
||||
stat.last_message_time = Math.floor(Date.now() / 1000);
|
||||
@@ -346,7 +354,7 @@ export class NapCatCore {
|
||||
// console.log('onMemberListChange', groupCode, arg);
|
||||
};
|
||||
groupListener.onMemberInfoChange = (groupCode, changeType, members) => {
|
||||
// console.log('onMemberInfoChange', arg);
|
||||
//console.log('onMemberInfoChange', groupCode, changeType, members);
|
||||
if (changeType === 0 && members.get(selfInfo.uid)?.isDelete) {
|
||||
// 自身退群或者被踢退群 5s用于Api操作 之后不再出现
|
||||
setTimeout(() => {
|
||||
@@ -359,6 +367,9 @@ export class NapCatCore {
|
||||
members.forEach((member, uid) => {
|
||||
const existMember = existMembers.get(uid);
|
||||
if (existMember) {
|
||||
// 检查管理变动
|
||||
member.isChangeRole = this.checkAdminEvent(groupCode, member, existMember);
|
||||
// 更新成员信息
|
||||
Object.assign(existMember, member);
|
||||
}
|
||||
else {
|
||||
@@ -471,6 +482,13 @@ export class NapCatCore {
|
||||
const loginList = await this.loginService.getLoginList();
|
||||
return loginList;
|
||||
}
|
||||
checkAdminEvent(groupCode: string, memberNew: GroupMember, memberOld: GroupMember | undefined): boolean {
|
||||
if (memberNew.role !== memberOld?.role) {
|
||||
log(`群 ${groupCode} ${memberNew.nick} 角色变更为 ${memberNew.role === 3 ? '管理员' : '群员'}`);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export const napCatCore = new NapCatCore();
|
||||
|
@@ -10,7 +10,8 @@ import {
|
||||
SendReplyElement,
|
||||
sendShareLocationElement,
|
||||
SendTextElement,
|
||||
SendVideoElement
|
||||
SendVideoElement,
|
||||
viedo_type
|
||||
} from './index';
|
||||
import { promises as fs } from 'node:fs';
|
||||
import ffmpeg from 'fluent-ffmpeg';
|
||||
@@ -127,7 +128,7 @@ export class SendMsgElementConstructor {
|
||||
return element;
|
||||
}
|
||||
|
||||
static async video(filePath: string, fileName: string = '', diyThumbPath: string = ''): Promise<SendVideoElement> {
|
||||
static async video(filePath: string, fileName: string = '', diyThumbPath: string = '', videotype: viedo_type = viedo_type.VIDEO_FORMAT_MP4): Promise<SendVideoElement> {
|
||||
const { fileName: _fileName, path, fileSize, md5 } = await NTQQFileApi.uploadFile(filePath, ElementType.VIDEO);
|
||||
if (fileSize === 0) {
|
||||
throw '文件异常,大小为0';
|
||||
@@ -195,6 +196,7 @@ export class SendMsgElementConstructor {
|
||||
thumbWidth: videoInfo.width,
|
||||
thumbHeight: videoInfo.height,
|
||||
fileSize: '' + fileSize,
|
||||
//fileFormat: videotype
|
||||
// fileUuid: "",
|
||||
// transferStatus: 0,
|
||||
// progress: 0,
|
||||
@@ -246,7 +248,28 @@ export class SendMsgElementConstructor {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// NodeIQQNTWrapperSession sendMsg [
|
||||
// "0",
|
||||
// {
|
||||
// "peerUid": "u_e_RIxgTs2NaJ68h0PwOPSg",
|
||||
// "chatType": 1,
|
||||
// "guildId": ""
|
||||
// },
|
||||
// [
|
||||
// {
|
||||
// "elementId": "0",
|
||||
// "elementType": 6,
|
||||
// "faceElement": {
|
||||
// "faceIndex": 0,
|
||||
// "faceType": 5,
|
||||
// "msgType": 0,
|
||||
// "pokeType": 1,
|
||||
// "pokeStrength": 0
|
||||
// }
|
||||
// }
|
||||
// ],
|
||||
// {}
|
||||
// ]
|
||||
static face(faceId: number): SendFaceElement {
|
||||
// 从face_config.json中获取表情名称
|
||||
const sysFaces = faceConfig.sysface;
|
||||
|
@@ -52,4 +52,5 @@ export interface GroupMember {
|
||||
isRobot: boolean;
|
||||
sex?: Sex
|
||||
qqLevel?: QQLevel
|
||||
isChangeRole: boolean;
|
||||
}
|
@@ -55,22 +55,24 @@ export enum ElementType {
|
||||
TASKTOPMSG = 29,
|
||||
RECOMMENDEDMSG = 43,
|
||||
ACTIONBAR = 44
|
||||
}
|
||||
export interface ActionBarElement {
|
||||
rows: InlineKeyboardRow[];
|
||||
botAppid: string;
|
||||
}
|
||||
export interface SendActionBarElement {
|
||||
elementType: ElementType.ACTIONBAR;
|
||||
elementId: string;
|
||||
actionBarElement: {
|
||||
rows: InlineKeyboardRow[];
|
||||
botAppid: string;
|
||||
}
|
||||
actionBarElement: ActionBarElement;
|
||||
}
|
||||
export interface RecommendedMsgElement {
|
||||
rows: InlineKeyboardRow[];
|
||||
botAppid: string;
|
||||
}
|
||||
export interface SendRecommendedMsgElement {
|
||||
elementType: ElementType.RECOMMENDEDMSG;
|
||||
elementId: string;
|
||||
recommendedMsgElement: {
|
||||
rows: InlineKeyboardRow[];
|
||||
botAppid: string;
|
||||
}
|
||||
recommendedMsgElement: RecommendedMsgElement;
|
||||
}
|
||||
export interface InlineKeyboardButton {
|
||||
id: string;
|
||||
@@ -93,67 +95,72 @@ export interface TofuElementContent {
|
||||
color: string;
|
||||
tittle: string;
|
||||
}
|
||||
export interface TaskTopMsgElement {
|
||||
msgTitle: string;
|
||||
msgSummary: string;
|
||||
iconUrl: string;
|
||||
topMsgType: number;
|
||||
}
|
||||
export interface SendTaskTopMsgElement {
|
||||
elementType: ElementType.TASKTOPMSG;
|
||||
elementId: string;
|
||||
taskTopMsgElement: {
|
||||
msgTitle: string;
|
||||
msgSummary: string;
|
||||
iconUrl: string;
|
||||
topMsgType: number;
|
||||
}
|
||||
taskTopMsgElement: TaskTopMsgElement;
|
||||
}
|
||||
export interface TofuRecordElement {
|
||||
type: number;
|
||||
busiid: string;
|
||||
busiuuid: string;
|
||||
descriptionContent: string;
|
||||
contentlist: TofuElementContent[],
|
||||
background: string;
|
||||
icon: string;
|
||||
uinlist: string[],
|
||||
uidlist: string[],
|
||||
busiExtra: string;
|
||||
updateTime: string;
|
||||
dependedmsgid: string;
|
||||
msgtime: string;
|
||||
onscreennotify: boolean;
|
||||
}
|
||||
export interface SendTofuRecordElement {
|
||||
elementType: ElementType.TOFURECORD;
|
||||
elementId: string;
|
||||
tofuRecordElement: {
|
||||
type: number;
|
||||
busiid: string;
|
||||
busiuuid: string;
|
||||
descriptionContent: string;
|
||||
contentlist: TofuElementContent[],
|
||||
background: string;
|
||||
icon: string;
|
||||
uinlist: string[],
|
||||
uidlist: string[],
|
||||
busiExtra: string;
|
||||
updateTime: string;
|
||||
dependedmsgid: string;
|
||||
msgtime: string;
|
||||
onscreennotify: boolean;
|
||||
tofuRecordElement: TofuRecordElement;
|
||||
}
|
||||
|
||||
export interface FaceBubbleElement {
|
||||
faceCount: number;
|
||||
faceSummary: string;
|
||||
faceFlag: number;
|
||||
content: string;
|
||||
oldVersionStr: string;
|
||||
faceType: number;
|
||||
others: string;
|
||||
yellowFaceInfo: {
|
||||
index: number;
|
||||
buf: string;
|
||||
compatibleText: string;
|
||||
text: string;
|
||||
}
|
||||
}
|
||||
export interface SendFaceBubbleElement {
|
||||
elementType: ElementType.FACEBUBBLE;
|
||||
elementId: string;
|
||||
faceBubbleElement: {
|
||||
faceCount: number;
|
||||
faceSummary: string;
|
||||
faceFlag: number;
|
||||
content: string;
|
||||
oldVersionStr: string;
|
||||
faceType: number;
|
||||
others: string;
|
||||
yellowFaceInfo: {
|
||||
index: number;
|
||||
buf: string;
|
||||
compatibleText: string;
|
||||
text: string;
|
||||
}
|
||||
}
|
||||
faceBubbleElement: FaceBubbleElement;
|
||||
|
||||
}
|
||||
export interface AvRecordElement {
|
||||
type: number;
|
||||
time: string;
|
||||
text: string;
|
||||
mainType: number;
|
||||
hasRead: boolean;
|
||||
extraType: number;
|
||||
}
|
||||
export interface SendavRecordElement {
|
||||
elementType: ElementType.AVRECORD;
|
||||
elementId: string;
|
||||
avRecordElement: {
|
||||
type: number;
|
||||
time: string;
|
||||
text: string;
|
||||
mainType: number;
|
||||
hasRead: boolean;
|
||||
extraType: number;
|
||||
}
|
||||
avRecordElement: AvRecordElement;
|
||||
}
|
||||
export interface YoloUserInfo {
|
||||
uid: string;
|
||||
@@ -170,37 +177,40 @@ export interface SendInlineKeyboardElement {
|
||||
}
|
||||
|
||||
}
|
||||
export interface YoloGameResultElement {
|
||||
UserInfo: YoloUserInfo[];
|
||||
}
|
||||
export interface SendYoloGameResultElement {
|
||||
elementType: ElementType.YOLOGAMERESULT;
|
||||
yoloGameResultElement: {
|
||||
UserInfo: YoloUserInfo[];
|
||||
}
|
||||
yoloGameResultElement: YoloGameResultElement
|
||||
}
|
||||
export interface GiphyElement {
|
||||
id: string;
|
||||
isClip: boolean;
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
export interface SendGiphyElement {
|
||||
elementType: ElementType.GIPHY;
|
||||
elementId: string;
|
||||
giphyElement: {
|
||||
id: string;
|
||||
isClip: boolean;
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
giphyElement: GiphyElement;
|
||||
}
|
||||
export interface SendWalletElement {
|
||||
elementType: ElementType.UNKNOWN;//不做 设置位置
|
||||
elementId: string;
|
||||
walletElement: {}
|
||||
}
|
||||
export interface CalendarElement {
|
||||
summary: string;
|
||||
msg: string;
|
||||
expireTimeMs: string;
|
||||
schemaType: number;
|
||||
schema: string
|
||||
}
|
||||
export interface SendCalendarElement {
|
||||
elementType: ElementType.CALENDAR;
|
||||
elementId: string;
|
||||
calendarElement: {
|
||||
summary: string;
|
||||
msg: string;
|
||||
expireTimeMs: string;
|
||||
schemaType: number;
|
||||
schema: string
|
||||
}
|
||||
calendarElement: CalendarElement;
|
||||
}
|
||||
export interface SendliveGiftElement {
|
||||
elementType: ElementType.LIVEGIFT;
|
||||
@@ -252,32 +262,18 @@ export enum PicSubType {
|
||||
export interface SendPicElement {
|
||||
elementType: ElementType.PIC;
|
||||
elementId: string;
|
||||
picElement: {
|
||||
md5HexStr: string;
|
||||
fileSize: number | string;
|
||||
picWidth: number;
|
||||
picHeight: number;
|
||||
fileName: string;
|
||||
sourcePath: string;
|
||||
original: boolean;
|
||||
picType: PicType;
|
||||
picSubType: PicSubType;
|
||||
fileUuid: string;
|
||||
fileSubId: string;
|
||||
thumbFileSize: number;
|
||||
summary: string;
|
||||
};
|
||||
picElement:PicElement
|
||||
}
|
||||
export interface ReplyElement {
|
||||
replayMsgSeq: string;
|
||||
replayMsgId: string;
|
||||
senderUin: string;
|
||||
senderUinStr: string;
|
||||
}
|
||||
|
||||
export interface SendReplyElement {
|
||||
elementType: ElementType.REPLY;
|
||||
elementId: string;
|
||||
replyElement: {
|
||||
replayMsgSeq: string;
|
||||
replayMsgId: string;
|
||||
senderUin: string;
|
||||
senderUinStr: string;
|
||||
}
|
||||
replyElement: ReplyElement
|
||||
}
|
||||
|
||||
export interface SendFaceElement {
|
||||
@@ -293,10 +289,11 @@ export interface SendMarketFaceElement {
|
||||
export interface SendstructLongMsgElement {
|
||||
elementType: ElementType.STRUCTLONGMSG;
|
||||
elementId: string;
|
||||
structLongMsgElement: {
|
||||
xmlContent: string;
|
||||
resId: string;
|
||||
}
|
||||
structLongMsgElement: StructLongMsgElement;
|
||||
}
|
||||
export interface StructLongMsgElement {
|
||||
xmlContent: string;
|
||||
resId: string;
|
||||
}
|
||||
export interface SendactionBarElement {
|
||||
elementType: ElementType.ACTIONBAR;
|
||||
@@ -306,13 +303,14 @@ export interface SendactionBarElement {
|
||||
botAppid: string;
|
||||
}
|
||||
}
|
||||
export interface ShareLocationElement {
|
||||
text: string;
|
||||
ext: string;
|
||||
}
|
||||
export interface sendShareLocationElement {
|
||||
elementType: ElementType.SHARELOCATION;
|
||||
elementId: string;
|
||||
shareLocationElement: {
|
||||
text: string;
|
||||
ext: string;
|
||||
}
|
||||
shareLocationElement?: ShareLocationElement;
|
||||
}
|
||||
|
||||
export interface FileElement {
|
||||
@@ -356,10 +354,51 @@ export interface SendMarkdownElement {
|
||||
elementId: string;
|
||||
markdownElement: MarkdownElement;
|
||||
}
|
||||
|
||||
export type SendMessageElement = SendTextElement | SendPttElement |
|
||||
SendPicElement | SendReplyElement | SendFaceElement | SendMarketFaceElement | SendFileElement | SendVideoElement | SendArkElement | SendMarkdownElement | sendShareLocationElement
|
||||
SendPicElement | SendReplyElement | SendFaceElement | SendMarketFaceElement | SendFileElement |
|
||||
SendVideoElement | SendArkElement | SendMarkdownElement | sendShareLocationElement;
|
||||
|
||||
export interface TextElement {
|
||||
content: string;
|
||||
atType: number;
|
||||
atUid: string;
|
||||
atTinyId: string;
|
||||
atNtUid: string;
|
||||
}
|
||||
export interface MessageElement {
|
||||
elementType: ElementType,
|
||||
elementId: string,
|
||||
extBufForUI: string,//"0x",
|
||||
textElement?: TextElement;
|
||||
faceElement?: FaceElement,
|
||||
marketFaceElement?: MarkdownElement,
|
||||
replyElement?: ReplyElement,
|
||||
picElement?: PicElement,
|
||||
pttElement?: PttElement,
|
||||
videoElement?: VideoElement,
|
||||
grayTipElement?: GrayTipElement,
|
||||
arkElement?: ArkElement,
|
||||
fileElement?: FileElement,
|
||||
liveGiftElement?: null,
|
||||
markdownElement?: MarkdownElement,
|
||||
structLongMsgElement?: StructLongMsgElement,
|
||||
multiForwardMsgElement?: MultiForwardMsgElement,
|
||||
giphyElement?: GiphyElement,
|
||||
walletElement?: null,
|
||||
inlineKeyboardElement?: InlineKeyboardElement,
|
||||
textGiftElement?: null,//????
|
||||
calendarElement?: CalendarElement,
|
||||
yoloGameResultElement?: YoloGameResultElement,
|
||||
avRecordElement?: AvRecordElement,
|
||||
structMsgElement?: null,
|
||||
faceBubbleElement?: FaceBubbleElement,
|
||||
shareLocationElement?: ShareLocationElement,
|
||||
tofuRecordElement?: TofuRecordElement,
|
||||
taskTopMsgElement?: TaskTopMsgElement,
|
||||
recommendedMsgElement?: RecommendedMsgElement,
|
||||
actionBarElement?:ActionBarElement
|
||||
|
||||
}
|
||||
export enum AtType {
|
||||
notAt = 0,
|
||||
atAll = 1,
|
||||
@@ -450,18 +489,23 @@ export const IMAGE_HTTP_HOST = 'https://gchat.qpic.cn';
|
||||
export const IMAGE_HTTP_HOST_NT = 'https://multimedia.nt.qq.com.cn';
|
||||
|
||||
export interface PicElement {
|
||||
picSubType?: number;
|
||||
originImageUrl: string; // http url, 没有host,host是https://gchat.qpic.cn/, 带download参数的是https://multimedia.nt.qq.com.cn
|
||||
originImageMd5?: string;
|
||||
sourcePath: string; // 图片本地路径
|
||||
thumbPath: Map<number, string>;
|
||||
md5HexStr?: string;
|
||||
fileSize: number | string;//number
|
||||
picWidth: number;
|
||||
picHeight: number;
|
||||
fileSize: number;
|
||||
fileName: string;
|
||||
sourcePath: string;
|
||||
original: boolean;
|
||||
picType: PicType;
|
||||
picSubType?: PicSubType;
|
||||
fileUuid: string;
|
||||
md5HexStr?: string;
|
||||
}
|
||||
fileSubId: string;
|
||||
thumbFileSize: number;
|
||||
summary: string;
|
||||
thumbPath: Map<number, string>;
|
||||
originImageMd5?: string;
|
||||
originImageUrl?: string; // http url, 没有host,host是https://gchat.qpic.cn/, 带download参数的是https://multimedia.nt.qq.com.cn
|
||||
};
|
||||
|
||||
export enum GrayTipElementSubType {
|
||||
INVITE_NEW_MEMBER = 12,
|
||||
@@ -526,7 +570,7 @@ export interface VideoElement {
|
||||
thumbMd5?: string
|
||||
fileTime?: number; // second
|
||||
thumbSize?: number; // byte
|
||||
fileFormat?: number; // 2表示mp4 参考下面条目
|
||||
fileFormat?: viedo_type; // 2表示mp4 参考下面条目
|
||||
fileSize?: string; // byte
|
||||
thumbWidth?: number;
|
||||
thumbHeight?: number;
|
||||
@@ -687,6 +731,7 @@ export interface MultiForwardMsgElement {
|
||||
}
|
||||
|
||||
export interface RawMessage {
|
||||
guildId: string;
|
||||
msgRandom: string;
|
||||
// int32, 自己维护的消息id
|
||||
id?: number;
|
||||
@@ -708,10 +753,12 @@ export interface RawMessage {
|
||||
chatType: ChatType;
|
||||
sendStatus?: number; // 消息状态,别人发的2是已撤回,自己发的2是已发送
|
||||
recallTime: string; // 撤回时间, "0"是没有撤回
|
||||
records: RawMessage[];
|
||||
elements: {
|
||||
elementId: string;
|
||||
elementType: ElementType;
|
||||
replyElement: {
|
||||
sourceMsgIdInRecords: string;
|
||||
senderUid: string; // 原消息发送者QQ号
|
||||
sourceMsgIsIncPic: boolean; // 原消息是否有图片
|
||||
sourceMsgText: string;
|
||||
|
@@ -62,6 +62,7 @@ export enum BuddyReqType {
|
||||
KMEINITIATORWAITPEERCONFIRM
|
||||
}
|
||||
export interface FriendRequest {
|
||||
isInitiator?: boolean;
|
||||
isDecide: boolean;
|
||||
friendUid: string;
|
||||
reqType: BuddyReqType,
|
||||
|
@@ -9,7 +9,178 @@ export interface BuddyCategoryType {
|
||||
categroyMbCount: number;
|
||||
buddyList: User[];
|
||||
}
|
||||
interface CoreInfo {
|
||||
uid: string;
|
||||
uin: string;
|
||||
nick: string;
|
||||
remark: string;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
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[];
|
||||
}
|
||||
|
||||
interface SimpleInfo {
|
||||
uid: string;
|
||||
uin: string;
|
||||
coreInfo: CoreInfo;
|
||||
baseInfo: BaseInfo;
|
||||
status: UserStatus;
|
||||
vasInfo: VasInfo;
|
||||
relationFlags: RelationFlags;
|
||||
otherFlags: any;
|
||||
intimate: any;
|
||||
}
|
||||
|
||||
export interface UserDetailInfoListenerArg {
|
||||
uid: string;
|
||||
uin: string;
|
||||
simpleInfo: SimpleInfo;
|
||||
commonExt: CommonExt;
|
||||
photoWall: PhotoWall;
|
||||
}
|
||||
export interface ModifyProfileParams {
|
||||
nick: string,
|
||||
longNick: string,
|
||||
|
@@ -40,14 +40,14 @@ export interface onGroupFileInfoUpdateParamType {
|
||||
// fromNick: '拾xxxx,
|
||||
// sig: '0x'
|
||||
// }
|
||||
export interface TempOnRecvParams{
|
||||
sessionType: number,//1
|
||||
chatType: ChatType,//100
|
||||
peerUid: string,//uid
|
||||
groupCode: string,//gc
|
||||
fromNick: string,//gc name
|
||||
sig: string,
|
||||
|
||||
export interface TempOnRecvParams {
|
||||
sessionType: number,//1
|
||||
chatType: ChatType,//100
|
||||
peerUid: string,//uid
|
||||
groupCode: string,//gc
|
||||
fromNick: string,//gc name
|
||||
sig: string,
|
||||
|
||||
}
|
||||
export interface IKernelMsgListener {
|
||||
onAddSendMsg(msgRecord: RawMessage): void;
|
||||
@@ -158,7 +158,41 @@ export interface IKernelMsgListener {
|
||||
|
||||
onRichMediaUploadComplete(fileTransNotifyInfo: unknown): void;
|
||||
|
||||
onSearchGroupFileInfoUpdate(searchGroupFileResult: unknown): void;
|
||||
onSearchGroupFileInfoUpdate(searchGroupFileResult:
|
||||
{
|
||||
result: {
|
||||
retCode: number,
|
||||
retMsg: string,
|
||||
clientWording: string
|
||||
},
|
||||
syncCookie: string,
|
||||
totalMatchCount: number,
|
||||
ownerMatchCount: number,
|
||||
isEnd: boolean,
|
||||
reqId: number,
|
||||
item: Array<{
|
||||
groupCode: string,
|
||||
groupName: string,
|
||||
uploaderUin: string,
|
||||
uploaderName: string,
|
||||
matchUin: string,
|
||||
matchWords: Array<unknown>,
|
||||
fileNameHits: Array<{
|
||||
start: number,
|
||||
end: number
|
||||
}>,
|
||||
fileModelId: string,
|
||||
fileId: string,
|
||||
fileName: string,
|
||||
fileSize: string,
|
||||
busId: number,
|
||||
uploadTime: number,
|
||||
modifyTime: number,
|
||||
deadTime: number,
|
||||
downloadTimes: number,
|
||||
localPath: string
|
||||
}>
|
||||
}): void;
|
||||
|
||||
onSendMsgError(j2: unknown, contact: unknown, i2: unknown, str: unknown): void;
|
||||
|
||||
@@ -192,7 +226,7 @@ export interface IKernelMsgListener {
|
||||
|
||||
}
|
||||
|
||||
export interface NodeIKernelMsgListener {
|
||||
export interface NodeIKernelMsgListener extends IKernelMsgListener {
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-new
|
||||
new(listener: IKernelMsgListener): NodeIKernelMsgListener;
|
||||
}
|
||||
|
@@ -1,8 +1,8 @@
|
||||
import { User } from '@/core/entities';
|
||||
import { User, UserDetailInfoListenerArg } from '@/core/entities';
|
||||
|
||||
interface IProfileListener {
|
||||
onProfileSimpleChanged(...args: unknown[]): void;
|
||||
|
||||
onUserDetailInfoChanged(arg: UserDetailInfoListenerArg): void;
|
||||
onProfileDetailInfoChanged(profile: User): void;
|
||||
|
||||
onStatusUpdate(...args: unknown[]): void;
|
||||
@@ -18,6 +18,9 @@ export interface NodeIKernelProfileListener extends IProfileListener {
|
||||
}
|
||||
|
||||
export class ProfileListener implements IProfileListener {
|
||||
onUserDetailInfoChanged(arg: UserDetailInfoListenerArg): void {
|
||||
|
||||
}
|
||||
onProfileSimpleChanged(...args: unknown[]) {
|
||||
|
||||
}
|
||||
|
44
src/core/src/listeners/NodeIKernelRecentContactListener.ts
Normal file
44
src/core/src/listeners/NodeIKernelRecentContactListener.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
interface IKernelRecentContactListener {
|
||||
onDeletedContactsNotify(...args: unknown[]): unknown;
|
||||
|
||||
onRecentContactNotification(...args: unknown[]): unknown;
|
||||
|
||||
onMsgUnreadCountUpdate(...args: unknown[]): unknown;
|
||||
|
||||
onGuildDisplayRecentContactListChanged(...args: unknown[]): unknown;
|
||||
|
||||
onRecentContactListChanged(...args: unknown[]): unknown;
|
||||
|
||||
onRecentContactListChangedVer2(...args: unknown[]): unknown;
|
||||
}
|
||||
|
||||
export interface NodeIKernelRecentContactListener extends IKernelRecentContactListener {
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-new
|
||||
new(listener: IKernelRecentContactListener): NodeIKernelRecentContactListener;
|
||||
}
|
||||
|
||||
export class KernelRecentContactListener implements IKernelRecentContactListener {
|
||||
onDeletedContactsNotify(...args: unknown[]) {
|
||||
|
||||
}
|
||||
|
||||
onRecentContactNotification(...args: unknown[]) {
|
||||
|
||||
}
|
||||
|
||||
onMsgUnreadCountUpdate(...args: unknown[]) {
|
||||
|
||||
}
|
||||
|
||||
onGuildDisplayRecentContactListChanged(...args: unknown[]) {
|
||||
|
||||
}
|
||||
|
||||
onRecentContactListChanged(...args: unknown[]) {
|
||||
|
||||
}
|
||||
|
||||
onRecentContactListChangedVer2(...args: unknown[]) {
|
||||
|
||||
}
|
||||
}
|
@@ -1,19 +1,35 @@
|
||||
import { Friend } from '@/core/entities';
|
||||
import { GeneralCallResult } from '@/core/services/common';
|
||||
import { NodeIKernelBuddyListener } from '@/core/listeners';
|
||||
|
||||
export enum BuddyListReqType {
|
||||
KNOMAL,
|
||||
KLETTER
|
||||
}
|
||||
export interface NodeIKernelBuddyService {
|
||||
// 以下为自行添加的,wrapper.node中并没有这些方法,目的是简化调用
|
||||
friends: Friend[];
|
||||
|
||||
getFriend(uidOrUin: string): Promise<Friend>;
|
||||
|
||||
// 26702 以上
|
||||
getBuddyListV2(callFrom: string, reqType: BuddyListReqType): Promise<GeneralCallResult>;
|
||||
//26702 以上
|
||||
getBuddyListFromCache(callFrom: string): Promise<Array<
|
||||
{
|
||||
categoryId: number,//9999应该跳过 那是兜底数据吧
|
||||
categorySortId: number,//排序方式
|
||||
categroyName: string,//分类名
|
||||
categroyMbCount: number,//不懂
|
||||
onlineCount: number,//在线数目
|
||||
buddyUids: Array<string>//Uids
|
||||
}>>;
|
||||
// 以下为原生方法
|
||||
addKernelBuddyListener(listener: NodeIKernelBuddyListener): number;
|
||||
|
||||
getAllBuddyCount(): number;
|
||||
|
||||
removeKernelBuddyListener(listener: unknown): void;
|
||||
|
||||
getBuddyList(bool: boolean): Promise<GeneralCallResult>;
|
||||
/**
|
||||
* @deprecated
|
||||
* @param nocache 使用缓存
|
||||
*/
|
||||
getBuddyList(nocache: boolean): Promise<GeneralCallResult>;
|
||||
|
||||
getBuddyNick(uid: number): string;
|
||||
|
||||
|
3
src/core/src/services/NodeIKernelECDHService.ts
Normal file
3
src/core/src/services/NodeIKernelECDHService.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export interface NodeIKernelECDHService{
|
||||
|
||||
}
|
@@ -9,6 +9,44 @@ import {
|
||||
import { GeneralCallResult } from '@/core/services/common';
|
||||
|
||||
export interface NodeIKernelGroupService {
|
||||
getGroupMemberLevelInfo(groupCode: string): Promise<unknown>;
|
||||
//26702
|
||||
getGroupHonorList(groupCodes: Array<string>): unknown;
|
||||
getUinByUids(uins: string[]): Promise<unknown>;
|
||||
getUidByUins(uins: string[]): Promise<unknown>;
|
||||
//26702(其实更早 但是我不知道)
|
||||
checkGroupMemberCache(arrayList: Array<string>): Promise<unknown>;
|
||||
//26702(其实更早 但是我不知道)
|
||||
getGroupLatestEssenceList(groupCode: string): Promise<unknown>;
|
||||
//26702(其实更早 但是我不知道)
|
||||
shareDigest(Req: {
|
||||
appId: string,
|
||||
appType: number,
|
||||
msgStyle: number,
|
||||
recvUin: string,
|
||||
sendType: number,
|
||||
clientInfo: {
|
||||
platform: number
|
||||
},
|
||||
richMsg: {
|
||||
usingArk: boolean,
|
||||
title: string,
|
||||
summary: string,
|
||||
url: string,
|
||||
pictureUrl: string,
|
||||
brief: string
|
||||
}
|
||||
}): Promise<unknown>;
|
||||
//26702(其实更早 但是我不知道)
|
||||
isEssenceMsg(Req: { groupCode: string, msgRandom: number, msgSeq: number }): Promise<unknown>;
|
||||
//26702(其实更早 但是我不知道)
|
||||
queryCachedEssenceMsg(Req: { groupCode: string, msgRandom: number, msgSeq: number }): Promise<unknown>;
|
||||
//26702(其实更早 但是我不知道)
|
||||
fetchGroupEssenceList(Req: { groupCode: string, pageStart: number, pageLimit: number }, Arg: unknown): Promise<unknown>;
|
||||
//26702
|
||||
getAllMemberList(groupCode: string, refresh: boolean): Promise<unknown>;
|
||||
|
||||
setHeader(uid: string, path: string): unknown;
|
||||
|
||||
addKernelGroupListener(listener: NodeIKernelGroupListener): number;
|
||||
|
||||
@@ -38,7 +76,7 @@ export interface NodeIKernelGroupService {
|
||||
|
||||
modifyMemberCardName(groupCode: string, uid: string, cardName: string): void;
|
||||
|
||||
getTransferableMemberInfo(uid: string): unknown;
|
||||
getTransferableMemberInfo(groupCode: string): unknown;//获取整个群的
|
||||
|
||||
transferGroup(uid: string): void;
|
||||
|
||||
|
3
src/core/src/services/NodeIKernelMSFService.ts
Normal file
3
src/core/src/services/NodeIKernelMSFService.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export interface NodeIKernelMSFService {
|
||||
getServerTime(): string;
|
||||
}
|
@@ -1,11 +1,24 @@
|
||||
import { ChatType, ElementType, Peer, RawMessage, SendMessageElement } from '@/core/entities';
|
||||
import { ElementType, MessageElement, Peer, RawMessage, SendMessageElement } from '@/core/entities';
|
||||
import { NodeIKernelMsgListener } from '@/core/listeners/NodeIKernelMsgListener';
|
||||
import { GeneralCallResult } from '@/core/services/common';
|
||||
|
||||
export interface QueryMsgsParams {
|
||||
chatInfo: Peer,
|
||||
filterMsgType: [],
|
||||
filterSendersUid: [],
|
||||
filterMsgFromTime: string,
|
||||
filterMsgToTime: string,
|
||||
pageLimit: number,
|
||||
isReverseOrder: boolean,
|
||||
isIncludeCurrent: boolean
|
||||
}
|
||||
export interface NodeIKernelMsgService {
|
||||
|
||||
generateMsgUniqueId(chatType: number, time: string): string;
|
||||
|
||||
addKernelMsgListener(nodeIKernelMsgListener: NodeIKernelMsgListener): number;
|
||||
|
||||
sendMsg(msgId: string, peer: Peer, msgElements: SendMessageElement[], map: Map<any, any>): Promise<unknown>;
|
||||
sendMsg(msgId: string, peer: Peer, msgElements: SendMessageElement[], map: Map<any, any>): Promise<GeneralCallResult>;
|
||||
|
||||
recallMsg(peer: Peer, msgIds: string[]): Promise<GeneralCallResult>;
|
||||
|
||||
@@ -21,7 +34,7 @@ export interface NodeIKernelMsgService {
|
||||
|
||||
getAutoReplyTextList(...args: unknown[]): unknown;
|
||||
|
||||
getOnLineDev(): Promise<any>;
|
||||
getOnLineDev(): void;
|
||||
|
||||
kickOffLine(DevInfo: Object): unknown;
|
||||
|
||||
@@ -73,7 +86,7 @@ export interface NodeIKernelMsgService {
|
||||
|
||||
cancelSendMsg(...args: unknown[]): unknown;
|
||||
|
||||
switchToOfflineSendMsg(...args: unknown[]): unknown;
|
||||
switchToOfflineSendMsg(peer: Peer, MsgId: string): unknown;
|
||||
|
||||
reqToOfflineSendMsg(...args: unknown[]): unknown;
|
||||
|
||||
@@ -115,9 +128,9 @@ export interface NodeIKernelMsgService {
|
||||
|
||||
addLocalTofuRecordMsg(...args: unknown[]): unknown;
|
||||
|
||||
addLocalRecordMsg(...args: unknown[]): unknown;
|
||||
addLocalRecordMsg(Peer: Peer, msgId: string, ele: MessageElement, attr: Array<any> | number, front: boolean): Promise<unknown>;
|
||||
|
||||
deleteMsg(...args: unknown[]): unknown;
|
||||
deleteMsg(Peer: Peer, msgIds: Array<string>): Promise<any>;
|
||||
|
||||
updateElementExtBufForUI(...args: unknown[]): unknown;
|
||||
|
||||
@@ -141,7 +154,7 @@ export interface NodeIKernelMsgService {
|
||||
|
||||
getLastMessageList(peer: Peer[]): Promise<unknown>;
|
||||
|
||||
getAioFirstViewLatestMsgs(...args: unknown[]): unknown;
|
||||
getAioFirstViewLatestMsgs(peer: Peer, num: number): unknown;
|
||||
|
||||
getMsgs(peer: Peer, msgId: string, count: unknown, queryOrder: boolean): Promise<unknown>;
|
||||
|
||||
@@ -154,7 +167,7 @@ export interface NodeIKernelMsgService {
|
||||
// this.$clientSeq = j3;
|
||||
// this.$cnt = i2;
|
||||
|
||||
getMsgsWithMsgTimeAndClientSeqForC2C(...args: unknown[]): Promise<unknown>;
|
||||
getMsgsWithMsgTimeAndClientSeqForC2C(...args: unknown[]): Promise<GeneralCallResult & { msgList: RawMessage[] }>;
|
||||
|
||||
getMsgsWithStatus(params: {
|
||||
peer: Peer
|
||||
@@ -164,9 +177,9 @@ export interface NodeIKernelMsgService {
|
||||
queryOrder: boolean
|
||||
isIncludeSelf: boolean
|
||||
appid: unknown
|
||||
}): Promise<unknown>;
|
||||
}): Promise<GeneralCallResult & { msgList: RawMessage[] }>;
|
||||
|
||||
getMsgsBySeqRange(peer: Peer, startSeq: string, endSeq: string): Promise<unknown>;
|
||||
getMsgsBySeqRange(peer: Peer, startSeq: string, endSeq: string): Promise<GeneralCallResult & { msgList: RawMessage[] }>;
|
||||
|
||||
getMsgsBySeqAndCount(peer: Peer, seq: string, count: number, desc: boolean, unknownArg: boolean): Promise<GeneralCallResult & { msgList: RawMessage[] }>;
|
||||
|
||||
@@ -174,39 +187,48 @@ export interface NodeIKernelMsgService {
|
||||
|
||||
getRecallMsgsByMsgId(peer: Peer, MsgId: string[]): Promise<unknown>;
|
||||
|
||||
getMsgsBySeqList(peer: Peer, seqList: string[]): Promise<unknown>;
|
||||
getMsgsBySeqList(peer: Peer, seqList: string[]): Promise<GeneralCallResult & { msgList: RawMessage[] }>;
|
||||
|
||||
getSingleMsg(Peer: Peer, msgSeq: string): unknown;
|
||||
getSingleMsg(Peer: Peer, msgSeq: string): Promise<GeneralCallResult & { msgList: RawMessage[] }>;
|
||||
|
||||
getSourceOfReplyMsg(...args: unknown[]): unknown;
|
||||
getSourceOfReplyMsg(peer: Peer, MsgId: string, SourceSeq: string): unknown;
|
||||
|
||||
getSourceOfReplyMsgV2(...args: unknown[]): unknown;
|
||||
getSourceOfReplyMsgV2(peer: Peer, RootMsgId: string, ReplyMsgId: string): unknown;
|
||||
|
||||
getMsgByClientSeqAndTime(...args: unknown[]): unknown;
|
||||
getMsgByClientSeqAndTime(peer: Peer, clientSeq: string, time: string): unknown;
|
||||
|
||||
getSourceOfReplyMsgByClientSeqAndTime(...args: unknown[]): unknown;
|
||||
getSourceOfReplyMsgByClientSeqAndTime(peer: Peer, clientSeq: string, time: string): unknown;
|
||||
//cnt clientSeq?并不是吧
|
||||
getMsgsByTypeFilter(peer: Peer, msgId: string, cnt: unknown, queryOrder: boolean, typeFilter: { type: number, subtype: Array<number> }): unknown;
|
||||
|
||||
getMsgsByTypeFilter(peer: Peer, msgId: string, cnt: unknown, queryOrder: boolean, typeFilters: unknown): unknown;
|
||||
|
||||
getMsgsByTypeFilters(...args: unknown[]): unknown;
|
||||
getMsgsByTypeFilters(peer: Peer, msgId: string, cnt: unknown, queryOrder: boolean, typeFilters: Array<{ type: number, subtype: Array<number> }>): unknown;
|
||||
|
||||
getMsgWithAbstractByFilterParam(...args: unknown[]): unknown;
|
||||
|
||||
queryMsgsWithFilter(...args: unknown[]): unknown;
|
||||
|
||||
queryMsgsWithFilterVer2(MsgId: string, MsgTime: string, param: {
|
||||
chatInfo: {
|
||||
chatType: number,
|
||||
peerUid: string
|
||||
},
|
||||
filterMsgType: [],
|
||||
filterSendersUid: [],
|
||||
filterMsgFromTime: string,
|
||||
filterMsgToTime: string,
|
||||
pageLimit: number,
|
||||
isReverseOrder: boolean,
|
||||
isIncludeCurrent: boolean
|
||||
}): Promise<unknown>;
|
||||
/**
|
||||
* @deprecated 该函数已被标记为废弃,请使用新的替代方法。
|
||||
* 使用过滤条件查询消息列表的版本2接口。
|
||||
*
|
||||
* 该函数通过一系列过滤条件来查询特定聊天中的消息列表。这些条件包括消息类型、发送者、时间范围等。
|
||||
* 函数返回一个Promise,解析为查询结果的未知类型对象。
|
||||
*
|
||||
* @param MsgId 消息ID,用于特定消息的查询。
|
||||
* @param MsgTime 消息时间,用于指定消息的时间范围。
|
||||
* @param param 查询参数对象,包含详细的过滤条件和分页信息。
|
||||
* @param param.chatInfo 聊天信息,包括聊天类型和对方用户ID。
|
||||
* @param param.filterMsgType 需要过滤的消息类型数组,留空表示不过滤。
|
||||
* @param param.filterSendersUid 需要过滤的发送者用户ID数组。
|
||||
* @param param.filterMsgFromTime 查询消息的起始时间。
|
||||
* @param param.filterMsgToTime 查询消息的结束时间。
|
||||
* @param param.pageLimit 每页的消息数量限制。
|
||||
* @param param.isReverseOrder 是否按时间顺序倒序返回消息。
|
||||
* @param param.isIncludeCurrent 是否包含当前页码。
|
||||
* @returns 返回一个Promise,解析为查询结果的未知类型对象。
|
||||
*/
|
||||
queryMsgsWithFilterVer2(MsgId: string, MsgTime: string, param: QueryMsgsParams): Promise<unknown>;
|
||||
|
||||
// this.chatType = i2;
|
||||
// this.peerUid = str;
|
||||
|
||||
@@ -222,46 +244,23 @@ export interface NodeIKernelMsgService {
|
||||
// this.isReverseOrder = z;
|
||||
// this.isIncludeCurrent = z2;
|
||||
//queryMsgsWithFilterEx(0L, 0L, 0L, new QueryMsgsParams(new ChatInfo(2, str), new ArrayList(), new ArrayList(), 0L, 0L, 250, false, true))
|
||||
queryMsgsWithFilterEx(msgId: string, msgTime: string, megSeq: string, param: {
|
||||
chatInfo: {
|
||||
chatType: number,
|
||||
peerUid: string
|
||||
},
|
||||
filterMsgType: [],
|
||||
filterSendersUid: [],
|
||||
filterMsgFromTime: string,
|
||||
filterMsgToTime: string,
|
||||
pageLimit: number,
|
||||
isReverseOrder: boolean,
|
||||
isIncludeCurrent: boolean
|
||||
|
||||
}): Promise<unknown>;
|
||||
queryMsgsWithFilterEx(msgId: string, msgTime: string, megSeq: string, param: QueryMsgsParams): Promise<GeneralCallResult & {
|
||||
msgList: RawMessage[]
|
||||
}>;
|
||||
//queryMsgsWithFilterEx(this.$msgId, this.$msgTime, this.$msgSeq, this.$param)
|
||||
queryFileMsgsDesktop(...args: unknown[]): unknown;
|
||||
|
||||
setMsgRichInfoFlag(...args: unknown[]): unknown;
|
||||
|
||||
queryPicOrVideoMsgs(msgId: string, msgTime: string, megSeq: string, param: {
|
||||
chatInfo: {
|
||||
chatType: number,
|
||||
peerUid: string
|
||||
},
|
||||
filterMsgType: [],
|
||||
filterSendersUid: [],
|
||||
filterMsgFromTime: string,
|
||||
filterMsgToTime: string,
|
||||
pageLimit: number,
|
||||
isReverseOrder: boolean,
|
||||
isIncludeCurrent: boolean
|
||||
}):Promise<unknown>;
|
||||
queryPicOrVideoMsgs(msgId: string, msgTime: string, megSeq: string, param: QueryMsgsParams): Promise<unknown>;
|
||||
|
||||
queryPicOrVideoMsgsDesktop(...args: unknown[]): unknown;
|
||||
|
||||
queryEmoticonMsgs(...args: unknown[]): unknown;
|
||||
queryEmoticonMsgs(msgId: string, msgTime: string, msgSeq: string, Params: QueryMsgsParams): Promise<unknown>;
|
||||
|
||||
queryTroopEmoticonMsgs(...args: unknown[]): unknown;
|
||||
queryTroopEmoticonMsgs(msgId: string, msgTime: string, msgSeq: string, Params: QueryMsgsParams): Promise<unknown>;
|
||||
|
||||
queryMsgsAndAbstractsWithFilter(...args: unknown[]): unknown;
|
||||
queryMsgsAndAbstractsWithFilter(msgId: string, msgTime: string, megSeq: string, param: QueryMsgsParams): unknown;
|
||||
|
||||
setFocusOnGuild(...args: unknown[]): unknown;
|
||||
|
||||
@@ -299,7 +298,7 @@ export interface NodeIKernelMsgService {
|
||||
|
||||
setMsgRead(peer: Peer): Promise<GeneralCallResult>;
|
||||
|
||||
setAllC2CAndGroupMsgRead(...args: unknown[]): unknown;
|
||||
setAllC2CAndGroupMsgRead(): Promise<unknown>;
|
||||
|
||||
setGuildMsgRead(...args: unknown[]): unknown;
|
||||
|
||||
@@ -327,7 +326,7 @@ export interface NodeIKernelMsgService {
|
||||
|
||||
setGuildTabUserFlag(...args: unknown[]): unknown;
|
||||
|
||||
setBuildMode(...args: unknown[]): unknown;
|
||||
setBuildMode(flag: number/*0 1 3*/): unknown;
|
||||
|
||||
setConfigurationServiceData(...args: unknown[]): unknown;
|
||||
|
||||
@@ -355,12 +354,39 @@ export interface NodeIKernelMsgService {
|
||||
getFileThumbSavePathForSend(...args: unknown[]): unknown;
|
||||
|
||||
getFileThumbSavePath(...args: unknown[]): unknown;
|
||||
|
||||
translatePtt2Text(...args: unknown[]): unknown;
|
||||
//猜测居多
|
||||
translatePtt2Text(MsgId: string, Peer: {}, MsgElement: {}): unknown;
|
||||
|
||||
setPttPlayedState(...args: unknown[]): unknown;
|
||||
|
||||
fetchFavEmojiList(...args: unknown[]): unknown;
|
||||
// NodeIQQNTWrapperSession fetchFavEmojiList [
|
||||
// "",
|
||||
// 48,
|
||||
// true,
|
||||
// true
|
||||
// ]
|
||||
fetchFavEmojiList(str: string, num: number, uk1: boolean, uk2: boolean): Promise<GeneralCallResult & {
|
||||
emojiInfoList: Array<{
|
||||
uin: string,
|
||||
emoId: number,
|
||||
emoPath: string,
|
||||
isExist: boolean,
|
||||
resId: string,
|
||||
url: string,
|
||||
md5: string,
|
||||
emoOriginalPath: string,
|
||||
thumbPath: string,
|
||||
RomaingType: string,
|
||||
isAPNG: false,
|
||||
isMarkFace: false,
|
||||
eId: string,
|
||||
epId: string,
|
||||
ocrWord: string,
|
||||
modifyWord: string,
|
||||
exposeNum: number,
|
||||
clickNum: number,
|
||||
desc: string
|
||||
}>
|
||||
}>;
|
||||
|
||||
addFavEmoji(...args: unknown[]): unknown;
|
||||
|
||||
@@ -406,7 +432,7 @@ export interface NodeIKernelMsgService {
|
||||
|
||||
getEmojiResourcePath(...args: unknown[]): unknown;
|
||||
|
||||
JoinDragonGroupEmoji(...args: unknown[]): unknown;
|
||||
JoinDragonGroupEmoji(JoinDragonGroupEmojiReq: any/*joinDragonGroupEmojiReq*/): unknown;
|
||||
|
||||
getMsgAbstracts(...args: unknown[]): unknown;
|
||||
|
||||
@@ -430,7 +456,10 @@ export interface NodeIKernelMsgService {
|
||||
|
||||
downloadRichMedia(...args: unknown[]): unknown;
|
||||
|
||||
getFirstUnreadMsgSeq(...args: unknown[]): unknown;
|
||||
getFirstUnreadMsgSeq(args: {
|
||||
peerUid: string
|
||||
guildId: string
|
||||
}): unknown;
|
||||
|
||||
getFirstUnreadCommonMsg(...args: unknown[]): unknown;
|
||||
|
||||
@@ -448,7 +477,7 @@ export interface NodeIKernelMsgService {
|
||||
|
||||
setMsgEmojiLikes(...args: unknown[]): unknown;
|
||||
|
||||
getMsgEmojiLikesList(...args: unknown[]): unknown;
|
||||
getMsgEmojiLikesList(peer: Peer, msgSeq: string, emojiId: string, emojiType: string, cookie: string, bForward: boolean, number: number): Promise<unknown>;
|
||||
|
||||
setMsgEmojiLikesForRole(...args: unknown[]): unknown;
|
||||
|
||||
@@ -470,13 +499,13 @@ export interface NodeIKernelMsgService {
|
||||
|
||||
queryCalendar(...args: unknown[]): unknown;
|
||||
|
||||
queryFirstMsgSeq(...args: unknown[]): unknown;
|
||||
queryFirstMsgSeq(peer: Peer, ...args: unknown[]): unknown;
|
||||
|
||||
queryRoamCalendar(...args: unknown[]): unknown;
|
||||
|
||||
queryFirstRoamMsg(...args: unknown[]): unknown;
|
||||
|
||||
fetchLongMsg(...args: unknown[]): unknown;
|
||||
fetchLongMsg(peer: Peer, msgId: string): unknown;
|
||||
|
||||
fetchLongMsgWithCb(...args: unknown[]): unknown;
|
||||
|
||||
@@ -510,9 +539,9 @@ export interface NodeIKernelMsgService {
|
||||
|
||||
deleteReplyDraft(...args: unknown[]): unknown;
|
||||
|
||||
getFirstUnreadAtMsg(...args: unknown[]): unknown;
|
||||
getFirstUnreadAtMsg(peer: Peer): unknown;
|
||||
|
||||
clearMsgRecords(...args: unknown[]): unknown;//设置已读后调用我觉得比较好 清理记录
|
||||
clearMsgRecords(...args: unknown[]): unknown;//设置已读后调用我觉得比较好 清理记录 现在别了
|
||||
|
||||
IsExistOldDb(...args: unknown[]): unknown;
|
||||
|
||||
@@ -532,7 +561,7 @@ export interface NodeIKernelMsgService {
|
||||
|
||||
getCurChatImportStatusByUin(...args: unknown[]): unknown;
|
||||
|
||||
getDataImportUserLevel(...args: unknown[]): unknown;
|
||||
getDataImportUserLevel(): unknown;
|
||||
|
||||
getMsgQRCode(...args: unknown[]): unknown;
|
||||
|
||||
@@ -563,6 +592,8 @@ export interface NodeIKernelMsgService {
|
||||
// this.selfPhone = str5;
|
||||
// this.gameSession = tempChatGameSession;
|
||||
prepareTempChat(args: unknown): unknown;//主动临时消息 不做
|
||||
|
||||
sendSsoCmdReqByContend(cmd: string, param: string): Promise<unknown>;
|
||||
|
||||
//chattype,uid->Promise<any>
|
||||
getTempChatInfo(ChatType: number, Uid: string): unknown;
|
||||
@@ -587,7 +618,7 @@ export interface NodeIKernelMsgService {
|
||||
|
||||
getABatchOfContactMsgBoxInfo(...args: unknown[]): unknown;
|
||||
|
||||
insertMsgToMsgBox(...args: unknown[]): unknown;
|
||||
insertMsgToMsgBox(peer: Peer, msgId: string, arg: 2006): unknown;
|
||||
|
||||
isHitEmojiKeyword(...args: unknown[]): unknown;
|
||||
|
||||
@@ -627,7 +658,25 @@ export interface NodeIKernelMsgService {
|
||||
|
||||
dataMigrationStopOperation(...args: unknown[]): unknown;
|
||||
|
||||
dataMigrationImportMsgPbRecord(...args: unknown[]): unknown;
|
||||
//新的希望
|
||||
dataMigrationImportMsgPbRecord(DataMigrationMsgInfo: Array<{
|
||||
extensionData: string//"Hex"
|
||||
extraData: string //""
|
||||
chatType: number
|
||||
chatUin: string
|
||||
msgType: number
|
||||
msgTime: string
|
||||
msgSeq: string
|
||||
msgRandom: string
|
||||
}>, DataMigrationResourceInfo: {
|
||||
extraData: string
|
||||
filePath: string
|
||||
fileSize: string
|
||||
msgRandom: string
|
||||
msgSeq: string
|
||||
msgSubType: number
|
||||
msgType: number
|
||||
}): unknown;
|
||||
|
||||
dataMigrationGetResourceLocalDestinyPath(...args: unknown[]): unknown;
|
||||
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import { BuddyProfileLikeReq } from "../entities/user";
|
||||
import { GeneralCallResult } from "./common";
|
||||
|
||||
export interface NodeIKernelProfileLikeService {
|
||||
addKernelProfileLikeListener(listener: NodeIKernelProfileLikeService): void;
|
||||
@@ -7,7 +8,13 @@ export interface NodeIKernelProfileLikeService {
|
||||
|
||||
setBuddyProfileLike(...args: unknown[]): { result: number, errMsg: string, succCounts: number };
|
||||
|
||||
getBuddyProfileLike(req: BuddyProfileLikeReq): void;
|
||||
getBuddyProfileLike(req: BuddyProfileLikeReq): Promise<GeneralCallResult & {
|
||||
"info": {
|
||||
"userLikeInfos": Array<any>,
|
||||
"friendMaxVotes": number,
|
||||
"start": number
|
||||
}
|
||||
}>;
|
||||
|
||||
getProfileLikeScidResourceInfo(...args: unknown[]): void;
|
||||
|
||||
|
@@ -2,8 +2,19 @@ import { AnyCnameRecord } from 'node:dns';
|
||||
import { BizKey, ModifyProfileParams, UserDetailInfoByUin } from '../entities';
|
||||
import { NodeIKernelProfileListener } from '../listeners';
|
||||
import { GeneralCallResult } from '@/core/services/common';
|
||||
|
||||
export enum UserDetailSource {
|
||||
KDB,
|
||||
KSERVER
|
||||
}
|
||||
export enum ProfileBizType {
|
||||
KALL,
|
||||
KBASEEXTEND,
|
||||
KVAS,
|
||||
KQZONE,
|
||||
KOTHER
|
||||
}
|
||||
export interface NodeIKernelProfileService {
|
||||
fetchUserDetailInfo(trace: string, uids: string[], arg2: number, arg3: number[]): Promise<unknown>;
|
||||
|
||||
addKernelProfileListener(listener: NodeIKernelProfileListener): number;
|
||||
|
||||
|
@@ -1,5 +1,28 @@
|
||||
import { Peer } from "../entities";
|
||||
|
||||
import { NodeIKernelRecentContactListener } from "../listeners/NodeIKernelRecentContactListener";
|
||||
import { GeneralCallResult } from "./common";
|
||||
export interface FSABRecentContactParams {
|
||||
anchorPointContact: {
|
||||
contactId: string;
|
||||
sortField: string;
|
||||
pos: number;
|
||||
},
|
||||
relativeMoveCount: number;
|
||||
listType: number;
|
||||
count: number;
|
||||
fetchOld: boolean;
|
||||
}
|
||||
// {
|
||||
// "anchorPointContact": {
|
||||
// "contactId": "",
|
||||
// "sortField": "",
|
||||
// "pos": 0
|
||||
// },
|
||||
// "relativeMoveCount": 0,
|
||||
// "listType": 1,
|
||||
// "count": 200,
|
||||
// "fetchOld": true
|
||||
// }
|
||||
export interface NodeIKernelRecentContactService {
|
||||
setGuildDisplayStatus(...args: unknown[]): unknown; // 2 arguments
|
||||
|
||||
@@ -11,7 +34,14 @@ export interface NodeIKernelRecentContactService {
|
||||
|
||||
enterOrExitMsgList(...args: unknown[]): unknown; // 1 arguments
|
||||
|
||||
getRecentContactListSnapShot(...args: unknown[]): unknown; // 1 arguments
|
||||
/*!---!*/getRecentContactListSnapShot(count: number): Promise<GeneralCallResult & {
|
||||
info: {
|
||||
errCode: number,
|
||||
errMsg: string,
|
||||
sortedContactList: Array<number>,
|
||||
changedList: Array<any>
|
||||
}
|
||||
}>; // 1 arguments
|
||||
|
||||
clearMsgUnreadCount(...args: unknown[]): unknown; // 1 arguments
|
||||
|
||||
@@ -19,7 +49,7 @@ export interface NodeIKernelRecentContactService {
|
||||
|
||||
jumpToSpecifyRecentContact(...args: unknown[]): unknown; // 1 arguments
|
||||
|
||||
fetchAndSubscribeABatchOfRecentContact(...args: unknown[]): unknown; // 1 arguments
|
||||
/*!---!*/fetchAndSubscribeABatchOfRecentContact(params: FSABRecentContactParams): unknown; // 1 arguments
|
||||
|
||||
addRecentContact(peer: Peer): unknown;
|
||||
|
||||
@@ -31,9 +61,9 @@ export interface NodeIKernelRecentContactService {
|
||||
|
||||
updateGameMsgConfigs(...args: unknown[]): unknown; // 1 arguments
|
||||
|
||||
removeKernelRecentContactListener(...args: unknown[]): unknown; // 1 arguments
|
||||
removeKernelRecentContactListener(listenerid: number): unknown; // 1 arguments
|
||||
|
||||
addKernelRecentContactListener(...args: unknown[]): unknown; // 1 arguments
|
||||
addKernelRecentContactListener(listener: NodeIKernelRecentContactListener): void;
|
||||
|
||||
clearRecentContactsByChatType(...args: unknown[]): unknown; // 1 arguments
|
||||
|
||||
@@ -43,7 +73,7 @@ export interface NodeIKernelRecentContactService {
|
||||
|
||||
deleteRecentContactsVer2(...args: unknown[]): unknown; // 1 arguments
|
||||
|
||||
getRecentContactList(): unknown;
|
||||
getRecentContactList(): Promise<any>;
|
||||
|
||||
getMsgUnreadCount(): unknown;
|
||||
|
||||
|
@@ -1,6 +1,50 @@
|
||||
import { GetFileListParam, Peer } from "../entities";
|
||||
import { GetFileListParam, MessageElement, Peer, SendMessageElement } from "../entities";
|
||||
import { GeneralCallResult } from "./common";
|
||||
|
||||
export enum UrlFileDownloadType {
|
||||
KUNKNOWN,
|
||||
KURLFILEDOWNLOADPRIVILEGEICON,
|
||||
KURLFILEDOWNLOADPHOTOWALL,
|
||||
KURLFILEDOWNLOADQZONE,
|
||||
KURLFILEDOWNLOADCOMMON,
|
||||
KURLFILEDOWNLOADINSTALLAPP
|
||||
}
|
||||
export enum RMBizTypeEnum {
|
||||
KUNKNOWN,
|
||||
KC2CFILE,
|
||||
KGROUPFILE,
|
||||
KC2CPIC,
|
||||
KGROUPPIC,
|
||||
KDISCPIC,
|
||||
KC2CVIDEO,
|
||||
KGROUPVIDEO,
|
||||
KC2CPTT,
|
||||
KGROUPPTT,
|
||||
KFEEDCOMMENTPIC,
|
||||
KGUILDFILE,
|
||||
KGUILDPIC,
|
||||
KGUILDPTT,
|
||||
KGUILDVIDEO
|
||||
}
|
||||
export interface CommonFileInfo {
|
||||
bizType: number;
|
||||
chatType: number;
|
||||
elemId: string;
|
||||
favId: string;
|
||||
fileModelId: string;
|
||||
fileName: string;
|
||||
fileSize: string;
|
||||
md5: string;
|
||||
md510m: string;
|
||||
msgId: string;
|
||||
msgTime: string;
|
||||
parent: string;
|
||||
peerUid: string;
|
||||
picThumbPath: Array<string>
|
||||
sha: string;
|
||||
sha3: string;
|
||||
subId: string;
|
||||
uuid: string;
|
||||
}
|
||||
export interface NodeIKernelRichMediaService {
|
||||
//getVideoPlayUrl(peer, msgId, elemId, videoCodecFormat, VideoRequestWay.KHAND, cb);
|
||||
// public enum VideoCodecFormatType {
|
||||
@@ -46,7 +90,7 @@ export interface NodeIKernelRichMediaService {
|
||||
}
|
||||
}>;
|
||||
|
||||
getRichMediaFileDir(arg1: unknown, arg2: unknown, arg3: unknown): unknown;
|
||||
getRichMediaFileDir(elementType: number, downType: number, isTemp: boolean): unknown;
|
||||
|
||||
// this.senderUid = "";
|
||||
// this.peerUid = "";
|
||||
@@ -65,16 +109,43 @@ export interface NodeIKernelRichMediaService {
|
||||
// this.elem = msgElement;
|
||||
// this.useHttps = num;
|
||||
|
||||
getVideoPlayUrlInVisit(arg: unknown): unknown;
|
||||
getVideoPlayUrlInVisit(arg: {
|
||||
downloadType: number,
|
||||
thumbSize: number,
|
||||
msgId: string,
|
||||
msgRandom: string,
|
||||
msgSeq: string,
|
||||
msgTime: string,
|
||||
chatType: number,
|
||||
senderUid: string,
|
||||
peerUid: string,
|
||||
guildId: string,
|
||||
ele: MessageElement,
|
||||
useHttps: boolean
|
||||
}): Promise<unknown>;
|
||||
|
||||
isFileExpired(arg: unknown): unknown;
|
||||
//arg双端number
|
||||
isFileExpired(arg: number): unknown;
|
||||
|
||||
deleteGroupFolder(GroupCode: string, FolderId: string): Promise<GeneralCallResult & { groupFileCommonResult: { retCode: number, retMsg: string, clientWording: string } }>;
|
||||
|
||||
//参数与getVideoPlayUrlInVisit一样
|
||||
downloadRichMediaInVisit(arg: unknown): unknown;
|
||||
|
||||
downloadFileForModelId(peer: Peer, arg: unknown[], arg3: string): unknown;
|
||||
downloadRichMediaInVisit(arg: {
|
||||
downloadType: number,
|
||||
thumbSize: number,
|
||||
msgId: string,
|
||||
msgRandom: string,
|
||||
msgSeq: string,
|
||||
msgTime: string,
|
||||
chatType: number,
|
||||
senderUid: string,
|
||||
peerUid: string,
|
||||
guildId: string,
|
||||
ele: MessageElement,
|
||||
useHttps: boolean
|
||||
}): unknown;
|
||||
//arg3为“”
|
||||
downloadFileForModelId(peer: Peer, ModelId: string[], arg3: string): unknown;
|
||||
//第三个参数 Array<Type>
|
||||
// this.fileId = "";
|
||||
// this.fileName = "";
|
||||
@@ -83,25 +154,30 @@ export interface NodeIKernelRichMediaService {
|
||||
// this.fileSize = j2;
|
||||
// this.fileModelId = j3;
|
||||
|
||||
downloadFileForFileUuid(peer: Peer, arg1: string, arg3: unknown[]): unknown;
|
||||
downloadFileForFileUuid(peer: Peer, uuid: string, arg3: {
|
||||
fileId: string,
|
||||
fileName: string,
|
||||
fileSize: string,
|
||||
fileModelId: string
|
||||
}[]): Promise<unknown>;
|
||||
|
||||
downloadFileByUrlListtransgroupfile(arg1: unknown, arg2: unknown): unknown;
|
||||
downloadFileByUrlList(fileDownloadTyp: UrlFileDownloadType, urlList: Array<string>): unknown;
|
||||
|
||||
downloadFileForFileInfotransgroupfile(arg1: unknown, arg2: unknown): unknown;
|
||||
downloadFileForFileInfo(fileInfo: CommonFileInfo[], savePath: string): unknown;
|
||||
|
||||
createGroupFolder(GroupCode: string, FolderName: string): Promise<GeneralCallResult & { resultWithGroupItem: { result: any, groupItem: Array<any> } }>
|
||||
|
||||
downloadFile(arg1: unknown, arg2: unknown, arg3: unknown, arg4: unknown): unknown;
|
||||
downloadFile(commonFile: CommonFileInfo, arg2: unknown, arg3: unknown, savePath: string): unknown;
|
||||
|
||||
createGroupFoldertransgroupfile(arg1: unknown, arg2: unknown): unknown;
|
||||
createGroupFolder(arg1: unknown, arg2: unknown): unknown;
|
||||
|
||||
downloadGroupFolder(arg1: unknown, arg2: unknown, arg3: unknown): unknown;
|
||||
|
||||
renameGroupFolder(arg1: unknown, arg2: unknown, arg3: unknown): unknown;
|
||||
|
||||
deleteGroupFoldertransgroupfile(arg1: unknown, arg2: unknown): unknown;
|
||||
deleteGroupFolder(arg1: unknown, arg2: unknown): unknown;
|
||||
|
||||
deleteTransferInfotransgroupfile(arg1: unknown, arg2: unknown): unknown;
|
||||
deleteTransferInfo(arg1: unknown, arg2: unknown): unknown;
|
||||
|
||||
cancelTransferTask(arg1: unknown, arg2: unknown, arg3: unknown): unknown;
|
||||
|
||||
@@ -122,11 +198,9 @@ export interface NodeIKernelRichMediaService {
|
||||
}
|
||||
}>;
|
||||
|
||||
getGroupFileInfotransgroupfile(arg1: unknown, arg2: unknown): unknown;
|
||||
getGroupFileInfo(arg1: unknown, arg2: unknown): unknown;
|
||||
|
||||
getGroupFileListtransgroupfile(arg1: unknown, arg2: unknown): unknown;
|
||||
|
||||
getGroupTransferListtransgroupfile(arg1: unknown, arg2: unknown): unknown;
|
||||
getGroupTransferList(arg1: unknown, arg2: unknown): unknown;
|
||||
|
||||
renameGroupFile(arg1: unknown, arg2: unknown, arg3: unknown, arg4: unknown, arg5: unknown): unknown;
|
||||
|
||||
@@ -134,6 +208,16 @@ export interface NodeIKernelRichMediaService {
|
||||
|
||||
transGroupFile(arg1: unknown, arg2: unknown): unknown;
|
||||
|
||||
searchGroupFile(
|
||||
keywords: Array<string>,
|
||||
param: {
|
||||
groupIds: Array<string>,
|
||||
fileType: number,
|
||||
context: string,
|
||||
count: number,
|
||||
sortType: number,
|
||||
groupNames: Array<string>
|
||||
}): Promise<unknown>;
|
||||
searchGroupFileByWord(arg1: unknown, arg2: unknown, arg3: unknown, arg4: unknown, arg5: unknown): unknown;
|
||||
|
||||
deleteGroupFile(GroupCode: string, params: Array<number>, Files: Array<string>): Promise<GeneralCallResult & {
|
||||
@@ -152,19 +236,31 @@ export interface NodeIKernelRichMediaService {
|
||||
|
||||
queryPicDownloadSize(arg: unknown): unknown;
|
||||
|
||||
searchGroupFiletransgroupfile(arg1: unknown, arg2: unknown): unknown;
|
||||
searchGroupFile(arg1: unknown, arg2: unknown): unknown;
|
||||
|
||||
searchMoreGroupFile(arg: unknown): unknown;
|
||||
|
||||
cancelSearcheGroupFile(arg1: unknown, arg2: unknown, arg3: unknown): unknown;
|
||||
|
||||
onlyDownloadFile(arg1: unknown, arg2: unknown, arg3: unknown): unknown;
|
||||
onlyDownloadFile(peer: Peer, arg2: unknown, arg3: Array<{
|
||||
fileId: string,
|
||||
fileName: string,
|
||||
fileSize: string,
|
||||
fileModelId: string
|
||||
}
|
||||
>): unknown;
|
||||
|
||||
onlyUploadFiletransgroupfile(arg1: unknown, arg2: unknown): unknown;
|
||||
onlyUploadFile(arg1: unknown, arg2: unknown): unknown;
|
||||
|
||||
isExtraLargePic(arg1: unknown, arg2: unknown, arg3: unknown): unknown;
|
||||
|
||||
uploadRMFileWithoutMsg(arg: unknown): unknown;
|
||||
uploadRMFileWithoutMsg(arg: {
|
||||
bizType: RMBizTypeEnum,
|
||||
filePath: string,
|
||||
peerUid: string,
|
||||
transferId: string
|
||||
useNTV2: string
|
||||
}): Promise<unknown>;
|
||||
|
||||
isNull(): boolean;
|
||||
}
|
@@ -30,5 +30,6 @@ export interface NodeIKernelRobotService {
|
||||
setRobotPickTts(arg1: unknown, arg2: unknown): unknown;
|
||||
|
||||
getRobotUinRange(data: any): Promise<{ response: { robotUinRanges: any } }>
|
||||
|
||||
isNull(): boolean;
|
||||
}
|
||||
}
|
||||
|
@@ -1,79 +1,129 @@
|
||||
export interface NodeIKernelSearchService{
|
||||
addKernelSearchListenerr(...args: any[]): unknown;// needs 1 arguments
|
||||
import { ChatType } from "../entities";
|
||||
|
||||
removeKernelSearchListenerr(...args: any[]): unknown;// needs 1 arguments
|
||||
export interface NodeIKernelSearchService {
|
||||
addKernelSearchListener(...args: any[]): unknown;// needs 1 arguments
|
||||
|
||||
searchStrangerr(...args: any[]): unknown;// needs 3 arguments
|
||||
removeKernelSearchListener(...args: any[]): unknown;// needs 1 arguments
|
||||
|
||||
searchGroupr(...args: any[]): unknown;// needs 1 arguments
|
||||
searchStranger(...args: any[]): unknown;// needs 3 arguments
|
||||
|
||||
searchLocalInfor(...args: any[]): unknown;// needs 2 arguments
|
||||
searchGroup(...args: any[]): unknown;// needs 1 arguments
|
||||
|
||||
cancelSearchLocalInfor(...args: any[]): unknown;// needs 3 arguments
|
||||
searchLocalInfo(keywords: string, unknown: number/*4*/): unknown;
|
||||
|
||||
searchBuddyChatInfor(...args: any[]): unknown;// needs 2 arguments
|
||||
cancelSearchLocalInfo(...args: any[]): unknown;// needs 3 arguments
|
||||
|
||||
searchMoreBuddyChatInfor(...args: any[]): unknown;// needs 1 arguments
|
||||
searchBuddyChatInfo(...args: any[]): unknown;// needs 2 arguments
|
||||
|
||||
cancelSearchBuddyChatInfor(...args: any[]): unknown;// needs 3 arguments
|
||||
searchMoreBuddyChatInfo(...args: any[]): unknown;// needs 1 arguments
|
||||
|
||||
searchContactr(...args: any[]): unknown;// needs 2 arguments
|
||||
cancelSearchBuddyChatInfo(...args: any[]): unknown;// needs 3 arguments
|
||||
|
||||
searchMoreContactr(...args: any[]): unknown;// needs 1 arguments
|
||||
searchContact(...args: any[]): unknown;// needs 2 arguments
|
||||
|
||||
cancelSearchContactr(...args: any[]): unknown;// needs 3 arguments
|
||||
searchMoreContact(...args: any[]): unknown;// needs 1 arguments
|
||||
|
||||
searchGroupChatInfor(...args: any[]): unknown;// needs 3 arguments
|
||||
cancelSearchContact(...args: any[]): unknown;// needs 3 arguments
|
||||
|
||||
resetSearchGroupChatInfoSortTyper(...args: any[]): unknown;// needs 3 arguments
|
||||
searchGroupChatInfo(...args: any[]): unknown;// needs 3 arguments
|
||||
|
||||
resetSearchGroupChatInfoFilterMembersr(...args: any[]): unknown;// needs 3 arguments
|
||||
resetSearchGroupChatInfoSortType(...args: any[]): unknown;// needs 3 arguments
|
||||
|
||||
searchMoreGroupChatInfor(...args: any[]): unknown;// needs 1 arguments
|
||||
resetSearchGroupChatInfoFilterMembers(...args: any[]): unknown;// needs 3 arguments
|
||||
|
||||
cancelSearchGroupChatInfor(...args: any[]): unknown;// needs 3 arguments
|
||||
searchMoreGroupChatInfo(...args: any[]): unknown;// needs 1 arguments
|
||||
|
||||
searchChatsWithKeywordsr(...args: any[]): unknown;// needs 3 arguments
|
||||
cancelSearchGroupChatInfo(...args: any[]): unknown;// needs 3 arguments
|
||||
|
||||
searchMoreChatsWithKeywordsr(...args: any[]): unknown;// needs 1 arguments
|
||||
searchChatsWithKeywords(...args: any[]): unknown;// needs 3 arguments
|
||||
|
||||
cancelSearchChatsWithKeywordsr(...args: any[]): unknown;// needs 3 arguments
|
||||
searchMoreChatsWithKeywords(...args: any[]): unknown;// needs 1 arguments
|
||||
|
||||
searchChatMsgsr(...args: any[]): unknown;// needs 2 arguments
|
||||
cancelSearchChatsWithKeywords(...args: any[]): unknown;// needs 3 arguments
|
||||
|
||||
searchMoreChatMsgsr(...args: any[]): unknown;// needs 1 arguments
|
||||
searchChatMsgs(...args: any[]): unknown;// needs 2 arguments
|
||||
|
||||
cancelSearchChatMsgsr(...args: any[]): unknown;// needs 3 arguments
|
||||
searchMoreChatMsgs(...args: any[]): unknown;// needs 1 arguments
|
||||
|
||||
searchMsgWithKeywordsr(...args: any[]): unknown;// needs 2 arguments
|
||||
cancelSearchChatMsgs(...args: any[]): unknown;// needs 3 arguments
|
||||
|
||||
searchMoreMsgWithKeywordsr(...args: any[]): unknown;// needs 1 arguments
|
||||
|
||||
cancelSearchMsgWithKeywordsr(...args: any[]): unknown;// needs 3 arguments
|
||||
searchMsgWithKeywords(...args: any[]): unknown;// needs 2 arguments
|
||||
|
||||
searchFileWithKeywordsr(...args: any[]): unknown;// needs 2 arguments
|
||||
searchMoreMsgWithKeywords(...args: any[]): unknown;// needs 1 arguments
|
||||
|
||||
searchMoreFileWithKeywordsr(...args: any[]): unknown;// needs 1 arguments
|
||||
cancelSearchMsgWithKeywords(...args: any[]): unknown;// needs 3 arguments
|
||||
|
||||
cancelSearchFileWithKeywordsr(...args: any[]): unknown;// needs 3 arguments
|
||||
searchFileWithKeywords(keywords: string[], source: number): Promise<string>;// needs 2 arguments
|
||||
|
||||
searchAtMeChatsr(...args: any[]): unknown;// needs 3 arguments
|
||||
searchMoreFileWithKeywords(...args: any[]): unknown;// needs 1 arguments
|
||||
|
||||
searchMoreAtMeChatsr(...args: any[]): unknown;// needs 1 arguments
|
||||
cancelSearchFileWithKeywords(...args: any[]): unknown;// needs 3 arguments
|
||||
|
||||
cancelSearchAtMeChatsr(...args: any[]): unknown;// needs 3 arguments
|
||||
searchAtMeChats(...args: any[]): unknown;// needs 3 arguments
|
||||
|
||||
searchChatAtMeMsgsr(...args: any[]): unknown;// needs 1 arguments
|
||||
searchMoreAtMeChats(...args: any[]): unknown;// needs 1 arguments
|
||||
|
||||
searchMoreChatAtMeMsgsr(...args: any[]): unknown;// needs 1 arguments
|
||||
cancelSearchAtMeChats(...args: any[]): unknown;// needs 3 arguments
|
||||
|
||||
cancelSearchChatAtMeMsgsr(...args: any[]): unknown;// needs 3 arguments
|
||||
searchChatAtMeMsgs(...args: any[]): unknown;// needs 1 arguments
|
||||
|
||||
addSearchHistoryr(...args: any[]): unknown;// needs 1 arguments
|
||||
searchMoreChatAtMeMsgs(...args: any[]): unknown;// needs 1 arguments
|
||||
|
||||
removeSearchHistoryr(...args: any[]): unknown;// needs 1 arguments
|
||||
cancelSearchChatAtMeMsgs(...args: any[]): unknown;// needs 3 arguments
|
||||
|
||||
searchCacher(...args: any[]): unknown;// needs 3 arguments
|
||||
addSearchHistory(param: {
|
||||
type: number,//4
|
||||
contactList: [],
|
||||
id: number,//-1
|
||||
groupInfos: [],
|
||||
msgs: [],
|
||||
fileInfos: [
|
||||
{
|
||||
chatType: ChatType,
|
||||
buddyChatInfo: Array<{ category_name: string, peerUid: string, peerUin: string, remark: string }>,
|
||||
discussChatInfo: [],
|
||||
groupChatInfo: Array<
|
||||
{
|
||||
groupCode: string,
|
||||
isConf: boolean,
|
||||
hasModifyConfGroupFace: boolean,
|
||||
hasModifyConfGroupName: boolean,
|
||||
groupName: string,
|
||||
remark: string
|
||||
}>
|
||||
,
|
||||
dataLineChatInfo: [],
|
||||
tmpChatInfo: [],
|
||||
msgId: string,
|
||||
msgSeq: string,
|
||||
msgTime: string,
|
||||
senderUid: string,
|
||||
senderNick: string,
|
||||
senderRemark: string,
|
||||
senderCard: string,
|
||||
elemId: string,
|
||||
elemType: string,//3
|
||||
fileSize: string,
|
||||
filePath: string,
|
||||
fileName: string,
|
||||
hits: Array<
|
||||
{
|
||||
start: 12,
|
||||
end: 14
|
||||
}
|
||||
>
|
||||
}
|
||||
]
|
||||
|
||||
clearSearchCacher(...args: any[]): unknown;// needs 1 arguments
|
||||
}): Promise<{
|
||||
result: number,
|
||||
errMsg: string,
|
||||
id?: number
|
||||
}>;
|
||||
|
||||
removeSearchHistory(...args: any[]): unknown;// needs 1 arguments
|
||||
|
||||
searchCache(...args: any[]): unknown;// needs 3 arguments
|
||||
|
||||
clearSearchCache(...args: any[]): unknown;// needs 1 arguments
|
||||
}
|
||||
|
14
src/core/src/services/NodeIkernelTestPerformanceService.ts
Normal file
14
src/core/src/services/NodeIkernelTestPerformanceService.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { MessageElement, Peer } from "../entities";
|
||||
|
||||
export interface NodeIkernelTestPerformanceService {
|
||||
insertMsg(MsgParam: {
|
||||
peer: Peer
|
||||
msgTime: string
|
||||
msgId: string
|
||||
msgSeq: string
|
||||
batchNums: number
|
||||
timesPerBatch: number
|
||||
numPerTime: number
|
||||
}, msg: Array<MessageElement>): Promise<unknown>;
|
||||
|
||||
}
|
@@ -12,18 +12,16 @@ export interface NapCatConfig {
|
||||
consoleLogLevel: LogLevel,
|
||||
}
|
||||
|
||||
class Config extends ConfigBase<NapCatConfig> implements NapCatConfig{
|
||||
class Config extends ConfigBase<NapCatConfig> implements NapCatConfig {
|
||||
name: string = 'napcat'
|
||||
fileLog = true;
|
||||
consoleLog = true;
|
||||
fileLogLevel = LogLevel.DEBUG;
|
||||
consoleLogLevel = LogLevel.INFO;
|
||||
fileLogLevel = LogLevel.DEBUG;
|
||||
consoleLogLevel = LogLevel.INFO;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
getConfigPath() {
|
||||
return path.join(this.getConfigDir(), `napcat_${selfInfo.uin}.json`);
|
||||
}
|
||||
}
|
||||
|
||||
export const napCatConfig = new Config();
|
||||
|
@@ -39,6 +39,10 @@ import { NodeIKernelTianShuService } from './services/NodeIKernelTianShuService'
|
||||
import { NodeIKernelUnitedConfigService } from './services/NodeIKernelUnitedConfigService';
|
||||
import { NodeIKernelSearchService } from './services/NodeIKernelSearchService';
|
||||
import { NodeIKernelCollectionService } from './services/NodeIKernelCollectionService';
|
||||
import { NodeIKernelRecentContactService } from './services/NodeIKernelRecentContactService';
|
||||
import { NodeIKernelMSFService } from './services/NodeIKernelMSFService';
|
||||
import { NodeIkernelTestPerformanceService } from './services/NodeIkernelTestPerformanceService';
|
||||
import { NodeIKernelECDHService } from './services/NodeIKernelECDHService';
|
||||
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
@@ -154,6 +158,10 @@ export interface NodeIQQNTWrapperSession {
|
||||
|
||||
startNT(): void;
|
||||
|
||||
getBdhUploadService(): unknown;
|
||||
|
||||
getECDHService(): NodeIKernelECDHService;
|
||||
|
||||
getMsgService(): NodeIKernelMsgService;
|
||||
|
||||
getProfileService(): NodeIKernelProfileService;
|
||||
@@ -208,7 +216,7 @@ export interface NodeIQQNTWrapperSession {
|
||||
|
||||
getSkinService(): unknown;
|
||||
|
||||
getTestPerformanceService(): unknown;
|
||||
getTestPerformanceService(): NodeIkernelTestPerformanceService;
|
||||
|
||||
getQQPlayService(): unknown;
|
||||
|
||||
@@ -230,13 +238,13 @@ export interface NodeIQQNTWrapperSession {
|
||||
|
||||
getLockService(): unknown;
|
||||
|
||||
getMSFService(): unknown
|
||||
getMSFService(): NodeIKernelMSFService;
|
||||
|
||||
getGuildHotUpdateService(): unknown;
|
||||
|
||||
getAVSDKService(): unknown;
|
||||
|
||||
getRecentContactService(): unknown;
|
||||
getRecentContactService(): NodeIKernelRecentContactService;
|
||||
|
||||
getConfigMgrService(): unknown;
|
||||
}
|
||||
|
24
src/index.ts
24
src/index.ts
@@ -13,13 +13,15 @@ import { deleteOldFiles, UpdateConfig } from './common/utils/helper';
|
||||
import { dirname } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import chalk from 'chalk';
|
||||
import { randomInt } from 'crypto';
|
||||
import { MessageUnique } from './common/utils/MessageUnique';
|
||||
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
const tagColor = chalk.cyan;
|
||||
program
|
||||
.option('-q, --qq <type>', 'QQ号')
|
||||
.option('-q, --qq [type]', 'QQ号')
|
||||
.parse(process.argv);
|
||||
|
||||
//deleteOldFiles(path.join(__dirname, 'logs'), 3).then().catch();
|
||||
@@ -67,7 +69,18 @@ const showQRCode = async (url: string, base64: string, buffer: Buffer) => {
|
||||
});
|
||||
});
|
||||
};
|
||||
const quickLoginQQ = cmdOptions.qq;
|
||||
|
||||
let quickLoginQQ = cmdOptions.qq; // undefine、true、string
|
||||
const QuickLoginList = await napCatCore.getQuickLoginList();
|
||||
if (quickLoginQQ == true) {
|
||||
if (QuickLoginList.LocalLoginInfoList.length > 0) {
|
||||
quickLoginQQ = QuickLoginList.LocalLoginInfoList[0].uin;
|
||||
log('-q 指令指定使用最近的QQ进行快速登录');
|
||||
} else {
|
||||
quickLoginQQ = '';
|
||||
}
|
||||
}
|
||||
|
||||
// napCatCore.on('system.login.error', (result) => {
|
||||
// console.error('登录失败', result);
|
||||
// napCatCore.qrLogin().then().catch(console.error);
|
||||
@@ -105,11 +118,14 @@ if (quickLoginQQ) {
|
||||
logError('快速登录错误:', res.loginErrorInfo.errMsg);
|
||||
}
|
||||
}).catch((e) => {
|
||||
logError(e);
|
||||
logError('快速登录错误:', e);
|
||||
napCatCore.qrLogin(showQRCode);
|
||||
});
|
||||
} else {
|
||||
log('没有 -q 参数指定快速登录的QQ,将使用二维码登录方式');
|
||||
log('没有 -q 指令指定快速登录,将使用二维码登录方式');
|
||||
if (QuickLoginList.LocalLoginInfoList.length > 0) {
|
||||
log(`可用于快速登录的QQ:${QuickLoginList.LocalLoginInfoList.map((u, index) => `\n${index}: ${u.uin} ${u.nickName}`)}`);
|
||||
}
|
||||
napCatCore.qrLogin(showQRCode);
|
||||
}
|
||||
|
||||
|
@@ -15,6 +15,6 @@ export class SetConfigAction extends BaseAction<OB11Config, void> {
|
||||
actionName = ActionName.SetConfig;
|
||||
|
||||
protected async _handle(payload: OB11Config): Promise<void> {
|
||||
ob11Config.save(payload);
|
||||
ob11Config.save(payload, true);
|
||||
}
|
||||
}
|
||||
|
21
src/onebot11/action/extends/FetchCustomFace.ts
Normal file
21
src/onebot11/action/extends/FetchCustomFace.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
|
||||
import BaseAction from '../BaseAction';
|
||||
import { ActionName } from '../types';
|
||||
import { NTQQMsgApi } from '@/core/apis';
|
||||
const SchemaData = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
count: { type: 'number' },
|
||||
}
|
||||
} as const satisfies JSONSchema;
|
||||
|
||||
type Payload = FromSchema<typeof SchemaData>;
|
||||
|
||||
export class FetchCustomFace extends BaseAction<Payload, string[]> {
|
||||
actionName = ActionName.FetchCustomFace;
|
||||
PayloadSchema = SchemaData;
|
||||
protected async _handle(payload: Payload) {
|
||||
const ret = await NTQQMsgApi.fetchFavEmojiList(payload.count || 48);
|
||||
return ret.emojiInfoList.map(e => e.url);
|
||||
}
|
||||
}
|
16
src/onebot11/action/extends/GetProfileLike.ts
Normal file
16
src/onebot11/action/extends/GetProfileLike.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { selfInfo } from '@/core/data';
|
||||
import BaseAction from '../BaseAction';
|
||||
import { ActionName } from '../types';
|
||||
import { NTQQUserApi } from '@/core/apis';
|
||||
|
||||
export class GetProfileLike extends BaseAction<void, any> {
|
||||
actionName = ActionName.GetProfileLike;
|
||||
protected async _handle(payload: void) {
|
||||
const ret = await NTQQUserApi.getProfileLike(selfInfo.uid);
|
||||
const listdata: any[] = ret.info.userLikeInfos[0].favoriteInfo.userInfos;
|
||||
for (let i = 0; i < listdata.length; i++) {
|
||||
listdata[i].uin = parseInt((await NTQQUserApi.getUinByUid(listdata[i].uid)) || '');
|
||||
}
|
||||
return listdata;
|
||||
}
|
||||
}
|
57
src/onebot11/action/extends/SetGroupHeader.ts
Normal file
57
src/onebot11/action/extends/SetGroupHeader.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import BaseAction from '../BaseAction';
|
||||
import { ActionName, BaseCheckResult } from '../types';
|
||||
import * as fs from 'node:fs';
|
||||
import { NTQQUserApi } from '@/core/apis/user';
|
||||
import { checkFileReceived, uri2local } from '@/common/utils/file';
|
||||
import { NTQQGroupApi } from '@/core';
|
||||
// import { log } from "../../../common/utils";
|
||||
|
||||
interface Payload {
|
||||
file: string,
|
||||
groupCode: string
|
||||
}
|
||||
|
||||
export default class SetGroupHeader extends BaseAction<Payload, any> {
|
||||
actionName = ActionName.SetGroupHeader;
|
||||
// 用不着复杂检测
|
||||
protected async check(payload: Payload): Promise<BaseCheckResult> {
|
||||
if (!payload.file || typeof payload.file != 'string' || !payload.groupCode || typeof payload.groupCode != 'string') {
|
||||
return {
|
||||
valid: false,
|
||||
message: 'file和groupCode字段不能为空或者类型错误',
|
||||
};
|
||||
}
|
||||
return {
|
||||
valid: true,
|
||||
};
|
||||
}
|
||||
protected async _handle(payload: Payload): Promise<any> {
|
||||
const { path, isLocal, errMsg } = (await uri2local(payload.file));
|
||||
if (errMsg) {
|
||||
throw `头像${payload.file}设置失败,file字段可能格式不正确`;
|
||||
}
|
||||
if (path) {
|
||||
await checkFileReceived(path, 5000); // 文件不存在QQ会崩溃,需要提前判断
|
||||
const ret = await NTQQGroupApi.setGroupAvatar(payload.groupCode,path);
|
||||
if (!isLocal) {
|
||||
fs.unlink(path, () => { });
|
||||
}
|
||||
if (!ret) {
|
||||
throw `头像${payload.file}设置失败,api无返回`;
|
||||
}
|
||||
// log(`头像设置返回:${JSON.stringify(ret)}`)
|
||||
// if (ret['result'] == 1004022) {
|
||||
// throw `头像${payload.file}设置失败,文件可能不是图片格式`;
|
||||
// } else if (ret['result'] != 0) {
|
||||
// throw `头像${payload.file}设置失败,未知的错误,${ret['result']}:${ret['errMsg']}`;
|
||||
// }
|
||||
return ret;
|
||||
} else {
|
||||
if (!isLocal) {
|
||||
fs.unlink(path, () => { });
|
||||
}
|
||||
throw `头像${payload.file}设置失败,无法获取头像,文件可能不存在`;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
32
src/onebot11/action/extends/TestApi01.ts
Normal file
32
src/onebot11/action/extends/TestApi01.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import BaseAction from '../BaseAction';
|
||||
import { ActionName, BaseCheckResult } from '../types';
|
||||
import * as fs from 'node:fs';
|
||||
import { NTQQUserApi } from '@/core/apis/user';
|
||||
import { checkFileReceived, uri2local } from '@/common/utils/file';
|
||||
import { napCatCore, NTQQGroupApi } from '@/core';
|
||||
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
|
||||
// import { log } from "../../../common/utils";
|
||||
|
||||
const SchemaData = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
cmd: { type: 'string' },
|
||||
param: { type: 'string' }
|
||||
},
|
||||
required: ['cmd', 'param'],
|
||||
} as const satisfies JSONSchema;
|
||||
|
||||
type Payload = FromSchema<typeof SchemaData>;
|
||||
|
||||
export default class TestApi01 extends BaseAction<Payload, any> {
|
||||
actionName = ActionName.TestApi01;
|
||||
// 用不着复杂检测
|
||||
protected async check(payload: Payload): Promise<BaseCheckResult> {
|
||||
return {
|
||||
valid: true,
|
||||
};
|
||||
}
|
||||
protected async _handle(payload: Payload): Promise<any> {
|
||||
return await napCatCore.session.getMsgService().sendSsoCmdReqByContend(payload.cmd, payload.param);
|
||||
}
|
||||
}
|
@@ -1,15 +1,12 @@
|
||||
import BaseAction from '../BaseAction';
|
||||
import fs from 'fs/promises';
|
||||
import { dbUtil } from '@/common/utils/db';
|
||||
import { ob11Config } from '@/onebot11/config';
|
||||
import { log, logDebug } from '@/common/utils/log';
|
||||
import { sleep } from '@/common/utils/helper';
|
||||
import { uri2local } from '@/common/utils/file';
|
||||
import { UUIDConverter } from '@/common/utils/helper';
|
||||
import { ActionName, BaseCheckResult } from '../types';
|
||||
import { FileElement, RawMessage, VideoElement } from '@/core/entities';
|
||||
import { NTQQFileApi } from '@/core/apis';
|
||||
import { ChatType, ElementType, FileElement, Peer, RawMessage, VideoElement } from '@/core/entities';
|
||||
import { NTQQFileApi, NTQQFriendApi, NTQQMsgApi, NTQQUserApi } from '@/core/apis';
|
||||
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
|
||||
import Ajv from 'ajv';
|
||||
import { getGroup } from '@/core/data';
|
||||
|
||||
export interface GetFilePayload {
|
||||
file: string; // 文件名或者fileUuid
|
||||
@@ -45,69 +42,163 @@ export class GetFileBase extends BaseAction<GetFilePayload, GetFileResponse> {
|
||||
return { id: element.elementId, element: element.fileElement };
|
||||
}
|
||||
protected async _handle(payload: GetFilePayload): Promise<GetFileResponse> {
|
||||
let cache = await dbUtil.getFileCacheByName(payload.file);
|
||||
if (!cache) {
|
||||
cache = await dbUtil.getFileCacheByUuid(payload.file);
|
||||
}
|
||||
if (!cache) {
|
||||
throw new Error('file not found');
|
||||
}
|
||||
const { enableLocalFile2Url } = ob11Config;
|
||||
let UuidData: {
|
||||
high: string;
|
||||
low: string;
|
||||
} | undefined;
|
||||
try {
|
||||
await fs.access(cache.path, fs.constants.F_OK);
|
||||
} catch (e) {
|
||||
logDebug('local file not found, start download...');
|
||||
// if (cache.url) {
|
||||
// const downloadResult = await uri2local(cache.url);
|
||||
// if (downloadResult.success) {
|
||||
// cache.path = downloadResult.path;
|
||||
// dbUtil.updateFileCache(cache).then();
|
||||
// } else {
|
||||
// throw new Error('file download failed. ' + downloadResult.errMsg);
|
||||
// }
|
||||
// } else {
|
||||
// // 没有url的可能是私聊文件或者群文件,需要自己下载
|
||||
// log('需要调用 NTQQ 下载文件api');
|
||||
let msg = await dbUtil.getMsgByLongId(cache.msgId);
|
||||
// log('文件 msg', msg);
|
||||
if (msg) {
|
||||
// 构建下载函数
|
||||
const downloadPath = await NTQQFileApi.downloadMedia(msg.msgId, msg.chatType, msg.peerUid,
|
||||
cache.elementId, '', '');
|
||||
// await sleep(1000);
|
||||
|
||||
// log('download result', downloadPath);
|
||||
msg = await dbUtil.getMsgByLongId(cache.msgId);
|
||||
// log('下载完成后的msg', msg);
|
||||
cache.path = downloadPath!;
|
||||
dbUtil.updateFileCache(cache).then();
|
||||
// log('下载完成后的msg', msg);
|
||||
// }
|
||||
UuidData = UUIDConverter.decode(payload.file);
|
||||
if (UuidData) {
|
||||
const peerUin = UuidData.high;
|
||||
const msgId = UuidData.low;
|
||||
const isGroup = await getGroup(peerUin);
|
||||
let peer: Peer | undefined;
|
||||
//识别Peer
|
||||
if (isGroup) {
|
||||
peer = { chatType: ChatType.group, peerUid: peerUin };
|
||||
}
|
||||
const PeerUid = await NTQQUserApi.getUidByUin(peerUin);
|
||||
if (PeerUid) {
|
||||
const isBuddy = await NTQQFriendApi.isBuddy(PeerUid);
|
||||
if (isBuddy) {
|
||||
peer = { chatType: ChatType.friend, peerUid: PeerUid };
|
||||
} else {
|
||||
peer = { chatType: ChatType.temp, peerUid: PeerUid };
|
||||
}
|
||||
}
|
||||
if (!peer) {
|
||||
throw new Error('chattype not support');
|
||||
}
|
||||
const msgList = await NTQQMsgApi.getMsgsByMsgId(peer, [msgId]);
|
||||
if (msgList.msgList.length == 0) {
|
||||
throw new Error('msg not found');
|
||||
}
|
||||
const msg = msgList.msgList[0];
|
||||
const findEle = msg.elements.find(e => e.elementType == ElementType.VIDEO || e.elementType == ElementType.FILE || e.elementType == ElementType.PTT);
|
||||
if (!findEle) {
|
||||
throw new Error('element not found');
|
||||
}
|
||||
const downloadPath = await NTQQFileApi.downloadMedia(msgId, msg.chatType, msg.peerUid, findEle.elementId, '', '');
|
||||
const fileSize = findEle?.videoElement?.fileSize || findEle?.fileElement?.fileSize || findEle?.pttElement?.fileSize || '0';
|
||||
const fileName = findEle?.videoElement?.fileName || findEle?.fileElement?.fileName || findEle?.pttElement?.fileName || '';
|
||||
const res: GetFileResponse = {
|
||||
file: downloadPath,
|
||||
url: downloadPath,
|
||||
file_size: fileSize,
|
||||
file_name: fileName
|
||||
};
|
||||
if (enableLocalFile2Url) {
|
||||
try {
|
||||
res.base64 = await fs.readFile(downloadPath, 'base64');
|
||||
} catch (e) {
|
||||
throw new Error('文件下载失败. ' + e);
|
||||
}
|
||||
}
|
||||
//不手动删除?文件持久化了
|
||||
return res;
|
||||
}
|
||||
} catch {
|
||||
|
||||
}
|
||||
// log('file found', cache);
|
||||
const res: GetFileResponse = {
|
||||
file: cache.path,
|
||||
url: cache.url,
|
||||
file_size: cache.size.toString(),
|
||||
file_name: cache.name
|
||||
};
|
||||
if (enableLocalFile2Url) {
|
||||
if (!cache.url) {
|
||||
|
||||
const NTSearchNameResult = (await NTQQFileApi.searchfile([payload.file])).resultItems;
|
||||
if (NTSearchNameResult.length !== 0) {
|
||||
const MsgId = NTSearchNameResult[0].msgId;
|
||||
let peer: Peer | undefined = undefined;
|
||||
if (NTSearchNameResult[0].chatType == ChatType.group) {
|
||||
peer = { chatType: ChatType.group, peerUid: NTSearchNameResult[0].groupChatInfo[0].groupCode };
|
||||
}
|
||||
if (!peer) {
|
||||
throw new Error('chattype not support');
|
||||
}
|
||||
const msgList: RawMessage[] = (await NTQQMsgApi.getMsgsByMsgId(peer, [MsgId]))?.msgList;
|
||||
if (!msgList || msgList.length == 0) {
|
||||
throw new Error('msg not found');
|
||||
}
|
||||
const msg = msgList[0];
|
||||
const file = msg.elements.filter(e => e.elementType == NTSearchNameResult[0].elemType);
|
||||
if (file.length == 0) {
|
||||
throw new Error('file not found');
|
||||
}
|
||||
const downloadPath = await NTQQFileApi.downloadMedia(msg.msgId, msg.chatType, msg.peerUid, file[0].elementId, '', '');
|
||||
const res: GetFileResponse = {
|
||||
file: downloadPath,
|
||||
url: downloadPath,
|
||||
file_size: NTSearchNameResult[0].fileSize.toString(),
|
||||
file_name: NTSearchNameResult[0].fileName
|
||||
};
|
||||
if (enableLocalFile2Url) {
|
||||
try {
|
||||
res.base64 = await fs.readFile(cache.path, 'base64');
|
||||
res.base64 = await fs.readFile(downloadPath, 'base64');
|
||||
} catch (e) {
|
||||
throw new Error('文件下载失败. ' + e);
|
||||
}
|
||||
}
|
||||
//不手动删除?文件持久化了
|
||||
return res;
|
||||
}
|
||||
// if (autoDeleteFile) {
|
||||
// setTimeout(() => {
|
||||
// fs.unlink(cache.filePath)
|
||||
// }, autoDeleteFileSecond * 1000)
|
||||
throw new Error('file not found');
|
||||
// let cache = await dbUtil.getFileCacheByName(payload.file);
|
||||
// if (!cache) {
|
||||
// cache = await dbUtil.getFileCacheByUuid(payload.file);
|
||||
// }
|
||||
return res;
|
||||
// if (!cache) {
|
||||
// throw new Error('file not found');
|
||||
// }
|
||||
// const { enableLocalFile2Url } = ob11Config;
|
||||
// try {
|
||||
// await fs.access(cache.path, fs.constants.F_OK);
|
||||
// } catch (e) {
|
||||
// logDebug('local file not found, start download...');
|
||||
// // if (cache.url) {
|
||||
// // const downloadResult = await uri2local(cache.url);
|
||||
// // if (downloadResult.success) {
|
||||
// // cache.path = downloadResult.path;
|
||||
// // dbUtil.updateFileCache(cache).then();
|
||||
// // } else {
|
||||
// // throw new Error('file download failed. ' + downloadResult.errMsg);
|
||||
// // }
|
||||
// // } else {
|
||||
// // // 没有url的可能是私聊文件或者群文件,需要自己下载
|
||||
// // log('需要调用 NTQQ 下载文件api');
|
||||
// let peer = MessageUnique.getPeerByMsgId(cache.msgId);
|
||||
// let msg = await NTQQMsgApi.getMsgsByMsgId(peer?.Peer!,cache.msgId);
|
||||
// // log('文件 msg', msg);
|
||||
// if (msg) {
|
||||
// // 构建下载函数
|
||||
// const downloadPath = await NTQQFileApi.downloadMedia(msg.msgId, msg.chatType, msg.peerUid,
|
||||
// cache.elementId, '', '');
|
||||
// // await sleep(1000);
|
||||
|
||||
// // log('download result', downloadPath);
|
||||
// let peer = MessageUnique.getPeerByMsgId(cache.msgId);
|
||||
// msg = await NTQQMsgApi.getMsgsByMsgId(peer?.Peer!,cache.msgId);
|
||||
// // log('下载完成后的msg', msg);
|
||||
// cache.path = downloadPath!;
|
||||
// dbUtil.updateFileCache(cache).then();
|
||||
// // log('下载完成后的msg', msg);
|
||||
// // }
|
||||
// }
|
||||
|
||||
// }
|
||||
// // log('file found', cache);
|
||||
// const res: GetFileResponse = {
|
||||
// file: cache.path,
|
||||
// url: cache.url,
|
||||
// file_size: cache.size.toString(),
|
||||
// file_name: cache.name
|
||||
// };
|
||||
// if (enableLocalFile2Url) {
|
||||
// if (!cache.url) {
|
||||
// try {
|
||||
// res.base64 = await fs.readFile(cache.path, 'base64');
|
||||
// } catch (e) {
|
||||
// throw new Error('文件下载失败. ' + e);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//return res;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,11 +1,10 @@
|
||||
import BaseAction from '../BaseAction';
|
||||
import { ActionName, BaseCheckResult } from '../types';
|
||||
import { ActionName } from '../types';
|
||||
import fs from 'fs';
|
||||
import { join as joinPath } from 'node:path';
|
||||
import { calculateFileMD5, getTempDir, httpDownload } from '@/common/utils/file';
|
||||
import { v4 as uuid4 } from 'uuid';
|
||||
import { randomUUID } from 'crypto';
|
||||
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
|
||||
import Ajv from 'ajv';
|
||||
interface FileResponse {
|
||||
file: string;
|
||||
}
|
||||
@@ -32,7 +31,7 @@ export default class GoCQHTTPDownloadFile extends BaseAction<Payload, FileRespon
|
||||
PayloadSchema = SchemaData;
|
||||
protected async _handle(payload: Payload): Promise<FileResponse> {
|
||||
const isRandomName = !payload.name;
|
||||
const name = payload.name || uuid4();
|
||||
const name = payload.name || randomUUID();
|
||||
const filePath = joinPath(getTempDir(), name);
|
||||
|
||||
if (payload.base64) {
|
||||
|
@@ -1,11 +1,10 @@
|
||||
import BaseAction from '../BaseAction';
|
||||
import { OB11ForwardMessage, OB11Message, OB11MessageData } from '../../types';
|
||||
import { NTQQMsgApi } from '@/core/apis';
|
||||
import { dbUtil } from '@/common/utils/db';
|
||||
import { OB11Constructor } from '../../constructor';
|
||||
import { ActionName, BaseCheckResult } from '../types';
|
||||
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
|
||||
import Ajv from 'ajv';
|
||||
import { MessageUnique } from '@/common/utils/MessageUnique';
|
||||
|
||||
const SchemaData = {
|
||||
type: 'object',
|
||||
@@ -29,24 +28,19 @@ export class GoCQHTTPGetForwardMsgAction extends BaseAction<Payload, any> {
|
||||
if (!msgId) {
|
||||
throw Error('message_id is required');
|
||||
}
|
||||
let rootMsg = await dbUtil.getMsgByLongId(msgId);
|
||||
const rootMsgId = MessageUnique.getShortIdByMsgId(msgId);
|
||||
const rootMsg = MessageUnique.getMsgIdAndPeerByShortId(rootMsgId || parseInt(msgId));
|
||||
if (!rootMsg) {
|
||||
rootMsg = await dbUtil.getMsgByShortId(parseInt(msgId));
|
||||
if (!rootMsg) {
|
||||
throw Error('msg not found');
|
||||
}
|
||||
throw Error('msg not found');
|
||||
}
|
||||
const data = await NTQQMsgApi.getMultiMsg({
|
||||
chatType: rootMsg.chatType,
|
||||
peerUid: rootMsg.peerUid
|
||||
}, rootMsg.msgId, rootMsg.msgId);
|
||||
const data = await NTQQMsgApi.getMultiMsg(rootMsg.Peer, rootMsg.MsgId, rootMsg.MsgId);
|
||||
if (!data || data.result !== 0) {
|
||||
throw Error('找不到相关的聊天记录' + data?.errMsg);
|
||||
}
|
||||
const msgList = data.msgList;
|
||||
const messages = await Promise.all(msgList.map(async msg => {
|
||||
const resMsg = await OB11Constructor.message(msg);
|
||||
resMsg.message_id = await dbUtil.addMsg(msg);
|
||||
resMsg.message_id = await MessageUnique.createMsg({ guildId:'',chatType:msg.chatType,peerUid:msg.peerUid },msg.msgId)!;
|
||||
return resMsg;
|
||||
}));
|
||||
messages.map(msg => {
|
||||
|
@@ -2,11 +2,11 @@ import BaseAction from '../BaseAction';
|
||||
import { OB11Message, OB11User } from '../../types';
|
||||
import { ActionName } from '../types';
|
||||
import { ChatType } from '@/core/entities';
|
||||
import { dbUtil } from '@/common/utils/db';
|
||||
import { NTQQMsgApi } from '@/core/apis/msg';
|
||||
import { OB11Constructor } from '../../constructor';
|
||||
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
|
||||
import { NTQQFriendApi, NTQQUserApi } from '@/core';
|
||||
import { MessageUnique } from '@/common/utils/MessageUnique';
|
||||
|
||||
interface Response {
|
||||
messages: OB11Message[];
|
||||
@@ -32,7 +32,7 @@ export default class GetFriendMsgHistory extends BaseAction<Payload, Response> {
|
||||
if (!uid) {
|
||||
throw `记录${payload.user_id}不存在`;
|
||||
}
|
||||
const startMsgId = (await dbUtil.getMsgByShortId(payload.message_seq))?.msgId || '0';
|
||||
const startMsgId = (await MessageUnique.getMsgIdAndPeerByShortId(payload.message_seq))?.MsgId || '0';
|
||||
const friend = await NTQQFriendApi.isBuddy(uid);
|
||||
const historyResult = (await NTQQMsgApi.getMsgHistory({
|
||||
chatType: friend ? ChatType.friend : ChatType.temp,
|
||||
@@ -41,7 +41,7 @@ export default class GetFriendMsgHistory extends BaseAction<Payload, Response> {
|
||||
//logDebug(historyResult);
|
||||
const msgList = historyResult.msgList;
|
||||
await Promise.all(msgList.map(async msg => {
|
||||
msg.id = await dbUtil.addMsg(msg);
|
||||
msg.id = await MessageUnique.createMsg({ guildId: '', chatType: msg.chatType, peerUid: msg.peerUid }, msg.msgId);
|
||||
}));
|
||||
const ob11MsgList = await Promise.all(msgList.map(msg => OB11Constructor.message(msg)));
|
||||
return { 'messages': ob11MsgList };
|
||||
|
@@ -2,12 +2,11 @@ import BaseAction from '../BaseAction';
|
||||
import { OB11Message, OB11User } from '../../types';
|
||||
import { getGroup, groups } from '@/core/data';
|
||||
import { ActionName } from '../types';
|
||||
import { ChatType } from '@/core/entities';
|
||||
import { dbUtil } from '@/common/utils/db';
|
||||
import { ChatType, RawMessage } from '@/core/entities';
|
||||
import { NTQQMsgApi } from '@/core/apis/msg';
|
||||
import { OB11Constructor } from '../../constructor';
|
||||
import { logDebug } from '@/common/utils/log';
|
||||
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
|
||||
import { MessageUnique } from '@/common/utils/MessageUnique';
|
||||
interface Response {
|
||||
messages: OB11Message[];
|
||||
}
|
||||
@@ -15,11 +14,11 @@ interface Response {
|
||||
const SchemaData = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
group_id: { type: [ 'number' , 'string' ] },
|
||||
group_id: { type: ['number', 'string'] },
|
||||
message_seq: { type: 'number' },
|
||||
count: { type: 'number' }
|
||||
},
|
||||
required: ['group_id', 'message_seq', 'count']
|
||||
required: ['group_id']
|
||||
} as const satisfies JSONSchema;
|
||||
|
||||
type Payload = FromSchema<typeof SchemaData>;
|
||||
@@ -32,16 +31,20 @@ export default class GoCQHTTPGetGroupMsgHistory extends BaseAction<Payload, Resp
|
||||
if (!group) {
|
||||
throw `群${payload.group_id}不存在`;
|
||||
}
|
||||
const startMsgId = (await dbUtil.getMsgByShortId(payload.message_seq))?.msgId || '0';
|
||||
// log("startMsgId", startMsgId)
|
||||
const historyResult = (await NTQQMsgApi.getMsgHistory({
|
||||
let targetMsgShortId, count = parseInt(payload.count?.toString() ?? '20');
|
||||
const peer = {
|
||||
chatType: ChatType.group,
|
||||
peerUid: group.groupCode
|
||||
}, startMsgId, parseInt(payload.count?.toString()) || 20));
|
||||
//logDebug(historyResult);
|
||||
const msgList = historyResult.msgList;
|
||||
};
|
||||
let msgList: RawMessage[];
|
||||
if (!payload.message_seq) {
|
||||
msgList = (await NTQQMsgApi.getLastestMsgByUids(peer, count)).msgList;
|
||||
} else {
|
||||
const startMsgId = (await MessageUnique.getMsgIdAndPeerByShortId(targetMsgShortId ?? (payload.message_seq ?? 0)))?.MsgId || '0';
|
||||
msgList = (await NTQQMsgApi.getMsgHistory(peer, startMsgId, count)).msgList;
|
||||
}
|
||||
await Promise.all(msgList.map(async msg => {
|
||||
msg.id = await dbUtil.addMsg(msg);
|
||||
msg.id = await MessageUnique.createMsg({ guildId: '', chatType: msg.chatType, peerUid: msg.peerUid }, msg.msgId);
|
||||
}));
|
||||
const ob11MsgList = await Promise.all(msgList.map(msg => OB11Constructor.message(msg)));
|
||||
return { 'messages': ob11MsgList };
|
||||
|
@@ -3,6 +3,7 @@ import BaseAction from '../BaseAction';
|
||||
import { ActionName } from '../types';
|
||||
import { JSONSchema } from 'json-schema-to-ts';
|
||||
import { NTQQSystemApi } from '@/core';
|
||||
import { sleep } from '@/common/utils/helper';
|
||||
|
||||
const SchemaData = {
|
||||
type: 'object',
|
||||
@@ -15,7 +16,8 @@ export class GetOnlineClient extends BaseAction<void, Array<any>> {
|
||||
actionName = ActionName.GetOnlineClient;
|
||||
|
||||
protected async _handle(payload: void) {
|
||||
//console.log(await NTQQSystemApi.getOnlineDev());
|
||||
NTQQSystemApi.getOnlineDev();
|
||||
await sleep(500);
|
||||
return DeviceList;
|
||||
}
|
||||
}
|
||||
|
51
src/onebot11/action/go-cqhttp/UploadPrivareFile.ts
Normal file
51
src/onebot11/action/go-cqhttp/UploadPrivareFile.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import BaseAction from '../BaseAction';
|
||||
import { getGroup } from '@/core/data';
|
||||
import { ActionName } from '../types';
|
||||
import { SendMsgElementConstructor } from '@/core/entities/constructor';
|
||||
import { ChatType, Peer, SendFileElement } from '@/core/entities';
|
||||
import fs from 'fs';
|
||||
import { SendMsg, sendMsg } from '@/onebot11/action/msg/SendMsg';
|
||||
import { uri2local } from '@/common/utils/file';
|
||||
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
|
||||
import { NTQQFriendApi, NTQQUserApi } from '@/core';
|
||||
const SchemaData = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
user_id: { type: ['number', 'string'] },
|
||||
file: { type: 'string' },
|
||||
name: { type: 'string' }
|
||||
},
|
||||
required: ['user_id', 'file', 'name']
|
||||
} as const satisfies JSONSchema;
|
||||
|
||||
type Payload = FromSchema<typeof SchemaData>;
|
||||
|
||||
export default class GoCQHTTPUploadPrivateFile extends BaseAction<Payload, null> {
|
||||
actionName = ActionName.GOCQHTTP_UploadPrivateFile;
|
||||
PayloadSchema = SchemaData;
|
||||
async getPeer(payload: Payload): Promise<Peer> {
|
||||
if (payload.user_id) {
|
||||
const peerUid = await NTQQUserApi.getUidByUin(payload.user_id.toString());
|
||||
if (!peerUid) {
|
||||
throw `私聊${payload.user_id}不存在`;
|
||||
}
|
||||
const isBuddy = await NTQQFriendApi.isBuddy(peerUid);
|
||||
return { chatType: isBuddy ? ChatType.friend : ChatType.temp, peerUid };
|
||||
}
|
||||
throw '缺少参数 user_id';
|
||||
}
|
||||
protected async _handle(payload: Payload): Promise<null> {
|
||||
const peer = await this.getPeer(payload);
|
||||
let file = payload.file;
|
||||
if (fs.existsSync(file)) {
|
||||
file = `file://${file}`;
|
||||
}
|
||||
const downloadResult = await uri2local(file);
|
||||
if (downloadResult.errMsg) {
|
||||
throw new Error(downloadResult.errMsg);
|
||||
}
|
||||
const sendFileEle: SendFileElement = await SendMsgElementConstructor.file(downloadResult.path, payload.name);
|
||||
await sendMsg(peer, [sendFileEle], [], true);
|
||||
return null;
|
||||
}
|
||||
}
|
31
src/onebot11/action/group/DelEssenceMsg.ts
Normal file
31
src/onebot11/action/group/DelEssenceMsg.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
|
||||
import BaseAction from '../BaseAction';
|
||||
import { ActionName } from '../types';
|
||||
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
|
||||
import { NTQQGroupApi } from '@/core';
|
||||
import { MessageUnique } from '@/common/utils/MessageUnique';
|
||||
|
||||
const SchemaData = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
message_id: { type: ['number', 'string'] }
|
||||
},
|
||||
required: ['message_id']
|
||||
} as const satisfies JSONSchema;
|
||||
|
||||
type Payload = FromSchema<typeof SchemaData>;
|
||||
|
||||
export default class DelEssenceMsg extends BaseAction<Payload, any> {
|
||||
actionName = ActionName.DelEssenceMsg;
|
||||
PayloadSchema = SchemaData;
|
||||
protected async _handle(payload: Payload): Promise<any> {
|
||||
const msg = await MessageUnique.getMsgIdAndPeerByShortId(parseInt(payload.message_id.toString()));
|
||||
if (!msg) {
|
||||
throw new Error('msg not found');
|
||||
}
|
||||
return await NTQQGroupApi.removeGroupEssence(
|
||||
msg.Peer.peerUid,
|
||||
msg.MsgId
|
||||
);
|
||||
}
|
||||
}
|
@@ -1,11 +1,10 @@
|
||||
import { OB11GroupMember } from '../../types';
|
||||
import { getGroup, getGroupMember, groupMembers } from '@/core/data';
|
||||
import { getGroup, getGroupMember, groupMembers, selfInfo } from '@/core/data';
|
||||
import { OB11Constructor } from '../../constructor';
|
||||
import BaseAction from '../BaseAction';
|
||||
import { ActionName } from '../types';
|
||||
import { NTQQUserApi } from '@/core/apis/user';
|
||||
import { log, logDebug } from '@/common/utils/log';
|
||||
import { isNull } from '../../../common/utils/helper';
|
||||
import { logDebug } from '@/common/utils/log';
|
||||
import { WebApi } from '@/core/apis/webapi';
|
||||
import { NTQQGroupApi } from '@/core';
|
||||
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
|
||||
@@ -28,25 +27,29 @@ class GetGroupMemberInfo extends BaseAction<Payload, OB11GroupMember> {
|
||||
PayloadSchema = SchemaData;
|
||||
protected async _handle(payload: Payload) {
|
||||
const group = await getGroup(payload.group_id.toString());
|
||||
const role = (await getGroupMember(payload.group_id, selfInfo.uin))?.role;
|
||||
const isPrivilege = role === 3 || role === 4;
|
||||
if (!group) {
|
||||
throw (`群(${payload.group_id})不存在`);
|
||||
}
|
||||
const webGroupMembers = await WebApi.getGroupMembers(payload.group_id.toString());
|
||||
if (payload.no_cache == true || payload.no_cache === 'true') {
|
||||
groupMembers.set(group.groupCode, await NTQQGroupApi.getGroupMembers(payload.group_id.toString()));
|
||||
}
|
||||
const member = await getGroupMember(payload.group_id.toString(), payload.user_id.toString());
|
||||
// log(member);
|
||||
if (member) {
|
||||
logDebug('获取群成员详细信息');
|
||||
try {
|
||||
const info = (await NTQQUserApi.getUserDetailInfo(member.uid));
|
||||
logDebug('群成员详细信息结果', info);
|
||||
Object.assign(member, info);
|
||||
} catch (e) {
|
||||
logDebug('获取群成员详细信息失败, 只能返回基础信息', e);
|
||||
}
|
||||
const retMember = OB11Constructor.groupMember(payload.group_id.toString(), member);
|
||||
//早返回
|
||||
if (!member) {
|
||||
throw (`群(${payload.group_id})成员${payload.user_id}不存在`);
|
||||
}
|
||||
try {
|
||||
const info = (await NTQQUserApi.getUserDetailInfo(member.uid));
|
||||
logDebug('群成员详细信息结果', info);
|
||||
Object.assign(member, info);
|
||||
} catch (e) {
|
||||
logDebug('获取群成员详细信息失败, 只能返回基础信息', e);
|
||||
}
|
||||
const retMember = OB11Constructor.groupMember(payload.group_id.toString(), member);
|
||||
if (isPrivilege) {
|
||||
const webGroupMembers = await WebApi.getGroupMembers(payload.group_id.toString());
|
||||
for (let i = 0, len = webGroupMembers.length; i < len; i++) {
|
||||
if (webGroupMembers[i]?.uin && webGroupMembers[i].uin === retMember.user_id) {
|
||||
retMember.join_time = webGroupMembers[i]?.join_time;
|
||||
@@ -55,11 +58,17 @@ class GetGroupMemberInfo extends BaseAction<Payload, OB11GroupMember> {
|
||||
retMember.level = webGroupMembers[i]?.lv.level.toString();
|
||||
}
|
||||
}
|
||||
return retMember;
|
||||
} else {
|
||||
throw (`群(${payload.group_id})成员${payload.user_id}不存在`);
|
||||
const LastestMsgList = await NTQQGroupApi.getLastestMsg(payload.group_id.toString(), [payload.user_id.toString()]);
|
||||
if (LastestMsgList?.msgList?.length && LastestMsgList?.msgList?.length > 0) {
|
||||
const last_send_time = LastestMsgList.msgList[0].msgTime;
|
||||
if (last_send_time && last_send_time != '0' && last_send_time != '') {
|
||||
retMember.last_sent_time = parseInt(last_send_time);
|
||||
retMember.join_time = Math.round(Date.now() / 1000);//兜底数据 防止群管乱杀
|
||||
}
|
||||
}
|
||||
}
|
||||
return retMember;
|
||||
}
|
||||
}
|
||||
|
||||
export default GetGroupMemberInfo;
|
||||
export default GetGroupMemberInfo;
|
@@ -3,13 +3,9 @@ import { OB11GroupMember } from '../../types';
|
||||
import { OB11Constructor } from '../../constructor';
|
||||
import BaseAction from '../BaseAction';
|
||||
import { ActionName } from '../types';
|
||||
import { napCatCore, NTQQGroupApi, NTQQUserApi } from '@/core';
|
||||
import { NTQQGroupApi } from '@/core';
|
||||
import { WebApi } from '@/core/apis/webapi';
|
||||
import { logDebug } from '@/common/utils/log';
|
||||
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
|
||||
import { ob11Config } from '@/onebot11/config';
|
||||
import { dbUtil } from '@/common/utils/db';
|
||||
import { TypeConvert } from '@/common/utils/type';
|
||||
|
||||
const SchemaData = {
|
||||
type: 'object',
|
||||
@@ -43,7 +39,11 @@ class GetGroupMemberList extends BaseAction<Payload, OB11GroupMember[]> {
|
||||
|
||||
const MemberMap: Map<number, OB11GroupMember> = new Map<number, OB11GroupMember>();
|
||||
// 转为Map 方便索引
|
||||
const date = Math.round(Date.now() / 1000);
|
||||
for (let i = 0, len = _groupMembers.length; i < len; i++) {
|
||||
// 保证基础数据有这个 同时避免群管插件过于依赖这个杀了
|
||||
_groupMembers[i].join_time = date;
|
||||
_groupMembers[i].last_sent_time = date;
|
||||
MemberMap.set(_groupMembers[i].user_id, _groupMembers[i]);
|
||||
}
|
||||
|
||||
@@ -63,15 +63,15 @@ class GetGroupMemberList extends BaseAction<Payload, OB11GroupMember[]> {
|
||||
MemberMap.set(webGroupMembers[i]?.uin, MemberData);
|
||||
}
|
||||
}
|
||||
} else if (ob11Config.GroupLocalTime.Record && ob11Config.GroupLocalTime.RecordList[0] === '-1' || ob11Config.GroupLocalTime.RecordList.includes(payload.group_id.toString())) {
|
||||
const _sendAndJoinRember = await dbUtil.getLastSentTimeAndJoinTime(TypeConvert.toNumber(payload.group_id));
|
||||
_sendAndJoinRember.forEach((element) => {
|
||||
const MemberData = MemberMap.get(element.user_id);
|
||||
} else {
|
||||
const DateMap = await NTQQGroupApi.getGroupMemberLastestSendTimeCache(payload.group_id.toString());//开始从本地拉取
|
||||
for (const DateUin of DateMap.keys()) {
|
||||
const MemberData = MemberMap.get(parseInt(DateUin));
|
||||
if (MemberData) {
|
||||
MemberData.join_time = element.join_time;
|
||||
MemberData.last_sent_time = element.last_sent_time;
|
||||
MemberData.last_sent_time = parseInt(DateMap.get(DateUin)!);
|
||||
//join_time 有基础数据兜底
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
// 还原索引到Array 一同返回
|
||||
|
||||
@@ -82,7 +82,7 @@ class GetGroupMemberList extends BaseAction<Payload, OB11GroupMember[]> {
|
||||
// }
|
||||
|
||||
// _groupMembers = Array.from(retData);
|
||||
|
||||
|
||||
_groupMembers = Array.from(MemberMap.values());
|
||||
return _groupMembers;
|
||||
}
|
||||
|
30
src/onebot11/action/group/SetEssenceMsg.ts
Normal file
30
src/onebot11/action/group/SetEssenceMsg.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import BaseAction from '../BaseAction';
|
||||
import { ActionName } from '../types';
|
||||
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
|
||||
import { NTQQGroupApi, NTQQMsgApi } from '@/core';
|
||||
import { MessageUnique } from '@/common/utils/MessageUnique';
|
||||
|
||||
const SchemaData = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
message_id: { type: ['number', 'string'] }
|
||||
},
|
||||
required: ['message_id']
|
||||
} as const satisfies JSONSchema;
|
||||
|
||||
type Payload = FromSchema<typeof SchemaData>;
|
||||
|
||||
export default class SetEssenceMsg extends BaseAction<Payload, any> {
|
||||
actionName = ActionName.SetEssenceMsg;
|
||||
PayloadSchema = SchemaData;
|
||||
protected async _handle(payload: Payload): Promise<any> {
|
||||
const msg = await MessageUnique.getMsgIdAndPeerByShortId(parseInt(payload.message_id.toString()));
|
||||
if (!msg) {
|
||||
throw new Error('msg not found');
|
||||
}
|
||||
return await NTQQGroupApi.addGroupEssence(
|
||||
msg.Peer.peerUid,
|
||||
msg.MsgId
|
||||
);
|
||||
}
|
||||
}
|
@@ -9,7 +9,7 @@ const SchemaData = {
|
||||
properties: {
|
||||
flag: { type: 'string' },
|
||||
approve: { type: ['string', 'boolean'] },
|
||||
reason: { type: 'string' }
|
||||
reason: { type: 'string', nullable: true, }
|
||||
},
|
||||
required: ['flag'],
|
||||
} as const satisfies JSONSchema;
|
||||
|
@@ -34,7 +34,7 @@ import SetGroupAdmin from './group/SetGroupAdmin';
|
||||
import SetGroupCard from './group/SetGroupCard';
|
||||
import GetImage from './file/GetImage';
|
||||
import GetRecord from './file/GetRecord';
|
||||
import { GoCQHTTPMarkMsgAsRead, MarkGroupMsgAsRead, MarkPrivateMsgAsRead } from './msg/MarkMsgAsRead';
|
||||
import { GoCQHTTPMarkMsgAsRead, MarkAllMsgAsRead, MarkGroupMsgAsRead, MarkPrivateMsgAsRead } from './msg/MarkMsgAsRead';
|
||||
import CleanCache from './system/CleanCache';
|
||||
import GoCQHTTPUploadGroupFile from './go-cqhttp/UploadGroupFile';
|
||||
import { GetConfigAction, SetConfigAction } from '@/onebot11/action/extends/Config';
|
||||
@@ -70,6 +70,14 @@ import { SetSelfProfile } from './extends/SetSelfProfile';
|
||||
import { shareGroupEx, sharePeer } from './extends/sharePeer';
|
||||
import { CreateCollection } from './extends/CreateCollection';
|
||||
import { SetLongNick } from './extends/SetLongNick';
|
||||
import DelEssenceMsg from './group/DelEssenceMsg';
|
||||
import SetEssenceMsg from './group/SetEssenceMsg';
|
||||
import GetRecentContact from './user/GetRecentContact';
|
||||
import { GetProfileLike } from './extends/GetProfileLike';
|
||||
import SetGroupHeader from './extends/SetGroupHeader';
|
||||
import { FetchCustomFace } from './extends/FetchCustomFace';
|
||||
import GoCQHTTPUploadPrivateFile from './go-cqhttp/UploadPrivareFile';
|
||||
import TestApi01 from './extends/TestApi01';
|
||||
|
||||
export const actionHandlers = [
|
||||
new RebootNormal(),
|
||||
@@ -81,10 +89,6 @@ export const actionHandlers = [
|
||||
new sharePeer(),
|
||||
new CreateCollection(),
|
||||
new SetLongNick(),
|
||||
// new GetConfigAction(),
|
||||
// new SetConfigAction(),
|
||||
// new GetGroupAddRequest(),
|
||||
// TranslateEnWordToZn = "translate_en2zh",
|
||||
new ForwardFriendSingleMsg(),
|
||||
new ForwardGroupSingleMsg(),
|
||||
new MarkGroupMsgAsRead(),
|
||||
@@ -101,9 +105,13 @@ export const actionHandlers = [
|
||||
new GetMsg(),
|
||||
new GetLoginInfo(),
|
||||
new GetFriendList(),
|
||||
new GetGroupList(), new GetGroupInfo(),
|
||||
new GetGroupMemberList(), new GetGroupMemberInfo(),
|
||||
new SendGroupMsg(), new SendPrivateMsg(), new SendMsg(),
|
||||
new GetGroupList(),
|
||||
new GetGroupInfo(),
|
||||
new GetGroupMemberList(),
|
||||
new GetGroupMemberInfo(),
|
||||
new SendGroupMsg(),
|
||||
new SendPrivateMsg(),
|
||||
new SendMsg(),
|
||||
new DeleteMsg(),
|
||||
new SetGroupAddRequest(),
|
||||
new SetFriendAddRequest(),
|
||||
@@ -121,9 +129,7 @@ export const actionHandlers = [
|
||||
new GetImage(),
|
||||
new GetRecord(),
|
||||
new SetMsgEmojiLike(),
|
||||
// new CleanCache(),
|
||||
new GetCookies(),
|
||||
//
|
||||
new SetOnlineStatus(),
|
||||
new GetRobotUinRange(),
|
||||
new GetFriendWithCategory(),
|
||||
@@ -147,9 +153,17 @@ export const actionHandlers = [
|
||||
new GoCQHTTPGetForwardMsgAction(),
|
||||
new GetFriendMsgHistory(),
|
||||
new GoCQHTTPHandleQuickAction(),
|
||||
new GetGroupSystemMsg()
|
||||
new GetGroupSystemMsg(),
|
||||
new DelEssenceMsg(),
|
||||
new SetEssenceMsg(),
|
||||
new GetRecentContact(),
|
||||
new MarkAllMsgAsRead(),
|
||||
new GetProfileLike(),
|
||||
new SetGroupHeader(),
|
||||
new FetchCustomFace(),
|
||||
new GoCQHTTPUploadPrivateFile(),
|
||||
new TestApi01()
|
||||
];
|
||||
|
||||
function initActionMap() {
|
||||
const actionMap = new Map<string, BaseAction<any, any>>();
|
||||
for (const action of actionHandlers) {
|
||||
|
@@ -1,13 +1,18 @@
|
||||
import { NTQQMsgApi } from '@/core/apis';
|
||||
import { ActionName } from '../types';
|
||||
import BaseAction from '../BaseAction';
|
||||
import { dbUtil } from '@/common/utils/db';
|
||||
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
|
||||
import { MessageUnique } from '@/common/utils/MessageUnique';
|
||||
|
||||
const SchemaData = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
message_id: { type: 'number' },
|
||||
message_id: {
|
||||
oneOf:[
|
||||
{ type: 'number' },
|
||||
{ type: 'string' }
|
||||
]
|
||||
}
|
||||
},
|
||||
required: ['message_id']
|
||||
} as const satisfies JSONSchema;
|
||||
@@ -18,9 +23,9 @@ class DeleteMsg extends BaseAction<Payload, void> {
|
||||
actionName = ActionName.DeleteMsg;
|
||||
PayloadSchema = SchemaData;
|
||||
protected async _handle(payload: Payload) {
|
||||
const msg = await dbUtil.getMsgByShortId(payload.message_id);
|
||||
const msg = await MessageUnique.getMsgIdAndPeerByShortId(Number(payload.message_id));
|
||||
if (msg) {
|
||||
await NTQQMsgApi.recallMsg({ peerUid: msg.peerUid, chatType: msg.chatType }, [msg.msgId]);
|
||||
await NTQQMsgApi.recallMsg(msg.Peer, [msg.MsgId]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,16 +1,16 @@
|
||||
import BaseAction from '../BaseAction';
|
||||
import { NTQQMsgApi, NTQQUserApi } from '@/core/apis';
|
||||
import { ChatType, Peer } from '@/core/entities';
|
||||
import { dbUtil } from '@/common/utils/db';
|
||||
import { ActionName } from '../types';
|
||||
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
|
||||
import { MessageUnique } from '@/common/utils/MessageUnique';
|
||||
|
||||
const SchemaData = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
message_id: { type: 'number' },
|
||||
group_id: { type: [ 'number' , 'string' ] },
|
||||
user_id: { type: [ 'number' , 'string' ] }
|
||||
group_id: { type: ['number', 'string'] },
|
||||
user_id: { type: ['number', 'string'] }
|
||||
},
|
||||
required: ['message_id']
|
||||
} as const satisfies JSONSchema;
|
||||
@@ -30,18 +30,14 @@ class ForwardSingleMsg extends BaseAction<Payload, null> {
|
||||
}
|
||||
|
||||
protected async _handle(payload: Payload): Promise<null> {
|
||||
const msg = await dbUtil.getMsgByShortId(payload.message_id);
|
||||
const msg = await MessageUnique.getMsgIdAndPeerByShortId(payload.message_id);
|
||||
if (!msg) {
|
||||
throw new Error(`无法找到消息${payload.message_id}`);
|
||||
}
|
||||
const peer = await this.getTargetPeer(payload);
|
||||
const ret = await NTQQMsgApi.forwardMsg(
|
||||
{
|
||||
chatType: msg.chatType,
|
||||
peerUid: msg.peerUid,
|
||||
},
|
||||
const ret = await NTQQMsgApi.forwardMsg(msg.Peer,
|
||||
peer,
|
||||
[msg.msgId],
|
||||
[msg.MsgId],
|
||||
);
|
||||
if (ret.result !== 0) {
|
||||
throw new Error(`转发消息失败 ${ret.errMsg}`);
|
||||
|
@@ -2,8 +2,9 @@ import { OB11Message } from '../../types';
|
||||
import { OB11Constructor } from '../../constructor';
|
||||
import BaseAction from '../BaseAction';
|
||||
import { ActionName } from '../types';
|
||||
import { dbUtil } from '@/common/utils/db';
|
||||
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
|
||||
import { MessageUnique } from '@/common/utils/MessageUnique';
|
||||
import { NTQQMsgApi } from '@/core';
|
||||
|
||||
|
||||
export type ReturnDataType = OB11Message
|
||||
@@ -11,7 +12,7 @@ export type ReturnDataType = OB11Message
|
||||
const SchemaData = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
message_id: { type: ['number','string'] },
|
||||
message_id: { type: ['number', 'string'] },
|
||||
},
|
||||
required: ['message_id']
|
||||
} as const satisfies JSONSchema;
|
||||
@@ -26,14 +27,23 @@ class GetMsg extends BaseAction<Payload, OB11Message> {
|
||||
if (!payload.message_id) {
|
||||
throw Error('参数message_id不能为空');
|
||||
}
|
||||
let msg = await dbUtil.getMsgByShortId(parseInt(payload.message_id.toString()));
|
||||
if (!msg) {
|
||||
msg = await dbUtil.getMsgByLongId(payload.message_id.toString());
|
||||
}
|
||||
if (!msg) {
|
||||
const MsgShortId = await MessageUnique.getShortIdByMsgId(payload.message_id.toString());
|
||||
const msgIdWithPeer = await MessageUnique.getMsgIdAndPeerByShortId(MsgShortId || parseInt(payload.message_id.toString()));
|
||||
if (!msgIdWithPeer) {
|
||||
throw ('消息不存在');
|
||||
}
|
||||
return await OB11Constructor.message(msg);
|
||||
const peer = { guildId: '', peerUid: msgIdWithPeer?.Peer.peerUid, chatType: msgIdWithPeer.Peer.chatType };
|
||||
const msg = await NTQQMsgApi.getMsgsByMsgId(
|
||||
peer,
|
||||
[msgIdWithPeer?.MsgId || payload.message_id.toString()]);
|
||||
const retMsg = await OB11Constructor.message(msg.msgList[0]);
|
||||
try {
|
||||
retMsg.message_id = MessageUnique.createMsg(peer, msg.msgList[0].msgId)!;
|
||||
retMsg.message_seq = retMsg.message_id;
|
||||
retMsg.real_id = retMsg.message_id;
|
||||
} catch (e) {
|
||||
}
|
||||
return retMsg;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -60,3 +60,12 @@ export class GoCQHTTPMarkMsgAsRead extends BaseAction<Payload, null> {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export class MarkAllMsgAsRead extends BaseAction<Payload, null> {
|
||||
actionName = ActionName._MarkAllMsgAsRead;
|
||||
|
||||
protected async _handle(payload: Payload): Promise<null> {
|
||||
await NTQQMsgApi.markallMsgAsRead();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@@ -5,51 +5,29 @@ import {
|
||||
Group,
|
||||
IdMusicSignPostData,
|
||||
NTQQFileApi,
|
||||
NTQQMsgApi,
|
||||
SendArkElement,
|
||||
SendMessageElement,
|
||||
SendMsgElementConstructor,
|
||||
SignMusicWrapper
|
||||
} from '@/core';
|
||||
import { getGroupMember } from '@/core/data';
|
||||
import { dbUtil } from '@/common/utils/db';
|
||||
import { logDebug, logError } from '@/common/utils/log';
|
||||
import { logError, logWarn } from '@/common/utils/log';
|
||||
import { uri2local } from '@/common/utils/file';
|
||||
import { ob11Config } from '@/onebot11/config';
|
||||
import { RequestUtil } from '@/common/utils/request';
|
||||
import fs from 'node:fs';
|
||||
import { MessageUnique } from '@/common/utils/MessageUnique';
|
||||
|
||||
export type MessageContext = {
|
||||
group?: Group,
|
||||
deleteAfterSentFiles: string[],
|
||||
}
|
||||
|
||||
async function handleOb11FileLikeMessage(
|
||||
{ data: { file, name: payloadFileName } }: OB11MessageFileBase,
|
||||
{ data: inputdata }: OB11MessageFileBase,
|
||||
{ deleteAfterSentFiles }: MessageContext
|
||||
) {
|
||||
let uri = file;
|
||||
|
||||
const cache = await dbUtil.getFileCacheByName(file);
|
||||
if (cache) {
|
||||
if (fs.existsSync(cache.path)) {
|
||||
uri = 'file://' + cache.path;
|
||||
} else if (cache.url) {
|
||||
uri = cache.url;
|
||||
} else {
|
||||
const fileMsg = await dbUtil.getMsgByLongId(cache.msgId);
|
||||
if (fileMsg) {
|
||||
cache.path = await NTQQFileApi.downloadMedia(
|
||||
fileMsg.msgId, fileMsg.chatType, fileMsg.peerUid,
|
||||
cache.elementId, '', ''
|
||||
);
|
||||
uri = 'file://' + cache.path;
|
||||
dbUtil.updateFileCache(cache);
|
||||
}
|
||||
}
|
||||
logDebug('找到文件缓存', uri);
|
||||
}
|
||||
|
||||
const { path, isLocal, fileName, errMsg } = (await uri2local(uri));
|
||||
//有的奇怪的框架将url作为参数 而不是file 此时优先url
|
||||
const { path, isLocal, fileName, errMsg } = (await uri2local(inputdata?.url || inputdata.file));
|
||||
|
||||
if (errMsg) {
|
||||
logError('文件下载失败', errMsg);
|
||||
@@ -60,7 +38,7 @@ async function handleOb11FileLikeMessage(
|
||||
deleteAfterSentFiles.push(path);
|
||||
}
|
||||
|
||||
return { path, fileName: payloadFileName || fileName };
|
||||
return { path, fileName: inputdata.name || fileName };
|
||||
}
|
||||
|
||||
const _handlers: {
|
||||
@@ -69,9 +47,9 @@ const _handlers: {
|
||||
// This picks the correct message type out
|
||||
// How great the type system of TypeScript is!
|
||||
context: MessageContext
|
||||
) => SendMessageElement | undefined | Promise<SendMessageElement | undefined>
|
||||
) => Promise<SendMessageElement | undefined>
|
||||
} = {
|
||||
[OB11MessageDataType.text]: ({ data: { text } }) => SendMsgElementConstructor.text(text),
|
||||
[OB11MessageDataType.text]: async ({ data: { text } }) => SendMsgElementConstructor.text(text),
|
||||
|
||||
[OB11MessageDataType.at]: async ({ data: { qq: atQQ } }, context) => {
|
||||
if (!context.group) return undefined;
|
||||
@@ -84,17 +62,21 @@ const _handlers: {
|
||||
SendMsgElementConstructor.at(atQQ, atMember.uid, AtType.atUser, atMember.cardName || atMember.nick) :
|
||||
undefined;
|
||||
},
|
||||
|
||||
[OB11MessageDataType.reply]: async ({ data: { id } }) => {
|
||||
const replyMsg = await dbUtil.getMsgByShortId(parseInt(id));
|
||||
const replyMsgM = MessageUnique.getMsgIdAndPeerByShortId(parseInt(id));
|
||||
if (!replyMsgM) {
|
||||
logWarn('回复消息不存在', id);
|
||||
return undefined;
|
||||
}
|
||||
const replyMsg = (await NTQQMsgApi.getMsgsByMsgId(replyMsgM?.Peer!, [replyMsgM?.MsgId!])).msgList[0];
|
||||
return replyMsg ?
|
||||
SendMsgElementConstructor.reply(replyMsg.msgSeq, replyMsg.msgId, replyMsg.senderUin!, replyMsg.senderUin!) :
|
||||
undefined;
|
||||
},
|
||||
|
||||
[OB11MessageDataType.face]: ({ data: { id } }) => SendMsgElementConstructor.face(parseInt(id)),
|
||||
[OB11MessageDataType.face]: async ({ data: { id } }) => SendMsgElementConstructor.face(parseInt(id)),
|
||||
|
||||
[OB11MessageDataType.mface]: ({
|
||||
[OB11MessageDataType.mface]: async ({
|
||||
data: {
|
||||
emoji_package_id,
|
||||
emoji_id,
|
||||
@@ -144,13 +126,13 @@ const _handlers: {
|
||||
[OB11MessageDataType.voice]: async (sendMsg, context) =>
|
||||
SendMsgElementConstructor.ptt((await handleOb11FileLikeMessage(sendMsg, context)).path),
|
||||
|
||||
[OB11MessageDataType.json]: ({ data: { data } }) => SendMsgElementConstructor.ark(data),
|
||||
[OB11MessageDataType.json]: async ({ data: { data } }) => SendMsgElementConstructor.ark(data),
|
||||
|
||||
[OB11MessageDataType.dice]: ({ data: { result } }) => SendMsgElementConstructor.dice(result),
|
||||
[OB11MessageDataType.dice]: async ({ data: { result } }) => SendMsgElementConstructor.dice(result),
|
||||
|
||||
[OB11MessageDataType.RPS]: ({ data: { result } }) => SendMsgElementConstructor.rps(result),
|
||||
[OB11MessageDataType.RPS]: async ({ data: { result } }) => SendMsgElementConstructor.rps(result),
|
||||
|
||||
[OB11MessageDataType.markdown]: ({ data: { content } }) => SendMsgElementConstructor.markdown(content),
|
||||
[OB11MessageDataType.markdown]: async ({ data: { content } }) => SendMsgElementConstructor.markdown(content),
|
||||
|
||||
[OB11MessageDataType.music]: async ({ data }) => {
|
||||
// 保留, 直到...找到更好的解决方案
|
||||
@@ -202,13 +184,13 @@ const _handlers: {
|
||||
}
|
||||
},
|
||||
|
||||
[OB11MessageDataType.node]: () => undefined,
|
||||
[OB11MessageDataType.node]: async () => undefined,
|
||||
|
||||
[OB11MessageDataType.forward]: () => undefined,
|
||||
[OB11MessageDataType.forward]: async () => undefined,
|
||||
|
||||
[OB11MessageDataType.xml]: () => undefined,
|
||||
[OB11MessageDataType.xml]: async () => undefined,
|
||||
|
||||
[OB11MessageDataType.poke]: () => undefined,
|
||||
[OB11MessageDataType.poke]: async () => undefined,
|
||||
|
||||
[OB11MessageDataType.Location]: async () => {
|
||||
return SendMsgElementConstructor.location();
|
||||
@@ -219,7 +201,7 @@ const handlers = <{
|
||||
[Key in OB11MessageDataType]: (
|
||||
sendMsg: OB11MessageData,
|
||||
context: MessageContext
|
||||
) => SendMessageElement | undefined | Promise<SendMessageElement | undefined>
|
||||
) => Promise<SendMessageElement | undefined>
|
||||
}>_handlers;
|
||||
|
||||
export default async function createSendElements(
|
||||
@@ -227,18 +209,20 @@ export default async function createSendElements(
|
||||
group?: Group,
|
||||
ignoreTypes: OB11MessageDataType[] = []
|
||||
) {
|
||||
const sendElements: SendMessageElement[] = [];
|
||||
const deleteAfterSentFiles: string[] = [];
|
||||
const callResultList: Array<Promise<SendMessageElement | undefined>> = [];
|
||||
for (const sendMsg of messageData) {
|
||||
if (ignoreTypes.includes(sendMsg.type)) {
|
||||
continue;
|
||||
}
|
||||
const callResult = await handlers[sendMsg.type](
|
||||
const callResult = handlers[sendMsg.type](
|
||||
sendMsg,
|
||||
{ group, deleteAfterSentFiles }
|
||||
);
|
||||
if (callResult) sendElements.push(callResult);
|
||||
)?.catch(undefined);
|
||||
callResultList.push(callResult);
|
||||
}
|
||||
const ret = await Promise.all(callResultList);
|
||||
const sendElements: SendMessageElement[] = ret.filter(ele => ele) as SendMessageElement[];
|
||||
return { sendElements, deleteAfterSentFiles };
|
||||
}
|
||||
|
||||
|
@@ -1,12 +1,11 @@
|
||||
import { ChatType, ElementType, Group, NTQQMsgApi, Peer, RawMessage, SendMessageElement } from '@/core';
|
||||
import { OB11MessageNode } from '@/onebot11/types';
|
||||
import { selfInfo } from '@/core/data';
|
||||
import { dbUtil } from '@/common/utils/db';
|
||||
import createSendElements from '@/onebot11/action/msg/SendMsg/create-send-elements';
|
||||
import { logDebug, logError } from '@/common/utils/log';
|
||||
import { sleep } from '@/common/utils/helper';
|
||||
import fs from 'node:fs';
|
||||
import { normalize, sendMsg } from '@/onebot11/action/msg/SendMsg/index';
|
||||
import { MessageUnique } from '@/common/utils/MessageUnique';
|
||||
|
||||
async function cloneMsg(msg: RawMessage): Promise<RawMessage | undefined> {
|
||||
const selfPeer = {
|
||||
@@ -14,7 +13,7 @@ async function cloneMsg(msg: RawMessage): Promise<RawMessage | undefined> {
|
||||
peerUid: selfInfo.uid
|
||||
};
|
||||
|
||||
// logDebug('克隆的目标消息', msg);
|
||||
//logDebug('克隆的目标消息', msg);
|
||||
|
||||
const sendElements: SendMessageElement[] = [];
|
||||
|
||||
@@ -54,13 +53,14 @@ export async function handleForwardNode(destPeer: Peer, messageNodes: OB11Messag
|
||||
const nodeId = messageNode.data.id;
|
||||
// 有nodeId表示一个子转发消息卡片
|
||||
if (nodeId) {
|
||||
const nodeMsg = await dbUtil.getMsgByShortId(parseInt(nodeId));
|
||||
const nodeMsg = MessageUnique.getMsgIdAndPeerByShortId(parseInt(nodeId));
|
||||
if (!needClone) {
|
||||
nodeMsgIds.push(nodeMsg!.msgId);
|
||||
nodeMsgIds.push(nodeMsg!.MsgId);
|
||||
} else {
|
||||
if (nodeMsg!.peerUid !== selfInfo.uid) {
|
||||
if (nodeMsg!.Peer.peerUid !== selfInfo.uid) {
|
||||
// need cloning
|
||||
const clonedMsg = await cloneMsg(nodeMsg!);
|
||||
const rawClone = await NTQQMsgApi.getMsgsByMsgId(nodeMsg?.Peer!, [nodeMsg?.MsgId!]);
|
||||
const clonedMsg = await cloneMsg(rawClone.msgList[0]);
|
||||
if (clonedMsg) {
|
||||
nodeMsgIds.push(clonedMsg.msgId);
|
||||
}
|
||||
@@ -91,7 +91,7 @@ export async function handleForwardNode(destPeer: Peer, messageNodes: OB11Messag
|
||||
//logDebug(sendElementsSplit);
|
||||
}
|
||||
// log("分割后的转发节点", sendElementsSplit)
|
||||
const MsgNodeList: Promise<RawMessage>[] = [];
|
||||
const MsgNodeList: Promise<RawMessage | undefined>[] = [];
|
||||
for (const sendElementsSplitElement of sendElementsSplit) {
|
||||
MsgNodeList.push(sendMsg(selfPeer, sendElementsSplitElement, [], true));
|
||||
await sleep(Math.trunc(sendElementsSplit.length / 10) * 100);
|
||||
@@ -99,7 +99,9 @@ export async function handleForwardNode(destPeer: Peer, messageNodes: OB11Messag
|
||||
}
|
||||
for (const msgNode of MsgNodeList) {
|
||||
const result = await msgNode;
|
||||
nodeMsgIds.push(result.msgId);
|
||||
if (result) {
|
||||
nodeMsgIds.push(result.msgId);
|
||||
}
|
||||
//logDebug('转发节点生成成功', result.msgId);
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -107,13 +109,14 @@ export async function handleForwardNode(destPeer: Peer, messageNodes: OB11Messag
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 检查srcPeer是否一致,不一致则需要克隆成自己的消息, 让所有srcPeer都变成自己的,使其保持一致才能够转发
|
||||
const nodeMsgArray: Array<RawMessage> = [];
|
||||
|
||||
let srcPeer: Peer | undefined = undefined;
|
||||
let needSendSelf = false;
|
||||
for (const msgId of nodeMsgIds) {
|
||||
const nodeMsg = await dbUtil.getMsgByLongId(msgId);
|
||||
const nodeMsgPeer = await MessageUnique.getPeerByMsgId(msgId);
|
||||
const nodeMsg = (await NTQQMsgApi.getMsgsByMsgId(nodeMsgPeer?.Peer!, [msgId])).msgList[0];
|
||||
if (nodeMsg) {
|
||||
nodeMsgArray.push(nodeMsg);
|
||||
if (!srcPeer) {
|
||||
@@ -124,7 +127,7 @@ export async function handleForwardNode(destPeer: Peer, messageNodes: OB11Messag
|
||||
}
|
||||
}
|
||||
}
|
||||
//logDebug('nodeMsgArray', nodeMsgArray);
|
||||
// logDebug('nodeMsgArray', nodeMsgArray);
|
||||
nodeMsgIds = nodeMsgArray.map(msg => msg.msgId);
|
||||
if (needSendSelf) {
|
||||
//logDebug('需要克隆转发消息');
|
||||
|
@@ -8,13 +8,13 @@ import {
|
||||
} from '@/onebot11/types';
|
||||
import { ActionName, BaseCheckResult } from '@/onebot11/action/types';
|
||||
import { getGroup } from '@/core/data';
|
||||
import { dbUtil } from '@/common/utils/db';
|
||||
import { ChatType, ElementType, Group, NTQQFileApi, NTQQFriendApi, NTQQMsgApi, NTQQUserApi, Peer, SendMessageElement, } from '@/core';
|
||||
import fs from 'node:fs';
|
||||
import { logDebug, logError } from '@/common/utils/log';
|
||||
import { decodeCQCode } from '@/onebot11/cqcode';
|
||||
import createSendElements from './create-send-elements';
|
||||
import { handleForwardNode } from '@/onebot11/action/msg/SendMsg/handle-forward-node';
|
||||
import { MessageUnique } from '@/common/utils/MessageUnique';
|
||||
|
||||
export interface ReturnDataType {
|
||||
message_id: number;
|
||||
@@ -40,7 +40,7 @@ export async function sendMsg(peer: Peer, sendElements: SendMessageElement[], de
|
||||
throw ('消息体无法解析, 请检查是否发送了不支持的消息类型');
|
||||
}
|
||||
let totalSize = 0;
|
||||
let timeout = 5000;
|
||||
let timeout = 10000;
|
||||
try {
|
||||
for (const fileElement of sendElements) {
|
||||
if (fileElement.elementType === ElementType.PTT) {
|
||||
@@ -59,17 +59,17 @@ export async function sendMsg(peer: Peer, sendElements: SendMessageElement[], de
|
||||
//且 PredictTime ((totalSize / 1024 / 512) * 1000)不等于Nan
|
||||
const PredictTime = totalSize / 1024 / 256 * 1000;
|
||||
if (!Number.isNaN(PredictTime)) {
|
||||
timeout += PredictTime;// 5S Basic Timeout + PredictTime( For File 512kb/s )
|
||||
timeout += PredictTime;// 10S Basic Timeout + PredictTime( For File 512kb/s )
|
||||
}
|
||||
} catch (e) {
|
||||
logError('发送消息计算预计时间异常', e);
|
||||
}
|
||||
const returnMsg = await NTQQMsgApi.sendMsg(peer, sendElements, waitComplete, timeout);
|
||||
try {
|
||||
returnMsg.id = await dbUtil.addMsg(returnMsg, false);
|
||||
returnMsg!.id = await MessageUnique.createMsg({ chatType: peer.chatType, guildId: '', peerUid: peer.peerUid }, returnMsg!.msgId);
|
||||
} catch (e: any) {
|
||||
logDebug('发送消息id获取失败', e);
|
||||
returnMsg.id = 0;
|
||||
returnMsg!.id = 0;
|
||||
}
|
||||
|
||||
deleteAfterSentFiles.map((f) => {
|
||||
@@ -155,8 +155,8 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
|
||||
if (getSpecialMsgNum(payload, OB11MessageDataType.node)) {
|
||||
const returnMsg = await handleForwardNode(peer, messages as OB11MessageNode[], group);
|
||||
if (returnMsg) {
|
||||
const msgShortId = await dbUtil.addMsg(returnMsg!, false);
|
||||
return { message_id: msgShortId };
|
||||
const msgShortId = await MessageUnique.createMsg({ guildId: '', peerUid: peer.peerUid, chatType: peer.chatType }, returnMsg!.msgId);
|
||||
return { message_id: msgShortId! };
|
||||
} else {
|
||||
throw Error('发送转发消息失败');
|
||||
}
|
||||
@@ -172,7 +172,7 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
|
||||
const { sendElements, deleteAfterSentFiles } = await createSendElements(messages, group);
|
||||
//console.log(peer, JSON.stringify(sendElements,null,2));
|
||||
const returnMsg = await sendMsg(peer, sendElements, deleteAfterSentFiles);
|
||||
return { message_id: returnMsg.id! };
|
||||
return { message_id: returnMsg!.id! };
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,14 +1,14 @@
|
||||
import { ActionName } from '../types';
|
||||
import BaseAction from '../BaseAction';
|
||||
import { dbUtil } from '@/common/utils/db';
|
||||
import { NTQQMsgApi } from '@/core/apis';
|
||||
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
|
||||
import { MessageUnique } from '@/common/utils/MessageUnique';
|
||||
|
||||
const SchemaData = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
message_id: { type: ['string','number'] },
|
||||
emoji_id: { type: ['string','number'] }
|
||||
message_id: { type: ['string', 'number'] },
|
||||
emoji_id: { type: ['string', 'number'] }
|
||||
},
|
||||
required: ['message_id', 'emoji_id']
|
||||
} as const satisfies JSONSchema;
|
||||
@@ -19,16 +19,17 @@ export class SetMsgEmojiLike extends BaseAction<Payload, any> {
|
||||
actionName = ActionName.SetMsgEmojiLike;
|
||||
PayloadSchema = SchemaData;
|
||||
protected async _handle(payload: Payload) {
|
||||
const msg = await dbUtil.getMsgByShortId(parseInt(payload.message_id.toString()));
|
||||
const msg = MessageUnique.getMsgIdAndPeerByShortId(parseInt(payload.message_id.toString()));
|
||||
if (!msg) {
|
||||
throw new Error('msg not found');
|
||||
}
|
||||
if (!payload.emoji_id){
|
||||
if (!payload.emoji_id) {
|
||||
throw new Error('emojiId not found');
|
||||
}
|
||||
return await NTQQMsgApi.setEmojiLike({
|
||||
chatType: msg.chatType,
|
||||
peerUid: msg.peerUid
|
||||
}, msg.msgSeq, payload.emoji_id.toString(), true);
|
||||
const msgData = (await NTQQMsgApi.getMsgsByMsgId(msg.Peer, [msg.MsgId])).msgList;
|
||||
if (!msgData || msgData.length == 0 || !msgData[0].msgSeq) {
|
||||
throw new Error('find msg by msgid error');
|
||||
}
|
||||
return await NTQQMsgApi.setEmojiLike(msg.Peer, msgData[0].msgSeq, payload.emoji_id.toString(), true);
|
||||
}
|
||||
}
|
||||
|
@@ -7,7 +7,6 @@ import {
|
||||
ChatCacheListItemBasic,
|
||||
CacheFileType
|
||||
} from '@/core/entities';
|
||||
import { dbUtil } from '@/common/utils/db';
|
||||
import { NTQQFileApi, NTQQFileCacheApi } from '@/core/apis/file';
|
||||
import { logError } from '@/common/utils/log';
|
||||
|
||||
|
@@ -93,5 +93,14 @@ export enum ActionName {
|
||||
SetSelfProfile = 'set_self_profile',
|
||||
CreateCollection = 'create_collection',
|
||||
GetCollectionList = 'get_collection_list',
|
||||
SetLongNick = 'set_self_longnick'
|
||||
SetLongNick = 'set_self_longnick',
|
||||
SetEssenceMsg = 'set_essence_msg',
|
||||
DelEssenceMsg = 'delete_essence_msg',
|
||||
GetRecentContact = 'get_recent_contact',
|
||||
_MarkAllMsgAsRead = '_mark_all_as_read',
|
||||
GetProfileLike = 'get_profile_like',
|
||||
SetGroupHeader = 'set_group_head',
|
||||
FetchCustomFace = 'fetch_custom_face',
|
||||
GOCQHTTP_UploadPrivateFile = 'upload_private_file',
|
||||
TestApi01 = 'test_api_01'
|
||||
}
|
||||
|
12
src/onebot11/action/user/GetRecentContact.ts
Normal file
12
src/onebot11/action/user/GetRecentContact.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
|
||||
import BaseAction from '../BaseAction';
|
||||
import { ActionName } from '../types';
|
||||
import { NTQQUserApi } from '@/core';
|
||||
|
||||
export default class GetRecentContact extends BaseAction<void, any> {
|
||||
actionName = ActionName.GetRecentContact;
|
||||
protected async _handle(payload: void) {
|
||||
//没有效果
|
||||
return await NTQQUserApi.getRecentContactListSnapShot(10);
|
||||
}
|
||||
}
|
@@ -42,6 +42,7 @@ export interface OB11Config {
|
||||
}
|
||||
|
||||
class Config extends ConfigBase<OB11Config> implements OB11Config {
|
||||
name: string = 'onebot11';
|
||||
http = {
|
||||
enable: false,
|
||||
host: '',
|
||||
@@ -72,10 +73,6 @@ class Config extends ConfigBase<OB11Config> implements OB11Config {
|
||||
RecordList: [] as Array<string>
|
||||
};
|
||||
|
||||
getConfigPath() {
|
||||
return path.join(this.getConfigDir(), `onebot11_${selfInfo.uin}.json`);
|
||||
}
|
||||
|
||||
protected getKeys(): string[] | null {
|
||||
return null;
|
||||
}
|
||||
|
@@ -12,29 +12,30 @@ import {
|
||||
import {
|
||||
AtType,
|
||||
ChatType,
|
||||
ElementType, FaceIndex,
|
||||
FaceIndex,
|
||||
Friend,
|
||||
GrayTipElementSubType,
|
||||
Group,
|
||||
GroupMember,
|
||||
IMAGE_HTTP_HOST, IMAGE_HTTP_HOST_NT, mFaceCache,
|
||||
mFaceCache,
|
||||
Peer,
|
||||
RawMessage,
|
||||
SelfInfo,
|
||||
Sex,
|
||||
TipGroupElementType,
|
||||
User
|
||||
User,
|
||||
VideoElement
|
||||
} from '@/core/entities';
|
||||
import { EventType } from './event/OB11BaseEvent';
|
||||
import { encodeCQCode } from './cqcode';
|
||||
import { dbUtil } from '@/common/utils/db';
|
||||
import { OB11GroupIncreaseEvent } from './event/notice/OB11GroupIncreaseEvent';
|
||||
import { OB11GroupBanEvent } from './event/notice/OB11GroupBanEvent';
|
||||
import { OB11GroupUploadNoticeEvent } from './event/notice/OB11GroupUploadNoticeEvent';
|
||||
import { OB11GroupNoticeEvent } from './event/notice/OB11GroupNoticeEvent';
|
||||
|
||||
import { calcQQLevel } from '../common/utils/qqlevel';
|
||||
import { log, logDebug, logError } from '../common/utils/log';
|
||||
import { sleep } from '../common/utils/helper';
|
||||
import { log, logDebug, logError, logWarn } from '../common/utils/log';
|
||||
import { sleep, UUIDConverter } from '../common/utils/helper';
|
||||
import { OB11GroupTitleEvent } from './event/notice/OB11GroupTitleEvent';
|
||||
import { OB11GroupCardEvent } from './event/notice/OB11GroupCardEvent';
|
||||
import { OB11GroupDecreaseEvent } from './event/notice/OB11GroupDecreaseEvent';
|
||||
@@ -42,9 +43,11 @@ import { ob11Config } from '@/onebot11/config';
|
||||
import { deleteGroup, getGroupMember, groupMembers, selfInfo, tempGroupCodeMap } from '@/core/data';
|
||||
import { NTQQFileApi, NTQQGroupApi, NTQQMsgApi, NTQQUserApi } from '@/core/apis';
|
||||
import { OB11GroupMsgEmojiLikeEvent } from '@/onebot11/event/notice/OB11MsgEmojiLikeEvent';
|
||||
import { napCatCore } from '@/core';
|
||||
import { OB11FriendPokeEvent, OB11GroupPokeEvent } from './event/notice/OB11PokeEvent';
|
||||
import { OB11FriendAddNoticeEvent } from './event/notice/OB11FriendAddNoticeEvent';
|
||||
import { OB11BaseNoticeEvent } from './event/notice/OB11BaseNoticeEvent';
|
||||
import { OB11GroupEssenceEvent } from './event/notice/OB11GroupEssenceEvent';
|
||||
import { MessageUnique } from '@/common/utils/MessageUnique';
|
||||
|
||||
|
||||
export class OB11Constructor {
|
||||
@@ -74,7 +77,12 @@ export class OB11Constructor {
|
||||
if (msg.chatType == ChatType.group) {
|
||||
resMsg.sub_type = 'normal'; // 这里go-cqhttp是group,而onebot11标准是normal, 蛋疼
|
||||
resMsg.group_id = parseInt(msg.peerUin);
|
||||
const member = await getGroupMember(msg.peerUin, msg.senderUin!);
|
||||
let member = await getGroupMember(msg.peerUin, msg.senderUin!);
|
||||
if (!member) {
|
||||
//直接去QQNative取
|
||||
const memberList = await NTQQGroupApi.getGroupMembers(msg.peerUin);
|
||||
member = memberList.get(msg.senderUin!);
|
||||
}
|
||||
if (member) {
|
||||
resMsg.sender.role = OB11Constructor.groupMemberRole(member.role);
|
||||
resMsg.sender.nickname = member.nick;
|
||||
@@ -93,33 +101,40 @@ export class OB11Constructor {
|
||||
}
|
||||
}
|
||||
for (const element of msg.elements) {
|
||||
const message_data: OB11MessageData | any = {
|
||||
data: {},
|
||||
type: 'unknown'
|
||||
let message_data: OB11MessageData = {
|
||||
data: {} as any,
|
||||
type: 'unknown' as any
|
||||
};
|
||||
if (element.textElement && element.textElement?.atType !== AtType.notAt) {
|
||||
message_data['type'] = OB11MessageDataType.at;
|
||||
let qq: `${number}` | 'all';
|
||||
let name: string | undefined;
|
||||
if (element.textElement.atType == AtType.atAll) {
|
||||
// message_data["data"]["mention"] = "all"
|
||||
message_data['data']['qq'] = 'all';
|
||||
qq = 'all';
|
||||
}
|
||||
else {
|
||||
const atUid = element.textElement.atNtUid;
|
||||
const { atNtUid, content } = element.textElement;
|
||||
let atQQ = element.textElement.atUid;
|
||||
if (!atQQ || atQQ === '0') {
|
||||
const atMember = await getGroupMember(msg.peerUin, atUid);
|
||||
const atMember = await getGroupMember(msg.peerUin, atNtUid);
|
||||
if (atMember) {
|
||||
atQQ = atMember.uin;
|
||||
}
|
||||
}
|
||||
if (atQQ) {
|
||||
// message_data["data"]["mention"] = atQQ
|
||||
message_data['data']['qq'] = atQQ;
|
||||
qq = atQQ as `${number}`;
|
||||
name = content.replace('@', '');
|
||||
}
|
||||
}
|
||||
message_data = {
|
||||
type: OB11MessageDataType.at,
|
||||
data: {
|
||||
qq: qq!,
|
||||
name
|
||||
}
|
||||
};
|
||||
}
|
||||
else if (element.textElement) {
|
||||
message_data['type'] = 'text';
|
||||
message_data['type'] = OB11MessageDataType.text;
|
||||
|
||||
let text = element.textElement.content;
|
||||
if (!text.trim()) {
|
||||
@@ -132,106 +147,142 @@ export class OB11Constructor {
|
||||
message_data['data']['text'] = text;
|
||||
}
|
||||
else if (element.replyElement) {
|
||||
message_data['type'] = 'reply';
|
||||
// log("收到回复消息", element.replyElement.replayMsgSeq)
|
||||
message_data['type'] = OB11MessageDataType.reply;
|
||||
//log("收到回复消息", element.replyElement);
|
||||
try {
|
||||
// let retData = await NTQQMsgApi.getMsgsBySeqAndCount(
|
||||
// {
|
||||
// chatType: msg.chatType,
|
||||
// peerUid: msg.peerUid,
|
||||
// guildId: '',
|
||||
// },
|
||||
// element.replyElement.replayMsgSeq,
|
||||
// 1,
|
||||
// false,
|
||||
// true
|
||||
// );
|
||||
// console.log(JSON.stringify(retData, null, 2));
|
||||
const replyMsg = await dbUtil.getMsgBySeq(msg.peerUid, element.replyElement.replayMsgSeq);
|
||||
// log("找到回复消息", replyMsg.msgShortId, replyMsg.msgId)
|
||||
if (replyMsg && replyMsg.id) {
|
||||
message_data['data']['id'] = replyMsg.id!.toString();
|
||||
//做这么多都是因为NC速度太快 可能nt还没有写入数据库
|
||||
//const records = msg.records.find(msgRecord => msgRecord.msgId === element.replyElement.sourceMsgIdInRecords);
|
||||
const peer = {
|
||||
chatType: msg.chatType,
|
||||
peerUid: msg.peerUid,
|
||||
guildId: '',
|
||||
};
|
||||
let replyMsg: RawMessage | undefined;
|
||||
|
||||
replyMsg = (await NTQQMsgApi.getMsgsBySeqAndCount({ peerUid: msg.peerUid, guildId: '', chatType: msg.chatType }, element.replyElement.replayMsgSeq, 1, true, true)).msgList[0];
|
||||
if (!replyMsg || element.replyElement.replayMsgSeq !== replyMsg.msgSeq) {
|
||||
replyMsg = (await NTQQMsgApi.getSingleMsg(peer, element.replyElement.replayMsgSeq)).msgList[0];
|
||||
}
|
||||
else {
|
||||
continue;
|
||||
|
||||
if (!replyMsg || element.replyElement.replayMsgSeq !== replyMsg.msgSeq) {
|
||||
throw new Error('回复消息消息验证失败');
|
||||
}
|
||||
message_data['data']['id'] = MessageUnique.createMsg({ peerUid: msg.peerUid, guildId: '', chatType: msg.chatType }, replyMsg.msgId)?.toString();
|
||||
//log("找到回复消息", message_data['data']['id'], replyMsg.msgList[0].msgId)
|
||||
} catch (e: any) {
|
||||
message_data['type'] = 'unknown' as any;
|
||||
message_data['data'] = undefined;
|
||||
logError('获取不到引用的消息', e.stack, element.replyElement.replayMsgSeq);
|
||||
}
|
||||
|
||||
}
|
||||
else if (element.picElement) {
|
||||
message_data['type'] = 'image';
|
||||
message_data['type'] = OB11MessageDataType.image;
|
||||
// message_data["data"]["file"] = element.picElement.sourcePath
|
||||
message_data['data']['file'] = element.picElement.fileName;
|
||||
message_data['subType'] = element.picElement.picSubType;
|
||||
message_data['data']['subType'] = element.picElement.picSubType;
|
||||
message_data['data']['file_id'] = UUIDConverter.encode(msg.peerUin, msg.msgId);
|
||||
// message_data["data"]["path"] = element.picElement.sourcePath
|
||||
// let currentRKey = "CAQSKAB6JWENi5LMk0kc62l8Pm3Jn1dsLZHyRLAnNmHGoZ3y_gDZPqZt-64"
|
||||
|
||||
try {
|
||||
message_data['data']['url'] = await NTQQFileApi.getImageUrl(element.picElement, msg.chatType !== ChatType.group);
|
||||
message_data['data']['url'] = await NTQQFileApi.getImageUrl(element.picElement);
|
||||
} catch (e: any) {
|
||||
logError('获取图片url失败', e.stack);
|
||||
}
|
||||
//console.log(message_data['data']['url'])
|
||||
// message_data["data"]["file_id"] = element.picElement.fileUuid
|
||||
message_data['data']['file_size'] = element.picElement.fileSize;
|
||||
dbUtil.addFileCache({
|
||||
name: element.picElement.fileName,
|
||||
path: element.picElement.sourcePath,
|
||||
size: element.picElement.fileSize,
|
||||
url: message_data['data']['url'],
|
||||
uuid: element.picElement.fileUuid || '',
|
||||
msgId: msg.msgId,
|
||||
element: element.picElement,
|
||||
elementType: ElementType.PIC,
|
||||
elementId: element.elementId
|
||||
}).then();
|
||||
// 不自动下载图片
|
||||
|
||||
}
|
||||
else if (element.videoElement || element.fileElement) {
|
||||
const videoOrFileElement = element.videoElement || element.fileElement;
|
||||
const ob11MessageDataType = element.videoElement ? OB11MessageDataType.video : OB11MessageDataType.file;
|
||||
const videoDownUrl = element.videoElement ? await NTQQFileApi.getVideoUrl(msg, element) : videoOrFileElement.filePath;
|
||||
message_data['type'] = ob11MessageDataType;
|
||||
message_data['data']['file'] = videoOrFileElement.fileName;
|
||||
else if (element.fileElement) {
|
||||
const FileElement = element.fileElement;
|
||||
message_data['type'] = OB11MessageDataType.file;
|
||||
message_data['data']['file'] = FileElement.fileName;
|
||||
message_data['data']['path'] = FileElement.filePath;
|
||||
message_data['data']['url'] = FileElement.filePath;
|
||||
message_data['data']['file_id'] = UUIDConverter.encode(msg.peerUin, msg.msgId);
|
||||
message_data['data']['file_size'] = FileElement.fileSize;
|
||||
await NTQQFileApi.addFileCache({
|
||||
peerUid: msg.peerUid,
|
||||
chatType: msg.chatType,
|
||||
guildId: '',
|
||||
},
|
||||
msg.msgId,
|
||||
msg.msgSeq,
|
||||
msg.senderUid,
|
||||
element.elementId,
|
||||
element.elementType.toString(),
|
||||
FileElement.fileSize,
|
||||
FileElement.fileName
|
||||
);
|
||||
}
|
||||
else if (element.videoElement) {
|
||||
const videoElement: VideoElement = element.videoElement;
|
||||
//读取视频链接并兜底
|
||||
let videoUrl;//Array
|
||||
try {
|
||||
videoUrl = await NTQQFileApi.getVideoUrl({
|
||||
chatType: msg.chatType,
|
||||
peerUid: msg.peerUid,
|
||||
guildId: '0'
|
||||
}, msg.msgId, element.elementId);
|
||||
} catch (error) {
|
||||
videoUrl = undefined;
|
||||
}
|
||||
//读取在线URL
|
||||
let videoDownUrl = undefined;
|
||||
|
||||
if (videoUrl) {
|
||||
const videoDownUrlTemp = videoUrl.find((url) => { if (url.url) { return true; } return false; });
|
||||
if (videoDownUrlTemp) {
|
||||
videoDownUrl = videoDownUrlTemp.url;
|
||||
}
|
||||
}
|
||||
//开始兜底
|
||||
if (!videoDownUrl) {
|
||||
videoDownUrl = videoElement.filePath;
|
||||
}
|
||||
message_data['type'] = OB11MessageDataType.video;
|
||||
message_data['data']['file'] = videoElement.fileName;
|
||||
message_data['data']['path'] = videoDownUrl;
|
||||
message_data['data']['url'] = videoDownUrl;
|
||||
message_data['data']['file_id'] = videoOrFileElement.fileUuid;
|
||||
message_data['data']['file_size'] = videoOrFileElement.fileSize;
|
||||
if (!element.videoElement) {
|
||||
dbUtil.addFileCache({
|
||||
msgId: msg.msgId,
|
||||
name: videoOrFileElement.fileName,
|
||||
path: videoOrFileElement.filePath,
|
||||
size: parseInt(videoOrFileElement.fileSize || '0'),
|
||||
uuid: videoOrFileElement.fileUuid || '',
|
||||
url: '',
|
||||
element: element.videoElement || element.fileElement,
|
||||
elementType: element.videoElement ? ElementType.VIDEO : ElementType.FILE,
|
||||
elementId: element.elementId
|
||||
}).then();
|
||||
}
|
||||
message_data['data']['file_id'] = UUIDConverter.encode(msg.peerUin, msg.msgId);
|
||||
message_data['data']['file_size'] = videoElement.fileSize;
|
||||
|
||||
await NTQQFileApi.addFileCache({
|
||||
peerUid: msg.peerUid,
|
||||
chatType: msg.chatType,
|
||||
guildId: '',
|
||||
},
|
||||
msg.msgId,
|
||||
msg.msgSeq,
|
||||
msg.senderUid,
|
||||
element.elementId,
|
||||
element.elementType.toString(),
|
||||
videoElement.fileSize || '0',
|
||||
videoElement.fileName
|
||||
);
|
||||
}
|
||||
else if (element.pttElement) {
|
||||
message_data['type'] = OB11MessageDataType.voice;
|
||||
message_data['data']['file'] = element.pttElement.fileName;
|
||||
message_data['data']['path'] = element.pttElement.filePath;
|
||||
// message_data["data"]["file_id"] = element.pttElement.fileUuid
|
||||
//message_data['data']['file_id'] = element.pttElement.fileUuid;
|
||||
message_data['data']['file_id'] = UUIDConverter.encode(msg.peerUin, msg.msgId);
|
||||
message_data['data']['file_size'] = element.pttElement.fileSize;
|
||||
dbUtil.addFileCache({
|
||||
name: element.pttElement.fileName,
|
||||
path: element.pttElement.filePath,
|
||||
size: parseInt(element.pttElement.fileSize) || 0,
|
||||
url: '',
|
||||
uuid: element.pttElement.fileUuid || '',
|
||||
msgId: msg.msgId,
|
||||
element: element.pttElement,
|
||||
elementType: ElementType.PTT,
|
||||
elementId: element.elementId
|
||||
}).then();
|
||||
|
||||
await NTQQFileApi.addFileCache({
|
||||
peerUid: msg.peerUid,
|
||||
chatType: msg.chatType,
|
||||
guildId: '',
|
||||
},
|
||||
msg.msgId,
|
||||
msg.msgSeq,
|
||||
msg.senderUid,
|
||||
element.elementId,
|
||||
element.elementType.toString(),
|
||||
element.pttElement.fileSize || '0',
|
||||
element.pttElement.fileUuid || ''
|
||||
);
|
||||
//以uuid作为文件名
|
||||
}
|
||||
else if (element.arkElement) {
|
||||
message_data['type'] = OB11MessageDataType.json;
|
||||
@@ -275,14 +326,16 @@ export class OB11Constructor {
|
||||
message_data['type'] = OB11MessageDataType.forward;
|
||||
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);
|
||||
|
||||
if (messagePostFormat === 'string') {
|
||||
(resMsg.message as string) += cqCode;
|
||||
}
|
||||
else (resMsg.message as OB11MessageData[]).push(message_data);
|
||||
resMsg.raw_message += cqCode;
|
||||
}
|
||||
|
||||
}
|
||||
resMsg.raw_message = resMsg.raw_message.trim();
|
||||
return resMsg;
|
||||
@@ -295,6 +348,7 @@ export class OB11Constructor {
|
||||
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事件
|
||||
@@ -303,19 +357,25 @@ export class OB11Constructor {
|
||||
pokedetail = pokedetail.filter(item => item.uid);
|
||||
//console.log("[NapCat] 群拍一拍 群:", pokedetail, parseInt(msg.peerUid), " ", await NTQQUserApi.getUinByUid(pokedetail[0].uid), "拍了拍", await NTQQUserApi.getUinByUid(pokedetail[1].uid));
|
||||
if (pokedetail.length == 2) {
|
||||
return new OB11FriendPokeEvent(parseInt((await NTQQUserApi.getUinByUid(pokedetail[0].uid))!), parseInt((await NTQQUserApi.getUinByUid(pokedetail[1].uid))!));
|
||||
return new OB11FriendPokeEvent(parseInt((await NTQQUserApi.getUinByUid(pokedetail[0].uid))!), parseInt((await NTQQUserApi.getUinByUid(pokedetail[1].uid))!), pokedetail);
|
||||
}
|
||||
}
|
||||
//下面得改 上面也是错的grayTipElement.subElementType == GrayTipElementSubType.MEMBER_NEW_TITLE
|
||||
}
|
||||
if (element.grayTipElement.subElementType == GrayTipElementSubType.INVITE_NEW_MEMBER) {
|
||||
//好友添加成功事件
|
||||
if (element.grayTipElement.xmlElement.templId === '10229' && msg.peerUin !== '') {
|
||||
return new OB11FriendAddNoticeEvent(parseInt(msg.peerUin));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
static async GroupEvent(msg: RawMessage): Promise<OB11GroupNoticeEvent | undefined> {
|
||||
//log("group msg", msg);
|
||||
if (msg.chatType !== ChatType.group) {
|
||||
return;
|
||||
}
|
||||
//log("group msg", msg);
|
||||
if (msg.senderUin && msg.senderUin !== '0') {
|
||||
const member = await getGroupMember(msg.peerUid, msg.senderUin);
|
||||
if (member && member.cardName !== msg.sendMemberName) {
|
||||
@@ -325,6 +385,7 @@ export class OB11Constructor {
|
||||
return event;
|
||||
}
|
||||
}
|
||||
|
||||
for (const element of msg.elements) {
|
||||
const grayTipElement = element.grayTipElement;
|
||||
const groupElement = grayTipElement?.groupElement;
|
||||
@@ -396,14 +457,6 @@ export class OB11Constructor {
|
||||
}
|
||||
if (grayTipElement) {
|
||||
if (grayTipElement.xmlElement?.templId === '10382') {
|
||||
// 表情回应消息
|
||||
// "content":
|
||||
// "<gtip align=\"center\">
|
||||
// <qq uin=\"u_snYxnEfja-Po_\" col=\"3\" jp=\"3794\"/>
|
||||
// <nor txt=\"回应了你的\"/>
|
||||
// <url jp= \"\" msgseq=\"74711\" col=\"3\" txt=\"消息:\"/>
|
||||
// <face type=\"1\" id=\"76\"> </face>
|
||||
// </gtip>",
|
||||
const emojiLikeData = new fastXmlParser.XMLParser({
|
||||
ignoreAttributes: false,
|
||||
attributeNamePrefix: ''
|
||||
@@ -413,11 +466,12 @@ export class OB11Constructor {
|
||||
const senderUin = emojiLikeData.gtip.qq.jp;
|
||||
const msgSeq = emojiLikeData.gtip.url.msgseq;
|
||||
const emojiId = emojiLikeData.gtip.face.id;
|
||||
const replyMsg = await dbUtil.getMsgBySeq(msg.peerUid, msgSeq);
|
||||
if (!replyMsg) {
|
||||
const replyMsgList = (await NTQQMsgApi.getMsgsBySeqAndCount({ chatType: ChatType.group, guildId: '', peerUid: msg.peerUid }, msgSeq, 1, true, true)).msgList;
|
||||
if (replyMsgList.length < 1) {
|
||||
return;
|
||||
}
|
||||
return new OB11GroupMsgEmojiLikeEvent(parseInt(msg.peerUid), parseInt(senderUin), replyMsg.id!, [{
|
||||
const replyMsg = replyMsgList[0];
|
||||
return new OB11GroupMsgEmojiLikeEvent(parseInt(msg.peerUid), parseInt(senderUin), MessageUnique.getShortIdByMsgId(replyMsg?.msgId!)!, [{
|
||||
emoji_id: emojiId,
|
||||
count: 1
|
||||
}]);
|
||||
@@ -447,46 +501,37 @@ export class OB11Constructor {
|
||||
//代码歧义 GrayTipElementSubType.MEMBER_NEW_TITLE
|
||||
else if (grayTipElement.subElementType == GrayTipElementSubType.MEMBER_NEW_TITLE) {
|
||||
const json = JSON.parse(grayTipElement.jsonGrayTipElement.jsonStr);
|
||||
/*
|
||||
{
|
||||
align: 'center',
|
||||
items: [
|
||||
{ txt: '恭喜', type: 'nor' },
|
||||
{
|
||||
col: '3',
|
||||
jp: '5',
|
||||
param: ["QQ号"],
|
||||
txt: '林雨辰',
|
||||
type: 'url'
|
||||
},
|
||||
{ txt: '获得群主授予的', type: 'nor' },
|
||||
{
|
||||
col: '3',
|
||||
jp: '',
|
||||
txt: '好好好',
|
||||
type: 'url'
|
||||
},
|
||||
{ txt: '头衔', type: 'nor' }
|
||||
]
|
||||
}
|
||||
|
||||
* */
|
||||
if (grayTipElement.jsonGrayTipElement.busiId == 1061) {
|
||||
//判断业务类型
|
||||
//Poke事件
|
||||
let pokedetail: any[] = json.items;
|
||||
const pokedetail: any[] = json.items;
|
||||
//筛选item带有uid的元素
|
||||
pokedetail = pokedetail.filter(item => item.uid);
|
||||
//console.log("[NapCat] 群拍一拍 群:", pokedetail, parseInt(msg.peerUid), " ", await NTQQUserApi.getUinByUid(pokedetail[0].uid), "拍了拍", await NTQQUserApi.getUinByUid(pokedetail[1].uid));
|
||||
if (pokedetail.length == 2) {
|
||||
return new OB11GroupPokeEvent(parseInt(msg.peerUid), parseInt((await NTQQUserApi.getUinByUid(pokedetail[0].uid))!), parseInt((await NTQQUserApi.getUinByUid(pokedetail[1].uid))!));
|
||||
const poke_uid = pokedetail.filter(item => item.uid);
|
||||
if (poke_uid.length == 2) {
|
||||
return new OB11GroupPokeEvent(parseInt(msg.peerUid), parseInt((await NTQQUserApi.getUinByUid(poke_uid[0].uid))!), parseInt((await NTQQUserApi.getUinByUid(poke_uid[1].uid))!), pokedetail);
|
||||
}
|
||||
}
|
||||
//下面得改 上面也是错的grayTipElement.subElementType == GrayTipElementSubType.MEMBER_NEW_TITLE
|
||||
const memberUin = json.items[1].param[0];
|
||||
const title = json.items[3].txt;
|
||||
logDebug('收到群成员新头衔消息', json);
|
||||
return new OB11GroupTitleEvent(parseInt(msg.peerUid), parseInt(memberUin), title);
|
||||
if (grayTipElement.jsonGrayTipElement.busiId == 2401) {
|
||||
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!
|
||||
};
|
||||
const msgData = await NTQQMsgApi.getMsgsBySeqAndCount(Peer, msgSeq.toString(), 1, true, true);
|
||||
return new OB11GroupEssenceEvent(parseInt(msg.peerUid), MessageUnique.getShortIdByMsgId(msgData.msgList[0].msgId)!, parseInt(msgData.msgList[0].senderUin));
|
||||
// 获取MsgSeq+Peer可获取具体消息
|
||||
}
|
||||
if (grayTipElement.jsonGrayTipElement.busiId == 2407) {
|
||||
//下面得改 上面也是错的grayTipElement.subElementType == GrayTipElementSubType.MEMBER_NEW_TITLE
|
||||
const memberUin = json.items[1].param[0];
|
||||
const title = json.items[3].txt;
|
||||
logDebug('收到群成员新头衔消息', json);
|
||||
return new OB11GroupTitleEvent(parseInt(msg.peerUid), parseInt(memberUin), title);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -67,7 +67,15 @@ export function encodeCQCode(data: OB11MessageData) {
|
||||
let result = '[CQ:' + data.type;
|
||||
for (const name in data.data) {
|
||||
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 += ']';
|
||||
return result;
|
||||
|
11
src/onebot11/event/notice/OB11FriendAddNoticeEvent.ts
Normal file
11
src/onebot11/event/notice/OB11FriendAddNoticeEvent.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { OB11BaseNoticeEvent } from './OB11BaseNoticeEvent';
|
||||
|
||||
export class OB11FriendAddNoticeEvent extends OB11BaseNoticeEvent {
|
||||
notice_type = 'friend_add';
|
||||
user_id: number;
|
||||
|
||||
public constructor(user_Id: number) {
|
||||
super();
|
||||
this.user_id = user_Id;
|
||||
}
|
||||
}
|
14
src/onebot11/event/notice/OB11GroupEssenceEvent.ts
Normal file
14
src/onebot11/event/notice/OB11GroupEssenceEvent.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { OB11GroupNoticeEvent } from './OB11GroupNoticeEvent';
|
||||
export class OB11GroupEssenceEvent extends OB11GroupNoticeEvent {
|
||||
notice_type = 'essence';
|
||||
message_id: number;
|
||||
sender_id: number;
|
||||
sub_type: 'add' | 'delete' = 'add';
|
||||
|
||||
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,6 +1,4 @@
|
||||
import { OB11GroupNoticeEvent } from './OB11GroupNoticeEvent';
|
||||
import { dbUtil } from '@/common/utils/db';
|
||||
import { ob11Config } from '@/onebot11/config';
|
||||
|
||||
type GroupIncreaseSubType = 'approve' | 'invite';
|
||||
export class OB11GroupIncreaseEvent extends OB11GroupNoticeEvent {
|
||||
@@ -13,8 +11,5 @@ export class OB11GroupIncreaseEvent extends OB11GroupNoticeEvent {
|
||||
this.operator_id = operatorId;
|
||||
this.user_id = userId;
|
||||
this.sub_type = subType;
|
||||
|
||||
if(ob11Config.GroupLocalTime.Record && (ob11Config.GroupLocalTime.RecordList[0] == '-1' || ob11Config.GroupLocalTime.RecordList.includes(groupId.toString())))
|
||||
dbUtil.insertJoinTime(groupId, userId, Math.floor(Date.now() / 1000));
|
||||
}
|
||||
}
|
||||
|
@@ -10,20 +10,25 @@ class OB11PokeEvent extends OB11BaseNoticeEvent {
|
||||
}
|
||||
|
||||
export class OB11FriendPokeEvent extends OB11PokeEvent {
|
||||
constructor(user_id: number, target_id: number) {
|
||||
raw_info: any;
|
||||
//raw_message nb等框架标准为string
|
||||
constructor(user_id: number, target_id: number, raw_message: any) {
|
||||
super();
|
||||
this.target_id = target_id;
|
||||
this.user_id = user_id;
|
||||
this.raw_info = raw_message;
|
||||
}
|
||||
}
|
||||
|
||||
export class OB11GroupPokeEvent extends OB11PokeEvent {
|
||||
group_id: number;
|
||||
|
||||
constructor(group_id: number, user_id: number = 0, target_id: number = 0,) {
|
||||
raw_info: any;
|
||||
//raw_message nb等框架标准为string
|
||||
constructor(group_id: number, user_id: number = 0, target_id: number = 0, raw_message: any) {
|
||||
super();
|
||||
this.group_id = group_id;
|
||||
this.target_id = target_id;
|
||||
this.user_id = user_id;
|
||||
this.raw_info = raw_message;
|
||||
}
|
||||
}
|
||||
|
@@ -63,6 +63,9 @@ export async function logMessage(ob11Message: OB11Message) {
|
||||
else if (segment.type === 'markdown') {
|
||||
msgParts.push(spSegColor(`[markdown|${segment.data.content}]`));
|
||||
}
|
||||
else if (segment.type === 'video') {
|
||||
msgParts.push(spSegColor(`[视频|${segment.data.url}]`));
|
||||
}
|
||||
else {
|
||||
msgParts.push(spSegColor(`[未实现|${JSON.stringify(segment)}]`));
|
||||
}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { napCatCore } from '@/core';
|
||||
import { MsgListener, TempOnRecvParams } from '@/core/listeners';
|
||||
import { DebugGroupListener, MsgListener, TempOnRecvParams } from '@/core/listeners';
|
||||
import { OB11Constructor } from '@/onebot11/constructor';
|
||||
import { postOB11Event } from '@/onebot11/server/postOB11Event';
|
||||
import {
|
||||
@@ -19,11 +19,10 @@ import { OB11Config, ob11Config } from '@/onebot11/config';
|
||||
import { httpHeart, ob11HTTPServer } from '@/onebot11/server/http';
|
||||
import { ob11WebsocketServer } from '@/onebot11/server/ws/WebsocketServer';
|
||||
import { ob11ReverseWebsockets } from '@/onebot11/server/ws/ReverseWebsocket';
|
||||
import { getGroup, getGroupMember, selfInfo, tempGroupCodeMap } from '@/core/data';
|
||||
import { dbUtil } from '@/common/utils/db';
|
||||
import { getGroup, getGroupMember, groupMembers, selfInfo, tempGroupCodeMap } from '@/core/data';
|
||||
import { BuddyListener, GroupListener, NodeIKernelBuddyListener } from '@/core/listeners';
|
||||
import { OB11FriendRequestEvent } from '@/onebot11/event/request/OB11FriendRequest';
|
||||
import { NTQQGroupApi, NTQQUserApi, SignMusicWrapper } from '@/core/apis';
|
||||
import { NTQQGroupApi, NTQQUserApi } from '@/core/apis';
|
||||
import { log, logDebug, logError, setLogSelfInfo } from '@/common/utils/log';
|
||||
import { OB11GroupRequestEvent } from '@/onebot11/event/request/OB11GroupRequest';
|
||||
import { OB11GroupAdminNoticeEvent } from '@/onebot11/event/notice/OB11GroupAdminNoticeEvent';
|
||||
@@ -32,11 +31,8 @@ import { OB11FriendRecallNoticeEvent } from '@/onebot11/event/notice/OB11FriendR
|
||||
import { OB11GroupRecallNoticeEvent } from '@/onebot11/event/notice/OB11GroupRecallNoticeEvent';
|
||||
import { logMessage, logNotice, logRequest } from '@/onebot11/log';
|
||||
import { OB11Message } from '@/onebot11/types';
|
||||
import { OB11LifeCycleEvent } from './event/meta/OB11LifeCycleEvent';
|
||||
import { Data as SysData } from '@/proto/SysMessage';
|
||||
import { Data as DeviceData } from '@/proto/SysMessage.DeviceChange';
|
||||
import { OB11FriendPokeEvent, OB11GroupPokeEvent } from './event/notice/OB11PokeEvent';
|
||||
import { isEqual } from '@/common/utils/helper';
|
||||
import { MessageUnique } from '@/common/utils/MessageUnique';
|
||||
|
||||
//下面几个其实应该移进Core-Data 缓存实现 但是现在在这里方便
|
||||
//
|
||||
@@ -45,11 +41,15 @@ export interface LineDevice {
|
||||
device_name: string;
|
||||
device_kind: string;
|
||||
}
|
||||
export let DeviceList = new Array<LineDevice>();
|
||||
export const DeviceList = new Array<LineDevice>();
|
||||
|
||||
//peer->cached(boolen)
|
||||
// const PokeCache = new Map<string, boolean>();
|
||||
|
||||
function check_http_ws_equal(conf: any) { // 放在NapCatOnebot11里能被onReady调用却不能被SetConfig调用 不知道为什么 只能放这里了
|
||||
return isEqual(conf.http.port, conf.ws.port) && isEqual(conf.http.host, conf.ws.host);
|
||||
}
|
||||
|
||||
export class NapCatOnebot11 {
|
||||
private bootTime: number = Date.now() / 1000; // 秒
|
||||
|
||||
@@ -77,7 +77,11 @@ export class NapCatOnebot11 {
|
||||
ob11HTTPServer.start(ob11Config.http.port, ob11Config.http.host);
|
||||
}
|
||||
if (ob11Config.ws.enable) {
|
||||
ob11WebsocketServer.start(ob11Config.ws.port, ob11Config.ws.host);
|
||||
if (check_http_ws_equal(ob11Config) && ob11HTTPServer.server) { // ob11HTTPServer.server != null 隐含了 ob11Config.http.enable == true 的条件
|
||||
ob11WebsocketServer.start(ob11HTTPServer.server);
|
||||
} else {
|
||||
ob11WebsocketServer.start(ob11Config.ws.port, ob11Config.ws.host);
|
||||
}
|
||||
}
|
||||
if (ob11Config.reverseWs.enable) {
|
||||
ob11ReverseWebsockets.start();
|
||||
@@ -89,109 +93,109 @@ export class NapCatOnebot11 {
|
||||
// Create MsgListener
|
||||
const msgListener = new MsgListener();
|
||||
msgListener.onRecvSysMsg = async (protobufData: number[]) => {
|
||||
function buf2hex(buffer: Buffer) {
|
||||
return [...new Uint8Array(buffer)]
|
||||
.map(x => x.toString(16).padStart(2, '0'))
|
||||
.join('');
|
||||
}
|
||||
// let Data: Data = {
|
||||
// header: {
|
||||
// GroupNumber: 0,
|
||||
// GroupString: '',
|
||||
// QQ: 0,
|
||||
// Uid: '',
|
||||
// },
|
||||
// Body: {
|
||||
// MsgType: 0,
|
||||
// SubType_0: 0,
|
||||
// SubType_1: 0,
|
||||
// MsgSeq: 0,
|
||||
// Time: 0,
|
||||
// MsgID: 0,
|
||||
// Other: 0,
|
||||
// function buf2hex(buffer: Buffer) {
|
||||
// return [...new Uint8Array(buffer)]
|
||||
// .map(x => x.toString(16).padStart(2, '0'))
|
||||
// .join('');
|
||||
// }
|
||||
// // let Data: Data = {
|
||||
// // header: {
|
||||
// // GroupNumber: 0,
|
||||
// // GroupString: '',
|
||||
// // QQ: 0,
|
||||
// // Uid: '',
|
||||
// // },
|
||||
// // Body: {
|
||||
// // MsgType: 0,
|
||||
// // SubType_0: 0,
|
||||
// // SubType_1: 0,
|
||||
// // MsgSeq: 0,
|
||||
// // Time: 0,
|
||||
// // MsgID: 0,
|
||||
// // Other: 0,
|
||||
// // }
|
||||
// // };
|
||||
// try {
|
||||
// // 生产环境会自己去掉
|
||||
// const hex = buf2hex(Buffer.from(protobufData));
|
||||
// //console.log(hex);
|
||||
// const sysMsg = SysData.fromBinary(Buffer.from(protobufData));
|
||||
// const peeruin = sysMsg.header[0].peerNumber;
|
||||
// const peeruid = sysMsg.header[0].peerString;
|
||||
// const MsgType = sysMsg.body[0].msgType;
|
||||
// const subType0 = sysMsg.body[0].subType0;
|
||||
// const subType1 = sysMsg.body[0].subType1;
|
||||
// // let pokeEvent: OB11FriendPokeEvent | OB11GroupPokeEvent;
|
||||
// // //console.log(peeruid);
|
||||
// // if (MsgType == 528 && subType0 == 290 && hex.length < 250 && hex.endsWith('04')) {
|
||||
// // // 防止上报两次 私聊戳一戳
|
||||
// // if (PokeCache.has(peeruid)) {
|
||||
// // //log('[私聊] 用户 ', peeruin, ' 对你戳一戳');
|
||||
// // pokeEvent = new OB11FriendPokeEvent(parseInt(selfInfo.uin), peeruin);
|
||||
// // postOB11Event(pokeEvent);
|
||||
// // }
|
||||
// // PokeCache.set(peeruid, false);
|
||||
// // setTimeout(() => {
|
||||
// // PokeCache.delete(peeruid);
|
||||
// // }, 1000);
|
||||
// // }
|
||||
// // if (MsgType == 732 && subType0 == 20 && hex.length < 150 && hex.endsWith('04')) {
|
||||
// // // 防止上报两次 群聊戳一戳
|
||||
// // if (PokeCache.has(peeruid)) {
|
||||
// // log('[群聊] 群组 ', peeruin, ' 戳一戳');
|
||||
// // pokeEvent = new OB11GroupPokeEvent(peeruin);
|
||||
// // postOB11Event(pokeEvent);
|
||||
// // }
|
||||
// // PokeCache.set(peeruid, false);
|
||||
// // setTimeout(() => {
|
||||
// // PokeCache.delete(peeruid);
|
||||
// // }, 1000);
|
||||
// // }
|
||||
// if (MsgType == 528 && subType0 == 349) {
|
||||
// const sysDeviceMsg = DeviceData.fromBinary(Buffer.from(protobufData));
|
||||
// DeviceList = [];
|
||||
// sysDeviceMsg.event[0].content[0].devices.forEach(device => {
|
||||
// DeviceList.push({
|
||||
// app_id: '0',
|
||||
// device_name: device.deviceName,
|
||||
// device_kind: 'none'
|
||||
// });
|
||||
// // log('[设备列表] 设备名称: ' + device.deviceName);
|
||||
// });
|
||||
// }
|
||||
// };
|
||||
try {
|
||||
// 生产环境会自己去掉
|
||||
const hex = buf2hex(Buffer.from(protobufData));
|
||||
//console.log(hex);
|
||||
const sysMsg = SysData.fromBinary(Buffer.from(protobufData));
|
||||
const peeruin = sysMsg.header[0].peerNumber;
|
||||
const peeruid = sysMsg.header[0].peerString;
|
||||
const MsgType = sysMsg.body[0].msgType;
|
||||
const subType0 = sysMsg.body[0].subType0;
|
||||
const subType1 = sysMsg.body[0].subType1;
|
||||
// let pokeEvent: OB11FriendPokeEvent | OB11GroupPokeEvent;
|
||||
// //console.log(peeruid);
|
||||
// if (MsgType == 528 && subType0 == 290 && hex.length < 250 && hex.endsWith('04')) {
|
||||
// // 防止上报两次 私聊戳一戳
|
||||
// if (PokeCache.has(peeruid)) {
|
||||
// //log('[私聊] 用户 ', peeruin, ' 对你戳一戳');
|
||||
// pokeEvent = new OB11FriendPokeEvent(parseInt(selfInfo.uin), peeruin);
|
||||
// postOB11Event(pokeEvent);
|
||||
// }
|
||||
// PokeCache.set(peeruid, false);
|
||||
// setTimeout(() => {
|
||||
// PokeCache.delete(peeruid);
|
||||
// }, 1000);
|
||||
// }
|
||||
// if (MsgType == 732 && subType0 == 20 && hex.length < 150 && hex.endsWith('04')) {
|
||||
// // 防止上报两次 群聊戳一戳
|
||||
// if (PokeCache.has(peeruid)) {
|
||||
// log('[群聊] 群组 ', peeruin, ' 戳一戳');
|
||||
// pokeEvent = new OB11GroupPokeEvent(peeruin);
|
||||
// postOB11Event(pokeEvent);
|
||||
// }
|
||||
// PokeCache.set(peeruid, false);
|
||||
// setTimeout(() => {
|
||||
// PokeCache.delete(peeruid);
|
||||
// }, 1000);
|
||||
// }
|
||||
if (MsgType == 528 && subType0 == 349) {
|
||||
const sysDeviceMsg = DeviceData.fromBinary(Buffer.from(protobufData));
|
||||
DeviceList = [];
|
||||
sysDeviceMsg.event[0].content[0].devices.forEach(device => {
|
||||
DeviceList.push({
|
||||
app_id: '0',
|
||||
device_name: device.deviceName,
|
||||
device_kind: 'none'
|
||||
});
|
||||
// log('[设备列表] 设备名称: ' + device.deviceName);
|
||||
});
|
||||
}
|
||||
// 未区分增加与减少
|
||||
// if (MsgType == 34 && subType0 == 0) {
|
||||
// const role = (await getGroupMember(peeruin, selfInfo.uin))?.role;
|
||||
// const isPrivilege = role === 3 || role === 4;
|
||||
// if (!isPrivilege) {
|
||||
// const leaveUin =
|
||||
// log('[群聊] 群组 ', peeruin, ' 成员' + leaveUin + '退出');
|
||||
// const groupDecreaseEvent = new OB11GroupDecreaseEvent(peeruin, parseInt(leaveUin), 0, 'leave');// 不知道怎么出去的
|
||||
// postOB11Event(groupDecreaseEvent, true);
|
||||
// }
|
||||
// }
|
||||
// MsgType (SubType) EventName
|
||||
// 33 GroupMemIncreased
|
||||
// 34 GroupMemberDecreased
|
||||
// 44 GroupAdminChange
|
||||
// 82 GroupMessage
|
||||
// 84 GroupApply
|
||||
// 87 InviteGroup
|
||||
// 528 35 FriendApply
|
||||
// 528 39 CardChange
|
||||
// 528 68 GroupApply
|
||||
// 528 138 C2CRecall
|
||||
// 528 290 C2CPoke
|
||||
// 528 349 DeviceChange
|
||||
// 732 12 GroupBan
|
||||
// 732 16 GroupUniqueTitleChange
|
||||
// 732 17 GroupRecall
|
||||
// 732 20 GroupCommonTips
|
||||
// 732 21 EssenceMessage
|
||||
} catch (e) {
|
||||
log('解析SysMsg异常', e);
|
||||
// console.log(e);
|
||||
}
|
||||
// // 未区分增加与减少
|
||||
// // if (MsgType == 34 && subType0 == 0) {
|
||||
// // const role = (await getGroupMember(peeruin, selfInfo.uin))?.role;
|
||||
// // const isPrivilege = role === 3 || role === 4;
|
||||
// // if (!isPrivilege) {
|
||||
// // const leaveUin =
|
||||
// // log('[群聊] 群组 ', peeruin, ' 成员' + leaveUin + '退出');
|
||||
// // const groupDecreaseEvent = new OB11GroupDecreaseEvent(peeruin, parseInt(leaveUin), 0, 'leave');// 不知道怎么出去的
|
||||
// // postOB11Event(groupDecreaseEvent, true);
|
||||
// // }
|
||||
// // }
|
||||
// // MsgType (SubType) EventName
|
||||
// // 33 GroupMemIncreased
|
||||
// // 34 GroupMemberDecreased
|
||||
// // 44 GroupAdminChange
|
||||
// // 82 GroupMessage
|
||||
// // 84 GroupApply
|
||||
// // 87 InviteGroup
|
||||
// // 528 35 FriendApply
|
||||
// // 528 39 CardChange
|
||||
// // 528 68 GroupApply
|
||||
// // 528 138 C2CRecall
|
||||
// // 528 290 C2CPoke
|
||||
// // 528 349 DeviceChange
|
||||
// // 732 12 GroupBan
|
||||
// // 732 16 GroupUniqueTitleChange
|
||||
// // 732 17 GroupRecall
|
||||
// // 732 20 GroupCommonTips
|
||||
// // 732 21 EssenceMessage
|
||||
// } catch (e) {
|
||||
// log('解析SysMsg异常', e);
|
||||
// // console.log(e);
|
||||
// }
|
||||
};
|
||||
msgListener.onKickedOffLine = (Info: KickedOffLineInfo) => {
|
||||
// 下线通知
|
||||
@@ -229,27 +233,32 @@ export class NapCatOnebot11 {
|
||||
// });
|
||||
// console.log(ret);
|
||||
new Promise((resolve) => {
|
||||
dbUtil.addMsg(m).then(msgShortId => {
|
||||
m.id = msgShortId;
|
||||
this.postReceiveMsg([m]).then().catch(logError);
|
||||
}).catch(logError);
|
||||
m.id = MessageUnique.createMsg({ chatType: m.chatType, peerUid: m.peerUid, guildId: '' }, m.msgId);
|
||||
this.postReceiveMsg([m]).then().catch(logError);
|
||||
}).then();
|
||||
}
|
||||
};
|
||||
msgListener.onMsgInfoListUpdate = (msgList) => {
|
||||
this.postRecallMsg(msgList).then().catch(logError);
|
||||
for (const msg of msgList.filter(e => e.senderUin == selfInfo.uin)) {
|
||||
// console.log(msg);
|
||||
if (msg.sendStatus == 2) {
|
||||
//完成后再post
|
||||
OB11Constructor.message(msg).then((_msg) => {
|
||||
_msg.target_id = parseInt(msg.peerUin);
|
||||
if (ob11Config.reportSelfMessage) {
|
||||
msg.id = MessageUnique.createMsg({ chatType: msg.chatType, peerUid: msg.peerUid, guildId: '' }, msg.msgId);
|
||||
this.postReceiveMsg([msg]).then().catch(logError);
|
||||
} else {
|
||||
logMessage(_msg as OB11Message).then().catch(logError);
|
||||
}
|
||||
}).catch(logError);
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
msgListener.onAddSendMsg = (msg) => {
|
||||
OB11Constructor.message(msg).then((_msg) => {
|
||||
_msg.target_id = parseInt(msg.peerUin);
|
||||
logMessage(_msg as OB11Message).then().catch(logError);
|
||||
}).catch(logError);
|
||||
if (ob11Config.reportSelfMessage) {
|
||||
dbUtil.addMsg(msg).then(id => {
|
||||
msg.id = id;
|
||||
this.postReceiveMsg([msg]).then().catch(logError);
|
||||
});
|
||||
}
|
||||
|
||||
};
|
||||
napCatCore.addListener(msgListener);
|
||||
logDebug('ob11 msg listener added');
|
||||
@@ -265,21 +274,70 @@ export class NapCatOnebot11 {
|
||||
|
||||
// GroupListener
|
||||
const groupListener = new GroupListener();
|
||||
// groupListener.onMemberListChange = async (arg: {
|
||||
// sceneId: string,
|
||||
// ids: string[],
|
||||
// infos: Map<string, GroupMember>, // uid -> GroupMember
|
||||
// finish: boolean,
|
||||
// hasRobot: boolean
|
||||
// }) => {
|
||||
|
||||
// }
|
||||
groupListener.onGroupNotifiesUpdated = async (doubt, notifies) => {
|
||||
//console.log('ob11 onGroupNotifiesUpdated', notifies[0]);
|
||||
this.postGroupNotifies(notifies).then().catch(e => logError('postGroupNotifies error: ', e));
|
||||
if (![GroupNotifyTypes.ADMIN_SET, GroupNotifyTypes.ADMIN_UNSET, GroupNotifyTypes.ADMIN_UNSET_OTHER].includes(notifies[0].type)) {
|
||||
this.postGroupNotifies(notifies).then().catch(e => logError('postGroupNotifies error: ', e));
|
||||
}
|
||||
};
|
||||
groupListener.onMemberInfoChange = async (groupCode: string, changeType: number, members: Map<string, GroupMember>) => {
|
||||
//console.log("ob11 onMemberInfoChange", groupCode, changeType, members)
|
||||
if (changeType === 1) {
|
||||
let member;
|
||||
for (const [key, value] of members) {
|
||||
member = value;
|
||||
break;
|
||||
}
|
||||
if (member) {
|
||||
const existMembers = groupMembers.get(groupCode);
|
||||
if (existMembers) {
|
||||
const existMember = existMembers.get(member.uid);
|
||||
if (existMember) {
|
||||
if (existMember.isChangeRole) {
|
||||
//console.log("ob11 onMemberInfoChange:eventMember:localMember", member, existMember)
|
||||
const notify: GroupNotify[] = [
|
||||
{
|
||||
time: Date.now(),
|
||||
seq: (Date.now() * 1000 * 1000).toString(),
|
||||
type: member.role === GroupMemberRole.admin ? GroupNotifyTypes.ADMIN_SET : GroupNotifyTypes.ADMIN_UNSET_OTHER, // 8 设置; 13 取消
|
||||
status: 0,
|
||||
group: { groupCode: groupCode, groupName: '' },
|
||||
user1: { uid: member.uid, nickName: member.nick },
|
||||
user2: { uid: member.uid, nickName: member.nick },
|
||||
actionUser: { uid: '', nickName: '' },
|
||||
actionTime: '0',
|
||||
invitationExt: { srcType: 0, groupCode: '0', waitStatus: 0 },
|
||||
postscript: '',
|
||||
repeatSeqs: [],
|
||||
warningTips: ''
|
||||
}
|
||||
];
|
||||
this.postGroupNotifies(notify).then().catch(e => logError('postGroupNotifies error: ', e));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 如果自身是非管理员也许要从这里获取Delete 成员变动 待测试与验证
|
||||
const role = (await getGroupMember(groupCode, selfInfo.uin))?.role;
|
||||
const isPrivilege = role === 3 || role === 4;
|
||||
for (const member of members.values()) {
|
||||
//console.log(member?.isDelete, role, isPrivilege);
|
||||
if (member?.isDelete && !isPrivilege && selfInfo.uin !== member.uin) {
|
||||
log('[群聊] 群组 ', groupCode, ' 成员' + member.uin + '退出');
|
||||
const groupDecreaseEvent = new OB11GroupDecreaseEvent(parseInt(groupCode), parseInt(member.uin), 0, 'leave');// 不知道怎么出去的
|
||||
postOB11Event(groupDecreaseEvent, true);
|
||||
}
|
||||
// Develop Mlikiowa Taged: 暂时屏蔽这个方案 考虑onMemberListChange
|
||||
// if (member?.isDelete && !isPrivilege && selfInfo.uin !== member.uin) {
|
||||
// log('[群聊] 群组 ', groupCode, ' 成员' + member.uin + '退出');
|
||||
// const groupDecreaseEvent = new OB11GroupDecreaseEvent(parseInt(groupCode), parseInt(member.uin), 0, 'leave');// 不知道怎么出去的
|
||||
// postOB11Event(groupDecreaseEvent, true);
|
||||
// }
|
||||
}
|
||||
};
|
||||
groupListener.onJoinGroupNotify = (...notify) => {
|
||||
@@ -298,9 +356,6 @@ export class NapCatOnebot11 {
|
||||
const { debug, reportSelfMessage } = ob11Config;
|
||||
for (const message of msgList) {
|
||||
logDebug('收到新消息', message);
|
||||
// if (message.senderUin !== selfInfo.uin){
|
||||
// message.msgShortId = await dbUtil.addMsg(message);
|
||||
// }
|
||||
OB11Constructor.message(message).then((msg) => {
|
||||
logDebug('收到消息: ', msg);
|
||||
if (debug) {
|
||||
@@ -312,10 +367,6 @@ export class NapCatOnebot11 {
|
||||
}
|
||||
if (msg.post_type === 'message') {
|
||||
logMessage(msg as OB11Message).then().catch(logError);
|
||||
// 大概测试了一下,10000个以内 includes 和 find 性能差距不大
|
||||
if (msg.message_type == 'group' && msg.group_id && ob11Config.GroupLocalTime.Record && (ob11Config.GroupLocalTime.RecordList[0] === '-1' || ob11Config.GroupLocalTime.RecordList.find(gid => gid == msg.group_id?.toString()))) {
|
||||
dbUtil.insertLastSentTime(msg.group_id, msg.user_id, msg.time);
|
||||
}
|
||||
} else if (msg.post_type === 'notice') {
|
||||
logNotice(msg).then().catch(logError);
|
||||
} else if (msg.post_type === 'request') {
|
||||
@@ -331,7 +382,6 @@ export class NapCatOnebot11 {
|
||||
postOB11Event(msg);
|
||||
// log("post msg", msg)
|
||||
}).catch(e => logError('constructMessage error: ', e));
|
||||
|
||||
OB11Constructor.GroupEvent(message).then(groupEvent => {
|
||||
if (groupEvent) {
|
||||
// log("post group event", groupEvent);
|
||||
@@ -354,51 +404,68 @@ export class NapCatOnebot11 {
|
||||
// throw new Error('Invalid configuration object');
|
||||
// }
|
||||
const OldConfig = JSON.parse(JSON.stringify(ob11Config)); //进行深拷贝
|
||||
ob11Config.save(NewOb11);//保存新配置
|
||||
ob11Config.save(NewOb11, true);//保存新配置
|
||||
|
||||
const isHttpChanged = !isEqual(NewOb11.http.port, OldConfig.http.port);
|
||||
const isHttpEnableChanged = !isEqual(NewOb11.http.enable, OldConfig.http.enable);
|
||||
const isHttpChanged = !isEqual(NewOb11.http.enable, OldConfig.http.enable) ||
|
||||
!isEqual(NewOb11.http.host, OldConfig.http.host) ||
|
||||
!isEqual(NewOb11.http.port, OldConfig.http.port);
|
||||
|
||||
// const isHttpPostChanged = !isEqual(NewOb11.http.postUrls, OldConfig.http.postUrls);
|
||||
// const isEnanleHttpPostChanged = !isEqual(NewOb11.http.enablePost, OldConfig.http.enablePost);
|
||||
|
||||
const isWsChanged = !isEqual(NewOb11.ws.port, OldConfig.ws.port);
|
||||
const isEnableWsChanged = !isEqual(NewOb11.ws.enable, OldConfig.ws.enable);
|
||||
const isWsChanged = !isEqual(NewOb11.ws.enable, OldConfig.ws.enable) ||
|
||||
!isEqual(NewOb11.ws.host, OldConfig.ws.host) ||
|
||||
!isEqual(NewOb11.ws.port, OldConfig.ws.port);
|
||||
|
||||
const isEnableWsReverseChanged = !isEqual(NewOb11.reverseWs.enable, OldConfig.reverseWs.enable);
|
||||
const isWsReverseUrlsChanged = !isEqual(NewOb11.reverseWs.urls, OldConfig.reverseWs.urls);
|
||||
const isWsReverseChanged = !isEqual(NewOb11.reverseWs.enable, OldConfig.reverseWs.enable) ||
|
||||
!isEqual(NewOb11.reverseWs.urls, OldConfig.reverseWs.urls);
|
||||
|
||||
//const isEnableHeartBeatChanged = !isEqual(NewOb11.heartInterval, OldConfig.heartInterval);
|
||||
|
||||
// http重启逻辑
|
||||
// console.log(isHttpEnableChanged, isHttpChanged, NewOb11.http.enable);
|
||||
if ((isHttpEnableChanged || isHttpChanged) && NewOb11.http.enable) {
|
||||
if (OldConfig.http.enable) {
|
||||
ob11HTTPServer.stop();
|
||||
}
|
||||
ob11HTTPServer.start(NewOb11.http.port, NewOb11.http.host);
|
||||
} else if (isHttpEnableChanged && !NewOb11.http.enable) {
|
||||
ob11HTTPServer.stop();
|
||||
}
|
||||
|
||||
// ws重启逻辑
|
||||
if ((isEnableWsChanged || isWsChanged) && NewOb11.ws.enable) {
|
||||
if (OldConfig.ws.enable) {
|
||||
if (check_http_ws_equal(NewOb11) || check_http_ws_equal(OldConfig)) {
|
||||
// http与ws共站 需要同步重启
|
||||
if (isHttpChanged || isWsChanged) {
|
||||
log('http与ws进行热重载');
|
||||
ob11WebsocketServer.stop();
|
||||
ob11HTTPServer.stop();
|
||||
if (NewOb11.http.enable) {
|
||||
ob11HTTPServer.start(NewOb11.http.port, NewOb11.http.host);
|
||||
}
|
||||
if (NewOb11.ws.enable) {
|
||||
if (check_http_ws_equal(NewOb11) && ob11HTTPServer.server) {
|
||||
ob11WebsocketServer.start(ob11HTTPServer.server);
|
||||
} else {
|
||||
ob11WebsocketServer.start(NewOb11.ws.port, NewOb11.ws.host);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// http重启逻辑
|
||||
if (isHttpChanged) {
|
||||
log('http进行热重载');
|
||||
ob11HTTPServer.stop();
|
||||
if (NewOb11.http.enable) {
|
||||
ob11HTTPServer.start(NewOb11.http.port, NewOb11.http.host);
|
||||
}
|
||||
}
|
||||
|
||||
// ws重启逻辑
|
||||
if (isWsChanged) {
|
||||
log('ws进行热重载');
|
||||
ob11WebsocketServer.stop();
|
||||
if (NewOb11.ws.enable) {
|
||||
ob11WebsocketServer.start(NewOb11.ws.port, NewOb11.ws.host);
|
||||
}
|
||||
}
|
||||
ob11WebsocketServer.start(NewOb11.ws.port, NewOb11.ws.host);
|
||||
} else if (isHttpEnableChanged && !NewOb11.http.enable) {
|
||||
ob11WebsocketServer.stop();
|
||||
}
|
||||
|
||||
// 反向ws重启逻辑
|
||||
if ((isEnableWsReverseChanged || isWsReverseUrlsChanged) && NewOb11.reverseWs.enable) {
|
||||
if (OldConfig.reverseWs.enable) {
|
||||
ob11ReverseWebsockets.stop();
|
||||
}
|
||||
ob11ReverseWebsockets.start();
|
||||
} else if (isHttpEnableChanged && !NewOb11.http.enable) {
|
||||
if (isWsReverseChanged) {
|
||||
log('反向ws进行热重载');
|
||||
ob11ReverseWebsockets.stop();
|
||||
if (NewOb11.reverseWs.enable) {
|
||||
ob11ReverseWebsockets.start();
|
||||
}
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
@@ -457,7 +524,8 @@ export class NapCatOnebot11 {
|
||||
} catch (e: any) {
|
||||
logError('获取群通知的成员信息失败', notify, e.stack.toString());
|
||||
}
|
||||
} else if ([GroupNotifyTypes.JOIN_REQUEST].includes(notify.type)) {
|
||||
//notify.status == 1 表示未处理 2表示处理完成
|
||||
} else if ([GroupNotifyTypes.JOIN_REQUEST].includes(notify.type) && notify.status == 1) {
|
||||
logDebug('有加群请求');
|
||||
const groupRequestEvent = new OB11GroupRequestEvent();
|
||||
groupRequestEvent.group_id = parseInt(notify.group.groupCode);
|
||||
@@ -477,7 +545,7 @@ export class NapCatOnebot11 {
|
||||
groupRequestEvent.flag = flag;
|
||||
postOB11Event(groupRequestEvent);
|
||||
} else if (notify.type == GroupNotifyTypes.INVITE_ME) {
|
||||
logDebug('收到邀请我加群通知');
|
||||
logDebug(`收到邀请我加群通知:${notify}`);
|
||||
const groupInviteEvent = new OB11GroupRequestEvent();
|
||||
groupInviteEvent.group_id = parseInt(notify.group.groupCode);
|
||||
const user_id = (await NTQQUserApi.getUinByUid(notify.user2.uid)) || '';
|
||||
@@ -498,12 +566,12 @@ export class NapCatOnebot11 {
|
||||
// log("message update", message.sendStatus, message.msgId, message.msgSeq)
|
||||
if (message.recallTime != '0') { //todo: 这个判断方法不太好,应该使用灰色消息元素来判断?
|
||||
// 撤回消息上报
|
||||
const oriMessage = await dbUtil.getMsgByLongId(message.msgId);
|
||||
if (!oriMessage) {
|
||||
const oriMessageId = await MessageUnique.getShortIdByMsgId(message.msgId);
|
||||
if (!oriMessageId) {
|
||||
continue;
|
||||
}
|
||||
if (message.chatType == ChatType.friend) {
|
||||
const friendRecallEvent = new OB11FriendRecallNoticeEvent(parseInt(message!.senderUin), oriMessage!.id!);
|
||||
const friendRecallEvent = new OB11FriendRecallNoticeEvent(parseInt(message!.senderUin), oriMessageId);
|
||||
postOB11Event(friendRecallEvent);
|
||||
} else if (message.chatType == ChatType.group) {
|
||||
let operatorId = message.senderUin;
|
||||
@@ -516,7 +584,7 @@ export class NapCatOnebot11 {
|
||||
parseInt(message.peerUin),
|
||||
parseInt(message.senderUin),
|
||||
parseInt(operatorId),
|
||||
oriMessage.id!
|
||||
oriMessageId
|
||||
);
|
||||
postOB11Event(groupRecallEvent);
|
||||
}
|
||||
@@ -526,7 +594,7 @@ export class NapCatOnebot11 {
|
||||
|
||||
async postFriendRequest(reqs: FriendRequest[]) {
|
||||
for (const req of reqs) {
|
||||
if (req.isDecide && req.reqType !== BuddyReqType.KMEINITIATORWAITPEERCONFIRM) {
|
||||
if (!!req.isInitiator || (req.isDecide && req.reqType !== BuddyReqType.KMEINITIATORWAITPEERCONFIRM)) {
|
||||
continue;
|
||||
}
|
||||
const friendRequestEvent = new OB11FriendRequestEvent();
|
||||
|
@@ -11,7 +11,6 @@ import { normalize, sendMsg } from '../action/msg/SendMsg';
|
||||
import { OB11FriendRequestEvent } from '../event/request/OB11FriendRequest';
|
||||
import { OB11GroupRequestEvent } from '../event/request/OB11GroupRequest';
|
||||
import { isNull } from '@/common/utils/helper';
|
||||
import { dbUtil } from '@/common/utils/db';
|
||||
import { getGroup, selfInfo } from '@/core/data';
|
||||
import { NTQQFriendApi, NTQQGroupApi, NTQQUserApi } from '@/core/apis';
|
||||
import createSendElements from '../action/msg/SendMsg/create-send-elements';
|
||||
@@ -125,7 +124,6 @@ export function postOB11Event(msg: QuickActionEvent, reportSelf = false, postWs
|
||||
}
|
||||
async function handleMsg(msg: OB11Message, quickAction: QuickAction) {
|
||||
msg = msg as OB11Message;
|
||||
const rawMessage = await dbUtil.getMsgByShortId(msg.message_id);
|
||||
const reply = quickAction.reply;
|
||||
const peer: Peer = {
|
||||
chatType: ChatType.friend,
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import { WebSocket } from 'ws';
|
||||
import http from 'http';
|
||||
import { actionMap } from '../../action';
|
||||
import { OB11Response } from '../../action/OB11Response';
|
||||
import { postWsEvent, registerWsEventSender, unregisterWsEventSender } from '../postOB11Event';
|
||||
@@ -18,7 +19,7 @@ const heartbeatRunning = false;
|
||||
|
||||
class OB11WebsocketServer extends WebsocketServerBase {
|
||||
|
||||
public start(port: number, host: string) {
|
||||
public start(port: number | http.Server, host: string = '') {
|
||||
this.token = ob11Config.token;
|
||||
super.start(port, host);
|
||||
}
|
||||
|
@@ -114,6 +114,7 @@ export interface OB11MessageAt {
|
||||
type: OB11MessageDataType.at
|
||||
data: {
|
||||
qq: `${number}` | 'all'
|
||||
name?: string
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,13 +178,20 @@ export interface OB11MessageMarkdown {
|
||||
}
|
||||
}
|
||||
|
||||
export interface OB11MessageForward {
|
||||
type: OB11MessageDataType.forward
|
||||
data: {
|
||||
id: string
|
||||
}
|
||||
}
|
||||
|
||||
export type OB11MessageData =
|
||||
OB11MessageText |
|
||||
OB11MessageFace | OB11MessageMFace |
|
||||
OB11MessageAt | OB11MessageReply |
|
||||
OB11MessageImage | OB11MessageRecord | OB11MessageFile | OB11MessageVideo |
|
||||
OB11MessageNode | OB11MessageIdMusic | OB11MessageCustomMusic | OB11MessageJson |
|
||||
OB11MessageDice | OB11MessageRPS | OB11MessageMarkdown
|
||||
OB11MessageDice | OB11MessageRPS | OB11MessageMarkdown | OB11MessageForward
|
||||
|
||||
export interface OB11PostSendMsg {
|
||||
message_type?: 'private' | 'group'
|
||||
|
@@ -1 +1 @@
|
||||
export const version = '1.6.6';
|
||||
export const version = '1.7.6';
|
||||
|
@@ -21,20 +21,25 @@ const __dirname = dirname(__filename);
|
||||
*/
|
||||
export async function InitWebUi() {
|
||||
const config = await WebUiConfig.GetWebUIConfig();
|
||||
if (config.port == 0) {
|
||||
log('[NapCat] [WebUi] Current WebUi is not run.');
|
||||
return;
|
||||
}
|
||||
app.use(express.json());
|
||||
// 初始服务
|
||||
app.all('/', (_req, res) => {
|
||||
// WebUI只在config.prefix所示路径上提供服务,可配合Nginx挂载到子目录中
|
||||
app.all(config.prefix + '/', (_req, res) => {
|
||||
res.json({
|
||||
msg: 'NapCat WebAPI is now running!',
|
||||
});
|
||||
});
|
||||
// 配置静态文件服务,提供./static目录下的文件服务,访问路径为/webui
|
||||
app.use('/webui', express.static(resolve(__dirname, './static')));
|
||||
app.use(config.prefix + '/webui', express.static(resolve(__dirname, './static')));
|
||||
//挂载API接口
|
||||
app.use('/api', ALLRouter);
|
||||
app.listen(config.port, async () => {
|
||||
log(`[NapCat] [WebUi] Current WebUi is running at IP:${config.port}`);
|
||||
app.use(config.prefix + '/api', ALLRouter);
|
||||
app.listen(config.port, config.host, async () => {
|
||||
log(`[NapCat] [WebUi] Current WebUi is running at http://${config.host}:${config.port}${config.prefix}`);
|
||||
log(`[NapCat] [WebUi] Login URL is http://${config.host}:${config.port}${config.prefix}/webui`);
|
||||
log(`[NapCat] [WebUi] Login Token is ${config.token}`);
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
@@ -12,7 +12,33 @@ const __dirname = dirname(__filename);
|
||||
// 限制尝试端口的次数,避免死循环
|
||||
const MAX_PORT_TRY = 100;
|
||||
|
||||
async function tryUsePort(port: number, tryCount: number = 0): Promise<number> {
|
||||
async function tryUseHost(host: string): Promise<string> {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
const server = net.createServer();
|
||||
server.on('listening', () => {
|
||||
server.close();
|
||||
resolve(host);
|
||||
});
|
||||
|
||||
server.on('error', (err: any) => {
|
||||
if (err.code === 'EADDRNOTAVAIL') {
|
||||
reject('主机地址验证失败,可能为非本机地址');
|
||||
} else {
|
||||
reject(`遇到错误: ${err.code}`);
|
||||
}
|
||||
});
|
||||
|
||||
// 尝试监听 让系统随机分配一个端口
|
||||
server.listen(0, host);
|
||||
} catch (error) {
|
||||
// 这里捕获到的错误应该是启动服务器时的同步错误
|
||||
reject(`服务器启动时发生错误: ${error}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function tryUsePort(port: number, host: string, tryCount: number = 0): Promise<number> {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
const server = net.createServer();
|
||||
@@ -25,7 +51,7 @@ async function tryUsePort(port: number, tryCount: number = 0): Promise<number> {
|
||||
if (err.code === 'EADDRINUSE') {
|
||||
if (tryCount < MAX_PORT_TRY) {
|
||||
// 使用循环代替递归
|
||||
resolve(tryUsePort(port + 1, tryCount + 1));
|
||||
resolve(tryUsePort(port + 1, host, tryCount + 1));
|
||||
} else {
|
||||
reject(`端口尝试失败,达到最大尝试次数: ${MAX_PORT_TRY}`);
|
||||
}
|
||||
@@ -35,7 +61,7 @@ async function tryUsePort(port: number, tryCount: number = 0): Promise<number> {
|
||||
});
|
||||
|
||||
// 尝试监听端口
|
||||
server.listen(port);
|
||||
server.listen(port, host);
|
||||
} catch (error) {
|
||||
// 这里捕获到的错误应该是启动服务器时的同步错误
|
||||
reject(`服务器启动时发生错误: ${error}`);
|
||||
@@ -44,44 +70,73 @@ async function tryUsePort(port: number, tryCount: number = 0): Promise<number> {
|
||||
}
|
||||
|
||||
export interface WebUiConfigType {
|
||||
host: string;
|
||||
port: number;
|
||||
prefix: string;
|
||||
token: string;
|
||||
loginRate: number
|
||||
}
|
||||
// 读取当前目录下名为 webui.json 的配置文件,如果不存在则创建初始化配置文件
|
||||
class WebUiConfigWrapper {
|
||||
WebUiConfigData: WebUiConfigType | undefined = undefined;
|
||||
private applyDefaults<T>(obj: Partial<T>, defaults: T): T {
|
||||
return { ...defaults, ...obj };
|
||||
}
|
||||
async GetWebUIConfig(): Promise<WebUiConfigType> {
|
||||
if (this.WebUiConfigData) {
|
||||
return this.WebUiConfigData;
|
||||
}
|
||||
const defaultconfig: WebUiConfigType = {
|
||||
host: '0.0.0.0',
|
||||
port: 6099,
|
||||
prefix: '',
|
||||
token: '', // 默认先填空,空密码无法登录
|
||||
loginRate: 3
|
||||
};
|
||||
try {
|
||||
defaultconfig.token = Math.random().toString(36).slice(2); //生成随机密码
|
||||
} catch (e) {
|
||||
logError('随机密码生成失败', e);
|
||||
}
|
||||
try {
|
||||
const configPath = resolve(__dirname, './config/webui.json');
|
||||
const config: WebUiConfigType = {
|
||||
port: 6099,
|
||||
token: Math.random().toString(36).slice(2),//生成随机密码
|
||||
loginRate: 3
|
||||
};
|
||||
|
||||
if (!existsSync(configPath)) {
|
||||
writeFileSync(configPath, JSON.stringify(config, null, 4));
|
||||
writeFileSync(configPath, JSON.stringify(defaultconfig, null, 4));
|
||||
}
|
||||
|
||||
const fileContent = readFileSync(configPath, 'utf-8');
|
||||
const parsedConfig = JSON.parse(fileContent) as WebUiConfigType;
|
||||
// 更新配置字段后新增字段可能会缺失,同步一下
|
||||
const parsedConfig = this.applyDefaults(JSON.parse(fileContent) as Partial<WebUiConfigType>, defaultconfig);
|
||||
|
||||
// 修正端口占用情况
|
||||
const [err, data] = await tryUsePort(parsedConfig.port).then(data => [null, data as number]).catch(err => [err, null]);
|
||||
parsedConfig.port = data;
|
||||
if (err) {
|
||||
//一般没那么离谱 如果真有这么离谱 考虑下 向外抛出异常
|
||||
if (!parsedConfig.prefix.startsWith('/')) parsedConfig.prefix = '/' + parsedConfig.prefix;
|
||||
if (parsedConfig.prefix.endsWith('/')) parsedConfig.prefix = parsedConfig.prefix.slice(0, -1);
|
||||
// 配置已经被操作过了,还是回写一下吧,不然新配置不会出现在配置文件里
|
||||
writeFileSync(configPath, JSON.stringify(parsedConfig, null, 4));
|
||||
// 不希望回写的配置放后面
|
||||
|
||||
// 查询主机地址是否可用
|
||||
const [host_err, host] = await tryUseHost(parsedConfig.host).then(data => [null, data as string]).catch(err => [err, null]);
|
||||
if (host_err) {
|
||||
logError('host不可用', host_err);
|
||||
parsedConfig.port = 0; // 设置为0,禁用WebUI
|
||||
} else {
|
||||
parsedConfig.host = host;
|
||||
// 修正端口占用情况
|
||||
const [port_err, port] = await tryUsePort(parsedConfig.port, parsedConfig.host).then(data => [null, data as number]).catch(err => [err, null]);
|
||||
if (port_err) {
|
||||
logError('port不可用', port_err);
|
||||
parsedConfig.port = 0; // 设置为0,禁用WebUI
|
||||
} else {
|
||||
parsedConfig.port = port;
|
||||
}
|
||||
}
|
||||
this.WebUiConfigData = parsedConfig;
|
||||
return this.WebUiConfigData;
|
||||
} catch (e) {
|
||||
logError('读取配置文件失败', e);
|
||||
}
|
||||
return {} as WebUiConfigType; // 理论上这行代码到不了,为了保持函数完整性而保留
|
||||
return defaultconfig; // 理论上这行代码到不了,到了只能返回默认配置了
|
||||
}
|
||||
}
|
||||
export const WebUiConfig = new WebUiConfigWrapper();
|
||||
export const WebUiConfig = new WebUiConfigWrapper();
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user