Compare commits

...

121 Commits

Author SHA1 Message Date
手瓜一十雪
48d5cb53bd release: 1.7.6 2024-08-03 22:45:36 +08:00
手瓜一十雪
fd7d2dbf53 release: 1.7.5 2024-08-03 20:48:13 +08:00
手瓜一十雪
6609697752 release: 1.7.5 2024-08-03 20:47:05 +08:00
手瓜一十雪
dcd6e1973e build:1.7.5For9.9.15 2024-08-03 16:23:55 +08:00
手瓜一十雪
3614a6e932 chore: 9.9.15 support 2024-08-03 16:08:23 +08:00
手瓜一十雪
931a0210e5 chore: 兼容9.915 信息获取 2024-08-03 15:36:56 +08:00
手瓜一十雪
f9e7de4b42 build: 1.7.5For9.9.15 2024-08-03 15:07:51 +08:00
手瓜一十雪
8e0b79594e style: lint 2024-08-03 15:06:02 +08:00
手瓜一十雪
17122c4360 feat: 精简历史获取 2024-08-03 15:03:41 +08:00
手瓜一十雪
154f7b6a30 chore: 清除老旧代码 2024-08-03 14:57:24 +08:00
手瓜一十雪
52e5543d0b chore: queryEmoticonMsgs 2024-08-03 14:44:48 +08:00
手瓜一十雪
3c304bd2ae feat: 补全类型 开始对9.9.15针对优化 2024-08-03 14:25:26 +08:00
手瓜一十雪
26609bb8fd chore: 9.9.15兼容sendmsg 2024-08-03 13:11:25 +08:00
手瓜一十雪
de3fa9aaa4 Merge branch 'main' of https://github.com/NapNeko/NapCatQQ 2024-08-03 12:28:41 +08:00
手瓜一十雪
788665f84c chore: support win 9.9.15 2024-08-03 12:28:25 +08:00
手瓜一十雪
3943782971 Merge pull request #201 from idranme/main
feat: at segment add name
2024-08-03 07:08:25 +08:00
idranme
8f899c40f2 chore 2024-08-02 15:49:01 +00:00
idranme
a1f582399e feat: at segment add name 2024-08-02 15:45:37 +00:00
手瓜一十雪
440b63f662 release: v1.7.4 2024-08-01 23:35:35 +08:00
手瓜一十雪
7d2cc3b56b fix: 多次上报自身消息 2024-08-01 22:00:40 +08:00
手瓜一十雪
5fe3422469 #176 revert 2024-08-01 21:48:17 +08:00
手瓜一十雪
6c02cedb1e build: 1.7.4 2024-08-01 19:44:28 +08:00
手瓜一十雪
3cc2f1dcad fix #183 2024-08-01 19:43:29 +08:00
手瓜一十雪
773cdc5877 build: test 2024-08-01 17:14:15 +08:00
手瓜一十雪
361a7329d7 Revert "build(deps-dev): bump @typescript-eslint/eslint-plugin"
This reverts commit 2562a38fa1.
2024-08-01 17:13:54 +08:00
手瓜一十雪
29910f1236 build: test api 2024-08-01 17:12:56 +08:00
手瓜一十雪
4a164016f5 chore: test 2024-08-01 17:08:22 +08:00
手瓜一十雪
cebd3e62a4 Merge pull request #192 from NapNeko/dependabot/npm_and_yarn/typescript-eslint/eslint-plugin-8.0.0
build(deps-dev): bump @typescript-eslint/eslint-plugin from 7.18.0 to 8.0.0
2024-08-01 16:26:50 +08:00
dependabot[bot]
2562a38fa1 build(deps-dev): bump @typescript-eslint/eslint-plugin
Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 7.18.0 to 8.0.0.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.0.0/packages/eslint-plugin)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/eslint-plugin"
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-01 08:22:34 +00:00
手瓜一十雪
d46c922bbf Merge branch 'main' of https://github.com/NapNeko/NapCatQQ 2024-08-01 13:11:19 +08:00
手瓜一十雪
66b59982f7 补充类型 移除无用代码 2024-08-01 13:11:09 +08:00
手瓜一十雪
ad397ccf7f Merge pull request #188 from Fripine/feat/FriendAddNoticeEvent
feat: support FriendAddNoticeEvent
2024-08-01 08:46:50 +08:00
Fripine
bdef80ede7 feat: support FriendAddNoticeEvent 2024-08-01 01:03:33 +08:00
手瓜一十雪
385dcbc75a fix #186 2024-07-31 22:23:12 +08:00
手瓜一十雪
74cf501c8f release: 1.7.3 2024-07-31 22:21:23 +08:00
手瓜一十雪
0c200d6748 build: 1.7.2 beta0 2024-07-31 22:20:39 +08:00
手瓜一十雪
e65a36c517 chore: Ver2SendMsg 2024-07-31 22:19:35 +08:00
手瓜一十雪
126b54ad40 fix #186 2024-07-31 21:46:12 +08:00
手瓜一十雪
78637751af fix #187 2024-07-31 21:41:31 +08:00
手瓜一十雪
f96526ee3a fix #184 2024-07-31 21:36:13 +08:00
手瓜一十雪
b3c7a91f3d refactor: getfile 2024-07-31 16:40:34 +08:00
手瓜一十雪
b8daeef0c4 fix #173 2024-07-31 16:02:08 +08:00
手瓜一十雪
2b662944cf typo fix #178 2024-07-31 15:58:27 +08:00
手瓜一十雪
3d516df01e try fix #183 2024-07-31 15:52:28 +08:00
手瓜一十雪
26b4a9b15b chore: 兼容wt 2024-07-31 14:21:05 +08:00
手瓜一十雪
0f8af273ae release: 1.7.2 2024-07-31 10:51:12 +08:00
手瓜一十雪
fa29a31da9 release: 1.7.2 2024-07-31 10:46:23 +08:00
手瓜一十雪
9e0d2606d8 build: 1.7.2 no log 2024-07-31 10:35:53 +08:00
手瓜一十雪
338dedd6e0 build: 1.7.1 双消息队列 2024-07-31 10:33:38 +08:00
手瓜一十雪
1f893b1393 build: 1.7.2 消息队列重构 2024-07-31 10:25:46 +08:00
手瓜一十雪
b783d6f928 refatcor: sendmsg 2024-07-31 01:12:46 +08:00
手瓜一十雪
8ca0d40f05 fix: error 2024-07-30 23:34:29 +08:00
手瓜一十雪
2f40a80434 fix: error 2024-07-30 23:33:32 +08:00
手瓜一十雪
812d8eb5bb feat: 对msgId兜底 2024-07-30 23:31:42 +08:00
手瓜一十雪
bf5f548349 fix: msghash性能问题 2024-07-30 23:06:58 +08:00
手瓜一十雪
ebf90e72b9 Merge pull request #180 from cnxysoft/main
BW5启动脚本修改
2024-07-30 22:30:04 +08:00
Alen
31d7d42edf BW5启动脚本修复
修改脚本为 UTF-8 with BOM 格式,解决添加注释后执行报错的问题
2024-07-30 19:56:17 +08:00
Alen
833875b42f Merge remote-tracking branch 'upstream/main' 2024-07-30 17:56:48 +08:00
Alen
b901c10f3c 修改BW5启动脚本
支持脚本自动提权
自动覆盖dbghelp.dll
修改默认启用UTF8
修改不再从新窗口运行NC
2024-07-30 17:56:28 +08:00
手瓜一十雪
8ab678bd97 chore: remove log 2024-07-30 16:45:03 +08:00
手瓜一十雪
55d5072f46 fix: setMsg 2024-07-30 16:34:33 +08:00
手瓜一十雪
a379ffd0f2 Merge pull request #175 from pohgxz/main
修复Way05无法带参
2024-07-30 09:12:15 +08:00
Nepenthe
4501d73134 修复Way05无法带参 2024-07-29 23:32:34 +08:00
手瓜一十雪
5f15774ec7 fix: script 2024-07-29 20:58:27 +08:00
手瓜一十雪
c9e057599e fix: error 2024-07-29 20:49:35 +08:00
手瓜一十雪
66851f5625 fix 2024-07-29 20:39:01 +08:00
手瓜一十雪
b33c235b4d release: 1.7.1 2024-07-29 20:13:09 +08:00
手瓜一十雪
d6693b6114 Merge pull request #171 from NapNeko/dependabot/npm_and_yarn/types/node-22.0.0
build(deps-dev): bump @types/node from 20.14.13 to 22.0.0
2024-07-29 17:23:02 +08:00
dependabot[bot]
36b4d26c78 build(deps-dev): bump @types/node from 20.14.13 to 22.0.0
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 20.14.13 to 22.0.0.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-29 08:23:55 +00:00
手瓜一十雪
617592d90a fix: 多次post 2024-07-29 11:32:55 +08:00
手瓜一十雪
15a77b8070 fix: bug 2024-07-29 10:24:02 +08:00
手瓜一十雪
606eccd22b refactor: 精简逻辑 2024-07-29 10:12:41 +08:00
手瓜一十雪
5613450313 refactor: 回复 2024-07-29 10:07:59 +08:00
手瓜一十雪
c59b5564af release: 1.7.0 2024-07-29 09:24:17 +08:00
手瓜一十雪
330b086b8b fix #98 2024-07-29 09:10:52 +08:00
手瓜一十雪
9837ef4f36 fix #125 2024-07-29 08:59:26 +08:00
手瓜一十雪
add46b3251 chore: 落地标准化 2024-07-29 08:43:37 +08:00
手瓜一十雪
e169199107 feat: #162 2024-07-29 08:29:24 +08:00
手瓜一十雪
92fe654850 fix #157 2024-07-29 08:12:49 +08:00
手瓜一十雪
b257486404 fix #158 2024-07-29 08:02:18 +08:00
手瓜一十雪
bdf2e33f40 Merge branch 'main' of https://github.com/NapNeko/NapCatQQ 2024-07-29 07:51:28 +08:00
手瓜一十雪
224d361923 fix #167 2024-07-29 07:51:18 +08:00
手瓜一十雪
3452fa56df build: 😭版本满天飞版本 2024-07-29 00:39:34 +08:00
手瓜一十雪
cd256235da refactor: selfMsgPost 2024-07-29 00:16:24 +08:00
手瓜一十雪
361a164f2a fix: typo 2024-07-28 23:54:50 +08:00
手瓜一十雪
0e60d4b198 fix: 延迟调整 2024-07-28 23:53:45 +08:00
手瓜一十雪
67b47e39b4 fix: typo 2024-07-28 23:49:04 +08:00
手瓜一十雪
8f54310f63 refactor: reply 2024-07-28 23:35:58 +08:00
手瓜一十雪
c7a7494d7e fix: 保证NC回复的消息一致性 2024-07-28 22:33:50 +08:00
手瓜一十雪
af88b3166d build: 169-test 2024-07-28 17:55:34 +08:00
手瓜一十雪
b7837b2a14 chore:test 2024-07-28 17:38:48 +08:00
手瓜一十雪
950ddc749e chore: 🥹LocalMsg不会写入数据库 2024-07-28 16:23:56 +08:00
手瓜一十雪
df081ef0cf chore: 移除调试代码 2024-07-28 15:40:44 +08:00
手瓜一十雪
7b24f90d9f rector: 离线文件重构初步完成 2024-07-28 15:37:34 +08:00
手瓜一十雪
f2e4579fd8 feat: 缓存文件 2024-07-28 15:34:08 +08:00
手瓜一十雪
97cb351827 fix: typo 2024-07-28 14:55:30 +08:00
手瓜一十雪
c1ec53fdbb refactor: fileget 2024-07-28 14:25:13 +08:00
手瓜一十雪
98214aa429 refactor: video element 2024-07-28 13:36:03 +08:00
手瓜一十雪
ce7deac2dd refactor: video element 2024-07-28 13:28:21 +08:00
手瓜一十雪
612092b867 build: 170 re 2024-07-28 13:15:14 +08:00
手瓜一十雪
92579d5949 Merge pull request #163 from cnxysoft/main
修复提交疏漏
2024-07-28 13:00:12 +08:00
手瓜一十雪
9ab07060ae fix: default 2024-07-28 12:59:20 +08:00
手瓜一十雪
0d45125d79 fix: uid && latestMsg 2024-07-28 09:56:00 +08:00
手瓜一十雪
9ced152778 fix: 修复uid转换异常问题 2024-07-28 09:24:03 +08:00
Alen
3685ab2e3e Merge remote-tracking branch 'upstream/main' 2024-07-27 15:01:23 +08:00
Alen
be605f11f2 修复提交疏漏
修改在查询群历史消息时,如未提供msg_seq,则返回最新消息
2024-07-27 15:01:08 +08:00
手瓜一十雪
8cca8df976 Merge pull request #159 from cnxysoft/main
bug修复和标准兼容
2024-07-27 06:46:25 +08:00
Alen
990a31e961 标准兼容
根据GOCQ标准将获取群历史消息中的msg_seq改为非必要参数,默认为0
2024-07-27 04:10:01 +08:00
Alen
5db201c342 BUG修复
修复创建reply消息体时向NTQQMsgApi.getMsgsByMsgId提交空值查询会导致QQ崩溃的BUG
2024-07-27 04:01:22 +08:00
手瓜一十雪
a625e30dd4 fix: search file 2024-07-26 17:37:04 +08:00
手瓜一十雪
b236cdd060 refactor: search file 2024-07-26 17:15:28 +08:00
手瓜一十雪
2db9899184 chore: type 2024-07-26 16:26:47 +08:00
手瓜一十雪
fe5d6db986 chore: wait release 1.7.0 2024-07-26 16:10:05 +08:00
手瓜一十雪
7c7bf8fecf chore: 类型+++++ 2024-07-26 16:08:28 +08:00
手瓜一十雪
76e3a46378 fix: type 2024-07-26 16:02:23 +08:00
手瓜一十雪
16f3897fec chore: 类型补全计划 2024-07-26 15:55:05 +08:00
手瓜一十雪
045e120854 refactor: type 2024-07-26 15:38:43 +08:00
Version
2b7fcce9b2 chore:version change 2024-07-26 05:25:49 +00:00
手瓜一十雪
9685931694 fix: uint 2024-07-26 13:25:19 +08:00
手瓜一十雪
1dc844435a Merge pull request #153 from Guation/main
feat: WebUI支持放置到二级目录中
2024-07-26 13:04:26 +08:00
挂神
a29b1154a9 feat: WebUI支持放置到二级目录中 2024-07-26 11:12:09 +08:00
65 changed files with 1832 additions and 638 deletions

View File

@@ -1,20 +0,0 @@
# v1.6.8
QQ Version: Windows 9.9.12-26000 / Linux 3.2.9-26000
## 使用前警告
1. 在最近版本由于QQ本体大幅变动为了保证NapCat可用性NapCat近期启动与安装方式将将大幅变动请关注文档和社群获取。
2. 在Core上完全执行开源请不要用于违法用途如此可能造成NapCat完全停止更新。
3. 针对原启动方式的围堵NapCat研发了多种方式除此其余理论与扩展的分析和思路将部分展示于Docs以便各位参与开发与维护NapCat。
## 其余·备注
启动方式: WayBoot.03 Electron Main进程为Node 直接注入代码 同理项目: LiteLoader
## 修复与优化
1. 移除数据库文件读写 ~ 优化性能
2. 重构消息发送 极限速度优化 ~ 优化性能
3. WebUi配置热重载优化 ~ 修复问题
4. 修复偶现崩溃问题 ~ 修复问题
## 新增与调整
1. 最后发言时间重构 入群时间失效 ~ 替换功能
新增的 API 详细见[API文档](https://napneko.github.io/zh-CN/develop/extends_api)

View 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)

View File

@@ -2,7 +2,7 @@
"name": "napcat",
"private": true,
"type": "module",
"version": "1.6.8",
"version": "1.7.6",
"scripts": {
"watch:dev": "vite --mode development",
"watch:prod": "vite --mode production",
@@ -31,7 +31,7 @@
"@types/figlet": "^1.5.8",
"@types/fluent-ffmpeg": "^2.1.24",
"@types/jest": "^29.5.12",
"@types/node": "^20.11.30",
"@types/node": "^22.0.0",
"@types/qrcode-terminal": "^0.12.2",
"@types/ws": "^8.5.10",
"@typescript-eslint/eslint-plugin": "^7.4.0",

View File

@@ -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
View 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

Binary file not shown.

View File

@@ -32,17 +32,17 @@ export class WebsocketServerBase {
if (port instanceof http.Server) {
try {
const wss = new WebSocketServer({
noServer: true,
maxPayload: 1024 * 1024 * 1024
}).on('error', () => {
});
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服务`);
log('ws服务启动成功, 绑定到HTTP服务');
} catch (e: any) {
throw Error('ws服务启动失败, 可能是绑定的HTTP服务异常' + e.toString());
}

View File

@@ -14,8 +14,8 @@ fs.mkdirSync(configDir, { recursive: true });
export class ConfigBase<T> {
public name: string = 'default_config'
private pathName: string | null = null // 本次读取的文件路径
public name: string = 'default_config';
private pathName: string | null = null; // 本次读取的文件路径
constructor() {
}
@@ -30,21 +30,21 @@ export class ConfigBase<T> {
return configDir;
}
getConfigPath(pathName: string | null): string {
const suffix = pathName ? `_${pathName}` : ''
const filename = `${this.name}${suffix}.json`
const suffix = pathName ? `_${pathName}` : '';
const filename = `${this.name}${suffix}.json`;
return path.join(this.getConfigDir(), filename);
}
read() {
// 尝试加载当前账号配置
if (this.read_from_file(selfInfo.uin, false)) return this
if (this.read_from_file(selfInfo.uin, false)) return this;
// 尝试加载默认配置
return this.read_from_file('', true)
return this.read_from_file('', true);
}
read_from_file(pathName: string, createIfNotExist: boolean) {
const configPath = this.getConfigPath(pathName);
if (!fs.existsSync(configPath)) {
if (!createIfNotExist) return null
this.pathName = pathName // 记录有效的设置文件
if (!createIfNotExist) return null;
this.pathName = pathName; // 记录有效的设置文件
try {
fs.writeFileSync(configPath, JSON.stringify(this, this.getKeys(), 2));
log(`配置文件${configPath}已创建\n如果修改此文件后需要重启 NapCat 生效`);
@@ -77,7 +77,7 @@ export class ConfigBase<T> {
Object.assign(this, config);
if (overwrite) {
// 用户要求强制写入,则变更当前文件为目标文件
this.pathName = `${selfInfo.uin}`
this.pathName = `${selfInfo.uin}`;
}
const configPath = this.getConfigPath(this.pathName);
try {

View File

@@ -96,7 +96,7 @@ export class NTEventWrapper {
}
});
}
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;
@@ -110,8 +110,47 @@ export class NTEventWrapper {
resolve(retData);
});
}
async RegisterListen<ListenerType extends (...args: any[]) => void>(ListenerName = '', waitTimes = 1, timeout = 5000, checker: (...args: Parameters<ListenerType>) => boolean) {
return new Promise<Parameters<ListenerType>>((resolve, reject) => {
const ListenerNameList = ListenerName.split('/');
const ListenerMainName = ListenerNameList[0];
const ListenerSubName = ListenerNameList[1];
const id = randomUUID();
let complete = 0;
let retData: Parameters<ListenerType> | undefined = undefined;
const databack = () => {
if (complete == 0) {
reject(new Error(' ListenerName:' + ListenerName + ' timeout'));
} else {
resolve(retData!);
}
};
const Timeouter = setTimeout(databack, timeout);
const eventCallbak = {
timeout: timeout,
createtime: Date.now(),
checker: checker,
func: (...args: Parameters<ListenerType>) => {
complete++;
retData = args;
if (complete >= waitTimes) {
clearTimeout(Timeouter);
databack();
}
}
};
if (!this.EventTask.get(ListenerMainName)) {
this.EventTask.set(ListenerMainName, new Map());
}
if (!(this.EventTask.get(ListenerMainName)?.get(ListenerSubName))) {
this.EventTask.get(ListenerMainName)?.set(ListenerSubName, new Map());
}
this.EventTask.get(ListenerMainName)?.get(ListenerSubName)?.set(id, eventCallbak);
this.CreatListenerFunction(ListenerMainName);
});
}
async CallNormalEvent<EventType extends (...args: any[]) => Promise<any>, ListenerType extends (...args: any[]) => void>
(EventName = '', ListenerName = '', waitTimes = 1, timeout: number = 3000, checker: (...args: Parameters<ListenerType>) => boolean, ...args: Parameters<EventType>) {
(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;
@@ -119,16 +158,19 @@ export class NTEventWrapper {
let retEvent: any = {};
const databack = () => {
if (complete == 0) {
reject(new Error('NTEvent EventName:' + EventName + ' ListenerName:' + ListenerName + ' timeout'));
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(),
@@ -152,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[]));
});
}

View File

@@ -15,10 +15,10 @@ class LimitedHashTable<K, V> {
}
set(key: K, value: V): void {
const isExist = this.keyToValue.get(key);
if (isExist && isExist === value) {
return;
}
// const isExist = this.keyToValue.get(key);
// if (isExist && isExist === value) {
// return;
// }
this.keyToValue.set(key, value);
this.valueToKey.set(value, key);
while (this.keyToValue.size !== this.valueToKey.size) {
@@ -61,6 +61,24 @@ class LimitedHashTable<K, V> {
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 {
@@ -70,16 +88,24 @@ class MessageUniqueWrapper {
this.msgIdMap = new LimitedHashTable<string, number>(maxMap);
this.msgDataMap = new LimitedHashTable<string, number>(maxMap);
}
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);
}
createMsg(peer: Peer, msgId: string): number | undefined {
const key = `${msgId}|${peer.chatType}|${peer.peerUid}`;
const hash = crypto.createHash('sha1').update(key);
const shortId = parseInt(hash.digest('hex').slice(0, 8), 16);
const isExist = this.msgIdMap.getKey(shortId);
//console.log(`${peer.peerUid} ${msgId} ------- ${shortId}`);
if (isExist && isExist === msgId) {
return shortId;
}
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;

View File

@@ -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;

View File

@@ -8,13 +8,34 @@ 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)
setTimeout(() => reject(new Error('PromiseTimer: Operation timed out')), ms)
);
return Promise.race([promise, timeoutPromise]);
}

View File

@@ -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) {

View File

@@ -3,7 +3,7 @@ import {
CacheFileType,
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';
@@ -18,6 +18,8 @@ import { sessionConfig } from '@/core/sessionConfig';
import { rkeyManager } from '../utils/rkey';
import { NTEventDispatch } from '@/common/utils/EventTask';
import { NodeIKernelSearchService } from '../services/NodeIKernelSearchService';
import { selfInfo } from '../data';
export class NTQQFileApi {
static async getFileType(filePath: string) {
@@ -31,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) {
@@ -70,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);
// 用于下载收到的消息中的图片等
@@ -146,8 +146,65 @@ export class NTQQFileApi {
});
});
}
static async searchfile(keys: string[]) {
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;
}
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,
@@ -187,22 +244,23 @@ export class NTQQFileApi {
}[]
}[]
};
const [id, data] = await NTEventDispatch.CallNormalEvent<EventType, (params: OnListener) => void>(
'NodeIKernelSearchService/searchFileWithKeywords',
'NodeIKernelSearchListener/onSearchFileKeywordsResult',
1,
10000,
(arg): boolean => { return id == data.searchId },
keys,
12
);
return data.resultItems[0];
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: { originImageUrl: any; md5HexStr?: any; fileUuid: any; }) {
static async getImageUrl(element: PicElement) {
if (!element) {
return '';
}
const url: string = element.originImageUrl; // 没有域名
const url: string = element.originImageUrl!; // 没有域名
const md5HexStr = element.md5HexStr;
const fileMd5 = element.md5HexStr;
const fileUuid = element.fileUuid;

View File

@@ -1,4 +1,4 @@
import { GroupMember, GroupRequestOperateTypes, GroupMemberRole, GroupNotify, Group, MemberExtSourceType, GroupNotifyTypes, ChatType } 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 { log } from '@/common/utils/log';
@@ -26,7 +26,6 @@ export class NTQQGroupApi {
static async getGroupMemberLastestSendTimeCache(GroupCode: string) {
return NTQQGroupApi.getGroupMemberLastestSendTime(GroupCode);
}
/**
* 通过QQ自带数据库获取群成员最后发言时间(仅返回有效数据 且消耗延迟大 需要进行缓存)
* @param GroupCode 群号
@@ -84,14 +83,9 @@ export class NTQQGroupApi {
static async getLastestMsg(GroupCode: string, uins: string[]) {
let uids: Array<string> = [];
for (let uin of uins) {
try {
let uid = await NTQQUserApi.getUidByUin(uin)
if (uid) {
uids.push(uid);
}
} catch (error) {
log("getLastestMsg--->", error);
return undefined;
let uid = await NTQQUserApi.getUidByUin(uin)
if (uid) {
uids.push(uid);
}
}
let ret = await napCatCore.session.getMsgService().queryMsgsWithFilterEx('0', '0', '0', {

View File

@@ -1,4 +1,4 @@
import { GetFileListParam, Peer, RawMessage, SendMessageElement } from '@/core/entities';
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';
@@ -7,6 +7,8 @@ import { onGroupFileInfoUpdateParamType } from '@/core/listeners';
import { GeneralCallResult } from '@/core/services/common';
import { MessageUnique } from '../../../common/utils/MessageUnique';
import { NTEventDispatch } from '@/common/utils/EventTask';
import { requireMinNTQBuild } from '@/common/utils/QQBasicInfo';
async function LoadMessageIdList(Peer: Peer, msgId: string) {
let msgList = await NTQQMsgApi.getMsgHistory(Peer, msgId, 50);
for (let j = 0; j < msgList.msgList.length; j++) {
@@ -17,11 +19,12 @@ async function loadMessageUnique() {
if (groups.size > 100) {
logWarn('群数量大于100可能会导致性能问题');
}
let predict = (groups.size + friends.size) / 5;
let predict = (groups.size + friends.size / 2) / 5;
predict = predict < 20 ? 20 : predict;
predict = predict > 50 ? 50 : predict;
//let waitpromise: Array<Promise<{ msgList: RawMessage[]; }>> = [];
MessageUnique.resize(predict * 50);
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) {
@@ -31,16 +34,37 @@ async function loadMessageUnique() {
}
}
await Promise.all(LoadMessageIdDo).then(() => {
log(`[消息序列] 加载 ${predict * 50} 条历史消息记录完成`);
log(`[消息序列] 加载 ${predict} 条历史消息记录完成`);
});
}
setTimeout(() => {
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;
@@ -63,10 +87,41 @@ 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);
}
@@ -78,7 +133,7 @@ export class NTQQMsgApi {
(GroupCode: string, params: GetFileListParam) => Promise<unknown>,
(groupFileListResult: onGroupFileInfoUpdateParamType) => void
>(
'NodeIKernelRichMediaService/sendMsg',
'NodeIKernelRichMediaService/getGroupFileList',
'NodeIKernelMsgListener/onGroupFileInfoUpdate',
1,
5000,
@@ -100,8 +155,18 @@ export class NTQQMsgApi {
peerUid: peer.peerUid
}, msgIds);
}
static async sendMsg(peer: Peer, msgElements: SendMessageElement[], waitComplete = true, timeout = 10000) {
let msgId = await NTQQMsgApi.getMsgUnique(await NTQQMsgApi.getServerTime());
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;
// }
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
@@ -130,16 +195,53 @@ export class NTQQMsgApi {
});
return retMsg;
}
static async getMsgUniqueEx(){
let msgId = await NTQQMsgApi.getMsgUnique(await NTQQMsgApi.getServerTime());
return msgId;
static sendMsgEx(peer: Peer, msgElements: SendMessageElement[], waitComplete = true, timeout = 10000) {
//return NTQQMsgApi.sendMsgV1(peer, msgElements, waitComplete, timeout);
}
static async getMsgUnique(time: string) {
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());
}

View File

@@ -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();

View File

@@ -1,12 +1,13 @@
import { ModifyProfileParams, SelfInfo, User, UserDetailInfoByUin } from '@/core/entities';
import { friends, selfInfo } from '@/core/data';
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 { logWarn } from '@/common/utils/log';
import { NTEventDispatch } from '@/common/utils/EventTask';
import { NodeIKernelProfileService } from '@/core/services';
import { NodeIKernelProfileService, ProfileBizType, UserDetailSource } from '@/core/services';
import { requireMinNTQBuild } from '@/common/utils/QQBasicInfo';
export class NTQQUserApi {
static async getProfileLike(uid: string) {
@@ -64,7 +65,49 @@ export class NTQQUserApi {
// KQZONE,
// KOTHER
// }
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;
}
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
@@ -143,30 +186,32 @@ export class NTQQUserApi {
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) {

View File

@@ -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, 没有hosthost是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, 没有hosthost是https://gchat.qpic.cn/, 带download参数的是https://multimedia.nt.qq.com.cn
};
export enum GrayTipElementSubType {
INVITE_NEW_MEMBER = 12,
@@ -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;

View File

@@ -62,6 +62,7 @@ export enum BuddyReqType {
KMEINITIATORWAITPEERCONFIRM
}
export interface FriendRequest {
isInitiator?: boolean;
isDecide: boolean;
friendUid: string;
reqType: BuddyReqType,

View File

@@ -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,

View File

@@ -226,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;
}

View File

@@ -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[]) {
}

View File

@@ -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;

View File

@@ -0,0 +1,3 @@
export interface NodeIKernelECDHService{
}

View File

@@ -9,7 +9,44 @@ import {
import { GeneralCallResult } from '@/core/services/common';
export interface NodeIKernelGroupService {
setHeader(uid:string,path:string): unknown;
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;
@@ -39,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;

View File

@@ -1,11 +1,24 @@
import { 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>;
@@ -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(Peer: Peer, msgId: string, rawMessage: RawMessage, attr: Array<any> | number, front: boolean): Promise<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(peer: Peer, unknown: number): 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,18 +187,18 @@ 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;
getSourceOfReplyMsgByClientSeqAndTime(...args: unknown[]): unknown;
getMsgByClientSeqAndTime(peer: Peer, clientSeq: string, time: string): unknown;
getSourceOfReplyMsgByClientSeqAndTime(peer: Peer, clientSeq: string, time: string): unknown;
//cnt clientSeq?并不是吧
getMsgsByTypeFilter(peer: Peer, msgId: string, cnt: unknown, queryOrder: boolean, typeFilter: { type: number, subtype: Array<number> }): unknown;
getMsgsByTypeFilters(peer: Peer, msgId: string, cnt: unknown, queryOrder: boolean, typeFilters: Array<{ type: number, subtype: Array<number> }>): unknown;
@@ -214,19 +227,7 @@ export interface NodeIKernelMsgService {
* @param param.isIncludeCurrent 是否包含当前页码。
* @returns 返回一个Promise解析为查询结果的未知类型对象。
*/
queryMsgsWithFilterVer2(MsgId: string, MsgTime: string, param: {
chatInfo: {
chatType: number,
peerUid: string
},
filterMsgType: [],
filterSendersUid: Array<string>,
filterMsgFromTime: string,
filterMsgToTime: string,
pageLimit: number,
isReverseOrder: boolean,
isIncludeCurrent: boolean
}): Promise<unknown>;
queryMsgsWithFilterVer2(MsgId: string, MsgTime: string, param: QueryMsgsParams): Promise<unknown>;
// this.chatType = i2;
// this.peerUid = str;
@@ -243,19 +244,7 @@ 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: string[],
filterMsgFromTime: string,
filterMsgToTime: string,
pageLimit: number,
isReverseOrder: boolean,
isIncludeCurrent: boolean
}): Promise<GeneralCallResult & {
queryMsgsWithFilterEx(msgId: string, msgTime: string, megSeq: string, param: QueryMsgsParams): Promise<GeneralCallResult & {
msgList: RawMessage[]
}>;
//queryMsgsWithFilterEx(this.$msgId, this.$msgTime, this.$msgSeq, this.$param)
@@ -263,39 +252,15 @@ export interface NodeIKernelMsgService {
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(msgId: string, msgTime: string, megSeq: string, param: {
chatInfo: {
chatType: number,
peerUid: string
},
filterMsgType: [],
filterSendersUid: [],
filterMsgFromTime: string,
filterMsgToTime: string,
pageLimit: number,
isReverseOrder: boolean,
isIncludeCurrent: boolean
}): unknown;
queryMsgsAndAbstractsWithFilter(msgId: string, msgTime: string, megSeq: string, param: QueryMsgsParams): unknown;
setFocusOnGuild(...args: unknown[]): unknown;
@@ -361,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;
@@ -393,8 +358,35 @@ export interface NodeIKernelMsgService {
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;
@@ -485,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;
@@ -507,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;
@@ -547,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;
@@ -569,7 +561,7 @@ export interface NodeIKernelMsgService {
getCurChatImportStatusByUin(...args: unknown[]): unknown;
getDataImportUserLevel(...args: unknown[]): unknown;
getDataImportUserLevel(): unknown;
getMsgQRCode(...args: unknown[]): unknown;
@@ -600,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;
@@ -624,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;
@@ -664,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;

View File

@@ -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;

View File

@@ -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,15 +154,20 @@ 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>;
downloadFileByUrlList(arg1: unknown, arg2: unknown): unknown;
downloadFileByUrlList(fileDownloadTyp: UrlFileDownloadType, urlList: Array<string>): unknown;
downloadFileForFileInfo(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;
createGroupFolder(arg1: unknown, arg2: unknown): unknown;
@@ -124,8 +200,6 @@ export interface NodeIKernelRichMediaService {
getGroupFileInfo(arg1: unknown, arg2: unknown): unknown;
getGroupFileList(arg1: unknown, arg2: unknown): unknown;
getGroupTransferList(arg1: unknown, arg2: unknown): unknown;
renameGroupFile(arg1: unknown, arg2: unknown, arg3: unknown, arg4: unknown, arg5: unknown): unknown;
@@ -133,7 +207,7 @@ export interface NodeIKernelRichMediaService {
moveGroupFile(arg1: unknown, arg2: unknown, arg3: unknown, arg4: unknown, arg5: unknown): unknown;
transGroupFile(arg1: unknown, arg2: unknown): unknown;
searchGroupFile(
keywords: Array<string>,
param: {
@@ -168,13 +242,25 @@ export interface NodeIKernelRichMediaService {
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;
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;
}

View File

@@ -1,79 +1,129 @@
import { ChatType } from "../entities";
export interface NodeIKernelSearchService {
addKernelSearchListenerr(...args: any[]): unknown;// needs 1 arguments
addKernelSearchListener(...args: any[]): unknown;// needs 1 arguments
removeKernelSearchListenerr(...args: any[]): unknown;// needs 1 arguments
removeKernelSearchListener(...args: any[]): unknown;// needs 1 arguments
searchStrangerr(...args: any[]): unknown;// needs 3 arguments
searchStranger(...args: any[]): unknown;// needs 3 arguments
searchGroupr(...args: any[]): unknown;// needs 1 arguments
searchGroup(...args: any[]): unknown;// needs 1 arguments
searchLocalInfor(...args: any[]): unknown;// needs 2 arguments
searchLocalInfo(keywords: string, unknown: number/*4*/): unknown;
cancelSearchLocalInfor(...args: any[]): unknown;// needs 3 arguments
cancelSearchLocalInfo(...args: any[]): unknown;// needs 3 arguments
searchBuddyChatInfor(...args: any[]): unknown;// needs 2 arguments
searchBuddyChatInfo(...args: any[]): unknown;// needs 2 arguments
searchMoreBuddyChatInfor(...args: any[]): unknown;// needs 1 arguments
searchMoreBuddyChatInfo(...args: any[]): unknown;// needs 1 arguments
cancelSearchBuddyChatInfor(...args: any[]): unknown;// needs 3 arguments
cancelSearchBuddyChatInfo(...args: any[]): unknown;// needs 3 arguments
searchContactr(...args: any[]): unknown;// needs 2 arguments
searchContact(...args: any[]): unknown;// needs 2 arguments
searchMoreContactr(...args: any[]): unknown;// needs 1 arguments
searchMoreContact(...args: any[]): unknown;// needs 1 arguments
cancelSearchContactr(...args: any[]): unknown;// needs 3 arguments
cancelSearchContact(...args: any[]): unknown;// needs 3 arguments
searchGroupChatInfor(...args: any[]): unknown;// needs 3 arguments
searchGroupChatInfo(...args: any[]): unknown;// needs 3 arguments
resetSearchGroupChatInfoSortTyper(...args: any[]): unknown;// needs 3 arguments
resetSearchGroupChatInfoSortType(...args: any[]): unknown;// needs 3 arguments
resetSearchGroupChatInfoFilterMembersr(...args: any[]): unknown;// needs 3 arguments
resetSearchGroupChatInfoFilterMembers(...args: any[]): unknown;// needs 3 arguments
searchMoreGroupChatInfor(...args: any[]): unknown;// needs 1 arguments
searchMoreGroupChatInfo(...args: any[]): unknown;// needs 1 arguments
cancelSearchGroupChatInfor(...args: any[]): unknown;// needs 3 arguments
cancelSearchGroupChatInfo(...args: any[]): unknown;// needs 3 arguments
searchChatsWithKeywordsr(...args: any[]): unknown;// needs 3 arguments
searchChatsWithKeywords(...args: any[]): unknown;// needs 3 arguments
searchMoreChatsWithKeywordsr(...args: any[]): unknown;// needs 1 arguments
searchMoreChatsWithKeywords(...args: any[]): unknown;// needs 1 arguments
cancelSearchChatsWithKeywordsr(...args: any[]): unknown;// needs 3 arguments
cancelSearchChatsWithKeywords(...args: any[]): unknown;// needs 3 arguments
searchChatMsgsr(...args: any[]): unknown;// needs 2 arguments
searchChatMsgs(...args: any[]): unknown;// needs 2 arguments
searchMoreChatMsgsr(...args: any[]): unknown;// needs 1 arguments
searchMoreChatMsgs(...args: any[]): unknown;// needs 1 arguments
cancelSearchChatMsgsr(...args: any[]): unknown;// needs 3 arguments
cancelSearchChatMsgs(...args: any[]): unknown;// needs 3 arguments
searchMsgWithKeywordsr(...args: any[]): unknown;// needs 2 arguments
searchMsgWithKeywords(...args: any[]): unknown;// needs 2 arguments
searchMoreMsgWithKeywordsr(...args: any[]): unknown;// needs 1 arguments
searchMoreMsgWithKeywords(...args: any[]): unknown;// needs 1 arguments
cancelSearchMsgWithKeywords(...args: any[]): unknown;// needs 3 arguments
searchFileWithKeywords(keywords: string[], source: number): Promise<unknown>;// needs 2 arguments
searchFileWithKeywords(keywords: string[], source: number): Promise<string>;// needs 2 arguments
searchMoreFileWithKeywordsr(...args: any[]): unknown;// needs 1 arguments
searchMoreFileWithKeywords(...args: any[]): unknown;// needs 1 arguments
cancelSearchFileWithKeywordsr(...args: any[]): unknown;// needs 3 arguments
cancelSearchFileWithKeywords(...args: any[]): unknown;// needs 3 arguments
searchAtMeChatsr(...args: any[]): unknown;// needs 3 arguments
searchAtMeChats(...args: any[]): unknown;// needs 3 arguments
searchMoreAtMeChatsr(...args: any[]): unknown;// needs 1 arguments
searchMoreAtMeChats(...args: any[]): unknown;// needs 1 arguments
cancelSearchAtMeChatsr(...args: any[]): unknown;// needs 3 arguments
cancelSearchAtMeChats(...args: any[]): unknown;// needs 3 arguments
searchChatAtMeMsgsr(...args: any[]): unknown;// needs 1 arguments
searchChatAtMeMsgs(...args: any[]): unknown;// needs 1 arguments
searchMoreChatAtMeMsgsr(...args: any[]): unknown;// needs 1 arguments
searchMoreChatAtMeMsgs(...args: any[]): unknown;// needs 1 arguments
cancelSearchChatAtMeMsgsr(...args: any[]): unknown;// needs 3 arguments
cancelSearchChatAtMeMsgs(...args: any[]): unknown;// needs 3 arguments
addSearchHistoryr(...args: any[]): unknown;// needs 1 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
}
>
}
]
removeSearchHistoryr(...args: any[]): unknown;// needs 1 arguments
}): Promise<{
result: number,
errMsg: string,
id?: number
}>;
searchCacher(...args: any[]): unknown;// needs 3 arguments
removeSearchHistory(...args: any[]): unknown;// needs 1 arguments
clearSearchCacher(...args: any[]): unknown;// needs 1 arguments
searchCache(...args: any[]): unknown;// needs 3 arguments
clearSearchCache(...args: any[]): unknown;// needs 1 arguments
}

View 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>;
}

View File

@@ -41,6 +41,8 @@ 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);
@@ -156,6 +158,10 @@ export interface NodeIQQNTWrapperSession {
startNT(): void;
getBdhUploadService(): unknown;
getECDHService(): NodeIKernelECDHService;
getMsgService(): NodeIKernelMsgService;
getProfileService(): NodeIKernelProfileService;
@@ -210,7 +216,7 @@ export interface NodeIQQNTWrapperSession {
getSkinService(): unknown;
getTestPerformanceService(): unknown;
getTestPerformanceService(): NodeIkernelTestPerformanceService;
getQQPlayService(): unknown;

View 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);
}
}

View 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);
}
}

View File

@@ -1,15 +1,12 @@
import BaseAction from '../BaseAction';
import fs from 'fs/promises';
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, NTQQMsgApi } 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 { MessageUnique } from '@/common/utils/MessageUnique';
import { getGroup } from '@/core/data';
export interface GetFilePayload {
file: string; // 文件名或者fileUuid
@@ -45,6 +42,102 @@ export class GetFileBase extends BaseAction<GetFilePayload, GetFileResponse> {
return { id: element.elementId, element: element.fileElement };
}
protected async _handle(payload: GetFilePayload): Promise<GetFileResponse> {
const { enableLocalFile2Url } = ob11Config;
let UuidData: {
high: string;
low: string;
} | undefined;
try {
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 {
}
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(downloadPath, 'base64');
} catch (e) {
throw new Error('文件下载失败. ' + e);
}
}
//不手动删除?文件持久化了
return res;
}
throw new Error('file not found');
// let cache = await dbUtil.getFileCacheByName(payload.file);
// if (!cache) {

View File

@@ -2,7 +2,7 @@ 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 { ChatType, RawMessage } from '@/core/entities';
import { NTQQMsgApi } from '@/core/apis/msg';
import { OB11Constructor } from '../../constructor';
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
@@ -18,7 +18,7 @@ const SchemaData = {
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>;
@@ -31,14 +31,18 @@ export default class GoCQHTTPGetGroupMsgHistory extends BaseAction<Payload, Resp
if (!group) {
throw `${payload.group_id}不存在`;
}
const startMsgId = (await MessageUnique.getMsgIdAndPeerByShortId(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 MessageUnique.createMsg({ guildId: '', chatType: msg.chatType, peerUid: msg.peerUid }, msg.msgId);
}));

View 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;
}
}

View File

@@ -59,9 +59,9 @@ class GetGroupMemberInfo extends BaseAction<Payload, OB11GroupMember> {
}
}
} else {
let LastestMsgList = await NTQQGroupApi.getLastestMsg(payload.group_id.toString(), [payload.user_id.toString()]);
const LastestMsgList = await NTQQGroupApi.getLastestMsg(payload.group_id.toString(), [payload.user_id.toString()]);
if (LastestMsgList?.msgList?.length && LastestMsgList?.msgList?.length > 0) {
let last_send_time = LastestMsgList.msgList[0].msgTime;
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);//兜底数据 防止群管乱杀

View File

@@ -39,7 +39,7 @@ class GetGroupMemberList extends BaseAction<Payload, OB11GroupMember[]> {
const MemberMap: Map<number, OB11GroupMember> = new Map<number, OB11GroupMember>();
// 转为Map 方便索引
let date = Math.round(Date.now() / 1000);
const date = Math.round(Date.now() / 1000);
for (let i = 0, len = _groupMembers.length; i < len; i++) {
// 保证基础数据有这个 同时避免群管插件过于依赖这个杀了
_groupMembers[i].join_time = date;
@@ -65,7 +65,7 @@ class GetGroupMemberList extends BaseAction<Payload, OB11GroupMember[]> {
}
} else {
const DateMap = await NTQQGroupApi.getGroupMemberLastestSendTimeCache(payload.group_id.toString());//开始从本地拉取
for (let DateUin of DateMap.keys()) {
for (const DateUin of DateMap.keys()) {
const MemberData = MemberMap.get(parseInt(DateUin));
if (MemberData) {
MemberData.last_sent_time = parseInt(DateMap.get(DateUin)!);

View File

@@ -75,6 +75,9 @@ 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(),
@@ -156,9 +159,11 @@ export const actionHandlers = [
new GetRecentContact(),
new MarkAllMsgAsRead(),
new GetProfileLike(),
new SetGroupHeader()
new SetGroupHeader(),
new FetchCustomFace(),
new GoCQHTTPUploadPrivateFile(),
new TestApi01()
];
function initActionMap() {
const actionMap = new Map<string, BaseAction<any, any>>();
for (const action of actionHandlers) {

View File

@@ -32,10 +32,18 @@ class GetMsg extends BaseAction<Payload, OB11Message> {
if (!msgIdWithPeer) {
throw ('消息不存在');
}
const peer = { guildId: '', peerUid: msgIdWithPeer?.Peer.peerUid, chatType: msgIdWithPeer.Peer.chatType };
const msg = await NTQQMsgApi.getMsgsByMsgId(
{ guildId: '', peerUid: msgIdWithPeer?.Peer.peerUid, chatType: msgIdWithPeer.Peer.chatType },
peer,
[msgIdWithPeer?.MsgId || payload.message_id.toString()]);
return await OB11Constructor.message(msg.msgList[0]);
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;
}
}

View File

@@ -12,45 +12,22 @@ import {
SignMusicWrapper
} from '@/core';
import { getGroupMember } from '@/core/data';
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
) {
const 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 fileMsgPeer = MessageUnique.getPeerByMsgId(cache.msgId);
// if (fileMsgPeer) {
// cache.path = await NTQQFileApi.downloadMedia(
// fileMsgPeer.MsgId, fileMsgPeer.Peer.chatType, fileMsgPeer.Peer.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);
@@ -61,7 +38,7 @@ async function handleOb11FileLikeMessage(
deleteAfterSentFiles.push(path);
}
return { path, fileName: payloadFileName || fileName };
return { path, fileName: inputdata.name || fileName };
}
const _handlers: {
@@ -85,9 +62,12 @@ const _handlers: {
SendMsgElementConstructor.at(atQQ, atMember.uid, AtType.atUser, atMember.cardName || atMember.nick) :
undefined;
},
[OB11MessageDataType.reply]: async ({ data: { 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!) :
@@ -230,7 +210,7 @@ export default async function createSendElements(
ignoreTypes: OB11MessageDataType[] = []
) {
const deleteAfterSentFiles: string[] = [];
let callResultList: Array<Promise<SendMessageElement | undefined>> = [];
const callResultList: Array<Promise<SendMessageElement | undefined>> = [];
for (const sendMsg of messageData) {
if (ignoreTypes.includes(sendMsg.type)) {
continue;
@@ -241,7 +221,7 @@ export default async function createSendElements(
)?.catch(undefined);
callResultList.push(callResult);
}
let ret = await Promise.all(callResultList);
const ret = await Promise.all(callResultList);
const sendElements: SendMessageElement[] = ret.filter(ele => ele) as SendMessageElement[];
return { sendElements, deleteAfterSentFiles };
}

View File

@@ -127,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('需要克隆转发消息');

View File

@@ -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,7 +59,7 @@ 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);

View File

@@ -7,8 +7,8 @@ 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;
@@ -23,10 +23,13 @@ export class SetMsgEmojiLike extends BaseAction<Payload, any> {
if (!msg) {
throw new Error('msg not found');
}
if (!payload.emoji_id){
if (!payload.emoji_id) {
throw new Error('emojiId not found');
}
const msgSeq = (await NTQQMsgApi.getMsgsByMsgId(msg.Peer, [msg.MsgId])).msgList[0].msgSeq;
return await NTQQMsgApi.setEmojiLike(msg.Peer, 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);
}
}

View File

@@ -99,5 +99,8 @@ export enum ActionName {
GetRecentContact = 'get_recent_contact',
_MarkAllMsgAsRead = '_mark_all_as_read',
GetProfileLike = 'get_profile_like',
SetGroupHeader = "set_group_head"
SetGroupHeader = 'set_group_head',
FetchCustomFace = 'fetch_custom_face',
GOCQHTTP_UploadPrivateFile = 'upload_private_file',
TestApi01 = 'test_api_01'
}

View File

@@ -42,7 +42,7 @@ export interface OB11Config {
}
class Config extends ConfigBase<OB11Config> implements OB11Config {
name: string = 'onebot11'
name: string = 'onebot11';
http = {
enable: false,
host: '',

View File

@@ -12,18 +12,19 @@ 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';
@@ -33,8 +34,8 @@ import { OB11GroupUploadNoticeEvent } from './event/notice/OB11GroupUploadNotice
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';
@@ -43,6 +44,7 @@ import { deleteGroup, getGroupMember, groupMembers, selfInfo, tempGroupCodeMap }
import { NTQQFileApi, NTQQGroupApi, NTQQMsgApi, NTQQUserApi } from '@/core/apis';
import { OB11GroupMsgEmojiLikeEvent } from '@/onebot11/event/notice/OB11MsgEmojiLikeEvent';
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';
@@ -75,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;
@@ -94,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()) {
@@ -133,41 +147,42 @@ 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 replyMsg = await NTQQMsgApi.getMsgsBySeqAndCount(
{
chatType: msg.chatType,
peerUid: msg.peerUid,
guildId: '',
},
element.replyElement.replayMsgSeq,
1,
true,
true
);
// console.log(JSON.stringify(retData, null, 2));
// const replyMsg = await NTQQMsgApi.getMsgsBySeqAndCount({ peerUid: msg.peerUid, guildId: '', chatType: msg.chatType }, element.replyElement.replayMsgSeq, 1, true, true);
if (replyMsg) {
message_data['data']['id'] = MessageUnique.createMsg({ peerUid: msg.peerUid, guildId: '', chatType: msg.chatType }, replyMsg.msgList[0].msgId)?.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);
@@ -177,63 +192,97 @@ export class OB11Constructor {
//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;
@@ -277,15 +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;
@@ -298,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事件
@@ -306,11 +357,17 @@ 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));
}
}
}
}
}

View File

@@ -67,15 +67,15 @@ export function encodeCQCode(data: OB11MessageData) {
let result = '[CQ:' + data.type;
for (const name in data.data) {
const value = data.data[name];
try {
// Check if the value can be converted to a string
value.toString();
} catch (error) {
// If it can't be converted, skip this name-value pair
// console.warn(`Skipping problematic name-value pair. Name: ${name}, Value: ${value}`);
if (value === undefined) {
continue;
}
result += `,${name}=${CQCodeEscape(value)}`;
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;

View 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;
}
}

View File

@@ -10,22 +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;
raw_message: any;
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_message = raw_message;
this.raw_info = raw_message;
}
}

View File

@@ -41,7 +41,7 @@ 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>();
@@ -240,16 +240,25 @@ export class NapCatOnebot11 {
};
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) {
msg.id = MessageUnique.createMsg({ chatType: msg.chatType, peerUid: msg.peerUid, guildId: '' }, msg.msgId);
this.postReceiveMsg([msg]).then().catch(logError);
}
};
napCatCore.addListener(msgListener);
logDebug('ob11 msg listener added');
@@ -323,11 +332,12 @@ export class NapCatOnebot11 {
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) => {
@@ -415,7 +425,7 @@ export class NapCatOnebot11 {
if (check_http_ws_equal(NewOb11) || check_http_ws_equal(OldConfig)) {
// http与ws共站 需要同步重启
if (isHttpChanged || isWsChanged) {
log("http与ws进行热重载")
log('http与ws进行热重载');
ob11WebsocketServer.stop();
ob11HTTPServer.stop();
if (NewOb11.http.enable) {
@@ -432,7 +442,7 @@ export class NapCatOnebot11 {
} else {
// http重启逻辑
if (isHttpChanged) {
log("http进行热重载")
log('http进行热重载');
ob11HTTPServer.stop();
if (NewOb11.http.enable) {
ob11HTTPServer.start(NewOb11.http.port, NewOb11.http.host);
@@ -441,7 +451,7 @@ export class NapCatOnebot11 {
// ws重启逻辑
if (isWsChanged) {
log("ws进行热重载")
log('ws进行热重载');
ob11WebsocketServer.stop();
if (NewOb11.ws.enable) {
ob11WebsocketServer.start(NewOb11.ws.port, NewOb11.ws.host);
@@ -451,7 +461,7 @@ export class NapCatOnebot11 {
// 反向ws重启逻辑
if (isWsReverseChanged) {
log("反向ws进行热重载")
log('反向ws进行热重载');
ob11ReverseWebsockets.stop();
if (NewOb11.reverseWs.enable) {
ob11ReverseWebsockets.start();
@@ -514,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);
@@ -583,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();

View File

@@ -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'

View File

@@ -1 +1 @@
export const version = '1.6.7';
export const version = '1.7.6';

View File

@@ -27,18 +27,19 @@ export async function InitWebUi() {
}
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}`);
});
}
}

View File

@@ -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();

View File

@@ -15,7 +15,7 @@ async function onSettingWindowCreated(view: Element) {
} else if (configKey.length === 3) {
ob11Config[configKey[1]][configKey[2]] = value;
}
OB11ConfigWrapper.SetOB11Config(ob11Config);
// OB11ConfigWrapper.SetOB11Config(ob11Config); // 只有当点保存时才下发配置,而不是在修改值后立即下发
};
const parser = new DOMParser();
@@ -29,7 +29,7 @@ async function onSettingWindowCreated(view: Element) {
SettingItem(
'<span id="napcat-update-title">Napcat</span>',
undefined,
SettingButton('V1.6.8', 'napcat-update-button', 'secondary')
SettingButton('V1.7.6', 'napcat-update-button', 'secondary')
),
]),
SettingList([
@@ -192,7 +192,7 @@ async function onSettingWindowCreated(view: Element) {
// 外链按钮
doc.querySelector('#open-github')?.addEventListener('click', () => {
window.open('https://napneko.github.io/', '_blank');
window.open('https://github.com/NapNeko/NapCatQQ', '_blank');
});
doc.querySelector('#open-telegram')?.addEventListener('click', () => {
window.open('https://t.me/+nLZEnpne-pQ1OWFl');
@@ -201,7 +201,7 @@ async function onSettingWindowCreated(view: Element) {
window.open('https://qm.qq.com/q/bDnHRG38aI');
});
doc.querySelector('#open-docs')?.addEventListener('click', () => {
window.open('https://github.com/NapNeko/NapCatQQ');
window.open('https://napneko.github.io/', '_blank');
});
// 生成反向地址列表
const buildHostListItem = (

View File

@@ -38,7 +38,7 @@ class WebUiApiOB11ConfigWrapper {
this.retCredential = Credential;
}
async GetOB11Config(): Promise<OB11Config> {
const ConfigResponse = await fetch('/api/OB11Config/GetConfig', {
const ConfigResponse = await fetch('../api/OB11Config/GetConfig', {
method: 'POST',
headers: {
Authorization: 'Bearer ' + this.retCredential,
@@ -54,7 +54,7 @@ class WebUiApiOB11ConfigWrapper {
return {} as OB11Config;
}
async SetOB11Config(config: OB11Config): Promise<boolean> {
const ConfigResponse = await fetch('/api/OB11Config/SetConfig', {
const ConfigResponse = await fetch('../api/OB11Config/SetConfig', {
method: 'POST',
headers: {
Authorization: 'Bearer ' + this.retCredential,

View File

@@ -1,5 +1,7 @@
{
"host": "0.0.0.0",
"port": 6099,
"prefix": "",
"token": "random",
"loginRate": 3

View File

@@ -160,7 +160,7 @@
</div>
<script>
async function GetQQLoginQrcode(retCredential) {
let QQLoginResponse = await fetch('/api/QQLogin/GetQQLoginQrcode', {
let QQLoginResponse = await fetch('../api/QQLogin/GetQQLoginQrcode', {
method: 'POST',
headers: {
'Authorization': "Bearer " + retCredential,
@@ -180,7 +180,7 @@
return "";
}
async function CheckQQLoginStatus(retCredential) {
let QQLoginResponse = await fetch('/api/QQLogin/CheckLoginStatus', {
let QQLoginResponse = await fetch('../api/QQLogin/CheckLoginStatus', {
method: 'POST',
headers: {
'Authorization': "Bearer " + retCredential,
@@ -200,7 +200,7 @@
return false;
}
async function GetQQQucickLoginList(retCredential) {
let QQLoginResponse = await fetch('/api/QQLogin/GetQuickLoginList', {
let QQLoginResponse = await fetch('../api/QQLogin/GetQuickLoginList', {
method: 'POST',
headers: {
'Authorization': "Bearer " + retCredential,
@@ -216,7 +216,7 @@
return [];
}
async function SetQuickLogin(uin, retCredential) {
let QQLoginResponse = await fetch('/api/QQLogin/SetQuickLogin', {
let QQLoginResponse = await fetch('../api/QQLogin/SetQuickLogin', {
method: 'POST',
headers: {
'Authorization': "Bearer " + retCredential,

View File

@@ -1,7 +1,7 @@
const SettingList = (items, title, isCollapsible = false, direction = "column") => {
return `<setting-section ${title && !isCollapsible ? `data-title="${title}"` : ""}>
return `<setting-section ${""}>
<setting-panel>
<setting-list ${direction ? `data-direction="${direction}"` : ""} ${isCollapsible ? "is-collapsible" : ""} ${title && isCollapsible ? `data-title="${title}"` : ""}>
<setting-list ${direction ? `data-direction="${direction}"` : ""} ${isCollapsible ? "is-collapsible" : ""} ${""}>
${items.join("")}
</setting-list>
</setting-panel>
@@ -66,16 +66,13 @@ window.customElements.define(
window[`${isHidden ? "remove" : "add"}EventListener`]("pointerdown", windowPointerDown);
};
const windowPointerDown = ({ target }) => {
if (!this.contains(target))
buttonClick();
if (!this.contains(target)) buttonClick();
};
this._button.addEventListener("click", buttonClick);
this._context.addEventListener("click", ({ target }) => {
if (target.tagName !== "SETTING-OPTION")
return;
if (target.tagName !== "SETTING-OPTION") return;
buttonClick();
if (target.hasAttribute("is-selected"))
return;
if (target.hasAttribute("is-selected")) return;
this.querySelectorAll("setting-option[is-selected]").forEach((dom) => dom.toggleAttribute("is-selected"));
target.toggleAttribute("is-selected");
this._text.value = target.textContent;
@@ -95,9 +92,9 @@ window.customElements.define(
}
);
const SettingSelect = (items, configKey, configValue) => {
return `<ob-setting-select ${configKey ? `data-config-key="${configKey}"` : ""}>
return `<ob-setting-select ${`data-config-key="${configKey}"` }>
${items.map((e, i) => {
return SettingOption(e.text, e.value, configKey && configValue ? configValue === e.value : i === 0);
return SettingOption(e.text, e.value, configValue ? configValue === e.value : i === 0);
}).join("")}
</ob-setting-select>`;
};
@@ -108,7 +105,7 @@ class WebUiApiOB11ConfigWrapper {
this.retCredential = Credential;
}
async GetOB11Config() {
const ConfigResponse = await fetch("/api/OB11Config/GetConfig", {
const ConfigResponse = await fetch("../api/OB11Config/GetConfig", {
method: "POST",
headers: {
Authorization: "Bearer " + this.retCredential,
@@ -124,7 +121,7 @@ class WebUiApiOB11ConfigWrapper {
return {};
}
async SetOB11Config(config) {
const ConfigResponse = await fetch("/api/OB11Config/SetConfig", {
const ConfigResponse = await fetch("../api/OB11Config/SetConfig", {
method: "POST",
headers: {
Authorization: "Bearer " + this.retCredential,
@@ -154,7 +151,6 @@ async function onSettingWindowCreated(view) {
} else if (configKey.length === 3) {
ob11Config[configKey[1]][configKey[2]] = value;
}
OB11ConfigWrapper.SetOB11Config(ob11Config);
};
const parser = new DOMParser();
const doc = parser.parseFromString(
@@ -167,7 +163,7 @@ async function onSettingWindowCreated(view) {
SettingItem(
'<span id="napcat-update-title">Napcat</span>',
void 0,
SettingButton("V1.6.8", "napcat-update-button", "secondary")
SettingButton("V1.7.6", "napcat-update-button", "secondary")
)
]),
SettingList([
@@ -326,7 +322,7 @@ async function onSettingWindowCreated(view) {
"text/html"
);
doc.querySelector("#open-github")?.addEventListener("click", () => {
window.open("https://napneko.github.io/", "_blank");
window.open("https://github.com/NapNeko/NapCatQQ", "_blank");
});
doc.querySelector("#open-telegram")?.addEventListener("click", () => {
window.open("https://t.me/+nLZEnpne-pQ1OWFl");
@@ -335,7 +331,7 @@ async function onSettingWindowCreated(view) {
window.open("https://qm.qq.com/q/bDnHRG38aI");
});
doc.querySelector("#open-docs")?.addEventListener("click", () => {
window.open("https://github.com/NapNeko/NapCatQQ");
window.open("https://napneko.github.io/", "_blank");
});
const buildHostListItem = (type, host, index, inputAttrs = {}) => {
const dom = {
@@ -431,19 +427,15 @@ async function onSettingWindowCreated(view) {
dom.addEventListener("click", () => {
const active = dom.getAttribute("is-active") == void 0;
setOB11Config(dom.dataset.configKey, active);
if (active)
dom.setAttribute("is-active", "");
else
dom.removeAttribute("is-active");
if (active) dom.setAttribute("is-active", "");
else dom.removeAttribute("is-active");
if (!isEmpty(dom.dataset.controlDisplayId)) {
const displayDom = document.querySelector(
//@ts-expect-error 等待修复
`#${dom.dataset.controlDisplayId}`
);
if (active)
displayDom?.removeAttribute("is-hidden");
else
displayDom?.setAttribute("is-hidden", "");
if (active) displayDom?.removeAttribute("is-hidden");
else displayDom?.setAttribute("is-hidden", "");
}
});
});

View File

@@ -10,7 +10,7 @@
<body>
<script>
async function CheckQQLoginStatus(retCredential) {
let QQLoginResponse = await fetch('/api/QQLogin/CheckLoginStatus', {
let QQLoginResponse = await fetch('../api/QQLogin/CheckLoginStatus', {
method: 'POST',
headers: {
'Authorization': "Bearer " + retCredential,
@@ -30,7 +30,7 @@
return false;
}
async function CheckWebUiLogined(retCredential) {
let LoginResponse = await fetch('/api/auth/check', {
let LoginResponse = await fetch('../api/auth/check', {
method: 'POST',
headers: {
'Authorization': "Bearer " + retCredential,

View File

@@ -86,7 +86,7 @@
let data = "";
try {
let loginResponse = await fetch('/api/auth/login', {
let loginResponse = await fetch('../api/auth/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
@@ -99,7 +99,7 @@
//登录成功
let retCredential = loginResponseJson.data.Credential;
localStorage.setItem('auth', retCredential);
let QQLoginResponse = await fetch('/api/QQLogin/CheckLoginStatus', {
let QQLoginResponse = await fetch('../api/QQLogin/CheckLoginStatus', {
method: 'POST',
headers: {
'Authorization': "Bearer " + retCredential,

View File

@@ -1,6 +1,5 @@
describe('MessageUniqueWrapper', () => {
let messageUniqueWrapper: MessageUniqueWrapper;
let messageUniqueWrapper: MessageUniqueWrapper;
beforeEach(() => {
messageUniqueWrapper = new MessageUniqueWrapper();
});
@@ -91,4 +90,4 @@ describe('MessageUniqueWrapper', () => {
expect(shortId).toBeUndefined();
});
});
});

View File

@@ -26,9 +26,9 @@ if (process.env.NAPCAT_BUILDSYS == 'linux') {
} else if (process.env.NAPCAT_BUILDSYS == 'win32') {
if (process.env.NAPCAT_BUILDARCH == 'x64') {
}
startScripts = ['./script/napcat.ps1', './script/napcat.bat', './script/napcat-utf8.bat', './script/napcat-utf8.ps1', './script/napcat-log.ps1', './script/NapCat.164.bat', './script/napcat-9912.ps1', './script/napcat-9912-utf8.ps1', './script/napcat-9912.bat', './script/napcat-9912-utf8.bat'];
startScripts = ['./script/dbghelp.dll', './script/BootWay05.ps1', './script/napcat-9912.ps1', './script/napcat-9912-utf8.ps1', './script/napcat-9912.bat', './script/napcat-9912-utf8.bat'];
} else {
startScripts = ['./script/napcat.sh', './script/napcat.ps1', './script/napcat.bat', './script/napcat-utf8.bat', './script/napcat-utf8.ps1', './script/napcat-log.ps1', './script/napcat-9912.ps1', './script/napcat-9912-utf8.ps1', './script/napcat-9912.bat', './script/napcat-9912-utf8.bat'];
startScripts = ['./script/dbghelp.dll', './script/BootWay05.ps1', './script/napcat.sh', './script/napcat.ps1', './script/napcat.bat', './script/napcat-utf8.bat', './script/napcat-utf8.ps1', './script/napcat-log.ps1', './script/napcat-9912.ps1', './script/napcat-9912-utf8.ps1', './script/napcat-9912.bat', './script/napcat-9912-utf8.bat'];
}
const baseConfigPlugin: PluginOption[] = [