Compare commits

...

106 Commits

Author SHA1 Message Date
手瓜一十雪
717b246cb6 release: 1.8.5 2024-08-09 21:38:36 +08:00
手瓜一十雪
827df80ec8 fix: error 2024-08-09 20:34:31 +08:00
手瓜一十雪
7117fae2b2 chore: wait for v2 2024-08-09 19:41:40 +08:00
手瓜一十雪
15e9462140 LICENSE: MPL2 To BSD 2024-08-09 19:40:35 +08:00
手瓜一十雪
9203fa3df2 Merge pull request #230 from canxin121/main
Fix typo in eventType field name
2024-08-09 13:35:45 +08:00
canxin121
c771d75a00 Fix typo in eventType field name 2024-08-09 13:12:11 +08:00
手瓜一十雪
7b10d75aeb chore: 1.8.4 2024-08-09 10:48:13 +08:00
Wesley F. Young
b46459de5f docs: 给 v2 引流 2024-08-08 23:15:28 +08:00
Wesley F. Young
ac7f025223 docs: 进化的语言中枢 2024-08-08 23:13:21 +08:00
手瓜一十雪
05b77b5042 build: test 2024-08-08 11:11:38 +08:00
手瓜一十雪
d6cfe11d97 build: test 2024-08-08 10:59:49 +08:00
手瓜一十雪
dd4d59e4e7 build: test 2024-08-08 10:50:36 +08:00
手瓜一十雪
7cb8626e16 build:test 2024-08-08 10:29:53 +08:00
手瓜一十雪
89b5202adb Merge pull request #224 from NapNeko/newold
build: test
2024-08-08 10:28:19 +08:00
手瓜一十雪
40ae0c1449 build: test 2024-08-08 10:26:49 +08:00
手瓜一十雪
81a8115c56 release:1.8.3 2024-08-07 01:32:48 +08:00
手瓜一十雪
0ddd26bd51 release: 1.8.3 2024-08-07 01:28:22 +08:00
手瓜一十雪
69e2133a27 chore: BootWay05Script 2024-08-07 01:25:02 +08:00
手瓜一十雪
b04f85949b docs: todo 2024-08-06 23:42:21 +08:00
手瓜一十雪
667dba01ae build: OB11InputStatusEvent 2024-08-06 23:39:22 +08:00
手瓜一十雪
85a8cef628 chore: 移除部分无用内容 2024-08-06 21:14:11 +08:00
手瓜一十雪
3099acfd00 DOCS:TODO 2024-08-06 20:46:17 +08:00
手瓜一十雪
b00fe0b5f8 docs: 标记TODO和兜底接口 2024-08-06 20:38:49 +08:00
手瓜一十雪
9a64b8bdb6 release:1.8.2 2024-08-06 20:10:01 +08:00
手瓜一十雪
c01e493bd2 feat: 多层合并完成 2024-08-06 18:55:36 +08:00
手瓜一十雪
890236af23 chore: 去除风控延迟 2024-08-06 18:01:36 +08:00
手瓜一十雪
ea678d805d chore: 多层合并转发初步可用 2024-08-06 17:58:48 +08:00
手瓜一十雪
29699418ff fix: 对漏掉的部分加入 2024-08-06 17:02:26 +08:00
手瓜一十雪
87bb36c39f refactor: SendMessageContext 2024-08-06 17:00:27 +08:00
手瓜一十雪
7cb85ed73c fix: 再次优化nodeid 2024-08-06 16:43:06 +08:00
手瓜一十雪
c647771a6d fix: 合并转发Node节点异常问题 2024-08-06 16:27:06 +08:00
手瓜一十雪
6c3d737219 fix: 合并reply 2024-08-06 16:00:25 +08:00
手瓜一十雪
a1f38fed7a release: v1.8.1 2024-08-06 15:55:59 +08:00
手瓜一十雪
c36bb77286 feat: 合并消息初步解析完成 2024-08-06 14:48:20 +08:00
手瓜一十雪
1a1acdc3c9 feat: 转发着色 2024-08-06 11:40:36 +08:00
手瓜一十雪
ef8e60c405 build: 180test 2024-08-06 11:31:48 +08:00
手瓜一十雪
31c1cc47bf build: 180 2024-08-06 11:30:35 +08:00
手瓜一十雪
351fed7359 fix #102 2024-08-06 11:30:12 +08:00
手瓜一十雪
f49e7cbe57 chore: log 2024-08-06 11:15:44 +08:00
手瓜一十雪
7da8ea5e99 style:lint 2024-08-06 11:12:25 +08:00
手瓜一十雪
8fa6a12a7c build: 181test 2024-08-06 11:07:37 +08:00
手瓜一十雪
1070278eaf release: 1.8.0 2024-08-06 09:13:03 +08:00
手瓜一十雪
16b1a6b153 release: 1.7.9 2024-08-05 21:04:49 +08:00
手瓜一十雪
096a7534e0 build: 1.7.9-test 2024-08-05 20:36:08 +08:00
手瓜一十雪
b0897187d2 feat: fetch_emoji_like 2024-08-05 20:33:24 +08:00
手瓜一十雪
885d94882d remove: 😢意外加入的文件 2024-08-05 18:40:39 +08:00
手瓜一十雪
11a3341e13 feat: GetRecentContact 私有标准 2024-08-05 18:39:02 +08:00
手瓜一十雪
231890f78a refactor: msgRandom控制 2024-08-05 17:42:07 +08:00
手瓜一十雪
a272feda6a release: 1.7.8 2024-08-05 13:52:34 +08:00
手瓜一十雪
bc936a0ca7 build: 178发言时间与加入时间完全兜底 2024-08-05 13:45:07 +08:00
手瓜一十雪
28030c2d13 chore: test 2024-08-05 13:01:26 +08:00
手瓜一十雪
7fe9176286 feat: richmsg failed 2024-08-05 12:45:06 +08:00
手瓜一十雪
1105f9b8d6 docs: 补充 2024-08-05 01:11:11 +08:00
手瓜一十雪
e4653defa8 LICENSE: break 2024-08-05 00:48:41 +08:00
Alen
1c62a1e839 Merge pull request #208 from cnxysoft/main
BUG修复
2024-08-05 00:20:17 +08:00
Alen
3a3bbfe201 BUG修复
修复(群聊/私聊)转发合并消息错误的问题
2024-08-05 00:18:42 +08:00
手瓜一十雪
1c38833998 fix: 177 2024-08-04 23:26:51 +08:00
手瓜一十雪
38894177ee fix: get_stranger_info 2024-08-04 22:21:24 +08:00
手瓜一十雪
dce8416942 fix: 177 2024-08-04 21:40:44 +08:00
手瓜一十雪
14219e9b42 release: v1.7.7 2024-08-04 21:23:55 +08:00
手瓜一十雪
7b459e7502 refactor: get_group_member_list 2024-08-04 21:21:36 +08:00
手瓜一十雪
31824c0504 refactor: get_group_list 2024-08-04 20:56:49 +08:00
手瓜一十雪
e203abae85 refactor: /get_group_member_info 2024-08-04 20:53:23 +08:00
手瓜一十雪
faf83b680b 《NTQQ参数全解》 2024-08-04 20:19:34 +08:00
手瓜一十雪
67dcbcb842 refactor: 开始重构群成员信息获取 2024-08-04 20:10:21 +08:00
手瓜一十雪
6533a25404 chore: 移除调试代码 2024-08-04 19:12:18 +08:00
手瓜一十雪
4dc760b0e9 feat: shareDigest 2024-08-04 19:11:44 +08:00
手瓜一十雪
25933b9043 chore: 移除旧代码 2024-08-04 18:50:40 +08:00
手瓜一十雪
a53aaa456e fix: 一处很久很久的看错的的问题 2024-08-04 18:49:40 +08:00
手瓜一十雪
e8a7ea07a5 refactor: Id转换 2024-08-04 18:45:00 +08:00
手瓜一十雪
8817dc6b10 refactor: Uid/Uin转换V2版本 2024-08-04 18:01:31 +08:00
手瓜一十雪
491ec04b46 fix: 准备第二次重构uid/uin 2024-08-04 16:50:23 +08:00
手瓜一十雪
8a5d4a683b feat: getBuddyV2ExWithCate 2024-08-04 16:45:14 +08:00
手瓜一十雪
dfc7c7357a Refactor Api: GetFriendsWithCategory 2024-08-04 16:37:15 +08:00
手瓜一十雪
690a2f7d34 refctor: getBuddyV2 支持分类 2024-08-04 16:27:25 +08:00
手瓜一十雪
58f22b24e4 refactor: api getbuddyv2 2024-08-04 16:05:07 +08:00
手瓜一十雪
3cce9f528b build: 1.7.7 2024-08-04 15:16:45 +08:00
手瓜一十雪
20fd5ac8cb chore: Todo 2024-08-04 15:12:55 +08:00
手瓜一十雪
9e05e086eb fix: 初始化问题 2024-08-04 15:01:43 +08:00
手瓜一十雪
056e0adddf build: 1.7.7-refactor 2024-08-04 14:39:45 +08:00
手瓜一十雪
b36388200d Merge branch 'main' of https://github.com/NapNeko/NapCatQQ 2024-08-04 14:35:47 +08:00
手瓜一十雪
9793e5741a refactor: Appid的获取 2024-08-04 14:35:26 +08:00
手瓜一十雪
143380c012 Merge pull request #206 from pohgxz/main
修复webui快捷登录失败
2024-08-04 14:15:53 +08:00
手瓜一十雪
4b92254945 chore: 订正类型 2024-08-04 14:15:40 +08:00
手瓜一十雪
f9c1d8b4a6 build: 1.7.7 refactor 2024-08-04 14:06:30 +08:00
手瓜一十雪
c0c469339b refactor: versionGet 2024-08-04 14:03:28 +08:00
Nepenthe
0ca6343ed7 修复webui快捷登录失败 2024-08-04 13:04:31 +08:00
手瓜一十雪
3db74c3427 refactor: BuddyList 2024-08-04 12:26:55 +08:00
手瓜一十雪
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
104 changed files with 1998 additions and 881 deletions

37
LICENSE
View File

@@ -1,21 +1,24 @@
MIT License
BSD 2-Clause License
Copyright (c) 2024 NapCatQQ
Copyright (c) 2024, NapNeko
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -1,32 +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. 修复偶现崩溃问题 ~ 修复问题
5. 修复群邀请通知事件多次推送问题 ~ 修复问题
6. 尝试修复因缓存引起的字段不全问题 ~ 修复问题
7. 修复在非常非常高并发的情况 上报自身消息 回复回错问题 ~ 修复问题
8. 修复图片SubType字段位置错误问题 ~ 修复问题
9. 修复Uid/Uin转换问题 ~ 修复问题
## 新增与调整
1. 最后发言时间重构 入群时间失效 ~ 替换功能
2. 重构文件发送/获取 ~ 优化性能
3. 支持GOCQ私聊上传接口 ~ 新增功能
4. 悄悄告诉你ws http可以同一个端口 ~ 新增功能
5. 根据config目录的默认配置初始化新的配置文件 ~ 新增功能
6. WebUi可以部署在nginx代理二级目录 配置端口设置为0可关闭WebUi ~ 新增功能
7. 新增收藏表情拉取接口 ~ 新增功能
8. 新增群头像设置接口 ~ 新增功能
新增的 API 详细见[API文档](https://napneko.github.io/zh-CN/develop/extends_api)

View File

@@ -0,0 +1,13 @@
# v1.8.3
QQ Version: Windows 9.9.15-26702 / Linux 3.2.12-26702
## 启动的方式
Way03/Way05
## 新增与调整
1. 输入状态变更事件加入
2. Way05 支持Bat启动
新增的 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.7.4",
"version": "1.8.5",
"scripts": {
"watch:dev": "vite --mode development",
"watch:prod": "vite --mode production",

90
script/BootWay05.bat Normal file
View File

@@ -0,0 +1,90 @@
@echo off
REM 检查当前会话是否具有管理员权限
openfiles >nul 2>&1
if %errorlevel% neq 0 (
REM 如果不是管理员,则重新启动脚本以管理员模式运行
echo 请求管理员权限...
powershell -Command "Start-Process cmd -ArgumentList '/c %~f0 %*' -Verb RunAs"
exit /b
)
REM 设置当前工作目录
cd /d %~dp0
REM 获取当前目录路径
set currentPath=%cd%
set currentPath=%currentPath:\=/%
REM 生成JavaScript代码
set "jsCode=(async () =^>await import('file:///%currentPath%/napcat.mjs'))();"
REM 将JavaScript代码保存到文件中
echo %jsCode% > loadScript.js
echo JavaScript code has been generated and saved to loadScript.js
REM 设置NAPCAT_PATH环境变量为 当前目录的loadScript.js地址
set NAPCAT_PATH=%cd%\loadScript.js
REM 获取QQ路径
:loop_read
for /f "tokens=2*" %%a in ('reg query "HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\QQ" /v "UninstallString"') do (
set RetString=%%b
goto :napcat_boot
)
:napcat_boot
for %%a in (%RetString%) do (
set "pathWithoutUninstall=%%~dpa"
)
SET QQPath=%pathWithoutUninstall%QQ.exe
REM 拿不到QQ路径则退出
if not exist "%QQpath%" (
echo provided QQ path is invalid: %QQpath%
pause
exit /b
)
REM 收集dbghelp.dll路径和HASH信息
set QQdir=%~dp0
set oldDllPath=%QQdir%dbghelp.dll
set newDllPath=%currentPath%\dbghelp.dll
for /f "tokens=*" %%A in ('certutil -hashfile "%oldDllPath%" MD5') do (
if not defined oldDllHash set oldDllHash=%%A
)
for /f "tokens=*" %%A in ('certutil -hashfile "%newDllPath%" MD5') do (
if not defined newDllHash set newDllHash=%%A
)
REM 如果文件一致则跳过
if "%oldDllHash%" neq "%newDllHash%" (
tasklist /fi "imagename eq QQ.exe" 2>nul | find /i "QQ.exe" >nul
if %errorlevel% equ 0 (
REM 文件占用则退出
echo dbghelp.dll is in use, cannot continue.
) else (
REM 文件未占用则尝试覆盖
copy /y "%newDllPath%" "%oldDllPath%"
if %errorlevel% neq 0 (
echo Failed to copy dbghelp.dll
pause
exit /b
) else (
echo dbghelp.dll has been copied to %QQdir%
)
)
)
REM 带参数启动QQ
REM 判断wt是否存在存在则通过wt启动不存在则通过cmd启动
REM %QQPath% --enable-logging %*
where wt >nul 2>nul
if %errorlevel% equ 0 (
wt "cmd" /c "%QQPath%" --enable-logging %*
) else (
"%QQPath%" --enable-logging %*
)

93
script/BootWay05.utf8.bat Normal file
View File

@@ -0,0 +1,93 @@
@echo off
REM 检查当前会话是否具有管理员权限
openfiles >nul 2>&1
if %errorlevel% neq 0 (
REM 如果不是管理员,则重新启动脚本以管理员模式运行
echo 请求管理员权限...
where wt >nul 2>nul
if %errorlevel% equ 0 (
powershell -Command "Start-Process cmd -ArgumentList ' /c %~f0 %*' -Verb RunAs"
) else (
powershell -Command "Start-Process wt -ArgumentList 'cmd /c %~f0 %*' -Verb RunAs"
)
REM wt "cmd" /c "%~f0 %*"
exit /b
)
REM 设置当前工作目录
cd /d %~dp0
REM 获取当前目录路径
set currentPath=%cd%
set currentPath=%currentPath:\=/%
REM 生成JavaScript代码
set "jsCode=(async () =^>await import('file:///%currentPath%/napcat.mjs'))();"
REM 将JavaScript代码保存到文件中
echo %jsCode% > loadScript.js
echo JavaScript code has been generated and saved to loadScript.js
REM 设置NAPCAT_PATH环境变量为 当前目录的loadScript.js地址
set NAPCAT_PATH=%cd%\loadScript.js
REM 获取QQ路径
:loop_read
for /f "tokens=2*" %%a in ('reg query "HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\QQ" /v "UninstallString"') do (
set RetString=%%b
goto :napcat_boot
)
:napcat_boot
for %%a in (%RetString%) do (
set "pathWithoutUninstall=%%~dpa"
)
SET QQPath=%pathWithoutUninstall%QQ.exe
REM 拿不到QQ路径则退出
if not exist "%QQpath%" (
echo provided QQ path is invalid: %QQpath%
pause
exit /b
)
REM 收集dbghelp.dll路径和HASH信息
set QQdir=%~dp0
set oldDllPath=%QQdir%dbghelp.dll
set newDllPath=%currentPath%\dbghelp.dll
for /f "tokens=*" %%A in ('certutil -hashfile "%oldDllPath%" MD5') do (
if not defined oldDllHash set oldDllHash=%%A
)
for /f "tokens=*" %%A in ('certutil -hashfile "%newDllPath%" MD5') do (
if not defined newDllHash set newDllHash=%%A
)
REM 如果文件一致则跳过
if "%oldDllHash%" neq "%newDllHash%" (
tasklist /fi "imagename eq QQ.exe" 2>nul | find /i "QQ.exe" >nul
if %errorlevel% equ 0 (
REM 文件占用则退出
echo dbghelp.dll is in use, cannot continue.
) else (
REM 文件未占用则尝试覆盖
copy /y "%newDllPath%" "%oldDllPath%"
if %errorlevel% neq 0 (
echo Failed to copy dbghelp.dll
pause
exit /b
) else (
echo dbghelp.dll has been copied to %QQdir%
)
)
)
REM 带参数启动QQ
REM 判断wt是否存在存在则通过wt启动不存在则通过cmd启动
REM %QQPath% --enable-logging %*
chcp 65001
"%QQPath%" --enable-logging %*

View File

@@ -1,6 +1,6 @@
// --------------------
// 2024.7.3 9.9.12 BootWay.03 其余方法暂不公开(此方案为临时方案 Win平台已验证
// 缺陷 (已知)
// 1.与非入侵式不同 现在破坏本体代码
// 2.重启代码与正常启动代码失效
// 3.Win需要补丁

View File

@@ -86,7 +86,7 @@ export abstract class HttpServerBase {
this.start(port, host);
}
abstract handleFailed(res: Response, payload: any, err: any): void
abstract handleFailed(res: Response, payload: any, err: Error): void
registerRouter(method: 'post' | 'get' | string, url: string, handler: RegisterHandler) {
if (!url.startsWith('/')) {
@@ -111,7 +111,7 @@ export abstract class HttpServerBase {
try {
res.send(await handler(res, payload));
} catch (e: any) {
this.handleFailed(res, payload, e.stack.toString());
this.handleFailed(res, payload, e);
}
});
}

View File

@@ -84,7 +84,7 @@ export class NTEventWrapper {
}
//统一回调清理事件
async DispatcherListener(ListenerMainName: string, ListenerSubName: string, ...args: any[]) {
//console.log(ListenerMainName, ListenerSubName, ...args,this.EventTask.get(ListenerMainName)?.get(ListenerSubName));
//console.log("[EventDispatcher]",ListenerMainName, ListenerSubName, ...args);
this.EventTask.get(ListenerMainName)?.get(ListenerSubName)?.forEach((task, uuid) => {
//console.log(task.func, uuid, task.createtime, task.timeout);
if (task.createtime + task.timeout < Date.now()) {
@@ -150,7 +150,7 @@ export class NTEventWrapper {
});
}
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;

View File

@@ -2,7 +2,7 @@ import { Peer } from '@/core';
import crypto, { randomInt, randomUUID } from 'crypto';
import { logError } from './log';
class LimitedHashTable<K, V> {
export class LimitedHashTable<K, V> {
private keyToValue: Map<K, V> = new Map();
private valueToKey: Map<V, K> = new Map();
private maxSize: number;
@@ -99,8 +99,10 @@ class MessageUniqueWrapper {
}
createMsg(peer: Peer, msgId: string): number | undefined {
const key = `${msgId}|${peer.chatType}|${peer.peerUid}`;
const hash = crypto.createHash('md5').update(key);
const shortId = hash.digest().readInt32BE(0);
const hash = crypto.createHash('md5').update(key).digest();
//设置第一个bit为0 保证shortId为正数
hash[0] &= 0x7f;
const shortId = hash.readInt32BE(0);
//减少性能损耗
// const isExist = this.msgIdMap.getKey(shortId);
// if (isExist && isExist === msgId) {

View File

@@ -1,62 +1,50 @@
import path from 'node:path';
import fs from 'node:fs';
import os from 'node:os';
import { systemPlatform } from '@/common/utils/system';
import { logError } from '@/common/utils/log';
import { getDefaultQQVersionConfigInfo, getQQVersionConfigPath } from './helper';
import AppidTable from '@/core/external/appid.json';
import { log } from './log';
export const exePath = process.execPath;
//基础目录获取
export const QQMainPath = process.execPath;
export const QQPackageInfoPath: string = path.join(path.dirname(QQMainPath), 'resources', 'app', 'package.json');
export const QQVersionConfigPath: string | undefined = getQQVersionConfigPath(QQMainPath);
export const pkgInfoPath = path.join(path.dirname(exePath), 'resources', 'app', 'package.json');
let configVersionInfoPath;
//基础信息获取 无快更则启用默认模板填充
export const isQuickUpdate: boolean = !!QQVersionConfigPath;
export const QQVersionConfig: QQVersionConfigType = isQuickUpdate ? JSON.parse(fs.readFileSync(QQVersionConfigPath!).toString()) : getDefaultQQVersionConfigInfo();
export const QQPackageInfo: QQPackageInfoType = JSON.parse(fs.readFileSync(QQPackageInfoPath).toString());
export const { appid: QQVersionAppid, qua: QQVersionQua } = getAppidV2();
if (os.platform() !== 'linux') {
configVersionInfoPath = path.join(path.dirname(exePath), 'resources', 'app', 'versions', 'config.json');
} else {
const userPath = os.homedir();
const appDataPath = path.resolve(userPath, './.config/QQ');
configVersionInfoPath = path.resolve(appDataPath, './versions/config.json');
//基础函数
export function getQQBuildStr() {
return isQuickUpdate ? QQVersionConfig.buildId : QQPackageInfo.buildVersion;
}
if (typeof configVersionInfoPath !== 'string') {
throw new Error('Something went wrong when load QQ info path');
export function getFullQQVesion() {
return isQuickUpdate ? QQVersionConfig.curVersion : QQPackageInfo.version;
}
export { configVersionInfoPath };
type QQPkgInfo = {
version: string;
buildVersion: string;
platform: string;
eleArch: string;
export function requireMinNTQQBuild(buildStr: string) {
return parseInt(getQQBuildStr()) >= parseInt(buildStr);
}
type QQVersionConfigInfo = {
baseVersion: string;
curVersion: string;
prevVersion: string;
onErrorVersions: Array<any>;
buildId: string;
//此方法不要直接使用
export function getQUAInternal() {
return systemPlatform === 'linux' ? `V1_LNX_NQ_${getFullQQVesion()}_${getQQBuildStr()}_GW_B` : `V1_WIN_NQ_${getFullQQVesion()}_${getQQBuildStr()}_GW_B`;
}
let _qqVersionConfigInfo: QQVersionConfigInfo = {
'baseVersion': '9.9.12-25765',
'curVersion': '9.9.12-25765',
'prevVersion': '',
'onErrorVersions': [],
'buildId': '25765'
};
if (fs.existsSync(configVersionInfoPath)) {
export function getAppidV2(): { appid: string, qua: string } {
const appidTbale = AppidTable as unknown as QQAppidTableType;
try {
const _ =JSON.parse(fs.readFileSync(configVersionInfoPath).toString());
_qqVersionConfigInfo = Object.assign(_qqVersionConfigInfo, _);
} catch (e) {
logError('Load QQ version config info failed, Use default version', e);
const data = appidTbale[getFullQQVesion()];
if (data) {
return data;
}
}
catch (e) {
log('[QQ版本兼容性检测] 获取Appid异常 请检测NapCat/QQNT是否正常');
}
// 以下是兜底措施
log(`[QQ版本兼容性检测] ${getFullQQVesion()} 版本兼容性不佳,可能会导致一些功能无法正常使用`);
return { appid: systemPlatform === 'linux' ? '537237950' : '537237765', qua: getQUAInternal() };
}
export const qqVersionConfigInfo: QQVersionConfigInfo = _qqVersionConfigInfo;
//V1_WIN_NQ_9.9.12_25765_GW_B
export const qqPkgInfo: QQPkgInfo = JSON.parse(fs.readFileSync(pkgInfoPath).toString());
// platform_type: 3,
// app_type: 4,
// app_version: '9.9.12-25765',
@@ -64,14 +52,6 @@ export const qqPkgInfo: QQPkgInfo = JSON.parse(fs.readFileSync(pkgInfoPath).toSt
// appid: '537234702',
// platVer: '10.0.26100',
// clientVer: '9.9.9-25765',
// Linux
// app_version: '3.2.9-25765',
// qua: 'V1_LNX_NQ_3.2.10_25765_GW_B',
let _appid: string = '537234702'; // 默认为 Windows 平台的 appid
if (systemPlatform === 'linux') {
_appid = '537234773';
}
// todo: mac 平台的 appid
export const appid = _appid;

View File

@@ -181,7 +181,7 @@ type Uri2LocalRes = {
isLocal: boolean
}
export async function uri2local(uri: string, fileName: string | null = null): Promise<Uri2LocalRes> {
export async function uri2local(UriOrPath: string, fileName: string | null = null): Promise<Uri2LocalRes> {
const res = {
success: false,
errMsg: '',
@@ -190,26 +190,29 @@ export async function uri2local(uri: string, fileName: string | null = null): Pr
path: '',
isLocal: false
};
if (!fileName) {
fileName = randomUUID();
}
let filePath = path.join(getTempDir(), fileName);
if (!fileName) fileName = randomUUID();
let filePath = path.join(getTempDir(), fileName);//临时目录
let url = null;
//区分path和uri
try {
url = new URL(uri);
} catch (e: any) {
res.errMsg = `uri ${uri} 解析失败,` + e.toString() + ` 可能${uri}不存在`;
if (fs.existsSync(UriOrPath)) url = new URL('file://' + UriOrPath);
} catch (error: any) { }
try {
url = new URL(UriOrPath);
} catch (error: any) { }
//验证url
if (!url) {
res.errMsg = `UriOrPath ${UriOrPath} 解析失败,可能${UriOrPath}不存在`;
return res;
}
// log("uri protocol", url.protocol, uri);
if (url.protocol == 'base64:') {
// base64转成文件
const base64Data = uri.split('base64://')[1];
const base64Data = UriOrPath.split('base64://')[1];
try {
const buffer = Buffer.from(base64Data, 'base64');
fs.writeFileSync(filePath, buffer);
} catch (e: any) {
res.errMsg = 'base64文件下载失败,' + e.toString();
return res;
@@ -218,7 +221,7 @@ export async function uri2local(uri: string, fileName: string | null = null): Pr
// 下载文件
let buffer: Buffer | null = null;
try {
buffer = await httpDownload(uri);
buffer = await httpDownload(UriOrPath);
} catch (e: any) {
res.errMsg = `${url}下载失败,` + e.toString();
return res;
@@ -252,6 +255,7 @@ export async function uri2local(uri: string, fileName: string | null = null): Pr
}
}
else {
// 26702执行forword file文件操作 不应该在这里乱来
// const cache = await dbUtil.getFileCacheByName(uri);
// if (cache) {
// filePath = cache.path;
@@ -259,7 +263,6 @@ export async function uri2local(uri: string, fileName: string | null = null): Pr
// filePath = uri;
// }
}
res.isLocal = true;
}
// else{

View File

@@ -1,10 +1,11 @@
import crypto from 'node:crypto';
import path from 'node:path';
import fs from 'fs/promises';
import fs from 'fs';
import { log, logDebug } from './log';
import { dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
import * as fsPromise from 'node:fs/promises';
import os from 'node:os';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
@@ -304,14 +305,14 @@ export function migrateConfig(oldConfig: any) {
}
// 升级旧的配置到新的
export async function UpdateConfig() {
const configFiles = await fs.readdir(path.join(__dirname, 'config'));
const configFiles = await fsPromise.readdir(path.join(__dirname, 'config'));
for (const file of configFiles) {
if (file.match(/^onebot11_\d+.json$/)) {
const CurrentConfig = JSON.parse(await fs.readFile(path.join(__dirname, 'config', file), 'utf8'));
const CurrentConfig = JSON.parse(await fsPromise.readFile(path.join(__dirname, 'config', file), 'utf8'));
if (isValidOldConfig(CurrentConfig)) {
log('正在迁移旧配置到新配置 File:', file);
const NewConfig = migrateConfig(CurrentConfig);
await fs.writeFile(path.join(__dirname, 'config', file), JSON.stringify(NewConfig, null, 2));
await fsPromise.writeFile(path.join(__dirname, 'config', file), JSON.stringify(NewConfig, null, 2));
}
}
}
@@ -331,7 +332,56 @@ export function isEqual(obj1: any, obj2: any) {
}
return true;
}
export function getDefaultQQVersionConfigInfo(): QQVersionConfigType {
if (os.platform() === 'linux') {
return {
baseVersion: '3.2.12-26702',
curVersion: '3.2.12-26702',
prevVersion: '',
onErrorVersions: [],
buildId: '26702'
};
}
return {
baseVersion: '9.9.15-26702',
curVersion: '9.9.15-26702',
prevVersion: '',
onErrorVersions: [],
buildId: '26702'
};
}
export async function promisePipeline(promises: Promise<any>[], callback: (result: any) => boolean): Promise<void> {
let callbackCalled = false;
for (const promise of promises) {
if (callbackCalled) break;
try {
const result = await promise;
if (!callbackCalled) {
callbackCalled = callback(result);
}
} catch (error) {
console.error('Error in promise pipeline:', error);
}
}
}
export function getQQVersionConfigPath(exePath: string = ''): string | undefined {
let configVersionInfoPath;
if (os.platform() !== 'linux') {
configVersionInfoPath = path.join(path.dirname(exePath), 'resources', 'app', 'versions', 'config.json');
} else {
const userPath = os.homedir();
const appDataPath = path.resolve(userPath, './.config/QQ');
configVersionInfoPath = path.resolve(appDataPath, './versions/config.json');
}
if (typeof configVersionInfoPath !== 'string') {
return undefined;
}
if (!fs.existsSync(configVersionInfoPath)) {
return undefined;
}
return configVersionInfoPath;
}
export async function deleteOldFiles(directoryPath: string, daysThreshold: number) {
try {
const files = await fsPromise.readdir(directoryPath);

View File

@@ -1,31 +1,17 @@
/**
* 运行时类型转换与检查类
*/
export class TypeCheck {
static isEmpty(value: any): boolean {
return value === null || value === undefined || value === '' ||
(Array.isArray(value) && value.length === 0) || (typeof value === 'object' && Object.keys(value).length === 0);
}
//QQVersionType
type QQPackageInfoType = {
version: string;
buildVersion: string;
platform: string;
eleArch: string;
}
export class TypeConvert {
static toNumber(value: any): number {
const num = Number(value);
if (isNaN(num)) {
throw new Error(`无法将输入转换为数字: ${value}`);
}
return num;
}
static toString(value: any): string {
return String(value);
}
static toBoolean(value: any): boolean {
return Boolean(value);
}
static toArray(value: any): any[] {
return Array.isArray(value) ? value : [value];
}
type QQVersionConfigType = {
baseVersion: string;
curVersion: string;
prevVersion: string;
onErrorVersions: Array<any>;
buildId: string;
}
type QQAppidTableType = {
[key: string]: { appid: string, qua: string };
}

View File

@@ -1,11 +1,66 @@
import { FriendRequest, User } from '@/core/entities';
import { napCatCore, OnBuddyChangeParams } from '@/core';
import { FriendRequest, FriendV2, SimpleInfo, User } from '@/core/entities';
import { BuddyListReqType, napCatCore, NodeIKernelBuddyListener, NodeIKernelProfileService, OnBuddyChangeParams } from '@/core';
import { NTEventDispatch } from '@/common/utils/EventTask';
import { LimitedHashTable } from '@/common/utils/MessageUnique';
import { CacheClassFuncAsyncExtend } from '@/common/utils/helper';
export class NTQQFriendApi {
static async getBuddyV2(refresh = false): Promise<FriendV2[]> {
let uids: string[] = [];
const buddyService = napCatCore.session.getBuddyService();
const buddyListV2 = refresh ? await buddyService.getBuddyListV2('0', BuddyListReqType.KNOMAL) : await buddyService.getBuddyListV2('0', BuddyListReqType.KNOMAL);
uids.push(...buddyListV2.data.flatMap(item => item.buddyUids));
const data = await NTEventDispatch.CallNoListenerEvent<NodeIKernelProfileService['getCoreAndBaseInfo']>(
'NodeIKernelProfileService/getCoreAndBaseInfo', 5000, 'nodeStore', uids
);
return Array.from(data.values());
}
@CacheClassFuncAsyncExtend(3600 * 1000, 'getBuddyIdMap', () => true)
static async getBuddyIdMapCache(refresh = false): Promise<LimitedHashTable<string, string>> {
return await NTQQFriendApi.getBuddyIdMap(refresh);
}
static async getBuddyIdMap(refresh = false): Promise<LimitedHashTable<string, string>> {
let uids: string[] = [];
let retMap: LimitedHashTable<string, string> = new LimitedHashTable<string, string>(5000);
const buddyService = napCatCore.session.getBuddyService();
const buddyListV2 = refresh ? await buddyService.getBuddyListV2('0', BuddyListReqType.KNOMAL) : await buddyService.getBuddyListV2('0', BuddyListReqType.KNOMAL);
uids.push(...buddyListV2.data.flatMap(item => item.buddyUids));
const data = await NTEventDispatch.CallNoListenerEvent<NodeIKernelProfileService['getCoreAndBaseInfo']>(
'NodeIKernelProfileService/getCoreAndBaseInfo', 5000, 'nodeStore', uids
);
data.forEach((value, key) => {
retMap.set(value.uin!, value.uid!);
});
//console.log('getBuddyIdMap', retMap.getValue);
return retMap;
}
static async getBuddyV2ExWithCate(refresh = false) {
let uids: string[] = [];
let categoryMap: Map<string, any> = new Map();
const buddyService = napCatCore.session.getBuddyService();
const buddyListV2 = refresh ? (await buddyService.getBuddyListV2('0', BuddyListReqType.KNOMAL)).data : (await buddyService.getBuddyListV2('0', BuddyListReqType.KNOMAL)).data;
uids.push(
...buddyListV2.flatMap(item => {
item.buddyUids.forEach(uid => {
categoryMap.set(uid, { categoryId: item.categoryId, categroyName: item.categroyName });
});
return item.buddyUids
}));
const data = await NTEventDispatch.CallNoListenerEvent<NodeIKernelProfileService['getCoreAndBaseInfo']>(
'NodeIKernelProfileService/getCoreAndBaseInfo', 5000, 'nodeStore', uids
);
return Array.from(data).map(([key, value]) => {
const category = categoryMap.get(key);
return category ? { ...value, categoryId: category.categoryId, categroyName: category.categroyName } : value;
});
}
static async isBuddy(uid: string) {
return napCatCore.session.getBuddyService().isBuddy(uid);
}
/**
* @deprecated
* @param forced
* @returns
*/
static async getFriends(forced = false): Promise<User[]> {
let [_retData, _BuddyArg] = await NTEventDispatch.CallNormalEvent
<(force: boolean) => Promise<any>, (arg: OnBuddyChangeParams) => void>

View File

@@ -1,5 +1,5 @@
import { GroupMember, GroupRequestOperateTypes, GroupMemberRole, GroupNotify, Group, MemberExtSourceType, GroupNotifyTypes, ChatType, Peer } from '../entities';
import { GeneralCallResult, NTQQUserApi, napCatCore } from '@/core';
import { GroupMember, GroupRequestOperateTypes, GroupMemberRole, GroupNotify, Group, MemberExtSourceType, GroupNotifyTypes, ChatType, Peer, GroupListUpdateType } from '../entities';
import { GeneralCallResult, NTQQUserApi, NodeIKernelGroupListener, NodeIKernelGroupService, napCatCore } from '@/core';
import { NTEventDispatch } from '@/common/utils/EventTask';
import { log } from '@/common/utils/log';
import { groupMembers } from '../data';
@@ -9,20 +9,21 @@ export class NTQQGroupApi {
return napCatCore.session.getGroupService().setHeader(gc, filePath);
}
static async getGroups(forced = false) {
type ListenerType = NodeIKernelGroupListener['onGroupListUpdate'];
let [_retData, _updateType, groupList] = await NTEventDispatch.CallNormalEvent
<(force: boolean) => Promise<any>, (updateType: number, groupList: Group[]) => void>
<(force: boolean) => Promise<any>, ListenerType>
(
'NodeIKernelGroupService/getGroupList',
'NodeIKernelGroupListener/onGroupListUpdate',
1,
5000,
() => true,
(updateType) => true,
forced
);
return groupList;
}
@CacheClassFuncAsyncExtend(600, "LastestSendTime", () => true)
@CacheClassFuncAsyncExtend(3600 * 1000, "LastestSendTime", () => true)
static async getGroupMemberLastestSendTimeCache(GroupCode: string) {
return NTQQGroupApi.getGroupMemberLastestSendTime(GroupCode);
}
@@ -80,6 +81,9 @@ export class NTQQGroupApi {
});
return ret;
}
static async getGroupMemberAll(GroupCode: string, forced = false) {
return napCatCore.session.getGroupService().getAllMemberList(GroupCode, forced);
}
static async getLastestMsg(GroupCode: string, uins: string[]) {
let uids: Array<string> = [];
for (let uin of uins) {
@@ -154,6 +158,25 @@ export class NTQQGroupApi {
);
return notifies;
}
static async getGroupMemberV2(GroupCode: string, uid: string, forced = false) {
type ListenerType = NodeIKernelGroupListener['onMemberInfoChange'];
type EventType = NodeIKernelGroupService['getMemberInfo'];
// NTEventDispatch.CreatListenerFunction('NodeIKernelGroupListener/onGroupMemberInfoUpdate',
//return napCatCore.session.getGroupService().getMemberInfo(GroupCode, [uid], forced);
const [ret, _groupCode, _changeType, _members] = await NTEventDispatch.CallNormalEvent
<EventType, ListenerType>
(
'NodeIKernelGroupService/getMemberInfo',
'NodeIKernelGroupListener/onMemberInfoChange',
1,
5000,
(groupCode: string, changeType: number, members: Map<string, GroupMember>) => {
return groupCode == GroupCode && members.has(uid);
},
GroupCode, [uid], forced
);
return _members.get(uid);
}
static async getGroupMembers(groupQQ: string, num = 3000): Promise<Map<string, GroupMember>> {
const groupService = napCatCore.session.getGroupService();
const sceneId = groupService.createMemberListScene(groupQQ, 'groupMemberList_MainWindow');
@@ -161,6 +184,7 @@ export class NTQQGroupApi {
if (result.errCode !== 0) {
throw ('获取群成员列表出错,' + result.errMsg);
}
//logDebug(`获取群(${groupQQ})成员列表结果:`, `finish: ${result.result.finish}`); //, Array.from(result.result.infos.values()));
return result.result.infos;
/*
@@ -196,7 +220,7 @@ export class NTQQGroupApi {
const _Pskey = (await NTQQUserApi.getPSkey(['qun.qq.com'])).domainPskeyMap.get('qun.qq.com')!;
return napCatCore.session.getGroupService().uploadGroupBulletinPic(GroupCode, _Pskey, imageurl);
}
static async handleGroupRequest(flag: string, operateType: GroupRequestOperateTypes, reason?: string) {
static async handleGroupRequest(flag: string , operateType: GroupRequestOperateTypes, reason?: string) {
let flagitem = flag.split('|');
let groupCode = flagitem[0];
let seq = flagitem[1];

View File

@@ -1,12 +1,13 @@
import { ElementType, GetFileListParam, MessageElement, Peer, RawMessage, SendMessageElement, SendMsgElementConstructor } from '@/core/entities';
import { friends, groups, selfInfo } from '@/core/data';
import { log, logError, logWarn } from '@/common/utils/log';
import { ChatType, ChatType2, GetFileListParam, Peer, RawMessage, SendMessageElement, SendMsgElementConstructor } from '@/core/entities';
import { friends, getGroupMember, groups, selfInfo } from '@/core/data';
import { log, logWarn } from '@/common/utils/log';
import { sleep } from '@/common/utils/helper';
import { napCatCore, NodeIKernelMsgService, NTQQUserApi } from '@/core';
import { NodeIKernelMsgListener, onGroupFileInfoUpdateParamType } from '@/core/listeners';
import { napCatCore, NTQQGroupApi, NTQQUserApi } from '@/core';
import { onGroupFileInfoUpdateParamType } from '@/core/listeners';
import { GeneralCallResult } from '@/core/services/common';
import { MessageUnique } from '../../../common/utils/MessageUnique';
import { NTEventDispatch } from '@/common/utils/EventTask';
import { requireMinNTQQBuild } from '@/common/utils/QQBasicInfo';
async function LoadMessageIdList(Peer: Peer, msgId: string) {
let msgList = await NTQQMsgApi.getMsgHistory(Peer, msgId, 50);
@@ -16,7 +17,7 @@ async function LoadMessageIdList(Peer: Peer, msgId: string) {
}
async function loadMessageUnique() {
if (groups.size > 100) {
logWarn('群数量大于100可能会导致性能问题');
logWarn('[性能检测] 群数量大于100可能会导致性能问题');
}
let predict = (groups.size + friends.size / 2) / 5;
predict = predict < 20 ? 20 : predict;
@@ -42,6 +43,7 @@ setTimeout(() => {
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);
});
@@ -65,28 +67,38 @@ setTimeout(() => {
// console.log(await NTQQMsgApi.multiForwardMsg(peer, peer, [MsgId]));
// }, 25000)
let SendMsgSeq = new Map<Peer, Array<number>>();
export class NTQQMsgApi {
// static async CheckSendMode() {
// try {
// NTQQMsgApi.sendMsgV2({ chatType: 1, peerUid: selfInfo.uid }, [SendMsgElementConstructor.text('消息队列模式测试')], true, 10000).then().catch();
// MsgSendMode = 2;
// logNotice('[消息队列] 消息模式确认: MsgId异步队列');
// return true;
// } catch (error) {
// logNotice('[消息队列] 消息模式失败: MsgId异步队列');
// }
// try {
// NTQQMsgApi.sendMsgV1({ chatType: 1, peerUid: selfInfo.uid }, [SendMsgElementConstructor.text('消息队列模式测试')], true, 10000).then().catch();
// MsgSendMode = 1;
// logNotice('[消息队列] 消息模式确认: MsgSeq异步队列');
// return true;
// } catch (error) {
// logNotice('[消息队列] 消息模式失败: MsgSeq异步队列');
// }
// return false;
// }
static async FetchLongMsg(peer: Peer, msgId: string) {
return napCatCore.session.getMsgService().fetchLongMsg(peer, msgId);
}
static async getTempChatInfo(chatType: ChatType2, peerUid: string) {
return napCatCore.session.getMsgService().getTempChatInfo(chatType, peerUid);
}
static async PrepareTempChat(toUserUid: string, GroupCode: string, nickname: string) {
//By Jadx/Ida Mlikiowa
let TempGameSession = {
nickname: "",
gameAppId: "",
selfTinyId: "",
peerRoleId: "",
peerOpenId: "",
};
return napCatCore.session.getMsgService().prepareTempChat({
chatType: ChatType2.KCHATTYPETEMPC2CFROMGROUP,
peerUid: toUserUid,
peerNickname: nickname,
fromGroupCode: GroupCode,
sig: "",
selfPhone: "",
selfUid: selfInfo.uid,
gameSession: TempGameSession
});
}
static async getMsgEmojiLikesList(peer: Peer, msgSeq: string, emojiId: string, emojiType: string, count: number = 20) {
//console.log(peer, msgSeq, emojiId, emojiType, count);
//注意此处emojiType 可选值一般为1-2 2好像是unicode表情dec值 大部分情况 Taged M likiowa
return napCatCore.session.getMsgService().getMsgEmojiLikesList(peer, msgSeq, emojiId, emojiType, "", false, 20)
}
// static napCatCore: NapCatCore | null = null;
// enum BaseEmojiType {
// NORMAL_EMOJI,
@@ -107,20 +119,26 @@ export class NTQQMsgApi {
} | undefined> {
return napCatCore.session.getMsgService().getMultiMsg(peer, rootMsgId, parentMsgId);
}
static async getLastestMsgByUids(peer: Peer) {
static async ForwardMsg(peer: Peer, msgIds: string[]) {
return napCatCore.session.getMsgService().forwardMsg(msgIds, peer, [peer], []);
}
static async getLastestMsgByUids(peer: Peer, count: number = 20, isReverseOrder: boolean = false) {
let ret = await napCatCore.session.getMsgService().queryMsgsWithFilterEx('0', '0', '0', {
chatInfo: peer,
filterMsgType: [],
filterSendersUid: [],
filterMsgToTime: '0',
filterMsgFromTime: '0',
isReverseOrder: false,
isReverseOrder: isReverseOrder,//此参数有点离谱 注意不是本次查询的排序 而是全部消历史信息的排序 默认false 从新消息拉取到旧消息
isIncludeCurrent: true,
pageLimit: 1,
pageLimit: count,
});
return ret;
}
static async getMsgsByMsgId(peer: Peer, msgIds: string[]) {
static async getMsgsByMsgId(peer: Peer | undefined, msgIds: string[] | undefined) {
if (!peer) throw new Error('peer is not allowed');
if (!msgIds) throw new Error('msgIds is not allowed');
//Mlikiowa 参数不合规会导致NC异常崩溃 原因是TX未对进入参数判断 对应Android标记@NotNull AndroidJADX分析可得
return await napCatCore.session.getMsgService().getMsgsByMsgId(peer, msgIds);
}
static async getSingleMsg(peer: Peer, seq: string) {
@@ -131,7 +149,7 @@ export class NTQQMsgApi {
}
static async queryMsgsWithFilterExWithSeq(peer: Peer, msgSeq: string) {
let ret = await napCatCore.session.getMsgService().queryMsgsWithFilterEx('0', '0', msgSeq, {
chatInfo: peer,
chatInfo: peer,//此处为Peer 为关键查询参数 没有啥也没有 by mlik iowa
filterMsgType: [],
filterSendersUid: [],
filterMsgToTime: '0',
@@ -142,6 +160,16 @@ export class NTQQMsgApi {
});
return ret;
}
/**
*
* @deprecated 从9.9.15-26702版本开始该接口已经废弃请使用getMsgsEx
* @param peer
* @param seq
* @param count
* @param desc
* @param z
* @returns
*/
static async getMsgsBySeqAndCount(peer: Peer, seq: string, count: number, desc: boolean, z: boolean) {
return await napCatCore.session.getMsgService().getMsgsBySeqAndCount(peer, seq, count, desc, z);
}
@@ -158,6 +186,7 @@ export class NTQQMsgApi {
1,
5000,
(groupFileListResult: onGroupFileInfoUpdateParamType) => {
//Developer Mlikiowa Todo: 此处有问题 无法判断是否成功
return true;
},
GroupCode,
@@ -165,9 +194,9 @@ export class NTQQMsgApi {
);
return data[1].item;
}
static async getMsgHistory(peer: Peer, msgId: string, count: number) {
static async getMsgHistory(peer: Peer, msgId: string, count: number, isReverseOrder: boolean = false) {
// 消息时间从旧到新
return napCatCore.session.getMsgService().getMsgsIncludeSelf(peer, msgId, count, true);
return napCatCore.session.getMsgService().getMsgsIncludeSelf(peer, msgId, count, isReverseOrder);
}
static async recallMsg(peer: Peer, msgIds: string[]) {
await napCatCore.session.getMsgService().recallMsg({
@@ -175,24 +204,30 @@ export class NTQQMsgApi {
peerUid: peer.peerUid
}, msgIds);
}
//并发Seq
static async sendMsgV3(peer: Peer, msgElements: SendMessageElement[], waitComplete = true, timeout = 10000) {
let msgList = await NTQQMsgApi.getLastestMsgByUids(peer);
let data = await napCatCore.session.getMsgService().sendMsg("0", peer, msgElements, new Map());
SendMsgSeq.get(peer)?.push()
}
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(await NTQQMsgApi.getServerTime());
if (peer.chatType === ChatType.temp) {
//await NTQQMsgApi.PrepareTempChat().then().catch();
}
function generateMsgId() {
const timestamp = Math.floor(Date.now() / 1000);
const random = Math.floor(Math.random() * Math.pow(2, 32));
const buffer = Buffer.alloc(8);
buffer.writeUInt32BE(timestamp, 0);
buffer.writeUInt32BE(random, 4);
const msgId = BigInt("0x" + buffer.toString('hex')).toString();
return msgId;
}
// 此处有采用Hack方法 利用数据返回正确得到对应消息
// 与之前 Peer队列 MsgSeq队列 真正的MsgId并发不同
// 谨慎采用 目前测试暂无问题 Developer.Mlikiowa
let msgId: string;
try {
msgId = await NTQQMsgApi.getMsgUnique(peer.chatType, await NTQQMsgApi.getServerTime());
} catch (error) {
//if (!napCatCore.session.getMsgService()['generateMsgUniqueId'])
//兜底识别策略V2
msgId = generateMsgId().toString();
}
let data = await NTEventDispatch.CallNormalEvent<
(msgId: string, peer: Peer, msgElements: SendMessageElement[], map: Map<any, any>) => Promise<unknown>,
(msgList: RawMessage[]) => void
@@ -226,7 +261,32 @@ export class NTQQMsgApi {
}
static async sendMsg(peer: Peer, msgElements: SendMessageElement[], waitComplete = true, timeout = 10000) {
//唉? !我有个想法
let msgId = await NTQQMsgApi.getMsgUnique(await NTQQMsgApi.getServerTime());
function generateMsgId() {
const timestamp = Math.floor(Date.now() / 1000);
const random = Math.floor(Math.random() * Math.pow(2, 32));
const buffer = Buffer.alloc(8);
buffer.writeUInt32BE(timestamp, 0);
buffer.writeUInt32BE(random, 4);
const msgId = BigInt("0x" + buffer.toString('hex')).toString();
return msgId;
}
// 此处有采用Hack方法 利用数据返回正确得到对应消息
// 与之前 Peer队列 MsgSeq队列 真正的MsgId并发不同
// 谨慎采用 目前测试暂无问题 Developer.Mlikiowa
let msgId: string;
try {
msgId = await NTQQMsgApi.getMsgUnique(peer.chatType, await NTQQMsgApi.getServerTime());
} catch (error) {
//if (!napCatCore.session.getMsgService()['generateMsgUniqueId'])
//兜底识别策略V2
msgId = generateMsgId();
}
if (peer.chatType === ChatType.temp && peer.guildId && peer.guildId !== '') {
let member = await getGroupMember(peer.guildId, peer.peerUid!);
if(member){
await NTQQMsgApi.PrepareTempChat(peer.peerUid,peer.guildId,member.nick);
}
}
peer.guildId = msgId;
let data = await NTEventDispatch.CallNormalEvent<
(msgId: string, peer: Peer, msgElements: SendMessageElement[], map: Map<any, any>) => Promise<unknown>,
@@ -256,16 +316,12 @@ export class NTQQMsgApi {
});
return retMsg;
}
static async getMsgUniqueEx() {
let msgId = await NTQQMsgApi.getMsgUnique(await NTQQMsgApi.getServerTime());
return msgId;
}
static async getMsgUnique(time: string) {
static async getMsgUnique(chatType: number, time: string) {
if (requireMinNTQQBuild('26702')) {
return napCatCore.session.getMsgService().generateMsgUniqueId(chatType, time);
}
return napCatCore.session.getMsgService().getMsgUniqueId(time);
}
static async getMsgUniqueByTimeV2() {
return NTEventDispatch.CallNoListenerEvent<NodeIKernelMsgService['getMsgUniqueId']>('NodeIKernelMsgService/getMsgUniqueId', 5000, await NTQQMsgApi.getServerTimeV2())
}
static async getServerTime() {
return napCatCore.session.getMSFService().getServerTime();
}
@@ -273,7 +329,7 @@ export class NTQQMsgApi {
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());
return napCatCore.session.getMsgService().forwardMsg(msgIds, srcPeer, [destPeer], []);
}
static async multiForwardMsg(srcPeer: Peer, destPeer: Peer, msgIds: string[]): Promise<RawMessage> {
const msgInfos = msgIds.map(id => {

View File

@@ -19,6 +19,43 @@ export interface CustomMusicSignPostData {
image?: string,
singer?: string
}
// let t = await napCatCore.session.getGroupService().shareDigest({
// appId: "100497308",
// appType: 1,
// msgStyle: 0,
// recvUin: "726067488",
// sendType: 1,
// clientInfo: {
// platform: 1
// },
// richMsg: {
// usingArk: true,
// title: "Bot测试title",
// summary: "Bot测试summary",
// url: "https://www.bilibili.com",
// pictureUrl: "https://y.qq.com/music/photo_new/T002R300x300M0000035DC6W4ZpSqf_1.jpg?max_age=2592000",
// brief: "Bot测试brief",
// }
// });
// {
// errCode: 0,
// errMsg: '',
// rsp: {
// sendType: 1,
// recvUin: '726067488',
// recvOpenId: '',
// errCode: 901501,
// errMsg: 'imagent service_error:150_OIDB_NO_PRIV',
// extInfo: {
// wording: '消息下发失败(错误码901501)',
// jumpResult: 0,
// jumpUrl: '',
// level: 0,
// subLevel: 0,
// developMsg: 'imagent error'
// }
// }
// }
// export class MusicSign {
// private readonly url: string;

View File

@@ -1,12 +1,13 @@
import { ModifyProfileParams, SelfInfo, User, UserDetailInfoByUin } from '@/core/entities';
import { ModifyProfileParams, SelfInfo, User, UserDetailInfoByUin, UserDetailInfoByUinV2 } from '@/core/entities';
import { friends, groupMembers, selfInfo } from '@/core/data';
import { CacheClassFuncAsync, CacheClassFuncAsyncExtend } from '@/common/utils/helper';
import { napCatCore } from '@/core';
import { napCatCore, NTQQFriendApi } 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 { requireMinNTQQBuild } from '@/common/utils/QQBasicInfo';
export class NTQQUserApi {
static async getProfileLike(uid: string) {
@@ -50,21 +51,87 @@ export class NTQQUserApi {
return napCatCore.session.getGroupService().setHeader(gc, filePath);
}
static async getSelfInfo() {
static async fetchUserDetailInfos(uids: string[]) {
//26702 以上使用新接口 .Dev Mlikiowa
type EventService = NodeIKernelProfileService['fetchUserDetailInfo'];
type EventListener = NodeIKernelProfileListener['onUserDetailInfoChanged'];
let retData: User[] = [];
let [_retData, _retListener] = await NTEventDispatch.CallNormalEvent
<EventService, EventListener>
(
'NodeIKernelProfileService/fetchUserDetailInfo',
'NodeIKernelProfileListener/onUserDetailInfoChanged',
uids.length,
5000,
(profile) => {
if (uids.includes(profile.uid)) {
let RetUser: User = {
...profile.simpleInfo.coreInfo,
...profile.simpleInfo.status,
...profile.simpleInfo.vasInfo,
...profile.commonExt,
...profile.simpleInfo.baseInfo,
qqLevel: profile.commonExt.qqLevel,
pendantId: ""
};
retData.push(RetUser);
return true;
}
return false;
},
"BuddyProfileStore",
uids,
UserDetailSource.KSERVER,
[
ProfileBizType.KALL
]
);
return retData;
}
static async getUserInfo(uid: string) {
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;
}
// enum ProfileBizType {
// KALL,
// KBASEEXTEND,
// KVAS,
// KQZONE,
// KOTHER
// }
static async getUserDetailInfo(uid: string) {
if (requireMinNTQQBuild('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
@@ -135,22 +202,72 @@ export class NTQQUserApi {
}
return skey;
}
@CacheClassFuncAsyncExtend(3600, 'Uin2Uid', (Uin: string, Uid: string | undefined) => {
@CacheClassFuncAsyncExtend(3600 * 1000, 'Uin2Uid', (Uin: string, Uid: string | undefined) => {
if (Uid && Uid.indexOf('u_') != -1) {
return true
}
logWarn("uin转换到uid时异常", Uin);
logWarn("uin转换到uid时异常", Uin, Uid);
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);
//此代码仅临时使用,后期会被废弃
if (requireMinNTQQBuild('26702')) {
return await NTQQUserApi.getUidByUinV2(Uin);
}
return await NTQQUserApi.getUidByUinV1(Uin);
}
@CacheClassFuncAsyncExtend(3600 * 1000, 'Uid2Uin', (Uid: string | undefined, Uin: number | undefined) => {
if (Uin && Uin != 0 && !isNaN(Uin)) {
return true
}
logWarn("uid转换到uin时异常", Uid, Uin);
return false;
})
static async getUinByUid(Uid: string) {
//此代码仅临时使用,后期会被废弃
if (requireMinNTQQBuild('26702')) {
return await NTQQUserApi.getUinByUidV2(Uid);
}
return await NTQQUserApi.getUinByUidV1(Uid);
}
//后期改成流水线处理
static async getUidByUinV2(Uin: string) {
let uid = (await napCatCore.session.getProfileService().getUidByUin('FriendsServiceImpl', [Uin])).get(Uin);
if (uid) return uid;
uid = (await napCatCore.session.getGroupService().getUidByUins([Uin])).uids.get(Uin);
if (uid) return uid;
uid = (await napCatCore.session.getUixConvertService().getUid([Uin])).uidInfo.get(Uin);
if (uid) return uid;
console.log((await NTQQFriendApi.getBuddyIdMapCache(true)));
uid = (await NTQQFriendApi.getBuddyIdMapCache(true)).getValue(Uin);//从Buddy缓存获取Uid
if (uid) return uid;
uid = (await NTQQFriendApi.getBuddyIdMap(true)).getValue(Uin);
if (uid) return uid;
let unveifyUid = (await NTQQUserApi.getUserDetailInfoByUinV2(Uin)).detail.uid;//从QQ Native 特殊转换
if (unveifyUid.indexOf("*") == -1) uid = unveifyUid;
//if (uid) return uid;
return uid;
}
//后期改成流水线处理
static async getUinByUidV2(Uid: string) {
let uin = (await napCatCore.session.getProfileService().getUinByUid('FriendsServiceImpl', [Uid])).get(Uid);
if (uin) return uin;
uin = (await napCatCore.session.getGroupService().getUinByUids([Uid])).uins.get(Uid);
if (uin) return uin;
uin = (await napCatCore.session.getUixConvertService().getUin([Uid])).uinInfo.get(Uid);
if (uin) return uin;
uin = (await NTQQFriendApi.getBuddyIdMapCache(true)).getKey(Uid);//从Buddy缓存获取Uin
if (uin) return uin;
uin = (await NTQQFriendApi.getBuddyIdMap(true)).getKey(Uid);
if (uin) return uin;
uin = (await NTQQUserApi.getUserDetailInfo(Uid)).uin; //从QQ Native 转换
return uin;
}
static async getUidByUinV1(Uin: string) {
// 通用转换开始尝试
let uid = (await napCatCore.session.getUixConvertService().getUid([Uin])).uidInfo.get(Uin);
// Uid 好友转
if (!uid) {
Array.from(friends.values()).forEach((t) => {
@@ -177,17 +294,7 @@ export class NTQQUserApi {
}
return uid;
}
@CacheClassFuncAsyncExtend(3600, 'Uid2Uin', (Uid: string | undefined, Uin: number | undefined) => {
if (Uin && Uin != 0 && !isNaN(Uin)) {
return true
}
logWarn("uid转换到uin时异常", Uid);
return false;
})
static async getUinByUid(Uid: string | undefined) {
if (!Uid) {
return '';
}
static async getUinByUidV1(Uid: string) {
let ret = await NTEventDispatch.CallNoListenerEvent
<(Uin: string[]) => Promise<{ uinInfo: Map<string, string> }>>(
'NodeIKernelUixConvertService/getUin',
@@ -227,6 +334,14 @@ export class NTQQUserApi {
static async getRecentContactList() {
return await napCatCore.session.getRecentContactService().getRecentContactList();
}
static async getUserDetailInfoByUinV2(Uin: string) {
return await NTEventDispatch.CallNoListenerEvent
<(Uin: string) => Promise<UserDetailInfoByUinV2>>(
'NodeIKernelProfileService/getUserDetailInfoByUin',
5000,
Uin
);
}
static async getUserDetailInfoByUin(Uin: string) {
return NTEventDispatch.CallNoListenerEvent
<(Uin: string) => Promise<UserDetailInfoByUin>>(

View File

@@ -114,6 +114,22 @@ export interface GroupEssenceMsgRet {
}
}
export class WebApi {
static async shareDigest(groupCode: string, msgSeq: string, msgRandom: string, targetGroupCode: string) {
const CookiesObject = await NTQQUserApi.getCookies('qun.qq.com');
const CookieValue = Object.entries(CookiesObject).map(([key, value]) => `${key}=${value}`).join('; ');
const Bkn = WebApi.genBkn(CookiesObject.skey);
let ret: any = undefined;
const data = 'group_code=' + groupCode + '&msg_seq=' + msgSeq + '&msg_random=' + msgRandom + '&target_group_code=' + targetGroupCode;
const url = 'https://qun.qq.com/cgi-bin/group_digest/share_digest?bkn=' + Bkn + "&" + data;
//console.log(url);
try {
ret = await RequestUtil.HttpGetText(url, 'GET', '', { 'Cookie': CookieValue });
return ret;
} catch (e) {
return undefined;
}
return undefined;
}
@CacheClassFuncAsync(3600 * 1000, 'webapi_get_group_members')
static async getGroupEssenceMsg(GroupCode: string, page_start: string) {
const CookiesObject = await NTQQUserApi.getCookies('qun.qq.com');

View File

@@ -15,12 +15,12 @@ import { DependsAdapter, DispatcherAdapter, GlobalAdapter, NodeIGlobalAdapter }
import path from 'node:path';
import os from 'node:os';
import fs from 'node:fs';
import { appid, qqVersionConfigInfo } from '@/common/utils/QQBasicInfo';
import { getFullQQVesion, QQVersionAppid, QQVersionQua, requireMinNTQQBuild } from '@/common/utils/QQBasicInfo';
import { hostname, systemVersion } from '@/common/utils/system';
import { genSessionConfig } from '@/core/sessionConfig';
import { sleep } from '@/common/utils/helper';
import crypto from 'node:crypto';
import { rawFriends, friends, groupMembers, groups, selfInfo, stat } from '@/core/data';
import { groupMembers, groups, selfInfo, stat } from '@/core/data';
import { GroupMember, RawMessage } from '@/core/entities';
import { NTEventDispatch } from '@/common/utils/EventTask';
import {
@@ -33,6 +33,7 @@ import {
setLogSelfInfo
} from '@/common/utils/log';
import { napCatConfig } from '@/core/utils/config';
import { NTQQFriendApi } from './apis';
export interface OnLoginSuccess {
(uin: string, uid: string): void | Promise<void>;
@@ -84,7 +85,6 @@ export class NapCatCore {
const dataPath = path.resolve(this.dataPath, './NapCat/data');
fs.mkdirSync(dataPath, { recursive: true });
logDebug('本账号数据/缓存目录:', dataPath);
// dbUtil.init(path.resolve(dataPath, `./${arg.uin}-v2.db`)).then(() => {
this.initDataListener();
this.onLoginSuccessFuncList.map(cb => {
new Promise((resolve, reject) => {
@@ -94,11 +94,6 @@ export class NapCatCore {
}
}).then();
});
// }).catch((e) => {
// logError('数据库初始化失败', e);
// });
// this.initDataListener();
}).catch((e) => {
logError('initSession failed', e);
throw new Error(`启动失败: ${JSON.stringify(e)}`);
@@ -139,10 +134,10 @@ export class NapCatCore {
base_path_prefix: '',
platform_type: 3,
app_type: 4,
app_version: qqVersionConfigInfo.curVersion,
app_version: getFullQQVesion(),
os_version: 'Windows 10 Pro',
use_xlog: true,
qua: `V1_WIN_NQ_${qqVersionConfigInfo.curVersion.replace('-', '_')}_GW_B`,
qua: QQVersionQua,
global_path_config: {
desktopGlobalPath: this.dataPathGlobal,
},
@@ -150,10 +145,10 @@ export class NapCatCore {
}, new QQWrapper.NodeIGlobalAdapter(new GlobalAdapter()));
this.loginService.initConfig({
machineId: '',
appid,
appid: QQVersionAppid,
platVer: systemVersion,
commonPath: this.dataPathGlobal,
clientVer: qqVersionConfigInfo.curVersion,
clientVer: getFullQQVesion(),
hostName: hostname
});
}
@@ -258,31 +253,23 @@ export class NapCatCore {
stat.packet_received += 1;
};
this.addListener(msgListener);
// 好友相关
// 好友相关
const buddyListener = new BuddyListener();
buddyListener.onBuddyListChange = arg => {
rawFriends.length = 0;
rawFriends.push(...arg);
// console.log('onBuddyListChange', arg);
for (const categoryItem of arg) {
for (const friend of categoryItem.buddyList) {
// console.log("onBuddyListChange", friend)
const existFriend = friends.get(friend.uid);
if (existFriend) {
Object.assign(existFriend, friend);
}
else {
friends.set(friend.uid, friend);
}
}
// console.log("onBuddyListChange", friend)
}
};
this.addListener(buddyListener);
// 刷新一次好友列表
this.session.getBuddyService().getBuddyList(true).then(arg => {
// console.log('getBuddyList', arg);
});
// 刷新一次好友列表 26702版本以下需要手动刷新一次获取 高版本NTQQ自带缓存
if (!requireMinNTQQBuild('26702')) {
this.session.getBuddyService().getBuddyList(true).then(arg => {
// console.log('getBuddyList', arg);
});
} else {
// NTQQFriendApi.getBuddyV2(true).then((res) => {
// res.forEach((item) => {
// CachedIdMap.set(item.uid!, item.uin!);
// });
// }).catch();
}
interface SelfStatusInfo {
uid: string
status: number
@@ -335,6 +322,7 @@ export class NapCatCore {
if (groupMembers.has(groupCode)) {
const existMembers = groupMembers.get(groupCode)!;
arg.infos.forEach((member, uid) => {
//console.log('onMemberListChange', member);
const existMember = existMembers.get(uid);
if (existMember) {
Object.assign(existMember, member);

View File

@@ -3,9 +3,11 @@ import {
type Group,
type GroupMember,
type SelfInfo,
type BuddyCategoryType
type BuddyCategoryType,
FriendV2
} from './entities';
import { isNumeric } from '@/common/utils/helper';
import { LimitedHashTable } from '@/common/utils/MessageUnique';
import { NTQQGroupApi } from '@/core/apis';
export const selfInfo: SelfInfo = {
@@ -14,8 +16,6 @@ export const selfInfo: SelfInfo = {
nick: '',
online: true
};
// 未来只在此处保留 selfInfo stat
// groupCode -> Group
export const groups: Map<string, Group> = new Map<string, Group>();
export function deleteGroup(groupQQ: string) {
@@ -26,9 +26,10 @@ export function deleteGroup(groupQQ: string) {
// 群号 -> 群成员map(uid=>GroupMember)
export const groupMembers: Map<string, Map<string, GroupMember>> = new Map<string, Map<string, GroupMember>>();
// uid -> Friend 下面这俩个准备移除 QQ里面自带缓存
export const friends: Map<string, Friend> = new Map<string, Friend>();
export const rawFriends: Array<BuddyCategoryType> = []; // 带分组的好友列表
//转换列表
//export const CachedIdMap = new LimitedHashTable<string, string>(1000);
export async function getGroup(qq: string | number): Promise<Group | undefined> {
let group = groups.get(qq.toString());
@@ -79,9 +80,6 @@ export async function getGroupMember(groupQQ: string | number, memberUinOrUid: s
}
return member;
}
// 考虑优化 移入QQ缓存或使用Api直接获取
export const tempGroupCodeMap: Record<string, string> = {}; // peerUid => 群号
// 保留 需要频繁读写
export const stat = {
packet_received: 0,

View File

@@ -1,12 +1,18 @@
import { QQLevel, Sex } from './user';
export enum GroupListUpdateType {
REFRESHALL,
GETALL,
MODIFIED,
REMOVE
}
export interface Group {
groupCode: string,
createTime?: string,//高版本才有
maxMember: number,
memberCount: number,
groupName: string,
groupStatus: 0,
memberRole: 2,
groupStatus: number,
memberRole: number,
isTop: boolean,
toppedTimestamp: string,
privilegeFlag: number, //65760
@@ -53,4 +59,6 @@ export interface GroupMember {
sex?: Sex
qqLevel?: QQLevel
isChangeRole: boolean;
joinTime: string;
lastSpeakTime: string;
}

View File

@@ -731,10 +731,12 @@ export interface MultiForwardMsgElement {
}
export interface RawMessage {
parentMsgPeer: Peer;
parentMsgIdList:string[];
id?: number;//扩展字段 用于处理OB11 ID
guildId: string;
msgRandom: string;
// int32, 自己维护的消息id
id?: number;
msgId: string;

View File

@@ -9,7 +9,181 @@ export interface BuddyCategoryType {
categroyMbCount: number;
buddyList: User[];
}
export interface CoreInfo {
uid: string;
uin: string;
nick: string;
remark: string;
}
export interface BaseInfo {
qid: string;
longNick: string;
birthday_year: number;
birthday_month: number;
birthday_day: number;
age: number;
sex: number;
eMail: string;
phoneNum: string;
categoryId: number;
richTime: number;
richBuffer: string;
}
interface MusicInfo {
buf: string;
}
interface VideoBizInfo {
cid: string;
tvUrl: string;
synchType: string;
}
interface VideoInfo {
name: string;
}
interface ExtOnlineBusinessInfo {
buf: string;
customStatus: any;
videoBizInfo: VideoBizInfo;
videoInfo: VideoInfo;
}
interface ExtBuffer {
buf: string;
}
interface UserStatus {
uid: string;
uin: string;
status: number;
extStatus: number;
batteryStatus: number;
termType: number;
netType: number;
iconType: number;
customStatus: any;
setTime: string;
specialFlag: number;
abiFlag: number;
eNetworkType: number;
showName: string;
termDesc: string;
musicInfo: MusicInfo;
extOnlineBusinessInfo: ExtOnlineBusinessInfo;
extBuffer: ExtBuffer;
}
interface PrivilegeIcon {
jumpUrl: string;
openIconList: any[];
closeIconList: any[];
}
interface VasInfo {
vipFlag: boolean;
yearVipFlag: boolean;
svipFlag: boolean;
vipLevel: number;
bigClub: boolean;
bigClubLevel: number;
nameplateVipType: number;
grayNameplateFlag: number;
superVipTemplateId: number;
diyFontId: number;
pendantId: number;
pendantDiyId: number;
faceId: number;
vipFont: number;
vipFontType: number;
magicFont: number;
fontEffect: number;
newLoverDiamondFlag: number;
extendNameplateId: number;
diyNameplateIDs: any[];
vipStartFlag: number;
vipDataFlag: number;
gameNameplateId: string;
gameLastLoginTime: string;
gameRank: number;
gameIconShowFlag: boolean;
gameCardId: string;
vipNameColorId: string;
privilegeIcon: PrivilegeIcon;
}
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[];
}
export interface SimpleInfo {
uid?: string;
uin?: string;
coreInfo: CoreInfo;
baseInfo: BaseInfo;
status: UserStatus | null;
vasInfo: VasInfo | null;
relationFlags: RelationFlags | null;
otherFlags: any | null;
intimate: any | null;
}
export interface FriendV2 extends SimpleInfo {
categoryId?: number;
categroyName?: string;
}
export interface UserDetailInfoListenerArg {
uid: string;
uin: string;
simpleInfo: SimpleInfo;
commonExt: CommonExt;
photoWall: PhotoWall;
}
export interface ModifyProfileParams {
nick: string,
longNick: string,
@@ -101,7 +275,18 @@ export interface Friend extends User { }
export enum BizKey {
KPRIVILEGEICON,
KPHOTOWALL
}
}
export interface UserDetailInfoByUinV2 {
result: number,
errMsg: string,
detail: {
uid: string,
uin: string,
simpleInfo: SimpleInfo,
commonExt: CommonExt,
photoWall: null
}
}
export interface UserDetailInfoByUin {
result: number,
errMsg: string,

50
src/core/src/external/appid.json vendored Normal file
View File

@@ -0,0 +1,50 @@
{
"3.1.2-13107": {
"appid": 537146866,
"qua": "V1_LNX_NQ_3.1.2-13107_RDM_B"
},
"3.2.10-25765": {
"appid": 537234773,
"qua": "V1_LNX_NQ_3.2.10_25765_GW_B"
},
"3.2.12-26702": {
"appid": 537237950,
"qua": "V1_LNX_NQ_3.2.12_26702_GW_B"
},
"3.2.12-26740": {
"appid": 537237950,
"qua": "V1_WIN_NQ_9.9.15_26740_GW_B"
},
"9.9.11-24815": {
"appid": 537226656,
"qua": "V1_WIN_NQ_9.9.11_24815_GW_B"
},
"9.9.12-25493": {
"appid": 537231759,
"qua": "V1_WIN_NQ_9.9.12_25493_GW_B"
},
"9.9.12-25765": {
"appid": 537234702,
"qua": "V1_WIN_NQ_9.9.12_25765_GW_B"
},
"9.9.12-26299": {
"appid": 537234826,
"qua": "V1_WIN_NQ_9.9.12_26299_GW_B"
},
"9.9.12-26339": {
"appid": 537234826,
"qua": "V1_WIN_NQ_9.9.12_26339_GW_B"
},
"9.9.12-26466": {
"appid": 537234826,
"qua": "V1_WIN_NQ_9.9.12_26466_GW_B"
},
"9.9.15-26702": {
"appid": 537237765,
"qua": "V1_WIN_NQ_9.9.15_26702_GW_B"
},
"9.9.15-26740": {
"appid": 537237765,
"qua": "V1_WIN_NQ_9.9.15_26702_GW_B"
}
}

View File

@@ -3,6 +3,8 @@ import { BuddyCategoryType, FriendRequestNotify } from '@/core/entities';
export type OnBuddyChangeParams = BuddyCategoryType[]
interface IBuddyListener {
onBuddyListChangedV2(arg: unknown): void,//V2版本 还没兼容
onBuddyListChange(arg: OnBuddyChangeParams): void,
onBuddyInfoChange(arg: unknown): void,
@@ -44,6 +46,9 @@ export interface NodeIKernelBuddyListener extends IBuddyListener {
}
export class BuddyListener implements IBuddyListener {
onBuddyListChangedV2(arg: unknown): void {
//throw new Error('Method not implemented.');
}
onAddBuddyNeedVerify(arg: unknown) {
}

View File

@@ -1,7 +1,7 @@
import { Group, GroupMember, GroupNotify } from '@/core/entities';
import { Group, GroupListUpdateType, GroupMember, GroupNotify } from '@/core/entities';
interface IGroupListener {
onGroupListUpdate(updateType: number, groupList: Group[]): void;
onGroupListUpdate(updateType: GroupListUpdateType, groupList: Group[]): void;
onGroupExtListUpdate(...args: unknown[]): void;
@@ -96,7 +96,7 @@ export class GroupListener implements IGroupListener {
onGroupFirstBulletinNotify(...args: unknown[]) {
}
onGroupListUpdate(updateType: number, groupList: Group[]) {
onGroupListUpdate(updateType: GroupListUpdateType, groupList: Group[]) {
}
onGroupNotifiesUpdated(dboubt: boolean, notifies: GroupNotify[]) {
@@ -202,7 +202,7 @@ export class DebugGroupListener implements IGroupListener {
console.log('onGroupNotifiesUnreadCountUpdated:', ...args);
}
onGroupSingleScreenNotifies(doubt: boolean, seq: string, notifies: GroupNotify[]){
onGroupSingleScreenNotifies(doubt: boolean, seq: string, notifies: GroupNotify[]) {
console.log('onGroupSingleScreenNotifies:');
}

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,44 @@
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 & {
data: Array<{
categoryId: number,
categorySortId: number,
categroyName: string,
categroyMbCount: number,
onlineCount: number,
buddyUids: Array<string>
}>
}>;
//26702 以上
getBuddyListFromCache(callFrom: string): Promise<Array<
{
categoryId: number,//9999应该跳过 那是兜底数据吧
categorySortId: number,//排序方式
categroyName: string,//分类名
categroyMbCount: number,//不懂
onlineCount: number,//在线数目
buddyUids: Array<string>//Uids
}>>;
// 以下为原生方法
addKernelBuddyListener(listener: 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

@@ -8,8 +8,90 @@ import {
} from '@/core/entities';
import { GeneralCallResult } from '@/core/services/common';
//高版本的接口不应该随意使用 使用应该严格进行pr审核 同时部分ipc中未出现的接口不要过于依赖 应该做好数据兜底
export interface NodeIKernelGroupService {
setHeader(uid:string,path:string): unknown;
getMemberCommonInfo(Req: {
groupCode: string,
startUin: string,
identifyFlag: string,
uinList: string[],
memberCommonFilter: {
memberUin: number,
uinFlag: number,
uinFlagExt: number,
uinMobileFlag: number,
shutUpTime: number,
privilege: number,
},
memberNum: number,
filterMethod: string,
onlineFlag: string,
realSpecialTitleFlag: number
}): Promise<unknown>;
//26702
getGroupMemberLevelInfo(groupCode: string): Promise<unknown>;
//26702
getGroupHonorList(groupCodes: Array<string>): unknown;
getUinByUids(uins: string[]): Promise<{
errCode: number,
errMsg: string,
uins: Map<string, string>
}>;
getUidByUins(uins: string[]): Promise<{
errCode: number,
errMsg: string,
uids: Map<string, string>
}>;
//26702(其实更早 但是我不知道)
checkGroupMemberCache(arrayList: Array<string>): Promise<unknown>;
//26702(其实更早 但是我不知道)
getGroupLatestEssenceList(groupCode: string): Promise<unknown>;
//26702(其实更早 但是我不知道)
shareDigest(Req: {
appId: string,
appType: number,
msgStyle: number,
recvUin: string,
sendType: number,
clientInfo: {
platform: number
},
richMsg: {
usingArk: boolean,
title: string,
summary: string,
url: string,
pictureUrl: string,
brief: string
}
}): Promise<unknown>;
//26702(其实更早 但是我不知道)
isEssenceMsg(Req: { groupCode: string, msgRandom: number, msgSeq: number }): Promise<unknown>;
//26702(其实更早 但是我不知道)
queryCachedEssenceMsg(Req: { groupCode: string, msgRandom: number, msgSeq: number }): Promise<unknown>;
//26702(其实更早 但是我不知道)
fetchGroupEssenceList(Req: { groupCode: string, pageStart: number, pageLimit: number }, Arg: unknown): Promise<unknown>;
//26702
getAllMemberList(groupCode: string, forceFetch: boolean): Promise<{
errCode: number,
errMsg: string,
result: {
ids: Array<{
uid: string,
index: number//0
}>,
infos: {},
finish: true,
hasRobot: false
}
}>;
setHeader(uid: string, path: string): unknown;
addKernelGroupListener(listener: NodeIKernelGroupListener): number;
@@ -17,7 +99,7 @@ export interface NodeIKernelGroupService {
createMemberListScene(groupCode: string, scene: string): string;
destroyMemberListScene(): void;
destroyMemberListScene(SceneId:string): void;
//About Arg (a) name: lastId 根据手Q来看为object {index:?(number),uid:string}
getNextMemberList(sceneId: string, a: undefined, num: number): Promise<{
errCode: number, errMsg: string,
@@ -28,9 +110,9 @@ export interface NodeIKernelGroupService {
monitorMemberList(): unknown;
searchMember(uid: string): unknown;
searchMember(sceneId: string, keywords: string[]): unknown;
getMemberInfo(uid: string): unknown;
getMemberInfo(group_id: string, uids: string[], forceFetch: boolean): Promise<GeneralCallResult>;
//getMemberInfo [ '56729xxxx', [ 'u_4Nj08cwW5Hxxxxx' ], true ]
kickMember(groupCode: string, memberUids: string[], refuseForever: boolean, kickReason: string): Promise<void>;
@@ -39,7 +121,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

@@ -2,7 +2,35 @@ import { ElementType, MessageElement, Peer, RawMessage, SendMessageElement } fro
import { NodeIKernelMsgListener } from '@/core/listeners/NodeIKernelMsgListener';
import { GeneralCallResult } from '@/core/services/common';
export interface QueryMsgsParams {
chatInfo: Peer,
filterMsgType: [],
filterSendersUid: string[],
filterMsgFromTime: string,
filterMsgToTime: string,
pageLimit: number,
isReverseOrder: boolean,
isIncludeCurrent: boolean
}
export interface TmpChatInfoApi {
errMsg: string;
result: number;
tmpChatInfo?: TmpChatInfo;
}
export interface TmpChatInfo {
chatType: number;
fromNick: string;
groupCode: string;
peerUid: string;
sessionType: number;
sig: string;
}
export interface NodeIKernelMsgService {
generateMsgUniqueId(chatType: number, time: string): string;
addKernelMsgListener(nodeIKernelMsgListener: NodeIKernelMsgListener): number;
sendMsg(msgId: string, peer: Peer, msgElements: SendMessageElement[], map: Map<any, any>): Promise<GeneralCallResult>;
@@ -84,8 +112,8 @@ export interface NodeIKernelMsgService {
recallMsg(...args: unknown[]): unknown;
reeditRecallMsg(...args: unknown[]): unknown;
forwardMsg(...args: unknown[]): Promise<GeneralCallResult>;
//调用请检查除开commentElements其余参数不能为null
forwardMsg(msgIds: string[], srcContact: Peer, dstContacts: Peer[], commentElements: MessageElement[]): Promise<GeneralCallResult>;
forwardMsgWithComment(...args: unknown[]): unknown;
@@ -143,6 +171,7 @@ export interface NodeIKernelMsgService {
getAioFirstViewLatestMsgs(peer: Peer, num: number): unknown;
//deprecated 从9.9.15-26702版本开始该接口已经废弃请使用getMsgsEx
getMsgs(peer: Peer, msgId: string, count: unknown, queryOrder: boolean): Promise<unknown>;
getMsgsIncludeSelf(peer: Peer, msgId: string, count: number, queryOrder: boolean): Promise<GeneralCallResult & {
@@ -214,19 +243,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 +260,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 +268,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;
@@ -512,7 +493,19 @@ export interface NodeIKernelMsgService {
setMsgEmojiLikes(...args: unknown[]): unknown;
getMsgEmojiLikesList(peer: Peer, msgSeq: string, emojiId: string, emojiType: string, cookie: string, bForward: boolean, number: number): Promise<unknown>;
getMsgEmojiLikesList(peer: Peer, msgSeq: string, emojiId: string, emojiType: string, cookie: string, bForward: boolean, number: number): Promise<{
result: number,
errMsg: string,
emojiLikesList:
Array<{
tinyId: string,
nickName: string,
headUrl: string
}>,
cookie: string,
isLastPage: boolean,
isFirstPage: boolean
}>;
setMsgEmojiLikesForRole(...args: unknown[]): unknown;
@@ -629,9 +622,9 @@ export interface NodeIKernelMsgService {
prepareTempChat(args: unknown): unknown;//主动临时消息 不做
sendSsoCmdReqByContend(cmd: string, param: string): Promise<unknown>;
//chattype,uid->Promise<any>
getTempChatInfo(ChatType: number, Uid: string): unknown;
getTempChatInfo(ChatType: number, Uid: string): Promise<TmpChatInfoApi>;
setContactLocalTop(...args: unknown[]): unknown;

View File

@@ -1,10 +1,36 @@
import { AnyCnameRecord } from 'node:dns';
import { BizKey, ModifyProfileParams, UserDetailInfoByUin } from '../entities';
import { BaseInfo, BizKey, CoreInfo, ModifyProfileParams, SimpleInfo, 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 {
getUidByUin(callfrom: string, uin: Array<string>): Promise<Map<string,string>>;//uin->uid
getUinByUid(callfrom: string, uid: Array<string>): Promise<Map<string,string>>;
// {
// coreInfo: CoreInfo,
// baseInfo: BaseInfo,
// status: null,
// vasInfo: null,
// relationFlags: null,
// otherFlags: null,
// intimate: null
// }
getCoreAndBaseInfo(callfrom: string, uids: string[]): Promise<Map<string, SimpleInfo>>;
fetchUserDetailInfo(trace: string, uids: string[], arg2: number, arg3: number[]): Promise<unknown>;
addKernelProfileListener(listener: NodeIKernelProfileListener): number;
removeKernelProfileListener(listenerId: number): void;

View File

@@ -1,4 +1,4 @@
import { Peer } from "../entities";
import { ChatType, Peer } from "../entities";
import { NodeIKernelRecentContactListener } from "../listeners/NodeIKernelRecentContactListener";
import { GeneralCallResult } from "./common";
export interface FSABRecentContactParams {
@@ -39,7 +39,13 @@ export interface NodeIKernelRecentContactService {
errCode: number,
errMsg: string,
sortedContactList: Array<number>,
changedList: Array<any>
changedList: Array<{
remark: any;
peerName: any;
sendMemberName: any;
sendNickName: any;
peerUid: string; peerUin: string, msgTime: string, chatType: ChatType, msgId: string
}>
}
}>; // 1 arguments

View File

@@ -21,7 +21,7 @@ export interface NodeIKernelRobotService {
removeKernelRobotListener(ListenerId: number): unknown;
getAllRobotFriendsFromCache(): unknown;
getAllRobotFriendsFromCache(): Promise<unknown>;
fetchAllRobots(arg1: unknown, arg2: unknown): unknown;

View File

@@ -1,5 +1,5 @@
export interface NodeIKernelUixConvertService {
getUin(uid: string[]): Promise<{ uidInfo: Map<string, string> }>;
getUin(uid: string[]): Promise<{ uinInfo: Map<string, string> }>;
getUid(uin: string[]): Promise<{ uinInfo: Map<string, string> }>;
getUid(uin: string[]): Promise<{ uidInfo: Map<string, string> }>;
}

View File

@@ -1,4 +1,4 @@
import { appid, qqPkgInfo, qqVersionConfigInfo } from '@/common/utils/QQBasicInfo';
import { getFullQQVesion, QQVersionAppid } from '@/common/utils/QQBasicInfo';
import { hostname, systemName, systemVersion } from '@/common/utils/system';
import path from 'node:path';
import fs from 'node:fs';
@@ -34,42 +34,42 @@ export interface WrapperSessionInitConfig {
account_path: string // 可以通过NodeQQNTWrapperUtil().getNTUserDataInfoConfig()获取
}
clientVer: string // 9.9.8-22355
a2: '',
d2: '',
d2Key: '',
machineId: '',
platform: 3, // 3是Windows?
a2: string,
d2: string,
d2Key: string,
machineId: string,
platform: PlatformType, // 3是Windows?
platVer: string, // 系统版本号, 应该可以固定
appid: string,
rdeliveryConfig: {
appKey: '',
systemId: 0,
appId: '',
logicEnvironment: '',
platform: 3,
language: '',
sdkVersion: '',
userId: '',
appVersion: '',
osVersion: '',
bundleId: '',
serverUrl: '',
fixedAfterHitKeys: ['']
appKey: string,
systemId: number,
appId: string,
logicEnvironment: string,
platform: PlatformType,
language: string,
sdkVersion: string,
userId: string,
appVersion: string,
osVersion: string,
bundleId: string,
serverUrl: string,
fixedAfterHitKeys: string[]
}
'defaultFileDownloadPath': string, // 这个可以通过环境变量获取?
'deviceInfo': {
'guid': string,
'buildVer': string,
'localId': 2052,
'devName': string,
'devType': string,
'vendorName': '',
'osVer': string,
'vendorOsName': string,
'setMute': false,
'vendorType': 0
defaultFileDownloadPath: string, // 这个可以通过环境变量获取?
deviceInfo: {
guid: string,
buildVer: string,
localId: number,
devName: string,
devType: string,
vendorName: string,
osVer: string,
vendorOsName: string,
setMute: boolean,
vendorType: VendorType
},
'deviceConfig': '{"appearance":{"isSplitViewMode":true},"msg":{}}'
deviceConfig: '{"appearance":{"isSplitViewMode":true},"msg":{}}'
}
export const sessionConfig: WrapperSessionInitConfig | any = {};
@@ -77,9 +77,9 @@ export const sessionConfig: WrapperSessionInitConfig | any = {};
export async function genSessionConfig(selfUin: string, selfUid: string, account_path: string): Promise<WrapperSessionInitConfig> {
const downloadPath = path.join(account_path, 'NapCat', 'temp');
fs.mkdirSync(downloadPath, { recursive: true });
let guid: string = await getMachineId();
let guid: string = await getMachineId();//26702 支持JS获取guid值 在LoginService中获取 TODO mlikiow a
//console.log(guid);
// guid = '52afb776-82f6-4e59-9d38-44705b112d0a';
// guid = '52afb776-82f6-4e59-9d38-44705b112d0a';
//let guid: string = await getMachineId();
const config: WrapperSessionInitConfig = {
selfUin,
@@ -87,20 +87,20 @@ export async function genSessionConfig(selfUin: string, selfUid: string, account
desktopPathConfig: {
account_path // 可以通过NodeQQNTWrapperUtil().getNTUserDataInfoConfig()获取
},
clientVer: qqVersionConfigInfo.curVersion, // 9.9.8-22355
clientVer: getFullQQVesion(), // 9.9.8-22355
a2: '',
d2: '',
d2Key: '',
machineId: '',
platform: 3, // 3是Windows?
platform: PlatformType.KWINDOWS, // 3是Windows?
platVer: systemVersion, // 系统版本号, 应该可以固定
appid: appid,
appid: QQVersionAppid,
rdeliveryConfig: {
appKey: '',
systemId: 0,
appId: '',
logicEnvironment: '',
platform: 3,
platform: PlatformType.KWINDOWS,
language: '',
sdkVersion: '',
userId: '',
@@ -110,22 +110,21 @@ export async function genSessionConfig(selfUin: string, selfUid: string, account
serverUrl: '',
fixedAfterHitKeys: ['']
},
'defaultFileDownloadPath': downloadPath,
'deviceInfo': {
defaultFileDownloadPath: downloadPath,
deviceInfo: {
guid,
'buildVer': qqPkgInfo.version,
'localId': 2052,
'devName': hostname,
'devType': systemName,
'vendorName': '',
'osVer': systemVersion,
'vendorOsName': systemName,
'setMute': false,
'vendorType': 0
buildVer: getFullQQVesion(),
localId: 2052,
devName: hostname,
devType: systemName,
vendorName: '',
osVer: systemVersion,
vendorOsName: systemName,
setMute: false,
vendorType: VendorType.KNOSETONIOS
},
'deviceConfig': '{"appearance":{"isSplitViewMode":true},"msg":{}}'
deviceConfig: '{"appearance":{"isSplitViewMode":true},"msg":{}}'
};
Object.assign(sessionConfig, config);
// log(sessionConfig);
return config;
}

View File

@@ -26,7 +26,6 @@ import {
NodeIKernelRichMediaService,
NodeIKernelAvatarService,
} from './services';
import { qqVersionConfigInfo } from '@/common/utils/QQBasicInfo';
import { NodeIKernelStorageCleanService } from './services/NodeIKernelStorageCleanService';
import { NodeIKernelRobotService } from './services/NodeIKernelRobotService';
import { dirname } from "node:path"
@@ -42,6 +41,8 @@ import { NodeIKernelCollectionService } from './services/NodeIKernelCollectionSe
import { NodeIKernelRecentContactService } from './services/NodeIKernelRecentContactService';
import { NodeIKernelMSFService } from './services/NodeIKernelMSFService';
import { NodeIkernelTestPerformanceService } from './services/NodeIkernelTestPerformanceService';
import { NodeIKernelECDHService } from './services/NodeIKernelECDHService';
import { getFullQQVesion } from '@/common/utils/QQBasicInfo';
const __filename = fileURLToPath(import.meta.url);
@@ -157,6 +158,10 @@ export interface NodeIQQNTWrapperSession {
startNT(): void;
getBdhUploadService(): unknown;
getECDHService(): NodeIKernelECDHService;
getMsgService(): NodeIKernelMsgService;
getProfileService(): NodeIKernelProfileService;
@@ -287,7 +292,7 @@ export interface WrapperNodeApi {
let wrapperNodePath = path.resolve(path.dirname(process.execPath), './resources/app/wrapper.node');
if (!fs.existsSync(wrapperNodePath)) {
wrapperNodePath = path.join(path.dirname(process.execPath), `resources/app/versions/${qqVersionConfigInfo.curVersion}/wrapper.node`);
wrapperNodePath = path.join(path.dirname(process.execPath), `resources/app/versions/${getFullQQVesion()}/wrapper.node`);
}
const nativemodule: any = { exports: {} };
process.dlopen(nativemodule, wrapperNodePath);

View File

@@ -92,9 +92,9 @@ napCatCore.getQuickLoginList().then((res) => {
WebUiDataRuntime.setQQQuickLoginCall(async (uin: string) => {
const QuickLogin: Promise<{ result: boolean, message: string }> = new Promise((resolve, reject) => {
if (quickLoginQQ) {
log('正在快速登录 ', quickLoginQQ);
napCatCore.quickLogin(quickLoginQQ).then(res => {
if (uin) {
log('正在快速登录 ', uin);
napCatCore.quickLogin(uin).then(res => {
if (res.loginErrorInfo.errMsg) {
resolve({ result: false, message: res.loginErrorInfo.errMsg });
}

4
src/nwebui/Readme.md Normal file
View File

@@ -0,0 +1,4 @@
# NewWebui
基于Vue3实现的现代化轻量化NapCat管理面板
## 进度
画饼

View File

@@ -2,7 +2,6 @@
import { NTQQCollectionApi } from '@/core/apis/collection';
import BaseAction from '../BaseAction';
import { ActionName } from '../types';
import { NTQQUserApi } from '@/core/apis';
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import { selfInfo } from '@/core/data';

View File

@@ -1,16 +1,12 @@
import BaseAction from '../BaseAction';
// import * as ntqqApi from "../../../ntqqapi/api";
import {
NTQQMsgApi,
NTQQFriendApi,
NTQQGroupApi,
NTQQUserApi,
NTQQFileApi,
// NTQQFileCacheApi,
} from '@/core';
import { ActionName } from '../types';
import { log, logDebug } from '@/common/utils/log';
interface Payload {
method: string,
args: any[],
@@ -20,12 +16,8 @@ export default class Debug extends BaseAction<Payload, any> {
actionName = ActionName.Debug;
protected async _handle(payload: Payload): Promise<any> {
//logDebug('debug call ntqq api', payload);
const ntqqApi = [NTQQMsgApi, NTQQFriendApi, NTQQGroupApi, NTQQUserApi, NTQQFileApi,
// NTQQFileCacheApi,
];
const ntqqApi = [NTQQMsgApi, NTQQFriendApi, NTQQGroupApi, NTQQUserApi, NTQQFileApi,];
for (const ntqqApiClass of ntqqApi) {
// logDebug('ntqqApiClass', ntqqApiClass);
const method = (<any>ntqqApiClass)[payload.method];
if (method) {
const result = method(...payload.args);
@@ -36,8 +28,5 @@ export default class Debug extends BaseAction<Payload, any> {
}
}
throw `${payload.method}方法 不存在`;
// const info = await NTQQApi.getUserDetailInfo(friends[0].uid);
// return info
}
}

View File

@@ -15,6 +15,7 @@ export class FetchCustomFace extends BaseAction<Payload, string[]> {
actionName = ActionName.FetchCustomFace;
PayloadSchema = SchemaData;
protected async _handle(payload: Payload) {
//48 可能正好是QQ需要的一个页面的数量 Tagged Mlikiowa
const ret = await NTQQMsgApi.fetchFavEmojiList(payload.count || 48);
return ret.emojiInfoList.map(e => e.url);
}

View File

@@ -0,0 +1,32 @@
//getMsgEmojiLikesList
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import BaseAction from '../BaseAction';
import { ActionName } from '../types';
import { NTQQMsgApi } from '@/core/apis';
import { MessageUnique } from '@/common/utils/MessageUnique';
const SchemaData = {
type: 'object',
properties: {
user_id: { type: 'string' },
group_id: { type: 'string' },
emojiId: { type: 'string' },
emojiType: { type: 'string' },
message_id: { type: ['string', 'number'] },
count: { type: 'number' }
},
required: ['emojiId', 'emojiType', 'message_id']
} as const satisfies JSONSchema;
type Payload = FromSchema<typeof SchemaData>;
export class FetchEmojioLike extends BaseAction<Payload, any> {
actionName = ActionName.FetchEmojioLike;
PayloadSchema = SchemaData;
protected async _handle(payload: Payload) {
const msgIdPeer = MessageUnique.getMsgIdAndPeerByShortId(parseInt(payload.message_id.toString()));
if (!msgIdPeer) throw new Error('消息不存在');
const msg = (await NTQQMsgApi.getMsgsByMsgId(msgIdPeer.Peer, [msgIdPeer.MsgId])).msgList[0];
const ret = await NTQQMsgApi.getMsgEmojiLikesList(msgIdPeer.Peer, msg.msgSeq, payload.emojiId, payload.emojiType, payload.count);
return ret;
}
}

View File

@@ -2,9 +2,7 @@
import { NTQQCollectionApi } from '@/core/apis/collection';
import BaseAction from '../BaseAction';
import { ActionName } from '../types';
import { NTQQUserApi } from '@/core/apis';
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import { selfInfo } from '@/core/data';
const SchemaData = {
type: 'object',

View File

@@ -1,12 +1,19 @@
import { rawFriends } from '@/core/data';
import { requireMinNTQQBuild } from '@/common/utils/QQBasicInfo';
import BaseAction from '../BaseAction';
import { ActionName } from '../types';
import { BuddyCategoryType } from '@/core/entities/';
import { NTQQFriendApi } from '@/core';
import { OB11Constructor } from '@/onebot11/constructor';
export class GetFriendWithCategory extends BaseAction<void, Array<BuddyCategoryType>> {
export class GetFriendWithCategory extends BaseAction<void, any> {
actionName = ActionName.GetFriendsWithCategory;
protected async _handle(payload: void) {
return rawFriends;
if (requireMinNTQQBuild('26702')) {
//全新逻辑
return OB11Constructor.friendsV2(await NTQQFriendApi.getBuddyV2ExWithCate(true));
} else {
throw new Error('this ntqq version not support, must be 26702 or later');
}
}
}

View File

@@ -5,7 +5,6 @@ export class GetRobotUinRange extends BaseAction<void, Array<any>> {
actionName = ActionName.GetRobotUinRange;
protected async _handle(payload: void) {
// console.log(await NTQQUserApi.getRobotUinRange());
return await NTQQUserApi.getRobotUinRange();
}
}

View File

@@ -20,8 +20,8 @@ export class OCRImage extends BaseAction<Payload, any> {
actionName = ActionName.OCRImage;
PayloadSchema = SchemaData;
protected async _handle(payload: Payload) {
const { path, isLocal, errMsg } = (await uri2local(payload.image));
if (errMsg) {
const { path, isLocal, errMsg, success } = (await uri2local(payload.image));
if (!success) {
throw `OCR ${payload.image}失败,image字段可能格式不正确`;
}
if (path) {

View File

@@ -1,10 +1,8 @@
import BaseAction from '../BaseAction';
import { ActionName, BaseCheckResult } from '../types';
import * as fs from 'node:fs';
import { NTQQUserApi } from '@/core/apis/user';
import { checkFileReceived, uri2local } from '@/common/utils/file';
import { NTQQGroupApi } from '@/core';
// import { log } from "../../../common/utils";
interface Payload {
file: string,
@@ -26,8 +24,8 @@ export default class SetGroupHeader extends BaseAction<Payload, any> {
};
}
protected async _handle(payload: Payload): Promise<any> {
const { path, isLocal, errMsg } = (await uri2local(payload.file));
if (errMsg) {
const { path, isLocal, errMsg,success } = (await uri2local(payload.file));
if (!success) {
throw `头像${payload.file}设置失败,file字段可能格式不正确`;
}
if (path) {

View File

@@ -1,4 +1,3 @@
import BaseAction from '../BaseAction';
import { ActionName } from '../types';
import { NTQQUserApi } from '@/core/apis';

View File

@@ -1,5 +1,5 @@
import BaseAction from '../BaseAction';
import { ActionName, BaseCheckResult } from '../types';
import { ActionName } from '../types';
import { NTQQUserApi } from '@/core/apis';
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
// 设置在线状态
@@ -20,12 +20,6 @@ export class SetOnlineStatus extends BaseAction<Payload, null> {
actionName = ActionName.SetOnlineStatus;
PayloadSchema = SchemaData;
protected async _handle(payload: Payload) {
// 可设置状态
// { status: 10, extStatus: 1027, batteryStatus: 0 }
// { status: 30, extStatus: 0, batteryStatus: 0 }
// { status: 50, extStatus: 0, batteryStatus: 0 }
// { status: 60, extStatus: 0, batteryStatus: 0 }
// { status: 70, extStatus: 0, batteryStatus: 0 }
const ret = await NTQQUserApi.setSelfOnlineStatus(payload.status, payload.extStatus, payload.batteryStatus);
if (ret.result !== 0) {
throw new Error('设置在线状态失败');

View File

@@ -3,7 +3,6 @@ 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 { log } from "../../../common/utils";
interface Payload {
file: string
@@ -24,8 +23,8 @@ export default class SetAvatar extends BaseAction<Payload, null> {
};
}
protected async _handle(payload: Payload): Promise<null> {
const { path, isLocal, errMsg } = (await uri2local(payload.file));
if (errMsg) {
const { path, isLocal, errMsg, success } = (await uri2local(payload.file));
if (!success) {
throw `头像${payload.file}设置失败,file字段可能格式不正确`;
}
if (path) {

View File

@@ -1,11 +1,7 @@
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',
@@ -22,9 +18,7 @@ export default class TestApi01 extends BaseAction<Payload, any> {
actionName = ActionName.TestApi01;
// 用不着复杂检测
protected async check(payload: Payload): Promise<BaseCheckResult> {
return {
valid: true,
};
return { valid: true };
}
protected async _handle(payload: Payload): Promise<any> {
return await napCatCore.session.getMsgService().sendSsoCmdReqByContend(payload.cmd, payload.param);

View File

@@ -1,18 +1,14 @@
import BaseAction from '../BaseAction';
import { ActionName, BaseCheckResult } from '../types';
import { NTQQSystemApi, NTQQUserApi } from '@/core/apis';
import { ActionName } from '../types';
import { NTQQSystemApi } from '@/core/apis';
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import Ajv from 'ajv';
// 设置在线状态
const SchemaData = {
type: 'object',
properties: {
words: {
type: 'array',
items: {
type: 'string',
}
items: { type: 'string' }
}
},
required: ['words'],

View File

@@ -1,7 +1,7 @@
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import BaseAction from '../BaseAction';
import { ActionName } from '../types';
import { NTQQGroupApi, NTQQMsgApi, NTQQUserApi } from '@/core/apis';
import { NTQQGroupApi } from '@/core/apis';
const SchemaData = {
type: 'object',

View File

@@ -2,7 +2,7 @@ import BaseAction from '../BaseAction';
import fs from 'fs/promises';
import { ob11Config } from '@/onebot11/config';
import { UUIDConverter } from '@/common/utils/helper';
import { ActionName, BaseCheckResult } from '../types';
import { ActionName } from '../types';
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';
@@ -50,17 +50,17 @@ export class GetFileBase extends BaseAction<GetFilePayload, GetFileResponse> {
try {
UuidData = UUIDConverter.decode(payload.file);
if (UuidData) {
let peerUin = UuidData.high;
let msgId = UuidData.low;
let isGroup = await getGroup(peerUin);
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 };
}
let PeerUid = await NTQQUserApi.getUidByUin(peerUin);
const PeerUid = await NTQQUserApi.getUidByUin(peerUin);
if (PeerUid) {
let isBuddy = await NTQQFriendApi.isBuddy(PeerUid);
const isBuddy = await NTQQFriendApi.isBuddy(PeerUid);
if (isBuddy) {
peer = { chatType: ChatType.friend, peerUid: PeerUid };
} else {
@@ -70,18 +70,18 @@ export class GetFileBase extends BaseAction<GetFilePayload, GetFileResponse> {
if (!peer) {
throw new Error('chattype not support');
}
let msgList = await NTQQMsgApi.getMsgsByMsgId(peer, [msgId]);
const msgList = await NTQQMsgApi.getMsgsByMsgId(peer, [msgId]);
if (msgList.msgList.length == 0) {
throw new Error('msg not found');
}
let msg = msgList.msgList[0];
let findEle = msg.elements.find(e => e.elementType == ElementType.VIDEO || e.elementType == ElementType.FILE || e.elementType == ElementType.PTT);
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');
}
let downloadPath = await NTQQFileApi.downloadMedia(msgId, msg.chatType, msg.peerUid, findEle.elementId, '', '');
let fileSize = findEle?.videoElement?.fileSize || findEle?.fileElement?.fileSize || findEle?.pttElement?.fileSize || '0';
let fileName = findEle?.videoElement?.fileName || findEle?.fileElement?.fileName || findEle?.pttElement?.fileName || '';
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,

View File

@@ -24,12 +24,12 @@ export class GoCQHTTPGetForwardMsgAction extends BaseAction<Payload, any> {
actionName = ActionName.GoCQHTTP_GetForwardMsg;
PayloadSchema = SchemaData;
protected async _handle(payload: Payload): Promise<any> {
const msgId = payload.message_id || payload.id;
if (!msgId) {
throw Error('message_id is required');
const msgIdMixOb11Id = payload.message_id || payload.id;
if (!msgIdMixOb11Id) {
throw Error('message_id or id is required');
}
const rootMsgId = MessageUnique.getShortIdByMsgId(msgId);
const rootMsg = MessageUnique.getMsgIdAndPeerByShortId(rootMsgId || parseInt(msgId));
const rootMsgId = MessageUnique.getShortIdByMsgId(msgIdMixOb11Id);
const rootMsg = MessageUnique.getMsgIdAndPeerByShortId(rootMsgId || parseInt(msgIdMixOb11Id));
if (!rootMsg) {
throw Error('msg not found');
}
@@ -40,7 +40,7 @@ export class GoCQHTTPGetForwardMsgAction extends BaseAction<Payload, any> {
const msgList = data.msgList;
const messages = await Promise.all(msgList.map(async msg => {
const resMsg = await OB11Constructor.message(msg);
resMsg.message_id = await MessageUnique.createMsg({ guildId:'',chatType:msg.chatType,peerUid:msg.peerUid },msg.msgId)!;
resMsg.message_id = MessageUnique.createMsg({ guildId: '', chatType: msg.chatType, peerUid: msg.peerUid }, msg.msgId)!;
return resMsg;
}));
messages.map(msg => {

View File

@@ -1,7 +1,7 @@
import BaseAction from '../BaseAction';
import { OB11Message, OB11User } from '../../types';
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';
@@ -17,9 +17,10 @@ const SchemaData = {
properties: {
user_id: { type: ['number', 'string'] },
message_seq: { type: 'number' },
count: { type: 'number' }
count: { type: 'number' },
reverseOrder: { type: 'boolean' }
},
required: ['user_id', 'message_seq', 'count']
required: ['user_id']
} as const satisfies JSONSchema;
type Payload = FromSchema<typeof SchemaData>;
@@ -28,21 +29,29 @@ export default class GetFriendMsgHistory extends BaseAction<Payload, Response> {
actionName = ActionName.GetFriendMsgHistory;
PayloadSchema = SchemaData;
protected async _handle(payload: Payload): Promise<Response> {
//处理参数
const uid = await NTQQUserApi.getUidByUin(payload.user_id.toString());
if (!uid) {
throw `记录${payload.user_id}不存在`;
}
const startMsgId = (await MessageUnique.getMsgIdAndPeerByShortId(payload.message_seq))?.MsgId || '0';
const MsgCount = payload.count || 20;
const isReverseOrder = payload.reverseOrder || true;
if (!uid) throw `记录${payload.user_id}不存在`;
const friend = await NTQQFriendApi.isBuddy(uid);
const historyResult = (await NTQQMsgApi.getMsgHistory({
chatType: friend ? ChatType.friend : ChatType.temp,
peerUid: uid
}, startMsgId, parseInt(payload.count?.toString()) || 20));
//logDebug(historyResult);
const msgList = historyResult.msgList;
const peer = { chatType: friend ? ChatType.friend : ChatType.temp, peerUid: uid };
//拉取消息
let msgList: RawMessage[];
if (!payload.message_seq || payload.message_seq == 0) {
msgList = (await NTQQMsgApi.getLastestMsgByUids(peer, MsgCount)).msgList;
} else {
const startMsgId = MessageUnique.getMsgIdAndPeerByShortId(payload.message_seq)?.MsgId;
if (!startMsgId) throw `消息${payload.message_seq}不存在`;
msgList = (await NTQQMsgApi.getMsgHistory(peer, startMsgId, MsgCount)).msgList;
}
if(isReverseOrder) msgList.reverse();
await Promise.all(msgList.map(async msg => {
msg.id = await MessageUnique.createMsg({ guildId: '', chatType: msg.chatType, peerUid: msg.peerUid }, msg.msgId);
msg.id = MessageUnique.createMsg({ guildId: '', chatType: msg.chatType, peerUid: msg.peerUid }, msg.msgId);
}));
//转换消息
const ob11MsgList = await Promise.all(msgList.map(msg => OB11Constructor.message(msg)));
return { 'messages': ob11MsgList };
}

View File

@@ -6,7 +6,7 @@ import { FromSchema, JSONSchema } from 'json-schema-to-ts';
const SchemaData = {
type: 'object',
properties: {
group_id: { type: [ 'number' , 'string' ] },
group_id: { type: ['number', 'string'] },
type: { enum: [WebHonorType.ALL, WebHonorType.EMOTION, WebHonorType.LEGEND, WebHonorType.PERFROMER, WebHonorType.STORONGE_NEWBI, WebHonorType.TALKACTIVE] }
},
required: ['group_id']

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, Peer, RawMessage } from '@/core/entities';
import { NTQQMsgApi } from '@/core/apis/msg';
import { OB11Constructor } from '../../constructor';
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
@@ -16,7 +16,8 @@ const SchemaData = {
properties: {
group_id: { type: ['number', 'string'] },
message_seq: { type: 'number' },
count: { type: 'number' }
count: { type: 'number' },
reverseOrder: { type: 'boolean' }
},
required: ['group_id']
} as const satisfies JSONSchema;
@@ -27,25 +28,28 @@ export default class GoCQHTTPGetGroupMsgHistory extends BaseAction<Payload, Resp
actionName = ActionName.GoCQHTTP_GetGroupMsgHistory;
PayloadSchema = SchemaData;
protected async _handle(payload: Payload): Promise<Response> {
//处理参数
const group = await getGroup(payload.group_id.toString());
if (!group) {
throw `${payload.group_id}不存在`;
const isReverseOrder = payload.reverseOrder || true;
const MsgCount = payload.count || 20;
const peer: Peer = { chatType: ChatType.group, peerUid: payload.group_id.toString() };
if (!group) throw `${payload.group_id}不存在`;
//拉取消息
let msgList: RawMessage[];
if (!payload.message_seq || payload.message_seq == 0) {
msgList = (await NTQQMsgApi.getLastestMsgByUids(peer, MsgCount)).msgList;
} else {
const startMsgId = MessageUnique.getMsgIdAndPeerByShortId(payload.message_seq)?.MsgId;
if (!startMsgId) throw `消息${payload.message_seq}不存在`;
msgList = (await NTQQMsgApi.getMsgHistory(peer, startMsgId, MsgCount)).msgList;
}
let targetMsgShortId, count = parseInt(payload.count?.toString() ?? '20');
const peer = {
chatType: ChatType.group,
peerUid: group.groupCode
};
if (!payload.message_seq) {
const latestMsgId = (await NTQQMsgApi.getLastestMsgByUids(peer)).msgList[0].msgId;
targetMsgShortId = await MessageUnique.createMsg(peer, latestMsgId || '0');
}
const startMsgId = (await MessageUnique.getMsgIdAndPeerByShortId(targetMsgShortId ?? (payload.message_seq ?? 0)))?.MsgId || '0';
const historyResult = (await NTQQMsgApi.getMsgHistory(peer, startMsgId, count));
const msgList = historyResult.msgList;
if(isReverseOrder) msgList.reverse();
await Promise.all(msgList.map(async msg => {
msg.id = await MessageUnique.createMsg({ guildId: '', chatType: msg.chatType, peerUid: msg.peerUid }, msg.msgId);
msg.id = MessageUnique.createMsg({ guildId: '', chatType: msg.chatType, peerUid: msg.peerUid }, msg.msgId);
}));
//转换消息
const ob11MsgList = await Promise.all(msgList.map(msg => OB11Constructor.message(msg)));
return { 'messages': ob11MsgList };
}

View File

@@ -5,6 +5,7 @@ import { ActionName } from '../types';
import { NTQQUserApi } from '@/core/apis/user';
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import { calcQQLevel } from '@/common/utils/qqlevel';
import { requireMinNTQQBuild } from '@/common/utils/QQBasicInfo';
const SchemaData = {
type: 'object',
@@ -20,24 +21,45 @@ export default class GoCQHTTPGetStrangerInfo extends BaseAction<Payload, OB11Use
actionName = ActionName.GoCQHTTP_GetStrangerInfo;
protected async _handle(payload: Payload): Promise<OB11User> {
const user_id = payload.user_id.toString();
const extendData = await NTQQUserApi.getUserDetailInfoByUin(user_id);
const uid = (await NTQQUserApi.getUidByUin(user_id))!;
if (!uid || uid.indexOf('*') != -1) {
const ret = {
...extendData,
user_id: parseInt(extendData.info.uin) || 0,
nickname: extendData.info.nick,
sex: OB11UserSex.unknown,
age: (extendData.info.birthday_year == 0) ? 0 : new Date().getFullYear() - extendData.info.birthday_year,
qid: extendData.info.qid,
level: extendData.info.qqLevel && calcQQLevel(extendData.info.qqLevel) || 0,
login_days: 0,
uid: ''
};
return ret;
if (!requireMinNTQQBuild('26702')) {
const user_id = payload.user_id.toString();
const extendData = await NTQQUserApi.getUserDetailInfoByUin(user_id);
const uid = (await NTQQUserApi.getUidByUin(user_id))!;
if (!uid || uid.indexOf('*') != -1) {
const ret = {
...extendData,
user_id: parseInt(extendData.info.uin) || 0,
nickname: extendData.info.nick,
sex: OB11UserSex.unknown,
age: (extendData.info.birthday_year == 0) ? 0 : new Date().getFullYear() - extendData.info.birthday_year,
qid: extendData.info.qid,
level: extendData.info.qqLevel && calcQQLevel(extendData.info.qqLevel) || 0,
login_days: 0,
uid: ''
};
return ret;
}
const data = { ...extendData, ...(await NTQQUserApi.getUserDetailInfo(uid)) };
return OB11Constructor.stranger(data);
} else {
const user_id = payload.user_id.toString();
const extendData = await NTQQUserApi.getUserDetailInfoByUinV2(user_id);
const uid = (await NTQQUserApi.getUidByUin(user_id))!;
if (!uid || uid.indexOf('*') != -1) {
const ret = {
...extendData,
user_id: parseInt(extendData.detail.uin) || 0,
nickname: extendData.detail.simpleInfo.coreInfo.nick,
sex: OB11UserSex.unknown,
age: 0,
level: extendData.detail.commonExt.qqLevel && calcQQLevel(extendData.detail.commonExt.qqLevel) || 0,
login_days: 0,
uid: ''
};
return ret;
}
const data = { ...extendData, ...(await NTQQUserApi.getUserDetailInfo(uid)) };
return OB11Constructor.stranger(data);
}
const data = { ...extendData, ...(await NTQQUserApi.getUserDetailInfo(uid)) };
return OB11Constructor.stranger(data);
}
}

View File

@@ -7,7 +7,7 @@ import { FromSchema, JSONSchema } from 'json-schema-to-ts';
const SchemaData = {
type: 'object',
properties: {
group_id: { type: [ 'number' , 'string' ] },
group_id: { type: ['number', 'string'] },
content: { type: 'string' },
image: { type: 'string' },
pinned: { type: 'number' },
@@ -24,8 +24,8 @@ export class SendGroupNotice extends BaseAction<Payload, null> {
let UploadImage: { id: string, width: number, height: number } | undefined = undefined;
if (payload.image) {
//公告图逻辑
const { errMsg, path, isLocal } = (await uri2local(payload.image));
if (errMsg) {
const { errMsg, path, isLocal, success } = (await uri2local(payload.image));
if (!success) {
throw `群公告${payload.image}设置失败,image字段可能格式不正确`;
}
if (!path) {
@@ -54,8 +54,6 @@ export class SendGroupNotice extends BaseAction<Payload, null> {
if (PublishGroupBulletinResult.result != 0) {
throw `设置群公告失败,错误信息:${PublishGroupBulletinResult.errMsg}`;
}
// 下面实现扬了
//await WebApi.setGroupNotice(payload.group_id, payload.content) ;
return null;
}
}

View File

@@ -4,7 +4,7 @@ import { ActionName } from '../types';
import { SendMsgElementConstructor } from '@/core/entities/constructor';
import { ChatType, SendFileElement } from '@/core/entities';
import fs from 'fs';
import { SendMsg, sendMsg } from '@/onebot11/action/msg/SendMsg';
import { sendMsg } from '@/onebot11/action/msg/SendMsg';
import { uri2local } from '@/common/utils/file';
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
const SchemaData = {
@@ -34,7 +34,7 @@ export default class GoCQHTTPUploadGroupFile extends BaseAction<Payload, null> {
file = `file://${file}`;
}
const downloadResult = await uri2local(file);
if (downloadResult.errMsg) {
if (!downloadResult.success) {
throw new Error(downloadResult.errMsg);
}
const sendFileEle: SendFileElement = await SendMsgElementConstructor.file(downloadResult.path, payload.name, payload.folder_id);

View File

@@ -1,10 +1,9 @@
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 { 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';
@@ -41,7 +40,7 @@ export default class GoCQHTTPUploadPrivateFile extends BaseAction<Payload, null>
file = `file://${file}`;
}
const downloadResult = await uri2local(file);
if (downloadResult.errMsg) {
if (!downloadResult.success) {
throw new Error(downloadResult.errMsg);
}
const sendFileEle: SendFileElement = await SendMsgElementConstructor.file(downloadResult.path, payload.name);

View File

@@ -1,9 +1,5 @@
import { getGroup } from '@/core/data';
import { OB11Group } from '../../types';
import { OB11Constructor } from '../../constructor';
import BaseAction from '../BaseAction';
import { ActionName } from '../types';
import { NTQQMsgApi } from '@/core/apis/msg';
import { GroupEssenceMsgRet, WebApi } from '@/core/apis/webapi';
import { FromSchema, JSONSchema } from 'json-schema-to-ts';

View File

@@ -2,12 +2,9 @@ import { OB11Group } from '../../types';
import { OB11Constructor } from '../../constructor';
import BaseAction from '../BaseAction';
import { ActionName } from '../types';
import { groups } from '@/core/data';
import { NTQQGroupApi } from '@/core/apis';
import { Group } from '@/core/entities';
import { log } from '@/common/utils/log';
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
// no_cache get时传字符串
const SchemaData = {
type: 'object',
properties: {
@@ -21,11 +18,7 @@ class GetGroupList extends BaseAction<Payload, OB11Group[]> {
actionName = ActionName.GetGroupList;
PayloadSchema = SchemaData;
protected async _handle(payload: Payload) {
let groupList: Group[] = Array.from(groups.values());
if (groupList.length === 0 || payload?.no_cache === true || payload.no_cache === 'true') {
groupList = await NTQQGroupApi.getGroups(true);
// log('get groups', groups);
}
const groupList: Group[] = await NTQQGroupApi.getGroups(payload?.no_cache === true || payload.no_cache === 'true');
return OB11Constructor.groups(groupList);
}
}

View File

@@ -1,5 +1,4 @@
import { OB11GroupMember } from '../../types';
import { getGroup, getGroupMember, groupMembers, selfInfo } from '@/core/data';
import { OB11Constructor } from '../../constructor';
import BaseAction from '../BaseAction';
import { ActionName } from '../types';
@@ -8,8 +7,8 @@ import { logDebug } from '@/common/utils/log';
import { WebApi } from '@/core/apis/webapi';
import { NTQQGroupApi } from '@/core';
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
// no_cache get时传字符串
import { getGroupMember, selfInfo } from '@/core/data';
import { requireMinNTQQBuild } from '@/common/utils/QQBasicInfo';
const SchemaData = {
type: 'object',
properties: {
@@ -26,17 +25,12 @@ class GetGroupMemberInfo extends BaseAction<Payload, OB11GroupMember> {
actionName = ActionName.GetGroupMemberInfo;
PayloadSchema = SchemaData;
protected async _handle(payload: Payload) {
const group = await getGroup(payload.group_id.toString());
const role = (await getGroupMember(payload.group_id, selfInfo.uin))?.role;
const isPrivilege = role === 3 || role === 4;
if (!group) {
throw (`群(${payload.group_id})不存在`);
const isNocache = payload.no_cache == true || payload.no_cache === 'true';
const uid = await NTQQUserApi.getUidByUin(payload.user_id.toString());
if (!uid) {
throw (`Uin2Uid Error ${payload.user_id}不存在`);
}
if (payload.no_cache == true || payload.no_cache === 'true') {
groupMembers.set(group.groupCode, await NTQQGroupApi.getGroupMembers(payload.group_id.toString()));
}
const member = await getGroupMember(payload.group_id.toString(), payload.user_id.toString());
//早返回
const member = await NTQQGroupApi.getGroupMemberV2(payload.group_id.toString(), uid, isNocache);
if (!member) {
throw (`群(${payload.group_id})成员${payload.user_id}不存在`);
}
@@ -47,26 +41,37 @@ class GetGroupMemberInfo extends BaseAction<Payload, OB11GroupMember> {
} catch (e) {
logDebug('获取群成员详细信息失败, 只能返回基础信息', e);
}
const date = Math.round(Date.now() / 1000);
const retMember = OB11Constructor.groupMember(payload.group_id.toString(), member);
if (isPrivilege) {
const webGroupMembers = await WebApi.getGroupMembers(payload.group_id.toString());
for (let i = 0, len = webGroupMembers.length; i < len; i++) {
if (webGroupMembers[i]?.uin && webGroupMembers[i].uin === retMember.user_id) {
retMember.join_time = webGroupMembers[i]?.join_time;
retMember.last_sent_time = webGroupMembers[i]?.last_speak_time;
retMember.qage = webGroupMembers[i]?.qage;
retMember.level = webGroupMembers[i]?.lv.level.toString();
if (!requireMinNTQQBuild('26702')) {
const SelfInfoInGroup = await NTQQGroupApi.getGroupMemberV2(payload.group_id.toString(), selfInfo.uid, isNocache);
let isPrivilege = false;
if (SelfInfoInGroup) {
isPrivilege = SelfInfoInGroup.role === 3 || SelfInfoInGroup.role === 4;
}
if (isPrivilege) {
const webGroupMembers = await WebApi.getGroupMembers(payload.group_id.toString());
for (let i = 0, len = webGroupMembers.length; i < len; i++) {
if (webGroupMembers[i]?.uin && webGroupMembers[i].uin === retMember.user_id) {
retMember.join_time = webGroupMembers[i]?.join_time;
retMember.last_sent_time = webGroupMembers[i]?.last_speak_time;
retMember.qage = webGroupMembers[i]?.qage;
retMember.level = webGroupMembers[i]?.lv.level.toString();
}
}
} else {
const LastestMsgList = await NTQQGroupApi.getLastestMsg(payload.group_id.toString(), [payload.user_id.toString()]);
if (LastestMsgList?.msgList?.length && LastestMsgList?.msgList?.length > 0) {
const last_send_time = LastestMsgList.msgList[0].msgTime;
if (last_send_time && last_send_time != '0' && last_send_time != '') {
retMember.last_sent_time = parseInt(last_send_time);
retMember.join_time = Math.round(Date.now() / 1000);//兜底数据 防止群管乱杀
}
}
}
} else {
const LastestMsgList = await NTQQGroupApi.getLastestMsg(payload.group_id.toString(), [payload.user_id.toString()]);
if (LastestMsgList?.msgList?.length && LastestMsgList?.msgList?.length > 0) {
const last_send_time = LastestMsgList.msgList[0].msgTime;
if (last_send_time && last_send_time != '0' && last_send_time != '') {
retMember.last_sent_time = parseInt(last_send_time);
retMember.join_time = Math.round(Date.now() / 1000);//兜底数据 防止群管乱杀
}
}
retMember.last_sent_time = parseInt((await getGroupMember(payload.group_id.toString(), retMember.user_id))?.lastSpeakTime || date.toString());
retMember.join_time = parseInt((await getGroupMember(payload.group_id.toString(), retMember.user_id))?.joinTime || date.toString());
}
return retMember;
}

View File

@@ -0,0 +1,84 @@
import { OB11GroupMember } from '../../types';
import { getGroup, getGroupMember, groupMembers, selfInfo } from '@/core/data';
import { OB11Constructor } from '../../constructor';
import BaseAction from '../BaseAction';
import { ActionName } from '../types';
import { NTQQUserApi } from '@/core/apis/user';
import { logDebug } from '@/common/utils/log';
import { WebApi } from '@/core/apis/webapi';
import { NTQQGroupApi } from '@/core';
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import { requireMinNTQQBuild } from '@/common/utils/QQBasicInfo';
// no_cache get时传字符串
const SchemaData = {
type: 'object',
properties: {
group_id: { type: ['number', 'string'] },
user_id: { type: ['number', 'string'] },
no_cache: { type: ['boolean', 'string'] },
},
required: ['group_id', 'user_id']
} as const satisfies JSONSchema;
type Payload = FromSchema<typeof SchemaData>;
class GetGroupMemberInfo extends BaseAction<Payload, OB11GroupMember> {
actionName = ActionName.GetGroupMemberInfo;
PayloadSchema = SchemaData;
protected async _handle(payload: Payload) {
if (requireMinNTQQBuild('26702')) {
const V2Data = await NTQQGroupApi.getGroupMemberV2(payload.group_id.toString(), payload.user_id.toString(), payload.no_cache == true || payload.no_cache === 'true');
if (V2Data) {
return OB11Constructor.groupMember(payload.group_id.toString(), V2Data);
} else {
throw (`群(${payload.group_id})成员${payload.user_id}不存在`);
}
}
const group = await getGroup(payload.group_id.toString());
const role = (await getGroupMember(payload.group_id, selfInfo.uin))?.role;
const isPrivilege = role === 3 || role === 4;
if (!group) {
throw (`群(${payload.group_id})不存在`);
}
if (payload.no_cache == true || payload.no_cache === 'true') {
groupMembers.set(group.groupCode, await NTQQGroupApi.getGroupMembers(payload.group_id.toString()));
}
const member = await getGroupMember(payload.group_id.toString(), payload.user_id.toString());
//早返回
if (!member) {
throw (`群(${payload.group_id})成员${payload.user_id}不存在`);
}
//console.log('GetGroupMemberInfo', JSON.stringify(await NTQQGroupApi.getGroupMemberV2(payload.group_id.toString(), member.uid, true), null, 4));
try {
const info = (await NTQQUserApi.getUserDetailInfo(member.uid));
logDebug('群成员详细信息结果', info);
Object.assign(member, info);
} catch (e) {
logDebug('获取群成员详细信息失败, 只能返回基础信息', e);
}
const retMember = OB11Constructor.groupMember(payload.group_id.toString(), member);
if (isPrivilege) {
const webGroupMembers = await WebApi.getGroupMembers(payload.group_id.toString());
for (let i = 0, len = webGroupMembers.length; i < len; i++) {
if (webGroupMembers[i]?.uin && webGroupMembers[i].uin === retMember.user_id) {
retMember.join_time = webGroupMembers[i]?.join_time;
retMember.last_sent_time = webGroupMembers[i]?.last_speak_time;
retMember.qage = webGroupMembers[i]?.qage;
retMember.level = webGroupMembers[i]?.lv.level.toString();
}
}
} else {
const LastestMsgList = await NTQQGroupApi.getLastestMsg(payload.group_id.toString(), [payload.user_id.toString()]);
if (LastestMsgList?.msgList?.length && LastestMsgList?.msgList?.length > 0) {
const last_send_time = LastestMsgList.msgList[0].msgTime;
if (last_send_time && last_send_time != '0' && last_send_time != '') {
retMember.last_sent_time = parseInt(last_send_time);
retMember.join_time = Math.round(Date.now() / 1000);//兜底数据 防止群管乱杀
}
}
}
return retMember;
}
}
export default GetGroupMemberInfo;

View File

@@ -1,4 +1,4 @@
import { getGroup, getGroupMember, groupMembers, selfInfo } from '@/core/data';
import { getGroup, getGroupMember, selfInfo } from '@/core/data';
import { OB11GroupMember } from '../../types';
import { OB11Constructor } from '../../constructor';
import BaseAction from '../BaseAction';
@@ -6,6 +6,7 @@ import { ActionName } from '../types';
import { NTQQGroupApi } from '@/core';
import { WebApi } from '@/core/apis/webapi';
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import { requireMinNTQQBuild } from '@/common/utils/QQBasicInfo';
const SchemaData = {
type: 'object',
@@ -22,20 +23,16 @@ class GetGroupMemberList extends BaseAction<Payload, OB11GroupMember[]> {
actionName = ActionName.GetGroupMemberList;
PayloadSchema = SchemaData;
protected async _handle(payload: Payload) {
const role = (await getGroupMember(payload.group_id, selfInfo.uin))?.role;
const isNocache = payload.no_cache == true || payload.no_cache === 'true';
const group = await getGroup(payload.group_id.toString());
const GroupList = await NTQQGroupApi.getGroups(isNocache);
const group = GroupList.find(item => item.groupCode == payload.group_id);
if (!group) {
throw (`${payload.group_id}不存在`);
}
// 从Data里面获取
let _groupMembers: OB11GroupMember[] = OB11Constructor.groupMembers(group);
if (payload.no_cache == true || payload.no_cache === 'true') {
// webGroupMembers = await WebApi.getGroupMembers(payload.group_id.toString());'
const _groupMembers = await NTQQGroupApi.getGroupMembers(payload.group_id.toString());
groupMembers.set(group.groupCode, _groupMembers);
}
const groupMembers = await NTQQGroupApi.getGroupMembers(payload.group_id.toString());
let _groupMembers = Array.from(groupMembers.values())
.map(item => { return OB11Constructor.groupMember(group.groupCode, item); });
const MemberMap: Map<number, OB11GroupMember> = new Map<number, OB11GroupMember>();
// 转为Map 方便索引
@@ -47,31 +44,47 @@ class GetGroupMemberList extends BaseAction<Payload, OB11GroupMember[]> {
MemberMap.set(_groupMembers[i].user_id, _groupMembers[i]);
}
const isPrivilege = role === 3 || role === 4;
if (isPrivilege) {
const webGroupMembers = await WebApi.getGroupMembers(payload.group_id.toString());
for (let i = 0, len = webGroupMembers.length; i < len; i++) {
if (!webGroupMembers[i]?.uin) {
continue;
if (!requireMinNTQQBuild('26702')) {
const selfRole = groupMembers.get(selfInfo.uid)?.role;
const isPrivilege = selfRole === 3 || selfRole === 4;
if (isPrivilege) {
const webGroupMembers = await WebApi.getGroupMembers(payload.group_id.toString());
for (let i = 0, len = webGroupMembers.length; i < len; i++) {
if (!webGroupMembers[i]?.uin) {
continue;
}
const MemberData = MemberMap.get(webGroupMembers[i]?.uin);
if (MemberData) {
MemberData.join_time = webGroupMembers[i]?.join_time;
MemberData.last_sent_time = webGroupMembers[i]?.last_speak_time;
MemberData.qage = webGroupMembers[i]?.qage;
MemberData.level = webGroupMembers[i]?.lv.level.toString();
MemberMap.set(webGroupMembers[i]?.uin, MemberData);
}
}
const MemberData = MemberMap.get(webGroupMembers[i]?.uin);
if (MemberData) {
MemberData.join_time = webGroupMembers[i]?.join_time;
MemberData.last_sent_time = webGroupMembers[i]?.last_speak_time;
MemberData.qage = webGroupMembers[i]?.qage;
MemberData.level = webGroupMembers[i]?.lv.level.toString();
MemberMap.set(webGroupMembers[i]?.uin, MemberData);
} else {
if (isNocache) {
const DateMap = await NTQQGroupApi.getGroupMemberLastestSendTimeCache(payload.group_id.toString());//开始从本地拉取
for (const DateUin of DateMap.keys()) {
const MemberData = MemberMap.get(parseInt(DateUin));
if (MemberData) {
MemberData.last_sent_time = parseInt(DateMap.get(DateUin)!);
//join_time 有基础数据兜底
}
}
} else {
_groupMembers.forEach(item => {
item.last_sent_time = date;
item.join_time = date;
});
}
}
} else {
const DateMap = await NTQQGroupApi.getGroupMemberLastestSendTimeCache(payload.group_id.toString());//开始从本地拉取
for (const DateUin of DateMap.keys()) {
const MemberData = MemberMap.get(parseInt(DateUin));
if (MemberData) {
MemberData.last_sent_time = parseInt(DateMap.get(DateUin)!);
//join_time 有基础数据兜底
}
}
_groupMembers.forEach(async item => {
item.last_sent_time = parseInt((await getGroupMember(payload.group_id.toString(), item.user_id))?.lastSpeakTime || date.toString());
item.join_time = parseInt((await getGroupMember(payload.group_id.toString(), item.user_id))?.joinTime || date.toString());
});
}
// 还原索引到Array 一同返回

View File

@@ -1,7 +1,7 @@
import BaseAction from '../BaseAction';
import { ActionName } from '../types';
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import { NTQQGroupApi, NTQQMsgApi } from '@/core';
import { NTQQGroupApi } from '@/core';
import { MessageUnique } from '@/common/utils/MessageUnique';
const SchemaData = {
@@ -18,7 +18,7 @@ export default class SetEssenceMsg extends BaseAction<Payload, any> {
actionName = ActionName.SetEssenceMsg;
PayloadSchema = SchemaData;
protected async _handle(payload: Payload): Promise<any> {
const msg = await MessageUnique.getMsgIdAndPeerByShortId(parseInt(payload.message_id.toString()));
const msg = MessageUnique.getMsgIdAndPeerByShortId(parseInt(payload.message_id.toString()));
if (!msg) {
throw new Error('msg not found');
}

View File

@@ -9,7 +9,7 @@ const SchemaData = {
properties: {
flag: { type: 'string' },
approve: { type: ['string', 'boolean'] },
reason: { type: 'string', nullable: true, }
reason: { type: 'string', nullable: true }
},
required: ['flag'],
} as const satisfies JSONSchema;
@@ -24,7 +24,7 @@ export default class SetGroupAddRequest extends BaseAction<Payload, null> {
const approve = payload.approve?.toString() !== 'false';
await NTQQGroupApi.handleGroupRequest(flag,
approve ? GroupRequestOperateTypes.approve : GroupRequestOperateTypes.reject,
payload.reason
payload.reason || ''
);
return null;
}

View File

@@ -35,9 +35,7 @@ import SetGroupCard from './group/SetGroupCard';
import GetImage from './file/GetImage';
import GetRecord from './file/GetRecord';
import { GoCQHTTPMarkMsgAsRead, MarkAllMsgAsRead, MarkGroupMsgAsRead, MarkPrivateMsgAsRead } from './msg/MarkMsgAsRead';
import CleanCache from './system/CleanCache';
import GoCQHTTPUploadGroupFile from './go-cqhttp/UploadGroupFile';
import { GetConfigAction, SetConfigAction } from '@/onebot11/action/extends/Config';
import GetGroupAddRequest from '@/onebot11/action/extends/GetGroupAddRequest';
import SetQQAvatar from '@/onebot11/action/extends/SetQQAvatar';
import GoCQHTTPDownloadFile from './go-cqhttp/DownloadFile';
@@ -78,8 +76,10 @@ import SetGroupHeader from './extends/SetGroupHeader';
import { FetchCustomFace } from './extends/FetchCustomFace';
import GoCQHTTPUploadPrivateFile from './go-cqhttp/UploadPrivareFile';
import TestApi01 from './extends/TestApi01';
import { FetchEmojioLike } from './extends/FetchEmojioLike';
export const actionHandlers = [
new FetchEmojioLike(),
new RebootNormal(),
new GetFile(),
new Debug(),

View File

@@ -3,12 +3,15 @@ import { ActionName } from '../types';
import BaseAction from '../BaseAction';
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import { MessageUnique } from '@/common/utils/MessageUnique';
import { sleep } from '@/common/utils/helper';
import { NTEventDispatch } from '@/common/utils/EventTask';
import { NodeIKernelMsgListener } from '@/core';
const SchemaData = {
type: 'object',
properties: {
message_id: {
oneOf:[
oneOf: [
{ type: 'number' },
{ type: 'string' }
]
@@ -23,9 +26,27 @@ class DeleteMsg extends BaseAction<Payload, void> {
actionName = ActionName.DeleteMsg;
PayloadSchema = SchemaData;
protected async _handle(payload: Payload) {
const msg = await MessageUnique.getMsgIdAndPeerByShortId(Number(payload.message_id));
const msg = MessageUnique.getMsgIdAndPeerByShortId(Number(payload.message_id));
if (msg) {
const ret = NTEventDispatch.RegisterListen<NodeIKernelMsgListener['onMsgInfoListUpdate']>
(
'NodeIKernelMsgListener/onMsgInfoListUpdate',
1,
5000,
(msgs) => {
if (msgs.find(m => m.msgId === msg.MsgId && m.recallTime !== '0')) {
return true;
}
return false;
}
).catch(e => new Promise<undefined>((resolve, reject) => { resolve(undefined); }));
await NTQQMsgApi.recallMsg(msg.Peer, [msg.MsgId]);
const data = await ret;
if (!data) {
throw new Error('Recall failed');
}
//await sleep(100);
//await NTQQMsgApi.getMsgsByMsgId(msg.Peer, [msg.MsgId]);
}
}
}

View File

@@ -30,7 +30,7 @@ class ForwardSingleMsg extends BaseAction<Payload, null> {
}
protected async _handle(payload: Payload): Promise<null> {
const msg = await MessageUnique.getMsgIdAndPeerByShortId(payload.message_id);
const msg = MessageUnique.getMsgIdAndPeerByShortId(payload.message_id);
if (!msg) {
throw new Error(`无法找到消息${payload.message_id}`);
}

View File

@@ -27,8 +27,8 @@ class GetMsg extends BaseAction<Payload, OB11Message> {
if (!payload.message_id) {
throw Error('参数message_id不能为空');
}
const MsgShortId = await MessageUnique.getShortIdByMsgId(payload.message_id.toString());
const msgIdWithPeer = await MessageUnique.getMsgIdAndPeerByShortId(MsgShortId || parseInt(payload.message_id.toString()));
const MsgShortId = MessageUnique.getShortIdByMsgId(payload.message_id.toString());
const msgIdWithPeer = MessageUnique.getMsgIdAndPeerByShortId(MsgShortId || parseInt(payload.message_id.toString()));
if (!msgIdWithPeer) {
throw ('消息不存在');
}
@@ -36,7 +36,7 @@ class GetMsg extends BaseAction<Payload, OB11Message> {
const msg = await NTQQMsgApi.getMsgsByMsgId(
peer,
[msgIdWithPeer?.MsgId || payload.message_id.toString()]);
let retMsg = 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;

View File

@@ -6,6 +6,7 @@ import {
IdMusicSignPostData,
NTQQFileApi,
NTQQMsgApi,
Peer,
SendArkElement,
SendMessageElement,
SendMsgElementConstructor,
@@ -17,19 +18,19 @@ import { uri2local } from '@/common/utils/file';
import { ob11Config } from '@/onebot11/config';
import { RequestUtil } from '@/common/utils/request';
import { MessageUnique } from '@/common/utils/MessageUnique';
console.log(process.pid);
export type MessageContext = {
group?: Group,
deleteAfterSentFiles: string[],
peer:Peer
}
async function handleOb11FileLikeMessage(
{ data: inputdata }: OB11MessageFileBase,
{ deleteAfterSentFiles }: MessageContext
) {
//有的奇怪的框架将url作为参数 而不是file 此时优先url
let { path, isLocal, fileName, errMsg } = (await uri2local(inputdata?.url || inputdata.file));
//有的奇怪的框架将url作为参数 而不是file 此时优先url 同时注意可能传入的是非file://开头的目录 By Mlikiowa
const { path, isLocal, fileName, errMsg,success } = (await uri2local(inputdata?.url || inputdata.file));
if (errMsg) {
if (!success) {
logError('文件下载失败', errMsg);
throw Error('文件下载失败' + errMsg);
}
@@ -52,12 +53,12 @@ const _handlers: {
[OB11MessageDataType.text]: async ({ data: { text } }) => SendMsgElementConstructor.text(text),
[OB11MessageDataType.at]: async ({ data: { qq: atQQ } }, context) => {
if (!context.group) return undefined;
if (!context.peer) return undefined;
if (atQQ === 'all') return SendMsgElementConstructor.at(atQQ, atQQ, AtType.atAll, '全体成员');
// then the qq is a group member
const atMember = await getGroupMember(context.group.groupCode, atQQ);
const atMember = await getGroupMember(context.peer.peerUid, atQQ);
return atMember ?
SendMsgElementConstructor.at(atQQ, atMember.uid, AtType.atUser, atMember.cardName || atMember.nick) :
undefined;
@@ -206,7 +207,7 @@ const handlers = <{
export default async function createSendElements(
messageData: OB11MessageData[],
group?: Group,
peer: Peer,
ignoreTypes: OB11MessageDataType[] = []
) {
const deleteAfterSentFiles: string[] = [];
@@ -217,7 +218,7 @@ export default async function createSendElements(
}
const callResult = handlers[sendMsg.type](
sendMsg,
{ group, deleteAfterSentFiles }
{ peer, deleteAfterSentFiles }
)?.catch(undefined);
callResultList.push(callResult);
}
@@ -228,7 +229,7 @@ export default async function createSendElements(
export async function createSendElementsParallel(
messageData: OB11MessageData[],
group?: Group,
peer: Peer,
ignoreTypes: OB11MessageDataType[] = []
) {
const deleteAfterSentFiles: string[] = [];
@@ -236,7 +237,7 @@ export async function createSendElementsParallel(
await Promise.all(
messageData.map(async sendMsg => ignoreTypes.includes(sendMsg.type) ?
undefined :
handlers[sendMsg.type](sendMsg, { group, deleteAfterSentFiles }))
handlers[sendMsg.type](sendMsg, { peer, deleteAfterSentFiles }))
).then(
results => results.filter(
element => element !== undefined

View File

@@ -1,5 +1,5 @@
import { ChatType, ElementType, Group, NTQQMsgApi, Peer, RawMessage, SendMessageElement } from '@/core';
import { OB11MessageNode } from '@/onebot11/types';
import { OB11MessageDataType, OB11MessageNode } from '@/onebot11/types';
import { selfInfo } from '@/core/data';
import createSendElements from '@/onebot11/action/msg/SendMsg/create-send-elements';
import { logDebug, logError } from '@/common/utils/log';
@@ -24,135 +24,92 @@ async function cloneMsg(msg: RawMessage): Promise<RawMessage | undefined> {
if (sendElements.length === 0) {
logDebug('需要clone的消息无法解析将会忽略掉', msg);
}
//logDebug('克隆消息', sendElements);
try {
const nodeMsg = await NTQQMsgApi.sendMsg(selfPeer, sendElements, true);
await sleep(500); // 防止风控
return nodeMsg;
} catch (e) {
logError(e, '克隆转发消息失败,将忽略本条消息', msg);
}
}
export async function handleForwardNode(destPeer: Peer, messageNodes: OB11MessageNode[], group: Group | undefined): Promise<RawMessage | null> {
export async function handleForwardNode(destPeer: Peer, messageNodes: OB11MessageNode[]): Promise<RawMessage | null> {
const selfPeer = {
chatType: ChatType.friend,
peerUid: selfInfo.uid
};
let nodeMsgIds: string[] = [];
// 先判断一遍是不是id和自定义混用
const needClone =
messageNodes.filter(node => node.data.id).length &&
messageNodes.filter(node => !node.data.id).length;
for (const messageNode of messageNodes) {
// 一个node表示一个人的消息
const nodeId = messageNode.data.id;
// 有nodeId表示一个子转发消息卡片
if (nodeId) {
const nodeMsg = MessageUnique.getMsgIdAndPeerByShortId(parseInt(nodeId));
if (!needClone) {
nodeMsgIds.push(nodeMsg!.MsgId);
} else {
if (nodeMsg!.Peer.peerUid !== selfInfo.uid) {
// need cloning
const rawClone = await NTQQMsgApi.getMsgsByMsgId(nodeMsg?.Peer!, [nodeMsg?.MsgId!]);
const clonedMsg = await cloneMsg(rawClone.msgList[0]);
if (clonedMsg) {
nodeMsgIds.push(clonedMsg.msgId);
}
}
//对Mgsid和OB11ID混用情况兜底
const nodeMsg = MessageUnique.getMsgIdAndPeerByShortId(parseInt(nodeId)) || MessageUnique.getPeerByMsgId(nodeId);
if (!nodeMsg) {
logError('转发消息失败,未找到消息', nodeId);
continue;
}
nodeMsgIds.push(nodeMsg.MsgId);
} else {
// 自定义的消息
// 提取消息段发给自己生成消息id
try {
const { sendElements } = await createSendElements(normalize(messageNode.data.content), group);
//logDebug('开始生成转发节点', sendElements);
const sendElementsSplit: SendMessageElement[][] = [];
let splitIndex = 0;
for (const sendElement of sendElements) {
if (!sendElementsSplit[splitIndex]) {
sendElementsSplit[splitIndex] = [];
}
if (sendElement.elementType === ElementType.FILE || sendElement.elementType === ElementType.VIDEO) {
if (sendElementsSplit[splitIndex].length > 0) {
splitIndex++;
}
sendElementsSplit[splitIndex] = [sendElement];
splitIndex++;
} else {
sendElementsSplit[splitIndex].push(sendElement);
}
//logDebug(sendElementsSplit);
const OB11Data = normalize(messageNode.data.content);
//筛选node消息
const isNodeMsg = OB11Data.filter(e => e.type === OB11MessageDataType.node).length;//找到子转发消息
if (isNodeMsg !== 0) {
if (isNodeMsg !== OB11Data.length) { logError('子消息中包含非node消息 跳过不合法部分'); continue; }
const nodeMsg = await handleForwardNode(selfPeer, OB11Data.filter(e => e.type === OB11MessageDataType.node));
if (nodeMsg) { nodeMsgIds.push(nodeMsg.msgId); MessageUnique.createMsg(selfPeer, nodeMsg.msgId); }
//完成子卡片生成跳过后续
continue;
}
// log("分割后的转发节点", sendElementsSplit)
const { sendElements } = await createSendElements(OB11Data, destPeer);
//拆分消息
const MixElement = sendElements.filter(element => element.elementType !== ElementType.FILE && element.elementType !== ElementType.VIDEO);
const SingleElement = sendElements.filter(element => element.elementType === ElementType.FILE || element.elementType === ElementType.VIDEO).map(e => [e]);
const AllElement: SendMessageElement[][] = [MixElement, ...SingleElement].filter(e => e !== undefined && e.length !== 0);
const MsgNodeList: Promise<RawMessage | undefined>[] = [];
for (const sendElementsSplitElement of sendElementsSplit) {
MsgNodeList.push(sendMsg(selfPeer, sendElementsSplitElement, [], true));
await sleep(Math.trunc(sendElementsSplit.length / 10) * 100);
//await sleep(10);
for (const sendElementsSplitElement of AllElement) {
MsgNodeList.push(sendMsg(selfPeer, sendElementsSplitElement, [], true).catch(e => new Promise((resolve, reject) => { resolve(undefined); })));
}
for (const msgNode of MsgNodeList) {
const result = await msgNode;
if (result) {
nodeMsgIds.push(result.msgId);
(await Promise.allSettled(MsgNodeList)).map((result) => {
if (result.status === 'fulfilled' && result.value) {
nodeMsgIds.push(result.value.msgId);
MessageUnique.createMsg(selfPeer, result.value.msgId);
}
//logDebug('转发节点生成成功', result.msgId);
}
});
} catch (e) {
logDebug('生成转发消息节点失败', e);
}
}
}
// 检查srcPeer是否一致不一致则需要克隆成自己的消息, 让所有srcPeer都变成自己的使其保持一致才能够转发
const nodeMsgArray: Array<RawMessage> = [];
let srcPeer: Peer | undefined = undefined;
let needSendSelf = false;
//检测是否处于同一个Peer 不在同一个peer则全部消息由自身发送
for (const msgId of nodeMsgIds) {
const nodeMsgPeer = await MessageUnique.getPeerByMsgId(msgId);
const nodeMsg = (await NTQQMsgApi.getMsgsByMsgId(nodeMsgPeer?.Peer!, [msgId])).msgList[0];
if (nodeMsg) {
nodeMsgArray.push(nodeMsg);
if (!srcPeer) {
srcPeer = { chatType: nodeMsg.chatType, peerUid: nodeMsg.peerUid };
} else if (srcPeer.peerUid !== nodeMsg.peerUid) {
needSendSelf = true;
srcPeer = selfPeer;
}
const nodeMsgPeer = MessageUnique.getPeerByMsgId(msgId);
if (!nodeMsgPeer) {
logError('转发消息失败,未找到消息', msgId);
continue;
}
const nodeMsg = (await NTQQMsgApi.getMsgsByMsgId(nodeMsgPeer.Peer, [msgId])).msgList[0];
srcPeer = srcPeer ?? { chatType: nodeMsg.chatType, peerUid: nodeMsg.peerUid };
if (srcPeer.peerUid !== nodeMsg.peerUid) {
needSendSelf = true;
}
nodeMsgArray.push(nodeMsg);
}
// logDebug('nodeMsgArray', nodeMsgArray);
nodeMsgIds = nodeMsgArray.map(msg => msg.msgId);
let retMsgIds: string[] = [];
if (needSendSelf) {
//logDebug('需要克隆转发消息');
for (const [index, msg] of nodeMsgArray.entries()) {
if (msg.peerUid !== selfInfo.uid) {
const clonedMsg = await cloneMsg(msg);
if (clonedMsg) {
nodeMsgIds[index] = clonedMsg.msgId;
}
}
if (msg.peerUid === selfInfo.uid) continue;
const ClonedMsg = await cloneMsg(msg);
if (ClonedMsg) retMsgIds.push(ClonedMsg.msgId);
}
} else {
retMsgIds = nodeMsgIds;
}
// elements之间用换行符分隔
// let _sendForwardElements: SendMessageElement[] = []
// for(let i = 0; i < sendForwardElements.length; i++){
// _sendForwardElements.push(sendForwardElements[i])
// _sendForwardElements.push(SendMsgElementConstructor.text("\n\n"))
// }
// const nodeMsg = await NTQQApi.sendMsg(selfPeer, _sendForwardElements, true);
// nodeIds.push(nodeMsg.msgId)
// await sleep(500);
// 开发转发
if (nodeMsgIds.length === 0) {
throw Error('转发消息失败,生成节点为空');
}
if (nodeMsgIds.length === 0) throw Error('转发消息失败,生成节点为空');
try {
logDebug('开发转发', srcPeer, destPeer, nodeMsgIds);
return await NTQQMsgApi.multiForwardMsg(srcPeer!, destPeer, nodeMsgIds);

View File

@@ -10,6 +10,7 @@ import { ActionName, BaseCheckResult } from '@/onebot11/action/types';
import { getGroup } from '@/core/data';
import { ChatType, ElementType, Group, NTQQFileApi, NTQQFriendApi, NTQQMsgApi, NTQQUserApi, Peer, SendMessageElement, } from '@/core';
import fs from 'node:fs';
import fsPromise from 'node:fs/promises';
import { logDebug, logError } from '@/common/utils/log';
import { decodeCQCode } from '@/onebot11/cqcode';
import createSendElements from './create-send-elements';
@@ -66,25 +67,16 @@ export async function sendMsg(peer: Peer, sendElements: SendMessageElement[], de
}
const returnMsg = await NTQQMsgApi.sendMsg(peer, sendElements, waitComplete, timeout);
try {
returnMsg!.id = await MessageUnique.createMsg({ chatType: peer.chatType, guildId: '', peerUid: peer.peerUid }, returnMsg!.msgId);
returnMsg!.id = MessageUnique.createMsg({ chatType: peer.chatType, guildId: '', peerUid: peer.peerUid }, returnMsg!.msgId);
} catch (e: any) {
logDebug('发送消息id获取失败', e);
returnMsg!.id = 0;
}
deleteAfterSentFiles.map((f) => {
try {
fs.unlinkSync(f);
} catch (e) {
logError('发送消息删除文件失败', e);
}
});
deleteAfterSentFiles.map((f) => { fsPromise.unlink(f).then().catch(e => logError('发送消息删除文件失败', e)); });
return returnMsg;
}
async function createContext(payload: OB11PostSendMsg, contextMode: ContextMode): Promise<{
peer: Peer, group?: Group
}> {
async function createContext(payload: OB11PostSendMsg, contextMode: ContextMode): Promise<Peer> {
// This function determines the type of message by the existence of user_id / group_id,
// not message_type.
// This redundant design of Ob11 here should be blamed.
@@ -92,11 +84,8 @@ async function createContext(payload: OB11PostSendMsg, contextMode: ContextMode)
if ((contextMode === ContextMode.Group || contextMode === ContextMode.Normal) && payload.group_id) {
const group = (await getGroup(payload.group_id))!; // checked before
return {
peer: {
chatType: ChatType.group,
peerUid: group.groupCode
},
group: group,
chatType: ChatType.group,
peerUid: group.groupCode
};
}
if ((contextMode === ContextMode.Private || contextMode === ContextMode.Normal) && payload.user_id) {
@@ -104,10 +93,9 @@ async function createContext(payload: OB11PostSendMsg, contextMode: ContextMode)
const isBuddy = await NTQQFriendApi.isBuddy(Uid!);
//console.log("[调试代码] UIN:", payload.user_id, " UID:", Uid, " IsBuddy:", isBuddy);
return {
peer: {
chatType: isBuddy ? ChatType.friend : ChatType.temp,
peerUid: Uid!
},
chatType: isBuddy ? ChatType.friend : ChatType.temp,
peerUid: Uid!,
guildId: payload.group_id || ''//临时主动发起时需要传入群号
};
}
throw '请指定 group_id 或 user_id';
@@ -134,7 +122,7 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
return { valid: false, message: `${payload.group_id}不存在` };
}
if (payload.user_id && payload.message_type !== 'group') {
const uid = await NTQQUserApi.getUidByUin(payload.user_id);
const uid = await NTQQUserApi.getUidByUin(payload.user_id.toString());
const isBuddy = await NTQQFriendApi.isBuddy(uid!);
// 此处有问题
if (!isBuddy) {
@@ -145,7 +133,7 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
}
protected async _handle(payload: OB11PostSendMsg): Promise<{ message_id: number }> {
const { peer, group } = await createContext(payload, this.contextMode);
const peer = await createContext(payload, this.contextMode);
const messages = normalize(
payload.message,
@@ -153,9 +141,9 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
);
if (getSpecialMsgNum(payload, OB11MessageDataType.node)) {
const returnMsg = await handleForwardNode(peer, messages as OB11MessageNode[], group);
const returnMsg = await handleForwardNode(peer, messages as OB11MessageNode[]);
if (returnMsg) {
const msgShortId = await MessageUnique.createMsg({ guildId: '', peerUid: peer.peerUid, chatType: peer.chatType }, returnMsg!.msgId);
const msgShortId = MessageUnique.createMsg({ guildId: '', peerUid: peer.peerUid, chatType: peer.chatType }, returnMsg!.msgId);
return { message_id: msgShortId! };
} else {
throw Error('发送转发消息失败');
@@ -169,7 +157,7 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
}
// log("send msg:", peer, sendElements)
const { sendElements, deleteAfterSentFiles } = await createSendElements(messages, group);
const { sendElements, deleteAfterSentFiles } = await createSendElements(messages, peer);
//console.log(peer, JSON.stringify(sendElements,null,2));
const returnMsg = await sendMsg(peer, sendElements, deleteAfterSentFiles);
return { message_id: returnMsg!.id! };

View File

@@ -11,5 +11,4 @@ class SendPrivateMsg extends SendMsg {
return super.check(payload);
}
}
export default SendPrivateMsg;

View File

@@ -14,7 +14,6 @@ const SchemaData = {
} as const satisfies JSONSchema;
type Payload = FromSchema<typeof SchemaData>;
export class SetMsgEmojiLike extends BaseAction<Payload, any> {
actionName = ActionName.SetMsgEmojiLike;
PayloadSchema = SchemaData;

View File

@@ -4,7 +4,6 @@ import CanSendRecord from './CanSendRecord';
interface ReturnType {
yes: boolean
}
export default class CanSendImage extends CanSendRecord {
actionName = ActionName.CanSendImage;
}

View File

@@ -3,8 +3,6 @@ import { OB11User } from '../../types';
import { OB11Constructor } from '../../constructor';
import BaseAction from '../BaseAction';
import { ActionName } from '../types';
import { napCatCore } from '@/core';
class GetLoginInfo extends BaseAction<null, OB11User> {
actionName = ActionName.GetLoginInfo;

View File

@@ -15,8 +15,8 @@ export interface InvalidCheckResult {
export enum ActionName {
// 以下为扩展napcat扩展
SharePeer = 'ArkShareGroup',
ShareGroupEx = 'ArkSharePeer',
SharePeer = 'ArkSharePeer',
ShareGroupEx = 'ArkShareGroupEx',
RebootNormal = 'reboot_normal',//无快速登录重新启动
GetRobotUinRange = 'get_robot_uin_range',
SetOnlineStatus = 'set_online_status',
@@ -102,5 +102,6 @@ export enum ActionName {
SetGroupHeader = 'set_group_head',
FetchCustomFace = 'fetch_custom_face',
GOCQHTTP_UploadPrivateFile = 'upload_private_file',
TestApi01 = "test_api_01"
TestApi01 = 'test_api_01',
FetchEmojioLike = 'fetch_emoji_like'
}

View File

@@ -5,8 +5,7 @@ import BaseAction from '../BaseAction';
import { ActionName } from '../types';
import { NTQQFriendApi } from '@/core';
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import { requireMinNTQQBuild } from '@/common/utils/QQBasicInfo';
// no_cache get时传字符串
const SchemaData = {
type: 'object',
@@ -20,6 +19,10 @@ export default class GetFriendList extends BaseAction<Payload, OB11User[]> {
actionName = ActionName.GetFriendList;
PayloadSchema = SchemaData;
protected async _handle(payload: Payload) {
if (requireMinNTQQBuild('26702')) {
//全新逻辑
return OB11Constructor.friendsV2(await NTQQFriendApi.getBuddyV2(payload?.no_cache === true || payload?.no_cache === 'true'));
}
if (friends.size === 0 || payload?.no_cache === true || payload?.no_cache === 'true') {
const _friends = await NTQQFriendApi.getFriends(true);
// log('强制刷新好友列表,结果: ', _friends)

View File

@@ -1,12 +1,52 @@
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import BaseAction from '../BaseAction';
import { ActionName } from '../types';
import { NTQQUserApi } from '@/core';
import { NTQQMsgApi, NTQQUserApi } from '@/core';
import { OB11Constructor } from '@/onebot11/constructor';
export default class GetRecentContact extends BaseAction<void, any> {
const SchemaData = {
type: 'object',
properties: {
count: { type: ['number', 'string'] }
}
} as const satisfies JSONSchema;
type Payload = FromSchema<typeof SchemaData>;
export default class GetRecentContact extends BaseAction<Payload, any> {
actionName = ActionName.GetRecentContact;
protected async _handle(payload: void) {
//没有效果
return await NTQQUserApi.getRecentContactListSnapShot(10);
PayloadSchema = SchemaData;
protected async _handle(payload: Payload) {
const ret = await NTQQUserApi.getRecentContactListSnapShot(parseInt((payload.count || 10).toString()));
const data = await Promise.all(ret.info.changedList.map(async (t) => {
const FastMsg = await NTQQMsgApi.getMsgsByMsgId({ chatType: t.chatType, peerUid: t.peerUid }, [t.msgId]);
if (FastMsg.msgList.length > 0) {
//扩展ret.info.changedList
const lastestMsg = await OB11Constructor.message(FastMsg.msgList[0]);
return {
lastestMsg: lastestMsg,
peerUin: t.peerUin,
remark: t.remark,
msgTime: t.msgTime,
chatType: t.chatType,
msgId: t.msgId,
sendNickName: t.sendNickName,
sendMemberName: t.sendMemberName,
peerName: t.peerName
};
}
return {
peerUin: t.peerUin,
remark: t.remark,
msgTime: t.msgTime,
chatType: t.chatType,
msgId: t.msgId,
sendNickName: t.sendNickName,
sendMemberName: t.sendMemberName,
peerName: t.peerName
};
}));
return data;
}
}

View File

@@ -13,7 +13,6 @@ const SchemaData = {
} as const satisfies JSONSchema;
type Payload = FromSchema<typeof SchemaData>;
export default class SendLike extends BaseAction<Payload, null> {
actionName = ActionName.SendLike;
PayloadSchema = SchemaData;

View File

@@ -1,10 +1,4 @@
import fs from 'node:fs';
import path from 'node:path';
import { selfInfo } from '@/core/data';
import { logDebug, logError } from '@/common/utils/log';
import { ConfigBase } from '@/common/utils/ConfigBase';
import { json } from 'stream/consumers';
export interface OB11Config {
http: {
enable: boolean;
@@ -36,7 +30,7 @@ export interface OB11Config {
Record: boolean,
RecordList: Array<string>
},
read(): OB11Config;
read(): OB11Config | null;
save(config: OB11Config): void;
}

View File

@@ -12,8 +12,10 @@ import {
import {
AtType,
ChatType,
ChatType2,
FaceIndex,
Friend,
FriendV2,
GrayTipElementSubType,
Group,
GroupMember,
@@ -22,6 +24,7 @@ import {
RawMessage,
SelfInfo,
Sex,
SimpleInfo,
TipGroupElementType,
User,
VideoElement
@@ -40,7 +43,7 @@ import { OB11GroupTitleEvent } from './event/notice/OB11GroupTitleEvent';
import { OB11GroupCardEvent } from './event/notice/OB11GroupCardEvent';
import { OB11GroupDecreaseEvent } from './event/notice/OB11GroupDecreaseEvent';
import { ob11Config } from '@/onebot11/config';
import { deleteGroup, getGroupMember, groupMembers, selfInfo, tempGroupCodeMap } from '@/core/data';
import { deleteGroup, getGroupMember, groupMembers, selfInfo } from '@/core/data';
import { NTQQFileApi, NTQQGroupApi, NTQQMsgApi, NTQQUserApi } from '@/core/apis';
import { OB11GroupMsgEmojiLikeEvent } from '@/onebot11/event/notice/OB11MsgEmojiLikeEvent';
import { OB11FriendPokeEvent, OB11GroupPokeEvent } from './event/notice/OB11PokeEvent';
@@ -63,7 +66,7 @@ export class OB11Constructor {
real_id: msg.id!,
message_type: msg.chatType == ChatType.group ? 'group' : 'private',
sender: {
user_id: parseInt(msg.senderUin!),
user_id: parseInt(msg.senderUin || '0'),
nickname: msg.sendNickName,
card: msg.sendMemberName || '',
},
@@ -90,44 +93,56 @@ export class OB11Constructor {
}
else if (msg.chatType == ChatType.friend) {
resMsg.sub_type = 'friend';
const user = await NTQQUserApi.getUserDetailInfoByUin(msg.senderUin!);
resMsg.sender.nickname = user.info.nick;
resMsg.sender.nickname = (await NTQQUserApi.getUserDetailInfo(msg.senderUid)).nick;
//const user = await NTQQUserApi.getUserDetailInfoByUin(msg.senderUin!);
//resMsg.sender.nickname = user.info.nick;
}
else if (msg.chatType == ChatType.temp) {
else if (msg.chatType as unknown as ChatType2 == ChatType2.KCHATTYPETEMPC2CFROMGROUP) {
resMsg.sub_type = 'group';
const tempGroupCode = tempGroupCodeMap[msg.peerUin];
if (tempGroupCode) {
resMsg.group_id = parseInt(tempGroupCode);
let ret = await NTQQMsgApi.getTempChatInfo(ChatType2.KCHATTYPETEMPC2CFROMGROUP, msg.senderUid);
if (ret.result === 0) {
resMsg.group_id = parseInt(ret.tmpChatInfo!.groupCode);
resMsg.sender.nickname = ret.tmpChatInfo!.fromNick;
} else {
resMsg.group_id = 284840486;//兜底数据
resMsg.sender.nickname = "临时会话";
}
}
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()) {
@@ -140,37 +155,38 @@ export class OB11Constructor {
message_data['data']['text'] = text;
}
else if (element.replyElement) {
message_data['type'] = 'reply';
message_data['type'] = OB11MessageDataType.reply;
//log("收到回复消息", element.replyElement);
try {
//做这么多都是因为NC速度太快 可能nt还没有写入数据库
//const records = msg.records.find(msgRecord => msgRecord.msgId === element.replyElement.sourceMsgIdInRecords);
const records = msg.records.find(msgRecord => msgRecord.msgId === element.replyElement.sourceMsgIdInRecords);
const peer = {
chatType: msg.chatType,
peerUid: msg.peerUid,
guildId: '',
};
let replyMsg: RawMessage | undefined;
if (!records) throw new Error('找不到回复消息');
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) {
if (!replyMsg || records.msgRandom !== replyMsg.msgRandom) {
replyMsg = (await NTQQMsgApi.getSingleMsg(peer, element.replyElement.replayMsgSeq)).msgList[0];
}
if (!replyMsg || element.replyElement.replayMsgSeq !== replyMsg.msgSeq) {
if (msg.peerUin == '284840486') {
//合并消息内侧 消息具体定位不到
}
if ((!replyMsg || records.msgRandom !== replyMsg.msgRandom) && msg.peerUin !== '284840486') {
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";
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['data']['subType'] = element.picElement.picSubType;
@@ -212,12 +228,18 @@ export class OB11Constructor {
const videoElement: VideoElement = element.videoElement;
//读取视频链接并兜底
let videoUrl;//Array
let peer:Peer = {
chatType: msg.chatType,
peerUid: msg.peerUid,
guildId: '0'
};
if (msg.peerUin == '284840486') {
peer = msg.parentMsgPeer;
//合并消息内部 应该进行特殊处理 可能需要重写peer 待测试与研究 Mlikiowa Taged TODO
}
try {
videoUrl = await NTQQFileApi.getVideoUrl({
chatType: msg.chatType,
peerUid: msg.peerUid,
guildId: '0'
}, msg.msgId, element.elementId);
videoUrl = await NTQQFileApi.getVideoUrl(peer, msg.msgId, element.elementId);
} catch (error) {
videoUrl = undefined;
}
@@ -318,8 +340,29 @@ export class OB11Constructor {
else if (element.multiForwardMsgElement) {
message_data['type'] = OB11MessageDataType.forward;
message_data['data']['id'] = msg.msgId;
const ParentMsgPeer = msg.parentMsgPeer ?? { chatType: msg.chatType, guildId: '', peerUid: msg.peerUid };
//判断是否在合并消息内
msg.parentMsgIdList = msg.parentMsgIdList ?? [];
//首次列表不存在则开始创建
msg.parentMsgIdList.push(msg.msgId);
//let parentMsgId = msg.parentMsgIdList[msg.parentMsgIdList.length - 2 < 0 ? 0 : msg.parentMsgIdList.length - 2];
//加入自身MsgId
const MultiMsgs = (await NTQQMsgApi.getMultiMsg(ParentMsgPeer, msg.parentMsgIdList[0], msg.msgId))?.msgList;
//拉取下级消息
if (!MultiMsgs) continue;
//拉取失败则跳过
message_data['data']['content'] = [];
for (const MultiMsg of MultiMsgs) {
//对每条拉取的消息传递ParentMsgPeer修正Peer
MultiMsg.parentMsgPeer = ParentMsgPeer;
MultiMsg.parentMsgIdList = msg.parentMsgIdList;
MultiMsg.id = MessageUnique.createMsg(ParentMsgPeer, MultiMsg.msgId);//该ID仅用查看 无法调用
const msgList = await OB11Constructor.message(MultiMsg);
message_data['data']['content'].push(msgList);
//console.log("合并消息", msgList);
}
}
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') {
@@ -449,6 +492,7 @@ export class OB11Constructor {
});
}
if (grayTipElement) {
//console.log('收到群提示消息', grayTipElement);
if (grayTipElement.xmlElement?.templId === '10382') {
const emojiLikeData = new fastXmlParser.XMLParser({
ignoreAttributes: false,
@@ -545,7 +589,24 @@ export class OB11Constructor {
nickname: selfInfo.nick,
};
}
static friendsV2(friends: FriendV2[]): OB11User[] {
const data: OB11User[] = [];
friends.forEach(friend => {
const sexValue = this.sex(friend.baseInfo.sex!);
data.push({
...friend.baseInfo,
...friend.coreInfo,
user_id: parseInt(friend.coreInfo.uin),
nickname: friend.coreInfo.nick,
remark: friend.coreInfo.nick,
sex: sexValue,
level: 0,
categroyName: friend.categroyName,
categoryId: friend.categoryId
});
});
return data;
}
static friends(friends: Friend[]): OB11User[] {
const data: OB11User[] = [];
friends.forEach(friend => {

View File

@@ -66,16 +66,16 @@ export function encodeCQCode(data: OB11MessageData) {
let result = '[CQ:' + data.type;
for (const name in data.data) {
let value = data.data[name];
try {
// Check if the value can be converted to a string
value = 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}`);
const value = data.data[name];
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

@@ -2,5 +2,5 @@ import { OB11GroupNoticeEvent } from './OB11GroupNoticeEvent';
export class OB11GroupAdminNoticeEvent extends OB11GroupNoticeEvent {
notice_type = 'group_admin';
sub_type: 'set' | 'unset'; // "set" | "unset"
sub_type: 'set' | 'unset' = 'set'; // "set" | "unset"
}

View File

@@ -0,0 +1,16 @@
import { OB11BaseNoticeEvent } from './OB11BaseNoticeEvent';
//输入状态事件 初步完成 Mlikio wa Todo 需要做一些过滤
export class OB11InputStatusEvent extends OB11BaseNoticeEvent {
notice_type = 'notify';
sub_type = 'input_status';
status_text = '对方正在输入...';
event_type = 1;
user_id = 0;
constructor(user_id: number, eventType: number, status_text: string) {
super();
this.user_id = user_id;
this.event_type = eventType;
this.status_text = status_text;
}
}

View File

@@ -8,7 +8,6 @@ export interface MsgEmojiLike {
export class OB11GroupMsgEmojiLikeEvent extends OB11GroupNoticeEvent {
notice_type = 'group_msg_emoji_like';
message_id: number;
sub_type: 'ban' | 'lift_ban';
likes: MsgEmojiLike[];
constructor(groupId: number, userId: number, messageId: number, likes: MsgEmojiLike[]) {

View File

@@ -1,6 +1,4 @@
import { OB11BaseNoticeEvent } from './OB11BaseNoticeEvent';
import { selfInfo } from '@/core/data';
import { OB11BaseEvent } from '../OB11BaseEvent';
class OB11PokeEvent extends OB11BaseNoticeEvent {
notice_type = 'notify';

View File

@@ -2,7 +2,7 @@ import { OB11Message } from '@/onebot11/types';
import { log } from '@/common/utils/log';
import { getGroup, getGroupMember, selfInfo } from '@/core/data';
import exp from 'constants';
import { Group } from '@/core';
import { Group, NTQQMsgApi } from '@/core';
import chalk from 'chalk';
const spSegColor = chalk.blue;// for special segment
@@ -24,8 +24,13 @@ export async function logMessage(ob11Message: OB11Message) {
}
}
if (ob11Message.message_type === 'group') {
group = await getGroup(ob11Message.group_id!);
prefix += `群[${group?.groupName}(${ob11Message.group_id})] `;
if (ob11Message.group_id == 284840486) {
group = await getGroup(ob11Message.group_id!);
prefix += '转发消息[外部来源] ';
} else {
group = await getGroup(ob11Message.group_id!);
prefix += `群[${group?.groupName}(${ob11Message.group_id})] `;
}
}
let msgChain = '';
if (Array.isArray(ob11Message.message)) {
@@ -66,6 +71,13 @@ export async function logMessage(ob11Message: OB11Message) {
else if (segment.type === 'video') {
msgParts.push(spSegColor(`[视频|${segment.data.url}]`));
}
else if (segment.type === 'forward') {
msgParts.push(spSegColor(`[转发|${segment.data.id}|消息开始]`));
segment.data.content.forEach((msg) => {
logMessage(msg);
});
msgParts.push(spSegColor(`[转发|${segment.data.id}|消息结束]`));
}
else {
msgParts.push(spSegColor(`[未实现|${JSON.stringify(segment)}]`));
}

View File

@@ -19,10 +19,10 @@ import { OB11Config, ob11Config } from '@/onebot11/config';
import { httpHeart, ob11HTTPServer } from '@/onebot11/server/http';
import { ob11WebsocketServer } from '@/onebot11/server/ws/WebsocketServer';
import { ob11ReverseWebsockets } from '@/onebot11/server/ws/ReverseWebsocket';
import { getGroup, getGroupMember, groupMembers, selfInfo, tempGroupCodeMap } from '@/core/data';
import { getGroup, getGroupMember, groupMembers, selfInfo } from '@/core/data';
import { BuddyListener, GroupListener, NodeIKernelBuddyListener } from '@/core/listeners';
import { OB11FriendRequestEvent } from '@/onebot11/event/request/OB11FriendRequest';
import { NTQQGroupApi, NTQQUserApi } from '@/core/apis';
import { NTQQGroupApi, NTQQUserApi, WebApi } from '@/core/apis';
import { log, logDebug, logError, setLogSelfInfo } from '@/common/utils/log';
import { OB11GroupRequestEvent } from '@/onebot11/event/request/OB11GroupRequest';
import { OB11GroupAdminNoticeEvent } from '@/onebot11/event/notice/OB11GroupAdminNoticeEvent';
@@ -33,6 +33,7 @@ import { logMessage, logNotice, logRequest } from '@/onebot11/log';
import { OB11Message } from '@/onebot11/types';
import { isEqual } from '@/common/utils/helper';
import { MessageUnique } from '@/common/utils/MessageUnique';
import { OB11InputStatusEvent } from './event/notice/OB11InputStatusEvent';
//下面几个其实应该移进Core-Data 缓存实现 但是现在在这里方便
//
@@ -92,12 +93,29 @@ export class NapCatOnebot11 {
}
// Create MsgListener
const msgListener = new MsgListener();
msgListener.onInputStatusPush = async (data: {
chatType: number;
eventType: number;
fromUin: string;
interval: string;
showTime: string;
statusText: string;
timestamp: string;
toUin: string;
}
) => {
const uin = await NTQQUserApi.getUinByUid(data.fromUin);
logNotice(`[输入状态] ${uin} ${data.statusText}`);
postOB11Event(new OB11InputStatusEvent(parseInt(uin), data.eventType, data.statusText));
};
msgListener.onRecvSysMsg = async (protobufData: number[]) => {
// function buf2hex(buffer: Buffer) {
// return [...new Uint8Array(buffer)]
// .map(x => x.toString(16).padStart(2, '0'))
// .join('');
// }
// const hex = buf2hex(Buffer.from(protobufData));
// console.log(hex);
// // let Data: Data = {
// // header: {
// // GroupNumber: 0,
@@ -192,6 +210,7 @@ export class NapCatOnebot11 {
// // 732 17 GroupRecall
// // 732 20 GroupCommonTips
// // 732 21 EssenceMessage
// // 732 16 16 GrayTips ->Busi->GroupEmjioLike
// } catch (e) {
// log('解析SysMsg异常', e);
// // console.log(e);
@@ -203,12 +222,13 @@ export class NapCatOnebot11 {
selfInfo.online = false;
};
msgListener.onTempChatInfoUpdate = (tempChatInfo: TempOnRecvParams) => {
if (tempChatInfo.sessionType == 1 && tempChatInfo.chatType == ChatType.temp) {
tempGroupCodeMap[tempChatInfo.peerUid] = tempChatInfo.groupCode;
}
// if (tempChatInfo.sessionType == 1 && tempChatInfo.chatType == ChatType.temp) {
// tempGroupCodeMap[tempChatInfo.peerUid] = tempChatInfo.groupCode;
// }
// 临时会话更新 tempGroupCodeMap uid -> source/GroupCode
};
msgListener.onRecvMsg = async (msg) => {
//console.log('ob11 onRecvMsg', JSON.stringify(msg, null, 2));
// logDebug('收到消息', msg);
for (const m of msg) {
@@ -290,7 +310,7 @@ export class NapCatOnebot11 {
}
};
groupListener.onMemberInfoChange = async (groupCode: string, changeType: number, members: Map<string, GroupMember>) => {
//console.log("ob11 onMemberInfoChange", groupCode, changeType, members)
// console.log("ob11 onMemberInfoChange", groupCode, changeType, members)
if (changeType === 1) {
let member;
for (const [key, value] of members) {

View File

@@ -10,8 +10,8 @@ import { postOB11Event } from '@/onebot11/server/postOB11Event';
class OB11HTTPServer extends HttpServerBase {
name = 'OneBot V11 server';
handleFailed(res: Response, payload: any, e: any) {
res.send(OB11Response.error(e.stack.toString(), 200));
handleFailed(res: Response, payload: any, e: Error) {
res.send(OB11Response.error(e?.stack?.toString() || e.message || 'Error Handle', 200));
}
protected listen(port: number, host: string) {

View File

@@ -159,7 +159,7 @@ async function handleMsg(msg: OB11Message, quickAction: QuickAction) {
}
}
replyMessage = replyMessage.concat(normalize(reply, quickAction.auto_escape));
const { sendElements, deleteAfterSentFiles } = await createSendElements(replyMessage, group);
const { sendElements, deleteAfterSentFiles } = await createSendElements(replyMessage, peer);
sendMsg(peer, sendElements, deleteAfterSentFiles, false).then().catch(logError);
}
}

View File

@@ -7,6 +7,8 @@ export interface OB11User {
age?: number;
qid?: string;
login_days?: number;
categroyName?:string;
categoryId?:number;
}
export enum OB11UserSex {

View File

@@ -114,6 +114,7 @@ export interface OB11MessageAt {
type: OB11MessageDataType.at
data: {
qq: `${number}` | 'all'
name?: string
}
}
@@ -177,13 +178,21 @@ export interface OB11MessageMarkdown {
}
}
export interface OB11MessageForward {
type: OB11MessageDataType.forward
data: {
id: string,
content: OB11Message[]
}
}
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'

Some files were not shown because too many files have changed in this diff Show More