mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2025-07-19 12:03:37 +00:00
Compare commits
2207 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
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 | ||
![]() |
52c6927c44 | ||
![]() |
a16e0a21a2 | ||
![]() |
e796b21157 | ||
![]() |
1c6bc478b4 | ||
![]() |
98f39c6388 | ||
![]() |
570c83571b | ||
![]() |
c0c38d89e0 | ||
![]() |
b866cfc03c | ||
![]() |
28c2755b37 | ||
![]() |
57bfc5c73a | ||
![]() |
0f3f7d53a3 | ||
![]() |
529e50fd7f | ||
![]() |
2fa283f91d | ||
![]() |
029a9ade93 | ||
![]() |
f1ca8b15c8 | ||
![]() |
4d8edd5da9 | ||
![]() |
6c63990653 | ||
![]() |
5b521409c6 | ||
![]() |
3268fc1014 | ||
![]() |
19afb4941b | ||
![]() |
40e5111d41 | ||
![]() |
a3a40e1e74 | ||
![]() |
101caa6826 | ||
![]() |
875fed8d77 | ||
![]() |
69e28eb000 | ||
![]() |
e5d3a8360c | ||
![]() |
4545d9285b | ||
![]() |
6702024805 | ||
![]() |
78bad4842b | ||
![]() |
b9a913cfed | ||
![]() |
6f5a6f353f | ||
![]() |
790c4f589d | ||
![]() |
cd1bd3461f | ||
![]() |
0280dcd6a8 | ||
![]() |
fc337292bc | ||
![]() |
fb1daa0e21 | ||
![]() |
579b9dc0c2 | ||
![]() |
dedd0be352 | ||
![]() |
1c7d9c3513 | ||
![]() |
0c7dfe2af4 | ||
![]() |
8d1351a8a3 | ||
![]() |
e6e68a6036 | ||
![]() |
24658edc45 | ||
![]() |
09eaa3116a | ||
![]() |
e9bff466b5 | ||
![]() |
5d77f50160 | ||
![]() |
2ab91e363f | ||
![]() |
34d881426f | ||
![]() |
13ecaa0ad4 | ||
![]() |
ce6185b1f7 | ||
![]() |
2cfde6b75a | ||
![]() |
37d0354751 | ||
![]() |
0a0edcf203 | ||
![]() |
d6aad2ea28 | ||
![]() |
63084506ee | ||
![]() |
c5d313574f | ||
![]() |
caab998212 | ||
![]() |
aa037cc3d9 | ||
![]() |
642bffe374 | ||
![]() |
d682b154fc | ||
![]() |
d4a06d98cf | ||
![]() |
856b5e16b1 | ||
![]() |
a0aa208860 | ||
![]() |
037a11e04f | ||
![]() |
bd8a1d715f | ||
![]() |
54ab1dc091 | ||
![]() |
9471e63857 | ||
![]() |
fa4a403f38 | ||
![]() |
d608d65bf4 | ||
![]() |
c0f2df172a | ||
![]() |
788ef5d81c | ||
![]() |
1c6b5cffe1 | ||
![]() |
c04382b623 | ||
![]() |
0bbe51f8fd | ||
![]() |
ff7d7d15a0 | ||
![]() |
4b3d083d3a | ||
![]() |
a566dd390b | ||
![]() |
7d1442da04 | ||
![]() |
17fc982f55 | ||
![]() |
ba417e2274 | ||
![]() |
d345094b75 | ||
![]() |
6da477480d | ||
![]() |
e274088c06 | ||
![]() |
1bcaa73c5c | ||
![]() |
ca94e8f621 | ||
![]() |
1c4e198f59 | ||
![]() |
fdd13f9c66 | ||
![]() |
4333ab624e | ||
![]() |
9fe1eb3a42 | ||
![]() |
ad251a7682 | ||
![]() |
1fa740de2d | ||
![]() |
466b89064a | ||
![]() |
2748cb0ba3 | ||
![]() |
aef0d5bdde | ||
![]() |
c71e8f024a | ||
![]() |
9411f07321 | ||
![]() |
9b2a5c9bbf | ||
![]() |
2b275523a0 | ||
![]() |
31fe2f6da4 | ||
![]() |
f95db623a5 | ||
![]() |
a46313e483 | ||
![]() |
31c330826e | ||
![]() |
c4cf800142 | ||
![]() |
b64a2b0006 | ||
![]() |
a3702f2270 | ||
![]() |
d221b1d470 | ||
![]() |
0b22a6bc1d | ||
![]() |
07e8acd003 | ||
![]() |
9fce617c57 | ||
![]() |
8d5c736975 | ||
![]() |
4ccec05186 | ||
![]() |
a4f456f002 | ||
![]() |
fbdb941c27 | ||
![]() |
a41cd42e8d | ||
![]() |
77521e4627 | ||
![]() |
b6a1242bac | ||
![]() |
2f325cfe26 | ||
![]() |
193b0ad0f0 | ||
![]() |
ed476b7793 | ||
![]() |
720fd94b7f | ||
![]() |
ff87da105c | ||
![]() |
a875e65536 | ||
![]() |
0b2c6bb662 | ||
![]() |
e44e2fbbb7 | ||
![]() |
b3c93644fd | ||
![]() |
a56b7ff636 | ||
![]() |
c724236930 | ||
![]() |
4853320b2b | ||
![]() |
ba1acb6ac1 | ||
![]() |
f32a6320fc | ||
![]() |
9f914ce36a | ||
![]() |
b037644e5a | ||
![]() |
afd8c59f83 | ||
![]() |
8aa4af3e91 | ||
![]() |
630a8a2b97 | ||
![]() |
dc34c4d00c | ||
![]() |
fb42729dec | ||
![]() |
b06989216a | ||
![]() |
e5144f08cd | ||
![]() |
c4a60190e8 | ||
![]() |
efe9e4fa4c | ||
![]() |
45800b1559 | ||
![]() |
b0b2b8104f | ||
![]() |
8dbc012825 | ||
![]() |
a434176063 | ||
![]() |
a013f750c7 | ||
![]() |
aa1f49d02f | ||
![]() |
7125a26309 | ||
![]() |
329a35ebf0 | ||
![]() |
d30043f595 | ||
![]() |
745dfa1911 | ||
![]() |
76203f49a7 | ||
![]() |
870a915377 | ||
![]() |
c174fce227 | ||
![]() |
2b6e42e919 | ||
![]() |
df73e1e5a3 | ||
![]() |
3e902311d4 | ||
![]() |
64a0037265 | ||
![]() |
bcd4e38093 | ||
![]() |
181a77d627 | ||
![]() |
b353595ba9 | ||
![]() |
75e3bb4f17 | ||
![]() |
d2fa9192d4 | ||
![]() |
4bcadc2de4 | ||
![]() |
8ddff74260 | ||
![]() |
95940fdb64 | ||
![]() |
9cd5708948 | ||
![]() |
d361683d79 | ||
![]() |
9ad17a01f7 | ||
![]() |
22ca1d443c | ||
![]() |
2662e875ca | ||
![]() |
8ae0d07ec1 | ||
![]() |
76a9edb7f5 | ||
![]() |
0ccb464e5b | ||
![]() |
bef600efa2 | ||
![]() |
58a182cd33 | ||
![]() |
aa43334f41 | ||
![]() |
a2a4c97f6c | ||
![]() |
4217ba99fd | ||
![]() |
589725f5cc | ||
![]() |
3fea4602f8 | ||
![]() |
8ea6aae875 | ||
![]() |
2c70b2af68 | ||
![]() |
54a2cbcb42 | ||
![]() |
fdef821c60 | ||
![]() |
dfa798a35d | ||
![]() |
39b8eb6ff1 | ||
![]() |
6cf71f67a9 | ||
![]() |
f2e919725e | ||
![]() |
869599126e | ||
![]() |
3b1b200f6f | ||
![]() |
93c646e3e4 | ||
![]() |
3552f80a21 | ||
![]() |
66d3a63998 | ||
![]() |
6447825978 | ||
![]() |
18b7df9fca | ||
![]() |
c3781cab96 | ||
![]() |
776098dba6 | ||
![]() |
8d1b4f61e7 | ||
![]() |
c13e2bdb96 | ||
![]() |
4682254157 | ||
![]() |
d7ca6b9213 | ||
![]() |
4a76afbde8 | ||
![]() |
a68349c23a | ||
![]() |
920e005366 | ||
![]() |
659f339020 | ||
![]() |
3ee2d463af | ||
![]() |
686ddb5460 | ||
![]() |
e5d62488b7 | ||
![]() |
eb93dd5005 | ||
![]() |
6999d02d2d | ||
![]() |
790e2b1427 | ||
![]() |
a29c7cdfe4 | ||
![]() |
6b7cd692a6 | ||
![]() |
4d3925872a | ||
![]() |
2bd0f6934a | ||
![]() |
51783f17ed | ||
![]() |
ce3aef3526 | ||
![]() |
ee70afdfbb | ||
![]() |
d96c4a56a2 | ||
![]() |
9a39513dea | ||
![]() |
8f22d63315 | ||
![]() |
7f2a5bb95e | ||
![]() |
0118dbd5fb | ||
![]() |
09405de26c | ||
![]() |
efa5ee0e57 | ||
![]() |
80d558f37a | ||
![]() |
901adc3fc7 | ||
![]() |
01417be954 | ||
![]() |
43b780cbe6 | ||
![]() |
e83f36a12f | ||
![]() |
77e3fc4ab0 | ||
![]() |
eafd1adaba | ||
![]() |
6b53abb7c9 | ||
![]() |
f994c5d284 | ||
![]() |
6fda220107 | ||
![]() |
da290ed1c3 | ||
![]() |
7e9cd80a1c | ||
![]() |
379b7413d8 | ||
![]() |
9181a4df16 | ||
![]() |
df982afd51 | ||
![]() |
5c2c3b4317 | ||
![]() |
92d1309103 | ||
![]() |
c43ee3c1d6 | ||
![]() |
e0726e5283 | ||
![]() |
5f3775584b | ||
![]() |
77873d63c5 | ||
![]() |
9e6b09765e | ||
![]() |
1ad6ea4049 | ||
![]() |
7c41da1cb9 | ||
![]() |
adcf4bfc53 | ||
![]() |
7a6321a9c1 | ||
![]() |
d56b27a7b0 | ||
![]() |
ed7657ab5f | ||
![]() |
a414838416 | ||
![]() |
93646577dc | ||
![]() |
46db66038e | ||
![]() |
efc4e9ce56 | ||
![]() |
8d5eac7f80 | ||
![]() |
7b94e49b81 | ||
![]() |
c35fd4bdc8 | ||
![]() |
98590e2d90 | ||
![]() |
e6da0e5dd5 | ||
![]() |
cb2baf747d | ||
![]() |
a2f2eb03ce | ||
![]() |
5c6acbb780 | ||
![]() |
1be7031199 | ||
![]() |
ed6399bde9 | ||
![]() |
6709893781 | ||
![]() |
686a426cda | ||
![]() |
4f90bc7813 | ||
![]() |
e307b289ae | ||
![]() |
3baeff61a7 | ||
![]() |
93ab9d12ee | ||
![]() |
36e1317792 | ||
![]() |
fa3e90a021 | ||
![]() |
782a69cf13 | ||
![]() |
d495f351c0 | ||
![]() |
30bd3d2d52 | ||
![]() |
ff5a21cca5 | ||
![]() |
f8abb73c92 | ||
![]() |
e97f323d9a | ||
![]() |
3d27a4c05d | ||
![]() |
9dbc13dbe4 | ||
![]() |
c46a4c75b1 | ||
![]() |
0bded73f16 | ||
![]() |
1333733684 | ||
![]() |
003be934de | ||
![]() |
93ef20d358 | ||
![]() |
94e1a6f0ba | ||
![]() |
8661d09d57 | ||
![]() |
0e5e21dc4e | ||
![]() |
3b25c4987c | ||
![]() |
2212eb17aa | ||
![]() |
768bac1db8 | ||
![]() |
3aef75085f | ||
![]() |
ce8bef638a | ||
![]() |
f0a0c90304 | ||
![]() |
cd6c32b21d | ||
![]() |
b31876d2d1 | ||
![]() |
ebab8a190e | ||
![]() |
1b7ce8e7a5 | ||
![]() |
646bb6bd79 | ||
![]() |
5a84b97ca9 | ||
![]() |
6d41b5a4a1 | ||
![]() |
a8bce36f3b | ||
![]() |
ac2132f8ba | ||
![]() |
cab4b57abe | ||
![]() |
938fb30359 | ||
![]() |
62346d7d9d | ||
![]() |
cf1e5ca64b | ||
![]() |
7d2d683d96 | ||
![]() |
fe5042f1c3 | ||
![]() |
a1dd76aee0 | ||
![]() |
d1c91be167 | ||
![]() |
9748d99f34 | ||
![]() |
c90ffbeb62 | ||
![]() |
eb7fafeabf | ||
![]() |
3e50629462 | ||
![]() |
65281a4554 | ||
![]() |
454ec09d6a | ||
![]() |
60e3c6858d | ||
![]() |
f911f5b4fc | ||
![]() |
ad1694d291 | ||
![]() |
1130965f26 | ||
![]() |
fe1f28998b | ||
![]() |
45727fce05 | ||
![]() |
d5c23e5add | ||
![]() |
e3a8285f6c | ||
![]() |
a791221cf6 | ||
![]() |
b954d9b403 | ||
![]() |
5e7e24a271 | ||
![]() |
ffb1e598f6 | ||
![]() |
bc2da8a645 | ||
![]() |
6f2be3ed30 | ||
![]() |
033a7bffb3 | ||
![]() |
f2b2ea61a1 | ||
![]() |
6f0783acc4 | ||
![]() |
ce60aa3823 | ||
![]() |
8075e70606 | ||
![]() |
4402fc2d0a | ||
![]() |
3e3ecda551 | ||
![]() |
50beb8f346 | ||
![]() |
8e033e3e06 | ||
![]() |
dc029a318b | ||
![]() |
8e91bc2c8e | ||
![]() |
0ff5b4e90b | ||
![]() |
20dec19bfe | ||
![]() |
d261fbff26 | ||
![]() |
6594b33bcc | ||
![]() |
a1bb6cc1b1 | ||
![]() |
7ce195b68e | ||
![]() |
16d8d04aaa | ||
![]() |
59565f7d90 | ||
![]() |
43784a2495 | ||
![]() |
3811d7469e | ||
![]() |
c72b40a1e1 | ||
![]() |
f00933969d | ||
![]() |
759adc45e3 | ||
![]() |
27ecf78372 | ||
![]() |
c91b83a7ba | ||
![]() |
39373ee63a | ||
![]() |
2db64c69ae | ||
![]() |
a699b71c02 | ||
![]() |
6c07d22cda | ||
![]() |
a2ee900ed5 | ||
![]() |
e709f31b99 | ||
![]() |
35afb12756 | ||
![]() |
9bed9fe162 | ||
![]() |
4ff5553804 | ||
![]() |
32213be7a7 | ||
![]() |
84894a73e1 | ||
![]() |
b6ea185ce7 | ||
![]() |
814ac0f731 | ||
![]() |
a40bb29da3 | ||
![]() |
e9b90079c0 | ||
![]() |
dba383c27e | ||
![]() |
42059b5817 | ||
![]() |
f92a17c01b | ||
![]() |
d6552ce333 | ||
![]() |
0db89bde5a | ||
![]() |
56a12185d4 | ||
![]() |
c40170db5d | ||
![]() |
1df3e9c414 | ||
![]() |
b1570df8b9 | ||
![]() |
023fd1ce36 | ||
![]() |
a7fe74bc0c | ||
![]() |
26c9abd9da | ||
![]() |
a5e34645c5 | ||
![]() |
b2831c0a19 | ||
![]() |
648c1ea0f9 | ||
![]() |
9cd927e06a | ||
![]() |
4272413f55 | ||
![]() |
e1711b7af6 | ||
![]() |
f7d3f27d45 | ||
![]() |
3a7a47f82d | ||
![]() |
cc211706d5 | ||
![]() |
22f74be4cd | ||
![]() |
5a00d14f94 | ||
![]() |
ecb4e7bf9f | ||
![]() |
56e5b546e1 | ||
![]() |
272f5a2f4f |
@@ -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:
|
||||||
|
50
.github/workflows/build.yml
vendored
50
.github/workflows/build.yml
vendored
@@ -1,71 +1,61 @@
|
|||||||
name: "Build"
|
name: "Build Action"
|
||||||
on:
|
on:
|
||||||
push:
|
workflow_dispatch:
|
||||||
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,darwin]
|
|
||||||
target_arch: [x64, arm64]
|
|
||||||
steps:
|
steps:
|
||||||
- name: Clone Main Repository
|
- name: Clone Main Repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
repository: 'NapNeko/NapCatQQ'
|
repository: 'NapNeko/NapCatQQ'
|
||||||
submodules: true
|
submodules: true
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
ref: main
|
||||||
|
token: ${{ secrets.NAPCAT_BUILD }}
|
||||||
- name: Use Node.js 20.X
|
- name: Use Node.js 20.X
|
||||||
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
|
||||||
with:
|
with:
|
||||||
repository: 'NapNeko/NapCatQQ'
|
repository: 'NapNeko/NapCatQQ'
|
||||||
submodules: true
|
submodules: true
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
ref: main
|
||||||
|
token: ${{ secrets.NAPCAT_BUILD }}
|
||||||
- name: Use Node.js 20.X
|
- name: Use Node.js 20.X
|
||||||
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
|
||||||
|
72
.github/workflows/release.yml
vendored
72
.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,darwin]
|
|
||||||
target_arch: [x64, arm64]
|
|
||||||
steps:
|
steps:
|
||||||
- name: Clone Main Repository
|
- name: Clone Main Repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
@@ -51,26 +46,21 @@ jobs:
|
|||||||
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
|
||||||
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
|
||||||
@@ -85,46 +75,58 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
node-version: 20.x
|
node-version: 20.x
|
||||||
|
|
||||||
- name: Build NuCat Linux
|
- name: Build NuCat Shell
|
||||||
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
|
||||||
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: Download All Artifact
|
- name: Download All Artifact
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v4
|
||||||
|
# - name: Compress subdirectories
|
||||||
|
# run: |
|
||||||
|
# cd ./NapCat.Shell/
|
||||||
|
# zip -q -r NapCat.Shell.zip *
|
||||||
|
# cd ..
|
||||||
|
# rm ./NapCat.Shell.zip -rf
|
||||||
|
# mv ./NapCat.Shell/NapCat.Shell.zip ./
|
||||||
- name: Compress subdirectories
|
- name: Compress subdirectories
|
||||||
run: |
|
run: |
|
||||||
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 ./
|
||||||
- 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
|
||||||
|
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
|
||||||
with:
|
with:
|
||||||
name: NapCat V${{ env.VERSION }}
|
name: NapCat V${{ env.VERSION }}
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
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.darwin.x64.zip
|
|
||||||
# NapCat.darwin.arm64.zip
|
|
||||||
draft: true
|
draft: true
|
||||||
|
7
.gitignore
vendored
7
.gitignore
vendored
@@ -1,11 +1,11 @@
|
|||||||
# Logs
|
|
||||||
|
|
||||||
# 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/
|
||||||
|
/localdebug/
|
||||||
|
|
||||||
# Editor
|
# Editor
|
||||||
.vscode/*
|
.vscode/*
|
||||||
@@ -15,3 +15,4 @@ src/core.lib/common/
|
|||||||
# 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"
|
||||||
|
}
|
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.
|
||||||
|
178
README.md
178
README.md
@@ -2,166 +2,42 @@
|
|||||||
<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?description=1&language=1&logo=https%3A%2F%2Fraw.githubusercontent.com%2FNapNeko%2FNapCatQQ%2Fmain%2Flogo.png&name=1&stargazers=1&theme=Auto" alt="NapCatQQ" width="640" height="320" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
## 项目介绍
|
---
|
||||||
|
## 欢迎回来
|
||||||
|
NapCatQQ (aka 猫猫框架) 是现代化的基于 NTQQ 的 Bot 协议端实现。
|
||||||
|
|
||||||
NapCatQQ(瞌睡猫QQ,不准叫我NCQQ!),像睡着了一样在后台低占用运行的无头(没有界面)的NTQQ
|
## 猫猫技能
|
||||||
|
- [x] **高性能**:1K+ 群聊数目、20 线程并行发送消息毫无压力
|
||||||
|
- [x] **多种启动方式**:支持以无头、LiteLoader 插件、仅 QQ GUI 三种方式启动
|
||||||
|
- [x] **多平台支持**: 覆盖 Windows / Linux (可选 Docker) / Android Termux / MacOS
|
||||||
|
- [x] **安装简单**: 支持一键脚本/程序自动部署/镜像部署等多种覆盖范围
|
||||||
|
- [x] **低占用**:无头模式占用资源极低,适合在服务器上运行
|
||||||
|
- [x] **超多接口**:实现大部分 OneBot 和 go-cqhttp 接口,超多扩展 API
|
||||||
|
- [x] **WebUI**:自带 WebUI 支持,远程管理更加便捷
|
||||||
|
- [x] **低故障率**:快速适配最新版本,日常保证 0 Issue
|
||||||
|
|
||||||
目前测试在 Windows 上表现优秀,最低可达只占用内存 **20M**左右
|
## 使用猫猫
|
||||||
|
|
||||||
由于 Linux 上的 QQ 图形依赖较多,会导致内存占用小高,大约 **100+M**,目前正在研究如何优化
|
可前往 [Release](https://github.com/NapNeko/NapCatQQ/releases/) 页面下载最新版本
|
||||||
|
|
||||||
具体占用会因人而异,QQ 群、好友越多占用越高
|
**首次使用**请务必前往[官方文档](https://napneko.github.io/)查看使用教程。
|
||||||
|
|
||||||
## 下载
|
## 回家旅途
|
||||||
|
[QQ Group](https://qm.qq.com/q/VfjAq5HIMS)
|
||||||
|
|
||||||
前往 Release 页面下载最新版本
|
[Telegram Link](https://t.me/+nLZEnpne-pQ1OWFl)
|
||||||
|
|
||||||
## 启动
|
## 猫猫朋友
|
||||||
|
感谢 [LLOneBot](https://github.com/LLOneBot/LLOneBot) 提供部分参考
|
||||||
|
|
||||||
NapCat 是基于 官方NTQQ 实现的Bot框架,因此先需要安装官方QQ,**注意同个账号不能同时登录原版 QQ 和 NapCatQQ**
|
感谢 [Lagrange](https://github.com/LagrangeDev/Lagrange.Core) 对本项目的大力支持
|
||||||
|
|
||||||
*如果没有安装 QQ 请往后翻查看安装方法*
|
不过最最重要的 还是需要感谢屏幕前的你哦~
|
||||||
|
|
||||||
修改 `config/onebot11.json`内容,并重名为 `onebot11_<你的QQ号>.json`,如`onebot11_1234567.json`
|
---
|
||||||
|
|
||||||
json 配置内容参数解释:
|
## 约法三章
|
||||||
|
> [!CAUTION]\
|
||||||
|
> **请不要在 QQ 官方群聊和任何影响力较大的简中互联网平台(包括但不限于: 哔哩哔哩,微博,知乎,抖音等)发布和讨论*任何*与本项目存在相关性的信息**
|
||||||
|
|
||||||
```json5
|
任何使用本仓库代码的地方,都应当严格遵守[本仓库开源许可](./LICENSE)。**此外,禁止任何项目未经授权二次分发或基于 NapCat 代码开发。**
|
||||||
{
|
|
||||||
// 是否启用http服务, true为启动,false为禁用,如果启用,可以通过http接口发送消息
|
|
||||||
"enableHttp": false,
|
|
||||||
// http服务端口
|
|
||||||
"httpPort": 3000,
|
|
||||||
// 是否启用正向websocket服务
|
|
||||||
"enableWs": false,
|
|
||||||
// 正向websocket服务端口
|
|
||||||
"wsPort": 3001,
|
|
||||||
// 是否启用反向websocket服务
|
|
||||||
"enableWsReverse": false,
|
|
||||||
// 反向websocket对接的地址, 如["ws://127.0.0.1:8080/onebot/v11/ws"]
|
|
||||||
"wsReverseUrls": [],
|
|
||||||
// 是否启用http上报服务
|
|
||||||
"enableHttpPost": false,
|
|
||||||
// http上报地址, 如["http://127.0.0.1:8080/onebot/v11/http"]
|
|
||||||
"httpPostUrls": [],
|
|
||||||
// 是否启用http心跳
|
|
||||||
"enableHttpHeart": false,
|
|
||||||
// http上报密钥,可为空
|
|
||||||
"httpSecret": "",
|
|
||||||
// 消息上报格式,array为消息组,string为cq码字符串
|
|
||||||
"messagePostFormat": "array",
|
|
||||||
// 是否上报自己发送的消息
|
|
||||||
"reportSelfMessage": false,
|
|
||||||
// 是否开启调试模式,开启后上报消息会携带一个raw字段,为原始消息内容
|
|
||||||
"debug": false,
|
|
||||||
// 调用get_file接口时如果获取不到url则使用base64字段返回文件内容
|
|
||||||
"enableLocalFile2Url": true,
|
|
||||||
// ws心跳间隔,单位毫秒
|
|
||||||
"heartInterval": 30000,
|
|
||||||
// access_token,可以为空
|
|
||||||
"token": ""
|
|
||||||
}
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
### Windows 启动
|
|
||||||
|
|
||||||
运行`powershell ./napcat.ps1`, 或者 `napcat.bat`,如果出现乱码,可以尝试运行`napcat-utf8.ps1` 或 `napcat-utf8.bat`
|
|
||||||
|
|
||||||
*如果出现 powershell 运行不了,以管理员身份打开 powershell,输入 `Set-ExecutionPolicy RemoteSigned`*
|
|
||||||
|
|
||||||
### Linux 启动
|
|
||||||
|
|
||||||
运行`napcat.sh`
|
|
||||||
|
|
||||||
## 使用无需扫码快速登录
|
|
||||||
|
|
||||||
前提是你已经成功登录过QQ,可以加参数` -q <你的QQ>` 进行登录,如`napcat.sh -q 1234567`
|
|
||||||
|
|
||||||
## 安装
|
|
||||||
|
|
||||||
### Linux安装
|
|
||||||
|
|
||||||
#### 安装 Linux QQ(22741),已经安装了的可以跳过
|
|
||||||
|
|
||||||
目前还在研究怎么精简安装,暂时只能安装官方QQ整体依赖
|
|
||||||
|
|
||||||
下载QQ
|
|
||||||
|
|
||||||
[deb x86版本](https://dldir1.qq.com/qqfile/qq/QQNT/Linux/QQ_3.2.7_240403_amd64_01.deb)
|
|
||||||
[deb arm版本](https://dldir1.qq.com/qqfile/qq/QQNT/Linux/QQ_3.2.7_240403_arm64_01.deb)
|
|
||||||
|
|
||||||
[rpm x86版本](https://dldir1.qq.com/qqfile/qq/QQNT/Linux/QQ_3.2.7_240403_x86_64_01.rpm)
|
|
||||||
[rpm arm版本](https://dldir1.qq.com/qqfile/qq/QQNT/Linux/QQ_3.2.7_240403_aarch64_01.rpm)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo apt install ./qq.deb
|
|
||||||
```
|
|
||||||
|
|
||||||
```bash
|
|
||||||
安装QQ的依赖
|
|
||||||
sudo apt install libgbm1 libasound2
|
|
||||||
```
|
|
||||||
|
|
||||||
### Windows 安装
|
|
||||||
|
|
||||||
#### 安装Windows QQ(22741),已经安装了的可以跳过
|
|
||||||
|
|
||||||
[Windows版本QQ下载](https://dldir1.qq.com/qqfile/qq/QQNT/Windows/QQ_9.9.9_240403_x64_01.exe)
|
|
||||||
|
|
||||||
### 编译安装 NapCat
|
|
||||||
|
|
||||||
**如果你是直接下载编译好的版本,可以跳过这一步**
|
|
||||||
|
|
||||||
准备环境 [node18.18](https://nodejs.org/download/release/v18.18.2/)
|
|
||||||
|
|
||||||
```
|
|
||||||
export NODE_ENV=production
|
|
||||||
npm install
|
|
||||||
```
|
|
||||||
|
|
||||||
## 常见问题
|
|
||||||
|
|
||||||
### 二维码无法扫描
|
|
||||||
|
|
||||||
NapCat 会自动保存二维码到目录,可以手动打开图片扫描
|
|
||||||
|
|
||||||
如果没有条件访问本地目录,可以将二维码解析的 url 复制到二维码生成网站上生成二维码,然后手机QQ扫描
|
|
||||||
|
|
||||||
### 语音、视频发送失败
|
|
||||||
|
|
||||||
需要配置 ffmpeg,将 ffmpeg 目录加入环境变量,如果仍未生效,可以修改 napcat 启动脚本加入 FFMPEG_PATH 变量指定到 ffmpeg
|
|
||||||
程序的完整路径
|
|
||||||
|
|
||||||
如 Windows 上修改 napcat.ps1,在第一行加入
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
$env:FFMPEG_PATH="d:\ffmpeg\bin\ffmpeg.exe"
|
|
||||||
```
|
|
||||||
|
|
||||||
### 出现 error code v2:-1 之类的提示
|
|
||||||
|
|
||||||
不用管,这是正常现象,是因为 QQ 本身的问题,不影响使用
|
|
||||||
|
|
||||||
## API 文档
|
|
||||||
|
|
||||||
参考 [LLOneBot](https://llonebot.github.io/zh-CN/develop/api) 的文档
|
|
||||||
|
|
||||||
<!--
|
|
||||||
QQ群:545402644
|
|
||||||
-->
|
|
||||||
|
|
||||||
## 声明
|
|
||||||
|
|
||||||
* 请不要在无关地方宣传NapCatQQ,本项目只是用于学习 node 相关知识,切勿用于违法用途
|
|
||||||
|
|
||||||
* NapCat 不会收集用户隐私信息,但是未来可能会为了更好的利于 NapCat 的优化会收集一些设备信息,如 cpu 架构,系统版本等
|
|
||||||
|
|
||||||
## 相关链接
|
|
||||||
|
|
||||||
[TG群](https://t.me/+nLZEnpne-pQ1OWFl)
|
|
||||||
|
|
||||||
## 鸣谢名单
|
|
||||||
[OpenShamrock]()
|
|
||||||
|
|
||||||
[Lagrange]()
|
|
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'));
|
||||||
|
})();
|
23
launcher/qqnt.json
Normal file
23
launcher/qqnt.json
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"name": "qq-chat",
|
||||||
|
"version": "9.9.15-28060",
|
||||||
|
"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": "28060",
|
||||||
|
"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": "2.6.6",
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
}
|
50
package.json
50
package.json
@@ -2,44 +2,31 @@
|
|||||||
"name": "napcat",
|
"name": "napcat",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "1.1.0",
|
"version": "2.6.6",
|
||||||
"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:webui": "cd ./src/webui && vite build",
|
||||||
"build:prod": "vite build --mode production",
|
|
||||||
"build": "npm run build:dev",
|
|
||||||
"build:core": "cd ./src/core && vite build --mode production",
|
|
||||||
"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",
|
"@protobuf-ts/plugin": "^2.9.4",
|
||||||
"@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": "^4.17.21",
|
||||||
"@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",
|
||||||
@@ -47,19 +34,22 @@
|
|||||||
"vite-tsconfig-paths": "^4.3.2"
|
"vite-tsconfig-paths": "^4.3.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"commander": "^12.0.0",
|
"ajv": "^8.13.0",
|
||||||
|
"async-mutex": "^0.5.0",
|
||||||
|
"chalk": "^5.3.0",
|
||||||
|
"commander": "^12.1.0",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"express": "^5.0.0-beta.2",
|
"express": "^5.0.0-beta.2",
|
||||||
|
"fast-xml-parser": "^4.3.6",
|
||||||
"file-type": "^19.0.0",
|
"file-type": "^19.0.0",
|
||||||
"fluent-ffmpeg": "^2.1.2",
|
"fluent-ffmpeg": "^2.1.2",
|
||||||
"image-size": "^1.1.1",
|
"image-size": "^1.1.1",
|
||||||
|
"json-schema-to-ts": "^3.1.0",
|
||||||
"log4js": "^6.9.1",
|
"log4js": "^6.9.1",
|
||||||
"protobufjs": "^7.2.6",
|
"protobufjs": "~7.4.0",
|
||||||
"qrcode-terminal": "^0.12.0",
|
"qrcode-terminal": "^0.12.0",
|
||||||
"silk-wasm": "^3.3.4",
|
"silk-wasm": "^3.6.1",
|
||||||
"sqlite3": "^5.1.7",
|
"strtok3": "8.0.1",
|
||||||
"uuid": "^9.0.1",
|
"ws": "^8.18.0"
|
||||||
"ws": "^8.16.0",
|
|
||||||
"yaml": "^2.4.1"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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.nanaeo.cn\"\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,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,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,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('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(`[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(`[Core] [Config] 配置文件格式错误,请检查配置文件:`, e.message);
|
||||||
|
} else {
|
||||||
|
logger.logError(`[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(`保存配置文件 ${configPath} 时发生错误:`, e.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,90 +0,0 @@
|
|||||||
import {
|
|
||||||
type Friend,
|
|
||||||
type FriendRequest,
|
|
||||||
type Group,
|
|
||||||
type GroupMember, GroupNotify,
|
|
||||||
type SelfInfo
|
|
||||||
} from '@/core/qqnt/entities';
|
|
||||||
import { isNumeric } from './utils/helper';
|
|
||||||
import { log } from '@/common/utils/log';
|
|
||||||
|
|
||||||
export const selfInfo: SelfInfo = {
|
|
||||||
uid: '',
|
|
||||||
uin: '',
|
|
||||||
nick: '',
|
|
||||||
online: true
|
|
||||||
};
|
|
||||||
|
|
||||||
// groupCode -> Group
|
|
||||||
export const groups: Map<string, Group> = new Map<string, Group>();
|
|
||||||
|
|
||||||
export function deleteGroup(groupQQ: string) {
|
|
||||||
groups.delete(groupQQ);
|
|
||||||
groupMembers.delete(groupQQ);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 群号 -> 群成员map(uid=>GroupMember)
|
|
||||||
export const groupMembers: Map<string, Map<string, GroupMember>> = new Map<string, Map<string, GroupMember>>();
|
|
||||||
|
|
||||||
// uid -> Friend
|
|
||||||
export const friends: Map<string, Friend> = new Map<string, Friend>();
|
|
||||||
|
|
||||||
export const friendRequests: Record<string, FriendRequest> = {}; // flag->FriendRequest
|
|
||||||
export const groupNotifies: Record<string, GroupNotify> = {}; // flag->GroupNotify
|
|
||||||
|
|
||||||
export const napCatError = {
|
|
||||||
ffmpegError: '',
|
|
||||||
httpServerError: '',
|
|
||||||
wsServerError: '',
|
|
||||||
otherError: 'NapCat未能正常启动,请检查日志查看错误'
|
|
||||||
};
|
|
||||||
|
|
||||||
export async function getFriend(uinOrUid: string): Promise<Friend | undefined> {
|
|
||||||
uinOrUid = uinOrUid.toString();
|
|
||||||
if (isNumeric(uinOrUid)) {
|
|
||||||
const friendList = Array.from(friends.values());
|
|
||||||
return friendList.find(friend => friend.uin === uinOrUid);
|
|
||||||
} else {
|
|
||||||
return friends.get(uinOrUid);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getGroup(qq: string): Promise<Group | undefined> {
|
|
||||||
const group = groups.get(qq.toString());
|
|
||||||
return group;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getGroupMember(groupQQ: string | number, memberUinOrUid: string | number) {
|
|
||||||
groupQQ = groupQQ.toString();
|
|
||||||
memberUinOrUid = memberUinOrUid.toString();
|
|
||||||
const members = groupMembers.get(groupQQ);
|
|
||||||
if (!members) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
// log('getGroupMember', members);
|
|
||||||
if (isNumeric(memberUinOrUid)) {
|
|
||||||
return Array.from(members.values()).find(member => member.uin === memberUinOrUid);
|
|
||||||
} else {
|
|
||||||
return members.get(memberUinOrUid);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function refreshGroupMembers(groupQQ: string) {
|
|
||||||
// const group = groups.find(group => group.groupCode === groupQQ)
|
|
||||||
// if (group) {
|
|
||||||
// group.members = await NTQQGroupApi.getGroupMembers(groupQQ)
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
export const uid2UinMap: Record<string, string> = {}; // 一串加密的字符串(uid) -> qq号
|
|
||||||
|
|
||||||
export function getUidByUin(uin: string) {
|
|
||||||
for (const uid in uid2UinMap) {
|
|
||||||
if (uid2UinMap[uid] === uin) {
|
|
||||||
return uid;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const tempGroupCodeMap: Record<string, string> = {}; // peerUid => 群号
|
|
||||||
|
|
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>]>(
|
||||||
|
(resolve, reject) => {
|
||||||
|
const id = randomUUID();
|
||||||
|
let complete = 0;
|
||||||
|
let retData: Parameters<ListenerType> | undefined = undefined;
|
||||||
|
const 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);
|
||||||
|
if (eventFunction) eventFunction(...(args)).then((retEvent: Awaited<ReturnType<EventType>>) => {
|
||||||
|
if (!checkerEvent(retEvent) && timeoutRef.hasRef()) {
|
||||||
|
clearTimeout(timeoutRef);
|
||||||
|
reject(
|
||||||
|
new Error(
|
||||||
|
'EventChecker Failed: NTEvent serviceAndMethod:' +
|
||||||
|
serviceAndMethod +
|
||||||
|
' ListenerName:' +
|
||||||
|
listenerAndMethod +
|
||||||
|
' EventRet:\n' +
|
||||||
|
JSON.stringify(retEvent, null, 4) +
|
||||||
|
'\n',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
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: '' };
|
||||||
|
}
|
237
src/common/helper.ts
Normal file
237
src/common/helper.ts
Normal file
@@ -0,0 +1,237 @@
|
|||||||
|
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, endString: string = ""): string {
|
||||||
|
const data = `NapCatOneBot|ModelIdFile|${peer.chatType}|${peer.peerUid}|${modelId}|${fileId}`;
|
||||||
|
//前四个字节塞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
|
||||||
|
} {
|
||||||
|
//前四个字节是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;
|
||||||
|
const [, , chatType, peerUid, modelId, fileId] = data;
|
||||||
|
return {
|
||||||
|
peer: {
|
||||||
|
chatType: chatType as any,
|
||||||
|
peerUid: peerUid,
|
||||||
|
},
|
||||||
|
modelId,
|
||||||
|
fileId
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static encode(peer: Peer, msgId: string, elementId: string, endString: string = ""): string {
|
||||||
|
const data = `NapCatOneBot|MsgFile|${peer.chatType}|${peer.peerUid}|${msgId}|${elementId}`;
|
||||||
|
//前四个字节塞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
|
||||||
|
} {
|
||||||
|
//前四个字节是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;
|
||||||
|
const [, , chatType, peerUid, msgId, elementId] = data;
|
||||||
|
return {
|
||||||
|
peer: {
|
||||||
|
chatType: chatType as any,
|
||||||
|
peerUid: peerUid,
|
||||||
|
},
|
||||||
|
msgId,
|
||||||
|
elementId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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-28060',
|
||||||
|
curVersion: '9.9.15-28060',
|
||||||
|
prevVersion: '',
|
||||||
|
onErrorVersions: [],
|
||||||
|
buildId: '28060',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
240
src/common/log.ts
Normal file
240
src/common/log.ts
Normal file
@@ -0,0 +1,240 @@
|
|||||||
|
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;
|
||||||
|
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) {
|
||||||
|
tokens.push(`群聊 (群 ${msg.peerUin} 的 ${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' // 非转发消息; 否则定位不到
|
||||||
|
?
|
||||||
|
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(' ');
|
||||||
|
}
|
31
src/common/lru-cache.ts
Normal file
31
src/common/lru-cache.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
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;
|
||||||
|
this.cache.delete(firstKey);
|
||||||
|
}
|
||||||
|
this.cache.set(key, value);
|
||||||
|
}
|
||||||
|
}
|
136
src/common/message-unique.ts
Normal file
136
src/common/message-unique.ts
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
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;
|
||||||
|
this.valueToKey.delete(this.keyToValue.get(oldestKey)!);
|
||||||
|
this.keyToValue.delete(oldestKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getValue(key: K): V | undefined {
|
||||||
|
return this.keyToValue.get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
getKey(value: V): K | undefined {
|
||||||
|
return this.valueToKey.get(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteByValue(value: V): void {
|
||||||
|
const key = this.valueToKey.get(value);
|
||||||
|
if (key !== undefined) {
|
||||||
|
this.keyToValue.delete(key);
|
||||||
|
this.valueToKey.delete(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteByKey(key: K): void {
|
||||||
|
const value = this.keyToValue.get(key);
|
||||||
|
if (value !== undefined) {
|
||||||
|
this.keyToValue.delete(key);
|
||||||
|
this.valueToKey.delete(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getKeyList(): K[] {
|
||||||
|
return Array.from(this.keyToValue.keys());
|
||||||
|
}
|
||||||
|
|
||||||
|
//获取最近刚写入的几个值
|
||||||
|
getHeads(size: number): { key: K; value: V }[] | undefined {
|
||||||
|
const keyList = this.getKeyList();
|
||||||
|
if (keyList.length === 0) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const result: { key: K; value: V }[] = [];
|
||||||
|
const listSize = Math.min(size, keyList.length);
|
||||||
|
for (let i = 0; i < listSize; i++) {
|
||||||
|
const key = keyList[listSize - i];
|
||||||
|
result.push({ key, value: this.keyToValue.get(key)! });
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MessageUniqueWrapper {
|
||||||
|
private msgDataMap: LimitedHashTable<string, number>;
|
||||||
|
private msgIdMap: LimitedHashTable<string, number>;
|
||||||
|
|
||||||
|
constructor(maxMap: number = 1000) {
|
||||||
|
this.msgIdMap = new LimitedHashTable<string, number>(maxMap);
|
||||||
|
this.msgDataMap = new LimitedHashTable<string, number>(maxMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
getRecentMsgIds(Peer: Peer, size: number): string[] {
|
||||||
|
const heads = this.msgIdMap.getHeads(size);
|
||||||
|
if (!heads) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const data = heads.map((t) => MessageUnique.getMsgIdAndPeerByShortId(t.value));
|
||||||
|
const ret = data.filter((t) => t?.Peer.chatType === Peer.chatType && t?.Peer.peerUid === Peer.peerUid);
|
||||||
|
return ret.map((t) => t?.MsgId).filter((t) => t !== undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
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));
|
||||||
|
}
|
93
src/common/qq-basic-info.ts
Normal file
93
src/common/qq-basic-info.ts
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
//此方法不要直接使用
|
||||||
|
getQUAInternal() {
|
||||||
|
switch (systemPlatform) {
|
||||||
|
case 'linux':
|
||||||
|
return `V1_LNX_${this.getFullQQVesion()}_${this.getQQBuildStr()}_GW_B`;
|
||||||
|
case 'darwin':
|
||||||
|
return `V1_MAC_${this.getFullQQVesion()}_${this.getQQBuildStr()}_GW_B`;
|
||||||
|
default:
|
||||||
|
return `V1_WIN_${this.getFullQQVesion()}_${this.getQQBuildStr()}_GW_B`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getAppidInternal() {
|
||||||
|
switch (systemPlatform) {
|
||||||
|
case 'linux':
|
||||||
|
return '537246140';
|
||||||
|
case 'darwin':
|
||||||
|
return '537246140';
|
||||||
|
default:
|
||||||
|
return '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.getAppidInternal(), qua: this.getQUAInternal() };
|
||||||
|
}
|
||||||
|
}
|
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.href,
|
||||||
|
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,117 +0,0 @@
|
|||||||
import express, { Express, Request, Response } from 'express';
|
|
||||||
import cors from 'cors';
|
|
||||||
import http from 'http';
|
|
||||||
import { log } from '../utils/log';
|
|
||||||
import { ob11Config } from '@/onebot11/config';
|
|
||||||
|
|
||||||
type RegisterHandler = (res: Response, payload: any) => Promise<any>
|
|
||||||
|
|
||||||
export abstract class HttpServerBase {
|
|
||||||
name: string = 'LLOneBot';
|
|
||||||
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) {
|
|
||||||
log('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() || '';
|
|
||||||
log('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();
|
|
||||||
}
|
|
||||||
log('receive http url token', clientToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (serverToken && clientToken != serverToken) {
|
|
||||||
return res.status(403).send(JSON.stringify({ message: 'token verify failed!' }));
|
|
||||||
}
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
|
|
||||||
start(port: number) {
|
|
||||||
try {
|
|
||||||
this.expressAPP.get('/', (req: Request, res: Response) => {
|
|
||||||
res.send(`${this.name}已启动`);
|
|
||||||
});
|
|
||||||
this.listen(port);
|
|
||||||
} catch (e: any) {
|
|
||||||
log('HTTP服务启动失败', e.toString());
|
|
||||||
// llonebotError.httpServerError = "HTTP服务启动失败, " + e.toString()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
stop() {
|
|
||||||
// llonebotError.httpServerError = ""
|
|
||||||
if (this.server) {
|
|
||||||
this.server.close();
|
|
||||||
this.server = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
restart(port: number) {
|
|
||||||
this.stop();
|
|
||||||
this.start(port);
|
|
||||||
}
|
|
||||||
|
|
||||||
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`;
|
|
||||||
log(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 };
|
|
||||||
}
|
|
||||||
log('收到http请求', url, payload);
|
|
||||||
try {
|
|
||||||
res.send(await handler(res, payload));
|
|
||||||
} catch (e: any) {
|
|
||||||
this.handleFailed(res, payload, e.stack.toString());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
protected listen(port: number) {
|
|
||||||
this.server = this.expressAPP.listen(port, '0.0.0.0', () => {
|
|
||||||
const info = `${this.name} started 0.0.0.0:${port}`;
|
|
||||||
log(info);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,102 +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) {
|
|
||||||
try {
|
|
||||||
this.ws = new WebSocketServer({
|
|
||||||
port ,
|
|
||||||
maxPayload: 1024 * 1024 * 1024
|
|
||||||
});
|
|
||||||
} catch (e: any) {
|
|
||||||
throw Error('ws服务启动失败, ' + 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() {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
73
src/common/system.ts
Normal file
73
src/common/system.ts
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
import os from 'node:os';
|
||||||
|
import path from 'node:path';
|
||||||
|
import { networkInterfaces } from 'os';
|
||||||
|
import { randomUUID } from 'crypto';
|
||||||
|
|
||||||
|
// 缓解Win7设备兼容性问题
|
||||||
|
let osName: string;
|
||||||
|
// 设备ID
|
||||||
|
let machineId: Promise<string>;
|
||||||
|
|
||||||
|
try {
|
||||||
|
osName = os.hostname();
|
||||||
|
} catch (e) {
|
||||||
|
osName = 'NapCat'; // + crypto.randomUUID().substring(0, 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
const invalidMacAddresses = new Set([
|
||||||
|
'00:00:00:00:00:00',
|
||||||
|
'ff:ff:ff:ff:ff:ff',
|
||||||
|
'ac:de:48:00:11:22',
|
||||||
|
]);
|
||||||
|
|
||||||
|
function validateMacAddress(candidate: string): boolean {
|
||||||
|
// eslint-disable-next-line no-useless-escape
|
||||||
|
const tempCandidate = candidate.replace(/\-/g, ':').toLowerCase();
|
||||||
|
return !invalidMacAddresses.has(tempCandidate);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getMachineId(): Promise<string> {
|
||||||
|
if (!machineId) {
|
||||||
|
machineId = (async () => {
|
||||||
|
const id = await getMacMachineId();
|
||||||
|
return id ?? randomUUID(); // fallback, generate a UUID
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
|
return machineId;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getMac(): string {
|
||||||
|
const ifaces = networkInterfaces();
|
||||||
|
for (const name in ifaces) {
|
||||||
|
const networkInterface = ifaces[name];
|
||||||
|
if (networkInterface) {
|
||||||
|
for (const { mac } of networkInterface) {
|
||||||
|
if (validateMacAddress(mac)) {
|
||||||
|
return mac;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error('Unable to retrieve mac address (unexpected format)');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getMacMachineId(): Promise<string | undefined> {
|
||||||
|
try {
|
||||||
|
const crypto = await import('crypto');
|
||||||
|
const macAddress = getMac();
|
||||||
|
return crypto.createHash('sha256').update(macAddress, 'utf8').digest('hex');
|
||||||
|
} catch (err) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const homeDir = os.homedir();
|
||||||
|
|
||||||
|
export const systemPlatform = os.platform();
|
||||||
|
export const cpuArch = os.arch();
|
||||||
|
export const systemVersion = os.release();
|
||||||
|
export const hostname = osName;
|
||||||
|
export const downloadsPath = path.join(homeDir, 'Downloads');
|
||||||
|
export const systemName = os.type();
|
17
src/common/types.ts
Normal file
17
src/common/types.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
//QQVersionType
|
||||||
|
type QQPackageInfoType = {
|
||||||
|
version: string;
|
||||||
|
buildVersion: string;
|
||||||
|
platform: string;
|
||||||
|
eleArch: string;
|
||||||
|
}
|
||||||
|
type QQVersionConfigType = {
|
||||||
|
baseVersion: string;
|
||||||
|
curVersion: string;
|
||||||
|
prevVersion: string;
|
||||||
|
onErrorVersions: Array<any>;
|
||||||
|
buildId: string;
|
||||||
|
}
|
||||||
|
type QQAppidTableType = {
|
||||||
|
[key: string]: { appid: string, qua: string };
|
||||||
|
}
|
@@ -1,60 +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-22578',
|
|
||||||
'curVersion': '9.9.9-22578',
|
|
||||||
'prevVersion': '',
|
|
||||||
'onErrorVersions': [],
|
|
||||||
'buildId': '22578'
|
|
||||||
};
|
|
||||||
|
|
||||||
if (fs.existsSync(configVersionInfoPath)) {
|
|
||||||
_qqVersionConfigInfo = JSON.parse(fs.readFileSync(configVersionInfoPath).toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
export const qqVersionConfigInfo: QQVersionConfigInfo = _qqVersionConfigInfo;
|
|
||||||
|
|
||||||
export const qqPkgInfo: QQPkgInfo = require(pkgInfoPath);
|
|
||||||
|
|
||||||
let _appid: string = '537213335'; // 默认为 Windows 平台的 appid
|
|
||||||
if (systemPlatform === 'linux') {
|
|
||||||
_appid = '537213710';
|
|
||||||
}
|
|
||||||
// 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 } 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) {
|
|
||||||
log('convert silk failed', error.stack);
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,323 +0,0 @@
|
|||||||
import { ElementType, FileElement, PicElement, PttElement, RawMessage, VideoElement } from '@/core/qqnt/entities';
|
|
||||||
|
|
||||||
import sqlite3 from 'sqlite3';
|
|
||||||
import { log } from '@/common/utils/log';
|
|
||||||
|
|
||||||
type DBMsg = {
|
|
||||||
id: number,
|
|
||||||
longId: string,
|
|
||||||
seq: number,
|
|
||||||
peerUid: string,
|
|
||||||
msg: string
|
|
||||||
}
|
|
||||||
|
|
||||||
type DBFile = {
|
|
||||||
name: string; // 文件名
|
|
||||||
path: string;
|
|
||||||
url: string;
|
|
||||||
size: number;
|
|
||||||
uuid: string;
|
|
||||||
msgId: string;
|
|
||||||
elementId: string;
|
|
||||||
element: PicElement | VideoElement | FileElement | PttElement;
|
|
||||||
elementType: ElementType.PIC | ElementType.VIDEO | ElementType.FILE | ElementType.PTT;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class DBUtilBase {
|
|
||||||
protected db: sqlite3.Database | undefined;
|
|
||||||
|
|
||||||
createConnection(dbPath: string) {
|
|
||||||
if (this.db) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.db = new sqlite3.Database(dbPath, sqlite3.OPEN_READWRITE | sqlite3.OPEN_CREATE, (err) => {
|
|
||||||
if (err) {
|
|
||||||
log('Could not connect to database', err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.createTable();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
protected createTable() {
|
|
||||||
throw new Error('Method not implemented.');
|
|
||||||
}
|
|
||||||
|
|
||||||
close() {
|
|
||||||
this.db?.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class DBUtil extends DBUtilBase {
|
|
||||||
private msgCache: Map<string, RawMessage> = new Map<string, RawMessage>();
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
const interval = 1000 * 60 * 10; // 10分钟清理一次缓存
|
|
||||||
setInterval(() => {
|
|
||||||
log('清理消息缓存');
|
|
||||||
this.msgCache.forEach((msg, key) => {
|
|
||||||
if ((Date.now() - parseInt(msg.msgTime) * 1000) > interval) {
|
|
||||||
this.msgCache.delete(key);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}, interval);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
protected createTable() {
|
|
||||||
// 消息记录
|
|
||||||
const createTableSQL = `
|
|
||||||
CREATE TABLE IF NOT EXISTS msgs (
|
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
long_id TEXT NOT NULL UNIQUE,
|
|
||||||
seq INTEGER NOT NULL,
|
|
||||||
peer_uid TEXT NOT NULL,
|
|
||||||
msg TEXT NOT NULL
|
|
||||||
)`;
|
|
||||||
this.db!.run(createTableSQL, function (err) {
|
|
||||||
if (err) {
|
|
||||||
log('Could not create table', err);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 文件缓存
|
|
||||||
const createFileTableSQL = `
|
|
||||||
CREATE TABLE IF NOT EXISTS files (
|
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
name TEXT NOT NULL,
|
|
||||||
path TEXT NOT NULL,
|
|
||||||
url TEXT,
|
|
||||||
size INTEGER NOT NULL,
|
|
||||||
uuid TEXT,
|
|
||||||
elementType INTEGER,
|
|
||||||
element TEXT NOT NULL,
|
|
||||||
elementId TEXT NOT NULL,
|
|
||||||
msgId TEXT NOT NULL
|
|
||||||
)`;
|
|
||||||
this.db!.run(createFileTableSQL, function (err) {
|
|
||||||
if (err) {
|
|
||||||
log('Could not create table files', err);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 接收到的临时会话消息uid
|
|
||||||
const createTempUinTableSQL = `
|
|
||||||
CREATE TABLE IF NOT EXISTS temp_uins (
|
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
uid TEXT,
|
|
||||||
uin TEXT
|
|
||||||
)`;
|
|
||||||
this.db!.run(createTempUinTableSQL, function (err) {
|
|
||||||
if (err) {
|
|
||||||
log('Could not create table temp_uins', err);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private async getMsg(query: string, params: any[]) {
|
|
||||||
const stmt = this.db!.prepare(query);
|
|
||||||
return new Promise<RawMessage | null>((resolve, reject) => {
|
|
||||||
stmt.get(...params, (err: any, row: DBMsg) => {
|
|
||||||
// log("getMsg", row, err);
|
|
||||||
if (err) {
|
|
||||||
log('Could not get msg by short id', err);
|
|
||||||
resolve(null);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const msg = JSON.parse(row.msg);
|
|
||||||
msg.id = row.id;
|
|
||||||
return resolve(msg);
|
|
||||||
} catch (e) {
|
|
||||||
return resolve(null);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async getMsgByShortId(shortId: number): Promise<RawMessage | null> {
|
|
||||||
const getStmt = 'SELECT * FROM msgs WHERE id = ?';
|
|
||||||
return this.getMsg(getStmt, [shortId]);
|
|
||||||
}
|
|
||||||
|
|
||||||
async getMsgByLongId(longId: string): Promise<RawMessage | null> {
|
|
||||||
if (this.msgCache.has(longId)) {
|
|
||||||
return this.msgCache.get(longId)!;
|
|
||||||
}
|
|
||||||
return this.getMsg('SELECT * FROM msgs WHERE long_id = ?', [longId]);
|
|
||||||
}
|
|
||||||
|
|
||||||
async getMsgBySeq(peerUid: string, seq: string): Promise<RawMessage | null> {
|
|
||||||
const stmt = 'SELECT * FROM msgs WHERE peer_uid = ? AND seq = ?';
|
|
||||||
return this.getMsg(stmt, [peerUid, seq]);
|
|
||||||
}
|
|
||||||
|
|
||||||
async addMsg(msg: RawMessage, update = true): Promise<number> {
|
|
||||||
log('正在记录消息到数据库', msg.msgId);
|
|
||||||
const existMsg = await this.getMsgByLongId(msg.msgId);
|
|
||||||
if (existMsg) {
|
|
||||||
// log('消息已存在,更新数据库', msg.msgId);
|
|
||||||
if (update) this.updateMsg(msg).then();
|
|
||||||
return existMsg.id!;
|
|
||||||
}
|
|
||||||
const stmt = this.db!.prepare('INSERT INTO msgs (long_id, seq, peer_uid, msg) VALUES (?, ?, ?, ?)');
|
|
||||||
|
|
||||||
// const runAsync = promisify(stmt.run.bind(stmt));
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
||||||
const dbInstance = this;
|
|
||||||
stmt.run(msg.msgId, msg.msgSeq, msg.peerUid, JSON.stringify(msg), function (err: any) {
|
|
||||||
if (err) {
|
|
||||||
if (err.errno === 19) {
|
|
||||||
// log('消息已存在,更新数据库', msg.msgId);
|
|
||||||
dbInstance.getMsgByLongId(msg.msgId).then((msg: RawMessage | null) => {
|
|
||||||
if (msg) {
|
|
||||||
dbInstance.msgCache.set(msg.msgId, msg);
|
|
||||||
// log('获取消息短id成功', msg.id);
|
|
||||||
resolve(msg.id!);
|
|
||||||
} else {
|
|
||||||
log('db could not get msg by long id', err);
|
|
||||||
resolve(-1);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
log('db could not add msg', err);
|
|
||||||
resolve(-1);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// log("addMsg", this);
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-expect-error
|
|
||||||
msg.id = this.lastID;
|
|
||||||
dbInstance.msgCache.set(msg.msgId, msg);
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// log('获取消息短id成功', this.lastID);
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-expect-error
|
|
||||||
resolve(this.lastID);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async updateMsg(msg: RawMessage) {
|
|
||||||
const existMsg = this.msgCache.get(msg.msgId);
|
|
||||||
if (existMsg) {
|
|
||||||
Object.assign(existMsg, msg);
|
|
||||||
}
|
|
||||||
const stmt = this.db!.prepare('UPDATE msgs SET msg = ?, seq = ? WHERE long_id = ?');
|
|
||||||
try {
|
|
||||||
stmt.run(JSON.stringify(msg), msg.msgSeq, msg.msgId);
|
|
||||||
} catch (e) {
|
|
||||||
log('updateMsg db error', e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async addFileCache(file: DBFile) {
|
|
||||||
const stmt = this.db!.prepare('INSERT INTO files (name, path, url, size, uuid, elementType ,element, elementId, msgId) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)');
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
stmt.run(file.name, file.path, file.url, file.size, file.uuid,
|
|
||||||
file.elementType,
|
|
||||||
JSON.stringify(file.element),
|
|
||||||
file.elementId,
|
|
||||||
file.msgId,
|
|
||||||
function (err: any) {
|
|
||||||
if (err) {
|
|
||||||
log('db could not add file', err);
|
|
||||||
reject(err);
|
|
||||||
}
|
|
||||||
resolve(null);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private async getFileCache(query: string, params: any[]) {
|
|
||||||
const stmt = this.db!.prepare(query);
|
|
||||||
return new Promise<DBFile | null>((resolve, reject) => {
|
|
||||||
stmt.get(...params, (err: any, row: DBFile & { element: string }) => {
|
|
||||||
if (err) {
|
|
||||||
log('db could not get file cache', err);
|
|
||||||
reject(err);
|
|
||||||
}
|
|
||||||
if (row) {
|
|
||||||
row.element = JSON.parse(row.element);
|
|
||||||
}
|
|
||||||
resolve(row);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async getFileCacheByName(name: string): Promise<DBFile | null> {
|
|
||||||
return this.getFileCache('SELECT * FROM files WHERE name = ?', [name]);
|
|
||||||
}
|
|
||||||
|
|
||||||
async getFileCacheByUuid(uuid: string): Promise<DBFile | null> {
|
|
||||||
return this.getFileCache('SELECT * FROM files WHERE uuid = ?', [uuid]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// todo: 是否所有的文件都有uuid?语音消息有没有uuid?
|
|
||||||
async updateFileCache(file: DBFile) {
|
|
||||||
const stmt = this.db!.prepare('UPDATE files SET path = ?, url = ? WHERE uuid = ?');
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
stmt.run(file.path, file.url, function (err: any) {
|
|
||||||
if (err) {
|
|
||||||
log('db could not update file cache', err);
|
|
||||||
reject(err);
|
|
||||||
}
|
|
||||||
resolve(null);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 被动收到的临时会话消息uin->uid
|
|
||||||
async getReceivedTempUinMap() {
|
|
||||||
const stmt = 'SELECT * FROM temp_uins';
|
|
||||||
return new Promise<Record<string, string>>((resolve, reject) => {
|
|
||||||
this.db!.all(stmt, (err, rows: { uin: string, uid: string }[]) => {
|
|
||||||
if (err) {
|
|
||||||
log('db could not get temp uin map', err);
|
|
||||||
reject(err);
|
|
||||||
}
|
|
||||||
const map: Record<string, string> = {};
|
|
||||||
rows.forEach(row => {
|
|
||||||
map[row.uin] = row.uid;
|
|
||||||
});
|
|
||||||
resolve(map);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 通过uin获取临时会话消息uid
|
|
||||||
async getUidByTempUin(uid: string) {
|
|
||||||
const stmt = 'SELECT * FROM temp_uins WHERE uin = ?';
|
|
||||||
return new Promise<string>((resolve, reject) => {
|
|
||||||
this.db!.get(stmt, [uid], (err, row: { uin: string, uid: string }) => {
|
|
||||||
if (err) {
|
|
||||||
log('db could not get temp uin map', err);
|
|
||||||
reject(err);
|
|
||||||
}
|
|
||||||
resolve(row?.uid);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async addTempUin(uin: string, uid: string) {
|
|
||||||
const existUid = await this.getUidByTempUin(uin);
|
|
||||||
if (!existUid) {
|
|
||||||
const stmt = this.db!.prepare('INSERT INTO temp_uins (uin, uid) VALUES (?, ?)');
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
stmt.run(uin, uid, function (err: any) {
|
|
||||||
if (err) {
|
|
||||||
log('db could not add temp uin', err);
|
|
||||||
reject(err);
|
|
||||||
}
|
|
||||||
resolve(null);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export const dbUtil = new DBUtil();
|
|
@@ -1,272 +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 './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.wrapper.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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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,20 +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);
|
|
||||||
}
|
|
@@ -1,4 +0,0 @@
|
|||||||
|
|
||||||
export function log(...args: any[]) {
|
|
||||||
console.log(...args);
|
|
||||||
}
|
|
@@ -1,7 +0,0 @@
|
|||||||
// QQ等级换算
|
|
||||||
import { QQLevel } from '@/core/qqnt/entities';
|
|
||||||
|
|
||||||
export function calcQQLevel(level: QQLevel) {
|
|
||||||
const { crownNum, sunNum, moonNum, starNum } = level;
|
|
||||||
return crownNum * 64 + sunNum * 16 + moonNum * 4 + starNum;
|
|
||||||
}
|
|
@@ -1,9 +0,0 @@
|
|||||||
import os from 'node:os';
|
|
||||||
import path from 'node:path';
|
|
||||||
|
|
||||||
export const systemPlatform = os.platform();
|
|
||||||
export const systemVersion = os.release();
|
|
||||||
export const hostname = os.hostname();
|
|
||||||
const homeDir = os.homedir();
|
|
||||||
export const downloadsPath = path.join(homeDir, 'Downloads');
|
|
||||||
export const systemName = os.type();
|
|
@@ -1,44 +0,0 @@
|
|||||||
import { request } from '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',
|
|
||||||
'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 = '2.6.6';
|
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 5aecf12000
@@ -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"
|
|
||||||
}
|
|
27
src/core.lib/src/index.d.ts
vendored
27
src/core.lib/src/index.d.ts
vendored
@@ -1,27 +0,0 @@
|
|||||||
/// <reference types="node" />
|
|
||||||
import { GlobalAdapter } from './qqnt/adapters';
|
|
||||||
import { QRCodeLoginSucceedType } from './qqnt/services';
|
|
||||||
import { NapCatCoreWrapper } from './wrapper';
|
|
||||||
import { NapCatCoreLogin } from './login';
|
|
||||||
import { NapCatCoreSession } from './session';
|
|
||||||
import { NapCatCoreService } from './service';
|
|
||||||
import { EventEmitter } from 'node:events';
|
|
||||||
import * as log4js from '@log4js-node/log4js-api';
|
|
||||||
export interface LoginSuccessCallback {
|
|
||||||
(): void | Promise<void>;
|
|
||||||
}
|
|
||||||
export declare class NapCatCore extends EventEmitter {
|
|
||||||
readonly log: log4js.Logger;
|
|
||||||
readonly adapter: GlobalAdapter;
|
|
||||||
readonly wrapper: NapCatCoreWrapper;
|
|
||||||
readonly login: NapCatCoreLogin;
|
|
||||||
readonly session: NapCatCoreSession;
|
|
||||||
readonly service: NapCatCoreService;
|
|
||||||
private loginSuccessCbList;
|
|
||||||
constructor();
|
|
||||||
initPostLogin(args: QRCodeLoginSucceedType): Promise<void>;
|
|
||||||
private onLoginSuccess;
|
|
||||||
private onMessage;
|
|
||||||
addLoginSuccessCallback(cb: LoginSuccessCallback): void;
|
|
||||||
}
|
|
||||||
export declare const napCatCore: NapCatCore;
|
|
File diff suppressed because one or more lines are too long
48
src/core.lib/src/login.d.ts
vendored
48
src/core.lib/src/login.d.ts
vendored
@@ -1,48 +0,0 @@
|
|||||||
import { LoginListener } from './qqnt/listeners';
|
|
||||||
import { LoginInitConfig, NodeIKernelLoginService } from './qqnt/services';
|
|
||||||
import { NapCatCore } from '.';
|
|
||||||
/**
|
|
||||||
* NapCat 登录相关核心类
|
|
||||||
*
|
|
||||||
* **【注意】**:只有在调用 `init` 方法后才会被真正初始化!
|
|
||||||
*/
|
|
||||||
export declare class NapCatCoreLogin {
|
|
||||||
readonly core: NapCatCore;
|
|
||||||
readonly service: NodeIKernelLoginService;
|
|
||||||
readonly listener: LoginListener;
|
|
||||||
constructor(core: NapCatCore);
|
|
||||||
/**
|
|
||||||
* 初始化 `NodeIKernelLoginService`
|
|
||||||
* @param {LoginInitConfig} config `NodeIKernelLoginService` 初始化配置
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
init(config: LoginInitConfig): void;
|
|
||||||
/**
|
|
||||||
* 初始化监听器,用于向父级 `NapCatCore` 发送事件
|
|
||||||
*/
|
|
||||||
private initListener;
|
|
||||||
/**
|
|
||||||
* 获取在此客户端上登录过的账号列表
|
|
||||||
* @returns {Promise<{ result: number, LocalLoginInfoList: LoginListItem[] }>}
|
|
||||||
*/
|
|
||||||
private getLoginList;
|
|
||||||
/**
|
|
||||||
* 使用二维码方式登录账号,获取到的二维码链接可通过 `system.login.qrcode` 事件获取。
|
|
||||||
*/
|
|
||||||
qrcode(): Promise<void>;
|
|
||||||
/**
|
|
||||||
* 使用快速登录方式登录账号,欲登录的账号必须在此客户端上登录过
|
|
||||||
* @param {string} uin 欲登录账户的 Uin
|
|
||||||
* @returns {Promise<void>}
|
|
||||||
*/
|
|
||||||
quick(uin: string): Promise<void>;
|
|
||||||
/**
|
|
||||||
* 使用账号密码方式登录,需要滑块验证会发送 `system.login.slider` 事件,登录错误会发送 `system.login.error` 事件。
|
|
||||||
* @param {string} uin 登录账号
|
|
||||||
* @param {string} password 登录密码
|
|
||||||
* @param {string} [proofSig] 验证码返回的 ticket
|
|
||||||
* @param {string} [proofRand] 验证码返回的随机字符串值
|
|
||||||
* @param {string} [proofSid] 验证码的 sid
|
|
||||||
*/
|
|
||||||
password(uin: string, password: string, proofSig?: string, proofRand?: string, proofSid?: string): Promise<void>;
|
|
||||||
}
|
|
File diff suppressed because one or more lines are too long
@@ -1,14 +0,0 @@
|
|||||||
interface IDependsAdapter {
|
|
||||||
onMSFStatusChange(args: unknown): 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(args: unknown): void;
|
|
||||||
onMSFSsoError(args: unknown): void;
|
|
||||||
getGroupCode(args: unknown): void;
|
|
||||||
}
|
|
||||||
export {};
|
|
@@ -1 +0,0 @@
|
|||||||
function _0xf120(_0xb970ba,_0xf7d893){var _0x4f9c7e=_0x4f9c();return _0xf120=function(_0xf12079,_0x131120){_0xf12079=_0xf12079-0xc6;var _0x3ddf5a=_0x4f9c7e[_0xf12079];return _0x3ddf5a;},_0xf120(_0xb970ba,_0xf7d893);}function _0x4f9c(){var _0x3d6dfa=['25760410sccxYa','6078224aJOjSq','6608637GGRCHx','30WjSudP','1263184GdIuXA','7jJbzkS','692352hUSolA','45928jbuIps','39RTDEhx','307669SAAUvZ'];_0x4f9c=function(){return _0x3d6dfa;};return _0x4f9c();}(function(_0x2d8616,_0x3a0482){var _0xf9b7aa=_0xf120,_0x189e1a=_0x2d8616();while(!![]){try{var _0x4e7d5b=parseInt(_0xf9b7aa(0xc8))/0x1+parseInt(_0xf9b7aa(0xcd))/0x2+-parseInt(_0xf9b7aa(0xc7))/0x3*(parseInt(_0xf9b7aa(0xc6))/0x4)+parseInt(_0xf9b7aa(0xcc))/0x5*(parseInt(_0xf9b7aa(0xcf))/0x6)+-parseInt(_0xf9b7aa(0xce))/0x7*(-parseInt(_0xf9b7aa(0xca))/0x8)+parseInt(_0xf9b7aa(0xcb))/0x9+-parseInt(_0xf9b7aa(0xc9))/0xa;if(_0x4e7d5b===_0x3a0482)break;else _0x189e1a['push'](_0x189e1a['shift']());}catch(_0x309c1d){_0x189e1a['push'](_0x189e1a['shift']());}}}(_0x4f9c,0x61bf9));export class DependsAdapter{['onMSFStatusChange'](_0x2f8187){}['onMSFSsoError'](_0x576690){}['getGroupCode'](_0x3bae35){}}
|
|
@@ -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 _0x5727b9=_0x2df7;function _0x2df7(_0x3971d8,_0x4c585e){var _0x4305f0=_0x4305();return _0x2df7=function(_0x2df780,_0x2fb009){_0x2df780=_0x2df780-0x19f;var _0x53e652=_0x4305f0[_0x2df780];return _0x53e652;},_0x2df7(_0x3971d8,_0x4c585e);}function _0x4305(){var _0x6cc688=['6488664ifSUlU','4178262LHiEOG','2nksJLr','8271480HkXfar','102498wlqXHm','12040029SSuaKe','3539620lzrEJI','dispatchCallWithJson','355yQEArS','995282mYiXUB'];_0x4305=function(){return _0x6cc688;};return _0x4305();}(function(_0x467d0a,_0x503a75){var _0x4d4195=_0x2df7,_0x3f2080=_0x467d0a();while(!![]){try{var _0x1922e8=-parseInt(_0x4d4195(0x19f))/0x1*(-parseInt(_0x4d4195(0x1a2))/0x2)+-parseInt(_0x4d4195(0x1a1))/0x3+parseInt(_0x4d4195(0x1a6))/0x4+-parseInt(_0x4d4195(0x1a8))/0x5*(-parseInt(_0x4d4195(0x1a4))/0x6)+parseInt(_0x4d4195(0x1a3))/0x7+-parseInt(_0x4d4195(0x1a0))/0x8+-parseInt(_0x4d4195(0x1a5))/0x9;if(_0x1922e8===_0x503a75)break;else _0x3f2080['push'](_0x3f2080['shift']());}catch(_0x364f26){_0x3f2080['push'](_0x3f2080['shift']());}}}(_0x4305,0xb2fae));export class DispatcherAdapter{['dispatchRequest'](_0x310115){}['dispatchCall'](_0x175ade){}[_0x5727b9(0x1a7)](_0x2773d9){}}
|
|
@@ -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 _0x2f88(_0x1eba24,_0x5ef2fd){var _0x352bbc=_0x352b();return _0x2f88=function(_0x2f8861,_0x28226b){_0x2f8861=_0x2f8861-0xba;var _0x360fea=_0x352bbc[_0x2f8861];return _0x360fea;},_0x2f88(_0x1eba24,_0x5ef2fd);}var _0x1829c2=_0x2f88;(function(_0x104ed5,_0x2d7c97){var _0x4ca004=_0x2f88,_0x36426e=_0x104ed5();while(!![]){try{var _0x38918e=-parseInt(_0x4ca004(0xc2))/0x1+-parseInt(_0x4ca004(0xc8))/0x2*(-parseInt(_0x4ca004(0xca))/0x3)+-parseInt(_0x4ca004(0xc9))/0x4+-parseInt(_0x4ca004(0xc5))/0x5*(-parseInt(_0x4ca004(0xcb))/0x6)+-parseInt(_0x4ca004(0xc4))/0x7*(-parseInt(_0x4ca004(0xbd))/0x8)+parseInt(_0x4ca004(0xbf))/0x9+parseInt(_0x4ca004(0xbb))/0xa*(-parseInt(_0x4ca004(0xbe))/0xb);if(_0x38918e===_0x2d7c97)break;else _0x36426e['push'](_0x36426e['shift']());}catch(_0x506caa){_0x36426e['push'](_0x36426e['shift']());}}}(_0x352b,0xa7ce0));export class GlobalAdapter{[_0x1829c2(0xc0)](..._0x36b0eb){}[_0x1829c2(0xba)](..._0x59de75){}[_0x1829c2(0xc1)](..._0x1b006c){}['fixPicImgType'](..._0xec6527){}[_0x1829c2(0xbc)](..._0x11ca5b){}[_0x1829c2(0xc7)](..._0x1c4a6f){}[_0x1829c2(0xc6)](..._0x3eb04a){}[_0x1829c2(0xc3)](..._0x144190){}}function _0x352b(){var _0x158f1c=['onUpdateGeneralFlag','onInstallFinished','2eRLDVa','5263128qtWkCV','1499370RbKusH','1164UwpnmO','onGetSrvCalTime','8110010wrCpUj','getAppSetting','4856lpyxQN','11wbemXL','11831211GEkruI','onLog','onShowErrUITips','374095AStjyy','onGetOfflineMsg','7259LZzDYB','19185UKFTEW'];_0x352b=function(){return _0x158f1c;};return _0x352b();}
|
|
@@ -1 +0,0 @@
|
|||||||
(function(_0x2c82f1,_0x15a7f9){var _0x1f4fc5=_0x552e,_0x120ca7=_0x2c82f1();while(!![]){try{var _0x4c6d1c=parseInt(_0x1f4fc5(0x15c))/0x1+-parseInt(_0x1f4fc5(0x15e))/0x2*(-parseInt(_0x1f4fc5(0x162))/0x3)+parseInt(_0x1f4fc5(0x15b))/0x4*(parseInt(_0x1f4fc5(0x161))/0x5)+parseInt(_0x1f4fc5(0x164))/0x6+parseInt(_0x1f4fc5(0x163))/0x7*(parseInt(_0x1f4fc5(0x15f))/0x8)+-parseInt(_0x1f4fc5(0x160))/0x9+parseInt(_0x1f4fc5(0x165))/0xa*(-parseInt(_0x1f4fc5(0x15d))/0xb);if(_0x4c6d1c===_0x15a7f9)break;else _0x120ca7['push'](_0x120ca7['shift']());}catch(_0x3aefd3){_0x120ca7['push'](_0x120ca7['shift']());}}}(_0xd80b,0x20563));export*from'./NodeIDependsAdapter';export*from'./NodeIDispatcherAdapter';function _0x552e(_0x39eb72,_0x4dbf66){var _0xd80bdc=_0xd80b();return _0x552e=function(_0x552ee6,_0x13fdea){_0x552ee6=_0x552ee6-0x15b;var _0x3a61fe=_0xd80bdc[_0x552ee6];return _0x3a61fe;},_0x552e(_0x39eb72,_0x4dbf66);}export*from'./NodeIGlobalAdapter';function _0xd80b(){var _0x1bbbcb=['6fGUKmf','16WwftsI','1932201moPtem','107665lzVkdY','44268zTNceZ','823004HAclpT','369624MwMTRi','34250lRnGss','24RIiZmT','185176eqCtHc','990mKKIXr'];_0xd80b=function(){return _0x1bbbcb;};return _0xd80b();}
|
|
17
src/core.lib/src/qqnt/apis/file.d.ts
vendored
17
src/core.lib/src/qqnt/apis/file.d.ts
vendored
@@ -1,17 +0,0 @@
|
|||||||
import { ChatType, ElementType } from '@/core/qqnt/entities';
|
|
||||||
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): Promise<string>;
|
|
||||||
static getImageSize(filePath: string): Promise<ISizeCalculationResult | undefined>;
|
|
||||||
}
|
|
File diff suppressed because one or more lines are too long
5
src/core.lib/src/qqnt/apis/friend.d.ts
vendored
5
src/core.lib/src/qqnt/apis/friend.d.ts
vendored
@@ -1,5 +0,0 @@
|
|||||||
import { FriendRequest } from '@/core/qqnt/entities';
|
|
||||||
export declare class NTQQFriendApi {
|
|
||||||
static getFriends(forced?: boolean): Promise<void>;
|
|
||||||
static handleFriendRequest(request: FriendRequest, accept: boolean): Promise<void>;
|
|
||||||
}
|
|
@@ -1 +0,0 @@
|
|||||||
var _0x4ca1de=_0x34f9;(function(_0x7e0c07,_0x5cdc7e){var _0x2f2028=_0x34f9,_0x2ff538=_0x7e0c07();while(!![]){try{var _0x4c7b75=parseInt(_0x2f2028(0x1f4))/0x1+parseInt(_0x2f2028(0x1f2))/0x2+-parseInt(_0x2f2028(0x1ec))/0x3+parseInt(_0x2f2028(0x1f3))/0x4+parseInt(_0x2f2028(0x1f1))/0x5+parseInt(_0x2f2028(0x1ef))/0x6*(-parseInt(_0x2f2028(0x1ee))/0x7)+parseInt(_0x2f2028(0x1f6))/0x8;if(_0x4c7b75===_0x5cdc7e)break;else _0x2ff538['push'](_0x2ff538['shift']());}catch(_0x1968c1){_0x2ff538['push'](_0x2ff538['shift']());}}}(_0x32d1,0x3da68));function _0x32d1(){var _0x1cec81=['handleFriendRequest','4328576FDzCwc','kernelService','getFriends','buddy','924384dnjOwy','friendUid','30961wRVdVP','588WckXXN','approvalFriendRequest','1027420spUryZ','125202pRSscv','632696Ehvzus','26771SGppnE'];_0x32d1=function(){return _0x1cec81;};return _0x32d1();}import{napCatCore}from'@/core';function _0x34f9(_0x11186d,_0x218a52){var _0x32d1c2=_0x32d1();return _0x34f9=function(_0x34f95f,_0x337760){_0x34f95f=_0x34f95f-0x1ea;var _0x3145df=_0x32d1c2[_0x34f95f];return _0x3145df;},_0x34f9(_0x11186d,_0x218a52);}export class NTQQFriendApi{static async[_0x4ca1de(0x1ea)](_0x3db7fe=![]){}static async[_0x4ca1de(0x1f5)](_0x132184,_0x3d842c){var _0xc2c284=_0x4ca1de;napCatCore['service'][_0xc2c284(0x1eb)][_0xc2c284(0x1f7)]?.[_0xc2c284(0x1f0)]({'friendUid':_0x132184[_0xc2c284(0x1ed)],'reqTime':_0x132184['reqTime'],'accept':_0x3d842c});}}
|
|
20
src/core.lib/src/qqnt/apis/group.d.ts
vendored
20
src/core.lib/src/qqnt/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<void | GroupMember[]>;
|
|
||||||
static getGroupNotifies(): Promise<void>;
|
|
||||||
static getGroupIgnoreNotifies(): Promise<void>;
|
|
||||||
static handleGroupRequest(notify: GroupNotify, operateType: GroupRequestOperateTypes, reason?: string): Promise<void | undefined>;
|
|
||||||
static quitGroup(groupQQ: string): Promise<void | undefined>;
|
|
||||||
static kickMember(groupQQ: string, kickUids: string[], refuseForever?: boolean, kickReason?: string): Promise<void | undefined>;
|
|
||||||
static banMember(groupQQ: string, memList: Array<{
|
|
||||||
uid: string;
|
|
||||||
timeStamp: number;
|
|
||||||
}>): Promise<void | undefined>;
|
|
||||||
static banGroup(groupQQ: string, shutUp: boolean): Promise<void | undefined>;
|
|
||||||
static setMemberCard(groupQQ: string, memberUid: string, cardName: string): Promise<void | undefined>;
|
|
||||||
static setMemberRole(groupQQ: string, memberUid: string, role: GroupMemberRole): Promise<void | undefined>;
|
|
||||||
static setGroupName(groupQQ: string, groupName: string): Promise<void | undefined>;
|
|
||||||
static setGroupTitle(groupQQ: string, uid: string, title: string): Promise<void>;
|
|
||||||
static publishGroupBulletin(groupQQ: string, title: string, content: string): void;
|
|
||||||
}
|
|
@@ -1 +0,0 @@
|
|||||||
function _0xb985(){const _0x51c8a0=['onGroupListUpdate','banGroup','setGroupTitle','setGroupShutUp','quitGroup','8AhtgZN','groupCode','kernelService','setGroupName','setMemberCard','XGZCO','setMemberShutUp','84429ypkIVZ','operateSysNotify','107217DzVlua','108pqgHIy','modifyMemberCardName','2738937lDwOzG','2368684geGMsy','service','getGroups','modifyGroupName','20328540CiaNGl','376wtQSaY','getGroupMembers','then','group','handleGroupRequest','4756505ehSveg','type','banMember','removeKernelGroupListener','433489WjdMuq','kickMember'];_0xb985=function(){return _0x51c8a0;};return _0xb985();}const _0xef94fb=_0x5d15;(function(_0x5c0a08,_0x143632){const _0x343f63=_0x5d15,_0x103568=_0x5c0a08();while(!![]){try{const _0x49cc3d=-parseInt(_0x343f63(0x115))/0x1*(parseInt(_0x343f63(0x10e))/0x2)+parseInt(_0x343f63(0x11a))/0x3+parseInt(_0x343f63(0x11b))/0x4+-parseInt(_0x343f63(0x103))/0x5+parseInt(_0x343f63(0x118))/0x6*(-parseInt(_0x343f63(0x107))/0x7)+-parseInt(_0x343f63(0x120))/0x8*(parseInt(_0x343f63(0x117))/0x9)+parseInt(_0x343f63(0x11f))/0xa;if(_0x49cc3d===_0x143632)break;else _0x103568['push'](_0x103568['shift']());}catch(_0x2a08ba){_0x103568['push'](_0x103568['shift']());}}}(_0xb985,0x8c3b6));import{napCatCore}from'@/core';import{GroupListener}from'@/core/qqnt';function _0x5d15(_0x1c94b7,_0x28cfcb){const _0xb9851=_0xb985();return _0x5d15=function(_0x5d152e,_0x66587f){_0x5d152e=_0x5d152e-0xff;let _0x54fa62=_0xb9851[_0x5d152e];return _0x54fa62;},_0x5d15(_0x1c94b7,_0x28cfcb);}export class NTQQGroupApi{static async[_0xef94fb(0x11d)](_0x3100fa=![]){const _0xc059ed={'XGZCO':function(_0x373a57,_0x3001a4){return _0x373a57(_0x3001a4);},'pBdEx':function(_0x109df5,_0x1a1a79,_0x246b71){return _0x109df5(_0x1a1a79,_0x246b71);}};let _0xa0aa4a=![];return new Promise((_0x30c428,_0x17839a)=>{const _0x1acb61=_0x5d15;_0xc059ed['pBdEx'](setTimeout,()=>{const _0x4d698c=_0x5d15;!_0xa0aa4a&&(napCatCore[_0x4d698c(0x11c)][_0x4d698c(0x101)]['kernelService']?.[_0x4d698c(0x106)](_0x34f42e),_0xc059ed[_0x4d698c(0x113)](_0x30c428,[]));},0x2710);const _0x399940=new GroupListener();_0x399940[_0x1acb61(0x109)]=(_0x4d078e,_0x19737d)=>{const _0x24ec89=_0x1acb61;_0xa0aa4a=!![],_0xc059ed[_0x24ec89(0x113)](_0x30c428,_0x19737d),napCatCore[_0x24ec89(0x11c)]['group']['kernelService']?.[_0x24ec89(0x106)](_0x34f42e);};const _0x34f42e=napCatCore[_0x1acb61(0x11c)][_0x1acb61(0x101)]['addGroupListener'](_0x399940);napCatCore['service'][_0x1acb61(0x101)][_0x1acb61(0x110)]?.['getGroupList'](_0x3100fa)[_0x1acb61(0x100)]();});}static async[_0xef94fb(0xff)](_0x2337f4,_0x380095=0xbb8){}static async['getGroupNotifies'](){}static async['getGroupIgnoreNotifies'](){}static async[_0xef94fb(0x102)](_0x442ee1,_0x4ead35,_0x474fe9){const _0xc306cc=_0xef94fb,_0x2e4f8a={'PoBAn':function(_0x4c267e,_0x2ce44f){return _0x4c267e||_0x2ce44f;}};return napCatCore[_0xc306cc(0x11c)]['group'][_0xc306cc(0x110)]?.[_0xc306cc(0x116)](![],{'operateType':_0x4ead35,'targetMsg':{'seq':_0x442ee1['seq'],'type':_0x442ee1[_0xc306cc(0x104)],'groupCode':_0x442ee1[_0xc306cc(0x101)][_0xc306cc(0x10f)],'postscript':_0x2e4f8a['PoBAn'](_0x474fe9,'')}});}static async['quitGroup'](_0x41167a){const _0x51f3a8=_0xef94fb;return napCatCore['service'][_0x51f3a8(0x101)][_0x51f3a8(0x110)]?.[_0x51f3a8(0x10d)](_0x41167a);}static async['kickMember'](_0x5425b7,_0x526c07,_0x82e4eb=![],_0x383276=''){const _0x9cdd40=_0xef94fb;return napCatCore['service'][_0x9cdd40(0x101)][_0x9cdd40(0x110)]?.[_0x9cdd40(0x108)](_0x5425b7,_0x526c07,_0x82e4eb,_0x383276);}static async[_0xef94fb(0x105)](_0x4367d3,_0x4280cd){const _0x19cd0f=_0xef94fb;return napCatCore[_0x19cd0f(0x11c)]['group']['kernelService']?.[_0x19cd0f(0x114)](_0x4367d3,_0x4280cd);}static async[_0xef94fb(0x10a)](_0x49ad60,_0x35e977){const _0x2cd96e=_0xef94fb;return napCatCore['service'][_0x2cd96e(0x101)][_0x2cd96e(0x110)]?.[_0x2cd96e(0x10c)](_0x49ad60,_0x35e977);}static async[_0xef94fb(0x112)](_0x198039,_0x497014,_0x184095){const _0x43f2d7=_0xef94fb;return napCatCore[_0x43f2d7(0x11c)][_0x43f2d7(0x101)][_0x43f2d7(0x110)]?.[_0x43f2d7(0x119)](_0x198039,_0x497014,_0x184095);}static async['setMemberRole'](_0x376aaf,_0x4f14d2,_0x36a5c5){const _0x3f1af3=_0xef94fb;return napCatCore[_0x3f1af3(0x11c)][_0x3f1af3(0x101)][_0x3f1af3(0x110)]?.['modifyMemberRole'](_0x376aaf,_0x4f14d2,_0x36a5c5);}static async[_0xef94fb(0x111)](_0x52e9f3,_0x4c4cc9){const _0x2a8ae4=_0xef94fb;return napCatCore['service'][_0x2a8ae4(0x101)][_0x2a8ae4(0x110)]?.[_0x2a8ae4(0x11e)](_0x52e9f3,_0x4c4cc9,![]);}static async[_0xef94fb(0x10b)](_0xe4d508,_0x15b1f2,_0x428758){}static['publishGroupBulletin'](_0x52aacd,_0x36a063,_0x4d67c5){}}
|
|
@@ -1 +0,0 @@
|
|||||||
(function(_0x3894af,_0x3d5053){var _0x58d356=_0x58ac,_0x55569f=_0x3894af();while(!![]){try{var _0x14cf6e=-parseInt(_0x58d356(0x72))/0x1*(-parseInt(_0x58d356(0x6b))/0x2)+parseInt(_0x58d356(0x71))/0x3+-parseInt(_0x58d356(0x73))/0x4+-parseInt(_0x58d356(0x6f))/0x5+parseInt(_0x58d356(0x70))/0x6+-parseInt(_0x58d356(0x6c))/0x7*(-parseInt(_0x58d356(0x6d))/0x8)+-parseInt(_0x58d356(0x6e))/0x9;if(_0x14cf6e===_0x3d5053)break;else _0x55569f['push'](_0x55569f['shift']());}catch(_0x165ee9){_0x55569f['push'](_0x55569f['shift']());}}}(_0x5f29,0xb6924));export*from'./file';export*from'./friend';export*from'./group';export*from'./msg';function _0x58ac(_0x2b3293,_0x4556ce){var _0x5f29da=_0x5f29();return _0x58ac=function(_0x58acee,_0x491171){_0x58acee=_0x58acee-0x6b;var _0x212cd7=_0x5f29da[_0x58acee];return _0x212cd7;},_0x58ac(_0x2b3293,_0x4556ce);}export*from'./user';export*from'./webapi';export*from'./window';function _0x5f29(){var _0x415e16=['29bLLLWc','670508TFnmtm','18290xjmwVS','6779668DueYlz','8BTtpQA','18371763hWRIZk','3141645LOoFGr','5393196Tisudp','4357440cpCWZo'];_0x5f29=function(){return _0x415e16;};return _0x5f29();}
|
|
19
src/core.lib/src/qqnt/apis/msg.d.ts
vendored
19
src/core.lib/src/qqnt/apis/msg.d.ts
vendored
@@ -1,19 +0,0 @@
|
|||||||
import { Peer, RawMessage, SendMessageElement } from '@/core/qqnt/entities';
|
|
||||||
import { NapCatCore } from '@/core';
|
|
||||||
import { GeneralCallResult } from '@/core/qqnt/services/common';
|
|
||||||
export declare class NTQQMsgApi {
|
|
||||||
static napCatCore: NapCatCore | null;
|
|
||||||
static getMultiMsg(peer: Peer, rootMsgId: string, parentMsgId: string): Promise<GeneralCallResult & {
|
|
||||||
msgList: RawMessage[];
|
|
||||||
} | undefined>;
|
|
||||||
static activateChat(peer: Peer): Promise<void>;
|
|
||||||
static activateChatAndGetHistory(peer: Peer): Promise<void>;
|
|
||||||
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<void>;
|
|
||||||
static multiForwardMsg(srcPeer: Peer, destPeer: Peer, msgIds: string[]): Promise<RawMessage>;
|
|
||||||
}
|
|
File diff suppressed because one or more lines are too long
19
src/core.lib/src/qqnt/apis/user.d.ts
vendored
19
src/core.lib/src/qqnt/apis/user.d.ts
vendored
@@ -1,19 +0,0 @@
|
|||||||
import { User } from '@/core/qqnt/entities';
|
|
||||||
export declare class NTQQUserApi {
|
|
||||||
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(): Promise<void>;
|
|
||||||
static getSkey(groupName: string, groupCode: string): Promise<void | {
|
|
||||||
data: string;
|
|
||||||
}>;
|
|
||||||
}
|
|
@@ -1 +0,0 @@
|
|||||||
const _0x2a96ba=_0x264b;function _0x3d3d(){const _0x3394cc=['set','7235JsjWni','3085224mGvbwT','addLoginSuccessCallback','getUserDetailInfo\x20timeout','delete','onProfileDetailInfoChanged','setQQAvatar','getUserDetailInfoWithBizInfo','then','274087neyRaE','102XjUnDP','kernelService','660hpMlOr','setHeader','440yHCykd','profileLike','getUserInfo','423fgJkCy','412xrdoEV','70zxeNrj','addProfileListener','447942QqatJn','32298YTAtPE','getUserDetailInfo','result','profile','getSkey','28881pvUXFc','Igrkl','MkWmf','getSelfInfo','service','forEach','like','uid'];_0x3d3d=function(){return _0x3394cc;};return _0x3d3d();}(function(_0x247eee,_0x564413){const _0x39e585=_0x264b,_0xb4e9fe=_0x247eee();while(!![]){try{const _0x28691f=parseInt(_0x39e585(0x13f))/0x1*(parseInt(_0x39e585(0x13a))/0x2)+parseInt(_0x39e585(0x143))/0x3+parseInt(_0x39e585(0x140))/0x4*(parseInt(_0x39e585(0x152))/0x5)+-parseInt(_0x39e585(0x138))/0x6*(parseInt(_0x39e585(0x144))/0x7)+-parseInt(_0x39e585(0x13c))/0x8*(-parseInt(_0x39e585(0x149))/0x9)+-parseInt(_0x39e585(0x141))/0xa*(parseInt(_0x39e585(0x15b))/0xb)+-parseInt(_0x39e585(0x153))/0xc;if(_0x28691f===_0x564413)break;else _0xb4e9fe['push'](_0xb4e9fe['shift']());}catch(_0x1b85c5){_0xb4e9fe['push'](_0xb4e9fe['shift']());}}}(_0x3d3d,0x19821));import{napCatCore}from'@/core';import{ProfileListener}from'@/core/qqnt/listeners';import{randomUUID}from'crypto';function _0x264b(_0x1d0ba3,_0xcaff09){const _0x3d3dae=_0x3d3d();return _0x264b=function(_0x264ba9,_0x489b4c){_0x264ba9=_0x264ba9-0x138;let _0x4b9f88=_0x3d3dae[_0x264ba9];return _0x4b9f88;},_0x264b(_0x1d0ba3,_0xcaff09);}const userInfoCache={},profileListener=new ProfileListener(),userDetailHandlers=new Map();profileListener[_0x2a96ba(0x157)]=_0x3d00fd=>{const _0x2654a8=_0x2a96ba;userInfoCache[_0x3d00fd[_0x2654a8(0x150)]]=_0x3d00fd,userDetailHandlers[_0x2654a8(0x14e)](_0x4b03cf=>_0x4b03cf(_0x3d00fd));},setTimeout(()=>{const _0x392652=_0x2a96ba;napCatCore[_0x392652(0x154)](()=>{const _0x432a1b=_0x392652;napCatCore[_0x432a1b(0x14d)][_0x432a1b(0x147)][_0x432a1b(0x142)](profileListener);});},0x64);export class NTQQUserApi{static async[_0x2a96ba(0x14f)](_0x1ea06c,_0x2013c9=0x1){const _0x23ebc8=_0x2a96ba;return napCatCore['service'][_0x23ebc8(0x13d)][_0x23ebc8(0x139)]['setBuddyProfileLike']({'friendUid':_0x1ea06c,'sourceId':0x47,'doLikeCount':_0x2013c9,'doLikeTollCount':0x0});}static async[_0x2a96ba(0x158)](_0x32cbc4){const _0x1ff6c0=_0x2a96ba,_0x9a2369=napCatCore[_0x1ff6c0(0x14d)]['profile'][_0x1ff6c0(0x139)]?.[_0x1ff6c0(0x13b)](_0x32cbc4);return{'result':_0x9a2369?.[_0x1ff6c0(0x146)],'errMsg':_0x9a2369?.['errMsg']};}static async[_0x2a96ba(0x14c)](){}static async[_0x2a96ba(0x13e)](_0x5dffdd){}static async[_0x2a96ba(0x145)](_0x390a41){const _0x3b31e2=_0x2a96ba,_0x324e04={'Igrkl':function(_0x10a511,_0x649a4e){return _0x10a511===_0x649a4e;},'DjnGf':function(_0x114dbb,_0x218a54){return _0x114dbb(_0x218a54);},'MkWmf':function(_0x5cfe20){return _0x5cfe20();},'BNGZC':function(_0x46500d,_0x1d5299,_0x41101e){return _0x46500d(_0x1d5299,_0x41101e);}},_0x55516c=napCatCore[_0x3b31e2(0x14d)]['profile'][_0x3b31e2(0x139)];return new Promise((_0x3d7af5,_0xf9f68a)=>{const _0x3ef6f2=_0x3b31e2,_0x4bd7c9=_0x324e04[_0x3ef6f2(0x14b)](randomUUID);let _0x157462=![];_0x324e04['BNGZC'](setTimeout,()=>{const _0x417930=_0x3ef6f2;!_0x157462&&_0xf9f68a(_0x417930(0x155));},0x1388),userDetailHandlers[_0x3ef6f2(0x151)](_0x4bd7c9,_0x5adb86=>{const _0x4f52c8=_0x3ef6f2;_0x324e04[_0x4f52c8(0x14a)](_0x5adb86['uid'],_0x390a41)&&(_0x157462=!![],userDetailHandlers[_0x4f52c8(0x156)](_0x4bd7c9),_0x324e04['DjnGf'](_0x3d7af5,_0x5adb86));}),_0x55516c[_0x3ef6f2(0x159)](_0x390a41,[0x0])[_0x3ef6f2(0x15a)](_0x26f1dc=>{});});}static async['getPSkey'](){}static async[_0x2a96ba(0x148)](_0x8e5e68,_0x1ff1c8){}}
|
|
13
src/core.lib/src/qqnt/apis/webapi.d.ts
vendored
13
src/core.lib/src/qqnt/apis/webapi.d.ts
vendored
@@ -1,13 +0,0 @@
|
|||||||
export declare class WebApi {
|
|
||||||
private static bkn;
|
|
||||||
private static skey;
|
|
||||||
private static pskey;
|
|
||||||
private static cookie;
|
|
||||||
private defaultHeaders;
|
|
||||||
constructor();
|
|
||||||
addGroupDigest(groupCode: string, msgSeq: string): Promise<any>;
|
|
||||||
getGroupDigest(groupCode: string): Promise<any>;
|
|
||||||
private genBkn;
|
|
||||||
private init;
|
|
||||||
private request;
|
|
||||||
}
|
|
@@ -1 +0,0 @@
|
|||||||
const _0x447b25=_0x44f5;(function(_0x5d3681,_0x395c87){const _0x3ebfd6=_0x44f5,_0x58ee89=_0x5d3681();while(!![]){try{const _0x1dc950=-parseInt(_0x3ebfd6(0x1ca))/0x1+parseInt(_0x3ebfd6(0x1bd))/0x2*(parseInt(_0x3ebfd6(0x1cf))/0x3)+parseInt(_0x3ebfd6(0x1c4))/0x4+parseInt(_0x3ebfd6(0x1c6))/0x5*(-parseInt(_0x3ebfd6(0x1bc))/0x6)+parseInt(_0x3ebfd6(0x1e0))/0x7*(-parseInt(_0x3ebfd6(0x1c1))/0x8)+parseInt(_0x3ebfd6(0x1d4))/0x9+-parseInt(_0x3ebfd6(0x1ce))/0xa*(parseInt(_0x3ebfd6(0x1e2))/0xb);if(_0x1dc950===_0x395c87)break;else _0x58ee89['push'](_0x58ee89['shift']());}catch(_0x469cb8){_0x58ee89['push'](_0x58ee89['shift']());}}}(_0x5824,0x266d9));function _0x5824(){const _0x59813a=['8449zENILZ','WIiGs','77MLZIXT','include','request','3048PAvCMs','16kuulLx','bkn','json','skey','352EvbWJT','&msg_seq=','CFmXU','862404fNrzqn','&bkn=','1455BKIqqx','https://qun.qq.com/cgi-bin/group_digest/digest_list?random=665&X-CROSS-ORIGIN=fetch&group_code=','headers','genBkn','129847bNmjZw','https://qun.qq.com/cgi-bin/group_digest/cancel_digest?random=665&X-CROSS-ORIGIN=fetch&group_code=','&msg_random=444021292','getGroupDigest','226020BorPhS','93831gVGRjN','JpGnD','addGroupDigest','length','afGhO','1625229eiBBXW','GET','VepUx','utBMM','YkHxF','defaultHeaders','MpBSl','init','toString','kVfpJ','cookie','&page_start=0&page_limit=20'];_0x5824=function(){return _0x59813a;};return _0x5824();}import{log}from'@/common/utils/log';function _0x44f5(_0x430e94,_0x3fc05c){const _0x582483=_0x5824();return _0x44f5=function(_0x44f52e,_0x30be5b){_0x44f52e=_0x44f52e-0x1bb;let _0x58ae39=_0x582483[_0x44f52e];return _0x58ae39;},_0x44f5(_0x430e94,_0x3fc05c);}export class WebApi{static [_0x447b25(0x1be)];static [_0x447b25(0x1c0)];static ['pskey'];static [_0x447b25(0x1de)];[_0x447b25(0x1d9)]={'User-Agent':'QQ/8.9.28.635\x20CFNetwork/1312\x20Darwin/21.0.0'};constructor(){}async[_0x447b25(0x1d1)](_0x34f337,_0x5c2aeb){const _0x305fa5=_0x447b25,_0x3aa085=_0x305fa5(0x1cb)+_0x34f337+_0x305fa5(0x1c2)+_0x5c2aeb+_0x305fa5(0x1cc),_0xbac247=await this[_0x305fa5(0x1bb)](_0x3aa085);return await _0xbac247[_0x305fa5(0x1bf)]();}async[_0x447b25(0x1cd)](_0x15fef1){const _0xaaf645=_0x447b25,_0x33c1d5={'JpGnD':function(_0x480b63,_0x41e7b7){return _0x480b63(_0x41e7b7);}},_0x1c16fd=_0xaaf645(0x1c7)+_0x15fef1+_0xaaf645(0x1df),_0x1c4d9e=await this[_0xaaf645(0x1bb)](_0x1c16fd);return _0x33c1d5[_0xaaf645(0x1d0)](log,_0x1c4d9e[_0xaaf645(0x1c8)]),await _0x1c4d9e[_0xaaf645(0x1bf)]();}[_0x447b25(0x1c9)](_0x3a8099){const _0x58828e=_0x447b25,_0x2ca5aa={'YkHxF':function(_0x5146bf,_0x35f0b3){return _0x5146bf||_0x35f0b3;},'MpBSl':function(_0x5b456c,_0x351cb6){return _0x5b456c<_0x351cb6;},'utBMM':function(_0x1697de,_0x520433){return _0x1697de+_0x520433;},'YKntL':function(_0x4eae49,_0x129bdc){return _0x4eae49&_0x129bdc;}};_0x3a8099=_0x2ca5aa[_0x58828e(0x1d8)](_0x3a8099,'');let _0x30751e=0x1505;for(let _0x2434a5=0x0;_0x2ca5aa[_0x58828e(0x1da)](_0x2434a5,_0x3a8099[_0x58828e(0x1d2)]);_0x2434a5++){const _0x255291=_0x3a8099['charCodeAt'](_0x2434a5);_0x30751e=_0x2ca5aa[_0x58828e(0x1d7)](_0x2ca5aa['utBMM'](_0x30751e,_0x30751e<<0x5),_0x255291);}return _0x2ca5aa['YKntL'](_0x30751e,0x7fffffff)[_0x58828e(0x1dc)]();}async[_0x447b25(0x1db)](){const _0x423548=_0x447b25;if(!WebApi[_0x423548(0x1be)]){}}async[_0x447b25(0x1bb)](_0x28965b,_0x544ccf=_0x447b25(0x1d5),_0x1921d6={}){const _0x84e9f9=_0x447b25,_0x31d253={'afGhO':function(_0x34ea1d,_0x5c41ae){return _0x34ea1d+_0x5c41ae;},'kVfpJ':_0x84e9f9(0x1e3),'CFmXU':function(_0x73469c,_0x591ea7,_0x1765ce,_0x31674b){return _0x73469c(_0x591ea7,_0x1765ce,_0x31674b);},'WIiGs':'request','VepUx':function(_0x47a805,_0xa98a61,_0x48042b){return _0x47a805(_0xa98a61,_0x48042b);}};await this[_0x84e9f9(0x1db)](),_0x28965b+=_0x31d253[_0x84e9f9(0x1d3)](_0x84e9f9(0x1c5),WebApi[_0x84e9f9(0x1be)]);const _0x37f976={...this[_0x84e9f9(0x1d9)],..._0x1921d6,'Cookie':WebApi[_0x84e9f9(0x1de)],'credentials':_0x31d253[_0x84e9f9(0x1dd)]};_0x31d253[_0x84e9f9(0x1c3)](log,_0x31d253[_0x84e9f9(0x1e1)],_0x28965b,_0x37f976);const _0x2b5a8b={'method':_0x544ccf,'headers':_0x37f976};return _0x31d253[_0x84e9f9(0x1d6)](fetch,_0x28965b,_0x2b5a8b);}}
|
|
11
src/core.lib/src/qqnt/apis/window.d.ts
vendored
11
src/core.lib/src/qqnt/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 {
|
|
||||||
}
|
|
@@ -1 +0,0 @@
|
|||||||
function _0x521e(){var _0x33adf8=['7059141BHboty','1270bIrDJy','386573vgVvuG','6784464wYencX','38096jnUSSo','GroupEssenceWindow','101TKMtJH','GroupNotifyFilterWindow','#/group-home-work','GroupHomeWorkWindow','132368pRXXlI','#/group-essence','15972wduSQg','318ujvPbx','2093830ZNfykx','133tGDqgg','#/group-notify-filter'];_0x521e=function(){return _0x33adf8;};return _0x521e();}var _0xad48dc=_0x22d1;(function(_0x5e7a4d,_0x98e132){var _0x3d8de1=_0x22d1,_0x3ae5b5=_0x5e7a4d();while(!![]){try{var _0x391d92=parseInt(_0x3d8de1(0x1dd))/0x1*(-parseInt(_0x3d8de1(0x1e3))/0x2)+-parseInt(_0x3d8de1(0x1e4))/0x3*(parseInt(_0x3d8de1(0x1db))/0x4)+parseInt(_0x3d8de1(0x1d4))/0x5+-parseInt(_0x3d8de1(0x1da))/0x6+parseInt(_0x3d8de1(0x1d5))/0x7*(-parseInt(_0x3d8de1(0x1e1))/0x8)+-parseInt(_0x3d8de1(0x1d7))/0x9+parseInt(_0x3d8de1(0x1d8))/0xa*(parseInt(_0x3d8de1(0x1d9))/0xb);if(_0x391d92===_0x98e132)break;else _0x3ae5b5['push'](_0x3ae5b5['shift']());}catch(_0x4ad141){_0x3ae5b5['push'](_0x3ae5b5['shift']());}}}(_0x521e,0xcc2ea));function _0x22d1(_0x1514d1,_0x59145c){var _0x521ed3=_0x521e();return _0x22d1=function(_0x22d10f,_0x50a268){_0x22d10f=_0x22d10f-0x1d4;var _0x3e5d51=_0x521ed3[_0x22d10f];return _0x3e5d51;},_0x22d1(_0x1514d1,_0x59145c);}export class NTQQWindows{static [_0xad48dc(0x1e0)]={'windowName':_0xad48dc(0x1e0),'windowUrlHash':_0xad48dc(0x1df)};static [_0xad48dc(0x1de)]={'windowName':'GroupNotifyFilterWindow','windowUrlHash':_0xad48dc(0x1d6)};static [_0xad48dc(0x1dc)]={'windowName':'GroupEssenceWindow','windowUrlHash':_0xad48dc(0x1e2)};}export class NTQQWindowApi{}
|
|
@@ -1 +0,0 @@
|
|||||||
function _0x9634(){var _0x4f7a62=['138qcUzkH','AQhpq','8ETqkLK','hkESv','13383326DCFGaZ','1415202apEvKv','171zJiCNa','912919eWeZfc','DOCUMENT','185894JtlDZu','414316MtZxXF','18640lxwwEZ','IMAGE','4OKPJIo','OTHER','119740LcVYkN','AUDIO','IrUoN','VIDEO','mJCEE'];_0x9634=function(){return _0x4f7a62;};return _0x9634();}(function(_0x34f60c,_0x13f860){var _0x521222=_0x4be1,_0x2905b3=_0x34f60c();while(!![]){try{var _0x463b86=-parseInt(_0x521222(0xf7))/0x1+-parseInt(_0x521222(0xf6))/0x2+-parseInt(_0x521222(0xf2))/0x3*(-parseInt(_0x521222(0xfa))/0x4)+parseInt(_0x521222(0xfc))/0x5*(-parseInt(_0x521222(0x101))/0x6)+-parseInt(_0x521222(0xf4))/0x7*(parseInt(_0x521222(0xef))/0x8)+-parseInt(_0x521222(0xf3))/0x9*(-parseInt(_0x521222(0xf8))/0xa)+parseInt(_0x521222(0xf1))/0xb;if(_0x463b86===_0x13f860)break;else _0x2905b3['push'](_0x2905b3['shift']());}catch(_0x429281){_0x2905b3['push'](_0x2905b3['shift']());}}}(_0x9634,0x82b24));function _0x4be1(_0x150f6b,_0x49f53){var _0x96345c=_0x9634();return _0x4be1=function(_0x4be103,_0xa966ae){_0x4be103=_0x4be103-0xef;var _0x2b74f9=_0x96345c[_0x4be103];return _0x2b74f9;},_0x4be1(_0x150f6b,_0x49f53);}export var CacheFileType;(function(_0x45b59b){var _0x3a1171=_0x4be1,_0x4f79d7={'VeYZL':_0x3a1171(0xf9),'AQhpq':_0x3a1171(0xff),'hkESv':_0x3a1171(0xfd),'IrUoN':_0x3a1171(0xf5),'mJCEE':_0x3a1171(0xfb)};_0x45b59b[_0x45b59b[_0x3a1171(0xf9)]=0x0]=_0x4f79d7['VeYZL'],_0x45b59b[_0x45b59b[_0x4f79d7[_0x3a1171(0x102)]]=0x1]=_0x4f79d7[_0x3a1171(0x102)],_0x45b59b[_0x45b59b[_0x4f79d7[_0x3a1171(0xf0)]]=0x2]=_0x4f79d7[_0x3a1171(0xf0)],_0x45b59b[_0x45b59b[_0x4f79d7['IrUoN']]=0x3]=_0x4f79d7[_0x3a1171(0xfe)],_0x45b59b[_0x45b59b[_0x4f79d7[_0x3a1171(0x100)]]=0x4]=_0x4f79d7[_0x3a1171(0x100)];}(CacheFileType||(CacheFileType={})));
|
|
14
src/core.lib/src/qqnt/entities/constructor.d.ts
vendored
14
src/core.lib/src/qqnt/entities/constructor.d.ts
vendored
@@ -1,14 +0,0 @@
|
|||||||
import { AtType, SendArkElement, SendFaceElement, SendFileElement, SendPicElement, SendPttElement, SendReplyElement, SendTextElement, SendVideoElement } from '../entities';
|
|
||||||
export declare class SendMsgElementConstructor {
|
|
||||||
static text(content: string): SendTextElement;
|
|
||||||
static at(atUid: string, atNtUid: string, atType: AtType, atName: string): SendTextElement;
|
|
||||||
static reply(msgSeq: string, msgId: string, senderUin: string, senderUinStr: string): SendReplyElement;
|
|
||||||
static pic(picPath: string, summary?: string, subType?: 0 | 1): Promise<SendPicElement>;
|
|
||||||
static file(filePath: string, fileName?: string): Promise<SendFileElement>;
|
|
||||||
static video(filePath: string, fileName?: string, diyThumbPath?: string): Promise<SendVideoElement>;
|
|
||||||
static ptt(pttPath: string): Promise<SendPttElement>;
|
|
||||||
static face(faceId: number): SendFaceElement;
|
|
||||||
static dice(resultId: number | null): SendFaceElement;
|
|
||||||
static rps(resultId: number | null): SendFaceElement;
|
|
||||||
static ark(data: any): SendArkElement;
|
|
||||||
}
|
|
File diff suppressed because one or more lines are too long
52
src/core.lib/src/qqnt/entities/group.d.ts
vendored
52
src/core.lib/src/qqnt/entities/group.d.ts
vendored
@@ -1,52 +0,0 @@
|
|||||||
import { QQLevel, Sex } from './user';
|
|
||||||
export interface Group {
|
|
||||||
groupCode: string;
|
|
||||||
maxMember: number;
|
|
||||||
memberCount: number;
|
|
||||||
groupName: string;
|
|
||||||
groupStatus: 0;
|
|
||||||
memberRole: 2;
|
|
||||||
isTop: boolean;
|
|
||||||
toppedTimestamp: '0';
|
|
||||||
privilegeFlag: number;
|
|
||||||
isConf: boolean;
|
|
||||||
hasModifyConfGroupFace: boolean;
|
|
||||||
hasModifyConfGroupName: boolean;
|
|
||||||
remarkName: string;
|
|
||||||
hasMemo: boolean;
|
|
||||||
groupShutupExpireTime: string;
|
|
||||||
personShutupExpireTime: string;
|
|
||||||
discussToGroupUin: string;
|
|
||||||
discussToGroupMaxMsgSeq: number;
|
|
||||||
discussToGroupTime: number;
|
|
||||||
groupFlagExt: number;
|
|
||||||
authGroupType: number;
|
|
||||||
groupCreditLevel: number;
|
|
||||||
groupFlagExt3: number;
|
|
||||||
groupOwnerId: {
|
|
||||||
'memberUin': string;
|
|
||||||
'memberUid': string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
export declare enum GroupMemberRole {
|
|
||||||
normal = 2,
|
|
||||||
admin = 3,
|
|
||||||
owner = 4
|
|
||||||
}
|
|
||||||
export interface GroupMember {
|
|
||||||
memberSpecialTitle?: string;
|
|
||||||
avatarPath: string;
|
|
||||||
cardName: string;
|
|
||||||
cardType: number;
|
|
||||||
isDelete: boolean;
|
|
||||||
nick: string;
|
|
||||||
qid: string;
|
|
||||||
remark: string;
|
|
||||||
role: GroupMemberRole;
|
|
||||||
shutUpTime: number;
|
|
||||||
uid: string;
|
|
||||||
uin: string;
|
|
||||||
isRobot: boolean;
|
|
||||||
sex?: Sex;
|
|
||||||
qqLevel?: QQLevel;
|
|
||||||
}
|
|
@@ -1 +0,0 @@
|
|||||||
function _0x1b34(_0x8e876c,_0x19b006){var _0x4081cd=_0x4081();return _0x1b34=function(_0x1b346b,_0x56ac77){_0x1b346b=_0x1b346b-0x15c;var _0xa26e7f=_0x4081cd[_0x1b346b];return _0xa26e7f;},_0x1b34(_0x8e876c,_0x19b006);}(function(_0x2751f4,_0x50d4d1){var _0x359eb7=_0x1b34,_0x1cca62=_0x2751f4();while(!![]){try{var _0x49d544=-parseInt(_0x359eb7(0x15c))/0x1+parseInt(_0x359eb7(0x15f))/0x2*(-parseInt(_0x359eb7(0x162))/0x3)+parseInt(_0x359eb7(0x169))/0x4+-parseInt(_0x359eb7(0x163))/0x5+parseInt(_0x359eb7(0x160))/0x6+-parseInt(_0x359eb7(0x15e))/0x7*(parseInt(_0x359eb7(0x167))/0x8)+parseInt(_0x359eb7(0x168))/0x9;if(_0x49d544===_0x50d4d1)break;else _0x1cca62['push'](_0x1cca62['shift']());}catch(_0x5c934e){_0x1cca62['push'](_0x1cca62['shift']());}}}(_0x4081,0x5c354));export var GroupMemberRole;function _0x4081(){var _0x1e9f80=['owner','GcJOL','1040FZbSnz','4749606ypAeck','1641448CsRPdi','3107MEMiUO','OuiNA','19243qqdYRA','12xClyqo','3801672uRAuBC','NsHtZ','116661MIIHfT','3001125uQXxBT','admin'];_0x4081=function(){return _0x1e9f80;};return _0x4081();}(function(_0x434f1e){var _0x493c64=_0x1b34,_0x1e9ec2={'NsHtZ':'normal','GcJOL':_0x493c64(0x164),'OuiNA':_0x493c64(0x165)};_0x434f1e[_0x434f1e[_0x1e9ec2[_0x493c64(0x161)]]=0x2]=_0x1e9ec2[_0x493c64(0x161)],_0x434f1e[_0x434f1e[_0x1e9ec2[_0x493c64(0x166)]]=0x3]=_0x1e9ec2[_0x493c64(0x166)],_0x434f1e[_0x434f1e[_0x1e9ec2[_0x493c64(0x15d)]]=0x4]=_0x493c64(0x165);}(GroupMemberRole||(GroupMemberRole={})));
|
|
@@ -1 +0,0 @@
|
|||||||
(function(_0x1a335b,_0x56de0e){var _0x506033=_0x50e6,_0x146c7d=_0x1a335b();while(!![]){try{var _0x10d1e7=parseInt(_0x506033(0xa8))/0x1+-parseInt(_0x506033(0xa6))/0x2*(-parseInt(_0x506033(0xaa))/0x3)+parseInt(_0x506033(0xac))/0x4+parseInt(_0x506033(0xa7))/0x5*(-parseInt(_0x506033(0xa3))/0x6)+parseInt(_0x506033(0xa4))/0x7+-parseInt(_0x506033(0xa9))/0x8*(parseInt(_0x506033(0xa5))/0x9)+-parseInt(_0x506033(0xab))/0xa;if(_0x10d1e7===_0x56de0e)break;else _0x146c7d['push'](_0x146c7d['shift']());}catch(_0x40b4c7){_0x146c7d['push'](_0x146c7d['shift']());}}}(_0x1fc1,0x9b216));export*from'./user';function _0x50e6(_0x1dc155,_0x32a10c){var _0x1fc10d=_0x1fc1();return _0x50e6=function(_0x50e6d1,_0x4c452c){_0x50e6d1=_0x50e6d1-0xa3;var _0x205f62=_0x1fc10d[_0x50e6d1];return _0x205f62;},_0x50e6(_0x1dc155,_0x32a10c);}export*from'./group';export*from'./msg';export*from'./notify';function _0x1fc1(){var _0x11f3d9=['9444915ZuKUfk','2020dWvUBo','52255TfdWUG','950201ZyxWlB','8xiakEF','1407KdoGan','7918380godIXk','2812956oVNMlC','402mYyiPG','7348418VtpeAU'];_0x1fc1=function(){return _0x11f3d9;};return _0x1fc1();}export*from'./cache';export*from'./constructor';
|
|
400
src/core.lib/src/qqnt/entities/msg.d.ts
vendored
400
src/core.lib/src/qqnt/entities/msg.d.ts
vendored
@@ -1,400 +0,0 @@
|
|||||||
import { GroupMemberRole } from './group';
|
|
||||||
export interface Peer {
|
|
||||||
chatType: ChatType;
|
|
||||||
peerUid: string;
|
|
||||||
guildId?: '';
|
|
||||||
}
|
|
||||||
export declare enum ElementType {
|
|
||||||
TEXT = 1,
|
|
||||||
PIC = 2,
|
|
||||||
FILE = 3,
|
|
||||||
PTT = 4,
|
|
||||||
VIDEO = 5,
|
|
||||||
FACE = 6,
|
|
||||||
REPLY = 7,
|
|
||||||
ARK = 10
|
|
||||||
}
|
|
||||||
export interface SendTextElement {
|
|
||||||
elementType: ElementType.TEXT;
|
|
||||||
elementId: '';
|
|
||||||
textElement: {
|
|
||||||
content: string;
|
|
||||||
atType: number;
|
|
||||||
atUid: string;
|
|
||||||
atTinyId: string;
|
|
||||||
atNtUid: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
export interface SendPttElement {
|
|
||||||
elementType: ElementType.PTT;
|
|
||||||
elementId: '';
|
|
||||||
pttElement: {
|
|
||||||
fileName: string;
|
|
||||||
filePath: string;
|
|
||||||
md5HexStr: string;
|
|
||||||
fileSize: number;
|
|
||||||
duration: number;
|
|
||||||
formatType: number;
|
|
||||||
voiceType: number;
|
|
||||||
voiceChangeType: number;
|
|
||||||
canConvert2Text: boolean;
|
|
||||||
waveAmplitudes: number[];
|
|
||||||
fileSubId: '';
|
|
||||||
playState: number;
|
|
||||||
autoConvertText: number;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
export declare enum PicType {
|
|
||||||
gif = 2000,
|
|
||||||
jpg = 1000
|
|
||||||
}
|
|
||||||
export declare enum PicSubType {
|
|
||||||
normal = 0,// 普通图片,大图
|
|
||||||
face = 1
|
|
||||||
}
|
|
||||||
export interface SendPicElement {
|
|
||||||
elementType: ElementType.PIC;
|
|
||||||
elementId: '';
|
|
||||||
picElement: {
|
|
||||||
md5HexStr: string;
|
|
||||||
fileSize: number | string;
|
|
||||||
picWidth: number;
|
|
||||||
picHeight: number;
|
|
||||||
fileName: string;
|
|
||||||
sourcePath: string;
|
|
||||||
original: boolean;
|
|
||||||
picType: PicType;
|
|
||||||
picSubType: PicSubType;
|
|
||||||
fileUuid: string;
|
|
||||||
fileSubId: string;
|
|
||||||
thumbFileSize: number;
|
|
||||||
summary: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
export interface SendReplyElement {
|
|
||||||
elementType: ElementType.REPLY;
|
|
||||||
elementId: '';
|
|
||||||
replyElement: {
|
|
||||||
replayMsgSeq: string;
|
|
||||||
replayMsgId: string;
|
|
||||||
senderUin: string;
|
|
||||||
senderUinStr: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
export interface SendFaceElement {
|
|
||||||
elementType: ElementType.FACE;
|
|
||||||
elementId: '';
|
|
||||||
faceElement: FaceElement;
|
|
||||||
}
|
|
||||||
export interface FileElement {
|
|
||||||
'fileMd5'?: '';
|
|
||||||
'fileName': string;
|
|
||||||
'filePath': string;
|
|
||||||
fileSize: string;
|
|
||||||
'picHeight'?: number;
|
|
||||||
'picWidth'?: number;
|
|
||||||
'picThumbPath'?: Map<number, string>;
|
|
||||||
'file10MMd5'?: '';
|
|
||||||
'fileSha'?: '';
|
|
||||||
'fileSha3'?: '';
|
|
||||||
'fileUuid'?: '';
|
|
||||||
'fileSubId'?: '';
|
|
||||||
'thumbFileSize'?: number;
|
|
||||||
fileBizId?: number;
|
|
||||||
}
|
|
||||||
export interface SendFileElement {
|
|
||||||
elementType: ElementType.FILE;
|
|
||||||
elementId: '';
|
|
||||||
fileElement: FileElement;
|
|
||||||
}
|
|
||||||
export interface SendVideoElement {
|
|
||||||
elementType: ElementType.VIDEO;
|
|
||||||
elementId: '';
|
|
||||||
videoElement: VideoElement;
|
|
||||||
}
|
|
||||||
export interface SendArkElement {
|
|
||||||
elementType: ElementType.ARK;
|
|
||||||
elementId: '';
|
|
||||||
arkElement: ArkElement;
|
|
||||||
}
|
|
||||||
export type SendMessageElement = SendTextElement | SendPttElement | SendPicElement | SendReplyElement | SendFaceElement | SendFileElement | SendVideoElement | SendArkElement;
|
|
||||||
export declare enum AtType {
|
|
||||||
notAt = 0,
|
|
||||||
atAll = 1,
|
|
||||||
atUser = 2
|
|
||||||
}
|
|
||||||
export declare enum ChatType {
|
|
||||||
friend = 1,
|
|
||||||
group = 2,
|
|
||||||
temp = 100
|
|
||||||
}
|
|
||||||
export interface PttElement {
|
|
||||||
canConvert2Text: boolean;
|
|
||||||
duration: number;
|
|
||||||
fileBizId: null;
|
|
||||||
fileId: number;
|
|
||||||
fileName: string;
|
|
||||||
filePath: string;
|
|
||||||
fileSize: string;
|
|
||||||
fileSubId: string;
|
|
||||||
fileUuid: string;
|
|
||||||
formatType: string;
|
|
||||||
invalidState: number;
|
|
||||||
md5HexStr: string;
|
|
||||||
playState: number;
|
|
||||||
progress: number;
|
|
||||||
text: string;
|
|
||||||
transferStatus: number;
|
|
||||||
translateStatus: number;
|
|
||||||
voiceChangeType: number;
|
|
||||||
voiceType: number;
|
|
||||||
waveAmplitudes: number[];
|
|
||||||
}
|
|
||||||
export interface ArkElement {
|
|
||||||
bytesData: string;
|
|
||||||
linkInfo: null;
|
|
||||||
subElementType: null;
|
|
||||||
}
|
|
||||||
export declare const IMAGE_HTTP_HOST = "https://gchat.qpic.cn";
|
|
||||||
export declare const IMAGE_HTTP_HOST_NT = "https://multimedia.nt.qq.com.cn";
|
|
||||||
export interface PicElement {
|
|
||||||
originImageUrl: string;
|
|
||||||
originImageMd5?: string;
|
|
||||||
sourcePath: string;
|
|
||||||
thumbPath: Map<number, string>;
|
|
||||||
picWidth: number;
|
|
||||||
picHeight: number;
|
|
||||||
fileSize: number;
|
|
||||||
fileName: string;
|
|
||||||
fileUuid: string;
|
|
||||||
md5HexStr?: string;
|
|
||||||
}
|
|
||||||
export declare enum GrayTipElementSubType {
|
|
||||||
INVITE_NEW_MEMBER = 12,
|
|
||||||
MEMBER_NEW_TITLE = 17
|
|
||||||
}
|
|
||||||
export interface GrayTipElement {
|
|
||||||
subElementType: GrayTipElementSubType;
|
|
||||||
revokeElement: {
|
|
||||||
operatorRole: string;
|
|
||||||
operatorUid: string;
|
|
||||||
operatorNick: string;
|
|
||||||
operatorRemark: string;
|
|
||||||
operatorMemRemark?: string;
|
|
||||||
wording: string;
|
|
||||||
};
|
|
||||||
aioOpGrayTipElement: TipAioOpGrayTipElement;
|
|
||||||
groupElement: TipGroupElement;
|
|
||||||
xmlElement: {
|
|
||||||
content: string;
|
|
||||||
};
|
|
||||||
jsonGrayTipElement: {
|
|
||||||
jsonStr: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
export declare enum FaceType {
|
|
||||||
normal = 1,// 小黄脸
|
|
||||||
normal2 = 2,// 新小黄脸, 从faceIndex 222开始?
|
|
||||||
dice = 3
|
|
||||||
}
|
|
||||||
export declare enum FaceIndex {
|
|
||||||
dice = 358,
|
|
||||||
RPS = 359
|
|
||||||
}
|
|
||||||
export interface FaceElement {
|
|
||||||
faceIndex: number;
|
|
||||||
faceType: FaceType;
|
|
||||||
faceText?: string;
|
|
||||||
packId?: string;
|
|
||||||
stickerId?: string;
|
|
||||||
sourceType?: number;
|
|
||||||
stickerType?: number;
|
|
||||||
resultId?: string;
|
|
||||||
surpriseId?: string;
|
|
||||||
randomType?: number;
|
|
||||||
}
|
|
||||||
export interface MarketFaceElement {
|
|
||||||
'itemType': 6;
|
|
||||||
'faceInfo': 1;
|
|
||||||
'emojiPackageId': 203875;
|
|
||||||
'subType': 3;
|
|
||||||
'mediaType': 0;
|
|
||||||
'imageWidth': 200;
|
|
||||||
'imageHeight': 200;
|
|
||||||
'faceName': string;
|
|
||||||
'emojiId': '094d53bd1c9ac5d35d04b08e8a6c992c';
|
|
||||||
'key': 'a8b1dd0aebc8d910';
|
|
||||||
'param': null;
|
|
||||||
'mobileParam': null;
|
|
||||||
'sourceType': null;
|
|
||||||
'startTime': null;
|
|
||||||
'endTime': null;
|
|
||||||
'emojiType': 1;
|
|
||||||
'hasIpProduct': null;
|
|
||||||
'voiceItemHeightArr': null;
|
|
||||||
'sourceName': null;
|
|
||||||
'sourceJumpUrl': null;
|
|
||||||
'sourceTypeName': null;
|
|
||||||
'backColor': null;
|
|
||||||
'volumeColor': null;
|
|
||||||
'staticFacePath': 'E:\\SystemDocuments\\QQ\\721011692\\nt_qq\\nt_data\\Emoji\\marketface\\203875\\094d53bd1c9ac5d35d04b08e8a6c992c_aio.png';
|
|
||||||
'dynamicFacePath': 'E:\\SystemDocuments\\QQ\\721011692\\nt_qq\\nt_data\\Emoji\\marketface\\203875\\094d53bd1c9ac5d35d04b08e8a6c992c';
|
|
||||||
'supportSize': [
|
|
||||||
{
|
|
||||||
'width': 300;
|
|
||||||
'height': 300;
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'width': 200;
|
|
||||||
'height': 200;
|
|
||||||
}
|
|
||||||
];
|
|
||||||
'apngSupportSize': null;
|
|
||||||
}
|
|
||||||
export interface VideoElement {
|
|
||||||
'filePath': string;
|
|
||||||
'fileName': string;
|
|
||||||
'videoMd5'?: string;
|
|
||||||
'thumbMd5'?: string;
|
|
||||||
'fileTime'?: number;
|
|
||||||
'thumbSize'?: number;
|
|
||||||
'fileFormat'?: number;
|
|
||||||
'fileSize'?: string;
|
|
||||||
'thumbWidth'?: number;
|
|
||||||
'thumbHeight'?: number;
|
|
||||||
'busiType'?: 0;
|
|
||||||
'subBusiType'?: 0;
|
|
||||||
'thumbPath'?: Map<number, any>;
|
|
||||||
'transferStatus'?: 0;
|
|
||||||
'progress'?: 0;
|
|
||||||
'invalidState'?: 0;
|
|
||||||
'fileUuid'?: string;
|
|
||||||
'fileSubId'?: '';
|
|
||||||
'fileBizId'?: null;
|
|
||||||
'originVideoMd5'?: '';
|
|
||||||
'import_rich_media_context'?: null;
|
|
||||||
'sourceVideoCodecFormat'?: number;
|
|
||||||
}
|
|
||||||
export interface MarkdownElement {
|
|
||||||
content: string;
|
|
||||||
}
|
|
||||||
export interface InlineKeyboardElementRowButton {
|
|
||||||
'id': '';
|
|
||||||
'label': string;
|
|
||||||
'visitedLabel': string;
|
|
||||||
'style': 1;
|
|
||||||
'type': 2;
|
|
||||||
'clickLimit': 0;
|
|
||||||
'unsupportTips': '请升级新版手机QQ';
|
|
||||||
'data': string;
|
|
||||||
'atBotShowChannelList': false;
|
|
||||||
'permissionType': 2;
|
|
||||||
'specifyRoleIds': [];
|
|
||||||
'specifyTinyids': [];
|
|
||||||
'isReply': false;
|
|
||||||
'anchor': 0;
|
|
||||||
'enter': false;
|
|
||||||
'subscribeDataTemplateIds': [];
|
|
||||||
}
|
|
||||||
export interface InlineKeyboardElement {
|
|
||||||
rows: [
|
|
||||||
{
|
|
||||||
buttons: InlineKeyboardElementRowButton[];
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
|
||||||
export interface TipAioOpGrayTipElement {
|
|
||||||
operateType: number;
|
|
||||||
peerUid: string;
|
|
||||||
fromGrpCodeOfTmpChat: string;
|
|
||||||
}
|
|
||||||
export declare enum TipGroupElementType {
|
|
||||||
memberIncrease = 1,
|
|
||||||
kicked = 3,// 被移出群
|
|
||||||
ban = 8
|
|
||||||
}
|
|
||||||
export interface TipGroupElement {
|
|
||||||
'type': TipGroupElementType;
|
|
||||||
'role': 0;
|
|
||||||
'groupName': string;
|
|
||||||
'memberUid': string;
|
|
||||||
'memberNick': string;
|
|
||||||
'memberRemark': string;
|
|
||||||
'adminUid': string;
|
|
||||||
'adminNick': string;
|
|
||||||
'adminRemark': string;
|
|
||||||
'createGroup': null;
|
|
||||||
'memberAdd'?: {
|
|
||||||
'showType': 1;
|
|
||||||
'otherAdd': null;
|
|
||||||
'otherAddByOtherQRCode': null;
|
|
||||||
'otherAddByYourQRCode': null;
|
|
||||||
'youAddByOtherQRCode': null;
|
|
||||||
'otherInviteOther': null;
|
|
||||||
'otherInviteYou': null;
|
|
||||||
'youInviteOther': null;
|
|
||||||
};
|
|
||||||
'shutUp'?: {
|
|
||||||
'curTime': string;
|
|
||||||
'duration': string;
|
|
||||||
'admin': {
|
|
||||||
'uid': string;
|
|
||||||
'card': string;
|
|
||||||
'name': string;
|
|
||||||
'role': GroupMemberRole;
|
|
||||||
};
|
|
||||||
'member': {
|
|
||||||
'uid': string;
|
|
||||||
'card': string;
|
|
||||||
'name': string;
|
|
||||||
'role': GroupMemberRole;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
export interface MultiForwardMsgElement {
|
|
||||||
xmlContent: string;
|
|
||||||
resId: string;
|
|
||||||
fileName: string;
|
|
||||||
}
|
|
||||||
export interface RawMessage {
|
|
||||||
id?: number;
|
|
||||||
msgId: string;
|
|
||||||
msgTime: string;
|
|
||||||
msgSeq: string;
|
|
||||||
senderUid: string;
|
|
||||||
senderUin: string;
|
|
||||||
peerUid: string;
|
|
||||||
peerUin: string;
|
|
||||||
sendNickName: string;
|
|
||||||
sendMemberName?: string;
|
|
||||||
chatType: ChatType;
|
|
||||||
sendStatus?: number;
|
|
||||||
recallTime: string;
|
|
||||||
elements: {
|
|
||||||
elementId: string;
|
|
||||||
elementType: ElementType;
|
|
||||||
replyElement: {
|
|
||||||
senderUid: string;
|
|
||||||
sourceMsgIsIncPic: boolean;
|
|
||||||
sourceMsgText: string;
|
|
||||||
replayMsgSeq: string;
|
|
||||||
};
|
|
||||||
textElement: {
|
|
||||||
atType: AtType;
|
|
||||||
atUid: string;
|
|
||||||
content: string;
|
|
||||||
atNtUid: string;
|
|
||||||
};
|
|
||||||
picElement: PicElement;
|
|
||||||
pttElement: PttElement;
|
|
||||||
arkElement: ArkElement;
|
|
||||||
grayTipElement: GrayTipElement;
|
|
||||||
faceElement: FaceElement;
|
|
||||||
videoElement: VideoElement;
|
|
||||||
fileElement: FileElement;
|
|
||||||
marketFaceElement: MarketFaceElement;
|
|
||||||
inlineKeyboardElement: InlineKeyboardElement;
|
|
||||||
markdownElement: MarkdownElement;
|
|
||||||
multiForwardMsgElement: MultiForwardMsgElement;
|
|
||||||
}[];
|
|
||||||
}
|
|
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user