mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2025-07-19 12:03:37 +00:00
Compare commits
521 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
8a61f5a03f | ||
![]() |
8c164910f6 | ||
![]() |
a560d3d266 | ||
![]() |
532f739272 | ||
![]() |
a120727f2d | ||
![]() |
a9bcb830a8 | ||
![]() |
56e5f0033f | ||
![]() |
101106996a | ||
![]() |
41a81534dc | ||
![]() |
1425e8f229 | ||
![]() |
75bb1d2193 | ||
![]() |
2a23820f9b | ||
![]() |
2ee0fed047 | ||
![]() |
40be6b9c43 | ||
![]() |
a06b3f0246 | ||
![]() |
4787fa53b4 | ||
![]() |
a06158bf01 | ||
![]() |
314e7485b8 | ||
![]() |
aed5d2d9f0 | ||
![]() |
f44e48a28b | ||
![]() |
38be90450c | ||
![]() |
2dd57d7676 | ||
![]() |
6b3b163fa8 | ||
![]() |
9792ebafdc | ||
![]() |
d10e7c37cb | ||
![]() |
d38f1853a4 | ||
![]() |
bdec16266e | ||
![]() |
49ca698ab9 | ||
![]() |
3efd8163c9 | ||
![]() |
cc2d11449c | ||
![]() |
7e9c19ca5b | ||
![]() |
3b01b6827f | ||
![]() |
8d9ef851ba | ||
![]() |
b070bc59bc | ||
![]() |
8d663946e1 | ||
![]() |
2a2328b029 | ||
![]() |
efc9064abb | ||
![]() |
dd70adf071 | ||
![]() |
0f427375cb | ||
![]() |
4001270b93 | ||
![]() |
e7f5ed3bcc | ||
![]() |
05cdc37d0a | ||
![]() |
27920e0bee | ||
![]() |
ae409b7249 | ||
![]() |
8276258348 | ||
![]() |
1bf96a97a5 | ||
![]() |
d672680c4c | ||
![]() |
b89f2805e7 | ||
![]() |
78b4aa9295 | ||
![]() |
0a06637e78 | ||
![]() |
13afa2c7ab | ||
![]() |
51d34d17cc | ||
![]() |
18a99341d5 | ||
![]() |
f01c8f0110 | ||
![]() |
d8070eee2a | ||
![]() |
8519b7f4df | ||
![]() |
591ab1b1df | ||
![]() |
393815b11e | ||
![]() |
341a397bc4 | ||
![]() |
e46d274a75 | ||
![]() |
ad6f21980c | ||
![]() |
017b8b7f15 | ||
![]() |
9b448b17e6 | ||
![]() |
f9996a9987 | ||
![]() |
000ef55273 | ||
![]() |
e1ac0f02b4 | ||
![]() |
b9297e3f1d | ||
![]() |
34d0669ca8 | ||
![]() |
25e42720cf | ||
![]() |
f7c1951191 | ||
![]() |
479b971b0c | ||
![]() |
347ba5f354 | ||
![]() |
81dbb9d980 | ||
![]() |
c4e1a3ab04 | ||
![]() |
90ec774a21 | ||
![]() |
db7a27e624 | ||
![]() |
f7d965eda2 | ||
![]() |
74ca2e2e16 | ||
![]() |
8ab550f2f5 | ||
![]() |
018aca4db2 | ||
![]() |
d4327166c1 | ||
![]() |
fa25d2e779 | ||
![]() |
3ce1c3f0ec | ||
![]() |
96dff5141e | ||
![]() |
78d85d9965 | ||
![]() |
37ec455b02 | ||
![]() |
6ab82739a6 | ||
![]() |
a36917e7c0 | ||
![]() |
21f3428b36 | ||
![]() |
f8a487db25 | ||
![]() |
73a859be04 | ||
![]() |
63bcee01a1 | ||
![]() |
85b4966ba8 | ||
![]() |
36c2c567b7 | ||
![]() |
7b1ac224f6 | ||
![]() |
34d9f04f15 | ||
![]() |
be5da7cc6f | ||
![]() |
8d32ccb5d4 | ||
![]() |
6acceb884c | ||
![]() |
4c834fd640 | ||
![]() |
301278c7a9 | ||
![]() |
42ee83c54f | ||
![]() |
e631f69621 | ||
![]() |
ce8760a39a | ||
![]() |
ff952956de | ||
![]() |
28f3ff4971 | ||
![]() |
19e728c3cb | ||
![]() |
269773ed6b | ||
![]() |
e0d32417e1 | ||
![]() |
9fa6083bed | ||
![]() |
4d2fccdfb4 | ||
![]() |
c1c4bdfe94 | ||
![]() |
8a0e9e8b61 | ||
![]() |
1190e14171 | ||
![]() |
00292b177a | ||
![]() |
88de57f984 | ||
![]() |
61ddf38892 | ||
![]() |
52b3540ec3 | ||
![]() |
5f831958c3 | ||
![]() |
c3d4698af3 | ||
![]() |
bd6e83217d | ||
![]() |
50ec49d9a2 | ||
![]() |
dc3a089070 | ||
![]() |
530e380178 | ||
![]() |
10e4387add | ||
![]() |
e925bc3aa8 | ||
![]() |
427b3a7560 | ||
![]() |
c8da950725 | ||
![]() |
743c5b8196 | ||
![]() |
5e62abea57 | ||
![]() |
6bfc545582 | ||
![]() |
411108a2d2 | ||
![]() |
308a6fa9e4 | ||
![]() |
2dc7b785d0 | ||
![]() |
0e69e9e839 | ||
![]() |
b83229b5da | ||
![]() |
6f053f5f7d | ||
![]() |
c3dc53eaaf | ||
![]() |
ffdc34cfe2 | ||
![]() |
4825a0e341 | ||
![]() |
95a00d7f35 | ||
![]() |
d885bab426 | ||
![]() |
e2a6a0bc02 | ||
![]() |
ff7d8609ce | ||
![]() |
7507b90e03 | ||
![]() |
2b226a4b27 | ||
![]() |
8b0232c4fe | ||
![]() |
0728ee9ad6 | ||
![]() |
8c6f04d0bc | ||
![]() |
c67fad789e | ||
![]() |
4072339d70 | ||
![]() |
3a244f5804 | ||
![]() |
f12cf59137 | ||
![]() |
c76f556a11 | ||
![]() |
e0f3d07b98 | ||
![]() |
378d85dc67 | ||
![]() |
875e91fc0e | ||
![]() |
15f7cd9814 | ||
![]() |
1eb5cd6237 | ||
![]() |
ad2f843c8f | ||
![]() |
8e550e216e | ||
![]() |
9f07b07c82 | ||
![]() |
0be6effc32 | ||
![]() |
7ab6a10fc9 | ||
![]() |
fb09af0e64 | ||
![]() |
0d99d30b2d | ||
![]() |
0000ec8b5b | ||
![]() |
0085bd8a1f | ||
![]() |
617139dfa4 | ||
![]() |
4eb4a612d0 | ||
![]() |
cda5e784f6 | ||
![]() |
d93a280ab3 | ||
![]() |
f7e2b3a4a7 | ||
![]() |
39d9c8fa74 | ||
![]() |
8823895a03 | ||
![]() |
b44a9e696c | ||
![]() |
cf28a3dc17 | ||
![]() |
7416e6caf6 | ||
![]() |
90f6896f3c | ||
![]() |
eebcd0700d | ||
![]() |
133eee0c66 | ||
![]() |
640fb75f74 | ||
![]() |
51dcc1add6 | ||
![]() |
730c928f91 | ||
![]() |
c3b7e111b9 | ||
![]() |
1874e48925 | ||
![]() |
e7a082c91c | ||
![]() |
5d4f45407e | ||
![]() |
17c37ec32f | ||
![]() |
b5f8140c79 | ||
![]() |
63f746c237 | ||
![]() |
dac6709f27 | ||
![]() |
470c8d0b29 | ||
![]() |
b0d35e803b | ||
![]() |
a71475be8b | ||
![]() |
b9f2cc5142 | ||
![]() |
2d46e55b9b | ||
![]() |
684e254996 | ||
![]() |
a2f7903960 | ||
![]() |
c0c757d6bd | ||
![]() |
da0fad743d | ||
![]() |
80b10d6025 | ||
![]() |
a27c2a69c4 | ||
![]() |
9ed2a2fd19 | ||
![]() |
aa9d96718c | ||
![]() |
aa67a2b71c | ||
![]() |
d3405edd42 | ||
![]() |
3612098d62 | ||
![]() |
2f08b72d69 | ||
![]() |
ab66904c1a | ||
![]() |
55542a3dbe | ||
![]() |
8569a45114 | ||
![]() |
c790311fc3 | ||
![]() |
3c45c8bd80 | ||
![]() |
d5b7b3ae31 | ||
![]() |
43e73a5f24 | ||
![]() |
698947ed97 | ||
![]() |
f3d967ae07 | ||
![]() |
dbe72fa07e | ||
![]() |
801a97d85b | ||
![]() |
9f8f938c47 | ||
![]() |
8fe37d1c1e | ||
![]() |
5cca8457e7 | ||
![]() |
e9332e7646 | ||
![]() |
31365505d8 | ||
![]() |
b3fbe9e34a | ||
![]() |
4082b651c5 | ||
![]() |
0081000ef0 | ||
![]() |
ad4d6a1070 | ||
![]() |
5190b26399 | ||
![]() |
29a8db96f4 | ||
![]() |
1a4c2cabfd | ||
![]() |
ef9189055c | ||
![]() |
5cc3719125 | ||
![]() |
5d46f41348 | ||
![]() |
3c2c1963f4 | ||
![]() |
4896ca9279 | ||
![]() |
f0afba6cd9 | ||
![]() |
bd717c298a | ||
![]() |
baaa8a70dc | ||
![]() |
6d561c6e6f | ||
![]() |
e6b6947d49 | ||
![]() |
52e99a2175 | ||
![]() |
052d17a46f | ||
![]() |
1aa1f4c212 | ||
![]() |
c3a48e3344 | ||
![]() |
1d5483dc28 | ||
![]() |
54277fa0df | ||
![]() |
ab04bd262f | ||
![]() |
fb23087b65 | ||
![]() |
846fee7ac8 | ||
![]() |
977eacc679 | ||
![]() |
dacfefe644 | ||
![]() |
345e941e11 | ||
![]() |
6cb7d45464 | ||
![]() |
e7222653fa | ||
![]() |
014f0758f5 | ||
![]() |
0e8b416f6d | ||
![]() |
09a60a2204 | ||
![]() |
b0eae307c2 | ||
![]() |
f5d2b54cca | ||
![]() |
3eefec3899 | ||
![]() |
b6a8094554 | ||
![]() |
4083b35436 | ||
![]() |
bb72d70baf | ||
![]() |
95d1a77f52 | ||
![]() |
051729886e | ||
![]() |
0f00123dc7 | ||
![]() |
0b0a089d86 | ||
![]() |
c711a7d99a | ||
![]() |
43f1d8c88c | ||
![]() |
e818e79d20 | ||
![]() |
cbad3ff1de | ||
![]() |
16a2e5e996 | ||
![]() |
331c6a50d0 | ||
![]() |
31c4540ec6 | ||
![]() |
1e6116554f | ||
![]() |
a12ea0e761 | ||
![]() |
c9e3bbcd9f | ||
![]() |
9c17dc1b8f | ||
![]() |
69d1cae686 | ||
![]() |
1c2404b6af | ||
![]() |
b33b33739d | ||
![]() |
2b7886c682 | ||
![]() |
106d1f6374 | ||
![]() |
e601786bd7 | ||
![]() |
fda2a98b40 | ||
![]() |
c01d70b8fc | ||
![]() |
eccbcc3e28 | ||
![]() |
7a4a255a89 | ||
![]() |
83bced82b1 | ||
![]() |
f3033ce732 | ||
![]() |
5c21a1727c | ||
![]() |
93aab437b7 | ||
![]() |
34e797270f | ||
![]() |
0f337a8d8c | ||
![]() |
cc9b83089e | ||
![]() |
a565929686 | ||
![]() |
6adacea774 | ||
![]() |
47ab5421ed | ||
![]() |
10c404d455 | ||
![]() |
dfdca11155 | ||
![]() |
698e095364 | ||
![]() |
524fd258d8 | ||
![]() |
17e70a4360 | ||
![]() |
e4a533e7b7 | ||
![]() |
0cb68d3737 | ||
![]() |
9faeadbebe | ||
![]() |
35d201cfb8 | ||
![]() |
205174255f | ||
![]() |
8873a030ab | ||
![]() |
0ab61bac12 | ||
![]() |
b1157f60f5 | ||
![]() |
bb93df06b2 | ||
![]() |
82e807fd80 | ||
![]() |
29da539467 | ||
![]() |
659aa005b0 | ||
![]() |
3f20733e7e | ||
![]() |
b15e1174d6 | ||
![]() |
05b05fd74e | ||
![]() |
d30d467a21 | ||
![]() |
cd62e8ca37 | ||
![]() |
f9e44820c1 | ||
![]() |
169ae6a4d0 | ||
![]() |
030ba15952 | ||
![]() |
964874bdad | ||
![]() |
7affa081ac | ||
![]() |
10e281ed35 | ||
![]() |
27081ae599 | ||
![]() |
61cbcdffe8 | ||
![]() |
eeb15ea564 | ||
![]() |
565c820925 | ||
![]() |
325dff5735 | ||
![]() |
397c2cf5f0 | ||
![]() |
1fbc339a42 | ||
![]() |
f2c719c60d | ||
![]() |
08505fcc9a | ||
![]() |
a79c933693 | ||
![]() |
b4cb3ddf1c | ||
![]() |
aa188a6e89 | ||
![]() |
a04b6b8a70 | ||
![]() |
11149d2743 | ||
![]() |
86bfd990db | ||
![]() |
9304430889 | ||
![]() |
095f1c270b | ||
![]() |
d3f91a832b | ||
![]() |
4790a1170f | ||
![]() |
501c392028 | ||
![]() |
9200520f70 | ||
![]() |
8122561337 | ||
![]() |
c6dc86ef8d | ||
![]() |
bea3b8485f | ||
![]() |
b807b89cdc | ||
![]() |
daac2f7fd9 | ||
![]() |
f0a5523174 | ||
![]() |
eda8fbb178 | ||
![]() |
67ca6184e9 | ||
![]() |
d79e91fc1e | ||
![]() |
1cdb93baa2 | ||
![]() |
f91991e25c | ||
![]() |
d21da47a7d | ||
![]() |
b4e22a345d | ||
![]() |
30e594ae5f | ||
![]() |
ffba3573ba | ||
![]() |
9df5bee8d3 | ||
![]() |
71c0728622 | ||
![]() |
476d8ba14d | ||
![]() |
274c956f16 | ||
![]() |
3068f9ee3d | ||
![]() |
a0c49d5f7f | ||
![]() |
a8534974fe | ||
![]() |
c517790391 | ||
![]() |
b7e875c77f | ||
![]() |
befd9c0624 | ||
![]() |
7a46f11089 | ||
![]() |
dc168bf8b9 | ||
![]() |
eef5293ca0 | ||
![]() |
a2c4498694 | ||
![]() |
938a84a460 | ||
![]() |
978d2c24ee | ||
![]() |
cdd00d665d | ||
![]() |
bb8b06c044 | ||
![]() |
604c5dcdc1 | ||
![]() |
6bc2ecdbf0 | ||
![]() |
e91c81def7 | ||
![]() |
bedd2fa15a | ||
![]() |
50465eef54 | ||
![]() |
07689adfcd | ||
![]() |
8f4f898675 | ||
![]() |
968bd7a437 | ||
![]() |
eba5900ba8 | ||
![]() |
69c477b104 | ||
![]() |
c8df8f4f54 | ||
![]() |
d35a19b4fd | ||
![]() |
a97437a6e5 | ||
![]() |
39c4473367 | ||
![]() |
b882bc721d | ||
![]() |
405cace489 | ||
![]() |
402a7b7fc9 | ||
![]() |
8ad805e654 | ||
![]() |
b23c357f73 | ||
![]() |
f561c2b0fa | ||
![]() |
5a8eea668f | ||
![]() |
777143e502 | ||
![]() |
0d8c9a82fe | ||
![]() |
d10ab1cce3 | ||
![]() |
ec25e09d73 | ||
![]() |
cba9c78ab1 | ||
![]() |
c32db4a881 | ||
![]() |
871add3071 | ||
![]() |
e661c617a3 | ||
![]() |
d4bf721540 | ||
![]() |
d91b55faed | ||
![]() |
9687832d4d | ||
![]() |
fc3e436744 | ||
![]() |
da90245f7b | ||
![]() |
410d6a85d7 | ||
![]() |
b693342e4f | ||
![]() |
acca361f2e | ||
![]() |
b663f47713 | ||
![]() |
d332b199b5 | ||
![]() |
78bac1dbd1 | ||
![]() |
724ff215f9 | ||
![]() |
68ea146469 | ||
![]() |
82583e616f | ||
![]() |
bfc339c58d | ||
![]() |
fe4427c076 | ||
![]() |
5745f388a9 | ||
![]() |
377e3c253f | ||
![]() |
3007a0c00e | ||
![]() |
f51ffc091d | ||
![]() |
c37c364a08 | ||
![]() |
331a106e9a | ||
![]() |
cd74687b7b | ||
![]() |
b3e145c1e6 | ||
![]() |
d8e1547736 | ||
![]() |
8617f01924 | ||
![]() |
55f9e75e6a | ||
![]() |
b93e7b7ed1 | ||
![]() |
89cc79ad60 | ||
![]() |
8dd0e60eea | ||
![]() |
df6113fdf6 | ||
![]() |
3a3095d15a | ||
![]() |
fb4d07391e | ||
![]() |
9bef9c85cf | ||
![]() |
b77b3f227f | ||
![]() |
6a065f0a34 | ||
![]() |
4e1e190797 | ||
![]() |
1ce8cd2100 | ||
![]() |
c03af6b9ad | ||
![]() |
adca850075 | ||
![]() |
e3616b484e | ||
![]() |
cfd7808169 | ||
![]() |
addcedc588 | ||
![]() |
bfea786088 | ||
![]() |
50e84c3c9e | ||
![]() |
dc92ace85e | ||
![]() |
1a543928b1 | ||
![]() |
652fe8d21e | ||
![]() |
199690f45f | ||
![]() |
37a4dd4b00 | ||
![]() |
34d4358bfc | ||
![]() |
90906b9019 | ||
![]() |
1c212ff2b4 | ||
![]() |
7d709f44a8 | ||
![]() |
ea9e88a18a | ||
![]() |
0be8a9c805 | ||
![]() |
fcf8139afe | ||
![]() |
62f969b50b | ||
![]() |
6726062500 | ||
![]() |
cf1f4bdcaf | ||
![]() |
b09a14ad4e | ||
![]() |
1dc62c9ca3 | ||
![]() |
beaa89a2dc | ||
![]() |
f39a000b49 | ||
![]() |
013a74fb14 | ||
![]() |
7c4964753b | ||
![]() |
8353533d60 | ||
![]() |
c06df27424 | ||
![]() |
ad82919ddf | ||
![]() |
44dbba17e1 | ||
![]() |
5ba110e1da | ||
![]() |
b6e392fdb2 | ||
![]() |
2280e83aa2 | ||
![]() |
f49b94edb9 | ||
![]() |
2428a12221 | ||
![]() |
9c353f3760 | ||
![]() |
5b86d25d7f | ||
![]() |
2b168e8bbc | ||
![]() |
537db32847 | ||
![]() |
498b7f9f2b | ||
![]() |
9935568597 | ||
![]() |
467003af8c | ||
![]() |
4c9edcc47b | ||
![]() |
24bf9cf121 | ||
![]() |
e06f6f39a9 | ||
![]() |
98ee0c307b | ||
![]() |
5e53ea0bc3 | ||
![]() |
847d88ea77 | ||
![]() |
d5046cc2b3 | ||
![]() |
3ad64b7cbb | ||
![]() |
0dbfe8ca55 | ||
![]() |
91b794d66d | ||
![]() |
0d65e1e314 | ||
![]() |
2d8f58c6d8 | ||
![]() |
65888fa816 | ||
![]() |
857e882c6e | ||
![]() |
add2931834 | ||
![]() |
cdda5f45ee | ||
![]() |
5f73d6a913 | ||
![]() |
0637882fbc | ||
![]() |
3f785bab20 | ||
![]() |
a4ca89bdd6 | ||
![]() |
1a64e796bd | ||
![]() |
a8b85a34f7 | ||
![]() |
e7bec7d6b0 | ||
![]() |
a582026037 | ||
![]() |
1a67a001c5 | ||
![]() |
406deac592 | ||
![]() |
e719ae0676 | ||
![]() |
d8b7726440 |
@@ -1,64 +0,0 @@
|
||||
module.exports = {
|
||||
'env': {
|
||||
'browser': true,
|
||||
'es2021': true,
|
||||
'node': true
|
||||
},
|
||||
'ignorePatterns': ['src/core/proto/'],
|
||||
'extends': [
|
||||
'eslint:recommended',
|
||||
'plugin:@typescript-eslint/recommended'
|
||||
],
|
||||
'overrides': [
|
||||
{
|
||||
'env': {
|
||||
'node': true
|
||||
},
|
||||
'files': [
|
||||
'.eslintrc.{js,cjs}'
|
||||
],
|
||||
'parserOptions': {
|
||||
'sourceType': 'script'
|
||||
}
|
||||
}
|
||||
],
|
||||
'parser': '@typescript-eslint/parser',
|
||||
'parserOptions': {
|
||||
'ecmaVersion': 'latest',
|
||||
'sourceType': 'module'
|
||||
},
|
||||
'plugins': [
|
||||
'@typescript-eslint',
|
||||
'import'
|
||||
],
|
||||
'settings': {
|
||||
'import/parsers': {
|
||||
'@typescript-eslint/parser': ['.ts']
|
||||
},
|
||||
'import/resolver': {
|
||||
'typescript': {
|
||||
'alwaysTryTypes': true
|
||||
}
|
||||
}
|
||||
},
|
||||
'rules': {
|
||||
'indent': [
|
||||
'error',
|
||||
4
|
||||
],
|
||||
'linebreak-style': [
|
||||
'error',
|
||||
'unix'
|
||||
],
|
||||
'semi': [
|
||||
'error',
|
||||
'always'
|
||||
],
|
||||
'no-unused-vars': 'off',
|
||||
'no-async-promise-executor': 'off',
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
'@typescript-eslint/no-unused-vars': 'off',
|
||||
'@typescript-eslint/no-var-requires': 'off',
|
||||
'object-curly-spacing': ['error', 'always'],
|
||||
}
|
||||
};
|
36
.github/workflows/release.yml
vendored
36
.github/workflows/release.yml
vendored
@@ -93,15 +93,18 @@ jobs:
|
||||
needs: [Build-LiteLoader,Build-Shell]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
- name: Clone Main Repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: 'NapNeko/NapCatQQ'
|
||||
submodules: true
|
||||
ref: main
|
||||
token: ${{ secrets.NAPCAT_BUILD }}
|
||||
|
||||
- name: Download All Artifact
|
||||
uses: actions/download-artifact@v4
|
||||
# - name: Compress subdirectories
|
||||
# run: |
|
||||
# cd ./NapCat.Shell/
|
||||
# zip -q -r NapCat.Shell.zip *
|
||||
# cd ..
|
||||
# rm ./NapCat.Shell.zip -rf
|
||||
# mv ./NapCat.Shell/NapCat.Shell.zip ./
|
||||
|
||||
- name: Compress subdirectories
|
||||
run: |
|
||||
cd ./NapCat.Shell/
|
||||
@@ -114,6 +117,20 @@ jobs:
|
||||
rm ./NapCat.Framework.zip -rf
|
||||
mv ./NapCat.Shell/NapCat.Shell.zip ./
|
||||
mv ./NapCat.Framework/NapCat.Framework.zip ./
|
||||
|
||||
mkdir ./NapCat.Framework.Windows.Once
|
||||
unzip -q ./external/LiteLoaderWrapper.zip -d ./NapCat.Framework.Windows.Once
|
||||
cd ./NapCat.Framework.Windows.Once
|
||||
ls
|
||||
mkdir -p ./LL/plugins/NapCatQQ
|
||||
unzip -q ../NapCat.Framework.zip -d ./LL/plugins/NapCatQQ
|
||||
zip -q -r NapCat.Framework.Windows.Once.zip *
|
||||
cd ..
|
||||
mv ./NapCat.Framework.Windows.Once/NapCat.Framework.Windows.Once.zip ./
|
||||
mv ./external/packet/napcat.packet.arm64 ./
|
||||
mv ./external/packet/napcat.packet.exe ./
|
||||
mv ./external/packet/napcat.packet.linux ./
|
||||
mv ./external/packet/napcat.packet.production.py ./
|
||||
- name: Extract version from tag
|
||||
run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_ENV
|
||||
|
||||
@@ -129,4 +146,9 @@ jobs:
|
||||
files: |
|
||||
NapCat.Framework.zip
|
||||
NapCat.Shell.zip
|
||||
NapCat.Framework.Windows.Once.zip
|
||||
napcat.packet.arm64
|
||||
napcat.packet.exe
|
||||
napcat.packet.linux
|
||||
napcat.packet.production.py
|
||||
draft: true
|
||||
|
31
README.md
31
README.md
@@ -1,34 +1,43 @@
|
||||
<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" />
|
||||
|
||||

|
||||
|
||||
</div>
|
||||
|
||||
---
|
||||
## 欢迎回来
|
||||
NapCatQQ (aka 猫猫框架) 是现代化的基于 NTQQ 的 Bot 协议端实现。
|
||||
NapCatQQ (aka 猫猫框架) 是现代化的基于 NTQQ 的 Bot 协议端实现
|
||||
|
||||
## 猫猫技能
|
||||
- [x] **高性能**:1K+ 群聊数目、20 线程并行发送消息毫无压力
|
||||
- [x] **多种启动方式**:支持以无头、LiteLoader 插件、仅 QQ GUI 三种方式启动
|
||||
- [x] **多平台支持**: 覆盖 Windows / Linux (可选 Docker) / Android Termux / MacOS
|
||||
- [x] **启动方式**:支持以无头、LiteLoader 插件、仅 QQ GUI 三种方式启动
|
||||
- [x] **覆盖平台**: 覆盖 Windows / Linux (可选 Docker) / Android Termux / MacOS
|
||||
- [x] **安装简单**: 支持一键脚本/程序自动部署/镜像部署等多种覆盖范围
|
||||
- [x] **低占用**:无头模式占用资源极低,适合在服务器上运行
|
||||
- [x] **超低占用**:无头模式占用资源极低,适合在服务器上运行
|
||||
- [x] **超多接口**:实现大部分 OneBot 和 go-cqhttp 接口,超多扩展 API
|
||||
- [x] **WebUI**:自带 WebUI 支持,远程管理更加便捷
|
||||
- [x] **低故障率**:快速适配最新版本,日常保证 0 Issue
|
||||
- [x] **远程管理**:自带 WebUI 支持,远程管理更加便捷
|
||||
|
||||
## 使用猫猫
|
||||
|
||||
可前往 [Release](https://github.com/NapNeko/NapCatQQ/releases/) 页面下载最新版本
|
||||
|
||||
**首次使用**请务必前往[官方文档](https://napneko.github.io/)查看使用教程。
|
||||
**首次使用**请务必查看如下文档看使用教程
|
||||
|
||||
### 文档地址
|
||||
|
||||
[Cloudflare.Worker](https://doc.napneko.icu/)
|
||||
|
||||
[Cloudflare.HKServer](https://napcat.napneko.icu/)
|
||||
|
||||
[Cloudflare.Pages](https://napneko.pages.dev/)
|
||||
|
||||
[Github.IO](https://napneko.github.io/)
|
||||
## 回家旅途
|
||||
[QQ Group](https://qm.qq.com/q/VfjAq5HIMS)
|
||||
|
||||
[Telegram Link](https://t.me/+nLZEnpne-pQ1OWFl)
|
||||
|
||||
## 猫猫朋友
|
||||
感谢 [LLOneBot](https://github.com/LLOneBot/LLOneBot) 提供部分参考
|
||||
感谢 [LLOneBot](https://github.com/LLOneBot/LLOneBot)
|
||||
|
||||
感谢 [Lagrange](https://github.com/LagrangeDev/Lagrange.Core) 对本项目的大力支持
|
||||
|
||||
@@ -40,4 +49,4 @@ NapCatQQ (aka 猫猫框架) 是现代化的基于 NTQQ 的 Bot 协议端实现
|
||||
> [!CAUTION]\
|
||||
> **请不要在 QQ 官方群聊和任何影响力较大的简中互联网平台(包括但不限于: 哔哩哔哩,微博,知乎,抖音等)发布和讨论*任何*与本项目存在相关性的信息**
|
||||
|
||||
任何使用本仓库代码的地方,都应当严格遵守[本仓库开源许可](./LICENSE)。**此外,禁止任何项目未经授权二次分发或基于 NapCat 代码开发。**
|
||||
任何使用本仓库代码的地方,都应当严格遵守[本仓库开源许可](./LICENSE)。**此外,禁止任何项目未经授权二次分发或基于 NapCat 代码开发。**
|
||||
|
70
eslint.config.mjs
Normal file
70
eslint.config.mjs
Normal file
@@ -0,0 +1,70 @@
|
||||
import typescriptEslint from "@typescript-eslint/eslint-plugin";
|
||||
import _import from "eslint-plugin-import";
|
||||
import { fixupPluginRules } from "@eslint/compat";
|
||||
import globals from "globals";
|
||||
import tsParser from "@typescript-eslint/parser";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import js from "@eslint/js";
|
||||
import { FlatCompat } from "@eslint/eslintrc";
|
||||
|
||||
const filename = fileURLToPath(import.meta.url);
|
||||
const dirname = path.dirname(filename);
|
||||
const compat = new FlatCompat({
|
||||
baseDirectory: dirname,
|
||||
recommendedConfig: js.configs.recommended,
|
||||
allConfig: js.configs.all
|
||||
});
|
||||
|
||||
export default [{
|
||||
ignores: ["src/core/proto/"],
|
||||
}, ...compat.extends("eslint:recommended", "plugin:@typescript-eslint/recommended"), {
|
||||
plugins: {
|
||||
"@typescript-eslint": typescriptEslint,
|
||||
import: fixupPluginRules(_import),
|
||||
},
|
||||
|
||||
languageOptions: {
|
||||
globals: {
|
||||
...globals.browser,
|
||||
...globals.node,
|
||||
},
|
||||
|
||||
parser: tsParser,
|
||||
ecmaVersion: "latest",
|
||||
sourceType: "module",
|
||||
},
|
||||
|
||||
settings: {
|
||||
"import/parsers": {
|
||||
"@typescript-eslint/parser": [".ts"],
|
||||
},
|
||||
|
||||
"import/resolver": {
|
||||
typescript: {
|
||||
alwaysTryTypes: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
rules: {
|
||||
indent: ["error", 4],
|
||||
semi: ["error", "always"],
|
||||
"no-unused-vars": "off",
|
||||
"no-async-promise-executor": "off",
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/no-unused-vars": "off",
|
||||
"@typescript-eslint/no-var-requires": "off",
|
||||
"object-curly-spacing": ["error", "always"],
|
||||
},
|
||||
}, {
|
||||
files: ["**/.eslintrc.{js,cjs}"],
|
||||
|
||||
languageOptions: {
|
||||
globals: {
|
||||
...globals.node,
|
||||
},
|
||||
ecmaVersion: 5,
|
||||
sourceType: "commonjs",
|
||||
},
|
||||
}];
|
BIN
external/LiteLoaderWrapper.zip
vendored
Normal file
BIN
external/LiteLoaderWrapper.zip
vendored
Normal file
Binary file not shown.
BIN
external/packet/napcat.packet.arm64
vendored
Normal file
BIN
external/packet/napcat.packet.arm64
vendored
Normal file
Binary file not shown.
BIN
external/packet/napcat.packet.exe
vendored
Normal file
BIN
external/packet/napcat.packet.exe
vendored
Normal file
Binary file not shown.
BIN
external/packet/napcat.packet.linux
vendored
Normal file
BIN
external/packet/napcat.packet.linux
vendored
Normal file
Binary file not shown.
102
external/packet/napcat.packet.production.py
vendored
Normal file
102
external/packet/napcat.packet.production.py
vendored
Normal file
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
@@ -33,7 +33,7 @@ if not exist "%QQpath%" (
|
||||
exit /b
|
||||
)
|
||||
set NAPCAT_MAIN_PATH=%NAPCAT_MAIN_PATH:\=/%
|
||||
echo (async () =^> {await import("file:///%NAPCAT_MAIN_PATH%")})() > %NAPCAT_LOAD_PATH%
|
||||
echo (async () =^> {await import("file:///%NAPCAT_MAIN_PATH%")})() > "%NAPCAT_LOAD_PATH%"
|
||||
|
||||
"%NAPCAT_LAUNCHER_PATH%" "%QQPath%" "%NAPCAT_INJECT_PATH%" %1
|
||||
|
||||
|
@@ -34,6 +34,6 @@ if not exist "%QQpath%" (
|
||||
)
|
||||
|
||||
set NAPCAT_MAIN_PATH=%NAPCAT_MAIN_PATH:\=/%
|
||||
echo (async () =^> {await import("file:///%NAPCAT_MAIN_PATH%")})() > %NAPCAT_LOAD_PATH%
|
||||
echo (async () =^> {await import("file:///%NAPCAT_MAIN_PATH%")})() > "%NAPCAT_LOAD_PATH%"
|
||||
|
||||
"%NAPCAT_LAUNCHER_PATH%" "%QQPath%" "%NAPCAT_INJECT_PATH%" %1
|
@@ -1,6 +1,9 @@
|
||||
{
|
||||
"name": "qq-chat",
|
||||
"version": "9.9.15-28060",
|
||||
"version": "9.9.16-29456",
|
||||
"verHash": "dd395162",
|
||||
"linuxVersion": "3.2.13-29456",
|
||||
"linuxVerHash": "e379390a",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"description": "QQ",
|
||||
@@ -15,9 +18,9 @@
|
||||
"qd": "externals/devtools/cli/index.js"
|
||||
},
|
||||
"main": "./loadNapCat.js",
|
||||
"buildVersion": "28060",
|
||||
"buildVersion": "29456",
|
||||
"isPureShell": true,
|
||||
"isByteCodeShell": true,
|
||||
"platform": "win32",
|
||||
"eleArch": "x64"
|
||||
}
|
||||
}
|
||||
|
BIN
logo.png
BIN
logo.png
Binary file not shown.
Before Width: | Height: | Size: 208 KiB After Width: | Height: | Size: 335 KiB |
@@ -4,7 +4,7 @@
|
||||
"name": "NapCatQQ",
|
||||
"slug": "NapCat.Framework",
|
||||
"description": "高性能的 OneBot 11 协议实现",
|
||||
"version": "2.6.0",
|
||||
"version": "3.6.5",
|
||||
"icon": "./logo.png",
|
||||
"authors": [
|
||||
{
|
||||
|
113
package.json
113
package.json
@@ -1,55 +1,58 @@
|
||||
{
|
||||
"name": "napcat",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"version": "2.6.0",
|
||||
"scripts": {
|
||||
"build:framework": "vite build --mode framework",
|
||||
"build:shell": "vite build --mode shell",
|
||||
"build:webui": "cd ./src/webui && vite build",
|
||||
"lint": "eslint --fix src/**/*.{js,ts}",
|
||||
"depend": "cd dist && npm install --omit=dev"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/preset-typescript": "^7.24.7",
|
||||
"@log4js-node/log4js-api": "^1.0.2",
|
||||
"@protobuf-ts/plugin": "^2.9.4",
|
||||
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||
"@rollup/plugin-typescript": "^11.1.6",
|
||||
"@types/cors": "^2.8.17",
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/fluent-ffmpeg": "^2.1.24",
|
||||
"@types/node": "^22.0.1",
|
||||
"@types/qrcode-terminal": "^0.12.2",
|
||||
"@types/ws": "^8.5.12",
|
||||
"@typescript-eslint/eslint-plugin": "^8.3.0",
|
||||
"@typescript-eslint/parser": "^8.3.0",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-import-resolver-typescript": "^3.6.1",
|
||||
"eslint-plugin-import": "^2.29.1",
|
||||
"typescript": "^5.3.3",
|
||||
"vite": "^5.2.6",
|
||||
"vite-plugin-cp": "^4.0.8",
|
||||
"vite-plugin-dts": "^3.8.2",
|
||||
"vite-tsconfig-paths": "^4.3.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"ajv": "^8.13.0",
|
||||
"async-mutex": "^0.5.0",
|
||||
"chalk": "^5.3.0",
|
||||
"commander": "^12.1.0",
|
||||
"cors": "^2.8.5",
|
||||
"express": "^5.0.0-beta.2",
|
||||
"fast-xml-parser": "^4.3.6",
|
||||
"file-type": "^19.0.0",
|
||||
"fluent-ffmpeg": "^2.1.2",
|
||||
"image-size": "^1.1.1",
|
||||
"json-schema-to-ts": "^3.1.0",
|
||||
"log4js": "^6.9.1",
|
||||
"protobufjs": "~7.4.0",
|
||||
"qrcode-terminal": "^0.12.0",
|
||||
"silk-wasm": "^3.6.1",
|
||||
"strtok3": "8.0.1",
|
||||
"ws": "^8.18.0"
|
||||
}
|
||||
}
|
||||
{
|
||||
"name": "napcat",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"version": "3.6.5",
|
||||
"scripts": {
|
||||
"build:framework": "vite build --mode framework",
|
||||
"build:shell": "vite build --mode shell",
|
||||
"build:webui": "cd ./src/webui && vite build",
|
||||
"lint": "eslint --fix src/**/*.{js,ts}",
|
||||
"depend": "cd dist && npm install --omit=dev"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/preset-typescript": "^7.24.7",
|
||||
"@eslint/compat": "^1.2.2",
|
||||
"@eslint/eslintrc": "^3.1.0",
|
||||
"@eslint/js": "^9.14.0",
|
||||
"@log4js-node/log4js-api": "^1.0.2",
|
||||
"@napneko/nap-proto-core": "^0.0.2",
|
||||
"@protobuf-ts/runtime": "^2.9.4",
|
||||
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||
"@rollup/plugin-typescript": "^11.1.6",
|
||||
"@types/cors": "^2.8.17",
|
||||
"@types/express": "^5.0.0",
|
||||
"@types/fluent-ffmpeg": "^2.1.24",
|
||||
"@types/node": "^22.0.1",
|
||||
"@types/qrcode-terminal": "^0.12.2",
|
||||
"@types/ws": "^8.5.12",
|
||||
"@typescript-eslint/eslint-plugin": "^8.3.0",
|
||||
"@typescript-eslint/parser": "^8.3.0",
|
||||
"ajv": "^8.13.0",
|
||||
"async-mutex": "^0.5.0",
|
||||
"chalk": "^5.3.0",
|
||||
"commander": "^12.1.0",
|
||||
"cors": "^2.8.5",
|
||||
"eslint": "^9.14.0",
|
||||
"eslint-import-resolver-typescript": "^3.6.1",
|
||||
"eslint-plugin-import": "^2.29.1",
|
||||
"fast-xml-parser": "^4.3.6",
|
||||
"file-type": "^19.0.0",
|
||||
"globals": "^15.12.0",
|
||||
"image-size": "^1.1.1",
|
||||
"json-schema-to-ts": "^3.1.1",
|
||||
"typescript": "^5.3.3",
|
||||
"typescript-eslint": "^8.13.0",
|
||||
"vite": "^5.2.6",
|
||||
"vite-plugin-cp": "^4.0.8",
|
||||
"vite-tsconfig-paths": "^5.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"express": "^5.0.0",
|
||||
"fluent-ffmpeg": "^2.1.2",
|
||||
"log4js": "^6.9.1",
|
||||
"qrcode-terminal": "^0.12.0",
|
||||
"silk-wasm": "^3.6.1",
|
||||
"ws": "^8.18.0"
|
||||
}
|
||||
}
|
||||
|
@@ -4,16 +4,27 @@ const process = require("process");
|
||||
console.log("[NapCat] [CheckVersion] 开始检测当前仓库版本...");
|
||||
try {
|
||||
const packageJson = require("../package.json");
|
||||
const manifsetJson = require("../manifest.json");
|
||||
|
||||
const currentVersion = packageJson.version;
|
||||
const targetVersion = process.env.VERSION;
|
||||
|
||||
const manifestCurrentVersion = manifsetJson.version;
|
||||
const manifestTargetVersion = process.env.VERSION;
|
||||
|
||||
console.log("[NapCat] [CheckVersion] currentVersion:", currentVersion, "targetVersion:", targetVersion);
|
||||
console.log("[NapCat] [CheckVersion] manifestCurrentVersion:", manifestCurrentVersion, "manifestTargetVersion:", manifestTargetVersion);
|
||||
|
||||
// 验证 targetVersion 格式
|
||||
if (!targetVersion || typeof targetVersion !== 'string') {
|
||||
console.log("[NapCat] [CheckVersion] 目标版本格式不正确或未设置!");
|
||||
return;
|
||||
}
|
||||
// 验证 manifestTargetVersion 格式
|
||||
if (!manifestTargetVersion || typeof manifestTargetVersion !== 'string') {
|
||||
console.log("[NapCat] [CheckVersion] manifest目标版本格式不正确或未设置!");
|
||||
return;
|
||||
}
|
||||
|
||||
// 写入脚本文件的统一函数
|
||||
const writeScriptToFile = (content) => {
|
||||
@@ -21,7 +32,7 @@ try {
|
||||
console.log("[NapCat] [CheckVersion] checkVersion.sh 文件已更新。");
|
||||
};
|
||||
|
||||
if (currentVersion === targetVersion) {
|
||||
if (currentVersion === targetVersion && manifestCurrentVersion === manifestTargetVersion) {
|
||||
// 不需要更新版本,写入一个简单的脚本
|
||||
const simpleScript = "#!/bin/bash\necho \"CheckVersion Is Done\"";
|
||||
writeScriptToFile(simpleScript);
|
||||
@@ -29,11 +40,14 @@ try {
|
||||
// 更新版本,构建安全的sed命令
|
||||
const safeScriptContent = `
|
||||
#!/bin/bash
|
||||
git config --global user.email "bot@test.wumiao.wang"
|
||||
git config --global user.name "Version"
|
||||
sed -i "s/\\\"version\\\": \\\"${currentVersion}\\\"/\\\"version\\\": \\\"${targetVersion}\\\"/g" package.json
|
||||
git config --global user.email "nanaeonn@outlook.com"
|
||||
git config --global user.name "Mlikiowa"
|
||||
sed -i "s/\\"version\\": \\"${currentVersion}\\"/\\"version\\": \\"${targetVersion}\\"/g" package.json
|
||||
sed -i "s/\\"version\\": \\"${manifestCurrentVersion}\\"/\\"version\\": \\"${targetVersion}\\"/g" manifest.json
|
||||
sed -i "s/napCatVersion = '.*'/napCatVersion = '${targetVersion}'/g" ./src/common/version.ts
|
||||
sed -i "s/SettingButton(\\"V.*\\", \\"napcat-update-button\\", \\"secondary\\")/SettingButton(\\"V${targetVersion}\\", \\"napcat-update-button\\", \\"secondary\\")/g" ./static/assets/renderer.js
|
||||
git add .
|
||||
git commit -m "chore:version change"
|
||||
git commit -m "release: v${targetVersion}"
|
||||
git push -u origin main`;
|
||||
writeScriptToFile(safeScriptContent);
|
||||
}
|
||||
|
@@ -45,9 +45,9 @@ async function handleWavFile(
|
||||
): 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: await convert(filePath, pcmPath, logger), sampleRate: 24000 };
|
||||
}
|
||||
return {input: file, sampleRate: fmt.sampleRate};
|
||||
return { input: file, sampleRate: fmt.sampleRate };
|
||||
}
|
||||
|
||||
export async function encodeSilk(filePath: string, TEMP_DIR: string, logger: LogWrapper) {
|
||||
@@ -59,7 +59,7 @@ export async function encodeSilk(filePath: string, TEMP_DIR: string, logger: Log
|
||||
const pcmPath = `${pttPath}.pcm`;
|
||||
const { input, sampleRate } = isWav(file)
|
||||
? (await handleWavFile(file, filePath, pcmPath, logger))
|
||||
: {input: await convert(filePath, pcmPath, logger), sampleRate: 24000};
|
||||
: { input: await convert(filePath, pcmPath, logger), sampleRate: 24000 };
|
||||
const silk = await encode(input, sampleRate);
|
||||
await fsPromise.writeFile(pttPath, silk.data);
|
||||
logger.log(`语音文件${filePath}转换成功!`, pttPath, '时长:', silk.duration);
|
||||
@@ -83,7 +83,7 @@ export async function encodeSilk(filePath: string, TEMP_DIR: string, logger: Log
|
||||
};
|
||||
}
|
||||
} catch (error: any) {
|
||||
logger.logError('convert silk failed', error.stack);
|
||||
logger.logError.bind(logger)('convert silk failed', error.stack);
|
||||
return {};
|
||||
}
|
||||
}
|
@@ -40,7 +40,7 @@ export abstract class ConfigBase<T> {
|
||||
fs.writeFileSync(configPath, fs.readFileSync(this.getConfigPath(undefined), 'utf-8'));
|
||||
logger.log(`[Core] [Config] 配置文件创建成功!\n`);
|
||||
} catch (e: any) {
|
||||
logger.logError(`[Core] [Config] 创建配置文件时发生错误:`, e.message);
|
||||
logger.logError.bind(logger)(`[Core] [Config] 创建配置文件时发生错误:`, e.message);
|
||||
}
|
||||
}
|
||||
try {
|
||||
@@ -49,9 +49,9 @@ export abstract class ConfigBase<T> {
|
||||
return this.configData;
|
||||
} catch (e: any) {
|
||||
if (e instanceof SyntaxError) {
|
||||
logger.logError(`[Core] [Config] 配置文件格式错误,请检查配置文件:`, e.message);
|
||||
logger.logError.bind(logger)(`[Core] [Config] 配置文件格式错误,请检查配置文件:`, e.message);
|
||||
} else {
|
||||
logger.logError(`[Core] [Config] 读取配置文件时发生错误:`, e.message);
|
||||
logger.logError.bind(logger)(`[Core] [Config] 读取配置文件时发生错误:`, e.message);
|
||||
}
|
||||
return {} as T;
|
||||
}
|
||||
@@ -66,7 +66,7 @@ export abstract class ConfigBase<T> {
|
||||
try {
|
||||
fs.writeFileSync(configPath, JSON.stringify(newConfigData, this.getKeys(), 2));
|
||||
} catch (e: any) {
|
||||
logger.logError(`保存配置文件 ${configPath} 时发生错误:`, e.message);
|
||||
logger.logError.bind(logger)(`保存配置文件 ${configPath} 时发生错误:`, e.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -9,6 +9,15 @@ interface InternalMapKey {
|
||||
checker: ((...args: any[]) => boolean) | undefined;
|
||||
}
|
||||
|
||||
type EnsureFunc<T> = T extends (...args: any) => any ? T : never;
|
||||
|
||||
type FuncKeys<T> = Extract<
|
||||
{
|
||||
[K in keyof T]: EnsureFunc<T[K]> extends never ? never : K;
|
||||
}[keyof T],
|
||||
string
|
||||
>;
|
||||
|
||||
export type ListenerClassBase = Record<string, string>;
|
||||
|
||||
export class NTEventWrapper {
|
||||
@@ -43,10 +52,8 @@ export class NTEventWrapper {
|
||||
|
||||
createEventFunction<
|
||||
Service extends keyof ServiceNamingMapping,
|
||||
ServiceMethod extends Exclude<keyof ServiceNamingMapping[Service], symbol>,
|
||||
// eslint-disable-next-line
|
||||
// @ts-ignore
|
||||
T extends (...args: any) => any = ServiceNamingMapping[Service][ServiceMethod],
|
||||
ServiceMethod extends FuncKeys<ServiceNamingMapping[Service]>,
|
||||
T extends (...args: any) => any = EnsureFunc<ServiceNamingMapping[Service][ServiceMethod]>,
|
||||
>(eventName: `${Service}/${ServiceMethod}`): T | undefined {
|
||||
const eventNameArr = eventName.split('/');
|
||||
type eventType = {
|
||||
@@ -98,10 +105,8 @@ export class NTEventWrapper {
|
||||
|
||||
async callNoListenerEvent<
|
||||
Service extends keyof ServiceNamingMapping,
|
||||
ServiceMethod extends Exclude<keyof ServiceNamingMapping[Service], symbol>,
|
||||
// eslint-disable-next-line
|
||||
// @ts-ignore
|
||||
EventType extends (...args: any) => any = ServiceNamingMapping[Service][ServiceMethod],
|
||||
ServiceMethod extends FuncKeys<ServiceNamingMapping[Service]>,
|
||||
EventType extends (...args: any) => any = EnsureFunc<ServiceNamingMapping[Service][ServiceMethod]>,
|
||||
>(
|
||||
serviceAndMethod: `${Service}/${ServiceMethod}`,
|
||||
...args: Parameters<EventType>
|
||||
@@ -111,10 +116,8 @@ export class NTEventWrapper {
|
||||
|
||||
async registerListen<
|
||||
Listener extends keyof ListenerNamingMapping,
|
||||
ListenerMethod extends Exclude<keyof ListenerNamingMapping[Listener], symbol>,
|
||||
// eslint-disable-next-line
|
||||
// @ts-ignore
|
||||
ListenerType extends (...args: any) => any = ListenerNamingMapping[Listener][ListenerMethod],
|
||||
ListenerMethod extends FuncKeys<ListenerNamingMapping[Listener]>,
|
||||
ListenerType extends (...args: any) => any = EnsureFunc<ListenerNamingMapping[Listener][ListenerMethod]>,
|
||||
>(
|
||||
listenerAndMethod: `${Listener}/${ListenerMethod}`,
|
||||
waitTimes = 1,
|
||||
@@ -164,15 +167,11 @@ export class NTEventWrapper {
|
||||
|
||||
async callNormalEventV2<
|
||||
Service extends keyof ServiceNamingMapping,
|
||||
ServiceMethod extends Exclude<keyof ServiceNamingMapping[Service], symbol>,
|
||||
ServiceMethod extends FuncKeys<ServiceNamingMapping[Service]>,
|
||||
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]
|
||||
ListenerMethod extends FuncKeys<ListenerNamingMapping[Listener]>,
|
||||
EventType extends (...args: any) => any = EnsureFunc<ServiceNamingMapping[Service][ServiceMethod]>,
|
||||
ListenerType extends (...args: any) => any = EnsureFunc<ListenerNamingMapping[Listener][ListenerMethod]>
|
||||
>(
|
||||
serviceAndMethod: `${Service}/${ServiceMethod}`,
|
||||
listenerAndMethod: `${Listener}/${ListenerMethod}`,
|
||||
@@ -183,7 +182,7 @@ export class NTEventWrapper {
|
||||
timeout = 5000,
|
||||
) {
|
||||
return new Promise<[EventRet: Awaited<ReturnType<EventType>>, ...Parameters<ListenerType>]>(
|
||||
(resolve, reject) => {
|
||||
async (resolve, reject) => {
|
||||
const id = randomUUID();
|
||||
let complete = 0;
|
||||
let retData: Parameters<ListenerType> | undefined = undefined;
|
||||
@@ -235,22 +234,22 @@ export class NTEventWrapper {
|
||||
this.EventTask.get(ListenerMainName)?.get(ListenerSubName)?.set(id, eventCallback);
|
||||
this.createListenerFunction(ListenerMainName);
|
||||
const eventFunction = this.createEventFunction(serviceAndMethod);
|
||||
if (eventFunction) eventFunction(...(args)).then((retEvent: Awaited<ReturnType<EventType>>) => {
|
||||
if (!checkerEvent(retEvent) && timeoutRef.hasRef()) {
|
||||
clearTimeout(timeoutRef);
|
||||
reject(
|
||||
new Error(
|
||||
'EventChecker Failed: NTEvent serviceAndMethod:' +
|
||||
serviceAndMethod +
|
||||
' ListenerName:' +
|
||||
listenerAndMethod +
|
||||
' EventRet:\n' +
|
||||
JSON.stringify(retEvent, null, 4) +
|
||||
'\n',
|
||||
),
|
||||
);
|
||||
}
|
||||
});
|
||||
retEvent = await eventFunction!(...(args));
|
||||
if (!checkerEvent(retEvent) && timeoutRef.hasRef()) {
|
||||
clearTimeout(timeoutRef);
|
||||
reject(
|
||||
new Error(
|
||||
'EventChecker Failed: NTEvent serviceAndMethod:' +
|
||||
serviceAndMethod +
|
||||
' ListenerName:' +
|
||||
listenerAndMethod +
|
||||
' EventRet:\n' +
|
||||
JSON.stringify(retEvent, null, 4) +
|
||||
'\n',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
},
|
||||
);
|
||||
}
|
||||
|
@@ -144,7 +144,7 @@ async function tryDownload(options: string | HttpDownloadOptions, useReferer: bo
|
||||
}
|
||||
if (useReferer && !headers['Referer']) {
|
||||
headers['Referer'] = url;
|
||||
};
|
||||
}
|
||||
const fetchRes = await fetch(url, { headers }).catch((err) => {
|
||||
if (err.cause) {
|
||||
throw err.cause;
|
||||
@@ -215,7 +215,7 @@ export async function checkUriType(Uri: string) {
|
||||
}
|
||||
if (uri.startsWith('file://')) {
|
||||
let filePath: string;
|
||||
const pathname = decodeURIComponent(new URL(uri).pathname);
|
||||
const pathname = decodeURIComponent(new URL(uri).pathname + new URL(uri).hash);
|
||||
if (process.platform === 'win32') {
|
||||
filePath = pathname.slice(1);
|
||||
} else {
|
||||
|
118
src/common/forward-msg-builder.ts
Normal file
118
src/common/forward-msg-builder.ts
Normal file
@@ -0,0 +1,118 @@
|
||||
import { PacketMsg } from "@/core/packet/message/message";
|
||||
import * as crypto from "node:crypto";
|
||||
|
||||
interface ForwardMsgJson {
|
||||
app: string
|
||||
config: ForwardMsgJsonConfig,
|
||||
desc: string,
|
||||
extra: ForwardMsgJsonExtra,
|
||||
meta: ForwardMsgJsonMeta,
|
||||
prompt: string,
|
||||
ver: string,
|
||||
view: string
|
||||
}
|
||||
|
||||
interface ForwardMsgJsonConfig {
|
||||
autosize: number,
|
||||
forward: number,
|
||||
round: number,
|
||||
type: string,
|
||||
width: number
|
||||
}
|
||||
|
||||
interface ForwardMsgJsonExtra {
|
||||
filename: string,
|
||||
tsum: number,
|
||||
}
|
||||
|
||||
interface ForwardMsgJsonMeta {
|
||||
detail: ForwardMsgJsonMetaDetail
|
||||
}
|
||||
|
||||
interface ForwardMsgJsonMetaDetail {
|
||||
news: {
|
||||
text: string
|
||||
}[],
|
||||
resid: string,
|
||||
source: string,
|
||||
summary: string,
|
||||
uniseq: string
|
||||
}
|
||||
|
||||
interface ForwardAdaptMsg {
|
||||
senderName?: string;
|
||||
isGroupMsg?: boolean;
|
||||
msg?: ForwardAdaptMsgElement[];
|
||||
}
|
||||
|
||||
interface ForwardAdaptMsgElement {
|
||||
preview?: string;
|
||||
}
|
||||
|
||||
export class ForwardMsgBuilder {
|
||||
private static build(resId: string, msg: ForwardAdaptMsg[], source?: string, news?: ForwardMsgJsonMetaDetail["news"], summary?: string, prompt?: string): ForwardMsgJson {
|
||||
const id = crypto.randomUUID();
|
||||
const isGroupMsg = msg.some(m => m.isGroupMsg);
|
||||
if (!source) {
|
||||
source = isGroupMsg ? "群聊的聊天记录" :
|
||||
msg.length
|
||||
? Array.from(new Set(msg.slice(0, 4).map(m => m.senderName)))
|
||||
.join('和') + '的聊天记录'
|
||||
: '聊天记录';
|
||||
}
|
||||
if (!news) {
|
||||
news = msg.length === 0 ? [{
|
||||
text: "Nya~ This message is send from NapCat.Packet!",
|
||||
}] : msg.map(m => ({
|
||||
text: `${m.senderName}: ${m.msg?.map(msg => msg.preview).join('')}`,
|
||||
}));
|
||||
}
|
||||
if (!summary) {
|
||||
summary = `查看${msg.length}条转发消息`;
|
||||
}
|
||||
if (!prompt) {
|
||||
prompt = "[聊天记录]";
|
||||
}
|
||||
return {
|
||||
app: "com.tencent.multimsg",
|
||||
config: {
|
||||
autosize: 1,
|
||||
forward: 1,
|
||||
round: 1,
|
||||
type: "normal",
|
||||
width: 300
|
||||
},
|
||||
desc: prompt,
|
||||
extra: {
|
||||
filename: id,
|
||||
tsum: msg.length,
|
||||
},
|
||||
meta: {
|
||||
detail: {
|
||||
news,
|
||||
resid: resId,
|
||||
source,
|
||||
summary,
|
||||
uniseq: id,
|
||||
}
|
||||
},
|
||||
prompt,
|
||||
ver: "0.0.0.5",
|
||||
view: "contact",
|
||||
};
|
||||
}
|
||||
|
||||
static fromResId(resId: string): ForwardMsgJson {
|
||||
return this.build(resId, []);
|
||||
}
|
||||
|
||||
static fromPacketMsg(resId: string, packetMsg: PacketMsg[], source?: string, news?: ForwardMsgJsonMetaDetail["news"], summary?: string, prompt?: string): ForwardMsgJson {
|
||||
return this.build(resId, packetMsg.map(msg => ({
|
||||
senderName: msg.senderName,
|
||||
isGroupMsg: msg.groupId !== undefined,
|
||||
msg: msg.msg.map(m => ({
|
||||
preview: m.valid? m.toPreview() : "[该消息类型暂不支持查看]",
|
||||
}))
|
||||
})), source, news, summary, prompt);
|
||||
}
|
||||
}
|
@@ -25,8 +25,8 @@ export async function solveAsyncProblem<T extends (...args: any[]) => Promise<an
|
||||
}
|
||||
|
||||
export class FileNapCatOneBotUUID {
|
||||
static encodeModelId(peer: Peer, modelId: string, fileId: string, endString: string = ""): string {
|
||||
const data = `NapCatOneBot|ModelIdFile|${peer.chatType}|${peer.peerUid}|${modelId}|${fileId}`;
|
||||
static encodeModelId(peer: Peer, modelId: string, fileId: string, fileUUID: string = "", endString: string = ""): string {
|
||||
const data = `NapCatOneBot|ModelIdFile|${peer.chatType}|${peer.peerUid}|${modelId}|${fileId}|${fileUUID}`;
|
||||
//前四个字节塞data长度
|
||||
const length = Buffer.alloc(4 + data.length);
|
||||
length.writeUInt32BE(data.length * 2, 0);//储存data的hex长度
|
||||
@@ -37,7 +37,8 @@ export class FileNapCatOneBotUUID {
|
||||
static decodeModelId(uuid: string): undefined | {
|
||||
peer: Peer,
|
||||
modelId: string,
|
||||
fileId: string
|
||||
fileId: string,
|
||||
fileUUID?: string
|
||||
} {
|
||||
//前四个字节是data长度
|
||||
const length = Buffer.from(uuid.slice(0, 8), 'hex').readUInt32BE(0);
|
||||
@@ -47,20 +48,21 @@ export class FileNapCatOneBotUUID {
|
||||
const realData = Buffer.from(dataId, 'hex').toString();
|
||||
if (!realData.startsWith('NapCatOneBot|ModelIdFile|')) return undefined;
|
||||
const data = realData.split('|');
|
||||
if (data.length !== 6) return undefined;
|
||||
const [, , chatType, peerUid, modelId, fileId] = data;
|
||||
if (data.length < 6) return undefined; // compatibility requirement
|
||||
const [, , chatType, peerUid, modelId, fileId, fileUUID = undefined] = data;
|
||||
return {
|
||||
peer: {
|
||||
chatType: chatType as any,
|
||||
chatType: +chatType,
|
||||
peerUid: peerUid,
|
||||
},
|
||||
modelId,
|
||||
fileId
|
||||
fileId,
|
||||
fileUUID
|
||||
};
|
||||
}
|
||||
|
||||
static encode(peer: Peer, msgId: string, elementId: string, endString: string = ""): string {
|
||||
const data = `NapCatOneBot|MsgFile|${peer.chatType}|${peer.peerUid}|${msgId}|${elementId}`;
|
||||
static encode(peer: Peer, msgId: string, elementId: string, fileUUID: string = "", endString: string = ""): string {
|
||||
const data = `NapCatOneBot|MsgFile|${peer.chatType}|${peer.peerUid}|${msgId}|${elementId}|${fileUUID}`;
|
||||
//前四个字节塞data长度
|
||||
//一个字节8位 一个ascii字符1字节 一个hex字符4位 表示一个ascii字符需要两个hex字符
|
||||
const length = Buffer.alloc(4 + data.length);
|
||||
@@ -72,7 +74,8 @@ export class FileNapCatOneBotUUID {
|
||||
static decode(uuid: string): undefined | {
|
||||
peer: Peer,
|
||||
msgId: string,
|
||||
elementId: string
|
||||
elementId: string,
|
||||
fileUUID?: string
|
||||
} {
|
||||
//前四个字节是data长度
|
||||
const length = Buffer.from(uuid.slice(0, 8), 'hex').readUInt32BE(0);
|
||||
@@ -82,15 +85,16 @@ export class FileNapCatOneBotUUID {
|
||||
const realData = Buffer.from(dataId, 'hex').toString();
|
||||
if (!realData.startsWith('NapCatOneBot|MsgFile|')) return undefined;
|
||||
const data = realData.split('|');
|
||||
if (data.length !== 6) return undefined;
|
||||
const [, , chatType, peerUid, msgId, elementId] = data;
|
||||
if (data.length < 6) return undefined; // compatibility requirement
|
||||
const [, , chatType, peerUid, msgId, elementId, fileUUID = undefined] = data;
|
||||
return {
|
||||
peer: {
|
||||
chatType: chatType as any,
|
||||
chatType: +chatType,
|
||||
peerUid: peerUid,
|
||||
},
|
||||
msgId,
|
||||
elementId,
|
||||
fileUUID
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -180,22 +184,28 @@ export function getDefaultQQVersionConfigInfo(): QQVersionConfigType {
|
||||
};
|
||||
}
|
||||
return {
|
||||
baseVersion: '9.9.15-28060',
|
||||
curVersion: '9.9.15-28060',
|
||||
baseVersion: '9.9.15-28131',
|
||||
curVersion: '9.9.15-28131',
|
||||
prevVersion: '',
|
||||
onErrorVersions: [],
|
||||
buildId: '28060',
|
||||
buildId: '28131',
|
||||
};
|
||||
}
|
||||
|
||||
export function getQQPackageInfoPath(exePath: string = '', version: string): string {
|
||||
export function getQQPackageInfoPath(exePath: string = '', version?: string): string {
|
||||
let packagePath;
|
||||
if (os.platform() === 'darwin') {
|
||||
return path.join(path.dirname(exePath), '..', 'Resources', 'app', 'package.json');
|
||||
packagePath = path.join(path.dirname(exePath), '..', 'Resources', 'app', 'package.json');
|
||||
} else if (os.platform() === 'linux') {
|
||||
return path.join(path.dirname(exePath), './resources/app/package.json');
|
||||
packagePath = path.join(path.dirname(exePath), './resources/app/package.json');
|
||||
} else {
|
||||
return path.join(path.dirname(exePath), './versions/' + version + '/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 {
|
||||
@@ -214,6 +224,10 @@ export function getQQVersionConfigPath(exePath: string = ''): string | undefined
|
||||
if (typeof configVersionInfoPath !== 'string') {
|
||||
return undefined;
|
||||
}
|
||||
//老版本兼容 未来去掉
|
||||
if (!fs.existsSync(configVersionInfoPath)) {
|
||||
configVersionInfoPath = path.join(path.dirname(exePath), './resources/app/versions/config.json');
|
||||
}
|
||||
if (!fs.existsSync(configVersionInfoPath)) {
|
||||
return undefined;
|
||||
}
|
||||
@@ -225,3 +239,42 @@ export function calcQQLevel(level?: QQLevel) {
|
||||
const { crownNum, sunNum, moonNum, starNum } = level;
|
||||
return crownNum * 64 + sunNum * 16 + moonNum * 4 + starNum;
|
||||
}
|
||||
|
||||
export function stringifyWithBigInt(obj: any) {
|
||||
return JSON.stringify(obj, (key, value) =>
|
||||
typeof value === 'bigint' ? value.toString() : value
|
||||
);
|
||||
}
|
||||
|
||||
export function parseAppidFromMajor(nodeMajor: string): string | undefined {
|
||||
const hexSequence = "A4 09 00 00 00 35";
|
||||
const sequenceBytes = Buffer.from(hexSequence.replace(/ /g, ""), "hex");
|
||||
const filePath = path.resolve(nodeMajor);
|
||||
const fileContent = fs.readFileSync(filePath);
|
||||
|
||||
let searchPosition = 0;
|
||||
while (true) {
|
||||
const index = fileContent.indexOf(sequenceBytes, searchPosition);
|
||||
if (index === -1) {
|
||||
break;
|
||||
}
|
||||
|
||||
const start = index + sequenceBytes.length - 1;
|
||||
const end = fileContent.indexOf(0x00, start);
|
||||
if (end === -1) {
|
||||
break;
|
||||
}
|
||||
const content = fileContent.subarray(start, end);
|
||||
if (!content.every(byte => byte === 0x00)) {
|
||||
try {
|
||||
return content.toString("utf-8");
|
||||
} catch (error) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
searchPosition = end + 1;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
@@ -139,9 +139,13 @@ export class LogWrapper {
|
||||
|
||||
logMessage(msg: RawMessage, selfInfo: SelfInfo) {
|
||||
const isSelfSent = msg.senderUin === selfInfo.uin;
|
||||
this.log(`${
|
||||
isSelfSent ? '发送 ->' : '接收 <-'
|
||||
} ${rawMessageToText(msg)}`);
|
||||
|
||||
// Intercept grey tip
|
||||
if (msg.elements[0]?.elementType === ElementType.GreyTip) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.log(`${isSelfSent ? '发送 ->' : '接收 <-' } ${rawMessageToText(msg)}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,7 +159,12 @@ export function rawMessageToText(msg: RawMessage, recursiveLevel = 0): string {
|
||||
if (msg.chatType == ChatType.KCHATTYPEC2C) {
|
||||
tokens.push(`私聊 (${msg.peerUin})`);
|
||||
} else if (msg.chatType == ChatType.KCHATTYPEGROUP) {
|
||||
tokens.push(`群聊 (群 ${msg.peerUin} 的 ${msg.senderUin})`);
|
||||
if (recursiveLevel < 1) {
|
||||
tokens.push(`群聊 [${msg.peerName}(${msg.peerUin})]`);
|
||||
}
|
||||
if (msg.senderUin !== '0') {
|
||||
tokens.push(`[${msg.sendMemberName || msg.sendRemarkName || msg.sendNickName}(${msg.senderUin})]`);
|
||||
}
|
||||
} else if (msg.chatType == ChatType.KCHATTYPEDATALINE) {
|
||||
tokens.push('移动设备');
|
||||
} else /* temp */ {
|
||||
@@ -180,12 +189,11 @@ export function rawMessageToText(msg: RawMessage, recursiveLevel = 0): string {
|
||||
const recordMsgOrNull = msg.records.find(
|
||||
record => element.replyElement!.sourceMsgIdInRecords === record.msgId,
|
||||
);
|
||||
return `[回复消息 ${
|
||||
recordMsgOrNull &&
|
||||
recordMsgOrNull.peerUin != '284840486' // 非转发消息; 否则定位不到
|
||||
?
|
||||
rawMessageToText(recordMsgOrNull, recursiveLevel + 1) :
|
||||
`未找到消息记录 (MsgId = ${element.replyElement.sourceMsgIdInRecords})`
|
||||
return `[回复消息 ${recordMsgOrNull &&
|
||||
recordMsgOrNull.peerUin != '284840486' && recordMsgOrNull.peerUin != '1094950020'// 非转发消息; 否则定位不到
|
||||
?
|
||||
rawMessageToText(recordMsgOrNull, recursiveLevel + 1) :
|
||||
`未找到消息记录 (MsgId = ${element.replyElement.sourceMsgIdInRecords})`
|
||||
}]`;
|
||||
}
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
export class LRUCache<K, V> {
|
||||
private capacity: number;
|
||||
private cache: Map<K, V>;
|
||||
public cache: Map<K, V>;
|
||||
|
||||
constructor(capacity: number) {
|
||||
this.capacity = capacity;
|
||||
@@ -24,7 +24,9 @@ export class LRUCache<K, V> {
|
||||
} else if (this.cache.size >= this.capacity) {
|
||||
// If the cache is full, remove the least recently used key (the first one in the map)
|
||||
const firstKey = this.cache.keys().next().value;
|
||||
this.cache.delete(firstKey);
|
||||
if (firstKey !== undefined) {
|
||||
this.cache.delete(firstKey);
|
||||
}
|
||||
}
|
||||
this.cache.set(key, value);
|
||||
}
|
||||
|
@@ -23,8 +23,10 @@ export class LimitedHashTable<K, V> {
|
||||
}
|
||||
while (this.keyToValue.size > this.maxSize || this.valueToKey.size > this.maxSize) {
|
||||
const oldestKey = this.keyToValue.keys().next().value;
|
||||
this.valueToKey.delete(this.keyToValue.get(oldestKey)!);
|
||||
this.keyToValue.delete(oldestKey);
|
||||
if (oldestKey !== undefined) {
|
||||
this.valueToKey.delete(this.keyToValue.get(oldestKey) as V);
|
||||
this.keyToValue.delete(oldestKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,8 +1,9 @@
|
||||
import fs from 'node:fs';
|
||||
import { systemPlatform } from '@/common/system';
|
||||
import { getDefaultQQVersionConfigInfo, getQQPackageInfoPath, getQQVersionConfigPath } from './helper';
|
||||
import { getDefaultQQVersionConfigInfo, getQQPackageInfoPath, getQQVersionConfigPath, parseAppidFromMajor } from './helper';
|
||||
import AppidTable from '@/core/external/appid.json';
|
||||
import { LogWrapper } from './log';
|
||||
import { getMajorPath } from '@/core';
|
||||
|
||||
export class QQBasicInfoWrapper {
|
||||
QQMainPath: string | undefined;
|
||||
@@ -28,7 +29,7 @@ export class QQBasicInfoWrapper {
|
||||
? JSON.parse(fs.readFileSync(this.QQVersionConfigPath!).toString())
|
||||
: getDefaultQQVersionConfigInfo();
|
||||
|
||||
this.QQPackageInfoPath = getQQPackageInfoPath(this.QQMainPath, this.QQVersionConfig?.curVersion!);
|
||||
this.QQPackageInfoPath = getQQPackageInfoPath(this.QQMainPath, this.QQVersionConfig?.curVersion);
|
||||
this.QQPackageInfo = JSON.parse(fs.readFileSync(this.QQPackageInfoPath).toString());
|
||||
const { appid: IQQVersionAppid, qua: IQQVersionQua } = this.getAppidV2();
|
||||
this.QQVersionAppid = IQQVersionAppid;
|
||||
@@ -53,29 +54,26 @@ export class QQBasicInfoWrapper {
|
||||
}
|
||||
|
||||
//此方法不要直接使用
|
||||
getQUAInternal() {
|
||||
switch (systemPlatform) {
|
||||
case 'linux':
|
||||
return `V1_LNX_${this.getFullQQVesion()}_${this.getQQBuildStr()}_GW_B`;
|
||||
case 'darwin':
|
||||
return `V1_MAC_${this.getFullQQVesion()}_${this.getQQBuildStr()}_GW_B`;
|
||||
default:
|
||||
return `V1_WIN_${this.getFullQQVesion()}_${this.getQQBuildStr()}_GW_B`;
|
||||
}
|
||||
getQUAFallback() {
|
||||
const platformMapping: Partial<Record<NodeJS.Platform, string>> = {
|
||||
win32: `V1_WIN_${this.getFullQQVesion()}_${this.getQQBuildStr()}_GW_B`,
|
||||
darwin: `V1_MAC_${this.getFullQQVesion()}_${this.getQQBuildStr()}_GW_B`,
|
||||
linux: `V1_LNX_${this.getFullQQVesion()}_${this.getQQBuildStr()}_GW_B`,
|
||||
};
|
||||
return platformMapping[systemPlatform] ?? (platformMapping.win32)!;
|
||||
}
|
||||
|
||||
getAppidInternal() {
|
||||
switch (systemPlatform) {
|
||||
case 'linux':
|
||||
return '537246140';
|
||||
case 'darwin':
|
||||
return '537246140';
|
||||
default:
|
||||
return '537246092';
|
||||
}
|
||||
getAppIdFallback() {
|
||||
const platformMapping: Partial<Record<NodeJS.Platform, string>> = {
|
||||
win32: '537246092',
|
||||
darwin: '537246140',
|
||||
linux: '537246140',
|
||||
};
|
||||
return platformMapping[systemPlatform] ?? '537246092';
|
||||
}
|
||||
|
||||
getAppidV2(): { appid: string; qua: string } {
|
||||
// 通过已有表 性能好
|
||||
const appidTbale = AppidTable as unknown as QQAppidTableType;
|
||||
const fullVersion = this.getFullQQVesion();
|
||||
if (fullVersion) {
|
||||
@@ -84,10 +82,25 @@ export class QQBasicInfoWrapper {
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
// else
|
||||
// 通过Major拉取 性能差
|
||||
try {
|
||||
const majorAppid = this.getAppidV2ByMajor(fullVersion);
|
||||
if (majorAppid) {
|
||||
this.context.logger.log(`[QQ版本兼容性检测] 当前版本Appid未内置 通过Major获取 为了更好的性能请尝试更新NapCat`);
|
||||
return { appid: majorAppid, qua: this.getQUAFallback() };
|
||||
}
|
||||
} catch (error) {
|
||||
this.context.logger.log(`[QQ版本兼容性检测] 通过Major 获取Appid异常 请检测NapCat/QQNT是否正常`);
|
||||
}
|
||||
// 最终兜底为老版本
|
||||
this.context.logger.log(`[QQ版本兼容性检测] 获取Appid异常 请检测NapCat/QQNT是否正常`);
|
||||
this.context.logger.log(`[QQ版本兼容性检测] ${fullVersion} 版本兼容性不佳,可能会导致一些功能无法正常使用`,);
|
||||
return { appid: this.getAppidInternal(), qua: this.getQUAInternal() };
|
||||
return { appid: this.getAppIdFallback(), qua: this.getQUAFallback() };
|
||||
}
|
||||
getAppidV2ByMajor(QQVersion: string) {
|
||||
const majorPath = getMajorPath(QQVersion);
|
||||
const appid = parseAppidFromMajor(majorPath);
|
||||
return appid;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -61,7 +61,7 @@ export class RequestUtil {
|
||||
const options = {
|
||||
hostname: option.hostname,
|
||||
port: option.port,
|
||||
path: option.href,
|
||||
path: option.pathname + option.search,
|
||||
method: method,
|
||||
headers: headers,
|
||||
};
|
||||
|
@@ -1,12 +1,8 @@
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
import { networkInterfaces } from 'os';
|
||||
import { randomUUID } from 'crypto';
|
||||
|
||||
// 缓解Win7设备兼容性问题
|
||||
let osName: string;
|
||||
// 设备ID
|
||||
let machineId: Promise<string>;
|
||||
|
||||
try {
|
||||
osName = os.hostname();
|
||||
@@ -14,54 +10,6 @@ try {
|
||||
osName = 'NapCat'; // + crypto.randomUUID().substring(0, 4);
|
||||
}
|
||||
|
||||
const invalidMacAddresses = new Set([
|
||||
'00:00:00:00:00:00',
|
||||
'ff:ff:ff:ff:ff:ff',
|
||||
'ac:de:48:00:11:22',
|
||||
]);
|
||||
|
||||
function validateMacAddress(candidate: string): boolean {
|
||||
// eslint-disable-next-line no-useless-escape
|
||||
const tempCandidate = candidate.replace(/\-/g, ':').toLowerCase();
|
||||
return !invalidMacAddresses.has(tempCandidate);
|
||||
}
|
||||
|
||||
export async function getMachineId(): Promise<string> {
|
||||
if (!machineId) {
|
||||
machineId = (async () => {
|
||||
const id = await getMacMachineId();
|
||||
return id ?? randomUUID(); // fallback, generate a UUID
|
||||
})();
|
||||
}
|
||||
|
||||
return machineId;
|
||||
}
|
||||
|
||||
export function getMac(): string {
|
||||
const ifaces = networkInterfaces();
|
||||
for (const name in ifaces) {
|
||||
const networkInterface = ifaces[name];
|
||||
if (networkInterface) {
|
||||
for (const { mac } of networkInterface) {
|
||||
if (validateMacAddress(mac)) {
|
||||
return mac;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error('Unable to retrieve mac address (unexpected format)');
|
||||
}
|
||||
|
||||
async function getMacMachineId(): Promise<string | undefined> {
|
||||
try {
|
||||
const crypto = await import('crypto');
|
||||
const macAddress = getMac();
|
||||
return crypto.createHash('sha256').update(macAddress, 'utf8').digest('hex');
|
||||
} catch (err) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
const homeDir = os.homedir();
|
||||
|
||||
|
@@ -1 +1 @@
|
||||
export const napCatVersion = '2.6.0';
|
||||
export const napCatVersion = '3.6.5';
|
||||
|
@@ -6,6 +6,7 @@ import {
|
||||
Peer,
|
||||
PicElement,
|
||||
PicType,
|
||||
RawMessage,
|
||||
SendFileElement,
|
||||
SendPicElement,
|
||||
SendPttElement,
|
||||
@@ -30,6 +31,7 @@ export class NTQQFileApi {
|
||||
context: InstanceContext;
|
||||
core: NapCatCore;
|
||||
rkeyManager: RkeyManager;
|
||||
packetRkey: Array<{ rkey: string; time: number; type: number; ttl: bigint }> | undefined;
|
||||
|
||||
constructor(context: InstanceContext, core: NapCatCore) {
|
||||
this.context = context;
|
||||
@@ -145,7 +147,7 @@ export class NTQQFileApi {
|
||||
try {
|
||||
videoInfo = await getVideoInfo(filePath, logger);
|
||||
} catch (e) {
|
||||
logger.logError('获取视频信息失败,将使用默认值', e);
|
||||
logger.logError.bind(logger)('获取视频信息失败,将使用默认值', e);
|
||||
}
|
||||
|
||||
let fileExt = 'mp4';
|
||||
@@ -153,7 +155,7 @@ export class NTQQFileApi {
|
||||
const tempExt = (await fileType.fileTypeFromFile(filePath))?.ext;
|
||||
if (tempExt) fileExt = tempExt;
|
||||
} catch (e) {
|
||||
this.context.logger.logError('获取文件类型失败', e);
|
||||
this.context.logger.logError.bind(logger)('获取文件类型失败', e);
|
||||
}
|
||||
const newFilePath = filePath + '.' + fileExt;
|
||||
fs.copyFileSync(filePath, newFilePath);
|
||||
@@ -173,14 +175,18 @@ export class NTQQFileApi {
|
||||
const thumbPath = pathLib.join(thumb, thumbFileName);
|
||||
ffmpeg(filePath)
|
||||
.on('error', (err) => {
|
||||
logger.logDebug('获取视频封面失败,使用默认封面', err);
|
||||
if (diyThumbPath) {
|
||||
fsPromises.copyFile(diyThumbPath, thumbPath).then(() => {
|
||||
try {
|
||||
logger.logDebug('获取视频封面失败,使用默认封面', err);
|
||||
if (diyThumbPath) {
|
||||
fsPromises.copyFile(diyThumbPath, thumbPath).then(() => {
|
||||
resolve(thumbPath);
|
||||
}).catch(reject);
|
||||
} else {
|
||||
fs.writeFileSync(thumbPath, Buffer.from(defaultVideoThumbB64, 'base64'));
|
||||
resolve(thumbPath);
|
||||
}).catch(reject);
|
||||
} else {
|
||||
fs.writeFileSync(thumbPath, Buffer.from(defaultVideoThumbB64, 'base64'));
|
||||
resolve(thumbPath);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.logError.bind(logger)('获取视频封面失败,使用默认封面失败', error);
|
||||
}
|
||||
})
|
||||
.screenshots({
|
||||
@@ -227,7 +233,7 @@ export class NTQQFileApi {
|
||||
}
|
||||
if (converted) {
|
||||
fsPromises.unlink(silkPath).then().catch(
|
||||
(e) => this.context.logger.logError('删除临时文件失败', e)
|
||||
(e) => this.context.logger.logError.bind(this.context.logger)('删除临时文件失败', e)
|
||||
);
|
||||
}
|
||||
return {
|
||||
@@ -237,7 +243,7 @@ export class NTQQFileApi {
|
||||
fileName: fileName,
|
||||
filePath: path,
|
||||
md5HexStr: md5,
|
||||
fileSize: fileSize,
|
||||
fileSize: fileSize.toString(),
|
||||
duration: duration ?? 1,
|
||||
formatType: 1,
|
||||
voiceType: 1,
|
||||
@@ -266,6 +272,53 @@ export class NTQQFileApi {
|
||||
return fileTransNotifyInfo.filePath;
|
||||
}
|
||||
|
||||
async downloadRawMsgMedia(msg: RawMessage[]) {
|
||||
const res = await Promise.all(
|
||||
msg.map(m =>
|
||||
Promise.all(
|
||||
m.elements
|
||||
.filter(element =>
|
||||
element.elementType === ElementType.PIC ||
|
||||
element.elementType === ElementType.VIDEO ||
|
||||
element.elementType === ElementType.PTT ||
|
||||
element.elementType === ElementType.FILE
|
||||
)
|
||||
.map(element =>
|
||||
this.downloadMedia(m.msgId, m.chatType, m.peerUid, element.elementId, '', '', 1000 * 60 * 2, true)
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
msg.forEach((m, msgIndex) => {
|
||||
const elementResults = res[msgIndex];
|
||||
let elementIndex = 0;
|
||||
m.elements.forEach(element => {
|
||||
if (
|
||||
element.elementType === ElementType.PIC ||
|
||||
element.elementType === ElementType.VIDEO ||
|
||||
element.elementType === ElementType.PTT ||
|
||||
element.elementType === ElementType.FILE
|
||||
) {
|
||||
switch (element.elementType) {
|
||||
case ElementType.PIC:
|
||||
element.picElement!.sourcePath = elementResults[elementIndex];
|
||||
break;
|
||||
case ElementType.VIDEO:
|
||||
element.videoElement!.filePath = elementResults[elementIndex];
|
||||
break;
|
||||
case ElementType.PTT:
|
||||
element.pttElement!.filePath = elementResults[elementIndex];
|
||||
break;
|
||||
case ElementType.FILE:
|
||||
element.fileElement!.filePath = elementResults[elementIndex];
|
||||
break;
|
||||
}
|
||||
elementIndex++;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async downloadMedia(msgId: string, chatType: ChatType, peerUid: string, elementId: string, thumbPath: string, sourcePath: string, timeout = 1000 * 60 * 2, force: boolean = false) {
|
||||
// 用于下载收到的消息中的图片等
|
||||
if (sourcePath && fs.existsSync(sourcePath)) {
|
||||
@@ -295,7 +348,7 @@ export class NTQQFileApi {
|
||||
filePath: thumbPath,
|
||||
}],
|
||||
() => true,
|
||||
(arg) => arg.msgId === msgId,
|
||||
(arg) => arg.msgElementId === elementId && arg.msgId === msgId,
|
||||
1,
|
||||
timeout,
|
||||
);
|
||||
@@ -365,22 +418,59 @@ export class NTQQFileApi {
|
||||
|
||||
if (url) {
|
||||
const parsedUrl = new URL(IMAGE_HTTP_HOST + url);
|
||||
const urlRkey = parsedUrl.searchParams.get('rkey');
|
||||
const imageAppid = parsedUrl.searchParams.get('appid');
|
||||
const isNTV2 = imageAppid && ['1406', '1407'].includes(imageAppid);
|
||||
if (isNTV2) {
|
||||
let rkey = parsedUrl.searchParams.get('rkey');
|
||||
if (rkey) {
|
||||
return IMAGE_HTTP_HOST_NT + url;
|
||||
const imageFileId = parsedUrl.searchParams.get('fileid');
|
||||
|
||||
const rkeyData = {
|
||||
private_rkey: 'CAQSKAB6JWENi5LM_xp9vumLbuThJSaYf-yzMrbZsuq7Uz2qEc3Rbib9LP4',
|
||||
group_rkey: 'CAQSKAB6JWENi5LM_xp9vumLbuThJSaYf-yzMrbZsuq7Uz2qffcqm614gds',
|
||||
online_rkey: false
|
||||
};
|
||||
try {
|
||||
if (this.core.apis.PacketApi.available) {
|
||||
const rkey_expired_private = !this.packetRkey || this.packetRkey[0].time + Number(this.packetRkey[0].ttl) < Date.now() / 1000;
|
||||
const rkey_expired_group = !this.packetRkey || this.packetRkey[0].time + Number(this.packetRkey[0].ttl) < Date.now() / 1000;
|
||||
if (rkey_expired_private || rkey_expired_group) {
|
||||
this.packetRkey = await this.core.apis.PacketApi.sendRkeyPacket();
|
||||
}
|
||||
if (this.packetRkey && this.packetRkey.length > 0) {
|
||||
rkeyData.group_rkey = this.packetRkey[1].rkey.slice(6);
|
||||
rkeyData.private_rkey = this.packetRkey[0].rkey.slice(6);
|
||||
rkeyData.online_rkey = true;
|
||||
}
|
||||
}
|
||||
const rkeyData = await this.rkeyManager.getRkey();
|
||||
rkey = imageAppid === '1406' ? rkeyData.private_rkey : rkeyData.group_rkey;
|
||||
return IMAGE_HTTP_HOST_NT + url + `${rkey}`;
|
||||
} else {
|
||||
return IMAGE_HTTP_HOST + url;
|
||||
} catch (error: any) {
|
||||
this.context.logger.logError.bind(this.context.logger)('获取rkey失败', error.message);
|
||||
}
|
||||
} else if (fileMd5 || md5HexStr) {
|
||||
|
||||
if (!rkeyData.online_rkey) {
|
||||
try {
|
||||
const tempRkeyData = await this.rkeyManager.getRkey();
|
||||
rkeyData.group_rkey = tempRkeyData.group_rkey;
|
||||
rkeyData.private_rkey = tempRkeyData.private_rkey;
|
||||
rkeyData.online_rkey = tempRkeyData.expired_time > Date.now() / 1000;
|
||||
} catch (e) {
|
||||
this.context.logger.logError.bind(this.context.logger)('获取rkey失败 Fallback Old Mode', e);
|
||||
}
|
||||
}
|
||||
if (isNTV2 && urlRkey) {
|
||||
return IMAGE_HTTP_HOST_NT + urlRkey;
|
||||
} else if (isNTV2 && rkeyData.online_rkey) {
|
||||
const rkey = imageAppid === '1406' ? rkeyData.private_rkey : rkeyData.group_rkey;
|
||||
return IMAGE_HTTP_HOST_NT + url + `&rkey=${rkey}`;
|
||||
} else if (isNTV2 && imageFileId) {
|
||||
const rkey = imageAppid === '1406' ? rkeyData.private_rkey : rkeyData.group_rkey;
|
||||
return IMAGE_HTTP_HOST + `/download?appid=${imageAppid}&fileid=${imageFileId}&rkey=${rkey}`;
|
||||
}
|
||||
|
||||
}
|
||||
//到这里说明可能是旧客户端
|
||||
if (fileMd5 || md5HexStr) {
|
||||
return `${IMAGE_HTTP_HOST}/gchatpic_new/0/0-0-${(fileMd5 ?? md5HexStr)!.toUpperCase()}/0`;
|
||||
}
|
||||
|
||||
this.context.logger.logDebug('图片url获取失败', element);
|
||||
return '';
|
||||
}
|
||||
|
@@ -10,7 +10,9 @@ export class NTQQFriendApi {
|
||||
this.context = context;
|
||||
this.core = core;
|
||||
}
|
||||
|
||||
async setBuddyRemark(uid: string, remark: string) {
|
||||
return this.context.session.getBuddyService().setBuddyRemark({ uid, remark });
|
||||
}
|
||||
async getBuddyV2SimpleInfoMap(refresh = false) {
|
||||
const buddyService = this.context.session.getBuddyService();
|
||||
const buddyListV2 = refresh ? await buddyService.getBuddyListV2('0', BuddyListReqType.KNOMAL) : await buddyService.getBuddyListV2('0', BuddyListReqType.KNOMAL);
|
||||
@@ -32,7 +34,13 @@ export class NTQQFriendApi {
|
||||
data.forEach((value) => retMap.set(value.uin!, value.uid!));
|
||||
return retMap;
|
||||
}
|
||||
|
||||
async delBuudy(uid: string, tempBlock = false, tempBothDel = false) {
|
||||
return this.context.session.getBuddyService().delBuddy({
|
||||
friendUid: uid,
|
||||
tempBlock: tempBlock,
|
||||
tempBothDel: tempBothDel
|
||||
});
|
||||
}
|
||||
async getBuddyV2ExWithCate(refresh = false) {
|
||||
const categoryMap: Map<string, any> = new Map();
|
||||
const buddyService = this.context.session.getBuddyService();
|
||||
|
@@ -20,6 +20,7 @@ export class NTQQGroupApi {
|
||||
groupMemberCache: Map<string, Map<string, GroupMember>> = new Map<string, Map<string, GroupMember>>();
|
||||
groups: Group[] = [];
|
||||
essenceLRU = new LimitedHashTable<number, string>(1000);
|
||||
session: any;
|
||||
|
||||
constructor(context: InstanceContext, core: NapCatCore) {
|
||||
this.context = context;
|
||||
@@ -33,7 +34,9 @@ export class NTQQGroupApi {
|
||||
this.groupCache.set(group.groupCode, group);
|
||||
}
|
||||
this.context.logger.logDebug(`加载${this.groups.length}个群组缓存完成`);
|
||||
// process.pid 调试点
|
||||
}
|
||||
|
||||
async getCoreAndBaseInfo(uids: string[]) {
|
||||
return await this.core.eventWrapper.callNoListenerEvent(
|
||||
'NodeIKernelProfileService/getCoreAndBaseInfo',
|
||||
@@ -41,6 +44,7 @@ export class NTQQGroupApi {
|
||||
uids,
|
||||
);
|
||||
}
|
||||
|
||||
async fetchGroupEssenceList(groupCode: string) {
|
||||
const pskey = (await this.core.apis.UserApi.getPSkey(['qun.qq.com'])).domainPskeyMap.get('qun.qq.com')!;
|
||||
return this.context.session.getGroupService().fetchGroupEssenceList({
|
||||
@@ -49,7 +53,11 @@ export class NTQQGroupApi {
|
||||
pageLimit: 300,
|
||||
}, pskey);
|
||||
}
|
||||
|
||||
async getGroupShutUpMemberList(groupCode: string) {
|
||||
const data = this.core.eventWrapper.registerListen('NodeIKernelGroupListener/onShutUpMemberListChanged', 1, 1000, (group_id) => group_id === groupCode);
|
||||
this.context.session.getGroupService().getGroupShutUpMemberList(groupCode);
|
||||
return (await data)[1];
|
||||
}
|
||||
async clearGroupNotifiesUnreadCount(uk: boolean) {
|
||||
return this.context.session.getGroupService().clearGroupNotifiesUnreadCount(uk);
|
||||
}
|
||||
@@ -137,7 +145,7 @@ export class NTQQGroupApi {
|
||||
let members = this.groupMemberCache.get(groupCodeStr);
|
||||
if (!members) {
|
||||
try {
|
||||
members = await this.getGroupMembersV2(groupCodeStr);
|
||||
members = await this.getGroupMembers(groupCodeStr);
|
||||
// 更新群成员列表
|
||||
this.groupMemberCache.set(groupCodeStr, members);
|
||||
} catch (e) {
|
||||
@@ -158,11 +166,12 @@ export class NTQQGroupApi {
|
||||
|
||||
let member = getMember();
|
||||
if (!member) {
|
||||
members = await this.getGroupMembersV2(groupCodeStr);
|
||||
members = await this.getGroupMembers(groupCodeStr);
|
||||
member = getMember();
|
||||
}
|
||||
return member;
|
||||
}
|
||||
|
||||
async getGroupRecommendContactArkJson(groupCode: string) {
|
||||
return this.context.session.getGroupService().getGroupRecommendContactArkJson(groupCode);
|
||||
}
|
||||
@@ -268,6 +277,7 @@ export class NTQQGroupApi {
|
||||
}
|
||||
return member;
|
||||
}
|
||||
|
||||
async searchGroup(groupCode: string) {
|
||||
const [, ret] = await this.core.eventWrapper.callNormalEventV2(
|
||||
'NodeIKernelSearchService/searchGroup',
|
||||
@@ -285,6 +295,7 @@ export class NTQQGroupApi {
|
||||
);
|
||||
return ret.groupInfos.find(g => g.groupCode === groupCode);
|
||||
}
|
||||
|
||||
async getGroupMemberEx(GroupCode: string, uid: string, forced = false, retry = 2) {
|
||||
const data = await solveAsyncProblem((eventWrapper: NTEventWrapper, GroupCode: string, uid: string, forced = false) => {
|
||||
return eventWrapper.callNormalEventV2(
|
||||
@@ -306,36 +317,77 @@ export class NTQQGroupApi {
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
async getGroupMembersV2(groupQQ: string, num = 3000): Promise<Map<string, GroupMember>> {
|
||||
const groupService = this.context.session.getGroupService();
|
||||
const sceneId = groupService.createMemberListScene(groupQQ, 'groupMemberList_MainWindow');
|
||||
const listener = this.core.eventWrapper.registerListen(
|
||||
'NodeIKernelGroupListener/onMemberListChange',
|
||||
1,
|
||||
5000,
|
||||
(params) => params.sceneId === sceneId,
|
||||
);
|
||||
try {
|
||||
const [membersFromFunc, membersFromListener] = await Promise.allSettled([
|
||||
groupService.getNextMemberList(sceneId, undefined, num),
|
||||
listener,
|
||||
]);
|
||||
if (membersFromFunc.status === 'fulfilled' && membersFromListener.status === 'fulfilled') {
|
||||
return new Map([
|
||||
...membersFromFunc.value.result.infos,
|
||||
...membersFromListener.value[0].infos,
|
||||
]);
|
||||
}
|
||||
if (membersFromFunc.status === 'fulfilled') {
|
||||
return membersFromFunc.value.result.infos;
|
||||
}
|
||||
if (membersFromListener.status === 'fulfilled') {
|
||||
return membersFromListener.value[0].infos;
|
||||
}
|
||||
throw new Error('获取群成员列表失败');
|
||||
} finally {
|
||||
groupService.destroyMemberListScene(sceneId);
|
||||
|
||||
async tryGetGroupMembersV2(modeListener = false, groupQQ: string, num = 30, timeout = 100): Promise<{
|
||||
infos: Map<string, GroupMember>;
|
||||
finish: boolean;
|
||||
hasNext: boolean | undefined;
|
||||
}> {
|
||||
const sceneId = this.context.session.getGroupService().createMemberListScene(groupQQ, 'groupMemberList_MainWindow_1');
|
||||
const once = this.core.eventWrapper.registerListen('NodeIKernelGroupListener/onMemberListChange', 0, timeout, (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);
|
||||
}
|
||||
let resMode2;
|
||||
if (modeListener) {
|
||||
const ret = (await once)?.[0];
|
||||
if (ret) {
|
||||
resMode2 = ret;
|
||||
}
|
||||
}
|
||||
this.context.session.getGroupService().destroyMemberListScene(sceneId);
|
||||
return {
|
||||
infos: new Map([...(resMode2?.infos ?? []), ...result.result.infos]),
|
||||
finish: result.result.finish,
|
||||
hasNext: resMode2?.hasNext,
|
||||
};
|
||||
}
|
||||
|
||||
async GetGroupMembersV3(groupQQ: string, num = 3000, timeout = 2500): Promise<{
|
||||
infos: Map<string, GroupMember>;
|
||||
finish: boolean;
|
||||
hasNext: boolean | undefined;
|
||||
listenerMode: boolean;
|
||||
}> {
|
||||
const sceneId = this.context.session.getGroupService().createMemberListScene(groupQQ, 'groupMemberList_MainWindow_1');
|
||||
const once = this.core.eventWrapper.registerListen('NodeIKernelGroupListener/onMemberListChange', 0, timeout, (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);
|
||||
}
|
||||
let resMode2;
|
||||
if (result.result.finish && result.result.infos.size === 0) {
|
||||
const ret = (await once)?.[0];
|
||||
if (ret) {
|
||||
resMode2 = ret;
|
||||
}
|
||||
}
|
||||
this.context.session.getGroupService().destroyMemberListScene(sceneId);
|
||||
//console.log('GetGroupMembersV3 len :', result.result.infos.size, resMode2?.infos.size, groupQQ);
|
||||
return {
|
||||
infos: new Map([...(resMode2?.infos ?? []), ...result.result.infos]),
|
||||
finish: result.result.finish,
|
||||
hasNext: resMode2?.hasNext,
|
||||
listenerMode: resMode2?.hasNext !== undefined ? true : false
|
||||
};
|
||||
}
|
||||
|
||||
async getGroupMembersV2(groupQQ: string, num = 3000): Promise<Map<string, GroupMember>> {
|
||||
//console.log('getGroupMembers -->', groupQQ);
|
||||
let res = await this.GetGroupMembersV3(groupQQ, num);
|
||||
let ret = res.infos;
|
||||
if (res.infos.size === 0 && !res.listenerMode) {
|
||||
res = await this.GetGroupMembersV3(groupQQ, num);
|
||||
ret = res.infos;
|
||||
}
|
||||
if (res.infos.size === 0) {
|
||||
ret = (await this.getGroupMemberAll(groupQQ)).result.infos;
|
||||
}
|
||||
//console.log("<---------------")
|
||||
return ret;
|
||||
}
|
||||
|
||||
async getGroupMembers(groupQQ: string, num = 3000): Promise<Map<string, GroupMember>> {
|
||||
@@ -433,7 +485,7 @@ export class NTQQGroupApi {
|
||||
}
|
||||
|
||||
async getGroupRemainAtTimes(GroupCode: string) {
|
||||
this.context.session.getGroupService().getGroupRemainAtTimes(GroupCode);
|
||||
return this.context.session.getGroupService().getGroupRemainAtTimes(GroupCode);
|
||||
}
|
||||
|
||||
async getMemberExtInfo(groupCode: string, uin: string) {
|
||||
|
@@ -3,6 +3,9 @@ import { InstanceContext, NapCatCore } from '@/core';
|
||||
import { GeneralCallResult } from '@/core/services/common';
|
||||
|
||||
export class NTQQMsgApi {
|
||||
getMsgByClientSeqAndTime(peer: Peer, replyMsgClientSeq: string, replyMsgTime: string) {
|
||||
return this.context.session.getMsgService().getMsgByClientSeqAndTime(peer, replyMsgClientSeq, replyMsgTime);
|
||||
}
|
||||
// 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
|
||||
@@ -22,7 +25,9 @@ export class NTQQMsgApi {
|
||||
async sendShowInputStatusReq(peer: Peer, eventType: number) {
|
||||
return this.context.session.getMsgService().sendShowInputStatusReq(peer.chatType, eventType, peer.peerUid);
|
||||
}
|
||||
|
||||
async getSourceOfReplyMsgV2(peer: Peer, clientSeq: string, time: string) {
|
||||
return this.context.session.getMsgService().getSourceOfReplyMsgV2(peer, clientSeq, time);
|
||||
}
|
||||
async getMsgEmojiLikesList(peer: Peer, msgSeq: string, emojiId: string, emojiType: string, count: number = 20) {
|
||||
//注意此处emojiType 可选值一般为1-2 2好像是unicode表情dec值 大部分情况 Taged Mlikiowa
|
||||
return this.context.session.getMsgService().getMsgEmojiLikesList(peer, msgSeq, emojiId, emojiType, '', false, count);
|
||||
@@ -82,6 +87,18 @@ export class NTQQMsgApi {
|
||||
pageLimit: 1,
|
||||
});
|
||||
}
|
||||
async queryMsgsWithFilterExWithSeqV3(peer: Peer, msgSeq: string, SendersUid: string[]) {
|
||||
return await this.context.session.getMsgService().queryMsgsWithFilterEx('0', '0', msgSeq, {
|
||||
chatInfo: peer,
|
||||
filterMsgType: [],
|
||||
filterSendersUid: SendersUid,
|
||||
filterMsgToTime: '0',
|
||||
filterMsgFromTime: '0',
|
||||
isReverseOrder: false,
|
||||
isIncludeCurrent: true,
|
||||
pageLimit: 1,
|
||||
});
|
||||
}
|
||||
async queryFirstMsgBySeq(peer: Peer, msgSeq: string) {
|
||||
return await this.context.session.getMsgService().queryMsgsWithFilterEx('0', '0', msgSeq, {
|
||||
chatInfo: peer,
|
||||
@@ -94,9 +111,9 @@ export class NTQQMsgApi {
|
||||
pageLimit: 1,
|
||||
});
|
||||
}
|
||||
//@deprecated
|
||||
async getMsgsBySeqAndCount(peer: Peer, seq: string, count: number, desc: boolean, z: boolean) {
|
||||
return await this.context.session.getMsgService().getMsgsBySeqAndCount(peer, seq, count, desc, z);
|
||||
// 客户端还在用别慌
|
||||
async getMsgsBySeqAndCount(peer: Peer, seq: string, count: number, desc: boolean, isReverseOrder: boolean) {
|
||||
return await this.context.session.getMsgService().getMsgsBySeqAndCount(peer, seq, count, desc, isReverseOrder);
|
||||
}
|
||||
async getMsgExBySeq(peer: Peer, msgSeq: string) {
|
||||
const DateNow = Math.floor(Date.now() / 1000);
|
||||
|
242
src/core/apis/packet.ts
Normal file
242
src/core/apis/packet.ts
Normal file
@@ -0,0 +1,242 @@
|
||||
import * as crypto from 'crypto';
|
||||
import * as os from 'os';
|
||||
import { ChatType, InstanceContext, NapCatCore } from '..';
|
||||
import offset from '@/core/external/offset.json';
|
||||
import { PacketSession } from "@/core/packet/session";
|
||||
import { OidbPacket, PacketHexStr } from "@/core/packet/packer";
|
||||
import { NapProtoMsg, NapProtoEncodeStructType, NapProtoDecodeStructType } from "@napneko/nap-proto-core";
|
||||
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/message/message";
|
||||
import { OidbSvcTrpcTcp0x6D6Response } from "@/core/packet/proto/oidb/Oidb.0x6D6";
|
||||
import {
|
||||
PacketMsgFileElement,
|
||||
PacketMsgPicElement,
|
||||
PacketMsgPttElement,
|
||||
PacketMsgVideoElement
|
||||
} from "@/core/packet/message/element";
|
||||
import { MiniAppReqParams, MiniAppRawData } from "@/core/packet/entities/miniApp";
|
||||
import { MiniAppAdaptShareInfoResp } from "@/core/packet/proto/action/miniAppAdaptShareInfo";
|
||||
import { AIVoiceChatType, AIVoiceItemList } from "@/core/packet/entities/aiChat";
|
||||
import { OidbSvcTrpcTcp0X929B_0Resp, OidbSvcTrpcTcp0X929D_0Resp } from "@/core/packet/proto/oidb/Oidb.0x929";
|
||||
import { IndexNode, MsgInfo } from "@/core/packet/proto/oidb/common/Ntv2.RichMediaReq";
|
||||
import { NTV2RichMediaResp } from "@/core/packet/proto/oidb/common/Ntv2.RichMediaResp";
|
||||
import { RecvPacketData } from "@/core/packet/client/client";
|
||||
import { napCatVersion } from "@/common/version";
|
||||
|
||||
|
||||
interface OffsetType {
|
||||
[key: string]: {
|
||||
recv: string;
|
||||
send: string;
|
||||
};
|
||||
}
|
||||
|
||||
const typedOffset: OffsetType = offset;
|
||||
|
||||
export class NTQQPacketApi {
|
||||
context: InstanceContext;
|
||||
core: NapCatCore;
|
||||
logger: LogWrapper;
|
||||
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;
|
||||
this.InitSendPacket(this.context.basicInfoWrapper.getFullQQVesion())
|
||||
.then()
|
||||
.catch(this.core.context.logger.logError.bind(this.core.context.logger));
|
||||
}
|
||||
|
||||
get available(): boolean {
|
||||
return this.packetSession?.client.available ?? false;
|
||||
}
|
||||
|
||||
async InitSendPacket(qqversion: string) {
|
||||
this.qqVersion = qqversion;
|
||||
const table = typedOffset[qqversion + '-' + os.arch()];
|
||||
if (!table) {
|
||||
this.logger.logError(`[Core] [Packet] PacketBackend 不支持当前QQ版本架构:${qqversion}-${os.arch()},
|
||||
请参照 https://github.com/NapNeko/NapCatQQ/releases/tag/v${napCatVersion} 配置正确的QQ版本!`);
|
||||
return false;
|
||||
}
|
||||
if (this.core.configLoader.configData.packetBackend === 'disable') {
|
||||
this.logger.logWarn('[Core] [Packet] 已禁用PacketBackend,NapCat.Packet将不会加载!');
|
||||
return false;
|
||||
}
|
||||
this.packetSession = new PacketSession(this.core);
|
||||
const cb = () => {
|
||||
if (this.packetSession && this.packetSession.client) {
|
||||
this.packetSession.client.init(process.pid, table.recv, table.send).then().catch(this.logger.logError.bind(this.logger));
|
||||
}
|
||||
};
|
||||
await this.packetSession.client.connect(cb);
|
||||
return true;
|
||||
}
|
||||
|
||||
async sendPacket(cmd: string, data: PacketHexStr, rsp = false): Promise<RecvPacketData> {
|
||||
return this.packetSession!.client.sendPacket(cmd, data, rsp);
|
||||
}
|
||||
|
||||
async sendOidbPacket(pkt: OidbPacket, rsp = false): Promise<RecvPacketData> {
|
||||
return this.sendPacket(pkt.cmd, pkt.data, rsp);
|
||||
}
|
||||
|
||||
async sendPokePacket(peer: number, group?: number) {
|
||||
const data = this.packetSession?.packer.packPokePacket(peer, group);
|
||||
await this.sendOidbPacket(data!, false);
|
||||
}
|
||||
|
||||
async sendRkeyPacket() {
|
||||
const packet = this.packetSession?.packer.packRkeyPacket();
|
||||
const ret = await this.sendOidbPacket(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 sendGroupSignPacket(groupCode: string) {
|
||||
const packet = this.packetSession?.packer.packGroupSignReq(this.core.selfInfo.uin, groupCode);
|
||||
await this.sendOidbPacket(packet!, true);
|
||||
}
|
||||
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.sendOidbPacket(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.sendOidbPacket(data!, true);
|
||||
}
|
||||
|
||||
// TODO: can simplify this
|
||||
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: groupUin ? String(groupUin) : this.core.selfInfo.uid
|
||||
}, e));
|
||||
}
|
||||
if (e instanceof PacketMsgVideoElement) {
|
||||
reqList.push(this.packetSession?.highwaySession.uploadVideo({
|
||||
chatType: groupUin ? ChatType.KCHATTYPEGROUP : ChatType.KCHATTYPEC2C,
|
||||
peerUid: groupUin ? String(groupUin) : this.core.selfInfo.uid
|
||||
}, e));
|
||||
}
|
||||
if (e instanceof PacketMsgPttElement) {
|
||||
reqList.push(this.packetSession?.highwaySession.uploadPtt({
|
||||
chatType: groupUin ? ChatType.KCHATTYPEGROUP : ChatType.KCHATTYPEC2C,
|
||||
peerUid: groupUin ? String(groupUin) : this.core.selfInfo.uid
|
||||
}, e));
|
||||
}
|
||||
if (e instanceof PacketMsgFileElement) {
|
||||
reqList.push(this.packetSession?.highwaySession.uploadFile({
|
||||
chatType: groupUin ? ChatType.KCHATTYPEGROUP : ChatType.KCHATTYPEC2C,
|
||||
peerUid: groupUin ? String(groupUin) : this.core.selfInfo.uid
|
||||
}, e));
|
||||
}
|
||||
}
|
||||
}
|
||||
const res = await Promise.allSettled(reqList);
|
||||
this.logger.log(`上传资源${res.length}个,失败${res.filter(r => r.status === 'rejected').length}个`);
|
||||
res.forEach((result, index) => {
|
||||
if (result.status === 'rejected') {
|
||||
this.logger.logError(`上传第${index + 1}个资源失败:${result.reason}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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.sendOidbPacket(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=`;
|
||||
}
|
||||
|
||||
async sendGroupPttFileDownloadReq(groupUin: number, node: NapProtoEncodeStructType<typeof IndexNode>) {
|
||||
const data = this.packetSession?.packer.packGroupPttFileDownloadReq(groupUin, node);
|
||||
const ret = await this.sendOidbPacket(data!, true);
|
||||
const body = new NapProtoMsg(OidbSvcTrpcTcpBaseRsp).decode(Buffer.from(ret.hex_data, 'hex')).body;
|
||||
const resp = new NapProtoMsg(NTV2RichMediaResp).decode(body);
|
||||
const info = resp.download.info;
|
||||
return `https://${info.domain}${info.urlPath}${resp.download.rKeyParam}`;
|
||||
}
|
||||
|
||||
async sendMiniAppShareInfoReq(param: MiniAppReqParams) {
|
||||
const data = this.packetSession?.packer.packMiniAppAdaptShareInfo(param);
|
||||
const ret = await this.sendPacket("LightAppSvc.mini_app_share.AdaptShareInfo", data!, true);
|
||||
const body = new NapProtoMsg(MiniAppAdaptShareInfoResp).decode(Buffer.from(ret.hex_data, 'hex'));
|
||||
return JSON.parse(body.content.jsonContent) as MiniAppRawData;
|
||||
}
|
||||
|
||||
async sendFetchAiVoiceListReq(groupUin: number, chatType: AIVoiceChatType) : Promise<AIVoiceItemList[] | null> {
|
||||
const data = this.packetSession?.packer.packFetchAiVoiceListReq(groupUin, chatType);
|
||||
const ret = await this.sendOidbPacket(data!, true);
|
||||
const body = new NapProtoMsg(OidbSvcTrpcTcpBaseRsp).decode(Buffer.from(ret.hex_data, 'hex')).body;
|
||||
const resp = new NapProtoMsg(OidbSvcTrpcTcp0X929D_0Resp).decode(body);
|
||||
if (!resp.content) return null;
|
||||
return resp.content.map((item) => {
|
||||
return {
|
||||
category: item.category,
|
||||
voices: item.voices
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
async sendAiVoiceChatReq(groupUin: number, voiceId: string, text: string, chatType: AIVoiceChatType): Promise<NapProtoDecodeStructType<typeof MsgInfo>> {
|
||||
let reqTime = 0;
|
||||
const reqMaxTime = 30;
|
||||
const sessionId = crypto.randomBytes(4).readUInt32BE(0);
|
||||
while (true) {
|
||||
if (reqTime >= reqMaxTime) {
|
||||
throw new Error(`sendAiVoiceChatReq failed after ${reqMaxTime} times`);
|
||||
}
|
||||
reqTime++;
|
||||
const data = this.packetSession?.packer.packAiVoiceChatReq(groupUin, voiceId, text, chatType, sessionId);
|
||||
const ret = await this.sendOidbPacket(data!, true);
|
||||
const body = new NapProtoMsg(OidbSvcTrpcTcpBase).decode(Buffer.from(ret.hex_data, 'hex'));
|
||||
if (body.errorCode) {
|
||||
throw new Error(`sendAiVoiceChatReq retCode: ${body.errorCode} error: ${body.errorMsg}`);
|
||||
}
|
||||
const resp = new NapProtoMsg(OidbSvcTrpcTcp0X929B_0Resp).decode(body.body);
|
||||
if (!resp.msgInfo) continue;
|
||||
return resp.msgInfo;
|
||||
}
|
||||
}
|
||||
}
|
@@ -18,7 +18,7 @@ export class NTQQUserApi {
|
||||
async getStatusByUid(uid: string) {
|
||||
return this.context.session.getProfileService().getStatus(uid);
|
||||
}
|
||||
async getProfileLike(uid: string) {
|
||||
async getProfileLike(uid: string, start: number, count: number) {
|
||||
return this.context.session.getProfileLikeService().getBuddyProfileLike({
|
||||
friendUids: [uid],
|
||||
basic: 1,
|
||||
@@ -26,8 +26,8 @@ export class NTQQUserApi {
|
||||
favorite: 0,
|
||||
userProfile: 1,
|
||||
type: 2,
|
||||
start: 0,
|
||||
limit: 20,
|
||||
start: start,
|
||||
limit: count,
|
||||
});
|
||||
}
|
||||
async fetchOtherProfileLike(uid: string) {
|
||||
@@ -68,8 +68,7 @@ export class NTQQUserApi {
|
||||
}
|
||||
|
||||
async setQQAvatar(filePath: string) {
|
||||
type setQQAvatarRet = { result: number, errMsg: string };
|
||||
const ret = await this.context.session.getProfileService().setHeader(filePath) as setQQAvatarRet;
|
||||
const ret = await this.context.session.getProfileService().setHeader(filePath);
|
||||
return { result: ret?.result, errMsg: ret?.errMsg };
|
||||
}
|
||||
|
||||
@@ -120,12 +119,20 @@ export class NTQQUserApi {
|
||||
return this.context.session.getProfileService().modifyDesktopMiniProfile(param);
|
||||
}
|
||||
|
||||
//需要异常处理
|
||||
async getCookies(domain: string) {
|
||||
const ClientKeyData = await this.forceFetchClientKey();
|
||||
const requestUrl = 'https://ssl.ptlogin2.qq.com/jump?ptlang=1033&clientuin=' + this.core.selfInfo.uin +
|
||||
'&clientkey=' + ClientKeyData.clientKey + '&u1=https%3A%2F%2F' + domain + '%2F' + this.core.selfInfo.uin + '%2Finfocenter&keyindex=19%27';
|
||||
return await RequestUtil.HttpsGetCookies(requestUrl);
|
||||
const data = await RequestUtil.HttpsGetCookies(requestUrl);
|
||||
if (!data.p_skey || data.p_skey.length == 0) {
|
||||
try {
|
||||
const pskey = (await this.getPSkey([domain])).domainPskeyMap.get(domain);
|
||||
if (pskey) data.p_skey = pskey;
|
||||
} catch {
|
||||
return data;
|
||||
}
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
async getPSkey(domainList: string[]) {
|
||||
|
@@ -264,7 +264,7 @@ export class NTQQWebApi {
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.context.logger.logError('获取龙王信息失败');
|
||||
this.context.logger.logError.bind(this.context.logger)('获取龙王信息失败');
|
||||
}
|
||||
}
|
||||
if (getType === WebHonorType.PERFORMER || getType === WebHonorType.ALL) {
|
||||
@@ -280,10 +280,10 @@ export class NTQQWebApi {
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.context.logger.logError('获取群聊之火失败');
|
||||
this.context.logger.logError.bind(this.context.logger)('获取群聊之火失败');
|
||||
}
|
||||
}
|
||||
if (getType === WebHonorType.PERFORMER || getType === WebHonorType.ALL) {
|
||||
if (getType === WebHonorType.LEGEND || getType === WebHonorType.ALL) {
|
||||
const RetInternal = await getDataInternal(groupCode, 3);
|
||||
if (RetInternal) {
|
||||
HonorInfo.legend_list = [];
|
||||
@@ -296,7 +296,7 @@ export class NTQQWebApi {
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.context.logger.logError('获取群聊炽焰失败');
|
||||
this.context.logger.logError.bind(this.context.logger)('获取群聊炽焰失败');
|
||||
}
|
||||
}
|
||||
if (getType === WebHonorType.EMOTION || getType === WebHonorType.ALL) {
|
||||
@@ -312,7 +312,7 @@ export class NTQQWebApi {
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.context.logger.logError('获取快乐源泉失败');
|
||||
this.context.logger.logError.bind(this.context.logger)('获取快乐源泉失败');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -338,4 +338,12 @@ export class NTQQWebApi {
|
||||
}
|
||||
return (hash & 0x7FFFFFFF).toString();
|
||||
}
|
||||
public getBknFromSKey(sKey: string) {
|
||||
let hash = 5381;
|
||||
for (let i = 0; i < sKey.length; i++) {
|
||||
const code = sKey.charCodeAt(i);
|
||||
hash = hash + (hash << 5) + code;
|
||||
}
|
||||
return (hash & 0x7FFFFFFF).toString();
|
||||
}
|
||||
}
|
||||
|
@@ -117,6 +117,7 @@ export enum GroupMemberRole {
|
||||
}
|
||||
|
||||
export interface GroupMember {
|
||||
memberRealLevel: string | undefined;
|
||||
memberSpecialTitle?: string;
|
||||
avatarPath: string;
|
||||
cardName: string;
|
||||
|
@@ -27,94 +27,70 @@ export interface GetFileListParam {
|
||||
|
||||
export enum ElementType {
|
||||
UNKNOWN = 0,
|
||||
|
||||
TEXT = 1,
|
||||
|
||||
PIC = 2,
|
||||
|
||||
FILE = 3,
|
||||
|
||||
PTT = 4,
|
||||
|
||||
VIDEO = 5,
|
||||
|
||||
FACE = 6,
|
||||
|
||||
REPLY = 7,
|
||||
|
||||
GreyTip = 8, // “小灰条”,包括拍一拍 (Poke)、撤回提示等
|
||||
WALLET = 9,
|
||||
|
||||
/**
|
||||
* “小灰条”,包括拍一拍 (Poke)、撤回提示等
|
||||
*/
|
||||
GreyTip = 8,
|
||||
|
||||
ARK = 10,
|
||||
|
||||
MFACE = 11,
|
||||
|
||||
LIVEGIFT = 12,
|
||||
|
||||
STRUCTLONGMSG = 13,
|
||||
|
||||
MARKDOWN = 14,
|
||||
|
||||
GIPHY = 15,
|
||||
|
||||
MULTIFORWARD = 16,
|
||||
|
||||
INLINEKEYBOARD = 17,
|
||||
|
||||
INTEXTGIFT = 18,
|
||||
|
||||
CALENDAR = 19,
|
||||
|
||||
YOLOGAMERESULT = 20,
|
||||
|
||||
AVRECORD = 21,
|
||||
|
||||
FEED = 22,
|
||||
|
||||
TOFURECORD = 23,
|
||||
|
||||
ACEBUBBLE = 24,
|
||||
|
||||
ACTIVITY = 25,
|
||||
|
||||
TOFU = 26,
|
||||
|
||||
FACEBUBBLE = 27,
|
||||
|
||||
SHARELOCATION = 28,
|
||||
|
||||
TASKTOPMSG = 29,
|
||||
|
||||
RECOMMENDEDMSG = 43,
|
||||
|
||||
ACTIONBAR = 44
|
||||
}
|
||||
|
||||
type ElementFullBase = Omit<MessageElement, 'elementType' | 'elementId' | 'extBufForUI'>;
|
||||
|
||||
type ElementBase<
|
||||
K extends keyof ElementFullBase,
|
||||
S extends Partial<{ [P in K]: keyof NonNullable<ElementFullBase[P]> | Array<keyof NonNullable<ElementFullBase[P]>> }> = object
|
||||
> = {
|
||||
[P in K]:
|
||||
S[P] extends Array<infer U>
|
||||
? Pick<NonNullable<ElementFullBase[P]>, U & keyof NonNullable<ElementFullBase[P]>>
|
||||
: S[P] extends keyof NonNullable<ElementFullBase[P]>
|
||||
? Pick<NonNullable<ElementFullBase[P]>, S[P]>
|
||||
: NonNullable<ElementFullBase[P]>;
|
||||
};
|
||||
|
||||
export interface SendElementBase<ET extends ElementType> {
|
||||
elementType: ET;
|
||||
elementId: string;
|
||||
extBufForUI?: string;
|
||||
}
|
||||
|
||||
export interface ActionBarElement {
|
||||
rows: InlineKeyboardRow[];
|
||||
botAppid: string;
|
||||
}
|
||||
|
||||
export interface SendActionBarElement {
|
||||
elementType: ElementType.ACTIONBAR;
|
||||
elementId: string;
|
||||
actionBarElement: ActionBarElement;
|
||||
}
|
||||
|
||||
export interface RecommendedMsgElement {
|
||||
rows: InlineKeyboardRow[];
|
||||
botAppid: string;
|
||||
}
|
||||
|
||||
export interface SendRecommendedMsgElement {
|
||||
elementType: ElementType.RECOMMENDEDMSG;
|
||||
elementId: string;
|
||||
recommendedMsgElement: RecommendedMsgElement;
|
||||
}
|
||||
export type SendRecommendedMsgElement = SendElementBase<ElementType.RECOMMENDEDMSG> & ElementBase<'recommendedMsgElement'>;
|
||||
|
||||
export interface InlineKeyboardButton {
|
||||
id: string;
|
||||
@@ -171,11 +147,7 @@ export enum NTMsgType {
|
||||
KMSGTYPEWALLET = 10
|
||||
}
|
||||
|
||||
export interface SendTaskTopMsgElement {
|
||||
elementType: ElementType.TASKTOPMSG;
|
||||
elementId: string;
|
||||
taskTopMsgElement: TaskTopMsgElement;
|
||||
}
|
||||
export type SendTaskTopMsgElement = SendElementBase<ElementType.TASKTOPMSG> & ElementBase<'taskTopMsgElement'>;
|
||||
|
||||
export interface TofuRecordElement {
|
||||
type: number;
|
||||
@@ -194,11 +166,7 @@ export interface TofuRecordElement {
|
||||
onscreennotify: boolean;
|
||||
}
|
||||
|
||||
export interface SendTofuRecordElement {
|
||||
elementType: ElementType.TOFURECORD;
|
||||
elementId: string;
|
||||
tofuRecordElement: TofuRecordElement;
|
||||
}
|
||||
export type SendTofuRecordElement = SendElementBase<ElementType.TOFURECORD> & ElementBase<'tofuRecordElement'>;
|
||||
|
||||
export interface FaceBubbleElement {
|
||||
faceCount: number;
|
||||
@@ -216,12 +184,7 @@ export interface FaceBubbleElement {
|
||||
};
|
||||
}
|
||||
|
||||
export interface SendFaceBubbleElement {
|
||||
elementType: ElementType.FACEBUBBLE;
|
||||
elementId: string;
|
||||
faceBubbleElement: FaceBubbleElement;
|
||||
|
||||
}
|
||||
export type SendFaceBubbleElement = SendElementBase<ElementType.FACEBUBBLE> & ElementBase<'faceBubbleElement'>;
|
||||
|
||||
export interface AvRecordElement {
|
||||
type: number;
|
||||
@@ -232,11 +195,7 @@ export interface AvRecordElement {
|
||||
extraType: number;
|
||||
}
|
||||
|
||||
export interface SendavRecordElement {
|
||||
elementType: ElementType.AVRECORD;
|
||||
elementId: string;
|
||||
avRecordElement: AvRecordElement;
|
||||
}
|
||||
export type SendAvRecordElement = SendElementBase<ElementType.AVRECORD> & ElementBase<'avRecordElement'>;
|
||||
|
||||
export interface YoloUserInfo {
|
||||
uid: string;
|
||||
@@ -245,24 +204,13 @@ export interface YoloUserInfo {
|
||||
bizId: string;
|
||||
}
|
||||
|
||||
export interface SendInlineKeyboardElement {
|
||||
elementType: ElementType.INLINEKEYBOARD;
|
||||
elementId: string;
|
||||
inlineKeyboardElement: {
|
||||
rows: number;
|
||||
botAppid: string;
|
||||
};
|
||||
|
||||
}
|
||||
export type SendInlineKeyboardElement = SendElementBase<ElementType.INLINEKEYBOARD> & ElementBase<'inlineKeyboardElement'>;
|
||||
|
||||
export interface YoloGameResultElement {
|
||||
UserInfo: YoloUserInfo[];
|
||||
}
|
||||
|
||||
export interface SendYoloGameResultElement {
|
||||
elementType: ElementType.YOLOGAMERESULT;
|
||||
yoloGameResultElement: YoloGameResultElement;
|
||||
}
|
||||
export type SendYoloGameResultElement = SendElementBase<ElementType.YOLOGAMERESULT> & ElementBase<'yoloGameResultElement'>;
|
||||
|
||||
export interface GiphyElement {
|
||||
id: string;
|
||||
@@ -271,17 +219,9 @@ export interface GiphyElement {
|
||||
height: number;
|
||||
}
|
||||
|
||||
export interface SendGiphyElement {
|
||||
elementType: ElementType.GIPHY;
|
||||
elementId: string;
|
||||
giphyElement: GiphyElement;
|
||||
}
|
||||
export type SendGiphyElement = SendElementBase<ElementType.GIPHY> & ElementBase<'giphyElement'>;
|
||||
|
||||
export interface SendWalletElement {
|
||||
elementType: ElementType.UNKNOWN;//不做 设置位置
|
||||
elementId: string;
|
||||
walletElement: Record<string, never>;
|
||||
}
|
||||
export type SendWalletElement = SendElementBase<ElementType.UNKNOWN> & ElementBase<'walletElement'>;
|
||||
|
||||
export interface CalendarElement {
|
||||
summary: string;
|
||||
@@ -291,49 +231,16 @@ export interface CalendarElement {
|
||||
schema: string;
|
||||
}
|
||||
|
||||
export interface SendCalendarElement {
|
||||
elementType: ElementType.CALENDAR;
|
||||
elementId: string;
|
||||
calendarElement: CalendarElement;
|
||||
}
|
||||
export type SendCalendarElement = SendElementBase<ElementType.CALENDAR> & ElementBase<'calendarElement'>;
|
||||
|
||||
export interface SendliveGiftElement {
|
||||
elementType: ElementType.LIVEGIFT;
|
||||
elementId: string;
|
||||
liveGiftElement: Record<string, never>;
|
||||
}
|
||||
export type SendLiveGiftElement = SendElementBase<ElementType.LIVEGIFT> & ElementBase<'liveGiftElement'>;
|
||||
|
||||
export interface SendTextElement {
|
||||
elementType: ElementType.TEXT;
|
||||
elementId: string;
|
||||
textElement: {
|
||||
content: string;
|
||||
atType: number;
|
||||
atUid: string;
|
||||
atTinyId: string;
|
||||
atNtUid: string;
|
||||
};
|
||||
}
|
||||
export type SendTextElement = SendElementBase<ElementType.TEXT> & ElementBase<'textElement'>;
|
||||
|
||||
export interface SendPttElement {
|
||||
elementType: ElementType.PTT;
|
||||
elementId: string;
|
||||
pttElement: {
|
||||
fileName: string;
|
||||
filePath: string;
|
||||
md5HexStr: string;
|
||||
fileSize: number;
|
||||
duration: number; // 单位是秒
|
||||
formatType: number;
|
||||
voiceType: number;
|
||||
voiceChangeType: number;
|
||||
canConvert2Text: boolean;
|
||||
waveAmplitudes: number[];
|
||||
fileSubId: string;
|
||||
playState: number;
|
||||
autoConvertText: number;
|
||||
};
|
||||
}
|
||||
export type SendPttElement = SendElementBase<ElementType.PTT> & ElementBase<'pttElement', {
|
||||
pttElement: ['fileName', 'filePath', 'md5HexStr', 'fileSize', 'duration', 'formatType', 'voiceType',
|
||||
'voiceChangeType', 'canConvert2Text', 'waveAmplitudes', 'fileSubId', 'playState', 'autoConvertText']
|
||||
}>;
|
||||
|
||||
export enum PicType {
|
||||
gif = 2000,
|
||||
@@ -359,11 +266,7 @@ export enum NTMsgAtType {
|
||||
ATTYPEUNKNOWN = 0
|
||||
}
|
||||
|
||||
export interface SendPicElement {
|
||||
elementType: ElementType.PIC;
|
||||
elementId: string;
|
||||
picElement: PicElement;
|
||||
}
|
||||
export type SendPicElement = SendElementBase<ElementType.PIC> & ElementBase<'picElement'>;
|
||||
|
||||
export interface ReplyElement {
|
||||
sourceMsgIdInRecords?: string;
|
||||
@@ -372,55 +275,30 @@ export interface ReplyElement {
|
||||
senderUin: string;
|
||||
senderUidStr?: string;
|
||||
replyMsgTime?: string;
|
||||
replyMsgClientSeq?: string;
|
||||
}
|
||||
|
||||
export interface SendReplyElement {
|
||||
elementType: ElementType.REPLY;
|
||||
elementId: string;
|
||||
replyElement: ReplyElement;
|
||||
}
|
||||
export type SendReplyElement = SendElementBase<ElementType.REPLY> & ElementBase<'replyElement'>;
|
||||
|
||||
export interface SendFaceElement {
|
||||
elementType: ElementType.FACE;
|
||||
elementId: string;
|
||||
faceElement: FaceElement;
|
||||
}
|
||||
export type SendFaceElement = SendElementBase<ElementType.FACE> & ElementBase<'faceElement'>;
|
||||
|
||||
export interface SendMarketFaceElement {
|
||||
elementType: ElementType.MFACE;
|
||||
marketFaceElement: MarketFaceElement;
|
||||
}
|
||||
export type SendMarketFaceElement = SendElementBase<ElementType.MFACE> & ElementBase<'marketFaceElement'>;
|
||||
|
||||
export interface SendstructLongMsgElement {
|
||||
elementType: ElementType.STRUCTLONGMSG;
|
||||
elementId: string;
|
||||
structLongMsgElement: StructLongMsgElement;
|
||||
}
|
||||
export type SendStructLongMsgElement = SendElementBase<ElementType.STRUCTLONGMSG> & ElementBase<'structLongMsgElement'>;
|
||||
|
||||
export interface StructLongMsgElement {
|
||||
xmlContent: string;
|
||||
resId: string;
|
||||
}
|
||||
|
||||
export interface SendactionBarElement {
|
||||
elementType: ElementType.ACTIONBAR;
|
||||
elementId: string;
|
||||
actionBarElement: {
|
||||
rows: number;
|
||||
botAppid: string;
|
||||
};
|
||||
}
|
||||
export type SendActionBarElement = SendElementBase<ElementType.ACTIONBAR> & ElementBase<'actionBarElement'>;
|
||||
|
||||
export interface ShareLocationElement {
|
||||
text: string;
|
||||
ext: string;
|
||||
}
|
||||
|
||||
export interface SendShareLocationElement {
|
||||
elementType: ElementType.SHARELOCATION;
|
||||
elementId: string;
|
||||
shareLocationElement?: ShareLocationElement;
|
||||
}
|
||||
export type SendShareLocationElement = SendElementBase<ElementType.SHARELOCATION> & ElementBase<'shareLocationElement'>;
|
||||
|
||||
export interface FileElement {
|
||||
fileMd5?: string;
|
||||
@@ -440,29 +318,13 @@ export interface FileElement {
|
||||
fileBizId?: number;
|
||||
}
|
||||
|
||||
export interface SendFileElement {
|
||||
elementType: ElementType.FILE;
|
||||
elementId: string;
|
||||
fileElement: FileElement;
|
||||
}
|
||||
export type SendFileElement = SendElementBase<ElementType.FILE> & ElementBase<'fileElement'>;
|
||||
|
||||
export interface SendVideoElement {
|
||||
elementType: ElementType.VIDEO;
|
||||
elementId: string;
|
||||
videoElement: VideoElement;
|
||||
}
|
||||
export type SendVideoElement = SendElementBase<ElementType.VIDEO> & ElementBase<'videoElement'>;
|
||||
|
||||
export interface SendArkElement {
|
||||
elementType: ElementType.ARK;
|
||||
elementId: string;
|
||||
arkElement: ArkElement;
|
||||
}
|
||||
export type SendArkElement = SendElementBase<ElementType.ARK> & ElementBase<'arkElement'>;
|
||||
|
||||
export interface SendMarkdownElement {
|
||||
elementType: ElementType.MARKDOWN;
|
||||
elementId: string;
|
||||
markdownElement: MarkdownElement;
|
||||
}
|
||||
export type SendMarkdownElement = SendElementBase<ElementType.MARKDOWN> & ElementBase<'markdownElement'>;
|
||||
|
||||
export type SendMessageElement = SendTextElement | SendPttElement |
|
||||
SendPicElement | SendReplyElement | SendFaceElement | SendMarketFaceElement | SendFileElement |
|
||||
@@ -479,7 +341,7 @@ export interface TextElement {
|
||||
export interface MessageElement {
|
||||
elementType: ElementType,
|
||||
elementId: string,
|
||||
extBufForUI: string,//"0x",
|
||||
extBufForUI?: string, //"0x",
|
||||
textElement?: TextElement;
|
||||
faceElement?: FaceElement,
|
||||
marketFaceElement?: MarketFaceElement,
|
||||
@@ -508,7 +370,6 @@ export interface MessageElement {
|
||||
taskTopMsgElement?: TaskTopMsgElement,
|
||||
recommendedMsgElement?: RecommendedMsgElement,
|
||||
actionBarElement?: ActionBarElement
|
||||
|
||||
}
|
||||
|
||||
export enum AtType {
|
||||
@@ -516,6 +377,12 @@ export enum AtType {
|
||||
atAll = 1,
|
||||
atUser = 2
|
||||
}
|
||||
export enum MsgSourceType {
|
||||
K_DOWN_SOURCETYPE_AIOINNER = 1,
|
||||
K_DOWN_SOURCETYPE_BIGSCREEN = 2,
|
||||
K_DOWN_SOURCETYPE_HISTORY = 3,
|
||||
K_DOWN_SOURCETYPE_UNKNOWN = 0
|
||||
}
|
||||
|
||||
// 来自Android分析
|
||||
export enum ChatType {
|
||||
@@ -571,7 +438,7 @@ export interface PttElement {
|
||||
fileSize: string; // "4261"
|
||||
fileSubId: string; // "0"
|
||||
fileUuid: string; // "90j3z7rmRphDPrdVgP9udFBaYar#oK0TWZIV"
|
||||
formatType: string; // 1
|
||||
formatType: number; // 1
|
||||
invalidState: number; // 0
|
||||
md5HexStr: string; // "e4d09c784d5a2abcb2f9980bdc7acfe6"
|
||||
playState: number; // 0
|
||||
@@ -582,6 +449,7 @@ export interface PttElement {
|
||||
voiceChangeType: number; // 0
|
||||
voiceType: number; // 0
|
||||
waveAmplitudes: number[];
|
||||
autoConvertText: number;
|
||||
}
|
||||
|
||||
export interface ArkElement {
|
||||
@@ -659,7 +527,8 @@ export interface GrayTipElement {
|
||||
export enum FaceType {
|
||||
normal = 1, // 小黄脸
|
||||
normal2 = 2, // 新小黄脸, 从faceIndex 222开始?
|
||||
dice = 3 // 骰子
|
||||
dice = 3, // 骰子
|
||||
poke = 5 // 拍一拍
|
||||
}
|
||||
|
||||
export enum FaceIndex {
|
||||
@@ -786,7 +655,8 @@ export interface InlineKeyboardElementRowButton {
|
||||
export interface InlineKeyboardElement {
|
||||
rows: [{
|
||||
buttons: InlineKeyboardElementRowButton[]
|
||||
}];
|
||||
}],
|
||||
botAppid: string;
|
||||
}
|
||||
|
||||
export interface TipAioOpGrayTipElement { // 这是什么提示来着?
|
||||
@@ -872,6 +742,8 @@ export interface RawMessage {
|
||||
/**
|
||||
* 扩展字段,与 Ob11 msg ID 有关
|
||||
*/
|
||||
|
||||
|
||||
id?: number;
|
||||
|
||||
guildId: string;
|
||||
@@ -908,11 +780,26 @@ export interface RawMessage {
|
||||
*/
|
||||
peerUin: string;
|
||||
|
||||
/**
|
||||
* 好友备注(如果是好友消息)
|
||||
*/
|
||||
remark?: string;
|
||||
|
||||
/**
|
||||
* 群名(如果是群消息)
|
||||
*/
|
||||
peerName: string;
|
||||
|
||||
/**
|
||||
* 发送者昵称(如果是好友消息)
|
||||
*/
|
||||
sendNickName: string;
|
||||
|
||||
/**
|
||||
* 发送者好友备注(如果是群消息并且有发送者好友)
|
||||
*/
|
||||
sendRemarkName: string;
|
||||
|
||||
/**
|
||||
* 发送者群名片(如果是群消息)
|
||||
*/
|
||||
@@ -933,6 +820,10 @@ export interface RawMessage {
|
||||
records: RawMessage[];
|
||||
|
||||
elements: MessageElement[];
|
||||
|
||||
sourceType: MsgSourceType;
|
||||
|
||||
isOnlineMsg: boolean;
|
||||
}
|
||||
export interface QueryMsgsParams {
|
||||
chatInfo: Peer;
|
||||
@@ -973,4 +864,4 @@ export interface MsgReqType {
|
||||
extraCnt: number
|
||||
}
|
||||
//getMsgsIncludeSelf Peer必须 byType 1
|
||||
//getMsgsWithMsgTimeAndClientSeqForC2C Peer必须 byType 3
|
||||
//getMsgsWithMsgTimeAndClientSeqForC2C Peer必须 byType 3
|
||||
|
@@ -43,6 +43,50 @@ export enum GroupInviteType {
|
||||
BYGROUPMEMBER,
|
||||
BYDISCUSSMEMBER
|
||||
}
|
||||
export interface ShutUpGroupHonor {
|
||||
[key: string]: number;
|
||||
}
|
||||
|
||||
export interface ShutUpGroupMember {
|
||||
uid: string;
|
||||
qid: string;
|
||||
uin: string;
|
||||
nick: string;
|
||||
remark: string;
|
||||
cardType: number;
|
||||
cardName: string;
|
||||
role: number;
|
||||
avatarPath: string;
|
||||
shutUpTime: number;
|
||||
isDelete: boolean;
|
||||
isSpecialConcerned: boolean;
|
||||
isSpecialShield: boolean;
|
||||
isRobot: boolean;
|
||||
groupHonor: ShutUpGroupHonor;
|
||||
memberRealLevel: number;
|
||||
memberLevel: number;
|
||||
globalGroupLevel: number;
|
||||
globalGroupPoint: number;
|
||||
memberTitleId: number;
|
||||
memberSpecialTitle: string;
|
||||
specialTitleExpireTime: string;
|
||||
userShowFlag: number;
|
||||
userShowFlagNew: number;
|
||||
richFlag: number;
|
||||
mssVipType: number;
|
||||
bigClubLevel: number;
|
||||
bigClubFlag: number;
|
||||
autoRemark: string;
|
||||
creditLevel: number;
|
||||
joinTime: number;
|
||||
lastSpeakTime: number;
|
||||
memberFlag: number;
|
||||
memberFlagExt: number;
|
||||
memberMobileFlag: number;
|
||||
memberFlagExt2: number;
|
||||
isSpecialShielded: boolean;
|
||||
cardNameId: number;
|
||||
}
|
||||
|
||||
export interface GroupNotify {
|
||||
seq: string; // 通知序列号
|
||||
|
@@ -1,14 +1,15 @@
|
||||
export interface IdMusicSignPostData {
|
||||
type: 'qq' | '163',
|
||||
type: 'qq' | '163' | 'kugou' | 'migu' | 'kuwo',
|
||||
id: string | number,
|
||||
}
|
||||
|
||||
export interface CustomMusicSignPostData {
|
||||
type: 'custom',
|
||||
type: 'qq' | '163' | 'kugou' | 'migu' | 'kuwo' | 'custom',
|
||||
id: undefined,
|
||||
url: string,
|
||||
audio: string,
|
||||
title: string,
|
||||
image?: string,
|
||||
audio?: string,
|
||||
title?: string,
|
||||
image: string,
|
||||
singer?: string
|
||||
}
|
||||
|
||||
|
@@ -288,9 +288,9 @@ export interface User {
|
||||
export interface SelfInfo extends User {
|
||||
online?: boolean;
|
||||
}
|
||||
export type Friend = User;
|
||||
|
||||
export interface Friend extends User {
|
||||
}
|
||||
// 本来是 Friend extends User 现在用不到
|
||||
|
||||
export enum BizKey {
|
||||
KPRIVILEGEICON,
|
||||
|
84
src/core/external/appid.json
vendored
84
src/core/external/appid.json
vendored
@@ -1,10 +1,90 @@
|
||||
{
|
||||
"9.9.15-28060":{
|
||||
"9.9.15-28060": {
|
||||
"appid": 537246092,
|
||||
"qua": "V1_WIN_NQ_9.9.15_28060_GW_B"
|
||||
},
|
||||
"3.2.12-28060":{
|
||||
"9.9.15-28131": {
|
||||
"appid": 537246092,
|
||||
"qua": "V1_WIN_NQ_9.9.15_28131_GW_B"
|
||||
},
|
||||
"3.2.12-28060": {
|
||||
"appid": 537246140,
|
||||
"qua": "V1_LNX_NQ_3.2.12_28060_GW_B"
|
||||
},
|
||||
"3.2.12-28131": {
|
||||
"appid": 537246140,
|
||||
"qua": "V1_LNX_NQ_3.2.12_28131_GW_B"
|
||||
},
|
||||
"6.9.55-28131": {
|
||||
"appid": 537246115,
|
||||
"qua": "V1_MAC_NQ_6.9.55_28131_GW_B"
|
||||
},
|
||||
"9.9.15-28327": {
|
||||
"appid": 537249321,
|
||||
"qua": "V1_WIN_NQ_9.9.15_28327_GW_B"
|
||||
},
|
||||
"3.2.12-28327": {
|
||||
"appid": 537249393,
|
||||
"qua": "V1_LNX_NQ_3.2.12_28327_GW_B"
|
||||
},
|
||||
"9.9.15-28418": {
|
||||
"appid": 537249321,
|
||||
"qua": "V1_WIN_NQ_9.9.15_28418_GW_B"
|
||||
},
|
||||
"3.2.12-28418": {
|
||||
"appid": 537249393,
|
||||
"qua": "V1_LNX_NQ_3.2.12_28418_GW_B"
|
||||
},
|
||||
"6.9.56-28418": {
|
||||
"appid": 537249367,
|
||||
"qua": "V1_MAC_NQ_6.9.56_28418_GW_B"
|
||||
},
|
||||
"9.9.15-28498": {
|
||||
"appid": 537249321,
|
||||
"qua": "V1_WIN_NQ_9.9.15_28498_GW_B"
|
||||
},
|
||||
"3.2.13-28788": {
|
||||
"appid": 537249787,
|
||||
"qua": "V1_LNX_NQ_3.2.13_28788_GW_B"
|
||||
},
|
||||
"9.9.16-28788": {
|
||||
"appid": 537249739,
|
||||
"qua": "V1_WIN_NQ_9.9.16_28788_GW_B"
|
||||
},
|
||||
"9.9.16-28971": {
|
||||
"appid": 537249775,
|
||||
"qua": "V1_WIN_NQ_9.9.16_28971_GW_B"
|
||||
},
|
||||
"3.2.13-28971": {
|
||||
"appid": 537249848,
|
||||
"qua": "V1_LNX_NQ_3.2.13_28971_GW_B"
|
||||
},
|
||||
"6.9.58-28971": {
|
||||
"appid": 537249826,
|
||||
"qua": "V1_MAC_NQ_6.9.58_28971_GW_B"
|
||||
},
|
||||
"9.9.16-29271": {
|
||||
"appid": 537249813,
|
||||
"qua": "V1_WIN_NQ_9.9.16_29271_GW_B"
|
||||
},
|
||||
"3.2.13-29271": {
|
||||
"appid": 537249913,
|
||||
"qua": "V1_LNX_NQ_3.2.13_29271_GW_B"
|
||||
},
|
||||
"6.9.59-29271": {
|
||||
"appid": 537249863,
|
||||
"qua": "V1_MAC_NQ_6.9.59_29271_GW_B"
|
||||
},
|
||||
"9.9.16-29456": {
|
||||
"appid": 537249875,
|
||||
"qua": "V1_WIN_NQ_9.9.16_29456_GW_B"
|
||||
},
|
||||
"3.2.13-29456": {
|
||||
"appid": 537249996,
|
||||
"qua": "V1_LNX_NQ_3.2.13_29456_GW_B"
|
||||
},
|
||||
"6.9.59-29456": {
|
||||
"appid": 537249961,
|
||||
"qua": "V1_MAC_NQ_6.9.59_29456_GW_B"
|
||||
}
|
||||
}
|
||||
|
4
src/core/external/napcat.json
vendored
4
src/core/external/napcat.json
vendored
@@ -2,5 +2,7 @@
|
||||
"fileLog": true,
|
||||
"consoleLog": true,
|
||||
"fileLogLevel": "debug",
|
||||
"consoleLogLevel": "info"
|
||||
"consoleLogLevel": "info",
|
||||
"packetBackend": "auto",
|
||||
"packetServer": ""
|
||||
}
|
||||
|
70
src/core/external/offset.json
vendored
Normal file
70
src/core/external/offset.json
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
{
|
||||
"6.9.56-28418-arm64": {
|
||||
"send": "4471360",
|
||||
"recv": "4473BCC"
|
||||
},
|
||||
"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"
|
||||
},
|
||||
"3.2.13-28788-arm64": {
|
||||
"send": "6E91018",
|
||||
"recv": "6E94850"
|
||||
},
|
||||
"9.9.16-28971-x64": {
|
||||
"send": "38079F0",
|
||||
"recv": "380BE24"
|
||||
},
|
||||
"3.2.13-28971-x64": {
|
||||
"send": "A0CEF60",
|
||||
"recv": "A0D2860"
|
||||
},
|
||||
"3.2.12-28971-arm64": {
|
||||
"send": "6E91318",
|
||||
"recv": "6E94B50"
|
||||
},
|
||||
"6.9.58-28971-arm64": {
|
||||
"send": "449ACA0",
|
||||
"recv": "449D50C"
|
||||
},
|
||||
"9.9.16-29271-x64": {
|
||||
"send": "3833510",
|
||||
"recv": "3837944"
|
||||
},
|
||||
"3.2.13-29271-x64": {
|
||||
"send": "A11E680",
|
||||
"recv": "A121F80"
|
||||
},
|
||||
"3.2.13-29271-arm64": {
|
||||
"send": "6ECA098",
|
||||
"recv": "6ECD8D0"
|
||||
},
|
||||
"9.9.16-29456-x64": {
|
||||
"send": "3835CD0",
|
||||
"recv": "383A104"
|
||||
},
|
||||
"3.2.13-29456-x64": {
|
||||
"send": "A11E820",
|
||||
"recv": "A122120"
|
||||
},
|
||||
"3.2.13-29456-arm64": {
|
||||
"send": "6ECA130",
|
||||
"recv": "6ECD968"
|
||||
}
|
||||
}
|
@@ -26,7 +26,8 @@ export class RkeyManager {
|
||||
try {
|
||||
await this.refreshRkey();
|
||||
} catch (e) {
|
||||
this.logger.logError('获取rkey失败', e);
|
||||
throw new Error(`获取rkey失败: ${e}`);//外抛
|
||||
//this.logger.logError.bind(this.logger)('获取rkey失败', e);
|
||||
}
|
||||
}
|
||||
return this.rkeyData;
|
||||
@@ -42,9 +43,18 @@ export class RkeyManager {
|
||||
//刷新rkey
|
||||
for (const url of this.serverUrl) {
|
||||
try {
|
||||
this.rkeyData = await RequestUtil.HttpGetJson<ServerRkeyData>(url, 'GET');
|
||||
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(`[Rkey] Get Rkey ${url} Error `, e);
|
||||
this.logger.logError.bind(this.logger)(`[Rkey] Get Rkey ${url} Error `, e);
|
||||
//是否为最后一个url
|
||||
if (url === this.serverUrl[this.serverUrl.length - 1]) {
|
||||
throw new Error(`获取rkey失败: ${e}`);//外抛
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -22,14 +22,14 @@ import { QQBasicInfoWrapper } from '@/common/qq-basic-info';
|
||||
import { NapCatPathWrapper } from '@/common/path';
|
||||
import path from 'node:path';
|
||||
import fs from 'node:fs';
|
||||
import { getMachineId, hostname, systemName, systemVersion } from '@/common/system';
|
||||
import { hostname, systemName, systemVersion } from '@/common/system';
|
||||
import { NTEventWrapper } from '@/common/event';
|
||||
import { DataSource, GroupMember, KickedOffLineInfo, SelfInfo, SelfStatusInfo } from '@/core/entities';
|
||||
import { NapCatConfigLoader } from '@/core/helper/config';
|
||||
import os from 'node:os';
|
||||
import { NodeIKernelGroupListener, NodeIKernelMsgListener, NodeIKernelProfileListener } from '@/core/listeners';
|
||||
import { proxiedListenerOf } from '@/common/proxy-handler';
|
||||
|
||||
import { NTQQPacketApi } from './apis/packet';
|
||||
export * from './wrapper';
|
||||
export * from './entities';
|
||||
export * from './services';
|
||||
@@ -54,11 +54,34 @@ export function loadQQWrapper(QQVersion: string): WrapperNodeApi {
|
||||
if (!fs.existsSync(wrapperNodePath)) {
|
||||
wrapperNodePath = path.join(appPath, `./resources/app/wrapper.node`);
|
||||
}
|
||||
//老版本兼容 未来去掉
|
||||
if (!fs.existsSync(wrapperNodePath)) {
|
||||
wrapperNodePath = path.join(path.dirname(process.execPath), `./resources/app/versions/${QQVersion}/wrapper.node`);
|
||||
}
|
||||
const nativemodule: any = { exports: {} };
|
||||
process.dlopen(nativemodule, wrapperNodePath);
|
||||
return nativemodule.exports;
|
||||
}
|
||||
|
||||
export function getMajorPath(QQVersion: string): string {
|
||||
// major.node
|
||||
let appPath;
|
||||
if (os.platform() === 'darwin') {
|
||||
appPath = path.resolve(path.dirname(process.execPath), '../Resources/app');
|
||||
} else if (os.platform() === 'linux') {
|
||||
appPath = path.resolve(path.dirname(process.execPath), './resources/app');
|
||||
} else {
|
||||
appPath = path.resolve(path.dirname(process.execPath), `./versions/${QQVersion}/`);
|
||||
}
|
||||
let majorPath = path.resolve(appPath, 'major.node');
|
||||
if (!fs.existsSync(majorPath)) {
|
||||
majorPath = path.join(appPath, `./resources/app/major.node`);
|
||||
}
|
||||
//老版本兼容 未来去掉
|
||||
if (!fs.existsSync(majorPath)) {
|
||||
majorPath = path.join(path.dirname(process.execPath), `./resources/app/versions/${QQVersion}/major.node`);
|
||||
}
|
||||
return majorPath;
|
||||
}
|
||||
export class NapCatCore {
|
||||
readonly context: InstanceContext;
|
||||
readonly apis: StableNTApiWrapper;
|
||||
@@ -77,17 +100,18 @@ export class NapCatCore {
|
||||
this.context = context;
|
||||
this.util = this.context.wrapper.NodeQQNTWrapperUtil;
|
||||
this.eventWrapper = new NTEventWrapper(context.session);
|
||||
this.configLoader = new NapCatConfigLoader(this, this.context.pathWrapper.configPath);
|
||||
this.apis = {
|
||||
FileApi: new NTQQFileApi(this.context, this),
|
||||
SystemApi: new NTQQSystemApi(this.context, this),
|
||||
CollectionApi: new NTQQCollectionApi(this.context, this),
|
||||
PacketApi: new NTQQPacketApi(this.context, this),
|
||||
WebApi: new NTQQWebApi(this.context, this),
|
||||
FriendApi: new NTQQFriendApi(this.context, this),
|
||||
MsgApi: new NTQQMsgApi(this.context, this),
|
||||
UserApi: new NTQQUserApi(this.context, this),
|
||||
GroupApi: new NTQQGroupApi(this.context, this),
|
||||
};
|
||||
this.configLoader = new NapCatConfigLoader(this, this.context.pathWrapper.configPath);
|
||||
this.NapCatDataPath = path.join(this.dataPath, 'NapCat');
|
||||
fs.mkdirSync(this.NapCatDataPath, { recursive: true });
|
||||
this.NapCatTempPath = path.join(this.NapCatDataPath, 'temp');
|
||||
@@ -95,7 +119,8 @@ export class NapCatCore {
|
||||
if (!fs.existsSync(this.NapCatTempPath)) {
|
||||
fs.mkdirSync(this.NapCatTempPath, { recursive: true });
|
||||
}
|
||||
this.initNapCatCoreListeners().then().catch(this.context.logger.logError);
|
||||
|
||||
this.initNapCatCoreListeners().then().catch(this.context.logger.logError.bind(this.context.logger));
|
||||
|
||||
this.context.logger.setFileLogEnabled(
|
||||
this.configLoader.configData.fileLog,
|
||||
@@ -123,7 +148,7 @@ export class NapCatCore {
|
||||
const msgListener = new NodeIKernelMsgListener();
|
||||
msgListener.onKickedOffLine = (Info: KickedOffLineInfo) => {
|
||||
// 下线通知
|
||||
this.context.logger.logError('[KickedOffLine] [' + Info.tipsTitle + '] ' + Info.tipsDesc);
|
||||
this.context.logger.logError.bind(this.context.logger)('[KickedOffLine] [' + Info.tipsTitle + '] ' + Info.tipsDesc);
|
||||
this.selfInfo.online = false;
|
||||
};
|
||||
msgListener.onRecvMsg = (msgs) => {
|
||||
@@ -134,7 +159,7 @@ export class NapCatCore {
|
||||
};
|
||||
//await sleep(2500);
|
||||
this.context.session.getMsgService().addKernelMsgListener(
|
||||
proxiedListenerOf(msgListener, this.context.logger) as any,
|
||||
proxiedListenerOf(msgListener, this.context.logger),
|
||||
);
|
||||
|
||||
const profileListener = new NodeIKernelProfileListener();
|
||||
@@ -230,7 +255,7 @@ export class NapCatCore {
|
||||
}
|
||||
};
|
||||
this.context.session.getGroupService().addKernelGroupListener(
|
||||
proxiedListenerOf(groupListener, this.context.logger) as any,
|
||||
proxiedListenerOf(groupListener, this.context.logger),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -243,22 +268,34 @@ export class NapCatCore {
|
||||
}
|
||||
}
|
||||
|
||||
export async function genSessionConfig(QQVersionAppid: string, QQVersion: string, selfUin: string, selfUid: string, account_path: string): Promise<WrapperSessionInitConfig> {
|
||||
export async function genSessionConfig(
|
||||
guid: string,
|
||||
QQVersionAppid: string,
|
||||
QQVersion: string,
|
||||
selfUin: string,
|
||||
selfUid: string,
|
||||
account_path: string
|
||||
): Promise<WrapperSessionInitConfig> {
|
||||
const downloadPath = path.join(account_path, 'NapCat', 'temp');
|
||||
fs.mkdirSync(downloadPath, { recursive: true });
|
||||
const guid: string = await getMachineId();//26702 支持JS获取guid值 在LoginService中获取 TODO mlikiow a
|
||||
const platformMapping: Partial<Record<NodeJS.Platform, PlatformType>> = {
|
||||
win32: PlatformType.KWINDOWS,
|
||||
darwin: PlatformType.KMAC,
|
||||
linux: PlatformType.KLINUX,
|
||||
};
|
||||
const systemPlatform = platformMapping[os.platform()] ?? PlatformType.KWINDOWS;
|
||||
return {
|
||||
selfUin,
|
||||
selfUid,
|
||||
desktopPathConfig: {
|
||||
account_path, // 可以通过NodeQQNTWrapperUtil().getNTUserDataInfoConfig()获取
|
||||
},
|
||||
clientVer: QQVersion, // 9.9.8-22355
|
||||
clientVer: QQVersion,
|
||||
a2: '',
|
||||
d2: '',
|
||||
d2Key: '',
|
||||
machineId: '',
|
||||
platform: PlatformType.KWINDOWS, // 3是Windows?
|
||||
platform: systemPlatform, // 3是Windows?
|
||||
platVer: systemVersion, // 系统版本号, 应该可以固定
|
||||
appid: QQVersionAppid,
|
||||
rdeliveryConfig: {
|
||||
@@ -266,7 +303,7 @@ export async function genSessionConfig(QQVersionAppid: string, QQVersion: string
|
||||
systemId: 0,
|
||||
appId: '',
|
||||
logicEnvironment: '',
|
||||
platform: PlatformType.KWINDOWS,
|
||||
platform: systemPlatform,
|
||||
language: '',
|
||||
sdkVersion: '',
|
||||
userId: '',
|
||||
@@ -306,6 +343,7 @@ export interface InstanceContext {
|
||||
export interface StableNTApiWrapper {
|
||||
FileApi: NTQQFileApi,
|
||||
SystemApi: NTQQSystemApi,
|
||||
PacketApi: NTQQPacketApi,
|
||||
CollectionApi: NTQQCollectionApi,
|
||||
WebApi: NTQQWebApi,
|
||||
FriendApi: NTQQFriendApi,
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { DataSource, Group, GroupListUpdateType, GroupMember, GroupNotify } from '@/core/entities';
|
||||
import { DataSource, Group, GroupListUpdateType, GroupMember, GroupNotify, ShutUpGroupMember } from '@/core/entities';
|
||||
|
||||
export class NodeIKernelGroupListener {
|
||||
onGroupListInited(listEmpty: boolean): void { }
|
||||
@@ -71,7 +71,8 @@ export class NodeIKernelGroupListener {
|
||||
sceneId: string,
|
||||
ids: string[],
|
||||
infos: Map<string, GroupMember>, // uid -> GroupMember
|
||||
finish: boolean,
|
||||
hasPrev: boolean,
|
||||
hasNext: boolean,
|
||||
hasRobot: boolean
|
||||
}) {
|
||||
}
|
||||
@@ -79,6 +80,6 @@ export class NodeIKernelGroupListener {
|
||||
onSearchMemberChange(...args: unknown[]) {
|
||||
}
|
||||
|
||||
onShutUpMemberListChanged(...args: unknown[]) {
|
||||
onShutUpMemberListChanged(groupCode: string, members: Array<ShutUpGroupMember>) {
|
||||
}
|
||||
}
|
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[]) {
|
||||
|
||||
}
|
||||
}
|
101
src/core/packet/client/client.ts
Normal file
101
src/core/packet/client/client.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
import { LRUCache } from "@/common/lru-cache";
|
||||
import { NapCatCore } from "@/core";
|
||||
import { LogWrapper } from "@/common/log";
|
||||
import crypto, { createHash } from "crypto";
|
||||
import { OidbPacket, PacketHexStr } from "@/core/packet/packer";
|
||||
import { NapCatConfig } from "@/core/helper/config";
|
||||
|
||||
export interface RecvPacket {
|
||||
type: string, // 仅recv
|
||||
trace_id_md5?: string,
|
||||
data: RecvPacketData
|
||||
}
|
||||
|
||||
export interface RecvPacketData {
|
||||
seq: number
|
||||
cmd: string
|
||||
hex_data: string
|
||||
}
|
||||
|
||||
export abstract class PacketClient {
|
||||
readonly napCatCore: NapCatCore;
|
||||
protected readonly logger: LogWrapper;
|
||||
protected readonly cb = new LRUCache<string, (json: RecvPacketData) => Promise<void>>(500); // trace_id-type callback
|
||||
protected isAvailable: boolean = false;
|
||||
protected config: NapCatConfig;
|
||||
|
||||
protected constructor(core: NapCatCore) {
|
||||
this.napCatCore = core;
|
||||
this.logger = core.context.logger;
|
||||
this.config = core.configLoader.configData;
|
||||
}
|
||||
|
||||
private randText(len: number): string {
|
||||
let text = '';
|
||||
const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
for (let i = 0; i < len; i++) {
|
||||
text += possible.charAt(Math.floor(Math.random() * possible.length));
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
get available(): boolean {
|
||||
return this.isAvailable;
|
||||
}
|
||||
|
||||
abstract check(core: NapCatCore): boolean;
|
||||
|
||||
abstract init(pid: number, recv: string, send: string): Promise<void>;
|
||||
|
||||
abstract connect(cb: () => void): Promise<void>;
|
||||
|
||||
abstract sendCommandImpl(cmd: string, data: string, trace_id: string): void;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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) => {
|
||||
const timeoutHandle = setTimeout(() => {
|
||||
reject(new Error(`sendCommand timed out after ${timeout} ms for ${cmd} with trace_id ${trace_id}`));
|
||||
}, timeout);
|
||||
this.registerCallback(trace_id, 'send', async (json: RecvPacketData) => {
|
||||
sendcb(json);
|
||||
if (!rsp) {
|
||||
clearTimeout(timeoutHandle);
|
||||
resolve(json);
|
||||
}
|
||||
});
|
||||
if (rsp) {
|
||||
this.registerCallback(trace_id, 'recv', async (json: RecvPacketData) => {
|
||||
clearTimeout(timeoutHandle);
|
||||
resolve(json);
|
||||
});
|
||||
}
|
||||
this.sendCommandImpl(cmd, data, trace_id);
|
||||
});
|
||||
}
|
||||
|
||||
async sendPacket(cmd: string, data: PacketHexStr, rsp = false): Promise<RecvPacketData> {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!this.available) {
|
||||
this.logger.logError('NapCat.Packet 未初始化!');
|
||||
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 () => {
|
||||
//console.log('sendPacket:', cmd, data, trace_id);
|
||||
await this.napCatCore.context.session.getMsgService().sendSsoCmdReqByContend(cmd, trace_id);
|
||||
}).then((res) => resolve(res)).catch((e: Error) => reject(e));
|
||||
});
|
||||
}
|
||||
|
||||
async sendOidbPacket(pkt: OidbPacket, rsp = false): Promise<RecvPacketData> {
|
||||
return this.sendPacket(pkt.cmd, pkt.data, rsp);
|
||||
}
|
||||
}
|
80
src/core/packet/client/nativeClient.ts
Normal file
80
src/core/packet/client/nativeClient.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
import crypto, { createHash } from "crypto";
|
||||
import { NapCatCore } from "@/core";
|
||||
import path, { dirname } from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
import fs from "fs";
|
||||
import { PacketClient } from "@/core/packet/client/client";
|
||||
import { constants } from "node:os";
|
||||
import { LRUCache } from "@/common/lru-cache";
|
||||
//0 send 1recv
|
||||
export interface NativePacketExportType {
|
||||
InitHook?: (recv: string, send: string, callback: (type: number, uin: string, cmd: string, seq: number, hex_data: string) => void) => boolean;
|
||||
SendPacket?: (cmd: string, data: string, trace_id: string) => void;
|
||||
}
|
||||
export class NativePacketClient extends PacketClient {
|
||||
private readonly supportedPlatforms = ['win32.x64', 'linux.x64', 'linux.arm64'];
|
||||
private MoeHooExport: { exports: NativePacketExportType } = { exports: {} };
|
||||
private sendEvent = new LRUCache<number, string>(500);//seq->trace_id
|
||||
constructor(core: NapCatCore) {
|
||||
super(core);
|
||||
}
|
||||
|
||||
get available(): boolean {
|
||||
return this.isAvailable;
|
||||
}
|
||||
|
||||
check(): boolean {
|
||||
const platform = process.platform + '.' + process.arch;
|
||||
if (!this.supportedPlatforms.includes(platform)) {
|
||||
this.logger.logWarn(`[Core] [Packet:Native] 不支持的平台: ${platform}`);
|
||||
return false;
|
||||
}
|
||||
const moehoo_path = path.join(dirname(fileURLToPath(import.meta.url)), './moehoo/MoeHoo.' + platform + '.node');
|
||||
if (!fs.existsSync(moehoo_path)) {
|
||||
this.logger.logWarn(`[Core] [Packet:Native] 缺失运行时文件: ${moehoo_path}`);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
async init(pid: number, recv: string, send: string): Promise<void> {
|
||||
const platform = process.platform + '.' + process.arch;
|
||||
const moehoo_path = path.join(dirname(fileURLToPath(import.meta.url)), './moehoo/MoeHoo.' + platform + '.node');
|
||||
process.dlopen(this.MoeHooExport, moehoo_path, constants.dlopen.RTLD_LAZY);
|
||||
this.MoeHooExport.exports.InitHook?.(send, recv, (type: number, uin: string, cmd: string, seq: number, hex_data: string) => {
|
||||
const trace_id = createHash('md5').update(Buffer.from(hex_data, 'hex')).digest('hex');
|
||||
if (type === 0 && this.cb.get(trace_id + 'recv')) {
|
||||
//此时为send 提取seq
|
||||
this.sendEvent.put(seq, trace_id);
|
||||
}
|
||||
if (type === 1 && this.sendEvent.get(seq)) {
|
||||
//此时为recv 调用callback
|
||||
const trace_id = this.sendEvent.get(seq);
|
||||
const callback = this.cb.get(trace_id + 'recv');
|
||||
// console.log('callback:', callback, trace_id);
|
||||
callback?.({ seq, cmd, hex_data });
|
||||
}
|
||||
|
||||
// const callback = this.cb.get(createHash('md5').update(Buffer.from(hex_data, 'hex')).digest('hex') + (type === 0 ? 'send' : 'recv'));
|
||||
// if (callback) {
|
||||
// callback({ seq, cmd, hex_data });
|
||||
// } else {
|
||||
// this.logger.logError(`Callback not found for hex_data: ${hex_data}`);
|
||||
// }
|
||||
//console.log('type:', type, 'cmd:', cmd, 'trace_id:', trace_id);
|
||||
});
|
||||
this.isAvailable = true;
|
||||
}
|
||||
|
||||
sendCommandImpl(cmd: string, data: string, trace_id: string): void {
|
||||
const trace_id_md5 = createHash('md5').update(trace_id).digest('hex');
|
||||
//console.log('sendCommandImpl:', cmd, data, trace_id_md5);
|
||||
this.MoeHooExport.exports.SendPacket?.(cmd, data, trace_id_md5);
|
||||
this.cb.get(trace_id_md5 + 'send')?.({ seq: 0, cmd, hex_data: '' });
|
||||
}
|
||||
|
||||
connect(cb: () => void): Promise<void> {
|
||||
cb();
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
112
src/core/packet/client/wsClient.ts
Normal file
112
src/core/packet/client/wsClient.ts
Normal file
@@ -0,0 +1,112 @@
|
||||
import { Data, WebSocket } from "ws";
|
||||
import { NapCatCore } from "@/core";
|
||||
import { PacketClient, RecvPacket } from "@/core/packet/client/client";
|
||||
|
||||
export class wsPacketClient extends PacketClient {
|
||||
private websocket: WebSocket | undefined;
|
||||
private reconnectAttempts: number = 0;
|
||||
private readonly maxReconnectAttempts: number = 60; // 现在暂时不可配置
|
||||
private readonly clientUrl: string | null = null;
|
||||
private clientUrlWrap: (url: string) => string = (url: string) => `ws://${url}/ws`;
|
||||
|
||||
constructor(core: NapCatCore) {
|
||||
super(core);
|
||||
this.clientUrl = this.config.packetServer ? this.clientUrlWrap( this.config.packetServer) : null;
|
||||
}
|
||||
|
||||
check(): boolean {
|
||||
if (!this.clientUrl) {
|
||||
this.logger.logWarn(`[Core] [Packet:Server] 未配置服务器地址`);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
connect(cb: () => void): 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.isAvailable = true;
|
||||
this.reconnectAttempts = 0;
|
||||
this.logger.log.bind(this.logger)(`[Core] [Packet:Server] 已连接到 ${this.clientUrl}`);
|
||||
cb();
|
||||
resolve();
|
||||
};
|
||||
|
||||
this.websocket.onerror = (error) => {
|
||||
//this.logger.logError.bind(this.logger)(`WebSocket error: ${error}`);
|
||||
reject(new Error(`${error.message}`));
|
||||
};
|
||||
|
||||
this.websocket.onmessage = (event) => {
|
||||
// const message = JSON.parse(event.data.toString());
|
||||
// console.log("Received message:", message);
|
||||
this.handleMessage(event.data).then().catch();
|
||||
};
|
||||
|
||||
this.websocket.onclose = () => {
|
||||
this.isAvailable = false;
|
||||
//this.logger.logWarn.bind(this.logger)(`[Core] [Packet Server] Disconnected from ${this.clientUrl}`);
|
||||
this.attemptReconnect(cb);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
private attemptReconnect(cb: any): void {
|
||||
try {
|
||||
if (this.reconnectAttempts < this.maxReconnectAttempts) {
|
||||
this.reconnectAttempts++;
|
||||
setTimeout(() => {
|
||||
this.connect(cb).catch((error) => {
|
||||
this.logger.logError.bind(this.logger)(`[Core] [Packet:Server] 尝试重连失败:${error.message}`);
|
||||
});
|
||||
}, 5000 * this.reconnectAttempts);
|
||||
} else {
|
||||
this.logger.logError.bind(this.logger)(`[Core] [Packet:Server] ${this.clientUrl} 已达到最大重连次数!`);
|
||||
}
|
||||
} catch (error: any) {
|
||||
this.logger.logError.bind(this.logger)(`[Core] [Packet:Server] 重连时出错: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
async init(pid: number, recv: string, send: string): Promise<void> {
|
||||
if (!this.isAvailable || !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));
|
||||
}
|
||||
|
||||
sendCommandImpl(cmd: string, data: string, trace_id: string) : void {
|
||||
const commandMessage = {
|
||||
action: 'send',
|
||||
cmd: cmd,
|
||||
data: data,
|
||||
trace_id: trace_id
|
||||
};
|
||||
this.websocket!.send(JSON.stringify(commandMessage));
|
||||
}
|
||||
|
||||
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}`);
|
||||
}
|
||||
}
|
||||
}
|
16
src/core/packet/entities/aiChat.ts
Normal file
16
src/core/packet/entities/aiChat.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
export enum AIVoiceChatType {
|
||||
Unknown = 0,
|
||||
Sound = 1,
|
||||
Sing = 2
|
||||
}
|
||||
|
||||
export interface AIVoiceItem {
|
||||
voiceId: string;
|
||||
voiceDisplayName: string;
|
||||
voiceExampleUrl: string;
|
||||
}
|
||||
|
||||
export interface AIVoiceItemList {
|
||||
category: string;
|
||||
voices: AIVoiceItem[];
|
||||
}
|
79
src/core/packet/entities/miniApp.ts
Normal file
79
src/core/packet/entities/miniApp.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
export interface MiniAppReqCustomParams {
|
||||
title: string;
|
||||
desc: string;
|
||||
picUrl: string;
|
||||
jumpUrl: string;
|
||||
}
|
||||
|
||||
export interface MiniAppReqTemplateParams {
|
||||
sdkId: string;
|
||||
appId: string;
|
||||
scene: number;
|
||||
iconUrl: string;
|
||||
templateType: number;
|
||||
businessType: number;
|
||||
verType: number;
|
||||
shareType: number;
|
||||
versionId: string;
|
||||
withShareTicket: number;
|
||||
}
|
||||
|
||||
export interface MiniAppReqParams extends MiniAppReqCustomParams, MiniAppReqTemplateParams {}
|
||||
|
||||
export interface MiniAppData {
|
||||
ver: string;
|
||||
prompt: string;
|
||||
config: Config;
|
||||
app: string;
|
||||
view: string;
|
||||
meta: MetaData;
|
||||
miniappShareOrigin: number;
|
||||
miniappOpenRefer: string;
|
||||
}
|
||||
|
||||
export interface MiniAppRawData {
|
||||
appName: string;
|
||||
appView: string;
|
||||
ver: string;
|
||||
desc: string;
|
||||
prompt: string;
|
||||
metaData: MetaData;
|
||||
config: Config;
|
||||
}
|
||||
|
||||
interface Config {
|
||||
type: string;
|
||||
width: number;
|
||||
height: number;
|
||||
forward: number;
|
||||
autoSize: number;
|
||||
ctime: number;
|
||||
token: string;
|
||||
}
|
||||
|
||||
interface Host {
|
||||
uin: number;
|
||||
nick: string;
|
||||
}
|
||||
|
||||
interface Detail {
|
||||
appid: string;
|
||||
appType: number;
|
||||
title: string;
|
||||
desc: string;
|
||||
icon: string;
|
||||
preview: string;
|
||||
url: string;
|
||||
scene: number;
|
||||
host: Host;
|
||||
shareTemplateId: string;
|
||||
shareTemplateData: Record<string, unknown>;
|
||||
showLittleTail: string;
|
||||
gamePoints: string;
|
||||
gamePointsUrl: string;
|
||||
shareOrigin: number;
|
||||
}
|
||||
|
||||
interface MetaData {
|
||||
detail_1: Detail;
|
||||
}
|
94
src/core/packet/helper/miniAppHelper.ts
Normal file
94
src/core/packet/helper/miniAppHelper.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
import {
|
||||
MiniAppData,
|
||||
MiniAppReqParams,
|
||||
MiniAppRawData,
|
||||
MiniAppReqCustomParams,
|
||||
MiniAppReqTemplateParams
|
||||
} from "@/core/packet/entities/miniApp";
|
||||
|
||||
type MiniAppTemplateNameList = "bili" | "weibo";
|
||||
|
||||
export abstract class MiniAppInfo {
|
||||
static sdkId: string = "V1_PC_MINISDK_99.99.99_1_APP_A";
|
||||
template: MiniAppReqTemplateParams;
|
||||
|
||||
private static appMap = new Map<MiniAppTemplateNameList, MiniAppInfo>();
|
||||
|
||||
protected constructor(template: MiniAppReqTemplateParams) {
|
||||
this.template = template;
|
||||
}
|
||||
|
||||
static get(name: MiniAppTemplateNameList): MiniAppInfo | undefined {
|
||||
return this.appMap.get(name);
|
||||
}
|
||||
|
||||
static Bili = new class extends MiniAppInfo {
|
||||
constructor() {
|
||||
super({
|
||||
sdkId: MiniAppInfo.sdkId,
|
||||
appId: "1109937557",
|
||||
scene: 1,
|
||||
templateType: 1,
|
||||
businessType: 0,
|
||||
verType: 3,
|
||||
shareType: 0,
|
||||
versionId: "cfc5f7b05b44b5956502edaecf9d2240",
|
||||
withShareTicket: 0,
|
||||
iconUrl: "https://miniapp.gtimg.cn/public/appicon/51f90239b78a2e4994c11215f4c4ba15_200.jpg"
|
||||
});
|
||||
MiniAppInfo.appMap.set("bili", this);
|
||||
}
|
||||
};
|
||||
|
||||
static WeiBo = new class extends MiniAppInfo {
|
||||
constructor() {
|
||||
super({
|
||||
sdkId: MiniAppInfo.sdkId,
|
||||
appId: "1109224783",
|
||||
scene: 1,
|
||||
templateType: 1,
|
||||
businessType: 0,
|
||||
verType: 3,
|
||||
shareType: 0,
|
||||
versionId: "e482a3cc4e574d9b772e96ba6eec9ba2",
|
||||
withShareTicket: 0,
|
||||
iconUrl: "https://miniapp.gtimg.cn/public/appicon/35bbb44dc68e65194cfacfb206b8f1f7_200.jpg"
|
||||
});
|
||||
MiniAppInfo.appMap.set("weibo", this);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export class MiniAppInfoHelper {
|
||||
static generateReq(custom: MiniAppReqCustomParams, template: MiniAppReqTemplateParams): MiniAppReqParams {
|
||||
return {
|
||||
...custom,
|
||||
...template
|
||||
};
|
||||
}
|
||||
|
||||
static RawToSend(rawData: MiniAppRawData): MiniAppData {
|
||||
return {
|
||||
ver: rawData.ver,
|
||||
prompt: rawData.prompt,
|
||||
config: rawData.config,
|
||||
app: rawData.appName,
|
||||
view: rawData.appView,
|
||||
meta: rawData.metaData,
|
||||
miniappShareOrigin: 3,
|
||||
miniappOpenRefer: "10002",
|
||||
};
|
||||
}
|
||||
|
||||
static SendToRaw(data: MiniAppData): MiniAppRawData {
|
||||
return {
|
||||
appName: data.app,
|
||||
appView: data.view,
|
||||
ver: data.ver,
|
||||
desc: data.meta.detail_1.desc,
|
||||
prompt: data.prompt,
|
||||
metaData: data.meta,
|
||||
config: data.config,
|
||||
};
|
||||
}
|
||||
}
|
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 = 1200): 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)];
|
||||
}
|
||||
}
|
570
src/core/packet/highway/session.ts
Normal file
570
src/core/packet/highway/session.ts
Normal file
@@ -0,0 +1,570 @@
|
||||
import * as fs from "node:fs";
|
||||
import { ChatType, Peer } from "@/core";
|
||||
import { LogWrapper } from "@/common/log";
|
||||
import { PacketPacker } from "@/core/packet/packer";
|
||||
import { NapProtoMsg } from "@napneko/nap-proto-core";
|
||||
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 {
|
||||
PacketMsgFileElement,
|
||||
PacketMsgPicElement,
|
||||
PacketMsgPttElement,
|
||||
PacketMsgVideoElement
|
||||
} from "@/core/packet/message/element";
|
||||
import { FileUploadExt, NTV2RichMediaHighwayExt } from "@/core/packet/proto/highway/highway";
|
||||
import { int32ip2str, oidbIpv4s2HighwayIpv4s } from "@/core/packet/highway/utils";
|
||||
import { calculateSha1, calculateSha1StreamBytes, computeMd5AndLengthWithLimit } from "@/core/packet/utils/crypto/hash";
|
||||
import { OidbSvcTrpcTcp0x6D6Response } from "@/core/packet/proto/oidb/Oidb.0x6D6";
|
||||
import { OidbSvcTrpcTcp0XE37_800Response, OidbSvcTrpcTcp0XE37Response } from "@/core/packet/proto/oidb/Oidb.0XE37_800";
|
||||
import { PacketClient } from "@/core/packet/client/client";
|
||||
|
||||
export const BlockSize = 1024 * 1024;
|
||||
|
||||
interface HighwayServerAddr {
|
||||
ip: string
|
||||
port: number
|
||||
}
|
||||
|
||||
export interface PacketHighwaySig {
|
||||
uin: string;
|
||||
uid: 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,
|
||||
uid: this.packetClient.napCatCore.selfInfo.uid,
|
||||
sigSession: null,
|
||||
sessionKey: null,
|
||||
serverAddr: [],
|
||||
};
|
||||
this.packer = packer;
|
||||
this.packetHighwayClient = new PacketHighwayClient(this.sig, this.logger);
|
||||
}
|
||||
|
||||
private async checkAvailable() {
|
||||
if (!this.packetClient.available) {
|
||||
throw new Error('packetBackend不可用,请参照文档 https://napneko.github.io/config/advanced 和启动日志检查packetBackend状态或进行配置!');
|
||||
}
|
||||
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(+peer.peerUid, img);
|
||||
} else if (peer.chatType === ChatType.KCHATTYPEC2C) {
|
||||
await this.uploadC2CImageReq(peer.peerUid, img);
|
||||
} else {
|
||||
throw new Error(`[Highway] unsupported chatType: ${peer.chatType}`);
|
||||
}
|
||||
}
|
||||
|
||||
async uploadVideo(peer: Peer, video: PacketMsgVideoElement): Promise<void> {
|
||||
await this.checkAvailable();
|
||||
if (+(video.fileSize ?? 0) > 1024 * 1024 * 100) {
|
||||
throw new Error(`[Highway] 视频文件过大: ${(+(video.fileSize ?? 0) / (1024 * 1024)).toFixed(2)} MB > 100 MB,请使用文件上传!`);
|
||||
}
|
||||
if (peer.chatType === ChatType.KCHATTYPEGROUP) {
|
||||
await this.uploadGroupVideoReq(+peer.peerUid, video);
|
||||
} else if (peer.chatType === ChatType.KCHATTYPEC2C) {
|
||||
await this.uploadC2CVideoReq(peer.peerUid, video);
|
||||
} else {
|
||||
throw new Error(`[Highway] unsupported chatType: ${peer.chatType}`);
|
||||
}
|
||||
}
|
||||
|
||||
async uploadPtt(peer: Peer, ptt: PacketMsgPttElement): Promise<void> {
|
||||
await this.checkAvailable();
|
||||
if (peer.chatType === ChatType.KCHATTYPEGROUP) {
|
||||
await this.uploadGroupPttReq(+peer.peerUid, ptt);
|
||||
} else if (peer.chatType === ChatType.KCHATTYPEC2C) {
|
||||
await this.uploadC2CPttReq(peer.peerUid, ptt);
|
||||
} else {
|
||||
throw new Error(`[Highway] unsupported chatType: ${peer.chatType}`);
|
||||
}
|
||||
}
|
||||
|
||||
async uploadFile(peer: Peer, file: PacketMsgFileElement): Promise<void> {
|
||||
await this.checkAvailable();
|
||||
if (peer.chatType === ChatType.KCHATTYPEGROUP) {
|
||||
await this.uploadGroupFileReq(+peer.peerUid, file);
|
||||
} else if (peer.chatType === ChatType.KCHATTYPEC2C) {
|
||||
await this.uploadC2CFileReq(peer.peerUid, file);
|
||||
} else {
|
||||
throw new Error(`[Highway] unsupported chatType: ${peer.chatType}`);
|
||||
}
|
||||
}
|
||||
|
||||
private async uploadGroupImageReq(groupUin: number, img: PacketMsgPicElement): Promise<void> {
|
||||
img.sha1 = Buffer.from(await calculateSha1(img.path)).toString('hex');
|
||||
const preReq = await this.packer.packUploadGroupImgReq(groupUin, img);
|
||||
const preRespRaw = await this.packetClient.sendOidbPacket(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] uploadGroupImageReq 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] uploadGroupImageReq 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> {
|
||||
img.sha1 = Buffer.from(await calculateSha1(img.path)).toString('hex');
|
||||
const preReq = await this.packer.packUploadC2CImgReq(peerUid, img);
|
||||
const preRespRaw = await this.packetClient.sendOidbPacket(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] uploadC2CImageReq 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
|
||||
);
|
||||
} else {
|
||||
this.logger.logDebug(`[Highway] uploadC2CImageReq get upload invalid ukey ${ukey}, don't need upload!`);
|
||||
}
|
||||
img.msgInfo = preRespData.upload.msgInfo;
|
||||
}
|
||||
|
||||
private async uploadGroupVideoReq(groupUin: number, video: PacketMsgVideoElement): Promise<void> {
|
||||
if (!video.filePath || !video.thumbPath) throw new Error("video.filePath or video.thumbPath is empty");
|
||||
video.fileSha1 = Buffer.from(await calculateSha1(video.filePath)).toString('hex');
|
||||
video.thumbSha1 = Buffer.from(await calculateSha1(video.thumbPath)).toString('hex');
|
||||
const preReq = await this.packer.packUploadGroupVideoReq(groupUin, video);
|
||||
const preRespRaw = await this.packetClient.sendOidbPacket(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] uploadGroupVideoReq get upload video ukey: ${ukey}, need upload!`);
|
||||
const index = preRespData.upload.msgInfo.msgInfoBody[0].index;
|
||||
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: await calculateSha1StreamBytes(video.filePath!)
|
||||
}
|
||||
});
|
||||
await this.packetHighwayClient.upload(
|
||||
1005,
|
||||
fs.createReadStream(video.filePath!, { highWaterMark: BlockSize }),
|
||||
+video.fileSize!,
|
||||
md5,
|
||||
extend
|
||||
);
|
||||
} else {
|
||||
this.logger.logDebug(`[Highway] uploadGroupVideoReq get upload invalid ukey ${ukey}, don't need upload!`);
|
||||
}
|
||||
const subFile = preRespData.upload.subFileInfos[0];
|
||||
if (subFile.uKey && subFile.uKey != "") {
|
||||
this.logger.logDebug(`[Highway] uploadGroupVideoReq get upload video thumb ukey: ${subFile.uKey}, need upload!`);
|
||||
const index = preRespData.upload.msgInfo.msgInfoBody[1].index;
|
||||
const md5 = Buffer.from(index.info.fileHash, 'hex');
|
||||
const sha1 = Buffer.from(index.info.fileSha1, 'hex');
|
||||
const extend = new NapProtoMsg(NTV2RichMediaHighwayExt).encode({
|
||||
fileUuid: index.fileUuid,
|
||||
uKey: subFile.uKey,
|
||||
network: {
|
||||
ipv4S: oidbIpv4s2HighwayIpv4s(subFile.ipv4S)
|
||||
},
|
||||
msgInfoBody: preRespData.upload.msgInfo.msgInfoBody,
|
||||
blockSize: BlockSize,
|
||||
hash: {
|
||||
fileSha1: [sha1]
|
||||
}
|
||||
});
|
||||
await this.packetHighwayClient.upload(
|
||||
1006,
|
||||
fs.createReadStream(video.thumbPath!, { highWaterMark: BlockSize }),
|
||||
+video.thumbSize!,
|
||||
md5,
|
||||
extend
|
||||
);
|
||||
} else {
|
||||
this.logger.logDebug(`[Highway] uploadGroupVideoReq get upload invalid thumb ukey ${subFile.uKey}, don't need upload!`);
|
||||
}
|
||||
video.msgInfo = preRespData.upload.msgInfo;
|
||||
}
|
||||
|
||||
private async uploadC2CVideoReq(peerUid: string, video: PacketMsgVideoElement): Promise<void> {
|
||||
if (!video.filePath || !video.thumbPath) throw new Error("video.filePath or video.thumbPath is empty");
|
||||
video.fileSha1 = Buffer.from(await calculateSha1(video.filePath)).toString('hex');
|
||||
video.thumbSha1 = Buffer.from(await calculateSha1(video.thumbPath)).toString('hex');
|
||||
const preReq = await this.packer.packUploadC2CVideoReq(peerUid, video);
|
||||
const preRespRaw = await this.packetClient.sendOidbPacket(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] uploadC2CVideoReq get upload video ukey: ${ukey}, need upload!`);
|
||||
const index = preRespData.upload.msgInfo.msgInfoBody[0].index;
|
||||
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: await calculateSha1StreamBytes(video.filePath!)
|
||||
}
|
||||
});
|
||||
await this.packetHighwayClient.upload(
|
||||
1001,
|
||||
fs.createReadStream(video.filePath!, { highWaterMark: BlockSize }),
|
||||
+video.fileSize!,
|
||||
md5,
|
||||
extend
|
||||
);
|
||||
} else {
|
||||
this.logger.logDebug(`[Highway] uploadC2CVideoReq get upload invalid ukey ${ukey}, don't need upload!`);
|
||||
}
|
||||
const subFile = preRespData.upload.subFileInfos[0];
|
||||
if (subFile.uKey && subFile.uKey != "") {
|
||||
this.logger.logDebug(`[Highway] uploadC2CVideoReq get upload video thumb ukey: ${subFile.uKey}, need upload!`);
|
||||
const index = preRespData.upload.msgInfo.msgInfoBody[1].index;
|
||||
const md5 = Buffer.from(index.info.fileHash, 'hex');
|
||||
const sha1 = Buffer.from(index.info.fileSha1, 'hex');
|
||||
const extend = new NapProtoMsg(NTV2RichMediaHighwayExt).encode({
|
||||
fileUuid: index.fileUuid,
|
||||
uKey: subFile.uKey,
|
||||
network: {
|
||||
ipv4S: oidbIpv4s2HighwayIpv4s(subFile.ipv4S)
|
||||
},
|
||||
msgInfoBody: preRespData.upload.msgInfo.msgInfoBody,
|
||||
blockSize: BlockSize,
|
||||
hash: {
|
||||
fileSha1: [sha1]
|
||||
}
|
||||
});
|
||||
await this.packetHighwayClient.upload(
|
||||
1002,
|
||||
fs.createReadStream(video.thumbPath!, { highWaterMark: BlockSize }),
|
||||
+video.thumbSize!,
|
||||
md5,
|
||||
extend
|
||||
);
|
||||
} else {
|
||||
this.logger.logDebug(`[Highway] uploadC2CVideoReq get upload invalid thumb ukey ${subFile.uKey}, don't need upload!`);
|
||||
}
|
||||
video.msgInfo = preRespData.upload.msgInfo;
|
||||
}
|
||||
|
||||
private async uploadGroupPttReq(groupUin: number, ptt: PacketMsgPttElement): Promise<void> {
|
||||
ptt.fileSha1 = Buffer.from(await calculateSha1(ptt.filePath)).toString('hex');
|
||||
const preReq = await this.packer.packUploadGroupPttReq(groupUin, ptt);
|
||||
const preRespRaw = await this.packetClient.sendOidbPacket(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] uploadGroupPttReq get upload ptt ukey: ${ukey}, need upload!`);
|
||||
const index = preRespData.upload.msgInfo.msgInfoBody[0].index;
|
||||
const md5 = Buffer.from(index.info.fileHash, 'hex');
|
||||
const sha1 = Buffer.from(index.info.fileSha1, '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(
|
||||
1008,
|
||||
fs.createReadStream(ptt.filePath, { highWaterMark: BlockSize }),
|
||||
ptt.fileSize,
|
||||
md5,
|
||||
extend
|
||||
);
|
||||
} else {
|
||||
this.logger.logDebug(`[Highway] uploadGroupPttReq get upload invalid ukey ${ukey}, don't need upload!`);
|
||||
}
|
||||
ptt.msgInfo = preRespData.upload.msgInfo;
|
||||
}
|
||||
|
||||
private async uploadC2CPttReq(peerUid: string, ptt: PacketMsgPttElement): Promise<void> {
|
||||
ptt.fileSha1 = Buffer.from(await calculateSha1(ptt.filePath)).toString('hex');
|
||||
const preReq = await this.packer.packUploadC2CPttReq(peerUid, ptt);
|
||||
const preRespRaw = await this.packetClient.sendOidbPacket(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] uploadC2CPttReq get upload ptt ukey: ${ukey}, need upload!`);
|
||||
const index = preRespData.upload.msgInfo.msgInfoBody[0].index;
|
||||
const md5 = Buffer.from(index.info.fileHash, 'hex');
|
||||
const sha1 = Buffer.from(index.info.fileSha1, '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(
|
||||
1007,
|
||||
fs.createReadStream(ptt.filePath, { highWaterMark: BlockSize }),
|
||||
ptt.fileSize,
|
||||
md5,
|
||||
extend
|
||||
);
|
||||
} else {
|
||||
this.logger.logDebug(`[Highway] uploadC2CPttReq get upload invalid ukey ${ukey}, don't need upload!`);
|
||||
}
|
||||
ptt.msgInfo = preRespData.upload.msgInfo;
|
||||
}
|
||||
|
||||
private async uploadGroupFileReq(groupUin: number, file: PacketMsgFileElement): Promise<void> {
|
||||
file.isGroupFile = true;
|
||||
file.fileMd5 = await computeMd5AndLengthWithLimit(file.filePath);
|
||||
file.fileSha1 = await calculateSha1(file.filePath);
|
||||
const preReq = await this.packer.packUploadGroupFileReq(groupUin, file);
|
||||
const preRespRaw = await this.packetClient.sendOidbPacket(preReq, true);
|
||||
const preResp = new NapProtoMsg(OidbSvcTrpcTcpBaseRsp).decode(
|
||||
Buffer.from(preRespRaw.hex_data, 'hex')
|
||||
);
|
||||
const preRespData = new NapProtoMsg(OidbSvcTrpcTcp0x6D6Response).decode(preResp.body);
|
||||
if (!preRespData?.upload?.boolFileExist) {
|
||||
this.logger.logDebug(`[Highway] uploadGroupFileReq file not exist, need upload!`);
|
||||
const ext = new NapProtoMsg(FileUploadExt).encode({
|
||||
unknown1: 100,
|
||||
unknown2: 1,
|
||||
entry: {
|
||||
busiBuff: {
|
||||
senderUin: BigInt(this.sig.uin),
|
||||
receiverUin: BigInt(groupUin),
|
||||
groupCode: BigInt(groupUin),
|
||||
},
|
||||
fileEntry: {
|
||||
fileSize: BigInt(file.fileSize),
|
||||
md5: file.fileMd5,
|
||||
md5S2: file.fileMd5,
|
||||
checkKey: preRespData.upload.checkKey,
|
||||
fileId: preRespData.upload.fileId,
|
||||
uploadKey: preRespData.upload.fileKey,
|
||||
},
|
||||
clientInfo: {
|
||||
clientType: 3,
|
||||
appId: "100",
|
||||
terminalType: 3,
|
||||
clientVer: "1.1.1",
|
||||
unknown: 4
|
||||
},
|
||||
fileNameInfo: {
|
||||
fileName: file.fileName
|
||||
},
|
||||
host: {
|
||||
hosts: [
|
||||
{
|
||||
url: {
|
||||
host: preRespData.upload.uploadIp,
|
||||
unknown: 1,
|
||||
},
|
||||
port: preRespData.upload.uploadPort,
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
unknown200: 0,
|
||||
});
|
||||
await this.packetHighwayClient.upload(
|
||||
71,
|
||||
fs.createReadStream(file.filePath, { highWaterMark: BlockSize }),
|
||||
file.fileSize,
|
||||
file.fileMd5,
|
||||
ext
|
||||
);
|
||||
} else {
|
||||
this.logger.logDebug(`[Highway] uploadGroupFileReq file exist, don't need upload!`);
|
||||
}
|
||||
file.fileUuid = preRespData.upload.fileId;
|
||||
}
|
||||
|
||||
private async uploadC2CFileReq(peerUid: string, file: PacketMsgFileElement): Promise<void> {
|
||||
file.isGroupFile = false;
|
||||
file.fileMd5 = await computeMd5AndLengthWithLimit(file.filePath);
|
||||
file.fileSha1 = await calculateSha1(file.filePath);
|
||||
const preReq = await this.packer.packUploadC2CFileReq(this.sig.uid, peerUid, file);
|
||||
const preRespRaw = await this.packetClient.sendOidbPacket( preReq, true);
|
||||
const preResp = new NapProtoMsg(OidbSvcTrpcTcpBaseRsp).decode(
|
||||
Buffer.from(preRespRaw.hex_data, 'hex')
|
||||
);
|
||||
const preRespData = new NapProtoMsg(OidbSvcTrpcTcp0XE37Response).decode(preResp.body);
|
||||
if (!preRespData.upload?.boolFileExist) {
|
||||
this.logger.logDebug(`[Highway] uploadC2CFileReq file not exist, need upload!`);
|
||||
const ext = new NapProtoMsg(FileUploadExt).encode({
|
||||
unknown1: 100,
|
||||
unknown2: 1,
|
||||
entry: {
|
||||
busiBuff: {
|
||||
senderUin: BigInt(this.sig.uin),
|
||||
},
|
||||
fileEntry: {
|
||||
fileSize: BigInt(file.fileSize),
|
||||
md5: file.fileMd5,
|
||||
md5S2: file.fileMd5,
|
||||
checkKey: file.fileSha1,
|
||||
fileId: preRespData.upload?.uuid,
|
||||
uploadKey: preRespData.upload?.mediaPlatformUploadKey,
|
||||
},
|
||||
clientInfo: {
|
||||
clientType: 3,
|
||||
appId: "100",
|
||||
terminalType: 3,
|
||||
clientVer: "1.1.1",
|
||||
unknown: 4
|
||||
},
|
||||
fileNameInfo: {
|
||||
fileName: file.fileName
|
||||
},
|
||||
host: {
|
||||
hosts: [
|
||||
{
|
||||
url: {
|
||||
host: preRespData.upload?.uploadIp,
|
||||
unknown: 1,
|
||||
},
|
||||
port: preRespData.upload?.uploadPort,
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
unknown200: 1,
|
||||
unknown3: 0
|
||||
});
|
||||
await this.packetHighwayClient.upload(
|
||||
95,
|
||||
fs.createReadStream(file.filePath, { highWaterMark: BlockSize }),
|
||||
file.fileSize,
|
||||
file.fileMd5,
|
||||
ext
|
||||
);
|
||||
}
|
||||
file.fileUuid = preRespData.upload?.uuid;
|
||||
file.fileHash = preRespData.upload?.fileAddon;
|
||||
const FetchExistFileReq = this.packer.packOfflineFileDownloadReq(file.fileUuid!, file.fileHash!, this.sig.uid, peerUid);
|
||||
const resp = await this.packetClient.sendOidbPacket(FetchExistFileReq, true);
|
||||
const oidb_resp = new NapProtoMsg(OidbSvcTrpcTcpBaseRsp).decode(Buffer.from(resp.hex_data, 'hex'));
|
||||
file._e37_800_rsp = new NapProtoMsg(OidbSvcTrpcTcp0XE37_800Response).decode(oidb_resp.body);
|
||||
file._private_send_uid = this.sig.uid;
|
||||
file._private_recv_uid = peerUid;
|
||||
}
|
||||
}
|
215
src/core/packet/highway/uploader.ts
Normal file
215
src/core/packet/highway/uploader.ts
Normal file
@@ -0,0 +1,215 @@
|
||||
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 "@napneko/nap-proto-core";
|
||||
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;
|
||||
}
|
||||
|
||||
private encryptTransExt(key: Uint8Array) {
|
||||
if (!this.trans.encrypt) return;
|
||||
this.trans.ext = tea.encrypt(Buffer.from(this.trans.ext), Buffer.from(key));
|
||||
}
|
||||
|
||||
protected timeout(): Promise<void> {
|
||||
return new Promise<void>((_, reject) => {
|
||||
setTimeout(() => {
|
||||
reject(new Error(`[Highway] timeout after ${this.trans.timeout}s`));
|
||||
}, (this.trans.timeout ?? Infinity) * 1000
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
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 controller = new AbortController();
|
||||
const { signal } = controller;
|
||||
const upload = new Promise<void>((resolve, reject) => {
|
||||
const highwayTransForm = new HighwayTcpUploaderTransform(this);
|
||||
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) {
|
||||
socket.end();
|
||||
reject(new Error(`[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) => {
|
||||
if (signal.aborted) {
|
||||
socket.end();
|
||||
reject(new Error('Upload aborted due to timeout'));
|
||||
}
|
||||
const [head, _] = Frame.unpack(chunk);
|
||||
handleRspHeader(head);
|
||||
});
|
||||
socket.on('close', () => {
|
||||
this.logger.logDebug('[Highway] tcpUpload socket closed.');
|
||||
resolve();
|
||||
});
|
||||
socket.on('error', (err) => {
|
||||
socket.end();
|
||||
reject(new Error(`[Highway] tcpUpload socket.on error: ${err}`));
|
||||
});
|
||||
this.trans.data.on('error', (err) => {
|
||||
socket.end();
|
||||
reject(new Error(`[Highway] tcpUpload readable error: ${err}`));
|
||||
});
|
||||
});
|
||||
const timeout = this.timeout().catch((err) => {
|
||||
controller.abort();
|
||||
throw new Error(err.message);
|
||||
});
|
||||
await Promise.race([upload, timeout]);
|
||||
}
|
||||
}
|
||||
|
||||
export class HighwayHttpUploader extends HighwayUploader {
|
||||
async upload(): Promise<void> {
|
||||
const controller = new AbortController();
|
||||
const { signal } = controller;
|
||||
const upload = (async () => {
|
||||
let offset = 0;
|
||||
for await (const chunk of this.trans.data) {
|
||||
if (signal.aborted) {
|
||||
throw new Error('Upload aborted due to timeout');
|
||||
}
|
||||
const block = chunk as Buffer;
|
||||
try {
|
||||
await this.uploadBlock(block, offset);
|
||||
} catch (err) {
|
||||
throw new Error(`[Highway] httpUpload Error uploading block at offset ${offset}: ${err}`);
|
||||
}
|
||||
offset += block.length;
|
||||
}
|
||||
})();
|
||||
const timeout = this.timeout().catch((err) => {
|
||||
controller.abort();
|
||||
throw new Error(err.message);
|
||||
});
|
||||
await Promise.race([upload, timeout]);
|
||||
}
|
||||
|
||||
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) throw new Error(`[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) => {
|
||||
const data: Buffer[] = [];
|
||||
res.on('data', (chunk) => {
|
||||
data.push(chunk);
|
||||
});
|
||||
res.on('end', () => {
|
||||
resolve(Buffer.concat(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 "@napneko/nap-proto-core";
|
||||
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>;
|
||||
});
|
||||
};
|
74
src/core/packet/message/builder.ts
Normal file
74
src/core/packet/message/builder.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
import * as crypto from "crypto";
|
||||
import { PushMsgBody } from "@/core/packet/proto/message/message";
|
||||
import { NapProtoEncodeStructType } from "@napneko/nap-proto-core";
|
||||
import { LogWrapper } from "@/common/log";
|
||||
import { PacketMsg, PacketSendMsgElement } from "@/core/packet/message/message";
|
||||
import { IPacketMsgElement, PacketMsgTextElement } from "@/core/packet/message/element";
|
||||
import { SendTextElement } from "@/core";
|
||||
|
||||
export class PacketMsgBuilder {
|
||||
private logger: LogWrapper;
|
||||
|
||||
constructor(logger: LogWrapper) {
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
protected static failBackText = new PacketMsgTextElement(
|
||||
{
|
||||
textElement: { content: "[该消息类型暂不支持查看]" }!
|
||||
} as SendTextElement
|
||||
);
|
||||
|
||||
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 msgContent = node.msg.reduceRight((acc: undefined | Uint8Array, msg: IPacketMsgElement<PacketSendMsgElement>) => {
|
||||
return acc !== undefined ? acc : msg.buildContent();
|
||||
}, undefined);
|
||||
const msgElement = node.msg.flatMap(msg => msg.buildElement() ?? []);
|
||||
if (!msgContent && !msgElement.length) {
|
||||
this.logger.logWarn(`[PacketMsgBuilder] buildFakeMsg: 空的msgContent和msgElement!`);
|
||||
msgElement.push(PacketMsgBuilder.failBackText.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: +node.time.toString().substring(0, 10),
|
||||
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
|
||||
},
|
||||
msgContent: msgContent,
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
163
src/core/packet/message/converter.ts
Normal file
163
src/core/packet/message/converter.ts
Normal file
@@ -0,0 +1,163 @@
|
||||
import {
|
||||
Peer,
|
||||
ChatType,
|
||||
ElementType,
|
||||
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/message/element";
|
||||
import { PacketMsg, PacketSendMsgElement } from "@/core/packet/message/message";
|
||||
import { LogWrapper } from "@/common/log";
|
||||
|
||||
const SupportedElementTypes = [
|
||||
ElementType.TEXT,
|
||||
ElementType.PIC,
|
||||
ElementType.REPLY,
|
||||
ElementType.FACE,
|
||||
ElementType.MFACE,
|
||||
ElementType.VIDEO,
|
||||
ElementType.FILE,
|
||||
ElementType.PTT,
|
||||
ElementType.ARK,
|
||||
ElementType.MARKDOWN,
|
||||
ElementType.STRUCTLONGMSG
|
||||
];
|
||||
|
||||
type SendMessageTypeElementMap = {
|
||||
[ElementType.TEXT]: SendTextElement,
|
||||
[ElementType.PIC]: SendPicElement,
|
||||
[ElementType.FILE]: SendFileElement,
|
||||
[ElementType.PTT]: SendPttElement,
|
||||
[ElementType.VIDEO]: SendVideoElement,
|
||||
[ElementType.FACE]: SendFaceElement,
|
||||
[ElementType.REPLY]: SendReplyElement,
|
||||
[ElementType.ARK]: SendArkElement,
|
||||
[ElementType.MFACE]: SendMarketFaceElement,
|
||||
[ElementType.STRUCTLONGMSG]: SendStructLongMsgElement,
|
||||
[ElementType.MARKDOWN]: SendMarkdownElement,
|
||||
};
|
||||
|
||||
type ElementToPacketMsgConverters = {
|
||||
[K in keyof SendMessageTypeElementMap]: (
|
||||
sendElement: MessageElement
|
||||
) => IPacketMsgElement<SendMessageTypeElementMap[K]>;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
private isValidElementType(type: ElementType): type is keyof ElementToPacketMsgConverters {
|
||||
return SupportedElementTypes.includes(type);
|
||||
}
|
||||
|
||||
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) => {
|
||||
if (!this.isValidElementType(element.elementType)) return null;
|
||||
return this.rawToPacketMsgConverters[element.elementType](element as MessageElement);
|
||||
}).filter((e) => e !== null)
|
||||
};
|
||||
}
|
||||
|
||||
rawMsgToPacketMsg(msg: RawMessage, ctxPeer: Peer): PacketMsg {
|
||||
return {
|
||||
seq: +msg.msgSeq,
|
||||
groupId: ctxPeer.chatType === ChatType.KCHATTYPEGROUP ? +msg.peerUid : undefined,
|
||||
senderUid: msg.senderUid,
|
||||
senderUin: +msg.senderUin,
|
||||
senderName: msg.sendMemberName && msg.sendMemberName !== ''
|
||||
? msg.sendMemberName
|
||||
: msg.sendNickName && msg.sendNickName !== ''
|
||||
? msg.sendNickName
|
||||
: "QQ用户",
|
||||
time: +msg.msgTime,
|
||||
msg: msg.elements.map((element) => {
|
||||
if (!this.isValidElementType(element.elementType)) return null;
|
||||
return this.rawToPacketMsgConverters[element.elementType](element);
|
||||
}).filter((e) => e !== null)
|
||||
};
|
||||
}
|
||||
|
||||
private rawToPacketMsgConverters: ElementToPacketMsgConverters = {
|
||||
[ElementType.TEXT]: (element) => {
|
||||
if (element.textElement?.atType) {
|
||||
return new PacketMsgAtElement(element as SendTextElement);
|
||||
}
|
||||
return new PacketMsgTextElement(element as SendTextElement);
|
||||
},
|
||||
[ElementType.PIC]: (element) => {
|
||||
return new PacketMsgPicElement(element as SendPicElement);
|
||||
},
|
||||
[ElementType.REPLY]: (element) => {
|
||||
return new PacketMsgReplyElement(element as SendReplyElement);
|
||||
},
|
||||
[ElementType.FACE]: (element) => {
|
||||
return new PacketMsgFaceElement(element as SendFaceElement);
|
||||
},
|
||||
[ElementType.MFACE]: (element) => {
|
||||
return new PacketMsgMarkFaceElement(element as SendMarketFaceElement);
|
||||
},
|
||||
[ElementType.VIDEO]: (element) => {
|
||||
return new PacketMsgVideoElement(element as SendVideoElement);
|
||||
},
|
||||
[ElementType.FILE]: (element) => {
|
||||
return new PacketMsgFileElement(element as SendFileElement);
|
||||
},
|
||||
[ElementType.PTT]: (element) => {
|
||||
return new PacketMsgPttElement(element as SendPttElement);
|
||||
},
|
||||
[ElementType.ARK]: (element) => {
|
||||
return new PacketMsgLightAppElement(element as SendArkElement);
|
||||
},
|
||||
[ElementType.MARKDOWN]: (element) => {
|
||||
return new PacketMsgMarkDownElement(element as SendMarkdownElement);
|
||||
},
|
||||
// TODO: check this logic, move it in arkElement?
|
||||
[ElementType.STRUCTLONGMSG]: (element) => {
|
||||
return new PacketMultiMsgElement(element as SendStructLongMsgElement);
|
||||
}
|
||||
};
|
||||
}
|
534
src/core/packet/message/element.ts
Normal file
534
src/core/packet/message/element.ts
Normal file
@@ -0,0 +1,534 @@
|
||||
import * as zlib from "node:zlib";
|
||||
import { NapProtoEncodeStructType, NapProtoMsg } from "@napneko/nap-proto-core";
|
||||
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/message/message";
|
||||
import { ForwardMsgBuilder } from "@/common/forward-msg-builder";
|
||||
import { FileExtra, GroupFileExtra } from "@/core/packet/proto/message/component";
|
||||
import { OidbSvcTrpcTcp0XE37_800Response } from "@/core/packet/proto/oidb/Oidb.0XE37_800";
|
||||
|
||||
// raw <-> packet
|
||||
// TODO: SendStructLongMsgElement
|
||||
export abstract class IPacketMsgElement<T extends PacketSendMsgElement> {
|
||||
protected constructor(rawElement: T) {
|
||||
}
|
||||
|
||||
get valid(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
buildContent(): Uint8Array | undefined {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
buildElement(): NapProtoEncodeStructType<typeof Elem>[] {
|
||||
return [];
|
||||
}
|
||||
|
||||
toPreview(): string {
|
||||
return '[暂不支持该消息类型喵~]';
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
)
|
||||
}
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
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 = +(element.replyElement.replayMsgSeq ?? 0);
|
||||
this.messageClientSeq = +(element.replyElement.replyMsgClientSeq ?? 0);
|
||||
this.targetUin = +(element.replyElement.senderUin ?? 0);
|
||||
this.targetUid = element.replyElement.senderUidStr ?? '';
|
||||
this.time = +(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 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 = +element.picElement.fileSize;
|
||||
this.md5 = element.picElement.md5HexStr ?? '';
|
||||
this.width = element.picElement.picWidth;
|
||||
this.height = element.picElement.picHeight;
|
||||
this.picType = element.picElement.picType;
|
||||
}
|
||||
|
||||
get valid(): boolean {
|
||||
return !!this.msgInfo;
|
||||
}
|
||||
|
||||
buildElement(): NapProtoEncodeStructType<typeof Elem>[] {
|
||||
if (!this.msgInfo) return [];
|
||||
return [{
|
||||
commonElem: {
|
||||
serviceType: 48,
|
||||
pbElem: new NapProtoMsg(MsgInfo).encode(this.msgInfo),
|
||||
businessType: 10,
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
toPreview(): string {
|
||||
return "[图片]";
|
||||
}
|
||||
}
|
||||
|
||||
export class PacketMsgVideoElement extends IPacketMsgElement<SendVideoElement> {
|
||||
fileSize?: string;
|
||||
filePath?: string;
|
||||
thumbSize?: number;
|
||||
thumbPath?: string;
|
||||
fileMd5?: string;
|
||||
fileSha1?: string;
|
||||
thumbMd5?: string;
|
||||
thumbSha1?: string;
|
||||
thumbWidth?: number;
|
||||
thumbHeight?: number;
|
||||
msgInfo: NapProtoEncodeStructType<typeof MsgInfo> | null = null;
|
||||
|
||||
constructor(element: SendVideoElement) {
|
||||
super(element);
|
||||
this.fileSize = element.videoElement.fileSize;
|
||||
this.filePath = element.videoElement.filePath;
|
||||
this.thumbSize = element.videoElement.thumbSize;
|
||||
this.thumbPath = element.videoElement.thumbPath?.get(0);
|
||||
this.fileMd5 = element.videoElement.videoMd5;
|
||||
this.thumbMd5 = element.videoElement.thumbMd5;
|
||||
this.thumbWidth = element.videoElement.thumbWidth;
|
||||
this.thumbHeight = element.videoElement.thumbHeight;
|
||||
}
|
||||
|
||||
get valid(): boolean {
|
||||
return !!this.msgInfo;
|
||||
}
|
||||
|
||||
buildElement(): NapProtoEncodeStructType<typeof Elem>[] {
|
||||
if (!this.msgInfo) return [];
|
||||
return [{
|
||||
commonElem: {
|
||||
serviceType: 48,
|
||||
pbElem: new NapProtoMsg(MsgInfo).encode(this.msgInfo),
|
||||
businessType: 21,
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
toPreview(): string {
|
||||
return "[视频]";
|
||||
}
|
||||
}
|
||||
|
||||
export class PacketMsgPttElement extends IPacketMsgElement<SendPttElement> {
|
||||
filePath: string;
|
||||
fileSize: number;
|
||||
fileMd5: string;
|
||||
fileSha1?: string;
|
||||
fileDuration: number;
|
||||
msgInfo: NapProtoEncodeStructType<typeof MsgInfo> | null = null;
|
||||
|
||||
constructor(element: SendPttElement) {
|
||||
super(element);
|
||||
this.filePath = element.pttElement.filePath;
|
||||
this.fileSize = +element.pttElement.fileSize; // TODO: cc
|
||||
this.fileMd5 = element.pttElement.md5HexStr;
|
||||
this.fileDuration = Math.round(element.pttElement.duration); // TODO: cc
|
||||
}
|
||||
|
||||
get valid(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
buildElement(): NapProtoEncodeStructType<typeof Elem>[] {
|
||||
return [];
|
||||
// if (!this.msgInfo) return [];
|
||||
// return [{
|
||||
// commonElem: {
|
||||
// serviceType: 48,
|
||||
// pbElem: new NapProtoMsg(MsgInfo).encode(this.msgInfo),
|
||||
// businessType: 22,
|
||||
// }
|
||||
// }];
|
||||
}
|
||||
|
||||
toPreview(): string {
|
||||
return "[语音]";
|
||||
}
|
||||
}
|
||||
|
||||
export class PacketMsgFileElement extends IPacketMsgElement<SendFileElement> {
|
||||
fileName: string;
|
||||
filePath: string;
|
||||
fileSize: number;
|
||||
fileSha1?: Uint8Array;
|
||||
fileMd5?: Uint8Array;
|
||||
fileUuid?: string;
|
||||
fileHash?: string;
|
||||
isGroupFile?: boolean;
|
||||
_private_send_uid?: string;
|
||||
_private_recv_uid?: string;
|
||||
_e37_800_rsp?: NapProtoEncodeStructType<typeof OidbSvcTrpcTcp0XE37_800Response>;
|
||||
|
||||
constructor(element: SendFileElement) {
|
||||
super(element);
|
||||
this.fileName = element.fileElement.fileName;
|
||||
this.filePath = element.fileElement.filePath;
|
||||
this.fileSize = +element.fileElement.fileSize;
|
||||
}
|
||||
|
||||
get valid(): boolean {
|
||||
return this.isGroupFile || Boolean(this._e37_800_rsp);
|
||||
}
|
||||
|
||||
buildContent(): Uint8Array | undefined {
|
||||
if (this.isGroupFile || !this._e37_800_rsp) return undefined;
|
||||
return new NapProtoMsg(FileExtra).encode({
|
||||
file: {
|
||||
fileType: 0,
|
||||
fileUuid: this.fileUuid,
|
||||
fileMd5: this.fileMd5,
|
||||
fileName: this.fileName,
|
||||
fileSize: BigInt(this.fileSize),
|
||||
subcmd: 1,
|
||||
dangerEvel: 0,
|
||||
expireTime: Math.floor(Date.now() / 1000) + 7 * 24 * 60 * 60,
|
||||
fileHash: this.fileHash,
|
||||
},
|
||||
field6: {
|
||||
field2: {
|
||||
field1: this._e37_800_rsp?.body?.field30?.field110,
|
||||
fileUuid: this.fileUuid,
|
||||
fileName: this.fileName,
|
||||
field6: this._e37_800_rsp?.body?.field30?.field3,
|
||||
field7: this._e37_800_rsp?.body?.field30?.field101,
|
||||
field8: this._e37_800_rsp?.body?.field30?.field100,
|
||||
timestamp1: this._e37_800_rsp?.body?.field30?.timestamp1,
|
||||
fileHash: this.fileHash,
|
||||
selfUid: this._private_send_uid,
|
||||
destUid: this._private_recv_uid,
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
buildElement(): NapProtoEncodeStructType<typeof Elem>[] {
|
||||
if (!this.isGroupFile) return [];
|
||||
const lb = Buffer.alloc(2);
|
||||
const transElemVal = new NapProtoMsg(GroupFileExtra).encode({
|
||||
field1: 6,
|
||||
fileName: this.fileName,
|
||||
inner: {
|
||||
info: {
|
||||
busId: 102,
|
||||
fileId: this.fileUuid,
|
||||
fileSize: BigInt(this.fileSize),
|
||||
fileName: this.fileName,
|
||||
fileSha: this.fileSha1,
|
||||
extInfoString: "",
|
||||
fileMd5: this.fileMd5,
|
||||
}
|
||||
}
|
||||
});
|
||||
lb.writeUInt16BE(transElemVal.length);
|
||||
return [{
|
||||
transElem: {
|
||||
elemType: 24,
|
||||
elemValue: Buffer.concat([Buffer.from([0x01]), lb, transElemVal]) // TLV
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
toPreview(): string {
|
||||
return `[文件]${this.fileName}`;
|
||||
}
|
||||
}
|
||||
|
||||
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 `[Markdown消息 ${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 ?? [];
|
||||
}
|
||||
|
||||
buildElement(): NapProtoEncodeStructType<typeof Elem>[] {
|
||||
return [{
|
||||
lightAppElem: {
|
||||
data: Buffer.concat([
|
||||
Buffer.from([0x01]),
|
||||
zlib.deflateSync(Buffer.from(JSON.stringify(ForwardMsgBuilder.fromPacketMsg(this.resid, this.message)), 'utf-8'))
|
||||
])
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
toPreview(): string {
|
||||
return "[聊天记录]";
|
||||
}
|
||||
}
|
15
src/core/packet/message/message.ts
Normal file
15
src/core/packet/message/message.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { IPacketMsgElement } from "@/core/packet/message/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>[]
|
||||
}
|
803
src/core/packet/packer.ts
Normal file
803
src/core/packet/packer.ts
Normal file
@@ -0,0 +1,803 @@
|
||||
import * as zlib from "node:zlib";
|
||||
import * as crypto from "node:crypto";
|
||||
import { computeMd5AndLengthWithLimit } from "@/core/packet/utils/crypto/hash";
|
||||
import { NapProtoEncodeStructType, NapProtoMsg } from "@napneko/nap-proto-core";
|
||||
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 { IndexNode, 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/message/builder";
|
||||
import {
|
||||
PacketMsgFileElement,
|
||||
PacketMsgPicElement,
|
||||
PacketMsgPttElement,
|
||||
PacketMsgVideoElement
|
||||
} from "@/core/packet/message/element";
|
||||
import { LogWrapper } from "@/common/log";
|
||||
import { PacketMsg } from "@/core/packet/message/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/message/converter";
|
||||
import { OidbSvcTrpcTcp0XE37_1700 } from "@/core/packet/proto/oidb/Oidb.0xE37_1700";
|
||||
import { OidbSvcTrpcTcp0XE37_800 } from "@/core/packet/proto/oidb/Oidb.0XE37_800";
|
||||
import { OidbSvcTrpcTcp0XEB7 } from "./proto/oidb/Oidb.0xEB7";
|
||||
import { MiniAppReqParams } from "@/core/packet/entities/miniApp";
|
||||
import { MiniAppAdaptShareInfoReq } from "@/core/packet/proto/action/miniAppAdaptShareInfo";
|
||||
import { AIVoiceChatType } from "@/core/packet/entities/aiChat";
|
||||
import { OidbSvcTrpcTcp0X929B_0, OidbSvcTrpcTcp0X929D_0 } from "@/core/packet/proto/oidb/Oidb.0x929";
|
||||
import { PacketClient } from "@/core/packet/client/client";
|
||||
|
||||
export type PacketHexStr = string & { readonly hexNya: unique symbol };
|
||||
|
||||
export interface OidbPacket {
|
||||
cmd: string;
|
||||
data: PacketHexStr
|
||||
}
|
||||
|
||||
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 packetPacket(byteArray: Uint8Array): PacketHexStr {
|
||||
return Buffer.from(byteArray).toString('hex') as PacketHexStr;
|
||||
}
|
||||
|
||||
packOidbPacket(cmd: number, subCmd: number, body: Uint8Array, isUid: boolean = true, isLafter: boolean = false): OidbPacket {
|
||||
const data = new NapProtoMsg(OidbSvcTrpcTcpBase).encode({
|
||||
command: cmd,
|
||||
subCommand: subCmd,
|
||||
body: body,
|
||||
isReserved: isUid ? 1 : 0
|
||||
});
|
||||
return {
|
||||
cmd: `OidbSvcTrpcTcp.0x${cmd.toString(16).toUpperCase()}_${subCmd}`,
|
||||
data: this.packetPacket(data)
|
||||
};
|
||||
}
|
||||
|
||||
packPokePacket(peer: number, group?: number): OidbPacket {
|
||||
const oidb_0xed3 = new NapProtoMsg(OidbSvcTrpcTcp0XED3_1).encode({
|
||||
uin: peer,
|
||||
groupUin: group,
|
||||
friendUin: group ?? peer,
|
||||
ext: 0
|
||||
});
|
||||
return this.packOidbPacket(0xed3, 1, oidb_0xed3);
|
||||
}
|
||||
|
||||
packRkeyPacket(): OidbPacket {
|
||||
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.packOidbPacket(0x9067, 202, oidb_0x9067_202);
|
||||
}
|
||||
|
||||
packSetSpecialTittlePacket(groupCode: string, uid: string, tittle: string): OidbPacket {
|
||||
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.packOidbPacket(0x8FC, 2, oidb_0x8FC_2, false, false);
|
||||
}
|
||||
|
||||
packStatusPacket(uin: number): OidbPacket {
|
||||
const oidb_0xfe1_2 = new NapProtoMsg(OidbSvcTrpcTcp0XFE1_2).encode({
|
||||
uin: uin,
|
||||
key: [{ key: 27372 }]
|
||||
});
|
||||
return 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.packetPacket(req);
|
||||
}
|
||||
|
||||
// highway part
|
||||
packHttp0x6ff_501(): PacketHexStr {
|
||||
return this.packetPacket(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<OidbPacket> {
|
||||
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: +img.size,
|
||||
fileHash: img.md5,
|
||||
fileSha1: img.sha1!,
|
||||
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.packOidbPacket(0x11c4, 100, req, true, false);
|
||||
}
|
||||
|
||||
async packUploadC2CImgReq(peerUin: string, img: PacketMsgPicElement): Promise<OidbPacket> {
|
||||
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: +img.size,
|
||||
fileHash: img.md5,
|
||||
fileSha1: img.sha1!,
|
||||
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.packOidbPacket(0x11c5, 100, req, true, false);
|
||||
}
|
||||
|
||||
async packUploadGroupVideoReq(groupUin: number, video: PacketMsgVideoElement): Promise<OidbPacket> {
|
||||
if (!video.fileSize || !video.thumbSize) throw new Error("video.fileSize or video.thumbSize is empty");
|
||||
const req = new NapProtoMsg(NTV2RichMediaReq).encode({
|
||||
reqHead: {
|
||||
common: {
|
||||
requestId: 3,
|
||||
command: 100
|
||||
},
|
||||
scene: {
|
||||
requestType: 2,
|
||||
businessType: 2,
|
||||
sceneType: 2,
|
||||
group: {
|
||||
groupUin: groupUin
|
||||
},
|
||||
},
|
||||
client: {
|
||||
agentType: 2
|
||||
}
|
||||
},
|
||||
upload: {
|
||||
uploadInfo: [
|
||||
{
|
||||
fileInfo: {
|
||||
fileSize: +video.fileSize,
|
||||
fileHash: video.fileMd5,
|
||||
fileSha1: video.fileSha1,
|
||||
fileName: "nya.mp4",
|
||||
type: {
|
||||
type: 2,
|
||||
picFormat: 0,
|
||||
videoFormat: 0,
|
||||
voiceFormat: 0
|
||||
},
|
||||
height: 0,
|
||||
width: 0,
|
||||
time: 0,
|
||||
original: 0
|
||||
},
|
||||
subFileType: 0
|
||||
}, {
|
||||
fileInfo: {
|
||||
fileSize: +video.thumbSize,
|
||||
fileHash: video.thumbMd5,
|
||||
fileSha1: video.thumbSha1,
|
||||
fileName: "nya.jpg",
|
||||
type: {
|
||||
type: 1,
|
||||
picFormat: 0,
|
||||
videoFormat: 0,
|
||||
voiceFormat: 0
|
||||
},
|
||||
height: video.thumbHeight,
|
||||
width: video.thumbWidth,
|
||||
time: 0,
|
||||
original: 0
|
||||
},
|
||||
subFileType: 100
|
||||
}
|
||||
],
|
||||
tryFastUploadCompleted: true,
|
||||
srvSendMsg: false,
|
||||
clientRandomId: crypto.randomBytes(8).readBigUInt64BE() & BigInt('0x7FFFFFFFFFFFFFFF'),
|
||||
compatQMsgSceneType: 2,
|
||||
extBizInfo: {
|
||||
pic: {
|
||||
bizType: 0,
|
||||
textSummary: "Nya~",
|
||||
},
|
||||
video: {
|
||||
bytesPbReserve: Buffer.from([0x80, 0x01, 0x00]),
|
||||
},
|
||||
ptt: {
|
||||
bytesPbReserve: Buffer.alloc(0),
|
||||
bytesReserve: Buffer.alloc(0),
|
||||
bytesGeneralFlags: Buffer.alloc(0),
|
||||
}
|
||||
},
|
||||
clientSeq: 0,
|
||||
noNeedCompatMsg: false
|
||||
}
|
||||
});
|
||||
return this.packOidbPacket(0x11EA, 100, req, true, false);
|
||||
}
|
||||
|
||||
async packUploadC2CVideoReq(peerUin: string, video: PacketMsgVideoElement): Promise<OidbPacket> {
|
||||
if (!video.fileSize || !video.thumbSize) throw new Error("video.fileSize or video.thumbSize is empty");
|
||||
const req = new NapProtoMsg(NTV2RichMediaReq).encode({
|
||||
reqHead: {
|
||||
common: {
|
||||
requestId: 3,
|
||||
command: 100
|
||||
},
|
||||
scene: {
|
||||
requestType: 2,
|
||||
businessType: 2,
|
||||
sceneType: 1,
|
||||
c2C: {
|
||||
accountType: 2,
|
||||
targetUid: peerUin
|
||||
}
|
||||
},
|
||||
client: {
|
||||
agentType: 2
|
||||
}
|
||||
},
|
||||
upload: {
|
||||
uploadInfo: [
|
||||
{
|
||||
fileInfo: {
|
||||
fileSize: +video.fileSize,
|
||||
fileHash: video.fileMd5,
|
||||
fileSha1: video.fileSha1,
|
||||
fileName: "nya.mp4",
|
||||
type: {
|
||||
type: 2,
|
||||
picFormat: 0,
|
||||
videoFormat: 0,
|
||||
voiceFormat: 0
|
||||
},
|
||||
height: 0,
|
||||
width: 0,
|
||||
time: 0,
|
||||
original: 0
|
||||
},
|
||||
subFileType: 0
|
||||
}, {
|
||||
fileInfo: {
|
||||
fileSize: +video.thumbSize,
|
||||
fileHash: video.thumbMd5,
|
||||
fileSha1: video.thumbSha1,
|
||||
fileName: "nya.jpg",
|
||||
type: {
|
||||
type: 1,
|
||||
picFormat: 0,
|
||||
videoFormat: 0,
|
||||
voiceFormat: 0
|
||||
},
|
||||
height: video.thumbHeight,
|
||||
width: video.thumbWidth,
|
||||
time: 0,
|
||||
original: 0
|
||||
},
|
||||
subFileType: 100
|
||||
}
|
||||
],
|
||||
tryFastUploadCompleted: true,
|
||||
srvSendMsg: false,
|
||||
clientRandomId: crypto.randomBytes(8).readBigUInt64BE() & BigInt('0x7FFFFFFFFFFFFFFF'),
|
||||
compatQMsgSceneType: 2,
|
||||
extBizInfo: {
|
||||
pic: {
|
||||
bizType: 0,
|
||||
textSummary: "Nya~",
|
||||
},
|
||||
video: {
|
||||
bytesPbReserve: Buffer.from([0x80, 0x01, 0x00]),
|
||||
},
|
||||
ptt: {
|
||||
bytesPbReserve: Buffer.alloc(0),
|
||||
bytesReserve: Buffer.alloc(0),
|
||||
bytesGeneralFlags: Buffer.alloc(0),
|
||||
}
|
||||
},
|
||||
clientSeq: 0,
|
||||
noNeedCompatMsg: false
|
||||
}
|
||||
});
|
||||
return this.packOidbPacket(0x11E9, 100, req, true, false);
|
||||
}
|
||||
|
||||
async packUploadGroupPttReq(groupUin: number, ptt: PacketMsgPttElement): Promise<OidbPacket> {
|
||||
const req = new NapProtoMsg(NTV2RichMediaReq).encode({
|
||||
reqHead: {
|
||||
common: {
|
||||
requestId: 1,
|
||||
command: 100
|
||||
},
|
||||
scene: {
|
||||
requestType: 2,
|
||||
businessType: 3,
|
||||
sceneType: 2,
|
||||
group: {
|
||||
groupUin: groupUin
|
||||
}
|
||||
},
|
||||
client: {
|
||||
agentType: 2
|
||||
}
|
||||
},
|
||||
upload: {
|
||||
uploadInfo: [
|
||||
{
|
||||
fileInfo: {
|
||||
fileSize: ptt.fileSize,
|
||||
fileHash: ptt.fileMd5,
|
||||
fileSha1: ptt.fileSha1,
|
||||
fileName: `${ptt.fileMd5}.amr`,
|
||||
type: {
|
||||
type: 3,
|
||||
picFormat: 0,
|
||||
videoFormat: 0,
|
||||
voiceFormat: 1
|
||||
},
|
||||
height: 0,
|
||||
width: 0,
|
||||
time: ptt.fileDuration,
|
||||
original: 0
|
||||
},
|
||||
subFileType: 0
|
||||
}
|
||||
],
|
||||
tryFastUploadCompleted: true,
|
||||
srvSendMsg: false,
|
||||
clientRandomId: crypto.randomBytes(8).readBigUInt64BE() & BigInt('0x7FFFFFFFFFFFFFFF'),
|
||||
compatQMsgSceneType: 2,
|
||||
extBizInfo: {
|
||||
pic: {
|
||||
textSummary: "Nya~",
|
||||
},
|
||||
video: {
|
||||
bytesPbReserve: Buffer.alloc(0),
|
||||
},
|
||||
ptt: {
|
||||
bytesPbReserve: Buffer.alloc(0),
|
||||
bytesReserve: Buffer.from([0x08, 0x00, 0x38, 0x00]),
|
||||
bytesGeneralFlags: Buffer.from([0x9a, 0x01, 0x07, 0xaa, 0x03, 0x04, 0x08, 0x08, 0x12, 0x00]),
|
||||
}
|
||||
},
|
||||
clientSeq: 0,
|
||||
noNeedCompatMsg: false
|
||||
}
|
||||
});
|
||||
return this.packOidbPacket(0x126E, 100, req, true, false);
|
||||
}
|
||||
|
||||
async packUploadC2CPttReq(peerUin: string, ptt: PacketMsgPttElement): Promise<OidbPacket> {
|
||||
const req = new NapProtoMsg(NTV2RichMediaReq).encode({
|
||||
reqHead: {
|
||||
common: {
|
||||
requestId: 4,
|
||||
command: 100
|
||||
},
|
||||
scene: {
|
||||
requestType: 2,
|
||||
businessType: 3,
|
||||
sceneType: 1,
|
||||
c2C: {
|
||||
accountType: 2,
|
||||
targetUid: peerUin
|
||||
}
|
||||
},
|
||||
client: {
|
||||
agentType: 2
|
||||
}
|
||||
},
|
||||
upload: {
|
||||
uploadInfo: [
|
||||
{
|
||||
fileInfo: {
|
||||
fileSize: ptt.fileSize,
|
||||
fileHash: ptt.fileMd5,
|
||||
fileSha1: ptt.fileSha1,
|
||||
fileName: `${ptt.fileMd5}.amr`,
|
||||
type: {
|
||||
type: 3,
|
||||
picFormat: 0,
|
||||
videoFormat: 0,
|
||||
voiceFormat: 1
|
||||
},
|
||||
height: 0,
|
||||
width: 0,
|
||||
time: ptt.fileDuration,
|
||||
original: 0
|
||||
},
|
||||
subFileType: 0
|
||||
}
|
||||
],
|
||||
tryFastUploadCompleted: true,
|
||||
srvSendMsg: false,
|
||||
clientRandomId: crypto.randomBytes(8).readBigUInt64BE() & BigInt('0x7FFFFFFFFFFFFFFF'),
|
||||
compatQMsgSceneType: 1,
|
||||
extBizInfo: {
|
||||
pic: {
|
||||
textSummary: "Nya~",
|
||||
},
|
||||
ptt: {
|
||||
bytesReserve: Buffer.from([0x08, 0x00, 0x38, 0x00]),
|
||||
bytesGeneralFlags: Buffer.from([0x9a, 0x01, 0x0b, 0xaa, 0x03, 0x08, 0x08, 0x04, 0x12, 0x04, 0x00, 0x00, 0x00, 0x00]),
|
||||
}
|
||||
},
|
||||
clientSeq: 0,
|
||||
noNeedCompatMsg: false
|
||||
}
|
||||
});
|
||||
return this.packOidbPacket(0x126D, 100, req, true, false);
|
||||
}
|
||||
|
||||
async packUploadGroupFileReq(groupUin: number, file: PacketMsgFileElement): Promise<OidbPacket> {
|
||||
const body = new NapProtoMsg(OidbSvcTrpcTcp0x6D6).encode({
|
||||
file: {
|
||||
groupUin: groupUin,
|
||||
appId: 4,
|
||||
busId: 102,
|
||||
entrance: 6,
|
||||
targetDirectory: '/', // TODO:
|
||||
fileName: file.fileName,
|
||||
localDirectory: `/${file.fileName}`,
|
||||
fileSize: BigInt(file.fileSize),
|
||||
fileMd5: file.fileMd5,
|
||||
fileSha1: file.fileSha1,
|
||||
fileSha3: Buffer.alloc(0),
|
||||
field15: true
|
||||
}
|
||||
});
|
||||
return this.packOidbPacket(0x6D6, 0, body, true, false);
|
||||
}
|
||||
|
||||
async packUploadC2CFileReq(selfUid: string, peerUid: string, file: PacketMsgFileElement): Promise<OidbPacket> {
|
||||
const body = new NapProtoMsg(OidbSvcTrpcTcp0XE37_1700).encode({
|
||||
command: 1700,
|
||||
seq: 0,
|
||||
upload: {
|
||||
senderUid: selfUid,
|
||||
receiverUid: peerUid,
|
||||
fileSize: file.fileSize,
|
||||
fileName: file.fileName,
|
||||
md510MCheckSum: await computeMd5AndLengthWithLimit(file.filePath, 10 * 1024 * 1024),
|
||||
sha1CheckSum: file.fileSha1,
|
||||
localPath: "/",
|
||||
md5CheckSum: file.fileMd5,
|
||||
sha3CheckSum: Buffer.alloc(0)
|
||||
},
|
||||
businessId: 3,
|
||||
clientType: 1,
|
||||
flagSupportMediaPlatform: 1
|
||||
});
|
||||
return this.packOidbPacket(0xE37, 1700, body, false, false);
|
||||
}
|
||||
|
||||
packOfflineFileDownloadReq(fileUUID: string, fileHash: string, senderUid: string, receiverUid: string): OidbPacket {
|
||||
return this.packOidbPacket(0xE37, 800, new NapProtoMsg(OidbSvcTrpcTcp0XE37_800).encode({
|
||||
subCommand: 800,
|
||||
field2: 0,
|
||||
body: {
|
||||
senderUid: senderUid,
|
||||
receiverUid: receiverUid,
|
||||
fileUuid: fileUUID,
|
||||
fileHash: fileHash,
|
||||
},
|
||||
field101: 3,
|
||||
field102: 1,
|
||||
field200: 1,
|
||||
}), false, false);
|
||||
}
|
||||
|
||||
packGroupFileDownloadReq(groupUin: number, fileUUID: string): OidbPacket {
|
||||
return 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.packetPacket(
|
||||
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])
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
packGroupPttFileDownloadReq(groupUin: number, node: NapProtoEncodeStructType<typeof IndexNode>): OidbPacket {
|
||||
return this.packOidbPacket(0x126E, 200, new NapProtoMsg(NTV2RichMediaReq).encode({
|
||||
reqHead: {
|
||||
common: {
|
||||
requestId: 4,
|
||||
command: 200
|
||||
},
|
||||
scene: {
|
||||
requestType: 1,
|
||||
businessType: 3,
|
||||
sceneType: 2,
|
||||
group: {
|
||||
groupUin: groupUin
|
||||
}
|
||||
},
|
||||
client: {
|
||||
agentType: 2
|
||||
}
|
||||
},
|
||||
download: {
|
||||
node: node,
|
||||
download: {
|
||||
video: {
|
||||
busiType: 0,
|
||||
sceneType: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
}), true, false);
|
||||
}
|
||||
|
||||
packGroupSignReq(uin: string, groupCode: string): OidbPacket {
|
||||
return this.packOidbPacket(0XEB7, 1, new NapProtoMsg(OidbSvcTrpcTcp0XEB7).encode(
|
||||
{
|
||||
body: {
|
||||
uin: uin,
|
||||
groupUin: groupCode,
|
||||
version: "9.0.90"
|
||||
}
|
||||
}
|
||||
), false, false);
|
||||
}
|
||||
|
||||
packMiniAppAdaptShareInfo(req: MiniAppReqParams): PacketHexStr {
|
||||
return this.packetPacket(
|
||||
new NapProtoMsg(MiniAppAdaptShareInfoReq).encode(
|
||||
{
|
||||
appId: req.sdkId,
|
||||
body: {
|
||||
extInfo: {
|
||||
field2: Buffer.alloc(0)
|
||||
},
|
||||
appid: req.appId,
|
||||
title: req.title,
|
||||
desc: req.desc,
|
||||
time: BigInt(Date.now()),
|
||||
scene: req.scene,
|
||||
templateType: req.templateType,
|
||||
businessType: req.businessType,
|
||||
picUrl: req.picUrl,
|
||||
vidUrl: "",
|
||||
jumpUrl: req.jumpUrl,
|
||||
iconUrl: req.iconUrl,
|
||||
verType: req.verType,
|
||||
shareType: req.shareType,
|
||||
versionId: req.versionId,
|
||||
withShareTicket: req.withShareTicket,
|
||||
webURL: "",
|
||||
appidRich: Buffer.alloc(0),
|
||||
template: {
|
||||
templateId: "",
|
||||
templateData: ""
|
||||
},
|
||||
field20: ""
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
packFetchAiVoiceListReq(groupUin: number, chatType: AIVoiceChatType): OidbPacket {
|
||||
return this.packOidbPacket(0x929D, 0,
|
||||
new NapProtoMsg(OidbSvcTrpcTcp0X929D_0).encode({
|
||||
groupUin: groupUin,
|
||||
chatType: chatType
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
packAiVoiceChatReq(groupUin: number, voiceId: string, text: string, chatType: AIVoiceChatType, sessionId: number): OidbPacket {
|
||||
return this.packOidbPacket(0x929B, 0,
|
||||
new NapProtoMsg(OidbSvcTrpcTcp0X929B_0).encode({
|
||||
groupUin: groupUin,
|
||||
voiceId: voiceId,
|
||||
text: text,
|
||||
chatType: chatType,
|
||||
session: {
|
||||
sessionId: sessionId
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
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 "@napneko/nap-proto-core";
|
||||
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),
|
||||
};
|
49
src/core/packet/proto/action/miniAppAdaptShareInfo.ts
Normal file
49
src/core/packet/proto/action/miniAppAdaptShareInfo.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { ScalarType } from "@protobuf-ts/runtime";
|
||||
import { ProtoField } from "@napneko/nap-proto-core";
|
||||
|
||||
export const MiniAppAdaptShareInfoReq = {
|
||||
appId: ProtoField(2, ScalarType.STRING),
|
||||
body: ProtoField(4, () => MiniAppAdaptShareInfoReqBody),
|
||||
};
|
||||
|
||||
export const MiniAppAdaptShareInfoReqBody = {
|
||||
extInfo: ProtoField(1, () => ExtInfo),
|
||||
appid: ProtoField(2, ScalarType.STRING),
|
||||
title: ProtoField(3, ScalarType.STRING),
|
||||
desc: ProtoField(4, ScalarType.STRING),
|
||||
time: ProtoField(5, ScalarType.UINT64),
|
||||
scene: ProtoField(6, ScalarType.UINT32),
|
||||
templateType: ProtoField(7, ScalarType.UINT32),
|
||||
businessType: ProtoField(8, ScalarType.UINT32),
|
||||
picUrl: ProtoField(9, ScalarType.STRING),
|
||||
vidUrl: ProtoField(10, ScalarType.STRING),
|
||||
jumpUrl: ProtoField(11, ScalarType.STRING),
|
||||
iconUrl: ProtoField(12, ScalarType.STRING),
|
||||
verType: ProtoField(13, ScalarType.UINT32),
|
||||
shareType: ProtoField(14, ScalarType.UINT32),
|
||||
versionId: ProtoField(15, ScalarType.STRING),
|
||||
withShareTicket: ProtoField(16, ScalarType.UINT32),
|
||||
webURL: ProtoField(17, ScalarType.STRING),
|
||||
appidRich: ProtoField(18, ScalarType.BYTES),
|
||||
template: ProtoField(19, () => Template),
|
||||
field20: ProtoField(20, ScalarType.STRING),
|
||||
};
|
||||
|
||||
export const ExtInfo = {
|
||||
field2: ProtoField(2, ScalarType.BYTES),
|
||||
};
|
||||
|
||||
export const Template = {
|
||||
templateId: ProtoField(1, ScalarType.STRING),
|
||||
templateData: ProtoField(2, ScalarType.STRING),
|
||||
};
|
||||
|
||||
export const MiniAppAdaptShareInfoResp = {
|
||||
field2: ProtoField(2, ScalarType.UINT32),
|
||||
field3: ProtoField(3, ScalarType.STRING),
|
||||
content: ProtoField(4, () => MiniAppAdaptShareInfoRespContent),
|
||||
};
|
||||
|
||||
export const MiniAppAdaptShareInfoRespContent = {
|
||||
jsonContent: ProtoField(2, ScalarType.STRING),
|
||||
};
|
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 "@napneko/nap-proto-core";
|
||||
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 "@napneko/nap-proto-core";
|
||||
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 "@napneko/nap-proto-core";
|
||||
|
||||
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),
|
||||
};
|
166
src/core/packet/proto/message/component.ts
Normal file
166
src/core/packet/proto/message/component.ts
Normal file
@@ -0,0 +1,166 @@
|
||||
import { ScalarType } from "@protobuf-ts/runtime";
|
||||
import { ProtoField } from "@napneko/nap-proto-core";
|
||||
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),
|
||||
field6: ProtoField(6, () => PrivateFileExtra),
|
||||
};
|
||||
|
||||
export const PrivateFileExtra = {
|
||||
field2: ProtoField(2, () => PrivateFileExtraField2),
|
||||
};
|
||||
|
||||
export const PrivateFileExtraField2 = {
|
||||
field1: ProtoField(1, ScalarType.UINT32),
|
||||
fileUuid: ProtoField(4, ScalarType.STRING),
|
||||
fileName: ProtoField(5, ScalarType.STRING),
|
||||
field6: ProtoField(6, ScalarType.UINT32),
|
||||
field7: ProtoField(7, ScalarType.BYTES),
|
||||
field8: ProtoField(8, ScalarType.BYTES),
|
||||
timestamp1: ProtoField(9, ScalarType.UINT32),
|
||||
fileHash: ProtoField(14, ScalarType.STRING),
|
||||
selfUid: ProtoField(15, ScalarType.STRING),
|
||||
destUid: ProtoField(16, ScalarType.STRING),
|
||||
};
|
||||
|
||||
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),
|
||||
fileSha: ProtoField(6, ScalarType.BYTES),
|
||||
extInfoString: ProtoField(7, ScalarType.STRING),
|
||||
fileMd5: ProtoField(8, ScalarType.BYTES),
|
||||
};
|
||||
|
||||
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 "@napneko/nap-proto-core";
|
||||
|
||||
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 "@napneko/nap-proto-core";
|
||||
|
||||
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 "@napneko/nap-proto-core";
|
||||
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 "@napneko/nap-proto-core";
|
||||
|
||||
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 "@napneko/nap-proto-core";
|
||||
|
||||
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),
|
||||
};
|
62
src/core/packet/proto/oidb/Oidb.0XE37_800.ts
Normal file
62
src/core/packet/proto/oidb/Oidb.0XE37_800.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import { ScalarType } from "@protobuf-ts/runtime";
|
||||
import { ProtoField } from "@napneko/nap-proto-core";
|
||||
import { OidbSvcTrpcTcp0XE37_800_1200Metadata } from "@/core/packet/proto/oidb/Oidb.0xE37_1200";
|
||||
|
||||
export const OidbSvcTrpcTcp0XE37_800 = {
|
||||
subCommand: ProtoField(1, ScalarType.UINT32),
|
||||
field2: ProtoField(2, ScalarType.INT32),
|
||||
body: ProtoField(10, () => OidbSvcTrpcTcp0XE37_800Body, true),
|
||||
field101: ProtoField(101, ScalarType.INT32),
|
||||
field102: ProtoField(102, ScalarType.INT32),
|
||||
field200: ProtoField(200, ScalarType.INT32)
|
||||
};
|
||||
|
||||
export const OidbSvcTrpcTcp0XE37_800Body = {
|
||||
senderUid: ProtoField(10, ScalarType.STRING, true),
|
||||
receiverUid: ProtoField(20, ScalarType.STRING, true),
|
||||
fileUuid: ProtoField(30, ScalarType.STRING, true),
|
||||
fileHash: ProtoField(40, ScalarType.STRING, true)
|
||||
};
|
||||
|
||||
export const OidbSvcTrpcTcp0XE37Response = {
|
||||
command: ProtoField(1, ScalarType.UINT32),
|
||||
seq: ProtoField(2, ScalarType.INT32),
|
||||
upload: ProtoField(19, () => ApplyUploadRespV3, true),
|
||||
businessId: ProtoField(101, ScalarType.INT32),
|
||||
clientType: ProtoField(102, ScalarType.INT32),
|
||||
flagSupportMediaPlatform: ProtoField(200, ScalarType.INT32)
|
||||
};
|
||||
|
||||
export const ApplyUploadRespV3 = {
|
||||
retCode: ProtoField(10, ScalarType.INT32),
|
||||
retMsg: ProtoField(20, ScalarType.STRING, true),
|
||||
totalSpace: ProtoField(30, ScalarType.INT64),
|
||||
usedSpace: ProtoField(40, ScalarType.INT64),
|
||||
uploadedSize: ProtoField(50, ScalarType.INT64),
|
||||
uploadIp: ProtoField(60, ScalarType.STRING, true),
|
||||
uploadDomain: ProtoField(70, ScalarType.STRING, true),
|
||||
uploadPort: ProtoField(80, ScalarType.UINT32),
|
||||
uuid: ProtoField(90, ScalarType.STRING, true),
|
||||
uploadKey: ProtoField(100, ScalarType.BYTES, true),
|
||||
boolFileExist: ProtoField(110, ScalarType.BOOL),
|
||||
packSize: ProtoField(120, ScalarType.INT32),
|
||||
uploadIpList: ProtoField(130, ScalarType.STRING, false, true), // repeated
|
||||
uploadHttpsPort: ProtoField(140, ScalarType.INT32),
|
||||
uploadHttpsDomain: ProtoField(150, ScalarType.STRING, true),
|
||||
uploadDns: ProtoField(160, ScalarType.STRING, true),
|
||||
uploadLanip: ProtoField(170, ScalarType.STRING, true),
|
||||
fileAddon: ProtoField(200, ScalarType.STRING, true),
|
||||
mediaPlatformUploadKey: ProtoField(220, ScalarType.BYTES, true)
|
||||
};
|
||||
|
||||
export const OidbSvcTrpcTcp0XE37_800Response = {
|
||||
command: ProtoField(1, ScalarType.UINT32, true),
|
||||
subCommand: ProtoField(2, ScalarType.UINT32, true),
|
||||
body: ProtoField(10, () => OidbSvcTrpcTcp0XE37_800ResponseBody, true),
|
||||
field50: ProtoField(50, ScalarType.UINT32, true),
|
||||
};
|
||||
|
||||
export const OidbSvcTrpcTcp0XE37_800ResponseBody = {
|
||||
field10: ProtoField(10, ScalarType.UINT32, true),
|
||||
field30: ProtoField(30, () => OidbSvcTrpcTcp0XE37_800_1200Metadata, true),
|
||||
};
|
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 "@napneko/nap-proto-core";
|
||||
|
||||
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 "@napneko/nap-proto-core";
|
||||
|
||||
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 "@napneko/nap-proto-core";
|
||||
|
||||
|
||||
//设置群头衔 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),
|
||||
};
|
27
src/core/packet/proto/oidb/Oidb.0x9067_202.ts
Normal file
27
src/core/packet/proto/oidb/Oidb.0x9067_202.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { ScalarType } from "@protobuf-ts/runtime";
|
||||
import { ProtoField } from "@napneko/nap-proto-core";
|
||||
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),
|
||||
ttl: ProtoField(2, ScalarType.UINT64),
|
||||
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),
|
||||
};
|
42
src/core/packet/proto/oidb/Oidb.0x929.ts
Normal file
42
src/core/packet/proto/oidb/Oidb.0x929.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { ScalarType } from "@protobuf-ts/runtime";
|
||||
import { ProtoField } from "@napneko/nap-proto-core";
|
||||
import { MsgInfo } from "@/core/packet/proto/oidb/common/Ntv2.RichMediaReq";
|
||||
|
||||
export const OidbSvcTrpcTcp0X929D_0 = {
|
||||
groupUin: ProtoField(1, ScalarType.UINT32),
|
||||
chatType: ProtoField(2, ScalarType.UINT32),
|
||||
};
|
||||
|
||||
export const OidbSvcTrpcTcp0X929D_0Resp = {
|
||||
content: ProtoField(1, () => OidbSvcTrpcTcp0X929D_0RespContent, false, true),
|
||||
};
|
||||
|
||||
export const OidbSvcTrpcTcp0X929D_0RespContent = {
|
||||
category: ProtoField(1, ScalarType.STRING),
|
||||
voices: ProtoField(2, () => OidbSvcTrpcTcp0X929D_0RespContentVoice, false, true),
|
||||
};
|
||||
|
||||
export const OidbSvcTrpcTcp0X929D_0RespContentVoice = {
|
||||
voiceId: ProtoField(1, ScalarType.STRING),
|
||||
voiceDisplayName: ProtoField(2, ScalarType.STRING),
|
||||
voiceExampleUrl: ProtoField(3, ScalarType.STRING),
|
||||
};
|
||||
|
||||
export const OidbSvcTrpcTcp0X929B_0 = {
|
||||
groupUin: ProtoField(1, ScalarType.UINT32),
|
||||
voiceId: ProtoField(2, ScalarType.STRING),
|
||||
text: ProtoField(3, ScalarType.STRING),
|
||||
chatType: ProtoField(4, ScalarType.UINT32),
|
||||
session: ProtoField(5, () => OidbSvcTrpcTcp0X929B_0_Session),
|
||||
};
|
||||
|
||||
export const OidbSvcTrpcTcp0X929B_0_Session = {
|
||||
sessionId: ProtoField(1, ScalarType.UINT32),
|
||||
};
|
||||
|
||||
export const OidbSvcTrpcTcp0X929B_0Resp = {
|
||||
statusCode: ProtoField(1, ScalarType.UINT32),
|
||||
field2: ProtoField(2, ScalarType.UINT32, true),
|
||||
field3: ProtoField(3, ScalarType.UINT32),
|
||||
msgInfo: ProtoField(4, () => MsgInfo, true),
|
||||
};
|
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 "@napneko/nap-proto-core";
|
||||
|
||||
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_800_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_800_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),
|
||||
};
|
23
src/core/packet/proto/oidb/Oidb.0xE37_1700.ts
Normal file
23
src/core/packet/proto/oidb/Oidb.0xE37_1700.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { ScalarType } from "@protobuf-ts/runtime";
|
||||
import { ProtoField } from "@napneko/nap-proto-core";
|
||||
|
||||
export const OidbSvcTrpcTcp0XE37_1700 = {
|
||||
command: ProtoField(1, ScalarType.UINT32, true),
|
||||
seq: ProtoField(2, ScalarType.INT32, true),
|
||||
upload: ProtoField(19, () => ApplyUploadReqV3, true),
|
||||
businessId: ProtoField(101, ScalarType.INT32, true),
|
||||
clientType: ProtoField(102, ScalarType.INT32, true),
|
||||
flagSupportMediaPlatform: ProtoField(200, ScalarType.INT32, true),
|
||||
};
|
||||
|
||||
export const ApplyUploadReqV3 = {
|
||||
senderUid: ProtoField(10, ScalarType.STRING, true),
|
||||
receiverUid: ProtoField(20, ScalarType.STRING, true),
|
||||
fileSize: ProtoField(30, ScalarType.UINT32, true),
|
||||
fileName: ProtoField(40, ScalarType.STRING, true),
|
||||
md510MCheckSum: ProtoField(50, ScalarType.BYTES, true),
|
||||
sha1CheckSum: ProtoField(60, ScalarType.BYTES, true),
|
||||
localPath: ProtoField(70, ScalarType.STRING, true),
|
||||
md5CheckSum: ProtoField(110, ScalarType.BYTES, true),
|
||||
sha3CheckSum: ProtoField(120, ScalarType.BYTES, true),
|
||||
};
|
12
src/core/packet/proto/oidb/Oidb.0xEB7.ts
Normal file
12
src/core/packet/proto/oidb/Oidb.0xEB7.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { ScalarType } from "@protobuf-ts/runtime";
|
||||
import { ProtoField } from "@napneko/nap-proto-core";
|
||||
|
||||
export const OidbSvcTrpcTcp0XEB7_Body = {
|
||||
uin: ProtoField(1, ScalarType.STRING),
|
||||
groupUin: ProtoField(2, ScalarType.STRING),
|
||||
version: ProtoField(3, ScalarType.STRING),
|
||||
};
|
||||
|
||||
export const OidbSvcTrpcTcp0XEB7 = {
|
||||
body: ProtoField(2, () => OidbSvcTrpcTcp0XEB7_Body),
|
||||
};
|
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 "@napneko/nap-proto-core";
|
||||
|
||||
// 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)
|
||||
};
|
14
src/core/packet/proto/oidb/OidbBase.ts
Normal file
14
src/core/packet/proto/oidb/OidbBase.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { ScalarType } from "@protobuf-ts/runtime";
|
||||
import { ProtoField } from "@napneko/nap-proto-core";
|
||||
|
||||
export const OidbSvcTrpcTcpBase = {
|
||||
command: ProtoField(1, ScalarType.UINT32),
|
||||
subCommand: ProtoField(2, ScalarType.UINT32),
|
||||
errorCode: ProtoField(3, 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 "@napneko/nap-proto-core";
|
||||
|
||||
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 "@napneko/nap-proto-core";
|
||||
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) });
|
||||
}
|
73
src/core/packet/session.ts
Normal file
73
src/core/packet/session.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import { PacketHighwaySession } from "@/core/packet/highway/session";
|
||||
import { LogWrapper } from "@/common/log";
|
||||
import { PacketPacker } from "@/core/packet/packer";
|
||||
import { PacketClient } from "@/core/packet/client/client";
|
||||
import { NativePacketClient } from "@/core/packet/client/nativeClient";
|
||||
import { wsPacketClient } from "@/core/packet/client/wsClient";
|
||||
import { NapCatCore } from "@/core";
|
||||
|
||||
type clientPriority = {
|
||||
[key: number]: (core: NapCatCore) => PacketClient;
|
||||
}
|
||||
|
||||
const clientPriority: clientPriority = {
|
||||
10: (core: NapCatCore) => new NativePacketClient(core),
|
||||
1: (core: NapCatCore) => new wsPacketClient(core),
|
||||
};
|
||||
|
||||
export class PacketSession {
|
||||
readonly logger: LogWrapper;
|
||||
readonly client: PacketClient ;
|
||||
readonly packer: PacketPacker;
|
||||
readonly highwaySession: PacketHighwaySession;
|
||||
|
||||
constructor(core: NapCatCore) {
|
||||
this.logger = core.context.logger;
|
||||
this.client = this.newClient(core);
|
||||
this.packer = new PacketPacker(this.logger, this.client);
|
||||
this.highwaySession = new PacketHighwaySession(this.logger, this.client, this.packer);
|
||||
}
|
||||
|
||||
private newClient(core: NapCatCore): PacketClient {
|
||||
const prefer = core.configLoader.configData.packetBackend;
|
||||
let client: PacketClient | null;
|
||||
switch (prefer) {
|
||||
case "native":
|
||||
this.logger.log("[Core] [Packet] 使用指定的 NativePacketClient 作为后端");
|
||||
client = new NativePacketClient(core);
|
||||
break;
|
||||
case "frida":
|
||||
this.logger.log("[Core] [Packet] 使用指定的 FridaPacketClient 作为后端");
|
||||
client = new wsPacketClient(core);
|
||||
break;
|
||||
case "auto":
|
||||
case undefined:
|
||||
client = this.judgeClient(core);
|
||||
break;
|
||||
default:
|
||||
this.logger.logError(`[Core] [Packet] 未知的PacketBackend ${prefer},请检查配置文件!`);
|
||||
client = null;
|
||||
}
|
||||
if (!(client && client.check(core))) {
|
||||
throw new Error("[Core] [Packet] 无可用的后端,NapCat.Packet将不会加载!");
|
||||
}
|
||||
return client;
|
||||
}
|
||||
|
||||
private judgeClient(core: NapCatCore): PacketClient {
|
||||
const sortedClients = Object.entries(clientPriority)
|
||||
.map(([priority, clientFactory]) => {
|
||||
const client = clientFactory(core);
|
||||
const score = +priority * +client.check(core);
|
||||
return { client, score };
|
||||
})
|
||||
.filter(({ score }) => score > 0)
|
||||
.sort((a, b) => b.score - a.score);
|
||||
const selectedClient = sortedClients[0]?.client;
|
||||
if (!selectedClient) {
|
||||
throw new Error("[Core] [Packet] 无可用的后端,NapCat.Packet将不会加载!");
|
||||
}
|
||||
this.logger.log(`[Core] [Packet] 自动选择 ${selectedClient.constructor.name} 作为后端`);
|
||||
return selectedClient;
|
||||
}
|
||||
}
|
47
src/core/packet/utils/crypto/hash.ts
Normal file
47
src/core/packet/utils/crypto/hash.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
// 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';
|
||||
import { CalculateStreamBytesTransform } from "@/core/packet/utils/crypto/sha1StreamBytesTransform";
|
||||
|
||||
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>;
|
||||
}
|
||||
|
||||
function md5Stream(readable: stream.Readable) {
|
||||
return new Promise((resolve, reject) => {
|
||||
readable.on('error', reject);
|
||||
readable.pipe(crypto.createHash('md5').on('error', reject).on('data', resolve));
|
||||
}) as Promise<Buffer>;
|
||||
}
|
||||
|
||||
export function calculateSha1(filePath: string): Promise<Buffer> {
|
||||
const readable = fs.createReadStream(filePath);
|
||||
return sha1Stream(readable);
|
||||
}
|
||||
|
||||
export function computeMd5AndLengthWithLimit(filePath: string, limit?: number): Promise<Buffer> {
|
||||
const readStream = fs.createReadStream(filePath, limit ? { start: 0, end: limit - 1 } : {});
|
||||
return md5Stream(readStream);
|
||||
}
|
||||
|
||||
export function calculateSha1StreamBytes(filePath: string): Promise<Buffer[]> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const readable = fs.createReadStream(filePath);
|
||||
const calculateStreamBytes = new CalculateStreamBytesTransform();
|
||||
const byteArrayList: Buffer[] = [];
|
||||
calculateStreamBytes.on('data', (chunk: Buffer) => {
|
||||
byteArrayList.push(chunk);
|
||||
});
|
||||
calculateStreamBytes.on('end', () => {
|
||||
resolve(byteArrayList);
|
||||
});
|
||||
calculateStreamBytes.on('error', (err) => {
|
||||
reject(err);
|
||||
});
|
||||
readable.pipe(calculateStreamBytes);
|
||||
});
|
||||
}
|
19
src/core/packet/utils/crypto/sha1Stream.test.ts
Normal file
19
src/core/packet/utils/crypto/sha1Stream.test.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import crypto from 'crypto';
|
||||
import assert from 'assert';
|
||||
import { Sha1Stream } from './sha1Stream';
|
||||
|
||||
function testSha1Stream() {
|
||||
for (let i = 0; i < 100000; i++) {
|
||||
const randomLength = Math.floor(Math.random() * 1024);
|
||||
const randomData = crypto.randomBytes(randomLength);
|
||||
const sha1Stream = new Sha1Stream();
|
||||
sha1Stream.update(randomData);
|
||||
const hash = sha1Stream.final();
|
||||
const expectedDigest = crypto.createHash('sha1').update(randomData).digest();
|
||||
assert.strictEqual(hash.toString('hex'), expectedDigest.toString('hex'));
|
||||
console.log(`Test ${i + 1}: Passed`);
|
||||
}
|
||||
console.log('All tests passed successfully.');
|
||||
}
|
||||
|
||||
testSha1Stream();
|
118
src/core/packet/utils/crypto/sha1Stream.ts
Normal file
118
src/core/packet/utils/crypto/sha1Stream.ts
Normal file
@@ -0,0 +1,118 @@
|
||||
export class Sha1Stream {
|
||||
readonly Sha1BlockSize = 64;
|
||||
readonly Sha1DigestSize = 20;
|
||||
private readonly _padding = Buffer.concat([Buffer.from([0x80]), Buffer.alloc(63)]);
|
||||
private readonly _state = new Uint32Array(5);
|
||||
private readonly _count = new Uint32Array(2);
|
||||
private readonly _buffer = Buffer.allocUnsafe(this.Sha1BlockSize);
|
||||
private readonly _w = new Uint32Array(80);
|
||||
|
||||
constructor() {
|
||||
this.reset();
|
||||
}
|
||||
|
||||
private reset(): void {
|
||||
this._state[0] = 0x67452301;
|
||||
this._state[1] = 0xEFCDAB89;
|
||||
this._state[2] = 0x98BADCFE;
|
||||
this._state[3] = 0x10325476;
|
||||
this._state[4] = 0xC3D2E1F0;
|
||||
this._count[0] = 0;
|
||||
this._count[1] = 0;
|
||||
this._buffer.fill(0);
|
||||
}
|
||||
|
||||
private rotateLeft(v: number, o: number): number {
|
||||
return ((v << o) | (v >>> (32 - o))) >>> 0;
|
||||
}
|
||||
|
||||
private transform(chunk: Buffer, offset: number): void {
|
||||
const w = this._w;
|
||||
const view = new DataView(chunk.buffer, chunk.byteOffset + offset, 64);
|
||||
|
||||
for (let i = 0; i < 16; i++) {
|
||||
w[i] = view.getUint32(i * 4, false);
|
||||
}
|
||||
|
||||
for (let i = 16; i < 80; i++) {
|
||||
w[i] = this.rotateLeft(w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16], 1) >>> 0;
|
||||
}
|
||||
|
||||
let a = this._state[0];
|
||||
let b = this._state[1];
|
||||
let c = this._state[2];
|
||||
let d = this._state[3];
|
||||
let e = this._state[4];
|
||||
|
||||
for (let i = 0; i < 80; i++) {
|
||||
const [f, k] = (i < 20) ? [(b & c) | ((~b) & d), 0x5A827999] :
|
||||
(i < 40) ? [b ^ c ^ d, 0x6ED9EBA1] :
|
||||
(i < 60) ? [(b & c) | (b & d) | (c & d), 0x8F1BBCDC] :
|
||||
[b ^ c ^ d, 0xCA62C1D6];
|
||||
const temp = (this.rotateLeft(a, 5) + f + k + e + w[i]) >>> 0;
|
||||
e = d;
|
||||
d = c;
|
||||
c = this.rotateLeft(b, 30) >>> 0;
|
||||
b = a;
|
||||
a = temp;
|
||||
}
|
||||
|
||||
this._state[0] = (this._state[0] + a) >>> 0;
|
||||
this._state[1] = (this._state[1] + b) >>> 0;
|
||||
this._state[2] = (this._state[2] + c) >>> 0;
|
||||
this._state[3] = (this._state[3] + d) >>> 0;
|
||||
this._state[4] = (this._state[4] + e) >>> 0;
|
||||
}
|
||||
|
||||
public update(data: Buffer, len?: number): void {
|
||||
let index = ((this._count[0] >>> 3) & 0x3F) >>> 0;
|
||||
const dataLen = len ?? data.length;
|
||||
this._count[0] = (this._count[0] + (dataLen << 3)) >>> 0;
|
||||
|
||||
if (this._count[0] < (dataLen << 3)) this._count[1] = (this._count[1] + 1) >>> 0;
|
||||
|
||||
this._count[1] = (this._count[1] + (dataLen >>> 29)) >>> 0;
|
||||
|
||||
const partLen = (this.Sha1BlockSize - index) >>> 0;
|
||||
let i = 0;
|
||||
|
||||
if (dataLen >= partLen) {
|
||||
data.copy(this._buffer, index, 0, partLen);
|
||||
this.transform(this._buffer, 0);
|
||||
for (i = partLen; (i + this.Sha1BlockSize) <= dataLen; i = (i + this.Sha1BlockSize) >>> 0) {
|
||||
this.transform(data, i);
|
||||
}
|
||||
index = 0;
|
||||
}
|
||||
|
||||
data.copy(this._buffer, index, i, dataLen);
|
||||
}
|
||||
|
||||
public hash(bigEndian: boolean = true): Buffer {
|
||||
const digest = Buffer.allocUnsafe(this.Sha1DigestSize);
|
||||
if (bigEndian) {
|
||||
for (let i = 0; i < 5; i++) digest.writeUInt32BE(this._state[i], i * 4);
|
||||
} else {
|
||||
for (let i = 0; i < 5; i++) digest.writeUInt32LE(this._state[i], i * 4);
|
||||
}
|
||||
return digest;
|
||||
}
|
||||
|
||||
public final(): Buffer {
|
||||
const digest = Buffer.allocUnsafe(this.Sha1DigestSize);
|
||||
const bits = Buffer.allocUnsafe(8);
|
||||
bits.writeUInt32BE(this._count[1], 0);
|
||||
bits.writeUInt32BE(this._count[0], 4);
|
||||
|
||||
const index = ((this._count[0] >>> 3) & 0x3F) >>> 0;
|
||||
const padLen = ((index < 56) ? (56 - index) : (120 - index)) >>> 0;
|
||||
this.update(this._padding, padLen);
|
||||
this.update(bits);
|
||||
|
||||
for (let i = 0; i < 5; i++) {
|
||||
digest.writeUInt32BE(this._state[i], i * 4);
|
||||
}
|
||||
|
||||
return digest;
|
||||
}
|
||||
}
|
53
src/core/packet/utils/crypto/sha1StreamBytesTransform.ts
Normal file
53
src/core/packet/utils/crypto/sha1StreamBytesTransform.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import * as stream from "node:stream";
|
||||
import { Sha1Stream } from "@/core/packet/utils/crypto/sha1Stream";
|
||||
|
||||
export class CalculateStreamBytesTransform extends stream.Transform {
|
||||
private readonly blockSize = 1024 * 1024;
|
||||
private sha1: Sha1Stream;
|
||||
private buffer: Buffer;
|
||||
private bytesRead: number;
|
||||
private readonly byteArrayList: Buffer[];
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.sha1 = new Sha1Stream();
|
||||
this.buffer = Buffer.alloc(0);
|
||||
this.bytesRead = 0;
|
||||
this.byteArrayList = [];
|
||||
}
|
||||
|
||||
_transform(chunk: Buffer, _: BufferEncoding, callback: stream.TransformCallback): void {
|
||||
try {
|
||||
this.buffer = Buffer.concat([this.buffer, chunk]);
|
||||
let offset = 0;
|
||||
while (this.buffer.length - offset >= this.sha1.Sha1BlockSize) {
|
||||
const block = this.buffer.subarray(offset, offset + this.sha1.Sha1BlockSize);
|
||||
this.sha1.update(block);
|
||||
offset += this.sha1.Sha1BlockSize;
|
||||
this.bytesRead += this.sha1.Sha1BlockSize;
|
||||
if (this.bytesRead % this.blockSize === 0) {
|
||||
const digest = this.sha1.hash(false);
|
||||
this.byteArrayList.push(Buffer.from(digest));
|
||||
}
|
||||
}
|
||||
this.buffer = this.buffer.subarray(offset);
|
||||
callback(null);
|
||||
} catch (err) {
|
||||
callback(err as Error);
|
||||
}
|
||||
}
|
||||
|
||||
_flush(callback: stream.TransformCallback): void {
|
||||
try {
|
||||
if (this.buffer.length > 0) this.sha1.update(this.buffer);
|
||||
const finalDigest = this.sha1.final();
|
||||
this.byteArrayList.push(Buffer.from(finalDigest));
|
||||
for (const digest of this.byteArrayList) {
|
||||
this.push(digest);
|
||||
}
|
||||
callback(null);
|
||||
} catch (err) {
|
||||
callback(err as Error);
|
||||
}
|
||||
}
|
||||
}
|
86
src/core/packet/utils/crypto/tea.ts
Normal file
86
src/core/packet/utils/crypto/tea.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
// love from https://github.com/LagrangeDev/lagrangejs/blob/main/src/core/tea.ts & https://github.com/takayama-lily/oicq/blob/main/lib/core/tea.ts
|
||||
const BUF7 = Buffer.alloc(7);
|
||||
const deltas = [
|
||||
0x9e3779b9, 0x3c6ef372, 0xdaa66d2b, 0x78dde6e4, 0x1715609d, 0xb54cda56, 0x5384540f, 0xf1bbcdc8, 0x8ff34781,
|
||||
0x2e2ac13a, 0xcc623af3, 0x6a99b4ac, 0x08d12e65, 0xa708a81e, 0x454021d7, 0xe3779b90,
|
||||
];
|
||||
|
||||
function _toUInt32(num: number) {
|
||||
return num >>> 0;
|
||||
}
|
||||
|
||||
function _encrypt(x: number, y: number, k0: number, k1: number, k2: number, k3: number): [number, number] {
|
||||
for (let i = 0; i < 16; ++i) {
|
||||
let aa = ((_toUInt32(((y << 4) >>> 0) + k0) ^ _toUInt32(y + deltas[i])) >>> 0) ^ _toUInt32(~~(y / 32) + k1);
|
||||
aa >>>= 0;
|
||||
x = _toUInt32(x + aa);
|
||||
let bb = ((_toUInt32(((x << 4) >>> 0) + k2) ^ _toUInt32(x + deltas[i])) >>> 0) ^ _toUInt32(~~(x / 32) + k3);
|
||||
bb >>>= 0;
|
||||
y = _toUInt32(y + bb);
|
||||
}
|
||||
return [x, y];
|
||||
}
|
||||
|
||||
export function encrypt(data: Buffer, key: Buffer) {
|
||||
let n = (6 - data.length) >>> 0;
|
||||
n = (n % 8) + 2;
|
||||
const v = Buffer.concat([Buffer.from([(n - 2) | 0xf8]), Buffer.allocUnsafe(n), data, BUF7]);
|
||||
const k0 = key.readUInt32BE(0);
|
||||
const k1 = key.readUInt32BE(4);
|
||||
const k2 = key.readUInt32BE(8);
|
||||
const k3 = key.readUInt32BE(12);
|
||||
let r1 = 0, r2 = 0, t1 = 0, t2 = 0;
|
||||
for (let i = 0; i < v.length; i += 8) {
|
||||
const a1 = v.readUInt32BE(i);
|
||||
const a2 = v.readUInt32BE(i + 4);
|
||||
const b1 = a1 ^ r1;
|
||||
const b2 = a2 ^ r2;
|
||||
const [x, y] = _encrypt(b1 >>> 0, b2 >>> 0, k0, k1, k2, k3);
|
||||
r1 = x ^ t1;
|
||||
r2 = y ^ t2;
|
||||
t1 = b1;
|
||||
t2 = b2;
|
||||
v.writeInt32BE(r1, i);
|
||||
v.writeInt32BE(r2, i + 4);
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
function _decrypt(x: number, y: number, k0: number, k1: number, k2: number, k3: number) {
|
||||
for (let i = 15; i >= 0; --i) {
|
||||
const aa = ((_toUInt32(((x << 4) >>> 0) + k2) ^ _toUInt32(x + deltas[i])) >>> 0) ^ _toUInt32(~~(x / 32) + k3);
|
||||
y = (y - aa) >>> 0;
|
||||
const bb = ((_toUInt32(((y << 4) >>> 0) + k0) ^ _toUInt32(y + deltas[i])) >>> 0) ^ _toUInt32(~~(y / 32) + k1);
|
||||
x = (x - bb) >>> 0;
|
||||
}
|
||||
return [x, y];
|
||||
}
|
||||
|
||||
export function decrypt(encrypted: Buffer, key: Buffer) {
|
||||
if (encrypted.length % 8) throw ERROR_ENCRYPTED_LENGTH;
|
||||
const k0 = key.readUInt32BE(0);
|
||||
const k1 = key.readUInt32BE(4);
|
||||
const k2 = key.readUInt32BE(8);
|
||||
const k3 = key.readUInt32BE(12);
|
||||
let r1 = 0, r2 = 0, t1 = 0, t2 = 0, x = 0, y = 0;
|
||||
for (let i = 0; i < encrypted.length; i += 8) {
|
||||
const a1 = encrypted.readUInt32BE(i);
|
||||
const a2 = encrypted.readUInt32BE(i + 4);
|
||||
const b1 = a1 ^ x;
|
||||
const b2 = a2 ^ y;
|
||||
[x, y] = _decrypt(b1 >>> 0, b2 >>> 0, k0, k1, k2, k3);
|
||||
r1 = x ^ t1;
|
||||
r2 = y ^ t2;
|
||||
t1 = a1;
|
||||
t2 = a2;
|
||||
encrypted.writeInt32BE(r1, i);
|
||||
encrypted.writeInt32BE(r2, i + 4);
|
||||
}
|
||||
if (Buffer.compare(encrypted.subarray(encrypted.length - 7), BUF7) !== 0) throw ERROR_ENCRYPTED_ILLEGAL;
|
||||
// if (Buffer.compare(encrypted.slice(encrypted.length - 7), BUF7) !== 0) throw ERROR_ENCRYPTED_ILLEGAL;
|
||||
return encrypted.subarray((encrypted[0] & 0x07) + 3, encrypted.length - 7);
|
||||
// return encrypted.slice((encrypted[0] & 0x07) + 3, encrypted.length - 7);
|
||||
}
|
||||
|
||||
const ERROR_ENCRYPTED_LENGTH = new Error('length of encrypted data must be a multiple of 8');
|
||||
const ERROR_ENCRYPTED_ILLEGAL = new Error('encrypted data is illegal');
|
@@ -1,95 +0,0 @@
|
||||
import * as pb from 'protobufjs';
|
||||
|
||||
// Proto: from src/core/proto/ProfileLike.proto
|
||||
// Author: Mlikiowa
|
||||
|
||||
export interface LikeDetailType {
|
||||
txt: string;
|
||||
uin: pb.Long;
|
||||
nickname: string;
|
||||
}
|
||||
export interface LikeMsgType {
|
||||
times: number;
|
||||
time: number;
|
||||
detail: LikeDetailType;
|
||||
}
|
||||
|
||||
export interface profileLikeSubTipType {
|
||||
msg: LikeMsgType;
|
||||
}
|
||||
|
||||
export interface ProfileLikeTipType {
|
||||
msgType: number;
|
||||
subType: number;
|
||||
content: profileLikeSubTipType;
|
||||
}
|
||||
export interface SysMessageHeaderType {
|
||||
id: string;
|
||||
timestamp: number;
|
||||
sender: string;
|
||||
}
|
||||
|
||||
export interface SysMessageMsgSpecType {
|
||||
msgType: number;
|
||||
subType: number;
|
||||
subSubType: number;
|
||||
msgSeq: number;
|
||||
time: number;
|
||||
msgId: pb.Long;
|
||||
other: number;
|
||||
}
|
||||
export interface SysMessageBodyWrapperType {
|
||||
wrappedBody: Uint8Array;
|
||||
}
|
||||
export interface SysMessageType {
|
||||
header: SysMessageHeaderType[];
|
||||
msgSpec: SysMessageMsgSpecType[];
|
||||
bodyWrapper: SysMessageBodyWrapperType;
|
||||
}
|
||||
|
||||
export const SysMessageHeader = new pb.Type("SysMessageHeader")
|
||||
.add(new pb.Field("PeerNumber", 1, "uint32"))
|
||||
.add(new pb.Field("PeerString", 2, "string"))
|
||||
.add(new pb.Field("Uin", 5, "uint32"))
|
||||
.add(new pb.Field("Uid", 6, "string", "optional"));
|
||||
|
||||
export const SysMessageMsgSpec = new pb.Type("SysMessageMsgSpec")
|
||||
.add(new pb.Field("msgType", 1, "uint32"))
|
||||
.add(new pb.Field("subType", 2, "uint32"))
|
||||
.add(new pb.Field("subSubType", 3, "uint32"))
|
||||
.add(new pb.Field("msgSeq", 5, "uint32"))
|
||||
.add(new pb.Field("time", 6, "uint32"))
|
||||
.add(new pb.Field("msgId", 12, "uint64"))
|
||||
.add(new pb.Field("other", 13, "uint32"));
|
||||
|
||||
export const SysMessageBodyWrapper = new pb.Type("SysMessageBodyWrapper")
|
||||
.add(new pb.Field("wrappedBody", 2, "bytes"));
|
||||
|
||||
export const SysMessage = new pb.Type("SysMessage")
|
||||
.add(SysMessageHeader)
|
||||
.add(SysMessageMsgSpec)
|
||||
.add(SysMessageBodyWrapper)
|
||||
.add(new pb.Field("header", 1, "SysMessageHeader", "repeated"))
|
||||
.add(new pb.Field("msgSpec", 2, "SysMessageMsgSpec", "repeated"))
|
||||
.add(new pb.Field("bodyWrapper", 3, "SysMessageBodyWrapper"));
|
||||
|
||||
export const likeDetail = new pb.Type("likeDetail")
|
||||
.add(new pb.Field("txt", 1, "string"))
|
||||
.add(new pb.Field("uin", 3, "int64"))
|
||||
.add(new pb.Field("nickname", 5, "string"));
|
||||
|
||||
export const likeMsg = new pb.Type("likeMsg")
|
||||
.add(likeDetail)
|
||||
.add(new pb.Field("times", 1, "int32"))
|
||||
.add(new pb.Field("time", 2, "int32"))
|
||||
.add(new pb.Field("detail", 3, "likeDetail"));
|
||||
|
||||
export const profileLikeSubTip = new pb.Type("profileLikeSubTip")
|
||||
.add(likeMsg)
|
||||
.add(new pb.Field("msg", 14, "likeMsg"))
|
||||
|
||||
export const profileLikeTip = new pb.Type("profileLikeTip")
|
||||
.add(profileLikeSubTip)
|
||||
.add(new pb.Field("msgType", 1, "int32"))
|
||||
.add(new pb.Field("subType", 2, "int32"))
|
||||
.add(new pb.Field("content", 203, "profileLikeSubTip"));
|
@@ -36,7 +36,7 @@ export interface NodeIKernelBuddyService {
|
||||
|
||||
getBuddyRemark(uid: number): string;
|
||||
|
||||
setBuddyRemark(uid: number, remark: string): void;
|
||||
setBuddyRemark(param: { uid: string, remark: string, signInfo?: unknown }): void;
|
||||
|
||||
getAvatarUrl(uid: number): string;
|
||||
|
||||
@@ -66,7 +66,11 @@ export interface NodeIKernelBuddyService {
|
||||
accept: boolean;
|
||||
}): Promise<void>;
|
||||
|
||||
delBuddy(uid: number): void;
|
||||
delBuddy(param: {
|
||||
friendUid: string;
|
||||
tempBlock: boolean;
|
||||
tempBothDel: boolean;
|
||||
}): Promise<unknown>;
|
||||
|
||||
delBatchBuddy(uids: number[]): void;
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user