mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2025-07-19 12:03:37 +00:00
Compare commits
1336 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
a12ea0e761 | ||
![]() |
c9e3bbcd9f | ||
![]() |
9c17dc1b8f | ||
![]() |
69d1cae686 | ||
![]() |
1c2404b6af | ||
![]() |
b33b33739d | ||
![]() |
2b7886c682 | ||
![]() |
106d1f6374 | ||
![]() |
e601786bd7 | ||
![]() |
fda2a98b40 | ||
![]() |
c01d70b8fc | ||
![]() |
eccbcc3e28 | ||
![]() |
7a4a255a89 | ||
![]() |
83bced82b1 | ||
![]() |
f3033ce732 | ||
![]() |
5c21a1727c | ||
![]() |
93aab437b7 | ||
![]() |
34e797270f | ||
![]() |
0f337a8d8c | ||
![]() |
cc9b83089e | ||
![]() |
a565929686 | ||
![]() |
6adacea774 | ||
![]() |
47ab5421ed | ||
![]() |
10c404d455 | ||
![]() |
dfdca11155 | ||
![]() |
698e095364 | ||
![]() |
524fd258d8 | ||
![]() |
17e70a4360 | ||
![]() |
e4a533e7b7 | ||
![]() |
0cb68d3737 | ||
![]() |
9faeadbebe | ||
![]() |
35d201cfb8 | ||
![]() |
205174255f | ||
![]() |
8873a030ab | ||
![]() |
0ab61bac12 | ||
![]() |
b1157f60f5 | ||
![]() |
bb93df06b2 | ||
![]() |
82e807fd80 | ||
![]() |
29da539467 | ||
![]() |
659aa005b0 | ||
![]() |
3f20733e7e | ||
![]() |
b15e1174d6 | ||
![]() |
05b05fd74e | ||
![]() |
d30d467a21 | ||
![]() |
cd62e8ca37 | ||
![]() |
f9e44820c1 | ||
![]() |
169ae6a4d0 | ||
![]() |
030ba15952 | ||
![]() |
964874bdad | ||
![]() |
7affa081ac | ||
![]() |
10e281ed35 | ||
![]() |
27081ae599 | ||
![]() |
61cbcdffe8 | ||
![]() |
eeb15ea564 | ||
![]() |
565c820925 | ||
![]() |
325dff5735 | ||
![]() |
397c2cf5f0 | ||
![]() |
1fbc339a42 | ||
![]() |
f2c719c60d | ||
![]() |
08505fcc9a | ||
![]() |
a79c933693 | ||
![]() |
b4cb3ddf1c | ||
![]() |
aa188a6e89 | ||
![]() |
a04b6b8a70 | ||
![]() |
11149d2743 | ||
![]() |
86bfd990db | ||
![]() |
9304430889 | ||
![]() |
095f1c270b | ||
![]() |
d3f91a832b | ||
![]() |
4790a1170f | ||
![]() |
501c392028 | ||
![]() |
9200520f70 | ||
![]() |
8122561337 | ||
![]() |
c6dc86ef8d | ||
![]() |
bea3b8485f | ||
![]() |
b807b89cdc | ||
![]() |
daac2f7fd9 | ||
![]() |
f0a5523174 | ||
![]() |
eda8fbb178 | ||
![]() |
67ca6184e9 | ||
![]() |
d79e91fc1e | ||
![]() |
1cdb93baa2 | ||
![]() |
f91991e25c | ||
![]() |
d21da47a7d | ||
![]() |
b4e22a345d | ||
![]() |
30e594ae5f | ||
![]() |
ffba3573ba | ||
![]() |
9df5bee8d3 | ||
![]() |
71c0728622 | ||
![]() |
476d8ba14d | ||
![]() |
274c956f16 | ||
![]() |
3068f9ee3d | ||
![]() |
a0c49d5f7f | ||
![]() |
a8534974fe | ||
![]() |
c517790391 | ||
![]() |
b7e875c77f | ||
![]() |
befd9c0624 | ||
![]() |
7a46f11089 | ||
![]() |
dc168bf8b9 | ||
![]() |
eef5293ca0 | ||
![]() |
a2c4498694 | ||
![]() |
938a84a460 | ||
![]() |
978d2c24ee | ||
![]() |
cdd00d665d | ||
![]() |
bb8b06c044 | ||
![]() |
604c5dcdc1 | ||
![]() |
6bc2ecdbf0 | ||
![]() |
e91c81def7 | ||
![]() |
bedd2fa15a | ||
![]() |
50465eef54 | ||
![]() |
07689adfcd | ||
![]() |
8f4f898675 | ||
![]() |
968bd7a437 | ||
![]() |
eba5900ba8 | ||
![]() |
69c477b104 | ||
![]() |
c8df8f4f54 | ||
![]() |
d35a19b4fd | ||
![]() |
a97437a6e5 | ||
![]() |
39c4473367 | ||
![]() |
b882bc721d | ||
![]() |
405cace489 | ||
![]() |
402a7b7fc9 | ||
![]() |
8ad805e654 | ||
![]() |
b23c357f73 | ||
![]() |
f561c2b0fa | ||
![]() |
5a8eea668f | ||
![]() |
777143e502 | ||
![]() |
0d8c9a82fe | ||
![]() |
d10ab1cce3 | ||
![]() |
ec25e09d73 | ||
![]() |
cba9c78ab1 | ||
![]() |
c32db4a881 | ||
![]() |
871add3071 | ||
![]() |
e661c617a3 | ||
![]() |
d4bf721540 | ||
![]() |
d91b55faed | ||
![]() |
9687832d4d | ||
![]() |
fc3e436744 | ||
![]() |
da90245f7b | ||
![]() |
410d6a85d7 | ||
![]() |
b693342e4f | ||
![]() |
acca361f2e | ||
![]() |
b663f47713 | ||
![]() |
d332b199b5 | ||
![]() |
78bac1dbd1 | ||
![]() |
724ff215f9 | ||
![]() |
68ea146469 | ||
![]() |
82583e616f | ||
![]() |
bfc339c58d | ||
![]() |
fe4427c076 | ||
![]() |
5745f388a9 | ||
![]() |
377e3c253f | ||
![]() |
3007a0c00e | ||
![]() |
f51ffc091d | ||
![]() |
c37c364a08 | ||
![]() |
331a106e9a | ||
![]() |
cd74687b7b | ||
![]() |
b3e145c1e6 | ||
![]() |
d8e1547736 | ||
![]() |
8617f01924 | ||
![]() |
55f9e75e6a | ||
![]() |
b93e7b7ed1 | ||
![]() |
89cc79ad60 | ||
![]() |
8dd0e60eea | ||
![]() |
df6113fdf6 | ||
![]() |
3a3095d15a | ||
![]() |
fb4d07391e | ||
![]() |
9bef9c85cf | ||
![]() |
b77b3f227f | ||
![]() |
6a065f0a34 | ||
![]() |
4e1e190797 | ||
![]() |
1ce8cd2100 | ||
![]() |
c03af6b9ad | ||
![]() |
adca850075 | ||
![]() |
e3616b484e | ||
![]() |
cfd7808169 | ||
![]() |
addcedc588 | ||
![]() |
bfea786088 | ||
![]() |
50e84c3c9e | ||
![]() |
dc92ace85e | ||
![]() |
1a543928b1 | ||
![]() |
652fe8d21e | ||
![]() |
199690f45f | ||
![]() |
37a4dd4b00 | ||
![]() |
34d4358bfc | ||
![]() |
90906b9019 | ||
![]() |
1c212ff2b4 | ||
![]() |
7d709f44a8 | ||
![]() |
ea9e88a18a | ||
![]() |
0be8a9c805 | ||
![]() |
fcf8139afe | ||
![]() |
62f969b50b | ||
![]() |
6726062500 | ||
![]() |
cf1f4bdcaf | ||
![]() |
b09a14ad4e | ||
![]() |
1dc62c9ca3 | ||
![]() |
beaa89a2dc | ||
![]() |
f39a000b49 | ||
![]() |
013a74fb14 | ||
![]() |
7c4964753b | ||
![]() |
8353533d60 | ||
![]() |
c06df27424 | ||
![]() |
ad82919ddf | ||
![]() |
44dbba17e1 | ||
![]() |
5ba110e1da | ||
![]() |
b6e392fdb2 | ||
![]() |
2280e83aa2 | ||
![]() |
f49b94edb9 | ||
![]() |
2428a12221 | ||
![]() |
9c353f3760 | ||
![]() |
5b86d25d7f | ||
![]() |
2b168e8bbc | ||
![]() |
537db32847 | ||
![]() |
498b7f9f2b | ||
![]() |
9935568597 | ||
![]() |
467003af8c | ||
![]() |
4c9edcc47b | ||
![]() |
24bf9cf121 | ||
![]() |
e06f6f39a9 | ||
![]() |
98ee0c307b | ||
![]() |
5e53ea0bc3 | ||
![]() |
847d88ea77 | ||
![]() |
d5046cc2b3 | ||
![]() |
3ad64b7cbb | ||
![]() |
0dbfe8ca55 | ||
![]() |
91b794d66d | ||
![]() |
0d65e1e314 | ||
![]() |
2d8f58c6d8 | ||
![]() |
65888fa816 | ||
![]() |
857e882c6e | ||
![]() |
add2931834 | ||
![]() |
cdda5f45ee | ||
![]() |
5f73d6a913 | ||
![]() |
0637882fbc | ||
![]() |
3f785bab20 | ||
![]() |
a4ca89bdd6 | ||
![]() |
1a64e796bd | ||
![]() |
a8b85a34f7 | ||
![]() |
e7bec7d6b0 | ||
![]() |
a582026037 | ||
![]() |
1a67a001c5 | ||
![]() |
406deac592 | ||
![]() |
e719ae0676 | ||
![]() |
d8b7726440 | ||
![]() |
49f642e712 | ||
![]() |
70117016ce | ||
![]() |
a4738f6281 | ||
![]() |
b1fc72d696 | ||
![]() |
457c2c2b50 | ||
![]() |
48848d7d1a | ||
![]() |
55b07ca3ab | ||
![]() |
a1d4882e18 | ||
![]() |
3843795d8f | ||
![]() |
f2bf8d42da | ||
![]() |
a3b244e114 | ||
![]() |
3093bdbc68 | ||
![]() |
9ab0799283 | ||
![]() |
236bec11ed | ||
![]() |
de48b0f940 | ||
![]() |
4885d4db86 | ||
![]() |
0c7bbda936 | ||
![]() |
fa07c2c1fb | ||
![]() |
5d17a191f6 | ||
![]() |
67fb74d3c2 | ||
![]() |
dc04cfc1b3 | ||
![]() |
d61d481965 | ||
![]() |
6b346ee1de | ||
![]() |
d0f248aaf9 | ||
![]() |
85c9227515 | ||
![]() |
73b6d3be84 | ||
![]() |
1ff6ce2343 | ||
![]() |
c145935d46 | ||
![]() |
e9ede6924e | ||
![]() |
515a21761d | ||
![]() |
8d6397028b | ||
![]() |
eb4828d81f | ||
![]() |
7e74578312 | ||
![]() |
640e3516d4 | ||
![]() |
bd295a4632 | ||
![]() |
166c30fe2c | ||
![]() |
66c1bab629 | ||
![]() |
66656304f9 | ||
![]() |
07f66e379d | ||
![]() |
7ae8fd60c4 | ||
![]() |
7275066994 | ||
![]() |
385adec186 | ||
![]() |
96b5bec5ab | ||
![]() |
6a9ec4e5f0 | ||
![]() |
d9851493df | ||
![]() |
efdb520414 | ||
![]() |
5548644aeb | ||
![]() |
e3fcd91b2d | ||
![]() |
2cae30ba88 | ||
![]() |
58cd38c4a8 | ||
![]() |
3300304feb | ||
![]() |
f0e376d06b | ||
![]() |
16f7bb48f2 | ||
![]() |
7f383dd29b | ||
![]() |
3dc529edf4 | ||
![]() |
45dedb4872 | ||
![]() |
afcdd01c0d | ||
![]() |
1164877e9a | ||
![]() |
fe92a449ba | ||
![]() |
401b0e2bd0 | ||
![]() |
cf9c71fcc1 | ||
![]() |
15a2400069 | ||
![]() |
d68a39b49e | ||
![]() |
066ca22e24 | ||
![]() |
0418b926fe | ||
![]() |
be40bbdf40 | ||
![]() |
df4f42e79e | ||
![]() |
5f80058f70 | ||
![]() |
0cbe59052d | ||
![]() |
af28a26e37 | ||
![]() |
70c596df93 | ||
![]() |
748b51428c | ||
![]() |
8ad746397c | ||
![]() |
45baed2f9a | ||
![]() |
74185f2d33 | ||
![]() |
90a91e4105 | ||
![]() |
11aa3a0315 | ||
![]() |
0c2e39214f | ||
![]() |
d89620d7a6 | ||
![]() |
edf80775b7 | ||
![]() |
46e56ac726 | ||
![]() |
40b2f6bfd6 | ||
![]() |
911e4921e2 | ||
![]() |
1db9bb419d | ||
![]() |
c6241a94e3 | ||
![]() |
1cbf75ca36 | ||
![]() |
8f85c897c8 | ||
![]() |
29c31b7aba | ||
![]() |
402919d6f2 | ||
![]() |
82608dd5ff | ||
![]() |
f312368df2 | ||
![]() |
374fc64427 | ||
![]() |
95bd74bb0d | ||
![]() |
a9f5069649 | ||
![]() |
957f7ffd8d | ||
![]() |
336dd3ce10 | ||
![]() |
47a7295477 | ||
![]() |
341a0e1c2a | ||
![]() |
c4f73d0eb8 | ||
![]() |
bd9258bae4 | ||
![]() |
e3b3260aa0 | ||
![]() |
676766c99e | ||
![]() |
1025a07593 | ||
![]() |
00c3fcd033 | ||
![]() |
b8457d4aff | ||
![]() |
a2ecf10d19 | ||
![]() |
1e63a2a7e7 | ||
![]() |
964014fc5c | ||
![]() |
fc2bb6d8c3 | ||
![]() |
1b10252d76 | ||
![]() |
ad8af12a10 | ||
![]() |
b040c9b118 | ||
![]() |
f6da7da90b | ||
![]() |
a745185408 | ||
![]() |
d3336f9027 | ||
![]() |
daf42c8203 | ||
![]() |
0a18bae3b5 | ||
![]() |
919705966c | ||
![]() |
2c54aee63e | ||
![]() |
3f80bdf2a3 | ||
![]() |
1c429b8dd3 | ||
![]() |
5669e2b0b7 | ||
![]() |
1a6a43babf | ||
![]() |
2650db5ddc | ||
![]() |
255491a107 | ||
![]() |
5c64147dfa | ||
![]() |
39f4118577 | ||
![]() |
f7f6e4736a | ||
![]() |
c635da7ebb | ||
![]() |
58124b006a | ||
![]() |
563aeccd0f | ||
![]() |
bd1a95a7f5 | ||
![]() |
cdb25828f2 | ||
![]() |
45803b3b23 | ||
![]() |
0e5e3d3383 | ||
![]() |
4672930037 | ||
![]() |
09be7131c3 | ||
![]() |
a804f90b9c | ||
![]() |
264cb6bbd2 | ||
![]() |
b7772e867b | ||
![]() |
cc0e77abfb | ||
![]() |
537d1c6f4f | ||
![]() |
80facadd67 | ||
![]() |
ba097dad23 | ||
![]() |
c13c15d046 | ||
![]() |
4f52128a06 | ||
![]() |
500b2d0e6d | ||
![]() |
e59d094feb | ||
![]() |
a8372f14f8 | ||
![]() |
5174ff422d | ||
![]() |
5c06751c3b | ||
![]() |
ac2b0118a6 | ||
![]() |
3eb8fd4abe | ||
![]() |
48b389ebe3 | ||
![]() |
065adeb2cd | ||
![]() |
269d0a06fe | ||
![]() |
8eca26b1a5 | ||
![]() |
3019ef7de4 | ||
![]() |
522311b547 | ||
![]() |
21061561ec | ||
![]() |
b83c41ad56 | ||
![]() |
e80a1cc64a | ||
![]() |
a01e4ca89f | ||
![]() |
c20362e9b6 | ||
![]() |
c90cfb99bd | ||
![]() |
7bcea14799 | ||
![]() |
b415c1a6d1 | ||
![]() |
452c72d280 | ||
![]() |
48350be625 | ||
![]() |
ab824fb219 | ||
![]() |
043d8a1861 | ||
![]() |
074ac15d0f | ||
![]() |
d36a28fa81 | ||
![]() |
ba12bc6c91 | ||
![]() |
87332778e5 | ||
![]() |
453feb8473 | ||
![]() |
8ff469974c | ||
![]() |
994ec5ac0f | ||
![]() |
43f7f9a363 | ||
![]() |
4a11ebc9b9 | ||
![]() |
d76a1305e7 | ||
![]() |
6a0d592491 | ||
![]() |
9898c2196d | ||
![]() |
41a8dc840f | ||
![]() |
c3eaae9d88 | ||
![]() |
3ca959b7a6 | ||
![]() |
1d2e2b6e5c | ||
![]() |
31d963c4d1 | ||
![]() |
7e96118cdc | ||
![]() |
709a0744bd | ||
![]() |
f59248cc5a | ||
![]() |
8647c5c607 | ||
![]() |
6699ff38a1 | ||
![]() |
d79b98bd55 | ||
![]() |
5065a052fb | ||
![]() |
45603bb78c | ||
![]() |
40948995b4 | ||
![]() |
4ccdd8d1d3 | ||
![]() |
30d0174f47 | ||
![]() |
5a986ba25c | ||
![]() |
fe63c24ac3 | ||
![]() |
c384bd6875 | ||
![]() |
dcbff3f569 | ||
![]() |
7d91e05a69 | ||
![]() |
a5ce424a40 | ||
![]() |
47c36ca062 | ||
![]() |
c4c5b3bf8b | ||
![]() |
b1a81b0d12 | ||
![]() |
ad9fe64850 | ||
![]() |
f236349dc6 | ||
![]() |
5f56c8a7d4 | ||
![]() |
309d8a9f18 | ||
![]() |
2981799803 | ||
![]() |
00f8e1c0da | ||
![]() |
e9482e2ec4 | ||
![]() |
9bff327377 | ||
![]() |
ae009f98c1 | ||
![]() |
77505a6f5b | ||
![]() |
19c729aa23 | ||
![]() |
595888128a | ||
![]() |
51589d0eae | ||
![]() |
f1643ac549 | ||
![]() |
3f24461612 | ||
![]() |
b5deb198de | ||
![]() |
78452cf6a9 | ||
![]() |
4b4a784f56 | ||
![]() |
3e53cbcf8f | ||
![]() |
f34740f1f0 | ||
![]() |
b406bdfc37 | ||
![]() |
03c056702c | ||
![]() |
9c5f3f1946 | ||
![]() |
b50d7c24e7 | ||
![]() |
f05cf68945 | ||
![]() |
efc1875e35 | ||
![]() |
df063e6762 | ||
![]() |
e5c55b4339 | ||
![]() |
bee9095d6f | ||
![]() |
92f8eaaac9 | ||
![]() |
f5e7288fe5 | ||
![]() |
214aa7b6e4 | ||
![]() |
5b5d5b41f5 | ||
![]() |
23d613321e | ||
![]() |
0b6be0923f | ||
![]() |
aba748ea13 | ||
![]() |
f1f1ac582d | ||
![]() |
54a7cbc3f4 | ||
![]() |
2f4dbaec4c | ||
![]() |
578f518aaf | ||
![]() |
077ba74b22 | ||
![]() |
e0efe635c7 | ||
![]() |
1a06841de0 | ||
![]() |
3987e0ee0b | ||
![]() |
9f53bea02f | ||
![]() |
737709f9e7 | ||
![]() |
39477aa6a0 | ||
![]() |
f097050b56 | ||
![]() |
f14726ed1a | ||
![]() |
e1e4d038d9 | ||
![]() |
d2db4cf887 | ||
![]() |
2f3ece9ca3 | ||
![]() |
9f82007116 | ||
![]() |
f79198a472 | ||
![]() |
ce3d35d7ec | ||
![]() |
f4d40f0466 | ||
![]() |
a2fa085d5f | ||
![]() |
a598266a6e | ||
![]() |
f5fe33cee7 | ||
![]() |
200c7226ef | ||
![]() |
53475a6a0e | ||
![]() |
b4ec1ad6c0 | ||
![]() |
ef511a729d | ||
![]() |
275c4ce226 | ||
![]() |
45f9c029c8 | ||
![]() |
db5e4ad5d9 | ||
![]() |
f05d0a9727 | ||
![]() |
04593e9d9a | ||
![]() |
b1ecf13f8e | ||
![]() |
e91e054f20 | ||
![]() |
130ff7517e | ||
![]() |
c7042d9684 | ||
![]() |
5752e45dd1 | ||
![]() |
1a034ecb53 | ||
![]() |
025da8fb76 | ||
![]() |
2027da1db5 | ||
![]() |
7732f28ca8 | ||
![]() |
7f9da8cc2d | ||
![]() |
c6342b80a7 | ||
![]() |
f99c82de4b | ||
![]() |
56fa57ea02 | ||
![]() |
cc85985d08 | ||
![]() |
bd1751903e | ||
![]() |
03a298a70f | ||
![]() |
2722ca2b0e | ||
![]() |
179c4b800e | ||
![]() |
6bdf14223d | ||
![]() |
1b8252aa4f | ||
![]() |
8219889154 | ||
![]() |
df4ac5dcce | ||
![]() |
738eaf9de9 | ||
![]() |
c483ccbbbc | ||
![]() |
0d65f846ae | ||
![]() |
f47e75c423 | ||
![]() |
c008e58fb8 | ||
![]() |
26e0f17bc5 | ||
![]() |
6543f28bdb | ||
![]() |
a86851b338 | ||
![]() |
3a03e455c6 | ||
![]() |
3d39fd1580 | ||
![]() |
601b0add26 | ||
![]() |
4f974cc913 | ||
![]() |
f691320453 | ||
![]() |
be39fc3a21 | ||
![]() |
d2fafaf33a | ||
![]() |
27ae331352 | ||
![]() |
3f2dcfbacc | ||
![]() |
8565aee8b6 | ||
![]() |
f983add599 | ||
![]() |
030192afeb | ||
![]() |
c8b6a158f1 | ||
![]() |
e71f7849a7 | ||
![]() |
b64d1ff4ff | ||
![]() |
5a0028be26 | ||
![]() |
926d7deb43 | ||
![]() |
6384b50bae | ||
![]() |
9feb0f4b53 | ||
![]() |
43ec1b7cfd | ||
![]() |
05b7a59f8d | ||
![]() |
17e680f7af | ||
![]() |
035d256d4e | ||
![]() |
8939adf886 | ||
![]() |
027ffbffa6 | ||
![]() |
3cca06712b | ||
![]() |
2b9359dbf4 | ||
![]() |
c0f5d3bd2e | ||
![]() |
2a2d5382e1 | ||
![]() |
2e4986024c | ||
![]() |
8a9c605dae | ||
![]() |
44f51a93c8 | ||
![]() |
66c8537b41 | ||
![]() |
86ae6dd332 | ||
![]() |
69380c9c73 | ||
![]() |
3d3759137c | ||
![]() |
9b9b8f6f6f | ||
![]() |
8ff87a8245 | ||
![]() |
d1896da171 | ||
![]() |
0bc4f6fd96 | ||
![]() |
b16a429686 | ||
![]() |
fa1d266696 | ||
![]() |
d5dd2e9551 | ||
![]() |
be57c312c4 | ||
![]() |
f180687ba3 | ||
![]() |
3f3d9cc6f1 | ||
![]() |
4f98c0d045 | ||
![]() |
c254441d40 | ||
![]() |
17cbe74fa3 | ||
![]() |
7aa0bd9b79 | ||
![]() |
2553cf6b72 | ||
![]() |
fe9050aeda | ||
![]() |
7092894d22 | ||
![]() |
af6ac26664 | ||
![]() |
a22ef67486 | ||
![]() |
7bb57cd78a | ||
![]() |
89b69bbdf8 | ||
![]() |
e21c779d06 | ||
![]() |
dfa3553b71 | ||
![]() |
19097388d0 | ||
![]() |
a71eddbed2 | ||
![]() |
65bbed0c26 | ||
![]() |
871cc61dfc | ||
![]() |
bc62feb71b | ||
![]() |
0bba329999 | ||
![]() |
b1a1fdbeee | ||
![]() |
542c5beb1b | ||
![]() |
7b87b0919b | ||
![]() |
9c34f558d3 | ||
![]() |
3e2da3b490 | ||
![]() |
fb4a4f50be | ||
![]() |
6596e9cab6 | ||
![]() |
f1b137f2e1 | ||
![]() |
535720d0fe | ||
![]() |
f063cf4a16 | ||
![]() |
90bbdbf2fe | ||
![]() |
5f1d8fb99d | ||
![]() |
5486ffcdcc | ||
![]() |
adfd123970 | ||
![]() |
f1a364bfa2 | ||
![]() |
9da714bf15 | ||
![]() |
fc73295520 | ||
![]() |
ab955e41fb | ||
![]() |
c64367335c | ||
![]() |
edc787eb3e | ||
![]() |
f2c69fc68b | ||
![]() |
d947fe743b | ||
![]() |
5b37ae9026 | ||
![]() |
ec9e042b29 | ||
![]() |
337ac0eab9 | ||
![]() |
f6a1b784c4 | ||
![]() |
332fcecb78 | ||
![]() |
18590be1e7 | ||
![]() |
b76edcaf1d | ||
![]() |
6024cabb69 | ||
![]() |
08446e648e | ||
![]() |
14af7a3572 | ||
![]() |
cdc4275f81 | ||
![]() |
a9ade98315 | ||
![]() |
f3ae6fa70f | ||
![]() |
96457bbec3 | ||
![]() |
8f465e376e | ||
![]() |
adc366a959 | ||
![]() |
a20a6bc8bb | ||
![]() |
b176fa66d4 | ||
![]() |
f81b1926fb | ||
![]() |
7b7609a068 | ||
![]() |
670d4108e6 | ||
![]() |
a5c7b88a40 | ||
![]() |
e52b2e6d69 | ||
![]() |
e4066fb8df | ||
![]() |
f7a0fb22b4 | ||
![]() |
cad2ae723c | ||
![]() |
889a8c6093 | ||
![]() |
573418914f | ||
![]() |
d7fb6f9c05 | ||
![]() |
136e27d655 | ||
![]() |
d5ff2d7099 | ||
![]() |
2a7f8d0c99 | ||
![]() |
e3ca5df713 | ||
![]() |
bda32f3e8f | ||
![]() |
a7c6e45a92 | ||
![]() |
7c20ca9b64 | ||
![]() |
a201461eff | ||
![]() |
e5d9df37c5 | ||
![]() |
106fbaf086 | ||
![]() |
a0024c98d5 | ||
![]() |
684a702638 | ||
![]() |
aec4a009d1 | ||
![]() |
822af575c9 | ||
![]() |
485efa7d44 | ||
![]() |
3d09d45423 | ||
![]() |
4c69c6d9fd | ||
![]() |
920a41acef | ||
![]() |
0cf13a284c | ||
![]() |
a89cdef436 | ||
![]() |
881d88f4ad | ||
![]() |
a72c96f56d | ||
![]() |
bc8235b209 | ||
![]() |
0087495749 | ||
![]() |
9560afd4a7 | ||
![]() |
56ec8559a0 | ||
![]() |
99ca79ac7d | ||
![]() |
24564f4c74 | ||
![]() |
212c802a1e | ||
![]() |
984b5d6c40 | ||
![]() |
0e3a4191a9 | ||
![]() |
570a34bca5 | ||
![]() |
c9a0c29286 | ||
![]() |
b5f804ec22 | ||
![]() |
dadbb83271 | ||
![]() |
848aacdbbf | ||
![]() |
da3665a167 | ||
![]() |
dcf0a06217 | ||
![]() |
3b5e6553cd | ||
![]() |
509390af20 | ||
![]() |
9ad511a9c0 | ||
![]() |
89c102513d | ||
![]() |
5e65ae76ad | ||
![]() |
b6c364cd78 | ||
![]() |
e086b8707f | ||
![]() |
90dddd10a9 | ||
![]() |
2dd0907565 | ||
![]() |
f31b0d0c71 | ||
![]() |
a0825b75f7 | ||
![]() |
a3bd4c0f73 | ||
![]() |
a3e8c9b28a | ||
![]() |
d7fb850b4a | ||
![]() |
d084778a6e | ||
![]() |
8ca30de760 | ||
![]() |
8a10b81bd9 | ||
![]() |
4a93c4e584 | ||
![]() |
50177cd6bd | ||
![]() |
71a2e52739 | ||
![]() |
4fac6d5aa3 | ||
![]() |
37d061b602 | ||
![]() |
40193e4edc | ||
![]() |
b4e9d61871 | ||
![]() |
d44b589e55 | ||
![]() |
68216415b6 | ||
![]() |
ba53da18d1 | ||
![]() |
9b76fa3582 | ||
![]() |
13d8d10a7f | ||
![]() |
5c6c1bb09d | ||
![]() |
12105d96ea | ||
![]() |
4054756035 | ||
![]() |
16769c7838 | ||
![]() |
cd076c5959 | ||
![]() |
f52e1aa131 | ||
![]() |
fdc1ef7e9a | ||
![]() |
9cccf2d47b | ||
![]() |
0796f27f2a | ||
![]() |
6c84014e0d | ||
![]() |
cd496a22bf | ||
![]() |
0200343780 | ||
![]() |
47fb629d26 | ||
![]() |
71ae08706b | ||
![]() |
50dd798757 | ||
![]() |
0c8cf73746 | ||
![]() |
1bee811312 | ||
![]() |
b4c0068637 | ||
![]() |
f484c6e5fe | ||
![]() |
7a08187c5f | ||
![]() |
c4d7d5a0d4 | ||
![]() |
5b75e753a7 | ||
![]() |
326e9b86ce | ||
![]() |
d22f5d369c | ||
![]() |
d76503995c | ||
![]() |
eab930c083 | ||
![]() |
e430cc54f2 | ||
![]() |
fd26a9c698 | ||
![]() |
e79b608f77 | ||
![]() |
42b23a6c9c | ||
![]() |
8d94f24c71 | ||
![]() |
6ac74c39d9 | ||
![]() |
836eb7b708 | ||
![]() |
698624b4dc | ||
![]() |
5c1df82076 | ||
![]() |
5d649b3687 | ||
![]() |
a6a3d71155 | ||
![]() |
1cc9d501ab | ||
![]() |
7a98025df8 | ||
![]() |
44d6ed5e80 | ||
![]() |
b5f2226bef | ||
![]() |
ddbffe55d2 | ||
![]() |
9676b1d0e9 | ||
![]() |
8142d3bfeb | ||
![]() |
755ad27a0a | ||
![]() |
5afa2dcdf1 | ||
![]() |
03098ee024 | ||
![]() |
a2bfdd003c | ||
![]() |
7eb80646ba | ||
![]() |
6fd24e57d3 | ||
![]() |
22c90adb47 | ||
![]() |
df0c6fafbe | ||
![]() |
dc30321b04 | ||
![]() |
63dd98d2df | ||
![]() |
caaa6ed506 | ||
![]() |
caf23792cb | ||
![]() |
e430db20aa | ||
![]() |
6fc5da9b67 | ||
![]() |
f428e57724 | ||
![]() |
14ab21fe9a | ||
![]() |
85626e19da | ||
![]() |
8712160fd7 | ||
![]() |
75b33f5cb1 | ||
![]() |
f5e8ede847 | ||
![]() |
3b3f684a8c | ||
![]() |
a78b60d40e | ||
![]() |
9ff06a3c44 | ||
![]() |
8532dc486c | ||
![]() |
861340f4bf | ||
![]() |
cdcb51ebe4 | ||
![]() |
0b11786d7d | ||
![]() |
1742247a9a | ||
![]() |
42bad123b2 | ||
![]() |
2d1e87defc | ||
![]() |
1c6f783a07 | ||
![]() |
6aafc097d5 | ||
![]() |
4010f233dd | ||
![]() |
75f67caa1b | ||
![]() |
d760ce54b7 | ||
![]() |
956976ebd5 | ||
![]() |
f9c2d4ca6c | ||
![]() |
dd5cc3c38c | ||
![]() |
daed4cc13e | ||
![]() |
6ff614dd18 | ||
![]() |
eb70ac4266 | ||
![]() |
a3a431adb7 | ||
![]() |
e12c72ab98 | ||
![]() |
9f8549b831 | ||
![]() |
b2de256f87 | ||
![]() |
7f32a5cf9e | ||
![]() |
56f8314d29 | ||
![]() |
4ceb2a8669 | ||
![]() |
c778d3b699 | ||
![]() |
47eda9cdf2 | ||
![]() |
dcaec4d356 | ||
![]() |
aee4f349c6 | ||
![]() |
daa2c39902 | ||
![]() |
5770fc02a1 | ||
![]() |
47cafd295b | ||
![]() |
3296f2daf8 | ||
![]() |
962616545c | ||
![]() |
11ea92c078 | ||
![]() |
1d64fa4817 | ||
![]() |
c46f2956c2 | ||
![]() |
8f6d4298be | ||
![]() |
3bce81326e | ||
![]() |
2ae9f6d0fe | ||
![]() |
9266828278 | ||
![]() |
a8a2ffc33e | ||
![]() |
27c4543471 | ||
![]() |
50a02cb59e | ||
![]() |
50579bb9e6 | ||
![]() |
50512ca63c | ||
![]() |
8b3577b216 | ||
![]() |
7553aab932 | ||
![]() |
5dacdcfe5e | ||
![]() |
8645a412b7 | ||
![]() |
acad07a588 | ||
![]() |
51bbb480bb | ||
![]() |
f0306cd10a | ||
![]() |
25253ad9e7 | ||
![]() |
51f2fb8e8b | ||
![]() |
9e8d650cbd | ||
![]() |
d222ccfa58 | ||
![]() |
9a05aaa906 | ||
![]() |
00fdce8876 | ||
![]() |
29b51adf7d | ||
![]() |
8e14b39969 | ||
![]() |
d9fb4d6c4d | ||
![]() |
fcf2f4c5f2 | ||
![]() |
4e97501690 | ||
![]() |
b0402391fb | ||
![]() |
f05a862cf9 | ||
![]() |
3ea92d57c2 | ||
![]() |
254b85fbd8 | ||
![]() |
16371c0cc4 | ||
![]() |
2256d67e2b | ||
![]() |
0af0bdede6 | ||
![]() |
379f31b9de | ||
![]() |
771a524734 | ||
![]() |
560e18a610 | ||
![]() |
147bdfab95 | ||
![]() |
36b1b0f663 | ||
![]() |
91511e4c3f | ||
![]() |
6a72056b25 | ||
![]() |
62e0c57a50 | ||
![]() |
9d92270931 | ||
![]() |
f61321d5a6 | ||
![]() |
08ab2f8649 | ||
![]() |
82962c4b42 | ||
![]() |
bd24e8a4ad | ||
![]() |
6224d9a292 | ||
![]() |
bbc58f3671 | ||
![]() |
fcd620283f | ||
![]() |
a78def3d2d | ||
![]() |
43e94a5db0 | ||
![]() |
e77bcc1267 | ||
![]() |
9b458958b8 | ||
![]() |
35419ade29 | ||
![]() |
15bd2ee887 | ||
![]() |
9394bafa8e | ||
![]() |
94150a0c48 | ||
![]() |
8955fdfc23 | ||
![]() |
c13aa6a545 | ||
![]() |
c73b50bd4a | ||
![]() |
0a17a38bf1 | ||
![]() |
0f7bfe1d66 | ||
![]() |
cf3f488663 | ||
![]() |
5f536fdb73 | ||
![]() |
99d0b13cce | ||
![]() |
b04937f012 | ||
![]() |
91c9b059cf | ||
![]() |
35cc643440 | ||
![]() |
b23bb8c46a | ||
![]() |
64fdf62c4b | ||
![]() |
1c8a808571 | ||
![]() |
d8f0295032 | ||
![]() |
d59771ac2f | ||
![]() |
45df093fac | ||
![]() |
fba2078fc0 | ||
![]() |
20a37fe2de | ||
![]() |
8f6d26b65c | ||
![]() |
b58a194c8a | ||
![]() |
52f1b0a0ce | ||
![]() |
c2b8fb223b | ||
![]() |
20e4eff899 | ||
![]() |
0efcca36d2 | ||
![]() |
ab417802a0 | ||
![]() |
73d68cce4a | ||
![]() |
e4ac2de660 | ||
![]() |
8d1241808a | ||
![]() |
b810040145 | ||
![]() |
c01d7dae2d | ||
![]() |
dfca3c2483 | ||
![]() |
1bb4be086f | ||
![]() |
fd226c45f6 | ||
![]() |
21fed5b25f | ||
![]() |
dde093d321 | ||
![]() |
b99fb247ac | ||
![]() |
28930fdad4 | ||
![]() |
ea4d1d3275 | ||
![]() |
62e852d510 | ||
![]() |
7ddd4d6461 | ||
![]() |
6b9307de2a | ||
![]() |
234046ce10 | ||
![]() |
73b29cf1e2 | ||
![]() |
4b3bf170c0 | ||
![]() |
a7fbaba2d7 | ||
![]() |
fc79241f3d | ||
![]() |
a88c37ea56 | ||
![]() |
9b2358b7f1 | ||
![]() |
257135763f | ||
![]() |
610a3499f2 | ||
![]() |
69752b8837 | ||
![]() |
610473b57c | ||
![]() |
1e5721d7d5 | ||
![]() |
6c2b45679a | ||
![]() |
6785922379 | ||
![]() |
4e85124aeb | ||
![]() |
6b30a03f55 | ||
![]() |
876894d8c6 | ||
![]() |
ea20d94146 | ||
![]() |
c7669777cb | ||
![]() |
3b43bba4a0 | ||
![]() |
0ab4946bf1 | ||
![]() |
a7fb18d5c0 | ||
![]() |
a0fbb0f861 | ||
![]() |
e6c93ab1c0 | ||
![]() |
7152213344 | ||
![]() |
a8e913cfde | ||
![]() |
4ac074f3dd | ||
![]() |
436249597d | ||
![]() |
016a742d90 | ||
![]() |
6d863ac29c | ||
![]() |
ae981fe57d | ||
![]() |
0c6a75b722 | ||
![]() |
bfd9b1b7c7 | ||
![]() |
12f6b1ca45 | ||
![]() |
04264110ee | ||
![]() |
e4a112c329 | ||
![]() |
ef4dee8886 | ||
![]() |
e7ee21ca30 | ||
![]() |
23ee480c4f | ||
![]() |
7816271302 | ||
![]() |
b57814f14a | ||
![]() |
b18e86f81c | ||
![]() |
7b1b503703 | ||
![]() |
32d4febf10 | ||
![]() |
814973af58 | ||
![]() |
ecee642e10 | ||
![]() |
9afc0f6667 | ||
![]() |
e9e517533a | ||
![]() |
4a531ccea1 | ||
![]() |
fb8e0595c2 | ||
![]() |
d748d6e400 | ||
![]() |
6b99fa1f24 | ||
![]() |
ca5abc635c | ||
![]() |
35e75be0d0 | ||
![]() |
cf401a659d | ||
![]() |
bd56968efb | ||
![]() |
a78bc686cd | ||
![]() |
ad8c962c25 | ||
![]() |
be91976498 | ||
![]() |
57821b839e | ||
![]() |
ad334ed09f | ||
![]() |
a955937e02 | ||
![]() |
3c42cc17c8 | ||
![]() |
deeab036d3 | ||
![]() |
a1badcd9a1 | ||
![]() |
52762438c6 | ||
![]() |
3294079b72 | ||
![]() |
1c6bdf20b6 | ||
![]() |
fac00be995 | ||
![]() |
e7e8e99946 | ||
![]() |
9f9749548a | ||
![]() |
db1ac85acf | ||
![]() |
d5eaeb429a | ||
![]() |
4e7595d8d1 | ||
![]() |
f25fdcdc3d | ||
![]() |
7a4de75e07 | ||
![]() |
545b57a57d | ||
![]() |
32c3aa7979 | ||
![]() |
013f703241 | ||
![]() |
c463ad5fd6 | ||
![]() |
412b8473fe | ||
![]() |
02df2132b4 | ||
![]() |
a964d5c93f | ||
![]() |
eafc32a915 | ||
![]() |
df4b84b4b9 | ||
![]() |
6e094eb4fc | ||
![]() |
9e7d7bcb4c | ||
![]() |
63b3cc8c02 | ||
![]() |
7a88786685 | ||
![]() |
427889f8ca | ||
![]() |
82c9c28439 | ||
![]() |
c84c1f2e96 | ||
![]() |
a3ee8672ed | ||
![]() |
4cfde09016 | ||
![]() |
0b8dcbebe9 | ||
![]() |
aa12506221 | ||
![]() |
39ed9dea01 | ||
![]() |
6f095470ad | ||
![]() |
2a5d2cc146 | ||
![]() |
b5e8218551 | ||
![]() |
062cc307fb | ||
![]() |
e99ff1be35 | ||
![]() |
404a213896 | ||
![]() |
0a07f16ef6 | ||
![]() |
f9bf8f9901 | ||
![]() |
b6ae67bf3e | ||
![]() |
191ce0798f | ||
![]() |
40362590c8 | ||
![]() |
87f6dc7c0b | ||
![]() |
2f2c1f263a | ||
![]() |
8841cbb3d0 | ||
![]() |
a2e20a8092 | ||
![]() |
6a7c7a0ab5 | ||
![]() |
44a8c8e35d | ||
![]() |
1cbfccc4eb | ||
![]() |
e12c0b5536 | ||
![]() |
b7a8781308 | ||
![]() |
73a8fcd35b | ||
![]() |
a2ad39f78d | ||
![]() |
832635d6f5 | ||
![]() |
dacb56bc20 | ||
![]() |
e5a9821027 | ||
![]() |
bbe666eb73 | ||
![]() |
000cb3d80c | ||
![]() |
40f85dbf5f | ||
![]() |
d6646ebadf | ||
![]() |
e02bddc78f | ||
![]() |
08e679184b | ||
![]() |
5c877e894b | ||
![]() |
5918f03cb1 | ||
![]() |
78263d716c | ||
![]() |
15f4841328 | ||
![]() |
ae5d50141b | ||
![]() |
8839563ff8 | ||
![]() |
6d954b2d5d | ||
![]() |
6e125f15a4 | ||
![]() |
e344921a06 | ||
![]() |
4cbaf0dc70 | ||
![]() |
ef7d2f4a82 | ||
![]() |
5f7d998b0b | ||
![]() |
2c14281168 | ||
![]() |
9feab4bc79 | ||
![]() |
63237bc112 | ||
![]() |
99f4752c89 | ||
![]() |
ef1ed5aa8b | ||
![]() |
1258270ac4 | ||
![]() |
bb7a2f5f6c | ||
![]() |
c865d32d95 | ||
![]() |
87c3b24488 | ||
![]() |
5a5257294b | ||
![]() |
a9ca951854 | ||
![]() |
2a9353ee70 | ||
![]() |
18e134b92a | ||
![]() |
6c87e15a52 | ||
![]() |
a710821c35 | ||
![]() |
de4aeedce5 | ||
![]() |
1f71a01453 | ||
![]() |
6371d79d33 | ||
![]() |
80d2218aa6 | ||
![]() |
bc636f109c | ||
![]() |
76d58af4d8 | ||
![]() |
84e5417a8c | ||
![]() |
89188958ec | ||
![]() |
b5d24d751d | ||
![]() |
3269061db4 | ||
![]() |
9f576f43cc | ||
![]() |
505c6e0e0e | ||
![]() |
9936b49ee0 | ||
![]() |
707bc765b9 | ||
![]() |
8780c987ea | ||
![]() |
7aa01f786d | ||
![]() |
340e94d54e | ||
![]() |
509b123064 | ||
![]() |
8060b1c753 | ||
![]() |
33ef3e7d59 | ||
![]() |
704e5f7134 | ||
![]() |
91bc3ab525 | ||
![]() |
2e6bded9d0 | ||
![]() |
c6e980ed96 | ||
![]() |
32a932ad5c | ||
![]() |
f6d2bd04e9 | ||
![]() |
b0266b470f | ||
![]() |
0ed969fa3f | ||
![]() |
90a7b5e0d3 | ||
![]() |
c5bf656fe7 | ||
![]() |
6bce4533a3 | ||
![]() |
8a8aa0016e | ||
![]() |
7e4ebd330c | ||
![]() |
c304845117 | ||
![]() |
ee85f3e824 | ||
![]() |
3f6f1dcd78 | ||
![]() |
c271a4b2cb | ||
![]() |
fa07dfb720 | ||
![]() |
6f6b258f22 | ||
![]() |
717b246cb6 | ||
![]() |
5990e0c2eb | ||
![]() |
827df80ec8 | ||
![]() |
7117fae2b2 | ||
![]() |
15e9462140 | ||
![]() |
714f8327ea | ||
![]() |
fedb77e304 | ||
![]() |
06d2884a88 | ||
![]() |
b50556802c | ||
![]() |
b18dfdb9ba | ||
![]() |
173f83808e | ||
![]() |
fbe2d78331 | ||
![]() |
e5fd9c6366 | ||
![]() |
3623b991ff | ||
![]() |
2cabd7879c | ||
![]() |
a1a378d6f5 | ||
![]() |
b016268fdb | ||
![]() |
dfb31b78d9 | ||
![]() |
4eed603d36 | ||
![]() |
cdc10d6c4b | ||
![]() |
cb03501eff | ||
![]() |
c2e28ab5a6 | ||
![]() |
24a166cb94 | ||
![]() |
1d3ac0c9b3 | ||
![]() |
ff29b62398 | ||
![]() |
d9e016db8b | ||
![]() |
6cfd50a7b8 | ||
![]() |
6605d3812a | ||
![]() |
be71abe580 | ||
![]() |
518ff48e97 | ||
![]() |
8a4add257f | ||
![]() |
7842cd0bc0 | ||
![]() |
ffe480ad44 | ||
![]() |
e4d3f95257 | ||
![]() |
245eabe85f | ||
![]() |
22e7eb1ffc | ||
![]() |
d663a58d65 | ||
![]() |
fa5d5f1bcc | ||
![]() |
7178095aef | ||
![]() |
4704faa011 | ||
![]() |
0f77d9df1f | ||
![]() |
e02c3fca8b | ||
![]() |
abe0838a63 | ||
![]() |
6583e3d0c9 | ||
![]() |
2093d68bfb | ||
![]() |
d3a55d50c0 | ||
![]() |
471733b243 | ||
![]() |
47fa717bce | ||
![]() |
cfc68e70b6 | ||
![]() |
bd9ee62118 | ||
![]() |
aaa874b099 | ||
![]() |
958709faf2 | ||
![]() |
52ab93013c | ||
![]() |
9203fa3df2 | ||
![]() |
9ef3edabce | ||
![]() |
c771d75a00 | ||
![]() |
1db27ab0e3 | ||
![]() |
cd45f7051c | ||
![]() |
588ea7978e | ||
![]() |
946f12cf6a | ||
![]() |
7e49bfa984 | ||
![]() |
7b10d75aeb | ||
![]() |
34e4963ccd | ||
![]() |
9fc9ed805c | ||
![]() |
db4c5bc3a3 | ||
![]() |
024faa2561 | ||
![]() |
b46459de5f | ||
![]() |
ac7f025223 | ||
![]() |
e4343650c6 | ||
![]() |
80c5259b05 | ||
![]() |
004a65d933 | ||
![]() |
38289918c2 | ||
![]() |
0e46f9f213 | ||
![]() |
be03b973e5 | ||
![]() |
a0bf2b3d3d | ||
![]() |
755ab36e83 | ||
![]() |
30975f7360 | ||
![]() |
828307fc52 | ||
![]() |
e79ca4fa4c | ||
![]() |
43288eb5c0 | ||
![]() |
9518595e48 | ||
![]() |
3c4cd3743f | ||
![]() |
b39cbffe14 | ||
![]() |
f588d3f35b | ||
![]() |
54b06872eb | ||
![]() |
0786c608a4 | ||
![]() |
c70b2eaa30 | ||
![]() |
c417a95e1f | ||
![]() |
5ae9be0291 | ||
![]() |
e2a6e3ea58 | ||
![]() |
8fd5fa185b | ||
![]() |
66707661e9 | ||
![]() |
4e18ec5951 | ||
![]() |
05b77b5042 | ||
![]() |
d6cfe11d97 | ||
![]() |
dd4d59e4e7 | ||
![]() |
7cb8626e16 | ||
![]() |
89b5202adb | ||
![]() |
40ae0c1449 | ||
![]() |
81a8115c56 | ||
![]() |
0ddd26bd51 | ||
![]() |
69e2133a27 | ||
![]() |
b04f85949b | ||
![]() |
667dba01ae | ||
![]() |
85a8cef628 | ||
![]() |
3099acfd00 | ||
![]() |
b00fe0b5f8 | ||
![]() |
9a64b8bdb6 | ||
![]() |
c01e493bd2 | ||
![]() |
890236af23 | ||
![]() |
ea678d805d | ||
![]() |
29699418ff | ||
![]() |
87bb36c39f | ||
![]() |
7cb85ed73c | ||
![]() |
c647771a6d | ||
![]() |
6c3d737219 | ||
![]() |
a1f38fed7a | ||
![]() |
c36bb77286 | ||
![]() |
1a1acdc3c9 | ||
![]() |
ef8e60c405 | ||
![]() |
31c1cc47bf | ||
![]() |
351fed7359 | ||
![]() |
f49e7cbe57 | ||
![]() |
7da8ea5e99 | ||
![]() |
8fa6a12a7c | ||
![]() |
1070278eaf | ||
![]() |
16b1a6b153 | ||
![]() |
096a7534e0 | ||
![]() |
b0897187d2 | ||
![]() |
885d94882d | ||
![]() |
11a3341e13 | ||
![]() |
231890f78a | ||
![]() |
a272feda6a | ||
![]() |
bc936a0ca7 | ||
![]() |
28030c2d13 | ||
![]() |
7fe9176286 | ||
![]() |
1105f9b8d6 | ||
![]() |
e4653defa8 | ||
![]() |
1c62a1e839 | ||
![]() |
3a3bbfe201 | ||
![]() |
1c38833998 | ||
![]() |
38894177ee | ||
![]() |
dce8416942 | ||
![]() |
14219e9b42 | ||
![]() |
7b459e7502 | ||
![]() |
31824c0504 | ||
![]() |
e203abae85 | ||
![]() |
faf83b680b | ||
![]() |
67dcbcb842 | ||
![]() |
6533a25404 | ||
![]() |
4dc760b0e9 | ||
![]() |
25933b9043 | ||
![]() |
a53aaa456e | ||
![]() |
e8a7ea07a5 | ||
![]() |
8817dc6b10 | ||
![]() |
491ec04b46 | ||
![]() |
8a5d4a683b | ||
![]() |
dfc7c7357a | ||
![]() |
690a2f7d34 | ||
![]() |
58f22b24e4 | ||
![]() |
3cce9f528b | ||
![]() |
20fd5ac8cb | ||
![]() |
9e05e086eb | ||
![]() |
056e0adddf | ||
![]() |
b36388200d | ||
![]() |
9793e5741a | ||
![]() |
143380c012 | ||
![]() |
4b92254945 | ||
![]() |
f9c1d8b4a6 | ||
![]() |
c0c469339b | ||
![]() |
0ca6343ed7 | ||
![]() |
3db74c3427 | ||
![]() |
48d5cb53bd | ||
![]() |
fd7d2dbf53 | ||
![]() |
6609697752 | ||
![]() |
dcd6e1973e | ||
![]() |
3614a6e932 | ||
![]() |
931a0210e5 | ||
![]() |
f9e7de4b42 | ||
![]() |
8e0b79594e | ||
![]() |
17122c4360 | ||
![]() |
154f7b6a30 | ||
![]() |
52e5543d0b | ||
![]() |
3c304bd2ae | ||
![]() |
26609bb8fd | ||
![]() |
de3fa9aaa4 | ||
![]() |
788665f84c | ||
![]() |
3943782971 | ||
![]() |
8f899c40f2 | ||
![]() |
a1f582399e | ||
![]() |
440b63f662 | ||
![]() |
7d2cc3b56b | ||
![]() |
5fe3422469 | ||
![]() |
6c02cedb1e | ||
![]() |
3cc2f1dcad | ||
![]() |
773cdc5877 | ||
![]() |
361a7329d7 | ||
![]() |
29910f1236 | ||
![]() |
4a164016f5 | ||
![]() |
cebd3e62a4 | ||
![]() |
2562a38fa1 | ||
![]() |
d46c922bbf | ||
![]() |
66b59982f7 | ||
![]() |
ad397ccf7f | ||
![]() |
bdef80ede7 |
@@ -1,21 +1,24 @@
|
||||
# EditorConfig is awesome: https://EditorConfig.org
|
||||
|
||||
# top-most EditorConfig file
|
||||
root = true
|
||||
|
||||
# Unix-style newlines with a newline ending every file
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
|
||||
# Matches multiple files with brace expansion notation
|
||||
# Set default charset
|
||||
charset = utf-8
|
||||
|
||||
# 2 space indentation
|
||||
[*.{cjs,mjs,js,jsx,ts,tsx,css,scss,sass,html,json}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
# Unfortunately, EditorConfig doesn't support space configuration inside import braces directly.
|
||||
# You'll need to rely on your linter/formatter like ESLint or Prettier for that.
|
||||
# EditorConfig is awesome: https://EditorConfig.org
|
||||
|
||||
# top-most EditorConfig file
|
||||
root = true
|
||||
|
||||
# Unix-style newlines with a newline ending every file
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
|
||||
# Matches multiple files with brace expansion notation
|
||||
# Set default charset
|
||||
charset = utf-8
|
||||
|
||||
# 2 space indentation
|
||||
[*.{cjs,mjs,js,jsx,ts,tsx,css,scss,sass,html,json}]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
[*.bat]
|
||||
charset = latin1
|
||||
|
||||
# Unfortunately, EditorConfig doesn't support space configuration inside import braces directly.
|
||||
# You'll need to rely on your linter/formatter like ESLint or Prettier for that.
|
||||
|
@@ -1 +0,0 @@
|
||||
VITE_BUILD_TYPE = Development
|
2
.env.framework
Normal file
2
.env.framework
Normal file
@@ -0,0 +1,2 @@
|
||||
VITE_BUILD_TYPE = Production
|
||||
VITE_BUILD_PLATFORM = Framework
|
@@ -1 +1,2 @@
|
||||
VITE_BUILD_TYPE = Production
|
||||
VITE_BUILD_PLATFORM = Shell
|
122
.eslintrc.cjs
122
.eslintrc.cjs
@@ -1,68 +1,64 @@
|
||||
module.exports = {
|
||||
'env': {
|
||||
'browser': true,
|
||||
'es2021': true,
|
||||
'node': true
|
||||
},
|
||||
'ignorePatterns': ['src/core/', 'src/core.lib/','src/proto/'],
|
||||
'extends': [
|
||||
'eslint:recommended',
|
||||
'plugin:@typescript-eslint/recommended'
|
||||
],
|
||||
'overrides': [
|
||||
{
|
||||
'env': {
|
||||
'env': {
|
||||
'browser': true,
|
||||
'es2021': true,
|
||||
'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
|
||||
}
|
||||
'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'],
|
||||
}
|
||||
},
|
||||
'rules': {
|
||||
'indent': [
|
||||
'error',
|
||||
2
|
||||
],
|
||||
'linebreak-style': [
|
||||
'error',
|
||||
'unix'
|
||||
],
|
||||
'quotes': [
|
||||
'error',
|
||||
'single'
|
||||
],
|
||||
'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'],
|
||||
}
|
||||
};
|
||||
|
3
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
3
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -10,6 +10,7 @@ body:
|
||||
在提交新的 Bug 反馈前,请确保您:
|
||||
* 已经搜索了现有的 issues,并且没有找到可以解决您问题的方法
|
||||
* 不与现有的某一 issue 重复
|
||||
* 不涉及[已经停止维护的特性](https://github.com/NapNeko/NapCatQQ?tab=readme-ov-file#挥别昨日),例如 CQ 码
|
||||
- type: input
|
||||
id: system-version
|
||||
attributes:
|
||||
@@ -78,4 +79,4 @@ body:
|
||||
attributes:
|
||||
label: OneBot 客户端运行日志
|
||||
description: 粘贴 OneBot 客户端的相关日志内容到此处
|
||||
render: shell
|
||||
render: shell
|
||||
|
41
.github/workflows/build.yml
vendored
41
.github/workflows/build.yml
vendored
@@ -1,21 +1,12 @@
|
||||
name: "Build Action"
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
permissions: write-all
|
||||
|
||||
jobs:
|
||||
build-linux:
|
||||
if: ${{ startsWith(github.event.head_commit.message, 'build:') }}
|
||||
Build-LiteLoader:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
target_platform: [linux]
|
||||
target_arch: [x64, arm64]
|
||||
steps:
|
||||
- name: Clone Main Repository
|
||||
uses: actions/checkout@v4
|
||||
@@ -28,26 +19,21 @@ jobs:
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20.x
|
||||
- name: Build NuCat Linux
|
||||
- name: Build NuCat Framework
|
||||
run: |
|
||||
npm i --arch=${{ matrix.target_arch }} --platform=${{ matrix.target_platform }}
|
||||
npm run build:prod
|
||||
npm i
|
||||
npm run build:framework
|
||||
cd dist
|
||||
npm i --omit=dev --arch=${{ matrix.target_arch }} --platform=${{ matrix.target_platform }}
|
||||
npm i --omit=dev
|
||||
rm package-lock.json
|
||||
cd ..
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: NapCat.${{ matrix.target_platform }}.${{ matrix.target_arch }}
|
||||
name: NapCat.Framework
|
||||
path: dist
|
||||
build-win32:
|
||||
if: ${{ startsWith(github.event.head_commit.message, 'build:') }}
|
||||
Build-Shell:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
target_platform: [win32]
|
||||
target_arch: [x64,ia32]
|
||||
steps:
|
||||
- name: Clone Main Repository
|
||||
uses: actions/checkout@v4
|
||||
@@ -60,15 +46,16 @@ jobs:
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20.x
|
||||
- name: Build NuCat Linux
|
||||
- name: Build NuCat LiteLoader
|
||||
run: |
|
||||
npm i --arch=${{ matrix.target_arch }} --platform=${{ matrix.target_platform }}
|
||||
npm run build:prod
|
||||
npm i
|
||||
npm run build:shell
|
||||
cd dist
|
||||
npm i --omit=dev --arch=${{ matrix.target_arch }} --platform=${{ matrix.target_platform }}
|
||||
npm i --omit=dev
|
||||
rm package-lock.json
|
||||
cd ..
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: NapCat.${{ matrix.target_platform }}.${{ matrix.target_arch }}
|
||||
name: NapCat.Shell
|
||||
path: dist
|
||||
|
85
.github/workflows/release.yml
vendored
85
.github/workflows/release.yml
vendored
@@ -30,14 +30,9 @@ jobs:
|
||||
ls
|
||||
node ./script/checkVersion.cjs
|
||||
sh ./checkVersion.sh
|
||||
build-linux:
|
||||
Build-LiteLoader:
|
||||
needs: [check-version]
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
target_platform: [linux]
|
||||
target_arch: [x64, arm64]
|
||||
steps:
|
||||
- name: Clone Main Repository
|
||||
uses: actions/checkout@v4
|
||||
@@ -51,28 +46,21 @@ jobs:
|
||||
with:
|
||||
node-version: 20.x
|
||||
|
||||
- name: Build NuCat Linux
|
||||
- name: Build NuCat Framework
|
||||
run: |
|
||||
export NAPCAT_BUILDSYS=${{ matrix.target_platform }}
|
||||
export NAPCAT_BUILDARCH=${{ matrix.target_arch }}
|
||||
npm i --arch=${{ matrix.target_arch }} --platform=${{ matrix.target_platform }}
|
||||
npm run build:prod
|
||||
npm i
|
||||
npm run build:framework
|
||||
cd dist
|
||||
npm i --omit=dev --arch=${{ matrix.target_arch }} --platform=${{ matrix.target_platform }}
|
||||
npm i --omit=dev
|
||||
cd ..
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: NapCat.${{ matrix.target_platform }}.${{ matrix.target_arch }}
|
||||
name: NapCat.Framework
|
||||
path: dist
|
||||
build-win32:
|
||||
Build-Shell:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [check-version]
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
target_platform: [win32]
|
||||
target_arch: [x64,ia32]
|
||||
steps:
|
||||
- name: Clone Main Repository
|
||||
uses: actions/checkout@v4
|
||||
@@ -87,36 +75,58 @@ jobs:
|
||||
with:
|
||||
node-version: 20.x
|
||||
|
||||
- name: Build NuCat Linux
|
||||
- name: Build NuCat Shell
|
||||
run: |
|
||||
export NAPCAT_BUILDSYS=${{ matrix.target_platform }}
|
||||
export NAPCAT_BUILDARCH=${{ matrix.target_arch }}
|
||||
npm i --arch=${{ matrix.target_arch }} --platform=${{ matrix.target_platform }}
|
||||
npm run build:prod
|
||||
npm i
|
||||
npm run build:shell
|
||||
cd dist
|
||||
npm i --omit=dev --arch=${{ matrix.target_arch }} --platform=${{ matrix.target_platform }}
|
||||
npm i --omit=dev
|
||||
cd ..
|
||||
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: NapCat.${{ matrix.target_platform }}.${{ matrix.target_arch }}
|
||||
name: NapCat.Shell
|
||||
path: dist
|
||||
|
||||
release-napcat:
|
||||
needs: [build-win32,build-linux]
|
||||
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: |
|
||||
for dir in */; do
|
||||
base=$(basename "$dir")
|
||||
zip -r "${base}.zip" "$dir"
|
||||
done
|
||||
|
||||
cd ./NapCat.Shell/
|
||||
zip -q -r NapCat.Shell.zip *
|
||||
cd ..
|
||||
cd ./NapCat.Framework/
|
||||
zip -q -r NapCat.Framework.zip *
|
||||
cd ..
|
||||
rm ./NapCat.Shell.zip -rf
|
||||
rm ./NapCat.Framework.zip -rf
|
||||
mv ./NapCat.Shell/NapCat.Shell.zip ./
|
||||
mv ./NapCat.Framework/NapCat.Framework.zip ./
|
||||
|
||||
mkdir ./NapCat.Framework.Windows.Once
|
||||
unzip -q ./external/LiteLoaderWrapper.zip -d ./NapCat.Framework.Windows.Once
|
||||
cd ./NapCat.Framework.Windows.Once
|
||||
ls
|
||||
mkdir -p ./LL/plugins/NapCatQQ
|
||||
unzip -q ../NapCat.Framework.zip -d ./LL/plugins/NapCatQQ
|
||||
zip -q -r NapCat.Framework.Windows.Once.zip *
|
||||
cd ..
|
||||
mv ./NapCat.Framework.Windows.Once/NapCat.Framework.Windows.Once.zip ./
|
||||
- name: Extract version from tag
|
||||
run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_ENV
|
||||
|
||||
@@ -130,10 +140,7 @@ jobs:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
body_path: CHANGELOG.md
|
||||
files: |
|
||||
NapCat.win32.ia32.zip
|
||||
NapCat.win32.x64.zip
|
||||
NapCat.linux.x64.zip
|
||||
NapCat.linux.arm64.zip
|
||||
# NapCat.darwin.x64.zip
|
||||
# NapCat.darwin.arm64.zip
|
||||
NapCat.Framework.zip
|
||||
NapCat.Shell.zip
|
||||
NapCat.Framework.Windows.Once.zip
|
||||
draft: true
|
||||
|
69
.github/workflows/test.yml
vendored
69
.github/workflows/test.yml
vendored
@@ -1,69 +0,0 @@
|
||||
name: "Build Test"
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions: write-all
|
||||
|
||||
jobs:
|
||||
build-linux:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
target_platform: [linux]
|
||||
target_arch: [x64, arm64]
|
||||
steps:
|
||||
- name: Clone Main Repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: 'NapNeko/NapCatQQ'
|
||||
submodules: true
|
||||
ref: main
|
||||
token: ${{ secrets.NAPCAT_BUILD }}
|
||||
- name: Use Node.js 20.X
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20.x
|
||||
- name: Build NuCat Linux
|
||||
run: |
|
||||
npm i --arch=${{ matrix.target_arch }} --platform=${{ matrix.target_platform }}
|
||||
npm run build:prod
|
||||
cd dist
|
||||
npm i --omit=dev --arch=${{ matrix.target_arch }} --platform=${{ matrix.target_platform }}
|
||||
cd ..
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: NapCat.${{ matrix.target_platform }}.${{ matrix.target_arch }}
|
||||
path: dist
|
||||
build-win32:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
target_platform: [win32]
|
||||
target_arch: [x64,ia32]
|
||||
steps:
|
||||
- name: Clone Main Repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: 'NapNeko/NapCatQQ'
|
||||
submodules: true
|
||||
ref: main
|
||||
token: ${{ secrets.NAPCAT_BUILD }}
|
||||
- name: Use Node.js 20.X
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20.x
|
||||
- name: Build NuCat Linux
|
||||
run: |
|
||||
npm i --arch=${{ matrix.target_arch }} --platform=${{ matrix.target_platform }}
|
||||
npm run build:prod
|
||||
cd dist
|
||||
npm i --omit=dev --arch=${{ matrix.target_arch }} --platform=${{ matrix.target_platform }}
|
||||
cd ..
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: NapCat.${{ matrix.target_platform }}.${{ matrix.target_arch }}
|
||||
path: dist
|
10
.prettierrc.json
Normal file
10
.prettierrc.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"trailingComma": "es5",
|
||||
"tabWidth": 4,
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"bracketSpacing": true,
|
||||
"arrowParens": "always",
|
||||
"printWidth": 120,
|
||||
"endOfLine": "auto"
|
||||
}
|
356
LICENSE
356
LICENSE
@@ -1,21 +1,343 @@
|
||||
MIT License
|
||||
GNU GENERAL PUBLIC Without Social media promotion LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (c) 2024 NapCatQQ
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
Preamble
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
License is intended to guarantee your freedom to share and change free
|
||||
software--to make sure the software is free for all its users. This
|
||||
General Public License applies to most of the Free Software
|
||||
Foundation's software and to any other program whose authors commit to
|
||||
using it. (Some other Free Software Foundation software is covered by
|
||||
the GNU Lesser General Public License instead.) You can apply it to
|
||||
your programs, too.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
this service if you wish), that you receive source code or can get it
|
||||
if you want it, that you can change the software or use pieces of it
|
||||
in new free programs; and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
anyone to deny you these rights or to ask you to surrender the rights.
|
||||
These restrictions translate to certain responsibilities for you if you
|
||||
distribute copies of the software, or if you modify it.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must give the recipients all the rights that
|
||||
you have. You must make sure that they, too, receive or can get the
|
||||
source code. And you must show them these terms so they know their
|
||||
rights.
|
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and
|
||||
(2) offer you this license which gives you legal permission to copy,
|
||||
distribute and/or modify the software.
|
||||
|
||||
Also, for each author's protection and ours, we want to make certain
|
||||
that everyone understands that there is no warranty for this free
|
||||
software. If the software is modified by someone else and passed on, we
|
||||
want its recipients to know that what they have is not the original, so
|
||||
that any problems introduced by others will not reflect on the original
|
||||
authors' reputations.
|
||||
|
||||
Finally, any free program is threatened constantly by software
|
||||
patents. We wish to avoid the danger that redistributors of a free
|
||||
program will individually obtain patent licenses, in effect making the
|
||||
program proprietary. To prevent this, we have made it clear that any
|
||||
patent must be licensed for everyone's free use or not licensed at all.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains
|
||||
a notice placed by the copyright holder saying it may be distributed
|
||||
under the terms of this General Public License. The "Program", below,
|
||||
refers to any such program or work, and a "work based on the Program"
|
||||
means either the Program or any derivative work under copyright law:
|
||||
that is to say, a work containing the Program or a portion of it,
|
||||
either verbatim or with modifications and/or translated into another
|
||||
language. (Hereinafter, translation is included without limitation in
|
||||
the term "modification".) Each licensee is addressed as "you".
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running the Program is not restricted, and the output from the Program
|
||||
is covered only if its contents constitute a work based on the
|
||||
Program (independent of having been made by running the Program).
|
||||
Whether that is true depends on what the Program does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Program's
|
||||
source code as you receive it, in any medium, provided that you
|
||||
conspicuously and appropriately publish on each copy an appropriate
|
||||
copyright notice and disclaimer of warranty; keep intact all the
|
||||
notices that refer to this License and to the absence of any warranty;
|
||||
and give any other recipients of the Program a copy of this License
|
||||
along with the Program.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and
|
||||
you may at your option offer warranty protection in exchange for a fee.
|
||||
|
||||
2. You may modify your copy or copies of the Program or any portion
|
||||
of it, thus forming a work based on the Program, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) You must cause the modified files to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in
|
||||
whole or in part contains or is derived from the Program or any
|
||||
part thereof, to be licensed as a whole at no charge to all third
|
||||
parties under the terms of this License.
|
||||
|
||||
c) If the modified program normally reads commands interactively
|
||||
when run, you must cause it, when started running for such
|
||||
interactive use in the most ordinary way, to print or display an
|
||||
announcement including an appropriate copyright notice and a
|
||||
notice that there is no warranty (or else, saying that you provide
|
||||
a warranty) and that users may redistribute the program under
|
||||
these conditions, and telling the user how to view a copy of this
|
||||
License. (Exception: if the Program itself is interactive but
|
||||
does not normally print such an announcement, your work based on
|
||||
the Program is not required to print an announcement.)
|
||||
|
||||
d)You may use this software in accordance with the above terms,
|
||||
but you are not allowed to promote this project or your projects
|
||||
based on this project on any public social media.
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Program,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Program, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Program.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program
|
||||
with the Program (or with a work based on the Program) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it,
|
||||
under Section 2) in object code or executable form under the terms of
|
||||
Sections 1 and 2 above provided that you also do one of the following:
|
||||
|
||||
a) Accompany it with the complete corresponding machine-readable
|
||||
source code, which must be distributed under the terms of Sections
|
||||
1 and 2 above on a medium customarily used for software interchange; or,
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three
|
||||
years, to give any third party, for a charge no more than your
|
||||
cost of physically performing source distribution, a complete
|
||||
machine-readable copy of the corresponding source code, to be
|
||||
distributed under the terms of Sections 1 and 2 above on a medium
|
||||
customarily used for software interchange; or,
|
||||
|
||||
c) Accompany it with the information you received as to the offer
|
||||
to distribute corresponding source code. (This alternative is
|
||||
allowed only for noncommercial distribution and only if you
|
||||
received the program in object code or executable form with such
|
||||
an offer, in accord with Subsection b above.)
|
||||
|
||||
The source code for a work means the preferred form of the work for
|
||||
making modifications to it. For an executable work, complete source
|
||||
code means all the source code for all modules it contains, plus any
|
||||
associated interface definition files, plus the scripts used to
|
||||
control compilation and installation of the executable. However, as a
|
||||
special exception, the source code distributed need not include
|
||||
anything that is normally distributed (in either source or binary
|
||||
form) with the major components (compiler, kernel, and so on) of the
|
||||
operating system on which the executable runs, unless that component
|
||||
itself accompanies the executable.
|
||||
|
||||
If distribution of executable or object code is made by offering
|
||||
access to copy from a designated place, then offering equivalent
|
||||
access to copy the source code from the same place counts as
|
||||
distribution of the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program
|
||||
except as expressly provided under this License. Any attempt
|
||||
otherwise to copy, modify, sublicense or distribute the Program is
|
||||
void, and will automatically terminate your rights under this License.
|
||||
However, parties who have received copies, or rights, from you under
|
||||
this License will not have their licenses terminated so long as such
|
||||
parties remain in full compliance.
|
||||
|
||||
5. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Program or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Program (or any work based on the
|
||||
Program), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Program or works based on it.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the
|
||||
Program), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute or modify the Program subject to
|
||||
these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties to
|
||||
this License.
|
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Program at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Program by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Program.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under
|
||||
any particular circumstance, the balance of the section is intended to
|
||||
apply and the section as a whole is intended to apply in other
|
||||
circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system, which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Program under this License
|
||||
may add an explicit geographical distribution limitation excluding
|
||||
those countries, so that distribution is permitted only in or among
|
||||
countries not thus excluded. In such case, this License incorporates
|
||||
the limitation as if written in the body of this License.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions
|
||||
of the General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program
|
||||
specifies a version number of this License which applies to it and "any
|
||||
later version", you have the option of following the terms and conditions
|
||||
either of that version or of any later version published by the Free
|
||||
Software Foundation. If the Program does not specify a version number of
|
||||
this License, you may choose any version ever published by the Free Software
|
||||
Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free
|
||||
programs whose distribution conditions are different, write to the author
|
||||
to ask for permission. For software which is copyrighted by the Free
|
||||
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||
make exceptions for this. Our decision will be guided by the two goals
|
||||
of preserving the free status of all derivatives of our free software and
|
||||
of promoting the sharing and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||
REPAIR OR CORRECTION.
|
||||
|
||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program is interactive, make it output a short notice like this
|
||||
when it starts in an interactive mode:
|
||||
|
||||
Gnomovision version 69, Copyright (C) year name of author
|
||||
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, the commands you use may
|
||||
be called something other than `show w' and `show c'; they could even be
|
||||
mouse-clicks or menu items--whatever suits your program.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1989
|
||||
Ty Coon, President of Vice
|
||||
|
||||
This General Public License does not permit incorporating your program into
|
||||
proprietary programs. If your program is a subroutine library, you may
|
||||
consider it more useful to permit linking proprietary applications with the
|
||||
library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License.
|
||||
|
53
README.md
53
README.md
@@ -1,33 +1,52 @@
|
||||
<div align="center">
|
||||
<img src="https://socialify.git.ci/NapNeko/NapCatQQ/image?description=1&language=1&logo=https%3A%2F%2Fraw.githubusercontent.com%2FNapNeko%2FNapCatQQ%2Fmain%2Flogo.png&name=1&stargazers=1&theme=Auto" alt="NapCatQQ" width="640" height="320" />
|
||||
<img src="https://socialify.git.ci/NapNeko/NapCatQQ/image?font=Jost&logo=https%3A%2F%2Fnapneko.github.io%2Fassets%2Flogo.png&name=1&owner=1&pattern=Diagonal%20Stripes&stargazers=1&theme=Auto" alt="NapCatQQ" width="640" height="320" />
|
||||
</div>
|
||||
|
||||
## 项目介绍
|
||||
---
|
||||
## 欢迎回来
|
||||
NapCatQQ (aka 猫猫框架) 是现代化的基于 NTQQ 的 Bot 协议端实现
|
||||
|
||||
NapCatQQ 是基于 PC NTQQ 本体实现一套无头 Bot 框架。
|
||||
## 猫猫技能
|
||||
- [x] **超高性能**:轻松数千群聊 独创消息队列
|
||||
- [x] **启动方式**:支持以无头、LiteLoader 插件、仅 QQ GUI 三种方式启动
|
||||
- [x] **覆盖平台**: 覆盖 Windows / Linux (可选 Docker) / Android Termux / MacOS
|
||||
- [x] **安装简单**: 支持一键脚本/程序自动部署/镜像部署等多种覆盖范围
|
||||
- [x] **超低占用**:无头模式占用资源极低,适合在服务器上运行
|
||||
- [x] **超多接口**:实现大部分 OneBot 和 go-cqhttp 接口,超多扩展 API
|
||||
- [x] **远程管理**:自带 WebUI 支持,远程管理更加便捷
|
||||
- [x] **扩展支持**:基于 MoeHoo 的Native 可实现发包与收包
|
||||
|
||||
名字寓意 瞌睡猫QQ,像睡着了一样在后台低占用运行的无需GUI界面的NTQQ。
|
||||
|
||||
## 如何使用
|
||||
## 使用猫猫
|
||||
|
||||
可前往 [Release](https://github.com/NapNeko/NapCatQQ/releases/) 页面下载最新版本
|
||||
|
||||
**首次使用** 请务必前往 [官方文档](https://napneko.github.io/) 查看使用文档与教程
|
||||
**首次使用**请务必查看如下文档看使用教程
|
||||
|
||||
### 文档地址
|
||||
[Github.IO](https://napneko.github.io/)
|
||||
|
||||
## 项目声明
|
||||
[Cloudflare.Worker](https://doc.napneko.icu/)
|
||||
|
||||
* 请不要在无关地方宣传NapCatQQ,本项目只是用于学习 node 相关知识,切勿用于违法用途
|
||||
[Cloudflare.HKServer](https://napcat.napneko.icu/)
|
||||
|
||||
[Cloudflare.Pages](https://napneko.pages.dev/)
|
||||
|
||||
## 回家旅途
|
||||
[QQ Group](https://qm.qq.com/q/VfjAq5HIMS)
|
||||
|
||||
* NapCat 不会收集用户隐私信息,但是未来可能会为了更好的利于 NapCat 的优化会收集一些设备信息,如 cpu 架构,系统版本等
|
||||
|
||||
## 相关链接
|
||||
[Telegram Link](https://t.me/+nLZEnpne-pQ1OWFl)
|
||||
|
||||
## 鸣谢名单
|
||||
## 猫猫朋友
|
||||
感谢 [LLOneBot](https://github.com/LLOneBot/LLOneBot)
|
||||
|
||||
[Lagrange](https://github.com/LagrangeDev/Lagrange.Core)
|
||||
感谢 [Lagrange](https://github.com/LagrangeDev/Lagrange.Core) 对本项目的大力支持
|
||||
|
||||
<!--
|
||||
QQ群:545402644
|
||||
-->
|
||||
不过最最重要的 还是需要感谢屏幕前的你哦~
|
||||
|
||||
---
|
||||
|
||||
## 约法三章
|
||||
> [!CAUTION]\
|
||||
> **请不要在 QQ 官方群聊和任何影响力较大的简中互联网平台(包括但不限于: 哔哩哔哩,微博,知乎,抖音等)发布和讨论*任何*与本项目存在相关性的信息**
|
||||
|
||||
任何使用本仓库代码的地方,都应当严格遵守[本仓库开源许可](./LICENSE)。**此外,禁止任何项目未经授权二次分发或基于 NapCat 代码开发。**
|
||||
|
@@ -1,32 +0,0 @@
|
||||
# v1.6.8
|
||||
|
||||
QQ Version: Windows 9.9.12-26000 / Linux 3.2.9-26000
|
||||
## 使用前警告
|
||||
1. 在最近版本由于QQ本体大幅变动,为了保证NapCat可用性,NapCat近期启动与安装方式将将大幅变动,请关注文档和社群获取。
|
||||
2. 在Core上完全执行开源,请不要用于违法用途,如此可能造成NapCat完全停止更新。
|
||||
3. 针对原启动方式的围堵,NapCat研发了多种方式,除此其余理论与扩展的分析和思路将部分展示于Docs,以便各位参与开发与维护NapCat。
|
||||
## 其余·备注
|
||||
启动方式: WayBoot.03 (Electron Main进程为Node 直接注入代码 同理项目: LiteLoader)
|
||||
|
||||
## 修复与优化
|
||||
1. 移除数据库文件读写 ~ 优化性能
|
||||
2. 重构消息发送 极限速度优化 ~ 优化性能
|
||||
3. WebUi配置热重载优化 ~ 修复问题
|
||||
4. 修复偶现崩溃问题 ~ 修复问题
|
||||
5. 修复群邀请通知事件多次推送问题 ~ 修复问题
|
||||
6. 尝试修复因缓存引起的字段不全问题 ~ 修复问题
|
||||
7. 修复在非常非常高并发的情况 上报自身消息 回复回错问题 ~ 修复问题
|
||||
8. 修复图片SubType字段位置错误问题 ~ 修复问题
|
||||
9. 修复Uid/Uin转换问题 ~ 修复问题
|
||||
|
||||
## 新增与调整
|
||||
1. 最后发言时间重构 入群时间失效 ~ 替换功能
|
||||
2. 重构文件发送/获取 ~ 优化性能
|
||||
3. 支持GOCQ私聊上传接口 ~ 新增功能
|
||||
4. 悄悄告诉你ws http可以同一个端口 ~ 新增功能
|
||||
5. 根据config目录的默认配置初始化新的配置文件 ~ 新增功能
|
||||
6. WebUi可以部署在nginx代理二级目录 配置端口设置为0可关闭WebUi ~ 新增功能
|
||||
7. 新增收藏表情拉取接口 ~ 新增功能
|
||||
8. 新增群头像设置接口 ~ 新增功能
|
||||
|
||||
新增的 API 详细见[API文档](https://napneko.github.io/zh-CN/develop/extends_api)
|
@@ -1,17 +0,0 @@
|
||||
# v1.3.3
|
||||
|
||||
QQ Version: Windows 9.9.9-23424 / Linux 3.2.7-23361
|
||||
|
||||
## 修复与优化
|
||||
* 尝试修复多开崩溃问题
|
||||
* 修复群列表更新问题
|
||||
* 修复兼容性问题支持Win7
|
||||
* 修复下载 http 资源缺少UA
|
||||
* 优化少量消息合并转发速度
|
||||
* 修复加载群通知时出现 getUserDetailInfo timeout 导致程序崩溃
|
||||
|
||||
## 新增与调整
|
||||
* 新增设置群公告 Api: /_send_group_notice
|
||||
* 新增重启实现 包括重启快速登录/普通重启 副作用: 原进程 无法清理
|
||||
|
||||
新增的 API 详细见[API文档](https://napneko.github.io/zh-CN/develop/extends_api)
|
@@ -1,18 +0,0 @@
|
||||
# v1.3.5
|
||||
|
||||
QQ Version: Windows 9.9.9-23424 / Linux 3.2.7-23361
|
||||
|
||||
## 修复与优化
|
||||
* 优化启动脚本
|
||||
* 修复非管理时群成员减少事件上报 **无法获取操作者与操作类型**
|
||||
* 修复快速重启进程清理问题
|
||||
* 优化配置文件格式 支持自动更新配置 但仍然建议 **备份配置**
|
||||
* 修复正向反向ws多个客户端周期多次心跳问题
|
||||
|
||||
## 新增与调整
|
||||
* 支持WebUi热重载
|
||||
* 新增启动输出WEBUI秘钥
|
||||
* 新增群荣誉信息 /get_group_honor_info
|
||||
* 支持获取群系统消息 /get_group_system_msg
|
||||
|
||||
新增的 API 详细见[API文档](https://napneko.github.io/zh-CN/develop/extends_api)
|
@@ -1,11 +0,0 @@
|
||||
# v1.3.6
|
||||
|
||||
QQ Version: Windows 9.9.9-23424 / Linux 3.2.7-23361
|
||||
|
||||
## 修复与优化
|
||||
* 修复戳一戳多次上报问题
|
||||
|
||||
## 新增与调整
|
||||
|
||||
|
||||
新增的 API 详细见[API文档](https://napneko.github.io/zh-CN/develop/extends_api)
|
@@ -1,15 +0,0 @@
|
||||
# v1.3.8
|
||||
|
||||
QQ Version: Windows 9.9.9-23873 / Linux 3.2.7-23361
|
||||
|
||||
## 修复与优化
|
||||
* 优化打包后体积问题
|
||||
* 修复QQ等级获取
|
||||
* 兼容 9.7.x 版本换行符 统一为 \n
|
||||
* 修复处理加群请求 字段异常情况
|
||||
* 修复退群通知问题
|
||||
|
||||
## 新增与调整
|
||||
|
||||
|
||||
新增的 API 详细见[API文档](https://napneko.github.io/zh-CN/develop/extends_api)
|
@@ -1,11 +0,0 @@
|
||||
# v1.3.9
|
||||
|
||||
QQ Version: Windows 9.9.10-23873 / Linux 3.2.7-23361
|
||||
|
||||
## 修复与优化
|
||||
* 修复QQ等级获取与兼容性问题
|
||||
|
||||
## 新增与调整
|
||||
|
||||
|
||||
新增的 API 详细见[API文档](https://napneko.github.io/zh-CN/develop/extends_api)
|
@@ -1,12 +0,0 @@
|
||||
# v1.4.0
|
||||
|
||||
QQ Version: Windows 9.9.10-24108 / Linux 3.2.7-23361
|
||||
|
||||
## 修复与优化
|
||||
|
||||
## 新增与调整
|
||||
* 支持空间Cookies获取
|
||||
* 支持获取在线设备 API /get_online_clients
|
||||
* 支持图片OCR API: /.ocr_image /ocr_image
|
||||
|
||||
新增的 API 详细见[API文档](https://napneko.github.io/zh-CN/develop/extends_api)
|
@@ -1,14 +0,0 @@
|
||||
# v1.4.1
|
||||
|
||||
QQ Version: Windows 9.9.10-24108 / Linux 3.2.7-23361
|
||||
|
||||
## 修复与优化
|
||||
* 提高部分Api兼容性
|
||||
* 优化日志膨胀问题
|
||||
* 在线状态刷新问题修复
|
||||
## 新增与调整
|
||||
* 支持非管理群 本地记录时间数据 (建议 **备份配置 清空配置 重新配置**)
|
||||
* 新增英译中接口 Api: /translate_en2zh
|
||||
* 新增群文件管理相关扩展接口 Api: /get_group_file_count /get_group_file_list /set_group_file_folder /del_group_file /del_group_file_folder
|
||||
|
||||
新增的 API 详细见[API文档](https://napneko.github.io/zh-CN/develop/extends_api)
|
@@ -1,12 +0,0 @@
|
||||
# v1.4.2
|
||||
|
||||
QQ Version: Windows 9.9.10-24108 / Linux 3.2.7-23361
|
||||
|
||||
## 修复与优化
|
||||
* 修复获取群文件列表Api
|
||||
* 修复退群通知问题
|
||||
|
||||
## 新增与调整
|
||||
|
||||
|
||||
新增的 API 详细见[API文档](https://napneko.github.io/zh-CN/develop/extends_api)
|
@@ -1,11 +0,0 @@
|
||||
# v1.4.3
|
||||
|
||||
QQ Version: Windows 9.9.10-24108 / Linux 3.2.7-23361
|
||||
|
||||
## 修复与优化
|
||||
* 修复名片通知
|
||||
|
||||
## 新增与调整
|
||||
|
||||
|
||||
新增的 API 详细见[API文档](https://napneko.github.io/zh-CN/develop/extends_api)
|
@@ -1,10 +0,0 @@
|
||||
# v1.4.4
|
||||
|
||||
QQ Version: Windows 9.9.10-24108 / Linux 3.2.7-23361
|
||||
|
||||
## 更新
|
||||
* **重大更新:**更新了版本号
|
||||
|
||||
|
||||
新增的 API 详细见[API文档](https://napneko.github.io/zh-CN/develop/extends_api)
|
||||
|
@@ -1,12 +0,0 @@
|
||||
# v1.4.5
|
||||
|
||||
QQ Version: Windows 9.9.10-24108 / Linux 3.2.7-23361
|
||||
|
||||
## 修复与优化
|
||||
* 紧急修复二维扫码问题
|
||||
|
||||
## 新增与调整
|
||||
|
||||
|
||||
新增的 API 详细见[API文档](https://napneko.github.io/zh-CN/develop/extends_api)
|
||||
|
@@ -1,12 +0,0 @@
|
||||
# v1.4.6
|
||||
|
||||
QQ Version: Windows 9.9.10-24108 / Linux 3.2.7-23361
|
||||
|
||||
## 修复与优化
|
||||
* 优化整体稳定性
|
||||
|
||||
## 新增与调整
|
||||
|
||||
|
||||
新增的 API 详细见[API文档](https://napneko.github.io/zh-CN/develop/extends_api)
|
||||
|
@@ -1,11 +0,0 @@
|
||||
# v1.4.7
|
||||
|
||||
QQ Version: Windows 9.9.10-24108 / Linux 3.2.7-23361
|
||||
|
||||
## 修复与优化
|
||||
* 临时扩展 Api: GoCQHTTPUploadGroupFile folder_id字段 用于选择文件夹
|
||||
|
||||
## 新增与调整
|
||||
|
||||
|
||||
新增的 API 详细见[API文档](https://napneko.github.io/zh-CN/develop/extends_api)
|
@@ -1,12 +0,0 @@
|
||||
# v1.4.8
|
||||
|
||||
QQ Version: Windows 9.9.10-24108 / Linux 3.2.7-23361
|
||||
|
||||
## 修复与优化
|
||||
* 优化Guid的生成方式
|
||||
* 支持临时消息获取群来源
|
||||
|
||||
## 新增与调整
|
||||
|
||||
|
||||
新增的 API 详细见[API文档](https://napneko.github.io/zh-CN/develop/extends_api)
|
@@ -1,11 +0,0 @@
|
||||
# v1.4.9
|
||||
|
||||
QQ Version: Windows 9.9.10-24108 / Linux 3.2.7-23361
|
||||
|
||||
## 修复与优化
|
||||
* 修复接口调用问题 接口标准化 API:set_group_add_request
|
||||
|
||||
## 新增与调整
|
||||
|
||||
|
||||
新增的 API 详细见[API文档](https://napneko.github.io/zh-CN/develop/extends_api)
|
@@ -1,11 +0,0 @@
|
||||
# v1.5.0
|
||||
|
||||
QQ Version: Windows 9.9.10-24108 / Linux 3.2.7-23361
|
||||
|
||||
## 修复与优化
|
||||
* 修正各Api默认值
|
||||
|
||||
## 新增与调整
|
||||
|
||||
|
||||
新增的 API 详细见[API文档](https://napneko.github.io/zh-CN/develop/extends_api)
|
@@ -1,12 +0,0 @@
|
||||
# v1.5.1
|
||||
|
||||
QQ Version: Windows 9.9.10-24108 / Linux 3.2.7-23361
|
||||
|
||||
## 修复与优化
|
||||
* 支持 新Api: set_self_profile 可设置个性签名
|
||||
* 修复 Api: get_group_system_msg
|
||||
* 整理日志、添加颜色、使用统一的日志函数以提高日志可读性
|
||||
## 新增与调整
|
||||
|
||||
|
||||
新增的 API 详细见[API文档](https://napneko.github.io/zh-CN/develop/extends_api)
|
@@ -1,13 +0,0 @@
|
||||
# v1.5.2
|
||||
|
||||
QQ Version: Windows 9.9.10-24108 / Linux 3.2.7-23361
|
||||
|
||||
## 修复与优化
|
||||
* 替换Uid/Uin为内部实现
|
||||
* 增加HttpApi调用稳定性
|
||||
* 修复 GetMsg 兼容性
|
||||
|
||||
## 新增与调整
|
||||
* 支持真正意义上的陌生人信息获取 Api: GoCQHTTP_GetStrangerInfo
|
||||
|
||||
新增的 API 详细见[API文档](https://napneko.github.io/zh-CN/develop/extends_api)
|
@@ -1,15 +0,0 @@
|
||||
# v1.5.3
|
||||
|
||||
QQ Version: Windows 9.9.11-24568 / Linux 3.2.9-23568
|
||||
|
||||
## 修复与优化
|
||||
* 修复引用消息id问题
|
||||
* 修复添加好友的通知
|
||||
|
||||
## 新增与调整
|
||||
* 扩展群分享Json生成
|
||||
* 扩展关于收藏的一系列接口
|
||||
* 支持专属群头衔获取
|
||||
* 支持视频获取直链
|
||||
|
||||
新增的 API 详细见[API文档](https://napneko.github.io/zh-CN/develop/extends_api)
|
@@ -1,11 +0,0 @@
|
||||
# v1.5.4
|
||||
|
||||
QQ Version: Windows 9.9.11-24568 / Linux 3.2.9-23568
|
||||
|
||||
## 修复与优化
|
||||
* 紧急修复视频与文件问题
|
||||
|
||||
## 新增与调整
|
||||
|
||||
|
||||
新增的 API 详细见[API文档](https://napneko.github.io/zh-CN/develop/extends_api)
|
@@ -1,11 +0,0 @@
|
||||
# v1.5.5
|
||||
|
||||
QQ Version: Windows 9.9.11-24568 / Linux 3.2.9-23568
|
||||
|
||||
## 修复与优化
|
||||
* 紧急修复一些问题
|
||||
|
||||
## 新增与调整
|
||||
|
||||
|
||||
新增的 API 详细见[API文档](https://napneko.github.io/zh-CN/develop/extends_api)
|
@@ -1,11 +0,0 @@
|
||||
# v1.5.6
|
||||
|
||||
QQ Version: Windows 9.9.11-24568 / Linux 3.2.9-24568
|
||||
|
||||
## 修复与优化
|
||||
* 修复一些问题
|
||||
|
||||
## 新增与调整
|
||||
|
||||
|
||||
新增的 API 详细见[API文档](https://napneko.github.io/zh-CN/develop/extends_api)
|
@@ -1,11 +0,0 @@
|
||||
# v1.5.7
|
||||
|
||||
QQ Version: Windows 9.9.11-24568 / Linux 3.2.9-24568
|
||||
|
||||
## 修复与优化
|
||||
* 修复一些问题
|
||||
|
||||
## 新增与调整
|
||||
|
||||
|
||||
新增的 API 详细见[API文档](https://napneko.github.io/zh-CN/develop/extends_api)
|
@@ -1,14 +0,0 @@
|
||||
# v1.5.8
|
||||
|
||||
QQ Version: Windows 9.9.11-24568 / Linux 3.2.9-24568
|
||||
|
||||
## 修复与优化
|
||||
* 修复视频文件残留问题
|
||||
* 重构 getcookies接口 支持大部分常见域
|
||||
|
||||
## 新增与调整
|
||||
* 日志大小限制
|
||||
* 支持 QQ音乐 卡片 无签名支持时 启用内置方法(缺点没有封面 限速1min/条)
|
||||
* 支持Window X86-32机器
|
||||
|
||||
新增的 API 详细见[API文档](https://napneko.github.io/zh-CN/develop/extends_api)
|
@@ -1,12 +0,0 @@
|
||||
# v1.5.9
|
||||
|
||||
QQ Version: Windows 9.9.11-24815 / Linux 3.2.9-24815
|
||||
|
||||
## 修复与优化
|
||||
* 优化缓存问题
|
||||
* 修复poke异常上报
|
||||
|
||||
## 新增与调整
|
||||
|
||||
|
||||
新增的 API 详细见[API文档](https://napneko.github.io/zh-CN/develop/extends_api)
|
@@ -1,11 +0,0 @@
|
||||
# v1.6.0
|
||||
|
||||
QQ Version: Windows 9.9.11-24815 / Linux 3.2.9-24815
|
||||
|
||||
## 修复与优化
|
||||
|
||||
|
||||
## 新增与调整
|
||||
* 新增图片subtype属性 区分表情图片与商城图片
|
||||
|
||||
新增的 API 详细见[API文档](https://napneko.github.io/zh-CN/develop/extends_api)
|
@@ -1,11 +0,0 @@
|
||||
# v1.6.1
|
||||
|
||||
QQ Version: Windows 9.9.11-24815 / Linux 3.2.9-24815
|
||||
|
||||
## 修复与优化
|
||||
|
||||
|
||||
## 新增与调整
|
||||
* 修复poke异常事件
|
||||
|
||||
新增的 API 详细见[API文档](https://napneko.github.io/zh-CN/develop/extends_api)
|
@@ -1,13 +0,0 @@
|
||||
# v1.6.2
|
||||
|
||||
QQ Version: Windows 9.9.11-24815 / Linux 3.2.9-24815
|
||||
|
||||
## 修复与优化
|
||||
* 修复获取Cookies异常崩溃问题
|
||||
* 尝试修复成员退群缓存问题
|
||||
* 修复自身退群后群缓存清理问题
|
||||
|
||||
## 新增与调整
|
||||
|
||||
|
||||
新增的 API 详细见[API文档](https://napneko.github.io/zh-CN/develop/extends_api)
|
@@ -1,13 +0,0 @@
|
||||
# v1.6.3
|
||||
|
||||
QQ Version: Windows 9.9.11-24815 / Linux 3.2.9-24815
|
||||
|
||||
## 修复与优化
|
||||
* 修复带有groupid的私聊消息异常发送到群聊消息
|
||||
* 尝试修复rws热重载失效问题
|
||||
* 尝试修复进群事件无法正常获取uin
|
||||
|
||||
## 新增与调整
|
||||
|
||||
|
||||
新增的 API 详细见[API文档](https://napneko.github.io/zh-CN/develop/extends_api)
|
@@ -1,18 +0,0 @@
|
||||
# v1.6.4
|
||||
|
||||
QQ Version: Windows 9.9.12-26000 / Linux 3.2.9-26000
|
||||
## 使用前警告
|
||||
1. 在最近版本由于QQ本体大幅变动,为了保证NapCat可用性,NapCat近期启动与安装方式将将大幅变动,请关注文档和社群获取。
|
||||
2. 在Core上完全执行开源,请不要用于违法用途,如此可能造成NapCat完全停止更新。
|
||||
3. 针对原启动方式的围堵,NapCat研发了多种方式,除此其余理论与扩展的分析和思路将部分展示于Docs,以便各位参与开发与维护NapCat。
|
||||
## 其余·备注
|
||||
启动方式: WayBoot.03 (Electron Main进程为Node 直接注入代码 同理项目: LiteLoader)
|
||||
|
||||
## 修复与优化
|
||||
1. 支持Win平台 9.9.12
|
||||
2. 修复部分发送图片下载异常情况
|
||||
|
||||
## 新增与调整
|
||||
没有哦
|
||||
|
||||
新增的 API 详细见[API文档](https://napneko.github.io/zh-CN/develop/extends_api)
|
@@ -1,18 +0,0 @@
|
||||
# v1.6.5
|
||||
|
||||
QQ Version: Windows 9.9.12-26000 / Linux 3.2.9-26000
|
||||
## 使用前警告
|
||||
1. 在最近版本由于QQ本体大幅变动,为了保证NapCat可用性,NapCat近期启动与安装方式将将大幅变动,请关注文档和社群获取。
|
||||
2. 在Core上完全执行开源,请不要用于违法用途,如此可能造成NapCat完全停止更新。
|
||||
3. 针对原启动方式的围堵,NapCat研发了多种方式,除此其余理论与扩展的分析和思路将部分展示于Docs,以便各位参与开发与维护NapCat。
|
||||
## 其余·备注
|
||||
启动方式: WayBoot.03 (Electron Main进程为Node 直接注入代码 同理项目: LiteLoader)
|
||||
|
||||
## 修复与优化
|
||||
1. 优化了WrapperNative载入代码
|
||||
2. 优化缓存
|
||||
|
||||
## 新增与调整
|
||||
没有哦
|
||||
|
||||
新增的 API 详细见[API文档](https://napneko.github.io/zh-CN/develop/extends_api)
|
@@ -1,17 +0,0 @@
|
||||
# v1.6.6
|
||||
|
||||
QQ Version: Windows 9.9.12-26000 / Linux 3.2.9-26000
|
||||
## 使用前警告
|
||||
1. 在最近版本由于QQ本体大幅变动,为了保证NapCat可用性,NapCat近期启动与安装方式将将大幅变动,请关注文档和社群获取。
|
||||
2. 在Core上完全执行开源,请不要用于违法用途,如此可能造成NapCat完全停止更新。
|
||||
3. 针对原启动方式的围堵,NapCat研发了多种方式,除此其余理论与扩展的分析和思路将部分展示于Docs,以便各位参与开发与维护NapCat。
|
||||
## 其余·备注
|
||||
启动方式: WayBoot.03 (Electron Main进程为Node 直接注入代码 同理项目: LiteLoader)
|
||||
|
||||
## 修复与优化
|
||||
1. 修复了一些问题
|
||||
|
||||
## 新增与调整
|
||||
没有哦
|
||||
|
||||
新增的 API 详细见[API文档](https://napneko.github.io/zh-CN/develop/extends_api)
|
@@ -1,49 +0,0 @@
|
||||
|
||||
public static final int C2C_PIC_DOWNLOAD = 1004;
|
||||
public static final String C2C_PIC_DOWNLOAD_DOMAIN = "c2cpicdw.qpic.cn";
|
||||
public static final String C2C_PIC_DOWNLOAD_QUIC_DOMAIN = "c2cpicdw.quic.qpic.cn";
|
||||
public static final int FLAG_NOT_UPLOAD = 3;
|
||||
public static final int FLAG_UPLOADINFO_ERROR = 4;
|
||||
public static final int GROUP_PIC_DOWNLOAD = 1000;
|
||||
public static final String GROUP_PIC_DOWNLOAD_DOMAIN = "gchat.qpic.cn";
|
||||
public static final String GROUP_PIC_DOWNLOAD_QUIC_DOMAIN = "gchat.quic.qpic.cn";
|
||||
public static final String GUILD_PIC_DOWNLOAD_DOMAIN = "gchat.qpic.cn/qmeetpic";
|
||||
public static final boolean NEW_STORE_FLAG = true;
|
||||
public static final String PTT_VIDEO_DOWNLOAD_DOMAIN = "grouptalk.c2c.qq.com";
|
||||
|
||||
protected static final int AVIF_DECODE_EXCEPTION = 4;
|
||||
protected static final int AVIF_DECODE_FAIL = 1;
|
||||
protected static final int AVIF_DECODE_FAIL_SO_FAIL = 2;
|
||||
protected static final int AVIF_DECODE_FAIL_UNKNOWN = 6;
|
||||
protected static final int AVIF_DECODE_FILETYPE_ERROR = 5;
|
||||
protected static final int AVIF_DECODE_OOM = 3;
|
||||
protected static final int AVIF_DECODE_RENAME_FAIL = 7;
|
||||
protected static final int AVIF_DECODE_SUC = 0;
|
||||
public static final String AVIF_FILE_SUFFIX = ".avif";
|
||||
public static final int AVIF_REQ_APPRUNTIME_NULL = 12;
|
||||
public static final int AVIF_REQ_CODEC_UNSURPPORT = 5;
|
||||
protected static final int AVIF_REQ_DENSITY_UNSURPPORT = 10;
|
||||
protected static final int AVIF_REQ_FLASH_PHOTO = 9;
|
||||
protected static final int AVIF_REQ_HAS_TMP_AVIF = 7;
|
||||
protected static final int AVIF_REQ_INVALID_MSG_RECORD = 2;
|
||||
protected static final int AVIF_REQ_IS_RAW_PHOTO = 3;
|
||||
protected static final int AVIF_REQ_OUTPUTSTREAM_UNSURPPORT = 11;
|
||||
protected static final int AVIF_REQ_OVERSIZE = 6;
|
||||
protected static final int AVIF_REQ_RETRY = 1;
|
||||
public static final int AVIF_REQ_SO_DOWNLOAD_FAILED = 8;
|
||||
protected static final int AVIF_REQ_SUC = 0;
|
||||
public static final int AVIF_REQ_SWITCH_CLOSE = 4;
|
||||
public static final String C2C_PIC_DOWNLOAD_ERROR_CODE = "C2CPicDownloadErrorCode";
|
||||
static final int DOWNLOAD_ST_COMPLETE = 1;
|
||||
static final int DOWNLOAD_ST_HEAD = 2;
|
||||
static final int DOWNLOAD_ST_LEFT = 4;
|
||||
static final int DOWNLOAD_ST_PART = 3;
|
||||
private static final int ENCRYPT_APPID = 1600000226;
|
||||
public static final String GROUP_PIC_DOWNLOAD_ERROR_CODE = "GroupPicDownloadErrorCode";
|
||||
public static final String KEY_PIC_DOWNLOAD_ERROR_CODE = "param_detail_code";
|
||||
protected static final int QUIC_FAIL_IP_LIST_EMPTY = 1;
|
||||
protected static final int QUIC_FAIL_REQUEST_HTTPS = 3;
|
||||
protected static final int QUIC_FAIL_REQUEST_QUIC = 2;
|
||||
protected static final int QUIC_FAIL_SO_LOAD = 4;
|
||||
public static final String REPORT_TAG_DIRECT_DOWNLOAD_FAIL = "report_direct_download_fail";
|
||||
public static final String REQ_PARAM_AVIF = "tp=avif";
|
@@ -1,444 +0,0 @@
|
||||
```java
|
||||
MsgConstant
|
||||
int ARKSTRUCTELEMENTSUBTYPETENCENTDOCFROMMINIAPP = 1;
|
||||
int ARKSTRUCTELEMENTSUBTYPETENCENTDOCFROMPLUSPANEL = 2;
|
||||
int ARKSTRUCTELEMENTSUBTYPEUNKNOWN = 0;
|
||||
int ATTYPEALL = 1;
|
||||
int ATTYPECATEGORY = 512;
|
||||
int ATTYPECHANNEL = 16;
|
||||
int ATTYPEME = 4;
|
||||
int ATTYPEONE = 2;
|
||||
int ATTYPEONLINE = 64;
|
||||
int ATTYPEROLE = 8;
|
||||
int ATTYPESUMMON = 32;
|
||||
int ATTYPESUMMONONLINE = 128;
|
||||
int ATTYPESUMMONROLE = 256;
|
||||
int ATTYPEUNKNOWN = 0;
|
||||
int CALENDARELEMSUBTYPECOMMON = 3;
|
||||
int CALENDARELEMSUBTYPESTRONG = 1;
|
||||
int CALENDARELEMSUBTYPEUNKNOWN = 0;
|
||||
int CALENDARELEMSUBTYPEWEAK = 2;
|
||||
int FACEBUBBLEELEMSUBTYPENORMAL = 1;
|
||||
int FACEBUBBLEELEMSUBTYPEUNKNOWN = 0;
|
||||
int FETCHLONGMSGERRCODEMSGEXPIRED = 196;
|
||||
int FILEELEMENTSUBTYPEAI = 16;
|
||||
int FILEELEMENTSUBTYPEAPP = 11;
|
||||
int FILEELEMENTSUBTYPEAUDIO = 3;
|
||||
int FILEELEMENTSUBTYPEDOC = 4;
|
||||
int FILEELEMENTSUBTYPEEMOTICON = 15;
|
||||
int FILEELEMENTSUBTYPEEXCEL = 6;
|
||||
int FILEELEMENTSUBTYPEFOLDER = 13;
|
||||
int FILEELEMENTSUBTYPEHTML = 10;
|
||||
int FILEELEMENTSUBTYPEIPA = 14;
|
||||
int FILEELEMENTSUBTYPENORMAL = 0;
|
||||
int FILEELEMENTSUBTYPEPDF = 7;
|
||||
int FILEELEMENTSUBTYPEPIC = 1;
|
||||
int FILEELEMENTSUBTYPEPPT = 5;
|
||||
int FILEELEMENTSUBTYPEPSD = 12;
|
||||
int FILEELEMENTSUBTYPETXT = 8;
|
||||
int FILEELEMENTSUBTYPEVIDEO = 2;
|
||||
int FILEELEMENTSUBTYPEZIP = 9;
|
||||
int GRAYTIPELEMENTSUBTYPEAIOOP = 15;
|
||||
int GRAYTIPELEMENTSUBTYPEBLOCK = 14;
|
||||
int GRAYTIPELEMENTSUBTYPEBUDDY = 5;
|
||||
int GRAYTIPELEMENTSUBTYPEBUDDYNOTIFY = 9;
|
||||
int GRAYTIPELEMENTSUBTYPEEMOJIREPLY = 3;
|
||||
int GRAYTIPELEMENTSUBTYPEESSENCE = 7;
|
||||
int GRAYTIPELEMENTSUBTYPEFEED = 6;
|
||||
int GRAYTIPELEMENTSUBTYPEFEEDCHANNELMSG = 11;
|
||||
int GRAYTIPELEMENTSUBTYPEFILE = 10;
|
||||
int GRAYTIPELEMENTSUBTYPEGROUP = 4;
|
||||
int GRAYTIPELEMENTSUBTYPEGROUPNOTIFY = 8;
|
||||
int GRAYTIPELEMENTSUBTYPEJSON = 17;
|
||||
int GRAYTIPELEMENTSUBTYPELOCALMSG = 13;
|
||||
int GRAYTIPELEMENTSUBTYPEPROCLAMATION = 2;
|
||||
int GRAYTIPELEMENTSUBTYPEREVOKE = 1;
|
||||
int GRAYTIPELEMENTSUBTYPEUNKNOWN = 0;
|
||||
int GRAYTIPELEMENTSUBTYPEWALLET = 16;
|
||||
int GRAYTIPELEMENTSUBTYPEXMLMSG = 12;
|
||||
int INLINEKEYBOARDBUTTONRENDERSTYLEBLUEBLACKGROUND = 4;
|
||||
int INLINEKEYBOARDBUTTONRENDERSTYLEBLUEBORDER = 1;
|
||||
int INLINEKEYBOARDBUTTONRENDERSTYLEGRAYBORDER = 0;
|
||||
int INLINEKEYBOARDBUTTONRENDERSTYLENOBORDER = 2;
|
||||
int INLINEKEYBOARDBUTTONRENDERSTYLEREDCHARACTER = 3;
|
||||
int INPUTSTATUSTYPECANCEL = 2;
|
||||
int INPUTSTATUSTYPESPEAK = 3;
|
||||
int INPUTSTATUSTYPETEXT = 1;
|
||||
int KACTIVITYMSG = 22;
|
||||
int KADDLOCALMSGEXTINFOTYPEPROLOGUEMSG = 1;
|
||||
int KANONYMOUSATMEMSGTYPEINMSGBOX = 1001;
|
||||
int KANONYMOUSFLAGFROMOTHERPEOPLE = 1;
|
||||
int KANONYMOUSFLAGFROMOWN = 2;
|
||||
int KANONYMOUSFLAGINVALID = 0;
|
||||
int KAPPCHANNELMSG = 16;
|
||||
int KATALLMSGTYPEINMSGBOX = 2000;
|
||||
int KATMEMSGTYPEINMSGBOX = 1000;
|
||||
int KATTRIBUTETYPEADELIEMSG = 16;
|
||||
int KATTRIBUTETYPEEXTENDBUSINESS = 13;
|
||||
int KATTRIBUTETYPEFEEDBACKSTATE = 17;
|
||||
int KATTRIBUTETYPEGROUPHONOR = 2;
|
||||
int KATTRIBUTETYPEKINGHONOR = 3;
|
||||
int KATTRIBUTETYPELONGMSG = 8;
|
||||
int KATTRIBUTETYPEMEMORYSTATEMSGINFO = 18;
|
||||
int KATTRIBUTETYPEMSG = 0;
|
||||
int KATTRIBUTETYPEMSGBOXEVENTTYPE = 14;
|
||||
int KATTRIBUTETYPEPERSONAL = 1;
|
||||
int KATTRIBUTETYPEPUBLICACCOUNT = 4;
|
||||
int KATTRIBUTETYPEQQCONNECT = 12;
|
||||
int KATTRIBUTETYPESENDMSGRSPTRANSSVRINFO = 15;
|
||||
int KATTRIBUTETYPESHAREDMSGINFO = 5;
|
||||
int KATTRIBUTETYPETEMPCHATGAMESESSION = 6;
|
||||
int KATTRIBUTETYPETOROBOTMSG = 9;
|
||||
int KATTRIBUTETYPEUININFO = 7;
|
||||
int KATTRIBUTETYPEZPLAN = 11;
|
||||
int KAUTOREPLYTEXTNONEINDEX = -1;
|
||||
int KAVRECORDMSG = 19;
|
||||
int KBUSINESSTYPGUILD = 1;
|
||||
int KBUSINESSTYPNT = 0;
|
||||
int KCHATTYPEADELIE = 42;
|
||||
int KCHATTYPEBUDDYNOTIFY = 5;
|
||||
int KCHATTYPEC2C = 1;
|
||||
int KCHATTYPECIRCLE = 113;
|
||||
int KCHATTYPEDATALINE = 8;
|
||||
int KCHATTYPEDATALINEMQQ = 134;
|
||||
int KCHATTYPEDISC = 3;
|
||||
int KCHATTYPEFAV = 41;
|
||||
int KCHATTYPEGAMEMESSAGE = 105;
|
||||
int KCHATTYPEGAMEMESSAGEFOLDER = 116;
|
||||
int KCHATTYPEGROUP = 2;
|
||||
int KCHATTYPEGROUPBLESS = 133;
|
||||
int KCHATTYPEGROUPGUILD = 9;
|
||||
int KCHATTYPEGROUPHELPER = 7;
|
||||
int KCHATTYPEGROUPNOTIFY = 6;
|
||||
int KCHATTYPEGUILD = 4;
|
||||
int KCHATTYPEGUILDMETA = 16;
|
||||
int KCHATTYPEMATCHFRIEND = 104;
|
||||
int KCHATTYPEMATCHFRIENDFOLDER = 109;
|
||||
int KCHATTYPENEARBY = 106;
|
||||
int KCHATTYPENEARBYASSISTANT = 107;
|
||||
int KCHATTYPENEARBYFOLDER = 110;
|
||||
int KCHATTYPENEARBYHELLOFOLDER = 112;
|
||||
int KCHATTYPENEARBYINTERACT = 108;
|
||||
int KCHATTYPEQQNOTIFY = 132;
|
||||
int KCHATTYPERELATEACCOUNT = 131;
|
||||
int KCHATTYPESERVICEASSISTANT = 118;
|
||||
int KCHATTYPESERVICEASSISTANTSUB = 201;
|
||||
int KCHATTYPESQUAREPUBLIC = 115;
|
||||
int KCHATTYPESUBSCRIBEFOLDER = 30;
|
||||
int KCHATTYPETEMPADDRESSBOOK = 111;
|
||||
int KCHATTYPETEMPBUSSINESSCRM = 102;
|
||||
int KCHATTYPETEMPC2CFROMGROUP = 100;
|
||||
int KCHATTYPETEMPC2CFROMUNKNOWN = 99;
|
||||
int KCHATTYPETEMPFRIENDVERIFY = 101;
|
||||
int KCHATTYPETEMPNEARBYPRO = 119;
|
||||
int KCHATTYPETEMPPUBLICACCOUNT = 103;
|
||||
int KCHATTYPETEMPWPA = 117;
|
||||
int KCHATTYPEUNKNOWN = 0;
|
||||
int KCHATTYPEWEIYUN = 40;
|
||||
int KCOMMONREDENVELOPEMSGTYPEINMSGBOX = 1007;
|
||||
int KDOWNSOURCETYPEAIOINNER = 1;
|
||||
int KDOWNSOURCETYPEBIGSCREEN = 2;
|
||||
int KDOWNSOURCETYPEHISTORY = 3;
|
||||
int KDOWNSOURCETYPEUNKNOWN = 0;
|
||||
int KELEMTYPEACTIVITY = 25;
|
||||
int KELEMTYPEACTIVITYSTATE = 41;
|
||||
int KELEMTYPEACTIVITYSUBTYPECREATEMOBATEAM = 12;
|
||||
int KELEMTYPEACTIVITYSUBTYPEDISBANDMOBATEAM = 11;
|
||||
int KELEMTYPEACTIVITYSUBTYPEFEEDSQUARE = 10001;
|
||||
int KELEMTYPEACTIVITYSUBTYPEFINISHGAME = 16;
|
||||
int KELEMTYPEACTIVITYSUBTYPEFINISHMATCHTEAM = 14;
|
||||
int KELEMTYPEACTIVITYSUBTYPEHOTCHAT = 10000;
|
||||
int KELEMTYPEACTIVITYSUBTYPEMINIGAME = 18;
|
||||
int KELEMTYPEACTIVITYSUBTYPEMUSICPLAY = 17;
|
||||
int KELEMTYPEACTIVITYSUBTYPENEWSMOBA = 9;
|
||||
int KELEMTYPEACTIVITYSUBTYPENOLIVE = 2;
|
||||
int KELEMTYPEACTIVITYSUBTYPENOSCREENSHARE = 7;
|
||||
int KELEMTYPEACTIVITYSUBTYPENOVOICE = 3;
|
||||
int KELEMTYPEACTIVITYSUBTYPEONLIVE = 1;
|
||||
int KELEMTYPEACTIVITYSUBTYPEONSCREENSHARE = 6;
|
||||
int KELEMTYPEACTIVITYSUBTYPEONVOICE = 4;
|
||||
int KELEMTYPEACTIVITYSUBTYPESTARTMATCHTEAM = 13;
|
||||
int KELEMTYPEACTIVITYSUBTYPETARTGAME = 15;
|
||||
int KELEMTYPEACTIVITYSUBTYPEUNKNOWN = 0;
|
||||
int KELEMTYPEADELIEACTIONBAR = 44;
|
||||
int KELEMTYPEADELIERECOMMENDEDMSG = 43;
|
||||
int KELEMTYPEARKSTRUCT = 10;
|
||||
int KELEMTYPEAVRECORD = 21;
|
||||
int KELEMTYPECALENDAR = 19;
|
||||
int KELEMTYPEFACE = 6;
|
||||
int KELEMTYPEFACEBUBBLE = 27;
|
||||
int KELEMTYPEFEED = 22;
|
||||
int KELEMTYPEFILE = 3;
|
||||
int KELEMTYPEGIPHY = 15;
|
||||
int KELEMTYPEGRAYTIP = 8;
|
||||
int KELEMTYPEINLINEKEYBOARD = 17;
|
||||
int KELEMTYPEINTEXTGIFT = 18;
|
||||
int KELEMTYPELIVEGIFT = 12;
|
||||
int KELEMTYPEMARKDOWN = 14;
|
||||
int KELEMTYPEMARKETFACE = 11;
|
||||
int KELEMTYPEMULTIFORWARD = 16;
|
||||
int KELEMTYPEONLINEFILE = 23;
|
||||
int KELEMTYPEPIC = 2;
|
||||
int KELEMTYPEPROLOGUE = 46;
|
||||
int KELEMTYPEPTT = 4;
|
||||
int KELEMTYPEREPLY = 7;
|
||||
int KELEMTYPESHARELOCATION = 28;
|
||||
int KELEMTYPESTRUCTLONGMSG = 13;
|
||||
int KELEMTYPETASKTOPMSG = 29;
|
||||
int KELEMTYPETEXT = 1;
|
||||
int KELEMTYPETOFU = 26;
|
||||
int KELEMTYPEUNKNOWN = 0;
|
||||
int KELEMTYPEVIDEO = 5;
|
||||
int KELEMTYPEWALLET = 9;
|
||||
int KELEMTYPEYOLOGAMERESULT = 20;
|
||||
int KENTERAIO = 1;
|
||||
int KEXITAIO = 2;
|
||||
int KFEEDBACKBUTTONTYPEDISLIKE = 2;
|
||||
int KFEEDBACKBUTTONTYPELIKE = 1;
|
||||
int KFEEDBACKBUTTONTYPEPROMPTCLICK = 5;
|
||||
int KFEEDBACKBUTTONTYPEREGENERATE = 4;
|
||||
int KFEEDBACKBUTTONTYPEUNKNOWN = 0;
|
||||
int KFEEDBACKOPTLIKE = 1;
|
||||
int KFEEDBACKOPTUNKNOWN = 0;
|
||||
int KFEEDBACKOPTUNLIKE = 2;
|
||||
int KFRIENDNEWADDEDMSGTYPEINMSGBOX = 1008;
|
||||
int KGAMEBOXNEWMSGTYPEINMSGBOX = 3000;
|
||||
int KGIFTATMEMSGTYPEINMSGBOX = 1005;
|
||||
int KGROUPFILEATALLMSGTYPEINMSGBOX = 2001;
|
||||
int KGROUPHOMEWORK = 20000;
|
||||
int KGROUPHOMEWORKTASK = 20001;
|
||||
int KGROUPKEYWORDMSGTYPEINMSGBOX = 2006;
|
||||
int KGROUPMANNOUNCEATALLMSGTYPEINMSGBOX = 2004;
|
||||
int KGROUPTASKATALLMSGTYPEINMSGBOX = 2003;
|
||||
int KGROUPUNREADTYPEINMSGBOX = 2007;
|
||||
int KGUILDCHANNELLIST = 10;
|
||||
int KHIGHLIGHTWORDINTEMPCHATTYPEINMSGBOX = 1009;
|
||||
int KHOMEWORKREMINDER = 10000;
|
||||
int KLIKEORDISLIKESTATEDISLIKE = 2;
|
||||
int KLIKEORDISLIKESTATELIKE = 1;
|
||||
int KLIKEORDISLIKESTATENONESELECTED = 0;
|
||||
int KMARKETFACE = 17;
|
||||
int KMEMORYSTATEMSGTYPEADELIEWELCOME = 1;
|
||||
int KMEMORYSTATEMSGTYPEUNKNOWN = 0;
|
||||
int KMINIPROGRAMNOTICE = 114;
|
||||
int KMSGSUBTYPEARKGROUPANNOUNCE = 3;
|
||||
int KMSGSUBTYPEARKGROUPANNOUNCECONFIRMREQUIRED = 4;
|
||||
int KMSGSUBTYPEARKGROUPGIFTATME = 5;
|
||||
int KMSGSUBTYPEARKGROUPTASKATALL = 6;
|
||||
int KMSGSUBTYPEARKMULTIMSG = 7;
|
||||
int KMSGSUBTYPEARKNORMAL = 0;
|
||||
int KMSGSUBTYPEARKTENCENTDOCFROMMINIAPP = 1;
|
||||
int KMSGSUBTYPEARKTENCENTDOCFROMPLUSPANEL = 2;
|
||||
int KMSGSUBTYPEEMOTICON = 15;
|
||||
int KMSGSUBTYPEFILEAPP = 11;
|
||||
int KMSGSUBTYPEFILEAUDIO = 3;
|
||||
int KMSGSUBTYPEFILEDOC = 4;
|
||||
int KMSGSUBTYPEFILEEXCEL = 6;
|
||||
int KMSGSUBTYPEFILEFOLDER = 13;
|
||||
int KMSGSUBTYPEFILEHTML = 10;
|
||||
int KMSGSUBTYPEFILEIPA = 14;
|
||||
int KMSGSUBTYPEFILENORMAL = 0;
|
||||
int KMSGSUBTYPEFILEPDF = 7;
|
||||
int KMSGSUBTYPEFILEPIC = 1;
|
||||
int KMSGSUBTYPEFILEPPT = 5;
|
||||
int KMSGSUBTYPEFILEPSD = 12;
|
||||
int KMSGSUBTYPEFILETXT = 8;
|
||||
int KMSGSUBTYPEFILEVIDEO = 2;
|
||||
int KMSGSUBTYPEFILEZIP = 9;
|
||||
int KMSGSUBTYPELINK = 5;
|
||||
int KMSGSUBTYPEMARKETFACE = 1;
|
||||
int KMSGSUBTYPEMIXEMOTICON = 7;
|
||||
int KMSGSUBTYPEMIXFACE = 3;
|
||||
int KMSGSUBTYPEMIXMARKETFACE = 2;
|
||||
int KMSGSUBTYPEMIXPIC = 1;
|
||||
int KMSGSUBTYPEMIXREPLY = 4;
|
||||
int KMSGSUBTYPEMIXTEXT = 0;
|
||||
int KMSGSUBTYPETENCENTDOC = 6;
|
||||
int KMSGTYPEARKSTRUCT = 11;
|
||||
int KMSGTYPEFACEBUBBLE = 24;
|
||||
int KMSGTYPEFILE = 3;
|
||||
int KMSGTYPEGIFT = 14;
|
||||
int KMSGTYPEGIPHY = 13;
|
||||
int KMSGTYPEGRAYTIPS = 5;
|
||||
int KMSGTYPEMIX = 2;
|
||||
int KMSGTYPEMULTIMSGFORWARD = 8;
|
||||
int KMSGTYPENULL = 1;
|
||||
int KMSGTYPEONLINEFILE = 21;
|
||||
int KMSGTYPEONLINEFOLDER = 27;
|
||||
int KMSGTYPEPROLOGUE = 29;
|
||||
int KMSGTYPEPTT = 6;
|
||||
int KMSGTYPEREPLY = 9;
|
||||
int KMSGTYPESHARELOCATION = 25;
|
||||
int KMSGTYPESTRUCT = 4;
|
||||
int KMSGTYPESTRUCTLONGMSG = 12;
|
||||
int KMSGTYPETEXTGIFT = 15;
|
||||
int KMSGTYPEUNKNOWN = 0;
|
||||
int KMSGTYPEVIDEO = 7;
|
||||
int KMSGTYPEWALLET = 10;
|
||||
int KNEEDCONFIRMGROUPMANNOUNCEATALLMSGTYPEINMSGBOX = 2005;
|
||||
int KNOTPASSTHROUGHEVENTTYPEUPPERBOUNDARY = 9999;
|
||||
int KPTTFORMATTYPEAMR = 0;
|
||||
int KPTTFORMATTYPESILK = 1;
|
||||
int KPTTTRANSLATESTATUSFAIL = 3;
|
||||
int KPTTTRANSLATESTATUSSUC = 2;
|
||||
int KPTTTRANSLATESTATUSTRANSLATING = 1;
|
||||
int KPTTTRANSLATESTATUSUNKNOWN = 0;
|
||||
int KPTTVIPLEVELTYPENONE = 0;
|
||||
int KPTTVIPLEVELTYPEQQVIP = 0;
|
||||
int KPTTVIPLEVELTYPESVIP = 0;
|
||||
int KPTTVOICECHANGETYPEBEASTMACHINE = 7;
|
||||
int KPTTVOICECHANGETYPEBOY = 2;
|
||||
int KPTTVOICECHANGETYPECATCHCOLD = 13;
|
||||
int KPTTVOICECHANGETYPEECHO = 5;
|
||||
int KPTTVOICECHANGETYPEFATGUY = 16;
|
||||
int KPTTVOICECHANGETYPEFLASHING = 9;
|
||||
int KPTTVOICECHANGETYPEGIRL = 1;
|
||||
int KPTTVOICECHANGETYPEHORRIBLE = 3;
|
||||
int KPTTVOICECHANGETYPEKINDERGARTEN = 6;
|
||||
int KPTTVOICECHANGETYPEMEDAROT = 15;
|
||||
int KPTTVOICECHANGETYPENONE = 0;
|
||||
int KPTTVOICECHANGETYPEOPTIMUSPRIME = 8;
|
||||
int KPTTVOICECHANGETYPEOUTOFDATE = 14;
|
||||
int KPTTVOICECHANGETYPEPAPI = 11;
|
||||
int KPTTVOICECHANGETYPEQUICK = 4;
|
||||
int KPTTVOICECHANGETYPESTUTTER = 10;
|
||||
int KPTTVOICECHANGETYPETRAPPEDBEAST = 12;
|
||||
int KPTTVOICETYPEINTERCOM = 1;
|
||||
int KPTTVOICETYPESOUNDRECORD = 2;
|
||||
int KPTTVOICETYPEUNKNOW = 0;
|
||||
int KPTTVOICETYPEVOICECHANGE = 3;
|
||||
int KPUBLICACCOUNTTIANSHUHIGHLIGHTWORDTYPEINMSGBOX = 1010;
|
||||
int KREPLYABSELEMTYPEFACE = 2;
|
||||
int KREPLYABSELEMTYPEPIC = 3;
|
||||
int KREPLYABSELEMTYPETEXT = 1;
|
||||
int KREPLYABSELEMTYPEUNKNOWN = 0;
|
||||
int KREPLYATMEMSGTYPEINMSGBOX = 1002;
|
||||
int KRMDOWNTYPEORIG = 1;
|
||||
int KRMDOWNTYPETHUMB = 2;
|
||||
int KRMDOWNTYPEUNKNOWN = 0;
|
||||
int KRMFILETHUMBSIZE128 = 128;
|
||||
int KRMFILETHUMBSIZE320 = 320;
|
||||
int KRMFILETHUMBSIZE384 = 384;
|
||||
int KRMFILETHUMBSIZE750 = 750;
|
||||
int KRMPICAIOTHUMBSIZE = 0;
|
||||
int KRMPICTHUMBSIZE198 = 198;
|
||||
int KRMPICTHUMBSIZE720 = 720;
|
||||
int KRMPICTYPEBMP = 3;
|
||||
int KRMPICTYPECHECKOTHER = 900;
|
||||
int KRMPICTYPEGIF = 2;
|
||||
int KRMPICTYPEJPG = 0;
|
||||
int KRMPICTYPENEWPICAPNG = 2001;
|
||||
int KRMPICTYPENEWPICBMP = 1005;
|
||||
int KRMPICTYPENEWPICGIF = 2000;
|
||||
int KRMPICTYPENEWPICJPEG = 1000;
|
||||
int KRMPICTYPENEWPICPNG = 1001;
|
||||
int KRMPICTYPENEWPICPROGERSSIVJPEG = 1003;
|
||||
int KRMPICTYPENEWPICSHARPP = 1004;
|
||||
int KRMPICTYPENEWPICWEBP = 1002;
|
||||
int KRMPICTYPEPNG = 1;
|
||||
int KRMPICTYPEUNKOWN = 0;
|
||||
int KRMTHUMBSIZEZERO = 0;
|
||||
int KRMTRNASFERSTATUSDOWNLOADING = 3;
|
||||
int KRMTRNASFERSTATUSFAIL = 5;
|
||||
int KRMTRNASFERSTATUSINIT = 1;
|
||||
int KRMTRNASFERSTATUSSUC = 4;
|
||||
int KRMTRNASFERSTATUSUNKOW = 0;
|
||||
int KRMTRNASFERSTATUSUPLOADING = 2;
|
||||
int KRMTRNASFERSTATUSUSERCANCEL = 6;
|
||||
int KSEEKINGPARTNERFLAGSEEKING = 1;
|
||||
int KSEEKINGPARTNERFLAGUNKNOWN = 0;
|
||||
int KSENDSTATUSFAILED = 0;
|
||||
int KSENDSTATUSSENDING = 1;
|
||||
int KSENDSTATUSSUCCESS = 2;
|
||||
int KSENDSTATUSSUCCESSNOSEQ = 3;
|
||||
int KSENDTYPEDROPPED = 6;
|
||||
int KSENDTYPELOCAL = 3;
|
||||
int KSENDTYPEOTHERDEVICE = 2;
|
||||
int KSENDTYPERECV = 0;
|
||||
int KSENDTYPESELF = 1;
|
||||
int KSENDTYPESELFFORWARD = 4;
|
||||
int KSENDTYPESELFMULTIFORWARD = 5;
|
||||
int KSESSIONTYPEADDRESSBOOK = 5;
|
||||
int KSESSIONTYPEC2C = 1;
|
||||
int KSESSIONTYPEDISC = 3;
|
||||
int KSESSIONTYPEFAV = 41;
|
||||
int KSESSIONTYPEGROUP = 2;
|
||||
int KSESSIONTYPEGROUPBLESS = 52;
|
||||
int KSESSIONTYPEGUILD = 4;
|
||||
int KSESSIONTYPEGUILDMETA = 16;
|
||||
int KSESSIONTYPENEARBYPRO = 54;
|
||||
int KSESSIONTYPEQQNOTIFY = 51;
|
||||
int KSESSIONTYPERELATEACCOUNT = 50;
|
||||
int KSESSIONTYPESERVICEASSISTANT = 19;
|
||||
int KSESSIONTYPESUBSCRIBEFOLDER = 30;
|
||||
int KSESSIONTYPETYPEBUDDYNOTIFY = 7;
|
||||
int KSESSIONTYPETYPEGROUPHELPER = 9;
|
||||
int KSESSIONTYPETYPEGROUPNOTIFY = 8;
|
||||
int KSESSIONTYPEUNKNOWN = 0;
|
||||
int KSESSIONTYPEWEIYUN = 40;
|
||||
int KSPECIALCAREMSGTYPEINMSGBOX = 1006;
|
||||
int KSPECIFIEDREDENVELOPEATMEMSGTYPEINMSGBOX = 1004;
|
||||
int KSPECIFIEDREDENVELOPEATONEMSGTYPEINMSGBOX = 1003;
|
||||
int KTENCENTDOCTYPEADDON = 110;
|
||||
int KTENCENTDOCTYPEDOC = 0;
|
||||
int KTENCENTDOCTYPEDRAWING = 89;
|
||||
int KTENCENTDOCTYPEDRIVE = 101;
|
||||
int KTENCENTDOCTYPEFILE = 100;
|
||||
int KTENCENTDOCTYPEFLOWCHART = 91;
|
||||
int KTENCENTDOCTYPEFOLDER = 3;
|
||||
int KTENCENTDOCTYPEFORM = 2;
|
||||
int KTENCENTDOCTYPEMIND = 90;
|
||||
int KTENCENTDOCTYPENOTES = 5;
|
||||
int KTENCENTDOCTYPEPDF = 6;
|
||||
int KTENCENTDOCTYPEPROGRAM = 7;
|
||||
int KTENCENTDOCTYPESHEET = 1;
|
||||
int KTENCENTDOCTYPESLIDE = 4;
|
||||
int KTENCENTDOCTYPESMARTCANVAS = 8;
|
||||
int KTENCENTDOCTYPESMARTSHEET = 9;
|
||||
int KTENCENTDOCTYPESPEECH = 102;
|
||||
int KTENCENTDOCTYPEUNKNOWN = 10;
|
||||
int KTOFURECORDMSG = 23;
|
||||
int KTOPMSGTYPETASK = 1;
|
||||
int KTOPMSGTYPEUNKNOWN = 0;
|
||||
int KTRIGGERTYPEAUTO = 1;
|
||||
int KTRIGGERTYPEMANUAL = 0;
|
||||
int KUNKNOWN = 0;
|
||||
int KUNKNOWNTYPEINMSGBOX = 0;
|
||||
int KUNREADCNTUPTYPEALLDIRECTSESSION = 4;
|
||||
int KUNREADCNTUPTYPEALLFEEDSINGUILD = 6;
|
||||
int KUNREADCNTUPTYPEALLGUILD = 3;
|
||||
int KUNREADCNTUPTYPECATEGORY = 5;
|
||||
int KUNREADCNTUPTYPECHANNEL = 1;
|
||||
int KUNREADCNTUPTYPECONTACT = 0;
|
||||
int KUNREADCNTUPTYPEGUILD = 2;
|
||||
int KUNREADCNTUPTYPEGUILDGROUP = 7;
|
||||
int KUNREADSHOWTTYPEGRAYPOINT = 2;
|
||||
int KUNREADSHOWTYPEREDPOINT = 1;
|
||||
int KUNREADSHOWTYPESMALLGRAYPOINT = 4;
|
||||
int KUNREADSHOWTYPESMALLREDPOINT = 3;
|
||||
int KUNREADSHOWTYPEUNKNOWN = 0;
|
||||
int KVASGIFTCOINTYPECOIN = 0;
|
||||
int KVASGIFTCOINTYPEMARKETCOIN = 1;
|
||||
int KYOLOGAMERESULTMSG = 18;
|
||||
int PIC_800_RECOMMENDED = 7;
|
||||
int PIC_AIGC_EMOJI = 14;
|
||||
int PIC_ALBUM_GIF = 11;
|
||||
int PIC_COMMERCIAL_ADVERTISING = 9;
|
||||
int PIC_FIND = 10;
|
||||
int PIC_HOT = 2;
|
||||
int PIC_HOT_EMOJI = 13;
|
||||
int PIC_NORMAL = 0;
|
||||
int PIC_PK = 3;
|
||||
int PIC_QQZONE = 5;
|
||||
int PIC_SELFIE_GIF = 8;
|
||||
int PIC_SEND_FROM_TAB_SEARCH_BOX = 12;
|
||||
int PIC_USER = 1;
|
||||
int PIC_WISDOM_FIGURE = 4;
|
||||
int REPLYORIGINALMSGSTATEHASRECALL = 1;
|
||||
int REPLYORIGINALMSGSTATEUNKNOWN = 0;
|
||||
int SHARELOCATIONELEMSUBTYPENORMAL = 1;
|
||||
int SHARELOCATIONELEMSUBTYPEUNKNOWN = 0;
|
||||
int TEXTELEMENTSUBTYPELINK = 1;
|
||||
int TEXTELEMENTSUBTYPETENCENTDOC = 2;
|
||||
int TEXTELEMENTSUBTYPEUNKNOWN = 0;
|
||||
```
|
@@ -1,16 +0,0 @@
|
||||
# 开发方向
|
||||
方向一 NativeCall/Hook:
|
||||
1. 崩溃检测机制的实现
|
||||
2. Api_Caller 的Hook 可以拿到Event/Handler 进一步提升NC 即时的拦截与处理一些事件比如ReCall拦截
|
||||
3. Node包装层 进一步分析,拿到脱离自带Listener/Adapter,可以拿到一些更加底层的数据变动 或许包括更多二进制数据
|
||||
|
||||
方向二 全新的无头启动 Way01
|
||||
1. 基于Node启动原理,借助导出符号获取函数地址 再次还原NodeMain
|
||||
|
||||
方向三 发包与收包
|
||||
1. 参考 方向一/3 大概可以收包
|
||||
2. 发包 (暂时没有计划)
|
||||
|
||||
方向四 版本控制
|
||||
1. 根据不同版本进行逻辑控制
|
||||
2. 某些参数的自动提取
|
@@ -1,8 +0,0 @@
|
||||
# Api方向
|
||||
## getMsgUniqueId √ 已应用
|
||||
getMsgUniqueId 传入时间 产出一个唯一ID 发送消息作为一个参数
|
||||
|
||||
# Native方向
|
||||
## magic_load
|
||||
## api_caller
|
||||
## NodeMain
|
BIN
external/LiteLoaderWrapper.zip
vendored
Normal file
BIN
external/LiteLoaderWrapper.zip
vendored
Normal file
Binary file not shown.
BIN
launcher/NapCatWinBootHook.dll
Normal file
BIN
launcher/NapCatWinBootHook.dll
Normal file
Binary file not shown.
BIN
launcher/NapCatWinBootMain.exe
Normal file
BIN
launcher/NapCatWinBootMain.exe
Normal file
Binary file not shown.
40
launcher/launcher-win10.bat
Normal file
40
launcher/launcher-win10.bat
Normal file
@@ -0,0 +1,40 @@
|
||||
@echo off
|
||||
chcp 65001
|
||||
net session >nul 2>&1
|
||||
if %errorLevel% == 0 (
|
||||
echo Administrator mode detected.
|
||||
) else (
|
||||
echo Please run this script in administrator mode.
|
||||
powershell -Command "Start-Process 'cmd.exe' -ArgumentList '/c cd /d \"%cd%\" && \"%~f0\" %1' -Verb runAs"
|
||||
exit
|
||||
)
|
||||
|
||||
set NAPCAT_PATCH_PACKAGE=%cd%\qqnt.json
|
||||
set NAPCAT_LOAD_PATH=%cd%\loadNapCat.js
|
||||
set NAPCAT_INJECT_PATH=%cd%\NapCatWinBootHook.dll
|
||||
set NAPCAT_LAUNCHER_PATH=%cd%\NapCatWinBootMain.exe
|
||||
set NAPCAT_MAIN_PATH=%cd%\napcat.mjs
|
||||
:loop_read
|
||||
for /f "tokens=2*" %%a in ('reg query "HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\QQ" /v "UninstallString"') do (
|
||||
set RetString=%%b
|
||||
goto :napcat_boot
|
||||
)
|
||||
|
||||
:napcat_boot
|
||||
for %%a in ("%RetString%") do (
|
||||
set "pathWithoutUninstall=%%~dpa"
|
||||
)
|
||||
|
||||
SET QQPath=%pathWithoutUninstall%QQ.exe
|
||||
|
||||
if not exist "%QQpath%" (
|
||||
echo provided QQ path is invalid: %QQpath%
|
||||
pause
|
||||
exit /b
|
||||
)
|
||||
set NAPCAT_MAIN_PATH=%NAPCAT_MAIN_PATH:\=/%
|
||||
echo (async () =^> {await import("file:///%NAPCAT_MAIN_PATH%")})() > "%NAPCAT_LOAD_PATH%"
|
||||
|
||||
"%NAPCAT_LAUNCHER_PATH%" "%QQPath%" "%NAPCAT_INJECT_PATH%" %1
|
||||
|
||||
REM "%NAPCAT_LAUNCHER_PATH%" "%QQPath%" "%NAPCAT_INJECT_PATH%" 123456
|
39
launcher/launcher.bat
Normal file
39
launcher/launcher.bat
Normal file
@@ -0,0 +1,39 @@
|
||||
@echo off
|
||||
chcp 65001
|
||||
net session >nul 2>&1
|
||||
if %errorLevel% == 0 (
|
||||
echo Administrator mode detected.
|
||||
) else (
|
||||
echo Please run this script in administrator mode.
|
||||
powershell -Command "Start-Process 'wt.exe' -ArgumentList 'cmd /c cd /d \"%cd%\" && \"%~f0\" %1' -Verb runAs"
|
||||
exit
|
||||
)
|
||||
|
||||
set NAPCAT_PATCH_PACKAGE=%cd%\qqnt.json
|
||||
set NAPCAT_LOAD_PATH=%cd%\loadNapCat.js
|
||||
set NAPCAT_INJECT_PATH=%cd%\NapCatWinBootHook.dll
|
||||
set NAPCAT_LAUNCHER_PATH=%cd%\NapCatWinBootMain.exe
|
||||
set NAPCAT_MAIN_PATH=%cd%\napcat.mjs
|
||||
:loop_read
|
||||
for /f "tokens=2*" %%a in ('reg query "HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\QQ" /v "UninstallString"') do (
|
||||
set RetString=%%b
|
||||
goto :napcat_boot
|
||||
)
|
||||
|
||||
:napcat_boot
|
||||
for %%a in ("%RetString%") do (
|
||||
set "pathWithoutUninstall=%%~dpa"
|
||||
)
|
||||
|
||||
SET QQPath=%pathWithoutUninstall%QQ.exe
|
||||
|
||||
if not exist "%QQpath%" (
|
||||
echo provided QQ path is invalid: %QQpath%
|
||||
pause
|
||||
exit /b
|
||||
)
|
||||
|
||||
set NAPCAT_MAIN_PATH=%NAPCAT_MAIN_PATH:\=/%
|
||||
echo (async () =^> {await import("file:///%NAPCAT_MAIN_PATH%")})() > "%NAPCAT_LOAD_PATH%"
|
||||
|
||||
"%NAPCAT_LAUNCHER_PATH%" "%QQPath%" "%NAPCAT_INJECT_PATH%" %1
|
5
launcher/loadNapCat.js
Normal file
5
launcher/loadNapCat.js
Normal file
@@ -0,0 +1,5 @@
|
||||
const path = require('path');
|
||||
const CurrentPath = path.dirname(__filename);
|
||||
(async () => {
|
||||
await import("file://" + path.join(CurrentPath, './napcat/napcat.mjs'));
|
||||
})();
|
26
launcher/qqnt.json
Normal file
26
launcher/qqnt.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"name": "qq-chat",
|
||||
"version": "9.9.15-28788",
|
||||
"verHash": "73b0c8f6",
|
||||
"linuxVersion": "3.2.12-28788",
|
||||
"linuxVerHash": "55fb6434",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"description": "QQ",
|
||||
"productName": "QQ",
|
||||
"author": {
|
||||
"name": "Tencent",
|
||||
"email": "QQ-Team@tencent.com"
|
||||
},
|
||||
"homepage": "https://im.qq.com",
|
||||
"sideEffects": true,
|
||||
"bin": {
|
||||
"qd": "externals/devtools/cli/index.js"
|
||||
},
|
||||
"main": "./loadNapCat.js",
|
||||
"buildVersion": "28788",
|
||||
"isPureShell": true,
|
||||
"isByteCodeShell": true,
|
||||
"platform": "win32",
|
||||
"eleArch": "x64"
|
||||
}
|
4
launcher/quickLoginExample.bat
Normal file
4
launcher/quickLoginExample.bat
Normal file
@@ -0,0 +1,4 @@
|
||||
@echo off
|
||||
REM ./launcher.bat 123456
|
||||
REM ./launcher-win10.bat 123456
|
||||
REM 带有REM的为注释 删掉你需要的系统的那行REM这三个单词 修改QQ本脚本启动即可
|
33
manifest.json
Normal file
33
manifest.json
Normal file
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"manifest_version": 4,
|
||||
"type": "extension",
|
||||
"name": "NapCatQQ",
|
||||
"slug": "NapCat.Framework",
|
||||
"description": "高性能的 OneBot 11 协议实现",
|
||||
"version": "3.0.0",
|
||||
"icon": "./logo.png",
|
||||
"authors": [
|
||||
{
|
||||
"name": "MliKiowa",
|
||||
"link": "https://github.com/MliKiowa"
|
||||
},
|
||||
{
|
||||
"name": "Young",
|
||||
"link": "https://github.com/Wesley-Young"
|
||||
}
|
||||
],
|
||||
"repository": {
|
||||
"repo": "NapNeko/NapCatQQ",
|
||||
"branch": "main"
|
||||
},
|
||||
"platform": [
|
||||
"win32",
|
||||
"linux",
|
||||
"darwin"
|
||||
],
|
||||
"injects": {
|
||||
"renderer": "./renderer.js",
|
||||
"main": "./liteloader.cjs",
|
||||
"preload": "./preload.cjs"
|
||||
}
|
||||
}
|
122
package.json
122
package.json
@@ -1,70 +1,52 @@
|
||||
{
|
||||
"name": "napcat",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"version": "1.7.3",
|
||||
"scripts": {
|
||||
"watch:dev": "vite --mode development",
|
||||
"watch:prod": "vite --mode production",
|
||||
"build:dev": "vite build --mode development",
|
||||
"build:prod": "vite build --mode production",
|
||||
"build": "npm run build:dev",
|
||||
"build:core": "cd ./src/core && npm run build && cd ../.. && node ./script/copy-core.cjs",
|
||||
"build:webui": "cd ./src/webui && vite build",
|
||||
"watch": "npm run watch:dev",
|
||||
"debug-win": "powershell dist/napcat.ps1",
|
||||
"lint": "eslint --fix src/**/*.{js,ts}",
|
||||
"release": "npm run build:prod",
|
||||
"depend": "cd dist && npm install --omit=dev"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.24.7",
|
||||
"@babel/plugin-proposal-class-properties": "^7.18.6",
|
||||
"@babel/plugin-proposal-decorators": "^7.24.7",
|
||||
"@babel/preset-typescript": "^7.24.7",
|
||||
"@log4js-node/log4js-api": "^1.0.2",
|
||||
"@protobuf-ts/plugin": "^2.9.4",
|
||||
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||
"@rollup/plugin-typescript": "^11.1.6",
|
||||
"@types/cors": "^2.8.17",
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/figlet": "^1.5.8",
|
||||
"@types/fluent-ffmpeg": "^2.1.24",
|
||||
"@types/jest": "^29.5.12",
|
||||
"@types/node": "^22.0.0",
|
||||
"@types/qrcode-terminal": "^0.12.2",
|
||||
"@types/ws": "^8.5.10",
|
||||
"@typescript-eslint/eslint-plugin": "^7.4.0",
|
||||
"@typescript-eslint/parser": "^7.4.0",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-import-resolver-typescript": "^3.6.1",
|
||||
"eslint-plugin-import": "^2.29.1",
|
||||
"i": "^0.3.7",
|
||||
"javascript-obfuscator": "^4.1.0",
|
||||
"rollup": "^4.13.2",
|
||||
"rollup-plugin-dts": "^6.1.0",
|
||||
"rollup-plugin-obfuscator": "^1.1.0",
|
||||
"typescript": "^5.3.3",
|
||||
"vite": "^5.2.6",
|
||||
"vite-plugin-babel": "^1.2.0",
|
||||
"vite-plugin-cp": "^4.0.8",
|
||||
"vite-plugin-dts": "^3.8.2",
|
||||
"vite-tsconfig-paths": "^4.3.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"ajv": "^8.13.0",
|
||||
"chalk": "^5.3.0",
|
||||
"commander": "^12.0.0",
|
||||
"cors": "^2.8.5",
|
||||
"express": "^5.0.0-beta.2",
|
||||
"fast-xml-parser": "^4.3.6",
|
||||
"file-type": "^19.0.0",
|
||||
"fluent-ffmpeg": "^2.1.2",
|
||||
"image-size": "^1.1.1",
|
||||
"json-schema-to-ts": "^3.1.0",
|
||||
"log4js": "^6.9.1",
|
||||
"qrcode-terminal": "^0.12.0",
|
||||
"silk-wasm": "^3.6.1",
|
||||
"ws": "^8.16.0"
|
||||
}
|
||||
}
|
||||
{
|
||||
"name": "napcat",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"version": "3.0.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",
|
||||
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||
"@rollup/plugin-typescript": "^11.1.6",
|
||||
"@types/cors": "^2.8.17",
|
||||
"@types/express": "^5.0.0",
|
||||
"@types/fluent-ffmpeg": "^2.1.24",
|
||||
"@types/node": "^22.0.1",
|
||||
"@types/qrcode-terminal": "^0.12.2",
|
||||
"@types/ws": "^8.5.12",
|
||||
"@typescript-eslint/eslint-plugin": "^8.3.0",
|
||||
"@typescript-eslint/parser": "^8.3.0",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-import-resolver-typescript": "^3.6.1",
|
||||
"eslint-plugin-import": "^2.29.1",
|
||||
"typescript": "^5.3.3",
|
||||
"vite": "^5.2.6",
|
||||
"vite-plugin-cp": "^4.0.8",
|
||||
"vite-tsconfig-paths": "^4.3.2",
|
||||
"@protobuf-ts/runtime": "^2.9.4",
|
||||
"ajv": "^8.13.0",
|
||||
"fast-xml-parser": "^4.3.6",
|
||||
"chalk": "^5.3.0",
|
||||
"commander": "^12.1.0",
|
||||
"async-mutex": "^0.5.0",
|
||||
"file-type": "^19.0.0",
|
||||
"json-schema-to-ts": "^3.1.0",
|
||||
"image-size": "^1.1.1",
|
||||
"cors": "^2.8.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"qrcode-terminal": "^0.12.0",
|
||||
"fluent-ffmpeg": "^2.1.2",
|
||||
"express": "^5.0.0-beta.2",
|
||||
"log4js": "^6.9.1",
|
||||
"silk-wasm": "^3.6.1",
|
||||
"ws": "^8.18.0"
|
||||
}
|
||||
}
|
@@ -1,45 +0,0 @@
|
||||
# Dont Use This Script
|
||||
# 2024.7.3
|
||||
function Get-QQpath {
|
||||
try {
|
||||
$key = Get-ItemProperty -Path "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\QQ"
|
||||
$uninstallString = $key.UninstallString
|
||||
return [System.IO.Path]::GetDirectoryName($uninstallString) + "\QQ.exe"
|
||||
}
|
||||
catch {
|
||||
throw "get QQ path error: $_"
|
||||
}
|
||||
}
|
||||
function Select-QQPath {
|
||||
Add-Type -AssemblyName System.Windows.Forms
|
||||
[System.Windows.Forms.Application]::EnableVisualStyles()
|
||||
|
||||
$dialogTitle = "Select QQ.exe"
|
||||
|
||||
$filePicker = New-Object System.Windows.Forms.OpenFileDialog
|
||||
$filePicker.Title = $dialogTitle
|
||||
$filePicker.Filter = "Executable Files (*.exe)|*.exe|All Files (*.*)|*.*"
|
||||
$filePicker.FilterIndex = 1
|
||||
$null = $filePicker.ShowDialog()
|
||||
if (-not ($filePicker.FileName)) {
|
||||
throw "User did not select an .exe file."
|
||||
}
|
||||
return $filePicker.FileName
|
||||
}
|
||||
|
||||
$params = $args -join " "
|
||||
Try {
|
||||
$QQpath = Get-QQpath
|
||||
}
|
||||
Catch {
|
||||
$QQpath = Select-QQPath
|
||||
}
|
||||
|
||||
if (!(Test-Path $QQpath)) {
|
||||
throw "provided QQ path is invalid: $QQpath"
|
||||
}
|
||||
|
||||
$Bootfile = Join-Path $PSScriptRoot "napcat.mjs"
|
||||
$env:ELECTRON_RUN_AS_NODE = 1
|
||||
$commandInfo = Get-Command $QQpath -ErrorAction Stop
|
||||
Start-Process powershell -ArgumentList "-noexit", "-noprofile", "-command &{& chcp 65001;& '$($commandInfo.Path)' --enable-logging }"
|
@@ -1,123 +0,0 @@
|
||||
# 检查当前会话是否具有管理员权限
|
||||
function Test-Administrator {
|
||||
$user = [Security.Principal.WindowsIdentity]::GetCurrent()
|
||||
(New-Object Security.Principal.WindowsPrincipal $user).IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)
|
||||
}
|
||||
|
||||
if (-not (Test-Administrator)) {
|
||||
# 如果不是管理员,则重新启动脚本以管理员模式运行
|
||||
$scriptPath = $myInvocation.MyCommand.Path
|
||||
if (-not $scriptPath) {
|
||||
$scriptPath = $PSCommandPath
|
||||
}
|
||||
$newProcess = New-Object System.Diagnostics.ProcessStartInfo "powershell";
|
||||
$newProcess.Arguments = "-File `"$scriptPath`" $args"
|
||||
$newProcess.Verb = "runas";
|
||||
[System.Diagnostics.Process]::Start($newProcess);
|
||||
exit
|
||||
}
|
||||
|
||||
function Get-QQpath {
|
||||
try {
|
||||
$key = Get-ItemProperty -Path "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\QQ"
|
||||
$uninstallString = $key.UninstallString
|
||||
return [System.IO.Path]::GetDirectoryName($uninstallString) + "\QQ.exe"
|
||||
}
|
||||
catch {
|
||||
throw "get QQ path error: $_"
|
||||
}
|
||||
}
|
||||
function Select-QQPath {
|
||||
Add-Type -AssemblyName System.Windows.Forms
|
||||
[System.Windows.Forms.Application]::EnableVisualStyles()
|
||||
|
||||
$dialogTitle = "Select QQ.exe"
|
||||
|
||||
$filePicker = New-Object System.Windows.Forms.OpenFileDialog
|
||||
$filePicker.Title = $dialogTitle
|
||||
$filePicker.Filter = "Executable Files (*.exe)|*.exe|All Files (*.*)|*.*"
|
||||
$filePicker.FilterIndex = 1
|
||||
$null = $filePicker.ShowDialog()
|
||||
if (-not ($filePicker.FileName)) {
|
||||
throw "User did not select an .exe file."
|
||||
}
|
||||
return $filePicker.FileName
|
||||
}
|
||||
|
||||
# 设置当前工作目录
|
||||
$scriptDirectory = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent
|
||||
Set-Location $scriptDirectory
|
||||
|
||||
# 获取当前目录路径
|
||||
$currentPath = Get-Location
|
||||
|
||||
# 替换\为/
|
||||
$currentPath = $currentPath -replace '\\', '/'
|
||||
|
||||
# 生成JavaScript代码
|
||||
$jsCode = @"
|
||||
(async () => {
|
||||
await import('file:///$currentPath/napcat.mjs');
|
||||
})();
|
||||
"@
|
||||
|
||||
# 将JavaScript代码保存到文件中
|
||||
$jsFilePath = Join-Path $currentPath "loadScript.js"
|
||||
$jsCode | Out-File -FilePath $jsFilePath -Encoding UTF8
|
||||
|
||||
Write-Output "JavaScript code has been generated and saved to $jsFilePath"
|
||||
# 设置NAPCAT_PATH环境变量为 当前目录的loadScript.js地址
|
||||
$env:NAPCAT_PATH = $jsFilePath
|
||||
|
||||
$params = $args -join " "
|
||||
Try {
|
||||
$QQpath = Get-QQpath
|
||||
}
|
||||
Catch {
|
||||
$QQpath = Select-QQPath
|
||||
}
|
||||
# 拿不到QQ路径则退出
|
||||
if (!(Test-Path $QQpath)) {
|
||||
Write-Output "provided QQ path is invalid: $QQpath"
|
||||
Read-Host "Press any key to continue..."
|
||||
exit
|
||||
}
|
||||
|
||||
$commandInfo = Get-Command $QQpath -ErrorAction Stop
|
||||
|
||||
# 收集dbghelp.dll路径和HASH信息
|
||||
$QQpath = Split-Path $QQpath
|
||||
$oldDllPath = Join-Path $QQpath "dbghelp.dll"
|
||||
$oldDllHash = Get-FileHash $oldDllPath -Algorithm MD5
|
||||
$newDllPath = Join-Path $currentPath "dbghelp.dll"
|
||||
$newDllHash = Get-FileHash $newDllPath -Algorithm MD5
|
||||
# 如果文件一致则跳过
|
||||
if ($oldDllHash.Hash -ne $newDllHash.Hash) {
|
||||
$processes = Get-Process -Name QQ -ErrorAction SilentlyContinue
|
||||
if ($processes) {
|
||||
# 文件占用则退出
|
||||
Write-Output "dbghelp.dll is in use by the following processes:"
|
||||
$processes | ForEach-Object { Write-Output "$($_.Id) $($_.Name) $($_.Path)" }
|
||||
Write-Output "dbghelp.dll is in use, cannot continue."
|
||||
Read-Host "Press any key to continue..."
|
||||
exit
|
||||
} else {
|
||||
# 文件未占用则尝试覆盖
|
||||
try {
|
||||
Copy-Item -Path "$newDllPath" -Destination "$oldDllPath" -Force
|
||||
Write-Output "dbghelp.dll has been copied to $QQpath"
|
||||
} catch {
|
||||
Write-Output "Failed to copy dbghelp.dll: $_"
|
||||
Read-Host "Press any key to continue..."
|
||||
exit
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# 带参数启动QQ
|
||||
try {
|
||||
Start-Process powershell -ArgumentList '-noexit', '-noprofile', "-command &{& chcp 65001;& '$($commandInfo.Path)' --enable-logging $params}" -NoNewWindow -ErrorAction Stop
|
||||
} catch {
|
||||
Write-Output "Failed to start process as administrator: $_"
|
||||
Read-Host "Press any key to continue..."
|
||||
}
|
2
script/KillQQ.bat
Normal file
2
script/KillQQ.bat
Normal file
@@ -0,0 +1,2 @@
|
||||
@echo off
|
||||
taskkill /f /im QQ.exe
|
@@ -1,28 +0,0 @@
|
||||
@echo off
|
||||
chcp 65001
|
||||
:: 检查是否有管理员权限
|
||||
net session >nul 2>&1
|
||||
if %errorlevel% neq 0 (
|
||||
echo 请求管理员权限...
|
||||
powershell -Command "Start-Process '%~f0' -Verb runAs"
|
||||
exit /b
|
||||
)
|
||||
:: 如果有管理员权限,继续执行
|
||||
setlocal enabledelayedexpansion
|
||||
:loop_read
|
||||
for /f "tokens=2*" %%a in ('reg query "HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\QQ" /v "UninstallString"') do (
|
||||
set "RetString=%%b"
|
||||
goto :napcat_boot
|
||||
)
|
||||
|
||||
:napcat_boot
|
||||
for %%a in ("!RetString!") do (
|
||||
set "pathWithoutUninstall=%%~dpa"
|
||||
)
|
||||
|
||||
set "QQPath=!pathWithoutUninstall!QQ.exe"
|
||||
|
||||
echo !QQPath!
|
||||
"!QQPath!" --enable-logging %*
|
||||
|
||||
pause
|
@@ -1,3 +0,0 @@
|
||||
REM 全新启动脚本 基于 Hook Native 预计版本1.6.0左右发布
|
||||
@echo off
|
||||
pause
|
@@ -11,7 +11,7 @@ try {
|
||||
|
||||
// 验证 targetVersion 格式
|
||||
if (!targetVersion || typeof targetVersion !== 'string') {
|
||||
console.error("[NapCat] [CheckVersion] 目标版本格式不正确或未设置!");
|
||||
console.log("[NapCat] [CheckVersion] 目标版本格式不正确或未设置!");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -38,5 +38,5 @@ try {
|
||||
writeScriptToFile(safeScriptContent);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("[NapCat] [CheckVersion] 检测过程中发生错误:", error);
|
||||
console.log("[NapCat] [CheckVersion] 检测过程中发生错误:", error);
|
||||
}
|
@@ -1,32 +0,0 @@
|
||||
let fs = require('fs');
|
||||
let path = require('path');
|
||||
|
||||
const coreDistDir = path.join(path.resolve(__dirname, '../'), 'src/core/dist/core/src');
|
||||
const coreLibDir = path.join(path.resolve(__dirname, '../'), 'src/core.lib/src');
|
||||
|
||||
function copyDir(currentPath, outputDir) {
|
||||
fs.readdir(currentPath, { withFileTypes: true }, (err, entries) => {
|
||||
if (err?.errno === -4058) return;
|
||||
|
||||
entries.forEach(entry => {
|
||||
const localBasePath = path.join(currentPath, entry.name);
|
||||
const outputLocalBasePath = path.join(outputDir, entry.name);
|
||||
|
||||
if (entry.isDirectory()) {
|
||||
// 如果是目录,递归调用
|
||||
if (!fs.existsSync(outputLocalBasePath)) {
|
||||
fs.mkdirSync(outputLocalBasePath, { recursive: true });
|
||||
}
|
||||
copyDir(localBasePath, outputLocalBasePath);
|
||||
}
|
||||
else{
|
||||
// 如果是文件,直接复制
|
||||
fs.copyFile(localBasePath, outputLocalBasePath, (err) => {
|
||||
if (err) throw err;
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
copyDir(coreDistDir, coreLibDir);
|
Binary file not shown.
@@ -1,21 +0,0 @@
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import { version } from '../src/onebot11/version'
|
||||
|
||||
const manifestPath = path.join(__dirname, '../package.json')
|
||||
|
||||
function readManifest (): any {
|
||||
if (fs.existsSync(manifestPath)) {
|
||||
return JSON.parse(fs.readFileSync(manifestPath, 'utf-8'))
|
||||
}
|
||||
}
|
||||
|
||||
function writeManifest (manifest: any) {
|
||||
fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2))
|
||||
}
|
||||
|
||||
const manifest = readManifest()
|
||||
if (version !== manifest.version) {
|
||||
manifest.version = version
|
||||
writeManifest(manifest)
|
||||
}
|
@@ -1,20 +0,0 @@
|
||||
// --------------------
|
||||
// 2024.7.3 9.9.12 BootWay.03 其余方法暂不公开(此方案为临时方案 Win平台已验证)
|
||||
// 缺陷 (已知)
|
||||
// 1.与非入侵式不同 现在破坏本体代码
|
||||
// 2.重启代码与正常启动代码失效
|
||||
// 3.Win需要补丁
|
||||
// 4.更新后丢失内容 需要重写此文件
|
||||
// 5.安装难度上升与周围基础设施失效
|
||||
// --------------------
|
||||
|
||||
const path = require('path');
|
||||
const CurrentPath = path.dirname(__filename)
|
||||
const hasNapcatParam = process.argv.includes('--enable-logging');
|
||||
if (hasNapcatParam) {
|
||||
(async () => {
|
||||
await import("file://" + path.join(CurrentPath, './napcat/napcat.mjs'));
|
||||
})();
|
||||
} else {
|
||||
require('./launcher.node').load('external_index', module);
|
||||
}
|
@@ -1,18 +0,0 @@
|
||||
@echo off
|
||||
setlocal enabledelayedexpansion
|
||||
chcp 65001
|
||||
:loop_read
|
||||
for /f "tokens=2*" %%a in ('reg query "HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\QQ" /v "UninstallString"') do (
|
||||
set "RetString=%%b"
|
||||
goto :napcat_boot
|
||||
)
|
||||
|
||||
:napcat_boot
|
||||
for %%a in ("!RetString!") do (
|
||||
set "pathWithoutUninstall=%%~dpa"
|
||||
)
|
||||
|
||||
set "QQPath=!pathWithoutUninstall!"
|
||||
cd /d !QQPath!
|
||||
echo !QQPath!
|
||||
QQ.exe --enable-logging %*
|
@@ -1,41 +0,0 @@
|
||||
function Get-QQpath {
|
||||
try {
|
||||
$key = Get-ItemProperty -Path "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\QQ"
|
||||
$uninstallString = $key.UninstallString
|
||||
return [System.IO.Path]::GetDirectoryName($uninstallString) + "\"
|
||||
}
|
||||
catch {
|
||||
throw "get QQ path error: $_"
|
||||
}
|
||||
}
|
||||
function Select-QQPath {
|
||||
Add-Type -AssemblyName System.Windows.Forms
|
||||
[System.Windows.Forms.Application]::EnableVisualStyles()
|
||||
|
||||
$dialogTitle = "Select QQ.exe"
|
||||
|
||||
$filePicker = New-Object System.Windows.Forms.OpenFileDialog
|
||||
$filePicker.Title = $dialogTitle
|
||||
$filePicker.Filter = "Executable Files (*.exe)|*.exe|All Files (*.*)|*.*"
|
||||
$filePicker.FilterIndex = 1
|
||||
$null = $filePicker.ShowDialog()
|
||||
if (-not ($filePicker.FileName)) {
|
||||
throw "User did not select an .exe file."
|
||||
}
|
||||
return $filePicker.FileName
|
||||
}
|
||||
|
||||
$params = $args -join " "
|
||||
Try {
|
||||
$QQpath = Get-QQpath
|
||||
}
|
||||
Catch {
|
||||
$QQpath = Select-QQPath
|
||||
}
|
||||
|
||||
if (!(Test-Path $QQpath)) {
|
||||
throw "provided QQ path is invalid: $QQpath"
|
||||
}
|
||||
|
||||
Set-Location -Path $QQpath
|
||||
Start-Process powershell -ArgumentList "-noexit", "-noprofile", "-command &{& chcp 65001;& ./QQ.exe --enable-logging $params}"
|
@@ -1,17 +0,0 @@
|
||||
@echo off
|
||||
setlocal enabledelayedexpansion
|
||||
:loop_read
|
||||
for /f "tokens=2*" %%a in ('reg query "HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\QQ" /v "UninstallString"') do (
|
||||
set "RetString=%%b"
|
||||
goto :napcat_boot
|
||||
)
|
||||
|
||||
:napcat_boot
|
||||
for %%a in ("!RetString!") do (
|
||||
set "pathWithoutUninstall=%%~dpa"
|
||||
)
|
||||
|
||||
set QQPath=!pathWithoutUninstall!
|
||||
cd /d !QQPath!
|
||||
echo !QQPath!
|
||||
QQ.exe --enable-logging %*
|
@@ -1,41 +0,0 @@
|
||||
function Get-QQpath {
|
||||
try {
|
||||
$key = Get-ItemProperty -Path "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\QQ"
|
||||
$uninstallString = $key.UninstallString
|
||||
return [System.IO.Path]::GetDirectoryName($uninstallString) + "\"
|
||||
}
|
||||
catch {
|
||||
throw "get QQ path error: $_"
|
||||
}
|
||||
}
|
||||
function Select-QQPath {
|
||||
Add-Type -AssemblyName System.Windows.Forms
|
||||
[System.Windows.Forms.Application]::EnableVisualStyles()
|
||||
|
||||
$dialogTitle = "Select QQ.exe"
|
||||
|
||||
$filePicker = New-Object System.Windows.Forms.OpenFileDialog
|
||||
$filePicker.Title = $dialogTitle
|
||||
$filePicker.Filter = "Executable Files (*.exe)|*.exe|All Files (*.*)|*.*"
|
||||
$filePicker.FilterIndex = 1
|
||||
$null = $filePicker.ShowDialog()
|
||||
if (-not ($filePicker.FileName)) {
|
||||
throw "User did not select an .exe file."
|
||||
}
|
||||
return $filePicker.FileName
|
||||
}
|
||||
|
||||
$params = $args -join " "
|
||||
Try {
|
||||
$QQpath = Get-QQpath
|
||||
}
|
||||
Catch {
|
||||
$QQpath = Select-QQPath
|
||||
}
|
||||
|
||||
if (!(Test-Path $QQpath)) {
|
||||
throw "provided QQ path is invalid: $QQpath"
|
||||
}
|
||||
|
||||
Set-Location -Path $QQpath
|
||||
Start-Process powershell -ArgumentList "-noexit", "-noprofile", "-command &{& ./QQ.exe --enable-logging $params}"
|
@@ -1,3 +0,0 @@
|
||||
chcp 65001
|
||||
set ELECTRON_RUN_AS_NODE=1
|
||||
"H:\Program Files\QQNT最新版\QQ.exe" %~dp0/napcat.cjs %*
|
@@ -1,15 +0,0 @@
|
||||
function Get-QQpath {
|
||||
try {
|
||||
$key = Get-ItemProperty -Path "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\QQ"
|
||||
$uninstallString = $key.UninstallString
|
||||
return [System.IO.Path]::GetDirectoryName($uninstallString) + "\QQ.exe"
|
||||
}
|
||||
catch {
|
||||
throw "Error getting UninstallString: $_"
|
||||
}
|
||||
}
|
||||
$params = $args -join " "
|
||||
$QQpath = Get-QQpath
|
||||
$Bootfile = Join-Path (Get-Location) "\dist\inject.cjs"
|
||||
$env:ELECTRON_RUN_AS_NODE = 1
|
||||
Start-Process powershell -ArgumentList "-noexit", "-noprofile", "-command &{& '$QQpath' --expose-gc $Bootfile $params}"
|
@@ -1,17 +0,0 @@
|
||||
function Get-QQpath {
|
||||
try {
|
||||
$key = Get-ItemProperty -Path "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\QQ"
|
||||
$uninstallString = $key.UninstallString
|
||||
return [System.IO.Path]::GetDirectoryName($uninstallString) + "\QQ.exe"
|
||||
}
|
||||
catch {
|
||||
return "D:\QQ.exe"
|
||||
}
|
||||
}
|
||||
$params = $args -join " "
|
||||
$QQpath = Get-QQpath
|
||||
$Bootfile = Join-Path $PSScriptRoot "napcat.cjs"
|
||||
$env:ELECTRON_RUN_AS_NODE = 1
|
||||
$argumentList = '-noexit', '-noprofile', "-command `"$QQpath`" `"$Bootfile`" $params"
|
||||
Start-Process powershell -ArgumentList $argumentList -RedirectStandardOutput "log.txt" -RedirectStandardError "error.txt"
|
||||
powershell Get-Content -Wait -Encoding UTF8 log.txt
|
@@ -1,18 +0,0 @@
|
||||
@echo off
|
||||
setlocal enabledelayedexpansion
|
||||
chcp 65001
|
||||
:loop_read
|
||||
for /f "tokens=2*" %%a in ('reg query "HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\QQ" /v "UninstallString"') do (
|
||||
set "RetString=%%b"
|
||||
goto :napcat_boot
|
||||
)
|
||||
|
||||
:napcat_boot
|
||||
for %%a in ("!RetString!") do (
|
||||
set "pathWithoutUninstall=%%~dpa"
|
||||
)
|
||||
|
||||
set "QQPath=!pathWithoutUninstall!QQ.exe"
|
||||
set ELECTRON_RUN_AS_NODE=1
|
||||
echo !QQPath!
|
||||
"!QQPath!" ./napcat.mjs %*
|
@@ -1,43 +0,0 @@
|
||||
function Get-QQpath {
|
||||
try {
|
||||
$key = Get-ItemProperty -Path "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\QQ"
|
||||
$uninstallString = $key.UninstallString
|
||||
return [System.IO.Path]::GetDirectoryName($uninstallString) + "\QQ.exe"
|
||||
}
|
||||
catch {
|
||||
throw "get QQ path error: $_"
|
||||
}
|
||||
}
|
||||
function Select-QQPath {
|
||||
Add-Type -AssemblyName System.Windows.Forms
|
||||
[System.Windows.Forms.Application]::EnableVisualStyles()
|
||||
|
||||
$dialogTitle = "Select QQ.exe"
|
||||
|
||||
$filePicker = New-Object System.Windows.Forms.OpenFileDialog
|
||||
$filePicker.Title = $dialogTitle
|
||||
$filePicker.Filter = "Executable Files (*.exe)|*.exe|All Files (*.*)|*.*"
|
||||
$filePicker.FilterIndex = 1
|
||||
$null = $filePicker.ShowDialog()
|
||||
if (-not ($filePicker.FileName)) {
|
||||
throw "User did not select an .exe file."
|
||||
}
|
||||
return $filePicker.FileName
|
||||
}
|
||||
|
||||
$params = $args -join " "
|
||||
Try {
|
||||
$QQpath = Get-QQpath
|
||||
}
|
||||
Catch {
|
||||
$QQpath = Select-QQPath
|
||||
}
|
||||
|
||||
if (!(Test-Path $QQpath)) {
|
||||
throw "provided QQ path is invalid: $QQpath"
|
||||
}
|
||||
|
||||
$Bootfile = Join-Path $PSScriptRoot "napcat.mjs"
|
||||
$env:ELECTRON_RUN_AS_NODE = 1
|
||||
$commandInfo = Get-Command $QQpath -ErrorAction Stop
|
||||
Start-Process powershell -ArgumentList "-noexit", "-noprofile", "-command &{& chcp 65001;& '$($commandInfo.Path)' $Bootfile $params}"
|
@@ -1,17 +0,0 @@
|
||||
@echo off
|
||||
setlocal enabledelayedexpansion
|
||||
:loop_read
|
||||
for /f "tokens=2*" %%a in ('reg query "HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\QQ" /v "UninstallString"') do (
|
||||
set "RetString=%%b"
|
||||
goto :napcat_boot
|
||||
)
|
||||
|
||||
:napcat_boot
|
||||
for %%a in ("!RetString!") do (
|
||||
set "pathWithoutUninstall=%%~dpa"
|
||||
)
|
||||
|
||||
set "QQPath=!pathWithoutUninstall!QQ.exe"
|
||||
set ELECTRON_RUN_AS_NODE=1
|
||||
echo !QQPath!
|
||||
"!QQPath!" ./napcat.mjs %*
|
@@ -1,43 +0,0 @@
|
||||
function Get-QQpath {
|
||||
try {
|
||||
$key = Get-ItemProperty -Path "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\QQ"
|
||||
$uninstallString = $key.UninstallString
|
||||
return [System.IO.Path]::GetDirectoryName($uninstallString) + "\QQ.exe"
|
||||
}
|
||||
catch {
|
||||
throw "get QQ path error: $_"
|
||||
}
|
||||
}
|
||||
function Select-QQPath {
|
||||
Add-Type -AssemblyName System.Windows.Forms
|
||||
[System.Windows.Forms.Application]::EnableVisualStyles()
|
||||
|
||||
$dialogTitle = "Select QQ.exe"
|
||||
|
||||
$filePicker = New-Object System.Windows.Forms.OpenFileDialog
|
||||
$filePicker.Title = $dialogTitle
|
||||
$filePicker.Filter = "Executable Files (*.exe)|*.exe|All Files (*.*)|*.*"
|
||||
$filePicker.FilterIndex = 1
|
||||
$null = $filePicker.ShowDialog()
|
||||
if (-not ($filePicker.FileName)) {
|
||||
throw "User did not select an .exe file."
|
||||
}
|
||||
return $filePicker.FileName
|
||||
}
|
||||
|
||||
$params = $args -join " "
|
||||
Try {
|
||||
$QQpath = Get-QQpath
|
||||
}
|
||||
Catch {
|
||||
$QQpath = Select-QQPath
|
||||
}
|
||||
|
||||
if (!(Test-Path $QQpath)) {
|
||||
throw "provided QQ path is invalid: $QQpath"
|
||||
}
|
||||
|
||||
$Bootfile = Join-Path $PSScriptRoot "napcat.mjs"
|
||||
$env:ELECTRON_RUN_AS_NODE = 1
|
||||
$commandInfo = Get-Command $QQpath -ErrorAction Stop
|
||||
Start-Process powershell -ArgumentList "-noexit", "-noprofile", "-command &{& '$($commandInfo.Path)' $Bootfile $params}"
|
@@ -1,21 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
get_script_dir() {
|
||||
local script_path="${1:-$0}"
|
||||
local script_dir
|
||||
script_path=$(readlink -f "$script_path")
|
||||
script_dir=$(dirname "$script_path")
|
||||
|
||||
echo "$script_dir"
|
||||
}
|
||||
|
||||
SCRIPT_DIR=$(get_script_dir)
|
||||
|
||||
export ELECTRON_RUN_AS_NODE=1
|
||||
|
||||
if ! [ -x /opt/QQ/qq ]; then
|
||||
echo "Error: /opt/QQ/qq is not executable or does not exist." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
/opt/QQ/qq "${SCRIPT_DIR}/napcat.mjs" "$@"
|
89
src/common/audio.ts
Normal file
89
src/common/audio.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
import fsPromise from 'fs/promises';
|
||||
import path from 'node:path';
|
||||
import { randomUUID } from 'crypto';
|
||||
import { spawn } from 'node:child_process';
|
||||
import { encode, getDuration, getWavFileInfo, isSilk, isWav } from 'silk-wasm';
|
||||
import { LogWrapper } from './log';
|
||||
|
||||
const ALLOW_SAMPLE_RATE = [8000, 12000, 16000, 24000, 32000, 44100, 48000];
|
||||
const EXIT_CODES = [0, 255];
|
||||
const FFMPEG_PATH = process.env.FFMPEG_PATH || 'ffmpeg';
|
||||
|
||||
async function guessDuration(pttPath: string, logger: LogWrapper) {
|
||||
const pttFileInfo = await fsPromise.stat(pttPath);
|
||||
const duration = Math.max(1, Math.floor(pttFileInfo.size / 1024 / 3)); // 3kb/s
|
||||
logger.log('通过文件大小估算语音的时长:', duration);
|
||||
return duration;
|
||||
}
|
||||
|
||||
async function convert(filePath: string, pcmPath: string, logger: LogWrapper): Promise<Buffer> {
|
||||
return new Promise<Buffer>((resolve, reject) => {
|
||||
const cp = spawn(FFMPEG_PATH, ['-y', '-i', filePath, '-ar', '24000', '-ac', '1', '-f', 's16le', pcmPath]);
|
||||
cp.on('error', (err: Error) => {
|
||||
logger.log('FFmpeg处理转换出错: ', err.message);
|
||||
reject(err);
|
||||
});
|
||||
cp.on('exit', async (code, signal) => {
|
||||
if (code == null || EXIT_CODES.includes(code)) {
|
||||
try {
|
||||
const data = await fsPromise.readFile(pcmPath);
|
||||
await fsPromise.unlink(pcmPath);
|
||||
resolve(data);
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
}
|
||||
} else {
|
||||
logger.log(`FFmpeg exit: code=${code ?? 'unknown'} sig=${signal ?? 'unknown'}`);
|
||||
reject(new Error('FFmpeg处理转换失败'));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function handleWavFile(
|
||||
file: Buffer, filePath: string, pcmPath: string, logger: LogWrapper
|
||||
): Promise<{input: Buffer, sampleRate: number}> {
|
||||
const { fmt } = getWavFileInfo(file);
|
||||
if (!ALLOW_SAMPLE_RATE.includes(fmt.sampleRate)) {
|
||||
return { input: await convert(filePath, pcmPath, logger), sampleRate: 24000 };
|
||||
}
|
||||
return { input: file, sampleRate: fmt.sampleRate };
|
||||
}
|
||||
|
||||
export async function encodeSilk(filePath: string, TEMP_DIR: string, logger: LogWrapper) {
|
||||
try {
|
||||
const file = await fsPromise.readFile(filePath);
|
||||
const pttPath = path.join(TEMP_DIR, randomUUID());
|
||||
if (!isSilk(file)) {
|
||||
logger.log(`语音文件${filePath}需要转换成silk`);
|
||||
const pcmPath = `${pttPath}.pcm`;
|
||||
const { input, sampleRate } = isWav(file)
|
||||
? (await handleWavFile(file, filePath, pcmPath, logger))
|
||||
: { input: await convert(filePath, pcmPath, logger), sampleRate: 24000 };
|
||||
const silk = await encode(input, sampleRate);
|
||||
await fsPromise.writeFile(pttPath, silk.data);
|
||||
logger.log(`语音文件${filePath}转换成功!`, pttPath, '时长:', silk.duration);
|
||||
return {
|
||||
converted: true,
|
||||
path: pttPath,
|
||||
duration: silk.duration / 1000,
|
||||
};
|
||||
} else {
|
||||
let duration = 0;
|
||||
try {
|
||||
duration = getDuration(file) / 1000;
|
||||
} catch (e: any) {
|
||||
logger.log('获取语音文件时长失败, 使用文件大小推测时长', filePath, e.stack);
|
||||
duration = await guessDuration(filePath, logger);
|
||||
}
|
||||
return {
|
||||
converted: false,
|
||||
path: filePath,
|
||||
duration,
|
||||
};
|
||||
}
|
||||
} catch (error: any) {
|
||||
logger.logError.bind(logger)('convert silk failed', error.stack);
|
||||
return {};
|
||||
}
|
||||
}
|
72
src/common/config-base.ts
Normal file
72
src/common/config-base.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
import path from 'node:path';
|
||||
import fs from 'node:fs';
|
||||
import type { NapCatCore } from '@/core';
|
||||
|
||||
export abstract class ConfigBase<T> {
|
||||
name: string;
|
||||
core: NapCatCore;
|
||||
configPath: string;
|
||||
configData: T = {} as T;
|
||||
|
||||
protected constructor(name: string, core: NapCatCore, configPath: string) {
|
||||
this.name = name;
|
||||
this.core = core;
|
||||
this.configPath = configPath;
|
||||
fs.mkdirSync(this.configPath, { recursive: true });
|
||||
this.read();
|
||||
}
|
||||
|
||||
protected getKeys(): string[] | null {
|
||||
// 决定 key 在json配置文件中的顺序
|
||||
return null;
|
||||
}
|
||||
|
||||
getConfigPath(pathName: string | undefined): string {
|
||||
if (!pathName) {
|
||||
const filename = `${this.name}.json`;
|
||||
const mainPath = this.core.context.pathWrapper.binaryPath;
|
||||
return path.join(mainPath, 'config', filename);
|
||||
} else {
|
||||
const filename = `${this.name}_${pathName}.json`;
|
||||
return path.join(this.configPath, filename);
|
||||
}
|
||||
}
|
||||
|
||||
read(): T {
|
||||
const logger = this.core.context.logger;
|
||||
const configPath = this.getConfigPath(this.core.selfInfo.uin);
|
||||
if (!fs.existsSync(configPath)) {
|
||||
try {
|
||||
fs.writeFileSync(configPath, fs.readFileSync(this.getConfigPath(undefined), 'utf-8'));
|
||||
logger.log(`[Core] [Config] 配置文件创建成功!\n`);
|
||||
} catch (e: any) {
|
||||
logger.logError.bind(logger)(`[Core] [Config] 创建配置文件时发生错误:`, e.message);
|
||||
}
|
||||
}
|
||||
try {
|
||||
this.configData = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
||||
logger.logDebug(`[Core] [Config] 配置文件${configPath}加载`, this.configData);
|
||||
return this.configData;
|
||||
} catch (e: any) {
|
||||
if (e instanceof SyntaxError) {
|
||||
logger.logError.bind(logger)(`[Core] [Config] 配置文件格式错误,请检查配置文件:`, e.message);
|
||||
} else {
|
||||
logger.logError.bind(logger)(`[Core] [Config] 读取配置文件时发生错误:`, e.message);
|
||||
}
|
||||
return {} as T;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
save(newConfigData: T = this.configData) {
|
||||
const logger = this.core.context.logger;
|
||||
const selfInfo = this.core.selfInfo;
|
||||
this.configData = newConfigData;
|
||||
const configPath = this.getConfigPath(selfInfo.uin);
|
||||
try {
|
||||
fs.writeFileSync(configPath, JSON.stringify(newConfigData, this.getKeys(), 2));
|
||||
} catch (e: any) {
|
||||
logger.logError.bind(logger)(`保存配置文件 ${configPath} 时发生错误:`, e.message);
|
||||
}
|
||||
}
|
||||
}
|
257
src/common/event.ts
Normal file
257
src/common/event.ts
Normal file
@@ -0,0 +1,257 @@
|
||||
import { NodeIQQNTWrapperSession } from '@/core/wrapper';
|
||||
import { randomUUID } from 'crypto';
|
||||
import { ListenerNamingMapping, ServiceNamingMapping } from '@/core';
|
||||
|
||||
interface InternalMapKey {
|
||||
timeout: number;
|
||||
createtime: number;
|
||||
func: (...arg: any[]) => any;
|
||||
checker: ((...args: any[]) => boolean) | undefined;
|
||||
}
|
||||
|
||||
export type ListenerClassBase = Record<string, string>;
|
||||
|
||||
export class NTEventWrapper {
|
||||
private WrapperSession: NodeIQQNTWrapperSession | undefined; //WrapperSession
|
||||
private listenerManager: Map<string, ListenerClassBase> = new Map<string, ListenerClassBase>(); //ListenerName-Unique -> Listener实例
|
||||
private EventTask = new Map<string, Map<string, Map<string, InternalMapKey>>>(); //tasks ListenerMainName -> ListenerSubName-> uuid -> {timeout,createtime,func}
|
||||
|
||||
constructor(
|
||||
wrapperSession: NodeIQQNTWrapperSession,
|
||||
) {
|
||||
this.WrapperSession = wrapperSession;
|
||||
}
|
||||
|
||||
createProxyDispatch(ListenerMainName: string) {
|
||||
const dispatcherListenerFunc = this.dispatcherListener.bind(this);
|
||||
return new Proxy(
|
||||
{},
|
||||
{
|
||||
get(target: any, prop: any, receiver: any) {
|
||||
if (typeof target[prop] === 'undefined') {
|
||||
// 如果方法不存在,返回一个函数,这个函数调用existentMethod
|
||||
return (...args: any[]) => {
|
||||
dispatcherListenerFunc(ListenerMainName, prop, ...args).then();
|
||||
};
|
||||
}
|
||||
// 如果方法存在,正常返回
|
||||
return Reflect.get(target, prop, receiver);
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
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],
|
||||
>(eventName: `${Service}/${ServiceMethod}`): T | undefined {
|
||||
const eventNameArr = eventName.split('/');
|
||||
type eventType = {
|
||||
[key: string]: () => { [key: string]: (...params: Parameters<T>) => Promise<ReturnType<T>> };
|
||||
};
|
||||
if (eventNameArr.length > 1) {
|
||||
const serviceName = 'get' + eventNameArr[0].replace('NodeIKernel', '');
|
||||
const eventName = eventNameArr[1];
|
||||
const services = (this.WrapperSession as unknown as eventType)[serviceName]();
|
||||
let event = services[eventName];
|
||||
//重新绑定this
|
||||
event = event.bind(services);
|
||||
if (event) {
|
||||
return event as T;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
createListenerFunction<T>(listenerMainName: string, uniqueCode: string = ''): T {
|
||||
const existListener = this.listenerManager.get(listenerMainName + uniqueCode);
|
||||
if (!existListener) {
|
||||
const Listener = this.createProxyDispatch(listenerMainName);
|
||||
const ServiceSubName = /^NodeIKernel(.*?)Listener$/.exec(listenerMainName)![1];
|
||||
const Service = `NodeIKernel${ServiceSubName}Service/addKernel${ServiceSubName}Listener`;
|
||||
// eslint-disable-next-line
|
||||
// @ts-ignore
|
||||
this.createEventFunction(Service)(Listener as T);
|
||||
this.listenerManager.set(listenerMainName + uniqueCode, Listener);
|
||||
return Listener as T;
|
||||
}
|
||||
return existListener as T;
|
||||
}
|
||||
|
||||
//统一回调清理事件
|
||||
async dispatcherListener(ListenerMainName: string, ListenerSubName: string, ...args: any[]) {
|
||||
this.EventTask.get(ListenerMainName)
|
||||
?.get(ListenerSubName)
|
||||
?.forEach((task, uuid) => {
|
||||
if (task.createtime + task.timeout < Date.now()) {
|
||||
this.EventTask.get(ListenerMainName)?.get(ListenerSubName)?.delete(uuid);
|
||||
return;
|
||||
}
|
||||
if (task?.checker?.(...args)) {
|
||||
task.func(...args);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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],
|
||||
>(
|
||||
serviceAndMethod: `${Service}/${ServiceMethod}`,
|
||||
...args: Parameters<EventType>
|
||||
): Promise<Awaited<ReturnType<EventType>>> {
|
||||
return (this.createEventFunction(serviceAndMethod))!(...args);
|
||||
}
|
||||
|
||||
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],
|
||||
>(
|
||||
listenerAndMethod: `${Listener}/${ListenerMethod}`,
|
||||
waitTimes = 1,
|
||||
timeout = 5000,
|
||||
checker: (...args: Parameters<ListenerType>) => boolean,
|
||||
) {
|
||||
return new Promise<Parameters<ListenerType>>((resolve, reject) => {
|
||||
const ListenerNameList = listenerAndMethod.split('/');
|
||||
const ListenerMainName = ListenerNameList[0];
|
||||
const ListenerSubName = ListenerNameList[1];
|
||||
const id = randomUUID();
|
||||
let complete = 0;
|
||||
let retData: Parameters<ListenerType> | undefined = undefined;
|
||||
|
||||
function sendDataCallback() {
|
||||
if (complete == 0) {
|
||||
reject(new Error(' ListenerName:' + listenerAndMethod + ' timeout'));
|
||||
} else {
|
||||
resolve(retData!);
|
||||
}
|
||||
}
|
||||
|
||||
const timeoutRef = setTimeout(sendDataCallback, timeout);
|
||||
const eventCallback = {
|
||||
timeout: timeout,
|
||||
createtime: Date.now(),
|
||||
checker: checker,
|
||||
func: (...args: Parameters<ListenerType>) => {
|
||||
complete++;
|
||||
retData = args;
|
||||
if (complete >= waitTimes) {
|
||||
clearTimeout(timeoutRef);
|
||||
sendDataCallback();
|
||||
}
|
||||
},
|
||||
};
|
||||
if (!this.EventTask.get(ListenerMainName)) {
|
||||
this.EventTask.set(ListenerMainName, new Map());
|
||||
}
|
||||
if (!this.EventTask.get(ListenerMainName)?.get(ListenerSubName)) {
|
||||
this.EventTask.get(ListenerMainName)?.set(ListenerSubName, new Map());
|
||||
}
|
||||
this.EventTask.get(ListenerMainName)?.get(ListenerSubName)?.set(id, eventCallback);
|
||||
this.createListenerFunction(ListenerMainName);
|
||||
});
|
||||
}
|
||||
|
||||
async callNormalEventV2<
|
||||
Service extends keyof ServiceNamingMapping,
|
||||
ServiceMethod extends Exclude<keyof ServiceNamingMapping[Service], symbol>,
|
||||
Listener extends keyof ListenerNamingMapping,
|
||||
ListenerMethod extends Exclude<keyof ListenerNamingMapping[Listener], symbol>,
|
||||
// eslint-disable-next-line
|
||||
// @ts-ignore
|
||||
EventType extends (...args: any) => any = ServiceNamingMapping[Service][ServiceMethod],
|
||||
// eslint-disable-next-line
|
||||
// @ts-ignore
|
||||
ListenerType extends (...args: any) => any = ListenerNamingMapping[Listener][ListenerMethod]
|
||||
>(
|
||||
serviceAndMethod: `${Service}/${ServiceMethod}`,
|
||||
listenerAndMethod: `${Listener}/${ListenerMethod}`,
|
||||
args: Parameters<EventType>,
|
||||
checkerEvent: (ret: Awaited<ReturnType<EventType>>) => boolean = () => true,
|
||||
checkerListener: (...args: Parameters<ListenerType>) => boolean = () => true,
|
||||
callbackTimesToWait = 1,
|
||||
timeout = 5000,
|
||||
) {
|
||||
return new Promise<[EventRet: Awaited<ReturnType<EventType>>, ...Parameters<ListenerType>]>(
|
||||
async (resolve, reject) => {
|
||||
const id = randomUUID();
|
||||
let complete = 0;
|
||||
let retData: Parameters<ListenerType> | undefined = undefined;
|
||||
let retEvent: any = {};
|
||||
|
||||
function sendDataCallback() {
|
||||
if (complete == 0) {
|
||||
reject(
|
||||
new Error(
|
||||
'Timeout: NTEvent serviceAndMethod:' +
|
||||
serviceAndMethod +
|
||||
' ListenerName:' +
|
||||
listenerAndMethod +
|
||||
' EventRet:\n' +
|
||||
JSON.stringify(retEvent, null, 4) +
|
||||
'\n',
|
||||
),
|
||||
);
|
||||
} else {
|
||||
resolve([retEvent as Awaited<ReturnType<EventType>>, ...retData!]);
|
||||
}
|
||||
}
|
||||
|
||||
const ListenerNameList = listenerAndMethod.split('/');
|
||||
const ListenerMainName = ListenerNameList[0];
|
||||
const ListenerSubName = ListenerNameList[1];
|
||||
|
||||
const timeoutRef = setTimeout(sendDataCallback, timeout);
|
||||
|
||||
const eventCallback = {
|
||||
timeout: timeout,
|
||||
createtime: Date.now(),
|
||||
checker: checkerListener,
|
||||
func: (...args: any[]) => {
|
||||
complete++;
|
||||
retData = args as Parameters<ListenerType>;
|
||||
if (complete >= callbackTimesToWait) {
|
||||
clearTimeout(timeoutRef);
|
||||
sendDataCallback();
|
||||
}
|
||||
},
|
||||
};
|
||||
if (!this.EventTask.get(ListenerMainName)) {
|
||||
this.EventTask.set(ListenerMainName, new Map());
|
||||
}
|
||||
if (!this.EventTask.get(ListenerMainName)?.get(ListenerSubName)) {
|
||||
this.EventTask.get(ListenerMainName)?.set(ListenerSubName, new Map());
|
||||
}
|
||||
this.EventTask.get(ListenerMainName)?.get(ListenerSubName)?.set(id, eventCallback);
|
||||
this.createListenerFunction(ListenerMainName);
|
||||
const eventFunction = this.createEventFunction(serviceAndMethod);
|
||||
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',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
293
src/common/file.ts
Normal file
293
src/common/file.ts
Normal file
@@ -0,0 +1,293 @@
|
||||
import fs from 'fs';
|
||||
import { stat } from 'fs/promises';
|
||||
import crypto, { randomUUID } from 'crypto';
|
||||
import util from 'util';
|
||||
import path from 'node:path';
|
||||
import * as fileType from 'file-type';
|
||||
import { solveProblem } from './helper';
|
||||
|
||||
export function isGIF(path: string) {
|
||||
const buffer = Buffer.alloc(4);
|
||||
const fd = fs.openSync(path, 'r');
|
||||
fs.readSync(fd, buffer, 0, 4, 0);
|
||||
fs.closeSync(fd);
|
||||
return buffer.toString() === 'GIF8';
|
||||
}
|
||||
|
||||
// 定义一个异步函数来检查文件是否存在
|
||||
export function checkFileReceived(path: string, timeout: number = 3000): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const startTime = Date.now();
|
||||
|
||||
function check() {
|
||||
if (fs.existsSync(path)) {
|
||||
resolve();
|
||||
} else if (Date.now() - startTime > timeout) {
|
||||
reject(new Error(`文件不存在: ${path}`));
|
||||
} else {
|
||||
setTimeout(check, 100);
|
||||
}
|
||||
}
|
||||
|
||||
check();
|
||||
});
|
||||
}
|
||||
|
||||
// 定义一个异步函数来检查文件是否存在
|
||||
export async function checkFileReceived2(path: string, timeout: number = 3000): Promise<void> {
|
||||
// 使用 Promise.race 来同时进行文件状态检查和超时计时
|
||||
// Promise.race 会返回第一个解决(resolve)或拒绝(reject)的 Promise
|
||||
await Promise.race([
|
||||
checkFile(path),
|
||||
timeoutPromise(timeout, `文件不存在: ${path}`),
|
||||
]);
|
||||
}
|
||||
|
||||
// 转换超时时间至 Promise
|
||||
function timeoutPromise(timeout: number, errorMsg: string): Promise<void> {
|
||||
return new Promise((_, reject) => {
|
||||
setTimeout(() => {
|
||||
reject(new Error(errorMsg));
|
||||
}, timeout);
|
||||
});
|
||||
}
|
||||
|
||||
// 异步检查文件是否存在
|
||||
async function checkFile(path: string): Promise<void> {
|
||||
try {
|
||||
await stat(path);
|
||||
} catch (error: any) {
|
||||
if (error.code === 'ENOENT') {
|
||||
// 如果文件不存在,则抛出一个错误
|
||||
throw new Error(`文件不存在: ${path}`);
|
||||
} else {
|
||||
// 对于 stat 调用的其他错误,重新抛出
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
// 如果文件存在,则无需做任何事情,Promise 解决(resolve)自身
|
||||
}
|
||||
|
||||
export async function file2base64(path: string) {
|
||||
const readFile = util.promisify(fs.readFile);
|
||||
const result = {
|
||||
err: '',
|
||||
data: '',
|
||||
};
|
||||
try {
|
||||
// 读取文件内容
|
||||
// if (!fs.existsSync(path)){
|
||||
// path = path.replace("\\Ori\\", "\\Thumb\\");
|
||||
// }
|
||||
try {
|
||||
await checkFileReceived(path, 5000);
|
||||
} catch (e: any) {
|
||||
result.err = e.toString();
|
||||
return result;
|
||||
}
|
||||
const data = await readFile(path);
|
||||
// 转换为Base64编码
|
||||
result.data = data.toString('base64');
|
||||
} catch (err: any) {
|
||||
result.err = err.toString();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export function calculateFileMD5(filePath: string): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
// 创建一个流式读取器
|
||||
const stream = fs.createReadStream(filePath);
|
||||
const hash = crypto.createHash('md5');
|
||||
|
||||
stream.on('data', (data: Buffer) => {
|
||||
// 当读取到数据时,更新哈希对象的状态
|
||||
hash.update(data);
|
||||
});
|
||||
|
||||
stream.on('end', () => {
|
||||
// 文件读取完成,计算哈希
|
||||
const md5 = hash.digest('hex');
|
||||
resolve(md5);
|
||||
});
|
||||
|
||||
stream.on('error', (err: Error) => {
|
||||
// 处理可能的读取错误
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export interface HttpDownloadOptions {
|
||||
url: string;
|
||||
headers?: Record<string, string> | string;
|
||||
}
|
||||
|
||||
async function tryDownload(options: string | HttpDownloadOptions, useReferer: boolean = false): Promise<Response> {
|
||||
// const chunks: Buffer[] = [];
|
||||
let url: string;
|
||||
let headers: Record<string, string> = {
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.71 Safari/537.36',
|
||||
};
|
||||
if (typeof options === 'string') {
|
||||
url = options;
|
||||
headers['Host'] = new URL(url).hostname;
|
||||
} else {
|
||||
url = options.url;
|
||||
if (options.headers) {
|
||||
if (typeof options.headers === 'string') {
|
||||
headers = JSON.parse(options.headers);
|
||||
} else {
|
||||
headers = options.headers;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (useReferer && !headers['Referer']) {
|
||||
headers['Referer'] = url;
|
||||
}
|
||||
const fetchRes = await fetch(url, { headers }).catch((err) => {
|
||||
if (err.cause) {
|
||||
throw err.cause;
|
||||
}
|
||||
throw err;
|
||||
});
|
||||
return fetchRes;
|
||||
}
|
||||
|
||||
export async function httpDownload(options: string | HttpDownloadOptions): Promise<Buffer> {
|
||||
const useReferer = typeof options === 'string';
|
||||
let resp = await tryDownload(options);
|
||||
if (resp.status === 403 && useReferer) {
|
||||
resp = await tryDownload(options, true);
|
||||
}
|
||||
if (!resp.ok) throw new Error(`下载文件失败: ${resp.statusText}`);
|
||||
const blob = await resp.blob();
|
||||
const buffer = await blob.arrayBuffer();
|
||||
return Buffer.from(buffer);
|
||||
}
|
||||
|
||||
type Uri2LocalRes = {
|
||||
success: boolean,
|
||||
errMsg: string,
|
||||
fileName: string,
|
||||
ext: string,
|
||||
path: string
|
||||
}
|
||||
|
||||
export async function checkFileV2(filePath: string) {
|
||||
try {
|
||||
const ext: string | undefined = (await fileType.fileTypeFromFile(filePath))?.ext;
|
||||
if (ext) {
|
||||
fs.renameSync(filePath, filePath + `.${ext}`);
|
||||
filePath += `.${ext}`;
|
||||
return { success: true, ext: ext, path: filePath };
|
||||
}
|
||||
} catch (e) {
|
||||
// log("获取文件类型失败", filePath,e.stack)
|
||||
}
|
||||
return { success: false, ext: '', path: filePath };
|
||||
}
|
||||
|
||||
export enum FileUriType {
|
||||
Unknown = 0,
|
||||
Local = 1,
|
||||
Remote = 2,
|
||||
Base64 = 3
|
||||
}
|
||||
|
||||
export async function checkUriType(Uri: string) {
|
||||
|
||||
const LocalFileRet = await solveProblem((uri: string) => {
|
||||
if (fs.existsSync(uri)) {
|
||||
return { Uri: uri, Type: FileUriType.Local };
|
||||
}
|
||||
return undefined;
|
||||
}, Uri);
|
||||
if (LocalFileRet) return LocalFileRet;
|
||||
const OtherFileRet = await solveProblem((uri: string) => {
|
||||
//再判断是否是Http
|
||||
if (uri.startsWith('http://') || uri.startsWith('https://')) {
|
||||
return { Uri: uri, Type: FileUriType.Remote };
|
||||
}
|
||||
//再判断是否是Base64
|
||||
if (uri.startsWith('base64://')) {
|
||||
return { Uri: uri, Type: FileUriType.Base64 };
|
||||
}
|
||||
if (uri.startsWith('file://')) {
|
||||
let filePath: string;
|
||||
const pathname = decodeURIComponent(new URL(uri).pathname);
|
||||
if (process.platform === 'win32') {
|
||||
filePath = pathname.slice(1);
|
||||
} else {
|
||||
filePath = pathname;
|
||||
}
|
||||
|
||||
return { Uri: filePath, Type: FileUriType.Local };
|
||||
}
|
||||
if (uri.startsWith('data:')) {
|
||||
const data = uri.split(',')[1];
|
||||
if (data) return { Uri: data, Type: FileUriType.Base64 };
|
||||
}
|
||||
}, Uri);
|
||||
if (OtherFileRet) return OtherFileRet;
|
||||
|
||||
return { Uri: Uri, Type: FileUriType.Unknown };
|
||||
}
|
||||
|
||||
export async function uri2local(dir: string, uri: string, filename: string | undefined = undefined): Promise<Uri2LocalRes> {
|
||||
const { Uri: HandledUri, Type: UriType } = await checkUriType(uri);
|
||||
//解析失败
|
||||
const tempName = randomUUID();
|
||||
if (!filename) filename = randomUUID();
|
||||
//解析Http和Https协议
|
||||
|
||||
if (UriType == FileUriType.Unknown) {
|
||||
return { success: false, errMsg: '未知文件类型', fileName: '', ext: '', path: '' };
|
||||
}
|
||||
//解析File协议和本地文件
|
||||
if (UriType == FileUriType.Local) {
|
||||
const fileExt = path.extname(HandledUri);
|
||||
let filename = path.basename(HandledUri, fileExt);
|
||||
filename += fileExt;
|
||||
//复制文件到临时文件并保持后缀
|
||||
const filenameTemp = tempName + fileExt;
|
||||
const filePath = path.join(dir, filenameTemp);
|
||||
fs.copyFileSync(HandledUri, filePath);
|
||||
return { success: true, errMsg: '', fileName: filename, ext: fileExt, path: filePath };
|
||||
}
|
||||
//接下来都要有文件名
|
||||
|
||||
if (UriType == FileUriType.Remote) {
|
||||
const pathInfo = path.parse(decodeURIComponent(new URL(HandledUri).pathname));
|
||||
if (pathInfo.name) {
|
||||
const pathlen = 200 - dir.length - pathInfo.name.length;
|
||||
filename = pathlen > 0 ? pathInfo.name.substring(0, pathlen) : pathInfo.name.substring(pathInfo.name.length, pathInfo.name.length - 10);//过长截断
|
||||
if (pathInfo.ext) {
|
||||
filename += pathInfo.ext;
|
||||
}
|
||||
}
|
||||
filename = filename.replace(/[/\\:*?"<>|]/g, '_');
|
||||
const fileExt = path.extname(HandledUri).replace(/[/\\:*?"<>|]/g, '_').substring(0, 10);
|
||||
const filePath = path.join(dir, tempName + fileExt);
|
||||
const buffer = await httpDownload(HandledUri);
|
||||
//没有文件就创建
|
||||
fs.writeFileSync(filePath, buffer, { flag: 'wx' });
|
||||
return { success: true, errMsg: '', fileName: filename, ext: fileExt, path: filePath };
|
||||
}
|
||||
//解析Base64
|
||||
if (UriType == FileUriType.Base64) {
|
||||
const base64 = HandledUri.replace(/^base64:\/\//, '');
|
||||
const buffer = Buffer.from(base64, 'base64');
|
||||
let filePath = path.join(dir, filename);
|
||||
let fileExt = '';
|
||||
fs.writeFileSync(filePath, buffer);
|
||||
const { success, ext, path: fileTypePath } = await checkFileV2(filePath);
|
||||
if (success) {
|
||||
filePath = fileTypePath;
|
||||
fileExt = ext;
|
||||
filename = filename + '.' + ext;
|
||||
}
|
||||
return { success: true, errMsg: '', fileName: filename, ext: fileExt, path: filePath };
|
||||
}
|
||||
return { success: false, errMsg: '未知文件类型', fileName: '', ext: '', path: '' };
|
||||
}
|
241
src/common/helper.ts
Normal file
241
src/common/helper.ts
Normal file
@@ -0,0 +1,241 @@
|
||||
import path from 'node:path';
|
||||
import fs from 'fs';
|
||||
import os from 'node:os';
|
||||
import { Peer, QQLevel } from '@/core';
|
||||
|
||||
export async function solveProblem<T extends (...arg: any[]) => any>(func: T, ...args: Parameters<T>): Promise<ReturnType<T> | undefined> {
|
||||
return new Promise<ReturnType<T> | undefined>((resolve) => {
|
||||
try {
|
||||
const result = func(...args);
|
||||
resolve(result);
|
||||
} catch (e) {
|
||||
resolve(undefined);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export async function solveAsyncProblem<T extends (...args: any[]) => Promise<any>>(func: T, ...args: Parameters<T>): Promise<Awaited<ReturnType<T>> | undefined> {
|
||||
return new Promise<Awaited<ReturnType<T>> | undefined>((resolve) => {
|
||||
func(...args).then((result) => {
|
||||
resolve(result);
|
||||
}).catch(() => {
|
||||
resolve(undefined);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export class FileNapCatOneBotUUID {
|
||||
static encodeModelId(peer: Peer, modelId: string, fileId: string, fileUUID: string = "", endString: string = ""): string {
|
||||
const data = `NapCatOneBot|ModelIdFile|${peer.chatType}|${peer.peerUid}|${modelId}|${fileId}|${fileUUID}`;
|
||||
//前四个字节塞data长度
|
||||
const length = Buffer.alloc(4 + data.length);
|
||||
length.writeUInt32BE(data.length * 2, 0);//储存data的hex长度
|
||||
length.write(data, 4);
|
||||
return length.toString('hex') + endString;
|
||||
}
|
||||
|
||||
static decodeModelId(uuid: string): undefined | {
|
||||
peer: Peer,
|
||||
modelId: string,
|
||||
fileId: string,
|
||||
fileUUID?: string
|
||||
} {
|
||||
//前四个字节是data长度
|
||||
const length = Buffer.from(uuid.slice(0, 8), 'hex').readUInt32BE(0);
|
||||
//根据length计算需要读取的长度
|
||||
const dataId = uuid.slice(8, 8 + length);
|
||||
//hex还原为string
|
||||
const realData = Buffer.from(dataId, 'hex').toString();
|
||||
if (!realData.startsWith('NapCatOneBot|ModelIdFile|')) return undefined;
|
||||
const data = realData.split('|');
|
||||
if (data.length < 6) return undefined; // compatibility requirement
|
||||
const [, , chatType, peerUid, modelId, fileId, fileUUID = undefined] = data;
|
||||
return {
|
||||
peer: {
|
||||
chatType: chatType as any,
|
||||
peerUid: peerUid,
|
||||
},
|
||||
modelId,
|
||||
fileId,
|
||||
fileUUID
|
||||
};
|
||||
}
|
||||
|
||||
static encode(peer: Peer, msgId: string, elementId: string, fileUUID: string = "", endString: string = ""): string {
|
||||
const data = `NapCatOneBot|MsgFile|${peer.chatType}|${peer.peerUid}|${msgId}|${elementId}|${fileUUID}`;
|
||||
//前四个字节塞data长度
|
||||
//一个字节8位 一个ascii字符1字节 一个hex字符4位 表示一个ascii字符需要两个hex字符
|
||||
const length = Buffer.alloc(4 + data.length);
|
||||
length.writeUInt32BE(data.length * 2, 0);
|
||||
length.write(data, 4);
|
||||
return length.toString('hex') + endString;
|
||||
}
|
||||
|
||||
static decode(uuid: string): undefined | {
|
||||
peer: Peer,
|
||||
msgId: string,
|
||||
elementId: string,
|
||||
fileUUID?: string
|
||||
} {
|
||||
//前四个字节是data长度
|
||||
const length = Buffer.from(uuid.slice(0, 8), 'hex').readUInt32BE(0);
|
||||
//根据length计算需要读取的长度
|
||||
const dataId = uuid.slice(8, 8 + length);
|
||||
//hex还原为string
|
||||
const realData = Buffer.from(dataId, 'hex').toString();
|
||||
if (!realData.startsWith('NapCatOneBot|MsgFile|')) return undefined;
|
||||
const data = realData.split('|');
|
||||
if (data.length < 6) return undefined; // compatibility requirement
|
||||
const [, , chatType, peerUid, msgId, elementId, fileUUID = undefined] = data;
|
||||
return {
|
||||
peer: {
|
||||
chatType: chatType as any,
|
||||
peerUid: peerUid,
|
||||
},
|
||||
msgId,
|
||||
elementId,
|
||||
fileUUID
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export function sleep(ms: number): Promise<void> {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
export function PromiseTimer<T>(promise: Promise<T>, ms: number): Promise<T> {
|
||||
const timeoutPromise = new Promise<T>((_, reject) =>
|
||||
setTimeout(() => reject(new Error('PromiseTimer: Operation timed out')), ms),
|
||||
);
|
||||
return Promise.race([promise, timeoutPromise]);
|
||||
}
|
||||
|
||||
export async function runAllWithTimeout<T>(tasks: Promise<T>[], timeout: number): Promise<T[]> {
|
||||
const wrappedTasks = tasks.map((task) =>
|
||||
PromiseTimer(task, timeout).then(
|
||||
(result) => ({ status: 'fulfilled', value: result }),
|
||||
(error) => ({ status: 'rejected', reason: error }),
|
||||
),
|
||||
);
|
||||
const results = await Promise.all(wrappedTasks);
|
||||
return results
|
||||
.filter((result) => result.status === 'fulfilled')
|
||||
.map((result) => (result as { status: 'fulfilled'; value: T }).value);
|
||||
}
|
||||
|
||||
export function isNull(value: any) {
|
||||
return value === undefined || value === null;
|
||||
}
|
||||
|
||||
export function isNumeric(str: string) {
|
||||
return /^\d+$/.test(str);
|
||||
}
|
||||
|
||||
export function truncateString(obj: any, maxLength = 500) {
|
||||
if (obj !== null && typeof obj === 'object') {
|
||||
Object.keys(obj).forEach((key) => {
|
||||
if (typeof obj[key] === 'string') {
|
||||
// 如果是字符串且超过指定长度,则截断
|
||||
if (obj[key].length > maxLength) {
|
||||
obj[key] = obj[key].substring(0, maxLength) + '...';
|
||||
}
|
||||
} else if (typeof obj[key] === 'object') {
|
||||
// 如果是对象或数组,则递归调用
|
||||
truncateString(obj[key], maxLength);
|
||||
}
|
||||
});
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
export function isEqual(obj1: any, obj2: any) {
|
||||
if (obj1 === obj2) return true;
|
||||
if (obj1 == null || obj2 == null) return false;
|
||||
if (typeof obj1 !== 'object' || typeof obj2 !== 'object') return obj1 === obj2;
|
||||
|
||||
const keys1 = Object.keys(obj1);
|
||||
const keys2 = Object.keys(obj2);
|
||||
|
||||
if (keys1.length !== keys2.length) return false;
|
||||
|
||||
for (const key of keys1) {
|
||||
if (!isEqual(obj1[key], obj2[key])) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
export function getDefaultQQVersionConfigInfo(): QQVersionConfigType {
|
||||
if (os.platform() === 'linux') {
|
||||
return {
|
||||
baseVersion: '3.2.12.28060',
|
||||
curVersion: '3.2.12.28060',
|
||||
prevVersion: '',
|
||||
onErrorVersions: [],
|
||||
buildId: '27254',
|
||||
};
|
||||
}
|
||||
if (os.platform() === 'darwin') {
|
||||
return {
|
||||
baseVersion: '6.9.53.28060',
|
||||
curVersion: '6.9.53.28060',
|
||||
prevVersion: '',
|
||||
onErrorVersions: [],
|
||||
buildId: '28060',
|
||||
};
|
||||
}
|
||||
return {
|
||||
baseVersion: '9.9.15-28131',
|
||||
curVersion: '9.9.15-28131',
|
||||
prevVersion: '',
|
||||
onErrorVersions: [],
|
||||
buildId: '28131',
|
||||
};
|
||||
}
|
||||
|
||||
export function getQQPackageInfoPath(exePath: string = '', version?: string): string {
|
||||
let packagePath;
|
||||
if (os.platform() === 'darwin') {
|
||||
packagePath = path.join(path.dirname(exePath), '..', 'Resources', 'app', 'package.json');
|
||||
} else if (os.platform() === 'linux') {
|
||||
packagePath = path.join(path.dirname(exePath), './resources/app/package.json');
|
||||
} else {
|
||||
packagePath = path.join(path.dirname(exePath), './versions/' + version + '/resources/app/package.json');
|
||||
}
|
||||
//下面是老版本兼容 未来去掉
|
||||
if (!fs.existsSync(packagePath)) {
|
||||
packagePath = path.join(path.dirname(exePath), './resources/app/versions/' + version + '/package.json');
|
||||
}
|
||||
return packagePath;
|
||||
}
|
||||
|
||||
export function getQQVersionConfigPath(exePath: string = ''): string | undefined {
|
||||
let configVersionInfoPath;
|
||||
if (os.platform() === 'win32') {
|
||||
configVersionInfoPath = path.join(path.dirname(exePath), 'versions', 'config.json');
|
||||
} else if (os.platform() === 'darwin') {
|
||||
const userPath = os.homedir();
|
||||
const appDataPath = path.resolve(userPath, './Library/Application Support/QQ');
|
||||
configVersionInfoPath = path.resolve(appDataPath, './versions/config.json');
|
||||
} else {
|
||||
const userPath = os.homedir();
|
||||
const appDataPath = path.resolve(userPath, './.config/QQ');
|
||||
configVersionInfoPath = path.resolve(appDataPath, './versions/config.json');
|
||||
}
|
||||
if (typeof configVersionInfoPath !== 'string') {
|
||||
return undefined;
|
||||
}
|
||||
//老版本兼容 未来去掉
|
||||
if (!fs.existsSync(configVersionInfoPath)) {
|
||||
configVersionInfoPath = path.join(path.dirname(exePath), './resources/app/versions/config.json');
|
||||
}
|
||||
if (!fs.existsSync(configVersionInfoPath)) {
|
||||
return undefined;
|
||||
}
|
||||
return configVersionInfoPath;
|
||||
}
|
||||
|
||||
export function calcQQLevel(level?: QQLevel) {
|
||||
if (!level) return 0;
|
||||
const { crownNum, sunNum, moonNum, starNum } = level;
|
||||
return crownNum * 64 + sunNum * 16 + moonNum * 4 + starNum;
|
||||
}
|
248
src/common/log.ts
Normal file
248
src/common/log.ts
Normal file
@@ -0,0 +1,248 @@
|
||||
import log4js, { Configuration } from 'log4js';
|
||||
import { truncateString } from '@/common/helper';
|
||||
import path from 'node:path';
|
||||
import chalk from 'chalk';
|
||||
import { AtType, ChatType, ElementType, MessageElement, RawMessage, SelfInfo } from '@/core';
|
||||
|
||||
export enum LogLevel {
|
||||
DEBUG = 'debug',
|
||||
INFO = 'info',
|
||||
WARN = 'warn',
|
||||
ERROR = 'error',
|
||||
FATAL = 'fatal',
|
||||
}
|
||||
|
||||
function getFormattedTimestamp() {
|
||||
const now = new Date();
|
||||
const year = now.getFullYear();
|
||||
const month = (now.getMonth() + 1).toString().padStart(2, '0');
|
||||
const day = now.getDate().toString().padStart(2, '0');
|
||||
const hours = now.getHours().toString().padStart(2, '0');
|
||||
const minutes = now.getMinutes().toString().padStart(2, '0');
|
||||
const seconds = now.getSeconds().toString().padStart(2, '0');
|
||||
const milliseconds = now.getMilliseconds().toString().padStart(3, '0');
|
||||
return `${year}-${month}-${day}_${hours}-${minutes}-${seconds}.${milliseconds}`;
|
||||
}
|
||||
|
||||
export class LogWrapper {
|
||||
fileLogEnabled = true;
|
||||
consoleLogEnabled = true;
|
||||
logConfig: Configuration;
|
||||
loggerConsole: log4js.Logger;
|
||||
loggerFile: log4js.Logger;
|
||||
loggerDefault: log4js.Logger;
|
||||
// eslint-disable-next-line no-control-regex
|
||||
colorEscape = /\x1B[@-_][0-?]*[ -/]*[@-~]/g;
|
||||
|
||||
constructor(logDir: string) {
|
||||
const filename = `${getFormattedTimestamp()}.log`;
|
||||
const logPath = path.join(logDir, filename);
|
||||
this.logConfig = {
|
||||
appenders: {
|
||||
FileAppender: { // 输出到文件的appender
|
||||
type: 'file',
|
||||
filename: logPath, // 指定日志文件的位置和文件名
|
||||
maxLogSize: 10485760, // 日志文件的最大大小(单位:字节),这里设置为10MB
|
||||
layout: {
|
||||
type: 'pattern',
|
||||
pattern: '%d{yyyy-MM-dd hh:mm:ss} [%p] %X{userInfo} | %m',
|
||||
},
|
||||
},
|
||||
ConsoleAppender: { // 输出到控制台的appender
|
||||
type: 'console',
|
||||
layout: {
|
||||
type: 'pattern',
|
||||
pattern: `%d{yyyy-MM-dd hh:mm:ss} [%[%p%]] ${chalk.magenta('%X{userInfo}')} | %m`,
|
||||
},
|
||||
},
|
||||
},
|
||||
categories: {
|
||||
default: { appenders: ['FileAppender', 'ConsoleAppender'], level: 'debug' }, // 默认情况下同时输出到文件和控制台
|
||||
file: { appenders: ['FileAppender'], level: 'debug' },
|
||||
console: { appenders: ['ConsoleAppender'], level: 'debug' },
|
||||
},
|
||||
};
|
||||
log4js.configure(this.logConfig);
|
||||
this.loggerConsole = log4js.getLogger('console');
|
||||
this.loggerFile = log4js.getLogger('file');
|
||||
this.loggerDefault = log4js.getLogger('default');
|
||||
this.setLogSelfInfo({ nick: '', uin: '', uid: '' });
|
||||
}
|
||||
|
||||
setFileAndConsoleLogLevel(fileLogLevel: LogLevel, consoleLogLevel: LogLevel) {
|
||||
this.logConfig.categories.file.level = fileLogLevel;
|
||||
this.logConfig.categories.console.level = consoleLogLevel;
|
||||
log4js.configure(this.logConfig);
|
||||
}
|
||||
|
||||
setLogSelfInfo(selfInfo: { nick: string, uin: string, uid: string }) {
|
||||
const userInfo = `${selfInfo.nick}(${selfInfo.uin})`;
|
||||
this.loggerConsole.addContext('userInfo', userInfo);
|
||||
this.loggerFile.addContext('userInfo', userInfo);
|
||||
this.loggerDefault.addContext('userInfo', userInfo);
|
||||
}
|
||||
|
||||
setFileLogEnabled(isEnabled: boolean) {
|
||||
this.fileLogEnabled = isEnabled;
|
||||
}
|
||||
|
||||
setConsoleLogEnabled(isEnabled: boolean) {
|
||||
this.consoleLogEnabled = isEnabled;
|
||||
}
|
||||
|
||||
formatMsg(msg: any[]) {
|
||||
let logMsg = '';
|
||||
for (const msgItem of msg) {
|
||||
if (msgItem instanceof Error) { // 判断是否是错误
|
||||
logMsg += msgItem.stack + ' ';
|
||||
continue;
|
||||
} else if (typeof msgItem === 'object') { // 判断是否是对象
|
||||
const obj = JSON.parse(JSON.stringify(msgItem, null, 2));
|
||||
logMsg += JSON.stringify(truncateString(obj)) + ' ';
|
||||
continue;
|
||||
}
|
||||
logMsg += msgItem + ' ';
|
||||
}
|
||||
return logMsg;
|
||||
}
|
||||
|
||||
|
||||
_log(level: LogLevel, ...args: any[]) {
|
||||
if (this.consoleLogEnabled) {
|
||||
this.loggerConsole[level](this.formatMsg(args));
|
||||
}
|
||||
if (this.fileLogEnabled) {
|
||||
this.loggerFile[level](this.formatMsg(args).replace(this.colorEscape, ''));
|
||||
}
|
||||
}
|
||||
|
||||
log(...args: any[]) {
|
||||
// info 等级
|
||||
this._log(LogLevel.INFO, ...args);
|
||||
}
|
||||
|
||||
logDebug(...args: any[]) {
|
||||
this._log(LogLevel.DEBUG, ...args);
|
||||
}
|
||||
|
||||
logError(...args: any[]) {
|
||||
this._log(LogLevel.ERROR, ...args);
|
||||
}
|
||||
|
||||
logWarn(...args: any[]) {
|
||||
this._log(LogLevel.WARN, ...args);
|
||||
}
|
||||
|
||||
logFatal(...args: any[]) {
|
||||
this._log(LogLevel.FATAL, ...args);
|
||||
}
|
||||
|
||||
logMessage(msg: RawMessage, selfInfo: SelfInfo) {
|
||||
const isSelfSent = msg.senderUin === selfInfo.uin;
|
||||
|
||||
// Intercept grey tip
|
||||
if (msg.elements[0]?.elementType === ElementType.GreyTip) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.log(`${isSelfSent ? '发送 ->' : '接收 <-' } ${rawMessageToText(msg)}`);
|
||||
}
|
||||
}
|
||||
|
||||
export function rawMessageToText(msg: RawMessage, recursiveLevel = 0): string {
|
||||
if (recursiveLevel > 2) {
|
||||
return '...';
|
||||
}
|
||||
|
||||
const tokens: string[] = [];
|
||||
|
||||
if (msg.chatType == ChatType.KCHATTYPEC2C) {
|
||||
tokens.push(`私聊 (${msg.peerUin})`);
|
||||
} else if (msg.chatType == ChatType.KCHATTYPEGROUP) {
|
||||
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 */ {
|
||||
tokens.push(`临时消息 (${msg.peerUin})`);
|
||||
}
|
||||
|
||||
// message content
|
||||
|
||||
function msgElementToText(element: MessageElement) {
|
||||
if (element.textElement) {
|
||||
if (element.textElement.atType === AtType.notAt) {
|
||||
const originalContentLines = element.textElement.content.split('\n');
|
||||
return `${originalContentLines[0]}${originalContentLines.length > 1 ? ' ...' : ''}`;
|
||||
} else if (element.textElement.atType === AtType.atAll) {
|
||||
return `@全体成员`;
|
||||
} else if (element.textElement.atType === AtType.atUser) {
|
||||
return `${element.textElement.content} (${element.textElement.atUid})`;
|
||||
}
|
||||
}
|
||||
|
||||
if (element.replyElement) {
|
||||
const recordMsgOrNull = msg.records.find(
|
||||
record => element.replyElement!.sourceMsgIdInRecords === record.msgId,
|
||||
);
|
||||
return `[回复消息 ${recordMsgOrNull &&
|
||||
recordMsgOrNull.peerUin != '284840486' && recordMsgOrNull.peerUin != '1094950020'// 非转发消息; 否则定位不到
|
||||
?
|
||||
rawMessageToText(recordMsgOrNull, recursiveLevel + 1) :
|
||||
`未找到消息记录 (MsgId = ${element.replyElement.sourceMsgIdInRecords})`
|
||||
}]`;
|
||||
}
|
||||
|
||||
if (element.picElement) {
|
||||
return '[图片]';
|
||||
}
|
||||
|
||||
if (element.fileElement) {
|
||||
return `[文件 ${element.fileElement.fileName}]`;
|
||||
}
|
||||
|
||||
if (element.videoElement) {
|
||||
return '[视频]';
|
||||
}
|
||||
|
||||
if (element.pttElement) {
|
||||
return `[语音 ${element.pttElement.duration}s]`;
|
||||
}
|
||||
|
||||
if (element.arkElement) {
|
||||
return '[卡片消息]';
|
||||
}
|
||||
|
||||
if (element.faceElement) {
|
||||
return `[表情 ${element.faceElement.faceText ?? ''}]`;
|
||||
}
|
||||
|
||||
if (element.marketFaceElement) {
|
||||
return element.marketFaceElement.faceName;
|
||||
}
|
||||
|
||||
if (element.markdownElement) {
|
||||
return '[Markdown 消息]';
|
||||
}
|
||||
|
||||
if (element.multiForwardMsgElement) {
|
||||
return '[转发消息]';
|
||||
}
|
||||
|
||||
if (element.elementType === ElementType.GreyTip) {
|
||||
return '[灰条消息]';
|
||||
}
|
||||
|
||||
return `[未实现 (ElementType = ${element.elementType})]`;
|
||||
}
|
||||
|
||||
for (const element of msg.elements) {
|
||||
tokens.push(msgElementToText(element));
|
||||
}
|
||||
|
||||
return tokens.join(' ');
|
||||
}
|
33
src/common/lru-cache.ts
Normal file
33
src/common/lru-cache.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
export class LRUCache<K, V> {
|
||||
private capacity: number;
|
||||
private cache: Map<K, V>;
|
||||
|
||||
constructor(capacity: number) {
|
||||
this.capacity = capacity;
|
||||
this.cache = new Map<K, V>();
|
||||
}
|
||||
|
||||
public get(key: K): V | undefined {
|
||||
const value = this.cache.get(key);
|
||||
if (value !== undefined) {
|
||||
// Move the accessed key to the end to mark it as most recently used
|
||||
this.cache.delete(key);
|
||||
this.cache.set(key, value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
public put(key: K, value: V): void {
|
||||
if (this.cache.has(key)) {
|
||||
// If the key already exists, move it to the end to mark it as most recently used
|
||||
this.cache.delete(key);
|
||||
} 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;
|
||||
if (firstKey !== undefined) {
|
||||
this.cache.delete(firstKey);
|
||||
}
|
||||
}
|
||||
this.cache.set(key, value);
|
||||
}
|
||||
}
|
138
src/common/message-unique.ts
Normal file
138
src/common/message-unique.ts
Normal file
@@ -0,0 +1,138 @@
|
||||
import { Peer } from '@/core';
|
||||
import crypto from 'crypto';
|
||||
|
||||
export class LimitedHashTable<K, V> {
|
||||
private keyToValue: Map<K, V> = new Map();
|
||||
private valueToKey: Map<V, K> = new Map();
|
||||
private maxSize: number;
|
||||
|
||||
constructor(maxSize: number) {
|
||||
this.maxSize = maxSize;
|
||||
}
|
||||
|
||||
resize(count: number) {
|
||||
this.maxSize = count;
|
||||
}
|
||||
|
||||
set(key: K, value: V): void {
|
||||
this.keyToValue.set(key, value);
|
||||
this.valueToKey.set(value, key);
|
||||
while (this.keyToValue.size !== this.valueToKey.size) {
|
||||
this.keyToValue.clear();
|
||||
this.valueToKey.clear();
|
||||
}
|
||||
while (this.keyToValue.size > this.maxSize || this.valueToKey.size > this.maxSize) {
|
||||
const oldestKey = this.keyToValue.keys().next().value;
|
||||
// @ts-ignore
|
||||
this.valueToKey.delete(this.keyToValue.get(oldestKey)!);
|
||||
// @ts-ignore
|
||||
this.keyToValue.delete(oldestKey);
|
||||
}
|
||||
}
|
||||
|
||||
getValue(key: K): V | undefined {
|
||||
return this.keyToValue.get(key);
|
||||
}
|
||||
|
||||
getKey(value: V): K | undefined {
|
||||
return this.valueToKey.get(value);
|
||||
}
|
||||
|
||||
deleteByValue(value: V): void {
|
||||
const key = this.valueToKey.get(value);
|
||||
if (key !== undefined) {
|
||||
this.keyToValue.delete(key);
|
||||
this.valueToKey.delete(value);
|
||||
}
|
||||
}
|
||||
|
||||
deleteByKey(key: K): void {
|
||||
const value = this.keyToValue.get(key);
|
||||
if (value !== undefined) {
|
||||
this.keyToValue.delete(key);
|
||||
this.valueToKey.delete(value);
|
||||
}
|
||||
}
|
||||
|
||||
getKeyList(): K[] {
|
||||
return Array.from(this.keyToValue.keys());
|
||||
}
|
||||
|
||||
//获取最近刚写入的几个值
|
||||
getHeads(size: number): { key: K; value: V }[] | undefined {
|
||||
const keyList = this.getKeyList();
|
||||
if (keyList.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
const result: { key: K; value: V }[] = [];
|
||||
const listSize = Math.min(size, keyList.length);
|
||||
for (let i = 0; i < listSize; i++) {
|
||||
const key = keyList[listSize - i];
|
||||
result.push({ key, value: this.keyToValue.get(key)! });
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
class MessageUniqueWrapper {
|
||||
private msgDataMap: LimitedHashTable<string, number>;
|
||||
private msgIdMap: LimitedHashTable<string, number>;
|
||||
|
||||
constructor(maxMap: number = 1000) {
|
||||
this.msgIdMap = new LimitedHashTable<string, number>(maxMap);
|
||||
this.msgDataMap = new LimitedHashTable<string, number>(maxMap);
|
||||
}
|
||||
|
||||
getRecentMsgIds(Peer: Peer, size: number): string[] {
|
||||
const heads = this.msgIdMap.getHeads(size);
|
||||
if (!heads) {
|
||||
return [];
|
||||
}
|
||||
const data = heads.map((t) => MessageUnique.getMsgIdAndPeerByShortId(t.value));
|
||||
const ret = data.filter((t) => t?.Peer.chatType === Peer.chatType && t?.Peer.peerUid === Peer.peerUid);
|
||||
return ret.map((t) => t?.MsgId).filter((t) => t !== undefined);
|
||||
}
|
||||
|
||||
createUniqueMsgId(peer: Peer, msgId: string) {
|
||||
const key = `${msgId}|${peer.chatType}|${peer.peerUid}`;
|
||||
const hash = crypto.createHash('md5').update(key).digest();
|
||||
//设置第一个bit为0 保证shortId为正数
|
||||
hash[0] &= 0x7f;
|
||||
const shortId = hash.readInt32BE(0);
|
||||
//减少性能损耗
|
||||
this.msgIdMap.set(msgId, shortId);
|
||||
this.msgDataMap.set(key, shortId);
|
||||
return shortId;
|
||||
}
|
||||
|
||||
getMsgIdAndPeerByShortId(shortId: number): { MsgId: string; Peer: Peer } | undefined {
|
||||
const data = this.msgDataMap.getKey(shortId);
|
||||
if (data) {
|
||||
const [msgId, chatTypeStr, peerUid] = data.split('|');
|
||||
const peer: Peer = {
|
||||
chatType: parseInt(chatTypeStr),
|
||||
peerUid,
|
||||
guildId: '',
|
||||
};
|
||||
return { MsgId: msgId, Peer: peer };
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
getShortIdByMsgId(msgId: string): number | undefined {
|
||||
return this.msgIdMap.getValue(msgId);
|
||||
}
|
||||
|
||||
getPeerByMsgId(msgId: string) {
|
||||
const shortId = this.msgIdMap.getValue(msgId);
|
||||
if (!shortId) return undefined;
|
||||
return this.getMsgIdAndPeerByShortId(shortId);
|
||||
}
|
||||
|
||||
resize(maxSize: number): void {
|
||||
this.msgIdMap.resize(maxSize);
|
||||
this.msgDataMap.resize(maxSize);
|
||||
}
|
||||
}
|
||||
|
||||
export const MessageUnique: MessageUniqueWrapper = new MessageUniqueWrapper();
|
35
src/common/path.ts
Normal file
35
src/common/path.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import path, { dirname } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import fs from 'fs';
|
||||
import os from 'os';
|
||||
|
||||
export class NapCatPathWrapper {
|
||||
binaryPath: string;
|
||||
logsPath: string;
|
||||
configPath: string;
|
||||
cachePath: string;
|
||||
staticPath: string;
|
||||
|
||||
constructor(mainPath: string = dirname(fileURLToPath(import.meta.url))) {
|
||||
this.binaryPath = mainPath;
|
||||
let writePath: string;
|
||||
if (os.platform() === 'darwin') {
|
||||
writePath = path.join(os.homedir(), 'Library', 'Application Support', 'QQ', 'NapCat');
|
||||
} else {
|
||||
writePath = this.binaryPath;
|
||||
}
|
||||
this.logsPath = path.join(writePath, 'logs');
|
||||
this.configPath = path.join(writePath, 'config');
|
||||
this.cachePath = path.join(writePath, 'cache');
|
||||
this.staticPath = path.join(this.binaryPath, 'static');
|
||||
if (!fs.existsSync(this.logsPath)) {
|
||||
fs.mkdirSync(this.logsPath, { recursive: true });
|
||||
}
|
||||
if (!fs.existsSync(this.configPath)) {
|
||||
fs.mkdirSync(this.configPath, { recursive: true });
|
||||
}
|
||||
if (!fs.existsSync(this.cachePath)) {
|
||||
fs.mkdirSync(this.cachePath, { recursive: true });
|
||||
}
|
||||
}
|
||||
}
|
20
src/common/proxy-handler.ts
Normal file
20
src/common/proxy-handler.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { LogWrapper } from './log';
|
||||
|
||||
export function proxyHandlerOf(logger: LogWrapper) {
|
||||
return {
|
||||
get(target: any, prop: any, receiver: any) {
|
||||
if (typeof target[prop] === 'undefined') {
|
||||
// 如果方法不存在,返回一个函数,这个函数调用existentMethod
|
||||
return (..._args: unknown[]) => {
|
||||
logger.logDebug(`${target.constructor.name} has no method ${prop}`);
|
||||
};
|
||||
}
|
||||
// 如果方法存在,正常返回
|
||||
return Reflect.get(target, prop, receiver);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function proxiedListenerOf<T extends object>(listener: T, logger: LogWrapper) {
|
||||
return new Proxy<T>(listener, proxyHandlerOf(logger));
|
||||
}
|
89
src/common/qq-basic-info.ts
Normal file
89
src/common/qq-basic-info.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
import fs from 'node:fs';
|
||||
import { systemPlatform } from '@/common/system';
|
||||
import { getDefaultQQVersionConfigInfo, getQQPackageInfoPath, getQQVersionConfigPath } from './helper';
|
||||
import AppidTable from '@/core/external/appid.json';
|
||||
import { LogWrapper } from './log';
|
||||
|
||||
export class QQBasicInfoWrapper {
|
||||
QQMainPath: string | undefined;
|
||||
QQPackageInfoPath: string | undefined;
|
||||
QQVersionConfigPath: string | undefined;
|
||||
isQuickUpdate: boolean | undefined;
|
||||
QQVersionConfig: QQVersionConfigType | undefined;
|
||||
QQPackageInfo: QQPackageInfoType | undefined;
|
||||
QQVersionAppid: string | undefined;
|
||||
QQVersionQua: string | undefined;
|
||||
context: { logger: LogWrapper };
|
||||
|
||||
constructor(context: { logger: LogWrapper }) {
|
||||
//基础目录获取
|
||||
this.context = context;
|
||||
this.QQMainPath = process.execPath;
|
||||
this.QQVersionConfigPath = getQQVersionConfigPath(this.QQMainPath);
|
||||
|
||||
|
||||
//基础信息获取 无快更则启用默认模板填充
|
||||
this.isQuickUpdate = !!this.QQVersionConfigPath;
|
||||
this.QQVersionConfig = this.isQuickUpdate
|
||||
? JSON.parse(fs.readFileSync(this.QQVersionConfigPath!).toString())
|
||||
: getDefaultQQVersionConfigInfo();
|
||||
|
||||
this.QQPackageInfoPath = getQQPackageInfoPath(this.QQMainPath, this.QQVersionConfig?.curVersion);
|
||||
this.QQPackageInfo = JSON.parse(fs.readFileSync(this.QQPackageInfoPath).toString());
|
||||
const { appid: IQQVersionAppid, qua: IQQVersionQua } = this.getAppidV2();
|
||||
this.QQVersionAppid = IQQVersionAppid;
|
||||
this.QQVersionQua = IQQVersionQua;
|
||||
}
|
||||
|
||||
//基础函数
|
||||
getQQBuildStr() {
|
||||
return this.isQuickUpdate ? this.QQVersionConfig?.buildId : this.QQPackageInfo?.buildVersion;
|
||||
}
|
||||
|
||||
getFullQQVesion() {
|
||||
const version = this.isQuickUpdate ? this.QQVersionConfig?.curVersion : this.QQPackageInfo?.version;
|
||||
if (!version) throw new Error('QQ版本获取失败');
|
||||
return version;
|
||||
}
|
||||
|
||||
requireMinNTQQBuild(buildStr: string) {
|
||||
const currentBuild = +(this.getQQBuildStr() ?? '0');
|
||||
if (currentBuild == 0) throw new Error('QQBuildStr获取失败');
|
||||
return currentBuild >= parseInt(buildStr);
|
||||
}
|
||||
|
||||
//此方法不要直接使用
|
||||
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)!;
|
||||
}
|
||||
|
||||
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) {
|
||||
const data = appidTbale[fullVersion];
|
||||
if (data) {
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
// else
|
||||
this.context.logger.log(`[QQ版本兼容性检测] 获取Appid异常 请检测NapCat/QQNT是否正常`);
|
||||
this.context.logger.log(`[QQ版本兼容性检测] ${fullVersion} 版本兼容性不佳,可能会导致一些功能无法正常使用`,);
|
||||
return { appid: this.getAppIdFallback(), qua: this.getQUAFallback() };
|
||||
}
|
||||
}
|
194
src/common/request.ts
Normal file
194
src/common/request.ts
Normal file
@@ -0,0 +1,194 @@
|
||||
import https from 'node:https';
|
||||
import http from 'node:http';
|
||||
import { readFileSync } from 'node:fs';
|
||||
|
||||
export class RequestUtil {
|
||||
// 适用于获取服务器下发cookies时获取,仅GET
|
||||
static async HttpsGetCookies(url: string): Promise<{ [key: string]: string }> {
|
||||
const client = url.startsWith('https') ? https : http;
|
||||
return new Promise((resolve, reject) => {
|
||||
const req = client.get(url, (res) => {
|
||||
let cookies: { [key: string]: string } = {};
|
||||
const handleRedirect = (res: http.IncomingMessage) => {
|
||||
//console.log(res.headers.location);
|
||||
if (res.statusCode === 301 || res.statusCode === 302) {
|
||||
if (res.headers.location) {
|
||||
const redirectUrl = new URL(res.headers.location, url);
|
||||
RequestUtil.HttpsGetCookies(redirectUrl.href).then((redirectCookies) => {
|
||||
// 合并重定向过程中的cookies
|
||||
cookies = { ...cookies, ...redirectCookies };
|
||||
resolve(cookies);
|
||||
}).catch((err) => {
|
||||
reject(err);
|
||||
});
|
||||
} else {
|
||||
resolve(cookies);
|
||||
}
|
||||
} else {
|
||||
resolve(cookies);
|
||||
}
|
||||
};
|
||||
res.on('data', () => {
|
||||
}); // Necessary to consume the stream
|
||||
res.on('end', () => {
|
||||
handleRedirect(res);
|
||||
});
|
||||
if (res.headers['set-cookie']) {
|
||||
//console.log(res.headers['set-cookie']);
|
||||
res.headers['set-cookie'].forEach((cookie) => {
|
||||
const parts = cookie.split(';')[0].split('=');
|
||||
const key = parts[0];
|
||||
const value = parts[1];
|
||||
if (key && value && key.length > 0 && value.length > 0) {
|
||||
cookies[key] = value;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
req.on('error', (error: any) => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// 请求和回复都是JSON data传原始内容 自动编码json
|
||||
static async HttpGetJson<T>(url: string, method: string = 'GET', data?: any, headers: {
|
||||
[key: string]: string
|
||||
} = {}, isJsonRet: boolean = true, isArgJson: boolean = true): Promise<T> {
|
||||
const option = new URL(url);
|
||||
const protocol = url.startsWith('https://') ? https : http;
|
||||
const options = {
|
||||
hostname: option.hostname,
|
||||
port: option.port,
|
||||
path: option.pathname + option.search,
|
||||
method: method,
|
||||
headers: headers,
|
||||
};
|
||||
// headers: {
|
||||
// 'Content-Type': 'application/json',
|
||||
// 'Content-Length': Buffer.byteLength(postData),
|
||||
// },
|
||||
return new Promise((resolve, reject) => {
|
||||
const req = protocol.request(options, (res: any) => {
|
||||
let responseBody = '';
|
||||
res.on('data', (chunk: string | Buffer) => {
|
||||
responseBody += chunk.toString();
|
||||
});
|
||||
|
||||
res.on('end', () => {
|
||||
try {
|
||||
if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
|
||||
if (isJsonRet) {
|
||||
const responseJson = JSON.parse(responseBody);
|
||||
resolve(responseJson as T);
|
||||
} else {
|
||||
resolve(responseBody as T);
|
||||
}
|
||||
} else {
|
||||
reject(new Error(`Unexpected status code: ${res.statusCode}`));
|
||||
}
|
||||
} catch (parseError) {
|
||||
reject(parseError);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
req.on('error', (error: any) => {
|
||||
reject(error);
|
||||
});
|
||||
if (method === 'POST' || method === 'PUT' || method === 'PATCH') {
|
||||
if (isArgJson) {
|
||||
req.write(JSON.stringify(data));
|
||||
} else {
|
||||
req.write(data);
|
||||
}
|
||||
|
||||
}
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
// 请求返回都是原始内容
|
||||
static async HttpGetText(url: string, method: string = 'GET', data?: any, headers: { [key: string]: string } = {}) {
|
||||
return this.HttpGetJson<string>(url, method, data, headers, false, false);
|
||||
}
|
||||
|
||||
static async createFormData(boundary: string, filePath: string): Promise<Buffer> {
|
||||
let type = 'image/png';
|
||||
if (filePath.endsWith('.jpg')) {
|
||||
type = 'image/jpeg';
|
||||
}
|
||||
const formDataParts = [
|
||||
`------${boundary}\r\n`,
|
||||
`Content-Disposition: form-data; name="share_image"; filename="${filePath}"\r\n`,
|
||||
'Content-Type: ' + type + '\r\n\r\n',
|
||||
];
|
||||
|
||||
const fileContent = readFileSync(filePath);
|
||||
const footer = `\r\n------${boundary}--`;
|
||||
return Buffer.concat([
|
||||
Buffer.from(formDataParts.join(''), 'utf8'),
|
||||
fileContent,
|
||||
Buffer.from(footer, 'utf8'),
|
||||
]);
|
||||
}
|
||||
|
||||
static async uploadImageForOpenPlatform(filePath: string, cookies: string): Promise<string> {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
type retType = { retcode: number, result?: { url: string } };
|
||||
try {
|
||||
const options = {
|
||||
hostname: 'cgi.connect.qq.com',
|
||||
port: 443,
|
||||
path: '/qqconnectopen/upload_share_image',
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Referer': 'https://cgi.connect.qq.com',
|
||||
'Cookie': cookies,
|
||||
'Accept': '*/*',
|
||||
'Connection': 'keep-alive',
|
||||
'Content-Type': 'multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW',
|
||||
},
|
||||
};
|
||||
const req = https.request(options, async (res) => {
|
||||
let responseBody = '';
|
||||
|
||||
res.on('data', (chunk: string | Buffer) => {
|
||||
responseBody += chunk.toString();
|
||||
});
|
||||
|
||||
res.on('end', () => {
|
||||
try {
|
||||
if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
|
||||
const responseJson = JSON.parse(responseBody) as retType;
|
||||
resolve(responseJson.result!.url!);
|
||||
} else {
|
||||
reject(new Error(`Unexpected status code: ${res.statusCode}`));
|
||||
}
|
||||
} catch (parseError) {
|
||||
reject(parseError);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
req.on('error', (error) => {
|
||||
reject(error);
|
||||
console.log('Error during upload:', error);
|
||||
});
|
||||
|
||||
const body = await RequestUtil.createFormData('WebKitFormBoundary7MA4YWxkTrZu0gW', filePath);
|
||||
// req.setHeader('Content-Length', Buffer.byteLength(body));
|
||||
// console.log(`Prepared data size: ${Buffer.byteLength(body)} bytes`);
|
||||
req.write(body);
|
||||
req.end();
|
||||
return;
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
}
|
||||
}
|
@@ -1,132 +0,0 @@
|
||||
import express, { Express, Request, Response } from 'express';
|
||||
import cors from 'cors';
|
||||
import http from 'http';
|
||||
import { log, logDebug, logError } from '../utils/log';
|
||||
import { ob11Config } from '@/onebot11/config';
|
||||
|
||||
type RegisterHandler = (res: Response, payload: any) => Promise<any>
|
||||
|
||||
export abstract class HttpServerBase {
|
||||
name: string = 'NapCatQQ';
|
||||
private readonly expressAPP: Express;
|
||||
private _server: http.Server | null = null;
|
||||
|
||||
public get server(): http.Server | null {
|
||||
return this._server;
|
||||
}
|
||||
|
||||
private set server(value: http.Server | null) {
|
||||
this._server = value;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
this.expressAPP = express();
|
||||
this.expressAPP.use(cors());
|
||||
this.expressAPP.use(express.urlencoded({ extended: true, limit: '5000mb' }));
|
||||
this.expressAPP.use((req, res, next) => {
|
||||
// 兼容处理没有带content-type的请求
|
||||
// log("req.headers['content-type']", req.headers['content-type'])
|
||||
req.headers['content-type'] = 'application/json';
|
||||
const originalJson = express.json({ limit: '5000mb' });
|
||||
// 调用原始的express.json()处理器
|
||||
originalJson(req, res, (err) => {
|
||||
if (err) {
|
||||
logError('Error parsing JSON:', err);
|
||||
return res.status(400).send('Invalid JSON');
|
||||
}
|
||||
next();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
authorize(req: Request, res: Response, next: () => void) {
|
||||
const serverToken = ob11Config.token;
|
||||
let clientToken = '';
|
||||
const authHeader = req.get('authorization');
|
||||
if (authHeader) {
|
||||
clientToken = authHeader.split('Bearer ').pop() || '';
|
||||
//logDebug('receive http header token', clientToken);
|
||||
} else if (req.query.access_token) {
|
||||
if (Array.isArray(req.query.access_token)) {
|
||||
clientToken = req.query.access_token[0].toString();
|
||||
} else {
|
||||
clientToken = req.query.access_token.toString();
|
||||
}
|
||||
//logDebug('receive http url token', clientToken);
|
||||
}
|
||||
|
||||
if (serverToken && clientToken != serverToken) {
|
||||
return res.status(403).send(JSON.stringify({ message: 'token verify failed!' }));
|
||||
}
|
||||
next();
|
||||
}
|
||||
|
||||
start(port: number, host: string) {
|
||||
try {
|
||||
this.expressAPP.get('/', (req: Request, res: Response) => {
|
||||
res.send(`${this.name}已启动`);
|
||||
});
|
||||
this.listen(port, host);
|
||||
} catch (e: any) {
|
||||
logError('HTTP服务启动失败', e.toString());
|
||||
// httpServerError = "HTTP服务启动失败, " + e.toString()
|
||||
}
|
||||
}
|
||||
|
||||
stop() {
|
||||
// httpServerError = ""
|
||||
if (this.server) {
|
||||
this.server.close();
|
||||
this.server = null;
|
||||
}
|
||||
}
|
||||
|
||||
restart(port: number, host: string) {
|
||||
this.stop();
|
||||
this.start(port, host);
|
||||
}
|
||||
|
||||
abstract handleFailed(res: Response, payload: any, err: any): void
|
||||
|
||||
registerRouter(method: 'post' | 'get' | string, url: string, handler: RegisterHandler) {
|
||||
if (!url.startsWith('/')) {
|
||||
url = '/' + url;
|
||||
}
|
||||
|
||||
// @ts-expect-error wait fix
|
||||
if (!this.expressAPP[method]) {
|
||||
const err = `${this.name} register router failed,${method} not exist`;
|
||||
logError(err);
|
||||
throw err;
|
||||
}
|
||||
// @ts-expect-error wait fix
|
||||
this.expressAPP[method](url, this.authorize, async (req: Request, res: Response) => {
|
||||
let payload = req.body;
|
||||
if (method == 'get') {
|
||||
payload = req.query;
|
||||
} else if (req.query) {
|
||||
payload = { ...req.query, ...req.body };
|
||||
}
|
||||
logDebug('收到http请求', url, payload);
|
||||
try {
|
||||
res.send(await handler(res, payload));
|
||||
} catch (e: any) {
|
||||
this.handleFailed(res, payload, e.stack.toString());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected listen(port: number, host: string = '0.0.0.0') {
|
||||
host = host || '0.0.0.0';
|
||||
try {
|
||||
this.server = this.expressAPP.listen(port, host, () => {
|
||||
const info = `${this.name} started ${host}:${port}`;
|
||||
log(info);
|
||||
}).on('error', (err) => {
|
||||
logError('HTTP服务启动失败', err.toString());
|
||||
});
|
||||
} catch (e: any) {
|
||||
logError('HTTP服务启动失败, 请检查监听的ip地址和端口', e.stack.toString());
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,127 +0,0 @@
|
||||
import { WebSocket, WebSocketServer } from 'ws';
|
||||
import http from 'http';
|
||||
import urlParse from 'url';
|
||||
import { IncomingMessage } from 'node:http';
|
||||
import { log } from '@/common/utils/log';
|
||||
|
||||
class WebsocketClientBase {
|
||||
private wsClient: WebSocket | undefined;
|
||||
|
||||
constructor() {
|
||||
}
|
||||
|
||||
send(msg: string) {
|
||||
if (this.wsClient && this.wsClient.readyState == WebSocket.OPEN) {
|
||||
this.wsClient.send(msg);
|
||||
}
|
||||
}
|
||||
|
||||
onMessage(msg: string) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
export class WebsocketServerBase {
|
||||
private ws: WebSocketServer | null = null;
|
||||
public token: string = '';
|
||||
|
||||
constructor() {
|
||||
}
|
||||
|
||||
start(port: number | http.Server, host: string = '') {
|
||||
if (port instanceof http.Server) {
|
||||
try {
|
||||
const wss = new WebSocketServer({
|
||||
noServer: true,
|
||||
maxPayload: 1024 * 1024 * 1024
|
||||
}).on('error', () => {
|
||||
});
|
||||
this.ws = wss;
|
||||
port.on('upgrade', function upgrade(request, socket, head) {
|
||||
wss.handleUpgrade(request, socket, head, function done(ws) {
|
||||
wss.emit('connection', ws, request);
|
||||
});
|
||||
});
|
||||
log('ws服务启动成功, 绑定到HTTP服务');
|
||||
} catch (e: any) {
|
||||
throw Error('ws服务启动失败, 可能是绑定的HTTP服务异常' + e.toString());
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
this.ws = new WebSocketServer({
|
||||
port,
|
||||
host: '',
|
||||
maxPayload: 1024 * 1024 * 1024
|
||||
}).on('error', () => {
|
||||
});
|
||||
log(`ws服务启动成功, ${host}:${port}`);
|
||||
} catch (e: any) {
|
||||
throw Error('ws服务启动失败, 请检查监听的ip和端口' + e.toString());
|
||||
}
|
||||
}
|
||||
this.ws.on('connection', (wsClient, req) => {
|
||||
const url: string = req.url!.split('?').shift() || '/';
|
||||
this.authorize(wsClient, req);
|
||||
this.onConnect(wsClient, url, req);
|
||||
wsClient.on('message', async (msg) => {
|
||||
this.onMessage(wsClient, url, msg.toString());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
stop() {
|
||||
if (this.ws) {
|
||||
this.ws.close((err) => {
|
||||
if (err) log('ws server close failed!', err);
|
||||
});
|
||||
this.ws = null;
|
||||
}
|
||||
}
|
||||
|
||||
restart(port: number) {
|
||||
this.stop();
|
||||
this.start(port);
|
||||
}
|
||||
|
||||
authorize(wsClient: WebSocket, req: IncomingMessage) {
|
||||
const url = req.url!.split('?').shift();
|
||||
log('ws connect', url);
|
||||
let clientToken: string = '';
|
||||
const authHeader = req.headers['authorization'];
|
||||
if (authHeader) {
|
||||
clientToken = authHeader.split('Bearer ').pop() || '';
|
||||
log('receive ws header token', clientToken);
|
||||
} else {
|
||||
const parsedUrl = urlParse.parse(req.url || '/', true);
|
||||
const urlToken = parsedUrl.query.access_token;
|
||||
if (urlToken) {
|
||||
if (Array.isArray(urlToken)) {
|
||||
clientToken = urlToken[0];
|
||||
} else {
|
||||
clientToken = urlToken;
|
||||
}
|
||||
log('receive ws url token', clientToken);
|
||||
}
|
||||
}
|
||||
if (this.token && clientToken != this.token) {
|
||||
this.authorizeFailed(wsClient);
|
||||
return wsClient.close();
|
||||
}
|
||||
}
|
||||
|
||||
authorizeFailed(wsClient: WebSocket) {
|
||||
|
||||
}
|
||||
|
||||
onConnect(wsClient: WebSocket, url: string, req: IncomingMessage) {
|
||||
|
||||
}
|
||||
|
||||
onMessage(wsClient: WebSocket, url: string, msg: string) {
|
||||
|
||||
}
|
||||
|
||||
sendHeart() {
|
||||
|
||||
}
|
||||
}
|
21
src/common/system.ts
Normal file
21
src/common/system.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
|
||||
// 缓解Win7设备兼容性问题
|
||||
let osName: string;
|
||||
|
||||
try {
|
||||
osName = os.hostname();
|
||||
} catch (e) {
|
||||
osName = 'NapCat'; // + crypto.randomUUID().substring(0, 4);
|
||||
}
|
||||
|
||||
|
||||
const homeDir = os.homedir();
|
||||
|
||||
export const systemPlatform = os.platform();
|
||||
export const cpuArch = os.arch();
|
||||
export const systemVersion = os.release();
|
||||
export const hostname = osName;
|
||||
export const downloadsPath = path.join(homeDir, 'Downloads');
|
||||
export const systemName = os.type();
|
17
src/common/types.ts
Normal file
17
src/common/types.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
//QQVersionType
|
||||
type QQPackageInfoType = {
|
||||
version: string;
|
||||
buildVersion: string;
|
||||
platform: string;
|
||||
eleArch: string;
|
||||
}
|
||||
type QQVersionConfigType = {
|
||||
baseVersion: string;
|
||||
curVersion: string;
|
||||
prevVersion: string;
|
||||
onErrorVersions: Array<any>;
|
||||
buildId: string;
|
||||
}
|
||||
type QQAppidTableType = {
|
||||
[key: string]: { appid: string, qua: string };
|
||||
}
|
@@ -1,89 +0,0 @@
|
||||
import path from 'node:path';
|
||||
import fs from 'node:fs';
|
||||
import { log, logDebug, logError } from '@/common/utils/log';
|
||||
import { dirname } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { selfInfo } from '@/core/data';
|
||||
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
const configDir = path.resolve(__dirname, 'config');
|
||||
fs.mkdirSync(configDir, { recursive: true });
|
||||
|
||||
|
||||
export class ConfigBase<T> {
|
||||
public name: string = 'default_config';
|
||||
private pathName: string | null = null; // 本次读取的文件路径
|
||||
constructor() {
|
||||
}
|
||||
|
||||
protected getKeys(): string[] | null {
|
||||
// 决定 key 在json配置文件中的顺序
|
||||
return null;
|
||||
}
|
||||
|
||||
getConfigDir() {
|
||||
const configDir = path.resolve(__dirname, 'config');
|
||||
fs.mkdirSync(configDir, { recursive: true });
|
||||
return configDir;
|
||||
}
|
||||
getConfigPath(pathName: string | null): string {
|
||||
const suffix = pathName ? `_${pathName}` : '';
|
||||
const filename = `${this.name}${suffix}.json`;
|
||||
return path.join(this.getConfigDir(), filename);
|
||||
}
|
||||
read() {
|
||||
// 尝试加载当前账号配置
|
||||
if (this.read_from_file(selfInfo.uin, false)) return this;
|
||||
// 尝试加载默认配置
|
||||
return this.read_from_file('', true);
|
||||
}
|
||||
read_from_file(pathName: string, createIfNotExist: boolean) {
|
||||
const configPath = this.getConfigPath(pathName);
|
||||
if (!fs.existsSync(configPath)) {
|
||||
if (!createIfNotExist) return null;
|
||||
this.pathName = pathName; // 记录有效的设置文件
|
||||
try {
|
||||
fs.writeFileSync(configPath, JSON.stringify(this, this.getKeys(), 2));
|
||||
log(`配置文件${configPath}已创建\n如果修改此文件后需要重启 NapCat 生效`);
|
||||
}
|
||||
catch (e: any) {
|
||||
logError(`创建配置文件 ${configPath} 时发生错误:`, e.message);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
try {
|
||||
const data = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
||||
logDebug(`配置文件${configPath}已加载`, data);
|
||||
Object.assign(this, data);
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-expect-error
|
||||
this.save(this); // 保存一次,让新版本的字段写入
|
||||
return this;
|
||||
} catch (e: any) {
|
||||
if (e instanceof SyntaxError) {
|
||||
logError(`配置文件 ${configPath} 格式错误,请检查配置文件:`, e.message);
|
||||
} else {
|
||||
logError(`读取配置文件 ${configPath} 时发生错误:`, e.message);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
save(config: T, overwrite: boolean = false) {
|
||||
Object.assign(this, config);
|
||||
if (overwrite) {
|
||||
// 用户要求强制写入,则变更当前文件为目标文件
|
||||
this.pathName = `${selfInfo.uin}`;
|
||||
}
|
||||
const configPath = this.getConfigPath(this.pathName);
|
||||
try {
|
||||
fs.writeFileSync(configPath, JSON.stringify(this, this.getKeys(), 2));
|
||||
} catch (e: any) {
|
||||
logError(`保存配置文件 ${configPath} 时发生错误:`, e.message);
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,227 +0,0 @@
|
||||
import { NodeIQQNTWrapperSession } from '@/core/wrapper';
|
||||
import { randomUUID } from 'crypto';
|
||||
|
||||
interface Internal_MapKey {
|
||||
timeout: number,
|
||||
createtime: number,
|
||||
func: (...arg: any[]) => any,
|
||||
checker: ((...args: any[]) => boolean) | undefined,
|
||||
}
|
||||
|
||||
export class ListenerClassBase {
|
||||
[key: string]: string;
|
||||
}
|
||||
|
||||
export interface ListenerIBase {
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-new
|
||||
new(listener: any): ListenerClassBase;
|
||||
}
|
||||
|
||||
export class NTEventWrapper {
|
||||
|
||||
private ListenerMap: { [key: string]: ListenerIBase } | undefined;//ListenerName-Unique -> Listener构造函数
|
||||
private WrapperSession: NodeIQQNTWrapperSession | undefined;//WrapperSession
|
||||
private ListenerManger: Map<string, ListenerClassBase> = new Map<string, ListenerClassBase>(); //ListenerName-Unique -> Listener实例
|
||||
private EventTask = new Map<string, Map<string, Map<string, Internal_MapKey>>>();//tasks ListenerMainName -> ListenerSubName-> uuid -> {timeout,createtime,func}
|
||||
constructor() {
|
||||
|
||||
}
|
||||
createProxyDispatch(ListenerMainName: string) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
||||
const current = this;
|
||||
return new Proxy({}, {
|
||||
get(target: any, prop: any, receiver: any) {
|
||||
// console.log('get', prop, typeof target[prop]);
|
||||
if (typeof target[prop] === 'undefined') {
|
||||
// 如果方法不存在,返回一个函数,这个函数调用existentMethod
|
||||
return (...args: any[]) => {
|
||||
current.DispatcherListener.apply(current, [ListenerMainName, prop, ...args]).then();
|
||||
};
|
||||
}
|
||||
// 如果方法存在,正常返回
|
||||
return Reflect.get(target, prop, receiver);
|
||||
}
|
||||
});
|
||||
}
|
||||
init({ ListenerMap, WrapperSession }: { ListenerMap: { [key: string]: typeof ListenerClassBase }, WrapperSession: NodeIQQNTWrapperSession }) {
|
||||
this.ListenerMap = ListenerMap;
|
||||
this.WrapperSession = WrapperSession;
|
||||
}
|
||||
CreatEventFunction<T extends (...args: any) => any>(eventName: string): T | undefined {
|
||||
const eventNameArr = eventName.split('/');
|
||||
type eventType = {
|
||||
[key: string]: () => { [key: string]: (...params: Parameters<T>) => Promise<ReturnType<T>> }
|
||||
}
|
||||
if (eventNameArr.length > 1) {
|
||||
const serviceName = 'get' + eventNameArr[0].replace('NodeIKernel', '');
|
||||
const eventName = eventNameArr[1];
|
||||
//getNodeIKernelGroupListener,GroupService
|
||||
//console.log('2', eventName);
|
||||
const services = (this.WrapperSession as unknown as eventType)[serviceName]();
|
||||
let event = services[eventName];
|
||||
//重新绑定this
|
||||
event = event.bind(services);
|
||||
if (event) {
|
||||
return event as T;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
}
|
||||
CreatListenerFunction<T>(listenerMainName: string, uniqueCode: string = ''): T {
|
||||
const ListenerType = this.ListenerMap![listenerMainName];
|
||||
let Listener = this.ListenerManger.get(listenerMainName + uniqueCode);
|
||||
if (!Listener && ListenerType) {
|
||||
Listener = new ListenerType(this.createProxyDispatch(listenerMainName));
|
||||
const ServiceSubName = listenerMainName.match(/^NodeIKernel(.*?)Listener$/)![1];
|
||||
const Service = 'NodeIKernel' + ServiceSubName + 'Service/addKernel' + ServiceSubName + 'Listener';
|
||||
const addfunc = this.CreatEventFunction<(listener: T) => number>(Service);
|
||||
addfunc!(Listener as T);
|
||||
//console.log(addfunc!(Listener as T));
|
||||
this.ListenerManger.set(listenerMainName + uniqueCode, Listener);
|
||||
}
|
||||
return Listener as T;
|
||||
}
|
||||
//统一回调清理事件
|
||||
async DispatcherListener(ListenerMainName: string, ListenerSubName: string, ...args: any[]) {
|
||||
//console.log(ListenerMainName, ListenerSubName, ...args,this.EventTask.get(ListenerMainName)?.get(ListenerSubName));
|
||||
this.EventTask.get(ListenerMainName)?.get(ListenerSubName)?.forEach((task, uuid) => {
|
||||
//console.log(task.func, uuid, task.createtime, task.timeout);
|
||||
if (task.createtime + task.timeout < Date.now()) {
|
||||
this.EventTask.get(ListenerMainName)?.get(ListenerSubName)?.delete(uuid);
|
||||
return;
|
||||
}
|
||||
if (task.checker && task.checker(...args)) {
|
||||
task.func(...args);
|
||||
}
|
||||
});
|
||||
}
|
||||
async CallNoListenerEvent<EventType extends (...args: any[]) => Promise<any> | any>(EventName = '', timeout: number = 3000, ...args: Parameters<EventType>) {
|
||||
return new Promise<Awaited<ReturnType<EventType>>>(async (resolve, reject) => {
|
||||
const EventFunc = this.CreatEventFunction<EventType>(EventName);
|
||||
let complete = false;
|
||||
const Timeouter = setTimeout(() => {
|
||||
if (!complete) {
|
||||
reject(new Error('NTEvent EventName:' + EventName + ' timeout'));
|
||||
}
|
||||
}, timeout);
|
||||
const retData = await EventFunc!(...args);
|
||||
complete = true;
|
||||
resolve(retData);
|
||||
});
|
||||
}
|
||||
async RegisterListen<ListenerType extends (...args: any[]) => void>(ListenerName = '', waitTimes = 1, timeout = 5000, checker: (...args: Parameters<ListenerType>) => boolean) {
|
||||
return new Promise<Parameters<ListenerType>>((resolve, reject) => {
|
||||
const ListenerNameList = ListenerName.split('/');
|
||||
const ListenerMainName = ListenerNameList[0];
|
||||
const ListenerSubName = ListenerNameList[1];
|
||||
const id = randomUUID();
|
||||
let complete = 0;
|
||||
let retData: Parameters<ListenerType> | undefined = undefined;
|
||||
const databack = () => {
|
||||
if (complete == 0) {
|
||||
reject(new Error(' ListenerName:' + ListenerName + ' timeout'));
|
||||
} else {
|
||||
resolve(retData!);
|
||||
}
|
||||
};
|
||||
const Timeouter = setTimeout(databack, timeout);
|
||||
const eventCallbak = {
|
||||
timeout: timeout,
|
||||
createtime: Date.now(),
|
||||
checker: checker,
|
||||
func: (...args: Parameters<ListenerType>) => {
|
||||
complete++;
|
||||
retData = args;
|
||||
if (complete >= waitTimes) {
|
||||
clearTimeout(Timeouter);
|
||||
databack();
|
||||
}
|
||||
}
|
||||
};
|
||||
if (!this.EventTask.get(ListenerMainName)) {
|
||||
this.EventTask.set(ListenerMainName, new Map());
|
||||
}
|
||||
if (!(this.EventTask.get(ListenerMainName)?.get(ListenerSubName))) {
|
||||
this.EventTask.get(ListenerMainName)?.set(ListenerSubName, new Map());
|
||||
}
|
||||
this.EventTask.get(ListenerMainName)?.get(ListenerSubName)?.set(id, eventCallbak);
|
||||
this.CreatListenerFunction(ListenerMainName);
|
||||
});
|
||||
}
|
||||
async CallNormalEvent<EventType extends (...args: any[]) => Promise<any>, ListenerType extends (...args: any[]) => void>
|
||||
(EventName = '', ListenerName = '', waitTimes = 1, timeout: number = 3000, checker: (...args: Parameters<ListenerType>) => boolean, ...args: Parameters<EventType>) {
|
||||
return new Promise<[EventRet: Awaited<ReturnType<EventType>>, ...Parameters<ListenerType>]>(async (resolve, reject) => {
|
||||
const id = randomUUID();
|
||||
let complete = 0;
|
||||
let retData: Parameters<ListenerType> | undefined = undefined;
|
||||
let retEvent: any = {};
|
||||
const databack = () => {
|
||||
if (complete == 0) {
|
||||
reject(new Error('Timeout: NTEvent EventName:' + EventName + ' ListenerName:' + ListenerName + ' EventRet:\n' + JSON.stringify(retEvent, null, 4) + '\n'));
|
||||
} else {
|
||||
resolve([retEvent as Awaited<ReturnType<EventType>>, ...retData!]);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const ListenerNameList = ListenerName.split('/');
|
||||
const ListenerMainName = ListenerNameList[0];
|
||||
const ListenerSubName = ListenerNameList[1];
|
||||
|
||||
const Timeouter = setTimeout(databack, timeout);
|
||||
|
||||
const eventCallbak = {
|
||||
timeout: timeout,
|
||||
createtime: Date.now(),
|
||||
checker: checker,
|
||||
func: (...args: any[]) => {
|
||||
complete++;
|
||||
//console.log('func', ...args);
|
||||
retData = args as Parameters<ListenerType>;
|
||||
if (complete >= waitTimes) {
|
||||
clearTimeout(Timeouter);
|
||||
databack();
|
||||
}
|
||||
}
|
||||
};
|
||||
if (!this.EventTask.get(ListenerMainName)) {
|
||||
this.EventTask.set(ListenerMainName, new Map());
|
||||
}
|
||||
if (!(this.EventTask.get(ListenerMainName)?.get(ListenerSubName))) {
|
||||
this.EventTask.get(ListenerMainName)?.set(ListenerSubName, new Map());
|
||||
}
|
||||
this.EventTask.get(ListenerMainName)?.get(ListenerSubName)?.set(id, eventCallbak);
|
||||
this.CreatListenerFunction(ListenerMainName);
|
||||
const EventFunc = this.CreatEventFunction<EventType>(EventName);
|
||||
retEvent = await EventFunc!(...(args as any[]));
|
||||
});
|
||||
}
|
||||
}
|
||||
export const NTEventDispatch = new NTEventWrapper();
|
||||
|
||||
// 示例代码 快速创建事件
|
||||
// let NTEvent = new NTEventWrapper();
|
||||
// let TestEvent = NTEvent.CreatEventFunction<(force: boolean) => Promise<Number>>('NodeIKernelProfileLikeService/GetTest');
|
||||
// if (TestEvent) {
|
||||
// TestEvent(true);
|
||||
// }
|
||||
|
||||
// 示例代码 快速创建监听Listener类
|
||||
// let NTEvent = new NTEventWrapper();
|
||||
// NTEvent.CreatListenerFunction<NodeIKernelMsgListener>('NodeIKernelMsgListener', 'core')
|
||||
|
||||
|
||||
// 调用接口
|
||||
//let NTEvent = new NTEventWrapper();
|
||||
//let ret = await NTEvent.CallNormalEvent<(force: boolean) => Promise<Number>, (data1: string, data2: number) => void>('NodeIKernelProfileLikeService/GetTest', 'NodeIKernelMsgListener/onAddSendMsg', 1, 3000, true);
|
||||
|
||||
// 注册监听 解除监听
|
||||
// NTEventDispatch.RigisterListener('NodeIKernelMsgListener/onAddSendMsg','core',cb);
|
||||
// NTEventDispatch.UnRigisterListener('NodeIKernelMsgListener/onAddSendMsg','core');
|
||||
|
||||
// let GetTest = NTEventDispatch.CreatEvent('NodeIKernelProfileLikeService/GetTest','NodeIKernelMsgListener/onAddSendMsg',Mode);
|
||||
// GetTest('test');
|
||||
|
||||
// always模式
|
||||
// NTEventDispatch.CreatEvent('NodeIKernelProfileLikeService/GetTest','NodeIKernelMsgListener/onAddSendMsg',Mode,(...args:any[])=>{ console.log(args) });
|
@@ -1,142 +0,0 @@
|
||||
import { Peer } from '@/core';
|
||||
import crypto, { randomInt, randomUUID } from 'crypto';
|
||||
import { logError } from './log';
|
||||
|
||||
class LimitedHashTable<K, V> {
|
||||
private keyToValue: Map<K, V> = new Map();
|
||||
private valueToKey: Map<V, K> = new Map();
|
||||
private maxSize: number;
|
||||
|
||||
constructor(maxSize: number) {
|
||||
this.maxSize = maxSize;
|
||||
}
|
||||
resize(count: number) {
|
||||
this.maxSize = count;
|
||||
}
|
||||
|
||||
set(key: K, value: V): void {
|
||||
// const isExist = this.keyToValue.get(key);
|
||||
// if (isExist && isExist === value) {
|
||||
// return;
|
||||
// }
|
||||
this.keyToValue.set(key, value);
|
||||
this.valueToKey.set(value, key);
|
||||
while (this.keyToValue.size !== this.valueToKey.size) {
|
||||
console.log('keyToValue.size !== valueToKey.size Error Atom');
|
||||
this.keyToValue.clear();
|
||||
this.valueToKey.clear();
|
||||
}
|
||||
// console.log('---------------');
|
||||
// console.log(this.keyToValue);
|
||||
// console.log(this.valueToKey);
|
||||
// console.log('---------------');
|
||||
while (this.keyToValue.size > this.maxSize || this.valueToKey.size > this.maxSize) {
|
||||
//console.log(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);
|
||||
}
|
||||
}
|
||||
|
||||
getValue(key: K): V | undefined {
|
||||
return this.keyToValue.get(key);
|
||||
}
|
||||
|
||||
getKey(value: V): K | undefined {
|
||||
return this.valueToKey.get(value);
|
||||
}
|
||||
|
||||
deleteByValue(value: V): void {
|
||||
const key = this.valueToKey.get(value);
|
||||
if (key !== undefined) {
|
||||
this.keyToValue.delete(key);
|
||||
this.valueToKey.delete(value);
|
||||
}
|
||||
}
|
||||
|
||||
deleteByKey(key: K): void {
|
||||
const value = this.keyToValue.get(key);
|
||||
if (value !== undefined) {
|
||||
this.keyToValue.delete(key);
|
||||
this.valueToKey.delete(value);
|
||||
}
|
||||
}
|
||||
|
||||
getKeyList(): K[] {
|
||||
return Array.from(this.keyToValue.keys());
|
||||
}
|
||||
//获取最近刚写入的几个值
|
||||
getHeads(size: number): { key: K; value: V }[] | undefined {
|
||||
const keyList = this.getKeyList();
|
||||
if (keyList.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
const result: { key: K; value: V }[] = [];
|
||||
const listSize = Math.min(size, keyList.length);
|
||||
for (let i = 0; i < listSize; i++) {
|
||||
const key = keyList[listSize - i];
|
||||
result.push({ key, value: this.keyToValue.get(key)! });
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
class MessageUniqueWrapper {
|
||||
private msgDataMap: LimitedHashTable<string, number>;
|
||||
private msgIdMap: LimitedHashTable<string, number>;
|
||||
constructor(maxMap: number = 1000) {
|
||||
this.msgIdMap = new LimitedHashTable<string, number>(maxMap);
|
||||
this.msgDataMap = new LimitedHashTable<string, number>(maxMap);
|
||||
}
|
||||
getRecentMsgIds(Peer: Peer, size: number): string[] {
|
||||
const heads = this.msgIdMap.getHeads(size);
|
||||
if (!heads) {
|
||||
return [];
|
||||
}
|
||||
const data = heads.map((t) => MessageUnique.getMsgIdAndPeerByShortId(t.value));
|
||||
const ret = data.filter((t) => t?.Peer.chatType === Peer.chatType && t?.Peer.peerUid === Peer.peerUid);
|
||||
return ret.map((t) => t?.MsgId).filter((t) => t !== undefined);
|
||||
}
|
||||
createMsg(peer: Peer, msgId: string): number | undefined {
|
||||
const key = `${msgId}|${peer.chatType}|${peer.peerUid}`;
|
||||
const hash = crypto.createHash('md5').update(key);
|
||||
const shortId = hash.digest().readInt32BE(0);
|
||||
//减少性能损耗
|
||||
// const isExist = this.msgIdMap.getKey(shortId);
|
||||
// if (isExist && isExist === msgId) {
|
||||
// return shortId;
|
||||
// }
|
||||
this.msgIdMap.set(msgId, shortId);
|
||||
this.msgDataMap.set(key, shortId);
|
||||
return shortId;
|
||||
}
|
||||
|
||||
getMsgIdAndPeerByShortId(shortId: number): { MsgId: string; Peer: Peer } | undefined {
|
||||
const data = this.msgDataMap.getKey(shortId);
|
||||
if (data) {
|
||||
const [msgId, chatTypeStr, peerUid] = data.split('|');
|
||||
const peer: Peer = {
|
||||
chatType: parseInt(chatTypeStr),
|
||||
peerUid,
|
||||
guildId: '',
|
||||
};
|
||||
return { MsgId: msgId, Peer: peer };
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
getShortIdByMsgId(msgId: string): number | undefined {
|
||||
return this.msgIdMap.getValue(msgId);
|
||||
}
|
||||
getPeerByMsgId(msgId: string) {
|
||||
const shortId = this.msgIdMap.getValue(msgId);
|
||||
if (!shortId) return undefined;
|
||||
return this.getMsgIdAndPeerByShortId(shortId);
|
||||
}
|
||||
resize(maxSize: number): void {
|
||||
this.msgIdMap.resize(maxSize);
|
||||
this.msgDataMap.resize(maxSize);
|
||||
}
|
||||
}
|
||||
|
||||
export const MessageUnique: MessageUniqueWrapper = new MessageUniqueWrapper();
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user