mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2025-07-19 12:03:37 +00:00
Compare commits
419 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
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 | ||
![]() |
bd1a95a7f5 | ||
![]() |
cdb25828f2 | ||
![]() |
45803b3b23 | ||
![]() |
0e5e3d3383 | ||
![]() |
4672930037 | ||
![]() |
09be7131c3 | ||
![]() |
a804f90b9c | ||
![]() |
264cb6bbd2 | ||
![]() |
b7772e867b | ||
![]() |
cc0e77abfb | ||
![]() |
537d1c6f4f | ||
![]() |
80facadd67 | ||
![]() |
ba097dad23 | ||
![]() |
c13c15d046 | ||
![]() |
4f52128a06 | ||
![]() |
500b2d0e6d | ||
![]() |
e59d094feb | ||
![]() |
a8372f14f8 | ||
![]() |
5174ff422d | ||
![]() |
5c06751c3b | ||
![]() |
ac2b0118a6 | ||
![]() |
3eb8fd4abe | ||
![]() |
48b389ebe3 | ||
![]() |
065adeb2cd | ||
![]() |
269d0a06fe | ||
![]() |
8eca26b1a5 | ||
![]() |
3019ef7de4 | ||
![]() |
522311b547 | ||
![]() |
21061561ec | ||
![]() |
b83c41ad56 | ||
![]() |
e80a1cc64a |
3
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
3
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -10,6 +10,7 @@ body:
|
|||||||
在提交新的 Bug 反馈前,请确保您:
|
在提交新的 Bug 反馈前,请确保您:
|
||||||
* 已经搜索了现有的 issues,并且没有找到可以解决您问题的方法
|
* 已经搜索了现有的 issues,并且没有找到可以解决您问题的方法
|
||||||
* 不与现有的某一 issue 重复
|
* 不与现有的某一 issue 重复
|
||||||
|
* 不涉及[已经停止维护的特性](https://github.com/NapNeko/NapCatQQ?tab=readme-ov-file#挥别昨日),例如 CQ 码
|
||||||
- type: input
|
- type: input
|
||||||
id: system-version
|
id: system-version
|
||||||
attributes:
|
attributes:
|
||||||
@@ -78,4 +79,4 @@ body:
|
|||||||
attributes:
|
attributes:
|
||||||
label: OneBot 客户端运行日志
|
label: OneBot 客户端运行日志
|
||||||
description: 粘贴 OneBot 客户端的相关日志内容到此处
|
description: 粘贴 OneBot 客户端的相关日志内容到此处
|
||||||
render: shell
|
render: shell
|
||||||
|
28
.github/workflows/release.yml
vendored
28
.github/workflows/release.yml
vendored
@@ -93,15 +93,18 @@ jobs:
|
|||||||
needs: [Build-LiteLoader,Build-Shell]
|
needs: [Build-LiteLoader,Build-Shell]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
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
|
- name: Download All Artifact
|
||||||
uses: actions/download-artifact@v4
|
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
|
- name: Compress subdirectories
|
||||||
run: |
|
run: |
|
||||||
cd ./NapCat.Shell/
|
cd ./NapCat.Shell/
|
||||||
@@ -114,6 +117,16 @@ jobs:
|
|||||||
rm ./NapCat.Framework.zip -rf
|
rm ./NapCat.Framework.zip -rf
|
||||||
mv ./NapCat.Shell/NapCat.Shell.zip ./
|
mv ./NapCat.Shell/NapCat.Shell.zip ./
|
||||||
mv ./NapCat.Framework/NapCat.Framework.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
|
- name: Extract version from tag
|
||||||
run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_ENV
|
run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_ENV
|
||||||
|
|
||||||
@@ -129,4 +142,5 @@ jobs:
|
|||||||
files: |
|
files: |
|
||||||
NapCat.Framework.zip
|
NapCat.Framework.zip
|
||||||
NapCat.Shell.zip
|
NapCat.Shell.zip
|
||||||
|
NapCat.Framework.Windows.Once.zip
|
||||||
draft: true
|
draft: true
|
||||||
|
31
README.md
31
README.md
@@ -1,26 +1,35 @@
|
|||||||
<div align="center">
|
<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>
|
</div>
|
||||||
|
|
||||||
---
|
---
|
||||||
## 欢迎回来
|
## 欢迎回来
|
||||||
NapCatQQ (aka 猫猫框架) 是现代化的基于 NTQQ 的 Bot 协议端实现。
|
NapCatQQ (aka 猫猫框架) 是现代化的基于 NTQQ 的 Bot 协议端实现
|
||||||
|
|
||||||
## 猫猫技能
|
## 猫猫技能
|
||||||
- [x] **高性能**:1K+ 群聊数目、20 线程并行发送消息毫无压力
|
- [x] **超高性能**:轻松数千群聊 独创消息队列
|
||||||
- [x] **多种启动方式**:支持以无头、LiteLoader 插件、仅 QQ GUI 三种方式启动
|
- [x] **启动方式**:支持以无头、LiteLoader 插件、仅 QQ GUI 三种方式启动
|
||||||
- [x] **多平台支持**: 支持Windows/Linux(可选Docker)/Android Termux/MacOs覆盖全平台
|
- [x] **覆盖平台**: 覆盖 Windows / Linux (可选 Docker) / Android Termux / MacOS
|
||||||
- [x] **安装简单**: 支持一键脚本/程序自动部署/镜像部署等多种覆盖范围
|
- [x] **安装简单**: 支持一键脚本/程序自动部署/镜像部署等多种覆盖范围
|
||||||
- [x] **低占用**:无头模式占用资源极低,适合在服务器上运行
|
- [x] **超低占用**:无头模式占用资源极低,适合在服务器上运行
|
||||||
- [x] **超多接口**:实现大部分 OneBot 和 go-cqhttp 接口,超多扩展 API
|
- [x] **超多接口**:实现大部分 OneBot 和 go-cqhttp 接口,超多扩展 API
|
||||||
- [x] **WebUI**:自带 WebUI 支持,远程管理更加便捷
|
- [x] **远程管理**:自带 WebUI 支持,远程管理更加便捷
|
||||||
- [x] **低故障率**:快速适配最新版本,日常保证 0 Issue
|
- [x] **扩展支持**:基于 MoeHoo 的Native 可实现发包与收包
|
||||||
|
|
||||||
## 使用猫猫
|
## 使用猫猫
|
||||||
|
|
||||||
可前往 [Release](https://github.com/NapNeko/NapCatQQ/releases/) 页面下载最新版本
|
可前往 [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)
|
[QQ Group](https://qm.qq.com/q/VfjAq5HIMS)
|
||||||
@@ -28,10 +37,12 @@ NapCatQQ (aka 猫猫框架) 是现代化的基于 NTQQ 的 Bot 协议端实现
|
|||||||
[Telegram Link](https://t.me/+nLZEnpne-pQ1OWFl)
|
[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) 对本项目的大力支持
|
感谢 [Lagrange](https://github.com/LagrangeDev/Lagrange.Core) 对本项目的大力支持
|
||||||
|
|
||||||
|
不过最最重要的 还是需要感谢屏幕前的你哦~
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 约法三章
|
## 约法三章
|
||||||
|
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.
|
echo Administrator mode detected.
|
||||||
) else (
|
) else (
|
||||||
echo Please run this script in administrator mode.
|
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
|
exit
|
||||||
)
|
)
|
||||||
|
|
||||||
set NAPCAT_PATCH_PATH=%cd%\patchNapCat.js
|
set NAPCAT_PATCH_PACKAGE=%cd%\qqnt.json
|
||||||
set NAPCAT_LOAD_PATH=%cd%\loadNapCat.js
|
set NAPCAT_LOAD_PATH=%cd%\loadNapCat.js
|
||||||
set NAPCAT_INJECT_PATH=%cd%\NapCatWinBootHook.dll
|
set NAPCAT_INJECT_PATH=%cd%\NapCatWinBootHook.dll
|
||||||
set NAPCAT_LAUNCHER_PATH=%cd%\NapCatWinBootMain.exe
|
set NAPCAT_LAUNCHER_PATH=%cd%\NapCatWinBootMain.exe
|
||||||
@@ -33,6 +33,8 @@ if not exist "%QQpath%" (
|
|||||||
exit /b
|
exit /b
|
||||||
)
|
)
|
||||||
set NAPCAT_MAIN_PATH=%NAPCAT_MAIN_PATH:\=/%
|
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.
|
echo Administrator mode detected.
|
||||||
) else (
|
) else (
|
||||||
echo Please run this script in administrator mode.
|
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
|
exit
|
||||||
)
|
)
|
||||||
|
|
||||||
set NAPCAT_PATCH_PATH=%cd%\patchNapCat.js
|
set NAPCAT_PATCH_PACKAGE=%cd%\qqnt.json
|
||||||
set NAPCAT_LOAD_PATH=%cd%\loadNapCat.js
|
set NAPCAT_LOAD_PATH=%cd%\loadNapCat.js
|
||||||
set NAPCAT_INJECT_PATH=%cd%\NapCatWinBootHook.dll
|
set NAPCAT_INJECT_PATH=%cd%\NapCatWinBootHook.dll
|
||||||
set NAPCAT_LAUNCHER_PATH=%cd%\NapCatWinBootMain.exe
|
set NAPCAT_LAUNCHER_PATH=%cd%\NapCatWinBootMain.exe
|
||||||
@@ -34,6 +34,6 @@ if not exist "%QQpath%" (
|
|||||||
)
|
)
|
||||||
|
|
||||||
set NAPCAT_MAIN_PATH=%NAPCAT_MAIN_PATH:\=/%
|
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
|
@@ -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",
|
"name": "NapCatQQ",
|
||||||
"slug": "NapCat.Framework",
|
"slug": "NapCat.Framework",
|
||||||
"description": "高性能的 OneBot 11 协议实现",
|
"description": "高性能的 OneBot 11 协议实现",
|
||||||
"version": "2.3.0",
|
"version": "3.0.1",
|
||||||
"icon": "./logo.png",
|
"icon": "./logo.png",
|
||||||
"authors": [
|
"authors": [
|
||||||
{
|
{
|
||||||
|
117
package.json
117
package.json
@@ -1,65 +1,52 @@
|
|||||||
{
|
{
|
||||||
"name": "napcat",
|
"name": "napcat",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "2.3.0",
|
"version": "3.0.1",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build:framework": "vite build --mode framework",
|
"build:framework": "vite build --mode framework",
|
||||||
"build:shell": "vite build --mode shell",
|
"build:shell": "vite build --mode shell",
|
||||||
"build:webui": "cd ./src/webui && vite build",
|
"build:webui": "cd ./src/webui && vite build",
|
||||||
"lint": "eslint --fix src/**/*.{js,ts}",
|
"lint": "eslint --fix src/**/*.{js,ts}",
|
||||||
"depend": "cd dist && npm install --omit=dev"
|
"depend": "cd dist && npm install --omit=dev"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.24.7",
|
"@babel/preset-typescript": "^7.24.7",
|
||||||
"@babel/plugin-proposal-class-properties": "^7.18.6",
|
"@log4js-node/log4js-api": "^1.0.2",
|
||||||
"@babel/plugin-proposal-decorators": "^7.24.7",
|
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||||
"@babel/preset-typescript": "^7.24.7",
|
"@rollup/plugin-typescript": "^11.1.6",
|
||||||
"@log4js-node/log4js-api": "^1.0.2",
|
"@types/cors": "^2.8.17",
|
||||||
"@protobuf-ts/plugin": "^2.9.4",
|
"@types/express": "^5.0.0",
|
||||||
"@rollup/plugin-node-resolve": "^15.2.3",
|
"@types/fluent-ffmpeg": "^2.1.24",
|
||||||
"@rollup/plugin-typescript": "^11.1.6",
|
"@types/node": "^22.0.1",
|
||||||
"@types/cors": "^2.8.17",
|
"@types/qrcode-terminal": "^0.12.2",
|
||||||
"@types/express": "^4.17.21",
|
"@types/ws": "^8.5.12",
|
||||||
"@types/figlet": "^1.5.8",
|
"@typescript-eslint/eslint-plugin": "^8.3.0",
|
||||||
"@types/fluent-ffmpeg": "^2.1.24",
|
"@typescript-eslint/parser": "^8.3.0",
|
||||||
"@types/jest": "^29.5.12",
|
"eslint": "^8.57.0",
|
||||||
"@types/node": "^22.0.1",
|
"eslint-import-resolver-typescript": "^3.6.1",
|
||||||
"@types/qrcode-terminal": "^0.12.2",
|
"eslint-plugin-import": "^2.29.1",
|
||||||
"@types/ws": "^8.5.12",
|
"typescript": "^5.3.3",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.3.0",
|
"vite": "^5.2.6",
|
||||||
"@typescript-eslint/parser": "^8.3.0",
|
"vite-plugin-cp": "^4.0.8",
|
||||||
"eslint": "^8.57.0",
|
"vite-tsconfig-paths": "^4.3.2",
|
||||||
"eslint-import-resolver-typescript": "^3.6.1",
|
"@protobuf-ts/runtime": "^2.9.4",
|
||||||
"eslint-plugin-import": "^2.29.1",
|
"ajv": "^8.13.0",
|
||||||
"i": "^0.3.7",
|
"fast-xml-parser": "^4.3.6",
|
||||||
"javascript-obfuscator": "^4.1.0",
|
"chalk": "^5.3.0",
|
||||||
"rollup": "^4.13.2",
|
"commander": "^12.1.0",
|
||||||
"rollup-plugin-dts": "^6.1.0",
|
"async-mutex": "^0.5.0",
|
||||||
"rollup-plugin-obfuscator": "^1.1.0",
|
"file-type": "^19.0.0",
|
||||||
"typescript": "^5.3.3",
|
"json-schema-to-ts": "^3.1.0",
|
||||||
"vite": "^5.2.6",
|
"image-size": "^1.1.1",
|
||||||
"vite-plugin-babel": "^1.2.0",
|
"cors": "^2.8.5"
|
||||||
"vite-plugin-cp": "^4.0.8",
|
},
|
||||||
"vite-plugin-dts": "^3.8.2",
|
"dependencies": {
|
||||||
"vite-tsconfig-paths": "^4.3.2"
|
"qrcode-terminal": "^0.12.0",
|
||||||
},
|
"fluent-ffmpeg": "^2.1.2",
|
||||||
"dependencies": {
|
"express": "^5.0.0-beta.2",
|
||||||
"ajv": "^8.13.0",
|
"log4js": "^6.9.1",
|
||||||
"async-mutex": "^0.5.0",
|
"silk-wasm": "^3.6.1",
|
||||||
"chalk": "^5.3.0",
|
"ws": "^8.18.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"
|
|
||||||
}
|
|
||||||
}
|
|
@@ -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 fsPromise from 'fs/promises';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import { randomUUID } from 'crypto';
|
import { randomUUID } from 'crypto';
|
||||||
import { spawn } from 'node:child_process';
|
import { spawn } from 'node:child_process';
|
||||||
|
import { encode, getDuration, getWavFileInfo, isSilk, isWav } from 'silk-wasm';
|
||||||
import { LogWrapper } from './log';
|
import { LogWrapper } from './log';
|
||||||
|
|
||||||
export async function encodeSilk(filePath: string, TEMP_DIR: string, logger: LogWrapper) {
|
const ALLOW_SAMPLE_RATE = [8000, 12000, 16000, 24000, 32000, 44100, 48000];
|
||||||
async function guessDuration(pttPath: string) {
|
const EXIT_CODES = [0, 255];
|
||||||
const pttFileInfo = await fsPromise.stat(pttPath);
|
const FFMPEG_PATH = process.env.FFMPEG_PATH || 'ffmpeg';
|
||||||
let duration = pttFileInfo.size / 1024 / 3; // 3kb/s
|
|
||||||
duration = Math.floor(duration);
|
|
||||||
duration = Math.max(1, duration);
|
|
||||||
logger.log('通过文件大小估算语音的时长:', duration);
|
|
||||||
return duration;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
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 {
|
try {
|
||||||
const file = await fsPromise.readFile(filePath);
|
const file = await fsPromise.readFile(filePath);
|
||||||
const pttPath = path.join(TEMP_DIR, randomUUID());
|
const pttPath = path.join(TEMP_DIR, randomUUID());
|
||||||
if (!isSilk(file)) {
|
if (!isSilk(file)) {
|
||||||
logger.log(`语音文件${filePath}需要转换成silk`);
|
logger.log(`语音文件${filePath}需要转换成silk`);
|
||||||
const _isWav = isWav(file);
|
const pcmPath = `${pttPath}.pcm`;
|
||||||
const pcmPath = pttPath + '.pcm';
|
const { input, sampleRate } = isWav(file)
|
||||||
let sampleRate = 0;
|
? (await handleWavFile(file, filePath, pcmPath, logger))
|
||||||
const convert = () => {
|
: { input: await convert(filePath, pcmPath, logger), sampleRate: 24000 };
|
||||||
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 silk = await encode(input, sampleRate);
|
const silk = await encode(input, sampleRate);
|
||||||
fs.writeFileSync(pttPath, silk.data);
|
await fsPromise.writeFile(pttPath, silk.data);
|
||||||
logger.log(`语音文件${filePath}转换成功!`, pttPath, '时长:', silk.duration);
|
logger.log(`语音文件${filePath}转换成功!`, pttPath, '时长:', silk.duration);
|
||||||
return {
|
return {
|
||||||
converted: true,
|
converted: true,
|
||||||
@@ -68,15 +69,13 @@ export async function encodeSilk(filePath: string, TEMP_DIR: string, logger: Log
|
|||||||
duration: silk.duration / 1000,
|
duration: silk.duration / 1000,
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
const silk = file;
|
|
||||||
let duration = 0;
|
let duration = 0;
|
||||||
try {
|
try {
|
||||||
duration = getDuration(silk) / 1000;
|
duration = getDuration(file) / 1000;
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
logger.log('获取语音文件时长失败, 使用文件大小推测时长', filePath, e.stack);
|
logger.log('获取语音文件时长失败, 使用文件大小推测时长', filePath, e.stack);
|
||||||
duration = await guessDuration(filePath);
|
duration = await guessDuration(filePath, logger);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
converted: false,
|
converted: false,
|
||||||
path: filePath,
|
path: filePath,
|
||||||
@@ -84,7 +83,7 @@ export async function encodeSilk(filePath: string, TEMP_DIR: string, logger: Log
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
logger.logError('convert silk failed', error.stack);
|
logger.logError.bind(logger)('convert silk failed', error.stack);
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -40,7 +40,7 @@ export abstract class ConfigBase<T> {
|
|||||||
fs.writeFileSync(configPath, fs.readFileSync(this.getConfigPath(undefined), 'utf-8'));
|
fs.writeFileSync(configPath, fs.readFileSync(this.getConfigPath(undefined), 'utf-8'));
|
||||||
logger.log(`[Core] [Config] 配置文件创建成功!\n`);
|
logger.log(`[Core] [Config] 配置文件创建成功!\n`);
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
logger.logError(`[Core] [Config] 创建配置文件时发生错误:`, e.message);
|
logger.logError.bind(logger)(`[Core] [Config] 创建配置文件时发生错误:`, e.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
@@ -49,9 +49,9 @@ export abstract class ConfigBase<T> {
|
|||||||
return this.configData;
|
return this.configData;
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
if (e instanceof SyntaxError) {
|
if (e instanceof SyntaxError) {
|
||||||
logger.logError(`[Core] [Config] 配置文件格式错误,请检查配置文件:`, e.message);
|
logger.logError.bind(logger)(`[Core] [Config] 配置文件格式错误,请检查配置文件:`, e.message);
|
||||||
} else {
|
} else {
|
||||||
logger.logError(`[Core] [Config] 读取配置文件时发生错误:`, e.message);
|
logger.logError.bind(logger)(`[Core] [Config] 读取配置文件时发生错误:`, e.message);
|
||||||
}
|
}
|
||||||
return {} as T;
|
return {} as T;
|
||||||
}
|
}
|
||||||
@@ -66,7 +66,7 @@ export abstract class ConfigBase<T> {
|
|||||||
try {
|
try {
|
||||||
fs.writeFileSync(configPath, JSON.stringify(newConfigData, this.getKeys(), 2));
|
fs.writeFileSync(configPath, JSON.stringify(newConfigData, this.getKeys(), 2));
|
||||||
} catch (e: any) {
|
} 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);
|
this.createListenerFunction(ListenerMainName);
|
||||||
const eventFunction = this.createEventFunction(serviceAndMethod);
|
const eventFunction = this.createEventFunction(serviceAndMethod);
|
||||||
retEvent = await eventFunction!(...(args));
|
retEvent = await eventFunction!(...(args));
|
||||||
if (!checkerEvent(retEvent)) {
|
if (!checkerEvent(retEvent) && timeoutRef.hasRef()) {
|
||||||
clearTimeout(timeoutRef);
|
clearTimeout(timeoutRef);
|
||||||
reject(
|
reject(
|
||||||
new Error(
|
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;
|
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[] = [];
|
// const chunks: Buffer[] = [];
|
||||||
let url: string;
|
let url: string;
|
||||||
let headers: Record<string, 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) => {
|
const fetchRes = await fetch(url, { headers }).catch((err) => {
|
||||||
if (err.cause) {
|
if (err.cause) {
|
||||||
throw err.cause;
|
throw err.cause;
|
||||||
}
|
}
|
||||||
throw err;
|
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();
|
const buffer = await blob.arrayBuffer();
|
||||||
return Buffer.from(buffer);
|
return Buffer.from(buffer);
|
||||||
}
|
}
|
||||||
@@ -160,8 +171,7 @@ type Uri2LocalRes = {
|
|||||||
errMsg: string,
|
errMsg: string,
|
||||||
fileName: string,
|
fileName: string,
|
||||||
ext: string,
|
ext: string,
|
||||||
path: string,
|
path: string
|
||||||
isLocal: boolean
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function checkFileV2(filePath: string) {
|
export async function checkFileV2(filePath: string) {
|
||||||
@@ -194,7 +204,6 @@ export async function checkUriType(Uri: string) {
|
|||||||
return undefined;
|
return undefined;
|
||||||
}, Uri);
|
}, Uri);
|
||||||
if (LocalFileRet) return LocalFileRet;
|
if (LocalFileRet) return LocalFileRet;
|
||||||
|
|
||||||
const OtherFileRet = await solveProblem((uri: string) => {
|
const OtherFileRet = await solveProblem((uri: string) => {
|
||||||
//再判断是否是Http
|
//再判断是否是Http
|
||||||
if (uri.startsWith('http://') || uri.startsWith('https://')) {
|
if (uri.startsWith('http://') || uri.startsWith('https://')) {
|
||||||
@@ -206,15 +215,19 @@ export async function checkUriType(Uri: string) {
|
|||||||
}
|
}
|
||||||
if (uri.startsWith('file://')) {
|
if (uri.startsWith('file://')) {
|
||||||
let filePath: string;
|
let filePath: string;
|
||||||
// await fs.copyFile(url.pathname, filePath);
|
|
||||||
const pathname = decodeURIComponent(new URL(uri).pathname);
|
const pathname = decodeURIComponent(new URL(uri).pathname);
|
||||||
if (process.platform === 'win32') {
|
if (process.platform === 'win32') {
|
||||||
filePath = pathname.slice(1);
|
filePath = pathname.slice(1);
|
||||||
} else {
|
} else {
|
||||||
filePath = pathname;
|
filePath = pathname;
|
||||||
}
|
}
|
||||||
|
|
||||||
return { Uri: filePath, Type: FileUriType.Local };
|
return { Uri: filePath, Type: FileUriType.Local };
|
||||||
}
|
}
|
||||||
|
if (uri.startsWith('data:')) {
|
||||||
|
const data = uri.split(',')[1];
|
||||||
|
if (data) return { Uri: data, Type: FileUriType.Base64 };
|
||||||
|
}
|
||||||
}, Uri);
|
}, Uri);
|
||||||
if (OtherFileRet) return OtherFileRet;
|
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> {
|
export async function uri2local(dir: string, uri: string, filename: string | undefined = undefined): Promise<Uri2LocalRes> {
|
||||||
const { Uri: HandledUri, Type: UriType } = await checkUriType(uri);
|
const { Uri: HandledUri, Type: UriType } = await checkUriType(uri);
|
||||||
//解析失败
|
//解析失败
|
||||||
|
const tempName = randomUUID();
|
||||||
|
if (!filename) filename = randomUUID();
|
||||||
|
//解析Http和Https协议
|
||||||
|
|
||||||
if (UriType == FileUriType.Unknown) {
|
if (UriType == FileUriType.Unknown) {
|
||||||
return { success: false, errMsg: '未知文件类型', fileName: '', ext: '', path: '', isLocal: false };
|
return { success: false, errMsg: '未知文件类型', fileName: '', ext: '', path: '' };
|
||||||
}
|
}
|
||||||
//解析File协议和本地文件
|
//解析File协议和本地文件
|
||||||
if (UriType == FileUriType.Local) {
|
if (UriType == FileUriType.Local) {
|
||||||
const fileExt = path.extname(HandledUri);
|
const fileExt = path.extname(HandledUri);
|
||||||
const filename = path.basename(HandledUri, fileExt);
|
let filename = path.basename(HandledUri, fileExt);
|
||||||
return { success: true, errMsg: '', fileName: filename, ext: fileExt, path: HandledUri, isLocal: true };
|
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) {
|
if (UriType == FileUriType.Remote) {
|
||||||
const pathInfo = path.parse(decodeURIComponent(new URL(HandledUri).pathname));
|
const pathInfo = path.parse(decodeURIComponent(new URL(HandledUri).pathname));
|
||||||
if (pathInfo.name) {
|
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) {
|
if (pathInfo.ext) {
|
||||||
filename += pathInfo.ext;
|
filename += pathInfo.ext;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
filename = filename.replace(/[/\\:*?"<>|]/g, '_');
|
filename = filename.replace(/[/\\:*?"<>|]/g, '_');
|
||||||
const fileExt = path.extname(HandledUri);
|
const fileExt = path.extname(HandledUri).replace(/[/\\:*?"<>|]/g, '_').substring(0, 10);
|
||||||
const filePath = path.join(dir, filename);
|
const filePath = path.join(dir, tempName + fileExt);
|
||||||
const buffer = await httpDownload(HandledUri);
|
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
|
//解析Base64
|
||||||
if (UriType == FileUriType.Base64) {
|
if (UriType == FileUriType.Base64) {
|
||||||
@@ -266,7 +287,7 @@ export async function uri2local(dir: string, uri: string, filename: string | und
|
|||||||
fileExt = ext;
|
fileExt = ext;
|
||||||
filename = filename + '.' + 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 {
|
export class FileNapCatOneBotUUID {
|
||||||
static encodeModelId(peer: Peer, modelId: string, fileId: string): string {
|
static encodeModelId(peer: Peer, modelId: string, fileId: string, fileUUID: string = "", endString: string = ""): string {
|
||||||
return `NapCatOneBot|ModelIdFile|${peer.chatType}|${peer.peerUid}|${modelId}|${fileId}`;
|
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 | {
|
static decodeModelId(uuid: string): undefined | {
|
||||||
peer: Peer,
|
peer: Peer,
|
||||||
modelId: string,
|
modelId: string,
|
||||||
fileId: string
|
fileId: string,
|
||||||
|
fileUUID?: string
|
||||||
} {
|
} {
|
||||||
if (!uuid.startsWith('NapCatOneBot|ModelIdFile|')) return undefined;
|
//前四个字节是data长度
|
||||||
const data = uuid.split('|');
|
const length = Buffer.from(uuid.slice(0, 8), 'hex').readUInt32BE(0);
|
||||||
if (data.length !== 6) return undefined;
|
//根据length计算需要读取的长度
|
||||||
const [, , chatType, peerUid, modelId, fileId] = data;
|
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 {
|
return {
|
||||||
peer: {
|
peer: {
|
||||||
chatType: chatType as any,
|
chatType: chatType as any,
|
||||||
peerUid: peerUid,
|
peerUid: peerUid,
|
||||||
},
|
},
|
||||||
modelId,
|
modelId,
|
||||||
fileId
|
fileId,
|
||||||
|
fileUUID
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
static encode(peer: Peer, msgId: string, elementId: string): string {
|
static encode(peer: Peer, msgId: string, elementId: string, fileUUID: string = "", endString: string = ""): string {
|
||||||
return `NapCatOneBot|MsgFile|${peer.chatType}|${peer.peerUid}|${msgId}|${elementId}`;
|
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 | {
|
static decode(uuid: string): undefined | {
|
||||||
peer: Peer,
|
peer: Peer,
|
||||||
msgId: string,
|
msgId: string,
|
||||||
elementId: string
|
elementId: string,
|
||||||
|
fileUUID?: string
|
||||||
} {
|
} {
|
||||||
if (!uuid.startsWith('NapCatOneBot|MsgFile|')) return undefined;
|
//前四个字节是data长度
|
||||||
const data = uuid.split('|');
|
const length = Buffer.from(uuid.slice(0, 8), 'hex').readUInt32BE(0);
|
||||||
if (data.length !== 6) return undefined;
|
//根据length计算需要读取的长度
|
||||||
const [, , chatType, peerUid, msgId, elementId] = data;
|
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 {
|
return {
|
||||||
peer: {
|
peer: {
|
||||||
chatType: chatType as any,
|
chatType: chatType as any,
|
||||||
@@ -68,6 +94,7 @@ export class FileNapCatOneBotUUID {
|
|||||||
},
|
},
|
||||||
msgId,
|
msgId,
|
||||||
elementId,
|
elementId,
|
||||||
|
fileUUID
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -140,34 +167,51 @@ export function isEqual(obj1: any, obj2: any) {
|
|||||||
export function getDefaultQQVersionConfigInfo(): QQVersionConfigType {
|
export function getDefaultQQVersionConfigInfo(): QQVersionConfigType {
|
||||||
if (os.platform() === 'linux') {
|
if (os.platform() === 'linux') {
|
||||||
return {
|
return {
|
||||||
baseVersion: '3.2.12-27597',
|
baseVersion: '3.2.12.28060',
|
||||||
curVersion: '3.2.12-27597',
|
curVersion: '3.2.12.28060',
|
||||||
prevVersion: '',
|
prevVersion: '',
|
||||||
onErrorVersions: [],
|
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 {
|
return {
|
||||||
baseVersion: '9.9.15-27597',
|
baseVersion: '9.9.15-28131',
|
||||||
curVersion: '9.9.15-27597',
|
curVersion: '9.9.15-28131',
|
||||||
prevVersion: '',
|
prevVersion: '',
|
||||||
onErrorVersions: [],
|
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') {
|
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 {
|
} 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 {
|
export function getQQVersionConfigPath(exePath: string = ''): string | undefined {
|
||||||
let configVersionInfoPath;
|
let configVersionInfoPath;
|
||||||
if (os.platform() === 'win32') {
|
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') {
|
} else if (os.platform() === 'darwin') {
|
||||||
const userPath = os.homedir();
|
const userPath = os.homedir();
|
||||||
const appDataPath = path.resolve(userPath, './Library/Application Support/QQ');
|
const appDataPath = path.resolve(userPath, './Library/Application Support/QQ');
|
||||||
@@ -180,13 +224,18 @@ export function getQQVersionConfigPath(exePath: string = ''): string | undefined
|
|||||||
if (typeof configVersionInfoPath !== 'string') {
|
if (typeof configVersionInfoPath !== 'string') {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
//老版本兼容 未来去掉
|
||||||
|
if (!fs.existsSync(configVersionInfoPath)) {
|
||||||
|
configVersionInfoPath = path.join(path.dirname(exePath), './resources/app/versions/config.json');
|
||||||
|
}
|
||||||
if (!fs.existsSync(configVersionInfoPath)) {
|
if (!fs.existsSync(configVersionInfoPath)) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
return configVersionInfoPath;
|
return configVersionInfoPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function calcQQLevel(level: QQLevel) {
|
export function calcQQLevel(level?: QQLevel) {
|
||||||
|
if (!level) return 0;
|
||||||
const { crownNum, sunNum, moonNum, starNum } = level;
|
const { crownNum, sunNum, moonNum, starNum } = level;
|
||||||
return crownNum * 64 + sunNum * 16 + moonNum * 4 + starNum;
|
return crownNum * 64 + sunNum * 16 + moonNum * 4 + starNum;
|
||||||
}
|
}
|
||||||
|
@@ -139,9 +139,13 @@ export class LogWrapper {
|
|||||||
|
|
||||||
logMessage(msg: RawMessage, selfInfo: SelfInfo) {
|
logMessage(msg: RawMessage, selfInfo: SelfInfo) {
|
||||||
const isSelfSent = msg.senderUin === selfInfo.uin;
|
const isSelfSent = msg.senderUin === selfInfo.uin;
|
||||||
this.log(`${
|
|
||||||
isSelfSent ? '发送 ->' : '接收 <-'
|
// Intercept grey tip
|
||||||
} ${rawMessageToText(msg)}`);
|
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) {
|
if (msg.chatType == ChatType.KCHATTYPEC2C) {
|
||||||
tokens.push(`私聊 (${msg.peerUin})`);
|
tokens.push(`私聊 (${msg.peerUin})`);
|
||||||
} else if (msg.chatType == ChatType.KCHATTYPEGROUP) {
|
} 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) {
|
} else if (msg.chatType == ChatType.KCHATTYPEDATALINE) {
|
||||||
tokens.push('移动设备');
|
tokens.push('移动设备');
|
||||||
} else /* temp */ {
|
} else /* temp */ {
|
||||||
@@ -167,7 +176,8 @@ export function rawMessageToText(msg: RawMessage, recursiveLevel = 0): string {
|
|||||||
function msgElementToText(element: MessageElement) {
|
function msgElementToText(element: MessageElement) {
|
||||||
if (element.textElement) {
|
if (element.textElement) {
|
||||||
if (element.textElement.atType === AtType.notAt) {
|
if (element.textElement.atType === AtType.notAt) {
|
||||||
return element.textElement.content;
|
const originalContentLines = element.textElement.content.split('\n');
|
||||||
|
return `${originalContentLines[0]}${originalContentLines.length > 1 ? ' ...' : ''}`;
|
||||||
} else if (element.textElement.atType === AtType.atAll) {
|
} else if (element.textElement.atType === AtType.atAll) {
|
||||||
return `@全体成员`;
|
return `@全体成员`;
|
||||||
} else if (element.textElement.atType === AtType.atUser) {
|
} else if (element.textElement.atType === AtType.atUser) {
|
||||||
@@ -179,17 +189,16 @@ export function rawMessageToText(msg: RawMessage, recursiveLevel = 0): string {
|
|||||||
const recordMsgOrNull = msg.records.find(
|
const recordMsgOrNull = msg.records.find(
|
||||||
record => element.replyElement!.sourceMsgIdInRecords === record.msgId,
|
record => element.replyElement!.sourceMsgIdInRecords === record.msgId,
|
||||||
);
|
);
|
||||||
return `[回复消息 ${
|
return `[回复消息 ${recordMsgOrNull &&
|
||||||
recordMsgOrNull &&
|
recordMsgOrNull.peerUin != '284840486' && recordMsgOrNull.peerUin != '1094950020'// 非转发消息; 否则定位不到
|
||||||
recordMsgOrNull.peerUin != '284840486' // 非转发消息; 否则定位不到
|
?
|
||||||
?
|
rawMessageToText(recordMsgOrNull, recursiveLevel + 1) :
|
||||||
rawMessageToText(recordMsgOrNull, recursiveLevel + 1) :
|
`未找到消息记录 (MsgId = ${element.replyElement.sourceMsgIdInRecords})`
|
||||||
`未找到消息记录 (MsgId = ${element.replyElement.sourceMsgIdInRecords})`
|
|
||||||
}]`;
|
}]`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (element.picElement) {
|
if (element.picElement) {
|
||||||
return `[图片 ${element.picElement.fileName}]`;
|
return '[图片]';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (element.fileElement) {
|
if (element.fileElement) {
|
||||||
@@ -197,7 +206,7 @@ export function rawMessageToText(msg: RawMessage, recursiveLevel = 0): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (element.videoElement) {
|
if (element.videoElement) {
|
||||||
return `[视频 ${element.videoElement.fileName}]`;
|
return '[视频]';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (element.pttElement) {
|
if (element.pttElement) {
|
||||||
@@ -205,7 +214,7 @@ export function rawMessageToText(msg: RawMessage, recursiveLevel = 0): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (element.arkElement) {
|
if (element.arkElement) {
|
||||||
return `[卡片消息 ${element.arkElement.bytesData}]`;
|
return '[卡片消息]';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (element.faceElement) {
|
if (element.faceElement) {
|
||||||
@@ -213,19 +222,19 @@ export function rawMessageToText(msg: RawMessage, recursiveLevel = 0): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (element.marketFaceElement) {
|
if (element.marketFaceElement) {
|
||||||
return `[商城表情 ${element.marketFaceElement.faceName}]`;
|
return element.marketFaceElement.faceName;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (element.markdownElement) {
|
if (element.markdownElement) {
|
||||||
return `[Markdown ${element.markdownElement.content}]`;
|
return '[Markdown 消息]';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (element.multiForwardMsgElement) {
|
if (element.multiForwardMsgElement) {
|
||||||
return `[转发消息]`;
|
return '[转发消息]';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (element.elementType === ElementType.GreyTip) {
|
if (element.elementType === ElementType.GreyTip) {
|
||||||
return `[灰条消息]`; // TODO: resolve the text
|
return '[灰条消息]';
|
||||||
}
|
}
|
||||||
|
|
||||||
return `[未实现 (ElementType = ${element.elementType})]`;
|
return `[未实现 (ElementType = ${element.elementType})]`;
|
||||||
|
@@ -24,7 +24,9 @@ export class LRUCache<K, V> {
|
|||||||
} else if (this.cache.size >= this.capacity) {
|
} else if (this.cache.size >= this.capacity) {
|
||||||
// If the cache is full, remove the least recently used key (the first one in the map)
|
// If the cache is full, remove the least recently used key (the first one in the map)
|
||||||
const firstKey = this.cache.keys().next().value;
|
const firstKey = this.cache.keys().next().value;
|
||||||
this.cache.delete(firstKey);
|
if (firstKey !== undefined) {
|
||||||
|
this.cache.delete(firstKey);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
this.cache.set(key, value);
|
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) {
|
while (this.keyToValue.size > this.maxSize || this.valueToKey.size > this.maxSize) {
|
||||||
const oldestKey = this.keyToValue.keys().next().value;
|
const oldestKey = this.keyToValue.keys().next().value;
|
||||||
|
// @ts-ignore
|
||||||
this.valueToKey.delete(this.keyToValue.get(oldestKey)!);
|
this.valueToKey.delete(this.keyToValue.get(oldestKey)!);
|
||||||
|
// @ts-ignore
|
||||||
this.keyToValue.delete(oldestKey);
|
this.keyToValue.delete(oldestKey);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -19,14 +19,16 @@ export class QQBasicInfoWrapper {
|
|||||||
//基础目录获取
|
//基础目录获取
|
||||||
this.context = context;
|
this.context = context;
|
||||||
this.QQMainPath = process.execPath;
|
this.QQMainPath = process.execPath;
|
||||||
this.QQPackageInfoPath = getQQPackageInfoPath(this.QQMainPath);
|
|
||||||
this.QQVersionConfigPath = getQQVersionConfigPath(this.QQMainPath);
|
this.QQVersionConfigPath = getQQVersionConfigPath(this.QQMainPath);
|
||||||
|
|
||||||
|
|
||||||
//基础信息获取 无快更则启用默认模板填充
|
//基础信息获取 无快更则启用默认模板填充
|
||||||
this.isQuickUpdate = !!this.QQVersionConfigPath;
|
this.isQuickUpdate = !!this.QQVersionConfigPath;
|
||||||
this.QQVersionConfig = this.isQuickUpdate
|
this.QQVersionConfig = this.isQuickUpdate
|
||||||
? JSON.parse(fs.readFileSync(this.QQVersionConfigPath!).toString())
|
? JSON.parse(fs.readFileSync(this.QQVersionConfigPath!).toString())
|
||||||
: getDefaultQQVersionConfigInfo();
|
: getDefaultQQVersionConfigInfo();
|
||||||
|
|
||||||
|
this.QQPackageInfoPath = getQQPackageInfoPath(this.QQMainPath, this.QQVersionConfig?.curVersion);
|
||||||
this.QQPackageInfo = JSON.parse(fs.readFileSync(this.QQPackageInfoPath).toString());
|
this.QQPackageInfo = JSON.parse(fs.readFileSync(this.QQPackageInfoPath).toString());
|
||||||
const { appid: IQQVersionAppid, qua: IQQVersionQua } = this.getAppidV2();
|
const { appid: IQQVersionAppid, qua: IQQVersionQua } = this.getAppidV2();
|
||||||
this.QQVersionAppid = IQQVersionAppid;
|
this.QQVersionAppid = IQQVersionAppid;
|
||||||
@@ -51,26 +53,22 @@ export class QQBasicInfoWrapper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//此方法不要直接使用
|
//此方法不要直接使用
|
||||||
getQUAInternal() {
|
getQUAFallback() {
|
||||||
switch (systemPlatform) {
|
const platformMapping: Partial<Record<NodeJS.Platform, string>> = {
|
||||||
case 'linux':
|
win32: `V1_WIN_${this.getFullQQVesion()}_${this.getQQBuildStr()}_GW_B`,
|
||||||
return `V1_LNX_${this.getFullQQVesion()}_${this.getQQBuildStr()}_GW_B`;
|
darwin: `V1_MAC_${this.getFullQQVesion()}_${this.getQQBuildStr()}_GW_B`,
|
||||||
case 'darwin':
|
linux: `V1_LNX_${this.getFullQQVesion()}_${this.getQQBuildStr()}_GW_B`,
|
||||||
return `V1_MAC_${this.getFullQQVesion()}_${this.getQQBuildStr()}_GW_B`;
|
};
|
||||||
default:
|
return platformMapping[systemPlatform] ?? (platformMapping.win32)!;
|
||||||
return `V1_WIN_${this.getFullQQVesion()}_${this.getQQBuildStr()}_GW_B`;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getAppidInternal() {
|
getAppIdFallback() {
|
||||||
switch (systemPlatform) {
|
const platformMapping: Partial<Record<NodeJS.Platform, string>> = {
|
||||||
case 'linux':
|
win32: '537246092',
|
||||||
return '537243600';
|
darwin: '537246140',
|
||||||
case 'darwin':
|
linux: '537246140',
|
||||||
return '537243441';
|
};
|
||||||
default:
|
return platformMapping[systemPlatform] ?? '537246092';
|
||||||
return '537243538';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getAppidV2(): { appid: string; qua: string } {
|
getAppidV2(): { appid: string; qua: string } {
|
||||||
@@ -86,6 +84,6 @@ export class QQBasicInfoWrapper {
|
|||||||
// else
|
// else
|
||||||
this.context.logger.log(`[QQ版本兼容性检测] 获取Appid异常 请检测NapCat/QQNT是否正常`);
|
this.context.logger.log(`[QQ版本兼容性检测] 获取Appid异常 请检测NapCat/QQNT是否正常`);
|
||||||
this.context.logger.log(`[QQ版本兼容性检测] ${fullVersion} 版本兼容性不佳,可能会导致一些功能无法正常使用`,);
|
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 = {
|
const options = {
|
||||||
hostname: option.hostname,
|
hostname: option.hostname,
|
||||||
port: option.port,
|
port: option.port,
|
||||||
path: option.href,
|
path: option.pathname + option.search,
|
||||||
method: method,
|
method: method,
|
||||||
headers: headers,
|
headers: headers,
|
||||||
};
|
};
|
||||||
|
@@ -1,12 +1,8 @@
|
|||||||
import os from 'node:os';
|
import os from 'node:os';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import { networkInterfaces } from 'os';
|
|
||||||
import { randomUUID } from 'crypto';
|
|
||||||
|
|
||||||
// 缓解Win7设备兼容性问题
|
// 缓解Win7设备兼容性问题
|
||||||
let osName: string;
|
let osName: string;
|
||||||
// 设备ID
|
|
||||||
let machineId: Promise<string>;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
osName = os.hostname();
|
osName = os.hostname();
|
||||||
@@ -14,54 +10,6 @@ try {
|
|||||||
osName = 'NapCat'; // + crypto.randomUUID().substring(0, 4);
|
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();
|
const homeDir = os.homedir();
|
||||||
|
|
||||||
|
@@ -1 +1 @@
|
|||||||
export const napCatVersion = '2.3.0';
|
export const napCatVersion = '3.0.1';
|
||||||
|
@@ -1,9 +1,8 @@
|
|||||||
|
import { MsfChangeReasonType, MsfStatusType } from "../entities/adapter";
|
||||||
|
|
||||||
export class NodeIDependsAdapter {
|
export class NodeIDependsAdapter {
|
||||||
onMSFStatusChange(arg1: number, arg2: number) {
|
onMSFStatusChange(statusType: MsfStatusType, changeReasonType: MsfChangeReasonType) {
|
||||||
// console.log(arg1, arg2);
|
|
||||||
// if (arg1 == 2 && arg2 == 2) {
|
|
||||||
// log("NapCat丢失网络连接,请检查网络")
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onMSFSsoError(args: unknown) {
|
onMSFSsoError(args: unknown) {
|
||||||
|
@@ -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 { defaultVideoThumbB64, getVideoInfo } from '@/common/video';
|
||||||
import ffmpeg from 'fluent-ffmpeg';
|
import ffmpeg from 'fluent-ffmpeg';
|
||||||
import { encodeSilk } from '@/common/audio';
|
import { encodeSilk } from '@/common/audio';
|
||||||
|
import { MessageContext } from '@/onebot/api';
|
||||||
|
|
||||||
export class NTQQFileApi {
|
export class NTQQFileApi {
|
||||||
context: InstanceContext;
|
context: InstanceContext;
|
||||||
core: NapCatCore;
|
core: NapCatCore;
|
||||||
rkeyManager: RkeyManager;
|
rkeyManager: RkeyManager;
|
||||||
|
packetRkey: Array<{ rkey: string; time: number; type: number; }> | undefined;
|
||||||
|
|
||||||
constructor(context: InstanceContext, core: NapCatCore) {
|
constructor(context: InstanceContext, core: NapCatCore) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
this.core = core;
|
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) {
|
async copyFile(filePath: string, destPath: string) {
|
||||||
@@ -71,7 +73,7 @@ export class NTQQFileApi {
|
|||||||
file_uuid: '',
|
file_uuid: '',
|
||||||
});
|
});
|
||||||
|
|
||||||
await this.copyFile(filePath, mediaPath!);
|
await this.copyFile(filePath, mediaPath);
|
||||||
const fileSize = await this.getFileSize(filePath);
|
const fileSize = await this.getFileSize(filePath);
|
||||||
return {
|
return {
|
||||||
md5: fileMd5,
|
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 {
|
const {
|
||||||
fileName: _fileName,
|
fileName: _fileName,
|
||||||
path,
|
path,
|
||||||
@@ -91,6 +93,7 @@ export class NTQQFileApi {
|
|||||||
if (fileSize === 0) {
|
if (fileSize === 0) {
|
||||||
throw new Error('文件异常,大小为0');
|
throw new Error('文件异常,大小为0');
|
||||||
}
|
}
|
||||||
|
context.deleteAfterSentFiles.push(path);
|
||||||
return {
|
return {
|
||||||
elementType: ElementType.FILE,
|
elementType: ElementType.FILE,
|
||||||
elementId: '',
|
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);
|
const { md5, fileName, path, fileSize } = await this.core.apis.FileApi.uploadFile(picPath, ElementType.PIC, subType);
|
||||||
if (fileSize === 0) {
|
if (fileSize === 0) {
|
||||||
throw new Error('文件异常,大小为0');
|
throw new Error('文件异常,大小为0');
|
||||||
}
|
}
|
||||||
const imageSize = await this.core.apis.FileApi.getImageSize(picPath);
|
const imageSize = await this.core.apis.FileApi.getImageSize(picPath);
|
||||||
|
context.deleteAfterSentFiles.push(path);
|
||||||
return {
|
return {
|
||||||
elementType: ElementType.PIC,
|
elementType: ElementType.PIC,
|
||||||
elementId: '',
|
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 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 = {
|
let videoInfo = {
|
||||||
width: 1920, height: 1080,
|
width: 1920, height: 1080,
|
||||||
time: 15,
|
time: 15,
|
||||||
format: 'mp4',
|
format: 'mp4',
|
||||||
size: fileSize,
|
size: 0,
|
||||||
filePath,
|
filePath,
|
||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
videoInfo = await getVideoInfo(path, logger);
|
videoInfo = await getVideoInfo(filePath, logger);
|
||||||
} catch (e) {
|
} 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 = new Map();
|
||||||
const _thumbPath = await new Promise<string | undefined>((resolve, reject) => {
|
const _thumbPath = await new Promise<string | undefined>((resolve, reject) => {
|
||||||
const thumbFileName = `${md5}_0.png`;
|
const thumbFileName = `${md5}_0.png`;
|
||||||
@@ -179,11 +197,13 @@ export class NTQQFileApi {
|
|||||||
const thumbSize = _thumbPath ? (await fsPromises.stat(_thumbPath)).size : 0;
|
const thumbSize = _thumbPath ? (await fsPromises.stat(_thumbPath)).size : 0;
|
||||||
thumbPath.set(0, _thumbPath);
|
thumbPath.set(0, _thumbPath);
|
||||||
const thumbMd5 = _thumbPath ? await calculateFileMD5(_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 {
|
return {
|
||||||
elementType: ElementType.VIDEO,
|
elementType: ElementType.VIDEO,
|
||||||
elementId: '',
|
elementId: '',
|
||||||
videoElement: {
|
videoElement: {
|
||||||
fileName: fileName || _fileName,
|
fileName: uploadName,
|
||||||
filePath: path,
|
filePath: path,
|
||||||
videoMd5: md5,
|
videoMd5: md5,
|
||||||
thumbMd5,
|
thumbMd5,
|
||||||
@@ -207,7 +227,9 @@ export class NTQQFileApi {
|
|||||||
throw new Error('文件异常,大小为0');
|
throw new Error('文件异常,大小为0');
|
||||||
}
|
}
|
||||||
if (converted) {
|
if (converted) {
|
||||||
fsPromises.unlink(silkPath);
|
fsPromises.unlink(silkPath).then().catch(
|
||||||
|
(e) => this.context.logger.logError.bind(this.context.logger)('删除临时文件失败', e)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
elementType: ElementType.PTT,
|
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) {
|
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 (sourcePath && fs.existsSync(sourcePath)) {
|
||||||
if (force) {
|
if (force) {
|
||||||
@@ -298,65 +319,6 @@ export class NTQQFileApi {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async addFileCache(peer: Peer, msgId: string, msgSeq: string, senderUid: string, elemId: string, elemType: string, fileSize: string, fileName: string) {
|
|
||||||
let GroupData;
|
|
||||||
let BuddyData;
|
|
||||||
if (peer.chatType === ChatType.KCHATTYPEGROUP) {
|
|
||||||
GroupData =
|
|
||||||
[{
|
|
||||||
groupCode: peer.peerUid,
|
|
||||||
isConf: false,
|
|
||||||
hasModifyConfGroupFace: true,
|
|
||||||
hasModifyConfGroupName: true,
|
|
||||||
groupName: 'NapCat.Cached',
|
|
||||||
remark: 'NapCat.Cached',
|
|
||||||
}];
|
|
||||||
} else if (peer.chatType === ChatType.KCHATTYPEC2C) {
|
|
||||||
BuddyData = [{
|
|
||||||
category_name: 'NapCat.Cached',
|
|
||||||
peerUid: peer.peerUid,
|
|
||||||
peerUin: peer.peerUid,
|
|
||||||
remark: 'NapCat.Cached',
|
|
||||||
}];
|
|
||||||
} else {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.context.session.getSearchService().addSearchHistory({
|
|
||||||
type: 4,
|
|
||||||
contactList: [],
|
|
||||||
id: -1,
|
|
||||||
groupInfos: [],
|
|
||||||
msgs: [],
|
|
||||||
fileInfos: [
|
|
||||||
{
|
|
||||||
chatType: peer.chatType,
|
|
||||||
buddyChatInfo: BuddyData || [],
|
|
||||||
discussChatInfo: [],
|
|
||||||
groupChatInfo: GroupData || [],
|
|
||||||
dataLineChatInfo: [],
|
|
||||||
tmpChatInfo: [],
|
|
||||||
msgId: msgId,
|
|
||||||
msgSeq: msgSeq,
|
|
||||||
msgTime: Math.floor(Date.now() / 1000).toString(),
|
|
||||||
senderUid: senderUid,
|
|
||||||
senderNick: 'NapCat.Cached',
|
|
||||||
senderRemark: 'NapCat.Cached',
|
|
||||||
senderCard: 'NapCat.Cached',
|
|
||||||
elemId: elemId,
|
|
||||||
elemType: elemType,
|
|
||||||
fileSize: fileSize,
|
|
||||||
filePath: '',
|
|
||||||
fileName: fileName,
|
|
||||||
hits: [{
|
|
||||||
start: 12,
|
|
||||||
end: 14,
|
|
||||||
}],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async searchForFile(keys: string[]): Promise<SearchResultItem | undefined> {
|
async searchForFile(keys: string[]): Promise<SearchResultItem | undefined> {
|
||||||
const randomResultId = 100000 + Math.floor(Math.random() * 10000);
|
const randomResultId = 100000 + Math.floor(Math.random() * 10000);
|
||||||
let searchId = 0;
|
let searchId = 0;
|
||||||
@@ -398,30 +360,63 @@ export class NTQQFileApi {
|
|||||||
if (!element) {
|
if (!element) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
const url: string = element.originImageUrl!; // 没有域名
|
const url: string = element.originImageUrl ?? '';
|
||||||
const md5HexStr = element.md5HexStr;
|
const md5HexStr = element.md5HexStr;
|
||||||
const fileMd5 = element.md5HexStr;
|
const fileMd5 = element.md5HexStr;
|
||||||
|
|
||||||
if (url) {
|
if (url) {
|
||||||
const parsedUrl = new URL(IMAGE_HTTP_HOST + url);//临时解析拼接
|
const parsedUrl = new URL(IMAGE_HTTP_HOST + url);
|
||||||
|
const urlRkey = parsedUrl.searchParams.get('rkey');
|
||||||
const imageAppid = parsedUrl.searchParams.get('appid');
|
const imageAppid = parsedUrl.searchParams.get('appid');
|
||||||
const isNTFlavoredPic = imageAppid && ['1406', '1407'].includes(imageAppid);
|
const isNTV2 = imageAppid && ['1406', '1407'].includes(imageAppid);
|
||||||
if (isNTFlavoredPic) {
|
const imageFileId = parsedUrl.searchParams.get('fileid');
|
||||||
let rkey = parsedUrl.searchParams.get('rkey');
|
|
||||||
if (rkey) {
|
const rkeyData = {
|
||||||
return IMAGE_HTTP_HOST_NT + url;
|
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();
|
} catch (error: any) {
|
||||||
rkey = imageAppid === '1406' ? rkeyData.private_rkey : rkeyData.group_rkey;
|
this.context.logger.logError.bind(this.context.logger)('获取rkey失败', error.message);
|
||||||
return IMAGE_HTTP_HOST_NT + url + `${rkey}`;
|
|
||||||
} else {
|
|
||||||
// 老的图片url,不需要rkey
|
|
||||||
return IMAGE_HTTP_HOST + url;
|
|
||||||
}
|
}
|
||||||
} 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`;
|
return `${IMAGE_HTTP_HOST}/gchatpic_new/0/0-0-${(fileMd5 ?? md5HexStr)!.toUpperCase()}/0`;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.context.logger.logDebug('图片url获取失败', element);
|
this.context.logger.logDebug('图片url获取失败', element);
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
@@ -6,16 +6,13 @@ export class NTQQFriendApi {
|
|||||||
context: InstanceContext;
|
context: InstanceContext;
|
||||||
core: NapCatCore;
|
core: NapCatCore;
|
||||||
|
|
||||||
// friends: Map<string, Friend> = new Map<string, FriendV2>();
|
|
||||||
|
|
||||||
constructor(context: InstanceContext, core: NapCatCore) {
|
constructor(context: InstanceContext, core: NapCatCore) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
this.core = core;
|
this.core = core;
|
||||||
// if (!this.context.basicInfoWrapper.requireMinNTQQBuild('26702')) {
|
|
||||||
// this.getFriends(true);
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
async setBuddyRemark(uid: string, remark: string) {
|
||||||
|
return this.context.session.getBuddyService().setBuddyRemark({ uid, remark });
|
||||||
|
}
|
||||||
async getBuddyV2SimpleInfoMap(refresh = false) {
|
async getBuddyV2SimpleInfoMap(refresh = false) {
|
||||||
const buddyService = this.context.session.getBuddyService();
|
const buddyService = this.context.session.getBuddyService();
|
||||||
const buddyListV2 = refresh ? await buddyService.getBuddyListV2('0', BuddyListReqType.KNOMAL) : await buddyService.getBuddyListV2('0', BuddyListReqType.KNOMAL);
|
const buddyListV2 = refresh ? await buddyService.getBuddyListV2('0', BuddyListReqType.KNOMAL) : await buddyService.getBuddyListV2('0', BuddyListReqType.KNOMAL);
|
||||||
|
@@ -9,8 +9,9 @@ import {
|
|||||||
MemberExtSourceType,
|
MemberExtSourceType,
|
||||||
NapCatCore,
|
NapCatCore,
|
||||||
} from '@/core';
|
} from '@/core';
|
||||||
import { isNumeric } from '@/common/helper';
|
import { isNumeric, solveAsyncProblem } from '@/common/helper';
|
||||||
import { LimitedHashTable } from '@/common/message-unique';
|
import { LimitedHashTable } from '@/common/message-unique';
|
||||||
|
import { NTEventWrapper } from '@/common/event';
|
||||||
|
|
||||||
export class NTQQGroupApi {
|
export class NTQQGroupApi {
|
||||||
context: InstanceContext;
|
context: InstanceContext;
|
||||||
@@ -19,11 +20,12 @@ export class NTQQGroupApi {
|
|||||||
groupMemberCache: Map<string, Map<string, GroupMember>> = new Map<string, Map<string, GroupMember>>();
|
groupMemberCache: Map<string, Map<string, GroupMember>> = new Map<string, Map<string, GroupMember>>();
|
||||||
groups: Group[] = [];
|
groups: Group[] = [];
|
||||||
essenceLRU = new LimitedHashTable<number, string>(1000);
|
essenceLRU = new LimitedHashTable<number, string>(1000);
|
||||||
|
session: any;
|
||||||
|
|
||||||
constructor(context: InstanceContext, core: NapCatCore) {
|
constructor(context: InstanceContext, core: NapCatCore) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
this.core = core;
|
this.core = core;
|
||||||
this.initCache().then().catch(context.logger.logError);
|
this.initCache().then().catch(context.logger.logError.bind(context.logger));
|
||||||
}
|
}
|
||||||
|
|
||||||
async initCache() {
|
async initCache() {
|
||||||
@@ -32,6 +34,15 @@ export class NTQQGroupApi {
|
|||||||
this.groupCache.set(group.groupCode, group);
|
this.groupCache.set(group.groupCode, group);
|
||||||
}
|
}
|
||||||
this.context.logger.logDebug(`加载${this.groups.length}个群组缓存完成`);
|
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) {
|
async fetchGroupEssenceList(groupCode: string) {
|
||||||
@@ -42,9 +53,11 @@ export class NTQQGroupApi {
|
|||||||
pageLimit: 300,
|
pageLimit: 300,
|
||||||
}, pskey);
|
}, pskey);
|
||||||
}
|
}
|
||||||
|
async getGroupShutUpMemberList(groupCode: string) {
|
||||||
async clearGroupNotifiesUnreadCount(unk: boolean) {
|
return this.context.session.getGroupService().getGroupShutUpMemberList(groupCode);
|
||||||
return this.context.session.getGroupService().clearGroupNotifiesUnreadCount(unk);
|
}
|
||||||
|
async clearGroupNotifiesUnreadCount(uk: boolean) {
|
||||||
|
return this.context.session.getGroupService().clearGroupNotifiesUnreadCount(uk);
|
||||||
}
|
}
|
||||||
|
|
||||||
async setGroupAvatar(gc: string, filePath: string) {
|
async setGroupAvatar(gc: string, filePath: string) {
|
||||||
@@ -60,9 +73,9 @@ export class NTQQGroupApi {
|
|||||||
return groupList;
|
return groupList;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getGroupExtFE0Info(GroupCode: string[], forced = true) {
|
async getGroupExtFE0Info(groupCode: string[], forced = true) {
|
||||||
return this.context.session.getGroupService().getGroupExt0xEF0Info(
|
return this.context.session.getGroupService().getGroupExt0xEF0Info(
|
||||||
GroupCode,
|
groupCode,
|
||||||
[],
|
[],
|
||||||
{
|
{
|
||||||
bindGuildId: 1,
|
bindGuildId: 1,
|
||||||
@@ -120,8 +133,8 @@ export class NTQQGroupApi {
|
|||||||
return group;
|
return group;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getGroupMemberAll(GroupCode: string, forced = false) {
|
async getGroupMemberAll(groupCode: string, forced = false) {
|
||||||
return this.context.session.getGroupService().getAllMemberList(GroupCode, forced);
|
return this.context.session.getGroupService().getAllMemberList(groupCode, forced);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getGroupMember(groupCode: string | number, memberUinOrUid: string | number) {
|
async getGroupMember(groupCode: string | number, memberUinOrUid: string | number) {
|
||||||
@@ -130,7 +143,7 @@ export class NTQQGroupApi {
|
|||||||
let members = this.groupMemberCache.get(groupCodeStr);
|
let members = this.groupMemberCache.get(groupCodeStr);
|
||||||
if (!members) {
|
if (!members) {
|
||||||
try {
|
try {
|
||||||
members = await this.getGroupMembersV2(groupCodeStr);
|
members = await this.getGroupMembers(groupCodeStr);
|
||||||
// 更新群成员列表
|
// 更新群成员列表
|
||||||
this.groupMemberCache.set(groupCodeStr, members);
|
this.groupMemberCache.set(groupCodeStr, members);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -151,13 +164,14 @@ export class NTQQGroupApi {
|
|||||||
|
|
||||||
let member = getMember();
|
let member = getMember();
|
||||||
if (!member) {
|
if (!member) {
|
||||||
members = await this.getGroupMembersV2(groupCodeStr);
|
members = await this.getGroupMembers(groupCodeStr);
|
||||||
member = getMember();
|
member = getMember();
|
||||||
}
|
}
|
||||||
return member;
|
return member;
|
||||||
}
|
}
|
||||||
async getGroupRecommendContactArkJson(GroupCode: string) {
|
|
||||||
return this.context.session.getGroupService().getGroupRecommendContactArkJson(GroupCode);
|
async getGroupRecommendContactArkJson(groupCode: string) {
|
||||||
|
return this.context.session.getGroupService().getGroupRecommendContactArkJson(groupCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
async CreatGroupFileFolder(groupCode: string, folderName: string) {
|
async CreatGroupFileFolder(groupCode: string, folderName: string) {
|
||||||
@@ -173,7 +187,6 @@ export class NTQQGroupApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async addGroupEssence(GroupCode: string, msgId: string) {
|
async addGroupEssence(GroupCode: string, msgId: string) {
|
||||||
// 需要 ob11msgId -> msgId + (peer) -> msgSeq + msgRandom
|
|
||||||
const MsgData = await this.context.session.getMsgService().getMsgsIncludeSelf({
|
const MsgData = await this.context.session.getMsgService().getMsgsIncludeSelf({
|
||||||
chatType: 2,
|
chatType: 2,
|
||||||
guildId: '',
|
guildId: '',
|
||||||
@@ -184,7 +197,6 @@ export class NTQQGroupApi {
|
|||||||
msgRandom: parseInt(MsgData.msgList[0].msgRandom),
|
msgRandom: parseInt(MsgData.msgList[0].msgRandom),
|
||||||
msgSeq: parseInt(MsgData.msgList[0].msgSeq),
|
msgSeq: parseInt(MsgData.msgList[0].msgSeq),
|
||||||
};
|
};
|
||||||
// GetMsgByShortID(shortID); -> MsgService.getMsgs(Peer,MsgId,1,false); -> 组出参数
|
|
||||||
return this.context.session.getGroupService().addGroupEssence(param);
|
return this.context.session.getGroupService().addGroupEssence(param);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -202,7 +214,6 @@ export class NTQQGroupApi {
|
|||||||
groupCode: GroupCode,
|
groupCode: GroupCode,
|
||||||
needDeleteLocalMsg: needDeleteLocalMsg,
|
needDeleteLocalMsg: needDeleteLocalMsg,
|
||||||
};
|
};
|
||||||
//应该是直接返回不需要Listener的 未经测试 需测试再发布
|
|
||||||
return this.context.session.getGroupService().quitGroupV2(param);
|
return this.context.session.getGroupService().quitGroupV2(param);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -265,36 +276,58 @@ export class NTQQGroupApi {
|
|||||||
return member;
|
return member;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getGroupMembersV2(groupQQ: string, num = 3000): Promise<Map<string, GroupMember>> {
|
async searchGroup(groupCode: string) {
|
||||||
const groupService = this.context.session.getGroupService();
|
const [, ret] = await this.core.eventWrapper.callNormalEventV2(
|
||||||
const sceneId = groupService.createMemberListScene(groupQQ, 'groupMemberList_MainWindow');
|
'NodeIKernelSearchService/searchGroup',
|
||||||
const listener = this.core.eventWrapper.registerListen(
|
'NodeIKernelSearchListener/onSearchGroupResult',
|
||||||
'NodeIKernelGroupListener/onMemberListChange',
|
[{
|
||||||
|
keyWords: groupCode,
|
||||||
|
groupNum: 25,
|
||||||
|
exactSearch: false,
|
||||||
|
penetrate: ''
|
||||||
|
}],
|
||||||
|
(ret) => ret.result === 0,
|
||||||
|
(params) => !!params.groupInfos.find(g => g.groupCode === groupCode),
|
||||||
1,
|
1,
|
||||||
500,
|
5000
|
||||||
(params) => params.sceneId === sceneId,
|
|
||||||
);
|
);
|
||||||
try {
|
return ret.groupInfos.find(g => g.groupCode === groupCode);
|
||||||
const [membersFromFunc, membersFromListener] = await Promise.allSettled([
|
}
|
||||||
groupService.getNextMemberList(sceneId, undefined, num),
|
|
||||||
listener,
|
async getGroupMemberEx(GroupCode: string, uid: string, forced = false, retry = 2) {
|
||||||
]);
|
const data = await solveAsyncProblem((eventWrapper: NTEventWrapper, GroupCode: string, uid: string, forced = false) => {
|
||||||
if (membersFromFunc.status === 'fulfilled' && membersFromListener.status === 'fulfilled') {
|
return eventWrapper.callNormalEventV2(
|
||||||
return new Map([
|
'NodeIKernelGroupService/getMemberInfo',
|
||||||
...membersFromFunc.value.result.infos,
|
'NodeIKernelGroupListener/onMemberInfoChange',
|
||||||
...membersFromListener.value[0].infos,
|
[GroupCode, [uid], forced],
|
||||||
]);
|
(ret) => ret.result === 0,
|
||||||
}
|
(params, _, members) => params === GroupCode && members.size > 0 && members.has(uid),
|
||||||
if (membersFromFunc.status === 'fulfilled') {
|
1,
|
||||||
return membersFromFunc.value.result.infos;
|
forced ? 2500 : 250
|
||||||
}
|
);
|
||||||
if (membersFromListener.status === 'fulfilled') {
|
}, this.core.eventWrapper, GroupCode, uid, forced);
|
||||||
return membersFromListener.value[0].infos;
|
if (data && data[3] instanceof Map && data[3].has(uid)) {
|
||||||
}
|
return data[3].get(uid);
|
||||||
throw new Error('获取群成员列表失败');
|
|
||||||
} finally {
|
|
||||||
groupService.destroyMemberListScene(sceneId);
|
|
||||||
}
|
}
|
||||||
|
if (retry > 0) {
|
||||||
|
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 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>> {
|
async getGroupMembers(groupQQ: string, num = 3000): Promise<Map<string, GroupMember>> {
|
||||||
@@ -304,8 +337,7 @@ export class NTQQGroupApi {
|
|||||||
if (result.errCode !== 0) {
|
if (result.errCode !== 0) {
|
||||||
throw new Error('获取群成员列表出错,' + result.errMsg);
|
throw new Error('获取群成员列表出错,' + result.errMsg);
|
||||||
}
|
}
|
||||||
|
this.context.logger.logDebug(`获取群(${groupQQ})成员列表结果:`, `members: ${result.result.infos.size}`);
|
||||||
this.context.logger.logDebug(`获取群(${groupQQ})成员列表结果:`, `members: ${result.result.infos.size}`); //, Array.from(result.result.infos.values()));
|
|
||||||
return result.result.infos;
|
return result.result.infos;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -336,12 +368,12 @@ export class NTQQGroupApi {
|
|||||||
return this.context.session.getGroupService().operateSysNotify(
|
return this.context.session.getGroupService().operateSysNotify(
|
||||||
false,
|
false,
|
||||||
{
|
{
|
||||||
'operateType': operateType, // 2 拒绝
|
operateType: operateType, // 2 拒绝
|
||||||
'targetMsg': {
|
targetMsg: {
|
||||||
'seq': seq, // 通知序列号
|
seq: seq, // 通知序列号
|
||||||
'type': type,
|
type: type,
|
||||||
'groupCode': groupCode,
|
groupCode: groupCode,
|
||||||
'postscript': reason ?? ' ', // 仅传空值可能导致处理失败,故默认给个空格
|
postscript: reason ?? ' ', // 仅传空值可能导致处理失败,故默认给个空格
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -397,7 +429,6 @@ export class NTQQGroupApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getMemberExtInfo(groupCode: string, uin: string) {
|
async getMemberExtInfo(groupCode: string, uin: string) {
|
||||||
// 仅NTQQ 9.9.11 24568测试 容易炸开谨慎使用
|
|
||||||
return this.context.session.getGroupService().getMemberExtInfo(
|
return this.context.session.getGroupService().getMemberExtInfo(
|
||||||
{
|
{
|
||||||
groupCode: groupCode,
|
groupCode: groupCode,
|
||||||
|
@@ -5,5 +5,4 @@ export * from './msg';
|
|||||||
export * from './user';
|
export * from './user';
|
||||||
export * from './webapi';
|
export * from './webapi';
|
||||||
export * from './sign';
|
export * from './sign';
|
||||||
export * from './system';
|
export * from './system';
|
||||||
export * from './cache';
|
|
@@ -19,27 +19,16 @@ export class NTQQMsgApi {
|
|||||||
return this.context.session.getMsgService().getAioFirstViewLatestMsgs(peer, MsgCount);
|
return this.context.session.getMsgService().getAioFirstViewLatestMsgs(peer, MsgCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getLatestDbMsgs(peer: Peer, MsgCount: number) {
|
|
||||||
return this.context.session.getMsgService().getLatestDbMsgs(peer, MsgCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
async FetchLongMsg(peer: Peer, msgId: string) {
|
|
||||||
return this.context.session.getMsgService().fetchLongMsg(peer, msgId);
|
|
||||||
}
|
|
||||||
|
|
||||||
async sendShowInputStatusReq(peer: Peer, eventType: number) {
|
async sendShowInputStatusReq(peer: Peer, eventType: number) {
|
||||||
return this.context.session.getMsgService().sendShowInputStatusReq(peer.chatType, eventType, peer.peerUid);
|
return this.context.session.getMsgService().sendShowInputStatusReq(peer.chatType, eventType, peer.peerUid);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getMsgEmojiLikesList(peer: Peer, msgSeq: string, emojiId: string, emojiType: string, count: number = 20) {
|
async getMsgEmojiLikesList(peer: Peer, msgSeq: string, emojiId: string, emojiType: string, count: number = 20) {
|
||||||
//注意此处emojiType 可选值一般为1-2 2好像是unicode表情dec值 大部分情况 Taged M likiowa
|
//注意此处emojiType 可选值一般为1-2 2好像是unicode表情dec值 大部分情况 Taged Mlikiowa
|
||||||
return this.context.session.getMsgService().getMsgEmojiLikesList(peer, msgSeq, emojiId, emojiType, '', false, count);
|
return this.context.session.getMsgService().getMsgEmojiLikesList(peer, msgSeq, emojiId, emojiType, '', false, count);
|
||||||
}
|
}
|
||||||
|
|
||||||
async setEmojiLike(peer: Peer, msgSeq: string, emojiId: string, set: boolean = true) {
|
async setEmojiLike(peer: Peer, msgSeq: string, emojiId: string, set: boolean = true) {
|
||||||
// nt_qq//global//nt_data//Emoji//emoji-resource//sysface_res/apng/ 下可以看到所有QQ表情预览
|
|
||||||
// nt_qq\global\nt_data\Emoji\emoji-resource\face_config.json 里面有所有表情的id, 自带表情id是QSid, 标准emoji表情id是QCid
|
|
||||||
// 其实以官方文档为准是最好的,https://bot.q.qq.com/wiki/develop/api-v2/openapi/emoji/model.html#EmojiType
|
|
||||||
emojiId = emojiId.toString();
|
emojiId = emojiId.toString();
|
||||||
return this.context.session.getMsgService().setMsgEmojiLikes(peer, msgSeq, emojiId, emojiId.length > 3 ? '2' : '1', set);
|
return this.context.session.getMsgService().setMsgEmojiLikes(peer, msgSeq, emojiId, emojiId.length > 3 ? '2' : '1', set);
|
||||||
}
|
}
|
||||||
@@ -105,17 +94,10 @@ export class NTQQMsgApi {
|
|||||||
pageLimit: 1,
|
pageLimit: 1,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
//@deprecated
|
||||||
async getMsgsBySeqAndCount(peer: Peer, seq: string, count: number, desc: boolean, z: boolean) {
|
async getMsgsBySeqAndCount(peer: Peer, seq: string, count: number, desc: boolean, z: boolean) {
|
||||||
return await this.context.session.getMsgService().getMsgsBySeqAndCount(peer, seq, count, desc, z);
|
return await this.context.session.getMsgService().getMsgsBySeqAndCount(peer, seq, count, desc, z);
|
||||||
}
|
}
|
||||||
async getMsgBySeqList(peer: Peer, msgSeqList: string[]) {
|
|
||||||
//坏的
|
|
||||||
return await this.context.session.getMsgService().getMsgsBySeqList(peer, msgSeqList);
|
|
||||||
}
|
|
||||||
async getMsgBySeqExFirstMsg(peer: Peer, rootMsgId: string, replyMsgId: string) {
|
|
||||||
let reply = await this.context.session.getMsgService().getSourceOfReplyMsgV2(peer, rootMsgId, replyMsgId);
|
|
||||||
console.log(reply);
|
|
||||||
}
|
|
||||||
async getMsgExBySeq(peer: Peer, msgSeq: string) {
|
async getMsgExBySeq(peer: Peer, msgSeq: string) {
|
||||||
const DateNow = Math.floor(Date.now() / 1000);
|
const DateNow = Math.floor(Date.now() / 1000);
|
||||||
const filterMsgFromTime = (DateNow - 300).toString();
|
const filterMsgFromTime = (DateNow - 300).toString();
|
||||||
@@ -157,11 +139,16 @@ export class NTQQMsgApi {
|
|||||||
return this.context.session.getMsgService().getMsgsIncludeSelf(peer, msgId, count, isReverseOrder);
|
return this.context.session.getMsgService().getMsgsIncludeSelf(peer, msgId, count, isReverseOrder);
|
||||||
}
|
}
|
||||||
|
|
||||||
async recallMsg(peer: Peer, msgIds: string[]) {
|
async recallMsg(peer: Peer, msgId: string) {
|
||||||
await this.context.session.getMsgService().recallMsg({
|
await this.core.eventWrapper.callNormalEventV2(
|
||||||
chatType: peer.chatType,
|
'NodeIKernelMsgService/recallMsg',
|
||||||
peerUid: peer.peerUid,
|
'NodeIKernelMsgListener/onMsgInfoListUpdate',
|
||||||
}, msgIds);
|
[peer, [msgId]],
|
||||||
|
() => true,
|
||||||
|
(updatedList) => updatedList.find(m => m.msgId === msgId && m.recallTime !== '0') !== undefined,
|
||||||
|
1,
|
||||||
|
1000,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async PrepareTempChat(toUserUid: string, GroupCode: string, nickname: string) {
|
async PrepareTempChat(toUserUid: string, GroupCode: string, nickname: string) {
|
||||||
@@ -195,7 +182,7 @@ export class NTQQMsgApi {
|
|||||||
await this.PrepareTempChat(peer.peerUid, peer.guildId, member.nick);
|
await this.PrepareTempChat(peer.peerUid, peer.guildId, member.nick);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const msgId = await this.generateMsgUniqueId(peer.chatType, await this.getServerTime());
|
const msgId = await this.generateMsgUniqueId(peer.chatType);
|
||||||
peer.guildId = msgId;
|
peer.guildId = msgId;
|
||||||
const [, msgList] = await this.core.eventWrapper.callNormalEventV2(
|
const [, msgList] = await this.core.eventWrapper.callNormalEventV2(
|
||||||
'NodeIKernelMsgService/sendMsg',
|
'NodeIKernelMsgService/sendMsg',
|
||||||
@@ -221,12 +208,8 @@ export class NTQQMsgApi {
|
|||||||
return msgList.find(msgRecord => msgRecord.guildId === msgId);
|
return msgList.find(msgRecord => msgRecord.guildId === msgId);
|
||||||
}
|
}
|
||||||
|
|
||||||
async generateMsgUniqueId(chatType: number, time: string) {
|
async generateMsgUniqueId(chatType: number) {
|
||||||
return this.context.session.getMsgService().generateMsgUniqueId(chatType, time);
|
return this.context.session.getMsgService().generateMsgUniqueId(chatType, this.context.session.getMSFService().getServerTime());
|
||||||
}
|
|
||||||
|
|
||||||
async getServerTime() {
|
|
||||||
return this.context.session.getMSFService().getServerTime();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async forwardMsg(srcPeer: Peer, destPeer: Peer, msgIds: string[]) {
|
async forwardMsg(srcPeer: Peer, destPeer: Peer, msgIds: string[]) {
|
||||||
|
143
src/core/apis/packet.ts
Normal file
143
src/core/apis/packet.ts
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
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";
|
||||||
|
|
||||||
|
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));
|
||||||
|
await this.packetSession.client.connect();
|
||||||
|
await this.packetSession.client.init(process.pid, table.recv, table.send);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async sendPacket(cmd: string, data: PacketHexStr, rsp = false): Promise<RecvPacketData> {
|
||||||
|
return this.packetSession!.client.sendPacket(cmd, data, rsp);
|
||||||
|
}
|
||||||
|
|
||||||
|
async sendPokePacket(group: number, peer: number) {
|
||||||
|
const data = this.packetSession?.packer.packPokePacket(group, peer);
|
||||||
|
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=`
|
||||||
|
}
|
||||||
|
}
|
@@ -214,11 +214,6 @@ export class NTQQMusicSignApi {
|
|||||||
//console.log(MusicReal);
|
//console.log(MusicReal);
|
||||||
return { ...MusicReal.data, mid: signedMid };
|
return { ...MusicReal.data, mid: signedMid };
|
||||||
}
|
}
|
||||||
|
|
||||||
async CreateMusicThirdWay1(id: string = '', mid: string = '') {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
//转换外域名为 https://qq.ugcimg.cn/v1/cpqcbu4b8870i61bde6k7cbmjgejq8mr3in82qir4qi7ielffv5slv8ck8g42novtmev26i233ujtuab6tvu2l2sjgtupfr389191v00s1j5oh5325j5eqi40774jv1i/khovifoh7jrqd6eahoiv7koh8o
|
//转换外域名为 https://qq.ugcimg.cn/v1/cpqcbu4b8870i61bde6k7cbmjgejq8mr3in82qir4qi7ielffv5slv8ck8g42novtmev26i233ujtuab6tvu2l2sjgtupfr389191v00s1j5oh5325j5eqi40774jv1i/khovifoh7jrqd6eahoiv7koh8o
|
||||||
//https://cgi.connect.qq.com/qqconnectopen/openapi/change_image_url?url=https://th.bing.com/th?id=OSK.b8ed36f1fb1889de6dc84fd81c187773&w=46&h=46&c=11&rs=1&qlt=80&o=6&dpr=2&pid=SANGAM
|
//https://cgi.connect.qq.com/qqconnectopen/openapi/change_image_url?url=https://th.bing.com/th?id=OSK.b8ed36f1fb1889de6dc84fd81c187773&w=46&h=46&c=11&rs=1&qlt=80&o=6&dpr=2&pid=SANGAM
|
||||||
|
|
||||||
|
@@ -22,7 +22,7 @@ export class NTQQSystemApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getOnlineDev() {
|
async getOnlineDev() {
|
||||||
return this.context.session.getMsgService().getOnLineDev();
|
this.context.session.getMsgService().getOnLineDev();
|
||||||
}
|
}
|
||||||
|
|
||||||
async getArkJsonCollection(cid: string) {
|
async getArkJsonCollection(cid: string) {
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
import type { ModifyProfileParams, User } from '@/core/entities';
|
import { ModifyProfileParams, User, UserDetailSource } from '@/core/entities';
|
||||||
import { RequestUtil } from '@/common/request';
|
import { RequestUtil } from '@/common/request';
|
||||||
import { ProfileBizType, UserDetailSource } from '@/core/services';
|
import { InstanceContext, NapCatCore, ProfileBizType } from '..';
|
||||||
import { InstanceContext, NapCatCore } from '..';
|
|
||||||
import { solveAsyncProblem } from '@/common/helper';
|
import { solveAsyncProblem } from '@/common/helper';
|
||||||
|
|
||||||
export class NTQQUserApi {
|
export class NTQQUserApi {
|
||||||
@@ -12,7 +11,13 @@ export class NTQQUserApi {
|
|||||||
this.context = context;
|
this.context = context;
|
||||||
this.core = core;
|
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) {
|
async getProfileLike(uid: string) {
|
||||||
return this.context.session.getProfileLikeService().getBuddyProfileLike({
|
return this.context.session.getProfileLikeService().getBuddyProfileLike({
|
||||||
friendUids: [uid],
|
friendUids: [uid],
|
||||||
@@ -25,7 +30,18 @@ export class NTQQUserApi {
|
|||||||
limit: 20,
|
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) {
|
async setLongNick(longNick: string) {
|
||||||
return this.context.session.getProfileService().setLongNick(longNick);
|
return this.context.session.getProfileService().setLongNick(longNick);
|
||||||
}
|
}
|
||||||
@@ -52,8 +68,7 @@ export class NTQQUserApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async setQQAvatar(filePath: string) {
|
async setQQAvatar(filePath: string) {
|
||||||
type setQQAvatarRet = { result: number, errMsg: string };
|
const ret = await this.context.session.getProfileService().setHeader(filePath);
|
||||||
const ret = await this.context.session.getProfileService().setHeader(filePath) as setQQAvatarRet;
|
|
||||||
return { result: ret?.result, errMsg: ret?.errMsg };
|
return { result: ret?.result, errMsg: ret?.errMsg };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,12 +119,20 @@ export class NTQQUserApi {
|
|||||||
return this.context.session.getProfileService().modifyDesktopMiniProfile(param);
|
return this.context.session.getProfileService().modifyDesktopMiniProfile(param);
|
||||||
}
|
}
|
||||||
|
|
||||||
//需要异常处理
|
|
||||||
async getCookies(domain: string) {
|
async getCookies(domain: string) {
|
||||||
const ClientKeyData = await this.forceFetchClientKey();
|
const ClientKeyData = await this.forceFetchClientKey();
|
||||||
const requestUrl = 'https://ssl.ptlogin2.qq.com/jump?ptlang=1033&clientuin=' + this.core.selfInfo.uin +
|
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';
|
'&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[]) {
|
async getPSkey(domainList: string[]) {
|
||||||
@@ -160,7 +183,7 @@ export class NTQQUserApi {
|
|||||||
if (uid) return uid;
|
if (uid) return uid;
|
||||||
uid = (await this.context.session.getUixConvertService().getUid([Uin])).uidInfo.get(Uin);
|
uid = (await this.context.session.getUixConvertService().getUid([Uin])).uidInfo.get(Uin);
|
||||||
if (uid) return uid;
|
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 (unverifiedUid.indexOf('*') == -1) uid = unverifiedUid;
|
||||||
//if (uid) return uid;
|
//if (uid) return uid;
|
||||||
return uid;
|
return uid;
|
||||||
@@ -196,7 +219,7 @@ export class NTQQUserApi {
|
|||||||
return await this.context.session.getRecentContactService().getRecentContactList();
|
return await this.context.session.getRecentContactService().getRecentContactList();
|
||||||
}
|
}
|
||||||
|
|
||||||
async getUserDetailInfoByUinV2(Uin: string) {
|
async getUserDetailInfoByUin(Uin: string) {
|
||||||
return await this.core.eventWrapper.callNoListenerEvent(
|
return await this.core.eventWrapper.callNoListenerEvent(
|
||||||
'NodeIKernelProfileService/getUserDetailInfoByUin',
|
'NodeIKernelProfileService/getUserDetailInfoByUin',
|
||||||
Uin
|
Uin
|
||||||
|
@@ -157,7 +157,7 @@ export class NTQQWebApi {
|
|||||||
const cookieObject = await this.core.apis.UserApi.getCookies('qun.qq.com');
|
const cookieObject = await this.core.apis.UserApi.getCookies('qun.qq.com');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let settings = JSON.stringify({
|
const settings = JSON.stringify({
|
||||||
is_show_edit_card: is_show_edit_card,
|
is_show_edit_card: is_show_edit_card,
|
||||||
tip_window_type: tip_window_type,
|
tip_window_type: tip_window_type,
|
||||||
confirm_required: confirm_required
|
confirm_required: confirm_required
|
||||||
@@ -167,7 +167,7 @@ export class NTQQWebApi {
|
|||||||
imgWidth: imgWidth.toString(),
|
imgWidth: imgWidth.toString(),
|
||||||
imgHeight: imgHeight.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({
|
`https://web.qun.qq.com/cgi-bin/announce/add_qun_notice?${new URLSearchParams({
|
||||||
bkn: this.getBknFromCookie(cookieObject),
|
bkn: this.getBknFromCookie(cookieObject),
|
||||||
qid: GroupCode,
|
qid: GroupCode,
|
||||||
@@ -264,7 +264,7 @@ export class NTQQWebApi {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.context.logger.logError('获取龙王信息失败');
|
this.context.logger.logError.bind(this.context.logger)('获取龙王信息失败');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (getType === WebHonorType.PERFORMER || getType === WebHonorType.ALL) {
|
if (getType === WebHonorType.PERFORMER || getType === WebHonorType.ALL) {
|
||||||
@@ -280,7 +280,7 @@ export class NTQQWebApi {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.context.logger.logError('获取群聊之火失败');
|
this.context.logger.logError.bind(this.context.logger)('获取群聊之火失败');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (getType === WebHonorType.PERFORMER || getType === WebHonorType.ALL) {
|
if (getType === WebHonorType.PERFORMER || getType === WebHonorType.ALL) {
|
||||||
@@ -296,7 +296,7 @@ export class NTQQWebApi {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.context.logger.logError('获取群聊炽焰失败');
|
this.context.logger.logError.bind(this.context.logger)('获取群聊炽焰失败');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (getType === WebHonorType.EMOTION || getType === WebHonorType.ALL) {
|
if (getType === WebHonorType.EMOTION || getType === WebHonorType.ALL) {
|
||||||
@@ -312,7 +312,7 @@ export class NTQQWebApi {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.context.logger.logError('获取快乐源泉失败');
|
this.context.logger.logError.bind(this.context.logger)('获取快乐源泉失败');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
11
src/core/entities/adapter.ts
Normal file
11
src/core/entities/adapter.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
export enum MsfStatusType {
|
||||||
|
KUNKNOWN,
|
||||||
|
KDISCONNECTED,
|
||||||
|
KCONNECTED
|
||||||
|
}
|
||||||
|
export enum MsfChangeReasonType {
|
||||||
|
KUNKNOWN,
|
||||||
|
KUSERLOGININ,
|
||||||
|
KUSERLOGINOUT,
|
||||||
|
KAUTO
|
||||||
|
}
|
12
src/core/entities/contact.ts
Normal file
12
src/core/entities/contact.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
|
||||||
|
export interface FSABRecentContactParams {
|
||||||
|
anchorPointContact: {
|
||||||
|
contactId: string;
|
||||||
|
sortField: string;
|
||||||
|
pos: number;
|
||||||
|
};
|
||||||
|
relativeMoveCount: number;
|
||||||
|
listType: number;
|
||||||
|
count: number;
|
||||||
|
fetchOld: boolean;
|
||||||
|
}
|
@@ -117,6 +117,7 @@ export enum GroupMemberRole {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface GroupMember {
|
export interface GroupMember {
|
||||||
|
memberRealLevel: string | undefined;
|
||||||
memberSpecialTitle?: string;
|
memberSpecialTitle?: string;
|
||||||
avatarPath: string;
|
avatarPath: string;
|
||||||
cardName: string;
|
cardName: string;
|
||||||
|
@@ -372,6 +372,7 @@ export interface ReplyElement {
|
|||||||
senderUin: string;
|
senderUin: string;
|
||||||
senderUidStr?: string;
|
senderUidStr?: string;
|
||||||
replyMsgTime?: string;
|
replyMsgTime?: string;
|
||||||
|
replyMsgClientSeq?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SendReplyElement {
|
export interface SendReplyElement {
|
||||||
@@ -391,7 +392,7 @@ export interface SendMarketFaceElement {
|
|||||||
marketFaceElement: MarketFaceElement;
|
marketFaceElement: MarketFaceElement;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SendstructLongMsgElement {
|
export interface SendStructLongMsgElement {
|
||||||
elementType: ElementType.STRUCTLONGMSG;
|
elementType: ElementType.STRUCTLONGMSG;
|
||||||
elementId: string;
|
elementId: string;
|
||||||
structLongMsgElement: StructLongMsgElement;
|
structLongMsgElement: StructLongMsgElement;
|
||||||
@@ -516,6 +517,12 @@ export enum AtType {
|
|||||||
atAll = 1,
|
atAll = 1,
|
||||||
atUser = 2
|
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分析
|
// 来自Android分析
|
||||||
export enum ChatType {
|
export enum ChatType {
|
||||||
@@ -659,7 +666,8 @@ export interface GrayTipElement {
|
|||||||
export enum FaceType {
|
export enum FaceType {
|
||||||
normal = 1, // 小黄脸
|
normal = 1, // 小黄脸
|
||||||
normal2 = 2, // 新小黄脸, 从faceIndex 222开始?
|
normal2 = 2, // 新小黄脸, 从faceIndex 222开始?
|
||||||
dice = 3 // 骰子
|
dice = 3, // 骰子
|
||||||
|
poke = 5 // 拍一拍
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum FaceIndex {
|
export enum FaceIndex {
|
||||||
@@ -872,6 +880,8 @@ export interface RawMessage {
|
|||||||
/**
|
/**
|
||||||
* 扩展字段,与 Ob11 msg ID 有关
|
* 扩展字段,与 Ob11 msg ID 有关
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
id?: number;
|
id?: number;
|
||||||
|
|
||||||
guildId: string;
|
guildId: string;
|
||||||
@@ -908,11 +918,26 @@ export interface RawMessage {
|
|||||||
*/
|
*/
|
||||||
peerUin: string;
|
peerUin: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 好友备注(如果是好友消息)
|
||||||
|
*/
|
||||||
|
remark?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 群名(如果是群消息)
|
||||||
|
*/
|
||||||
|
peerName: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 发送者昵称(如果是好友消息)
|
* 发送者昵称(如果是好友消息)
|
||||||
*/
|
*/
|
||||||
sendNickName: string;
|
sendNickName: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送者好友备注(如果是群消息并且有发送者好友)
|
||||||
|
*/
|
||||||
|
sendRemarkName: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 发送者群名片(如果是群消息)
|
* 发送者群名片(如果是群消息)
|
||||||
*/
|
*/
|
||||||
@@ -933,4 +958,46 @@ export interface RawMessage {
|
|||||||
records: RawMessage[];
|
records: RawMessage[];
|
||||||
|
|
||||||
elements: MessageElement[];
|
elements: MessageElement[];
|
||||||
|
|
||||||
|
sourceType: MsgSourceType;
|
||||||
}
|
}
|
||||||
|
export interface QueryMsgsParams {
|
||||||
|
chatInfo: Peer;
|
||||||
|
filterMsgType: [];
|
||||||
|
filterSendersUid: string[];
|
||||||
|
filterMsgFromTime: string;
|
||||||
|
filterMsgToTime: string;
|
||||||
|
pageLimit: number;
|
||||||
|
isReverseOrder: boolean;
|
||||||
|
isIncludeCurrent: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TmpChatInfoApi {
|
||||||
|
errMsg: string;
|
||||||
|
result: number;
|
||||||
|
tmpChatInfo?: TmpChatInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TmpChatInfo {
|
||||||
|
chatType: number;
|
||||||
|
fromNick: string;
|
||||||
|
groupCode: string;
|
||||||
|
peerUid: string;
|
||||||
|
sessionType: number;
|
||||||
|
sig: string;
|
||||||
|
}
|
||||||
|
export interface 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 {
|
export interface IdMusicSignPostData {
|
||||||
type: 'qq' | '163',
|
type: 'qq' | '163' | 'kugou' | 'migu' | 'kuwo',
|
||||||
id: string | number,
|
id: string | number,
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CustomMusicSignPostData {
|
export interface CustomMusicSignPostData {
|
||||||
type: 'custom',
|
type: 'qq' | '163' | 'kugou' | 'migu' | 'kuwo' | 'custom',
|
||||||
|
id: undefined,
|
||||||
url: string,
|
url: string,
|
||||||
audio: string,
|
audio?: string,
|
||||||
title: string,
|
title?: string,
|
||||||
image?: string,
|
image: string,
|
||||||
singer?: string
|
singer?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -153,7 +153,10 @@ interface CommonExt {
|
|||||||
labels: any[];
|
labels: any[];
|
||||||
qqLevel: QQLevel;
|
qqLevel: QQLevel;
|
||||||
}
|
}
|
||||||
|
export enum BuddyListReqType {
|
||||||
|
KNOMAL,
|
||||||
|
KLETTER
|
||||||
|
}
|
||||||
interface Pic {
|
interface Pic {
|
||||||
picId: string;
|
picId: string;
|
||||||
picTime: number;
|
picTime: number;
|
||||||
@@ -213,7 +216,7 @@ export interface BuddyProfileLikeReq {
|
|||||||
userProfile: number;
|
userProfile: number;
|
||||||
type: number;
|
type: number;
|
||||||
start: number;
|
start: number;
|
||||||
limit: number;
|
limit?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface QQLevel {
|
export interface QQLevel {
|
||||||
@@ -285,16 +288,16 @@ export interface User {
|
|||||||
export interface SelfInfo extends User {
|
export interface SelfInfo extends User {
|
||||||
online?: boolean;
|
online?: boolean;
|
||||||
}
|
}
|
||||||
|
export type Friend = User;
|
||||||
|
|
||||||
export interface Friend extends User {
|
// 本来是 Friend extends User 现在用不到
|
||||||
}
|
|
||||||
|
|
||||||
export enum BizKey {
|
export enum BizKey {
|
||||||
KPRIVILEGEICON,
|
KPRIVILEGEICON,
|
||||||
KPHOTOWALL
|
KPHOTOWALL
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UserDetailInfoByUinV2 {
|
export interface UserDetailInfoByUin {
|
||||||
result: number,
|
result: number,
|
||||||
errMsg: string,
|
errMsg: string,
|
||||||
detail: {
|
detail: {
|
||||||
@@ -305,62 +308,15 @@ export interface UserDetailInfoByUinV2 {
|
|||||||
photoWall: null
|
photoWall: null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
export enum UserDetailSource {
|
||||||
export interface UserDetailInfoByUin {
|
KDB,
|
||||||
result: number,
|
KSERVER
|
||||||
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 ProfileBizType {
|
||||||
|
KALL,
|
||||||
|
KBASEEXTEND,
|
||||||
|
KVAS,
|
||||||
|
KQZONE,
|
||||||
|
KOTHER
|
||||||
|
}
|
60
src/core/external/appid.json
vendored
60
src/core/external/appid.json
vendored
@@ -1,14 +1,54 @@
|
|||||||
{
|
{
|
||||||
"3.2.12-27597": {
|
"9.9.15-28060": {
|
||||||
"appid": 537243600,
|
"appid": 537246092,
|
||||||
"qua": "V1_LNX_NQ_3.2.12_27597_GW_B"
|
"qua": "V1_WIN_NQ_9.9.15_28060_GW_B"
|
||||||
},
|
},
|
||||||
"9.9.15-27597": {
|
"9.9.15-28131": {
|
||||||
"appid": 537243441,
|
"appid": 537246092,
|
||||||
"qua": "V1_WIN_NQ_9.9.15_27597_GW_B"
|
"qua": "V1_WIN_NQ_9.9.15_28131_GW_B"
|
||||||
},
|
},
|
||||||
"6.9.53-27597": {
|
"3.2.12-28060": {
|
||||||
"appid": 537243538,
|
"appid": 537246140,
|
||||||
"qua": "V1_MAC_NQ_6.9.53_27597_GW_B"
|
"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,
|
"fileLog": true,
|
||||||
"consoleLog": true,
|
"consoleLog": true,
|
||||||
"fileLogLevel": "debug",
|
"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 {
|
export class RkeyManager {
|
||||||
serverUrl: string = '';
|
serverUrl: string[] = [];
|
||||||
logger: LogWrapper;
|
logger: LogWrapper;
|
||||||
private rkeyData: ServerRkeyData = {
|
private rkeyData: ServerRkeyData = {
|
||||||
group_rkey: '',
|
group_rkey: '',
|
||||||
@@ -16,7 +16,7 @@ export class RkeyManager {
|
|||||||
expired_time: 0,
|
expired_time: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(serverUrl: string, logger: LogWrapper) {
|
constructor(serverUrl: string[], logger: LogWrapper) {
|
||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
this.serverUrl = serverUrl;
|
this.serverUrl = serverUrl;
|
||||||
}
|
}
|
||||||
@@ -26,7 +26,8 @@ export class RkeyManager {
|
|||||||
try {
|
try {
|
||||||
await this.refreshRkey();
|
await this.refreshRkey();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.logger.logError('获取rkey失败', e);
|
throw new Error(`获取rkey失败: ${e}`);//外抛
|
||||||
|
//this.logger.logError.bind(this.logger)('获取rkey失败', e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return this.rkeyData;
|
return this.rkeyData;
|
||||||
@@ -40,6 +41,22 @@ export class RkeyManager {
|
|||||||
|
|
||||||
async refreshRkey(): Promise<any> {
|
async refreshRkey(): Promise<any> {
|
||||||
//刷新rkey
|
//刷新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 { NapCatPathWrapper } from '@/common/path';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import fs from 'node:fs';
|
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 { NTEventWrapper } from '@/common/event';
|
||||||
import { DataSource, GroupMember, KickedOffLineInfo, SelfInfo, SelfStatusInfo } from '@/core/entities';
|
import { DataSource, GroupMember, KickedOffLineInfo, SelfInfo, SelfStatusInfo } from '@/core/entities';
|
||||||
import { NapCatConfigLoader } from '@/core/helper/config';
|
import { NapCatConfigLoader } from '@/core/helper/config';
|
||||||
import os from 'node:os';
|
import os from 'node:os';
|
||||||
import { NodeIKernelGroupListener, NodeIKernelMsgListener, NodeIKernelProfileListener } from '@/core/listeners';
|
import { NodeIKernelGroupListener, NodeIKernelMsgListener, NodeIKernelProfileListener } from '@/core/listeners';
|
||||||
import { proxiedListenerOf } from '@/common/proxy-handler';
|
import { proxiedListenerOf } from '@/common/proxy-handler';
|
||||||
|
import { NTQQPacketApi } from './apis/packet';
|
||||||
export * from './wrapper';
|
export * from './wrapper';
|
||||||
export * from './entities';
|
export * from './entities';
|
||||||
export * from './services';
|
export * from './services';
|
||||||
@@ -45,12 +45,18 @@ export function loadQQWrapper(QQVersion: string): WrapperNodeApi {
|
|||||||
let appPath;
|
let appPath;
|
||||||
if (os.platform() === 'darwin') {
|
if (os.platform() === 'darwin') {
|
||||||
appPath = path.resolve(path.dirname(process.execPath), '../Resources/app');
|
appPath = path.resolve(path.dirname(process.execPath), '../Resources/app');
|
||||||
} else {
|
} else if (os.platform() === 'linux') {
|
||||||
appPath = path.resolve(path.dirname(process.execPath), './resources/app');
|
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');
|
let wrapperNodePath = path.resolve(appPath, 'wrapper.node');
|
||||||
if (!fs.existsSync(wrapperNodePath)) {
|
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: {} };
|
const nativemodule: any = { exports: {} };
|
||||||
process.dlopen(nativemodule, wrapperNodePath);
|
process.dlopen(nativemodule, wrapperNodePath);
|
||||||
@@ -75,17 +81,18 @@ export class NapCatCore {
|
|||||||
this.context = context;
|
this.context = context;
|
||||||
this.util = this.context.wrapper.NodeQQNTWrapperUtil;
|
this.util = this.context.wrapper.NodeQQNTWrapperUtil;
|
||||||
this.eventWrapper = new NTEventWrapper(context.session);
|
this.eventWrapper = new NTEventWrapper(context.session);
|
||||||
|
this.configLoader = new NapCatConfigLoader(this, this.context.pathWrapper.configPath);
|
||||||
this.apis = {
|
this.apis = {
|
||||||
FileApi: new NTQQFileApi(this.context, this),
|
FileApi: new NTQQFileApi(this.context, this),
|
||||||
SystemApi: new NTQQSystemApi(this.context, this),
|
SystemApi: new NTQQSystemApi(this.context, this),
|
||||||
CollectionApi: new NTQQCollectionApi(this.context, this),
|
CollectionApi: new NTQQCollectionApi(this.context, this),
|
||||||
|
PacketApi: new NTQQPacketApi(this.context, this),
|
||||||
WebApi: new NTQQWebApi(this.context, this),
|
WebApi: new NTQQWebApi(this.context, this),
|
||||||
FriendApi: new NTQQFriendApi(this.context, this),
|
FriendApi: new NTQQFriendApi(this.context, this),
|
||||||
MsgApi: new NTQQMsgApi(this.context, this),
|
MsgApi: new NTQQMsgApi(this.context, this),
|
||||||
UserApi: new NTQQUserApi(this.context, this),
|
UserApi: new NTQQUserApi(this.context, this),
|
||||||
GroupApi: new NTQQGroupApi(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');
|
this.NapCatDataPath = path.join(this.dataPath, 'NapCat');
|
||||||
fs.mkdirSync(this.NapCatDataPath, { recursive: true });
|
fs.mkdirSync(this.NapCatDataPath, { recursive: true });
|
||||||
this.NapCatTempPath = path.join(this.NapCatDataPath, 'temp');
|
this.NapCatTempPath = path.join(this.NapCatDataPath, 'temp');
|
||||||
@@ -93,7 +100,8 @@ export class NapCatCore {
|
|||||||
if (!fs.existsSync(this.NapCatTempPath)) {
|
if (!fs.existsSync(this.NapCatTempPath)) {
|
||||||
fs.mkdirSync(this.NapCatTempPath, { recursive: true });
|
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.context.logger.setFileLogEnabled(
|
||||||
this.configLoader.configData.fileLog,
|
this.configLoader.configData.fileLog,
|
||||||
@@ -121,7 +129,7 @@ export class NapCatCore {
|
|||||||
const msgListener = new NodeIKernelMsgListener();
|
const msgListener = new NodeIKernelMsgListener();
|
||||||
msgListener.onKickedOffLine = (Info: KickedOffLineInfo) => {
|
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;
|
this.selfInfo.online = false;
|
||||||
};
|
};
|
||||||
msgListener.onRecvMsg = (msgs) => {
|
msgListener.onRecvMsg = (msgs) => {
|
||||||
@@ -145,8 +153,9 @@ export class NapCatCore {
|
|||||||
if (Info.status == 20) {
|
if (Info.status == 20) {
|
||||||
this.selfInfo.online = false;
|
this.selfInfo.online = false;
|
||||||
this.context.logger.log("账号状态变更为离线");
|
this.context.logger.log("账号状态变更为离线");
|
||||||
|
} else {
|
||||||
|
this.selfInfo.online = true;
|
||||||
}
|
}
|
||||||
this.selfInfo.online = true;
|
|
||||||
};
|
};
|
||||||
this.context.session.getProfileService().addKernelProfileListener(
|
this.context.session.getProfileService().addKernelProfileListener(
|
||||||
proxiedListenerOf(profileListener, this.context.logger),
|
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');
|
const downloadPath = path.join(account_path, 'NapCat', 'temp');
|
||||||
fs.mkdirSync(downloadPath, { recursive: true });
|
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 {
|
return {
|
||||||
selfUin,
|
selfUin,
|
||||||
selfUid,
|
selfUid,
|
||||||
desktopPathConfig: {
|
desktopPathConfig: {
|
||||||
account_path, // 可以通过NodeQQNTWrapperUtil().getNTUserDataInfoConfig()获取
|
account_path, // 可以通过NodeQQNTWrapperUtil().getNTUserDataInfoConfig()获取
|
||||||
},
|
},
|
||||||
clientVer: QQVersion, // 9.9.8-22355
|
clientVer: QQVersion,
|
||||||
a2: '',
|
a2: '',
|
||||||
d2: '',
|
d2: '',
|
||||||
d2Key: '',
|
d2Key: '',
|
||||||
machineId: '',
|
machineId: '',
|
||||||
platform: PlatformType.KWINDOWS, // 3是Windows?
|
platform: systemPlatform, // 3是Windows?
|
||||||
platVer: systemVersion, // 系统版本号, 应该可以固定
|
platVer: systemVersion, // 系统版本号, 应该可以固定
|
||||||
appid: QQVersionAppid,
|
appid: QQVersionAppid,
|
||||||
rdeliveryConfig: {
|
rdeliveryConfig: {
|
||||||
@@ -263,7 +284,7 @@ export async function genSessionConfig(QQVersionAppid: string, QQVersion: string
|
|||||||
systemId: 0,
|
systemId: 0,
|
||||||
appId: '',
|
appId: '',
|
||||||
logicEnvironment: '',
|
logicEnvironment: '',
|
||||||
platform: PlatformType.KWINDOWS,
|
platform: systemPlatform,
|
||||||
language: '',
|
language: '',
|
||||||
sdkVersion: '',
|
sdkVersion: '',
|
||||||
userId: '',
|
userId: '',
|
||||||
@@ -303,6 +324,7 @@ export interface InstanceContext {
|
|||||||
export interface StableNTApiWrapper {
|
export interface StableNTApiWrapper {
|
||||||
FileApi: NTQQFileApi,
|
FileApi: NTQQFileApi,
|
||||||
SystemApi: NTQQSystemApi,
|
SystemApi: NTQQSystemApi,
|
||||||
|
PacketApi: NTQQPacketApi,
|
||||||
CollectionApi: NTQQCollectionApi,
|
CollectionApi: NTQQCollectionApi,
|
||||||
WebApi: NTQQWebApi,
|
WebApi: NTQQWebApi,
|
||||||
FriendApi: NTQQFriendApi,
|
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 './NodeIKernelTicketListener';
|
||||||
export * from './NodeIKernelStorageCleanListener';
|
export * from './NodeIKernelStorageCleanListener';
|
||||||
export * from './NodeIKernelFileAssistantListener';
|
export * from './NodeIKernelFileAssistantListener';
|
||||||
export * from './NodeIKernelSearchListener_Polyfill';
|
export * from './NodeIKernelSearchListener';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
NodeIKernelBuddyListener,
|
NodeIKernelBuddyListener,
|
||||||
@@ -19,11 +19,11 @@ import type {
|
|||||||
NodeIKernelMsgListener,
|
NodeIKernelMsgListener,
|
||||||
NodeIKernelProfileListener,
|
NodeIKernelProfileListener,
|
||||||
NodeIKernelRobotListener,
|
NodeIKernelRobotListener,
|
||||||
NodeIKernelSearchListener_Polyfill,
|
|
||||||
NodeIKernelSessionListener,
|
NodeIKernelSessionListener,
|
||||||
NodeIKernelStorageCleanListener,
|
NodeIKernelStorageCleanListener,
|
||||||
NodeIKernelTicketListener,
|
NodeIKernelTicketListener,
|
||||||
} from '.';
|
} from '.';
|
||||||
|
import { NodeIKernelSearchListener } from './NodeIKernelSearchListener';
|
||||||
|
|
||||||
export type ListenerNamingMapping = {
|
export type ListenerNamingMapping = {
|
||||||
NodeIKernelSessionListener: NodeIKernelSessionListener;
|
NodeIKernelSessionListener: NodeIKernelSessionListener;
|
||||||
@@ -36,5 +36,5 @@ export type ListenerNamingMapping = {
|
|||||||
NodeIKernelTicketListener: NodeIKernelTicketListener;
|
NodeIKernelTicketListener: NodeIKernelTicketListener;
|
||||||
NodeIKernelStorageCleanListener: NodeIKernelStorageCleanListener;
|
NodeIKernelStorageCleanListener: NodeIKernelStorageCleanListener;
|
||||||
NodeIKernelFileAssistantListener: NodeIKernelFileAssistantListener;
|
NodeIKernelFileAssistantListener: NodeIKernelFileAssistantListener;
|
||||||
NodeIKernelSearchListener: NodeIKernelSearchListener_Polyfill;
|
NodeIKernelSearchListener: NodeIKernelSearchListener;
|
||||||
};
|
};
|
||||||
|
179
src/core/packet/client.ts
Normal file
179
src/core/packet/client.ts
Normal file
@@ -0,0 +1,179 @@
|
|||||||
|
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 = 5;//现在暂时不可配置
|
||||||
|
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(): 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}`);
|
||||||
|
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();
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private attemptReconnect(): void {
|
||||||
|
try {
|
||||||
|
if (this.reconnectAttempts < this.maxReconnectAttempts) {
|
||||||
|
this.reconnectAttempts++;
|
||||||
|
setTimeout(() => {
|
||||||
|
this.connect().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(group: number, peer: number): PacketHexStr {
|
||||||
|
const oidb_0xed3 = new NapProtoMsg(OidbSvcTrpcTcp0XED3_1).encode({
|
||||||
|
uin: peer,
|
||||||
|
groupUin: group,
|
||||||
|
friendUin: group,
|
||||||
|
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);
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user