mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2025-07-19 12:03:37 +00:00
Compare commits
2056 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
b0eae307c2 | ||
![]() |
f5d2b54cca | ||
![]() |
3eefec3899 | ||
![]() |
b6a8094554 | ||
![]() |
4083b35436 | ||
![]() |
bb72d70baf | ||
![]() |
95d1a77f52 | ||
![]() |
051729886e | ||
![]() |
0f00123dc7 | ||
![]() |
0b0a089d86 | ||
![]() |
c711a7d99a | ||
![]() |
43f1d8c88c | ||
![]() |
e818e79d20 | ||
![]() |
cbad3ff1de | ||
![]() |
16a2e5e996 | ||
![]() |
331c6a50d0 | ||
![]() |
31c4540ec6 | ||
![]() |
1e6116554f | ||
![]() |
a12ea0e761 | ||
![]() |
c9e3bbcd9f | ||
![]() |
9c17dc1b8f | ||
![]() |
69d1cae686 | ||
![]() |
1c2404b6af | ||
![]() |
b33b33739d | ||
![]() |
2b7886c682 | ||
![]() |
106d1f6374 | ||
![]() |
e601786bd7 | ||
![]() |
fda2a98b40 | ||
![]() |
c01d70b8fc | ||
![]() |
eccbcc3e28 | ||
![]() |
7a4a255a89 | ||
![]() |
83bced82b1 | ||
![]() |
f3033ce732 | ||
![]() |
5c21a1727c | ||
![]() |
93aab437b7 | ||
![]() |
34e797270f | ||
![]() |
0f337a8d8c | ||
![]() |
cc9b83089e | ||
![]() |
a565929686 | ||
![]() |
6adacea774 | ||
![]() |
47ab5421ed | ||
![]() |
10c404d455 | ||
![]() |
dfdca11155 | ||
![]() |
698e095364 | ||
![]() |
524fd258d8 | ||
![]() |
17e70a4360 | ||
![]() |
e4a533e7b7 | ||
![]() |
0cb68d3737 | ||
![]() |
9faeadbebe | ||
![]() |
35d201cfb8 | ||
![]() |
205174255f | ||
![]() |
8873a030ab | ||
![]() |
0ab61bac12 | ||
![]() |
b1157f60f5 | ||
![]() |
bb93df06b2 | ||
![]() |
82e807fd80 | ||
![]() |
29da539467 | ||
![]() |
659aa005b0 | ||
![]() |
3f20733e7e | ||
![]() |
b15e1174d6 | ||
![]() |
05b05fd74e | ||
![]() |
d30d467a21 | ||
![]() |
cd62e8ca37 | ||
![]() |
f9e44820c1 | ||
![]() |
169ae6a4d0 | ||
![]() |
030ba15952 | ||
![]() |
964874bdad | ||
![]() |
7affa081ac | ||
![]() |
10e281ed35 | ||
![]() |
27081ae599 | ||
![]() |
61cbcdffe8 | ||
![]() |
eeb15ea564 | ||
![]() |
565c820925 | ||
![]() |
325dff5735 | ||
![]() |
397c2cf5f0 | ||
![]() |
1fbc339a42 | ||
![]() |
f2c719c60d | ||
![]() |
08505fcc9a | ||
![]() |
a79c933693 | ||
![]() |
b4cb3ddf1c | ||
![]() |
aa188a6e89 | ||
![]() |
a04b6b8a70 | ||
![]() |
11149d2743 | ||
![]() |
86bfd990db | ||
![]() |
9304430889 | ||
![]() |
095f1c270b | ||
![]() |
d3f91a832b | ||
![]() |
4790a1170f | ||
![]() |
501c392028 | ||
![]() |
9200520f70 | ||
![]() |
8122561337 | ||
![]() |
c6dc86ef8d | ||
![]() |
bea3b8485f | ||
![]() |
b807b89cdc | ||
![]() |
daac2f7fd9 | ||
![]() |
f0a5523174 | ||
![]() |
eda8fbb178 | ||
![]() |
67ca6184e9 | ||
![]() |
d79e91fc1e | ||
![]() |
1cdb93baa2 | ||
![]() |
f91991e25c | ||
![]() |
d21da47a7d | ||
![]() |
b4e22a345d | ||
![]() |
30e594ae5f | ||
![]() |
ffba3573ba | ||
![]() |
9df5bee8d3 | ||
![]() |
71c0728622 | ||
![]() |
476d8ba14d | ||
![]() |
274c956f16 | ||
![]() |
3068f9ee3d | ||
![]() |
a0c49d5f7f | ||
![]() |
a8534974fe | ||
![]() |
c517790391 | ||
![]() |
b7e875c77f | ||
![]() |
befd9c0624 | ||
![]() |
7a46f11089 | ||
![]() |
dc168bf8b9 | ||
![]() |
eef5293ca0 | ||
![]() |
a2c4498694 | ||
![]() |
938a84a460 | ||
![]() |
978d2c24ee | ||
![]() |
cdd00d665d | ||
![]() |
bb8b06c044 | ||
![]() |
604c5dcdc1 | ||
![]() |
6bc2ecdbf0 | ||
![]() |
e91c81def7 | ||
![]() |
bedd2fa15a | ||
![]() |
50465eef54 | ||
![]() |
07689adfcd | ||
![]() |
8f4f898675 | ||
![]() |
968bd7a437 | ||
![]() |
eba5900ba8 | ||
![]() |
69c477b104 | ||
![]() |
c8df8f4f54 | ||
![]() |
d35a19b4fd | ||
![]() |
a97437a6e5 | ||
![]() |
39c4473367 | ||
![]() |
b882bc721d | ||
![]() |
405cace489 | ||
![]() |
402a7b7fc9 | ||
![]() |
8ad805e654 | ||
![]() |
b23c357f73 | ||
![]() |
f561c2b0fa | ||
![]() |
5a8eea668f | ||
![]() |
777143e502 | ||
![]() |
0d8c9a82fe | ||
![]() |
d10ab1cce3 | ||
![]() |
ec25e09d73 | ||
![]() |
cba9c78ab1 | ||
![]() |
c32db4a881 | ||
![]() |
871add3071 | ||
![]() |
e661c617a3 | ||
![]() |
d4bf721540 | ||
![]() |
d91b55faed | ||
![]() |
9687832d4d | ||
![]() |
fc3e436744 | ||
![]() |
da90245f7b | ||
![]() |
410d6a85d7 | ||
![]() |
b693342e4f | ||
![]() |
acca361f2e | ||
![]() |
b663f47713 | ||
![]() |
d332b199b5 | ||
![]() |
78bac1dbd1 | ||
![]() |
724ff215f9 | ||
![]() |
68ea146469 | ||
![]() |
82583e616f | ||
![]() |
bfc339c58d | ||
![]() |
fe4427c076 | ||
![]() |
5745f388a9 | ||
![]() |
377e3c253f | ||
![]() |
3007a0c00e | ||
![]() |
f51ffc091d | ||
![]() |
c37c364a08 | ||
![]() |
331a106e9a | ||
![]() |
cd74687b7b | ||
![]() |
b3e145c1e6 | ||
![]() |
d8e1547736 | ||
![]() |
8617f01924 | ||
![]() |
55f9e75e6a | ||
![]() |
b93e7b7ed1 | ||
![]() |
89cc79ad60 | ||
![]() |
8dd0e60eea | ||
![]() |
df6113fdf6 | ||
![]() |
3a3095d15a | ||
![]() |
fb4d07391e | ||
![]() |
9bef9c85cf | ||
![]() |
b77b3f227f | ||
![]() |
6a065f0a34 | ||
![]() |
4e1e190797 | ||
![]() |
1ce8cd2100 | ||
![]() |
c03af6b9ad | ||
![]() |
adca850075 | ||
![]() |
e3616b484e | ||
![]() |
cfd7808169 | ||
![]() |
addcedc588 | ||
![]() |
bfea786088 | ||
![]() |
50e84c3c9e | ||
![]() |
dc92ace85e | ||
![]() |
1a543928b1 | ||
![]() |
652fe8d21e | ||
![]() |
199690f45f | ||
![]() |
37a4dd4b00 | ||
![]() |
34d4358bfc | ||
![]() |
90906b9019 | ||
![]() |
1c212ff2b4 | ||
![]() |
7d709f44a8 | ||
![]() |
ea9e88a18a | ||
![]() |
0be8a9c805 | ||
![]() |
fcf8139afe | ||
![]() |
62f969b50b | ||
![]() |
6726062500 | ||
![]() |
cf1f4bdcaf | ||
![]() |
b09a14ad4e | ||
![]() |
1dc62c9ca3 | ||
![]() |
beaa89a2dc | ||
![]() |
f39a000b49 | ||
![]() |
013a74fb14 | ||
![]() |
7c4964753b | ||
![]() |
8353533d60 | ||
![]() |
c06df27424 | ||
![]() |
ad82919ddf | ||
![]() |
44dbba17e1 | ||
![]() |
5ba110e1da | ||
![]() |
b6e392fdb2 | ||
![]() |
2280e83aa2 | ||
![]() |
f49b94edb9 | ||
![]() |
2428a12221 | ||
![]() |
9c353f3760 | ||
![]() |
5b86d25d7f | ||
![]() |
2b168e8bbc | ||
![]() |
537db32847 | ||
![]() |
498b7f9f2b | ||
![]() |
9935568597 | ||
![]() |
467003af8c | ||
![]() |
4c9edcc47b | ||
![]() |
24bf9cf121 | ||
![]() |
e06f6f39a9 | ||
![]() |
98ee0c307b | ||
![]() |
5e53ea0bc3 | ||
![]() |
847d88ea77 | ||
![]() |
d5046cc2b3 | ||
![]() |
3ad64b7cbb | ||
![]() |
0dbfe8ca55 | ||
![]() |
91b794d66d | ||
![]() |
0d65e1e314 | ||
![]() |
2d8f58c6d8 | ||
![]() |
65888fa816 | ||
![]() |
857e882c6e | ||
![]() |
add2931834 | ||
![]() |
cdda5f45ee | ||
![]() |
5f73d6a913 | ||
![]() |
0637882fbc | ||
![]() |
3f785bab20 | ||
![]() |
a4ca89bdd6 | ||
![]() |
1a64e796bd | ||
![]() |
a8b85a34f7 | ||
![]() |
e7bec7d6b0 | ||
![]() |
a582026037 | ||
![]() |
1a67a001c5 | ||
![]() |
406deac592 | ||
![]() |
e719ae0676 | ||
![]() |
d8b7726440 | ||
![]() |
49f642e712 | ||
![]() |
70117016ce | ||
![]() |
a4738f6281 | ||
![]() |
b1fc72d696 | ||
![]() |
457c2c2b50 | ||
![]() |
48848d7d1a | ||
![]() |
55b07ca3ab | ||
![]() |
a1d4882e18 | ||
![]() |
3843795d8f | ||
![]() |
f2bf8d42da | ||
![]() |
a3b244e114 | ||
![]() |
3093bdbc68 | ||
![]() |
9ab0799283 | ||
![]() |
236bec11ed | ||
![]() |
de48b0f940 | ||
![]() |
4885d4db86 | ||
![]() |
0c7bbda936 | ||
![]() |
fa07c2c1fb | ||
![]() |
5d17a191f6 | ||
![]() |
67fb74d3c2 | ||
![]() |
dc04cfc1b3 | ||
![]() |
d61d481965 | ||
![]() |
6b346ee1de | ||
![]() |
d0f248aaf9 | ||
![]() |
85c9227515 | ||
![]() |
73b6d3be84 | ||
![]() |
1ff6ce2343 | ||
![]() |
c145935d46 | ||
![]() |
e9ede6924e | ||
![]() |
515a21761d | ||
![]() |
8d6397028b | ||
![]() |
eb4828d81f | ||
![]() |
7e74578312 | ||
![]() |
640e3516d4 | ||
![]() |
bd295a4632 | ||
![]() |
166c30fe2c | ||
![]() |
66c1bab629 | ||
![]() |
66656304f9 | ||
![]() |
07f66e379d | ||
![]() |
7ae8fd60c4 | ||
![]() |
7275066994 | ||
![]() |
385adec186 | ||
![]() |
96b5bec5ab | ||
![]() |
6a9ec4e5f0 | ||
![]() |
d9851493df | ||
![]() |
efdb520414 | ||
![]() |
5548644aeb | ||
![]() |
e3fcd91b2d | ||
![]() |
2cae30ba88 | ||
![]() |
58cd38c4a8 | ||
![]() |
3300304feb | ||
![]() |
f0e376d06b | ||
![]() |
16f7bb48f2 | ||
![]() |
7f383dd29b | ||
![]() |
3dc529edf4 | ||
![]() |
45dedb4872 | ||
![]() |
afcdd01c0d | ||
![]() |
1164877e9a | ||
![]() |
fe92a449ba | ||
![]() |
401b0e2bd0 | ||
![]() |
cf9c71fcc1 | ||
![]() |
15a2400069 | ||
![]() |
d68a39b49e | ||
![]() |
066ca22e24 | ||
![]() |
0418b926fe | ||
![]() |
be40bbdf40 | ||
![]() |
df4f42e79e | ||
![]() |
5f80058f70 | ||
![]() |
0cbe59052d | ||
![]() |
af28a26e37 | ||
![]() |
70c596df93 | ||
![]() |
748b51428c | ||
![]() |
8ad746397c | ||
![]() |
45baed2f9a | ||
![]() |
74185f2d33 | ||
![]() |
90a91e4105 | ||
![]() |
11aa3a0315 | ||
![]() |
0c2e39214f | ||
![]() |
d89620d7a6 | ||
![]() |
edf80775b7 | ||
![]() |
46e56ac726 | ||
![]() |
40b2f6bfd6 | ||
![]() |
911e4921e2 | ||
![]() |
1db9bb419d | ||
![]() |
c6241a94e3 | ||
![]() |
1cbf75ca36 | ||
![]() |
8f85c897c8 | ||
![]() |
29c31b7aba | ||
![]() |
402919d6f2 | ||
![]() |
82608dd5ff | ||
![]() |
f312368df2 | ||
![]() |
374fc64427 | ||
![]() |
95bd74bb0d | ||
![]() |
a9f5069649 | ||
![]() |
957f7ffd8d | ||
![]() |
336dd3ce10 | ||
![]() |
47a7295477 | ||
![]() |
341a0e1c2a | ||
![]() |
c4f73d0eb8 | ||
![]() |
bd9258bae4 | ||
![]() |
e3b3260aa0 | ||
![]() |
676766c99e | ||
![]() |
1025a07593 | ||
![]() |
00c3fcd033 | ||
![]() |
b8457d4aff | ||
![]() |
a2ecf10d19 | ||
![]() |
1e63a2a7e7 | ||
![]() |
964014fc5c | ||
![]() |
fc2bb6d8c3 | ||
![]() |
1b10252d76 | ||
![]() |
ad8af12a10 | ||
![]() |
b040c9b118 | ||
![]() |
f6da7da90b | ||
![]() |
a745185408 | ||
![]() |
d3336f9027 | ||
![]() |
daf42c8203 | ||
![]() |
0a18bae3b5 | ||
![]() |
919705966c | ||
![]() |
2c54aee63e | ||
![]() |
3f80bdf2a3 | ||
![]() |
1c429b8dd3 | ||
![]() |
5669e2b0b7 | ||
![]() |
1a6a43babf | ||
![]() |
2650db5ddc | ||
![]() |
255491a107 | ||
![]() |
5c64147dfa | ||
![]() |
39f4118577 | ||
![]() |
f7f6e4736a | ||
![]() |
c635da7ebb | ||
![]() |
58124b006a | ||
![]() |
563aeccd0f | ||
![]() |
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 | ||
![]() |
385dcbc75a | ||
![]() |
74cf501c8f | ||
![]() |
0c200d6748 | ||
![]() |
e65a36c517 | ||
![]() |
126b54ad40 | ||
![]() |
78637751af | ||
![]() |
f96526ee3a | ||
![]() |
b3c7a91f3d | ||
![]() |
b8daeef0c4 | ||
![]() |
2b662944cf | ||
![]() |
3d516df01e | ||
![]() |
26b4a9b15b | ||
![]() |
0f8af273ae | ||
![]() |
fa29a31da9 | ||
![]() |
9e0d2606d8 | ||
![]() |
338dedd6e0 | ||
![]() |
1f893b1393 | ||
![]() |
b783d6f928 | ||
![]() |
8ca0d40f05 | ||
![]() |
2f40a80434 | ||
![]() |
812d8eb5bb | ||
![]() |
bf5f548349 | ||
![]() |
ebf90e72b9 | ||
![]() |
31d7d42edf | ||
![]() |
833875b42f | ||
![]() |
b901c10f3c | ||
![]() |
8ab678bd97 | ||
![]() |
55d5072f46 | ||
![]() |
a379ffd0f2 | ||
![]() |
4501d73134 | ||
![]() |
5f15774ec7 | ||
![]() |
c9e057599e | ||
![]() |
66851f5625 | ||
![]() |
b33c235b4d | ||
![]() |
d6693b6114 | ||
![]() |
36b4d26c78 | ||
![]() |
617592d90a | ||
![]() |
15a77b8070 | ||
![]() |
606eccd22b | ||
![]() |
5613450313 | ||
![]() |
c59b5564af | ||
![]() |
330b086b8b | ||
![]() |
9837ef4f36 | ||
![]() |
add46b3251 | ||
![]() |
e169199107 | ||
![]() |
92fe654850 | ||
![]() |
b257486404 | ||
![]() |
bdf2e33f40 | ||
![]() |
224d361923 | ||
![]() |
3452fa56df | ||
![]() |
cd256235da | ||
![]() |
361a164f2a | ||
![]() |
0e60d4b198 | ||
![]() |
67b47e39b4 | ||
![]() |
8f54310f63 | ||
![]() |
c7a7494d7e | ||
![]() |
af88b3166d | ||
![]() |
b7837b2a14 | ||
![]() |
950ddc749e | ||
![]() |
df081ef0cf | ||
![]() |
7b24f90d9f | ||
![]() |
f2e4579fd8 | ||
![]() |
97cb351827 | ||
![]() |
c1ec53fdbb | ||
![]() |
98214aa429 | ||
![]() |
ce7deac2dd | ||
![]() |
612092b867 | ||
![]() |
92579d5949 | ||
![]() |
9ab07060ae | ||
![]() |
0d45125d79 | ||
![]() |
9ced152778 | ||
![]() |
3685ab2e3e | ||
![]() |
be605f11f2 | ||
![]() |
8cca8df976 | ||
![]() |
990a31e961 | ||
![]() |
5db201c342 | ||
![]() |
a625e30dd4 | ||
![]() |
b236cdd060 | ||
![]() |
2db9899184 | ||
![]() |
fe5d6db986 | ||
![]() |
7c7bf8fecf | ||
![]() |
76e3a46378 | ||
![]() |
16f3897fec | ||
![]() |
045e120854 | ||
![]() |
2b7fcce9b2 | ||
![]() |
9685931694 | ||
![]() |
1dc844435a | ||
![]() |
18892379de | ||
![]() |
620d61c8dc | ||
![]() |
9f91398875 | ||
![]() |
a29b1154a9 | ||
![]() |
34d19a471a | ||
![]() |
2ef6477d7c | ||
![]() |
26e6800836 | ||
![]() |
9dbbcf3872 | ||
![]() |
6b3343e1e4 | ||
![]() |
ef48f754a5 | ||
![]() |
3be1ede847 | ||
![]() |
7bff1b61e8 | ||
![]() |
6affd0eb68 | ||
![]() |
0a112d15e0 | ||
![]() |
4e03f582bb | ||
![]() |
8f186c1c5e | ||
![]() |
cd1bae9a1f | ||
![]() |
60796c26ca | ||
![]() |
28927f950d | ||
![]() |
95f16ebc8c | ||
![]() |
25bca8385d | ||
![]() |
965c7f23b4 | ||
![]() |
33082af9cc | ||
![]() |
1f7f3565b0 | ||
![]() |
f784363696 | ||
![]() |
37bd51e138 | ||
![]() |
c367728c43 | ||
![]() |
61f0f5d884 | ||
![]() |
5f2ebeead7 | ||
![]() |
7646037fc7 | ||
![]() |
eac6d285ff | ||
![]() |
b921d5e734 | ||
![]() |
831d808e63 | ||
![]() |
451b88d7e3 | ||
![]() |
f916682a71 | ||
![]() |
2d76bcf0cf | ||
![]() |
8db294efe6 | ||
![]() |
5cc5149aed | ||
![]() |
7ecb01dc9f | ||
![]() |
8bf1a545d9 | ||
![]() |
efb2be2f94 | ||
![]() |
bd2edda494 | ||
![]() |
991172eae4 | ||
![]() |
fc7631f9aa | ||
![]() |
a21efb7d2f | ||
![]() |
03bc844ad0 | ||
![]() |
f7bdc35ed6 | ||
![]() |
0efdffd857 | ||
![]() |
6a16a42d0c | ||
![]() |
e9c00c72b1 | ||
![]() |
ab22f36b8a | ||
![]() |
c57497cd91 | ||
![]() |
ba123236e5 | ||
![]() |
338b6e4607 | ||
![]() |
f88c717560 | ||
![]() |
f8ffc92db5 | ||
![]() |
98c23c172c | ||
![]() |
781c107d8c | ||
![]() |
186668c075 | ||
![]() |
cf9f785193 | ||
![]() |
72d2d3f224 | ||
![]() |
087c76b394 | ||
![]() |
4f9fb2c8c3 | ||
![]() |
334e43e764 | ||
![]() |
7843256402 | ||
![]() |
0522ba35fe | ||
![]() |
24d3b52e0b | ||
![]() |
3177110f0f | ||
![]() |
e1b8243a67 | ||
![]() |
b1c6ce3885 | ||
![]() |
0b4b25a11e | ||
![]() |
1176fe984a | ||
![]() |
6ca768c3ee | ||
![]() |
3da1659c8d | ||
![]() |
9aa4cd319c | ||
![]() |
5af866cdca | ||
![]() |
2b421fa447 | ||
![]() |
30ec964325 | ||
![]() |
714d7d72eb | ||
![]() |
687aa0f363 | ||
![]() |
8363ab07a7 | ||
![]() |
c46a757339 | ||
![]() |
557864395b | ||
![]() |
3f7a85d80b | ||
![]() |
8d18d2ce1f | ||
![]() |
7141ba1587 | ||
![]() |
44d350a225 | ||
![]() |
239b8e72d9 | ||
![]() |
279bdb6fb1 | ||
![]() |
a0cea819da | ||
![]() |
9ab7f60544 | ||
![]() |
aaf7191bf3 | ||
![]() |
628c9be0c8 | ||
![]() |
733052720c | ||
![]() |
a10f007194 | ||
![]() |
6fa50c58d3 | ||
![]() |
c54a58d6e4 | ||
![]() |
34cb1ea3fd | ||
![]() |
f640b0ca91 | ||
![]() |
60e16da42e | ||
![]() |
0cdceb95d6 | ||
![]() |
70bd22d925 | ||
![]() |
82462dd647 | ||
![]() |
c0466e943d | ||
![]() |
b187b4695d | ||
![]() |
b1956d2a37 | ||
![]() |
590b622e5f | ||
![]() |
3d8174396a | ||
![]() |
b8dc6e9bd9 | ||
![]() |
8ee99109dc | ||
![]() |
902041d4ee | ||
![]() |
cc34aef47e | ||
![]() |
0afbbe7c7a | ||
![]() |
8004553ba7 | ||
![]() |
0023b2846a | ||
![]() |
34775c1816 | ||
![]() |
e0759e704b | ||
![]() |
0aa225ca78 | ||
![]() |
b43b4ee5c0 | ||
![]() |
0cdb8cecbf | ||
![]() |
fd6a306742 | ||
![]() |
7f3b3d2277 | ||
![]() |
8be5b977bf | ||
![]() |
d7ddb15f9c | ||
![]() |
9a6a1798d0 | ||
![]() |
14196fd349 | ||
![]() |
941b89a523 | ||
![]() |
a5f9e5f8c0 | ||
![]() |
80c3356c8f | ||
![]() |
914136b750 | ||
![]() |
f9a60795f5 | ||
![]() |
19640927c7 | ||
![]() |
22faac7e36 | ||
![]() |
30d260ab32 | ||
![]() |
115120d066 | ||
![]() |
1327844736 | ||
![]() |
29904f3cb7 | ||
![]() |
50395594b7 | ||
![]() |
9360af88b3 | ||
![]() |
376370336c | ||
![]() |
70df6e3302 | ||
![]() |
0a1fc2dc12 | ||
![]() |
9857f6e437 | ||
![]() |
56d6ebe916 | ||
![]() |
81134ea2d4 | ||
![]() |
a9f3e7fc54 | ||
![]() |
eb84e2f8c9 | ||
![]() |
61cfa0e86d | ||
![]() |
0a01b8ade9 | ||
![]() |
1457efa9a4 | ||
![]() |
fa5c7add7a | ||
![]() |
d644eba4d1 | ||
![]() |
9c422c1a8f | ||
![]() |
b6db37202f | ||
![]() |
4ca3891089 | ||
![]() |
4c7ed01776 | ||
![]() |
45c922c377 | ||
![]() |
f854c258bd | ||
![]() |
a643fac073 | ||
![]() |
861f105bea | ||
![]() |
bf10ce9f1e | ||
![]() |
ccf521d0a8 | ||
![]() |
6075d98eaa | ||
![]() |
a3801fc243 | ||
![]() |
a1f0c05f3a | ||
![]() |
a568c96929 | ||
![]() |
d58bcf3c0e | ||
![]() |
985f2e6436 | ||
![]() |
ad441fa793 | ||
![]() |
316300cc86 | ||
![]() |
5c4f37b234 | ||
![]() |
77b51a072d | ||
![]() |
2536e1ae6a | ||
![]() |
14822c9599 | ||
![]() |
e53c37adc9 | ||
![]() |
c37539354c | ||
![]() |
ae0277f33c | ||
![]() |
b863896249 | ||
![]() |
5b42f8b743 | ||
![]() |
3883fab614 | ||
![]() |
61d6bcec4b | ||
![]() |
3b5902b033 | ||
![]() |
3a88c21a3b | ||
![]() |
91a5055dee | ||
![]() |
7befd1469f | ||
![]() |
c72ebe495c | ||
![]() |
19e06b97e6 | ||
![]() |
7519825303 | ||
![]() |
d9315bf309 | ||
![]() |
8c36c809a0 | ||
![]() |
8138aa3cb2 | ||
![]() |
87aef3ca78 | ||
![]() |
a3f1d26d6b | ||
![]() |
06cebc5670 | ||
![]() |
867fd62d77 | ||
![]() |
650cdf2916 | ||
![]() |
ebf461f2fd | ||
![]() |
27fa319b2a | ||
![]() |
d95ac894f4 | ||
![]() |
ae84a8dd11 | ||
![]() |
2fc963f986 | ||
![]() |
be1f938ebd | ||
![]() |
cccf4d503d | ||
![]() |
9dad2a8ac6 | ||
![]() |
75af104f07 | ||
![]() |
76ecba245b | ||
![]() |
3697c2ced8 | ||
![]() |
b9d1d84716 | ||
![]() |
64b2d547ce | ||
![]() |
d8d2ff7e4e | ||
![]() |
8aa5dc6482 | ||
![]() |
474ba20e61 | ||
![]() |
bdea2d02a9 | ||
![]() |
c4307481f1 | ||
![]() |
b8ac1b28bd | ||
![]() |
24038cda95 | ||
![]() |
86c82e9608 | ||
![]() |
daab5d150b | ||
![]() |
9ff82bdb90 | ||
![]() |
c6d70ef1cf | ||
![]() |
15d4bb3c76 | ||
![]() |
3e698981fd | ||
![]() |
9d45c934a5 | ||
![]() |
c2bf9cf93e | ||
![]() |
b3c6fd7f26 | ||
![]() |
ccd155de71 | ||
![]() |
1f90d2e46b | ||
![]() |
4c5d974c22 | ||
![]() |
392eda1cbc | ||
![]() |
a9da3279e8 | ||
![]() |
1ce8351180 | ||
![]() |
96c334478a | ||
![]() |
f1b0875b05 | ||
![]() |
cea9e11c83 | ||
![]() |
f098b39200 | ||
![]() |
012d948b59 | ||
![]() |
3334cd0a71 | ||
![]() |
d63d53fd88 | ||
![]() |
a7fa39b2fd | ||
![]() |
40bb42e193 | ||
![]() |
9c382c639b | ||
![]() |
a43cde38f1 | ||
![]() |
c35d2e08cd | ||
![]() |
3377c383c1 | ||
![]() |
c00e6d95cd | ||
![]() |
725fccf4ed | ||
![]() |
13129bd219 | ||
![]() |
4561977bcf | ||
![]() |
40be8a91f5 | ||
![]() |
2a04d5830b | ||
![]() |
82a38574f3 | ||
![]() |
fea3a33c2b | ||
![]() |
9a502cdf6f | ||
![]() |
4b616299cf | ||
![]() |
102243e064 | ||
![]() |
4b21ac5ebe | ||
![]() |
4dd7363dd3 | ||
![]() |
3d5e5ab78f | ||
![]() |
73045a1b21 | ||
![]() |
871173a7cf | ||
![]() |
0002313093 | ||
![]() |
948cf5cca6 | ||
![]() |
d40230879c | ||
![]() |
ab22b775f1 | ||
![]() |
42c85224ba | ||
![]() |
e57444a353 | ||
![]() |
3c6503d495 | ||
![]() |
149b518f48 | ||
![]() |
74621447ff | ||
![]() |
3280952931 | ||
![]() |
9e670e2736 | ||
![]() |
9fc6347a2f | ||
![]() |
ec7a15a192 | ||
![]() |
7f99982810 | ||
![]() |
935d83aaf8 | ||
![]() |
0ff6edd546 | ||
![]() |
94f629585a | ||
![]() |
89c04be02f | ||
![]() |
3151965ea8 | ||
![]() |
bdf5159be1 | ||
![]() |
0499ebbea3 | ||
![]() |
d5843b7236 | ||
![]() |
1c9c574a90 | ||
![]() |
39acf20e48 | ||
![]() |
52eb6ed5ab | ||
![]() |
ee78d2d59d | ||
![]() |
60dc5c4a38 | ||
![]() |
50a0dc0355 | ||
![]() |
3f681ec914 | ||
![]() |
0bf499f191 | ||
![]() |
389695a0d6 | ||
![]() |
07f1afb312 | ||
![]() |
ae91e61304 | ||
![]() |
6248991b01 | ||
![]() |
7f2d57ef62 | ||
![]() |
31f8f884f1 | ||
![]() |
4f4af5985a | ||
![]() |
a716fdf6d4 | ||
![]() |
9717f64abd | ||
![]() |
adf239183a | ||
![]() |
6cf209c79c | ||
![]() |
decc5fb3c0 | ||
![]() |
1e0820d613 | ||
![]() |
70124d5177 | ||
![]() |
269de65201 | ||
![]() |
1d11abbfb6 | ||
![]() |
700f308d6e | ||
![]() |
21b6928ca6 | ||
![]() |
998c67a649 | ||
![]() |
fb99e878b0 | ||
![]() |
1619adfc27 | ||
![]() |
5510fb473f | ||
![]() |
be1878cb2b | ||
![]() |
15ab121cbd | ||
![]() |
aa79b0e861 | ||
![]() |
b80e550bcd | ||
![]() |
dbc40b5814 | ||
![]() |
0d5696a644 | ||
![]() |
ceffa05802 | ||
![]() |
d5668920b6 | ||
![]() |
516f2da144 | ||
![]() |
33c94e1888 | ||
![]() |
51ab58cd91 | ||
![]() |
aa7798d1d1 | ||
![]() |
9067a1fc92 | ||
![]() |
4024b6c564 | ||
![]() |
d39730928b | ||
![]() |
e1f049229c | ||
![]() |
8f2676ec19 | ||
![]() |
32d26248dc | ||
![]() |
16f926401b | ||
![]() |
66d60d3599 | ||
![]() |
5a35ab6c34 | ||
![]() |
ba1542bd31 | ||
![]() |
453060945a | ||
![]() |
c8351be461 | ||
![]() |
9954da22a6 | ||
![]() |
907b5611eb | ||
![]() |
5f075de212 | ||
![]() |
8fcf3c5079 | ||
![]() |
07cee90c7a | ||
![]() |
75ad495b98 | ||
![]() |
0bb7288ad2 | ||
![]() |
ad72415532 | ||
![]() |
0ad0353fc0 | ||
![]() |
9fa0dcd7aa | ||
![]() |
1f2e80cd39 | ||
![]() |
6cb6034d43 | ||
![]() |
25134c6ac6 | ||
![]() |
92bf42878a | ||
![]() |
9f4582d158 | ||
![]() |
68af73970e | ||
![]() |
b6ed8d4975 | ||
![]() |
d07d3645ce | ||
![]() |
123759ab17 | ||
![]() |
f2f1f893d8 | ||
![]() |
db93a8eed2 | ||
![]() |
12ab6d4a7d | ||
![]() |
add759e889 | ||
![]() |
f315f7977d | ||
![]() |
f2f6701ebd | ||
![]() |
1a92794d33 | ||
![]() |
7640deb798 | ||
![]() |
f1e8ef1cf6 | ||
![]() |
5e5ac0162e | ||
![]() |
0c013820f0 | ||
![]() |
4b3a9e5847 | ||
![]() |
e4982256a4 | ||
![]() |
babc4927a8 | ||
![]() |
6dd84cf469 | ||
![]() |
a8800e3899 | ||
![]() |
5f03496046 | ||
![]() |
41500c17a2 | ||
![]() |
2dcfde8b9a | ||
![]() |
5c3305d8fa | ||
![]() |
0d1fe99f53 | ||
![]() |
4c03ffeec7 | ||
![]() |
8101d17482 | ||
![]() |
bc7b4dcc2a | ||
![]() |
3db8b9078d | ||
![]() |
943dbbefd3 | ||
![]() |
480abcb853 | ||
![]() |
60aaaff58e | ||
![]() |
e3b889bbe8 | ||
![]() |
ac5506a43b | ||
![]() |
b29f533a3b | ||
![]() |
a8ee86b09e | ||
![]() |
0238c53302 | ||
![]() |
665e3c806f | ||
![]() |
8c96838441 | ||
![]() |
4a722daec6 | ||
![]() |
4e0cdbcb91 | ||
![]() |
08976624cd | ||
![]() |
fdeba94653 | ||
![]() |
d3b100b7e5 | ||
![]() |
1de3e18b08 | ||
![]() |
d5c3c95682 | ||
![]() |
dabe1e29ed | ||
![]() |
203d1c0cfc | ||
![]() |
7edd8601be | ||
![]() |
a4423247f4 | ||
![]() |
4834b203a0 | ||
![]() |
bbabb32d13 | ||
![]() |
95112d6bdf | ||
![]() |
36cdca5a3e | ||
![]() |
6980a9f3fc | ||
![]() |
7b09479cd2 | ||
![]() |
5825fd6f36 | ||
![]() |
2d5b45dd82 | ||
![]() |
52dda1d1fe | ||
![]() |
420624bee4 | ||
![]() |
8abde7b7d0 | ||
![]() |
9e5b1ba28e | ||
![]() |
b9c7d3c18e | ||
![]() |
10aeccbbe5 | ||
![]() |
15d351ebc2 | ||
![]() |
7194f31cb6 | ||
![]() |
84b7e82446 | ||
![]() |
8264423b1a | ||
![]() |
37f897f3bf | ||
![]() |
fe3efac145 | ||
![]() |
9773aebefc | ||
![]() |
06f2b8c371 | ||
![]() |
e8f0bb8350 | ||
![]() |
9bfa6b827b | ||
![]() |
b21bc17a58 | ||
![]() |
f4d5d417d0 | ||
![]() |
91fc83621e | ||
![]() |
461feca0ca | ||
![]() |
5e9afab3f7 | ||
![]() |
2599ca6450 | ||
![]() |
fc99ad3a39 | ||
![]() |
10e1c3e72c | ||
![]() |
af5dedd4d4 | ||
![]() |
3b986c1076 | ||
![]() |
72f77e8b7c | ||
![]() |
e893bf676f | ||
![]() |
80eb34f611 | ||
![]() |
5d01947552 | ||
![]() |
d3a025ef7b | ||
![]() |
c466df841e | ||
![]() |
b3c6e2a0f3 | ||
![]() |
076c9cfed7 | ||
![]() |
c3f3d12f83 | ||
![]() |
44974034ec | ||
![]() |
d6175acd38 | ||
![]() |
62eee5f05c | ||
![]() |
d4e5201913 | ||
![]() |
f4d584765a | ||
![]() |
26e224f852 | ||
![]() |
252358ed66 | ||
![]() |
475afeb7c8 | ||
![]() |
7cbbb846eb | ||
![]() |
25f947968c | ||
![]() |
cad824dcbc | ||
![]() |
e506f50b00 | ||
![]() |
96ec149a98 | ||
![]() |
8c913512f6 | ||
![]() |
4cc307299d | ||
![]() |
407c6b4c5f | ||
![]() |
8f87070434 | ||
![]() |
4a63996ee2 | ||
![]() |
0358fe7620 | ||
![]() |
55e64395ed | ||
![]() |
ff5fb18e14 | ||
![]() |
52dd960857 | ||
![]() |
430221c2de | ||
![]() |
217bdf8f92 | ||
![]() |
38c6c869bf | ||
![]() |
84d46da67e | ||
![]() |
eb9d6240d7 | ||
![]() |
2d44a871b0 | ||
![]() |
3f89f350ff | ||
![]() |
1a8407a782 | ||
![]() |
cf288a3f73 | ||
![]() |
f1f37fb180 | ||
![]() |
fb0dd079fd | ||
![]() |
a6c584c85c | ||
![]() |
77adf35a30 | ||
![]() |
dc6951c2a9 | ||
![]() |
d14ba3f0f7 | ||
![]() |
78ddf36e35 | ||
![]() |
d42734624d | ||
![]() |
b5dbd9d59b | ||
![]() |
bed3e1289b | ||
![]() |
b11ca4e60e | ||
![]() |
4fcf3aa2bd | ||
![]() |
dc39da8ca5 | ||
![]() |
c10c87d28e | ||
![]() |
c6fe6f1cc5 | ||
![]() |
1c2bbeb26d | ||
![]() |
17ed3692d0 | ||
![]() |
966a00f41e | ||
![]() |
fd8d8f89aa | ||
![]() |
305bb74072 | ||
![]() |
7f4dcdd134 | ||
![]() |
aac37dcce1 | ||
![]() |
f539c662a5 | ||
![]() |
c82f346dd0 | ||
![]() |
21b4a87837 | ||
![]() |
ae73bcf24b | ||
![]() |
2a3b56bde1 | ||
![]() |
b8ebededd8 | ||
![]() |
227c4c422c | ||
![]() |
652bfb93cc | ||
![]() |
c2278e3536 | ||
![]() |
caa2fca4e8 | ||
![]() |
745cb0175c | ||
![]() |
e5165a780f | ||
![]() |
b4b91af02b | ||
![]() |
5649ff9c2e | ||
![]() |
5b4bf6c62a | ||
![]() |
93cb662282 | ||
![]() |
00a8715e58 | ||
![]() |
7ecd479b3e | ||
![]() |
8fe7d3aaec | ||
![]() |
f32a693393 | ||
![]() |
17ebc01597 | ||
![]() |
827fb698e1 | ||
![]() |
32bdf10fd2 | ||
![]() |
b795e6c3d2 | ||
![]() |
42ba524e4e | ||
![]() |
317c6d96e3 | ||
![]() |
3692d1499f | ||
![]() |
b21fbad8a3 | ||
![]() |
743334a68a | ||
![]() |
951413eb38 | ||
![]() |
32dcdef853 | ||
![]() |
34c9254d4a | ||
![]() |
14012a4668 | ||
![]() |
575debca63 | ||
![]() |
763cac8532 | ||
![]() |
43faacd7a7 | ||
![]() |
1d4e307e96 | ||
![]() |
7f8933b0de | ||
![]() |
81608ff025 | ||
![]() |
db63675b8e | ||
![]() |
f74a83bc46 | ||
![]() |
bc1deba3e4 | ||
![]() |
d6113a8f0a | ||
![]() |
2062cd48ea | ||
![]() |
1c965ef515 | ||
![]() |
58291b7156 | ||
![]() |
afd1648d80 | ||
![]() |
21814ffa9a | ||
![]() |
9d3522da54 | ||
![]() |
e07a76755e | ||
![]() |
ba46bcdeae | ||
![]() |
8d7e44314c | ||
![]() |
35a67498c7 | ||
![]() |
90dd934f95 | ||
![]() |
4087045542 | ||
![]() |
d11cef5907 | ||
![]() |
76c91d226c | ||
![]() |
c2b4dd2afd | ||
![]() |
25b39cb39a | ||
![]() |
35dcb7b88b | ||
![]() |
e5f7e7c26e | ||
![]() |
c5c11fd6a6 | ||
![]() |
8134083419 | ||
![]() |
a87e624198 | ||
![]() |
e4c62d20b4 | ||
![]() |
fa195d9e55 | ||
![]() |
5ef5773d23 | ||
![]() |
6eea52afdf | ||
![]() |
80e64af30f | ||
![]() |
563b6ddc36 | ||
![]() |
c051ab9dc4 | ||
![]() |
87737a8bdb | ||
![]() |
94273d80b0 | ||
![]() |
a08ec2a4bd | ||
![]() |
d246c556f4 | ||
![]() |
65aa365e38 | ||
![]() |
eeeae449b4 | ||
![]() |
17c10a7ba2 | ||
![]() |
69f4383678 | ||
![]() |
07852a7295 | ||
![]() |
20b7e9b6b5 | ||
![]() |
75f43ccea4 | ||
![]() |
59e5785e93 | ||
![]() |
b38f52dba9 | ||
![]() |
2a6b17a48e | ||
![]() |
a6c056a894 | ||
![]() |
5c3442a71f | ||
![]() |
390253242f | ||
![]() |
9ab80fe1ac | ||
![]() |
91fdd09e7a | ||
![]() |
db5bd5c8a4 | ||
![]() |
ef94c2fe7c | ||
![]() |
72a25ed8e1 | ||
![]() |
eb065e218f | ||
![]() |
33426736fc | ||
![]() |
896658d5ce | ||
![]() |
b14135ed72 | ||
![]() |
a1baf2e32d | ||
![]() |
f9aa2d3bce | ||
![]() |
c95d0e0696 | ||
![]() |
ad4b84d446 | ||
![]() |
3e27d5fcb0 | ||
![]() |
48a100f49a | ||
![]() |
698649f981 | ||
![]() |
780078c3aa | ||
![]() |
4c25e4ddee | ||
![]() |
c0a5ac2ac5 | ||
![]() |
0435409870 | ||
![]() |
c521269409 | ||
![]() |
1e252b7e4c | ||
![]() |
d72b1edc48 | ||
![]() |
f7307e8e01 | ||
![]() |
127905f04b | ||
![]() |
261c6dabd5 | ||
![]() |
cae84bbf02 | ||
![]() |
cdb2bc52fa | ||
![]() |
cd2972eee0 | ||
![]() |
4036aa8d0e |
@@ -15,7 +15,10 @@ charset = utf-8
|
|||||||
# 2 space indentation
|
# 2 space indentation
|
||||||
[*.{cjs,mjs,js,jsx,ts,tsx,css,scss,sass,html,json}]
|
[*.{cjs,mjs,js,jsx,ts,tsx,css,scss,sass,html,json}]
|
||||||
indent_style = space
|
indent_style = space
|
||||||
indent_size = 2
|
indent_size = 4
|
||||||
|
|
||||||
|
[*.bat]
|
||||||
|
charset = latin1
|
||||||
|
|
||||||
# Unfortunately, EditorConfig doesn't support space configuration inside import braces directly.
|
# 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.
|
# 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 +0,0 @@
|
|||||||
VITE_BUILD_TYPE = Production
|
|
2
.env.shell
Normal file
2
.env.shell
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
VITE_BUILD_TYPE = Production
|
||||||
|
VITE_BUILD_PLATFORM = Shell
|
@@ -1,9 +1,10 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
'env': {
|
'env': {
|
||||||
|
'browser': true,
|
||||||
'es2021': true,
|
'es2021': true,
|
||||||
'node': true
|
'node': true
|
||||||
},
|
},
|
||||||
'ignorePatterns': ['src/core/', 'src/core.lib/'],
|
'ignorePatterns': ['src/core/proto/'],
|
||||||
'extends': [
|
'extends': [
|
||||||
'eslint:recommended',
|
'eslint:recommended',
|
||||||
'plugin:@typescript-eslint/recommended'
|
'plugin:@typescript-eslint/recommended'
|
||||||
@@ -43,16 +44,12 @@ module.exports = {
|
|||||||
'rules': {
|
'rules': {
|
||||||
'indent': [
|
'indent': [
|
||||||
'error',
|
'error',
|
||||||
2
|
4
|
||||||
],
|
],
|
||||||
'linebreak-style': [
|
'linebreak-style': [
|
||||||
'error',
|
'error',
|
||||||
'unix'
|
'unix'
|
||||||
],
|
],
|
||||||
'quotes': [
|
|
||||||
'error',
|
|
||||||
'single'
|
|
||||||
],
|
|
||||||
'semi': [
|
'semi': [
|
||||||
'error',
|
'error',
|
||||||
'always'
|
'always'
|
||||||
|
1
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
1
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -10,6 +10,7 @@ body:
|
|||||||
在提交新的 Bug 反馈前,请确保您:
|
在提交新的 Bug 反馈前,请确保您:
|
||||||
* 已经搜索了现有的 issues,并且没有找到可以解决您问题的方法
|
* 已经搜索了现有的 issues,并且没有找到可以解决您问题的方法
|
||||||
* 不与现有的某一 issue 重复
|
* 不与现有的某一 issue 重复
|
||||||
|
* 不涉及[已经停止维护的特性](https://github.com/NapNeko/NapCatQQ?tab=readme-ov-file#挥别昨日),例如 CQ 码
|
||||||
- type: input
|
- type: input
|
||||||
id: system-version
|
id: system-version
|
||||||
attributes:
|
attributes:
|
||||||
|
43
.github/workflows/build.yml
vendored
43
.github/workflows/build.yml
vendored
@@ -1,21 +1,12 @@
|
|||||||
name: "Build"
|
name: "Build Action"
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
|
|
||||||
permissions: write-all
|
permissions: write-all
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-linux:
|
Build-LiteLoader:
|
||||||
if: ${{ startsWith(github.event.head_commit.message, 'build:') }}
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
target_platform: [linux]
|
|
||||||
target_arch: [x64, arm64]
|
|
||||||
steps:
|
steps:
|
||||||
- name: Clone Main Repository
|
- name: Clone Main Repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
@@ -28,26 +19,21 @@ jobs:
|
|||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 20.x
|
node-version: 20.x
|
||||||
- name: Build NuCat Linux
|
- name: Build NuCat Framework
|
||||||
run: |
|
run: |
|
||||||
npm i --arch=${{ matrix.target_arch }} --platform=${{ matrix.target_platform }}
|
npm i
|
||||||
npm run build:prod
|
npm run build:framework
|
||||||
cd dist
|
cd dist
|
||||||
npm i --omit=dev --arch=${{ matrix.target_arch }} --platform=${{ matrix.target_platform }}
|
npm i --omit=dev
|
||||||
|
rm package-lock.json
|
||||||
cd ..
|
cd ..
|
||||||
- name: Upload Artifact
|
- name: Upload Artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: NapCat.${{ matrix.target_platform }}.${{ matrix.target_arch }}
|
name: NapCat.Framework
|
||||||
path: dist
|
path: dist
|
||||||
build-win32:
|
Build-Shell:
|
||||||
if: ${{ startsWith(github.event.head_commit.message, 'build:') }}
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
target_platform: [win32]
|
|
||||||
target_arch: [x64]
|
|
||||||
steps:
|
steps:
|
||||||
- name: Clone Main Repository
|
- name: Clone Main Repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
@@ -60,15 +46,16 @@ jobs:
|
|||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 20.x
|
node-version: 20.x
|
||||||
- name: Build NuCat Linux
|
- name: Build NuCat LiteLoader
|
||||||
run: |
|
run: |
|
||||||
npm i --arch=${{ matrix.target_arch }} --platform=${{ matrix.target_platform }}
|
npm i
|
||||||
npm run build:prod
|
npm run build:shell
|
||||||
cd dist
|
cd dist
|
||||||
npm i --omit=dev --arch=${{ matrix.target_arch }} --platform=${{ matrix.target_platform }}
|
npm i --omit=dev
|
||||||
|
rm package-lock.json
|
||||||
cd ..
|
cd ..
|
||||||
- name: Upload Artifact
|
- name: Upload Artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: NapCat.${{ matrix.target_platform }}.${{ matrix.target_arch }}
|
name: NapCat.Shell
|
||||||
path: dist
|
path: dist
|
||||||
|
84
.github/workflows/release.yml
vendored
84
.github/workflows/release.yml
vendored
@@ -1,4 +1,4 @@
|
|||||||
name: "release"
|
name: "Build Release"
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
@@ -30,14 +30,9 @@ jobs:
|
|||||||
ls
|
ls
|
||||||
node ./script/checkVersion.cjs
|
node ./script/checkVersion.cjs
|
||||||
sh ./checkVersion.sh
|
sh ./checkVersion.sh
|
||||||
build-linux:
|
Build-LiteLoader:
|
||||||
needs: [check-version]
|
needs: [check-version]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
target_platform: [linux]
|
|
||||||
target_arch: [x64, arm64]
|
|
||||||
steps:
|
steps:
|
||||||
- name: Clone Main Repository
|
- name: Clone Main Repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
@@ -51,28 +46,21 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
node-version: 20.x
|
node-version: 20.x
|
||||||
|
|
||||||
- name: Build NuCat Linux
|
- name: Build NuCat Framework
|
||||||
run: |
|
run: |
|
||||||
export NAPCAT_BUILDSYS=${{ matrix.target_platform }}
|
npm i
|
||||||
export NAPCAT_BUILDARCH=${{ matrix.target_arch }}
|
npm run build:framework
|
||||||
npm i --arch=${{ matrix.target_arch }} --platform=${{ matrix.target_platform }}
|
|
||||||
npm run build:prod
|
|
||||||
cd dist
|
cd dist
|
||||||
npm i --omit=dev --arch=${{ matrix.target_arch }} --platform=${{ matrix.target_platform }}
|
npm i --omit=dev
|
||||||
cd ..
|
cd ..
|
||||||
- name: Upload Artifact
|
- name: Upload Artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: NapCat.${{ matrix.target_platform }}.${{ matrix.target_arch }}
|
name: NapCat.Framework
|
||||||
path: dist
|
path: dist
|
||||||
build-win32:
|
Build-Shell:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [check-version]
|
needs: [check-version]
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
target_platform: [win32]
|
|
||||||
target_arch: [x64]
|
|
||||||
steps:
|
steps:
|
||||||
- name: Clone Main Repository
|
- name: Clone Main Repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
@@ -87,41 +75,63 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
node-version: 20.x
|
node-version: 20.x
|
||||||
|
|
||||||
- name: Build NuCat Linux
|
- name: Build NuCat Shell
|
||||||
run: |
|
run: |
|
||||||
export NAPCAT_BUILDSYS=${{ matrix.target_platform }}
|
npm i
|
||||||
export NAPCAT_BUILDARCH=${{ matrix.target_arch }}
|
npm run build:shell
|
||||||
npm i --arch=${{ matrix.target_arch }} --platform=${{ matrix.target_platform }}
|
|
||||||
npm run build:prod
|
|
||||||
cd dist
|
cd dist
|
||||||
npm i --omit=dev --arch=${{ matrix.target_arch }} --platform=${{ matrix.target_platform }}
|
npm i --omit=dev
|
||||||
cd ..
|
cd ..
|
||||||
|
|
||||||
- name: Upload Artifact
|
- name: Upload Artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: NapCat.${{ matrix.target_platform }}.${{ matrix.target_arch }}
|
name: NapCat.Shell
|
||||||
path: dist
|
path: dist
|
||||||
|
|
||||||
release-napcat:
|
release-napcat:
|
||||||
needs: [build-win32,build-linux]
|
needs: [Build-LiteLoader,Build-Shell]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
|
|
||||||
|
- name: Clone Main Repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
repository: 'NapNeko/NapCatQQ'
|
||||||
|
submodules: true
|
||||||
|
ref: main
|
||||||
|
token: ${{ secrets.NAPCAT_BUILD }}
|
||||||
|
|
||||||
- name: Download All Artifact
|
- name: Download All Artifact
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v4
|
||||||
|
|
||||||
- name: Compress subdirectories
|
- name: Compress subdirectories
|
||||||
run: |
|
run: |
|
||||||
for dir in */; do
|
cd ./NapCat.Shell/
|
||||||
base=$(basename "$dir")
|
zip -q -r NapCat.Shell.zip *
|
||||||
zip -r "${base}.zip" "$dir"
|
cd ..
|
||||||
done
|
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
|
- name: Extract version from tag
|
||||||
run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_ENV
|
run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Clone Changes Log
|
- name: Clone Changes Log
|
||||||
run: curl -o CHANGELOG.md https://fastly.jsdelivr.net/gh/NapNeko/NapCatQQ@main/CHANGELOG.md
|
run: curl -o CHANGELOG.md https://fastly.jsdelivr.net/gh/NapNeko/NapCatQQ@main/docs/changelogs/CHANGELOG.v${{ env.VERSION }}.md
|
||||||
|
|
||||||
- name: Create Release Draft and Upload Artifacts
|
- name: Create Release Draft and Upload Artifacts
|
||||||
uses: softprops/action-gh-release@v1
|
uses: softprops/action-gh-release@v1
|
||||||
@@ -130,9 +140,7 @@ jobs:
|
|||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
body_path: CHANGELOG.md
|
body_path: CHANGELOG.md
|
||||||
files: |
|
files: |
|
||||||
NapCat.win32.x64.zip
|
NapCat.Framework.zip
|
||||||
NapCat.linux.x64.zip
|
NapCat.Shell.zip
|
||||||
NapCat.linux.arm64.zip
|
NapCat.Framework.Windows.Once.zip
|
||||||
# NapCat.darwin.x64.zip
|
|
||||||
# NapCat.darwin.arm64.zip
|
|
||||||
draft: true
|
draft: true
|
||||||
|
6
.gitignore
vendored
6
.gitignore
vendored
@@ -1,10 +1,11 @@
|
|||||||
# Develop
|
# Develop
|
||||||
node_modules/
|
node_modules/
|
||||||
package-lock.json
|
package-lock.json
|
||||||
|
pnpm-lock.yaml
|
||||||
out/
|
out/
|
||||||
dist/
|
dist/
|
||||||
src/core.lib/common/
|
/src/core.lib/common/
|
||||||
test
|
/localdebug/
|
||||||
|
|
||||||
# Editor
|
# Editor
|
||||||
.vscode/*
|
.vscode/*
|
||||||
@@ -14,3 +15,4 @@ test
|
|||||||
# Build
|
# Build
|
||||||
*.db
|
*.db
|
||||||
checkVersion.sh
|
checkVersion.sh
|
||||||
|
bun.lockb
|
||||||
|
4
.gitmodules
vendored
4
.gitmodules
vendored
@@ -1,4 +0,0 @@
|
|||||||
[submodule "src/core"]
|
|
||||||
path = src/core
|
|
||||||
url = https://github.com/NapNeko/core.git
|
|
||||||
branch = master
|
|
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"
|
||||||
|
}
|
15
CHANGELOG.md
15
CHANGELOG.md
@@ -1,15 +0,0 @@
|
|||||||
# v1.3.0
|
|
||||||
|
|
||||||
QQ Version: Windows 9.9.9-23424 / Linux 3.2.7-23361
|
|
||||||
|
|
||||||
## 修复与优化
|
|
||||||
* 修复了一个导致每个图片都自动下载的 bug
|
|
||||||
* 再一次修复图片URL,支持 Win/Linux X64 获取Rkey,暂时不支持arm64
|
|
||||||
* 修复了设置消息群聊与私聊已读接口
|
|
||||||
* 修复无法获取进群申请人员信息
|
|
||||||
## 新增与调整
|
|
||||||
* 再一次对获取Cookies与获取群成员优化,分别添加30/60/120分钟缓存
|
|
||||||
* 新增 WebUi 支持远程配置设置 详细参考官方教程
|
|
||||||
* 新增二维码过期自动刷新功能
|
|
||||||
|
|
||||||
新增的 API 详细见[API文档](https://napneko.github.io/zh-CN/develop/extends_api)
|
|
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
|
Preamble
|
||||||
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:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
The licenses for most software are designed to take away your
|
||||||
copies or substantial portions of the Software.
|
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
|
When we speak of free software, we are referring to freedom, not
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
price. Our General Public Licenses are designed to make sure that you
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
have the freedom to distribute copies of free software (and charge for
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
this service if you wish), that you receive source code or can get it
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
if you want it, that you can change the software or use pieces of it
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
in new free programs; and that you know you can do these things.
|
||||||
SOFTWARE.
|
|
||||||
|
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.
|
||||||
|
51
README.md
51
README.md
@@ -1,33 +1,52 @@
|
|||||||
<div align="center">
|
<div align="center">
|
||||||
<img src="https://socialify.git.ci/NapNeko/NapCatQQ/image?description=1&language=1&logo=https%3A%2F%2Fraw.githubusercontent.com%2FNapNeko%2FNapCatQQ%2Fmain%2Flogo.png&name=1&stargazers=1&theme=Auto" alt="NapCatQQ" width="640" height="320" />
|
<img src="https://socialify.git.ci/NapNeko/NapCatQQ/image?font=Jost&logo=https%3A%2F%2Fnapneko.github.io%2Fassets%2Flogo.png&name=1&owner=1&pattern=Diagonal%20Stripes&stargazers=1&theme=Auto" alt="NapCatQQ" width="640" height="320" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
## 项目介绍
|
---
|
||||||
|
## 欢迎回来
|
||||||
|
NapCatQQ (aka 猫猫框架) 是现代化的基于 NTQQ 的 Bot 协议端实现
|
||||||
|
|
||||||
NapCatQQ 是基于 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/) 页面下载最新版本
|
可前往 [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/)
|
||||||
|
|
||||||
* NapCat 不会收集用户隐私信息,但是未来可能会为了更好的利于 NapCat 的优化会收集一些设备信息,如 cpu 架构,系统版本等
|
[Cloudflare.Pages](https://napneko.pages.dev/)
|
||||||
|
|
||||||
|
## 回家旅途
|
||||||
|
[QQ Group](https://qm.qq.com/q/VfjAq5HIMS)
|
||||||
|
|
||||||
## 相关链接
|
|
||||||
[Telegram Link](https://t.me/+nLZEnpne-pQ1OWFl)
|
[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 代码开发。**
|
||||||
|
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.16-28788",
|
||||||
|
"verHash": "73b0c8f6",
|
||||||
|
"linuxVersion": "3.2.13-28788",
|
||||||
|
"linuxVerHash": "55fb6434",
|
||||||
|
"type": "module",
|
||||||
|
"private": true,
|
||||||
|
"description": "QQ",
|
||||||
|
"productName": "QQ",
|
||||||
|
"author": {
|
||||||
|
"name": "Tencent",
|
||||||
|
"email": "QQ-Team@tencent.com"
|
||||||
|
},
|
||||||
|
"homepage": "https://im.qq.com",
|
||||||
|
"sideEffects": true,
|
||||||
|
"bin": {
|
||||||
|
"qd": "externals/devtools/cli/index.js"
|
||||||
|
},
|
||||||
|
"main": "./loadNapCat.js",
|
||||||
|
"buildVersion": "28788",
|
||||||
|
"isPureShell": true,
|
||||||
|
"isByteCodeShell": true,
|
||||||
|
"platform": "win32",
|
||||||
|
"eleArch": "x64"
|
||||||
|
}
|
4
launcher/quickLoginExample.bat
Normal file
4
launcher/quickLoginExample.bat
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
@echo off
|
||||||
|
REM ./launcher.bat 123456
|
||||||
|
REM ./launcher-win10.bat 123456
|
||||||
|
REM 带有REM的为注释 删掉你需要的系统的那行REM这三个单词 修改QQ本脚本启动即可
|
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.5",
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
}
|
63
package.json
63
package.json
@@ -2,64 +2,51 @@
|
|||||||
"name": "napcat",
|
"name": "napcat",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "1.3.0",
|
"version": "3.0.5",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"watch:dev": "vite --mode development",
|
"build:framework": "vite build --mode framework",
|
||||||
"watch:prod": "vite --mode production",
|
"build:shell": "vite build --mode shell",
|
||||||
"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",
|
"build:webui": "cd ./src/webui && vite build",
|
||||||
"watch": "npm run watch:dev",
|
|
||||||
"debug-win": "powershell dist/napcat.ps1",
|
|
||||||
"lint": "eslint --fix src/**/*.{js,ts}",
|
"lint": "eslint --fix src/**/*.{js,ts}",
|
||||||
"release": "npm run build:prod",
|
|
||||||
"depend": "cd dist && npm install --omit=dev"
|
"depend": "cd dist && npm install --omit=dev"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@babel/preset-typescript": "^7.24.7",
|
||||||
"@log4js-node/log4js-api": "^1.0.2",
|
"@log4js-node/log4js-api": "^1.0.2",
|
||||||
"@rollup/plugin-commonjs": "^25.0.7",
|
|
||||||
"@rollup/plugin-node-resolve": "^15.2.3",
|
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||||
"@rollup/plugin-typescript": "^11.1.6",
|
"@rollup/plugin-typescript": "^11.1.6",
|
||||||
"@types/cors": "^2.8.17",
|
"@types/cors": "^2.8.17",
|
||||||
"@types/express": "^4.17.21",
|
"@types/express": "^5.0.0",
|
||||||
"@types/figlet": "^1.5.8",
|
|
||||||
"@types/fluent-ffmpeg": "^2.1.24",
|
"@types/fluent-ffmpeg": "^2.1.24",
|
||||||
"@types/node": "^20.11.30",
|
"@types/node": "^22.0.1",
|
||||||
"@types/qrcode-terminal": "^0.12.2",
|
"@types/qrcode-terminal": "^0.12.2",
|
||||||
"@types/uuid": "^9.0.8",
|
"@types/ws": "^8.5.12",
|
||||||
"@types/ws": "^8.5.10",
|
"@typescript-eslint/eslint-plugin": "^8.3.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^7.4.0",
|
"@typescript-eslint/parser": "^8.3.0",
|
||||||
"@typescript-eslint/parser": "^7.4.0",
|
|
||||||
"eslint": "^8.57.0",
|
"eslint": "^8.57.0",
|
||||||
"eslint-import-resolver-typescript": "^3.6.1",
|
"eslint-import-resolver-typescript": "^3.6.1",
|
||||||
"eslint-plugin-import": "^2.29.1",
|
"eslint-plugin-import": "^2.29.1",
|
||||||
"i": "^0.3.7",
|
|
||||||
"javascript-obfuscator": "^4.1.0",
|
|
||||||
"protobufjs-cli": "^1.1.2",
|
|
||||||
"rollup": "^4.13.2",
|
|
||||||
"rollup-plugin-dts": "^6.1.0",
|
|
||||||
"rollup-plugin-obfuscator": "^1.1.0",
|
|
||||||
"typescript": "^5.3.3",
|
"typescript": "^5.3.3",
|
||||||
"vite": "^5.2.6",
|
"vite": "^5.2.6",
|
||||||
"vite-plugin-cp": "^4.0.8",
|
"vite-plugin-cp": "^4.0.8",
|
||||||
"vite-plugin-dts": "^3.8.2",
|
"vite-tsconfig-paths": "^4.3.2",
|
||||||
"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": {
|
"dependencies": {
|
||||||
"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",
|
|
||||||
"log4js": "^6.9.1",
|
|
||||||
"qrcode-terminal": "^0.12.0",
|
"qrcode-terminal": "^0.12.0",
|
||||||
"silk-wasm": "^3.3.4",
|
"fluent-ffmpeg": "^2.1.2",
|
||||||
"sqlite3": "^5.1.7",
|
"express": "^5.0.0-beta.2",
|
||||||
"uuid": "^9.0.1",
|
"log4js": "^6.9.1",
|
||||||
"ws": "^8.16.0"
|
"silk-wasm": "^3.6.1",
|
||||||
|
"ws": "^8.18.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
2
script/KillQQ.bat
Normal file
2
script/KillQQ.bat
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
@echo off
|
||||||
|
taskkill /f /im QQ.exe
|
@@ -1,13 +1,42 @@
|
|||||||
let fs = require("fs");
|
const fs = require("fs");
|
||||||
let process = require("process")
|
const process = require("process");
|
||||||
|
|
||||||
console.log("[NapCat] [CheckVersion] 开始检测当前仓库版本...");
|
console.log("[NapCat] [CheckVersion] 开始检测当前仓库版本...");
|
||||||
let currentVersion = require("../package.json").version;
|
try {
|
||||||
let targetVersion = process.env.VERSION;
|
const packageJson = require("../package.json");
|
||||||
|
const currentVersion = packageJson.version;
|
||||||
|
const targetVersion = process.env.VERSION;
|
||||||
|
|
||||||
console.log("[NapCat] [CheckVersion] currentVersion:", currentVersion, "targetVersion:", targetVersion);
|
console.log("[NapCat] [CheckVersion] currentVersion:", currentVersion, "targetVersion:", targetVersion);
|
||||||
// fs.mkdirSync("./dist");
|
|
||||||
if (currentVersion === targetVersion) {
|
// 验证 targetVersion 格式
|
||||||
fs.writeFileSync("./checkVersion.sh", "#!/bin/bashe\necho \"CheckVersion Is Done\"")
|
if (!targetVersion || typeof targetVersion !== 'string') {
|
||||||
} else {
|
console.log("[NapCat] [CheckVersion] 目标版本格式不正确或未设置!");
|
||||||
let runscript = "sed -i 's/\"version\": \"" + currentVersion + "\"/\"version\": \"" + targetVersion + "\"/g' package.json";
|
return;
|
||||||
fs.writeFileSync("./checkVersion.sh", "#!/bin/bashe\ngit config --global user.email \"bot@test.wumiao.wang\"\n git config --global user.name \"Version\"\n" + runscript + "\ngit add .\n git commit -m \"chore:version change\"\n git push -u origin main")
|
}
|
||||||
|
|
||||||
|
// 写入脚本文件的统一函数
|
||||||
|
const writeScriptToFile = (content) => {
|
||||||
|
fs.writeFileSync("./checkVersion.sh", content, { flag: 'w' });
|
||||||
|
console.log("[NapCat] [CheckVersion] checkVersion.sh 文件已更新。");
|
||||||
|
};
|
||||||
|
|
||||||
|
if (currentVersion === targetVersion) {
|
||||||
|
// 不需要更新版本,写入一个简单的脚本
|
||||||
|
const simpleScript = "#!/bin/bash\necho \"CheckVersion Is Done\"";
|
||||||
|
writeScriptToFile(simpleScript);
|
||||||
|
} else {
|
||||||
|
// 更新版本,构建安全的sed命令
|
||||||
|
const safeScriptContent = `
|
||||||
|
#!/bin/bash
|
||||||
|
git config --global user.email "bot@test.wumiao.wang"
|
||||||
|
git config --global user.name "Version"
|
||||||
|
sed -i "s/\\\"version\\\": \\\"${currentVersion}\\\"/\\\"version\\\": \\\"${targetVersion}\\\"/g" package.json
|
||||||
|
git add .
|
||||||
|
git commit -m "chore:version change"
|
||||||
|
git push -u origin main`;
|
||||||
|
writeScriptToFile(safeScriptContent);
|
||||||
|
}
|
||||||
|
} catch (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);
|
|
@@ -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,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.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 {
|
|
||||||
return "D:\QQ.exe"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$params = $args -join " "
|
|
||||||
$QQpath = Get-QQpath
|
|
||||||
$Bootfile = Join-Path $PSScriptRoot "napcat.cjs"
|
|
||||||
$env:ELECTRON_RUN_AS_NODE = 1
|
|
||||||
Start-Process powershell -ArgumentList "-noexit", "-noprofile", "-command &{& chcp 65001;& '$QQpath' $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.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 {
|
|
||||||
return "D:\QQ.exe"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$params = $args -join " "
|
|
||||||
$QQpath = Get-QQpath
|
|
||||||
$Bootfile = Join-Path $PSScriptRoot "napcat.cjs"
|
|
||||||
$env:ELECTRON_RUN_AS_NODE = 1
|
|
||||||
Start-Process powershell -ArgumentList "-noexit", "-noprofile", "-command &{& '$QQpath' $Bootfile $params}"
|
|
@@ -1,4 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
SCRIPT_DIR=$(realpath $(dirname "${BASH_SOURCE[0]}"))
|
|
||||||
export ELECTRON_RUN_AS_NODE=1
|
|
||||||
/opt/QQ/qq ${SCRIPT_DIR}/napcat.cjs $@
|
|
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,122 +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;
|
|
||||||
|
|
||||||
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());
|
|
||||||
// llonebotError.httpServerError = "HTTP服务启动失败, " + e.toString()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
stop() {
|
|
||||||
// llonebotError.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);
|
|
||||||
});
|
|
||||||
}catch (e: any) {
|
|
||||||
logError('HTTP服务启动失败, 请检查监听的ip地址和端口', e.stack.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,104 +0,0 @@
|
|||||||
import { WebSocket, WebSocketServer } from 'ws';
|
|
||||||
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, host: string = '') {
|
|
||||||
try {
|
|
||||||
this.ws = new WebSocketServer({
|
|
||||||
port ,
|
|
||||||
host: '',
|
|
||||||
maxPayload: 1024 * 1024 * 1024
|
|
||||||
});
|
|
||||||
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() {
|
|
||||||
this.ws && this.ws.close((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() {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,10 +1,21 @@
|
|||||||
import os from 'node:os';
|
import os from 'node:os';
|
||||||
import path from 'node:path';
|
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 systemPlatform = os.platform();
|
||||||
export const cpuArch = os.arch();
|
export const cpuArch = os.arch();
|
||||||
export const systemVersion = os.release();
|
export const systemVersion = os.release();
|
||||||
export const hostname = os.hostname();
|
export const hostname = osName;
|
||||||
const homeDir = os.homedir();
|
|
||||||
export const downloadsPath = path.join(homeDir, 'Downloads');
|
export const downloadsPath = path.join(homeDir, 'Downloads');
|
||||||
export const systemName = os.type();
|
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,35 +0,0 @@
|
|||||||
import { sleep } from '@/common/utils/helper';
|
|
||||||
|
|
||||||
type AsyncQueueTask = (() => void) | (()=>Promise<void>);
|
|
||||||
|
|
||||||
|
|
||||||
export class AsyncQueue {
|
|
||||||
private tasks: (AsyncQueueTask)[] = [];
|
|
||||||
|
|
||||||
public addTask(task: AsyncQueueTask) {
|
|
||||||
this.tasks.push(task);
|
|
||||||
// console.log('addTask', this.tasks.length);
|
|
||||||
if (this.tasks.length === 1) {
|
|
||||||
this.runQueue().then().catch(()=>{});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async runQueue() {
|
|
||||||
// console.log('runQueue', this.tasks.length);
|
|
||||||
while (this.tasks.length > 0) {
|
|
||||||
const task = this.tasks[0];
|
|
||||||
// console.log('typeof task', typeof task);
|
|
||||||
try {
|
|
||||||
const taskRet = task();
|
|
||||||
// console.log('type of taskRet', typeof taskRet, taskRet);
|
|
||||||
if (taskRet instanceof Promise) {
|
|
||||||
await taskRet;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
}
|
|
||||||
this.tasks.shift();
|
|
||||||
await sleep(100);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,67 +0,0 @@
|
|||||||
import path from 'node:path';
|
|
||||||
import fs from 'node:fs';
|
|
||||||
import { log, logDebug, logError } from '@/common/utils/log';
|
|
||||||
|
|
||||||
const configDir = path.resolve(__dirname, 'config');
|
|
||||||
fs.mkdirSync(configDir, { recursive: true });
|
|
||||||
|
|
||||||
|
|
||||||
export class ConfigBase<T>{
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
}
|
|
||||||
|
|
||||||
protected getKeys(): string[] | null {
|
|
||||||
// 决定 key 在json配置文件中的顺序
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
getConfigDir(){
|
|
||||||
const configDir = path.resolve(__dirname, 'config');
|
|
||||||
fs.mkdirSync(configDir, { recursive: true });
|
|
||||||
return configDir;
|
|
||||||
}
|
|
||||||
getConfigPath(): string {
|
|
||||||
throw new Error('Method not implemented.');
|
|
||||||
}
|
|
||||||
|
|
||||||
read() {
|
|
||||||
const configPath = this.getConfigPath();
|
|
||||||
if (!fs.existsSync(configPath)) {
|
|
||||||
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) {
|
|
||||||
Object.assign(this, config);
|
|
||||||
const configPath = this.getConfigPath();
|
|
||||||
try {
|
|
||||||
fs.writeFileSync(configPath, JSON.stringify(this, this.getKeys(), 2));
|
|
||||||
} catch (e: any) {
|
|
||||||
logError(`保存配置文件 ${configPath} 时发生错误:`, e.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,72 +0,0 @@
|
|||||||
import path from 'node:path';
|
|
||||||
import fs from 'node:fs';
|
|
||||||
import os from 'node:os';
|
|
||||||
import { systemPlatform } from '@/common/utils/system';
|
|
||||||
|
|
||||||
export const exePath = process.execPath;
|
|
||||||
|
|
||||||
export const pkgInfoPath = path.join(path.dirname(exePath), 'resources', 'app', 'package.json');
|
|
||||||
let configVersionInfoPath;
|
|
||||||
|
|
||||||
if (os.platform() !== 'linux') {
|
|
||||||
configVersionInfoPath = path.join(path.dirname(exePath), 'resources', 'app', 'versions', 'config.json');
|
|
||||||
} else {
|
|
||||||
const userPath = os.homedir();
|
|
||||||
const appDataPath = path.resolve(userPath, './.config/QQ');
|
|
||||||
configVersionInfoPath = path.resolve(appDataPath, './versions/config.json');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof configVersionInfoPath !== 'string') {
|
|
||||||
throw new Error('Something went wrong when load QQ info path');
|
|
||||||
}
|
|
||||||
|
|
||||||
export { configVersionInfoPath };
|
|
||||||
|
|
||||||
type QQPkgInfo = {
|
|
||||||
version: string;
|
|
||||||
buildVersion: string;
|
|
||||||
platform: string;
|
|
||||||
eleArch: string;
|
|
||||||
}
|
|
||||||
type QQVersionConfigInfo = {
|
|
||||||
baseVersion: string;
|
|
||||||
curVersion: string;
|
|
||||||
prevVersion: string;
|
|
||||||
onErrorVersions: Array<any>;
|
|
||||||
buildId: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
let _qqVersionConfigInfo: QQVersionConfigInfo = {
|
|
||||||
'baseVersion': '9.9.9-23361',
|
|
||||||
'curVersion': '9.9.9-23361',
|
|
||||||
'prevVersion': '',
|
|
||||||
'onErrorVersions': [],
|
|
||||||
'buildId': '23361'
|
|
||||||
};
|
|
||||||
|
|
||||||
if (fs.existsSync(configVersionInfoPath)) {
|
|
||||||
try {
|
|
||||||
const _ =JSON.parse(fs.readFileSync(configVersionInfoPath).toString());
|
|
||||||
_qqVersionConfigInfo = Object.assign(_qqVersionConfigInfo, _);
|
|
||||||
} catch (e) {
|
|
||||||
console.error('Load QQ version config info failed, Use default version', e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const qqVersionConfigInfo: QQVersionConfigInfo = _qqVersionConfigInfo;
|
|
||||||
|
|
||||||
export const qqPkgInfo: QQPkgInfo = require(pkgInfoPath);
|
|
||||||
// platform_type: 3,
|
|
||||||
// app_type: 4,
|
|
||||||
// app_version: '9.9.9-23159',
|
|
||||||
// qua: 'V1_WIN_NQ_9.9.9_23159_GW_B',
|
|
||||||
// appid: '537213764',
|
|
||||||
// platVer: '10.0.26100',
|
|
||||||
// clientVer: '9.9.9-23159',
|
|
||||||
|
|
||||||
let _appid: string = '537213803'; // 默认为 Windows 平台的 appid
|
|
||||||
if (systemPlatform === 'linux') {
|
|
||||||
_appid = '537213827';
|
|
||||||
}
|
|
||||||
// todo: mac 平台的 appid
|
|
||||||
export const appid = _appid;
|
|
@@ -1,135 +0,0 @@
|
|||||||
import fs from 'fs';
|
|
||||||
import { encode, getDuration, getWavFileInfo, isWav } from 'silk-wasm';
|
|
||||||
import fsPromise from 'fs/promises';
|
|
||||||
import { log, logError } from './log';
|
|
||||||
import path from 'node:path';
|
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
|
||||||
import { spawn } from 'node:child_process';
|
|
||||||
import { getTempDir } from '@/common/utils/file';
|
|
||||||
|
|
||||||
let TEMP_DIR = './';
|
|
||||||
setTimeout(() => {
|
|
||||||
TEMP_DIR = getTempDir();
|
|
||||||
}, 100);
|
|
||||||
export async function encodeSilk(filePath: string) {
|
|
||||||
function getFileHeader(filePath: string) {
|
|
||||||
// 定义要读取的字节数
|
|
||||||
const bytesToRead = 7;
|
|
||||||
try {
|
|
||||||
const buffer = fs.readFileSync(filePath, {
|
|
||||||
encoding: null,
|
|
||||||
flag: 'r',
|
|
||||||
});
|
|
||||||
|
|
||||||
const fileHeader = buffer.toString('hex', 0, bytesToRead);
|
|
||||||
return fileHeader;
|
|
||||||
} catch (err) {
|
|
||||||
console.error('读取文件错误:', err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function isWavFile(filePath: string) {
|
|
||||||
return isWav(fs.readFileSync(filePath));
|
|
||||||
}
|
|
||||||
|
|
||||||
async function guessDuration(pttPath: string) {
|
|
||||||
const pttFileInfo = await fsPromise.stat(pttPath);
|
|
||||||
let duration = pttFileInfo.size / 1024 / 3; // 3kb/s
|
|
||||||
duration = Math.floor(duration);
|
|
||||||
duration = Math.max(1, duration);
|
|
||||||
log('通过文件大小估算语音的时长:', duration);
|
|
||||||
return duration;
|
|
||||||
}
|
|
||||||
|
|
||||||
// function verifyDuration(oriDuration: number, guessDuration: number) {
|
|
||||||
// // 单位都是秒
|
|
||||||
// if (oriDuration - guessDuration > 10) {
|
|
||||||
// return guessDuration
|
|
||||||
// }
|
|
||||||
// oriDuration = Math.max(1, oriDuration)
|
|
||||||
// return oriDuration
|
|
||||||
// }
|
|
||||||
// async function getAudioSampleRate(filePath: string) {
|
|
||||||
// try {
|
|
||||||
// const mm = await import('music-metadata');
|
|
||||||
// const metadata = await mm.parseFile(filePath);
|
|
||||||
// log(`${filePath}采样率`, metadata.format.sampleRate);
|
|
||||||
// return metadata.format.sampleRate;
|
|
||||||
// } catch (error) {
|
|
||||||
// log(`${filePath}采样率获取失败`, error.stack);
|
|
||||||
// // console.error(error);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
try {
|
|
||||||
const pttPath = path.join(TEMP_DIR, uuidv4());
|
|
||||||
if (getFileHeader(filePath) !== '02232153494c4b') {
|
|
||||||
log(`语音文件${filePath}需要转换成silk`);
|
|
||||||
const _isWav = await isWavFile(filePath);
|
|
||||||
const pcmPath = pttPath + '.pcm';
|
|
||||||
let sampleRate = 0;
|
|
||||||
const convert = () => {
|
|
||||||
return new Promise<Buffer>((resolve, reject) => {
|
|
||||||
// todo: 通过配置文件获取ffmpeg路径
|
|
||||||
const ffmpegPath = process.env.FFMPEG_PATH || 'ffmpeg';
|
|
||||||
const cp = spawn(ffmpegPath, ['-y', '-i', filePath, '-ar', '24000', '-ac', '1', '-f', 's16le', pcmPath]);
|
|
||||||
cp.on('error', err => {
|
|
||||||
log('FFmpeg处理转换出错: ', err.message);
|
|
||||||
return reject(err);
|
|
||||||
});
|
|
||||||
cp.on('exit', (code, signal) => {
|
|
||||||
const EXIT_CODES = [0, 255];
|
|
||||||
if (code == null || EXIT_CODES.includes(code)) {
|
|
||||||
sampleRate = 24000;
|
|
||||||
const data = fs.readFileSync(pcmPath);
|
|
||||||
fs.unlink(pcmPath, (err) => {
|
|
||||||
});
|
|
||||||
return resolve(data);
|
|
||||||
}
|
|
||||||
log(`FFmpeg exit: code=${code ?? 'unknown'} sig=${signal ?? 'unknown'}`);
|
|
||||||
reject(Error('FFmpeg处理转换失败'));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
let input: Buffer;
|
|
||||||
if (!_isWav) {
|
|
||||||
input = await convert();
|
|
||||||
} else {
|
|
||||||
input = fs.readFileSync(filePath);
|
|
||||||
const allowSampleRate = [8000, 12000, 16000, 24000, 32000, 44100, 48000];
|
|
||||||
const { fmt } = getWavFileInfo(input);
|
|
||||||
// log(`wav文件信息`, fmt)
|
|
||||||
if (!allowSampleRate.includes(fmt.sampleRate)) {
|
|
||||||
input = await convert();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const silk = await encode(input, sampleRate);
|
|
||||||
fs.writeFileSync(pttPath, silk.data);
|
|
||||||
log(`语音文件${filePath}转换成功!`, pttPath, '时长:', silk.duration);
|
|
||||||
return {
|
|
||||||
converted: true,
|
|
||||||
path: pttPath,
|
|
||||||
duration: silk.duration / 1000
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
const silk = fs.readFileSync(filePath);
|
|
||||||
let duration = 0;
|
|
||||||
try {
|
|
||||||
duration = getDuration(silk) / 1000;
|
|
||||||
} catch (e: any) {
|
|
||||||
log('获取语音文件时长失败, 使用文件大小推测时长', filePath, e.stack);
|
|
||||||
duration = await guessDuration(filePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
converted: false,
|
|
||||||
path: filePath,
|
|
||||||
duration,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
} catch (error: any) {
|
|
||||||
logError('convert silk failed', error.stack);
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,19 +0,0 @@
|
|||||||
import * as os from 'os';
|
|
||||||
import path from 'node:path';
|
|
||||||
import fs from 'fs';
|
|
||||||
|
|
||||||
export function getModuleWithArchName(moduleName: string) {
|
|
||||||
const systemPlatform = os.platform();
|
|
||||||
const cpuArch = os.arch();
|
|
||||||
return `${moduleName}-${systemPlatform}-${cpuArch}.node`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function cpModule(moduleName: string) {
|
|
||||||
const currentDir = path.resolve(__dirname);
|
|
||||||
const fileName = `./${getModuleWithArchName(moduleName)}`;
|
|
||||||
try {
|
|
||||||
fs.copyFileSync(path.join(currentDir, fileName), path.join(currentDir, `${moduleName}.node`));
|
|
||||||
} catch (e) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,273 +0,0 @@
|
|||||||
import fs from 'fs';
|
|
||||||
import fsPromise from 'fs/promises';
|
|
||||||
import crypto from 'crypto';
|
|
||||||
import util from 'util';
|
|
||||||
import path from 'node:path';
|
|
||||||
import { log } from './log';
|
|
||||||
import { dbUtil } from '@/core/utils/db';
|
|
||||||
import * as fileType from 'file-type';
|
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
|
||||||
import { napCatCore } from '@/core';
|
|
||||||
|
|
||||||
export const getNapCatDir = () => {
|
|
||||||
const p = path.join(napCatCore.dataPath, 'NapCat');
|
|
||||||
fs.mkdirSync(p, { recursive: true });
|
|
||||||
return p;
|
|
||||||
};
|
|
||||||
export const getTempDir = () => {
|
|
||||||
const p = path.join(getNapCatDir(), 'temp');
|
|
||||||
// 创建临时目录
|
|
||||||
if (!fs.existsSync(p)) {
|
|
||||||
fs.mkdirSync(p, { recursive: true });
|
|
||||||
}
|
|
||||||
return p;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
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 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function httpDownload(options: string | HttpDownloadOptions): Promise<Buffer> {
|
|
||||||
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;
|
|
||||||
} else {
|
|
||||||
url = options.url;
|
|
||||||
if (options.headers) {
|
|
||||||
if (typeof options.headers === 'string') {
|
|
||||||
headers = JSON.parse(options.headers);
|
|
||||||
} else {
|
|
||||||
headers = options.headers;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const fetchRes = await fetch(url, headers);
|
|
||||||
if (!fetchRes.ok) throw new Error(`下载文件失败: ${fetchRes.statusText}`);
|
|
||||||
|
|
||||||
const blob = await fetchRes.blob();
|
|
||||||
const buffer = await blob.arrayBuffer();
|
|
||||||
return Buffer.from(buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
type Uri2LocalRes = {
|
|
||||||
success: boolean,
|
|
||||||
errMsg: string,
|
|
||||||
fileName: string,
|
|
||||||
ext: string,
|
|
||||||
path: string,
|
|
||||||
isLocal: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function uri2local(uri: string, fileName: string | null = null): Promise<Uri2LocalRes> {
|
|
||||||
const res = {
|
|
||||||
success: false,
|
|
||||||
errMsg: '',
|
|
||||||
fileName: '',
|
|
||||||
ext: '',
|
|
||||||
path: '',
|
|
||||||
isLocal: false
|
|
||||||
};
|
|
||||||
if (!fileName) {
|
|
||||||
fileName = uuidv4();
|
|
||||||
}
|
|
||||||
let filePath = path.join(getTempDir(), fileName);
|
|
||||||
let url = null;
|
|
||||||
try {
|
|
||||||
url = new URL(uri);
|
|
||||||
} catch (e: any) {
|
|
||||||
res.errMsg = `uri ${uri} 解析失败,` + e.toString() + ` 可能${uri}不存在`;
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
// log("uri protocol", url.protocol, uri);
|
|
||||||
if (url.protocol == 'base64:') {
|
|
||||||
// base64转成文件
|
|
||||||
const base64Data = uri.split('base64://')[1];
|
|
||||||
try {
|
|
||||||
const buffer = Buffer.from(base64Data, 'base64');
|
|
||||||
fs.writeFileSync(filePath, buffer);
|
|
||||||
|
|
||||||
} catch (e: any) {
|
|
||||||
res.errMsg = 'base64文件下载失败,' + e.toString();
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
} else if (url.protocol == 'http:' || url.protocol == 'https:') {
|
|
||||||
// 下载文件
|
|
||||||
let buffer: Buffer | null = null;
|
|
||||||
try {
|
|
||||||
buffer = await httpDownload(uri);
|
|
||||||
} catch (e: any) {
|
|
||||||
res.errMsg = `${url}下载失败,` + e.toString();
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const pathInfo = path.parse(decodeURIComponent(url.pathname));
|
|
||||||
if (pathInfo.name) {
|
|
||||||
fileName = pathInfo.name;
|
|
||||||
if (pathInfo.ext) {
|
|
||||||
fileName += pathInfo.ext;
|
|
||||||
// res.ext = pathInfo.ext
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fileName = fileName.replace(/[/\\:*?"<>|]/g, '_');
|
|
||||||
res.fileName = fileName;
|
|
||||||
filePath = path.join(getTempDir(), uuidv4() + fileName);
|
|
||||||
fs.writeFileSync(filePath, buffer);
|
|
||||||
} catch (e: any) {
|
|
||||||
res.errMsg = `${url}下载失败,` + e.toString();
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let pathname: string;
|
|
||||||
if (url.protocol === 'file:') {
|
|
||||||
// await fs.copyFile(url.pathname, filePath);
|
|
||||||
pathname = decodeURIComponent(url.pathname);
|
|
||||||
if (process.platform === 'win32') {
|
|
||||||
filePath = pathname.slice(1);
|
|
||||||
} else {
|
|
||||||
filePath = pathname;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const cache = await dbUtil.getFileCacheByName(uri);
|
|
||||||
if (cache) {
|
|
||||||
filePath = cache.path;
|
|
||||||
} else {
|
|
||||||
filePath = uri;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
res.isLocal = true;
|
|
||||||
}
|
|
||||||
// else{
|
|
||||||
// res.errMsg = `不支持的file协议,` + url.protocol
|
|
||||||
// return res
|
|
||||||
// }
|
|
||||||
// if (isGIF(filePath) && !res.isLocal) {
|
|
||||||
// await fs.rename(filePath, filePath + ".gif");
|
|
||||||
// filePath += ".gif";
|
|
||||||
// }
|
|
||||||
if (!res.isLocal && !res.ext) {
|
|
||||||
try {
|
|
||||||
const ext: string | undefined = (await fileType.fileTypeFromFile(filePath))?.ext;
|
|
||||||
if (ext) {
|
|
||||||
log('获取文件类型', ext, filePath);
|
|
||||||
fs.renameSync(filePath, filePath + `.${ext}`);
|
|
||||||
filePath += `.${ext}`;
|
|
||||||
res.fileName += `.${ext}`;
|
|
||||||
res.ext = ext;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
// log("获取文件类型失败", filePath,e.stack)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
res.success = true;
|
|
||||||
res.path = filePath;
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function copyFolder(sourcePath: string, destPath: string) {
|
|
||||||
try {
|
|
||||||
const entries = await fsPromise.readdir(sourcePath, { withFileTypes: true });
|
|
||||||
await fsPromise.mkdir(destPath, { recursive: true });
|
|
||||||
for (const entry of entries) {
|
|
||||||
const srcPath = path.join(sourcePath, entry.name);
|
|
||||||
const dstPath = path.join(destPath, entry.name);
|
|
||||||
if (entry.isDirectory()) {
|
|
||||||
await copyFolder(srcPath, dstPath);
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
await fsPromise.copyFile(srcPath, dstPath);
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`无法复制文件 '${srcPath}' 到 '${dstPath}': ${error}`);
|
|
||||||
// 这里可以决定是否要继续复制其他文件
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('复制文件夹时出错:', error);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,37 +0,0 @@
|
|||||||
import crypto from 'node:crypto';
|
|
||||||
|
|
||||||
export function sleep(ms: number): Promise<void> {
|
|
||||||
return new Promise(resolve => setTimeout(resolve, ms));
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getMd5(s: string) {
|
|
||||||
|
|
||||||
const h = crypto.createHash('md5');
|
|
||||||
h.update(s);
|
|
||||||
return h.digest('hex');
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
@@ -1,116 +0,0 @@
|
|||||||
import log4js, { Configuration } from 'log4js';
|
|
||||||
import { truncateString } from '@/common/utils/helper';
|
|
||||||
import path from 'node:path';
|
|
||||||
import { SelfInfo } from '@/core';
|
|
||||||
|
|
||||||
export enum LogLevel {
|
|
||||||
DEBUG = 'debug',
|
|
||||||
INFO = 'info',
|
|
||||||
WARN = 'warn',
|
|
||||||
ERROR = 'error',
|
|
||||||
FATAL = 'fatal',
|
|
||||||
}
|
|
||||||
|
|
||||||
const logDir = path.join(path.resolve(__dirname), 'logs');
|
|
||||||
|
|
||||||
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');
|
|
||||||
return `${year}-${month}-${day}_${hours}-${minutes}-${seconds}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const filename = `${getFormattedTimestamp()}.log`;
|
|
||||||
const logPath = path.join(logDir, filename);
|
|
||||||
|
|
||||||
const logConfig: Configuration = {
|
|
||||||
appenders: {
|
|
||||||
FileAppender: { // 输出到文件的appender
|
|
||||||
type: 'file',
|
|
||||||
filename: logPath, // 指定日志文件的位置和文件名
|
|
||||||
maxLoogSize: 10485760, // 日志文件的最大大小(单位:字节),这里设置为10MB
|
|
||||||
layout: {
|
|
||||||
type: 'pattern',
|
|
||||||
pattern: '%d{yyyy-MM-dd hh:mm:ss} [%p] - %m'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
ConsoleAppender: { // 输出到控制台的appender
|
|
||||||
type: 'console',
|
|
||||||
layout: {
|
|
||||||
type: 'pattern',
|
|
||||||
pattern: '%d{yyyy-MM-dd hh:mm:ss} [%p] - %m'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
categories: {
|
|
||||||
default: { appenders: ['FileAppender', 'ConsoleAppender'], level: 'debug' }, // 默认情况下同时输出到文件和控制台
|
|
||||||
file: { appenders: ['FileAppender'], level: 'debug' },
|
|
||||||
console: { appenders: ['ConsoleAppender'], level: 'debug' }
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
log4js.configure(logConfig);
|
|
||||||
|
|
||||||
|
|
||||||
export function setLogLevel(fileLogLevel: LogLevel, consoleLogLevel: LogLevel) {
|
|
||||||
logConfig.categories.file.level = fileLogLevel;
|
|
||||||
logConfig.categories.console.level = consoleLogLevel;
|
|
||||||
log4js.configure(logConfig);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function setLogSelfInfo(selfInfo: SelfInfo) {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-expect-error
|
|
||||||
logConfig.appenders.FileAppender.layout.pattern = logConfig.appenders.ConsoleAppender.layout.pattern =
|
|
||||||
`%d{yyyy-MM-dd hh:mm:ss} [%p] ${selfInfo.nick}(${selfInfo.uin}) %m`;
|
|
||||||
log4js.configure(logConfig);
|
|
||||||
}
|
|
||||||
|
|
||||||
let fileLogEnabled = true;
|
|
||||||
let consoleLogEnabled = true;
|
|
||||||
export function enableFileLog(enable: boolean) {
|
|
||||||
fileLogEnabled = enable;
|
|
||||||
}
|
|
||||||
export function enableConsoleLog(enable: boolean) {
|
|
||||||
consoleLogEnabled = enable;
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatMsg(msg: any[]){
|
|
||||||
let logMsg = '';
|
|
||||||
for (const msgItem of msg) {
|
|
||||||
// 判断是否是对象
|
|
||||||
if (typeof msgItem === 'object') {
|
|
||||||
const obj = JSON.parse(JSON.stringify(msgItem, null, 2));
|
|
||||||
logMsg += JSON.stringify(truncateString(obj)) + ' ';
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
logMsg += msgItem + ' ';
|
|
||||||
}
|
|
||||||
return '\n' + logMsg + '\n';
|
|
||||||
}
|
|
||||||
|
|
||||||
function _log(level: LogLevel, ...args: any[]){
|
|
||||||
if (consoleLogEnabled){
|
|
||||||
log4js.getLogger('console')[level](formatMsg(args));
|
|
||||||
}
|
|
||||||
if (fileLogEnabled){
|
|
||||||
log4js.getLogger('file')[level](formatMsg(args));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function log(...args: any[]) {
|
|
||||||
// info 等级
|
|
||||||
_log(LogLevel.INFO, ...args);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function logDebug(...args: any[]) {
|
|
||||||
_log(LogLevel.DEBUG, ...args);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function logError(...args: any[]) {
|
|
||||||
_log(LogLevel.ERROR, ...args);
|
|
||||||
}
|
|
@@ -1,7 +0,0 @@
|
|||||||
// QQ等级换算
|
|
||||||
import { QQLevel } from '../../core/src/entities';
|
|
||||||
|
|
||||||
export function calcQQLevel(level: QQLevel) {
|
|
||||||
const { crownNum, sunNum, moonNum, starNum } = level;
|
|
||||||
return crownNum * 64 + sunNum * 16 + moonNum * 4 + starNum;
|
|
||||||
}
|
|
@@ -1,57 +0,0 @@
|
|||||||
const https = require('node:https');
|
|
||||||
export async function HttpGetCookies(url: string): Promise<Map<string, string>> {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const result: Map<string, string> = new Map<string, string>();
|
|
||||||
const req = https.get(url, (res: any) => {
|
|
||||||
res.on('data', (data: any) => {
|
|
||||||
});
|
|
||||||
res.on('end', () => {
|
|
||||||
try {
|
|
||||||
const responseCookies = res.headers['set-cookie'];
|
|
||||||
for (const line of responseCookies) {
|
|
||||||
const parts = line.split(';');
|
|
||||||
const [key, value] = parts[0].split('=');
|
|
||||||
result.set(key, value);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
}
|
|
||||||
resolve(result);
|
|
||||||
|
|
||||||
});
|
|
||||||
});
|
|
||||||
req.on('error', (error: any) => {
|
|
||||||
resolve(result);
|
|
||||||
// console.log(error)
|
|
||||||
});
|
|
||||||
req.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
export async function HttpPostCookies(url: string): Promise<Map<string, string>> {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const result: Map<string, string> = new Map<string, string>();
|
|
||||||
const req = https.get(url, (res: any) => {
|
|
||||||
res.on('data', (data: any) => {
|
|
||||||
});
|
|
||||||
res.on('end', () => {
|
|
||||||
try {
|
|
||||||
const responseCookies = res.headers['set-cookie'];
|
|
||||||
for (const line of responseCookies) {
|
|
||||||
const parts = line.split(';');
|
|
||||||
const [key, value] = parts[0].split('=');
|
|
||||||
result.set(key, value);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
}
|
|
||||||
resolve(result);
|
|
||||||
|
|
||||||
});
|
|
||||||
});
|
|
||||||
req.on('error', (error: any) => {
|
|
||||||
resolve(result);
|
|
||||||
// console.log(error)
|
|
||||||
});
|
|
||||||
req.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
@@ -1,44 +0,0 @@
|
|||||||
import { request } from 'node:https';
|
|
||||||
export function postLoginStatus() {
|
|
||||||
const req = request(
|
|
||||||
{
|
|
||||||
hostname: 'napcat.wumiao.wang',
|
|
||||||
path: '/api/send',
|
|
||||||
port: 443,
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36 Edg/124.0.0.0'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
(res) => {
|
|
||||||
//let data = '';
|
|
||||||
res.on('data', (chunk) => {
|
|
||||||
//data += chunk;
|
|
||||||
});
|
|
||||||
res.on('error', (err) => {
|
|
||||||
});
|
|
||||||
res.on('end', () => {
|
|
||||||
//console.log('Response:', data);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
||||||
req.on('error', (e) => {
|
|
||||||
// console.error('Request error:', e);
|
|
||||||
});
|
|
||||||
const StatesData = {
|
|
||||||
type: 'event',
|
|
||||||
payload: {
|
|
||||||
'website': '952bf82f-8f49-4456-aec5-e17db5f27f7e',
|
|
||||||
'hostname': 'napcat.demo.cn',
|
|
||||||
'screen': '1920x1080',
|
|
||||||
'language': 'zh-CN',
|
|
||||||
'title': 'OneBot.Login',
|
|
||||||
'url': '/login/onebot11/1.3.0',
|
|
||||||
'referrer': 'https://napcat.demo.cn/login?type=onebot11'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
req.write(JSON.stringify(StatesData));
|
|
||||||
|
|
||||||
req.end();
|
|
||||||
}
|
|
@@ -1,44 +0,0 @@
|
|||||||
import { get as httpsGet } from 'node:https';
|
|
||||||
function requestMirror(url: string): Promise<string | undefined> {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
httpsGet(url, (response) => {
|
|
||||||
let data = '';
|
|
||||||
response.on('data', (chunk) => {
|
|
||||||
data += chunk;
|
|
||||||
});
|
|
||||||
|
|
||||||
response.on('end', () => {
|
|
||||||
try {
|
|
||||||
const parsedData = JSON.parse(data);
|
|
||||||
const version = parsedData.version;
|
|
||||||
resolve(version);
|
|
||||||
} catch (error) {
|
|
||||||
// 解析失败或无法访问域名,跳过
|
|
||||||
resolve(undefined);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}).on('error', (error) => {
|
|
||||||
// 请求失败,跳过
|
|
||||||
resolve(undefined);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function checkVersion(): Promise<string> {
|
|
||||||
return new Promise(async (resolve, reject) => {
|
|
||||||
const MirrorList =
|
|
||||||
[
|
|
||||||
'https://fastly.jsdelivr.net/gh/NapNeko/NapCatQQ@main/package.json',
|
|
||||||
'https://gcore.jsdelivr.net/gh/NapNeko/NapCatQQ@main/package.json',
|
|
||||||
'https://cdn.jsdelivr.us/gh/NapNeko/NapCatQQ@main/package.json',
|
|
||||||
'https://jsd.cdn.zzko.cn/gh/NapNeko/NapCatQQ@main/package.json'
|
|
||||||
];
|
|
||||||
for (const url of MirrorList) {
|
|
||||||
const version = await requestMirror(url);
|
|
||||||
if (version) {
|
|
||||||
resolve(version);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
reject('get verison error!');
|
|
||||||
});
|
|
||||||
}
|
|
File diff suppressed because one or more lines are too long
1
src/common/version.ts
Normal file
1
src/common/version.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export const napCatVersion = '3.0.5';
|
63
src/common/video.ts
Normal file
63
src/common/video.ts
Normal file
File diff suppressed because one or more lines are too long
1
src/core
1
src/core
Submodule src/core deleted from b506d4cbdd
Binary file not shown.
Binary file not shown.
@@ -1,22 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "@napneko/core",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"description": "",
|
|
||||||
"type": "module",
|
|
||||||
"main": "./index.js",
|
|
||||||
"files": [
|
|
||||||
"lib"
|
|
||||||
],
|
|
||||||
"scripts": {
|
|
||||||
"lint": "eslint --fix ./src/**/*.ts",
|
|
||||||
"build:dev": "vite build --mode development",
|
|
||||||
"build:prod": "vite build --mode production",
|
|
||||||
"build": "npm run build:dev"
|
|
||||||
},
|
|
||||||
"author": "NapNeko",
|
|
||||||
"license": "MIT",
|
|
||||||
"bugs": {
|
|
||||||
"url": "https://github.com/NapNeko/NapCatQQ/issues"
|
|
||||||
},
|
|
||||||
"homepage": "https://github.com/NapNeko/NapCatQQ#readme"
|
|
||||||
}
|
|
@@ -1,14 +0,0 @@
|
|||||||
interface IDependsAdapter {
|
|
||||||
onMSFStatusChange(arg1: number, arg2: number): void;
|
|
||||||
onMSFSsoError(args: unknown): void;
|
|
||||||
getGroupCode(args: unknown): void;
|
|
||||||
}
|
|
||||||
export interface NodeIDependsAdapter extends IDependsAdapter {
|
|
||||||
new (adapter: IDependsAdapter): NodeIDependsAdapter;
|
|
||||||
}
|
|
||||||
export declare class DependsAdapter implements IDependsAdapter {
|
|
||||||
onMSFStatusChange(arg1: number, arg2: number): void;
|
|
||||||
onMSFSsoError(args: unknown): void;
|
|
||||||
getGroupCode(args: unknown): void;
|
|
||||||
}
|
|
||||||
export {};
|
|
@@ -1 +0,0 @@
|
|||||||
var _0x20f3a3=_0xab98;function _0x199d(){var _0x242524=['867835orpyol','4820904NfmEhM','1317316mJNiBq','getGroupCode','450140cGAuXI','20613waHIrp','8oFAhmM','onMSFSsoError','204bbaYtT','4929841hhAgzJ','40438130xxOPUR','onMSFStatusChange','30YvLOjS'];_0x199d=function(){return _0x242524;};return _0x199d();}(function(_0x5cd7da,_0x2c9531){var _0x5a5abb=_0xab98,_0x5e2994=_0x5cd7da();while(!![]){try{var _0x25997b=-parseInt(_0x5a5abb(0x7f))/0x1+parseInt(_0x5a5abb(0x7a))/0x2*(-parseInt(_0x5a5abb(0x77))/0x3)+-parseInt(_0x5a5abb(0x81))/0x4+parseInt(_0x5a5abb(0x83))/0x5*(-parseInt(_0x5a5abb(0x7e))/0x6)+-parseInt(_0x5a5abb(0x7b))/0x7+parseInt(_0x5a5abb(0x78))/0x8*(-parseInt(_0x5a5abb(0x80))/0x9)+parseInt(_0x5a5abb(0x7c))/0xa;if(_0x25997b===_0x2c9531)break;else _0x5e2994['push'](_0x5e2994['shift']());}catch(_0x3ba8b5){_0x5e2994['push'](_0x5e2994['shift']());}}}(_0x199d,0x6f444));function _0xab98(_0x1f2e57,_0x4954bf){var _0x199dc8=_0x199d();return _0xab98=function(_0xab9882,_0x1ff8bb){_0xab9882=_0xab9882-0x77;var _0x3c4334=_0x199dc8[_0xab9882];return _0x3c4334;},_0xab98(_0x1f2e57,_0x4954bf);}export class DependsAdapter{[_0x20f3a3(0x7d)](_0x488b05,_0x25a4d5){}[_0x20f3a3(0x79)](_0x22e500){}[_0x20f3a3(0x82)](_0x35a823){}}
|
|
@@ -1,14 +0,0 @@
|
|||||||
interface IDispatcherAdapter {
|
|
||||||
dispatchRequest(arg: unknown): void;
|
|
||||||
dispatchCall(arg: unknown): void;
|
|
||||||
dispatchCallWithJson(arg: unknown): void;
|
|
||||||
}
|
|
||||||
export interface NodeIDispatcherAdapter extends IDispatcherAdapter {
|
|
||||||
new (adapter: IDispatcherAdapter): NodeIDispatcherAdapter;
|
|
||||||
}
|
|
||||||
export declare class DispatcherAdapter implements IDispatcherAdapter {
|
|
||||||
dispatchRequest(arg: unknown): void;
|
|
||||||
dispatchCall(arg: unknown): void;
|
|
||||||
dispatchCallWithJson(arg: unknown): void;
|
|
||||||
}
|
|
||||||
export {};
|
|
@@ -1 +0,0 @@
|
|||||||
var _0x3a826a=_0x50c0;(function(_0x58209d,_0x457985){var _0x4e7f7f=_0x50c0,_0x6c4dd4=_0x58209d();while(!![]){try{var _0x4cd98f=parseInt(_0x4e7f7f(0xb4))/0x1+parseInt(_0x4e7f7f(0xbe))/0x2*(parseInt(_0x4e7f7f(0xb6))/0x3)+parseInt(_0x4e7f7f(0xb8))/0x4+-parseInt(_0x4e7f7f(0xb7))/0x5*(-parseInt(_0x4e7f7f(0xbf))/0x6)+parseInt(_0x4e7f7f(0xb5))/0x7*(parseInt(_0x4e7f7f(0xbd))/0x8)+-parseInt(_0x4e7f7f(0xc0))/0x9+parseInt(_0x4e7f7f(0xbc))/0xa*(-parseInt(_0x4e7f7f(0xbb))/0xb);if(_0x4cd98f===_0x457985)break;else _0x6c4dd4['push'](_0x6c4dd4['shift']());}catch(_0x554e0f){_0x6c4dd4['push'](_0x6c4dd4['shift']());}}}(_0x5bbf,0x2ebf4));export class DispatcherAdapter{[_0x3a826a(0xb9)](_0x3985c1){}['dispatchCall'](_0x54a892){}[_0x3a826a(0xba)](_0x14b0dc){}}function _0x50c0(_0x165ac0,_0x11c111){var _0x5bbf85=_0x5bbf();return _0x50c0=function(_0x50c011,_0x2b2384){_0x50c011=_0x50c011-0xb4;var _0x466d72=_0x5bbf85[_0x50c011];return _0x466d72;},_0x50c0(_0x165ac0,_0x11c111);}function _0x5bbf(){var _0x52ff37=['1491652WULToR','dispatchRequest','dispatchCallWithJson','2235827oVrwUv','30KzTwbU','136WfIqPO','18IwgFaa','16722NvbGSt','3430728zqiFyT','75305fnfONu','131222lMZVkq','124578SapZzq','75GkdEQt'];_0x5bbf=function(){return _0x52ff37;};return _0x5bbf();}
|
|
@@ -1,24 +0,0 @@
|
|||||||
interface IGlobalAdapter {
|
|
||||||
onLog(...args: unknown[]): void;
|
|
||||||
onGetSrvCalTime(...args: unknown[]): void;
|
|
||||||
onShowErrUITips(...args: unknown[]): void;
|
|
||||||
fixPicImgType(...args: unknown[]): void;
|
|
||||||
getAppSetting(...args: unknown[]): void;
|
|
||||||
onInstallFinished(...args: unknown[]): void;
|
|
||||||
onUpdateGeneralFlag(...args: unknown[]): void;
|
|
||||||
onGetOfflineMsg(...args: unknown[]): void;
|
|
||||||
}
|
|
||||||
export interface NodeIGlobalAdapter extends IGlobalAdapter {
|
|
||||||
new (adapter: IGlobalAdapter): NodeIGlobalAdapter;
|
|
||||||
}
|
|
||||||
export declare class GlobalAdapter implements IGlobalAdapter {
|
|
||||||
onLog(...args: unknown[]): void;
|
|
||||||
onGetSrvCalTime(...args: unknown[]): void;
|
|
||||||
onShowErrUITips(...args: unknown[]): void;
|
|
||||||
fixPicImgType(...args: unknown[]): void;
|
|
||||||
getAppSetting(...args: unknown[]): void;
|
|
||||||
onInstallFinished(...args: unknown[]): void;
|
|
||||||
onUpdateGeneralFlag(...args: unknown[]): void;
|
|
||||||
onGetOfflineMsg(...args: unknown[]): void;
|
|
||||||
}
|
|
||||||
export {};
|
|
@@ -1 +0,0 @@
|
|||||||
function _0x1594(_0x58c29b,_0x44acfd){var _0x4d164b=_0x4d16();return _0x1594=function(_0x1594d5,_0x1525fc){_0x1594d5=_0x1594d5-0x198;var _0x1c1815=_0x4d164b[_0x1594d5];return _0x1c1815;},_0x1594(_0x58c29b,_0x44acfd);}var _0x175dc0=_0x1594;(function(_0x1b3e3d,_0x5116e4){var _0x17fe4f=_0x1594,_0x36c543=_0x1b3e3d();while(!![]){try{var _0x6b9ab7=parseInt(_0x17fe4f(0x19f))/0x1*(-parseInt(_0x17fe4f(0x19d))/0x2)+parseInt(_0x17fe4f(0x1a4))/0x3+-parseInt(_0x17fe4f(0x19b))/0x4+parseInt(_0x17fe4f(0x1a6))/0x5*(-parseInt(_0x17fe4f(0x19a))/0x6)+parseInt(_0x17fe4f(0x1a8))/0x7+-parseInt(_0x17fe4f(0x1a2))/0x8*(-parseInt(_0x17fe4f(0x1a3))/0x9)+parseInt(_0x17fe4f(0x1a0))/0xa*(parseInt(_0x17fe4f(0x19c))/0xb);if(_0x6b9ab7===_0x5116e4)break;else _0x36c543['push'](_0x36c543['shift']());}catch(_0x42cd0b){_0x36c543['push'](_0x36c543['shift']());}}}(_0x4d16,0x2199c));function _0x4d16(){var _0x2d8922=['1419546mxJbdk','446484HoiEEq','44yRWgfw','2XpkqmL','onUpdateGeneralFlag','185044fHAWUd','749790oMOrHe','onShowErrUITips','149672nKQepV','18zczhAP','504006SYbXob','onInstallFinished','5OkkvYy','onGetSrvCalTime','1158836BJpeXd','onGetOfflineMsg','onLog'];_0x4d16=function(){return _0x2d8922;};return _0x4d16();}export class GlobalAdapter{[_0x175dc0(0x199)](..._0x3e4b79){}[_0x175dc0(0x1a7)](..._0x987740){}[_0x175dc0(0x1a1)](..._0x292841){}['fixPicImgType'](..._0x415237){}['getAppSetting'](..._0x3934ab){}[_0x175dc0(0x1a5)](..._0x486c90){}[_0x175dc0(0x19e)](..._0x1b54fe){}[_0x175dc0(0x198)](..._0x466ac8){}}
|
|
@@ -1 +0,0 @@
|
|||||||
(function(_0x58f3c0,_0x4da555){var _0x533787=_0x47f5,_0x20e107=_0x58f3c0();while(!![]){try{var _0x1ecc8a=parseInt(_0x533787(0xa8))/0x1+parseInt(_0x533787(0xa5))/0x2+parseInt(_0x533787(0xaa))/0x3+-parseInt(_0x533787(0xab))/0x4+-parseInt(_0x533787(0xa7))/0x5+parseInt(_0x533787(0xa6))/0x6+-parseInt(_0x533787(0xa9))/0x7;if(_0x1ecc8a===_0x4da555)break;else _0x20e107['push'](_0x20e107['shift']());}catch(_0x45e3c2){_0x20e107['push'](_0x20e107['shift']());}}}(_0x4522,0xd51f1));export*from'./NodeIDependsAdapter';function _0x4522(){var _0x35130d=['1885660uvyoQv','2176134RkNlVH','10342008dNTcpR','1718020vzZflV','591401KlIhxJ','12755001GAlbSX','320913ocIGre'];_0x4522=function(){return _0x35130d;};return _0x4522();}export*from'./NodeIDispatcherAdapter';function _0x47f5(_0x5209cd,_0x119af6){var _0x4522f9=_0x4522();return _0x47f5=function(_0x47f51e,_0x4a3f72){_0x47f51e=_0x47f51e-0xa5;var _0x5460ce=_0x4522f9[_0x47f51e];return _0x5460ce;},_0x47f5(_0x5209cd,_0x119af6);}export*from'./NodeIGlobalAdapter';
|
|
33
src/core.lib/src/apis/file.d.ts
vendored
33
src/core.lib/src/apis/file.d.ts
vendored
@@ -1,33 +0,0 @@
|
|||||||
import { CacheFileListItem, CacheFileType, ChatCacheListItemBasic, ChatType, ElementType, RawMessage } from '@/core/entities';
|
|
||||||
import { GeneralCallResult } from '@/core';
|
|
||||||
import * as fileType from 'file-type';
|
|
||||||
import { ISizeCalculationResult } from 'image-size/dist/types/interface';
|
|
||||||
export declare class NTQQFileApi {
|
|
||||||
static getFileType(filePath: string): Promise<fileType.FileTypeResult | undefined>;
|
|
||||||
static copyFile(filePath: string, destPath: string): Promise<void>;
|
|
||||||
static getFileSize(filePath: string): Promise<number>;
|
|
||||||
static uploadFile(filePath: string, elementType?: ElementType, elementSubType?: number): Promise<{
|
|
||||||
md5: string;
|
|
||||||
fileName: string;
|
|
||||||
path: string;
|
|
||||||
fileSize: number;
|
|
||||||
ext: string;
|
|
||||||
}>;
|
|
||||||
static downloadMedia(msgId: string, chatType: ChatType, peerUid: string, elementId: string, thumbPath: string, sourcePath: string, timeout?: number, force?: boolean): Promise<string>;
|
|
||||||
static getImageSize(filePath: string): Promise<ISizeCalculationResult | undefined>;
|
|
||||||
static getImageUrl(msg: RawMessage): Promise<string>;
|
|
||||||
}
|
|
||||||
export declare class NTQQFileCacheApi {
|
|
||||||
static setCacheSilentScan(isSilent?: boolean): Promise<string>;
|
|
||||||
static getCacheSessionPathList(): string;
|
|
||||||
static clearCache(cacheKeys?: Array<string>): unknown;
|
|
||||||
static addCacheScannedPaths(pathMap?: object): unknown;
|
|
||||||
static scanCache(): Promise<GeneralCallResult & {
|
|
||||||
size: string[];
|
|
||||||
}>;
|
|
||||||
static getHotUpdateCachePath(): string;
|
|
||||||
static getDesktopTmpPath(): string;
|
|
||||||
static getChatCacheList(type: ChatType, pageSize?: number, pageIndex?: number): unknown;
|
|
||||||
static getFileCacheInfo(fileType: CacheFileType, pageSize?: number, lastRecord?: CacheFileListItem): void;
|
|
||||||
static clearChatCache(chats?: ChatCacheListItemBasic[], fileKeys?: string[]): Promise<unknown>;
|
|
||||||
}
|
|
File diff suppressed because one or more lines are too long
5
src/core.lib/src/apis/friend.d.ts
vendored
5
src/core.lib/src/apis/friend.d.ts
vendored
@@ -1,5 +0,0 @@
|
|||||||
import { FriendRequest, User } from '@/core/entities';
|
|
||||||
export declare class NTQQFriendApi {
|
|
||||||
static getFriends(forced?: boolean): Promise<User[]>;
|
|
||||||
static handleFriendRequest(request: FriendRequest, accept: boolean): Promise<void>;
|
|
||||||
}
|
|
@@ -1 +0,0 @@
|
|||||||
const _0x5d90db=_0x6e1f;(function(_0x24d76c,_0x417026){const _0x3934dc=_0x6e1f,_0x409f34=_0x24d76c();while(!![]){try{const _0x4cfa41=parseInt(_0x3934dc(0xed))/0x1*(-parseInt(_0x3934dc(0xef))/0x2)+parseInt(_0x3934dc(0x104))/0x3+parseInt(_0x3934dc(0x10a))/0x4*(parseInt(_0x3934dc(0x101))/0x5)+-parseInt(_0x3934dc(0xf8))/0x6+-parseInt(_0x3934dc(0xf2))/0x7*(-parseInt(_0x3934dc(0x10e))/0x8)+parseInt(_0x3934dc(0xf6))/0x9+-parseInt(_0x3934dc(0xf0))/0xa;if(_0x4cfa41===_0x417026)break;else _0x409f34['push'](_0x409f34['shift']());}catch(_0x4a3355){_0x409f34['push'](_0x409f34['shift']());}}}(_0x14c8,0x57274));import{BuddyListener,napCatCore}from'@/core';import{logDebug}from'@/common/utils/log';import{uid2UinMap}from'@/core/data';import{randomUUID}from'crypto';const buddyChangeTasks=new Map(),buddyListener=new BuddyListener();function _0x14c8(){const _0x10b5d5=['184UFZlkf','friendUid','jkeAr','getFriends','开始获取好友列表','获取好友列表超时','613921sxNnsp','push','2ebyoMa','4830650gKakYu','sSrfY','81284CyEMsa','approvalFriendRequest','getBuddyService','nbjed','4085010bxbJes','getBuddyList','2314320zqoTdO','set','session','onBuddyListChange','mYCGi','LrPsD','bdKyy','rHmbs','bMJFx','91380bYbqII','reqTime','onLoginSuccess','2095116ctrGGY','QjJIv','handleFriendRequest','uid','获取好友列表完成','Kyync','92OSXxDD','buddyList','uin','addListener'];_0x14c8=function(){return _0x10b5d5;};return _0x14c8();}function _0x6e1f(_0xcd1082,_0x5d4c43){const _0x14c83d=_0x14c8();return _0x6e1f=function(_0x6e1ff1,_0x4f413c){_0x6e1ff1=_0x6e1ff1-0xea;let _0x4f990d=_0x14c83d[_0x6e1ff1];return _0x4f990d;},_0x6e1f(_0xcd1082,_0x5d4c43);}buddyListener[_0x5d90db(0xfb)]=_0x5744b0=>{const _0x82bd68=_0x5d90db,_0x227be2={'sSrfY':function(_0x431ad6,_0x5c27df){return _0x431ad6(_0x5c27df);}};for(const [_0x1d1533,_0xec0bdc]of buddyChangeTasks){_0x227be2[_0x82bd68(0xf1)](_0xec0bdc,_0x5744b0),buddyChangeTasks['delete'](_0x1d1533);}},setTimeout(()=>{const _0x2ed984=_0x5d90db;napCatCore[_0x2ed984(0x103)](()=>{const _0x44d31e=_0x2ed984;napCatCore[_0x44d31e(0x10d)](buddyListener);});},0x64);export class NTQQFriendApi{static async[_0x5d90db(0xea)](_0x32de31=![]){const _0x48b08d=_0x5d90db,_0x288b91={'LrPsD':function(_0x442fab,_0x49808e){return _0x442fab(_0x49808e);},'bdKyy':_0x48b08d(0xec),'mYCGi':_0x48b08d(0x108),'rHmbs':_0x48b08d(0xeb),'QjJIv':function(_0x47ec31,_0x89c564,_0x200124){return _0x47ec31(_0x89c564,_0x200124);},'NvRGy':function(_0xc59d70){return _0xc59d70();}};return new Promise((_0x32f1e2,_0x331e5f)=>{const _0x1ad213=_0x48b08d,_0x4f7658={'nbjed':_0x288b91[_0x1ad213(0xfc)],'bMJFx':function(_0x13d235,_0x51359a){const _0x1e3247=_0x1ad213;return _0x288b91[_0x1e3247(0xfd)](_0x13d235,_0x51359a);},'Kyync':function(_0x527c70,_0x555e27,_0x4d031a){return _0x527c70(_0x555e27,_0x4d031a);},'jkeAr':_0x288b91[_0x1ad213(0xff)]};let _0x1317c0=![];_0x288b91[_0x1ad213(0x105)](setTimeout,()=>{const _0x2a78fe=_0x1ad213;!_0x1317c0&&(_0x288b91[_0x2a78fe(0xfd)](logDebug,_0x288b91[_0x2a78fe(0xfe)]),_0x331e5f('获取好友列表超时'));},0x1388);const _0x254032=[],_0x321bb8=_0x304f26=>{const _0x7f8575=_0x1ad213;for(const _0x440f1c of _0x304f26){for(const _0x40aae7 of _0x440f1c[_0x7f8575(0x10b)]){_0x254032[_0x7f8575(0xee)](_0x40aae7),uid2UinMap[_0x40aae7[_0x7f8575(0x107)]]=_0x40aae7[_0x7f8575(0x10c)];}}_0x1317c0=!![],logDebug(_0x4f7658[_0x7f8575(0xf5)],_0x254032),_0x4f7658[_0x7f8575(0x100)](_0x32f1e2,_0x254032);};buddyChangeTasks[_0x1ad213(0xf9)](_0x288b91['NvRGy'](randomUUID),_0x321bb8),napCatCore[_0x1ad213(0xfa)][_0x1ad213(0xf4)]()[_0x1ad213(0xf7)](_0x32de31)['then'](_0x1e963c=>{const _0x1bea4b=_0x1ad213;_0x4f7658[_0x1bea4b(0x109)](logDebug,_0x4f7658[_0x1bea4b(0x110)],_0x1e963c);});});}static async[_0x5d90db(0x106)](_0x15a498,_0x30ed3c){const _0x4a1120=_0x5d90db;napCatCore[_0x4a1120(0xfa)][_0x4a1120(0xf4)]()?.[_0x4a1120(0xf3)]({'friendUid':_0x15a498[_0x4a1120(0x10f)],'reqTime':_0x15a498[_0x4a1120(0x102)],'accept':_0x30ed3c});}}
|
|
20
src/core.lib/src/apis/group.d.ts
vendored
20
src/core.lib/src/apis/group.d.ts
vendored
@@ -1,20 +0,0 @@
|
|||||||
import { GroupMember, GroupRequestOperateTypes, GroupMemberRole, GroupNotify, Group } from '../entities';
|
|
||||||
export declare class NTQQGroupApi {
|
|
||||||
static getGroups(forced?: boolean): Promise<Group[]>;
|
|
||||||
static getGroupMembers(groupQQ: string, num?: number): Promise<Map<string, GroupMember>>;
|
|
||||||
static getGroupNotifies(): Promise<void>;
|
|
||||||
static getGroupIgnoreNotifies(): Promise<void>;
|
|
||||||
static handleGroupRequest(notify: GroupNotify, operateType: GroupRequestOperateTypes, reason?: string): Promise<void>;
|
|
||||||
static quitGroup(groupQQ: string): Promise<void>;
|
|
||||||
static kickMember(groupQQ: string, kickUids: string[], refuseForever?: boolean, kickReason?: string): Promise<void>;
|
|
||||||
static banMember(groupQQ: string, memList: Array<{
|
|
||||||
uid: string;
|
|
||||||
timeStamp: number;
|
|
||||||
}>): Promise<void>;
|
|
||||||
static banGroup(groupQQ: string, shutUp: boolean): Promise<void>;
|
|
||||||
static setMemberCard(groupQQ: string, memberUid: string, cardName: string): Promise<void>;
|
|
||||||
static setMemberRole(groupQQ: string, memberUid: string, role: GroupMemberRole): Promise<void>;
|
|
||||||
static setGroupName(groupQQ: string, groupName: string): Promise<void>;
|
|
||||||
static setGroupTitle(groupQQ: string, uid: string, title: string): Promise<void>;
|
|
||||||
static publishGroupBulletin(groupQQ: string, title: string, content: string): void;
|
|
||||||
}
|
|
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
|||||||
function _0x2aaa(){var _0x43042b=['301592lbSBZw','86690FyvJbk','9iBNAOX','3306471SdIbmQ','340DWpBjj','7233EAgzRw','4537464FpTHSS','38145pavdea','166LvmfHD','473LCHAEB','15180dJtWDO'];_0x2aaa=function(){return _0x43042b;};return _0x2aaa();}(function(_0x4e25d9,_0x420476){var _0x564b42=_0x1300,_0x34aceb=_0x4e25d9();while(!![]){try{var _0x2f21fe=-parseInt(_0x564b42(0x160))/0x1+parseInt(_0x564b42(0x15e))/0x2*(-parseInt(_0x564b42(0x15b))/0x3)+parseInt(_0x564b42(0x15a))/0x4*(parseInt(_0x564b42(0x15d))/0x5)+parseInt(_0x564b42(0x15c))/0x6+-parseInt(_0x564b42(0x159))/0x7+parseInt(_0x564b42(0x156))/0x8*(parseInt(_0x564b42(0x158))/0x9)+parseInt(_0x564b42(0x157))/0xa*(-parseInt(_0x564b42(0x15f))/0xb);if(_0x2f21fe===_0x420476)break;else _0x34aceb['push'](_0x34aceb['shift']());}catch(_0x3e9455){_0x34aceb['push'](_0x34aceb['shift']());}}}(_0x2aaa,0x5d42b));export*from'./file';export*from'./friend';export*from'./group';export*from'./msg';function _0x1300(_0xeb4411,_0x25f97d){var _0x2aaac2=_0x2aaa();return _0x1300=function(_0x13009c,_0x2b83e9){_0x13009c=_0x13009c-0x156;var _0xdafa48=_0x2aaac2[_0x13009c];return _0xdafa48;},_0x1300(_0xeb4411,_0x25f97d);}export*from'./user';export*from'./webapi';
|
|
25
src/core.lib/src/apis/msg.d.ts
vendored
25
src/core.lib/src/apis/msg.d.ts
vendored
@@ -1,25 +0,0 @@
|
|||||||
import { Peer, RawMessage, SendMessageElement } from '@/core/entities';
|
|
||||||
import { GeneralCallResult } from '@/core/services/common';
|
|
||||||
export declare class NTQQMsgApi {
|
|
||||||
static setEmojiLike(peer: Peer, msgSeq: string, emojiId: string, set?: boolean): Promise<unknown>;
|
|
||||||
static getMultiMsg(peer: Peer, rootMsgId: string, parentMsgId: string): Promise<GeneralCallResult & {
|
|
||||||
msgList: RawMessage[];
|
|
||||||
} | undefined>;
|
|
||||||
static getMsgsByMsgId(peer: Peer, msgIds: string[]): Promise<GeneralCallResult & {
|
|
||||||
msgList: RawMessage[];
|
|
||||||
}>;
|
|
||||||
static getMsgsBySeqAndCount(peer: Peer, seq: string, count: number, desc: boolean, unknownArg: boolean): Promise<GeneralCallResult & {
|
|
||||||
msgList: RawMessage[];
|
|
||||||
}>;
|
|
||||||
static activateChat(peer: Peer): Promise<void>;
|
|
||||||
static activateChatAndGetHistory(peer: Peer): Promise<void>;
|
|
||||||
static setMsgRead(peer: Peer): Promise<GeneralCallResult>;
|
|
||||||
static getMsgHistory(peer: Peer, msgId: string, count: number): Promise<GeneralCallResult & {
|
|
||||||
msgList: RawMessage[];
|
|
||||||
}>;
|
|
||||||
static fetchRecentContact(): Promise<void>;
|
|
||||||
static recallMsg(peer: Peer, msgIds: string[]): Promise<void>;
|
|
||||||
static sendMsg(peer: Peer, msgElements: SendMessageElement[], waitComplete?: boolean, timeout?: number): Promise<RawMessage>;
|
|
||||||
static forwardMsg(srcPeer: Peer, destPeer: Peer, msgIds: string[]): Promise<GeneralCallResult>;
|
|
||||||
static multiForwardMsg(srcPeer: Peer, destPeer: Peer, msgIds: string[]): Promise<RawMessage>;
|
|
||||||
}
|
|
File diff suppressed because one or more lines are too long
17
src/core.lib/src/apis/sign.d.ts
vendored
17
src/core.lib/src/apis/sign.d.ts
vendored
@@ -1,17 +0,0 @@
|
|||||||
export interface IdMusicSignPostData {
|
|
||||||
type: 'qq' | '163';
|
|
||||||
id: string | number;
|
|
||||||
}
|
|
||||||
export interface CustomMusicSignPostData {
|
|
||||||
type: 'custom';
|
|
||||||
url: string;
|
|
||||||
audio: string;
|
|
||||||
title: string;
|
|
||||||
image?: string;
|
|
||||||
singer?: string;
|
|
||||||
}
|
|
||||||
export declare class MusicSign {
|
|
||||||
private readonly url;
|
|
||||||
constructor(url: string);
|
|
||||||
sign(postData: CustomMusicSignPostData | IdMusicSignPostData): Promise<any>;
|
|
||||||
}
|
|
@@ -1 +0,0 @@
|
|||||||
var _0xf9ee2f=_0x1a53;function _0x1a53(_0x3901e0,_0x1b3559){var _0x30411d=_0x3041();return _0x1a53=function(_0x1a53f8,_0x27cf36){_0x1a53f8=_0x1a53f8-0x11b;var _0x590816=_0x30411d[_0x1a53f8];return _0x590816;},_0x1a53(_0x3901e0,_0x1b3559);}function _0x3041(){var _0x1c91aa=['application/json','json','1947612FUGSZi','1689758UvDRpj','url','392ZmOcmc','vVHnS','then','statusText','7316235TlDBqM','7448spIJaX','525628eDeiHn','fvEWg','stringify','835096bylraq','音乐消息生成成功','1027146RxGgmg','sign','20AbZWDO'];_0x3041=function(){return _0x1c91aa;};return _0x3041();}(function(_0x1469d4,_0x225d39){var _0x54c5ba=_0x1a53,_0xa7b16b=_0x1469d4();while(!![]){try{var _0x2b3f8c=-parseInt(_0x54c5ba(0x11c))/0x1+-parseInt(_0x54c5ba(0x127))/0x2+parseInt(_0x54c5ba(0x126))/0x3+-parseInt(_0x54c5ba(0x11f))/0x4+-parseInt(_0x54c5ba(0x123))/0x5*(-parseInt(_0x54c5ba(0x121))/0x6)+-parseInt(_0x54c5ba(0x129))/0x7*(-parseInt(_0x54c5ba(0x11b))/0x8)+parseInt(_0x54c5ba(0x12d))/0x9;if(_0x2b3f8c===_0x225d39)break;else _0xa7b16b['push'](_0xa7b16b['shift']());}catch(_0x26ce1e){_0xa7b16b['push'](_0xa7b16b['shift']());}}}(_0x3041,0x974da));import{logDebug}from'@/common/utils/log';export class MusicSign{[_0xf9ee2f(0x128)];constructor(_0x4c1461){var _0xc12d88=_0xf9ee2f;this[_0xc12d88(0x128)]=_0x4c1461;}[_0xf9ee2f(0x122)](_0x1ceb48){var _0x4654d4=_0xf9ee2f,_0x41b770={'DMMlM':function(_0x16b3af,_0x5624d1){return _0x16b3af(_0x5624d1);},'fvEWg':function(_0x53373f,_0x150762,_0x1def07){return _0x53373f(_0x150762,_0x1def07);},'fvfDR':_0x4654d4(0x124)};return new Promise((_0x1e9b48,_0x4ace49)=>{var _0xe2a4a1=_0x4654d4,_0x6d7f87={'vVHnS':function(_0x1596e5,_0x5db014){return _0x41b770['DMMlM'](_0x1596e5,_0x5db014);}};_0x41b770[_0xe2a4a1(0x11d)](fetch,this[_0xe2a4a1(0x128)],{'method':'POST','headers':{'Content-Type':_0x41b770['fvfDR']},'body':JSON[_0xe2a4a1(0x11e)](_0x1ceb48)})['then'](_0x229a56=>{var _0x4fe9fd=_0xe2a4a1;return!_0x229a56['ok']&&_0x6d7f87[_0x4fe9fd(0x12a)](_0x4ace49,_0x229a56[_0x4fe9fd(0x12c)]),_0x229a56[_0x4fe9fd(0x125)]();})[_0xe2a4a1(0x12b)](_0x1d12d7=>{var _0x5f5483=_0xe2a4a1;logDebug(_0x5f5483(0x120),_0x1d12d7),_0x6d7f87[_0x5f5483(0x12a)](_0x1e9b48,_0x1d12d7);})['catch'](_0x13ca27=>{_0x4ace49(_0x13ca27);});});}}
|
|
20
src/core.lib/src/apis/user.d.ts
vendored
20
src/core.lib/src/apis/user.d.ts
vendored
@@ -1,20 +0,0 @@
|
|||||||
import { User } from '@/core/entities';
|
|
||||||
import { GeneralCallResult } from '@/core';
|
|
||||||
export declare class NTQQUserApi {
|
|
||||||
static setSelfOnlineStatus(status: number, extStatus: number, batteryStatus: number): Promise<GeneralCallResult>;
|
|
||||||
static like(uid: string, count?: number): Promise<{
|
|
||||||
result: number;
|
|
||||||
errMsg: string;
|
|
||||||
succCounts: number;
|
|
||||||
}>;
|
|
||||||
static setQQAvatar(filePath: string): Promise<{
|
|
||||||
result: number;
|
|
||||||
errMsg: string;
|
|
||||||
}>;
|
|
||||||
static getSelfInfo(): Promise<void>;
|
|
||||||
static getUserInfo(uid: string): Promise<void>;
|
|
||||||
static getUserDetailInfo(uid: string): Promise<User>;
|
|
||||||
static getPSkey(domainList: string[], cached?: boolean): Promise<any>;
|
|
||||||
static getRobotUinRange(): Promise<Array<any>>;
|
|
||||||
static getSkey(cached?: boolean): Promise<string | undefined>;
|
|
||||||
}
|
|
File diff suppressed because one or more lines are too long
98
src/core.lib/src/apis/webapi.d.ts
vendored
98
src/core.lib/src/apis/webapi.d.ts
vendored
@@ -1,98 +0,0 @@
|
|||||||
export interface WebApiGroupMember {
|
|
||||||
uin: number;
|
|
||||||
role: number;
|
|
||||||
g: number;
|
|
||||||
join_time: number;
|
|
||||||
last_speak_time: number;
|
|
||||||
lv: {
|
|
||||||
point: number;
|
|
||||||
level: number;
|
|
||||||
};
|
|
||||||
card: string;
|
|
||||||
tags: string;
|
|
||||||
flag: number;
|
|
||||||
nick: string;
|
|
||||||
qage: number;
|
|
||||||
rm: number;
|
|
||||||
}
|
|
||||||
export interface WebApiGroupNoticeFeed {
|
|
||||||
u: number;
|
|
||||||
fid: string;
|
|
||||||
pubt: number;
|
|
||||||
msg: {
|
|
||||||
text: string;
|
|
||||||
text_face: string;
|
|
||||||
title: string;
|
|
||||||
pics?: {
|
|
||||||
id: string;
|
|
||||||
w: string;
|
|
||||||
h: string;
|
|
||||||
}[];
|
|
||||||
};
|
|
||||||
type: number;
|
|
||||||
fn: number;
|
|
||||||
cn: number;
|
|
||||||
vn: number;
|
|
||||||
settings: {
|
|
||||||
is_show_edit_card: number;
|
|
||||||
remind_ts: number;
|
|
||||||
tip_window_type: number;
|
|
||||||
confirm_required: number;
|
|
||||||
};
|
|
||||||
read_num: number;
|
|
||||||
is_read: number;
|
|
||||||
is_all_confirm: number;
|
|
||||||
}
|
|
||||||
export interface WebApiGroupNoticeRet {
|
|
||||||
ec: number;
|
|
||||||
em: string;
|
|
||||||
ltsm: number;
|
|
||||||
srv_code: number;
|
|
||||||
read_only: number;
|
|
||||||
role: number;
|
|
||||||
feeds: WebApiGroupNoticeFeed[];
|
|
||||||
group: {
|
|
||||||
group_id: number;
|
|
||||||
class_ext: number;
|
|
||||||
};
|
|
||||||
sta: number;
|
|
||||||
gln: number;
|
|
||||||
tst: number;
|
|
||||||
ui: any;
|
|
||||||
server_time: number;
|
|
||||||
svrt: number;
|
|
||||||
ad: number;
|
|
||||||
}
|
|
||||||
interface GroupEssenceMsg {
|
|
||||||
group_code: string;
|
|
||||||
msg_seq: number;
|
|
||||||
msg_random: number;
|
|
||||||
sender_uin: string;
|
|
||||||
sender_nick: string;
|
|
||||||
sender_time: number;
|
|
||||||
add_digest_uin: string;
|
|
||||||
add_digest_nick: string;
|
|
||||||
add_digest_time: number;
|
|
||||||
msg_content: any[];
|
|
||||||
can_be_removed: true;
|
|
||||||
}
|
|
||||||
export interface GroupEssenceMsgRet {
|
|
||||||
retcode: number;
|
|
||||||
retmsg: string;
|
|
||||||
data: {
|
|
||||||
msg_list: GroupEssenceMsg[];
|
|
||||||
is_end: boolean;
|
|
||||||
group_role: number;
|
|
||||||
config_page_url: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
export declare class WebApi {
|
|
||||||
static getGroupEssenceMsg(GroupCode: string, page_start: string): Promise<GroupEssenceMsgRet | undefined>;
|
|
||||||
static getGroupMembers(GroupCode: string, cached?: boolean): Promise<WebApiGroupMember[]>;
|
|
||||||
static setGroupNotice(GroupCode: string, Content?: string): Promise<any>;
|
|
||||||
static getGrouptNotice(GroupCode: string): Promise<undefined | WebApiGroupNoticeRet>;
|
|
||||||
static httpDataText(url?: string, method?: string, data?: string, CookiesValue?: string): Promise<string>;
|
|
||||||
static httpDataJson<T>(url?: string, method?: string, data?: string, CookiesValue?: string): Promise<T>;
|
|
||||||
static genBkn(sKey: string): string;
|
|
||||||
}
|
|
||||||
export {};
|
|
File diff suppressed because one or more lines are too long
11
src/core.lib/src/apis/window.d.ts
vendored
11
src/core.lib/src/apis/window.d.ts
vendored
@@ -1,11 +0,0 @@
|
|||||||
export interface NTQQWindow {
|
|
||||||
windowName: string;
|
|
||||||
windowUrlHash: string;
|
|
||||||
}
|
|
||||||
export declare class NTQQWindows {
|
|
||||||
static GroupHomeWorkWindow: NTQQWindow;
|
|
||||||
static GroupNotifyFilterWindow: NTQQWindow;
|
|
||||||
static GroupEssenceWindow: NTQQWindow;
|
|
||||||
}
|
|
||||||
export declare class NTQQWindowApi {
|
|
||||||
}
|
|
File diff suppressed because one or more lines are too long
36
src/core.lib/src/core.d.ts
vendored
36
src/core.lib/src/core.d.ts
vendored
@@ -1,36 +0,0 @@
|
|||||||
/// <reference types="node" />
|
|
||||||
import { NodeIQQNTWrapperSession, NodeQQNTWrapperUtil } from '@/core/wrapper';
|
|
||||||
import { QuickLoginResult } from '@/core/services';
|
|
||||||
import { BuddyListener, GroupListener, MsgListener, ProfileListener } from '@/core/listeners';
|
|
||||||
export interface OnLoginSuccess {
|
|
||||||
(uin: string, uid: string): void | Promise<void>;
|
|
||||||
}
|
|
||||||
export declare class NapCatCore {
|
|
||||||
readonly session: NodeIQQNTWrapperSession;
|
|
||||||
readonly util: NodeQQNTWrapperUtil;
|
|
||||||
private engine;
|
|
||||||
private loginService;
|
|
||||||
private readonly loginListener;
|
|
||||||
private onLoginSuccessFuncList;
|
|
||||||
private proxyHandler;
|
|
||||||
constructor();
|
|
||||||
get dataPath(): string;
|
|
||||||
get dataPathGlobal(): string;
|
|
||||||
private initConfig;
|
|
||||||
private initSession;
|
|
||||||
private initDataListener;
|
|
||||||
addListener(listener: BuddyListener | GroupListener | MsgListener | ProfileListener): number;
|
|
||||||
onLoginSuccess(func: OnLoginSuccess): void;
|
|
||||||
quickLogin(uin: string): Promise<QuickLoginResult>;
|
|
||||||
qrLogin(cb: (url: string, base64: string, buffer: Buffer) => Promise<void>): Promise<{
|
|
||||||
url: string;
|
|
||||||
base64: string;
|
|
||||||
buffer: Buffer;
|
|
||||||
}>;
|
|
||||||
passwordLogin(uin: string, password: string, proofSig?: string, proofRand?: string, proofSid?: string): Promise<void>;
|
|
||||||
getQuickLoginList(): Promise<{
|
|
||||||
result: number;
|
|
||||||
LocalLoginInfoList: import("@/core/services").LoginListItem[];
|
|
||||||
}>;
|
|
||||||
}
|
|
||||||
export declare const napCatCore: NapCatCore;
|
|
File diff suppressed because one or more lines are too long
41
src/core.lib/src/data.d.ts
vendored
41
src/core.lib/src/data.d.ts
vendored
@@ -1,41 +0,0 @@
|
|||||||
import { type Friend, type FriendRequest, type Group, type GroupMember, GroupNotify, type SelfInfo } from './entities';
|
|
||||||
import { WebApiGroupMember } from '@/core/apis';
|
|
||||||
export declare const Credentials: {
|
|
||||||
Skey: string;
|
|
||||||
CreatTime: number;
|
|
||||||
PskeyData: Map<string, string>;
|
|
||||||
PskeyTime: Map<string, number>;
|
|
||||||
};
|
|
||||||
export declare const WebGroupData: {
|
|
||||||
GroupData: Map<string, WebApiGroupMember[]>;
|
|
||||||
GroupTime: Map<string, number>;
|
|
||||||
};
|
|
||||||
export declare const selfInfo: SelfInfo;
|
|
||||||
export declare const groups: Map<string, Group>;
|
|
||||||
export declare function deleteGroup(groupQQ: string): void;
|
|
||||||
export declare const groupMembers: Map<string, Map<string, GroupMember>>;
|
|
||||||
export declare const friends: Map<string, Friend>;
|
|
||||||
export declare const friendRequests: Record<string, FriendRequest>;
|
|
||||||
export declare const groupNotifies: Record<string, GroupNotify>;
|
|
||||||
export declare const napCatError: {
|
|
||||||
ffmpegError: string;
|
|
||||||
httpServerError: string;
|
|
||||||
wsServerError: string;
|
|
||||||
otherError: string;
|
|
||||||
};
|
|
||||||
export declare function getFriend(uinOrUid: string): Promise<Friend | undefined>;
|
|
||||||
export declare function getGroup(qq: string | number): Promise<Group | undefined>;
|
|
||||||
export declare function getGroupMember(groupQQ: string | number, memberUinOrUid: string | number): Promise<GroupMember | null | undefined>;
|
|
||||||
export declare const uid2UinMap: Record<string, string>;
|
|
||||||
export declare function getUidByUin(uin: string): string | undefined;
|
|
||||||
export declare const tempGroupCodeMap: Record<string, string>;
|
|
||||||
export declare const stat: {
|
|
||||||
packet_received: number;
|
|
||||||
packet_sent: number;
|
|
||||||
message_received: number;
|
|
||||||
message_sent: number;
|
|
||||||
last_message_time: number;
|
|
||||||
disconnect_times: number;
|
|
||||||
lost_times: number;
|
|
||||||
packet_lost: number;
|
|
||||||
};
|
|
@@ -1 +0,0 @@
|
|||||||
const _0x474d1f=_0x3cc6;(function(_0x484c3c,_0x3e08b9){const _0x55a41e=_0x3cc6,_0x4b3d93=_0x484c3c();while(!![]){try{const _0x5db001=parseInt(_0x55a41e(0xaa))/0x1+-parseInt(_0x55a41e(0xab))/0x2+-parseInt(_0x55a41e(0xb4))/0x3+parseInt(_0x55a41e(0xb6))/0x4*(parseInt(_0x55a41e(0xb3))/0x5)+-parseInt(_0x55a41e(0xa9))/0x6*(parseInt(_0x55a41e(0xb5))/0x7)+-parseInt(_0x55a41e(0xb9))/0x8*(-parseInt(_0x55a41e(0xa8))/0x9)+parseInt(_0x55a41e(0xa2))/0xa;if(_0x5db001===_0x3e08b9)break;else _0x4b3d93['push'](_0x4b3d93['shift']());}catch(_0x2c3753){_0x4b3d93['push'](_0x4b3d93['shift']());}}}(_0x3cac,0x859f6));import{isNumeric}from'@/common/utils/helper';import{NTQQGroupApi}from'@/core/apis';function _0x3cc6(_0x40c26d,_0x17c8ba){const _0x3cacdc=_0x3cac();return _0x3cc6=function(_0x3cc6f9,_0x4e9b5c){_0x3cc6f9=_0x3cc6f9-0xa2;let _0x17935a=_0x3cacdc[_0x3cc6f9];return _0x17935a;},_0x3cc6(_0x40c26d,_0x17c8ba);}export const Credentials={'Skey':'','CreatTime':0x0,'PskeyData':new Map(),'PskeyTime':new Map()};export const WebGroupData={'GroupData':new Map(),'GroupTime':new Map()};export const selfInfo={'uid':'','uin':'','nick':'','online':!![]};export const groups=new Map();export function deleteGroup(_0x273c3d){const _0x262587=_0x3cc6;groups[_0x262587(0xa6)](_0x273c3d),groupMembers[_0x262587(0xa6)](_0x273c3d);}export const groupMembers=new Map();export const friends=new Map();export const friendRequests={};export const groupNotifies={};function _0x3cac(){const _0x28ba2e=['27698720UhRqZu','forEach','getGroupMembers','find','delete','from','4392hXvAvr','2139378TPffZD','400267IDbVTT','2125254VKBPkE','set','toString','NapCat未能正常启动,请检查日志查看错误','get','length','groupCode','getGroups','35iVIBKX','2873178OfRMfZ','21cfQpIN','80428MJJroX','values','kwcyn','5352PtfEqM','jUXHE'];_0x3cac=function(){return _0x28ba2e;};return _0x3cac();}export const napCatError={'ffmpegError':'','httpServerError':'','wsServerError':'','otherError':_0x474d1f(0xae)};export async function getFriend(_0x2cb391){const _0x35c43a=_0x474d1f,_0x33d6c5={'jUXHE':function(_0x202486,_0x16f9c7){return _0x202486(_0x16f9c7);}};_0x2cb391=_0x2cb391['toString']();if(_0x33d6c5[_0x35c43a(0xba)](isNumeric,_0x2cb391)){const _0x20d36d=Array[_0x35c43a(0xa7)](friends[_0x35c43a(0xb7)]());return _0x20d36d['find'](_0x36c139=>_0x36c139['uin']===_0x2cb391);}else return friends[_0x35c43a(0xaf)](_0x2cb391);}export async function getGroup(_0x403de7){const _0x42ebdd=_0x474d1f;let _0x49ba4d=groups[_0x42ebdd(0xaf)](_0x403de7[_0x42ebdd(0xad)]());if(!_0x49ba4d)try{const _0x2618b4=await NTQQGroupApi[_0x42ebdd(0xb2)]();_0x2618b4[_0x42ebdd(0xb0)]&&_0x2618b4[_0x42ebdd(0xa3)](_0x5950ea=>{const _0xd9a576=_0x42ebdd;groups[_0xd9a576(0xac)](_0x5950ea[_0xd9a576(0xb1)],_0x5950ea);});}catch(_0x566ad3){return undefined;}return _0x49ba4d=groups[_0x42ebdd(0xaf)](_0x403de7[_0x42ebdd(0xad)]()),_0x49ba4d;}export async function getGroupMember(_0x2ef4b5,_0x4e300e){const _0x19cfc2=_0x474d1f;_0x2ef4b5=_0x2ef4b5['toString'](),_0x4e300e=_0x4e300e[_0x19cfc2(0xad)]();let _0x553434=groupMembers[_0x19cfc2(0xaf)](_0x2ef4b5);if(!_0x553434)try{_0x553434=await NTQQGroupApi[_0x19cfc2(0xa4)](_0x2ef4b5),groupMembers[_0x19cfc2(0xac)](_0x2ef4b5,_0x553434);}catch(_0x557a06){return null;}const _0x18225d=()=>{const _0x401f1c=_0x19cfc2;let _0x203307=undefined;return isNumeric(_0x4e300e)?_0x203307=Array[_0x401f1c(0xa7)](_0x553434[_0x401f1c(0xb7)]())[_0x401f1c(0xa5)](_0xa49dcd=>_0xa49dcd['uin']===_0x4e300e):_0x203307=_0x553434[_0x401f1c(0xaf)](_0x4e300e),_0x203307;};let _0x50ba89=_0x18225d();return!_0x50ba89&&(_0x553434=await NTQQGroupApi[_0x19cfc2(0xa4)](_0x2ef4b5),_0x50ba89=_0x18225d()),_0x50ba89;}export const uid2UinMap={};export function getUidByUin(_0x1a604f){const _0xedf8ca=_0x474d1f,_0x4961e3={'kwcyn':function(_0x14a7b4,_0x3c4d98){return _0x14a7b4===_0x3c4d98;}};for(const _0x4b3e9b in uid2UinMap){if(_0x4961e3[_0xedf8ca(0xb8)](uid2UinMap[_0x4b3e9b],_0x1a604f))return _0x4b3e9b;}}export const tempGroupCodeMap={};export const stat={'packet_received':0x0,'packet_sent':0x0,'message_received':0x0,'message_sent':0x0,'last_message_time':0x0,'disconnect_times':0x0,'lost_times':0x0,'packet_lost':0x0};
|
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user