mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2025-07-19 12:03:37 +00:00
Compare commits
397 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
e7222653fa | ||
![]() |
014f0758f5 | ||
![]() |
0e8b416f6d | ||
![]() |
09a60a2204 | ||
![]() |
b0eae307c2 | ||
![]() |
f5d2b54cca | ||
![]() |
3eefec3899 | ||
![]() |
b6a8094554 | ||
![]() |
4083b35436 | ||
![]() |
bb72d70baf | ||
![]() |
95d1a77f52 | ||
![]() |
051729886e | ||
![]() |
0f00123dc7 | ||
![]() |
0b0a089d86 | ||
![]() |
c711a7d99a | ||
![]() |
43f1d8c88c | ||
![]() |
e818e79d20 | ||
![]() |
cbad3ff1de | ||
![]() |
16a2e5e996 | ||
![]() |
331c6a50d0 | ||
![]() |
31c4540ec6 | ||
![]() |
1e6116554f | ||
![]() |
a12ea0e761 | ||
![]() |
c9e3bbcd9f | ||
![]() |
9c17dc1b8f | ||
![]() |
69d1cae686 | ||
![]() |
1c2404b6af | ||
![]() |
b33b33739d | ||
![]() |
2b7886c682 | ||
![]() |
106d1f6374 | ||
![]() |
e601786bd7 | ||
![]() |
fda2a98b40 | ||
![]() |
c01d70b8fc | ||
![]() |
eccbcc3e28 | ||
![]() |
7a4a255a89 | ||
![]() |
83bced82b1 | ||
![]() |
f3033ce732 | ||
![]() |
5c21a1727c | ||
![]() |
93aab437b7 | ||
![]() |
34e797270f | ||
![]() |
0f337a8d8c | ||
![]() |
cc9b83089e | ||
![]() |
a565929686 | ||
![]() |
6adacea774 | ||
![]() |
47ab5421ed | ||
![]() |
10c404d455 | ||
![]() |
dfdca11155 | ||
![]() |
698e095364 | ||
![]() |
524fd258d8 | ||
![]() |
17e70a4360 | ||
![]() |
e4a533e7b7 | ||
![]() |
0cb68d3737 | ||
![]() |
9faeadbebe | ||
![]() |
35d201cfb8 | ||
![]() |
205174255f | ||
![]() |
8873a030ab | ||
![]() |
0ab61bac12 | ||
![]() |
b1157f60f5 | ||
![]() |
bb93df06b2 | ||
![]() |
82e807fd80 | ||
![]() |
29da539467 | ||
![]() |
659aa005b0 | ||
![]() |
3f20733e7e | ||
![]() |
b15e1174d6 | ||
![]() |
05b05fd74e | ||
![]() |
d30d467a21 | ||
![]() |
cd62e8ca37 | ||
![]() |
f9e44820c1 | ||
![]() |
169ae6a4d0 | ||
![]() |
030ba15952 | ||
![]() |
964874bdad | ||
![]() |
7affa081ac | ||
![]() |
10e281ed35 | ||
![]() |
27081ae599 | ||
![]() |
61cbcdffe8 | ||
![]() |
eeb15ea564 | ||
![]() |
565c820925 | ||
![]() |
325dff5735 | ||
![]() |
397c2cf5f0 | ||
![]() |
1fbc339a42 | ||
![]() |
f2c719c60d | ||
![]() |
08505fcc9a | ||
![]() |
a79c933693 | ||
![]() |
b4cb3ddf1c | ||
![]() |
aa188a6e89 | ||
![]() |
a04b6b8a70 | ||
![]() |
11149d2743 | ||
![]() |
86bfd990db | ||
![]() |
9304430889 | ||
![]() |
095f1c270b | ||
![]() |
d3f91a832b | ||
![]() |
4790a1170f | ||
![]() |
501c392028 | ||
![]() |
9200520f70 | ||
![]() |
8122561337 | ||
![]() |
c6dc86ef8d | ||
![]() |
bea3b8485f | ||
![]() |
b807b89cdc | ||
![]() |
daac2f7fd9 | ||
![]() |
f0a5523174 | ||
![]() |
eda8fbb178 | ||
![]() |
67ca6184e9 | ||
![]() |
d79e91fc1e | ||
![]() |
1cdb93baa2 | ||
![]() |
f91991e25c | ||
![]() |
d21da47a7d | ||
![]() |
b4e22a345d | ||
![]() |
30e594ae5f | ||
![]() |
ffba3573ba | ||
![]() |
9df5bee8d3 | ||
![]() |
71c0728622 | ||
![]() |
476d8ba14d | ||
![]() |
274c956f16 | ||
![]() |
3068f9ee3d | ||
![]() |
a0c49d5f7f | ||
![]() |
a8534974fe | ||
![]() |
c517790391 | ||
![]() |
b7e875c77f | ||
![]() |
befd9c0624 | ||
![]() |
7a46f11089 | ||
![]() |
dc168bf8b9 | ||
![]() |
eef5293ca0 | ||
![]() |
a2c4498694 | ||
![]() |
938a84a460 | ||
![]() |
978d2c24ee | ||
![]() |
cdd00d665d | ||
![]() |
bb8b06c044 | ||
![]() |
604c5dcdc1 | ||
![]() |
6bc2ecdbf0 | ||
![]() |
e91c81def7 | ||
![]() |
bedd2fa15a | ||
![]() |
50465eef54 | ||
![]() |
07689adfcd | ||
![]() |
8f4f898675 | ||
![]() |
968bd7a437 | ||
![]() |
eba5900ba8 | ||
![]() |
69c477b104 | ||
![]() |
c8df8f4f54 | ||
![]() |
d35a19b4fd | ||
![]() |
a97437a6e5 | ||
![]() |
39c4473367 | ||
![]() |
b882bc721d | ||
![]() |
405cace489 | ||
![]() |
402a7b7fc9 | ||
![]() |
8ad805e654 | ||
![]() |
b23c357f73 | ||
![]() |
f561c2b0fa | ||
![]() |
5a8eea668f | ||
![]() |
777143e502 | ||
![]() |
0d8c9a82fe | ||
![]() |
d10ab1cce3 | ||
![]() |
ec25e09d73 | ||
![]() |
cba9c78ab1 | ||
![]() |
c32db4a881 | ||
![]() |
871add3071 | ||
![]() |
e661c617a3 | ||
![]() |
d4bf721540 | ||
![]() |
d91b55faed | ||
![]() |
9687832d4d | ||
![]() |
fc3e436744 | ||
![]() |
da90245f7b | ||
![]() |
410d6a85d7 | ||
![]() |
b693342e4f | ||
![]() |
acca361f2e | ||
![]() |
b663f47713 | ||
![]() |
d332b199b5 | ||
![]() |
78bac1dbd1 | ||
![]() |
724ff215f9 | ||
![]() |
68ea146469 | ||
![]() |
82583e616f | ||
![]() |
bfc339c58d | ||
![]() |
fe4427c076 | ||
![]() |
5745f388a9 | ||
![]() |
377e3c253f | ||
![]() |
3007a0c00e | ||
![]() |
f51ffc091d | ||
![]() |
c37c364a08 | ||
![]() |
331a106e9a | ||
![]() |
cd74687b7b | ||
![]() |
b3e145c1e6 | ||
![]() |
d8e1547736 | ||
![]() |
8617f01924 | ||
![]() |
55f9e75e6a | ||
![]() |
b93e7b7ed1 | ||
![]() |
89cc79ad60 | ||
![]() |
8dd0e60eea | ||
![]() |
df6113fdf6 | ||
![]() |
3a3095d15a | ||
![]() |
fb4d07391e | ||
![]() |
9bef9c85cf | ||
![]() |
b77b3f227f | ||
![]() |
6a065f0a34 | ||
![]() |
4e1e190797 | ||
![]() |
1ce8cd2100 | ||
![]() |
c03af6b9ad | ||
![]() |
adca850075 | ||
![]() |
e3616b484e | ||
![]() |
cfd7808169 | ||
![]() |
addcedc588 | ||
![]() |
bfea786088 | ||
![]() |
50e84c3c9e | ||
![]() |
dc92ace85e | ||
![]() |
1a543928b1 | ||
![]() |
652fe8d21e | ||
![]() |
199690f45f | ||
![]() |
37a4dd4b00 | ||
![]() |
34d4358bfc | ||
![]() |
90906b9019 | ||
![]() |
1c212ff2b4 | ||
![]() |
7d709f44a8 | ||
![]() |
ea9e88a18a | ||
![]() |
0be8a9c805 | ||
![]() |
fcf8139afe | ||
![]() |
62f969b50b | ||
![]() |
6726062500 | ||
![]() |
cf1f4bdcaf | ||
![]() |
b09a14ad4e | ||
![]() |
1dc62c9ca3 | ||
![]() |
beaa89a2dc | ||
![]() |
f39a000b49 | ||
![]() |
013a74fb14 | ||
![]() |
7c4964753b | ||
![]() |
8353533d60 | ||
![]() |
c06df27424 | ||
![]() |
ad82919ddf | ||
![]() |
44dbba17e1 | ||
![]() |
5ba110e1da | ||
![]() |
b6e392fdb2 | ||
![]() |
2280e83aa2 | ||
![]() |
f49b94edb9 | ||
![]() |
2428a12221 | ||
![]() |
9c353f3760 | ||
![]() |
5b86d25d7f | ||
![]() |
2b168e8bbc | ||
![]() |
537db32847 | ||
![]() |
498b7f9f2b | ||
![]() |
9935568597 | ||
![]() |
467003af8c | ||
![]() |
4c9edcc47b | ||
![]() |
24bf9cf121 | ||
![]() |
e06f6f39a9 | ||
![]() |
98ee0c307b | ||
![]() |
5e53ea0bc3 | ||
![]() |
847d88ea77 | ||
![]() |
d5046cc2b3 | ||
![]() |
3ad64b7cbb | ||
![]() |
0dbfe8ca55 | ||
![]() |
91b794d66d | ||
![]() |
0d65e1e314 | ||
![]() |
2d8f58c6d8 | ||
![]() |
65888fa816 | ||
![]() |
857e882c6e | ||
![]() |
add2931834 | ||
![]() |
cdda5f45ee | ||
![]() |
5f73d6a913 | ||
![]() |
0637882fbc | ||
![]() |
3f785bab20 | ||
![]() |
a4ca89bdd6 | ||
![]() |
1a64e796bd | ||
![]() |
a8b85a34f7 | ||
![]() |
e7bec7d6b0 | ||
![]() |
a582026037 | ||
![]() |
1a67a001c5 | ||
![]() |
406deac592 | ||
![]() |
e719ae0676 | ||
![]() |
d8b7726440 | ||
![]() |
49f642e712 | ||
![]() |
70117016ce | ||
![]() |
a4738f6281 | ||
![]() |
b1fc72d696 | ||
![]() |
457c2c2b50 | ||
![]() |
48848d7d1a | ||
![]() |
55b07ca3ab | ||
![]() |
a1d4882e18 | ||
![]() |
3843795d8f | ||
![]() |
f2bf8d42da | ||
![]() |
a3b244e114 | ||
![]() |
3093bdbc68 | ||
![]() |
9ab0799283 | ||
![]() |
236bec11ed | ||
![]() |
de48b0f940 | ||
![]() |
4885d4db86 | ||
![]() |
0c7bbda936 | ||
![]() |
fa07c2c1fb | ||
![]() |
5d17a191f6 | ||
![]() |
67fb74d3c2 | ||
![]() |
dc04cfc1b3 | ||
![]() |
d61d481965 | ||
![]() |
6b346ee1de | ||
![]() |
d0f248aaf9 | ||
![]() |
85c9227515 | ||
![]() |
73b6d3be84 | ||
![]() |
1ff6ce2343 | ||
![]() |
c145935d46 | ||
![]() |
e9ede6924e | ||
![]() |
515a21761d | ||
![]() |
8d6397028b | ||
![]() |
eb4828d81f | ||
![]() |
7e74578312 | ||
![]() |
640e3516d4 | ||
![]() |
bd295a4632 | ||
![]() |
166c30fe2c | ||
![]() |
66c1bab629 | ||
![]() |
66656304f9 | ||
![]() |
07f66e379d | ||
![]() |
7ae8fd60c4 | ||
![]() |
7275066994 | ||
![]() |
385adec186 | ||
![]() |
96b5bec5ab | ||
![]() |
6a9ec4e5f0 | ||
![]() |
d9851493df | ||
![]() |
efdb520414 | ||
![]() |
5548644aeb | ||
![]() |
e3fcd91b2d | ||
![]() |
2cae30ba88 | ||
![]() |
58cd38c4a8 | ||
![]() |
3300304feb | ||
![]() |
f0e376d06b | ||
![]() |
16f7bb48f2 | ||
![]() |
7f383dd29b | ||
![]() |
3dc529edf4 | ||
![]() |
45dedb4872 | ||
![]() |
afcdd01c0d | ||
![]() |
1164877e9a | ||
![]() |
fe92a449ba | ||
![]() |
401b0e2bd0 | ||
![]() |
cf9c71fcc1 | ||
![]() |
15a2400069 | ||
![]() |
d68a39b49e | ||
![]() |
066ca22e24 | ||
![]() |
0418b926fe | ||
![]() |
be40bbdf40 | ||
![]() |
df4f42e79e | ||
![]() |
5f80058f70 | ||
![]() |
0cbe59052d | ||
![]() |
af28a26e37 | ||
![]() |
70c596df93 | ||
![]() |
748b51428c | ||
![]() |
8ad746397c | ||
![]() |
45baed2f9a | ||
![]() |
74185f2d33 | ||
![]() |
90a91e4105 | ||
![]() |
11aa3a0315 | ||
![]() |
0c2e39214f | ||
![]() |
d89620d7a6 | ||
![]() |
edf80775b7 | ||
![]() |
46e56ac726 | ||
![]() |
40b2f6bfd6 | ||
![]() |
911e4921e2 | ||
![]() |
1db9bb419d | ||
![]() |
c6241a94e3 | ||
![]() |
1cbf75ca36 | ||
![]() |
8f85c897c8 | ||
![]() |
29c31b7aba | ||
![]() |
402919d6f2 | ||
![]() |
82608dd5ff | ||
![]() |
f312368df2 | ||
![]() |
374fc64427 | ||
![]() |
95bd74bb0d | ||
![]() |
a9f5069649 | ||
![]() |
957f7ffd8d | ||
![]() |
336dd3ce10 | ||
![]() |
47a7295477 | ||
![]() |
341a0e1c2a | ||
![]() |
c4f73d0eb8 | ||
![]() |
bd9258bae4 | ||
![]() |
e3b3260aa0 | ||
![]() |
676766c99e | ||
![]() |
1025a07593 | ||
![]() |
00c3fcd033 | ||
![]() |
b8457d4aff | ||
![]() |
a2ecf10d19 | ||
![]() |
1e63a2a7e7 | ||
![]() |
964014fc5c | ||
![]() |
fc2bb6d8c3 | ||
![]() |
1b10252d76 | ||
![]() |
ad8af12a10 | ||
![]() |
b040c9b118 | ||
![]() |
f6da7da90b | ||
![]() |
a745185408 | ||
![]() |
d3336f9027 | ||
![]() |
daf42c8203 | ||
![]() |
0a18bae3b5 | ||
![]() |
919705966c | ||
![]() |
2c54aee63e | ||
![]() |
3f80bdf2a3 | ||
![]() |
1c429b8dd3 | ||
![]() |
5669e2b0b7 | ||
![]() |
1a6a43babf | ||
![]() |
2650db5ddc | ||
![]() |
255491a107 | ||
![]() |
5c64147dfa | ||
![]() |
39f4118577 | ||
![]() |
f7f6e4736a | ||
![]() |
c635da7ebb | ||
![]() |
58124b006a | ||
![]() |
563aeccd0f |
28
.github/workflows/release.yml
vendored
28
.github/workflows/release.yml
vendored
@@ -93,15 +93,18 @@ jobs:
|
||||
needs: [Build-LiteLoader,Build-Shell]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
- name: Clone Main Repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: 'NapNeko/NapCatQQ'
|
||||
submodules: true
|
||||
ref: main
|
||||
token: ${{ secrets.NAPCAT_BUILD }}
|
||||
|
||||
- name: Download All Artifact
|
||||
uses: actions/download-artifact@v4
|
||||
# - name: Compress subdirectories
|
||||
# run: |
|
||||
# cd ./NapCat.Shell/
|
||||
# zip -q -r NapCat.Shell.zip *
|
||||
# cd ..
|
||||
# rm ./NapCat.Shell.zip -rf
|
||||
# mv ./NapCat.Shell/NapCat.Shell.zip ./
|
||||
|
||||
- name: Compress subdirectories
|
||||
run: |
|
||||
cd ./NapCat.Shell/
|
||||
@@ -114,6 +117,16 @@ jobs:
|
||||
rm ./NapCat.Framework.zip -rf
|
||||
mv ./NapCat.Shell/NapCat.Shell.zip ./
|
||||
mv ./NapCat.Framework/NapCat.Framework.zip ./
|
||||
|
||||
mkdir ./NapCat.Framework.Windows.Once
|
||||
unzip -q ./external/LiteLoaderWrapper.zip -d ./NapCat.Framework.Windows.Once
|
||||
cd ./NapCat.Framework.Windows.Once
|
||||
ls
|
||||
mkdir -p ./LL/plugins/NapCatQQ
|
||||
unzip -q ../NapCat.Framework.zip -d ./LL/plugins/NapCatQQ
|
||||
zip -q -r NapCat.Framework.Windows.Once.zip *
|
||||
cd ..
|
||||
mv ./NapCat.Framework.Windows.Once/NapCat.Framework.Windows.Once.zip ./
|
||||
- name: Extract version from tag
|
||||
run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_ENV
|
||||
|
||||
@@ -129,4 +142,5 @@ jobs:
|
||||
files: |
|
||||
NapCat.Framework.zip
|
||||
NapCat.Shell.zip
|
||||
NapCat.Framework.Windows.Once.zip
|
||||
draft: true
|
||||
|
31
README.md
31
README.md
@@ -1,26 +1,35 @@
|
||||
<div align="center">
|
||||
<img src="https://socialify.git.ci/NapNeko/NapCatQQ/image?description=1&language=1&logo=https%3A%2F%2Fraw.githubusercontent.com%2FNapNeko%2FNapCatQQ%2Fmain%2Flogo.png&name=1&stargazers=1&theme=Auto" alt="NapCatQQ" width="640" height="320" />
|
||||
<img src="https://socialify.git.ci/NapNeko/NapCatQQ/image?font=Jost&logo=https%3A%2F%2Fnapneko.github.io%2Fassets%2Flogo.png&name=1&owner=1&pattern=Diagonal%20Stripes&stargazers=1&theme=Auto" alt="NapCatQQ" width="640" height="320" />
|
||||
</div>
|
||||
|
||||
---
|
||||
## 欢迎回来
|
||||
NapCatQQ (aka 猫猫框架) 是现代化的基于 NTQQ 的 Bot 协议端实现。
|
||||
NapCatQQ (aka 猫猫框架) 是现代化的基于 NTQQ 的 Bot 协议端实现
|
||||
|
||||
## 猫猫技能
|
||||
- [x] **高性能**:1K+ 群聊数目、20 线程并行发送消息毫无压力
|
||||
- [x] **多种启动方式**:支持以无头、LiteLoader 插件、仅 QQ GUI 三种方式启动
|
||||
- [x] **多平台支持**: 覆盖 Windows / Linux (可选 Docker) / Android Termux / MacOS
|
||||
- [x] **超高性能**:轻松数千群聊 独创消息队列
|
||||
- [x] **启动方式**:支持以无头、LiteLoader 插件、仅 QQ GUI 三种方式启动
|
||||
- [x] **覆盖平台**: 覆盖 Windows / Linux (可选 Docker) / Android Termux / MacOS
|
||||
- [x] **安装简单**: 支持一键脚本/程序自动部署/镜像部署等多种覆盖范围
|
||||
- [x] **低占用**:无头模式占用资源极低,适合在服务器上运行
|
||||
- [x] **超低占用**:无头模式占用资源极低,适合在服务器上运行
|
||||
- [x] **超多接口**:实现大部分 OneBot 和 go-cqhttp 接口,超多扩展 API
|
||||
- [x] **WebUI**:自带 WebUI 支持,远程管理更加便捷
|
||||
- [x] **低故障率**:快速适配最新版本,日常保证 0 Issue
|
||||
- [x] **远程管理**:自带 WebUI 支持,远程管理更加便捷
|
||||
- [x] **扩展支持**:基于 MoeHoo 的Native 可实现发包与收包
|
||||
|
||||
## 使用猫猫
|
||||
|
||||
可前往 [Release](https://github.com/NapNeko/NapCatQQ/releases/) 页面下载最新版本
|
||||
|
||||
**首次使用**请务必前往[官方文档](https://napneko.github.io/)查看使用教程。
|
||||
**首次使用**请务必查看如下文档看使用教程
|
||||
|
||||
### 文档地址
|
||||
[Github.IO](https://napneko.github.io/)
|
||||
|
||||
[Cloudflare.Worker](https://doc.napneko.icu/)
|
||||
|
||||
[Cloudflare.HKServer](https://napcat.napneko.icu/)
|
||||
|
||||
[Cloudflare.Pages](https://napneko.pages.dev/)
|
||||
|
||||
## 回家旅途
|
||||
[QQ Group](https://qm.qq.com/q/VfjAq5HIMS)
|
||||
@@ -28,7 +37,7 @@ NapCatQQ (aka 猫猫框架) 是现代化的基于 NTQQ 的 Bot 协议端实现
|
||||
[Telegram Link](https://t.me/+nLZEnpne-pQ1OWFl)
|
||||
|
||||
## 猫猫朋友
|
||||
感谢 [LLOneBot](https://github.com/LLOneBot/LLOneBot) 提供部分参考
|
||||
感谢 [LLOneBot](https://github.com/LLOneBot/LLOneBot)
|
||||
|
||||
感谢 [Lagrange](https://github.com/LagrangeDev/Lagrange.Core) 对本项目的大力支持
|
||||
|
||||
@@ -40,4 +49,4 @@ NapCatQQ (aka 猫猫框架) 是现代化的基于 NTQQ 的 Bot 协议端实现
|
||||
> [!CAUTION]\
|
||||
> **请不要在 QQ 官方群聊和任何影响力较大的简中互联网平台(包括但不限于: 哔哩哔哩,微博,知乎,抖音等)发布和讨论*任何*与本项目存在相关性的信息**
|
||||
|
||||
任何使用本仓库代码的地方,都应当严格遵守[本仓库开源许可](./LICENSE)。**此外,禁止任何项目未经授权二次分发或基于 NapCat 代码开发。**
|
||||
任何使用本仓库代码的地方,都应当严格遵守[本仓库开源许可](./LICENSE)。**此外,禁止任何项目未经授权二次分发或基于 NapCat 代码开发。**
|
||||
|
BIN
external/LiteLoaderWrapper.zip
vendored
Normal file
BIN
external/LiteLoaderWrapper.zip
vendored
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -5,11 +5,11 @@ if %errorLevel% == 0 (
|
||||
echo Administrator mode detected.
|
||||
) else (
|
||||
echo Please run this script in administrator mode.
|
||||
powershell -Command "Start-Process 'cmd.exe' -ArgumentList '/c cd /d \"%cd%\" && \"%~f0\"' -Verb runAs"
|
||||
powershell -Command "Start-Process 'cmd.exe' -ArgumentList '/c cd /d \"%cd%\" && \"%~f0\" %1' -Verb runAs"
|
||||
exit
|
||||
)
|
||||
|
||||
set NAPCAT_PATCH_PATH=%cd%\patchNapCat.js
|
||||
set NAPCAT_PATCH_PACKAGE=%cd%\qqnt.json
|
||||
set NAPCAT_LOAD_PATH=%cd%\loadNapCat.js
|
||||
set NAPCAT_INJECT_PATH=%cd%\NapCatWinBootHook.dll
|
||||
set NAPCAT_LAUNCHER_PATH=%cd%\NapCatWinBootMain.exe
|
||||
@@ -33,8 +33,8 @@ if not exist "%QQpath%" (
|
||||
exit /b
|
||||
)
|
||||
set NAPCAT_MAIN_PATH=%NAPCAT_MAIN_PATH:\=/%
|
||||
echo (async () =^> {await import("file:///%NAPCAT_MAIN_PATH%")})() > %NAPCAT_LOAD_PATH%
|
||||
echo (async () =^> {await import("file:///%NAPCAT_MAIN_PATH%")})() > "%NAPCAT_LOAD_PATH%"
|
||||
|
||||
"%NAPCAT_LAUNCHER_PATH%" "%QQPath%" "%NAPCAT_INJECT_PATH%"
|
||||
"%NAPCAT_LAUNCHER_PATH%" "%QQPath%" "%NAPCAT_INJECT_PATH%" %1
|
||||
|
||||
REM "%NAPCAT_LAUNCHER_PATH%" "%QQPath%" "%NAPCAT_INJECT_PATH%" 123456
|
@@ -5,11 +5,11 @@ if %errorLevel% == 0 (
|
||||
echo Administrator mode detected.
|
||||
) else (
|
||||
echo Please run this script in administrator mode.
|
||||
powershell -Command "Start-Process 'wt.exe' -ArgumentList 'cmd /c cd /d \"%cd%\" && \"%~f0\"' -Verb runAs"
|
||||
powershell -Command "Start-Process 'wt.exe' -ArgumentList 'cmd /c cd /d \"%cd%\" && \"%~f0\" %1' -Verb runAs"
|
||||
exit
|
||||
)
|
||||
|
||||
set NAPCAT_PATCH_PATH=%cd%\patchNapCat.js
|
||||
set NAPCAT_PATCH_PACKAGE=%cd%\qqnt.json
|
||||
set NAPCAT_LOAD_PATH=%cd%\loadNapCat.js
|
||||
set NAPCAT_INJECT_PATH=%cd%\NapCatWinBootHook.dll
|
||||
set NAPCAT_LAUNCHER_PATH=%cd%\NapCatWinBootMain.exe
|
||||
@@ -34,8 +34,6 @@ if not exist "%QQpath%" (
|
||||
)
|
||||
|
||||
set NAPCAT_MAIN_PATH=%NAPCAT_MAIN_PATH:\=/%
|
||||
echo (async () =^> {await import("file:///%NAPCAT_MAIN_PATH%")})() > %NAPCAT_LOAD_PATH%
|
||||
echo (async () =^> {await import("file:///%NAPCAT_MAIN_PATH%")})() > "%NAPCAT_LOAD_PATH%"
|
||||
|
||||
"%NAPCAT_LAUNCHER_PATH%" "%QQPath%" "%NAPCAT_INJECT_PATH%"
|
||||
|
||||
REM "%NAPCAT_LAUNCHER_PATH%" "%QQPath%" "%NAPCAT_INJECT_PATH%" 123456
|
||||
"%NAPCAT_LAUNCHER_PATH%" "%QQPath%" "%NAPCAT_INJECT_PATH%" %1
|
@@ -1 +0,0 @@
|
||||
require('./launcher.node').load('external_index', module);
|
26
launcher/qqnt.json
Normal file
26
launcher/qqnt.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"name": "qq-chat",
|
||||
"version": "9.9.16-28788",
|
||||
"verHash": "73b0c8f6",
|
||||
"linuxVersion": "3.2.13-28788",
|
||||
"linuxVerHash": "55fb6434",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"description": "QQ",
|
||||
"productName": "QQ",
|
||||
"author": {
|
||||
"name": "Tencent",
|
||||
"email": "QQ-Team@tencent.com"
|
||||
},
|
||||
"homepage": "https://im.qq.com",
|
||||
"sideEffects": true,
|
||||
"bin": {
|
||||
"qd": "externals/devtools/cli/index.js"
|
||||
},
|
||||
"main": "./loadNapCat.js",
|
||||
"buildVersion": "28788",
|
||||
"isPureShell": true,
|
||||
"isByteCodeShell": true,
|
||||
"platform": "win32",
|
||||
"eleArch": "x64"
|
||||
}
|
4
launcher/quickLoginExample.bat
Normal file
4
launcher/quickLoginExample.bat
Normal file
@@ -0,0 +1,4 @@
|
||||
@echo off
|
||||
REM ./launcher.bat 123456
|
||||
REM ./launcher-win10.bat 123456
|
||||
REM 带有REM的为注释 删掉你需要的系统的那行REM这三个单词 修改QQ本脚本启动即可
|
@@ -4,7 +4,7 @@
|
||||
"name": "NapCatQQ",
|
||||
"slug": "NapCat.Framework",
|
||||
"description": "高性能的 OneBot 11 协议实现",
|
||||
"version": "2.3.7",
|
||||
"version": "3.0.6",
|
||||
"icon": "./logo.png",
|
||||
"authors": [
|
||||
{
|
||||
|
117
package.json
117
package.json
@@ -1,65 +1,52 @@
|
||||
{
|
||||
"name": "napcat",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"version": "2.3.7",
|
||||
"scripts": {
|
||||
"build:framework": "vite build --mode framework",
|
||||
"build:shell": "vite build --mode shell",
|
||||
"build:webui": "cd ./src/webui && vite build",
|
||||
"lint": "eslint --fix src/**/*.{js,ts}",
|
||||
"depend": "cd dist && npm install --omit=dev"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.24.7",
|
||||
"@babel/plugin-proposal-class-properties": "^7.18.6",
|
||||
"@babel/plugin-proposal-decorators": "^7.24.7",
|
||||
"@babel/preset-typescript": "^7.24.7",
|
||||
"@log4js-node/log4js-api": "^1.0.2",
|
||||
"@protobuf-ts/plugin": "^2.9.4",
|
||||
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||
"@rollup/plugin-typescript": "^11.1.6",
|
||||
"@types/cors": "^2.8.17",
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/figlet": "^1.5.8",
|
||||
"@types/fluent-ffmpeg": "^2.1.24",
|
||||
"@types/jest": "^29.5.12",
|
||||
"@types/node": "^22.0.1",
|
||||
"@types/qrcode-terminal": "^0.12.2",
|
||||
"@types/ws": "^8.5.12",
|
||||
"@typescript-eslint/eslint-plugin": "^8.3.0",
|
||||
"@typescript-eslint/parser": "^8.3.0",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-import-resolver-typescript": "^3.6.1",
|
||||
"eslint-plugin-import": "^2.29.1",
|
||||
"i": "^0.3.7",
|
||||
"javascript-obfuscator": "^4.1.0",
|
||||
"rollup": "^4.13.2",
|
||||
"rollup-plugin-dts": "^6.1.0",
|
||||
"rollup-plugin-obfuscator": "^1.1.0",
|
||||
"typescript": "^5.3.3",
|
||||
"vite": "^5.2.6",
|
||||
"vite-plugin-babel": "^1.2.0",
|
||||
"vite-plugin-cp": "^4.0.8",
|
||||
"vite-plugin-dts": "^3.8.2",
|
||||
"vite-tsconfig-paths": "^4.3.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"ajv": "^8.13.0",
|
||||
"async-mutex": "^0.5.0",
|
||||
"chalk": "^5.3.0",
|
||||
"commander": "^12.1.0",
|
||||
"cors": "^2.8.5",
|
||||
"express": "^5.0.0-beta.2",
|
||||
"fast-xml-parser": "^4.3.6",
|
||||
"file-type": "^19.0.0",
|
||||
"fluent-ffmpeg": "^2.1.2",
|
||||
"image-size": "^1.1.1",
|
||||
"json-schema-to-ts": "^3.1.0",
|
||||
"log4js": "^6.9.1",
|
||||
"qrcode-terminal": "^0.12.0",
|
||||
"silk-wasm": "^3.6.1",
|
||||
"strtok3": "8.0.1",
|
||||
"ws": "^8.18.0"
|
||||
}
|
||||
}
|
||||
{
|
||||
"name": "napcat",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"version": "3.0.6",
|
||||
"scripts": {
|
||||
"build:framework": "vite build --mode framework",
|
||||
"build:shell": "vite build --mode shell",
|
||||
"build:webui": "cd ./src/webui && vite build",
|
||||
"lint": "eslint --fix src/**/*.{js,ts}",
|
||||
"depend": "cd dist && npm install --omit=dev"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/preset-typescript": "^7.24.7",
|
||||
"@log4js-node/log4js-api": "^1.0.2",
|
||||
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||
"@rollup/plugin-typescript": "^11.1.6",
|
||||
"@types/cors": "^2.8.17",
|
||||
"@types/express": "^5.0.0",
|
||||
"@types/fluent-ffmpeg": "^2.1.24",
|
||||
"@types/node": "^22.0.1",
|
||||
"@types/qrcode-terminal": "^0.12.2",
|
||||
"@types/ws": "^8.5.12",
|
||||
"@typescript-eslint/eslint-plugin": "^8.3.0",
|
||||
"@typescript-eslint/parser": "^8.3.0",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-import-resolver-typescript": "^3.6.1",
|
||||
"eslint-plugin-import": "^2.29.1",
|
||||
"typescript": "^5.3.3",
|
||||
"vite": "^5.2.6",
|
||||
"vite-plugin-cp": "^4.0.8",
|
||||
"vite-tsconfig-paths": "^4.3.2",
|
||||
"@protobuf-ts/runtime": "^2.9.4",
|
||||
"ajv": "^8.13.0",
|
||||
"fast-xml-parser": "^4.3.6",
|
||||
"chalk": "^5.3.0",
|
||||
"commander": "^12.1.0",
|
||||
"async-mutex": "^0.5.0",
|
||||
"file-type": "^19.0.0",
|
||||
"json-schema-to-ts": "^3.1.0",
|
||||
"image-size": "^1.1.1",
|
||||
"cors": "^2.8.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"qrcode-terminal": "^0.12.0",
|
||||
"fluent-ffmpeg": "^2.1.2",
|
||||
"express": "^5.0.0-beta.2",
|
||||
"log4js": "^6.9.1",
|
||||
"silk-wasm": "^3.6.1",
|
||||
"ws": "^8.18.0"
|
||||
}
|
||||
}
|
||||
|
@@ -1,45 +0,0 @@
|
||||
# Dont Use This Script
|
||||
# 2024.7.3
|
||||
function Get-QQpath {
|
||||
try {
|
||||
$key = Get-ItemProperty -Path "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\QQ"
|
||||
$uninstallString = $key.UninstallString
|
||||
return [System.IO.Path]::GetDirectoryName($uninstallString) + "\QQ.exe"
|
||||
}
|
||||
catch {
|
||||
throw "get QQ path error: $_"
|
||||
}
|
||||
}
|
||||
function Select-QQPath {
|
||||
Add-Type -AssemblyName System.Windows.Forms
|
||||
[System.Windows.Forms.Application]::EnableVisualStyles()
|
||||
|
||||
$dialogTitle = "Select QQ.exe"
|
||||
|
||||
$filePicker = New-Object System.Windows.Forms.OpenFileDialog
|
||||
$filePicker.Title = $dialogTitle
|
||||
$filePicker.Filter = "Executable Files (*.exe)|*.exe|All Files (*.*)|*.*"
|
||||
$filePicker.FilterIndex = 1
|
||||
$null = $filePicker.ShowDialog()
|
||||
if (-not ($filePicker.FileName)) {
|
||||
throw "User did not select an .exe file."
|
||||
}
|
||||
return $filePicker.FileName
|
||||
}
|
||||
|
||||
$params = $args -join " "
|
||||
Try {
|
||||
$QQpath = Get-QQpath
|
||||
}
|
||||
Catch {
|
||||
$QQpath = Select-QQPath
|
||||
}
|
||||
|
||||
if (!(Test-Path $QQpath)) {
|
||||
throw "provided QQ path is invalid: $QQpath"
|
||||
}
|
||||
|
||||
$Bootfile = Join-Path $PSScriptRoot "napcat.mjs"
|
||||
$env:ELECTRON_RUN_AS_NODE = 1
|
||||
$commandInfo = Get-Command $QQpath -ErrorAction Stop
|
||||
Start-Process powershell -ArgumentList "-noexit", "-noprofile", "-command &{& chcp 65001;& '$($commandInfo.Path)' --enable-logging }"
|
@@ -1,90 +0,0 @@
|
||||
@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 %*
|
||||
)
|
@@ -1,123 +0,0 @@
|
||||
# 检查当前会话是否具有管理员权限
|
||||
function Test-Administrator {
|
||||
$user = [Security.Principal.WindowsIdentity]::GetCurrent()
|
||||
(New-Object Security.Principal.WindowsPrincipal $user).IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)
|
||||
}
|
||||
|
||||
if (-not (Test-Administrator)) {
|
||||
# 如果不是管理员,则重新启动脚本以管理员模式运行
|
||||
$scriptPath = $myInvocation.MyCommand.Path
|
||||
if (-not $scriptPath) {
|
||||
$scriptPath = $PSCommandPath
|
||||
}
|
||||
$newProcess = New-Object System.Diagnostics.ProcessStartInfo "powershell";
|
||||
$newProcess.Arguments = "-File `"$scriptPath`" $args"
|
||||
$newProcess.Verb = "runas";
|
||||
[System.Diagnostics.Process]::Start($newProcess);
|
||||
exit
|
||||
}
|
||||
|
||||
function Get-QQpath {
|
||||
try {
|
||||
$key = Get-ItemProperty -Path "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\QQ"
|
||||
$uninstallString = $key.UninstallString
|
||||
return [System.IO.Path]::GetDirectoryName($uninstallString) + "\QQ.exe"
|
||||
}
|
||||
catch {
|
||||
throw "get QQ path error: $_"
|
||||
}
|
||||
}
|
||||
function Select-QQPath {
|
||||
Add-Type -AssemblyName System.Windows.Forms
|
||||
[System.Windows.Forms.Application]::EnableVisualStyles()
|
||||
|
||||
$dialogTitle = "Select QQ.exe"
|
||||
|
||||
$filePicker = New-Object System.Windows.Forms.OpenFileDialog
|
||||
$filePicker.Title = $dialogTitle
|
||||
$filePicker.Filter = "Executable Files (*.exe)|*.exe|All Files (*.*)|*.*"
|
||||
$filePicker.FilterIndex = 1
|
||||
$null = $filePicker.ShowDialog()
|
||||
if (-not ($filePicker.FileName)) {
|
||||
throw "User did not select an .exe file."
|
||||
}
|
||||
return $filePicker.FileName
|
||||
}
|
||||
|
||||
# 设置当前工作目录
|
||||
$scriptDirectory = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent
|
||||
Set-Location $scriptDirectory
|
||||
|
||||
# 获取当前目录路径
|
||||
$currentPath = Get-Location
|
||||
|
||||
# 替换\为/
|
||||
$currentPath = $currentPath -replace '\\', '/'
|
||||
|
||||
# 生成JavaScript代码
|
||||
$jsCode = @"
|
||||
(async () => {
|
||||
await import('file:///$currentPath/napcat.mjs');
|
||||
})();
|
||||
"@
|
||||
|
||||
# 将JavaScript代码保存到文件中
|
||||
$jsFilePath = Join-Path $currentPath "loadScript.js"
|
||||
$jsCode | Out-File -FilePath $jsFilePath -Encoding UTF8
|
||||
|
||||
Write-Output "JavaScript code has been generated and saved to $jsFilePath"
|
||||
# 设置NAPCAT_PATH环境变量为 当前目录的loadScript.js地址
|
||||
$env:NAPCAT_PATH = $jsFilePath
|
||||
|
||||
$params = $args -join " "
|
||||
Try {
|
||||
$QQpath = Get-QQpath
|
||||
}
|
||||
Catch {
|
||||
$QQpath = Select-QQPath
|
||||
}
|
||||
# 拿不到QQ路径则退出
|
||||
if (!(Test-Path $QQpath)) {
|
||||
Write-Output "provided QQ path is invalid: $QQpath"
|
||||
Read-Host "Press any key to continue..."
|
||||
exit
|
||||
}
|
||||
|
||||
$commandInfo = Get-Command $QQpath -ErrorAction Stop
|
||||
|
||||
# 收集dbghelp.dll路径和HASH信息
|
||||
$QQpath = Split-Path $QQpath
|
||||
$oldDllPath = Join-Path $QQpath "dbghelp.dll"
|
||||
$oldDllHash = Get-FileHash $oldDllPath -Algorithm MD5
|
||||
$newDllPath = Join-Path $currentPath "dbghelp.dll"
|
||||
$newDllHash = Get-FileHash $newDllPath -Algorithm MD5
|
||||
# 如果文件一致则跳过
|
||||
if ($oldDllHash.Hash -ne $newDllHash.Hash) {
|
||||
$processes = Get-Process -Name QQ -ErrorAction SilentlyContinue
|
||||
if ($processes) {
|
||||
# 文件占用则退出
|
||||
Write-Output "dbghelp.dll is in use by the following processes:"
|
||||
$processes | ForEach-Object { Write-Output "$($_.Id) $($_.Name) $($_.Path)" }
|
||||
Write-Output "dbghelp.dll is in use, cannot continue."
|
||||
Read-Host "Press any key to continue..."
|
||||
exit
|
||||
} else {
|
||||
# 文件未占用则尝试覆盖
|
||||
try {
|
||||
Copy-Item -Path "$newDllPath" -Destination "$oldDllPath" -Force
|
||||
Write-Output "dbghelp.dll has been copied to $QQpath"
|
||||
} catch {
|
||||
Write-Output "Failed to copy dbghelp.dll: $_"
|
||||
Read-Host "Press any key to continue..."
|
||||
exit
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# 带参数启动QQ
|
||||
try {
|
||||
Start-Process powershell -ArgumentList '-noexit', '-noprofile', "-command &{& chcp 65001;& '$($commandInfo.Path)' --enable-logging $params}" -NoNewWindow -ErrorAction Stop
|
||||
} catch {
|
||||
Write-Output "Failed to start process as administrator: $_"
|
||||
Read-Host "Press any key to continue..."
|
||||
}
|
@@ -1,93 +0,0 @@
|
||||
@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 %*
|
@@ -1,77 +0,0 @@
|
||||
@echo off
|
||||
REM Check if the script is running as administrator
|
||||
openfiles >nul 2>&1
|
||||
if %errorlevel% neq 0 (
|
||||
REM If not, restart the script in administrator mode
|
||||
echo Requesting administrator privileges...
|
||||
powershell -Command "Start-Process cmd -ArgumentList '/c %~f0 %*' -Verb RunAs"
|
||||
exit /b
|
||||
)
|
||||
|
||||
cd /d %~dp0
|
||||
|
||||
set currentPath=%cd%
|
||||
set currentPath=%currentPath:\=/%
|
||||
|
||||
REM Generate JavaScript code
|
||||
set "jsCode=(async () =^>await import('file:///%currentPath%/napcat.mjs'))();"
|
||||
|
||||
REM Save JavaScript code to a file
|
||||
echo %jsCode% > loadScript.js
|
||||
echo JavaScript code has been generated and saved to loadScript.js
|
||||
|
||||
REM Set NAPCAT_PATH environment variable to the address of loadScript.js in the current directory
|
||||
set NAPCAT_PATH=%cd%\loadScript.js
|
||||
|
||||
REM Get QQ path and cache it
|
||||
: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"
|
||||
)
|
||||
|
||||
set "pathWithoutUninstall=%RetString:Uninstall.exe=%"
|
||||
|
||||
SET QQPath=%pathWithoutUninstall%QQ.exe
|
||||
echo %QQPath%>qq_path_cache.txt
|
||||
echo QQ path %QQPath% has been cached to qq_path_cache.txt
|
||||
|
||||
REM Exit if QQ path is invalid
|
||||
if not exist "%QQpath%" (
|
||||
echo provided QQ path is invalid: %QQpath%
|
||||
pause
|
||||
exit /b
|
||||
)
|
||||
|
||||
REM Collect dbghelp.dll path and HASH information
|
||||
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 Compare the HASH of the old and new dbghelp.dll, and replace the old one if they are different
|
||||
if "%oldDllHash%" neq "%newDllHash%" (
|
||||
tasklist /fi "imagename eq QQ.exe" 2>nul | find /i "QQ.exe" >nul
|
||||
if %errorlevel% equ 0 (
|
||||
REM If the file is in use, prompt the user to close QQ
|
||||
echo dbghelp.dll is in use, please close QQ first.
|
||||
) else (
|
||||
copy /y "%newDllPath%" "%oldDllPath%"
|
||||
if %errorlevel% neq 0 (
|
||||
echo Copy dbghelp.dll failed, please check and try again.
|
||||
pause
|
||||
exit /b
|
||||
) else (
|
||||
echo dbghelp.dll has been updated.
|
||||
echo Please run BootWay05_run.bat to start QQ.
|
||||
echo If you update QQ in the future, please run BootWay05_init.bat again.
|
||||
pause
|
||||
exit /b
|
||||
)
|
||||
)
|
||||
)
|
@@ -1,10 +0,0 @@
|
||||
@echo off
|
||||
set /p QQPath=<qq_path_cache.txt
|
||||
echo QQ path %QQPath% has been read from qq_path_cache.txt
|
||||
echo If failed to start QQ, please try running this script in administrator mode.
|
||||
|
||||
set NAPCAT_PATH=%cd%\loadScript.js
|
||||
|
||||
REM Launch QQ.exe with params provided
|
||||
|
||||
"%QQPath%" --enable-logging %*
|
@@ -1,13 +0,0 @@
|
||||
@echo off
|
||||
|
||||
chcp 65001
|
||||
|
||||
set /p QQPath=<qq_path_cache.txt
|
||||
echo QQ path %QQPath% has been read from qq_path_cache.txt
|
||||
echo If failed to start QQ, please try running this script in administrator mode.
|
||||
|
||||
set NAPCAT_PATH=%cd%\loadScript.js
|
||||
|
||||
REM Launch QQ.exe with params provided
|
||||
|
||||
"%QQPath%" --enable-logging %*
|
Binary file not shown.
@@ -1,66 +1,67 @@
|
||||
import fs from 'fs';
|
||||
import { encode, getDuration, getWavFileInfo, isSilk, isWav } from 'silk-wasm';
|
||||
import fsPromise from 'fs/promises';
|
||||
import path from 'node:path';
|
||||
import { randomUUID } from 'crypto';
|
||||
import { spawn } from 'node:child_process';
|
||||
import { encode, getDuration, getWavFileInfo, isSilk, isWav } from 'silk-wasm';
|
||||
import { LogWrapper } from './log';
|
||||
|
||||
export async function encodeSilk(filePath: string, TEMP_DIR: string, logger: LogWrapper) {
|
||||
async function guessDuration(pttPath: string) {
|
||||
const pttFileInfo = await fsPromise.stat(pttPath);
|
||||
let duration = pttFileInfo.size / 1024 / 3; // 3kb/s
|
||||
duration = Math.floor(duration);
|
||||
duration = Math.max(1, duration);
|
||||
logger.log('通过文件大小估算语音的时长:', duration);
|
||||
return duration;
|
||||
}
|
||||
const ALLOW_SAMPLE_RATE = [8000, 12000, 16000, 24000, 32000, 44100, 48000];
|
||||
const EXIT_CODES = [0, 255];
|
||||
const FFMPEG_PATH = process.env.FFMPEG_PATH || 'ffmpeg';
|
||||
|
||||
async function guessDuration(pttPath: string, logger: LogWrapper) {
|
||||
const pttFileInfo = await fsPromise.stat(pttPath);
|
||||
const duration = Math.max(1, Math.floor(pttFileInfo.size / 1024 / 3)); // 3kb/s
|
||||
logger.log('通过文件大小估算语音的时长:', duration);
|
||||
return duration;
|
||||
}
|
||||
|
||||
async function convert(filePath: string, pcmPath: string, logger: LogWrapper): Promise<Buffer> {
|
||||
return new Promise<Buffer>((resolve, reject) => {
|
||||
const cp = spawn(FFMPEG_PATH, ['-y', '-i', filePath, '-ar', '24000', '-ac', '1', '-f', 's16le', pcmPath]);
|
||||
cp.on('error', (err: Error) => {
|
||||
logger.log('FFmpeg处理转换出错: ', err.message);
|
||||
reject(err);
|
||||
});
|
||||
cp.on('exit', async (code, signal) => {
|
||||
if (code == null || EXIT_CODES.includes(code)) {
|
||||
try {
|
||||
const data = await fsPromise.readFile(pcmPath);
|
||||
await fsPromise.unlink(pcmPath);
|
||||
resolve(data);
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
}
|
||||
} else {
|
||||
logger.log(`FFmpeg exit: code=${code ?? 'unknown'} sig=${signal ?? 'unknown'}`);
|
||||
reject(new Error('FFmpeg处理转换失败'));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function handleWavFile(
|
||||
file: Buffer, filePath: string, pcmPath: string, logger: LogWrapper
|
||||
): Promise<{input: Buffer, sampleRate: number}> {
|
||||
const { fmt } = getWavFileInfo(file);
|
||||
if (!ALLOW_SAMPLE_RATE.includes(fmt.sampleRate)) {
|
||||
return { input: await convert(filePath, pcmPath, logger), sampleRate: 24000 };
|
||||
}
|
||||
return { input: file, sampleRate: fmt.sampleRate };
|
||||
}
|
||||
|
||||
export async function encodeSilk(filePath: string, TEMP_DIR: string, logger: LogWrapper) {
|
||||
try {
|
||||
const file = await fsPromise.readFile(filePath);
|
||||
const pttPath = path.join(TEMP_DIR, randomUUID());
|
||||
if (!isSilk(file)) {
|
||||
logger.log(`语音文件${filePath}需要转换成silk`);
|
||||
const _isWav = isWav(file);
|
||||
const pcmPath = pttPath + '.pcm';
|
||||
let sampleRate = 0;
|
||||
const convert = () => {
|
||||
return new Promise<Buffer>((resolve, reject) => {
|
||||
// todo: 通过配置文件获取ffmpeg路径
|
||||
const ffmpegPath = process.env.FFMPEG_PATH || 'ffmpeg';
|
||||
const cp = spawn(ffmpegPath, ['-y', '-i', filePath, '-ar', '24000', '-ac', '1', '-f', 's16le', pcmPath]);
|
||||
cp.on('error', err => {
|
||||
logger.log('FFmpeg处理转换出错: ', err.message);
|
||||
return reject(err);
|
||||
});
|
||||
cp.on('exit', (code, signal) => {
|
||||
const EXIT_CODES = [0, 255];
|
||||
if (code == null || EXIT_CODES.includes(code)) {
|
||||
sampleRate = 24000;
|
||||
const data = fs.readFileSync(pcmPath);
|
||||
fs.unlink(pcmPath, (err) => {
|
||||
});
|
||||
return resolve(data);
|
||||
}
|
||||
logger.log(`FFmpeg exit: code=${code ?? 'unknown'} sig=${signal ?? 'unknown'}`);
|
||||
reject(Error('FFmpeg处理转换失败'));
|
||||
});
|
||||
});
|
||||
};
|
||||
let input: Buffer;
|
||||
if (!_isWav) {
|
||||
input = await convert();
|
||||
} else {
|
||||
input = file;
|
||||
const allowSampleRate = [8000, 12000, 16000, 24000, 32000, 44100, 48000];
|
||||
const { fmt } = getWavFileInfo(input);
|
||||
// log(`wav文件信息`, fmt)
|
||||
if (!allowSampleRate.includes(fmt.sampleRate)) {
|
||||
input = await convert();
|
||||
}
|
||||
}
|
||||
const pcmPath = `${pttPath}.pcm`;
|
||||
const { input, sampleRate } = isWav(file)
|
||||
? (await handleWavFile(file, filePath, pcmPath, logger))
|
||||
: { input: await convert(filePath, pcmPath, logger), sampleRate: 24000 };
|
||||
const silk = await encode(input, sampleRate);
|
||||
fs.writeFileSync(pttPath, silk.data);
|
||||
await fsPromise.writeFile(pttPath, silk.data);
|
||||
logger.log(`语音文件${filePath}转换成功!`, pttPath, '时长:', silk.duration);
|
||||
return {
|
||||
converted: true,
|
||||
@@ -68,15 +69,13 @@ export async function encodeSilk(filePath: string, TEMP_DIR: string, logger: Log
|
||||
duration: silk.duration / 1000,
|
||||
};
|
||||
} else {
|
||||
const silk = file;
|
||||
let duration = 0;
|
||||
try {
|
||||
duration = getDuration(silk) / 1000;
|
||||
duration = getDuration(file) / 1000;
|
||||
} catch (e: any) {
|
||||
logger.log('获取语音文件时长失败, 使用文件大小推测时长', filePath, e.stack);
|
||||
duration = await guessDuration(filePath);
|
||||
duration = await guessDuration(filePath, logger);
|
||||
}
|
||||
|
||||
return {
|
||||
converted: false,
|
||||
path: filePath,
|
||||
@@ -84,7 +83,7 @@ export async function encodeSilk(filePath: string, TEMP_DIR: string, logger: Log
|
||||
};
|
||||
}
|
||||
} catch (error: any) {
|
||||
logger.logError('convert silk failed', error.stack);
|
||||
logger.logError.bind(logger)('convert silk failed', error.stack);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
}
|
@@ -40,7 +40,7 @@ export abstract class ConfigBase<T> {
|
||||
fs.writeFileSync(configPath, fs.readFileSync(this.getConfigPath(undefined), 'utf-8'));
|
||||
logger.log(`[Core] [Config] 配置文件创建成功!\n`);
|
||||
} catch (e: any) {
|
||||
logger.logError(`[Core] [Config] 创建配置文件时发生错误:`, e.message);
|
||||
logger.logError.bind(logger)(`[Core] [Config] 创建配置文件时发生错误:`, e.message);
|
||||
}
|
||||
}
|
||||
try {
|
||||
@@ -49,9 +49,9 @@ export abstract class ConfigBase<T> {
|
||||
return this.configData;
|
||||
} catch (e: any) {
|
||||
if (e instanceof SyntaxError) {
|
||||
logger.logError(`[Core] [Config] 配置文件格式错误,请检查配置文件:`, e.message);
|
||||
logger.logError.bind(logger)(`[Core] [Config] 配置文件格式错误,请检查配置文件:`, e.message);
|
||||
} else {
|
||||
logger.logError(`[Core] [Config] 读取配置文件时发生错误:`, e.message);
|
||||
logger.logError.bind(logger)(`[Core] [Config] 读取配置文件时发生错误:`, e.message);
|
||||
}
|
||||
return {} as T;
|
||||
}
|
||||
@@ -66,7 +66,7 @@ export abstract class ConfigBase<T> {
|
||||
try {
|
||||
fs.writeFileSync(configPath, JSON.stringify(newConfigData, this.getKeys(), 2));
|
||||
} catch (e: any) {
|
||||
logger.logError(`保存配置文件 ${configPath} 时发生错误:`, e.message);
|
||||
logger.logError.bind(logger)(`保存配置文件 ${configPath} 时发生错误:`, e.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,133 +0,0 @@
|
||||
import type { NodeIQQNTWrapperSession, WrapperNodeApi } from '@/core/wrapper';
|
||||
import EventEmitter from 'node:events';
|
||||
|
||||
export type ListenerClassBase = Record<string, string>;
|
||||
|
||||
export interface ListenerIBase {
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-new
|
||||
new(listener: any): ListenerClassBase;
|
||||
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export class NTEventWrapperV2 extends EventEmitter {
|
||||
private wrapperApi: WrapperNodeApi;
|
||||
private wrapperSession: NodeIQQNTWrapperSession;
|
||||
private listenerRefStorage = new Map<string, ListenerIBase>();
|
||||
|
||||
constructor(WrapperApi: WrapperNodeApi, WrapperSession: NodeIQQNTWrapperSession) {
|
||||
super();
|
||||
this.on('error', () => {
|
||||
});
|
||||
this.wrapperApi = WrapperApi;
|
||||
this.wrapperSession = WrapperSession;
|
||||
}
|
||||
|
||||
dispatcherListener(ListenerEvent: string, ...args: any[]) {
|
||||
this.emit(ListenerEvent, ...args);
|
||||
}
|
||||
|
||||
createProxyDispatch(ListenerMainName: string) {
|
||||
const dispatcherListener = this.dispatcherListener.bind(this);
|
||||
return new Proxy({}, {
|
||||
get(_target: any, prop: any, _receiver: any) {
|
||||
return (...args: any[]) => {
|
||||
dispatcherListener(ListenerMainName + '/' + prop, ...args);
|
||||
};
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async getOrInitListener<T>(listenerMainName: string): Promise<T> {
|
||||
const ListenerType = this.wrapperApi[listenerMainName];
|
||||
//获取NTQQ 外部 Listener包装
|
||||
if (!ListenerType) throw new Error('Init Listener not found');
|
||||
let Listener = this.listenerRefStorage.get(listenerMainName);
|
||||
//判断是否已创建 创建则跳过
|
||||
if (!Listener && ListenerType) {
|
||||
Listener = new ListenerType(this.createProxyDispatch(listenerMainName));
|
||||
if (!Listener) throw new Error('Init Listener failed');
|
||||
//实例化NTQQ Listener外包装
|
||||
const ServiceSubName = /^NodeIKernel(.*?)Listener$/.exec(listenerMainName)![1];
|
||||
const Service = 'NodeIKernel' + ServiceSubName + 'Service/addKernel' + ServiceSubName + 'Listener';
|
||||
const addfunc = this.createEventFunction<(listener: T) => number>(Service);
|
||||
//添加Listener到NTQQ
|
||||
addfunc!(Listener as T);
|
||||
this.listenerRefStorage.set(listenerMainName, Listener);
|
||||
//保存Listener实例
|
||||
}
|
||||
return Listener as T;
|
||||
}
|
||||
|
||||
async createEventWithListener<EventType extends (...args: any) => any, ListenerType extends (...args: any) => any>
|
||||
(
|
||||
eventName: string,
|
||||
listenerName: string,
|
||||
waitTimes = 1,
|
||||
timeout: number = 3000,
|
||||
checker: (...args: Parameters<ListenerType>) => boolean,
|
||||
...eventArg: Parameters<EventType>
|
||||
) {
|
||||
return new Promise<[EventRet: Awaited<ReturnType<EventType>>, ...Parameters<ListenerType>]>(async (resolve, reject) => {
|
||||
const ListenerNameList = listenerName.split('/');
|
||||
const ListenerMainName = ListenerNameList[0];
|
||||
//const ListenerSubName = ListenerNameList[1];
|
||||
this.getOrInitListener<ListenerType>(ListenerMainName);
|
||||
let complete = 0;
|
||||
const retData: Parameters<ListenerType> | undefined = undefined;
|
||||
let retEvent: any = {};
|
||||
const databack = () => {
|
||||
if (complete == 0) {
|
||||
reject(new Error('Timeout: NTEvent EventName:' + eventName + ' ListenerName:' + listenerName + ' EventRet:\n' + JSON.stringify(retEvent, null, 4) + '\n'));
|
||||
} else {
|
||||
resolve([retEvent as Awaited<ReturnType<EventType>>, ...retData!]);
|
||||
}
|
||||
};
|
||||
const Timeouter = setTimeout(databack, timeout);
|
||||
const callback = (...args: Parameters<ListenerType>) => {
|
||||
if (checker(...args)) {
|
||||
complete++;
|
||||
if (complete >= waitTimes) {
|
||||
clearTimeout(Timeouter);
|
||||
this.removeListener(listenerName, callback);
|
||||
databack();
|
||||
}
|
||||
}
|
||||
};
|
||||
this.on(listenerName, callback);
|
||||
const EventFunc = this.createEventFunction<EventType>(eventName);
|
||||
retEvent = await EventFunc!(...(eventArg as any[]));
|
||||
});
|
||||
}
|
||||
|
||||
private createEventFunction<T extends (...args: any) => any>(eventName: string): T | undefined {
|
||||
const eventNameArr = eventName.split('/');
|
||||
type eventType = {
|
||||
[key: string]: () => { [key: string]: (...params: Parameters<T>) => Promise<ReturnType<T>> }
|
||||
}
|
||||
if (eventNameArr.length > 1) {
|
||||
const serviceName = 'get' + eventNameArr[0].replace('NodeIKernel', '');
|
||||
const eventName = eventNameArr[1];
|
||||
//getNodeIKernelGroupListener,GroupService
|
||||
//console.log('2', eventName);
|
||||
const services = (this.wrapperSession as unknown as eventType)[serviceName]();
|
||||
const event = services[eventName]
|
||||
//重新绑定this
|
||||
.bind(services);
|
||||
if (event) {
|
||||
return event as T;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
async callEvent<EventType extends (...args: any[]) => Promise<any> | any>(
|
||||
EventName = '', timeout: number = 3000, ...args: Parameters<EventType>) {
|
||||
return new Promise<Awaited<ReturnType<EventType>>>((resolve) => {
|
||||
const EventFunc = this.createEventFunction<EventType>(EventName);
|
||||
EventFunc!(...args).then((retData: Awaited<ReturnType<EventType>> | PromiseLike<Awaited<ReturnType<EventType>>>) => resolve(retData));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
//NTEvent2.0
|
@@ -236,7 +236,7 @@ export class NTEventWrapper {
|
||||
this.createListenerFunction(ListenerMainName);
|
||||
const eventFunction = this.createEventFunction(serviceAndMethod);
|
||||
retEvent = await eventFunction!(...(args));
|
||||
if (!checkerEvent(retEvent)) {
|
||||
if (!checkerEvent(retEvent) && timeoutRef.hasRef()) {
|
||||
clearTimeout(timeoutRef);
|
||||
reject(
|
||||
new Error(
|
||||
@@ -250,86 +250,8 @@ export class NTEventWrapper {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
async callNormalEvent<
|
||||
Service extends keyof ServiceNamingMapping,
|
||||
ServiceMethod extends Exclude<keyof ServiceNamingMapping[Service], symbol>,
|
||||
Listener extends keyof ListenerNamingMapping,
|
||||
ListenerMethod extends Exclude<keyof ListenerNamingMapping[Listener], symbol>,
|
||||
// eslint-disable-next-line
|
||||
// @ts-ignore
|
||||
EventType extends (...args: any) => any = ServiceNamingMapping[Service][ServiceMethod],
|
||||
// eslint-disable-next-line
|
||||
// @ts-ignore
|
||||
ListenerType extends (...args: any) => any = ListenerNamingMapping[Listener][ListenerMethod]
|
||||
>(
|
||||
serviceAndMethod: `${Service}/${ServiceMethod}`,
|
||||
listenerAndMethod: `${Listener}/${ListenerMethod}`,
|
||||
waitTimes = 1,
|
||||
timeout: number = 3000,
|
||||
checker: (...args: Parameters<ListenerType>) => boolean,
|
||||
...args: Parameters<EventType>
|
||||
) {
|
||||
return new Promise<[EventRet: Awaited<ReturnType<EventType>>, ...Parameters<ListenerType>]>(
|
||||
async (resolve, reject) => {
|
||||
const id = randomUUID();
|
||||
let complete = 0;
|
||||
let retData: Parameters<ListenerType> | undefined = undefined;
|
||||
let retEvent: any = {};
|
||||
const databack = () => {
|
||||
if (complete == 0) {
|
||||
reject(
|
||||
new Error(
|
||||
'Timeout: NTEvent EventName:' +
|
||||
serviceAndMethod +
|
||||
' ListenerName:' +
|
||||
listenerAndMethod +
|
||||
' EventRet:\n' +
|
||||
JSON.stringify(retEvent, null, 4) +
|
||||
'\n',
|
||||
),
|
||||
);
|
||||
} else {
|
||||
resolve([retEvent as Awaited<ReturnType<EventType>>, ...retData!]);
|
||||
}
|
||||
};
|
||||
|
||||
const ListenerNameList = listenerAndMethod.split('/');
|
||||
const ListenerMainName = ListenerNameList[0];
|
||||
const ListenerSubName = ListenerNameList[1];
|
||||
|
||||
const Timeouter = setTimeout(databack, timeout);
|
||||
|
||||
const eventCallbak = {
|
||||
timeout: timeout,
|
||||
createtime: Date.now(),
|
||||
checker: checker,
|
||||
func: (...args: any[]) => {
|
||||
complete++;
|
||||
//console.log('func', ...args);
|
||||
retData = args as Parameters<ListenerType>;
|
||||
if (complete >= waitTimes) {
|
||||
clearTimeout(Timeouter);
|
||||
databack();
|
||||
}
|
||||
},
|
||||
};
|
||||
if (!this.EventTask.get(ListenerMainName)) {
|
||||
this.EventTask.set(ListenerMainName, new Map());
|
||||
}
|
||||
if (!this.EventTask.get(ListenerMainName)?.get(ListenerSubName)) {
|
||||
this.EventTask.get(ListenerMainName)?.set(ListenerSubName, new Map());
|
||||
}
|
||||
this.EventTask.get(ListenerMainName)?.get(ListenerSubName)?.set(id, eventCallbak);
|
||||
this.createListenerFunction(ListenerMainName);
|
||||
const EventFunc = this.createEventFunction<EventType>(serviceAndMethod);
|
||||
retEvent = await EventFunc!(...(args as any[]));
|
||||
},
|
||||
);
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
@@ -123,7 +123,7 @@ export interface HttpDownloadOptions {
|
||||
headers?: Record<string, string> | string;
|
||||
}
|
||||
|
||||
export async function httpDownload(options: string | HttpDownloadOptions): Promise<Buffer> {
|
||||
async function tryDownload(options: string | HttpDownloadOptions, useReferer: boolean = false): Promise<Response> {
|
||||
// const chunks: Buffer[] = [];
|
||||
let url: string;
|
||||
let headers: Record<string, string> = {
|
||||
@@ -142,15 +142,26 @@ export async function httpDownload(options: string | HttpDownloadOptions): Promi
|
||||
}
|
||||
}
|
||||
}
|
||||
if (useReferer && !headers['Referer']) {
|
||||
headers['Referer'] = url;
|
||||
}
|
||||
const fetchRes = await fetch(url, { headers }).catch((err) => {
|
||||
if (err.cause) {
|
||||
throw err.cause;
|
||||
}
|
||||
throw err;
|
||||
});
|
||||
if (!fetchRes.ok) throw new Error(`下载文件失败: ${fetchRes.statusText}`);
|
||||
return fetchRes;
|
||||
}
|
||||
|
||||
const blob = await fetchRes.blob();
|
||||
export async function httpDownload(options: string | HttpDownloadOptions): Promise<Buffer> {
|
||||
const useReferer = typeof options === 'string';
|
||||
let resp = await tryDownload(options);
|
||||
if (resp.status === 403 && useReferer) {
|
||||
resp = await tryDownload(options, true);
|
||||
}
|
||||
if (!resp.ok) throw new Error(`下载文件失败: ${resp.statusText}`);
|
||||
const blob = await resp.blob();
|
||||
const buffer = await blob.arrayBuffer();
|
||||
return Buffer.from(buffer);
|
||||
}
|
||||
@@ -160,8 +171,7 @@ type Uri2LocalRes = {
|
||||
errMsg: string,
|
||||
fileName: string,
|
||||
ext: string,
|
||||
path: string,
|
||||
isLocal: boolean
|
||||
path: string
|
||||
}
|
||||
|
||||
export async function checkFileV2(filePath: string) {
|
||||
@@ -194,7 +204,6 @@ export async function checkUriType(Uri: string) {
|
||||
return undefined;
|
||||
}, Uri);
|
||||
if (LocalFileRet) return LocalFileRet;
|
||||
|
||||
const OtherFileRet = await solveProblem((uri: string) => {
|
||||
//再判断是否是Http
|
||||
if (uri.startsWith('http://') || uri.startsWith('https://')) {
|
||||
@@ -206,15 +215,19 @@ export async function checkUriType(Uri: string) {
|
||||
}
|
||||
if (uri.startsWith('file://')) {
|
||||
let filePath: string;
|
||||
// await fs.copyFile(url.pathname, filePath);
|
||||
const pathname = decodeURIComponent(new URL(uri).pathname);
|
||||
if (process.platform === 'win32') {
|
||||
filePath = pathname.slice(1);
|
||||
} else {
|
||||
filePath = pathname;
|
||||
}
|
||||
|
||||
return { Uri: filePath, Type: FileUriType.Local };
|
||||
}
|
||||
if (uri.startsWith('data:')) {
|
||||
const data = uri.split(',')[1];
|
||||
if (data) return { Uri: data, Type: FileUriType.Base64 };
|
||||
}
|
||||
}, Uri);
|
||||
if (OtherFileRet) return OtherFileRet;
|
||||
|
||||
@@ -224,34 +237,42 @@ export async function checkUriType(Uri: string) {
|
||||
export async function uri2local(dir: string, uri: string, filename: string | undefined = undefined): Promise<Uri2LocalRes> {
|
||||
const { Uri: HandledUri, Type: UriType } = await checkUriType(uri);
|
||||
//解析失败
|
||||
const tempName = randomUUID();
|
||||
if (!filename) filename = randomUUID();
|
||||
//解析Http和Https协议
|
||||
|
||||
if (UriType == FileUriType.Unknown) {
|
||||
return { success: false, errMsg: '未知文件类型', fileName: '', ext: '', path: '', isLocal: false };
|
||||
return { success: false, errMsg: '未知文件类型', fileName: '', ext: '', path: '' };
|
||||
}
|
||||
//解析File协议和本地文件
|
||||
if (UriType == FileUriType.Local) {
|
||||
const fileExt = path.extname(HandledUri);
|
||||
const filename = path.basename(HandledUri, fileExt);
|
||||
return { success: true, errMsg: '', fileName: filename, ext: fileExt, path: HandledUri, isLocal: true };
|
||||
let filename = path.basename(HandledUri, fileExt);
|
||||
filename += fileExt;
|
||||
//复制文件到临时文件并保持后缀
|
||||
const filenameTemp = tempName + fileExt;
|
||||
const filePath = path.join(dir, filenameTemp);
|
||||
fs.copyFileSync(HandledUri, filePath);
|
||||
return { success: true, errMsg: '', fileName: filename, ext: fileExt, path: filePath };
|
||||
}
|
||||
//接下来都要有文件名
|
||||
if (!filename) filename = randomUUID();
|
||||
//解析Http和Https协议
|
||||
|
||||
if (UriType == FileUriType.Remote) {
|
||||
const pathInfo = path.parse(decodeURIComponent(new URL(HandledUri).pathname));
|
||||
if (pathInfo.name) {
|
||||
filename = pathInfo.name;
|
||||
const pathlen = 200 - dir.length - pathInfo.name.length;
|
||||
filename = pathlen > 0 ? pathInfo.name.substring(0, pathlen) : pathInfo.name.substring(pathInfo.name.length, pathInfo.name.length - 10);//过长截断
|
||||
if (pathInfo.ext) {
|
||||
filename += pathInfo.ext;
|
||||
}
|
||||
}
|
||||
filename = filename.replace(/[/\\:*?"<>|]/g, '_');
|
||||
const fileExt = path.extname(HandledUri);
|
||||
const filePath = path.join(dir, filename);
|
||||
const fileExt = path.extname(HandledUri).replace(/[/\\:*?"<>|]/g, '_').substring(0, 10);
|
||||
const filePath = path.join(dir, tempName + fileExt);
|
||||
const buffer = await httpDownload(HandledUri);
|
||||
fs.writeFileSync(filePath, buffer);
|
||||
return { success: true, errMsg: '', fileName: filename, ext: fileExt, path: filePath, isLocal: true };
|
||||
//没有文件就创建
|
||||
fs.writeFileSync(filePath, buffer, { flag: 'wx' });
|
||||
return { success: true, errMsg: '', fileName: filename, ext: fileExt, path: filePath };
|
||||
}
|
||||
//解析Base64
|
||||
if (UriType == FileUriType.Base64) {
|
||||
@@ -266,7 +287,7 @@ export async function uri2local(dir: string, uri: string, filename: string | und
|
||||
fileExt = ext;
|
||||
filename = filename + '.' + ext;
|
||||
}
|
||||
return { success: true, errMsg: '', fileName: filename, ext: fileExt, path: filePath, isLocal: true };
|
||||
return { success: true, errMsg: '', fileName: filename, ext: fileExt, path: filePath };
|
||||
}
|
||||
return { success: false, errMsg: '未知文件类型', fileName: '', ext: '', path: '', isLocal: false };
|
||||
return { success: false, errMsg: '未知文件类型', fileName: '', ext: '', path: '' };
|
||||
}
|
||||
|
@@ -25,42 +25,68 @@ export async function solveAsyncProblem<T extends (...args: any[]) => Promise<an
|
||||
}
|
||||
|
||||
export class FileNapCatOneBotUUID {
|
||||
static encodeModelId(peer: Peer, modelId: string, fileId: string): string {
|
||||
return `NapCatOneBot|ModelIdFile|${peer.chatType}|${peer.peerUid}|${modelId}|${fileId}`;
|
||||
static encodeModelId(peer: Peer, modelId: string, fileId: string, fileUUID: string = "", endString: string = ""): string {
|
||||
const data = `NapCatOneBot|ModelIdFile|${peer.chatType}|${peer.peerUid}|${modelId}|${fileId}|${fileUUID}`;
|
||||
//前四个字节塞data长度
|
||||
const length = Buffer.alloc(4 + data.length);
|
||||
length.writeUInt32BE(data.length * 2, 0);//储存data的hex长度
|
||||
length.write(data, 4);
|
||||
return length.toString('hex') + endString;
|
||||
}
|
||||
|
||||
static decodeModelId(uuid: string): undefined | {
|
||||
peer: Peer,
|
||||
modelId: string,
|
||||
fileId: string
|
||||
fileId: string,
|
||||
fileUUID?: string
|
||||
} {
|
||||
if (!uuid.startsWith('NapCatOneBot|ModelIdFile|')) return undefined;
|
||||
const data = uuid.split('|');
|
||||
if (data.length !== 6) return undefined;
|
||||
const [, , chatType, peerUid, modelId, fileId] = data;
|
||||
//前四个字节是data长度
|
||||
const length = Buffer.from(uuid.slice(0, 8), 'hex').readUInt32BE(0);
|
||||
//根据length计算需要读取的长度
|
||||
const dataId = uuid.slice(8, 8 + length);
|
||||
//hex还原为string
|
||||
const realData = Buffer.from(dataId, 'hex').toString();
|
||||
if (!realData.startsWith('NapCatOneBot|ModelIdFile|')) return undefined;
|
||||
const data = realData.split('|');
|
||||
if (data.length < 6) return undefined; // compatibility requirement
|
||||
const [, , chatType, peerUid, modelId, fileId, fileUUID = undefined] = data;
|
||||
return {
|
||||
peer: {
|
||||
chatType: chatType as any,
|
||||
peerUid: peerUid,
|
||||
},
|
||||
modelId,
|
||||
fileId
|
||||
fileId,
|
||||
fileUUID
|
||||
};
|
||||
}
|
||||
|
||||
static encode(peer: Peer, msgId: string, elementId: string): string {
|
||||
return `NapCatOneBot|MsgFile|${peer.chatType}|${peer.peerUid}|${msgId}|${elementId}`;
|
||||
static encode(peer: Peer, msgId: string, elementId: string, fileUUID: string = "", endString: string = ""): string {
|
||||
const data = `NapCatOneBot|MsgFile|${peer.chatType}|${peer.peerUid}|${msgId}|${elementId}|${fileUUID}`;
|
||||
//前四个字节塞data长度
|
||||
//一个字节8位 一个ascii字符1字节 一个hex字符4位 表示一个ascii字符需要两个hex字符
|
||||
const length = Buffer.alloc(4 + data.length);
|
||||
length.writeUInt32BE(data.length * 2, 0);
|
||||
length.write(data, 4);
|
||||
return length.toString('hex') + endString;
|
||||
}
|
||||
|
||||
static decode(uuid: string): undefined | {
|
||||
peer: Peer,
|
||||
msgId: string,
|
||||
elementId: string
|
||||
elementId: string,
|
||||
fileUUID?: string
|
||||
} {
|
||||
if (!uuid.startsWith('NapCatOneBot|MsgFile|')) return undefined;
|
||||
const data = uuid.split('|');
|
||||
if (data.length !== 6) return undefined;
|
||||
const [, , chatType, peerUid, msgId, elementId] = data;
|
||||
//前四个字节是data长度
|
||||
const length = Buffer.from(uuid.slice(0, 8), 'hex').readUInt32BE(0);
|
||||
//根据length计算需要读取的长度
|
||||
const dataId = uuid.slice(8, 8 + length);
|
||||
//hex还原为string
|
||||
const realData = Buffer.from(dataId, 'hex').toString();
|
||||
if (!realData.startsWith('NapCatOneBot|MsgFile|')) return undefined;
|
||||
const data = realData.split('|');
|
||||
if (data.length < 6) return undefined; // compatibility requirement
|
||||
const [, , chatType, peerUid, msgId, elementId, fileUUID = undefined] = data;
|
||||
return {
|
||||
peer: {
|
||||
chatType: chatType as any,
|
||||
@@ -68,6 +94,7 @@ export class FileNapCatOneBotUUID {
|
||||
},
|
||||
msgId,
|
||||
elementId,
|
||||
fileUUID
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -140,34 +167,51 @@ export function isEqual(obj1: any, obj2: any) {
|
||||
export function getDefaultQQVersionConfigInfo(): QQVersionConfigType {
|
||||
if (os.platform() === 'linux') {
|
||||
return {
|
||||
baseVersion: '3.2.12-27597',
|
||||
curVersion: '3.2.12-27597',
|
||||
baseVersion: '3.2.12.28060',
|
||||
curVersion: '3.2.12.28060',
|
||||
prevVersion: '',
|
||||
onErrorVersions: [],
|
||||
buildId: '27597',
|
||||
buildId: '27254',
|
||||
};
|
||||
}
|
||||
if (os.platform() === 'darwin') {
|
||||
return {
|
||||
baseVersion: '6.9.53.28060',
|
||||
curVersion: '6.9.53.28060',
|
||||
prevVersion: '',
|
||||
onErrorVersions: [],
|
||||
buildId: '28060',
|
||||
};
|
||||
}
|
||||
return {
|
||||
baseVersion: '9.9.15-27597',
|
||||
curVersion: '9.9.15-27597',
|
||||
baseVersion: '9.9.15-28131',
|
||||
curVersion: '9.9.15-28131',
|
||||
prevVersion: '',
|
||||
onErrorVersions: [],
|
||||
buildId: '27597',
|
||||
buildId: '28131',
|
||||
};
|
||||
}
|
||||
|
||||
export function getQQPackageInfoPath(exePath: string = ''): string {
|
||||
export function getQQPackageInfoPath(exePath: string = '', version?: string): string {
|
||||
let packagePath;
|
||||
if (os.platform() === 'darwin') {
|
||||
return path.join(path.dirname(exePath), '..', 'Resources', 'app', 'package.json');
|
||||
packagePath = path.join(path.dirname(exePath), '..', 'Resources', 'app', 'package.json');
|
||||
} else if (os.platform() === 'linux') {
|
||||
packagePath = path.join(path.dirname(exePath), './resources/app/package.json');
|
||||
} else {
|
||||
return path.join(path.dirname(exePath), 'resources', 'app', 'package.json');
|
||||
packagePath = path.join(path.dirname(exePath), './versions/' + version + '/resources/app/package.json');
|
||||
}
|
||||
//下面是老版本兼容 未来去掉
|
||||
if (!fs.existsSync(packagePath)) {
|
||||
packagePath = path.join(path.dirname(exePath), './resources/app/versions/' + version + '/package.json');
|
||||
}
|
||||
return packagePath;
|
||||
}
|
||||
|
||||
export function getQQVersionConfigPath(exePath: string = ''): string | undefined {
|
||||
let configVersionInfoPath;
|
||||
if (os.platform() === 'win32') {
|
||||
configVersionInfoPath = path.join(path.dirname(exePath), 'resources', 'app', 'versions', 'config.json');
|
||||
configVersionInfoPath = path.join(path.dirname(exePath), 'versions', 'config.json');
|
||||
} else if (os.platform() === 'darwin') {
|
||||
const userPath = os.homedir();
|
||||
const appDataPath = path.resolve(userPath, './Library/Application Support/QQ');
|
||||
@@ -180,13 +224,18 @@ export function getQQVersionConfigPath(exePath: string = ''): string | undefined
|
||||
if (typeof configVersionInfoPath !== 'string') {
|
||||
return undefined;
|
||||
}
|
||||
//老版本兼容 未来去掉
|
||||
if (!fs.existsSync(configVersionInfoPath)) {
|
||||
configVersionInfoPath = path.join(path.dirname(exePath), './resources/app/versions/config.json');
|
||||
}
|
||||
if (!fs.existsSync(configVersionInfoPath)) {
|
||||
return undefined;
|
||||
}
|
||||
return configVersionInfoPath;
|
||||
}
|
||||
|
||||
export function calcQQLevel(level: QQLevel) {
|
||||
export function calcQQLevel(level?: QQLevel) {
|
||||
if (!level) return 0;
|
||||
const { crownNum, sunNum, moonNum, starNum } = level;
|
||||
return crownNum * 64 + sunNum * 16 + moonNum * 4 + starNum;
|
||||
}
|
||||
|
@@ -139,9 +139,13 @@ export class LogWrapper {
|
||||
|
||||
logMessage(msg: RawMessage, selfInfo: SelfInfo) {
|
||||
const isSelfSent = msg.senderUin === selfInfo.uin;
|
||||
this.log(`${
|
||||
isSelfSent ? '发送 ->' : '接收 <-'
|
||||
} ${rawMessageToText(msg)}`);
|
||||
|
||||
// Intercept grey tip
|
||||
if (msg.elements[0]?.elementType === ElementType.GreyTip) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.log(`${isSelfSent ? '发送 ->' : '接收 <-' } ${rawMessageToText(msg)}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,7 +159,12 @@ export function rawMessageToText(msg: RawMessage, recursiveLevel = 0): string {
|
||||
if (msg.chatType == ChatType.KCHATTYPEC2C) {
|
||||
tokens.push(`私聊 (${msg.peerUin})`);
|
||||
} else if (msg.chatType == ChatType.KCHATTYPEGROUP) {
|
||||
tokens.push(`群聊 (群 ${msg.peerUin} 的 ${msg.senderUin})`);
|
||||
if (recursiveLevel < 1) {
|
||||
tokens.push(`群聊 [${msg.peerName}(${msg.peerUin})]`);
|
||||
}
|
||||
if (msg.senderUin !== '0') {
|
||||
tokens.push(`[${msg.sendMemberName || msg.sendRemarkName || msg.sendNickName}(${msg.senderUin})]`);
|
||||
}
|
||||
} else if (msg.chatType == ChatType.KCHATTYPEDATALINE) {
|
||||
tokens.push('移动设备');
|
||||
} else /* temp */ {
|
||||
@@ -180,12 +189,11 @@ export function rawMessageToText(msg: RawMessage, recursiveLevel = 0): string {
|
||||
const recordMsgOrNull = msg.records.find(
|
||||
record => element.replyElement!.sourceMsgIdInRecords === record.msgId,
|
||||
);
|
||||
return `[回复消息 ${
|
||||
recordMsgOrNull &&
|
||||
recordMsgOrNull.peerUin != '284840486' // 非转发消息; 否则定位不到
|
||||
?
|
||||
rawMessageToText(recordMsgOrNull, recursiveLevel + 1) :
|
||||
`未找到消息记录 (MsgId = ${element.replyElement.sourceMsgIdInRecords})`
|
||||
return `[回复消息 ${recordMsgOrNull &&
|
||||
recordMsgOrNull.peerUin != '284840486' && recordMsgOrNull.peerUin != '1094950020'// 非转发消息; 否则定位不到
|
||||
?
|
||||
rawMessageToText(recordMsgOrNull, recursiveLevel + 1) :
|
||||
`未找到消息记录 (MsgId = ${element.replyElement.sourceMsgIdInRecords})`
|
||||
}]`;
|
||||
}
|
||||
|
||||
|
@@ -24,7 +24,9 @@ export class LRUCache<K, V> {
|
||||
} else if (this.cache.size >= this.capacity) {
|
||||
// If the cache is full, remove the least recently used key (the first one in the map)
|
||||
const firstKey = this.cache.keys().next().value;
|
||||
this.cache.delete(firstKey);
|
||||
if (firstKey !== undefined) {
|
||||
this.cache.delete(firstKey);
|
||||
}
|
||||
}
|
||||
this.cache.set(key, value);
|
||||
}
|
||||
|
@@ -23,7 +23,9 @@ export class LimitedHashTable<K, V> {
|
||||
}
|
||||
while (this.keyToValue.size > this.maxSize || this.valueToKey.size > this.maxSize) {
|
||||
const oldestKey = this.keyToValue.keys().next().value;
|
||||
// @ts-ignore
|
||||
this.valueToKey.delete(this.keyToValue.get(oldestKey)!);
|
||||
// @ts-ignore
|
||||
this.keyToValue.delete(oldestKey);
|
||||
}
|
||||
}
|
||||
|
@@ -19,14 +19,16 @@ export class QQBasicInfoWrapper {
|
||||
//基础目录获取
|
||||
this.context = context;
|
||||
this.QQMainPath = process.execPath;
|
||||
this.QQPackageInfoPath = getQQPackageInfoPath(this.QQMainPath);
|
||||
this.QQVersionConfigPath = getQQVersionConfigPath(this.QQMainPath);
|
||||
|
||||
|
||||
//基础信息获取 无快更则启用默认模板填充
|
||||
this.isQuickUpdate = !!this.QQVersionConfigPath;
|
||||
this.QQVersionConfig = this.isQuickUpdate
|
||||
? JSON.parse(fs.readFileSync(this.QQVersionConfigPath!).toString())
|
||||
: getDefaultQQVersionConfigInfo();
|
||||
|
||||
this.QQPackageInfoPath = getQQPackageInfoPath(this.QQMainPath, this.QQVersionConfig?.curVersion);
|
||||
this.QQPackageInfo = JSON.parse(fs.readFileSync(this.QQPackageInfoPath).toString());
|
||||
const { appid: IQQVersionAppid, qua: IQQVersionQua } = this.getAppidV2();
|
||||
this.QQVersionAppid = IQQVersionAppid;
|
||||
@@ -51,26 +53,22 @@ export class QQBasicInfoWrapper {
|
||||
}
|
||||
|
||||
//此方法不要直接使用
|
||||
getQUAInternal() {
|
||||
switch (systemPlatform) {
|
||||
case 'linux':
|
||||
return `V1_LNX_${this.getFullQQVesion()}_${this.getQQBuildStr()}_GW_B`;
|
||||
case 'darwin':
|
||||
return `V1_MAC_${this.getFullQQVesion()}_${this.getQQBuildStr()}_GW_B`;
|
||||
default:
|
||||
return `V1_WIN_${this.getFullQQVesion()}_${this.getQQBuildStr()}_GW_B`;
|
||||
}
|
||||
getQUAFallback() {
|
||||
const platformMapping: Partial<Record<NodeJS.Platform, string>> = {
|
||||
win32: `V1_WIN_${this.getFullQQVesion()}_${this.getQQBuildStr()}_GW_B`,
|
||||
darwin: `V1_MAC_${this.getFullQQVesion()}_${this.getQQBuildStr()}_GW_B`,
|
||||
linux: `V1_LNX_${this.getFullQQVesion()}_${this.getQQBuildStr()}_GW_B`,
|
||||
};
|
||||
return platformMapping[systemPlatform] ?? (platformMapping.win32)!;
|
||||
}
|
||||
|
||||
getAppidInternal() {
|
||||
switch (systemPlatform) {
|
||||
case 'linux':
|
||||
return '537243600';
|
||||
case 'darwin':
|
||||
return '537243441';
|
||||
default:
|
||||
return '537243538';
|
||||
}
|
||||
getAppIdFallback() {
|
||||
const platformMapping: Partial<Record<NodeJS.Platform, string>> = {
|
||||
win32: '537246092',
|
||||
darwin: '537246140',
|
||||
linux: '537246140',
|
||||
};
|
||||
return platformMapping[systemPlatform] ?? '537246092';
|
||||
}
|
||||
|
||||
getAppidV2(): { appid: string; qua: string } {
|
||||
@@ -86,6 +84,6 @@ export class QQBasicInfoWrapper {
|
||||
// else
|
||||
this.context.logger.log(`[QQ版本兼容性检测] 获取Appid异常 请检测NapCat/QQNT是否正常`);
|
||||
this.context.logger.log(`[QQ版本兼容性检测] ${fullVersion} 版本兼容性不佳,可能会导致一些功能无法正常使用`,);
|
||||
return { appid: this.getAppidInternal(), qua: this.getQUAInternal() };
|
||||
return { appid: this.getAppIdFallback(), qua: this.getQUAFallback() };
|
||||
}
|
||||
}
|
||||
|
@@ -61,7 +61,7 @@ export class RequestUtil {
|
||||
const options = {
|
||||
hostname: option.hostname,
|
||||
port: option.port,
|
||||
path: option.href,
|
||||
path: option.pathname + option.search,
|
||||
method: method,
|
||||
headers: headers,
|
||||
};
|
||||
|
@@ -1,12 +1,8 @@
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
import { networkInterfaces } from 'os';
|
||||
import { randomUUID } from 'crypto';
|
||||
|
||||
// 缓解Win7设备兼容性问题
|
||||
let osName: string;
|
||||
// 设备ID
|
||||
let machineId: Promise<string>;
|
||||
|
||||
try {
|
||||
osName = os.hostname();
|
||||
@@ -14,54 +10,6 @@ try {
|
||||
osName = 'NapCat'; // + crypto.randomUUID().substring(0, 4);
|
||||
}
|
||||
|
||||
const invalidMacAddresses = new Set([
|
||||
'00:00:00:00:00:00',
|
||||
'ff:ff:ff:ff:ff:ff',
|
||||
'ac:de:48:00:11:22',
|
||||
]);
|
||||
|
||||
function validateMacAddress(candidate: string): boolean {
|
||||
// eslint-disable-next-line no-useless-escape
|
||||
const tempCandidate = candidate.replace(/\-/g, ':').toLowerCase();
|
||||
return !invalidMacAddresses.has(tempCandidate);
|
||||
}
|
||||
|
||||
export async function getMachineId(): Promise<string> {
|
||||
if (!machineId) {
|
||||
machineId = (async () => {
|
||||
const id = await getMacMachineId();
|
||||
return id ?? randomUUID(); // fallback, generate a UUID
|
||||
})();
|
||||
}
|
||||
|
||||
return machineId;
|
||||
}
|
||||
|
||||
export function getMac(): string {
|
||||
const ifaces = networkInterfaces();
|
||||
for (const name in ifaces) {
|
||||
const networkInterface = ifaces[name];
|
||||
if (networkInterface) {
|
||||
for (const { mac } of networkInterface) {
|
||||
if (validateMacAddress(mac)) {
|
||||
return mac;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error('Unable to retrieve mac address (unexpected format)');
|
||||
}
|
||||
|
||||
async function getMacMachineId(): Promise<string | undefined> {
|
||||
try {
|
||||
const crypto = await import('crypto');
|
||||
const macAddress = getMac();
|
||||
return crypto.createHash('sha256').update(macAddress, 'utf8').digest('hex');
|
||||
} catch (err) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
const homeDir = os.homedir();
|
||||
|
||||
|
@@ -1 +1 @@
|
||||
export const napCatVersion = '2.3.7';
|
||||
export const napCatVersion = '3.0.6';
|
||||
|
@@ -1,63 +0,0 @@
|
||||
import {
|
||||
CacheFileListItem,
|
||||
CacheFileType,
|
||||
ChatCacheListItemBasic,
|
||||
ChatType,
|
||||
InstanceContext,
|
||||
NapCatCore,
|
||||
} from '@/core';
|
||||
|
||||
export class NTQQCacheApi {
|
||||
context: InstanceContext;
|
||||
core: NapCatCore;
|
||||
|
||||
constructor(context: InstanceContext, core: NapCatCore) {
|
||||
this.context = context;
|
||||
this.core = core;
|
||||
}
|
||||
|
||||
async setCacheSilentScan(isSilent: boolean = true) {
|
||||
return '';
|
||||
}
|
||||
|
||||
getCacheSessionPathList() {
|
||||
return '';
|
||||
}
|
||||
|
||||
clearCache(cacheKeys: Array<string> = ['tmp', 'hotUpdate']) {
|
||||
// 参数未验证
|
||||
return this.context.session.getStorageCleanService().clearCacheDataByKeys(cacheKeys);
|
||||
}
|
||||
|
||||
addCacheScannedPaths(pathMap: object = {}) {
|
||||
return this.context.session.getStorageCleanService().addCacheScanedPaths(pathMap);
|
||||
}
|
||||
|
||||
scanCache() {
|
||||
//return (await this.context.session.getStorageCleanService().scanCache()).size;
|
||||
}
|
||||
|
||||
getHotUpdateCachePath() {
|
||||
// 未实现
|
||||
return '';
|
||||
}
|
||||
|
||||
getDesktopTmpPath() {
|
||||
// 未实现
|
||||
return '';
|
||||
}
|
||||
|
||||
getChatCacheList(type: ChatType, pageSize: number = 1000, pageIndex: number = 0) {
|
||||
return this.context.session.getStorageCleanService().getChatCacheInfo(type, pageSize, 1, pageIndex);
|
||||
}
|
||||
|
||||
getFileCacheInfo(fileType: CacheFileType, pageSize: number = 1000, lastRecord?: CacheFileListItem) {
|
||||
// const _lastRecord = lastRecord ? lastRecord : { fileType: fileType };
|
||||
// 需要五个参数
|
||||
// return napCatCore.session.getStorageCleanService().getFileCacheInfo();
|
||||
}
|
||||
|
||||
async clearChatCache(chats: ChatCacheListItemBasic[] = [], fileKeys: string[] = []) {
|
||||
return this.context.session.getStorageCleanService().clearChatCacheInfo(chats, fileKeys);
|
||||
}
|
||||
}
|
@@ -24,16 +24,18 @@ import pathLib from 'node:path';
|
||||
import { defaultVideoThumbB64, getVideoInfo } from '@/common/video';
|
||||
import ffmpeg from 'fluent-ffmpeg';
|
||||
import { encodeSilk } from '@/common/audio';
|
||||
import { MessageContext } from '@/onebot/api';
|
||||
|
||||
export class NTQQFileApi {
|
||||
context: InstanceContext;
|
||||
core: NapCatCore;
|
||||
rkeyManager: RkeyManager;
|
||||
packetRkey: Array<{ rkey: string; time: number; type: number; }> | undefined;
|
||||
|
||||
constructor(context: InstanceContext, core: NapCatCore) {
|
||||
this.context = context;
|
||||
this.core = core;
|
||||
this.rkeyManager = new RkeyManager('http://napcat-sign.wumiao.wang:2082/rkey', this.context.logger);
|
||||
this.rkeyManager = new RkeyManager(['https://llob.linyuchen.net/rkey', 'http://napcat-sign.wumiao.wang:2082/rkey'], this.context.logger);
|
||||
}
|
||||
|
||||
async copyFile(filePath: string, destPath: string) {
|
||||
@@ -71,7 +73,7 @@ export class NTQQFileApi {
|
||||
file_uuid: '',
|
||||
});
|
||||
|
||||
await this.copyFile(filePath, mediaPath!);
|
||||
await this.copyFile(filePath, mediaPath);
|
||||
const fileSize = await this.getFileSize(filePath);
|
||||
return {
|
||||
md5: fileMd5,
|
||||
@@ -82,7 +84,7 @@ export class NTQQFileApi {
|
||||
};
|
||||
}
|
||||
|
||||
async createValidSendFileElement(filePath: string, fileName: string = '', folderId: string = '',): Promise<SendFileElement> {
|
||||
async createValidSendFileElement(context: MessageContext, filePath: string, fileName: string = '', folderId: string = '',): Promise<SendFileElement> {
|
||||
const {
|
||||
fileName: _fileName,
|
||||
path,
|
||||
@@ -91,6 +93,7 @@ export class NTQQFileApi {
|
||||
if (fileSize === 0) {
|
||||
throw new Error('文件异常,大小为0');
|
||||
}
|
||||
context.deleteAfterSentFiles.push(path);
|
||||
return {
|
||||
elementType: ElementType.FILE,
|
||||
elementId: '',
|
||||
@@ -103,12 +106,13 @@ export class NTQQFileApi {
|
||||
};
|
||||
}
|
||||
|
||||
async createValidSendPicElement(picPath: string, summary: string = '', subType: 0 | 1 = 0,): Promise<SendPicElement> {
|
||||
async createValidSendPicElement(context: MessageContext, picPath: string, summary: string = '', subType: 0 | 1 = 0,): Promise<SendPicElement> {
|
||||
const { md5, fileName, path, fileSize } = await this.core.apis.FileApi.uploadFile(picPath, ElementType.PIC, subType);
|
||||
if (fileSize === 0) {
|
||||
throw new Error('文件异常,大小为0');
|
||||
}
|
||||
const imageSize = await this.core.apis.FileApi.getImageSize(picPath);
|
||||
context.deleteAfterSentFiles.push(path);
|
||||
return {
|
||||
elementType: ElementType.PIC,
|
||||
elementId: '',
|
||||
@@ -130,26 +134,40 @@ export class NTQQFileApi {
|
||||
};
|
||||
}
|
||||
|
||||
async createValidSendVideoElement(filePath: string, fileName: string = '', diyThumbPath: string = ''): Promise<SendVideoElement> {
|
||||
async createValidSendVideoElement(context: MessageContext, filePath: string, fileName: string = '', diyThumbPath: string = ''): Promise<SendVideoElement> {
|
||||
const logger = this.core.context.logger;
|
||||
const { fileName: _fileName, path, fileSize, md5 } = await this.core.apis.FileApi.uploadFile(filePath, ElementType.VIDEO);
|
||||
if (fileSize === 0) {
|
||||
throw new Error('文件异常,大小为0');
|
||||
}
|
||||
let thumb = path.replace(`${pathLib.sep}Ori${pathLib.sep}`, `${pathLib.sep}Thumb${pathLib.sep}`);
|
||||
thumb = pathLib.dirname(thumb);
|
||||
let videoInfo = {
|
||||
width: 1920, height: 1080,
|
||||
time: 15,
|
||||
format: 'mp4',
|
||||
size: fileSize,
|
||||
size: 0,
|
||||
filePath,
|
||||
};
|
||||
try {
|
||||
videoInfo = await getVideoInfo(path, logger);
|
||||
videoInfo = await getVideoInfo(filePath, logger);
|
||||
} catch (e) {
|
||||
logger.logError('获取视频信息失败,将使用默认值', e);
|
||||
logger.logError.bind(logger)('获取视频信息失败,将使用默认值', e);
|
||||
}
|
||||
|
||||
let fileExt = 'mp4';
|
||||
try {
|
||||
const tempExt = (await fileType.fileTypeFromFile(filePath))?.ext;
|
||||
if (tempExt) fileExt = tempExt;
|
||||
} catch (e) {
|
||||
this.context.logger.logError.bind(logger)('获取文件类型失败', e);
|
||||
}
|
||||
const newFilePath = filePath + '.' + fileExt;
|
||||
fs.copyFileSync(filePath, newFilePath);
|
||||
context.deleteAfterSentFiles.push(newFilePath);
|
||||
filePath = newFilePath;
|
||||
const { fileName: _fileName, path, fileSize, md5 } = await this.core.apis.FileApi.uploadFile(filePath, ElementType.VIDEO);
|
||||
if (fileSize === 0) {
|
||||
throw new Error('文件异常,大小为0');
|
||||
}
|
||||
videoInfo.size = fileSize;
|
||||
let thumb = path.replace(`${pathLib.sep}Ori${pathLib.sep}`, `${pathLib.sep}Thumb${pathLib.sep}`);
|
||||
thumb = pathLib.dirname(thumb);
|
||||
|
||||
const thumbPath = new Map();
|
||||
const _thumbPath = await new Promise<string | undefined>((resolve, reject) => {
|
||||
const thumbFileName = `${md5}_0.png`;
|
||||
@@ -179,11 +197,13 @@ export class NTQQFileApi {
|
||||
const thumbSize = _thumbPath ? (await fsPromises.stat(_thumbPath)).size : 0;
|
||||
thumbPath.set(0, _thumbPath);
|
||||
const thumbMd5 = _thumbPath ? await calculateFileMD5(_thumbPath) : '';
|
||||
context.deleteAfterSentFiles.push(path);
|
||||
const uploadName = (fileName || _fileName).toLocaleLowerCase().endsWith('.' + fileExt.toLocaleLowerCase()) ? (fileName || _fileName) : (fileName || _fileName) + '.' + fileExt;
|
||||
return {
|
||||
elementType: ElementType.VIDEO,
|
||||
elementId: '',
|
||||
videoElement: {
|
||||
fileName: fileName || _fileName,
|
||||
fileName: uploadName,
|
||||
filePath: path,
|
||||
videoMd5: md5,
|
||||
thumbMd5,
|
||||
@@ -207,7 +227,9 @@ export class NTQQFileApi {
|
||||
throw new Error('文件异常,大小为0');
|
||||
}
|
||||
if (converted) {
|
||||
fsPromises.unlink(silkPath);
|
||||
fsPromises.unlink(silkPath).then().catch(
|
||||
(e) => this.context.logger.logError.bind(this.context.logger)('删除临时文件失败', e)
|
||||
);
|
||||
}
|
||||
return {
|
||||
elementType: ElementType.PTT,
|
||||
@@ -246,7 +268,6 @@ export class NTQQFileApi {
|
||||
}
|
||||
|
||||
async downloadMedia(msgId: string, chatType: ChatType, peerUid: string, elementId: string, thumbPath: string, sourcePath: string, timeout = 1000 * 60 * 2, force: boolean = false) {
|
||||
//logDebug('receive downloadMedia task', msgId, chatType, peerUid, elementId, thumbPath, sourcePath, timeout, force);
|
||||
// 用于下载收到的消息中的图片等
|
||||
if (sourcePath && fs.existsSync(sourcePath)) {
|
||||
if (force) {
|
||||
@@ -345,24 +366,57 @@ export class NTQQFileApi {
|
||||
|
||||
if (url) {
|
||||
const parsedUrl = new URL(IMAGE_HTTP_HOST + url);
|
||||
const urlRkey = parsedUrl.searchParams.get('rkey');
|
||||
const imageAppid = parsedUrl.searchParams.get('appid');
|
||||
const isNTFlavoredPic = imageAppid && ['1406', '1407'].includes(imageAppid);
|
||||
if (isNTFlavoredPic) {
|
||||
let rkey = parsedUrl.searchParams.get('rkey');
|
||||
if (rkey) {
|
||||
return IMAGE_HTTP_HOST_NT + url;
|
||||
const isNTV2 = imageAppid && ['1406', '1407'].includes(imageAppid);
|
||||
const imageFileId = parsedUrl.searchParams.get('fileid');
|
||||
|
||||
const rkeyData = {
|
||||
private_rkey: 'CAQSKAB6JWENi5LM_xp9vumLbuThJSaYf-yzMrbZsuq7Uz2qEc3Rbib9LP4',
|
||||
group_rkey: 'CAQSKAB6JWENi5LM_xp9vumLbuThJSaYf-yzMrbZsuq7Uz2qffcqm614gds',
|
||||
online_rkey: false
|
||||
};
|
||||
try {
|
||||
if (this.core.apis.PacketApi.available) {
|
||||
if ((!this.packetRkey || this.packetRkey[0].time > Date.now() / 1000)) {
|
||||
this.packetRkey = await this.core.apis.PacketApi.sendRkeyPacket();
|
||||
}
|
||||
if (this.packetRkey.length > 0) {
|
||||
rkeyData.group_rkey = this.packetRkey[1].rkey.slice(6);
|
||||
rkeyData.private_rkey = this.packetRkey[0].rkey.slice(6);
|
||||
rkeyData.online_rkey = true;
|
||||
}
|
||||
}
|
||||
const rkeyData = await this.rkeyManager.getRkey();
|
||||
rkey = imageAppid === '1406' ? rkeyData.private_rkey : rkeyData.group_rkey;
|
||||
return IMAGE_HTTP_HOST_NT + url + `${rkey}`;
|
||||
} else {
|
||||
// 老的图片url,不需要rkey
|
||||
return IMAGE_HTTP_HOST + url;
|
||||
} catch (error: any) {
|
||||
this.context.logger.logError.bind(this.context.logger)('获取rkey失败', error.message);
|
||||
}
|
||||
} else if (fileMd5 || md5HexStr) {
|
||||
// 没有url,需要自己拼接
|
||||
|
||||
if (!rkeyData.online_rkey) {
|
||||
try {
|
||||
const tempRkeyData = await this.rkeyManager.getRkey();
|
||||
rkeyData.group_rkey = tempRkeyData.group_rkey;
|
||||
rkeyData.private_rkey = tempRkeyData.private_rkey;
|
||||
rkeyData.online_rkey = tempRkeyData.expired_time > Date.now() / 1000;
|
||||
} catch (e) {
|
||||
this.context.logger.logError.bind(this.context.logger)('获取rkey失败 Fallback Old Mode', e);
|
||||
}
|
||||
}
|
||||
if (isNTV2 && urlRkey) {
|
||||
return IMAGE_HTTP_HOST_NT + urlRkey;
|
||||
} else if (isNTV2 && rkeyData.online_rkey) {
|
||||
const rkey = imageAppid === '1406' ? rkeyData.private_rkey : rkeyData.group_rkey;
|
||||
return IMAGE_HTTP_HOST_NT + url + `&rkey=${rkey}`;
|
||||
} else if (isNTV2 && imageFileId) {
|
||||
const rkey = imageAppid === '1406' ? rkeyData.private_rkey : rkeyData.group_rkey;
|
||||
return IMAGE_HTTP_HOST + `/download?appid=${imageAppid}&fileid=${imageFileId}&rkey=${rkey}`;
|
||||
}
|
||||
|
||||
}
|
||||
//到这里说明可能是旧客户端
|
||||
if (fileMd5 || md5HexStr) {
|
||||
return `${IMAGE_HTTP_HOST}/gchatpic_new/0/0-0-${(fileMd5 ?? md5HexStr)!.toUpperCase()}/0`;
|
||||
}
|
||||
|
||||
this.context.logger.logDebug('图片url获取失败', element);
|
||||
return '';
|
||||
}
|
||||
|
@@ -10,7 +10,9 @@ export class NTQQFriendApi {
|
||||
this.context = context;
|
||||
this.core = core;
|
||||
}
|
||||
|
||||
async setBuddyRemark(uid: string, remark: string) {
|
||||
return this.context.session.getBuddyService().setBuddyRemark({ uid, remark });
|
||||
}
|
||||
async getBuddyV2SimpleInfoMap(refresh = false) {
|
||||
const buddyService = this.context.session.getBuddyService();
|
||||
const buddyListV2 = refresh ? await buddyService.getBuddyListV2('0', BuddyListReqType.KNOMAL) : await buddyService.getBuddyListV2('0', BuddyListReqType.KNOMAL);
|
||||
|
@@ -20,11 +20,12 @@ export class NTQQGroupApi {
|
||||
groupMemberCache: Map<string, Map<string, GroupMember>> = new Map<string, Map<string, GroupMember>>();
|
||||
groups: Group[] = [];
|
||||
essenceLRU = new LimitedHashTable<number, string>(1000);
|
||||
session: any;
|
||||
|
||||
constructor(context: InstanceContext, core: NapCatCore) {
|
||||
this.context = context;
|
||||
this.core = core;
|
||||
this.initCache().then().catch(context.logger.logError);
|
||||
this.initCache().then().catch(context.logger.logError.bind(context.logger));
|
||||
}
|
||||
|
||||
async initCache() {
|
||||
@@ -33,6 +34,15 @@ export class NTQQGroupApi {
|
||||
this.groupCache.set(group.groupCode, group);
|
||||
}
|
||||
this.context.logger.logDebug(`加载${this.groups.length}个群组缓存完成`);
|
||||
// process.pid 调试点
|
||||
}
|
||||
|
||||
async getCoreAndBaseInfo(uids: string[]) {
|
||||
return await this.core.eventWrapper.callNoListenerEvent(
|
||||
'NodeIKernelProfileService/getCoreAndBaseInfo',
|
||||
'nodeStore',
|
||||
uids,
|
||||
);
|
||||
}
|
||||
|
||||
async fetchGroupEssenceList(groupCode: string) {
|
||||
@@ -43,7 +53,9 @@ export class NTQQGroupApi {
|
||||
pageLimit: 300,
|
||||
}, pskey);
|
||||
}
|
||||
|
||||
async getGroupShutUpMemberList(groupCode: string) {
|
||||
return this.context.session.getGroupService().getGroupShutUpMemberList(groupCode);
|
||||
}
|
||||
async clearGroupNotifiesUnreadCount(uk: boolean) {
|
||||
return this.context.session.getGroupService().clearGroupNotifiesUnreadCount(uk);
|
||||
}
|
||||
@@ -131,7 +143,7 @@ export class NTQQGroupApi {
|
||||
let members = this.groupMemberCache.get(groupCodeStr);
|
||||
if (!members) {
|
||||
try {
|
||||
members = await this.getGroupMembersV2(groupCodeStr);
|
||||
members = await this.getGroupMembers(groupCodeStr);
|
||||
// 更新群成员列表
|
||||
this.groupMemberCache.set(groupCodeStr, members);
|
||||
} catch (e) {
|
||||
@@ -152,11 +164,12 @@ export class NTQQGroupApi {
|
||||
|
||||
let member = getMember();
|
||||
if (!member) {
|
||||
members = await this.getGroupMembersV2(groupCodeStr);
|
||||
members = await this.getGroupMembers(groupCodeStr);
|
||||
member = getMember();
|
||||
}
|
||||
return member;
|
||||
}
|
||||
|
||||
async getGroupRecommendContactArkJson(groupCode: string) {
|
||||
return this.context.session.getGroupService().getGroupRecommendContactArkJson(groupCode);
|
||||
}
|
||||
@@ -262,8 +275,27 @@ export class NTQQGroupApi {
|
||||
}
|
||||
return member;
|
||||
}
|
||||
|
||||
async searchGroup(groupCode: string) {
|
||||
const [, ret] = await this.core.eventWrapper.callNormalEventV2(
|
||||
'NodeIKernelSearchService/searchGroup',
|
||||
'NodeIKernelSearchListener/onSearchGroupResult',
|
||||
[{
|
||||
keyWords: groupCode,
|
||||
groupNum: 25,
|
||||
exactSearch: false,
|
||||
penetrate: ''
|
||||
}],
|
||||
(ret) => ret.result === 0,
|
||||
(params) => !!params.groupInfos.find(g => g.groupCode === groupCode),
|
||||
1,
|
||||
5000
|
||||
);
|
||||
return ret.groupInfos.find(g => g.groupCode === groupCode);
|
||||
}
|
||||
|
||||
async getGroupMemberEx(GroupCode: string, uid: string, forced = false, retry = 2) {
|
||||
let data = await solveAsyncProblem((eventWrapper: NTEventWrapper, GroupCode: string, uid: string, forced = false) => {
|
||||
const data = await solveAsyncProblem((eventWrapper: NTEventWrapper, GroupCode: string, uid: string, forced = false) => {
|
||||
return eventWrapper.callNormalEventV2(
|
||||
'NodeIKernelGroupService/getMemberInfo',
|
||||
'NodeIKernelGroupListener/onMemberInfoChange',
|
||||
@@ -278,41 +310,24 @@ export class NTQQGroupApi {
|
||||
return data[3].get(uid);
|
||||
}
|
||||
if (retry > 0) {
|
||||
let trydata = await this.getGroupMemberEx(GroupCode, uid, true, retry - 1) as GroupMember | undefined;
|
||||
const trydata = await this.getGroupMemberEx(GroupCode, uid, true, retry - 1) as GroupMember | undefined;
|
||||
if (trydata) return trydata;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
async getGroupMembersV2(groupQQ: string, num = 3000): Promise<Map<string, GroupMember>> {
|
||||
const groupService = this.context.session.getGroupService();
|
||||
const sceneId = groupService.createMemberListScene(groupQQ, 'groupMemberList_MainWindow');
|
||||
const listener = this.core.eventWrapper.registerListen(
|
||||
'NodeIKernelGroupListener/onMemberListChange',
|
||||
1,
|
||||
5000,
|
||||
(params) => params.sceneId === sceneId,
|
||||
);
|
||||
try {
|
||||
const [membersFromFunc, membersFromListener] = await Promise.allSettled([
|
||||
groupService.getNextMemberList(sceneId, undefined, num),
|
||||
listener,
|
||||
]);
|
||||
if (membersFromFunc.status === 'fulfilled' && membersFromListener.status === 'fulfilled') {
|
||||
return new Map([
|
||||
...membersFromFunc.value.result.infos,
|
||||
...membersFromListener.value[0].infos,
|
||||
]);
|
||||
}
|
||||
if (membersFromFunc.status === 'fulfilled') {
|
||||
return membersFromFunc.value.result.infos;
|
||||
}
|
||||
if (membersFromListener.status === 'fulfilled') {
|
||||
return membersFromListener.value[0].infos;
|
||||
}
|
||||
throw new Error('获取群成员列表失败');
|
||||
} finally {
|
||||
groupService.destroyMemberListScene(sceneId);
|
||||
const sceneId = this.context.session.getGroupService().createMemberListScene(groupQQ, 'groupMemberList_MainWindow');
|
||||
let once = this.core.eventWrapper.registerListen('NodeIKernelGroupListener/onMemberListChange', 1, 2000, (params) => params.sceneId === sceneId)
|
||||
.catch();
|
||||
const result = await this.context.session.getGroupService().getNextMemberList(sceneId!, undefined, num);
|
||||
if (result.errCode !== 0) {
|
||||
throw new Error('获取群成员列表出错,' + result.errMsg);
|
||||
}
|
||||
if (result.result.infos.size === 0) {
|
||||
return (await once)[0].infos;
|
||||
}
|
||||
return result.result.infos;
|
||||
}
|
||||
|
||||
async getGroupMembers(groupQQ: string, num = 3000): Promise<Map<string, GroupMember>> {
|
||||
|
@@ -5,5 +5,4 @@ export * from './msg';
|
||||
export * from './user';
|
||||
export * from './webapi';
|
||||
export * from './sign';
|
||||
export * from './system';
|
||||
export * from './cache';
|
||||
export * from './system';
|
@@ -94,6 +94,7 @@ export class NTQQMsgApi {
|
||||
pageLimit: 1,
|
||||
});
|
||||
}
|
||||
//@deprecated
|
||||
async getMsgsBySeqAndCount(peer: Peer, seq: string, count: number, desc: boolean, z: boolean) {
|
||||
return await this.context.session.getMsgService().getMsgsBySeqAndCount(peer, seq, count, desc, z);
|
||||
}
|
||||
|
148
src/core/apis/packet.ts
Normal file
148
src/core/apis/packet.ts
Normal file
@@ -0,0 +1,148 @@
|
||||
import * as os from 'os';
|
||||
import { ChatType, InstanceContext, NapCatCore } from '..';
|
||||
import offset from '@/core/external/offset.json';
|
||||
import { PacketClient, RecvPacketData } from '@/core/packet/client';
|
||||
import { PacketSession } from "@/core/packet/session";
|
||||
import { PacketHexStr } from "@/core/packet/packer";
|
||||
import { NapProtoMsg } from '@/core/packet/proto/NapProto';
|
||||
import { OidbSvcTrpcTcp0X9067_202_Rsp_Body } from '@/core/packet/proto/oidb/Oidb.0x9067_202';
|
||||
import { OidbSvcTrpcTcpBase, OidbSvcTrpcTcpBaseRsp } from '@/core/packet/proto/oidb/OidbBase';
|
||||
import { OidbSvcTrpcTcp0XFE1_2RSP } from '@/core/packet/proto/oidb/Oidb.0XFE1_2';
|
||||
import { LogWrapper } from "@/common/log";
|
||||
import { SendLongMsgResp } from "@/core/packet/proto/message/action";
|
||||
import { PacketMsg } from "@/core/packet/msg/message";
|
||||
import { OidbSvcTrpcTcp0x6D6Response } from "@/core/packet/proto/oidb/Oidb.0x6D6";
|
||||
import { PacketMsgPicElement } from "@/core/packet/msg/element";
|
||||
import { c } from 'vite/dist/node/types.d-aGj9QkWt';
|
||||
|
||||
interface OffsetType {
|
||||
[key: string]: {
|
||||
recv: string;
|
||||
send: string;
|
||||
};
|
||||
}
|
||||
|
||||
const typedOffset: OffsetType = offset;
|
||||
|
||||
export class NTQQPacketApi {
|
||||
context: InstanceContext;
|
||||
core: NapCatCore;
|
||||
logger: LogWrapper
|
||||
serverUrl: string | undefined;
|
||||
qqVersion: string | undefined;
|
||||
packetSession: PacketSession | undefined;
|
||||
|
||||
constructor(context: InstanceContext, core: NapCatCore) {
|
||||
this.context = context;
|
||||
this.core = core;
|
||||
this.logger = core.context.logger;
|
||||
this.packetSession = undefined;
|
||||
const config = this.core.configLoader.configData;
|
||||
if (config && config.packetServer && config.packetServer.length > 0) {
|
||||
const serverUrl = this.core.configLoader.configData.packetServer ?? '127.0.0.1:8086';
|
||||
this.InitSendPacket(serverUrl, this.context.basicInfoWrapper.getFullQQVesion())
|
||||
.then()
|
||||
.catch(this.core.context.logger.logError.bind(this.core.context.logger));
|
||||
} else {
|
||||
this.core.context.logger.logWarn('PacketServer is not set, will not init NapCat.Packet!');
|
||||
}
|
||||
}
|
||||
|
||||
get available(): boolean {
|
||||
return this.packetSession?.client.available ?? false;
|
||||
}
|
||||
|
||||
async InitSendPacket(serverUrl: string, qqversion: string) {
|
||||
this.serverUrl = serverUrl;
|
||||
this.qqVersion = qqversion;
|
||||
const offsetTable: OffsetType = offset;
|
||||
const table = offsetTable[qqversion + '-' + os.arch()];
|
||||
if (!table) return false;
|
||||
const url = 'ws://' + this.serverUrl + '/ws';
|
||||
this.packetSession = new PacketSession(this.core.context.logger, new PacketClient(url, this.core));
|
||||
const cb = () => {
|
||||
if (this.packetSession && this.packetSession.client) {
|
||||
this.packetSession.client.init(process.pid, table.recv, table.send).then().catch(this.logger.logError.bind(this.logger));
|
||||
}
|
||||
}
|
||||
await this.packetSession.client.connect(cb);
|
||||
return true;
|
||||
}
|
||||
|
||||
async sendPacket(cmd: string, data: PacketHexStr, rsp = false): Promise<RecvPacketData> {
|
||||
return this.packetSession!.client.sendPacket(cmd, data, rsp);
|
||||
}
|
||||
|
||||
async sendPokePacket(peer: number, group?: number) {
|
||||
const data = this.packetSession?.packer.packPokePacket(peer, group);
|
||||
await this.sendPacket('OidbSvcTrpcTcp.0xed3_1', data!, false);
|
||||
}
|
||||
|
||||
async sendRkeyPacket() {
|
||||
const packet = this.packetSession?.packer.packRkeyPacket();
|
||||
const ret = await this.sendPacket('OidbSvcTrpcTcp.0x9067_202', packet!, true);
|
||||
if (!ret?.hex_data) return [];
|
||||
const body = new NapProtoMsg(OidbSvcTrpcTcpBaseRsp).decode(Buffer.from(ret.hex_data, 'hex')).body;
|
||||
const retData = new NapProtoMsg(OidbSvcTrpcTcp0X9067_202_Rsp_Body).decode(body);
|
||||
return retData.data.rkeyList;
|
||||
}
|
||||
|
||||
async sendStatusPacket(uin: number): Promise<{ status: number; ext_status: number; } | undefined> {
|
||||
let status = 0;
|
||||
try {
|
||||
const packet = this.packetSession?.packer.packStatusPacket(uin);
|
||||
const ret = await this.sendPacket('OidbSvcTrpcTcp.0xfe1_2', packet!, true);
|
||||
const data = Buffer.from(ret.hex_data, 'hex');
|
||||
const ext = new NapProtoMsg(OidbSvcTrpcTcp0XFE1_2RSP).decode(new NapProtoMsg(OidbSvcTrpcTcpBase).decode(data).body).data.status.value;
|
||||
// ext & 0xff00 + ext >> 16 & 0xff
|
||||
const extBigInt = BigInt(ext); // 转换为 BigInt
|
||||
if (extBigInt <= 10n) {
|
||||
return { status: Number(extBigInt) * 10, ext_status: 0 };
|
||||
}
|
||||
status = Number((extBigInt & 0xff00n) + ((extBigInt >> 16n) & 0xffn)); // 使用 BigInt 操作符
|
||||
return { status: 10, ext_status: status };
|
||||
} catch (error) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
async sendSetSpecialTittlePacket(groupCode: string, uid: string, tittle: string) {
|
||||
const data = this.packetSession?.packer.packSetSpecialTittlePacket(groupCode, uid, tittle);
|
||||
await this.sendPacket('OidbSvcTrpcTcp.0x8fc_2', data!, true);
|
||||
}
|
||||
|
||||
private async uploadResources(msg: PacketMsg[], groupUin: number = 0) {
|
||||
const reqList = []
|
||||
for (const m of msg) {
|
||||
for (const e of m.msg) {
|
||||
if (e instanceof PacketMsgPicElement) {
|
||||
reqList.push(this.packetSession?.highwaySession.uploadImage({
|
||||
chatType: groupUin ? ChatType.KCHATTYPEGROUP : ChatType.KCHATTYPEC2C,
|
||||
peerUid: String(groupUin) ? String(groupUin) : this.core.selfInfo.uid
|
||||
}, e));
|
||||
}
|
||||
}
|
||||
}
|
||||
return Promise.all(reqList);
|
||||
}
|
||||
|
||||
async sendUploadForwardMsg(msg: PacketMsg[], groupUin: number = 0) {
|
||||
await this.uploadResources(msg, groupUin);
|
||||
const data = await this.packetSession?.packer.packUploadForwardMsg(this.core.selfInfo.uid, msg, groupUin);
|
||||
const ret = await this.sendPacket('trpc.group.long_msg_interface.MsgService.SsoSendLongMsg', data!, true);
|
||||
this.logger.logDebug('sendUploadForwardMsg', ret);
|
||||
const resp = new NapProtoMsg(SendLongMsgResp).decode(Buffer.from(ret.hex_data, 'hex'));
|
||||
return resp.result.resId;
|
||||
}
|
||||
|
||||
async sendGroupFileDownloadReq(groupUin: number, fileUUID: string) {
|
||||
const data = this.packetSession?.packer.packGroupFileDownloadReq(groupUin, fileUUID);
|
||||
const ret = await this.sendPacket('OidbSvcTrpcTcp.0x6d6_2', data!, true);
|
||||
const body = new NapProtoMsg(OidbSvcTrpcTcpBaseRsp).decode(Buffer.from(ret.hex_data, 'hex')).body;
|
||||
const resp = new NapProtoMsg(OidbSvcTrpcTcp0x6D6Response).decode(body);
|
||||
if (resp.download.retCode !== 0) {
|
||||
throw new Error(`sendGroupFileDownloadReq error: ${resp.download.clientWording}`);
|
||||
}
|
||||
return `https://${resp.download.downloadDns}/ftn_handler/${Buffer.from(resp.download.downloadUrl).toString('hex')}/?fname=`
|
||||
}
|
||||
}
|
@@ -11,7 +11,13 @@ export class NTQQUserApi {
|
||||
this.context = context;
|
||||
this.core = core;
|
||||
}
|
||||
|
||||
//self_tind格式
|
||||
async createUidFromTinyId(tinyId: string) {
|
||||
return this.context.session.getMsgService().createUidFromTinyId(this.core.selfInfo.uin, tinyId);
|
||||
}
|
||||
async getStatusByUid(uid: string) {
|
||||
return this.context.session.getProfileService().getStatus(uid);
|
||||
}
|
||||
async getProfileLike(uid: string) {
|
||||
return this.context.session.getProfileLikeService().getBuddyProfileLike({
|
||||
friendUids: [uid],
|
||||
@@ -24,7 +30,18 @@ export class NTQQUserApi {
|
||||
limit: 20,
|
||||
});
|
||||
}
|
||||
|
||||
async fetchOtherProfileLike(uid: string) {
|
||||
return this.context.session.getProfileLikeService().getBuddyProfileLike({
|
||||
friendUids: [uid],
|
||||
basic: 1,
|
||||
vote: 1,
|
||||
favorite: 0,
|
||||
userProfile: 0,
|
||||
type: 1,
|
||||
start: 0,
|
||||
limit: 20,
|
||||
});
|
||||
}
|
||||
async setLongNick(longNick: string) {
|
||||
return this.context.session.getProfileService().setLongNick(longNick);
|
||||
}
|
||||
@@ -51,8 +68,7 @@ export class NTQQUserApi {
|
||||
}
|
||||
|
||||
async setQQAvatar(filePath: string) {
|
||||
type setQQAvatarRet = { result: number, errMsg: string };
|
||||
const ret = await this.context.session.getProfileService().setHeader(filePath) as setQQAvatarRet;
|
||||
const ret = await this.context.session.getProfileService().setHeader(filePath);
|
||||
return { result: ret?.result, errMsg: ret?.errMsg };
|
||||
}
|
||||
|
||||
@@ -103,12 +119,20 @@ export class NTQQUserApi {
|
||||
return this.context.session.getProfileService().modifyDesktopMiniProfile(param);
|
||||
}
|
||||
|
||||
//需要异常处理
|
||||
async getCookies(domain: string) {
|
||||
const ClientKeyData = await this.forceFetchClientKey();
|
||||
const requestUrl = 'https://ssl.ptlogin2.qq.com/jump?ptlang=1033&clientuin=' + this.core.selfInfo.uin +
|
||||
'&clientkey=' + ClientKeyData.clientKey + '&u1=https%3A%2F%2F' + domain + '%2F' + this.core.selfInfo.uin + '%2Finfocenter&keyindex=19%27';
|
||||
return await RequestUtil.HttpsGetCookies(requestUrl);
|
||||
const data = await RequestUtil.HttpsGetCookies(requestUrl);
|
||||
if (!data.p_skey || data.p_skey.length == 0) {
|
||||
try {
|
||||
const pskey = (await this.getPSkey([domain])).domainPskeyMap.get(domain);
|
||||
if (pskey) data.p_skey = pskey;
|
||||
} catch {
|
||||
return data;
|
||||
}
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
async getPSkey(domainList: string[]) {
|
||||
@@ -159,7 +183,7 @@ export class NTQQUserApi {
|
||||
if (uid) return uid;
|
||||
uid = (await this.context.session.getUixConvertService().getUid([Uin])).uidInfo.get(Uin);
|
||||
if (uid) return uid;
|
||||
const unverifiedUid = (await this.getUserDetailInfoByUinV2(Uin)).detail.uid;//从QQ Native 特殊转换
|
||||
const unverifiedUid = (await this.getUserDetailInfoByUin(Uin)).detail.uid;//从QQ Native 特殊转换
|
||||
if (unverifiedUid.indexOf('*') == -1) uid = unverifiedUid;
|
||||
//if (uid) return uid;
|
||||
return uid;
|
||||
@@ -195,7 +219,7 @@ export class NTQQUserApi {
|
||||
return await this.context.session.getRecentContactService().getRecentContactList();
|
||||
}
|
||||
|
||||
async getUserDetailInfoByUinV2(Uin: string) {
|
||||
async getUserDetailInfoByUin(Uin: string) {
|
||||
return await this.core.eventWrapper.callNoListenerEvent(
|
||||
'NodeIKernelProfileService/getUserDetailInfoByUin',
|
||||
Uin
|
||||
|
@@ -157,7 +157,7 @@ export class NTQQWebApi {
|
||||
const cookieObject = await this.core.apis.UserApi.getCookies('qun.qq.com');
|
||||
|
||||
try {
|
||||
let settings = JSON.stringify({
|
||||
const settings = JSON.stringify({
|
||||
is_show_edit_card: is_show_edit_card,
|
||||
tip_window_type: tip_window_type,
|
||||
confirm_required: confirm_required
|
||||
@@ -167,7 +167,7 @@ export class NTQQWebApi {
|
||||
imgWidth: imgWidth.toString(),
|
||||
imgHeight: imgHeight.toString(),
|
||||
};
|
||||
let ret: SetNoticeRetSuccess = await RequestUtil.HttpGetJson<SetNoticeRetSuccess>(
|
||||
const ret: SetNoticeRetSuccess = await RequestUtil.HttpGetJson<SetNoticeRetSuccess>(
|
||||
`https://web.qun.qq.com/cgi-bin/announce/add_qun_notice?${new URLSearchParams({
|
||||
bkn: this.getBknFromCookie(cookieObject),
|
||||
qid: GroupCode,
|
||||
@@ -264,7 +264,7 @@ export class NTQQWebApi {
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.context.logger.logError('获取龙王信息失败');
|
||||
this.context.logger.logError.bind(this.context.logger)('获取龙王信息失败');
|
||||
}
|
||||
}
|
||||
if (getType === WebHonorType.PERFORMER || getType === WebHonorType.ALL) {
|
||||
@@ -280,7 +280,7 @@ export class NTQQWebApi {
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.context.logger.logError('获取群聊之火失败');
|
||||
this.context.logger.logError.bind(this.context.logger)('获取群聊之火失败');
|
||||
}
|
||||
}
|
||||
if (getType === WebHonorType.PERFORMER || getType === WebHonorType.ALL) {
|
||||
@@ -296,7 +296,7 @@ export class NTQQWebApi {
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.context.logger.logError('获取群聊炽焰失败');
|
||||
this.context.logger.logError.bind(this.context.logger)('获取群聊炽焰失败');
|
||||
}
|
||||
}
|
||||
if (getType === WebHonorType.EMOTION || getType === WebHonorType.ALL) {
|
||||
@@ -312,7 +312,7 @@ export class NTQQWebApi {
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.context.logger.logError('获取快乐源泉失败');
|
||||
this.context.logger.logError.bind(this.context.logger)('获取快乐源泉失败');
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -117,6 +117,7 @@ export enum GroupMemberRole {
|
||||
}
|
||||
|
||||
export interface GroupMember {
|
||||
memberRealLevel: string | undefined;
|
||||
memberSpecialTitle?: string;
|
||||
avatarPath: string;
|
||||
cardName: string;
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { GroupMemberRole, Peer } from '@/core';
|
||||
import { GroupMemberRole } from '@/core';
|
||||
|
||||
export interface Peer {
|
||||
chatType: ChatType;
|
||||
@@ -372,6 +372,7 @@ export interface ReplyElement {
|
||||
senderUin: string;
|
||||
senderUidStr?: string;
|
||||
replyMsgTime?: string;
|
||||
replyMsgClientSeq?: string;
|
||||
}
|
||||
|
||||
export interface SendReplyElement {
|
||||
@@ -391,7 +392,7 @@ export interface SendMarketFaceElement {
|
||||
marketFaceElement: MarketFaceElement;
|
||||
}
|
||||
|
||||
export interface SendstructLongMsgElement {
|
||||
export interface SendStructLongMsgElement {
|
||||
elementType: ElementType.STRUCTLONGMSG;
|
||||
elementId: string;
|
||||
structLongMsgElement: StructLongMsgElement;
|
||||
@@ -516,6 +517,12 @@ export enum AtType {
|
||||
atAll = 1,
|
||||
atUser = 2
|
||||
}
|
||||
export enum MsgSourceType {
|
||||
K_DOWN_SOURCETYPE_AIOINNER = 1,
|
||||
K_DOWN_SOURCETYPE_BIGSCREEN = 2,
|
||||
K_DOWN_SOURCETYPE_HISTORY = 3,
|
||||
K_DOWN_SOURCETYPE_UNKNOWN = 0
|
||||
}
|
||||
|
||||
// 来自Android分析
|
||||
export enum ChatType {
|
||||
@@ -659,7 +666,8 @@ export interface GrayTipElement {
|
||||
export enum FaceType {
|
||||
normal = 1, // 小黄脸
|
||||
normal2 = 2, // 新小黄脸, 从faceIndex 222开始?
|
||||
dice = 3 // 骰子
|
||||
dice = 3, // 骰子
|
||||
poke = 5 // 拍一拍
|
||||
}
|
||||
|
||||
export enum FaceIndex {
|
||||
@@ -872,6 +880,8 @@ export interface RawMessage {
|
||||
/**
|
||||
* 扩展字段,与 Ob11 msg ID 有关
|
||||
*/
|
||||
|
||||
|
||||
id?: number;
|
||||
|
||||
guildId: string;
|
||||
@@ -908,11 +918,26 @@ export interface RawMessage {
|
||||
*/
|
||||
peerUin: string;
|
||||
|
||||
/**
|
||||
* 好友备注(如果是好友消息)
|
||||
*/
|
||||
remark?: string;
|
||||
|
||||
/**
|
||||
* 群名(如果是群消息)
|
||||
*/
|
||||
peerName: string;
|
||||
|
||||
/**
|
||||
* 发送者昵称(如果是好友消息)
|
||||
*/
|
||||
sendNickName: string;
|
||||
|
||||
/**
|
||||
* 发送者好友备注(如果是群消息并且有发送者好友)
|
||||
*/
|
||||
sendRemarkName: string;
|
||||
|
||||
/**
|
||||
* 发送者群名片(如果是群消息)
|
||||
*/
|
||||
@@ -933,6 +958,8 @@ export interface RawMessage {
|
||||
records: RawMessage[];
|
||||
|
||||
elements: MessageElement[];
|
||||
|
||||
sourceType: MsgSourceType;
|
||||
}
|
||||
export interface QueryMsgsParams {
|
||||
chatInfo: Peer;
|
||||
@@ -959,3 +986,18 @@ export interface TmpChatInfo {
|
||||
sessionType: number;
|
||||
sig: string;
|
||||
}
|
||||
export interface MsgReqType {
|
||||
peer: Peer,
|
||||
byType: number,
|
||||
msgId: string,
|
||||
msgSeq: string,
|
||||
msgTime: string,
|
||||
clientSeq: string,
|
||||
cnt: number,
|
||||
queryOrder: boolean,
|
||||
includeSelf: boolean,
|
||||
includeDeleteMsg: boolean,
|
||||
extraCnt: number
|
||||
}
|
||||
//getMsgsIncludeSelf Peer必须 byType 1
|
||||
//getMsgsWithMsgTimeAndClientSeqForC2C Peer必须 byType 3
|
||||
|
@@ -1,14 +1,15 @@
|
||||
export interface IdMusicSignPostData {
|
||||
type: 'qq' | '163',
|
||||
type: 'qq' | '163' | 'kugou' | 'migu' | 'kuwo',
|
||||
id: string | number,
|
||||
}
|
||||
|
||||
export interface CustomMusicSignPostData {
|
||||
type: 'custom',
|
||||
type: 'qq' | '163' | 'kugou' | 'migu' | 'kuwo' | 'custom',
|
||||
id: undefined,
|
||||
url: string,
|
||||
audio: string,
|
||||
title: string,
|
||||
image?: string,
|
||||
audio?: string,
|
||||
title?: string,
|
||||
image: string,
|
||||
singer?: string
|
||||
}
|
||||
|
||||
|
@@ -153,7 +153,10 @@ interface CommonExt {
|
||||
labels: any[];
|
||||
qqLevel: QQLevel;
|
||||
}
|
||||
|
||||
export enum BuddyListReqType {
|
||||
KNOMAL,
|
||||
KLETTER
|
||||
}
|
||||
interface Pic {
|
||||
picId: string;
|
||||
picTime: number;
|
||||
@@ -213,7 +216,7 @@ export interface BuddyProfileLikeReq {
|
||||
userProfile: number;
|
||||
type: number;
|
||||
start: number;
|
||||
limit: number;
|
||||
limit?: number;
|
||||
}
|
||||
|
||||
export interface QQLevel {
|
||||
@@ -285,16 +288,16 @@ export interface User {
|
||||
export interface SelfInfo extends User {
|
||||
online?: boolean;
|
||||
}
|
||||
export type Friend = User;
|
||||
|
||||
export interface Friend extends User {
|
||||
}
|
||||
// 本来是 Friend extends User 现在用不到
|
||||
|
||||
export enum BizKey {
|
||||
KPRIVILEGEICON,
|
||||
KPHOTOWALL
|
||||
}
|
||||
|
||||
export interface UserDetailInfoByUinV2 {
|
||||
export interface UserDetailInfoByUin {
|
||||
result: number,
|
||||
errMsg: string,
|
||||
detail: {
|
||||
@@ -305,65 +308,6 @@ export interface UserDetailInfoByUinV2 {
|
||||
photoWall: null
|
||||
}
|
||||
}
|
||||
|
||||
export interface UserDetailInfoByUin {
|
||||
result: number,
|
||||
errMsg: string,
|
||||
info: {
|
||||
uid: string,//这个没办法用
|
||||
qid: string,
|
||||
uin: string,
|
||||
nick: string,
|
||||
remark: string,
|
||||
longNick: string,
|
||||
avatarUrl: string,
|
||||
birthday_year: number,
|
||||
birthday_month: number,
|
||||
birthday_day: number,
|
||||
sex: number,//0
|
||||
topTime: string,
|
||||
constellation: number,
|
||||
shengXiao: number,
|
||||
kBloodType: number,
|
||||
homeTown: string,
|
||||
makeFriendCareer: number,
|
||||
pos: string,
|
||||
eMail: string,
|
||||
phoneNum: string,
|
||||
college: string,
|
||||
country: string,
|
||||
province: string,
|
||||
city: string,
|
||||
postCode: string,
|
||||
address: string,
|
||||
isBlock: boolean,
|
||||
isSpecialCareOpen: boolean,
|
||||
isSpecialCareZone: boolean,
|
||||
ringId: string,
|
||||
regTime: number,
|
||||
interest: string,
|
||||
termType: number,
|
||||
labels: any[],
|
||||
qqLevel: { crownNum: number, sunNum: number, moonNum: number, starNum: number },
|
||||
isHideQQLevel: number,
|
||||
privilegeIcon: { jumpUrl: string, openIconList: any[], closeIconList: any[] },
|
||||
isHidePrivilegeIcon: number,
|
||||
photoWall: { picList: any[] },
|
||||
vipFlag: boolean,
|
||||
yearVipFlag: boolean,
|
||||
svipFlag: boolean,
|
||||
vipLevel: number,
|
||||
status: number,
|
||||
qidianMasterFlag: number,
|
||||
qidianCrewFlag: number,
|
||||
qidianCrewFlag2: number,
|
||||
extStatus: number,
|
||||
recommendImgFlag: number,
|
||||
disableEmojiShortCuts: number,
|
||||
pendantId: string,
|
||||
vipNameColorId: string
|
||||
}
|
||||
}
|
||||
export enum UserDetailSource {
|
||||
KDB,
|
||||
KSERVER
|
||||
@@ -375,8 +319,4 @@ export enum ProfileBizType {
|
||||
KVAS,
|
||||
KQZONE,
|
||||
KOTHER
|
||||
}export enum BuddyListReqType {
|
||||
KNOMAL,
|
||||
KLETTER
|
||||
}
|
||||
|
||||
}
|
60
src/core/external/appid.json
vendored
60
src/core/external/appid.json
vendored
@@ -1,14 +1,54 @@
|
||||
{
|
||||
"3.2.12-27597": {
|
||||
"appid": 537243600,
|
||||
"qua": "V1_LNX_NQ_3.2.12_27597_GW_B"
|
||||
"9.9.15-28060": {
|
||||
"appid": 537246092,
|
||||
"qua": "V1_WIN_NQ_9.9.15_28060_GW_B"
|
||||
},
|
||||
"9.9.15-27597": {
|
||||
"appid": 537243441,
|
||||
"qua": "V1_WIN_NQ_9.9.15_27597_GW_B"
|
||||
"9.9.15-28131": {
|
||||
"appid": 537246092,
|
||||
"qua": "V1_WIN_NQ_9.9.15_28131_GW_B"
|
||||
},
|
||||
"6.9.53-27597": {
|
||||
"appid": 537243538,
|
||||
"qua": "V1_MAC_NQ_6.9.53_27597_GW_B"
|
||||
"3.2.12-28060": {
|
||||
"appid": 537246140,
|
||||
"qua": "V1_LNX_NQ_3.2.12_28060_GW_B"
|
||||
},
|
||||
"3.2.12-28131": {
|
||||
"appid": 537246140,
|
||||
"qua": "V1_LNX_NQ_3.2.12_28131_GW_B"
|
||||
},
|
||||
"6.9.55-28131": {
|
||||
"appid": 537246115,
|
||||
"qua": "V1_MAC_NQ_6.9.55_28131_GW_B"
|
||||
},
|
||||
"9.9.15-28327": {
|
||||
"appid": 537249321,
|
||||
"qua": "V1_WIN_NQ_9.9.15_28327_GW_B"
|
||||
},
|
||||
"3.2.12-28327": {
|
||||
"appid": 537249393,
|
||||
"qua": "V1_LNX_NQ_3.2.12_28327_GW_B"
|
||||
},
|
||||
"9.9.15-28418": {
|
||||
"appid": 537249321,
|
||||
"qua": "V1_WIN_NQ_9.9.15_28418_GW_B"
|
||||
},
|
||||
"3.2.12-28418": {
|
||||
"appid": 537249393,
|
||||
"qua": "V1_LNX_NQ_3.2.12_28418_GW_B"
|
||||
},
|
||||
"6.9.56-28418": {
|
||||
"appid": 537249367,
|
||||
"qua": "V1_MAC_NQ_6.9.56_28418_GW_B"
|
||||
},
|
||||
"9.9.15-28498": {
|
||||
"appid": 537249321,
|
||||
"qua": "V1_WIN_NQ_9.9.15_28498_GW_B"
|
||||
},
|
||||
"3.2.13-28788": {
|
||||
"appid": 537249787,
|
||||
"qua": "V1_LNX_NQ_3.2.13_28788_GW_B"
|
||||
},
|
||||
"9.9.16-28788": {
|
||||
"appid": 537249739,
|
||||
"qua": "V1_WIN_NQ_9.9.16_28788_GW_B"
|
||||
}
|
||||
}
|
||||
}
|
5
src/core/external/napcat.json
vendored
5
src/core/external/napcat.json
vendored
@@ -2,5 +2,6 @@
|
||||
"fileLog": true,
|
||||
"consoleLog": true,
|
||||
"fileLogLevel": "debug",
|
||||
"consoleLogLevel": "info"
|
||||
}
|
||||
"consoleLogLevel": "info",
|
||||
"packetServer": ""
|
||||
}
|
22
src/core/external/offset.json
vendored
Normal file
22
src/core/external/offset.json
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"3.2.12-28418-x64": {
|
||||
"recv": "A0723E0",
|
||||
"send": "A06EAE0"
|
||||
},
|
||||
"9.9.15-28418-x64": {
|
||||
"recv": "37A9004",
|
||||
"send": "37A4BD0"
|
||||
},
|
||||
"9.9.15-28498-x64": {
|
||||
"recv": "37A9004",
|
||||
"send": "37A4BD0"
|
||||
},
|
||||
"9.9.16-28788-x64": {
|
||||
"send": "38076D0",
|
||||
"recv": "380BB04"
|
||||
},
|
||||
"3.2.13-28788-x64": {
|
||||
"send": "A0CEC20",
|
||||
"recv": "A0D2520"
|
||||
}
|
||||
}
|
18
src/core/external/proto/ProfileLikeTip.proto
vendored
Normal file
18
src/core/external/proto/ProfileLikeTip.proto
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
syntax = "proto3";
|
||||
package SysMessage;
|
||||
|
||||
message likeDetail {
|
||||
string txt = 1;
|
||||
int64 uin = 3;
|
||||
string nickname = 5;
|
||||
}
|
||||
|
||||
message likeMsg {
|
||||
int32 times = 1;
|
||||
int32 time = 2;
|
||||
likeDetail detail = 3;
|
||||
}
|
||||
|
||||
message profileLikeTip {
|
||||
likeMsg msg = 14;
|
||||
}
|
@@ -8,7 +8,7 @@ interface ServerRkeyData {
|
||||
}
|
||||
|
||||
export class RkeyManager {
|
||||
serverUrl: string = '';
|
||||
serverUrl: string[] = [];
|
||||
logger: LogWrapper;
|
||||
private rkeyData: ServerRkeyData = {
|
||||
group_rkey: '',
|
||||
@@ -16,7 +16,7 @@ export class RkeyManager {
|
||||
expired_time: 0,
|
||||
};
|
||||
|
||||
constructor(serverUrl: string, logger: LogWrapper) {
|
||||
constructor(serverUrl: string[], logger: LogWrapper) {
|
||||
this.logger = logger;
|
||||
this.serverUrl = serverUrl;
|
||||
}
|
||||
@@ -26,7 +26,8 @@ export class RkeyManager {
|
||||
try {
|
||||
await this.refreshRkey();
|
||||
} catch (e) {
|
||||
this.logger.logError('获取rkey失败', e);
|
||||
throw new Error(`获取rkey失败: ${e}`);//外抛
|
||||
//this.logger.logError.bind(this.logger)('获取rkey失败', e);
|
||||
}
|
||||
}
|
||||
return this.rkeyData;
|
||||
@@ -40,6 +41,22 @@ export class RkeyManager {
|
||||
|
||||
async refreshRkey(): Promise<any> {
|
||||
//刷新rkey
|
||||
this.rkeyData = await RequestUtil.HttpGetJson<ServerRkeyData>(this.serverUrl, 'GET');
|
||||
for (const url of this.serverUrl) {
|
||||
try {
|
||||
const temp = await RequestUtil.HttpGetJson<ServerRkeyData>(url, 'GET');
|
||||
this.rkeyData = {
|
||||
group_rkey: temp.group_rkey.slice(6),
|
||||
private_rkey: temp.private_rkey.slice(6),
|
||||
expired_time: temp.expired_time
|
||||
};
|
||||
} catch (e) {
|
||||
this.logger.logError.bind(this.logger)(`[Rkey] Get Rkey ${url} Error `, e);
|
||||
//是否为最后一个url
|
||||
if (url === this.serverUrl[this.serverUrl.length - 1]) {
|
||||
throw new Error(`获取rkey失败: ${e}`);//外抛
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@@ -22,14 +22,14 @@ import { QQBasicInfoWrapper } from '@/common/qq-basic-info';
|
||||
import { NapCatPathWrapper } from '@/common/path';
|
||||
import path from 'node:path';
|
||||
import fs from 'node:fs';
|
||||
import { getMachineId, hostname, systemName, systemVersion } from '@/common/system';
|
||||
import { hostname, systemName, systemVersion } from '@/common/system';
|
||||
import { NTEventWrapper } from '@/common/event';
|
||||
import { DataSource, GroupMember, KickedOffLineInfo, SelfInfo, SelfStatusInfo } from '@/core/entities';
|
||||
import { NapCatConfigLoader } from '@/core/helper/config';
|
||||
import os from 'node:os';
|
||||
import { NodeIKernelGroupListener, NodeIKernelMsgListener, NodeIKernelProfileListener } from '@/core/listeners';
|
||||
import { proxiedListenerOf } from '@/common/proxy-handler';
|
||||
|
||||
import { NTQQPacketApi } from './apis/packet';
|
||||
export * from './wrapper';
|
||||
export * from './entities';
|
||||
export * from './services';
|
||||
@@ -45,12 +45,18 @@ export function loadQQWrapper(QQVersion: string): WrapperNodeApi {
|
||||
let appPath;
|
||||
if (os.platform() === 'darwin') {
|
||||
appPath = path.resolve(path.dirname(process.execPath), '../Resources/app');
|
||||
} else {
|
||||
} else if (os.platform() === 'linux') {
|
||||
appPath = path.resolve(path.dirname(process.execPath), './resources/app');
|
||||
} else {
|
||||
appPath = path.resolve(path.dirname(process.execPath), `./versions/${QQVersion}/`);
|
||||
}
|
||||
let wrapperNodePath = path.resolve(appPath, 'wrapper.node');
|
||||
if (!fs.existsSync(wrapperNodePath)) {
|
||||
wrapperNodePath = path.join(appPath, `versions/${QQVersion}/wrapper.node`);
|
||||
wrapperNodePath = path.join(appPath, `./resources/app/wrapper.node`);
|
||||
}
|
||||
//老版本兼容 未来去掉
|
||||
if (!fs.existsSync(wrapperNodePath)) {
|
||||
wrapperNodePath = path.join(path.dirname(process.execPath), `./resources/app/versions/${QQVersion}/wrapper.node`);
|
||||
}
|
||||
const nativemodule: any = { exports: {} };
|
||||
process.dlopen(nativemodule, wrapperNodePath);
|
||||
@@ -75,17 +81,18 @@ export class NapCatCore {
|
||||
this.context = context;
|
||||
this.util = this.context.wrapper.NodeQQNTWrapperUtil;
|
||||
this.eventWrapper = new NTEventWrapper(context.session);
|
||||
this.configLoader = new NapCatConfigLoader(this, this.context.pathWrapper.configPath);
|
||||
this.apis = {
|
||||
FileApi: new NTQQFileApi(this.context, this),
|
||||
SystemApi: new NTQQSystemApi(this.context, this),
|
||||
CollectionApi: new NTQQCollectionApi(this.context, this),
|
||||
PacketApi: new NTQQPacketApi(this.context, this),
|
||||
WebApi: new NTQQWebApi(this.context, this),
|
||||
FriendApi: new NTQQFriendApi(this.context, this),
|
||||
MsgApi: new NTQQMsgApi(this.context, this),
|
||||
UserApi: new NTQQUserApi(this.context, this),
|
||||
GroupApi: new NTQQGroupApi(this.context, this),
|
||||
};
|
||||
this.configLoader = new NapCatConfigLoader(this, this.context.pathWrapper.configPath);
|
||||
this.NapCatDataPath = path.join(this.dataPath, 'NapCat');
|
||||
fs.mkdirSync(this.NapCatDataPath, { recursive: true });
|
||||
this.NapCatTempPath = path.join(this.NapCatDataPath, 'temp');
|
||||
@@ -93,7 +100,8 @@ export class NapCatCore {
|
||||
if (!fs.existsSync(this.NapCatTempPath)) {
|
||||
fs.mkdirSync(this.NapCatTempPath, { recursive: true });
|
||||
}
|
||||
this.initNapCatCoreListeners().then().catch(this.context.logger.logError);
|
||||
|
||||
this.initNapCatCoreListeners().then().catch(this.context.logger.logError.bind(this.context.logger));
|
||||
|
||||
this.context.logger.setFileLogEnabled(
|
||||
this.configLoader.configData.fileLog,
|
||||
@@ -121,7 +129,7 @@ export class NapCatCore {
|
||||
const msgListener = new NodeIKernelMsgListener();
|
||||
msgListener.onKickedOffLine = (Info: KickedOffLineInfo) => {
|
||||
// 下线通知
|
||||
this.context.logger.logError('[KickedOffLine] [' + Info.tipsTitle + '] ' + Info.tipsDesc);
|
||||
this.context.logger.logError.bind(this.context.logger)('[KickedOffLine] [' + Info.tipsTitle + '] ' + Info.tipsDesc);
|
||||
this.selfInfo.online = false;
|
||||
};
|
||||
msgListener.onRecvMsg = (msgs) => {
|
||||
@@ -145,8 +153,9 @@ export class NapCatCore {
|
||||
if (Info.status == 20) {
|
||||
this.selfInfo.online = false;
|
||||
this.context.logger.log("账号状态变更为离线");
|
||||
} else {
|
||||
this.selfInfo.online = true;
|
||||
}
|
||||
this.selfInfo.online = true;
|
||||
};
|
||||
this.context.session.getProfileService().addKernelProfileListener(
|
||||
proxiedListenerOf(profileListener, this.context.logger),
|
||||
@@ -240,22 +249,34 @@ export class NapCatCore {
|
||||
}
|
||||
}
|
||||
|
||||
export async function genSessionConfig(QQVersionAppid: string, QQVersion: string, selfUin: string, selfUid: string, account_path: string): Promise<WrapperSessionInitConfig> {
|
||||
export async function genSessionConfig(
|
||||
guid: string,
|
||||
QQVersionAppid: string,
|
||||
QQVersion: string,
|
||||
selfUin: string,
|
||||
selfUid: string,
|
||||
account_path: string
|
||||
): Promise<WrapperSessionInitConfig> {
|
||||
const downloadPath = path.join(account_path, 'NapCat', 'temp');
|
||||
fs.mkdirSync(downloadPath, { recursive: true });
|
||||
const guid: string = await getMachineId();//26702 支持JS获取guid值 在LoginService中获取 TODO mlikiow a
|
||||
const platformMapping: Partial<Record<NodeJS.Platform, PlatformType>> = {
|
||||
win32: PlatformType.KWINDOWS,
|
||||
darwin: PlatformType.KMAC,
|
||||
linux: PlatformType.KLINUX,
|
||||
};
|
||||
const systemPlatform = platformMapping[os.platform()] ?? PlatformType.KWINDOWS;
|
||||
return {
|
||||
selfUin,
|
||||
selfUid,
|
||||
desktopPathConfig: {
|
||||
account_path, // 可以通过NodeQQNTWrapperUtil().getNTUserDataInfoConfig()获取
|
||||
},
|
||||
clientVer: QQVersion, // 9.9.8-22355
|
||||
clientVer: QQVersion,
|
||||
a2: '',
|
||||
d2: '',
|
||||
d2Key: '',
|
||||
machineId: '',
|
||||
platform: PlatformType.KWINDOWS, // 3是Windows?
|
||||
platform: systemPlatform, // 3是Windows?
|
||||
platVer: systemVersion, // 系统版本号, 应该可以固定
|
||||
appid: QQVersionAppid,
|
||||
rdeliveryConfig: {
|
||||
@@ -263,7 +284,7 @@ export async function genSessionConfig(QQVersionAppid: string, QQVersion: string
|
||||
systemId: 0,
|
||||
appId: '',
|
||||
logicEnvironment: '',
|
||||
platform: PlatformType.KWINDOWS,
|
||||
platform: systemPlatform,
|
||||
language: '',
|
||||
sdkVersion: '',
|
||||
userId: '',
|
||||
@@ -303,6 +324,7 @@ export interface InstanceContext {
|
||||
export interface StableNTApiWrapper {
|
||||
FileApi: NTQQFileApi,
|
||||
SystemApi: NTQQSystemApi,
|
||||
PacketApi: NTQQPacketApi,
|
||||
CollectionApi: NTQQCollectionApi,
|
||||
WebApi: NTQQWebApi,
|
||||
FriendApi: NTQQFriendApi,
|
||||
|
97
src/core/listeners/NodeIKernelSearchListener.ts
Normal file
97
src/core/listeners/NodeIKernelSearchListener.ts
Normal file
@@ -0,0 +1,97 @@
|
||||
import { ChatType } from '@/core';
|
||||
export interface SearchGroupInfo {
|
||||
groupCode: string;
|
||||
ownerUid: string;
|
||||
groupFlag: number;
|
||||
groupFlagExt: number;
|
||||
maxMemberNum: number;
|
||||
memberNum: number;
|
||||
groupOption: number;
|
||||
classExt: number;
|
||||
groupName: string;
|
||||
fingerMemo: string;
|
||||
groupQuestion: string;
|
||||
certType: number;
|
||||
shutUpAllTimestamp: number;
|
||||
shutUpMeTimestamp: number;
|
||||
groupTypeFlag: number;
|
||||
privilegeFlag: number;
|
||||
groupSecLevel: number;
|
||||
groupFlagExt3: number;
|
||||
isConfGroup: number;
|
||||
isModifyConfGroupFace: number;
|
||||
isModifyConfGroupName: number;
|
||||
noFigerOpenFlag: number;
|
||||
noCodeFingerOpenFlag: number;
|
||||
groupFlagExt4: number;
|
||||
groupMemo: string;
|
||||
cmdUinMsgSeq: number;
|
||||
cmdUinJoinTime: number;
|
||||
cmdUinUinFlag: number;
|
||||
cmdUinMsgMask: number;
|
||||
groupSecLevelInfo: number;
|
||||
cmdUinPrivilege: number;
|
||||
cmdUinFlagEx2: number;
|
||||
appealDeadline: number;
|
||||
remarkName: string;
|
||||
isTop: boolean;
|
||||
richFingerMemo: string;
|
||||
groupAnswer: string;
|
||||
joinGroupAuth: string;
|
||||
isAllowModifyConfGroupName: number;
|
||||
}
|
||||
|
||||
export interface GroupInfo {
|
||||
groupCode: string;
|
||||
searchGroupInfo: SearchGroupInfo;
|
||||
privilege: number;
|
||||
}
|
||||
|
||||
export interface GroupSearchResult {
|
||||
keyWord: string;
|
||||
errorCode: number;
|
||||
groupInfos: GroupInfo[];
|
||||
penetrate: string;
|
||||
isEnd: boolean;
|
||||
nextPos: number;
|
||||
}
|
||||
export interface NodeIKernelSearchListener {
|
||||
|
||||
onSearchGroupResult(params: GroupSearchResult): void;
|
||||
|
||||
onSearchFileKeywordsResult(params: {
|
||||
searchId: string,
|
||||
hasMore: boolean,
|
||||
resultItems: {
|
||||
chatType: ChatType,
|
||||
buddyChatInfo: any[],
|
||||
discussChatInfo: any[],
|
||||
groupChatInfo: {
|
||||
groupCode: string,
|
||||
isConf: boolean,
|
||||
hasModifyConfGroupFace: boolean,
|
||||
hasModifyConfGroupName: boolean,
|
||||
groupName: string,
|
||||
remark: string
|
||||
}[],
|
||||
dataLineChatInfo: any[],
|
||||
tmpChatInfo: any[],
|
||||
msgId: string,
|
||||
msgSeq: string,
|
||||
msgTime: string,
|
||||
senderUid: string,
|
||||
senderNick: string,
|
||||
senderRemark: string,
|
||||
senderCard: string,
|
||||
elemId: string,
|
||||
elemType: number,
|
||||
fileSize: string,
|
||||
filePath: string,
|
||||
fileName: string,
|
||||
hits: {
|
||||
start: number,
|
||||
end: number
|
||||
}[]
|
||||
}[]
|
||||
}): void;
|
||||
}
|
@@ -1,39 +0,0 @@
|
||||
import { ChatType } from '@/core';
|
||||
|
||||
export interface NodeIKernelSearchListener_Polyfill {
|
||||
onSearchFileKeywordsResult(params: {
|
||||
searchId: string,
|
||||
hasMore: boolean,
|
||||
resultItems: {
|
||||
chatType: ChatType,
|
||||
buddyChatInfo: any[],
|
||||
discussChatInfo: any[],
|
||||
groupChatInfo: {
|
||||
groupCode: string,
|
||||
isConf: boolean,
|
||||
hasModifyConfGroupFace: boolean,
|
||||
hasModifyConfGroupName: boolean,
|
||||
groupName: string,
|
||||
remark: string
|
||||
}[],
|
||||
dataLineChatInfo: any[],
|
||||
tmpChatInfo: any[],
|
||||
msgId: string,
|
||||
msgSeq: string,
|
||||
msgTime: string,
|
||||
senderUid: string,
|
||||
senderNick: string,
|
||||
senderRemark: string,
|
||||
senderCard: string,
|
||||
elemId: string,
|
||||
elemType: number,
|
||||
fileSize: string,
|
||||
filePath: string,
|
||||
fileName: string,
|
||||
hits: {
|
||||
start: number,
|
||||
end: number
|
||||
}[]
|
||||
}[]
|
||||
}): void;
|
||||
}
|
5
src/core/listeners/NodeIO3MiscListener.ts
Normal file
5
src/core/listeners/NodeIO3MiscListener.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export class NodeIO3MiscListener {
|
||||
getOnAmgomDataPiece(...arg: unknown[]) {
|
||||
|
||||
}
|
||||
}
|
@@ -9,7 +9,7 @@ export * from './NodeIKernelProfileListener';
|
||||
export * from './NodeIKernelTicketListener';
|
||||
export * from './NodeIKernelStorageCleanListener';
|
||||
export * from './NodeIKernelFileAssistantListener';
|
||||
export * from './NodeIKernelSearchListener_Polyfill';
|
||||
export * from './NodeIKernelSearchListener';
|
||||
|
||||
import type {
|
||||
NodeIKernelBuddyListener,
|
||||
@@ -19,11 +19,11 @@ import type {
|
||||
NodeIKernelMsgListener,
|
||||
NodeIKernelProfileListener,
|
||||
NodeIKernelRobotListener,
|
||||
NodeIKernelSearchListener_Polyfill,
|
||||
NodeIKernelSessionListener,
|
||||
NodeIKernelStorageCleanListener,
|
||||
NodeIKernelTicketListener,
|
||||
} from '.';
|
||||
import { NodeIKernelSearchListener } from './NodeIKernelSearchListener';
|
||||
|
||||
export type ListenerNamingMapping = {
|
||||
NodeIKernelSessionListener: NodeIKernelSessionListener;
|
||||
@@ -36,5 +36,5 @@ export type ListenerNamingMapping = {
|
||||
NodeIKernelTicketListener: NodeIKernelTicketListener;
|
||||
NodeIKernelStorageCleanListener: NodeIKernelStorageCleanListener;
|
||||
NodeIKernelFileAssistantListener: NodeIKernelFileAssistantListener;
|
||||
NodeIKernelSearchListener: NodeIKernelSearchListener_Polyfill;
|
||||
NodeIKernelSearchListener: NodeIKernelSearchListener;
|
||||
};
|
||||
|
180
src/core/packet/client.ts
Normal file
180
src/core/packet/client.ts
Normal file
@@ -0,0 +1,180 @@
|
||||
import { LogWrapper } from "@/common/log";
|
||||
import { LRUCache } from "@/common/lru-cache";
|
||||
import WebSocket, { Data } from "ws";
|
||||
import crypto, { createHash } from "crypto";
|
||||
import { NapCatCore } from "@/core";
|
||||
import { PacketHexStr } from "@/core/packet/packer";
|
||||
import { sleep } from "@/common/helper";
|
||||
|
||||
export interface RecvPacket {
|
||||
type: string, // 仅recv
|
||||
trace_id_md5?: string,
|
||||
data: RecvPacketData
|
||||
}
|
||||
|
||||
export interface RecvPacketData {
|
||||
seq: number
|
||||
cmd: string
|
||||
hex_data: string
|
||||
}
|
||||
|
||||
export class PacketClient {
|
||||
private websocket: WebSocket | undefined;
|
||||
private isConnected: boolean = false;
|
||||
private reconnectAttempts: number = 0;
|
||||
private readonly maxReconnectAttempts: number = 60;//现在暂时不可配置
|
||||
private readonly cb = new LRUCache<string, (json: RecvPacketData) => Promise<void>>(500); // trace_id-type callback
|
||||
private readonly clientUrl: string = '';
|
||||
readonly napCatCore: NapCatCore;
|
||||
private readonly logger: LogWrapper;
|
||||
|
||||
constructor(url: string, core: NapCatCore) {
|
||||
this.clientUrl = url;
|
||||
this.napCatCore = core;
|
||||
this.logger = core.context.logger;
|
||||
}
|
||||
|
||||
get available(): boolean {
|
||||
return this.isConnected && this.websocket !== undefined;
|
||||
}
|
||||
|
||||
private randText(len: number) {
|
||||
let text = '';
|
||||
const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
for (let i = 0; i < len; i++) {
|
||||
text += possible.charAt(Math.floor(Math.random() * possible.length));
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
connect(cb: any): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
//this.logger.log.bind(this.logger)(`[Core] [Packet Server] Attempting to connect to ${this.clientUrl}`);
|
||||
this.websocket = new WebSocket(this.clientUrl);
|
||||
this.websocket.on('error', (err) => { }/*this.logger.logError.bind(this.logger)('[Core] [Packet Server] Error:', err.message)*/);
|
||||
|
||||
this.websocket.onopen = () => {
|
||||
this.isConnected = true;
|
||||
this.reconnectAttempts = 0;
|
||||
this.logger.log.bind(this.logger)(`[Core] [Packet Server] Connected to ${this.clientUrl}`);
|
||||
cb();
|
||||
resolve();
|
||||
};
|
||||
|
||||
this.websocket.onerror = (error) => {
|
||||
//this.logger.logError.bind(this.logger)(`WebSocket error: ${error}`);
|
||||
reject(new Error(`${error.message}`));
|
||||
};
|
||||
|
||||
this.websocket.onmessage = (event) => {
|
||||
// const message = JSON.parse(event.data.toString());
|
||||
// console.log("Received message:", message);
|
||||
this.handleMessage(event.data).then().catch();
|
||||
};
|
||||
|
||||
this.websocket.onclose = () => {
|
||||
this.isConnected = false;
|
||||
//this.logger.logWarn.bind(this.logger)(`[Core] [Packet Server] Disconnected from ${this.clientUrl}`);
|
||||
this.attemptReconnect(cb);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
private attemptReconnect(cb: any): void {
|
||||
try {
|
||||
if (this.reconnectAttempts < this.maxReconnectAttempts) {
|
||||
this.reconnectAttempts++;
|
||||
setTimeout(() => {
|
||||
this.connect(cb).catch((error) => {
|
||||
this.logger.logError.bind(this.logger)(`[Core] [Packet Server] Reconnecting attempt failed,${error.message}`);
|
||||
});
|
||||
}, 5000 * this.reconnectAttempts);
|
||||
} else {
|
||||
this.logger.logError.bind(this.logger)(`[Core] [Packet Server] Max reconnect attempts reached. ${this.clientUrl}`);
|
||||
}
|
||||
} catch (error: any) {
|
||||
this.logger.logError.bind(this.logger)(`Error attempting to reconnect: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
private async registerCallback(trace_id: string, type: string, callback: (json: RecvPacketData) => Promise<void>): Promise<void> {
|
||||
this.cb.put(createHash('md5').update(trace_id).digest('hex') + type, callback);
|
||||
}
|
||||
|
||||
async init(pid: number, recv: string, send: string): Promise<void> {
|
||||
if (!this.isConnected || !this.websocket) {
|
||||
throw new Error("WebSocket is not connected");
|
||||
}
|
||||
const initMessage = {
|
||||
action: 'init',
|
||||
pid: pid,
|
||||
recv: recv,
|
||||
send: send
|
||||
};
|
||||
this.websocket.send(JSON.stringify(initMessage));
|
||||
}
|
||||
|
||||
private async sendCommand(cmd: string, data: string, trace_id: string, rsp: boolean = false, timeout: number = 20000, sendcb: (json: RecvPacketData) => void = () => {
|
||||
}): Promise<RecvPacketData> {
|
||||
return new Promise<RecvPacketData>((resolve, reject) => {
|
||||
if (!this.isConnected || !this.websocket) {
|
||||
throw new Error("WebSocket is not connected");
|
||||
}
|
||||
const commandMessage = {
|
||||
action: 'send',
|
||||
cmd: cmd,
|
||||
data: data,
|
||||
trace_id: trace_id
|
||||
};
|
||||
this.websocket.send(JSON.stringify(commandMessage));
|
||||
if (rsp) {
|
||||
this.registerCallback(trace_id, 'recv', async (json: RecvPacketData) => {
|
||||
clearTimeout(timeoutHandle);
|
||||
resolve(json);
|
||||
});
|
||||
}
|
||||
this.registerCallback(trace_id, 'send', async (json: RecvPacketData) => {
|
||||
sendcb(json);
|
||||
if (!rsp) {
|
||||
clearTimeout(timeoutHandle);
|
||||
resolve(json);
|
||||
}
|
||||
});
|
||||
const timeoutHandle = setTimeout(() => {
|
||||
reject(new Error(`sendCommand timed out after ${timeout} ms for ${cmd} with trace_id ${trace_id}`));
|
||||
}, timeout);
|
||||
});
|
||||
}
|
||||
|
||||
private async handleMessage(message: Data): Promise<void> {
|
||||
try {
|
||||
const json: RecvPacket = JSON.parse(message.toString());
|
||||
const trace_id_md5 = json.trace_id_md5;
|
||||
const action = json?.type ?? 'init';
|
||||
const event = this.cb.get(trace_id_md5 + action);
|
||||
if (event) {
|
||||
await event(json.data);
|
||||
}
|
||||
//console.log("Received message:", json);
|
||||
} catch (error) {
|
||||
this.logger.logError.bind(this.logger)(`Error parsing message: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
async sendPacket(cmd: string, data: PacketHexStr, rsp = false): Promise<RecvPacketData> {
|
||||
// wtfk tx
|
||||
// 校验失败和异常 可能返回undefined
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!this.available) {
|
||||
this.logger.logError('NapCat.Packet is not init');
|
||||
return undefined;
|
||||
}
|
||||
const md5 = crypto.createHash('md5').update(data).digest('hex');
|
||||
const trace_id = (this.randText(4) + md5 + data).slice(0, data.length / 2);
|
||||
this.sendCommand(cmd, data, trace_id, rsp, 20000, async () => {
|
||||
// await sleep(10);
|
||||
await this.napCatCore.context.session.getMsgService().sendSsoCmdReqByContend(cmd, trace_id);
|
||||
}).then((res) => resolve(res)).catch((e: Error) => reject(e));
|
||||
});
|
||||
}
|
||||
}
|
72
src/core/packet/highway/client.ts
Normal file
72
src/core/packet/highway/client.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
import * as stream from 'node:stream';
|
||||
import {ReadStream} from "node:fs";
|
||||
import {PacketHighwaySig} from "@/core/packet/highway/session";
|
||||
import {HighwayHttpUploader, HighwayTcpUploader} from "@/core/packet/highway/uploader";
|
||||
import {LogWrapper} from "@/common/log";
|
||||
|
||||
export interface PacketHighwayTrans {
|
||||
uin: string;
|
||||
cmd: number;
|
||||
command: string;
|
||||
data: stream.Readable;
|
||||
sum: Uint8Array;
|
||||
size: number;
|
||||
ticket: Uint8Array;
|
||||
loginSig?: Uint8Array;
|
||||
ext: Uint8Array;
|
||||
encrypt: boolean;
|
||||
timeout?: number;
|
||||
server: string;
|
||||
port: number;
|
||||
}
|
||||
|
||||
export class PacketHighwayClient {
|
||||
sig: PacketHighwaySig;
|
||||
server: string = 'htdata3.qq.com';
|
||||
port: number = 80;
|
||||
logger: LogWrapper;
|
||||
|
||||
constructor(sig: PacketHighwaySig, logger: LogWrapper, server: string = 'htdata3.qq.com', port: number = 80) {
|
||||
this.sig = sig;
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
changeServer(server: string, port: number) {
|
||||
this.server = server;
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
private buildDataUpTrans(cmd: number, data: ReadStream, fileSize: number, md5: Uint8Array, extendInfo: Uint8Array, timeout: number = 3600): PacketHighwayTrans {
|
||||
return {
|
||||
uin: this.sig.uin,
|
||||
cmd: cmd,
|
||||
command: 'PicUp.DataUp',
|
||||
data: data,
|
||||
sum: md5,
|
||||
size: fileSize,
|
||||
ticket: this.sig.sigSession!,
|
||||
ext: extendInfo,
|
||||
encrypt: false,
|
||||
timeout: timeout,
|
||||
server: this.server,
|
||||
port: this.port,
|
||||
} as PacketHighwayTrans;
|
||||
}
|
||||
|
||||
async upload(cmd: number, data: ReadStream, fileSize: number, md5: Uint8Array, extendInfo: Uint8Array): Promise<void> {
|
||||
const trans = this.buildDataUpTrans(cmd, data, fileSize, md5, extendInfo);
|
||||
try {
|
||||
const tcpUploader = new HighwayTcpUploader(trans, this.logger);
|
||||
await tcpUploader.upload();
|
||||
} catch (e) {
|
||||
this.logger.logError(`[Highway] upload failed: ${e}, fallback to http upload`);
|
||||
try {
|
||||
const httpUploader = new HighwayHttpUploader(trans, this.logger);
|
||||
await httpUploader.upload();
|
||||
} catch (e) {
|
||||
this.logger.logError(`[Highway] http upload failed: ${e}`);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
23
src/core/packet/highway/frame.ts
Normal file
23
src/core/packet/highway/frame.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import assert from "node:assert";
|
||||
|
||||
export class Frame{
|
||||
static pack(head: Buffer, body: Buffer): Buffer {
|
||||
const totalLength = 9 + head.length + body.length + 1;
|
||||
const buffer = Buffer.allocUnsafe(totalLength);
|
||||
buffer[0] = 0x28;
|
||||
buffer.writeUInt32BE(head.length, 1);
|
||||
buffer.writeUInt32BE(body.length, 5);
|
||||
head.copy(buffer, 9);
|
||||
body.copy(buffer, 9 + head.length);
|
||||
buffer[totalLength - 1] = 0x29;
|
||||
return buffer;
|
||||
}
|
||||
|
||||
static unpack(frame: Buffer): [Buffer, Buffer] {
|
||||
assert(frame[0] === 0x28 && frame[frame.length - 1] === 0x29, 'Invalid frame!');
|
||||
const headLen = frame.readUInt32BE(1);
|
||||
const bodyLen = frame.readUInt32BE(5);
|
||||
// assert(frame.length === 9 + headLen + bodyLen + 1, `Frame ${frame.toString('hex')} length does not match head and body lengths!`);
|
||||
return [frame.subarray(9, 9 + headLen), frame.subarray(9 + headLen, 9 + headLen + bodyLen)];
|
||||
}
|
||||
}
|
171
src/core/packet/highway/session.ts
Normal file
171
src/core/packet/highway/session.ts
Normal file
@@ -0,0 +1,171 @@
|
||||
import * as fs from "node:fs";
|
||||
import {ChatType, Peer} from "@/core";
|
||||
import {LogWrapper} from "@/common/log";
|
||||
import {PacketClient} from "@/core/packet/client";
|
||||
import {PacketPacker} from "@/core/packet/packer";
|
||||
import {NapProtoMsg} from "@/core/packet/proto/NapProto";
|
||||
import {HttpConn0x6ff_501Response} from "@/core/packet/proto/action/action";
|
||||
import {PacketHighwayClient} from "@/core/packet/highway/client";
|
||||
import {NTV2RichMediaResp} from "@/core/packet/proto/oidb/common/Ntv2.RichMediaResp";
|
||||
import {OidbSvcTrpcTcpBaseRsp} from "@/core/packet/proto/oidb/OidbBase";
|
||||
import {PacketMsgPicElement} from "@/core/packet/msg/element";
|
||||
import {NTV2RichMediaHighwayExt} from "@/core/packet/proto/highway/highway";
|
||||
import {int32ip2str, oidbIpv4s2HighwayIpv4s} from "@/core/packet/highway/utils";
|
||||
|
||||
export const BlockSize = 1024 * 1024;
|
||||
|
||||
interface HighwayServerAddr {
|
||||
ip: string
|
||||
port: number
|
||||
}
|
||||
|
||||
export interface PacketHighwaySig {
|
||||
uin: string;
|
||||
sigSession: Uint8Array | null
|
||||
sessionKey: Uint8Array | null
|
||||
serverAddr: HighwayServerAddr[]
|
||||
}
|
||||
|
||||
export class PacketHighwaySession {
|
||||
protected packetClient: PacketClient;
|
||||
protected packetHighwayClient: PacketHighwayClient;
|
||||
protected sig: PacketHighwaySig;
|
||||
protected logger: LogWrapper;
|
||||
protected packer: PacketPacker;
|
||||
private cachedPrepareReq: Promise<void> | null = null;
|
||||
|
||||
constructor(logger: LogWrapper, client: PacketClient, packer: PacketPacker) {
|
||||
this.packetClient = client;
|
||||
this.logger = logger;
|
||||
this.sig = {
|
||||
uin: this.packetClient.napCatCore.selfInfo.uin,
|
||||
sigSession: null,
|
||||
sessionKey: null,
|
||||
serverAddr: [],
|
||||
}
|
||||
this.packer = packer;
|
||||
this.packetHighwayClient = new PacketHighwayClient(this.sig, this.logger);
|
||||
}
|
||||
|
||||
private async checkAvailable() {
|
||||
if (!this.packetClient.available) {
|
||||
this.logger.logError('[Highway] packetServer not available!');
|
||||
throw new Error('packetServer不可用,请参照文档 https://napneko.github.io/config/advanced 检查packetServer状态或进行配置');
|
||||
}
|
||||
if (this.sig.sigSession === null || this.sig.sessionKey === null) {
|
||||
this.logger.logWarn('[Highway] sigSession or sessionKey not available!');
|
||||
if (this.cachedPrepareReq === null) {
|
||||
this.cachedPrepareReq = this.prepareUpload().finally(() => {
|
||||
this.cachedPrepareReq = null;
|
||||
});
|
||||
}
|
||||
await this.cachedPrepareReq;
|
||||
}
|
||||
}
|
||||
|
||||
private async prepareUpload(): Promise<void> {
|
||||
const packet = this.packer.packHttp0x6ff_501();
|
||||
const req = await this.packetClient.sendPacket('HttpConn.0x6ff_501', packet, true);
|
||||
const rsp = new NapProtoMsg(HttpConn0x6ff_501Response).decode(
|
||||
Buffer.from(req.hex_data, 'hex')
|
||||
);
|
||||
this.sig.sigSession = rsp.httpConn.sigSession
|
||||
this.sig.sessionKey = rsp.httpConn.sessionKey
|
||||
for (const info of rsp.httpConn.serverInfos) {
|
||||
if (info.serviceType !== 1) continue;
|
||||
for (const addr of info.serverAddrs) {
|
||||
this.logger.logDebug(`[Highway PrepareUpload] server addr add: ${int32ip2str(addr.ip)}:${addr.port}`);
|
||||
this.sig.serverAddr.push({
|
||||
ip: int32ip2str(addr.ip),
|
||||
port: addr.port
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async uploadImage(peer: Peer, img: PacketMsgPicElement): Promise<void> {
|
||||
await this.checkAvailable();
|
||||
if (peer.chatType === ChatType.KCHATTYPEGROUP) {
|
||||
await this.uploadGroupImageReq(Number(peer.peerUid), img);
|
||||
} else if (peer.chatType === ChatType.KCHATTYPEC2C) {
|
||||
await this.uploadC2CImageReq(peer.peerUid, img);
|
||||
} else {
|
||||
throw new Error(`[Highway] unsupported chatType: ${peer.chatType}`);
|
||||
}
|
||||
}
|
||||
|
||||
private async uploadGroupImageReq(groupUin: number, img: PacketMsgPicElement): Promise<void> {
|
||||
const preReq = await this.packer.packUploadGroupImgReq(groupUin, img);
|
||||
const preRespRaw = await this.packetClient.sendPacket('OidbSvcTrpcTcp.0x11c4_100', preReq, true);
|
||||
const preResp = new NapProtoMsg(OidbSvcTrpcTcpBaseRsp).decode(
|
||||
Buffer.from(preRespRaw.hex_data, 'hex')
|
||||
);
|
||||
const preRespData = new NapProtoMsg(NTV2RichMediaResp).decode(preResp.body);
|
||||
const ukey = preRespData.upload.uKey;
|
||||
if (ukey && ukey != "") {
|
||||
this.logger.logDebug(`[Highway] get upload ukey: ${ukey}, need upload!`);
|
||||
const index = preRespData.upload.msgInfo.msgInfoBody[0].index;
|
||||
const sha1 = Buffer.from(index.info.fileSha1, 'hex');
|
||||
const md5 = Buffer.from(index.info.fileHash, 'hex');
|
||||
const extend = new NapProtoMsg(NTV2RichMediaHighwayExt).encode({
|
||||
fileUuid: index.fileUuid,
|
||||
uKey: ukey,
|
||||
network: {
|
||||
ipv4S: oidbIpv4s2HighwayIpv4s(preRespData.upload.ipv4S)
|
||||
},
|
||||
msgInfoBody: preRespData.upload.msgInfo.msgInfoBody,
|
||||
blockSize: BlockSize,
|
||||
hash: {
|
||||
fileSha1: [sha1]
|
||||
}
|
||||
})
|
||||
await this.packetHighwayClient.upload(
|
||||
1004,
|
||||
fs.createReadStream(img.path, {highWaterMark: BlockSize}),
|
||||
img.size,
|
||||
md5,
|
||||
extend
|
||||
);
|
||||
} else {
|
||||
this.logger.logDebug(`[Highway] get upload invalid ukey ${ukey}, don't need upload!`);
|
||||
}
|
||||
img.msgInfo = preRespData.upload.msgInfo;
|
||||
// img.groupPicExt = new NapProtoMsg(CustomFace).decode(preRespData.tcpUpload.compatQMsg)
|
||||
}
|
||||
|
||||
private async uploadC2CImageReq(peerUid: string, img: PacketMsgPicElement): Promise<void> {
|
||||
const preReq = await this.packer.packUploadC2CImgReq(peerUid, img);
|
||||
const preRespRaw = await this.packetClient.sendPacket('OidbSvcTrpcTcp.0x11c5_100', preReq, true);
|
||||
const preResp = new NapProtoMsg(OidbSvcTrpcTcpBaseRsp).decode(
|
||||
Buffer.from(preRespRaw.hex_data, 'hex')
|
||||
);
|
||||
const preRespData = new NapProtoMsg(NTV2RichMediaResp).decode(preResp.body);
|
||||
const ukey = preRespData.upload.uKey;
|
||||
if (ukey && ukey != "") {
|
||||
this.logger.logDebug(`[Highway] get upload ukey: ${ukey}, need upload!`);
|
||||
const index = preRespData.upload.msgInfo.msgInfoBody[0].index;
|
||||
const sha1 = Buffer.from(index.info.fileSha1, 'hex');
|
||||
const md5 = Buffer.from(index.info.fileHash, 'hex');
|
||||
const extend = new NapProtoMsg(NTV2RichMediaHighwayExt).encode({
|
||||
fileUuid: index.fileUuid,
|
||||
uKey: ukey,
|
||||
network: {
|
||||
ipv4S: oidbIpv4s2HighwayIpv4s(preRespData.upload.ipv4S)
|
||||
},
|
||||
msgInfoBody: preRespData.upload.msgInfo.msgInfoBody,
|
||||
blockSize: BlockSize,
|
||||
hash: {
|
||||
fileSha1: [sha1]
|
||||
}
|
||||
})
|
||||
await this.packetHighwayClient.upload(
|
||||
1003,
|
||||
fs.createReadStream(img.path, {highWaterMark: BlockSize}),
|
||||
img.size,
|
||||
md5,
|
||||
extend
|
||||
);
|
||||
}
|
||||
img.msgInfo = preRespData.upload.msgInfo;
|
||||
}
|
||||
}
|
196
src/core/packet/highway/uploader.ts
Normal file
196
src/core/packet/highway/uploader.ts
Normal file
@@ -0,0 +1,196 @@
|
||||
import * as net from "node:net";
|
||||
import * as crypto from "node:crypto";
|
||||
import * as http from "node:http";
|
||||
import * as stream from "node:stream";
|
||||
import {LogWrapper} from "@/common/log";
|
||||
import * as tea from "@/core/packet/utils/crypto/tea";
|
||||
import {NapProtoMsg} from "@/core/packet/proto/NapProto";
|
||||
import {ReqDataHighwayHead, RespDataHighwayHead} from "@/core/packet/proto/highway/highway";
|
||||
import {BlockSize} from "@/core/packet/highway/session";
|
||||
import {PacketHighwayTrans} from "@/core/packet/highway/client";
|
||||
import {Frame} from "@/core/packet/highway/frame";
|
||||
|
||||
abstract class HighwayUploader {
|
||||
readonly trans: PacketHighwayTrans;
|
||||
readonly logger: LogWrapper;
|
||||
|
||||
constructor(trans: PacketHighwayTrans, logger: LogWrapper) {
|
||||
this.trans = trans;
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
encryptTransExt(key: Uint8Array) {
|
||||
if (!this.trans.encrypt) return;
|
||||
this.trans.ext = tea.encrypt(Buffer.from(this.trans.ext), Buffer.from(key));
|
||||
}
|
||||
|
||||
buildPicUpHead(offset: number, bodyLength: number, bodyMd5: Uint8Array): Uint8Array {
|
||||
return new NapProtoMsg(ReqDataHighwayHead).encode({
|
||||
msgBaseHead: {
|
||||
version: 1,
|
||||
uin: this.trans.uin,
|
||||
command: "PicUp.DataUp",
|
||||
seq: 0,
|
||||
retryTimes: 0,
|
||||
appId: 1600001604,
|
||||
dataFlag: 16,
|
||||
commandId: this.trans.cmd,
|
||||
},
|
||||
msgSegHead: {
|
||||
serviceId: 0,
|
||||
filesize: BigInt(this.trans.size),
|
||||
dataOffset: BigInt(offset),
|
||||
dataLength: bodyLength,
|
||||
serviceTicket: this.trans.ticket,
|
||||
md5: bodyMd5,
|
||||
fileMd5: this.trans.sum,
|
||||
cacheAddr: 0,
|
||||
cachePort: 0,
|
||||
},
|
||||
bytesReqExtendInfo: this.trans.ext,
|
||||
timestamp: BigInt(0),
|
||||
msgLoginSigHead: {
|
||||
uint32LoginSigType: 8,
|
||||
appId: 1600001604,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
abstract upload(): Promise<void>;
|
||||
}
|
||||
|
||||
class HighwayTcpUploaderTransform extends stream.Transform {
|
||||
uploader: HighwayTcpUploader;
|
||||
offset: number;
|
||||
|
||||
constructor(uploader: HighwayTcpUploader) {
|
||||
super();
|
||||
this.uploader = uploader;
|
||||
this.offset = 0;
|
||||
}
|
||||
|
||||
_transform(data: Buffer, _: BufferEncoding, callback: stream.TransformCallback) {
|
||||
let chunkOffset = 0;
|
||||
while (chunkOffset < data.length) {
|
||||
const chunkSize = Math.min(BlockSize, data.length - chunkOffset);
|
||||
const chunk = data.subarray(chunkOffset, chunkOffset + chunkSize);
|
||||
const chunkMd5 = crypto.createHash('md5').update(chunk).digest();
|
||||
const head = this.uploader.buildPicUpHead(this.offset, chunk.length, chunkMd5);
|
||||
chunkOffset += chunk.length;
|
||||
this.offset += chunk.length;
|
||||
this.push(Frame.pack(Buffer.from(head), chunk));
|
||||
}
|
||||
callback(null);
|
||||
}
|
||||
}
|
||||
|
||||
export class HighwayTcpUploader extends HighwayUploader {
|
||||
async upload(): Promise<void> {
|
||||
const highwayTransForm = new HighwayTcpUploaderTransform(this);
|
||||
const upload = new Promise<void>((resolve, _) => {
|
||||
const socket = net.connect(this.trans.port, this.trans.server, () => {
|
||||
this.trans.data.pipe(highwayTransForm).pipe(socket, {end: false});
|
||||
})
|
||||
const handleRspHeader = (header: Buffer) => {
|
||||
const rsp = new NapProtoMsg(RespDataHighwayHead).decode(header);
|
||||
if (rsp.errorCode !== 0) {
|
||||
this.logger.logWarn(`[Highway] tcpUpload failed (code: ${rsp.errorCode})`);
|
||||
}
|
||||
const percent = ((Number(rsp.msgSegHead?.dataOffset) + Number(rsp.msgSegHead?.dataLength)) / Number(rsp.msgSegHead?.filesize)).toFixed(2);
|
||||
this.logger.logDebug(`[Highway] tcpUpload ${rsp.errorCode} | ${percent} | ${Buffer.from(header).toString('hex')}`);
|
||||
if (Number(rsp.msgSegHead?.dataOffset) + Number(rsp.msgSegHead?.dataLength) >= Number(rsp.msgSegHead?.filesize)) {
|
||||
this.logger.logDebug('[Highway] tcpUpload finished.');
|
||||
socket.end();
|
||||
resolve();
|
||||
}
|
||||
};
|
||||
socket.on('data', (chunk: Buffer) => {
|
||||
try {
|
||||
const [head, _] = Frame.unpack(chunk);
|
||||
handleRspHeader(head);
|
||||
} catch (e) {
|
||||
this.logger.logError(`[Highway] tcpUpload parse response error: ${e}`);
|
||||
}
|
||||
})
|
||||
socket.on('close', () => {
|
||||
this.logger.logDebug('[Highway] tcpUpload socket closed.');
|
||||
resolve();
|
||||
})
|
||||
socket.on('error', (err) => {
|
||||
this.logger.logError('[Highway] tcpUpload socket.on error:', err);
|
||||
})
|
||||
this.trans.data.on('error', (err) => {
|
||||
this.logger.logError('[Highway] tcpUpload readable error:', err);
|
||||
socket.end();
|
||||
})
|
||||
})
|
||||
const timeout = new Promise<void>((_, reject) => {
|
||||
setTimeout(() => {
|
||||
reject(new Error(`[Highway] tcpUpload timeout after ${this.trans.timeout}s`))
|
||||
}, (this.trans.timeout ?? Infinity) * 1000
|
||||
)
|
||||
})
|
||||
await Promise.race([upload, timeout]);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: timeout impl
|
||||
export class HighwayHttpUploader extends HighwayUploader {
|
||||
async upload(): Promise<void> {
|
||||
let offset = 0;
|
||||
for await (const chunk of this.trans.data) {
|
||||
let block = chunk as Buffer;
|
||||
try {
|
||||
await this.uploadBlock(block, offset);
|
||||
} catch (err) {
|
||||
this.logger.logError(`[Highway] httpUpload Error uploading block at offset ${offset}: ${err}`);
|
||||
throw err;
|
||||
}
|
||||
offset += block.length;
|
||||
}
|
||||
}
|
||||
|
||||
private async uploadBlock(block: Buffer, offset: number): Promise<void> {
|
||||
const chunkMD5 = crypto.createHash('md5').update(block).digest();
|
||||
const payload = this.buildPicUpHead(offset, block.length, chunkMD5);
|
||||
const frame = Frame.pack(Buffer.from(payload), block)
|
||||
const resp = await this.httpPostHighwayContent(frame, `http://${this.trans.server}:${this.trans.port}/cgi-bin/httpconn?htcmd=0x6FF0087&uin=${this.trans.uin}`);
|
||||
const [head, body] = Frame.unpack(resp);
|
||||
const headData = new NapProtoMsg(RespDataHighwayHead).decode(head);
|
||||
this.logger.logDebug(`[Highway] httpUploadBlock: ${headData.errorCode} | ${headData.msgSegHead?.retCode} | ${headData.bytesRspExtendInfo} | ${head.toString('hex')} | ${body.toString('hex')}`);
|
||||
if (headData.errorCode !== 0) {
|
||||
this.logger.logError(`[Highway] httpUploadBlock failed (code=${headData.errorCode})`);
|
||||
}
|
||||
}
|
||||
|
||||
private async httpPostHighwayContent(frame: Buffer, serverURL: string): Promise<Buffer> {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
const options: http.RequestOptions = {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Connection': 'keep-alive',
|
||||
'Accept-Encoding': 'identity',
|
||||
'User-Agent': 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2)',
|
||||
'Content-Length': frame.length.toString(),
|
||||
},
|
||||
};
|
||||
const req = http.request(serverURL, options, (res) => {
|
||||
let data = Buffer.alloc(0);
|
||||
res.on('data', (chunk) => {
|
||||
data = Buffer.concat([data, chunk]);
|
||||
});
|
||||
res.on('end', () => {
|
||||
resolve(data);
|
||||
});
|
||||
});
|
||||
req.write(frame);
|
||||
req.on('error', (error) => {
|
||||
reject(error);
|
||||
});
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
20
src/core/packet/highway/utils.ts
Normal file
20
src/core/packet/highway/utils.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import {NapProtoEncodeStructType} from "@/core/packet/proto/NapProto";
|
||||
import {IPv4} from "@/core/packet/proto/oidb/common/Ntv2.RichMediaResp";
|
||||
import {NTHighwayIPv4} from "@/core/packet/proto/highway/highway";
|
||||
|
||||
export const int32ip2str = (ip: number) => {
|
||||
ip = ip & 0xffffffff;
|
||||
return [ip & 0xff, (ip & 0xff00) >> 8, (ip & 0xff0000) >> 16, ((ip & 0xff000000) >> 24) & 0xff].join('.');
|
||||
}
|
||||
|
||||
export const oidbIpv4s2HighwayIpv4s = (ipv4s: NapProtoEncodeStructType<typeof IPv4>[]): NapProtoEncodeStructType<typeof NTHighwayIPv4>[] =>{
|
||||
return ipv4s.map((ip) => {
|
||||
return {
|
||||
domain: {
|
||||
isEnable: true,
|
||||
ip: int32ip2str(ip.outIP!),
|
||||
},
|
||||
port: ip.outPort!
|
||||
} as NapProtoEncodeStructType<typeof NTHighwayIPv4>
|
||||
})
|
||||
}
|
58
src/core/packet/msg/builder.ts
Normal file
58
src/core/packet/msg/builder.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import * as crypto from "crypto";
|
||||
import {PushMsgBody} from "@/core/packet/proto/message/message";
|
||||
import {NapProtoEncodeStructType} from "@/core/packet/proto/NapProto";
|
||||
import {LogWrapper} from "@/common/log";
|
||||
import {PacketMsg} from "@/core/packet/msg/message";
|
||||
|
||||
export class PacketMsgBuilder {
|
||||
private logger: LogWrapper;
|
||||
|
||||
constructor(logger: LogWrapper) {
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
buildFakeMsg(selfUid: string, element: PacketMsg[]): NapProtoEncodeStructType<typeof PushMsgBody>[] {
|
||||
return element.map((node): NapProtoEncodeStructType<typeof PushMsgBody> => {
|
||||
const avatar = `https://q.qlogo.cn/headimg_dl?dst_uin=${node.senderUin}&spec=640&img_type=jpg`;
|
||||
const msgElement = node.msg.flatMap(msg => msg.buildElement() ?? []);
|
||||
return {
|
||||
responseHead: {
|
||||
fromUid: "",
|
||||
fromUin: node.senderUin,
|
||||
toUid: node.groupId ? undefined : selfUid,
|
||||
forward: node.groupId ? undefined : {
|
||||
friendName: node.senderName,
|
||||
},
|
||||
grp: node.groupId ? {
|
||||
groupUin: node.groupId,
|
||||
memberName: node.senderName,
|
||||
unknown5: 2
|
||||
} : undefined,
|
||||
},
|
||||
contentHead: {
|
||||
type: node.groupId ? 82 : 9,
|
||||
subType: node.groupId ? undefined : 4,
|
||||
divSeq: node.groupId ? undefined : 4,
|
||||
msgId: crypto.randomBytes(4).readUInt32LE(0),
|
||||
sequence: crypto.randomBytes(4).readUInt32LE(0),
|
||||
timeStamp: Math.floor(Date.now() / 1000),
|
||||
field7: BigInt(1),
|
||||
field8: 0,
|
||||
field9: 0,
|
||||
forward: {
|
||||
field1: 0,
|
||||
field2: 0,
|
||||
field3: node.groupId ? 0 : 2,
|
||||
unknownBase64: avatar,
|
||||
avatar: avatar
|
||||
}
|
||||
},
|
||||
body: {
|
||||
richText: {
|
||||
elems: msgElement
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
131
src/core/packet/msg/converter.ts
Normal file
131
src/core/packet/msg/converter.ts
Normal file
@@ -0,0 +1,131 @@
|
||||
import {
|
||||
MessageElement,
|
||||
RawMessage,
|
||||
SendArkElement,
|
||||
SendFaceElement,
|
||||
SendFileElement,
|
||||
SendMarkdownElement,
|
||||
SendMarketFaceElement,
|
||||
SendPicElement,
|
||||
SendPttElement,
|
||||
SendReplyElement,
|
||||
SendStructLongMsgElement,
|
||||
SendTextElement,
|
||||
SendVideoElement
|
||||
} from "@/core";
|
||||
import {
|
||||
IPacketMsgElement,
|
||||
PacketMsgAtElement,
|
||||
PacketMsgFaceElement,
|
||||
PacketMsgFileElement,
|
||||
PacketMsgLightAppElement,
|
||||
PacketMsgMarkDownElement,
|
||||
PacketMsgMarkFaceElement,
|
||||
PacketMsgPicElement,
|
||||
PacketMsgPttElement,
|
||||
PacketMsgReplyElement,
|
||||
PacketMsgTextElement,
|
||||
PacketMsgVideoElement,
|
||||
PacketMultiMsgElement
|
||||
} from "@/core/packet/msg/element";
|
||||
import {PacketMsg, PacketSendMsgElement} from "@/core/packet/msg/message";
|
||||
import {LogWrapper} from "@/common/log";
|
||||
|
||||
type SendMessageElementMap = {
|
||||
textElement: SendTextElement,
|
||||
picElement: SendPicElement,
|
||||
replyElement: SendReplyElement,
|
||||
faceElement: SendFaceElement,
|
||||
marketFaceElement: SendMarketFaceElement,
|
||||
videoElement: SendVideoElement,
|
||||
fileElement: SendFileElement,
|
||||
pttElement: SendPttElement,
|
||||
arkElement: SendArkElement,
|
||||
markdownElement: SendMarkdownElement,
|
||||
structLongMsgElement: SendStructLongMsgElement
|
||||
};
|
||||
|
||||
type RawToPacketMsgConverters = {
|
||||
[K in keyof SendMessageElementMap]: (
|
||||
element: SendMessageElementMap[K],
|
||||
msg?: RawMessage,
|
||||
elementWrapper?: MessageElement,
|
||||
) => IPacketMsgElement<SendMessageElementMap[K]> | null;
|
||||
};
|
||||
|
||||
export type rawMsgWithSendMsg = {
|
||||
senderUin: number;
|
||||
senderUid?: string;
|
||||
senderName: string;
|
||||
groupId?: number;
|
||||
time: number;
|
||||
msg: PacketSendMsgElement[]
|
||||
}
|
||||
|
||||
export class PacketMsgConverter {
|
||||
private logger: LogWrapper;
|
||||
|
||||
constructor(logger: LogWrapper) {
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
rawMsgWithSendMsgToPacketMsg(msg: rawMsgWithSendMsg): PacketMsg {
|
||||
return {
|
||||
senderUid: msg.senderUid ?? '',
|
||||
senderUin: msg.senderUin,
|
||||
senderName: msg.senderName,
|
||||
groupId: msg.groupId,
|
||||
time: msg.time,
|
||||
msg: msg.msg.map((element) => {
|
||||
const key = (Object.keys(this.rawToPacketMsgConverters) as Array<keyof SendMessageElementMap>).find(
|
||||
(k) => (element as any)[k] !== undefined // TODO:
|
||||
);
|
||||
if (key) {
|
||||
const elementData = (element as any)[key]; // TODO:
|
||||
if (elementData) return this.rawToPacketMsgConverters[key](element as any)
|
||||
}
|
||||
return null;
|
||||
}).filter((e) => e !== null)
|
||||
}
|
||||
}
|
||||
|
||||
private rawToPacketMsgConverters: RawToPacketMsgConverters = {
|
||||
textElement: (element: SendTextElement) => {
|
||||
if (element.textElement.atType) {
|
||||
return new PacketMsgAtElement(element)
|
||||
}
|
||||
return new PacketMsgTextElement(element)
|
||||
},
|
||||
picElement: (element: SendPicElement) => {
|
||||
return new PacketMsgPicElement(element)
|
||||
},
|
||||
replyElement: (element: SendReplyElement) => {
|
||||
return new PacketMsgReplyElement(element)
|
||||
},
|
||||
faceElement: (element: SendFaceElement) => {
|
||||
return new PacketMsgFaceElement(element)
|
||||
},
|
||||
marketFaceElement: (element: SendMarketFaceElement) => {
|
||||
return new PacketMsgMarkFaceElement(element)
|
||||
},
|
||||
videoElement: (element: SendVideoElement) => {
|
||||
return new PacketMsgVideoElement(element)
|
||||
},
|
||||
fileElement: (element: SendFileElement) => {
|
||||
return new PacketMsgFileElement(element)
|
||||
},
|
||||
pttElement: (element: SendPttElement) => {
|
||||
return new PacketMsgPttElement(element)
|
||||
},
|
||||
arkElement: (element: SendArkElement) => {
|
||||
return new PacketMsgLightAppElement(element)
|
||||
},
|
||||
markdownElement: (element: SendMarkdownElement) => {
|
||||
return new PacketMsgMarkDownElement(element)
|
||||
},
|
||||
// TODO: check this logic, move it in arkElement?
|
||||
structLongMsgElement: (element: SendStructLongMsgElement) => {
|
||||
return new PacketMultiMsgElement(element)
|
||||
}
|
||||
}
|
||||
}
|
423
src/core/packet/msg/element.ts
Normal file
423
src/core/packet/msg/element.ts
Normal file
@@ -0,0 +1,423 @@
|
||||
import assert from "node:assert";
|
||||
import * as zlib from "node:zlib";
|
||||
import * as crypto from "node:crypto";
|
||||
import {NapProtoEncodeStructType, NapProtoMsg} from "@/core/packet/proto/NapProto";
|
||||
import {
|
||||
CustomFace,
|
||||
Elem,
|
||||
MarkdownData,
|
||||
MentionExtra,
|
||||
NotOnlineImage,
|
||||
QBigFaceExtra,
|
||||
QSmallFaceExtra
|
||||
} from "@/core/packet/proto/message/element";
|
||||
import {
|
||||
AtType,
|
||||
PicType,
|
||||
SendArkElement,
|
||||
SendFaceElement,
|
||||
SendFileElement,
|
||||
SendMarkdownElement,
|
||||
SendMarketFaceElement,
|
||||
SendPicElement,
|
||||
SendPttElement,
|
||||
SendReplyElement,
|
||||
SendStructLongMsgElement,
|
||||
SendTextElement,
|
||||
SendVideoElement
|
||||
} from "@/core";
|
||||
import {MsgInfo} from "@/core/packet/proto/oidb/common/Ntv2.RichMediaReq";
|
||||
import {PacketMsg, PacketSendMsgElement} from "@/core/packet/msg/message";
|
||||
|
||||
// raw <-> packet
|
||||
// TODO: check ob11 -> raw impl!
|
||||
// TODO: parse to raw element
|
||||
// TODO: SendStructLongMsgElement
|
||||
export abstract class IPacketMsgElement<T extends PacketSendMsgElement> {
|
||||
protected constructor(rawElement: T) {
|
||||
}
|
||||
|
||||
buildContent(): Uint8Array | undefined {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
buildElement(): NapProtoEncodeStructType<typeof Elem>[] | undefined {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
toPreview(): string {
|
||||
return '[nya~]';
|
||||
}
|
||||
}
|
||||
|
||||
export class PacketMsgTextElement extends IPacketMsgElement<SendTextElement> {
|
||||
text: string;
|
||||
|
||||
constructor(element: SendTextElement) {
|
||||
super(element);
|
||||
this.text = element.textElement.content;
|
||||
}
|
||||
|
||||
buildElement(): NapProtoEncodeStructType<typeof Elem>[] {
|
||||
return [{
|
||||
text: {
|
||||
str: this.text
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
toPreview(): string {
|
||||
return this.text;
|
||||
}
|
||||
}
|
||||
|
||||
export class PacketMsgAtElement extends PacketMsgTextElement {
|
||||
targetUid: string;
|
||||
atAll: boolean;
|
||||
|
||||
constructor(element: SendTextElement) {
|
||||
super(element);
|
||||
this.targetUid = element.textElement.atNtUid;
|
||||
this.atAll = element.textElement.atType === AtType.atAll;
|
||||
}
|
||||
|
||||
buildElement(): NapProtoEncodeStructType<typeof Elem>[] {
|
||||
return [{
|
||||
text: {
|
||||
str: this.text,
|
||||
pbReserve: new NapProtoMsg(MentionExtra).encode({
|
||||
type: this.atAll ? 1 : 2,
|
||||
uin: 0,
|
||||
field5: 0,
|
||||
uid: this.targetUid,
|
||||
}
|
||||
)
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
toPreview(): string {
|
||||
return `@${this.targetUid} ${this.text}`;
|
||||
}
|
||||
}
|
||||
|
||||
export class PacketMsgPicElement extends IPacketMsgElement<SendPicElement> {
|
||||
path: string;
|
||||
name: string
|
||||
size: number;
|
||||
md5: string;
|
||||
width: number;
|
||||
height: number;
|
||||
picType: PicType;
|
||||
sha1: string | null = null;
|
||||
msgInfo: NapProtoEncodeStructType<typeof MsgInfo> | null = null;
|
||||
groupPicExt: NapProtoEncodeStructType<typeof CustomFace> | null = null;
|
||||
c2cPicExt: NapProtoEncodeStructType<typeof NotOnlineImage> | null = null;
|
||||
|
||||
constructor(element: SendPicElement) {
|
||||
super(element);
|
||||
this.path = element.picElement.sourcePath;
|
||||
this.name = element.picElement.fileName;
|
||||
this.size = Number(element.picElement.fileSize);
|
||||
this.md5 = element.picElement.md5HexStr ?? '';
|
||||
this.width = element.picElement.picWidth;
|
||||
this.height = element.picElement.picHeight;
|
||||
this.picType = element.picElement.picType;
|
||||
}
|
||||
|
||||
buildElement(): NapProtoEncodeStructType<typeof Elem>[] {
|
||||
assert(this.msgInfo !== null, 'msgInfo is null, expected not null');
|
||||
return [{
|
||||
commonElem: {
|
||||
serviceType: 48,
|
||||
pbElem: new NapProtoMsg(MsgInfo).encode(this.msgInfo),
|
||||
businessType: 10,
|
||||
}
|
||||
}]
|
||||
}
|
||||
|
||||
toPreview(): string {
|
||||
return "[图片]";
|
||||
}
|
||||
}
|
||||
|
||||
export class PacketMsgReplyElement extends IPacketMsgElement<SendReplyElement> {
|
||||
messageId: bigint;
|
||||
messageSeq: number;
|
||||
messageClientSeq: number;
|
||||
targetUin: number;
|
||||
targetUid: string;
|
||||
time: number;
|
||||
elems: PacketMsg[];
|
||||
|
||||
constructor(element: SendReplyElement) {
|
||||
super(element);
|
||||
this.messageId = BigInt(element.replyElement.replayMsgId ?? 0);
|
||||
this.messageSeq = Number(element.replyElement.replayMsgSeq ?? 0);
|
||||
this.messageClientSeq = Number(element.replyElement.replyMsgClientSeq ?? 0);
|
||||
this.targetUin = Number(element.replyElement.senderUin ?? 0);
|
||||
this.targetUid = element.replyElement.senderUidStr ?? '';
|
||||
this.time = Number(element.replyElement.replyMsgTime ?? 0);
|
||||
this.elems = []; // TODO: in replyElement.sourceMsgTextElems
|
||||
}
|
||||
|
||||
get isGroupReply(): boolean {
|
||||
return this.messageClientSeq !== 0;
|
||||
}
|
||||
|
||||
buildElement(): NapProtoEncodeStructType<typeof Elem>[] {
|
||||
return [{
|
||||
srcMsg: {
|
||||
origSeqs: [this.isGroupReply ? this.messageClientSeq : this.messageSeq],
|
||||
senderUin: BigInt(this.targetUin),
|
||||
time: this.time,
|
||||
elems: [], // TODO: in replyElement.sourceMsgTextElems
|
||||
pbReserve: {
|
||||
messageId: this.messageId,
|
||||
},
|
||||
toUin: BigInt(0),
|
||||
}
|
||||
}, {
|
||||
text: this.isGroupReply ? {
|
||||
str: 'nya~',
|
||||
pbReserve: new NapProtoMsg(MentionExtra).encode({
|
||||
type: this.targetUin === 0 ? 1 : 2,
|
||||
uin: 0,
|
||||
field5: 0,
|
||||
uid: String(this.targetUid),
|
||||
}),
|
||||
} : undefined,
|
||||
}]
|
||||
}
|
||||
|
||||
toPreview(): string {
|
||||
return "[回复]";
|
||||
}
|
||||
}
|
||||
|
||||
export class PacketMsgFaceElement extends IPacketMsgElement<SendFaceElement> {
|
||||
faceId: number;
|
||||
isLargeFace: boolean;
|
||||
|
||||
constructor(element: SendFaceElement) {
|
||||
super(element);
|
||||
this.faceId = element.faceElement.faceIndex;
|
||||
this.isLargeFace = element.faceElement.faceType === 3;
|
||||
}
|
||||
|
||||
buildElement(): NapProtoEncodeStructType<typeof Elem>[] {
|
||||
if (this.isLargeFace) {
|
||||
return [{
|
||||
commonElem: {
|
||||
serviceType: 37,
|
||||
pbElem: new NapProtoMsg(QBigFaceExtra).encode({
|
||||
aniStickerPackId: "1",
|
||||
aniStickerId: "8",
|
||||
faceId: this.faceId,
|
||||
field4: 1,
|
||||
field6: "",
|
||||
preview: "",
|
||||
field9: 1
|
||||
}),
|
||||
businessType: 1
|
||||
}
|
||||
}]
|
||||
} else if (this.faceId < 260) {
|
||||
return [{
|
||||
face: {
|
||||
index: this.faceId
|
||||
}
|
||||
}];
|
||||
} else {
|
||||
return [{
|
||||
commonElem: {
|
||||
serviceType: 33,
|
||||
pbElem: new NapProtoMsg(QSmallFaceExtra).encode({
|
||||
faceId: this.faceId,
|
||||
preview: "",
|
||||
preview2: ""
|
||||
}),
|
||||
businessType: 1
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
toPreview(): string {
|
||||
return "[表情]";
|
||||
}
|
||||
}
|
||||
|
||||
export class PacketMsgMarkFaceElement extends IPacketMsgElement<SendMarketFaceElement> {
|
||||
emojiName: string;
|
||||
emojiId: string;
|
||||
emojiPackageId: number;
|
||||
emojiKey: string;
|
||||
|
||||
constructor(element: SendMarketFaceElement) {
|
||||
super(element);
|
||||
this.emojiName = element.marketFaceElement.faceName;
|
||||
this.emojiId = element.marketFaceElement.emojiId;
|
||||
this.emojiPackageId = element.marketFaceElement.emojiPackageId;
|
||||
this.emojiKey = element.marketFaceElement.key;
|
||||
}
|
||||
|
||||
buildElement(): NapProtoEncodeStructType<typeof Elem>[] {
|
||||
return [{
|
||||
marketFace: {
|
||||
faceName: this.emojiName,
|
||||
itemType: 6,
|
||||
faceInfo: 1,
|
||||
faceId: Buffer.from(this.emojiId, 'hex'),
|
||||
tabId: this.emojiPackageId,
|
||||
subType: 3,
|
||||
key: this.emojiKey,
|
||||
imageWidth: 300,
|
||||
imageHeight: 300,
|
||||
pbReserve: {
|
||||
field8: 1
|
||||
}
|
||||
}
|
||||
}]
|
||||
}
|
||||
|
||||
toPreview(): string {
|
||||
return this.emojiName;
|
||||
}
|
||||
}
|
||||
|
||||
export class PacketMsgVideoElement extends IPacketMsgElement<SendVideoElement> {
|
||||
constructor(element: SendVideoElement) {
|
||||
super(element);
|
||||
}
|
||||
}
|
||||
|
||||
export class PacketMsgFileElement extends IPacketMsgElement<SendFileElement> {
|
||||
constructor(element: SendFileElement) {
|
||||
super(element);
|
||||
}
|
||||
}
|
||||
|
||||
export class PacketMsgPttElement extends IPacketMsgElement<SendPttElement> {
|
||||
constructor(element: SendPttElement) {
|
||||
super(element);
|
||||
}
|
||||
}
|
||||
|
||||
export class PacketMsgLightAppElement extends IPacketMsgElement<SendArkElement> {
|
||||
payload: string;
|
||||
|
||||
constructor(element: SendArkElement) {
|
||||
super(element);
|
||||
this.payload = element.arkElement.bytesData;
|
||||
}
|
||||
|
||||
buildElement(): NapProtoEncodeStructType<typeof Elem>[] {
|
||||
return [{
|
||||
lightAppElem: {
|
||||
data: Buffer.concat([
|
||||
Buffer.from([0x01]),
|
||||
zlib.deflateSync(Buffer.from(this.payload, 'utf-8'))
|
||||
])
|
||||
}
|
||||
}]
|
||||
}
|
||||
|
||||
toPreview(): string {
|
||||
return "[小程序]";
|
||||
}
|
||||
}
|
||||
|
||||
export class PacketMsgMarkDownElement extends IPacketMsgElement<SendMarkdownElement> {
|
||||
content: string;
|
||||
|
||||
constructor(element: SendMarkdownElement) {
|
||||
super(element);
|
||||
this.content = element.markdownElement.content;
|
||||
}
|
||||
|
||||
buildElement(): NapProtoEncodeStructType<typeof Elem>[] {
|
||||
return [{
|
||||
commonElem: {
|
||||
serviceType: 45,
|
||||
pbElem: new NapProtoMsg(MarkdownData).encode({
|
||||
content: this.content
|
||||
}),
|
||||
businessType: 1
|
||||
}
|
||||
}]
|
||||
}
|
||||
|
||||
toPreview(): string {
|
||||
return this.content;
|
||||
}
|
||||
}
|
||||
|
||||
export class PacketMultiMsgElement extends IPacketMsgElement<SendStructLongMsgElement> {
|
||||
resid: string;
|
||||
message: PacketMsg[];
|
||||
|
||||
constructor(rawElement: SendStructLongMsgElement, message?: PacketMsg[]) {
|
||||
super(rawElement);
|
||||
this.resid = rawElement.structLongMsgElement.resId;
|
||||
this.message = message ?? [];
|
||||
}
|
||||
|
||||
get isGroupMsg(): boolean {
|
||||
return this.message.some(msg => msg.groupId !== undefined);
|
||||
}
|
||||
|
||||
get JSON() {
|
||||
const id = crypto.randomUUID();
|
||||
return {
|
||||
app: "com.tencent.multimsg",
|
||||
config: {
|
||||
autosize: 1,
|
||||
forward: 1,
|
||||
round: 1,
|
||||
type: "normal",
|
||||
width: 300
|
||||
},
|
||||
desc: "[聊天记录]",
|
||||
extra: {
|
||||
filename: id,
|
||||
tsum: this.message.length,
|
||||
},
|
||||
meta: {
|
||||
detail: {
|
||||
news: this.message.length === 0 ? [{
|
||||
text: "[Nya~ This message is send from NapCat.Packet!]",
|
||||
}] : this.message.map(packetMsg => ({
|
||||
text: `${packetMsg.senderName}: ${packetMsg.msg.map(msg => msg.toPreview()).join('')}`,
|
||||
})),
|
||||
resid: this.resid,
|
||||
source: this.isGroupMsg ? "群聊的聊天记录" :
|
||||
this.message.length
|
||||
? Array.from(new Set(this.message.map(msg => msg.senderName)))
|
||||
.join('和') + '的聊天记录'
|
||||
: '聊天记录',
|
||||
summary: `查看${this.message.length}条转发消息`,
|
||||
uniseq: id,
|
||||
}
|
||||
},
|
||||
prompt: "[聊天记录]",
|
||||
ver: "0.0.0.5",
|
||||
view: "contact",
|
||||
}
|
||||
}
|
||||
|
||||
buildElement(): NapProtoEncodeStructType<typeof Elem>[] {
|
||||
return [{
|
||||
lightAppElem: {
|
||||
data: Buffer.concat([
|
||||
Buffer.from([0x01]),
|
||||
zlib.deflateSync(Buffer.from(JSON.stringify(this.JSON), 'utf-8'))
|
||||
])
|
||||
}
|
||||
}]
|
||||
}
|
||||
|
||||
toPreview(): string {
|
||||
return "[聊天记录]";
|
||||
}
|
||||
}
|
15
src/core/packet/msg/message.ts
Normal file
15
src/core/packet/msg/message.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import {IPacketMsgElement} from "@/core/packet/msg/element";
|
||||
import {SendMessageElement, SendStructLongMsgElement} from "@/core";
|
||||
|
||||
export type PacketSendMsgElement = SendMessageElement | SendStructLongMsgElement
|
||||
|
||||
export interface PacketMsg {
|
||||
seq?: number;
|
||||
clientSeq?: number;
|
||||
groupId?: number;
|
||||
senderUid: string;
|
||||
senderUin: number;
|
||||
senderName: string;
|
||||
time: number;
|
||||
msg: IPacketMsgElement<PacketSendMsgElement>[]
|
||||
}
|
324
src/core/packet/packer.ts
Normal file
324
src/core/packet/packer.ts
Normal file
@@ -0,0 +1,324 @@
|
||||
import * as zlib from "node:zlib";
|
||||
import * as crypto from "node:crypto";
|
||||
import {calculateSha1} from "@/core/packet/utils/crypto/hash"
|
||||
import {NapProtoMsg} from "@/core/packet/proto/NapProto";
|
||||
import {OidbSvcTrpcTcpBase} from "@/core/packet/proto/oidb/OidbBase";
|
||||
import {OidbSvcTrpcTcp0X9067_202} from "@/core/packet/proto/oidb/Oidb.0x9067_202";
|
||||
import {OidbSvcTrpcTcp0X8FC_2, OidbSvcTrpcTcp0X8FC_2_Body} from "@/core/packet/proto/oidb/Oidb.0x8FC_2";
|
||||
import {OidbSvcTrpcTcp0XFE1_2} from "@/core/packet/proto/oidb/Oidb.0XFE1_2";
|
||||
import {OidbSvcTrpcTcp0XED3_1} from "@/core/packet/proto/oidb/Oidb.0xED3_1";
|
||||
import {NTV2RichMediaReq} from "@/core/packet/proto/oidb/common/Ntv2.RichMediaReq";
|
||||
import {HttpConn0x6ff_501} from "@/core/packet/proto/action/action";
|
||||
import {LongMsgResult, SendLongMsgReq} from "@/core/packet/proto/message/action";
|
||||
import {PacketMsgBuilder} from "@/core/packet/msg/builder";
|
||||
import {PacketMsgPicElement} from "@/core/packet/msg/element";
|
||||
import {LogWrapper} from "@/common/log";
|
||||
import {PacketMsg} from "@/core/packet/msg/message";
|
||||
import {OidbSvcTrpcTcp0x6D6} from "@/core/packet/proto/oidb/Oidb.0x6D6";
|
||||
import {OidbSvcTrpcTcp0XE37_1200} from "@/core/packet/proto/oidb/Oidb.0xE37_1200";
|
||||
import {PacketMsgConverter} from "@/core/packet/msg/converter";
|
||||
import {PacketClient} from "@/core/packet/client";
|
||||
|
||||
export type PacketHexStr = string & { readonly hexNya: unique symbol };
|
||||
|
||||
export class PacketPacker {
|
||||
readonly logger: LogWrapper;
|
||||
readonly client: PacketClient;
|
||||
readonly packetBuilder: PacketMsgBuilder;
|
||||
readonly packetConverter: PacketMsgConverter;
|
||||
|
||||
constructor(logger: LogWrapper, client: PacketClient) {
|
||||
this.logger = logger;
|
||||
this.client = client;
|
||||
this.packetBuilder = new PacketMsgBuilder(logger);
|
||||
this.packetConverter = new PacketMsgConverter(logger);
|
||||
}
|
||||
|
||||
private toHexStr(byteArray: Uint8Array): PacketHexStr {
|
||||
return Buffer.from(byteArray).toString('hex') as PacketHexStr;
|
||||
}
|
||||
|
||||
packOidbPacket(cmd: number, subCmd: number, body: Uint8Array, isUid: boolean = true, isLafter: boolean = false): Uint8Array {
|
||||
return new NapProtoMsg(OidbSvcTrpcTcpBase).encode({
|
||||
command: cmd,
|
||||
subCommand: subCmd,
|
||||
body: body,
|
||||
isReserved: isUid ? 1 : 0
|
||||
});
|
||||
}
|
||||
|
||||
packPokePacket(peer: number, group?: number): PacketHexStr {
|
||||
const oidb_0xed3 = new NapProtoMsg(OidbSvcTrpcTcp0XED3_1).encode({
|
||||
uin: peer,
|
||||
groupUin: group,
|
||||
friendUin: group ?? peer,
|
||||
ext: 0
|
||||
});
|
||||
return this.toHexStr(this.packOidbPacket(0xed3, 1, oidb_0xed3));
|
||||
}
|
||||
|
||||
packRkeyPacket(): PacketHexStr {
|
||||
const oidb_0x9067_202 = new NapProtoMsg(OidbSvcTrpcTcp0X9067_202).encode({
|
||||
reqHead: {
|
||||
common: {
|
||||
requestId: 1,
|
||||
command: 202
|
||||
},
|
||||
scene: {
|
||||
requestType: 2,
|
||||
businessType: 1,
|
||||
sceneType: 0
|
||||
},
|
||||
client: {
|
||||
agentType: 2
|
||||
}
|
||||
},
|
||||
downloadRKeyReq: {
|
||||
key: [10, 20, 2]
|
||||
},
|
||||
});
|
||||
return this.toHexStr(this.packOidbPacket(0x9067, 202, oidb_0x9067_202));
|
||||
}
|
||||
|
||||
packSetSpecialTittlePacket(groupCode: string, uid: string, tittle: string): PacketHexStr {
|
||||
const oidb_0x8FC_2_body = new NapProtoMsg(OidbSvcTrpcTcp0X8FC_2_Body).encode({
|
||||
targetUid: uid,
|
||||
specialTitle: tittle,
|
||||
expiredTime: -1,
|
||||
uinName: tittle
|
||||
});
|
||||
const oidb_0x8FC_2 = new NapProtoMsg(OidbSvcTrpcTcp0X8FC_2).encode({
|
||||
groupUin: +groupCode,
|
||||
body: oidb_0x8FC_2_body
|
||||
});
|
||||
return this.toHexStr(this.packOidbPacket(0x8FC, 2, oidb_0x8FC_2, false, false));
|
||||
}
|
||||
|
||||
packStatusPacket(uin: number): PacketHexStr {
|
||||
const oidb_0xfe1_2 = new NapProtoMsg(OidbSvcTrpcTcp0XFE1_2).encode({
|
||||
uin: uin,
|
||||
key: [{key: 27372}]
|
||||
});
|
||||
return this.toHexStr(this.packOidbPacket(0xfe1, 2, oidb_0xfe1_2));
|
||||
}
|
||||
|
||||
async packUploadForwardMsg(selfUid: string, msg: PacketMsg[], groupUin: number = 0): Promise<PacketHexStr> {
|
||||
const msgBody = this.packetBuilder.buildFakeMsg(selfUid, msg);
|
||||
const longMsgResultData = new NapProtoMsg(LongMsgResult).encode(
|
||||
{
|
||||
action: {
|
||||
actionCommand: "MultiMsg",
|
||||
actionData: {
|
||||
msgBody: msgBody
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
const payload = zlib.gzipSync(Buffer.from(longMsgResultData));
|
||||
const req = new NapProtoMsg(SendLongMsgReq).encode(
|
||||
{
|
||||
info: {
|
||||
type: groupUin === 0 ? 1 : 3,
|
||||
uid: {
|
||||
uid: groupUin === 0 ? selfUid : groupUin.toString(),
|
||||
},
|
||||
groupUin: groupUin,
|
||||
payload: payload
|
||||
},
|
||||
settings: {
|
||||
field1: 4, field2: 1, field3: 7, field4: 0
|
||||
}
|
||||
}
|
||||
);
|
||||
// this.logger.logDebug("packUploadForwardMsg REQ!!!", req);
|
||||
return this.toHexStr(req);
|
||||
}
|
||||
|
||||
// highway part
|
||||
packHttp0x6ff_501(): PacketHexStr {
|
||||
return this.toHexStr(new NapProtoMsg(HttpConn0x6ff_501).encode({
|
||||
httpConn: {
|
||||
field1: 0,
|
||||
field2: 0,
|
||||
field3: 16,
|
||||
field4: 1,
|
||||
field6: 3,
|
||||
serviceTypes: [1, 5, 10, 21],
|
||||
// tgt: "", // TODO: do we really need tgt? seems not
|
||||
field9: 2,
|
||||
field10: 9,
|
||||
field11: 8,
|
||||
ver: "1.0.1"
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
async packUploadGroupImgReq(groupUin: number, img: PacketMsgPicElement): Promise<PacketHexStr> {
|
||||
const req = new NapProtoMsg(NTV2RichMediaReq).encode(
|
||||
{
|
||||
reqHead: {
|
||||
common: {
|
||||
requestId: 1,
|
||||
command: 100
|
||||
},
|
||||
scene: {
|
||||
requestType: 2,
|
||||
businessType: 1,
|
||||
sceneType: 2,
|
||||
group: {
|
||||
groupUin: groupUin
|
||||
},
|
||||
},
|
||||
client: {
|
||||
agentType: 2
|
||||
}
|
||||
},
|
||||
upload: {
|
||||
uploadInfo: [
|
||||
{
|
||||
fileInfo: {
|
||||
fileSize: Number(img.size),
|
||||
fileHash: img.md5,
|
||||
fileSha1: this.toHexStr(await calculateSha1(img.path)),
|
||||
fileName: img.name,
|
||||
type: {
|
||||
type: 1,
|
||||
picFormat: img.picType, //TODO: extend NapCat imgType /cc @MliKiowa
|
||||
videoFormat: 0,
|
||||
voiceFormat: 0,
|
||||
},
|
||||
width: img.width,
|
||||
height: img.height,
|
||||
time: 0,
|
||||
original: 1
|
||||
},
|
||||
subFileType: 0,
|
||||
}
|
||||
],
|
||||
tryFastUploadCompleted: true,
|
||||
srvSendMsg: false,
|
||||
clientRandomId: crypto.randomBytes(8).readBigUInt64BE() & BigInt('0x7FFFFFFFFFFFFFFF'),
|
||||
compatQMsgSceneType: 2,
|
||||
extBizInfo: {
|
||||
pic: {
|
||||
bytesPbReserveTroop: Buffer.from("0800180020004200500062009201009a0100a2010c080012001800200028003a00", 'hex'),
|
||||
textSummary: "Nya~", // TODO:
|
||||
},
|
||||
video: {
|
||||
bytesPbReserve: Buffer.alloc(0),
|
||||
},
|
||||
ptt: {
|
||||
bytesPbReserve: Buffer.alloc(0),
|
||||
bytesReserve: Buffer.alloc(0),
|
||||
bytesGeneralFlags: Buffer.alloc(0),
|
||||
}
|
||||
},
|
||||
clientSeq: 0,
|
||||
noNeedCompatMsg: false,
|
||||
}
|
||||
}
|
||||
)
|
||||
return this.toHexStr(this.packOidbPacket(0x11c4, 100, req, true, false));
|
||||
}
|
||||
|
||||
async packUploadC2CImgReq(peerUin: string, img: PacketMsgPicElement): Promise<PacketHexStr> {
|
||||
const req = new NapProtoMsg(NTV2RichMediaReq).encode({
|
||||
reqHead: {
|
||||
common: {
|
||||
requestId: 1,
|
||||
command: 100
|
||||
},
|
||||
scene: {
|
||||
requestType: 2,
|
||||
businessType: 1,
|
||||
sceneType: 1,
|
||||
c2C: {
|
||||
accountType: 2,
|
||||
targetUid: peerUin
|
||||
},
|
||||
},
|
||||
client: {
|
||||
agentType: 2,
|
||||
}
|
||||
},
|
||||
upload: {
|
||||
uploadInfo: [
|
||||
{
|
||||
fileInfo: {
|
||||
fileSize: Number(img.size),
|
||||
fileHash: img.md5,
|
||||
fileSha1: this.toHexStr(await calculateSha1(img.path)),
|
||||
fileName: img.name,
|
||||
type: {
|
||||
type: 1,
|
||||
picFormat: img.picType, //TODO: extend NapCat imgType /cc @MliKiowa
|
||||
videoFormat: 0,
|
||||
voiceFormat: 0,
|
||||
},
|
||||
width: img.width,
|
||||
height: img.height,
|
||||
time: 0,
|
||||
original: 1
|
||||
},
|
||||
subFileType: 0,
|
||||
}
|
||||
],
|
||||
tryFastUploadCompleted: true,
|
||||
srvSendMsg: false,
|
||||
clientRandomId: crypto.randomBytes(8).readBigUInt64BE() & BigInt('0x7FFFFFFFFFFFFFFF'),
|
||||
compatQMsgSceneType: 1,
|
||||
extBizInfo: {
|
||||
pic: {
|
||||
bytesPbReserveTroop: Buffer.from("0800180020004200500062009201009a0100a2010c080012001800200028003a00", 'hex'),
|
||||
textSummary: "Nya~", // TODO:
|
||||
},
|
||||
video: {
|
||||
bytesPbReserve: Buffer.alloc(0),
|
||||
},
|
||||
ptt: {
|
||||
bytesPbReserve: Buffer.alloc(0),
|
||||
bytesReserve: Buffer.alloc(0),
|
||||
bytesGeneralFlags: Buffer.alloc(0),
|
||||
}
|
||||
},
|
||||
clientSeq: 0,
|
||||
noNeedCompatMsg: false,
|
||||
}
|
||||
}
|
||||
)
|
||||
return this.toHexStr(this.packOidbPacket(0x11c5, 100, req, true, false));
|
||||
}
|
||||
|
||||
packGroupFileDownloadReq(groupUin: number, fileUUID: string): PacketHexStr {
|
||||
return this.toHexStr(
|
||||
this.packOidbPacket(0x6D6, 2, new NapProtoMsg(OidbSvcTrpcTcp0x6D6).encode({
|
||||
download: {
|
||||
groupUin: groupUin,
|
||||
appId: 7,
|
||||
busId: 102,
|
||||
fileId: fileUUID
|
||||
}
|
||||
}), true, false)
|
||||
)
|
||||
}
|
||||
|
||||
packC2CFileDownloadReq(selfUid: string, fileUUID: string, fileHash: string): PacketHexStr {
|
||||
return this.toHexStr(
|
||||
new NapProtoMsg(OidbSvcTrpcTcp0XE37_1200).encode({
|
||||
subCommand: 1200,
|
||||
field2: 1,
|
||||
body: {
|
||||
receiverUid: selfUid,
|
||||
fileUuid: fileUUID,
|
||||
type: 2,
|
||||
fileHash: fileHash,
|
||||
t2: 0
|
||||
},
|
||||
field101: 3,
|
||||
field102: 103,
|
||||
field200: 1,
|
||||
field99999: Buffer.from([0xc0, 0x85, 0x2c, 0x01])
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
139
src/core/packet/proto/NapProto.ts
Normal file
139
src/core/packet/proto/NapProto.ts
Normal file
@@ -0,0 +1,139 @@
|
||||
import { MessageType, PartialMessage, RepeatType, ScalarType } from '@protobuf-ts/runtime';
|
||||
import { PartialFieldInfo } from "@protobuf-ts/runtime/build/types/reflection-info";
|
||||
|
||||
type LowerCamelCase<S extends string> = CamelCaseHelper<S, false, true>;
|
||||
|
||||
type CamelCaseHelper<
|
||||
S extends string,
|
||||
CapNext extends boolean,
|
||||
IsFirstChar extends boolean
|
||||
> = S extends `${infer F}${infer R}`
|
||||
? F extends '_'
|
||||
? CamelCaseHelper<R, true, false>
|
||||
: F extends `${number}`
|
||||
? `${F}${CamelCaseHelper<R, true, false>}`
|
||||
: CapNext extends true
|
||||
? `${Uppercase<F>}${CamelCaseHelper<R, false, false>}`
|
||||
: IsFirstChar extends true
|
||||
? `${Lowercase<F>}${CamelCaseHelper<R, false, false>}`
|
||||
: `${F}${CamelCaseHelper<R, false, false>}`
|
||||
: '';
|
||||
|
||||
type ScalarTypeToTsType<T extends ScalarType> =
|
||||
T extends ScalarType.DOUBLE | ScalarType.FLOAT | ScalarType.INT32 | ScalarType.FIXED32 | ScalarType.UINT32 | ScalarType.SFIXED32 | ScalarType.SINT32 ? number :
|
||||
T extends ScalarType.INT64 | ScalarType.UINT64 | ScalarType.FIXED64 | ScalarType.SFIXED64 | ScalarType.SINT64 ? bigint :
|
||||
T extends ScalarType.BOOL ? boolean :
|
||||
T extends ScalarType.STRING ? string :
|
||||
T extends ScalarType.BYTES ? Uint8Array :
|
||||
never;
|
||||
|
||||
interface BaseProtoFieldType<T, O extends boolean, R extends O extends true ? false : boolean> {
|
||||
kind: 'scalar' | 'message';
|
||||
no: number;
|
||||
type: T;
|
||||
optional: O;
|
||||
repeat: R;
|
||||
}
|
||||
|
||||
interface ScalarProtoFieldType<T extends ScalarType, O extends boolean, R extends O extends true ? false : boolean> extends BaseProtoFieldType<T, O, R> {
|
||||
kind: 'scalar';
|
||||
}
|
||||
|
||||
interface MessageProtoFieldType<T extends () => ProtoMessageType, O extends boolean, R extends O extends true ? false : boolean> extends BaseProtoFieldType<T, O, R> {
|
||||
kind: 'message';
|
||||
}
|
||||
|
||||
type ProtoFieldType =
|
||||
| ScalarProtoFieldType<ScalarType, boolean, boolean>
|
||||
| MessageProtoFieldType<() => ProtoMessageType, boolean, boolean>;
|
||||
|
||||
type ProtoMessageType = {
|
||||
[key: string]: ProtoFieldType;
|
||||
};
|
||||
|
||||
export function ProtoField<T extends ScalarType, O extends boolean = false, R extends O extends true ? false : boolean = false>(no: number, type: T, optional?: O, repeat?: R): ScalarProtoFieldType<T, O, R>;
|
||||
export function ProtoField<T extends () => ProtoMessageType, O extends boolean = false, R extends O extends true ? false : boolean = false>(no: number, type: T, optional?: O, repeat?: R): MessageProtoFieldType<T, O, R>;
|
||||
export function ProtoField(no: number, type: ScalarType | (() => ProtoMessageType), optional?: boolean, repeat?: boolean): ProtoFieldType {
|
||||
if (typeof type === 'function') {
|
||||
return { kind: 'message', no: no, type: type, optional: optional ?? false, repeat: repeat ?? false };
|
||||
} else {
|
||||
return { kind: 'scalar', no: no, type: type, optional: optional ?? false, repeat: repeat ?? false };
|
||||
}
|
||||
}
|
||||
|
||||
type ProtoFieldReturnType<T extends unknown, E extends boolean> = NonNullable<T> extends ScalarProtoFieldType<infer S, infer O, infer R>
|
||||
? ScalarTypeToTsType<S>
|
||||
: T extends NonNullable<MessageProtoFieldType<infer S, infer O, infer R>>
|
||||
? NonNullable<NapProtoStructType<ReturnType<S>, E>>
|
||||
: never;
|
||||
|
||||
type RequiredFieldsBaseType<T extends unknown, E extends boolean> = {
|
||||
[K in keyof T as T[K] extends { optional: true } ? never : LowerCamelCase<K & string>]:
|
||||
T[K] extends { repeat: true }
|
||||
? ProtoFieldReturnType<T[K], E>[]
|
||||
: ProtoFieldReturnType<T[K], E>
|
||||
}
|
||||
|
||||
type OptionalFieldsBaseType<T extends unknown, E extends boolean> = {
|
||||
[K in keyof T as T[K] extends { optional: true } ? LowerCamelCase<K & string> : never]?:
|
||||
T[K] extends { repeat: true }
|
||||
? ProtoFieldReturnType<T[K], E>[]
|
||||
: ProtoFieldReturnType<T[K], E>
|
||||
}
|
||||
|
||||
type RequiredFieldsType<T extends unknown, E extends boolean> = E extends true ? Partial<RequiredFieldsBaseType<T, E>> : RequiredFieldsBaseType<T, E>;
|
||||
|
||||
type OptionalFieldsType<T extends unknown, E extends boolean> = E extends true ? Partial<OptionalFieldsBaseType<T, E>> : OptionalFieldsBaseType<T, E>;
|
||||
|
||||
type NapProtoStructType<T extends unknown, E extends boolean> = RequiredFieldsType<T, E> & OptionalFieldsType<T, E>;
|
||||
|
||||
export type NapProtoEncodeStructType<T extends unknown> = NapProtoStructType<T, true>;
|
||||
|
||||
export type NapProtoDecodeStructType<T extends unknown> = NapProtoStructType<T, false>;
|
||||
|
||||
const NapProtoMsgCache = new Map<ProtoMessageType, MessageType<NapProtoStructType<ProtoMessageType, boolean>>>();
|
||||
|
||||
export class NapProtoMsg<T extends ProtoMessageType> {
|
||||
private readonly _msg: T;
|
||||
private readonly _field: PartialFieldInfo[];
|
||||
private readonly _proto_msg: MessageType<NapProtoStructType<T, boolean>>;
|
||||
|
||||
constructor(fields: T) {
|
||||
this._msg = fields;
|
||||
this._field = Object.keys(fields).map(key => {
|
||||
const field = fields[key];
|
||||
if (field.kind === 'scalar') {
|
||||
const repeatType = field.repeat
|
||||
? [ScalarType.STRING, ScalarType.BYTES].includes(field.type)
|
||||
? RepeatType.UNPACKED
|
||||
: RepeatType.PACKED
|
||||
: RepeatType.NO;
|
||||
return {
|
||||
no: field.no,
|
||||
name: key,
|
||||
kind: 'scalar',
|
||||
T: field.type,
|
||||
opt: field.optional,
|
||||
repeat: repeatType,
|
||||
};
|
||||
} else if (field.kind === 'message') {
|
||||
return {
|
||||
no: field.no,
|
||||
name: key,
|
||||
kind: 'message',
|
||||
repeat: field.repeat ? RepeatType.PACKED : RepeatType.NO,
|
||||
T: () => new NapProtoMsg(field.type())._proto_msg,
|
||||
};
|
||||
}
|
||||
}) as PartialFieldInfo[];
|
||||
this._proto_msg = new MessageType<NapProtoStructType<T, boolean>>('nya', this._field);
|
||||
}
|
||||
|
||||
encode(data: NapProtoEncodeStructType<T>): Uint8Array {
|
||||
return this._proto_msg.toBinary(this._proto_msg.create(data as PartialMessage<NapProtoEncodeStructType<T>>));
|
||||
}
|
||||
|
||||
decode(data: Uint8Array): NapProtoDecodeStructType<T> {
|
||||
return this._proto_msg.fromBinary(data) as NapProtoDecodeStructType<T>;
|
||||
}
|
||||
}
|
114
src/core/packet/proto/action/action.ts
Normal file
114
src/core/packet/proto/action/action.ts
Normal file
@@ -0,0 +1,114 @@
|
||||
import { ScalarType } from "@protobuf-ts/runtime";
|
||||
import { ProtoField } from "../NapProto";
|
||||
import {ContentHead, MessageBody, MessageControl, RoutingHead} from "@/core/packet/proto/message/message";
|
||||
|
||||
export const FaceRoamRequest = {
|
||||
comm: ProtoField(1, () => PlatInfo, true),
|
||||
selfUin: ProtoField(2, ScalarType.UINT32),
|
||||
subCmd: ProtoField(3, ScalarType.UINT32),
|
||||
field6: ProtoField(6, ScalarType.UINT32),
|
||||
};
|
||||
|
||||
export const PlatInfo = {
|
||||
imPlat: ProtoField(1, ScalarType.UINT32),
|
||||
osVersion: ProtoField(2, ScalarType.STRING, true),
|
||||
qVersion: ProtoField(3, ScalarType.STRING, true),
|
||||
};
|
||||
|
||||
export const FaceRoamResponse = {
|
||||
retCode: ProtoField(1, ScalarType.UINT32),
|
||||
errMsg: ProtoField(2, ScalarType.STRING),
|
||||
subCmd: ProtoField(3, ScalarType.UINT32),
|
||||
userInfo: ProtoField(6, () => FaceRoamUserInfo),
|
||||
};
|
||||
|
||||
export const FaceRoamUserInfo = {
|
||||
fileName: ProtoField(1, ScalarType.STRING, false, true),
|
||||
deleteFile: ProtoField(2, ScalarType.STRING, false, true),
|
||||
bid: ProtoField(3, ScalarType.STRING),
|
||||
maxRoamSize: ProtoField(4, ScalarType.UINT32),
|
||||
emojiType: ProtoField(5, ScalarType.UINT32, false, true),
|
||||
};
|
||||
|
||||
export const SendMessageRequest = {
|
||||
state: ProtoField(1, ScalarType.INT32),
|
||||
sizeCache: ProtoField(2, ScalarType.INT32),
|
||||
unknownFields: ProtoField(3, ScalarType.BYTES),
|
||||
routingHead: ProtoField(4, () => RoutingHead),
|
||||
contentHead: ProtoField(5, () => ContentHead),
|
||||
messageBody: ProtoField(6, () => MessageBody),
|
||||
msgSeq: ProtoField(7, ScalarType.INT32),
|
||||
msgRand: ProtoField(8, ScalarType.INT32),
|
||||
syncCookie: ProtoField(9, ScalarType.BYTES),
|
||||
msgVia: ProtoField(10, ScalarType.INT32),
|
||||
dataStatist: ProtoField(11, ScalarType.INT32),
|
||||
messageControl: ProtoField(12, () => MessageControl),
|
||||
multiSendSeq: ProtoField(13, ScalarType.INT32),
|
||||
};
|
||||
|
||||
export const SendMessageResponse = {
|
||||
result: ProtoField(1, ScalarType.INT32),
|
||||
errMsg: ProtoField(2, ScalarType.STRING, true),
|
||||
timestamp1: ProtoField(3, ScalarType.UINT32),
|
||||
field10: ProtoField(10, ScalarType.UINT32),
|
||||
groupSequence: ProtoField(11, ScalarType.UINT32, true),
|
||||
timestamp2: ProtoField(12, ScalarType.UINT32),
|
||||
privateSequence: ProtoField(14, ScalarType.UINT32),
|
||||
};
|
||||
|
||||
export const SetStatus = {
|
||||
status: ProtoField(1, ScalarType.UINT32),
|
||||
extStatus: ProtoField(2, ScalarType.UINT32),
|
||||
batteryStatus: ProtoField(3, ScalarType.UINT32),
|
||||
customExt: ProtoField(4, () => SetStatusCustomExt, true),
|
||||
};
|
||||
|
||||
export const SetStatusCustomExt = {
|
||||
faceId: ProtoField(1, ScalarType.UINT32),
|
||||
text: ProtoField(2, ScalarType.STRING, true),
|
||||
field3: ProtoField(3, ScalarType.UINT32),
|
||||
};
|
||||
|
||||
export const SetStatusResponse = {
|
||||
message: ProtoField(2, ScalarType.STRING),
|
||||
};
|
||||
|
||||
export const HttpConn = {
|
||||
field1: ProtoField(1, ScalarType.INT32),
|
||||
field2: ProtoField(2, ScalarType.INT32),
|
||||
field3: ProtoField(3, ScalarType.INT32),
|
||||
field4: ProtoField(4, ScalarType.INT32),
|
||||
tgt: ProtoField(5, ScalarType.STRING),
|
||||
field6: ProtoField(6, ScalarType.INT32),
|
||||
serviceTypes: ProtoField(7, ScalarType.INT32, false, true),
|
||||
field9: ProtoField(9, ScalarType.INT32),
|
||||
field10: ProtoField(10, ScalarType.INT32),
|
||||
field11: ProtoField(11, ScalarType.INT32),
|
||||
ver: ProtoField(15, ScalarType.STRING),
|
||||
};
|
||||
|
||||
export const HttpConn0x6ff_501 = {
|
||||
httpConn: ProtoField(0x501, () => HttpConn),
|
||||
};
|
||||
|
||||
export const HttpConn0x6ff_501Response = {
|
||||
httpConn: ProtoField(0x501, () => HttpConnResponse),
|
||||
};
|
||||
|
||||
export const HttpConnResponse = {
|
||||
sigSession: ProtoField(1, ScalarType.BYTES),
|
||||
sessionKey: ProtoField(2, ScalarType.BYTES),
|
||||
serverInfos: ProtoField(3, () => ServerInfo, false, true),
|
||||
};
|
||||
|
||||
export const ServerAddr = {
|
||||
type: ProtoField(1, ScalarType.UINT32),
|
||||
ip: ProtoField(2, ScalarType.FIXED32),
|
||||
port: ProtoField(3, ScalarType.UINT32),
|
||||
area: ProtoField(4, ScalarType.UINT32),
|
||||
};
|
||||
|
||||
export const ServerInfo = {
|
||||
serviceType: ProtoField(1, ScalarType.UINT32),
|
||||
serverAddrs: ProtoField(2, () => ServerAddr, false, true),
|
||||
};
|
155
src/core/packet/proto/highway/highway.ts
Normal file
155
src/core/packet/proto/highway/highway.ts
Normal file
@@ -0,0 +1,155 @@
|
||||
import {ScalarType} from "@protobuf-ts/runtime";
|
||||
import {ProtoField} from "../NapProto";
|
||||
import {MsgInfo, MsgInfoBody} from "@/core/packet/proto/oidb/common/Ntv2.RichMediaReq";
|
||||
|
||||
export const DataHighwayHead = {
|
||||
version: ProtoField(1, ScalarType.UINT32),
|
||||
uin: ProtoField(2, ScalarType.STRING, true),
|
||||
command: ProtoField(3, ScalarType.STRING, true),
|
||||
seq: ProtoField(4, ScalarType.UINT32, true),
|
||||
retryTimes: ProtoField(5, ScalarType.UINT32, true),
|
||||
appId: ProtoField(6, ScalarType.UINT32),
|
||||
dataFlag: ProtoField(7, ScalarType.UINT32),
|
||||
commandId: ProtoField(8, ScalarType.UINT32),
|
||||
buildVer: ProtoField(9, ScalarType.BYTES, true),
|
||||
}
|
||||
|
||||
export const FileUploadExt = {
|
||||
unknown1: ProtoField(1, ScalarType.INT32),
|
||||
unknown2: ProtoField(2, ScalarType.INT32),
|
||||
unknown3: ProtoField(3, ScalarType.INT32),
|
||||
entry: ProtoField(100, () => FileUploadEntry),
|
||||
unknown200: ProtoField(200, ScalarType.INT32),
|
||||
}
|
||||
|
||||
export const FileUploadEntry = {
|
||||
busiBuff: ProtoField(100, () => ExcitingBusiInfo),
|
||||
fileEntry: ProtoField(200, () => ExcitingFileEntry),
|
||||
clientInfo: ProtoField(300, () => ExcitingClientInfo),
|
||||
fileNameInfo: ProtoField(400, () => ExcitingFileNameInfo),
|
||||
host: ProtoField(500, () => ExcitingHostConfig),
|
||||
}
|
||||
|
||||
export const ExcitingBusiInfo = {
|
||||
busId: ProtoField(1, ScalarType.INT32),
|
||||
senderUin: ProtoField(100, ScalarType.UINT64),
|
||||
receiverUin: ProtoField(200, ScalarType.UINT64),
|
||||
groupCode: ProtoField(400, ScalarType.UINT64),
|
||||
}
|
||||
|
||||
export const ExcitingFileEntry = {
|
||||
fileSize: ProtoField(100, ScalarType.UINT64),
|
||||
md5: ProtoField(200, ScalarType.BYTES),
|
||||
checkKey: ProtoField(300, ScalarType.BYTES),
|
||||
md5S2: ProtoField(400, ScalarType.BYTES),
|
||||
fileId: ProtoField(600, ScalarType.STRING),
|
||||
uploadKey: ProtoField(700, ScalarType.BYTES),
|
||||
}
|
||||
|
||||
export const ExcitingClientInfo = {
|
||||
clientType: ProtoField(100, ScalarType.INT32),
|
||||
appId: ProtoField(200, ScalarType.STRING),
|
||||
terminalType: ProtoField(300, ScalarType.INT32),
|
||||
clientVer: ProtoField(400, ScalarType.STRING),
|
||||
unknown: ProtoField(600, ScalarType.INT32),
|
||||
}
|
||||
|
||||
export const ExcitingFileNameInfo = {
|
||||
fileName: ProtoField(100, ScalarType.STRING),
|
||||
}
|
||||
|
||||
export const ExcitingHostConfig = {
|
||||
hosts: ProtoField(200, () => ExcitingHostInfo, false, true),
|
||||
}
|
||||
|
||||
export const ExcitingHostInfo = {
|
||||
url: ProtoField(1, () => ExcitingUrlInfo),
|
||||
port: ProtoField(2, ScalarType.UINT32),
|
||||
}
|
||||
|
||||
export const ExcitingUrlInfo = {
|
||||
unknown: ProtoField(1, ScalarType.INT32),
|
||||
host: ProtoField(2, ScalarType.STRING),
|
||||
}
|
||||
|
||||
export const LoginSigHead = {
|
||||
uint32LoginSigType: ProtoField(1, ScalarType.UINT32),
|
||||
bytesLoginSig: ProtoField(2, ScalarType.BYTES),
|
||||
appId: ProtoField(3, ScalarType.UINT32),
|
||||
}
|
||||
|
||||
export const NTV2RichMediaHighwayExt = {
|
||||
fileUuid: ProtoField(1, ScalarType.STRING),
|
||||
uKey: ProtoField(2, ScalarType.STRING),
|
||||
network: ProtoField(5, () => NTHighwayNetwork),
|
||||
msgInfoBody: ProtoField(6, () => MsgInfoBody, false, true),
|
||||
blockSize: ProtoField(10, ScalarType.UINT32),
|
||||
hash: ProtoField(11, () => NTHighwayHash),
|
||||
}
|
||||
|
||||
export const NTHighwayHash = {
|
||||
fileSha1: ProtoField(1, ScalarType.BYTES, false, true),
|
||||
}
|
||||
|
||||
export const NTHighwayNetwork = {
|
||||
ipv4s: ProtoField(1, () => NTHighwayIPv4, false, true),
|
||||
}
|
||||
|
||||
export const NTHighwayIPv4 = {
|
||||
domain: ProtoField(1, () => NTHighwayDomain),
|
||||
port: ProtoField(2, ScalarType.UINT32),
|
||||
}
|
||||
|
||||
export const NTHighwayDomain = {
|
||||
isEnable: ProtoField(1, ScalarType.BOOL),
|
||||
ip: ProtoField(2, ScalarType.STRING),
|
||||
}
|
||||
|
||||
export const ReqDataHighwayHead = {
|
||||
msgBaseHead: ProtoField(1, () => DataHighwayHead, true),
|
||||
msgSegHead: ProtoField(2, () => SegHead, true),
|
||||
bytesReqExtendInfo: ProtoField(3, ScalarType.BYTES, true),
|
||||
timestamp: ProtoField(4, ScalarType.UINT64),
|
||||
msgLoginSigHead: ProtoField(5, () => LoginSigHead, true),
|
||||
}
|
||||
|
||||
export const RespDataHighwayHead = {
|
||||
msgBaseHead: ProtoField(1, () => DataHighwayHead, true),
|
||||
msgSegHead: ProtoField(2, () => SegHead, true),
|
||||
errorCode: ProtoField(3, ScalarType.UINT32),
|
||||
allowRetry: ProtoField(4, ScalarType.UINT32),
|
||||
cacheCost: ProtoField(5, ScalarType.UINT32),
|
||||
htCost: ProtoField(6, ScalarType.UINT32),
|
||||
bytesRspExtendInfo: ProtoField(7, ScalarType.BYTES, true),
|
||||
timestamp: ProtoField(8, ScalarType.UINT64),
|
||||
range: ProtoField(9, ScalarType.UINT64),
|
||||
isReset: ProtoField(10, ScalarType.UINT32),
|
||||
}
|
||||
|
||||
export const SegHead = {
|
||||
serviceId: ProtoField(1, ScalarType.UINT32, true),
|
||||
filesize: ProtoField(2, ScalarType.UINT64),
|
||||
dataOffset: ProtoField(3, ScalarType.UINT64, true),
|
||||
dataLength: ProtoField(4, ScalarType.UINT32),
|
||||
retCode: ProtoField(5, ScalarType.UINT32, true),
|
||||
serviceTicket: ProtoField(6, ScalarType.BYTES),
|
||||
flag: ProtoField(7, ScalarType.UINT32, true),
|
||||
md5: ProtoField(8, ScalarType.BYTES),
|
||||
fileMd5: ProtoField(9, ScalarType.BYTES),
|
||||
cacheAddr: ProtoField(10, ScalarType.UINT32, true),
|
||||
queryTimes: ProtoField(11, ScalarType.UINT32),
|
||||
updateCacheIp: ProtoField(12, ScalarType.UINT32),
|
||||
cachePort: ProtoField(13, ScalarType.UINT32, true),
|
||||
}
|
||||
|
||||
export const GroupAvatarExtra = {
|
||||
type: ProtoField(1, ScalarType.UINT32),
|
||||
groupUin: ProtoField(2, ScalarType.UINT32),
|
||||
field3: ProtoField(3, () => GroupAvatarExtraField3),
|
||||
field5: ProtoField(5, ScalarType.UINT32),
|
||||
field6: ProtoField(6, ScalarType.UINT32),
|
||||
}
|
||||
|
||||
export const GroupAvatarExtraField3 = {
|
||||
field1: ProtoField(1, ScalarType.UINT32),
|
||||
}
|
117
src/core/packet/proto/message/action.ts
Normal file
117
src/core/packet/proto/message/action.ts
Normal file
@@ -0,0 +1,117 @@
|
||||
import { ScalarType } from "@protobuf-ts/runtime";
|
||||
import { ProtoField } from "../NapProto";
|
||||
import { PushMsgBody } from "@/core/packet/proto/message/message";
|
||||
|
||||
export const LongMsgResult = {
|
||||
action: ProtoField(2, () => LongMsgAction)
|
||||
};
|
||||
|
||||
export const LongMsgAction = {
|
||||
actionCommand: ProtoField(1, ScalarType.STRING),
|
||||
actionData: ProtoField(2, () => LongMsgContent)
|
||||
};
|
||||
|
||||
export const LongMsgContent = {
|
||||
msgBody: ProtoField(1, () => PushMsgBody, false, true)
|
||||
};
|
||||
|
||||
export const RecvLongMsgReq = {
|
||||
info: ProtoField(1, () => RecvLongMsgInfo, true),
|
||||
settings: ProtoField(15, () => LongMsgSettings, true)
|
||||
};
|
||||
|
||||
export const RecvLongMsgInfo = {
|
||||
uid: ProtoField(1, () => LongMsgUid, true),
|
||||
resId: ProtoField(2, ScalarType.STRING, true),
|
||||
acquire: ProtoField(3, ScalarType.BOOL)
|
||||
};
|
||||
|
||||
export const LongMsgUid = {
|
||||
uid: ProtoField(2, ScalarType.STRING, true)
|
||||
};
|
||||
|
||||
export const LongMsgSettings = {
|
||||
field1: ProtoField(1, ScalarType.UINT32),
|
||||
field2: ProtoField(2, ScalarType.UINT32),
|
||||
field3: ProtoField(3, ScalarType.UINT32),
|
||||
field4: ProtoField(4, ScalarType.UINT32)
|
||||
};
|
||||
|
||||
export const RecvLongMsgResp = {
|
||||
result: ProtoField(1, () => RecvLongMsgResult),
|
||||
settings: ProtoField(15, () => LongMsgSettings)
|
||||
};
|
||||
|
||||
export const RecvLongMsgResult = {
|
||||
resId: ProtoField(3, ScalarType.STRING),
|
||||
payload: ProtoField(4, ScalarType.BYTES)
|
||||
};
|
||||
|
||||
export const SendLongMsgReq = {
|
||||
info: ProtoField(2, () => SendLongMsgInfo),
|
||||
settings: ProtoField(15, () => LongMsgSettings)
|
||||
};
|
||||
|
||||
export const SendLongMsgInfo = {
|
||||
type: ProtoField(1, ScalarType.UINT32),
|
||||
uid: ProtoField(2, () => LongMsgUid, true),
|
||||
groupUin: ProtoField(3, ScalarType.UINT32, true),
|
||||
payload: ProtoField(4, ScalarType.BYTES, true)
|
||||
};
|
||||
|
||||
export const SendLongMsgResp = {
|
||||
result: ProtoField(2, () => SendLongMsgResult),
|
||||
settings: ProtoField(15, () => LongMsgSettings)
|
||||
};
|
||||
|
||||
export const SendLongMsgResult = {
|
||||
resId: ProtoField(3, ScalarType.STRING)
|
||||
};
|
||||
|
||||
export const SsoGetGroupMsg = {
|
||||
info: ProtoField(1, () => SsoGetGroupMsgInfo),
|
||||
direction: ProtoField(2, ScalarType.BOOL)
|
||||
};
|
||||
|
||||
export const SsoGetGroupMsgInfo = {
|
||||
groupUin: ProtoField(1, ScalarType.UINT32),
|
||||
startSequence: ProtoField(2, ScalarType.UINT32),
|
||||
endSequence: ProtoField(3, ScalarType.UINT32)
|
||||
};
|
||||
|
||||
export const SsoGetGroupMsgResponse = {
|
||||
body: ProtoField(3, () => SsoGetGroupMsgResponseBody)
|
||||
};
|
||||
|
||||
export const SsoGetGroupMsgResponseBody = {
|
||||
groupUin: ProtoField(3, ScalarType.UINT32),
|
||||
startSequence: ProtoField(4, ScalarType.UINT32),
|
||||
endSequence: ProtoField(5, ScalarType.UINT32),
|
||||
messages: ProtoField(6, () => PushMsgBody, false, true)
|
||||
};
|
||||
|
||||
export const SsoGetRoamMsg = {
|
||||
friendUid: ProtoField(1, ScalarType.STRING, true),
|
||||
time: ProtoField(2, ScalarType.UINT32),
|
||||
random: ProtoField(3, ScalarType.UINT32),
|
||||
count: ProtoField(4, ScalarType.UINT32),
|
||||
direction: ProtoField(5, ScalarType.BOOL)
|
||||
};
|
||||
|
||||
export const SsoGetRoamMsgResponse = {
|
||||
friendUid: ProtoField(3, ScalarType.STRING),
|
||||
timestamp: ProtoField(5, ScalarType.UINT32),
|
||||
random: ProtoField(6, ScalarType.UINT32),
|
||||
messages: ProtoField(7, () => PushMsgBody, false, true)
|
||||
};
|
||||
|
||||
export const SsoGetC2cMsg = {
|
||||
friendUid: ProtoField(2, ScalarType.STRING, true),
|
||||
startSequence: ProtoField(3, ScalarType.UINT32),
|
||||
endSequence: ProtoField(4, ScalarType.UINT32)
|
||||
};
|
||||
|
||||
export const SsoGetC2cMsgResponse = {
|
||||
friendUid: ProtoField(4, ScalarType.STRING),
|
||||
messages: ProtoField(7, () => PushMsgBody, false, true)
|
||||
};
|
11
src/core/packet/proto/message/c2c.ts
Normal file
11
src/core/packet/proto/message/c2c.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { ScalarType } from "@protobuf-ts/runtime";
|
||||
import { ProtoField } from "../NapProto";
|
||||
|
||||
export const C2C = {
|
||||
uin: ProtoField(1, ScalarType.UINT32, true),
|
||||
uid: ProtoField(2, ScalarType.STRING, true),
|
||||
field3: ProtoField(3, ScalarType.UINT32, true),
|
||||
sig: ProtoField(4, ScalarType.UINT32, true),
|
||||
receiverUin: ProtoField(5, ScalarType.UINT32, true),
|
||||
receiverUid: ProtoField(6, ScalarType.STRING, true),
|
||||
};
|
147
src/core/packet/proto/message/component.ts
Normal file
147
src/core/packet/proto/message/component.ts
Normal file
@@ -0,0 +1,147 @@
|
||||
import { ScalarType } from "@protobuf-ts/runtime";
|
||||
import { ProtoField } from "../NapProto";
|
||||
import { Elem } from "@/core/packet/proto/message/element";
|
||||
|
||||
export const Attr = {
|
||||
codePage: ProtoField(1, ScalarType.INT32),
|
||||
time: ProtoField(2, ScalarType.INT32),
|
||||
random: ProtoField(3, ScalarType.INT32),
|
||||
color: ProtoField(4, ScalarType.INT32),
|
||||
size: ProtoField(5, ScalarType.INT32),
|
||||
effect: ProtoField(6, ScalarType.INT32),
|
||||
charSet: ProtoField(7, ScalarType.INT32),
|
||||
pitchAndFamily: ProtoField(8, ScalarType.INT32),
|
||||
fontName: ProtoField(9, ScalarType.STRING),
|
||||
reserveData: ProtoField(10, ScalarType.BYTES),
|
||||
};
|
||||
|
||||
export const NotOnlineFile = {
|
||||
fileType: ProtoField(1, ScalarType.INT32, true),
|
||||
sig: ProtoField(2, ScalarType.BYTES, true),
|
||||
fileUuid: ProtoField(3, ScalarType.STRING, true),
|
||||
fileMd5: ProtoField(4, ScalarType.BYTES, true),
|
||||
fileName: ProtoField(5, ScalarType.STRING, true),
|
||||
fileSize: ProtoField(6, ScalarType.INT64, true),
|
||||
note: ProtoField(7, ScalarType.BYTES, true),
|
||||
reserved: ProtoField(8, ScalarType.INT32, true),
|
||||
subcmd: ProtoField(9, ScalarType.INT32, true),
|
||||
microCloud: ProtoField(10, ScalarType.INT32, true),
|
||||
bytesFileUrls: ProtoField(11, ScalarType.BYTES, false, true),
|
||||
downloadFlag: ProtoField(12, ScalarType.INT32, true),
|
||||
dangerEvel: ProtoField(50, ScalarType.INT32, true),
|
||||
lifeTime: ProtoField(51, ScalarType.INT32, true),
|
||||
uploadTime: ProtoField(52, ScalarType.INT32, true),
|
||||
absFileType: ProtoField(53, ScalarType.INT32, true),
|
||||
clientType: ProtoField(54, ScalarType.INT32, true),
|
||||
expireTime: ProtoField(55, ScalarType.INT32, true),
|
||||
pbReserve: ProtoField(56, ScalarType.BYTES, true),
|
||||
fileHash: ProtoField(57, ScalarType.STRING, true),
|
||||
};
|
||||
|
||||
export const Ptt = {
|
||||
fileType: ProtoField(1, ScalarType.INT32),
|
||||
srcUin: ProtoField(2, ScalarType.UINT64),
|
||||
fileUuid: ProtoField(3, ScalarType.STRING),
|
||||
fileMd5: ProtoField(4, ScalarType.BYTES),
|
||||
fileName: ProtoField(5, ScalarType.STRING),
|
||||
fileSize: ProtoField(6, ScalarType.INT32),
|
||||
reserve: ProtoField(7, ScalarType.BYTES),
|
||||
fileId: ProtoField(8, ScalarType.INT32),
|
||||
serverIp: ProtoField(9, ScalarType.INT32),
|
||||
serverPort: ProtoField(10, ScalarType.INT32),
|
||||
boolValid: ProtoField(11, ScalarType.BOOL),
|
||||
signature: ProtoField(12, ScalarType.BYTES),
|
||||
shortcut: ProtoField(13, ScalarType.BYTES),
|
||||
fileKey: ProtoField(14, ScalarType.BYTES),
|
||||
magicPttIndex: ProtoField(15, ScalarType.INT32),
|
||||
voiceSwitch: ProtoField(16, ScalarType.INT32),
|
||||
pttUrl: ProtoField(17, ScalarType.BYTES),
|
||||
groupFileKey: ProtoField(18, ScalarType.STRING),
|
||||
time: ProtoField(19, ScalarType.INT32),
|
||||
downPara: ProtoField(20, ScalarType.BYTES),
|
||||
format: ProtoField(29, ScalarType.INT32),
|
||||
pbReserve: ProtoField(30, ScalarType.BYTES),
|
||||
bytesPttUrls: ProtoField(31, ScalarType.BYTES, false, true),
|
||||
downloadFlag: ProtoField(32, ScalarType.INT32),
|
||||
};
|
||||
|
||||
export const RichText = {
|
||||
attr: ProtoField(1, () => Attr, true),
|
||||
elems: ProtoField(2, () => Elem, false, true),
|
||||
notOnlineFile: ProtoField(3, () => NotOnlineFile, true),
|
||||
ptt: ProtoField(4, () => Ptt, true),
|
||||
};
|
||||
|
||||
export const ButtonExtra = {
|
||||
data: ProtoField(1, () => KeyboardData),
|
||||
};
|
||||
|
||||
export const KeyboardData = {
|
||||
rows: ProtoField(1, () => Row, false, true),
|
||||
};
|
||||
|
||||
export const Row = {
|
||||
buttons: ProtoField(1, () => Button, false, true),
|
||||
};
|
||||
|
||||
export const Button = {
|
||||
id: ProtoField(1, ScalarType.STRING),
|
||||
renderData: ProtoField(2, () => RenderData),
|
||||
action: ProtoField(3, () => Action),
|
||||
};
|
||||
|
||||
export const RenderData = {
|
||||
label: ProtoField(1, ScalarType.STRING),
|
||||
visitedLabel: ProtoField(2, ScalarType.STRING),
|
||||
style: ProtoField(3, ScalarType.INT32),
|
||||
};
|
||||
|
||||
export const Action = {
|
||||
type: ProtoField(1, ScalarType.INT32),
|
||||
permission: ProtoField(2, () => Permission),
|
||||
unsupportTips: ProtoField(4, ScalarType.STRING),
|
||||
data: ProtoField(5, ScalarType.STRING),
|
||||
reply: ProtoField(7, ScalarType.BOOL),
|
||||
enter: ProtoField(8, ScalarType.BOOL),
|
||||
};
|
||||
|
||||
export const Permission = {
|
||||
type: ProtoField(1, ScalarType.INT32),
|
||||
specifyRoleIds: ProtoField(2, ScalarType.STRING, false, true),
|
||||
specifyUserIds: ProtoField(3, ScalarType.STRING, false, true),
|
||||
};
|
||||
|
||||
export const FileExtra = {
|
||||
file: ProtoField(1, () => NotOnlineFile),
|
||||
};
|
||||
|
||||
export const GroupFileExtra = {
|
||||
field1: ProtoField(1, ScalarType.UINT32),
|
||||
fileName: ProtoField(2, ScalarType.STRING),
|
||||
display: ProtoField(3, ScalarType.STRING),
|
||||
inner: ProtoField(7, () => GroupFileExtraInner),
|
||||
};
|
||||
|
||||
export const GroupFileExtraInner = {
|
||||
info: ProtoField(2, () => GroupFileExtraInfo),
|
||||
};
|
||||
|
||||
export const GroupFileExtraInfo = {
|
||||
busId: ProtoField(1, ScalarType.UINT32),
|
||||
fileId: ProtoField(2, ScalarType.STRING),
|
||||
fileSize: ProtoField(3, ScalarType.UINT64),
|
||||
fileName: ProtoField(4, ScalarType.STRING),
|
||||
field5: ProtoField(5, ScalarType.UINT32),
|
||||
field7: ProtoField(7, ScalarType.STRING),
|
||||
fileMd5: ProtoField(8, ScalarType.STRING),
|
||||
};
|
||||
|
||||
export const ImageExtraUrl = {
|
||||
origUrl: ProtoField(30, ScalarType.STRING),
|
||||
};
|
||||
|
||||
export const PokeExtra = {
|
||||
type: ProtoField(1, ScalarType.UINT32),
|
||||
field7: ProtoField(7, ScalarType.UINT32),
|
||||
field8: ProtoField(8, ScalarType.UINT32),
|
||||
};
|
361
src/core/packet/proto/message/element.ts
Normal file
361
src/core/packet/proto/message/element.ts
Normal file
@@ -0,0 +1,361 @@
|
||||
import {ScalarType} from "@protobuf-ts/runtime";
|
||||
import {ProtoField} from "../NapProto";
|
||||
|
||||
export const Elem = {
|
||||
text: ProtoField(1, () => Text, true),
|
||||
face: ProtoField(2, () => Face, true),
|
||||
onlineImage: ProtoField(3, () => OnlineImage, true),
|
||||
notOnlineImage: ProtoField(4, () => NotOnlineImage, true),
|
||||
transElem: ProtoField(5, () => TransElem, true),
|
||||
marketFace: ProtoField(6, () => MarketFace, true),
|
||||
customFace: ProtoField(8, () => CustomFace, true),
|
||||
elemFlags2: ProtoField(9, () => ElemFlags2, true),
|
||||
richMsg: ProtoField(12, () => RichMsg, true),
|
||||
groupFile: ProtoField(13, () => GroupFile, true),
|
||||
extraInfo: ProtoField(16, () => ExtraInfo, true),
|
||||
videoFile: ProtoField(19, () => VideoFile, true),
|
||||
anonymousGroupMessage: ProtoField(21, () => AnonymousGroupMessage, true),
|
||||
customElem: ProtoField(31, () => CustomElem, true),
|
||||
generalFlags: ProtoField(37, () => GeneralFlags, true),
|
||||
srcMsg: ProtoField(45, () => SrcMsg, true),
|
||||
lightAppElem: ProtoField(51, () => LightAppElem, true),
|
||||
commonElem: ProtoField(53, () => CommonElem, true),
|
||||
};
|
||||
|
||||
export const Text = {
|
||||
str: ProtoField(1, ScalarType.STRING, true),
|
||||
lint: ProtoField(2, ScalarType.STRING, true),
|
||||
attr6Buf: ProtoField(3, ScalarType.BYTES, true),
|
||||
attr7Buf: ProtoField(4, ScalarType.BYTES, true),
|
||||
buf: ProtoField(11, ScalarType.BYTES, true),
|
||||
pbReserve: ProtoField(12, ScalarType.BYTES, true),
|
||||
};
|
||||
|
||||
export const Face = {
|
||||
index: ProtoField(1, ScalarType.INT32, true),
|
||||
old: ProtoField(2, ScalarType.BYTES, true),
|
||||
buf: ProtoField(11, ScalarType.BYTES, true),
|
||||
};
|
||||
|
||||
export const OnlineImage = {
|
||||
guid: ProtoField(1, ScalarType.BYTES),
|
||||
filePath: ProtoField(2, ScalarType.BYTES),
|
||||
oldVerSendFile: ProtoField(3, ScalarType.BYTES),
|
||||
};
|
||||
|
||||
export const NotOnlineImage = {
|
||||
filePath: ProtoField(1, ScalarType.STRING),
|
||||
fileLen: ProtoField(2, ScalarType.UINT32),
|
||||
downloadPath: ProtoField(3, ScalarType.STRING),
|
||||
oldVerSendFile: ProtoField(4, ScalarType.BYTES),
|
||||
imgType: ProtoField(5, ScalarType.INT32),
|
||||
previewsImage: ProtoField(6, ScalarType.BYTES),
|
||||
picMd5: ProtoField(7, ScalarType.BYTES),
|
||||
picHeight: ProtoField(8, ScalarType.UINT32),
|
||||
picWidth: ProtoField(9, ScalarType.UINT32),
|
||||
resId: ProtoField(10, ScalarType.STRING),
|
||||
flag: ProtoField(11, ScalarType.BYTES),
|
||||
thumbUrl: ProtoField(12, ScalarType.STRING),
|
||||
original: ProtoField(13, ScalarType.INT32),
|
||||
bigUrl: ProtoField(14, ScalarType.STRING),
|
||||
origUrl: ProtoField(15, ScalarType.STRING),
|
||||
bizType: ProtoField(16, ScalarType.INT32),
|
||||
result: ProtoField(17, ScalarType.INT32),
|
||||
index: ProtoField(18, ScalarType.INT32),
|
||||
opFaceBuf: ProtoField(19, ScalarType.BYTES),
|
||||
oldPicMd5: ProtoField(20, ScalarType.BOOL),
|
||||
thumbWidth: ProtoField(21, ScalarType.INT32),
|
||||
thumbHeight: ProtoField(22, ScalarType.INT32),
|
||||
fileId: ProtoField(23, ScalarType.INT32),
|
||||
showLen: ProtoField(24, ScalarType.UINT32),
|
||||
downloadLen: ProtoField(25, ScalarType.UINT32),
|
||||
x400Url: ProtoField(26, ScalarType.STRING),
|
||||
x400Width: ProtoField(27, ScalarType.INT32),
|
||||
x400Height: ProtoField(28, ScalarType.INT32),
|
||||
pbRes: ProtoField(29, () => NotOnlineImage_PbReserve),
|
||||
};
|
||||
|
||||
export const NotOnlineImage_PbReserve = {
|
||||
subType: ProtoField(1, ScalarType.INT32),
|
||||
field3: ProtoField(3, ScalarType.INT32),
|
||||
field4: ProtoField(4, ScalarType.INT32),
|
||||
summary: ProtoField(8, ScalarType.STRING),
|
||||
field10: ProtoField(10, ScalarType.INT32),
|
||||
field20: ProtoField(20, () => NotOnlineImage_PbReserve2),
|
||||
url: ProtoField(30, ScalarType.STRING),
|
||||
md5Str: ProtoField(31, ScalarType.STRING),
|
||||
};
|
||||
|
||||
export const NotOnlineImage_PbReserve2 = {
|
||||
field1: ProtoField(1, ScalarType.INT32),
|
||||
field2: ProtoField(2, ScalarType.STRING),
|
||||
field3: ProtoField(3, ScalarType.INT32),
|
||||
field4: ProtoField(4, ScalarType.INT32),
|
||||
field5: ProtoField(5, ScalarType.INT32),
|
||||
field7: ProtoField(7, ScalarType.STRING),
|
||||
};
|
||||
|
||||
export const TransElem = {
|
||||
elemType: ProtoField(1, ScalarType.INT32),
|
||||
elemValue: ProtoField(2, ScalarType.BYTES),
|
||||
};
|
||||
|
||||
export const MarketFace = {
|
||||
faceName: ProtoField(1, ScalarType.STRING),
|
||||
itemType: ProtoField(2, ScalarType.INT32),
|
||||
faceInfo: ProtoField(3, ScalarType.INT32),
|
||||
faceId: ProtoField(4, ScalarType.BYTES),
|
||||
tabId: ProtoField(5, ScalarType.INT32),
|
||||
subType: ProtoField(6, ScalarType.INT32),
|
||||
key: ProtoField(7, ScalarType.STRING),
|
||||
param: ProtoField(8, ScalarType.BYTES),
|
||||
mediaType: ProtoField(9, ScalarType.INT32),
|
||||
imageWidth: ProtoField(10, ScalarType.INT32),
|
||||
imageHeight: ProtoField(11, ScalarType.INT32),
|
||||
mobileparam: ProtoField(12, ScalarType.BYTES),
|
||||
pbReserve: ProtoField(13, () => MarketFacePbRes),
|
||||
};
|
||||
|
||||
export const MarketFacePbRes = {
|
||||
field8: ProtoField(8, ScalarType.INT32)
|
||||
}
|
||||
|
||||
export const CustomFace = {
|
||||
guid: ProtoField(1, ScalarType.BYTES),
|
||||
filePath: ProtoField(2, ScalarType.STRING),
|
||||
shortcut: ProtoField(3, ScalarType.STRING),
|
||||
buffer: ProtoField(4, ScalarType.BYTES),
|
||||
flag: ProtoField(5, ScalarType.BYTES),
|
||||
oldData: ProtoField(6, ScalarType.BYTES, true),
|
||||
fileId: ProtoField(7, ScalarType.UINT32),
|
||||
serverIp: ProtoField(8, ScalarType.INT32, true),
|
||||
serverPort: ProtoField(9, ScalarType.INT32, true),
|
||||
fileType: ProtoField(10, ScalarType.INT32),
|
||||
signature: ProtoField(11, ScalarType.BYTES),
|
||||
useful: ProtoField(12, ScalarType.INT32),
|
||||
md5: ProtoField(13, ScalarType.BYTES),
|
||||
thumbUrl: ProtoField(14, ScalarType.STRING),
|
||||
bigUrl: ProtoField(15, ScalarType.STRING),
|
||||
origUrl: ProtoField(16, ScalarType.STRING),
|
||||
bizType: ProtoField(17, ScalarType.INT32),
|
||||
repeatIndex: ProtoField(18, ScalarType.INT32),
|
||||
repeatImage: ProtoField(19, ScalarType.INT32),
|
||||
imageType: ProtoField(20, ScalarType.INT32),
|
||||
index: ProtoField(21, ScalarType.INT32),
|
||||
width: ProtoField(22, ScalarType.INT32),
|
||||
height: ProtoField(23, ScalarType.INT32),
|
||||
source: ProtoField(24, ScalarType.INT32),
|
||||
size: ProtoField(25, ScalarType.UINT32),
|
||||
origin: ProtoField(26, ScalarType.INT32),
|
||||
thumbWidth: ProtoField(27, ScalarType.INT32, true),
|
||||
thumbHeight: ProtoField(28, ScalarType.INT32, true),
|
||||
showLen: ProtoField(29, ScalarType.INT32),
|
||||
downloadLen: ProtoField(30, ScalarType.INT32),
|
||||
x400Url: ProtoField(31, ScalarType.STRING, true),
|
||||
x400Width: ProtoField(32, ScalarType.INT32),
|
||||
x400Height: ProtoField(33, ScalarType.INT32),
|
||||
pbRes: ProtoField(34, () => CustomFace_PbReserve, true),
|
||||
};
|
||||
|
||||
export const CustomFace_PbReserve = {
|
||||
subType: ProtoField(1, ScalarType.INT32),
|
||||
summary: ProtoField(9, ScalarType.STRING),
|
||||
};
|
||||
|
||||
export const ElemFlags2 = {
|
||||
colorTextId: ProtoField(1, ScalarType.UINT32),
|
||||
msgId: ProtoField(2, ScalarType.UINT64),
|
||||
whisperSessionId: ProtoField(3, ScalarType.UINT32),
|
||||
pttChangeBit: ProtoField(4, ScalarType.UINT32),
|
||||
vipStatus: ProtoField(5, ScalarType.UINT32),
|
||||
compatibleId: ProtoField(6, ScalarType.UINT32),
|
||||
insts: ProtoField(7, () => Instance, false, true),
|
||||
msgRptCnt: ProtoField(8, ScalarType.UINT32),
|
||||
srcInst: ProtoField(9, () => Instance),
|
||||
longtitude: ProtoField(10, ScalarType.UINT32),
|
||||
latitude: ProtoField(11, ScalarType.UINT32),
|
||||
customFont: ProtoField(12, ScalarType.UINT32),
|
||||
pcSupportDef: ProtoField(13, () => PcSupportDef),
|
||||
crmFlags: ProtoField(14, ScalarType.UINT32, true),
|
||||
};
|
||||
|
||||
export const PcSupportDef = {
|
||||
pcPtlBegin: ProtoField(1, ScalarType.UINT32),
|
||||
pcPtlEnd: ProtoField(2, ScalarType.UINT32),
|
||||
macPtlBegin: ProtoField(3, ScalarType.UINT32),
|
||||
macPtlEnd: ProtoField(4, ScalarType.UINT32),
|
||||
ptlsSupport: ProtoField(5, ScalarType.INT32, false, true),
|
||||
ptlsNotSupport: ProtoField(6, ScalarType.UINT32, false, true),
|
||||
};
|
||||
|
||||
export const Instance = {
|
||||
appId: ProtoField(1, ScalarType.UINT32),
|
||||
instId: ProtoField(2, ScalarType.UINT32),
|
||||
};
|
||||
|
||||
export const RichMsg = {
|
||||
template1: ProtoField(1, ScalarType.BYTES, true),
|
||||
serviceId: ProtoField(2, ScalarType.INT32, true),
|
||||
msgResId: ProtoField(3, ScalarType.BYTES, true),
|
||||
rand: ProtoField(4, ScalarType.INT32, true),
|
||||
seq: ProtoField(5, ScalarType.UINT32, true),
|
||||
};
|
||||
|
||||
export const GroupFile = {
|
||||
filename: ProtoField(1, ScalarType.BYTES),
|
||||
fileSize: ProtoField(2, ScalarType.UINT64),
|
||||
fileId: ProtoField(3, ScalarType.BYTES),
|
||||
batchId: ProtoField(4, ScalarType.BYTES),
|
||||
fileKey: ProtoField(5, ScalarType.BYTES),
|
||||
mark: ProtoField(6, ScalarType.BYTES),
|
||||
sequence: ProtoField(7, ScalarType.UINT64),
|
||||
batchItemId: ProtoField(8, ScalarType.BYTES),
|
||||
feedMsgTime: ProtoField(9, ScalarType.INT32),
|
||||
pbReserve: ProtoField(10, ScalarType.BYTES),
|
||||
};
|
||||
|
||||
export const ExtraInfo = {
|
||||
nick: ProtoField(1, ScalarType.BYTES),
|
||||
groupCard: ProtoField(2, ScalarType.BYTES),
|
||||
level: ProtoField(3, ScalarType.INT32),
|
||||
flags: ProtoField(4, ScalarType.INT32),
|
||||
groupMask: ProtoField(5, ScalarType.INT32),
|
||||
msgTailId: ProtoField(6, ScalarType.INT32),
|
||||
senderTitle: ProtoField(7, ScalarType.BYTES),
|
||||
apnsTips: ProtoField(8, ScalarType.BYTES),
|
||||
uin: ProtoField(9, ScalarType.UINT64),
|
||||
msgStateFlag: ProtoField(10, ScalarType.INT32),
|
||||
apnsSoundType: ProtoField(11, ScalarType.INT32),
|
||||
newGroupFlag: ProtoField(12, ScalarType.INT32),
|
||||
};
|
||||
|
||||
export const VideoFile = {
|
||||
fileUuid: ProtoField(1, ScalarType.STRING),
|
||||
fileMd5: ProtoField(2, ScalarType.BYTES),
|
||||
fileName: ProtoField(3, ScalarType.STRING),
|
||||
fileFormat: ProtoField(4, ScalarType.INT32),
|
||||
fileTime: ProtoField(5, ScalarType.INT32),
|
||||
fileSize: ProtoField(6, ScalarType.INT32),
|
||||
thumbWidth: ProtoField(7, ScalarType.INT32),
|
||||
thumbHeight: ProtoField(8, ScalarType.INT32),
|
||||
thumbFileMd5: ProtoField(9, ScalarType.BYTES),
|
||||
source: ProtoField(10, ScalarType.BYTES),
|
||||
thumbFileSize: ProtoField(11, ScalarType.INT32),
|
||||
busiType: ProtoField(12, ScalarType.INT32),
|
||||
fromChatType: ProtoField(13, ScalarType.INT32),
|
||||
toChatType: ProtoField(14, ScalarType.INT32),
|
||||
boolSupportProgressive: ProtoField(15, ScalarType.BOOL),
|
||||
fileWidth: ProtoField(16, ScalarType.INT32),
|
||||
fileHeight: ProtoField(17, ScalarType.INT32),
|
||||
subBusiType: ProtoField(18, ScalarType.INT32),
|
||||
videoAttr: ProtoField(19, ScalarType.INT32),
|
||||
bytesThumbFileUrls: ProtoField(20, ScalarType.BYTES, false, true),
|
||||
bytesVideoFileUrls: ProtoField(21, ScalarType.BYTES, false, true),
|
||||
thumbDownloadFlag: ProtoField(22, ScalarType.INT32),
|
||||
videoDownloadFlag: ProtoField(23, ScalarType.INT32),
|
||||
pbReserve: ProtoField(24, ScalarType.BYTES),
|
||||
};
|
||||
|
||||
export const AnonymousGroupMessage = {
|
||||
flags: ProtoField(1, ScalarType.INT32),
|
||||
anonId: ProtoField(2, ScalarType.BYTES),
|
||||
anonNick: ProtoField(3, ScalarType.BYTES),
|
||||
headPortrait: ProtoField(4, ScalarType.INT32),
|
||||
expireTime: ProtoField(5, ScalarType.INT32),
|
||||
bubbleId: ProtoField(6, ScalarType.INT32),
|
||||
rankColor: ProtoField(7, ScalarType.BYTES),
|
||||
};
|
||||
|
||||
export const CustomElem = {
|
||||
desc: ProtoField(1, ScalarType.BYTES),
|
||||
data: ProtoField(2, ScalarType.BYTES),
|
||||
enumType: ProtoField(3, ScalarType.INT32),
|
||||
ext: ProtoField(4, ScalarType.BYTES),
|
||||
sound: ProtoField(5, ScalarType.BYTES),
|
||||
};
|
||||
|
||||
export const GeneralFlags = {
|
||||
bubbleDiyTextId: ProtoField(1, ScalarType.INT32),
|
||||
groupFlagNew: ProtoField(2, ScalarType.INT32),
|
||||
uin: ProtoField(3, ScalarType.UINT64),
|
||||
rpId: ProtoField(4, ScalarType.BYTES),
|
||||
prpFold: ProtoField(5, ScalarType.INT32),
|
||||
longTextFlag: ProtoField(6, ScalarType.INT32),
|
||||
longTextResId: ProtoField(7, ScalarType.STRING, true),
|
||||
groupType: ProtoField(8, ScalarType.INT32),
|
||||
toUinFlag: ProtoField(9, ScalarType.INT32),
|
||||
glamourLevel: ProtoField(10, ScalarType.INT32),
|
||||
memberLevel: ProtoField(11, ScalarType.INT32),
|
||||
groupRankSeq: ProtoField(12, ScalarType.UINT64),
|
||||
olympicTorch: ProtoField(13, ScalarType.INT32),
|
||||
babyqGuideMsgCookie: ProtoField(14, ScalarType.BYTES),
|
||||
uin32ExpertFlag: ProtoField(15, ScalarType.INT32),
|
||||
bubbleSubId: ProtoField(16, ScalarType.INT32),
|
||||
pendantId: ProtoField(17, ScalarType.UINT64),
|
||||
rpIndex: ProtoField(18, ScalarType.BYTES),
|
||||
pbReserve: ProtoField(19, ScalarType.BYTES),
|
||||
};
|
||||
|
||||
export const SrcMsg = {
|
||||
origSeqs: ProtoField(1, ScalarType.UINT32, false, true),
|
||||
senderUin: ProtoField(2, ScalarType.UINT64),
|
||||
time: ProtoField(3, ScalarType.INT32, true),
|
||||
flag: ProtoField(4, ScalarType.INT32, true),
|
||||
elems: ProtoField(5, () => Elem, false, true),
|
||||
type: ProtoField(6, ScalarType.INT32, true),
|
||||
richMsg: ProtoField(7, ScalarType.BYTES, true),
|
||||
pbReserve: ProtoField(8, () => SrcMsgPbRes, true),
|
||||
sourceMsg: ProtoField(9, ScalarType.BYTES, true),
|
||||
toUin: ProtoField(10, ScalarType.UINT64, true),
|
||||
troopName: ProtoField(11, ScalarType.BYTES, true),
|
||||
};
|
||||
|
||||
export const SrcMsgPbRes = {
|
||||
messageId: ProtoField(3, ScalarType.UINT64),
|
||||
senderUid: ProtoField(6, ScalarType.STRING, true),
|
||||
receiverUid: ProtoField(7, ScalarType.STRING, true),
|
||||
friendSeq: ProtoField(8, ScalarType.UINT32, true),
|
||||
}
|
||||
|
||||
export const LightAppElem = {
|
||||
data: ProtoField(1, ScalarType.BYTES),
|
||||
msgResid: ProtoField(2, ScalarType.BYTES, true),
|
||||
};
|
||||
|
||||
export const CommonElem = {
|
||||
serviceType: ProtoField(1, ScalarType.INT32),
|
||||
pbElem: ProtoField(2, ScalarType.BYTES),
|
||||
businessType: ProtoField(3, ScalarType.UINT32),
|
||||
};
|
||||
|
||||
export const FaceExtra = {
|
||||
faceId: ProtoField(1, ScalarType.INT32, true),
|
||||
};
|
||||
|
||||
export const MentionExtra = {
|
||||
type: ProtoField(3, ScalarType.INT32, true),
|
||||
uin: ProtoField(4, ScalarType.UINT32, true),
|
||||
field5: ProtoField(5, ScalarType.INT32, true),
|
||||
uid: ProtoField(9, ScalarType.STRING, true),
|
||||
};
|
||||
|
||||
export const QBigFaceExtra = {
|
||||
AniStickerPackId: ProtoField(1, ScalarType.STRING, true),
|
||||
AniStickerId: ProtoField(2, ScalarType.STRING, true),
|
||||
faceId: ProtoField(3, ScalarType.INT32, true),
|
||||
Field4: ProtoField(4, ScalarType.INT32, true),
|
||||
AniStickerType: ProtoField(5, ScalarType.INT32, true),
|
||||
field6: ProtoField(6, ScalarType.STRING, true),
|
||||
preview: ProtoField(7, ScalarType.STRING, true),
|
||||
field9: ProtoField(9, ScalarType.INT32, true),
|
||||
};
|
||||
|
||||
export const QSmallFaceExtra = {
|
||||
faceId: ProtoField(1, ScalarType.UINT32),
|
||||
preview: ProtoField(2, ScalarType.STRING),
|
||||
preview2: ProtoField(3, ScalarType.STRING),
|
||||
};
|
||||
|
||||
export const MarkdownData = {
|
||||
content: ProtoField(1, ScalarType.STRING)
|
||||
}
|
19
src/core/packet/proto/message/group.ts
Normal file
19
src/core/packet/proto/message/group.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { ScalarType } from "@protobuf-ts/runtime";
|
||||
import { ProtoField } from "../NapProto";
|
||||
|
||||
export const GroupRecallMsg = {
|
||||
type: ProtoField(1, ScalarType.UINT32),
|
||||
groupUin: ProtoField(2, ScalarType.UINT32),
|
||||
field3: ProtoField(3, () => GroupRecallMsgField3),
|
||||
field4: ProtoField(4, () => GroupRecallMsgField4),
|
||||
};
|
||||
|
||||
export const GroupRecallMsgField3 = {
|
||||
sequence: ProtoField(1, ScalarType.UINT32),
|
||||
random: ProtoField(2, ScalarType.UINT32),
|
||||
field3: ProtoField(3, ScalarType.UINT32),
|
||||
};
|
||||
|
||||
export const GroupRecallMsgField4 = {
|
||||
field1: ProtoField(1, ScalarType.UINT32),
|
||||
};
|
75
src/core/packet/proto/message/message.ts
Normal file
75
src/core/packet/proto/message/message.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
import { ScalarType } from "@protobuf-ts/runtime";
|
||||
import { ProtoField } from "../NapProto";
|
||||
import { ForwardHead, Grp, GrpTmp, ResponseForward, ResponseGrp, Trans0X211, WPATmp } from "@/core/packet/proto/message/routing";
|
||||
import { RichText } from "@/core/packet/proto/message/component";
|
||||
import { C2C } from "@/core/packet/proto/message/c2c";
|
||||
|
||||
export const ContentHead = {
|
||||
type: ProtoField(1, ScalarType.UINT32),
|
||||
subType: ProtoField(2, ScalarType.UINT32, true),
|
||||
divSeq: ProtoField(3, ScalarType.UINT32, true),
|
||||
msgId: ProtoField(4, ScalarType.UINT32, true),
|
||||
sequence: ProtoField(5, ScalarType.UINT32, true),
|
||||
timeStamp: ProtoField(6, ScalarType.UINT32, true),
|
||||
field7: ProtoField(7, ScalarType.UINT64, true),
|
||||
field8: ProtoField(8, ScalarType.UINT32, true),
|
||||
field9: ProtoField(9, ScalarType.UINT32, true),
|
||||
newId: ProtoField(12, ScalarType.UINT64, true),
|
||||
forward: ProtoField(15, () => ForwardHead, true),
|
||||
};
|
||||
|
||||
export const MessageBody = {
|
||||
richText: ProtoField(1, () => RichText, true),
|
||||
msgContent: ProtoField(2, ScalarType.BYTES, true),
|
||||
msgEncryptContent: ProtoField(3, ScalarType.BYTES, true),
|
||||
};
|
||||
|
||||
export const Message = {
|
||||
routingHead: ProtoField(1, () => RoutingHead, true),
|
||||
contentHead: ProtoField(2, () => ContentHead, true),
|
||||
body: ProtoField(3, () => MessageBody, true),
|
||||
clientSequence: ProtoField(4, ScalarType.UINT32, true),
|
||||
random: ProtoField(5, ScalarType.UINT32, true),
|
||||
syncCookie: ProtoField(6, ScalarType.BYTES, true),
|
||||
via: ProtoField(8, ScalarType.UINT32, true),
|
||||
dataStatist: ProtoField(9, ScalarType.UINT32, true),
|
||||
ctrl: ProtoField(12, () => MessageControl, true),
|
||||
multiSendSeq: ProtoField(14, ScalarType.UINT32),
|
||||
};
|
||||
|
||||
export const MessageControl = {
|
||||
msgFlag: ProtoField(1, ScalarType.INT32),
|
||||
};
|
||||
|
||||
export const PushMsg = {
|
||||
message: ProtoField(1, () => PushMsgBody),
|
||||
status: ProtoField(3, ScalarType.INT32, true),
|
||||
pingFlag: ProtoField(5, ScalarType.INT32, true),
|
||||
generalFlag: ProtoField(9, ScalarType.INT32, true),
|
||||
};
|
||||
|
||||
export const PushMsgBody = {
|
||||
responseHead: ProtoField(1, () => ResponseHead),
|
||||
contentHead: ProtoField(2, () => ContentHead),
|
||||
body: ProtoField(3, () => MessageBody, true),
|
||||
};
|
||||
|
||||
export const ResponseHead = {
|
||||
fromUin: ProtoField(1, ScalarType.UINT32),
|
||||
fromUid: ProtoField(2, ScalarType.STRING, true),
|
||||
type: ProtoField(3, ScalarType.UINT32),
|
||||
sigMap: ProtoField(4, ScalarType.UINT32),
|
||||
toUin: ProtoField(5, ScalarType.UINT32),
|
||||
toUid: ProtoField(6, ScalarType.STRING, true),
|
||||
forward: ProtoField(7, () => ResponseForward, true),
|
||||
grp: ProtoField(8, () => ResponseGrp, true),
|
||||
};
|
||||
|
||||
export const RoutingHead = {
|
||||
c2c: ProtoField(1, () => C2C, true),
|
||||
grp: ProtoField(2, () => Grp, true),
|
||||
grpTmp: ProtoField(3, () => GrpTmp, true),
|
||||
wpaTmp: ProtoField(6, () => WPATmp, true),
|
||||
trans0X211: ProtoField(15, () => Trans0X211, true),
|
||||
};
|
||||
|
22
src/core/packet/proto/message/notify.ts
Normal file
22
src/core/packet/proto/message/notify.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { ScalarType } from "@protobuf-ts/runtime";
|
||||
import { ProtoField } from "../NapProto";
|
||||
|
||||
export const FriendRecall = {
|
||||
info: ProtoField(1, () => FriendRecallInfo),
|
||||
instId: ProtoField(2, ScalarType.UINT32),
|
||||
appId: ProtoField(3, ScalarType.UINT32),
|
||||
longMessageFlag: ProtoField(4, ScalarType.UINT32),
|
||||
reserved: ProtoField(5, ScalarType.BYTES),
|
||||
};
|
||||
|
||||
export const FriendRecallInfo = {
|
||||
fromUid: ProtoField(1, ScalarType.STRING),
|
||||
toUid: ProtoField(2, ScalarType.STRING),
|
||||
sequence: ProtoField(3, ScalarType.UINT32),
|
||||
newId: ProtoField(4, ScalarType.UINT64),
|
||||
time: ProtoField(5, ScalarType.UINT32),
|
||||
random: ProtoField(6, ScalarType.UINT32),
|
||||
pkgNum: ProtoField(7, ScalarType.UINT32),
|
||||
pkgIndex: ProtoField(8, ScalarType.UINT32),
|
||||
divSeq: ProtoField(9, ScalarType.UINT32),
|
||||
};
|
41
src/core/packet/proto/message/routing.ts
Normal file
41
src/core/packet/proto/message/routing.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { ScalarType } from "@protobuf-ts/runtime";
|
||||
import { ProtoField } from "../NapProto";
|
||||
|
||||
export const ForwardHead = {
|
||||
field1: ProtoField(1, ScalarType.UINT32, true),
|
||||
field2: ProtoField(2, ScalarType.UINT32, true),
|
||||
field3: ProtoField(3, ScalarType.UINT32, true),
|
||||
unknownBase64: ProtoField(5, ScalarType.STRING, true),
|
||||
avatar: ProtoField(6, ScalarType.STRING, true),
|
||||
};
|
||||
|
||||
export const Grp = {
|
||||
groupCode: ProtoField(1, ScalarType.UINT32, true),
|
||||
};
|
||||
|
||||
export const GrpTmp = {
|
||||
groupUin: ProtoField(1, ScalarType.UINT32, true),
|
||||
toUin: ProtoField(2, ScalarType.UINT32, true),
|
||||
};
|
||||
|
||||
export const ResponseForward = {
|
||||
friendName: ProtoField(6, ScalarType.STRING, true),
|
||||
};
|
||||
|
||||
export const ResponseGrp = {
|
||||
groupUin: ProtoField(1, ScalarType.UINT32),
|
||||
memberName: ProtoField(4, ScalarType.STRING),
|
||||
unknown5: ProtoField(5, ScalarType.UINT32),
|
||||
groupName: ProtoField(7, ScalarType.STRING),
|
||||
};
|
||||
|
||||
export const Trans0X211 = {
|
||||
toUin: ProtoField(1, ScalarType.UINT64, true),
|
||||
ccCmd: ProtoField(2, ScalarType.UINT32, true),
|
||||
uid: ProtoField(8, ScalarType.STRING, true),
|
||||
};
|
||||
|
||||
export const WPATmp = {
|
||||
toUin: ProtoField(1, ScalarType.UINT64),
|
||||
sig: ProtoField(2, ScalarType.BYTES),
|
||||
};
|
23
src/core/packet/proto/oidb/Oidb.0XFE1_2.ts
Normal file
23
src/core/packet/proto/oidb/Oidb.0XFE1_2.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { ScalarType } from "@protobuf-ts/runtime";
|
||||
import { ProtoField } from "../NapProto";
|
||||
|
||||
export const OidbSvcTrpcTcp0XFE1_2 = {
|
||||
uin: ProtoField(1, ScalarType.UINT32),
|
||||
key: ProtoField(3, () => OidbSvcTrpcTcp0XFE1_2Key, false, true),
|
||||
};
|
||||
|
||||
export const OidbSvcTrpcTcp0XFE1_2Key = {
|
||||
key: ProtoField(1, ScalarType.UINT32)
|
||||
};
|
||||
export const OidbSvcTrpcTcp0XFE1_2RSP_Status = {
|
||||
key: ProtoField(1, ScalarType.UINT32),
|
||||
value: ProtoField(2, ScalarType.UINT64)
|
||||
};
|
||||
|
||||
export const OidbSvcTrpcTcp0XFE1_2RSP_Data = {
|
||||
status: ProtoField(2, () => OidbSvcTrpcTcp0XFE1_2RSP_Status)
|
||||
};
|
||||
|
||||
export const OidbSvcTrpcTcp0XFE1_2RSP = {
|
||||
data: ProtoField(1, () => OidbSvcTrpcTcp0XFE1_2RSP_Data)
|
||||
};
|
100
src/core/packet/proto/oidb/Oidb.0x6D6.ts
Normal file
100
src/core/packet/proto/oidb/Oidb.0x6D6.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
import { ScalarType } from "@protobuf-ts/runtime";
|
||||
import { ProtoField } from "../NapProto";
|
||||
|
||||
export const OidbSvcTrpcTcp0x6D6 = {
|
||||
file: ProtoField(1, () => OidbSvcTrpcTcp0x6D6Upload, true),
|
||||
download: ProtoField(3, () => OidbSvcTrpcTcp0x6D6Download, true),
|
||||
delete: ProtoField(4, () => OidbSvcTrpcTcp0x6D6Delete, true),
|
||||
rename: ProtoField(5, () => OidbSvcTrpcTcp0x6D6Rename, true),
|
||||
move: ProtoField(6, () => OidbSvcTrpcTcp0x6D6Move, true),
|
||||
};
|
||||
|
||||
export const OidbSvcTrpcTcp0x6D6Upload = {
|
||||
groupUin: ProtoField(1, ScalarType.UINT32),
|
||||
appId: ProtoField(2, ScalarType.UINT32),
|
||||
busId: ProtoField(3, ScalarType.UINT32),
|
||||
entrance: ProtoField(4, ScalarType.UINT32),
|
||||
targetDirectory: ProtoField(5, ScalarType.STRING),
|
||||
fileName: ProtoField(6, ScalarType.STRING),
|
||||
localDirectory: ProtoField(7, ScalarType.STRING),
|
||||
fileSize: ProtoField(8, ScalarType.UINT64),
|
||||
fileSha1: ProtoField(9, ScalarType.BYTES),
|
||||
fileSha3: ProtoField(10, ScalarType.BYTES),
|
||||
fileMd5: ProtoField(11, ScalarType.BYTES),
|
||||
field15: ProtoField(15, ScalarType.BOOL),
|
||||
};
|
||||
|
||||
export const OidbSvcTrpcTcp0x6D6Download = {
|
||||
groupUin: ProtoField(1, ScalarType.UINT32),
|
||||
appId: ProtoField(2, ScalarType.UINT32),
|
||||
busId: ProtoField(3, ScalarType.UINT32),
|
||||
fileId: ProtoField(4, ScalarType.STRING),
|
||||
};
|
||||
|
||||
export const OidbSvcTrpcTcp0x6D6Delete = {
|
||||
groupUin: ProtoField(1, ScalarType.UINT32),
|
||||
busId: ProtoField(3, ScalarType.UINT32),
|
||||
fileId: ProtoField(5, ScalarType.STRING),
|
||||
};
|
||||
|
||||
export const OidbSvcTrpcTcp0x6D6Rename = {
|
||||
groupUin: ProtoField(1, ScalarType.UINT32),
|
||||
busId: ProtoField(3, ScalarType.UINT32),
|
||||
fileId: ProtoField(4, ScalarType.STRING),
|
||||
parentFolder: ProtoField(5, ScalarType.STRING),
|
||||
newFileName: ProtoField(6, ScalarType.STRING),
|
||||
};
|
||||
|
||||
export const OidbSvcTrpcTcp0x6D6Move = {
|
||||
groupUin: ProtoField(1, ScalarType.UINT32),
|
||||
appId: ProtoField(2, ScalarType.UINT32),
|
||||
busId: ProtoField(3, ScalarType.UINT32),
|
||||
fileId: ProtoField(4, ScalarType.STRING),
|
||||
parentDirectory: ProtoField(5, ScalarType.STRING),
|
||||
targetDirectory: ProtoField(6, ScalarType.STRING),
|
||||
};
|
||||
|
||||
export const OidbSvcTrpcTcp0x6D6Response = {
|
||||
upload: ProtoField(1, () => OidbSvcTrpcTcp0x6D6_0Response),
|
||||
download: ProtoField(3, () => OidbSvcTrpcTcp0x6D6_2Response),
|
||||
delete: ProtoField(4, () => OidbSvcTrpcTcp0x6D6_3_4_5Response),
|
||||
rename: ProtoField(5, () => OidbSvcTrpcTcp0x6D6_3_4_5Response),
|
||||
move: ProtoField(6, () => OidbSvcTrpcTcp0x6D6_3_4_5Response),
|
||||
};
|
||||
|
||||
export const OidbSvcTrpcTcp0x6D6_0Response = {
|
||||
retCode: ProtoField(1, ScalarType.INT32),
|
||||
retMsg: ProtoField(2, ScalarType.STRING),
|
||||
clientWording: ProtoField(3, ScalarType.STRING),
|
||||
uploadIp: ProtoField(4, ScalarType.STRING),
|
||||
serverDns: ProtoField(5, ScalarType.STRING),
|
||||
busId: ProtoField(6, ScalarType.INT32),
|
||||
fileId: ProtoField(7, ScalarType.STRING),
|
||||
checkKey: ProtoField(8, ScalarType.BYTES),
|
||||
fileKey: ProtoField(9, ScalarType.BYTES),
|
||||
boolFileExist: ProtoField(10, ScalarType.BOOL),
|
||||
uploadIpLanV4: ProtoField(12, ScalarType.STRING, false, true),
|
||||
uploadIpLanV6: ProtoField(13, ScalarType.STRING, false, true),
|
||||
uploadPort: ProtoField(14, ScalarType.UINT32),
|
||||
};
|
||||
|
||||
export const OidbSvcTrpcTcp0x6D6_2Response = {
|
||||
retCode: ProtoField(1, ScalarType.INT32),
|
||||
retMsg: ProtoField(2, ScalarType.STRING),
|
||||
clientWording: ProtoField(3, ScalarType.STRING),
|
||||
downloadIp: ProtoField(4, ScalarType.STRING),
|
||||
downloadDns: ProtoField(5, ScalarType.STRING),
|
||||
downloadUrl: ProtoField(6, ScalarType.BYTES),
|
||||
fileSha1: ProtoField(7, ScalarType.BYTES),
|
||||
fileSha3: ProtoField(8, ScalarType.BYTES),
|
||||
fileMd5: ProtoField(9, ScalarType.BYTES),
|
||||
cookieVal: ProtoField(10, ScalarType.BYTES),
|
||||
saveFileName: ProtoField(11, ScalarType.STRING),
|
||||
previewPort: ProtoField(12, ScalarType.UINT32),
|
||||
};
|
||||
|
||||
export const OidbSvcTrpcTcp0x6D6_3_4_5Response = {
|
||||
retCode: ProtoField(1, ScalarType.INT32),
|
||||
retMsg: ProtoField(2, ScalarType.STRING),
|
||||
clientWording: ProtoField(3, ScalarType.STRING),
|
||||
};
|
16
src/core/packet/proto/oidb/Oidb.0x8FC_2.ts
Normal file
16
src/core/packet/proto/oidb/Oidb.0x8FC_2.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { ScalarType } from "@protobuf-ts/runtime";
|
||||
import { ProtoField } from "../NapProto";
|
||||
|
||||
|
||||
//设置群头衔 OidbSvcTrpcTcp.0x8fc_2
|
||||
export const OidbSvcTrpcTcp0X8FC_2_Body = {
|
||||
targetUid: ProtoField(1, ScalarType.STRING),
|
||||
specialTitle: ProtoField(5, ScalarType.STRING),
|
||||
expiredTime: ProtoField(6, ScalarType.SINT32),
|
||||
uinName: ProtoField(7, ScalarType.STRING),
|
||||
targetName: ProtoField(8, ScalarType.STRING),
|
||||
};
|
||||
export const OidbSvcTrpcTcp0X8FC_2 = {
|
||||
groupUin: ProtoField(1, ScalarType.UINT32),
|
||||
body: ProtoField(3, ScalarType.BYTES),
|
||||
};
|
26
src/core/packet/proto/oidb/Oidb.0x9067_202.ts
Normal file
26
src/core/packet/proto/oidb/Oidb.0x9067_202.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { ScalarType } from "@protobuf-ts/runtime";
|
||||
import { ProtoField } from "../NapProto";
|
||||
import { MultiMediaReqHead } from "./common/Ntv2.RichMediaReq";
|
||||
|
||||
//Req
|
||||
export const OidbSvcTrpcTcp0X9067_202 = {
|
||||
ReqHead: ProtoField(1, () => MultiMediaReqHead),
|
||||
DownloadRKeyReq: ProtoField(4, () => OidbSvcTrpcTcp0X9067_202Key),
|
||||
};
|
||||
export const OidbSvcTrpcTcp0X9067_202Key = {
|
||||
key: ProtoField(1, ScalarType.INT32, false, true),
|
||||
};
|
||||
|
||||
//Rsp
|
||||
export const OidbSvcTrpcTcp0X9067_202_RkeyList = {
|
||||
rkey: ProtoField(1, ScalarType.STRING),
|
||||
time: ProtoField(4, ScalarType.UINT32),
|
||||
type: ProtoField(5, ScalarType.UINT32),
|
||||
|
||||
};
|
||||
export const OidbSvcTrpcTcp0X9067_202_Data = {
|
||||
rkeyList: ProtoField(1, () => OidbSvcTrpcTcp0X9067_202_RkeyList, false, true),
|
||||
};
|
||||
export const OidbSvcTrpcTcp0X9067_202_Rsp_Body = {
|
||||
data: ProtoField(4, () => OidbSvcTrpcTcp0X9067_202_Data),
|
||||
};
|
61
src/core/packet/proto/oidb/Oidb.0xE37_1200.ts
Normal file
61
src/core/packet/proto/oidb/Oidb.0xE37_1200.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import { ScalarType } from "@protobuf-ts/runtime";
|
||||
import { ProtoField } from "../NapProto";
|
||||
|
||||
export const OidbSvcTrpcTcp0XE37_1200 = {
|
||||
subCommand: ProtoField(1, ScalarType.UINT32, true),
|
||||
field2: ProtoField(2, ScalarType.INT32, true),
|
||||
body: ProtoField(14, () => OidbSvcTrpcTcp0XE37_1200Body, true),
|
||||
field101: ProtoField(101, ScalarType.INT32, true),
|
||||
field102: ProtoField(102, ScalarType.INT32, true),
|
||||
field200: ProtoField(200, ScalarType.INT32, true),
|
||||
field99999: ProtoField(99999, ScalarType.BYTES, true),
|
||||
};
|
||||
|
||||
export const OidbSvcTrpcTcp0XE37_1200Body = {
|
||||
receiverUid: ProtoField(10, ScalarType.STRING, true),
|
||||
fileUuid: ProtoField(20, ScalarType.STRING, true),
|
||||
type: ProtoField(30, ScalarType.INT32, true),
|
||||
fileHash: ProtoField(60, ScalarType.STRING, true),
|
||||
t2: ProtoField(601, ScalarType.INT32, true),
|
||||
};
|
||||
|
||||
export const OidbSvcTrpcTcp0XE37_1200Response = {
|
||||
command: ProtoField(1, ScalarType.UINT32, true),
|
||||
subCommand: ProtoField(2, ScalarType.UINT32, true),
|
||||
body: ProtoField(14, () => OidbSvcTrpcTcp0XE37_1200ResponseBody, true),
|
||||
field50: ProtoField(50, ScalarType.UINT32, true),
|
||||
};
|
||||
|
||||
export const OidbSvcTrpcTcp0XE37_1200ResponseBody = {
|
||||
field10: ProtoField(10, ScalarType.UINT32, true),
|
||||
state: ProtoField(20, ScalarType.STRING, true),
|
||||
result: ProtoField(30, () => OidbSvcTrpcTcp0XE37_1200Result, true),
|
||||
metadata: ProtoField(40, () => OidbSvcTrpcTcp0XE37_1200Metadata, true),
|
||||
};
|
||||
|
||||
export const OidbSvcTrpcTcp0XE37_1200Result = {
|
||||
server: ProtoField(20, ScalarType.STRING, true),
|
||||
port: ProtoField(40, ScalarType.UINT32, true),
|
||||
url: ProtoField(50, ScalarType.STRING, true),
|
||||
additionalServer: ProtoField(60, ScalarType.STRING, false, true),
|
||||
ssoPort: ProtoField(80, ScalarType.UINT32, true),
|
||||
ssoUrl: ProtoField(90, ScalarType.STRING, true),
|
||||
extra: ProtoField(120, ScalarType.BYTES, true),
|
||||
};
|
||||
|
||||
export const OidbSvcTrpcTcp0XE37_1200Metadata = {
|
||||
uin: ProtoField(1, ScalarType.UINT32, true),
|
||||
field2: ProtoField(2, ScalarType.UINT32, true),
|
||||
field3: ProtoField(3, ScalarType.UINT32, true),
|
||||
size: ProtoField(4, ScalarType.UINT32, true),
|
||||
timestamp: ProtoField(5, ScalarType.UINT32, true),
|
||||
fileUuid: ProtoField(6, ScalarType.STRING, true),
|
||||
fileName: ProtoField(7, ScalarType.STRING, true),
|
||||
field100: ProtoField(100, ScalarType.BYTES, true),
|
||||
field101: ProtoField(101, ScalarType.BYTES, true),
|
||||
field110: ProtoField(110, ScalarType.UINT32, true),
|
||||
timestamp1: ProtoField(130, ScalarType.UINT32, true),
|
||||
fileHash: ProtoField(140, ScalarType.STRING, true),
|
||||
field141: ProtoField(141, ScalarType.BYTES, true),
|
||||
field142: ProtoField(142, ScalarType.BYTES, true),
|
||||
};
|
10
src/core/packet/proto/oidb/Oidb.0xED3_1.ts
Normal file
10
src/core/packet/proto/oidb/Oidb.0xED3_1.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { ScalarType } from "@protobuf-ts/runtime";
|
||||
import { ProtoField } from "../NapProto";
|
||||
|
||||
// Send Poke
|
||||
export const OidbSvcTrpcTcp0XED3_1 = {
|
||||
uin: ProtoField(1, ScalarType.UINT32),
|
||||
groupUin: ProtoField(2, ScalarType.UINT32),
|
||||
friendUin: ProtoField(5, ScalarType.UINT32),
|
||||
ext: ProtoField(6, ScalarType.UINT32, true)
|
||||
};
|
13
src/core/packet/proto/oidb/OidbBase.ts
Normal file
13
src/core/packet/proto/oidb/OidbBase.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { ScalarType } from "@protobuf-ts/runtime";
|
||||
import { ProtoField } from "../NapProto";
|
||||
|
||||
export const OidbSvcTrpcTcpBase = {
|
||||
command: ProtoField(1, ScalarType.UINT32),
|
||||
subCommand: ProtoField(2, ScalarType.UINT32),
|
||||
body: ProtoField(4, ScalarType.BYTES),
|
||||
errorMsg: ProtoField(5, ScalarType.STRING, true),
|
||||
isReserved: ProtoField(12, ScalarType.UINT32)
|
||||
};
|
||||
export const OidbSvcTrpcTcpBaseRsp = {
|
||||
body: ProtoField(4, ScalarType.BYTES)
|
||||
};
|
214
src/core/packet/proto/oidb/common/Ntv2.RichMediaReq.ts
Normal file
214
src/core/packet/proto/oidb/common/Ntv2.RichMediaReq.ts
Normal file
@@ -0,0 +1,214 @@
|
||||
import {ScalarType} from "@protobuf-ts/runtime";
|
||||
import {ProtoField} from "../../NapProto";
|
||||
|
||||
export const NTV2RichMediaReq = {
|
||||
ReqHead: ProtoField(1, () => MultiMediaReqHead),
|
||||
Upload: ProtoField(2, () => UploadReq),
|
||||
Download: ProtoField(3, () => DownloadReq),
|
||||
DownloadRKey: ProtoField(4, () => DownloadRKeyReq),
|
||||
Delete: ProtoField(5, () => DeleteReq),
|
||||
UploadCompleted: ProtoField(6, () => UploadCompletedReq),
|
||||
MsgInfoAuth: ProtoField(7, () => MsgInfoAuthReq),
|
||||
UploadKeyRenewal: ProtoField(8, () => UploadKeyRenewalReq),
|
||||
DownloadSafe: ProtoField(9, () => DownloadSafeReq),
|
||||
Extension: ProtoField(99, ScalarType.BYTES, true),
|
||||
};
|
||||
|
||||
export const MultiMediaReqHead = {
|
||||
Common: ProtoField(1, () => CommonHead),
|
||||
Scene: ProtoField(2, () => SceneInfo),
|
||||
Client: ProtoField(3, () => ClientMeta),
|
||||
};
|
||||
|
||||
export const CommonHead = {
|
||||
RequestId: ProtoField(1, ScalarType.UINT32),
|
||||
Command: ProtoField(2, ScalarType.UINT32),
|
||||
};
|
||||
|
||||
export const SceneInfo = {
|
||||
RequestType: ProtoField(101, ScalarType.UINT32),
|
||||
BusinessType: ProtoField(102, ScalarType.UINT32),
|
||||
SceneType: ProtoField(200, ScalarType.UINT32),
|
||||
C2C: ProtoField(201, () => C2CUserInfo, true),
|
||||
Group: ProtoField(202, () => NTGroupInfo, true),
|
||||
};
|
||||
|
||||
export const C2CUserInfo = {
|
||||
AccountType: ProtoField(1, ScalarType.UINT32),
|
||||
TargetUid: ProtoField(2, ScalarType.STRING),
|
||||
};
|
||||
|
||||
export const NTGroupInfo = {
|
||||
GroupUin: ProtoField(1, ScalarType.UINT32),
|
||||
};
|
||||
|
||||
export const ClientMeta = {
|
||||
AgentType: ProtoField(1, ScalarType.UINT32),
|
||||
};
|
||||
|
||||
export const DownloadReq = {
|
||||
Node: ProtoField(1, () => IndexNode),
|
||||
Download: ProtoField(2, () => DownloadExt),
|
||||
};
|
||||
|
||||
export const IndexNode = {
|
||||
Info: ProtoField(1, () => FileInfo),
|
||||
FileUuid: ProtoField(2, ScalarType.STRING),
|
||||
StoreId: ProtoField(3, ScalarType.UINT32),
|
||||
UploadTime: ProtoField(4, ScalarType.UINT32),
|
||||
Ttl: ProtoField(5, ScalarType.UINT32),
|
||||
SubType: ProtoField(6, ScalarType.UINT32),
|
||||
};
|
||||
|
||||
export const FileInfo = {
|
||||
FileSize: ProtoField(1, ScalarType.UINT32),
|
||||
FileHash: ProtoField(2, ScalarType.STRING),
|
||||
FileSha1: ProtoField(3, ScalarType.STRING),
|
||||
FileName: ProtoField(4, ScalarType.STRING),
|
||||
Type: ProtoField(5, () => FileType),
|
||||
Width: ProtoField(6, ScalarType.UINT32),
|
||||
Height: ProtoField(7, ScalarType.UINT32),
|
||||
Time: ProtoField(8, ScalarType.UINT32),
|
||||
Original: ProtoField(9, ScalarType.UINT32),
|
||||
};
|
||||
|
||||
export const FileType = {
|
||||
Type: ProtoField(1, ScalarType.UINT32),
|
||||
PicFormat: ProtoField(2, ScalarType.UINT32),
|
||||
VideoFormat: ProtoField(3, ScalarType.UINT32),
|
||||
VoiceFormat: ProtoField(4, ScalarType.UINT32),
|
||||
};
|
||||
|
||||
export const DownloadExt = {
|
||||
Pic: ProtoField(1, () => PicDownloadExt),
|
||||
Video: ProtoField(2, () => VideoDownloadExt),
|
||||
Ptt: ProtoField(3, () => PttDownloadExt),
|
||||
};
|
||||
|
||||
export const VideoDownloadExt = {
|
||||
BusiType: ProtoField(1, ScalarType.UINT32),
|
||||
SceneType: ProtoField(2, ScalarType.UINT32),
|
||||
SubBusiType: ProtoField(3, ScalarType.UINT32),
|
||||
};
|
||||
|
||||
export const PicDownloadExt = {};
|
||||
|
||||
export const PttDownloadExt = {};
|
||||
|
||||
export const DownloadRKeyReq = {
|
||||
Types: ProtoField(1, ScalarType.INT32, false, true),
|
||||
};
|
||||
|
||||
export const DeleteReq = {
|
||||
Index: ProtoField(1, () => IndexNode, false, true),
|
||||
NeedRecallMsg: ProtoField(2, ScalarType.BOOL),
|
||||
MsgSeq: ProtoField(3, ScalarType.UINT64),
|
||||
MsgRandom: ProtoField(4, ScalarType.UINT64),
|
||||
MsgTime: ProtoField(5, ScalarType.UINT64),
|
||||
};
|
||||
|
||||
export const UploadCompletedReq = {
|
||||
SrvSendMsg: ProtoField(1, ScalarType.BOOL),
|
||||
ClientRandomId: ProtoField(2, ScalarType.UINT64),
|
||||
MsgInfo: ProtoField(3, () => MsgInfo),
|
||||
ClientSeq: ProtoField(4, ScalarType.UINT32),
|
||||
};
|
||||
|
||||
export const MsgInfoAuthReq = {
|
||||
Msg: ProtoField(1, ScalarType.BYTES),
|
||||
AuthTime: ProtoField(2, ScalarType.UINT64),
|
||||
};
|
||||
|
||||
export const DownloadSafeReq = {
|
||||
Index: ProtoField(1, () => IndexNode),
|
||||
};
|
||||
|
||||
export const UploadKeyRenewalReq = {
|
||||
OldUKey: ProtoField(1, ScalarType.STRING),
|
||||
SubType: ProtoField(2, ScalarType.UINT32),
|
||||
};
|
||||
|
||||
export const MsgInfo = {
|
||||
MsgInfoBody: ProtoField(1, () => MsgInfoBody, false, true),
|
||||
ExtBizInfo: ProtoField(2, () => ExtBizInfo),
|
||||
};
|
||||
|
||||
export const MsgInfoBody = {
|
||||
Index: ProtoField(1, () => IndexNode),
|
||||
Picture: ProtoField(2, () => PictureInfo),
|
||||
Video: ProtoField(3, () => VideoInfo),
|
||||
Audio: ProtoField(4, () => AudioInfo),
|
||||
FileExist: ProtoField(5, ScalarType.BOOL),
|
||||
HashSum: ProtoField(6, ScalarType.BYTES),
|
||||
};
|
||||
|
||||
export const VideoInfo = {};
|
||||
|
||||
export const AudioInfo = {};
|
||||
|
||||
export const PictureInfo = {
|
||||
UrlPath: ProtoField(1, ScalarType.STRING),
|
||||
Ext: ProtoField(2, () => PicUrlExtInfo),
|
||||
Domain: ProtoField(3, ScalarType.STRING),
|
||||
};
|
||||
|
||||
export const PicUrlExtInfo = {
|
||||
OriginalParameter: ProtoField(1, ScalarType.STRING),
|
||||
BigParameter: ProtoField(2, ScalarType.STRING),
|
||||
ThumbParameter: ProtoField(3, ScalarType.STRING),
|
||||
};
|
||||
|
||||
export const VideoExtInfo = {
|
||||
VideoCodecFormat: ProtoField(1, ScalarType.UINT32),
|
||||
}
|
||||
|
||||
export const ExtBizInfo = {
|
||||
Pic: ProtoField(1, () => PicExtBizInfo),
|
||||
Video: ProtoField(2, () => VideoExtBizInfo),
|
||||
Ptt: ProtoField(3, () => PttExtBizInfo),
|
||||
BusiType: ProtoField(10, ScalarType.UINT32),
|
||||
};
|
||||
|
||||
export const PttExtBizInfo = {
|
||||
SrcUin: ProtoField(1, ScalarType.UINT64),
|
||||
PttScene: ProtoField(2, ScalarType.UINT32),
|
||||
PttType: ProtoField(3, ScalarType.UINT32),
|
||||
ChangeVoice: ProtoField(4, ScalarType.UINT32),
|
||||
Waveform: ProtoField(5, ScalarType.BYTES),
|
||||
AutoConvertText: ProtoField(6, ScalarType.UINT32),
|
||||
BytesReserve: ProtoField(11, ScalarType.BYTES),
|
||||
BytesPbReserve: ProtoField(12, ScalarType.BYTES),
|
||||
BytesGeneralFlags: ProtoField(13, ScalarType.BYTES),
|
||||
};
|
||||
|
||||
export const VideoExtBizInfo = {
|
||||
FromScene: ProtoField(1, ScalarType.UINT32),
|
||||
ToScene: ProtoField(2, ScalarType.UINT32),
|
||||
BytesPbReserve: ProtoField(3, ScalarType.BYTES),
|
||||
};
|
||||
|
||||
export const PicExtBizInfo = {
|
||||
BizType: ProtoField(1, ScalarType.UINT32),
|
||||
TextSummary: ProtoField(2, ScalarType.STRING),
|
||||
BytesPbReserveC2c: ProtoField(11, ScalarType.BYTES),
|
||||
BytesPbReserveTroop: ProtoField(12, ScalarType.BYTES),
|
||||
FromScene: ProtoField(1001, ScalarType.UINT32),
|
||||
ToScene: ProtoField(1002, ScalarType.UINT32),
|
||||
OldFileId: ProtoField(1003, ScalarType.UINT32),
|
||||
};
|
||||
|
||||
export const UploadReq = {
|
||||
UploadInfo: ProtoField(1, () => UploadInfo, false, true),
|
||||
TryFastUploadCompleted: ProtoField(2, ScalarType.BOOL),
|
||||
SrvSendMsg: ProtoField(3, ScalarType.BOOL),
|
||||
ClientRandomId: ProtoField(4, ScalarType.UINT64),
|
||||
CompatQMsgSceneType: ProtoField(5, ScalarType.UINT32),
|
||||
ExtBizInfo: ProtoField(6, () => ExtBizInfo),
|
||||
ClientSeq: ProtoField(7, ScalarType.UINT32),
|
||||
NoNeedCompatMsg: ProtoField(8, ScalarType.BOOL),
|
||||
};
|
||||
|
||||
export const UploadInfo = {
|
||||
FileInfo: ProtoField(1, () => FileInfo),
|
||||
SubFileType: ProtoField(2, ScalarType.UINT32),
|
||||
};
|
114
src/core/packet/proto/oidb/common/Ntv2.RichMediaResp.ts
Normal file
114
src/core/packet/proto/oidb/common/Ntv2.RichMediaResp.ts
Normal file
@@ -0,0 +1,114 @@
|
||||
import {ScalarType} from "@protobuf-ts/runtime";
|
||||
import {ProtoField} from "../../NapProto";
|
||||
import {CommonHead, MsgInfo, PicUrlExtInfo, VideoExtInfo} from "@/core/packet/proto/oidb/common/Ntv2.RichMediaReq";
|
||||
|
||||
export const NTV2RichMediaResp = {
|
||||
respHead: ProtoField(1, () => MultiMediaRespHead),
|
||||
upload: ProtoField(2, () => UploadResp),
|
||||
download: ProtoField(3, () => DownloadResp),
|
||||
downloadRKey: ProtoField(4, () => DownloadRKeyResp),
|
||||
delete: ProtoField(5, () => DeleteResp),
|
||||
uploadCompleted: ProtoField(6, () => UploadCompletedResp),
|
||||
msgInfoAuth: ProtoField(7, () => MsgInfoAuthResp),
|
||||
uploadKeyRenewal: ProtoField(8, () => UploadKeyRenewalResp),
|
||||
downloadSafe: ProtoField(9, () => DownloadSafeResp),
|
||||
extension: ProtoField(99, ScalarType.BYTES, true),
|
||||
}
|
||||
|
||||
export const MultiMediaRespHead = {
|
||||
common: ProtoField(1, () => CommonHead),
|
||||
retCode: ProtoField(2, ScalarType.UINT32),
|
||||
message: ProtoField(3, ScalarType.STRING),
|
||||
}
|
||||
|
||||
export const DownloadResp = {
|
||||
rKeyParam: ProtoField(1, ScalarType.STRING),
|
||||
rKeyTtlSecond: ProtoField(2, ScalarType.UINT32),
|
||||
info: ProtoField(3, () => DownloadInfo),
|
||||
rKeyCreateTime: ProtoField(4, ScalarType.UINT32),
|
||||
}
|
||||
|
||||
export const DownloadInfo = {
|
||||
domain: ProtoField(1, ScalarType.STRING),
|
||||
urlPath: ProtoField(2, ScalarType.STRING),
|
||||
httpsPort: ProtoField(3, ScalarType.UINT32),
|
||||
ipv4s: ProtoField(4, () => IPv4, false, true),
|
||||
ipv6s: ProtoField(5, () => IPv6, false, true),
|
||||
picUrlExtInfo: ProtoField(6, () => PicUrlExtInfo),
|
||||
videoExtInfo: ProtoField(7, () => VideoExtInfo),
|
||||
}
|
||||
|
||||
export const IPv4 = {
|
||||
outIP: ProtoField(1, ScalarType.UINT32),
|
||||
outPort: ProtoField(2, ScalarType.UINT32),
|
||||
inIP: ProtoField(3, ScalarType.UINT32),
|
||||
inPort: ProtoField(4, ScalarType.UINT32),
|
||||
ipType: ProtoField(5, ScalarType.UINT32),
|
||||
}
|
||||
|
||||
export const IPv6 = {
|
||||
outIP: ProtoField(1, ScalarType.BYTES),
|
||||
outPort: ProtoField(2, ScalarType.UINT32),
|
||||
inIP: ProtoField(3, ScalarType.BYTES),
|
||||
inPort: ProtoField(4, ScalarType.UINT32),
|
||||
ipType: ProtoField(5, ScalarType.UINT32),
|
||||
}
|
||||
|
||||
export const UploadResp = {
|
||||
uKey: ProtoField(1, ScalarType.STRING, true),
|
||||
uKeyTtlSecond: ProtoField(2, ScalarType.UINT32),
|
||||
ipv4s: ProtoField(3, () => IPv4, false, true),
|
||||
ipv6s: ProtoField(4, () => IPv6, false, true),
|
||||
msgSeq: ProtoField(5, ScalarType.UINT64),
|
||||
msgInfo: ProtoField(6, () => MsgInfo),
|
||||
ext: ProtoField(7, () => RichMediaStorageTransInfo, false, true),
|
||||
compatQMsg: ProtoField(8, ScalarType.BYTES),
|
||||
subFileInfos: ProtoField(10, () => SubFileInfo, false, true),
|
||||
}
|
||||
|
||||
export const RichMediaStorageTransInfo = {
|
||||
subType: ProtoField(1, ScalarType.UINT32),
|
||||
extType: ProtoField(2, ScalarType.UINT32),
|
||||
extValue: ProtoField(3, ScalarType.BYTES),
|
||||
}
|
||||
|
||||
export const SubFileInfo = {
|
||||
subType: ProtoField(1, ScalarType.UINT32),
|
||||
uKey: ProtoField(2, ScalarType.STRING),
|
||||
uKeyTtlSecond: ProtoField(3, ScalarType.UINT32),
|
||||
ipv4s: ProtoField(4, () => IPv4, false, true),
|
||||
ipv6s: ProtoField(5, () => IPv6, false, true),
|
||||
}
|
||||
|
||||
export const DownloadSafeResp = {
|
||||
}
|
||||
|
||||
export const UploadKeyRenewalResp = {
|
||||
ukey: ProtoField(1, ScalarType.STRING),
|
||||
ukeyTtlSec: ProtoField(2, ScalarType.UINT64),
|
||||
}
|
||||
|
||||
export const MsgInfoAuthResp = {
|
||||
authCode: ProtoField(1, ScalarType.UINT32),
|
||||
msg: ProtoField(2, ScalarType.BYTES),
|
||||
resultTime: ProtoField(3, ScalarType.UINT64),
|
||||
}
|
||||
|
||||
export const UploadCompletedResp = {
|
||||
msgSeq: ProtoField(1, ScalarType.UINT64),
|
||||
}
|
||||
|
||||
export const DeleteResp = {
|
||||
}
|
||||
|
||||
export const DownloadRKeyResp = {
|
||||
rKeys: ProtoField(1, () => RKeyInfo, false, true),
|
||||
}
|
||||
|
||||
export const RKeyInfo = {
|
||||
rkey: ProtoField(1, ScalarType.STRING),
|
||||
rkeyTtlSec: ProtoField(2, ScalarType.UINT64),
|
||||
storeId: ProtoField(3, ScalarType.UINT32),
|
||||
rkeyCreateTime: ProtoField(4, ScalarType.UINT32, true),
|
||||
type: ProtoField(5, ScalarType.UINT32, true),
|
||||
}
|
49
src/core/packet/proto/old/Message.ts
Normal file
49
src/core/packet/proto/old/Message.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
// TODO: refactor with NapProto
|
||||
import { MessageType, BinaryReader, ScalarType } from '@protobuf-ts/runtime';
|
||||
|
||||
export const BodyInner = new MessageType("BodyInner", [
|
||||
{ no: 1, name: "msgType", kind: "scalar", T: ScalarType.UINT32 /* uint32 */, opt: true },
|
||||
{ no: 2, name: "subType", kind: "scalar", T: ScalarType.UINT32 /* uint32 */, opt: true }
|
||||
]);
|
||||
|
||||
export const NoifyData = new MessageType("NoifyData", [
|
||||
{ no: 1, name: "skip", kind: "scalar", T: ScalarType.BYTES /* bytes */, opt: true },
|
||||
{ no: 2, name: "innerData", kind: "scalar", T: ScalarType.BYTES /* bytes */, opt: true }
|
||||
]);
|
||||
|
||||
export const MsgHead = new MessageType("MsgHead", [
|
||||
{ no: 2, name: "bodyInner", kind: "message", T: () => BodyInner, opt: true },
|
||||
{ no: 3, name: "noifyData", kind: "message", T: () => NoifyData, opt: true }
|
||||
]);
|
||||
|
||||
export const Message = new MessageType("Message", [
|
||||
{ no: 1, name: "msgHead", kind: "message", T: () => MsgHead }
|
||||
]);
|
||||
|
||||
export const SubDetail = new MessageType("SubDetail", [
|
||||
{ no: 1, name: "msgSeq", kind: "scalar", T: ScalarType.UINT32 /* uint32 */ },
|
||||
{ no: 2, name: "msgTime", kind: "scalar", T: ScalarType.UINT32 /* uint32 */ },
|
||||
{ no: 6, name: "senderUid", kind: "scalar", T: ScalarType.STRING /* string */ }
|
||||
]);
|
||||
|
||||
export const RecallDetails = new MessageType("RecallDetails", [
|
||||
{ no: 1, name: "operatorUid", kind: "scalar", T: ScalarType.STRING /* string */ },
|
||||
{ no: 3, name: "subDetail", kind: "message", T: () => SubDetail }
|
||||
]);
|
||||
|
||||
export const RecallGroup = new MessageType("RecallGroup", [
|
||||
{ no: 1, name: "type", kind: "scalar", T: ScalarType.INT32 /* int32 */ },
|
||||
{ no: 4, name: "peerUid", kind: "scalar", T: ScalarType.UINT32 /* uint32 */ },
|
||||
{ no: 11, name: "recallDetails", kind: "message", T: () => RecallDetails },
|
||||
{ no: 37, name: "grayTipsSeq", kind: "scalar", T: ScalarType.UINT32 /* uint32 */ }
|
||||
]);
|
||||
|
||||
export function decodeMessage(buffer: Uint8Array): any {
|
||||
const reader = new BinaryReader(buffer);
|
||||
return Message.internalBinaryRead(reader, reader.len, { readUnknownField: true, readerFactory: () => new BinaryReader(buffer) });
|
||||
}
|
||||
|
||||
export function decodeRecallGroup(buffer: Uint8Array): any {
|
||||
const reader = new BinaryReader(buffer);
|
||||
return RecallGroup.internalBinaryRead(reader, reader.len, { readUnknownField: true, readerFactory: () => new BinaryReader(buffer) });
|
||||
}
|
59
src/core/packet/proto/old/ProfileLike.ts
Normal file
59
src/core/packet/proto/old/ProfileLike.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
// TODO: refactor with NapProto
|
||||
import { MessageType, BinaryReader, ScalarType, RepeatType } from '@protobuf-ts/runtime';
|
||||
|
||||
export const LikeDetail = new MessageType("likeDetail", [
|
||||
{ no: 1, name: "txt", kind: "scalar", T: ScalarType.STRING /* string */ },
|
||||
{ no: 3, name: "uin", kind: "scalar", T: ScalarType.INT64 /* int64 */ },
|
||||
{ no: 5, name: "nickname", kind: "scalar", T: ScalarType.STRING /* string */ }
|
||||
]);
|
||||
|
||||
export const LikeMsg = new MessageType("likeMsg", [
|
||||
{ no: 1, name: "times", kind: "scalar", T: ScalarType.INT32 /* int32 */ },
|
||||
{ no: 2, name: "time", kind: "scalar", T: ScalarType.INT32 /* int32 */ },
|
||||
{ no: 3, name: "detail", kind: "message", T: () => LikeDetail }
|
||||
]);
|
||||
|
||||
export const ProfileLikeSubTip = new MessageType("profileLikeSubTip", [
|
||||
{ no: 14, name: "msg", kind: "message", T: () => LikeMsg }
|
||||
]);
|
||||
export const ProfileLikeTip = new MessageType("profileLikeTip", [
|
||||
{ no: 1, name: "msgType", kind: "scalar", T: ScalarType.INT32 /* int32 */ },
|
||||
{ no: 2, name: "subType", kind: "scalar", T: ScalarType.INT32 /* int32 */ },
|
||||
{ no: 203, name: "content", kind: "message", T: () => ProfileLikeSubTip }
|
||||
]);
|
||||
export const SysMessageHeader = new MessageType("SysMessageHeader", [
|
||||
{ no: 1, name: "PeerNumber", kind: "scalar", T: ScalarType.UINT32 /* uint32 */ },
|
||||
{ no: 2, name: "PeerString", kind: "scalar", T: ScalarType.STRING /* string */ },
|
||||
{ no: 5, name: "Uin", kind: "scalar", T: ScalarType.UINT32 /* uint32 */ },
|
||||
{ no: 6, name: "Uid", kind: "scalar", T: ScalarType.STRING /* string */, opt: true }
|
||||
]);
|
||||
|
||||
export const SysMessageMsgSpec = new MessageType("SysMessageMsgSpec", [
|
||||
{ no: 1, name: "msgType", kind: "scalar", T: ScalarType.UINT32 /* uint32 */ },
|
||||
{ no: 2, name: "subType", kind: "scalar", T: ScalarType.UINT32 /* uint32 */ },
|
||||
{ no: 3, name: "subSubType", kind: "scalar", T: ScalarType.UINT32 /* uint32 */ },
|
||||
{ no: 5, name: "msgSeq", kind: "scalar", T: ScalarType.UINT32 /* uint32 */ },
|
||||
{ no: 6, name: "time", kind: "scalar", T: ScalarType.UINT32 /* uint32 */ },
|
||||
{ no: 12, name: "msgId", kind: "scalar", T: ScalarType.UINT64 /* uint64 */ },
|
||||
{ no: 13, name: "other", kind: "scalar", T: ScalarType.UINT32 /* uint32 */ }
|
||||
]);
|
||||
|
||||
export const SysMessageBodyWrapper = new MessageType("SysMessageBodyWrapper", [
|
||||
{ no: 2, name: "wrappedBody", kind: "scalar", T: ScalarType.BYTES /* bytes */ }
|
||||
]);
|
||||
|
||||
export const SysMessage = new MessageType("SysMessage", [
|
||||
{ no: 1, name: "header", kind: "message", T: () => SysMessageHeader, repeat: RepeatType.UNPACKED },
|
||||
{ no: 2, name: "msgSpec", kind: "message", T: () => SysMessageMsgSpec, repeat: RepeatType.UNPACKED },
|
||||
{ no: 3, name: "bodyWrapper", kind: "message", T: () => SysMessageBodyWrapper }
|
||||
]);
|
||||
|
||||
export function decodeProfileLikeTip(buffer: Uint8Array): any {
|
||||
const reader = new BinaryReader(buffer);
|
||||
return ProfileLikeTip.internalBinaryRead(reader, reader.len, { readUnknownField: true, readerFactory: () => new BinaryReader(buffer) });
|
||||
}
|
||||
|
||||
export function decodeSysMessage(buffer: Uint8Array): any {
|
||||
const reader = new BinaryReader(buffer);
|
||||
return SysMessage.internalBinaryRead(reader, reader.len, { readUnknownField: true, readerFactory: () => new BinaryReader(buffer) });
|
||||
}
|
18
src/core/packet/session.ts
Normal file
18
src/core/packet/session.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { PacketClient } from "@/core/packet/client";
|
||||
import { PacketHighwaySession } from "@/core/packet/highway/session";
|
||||
import { LogWrapper } from "@/common/log";
|
||||
import {PacketPacker} from "@/core/packet/packer";
|
||||
|
||||
export class PacketSession {
|
||||
readonly logger: LogWrapper;
|
||||
readonly client: PacketClient;
|
||||
readonly packer: PacketPacker;
|
||||
readonly highwaySession: PacketHighwaySession;
|
||||
|
||||
constructor(logger: LogWrapper, client: PacketClient) {
|
||||
this.logger = logger;
|
||||
this.client = client;
|
||||
this.packer = new PacketPacker(this.logger, this.client);
|
||||
this.highwaySession = new PacketHighwaySession(this.logger, this.client, this.packer);
|
||||
}
|
||||
}
|
16
src/core/packet/utils/crypto/hash.ts
Normal file
16
src/core/packet/utils/crypto/hash.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
// love from https://github.com/LagrangeDev/lagrangejs & https://github.com/takayama-lily/oicq
|
||||
import * as crypto from 'crypto';
|
||||
import * as stream from 'stream';
|
||||
import * as fs from 'fs';
|
||||
|
||||
function sha1Stream(readable: stream.Readable) {
|
||||
return new Promise((resolve, reject) => {
|
||||
readable.on('error', reject);
|
||||
readable.pipe(crypto.createHash('sha1').on('error', reject).on('data', resolve));
|
||||
}) as Promise<Buffer>;
|
||||
}
|
||||
|
||||
export function calculateSha1(filePath: string): Promise<Buffer> {
|
||||
const readable = fs.createReadStream(filePath);
|
||||
return sha1Stream(readable);
|
||||
}
|
86
src/core/packet/utils/crypto/tea.ts
Normal file
86
src/core/packet/utils/crypto/tea.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
// love from https://github.com/LagrangeDev/lagrangejs/blob/main/src/core/tea.ts & https://github.com/takayama-lily/oicq/blob/main/lib/core/tea.ts
|
||||
const BUF7 = Buffer.alloc(7);
|
||||
const deltas = [
|
||||
0x9e3779b9, 0x3c6ef372, 0xdaa66d2b, 0x78dde6e4, 0x1715609d, 0xb54cda56, 0x5384540f, 0xf1bbcdc8, 0x8ff34781,
|
||||
0x2e2ac13a, 0xcc623af3, 0x6a99b4ac, 0x08d12e65, 0xa708a81e, 0x454021d7, 0xe3779b90,
|
||||
];
|
||||
|
||||
function _toUInt32(num: number) {
|
||||
return num >>> 0;
|
||||
}
|
||||
|
||||
function _encrypt(x: number, y: number, k0: number, k1: number, k2: number, k3: number): [number, number] {
|
||||
for (let i = 0; i < 16; ++i) {
|
||||
let aa = ((_toUInt32(((y << 4) >>> 0) + k0) ^ _toUInt32(y + deltas[i])) >>> 0) ^ _toUInt32(~~(y / 32) + k1);
|
||||
aa >>>= 0;
|
||||
x = _toUInt32(x + aa);
|
||||
let bb = ((_toUInt32(((x << 4) >>> 0) + k2) ^ _toUInt32(x + deltas[i])) >>> 0) ^ _toUInt32(~~(x / 32) + k3);
|
||||
bb >>>= 0;
|
||||
y = _toUInt32(y + bb);
|
||||
}
|
||||
return [x, y];
|
||||
}
|
||||
|
||||
export function encrypt(data: Buffer, key: Buffer) {
|
||||
let n = (6 - data.length) >>> 0;
|
||||
n = (n % 8) + 2;
|
||||
const v = Buffer.concat([Buffer.from([(n - 2) | 0xf8]), Buffer.allocUnsafe(n), data, BUF7]);
|
||||
const k0 = key.readUInt32BE(0);
|
||||
const k1 = key.readUInt32BE(4);
|
||||
const k2 = key.readUInt32BE(8);
|
||||
const k3 = key.readUInt32BE(12);
|
||||
let r1 = 0, r2 = 0, t1 = 0, t2 = 0;
|
||||
for (let i = 0; i < v.length; i += 8) {
|
||||
const a1 = v.readUInt32BE(i);
|
||||
const a2 = v.readUInt32BE(i + 4);
|
||||
const b1 = a1 ^ r1;
|
||||
const b2 = a2 ^ r2;
|
||||
const [x, y] = _encrypt(b1 >>> 0, b2 >>> 0, k0, k1, k2, k3);
|
||||
r1 = x ^ t1;
|
||||
r2 = y ^ t2;
|
||||
t1 = b1;
|
||||
t2 = b2;
|
||||
v.writeInt32BE(r1, i);
|
||||
v.writeInt32BE(r2, i + 4);
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
function _decrypt(x: number, y: number, k0: number, k1: number, k2: number, k3: number) {
|
||||
for (let i = 15; i >= 0; --i) {
|
||||
const aa = ((_toUInt32(((x << 4) >>> 0) + k2) ^ _toUInt32(x + deltas[i])) >>> 0) ^ _toUInt32(~~(x / 32) + k3);
|
||||
y = (y - aa) >>> 0;
|
||||
const bb = ((_toUInt32(((y << 4) >>> 0) + k0) ^ _toUInt32(y + deltas[i])) >>> 0) ^ _toUInt32(~~(y / 32) + k1);
|
||||
x = (x - bb) >>> 0;
|
||||
}
|
||||
return [x, y];
|
||||
}
|
||||
|
||||
export function decrypt(encrypted: Buffer, key: Buffer) {
|
||||
if (encrypted.length % 8) throw ERROR_ENCRYPTED_LENGTH;
|
||||
const k0 = key.readUInt32BE(0);
|
||||
const k1 = key.readUInt32BE(4);
|
||||
const k2 = key.readUInt32BE(8);
|
||||
const k3 = key.readUInt32BE(12);
|
||||
let r1 = 0, r2 = 0, t1 = 0, t2 = 0, x = 0, y = 0;
|
||||
for (let i = 0; i < encrypted.length; i += 8) {
|
||||
const a1 = encrypted.readUInt32BE(i);
|
||||
const a2 = encrypted.readUInt32BE(i + 4);
|
||||
const b1 = a1 ^ x;
|
||||
const b2 = a2 ^ y;
|
||||
[x, y] = _decrypt(b1 >>> 0, b2 >>> 0, k0, k1, k2, k3);
|
||||
r1 = x ^ t1;
|
||||
r2 = y ^ t2;
|
||||
t1 = a1;
|
||||
t2 = a2;
|
||||
encrypted.writeInt32BE(r1, i);
|
||||
encrypted.writeInt32BE(r2, i + 4);
|
||||
}
|
||||
if (Buffer.compare(encrypted.subarray(encrypted.length - 7), BUF7) !== 0) throw ERROR_ENCRYPTED_ILLEGAL
|
||||
// if (Buffer.compare(encrypted.slice(encrypted.length - 7), BUF7) !== 0) throw ERROR_ENCRYPTED_ILLEGAL;
|
||||
return encrypted.subarray((encrypted[0] & 0x07) + 3, encrypted.length - 7);
|
||||
// return encrypted.slice((encrypted[0] & 0x07) + 3, encrypted.length - 7);
|
||||
}
|
||||
|
||||
const ERROR_ENCRYPTED_LENGTH = new Error('length of encrypted data must be a multiple of 8');
|
||||
const ERROR_ENCRYPTED_ILLEGAL = new Error('encrypted data is illegal');
|
@@ -1,341 +0,0 @@
|
||||
// @generated by protobuf-ts 2.9.4
|
||||
// @generated from protobuf file "EmojiLikeToOthers.proto" (package "SysMessage", syntax proto3)
|
||||
// tslint:disable
|
||||
import type {
|
||||
BinaryReadOptions,
|
||||
BinaryWriteOptions,
|
||||
IBinaryReader,
|
||||
IBinaryWriter,
|
||||
PartialMessage,
|
||||
} from '@protobuf-ts/runtime';
|
||||
import { MessageType, reflectionMergePartial, UnknownFieldHandler, WireType } from '@protobuf-ts/runtime';
|
||||
|
||||
/**
|
||||
* @generated from protobuf message SysMessage.EmojiLikeToOthersWrapper1
|
||||
*/
|
||||
export interface EmojiLikeToOthersWrapper1 {
|
||||
/**
|
||||
* @generated from protobuf field: SysMessage.EmojiLikeToOthersWrapper2 wrapper = 1;
|
||||
*/
|
||||
wrapper?: EmojiLikeToOthersWrapper2;
|
||||
}
|
||||
/**
|
||||
* @generated from protobuf message SysMessage.EmojiLikeToOthersWrapper2
|
||||
*/
|
||||
export interface EmojiLikeToOthersWrapper2 {
|
||||
/**
|
||||
* @generated from protobuf field: SysMessage.EmojiLikeToOthersWrapper3 body = 1;
|
||||
*/
|
||||
body?: EmojiLikeToOthersWrapper3;
|
||||
}
|
||||
/**
|
||||
* @generated from protobuf message SysMessage.EmojiLikeToOthersWrapper3
|
||||
*/
|
||||
export interface EmojiLikeToOthersWrapper3 {
|
||||
/**
|
||||
* @generated from protobuf field: SysMessage.EmojiLikeToOthersMsgSpec msgSpec = 2;
|
||||
*/
|
||||
msgSpec?: EmojiLikeToOthersMsgSpec;
|
||||
/**
|
||||
* @generated from protobuf field: SysMessage.EmojiLikeToOthersAttributes attributes = 3;
|
||||
*/
|
||||
attributes?: EmojiLikeToOthersAttributes;
|
||||
}
|
||||
/**
|
||||
* @generated from protobuf message SysMessage.EmojiLikeToOthersMsgSpec
|
||||
*/
|
||||
export interface EmojiLikeToOthersMsgSpec {
|
||||
/**
|
||||
* @generated from protobuf field: uint32 msgSeq = 1;
|
||||
*/
|
||||
msgSeq: number;
|
||||
}
|
||||
/**
|
||||
* @generated from protobuf message SysMessage.EmojiLikeToOthersAttributes
|
||||
*/
|
||||
export interface EmojiLikeToOthersAttributes {
|
||||
/**
|
||||
* @generated from protobuf field: string emojiId = 1;
|
||||
*/
|
||||
emojiId: string;
|
||||
/**
|
||||
* @generated from protobuf field: string senderUid = 4;
|
||||
*/
|
||||
senderUid: string;
|
||||
/**
|
||||
* @generated from protobuf field: SysMessage.EmojiLikeToOthersAttributes.Operation operation = 5;
|
||||
*/
|
||||
operation: EmojiLikeToOthersAttributes_Operation;
|
||||
}
|
||||
/**
|
||||
* @generated from protobuf enum SysMessage.EmojiLikeToOthersAttributes.Operation
|
||||
*/
|
||||
export enum EmojiLikeToOthersAttributes_Operation {
|
||||
/**
|
||||
* @generated from protobuf enum value: FALLBACK = 0;
|
||||
*/
|
||||
FALLBACK = 0,
|
||||
/**
|
||||
* @generated from protobuf enum value: LIKE = 1;
|
||||
*/
|
||||
LIKE = 1,
|
||||
/**
|
||||
* @generated from protobuf enum value: UNLIKE = 2;
|
||||
*/
|
||||
UNLIKE = 2
|
||||
}
|
||||
// @generated message type with reflection information, may provide speed optimized methods
|
||||
class EmojiLikeToOthersWrapper1$Type extends MessageType<EmojiLikeToOthersWrapper1> {
|
||||
constructor() {
|
||||
super("SysMessage.EmojiLikeToOthersWrapper1", [
|
||||
{ no: 1, name: "wrapper", kind: "message", T: () => EmojiLikeToOthersWrapper2 }
|
||||
]);
|
||||
}
|
||||
create(value?: PartialMessage<EmojiLikeToOthersWrapper1>): EmojiLikeToOthersWrapper1 {
|
||||
const message = globalThis.Object.create((this.messagePrototype!));
|
||||
if (value !== undefined)
|
||||
reflectionMergePartial<EmojiLikeToOthersWrapper1>(this, message, value);
|
||||
return message;
|
||||
}
|
||||
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: EmojiLikeToOthersWrapper1): EmojiLikeToOthersWrapper1 {
|
||||
let message = target ?? this.create(), end = reader.pos + length;
|
||||
while (reader.pos < end) {
|
||||
let [fieldNo, wireType] = reader.tag();
|
||||
switch (fieldNo) {
|
||||
case /* SysMessage.EmojiLikeToOthersWrapper2 wrapper */ 1:
|
||||
message.wrapper = EmojiLikeToOthersWrapper2.internalBinaryRead(reader, reader.uint32(), options, message.wrapper);
|
||||
break;
|
||||
default:
|
||||
let u = options.readUnknownField;
|
||||
if (u === "throw")
|
||||
throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
|
||||
let d = reader.skip(wireType);
|
||||
if (u !== false)
|
||||
(u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
|
||||
}
|
||||
}
|
||||
return message;
|
||||
}
|
||||
internalBinaryWrite(message: EmojiLikeToOthersWrapper1, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
|
||||
/* SysMessage.EmojiLikeToOthersWrapper2 wrapper = 1; */
|
||||
if (message.wrapper)
|
||||
EmojiLikeToOthersWrapper2.internalBinaryWrite(message.wrapper, writer.tag(1, WireType.LengthDelimited).fork(), options).join();
|
||||
let u = options.writeUnknownFields;
|
||||
if (u !== false)
|
||||
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
|
||||
return writer;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @generated MessageType for protobuf message SysMessage.EmojiLikeToOthersWrapper1
|
||||
*/
|
||||
export const EmojiLikeToOthersWrapper1 = new EmojiLikeToOthersWrapper1$Type();
|
||||
// @generated message type with reflection information, may provide speed optimized methods
|
||||
class EmojiLikeToOthersWrapper2$Type extends MessageType<EmojiLikeToOthersWrapper2> {
|
||||
constructor() {
|
||||
super("SysMessage.EmojiLikeToOthersWrapper2", [
|
||||
{ no: 1, name: "body", kind: "message", T: () => EmojiLikeToOthersWrapper3 }
|
||||
]);
|
||||
}
|
||||
create(value?: PartialMessage<EmojiLikeToOthersWrapper2>): EmojiLikeToOthersWrapper2 {
|
||||
const message = globalThis.Object.create((this.messagePrototype!));
|
||||
if (value !== undefined)
|
||||
reflectionMergePartial<EmojiLikeToOthersWrapper2>(this, message, value);
|
||||
return message;
|
||||
}
|
||||
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: EmojiLikeToOthersWrapper2): EmojiLikeToOthersWrapper2 {
|
||||
let message = target ?? this.create(), end = reader.pos + length;
|
||||
while (reader.pos < end) {
|
||||
let [fieldNo, wireType] = reader.tag();
|
||||
switch (fieldNo) {
|
||||
case /* SysMessage.EmojiLikeToOthersWrapper3 body */ 1:
|
||||
message.body = EmojiLikeToOthersWrapper3.internalBinaryRead(reader, reader.uint32(), options, message.body);
|
||||
break;
|
||||
default:
|
||||
let u = options.readUnknownField;
|
||||
if (u === "throw")
|
||||
throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
|
||||
let d = reader.skip(wireType);
|
||||
if (u !== false)
|
||||
(u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
|
||||
}
|
||||
}
|
||||
return message;
|
||||
}
|
||||
internalBinaryWrite(message: EmojiLikeToOthersWrapper2, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
|
||||
/* SysMessage.EmojiLikeToOthersWrapper3 body = 1; */
|
||||
if (message.body)
|
||||
EmojiLikeToOthersWrapper3.internalBinaryWrite(message.body, writer.tag(1, WireType.LengthDelimited).fork(), options).join();
|
||||
let u = options.writeUnknownFields;
|
||||
if (u !== false)
|
||||
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
|
||||
return writer;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @generated MessageType for protobuf message SysMessage.EmojiLikeToOthersWrapper2
|
||||
*/
|
||||
export const EmojiLikeToOthersWrapper2 = new EmojiLikeToOthersWrapper2$Type();
|
||||
// @generated message type with reflection information, may provide speed optimized methods
|
||||
class EmojiLikeToOthersWrapper3$Type extends MessageType<EmojiLikeToOthersWrapper3> {
|
||||
constructor() {
|
||||
super("SysMessage.EmojiLikeToOthersWrapper3", [
|
||||
{ no: 2, name: "msgSpec", kind: "message", T: () => EmojiLikeToOthersMsgSpec },
|
||||
{ no: 3, name: "attributes", kind: "message", T: () => EmojiLikeToOthersAttributes }
|
||||
]);
|
||||
}
|
||||
create(value?: PartialMessage<EmojiLikeToOthersWrapper3>): EmojiLikeToOthersWrapper3 {
|
||||
const message = globalThis.Object.create((this.messagePrototype!));
|
||||
if (value !== undefined)
|
||||
reflectionMergePartial<EmojiLikeToOthersWrapper3>(this, message, value);
|
||||
return message;
|
||||
}
|
||||
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: EmojiLikeToOthersWrapper3): EmojiLikeToOthersWrapper3 {
|
||||
let message = target ?? this.create(), end = reader.pos + length;
|
||||
while (reader.pos < end) {
|
||||
let [fieldNo, wireType] = reader.tag();
|
||||
switch (fieldNo) {
|
||||
case /* SysMessage.EmojiLikeToOthersMsgSpec msgSpec */ 2:
|
||||
message.msgSpec = EmojiLikeToOthersMsgSpec.internalBinaryRead(reader, reader.uint32(), options, message.msgSpec);
|
||||
break;
|
||||
case /* SysMessage.EmojiLikeToOthersAttributes attributes */ 3:
|
||||
message.attributes = EmojiLikeToOthersAttributes.internalBinaryRead(reader, reader.uint32(), options, message.attributes);
|
||||
break;
|
||||
default:
|
||||
let u = options.readUnknownField;
|
||||
if (u === "throw")
|
||||
throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
|
||||
let d = reader.skip(wireType);
|
||||
if (u !== false)
|
||||
(u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
|
||||
}
|
||||
}
|
||||
return message;
|
||||
}
|
||||
internalBinaryWrite(message: EmojiLikeToOthersWrapper3, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
|
||||
/* SysMessage.EmojiLikeToOthersMsgSpec msgSpec = 2; */
|
||||
if (message.msgSpec)
|
||||
EmojiLikeToOthersMsgSpec.internalBinaryWrite(message.msgSpec, writer.tag(2, WireType.LengthDelimited).fork(), options).join();
|
||||
/* SysMessage.EmojiLikeToOthersAttributes attributes = 3; */
|
||||
if (message.attributes)
|
||||
EmojiLikeToOthersAttributes.internalBinaryWrite(message.attributes, writer.tag(3, WireType.LengthDelimited).fork(), options).join();
|
||||
let u = options.writeUnknownFields;
|
||||
if (u !== false)
|
||||
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
|
||||
return writer;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @generated MessageType for protobuf message SysMessage.EmojiLikeToOthersWrapper3
|
||||
*/
|
||||
export const EmojiLikeToOthersWrapper3 = new EmojiLikeToOthersWrapper3$Type();
|
||||
// @generated message type with reflection information, may provide speed optimized methods
|
||||
class EmojiLikeToOthersMsgSpec$Type extends MessageType<EmojiLikeToOthersMsgSpec> {
|
||||
constructor() {
|
||||
super("SysMessage.EmojiLikeToOthersMsgSpec", [
|
||||
{ no: 1, name: "msgSeq", kind: "scalar", T: 13 /*ScalarType.UINT32*/ }
|
||||
]);
|
||||
}
|
||||
create(value?: PartialMessage<EmojiLikeToOthersMsgSpec>): EmojiLikeToOthersMsgSpec {
|
||||
const message = globalThis.Object.create((this.messagePrototype!));
|
||||
message.msgSeq = 0;
|
||||
if (value !== undefined)
|
||||
reflectionMergePartial<EmojiLikeToOthersMsgSpec>(this, message, value);
|
||||
return message;
|
||||
}
|
||||
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: EmojiLikeToOthersMsgSpec): EmojiLikeToOthersMsgSpec {
|
||||
let message = target ?? this.create(), end = reader.pos + length;
|
||||
while (reader.pos < end) {
|
||||
let [fieldNo, wireType] = reader.tag();
|
||||
switch (fieldNo) {
|
||||
case /* uint32 msgSeq */ 1:
|
||||
message.msgSeq = reader.uint32();
|
||||
break;
|
||||
default:
|
||||
let u = options.readUnknownField;
|
||||
if (u === "throw")
|
||||
throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
|
||||
let d = reader.skip(wireType);
|
||||
if (u !== false)
|
||||
(u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
|
||||
}
|
||||
}
|
||||
return message;
|
||||
}
|
||||
internalBinaryWrite(message: EmojiLikeToOthersMsgSpec, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
|
||||
/* uint32 msgSeq = 1; */
|
||||
if (message.msgSeq !== 0)
|
||||
writer.tag(1, WireType.Varint).uint32(message.msgSeq);
|
||||
let u = options.writeUnknownFields;
|
||||
if (u !== false)
|
||||
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
|
||||
return writer;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @generated MessageType for protobuf message SysMessage.EmojiLikeToOthersMsgSpec
|
||||
*/
|
||||
export const EmojiLikeToOthersMsgSpec = new EmojiLikeToOthersMsgSpec$Type();
|
||||
// @generated message type with reflection information, may provide speed optimized methods
|
||||
class EmojiLikeToOthersAttributes$Type extends MessageType<EmojiLikeToOthersAttributes> {
|
||||
constructor() {
|
||||
super("SysMessage.EmojiLikeToOthersAttributes", [
|
||||
{ no: 1, name: "emojiId", kind: "scalar", T: 9 /*ScalarType.STRING*/ },
|
||||
{ no: 4, name: "senderUid", kind: "scalar", T: 9 /*ScalarType.STRING*/ },
|
||||
{ no: 5, name: "operation", kind: "enum", T: () => ["SysMessage.EmojiLikeToOthersAttributes.Operation", EmojiLikeToOthersAttributes_Operation] }
|
||||
]);
|
||||
}
|
||||
create(value?: PartialMessage<EmojiLikeToOthersAttributes>): EmojiLikeToOthersAttributes {
|
||||
const message = globalThis.Object.create((this.messagePrototype!));
|
||||
message.emojiId = "";
|
||||
message.senderUid = "";
|
||||
message.operation = 0;
|
||||
if (value !== undefined)
|
||||
reflectionMergePartial<EmojiLikeToOthersAttributes>(this, message, value);
|
||||
return message;
|
||||
}
|
||||
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: EmojiLikeToOthersAttributes): EmojiLikeToOthersAttributes {
|
||||
let message = target ?? this.create(), end = reader.pos + length;
|
||||
while (reader.pos < end) {
|
||||
let [fieldNo, wireType] = reader.tag();
|
||||
switch (fieldNo) {
|
||||
case /* string emojiId */ 1:
|
||||
message.emojiId = reader.string();
|
||||
break;
|
||||
case /* string senderUid */ 4:
|
||||
message.senderUid = reader.string();
|
||||
break;
|
||||
case /* SysMessage.EmojiLikeToOthersAttributes.Operation operation */ 5:
|
||||
message.operation = reader.int32();
|
||||
break;
|
||||
default:
|
||||
let u = options.readUnknownField;
|
||||
if (u === "throw")
|
||||
throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
|
||||
let d = reader.skip(wireType);
|
||||
if (u !== false)
|
||||
(u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
|
||||
}
|
||||
}
|
||||
return message;
|
||||
}
|
||||
internalBinaryWrite(message: EmojiLikeToOthersAttributes, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
|
||||
/* string emojiId = 1; */
|
||||
if (message.emojiId !== "")
|
||||
writer.tag(1, WireType.LengthDelimited).string(message.emojiId);
|
||||
/* string senderUid = 4; */
|
||||
if (message.senderUid !== "")
|
||||
writer.tag(4, WireType.LengthDelimited).string(message.senderUid);
|
||||
/* SysMessage.EmojiLikeToOthersAttributes.Operation operation = 5; */
|
||||
if (message.operation !== 0)
|
||||
writer.tag(5, WireType.Varint).int32(message.operation);
|
||||
let u = options.writeUnknownFields;
|
||||
if (u !== false)
|
||||
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
|
||||
return writer;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @generated MessageType for protobuf message SysMessage.EmojiLikeToOthersAttributes
|
||||
*/
|
||||
export const EmojiLikeToOthersAttributes = new EmojiLikeToOthersAttributes$Type();
|
@@ -1,104 +0,0 @@
|
||||
// @generated by protobuf-ts 2.9.4
|
||||
// @generated from protobuf file "GreyTipWrapper.proto" (package "SysMessage", syntax proto3)
|
||||
// tslint:disable
|
||||
import type {
|
||||
BinaryReadOptions,
|
||||
BinaryWriteOptions,
|
||||
IBinaryReader,
|
||||
IBinaryWriter,
|
||||
PartialMessage,
|
||||
} from '@protobuf-ts/runtime';
|
||||
import { MessageType, reflectionMergePartial, UnknownFieldHandler, WireType } from '@protobuf-ts/runtime';
|
||||
|
||||
/**
|
||||
* @generated from protobuf message SysMessage.GreyTipWrapper
|
||||
*/
|
||||
export interface GreyTipWrapper {
|
||||
/**
|
||||
* @generated from protobuf field: uint32 subTypeId = 1;
|
||||
*/
|
||||
subTypeId: number;
|
||||
/**
|
||||
* @generated from protobuf field: uint32 groupCode = 4;
|
||||
*/
|
||||
groupCode: number;
|
||||
/**
|
||||
* @generated from protobuf field: uint32 subTypeIdMinusOne = 13;
|
||||
*/
|
||||
subTypeIdMinusOne: number;
|
||||
/**
|
||||
* @generated from protobuf field: bytes rest = 44;
|
||||
*/
|
||||
rest: Uint8Array;
|
||||
}
|
||||
// @generated message type with reflection information, may provide speed optimized methods
|
||||
class GreyTipWrapper$Type extends MessageType<GreyTipWrapper> {
|
||||
constructor() {
|
||||
super("SysMessage.GreyTipWrapper", [
|
||||
{ no: 1, name: "subTypeId", kind: "scalar", T: 13 /*ScalarType.UINT32*/ },
|
||||
{ no: 4, name: "groupCode", kind: "scalar", T: 13 /*ScalarType.UINT32*/ },
|
||||
{ no: 13, name: "subTypeIdMinusOne", kind: "scalar", T: 13 /*ScalarType.UINT32*/ },
|
||||
{ no: 44, name: "rest", kind: "scalar", T: 12 /*ScalarType.BYTES*/ }
|
||||
]);
|
||||
}
|
||||
create(value?: PartialMessage<GreyTipWrapper>): GreyTipWrapper {
|
||||
const message = globalThis.Object.create((this.messagePrototype!));
|
||||
message.subTypeId = 0;
|
||||
message.groupCode = 0;
|
||||
message.subTypeIdMinusOne = 0;
|
||||
message.rest = new Uint8Array(0);
|
||||
if (value !== undefined)
|
||||
reflectionMergePartial<GreyTipWrapper>(this, message, value);
|
||||
return message;
|
||||
}
|
||||
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: GreyTipWrapper): GreyTipWrapper {
|
||||
let message = target ?? this.create(), end = reader.pos + length;
|
||||
while (reader.pos < end) {
|
||||
let [fieldNo, wireType] = reader.tag();
|
||||
switch (fieldNo) {
|
||||
case /* uint32 subTypeId */ 1:
|
||||
message.subTypeId = reader.uint32();
|
||||
break;
|
||||
case /* uint32 groupCode */ 4:
|
||||
message.groupCode = reader.uint32();
|
||||
break;
|
||||
case /* uint32 subTypeIdMinusOne */ 13:
|
||||
message.subTypeIdMinusOne = reader.uint32();
|
||||
break;
|
||||
case /* bytes rest */ 44:
|
||||
message.rest = reader.bytes();
|
||||
break;
|
||||
default:
|
||||
let u = options.readUnknownField;
|
||||
if (u === "throw")
|
||||
throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
|
||||
let d = reader.skip(wireType);
|
||||
if (u !== false)
|
||||
(u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
|
||||
}
|
||||
}
|
||||
return message;
|
||||
}
|
||||
internalBinaryWrite(message: GreyTipWrapper, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
|
||||
/* uint32 subTypeId = 1; */
|
||||
if (message.subTypeId !== 0)
|
||||
writer.tag(1, WireType.Varint).uint32(message.subTypeId);
|
||||
/* uint32 groupCode = 4; */
|
||||
if (message.groupCode !== 0)
|
||||
writer.tag(4, WireType.Varint).uint32(message.groupCode);
|
||||
/* uint32 subTypeIdMinusOne = 13; */
|
||||
if (message.subTypeIdMinusOne !== 0)
|
||||
writer.tag(13, WireType.Varint).uint32(message.subTypeIdMinusOne);
|
||||
/* bytes rest = 44; */
|
||||
if (message.rest.length)
|
||||
writer.tag(44, WireType.LengthDelimited).bytes(message.rest);
|
||||
let u = options.writeUnknownFields;
|
||||
if (u !== false)
|
||||
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
|
||||
return writer;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @generated MessageType for protobuf message SysMessage.GreyTipWrapper
|
||||
*/
|
||||
export const GreyTipWrapper = new GreyTipWrapper$Type();
|
@@ -1,435 +0,0 @@
|
||||
// @generated by protobuf-ts 2.9.4
|
||||
// @generated from protobuf file "SysMessage.proto" (package "SysMessage", syntax proto3)
|
||||
// tslint:disable
|
||||
import type {
|
||||
BinaryReadOptions,
|
||||
BinaryWriteOptions,
|
||||
IBinaryReader,
|
||||
IBinaryWriter,
|
||||
PartialMessage,
|
||||
} from '@protobuf-ts/runtime';
|
||||
import { MessageType, reflectionMergePartial, UnknownFieldHandler, WireType } from '@protobuf-ts/runtime';
|
||||
|
||||
/**
|
||||
* @generated from protobuf message SysMessage.SysMessage
|
||||
*/
|
||||
export interface SysMessage {
|
||||
/**
|
||||
* @generated from protobuf field: repeated SysMessage.SysMessageHeader header = 1;
|
||||
*/
|
||||
header: SysMessageHeader[];
|
||||
/**
|
||||
* @generated from protobuf field: repeated SysMessage.SysMessageMsgSpec msgSpec = 2;
|
||||
*/
|
||||
msgSpec: SysMessageMsgSpec[];
|
||||
/**
|
||||
* @generated from protobuf field: SysMessage.SysMessageBodyWrapper bodyWrapper = 3;
|
||||
*/
|
||||
bodyWrapper?: SysMessageBodyWrapper;
|
||||
}
|
||||
/**
|
||||
* @generated from protobuf message SysMessage.SysMessageHeader
|
||||
*/
|
||||
export interface SysMessageHeader {
|
||||
/**
|
||||
* @generated from protobuf field: uint32 PeerNumber = 1 [json_name = "PeerNumber"];
|
||||
*/
|
||||
peerNumber: number;
|
||||
/**
|
||||
* @generated from protobuf field: string PeerString = 2 [json_name = "PeerString"];
|
||||
*/
|
||||
peerString: string;
|
||||
/**
|
||||
* @generated from protobuf field: uint32 Uin = 5 [json_name = "Uin"];
|
||||
*/
|
||||
uin: number;
|
||||
/**
|
||||
* @generated from protobuf field: optional string Uid = 6 [json_name = "Uid"];
|
||||
*/
|
||||
uid?: string;
|
||||
}
|
||||
/**
|
||||
* @generated from protobuf message SysMessage.SysMessageMsgSpec
|
||||
*/
|
||||
export interface SysMessageMsgSpec {
|
||||
/**
|
||||
* @generated from protobuf field: uint32 msgType = 1;
|
||||
*/
|
||||
msgType: number;
|
||||
/**
|
||||
* @generated from protobuf field: uint32 subType = 2;
|
||||
*/
|
||||
subType: number;
|
||||
/**
|
||||
* @generated from protobuf field: uint32 subSubType = 3;
|
||||
*/
|
||||
subSubType: number;
|
||||
/**
|
||||
* @generated from protobuf field: uint32 msgSeq = 5;
|
||||
*/
|
||||
msgSeq: number;
|
||||
/**
|
||||
* @generated from protobuf field: uint32 time = 6;
|
||||
*/
|
||||
time: number;
|
||||
/**
|
||||
* @generated from protobuf field: uint64 msgId = 12;
|
||||
*/
|
||||
msgId: bigint;
|
||||
/**
|
||||
* @generated from protobuf field: uint32 other = 13;
|
||||
*/
|
||||
other: number;
|
||||
}
|
||||
/**
|
||||
* @generated from protobuf message SysMessage.SysMessageBodyWrapper
|
||||
*/
|
||||
export interface SysMessageBodyWrapper {
|
||||
/**
|
||||
* @generated from protobuf field: bytes wrappedBody = 2;
|
||||
*/
|
||||
wrappedBody: Uint8Array; // Find the first [08], or ignore the first 7 bytes?
|
||||
// And it becomes another ProtoBuf message.
|
||||
}
|
||||
/**
|
||||
* @generated from protobuf message SysMessage.KeyValuePair
|
||||
*/
|
||||
export interface KeyValuePair {
|
||||
/**
|
||||
* @generated from protobuf field: string key = 1;
|
||||
*/
|
||||
key: string;
|
||||
/**
|
||||
* @generated from protobuf field: string value = 2;
|
||||
*/
|
||||
value: string;
|
||||
}
|
||||
// @generated message type with reflection information, may provide speed optimized methods
|
||||
class SysMessage$Type extends MessageType<SysMessage> {
|
||||
constructor() {
|
||||
super("SysMessage.SysMessage", [
|
||||
{ no: 1, name: "header", kind: "message", repeat: 1 /*RepeatType.PACKED*/, T: () => SysMessageHeader },
|
||||
{ no: 2, name: "msgSpec", kind: "message", repeat: 1 /*RepeatType.PACKED*/, T: () => SysMessageMsgSpec },
|
||||
{ no: 3, name: "bodyWrapper", kind: "message", T: () => SysMessageBodyWrapper }
|
||||
]);
|
||||
}
|
||||
create(value?: PartialMessage<SysMessage>): SysMessage {
|
||||
const message = globalThis.Object.create((this.messagePrototype!));
|
||||
message.header = [];
|
||||
message.msgSpec = [];
|
||||
if (value !== undefined)
|
||||
reflectionMergePartial<SysMessage>(this, message, value);
|
||||
return message;
|
||||
}
|
||||
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: SysMessage): SysMessage {
|
||||
let message = target ?? this.create(), end = reader.pos + length;
|
||||
while (reader.pos < end) {
|
||||
let [fieldNo, wireType] = reader.tag();
|
||||
switch (fieldNo) {
|
||||
case /* repeated SysMessage.SysMessageHeader header */ 1:
|
||||
message.header.push(SysMessageHeader.internalBinaryRead(reader, reader.uint32(), options));
|
||||
break;
|
||||
case /* repeated SysMessage.SysMessageMsgSpec msgSpec */ 2:
|
||||
message.msgSpec.push(SysMessageMsgSpec.internalBinaryRead(reader, reader.uint32(), options));
|
||||
break;
|
||||
case /* SysMessage.SysMessageBodyWrapper bodyWrapper */ 3:
|
||||
message.bodyWrapper = SysMessageBodyWrapper.internalBinaryRead(reader, reader.uint32(), options, message.bodyWrapper);
|
||||
break;
|
||||
default:
|
||||
let u = options.readUnknownField;
|
||||
if (u === "throw")
|
||||
throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
|
||||
let d = reader.skip(wireType);
|
||||
if (u !== false)
|
||||
(u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
|
||||
}
|
||||
}
|
||||
return message;
|
||||
}
|
||||
internalBinaryWrite(message: SysMessage, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
|
||||
/* repeated SysMessage.SysMessageHeader header = 1; */
|
||||
for (let i = 0; i < message.header.length; i++)
|
||||
SysMessageHeader.internalBinaryWrite(message.header[i], writer.tag(1, WireType.LengthDelimited).fork(), options).join();
|
||||
/* repeated SysMessage.SysMessageMsgSpec msgSpec = 2; */
|
||||
for (let i = 0; i < message.msgSpec.length; i++)
|
||||
SysMessageMsgSpec.internalBinaryWrite(message.msgSpec[i], writer.tag(2, WireType.LengthDelimited).fork(), options).join();
|
||||
/* SysMessage.SysMessageBodyWrapper bodyWrapper = 3; */
|
||||
if (message.bodyWrapper)
|
||||
SysMessageBodyWrapper.internalBinaryWrite(message.bodyWrapper, writer.tag(3, WireType.LengthDelimited).fork(), options).join();
|
||||
let u = options.writeUnknownFields;
|
||||
if (u !== false)
|
||||
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
|
||||
return writer;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @generated MessageType for protobuf message SysMessage.SysMessage
|
||||
*/
|
||||
export const SysMessage = new SysMessage$Type();
|
||||
// @generated message type with reflection information, may provide speed optimized methods
|
||||
class SysMessageHeader$Type extends MessageType<SysMessageHeader> {
|
||||
constructor() {
|
||||
super("SysMessage.SysMessageHeader", [
|
||||
{ no: 1, name: "PeerNumber", kind: "scalar", jsonName: "PeerNumber", T: 13 /*ScalarType.UINT32*/ },
|
||||
{ no: 2, name: "PeerString", kind: "scalar", jsonName: "PeerString", T: 9 /*ScalarType.STRING*/ },
|
||||
{ no: 5, name: "Uin", kind: "scalar", jsonName: "Uin", T: 13 /*ScalarType.UINT32*/ },
|
||||
{ no: 6, name: "Uid", kind: "scalar", jsonName: "Uid", opt: true, T: 9 /*ScalarType.STRING*/ }
|
||||
]);
|
||||
}
|
||||
create(value?: PartialMessage<SysMessageHeader>): SysMessageHeader {
|
||||
const message = globalThis.Object.create((this.messagePrototype!));
|
||||
message.peerNumber = 0;
|
||||
message.peerString = "";
|
||||
message.uin = 0;
|
||||
if (value !== undefined)
|
||||
reflectionMergePartial<SysMessageHeader>(this, message, value);
|
||||
return message;
|
||||
}
|
||||
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: SysMessageHeader): SysMessageHeader {
|
||||
let message = target ?? this.create(), end = reader.pos + length;
|
||||
while (reader.pos < end) {
|
||||
let [fieldNo, wireType] = reader.tag();
|
||||
switch (fieldNo) {
|
||||
case /* uint32 PeerNumber = 1 [json_name = "PeerNumber"];*/ 1:
|
||||
message.peerNumber = reader.uint32();
|
||||
break;
|
||||
case /* string PeerString = 2 [json_name = "PeerString"];*/ 2:
|
||||
message.peerString = reader.string();
|
||||
break;
|
||||
case /* uint32 Uin = 5 [json_name = "Uin"];*/ 5:
|
||||
message.uin = reader.uint32();
|
||||
break;
|
||||
case /* optional string Uid = 6 [json_name = "Uid"];*/ 6:
|
||||
message.uid = reader.string();
|
||||
break;
|
||||
default:
|
||||
let u = options.readUnknownField;
|
||||
if (u === "throw")
|
||||
throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
|
||||
let d = reader.skip(wireType);
|
||||
if (u !== false)
|
||||
(u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
|
||||
}
|
||||
}
|
||||
return message;
|
||||
}
|
||||
internalBinaryWrite(message: SysMessageHeader, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
|
||||
/* uint32 PeerNumber = 1 [json_name = "PeerNumber"]; */
|
||||
if (message.peerNumber !== 0)
|
||||
writer.tag(1, WireType.Varint).uint32(message.peerNumber);
|
||||
/* string PeerString = 2 [json_name = "PeerString"]; */
|
||||
if (message.peerString !== "")
|
||||
writer.tag(2, WireType.LengthDelimited).string(message.peerString);
|
||||
/* uint32 Uin = 5 [json_name = "Uin"]; */
|
||||
if (message.uin !== 0)
|
||||
writer.tag(5, WireType.Varint).uint32(message.uin);
|
||||
/* optional string Uid = 6 [json_name = "Uid"]; */
|
||||
if (message.uid !== undefined)
|
||||
writer.tag(6, WireType.LengthDelimited).string(message.uid);
|
||||
let u = options.writeUnknownFields;
|
||||
if (u !== false)
|
||||
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
|
||||
return writer;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @generated MessageType for protobuf message SysMessage.SysMessageHeader
|
||||
*/
|
||||
export const SysMessageHeader = new SysMessageHeader$Type();
|
||||
// @generated message type with reflection information, may provide speed optimized methods
|
||||
class SysMessageMsgSpec$Type extends MessageType<SysMessageMsgSpec> {
|
||||
constructor() {
|
||||
super("SysMessage.SysMessageMsgSpec", [
|
||||
{ no: 1, name: "msgType", kind: "scalar", T: 13 /*ScalarType.UINT32*/ },
|
||||
{ no: 2, name: "subType", kind: "scalar", T: 13 /*ScalarType.UINT32*/ },
|
||||
{ no: 3, name: "subSubType", kind: "scalar", T: 13 /*ScalarType.UINT32*/ },
|
||||
{ no: 5, name: "msgSeq", kind: "scalar", T: 13 /*ScalarType.UINT32*/ },
|
||||
{ no: 6, name: "time", kind: "scalar", T: 13 /*ScalarType.UINT32*/ },
|
||||
{ no: 12, name: "msgId", kind: "scalar", T: 4 /*ScalarType.UINT64*/, L: 0 /*LongType.BIGINT*/ },
|
||||
{ no: 13, name: "other", kind: "scalar", T: 13 /*ScalarType.UINT32*/ }
|
||||
]);
|
||||
}
|
||||
create(value?: PartialMessage<SysMessageMsgSpec>): SysMessageMsgSpec {
|
||||
const message = globalThis.Object.create((this.messagePrototype!));
|
||||
message.msgType = 0;
|
||||
message.subType = 0;
|
||||
message.subSubType = 0;
|
||||
message.msgSeq = 0;
|
||||
message.time = 0;
|
||||
message.msgId = 0n;
|
||||
message.other = 0;
|
||||
if (value !== undefined)
|
||||
reflectionMergePartial<SysMessageMsgSpec>(this, message, value);
|
||||
return message;
|
||||
}
|
||||
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: SysMessageMsgSpec): SysMessageMsgSpec {
|
||||
let message = target ?? this.create(), end = reader.pos + length;
|
||||
while (reader.pos < end) {
|
||||
let [fieldNo, wireType] = reader.tag();
|
||||
switch (fieldNo) {
|
||||
case /* uint32 msgType */ 1:
|
||||
message.msgType = reader.uint32();
|
||||
break;
|
||||
case /* uint32 subType */ 2:
|
||||
message.subType = reader.uint32();
|
||||
break;
|
||||
case /* uint32 subSubType */ 3:
|
||||
message.subSubType = reader.uint32();
|
||||
break;
|
||||
case /* uint32 msgSeq */ 5:
|
||||
message.msgSeq = reader.uint32();
|
||||
break;
|
||||
case /* uint32 time */ 6:
|
||||
message.time = reader.uint32();
|
||||
break;
|
||||
case /* uint64 msgId */ 12:
|
||||
message.msgId = reader.uint64().toBigInt();
|
||||
break;
|
||||
case /* uint32 other */ 13:
|
||||
message.other = reader.uint32();
|
||||
break;
|
||||
default:
|
||||
let u = options.readUnknownField;
|
||||
if (u === "throw")
|
||||
throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
|
||||
let d = reader.skip(wireType);
|
||||
if (u !== false)
|
||||
(u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
|
||||
}
|
||||
}
|
||||
return message;
|
||||
}
|
||||
internalBinaryWrite(message: SysMessageMsgSpec, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
|
||||
/* uint32 msgType = 1; */
|
||||
if (message.msgType !== 0)
|
||||
writer.tag(1, WireType.Varint).uint32(message.msgType);
|
||||
/* uint32 subType = 2; */
|
||||
if (message.subType !== 0)
|
||||
writer.tag(2, WireType.Varint).uint32(message.subType);
|
||||
/* uint32 subSubType = 3; */
|
||||
if (message.subSubType !== 0)
|
||||
writer.tag(3, WireType.Varint).uint32(message.subSubType);
|
||||
/* uint32 msgSeq = 5; */
|
||||
if (message.msgSeq !== 0)
|
||||
writer.tag(5, WireType.Varint).uint32(message.msgSeq);
|
||||
/* uint32 time = 6; */
|
||||
if (message.time !== 0)
|
||||
writer.tag(6, WireType.Varint).uint32(message.time);
|
||||
/* uint64 msgId = 12; */
|
||||
if (message.msgId !== 0n)
|
||||
writer.tag(12, WireType.Varint).uint64(message.msgId);
|
||||
/* uint32 other = 13; */
|
||||
if (message.other !== 0)
|
||||
writer.tag(13, WireType.Varint).uint32(message.other);
|
||||
let u = options.writeUnknownFields;
|
||||
if (u !== false)
|
||||
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
|
||||
return writer;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @generated MessageType for protobuf message SysMessage.SysMessageMsgSpec
|
||||
*/
|
||||
export const SysMessageMsgSpec = new SysMessageMsgSpec$Type();
|
||||
// @generated message type with reflection information, may provide speed optimized methods
|
||||
class SysMessageBodyWrapper$Type extends MessageType<SysMessageBodyWrapper> {
|
||||
constructor() {
|
||||
super("SysMessage.SysMessageBodyWrapper", [
|
||||
{ no: 2, name: "wrappedBody", kind: "scalar", T: 12 /*ScalarType.BYTES*/ }
|
||||
]);
|
||||
}
|
||||
create(value?: PartialMessage<SysMessageBodyWrapper>): SysMessageBodyWrapper {
|
||||
const message = globalThis.Object.create((this.messagePrototype!));
|
||||
message.wrappedBody = new Uint8Array(0);
|
||||
if (value !== undefined)
|
||||
reflectionMergePartial<SysMessageBodyWrapper>(this, message, value);
|
||||
return message;
|
||||
}
|
||||
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: SysMessageBodyWrapper): SysMessageBodyWrapper {
|
||||
let message = target ?? this.create(), end = reader.pos + length;
|
||||
while (reader.pos < end) {
|
||||
let [fieldNo, wireType] = reader.tag();
|
||||
switch (fieldNo) {
|
||||
case /* bytes wrappedBody */ 2:
|
||||
message.wrappedBody = reader.bytes();
|
||||
break;
|
||||
default:
|
||||
let u = options.readUnknownField;
|
||||
if (u === "throw")
|
||||
throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
|
||||
let d = reader.skip(wireType);
|
||||
if (u !== false)
|
||||
(u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
|
||||
}
|
||||
}
|
||||
return message;
|
||||
}
|
||||
internalBinaryWrite(message: SysMessageBodyWrapper, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
|
||||
/* bytes wrappedBody = 2; */
|
||||
if (message.wrappedBody.length)
|
||||
writer.tag(2, WireType.LengthDelimited).bytes(message.wrappedBody);
|
||||
let u = options.writeUnknownFields;
|
||||
if (u !== false)
|
||||
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
|
||||
return writer;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @generated MessageType for protobuf message SysMessage.SysMessageBodyWrapper
|
||||
*/
|
||||
export const SysMessageBodyWrapper = new SysMessageBodyWrapper$Type();
|
||||
// @generated message type with reflection information, may provide speed optimized methods
|
||||
class KeyValuePair$Type extends MessageType<KeyValuePair> {
|
||||
constructor() {
|
||||
super("SysMessage.KeyValuePair", [
|
||||
{ no: 1, name: "key", kind: "scalar", T: 9 /*ScalarType.STRING*/ },
|
||||
{ no: 2, name: "value", kind: "scalar", T: 9 /*ScalarType.STRING*/ }
|
||||
]);
|
||||
}
|
||||
create(value?: PartialMessage<KeyValuePair>): KeyValuePair {
|
||||
const message = globalThis.Object.create((this.messagePrototype!));
|
||||
message.key = "";
|
||||
message.value = "";
|
||||
if (value !== undefined)
|
||||
reflectionMergePartial<KeyValuePair>(this, message, value);
|
||||
return message;
|
||||
}
|
||||
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: KeyValuePair): KeyValuePair {
|
||||
let message = target ?? this.create(), end = reader.pos + length;
|
||||
while (reader.pos < end) {
|
||||
let [fieldNo, wireType] = reader.tag();
|
||||
switch (fieldNo) {
|
||||
case /* string key */ 1:
|
||||
message.key = reader.string();
|
||||
break;
|
||||
case /* string value */ 2:
|
||||
message.value = reader.string();
|
||||
break;
|
||||
default:
|
||||
let u = options.readUnknownField;
|
||||
if (u === "throw")
|
||||
throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
|
||||
let d = reader.skip(wireType);
|
||||
if (u !== false)
|
||||
(u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
|
||||
}
|
||||
}
|
||||
return message;
|
||||
}
|
||||
internalBinaryWrite(message: KeyValuePair, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
|
||||
/* string key = 1; */
|
||||
if (message.key !== "")
|
||||
writer.tag(1, WireType.LengthDelimited).string(message.key);
|
||||
/* string value = 2; */
|
||||
if (message.value !== "")
|
||||
writer.tag(2, WireType.LengthDelimited).string(message.value);
|
||||
let u = options.writeUnknownFields;
|
||||
if (u !== false)
|
||||
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
|
||||
return writer;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @generated MessageType for protobuf message SysMessage.KeyValuePair
|
||||
*/
|
||||
export const KeyValuePair = new KeyValuePair$Type();
|
@@ -14,7 +14,7 @@ export interface NodeIKernelBuddyService {
|
||||
}>
|
||||
}>;
|
||||
|
||||
getBuddyListFromCache(callFrom: string): Promise<Array<
|
||||
getBuddyListFromCache(reqType: BuddyListReqType): Promise<Array<
|
||||
{
|
||||
categoryId: number,//9999应该跳过 那是兜底数据吧
|
||||
categorySortId: number,//排序方式
|
||||
@@ -23,7 +23,7 @@ export interface NodeIKernelBuddyService {
|
||||
onlineCount: number,//在线数目
|
||||
buddyUids: Array<string>//Uids
|
||||
}>>;
|
||||
|
||||
|
||||
addKernelBuddyListener(listener: NodeIKernelBuddyListener): number;
|
||||
|
||||
getAllBuddyCount(): number;
|
||||
@@ -36,7 +36,7 @@ export interface NodeIKernelBuddyService {
|
||||
|
||||
getBuddyRemark(uid: number): string;
|
||||
|
||||
setBuddyRemark(uid: number, remark: string): void;
|
||||
setBuddyRemark(param: { uid: string, remark: string, signInfo?: unknown }): void;
|
||||
|
||||
getAvatarUrl(uid: number): string;
|
||||
|
||||
@@ -114,7 +114,7 @@ export interface NodeIKernelBuddyService {
|
||||
|
||||
reportDoubtBuddyReqUnread(): void;
|
||||
|
||||
getBuddyRecommendContactArkJson(uid: string, phoneNumber: string): Promise<unknown>;
|
||||
getBuddyRecommendContactArkJson(uid: string, phoneNumber: string): Promise<GeneralCallResult & { arkMsg: string }>;
|
||||
|
||||
isNull(): boolean;
|
||||
}
|
||||
|
@@ -1,2 +1,3 @@
|
||||
export interface NodeIKernelECDHService {
|
||||
sendOIDBECRequest: (data: Uint8Array) => Promise<Uint8Array>;
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user