mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2025-07-19 12:03:37 +00:00
Compare commits
682 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
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 |
@@ -17,5 +17,8 @@ charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
[*.bat]
|
||||
charset = latin1
|
||||
|
||||
# Unfortunately, EditorConfig doesn't support space configuration inside import braces directly.
|
||||
# You'll need to rely on your linter/formatter like ESLint or Prettier for that.
|
||||
|
@@ -4,7 +4,7 @@ module.exports = {
|
||||
'es2021': true,
|
||||
'node': true
|
||||
},
|
||||
'ignorePatterns': ['src/proto/'],
|
||||
'ignorePatterns': ['src/core/proto/'],
|
||||
'extends': [
|
||||
'eslint:recommended',
|
||||
'plugin:@typescript-eslint/recommended'
|
||||
|
3
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
3
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -10,6 +10,7 @@ body:
|
||||
在提交新的 Bug 反馈前,请确保您:
|
||||
* 已经搜索了现有的 issues,并且没有找到可以解决您问题的方法
|
||||
* 不与现有的某一 issue 重复
|
||||
* 不涉及[已经停止维护的特性](https://github.com/NapNeko/NapCatQQ?tab=readme-ov-file#挥别昨日),例如 CQ 码
|
||||
- type: input
|
||||
id: system-version
|
||||
attributes:
|
||||
@@ -78,4 +79,4 @@ body:
|
||||
attributes:
|
||||
label: OneBot 客户端运行日志
|
||||
description: 粘贴 OneBot 客户端的相关日志内容到此处
|
||||
render: shell
|
||||
render: shell
|
||||
|
7
.github/workflows/build.yml
vendored
7
.github/workflows/build.yml
vendored
@@ -1,15 +1,11 @@
|
||||
name: "Build Action"
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
permissions: write-all
|
||||
|
||||
jobs:
|
||||
Build-LiteLoader:
|
||||
if: ${{ startsWith(github.event.head_commit.message, 'build:') }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Clone Main Repository
|
||||
@@ -37,7 +33,6 @@ jobs:
|
||||
name: NapCat.Framework
|
||||
path: dist
|
||||
Build-Shell:
|
||||
if: ${{ startsWith(github.event.head_commit.message, 'build:') }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Clone Main Repository
|
||||
@@ -63,4 +58,4 @@ jobs:
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: NapCat.Shell
|
||||
path: dist
|
||||
path: dist
|
||||
|
25
.github/workflows/release.yml
vendored
25
.github/workflows/release.yml
vendored
@@ -95,14 +95,25 @@ jobs:
|
||||
steps:
|
||||
- name: Download All Artifact
|
||||
uses: actions/download-artifact@v4
|
||||
|
||||
# - name: Compress subdirectories
|
||||
# run: |
|
||||
# cd ./NapCat.Shell/
|
||||
# zip -q -r NapCat.Shell.zip *
|
||||
# cd ..
|
||||
# rm ./NapCat.Shell.zip -rf
|
||||
# mv ./NapCat.Shell/NapCat.Shell.zip ./
|
||||
- name: Compress subdirectories
|
||||
run: |
|
||||
for dir in */; do
|
||||
base=$(basename "$dir")
|
||||
zip -r "${base}.zip" "$dir"
|
||||
done
|
||||
|
||||
cd ./NapCat.Shell/
|
||||
zip -q -r NapCat.Shell.zip *
|
||||
cd ..
|
||||
cd ./NapCat.Framework/
|
||||
zip -q -r NapCat.Framework.zip *
|
||||
cd ..
|
||||
rm ./NapCat.Shell.zip -rf
|
||||
rm ./NapCat.Framework.zip -rf
|
||||
mv ./NapCat.Shell/NapCat.Shell.zip ./
|
||||
mv ./NapCat.Framework/NapCat.Framework.zip ./
|
||||
- name: Extract version from tag
|
||||
run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_ENV
|
||||
|
||||
@@ -118,6 +129,4 @@ jobs:
|
||||
files: |
|
||||
NapCat.Framework.zip
|
||||
NapCat.Shell.zip
|
||||
# NapCat.darwin.x64.zip
|
||||
# NapCat.darwin.arm64.zip
|
||||
draft: true
|
||||
|
6
LICENSE
6
LICENSE
@@ -1,4 +1,4 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
GNU GENERAL PUBLIC Without Social media promotion LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
|
||||
@@ -111,6 +111,10 @@ above, provided that you also meet all of these conditions:
|
||||
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
|
||||
|
38
README.md
38
README.md
@@ -3,39 +3,41 @@
|
||||
</div>
|
||||
|
||||
---
|
||||
## 欢迎回来
|
||||
NapCatQQ (aka 猫猫框架) 是现代化的基于 NTQQ 的 Bot 协议端实现。
|
||||
|
||||
## 项目介绍
|
||||
NapCatQQ 是现代化的基于 NTQQ 的 Bot 协议端实现。
|
||||
|
||||
## 项目优势
|
||||
## 猫猫技能
|
||||
- [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
|
||||
|
||||
### V2 Roadmap
|
||||
|
||||
- **依赖注入**:彻底理清模块间依赖,明确加载顺序,根除滥用变量导出、`setTimeout` 初始化
|
||||
- **ES 出包**:可读性更高,报错更可读
|
||||
- **代码优化**:减少代码冗余,提高代码质量
|
||||
- **输出标准化**:统一控制台输出格式
|
||||
|
||||
## 如何使用
|
||||
## 使用猫猫
|
||||
|
||||
可前往 [Release](https://github.com/NapNeko/NapCatQQ/releases/) 页面下载最新版本
|
||||
|
||||
**首次使用**请务必前往[官方文档](https://napneko.github.io/)查看使用教程。
|
||||
|
||||
## 相关链接
|
||||
## 回家旅途
|
||||
[QQ Group](https://qm.qq.com/q/VfjAq5HIMS)
|
||||
|
||||
[Telegram Link](https://t.me/+nLZEnpne-pQ1OWFl)
|
||||
|
||||
## 鸣谢名单
|
||||
## 猫猫朋友
|
||||
感谢 [LLOneBot](https://github.com/LLOneBot/LLOneBot) 提供部分参考
|
||||
|
||||
[Lagrange](https://github.com/LagrangeDev/Lagrange.Core)
|
||||
感谢 [Lagrange](https://github.com/LagrangeDev/Lagrange.Core) 对本项目的大力支持
|
||||
|
||||
不过最最重要的 还是需要感谢屏幕前的你哦~
|
||||
|
||||
---
|
||||
|
||||
**任何使用本仓库代码的地方,都应当严格遵守[本仓库开源许可](./LICENSE)。**
|
||||
## 约法三章
|
||||
> [!CAUTION]\
|
||||
> **请不要在 QQ 官方群聊和任何影响力较大的简中互联网平台(包括但不限于: 哔哩哔哩,微博,知乎,抖音等)发布和讨论*任何*与本项目存在相关性的信息**
|
||||
|
||||
## 附加协议
|
||||
禁止任何项目使用core部分代码用于二次开发与分发
|
||||
任何使用本仓库代码的地方,都应当严格遵守[本仓库开源许可](./LICENSE)。**此外,禁止任何项目未经授权二次分发或基于 NapCat 代码开发。**
|
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本脚本启动即可
|
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"manifest_version": 4,
|
||||
"type": "extension",
|
||||
"name": "NapCat",
|
||||
"slug": "NapCat",
|
||||
"description": "OneBot v11 protocol implementation with NapCat logic",
|
||||
"version": "2.0.1",
|
||||
"name": "NapCatQQ",
|
||||
"slug": "NapCat.Framework",
|
||||
"description": "高性能的 OneBot 11 协议实现",
|
||||
"version": "2.6.4",
|
||||
"icon": "./logo.png",
|
||||
"authors": [
|
||||
{
|
||||
@@ -30,4 +30,4 @@
|
||||
"main": "./liteloader.cjs",
|
||||
"preload": "./preload.cjs"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
18
package.json
18
package.json
@@ -2,7 +2,7 @@
|
||||
"name": "napcat",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"version": "2.0.1",
|
||||
"version": "2.6.4",
|
||||
"scripts": {
|
||||
"build:framework": "vite build --mode framework",
|
||||
"build:shell": "vite build --mode shell",
|
||||
@@ -11,9 +11,6 @@
|
||||
"depend": "cd dist && npm install --omit=dev"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.24.7",
|
||||
"@babel/plugin-proposal-class-properties": "^7.18.6",
|
||||
"@babel/plugin-proposal-decorators": "^7.24.7",
|
||||
"@babel/preset-typescript": "^7.24.7",
|
||||
"@log4js-node/log4js-api": "^1.0.2",
|
||||
"@protobuf-ts/plugin": "^2.9.4",
|
||||
@@ -21,25 +18,17 @@
|
||||
"@rollup/plugin-typescript": "^11.1.6",
|
||||
"@types/cors": "^2.8.17",
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/figlet": "^1.5.8",
|
||||
"@types/fluent-ffmpeg": "^2.1.24",
|
||||
"@types/jest": "^29.5.12",
|
||||
"@types/node": "^22.0.1",
|
||||
"@types/qrcode-terminal": "^0.12.2",
|
||||
"@types/ws": "^8.5.12",
|
||||
"@typescript-eslint/eslint-plugin": "^7.4.0",
|
||||
"@typescript-eslint/parser": "^7.4.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.3.0",
|
||||
"@typescript-eslint/parser": "^8.3.0",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-import-resolver-typescript": "^3.6.1",
|
||||
"eslint-plugin-import": "^2.29.1",
|
||||
"i": "^0.3.7",
|
||||
"javascript-obfuscator": "^4.1.0",
|
||||
"rollup": "^4.13.2",
|
||||
"rollup-plugin-dts": "^6.1.0",
|
||||
"rollup-plugin-obfuscator": "^1.1.0",
|
||||
"typescript": "^5.3.3",
|
||||
"vite": "^5.2.6",
|
||||
"vite-plugin-babel": "^1.2.0",
|
||||
"vite-plugin-cp": "^4.0.8",
|
||||
"vite-plugin-dts": "^3.8.2",
|
||||
"vite-tsconfig-paths": "^4.3.2"
|
||||
@@ -57,6 +46,7 @@
|
||||
"image-size": "^1.1.1",
|
||||
"json-schema-to-ts": "^3.1.0",
|
||||
"log4js": "^6.9.1",
|
||||
"protobufjs": "~7.4.0",
|
||||
"qrcode-terminal": "^0.12.0",
|
||||
"silk-wasm": "^3.6.1",
|
||||
"strtok3": "8.0.1",
|
||||
|
@@ -1,45 +0,0 @@
|
||||
# Dont Use This Script
|
||||
# 2024.7.3
|
||||
function Get-QQpath {
|
||||
try {
|
||||
$key = Get-ItemProperty -Path "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\QQ"
|
||||
$uninstallString = $key.UninstallString
|
||||
return [System.IO.Path]::GetDirectoryName($uninstallString) + "\QQ.exe"
|
||||
}
|
||||
catch {
|
||||
throw "get QQ path error: $_"
|
||||
}
|
||||
}
|
||||
function Select-QQPath {
|
||||
Add-Type -AssemblyName System.Windows.Forms
|
||||
[System.Windows.Forms.Application]::EnableVisualStyles()
|
||||
|
||||
$dialogTitle = "Select QQ.exe"
|
||||
|
||||
$filePicker = New-Object System.Windows.Forms.OpenFileDialog
|
||||
$filePicker.Title = $dialogTitle
|
||||
$filePicker.Filter = "Executable Files (*.exe)|*.exe|All Files (*.*)|*.*"
|
||||
$filePicker.FilterIndex = 1
|
||||
$null = $filePicker.ShowDialog()
|
||||
if (-not ($filePicker.FileName)) {
|
||||
throw "User did not select an .exe file."
|
||||
}
|
||||
return $filePicker.FileName
|
||||
}
|
||||
|
||||
$params = $args -join " "
|
||||
Try {
|
||||
$QQpath = Get-QQpath
|
||||
}
|
||||
Catch {
|
||||
$QQpath = Select-QQPath
|
||||
}
|
||||
|
||||
if (!(Test-Path $QQpath)) {
|
||||
throw "provided QQ path is invalid: $QQpath"
|
||||
}
|
||||
|
||||
$Bootfile = Join-Path $PSScriptRoot "napcat.mjs"
|
||||
$env:ELECTRON_RUN_AS_NODE = 1
|
||||
$commandInfo = Get-Command $QQpath -ErrorAction Stop
|
||||
Start-Process powershell -ArgumentList "-noexit", "-noprofile", "-command &{& chcp 65001;& '$($commandInfo.Path)' --enable-logging }"
|
@@ -1,90 +0,0 @@
|
||||
@echo off
|
||||
REM 检查当前会话是否具有管理员权限
|
||||
openfiles >nul 2>&1
|
||||
if %errorlevel% neq 0 (
|
||||
REM 如果不是管理员,则重新启动脚本以管理员模式运行
|
||||
echo 请求管理员权限...
|
||||
powershell -Command "Start-Process cmd -ArgumentList '/c %~f0 %*' -Verb RunAs"
|
||||
exit /b
|
||||
)
|
||||
|
||||
REM 设置当前工作目录
|
||||
cd /d %~dp0
|
||||
|
||||
REM 获取当前目录路径
|
||||
set currentPath=%cd%
|
||||
set currentPath=%currentPath:\=/%
|
||||
|
||||
REM 生成JavaScript代码
|
||||
set "jsCode=(async () =^>await import('file:///%currentPath%/napcat.mjs'))();"
|
||||
|
||||
REM 将JavaScript代码保存到文件中
|
||||
echo %jsCode% > loadScript.js
|
||||
echo JavaScript code has been generated and saved to loadScript.js
|
||||
|
||||
REM 设置NAPCAT_PATH环境变量为 当前目录的loadScript.js地址
|
||||
set NAPCAT_PATH=%cd%\loadScript.js
|
||||
|
||||
REM 获取QQ路径
|
||||
|
||||
|
||||
:loop_read
|
||||
for /f "tokens=2*" %%a in ('reg query "HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\QQ" /v "UninstallString"') do (
|
||||
set RetString=%%b
|
||||
goto :napcat_boot
|
||||
)
|
||||
|
||||
:napcat_boot
|
||||
for %%a in (%RetString%) do (
|
||||
set "pathWithoutUninstall=%%~dpa"
|
||||
)
|
||||
|
||||
SET QQPath=%pathWithoutUninstall%QQ.exe
|
||||
|
||||
REM 拿不到QQ路径则退出
|
||||
if not exist "%QQpath%" (
|
||||
echo provided QQ path is invalid: %QQpath%
|
||||
pause
|
||||
exit /b
|
||||
)
|
||||
|
||||
REM 收集dbghelp.dll路径和HASH信息
|
||||
set QQdir=%~dp0
|
||||
set oldDllPath=%QQdir%dbghelp.dll
|
||||
set newDllPath=%currentPath%\dbghelp.dll
|
||||
|
||||
for /f "tokens=*" %%A in ('certutil -hashfile "%oldDllPath%" MD5') do (
|
||||
if not defined oldDllHash set oldDllHash=%%A
|
||||
)
|
||||
for /f "tokens=*" %%A in ('certutil -hashfile "%newDllPath%" MD5') do (
|
||||
if not defined newDllHash set newDllHash=%%A
|
||||
)
|
||||
|
||||
REM 如果文件一致则跳过
|
||||
if "%oldDllHash%" neq "%newDllHash%" (
|
||||
tasklist /fi "imagename eq QQ.exe" 2>nul | find /i "QQ.exe" >nul
|
||||
if %errorlevel% equ 0 (
|
||||
REM 文件占用则退出
|
||||
echo dbghelp.dll is in use, cannot continue.
|
||||
) else (
|
||||
REM 文件未占用则尝试覆盖
|
||||
copy /y "%newDllPath%" "%oldDllPath%"
|
||||
if %errorlevel% neq 0 (
|
||||
echo Failed to copy dbghelp.dll
|
||||
pause
|
||||
exit /b
|
||||
) else (
|
||||
echo dbghelp.dll has been copied to %QQdir%
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
REM 带参数启动QQ
|
||||
REM 判断wt是否存在,存在则通过wt启动,不存在则通过cmd启动
|
||||
REM %QQPath% --enable-logging %*
|
||||
where wt >nul 2>nul
|
||||
if %errorlevel% equ 0 (
|
||||
wt "cmd" /c "%QQPath%" --enable-logging %*
|
||||
) else (
|
||||
"%QQPath%" --enable-logging %*
|
||||
)
|
@@ -1,123 +0,0 @@
|
||||
# 检查当前会话是否具有管理员权限
|
||||
function Test-Administrator {
|
||||
$user = [Security.Principal.WindowsIdentity]::GetCurrent()
|
||||
(New-Object Security.Principal.WindowsPrincipal $user).IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)
|
||||
}
|
||||
|
||||
if (-not (Test-Administrator)) {
|
||||
# 如果不是管理员,则重新启动脚本以管理员模式运行
|
||||
$scriptPath = $myInvocation.MyCommand.Path
|
||||
if (-not $scriptPath) {
|
||||
$scriptPath = $PSCommandPath
|
||||
}
|
||||
$newProcess = New-Object System.Diagnostics.ProcessStartInfo "powershell";
|
||||
$newProcess.Arguments = "-File `"$scriptPath`" $args"
|
||||
$newProcess.Verb = "runas";
|
||||
[System.Diagnostics.Process]::Start($newProcess);
|
||||
exit
|
||||
}
|
||||
|
||||
function Get-QQpath {
|
||||
try {
|
||||
$key = Get-ItemProperty -Path "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\QQ"
|
||||
$uninstallString = $key.UninstallString
|
||||
return [System.IO.Path]::GetDirectoryName($uninstallString) + "\QQ.exe"
|
||||
}
|
||||
catch {
|
||||
throw "get QQ path error: $_"
|
||||
}
|
||||
}
|
||||
function Select-QQPath {
|
||||
Add-Type -AssemblyName System.Windows.Forms
|
||||
[System.Windows.Forms.Application]::EnableVisualStyles()
|
||||
|
||||
$dialogTitle = "Select QQ.exe"
|
||||
|
||||
$filePicker = New-Object System.Windows.Forms.OpenFileDialog
|
||||
$filePicker.Title = $dialogTitle
|
||||
$filePicker.Filter = "Executable Files (*.exe)|*.exe|All Files (*.*)|*.*"
|
||||
$filePicker.FilterIndex = 1
|
||||
$null = $filePicker.ShowDialog()
|
||||
if (-not ($filePicker.FileName)) {
|
||||
throw "User did not select an .exe file."
|
||||
}
|
||||
return $filePicker.FileName
|
||||
}
|
||||
|
||||
# 设置当前工作目录
|
||||
$scriptDirectory = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent
|
||||
Set-Location $scriptDirectory
|
||||
|
||||
# 获取当前目录路径
|
||||
$currentPath = Get-Location
|
||||
|
||||
# 替换\为/
|
||||
$currentPath = $currentPath -replace '\\', '/'
|
||||
|
||||
# 生成JavaScript代码
|
||||
$jsCode = @"
|
||||
(async () => {
|
||||
await import('file:///$currentPath/napcat.mjs');
|
||||
})();
|
||||
"@
|
||||
|
||||
# 将JavaScript代码保存到文件中
|
||||
$jsFilePath = Join-Path $currentPath "loadScript.js"
|
||||
$jsCode | Out-File -FilePath $jsFilePath -Encoding UTF8
|
||||
|
||||
Write-Output "JavaScript code has been generated and saved to $jsFilePath"
|
||||
# 设置NAPCAT_PATH环境变量为 当前目录的loadScript.js地址
|
||||
$env:NAPCAT_PATH = $jsFilePath
|
||||
|
||||
$params = $args -join " "
|
||||
Try {
|
||||
$QQpath = Get-QQpath
|
||||
}
|
||||
Catch {
|
||||
$QQpath = Select-QQPath
|
||||
}
|
||||
# 拿不到QQ路径则退出
|
||||
if (!(Test-Path $QQpath)) {
|
||||
Write-Output "provided QQ path is invalid: $QQpath"
|
||||
Read-Host "Press any key to continue..."
|
||||
exit
|
||||
}
|
||||
|
||||
$commandInfo = Get-Command $QQpath -ErrorAction Stop
|
||||
|
||||
# 收集dbghelp.dll路径和HASH信息
|
||||
$QQpath = Split-Path $QQpath
|
||||
$oldDllPath = Join-Path $QQpath "dbghelp.dll"
|
||||
$oldDllHash = Get-FileHash $oldDllPath -Algorithm MD5
|
||||
$newDllPath = Join-Path $currentPath "dbghelp.dll"
|
||||
$newDllHash = Get-FileHash $newDllPath -Algorithm MD5
|
||||
# 如果文件一致则跳过
|
||||
if ($oldDllHash.Hash -ne $newDllHash.Hash) {
|
||||
$processes = Get-Process -Name QQ -ErrorAction SilentlyContinue
|
||||
if ($processes) {
|
||||
# 文件占用则退出
|
||||
Write-Output "dbghelp.dll is in use by the following processes:"
|
||||
$processes | ForEach-Object { Write-Output "$($_.Id) $($_.Name) $($_.Path)" }
|
||||
Write-Output "dbghelp.dll is in use, cannot continue."
|
||||
Read-Host "Press any key to continue..."
|
||||
exit
|
||||
} else {
|
||||
# 文件未占用则尝试覆盖
|
||||
try {
|
||||
Copy-Item -Path "$newDllPath" -Destination "$oldDllPath" -Force
|
||||
Write-Output "dbghelp.dll has been copied to $QQpath"
|
||||
} catch {
|
||||
Write-Output "Failed to copy dbghelp.dll: $_"
|
||||
Read-Host "Press any key to continue..."
|
||||
exit
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# 带参数启动QQ
|
||||
try {
|
||||
Start-Process powershell -ArgumentList '-noexit', '-noprofile', "-command &{& chcp 65001;& '$($commandInfo.Path)' --enable-logging $params}" -NoNewWindow -ErrorAction Stop
|
||||
} catch {
|
||||
Write-Output "Failed to start process as administrator: $_"
|
||||
Read-Host "Press any key to continue..."
|
||||
}
|
@@ -1,93 +0,0 @@
|
||||
@echo off
|
||||
REM 检查当前会话是否具有管理员权限
|
||||
openfiles >nul 2>&1
|
||||
if %errorlevel% neq 0 (
|
||||
REM 如果不是管理员,则重新启动脚本以管理员模式运行
|
||||
echo 请求管理员权限...
|
||||
where wt >nul 2>nul
|
||||
if %errorlevel% equ 0 (
|
||||
powershell -Command "Start-Process cmd -ArgumentList ' /c %~f0 %*' -Verb RunAs"
|
||||
) else (
|
||||
powershell -Command "Start-Process wt -ArgumentList 'cmd /c %~f0 %*' -Verb RunAs"
|
||||
)
|
||||
|
||||
REM wt "cmd" /c "%~f0 %*"
|
||||
exit /b
|
||||
)
|
||||
|
||||
REM 设置当前工作目录
|
||||
cd /d %~dp0
|
||||
|
||||
REM 获取当前目录路径
|
||||
set currentPath=%cd%
|
||||
set currentPath=%currentPath:\=/%
|
||||
|
||||
REM 生成JavaScript代码
|
||||
set "jsCode=(async () =^>await import('file:///%currentPath%/napcat.mjs'))();"
|
||||
|
||||
REM 将JavaScript代码保存到文件中
|
||||
echo %jsCode% > loadScript.js
|
||||
echo JavaScript code has been generated and saved to loadScript.js
|
||||
|
||||
REM 设置NAPCAT_PATH环境变量为 当前目录的loadScript.js地址
|
||||
set NAPCAT_PATH=%cd%\loadScript.js
|
||||
|
||||
REM 获取QQ路径
|
||||
|
||||
|
||||
:loop_read
|
||||
for /f "tokens=2*" %%a in ('reg query "HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\QQ" /v "UninstallString"') do (
|
||||
set RetString=%%b
|
||||
goto :napcat_boot
|
||||
)
|
||||
|
||||
:napcat_boot
|
||||
for %%a in (%RetString%) do (
|
||||
set "pathWithoutUninstall=%%~dpa"
|
||||
)
|
||||
|
||||
SET QQPath=%pathWithoutUninstall%QQ.exe
|
||||
|
||||
REM 拿不到QQ路径则退出
|
||||
if not exist "%QQpath%" (
|
||||
echo provided QQ path is invalid: %QQpath%
|
||||
pause
|
||||
exit /b
|
||||
)
|
||||
|
||||
REM 收集dbghelp.dll路径和HASH信息
|
||||
set QQdir=%~dp0
|
||||
set oldDllPath=%QQdir%dbghelp.dll
|
||||
set newDllPath=%currentPath%\dbghelp.dll
|
||||
|
||||
for /f "tokens=*" %%A in ('certutil -hashfile "%oldDllPath%" MD5') do (
|
||||
if not defined oldDllHash set oldDllHash=%%A
|
||||
)
|
||||
for /f "tokens=*" %%A in ('certutil -hashfile "%newDllPath%" MD5') do (
|
||||
if not defined newDllHash set newDllHash=%%A
|
||||
)
|
||||
|
||||
REM 如果文件一致则跳过
|
||||
if "%oldDllHash%" neq "%newDllHash%" (
|
||||
tasklist /fi "imagename eq QQ.exe" 2>nul | find /i "QQ.exe" >nul
|
||||
if %errorlevel% equ 0 (
|
||||
REM 文件占用则退出
|
||||
echo dbghelp.dll is in use, cannot continue.
|
||||
) else (
|
||||
REM 文件未占用则尝试覆盖
|
||||
copy /y "%newDllPath%" "%oldDllPath%"
|
||||
if %errorlevel% neq 0 (
|
||||
echo Failed to copy dbghelp.dll
|
||||
pause
|
||||
exit /b
|
||||
) else (
|
||||
echo dbghelp.dll has been copied to %QQdir%
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
REM 带参数启动QQ
|
||||
REM 判断wt是否存在,存在则通过wt启动,不存在则通过cmd启动
|
||||
REM %QQPath% --enable-logging %*
|
||||
chcp 65001
|
||||
"%QQPath%" --enable-logging %*
|
2
script/KillQQ.bat
Normal file
2
script/KillQQ.bat
Normal file
@@ -0,0 +1,2 @@
|
||||
@echo off
|
||||
taskkill /f /im QQ.exe
|
Binary file not shown.
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 {};
|
||||
}
|
||||
}
|
@@ -4,13 +4,13 @@ import type { NapCatCore } from '@/core';
|
||||
|
||||
export abstract class ConfigBase<T> {
|
||||
name: string;
|
||||
coreContext: NapCatCore;
|
||||
core: NapCatCore;
|
||||
configPath: string;
|
||||
configData: T = {} as T;
|
||||
|
||||
protected constructor(name: string, coreContext: NapCatCore, configPath: string) {
|
||||
protected constructor(name: string, core: NapCatCore, configPath: string) {
|
||||
this.name = name;
|
||||
this.coreContext = coreContext;
|
||||
this.core = core;
|
||||
this.configPath = configPath;
|
||||
fs.mkdirSync(this.configPath, { recursive: true });
|
||||
this.read();
|
||||
@@ -22,14 +22,19 @@ export abstract class ConfigBase<T> {
|
||||
}
|
||||
|
||||
getConfigPath(pathName: string | undefined): string {
|
||||
const suffix = pathName ? `_${pathName}` : '';
|
||||
const filename = `${this.name}${suffix}.json`;
|
||||
return path.join(this.configPath, filename);
|
||||
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.coreContext.context.logger;
|
||||
const configPath = this.getConfigPath(this.coreContext.selfInfo.uin);
|
||||
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'));
|
||||
@@ -53,9 +58,9 @@ export abstract class ConfigBase<T> {
|
||||
}
|
||||
|
||||
|
||||
save(newConfigData: T = this.configData as T) {
|
||||
const logger = this.coreContext.context.logger;
|
||||
const selfInfo = this.coreContext.selfInfo;
|
||||
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 {
|
@@ -1,7 +1,8 @@
|
||||
import { NodeIQQNTWrapperSession } from '@/core/wrapper/wrapper';
|
||||
import { NodeIQQNTWrapperSession } from '@/core/wrapper';
|
||||
import { randomUUID } from 'crypto';
|
||||
import { ListenerNamingMapping, ServiceNamingMapping } from '@/core';
|
||||
|
||||
interface Internal_MapKey {
|
||||
interface InternalMapKey {
|
||||
timeout: number;
|
||||
createtime: number;
|
||||
func: (...arg: any[]) => any;
|
||||
@@ -10,37 +11,27 @@ interface Internal_MapKey {
|
||||
|
||||
export type ListenerClassBase = Record<string, string>;
|
||||
|
||||
export interface ListenerIBase {
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-new
|
||||
new(listener: any): ListenerClassBase;
|
||||
}
|
||||
|
||||
export class LegacyNTEventWrapper {
|
||||
private listenerMapping: Record<string, ListenerIBase>; //ListenerName-Unique -> Listener构造函数
|
||||
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, Internal_MapKey>>>(); //tasks ListenerMainName -> ListenerSubName-> uuid -> {timeout,createtime,func}
|
||||
private EventTask = new Map<string, Map<string, Map<string, InternalMapKey>>>(); //tasks ListenerMainName -> ListenerSubName-> uuid -> {timeout,createtime,func}
|
||||
|
||||
constructor(
|
||||
listenerMapping: Record<string, ListenerIBase>,
|
||||
wrapperSession: NodeIQQNTWrapperSession,
|
||||
) {
|
||||
this.listenerMapping = listenerMapping;
|
||||
this.WrapperSession = wrapperSession;
|
||||
}
|
||||
|
||||
createProxyDispatch(ListenerMainName: string) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
||||
const current = this;
|
||||
const dispatcherListenerFunc = this.dispatcherListener.bind(this);
|
||||
return new Proxy(
|
||||
{},
|
||||
{
|
||||
get(target: any, prop: any, receiver: any) {
|
||||
// console.log('get', prop, typeof target[prop]);
|
||||
if (typeof target[prop] === 'undefined') {
|
||||
// 如果方法不存在,返回一个函数,这个函数调用existentMethod
|
||||
return (...args: any[]) => {
|
||||
current.dispatcherListener.apply(current, [ListenerMainName, prop, ...args]).then();
|
||||
dispatcherListenerFunc(ListenerMainName, prop, ...args).then();
|
||||
};
|
||||
}
|
||||
// 如果方法存在,正常返回
|
||||
@@ -50,7 +41,13 @@ export class LegacyNTEventWrapper {
|
||||
);
|
||||
}
|
||||
|
||||
createEventFunction<T extends (...args: any) => any>(eventName: string): T | undefined {
|
||||
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>> };
|
||||
@@ -58,8 +55,6 @@ export class LegacyNTEventWrapper {
|
||||
if (eventNameArr.length > 1) {
|
||||
const serviceName = 'get' + eventNameArr[0].replace('NodeIKernel', '');
|
||||
const eventName = eventNameArr[1];
|
||||
//getNodeIKernelGroupListener,GroupService
|
||||
//console.log('2', eventName);
|
||||
const services = (this.WrapperSession as unknown as eventType)[serviceName]();
|
||||
let event = services[eventName];
|
||||
//重新绑定this
|
||||
@@ -72,78 +67,78 @@ export class LegacyNTEventWrapper {
|
||||
}
|
||||
|
||||
createListenerFunction<T>(listenerMainName: string, uniqueCode: string = ''): T {
|
||||
const ListenerType = this.listenerMapping![listenerMainName];
|
||||
let Listener = this.listenerManager.get(listenerMainName + uniqueCode);
|
||||
if (!Listener && ListenerType) {
|
||||
Listener = new ListenerType(this.createProxyDispatch(listenerMainName));
|
||||
const ServiceSubName = listenerMainName.match(/^NodeIKernel(.*?)Listener$/)![1];
|
||||
const Service = 'NodeIKernel' + ServiceSubName + 'Service/addKernel' + ServiceSubName + 'Listener';
|
||||
const addfunc = this.createEventFunction<(listener: T) => number>(Service);
|
||||
addfunc!(Listener as T);
|
||||
//console.log(addfunc!(Listener as 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 Listener as T;
|
||||
return existListener as T;
|
||||
}
|
||||
|
||||
//统一回调清理事件
|
||||
async dispatcherListener(ListenerMainName: string, ListenerSubName: string, ...args: any[]) {
|
||||
//console.log("[EventDispatcher]",ListenerMainName, ListenerSubName, ...args);
|
||||
this.EventTask.get(ListenerMainName)
|
||||
?.get(ListenerSubName)
|
||||
?.forEach((task, uuid) => {
|
||||
//console.log(task.func, uuid, task.createtime, task.timeout);
|
||||
if (task.createtime + task.timeout < Date.now()) {
|
||||
this.EventTask.get(ListenerMainName)?.get(ListenerSubName)?.delete(uuid);
|
||||
return;
|
||||
}
|
||||
if (task.checker && task.checker(...args)) {
|
||||
if (task?.checker?.(...args)) {
|
||||
task.func(...args);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async callNoListenerEvent<EventType extends (...args: any[]) => Promise<any> | any>(
|
||||
EventName = '',
|
||||
timeout: number = 3000,
|
||||
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>
|
||||
) {
|
||||
return new Promise<Awaited<ReturnType<EventType>>>(async (resolve, reject) => {
|
||||
const EventFunc = this.createEventFunction<EventType>(EventName);
|
||||
let complete = false;
|
||||
setTimeout(() => {
|
||||
if (!complete) {
|
||||
reject(new Error('NTEvent EventName:' + EventName + ' timeout'));
|
||||
}
|
||||
}, timeout);
|
||||
const retData = await EventFunc!(...args);
|
||||
complete = true;
|
||||
resolve(retData);
|
||||
});
|
||||
): Promise<Awaited<ReturnType<EventType>>> {
|
||||
return (this.createEventFunction(serviceAndMethod))!(...args);
|
||||
}
|
||||
|
||||
async RegisterListen<ListenerType extends (...args: any[]) => void>(
|
||||
ListenerName = '',
|
||||
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 = ListenerName.split('/');
|
||||
const ListenerNameList = listenerAndMethod.split('/');
|
||||
const ListenerMainName = ListenerNameList[0];
|
||||
const ListenerSubName = ListenerNameList[1];
|
||||
const id = randomUUID();
|
||||
let complete = 0;
|
||||
let retData: Parameters<ListenerType> | undefined = undefined;
|
||||
const databack = () => {
|
||||
|
||||
function sendDataCallback() {
|
||||
if (complete == 0) {
|
||||
reject(new Error(' ListenerName:' + ListenerName + ' timeout'));
|
||||
reject(new Error(' ListenerName:' + listenerAndMethod + ' timeout'));
|
||||
} else {
|
||||
resolve(retData!);
|
||||
}
|
||||
};
|
||||
const timeoutRef = setTimeout(databack, timeout);
|
||||
const eventCallbak = {
|
||||
}
|
||||
|
||||
const timeoutRef = setTimeout(sendDataCallback, timeout);
|
||||
const eventCallback = {
|
||||
timeout: timeout,
|
||||
createtime: Date.now(),
|
||||
checker: checker,
|
||||
@@ -152,7 +147,7 @@ export class LegacyNTEventWrapper {
|
||||
retData = args;
|
||||
if (complete >= waitTimes) {
|
||||
clearTimeout(timeoutRef);
|
||||
databack();
|
||||
sendDataCallback();
|
||||
}
|
||||
},
|
||||
};
|
||||
@@ -162,36 +157,46 @@ export class LegacyNTEventWrapper {
|
||||
if (!this.EventTask.get(ListenerMainName)?.get(ListenerSubName)) {
|
||||
this.EventTask.get(ListenerMainName)?.set(ListenerSubName, new Map());
|
||||
}
|
||||
this.EventTask.get(ListenerMainName)?.get(ListenerSubName)?.set(id, eventCallbak);
|
||||
this.EventTask.get(ListenerMainName)?.get(ListenerSubName)?.set(id, eventCallback);
|
||||
this.createListenerFunction(ListenerMainName);
|
||||
});
|
||||
}
|
||||
|
||||
async CallNormalEvent<
|
||||
EventType extends (...args: any[]) => Promise<any>,
|
||||
ListenerType extends (...args: any[]) => void
|
||||
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]
|
||||
>(
|
||||
EventName = '',
|
||||
ListenerName = '',
|
||||
waitTimes = 1,
|
||||
timeout: number = 3000,
|
||||
checker: (...args: Parameters<ListenerType>) => boolean,
|
||||
...args: Parameters<EventType>
|
||||
serviceAndMethod: `${Service}/${ServiceMethod}`,
|
||||
listenerAndMethod: `${Listener}/${ListenerMethod}`,
|
||||
args: Parameters<EventType>,
|
||||
checkerEvent: (ret: Awaited<ReturnType<EventType>>) => boolean = () => true,
|
||||
checkerListener: (...args: Parameters<ListenerType>) => boolean = () => true,
|
||||
callbackTimesToWait = 1,
|
||||
timeout = 5000,
|
||||
) {
|
||||
return new Promise<[EventRet: Awaited<ReturnType<EventType>>, ...Parameters<ListenerType>]>(
|
||||
async (resolve, reject) => {
|
||||
(resolve, reject) => {
|
||||
const id = randomUUID();
|
||||
let complete = 0;
|
||||
let retData: Parameters<ListenerType> | undefined = undefined;
|
||||
let retEvent: any = {};
|
||||
const databack = () => {
|
||||
const retEvent: any = {};
|
||||
|
||||
function sendDataCallback() {
|
||||
if (complete == 0) {
|
||||
reject(
|
||||
new Error(
|
||||
'Timeout: NTEvent EventName:' +
|
||||
EventName +
|
||||
'Timeout: NTEvent serviceAndMethod:' +
|
||||
serviceAndMethod +
|
||||
' ListenerName:' +
|
||||
ListenerName +
|
||||
listenerAndMethod +
|
||||
' EventRet:\n' +
|
||||
JSON.stringify(retEvent, null, 4) +
|
||||
'\n',
|
||||
@@ -200,25 +205,24 @@ export class LegacyNTEventWrapper {
|
||||
} else {
|
||||
resolve([retEvent as Awaited<ReturnType<EventType>>, ...retData!]);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const ListenerNameList = ListenerName.split('/');
|
||||
const ListenerNameList = listenerAndMethod.split('/');
|
||||
const ListenerMainName = ListenerNameList[0];
|
||||
const ListenerSubName = ListenerNameList[1];
|
||||
|
||||
const Timeouter = setTimeout(databack, timeout);
|
||||
const timeoutRef = setTimeout(sendDataCallback, timeout);
|
||||
|
||||
const eventCallbak = {
|
||||
const eventCallback = {
|
||||
timeout: timeout,
|
||||
createtime: Date.now(),
|
||||
checker: checker,
|
||||
checker: checkerListener,
|
||||
func: (...args: any[]) => {
|
||||
complete++;
|
||||
//console.log('func', ...args);
|
||||
retData = args as Parameters<ListenerType>;
|
||||
if (complete >= waitTimes) {
|
||||
clearTimeout(Timeouter);
|
||||
databack();
|
||||
if (complete >= callbackTimesToWait) {
|
||||
clearTimeout(timeoutRef);
|
||||
sendDataCallback();
|
||||
}
|
||||
},
|
||||
};
|
||||
@@ -228,36 +232,26 @@ export class LegacyNTEventWrapper {
|
||||
if (!this.EventTask.get(ListenerMainName)?.get(ListenerSubName)) {
|
||||
this.EventTask.get(ListenerMainName)?.set(ListenerSubName, new Map());
|
||||
}
|
||||
this.EventTask.get(ListenerMainName)?.get(ListenerSubName)?.set(id, eventCallbak);
|
||||
this.EventTask.get(ListenerMainName)?.get(ListenerSubName)?.set(id, eventCallback);
|
||||
this.createListenerFunction(ListenerMainName);
|
||||
const EventFunc = this.createEventFunction<EventType>(EventName);
|
||||
retEvent = await EventFunc!(...(args as any[]));
|
||||
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',
|
||||
),
|
||||
);
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 示例代码 快速创建事件
|
||||
// let NTEvent = new NTEventWrapper();
|
||||
// let TestEvent = NTEvent.CreatEventFunction<(force: boolean) => Promise<Number>>('NodeIKernelProfileLikeService/GetTest');
|
||||
// if (TestEvent) {
|
||||
// TestEvent(true);
|
||||
// }
|
||||
|
||||
// 示例代码 快速创建监听Listener类
|
||||
// let NTEvent = new NTEventWrapper();
|
||||
// NTEvent.CreatListenerFunction<NodeIKernelMsgListener>('NodeIKernelMsgListener', 'core')
|
||||
|
||||
// 调用接口
|
||||
//let NTEvent = new NTEventWrapper();
|
||||
//let ret = await NTEvent.CallNormalEvent<(force: boolean) => Promise<Number>, (data1: string, data2: number) => void>('NodeIKernelProfileLikeService/GetTest', 'NodeIKernelMsgListener/onAddSendMsg', 1, 3000, true);
|
||||
|
||||
// 注册监听 解除监听
|
||||
// NTEventDispatch.RigisterListener('NodeIKernelMsgListener/onAddSendMsg','core',cb);
|
||||
// NTEventDispatch.UnRigisterListener('NodeIKernelMsgListener/onAddSendMsg','core');
|
||||
|
||||
// let GetTest = NTEventDispatch.CreatEvent('NodeIKernelProfileLikeService/GetTest','NodeIKernelMsgListener/onAddSendMsg',Mode);
|
||||
// GetTest('test');
|
||||
|
||||
// always模式
|
||||
// NTEventDispatch.CreatEvent('NodeIKernelProfileLikeService/GetTest','NodeIKernelMsgListener/onAddSendMsg',Mode,(...args:any[])=>{ console.log(args) });
|
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: '' };
|
||||
}
|
@@ -1,140 +0,0 @@
|
||||
import type { NodeIQQNTWrapperSession, WrapperNodeApi } from '@/core/wrapper/wrapper';
|
||||
import EventEmitter from 'node:events';
|
||||
|
||||
export type ListenerClassBase = Record<string, string>;
|
||||
|
||||
export interface ListenerIBase {
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-new
|
||||
new(listener: any): ListenerClassBase;
|
||||
}
|
||||
|
||||
export class NTEventChannel extends EventEmitter {
|
||||
private wrapperApi: WrapperNodeApi;
|
||||
private wrapperSession: NodeIQQNTWrapperSession;
|
||||
private listenerRefStorage = new Map<string, ListenerIBase>();
|
||||
|
||||
constructor(WrapperApi: WrapperNodeApi, WrapperSession: NodeIQQNTWrapperSession) {
|
||||
super();
|
||||
this.on('error', () => {
|
||||
});
|
||||
this.wrapperApi = WrapperApi;
|
||||
this.wrapperSession = WrapperSession;
|
||||
}
|
||||
|
||||
dispatcherListener(ListenerEvent: string, ...args: any[]) {
|
||||
this.emit(ListenerEvent, ...args);
|
||||
}
|
||||
|
||||
createProxyDispatch(ListenerMainName: string) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
||||
const current = this;
|
||||
return new Proxy({}, {
|
||||
get(_target: any, prop: any, _receiver: any) {
|
||||
return (...args: any[]) => {
|
||||
current.dispatcherListener.apply(current, [ListenerMainName + '/' + prop, ...args]);
|
||||
};
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async getOrInitListener<T>(listenerMainName: string): Promise<T> {
|
||||
const ListenerType = this.wrapperApi[listenerMainName];
|
||||
//获取NTQQ 外部 Listener包装
|
||||
if (!ListenerType) throw new Error('Init Listener not found');
|
||||
let Listener = this.listenerRefStorage.get(listenerMainName);
|
||||
//判断是否已创建 创建则跳过
|
||||
if (!Listener && ListenerType) {
|
||||
Listener = new ListenerType(this.createProxyDispatch(listenerMainName));
|
||||
if (!Listener) throw new Error('Init Listener failed');
|
||||
//实例化NTQQ Listener外包装
|
||||
const ServiceSubName = listenerMainName.match(/^NodeIKernel(.*?)Listener$/)![1];
|
||||
const Service = 'NodeIKernel' + ServiceSubName + 'Service/addKernel' + ServiceSubName + 'Listener';
|
||||
const addfunc = this.createEventFunction<(listener: T) => number>(Service);
|
||||
//添加Listener到NTQQ
|
||||
addfunc!(Listener as T);
|
||||
this.listenerRefStorage.set(listenerMainName, Listener);
|
||||
//保存Listener实例
|
||||
}
|
||||
return Listener as T;
|
||||
}
|
||||
|
||||
async createEventWithListener<EventType extends (...args: any) => any, ListenerType extends (...args: any) => any>
|
||||
(
|
||||
eventName: string,
|
||||
listenerName: string,
|
||||
waitTimes = 1,
|
||||
timeout: number = 3000,
|
||||
checker: (...args: Parameters<ListenerType>) => boolean,
|
||||
...eventArg: Parameters<EventType>
|
||||
) {
|
||||
return new Promise<[EventRet: Awaited<ReturnType<EventType>>, ...Parameters<ListenerType>]>(async (resolve, reject) => {
|
||||
const ListenerNameList = listenerName.split('/');
|
||||
const ListenerMainName = ListenerNameList[0];
|
||||
//const ListenerSubName = ListenerNameList[1];
|
||||
this.getOrInitListener<ListenerType>(ListenerMainName);
|
||||
let complete = 0;
|
||||
const retData: Parameters<ListenerType> | undefined = undefined;
|
||||
let retEvent: any = {};
|
||||
const databack = () => {
|
||||
if (complete == 0) {
|
||||
reject(new Error('Timeout: NTEvent EventName:' + eventName + ' ListenerName:' + listenerName + ' EventRet:\n' + JSON.stringify(retEvent, null, 4) + '\n'));
|
||||
} else {
|
||||
resolve([retEvent as Awaited<ReturnType<EventType>>, ...retData!]);
|
||||
}
|
||||
};
|
||||
const Timeouter = setTimeout(databack, timeout);
|
||||
const callback = (...args: Parameters<ListenerType>) => {
|
||||
if (checker(...args)) {
|
||||
complete++;
|
||||
if (complete >= waitTimes) {
|
||||
clearTimeout(Timeouter);
|
||||
this.removeListener(listenerName, callback);
|
||||
databack();
|
||||
}
|
||||
}
|
||||
};
|
||||
this.on(listenerName, callback);
|
||||
const EventFunc = this.createEventFunction<EventType>(eventName);
|
||||
retEvent = await EventFunc!(...(eventArg as any[]));
|
||||
});
|
||||
}
|
||||
|
||||
private createEventFunction<T extends (...args: any) => any>(eventName: string): T | undefined {
|
||||
const eventNameArr = eventName.split('/');
|
||||
type eventType = {
|
||||
[key: string]: () => { [key: string]: (...params: Parameters<T>) => Promise<ReturnType<T>> }
|
||||
}
|
||||
if (eventNameArr.length > 1) {
|
||||
const serviceName = 'get' + eventNameArr[0].replace('NodeIKernel', '');
|
||||
const eventName = eventNameArr[1];
|
||||
//getNodeIKernelGroupListener,GroupService
|
||||
//console.log('2', eventName);
|
||||
const services = (this.wrapperSession as unknown as eventType)[serviceName]();
|
||||
const event = services[eventName]
|
||||
//重新绑定this
|
||||
.bind(services);
|
||||
if (event) {
|
||||
return event as T;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
async callEvent<EventType extends (...args: any[]) => Promise<any> | any>(
|
||||
EventName = '', timeout: number = 3000, ...args: Parameters<EventType>) {
|
||||
return new Promise<Awaited<ReturnType<EventType>>>(async (resolve, reject) => {
|
||||
const EventFunc = this.createEventFunction<EventType>(EventName);
|
||||
let complete = false;
|
||||
const Timeouter = setTimeout(() => {
|
||||
if (!complete) {
|
||||
reject(new Error('NTEvent EventName:' + EventName + ' timeout'));
|
||||
}
|
||||
}, timeout);
|
||||
const retData = await EventFunc!(...args);
|
||||
complete = true;
|
||||
resolve(retData);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
//NTEvent2.0
|
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;
|
||||
}
|
@@ -1,7 +1,8 @@
|
||||
import log4js, { Configuration } from 'log4js';
|
||||
import { truncateString } from '@/common/utils/helper';
|
||||
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',
|
||||
@@ -135,4 +136,105 @@ export class LogWrapper {
|
||||
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);
|
||||
}
|
||||
}
|
@@ -15,23 +15,13 @@ export class LimitedHashTable<K, V> {
|
||||
}
|
||||
|
||||
set(key: K, value: V): void {
|
||||
// const isExist = this.keyToValue.get(key);
|
||||
// if (isExist && isExist === value) {
|
||||
// return;
|
||||
// }
|
||||
this.keyToValue.set(key, value);
|
||||
this.valueToKey.set(value, key);
|
||||
while (this.keyToValue.size !== this.valueToKey.size) {
|
||||
//console.log('keyToValue.size !== valueToKey.size Error Atom');
|
||||
this.keyToValue.clear();
|
||||
this.valueToKey.clear();
|
||||
}
|
||||
// console.log('---------------');
|
||||
// console.log(this.keyToValue);
|
||||
// console.log(this.valueToKey);
|
||||
// console.log('---------------');
|
||||
while (this.keyToValue.size > this.maxSize || this.valueToKey.size > this.maxSize) {
|
||||
//console.log(this.keyToValue.size > this.maxSize, this.valueToKey.size > this.maxSize);
|
||||
const oldestKey = this.keyToValue.keys().next().value;
|
||||
this.valueToKey.delete(this.keyToValue.get(oldestKey)!);
|
||||
this.keyToValue.delete(oldestKey);
|
||||
@@ -101,17 +91,13 @@ class MessageUniqueWrapper {
|
||||
return ret.map((t) => t?.MsgId).filter((t) => t !== undefined);
|
||||
}
|
||||
|
||||
createMsg(peer: Peer, msgId: string): number | 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);
|
||||
//减少性能损耗
|
||||
// const isExist = this.msgIdMap.getKey(shortId);
|
||||
// if (isExist && isExist === msgId) {
|
||||
// return shortId;
|
||||
// }
|
||||
this.msgIdMap.set(msgId, shortId);
|
||||
this.msgDataMap.set(key, shortId);
|
||||
return shortId;
|
@@ -1,8 +1,7 @@
|
||||
import path, { dirname } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import fs from 'fs';
|
||||
|
||||
export const napcat_version = '2.0.1';
|
||||
import os from 'os';
|
||||
|
||||
export class NapCatPathWrapper {
|
||||
binaryPath: string;
|
||||
@@ -13,17 +12,23 @@ export class NapCatPathWrapper {
|
||||
|
||||
constructor(mainPath: string = dirname(fileURLToPath(import.meta.url))) {
|
||||
this.binaryPath = mainPath;
|
||||
this.logsPath = path.join(this.binaryPath, 'logs');
|
||||
this.configPath = path.join(this.binaryPath, 'config');
|
||||
this.cachePath = path.join(this.binaryPath, 'cache');
|
||||
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)) {
|
||||
if (!fs.existsSync(this.logsPath)) {
|
||||
fs.mkdirSync(this.logsPath, { recursive: true });
|
||||
}
|
||||
if (fs.existsSync(this.configPath)) {
|
||||
if (!fs.existsSync(this.configPath)) {
|
||||
fs.mkdirSync(this.configPath, { recursive: true });
|
||||
}
|
||||
if (fs.existsSync(this.cachePath)) {
|
||||
if (!fs.existsSync(this.cachePath)) {
|
||||
fs.mkdirSync(this.cachePath, { recursive: true });
|
||||
}
|
||||
}
|
@@ -3,7 +3,6 @@ import { LogWrapper } from './log';
|
||||
export function proxyHandlerOf(logger: LogWrapper) {
|
||||
return {
|
||||
get(target: any, prop: any, receiver: any) {
|
||||
// console.log('get', prop, typeof target[prop]);
|
||||
if (typeof target[prop] === 'undefined') {
|
||||
// 如果方法不存在,返回一个函数,这个函数调用existentMethod
|
||||
return (..._args: unknown[]) => {
|
@@ -1,7 +1,6 @@
|
||||
import path from 'node:path';
|
||||
import fs from 'node:fs';
|
||||
import { systemPlatform } from '@/common/utils/system';
|
||||
import { getDefaultQQVersionConfigInfo, getQQVersionConfigPath } from './helper';
|
||||
import { systemPlatform } from '@/common/system';
|
||||
import { getDefaultQQVersionConfigInfo, getQQPackageInfoPath, getQQVersionConfigPath } from './helper';
|
||||
import AppidTable from '@/core/external/appid.json';
|
||||
import { LogWrapper } from './log';
|
||||
|
||||
@@ -20,14 +19,16 @@ export class QQBasicInfoWrapper {
|
||||
//基础目录获取
|
||||
this.context = context;
|
||||
this.QQMainPath = process.execPath;
|
||||
this.QQPackageInfoPath = path.join(path.dirname(this.QQMainPath), 'resources', 'app', 'package.json');
|
||||
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;
|
||||
@@ -46,36 +47,47 @@ export class QQBasicInfoWrapper {
|
||||
}
|
||||
|
||||
requireMinNTQQBuild(buildStr: string) {
|
||||
const currentBuild = parseInt(this.getQQBuildStr() || '0');
|
||||
const currentBuild = +(this.getQQBuildStr() ?? '0');
|
||||
if (currentBuild == 0) throw new Error('QQBuildStr获取失败');
|
||||
return currentBuild >= parseInt(buildStr);
|
||||
}
|
||||
|
||||
//此方法不要直接使用
|
||||
getQUAInternal() {
|
||||
return systemPlatform === 'linux'
|
||||
? `V1_LNX_NQ_${this.getFullQQVesion()}_${this.getQQBuildStr()}_GW_B`
|
||||
: `V1_WIN_NQ_${this.getFullQQVesion()}_${this.getQQBuildStr()}_GW_B`;
|
||||
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;
|
||||
try {
|
||||
const fullVersion = this.getFullQQVesion();
|
||||
if (!fullVersion) throw new Error('QQ版本获取失败');
|
||||
const fullVersion = this.getFullQQVesion();
|
||||
if (fullVersion) {
|
||||
const data = appidTbale[fullVersion];
|
||||
if (data) {
|
||||
return data;
|
||||
}
|
||||
} catch (e) {
|
||||
this.context.logger.log(`[QQ版本兼容性检测] 获取Appid异常 请检测NapCat/QQNT是否正常`);
|
||||
}
|
||||
// 以下是兜底措施
|
||||
this.context.logger.log(
|
||||
`[QQ版本兼容性检测] ${this.getFullQQVesion()} 版本兼容性不佳,可能会导致一些功能无法正常使用`,
|
||||
);
|
||||
return { appid: systemPlatform === 'linux' ? '537237950' : '537237765', qua: this.getQUAInternal() };
|
||||
|
||||
// else
|
||||
this.context.logger.log(`[QQ版本兼容性检测] 获取Appid异常 请检测NapCat/QQNT是否正常`);
|
||||
this.context.logger.log(`[QQ版本兼容性检测] ${fullVersion} 版本兼容性不佳,可能会导致一些功能无法正常使用`,);
|
||||
return { appid: this.getAppidInternal(), qua: this.getQUAInternal() };
|
||||
}
|
||||
}
|
||||
|
||||
export let QQBasicInfo: QQBasicInfoWrapper | undefined;
|
@@ -30,7 +30,7 @@ export async function getMachineId(): Promise<string> {
|
||||
if (!machineId) {
|
||||
machineId = (async () => {
|
||||
const id = await getMacMachineId();
|
||||
return id || randomUUID(); // fallback, generate a UUID
|
||||
return id ?? randomUUID(); // fallback, generate a UUID
|
||||
})();
|
||||
}
|
||||
|
@@ -1,90 +0,0 @@
|
||||
import fs from 'fs';
|
||||
import { encode, getDuration, getWavFileInfo, isSilk, isWav } from 'silk-wasm';
|
||||
import fsPromise from 'fs/promises';
|
||||
import path from 'node:path';
|
||||
import { randomUUID } from 'crypto';
|
||||
import { spawn } from 'node:child_process';
|
||||
import { LogWrapper } from './log';
|
||||
|
||||
export async function encodeSilk(filePath: string, TEMP_DIR: string, logger: LogWrapper) {
|
||||
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);
|
||||
logger.log('通过文件大小估算语音的时长:', duration);
|
||||
return duration;
|
||||
}
|
||||
|
||||
try {
|
||||
const file = await fsPromise.readFile(filePath);
|
||||
const pttPath = path.join(TEMP_DIR, randomUUID());
|
||||
if (!isSilk(file)) {
|
||||
logger.log(`语音文件${filePath}需要转换成silk`);
|
||||
const _isWav = isWav(file);
|
||||
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 => {
|
||||
logger.log('FFmpeg处理转换出错: ', err.message);
|
||||
return reject(err);
|
||||
});
|
||||
cp.on('exit', (code, signal) => {
|
||||
const EXIT_CODES = [0, 255];
|
||||
if (code == null || EXIT_CODES.includes(code)) {
|
||||
sampleRate = 24000;
|
||||
const data = fs.readFileSync(pcmPath);
|
||||
fs.unlink(pcmPath, (err) => {
|
||||
});
|
||||
return resolve(data);
|
||||
}
|
||||
logger.log(`FFmpeg exit: code=${code ?? 'unknown'} sig=${signal ?? 'unknown'}`);
|
||||
reject(Error('FFmpeg处理转换失败'));
|
||||
});
|
||||
});
|
||||
};
|
||||
let input: Buffer;
|
||||
if (!_isWav) {
|
||||
input = await convert();
|
||||
} else {
|
||||
input = file;
|
||||
const allowSampleRate = [8000, 12000, 16000, 24000, 32000, 44100, 48000];
|
||||
const { fmt } = getWavFileInfo(input);
|
||||
// log(`wav文件信息`, fmt)
|
||||
if (!allowSampleRate.includes(fmt.sampleRate)) {
|
||||
input = await convert();
|
||||
}
|
||||
}
|
||||
const silk = await encode(input, sampleRate);
|
||||
fs.writeFileSync(pttPath, silk.data);
|
||||
logger.log(`语音文件${filePath}转换成功!`, pttPath, '时长:', silk.duration);
|
||||
return {
|
||||
converted: true,
|
||||
path: pttPath,
|
||||
duration: silk.duration / 1000,
|
||||
};
|
||||
} else {
|
||||
const silk = file;
|
||||
let duration = 0;
|
||||
try {
|
||||
duration = getDuration(silk) / 1000;
|
||||
} catch (e: any) {
|
||||
logger.log('获取语音文件时长失败, 使用文件大小推测时长', filePath, e.stack);
|
||||
duration = await guessDuration(filePath);
|
||||
}
|
||||
|
||||
return {
|
||||
converted: false,
|
||||
path: filePath,
|
||||
duration,
|
||||
};
|
||||
}
|
||||
} catch (error: any) {
|
||||
logger.logError('convert silk failed', error.stack);
|
||||
return {};
|
||||
}
|
||||
}
|
@@ -1,301 +0,0 @@
|
||||
import fs from 'fs';
|
||||
import fsPromise, { 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 { LogWrapper } from './log';
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
const host = new URL(url).hostname;
|
||||
headers['Host'] = host;
|
||||
} 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 }).catch((err) => {
|
||||
if (err.cause) {
|
||||
throw err.cause;
|
||||
}
|
||||
throw err;
|
||||
});
|
||||
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(TempDir: string, UriOrPath: string, fileName: string | null = null): Promise<Uri2LocalRes> {
|
||||
const res = {
|
||||
success: false,
|
||||
errMsg: '',
|
||||
fileName: '',
|
||||
ext: '',
|
||||
path: '',
|
||||
isLocal: false,
|
||||
};
|
||||
if (!fileName) fileName = randomUUID();
|
||||
let filePath = path.join(TempDir, fileName);//临时目录
|
||||
let url = null;
|
||||
//区分path和uri
|
||||
try {
|
||||
if (fs.existsSync(UriOrPath)) url = new URL('file://' + UriOrPath);
|
||||
} catch (error: any) {
|
||||
}
|
||||
try {
|
||||
url = new URL(UriOrPath);
|
||||
} catch (error: any) {
|
||||
}
|
||||
|
||||
//验证url
|
||||
if (!url) {
|
||||
res.errMsg = `UriOrPath ${UriOrPath} 解析失败,可能${UriOrPath}不存在`;
|
||||
return res;
|
||||
}
|
||||
|
||||
if (url.protocol == 'base64:') {
|
||||
// base64转成文件
|
||||
const base64Data = UriOrPath.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(UriOrPath);
|
||||
} catch (e: any) {
|
||||
res.errMsg = `${url}下载失败,` + e.toString();
|
||||
return res;
|
||||
}
|
||||
try {
|
||||
const pathInfo = path.parse(decodeURIComponent(url.pathname));
|
||||
if (pathInfo.name) {
|
||||
fileName = pathInfo.name;
|
||||
if (pathInfo.ext) {
|
||||
fileName += pathInfo.ext;
|
||||
// res.ext = pathInfo.ext
|
||||
}
|
||||
}
|
||||
fileName = fileName.replace(/[/\\:*?"<>|]/g, '_');
|
||||
res.fileName = fileName;
|
||||
filePath = path.join(TempDir, randomUUID() + 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 {
|
||||
// 26702执行forword file文件操作 不应该在这里乱来
|
||||
// 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) {
|
||||
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, logger: LogWrapper) {
|
||||
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, logger);
|
||||
} else {
|
||||
try {
|
||||
await fsPromise.copyFile(srcPath, dstPath);
|
||||
} catch (error) {
|
||||
logger.logError(`无法复制文件 '${srcPath}' 到 '${dstPath}': ${error}`);
|
||||
// 这里可以决定是否要继续复制其他文件
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
logger.logError('复制文件夹时出错:', error);
|
||||
}
|
||||
}
|
@@ -1,165 +0,0 @@
|
||||
import crypto from 'node:crypto';
|
||||
import path from 'node:path';
|
||||
import fs from 'fs';
|
||||
import * as fsPromise from 'node:fs/promises';
|
||||
import os from 'node:os';
|
||||
import { QQLevel } from '@/core';
|
||||
|
||||
//下面这个类是用于将uid+msgid合并的类
|
||||
|
||||
export class UUIDConverter {
|
||||
static encode(highStr: string, lowStr: string): string {
|
||||
const high = BigInt(highStr);
|
||||
const low = BigInt(lowStr);
|
||||
const highHex = high.toString(16).padStart(16, '0');
|
||||
const lowHex = low.toString(16).padStart(16, '0');
|
||||
const combinedHex = highHex + lowHex;
|
||||
const uuid = `${combinedHex.substring(0, 8)}-${combinedHex.substring(8, 12)}-${combinedHex.substring(
|
||||
12,
|
||||
16,
|
||||
)}-${combinedHex.substring(16, 20)}-${combinedHex.substring(20)}`;
|
||||
return uuid;
|
||||
}
|
||||
|
||||
static decode(uuid: string): { high: string; low: string } {
|
||||
const hex = uuid.replace(/-/g, '');
|
||||
const high = BigInt('0x' + hex.substring(0, 16));
|
||||
const low = BigInt('0x' + hex.substring(16));
|
||||
return { high: high.toString(), low: low.toString() };
|
||||
}
|
||||
}
|
||||
|
||||
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 getMd5(s: string) {
|
||||
const h = crypto.createHash('md5');
|
||||
h.update(s);
|
||||
return h.digest('hex');
|
||||
}
|
||||
|
||||
export function isNull(value: any) {
|
||||
return value === undefined || value === null;
|
||||
}
|
||||
|
||||
export function isNumeric(str: string) {
|
||||
return /^\d+$/.test(str);
|
||||
}
|
||||
|
||||
export function truncateString(obj: any, maxLength = 500) {
|
||||
if (obj !== null && typeof obj === 'object') {
|
||||
Object.keys(obj).forEach((key) => {
|
||||
if (typeof obj[key] === 'string') {
|
||||
// 如果是字符串且超过指定长度,则截断
|
||||
if (obj[key].length > maxLength) {
|
||||
obj[key] = obj[key].substring(0, maxLength) + '...';
|
||||
}
|
||||
} else if (typeof obj[key] === 'object') {
|
||||
// 如果是对象或数组,则递归调用
|
||||
truncateString(obj[key], maxLength);
|
||||
}
|
||||
});
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
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-26702',
|
||||
curVersion: '3.2.12-26702',
|
||||
prevVersion: '',
|
||||
onErrorVersions: [],
|
||||
buildId: '26702',
|
||||
};
|
||||
}
|
||||
return {
|
||||
baseVersion: '9.9.15-26702',
|
||||
curVersion: '9.9.15-26702',
|
||||
prevVersion: '',
|
||||
onErrorVersions: [],
|
||||
buildId: '26702',
|
||||
};
|
||||
}
|
||||
|
||||
export function getQQVersionConfigPath(exePath: string = ''): string | undefined {
|
||||
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') {
|
||||
return undefined;
|
||||
}
|
||||
if (!fs.existsSync(configVersionInfoPath)) {
|
||||
return undefined;
|
||||
}
|
||||
return configVersionInfoPath;
|
||||
}
|
||||
|
||||
export async function deleteOldFiles(directoryPath: string, daysThreshold: number) {
|
||||
try {
|
||||
const files = await fsPromise.readdir(directoryPath);
|
||||
|
||||
for (const file of files) {
|
||||
const filePath = path.join(directoryPath, file);
|
||||
const stats = await fsPromise.stat(filePath);
|
||||
const lastModifiedTime = stats.mtimeMs;
|
||||
const currentTime = Date.now();
|
||||
const timeDifference = currentTime - lastModifiedTime;
|
||||
const daysDifference = timeDifference / (1000 * 60 * 60 * 24);
|
||||
|
||||
if (daysDifference > daysThreshold) {
|
||||
await fsPromise.unlink(filePath); // Delete the file
|
||||
//console.log(`Deleted: ${filePath}`);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
//console.error('Error deleting files:', error);
|
||||
}
|
||||
}
|
||||
|
||||
export function calcQQLevel(level: QQLevel) {
|
||||
const { crownNum, sunNum, moonNum, starNum } = level;
|
||||
return crownNum * 64 + sunNum * 16 + moonNum * 4 + starNum;
|
||||
}
|
@@ -1,59 +0,0 @@
|
||||
import ffmpeg, { FfprobeStream } from 'fluent-ffmpeg';
|
||||
import fs from 'fs';
|
||||
import type { LogWrapper } from './log';
|
||||
|
||||
export async function getVideoInfo(filePath: string, logger: LogWrapper) {
|
||||
const size = fs.statSync(filePath).size;
|
||||
return new Promise<{
|
||||
width: number,
|
||||
height: number,
|
||||
time: number,
|
||||
format: string,
|
||||
size: number,
|
||||
filePath: string
|
||||
}>((resolve, reject) => {
|
||||
const ffmpegPath = process.env.FFMPEG_PATH;
|
||||
ffmpegPath && ffmpeg.setFfmpegPath(ffmpegPath);
|
||||
ffmpeg(filePath).ffprobe((err: any, metadata: ffmpeg.FfprobeData) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
const videoStream = metadata.streams.find((s: FfprobeStream) => s.codec_type === 'video');
|
||||
if (videoStream) {
|
||||
logger.log(`视频尺寸: ${videoStream.width}x${videoStream.height}`);
|
||||
} else {
|
||||
return reject('未找到视频流信息。');
|
||||
}
|
||||
resolve({
|
||||
width: videoStream.width!, height: videoStream.height!,
|
||||
time: parseInt(videoStream.duration!),
|
||||
format: metadata.format.format_name!,
|
||||
size,
|
||||
filePath,
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function checkFfmpeg(newPath: string | null = null, logger: LogWrapper): Promise<boolean> {
|
||||
return new Promise((resolve, reject) => {
|
||||
logger.log('开始检查ffmpeg', newPath);
|
||||
if (newPath) {
|
||||
ffmpeg.setFfmpegPath(newPath);
|
||||
}
|
||||
try {
|
||||
ffmpeg.getAvailableFormats((err: any, formats: any) => {
|
||||
if (err) {
|
||||
logger.log('ffmpeg is not installed or not found in PATH:', err);
|
||||
resolve(false);
|
||||
} else {
|
||||
logger.log('ffmpeg is installed.');
|
||||
resolve(true);
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
resolve(false);
|
||||
}
|
||||
});
|
||||
}
|
1
src/common/version.ts
Normal file
1
src/common/version.ts
Normal file
@@ -0,0 +1 @@
|
||||
export const napCatVersion = '2.6.4';
|
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,22 +1,8 @@
|
||||
interface IDependsAdapter {
|
||||
onMSFStatusChange(arg1: number, arg2: number): void;
|
||||
import { MsfChangeReasonType, MsfStatusType } from "../entities/adapter";
|
||||
|
||||
onMSFSsoError(args: unknown): void;
|
||||
export class NodeIDependsAdapter {
|
||||
onMSFStatusChange(statusType: MsfStatusType, changeReasonType: MsfChangeReasonType) {
|
||||
|
||||
getGroupCode(args: unknown): void;
|
||||
}
|
||||
|
||||
export interface NodeIDependsAdapter extends IDependsAdapter {
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-new
|
||||
new(adapter: IDependsAdapter): NodeIDependsAdapter;
|
||||
}
|
||||
|
||||
export class DependsAdapter implements IDependsAdapter {
|
||||
onMSFStatusChange(arg1: number, arg2: number) {
|
||||
// console.log(arg1, arg2);
|
||||
// if (arg1 == 2 && arg2 == 2) {
|
||||
// log("NapCat丢失网络连接,请检查网络")
|
||||
// }
|
||||
}
|
||||
|
||||
onMSFSsoError(args: unknown) {
|
||||
|
@@ -1,17 +1,4 @@
|
||||
interface IDispatcherAdapter {
|
||||
dispatchRequest(arg: unknown): void;
|
||||
|
||||
dispatchCall(arg: unknown): void;
|
||||
|
||||
dispatchCallWithJson(arg: unknown): void;
|
||||
}
|
||||
|
||||
export interface NodeIDispatcherAdapter extends IDispatcherAdapter {
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-new
|
||||
new(adapter: IDispatcherAdapter): NodeIDispatcherAdapter;
|
||||
}
|
||||
|
||||
export class DispatcherAdapter implements IDispatcherAdapter {
|
||||
export class NodeIDispatcherAdapter {
|
||||
dispatchRequest(arg: unknown) {
|
||||
}
|
||||
|
||||
|
@@ -1,27 +1,4 @@
|
||||
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 {
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-new
|
||||
new(adapter: IGlobalAdapter): NodeIGlobalAdapter;
|
||||
}
|
||||
|
||||
export class GlobalAdapter implements IGlobalAdapter {
|
||||
export class NodeIGlobalAdapter {
|
||||
onLog(...args: unknown[]) {
|
||||
}
|
||||
|
||||
|
@@ -10,7 +10,7 @@ export class NTQQCollectionApi {
|
||||
}
|
||||
|
||||
async createCollection(authorUin: string, authorUid: string, authorName: string, brief: string, rawData: string) {
|
||||
const param = {
|
||||
return this.context.session.getCollectionService().createNewCollectionItem({
|
||||
commInfo: {
|
||||
bid: 1,
|
||||
category: 2,
|
||||
@@ -43,12 +43,11 @@ export class NTQQCollectionApi {
|
||||
fileList: [],
|
||||
},
|
||||
need_share_url: false,
|
||||
};
|
||||
return this.context.session.getCollectionService().createNewCollectionItem(param);
|
||||
});
|
||||
}
|
||||
|
||||
async getAllCollection(category: number = 0, count: number = 50) {
|
||||
const param = {
|
||||
return this.context.session.getCollectionService().getCollectionItemList({
|
||||
category: category,
|
||||
groupId: -1,
|
||||
forceSync: true,
|
||||
@@ -56,7 +55,6 @@ export class NTQQCollectionApi {
|
||||
timeStamp: '0',
|
||||
count: count,
|
||||
searchDown: true,
|
||||
};
|
||||
return this.context.session.getCollectionService().getCollectionItemList(param);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -1,25 +1,30 @@
|
||||
import {
|
||||
CacheFileListItem,
|
||||
CacheFileType,
|
||||
ChatCacheListItemBasic,
|
||||
ChatType,
|
||||
ElementType,
|
||||
IMAGE_HTTP_HOST,
|
||||
IMAGE_HTTP_HOST_NT,
|
||||
Peer,
|
||||
PicElement,
|
||||
PicType,
|
||||
SendFileElement,
|
||||
SendPicElement,
|
||||
SendPttElement,
|
||||
SendVideoElement,
|
||||
} from '@/core/entities';
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import fsPromises from 'fs/promises';
|
||||
import { InstanceContext, NapCatCore, OnRichMediaDownloadCompleteParams } from '@/core';
|
||||
import { InstanceContext, NapCatCore, SearchResultItem } from '@/core';
|
||||
import * as fileType from 'file-type';
|
||||
import imageSize from 'image-size';
|
||||
import { ISizeCalculationResult } from 'image-size/dist/types/interface';
|
||||
import { NodeIKernelSearchService } from '../services/NodeIKernelSearchService';
|
||||
import { RkeyManager } from '../helper/rkey';
|
||||
import { calculateFileMD5 } from '@/common/utils/file';
|
||||
|
||||
import { calculateFileMD5, isGIF } from '@/common/file';
|
||||
import pathLib from 'node:path';
|
||||
import { defaultVideoThumbB64, getVideoInfo } from '@/common/video';
|
||||
import ffmpeg from 'fluent-ffmpeg';
|
||||
import { encodeSilk } from '@/common/audio';
|
||||
import { MessageContext } from '@/onebot/api';
|
||||
|
||||
export class NTQQFileApi {
|
||||
context: InstanceContext;
|
||||
@@ -29,11 +34,7 @@ export class NTQQFileApi {
|
||||
constructor(context: InstanceContext, core: NapCatCore) {
|
||||
this.context = context;
|
||||
this.core = core;
|
||||
this.rkeyManager = new RkeyManager('http://napcat-sign.wumiao.wang:2082/rkey', this.context.logger);
|
||||
}
|
||||
|
||||
async getFileType(filePath: string) {
|
||||
return fileType.fileTypeFromFile(filePath);
|
||||
this.rkeyManager = new RkeyManager(['https://llob.linyuchen.net/rkey', 'http://napcat-sign.wumiao.wang:2082/rkey'], this.context.logger);
|
||||
}
|
||||
|
||||
async copyFile(filePath: string, destPath: string) {
|
||||
@@ -51,18 +52,15 @@ export class NTQQFileApi {
|
||||
})).urlResult.domainUrl;
|
||||
}
|
||||
|
||||
// 上传文件到QQ的文件夹
|
||||
async uploadFile(filePath: string, elementType: ElementType = ElementType.PIC, elementSubType: number = 0) {
|
||||
// napCatCore.wrapper.util.
|
||||
const fileMd5 = await calculateFileMD5(filePath);
|
||||
let ext: string = (await this.getFileType(filePath))?.ext as string || '';
|
||||
if (ext) {
|
||||
ext = '.' + ext;
|
||||
}
|
||||
const extOrEmpty = (await fileType.fileTypeFromFile(filePath))?.ext;
|
||||
const ext = extOrEmpty ? `.${extOrEmpty}` : '';
|
||||
let fileName = `${path.basename(filePath)}`;
|
||||
if (fileName.indexOf('.') === -1) {
|
||||
fileName += ext;
|
||||
}
|
||||
|
||||
const mediaPath = this.context.session.getMsgService().getRichMediaFilePathForGuild({
|
||||
md5HexStr: fileMd5,
|
||||
fileName: fileName,
|
||||
@@ -73,7 +71,8 @@ export class NTQQFileApi {
|
||||
downloadType: 1,
|
||||
file_uuid: '',
|
||||
});
|
||||
await this.copyFile(filePath, mediaPath!);
|
||||
|
||||
await this.copyFile(filePath, mediaPath);
|
||||
const fileSize = await this.getFileSize(filePath);
|
||||
return {
|
||||
md5: fileMd5,
|
||||
@@ -84,12 +83,190 @@ export class NTQQFileApi {
|
||||
};
|
||||
}
|
||||
|
||||
async downloadMediaByUuid() {
|
||||
//napCatCore.session.getRichMediaService().downloadFileForFileUuid();
|
||||
async createValidSendFileElement(context: MessageContext, filePath: string, fileName: string = '', folderId: string = '',): Promise<SendFileElement> {
|
||||
const {
|
||||
fileName: _fileName,
|
||||
path,
|
||||
fileSize,
|
||||
} = await this.core.apis.FileApi.uploadFile(filePath, ElementType.FILE);
|
||||
if (fileSize === 0) {
|
||||
throw new Error('文件异常,大小为0');
|
||||
}
|
||||
context.deleteAfterSentFiles.push(path);
|
||||
return {
|
||||
elementType: ElementType.FILE,
|
||||
elementId: '',
|
||||
fileElement: {
|
||||
fileName: fileName || _fileName,
|
||||
folderId: folderId,
|
||||
filePath: path,
|
||||
fileSize: (fileSize).toString(),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async createValidSendPicElement(context: MessageContext, picPath: string, summary: string = '', subType: 0 | 1 = 0,): Promise<SendPicElement> {
|
||||
const { md5, fileName, path, fileSize } = await this.core.apis.FileApi.uploadFile(picPath, ElementType.PIC, subType);
|
||||
if (fileSize === 0) {
|
||||
throw new Error('文件异常,大小为0');
|
||||
}
|
||||
const imageSize = await this.core.apis.FileApi.getImageSize(picPath);
|
||||
context.deleteAfterSentFiles.push(path);
|
||||
return {
|
||||
elementType: ElementType.PIC,
|
||||
elementId: '',
|
||||
picElement: {
|
||||
md5HexStr: md5,
|
||||
fileSize: fileSize.toString(),
|
||||
picWidth: imageSize.width,
|
||||
picHeight: imageSize.height,
|
||||
fileName: fileName,
|
||||
sourcePath: path,
|
||||
original: true,
|
||||
picType: isGIF(picPath) ? PicType.gif : PicType.jpg,
|
||||
picSubType: subType,
|
||||
fileUuid: '',
|
||||
fileSubId: '',
|
||||
thumbFileSize: 0,
|
||||
summary,
|
||||
} as PicElement,
|
||||
};
|
||||
}
|
||||
|
||||
async createValidSendVideoElement(context: MessageContext, filePath: string, fileName: string = '', diyThumbPath: string = ''): Promise<SendVideoElement> {
|
||||
const logger = this.core.context.logger;
|
||||
let videoInfo = {
|
||||
width: 1920, height: 1080,
|
||||
time: 15,
|
||||
format: 'mp4',
|
||||
size: 0,
|
||||
filePath,
|
||||
};
|
||||
try {
|
||||
videoInfo = await getVideoInfo(filePath, logger);
|
||||
} catch (e) {
|
||||
logger.logError('获取视频信息失败,将使用默认值', e);
|
||||
}
|
||||
|
||||
let fileExt = 'mp4';
|
||||
try {
|
||||
const tempExt = (await fileType.fileTypeFromFile(filePath))?.ext;
|
||||
if (tempExt) fileExt = tempExt;
|
||||
} catch (e) {
|
||||
this.context.logger.logError('获取文件类型失败', e);
|
||||
}
|
||||
const newFilePath = filePath + '.' + fileExt;
|
||||
fs.copyFileSync(filePath, newFilePath);
|
||||
context.deleteAfterSentFiles.push(newFilePath);
|
||||
filePath = newFilePath;
|
||||
const { fileName: _fileName, path, fileSize, md5 } = await this.core.apis.FileApi.uploadFile(filePath, ElementType.VIDEO);
|
||||
if (fileSize === 0) {
|
||||
throw new Error('文件异常,大小为0');
|
||||
}
|
||||
videoInfo.size = fileSize;
|
||||
let thumb = path.replace(`${pathLib.sep}Ori${pathLib.sep}`, `${pathLib.sep}Thumb${pathLib.sep}`);
|
||||
thumb = pathLib.dirname(thumb);
|
||||
|
||||
const thumbPath = new Map();
|
||||
const _thumbPath = await new Promise<string | undefined>((resolve, reject) => {
|
||||
const thumbFileName = `${md5}_0.png`;
|
||||
const thumbPath = pathLib.join(thumb, thumbFileName);
|
||||
ffmpeg(filePath)
|
||||
.on('error', (err) => {
|
||||
logger.logDebug('获取视频封面失败,使用默认封面', err);
|
||||
if (diyThumbPath) {
|
||||
fsPromises.copyFile(diyThumbPath, thumbPath).then(() => {
|
||||
resolve(thumbPath);
|
||||
}).catch(reject);
|
||||
} else {
|
||||
fs.writeFileSync(thumbPath, Buffer.from(defaultVideoThumbB64, 'base64'));
|
||||
resolve(thumbPath);
|
||||
}
|
||||
})
|
||||
.screenshots({
|
||||
timestamps: [0],
|
||||
filename: thumbFileName,
|
||||
folder: thumb,
|
||||
size: videoInfo.width + 'x' + videoInfo.height,
|
||||
})
|
||||
.on('end', () => {
|
||||
resolve(thumbPath);
|
||||
});
|
||||
});
|
||||
const thumbSize = _thumbPath ? (await fsPromises.stat(_thumbPath)).size : 0;
|
||||
thumbPath.set(0, _thumbPath);
|
||||
const thumbMd5 = _thumbPath ? await calculateFileMD5(_thumbPath) : '';
|
||||
context.deleteAfterSentFiles.push(path);
|
||||
const uploadName = (fileName || _fileName).toLocaleLowerCase().endsWith('.' + fileExt.toLocaleLowerCase()) ? (fileName || _fileName) : (fileName || _fileName) + '.' + fileExt;
|
||||
return {
|
||||
elementType: ElementType.VIDEO,
|
||||
elementId: '',
|
||||
videoElement: {
|
||||
fileName: uploadName,
|
||||
filePath: path,
|
||||
videoMd5: md5,
|
||||
thumbMd5,
|
||||
fileTime: videoInfo.time,
|
||||
thumbPath: thumbPath,
|
||||
thumbSize,
|
||||
thumbWidth: videoInfo.width,
|
||||
thumbHeight: videoInfo.height,
|
||||
fileSize: '' + fileSize,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async createValidSendPttElement(pttPath: string): Promise<SendPttElement> {
|
||||
const { converted, path: silkPath, duration } = await encodeSilk(pttPath, this.core.NapCatTempPath, this.core.context.logger);
|
||||
if (!silkPath) {
|
||||
throw new Error('语音转换失败, 请检查语音文件是否正常');
|
||||
}
|
||||
const { md5, fileName, path, fileSize } = await this.core.apis.FileApi.uploadFile(silkPath, ElementType.PTT);
|
||||
if (fileSize === 0) {
|
||||
throw new Error('文件异常,大小为0');
|
||||
}
|
||||
if (converted) {
|
||||
fsPromises.unlink(silkPath).then().catch(
|
||||
(e) => this.context.logger.logError('删除临时文件失败', e)
|
||||
);
|
||||
}
|
||||
return {
|
||||
elementType: ElementType.PTT,
|
||||
elementId: '',
|
||||
pttElement: {
|
||||
fileName: fileName,
|
||||
filePath: path,
|
||||
md5HexStr: md5,
|
||||
fileSize: fileSize,
|
||||
duration: duration ?? 1,
|
||||
formatType: 1,
|
||||
voiceType: 1,
|
||||
voiceChangeType: 0,
|
||||
canConvert2Text: true,
|
||||
waveAmplitudes: [
|
||||
0, 18, 9, 23, 16, 17, 16, 15, 44, 17, 24, 20, 14, 15, 17,
|
||||
],
|
||||
fileSubId: '',
|
||||
playState: 1,
|
||||
autoConvertText: 0,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async downloadFileForModelId(peer: Peer, modelId: string, unknown: string, timeout = 1000 * 60 * 2) {
|
||||
const [, fileTransNotifyInfo] = await this.core.eventWrapper.callNormalEventV2(
|
||||
'NodeIKernelRichMediaService/downloadFileForModelId',
|
||||
'NodeIKernelMsgListener/onRichMediaDownloadComplete',
|
||||
[peer, [modelId], unknown],
|
||||
() => true,
|
||||
(arg) => arg?.commonFileInfo?.fileModelId === modelId,
|
||||
1,
|
||||
timeout,
|
||||
);
|
||||
return fileTransNotifyInfo.filePath;
|
||||
}
|
||||
|
||||
async downloadMedia(msgId: string, chatType: ChatType, peerUid: string, elementId: string, thumbPath: string, sourcePath: string, timeout = 1000 * 60 * 2, force: boolean = false) {
|
||||
//logDebug('receive downloadMedia task', msgId, chatType, peerUid, elementId, thumbPath, sourcePath, timeout, force);
|
||||
// 用于下载收到的消息中的图片等
|
||||
if (sourcePath && fs.existsSync(sourcePath)) {
|
||||
if (force) {
|
||||
@@ -102,33 +279,10 @@ export class NTQQFileApi {
|
||||
return sourcePath;
|
||||
}
|
||||
}
|
||||
const data = await this.core.eventWrapper.CallNormalEvent<
|
||||
(
|
||||
params: {
|
||||
fileModelId: string,
|
||||
downloadSourceType: number,
|
||||
triggerType: number,
|
||||
msgId: string,
|
||||
chatType: ChatType,
|
||||
peerUid: string,
|
||||
elementId: string,
|
||||
thumbSize: number,
|
||||
downloadType: number,
|
||||
filePath: string
|
||||
}) => Promise<unknown>,
|
||||
(fileTransNotifyInfo: OnRichMediaDownloadCompleteParams) => void
|
||||
>(
|
||||
const [, completeRetData] = await this.core.eventWrapper.callNormalEventV2(
|
||||
'NodeIKernelMsgService/downloadRichMedia',
|
||||
'NodeIKernelMsgListener/onRichMediaDownloadComplete',
|
||||
1,
|
||||
timeout,
|
||||
(arg: OnRichMediaDownloadCompleteParams) => {
|
||||
if (arg.msgId === msgId) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
{
|
||||
[{
|
||||
fileModelId: '0',
|
||||
downloadSourceType: 0,
|
||||
triggerType: 1,
|
||||
@@ -139,242 +293,96 @@ export class NTQQFileApi {
|
||||
thumbSize: 0,
|
||||
downloadType: 1,
|
||||
filePath: thumbPath,
|
||||
},
|
||||
}],
|
||||
() => true,
|
||||
(arg) => arg.msgId === msgId,
|
||||
1,
|
||||
timeout,
|
||||
);
|
||||
let msg = await this.core.apis.MsgApi.getMsgsByMsgId({
|
||||
guildId: '',
|
||||
chatType: chatType,
|
||||
peerUid: peerUid,
|
||||
}, [msgId]);
|
||||
if (msg.msgList.length === 0) {
|
||||
return data[1].filePath;
|
||||
}
|
||||
//获取原始消息
|
||||
let FileElements = msg?.msgList[0]?.elements?.find(e => e.elementId === elementId);
|
||||
if (!FileElements) {
|
||||
//失败则就乱来 Todo
|
||||
return data[1].filePath;
|
||||
}
|
||||
//从原始消息获取文件路径
|
||||
let filePath =
|
||||
FileElements?.fileElement?.filePath ||
|
||||
FileElements?.pttElement?.filePath ||
|
||||
FileElements?.videoElement?.filePath ||
|
||||
FileElements?.picElement?.sourcePath;
|
||||
return filePath;
|
||||
return completeRetData.filePath;
|
||||
}
|
||||
|
||||
async getImageSize(filePath: string): Promise<ISizeCalculationResult | undefined> {
|
||||
async getImageSize(filePath: string): Promise<ISizeCalculationResult> {
|
||||
return new Promise((resolve, reject) => {
|
||||
imageSize(filePath, (err, dimensions) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(dimensions);
|
||||
if (!dimensions) {
|
||||
reject(new Error('获取图片尺寸失败'));
|
||||
} else {
|
||||
resolve(dimensions);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async addFileCache(peer: Peer, msgId: string, msgSeq: string, senderUid: string, elemId: string, elemType: string, fileSize: string, fileName: string) {
|
||||
let GroupData;
|
||||
let BuddyData;
|
||||
if (peer.chatType === ChatType.group) {
|
||||
GroupData =
|
||||
[{
|
||||
groupCode: peer.peerUid,
|
||||
isConf: false,
|
||||
hasModifyConfGroupFace: true,
|
||||
hasModifyConfGroupName: true,
|
||||
groupName: 'NapCat.Cached',
|
||||
remark: 'NapCat.Cached',
|
||||
}];
|
||||
} else if (peer.chatType === ChatType.friend) {
|
||||
BuddyData = [{
|
||||
category_name: 'NapCat.Cached',
|
||||
peerUid: peer.peerUid,
|
||||
peerUin: peer.peerUid,
|
||||
remark: 'NapCat.Cached',
|
||||
}];
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return this.context.session.getSearchService().addSearchHistory({
|
||||
type: 4,
|
||||
contactList: [],
|
||||
id: -1,
|
||||
groupInfos: [],
|
||||
msgs: [],
|
||||
fileInfos: [
|
||||
{
|
||||
chatType: peer.chatType,
|
||||
buddyChatInfo: BuddyData || [],
|
||||
discussChatInfo: [],
|
||||
groupChatInfo: GroupData || [],
|
||||
dataLineChatInfo: [],
|
||||
tmpChatInfo: [],
|
||||
msgId: msgId,
|
||||
msgSeq: msgSeq,
|
||||
msgTime: Math.floor(Date.now() / 1000).toString(),
|
||||
senderUid: senderUid,
|
||||
senderNick: 'NapCat.Cached',
|
||||
senderRemark: 'NapCat.Cached',
|
||||
senderCard: 'NapCat.Cached',
|
||||
elemId: elemId,
|
||||
elemType: elemType,
|
||||
fileSize: fileSize,
|
||||
filePath: '',
|
||||
fileName: fileName,
|
||||
hits: [{
|
||||
start: 12,
|
||||
end: 14,
|
||||
}],
|
||||
},
|
||||
async searchForFile(keys: string[]): Promise<SearchResultItem | undefined> {
|
||||
const randomResultId = 100000 + Math.floor(Math.random() * 10000);
|
||||
let searchId = 0;
|
||||
const [, searchResult] = await this.core.eventWrapper.callNormalEventV2(
|
||||
'NodeIKernelFileAssistantService/searchFile',
|
||||
'NodeIKernelFileAssistantListener/onFileSearch',
|
||||
[
|
||||
keys,
|
||||
{ resultType: 2, pageLimit: 1 },
|
||||
randomResultId,
|
||||
],
|
||||
});
|
||||
(ret) => {
|
||||
searchId = ret;
|
||||
return true;
|
||||
},
|
||||
result => result.searchId === searchId && result.resultId === randomResultId,
|
||||
);
|
||||
return searchResult.resultItems[0];
|
||||
}
|
||||
|
||||
async searchfile(keys: string[]) {
|
||||
type EventType = NodeIKernelSearchService['searchFileWithKeywords'];
|
||||
|
||||
interface OnListener {
|
||||
searchId: string,
|
||||
hasMore: boolean,
|
||||
resultItems: {
|
||||
chatType: ChatType,
|
||||
buddyChatInfo: any[],
|
||||
discussChatInfo: any[],
|
||||
groupChatInfo:
|
||||
{
|
||||
groupCode: string,
|
||||
isConf: boolean,
|
||||
hasModifyConfGroupFace: boolean,
|
||||
hasModifyConfGroupName: boolean,
|
||||
groupName: string,
|
||||
remark: string
|
||||
}[],
|
||||
dataLineChatInfo: any[],
|
||||
tmpChatInfo: any[],
|
||||
msgId: string,
|
||||
msgSeq: string,
|
||||
msgTime: string,
|
||||
senderUid: string,
|
||||
senderNick: string,
|
||||
senderRemark: string,
|
||||
senderCard: string,
|
||||
elemId: string,
|
||||
elemType: number,
|
||||
fileSize: string,
|
||||
filePath: string,
|
||||
fileName: string,
|
||||
hits:
|
||||
{
|
||||
start: number,
|
||||
end: number
|
||||
}[]
|
||||
}[]
|
||||
}
|
||||
|
||||
const Event = this.core.eventWrapper.createEventFunction<EventType>('NodeIKernelSearchService/searchFileWithKeywords');
|
||||
let id = '';
|
||||
const Listener = this.core.eventWrapper.RegisterListen<(params: OnListener) => void>
|
||||
(
|
||||
'NodeIKernelSearchListener/onSearchFileKeywordsResult',
|
||||
1,
|
||||
20000,
|
||||
(params) => id !== '' && params.searchId == id,
|
||||
);
|
||||
id = await Event!(keys, 12);
|
||||
const [ret] = (await Listener);
|
||||
return ret;
|
||||
async downloadFileById(
|
||||
fileId: string,
|
||||
fileSize: number = 1024576,
|
||||
estimatedTime: number = (fileSize * 1000 / 1024576) + 5000,
|
||||
) {
|
||||
const [, fileData] = await this.core.eventWrapper.callNormalEventV2(
|
||||
'NodeIKernelFileAssistantService/downloadFile',
|
||||
'NodeIKernelFileAssistantListener/onFileStatusChanged',
|
||||
[[fileId]],
|
||||
ret => ret.result === 0,
|
||||
status => status.fileStatus === 2 && status.fileProgress === '0',
|
||||
1,
|
||||
estimatedTime, // estimate 1MB/s
|
||||
);
|
||||
return fileData.filePath!;
|
||||
}
|
||||
|
||||
async getImageUrl(element: PicElement) {
|
||||
if (!element) {
|
||||
return '';
|
||||
}
|
||||
const url: string = element.originImageUrl!; // 没有域名
|
||||
const url: string = element.originImageUrl ?? '';
|
||||
const md5HexStr = element.md5HexStr;
|
||||
const fileMd5 = element.md5HexStr;
|
||||
const fileUuid = element.fileUuid;
|
||||
|
||||
if (url) {
|
||||
const UrlParse = new URL(IMAGE_HTTP_HOST + url);//临时解析拼接
|
||||
const imageAppid = UrlParse.searchParams.get('appid');
|
||||
const isNewPic = imageAppid && ['1406', '1407'].includes(imageAppid);
|
||||
if (isNewPic) {
|
||||
let UrlRkey = UrlParse.searchParams.get('rkey');
|
||||
if (UrlRkey) {
|
||||
const parsedUrl = new URL(IMAGE_HTTP_HOST + url);
|
||||
const imageAppid = parsedUrl.searchParams.get('appid');
|
||||
const isNTV2 = imageAppid && ['1406', '1407'].includes(imageAppid);
|
||||
if (isNTV2) {
|
||||
let rkey = parsedUrl.searchParams.get('rkey');
|
||||
if (rkey) {
|
||||
return IMAGE_HTTP_HOST_NT + url;
|
||||
}
|
||||
const rkeyData = await this.rkeyManager.getRkey();
|
||||
UrlRkey = imageAppid === '1406' ? rkeyData.private_rkey : rkeyData.group_rkey;
|
||||
return IMAGE_HTTP_HOST_NT + url + `${UrlRkey}`;
|
||||
rkey = imageAppid === '1406' ? rkeyData.private_rkey : rkeyData.group_rkey;
|
||||
return IMAGE_HTTP_HOST_NT + url + `${rkey}`;
|
||||
} else {
|
||||
// 老的图片url,不需要rkey
|
||||
return IMAGE_HTTP_HOST + url;
|
||||
}
|
||||
} else if (fileMd5 || md5HexStr) {
|
||||
// 没有url,需要自己拼接
|
||||
return `${IMAGE_HTTP_HOST}/gchatpic_new/0/0-0-${(fileMd5 || md5HexStr)!.toUpperCase()}/0`;
|
||||
return `${IMAGE_HTTP_HOST}/gchatpic_new/0/0-0-${(fileMd5 ?? md5HexStr)!.toUpperCase()}/0`;
|
||||
}
|
||||
this.context.logger.logDebug('图片url获取失败', element);
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
export class NTQQFileCacheApi {
|
||||
context: InstanceContext;
|
||||
core: NapCatCore;
|
||||
|
||||
constructor(context: InstanceContext, core: NapCatCore) {
|
||||
this.context = context;
|
||||
this.core = core;
|
||||
}
|
||||
|
||||
async setCacheSilentScan(isSilent: boolean = true) {
|
||||
return '';
|
||||
}
|
||||
|
||||
getCacheSessionPathList() {
|
||||
return '';
|
||||
}
|
||||
|
||||
clearCache(cacheKeys: Array<string> = ['tmp', 'hotUpdate']) {
|
||||
// 参数未验证
|
||||
return this.context.session.getStorageCleanService().clearCacheDataByKeys(cacheKeys);
|
||||
}
|
||||
|
||||
addCacheScannedPaths(pathMap: object = {}) {
|
||||
return this.context.session.getStorageCleanService().addCacheScanedPaths(pathMap);
|
||||
}
|
||||
|
||||
scanCache() {
|
||||
//return (await this.context.session.getStorageCleanService().scanCache()).size;
|
||||
}
|
||||
|
||||
getHotUpdateCachePath() {
|
||||
// 未实现
|
||||
return '';
|
||||
}
|
||||
|
||||
getDesktopTmpPath() {
|
||||
// 未实现
|
||||
return '';
|
||||
}
|
||||
|
||||
getChatCacheList(type: ChatType, pageSize: number = 1000, pageIndex: number = 0) {
|
||||
return this.context.session.getStorageCleanService().getChatCacheInfo(type, pageSize, 1, pageIndex);
|
||||
}
|
||||
|
||||
getFileCacheInfo(fileType: CacheFileType, pageSize: number = 1000, lastRecord?: CacheFileListItem) {
|
||||
const _lastRecord = lastRecord ? lastRecord : { fileType: fileType };
|
||||
//需要五个参数
|
||||
//return napCatCore.session.getStorageCleanService().getFileCacheInfo();
|
||||
}
|
||||
|
||||
async clearChatCache(chats: ChatCacheListItemBasic[] = [], fileKeys: string[] = []) {
|
||||
return this.context.session.getStorageCleanService().clearChatCacheInfo(chats, fileKeys);
|
||||
}
|
||||
}
|
||||
|
@@ -1,102 +1,78 @@
|
||||
import { Friend, FriendV2, User } from '@/core/entities';
|
||||
import { BuddyListReqType, InstanceContext, NapCatCore, NodeIKernelProfileService, OnBuddyChangeParams } from '@/core';
|
||||
import { LimitedHashTable } from '@/common/utils/MessageUnique';
|
||||
import { FriendV2 } from '@/core/entities';
|
||||
import { BuddyListReqType, InstanceContext, NapCatCore } from '@/core';
|
||||
import { LimitedHashTable } from '@/common/message-unique';
|
||||
|
||||
export class NTQQFriendApi {
|
||||
context: InstanceContext;
|
||||
core: NapCatCore;
|
||||
//friends: Map<string, Friend> = new Map<string, FriendV2>();
|
||||
|
||||
constructor(context: InstanceContext, core: NapCatCore) {
|
||||
this.context = context;
|
||||
this.core = core;
|
||||
// if (!this.context.basicInfoWrapper.requireMinNTQQBuild('26702')) {
|
||||
// this.getFriends(true);
|
||||
// }
|
||||
}
|
||||
|
||||
async getBuddyV2SimpleInfoMap(refresh = false) {
|
||||
const buddyService = this.context.session.getBuddyService();
|
||||
const buddyListV2 = refresh ? await buddyService.getBuddyListV2('0', BuddyListReqType.KNOMAL) : await buddyService.getBuddyListV2('0', BuddyListReqType.KNOMAL);
|
||||
const uids = buddyListV2.data.flatMap(item => item.buddyUids);
|
||||
return await this.core.eventWrapper.callNoListenerEvent(
|
||||
'NodeIKernelProfileService/getCoreAndBaseInfo',
|
||||
'nodeStore',
|
||||
uids,
|
||||
);
|
||||
}
|
||||
|
||||
async getBuddyV2(refresh = false): Promise<FriendV2[]> {
|
||||
const uids: string[] = [];
|
||||
const buddyService = this.context.session.getBuddyService();
|
||||
const buddyListV2 = refresh ? await buddyService.getBuddyListV2('0', BuddyListReqType.KNOMAL) : await buddyService.getBuddyListV2('0', BuddyListReqType.KNOMAL);
|
||||
uids.push(...buddyListV2.data.flatMap(item => item.buddyUids));
|
||||
const data = await this.core.eventWrapper.callNoListenerEvent<NodeIKernelProfileService['getCoreAndBaseInfo']>(
|
||||
'NodeIKernelProfileService/getCoreAndBaseInfo', 5000, 'nodeStore', uids,
|
||||
);
|
||||
return Array.from(data.values());
|
||||
}
|
||||
|
||||
async getBuddyIdMapCache(refresh = false): Promise<LimitedHashTable<string, string>> {
|
||||
return await this.getBuddyIdMap(refresh);
|
||||
return Array.from((await this.getBuddyV2SimpleInfoMap(refresh)).values());
|
||||
}
|
||||
|
||||
async getBuddyIdMap(refresh = false): Promise<LimitedHashTable<string, string>> {
|
||||
const uids: string[] = [];
|
||||
const retMap: LimitedHashTable<string, string> = new LimitedHashTable<string, string>(5000);
|
||||
const buddyService = this.context.session.getBuddyService();
|
||||
const buddyListV2 = refresh ? await buddyService.getBuddyListV2('0', BuddyListReqType.KNOMAL) : await buddyService.getBuddyListV2('0', BuddyListReqType.KNOMAL);
|
||||
uids.push(...buddyListV2.data.flatMap(item => item.buddyUids));
|
||||
const data = await this.core.eventWrapper.callNoListenerEvent<NodeIKernelProfileService['getCoreAndBaseInfo']>(
|
||||
'NodeIKernelProfileService/getCoreAndBaseInfo', 5000, 'nodeStore', uids,
|
||||
);
|
||||
data.forEach((value, key) => {
|
||||
retMap.set(value.uin!, value.uid!);
|
||||
});
|
||||
//console.log('getBuddyIdMap', retMap.getValue);
|
||||
const data = await this.getBuddyV2SimpleInfoMap(refresh);
|
||||
data.forEach((value) => retMap.set(value.uin!, value.uid!));
|
||||
return retMap;
|
||||
}
|
||||
|
||||
async getBuddyV2ExWithCate(refresh = false) {
|
||||
const uids: string[] = [];
|
||||
const categoryMap: Map<string, any> = new Map();
|
||||
const buddyService = this.context.session.getBuddyService();
|
||||
const buddyListV2 = refresh ? (await buddyService.getBuddyListV2('0', BuddyListReqType.KNOMAL)).data : (await buddyService.getBuddyListV2('0', BuddyListReqType.KNOMAL)).data;
|
||||
uids.push(
|
||||
...buddyListV2.flatMap(item => {
|
||||
item.buddyUids.forEach(uid => {
|
||||
categoryMap.set(uid, { categoryId: item.categoryId, categroyName: item.categroyName });
|
||||
});
|
||||
return item.buddyUids;
|
||||
}));
|
||||
const data = await this.core.eventWrapper.callNoListenerEvent<NodeIKernelProfileService['getCoreAndBaseInfo']>(
|
||||
'NodeIKernelProfileService/getCoreAndBaseInfo', 5000, 'nodeStore', uids,
|
||||
);
|
||||
return Array.from(data).map(([key, value]) => {
|
||||
const category = categoryMap.get(key);
|
||||
return category ? {
|
||||
...value,
|
||||
categoryId: category.categoryId,
|
||||
categroyName: category.categroyName,
|
||||
} : value;
|
||||
const uids = buddyListV2.flatMap(item => {
|
||||
item.buddyUids.forEach(uid => {
|
||||
categoryMap.set(uid, { categoryId: item.categoryId, categoryName: item.categroyName });
|
||||
});
|
||||
return item.buddyUids;
|
||||
});
|
||||
const data = await this.core.eventWrapper.callNoListenerEvent(
|
||||
'NodeIKernelProfileService/getCoreAndBaseInfo',
|
||||
'nodeStore',
|
||||
uids,
|
||||
);
|
||||
return buddyListV2.map(category => ({
|
||||
categoryId: category.categoryId,
|
||||
categorySortId: category.categorySortId,
|
||||
categoryName: category.categroyName,
|
||||
categoryMbCount: category.categroyMbCount,
|
||||
onlineCount: category.onlineCount,
|
||||
buddyList: category.buddyUids.map(uid => data.get(uid)!).filter(value => value),
|
||||
}));
|
||||
}
|
||||
|
||||
async isBuddy(uid: string) {
|
||||
return this.context.session.getBuddyService().isBuddy(uid);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @param forced
|
||||
* @returns
|
||||
*/
|
||||
async getFriends(forced = false): Promise<User[]> {
|
||||
const [_retData, _BuddyArg] = await this.core.eventWrapper.CallNormalEvent<(force: boolean) => Promise<any>, (arg: OnBuddyChangeParams) => void>
|
||||
(
|
||||
'NodeIKernelBuddyService/getBuddyList',
|
||||
'NodeIKernelBuddyListener/onBuddyListChange',
|
||||
1,
|
||||
5000,
|
||||
() => true,
|
||||
forced,
|
||||
);
|
||||
const friends: User[] = [];
|
||||
for (const categoryItem of _BuddyArg) {
|
||||
for (const friend of categoryItem.buddyList) {
|
||||
friends.push(friend);
|
||||
}
|
||||
}
|
||||
return friends;
|
||||
async clearBuddyReqUnreadCnt() {
|
||||
return this.context.session.getBuddyService().clearBuddyReqUnreadCnt();
|
||||
}
|
||||
|
||||
async getBuddyReq() {
|
||||
const [, ret] = await this.core.eventWrapper.callNormalEventV2(
|
||||
'NodeIKernelBuddyService/getBuddyReq',
|
||||
'NodeIKernelBuddyListener/onBuddyReqChange',
|
||||
[],
|
||||
);
|
||||
return ret;
|
||||
}
|
||||
|
||||
async handleFriendRequest(flag: string, accept: boolean) {
|
||||
|
@@ -1,18 +1,17 @@
|
||||
import {
|
||||
ChatType,
|
||||
GeneralCallResult,
|
||||
Group,
|
||||
GroupMember,
|
||||
GroupMemberRole,
|
||||
GroupNotify,
|
||||
GroupRequestOperateTypes,
|
||||
InstanceContext,
|
||||
KickMemberV2Req,
|
||||
MemberExtSourceType,
|
||||
NapCatCore,
|
||||
NodeIKernelGroupListener,
|
||||
NodeIKernelGroupService,
|
||||
} from '@/core';
|
||||
import { isNumeric, runAllWithTimeout } from '@/common/utils/helper';
|
||||
import { isNumeric, solveAsyncProblem } from '@/common/helper';
|
||||
import { LimitedHashTable } from '@/common/message-unique';
|
||||
import { NTEventWrapper } from '@/common/event';
|
||||
|
||||
export class NTQQGroupApi {
|
||||
context: InstanceContext;
|
||||
@@ -20,38 +19,96 @@ export class NTQQGroupApi {
|
||||
groupCache: Map<string, Group> = new Map<string, Group>();
|
||||
groupMemberCache: Map<string, Map<string, GroupMember>> = new Map<string, Map<string, GroupMember>>();
|
||||
groups: Group[] = [];
|
||||
essenceLRU = new LimitedHashTable<number, string>(1000);
|
||||
|
||||
constructor(context: InstanceContext, core: NapCatCore) {
|
||||
this.context = context;
|
||||
this.core = core;
|
||||
this.initCache().then().catch(context.logger.logError);
|
||||
this.initCache().then().catch(context.logger.logError.bind(context.logger));
|
||||
}
|
||||
|
||||
async initCache() {
|
||||
this.groups = await this.getGroups();
|
||||
for (const group of this.groups) {
|
||||
this.groupCache.set(group.groupCode, group);
|
||||
const data = await this.getGroupMembers(group.groupCode, 3000);
|
||||
this.groupMemberCache.set(group.groupCode, data);
|
||||
}
|
||||
this.context.logger.logDebug(`加载${this.groups.length}个群组缓存完成`);
|
||||
}
|
||||
async getCoreAndBaseInfo(uids: string[]) {
|
||||
return await this.core.eventWrapper.callNoListenerEvent(
|
||||
'NodeIKernelProfileService/getCoreAndBaseInfo',
|
||||
'nodeStore',
|
||||
uids,
|
||||
);
|
||||
}
|
||||
async fetchGroupEssenceList(groupCode: string) {
|
||||
const pskey = (await this.core.apis.UserApi.getPSkey(['qun.qq.com'])).domainPskeyMap.get('qun.qq.com')!;
|
||||
return this.context.session.getGroupService().fetchGroupEssenceList({
|
||||
groupCode: groupCode,
|
||||
pageStart: 0,
|
||||
pageLimit: 300,
|
||||
}, pskey);
|
||||
}
|
||||
|
||||
async clearGroupNotifiesUnreadCount(uk: boolean) {
|
||||
return this.context.session.getGroupService().clearGroupNotifiesUnreadCount(uk);
|
||||
}
|
||||
|
||||
async setGroupAvatar(gc: string, filePath: string) {
|
||||
return this.context.session.getGroupService().setHeader(gc, filePath);
|
||||
}
|
||||
|
||||
async getGroups(forced = false) {
|
||||
type ListenerType = NodeIKernelGroupListener['onGroupListUpdate'];
|
||||
const [_retData, _updateType, groupList] = await this.core.eventWrapper.CallNormalEvent<(force: boolean) => Promise<any>, ListenerType>
|
||||
(
|
||||
const [, , groupList] = await this.core.eventWrapper.callNormalEventV2(
|
||||
'NodeIKernelGroupService/getGroupList',
|
||||
'NodeIKernelGroupListener/onGroupListUpdate',
|
||||
1,
|
||||
5000,
|
||||
() => true,
|
||||
forced,
|
||||
);
|
||||
[forced],
|
||||
);
|
||||
return groupList;
|
||||
}
|
||||
|
||||
async getGroupExtFE0Info(groupCode: string[], forced = true) {
|
||||
return this.context.session.getGroupService().getGroupExt0xEF0Info(
|
||||
groupCode,
|
||||
[],
|
||||
{
|
||||
bindGuildId: 1,
|
||||
blacklistExpireTime: 1,
|
||||
companyId: 1,
|
||||
essentialMsgPrivilege: 1,
|
||||
essentialMsgSwitch: 1,
|
||||
fullGroupExpansionSeq: 1,
|
||||
fullGroupExpansionSwitch: 1,
|
||||
gangUpId: 1,
|
||||
groupAioBindGuildId: 1,
|
||||
groupBindGuildIds: 1,
|
||||
groupBindGuildSwitch: 1,
|
||||
groupExcludeGuildIds: 1,
|
||||
groupExtFlameData: 1,
|
||||
groupFlagPro1: 1,
|
||||
groupInfoExtSeq: 1,
|
||||
groupOwnerId: 1,
|
||||
groupSquareSwitch: 1,
|
||||
hasGroupCustomPortrait: 1,
|
||||
inviteRobotMemberExamine: 1,
|
||||
inviteRobotMemberSwitch: 1,
|
||||
inviteRobotSwitch: 1,
|
||||
isLimitGroupRtc: 1,
|
||||
lightCharNum: 1,
|
||||
luckyWord: 1,
|
||||
luckyWordId: 1,
|
||||
msgEventSeq: 1,
|
||||
qqMusicMedalSwitch: 1,
|
||||
reserve: 1,
|
||||
showPlayTogetherSwitch: 1,
|
||||
starId: 1,
|
||||
todoSeq: 1,
|
||||
viewedMsgDisappearTime: 1,
|
||||
},
|
||||
forced,
|
||||
);
|
||||
}
|
||||
|
||||
async getGroup(groupCode: string, forced = false) {
|
||||
let group = this.groupCache.get(groupCode.toString());
|
||||
if (!group) {
|
||||
@@ -70,64 +127,8 @@ export class NTQQGroupApi {
|
||||
return group;
|
||||
}
|
||||
|
||||
async getGroupMemberLatestSendTimeCache(GroupCode: string, uids: string[]) {
|
||||
return this.getGroupMemberLatestSendTime(GroupCode, uids);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过QQ自带数据库获取群成员最后发言时间(仅返回有效数据 且消耗延迟大 需要进行缓存)
|
||||
* @param GroupCode 群号
|
||||
* @param uids QQ号
|
||||
* @returns Map<string, string> key: uin value: sendTime
|
||||
* @example
|
||||
* let ret = await NTQQGroupApi.getGroupMemberLastestSendTime('123456');
|
||||
* for (let [uin, sendTime] of ret) {
|
||||
* console.log(uin, sendTime);
|
||||
* }
|
||||
*/
|
||||
async getGroupMemberLatestSendTime(GroupCode: string, uids: string[]) {
|
||||
const getdata = async (uid: string) => {
|
||||
const NTRet = await this.getLatestMsgByUids(GroupCode, [uid]);
|
||||
if (NTRet.result != 0 && NTRet.msgList.length < 1) {
|
||||
return undefined;
|
||||
}
|
||||
return { sendUin: NTRet.msgList[0].senderUin, sendTime: NTRet.msgList[0].msgTime };
|
||||
};
|
||||
const PromiseData: Promise<({
|
||||
sendUin: string;
|
||||
sendTime: string;
|
||||
} | undefined)>[] = [];
|
||||
const ret: Map<string, string> = new Map();
|
||||
for (const uid of uids) {
|
||||
PromiseData.push(getdata(uid).catch(() => undefined));
|
||||
}
|
||||
const allRet = await runAllWithTimeout(PromiseData, 2500);
|
||||
for (const PromiseDo of allRet) {
|
||||
if (PromiseDo) {
|
||||
ret.set(PromiseDo.sendUin, PromiseDo.sendTime);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
async getLatestMsgByUids(GroupCode: string, uids: string[]) {
|
||||
return await this.context.session.getMsgService().queryMsgsWithFilterEx('0', '0', '0', {
|
||||
chatInfo: {
|
||||
peerUid: GroupCode,
|
||||
chatType: ChatType.group,
|
||||
},
|
||||
filterMsgType: [],
|
||||
filterSendersUid: uids,
|
||||
filterMsgToTime: '0',
|
||||
filterMsgFromTime: '0',
|
||||
isReverseOrder: false,
|
||||
isIncludeCurrent: true,
|
||||
pageLimit: 1,
|
||||
});
|
||||
}
|
||||
|
||||
async getGroupMemberAll(GroupCode: string, forced = false) {
|
||||
return this.context.session.getGroupService().getAllMemberList(GroupCode, forced);
|
||||
async getGroupMemberAll(groupCode: string, forced = false) {
|
||||
return this.context.session.getGroupService().getAllMemberList(groupCode, forced);
|
||||
}
|
||||
|
||||
async getGroupMember(groupCode: string | number, memberUinOrUid: string | number) {
|
||||
@@ -136,11 +137,10 @@ export class NTQQGroupApi {
|
||||
let members = this.groupMemberCache.get(groupCodeStr);
|
||||
if (!members) {
|
||||
try {
|
||||
members = await this.getGroupMembers(groupCodeStr);
|
||||
members = await this.getGroupMembersV2(groupCodeStr);
|
||||
// 更新群成员列表
|
||||
this.groupMemberCache.set(groupCodeStr, members);
|
||||
}
|
||||
catch (e) {
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -158,37 +158,13 @@ export class NTQQGroupApi {
|
||||
|
||||
let member = getMember();
|
||||
if (!member) {
|
||||
members = await this.getGroupMembers(groupCodeStr);
|
||||
members = await this.getGroupMembersV2(groupCodeStr);
|
||||
member = getMember();
|
||||
}
|
||||
return member;
|
||||
}
|
||||
|
||||
async getLatestMsg(GroupCode: string, uins: string[]) {
|
||||
const uids: Array<string> = [];
|
||||
for (const uin of uins) {
|
||||
const uid = await this.core.apis.UserApi.getUidByUinV2(uin);
|
||||
if (uid) {
|
||||
uids.push(uid);
|
||||
}
|
||||
}
|
||||
return await this.context.session.getMsgService().queryMsgsWithFilterEx('0', '0', '0', {
|
||||
chatInfo: {
|
||||
peerUid: GroupCode,
|
||||
chatType: ChatType.group,
|
||||
},
|
||||
filterMsgType: [],
|
||||
filterSendersUid: uids,
|
||||
filterMsgToTime: '0',
|
||||
filterMsgFromTime: '0',
|
||||
isReverseOrder: false,
|
||||
isIncludeCurrent: true,
|
||||
pageLimit: 1,
|
||||
});
|
||||
}
|
||||
|
||||
async getGroupRecommendContactArkJson(GroupCode: string) {
|
||||
return this.context.session.getGroupService().getGroupRecommendContactArkJson(GroupCode);
|
||||
async getGroupRecommendContactArkJson(groupCode: string) {
|
||||
return this.context.session.getGroupService().getGroupRecommendContactArkJson(groupCode);
|
||||
}
|
||||
|
||||
async CreatGroupFileFolder(groupCode: string, folderName: string) {
|
||||
@@ -204,8 +180,6 @@ export class NTQQGroupApi {
|
||||
}
|
||||
|
||||
async addGroupEssence(GroupCode: string, msgId: string) {
|
||||
// 代码没测过
|
||||
// 需要 ob11msgid->msgId + (peer) -> msgSeq + msgRandom
|
||||
const MsgData = await this.context.session.getMsgService().getMsgsIncludeSelf({
|
||||
chatType: 2,
|
||||
guildId: '',
|
||||
@@ -216,13 +190,36 @@ export class NTQQGroupApi {
|
||||
msgRandom: parseInt(MsgData.msgList[0].msgRandom),
|
||||
msgSeq: parseInt(MsgData.msgList[0].msgSeq),
|
||||
};
|
||||
// GetMsgByShoretID(ShoretID); -> MsgService.getMsgs(Peer,MsgId,1,false); -> 组出参数
|
||||
return this.context.session.getGroupService().addGroupEssence(param);
|
||||
}
|
||||
|
||||
async kickMemberV2Inner(param: KickMemberV2Req) {
|
||||
return this.context.session.getGroupService().kickMemberV2(param);
|
||||
}
|
||||
|
||||
async deleteGroupBulletin(GroupCode: string, noticeId: string) {
|
||||
const psKey = (await this.core.apis.UserApi.getPSkey(['qun.qq.com'])).domainPskeyMap.get('qun.qq.com')!;
|
||||
return this.context.session.getGroupService().deleteGroupBulletin(GroupCode, psKey, noticeId);
|
||||
}
|
||||
|
||||
async quitGroupV2(GroupCode: string, needDeleteLocalMsg: boolean) {
|
||||
const param = {
|
||||
groupCode: GroupCode,
|
||||
needDeleteLocalMsg: needDeleteLocalMsg,
|
||||
};
|
||||
return this.context.session.getGroupService().quitGroupV2(param);
|
||||
}
|
||||
|
||||
async removeGroupEssenceBySeq(GroupCode: string, msgRandom: string, msgSeq: string) {
|
||||
const param = {
|
||||
groupCode: GroupCode,
|
||||
msgRandom: parseInt(msgRandom),
|
||||
msgSeq: parseInt(msgSeq),
|
||||
};
|
||||
return this.context.session.getGroupService().removeGroupEssence(param);
|
||||
}
|
||||
|
||||
async removeGroupEssence(GroupCode: string, msgId: string) {
|
||||
// 代码没测过
|
||||
// 需要 ob11msgid->msgId + (peer) -> msgSeq + msgRandom
|
||||
const MsgData = await this.context.session.getMsgService().getMsgsIncludeSelf({
|
||||
chatType: 2,
|
||||
guildId: '',
|
||||
@@ -233,42 +230,112 @@ export class NTQQGroupApi {
|
||||
msgRandom: parseInt(MsgData.msgList[0].msgRandom),
|
||||
msgSeq: parseInt(MsgData.msgList[0].msgSeq),
|
||||
};
|
||||
// GetMsgByShoretID(ShoretID); -> MsgService.getMsgs(Peer,MsgId,1,false); -> 组出参数
|
||||
return this.context.session.getGroupService().removeGroupEssence(param);
|
||||
}
|
||||
|
||||
async getSingleScreenNotifies(num: number) {
|
||||
const [_retData, _doubt, _seq, notifies] = await this.core.eventWrapper.CallNormalEvent<(arg1: boolean, arg2: string, arg3: number) => Promise<any>, (doubt: boolean, seq: string, notifies: GroupNotify[]) => void>
|
||||
(
|
||||
async getSingleScreenNotifies(doubt: boolean, num: number) {
|
||||
const [, , , notifies] = await this.core.eventWrapper.callNormalEventV2(
|
||||
'NodeIKernelGroupService/getSingleScreenNotifies',
|
||||
'NodeIKernelGroupListener/onGroupSingleScreenNotifies',
|
||||
1,
|
||||
5000,
|
||||
() => true,
|
||||
false,
|
||||
'',
|
||||
num,
|
||||
);
|
||||
[
|
||||
doubt,
|
||||
'',
|
||||
num,
|
||||
],
|
||||
);
|
||||
return notifies;
|
||||
}
|
||||
|
||||
async getGroupMemberV2(GroupCode: string, uid: string, forced = false) {
|
||||
type ListenerType = NodeIKernelGroupListener['onMemberInfoChange'];
|
||||
type EventType = NodeIKernelGroupService['getMemberInfo'];
|
||||
// NTEventDispatch.CreatListenerFunction('NodeIKernelGroupListener/onGroupMemberInfoUpdate',
|
||||
//return napCatCore.session.getGroupService().getMemberInfo(GroupCode, [uid], forced);
|
||||
const [, , , _members] = await this.core.eventWrapper.CallNormalEvent<EventType, ListenerType>
|
||||
(
|
||||
'NodeIKernelGroupService/getMemberInfo',
|
||||
const Listener = this.core.eventWrapper.registerListen(
|
||||
'NodeIKernelGroupListener/onMemberInfoChange',
|
||||
1,
|
||||
5000,
|
||||
(groupCode: string, changeType: number, members: Map<string, GroupMember>) => {
|
||||
return groupCode == GroupCode && members.has(uid);
|
||||
},
|
||||
GroupCode, [uid], forced,
|
||||
forced ? 5000 : 250,
|
||||
(params, _, members) => params === GroupCode && members.size > 0,
|
||||
);
|
||||
return _members.get(uid);
|
||||
const retData = await (
|
||||
this.core.eventWrapper
|
||||
.createEventFunction('NodeIKernelGroupService/getMemberInfo')
|
||||
)!(GroupCode, [uid], forced);
|
||||
if (retData.result !== 0) {
|
||||
throw new Error(`${retData.errMsg}`);
|
||||
}
|
||||
const result = await Listener as unknown;
|
||||
let member: GroupMember | undefined;
|
||||
if (Array.isArray(result) && result?.[2] instanceof Map) {
|
||||
const members = result[2] as Map<string, GroupMember>;
|
||||
member = members.get(uid);
|
||||
}
|
||||
return member;
|
||||
}
|
||||
async searchGroup(groupCode: string) {
|
||||
const [, ret] = await this.core.eventWrapper.callNormalEventV2(
|
||||
'NodeIKernelSearchService/searchGroup',
|
||||
'NodeIKernelSearchListener/onSearchGroupResult',
|
||||
[{
|
||||
keyWords: groupCode,
|
||||
groupNum: 25,
|
||||
exactSearch: false,
|
||||
penetrate: ''
|
||||
}],
|
||||
(ret) => ret.result === 0,
|
||||
(params) => !!params.groupInfos.find(g => g.groupCode === groupCode),
|
||||
1,
|
||||
5000
|
||||
);
|
||||
return ret.groupInfos.find(g => g.groupCode === groupCode);
|
||||
}
|
||||
async getGroupMemberEx(GroupCode: string, uid: string, forced = false, retry = 2) {
|
||||
const data = await solveAsyncProblem((eventWrapper: NTEventWrapper, GroupCode: string, uid: string, forced = false) => {
|
||||
return eventWrapper.callNormalEventV2(
|
||||
'NodeIKernelGroupService/getMemberInfo',
|
||||
'NodeIKernelGroupListener/onMemberInfoChange',
|
||||
[GroupCode, [uid], forced],
|
||||
(ret) => ret.result === 0,
|
||||
(params, _, members) => params === GroupCode && members.size > 0 && members.has(uid),
|
||||
1,
|
||||
forced ? 2500 : 250
|
||||
);
|
||||
}, this.core.eventWrapper, GroupCode, uid, forced);
|
||||
if (data && data[3] instanceof Map && data[3].has(uid)) {
|
||||
return data[3].get(uid);
|
||||
}
|
||||
if (retry > 0) {
|
||||
const trydata = await this.getGroupMemberEx(GroupCode, uid, true, retry - 1) as GroupMember | undefined;
|
||||
if (trydata) return trydata;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
async getGroupMembersV2(groupQQ: string, num = 3000): Promise<Map<string, GroupMember>> {
|
||||
const groupService = this.context.session.getGroupService();
|
||||
const sceneId = groupService.createMemberListScene(groupQQ, 'groupMemberList_MainWindow');
|
||||
const listener = this.core.eventWrapper.registerListen(
|
||||
'NodeIKernelGroupListener/onMemberListChange',
|
||||
1,
|
||||
5000,
|
||||
(params) => params.sceneId === sceneId,
|
||||
);
|
||||
try {
|
||||
const [membersFromFunc, membersFromListener] = await Promise.allSettled([
|
||||
groupService.getNextMemberList(sceneId, undefined, num),
|
||||
listener,
|
||||
]);
|
||||
if (membersFromFunc.status === 'fulfilled' && membersFromListener.status === 'fulfilled') {
|
||||
return new Map([
|
||||
...membersFromFunc.value.result.infos,
|
||||
...membersFromListener.value[0].infos,
|
||||
]);
|
||||
}
|
||||
if (membersFromFunc.status === 'fulfilled') {
|
||||
return membersFromFunc.value.result.infos;
|
||||
}
|
||||
if (membersFromListener.status === 'fulfilled') {
|
||||
return membersFromListener.value[0].infos;
|
||||
}
|
||||
throw new Error('获取群成员列表失败');
|
||||
} finally {
|
||||
groupService.destroyMemberListScene(sceneId);
|
||||
}
|
||||
}
|
||||
|
||||
async getGroupMembers(groupQQ: string, num = 3000): Promise<Map<string, GroupMember>> {
|
||||
@@ -276,41 +343,21 @@ export class NTQQGroupApi {
|
||||
const sceneId = groupService.createMemberListScene(groupQQ, 'groupMemberList_MainWindow');
|
||||
const result = await groupService.getNextMemberList(sceneId!, undefined, num);
|
||||
if (result.errCode !== 0) {
|
||||
throw ('获取群成员列表出错,' + result.errMsg);
|
||||
throw new Error('获取群成员列表出错,' + result.errMsg);
|
||||
}
|
||||
|
||||
//logDebug(`获取群(${groupQQ})成员列表结果:`, `finish: ${result.result.finish}`); //, Array.from(result.result.infos.values()));
|
||||
this.context.logger.logDebug(`获取群(${groupQQ})成员列表结果:`, `members: ${result.result.infos.size}`);
|
||||
return result.result.infos;
|
||||
/*
|
||||
console.log(sceneId);
|
||||
const result = await napCatCore.getGroupService().getNextMemberList(sceneId, num);
|
||||
console.log(result);
|
||||
|
||||
return result;
|
||||
*/
|
||||
}
|
||||
|
||||
async getGroupNotifies() {
|
||||
// 获取管理员变更
|
||||
// 加群通知,退出通知,需要管理员权限
|
||||
|
||||
}
|
||||
|
||||
async GetGroupFileCount(Gids: Array<string>) {
|
||||
async getGroupFileCount(Gids: Array<string>) {
|
||||
return this.context.session.getRichMediaService().batchGetGroupFileCount(Gids);
|
||||
}
|
||||
|
||||
async getGroupIgnoreNotifies() {
|
||||
}
|
||||
|
||||
async getArkJsonGroupShare(GroupCode: string) {
|
||||
const ret = await this.core.eventWrapper.callNoListenerEvent<(GroupId: string) => Promise<GeneralCallResult & {
|
||||
arkJson: string
|
||||
}>>(
|
||||
const ret = await this.core.eventWrapper.callNoListenerEvent(
|
||||
'NodeIKernelGroupService/getGroupRecommendContactArkJson',
|
||||
5000,
|
||||
GroupCode,
|
||||
);
|
||||
) as GeneralCallResult & { arkJson: string };
|
||||
return ret.arkJson;
|
||||
}
|
||||
|
||||
@@ -329,12 +376,12 @@ export class NTQQGroupApi {
|
||||
return this.context.session.getGroupService().operateSysNotify(
|
||||
false,
|
||||
{
|
||||
'operateType': operateType, // 2 拒绝
|
||||
'targetMsg': {
|
||||
'seq': seq, // 通知序列号
|
||||
'type': type,
|
||||
'groupCode': groupCode,
|
||||
'postscript': reason || ' ', // 仅传空值可能导致处理失败,故默认给个空格
|
||||
operateType: operateType, // 2 拒绝
|
||||
targetMsg: {
|
||||
seq: seq, // 通知序列号
|
||||
type: type,
|
||||
groupCode: groupCode,
|
||||
postscript: reason ?? ' ', // 仅传空值可能导致处理失败,故默认给个空格
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -368,19 +415,12 @@ export class NTQQGroupApi {
|
||||
return this.context.session.getGroupService().modifyGroupName(groupQQ, groupName, false);
|
||||
}
|
||||
|
||||
// 头衔不可用
|
||||
/*
|
||||
async setGroupTitle(groupQQ: string, uid: string, title: string) {
|
||||
|
||||
}
|
||||
*/
|
||||
|
||||
async publishGroupBulletin(groupQQ: string, content: string, picInfo: {
|
||||
id: string,
|
||||
width: number,
|
||||
height: number
|
||||
} | undefined = undefined, pinned: number = 0, confirmRequired: number = 0) {
|
||||
const _Pskey = (await this.core.apis.UserApi.getPSkey(['qun.qq.com'])).domainPskeyMap.get('qun.qq.com');
|
||||
const psKey = (await this.core.apis.UserApi.getPSkey(['qun.qq.com'])).domainPskeyMap.get('qun.qq.com');
|
||||
//text是content内容url编码
|
||||
const data = {
|
||||
text: encodeURI(content),
|
||||
@@ -389,7 +429,7 @@ export class NTQQGroupApi {
|
||||
pinned: pinned,
|
||||
confirmRequired: confirmRequired,
|
||||
};
|
||||
return this.context.session.getGroupService().publishGroupBulletin(groupQQ, _Pskey!, data);
|
||||
return this.context.session.getGroupService().publishGroupBulletin(groupQQ, psKey!, data);
|
||||
}
|
||||
|
||||
async getGroupRemainAtTimes(GroupCode: string) {
|
||||
@@ -397,7 +437,6 @@ export class NTQQGroupApi {
|
||||
}
|
||||
|
||||
async getMemberExtInfo(groupCode: string, uin: string) {
|
||||
// 仅NTQQ 9.9.11 24568测试 容易炸开谨慎使用
|
||||
return this.context.session.getGroupService().getMemberExtInfo(
|
||||
{
|
||||
groupCode: groupCode,
|
||||
|
@@ -1,9 +1,12 @@
|
||||
import { ChatType, GetFileListParam, Peer, RawMessage, SendMessageElement } from '@/core/entities';
|
||||
import { ChatType, GetFileListParam, Peer, RawMessage, SendMessageElement, SendStatusType } from '@/core/entities';
|
||||
import { InstanceContext, NapCatCore } from '@/core';
|
||||
import { onGroupFileInfoUpdateParamType } from '@/core/listeners';
|
||||
import { GeneralCallResult } from '@/core/services/common';
|
||||
|
||||
export class NTQQMsgApi {
|
||||
// nt_qq//global//nt_data//Emoji//emoji-resource//sysface_res/apng/ 下可以看到所有QQ表情预览
|
||||
// nt_qq\global\nt_data\Emoji\emoji-resource\face_config.json 里面有所有表情的id, 自带表情id是QSid, 标准emoji表情id是QCid
|
||||
// 其实以官方文档为准是最好的,https://bot.q.qq.com/wiki/develop/api-v2/openapi/emoji/model.html#EmojiType
|
||||
|
||||
context: InstanceContext;
|
||||
core: NapCatCore;
|
||||
|
||||
@@ -12,28 +15,20 @@ export class NTQQMsgApi {
|
||||
this.core = core;
|
||||
}
|
||||
|
||||
async FetchLongMsg(peer: Peer, msgId: string) {
|
||||
return this.context.session.getMsgService().fetchLongMsg(peer, msgId);
|
||||
async getAioFirstViewLatestMsgs(peer: Peer, MsgCount: number) {
|
||||
return this.context.session.getMsgService().getAioFirstViewLatestMsgs(peer, MsgCount);
|
||||
}
|
||||
|
||||
async sendShowInputStatusReq(peer: Peer, eventType: number) {
|
||||
return this.context.session.getMsgService().sendShowInputStatusReq(peer.chatType, eventType, peer.peerUid);
|
||||
}
|
||||
|
||||
async getMsgEmojiLikesList(peer: Peer, msgSeq: string, emojiId: string, emojiType: string, count: number = 20) {
|
||||
//console.log(peer, msgSeq, emojiId, emojiType, count);
|
||||
//注意此处emojiType 可选值一般为1-2 2好像是unicode表情dec值 大部分情况 Taged M likiowa
|
||||
return this.context.session.getMsgService().getMsgEmojiLikesList(peer, msgSeq, emojiId, emojiType, '', false, 20);
|
||||
//注意此处emojiType 可选值一般为1-2 2好像是unicode表情dec值 大部分情况 Taged Mlikiowa
|
||||
return this.context.session.getMsgService().getMsgEmojiLikesList(peer, msgSeq, emojiId, emojiType, '', false, count);
|
||||
}
|
||||
|
||||
// napCatCore: NapCatCore | null = null;
|
||||
// enum BaseEmojiType {
|
||||
// NORMAL_EMOJI,
|
||||
// SUPER_EMOJI,
|
||||
// RANDOM_SUPER_EMOJI,
|
||||
// CHAIN_SUPER_EMOJI,
|
||||
// EMOJI_EMOJI
|
||||
// }
|
||||
async setEmojiLike(peer: Peer, msgSeq: string, emojiId: string, set: boolean = true) {
|
||||
// nt_qq//global//nt_data//Emoji//emoji-resource//sysface_res/apng/ 下可以看到所有QQ表情预览
|
||||
// nt_qq\global\nt_data\Emoji\emoji-resource\face_config.json 里面有所有表情的id, 自带表情id是QSid, 标准emoji表情id是QCid
|
||||
// 其实以官方文档为准是最好的,https://bot.q.qq.com/wiki/develop/api-v2/openapi/emoji/model.html#EmojiType
|
||||
emojiId = emojiId.toString();
|
||||
return this.context.session.getMsgService().setMsgEmojiLikes(peer, msgSeq, emojiId, emojiId.length > 3 ? '2' : '1', set);
|
||||
}
|
||||
@@ -48,24 +43,10 @@ export class NTQQMsgApi {
|
||||
return this.context.session.getMsgService().forwardMsg(msgIds, peer, [peer], new Map());
|
||||
}
|
||||
|
||||
async getLastestMsgByUids(peer: Peer, count: number = 20, isReverseOrder: boolean = false) {
|
||||
const ret = await this.context.session.getMsgService().queryMsgsWithFilterEx('0', '0', '0', {
|
||||
chatInfo: peer,
|
||||
filterMsgType: [],
|
||||
filterSendersUid: [],
|
||||
filterMsgToTime: '0',
|
||||
filterMsgFromTime: '0',
|
||||
isReverseOrder: isReverseOrder,//此参数有点离谱 注意不是本次查询的排序 而是全部消历史信息的排序 默认false 从新消息拉取到旧消息
|
||||
isIncludeCurrent: true,
|
||||
pageLimit: count,
|
||||
});
|
||||
return ret;
|
||||
}
|
||||
|
||||
async getMsgsByMsgId(peer: Peer | undefined, msgIds: string[] | undefined) {
|
||||
if (!peer) throw new Error('peer is not allowed');
|
||||
if (!msgIds) throw new Error('msgIds is not allowed');
|
||||
//Mlikiowa: 参数不合规会导致NC异常崩溃 原因是TX未对进入参数判断 对应Android标记@NotNull AndroidJADX分析可得
|
||||
//MliKiowa: 参数不合规会导致NC异常崩溃 原因是TX未对进入参数判断 对应Android标记@NotNull AndroidJADX分析可得
|
||||
return await this.context.session.getMsgService().getMsgsByMsgId(peer, msgIds);
|
||||
}
|
||||
|
||||
@@ -78,8 +59,8 @@ export class NTQQMsgApi {
|
||||
}
|
||||
|
||||
async queryMsgsWithFilterExWithSeq(peer: Peer, msgSeq: string) {
|
||||
const ret = await this.context.session.getMsgService().queryMsgsWithFilterEx('0', '0', msgSeq, {
|
||||
chatInfo: peer,//此处为Peer 为关键查询参数 没有啥也没有 by mlik iowa
|
||||
return await this.context.session.getMsgService().queryMsgsWithFilterEx('0', '0', msgSeq, {
|
||||
chatInfo: peer,
|
||||
filterMsgType: [],
|
||||
filterSendersUid: [],
|
||||
filterMsgToTime: '0',
|
||||
@@ -88,34 +69,69 @@ export class NTQQMsgApi {
|
||||
isIncludeCurrent: true,
|
||||
pageLimit: 1,
|
||||
});
|
||||
return ret;
|
||||
}
|
||||
|
||||
async queryMsgsWithFilterExWithSeqV2(peer: Peer, msgSeq: string, MsgTime: string, SendersUid: string[]) {
|
||||
return await this.context.session.getMsgService().queryMsgsWithFilterEx('0', '0', msgSeq, {
|
||||
chatInfo: peer,
|
||||
filterMsgType: [],
|
||||
filterSendersUid: SendersUid,
|
||||
filterMsgToTime: MsgTime,
|
||||
filterMsgFromTime: MsgTime,
|
||||
isReverseOrder: false,
|
||||
isIncludeCurrent: true,
|
||||
pageLimit: 1,
|
||||
});
|
||||
}
|
||||
async queryFirstMsgBySeq(peer: Peer, msgSeq: string) {
|
||||
return await this.context.session.getMsgService().queryMsgsWithFilterEx('0', '0', msgSeq, {
|
||||
chatInfo: peer,
|
||||
filterMsgType: [],
|
||||
filterSendersUid: [],
|
||||
filterMsgToTime: '0',
|
||||
filterMsgFromTime: '0',
|
||||
isReverseOrder: true,
|
||||
isIncludeCurrent: true,
|
||||
pageLimit: 1,
|
||||
});
|
||||
}
|
||||
//@deprecated
|
||||
async getMsgsBySeqAndCount(peer: Peer, seq: string, count: number, desc: boolean, z: boolean) {
|
||||
return await this.context.session.getMsgService().getMsgsBySeqAndCount(peer, seq, count, desc, z);
|
||||
}
|
||||
async getMsgExBySeq(peer: Peer, msgSeq: string) {
|
||||
const DateNow = Math.floor(Date.now() / 1000);
|
||||
const filterMsgFromTime = (DateNow - 300).toString();
|
||||
const filterMsgToTime = DateNow.toString();
|
||||
return await this.context.session.getMsgService().queryMsgsWithFilterEx('0', '0', msgSeq, {
|
||||
chatInfo: peer,//此处为Peer 为关键查询参数 没有啥也没有 by mlik iowa
|
||||
filterMsgType: [],
|
||||
filterSendersUid: [],
|
||||
filterMsgToTime: filterMsgToTime,
|
||||
filterMsgFromTime: filterMsgFromTime,
|
||||
isReverseOrder: false,
|
||||
isIncludeCurrent: true,
|
||||
pageLimit: 100,
|
||||
});
|
||||
}
|
||||
|
||||
async setMsgRead(peer: Peer) {
|
||||
return this.context.session.getMsgService().setMsgRead(peer);
|
||||
}
|
||||
|
||||
async getGroupFileList(GroupCode: string, params: GetFileListParam) {
|
||||
const data = await this.core.eventWrapper.CallNormalEvent<
|
||||
(GroupCode: string, params: GetFileListParam) => Promise<unknown>,
|
||||
(groupFileListResult: onGroupFileInfoUpdateParamType) => void
|
||||
>(
|
||||
const [, groupFileListResult] = await this.core.eventWrapper.callNormalEventV2(
|
||||
'NodeIKernelRichMediaService/getGroupFileList',
|
||||
'NodeIKernelMsgListener/onGroupFileInfoUpdate',
|
||||
[
|
||||
GroupCode,
|
||||
params,
|
||||
],
|
||||
() => true,
|
||||
() => true, // Todo: 应当通过 groupFileListResult 判断
|
||||
1,
|
||||
5000,
|
||||
(groupFileListResult: onGroupFileInfoUpdateParamType) => {
|
||||
//Developer Mlikiowa Todo: 此处有问题 无法判断是否成功
|
||||
return true;
|
||||
},
|
||||
GroupCode,
|
||||
params,
|
||||
);
|
||||
return data[1].item;
|
||||
return groupFileListResult.item;
|
||||
}
|
||||
|
||||
async getMsgHistory(peer: Peer, msgId: string, count: number, isReverseOrder: boolean = false) {
|
||||
@@ -123,142 +139,77 @@ export class NTQQMsgApi {
|
||||
return this.context.session.getMsgService().getMsgsIncludeSelf(peer, msgId, count, isReverseOrder);
|
||||
}
|
||||
|
||||
async recallMsg(peer: Peer, msgIds: string[]) {
|
||||
await this.context.session.getMsgService().recallMsg({
|
||||
chatType: peer.chatType,
|
||||
peerUid: peer.peerUid,
|
||||
}, msgIds);
|
||||
}
|
||||
|
||||
async sendMsgV2(peer: Peer, msgElements: SendMessageElement[], waitComplete = true, timeout = 10000) {
|
||||
function generateMsgId() {
|
||||
const timestamp = Math.floor(Date.now() / 1000);
|
||||
const random = Math.floor(Math.random() * Math.pow(2, 32));
|
||||
const buffer = Buffer.alloc(8);
|
||||
buffer.writeUInt32BE(timestamp, 0);
|
||||
buffer.writeUInt32BE(random, 4);
|
||||
const msgId = BigInt('0x' + buffer.toString('hex')).toString();
|
||||
return msgId;
|
||||
}
|
||||
|
||||
// 此处有采用Hack方法 利用数据返回正确得到对应消息
|
||||
// 与之前 Peer队列 MsgSeq队列 真正的MsgId并发不同
|
||||
// 谨慎采用 目前测试暂无问题 Developer.Mlikiowa
|
||||
let msgId: string;
|
||||
try {
|
||||
msgId = await this.getMsgUnique(peer.chatType, await this.getServerTime());
|
||||
} catch (error) {
|
||||
//if (!napCatCore.session.getMsgService()['generateMsgUniqueId'])
|
||||
//兜底识别策略V2
|
||||
msgId = generateMsgId().toString();
|
||||
}
|
||||
const data = await this.core.eventWrapper.CallNormalEvent<
|
||||
(msgId: string, peer: Peer, msgElements: SendMessageElement[], map: Map<any, any>) => Promise<unknown>,
|
||||
(msgList: RawMessage[]) => void
|
||||
>(
|
||||
'NodeIKernelMsgService/sendMsg',
|
||||
async recallMsg(peer: Peer, msgId: string) {
|
||||
await this.core.eventWrapper.callNormalEventV2(
|
||||
'NodeIKernelMsgService/recallMsg',
|
||||
'NodeIKernelMsgListener/onMsgInfoListUpdate',
|
||||
[peer, [msgId]],
|
||||
() => true,
|
||||
(updatedList) => updatedList.find(m => m.msgId === msgId && m.recallTime !== '0') !== undefined,
|
||||
1,
|
||||
timeout,
|
||||
(msgRecords: RawMessage[]) => {
|
||||
for (const msgRecord of msgRecords) {
|
||||
if (msgRecord.msgId === msgId && msgRecord.sendStatus === 2) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
msgId,
|
||||
peer,
|
||||
msgElements,
|
||||
new Map(),
|
||||
1000,
|
||||
);
|
||||
const retMsg = data[1].find(msgRecord => {
|
||||
if (msgRecord.msgId === msgId) {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
return retMsg;
|
||||
}
|
||||
|
||||
sendMsgEx(peer: Peer, msgElements: SendMessageElement[], waitComplete = true, timeout = 10000) {
|
||||
//return NTQQMsgApi.sendMsgV1(peer, msgElements, waitComplete, timeout);
|
||||
}
|
||||
async PrepareTempChat(toUserUid: string, GroupCode: string, nickname: string) {
|
||||
//By Jadx/Ida Mlikiowa
|
||||
let TempGameSession = {
|
||||
nickname: "",
|
||||
gameAppId: "",
|
||||
selfTinyId: "",
|
||||
peerRoleId: "",
|
||||
peerOpenId: "",
|
||||
};
|
||||
return this.context.session.getMsgService().prepareTempChat({
|
||||
chatType: ChatType.temp,
|
||||
chatType: ChatType.KCHATTYPETEMPC2CFROMGROUP,
|
||||
peerUid: toUserUid,
|
||||
peerNickname: nickname,
|
||||
fromGroupCode: GroupCode,
|
||||
sig: "",
|
||||
selfPhone: "",
|
||||
sig: '',
|
||||
selfPhone: '',
|
||||
selfUid: this.core.selfInfo.uid,
|
||||
gameSession: TempGameSession
|
||||
gameSession: {
|
||||
nickname: '',
|
||||
gameAppId: '',
|
||||
selfTinyId: '',
|
||||
peerRoleId: '',
|
||||
peerOpenId: '',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async getTempChatInfo(chatType: ChatType, peerUid: string) {
|
||||
return this.context.session.getMsgService().getTempChatInfo(chatType, peerUid);
|
||||
}
|
||||
|
||||
async sendMsg(peer: Peer, msgElements: SendMessageElement[], waitComplete = true, timeout = 10000) {
|
||||
//唉? !我有个想法
|
||||
if (peer.chatType === ChatType.temp && peer.guildId && peer.guildId !== '') {
|
||||
let member = await this.core.apis.GroupApi.getGroupMember(peer.guildId, peer.peerUid!);
|
||||
//唉?!我有个想法
|
||||
if (peer.chatType === ChatType.KCHATTYPETEMPC2CFROMGROUP && peer.guildId && peer.guildId !== '') {
|
||||
const member = await this.core.apis.GroupApi.getGroupMember(peer.guildId, peer.peerUid!);
|
||||
if (member) {
|
||||
await this.PrepareTempChat(peer.peerUid, peer.guildId, member.nick);
|
||||
}
|
||||
}
|
||||
const msgId = await this.getMsgUnique(peer.chatType, await this.getServerTime());
|
||||
const msgId = await this.generateMsgUniqueId(peer.chatType);
|
||||
peer.guildId = msgId;
|
||||
const data = await this.core.eventWrapper.CallNormalEvent<
|
||||
(msgId: string, peer: Peer, msgElements: SendMessageElement[], map: Map<any, any>) => Promise<unknown>,
|
||||
(msgList: RawMessage[]) => void
|
||||
>(
|
||||
const [, msgList] = await this.core.eventWrapper.callNormalEventV2(
|
||||
'NodeIKernelMsgService/sendMsg',
|
||||
'NodeIKernelMsgListener/onMsgInfoListUpdate',
|
||||
1,
|
||||
timeout,
|
||||
(msgRecords: RawMessage[]) => {
|
||||
[
|
||||
'0',
|
||||
peer,
|
||||
msgElements,
|
||||
new Map(),
|
||||
],
|
||||
(ret) => ret.result === 0,
|
||||
msgRecords => {
|
||||
for (const msgRecord of msgRecords) {
|
||||
if (msgRecord.guildId === msgId && msgRecord.sendStatus === 2) {
|
||||
if (msgRecord.guildId === msgId && msgRecord.sendStatus === SendStatusType.KSEND_STATUS_SUCCESS) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
'0',
|
||||
peer,
|
||||
msgElements,
|
||||
new Map(),
|
||||
1,
|
||||
timeout,
|
||||
);
|
||||
const retMsg = data[1].find(msgRecord => {
|
||||
if (msgRecord.guildId === msgId) {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
return retMsg;
|
||||
return msgList.find(msgRecord => msgRecord.guildId === msgId);
|
||||
}
|
||||
|
||||
async getMsgUnique(chatType: number, time: string) {
|
||||
if (this.context.basicInfoWrapper.requireMinNTQQBuild('26702')) {
|
||||
return this.context.session.getMsgService().generateMsgUniqueId(chatType, time);
|
||||
}
|
||||
return this.context.session.getMsgService().getMsgUniqueId(time);
|
||||
}
|
||||
|
||||
async getServerTime() {
|
||||
return this.context.session.getMSFService().getServerTime();
|
||||
}
|
||||
|
||||
async getServerTimeV2() {
|
||||
return this.core.eventWrapper.callNoListenerEvent<() => string>('NodeIKernelMsgService/getServerTime', 5000);
|
||||
async generateMsgUniqueId(chatType: number) {
|
||||
return this.context.session.getMsgService().generateMsgUniqueId(chatType, this.context.session.getMSFService().getServerTime());
|
||||
}
|
||||
|
||||
async forwardMsg(srcPeer: Peer, destPeer: Peer, msgIds: string[]) {
|
||||
@@ -269,34 +220,28 @@ export class NTQQMsgApi {
|
||||
const msgInfos = msgIds.map(id => {
|
||||
return { msgId: id, senderShowName: this.core.selfInfo.nick };
|
||||
});
|
||||
const data = await this.core.eventWrapper.CallNormalEvent<
|
||||
(msgInfo: typeof msgInfos, srcPeer: Peer, destPeer: Peer, comment: Array<any>, attr: Map<any, any>) => Promise<unknown>,
|
||||
(msgList: RawMessage[]) => void
|
||||
>(
|
||||
const [, msgList] = await this.core.eventWrapper.callNormalEventV2(
|
||||
'NodeIKernelMsgService/multiForwardMsgWithComment',
|
||||
'NodeIKernelMsgListener/onMsgInfoListUpdate',
|
||||
1,
|
||||
5000,
|
||||
(msgRecords: RawMessage[]) => {
|
||||
for (const msgRecord of msgRecords) {
|
||||
if (msgRecord.peerUid == destPeer.peerUid && msgRecord.senderUid == this.core.selfInfo.uid) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
msgInfos,
|
||||
srcPeer,
|
||||
destPeer,
|
||||
[],
|
||||
new Map(),
|
||||
[
|
||||
msgInfos,
|
||||
srcPeer,
|
||||
destPeer,
|
||||
[],
|
||||
new Map(),
|
||||
],
|
||||
() => true,
|
||||
(msgRecords) => msgRecords.some(
|
||||
msgRecord => msgRecord.peerUid === destPeer.peerUid
|
||||
&& msgRecord.senderUid === this.core.selfInfo.uid
|
||||
),
|
||||
);
|
||||
for (const msg of data[1]) {
|
||||
for (const msg of msgList) {
|
||||
const arkElement = msg.elements.find(ele => ele.arkElement);
|
||||
if (!arkElement) {
|
||||
continue;
|
||||
}
|
||||
const forwardData: any = JSON.parse(arkElement.arkElement.bytesData);
|
||||
const forwardData: any = JSON.parse(arkElement.arkElement?.bytesData ?? '');
|
||||
if (forwardData.app != 'com.tencent.multimsg') {
|
||||
continue;
|
||||
}
|
||||
@@ -307,7 +252,7 @@ export class NTQQMsgApi {
|
||||
throw new Error('转发消息超时');
|
||||
}
|
||||
|
||||
async markallMsgAsRead() {
|
||||
async markAllMsgAsRead() {
|
||||
return this.context.session.getMsgService().setAllC2CAndGroupMsgRead();
|
||||
}
|
||||
}
|
||||
|
@@ -1,78 +1,7 @@
|
||||
import { RequestUtil } from '@/common/utils/request';
|
||||
import { RequestUtil } from '@/common/request';
|
||||
import { MiniAppLuaJsonType } from '@/core';
|
||||
import { InstanceContext, NapCatCore } from '..';
|
||||
|
||||
// let t = await napCatCore.session.getGroupService().shareDigest({
|
||||
// appId: "100497308",
|
||||
// appType: 1,
|
||||
// msgStyle: 0,
|
||||
// recvUin: "726067488",
|
||||
// sendType: 1,
|
||||
// clientInfo: {
|
||||
// platform: 1
|
||||
// },
|
||||
// richMsg: {
|
||||
// usingArk: true,
|
||||
// title: "Bot测试title",
|
||||
// summary: "Bot测试summary",
|
||||
// url: "https://www.bilibili.com",
|
||||
// pictureUrl: "https://y.qq.com/music/photo_new/T002R300x300M0000035DC6W4ZpSqf_1.jpg?max_age=2592000",
|
||||
// brief: "Bot测试brief",
|
||||
// }
|
||||
// });
|
||||
// {
|
||||
// errCode: 0,
|
||||
// errMsg: '',
|
||||
// rsp: {
|
||||
// sendType: 1,
|
||||
// recvUin: '726067488',
|
||||
// recvOpenId: '',
|
||||
// errCode: 901501,
|
||||
// errMsg: 'imagent service_error:150_OIDB_NO_PRIV',
|
||||
// extInfo: {
|
||||
// wording: '消息下发失败(错误码:901501)',
|
||||
// jumpResult: 0,
|
||||
// jumpUrl: '',
|
||||
// level: 0,
|
||||
// subLevel: 0,
|
||||
// developMsg: 'imagent error'
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// export class MusicSign {
|
||||
// private readonly url: string;
|
||||
|
||||
// constructor(url: string) {
|
||||
// this.url = url;
|
||||
// }
|
||||
|
||||
// sign(postData: CustomMusicSignPostData | IdMusicSignPostData): Promise<any> {
|
||||
// return new Promise((resolve, reject) => {
|
||||
// fetch(this.url, {
|
||||
// method: 'POST', // 指定请求方法为 POST
|
||||
// headers: {
|
||||
// 'Content-Type': 'application/json' // 设置请求头,指明发送的数据类型为 JSON
|
||||
// },
|
||||
// body: JSON.stringify(postData) // 将 JavaScript 对象转换为 JSON 字符串作为请求体
|
||||
// })
|
||||
// .then(response => {
|
||||
// if (!response.ok) {
|
||||
// reject(response.statusText); // 请求失败,返回错误信息
|
||||
// }
|
||||
// return response.json(); // 解析 JSON 格式的响应体
|
||||
// })
|
||||
// .then(data => {
|
||||
// logDebug('音乐消息生成成功', data);
|
||||
// resolve(data);
|
||||
// })
|
||||
// .catch(error => {
|
||||
// reject(error);
|
||||
// });
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
|
||||
export class NTQQMusicSignApi {
|
||||
context: InstanceContext;
|
||||
core: NapCatCore;
|
||||
@@ -285,11 +214,6 @@ export class NTQQMusicSignApi {
|
||||
//console.log(MusicReal);
|
||||
return { ...MusicReal.data, mid: signedMid };
|
||||
}
|
||||
|
||||
async CreateMusicThirdWay1(id: string = '', mid: string = '') {
|
||||
|
||||
}
|
||||
|
||||
//转换外域名为 https://qq.ugcimg.cn/v1/cpqcbu4b8870i61bde6k7cbmjgejq8mr3in82qir4qi7ielffv5slv8ck8g42novtmev26i233ujtuab6tvu2l2sjgtupfr389191v00s1j5oh5325j5eqi40774jv1i/khovifoh7jrqd6eahoiv7koh8o
|
||||
//https://cgi.connect.qq.com/qqconnectopen/openapi/change_image_url?url=https://th.bing.com/th?id=OSK.b8ed36f1fb1889de6dc84fd81c187773&w=46&h=46&c=11&rs=1&qlt=80&o=6&dpr=2&pid=SANGAM
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { GeneralCallResult, InstanceContext, NapCatCore } from '@/core';
|
||||
import { InstanceContext, NapCatCore } from '@/core';
|
||||
|
||||
export class NTQQSystemApi {
|
||||
context: InstanceContext;
|
||||
@@ -13,7 +13,7 @@ export class NTQQSystemApi {
|
||||
return this.core.util.hasOtherRunningQQProcess();
|
||||
}
|
||||
|
||||
async ORCImage(filePath: string) {
|
||||
async ocrImage(filePath: string) {
|
||||
return this.context.session.getNodeMiscService().wantWinScreenOCR(filePath);
|
||||
}
|
||||
|
||||
@@ -21,27 +21,16 @@ export class NTQQSystemApi {
|
||||
return this.context.session.getRichMediaService().translateEnWordToZn(words);
|
||||
}
|
||||
|
||||
//调用会超时 没灯用 (好像是通知listener的) onLineDev
|
||||
async getOnlineDev() {
|
||||
return this.context.session.getMsgService().getOnLineDev();
|
||||
this.context.session.getMsgService().getOnLineDev();
|
||||
}
|
||||
|
||||
//1-2-162b9b42-65b9-4405-a8ed-2e256ec8aa50
|
||||
async getArkJsonCollection(cid: string) {
|
||||
const ret = await this.core.eventWrapper.callNoListenerEvent<(cid: string) => Promise<GeneralCallResult & {
|
||||
arkJson: string
|
||||
}>>(
|
||||
'NodeIKernelCollectionService/collectionArkShare',
|
||||
5000,
|
||||
'1717662698058',
|
||||
);
|
||||
return ret;
|
||||
return await this.core.eventWrapper.callNoListenerEvent('NodeIKernelCollectionService/collectionArkShare', '1717662698058');
|
||||
}
|
||||
|
||||
async BootMiniApp(appfile: string, params: string) {
|
||||
async bootMiniApp(appFile: string, params: string) {
|
||||
await this.context.session.getNodeMiscService().setMiniAppVersion('2.16.4');
|
||||
const c = await this.context.session.getNodeMiscService().getMiniAppPath();
|
||||
|
||||
return this.context.session.getNodeMiscService().startNewMiniApp(appfile, params);
|
||||
return this.context.session.getNodeMiscService().startNewMiniApp(appFile, params);
|
||||
}
|
||||
}
|
||||
|
@@ -1,8 +1,7 @@
|
||||
import type { ModifyProfileParams, User, UserDetailInfoByUin, UserDetailInfoByUinV2 } from '@/core/entities';
|
||||
import { NodeIKernelProfileListener } from '@/core/listeners';
|
||||
import { RequestUtil } from '@/common/utils/request';
|
||||
import { NodeIKernelProfileService, ProfileBizType, UserDetailSource } from '@/core/services';
|
||||
import { InstanceContext, NapCatCore } from '..';
|
||||
import { ModifyProfileParams, User, UserDetailSource } from '@/core/entities';
|
||||
import { RequestUtil } from '@/common/request';
|
||||
import { InstanceContext, NapCatCore, ProfileBizType } from '..';
|
||||
import { solveAsyncProblem } from '@/common/helper';
|
||||
|
||||
export class NTQQUserApi {
|
||||
context: InstanceContext;
|
||||
@@ -12,12 +11,16 @@ export class NTQQUserApi {
|
||||
this.context = context;
|
||||
this.core = core;
|
||||
}
|
||||
|
||||
//self_tind格式
|
||||
async createUidFromTinyId(tinyId: string) {
|
||||
return this.context.session.getMsgService().createUidFromTinyId(this.core.selfInfo.uin, tinyId);
|
||||
}
|
||||
async getStatusByUid(uid: string) {
|
||||
return this.context.session.getProfileService().getStatus(uid);
|
||||
}
|
||||
async getProfileLike(uid: string) {
|
||||
return this.context.session.getProfileLikeService().getBuddyProfileLike({
|
||||
friendUids: [
|
||||
uid,
|
||||
],
|
||||
friendUids: [uid],
|
||||
basic: 1,
|
||||
vote: 1,
|
||||
favorite: 0,
|
||||
@@ -27,7 +30,18 @@ export class NTQQUserApi {
|
||||
limit: 20,
|
||||
});
|
||||
}
|
||||
|
||||
async fetchOtherProfileLike(uid: string) {
|
||||
return this.context.session.getProfileLikeService().getBuddyProfileLike({
|
||||
friendUids: [uid],
|
||||
basic: 1,
|
||||
vote: 1,
|
||||
favorite: 0,
|
||||
userProfile: 0,
|
||||
type: 1,
|
||||
start: 0,
|
||||
limit: 20,
|
||||
});
|
||||
}
|
||||
async setLongNick(longNick: string) {
|
||||
return this.context.session.getProfileService().setLongNick(longNick);
|
||||
}
|
||||
@@ -63,89 +77,43 @@ export class NTQQUserApi {
|
||||
return this.context.session.getGroupService().setHeader(gc, filePath);
|
||||
}
|
||||
|
||||
async fetchUserDetailInfos(uids: string[]) {
|
||||
//26702 以上使用新接口 .Dev Mlikiowa
|
||||
type EventService = NodeIKernelProfileService['fetchUserDetailInfo'];
|
||||
type EventListener = NodeIKernelProfileListener['onUserDetailInfoChanged'];
|
||||
const retData: User[] = [];
|
||||
const [_retData, _retListener] = await this.core.eventWrapper.CallNormalEvent<
|
||||
EventService, EventListener
|
||||
>(
|
||||
async fetchUserDetailInfo(uid: string, mode: UserDetailSource = UserDetailSource.KDB) {
|
||||
const [_retData, profile] = await this.core.eventWrapper.callNormalEventV2(
|
||||
'NodeIKernelProfileService/fetchUserDetailInfo',
|
||||
'NodeIKernelProfileListener/onUserDetailInfoChanged',
|
||||
uids.length,
|
||||
5000,
|
||||
(profile) => {
|
||||
if (uids.includes(profile.uid)) {
|
||||
const RetUser: User = {
|
||||
...profile.simpleInfo.coreInfo,
|
||||
...profile.simpleInfo.status,
|
||||
...profile.simpleInfo.vasInfo,
|
||||
...profile.commonExt,
|
||||
...profile.simpleInfo.baseInfo,
|
||||
qqLevel: profile.commonExt.qqLevel,
|
||||
pendantId: '',
|
||||
};
|
||||
retData.push(RetUser);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
'BuddyProfileStore',
|
||||
uids,
|
||||
UserDetailSource.KSERVER,
|
||||
[ProfileBizType.KALL],
|
||||
);
|
||||
|
||||
return retData;
|
||||
}
|
||||
|
||||
async fetchUserDetailInfo(uid: string) {
|
||||
type EventService = NodeIKernelProfileService['fetchUserDetailInfo'];
|
||||
type EventListener = NodeIKernelProfileListener['onUserDetailInfoChanged'];
|
||||
const [_retData, profile] = await this.core.eventWrapper.CallNormalEvent<EventService, EventListener>(
|
||||
'NodeIKernelProfileService/fetchUserDetailInfo',
|
||||
'NodeIKernelProfileListener/onUserDetailInfoChanged',
|
||||
1,
|
||||
5000,
|
||||
[
|
||||
'BuddyProfileStore',
|
||||
[uid],
|
||||
mode,
|
||||
[ProfileBizType.KALL],
|
||||
],
|
||||
() => true,
|
||||
(profile) => profile.uid === uid,
|
||||
'BuddyProfileStore',
|
||||
[uid],
|
||||
UserDetailSource.KSERVER,
|
||||
[ProfileBizType.KALL],
|
||||
);
|
||||
const RetUser: User = {
|
||||
...profile.simpleInfo.coreInfo,
|
||||
...profile.simpleInfo.status,
|
||||
...profile.simpleInfo.vasInfo,
|
||||
...profile.commonExt,
|
||||
...profile.simpleInfo.baseInfo,
|
||||
qqLevel: profile.commonExt.qqLevel,
|
||||
qqLevel: profile.commonExt?.qqLevel,
|
||||
age: profile.simpleInfo.baseInfo.age,
|
||||
pendantId: '',
|
||||
...profile.simpleInfo.coreInfo
|
||||
};
|
||||
return RetUser;
|
||||
}
|
||||
|
||||
async getUserDetailInfo(uid: string) {
|
||||
if (this.context.basicInfoWrapper.requireMinNTQQBuild('26702')) {
|
||||
return this.fetchUserDetailInfo(uid);
|
||||
async getUserDetailInfo(uid: string): Promise<User> {
|
||||
let retUser = await solveAsyncProblem(async (uid) => this.fetchUserDetailInfo(uid, UserDetailSource.KDB), uid);
|
||||
if (retUser && retUser.uin !== '0') {
|
||||
return retUser;
|
||||
}
|
||||
return this.getUserDetailInfoOld(uid);
|
||||
}
|
||||
|
||||
async getUserDetailInfoOld(uid: string) {
|
||||
type EventService = NodeIKernelProfileService['getUserDetailInfoWithBizInfo'];
|
||||
type EventListener = NodeIKernelProfileListener['onProfileDetailInfoChanged'];
|
||||
const [_retData, profile] = await this.core.eventWrapper.CallNormalEvent<EventService, EventListener>(
|
||||
'NodeIKernelProfileService/getUserDetailInfoWithBizInfo',
|
||||
'NodeIKernelProfileListener/onProfileDetailInfoChanged',
|
||||
2,
|
||||
5000,
|
||||
(profile) => profile.uid === uid,
|
||||
uid,
|
||||
[0],
|
||||
);
|
||||
return profile;
|
||||
this.context.logger.logDebug('[NapCat] [Mark] getUserDetailInfo Mode1 Failed.');
|
||||
retUser = await this.fetchUserDetailInfo(uid, UserDetailSource.KSERVER);
|
||||
if (retUser && retUser.uin === '0') {
|
||||
retUser.uin = await this.core.apis.UserApi.getUidByUinV2(uid) ?? '0';
|
||||
}
|
||||
return retUser;
|
||||
}
|
||||
|
||||
async modifySelfProfile(param: ModifyProfileParams) {
|
||||
@@ -155,15 +123,8 @@ export class NTQQUserApi {
|
||||
//需要异常处理
|
||||
async getCookies(domain: string) {
|
||||
const ClientKeyData = await this.forceFetchClientKey();
|
||||
const requestUrl = `https://ssl.ptlogin2.qq.com/jump?${
|
||||
new URLSearchParams({
|
||||
ptlang: '1033',
|
||||
clientuin: this.core.selfInfo.uin,
|
||||
clientkey: ClientKeyData.clientKey,
|
||||
u1: `https://user.qzone.qq.com/${this.core.selfInfo.uin}/infocenter`,
|
||||
keyindex: '19',
|
||||
})
|
||||
}`;
|
||||
const requestUrl = 'https://ssl.ptlogin2.qq.com/jump?ptlang=1033&clientuin=' + this.core.selfInfo.uin +
|
||||
'&clientkey=' + ClientKeyData.clientKey + '&u1=https%3A%2F%2F' + domain + '%2F' + this.core.selfInfo.uin + '%2Finfocenter&keyindex=19%27';
|
||||
return await RequestUtil.HttpsGetCookies(requestUrl);
|
||||
}
|
||||
|
||||
@@ -178,7 +139,6 @@ export class NTQQUserApi {
|
||||
version: 0,
|
||||
aioKeywordVersion: 0,
|
||||
});
|
||||
// console.log(robotUinRanges?.response?.robotUinRanges);
|
||||
return robotUinRanges?.response?.robotUinRanges;
|
||||
}
|
||||
|
||||
@@ -187,113 +147,55 @@ export class NTQQUserApi {
|
||||
async getQzoneCookies() {
|
||||
const ClientKeyData = await this.forceFetchClientKey();
|
||||
const requestUrl = 'https://ssl.ptlogin2.qq.com/jump?ptlang=1033&clientuin=' + this.core.selfInfo.uin + '&clientkey=' + ClientKeyData.clientKey + '&u1=https%3A%2F%2Fuser.qzone.qq.com%2F' + this.core.selfInfo.uin + '%2Finfocenter&keyindex=19%27';
|
||||
const cookies: { [key: string]: string; } = await RequestUtil.HttpsGetCookies(requestUrl);
|
||||
return cookies;
|
||||
return await RequestUtil.HttpsGetCookies(requestUrl);
|
||||
}
|
||||
|
||||
//需要异常处理
|
||||
|
||||
async getSkey(): Promise<string | undefined> {
|
||||
async getSKey(): Promise<string | undefined> {
|
||||
const ClientKeyData = await this.forceFetchClientKey();
|
||||
if (ClientKeyData.result !== 0) {
|
||||
throw new Error('getClientKey Error');
|
||||
}
|
||||
const clientKey = ClientKeyData.clientKey;
|
||||
const keyIndex = ClientKeyData.keyIndex;
|
||||
// const keyIndex = ClientKeyData.keyIndex;
|
||||
const requestUrl = 'https://ssl.ptlogin2.qq.com/jump?ptlang=1033&clientuin=' + this.core.selfInfo.uin + '&clientkey=' + clientKey + '&u1=https%3A%2F%2Fh5.qzone.qq.com%2Fqqnt%2Fqzoneinpcqq%2Ffriend%3Frefresh%3D0%26clientuin%3D0%26darkMode%3D0&keyindex=19%27';
|
||||
const cookies: { [key: string]: string; } = await RequestUtil.HttpsGetCookies(requestUrl);
|
||||
const skey = cookies['skey'];
|
||||
if (!skey) {
|
||||
throw new Error('getSkey Skey is Empty');
|
||||
throw new Error('SKey is Empty');
|
||||
}
|
||||
return skey;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
async getUidByUin(Uin: string) {
|
||||
if (this.context.basicInfoWrapper.requireMinNTQQBuild('26702')) {
|
||||
return await this.getUidByUinV2(Uin);
|
||||
}
|
||||
return await this.getUidByUinV1(Uin);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
async getUinByUid(Uid: string) {
|
||||
if (this.context.basicInfoWrapper.requireMinNTQQBuild('26702')) {
|
||||
return await this.getUinByUidV2(Uid);
|
||||
}
|
||||
return await this.getUinByUidV1(Uid);
|
||||
}
|
||||
|
||||
//后期改成流水线处理
|
||||
async getUidByUinV2(Uin: string) {
|
||||
let uid = (await this.context.session.getProfileService().getUidByUinV2('FriendsServiceImpl', [Uin])).get(Uin);
|
||||
let uid = (await this.context.session.getGroupService().getUidByUins([Uin])).uids.get(Uin);
|
||||
if (uid) return uid;
|
||||
uid = (await this.context.session.getGroupService().getUidByUins([Uin])).uids.get(Uin);
|
||||
uid = (await this.context.session.getProfileService().getUidByUin('FriendsServiceImpl', [Uin])).get(Uin);
|
||||
if (uid) return uid;
|
||||
uid = (await this.context.session.getUixConvertService().getUid([Uin])).uidInfo.get(Uin);
|
||||
if (uid) return uid;
|
||||
// console.log((await this.core.getApiContext().FriendApi.getBuddyIdMapCache(true)));
|
||||
uid = (await this.core.apis.FriendApi.getBuddyIdMapCache(true)).getValue(Uin);//从Buddy缓存获取Uid
|
||||
if (uid) return uid;
|
||||
uid = (await this.core.apis.FriendApi.getBuddyIdMap(true)).getValue(Uin);
|
||||
if (uid) return uid;
|
||||
const unveifyUid = (await this.getUserDetailInfoByUinV2(Uin)).detail.uid;//从QQ Native 特殊转换
|
||||
if (unveifyUid.indexOf('*') == -1) uid = unveifyUid;
|
||||
const unverifiedUid = (await this.getUserDetailInfoByUin(Uin)).detail.uid;//从QQ Native 特殊转换
|
||||
if (unverifiedUid.indexOf('*') == -1) uid = unverifiedUid;
|
||||
//if (uid) return uid;
|
||||
return uid;
|
||||
}
|
||||
|
||||
//后期改成流水线处理
|
||||
async getUinByUidV2(Uid: string) {
|
||||
let uin = (await this.context.session.getProfileService().getUinByUid('FriendsServiceImpl', [Uid])).get(Uid);
|
||||
let uin = (await this.context.session.getGroupService().getUinByUids([Uid])).uins.get(Uid);
|
||||
if (uin) return uin;
|
||||
uin = (await this.context.session.getGroupService().getUinByUids([Uid])).uins.get(Uid);
|
||||
uin = (await this.context.session.getProfileService().getUinByUid('FriendsServiceImpl', [Uid])).get(Uid);
|
||||
if (uin) return uin;
|
||||
uin = (await this.context.session.getUixConvertService().getUin([Uid])).uinInfo.get(Uid);
|
||||
if (uin) return uin;
|
||||
uin = (await this.core.apis.FriendApi.getBuddyIdMapCache(true)).getKey(Uid);//从Buddy缓存获取Uin
|
||||
if (uin) return uin;
|
||||
uin = (await this.core.apis.FriendApi.getBuddyIdMap(true)).getKey(Uid);
|
||||
if (uin) return uin;
|
||||
uin = (await this.getUserDetailInfo(Uid)).uin; //从QQ Native 转换
|
||||
return uin;
|
||||
}
|
||||
|
||||
async getUidByUinV1(Uin: string) {
|
||||
// 通用转换开始尝试
|
||||
let uid = (await this.context.session.getUixConvertService().getUid([Uin])).uidInfo.get(Uin);
|
||||
if (!uid) {
|
||||
const unveifyUid = (await this.getUserDetailInfoByUin(Uin)).info.uid;//从QQ Native 特殊转换 方法三
|
||||
if (unveifyUid.indexOf('*') == -1) {
|
||||
uid = unveifyUid;
|
||||
}
|
||||
}
|
||||
return uid;
|
||||
}
|
||||
|
||||
async getUinByUidV1(Uid: string) {
|
||||
const ret = await this.core.eventWrapper.callNoListenerEvent<(Uin: string[]) => Promise<{
|
||||
uinInfo: Map<string, string>
|
||||
}>>
|
||||
('NodeIKernelUixConvertService/getUin', 5000, [Uid]);
|
||||
let uin = ret.uinInfo.get(Uid);
|
||||
if (!uin) {
|
||||
uin = (await this.getUserDetailInfo(Uid)).uin; //从QQ Native 转换
|
||||
}
|
||||
// if (!uin) {
|
||||
// uin = (await NTQQFriendApi.getFriends(false)).find((t) => { t.uid == Uid })?.uin; //从QQ Native 缓存转换
|
||||
// }
|
||||
// if (!uin) {
|
||||
// uin = (await NTQQFriendApi.getFriends(true)).find((t) => { t.uid == Uid })?.uin; //从QQ Native 非缓存转换
|
||||
// }
|
||||
return uin;
|
||||
}
|
||||
|
||||
async getRecentContactListSnapShot(count: number) {
|
||||
return await this.context.session.getRecentContactService().getRecentContactListSnapShot(count);
|
||||
}
|
||||
@@ -310,14 +212,11 @@ export class NTQQUserApi {
|
||||
return await this.context.session.getRecentContactService().getRecentContactList();
|
||||
}
|
||||
|
||||
async getUserDetailInfoByUinV2(Uin: string) {
|
||||
return await this.core.eventWrapper.callNoListenerEvent<(Uin: string) => Promise<UserDetailInfoByUinV2>>
|
||||
('NodeIKernelProfileService/getUserDetailInfoByUin', 5000, Uin);
|
||||
}
|
||||
|
||||
async getUserDetailInfoByUin(Uin: string) {
|
||||
return this.core.eventWrapper.callNoListenerEvent<(Uin: string) => Promise<UserDetailInfoByUin>>
|
||||
('NodeIKernelProfileService/getUserDetailInfoByUin', 5000, Uin);
|
||||
return await this.core.eventWrapper.callNoListenerEvent(
|
||||
'NodeIKernelProfileService/getUserDetailInfoByUin',
|
||||
Uin
|
||||
);
|
||||
}
|
||||
|
||||
async forceFetchClientKey() {
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { RequestUtil } from '@/common/utils/request';
|
||||
import { RequestUtil } from '@/common/request';
|
||||
import {
|
||||
GroupEssenceMsgRet,
|
||||
InstanceContext,
|
||||
@@ -20,43 +20,48 @@ export class NTQQWebApi {
|
||||
|
||||
async shareDigest(groupCode: string, msgSeq: string, msgRandom: string, targetGroupCode: string) {
|
||||
const cookieObject = await this.core.apis.UserApi.getCookies('qun.qq.com');
|
||||
const url = `https://qun.qq.com/cgi-bin/group_digest/share_digest?${
|
||||
new URLSearchParams({
|
||||
bkn: this.getBknFromCookie(cookieObject),
|
||||
group_code: groupCode,
|
||||
msg_seq: msgSeq,
|
||||
msg_random: msgRandom,
|
||||
target_group_code: targetGroupCode,
|
||||
}).toString()
|
||||
}`;
|
||||
const url = `https://qun.qq.com/cgi-bin/group_digest/share_digest?${new URLSearchParams({
|
||||
bkn: this.getBknFromCookie(cookieObject),
|
||||
group_code: groupCode,
|
||||
msg_seq: msgSeq,
|
||||
msg_random: msgRandom,
|
||||
target_group_code: targetGroupCode,
|
||||
}).toString()}`;
|
||||
try {
|
||||
return RequestUtil.HttpGetText(url, 'GET', '', { 'Cookie': this.cookieToString(cookieObject) });
|
||||
} catch (e) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
async getGroupEssenceMsg(GroupCode: string, page_start: string) {
|
||||
async getGroupEssenceMsgAll(GroupCode: string) {
|
||||
const ret: GroupEssenceMsgRet[] = [];
|
||||
for (let i = 0; i < 20; i++) {
|
||||
const data = await this.getGroupEssenceMsg(GroupCode, i, 50);
|
||||
if (!data) break;
|
||||
ret.push(data);
|
||||
if (data.data.is_end) break;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
async getGroupEssenceMsg(GroupCode: string, page_start: number = 0, page_limit: number = 50) {
|
||||
const cookieObject = await this.core.apis.UserApi.getCookies('qun.qq.com');
|
||||
const url = `https://qun.qq.com/cgi-bin/group_digest/digest_list?${
|
||||
new URLSearchParams({
|
||||
bkn: this.getBknFromCookie(cookieObject),
|
||||
group_code: GroupCode,
|
||||
page_start,
|
||||
page_limit: '20',
|
||||
}).toString()
|
||||
}`;
|
||||
let ret;
|
||||
const url = `https://qun.qq.com/cgi-bin/group_digest/digest_list?${new URLSearchParams({
|
||||
bkn: this.getBknFromCookie(cookieObject),
|
||||
page_start: page_start.toString(),
|
||||
page_limit: page_limit.toString(),
|
||||
group_code: GroupCode,
|
||||
}).toString()}`;
|
||||
try {
|
||||
ret = await RequestUtil.HttpGetJson<GroupEssenceMsgRet>
|
||||
(url, 'GET', '', { 'Cookie': this.cookieToString(cookieObject) });
|
||||
const ret = await RequestUtil.HttpGetJson<GroupEssenceMsgRet>(
|
||||
url,
|
||||
'GET',
|
||||
'',
|
||||
{ 'Cookie': this.cookieToString(cookieObject) }
|
||||
);
|
||||
return ret.retcode === 0 ? ret : undefined;
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
if (ret.retcode !== 0) {
|
||||
return undefined;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
async getGroupMembers(GroupCode: string, cached: boolean = true): Promise<WebApiGroupMember[]> {
|
||||
@@ -64,16 +69,18 @@ export class NTQQWebApi {
|
||||
const memberData: Array<WebApiGroupMember> = new Array<WebApiGroupMember>();
|
||||
const cookieObject = await this.core.apis.UserApi.getCookies('qun.qq.com');
|
||||
const retList: Promise<WebApiGroupMemberRet>[] = [];
|
||||
const fastRet = await RequestUtil.HttpGetJson<WebApiGroupMemberRet>
|
||||
(`https://qun.qq.com/cgi-bin/qun_mgr/search_group_members?${
|
||||
new URLSearchParams({
|
||||
const fastRet = await RequestUtil.HttpGetJson<WebApiGroupMemberRet>(
|
||||
`https://qun.qq.com/cgi-bin/qun_mgr/search_group_members?${new URLSearchParams({
|
||||
st: '0',
|
||||
end: '40',
|
||||
sort: '1',
|
||||
gc: GroupCode,
|
||||
bkn: this.getBknFromCookie(cookieObject),
|
||||
}).toString()
|
||||
}`, 'POST', '', { 'Cookie': this.cookieToString(cookieObject) });
|
||||
}).toString()}`,
|
||||
'POST',
|
||||
'',
|
||||
{ 'Cookie': this.cookieToString(cookieObject) }
|
||||
);
|
||||
if (!fastRet?.count || fastRet?.errcode !== 0 || !fastRet?.mems) {
|
||||
return [];
|
||||
} else {
|
||||
@@ -85,16 +92,18 @@ export class NTQQWebApi {
|
||||
const PageNum = Math.ceil(fastRet.count / 40);
|
||||
//遍历批量请求
|
||||
for (let i = 2; i <= PageNum; i++) {
|
||||
const ret = RequestUtil.HttpGetJson<WebApiGroupMemberRet>
|
||||
(`https://qun.qq.com/cgi-bin/qun_mgr/search_group_members?${
|
||||
new URLSearchParams({
|
||||
const ret = RequestUtil.HttpGetJson<WebApiGroupMemberRet>(
|
||||
`https://qun.qq.com/cgi-bin/qun_mgr/search_group_members?${new URLSearchParams({
|
||||
st: ((i - 1) * 40).toString(),
|
||||
end: (i * 40).toString(),
|
||||
sort: '1',
|
||||
gc: GroupCode,
|
||||
bkn: this.getBknFromCookie(cookieObject),
|
||||
}).toString()
|
||||
}`, 'POST', '', { 'Cookie': this.cookieToString(cookieObject) });
|
||||
}).toString()}`,
|
||||
'POST',
|
||||
'',
|
||||
{ 'Cookie': this.cookieToString(cookieObject) }
|
||||
);
|
||||
retList.push(ret);
|
||||
}
|
||||
//批量等待
|
||||
@@ -122,21 +131,56 @@ export class NTQQWebApi {
|
||||
// return await res.json();
|
||||
// }
|
||||
|
||||
async setGroupNotice(GroupCode: string, Content: string) {
|
||||
async setGroupNotice(
|
||||
GroupCode: string,
|
||||
Content: string,
|
||||
pinned: number = 0,
|
||||
type: number = 1,
|
||||
is_show_edit_card: number = 1,
|
||||
tip_window_type: number = 1,
|
||||
confirm_required: number = 1,
|
||||
picId: string = '',
|
||||
imgWidth: number = 540,
|
||||
imgHeight: number = 300,
|
||||
) {
|
||||
interface SetNoticeRetSuccess {
|
||||
ec: number;
|
||||
em: string;
|
||||
id: number;
|
||||
ltsm: number;
|
||||
new_fid: string;
|
||||
read_only: number;
|
||||
role: number;
|
||||
srv_code: number;
|
||||
}
|
||||
|
||||
const cookieObject = await this.core.apis.UserApi.getCookies('qun.qq.com');
|
||||
let ret: any = undefined;
|
||||
|
||||
try {
|
||||
ret = await RequestUtil.HttpGetJson<any>
|
||||
(`https://web.qun.qq.com/cgi-bin/announce/add_qun_notice${
|
||||
new URLSearchParams({
|
||||
const settings = JSON.stringify({
|
||||
is_show_edit_card: is_show_edit_card,
|
||||
tip_window_type: tip_window_type,
|
||||
confirm_required: confirm_required
|
||||
});
|
||||
const externalParam = {
|
||||
pic: picId,
|
||||
imgWidth: imgWidth.toString(),
|
||||
imgHeight: imgHeight.toString(),
|
||||
};
|
||||
const ret: SetNoticeRetSuccess = await RequestUtil.HttpGetJson<SetNoticeRetSuccess>(
|
||||
`https://web.qun.qq.com/cgi-bin/announce/add_qun_notice?${new URLSearchParams({
|
||||
bkn: this.getBknFromCookie(cookieObject),
|
||||
qid: GroupCode,
|
||||
text: Content,
|
||||
pinned: '0',
|
||||
type: '1',
|
||||
settings: '{"is_show_edit_card":1,"tip_window_type":1,"confirm_required":1}',
|
||||
}).toString()
|
||||
}`, 'GET', '', { 'Cookie': this.cookieToString(cookieObject) });
|
||||
pinned: pinned.toString(),
|
||||
type: type.toString(),
|
||||
settings: settings,
|
||||
...(picId === '' ? {} : externalParam)
|
||||
}).toString()}`,
|
||||
'POST',
|
||||
'',
|
||||
{ 'Cookie': this.cookieToString(cookieObject) }
|
||||
);
|
||||
return ret;
|
||||
} catch (e) {
|
||||
return undefined;
|
||||
@@ -145,21 +189,24 @@ export class NTQQWebApi {
|
||||
|
||||
async getGroupNotice(GroupCode: string): Promise<undefined | WebApiGroupNoticeRet> {
|
||||
const cookieObject = await this.core.apis.UserApi.getCookies('qun.qq.com');
|
||||
let ret: WebApiGroupNoticeRet | undefined = undefined;
|
||||
try {
|
||||
ret = await RequestUtil.HttpGetJson<WebApiGroupNoticeRet>(`https://web.qun.qq.com/cgi-bin/announce/get_t_list?${
|
||||
new URLSearchParams({
|
||||
const ret = await RequestUtil.HttpGetJson<WebApiGroupNoticeRet>(
|
||||
`https://web.qun.qq.com/cgi-bin/announce/get_t_list?${new URLSearchParams({
|
||||
bkn: this.getBknFromCookie(cookieObject),
|
||||
qid: GroupCode,
|
||||
type: '1',
|
||||
start: '0',
|
||||
num: '1',
|
||||
}).toString()
|
||||
}`, 'GET', '', { 'Cookie': this.cookieToString(cookieObject) });
|
||||
if (ret?.ec !== 0) {
|
||||
return undefined;
|
||||
}
|
||||
return ret;
|
||||
ft: '23',
|
||||
ni: '1',
|
||||
n: '1',
|
||||
i: '1',
|
||||
log_read: '1',
|
||||
platform: '1',
|
||||
s: '-1',
|
||||
}).toString()}&n=20`,
|
||||
'GET',
|
||||
'',
|
||||
{ 'Cookie': this.cookieToString(cookieObject) }
|
||||
);
|
||||
return ret?.ec === 0 ? ret : undefined;
|
||||
} catch (e) {
|
||||
return undefined;
|
||||
}
|
||||
@@ -168,16 +215,18 @@ export class NTQQWebApi {
|
||||
async getGroupHonorInfo(groupCode: string, getType: WebHonorType) {
|
||||
const cookieObject = await this.core.apis.UserApi.getCookies('qun.qq.com');
|
||||
const getDataInternal = async (Internal_groupCode: string, Internal_type: number) => {
|
||||
const url = `https://qun.qq.com/interactive/honorlist?${
|
||||
new URLSearchParams({
|
||||
gc: Internal_groupCode,
|
||||
type: Internal_type.toString(),
|
||||
}).toString()
|
||||
}`;
|
||||
let resJson;
|
||||
try {
|
||||
const res = await RequestUtil.HttpGetText(url, 'GET', '', { 'Cookie': this.cookieToString(cookieObject) });
|
||||
const match = res.match(/window\.__INITIAL_STATE__=(.*?);/);
|
||||
const res = await RequestUtil.HttpGetText(
|
||||
`https://qun.qq.com/interactive/honorlist?${new URLSearchParams({
|
||||
gc: Internal_groupCode,
|
||||
type: Internal_type.toString(),
|
||||
}).toString()}`,
|
||||
'GET',
|
||||
'',
|
||||
{ 'Cookie': this.cookieToString(cookieObject) }
|
||||
);
|
||||
const match = /window\.__INITIAL_STATE__=(.*?);/.exec(res);
|
||||
if (match) {
|
||||
resJson = JSON.parse(match[1].trim());
|
||||
}
|
||||
@@ -187,7 +236,7 @@ export class NTQQWebApi {
|
||||
return resJson?.actorList;
|
||||
}
|
||||
} catch (e) {
|
||||
this.context.logger.logDebug('获取当前群荣耀失败', url, e);
|
||||
this.context.logger.logDebug('获取当前群荣耀失败', e);
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
@@ -195,11 +244,8 @@ export class NTQQWebApi {
|
||||
const HonorInfo: any = { group_id: groupCode };
|
||||
|
||||
if (getType === WebHonorType.TALKATIVE || getType === WebHonorType.ALL) {
|
||||
try {
|
||||
const RetInternal = await getDataInternal(groupCode, 1);
|
||||
if (!RetInternal) {
|
||||
throw new Error('获取龙王信息失败');
|
||||
}
|
||||
const RetInternal = await getDataInternal(groupCode, 1);
|
||||
if (RetInternal) {
|
||||
HonorInfo.current_talkative = {
|
||||
user_id: RetInternal[0]?.uin,
|
||||
avatar: RetInternal[0]?.avatar,
|
||||
@@ -217,16 +263,13 @@ export class NTQQWebApi {
|
||||
nickname: talkative_ele?.name,
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
this.context.logger.logDebug(e);
|
||||
} else {
|
||||
this.context.logger.logError('获取龙王信息失败');
|
||||
}
|
||||
}
|
||||
if (getType === WebHonorType.PERFORMER || getType === WebHonorType.ALL) {
|
||||
try {
|
||||
const RetInternal = await getDataInternal(groupCode, 2);
|
||||
if (!RetInternal) {
|
||||
throw new Error('获取群聊之火失败');
|
||||
}
|
||||
const RetInternal = await getDataInternal(groupCode, 2);
|
||||
if (RetInternal) {
|
||||
HonorInfo.performer_list = [];
|
||||
for (const performer_ele of RetInternal) {
|
||||
HonorInfo.performer_list.push({
|
||||
@@ -236,16 +279,13 @@ export class NTQQWebApi {
|
||||
description: performer_ele?.desc,
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
this.context.logger.logDebug(e);
|
||||
} else {
|
||||
this.context.logger.logError('获取群聊之火失败');
|
||||
}
|
||||
}
|
||||
if (getType === WebHonorType.PERFORMER || getType === WebHonorType.ALL) {
|
||||
try {
|
||||
const RetInternal = await getDataInternal(groupCode, 3);
|
||||
if (!RetInternal) {
|
||||
throw new Error('获取群聊炽焰失败');
|
||||
}
|
||||
const RetInternal = await getDataInternal(groupCode, 3);
|
||||
if (RetInternal) {
|
||||
HonorInfo.legend_list = [];
|
||||
for (const legend_ele of RetInternal) {
|
||||
HonorInfo.legend_list.push({
|
||||
@@ -255,16 +295,13 @@ export class NTQQWebApi {
|
||||
desc: legend_ele?.description,
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
this.context.logger.logDebug('获取群聊炽焰失败', e);
|
||||
} else {
|
||||
this.context.logger.logError('获取群聊炽焰失败');
|
||||
}
|
||||
}
|
||||
if (getType === WebHonorType.EMOTION || getType === WebHonorType.ALL) {
|
||||
try {
|
||||
const RetInternal = await getDataInternal(groupCode, 6);
|
||||
if (!RetInternal) {
|
||||
throw new Error('获取快乐源泉失败');
|
||||
}
|
||||
const RetInternal = await getDataInternal(groupCode, 6);
|
||||
if (RetInternal) {
|
||||
HonorInfo.emotion_list = [];
|
||||
for (const emotion_ele of RetInternal) {
|
||||
HonorInfo.emotion_list.push({
|
||||
@@ -274,14 +311,16 @@ export class NTQQWebApi {
|
||||
desc: emotion_ele.description,
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
this.context.logger.logDebug('获取快乐源泉失败', e);
|
||||
} else {
|
||||
this.context.logger.logError('获取快乐源泉失败');
|
||||
}
|
||||
}
|
||||
//冒尖小春笋好像已经被tx扬了
|
||||
|
||||
// 冒尖小春笋好像已经被tx扬了 R.I.P.
|
||||
if (getType === WebHonorType.EMOTION || getType === WebHonorType.ALL) {
|
||||
HonorInfo.strong_newbie_list = [];
|
||||
}
|
||||
|
||||
return HonorInfo;
|
||||
}
|
||||
|
||||
|
204
src/core/core.ts
204
src/core/core.ts
@@ -1,204 +0,0 @@
|
||||
import { NodeQQNTWrapperUtil, NTApiContext, WrapperNodeApi } from '@/core/wrapper';
|
||||
import path from 'node:path';
|
||||
import fs from 'node:fs';
|
||||
import { InstanceContext } from './wrapper';
|
||||
import { proxiedListenerOf } from '@/common/utils/proxy-handler';
|
||||
import { GroupListener, MsgListener, ProfileListener } from './listeners';
|
||||
import { GroupMember, SelfInfo, SelfStatusInfo } from './entities';
|
||||
import { LegacyNTEventWrapper } from '@/common/framework/event-legacy';
|
||||
import { NTQQFileApi, NTQQFriendApi, NTQQGroupApi, NTQQMsgApi, NTQQSystemApi, NTQQUserApi, NTQQWebApi } from './apis';
|
||||
import os from 'node:os';
|
||||
import { NTQQCollectionApi } from './apis/collection';
|
||||
import { NapCatConfigLoader } from './helper/config';
|
||||
import { LogLevel } from '@/common/utils/log';
|
||||
|
||||
export enum NapCatCoreWorkingEnv {
|
||||
Unknown = 0,
|
||||
Shell = 1,
|
||||
Framework = 2,
|
||||
}
|
||||
|
||||
export function loadQQWrapper(QQVersion: string): WrapperNodeApi {
|
||||
let wrapperNodePath = path.resolve(path.dirname(process.execPath), './resources/app/wrapper.node');
|
||||
if (!fs.existsSync(wrapperNodePath)) {
|
||||
wrapperNodePath = path.join(path.dirname(process.execPath), `resources/app/versions/${QQVersion}/wrapper.node`);
|
||||
}
|
||||
const nativemodule: any = { exports: {} };
|
||||
process.dlopen(nativemodule, wrapperNodePath);
|
||||
return nativemodule.exports;
|
||||
}
|
||||
|
||||
export class NapCatCore {
|
||||
readonly context: InstanceContext;
|
||||
readonly apis: NTApiContext;
|
||||
readonly eventWrapper: LegacyNTEventWrapper;
|
||||
// readonly eventChannel: NTEventChannel;
|
||||
NapCatDataPath: string;
|
||||
NapCatTempPath: string;
|
||||
// runtime info, not readonly
|
||||
selfInfo: SelfInfo;
|
||||
util: NodeQQNTWrapperUtil;
|
||||
configLoader: NapCatConfigLoader;
|
||||
|
||||
// 通过构造器递过去的 runtime info 应该尽量少
|
||||
constructor(context: InstanceContext, selfInfo: SelfInfo) {
|
||||
this.selfInfo = selfInfo;
|
||||
this.context = context;
|
||||
this.util = new this.context.wrapper.NodeQQNTWrapperUtil();
|
||||
this.eventWrapper = new LegacyNTEventWrapper(context.wrapper, context.session);
|
||||
this.apis = {
|
||||
FileApi: new NTQQFileApi(this.context, this),
|
||||
SystemApi: new NTQQSystemApi(this.context, this),
|
||||
CollectionApi: new NTQQCollectionApi(this.context, this),
|
||||
WebApi: new NTQQWebApi(this.context, this),
|
||||
FriendApi: new NTQQFriendApi(this.context, this),
|
||||
MsgApi: new NTQQMsgApi(this.context, this),
|
||||
UserApi: new NTQQUserApi(this.context, this),
|
||||
GroupApi: new NTQQGroupApi(this.context, this),
|
||||
};
|
||||
this.configLoader = new NapCatConfigLoader(this, this.context.pathWrapper.configPath);
|
||||
this.NapCatDataPath = path.join(this.dataPath, 'NapCat');
|
||||
fs.mkdirSync(this.NapCatDataPath, { recursive: true });
|
||||
this.NapCatTempPath = path.join(this.NapCatDataPath, 'temp');
|
||||
// 创建临时目录
|
||||
if (!fs.existsSync(this.NapCatTempPath)) {
|
||||
fs.mkdirSync(this.NapCatTempPath, { recursive: true });
|
||||
}
|
||||
this.initNapCatCoreListeners().then().catch(this.context.logger.logError);
|
||||
|
||||
this.context.logger.setFileLogEnabled(
|
||||
this.configLoader.configData.fileLog,
|
||||
);
|
||||
this.context.logger.setConsoleLogEnabled(
|
||||
this.configLoader.configData.consoleLog,
|
||||
);
|
||||
this.context.logger.setFileAndConsoleLogLevel(
|
||||
this.configLoader.configData.fileLogLevel as LogLevel,
|
||||
this.configLoader.configData.consoleLogLevel as LogLevel,
|
||||
);
|
||||
}
|
||||
|
||||
get dataPath(): string {
|
||||
let result = this.util.getNTUserDataInfoConfig();
|
||||
if (!result) {
|
||||
result = path.resolve(os.homedir(), './.config/QQ');
|
||||
fs.mkdirSync(result, { recursive: true });
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Renamed from 'InitDataListener'
|
||||
async initNapCatCoreListeners() {
|
||||
const msgListener = new MsgListener();
|
||||
msgListener.onRecvMsg = (msg) => {
|
||||
//console.log('RecvMsg', msg);
|
||||
};
|
||||
//await sleep(2500);
|
||||
this.context.session.getMsgService().addKernelMsgListener(
|
||||
new this.context.wrapper.NodeIKernelMsgListener(proxiedListenerOf(msgListener, this.context.logger)),
|
||||
);
|
||||
|
||||
const profileListener = new ProfileListener();
|
||||
profileListener.onProfileDetailInfoChanged = (profile) => {
|
||||
if (profile.uid === this.selfInfo.uid) {
|
||||
Object.assign(this.selfInfo, profile);
|
||||
}
|
||||
};
|
||||
profileListener.onSelfStatusChanged = (Info: SelfStatusInfo) => {
|
||||
// if (Info.status == 20) {
|
||||
// log("账号状态变更为离线")
|
||||
// }
|
||||
};
|
||||
this.context.session.getProfileService().addKernelProfileListener(
|
||||
new this.context.wrapper.NodeIKernelProfileListener(proxiedListenerOf(profileListener, this.context.logger)),
|
||||
);
|
||||
|
||||
// 群相关
|
||||
const groupListener = new GroupListener();
|
||||
groupListener.onGroupListUpdate = (updateType, groupList) => {
|
||||
// console.log("onGroupListUpdate", updateType, groupList)
|
||||
groupList.map(g => {
|
||||
const existGroup = this.apis.GroupApi.groupCache.get(g.groupCode);
|
||||
//群成员数量变化 应该刷新缓存
|
||||
if (existGroup && g.memberCount === existGroup.memberCount) {
|
||||
Object.assign(existGroup, g);
|
||||
}
|
||||
else {
|
||||
this.apis.GroupApi.groupCache.set(g.groupCode, g);
|
||||
// 获取群成员
|
||||
}
|
||||
const sceneId = this.context.session.getGroupService().createMemberListScene(g.groupCode, 'groupMemberList_MainWindow');
|
||||
this.context.session.getGroupService().getNextMemberList(sceneId!, undefined, 3000).then(r => {
|
||||
// console.log(`get group ${g.groupCode} members`, r);
|
||||
// r.result.infos.forEach(member => {
|
||||
// });
|
||||
// groupMembers.set(g.groupCode, r.result.infos);
|
||||
});
|
||||
});
|
||||
};
|
||||
groupListener.onMemberListChange = (arg) => {
|
||||
// todo: 应该加一个内部自己维护的成员变动callback,用于判断成员变化通知
|
||||
const groupCode = arg.sceneId.split('_')[0];
|
||||
if (this.apis.GroupApi.groupMemberCache.has(groupCode)) {
|
||||
const existMembers = this.apis.GroupApi.groupMemberCache.get(groupCode)!;
|
||||
arg.infos.forEach((member, uid) => {
|
||||
//console.log('onMemberListChange', member);
|
||||
const existMember = existMembers.get(uid);
|
||||
if (existMember) {
|
||||
Object.assign(existMember, member);
|
||||
}
|
||||
else {
|
||||
existMembers!.set(uid, member);
|
||||
}
|
||||
//移除成员
|
||||
if (member.isDelete) {
|
||||
existMembers.delete(uid);
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
this.apis.GroupApi.groupMemberCache.set(groupCode, arg.infos);
|
||||
}
|
||||
// console.log('onMemberListChange', groupCode, arg);
|
||||
};
|
||||
groupListener.onMemberInfoChange = (groupCode, changeType, members) => {
|
||||
//console.log('onMemberInfoChange', groupCode, changeType, members);
|
||||
if (changeType === 0 && members.get(this.selfInfo.uid)?.isDelete) {
|
||||
// 自身退群或者被踢退群 5s用于Api操作 之后不再出现
|
||||
setTimeout(() => {
|
||||
this.apis.GroupApi.groupCache.delete(groupCode);
|
||||
}, 5000);
|
||||
|
||||
}
|
||||
const existMembers = this.apis.GroupApi.groupMemberCache.get(groupCode);
|
||||
if (existMembers) {
|
||||
members.forEach((member, uid) => {
|
||||
const existMember = existMembers.get(uid);
|
||||
if (existMember) {
|
||||
// 检查管理变动
|
||||
member.isChangeRole = this.checkAdminEvent(groupCode, member, existMember);
|
||||
// 更新成员信息
|
||||
Object.assign(existMember, member);
|
||||
}
|
||||
else {
|
||||
existMembers.set(uid, member);
|
||||
}
|
||||
//移除成员
|
||||
if (member.isDelete) {
|
||||
existMembers.delete(uid);
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
this.apis.GroupApi.groupMemberCache.set(groupCode, members);
|
||||
}
|
||||
};
|
||||
}
|
||||
checkAdminEvent(groupCode: string, memberNew: GroupMember, memberOld: GroupMember | undefined): boolean {
|
||||
if (memberNew.role !== memberOld?.role) {
|
||||
this.context.logger.log(`群 ${groupCode} ${memberNew.nick} 角色变更为 ${memberNew.role === 3 ? '管理员' : '群员'}`);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
11
src/core/entities/adapter.ts
Normal file
11
src/core/entities/adapter.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
export enum MsfStatusType {
|
||||
KUNKNOWN,
|
||||
KDISCONNECTED,
|
||||
KCONNECTED
|
||||
}
|
||||
export enum MsfChangeReasonType {
|
||||
KUNKNOWN,
|
||||
KUSERLOGININ,
|
||||
KUSERLOGINOUT,
|
||||
KAUTO
|
||||
}
|
12
src/core/entities/contact.ts
Normal file
12
src/core/entities/contact.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
|
||||
export interface FSABRecentContactParams {
|
||||
anchorPointContact: {
|
||||
contactId: string;
|
||||
sortField: string;
|
||||
pos: number;
|
||||
};
|
||||
relativeMoveCount: number;
|
||||
listType: number;
|
||||
count: number;
|
||||
fetchOld: boolean;
|
||||
}
|
@@ -1,18 +1,84 @@
|
||||
import { QQLevel, Sex } from './user';
|
||||
|
||||
export interface KickMemberInfo {
|
||||
optFlag: number,
|
||||
optOperate: number,
|
||||
optMemberUid: string,
|
||||
optBytesMsg: string,
|
||||
}
|
||||
//getGroupDetailInfo GroupCode,GroupInfoSource
|
||||
export enum GroupInfoSource {
|
||||
KUNSPECIFIED,
|
||||
KBIGDATACARD,
|
||||
KDATACARD,
|
||||
KNOTICE,
|
||||
KAIO,
|
||||
KRECENTCONTACT,
|
||||
KMOREPANEL
|
||||
}
|
||||
export interface GroupExt0xEF0InfoFilter {
|
||||
bindGuildId: number;
|
||||
blacklistExpireTime: number;
|
||||
companyId: number;
|
||||
essentialMsgPrivilege: number;
|
||||
essentialMsgSwitch: number;
|
||||
fullGroupExpansionSeq: number;
|
||||
fullGroupExpansionSwitch: number;
|
||||
gangUpId: number;
|
||||
groupAioBindGuildId: number;
|
||||
groupBindGuildIds: number;
|
||||
groupBindGuildSwitch: number;
|
||||
groupExcludeGuildIds: number;
|
||||
groupExtFlameData: number;
|
||||
groupFlagPro1: number;
|
||||
groupInfoExtSeq: number;
|
||||
groupOwnerId: number;
|
||||
groupSquareSwitch: number;
|
||||
hasGroupCustomPortrait: number;
|
||||
inviteRobotMemberExamine: number;
|
||||
inviteRobotMemberSwitch: number;
|
||||
inviteRobotSwitch: number;
|
||||
isLimitGroupRtc: number;
|
||||
lightCharNum: number;
|
||||
luckyWord: number;
|
||||
luckyWordId: number;
|
||||
msgEventSeq: number;
|
||||
qqMusicMedalSwitch: number;
|
||||
reserve: number;
|
||||
showPlayTogetherSwitch: number;
|
||||
starId: number;
|
||||
todoSeq: number;
|
||||
viewedMsgDisappearTime: number;
|
||||
}
|
||||
|
||||
export interface KickMemberV2Req {
|
||||
groupCode: string,
|
||||
kickFlag: number,
|
||||
kickList: Array<KickMemberInfo>,
|
||||
kickListUids: Array<string>,
|
||||
kickMsg: string
|
||||
}
|
||||
|
||||
export enum DataSource {
|
||||
LOCAL,
|
||||
REMOTE
|
||||
}
|
||||
|
||||
export enum GroupListUpdateType {
|
||||
REFRESHALL,
|
||||
GETALL,
|
||||
MODIFIED,
|
||||
REMOVE
|
||||
}
|
||||
|
||||
export interface GroupMemberCache {
|
||||
group: {
|
||||
data: GroupMember[];
|
||||
isExpired: boolean;
|
||||
}
|
||||
};
|
||||
isExpired: boolean;
|
||||
}
|
||||
|
||||
export interface Group {
|
||||
groupCode: string,
|
||||
createTime?: string,//高版本才有
|
||||
@@ -65,6 +131,7 @@ export interface GroupMember {
|
||||
uin: string; // QQ号
|
||||
isRobot: boolean;
|
||||
sex?: Sex;
|
||||
age?: number;
|
||||
qqLevel?: QQLevel;
|
||||
isChangeRole: boolean;
|
||||
joinTime: string;
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { GroupMemberRole } from './group';
|
||||
import { GroupMemberRole } from '@/core';
|
||||
|
||||
export interface Peer {
|
||||
chatType: ChatType;
|
||||
@@ -22,40 +22,75 @@ export interface GetFileListParam {
|
||||
startIndex: number;
|
||||
sortOrder: number;
|
||||
showOnlinedocFolder: number;
|
||||
folderId?: string;
|
||||
}
|
||||
|
||||
export enum ElementType {
|
||||
UNKNOWN = 0,
|
||||
|
||||
TEXT = 1,
|
||||
|
||||
PIC = 2,
|
||||
|
||||
FILE = 3,
|
||||
|
||||
PTT = 4,
|
||||
|
||||
VIDEO = 5,
|
||||
|
||||
FACE = 6,
|
||||
|
||||
REPLY = 7,
|
||||
|
||||
WALLET = 9,
|
||||
GreyTip = 8,//Poke别叫戳一搓了 官方名字拍一拍 戳一戳是另一个名字
|
||||
|
||||
/**
|
||||
* “小灰条”,包括拍一拍 (Poke)、撤回提示等
|
||||
*/
|
||||
GreyTip = 8,
|
||||
|
||||
ARK = 10,
|
||||
|
||||
MFACE = 11,
|
||||
|
||||
LIVEGIFT = 12,
|
||||
|
||||
STRUCTLONGMSG = 13,
|
||||
|
||||
MARKDOWN = 14,
|
||||
|
||||
GIPHY = 15,
|
||||
|
||||
MULTIFORWARD = 16,
|
||||
|
||||
INLINEKEYBOARD = 17,
|
||||
|
||||
INTEXTGIFT = 18,
|
||||
|
||||
CALENDAR = 19,
|
||||
|
||||
YOLOGAMERESULT = 20,
|
||||
|
||||
AVRECORD = 21,
|
||||
|
||||
FEED = 22,
|
||||
|
||||
TOFURECORD = 23,
|
||||
|
||||
ACEBUBBLE = 24,
|
||||
|
||||
ACTIVITY = 25,
|
||||
|
||||
TOFU = 26,
|
||||
|
||||
FACEBUBBLE = 27,
|
||||
|
||||
SHARELOCATION = 28,
|
||||
|
||||
TASKTOPMSG = 29,
|
||||
|
||||
RECOMMENDEDMSG = 43,
|
||||
|
||||
ACTIONBAR = 44
|
||||
}
|
||||
|
||||
@@ -111,6 +146,7 @@ export interface TaskTopMsgElement {
|
||||
iconUrl: string;
|
||||
topMsgType: number;
|
||||
}
|
||||
|
||||
export enum NTMsgType {
|
||||
KMSGTYPEARKSTRUCT = 11,
|
||||
KMSGTYPEFACEBUBBLE = 24,
|
||||
@@ -134,6 +170,7 @@ export enum NTMsgType {
|
||||
KMSGTYPEVIDEO = 7,
|
||||
KMSGTYPEWALLET = 10
|
||||
}
|
||||
|
||||
export interface SendTaskTopMsgElement {
|
||||
elementType: ElementType.TASKTOPMSG;
|
||||
elementId: string;
|
||||
@@ -321,6 +358,7 @@ export enum NTMsgAtType {
|
||||
ATTYPESUMMONROLE = 256,
|
||||
ATTYPEUNKNOWN = 0
|
||||
}
|
||||
|
||||
export interface SendPicElement {
|
||||
elementType: ElementType.PIC;
|
||||
elementId: string;
|
||||
@@ -328,10 +366,12 @@ export interface SendPicElement {
|
||||
}
|
||||
|
||||
export interface ReplyElement {
|
||||
sourceMsgIdInRecords?: string;
|
||||
replayMsgSeq: string;
|
||||
replayMsgId: string;
|
||||
senderUin: string;
|
||||
senderUinStr: string;
|
||||
senderUidStr?: string;
|
||||
replyMsgTime?: string;
|
||||
}
|
||||
|
||||
export interface SendReplyElement {
|
||||
@@ -376,7 +416,7 @@ export interface ShareLocationElement {
|
||||
ext: string;
|
||||
}
|
||||
|
||||
export interface sendShareLocationElement {
|
||||
export interface SendShareLocationElement {
|
||||
elementType: ElementType.SHARELOCATION;
|
||||
elementId: string;
|
||||
shareLocationElement?: ShareLocationElement;
|
||||
@@ -426,7 +466,7 @@ export interface SendMarkdownElement {
|
||||
|
||||
export type SendMessageElement = SendTextElement | SendPttElement |
|
||||
SendPicElement | SendReplyElement | SendFaceElement | SendMarketFaceElement | SendFileElement |
|
||||
SendVideoElement | SendArkElement | SendMarkdownElement | sendShareLocationElement;
|
||||
SendVideoElement | SendArkElement | SendMarkdownElement | SendShareLocationElement;
|
||||
|
||||
export interface TextElement {
|
||||
content: string;
|
||||
@@ -442,7 +482,7 @@ export interface MessageElement {
|
||||
extBufForUI: string,//"0x",
|
||||
textElement?: TextElement;
|
||||
faceElement?: FaceElement,
|
||||
marketFaceElement?: MarkdownElement,
|
||||
marketFaceElement?: MarketFaceElement,
|
||||
replyElement?: ReplyElement,
|
||||
picElement?: PicElement,
|
||||
pttElement?: PttElement,
|
||||
@@ -477,16 +517,8 @@ export enum AtType {
|
||||
atUser = 2
|
||||
}
|
||||
|
||||
export enum ChatType {
|
||||
friend = 1,
|
||||
group = 2,
|
||||
chatDevice = 8, //移动设备?
|
||||
temp = 100
|
||||
|
||||
}
|
||||
|
||||
// 来自Android分析
|
||||
export enum ChatType2 {
|
||||
export enum ChatType {
|
||||
KCHATTYPEADELIE = 42,
|
||||
KCHATTYPEBUDDYNOTIFY = 5,
|
||||
KCHATTYPEC2C = 1,
|
||||
@@ -563,6 +595,7 @@ export const IMAGE_HTTP_HOST_NT = 'https://multimedia.nt.qq.com.cn';
|
||||
|
||||
export interface PicElement {
|
||||
md5HexStr?: string;
|
||||
filePath?: string;
|
||||
fileSize: number | string;//number
|
||||
picWidth: number;
|
||||
picHeight: number;
|
||||
@@ -580,13 +613,29 @@ export interface PicElement {
|
||||
originImageUrl?: string; // http url, 没有host,host是https://gchat.qpic.cn/, 带download参数的是https://multimedia.nt.qq.com.cn
|
||||
}
|
||||
|
||||
export enum GrayTipElementSubType {
|
||||
INVITE_NEW_MEMBER = 12,
|
||||
MEMBER_NEW_TITLE = 17
|
||||
export enum NTGrayTipElementSubTypeV2 {
|
||||
GRAYTIP_ELEMENT_SUBTYPE_AIOOP = 15,
|
||||
GRAYTIP_ELEMENT_SUBTYPE_BLOCK = 14,
|
||||
GRAYTIP_ELEMENT_SUBTYPE_BUDDY = 5,
|
||||
GRAYTIP_ELEMENT_SUBTYPE_BUDDYNOTIFY = 9,
|
||||
GRAYTIP_ELEMENT_SUBTYPE_EMOJIREPLY = 3,
|
||||
GRAYTIP_ELEMENT_SUBTYPE_ESSENCE = 7,
|
||||
GRAYTIP_ELEMENT_SUBTYPE_FEED = 6,
|
||||
GRAYTIP_ELEMENT_SUBTYPE_FEEDCHANNELMSG = 11,
|
||||
GRAYTIP_ELEMENT_SUBTYPE_FILE = 10,
|
||||
GRAYTIP_ELEMENT_SUBTYPE_GROUP = 4,
|
||||
GRAYTIP_ELEMENT_SUBTYPE_GROUPNOTIFY = 8,
|
||||
GRAYTIP_ELEMENT_SUBTYPE_JSON = 17,
|
||||
GRAYTIP_ELEMENT_SUBTYPE_LOCALMSG = 13,
|
||||
GRAYTIP_ELEMENT_SUBTYPE_PROCLAMATION = 2,
|
||||
GRAYTIP_ELEMENT_SUBTYPE_REVOKE = 1,
|
||||
GRAYTIP_ELEMENT_SUBTYPE_UNKNOWN = 0,
|
||||
GRAYTIP_ELEMENT_SUBTYPE_WALLET = 16,
|
||||
GRAYTIP_ELEMENT_SUBTYPE_XMLMSG = 12,
|
||||
}
|
||||
|
||||
export interface GrayTipElement {
|
||||
subElementType: GrayTipElementSubType;
|
||||
subElementType: NTGrayTipElementSubTypeV2;
|
||||
revokeElement: {
|
||||
operatorRole: string;
|
||||
operatorUid: string;
|
||||
@@ -807,93 +856,121 @@ export interface MultiForwardMsgElement {
|
||||
resId: string;
|
||||
fileName: string;
|
||||
}
|
||||
export enum NTSubMsgType {
|
||||
KMSGSUBTYPEARKGROUPANNOUNCE = 3,
|
||||
KMSGSUBTYPEARKGROUPANNOUNCECONFIRMREQUIRED = 4,
|
||||
KMSGSUBTYPEARKGROUPGIFTATME = 5,
|
||||
KMSGSUBTYPEARKGROUPTASKATALL = 6,
|
||||
KMSGSUBTYPEARKMULTIMSG = 7,
|
||||
KMSGSUBTYPEARKNORMAL = 0,
|
||||
KMSGSUBTYPEARKTENCENTDOCFROMMINIAPP = 1,
|
||||
KMSGSUBTYPEARKTENCENTDOCFROMPLUSPANEL = 2,
|
||||
KMSGSUBTYPEEMOTICON = 15,
|
||||
KMSGSUBTYPEFILEAPP = 11,
|
||||
KMSGSUBTYPEFILEAUDIO = 3,
|
||||
KMSGSUBTYPEFILEDOC = 4,
|
||||
KMSGSUBTYPEFILEEXCEL = 6,
|
||||
KMSGSUBTYPEFILEFOLDER = 13,
|
||||
KMSGSUBTYPEFILEHTML = 10,
|
||||
KMSGSUBTYPEFILEIPA = 14,
|
||||
KMSGSUBTYPEFILENORMAL = 0,
|
||||
KMSGSUBTYPEFILEPDF = 7,
|
||||
KMSGSUBTYPEFILEPIC = 1,
|
||||
KMSGSUBTYPEFILEPPT = 5,
|
||||
KMSGSUBTYPEFILEPSD = 12,
|
||||
KMSGSUBTYPEFILETXT = 8,
|
||||
KMSGSUBTYPEFILEVIDEO = 2,
|
||||
KMSGSUBTYPEFILEZIP = 9,
|
||||
KMSGSUBTYPELINK = 5,
|
||||
KMSGSUBTYPEMARKETFACE = 1,
|
||||
KMSGSUBTYPEMIXEMOTICON = 7,
|
||||
KMSGSUBTYPEMIXFACE = 3,
|
||||
KMSGSUBTYPEMIXMARKETFACE = 2,
|
||||
KMSGSUBTYPEMIXPIC = 1,
|
||||
KMSGSUBTYPEMIXREPLY = 4,
|
||||
KMSGSUBTYPEMIXTEXT = 0,
|
||||
KMSGSUBTYPETENCENTDOC = 6
|
||||
|
||||
export enum SendStatusType {
|
||||
KSEND_STATUS_FAILED = 0,
|
||||
KSEND_STATUS_SENDING = 1,
|
||||
KSEND_STATUS_SUCCESS = 2,
|
||||
KSEND_STATUS_SUCCESS_NOSEQ = 3
|
||||
}
|
||||
|
||||
export interface RawMessage {
|
||||
parentMsgPeer: Peer;
|
||||
parentMsgIdList: string[];
|
||||
id?: number;//扩展字段 用于处理OB11 ID
|
||||
guildId: string;
|
||||
msgRandom: string;
|
||||
|
||||
parentMsgIdList: string[];
|
||||
|
||||
/**
|
||||
* 扩展字段,与 Ob11 msg ID 有关
|
||||
*/
|
||||
id?: number;
|
||||
|
||||
guildId: string;
|
||||
|
||||
msgRandom: string;
|
||||
|
||||
msgId: string;
|
||||
|
||||
// 时间戳,秒
|
||||
/**
|
||||
* 消息时间戳(秒)
|
||||
*/
|
||||
msgTime: string;
|
||||
|
||||
msgSeq: string;
|
||||
|
||||
msgType: NTMsgType;
|
||||
subMsgType: NTSubMsgType;
|
||||
|
||||
subMsgType: number;
|
||||
|
||||
senderUid: string;
|
||||
senderUin: string; // 发送者QQ号
|
||||
peerUid: string; // 群号 或者 QQ uid
|
||||
peerUin: string; // 群号 或者 发送者QQ号
|
||||
|
||||
/**
|
||||
* 发送者 QQ 号
|
||||
*/
|
||||
senderUin: string;
|
||||
|
||||
/**
|
||||
* 群号 / 用户 UID
|
||||
*/
|
||||
peerUid: string;
|
||||
|
||||
/**
|
||||
* 群号 / 用户 QQ 号
|
||||
*/
|
||||
peerUin: string;
|
||||
|
||||
/**
|
||||
* 发送者昵称(如果是好友消息)
|
||||
*/
|
||||
sendNickName: string;
|
||||
sendMemberName?: string; // 发送者群名片
|
||||
|
||||
/**
|
||||
* 发送者群名片(如果是群消息)
|
||||
*/
|
||||
sendMemberName?: string;
|
||||
|
||||
chatType: ChatType;
|
||||
sendStatus?: number; // 消息状态,别人发的2是已撤回,自己发的2是已发送
|
||||
recallTime: string; // 撤回时间, "0"是没有撤回
|
||||
|
||||
/**
|
||||
* 消息状态,别人发的 2 是已撤回,自己发的 2 是已发送
|
||||
*/
|
||||
sendStatus?: SendStatusType;
|
||||
|
||||
/**
|
||||
* 撤回时间,"0" 是没有撤回
|
||||
*/
|
||||
recallTime: string;
|
||||
|
||||
records: RawMessage[];
|
||||
elements: {
|
||||
elementId: string;
|
||||
elementType: ElementType;
|
||||
replyElement: {
|
||||
sourceMsgIdInRecords: string;
|
||||
senderUid: string; // 原消息发送者QQ号
|
||||
sourceMsgIsIncPic: boolean; // 原消息是否有图片
|
||||
sourceMsgText: string;
|
||||
replayMsgSeq: string; // 源消息的msgSeq,可以通过这个找到源消息的msgId
|
||||
};
|
||||
textElement: {
|
||||
atType: AtType;
|
||||
atUid: string; // QQ号
|
||||
content: string;
|
||||
atNtUid: string; // uid号
|
||||
};
|
||||
picElement: PicElement;
|
||||
pttElement: PttElement;
|
||||
arkElement: ArkElement;
|
||||
grayTipElement: GrayTipElement;
|
||||
faceElement: FaceElement;
|
||||
videoElement: VideoElement;
|
||||
fileElement: FileElement;
|
||||
marketFaceElement: MarketFaceElement;
|
||||
inlineKeyboardElement: InlineKeyboardElement;
|
||||
markdownElement: MarkdownElement;
|
||||
multiForwardMsgElement: MultiForwardMsgElement;
|
||||
}[];
|
||||
|
||||
elements: MessageElement[];
|
||||
}
|
||||
export interface QueryMsgsParams {
|
||||
chatInfo: Peer;
|
||||
filterMsgType: [];
|
||||
filterSendersUid: string[];
|
||||
filterMsgFromTime: string;
|
||||
filterMsgToTime: string;
|
||||
pageLimit: number;
|
||||
isReverseOrder: boolean;
|
||||
isIncludeCurrent: boolean;
|
||||
}
|
||||
|
||||
export interface TmpChatInfoApi {
|
||||
errMsg: string;
|
||||
result: number;
|
||||
tmpChatInfo?: TmpChatInfo;
|
||||
}
|
||||
|
||||
export interface TmpChatInfo {
|
||||
chatType: number;
|
||||
fromNick: string;
|
||||
groupCode: string;
|
||||
peerUid: string;
|
||||
sessionType: number;
|
||||
sig: string;
|
||||
}
|
||||
export interface MsgReqType {
|
||||
peer: Peer,
|
||||
byType: number,
|
||||
msgId: string,
|
||||
msgSeq: string,
|
||||
msgTime: string,
|
||||
clientSeq: string,
|
||||
cnt: number,
|
||||
queryOrder: boolean,
|
||||
includeSelf: boolean,
|
||||
includeDeleteMsg: boolean,
|
||||
extraCnt: number
|
||||
}
|
||||
//getMsgsIncludeSelf Peer必须 byType 1
|
||||
//getMsgsWithMsgTimeAndClientSeqForC2C Peer必须 byType 3
|
@@ -1,12 +1,20 @@
|
||||
export enum GroupNotifyTypes {
|
||||
INVITE_ME = 1,
|
||||
INVITED_JOIN = 4, // 有人接受了邀请入群
|
||||
JOIN_REQUEST = 7,
|
||||
ADMIN_SET = 8,
|
||||
KICK_MEMBER = 9,
|
||||
MEMBER_EXIT = 11, // 主动退出
|
||||
ADMIN_UNSET = 12,
|
||||
ADMIN_UNSET_OTHER = 13, // 其他人取消管理员
|
||||
export enum GroupNotifyMsgType {
|
||||
UN_SPECIFIED,
|
||||
INVITED_BY_MEMBER,
|
||||
REFUSE_INVITED,
|
||||
REFUSED_BY_ADMINI_STRATOR,
|
||||
AGREED_TOJOIN_DIRECT,// 有人接受了邀请入群
|
||||
INVITED_NEED_ADMINI_STRATOR_PASS,
|
||||
AGREED_TO_JOIN_BY_ADMINI_STRATOR,
|
||||
REQUEST_JOIN_NEED_ADMINI_STRATOR_PASS,
|
||||
SET_ADMIN,
|
||||
KICK_MEMBER_NOTIFY_ADMIN,
|
||||
KICK_MEMBER_NOTIFY_KICKED,
|
||||
MEMBER_LEAVE_NOTIFY_ADMIN,// 主动退出
|
||||
CANCEL_ADMIN_NOTIFY_CANCELED,
|
||||
CANCEL_ADMIN_NOTIFY_ADMIN,// 其他人取消管理员
|
||||
TRANSFER_GROUP_NOTIFY_OLDOWNER,
|
||||
TRANSFER_GROUP_NOTIFY_ADMIN
|
||||
}
|
||||
|
||||
export interface GroupNotifies {
|
||||
@@ -15,26 +23,40 @@ export interface GroupNotifies {
|
||||
notifies: GroupNotify[];
|
||||
}
|
||||
|
||||
export enum GroupNotifyStatus {
|
||||
IGNORE = 0,
|
||||
WAIT_HANDLE = 1,
|
||||
APPROVE = 2,
|
||||
REJECT = 3
|
||||
export enum GroupNotifyMsgStatus {
|
||||
KINIT,//初始化
|
||||
KUNHANDLE,//未处理
|
||||
KAGREED,//同意
|
||||
KREFUSED,//拒绝
|
||||
KIGNORED//忽略
|
||||
}
|
||||
|
||||
export enum GroupInviteStatus {
|
||||
INIT,
|
||||
WAIT_TO_APPROVE,
|
||||
JOINED,
|
||||
REFUSED_BY_ADMINI_STRATOR
|
||||
}
|
||||
|
||||
export enum GroupInviteType {
|
||||
BYBUDDY,
|
||||
BYGROUPMEMBER,
|
||||
BYDISCUSSMEMBER
|
||||
}
|
||||
|
||||
export interface GroupNotify {
|
||||
time: number; // 自己添加的字段,时间戳,毫秒, 用于判断收到短时间内收到重复的notify
|
||||
seq: string; // 唯一标识符,转成数字再除以1000应该就是时间戳?
|
||||
type: GroupNotifyTypes;
|
||||
status: GroupNotifyStatus; // 0是已忽略?,1是未处理,2是已同意
|
||||
seq: string; // 通知序列号
|
||||
type: GroupNotifyMsgType;
|
||||
status: GroupNotifyMsgStatus;
|
||||
group: { groupCode: string; groupName: string };
|
||||
user1: { uid: string; nickName: string }; // 被设置管理员的人
|
||||
user2: { uid: string; nickName: string }; // 操作者
|
||||
actionUser: { uid: string; nickName: string }; //未知
|
||||
actionTime: string;
|
||||
invitationExt: {
|
||||
srcType: number; // 0?未知
|
||||
groupCode: string; waitStatus: number
|
||||
srcType: GroupInviteType; // 邀请来源
|
||||
groupCode: string;
|
||||
waitStatus: GroupInviteStatus
|
||||
};
|
||||
postscript: string; // 加群用户填写的验证信息
|
||||
repeatSeqs: [];
|
||||
@@ -64,6 +86,7 @@ export enum BuddyReqType {
|
||||
}
|
||||
|
||||
export interface FriendRequest {
|
||||
isBuddy?: boolean;
|
||||
isInitiator?: boolean;
|
||||
isDecide: boolean;
|
||||
friendUid: string;
|
||||
|
@@ -153,7 +153,10 @@ interface CommonExt {
|
||||
labels: any[];
|
||||
qqLevel: QQLevel;
|
||||
}
|
||||
|
||||
export enum BuddyListReqType {
|
||||
KNOMAL,
|
||||
KLETTER
|
||||
}
|
||||
interface Pic {
|
||||
picId: string;
|
||||
picTime: number;
|
||||
@@ -176,10 +179,7 @@ export interface SimpleInfo {
|
||||
intimate: any | null;
|
||||
}
|
||||
|
||||
export interface FriendV2 extends SimpleInfo {
|
||||
categoryId?: number;
|
||||
categroyName?: string;
|
||||
}
|
||||
export type FriendV2 = SimpleInfo;
|
||||
|
||||
export interface SelfStatusInfo {
|
||||
uid: string;
|
||||
@@ -216,7 +216,7 @@ export interface BuddyProfileLikeReq {
|
||||
userProfile: number;
|
||||
type: number;
|
||||
start: number;
|
||||
limit: number;
|
||||
limit?: number;
|
||||
}
|
||||
|
||||
export interface QQLevel {
|
||||
@@ -234,6 +234,7 @@ export interface User {
|
||||
longNick?: string; // 签名
|
||||
remark?: string;
|
||||
sex?: Sex;
|
||||
age?: number;
|
||||
qqLevel?: QQLevel;
|
||||
qid?: string;
|
||||
birthday_year?: number;
|
||||
@@ -296,7 +297,7 @@ export enum BizKey {
|
||||
KPHOTOWALL
|
||||
}
|
||||
|
||||
export interface UserDetailInfoByUinV2 {
|
||||
export interface UserDetailInfoByUin {
|
||||
result: number,
|
||||
errMsg: string,
|
||||
detail: {
|
||||
@@ -307,62 +308,15 @@ export interface UserDetailInfoByUinV2 {
|
||||
photoWall: null
|
||||
}
|
||||
}
|
||||
|
||||
export interface UserDetailInfoByUin {
|
||||
result: number,
|
||||
errMsg: string,
|
||||
info: {
|
||||
uid: string,//这个没办法用
|
||||
qid: string,
|
||||
uin: string,
|
||||
nick: string,
|
||||
remark: string,
|
||||
longNick: string,
|
||||
avatarUrl: string,
|
||||
birthday_year: number,
|
||||
birthday_month: number,
|
||||
birthday_day: number,
|
||||
sex: number,//0
|
||||
topTime: string,
|
||||
constellation: number,
|
||||
shengXiao: number,
|
||||
kBloodType: number,
|
||||
homeTown: string,
|
||||
makeFriendCareer: number,
|
||||
pos: string,
|
||||
eMail: string,
|
||||
phoneNum: string,
|
||||
college: string,
|
||||
country: string,
|
||||
province: string,
|
||||
city: string,
|
||||
postCode: string,
|
||||
address: string,
|
||||
isBlock: boolean,
|
||||
isSpecialCareOpen: boolean,
|
||||
isSpecialCareZone: boolean,
|
||||
ringId: string,
|
||||
regTime: number,
|
||||
interest: string,
|
||||
termType: number,
|
||||
labels: any[],
|
||||
qqLevel: { crownNum: number, sunNum: number, moonNum: number, starNum: number },
|
||||
isHideQQLevel: number,
|
||||
privilegeIcon: { jumpUrl: string, openIconList: any[], closeIconList: any[] },
|
||||
isHidePrivilegeIcon: number,
|
||||
photoWall: { picList: any[] },
|
||||
vipFlag: boolean,
|
||||
yearVipFlag: boolean,
|
||||
svipFlag: boolean,
|
||||
vipLevel: number,
|
||||
status: number,
|
||||
qidianMasterFlag: number,
|
||||
qidianCrewFlag: number,
|
||||
qidianCrewFlag2: number,
|
||||
extStatus: number,
|
||||
recommendImgFlag: number,
|
||||
disableEmojiShortCuts: number,
|
||||
pendantId: string,
|
||||
vipNameColorId: string
|
||||
}
|
||||
export enum UserDetailSource {
|
||||
KDB,
|
||||
KSERVER
|
||||
}
|
||||
|
||||
export enum ProfileBizType {
|
||||
KALL,
|
||||
KBASEEXTEND,
|
||||
KVAS,
|
||||
KQZONE,
|
||||
KOTHER
|
||||
}
|
@@ -57,7 +57,7 @@ export interface WebApiGroupMemberRet {
|
||||
|
||||
export interface WebApiGroupNoticeFeed {
|
||||
u: number;//发送者
|
||||
fid: string;//fid
|
||||
fid: string;//fid,notice_id
|
||||
pubt: number;//时间
|
||||
msg: {
|
||||
text: string
|
||||
|
62
src/core/external/appid.json
vendored
62
src/core/external/appid.json
vendored
@@ -1,58 +1,10 @@
|
||||
{
|
||||
"3.1.2-13107": {
|
||||
"appid": 537146866,
|
||||
"qua": "V1_LNX_NQ_3.1.2-13107_RDM_B"
|
||||
"9.9.15-28060":{
|
||||
"appid": 537246092,
|
||||
"qua": "V1_WIN_NQ_9.9.15_28060_GW_B"
|
||||
},
|
||||
"3.2.10-25765": {
|
||||
"appid": 537234773,
|
||||
"qua": "V1_LNX_NQ_3.2.10_25765_GW_B"
|
||||
},
|
||||
"3.2.12-26702": {
|
||||
"appid": 537237950,
|
||||
"qua": "V1_LNX_NQ_3.2.12_26702_GW_B"
|
||||
},
|
||||
"3.2.12-26740": {
|
||||
"appid": 537237950,
|
||||
"qua": "V1_WIN_NQ_9.9.15_26740_GW_B"
|
||||
},
|
||||
"3.2.12-26909": {
|
||||
"appid": 537237923,
|
||||
"qua": "V1_LNX_NQ_3.2.12_26909_GW_B"
|
||||
},
|
||||
"9.9.11-24815": {
|
||||
"appid": 537226656,
|
||||
"qua": "V1_WIN_NQ_9.9.11_24815_GW_B"
|
||||
},
|
||||
"9.9.12-25493": {
|
||||
"appid": 537231759,
|
||||
"qua": "V1_WIN_NQ_9.9.12_25493_GW_B"
|
||||
},
|
||||
"9.9.12-25765": {
|
||||
"appid": 537234702,
|
||||
"qua": "V1_WIN_NQ_9.9.12_25765_GW_B"
|
||||
},
|
||||
"9.9.12-26299": {
|
||||
"appid": 537234826,
|
||||
"qua": "V1_WIN_NQ_9.9.12_26299_GW_B"
|
||||
},
|
||||
"9.9.12-26339": {
|
||||
"appid": 537234826,
|
||||
"qua": "V1_WIN_NQ_9.9.12_26339_GW_B"
|
||||
},
|
||||
"9.9.12-26466": {
|
||||
"appid": 537234826,
|
||||
"qua": "V1_WIN_NQ_9.9.12_26466_GW_B"
|
||||
},
|
||||
"9.9.15-26702": {
|
||||
"appid": 537237765,
|
||||
"qua": "V1_WIN_NQ_9.9.15_26702_GW_B"
|
||||
},
|
||||
"9.9.15-26740": {
|
||||
"appid": 537237765,
|
||||
"qua": "V1_WIN_NQ_9.9.15_26702_GW_B"
|
||||
},
|
||||
"9.9.15-26909": {
|
||||
"appid": 537237802,
|
||||
"qua": "V1_WIN_NQ_9.9.15_26909_GW_B"
|
||||
"3.2.12-28060":{
|
||||
"appid": 537246140,
|
||||
"qua": "V1_LNX_NQ_3.2.12_28060_GW_B"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
31
src/core/external/proto/EmojiLikeToOthers.proto
vendored
Normal file
31
src/core/external/proto/EmojiLikeToOthers.proto
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
syntax = 'proto3';
|
||||
package SysMessage;
|
||||
|
||||
message EmojiLikeToOthersWrapper1 {
|
||||
EmojiLikeToOthersWrapper2 wrapper = 1;
|
||||
}
|
||||
|
||||
message EmojiLikeToOthersWrapper2 {
|
||||
EmojiLikeToOthersWrapper3 body = 1;
|
||||
}
|
||||
|
||||
message EmojiLikeToOthersWrapper3 {
|
||||
EmojiLikeToOthersMsgSpec msgSpec = 2;
|
||||
EmojiLikeToOthersAttributes attributes = 3;
|
||||
}
|
||||
|
||||
message EmojiLikeToOthersMsgSpec {
|
||||
uint32 msgSeq = 1;
|
||||
}
|
||||
|
||||
message EmojiLikeToOthersAttributes {
|
||||
enum Operation {
|
||||
FALLBACK = 0;
|
||||
LIKE = 1;
|
||||
UNLIKE = 2;
|
||||
}
|
||||
|
||||
string emojiId = 1;
|
||||
string senderUid = 4;
|
||||
Operation operation = 5;
|
||||
}
|
9
src/core/external/proto/GreyTipWrapper.proto
vendored
Normal file
9
src/core/external/proto/GreyTipWrapper.proto
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
syntax = 'proto3';
|
||||
package SysMessage;
|
||||
|
||||
message GreyTipWrapper {
|
||||
uint32 subTypeId = 1;
|
||||
uint32 groupCode = 4;
|
||||
uint32 subTypeIdMinusOne = 13;
|
||||
bytes rest = 44;
|
||||
}
|
18
src/core/external/proto/ProfileLikeTip.proto
vendored
Normal file
18
src/core/external/proto/ProfileLikeTip.proto
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
syntax = "proto3";
|
||||
package SysMessage;
|
||||
|
||||
message likeDetail {
|
||||
string txt = 1;
|
||||
int64 uin = 3;
|
||||
string nickname = 5;
|
||||
}
|
||||
|
||||
message likeMsg {
|
||||
int32 times = 1;
|
||||
int32 time = 2;
|
||||
likeDetail detail = 3;
|
||||
}
|
||||
|
||||
message profileLikeTip {
|
||||
likeMsg msg = 14;
|
||||
}
|
36
src/core/external/proto/SysMessage.proto
vendored
Normal file
36
src/core/external/proto/SysMessage.proto
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
syntax = 'proto3';
|
||||
package SysMessage;
|
||||
|
||||
message SysMessage {
|
||||
repeated SysMessageHeader header = 1;
|
||||
repeated SysMessageMsgSpec msgSpec = 2;
|
||||
SysMessageBodyWrapper bodyWrapper = 3;
|
||||
}
|
||||
|
||||
message SysMessageHeader {
|
||||
uint32 PeerNumber = 1;
|
||||
string PeerString = 2;
|
||||
uint32 Uin = 5;
|
||||
optional string Uid = 6;
|
||||
}
|
||||
|
||||
message SysMessageMsgSpec {
|
||||
uint32 msgType = 1;
|
||||
uint32 subType = 2;
|
||||
uint32 subSubType = 3;
|
||||
uint32 msgSeq = 5;
|
||||
uint32 time = 6;
|
||||
uint64 msgId = 12;
|
||||
uint32 other = 13;
|
||||
}
|
||||
|
||||
message SysMessageBodyWrapper {
|
||||
bytes wrappedBody = 2;
|
||||
// Find the first [08], or ignore the first 7 bytes?
|
||||
// And it becomes another ProtoBuf message.
|
||||
}
|
||||
|
||||
message KeyValuePair {
|
||||
string key = 1;
|
||||
string value = 2;
|
||||
}
|
@@ -1,13 +1,11 @@
|
||||
import { ConfigBase } from "@/common/utils/ConfigBase";
|
||||
import { ConfigBase } from '@/common/config-base';
|
||||
import napCatDefaultConfig from '@/core/external/napcat.json';
|
||||
import { NapCatCore } from '@/core';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging
|
||||
export type NapCatConfig = typeof napCatDefaultConfig;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging
|
||||
export class NapCatConfigLoader extends ConfigBase<NapCatConfig> {
|
||||
constructor(coreContext: NapCatCore, configPath: string) {
|
||||
super('napcat', coreContext, configPath);
|
||||
constructor(core: NapCatCore, configPath: string) {
|
||||
super('napcat', core, configPath);
|
||||
}
|
||||
}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { LogWrapper } from '@/common/utils/log';
|
||||
import { RequestUtil } from '@/common/utils/request';
|
||||
import { LogWrapper } from '@/common/log';
|
||||
import { RequestUtil } from '@/common/request';
|
||||
|
||||
interface ServerRkeyData {
|
||||
group_rkey: string;
|
||||
@@ -8,7 +8,7 @@ interface ServerRkeyData {
|
||||
}
|
||||
|
||||
export class RkeyManager {
|
||||
serverUrl: string = '';
|
||||
serverUrl: string[] = [];
|
||||
logger: LogWrapper;
|
||||
private rkeyData: ServerRkeyData = {
|
||||
group_rkey: '',
|
||||
@@ -16,7 +16,7 @@ export class RkeyManager {
|
||||
expired_time: 0,
|
||||
};
|
||||
|
||||
constructor(serverUrl: string, logger: LogWrapper) {
|
||||
constructor(serverUrl: string[], logger: LogWrapper) {
|
||||
this.logger = logger;
|
||||
this.serverUrl = serverUrl;
|
||||
}
|
||||
@@ -40,6 +40,13 @@ export class RkeyManager {
|
||||
|
||||
async refreshRkey(): Promise<any> {
|
||||
//刷新rkey
|
||||
this.rkeyData = await RequestUtil.HttpGetJson<ServerRkeyData>(this.serverUrl, 'GET');
|
||||
for (const url of this.serverUrl) {
|
||||
try {
|
||||
this.rkeyData = await RequestUtil.HttpGetJson<ServerRkeyData>(url, 'GET');
|
||||
} catch (e) {
|
||||
this.logger.logError(`[Rkey] Get Rkey ${url} Error `, e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@@ -1,5 +1,332 @@
|
||||
export * from './core';
|
||||
import {
|
||||
NTQQFileApi,
|
||||
NTQQFriendApi,
|
||||
NTQQGroupApi,
|
||||
NTQQMsgApi,
|
||||
NTQQSystemApi,
|
||||
NTQQUserApi,
|
||||
NTQQWebApi,
|
||||
} from '@/core/apis';
|
||||
import { NTQQCollectionApi } from '@/core/apis/collection';
|
||||
import {
|
||||
NodeIQQNTWrapperSession,
|
||||
NodeQQNTWrapperUtil,
|
||||
PlatformType,
|
||||
VendorType,
|
||||
WrapperNodeApi,
|
||||
WrapperSessionInitConfig,
|
||||
} from '@/core/wrapper';
|
||||
import { LogLevel, LogWrapper } from '@/common/log';
|
||||
import { NodeIKernelLoginService } from '@/core/services';
|
||||
import { QQBasicInfoWrapper } from '@/common/qq-basic-info';
|
||||
import { NapCatPathWrapper } from '@/common/path';
|
||||
import path from 'node:path';
|
||||
import fs from 'node:fs';
|
||||
import { getMachineId, hostname, systemName, systemVersion } from '@/common/system';
|
||||
import { NTEventWrapper } from '@/common/event';
|
||||
import { DataSource, GroupMember, KickedOffLineInfo, SelfInfo, SelfStatusInfo } from '@/core/entities';
|
||||
import { NapCatConfigLoader } from '@/core/helper/config';
|
||||
import os from 'node:os';
|
||||
import { NodeIKernelGroupListener, NodeIKernelMsgListener, NodeIKernelProfileListener } from '@/core/listeners';
|
||||
import { proxiedListenerOf } from '@/common/proxy-handler';
|
||||
|
||||
export * from './wrapper';
|
||||
export * from './entities';
|
||||
export * from './services';
|
||||
export * from './listeners';
|
||||
|
||||
export enum NapCatCoreWorkingEnv {
|
||||
Unknown = 0,
|
||||
Shell = 1,
|
||||
Framework = 2,
|
||||
}
|
||||
|
||||
export function loadQQWrapper(QQVersion: string): WrapperNodeApi {
|
||||
let appPath;
|
||||
if (os.platform() === 'darwin') {
|
||||
appPath = path.resolve(path.dirname(process.execPath), '../Resources/app');
|
||||
} else if (os.platform() === 'linux') {
|
||||
appPath = path.resolve(path.dirname(process.execPath), './resources/app');
|
||||
} else {
|
||||
appPath = path.resolve(path.dirname(process.execPath), `./versions/${QQVersion}/`);
|
||||
}
|
||||
let wrapperNodePath = path.resolve(appPath, 'wrapper.node');
|
||||
if (!fs.existsSync(wrapperNodePath)) {
|
||||
wrapperNodePath = path.join(appPath, `./resources/app/wrapper.node`);
|
||||
}
|
||||
//老版本兼容 未来去掉
|
||||
if (!fs.existsSync(wrapperNodePath)) {
|
||||
wrapperNodePath = path.join(path.dirname(process.execPath), `./resources/app/versions/${QQVersion}/wrapper.node`);
|
||||
}
|
||||
const nativemodule: any = { exports: {} };
|
||||
process.dlopen(nativemodule, wrapperNodePath);
|
||||
return nativemodule.exports;
|
||||
}
|
||||
|
||||
export class NapCatCore {
|
||||
readonly context: InstanceContext;
|
||||
readonly apis: StableNTApiWrapper;
|
||||
readonly eventWrapper: NTEventWrapper;
|
||||
// readonly eventChannel: NTEventChannel;
|
||||
NapCatDataPath: string;
|
||||
NapCatTempPath: string;
|
||||
// runtime info, not readonly
|
||||
selfInfo: SelfInfo;
|
||||
util: NodeQQNTWrapperUtil;
|
||||
configLoader: NapCatConfigLoader;
|
||||
|
||||
// 通过构造器递过去的 runtime info 应该尽量少
|
||||
constructor(context: InstanceContext, selfInfo: SelfInfo) {
|
||||
this.selfInfo = selfInfo;
|
||||
this.context = context;
|
||||
this.util = this.context.wrapper.NodeQQNTWrapperUtil;
|
||||
this.eventWrapper = new NTEventWrapper(context.session);
|
||||
this.apis = {
|
||||
FileApi: new NTQQFileApi(this.context, this),
|
||||
SystemApi: new NTQQSystemApi(this.context, this),
|
||||
CollectionApi: new NTQQCollectionApi(this.context, this),
|
||||
WebApi: new NTQQWebApi(this.context, this),
|
||||
FriendApi: new NTQQFriendApi(this.context, this),
|
||||
MsgApi: new NTQQMsgApi(this.context, this),
|
||||
UserApi: new NTQQUserApi(this.context, this),
|
||||
GroupApi: new NTQQGroupApi(this.context, this),
|
||||
};
|
||||
this.configLoader = new NapCatConfigLoader(this, this.context.pathWrapper.configPath);
|
||||
this.NapCatDataPath = path.join(this.dataPath, 'NapCat');
|
||||
fs.mkdirSync(this.NapCatDataPath, { recursive: true });
|
||||
this.NapCatTempPath = path.join(this.NapCatDataPath, 'temp');
|
||||
// 创建临时目录
|
||||
if (!fs.existsSync(this.NapCatTempPath)) {
|
||||
fs.mkdirSync(this.NapCatTempPath, { recursive: true });
|
||||
}
|
||||
this.initNapCatCoreListeners().then().catch(this.context.logger.logError);
|
||||
|
||||
this.context.logger.setFileLogEnabled(
|
||||
this.configLoader.configData.fileLog,
|
||||
);
|
||||
this.context.logger.setConsoleLogEnabled(
|
||||
this.configLoader.configData.consoleLog,
|
||||
);
|
||||
this.context.logger.setFileAndConsoleLogLevel(
|
||||
this.configLoader.configData.fileLogLevel as LogLevel,
|
||||
this.configLoader.configData.consoleLogLevel as LogLevel,
|
||||
);
|
||||
}
|
||||
|
||||
get dataPath(): string {
|
||||
let result = this.context.wrapper.NodeQQNTWrapperUtil.getNTUserDataInfoConfig();
|
||||
if (!result) {
|
||||
result = path.resolve(os.homedir(), './.config/QQ');
|
||||
fs.mkdirSync(result, { recursive: true });
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Renamed from 'InitDataListener'
|
||||
async initNapCatCoreListeners() {
|
||||
const msgListener = new NodeIKernelMsgListener();
|
||||
msgListener.onKickedOffLine = (Info: KickedOffLineInfo) => {
|
||||
// 下线通知
|
||||
this.context.logger.logError('[KickedOffLine] [' + Info.tipsTitle + '] ' + Info.tipsDesc);
|
||||
this.selfInfo.online = false;
|
||||
};
|
||||
msgListener.onRecvMsg = (msgs) => {
|
||||
msgs.forEach(msg => this.context.logger.logMessage(msg, this.selfInfo));
|
||||
};
|
||||
msgListener.onAddSendMsg = (msg) => {
|
||||
this.context.logger.logMessage(msg, this.selfInfo);
|
||||
};
|
||||
//await sleep(2500);
|
||||
this.context.session.getMsgService().addKernelMsgListener(
|
||||
proxiedListenerOf(msgListener, this.context.logger) as any,
|
||||
);
|
||||
|
||||
const profileListener = new NodeIKernelProfileListener();
|
||||
profileListener.onProfileDetailInfoChanged = (profile) => {
|
||||
if (profile.uid === this.selfInfo.uid) {
|
||||
Object.assign(this.selfInfo, profile);
|
||||
}
|
||||
};
|
||||
profileListener.onSelfStatusChanged = (Info: SelfStatusInfo) => {
|
||||
if (Info.status == 20) {
|
||||
this.selfInfo.online = false;
|
||||
this.context.logger.log("账号状态变更为离线");
|
||||
} else {
|
||||
this.selfInfo.online = true;
|
||||
}
|
||||
};
|
||||
this.context.session.getProfileService().addKernelProfileListener(
|
||||
proxiedListenerOf(profileListener, this.context.logger),
|
||||
);
|
||||
|
||||
// 群相关
|
||||
const groupListener = new NodeIKernelGroupListener();
|
||||
groupListener.onGroupListUpdate = (updateType, groupList) => {
|
||||
// console.log("onGroupListUpdate", updateType, groupList)
|
||||
groupList.map(g => {
|
||||
const existGroup = this.apis.GroupApi.groupCache.get(g.groupCode);
|
||||
//群成员数量变化 应该刷新缓存
|
||||
if (existGroup && g.memberCount === existGroup.memberCount) {
|
||||
Object.assign(existGroup, g);
|
||||
} else {
|
||||
this.apis.GroupApi.groupCache.set(g.groupCode, g);
|
||||
// 获取群成员
|
||||
}
|
||||
const sceneId = this.context.session.getGroupService().createMemberListScene(g.groupCode, 'groupMemberList_MainWindow');
|
||||
this.context.session.getGroupService().getNextMemberList(sceneId, undefined, 3000).then( /* r => {
|
||||
// console.log(`get group ${g.groupCode} members`, r);
|
||||
// r.result.infos.forEach(member => {
|
||||
// });
|
||||
// groupMembers.set(g.groupCode, r.result.infos);
|
||||
} */);
|
||||
this.context.session.getGroupService().destroyMemberListScene(sceneId);
|
||||
});
|
||||
};
|
||||
groupListener.onMemberListChange = (arg) => {
|
||||
// todo: 应该加一个内部自己维护的成员变动callback,用于判断成员变化通知
|
||||
const groupCode = arg.sceneId.split('_')[0];
|
||||
if (this.apis.GroupApi.groupMemberCache.has(groupCode)) {
|
||||
const existMembers = this.apis.GroupApi.groupMemberCache.get(groupCode)!;
|
||||
arg.infos.forEach((member, uid) => {
|
||||
//console.log('onMemberListChange', member);
|
||||
const existMember = existMembers.get(uid);
|
||||
if (existMember) {
|
||||
Object.assign(existMember, member);
|
||||
} else {
|
||||
existMembers!.set(uid, member);
|
||||
}
|
||||
//移除成员
|
||||
if (member.isDelete) {
|
||||
existMembers.delete(uid);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.apis.GroupApi.groupMemberCache.set(groupCode, arg.infos);
|
||||
}
|
||||
};
|
||||
groupListener.onMemberInfoChange = (groupCode, dataSource, members) => {
|
||||
if (dataSource === DataSource.LOCAL && members.get(this.selfInfo.uid)?.isDelete) {
|
||||
// 自身退群或者被踢退群 5s用于Api操作 之后不再出现
|
||||
setTimeout(() => {
|
||||
this.apis.GroupApi.groupCache.delete(groupCode);
|
||||
}, 5000);
|
||||
|
||||
}
|
||||
const existMembers = this.apis.GroupApi.groupMemberCache.get(groupCode);
|
||||
if (existMembers) {
|
||||
members.forEach((member, uid) => {
|
||||
const existMember = existMembers.get(uid);
|
||||
if (existMember) {
|
||||
// 检查管理变动
|
||||
member.isChangeRole = this.checkAdminEvent(groupCode, member, existMember);
|
||||
// 更新成员信息
|
||||
Object.assign(existMember, member);
|
||||
} else {
|
||||
existMembers.set(uid, member);
|
||||
}
|
||||
//移除成员
|
||||
if (member.isDelete) {
|
||||
existMembers.delete(uid);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.apis.GroupApi.groupMemberCache.set(groupCode, members);
|
||||
}
|
||||
};
|
||||
this.context.session.getGroupService().addKernelGroupListener(
|
||||
proxiedListenerOf(groupListener, this.context.logger) as any,
|
||||
);
|
||||
}
|
||||
|
||||
checkAdminEvent(groupCode: string, memberNew: GroupMember, memberOld: GroupMember | undefined): boolean {
|
||||
if (memberNew.role !== memberOld?.role) {
|
||||
this.context.logger.logDebug(`群 ${groupCode} ${memberNew.nick} 角色变更为 ${memberNew.role === 3 ? '管理员' : '群员'}`);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export async function genSessionConfig(QQVersionAppid: string, QQVersion: string, selfUin: string, selfUid: string, account_path: string): Promise<WrapperSessionInitConfig> {
|
||||
const downloadPath = path.join(account_path, 'NapCat', 'temp');
|
||||
fs.mkdirSync(downloadPath, { recursive: true });
|
||||
const guid: string = await getMachineId();//26702 支持JS获取guid值 在LoginService中获取 TODO mlikiow a
|
||||
//os.platform()
|
||||
let systemPlatform = PlatformType.KWINDOWS;
|
||||
switch (os.platform()) {
|
||||
case 'win32':
|
||||
systemPlatform = PlatformType.KWINDOWS;
|
||||
break;
|
||||
case 'darwin':
|
||||
systemPlatform = PlatformType.KMAC;
|
||||
break;
|
||||
case 'linux':
|
||||
systemPlatform = PlatformType.KANDROID; //Android 怎么不算Linux!
|
||||
break;
|
||||
}
|
||||
return {
|
||||
selfUin,
|
||||
selfUid,
|
||||
desktopPathConfig: {
|
||||
account_path, // 可以通过NodeQQNTWrapperUtil().getNTUserDataInfoConfig()获取
|
||||
},
|
||||
clientVer: QQVersion,
|
||||
a2: '',
|
||||
d2: '',
|
||||
d2Key: '',
|
||||
machineId: '',
|
||||
platform: systemPlatform, // 3是Windows?
|
||||
platVer: systemVersion, // 系统版本号, 应该可以固定
|
||||
appid: QQVersionAppid,
|
||||
rdeliveryConfig: {
|
||||
appKey: '',
|
||||
systemId: 0,
|
||||
appId: '',
|
||||
logicEnvironment: '',
|
||||
platform: systemPlatform,
|
||||
language: '',
|
||||
sdkVersion: '',
|
||||
userId: '',
|
||||
appVersion: '',
|
||||
osVersion: '',
|
||||
bundleId: '',
|
||||
serverUrl: '',
|
||||
fixedAfterHitKeys: [''],
|
||||
},
|
||||
defaultFileDownloadPath: downloadPath,
|
||||
deviceInfo: {
|
||||
guid,
|
||||
buildVer: QQVersion,
|
||||
localId: 2052,
|
||||
devName: hostname,
|
||||
devType: systemName,
|
||||
vendorName: '',
|
||||
osVer: systemVersion,
|
||||
vendorOsName: systemName,
|
||||
setMute: false,
|
||||
vendorType: VendorType.KNOSETONIOS,
|
||||
},
|
||||
deviceConfig: '{"appearance":{"isSplitViewMode":true},"msg":{}}',
|
||||
};
|
||||
}
|
||||
|
||||
export interface InstanceContext {
|
||||
readonly workingEnv: NapCatCoreWorkingEnv;
|
||||
readonly wrapper: WrapperNodeApi;
|
||||
readonly session: NodeIQQNTWrapperSession;
|
||||
readonly logger: LogWrapper;
|
||||
readonly loginService: NodeIKernelLoginService;
|
||||
readonly basicInfoWrapper: QQBasicInfoWrapper;
|
||||
readonly pathWrapper: NapCatPathWrapper;
|
||||
}
|
||||
|
||||
export interface StableNTApiWrapper {
|
||||
FileApi: NTQQFileApi,
|
||||
SystemApi: NTQQSystemApi,
|
||||
CollectionApi: NTQQCollectionApi,
|
||||
WebApi: NTQQWebApi,
|
||||
FriendApi: NTQQFriendApi,
|
||||
MsgApi: NTQQMsgApi,
|
||||
UserApi: NTQQUserApi,
|
||||
GroupApi: NTQQGroupApi
|
||||
}
|
||||
|
@@ -1,53 +1,9 @@
|
||||
import { BuddyCategoryType, FriendRequestNotify } from '@/core/entities';
|
||||
|
||||
export type OnBuddyChangeParams = BuddyCategoryType[]
|
||||
export type OnBuddyChangeParams = BuddyCategoryType[];
|
||||
|
||||
interface IBuddyListener {
|
||||
onBuddyListChangedV2(arg: unknown): void,//V2版本 还没兼容
|
||||
|
||||
onBuddyListChange(arg: OnBuddyChangeParams): void,
|
||||
|
||||
onBuddyInfoChange(arg: unknown): void,
|
||||
|
||||
onBuddyDetailInfoChange(arg: unknown): void,
|
||||
|
||||
onNickUpdated(arg: unknown): void,
|
||||
|
||||
onBuddyRemarkUpdated(arg: unknown): void,
|
||||
|
||||
onAvatarUrlUpdated(arg: unknown): void,
|
||||
|
||||
onBuddyReqChange(arg: FriendRequestNotify): void,
|
||||
|
||||
onBuddyReqUnreadCntChange(arg: unknown): void,
|
||||
|
||||
onCheckBuddySettingResult(arg: unknown): void,
|
||||
|
||||
onAddBuddyNeedVerify(arg: unknown): void,
|
||||
|
||||
onSmartInfos(arg: unknown): void,
|
||||
|
||||
onSpacePermissionInfos(arg: unknown): void,
|
||||
|
||||
onDoubtBuddyReqChange(arg: unknown): void,
|
||||
|
||||
onDoubtBuddyReqUnreadNumChange(arg: unknown): void,
|
||||
|
||||
onBlockChanged(arg: unknown): void,
|
||||
|
||||
onAddMeSettingChanged(arg: unknown): void,
|
||||
|
||||
onDelBatchBuddyInfos(arg: unknown): void
|
||||
}
|
||||
|
||||
export interface NodeIKernelBuddyListener extends IBuddyListener {
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-new
|
||||
new(listener: IBuddyListener): NodeIKernelBuddyListener;
|
||||
}
|
||||
|
||||
export class BuddyListener implements IBuddyListener {
|
||||
export class NodeIKernelBuddyListener {
|
||||
onBuddyListChangedV2(arg: unknown): void {
|
||||
//throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
onAddBuddyNeedVerify(arg: unknown) {
|
||||
|
@@ -1,22 +1,13 @@
|
||||
export interface IKernelFileAssistantListener {
|
||||
onFileStatusChanged(...args: unknown[]): unknown;
|
||||
|
||||
onSessionListChanged(...args: unknown[]): unknown;
|
||||
|
||||
onSessionChanged(...args: unknown[]): unknown;
|
||||
|
||||
onFileListChanged(...args: unknown[]): unknown;
|
||||
|
||||
onFileSearch(...args: unknown[]): unknown;
|
||||
}
|
||||
|
||||
export interface NodeIKernelFileAssistantListener extends IKernelFileAssistantListener {
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-new
|
||||
new(adapter: IKernelFileAssistantListener): NodeIKernelFileAssistantListener;
|
||||
}
|
||||
|
||||
export class KernelFileAssistantListener implements IKernelFileAssistantListener {
|
||||
onFileStatusChanged(...args: unknown[]) {
|
||||
export class NodeIKernelFileAssistantListener {
|
||||
onFileStatusChanged(fileStatus: {
|
||||
id: string,
|
||||
fileStatus: number,
|
||||
fileProgress: `${number}`,
|
||||
fileSize: `${number}`,
|
||||
fileSpeed: number,
|
||||
thumbPath: string | null,
|
||||
filePath: string | null,
|
||||
}) {
|
||||
}
|
||||
|
||||
onSessionListChanged(...args: unknown[]) {
|
||||
@@ -28,6 +19,42 @@ export class KernelFileAssistantListener implements IKernelFileAssistantListener
|
||||
onFileListChanged(...args: unknown[]) {
|
||||
}
|
||||
|
||||
onFileSearch(...args: unknown[]) {
|
||||
onFileSearch(searchResult: SearchResultWrapper) {
|
||||
}
|
||||
}
|
||||
|
||||
export type SearchResultWrapper = {
|
||||
searchId: number,
|
||||
resultId: number,
|
||||
hasMore: boolean,
|
||||
resultItems: SearchResultItem[],
|
||||
};
|
||||
|
||||
export type SearchResultItem = {
|
||||
id: string,
|
||||
fileName: string,
|
||||
fileNameHits: string[],
|
||||
fileStatus: number,
|
||||
fileSize: string,
|
||||
isSend: boolean,
|
||||
source: number,
|
||||
fileTime: string,
|
||||
expTime: string,
|
||||
session: {
|
||||
context: null,
|
||||
uid: string,
|
||||
nick: string,
|
||||
remark: string,
|
||||
memberCard: string,
|
||||
groupCode: string,
|
||||
groupName: string,
|
||||
groupRemark: string,
|
||||
count: number,
|
||||
},
|
||||
thumbPath: string,
|
||||
filePath: string,
|
||||
msgId: string,
|
||||
chatType: number,
|
||||
peerUid: string,
|
||||
fileType: number,
|
||||
};
|
||||
|
@@ -1,68 +1,7 @@
|
||||
import { Group, GroupListUpdateType, GroupMember, GroupNotify } from '@/core/entities';
|
||||
import { DataSource, Group, GroupListUpdateType, GroupMember, GroupNotify } from '@/core/entities';
|
||||
|
||||
interface IGroupListener {
|
||||
onGroupListUpdate(updateType: GroupListUpdateType, groupList: Group[]): void;
|
||||
|
||||
onGroupExtListUpdate(...args: unknown[]): void;
|
||||
|
||||
onGroupSingleScreenNotifies(doubt: boolean, seq: string, notifies: GroupNotify[]): void;
|
||||
|
||||
onGroupNotifiesUpdated(dboubt: boolean, notifies: GroupNotify[]): void;
|
||||
|
||||
onGroupNotifiesUnreadCountUpdated(...args: unknown[]): void;
|
||||
|
||||
onGroupDetailInfoChange(...args: unknown[]): void;
|
||||
|
||||
onGroupAllInfoChange(...args: unknown[]): void;
|
||||
|
||||
onGroupsMsgMaskResult(...args: unknown[]): void;
|
||||
|
||||
onGroupConfMemberChange(...args: unknown[]): void;
|
||||
|
||||
onGroupBulletinChange(...args: unknown[]): void;
|
||||
|
||||
onGetGroupBulletinListResult(...args: unknown[]): void;
|
||||
|
||||
onMemberListChange(arg: {
|
||||
sceneId: string,
|
||||
ids: string[],
|
||||
infos: Map<string, GroupMember>,
|
||||
finish: boolean,
|
||||
hasRobot: boolean
|
||||
}): void;
|
||||
|
||||
onMemberInfoChange(groupCode: string, changeType: number, members: Map<string, GroupMember>): void;
|
||||
|
||||
onSearchMemberChange(...args: unknown[]): void;
|
||||
|
||||
onGroupBulletinRichMediaDownloadComplete(...args: unknown[]): void;
|
||||
|
||||
onGroupBulletinRichMediaProgressUpdate(...args: unknown[]): void;
|
||||
|
||||
onGroupStatisticInfoChange(...args: unknown[]): void;
|
||||
|
||||
onJoinGroupNotify(...args: unknown[]): void;
|
||||
|
||||
onShutUpMemberListChanged(...args: unknown[]): void;
|
||||
|
||||
onGroupBulletinRemindNotify(...args: unknown[]): void;
|
||||
|
||||
onGroupFirstBulletinNotify(...args: unknown[]): void;
|
||||
|
||||
onJoinGroupNoVerifyFlag(...args: unknown[]): void;
|
||||
|
||||
onGroupArkInviteStateResult(...args: unknown[]): void;
|
||||
|
||||
// 发现于Win 9.9.9 23159
|
||||
onGroupMemberLevelInfoChange(...args: unknown[]): void;
|
||||
}
|
||||
|
||||
export interface NodeIKernelGroupListener extends IGroupListener {
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-new
|
||||
new(listener: IGroupListener): NodeIKernelGroupListener;
|
||||
}
|
||||
|
||||
export class GroupListener implements IGroupListener {
|
||||
export class NodeIKernelGroupListener {
|
||||
onGroupListInited(listEmpty: boolean): void { }
|
||||
// 发现于Win 9.9.9 23159
|
||||
onGroupMemberLevelInfoChange(...args: unknown[]): void {
|
||||
|
||||
@@ -125,7 +64,7 @@ export class GroupListener implements IGroupListener {
|
||||
onJoinGroupNoVerifyFlag(...args: unknown[]) {
|
||||
}
|
||||
|
||||
onMemberInfoChange(groupCode: string, changeType: number, members: Map<string, GroupMember>) {
|
||||
onMemberInfoChange(groupCode: string, dateSource: DataSource, members: Map<string, GroupMember>) {
|
||||
}
|
||||
|
||||
onMemberListChange(arg: {
|
||||
@@ -142,102 +81,4 @@ export class GroupListener implements IGroupListener {
|
||||
|
||||
onShutUpMemberListChanged(...args: unknown[]) {
|
||||
}
|
||||
}
|
||||
|
||||
export class DebugGroupListener implements IGroupListener {
|
||||
onGroupMemberLevelInfoChange(...args: unknown[]): void {
|
||||
console.log('onGroupMemberLevelInfoChange:', ...args);
|
||||
}
|
||||
|
||||
onGetGroupBulletinListResult(...args: unknown[]) {
|
||||
console.log('onGetGroupBulletinListResult:', ...args);
|
||||
}
|
||||
|
||||
onGroupAllInfoChange(...args: unknown[]) {
|
||||
console.log('onGroupAllInfoChange:', ...args);
|
||||
}
|
||||
|
||||
onGroupBulletinChange(...args: unknown[]) {
|
||||
console.log('onGroupBulletinChange:', ...args);
|
||||
}
|
||||
|
||||
onGroupBulletinRemindNotify(...args: unknown[]) {
|
||||
console.log('onGroupBulletinRemindNotify:', ...args);
|
||||
}
|
||||
|
||||
onGroupArkInviteStateResult(...args: unknown[]) {
|
||||
console.log('onGroupArkInviteStateResult:', ...args);
|
||||
}
|
||||
|
||||
onGroupBulletinRichMediaDownloadComplete(...args: unknown[]) {
|
||||
console.log('onGroupBulletinRichMediaDownloadComplete:', ...args);
|
||||
}
|
||||
|
||||
onGroupConfMemberChange(...args: unknown[]) {
|
||||
console.log('onGroupConfMemberChange:', ...args);
|
||||
}
|
||||
|
||||
onGroupDetailInfoChange(...args: unknown[]) {
|
||||
console.log('onGroupDetailInfoChange:', ...args);
|
||||
}
|
||||
|
||||
onGroupExtListUpdate(...args: unknown[]) {
|
||||
console.log('onGroupExtListUpdate:', ...args);
|
||||
}
|
||||
|
||||
onGroupFirstBulletinNotify(...args: unknown[]) {
|
||||
console.log('onGroupFirstBulletinNotify:', ...args);
|
||||
}
|
||||
|
||||
onGroupListUpdate(...args: unknown[]) {
|
||||
console.log('onGroupListUpdate:', ...args);
|
||||
}
|
||||
|
||||
onGroupNotifiesUpdated(...args: unknown[]) {
|
||||
console.log('onGroupNotifiesUpdated:', ...args);
|
||||
}
|
||||
|
||||
onGroupBulletinRichMediaProgressUpdate(...args: unknown[]) {
|
||||
console.log('onGroupBulletinRichMediaProgressUpdate:', ...args);
|
||||
}
|
||||
|
||||
onGroupNotifiesUnreadCountUpdated(...args: unknown[]) {
|
||||
console.log('onGroupNotifiesUnreadCountUpdated:', ...args);
|
||||
}
|
||||
|
||||
onGroupSingleScreenNotifies(doubt: boolean, seq: string, notifies: GroupNotify[]) {
|
||||
console.log('onGroupSingleScreenNotifies:');
|
||||
}
|
||||
|
||||
onGroupsMsgMaskResult(...args: unknown[]) {
|
||||
console.log('onGroupsMsgMaskResult:', ...args);
|
||||
}
|
||||
|
||||
onGroupStatisticInfoChange(...args: unknown[]) {
|
||||
console.log('onGroupStatisticInfoChange:', ...args);
|
||||
}
|
||||
|
||||
onJoinGroupNotify(...args: unknown[]) {
|
||||
console.log('onJoinGroupNotify:', ...args);
|
||||
}
|
||||
|
||||
onJoinGroupNoVerifyFlag(...args: unknown[]) {
|
||||
console.log('onJoinGroupNoVerifyFlag:', ...args);
|
||||
}
|
||||
|
||||
onMemberInfoChange(groupCode: string, changeType: number, members: Map<string, GroupMember>) {
|
||||
console.log('onMemberInfoChange:', groupCode, changeType, members);
|
||||
}
|
||||
|
||||
onMemberListChange(...args: unknown[]) {
|
||||
console.log('onMemberListChange:', ...args);
|
||||
}
|
||||
|
||||
onSearchMemberChange(...args: unknown[]) {
|
||||
console.log('onSearchMemberChange:', ...args);
|
||||
}
|
||||
|
||||
onShutUpMemberListChanged(...args: unknown[]) {
|
||||
console.log('onShutUpMemberListChanged:', ...args);
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,45 +1,4 @@
|
||||
export interface IKernelLoginListener {
|
||||
onLoginConnected(...args: any[]): void;
|
||||
|
||||
onLoginDisConnected(...args: any[]): void;
|
||||
|
||||
onLoginConnecting(...args: any[]): void;
|
||||
|
||||
onQRCodeGetPicture(...args: any[]): void;
|
||||
|
||||
onQRCodeLoginPollingStarted(...args: any[]): void;
|
||||
|
||||
onQRCodeSessionUserScaned(...args: any[]): void;
|
||||
|
||||
onQRCodeLoginSucceed(...args: any[]): void;
|
||||
|
||||
onQRCodeSessionFailed(...args: any[]): void;
|
||||
|
||||
onLoginFailed(...args: any[]): void;
|
||||
|
||||
onLogoutSucceed(...args: any[]): void;
|
||||
|
||||
onLogoutFailed(...args: any[]): void;
|
||||
|
||||
onUserLoggedIn(...args: any[]): void;
|
||||
|
||||
onQRCodeSessionQuickLoginFailed(...args: any[]): void;
|
||||
|
||||
onPasswordLoginFailed(...args: any[]): void;
|
||||
|
||||
OnConfirmUnusualDeviceFailed(...args: any[]): void;
|
||||
|
||||
onQQLoginNumLimited(...args: any[]): void;
|
||||
|
||||
onLoginState(...args: any[]): void;
|
||||
}
|
||||
|
||||
export interface NodeIKernelLoginListener {
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-new
|
||||
new(listener: IKernelLoginListener): NodeIKernelLoginListener;
|
||||
}
|
||||
|
||||
export class LoginListener implements IKernelLoginListener {
|
||||
export class NodeIKernelLoginListener {
|
||||
onLoginConnected(...args: any[]): void {
|
||||
}
|
||||
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import { ChatType, RawMessage } from '@/core/entities';
|
||||
import { CommonFileInfo } from '@/core';
|
||||
|
||||
export interface OnRichMediaDownloadCompleteParams {
|
||||
fileModelId: string,
|
||||
@@ -15,7 +16,7 @@ export interface OnRichMediaDownloadCompleteParams {
|
||||
totalSize: string,
|
||||
trasferStatus: number,
|
||||
step: number,
|
||||
commonFileInfo: unknown | null,
|
||||
commonFileInfo?: CommonFileInfo,
|
||||
fileSrvErrCode: string,
|
||||
clientMsg: string,
|
||||
businessId: number,
|
||||
@@ -23,12 +24,52 @@ export interface OnRichMediaDownloadCompleteParams {
|
||||
userUsedSpacePerDay: unknown | null
|
||||
}
|
||||
|
||||
export interface onGroupFileInfoUpdateParamType {
|
||||
export interface GroupFileInfoUpdateParamType {
|
||||
retCode: number;
|
||||
retMsg: string;
|
||||
clientWording: string;
|
||||
isEnd: boolean;
|
||||
item: Array<any>;
|
||||
item: Array<{
|
||||
peerId: string;
|
||||
type: number;
|
||||
folderInfo?: {
|
||||
folderId: string;
|
||||
parentFolderId: string;
|
||||
folderName: string;
|
||||
createTime: number;
|
||||
modifyTime: number;
|
||||
createUin: string;
|
||||
creatorName: string;
|
||||
totalFileCount: number;
|
||||
modifyUin: string;
|
||||
modifyName: string;
|
||||
usedSpace: string;
|
||||
},
|
||||
fileInfo?: {
|
||||
fileModelId: string;
|
||||
fileId: string;
|
||||
fileName: string;
|
||||
fileSize: string;
|
||||
busId: number;
|
||||
uploadedSize: string;
|
||||
uploadTime: number;
|
||||
deadTime: number;
|
||||
modifyTime: number;
|
||||
downloadTimes: number;
|
||||
sha: string;
|
||||
sha3: string;
|
||||
md5: string;
|
||||
uploaderLocalPath: string;
|
||||
uploaderName: string;
|
||||
uploaderUin: string;
|
||||
parentFolderId: string;
|
||||
localPath: string;
|
||||
transStatus: number;
|
||||
transType: number;
|
||||
elementId: string;
|
||||
isFolder: boolean;
|
||||
},
|
||||
}>;
|
||||
allFileCount: string;
|
||||
nextIndex: string;
|
||||
reqId: string;
|
||||
@@ -52,199 +93,7 @@ export interface TempOnRecvParams {
|
||||
|
||||
}
|
||||
|
||||
export interface IKernelMsgListener {
|
||||
onAddSendMsg(msgRecord: RawMessage): void;
|
||||
|
||||
onBroadcastHelperDownloadComplete(broadcastHelperTransNotifyInfo: unknown): void;
|
||||
|
||||
onBroadcastHelperProgressUpdate(broadcastHelperTransNotifyInfo: unknown): void;
|
||||
|
||||
onChannelFreqLimitInfoUpdate(contact: unknown, z: unknown, freqLimitInfo: unknown): void;
|
||||
|
||||
onContactUnreadCntUpdate(hashMap: unknown): void;
|
||||
|
||||
onCustomWithdrawConfigUpdate(customWithdrawConfig: unknown): void;
|
||||
|
||||
onDraftUpdate(contact: unknown, arrayList: unknown, j2: unknown): void;
|
||||
|
||||
onEmojiDownloadComplete(emojiNotifyInfo: unknown): void;
|
||||
|
||||
onEmojiResourceUpdate(emojiResourceInfo: unknown): void;
|
||||
|
||||
onFeedEventUpdate(firstViewDirectMsgNotifyInfo: unknown): void;
|
||||
|
||||
onFileMsgCome(arrayList: unknown): void;
|
||||
|
||||
onFirstViewDirectMsgUpdate(firstViewDirectMsgNotifyInfo: unknown): void;
|
||||
|
||||
onFirstViewGroupGuildMapping(arrayList: unknown): void;
|
||||
|
||||
onGrabPasswordRedBag(i2: unknown, str: unknown, i3: unknown, recvdOrder: unknown, msgRecord: unknown): void;
|
||||
|
||||
onGroupFileInfoAdd(groupItem: unknown): void;
|
||||
|
||||
onGroupFileInfoUpdate(groupFileListResult: onGroupFileInfoUpdateParamType): void;
|
||||
|
||||
onGroupGuildUpdate(groupGuildNotifyInfo: unknown): void;
|
||||
|
||||
onGroupTransferInfoAdd(groupItem: unknown): void;
|
||||
|
||||
onGroupTransferInfoUpdate(groupFileListResult: unknown): void;
|
||||
|
||||
onGuildInteractiveUpdate(guildInteractiveNotificationItem: unknown): void;
|
||||
|
||||
onGuildMsgAbFlagChanged(guildMsgAbFlag: unknown): void;
|
||||
|
||||
onGuildNotificationAbstractUpdate(guildNotificationAbstractInfo: unknown): void;
|
||||
|
||||
onHitCsRelatedEmojiResult(downloadRelateEmojiResultInfo: unknown): void;
|
||||
|
||||
onHitEmojiKeywordResult(hitRelatedEmojiWordsResult: unknown): void;
|
||||
|
||||
onHitRelatedEmojiResult(relatedWordEmojiInfo: unknown): void;
|
||||
|
||||
onImportOldDbProgressUpdate(importOldDbMsgNotifyInfo: unknown): void;
|
||||
|
||||
onInputStatusPush(inputStatusInfo: {
|
||||
chatType: number;
|
||||
eventType: number;
|
||||
fromUin: string;
|
||||
interval: string;
|
||||
showTime: string;
|
||||
statusText: string;
|
||||
timestamp: string;
|
||||
toUin: string;
|
||||
}): void;
|
||||
|
||||
onKickedOffLine(kickedInfo: unknown): void;
|
||||
|
||||
onLineDev(arrayList: unknown): void;
|
||||
|
||||
onLogLevelChanged(j2: unknown): void;
|
||||
|
||||
onMsgAbstractUpdate(arrayList: unknown): void;
|
||||
|
||||
onMsgBoxChanged(arrayList: unknown): void;
|
||||
|
||||
onMsgDelete(contact: unknown, arrayList: unknown): void;
|
||||
|
||||
onMsgEventListUpdate(hashMap: unknown): void;
|
||||
|
||||
onMsgInfoListAdd(arrayList: unknown): void;
|
||||
|
||||
onMsgInfoListUpdate(msgList: RawMessage[]): void;
|
||||
|
||||
onMsgQRCodeStatusChanged(i2: unknown): void;
|
||||
|
||||
onMsgRecall(i2: unknown, str: unknown, j2: unknown): void;
|
||||
|
||||
onMsgSecurityNotify(msgRecord: unknown): void;
|
||||
|
||||
onMsgSettingUpdate(msgSetting: unknown): void;
|
||||
|
||||
onNtFirstViewMsgSyncEnd(): void;
|
||||
|
||||
onNtMsgSyncEnd(): void;
|
||||
|
||||
onNtMsgSyncStart(): void;
|
||||
|
||||
onReadFeedEventUpdate(firstViewDirectMsgNotifyInfo: unknown): void;
|
||||
|
||||
onRecvGroupGuildFlag(i2: unknown): void;
|
||||
|
||||
onRecvMsg(...arrayList: unknown[]): void;
|
||||
|
||||
onRecvMsgSvrRspTransInfo(j2: unknown, contact: unknown, i2: unknown, i3: unknown, str: unknown, bArr: unknown): void;
|
||||
|
||||
onRecvOnlineFileMsg(arrayList: unknown): void;
|
||||
|
||||
onRecvS2CMsg(arrayList: unknown): void;
|
||||
|
||||
onRecvSysMsg(arrayList: unknown): void;
|
||||
|
||||
onRecvUDCFlag(i2: unknown): void;
|
||||
|
||||
onRichMediaDownloadComplete(fileTransNotifyInfo: OnRichMediaDownloadCompleteParams): void;
|
||||
|
||||
onRichMediaProgerssUpdate(fileTransNotifyInfo: unknown): void;
|
||||
|
||||
onRichMediaUploadComplete(fileTransNotifyInfo: unknown): void;
|
||||
|
||||
onSearchGroupFileInfoUpdate(searchGroupFileResult:
|
||||
{
|
||||
result: {
|
||||
retCode: number,
|
||||
retMsg: string,
|
||||
clientWording: string
|
||||
},
|
||||
syncCookie: string,
|
||||
totalMatchCount: number,
|
||||
ownerMatchCount: number,
|
||||
isEnd: boolean,
|
||||
reqId: number,
|
||||
item: Array<{
|
||||
groupCode: string,
|
||||
groupName: string,
|
||||
uploaderUin: string,
|
||||
uploaderName: string,
|
||||
matchUin: string,
|
||||
matchWords: Array<unknown>,
|
||||
fileNameHits: Array<{
|
||||
start: number,
|
||||
end: number
|
||||
}>,
|
||||
fileModelId: string,
|
||||
fileId: string,
|
||||
fileName: string,
|
||||
fileSize: string,
|
||||
busId: number,
|
||||
uploadTime: number,
|
||||
modifyTime: number,
|
||||
deadTime: number,
|
||||
downloadTimes: number,
|
||||
localPath: string
|
||||
}>
|
||||
}): void;
|
||||
|
||||
onSendMsgError(j2: unknown, contact: unknown, i2: unknown, str: unknown): void;
|
||||
|
||||
onSysMsgNotification(i2: unknown, j2: unknown, j3: unknown, arrayList: unknown): void;
|
||||
|
||||
onTempChatInfoUpdate(tempChatInfo: TempOnRecvParams): void;
|
||||
|
||||
onUnreadCntAfterFirstView(hashMap: unknown): void;
|
||||
|
||||
onUnreadCntUpdate(hashMap: unknown): void;
|
||||
|
||||
onUserChannelTabStatusChanged(z: unknown): void;
|
||||
|
||||
onUserOnlineStatusChanged(z: unknown): void;
|
||||
|
||||
onUserTabStatusChanged(arrayList: unknown): void;
|
||||
|
||||
onlineStatusBigIconDownloadPush(i2: unknown, j2: unknown, str: unknown): void;
|
||||
|
||||
onlineStatusSmallIconDownloadPush(i2: unknown, j2: unknown, str: unknown): void;
|
||||
|
||||
// 第一次发现于Linux
|
||||
onUserSecQualityChanged(...args: unknown[]): void;
|
||||
|
||||
onMsgWithRichLinkInfoUpdate(...args: unknown[]): void;
|
||||
|
||||
onRedTouchChanged(...args: unknown[]): void;
|
||||
|
||||
// 第一次发现于Win 9.9.9 23159
|
||||
onBroadcastHelperProgerssUpdate(...args: unknown[]): void;
|
||||
|
||||
}
|
||||
|
||||
export interface NodeIKernelMsgListener extends IKernelMsgListener {
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-new
|
||||
new(listener: IKernelMsgListener): NodeIKernelMsgListener;
|
||||
}
|
||||
|
||||
|
||||
export class MsgListener implements IKernelMsgListener {
|
||||
export class NodeIKernelMsgListener {
|
||||
onAddSendMsg(msgRecord: RawMessage) {
|
||||
|
||||
}
|
||||
@@ -305,7 +154,7 @@ export class MsgListener implements IKernelMsgListener {
|
||||
|
||||
}
|
||||
|
||||
onGroupFileInfoUpdate(groupFileListResult: onGroupFileInfoUpdateParamType) {
|
||||
onGroupFileInfoUpdate(groupFileListResult: GroupFileInfoUpdateParamType) {
|
||||
|
||||
}
|
||||
|
||||
@@ -451,7 +300,7 @@ export class MsgListener implements IKernelMsgListener {
|
||||
|
||||
}
|
||||
|
||||
onRecvSysMsg(arrayList: unknown) {
|
||||
onRecvSysMsg(arrayList: Array<number>) {
|
||||
|
||||
}
|
||||
|
||||
|
@@ -1,25 +1,6 @@
|
||||
import { User, UserDetailInfoListenerArg } from '@/core/entities';
|
||||
|
||||
interface IProfileListener {
|
||||
onProfileSimpleChanged(...args: unknown[]): void;
|
||||
|
||||
onUserDetailInfoChanged(arg: UserDetailInfoListenerArg): void;
|
||||
|
||||
onProfileDetailInfoChanged(profile: User): void;
|
||||
|
||||
onStatusUpdate(...args: unknown[]): void;
|
||||
|
||||
onSelfStatusChanged(...args: unknown[]): void;
|
||||
|
||||
onStrangerRemarkChanged(...args: unknown[]): void;
|
||||
}
|
||||
|
||||
export interface NodeIKernelProfileListener extends IProfileListener {
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-new
|
||||
new(listener: IProfileListener): NodeIKernelProfileListener;
|
||||
}
|
||||
|
||||
export class ProfileListener implements IProfileListener {
|
||||
export class NodeIKernelProfileListener {
|
||||
onUserDetailInfoChanged(arg: UserDetailInfoListenerArg): void {
|
||||
|
||||
}
|
||||
@@ -43,4 +24,48 @@ export class ProfileListener implements IProfileListener {
|
||||
onStrangerRemarkChanged(...args: unknown[]) {
|
||||
|
||||
}
|
||||
|
||||
onMemberListChange(...args: unknown[]) {
|
||||
|
||||
}
|
||||
|
||||
onMemberInfoChange(...args: unknown[]) {
|
||||
|
||||
}
|
||||
|
||||
onGroupListUpdate(...args: unknown[]) {
|
||||
|
||||
}
|
||||
|
||||
onGroupAllInfoChange(...args: unknown[]) {
|
||||
|
||||
}
|
||||
|
||||
onGroupDetailInfoChange(...args: unknown[]) {
|
||||
|
||||
}
|
||||
|
||||
onGroupConfMemberChange(...args: unknown[]) {
|
||||
|
||||
}
|
||||
|
||||
onGroupExtListUpdate(...args: unknown[]) {
|
||||
|
||||
}
|
||||
|
||||
onGroupNotifiesUpdated(...args: unknown[]) {
|
||||
|
||||
}
|
||||
|
||||
onGroupNotifiesUnreadCountUpdated(...args: unknown[]) {
|
||||
|
||||
}
|
||||
|
||||
onGroupMemberLevelInfoChange(...args: unknown[]) {
|
||||
|
||||
}
|
||||
|
||||
onGroupBulletinChange(...args: unknown[]) {
|
||||
|
||||
}
|
||||
}
|
||||
|
@@ -1,28 +1,9 @@
|
||||
interface IKernelRecentContactListener {
|
||||
onDeletedContactsNotify(...args: unknown[]): unknown;
|
||||
|
||||
onRecentContactNotification(...args: unknown[]): unknown;
|
||||
|
||||
onMsgUnreadCountUpdate(...args: unknown[]): unknown;
|
||||
|
||||
onGuildDisplayRecentContactListChanged(...args: unknown[]): unknown;
|
||||
|
||||
onRecentContactListChanged(...args: unknown[]): unknown;
|
||||
|
||||
onRecentContactListChangedVer2(...args: unknown[]): unknown;
|
||||
}
|
||||
|
||||
export interface NodeIKernelRecentContactListener extends IKernelRecentContactListener {
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-new
|
||||
new(listener: IKernelRecentContactListener): NodeIKernelRecentContactListener;
|
||||
}
|
||||
|
||||
export class KernelRecentContactListener implements IKernelRecentContactListener {
|
||||
export class NodeIKernelRecentContactListener {
|
||||
onDeletedContactsNotify(...args: unknown[]) {
|
||||
|
||||
}
|
||||
|
||||
onRecentContactNotification(...args: unknown[]) {
|
||||
onRecentContactNotification(msgList: any, arg0: { msgListUnreadCnt: string }, arg1: number) {
|
||||
|
||||
}
|
||||
|
||||
|
@@ -1,17 +1,4 @@
|
||||
export interface IKernelRobotListener {
|
||||
onRobotFriendListChanged(...args: unknown[]): void;
|
||||
|
||||
onRobotListChanged(...args: unknown[]): void;
|
||||
|
||||
onRobotProfileChanged(...args: unknown[]): void;
|
||||
}
|
||||
|
||||
export interface NodeIKernelRobotListener extends IKernelRobotListener {
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-new
|
||||
new(adapter: IKernelRobotListener): NodeIKernelRobotListener;
|
||||
}
|
||||
|
||||
export class KernelRobotListener implements IKernelRobotListener {
|
||||
export class NodeIKernelRobotListener {
|
||||
onRobotFriendListChanged(...args: unknown[]) {
|
||||
|
||||
}
|
||||
|
97
src/core/listeners/NodeIKernelSearchListener.ts
Normal file
97
src/core/listeners/NodeIKernelSearchListener.ts
Normal file
@@ -0,0 +1,97 @@
|
||||
import { ChatType } from '@/core';
|
||||
export interface SearchGroupInfo {
|
||||
groupCode: string;
|
||||
ownerUid: string;
|
||||
groupFlag: number;
|
||||
groupFlagExt: number;
|
||||
maxMemberNum: number;
|
||||
memberNum: number;
|
||||
groupOption: number;
|
||||
classExt: number;
|
||||
groupName: string;
|
||||
fingerMemo: string;
|
||||
groupQuestion: string;
|
||||
certType: number;
|
||||
shutUpAllTimestamp: number;
|
||||
shutUpMeTimestamp: number;
|
||||
groupTypeFlag: number;
|
||||
privilegeFlag: number;
|
||||
groupSecLevel: number;
|
||||
groupFlagExt3: number;
|
||||
isConfGroup: number;
|
||||
isModifyConfGroupFace: number;
|
||||
isModifyConfGroupName: number;
|
||||
noFigerOpenFlag: number;
|
||||
noCodeFingerOpenFlag: number;
|
||||
groupFlagExt4: number;
|
||||
groupMemo: string;
|
||||
cmdUinMsgSeq: number;
|
||||
cmdUinJoinTime: number;
|
||||
cmdUinUinFlag: number;
|
||||
cmdUinMsgMask: number;
|
||||
groupSecLevelInfo: number;
|
||||
cmdUinPrivilege: number;
|
||||
cmdUinFlagEx2: number;
|
||||
appealDeadline: number;
|
||||
remarkName: string;
|
||||
isTop: boolean;
|
||||
richFingerMemo: string;
|
||||
groupAnswer: string;
|
||||
joinGroupAuth: string;
|
||||
isAllowModifyConfGroupName: number;
|
||||
}
|
||||
|
||||
export interface GroupInfo {
|
||||
groupCode: string;
|
||||
searchGroupInfo: SearchGroupInfo;
|
||||
privilege: number;
|
||||
}
|
||||
|
||||
export interface GroupSearchResult {
|
||||
keyWord: string;
|
||||
errorCode: number;
|
||||
groupInfos: GroupInfo[];
|
||||
penetrate: string;
|
||||
isEnd: boolean;
|
||||
nextPos: number;
|
||||
}
|
||||
export interface NodeIKernelSearchListener {
|
||||
|
||||
onSearchGroupResult(params: GroupSearchResult): void;
|
||||
|
||||
onSearchFileKeywordsResult(params: {
|
||||
searchId: string,
|
||||
hasMore: boolean,
|
||||
resultItems: {
|
||||
chatType: ChatType,
|
||||
buddyChatInfo: any[],
|
||||
discussChatInfo: any[],
|
||||
groupChatInfo: {
|
||||
groupCode: string,
|
||||
isConf: boolean,
|
||||
hasModifyConfGroupFace: boolean,
|
||||
hasModifyConfGroupName: boolean,
|
||||
groupName: string,
|
||||
remark: string
|
||||
}[],
|
||||
dataLineChatInfo: any[],
|
||||
tmpChatInfo: any[],
|
||||
msgId: string,
|
||||
msgSeq: string,
|
||||
msgTime: string,
|
||||
senderUid: string,
|
||||
senderNick: string,
|
||||
senderRemark: string,
|
||||
senderCard: string,
|
||||
elemId: string,
|
||||
elemType: number,
|
||||
fileSize: string,
|
||||
filePath: string,
|
||||
fileName: string,
|
||||
hits: {
|
||||
start: number,
|
||||
end: number
|
||||
}[]
|
||||
}[]
|
||||
}): void;
|
||||
}
|
@@ -1,23 +1,4 @@
|
||||
export interface ISessionListener {
|
||||
onNTSessionCreate(args: unknown): void;
|
||||
|
||||
onGProSessionCreate(args: unknown): void;
|
||||
|
||||
onSessionInitComplete(args: unknown): void;
|
||||
|
||||
onOpentelemetryInit(args: unknown): void;
|
||||
|
||||
onUserOnlineResult(args: unknown): void;
|
||||
|
||||
onGetSelfTinyId(args: unknown): void;
|
||||
}
|
||||
|
||||
export interface NodeIKernelSessionListener extends ISessionListener {
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-new
|
||||
new(adapter: ISessionListener): NodeIKernelSessionListener;
|
||||
}
|
||||
|
||||
export class SessionListener implements ISessionListener {
|
||||
export class NodeIKernelSessionListener {
|
||||
onNTSessionCreate(args: unknown) {
|
||||
|
||||
}
|
||||
|
@@ -1,22 +1,4 @@
|
||||
export interface IStorageCleanListener {
|
||||
onCleanCacheProgressChanged(args: unknown): void;
|
||||
|
||||
onScanCacheProgressChanged(args: unknown): void;
|
||||
|
||||
onCleanCacheStorageChanged(args: unknown): void;
|
||||
|
||||
onFinishScan(args: unknown): void;
|
||||
|
||||
onChatCleanDone(args: unknown): void;
|
||||
|
||||
}
|
||||
|
||||
export interface NodeIKernelStorageCleanListener extends IStorageCleanListener {
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-new
|
||||
new(adapter: IStorageCleanListener): NodeIKernelStorageCleanListener;
|
||||
}
|
||||
|
||||
export class StorageCleanListener implements IStorageCleanListener {
|
||||
export class NodeIKernelStorageCleanListener {
|
||||
onCleanCacheProgressChanged(args: unknown) {
|
||||
|
||||
}
|
||||
|
@@ -1,10 +1,2 @@
|
||||
export interface IKernelTicketListener {
|
||||
}
|
||||
|
||||
export interface NodeIKernelTicketListener extends IKernelTicketListener {
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-new
|
||||
new(adapter: IKernelTicketListener): NodeIKernelTicketListener;
|
||||
}
|
||||
|
||||
export class KernelTicketListener implements IKernelTicketListener {
|
||||
export class NodeIKernelTicketListener {
|
||||
}
|
||||
|
@@ -9,4 +9,32 @@ export * from './NodeIKernelProfileListener';
|
||||
export * from './NodeIKernelTicketListener';
|
||||
export * from './NodeIKernelStorageCleanListener';
|
||||
export * from './NodeIKernelFileAssistantListener';
|
||||
export * from './NodeIKernelSearchListener';
|
||||
|
||||
import type {
|
||||
NodeIKernelBuddyListener,
|
||||
NodeIKernelFileAssistantListener,
|
||||
NodeIKernelGroupListener,
|
||||
NodeIKernelLoginListener,
|
||||
NodeIKernelMsgListener,
|
||||
NodeIKernelProfileListener,
|
||||
NodeIKernelRobotListener,
|
||||
NodeIKernelSessionListener,
|
||||
NodeIKernelStorageCleanListener,
|
||||
NodeIKernelTicketListener,
|
||||
} from '.';
|
||||
import { NodeIKernelSearchListener } from './NodeIKernelSearchListener';
|
||||
|
||||
export type ListenerNamingMapping = {
|
||||
NodeIKernelSessionListener: NodeIKernelSessionListener;
|
||||
NodeIKernelLoginListener: NodeIKernelLoginListener;
|
||||
NodeIKernelMsgListener: NodeIKernelMsgListener;
|
||||
NodeIKernelGroupListener: NodeIKernelGroupListener;
|
||||
NodeIKernelBuddyListener: NodeIKernelBuddyListener;
|
||||
NodeIKernelProfileListener: NodeIKernelProfileListener;
|
||||
NodeIKernelRobotListener: NodeIKernelRobotListener;
|
||||
NodeIKernelTicketListener: NodeIKernelTicketListener;
|
||||
NodeIKernelStorageCleanListener: NodeIKernelStorageCleanListener;
|
||||
NodeIKernelFileAssistantListener: NodeIKernelFileAssistantListener;
|
||||
NodeIKernelSearchListener: NodeIKernelSearchListener;
|
||||
};
|
||||
|
95
src/core/proto/ProfileLike.ts
Normal file
95
src/core/proto/ProfileLike.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
import * as pb from 'protobufjs';
|
||||
|
||||
// Proto: from src/core/proto/ProfileLike.proto
|
||||
// Author: Mlikiowa
|
||||
|
||||
export interface LikeDetailType {
|
||||
txt: string;
|
||||
uin: pb.Long;
|
||||
nickname: string;
|
||||
}
|
||||
export interface LikeMsgType {
|
||||
times: number;
|
||||
time: number;
|
||||
detail: LikeDetailType;
|
||||
}
|
||||
|
||||
export interface profileLikeSubTipType {
|
||||
msg: LikeMsgType;
|
||||
}
|
||||
|
||||
export interface ProfileLikeTipType {
|
||||
msgType: number;
|
||||
subType: number;
|
||||
content: profileLikeSubTipType;
|
||||
}
|
||||
export interface SysMessageHeaderType {
|
||||
id: string;
|
||||
timestamp: number;
|
||||
sender: string;
|
||||
}
|
||||
|
||||
export interface SysMessageMsgSpecType {
|
||||
msgType: number;
|
||||
subType: number;
|
||||
subSubType: number;
|
||||
msgSeq: number;
|
||||
time: number;
|
||||
msgId: pb.Long;
|
||||
other: number;
|
||||
}
|
||||
export interface SysMessageBodyWrapperType {
|
||||
wrappedBody: Uint8Array;
|
||||
}
|
||||
export interface SysMessageType {
|
||||
header: SysMessageHeaderType[];
|
||||
msgSpec: SysMessageMsgSpecType[];
|
||||
bodyWrapper: SysMessageBodyWrapperType;
|
||||
}
|
||||
|
||||
export const SysMessageHeader = new pb.Type("SysMessageHeader")
|
||||
.add(new pb.Field("PeerNumber", 1, "uint32"))
|
||||
.add(new pb.Field("PeerString", 2, "string"))
|
||||
.add(new pb.Field("Uin", 5, "uint32"))
|
||||
.add(new pb.Field("Uid", 6, "string", "optional"));
|
||||
|
||||
export const SysMessageMsgSpec = new pb.Type("SysMessageMsgSpec")
|
||||
.add(new pb.Field("msgType", 1, "uint32"))
|
||||
.add(new pb.Field("subType", 2, "uint32"))
|
||||
.add(new pb.Field("subSubType", 3, "uint32"))
|
||||
.add(new pb.Field("msgSeq", 5, "uint32"))
|
||||
.add(new pb.Field("time", 6, "uint32"))
|
||||
.add(new pb.Field("msgId", 12, "uint64"))
|
||||
.add(new pb.Field("other", 13, "uint32"));
|
||||
|
||||
export const SysMessageBodyWrapper = new pb.Type("SysMessageBodyWrapper")
|
||||
.add(new pb.Field("wrappedBody", 2, "bytes"));
|
||||
|
||||
export const SysMessage = new pb.Type("SysMessage")
|
||||
.add(SysMessageHeader)
|
||||
.add(SysMessageMsgSpec)
|
||||
.add(SysMessageBodyWrapper)
|
||||
.add(new pb.Field("header", 1, "SysMessageHeader", "repeated"))
|
||||
.add(new pb.Field("msgSpec", 2, "SysMessageMsgSpec", "repeated"))
|
||||
.add(new pb.Field("bodyWrapper", 3, "SysMessageBodyWrapper"));
|
||||
|
||||
export const likeDetail = new pb.Type("likeDetail")
|
||||
.add(new pb.Field("txt", 1, "string"))
|
||||
.add(new pb.Field("uin", 3, "int64"))
|
||||
.add(new pb.Field("nickname", 5, "string"));
|
||||
|
||||
export const likeMsg = new pb.Type("likeMsg")
|
||||
.add(likeDetail)
|
||||
.add(new pb.Field("times", 1, "int32"))
|
||||
.add(new pb.Field("time", 2, "int32"))
|
||||
.add(new pb.Field("detail", 3, "likeDetail"));
|
||||
|
||||
export const profileLikeSubTip = new pb.Type("profileLikeSubTip")
|
||||
.add(likeMsg)
|
||||
.add(new pb.Field("msg", 14, "likeMsg"))
|
||||
|
||||
export const profileLikeTip = new pb.Type("profileLikeTip")
|
||||
.add(profileLikeSubTip)
|
||||
.add(new pb.Field("msgType", 1, "int32"))
|
||||
.add(new pb.Field("subType", 2, "int32"))
|
||||
.add(new pb.Field("content", 203, "profileLikeSubTip"));
|
@@ -1,4 +1,5 @@
|
||||
export interface NodeIKernelAlbumService {
|
||||
|
||||
setAlbumServiceInfo(...args: any[]): unknown;// needs 3 arguments
|
||||
|
||||
getMainPage(...args: any[]): unknown;// needs 2 arguments
|
||||
|
@@ -1,7 +1,7 @@
|
||||
export interface NodeIKernelAvatarService {
|
||||
addAvatarListener(arg: unknown): unknown;
|
||||
addAvatarListener(listener: unknown): void;
|
||||
|
||||
removeAvatarListener(arg: unknown): unknown;
|
||||
removeAvatarListener(listenerId: number): void;
|
||||
|
||||
getAvatarPath(arg1: unknown, arg2: unknown): unknown;
|
||||
|
||||
|
@@ -1,13 +1,8 @@
|
||||
import { GeneralCallResult } from '@/core/services/common';
|
||||
import { NodeIKernelBuddyListener } from '@/core/listeners';
|
||||
|
||||
export enum BuddyListReqType {
|
||||
KNOMAL,
|
||||
KLETTER
|
||||
}
|
||||
import { BuddyListReqType } from '../entities/user';
|
||||
|
||||
export interface NodeIKernelBuddyService {
|
||||
// 26702 以上
|
||||
getBuddyListV2(callFrom: string, reqType: BuddyListReqType): Promise<GeneralCallResult & {
|
||||
data: Array<{
|
||||
categoryId: number,
|
||||
@@ -19,8 +14,7 @@ export interface NodeIKernelBuddyService {
|
||||
}>
|
||||
}>;
|
||||
|
||||
//26702 以上
|
||||
getBuddyListFromCache(callFrom: string): Promise<Array<
|
||||
getBuddyListFromCache(reqType: BuddyListReqType): Promise<Array<
|
||||
{
|
||||
categoryId: number,//9999应该跳过 那是兜底数据吧
|
||||
categorySortId: number,//排序方式
|
||||
@@ -30,18 +24,13 @@ export interface NodeIKernelBuddyService {
|
||||
buddyUids: Array<string>//Uids
|
||||
}>>;
|
||||
|
||||
// 以下为原生方法
|
||||
addKernelBuddyListener(listener: NodeIKernelBuddyListener): number;
|
||||
|
||||
getAllBuddyCount(): number;
|
||||
|
||||
removeKernelBuddyListener(listener: unknown): void;
|
||||
removeKernelBuddyListener(listenerId: number): void;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @param nocache 使用缓存
|
||||
*/
|
||||
getBuddyList(nocache: boolean): Promise<GeneralCallResult>;
|
||||
//getBuddyList(nocache: boolean): Promise<GeneralCallResult>;
|
||||
|
||||
getBuddyNick(uid: number): string;
|
||||
|
||||
@@ -61,11 +50,11 @@ export interface NodeIKernelBuddyService {
|
||||
|
||||
getBuddyReqUnreadCnt(): number;
|
||||
|
||||
getBuddyReq(): unknown;
|
||||
getBuddyReq(): Promise<GeneralCallResult>;
|
||||
|
||||
delBuddyReq(uid: number): void;
|
||||
|
||||
clearBuddyReqUnreadCnt(): void;
|
||||
clearBuddyReqUnreadCnt(): Promise<GeneralCallResult>;
|
||||
|
||||
reqToAddFriends(uid: number, msg: string): void;
|
||||
|
||||
@@ -125,7 +114,7 @@ export interface NodeIKernelBuddyService {
|
||||
|
||||
reportDoubtBuddyReqUnread(): void;
|
||||
|
||||
getBuddyRecommendContactArkJson(uid: string, phoneNumber: string): Promise<unknown>;
|
||||
getBuddyRecommendContactArkJson(uid: string, phoneNumber: string): Promise<GeneralCallResult & { arkMsg: string }>;
|
||||
|
||||
isNull(): boolean;
|
||||
}
|
||||
|
@@ -1,9 +1,9 @@
|
||||
import { GeneralCallResult } from './common';
|
||||
|
||||
export interface NodeIKernelCollectionService {
|
||||
addKernelCollectionListener(...args: any[]): unknown;//needs 1 arguments
|
||||
addKernelCollectionListener(...args: any[]): void;//needs 1 arguments
|
||||
|
||||
removeKernelCollectionListener(...args: any[]): unknown;//needs 1 arguments
|
||||
removeKernelCollectionListener(listenerId: number): void;
|
||||
|
||||
getCollectionItemList(param: {
|
||||
category: number,
|
||||
@@ -14,46 +14,46 @@ export interface NodeIKernelCollectionService {
|
||||
count: number,
|
||||
searchDown: boolean
|
||||
}): Promise<GeneralCallResult &
|
||||
{
|
||||
collectionSearchList: {
|
||||
collectionItemList: Array<
|
||||
{
|
||||
cid: string,
|
||||
{
|
||||
collectionSearchList: {
|
||||
collectionItemList: Array<
|
||||
{
|
||||
cid: string,
|
||||
type: number,
|
||||
status: number,
|
||||
author: {
|
||||
type: number,
|
||||
status: number,
|
||||
author: {
|
||||
type: number,
|
||||
numId: string,
|
||||
strId: string,
|
||||
groupId: string,
|
||||
groupName: string,
|
||||
uid: string
|
||||
},
|
||||
bid: number,
|
||||
category: number,
|
||||
createTime: string,
|
||||
collectTime: string,
|
||||
modifyTime: string,
|
||||
sequence: string,
|
||||
shareUrl: string,
|
||||
customGroupId: number,
|
||||
securityBeat: boolean,
|
||||
summary: {
|
||||
textSummary: unknown,
|
||||
linkSummary: unknown,
|
||||
gallerySummary: unknown,
|
||||
audioSummary: unknown,
|
||||
videoSummary: unknown,
|
||||
fileSummary: unknown,
|
||||
locationSummary: unknown,
|
||||
richMediaSummary: unknown,
|
||||
}
|
||||
}>,
|
||||
hasMore: boolean,
|
||||
bottomTimeStamp: string
|
||||
}
|
||||
numId: string,
|
||||
strId: string,
|
||||
groupId: string,
|
||||
groupName: string,
|
||||
uid: string
|
||||
},
|
||||
bid: number,
|
||||
category: number,
|
||||
createTime: string,
|
||||
collectTime: string,
|
||||
modifyTime: string,
|
||||
sequence: string,
|
||||
shareUrl: string,
|
||||
customGroupId: number,
|
||||
securityBeat: boolean,
|
||||
summary: {
|
||||
textSummary: unknown,
|
||||
linkSummary: unknown,
|
||||
gallerySummary: unknown,
|
||||
audioSummary: unknown,
|
||||
videoSummary: unknown,
|
||||
fileSummary: unknown,
|
||||
locationSummary: unknown,
|
||||
richMediaSummary: unknown,
|
||||
}
|
||||
}>,
|
||||
hasMore: boolean,
|
||||
bottomTimeStamp: string
|
||||
}
|
||||
>;//needs 1 arguments
|
||||
}
|
||||
>;
|
||||
|
||||
getCollectionContent(...args: any[]): unknown;//needs 5 arguments
|
||||
|
||||
|
@@ -1,7 +1,9 @@
|
||||
export interface NodeIKernelDbToolsService {
|
||||
|
||||
depositDatabase(...args: unknown[]): unknown;
|
||||
|
||||
backupDatabase(...args: unknown[]): unknown;
|
||||
|
||||
retrieveDatabase(...args: unknown[]): unknown;
|
||||
|
||||
}
|
||||
|
@@ -1,3 +1,2 @@
|
||||
export interface NodeIKernelECDHService {
|
||||
|
||||
}
|
||||
|
@@ -1,5 +1,7 @@
|
||||
import { NodeIKernelFileAssistantListener } from '@/core';
|
||||
|
||||
export interface NodeIKernelFileAssistantService {
|
||||
addKernelFileAssistantListener(arg1: unknown[]): unknown;
|
||||
addKernelFileAssistantListener(listener: NodeIKernelFileAssistantListener): unknown;
|
||||
|
||||
removeKernelFileAssistantListener(arg1: unknown[]): unknown;
|
||||
|
||||
@@ -9,7 +11,7 @@ export interface NodeIKernelFileAssistantService {
|
||||
|
||||
getFileSessionList(): unknown;
|
||||
|
||||
searchFile(arg1: unknown, arg2: unknown, arg3: unknown): unknown;
|
||||
searchFile(keywords: string[], params: { resultType: number, pageLimit: number }, resultId: number): number;
|
||||
|
||||
resetSearchFileSortType(arg1: unknown, arg2: unknown, arg3: unknown): unknown;
|
||||
|
||||
@@ -17,7 +19,7 @@ export interface NodeIKernelFileAssistantService {
|
||||
|
||||
cancelSearchFile(arg1: unknown, arg2: unknown, arg3: unknown): unknown;
|
||||
|
||||
downloadFile(arg1: unknown[]): unknown;
|
||||
downloadFile(fileIds: string[]): { result: number, errMsg: string };
|
||||
|
||||
forwardFile(arg1: unknown, arg2: unknown, arg3: unknown): unknown;
|
||||
|
||||
@@ -32,4 +34,4 @@ export interface NodeIKernelFileAssistantService {
|
||||
saveAsWithRename(arg1: unknown, arg2: unknown, arg3: unknown): unknown;
|
||||
|
||||
isNull(): boolean;
|
||||
}
|
||||
}
|
||||
|
@@ -1,16 +1,24 @@
|
||||
import { NodeIKernelGroupListener } from '@/core/listeners/NodeIKernelGroupListener';
|
||||
import {
|
||||
GroupExt0xEF0InfoFilter,
|
||||
GroupExtParam,
|
||||
GroupInfoSource,
|
||||
GroupMember,
|
||||
GroupMemberRole,
|
||||
GroupNotifyTypes,
|
||||
GroupNotifyMsgType,
|
||||
GroupRequestOperateTypes,
|
||||
KickMemberV2Req,
|
||||
} from '@/core/entities';
|
||||
import { GeneralCallResult } from '@/core/services/common';
|
||||
|
||||
//高版本的接口不应该随意使用 使用应该严格进行pr审核 同时部分ipc中未出现的接口不要过于依赖 应该做好数据兜底
|
||||
|
||||
export interface NodeIKernelGroupService {
|
||||
getGroupExt0xEF0Info(enableGroupCodes: string[], bannedGroupCodes: string[], filter: GroupExt0xEF0InfoFilter, forceFetch: boolean):
|
||||
Promise<GeneralCallResult & { result: { groupExtInfos: Map<string, any> } }>;
|
||||
|
||||
kickMemberV2(param: KickMemberV2Req): Promise<GeneralCallResult>;
|
||||
|
||||
quitGroupV2(param: { groupCode: string; needDeleteLocalMsg: boolean; }): Promise<GeneralCallResult>;
|
||||
|
||||
getMemberCommonInfo(Req: {
|
||||
groupCode: string,
|
||||
startUin: string,
|
||||
@@ -30,11 +38,11 @@ export interface NodeIKernelGroupService {
|
||||
realSpecialTitleFlag: number
|
||||
}): Promise<unknown>;
|
||||
|
||||
//26702
|
||||
getGroupMemberLevelInfo(groupCode: string): Promise<unknown>;
|
||||
|
||||
//26702
|
||||
getGroupHonorList(groupCodes: Array<string>): unknown;
|
||||
getGroupInfoForJoinGroup(groupCode: string, needPrivilegeFlag: boolean, serviceType: number): Promise<unknown>;
|
||||
|
||||
getGroupHonorList(req: { groupCodes: Array<string> }): Promise<unknown>;
|
||||
|
||||
getUinByUids(uins: string[]): Promise<{
|
||||
errCode: number,
|
||||
@@ -48,13 +56,10 @@ export interface NodeIKernelGroupService {
|
||||
uids: Map<string, string>
|
||||
}>;
|
||||
|
||||
//26702(其实更早 但是我不知道)
|
||||
checkGroupMemberCache(arrayList: Array<string>): Promise<unknown>;
|
||||
|
||||
//26702(其实更早 但是我不知道)
|
||||
getGroupLatestEssenceList(groupCode: string): Promise<unknown>;
|
||||
|
||||
//26702(其实更早 但是我不知道)
|
||||
shareDigest(Req: {
|
||||
appId: string,
|
||||
appType: number,
|
||||
@@ -74,20 +79,17 @@ export interface NodeIKernelGroupService {
|
||||
}
|
||||
}): Promise<unknown>;
|
||||
|
||||
//26702(其实更早 但是我不知道)
|
||||
isEssenceMsg(Req: { groupCode: string, msgRandom: number, msgSeq: number }): Promise<unknown>;
|
||||
|
||||
//26702(其实更早 但是我不知道)
|
||||
queryCachedEssenceMsg(Req: { groupCode: string, msgRandom: number, msgSeq: number }): Promise<unknown>;
|
||||
isEssenceMsg(req: { groupCode: string, msgRandom: number, msgSeq: number }): Promise<unknown>;
|
||||
|
||||
//26702(其实更早 但是我不知道)
|
||||
fetchGroupEssenceList(Req: {
|
||||
queryCachedEssenceMsg(req: { groupCode: string, msgRandom: number, msgSeq: number }): Promise<unknown>;
|
||||
|
||||
fetchGroupEssenceList(req: {
|
||||
groupCode: string,
|
||||
pageStart: number,
|
||||
pageLimit: number
|
||||
}, Arg: unknown): Promise<unknown>;
|
||||
|
||||
//26702
|
||||
getAllMemberList(groupCode: string, forceFetch: boolean): Promise<{
|
||||
errCode: number,
|
||||
errMsg: string,
|
||||
@@ -96,7 +98,7 @@ export interface NodeIKernelGroupService {
|
||||
uid: string,
|
||||
index: number//0
|
||||
}>,
|
||||
infos: {},
|
||||
infos: unknown,
|
||||
finish: true,
|
||||
hasRobot: false
|
||||
}
|
||||
@@ -106,13 +108,12 @@ export interface NodeIKernelGroupService {
|
||||
|
||||
addKernelGroupListener(listener: NodeIKernelGroupListener): number;
|
||||
|
||||
removeKernelGroupListener(listenerId: unknown): void;
|
||||
removeKernelGroupListener(listenerId: number): void;
|
||||
|
||||
createMemberListScene(groupCode: string, scene: string): string;
|
||||
|
||||
destroyMemberListScene(SceneId: string): void;
|
||||
|
||||
//About Arg (a) name: lastId 根据手Q来看为object {index:?(number),uid:string}
|
||||
getNextMemberList(sceneId: string, a: undefined, num: number): Promise<{
|
||||
errCode: number, errMsg: string,
|
||||
result: { ids: string[], infos: Map<string, GroupMember>, finish: boolean, hasRobot: boolean }
|
||||
@@ -126,8 +127,6 @@ export interface NodeIKernelGroupService {
|
||||
|
||||
getMemberInfo(group_id: string, uids: string[], forceFetch: boolean): Promise<GeneralCallResult>;
|
||||
|
||||
//getMemberInfo [ '56729xxxx', [ 'u_4Nj08cwW5Hxxxxx' ], true ]
|
||||
|
||||
kickMember(groupCode: string, memberUids: string[], refuseForever: boolean, kickReason: string): Promise<void>;
|
||||
|
||||
modifyMemberRole(groupCode: string, uid: string, role: GroupMemberRole): void;
|
||||
@@ -142,7 +141,7 @@ export interface NodeIKernelGroupService {
|
||||
|
||||
getGroupExtList(force: boolean): Promise<GeneralCallResult>;
|
||||
|
||||
getGroupDetailInfo(groupCode: string): unknown;
|
||||
getGroupDetailInfo(groupCode: string, groupInfoSource: GroupInfoSource): Promise<unknown>;
|
||||
|
||||
getMemberExtInfo(param: GroupExtParam): Promise<unknown>;//req
|
||||
|
||||
@@ -180,14 +179,13 @@ export interface NodeIKernelGroupService {
|
||||
|
||||
destroyGroup(groupCode: string): void;
|
||||
|
||||
//获取单屏群通知列表
|
||||
getSingleScreenNotifies(force: boolean, start_seq: string, num: number): Promise<GeneralCallResult>;
|
||||
getSingleScreenNotifies(doubted: boolean, start_seq: string, num: number): Promise<GeneralCallResult>;
|
||||
|
||||
clearGroupNotifies(groupCode: string): void;
|
||||
|
||||
getGroupNotifiesUnreadCount(unknown: boolean): Promise<GeneralCallResult>;
|
||||
|
||||
clearGroupNotifiesUnreadCount(groupCode: string): void;
|
||||
clearGroupNotifiesUnreadCount(unknown: boolean): void;
|
||||
|
||||
operateSysNotify(
|
||||
doubt: boolean,
|
||||
@@ -195,7 +193,7 @@ export interface NodeIKernelGroupService {
|
||||
operateType: GroupRequestOperateTypes, // 2 拒绝
|
||||
targetMsg: {
|
||||
seq: string, // 通知序列号
|
||||
type: GroupNotifyTypes,
|
||||
type: GroupNotifyMsgType,
|
||||
groupCode: string,
|
||||
postscript: string
|
||||
}
|
||||
@@ -205,7 +203,7 @@ export interface NodeIKernelGroupService {
|
||||
|
||||
getGroupBulletin(groupCode: string): unknown;
|
||||
|
||||
deleteGroupBulletin(groupCode: string, seq: string): void;
|
||||
deleteGroupBulletin(groupCode: string, seq: string, noticeId: string): void;
|
||||
|
||||
publishGroupBulletin(groupCode: string, pskey: string, data: any): Promise<GeneralCallResult>;
|
||||
|
||||
@@ -246,14 +244,12 @@ export interface NodeIKernelGroupService {
|
||||
|
||||
modifyGroupExtInfo(groupCode: string, arg: unknown): void;
|
||||
|
||||
//需要提前判断是否存在 高版本新增
|
||||
addGroupEssence(param: {
|
||||
groupCode: string
|
||||
msgRandom: number,
|
||||
msgSeq: number
|
||||
}): Promise<unknown>;
|
||||
|
||||
//需要提前判断是否存在 高版本新增
|
||||
removeGroupEssence(param: {
|
||||
groupCode: string
|
||||
msgRandom: number,
|
||||
|
@@ -9,7 +9,7 @@ export interface LoginInitConfig {
|
||||
hostName: string;
|
||||
}
|
||||
|
||||
export interface passwordLoginRetType {
|
||||
export interface PasswordLoginRetType {
|
||||
result: string,
|
||||
loginErrorInfo: {
|
||||
step: number;
|
||||
@@ -23,7 +23,7 @@ export interface passwordLoginRetType {
|
||||
}
|
||||
}
|
||||
|
||||
export interface passwordLoginArgType {
|
||||
export interface PasswordLoginArgType {
|
||||
uin: string;
|
||||
passwordMd5: string;//passwMD5
|
||||
step: number;//猜测是需要二次认证 参数 一次为0
|
||||
@@ -59,8 +59,9 @@ export interface QuickLoginResult {
|
||||
}
|
||||
|
||||
export interface NodeIKernelLoginService {
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-new
|
||||
new(): NodeIKernelLoginService;
|
||||
get(): NodeIKernelLoginService;
|
||||
|
||||
connect(): boolean;
|
||||
|
||||
addKernelLoginListener(listener: NodeIKernelLoginListener): number;
|
||||
|
||||
@@ -77,7 +78,7 @@ export interface NodeIKernelLoginService {
|
||||
|
||||
quickLoginWithUin(uin: string): Promise<QuickLoginResult>;
|
||||
|
||||
passwordLogin(param: passwordLoginArgType): Promise<any>;
|
||||
passwordLogin(param: PasswordLoginArgType): Promise<any>;
|
||||
|
||||
getQRCodePicture(): boolean;
|
||||
}
|
||||
|
@@ -1,7 +1,7 @@
|
||||
export interface NodeIKernelMsgBackupService {
|
||||
addKernelMsgBackupListener(...args: any[]): unknown;// needs 1 arguments
|
||||
addKernelMsgBackupListener(listener: unknown): number;
|
||||
|
||||
removeKernelMsgBackupListener(...args: any[]): unknown;// needs 1 arguments
|
||||
removeKernelMsgBackupListener(listenerId: number): void;
|
||||
|
||||
getMsgBackupLocation(...args: any[]): unknown;// needs 0 arguments
|
||||
|
||||
|
@@ -1,31 +1,7 @@
|
||||
import { ElementType, MessageElement, Peer, RawMessage, SendMessageElement } from '@/core/entities';
|
||||
import { NodeIKernelMsgListener } from '@/core/listeners/NodeIKernelMsgListener';
|
||||
import { GeneralCallResult } from '@/core/services/common';
|
||||
|
||||
export interface QueryMsgsParams {
|
||||
chatInfo: Peer,
|
||||
filterMsgType: [],
|
||||
filterSendersUid: string[],
|
||||
filterMsgFromTime: string,
|
||||
filterMsgToTime: string,
|
||||
pageLimit: number,
|
||||
isReverseOrder: boolean,
|
||||
isIncludeCurrent: boolean
|
||||
}
|
||||
export interface TmpChatInfoApi {
|
||||
errMsg: string;
|
||||
result: number;
|
||||
tmpChatInfo?: TmpChatInfo;
|
||||
}
|
||||
|
||||
export interface TmpChatInfo {
|
||||
chatType: number;
|
||||
fromNick: string;
|
||||
groupCode: string;
|
||||
peerUid: string;
|
||||
sessionType: number;
|
||||
sig: string;
|
||||
}
|
||||
import { MsgReqType, QueryMsgsParams, TmpChatInfoApi } from '../entities/msg';
|
||||
|
||||
export interface NodeIKernelMsgService {
|
||||
|
||||
@@ -37,7 +13,7 @@ export interface NodeIKernelMsgService {
|
||||
|
||||
recallMsg(peer: Peer, msgIds: string[]): Promise<GeneralCallResult>;
|
||||
|
||||
addKernelMsgImportToolListener(arg: Object): unknown;
|
||||
addKernelMsgImportToolListener(arg: unknown): unknown;
|
||||
|
||||
removeKernelMsgListener(args: unknown): unknown;
|
||||
|
||||
@@ -51,7 +27,7 @@ export interface NodeIKernelMsgService {
|
||||
|
||||
getOnLineDev(): void;
|
||||
|
||||
kickOffLine(DevInfo: Object): unknown;
|
||||
kickOffLine(DevInfo: unknown): unknown;
|
||||
|
||||
setStatus(args: { status: number, extStatus: number, batteryStatus: number }): Promise<GeneralCallResult>;
|
||||
|
||||
@@ -75,18 +51,12 @@ export interface NodeIKernelMsgService {
|
||||
|
||||
downloadOnlineStatusCommonByUrl(arg0: string, arg1: string): unknown;
|
||||
|
||||
// this.tokenType = i2;
|
||||
// this.apnsToken = bArr;
|
||||
// this.voipToken = bArr2;
|
||||
// this.profileId = str;
|
||||
|
||||
setToken(arg: Object): unknown;
|
||||
setToken(arg: unknown): unknown;
|
||||
|
||||
switchForeGround(): unknown;
|
||||
|
||||
switchBackGround(arg: Object): unknown;
|
||||
switchBackGround(arg: unknown): unknown;
|
||||
|
||||
//hex
|
||||
setTokenForMqq(token: string): unknown;
|
||||
|
||||
switchForeGroundForMqq(...args: unknown[]): unknown;
|
||||
@@ -123,7 +93,6 @@ export interface NodeIKernelMsgService {
|
||||
|
||||
forwardFile(...args: unknown[]): unknown;
|
||||
|
||||
//Array<Msg>, Peer from, Peer to
|
||||
multiForwardMsg(...args: unknown[]): unknown;
|
||||
|
||||
multiForwardMsgWithComment(...args: unknown[]): unknown;
|
||||
@@ -166,23 +135,27 @@ export interface NodeIKernelMsgService {
|
||||
|
||||
getAllOnlineFileMsgs(...args: unknown[]): unknown;
|
||||
|
||||
getLatestDbMsgs(peer: Peer, cnt: number): Promise<unknown>;
|
||||
getLatestDbMsgs(peer: Peer, cnt: number): Promise<GeneralCallResult & {
|
||||
msgList: RawMessage[]
|
||||
}>;
|
||||
|
||||
getLastMessageList(peer: Peer[]): Promise<unknown>;
|
||||
getLastMessageList(peer: Peer[]): Promise<GeneralCallResult & {
|
||||
msgList: RawMessage[]
|
||||
}>;
|
||||
|
||||
getAioFirstViewLatestMsgs(peer: Peer, num: number): unknown;
|
||||
getAioFirstViewLatestMsgs(peer: Peer, num: number): Promise<GeneralCallResult & {
|
||||
msgList: RawMessage[]
|
||||
}>;
|
||||
|
||||
//@deprecated
|
||||
getMsgs(peer: Peer, msgId: string, count: unknown, queryOrder: boolean): Promise<unknown>;
|
||||
|
||||
//@deprecated
|
||||
getMsgsIncludeSelf(peer: Peer, msgId: string, count: number, queryOrder: boolean): Promise<GeneralCallResult & {
|
||||
msgList: RawMessage[]
|
||||
}>;
|
||||
|
||||
// this.$peer = contact;
|
||||
// this.$msgTime = j2;
|
||||
// this.$clientSeq = j3;
|
||||
// this.$cnt = i2;
|
||||
|
||||
//@deprecated
|
||||
getMsgsWithMsgTimeAndClientSeqForC2C(...args: unknown[]): Promise<GeneralCallResult & { msgList: RawMessage[] }>;
|
||||
|
||||
getMsgsWithStatus(params: {
|
||||
@@ -198,7 +171,7 @@ export interface NodeIKernelMsgService {
|
||||
getMsgsBySeqRange(peer: Peer, startSeq: string, endSeq: string): Promise<GeneralCallResult & {
|
||||
msgList: RawMessage[]
|
||||
}>;
|
||||
|
||||
//@deprecated
|
||||
getMsgsBySeqAndCount(peer: Peer, seq: string, count: number, desc: boolean, unknownArg: boolean): Promise<GeneralCallResult & {
|
||||
msgList: RawMessage[]
|
||||
}>;
|
||||
@@ -209,6 +182,8 @@ export interface NodeIKernelMsgService {
|
||||
|
||||
getMsgsBySeqList(peer: Peer, seqList: string[]): Promise<GeneralCallResult & { msgList: RawMessage[] }>;
|
||||
|
||||
getMsgsExt(msgReq: MsgReqType): Promise<GeneralCallResult & { msgList: RawMessage[] }>;
|
||||
|
||||
getSingleMsg(Peer: Peer, msgSeq: string): Promise<GeneralCallResult & { msgList: RawMessage[] }>;
|
||||
|
||||
getSourceOfReplyMsg(peer: Peer, MsgId: string, SourceSeq: string): unknown;
|
||||
@@ -219,7 +194,6 @@ export interface NodeIKernelMsgService {
|
||||
|
||||
getSourceOfReplyMsgByClientSeqAndTime(peer: Peer, clientSeq: string, time: string): unknown;
|
||||
|
||||
//cnt clientSeq?并不是吧
|
||||
getMsgsByTypeFilter(peer: Peer, msgId: string, cnt: unknown, queryOrder: boolean, typeFilter: {
|
||||
type: number,
|
||||
subtype: Array<number>
|
||||
@@ -234,49 +208,15 @@ export interface NodeIKernelMsgService {
|
||||
|
||||
queryMsgsWithFilter(...args: unknown[]): unknown;
|
||||
|
||||
/**
|
||||
* @deprecated 该函数已被标记为废弃,请使用新的替代方法。
|
||||
* 使用过滤条件查询消息列表的版本2接口。
|
||||
*
|
||||
* 该函数通过一系列过滤条件来查询特定聊天中的消息列表。这些条件包括消息类型、发送者、时间范围等。
|
||||
* 函数返回一个Promise,解析为查询结果的未知类型对象。
|
||||
*
|
||||
* @param MsgId 消息ID,用于特定消息的查询。
|
||||
* @param MsgTime 消息时间,用于指定消息的时间范围。
|
||||
* @param param 查询参数对象,包含详细的过滤条件和分页信息。
|
||||
* @param param.chatInfo 聊天信息,包括聊天类型和对方用户ID。
|
||||
* @param param.filterMsgType 需要过滤的消息类型数组,留空表示不过滤。
|
||||
* @param param.filterSendersUid 需要过滤的发送者用户ID数组。
|
||||
* @param param.filterMsgFromTime 查询消息的起始时间。
|
||||
* @param param.filterMsgToTime 查询消息的结束时间。
|
||||
* @param param.pageLimit 每页的消息数量限制。
|
||||
* @param param.isReverseOrder 是否按时间顺序倒序返回消息。
|
||||
* @param param.isIncludeCurrent 是否包含当前页码。
|
||||
* @returns 返回一个Promise,解析为查询结果的未知类型对象。
|
||||
*/
|
||||
queryMsgsWithFilterVer2(MsgId: string, MsgTime: string, param: QueryMsgsParams): Promise<unknown>;
|
||||
//queryMsgsWithFilterVer2(MsgId: string, MsgTime: string, param: QueryMsgsParams): Promise<unknown>;
|
||||
|
||||
// this.chatType = i2;
|
||||
// this.peerUid = str;
|
||||
|
||||
// this.chatInfo = new ChatInfo();
|
||||
// this.filterMsgType = new ArrayList<>();
|
||||
// this.filterSendersUid = new ArrayList<>();
|
||||
// this.chatInfo = chatInfo;
|
||||
// this.filterMsgType = arrayList;
|
||||
// this.filterSendersUid = arrayList2;
|
||||
// this.filterMsgFromTime = j2;
|
||||
// this.filterMsgToTime = j3;
|
||||
// this.pageLimit = i2;
|
||||
// this.isReverseOrder = z;
|
||||
// this.isIncludeCurrent = z2;
|
||||
//queryMsgsWithFilterEx(0L, 0L, 0L, new QueryMsgsParams(new ChatInfo(2, str), new ArrayList(), new ArrayList(), 0L, 0L, 250, false, true))
|
||||
queryMsgsWithFilterEx(msgId: string, msgTime: string, megSeq: string, param: QueryMsgsParams): Promise<GeneralCallResult & {
|
||||
msgList: RawMessage[]
|
||||
}>;
|
||||
|
||||
//queryMsgsWithFilterEx(this.$msgId, this.$msgTime, this.$msgSeq, this.$param)
|
||||
queryFileMsgsDesktop(...args: unknown[]): unknown;
|
||||
queryFileMsgsDesktop(msgId: string, msgTime: string, msgSeq: string, param: QueryMsgsParams): Promise<GeneralCallResult & {
|
||||
msgList: RawMessage[]
|
||||
}>;
|
||||
|
||||
setMsgRichInfoFlag(...args: unknown[]): unknown;
|
||||
|
||||
@@ -383,17 +323,11 @@ export interface NodeIKernelMsgService {
|
||||
|
||||
getFileThumbSavePath(...args: unknown[]): unknown;
|
||||
|
||||
//猜测居多
|
||||
translatePtt2Text(MsgId: string, Peer: {}, MsgElement: {}): unknown;
|
||||
translatePtt2Text(msgId: string, peer: Peer, msgElement: MessageElement): unknown;
|
||||
|
||||
setPttPlayedState(...args: unknown[]): unknown;
|
||||
|
||||
// NodeIQQNTWrapperSession fetchFavEmojiList [
|
||||
// "",
|
||||
// 48,
|
||||
// true,
|
||||
// true
|
||||
// ]
|
||||
//uk1 uk2 true
|
||||
fetchFavEmojiList(str: string, num: number, uk1: boolean, uk2: boolean): Promise<GeneralCallResult & {
|
||||
emojiInfoList: Array<{
|
||||
uin: string,
|
||||
@@ -462,7 +396,12 @@ export interface NodeIKernelMsgService {
|
||||
|
||||
getEmojiResourcePath(...args: unknown[]): unknown;
|
||||
|
||||
JoinDragonGroupEmoji(JoinDragonGroupEmojiReq: any/*joinDragonGroupEmojiReq*/): unknown;
|
||||
JoinDragonGroupEmoji(JoinDragonGroupEmojiReq: {
|
||||
latestMsgSeq: string,
|
||||
manageEmojiId: number,
|
||||
manageMsgSeq: string,
|
||||
peerContact: Peer
|
||||
}): Promise<unknown>;
|
||||
|
||||
getMsgAbstracts(...args: unknown[]): unknown;
|
||||
|
||||
@@ -489,7 +428,7 @@ export interface NodeIKernelMsgService {
|
||||
getFirstUnreadMsgSeq(args: {
|
||||
peerUid: string
|
||||
guildId: string
|
||||
}): unknown;
|
||||
}): Promise<unknown>;
|
||||
|
||||
getFirstUnreadCommonMsg(...args: unknown[]): unknown;
|
||||
|
||||
@@ -583,13 +522,13 @@ export interface NodeIKernelMsgService {
|
||||
|
||||
getFirstUnreadAtMsg(peer: Peer): unknown;
|
||||
|
||||
clearMsgRecords(...args: unknown[]): unknown;//设置已读后调用我觉得比较好 清理记录 现在别了
|
||||
clearMsgRecords(...args: unknown[]): unknown;
|
||||
|
||||
IsExistOldDb(...args: unknown[]): unknown;
|
||||
|
||||
canImportOldDbMsg(...args: unknown[]): unknown;
|
||||
|
||||
setPowerStatus(z: boolean): unknown;
|
||||
setPowerStatus(isPowerOn: boolean): unknown;
|
||||
|
||||
canProcessDataMigration(...args: unknown[]): unknown;
|
||||
|
||||
@@ -619,25 +558,10 @@ export interface NodeIKernelMsgService {
|
||||
|
||||
enterOrExitAio(...args: unknown[]): unknown;
|
||||
|
||||
// this.peerUid = "";
|
||||
// this.peerNickname = "";
|
||||
// this.fromGroupCode = "";
|
||||
// this.sig = new byte[0];
|
||||
// this.selfUid = "";
|
||||
// this.selfPhone = "";
|
||||
// this.chatType = i2;
|
||||
// this.peerUid = str;
|
||||
// this.peerNickname = str2;
|
||||
// this.fromGroupCode = str3;
|
||||
// this.sig = bArr;
|
||||
// this.selfUid = str4;
|
||||
// this.selfPhone = str5;
|
||||
// this.gameSession = tempChatGameSession;
|
||||
prepareTempChat(args: unknown): unknown;//主动临时消息 不做
|
||||
prepareTempChat(args: unknown): unknown;
|
||||
|
||||
sendSsoCmdReqByContend(cmd: string, param: string): Promise<unknown>;
|
||||
|
||||
//chattype,uid->Promise<any>
|
||||
getTempChatInfo(ChatType: number, Uid: string): Promise<TmpChatInfoApi>;
|
||||
|
||||
setContactLocalTop(...args: unknown[]): unknown;
|
||||
@@ -668,7 +592,7 @@ export interface NodeIKernelMsgService {
|
||||
|
||||
recordEmoji(...args: unknown[]): unknown;
|
||||
|
||||
fetchGetHitEmotionsByWord(args: Object): Promise<unknown>;//表情推荐?
|
||||
fetchGetHitEmotionsByWord(args: unknown): Promise<unknown>;//表情推荐?
|
||||
|
||||
deleteAllRoamMsgs(...args: unknown[]): unknown;//漫游消息?
|
||||
|
||||
@@ -693,7 +617,7 @@ export interface NodeIKernelMsgService {
|
||||
setIKernelPublicAccountAdapter(...args: unknown[]): unknown;
|
||||
|
||||
//tempChatGameSession有关
|
||||
createUidFromTinyId(fromTinyId: string, toTinyId: string): unknown;
|
||||
createUidFromTinyId(fromTinyId: string, toTinyId: string): string;
|
||||
|
||||
dataMigrationGetDataAvaiableContactList(...args: unknown[]): unknown;
|
||||
|
||||
@@ -701,7 +625,6 @@ export interface NodeIKernelMsgService {
|
||||
|
||||
dataMigrationStopOperation(...args: unknown[]): unknown;
|
||||
|
||||
//新的希望
|
||||
dataMigrationImportMsgPbRecord(DataMigrationMsgInfo: Array<{
|
||||
extensionData: string//"Hex"
|
||||
extraData: string //""
|
||||
@@ -753,6 +676,6 @@ export interface NodeIKernelMsgService {
|
||||
|
||||
getGuildMsgAbFlag(...args: unknown[]): unknown;
|
||||
|
||||
getGroupMsgStorageTime(): unknown;//这是嘛啊
|
||||
getGroupMsgStorageTime(): unknown;
|
||||
|
||||
}
|
||||
|
@@ -1,6 +1,5 @@
|
||||
import { GeneralCallResult } from './common';
|
||||
|
||||
//没扒干净 因为用不着
|
||||
export interface NodeIKernelNodeMiscService {
|
||||
getMiniAppPath(): unknown;
|
||||
|
||||
@@ -11,7 +10,4 @@ export interface NodeIKernelNodeMiscService {
|
||||
SendMiniAppMsg(arg1: string, arg2: string, arg3: string): unknown;
|
||||
|
||||
startNewMiniApp(appfile: string, params: string): unknown;
|
||||
|
||||
// 我的计划是转发给一个新程序避免吃掉Electron_AS_Node的环境 然后重写启动MiniApp 挂载相应JS脚本 这样有个问题
|
||||
// 需要自己转发ipc参数 然后必须处在gui环境 且完成校验破解 才能实现发包 有点抽象了
|
||||
}
|
||||
|
@@ -1,8 +1,8 @@
|
||||
export interface NodeIKernelOnlineStatusService {
|
||||
|
||||
addKernelOnlineStatusListener(listener: unknown): void;
|
||||
addKernelOnlineStatusListener(listener: unknown): number;
|
||||
|
||||
removeKernelOnlineStatusListener(listenerId: unknown): void;
|
||||
removeKernelOnlineStatusListener(listenerId: number): void;
|
||||
|
||||
getShouldShowAIOStatusAnimation(arg: unknown): unknown;
|
||||
|
||||
|
@@ -1,17 +1,17 @@
|
||||
import { BuddyProfileLikeReq, GeneralCallResult } from '@/core';
|
||||
|
||||
export interface NodeIKernelProfileLikeService {
|
||||
addKernelProfileLikeListener(listener: NodeIKernelProfileLikeService): void;
|
||||
addKernelProfileLikeListener(listener: unknown): number;
|
||||
|
||||
removeKernelProfileLikeListener(listener: unknown): void;
|
||||
removeKernelProfileLikeListener(listenerId: unknown): void;
|
||||
|
||||
setBuddyProfileLike(...args: unknown[]): { result: number, errMsg: string, succCounts: number };
|
||||
|
||||
getBuddyProfileLike(req: BuddyProfileLikeReq): Promise<GeneralCallResult & {
|
||||
'info': {
|
||||
'userLikeInfos': Array<any>,
|
||||
'friendMaxVotes': number,
|
||||
'start': number
|
||||
info: {
|
||||
userLikeInfos: Array<any>,
|
||||
friendMaxVotes: number,
|
||||
start: number
|
||||
}
|
||||
}>;
|
||||
|
||||
|
@@ -1,39 +1,21 @@
|
||||
import { AnyCnameRecord } from 'node:dns';
|
||||
import { BizKey, ModifyProfileParams, SimpleInfo, UserDetailInfoByUin } from '../entities';
|
||||
import { NodeIKernelProfileListener } from '../listeners';
|
||||
import { BizKey, ModifyProfileParams, NodeIKernelProfileListener, ProfileBizType, SimpleInfo, UserDetailInfoByUin, UserDetailSource } from '@/core';
|
||||
import { GeneralCallResult } from '@/core/services/common';
|
||||
|
||||
export enum UserDetailSource {
|
||||
KDB,
|
||||
KSERVER
|
||||
}
|
||||
|
||||
export enum ProfileBizType {
|
||||
KALL,
|
||||
KBASEEXTEND,
|
||||
KVAS,
|
||||
KQZONE,
|
||||
KOTHER
|
||||
}
|
||||
|
||||
export interface NodeIKernelProfileService {
|
||||
getOtherFlag(callfrom: string, uids: string[]): Promise<Map<string, any>>;
|
||||
|
||||
getVasInfo(callfrom: string, uids: string[]): Promise<Map<string, any>>;
|
||||
|
||||
getUidByUinV2(callfrom: string, uin: Array<string>): Promise<Map<string, string>>;//uin->uid
|
||||
getRelationFlag(callfrom: string, uids: string[]): Promise<Map<string, any>>;
|
||||
|
||||
getUidByUin(callfrom: string, uin: Array<string>): Promise<Map<string, string>>;
|
||||
|
||||
getUinByUid(callfrom: string, uid: Array<string>): Promise<Map<string, string>>;
|
||||
|
||||
// {
|
||||
// coreInfo: CoreInfo,
|
||||
// baseInfo: BaseInfo,
|
||||
// status: null,
|
||||
// vasInfo: null,
|
||||
// relationFlags: null,
|
||||
// otherFlags: null,
|
||||
// intimate: null
|
||||
// }
|
||||
getCoreAndBaseInfo(callfrom: string, uids: string[]): Promise<Map<string, SimpleInfo>>;
|
||||
|
||||
fetchUserDetailInfo(trace: string, uids: string[], arg2: number, arg3: number[]): Promise<unknown>;
|
||||
fetchUserDetailInfo(trace: string, uids: string[], source: UserDetailSource, bizType: ProfileBizType[]): Promise<GeneralCallResult>;
|
||||
|
||||
addKernelProfileListener(listener: NodeIKernelProfileListener): number;
|
||||
|
||||
@@ -45,30 +27,17 @@ export interface NodeIKernelProfileService {
|
||||
|
||||
enumCountryOptions(): Array<string>;
|
||||
|
||||
enumProvinceOptions(Country: string): Array<string>;
|
||||
enumProvinceOptions(country: string): Array<string>;
|
||||
|
||||
enumCityOptions(Country: string, Province: string): unknown;
|
||||
enumCityOptions(country: string, province: string): unknown;
|
||||
|
||||
enumAreaOptions(...args: unknown[]): unknown;
|
||||
|
||||
//SimpleInfo
|
||||
// this.uid = "";
|
||||
// this.uid = str;
|
||||
// this.uin = j2;
|
||||
// this.isBuddy = z;
|
||||
// this.coreInfo = coreInfo;
|
||||
// this.baseInfo = baseInfo;
|
||||
// this.status = statusInfo;
|
||||
// this.vasInfo = vasInfo;
|
||||
// this.relationFlags = relationFlag;
|
||||
// this.otherFlags = otherFlag;
|
||||
// this.intimate = intimate;
|
||||
|
||||
modifySelfProfile(...args: unknown[]): Promise<unknown>;
|
||||
|
||||
modifyDesktopMiniProfile(param: ModifyProfileParams): Promise<GeneralCallResult>;
|
||||
|
||||
setNickName(NickName: string): Promise<unknown>;
|
||||
setNickName(nickName: string): Promise<unknown>;
|
||||
|
||||
setLongNick(longNick: string): Promise<unknown>;
|
||||
|
||||
@@ -96,14 +65,12 @@ export interface NodeIKernelProfileService {
|
||||
|
||||
getSelfStatus(): Promise<unknown>;
|
||||
|
||||
//
|
||||
setdisableEmojiShortCuts(...args: unknown[]): unknown;
|
||||
|
||||
getProfileQzonePicInfo(uid: string, type: number, force: boolean): Promise<unknown>;
|
||||
|
||||
//profileService.getCoreInfo("UserRemarkServiceImpl::getStrangerRemarkByUid", arrayList);
|
||||
getCoreInfo(name: string, arg: any[]): unknown;
|
||||
// UserRemarkServiceImpl::getStrangerRemarkByUid []
|
||||
getCoreInfo(sceneId: string, arg: any[]): unknown;
|
||||
|
||||
//m429253e12.getOtherFlag("FriendListInfoCache_getKernelDataAndPutCache", new ArrayList<>());
|
||||
isNull(): boolean;
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user