mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2026-02-06 21:10:23 +00:00
Compare commits
888 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4dd7363dd3 | ||
|
|
3d5e5ab78f | ||
|
|
73045a1b21 | ||
|
|
871173a7cf | ||
|
|
0002313093 | ||
|
|
948cf5cca6 | ||
|
|
d40230879c | ||
|
|
ab22b775f1 | ||
|
|
42c85224ba | ||
|
|
e57444a353 | ||
|
|
3c6503d495 | ||
|
|
149b518f48 | ||
|
|
74621447ff | ||
|
|
3280952931 | ||
|
|
9e670e2736 | ||
|
|
9fc6347a2f | ||
|
|
ec7a15a192 | ||
|
|
7f99982810 | ||
|
|
935d83aaf8 | ||
|
|
0ff6edd546 | ||
|
|
94f629585a | ||
|
|
89c04be02f | ||
|
|
3151965ea8 | ||
|
|
bdf5159be1 | ||
|
|
0499ebbea3 | ||
|
|
d5843b7236 | ||
|
|
1c9c574a90 | ||
|
|
39acf20e48 | ||
|
|
52eb6ed5ab | ||
|
|
ee78d2d59d | ||
|
|
60dc5c4a38 | ||
|
|
50a0dc0355 | ||
|
|
3f681ec914 | ||
|
|
0bf499f191 | ||
|
|
389695a0d6 | ||
|
|
07f1afb312 | ||
|
|
ae91e61304 | ||
|
|
6248991b01 | ||
|
|
7f2d57ef62 | ||
|
|
31f8f884f1 | ||
|
|
4f4af5985a | ||
|
|
a716fdf6d4 | ||
|
|
9717f64abd | ||
|
|
adf239183a | ||
|
|
6cf209c79c | ||
|
|
decc5fb3c0 | ||
|
|
1e0820d613 | ||
|
|
70124d5177 | ||
|
|
269de65201 | ||
|
|
1d11abbfb6 | ||
|
|
700f308d6e | ||
|
|
21b6928ca6 | ||
|
|
998c67a649 | ||
|
|
fb99e878b0 | ||
|
|
1619adfc27 | ||
|
|
5510fb473f | ||
|
|
be1878cb2b | ||
|
|
15ab121cbd | ||
|
|
aa79b0e861 | ||
|
|
b80e550bcd | ||
|
|
dbc40b5814 | ||
|
|
0d5696a644 | ||
|
|
ceffa05802 | ||
|
|
d5668920b6 | ||
|
|
516f2da144 | ||
|
|
33c94e1888 | ||
|
|
51ab58cd91 | ||
|
|
aa7798d1d1 | ||
|
|
9067a1fc92 | ||
|
|
4024b6c564 | ||
|
|
d39730928b | ||
|
|
e1f049229c | ||
|
|
8f2676ec19 | ||
|
|
32d26248dc | ||
|
|
16f926401b | ||
|
|
66d60d3599 | ||
|
|
5a35ab6c34 | ||
|
|
ba1542bd31 | ||
|
|
453060945a | ||
|
|
c8351be461 | ||
|
|
9954da22a6 | ||
|
|
907b5611eb | ||
|
|
5f075de212 | ||
|
|
8fcf3c5079 | ||
|
|
07cee90c7a | ||
|
|
75ad495b98 | ||
|
|
0bb7288ad2 | ||
|
|
ad72415532 | ||
|
|
0ad0353fc0 | ||
|
|
9fa0dcd7aa | ||
|
|
1f2e80cd39 | ||
|
|
6cb6034d43 | ||
|
|
25134c6ac6 | ||
|
|
92bf42878a | ||
|
|
9f4582d158 | ||
|
|
68af73970e | ||
|
|
b6ed8d4975 | ||
|
|
d07d3645ce | ||
|
|
123759ab17 | ||
|
|
f2f1f893d8 | ||
|
|
db93a8eed2 | ||
|
|
12ab6d4a7d | ||
|
|
add759e889 | ||
|
|
f315f7977d | ||
|
|
f2f6701ebd | ||
|
|
1a92794d33 | ||
|
|
7640deb798 | ||
|
|
f1e8ef1cf6 | ||
|
|
5e5ac0162e | ||
|
|
0c013820f0 | ||
|
|
4b3a9e5847 | ||
|
|
e4982256a4 | ||
|
|
babc4927a8 | ||
|
|
6dd84cf469 | ||
|
|
a8800e3899 | ||
|
|
5f03496046 | ||
|
|
41500c17a2 | ||
|
|
2dcfde8b9a | ||
|
|
5c3305d8fa | ||
|
|
0d1fe99f53 | ||
|
|
4c03ffeec7 | ||
|
|
8101d17482 | ||
|
|
bc7b4dcc2a | ||
|
|
3db8b9078d | ||
|
|
943dbbefd3 | ||
|
|
480abcb853 | ||
|
|
60aaaff58e | ||
|
|
e3b889bbe8 | ||
|
|
ac5506a43b | ||
|
|
b29f533a3b | ||
|
|
a8ee86b09e | ||
|
|
0238c53302 | ||
|
|
665e3c806f | ||
|
|
8c96838441 | ||
|
|
4a722daec6 | ||
|
|
4e0cdbcb91 | ||
|
|
08976624cd | ||
|
|
fdeba94653 | ||
|
|
d3b100b7e5 | ||
|
|
1de3e18b08 | ||
|
|
d5c3c95682 | ||
|
|
dabe1e29ed | ||
|
|
203d1c0cfc | ||
|
|
7edd8601be | ||
|
|
a4423247f4 | ||
|
|
4834b203a0 | ||
|
|
bbabb32d13 | ||
|
|
95112d6bdf | ||
|
|
36cdca5a3e | ||
|
|
6980a9f3fc | ||
|
|
7b09479cd2 | ||
|
|
5825fd6f36 | ||
|
|
2d5b45dd82 | ||
|
|
52dda1d1fe | ||
|
|
420624bee4 | ||
|
|
8abde7b7d0 | ||
|
|
9e5b1ba28e | ||
|
|
b9c7d3c18e | ||
|
|
10aeccbbe5 | ||
|
|
15d351ebc2 | ||
|
|
7194f31cb6 | ||
|
|
84b7e82446 | ||
|
|
8264423b1a | ||
|
|
37f897f3bf | ||
|
|
fe3efac145 | ||
|
|
9773aebefc | ||
|
|
06f2b8c371 | ||
|
|
e8f0bb8350 | ||
|
|
9bfa6b827b | ||
|
|
b21bc17a58 | ||
|
|
f4d5d417d0 | ||
|
|
91fc83621e | ||
|
|
461feca0ca | ||
|
|
5e9afab3f7 | ||
|
|
2599ca6450 | ||
|
|
fc99ad3a39 | ||
|
|
10e1c3e72c | ||
|
|
af5dedd4d4 | ||
|
|
3b986c1076 | ||
|
|
72f77e8b7c | ||
|
|
e893bf676f | ||
|
|
80eb34f611 | ||
|
|
5d01947552 | ||
|
|
d3a025ef7b | ||
|
|
c466df841e | ||
|
|
b3c6e2a0f3 | ||
|
|
076c9cfed7 | ||
|
|
c3f3d12f83 | ||
|
|
44974034ec | ||
|
|
d6175acd38 | ||
|
|
62eee5f05c | ||
|
|
d4e5201913 | ||
|
|
f4d584765a | ||
|
|
26e224f852 | ||
|
|
252358ed66 | ||
|
|
475afeb7c8 | ||
|
|
7cbbb846eb | ||
|
|
25f947968c | ||
|
|
cad824dcbc | ||
|
|
e506f50b00 | ||
|
|
96ec149a98 | ||
|
|
8c913512f6 | ||
|
|
4cc307299d | ||
|
|
407c6b4c5f | ||
|
|
8f87070434 | ||
|
|
4a63996ee2 | ||
|
|
0358fe7620 | ||
|
|
55e64395ed | ||
|
|
ff5fb18e14 | ||
|
|
52dd960857 | ||
|
|
430221c2de | ||
|
|
217bdf8f92 | ||
|
|
38c6c869bf | ||
|
|
84d46da67e | ||
|
|
eb9d6240d7 | ||
|
|
2d44a871b0 | ||
|
|
3f89f350ff | ||
|
|
1a8407a782 | ||
|
|
cf288a3f73 | ||
|
|
f1f37fb180 | ||
|
|
fb0dd079fd | ||
|
|
a6c584c85c | ||
|
|
77adf35a30 | ||
|
|
dc6951c2a9 | ||
|
|
d14ba3f0f7 | ||
|
|
78ddf36e35 | ||
|
|
d42734624d | ||
|
|
b5dbd9d59b | ||
|
|
bed3e1289b | ||
|
|
b11ca4e60e | ||
|
|
4fcf3aa2bd | ||
|
|
dc39da8ca5 | ||
|
|
c10c87d28e | ||
|
|
c6fe6f1cc5 | ||
|
|
1c2bbeb26d | ||
|
|
17ed3692d0 | ||
|
|
966a00f41e | ||
|
|
fd8d8f89aa | ||
|
|
305bb74072 | ||
|
|
7f4dcdd134 | ||
|
|
aac37dcce1 | ||
|
|
f539c662a5 | ||
|
|
c82f346dd0 | ||
|
|
21b4a87837 | ||
|
|
ae73bcf24b | ||
|
|
2a3b56bde1 | ||
|
|
b8ebededd8 | ||
|
|
227c4c422c | ||
|
|
652bfb93cc | ||
|
|
c2278e3536 | ||
|
|
caa2fca4e8 | ||
|
|
745cb0175c | ||
|
|
e5165a780f | ||
|
|
b4b91af02b | ||
|
|
5649ff9c2e | ||
|
|
5b4bf6c62a | ||
|
|
93cb662282 | ||
|
|
00a8715e58 | ||
|
|
7ecd479b3e | ||
|
|
8fe7d3aaec | ||
|
|
f32a693393 | ||
|
|
17ebc01597 | ||
|
|
827fb698e1 | ||
|
|
32bdf10fd2 | ||
|
|
b795e6c3d2 | ||
|
|
42ba524e4e | ||
|
|
317c6d96e3 | ||
|
|
3692d1499f | ||
|
|
b21fbad8a3 | ||
|
|
743334a68a | ||
|
|
951413eb38 | ||
|
|
32dcdef853 | ||
|
|
34c9254d4a | ||
|
|
14012a4668 | ||
|
|
575debca63 | ||
|
|
763cac8532 | ||
|
|
43faacd7a7 | ||
|
|
1d4e307e96 | ||
|
|
7f8933b0de | ||
|
|
81608ff025 | ||
|
|
db63675b8e | ||
|
|
f74a83bc46 | ||
|
|
bc1deba3e4 | ||
|
|
d6113a8f0a | ||
|
|
2062cd48ea | ||
|
|
1c965ef515 | ||
|
|
58291b7156 | ||
|
|
afd1648d80 | ||
|
|
21814ffa9a | ||
|
|
9d3522da54 | ||
|
|
e07a76755e | ||
|
|
ba46bcdeae | ||
|
|
8d7e44314c | ||
|
|
35a67498c7 | ||
|
|
90dd934f95 | ||
|
|
4087045542 | ||
|
|
d11cef5907 | ||
|
|
76c91d226c | ||
|
|
c2b4dd2afd | ||
|
|
25b39cb39a | ||
|
|
35dcb7b88b | ||
|
|
e5f7e7c26e | ||
|
|
c5c11fd6a6 | ||
|
|
8134083419 | ||
|
|
a87e624198 | ||
|
|
e4c62d20b4 | ||
|
|
fa195d9e55 | ||
|
|
5ef5773d23 | ||
|
|
6eea52afdf | ||
|
|
80e64af30f | ||
|
|
563b6ddc36 | ||
|
|
c051ab9dc4 | ||
|
|
87737a8bdb | ||
|
|
94273d80b0 | ||
|
|
a08ec2a4bd | ||
|
|
d246c556f4 | ||
|
|
65aa365e38 | ||
|
|
eeeae449b4 | ||
|
|
17c10a7ba2 | ||
|
|
69f4383678 | ||
|
|
07852a7295 | ||
|
|
20b7e9b6b5 | ||
|
|
75f43ccea4 | ||
|
|
59e5785e93 | ||
|
|
b38f52dba9 | ||
|
|
2a6b17a48e | ||
|
|
a6c056a894 | ||
|
|
5c3442a71f | ||
|
|
390253242f | ||
|
|
9ab80fe1ac | ||
|
|
91fdd09e7a | ||
|
|
db5bd5c8a4 | ||
|
|
ef94c2fe7c | ||
|
|
72a25ed8e1 | ||
|
|
eb065e218f | ||
|
|
33426736fc | ||
|
|
896658d5ce | ||
|
|
b14135ed72 | ||
|
|
a1baf2e32d | ||
|
|
f9aa2d3bce | ||
|
|
c95d0e0696 | ||
|
|
ad4b84d446 | ||
|
|
3e27d5fcb0 | ||
|
|
48a100f49a | ||
|
|
698649f981 | ||
|
|
780078c3aa | ||
|
|
4c25e4ddee | ||
|
|
c0a5ac2ac5 | ||
|
|
0435409870 | ||
|
|
c521269409 | ||
|
|
1e252b7e4c | ||
|
|
d72b1edc48 | ||
|
|
f7307e8e01 | ||
|
|
127905f04b | ||
|
|
261c6dabd5 | ||
|
|
cae84bbf02 | ||
|
|
cdb2bc52fa | ||
|
|
cd2972eee0 | ||
|
|
4036aa8d0e | ||
|
|
52c6927c44 | ||
|
|
a16e0a21a2 | ||
|
|
e796b21157 | ||
|
|
1c6bc478b4 | ||
|
|
98f39c6388 | ||
|
|
570c83571b | ||
|
|
c0c38d89e0 | ||
|
|
b866cfc03c | ||
|
|
28c2755b37 | ||
|
|
57bfc5c73a | ||
|
|
0f3f7d53a3 | ||
|
|
529e50fd7f | ||
|
|
2fa283f91d | ||
|
|
029a9ade93 | ||
|
|
f1ca8b15c8 | ||
|
|
4d8edd5da9 | ||
|
|
6c63990653 | ||
|
|
5b521409c6 | ||
|
|
3268fc1014 | ||
|
|
19afb4941b | ||
|
|
40e5111d41 | ||
|
|
a3a40e1e74 | ||
|
|
101caa6826 | ||
|
|
875fed8d77 | ||
|
|
69e28eb000 | ||
|
|
e5d3a8360c | ||
|
|
4545d9285b | ||
|
|
6702024805 | ||
|
|
78bad4842b | ||
|
|
b9a913cfed | ||
|
|
6f5a6f353f | ||
|
|
790c4f589d | ||
|
|
cd1bd3461f | ||
|
|
0280dcd6a8 | ||
|
|
fc337292bc | ||
|
|
fb1daa0e21 | ||
|
|
579b9dc0c2 | ||
|
|
dedd0be352 | ||
|
|
1c7d9c3513 | ||
|
|
0c7dfe2af4 | ||
|
|
8d1351a8a3 | ||
|
|
e6e68a6036 | ||
|
|
24658edc45 | ||
|
|
09eaa3116a | ||
|
|
e9bff466b5 | ||
|
|
5d77f50160 | ||
|
|
2ab91e363f | ||
|
|
34d881426f | ||
|
|
13ecaa0ad4 | ||
|
|
ce6185b1f7 | ||
|
|
2cfde6b75a | ||
|
|
37d0354751 | ||
|
|
0a0edcf203 | ||
|
|
d6aad2ea28 | ||
|
|
63084506ee | ||
|
|
c5d313574f | ||
|
|
caab998212 | ||
|
|
aa037cc3d9 | ||
|
|
642bffe374 | ||
|
|
d682b154fc | ||
|
|
d4a06d98cf | ||
|
|
856b5e16b1 | ||
|
|
a0aa208860 | ||
|
|
037a11e04f | ||
|
|
bd8a1d715f | ||
|
|
54ab1dc091 | ||
|
|
9471e63857 | ||
|
|
fa4a403f38 | ||
|
|
d608d65bf4 | ||
|
|
c0f2df172a | ||
|
|
788ef5d81c | ||
|
|
1c6b5cffe1 | ||
|
|
c04382b623 | ||
|
|
0bbe51f8fd | ||
|
|
ff7d7d15a0 | ||
|
|
4b3d083d3a | ||
|
|
a566dd390b | ||
|
|
7d1442da04 | ||
|
|
17fc982f55 | ||
|
|
ba417e2274 | ||
|
|
d345094b75 | ||
|
|
6da477480d | ||
|
|
e274088c06 | ||
|
|
1bcaa73c5c | ||
|
|
ca94e8f621 | ||
|
|
1c4e198f59 | ||
|
|
fdd13f9c66 | ||
|
|
4333ab624e | ||
|
|
9fe1eb3a42 | ||
|
|
ad251a7682 | ||
|
|
1fa740de2d | ||
|
|
466b89064a | ||
|
|
2748cb0ba3 | ||
|
|
aef0d5bdde | ||
|
|
c71e8f024a | ||
|
|
9411f07321 | ||
|
|
9b2a5c9bbf | ||
|
|
2b275523a0 | ||
|
|
31fe2f6da4 | ||
|
|
f95db623a5 | ||
|
|
a46313e483 | ||
|
|
31c330826e | ||
|
|
c4cf800142 | ||
|
|
b64a2b0006 | ||
|
|
a3702f2270 | ||
|
|
d221b1d470 | ||
|
|
0b22a6bc1d | ||
|
|
07e8acd003 | ||
|
|
9fce617c57 | ||
|
|
8d5c736975 | ||
|
|
4ccec05186 | ||
|
|
a4f456f002 | ||
|
|
fbdb941c27 | ||
|
|
a41cd42e8d | ||
|
|
77521e4627 | ||
|
|
b6a1242bac | ||
|
|
2f325cfe26 | ||
|
|
193b0ad0f0 | ||
|
|
ed476b7793 | ||
|
|
720fd94b7f | ||
|
|
ff87da105c | ||
|
|
a875e65536 | ||
|
|
0b2c6bb662 | ||
|
|
e44e2fbbb7 | ||
|
|
b3c93644fd | ||
|
|
a56b7ff636 | ||
|
|
c724236930 | ||
|
|
4853320b2b | ||
|
|
ba1acb6ac1 | ||
|
|
f32a6320fc | ||
|
|
9f914ce36a | ||
|
|
b037644e5a | ||
|
|
afd8c59f83 | ||
|
|
8aa4af3e91 | ||
|
|
630a8a2b97 | ||
|
|
dc34c4d00c | ||
|
|
fb42729dec | ||
|
|
b06989216a | ||
|
|
e5144f08cd | ||
|
|
c4a60190e8 | ||
|
|
efe9e4fa4c | ||
|
|
45800b1559 | ||
|
|
b0b2b8104f | ||
|
|
8dbc012825 | ||
|
|
a434176063 | ||
|
|
a013f750c7 | ||
|
|
aa1f49d02f | ||
|
|
7125a26309 | ||
|
|
329a35ebf0 | ||
|
|
d30043f595 | ||
|
|
745dfa1911 | ||
|
|
76203f49a7 | ||
|
|
870a915377 | ||
|
|
c174fce227 | ||
|
|
2b6e42e919 | ||
|
|
df73e1e5a3 | ||
|
|
3e902311d4 | ||
|
|
64a0037265 | ||
|
|
bcd4e38093 | ||
|
|
181a77d627 | ||
|
|
b353595ba9 | ||
|
|
75e3bb4f17 | ||
|
|
d2fa9192d4 | ||
|
|
4bcadc2de4 | ||
|
|
8ddff74260 | ||
|
|
95940fdb64 | ||
|
|
9cd5708948 | ||
|
|
d361683d79 | ||
|
|
9ad17a01f7 | ||
|
|
22ca1d443c | ||
|
|
2662e875ca | ||
|
|
8ae0d07ec1 | ||
|
|
76a9edb7f5 | ||
|
|
0ccb464e5b | ||
|
|
bef600efa2 | ||
|
|
58a182cd33 | ||
|
|
aa43334f41 | ||
|
|
a2a4c97f6c | ||
|
|
4217ba99fd | ||
|
|
589725f5cc | ||
|
|
3fea4602f8 | ||
|
|
8ea6aae875 | ||
|
|
2c70b2af68 | ||
|
|
54a2cbcb42 | ||
|
|
fdef821c60 | ||
|
|
dfa798a35d | ||
|
|
39b8eb6ff1 | ||
|
|
6cf71f67a9 | ||
|
|
f2e919725e | ||
|
|
869599126e | ||
|
|
3b1b200f6f | ||
|
|
93c646e3e4 | ||
|
|
3552f80a21 | ||
|
|
66d3a63998 | ||
|
|
6447825978 | ||
|
|
18b7df9fca | ||
|
|
c3781cab96 | ||
|
|
776098dba6 | ||
|
|
8d1b4f61e7 | ||
|
|
c13e2bdb96 | ||
|
|
4682254157 | ||
|
|
d7ca6b9213 | ||
|
|
4a76afbde8 | ||
|
|
a68349c23a | ||
|
|
920e005366 | ||
|
|
659f339020 | ||
|
|
3ee2d463af | ||
|
|
686ddb5460 | ||
|
|
e5d62488b7 | ||
|
|
eb93dd5005 | ||
|
|
6999d02d2d | ||
|
|
790e2b1427 | ||
|
|
a29c7cdfe4 | ||
|
|
6b7cd692a6 | ||
|
|
4d3925872a | ||
|
|
2bd0f6934a | ||
|
|
51783f17ed | ||
|
|
ce3aef3526 | ||
|
|
ee70afdfbb | ||
|
|
d96c4a56a2 | ||
|
|
9a39513dea | ||
|
|
8f22d63315 | ||
|
|
7f2a5bb95e | ||
|
|
0118dbd5fb | ||
|
|
09405de26c | ||
|
|
efa5ee0e57 | ||
|
|
80d558f37a | ||
|
|
901adc3fc7 | ||
|
|
01417be954 | ||
|
|
43b780cbe6 | ||
|
|
e83f36a12f | ||
|
|
77e3fc4ab0 | ||
|
|
eafd1adaba | ||
|
|
6b53abb7c9 | ||
|
|
f994c5d284 | ||
|
|
6fda220107 | ||
|
|
da290ed1c3 | ||
|
|
7e9cd80a1c | ||
|
|
379b7413d8 | ||
|
|
9181a4df16 | ||
|
|
df982afd51 | ||
|
|
5c2c3b4317 | ||
|
|
92d1309103 | ||
|
|
c43ee3c1d6 | ||
|
|
e0726e5283 | ||
|
|
5f3775584b | ||
|
|
77873d63c5 | ||
|
|
9e6b09765e | ||
|
|
1ad6ea4049 | ||
|
|
7c41da1cb9 | ||
|
|
adcf4bfc53 | ||
|
|
7a6321a9c1 | ||
|
|
d56b27a7b0 | ||
|
|
ed7657ab5f | ||
|
|
a414838416 | ||
|
|
93646577dc | ||
|
|
46db66038e | ||
|
|
efc4e9ce56 | ||
|
|
8d5eac7f80 | ||
|
|
7b94e49b81 | ||
|
|
c35fd4bdc8 | ||
|
|
98590e2d90 | ||
|
|
e6da0e5dd5 | ||
|
|
cb2baf747d | ||
|
|
a2f2eb03ce | ||
|
|
5c6acbb780 | ||
|
|
1be7031199 | ||
|
|
ed6399bde9 | ||
|
|
6709893781 | ||
|
|
686a426cda | ||
|
|
4f90bc7813 | ||
|
|
e307b289ae | ||
|
|
3baeff61a7 | ||
|
|
93ab9d12ee | ||
|
|
36e1317792 | ||
|
|
fa3e90a021 | ||
|
|
782a69cf13 | ||
|
|
d495f351c0 | ||
|
|
30bd3d2d52 | ||
|
|
ff5a21cca5 | ||
|
|
f8abb73c92 | ||
|
|
e97f323d9a | ||
|
|
3d27a4c05d | ||
|
|
9dbc13dbe4 | ||
|
|
c46a4c75b1 | ||
|
|
0bded73f16 | ||
|
|
1333733684 | ||
|
|
003be934de | ||
|
|
93ef20d358 | ||
|
|
94e1a6f0ba | ||
|
|
8661d09d57 | ||
|
|
0e5e21dc4e | ||
|
|
3b25c4987c | ||
|
|
2212eb17aa | ||
|
|
768bac1db8 | ||
|
|
3aef75085f | ||
|
|
ce8bef638a | ||
|
|
f0a0c90304 | ||
|
|
cd6c32b21d | ||
|
|
b31876d2d1 | ||
|
|
ebab8a190e | ||
|
|
1b7ce8e7a5 | ||
|
|
646bb6bd79 | ||
|
|
5a84b97ca9 | ||
|
|
6d41b5a4a1 | ||
|
|
a8bce36f3b | ||
|
|
ac2132f8ba | ||
|
|
cab4b57abe | ||
|
|
938fb30359 | ||
|
|
62346d7d9d | ||
|
|
cf1e5ca64b | ||
|
|
7d2d683d96 | ||
|
|
fe5042f1c3 | ||
|
|
a1dd76aee0 | ||
|
|
d1c91be167 | ||
|
|
9748d99f34 | ||
|
|
c90ffbeb62 | ||
|
|
eb7fafeabf | ||
|
|
3e50629462 | ||
|
|
65281a4554 | ||
|
|
454ec09d6a | ||
|
|
60e3c6858d | ||
|
|
f911f5b4fc | ||
|
|
ad1694d291 | ||
|
|
1130965f26 | ||
|
|
fe1f28998b | ||
|
|
45727fce05 | ||
|
|
d5c23e5add | ||
|
|
e3a8285f6c | ||
|
|
a791221cf6 | ||
|
|
b954d9b403 | ||
|
|
5e7e24a271 | ||
|
|
ffb1e598f6 | ||
|
|
bc2da8a645 | ||
|
|
6f2be3ed30 | ||
|
|
033a7bffb3 | ||
|
|
f2b2ea61a1 | ||
|
|
6f0783acc4 | ||
|
|
ce60aa3823 | ||
|
|
8075e70606 | ||
|
|
4402fc2d0a | ||
|
|
3e3ecda551 | ||
|
|
50beb8f346 | ||
|
|
8e033e3e06 | ||
|
|
dc029a318b | ||
|
|
8e91bc2c8e | ||
|
|
0ff5b4e90b | ||
|
|
20dec19bfe | ||
|
|
d261fbff26 | ||
|
|
6594b33bcc | ||
|
|
a1bb6cc1b1 | ||
|
|
7ce195b68e | ||
|
|
16d8d04aaa | ||
|
|
59565f7d90 | ||
|
|
43784a2495 | ||
|
|
3811d7469e | ||
|
|
c72b40a1e1 | ||
|
|
f00933969d | ||
|
|
759adc45e3 | ||
|
|
27ecf78372 | ||
|
|
c91b83a7ba | ||
|
|
39373ee63a | ||
|
|
2db64c69ae | ||
|
|
a699b71c02 | ||
|
|
6c07d22cda | ||
|
|
a2ee900ed5 | ||
|
|
e709f31b99 | ||
|
|
35afb12756 | ||
|
|
9bed9fe162 | ||
|
|
4ff5553804 | ||
|
|
32213be7a7 | ||
|
|
84894a73e1 | ||
|
|
b6ea185ce7 | ||
|
|
814ac0f731 | ||
|
|
a40bb29da3 | ||
|
|
e9b90079c0 | ||
|
|
dba383c27e | ||
|
|
42059b5817 | ||
|
|
f92a17c01b | ||
|
|
d6552ce333 | ||
|
|
0db89bde5a | ||
|
|
56a12185d4 | ||
|
|
c40170db5d | ||
|
|
1df3e9c414 | ||
|
|
b1570df8b9 | ||
|
|
023fd1ce36 | ||
|
|
a7fe74bc0c | ||
|
|
26c9abd9da | ||
|
|
a5e34645c5 | ||
|
|
b2831c0a19 | ||
|
|
648c1ea0f9 | ||
|
|
9cd927e06a | ||
|
|
4272413f55 | ||
|
|
e1711b7af6 | ||
|
|
f7d3f27d45 | ||
|
|
3a7a47f82d | ||
|
|
cc211706d5 | ||
|
|
22f74be4cd | ||
|
|
5a00d14f94 | ||
|
|
ecb4e7bf9f | ||
|
|
56e5b546e1 | ||
|
|
272f5a2f4f | ||
|
|
ddcbe78a01 | ||
|
|
00b6c964e2 | ||
|
|
d7d2b06ecc | ||
|
|
fafc59360d | ||
|
|
19e105785e | ||
|
|
b87ac09e43 | ||
|
|
af9092d7c7 | ||
|
|
24a1ffd652 | ||
|
|
662813cc58 | ||
|
|
d890b78290 | ||
|
|
58747d7d4a | ||
|
|
0773a4f39c | ||
|
|
66cc7f8a1f | ||
|
|
01ab40bf4a | ||
|
|
4c09147fd1 | ||
|
|
f9f426d788 | ||
|
|
ff8fa1bf31 | ||
|
|
59f99e4f6a | ||
|
|
7449ce9c3b | ||
|
|
f6bc8f0a1f | ||
|
|
4d10b8cdee | ||
|
|
5a61c5de09 | ||
|
|
f84d0db811 | ||
|
|
36ce3b08fe | ||
|
|
da8ea5b545 | ||
|
|
fad3dbf4cd | ||
|
|
034d12c347 | ||
|
|
c94dbf1d9a | ||
|
|
e516687a9e | ||
|
|
4a2f77b0a6 | ||
|
|
7b29ecba71 | ||
|
|
11241b8e07 | ||
|
|
52bbd1f20b | ||
|
|
4044750515 | ||
|
|
b670c546b9 | ||
|
|
f37bbf93cb | ||
|
|
87311ab41a | ||
|
|
ecb4d1845c | ||
|
|
35c232ab25 | ||
|
|
df0be2e251 | ||
|
|
871b3a102b | ||
|
|
02299e3892 | ||
|
|
6af4d6f5b8 | ||
|
|
4fb5700367 | ||
|
|
8579276381 | ||
|
|
7ba60b22c5 | ||
|
|
031932f41c | ||
|
|
079d0a89b1 | ||
|
|
c4fdce6d64 | ||
|
|
5604c2b29f | ||
|
|
74b5ab2b47 | ||
|
|
c29cbfe123 | ||
|
|
6fe5cb1ffd | ||
|
|
7edd5a7a8e | ||
|
|
c1edc1b99b | ||
|
|
4d1d890f72 | ||
|
|
fe0f82fa2b | ||
|
|
84083a65a8 | ||
|
|
fc91c6bc08 | ||
|
|
09120171ba | ||
|
|
a362f920dc | ||
|
|
9d7729f548 | ||
|
|
ed56e177cf | ||
|
|
9db28bd502 | ||
|
|
aded70eb2e | ||
|
|
dfbad85465 | ||
|
|
52076fe182 | ||
|
|
5575c3cb13 | ||
|
|
637d32efff | ||
|
|
fd54658e53 | ||
|
|
2f39a8d76e | ||
|
|
6a3e793500 | ||
|
|
3b3ffeda6b | ||
|
|
f7d92a3b11 | ||
|
|
d9d9ba8bf1 | ||
|
|
f5d9090183 | ||
|
|
705ecd1ef1 | ||
|
|
08b5266a86 | ||
|
|
ecc4846ba8 | ||
|
|
4aab705d11 | ||
|
|
4615a68bcc | ||
|
|
bf6934e8ac | ||
|
|
af8c304bd4 | ||
|
|
51dac5a5a8 | ||
|
|
56463d9e36 | ||
|
|
a6a339dc59 | ||
|
|
8423304ab5 | ||
|
|
bb7408dbe9 | ||
|
|
7eff4dcf02 | ||
|
|
d7ee3fec3d | ||
|
|
5e026a3e8d | ||
|
|
d5e117b89f | ||
|
|
c87a5501df | ||
|
|
7584ebba0b | ||
|
|
66075e3960 | ||
|
|
193ba781a0 | ||
|
|
3e5dd64acc | ||
|
|
d66ab7d389 | ||
|
|
d2e6b27ecd | ||
|
|
0588541357 | ||
|
|
096ea84af6 | ||
|
|
04d0cfd510 | ||
|
|
7653f969ec | ||
|
|
c4ab6a4a8d | ||
|
|
d1ecd1318f | ||
|
|
8d65b1427d | ||
|
|
e693a6057e | ||
|
|
d0aa490ac3 | ||
|
|
0b6cad7d4f | ||
|
|
14e6c6d9a6 | ||
|
|
b2061347a5 | ||
|
|
79979f0a3b | ||
|
|
48d70b2349 | ||
|
|
b1f6309662 | ||
|
|
1acde76292 | ||
|
|
3b4867d7ab | ||
|
|
5ae965f4d3 | ||
|
|
742427b77b | ||
|
|
3d70a101f1 | ||
|
|
99062a5ea3 | ||
|
|
056d87f7ae | ||
|
|
df5a58772c | ||
|
|
e45db05b8e | ||
|
|
d234f74703 | ||
|
|
3ae2a1be4a | ||
|
|
2b828abd90 | ||
|
|
4b598b1575 |
21
.editorconfig
Normal file
21
.editorconfig
Normal file
@@ -0,0 +1,21 @@
|
||||
# EditorConfig is awesome: https://EditorConfig.org
|
||||
|
||||
# top-most EditorConfig file
|
||||
root = true
|
||||
|
||||
# Unix-style newlines with a newline ending every file
|
||||
[*]
|
||||
end_of_line = lf|crlf
|
||||
insert_final_newline = true
|
||||
|
||||
# Matches multiple files with brace expansion notation
|
||||
# Set default charset
|
||||
charset = utf-8
|
||||
|
||||
# 2 space indentation
|
||||
[*.{cjs,mjs,js,jsx,ts,tsx,css,scss,sass,html,json}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
# Unfortunately, EditorConfig doesn't support space configuration inside import braces directly.
|
||||
# You'll need to rely on your linter/formatter like ESLint or Prettier for that.
|
||||
1
.env.development
Normal file
1
.env.development
Normal file
@@ -0,0 +1 @@
|
||||
VITE_BUILD_TYPE = Development
|
||||
1
.env.production
Normal file
1
.env.production
Normal file
@@ -0,0 +1 @@
|
||||
VITE_BUILD_TYPE = Production
|
||||
68
.eslintrc.cjs
Normal file
68
.eslintrc.cjs
Normal file
@@ -0,0 +1,68 @@
|
||||
module.exports = {
|
||||
'env': {
|
||||
'browser': true,
|
||||
'es2021': true,
|
||||
'node': true
|
||||
},
|
||||
'ignorePatterns': ['src/core/', 'src/core.lib/','src/proto/'],
|
||||
'extends': [
|
||||
'eslint:recommended',
|
||||
'plugin:@typescript-eslint/recommended'
|
||||
],
|
||||
'overrides': [
|
||||
{
|
||||
'env': {
|
||||
'node': true
|
||||
},
|
||||
'files': [
|
||||
'.eslintrc.{js,cjs}'
|
||||
],
|
||||
'parserOptions': {
|
||||
'sourceType': 'script'
|
||||
}
|
||||
}
|
||||
],
|
||||
'parser': '@typescript-eslint/parser',
|
||||
'parserOptions': {
|
||||
'ecmaVersion': 'latest',
|
||||
'sourceType': 'module'
|
||||
},
|
||||
'plugins': [
|
||||
'@typescript-eslint',
|
||||
'import'
|
||||
],
|
||||
'settings': {
|
||||
'import/parsers': {
|
||||
'@typescript-eslint/parser': ['.ts']
|
||||
},
|
||||
'import/resolver': {
|
||||
'typescript': {
|
||||
'alwaysTryTypes': true
|
||||
}
|
||||
}
|
||||
},
|
||||
'rules': {
|
||||
'indent': [
|
||||
'error',
|
||||
2
|
||||
],
|
||||
'linebreak-style': [
|
||||
'error',
|
||||
'unix'
|
||||
],
|
||||
'quotes': [
|
||||
'error',
|
||||
'single'
|
||||
],
|
||||
'semi': [
|
||||
'error',
|
||||
'always'
|
||||
],
|
||||
'no-unused-vars': 'off',
|
||||
'no-async-promise-executor': 'off',
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
'@typescript-eslint/no-unused-vars': 'off',
|
||||
'@typescript-eslint/no-var-requires': 'off',
|
||||
'object-curly-spacing': ['error', 'always'],
|
||||
}
|
||||
};
|
||||
81
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
81
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
@@ -0,0 +1,81 @@
|
||||
name: Bug 反馈
|
||||
description: 报告可能的 NapCat 异常行为
|
||||
title: '[BUG] '
|
||||
labels: bug
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
欢迎来到 NapCat 的 Issue Tracker!请填写以下表格来提交 Bug。
|
||||
在提交新的 Bug 反馈前,请确保您:
|
||||
* 已经搜索了现有的 issues,并且没有找到可以解决您问题的方法
|
||||
* 不与现有的某一 issue 重复
|
||||
- type: input
|
||||
id: system-version
|
||||
attributes:
|
||||
label: 系统版本
|
||||
description: 运行 QQNT 的系统版本
|
||||
placeholder: Windows 10 Pro Workstation 22H2
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: qqnt-version
|
||||
attributes:
|
||||
label: QQNT 版本
|
||||
description: 可在 QQNT 的「关于」的设置页中找到
|
||||
placeholder: 9.9.7-21804
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: napcat-version
|
||||
attributes:
|
||||
label: NapCat 版本
|
||||
description: 可在 LiteLoaderQQNT 的设置页或是 QQNT 的设置页侧栏中找到
|
||||
placeholder: 1.0.0
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: onebot-client-version
|
||||
attributes:
|
||||
label: OneBot 客户端
|
||||
description: 连接至 NapCat 的客户端版本信息
|
||||
placeholder: Overflow 2.16.0-2cf7991-SNAPSHOT
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: what-happened
|
||||
attributes:
|
||||
label: 发生了什么?
|
||||
description: 填写你认为的 NapCat 的不正常行为
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: how-reproduce
|
||||
attributes:
|
||||
label: 如何复现
|
||||
description: 填写应当如何操作才能触发这个不正常行为
|
||||
placeholder: |
|
||||
1. xxx
|
||||
2. xxx
|
||||
3. xxx
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: what-expected
|
||||
attributes:
|
||||
label: 期望的结果?
|
||||
description: 填写你认为 NapCat 应当执行的正常行为
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: napcat-log
|
||||
attributes:
|
||||
label: NapCat 运行日志
|
||||
description: 粘贴相关日志内容到此处
|
||||
render: shell
|
||||
- type: textarea
|
||||
id: onebot-client-log
|
||||
attributes:
|
||||
label: OneBot 客户端运行日志
|
||||
description: 粘贴 OneBot 客户端的相关日志内容到此处
|
||||
render: shell
|
||||
11
.github/dependabot.yml
vendored
Normal file
11
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
# To get started with Dependabot version updates, you'll need to specify which
|
||||
# package ecosystems to update and where the package manifests are located.
|
||||
# Please see the documentation for all configuration options:
|
||||
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
|
||||
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "npm" # See documentation for possible values
|
||||
directory: "/" # Location of package manifests
|
||||
schedule:
|
||||
interval: "daily"
|
||||
74
.github/workflows/build.yml
vendored
Normal file
74
.github/workflows/build.yml
vendored
Normal file
@@ -0,0 +1,74 @@
|
||||
name: "Build"
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
permissions: write-all
|
||||
|
||||
jobs:
|
||||
build-linux:
|
||||
if: ${{ startsWith(github.event.head_commit.message, 'build:') }}
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
target_platform: [linux]
|
||||
target_arch: [x64, arm64]
|
||||
steps:
|
||||
- name: Clone Main Repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: 'NapNeko/NapCatQQ'
|
||||
submodules: true
|
||||
ref: main
|
||||
token: ${{ secrets.NAPCAT_BUILD }}
|
||||
- name: Use Node.js 20.X
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20.x
|
||||
- name: Build NuCat Linux
|
||||
run: |
|
||||
npm i --arch=${{ matrix.target_arch }} --platform=${{ matrix.target_platform }}
|
||||
npm run build:prod
|
||||
cd dist
|
||||
npm i --omit=dev --arch=${{ matrix.target_arch }} --platform=${{ matrix.target_platform }}
|
||||
cd ..
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: NapCat.${{ matrix.target_platform }}.${{ matrix.target_arch }}
|
||||
path: dist
|
||||
build-win32:
|
||||
if: ${{ startsWith(github.event.head_commit.message, 'build:') }}
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
target_platform: [win32]
|
||||
target_arch: [x64]
|
||||
steps:
|
||||
- name: Clone Main Repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: 'NapNeko/NapCatQQ'
|
||||
submodules: true
|
||||
ref: main
|
||||
token: ${{ secrets.NAPCAT_BUILD }}
|
||||
- name: Use Node.js 20.X
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20.x
|
||||
- name: Build NuCat Linux
|
||||
run: |
|
||||
npm i --arch=${{ matrix.target_arch }} --platform=${{ matrix.target_platform }}
|
||||
npm run build:prod
|
||||
cd dist
|
||||
npm i --omit=dev --arch=${{ matrix.target_arch }} --platform=${{ matrix.target_platform }}
|
||||
cd ..
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: NapCat.${{ matrix.target_platform }}.${{ matrix.target_arch }}
|
||||
path: dist
|
||||
138
.github/workflows/release.yml
vendored
Normal file
138
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,138 @@
|
||||
name: "release"
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*"
|
||||
|
||||
permissions: write-all
|
||||
|
||||
jobs:
|
||||
check-version:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Clone Repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: main
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Extract version from tag
|
||||
run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_ENV
|
||||
|
||||
- name: Use Node.js 20.X
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20.x
|
||||
|
||||
- name: Check Version
|
||||
run: |
|
||||
ls
|
||||
node ./script/checkVersion.cjs
|
||||
sh ./checkVersion.sh
|
||||
build-linux:
|
||||
needs: [check-version]
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
target_platform: [linux]
|
||||
target_arch: [x64, arm64]
|
||||
steps:
|
||||
- name: Clone Main Repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: 'NapNeko/NapCatQQ'
|
||||
submodules: true
|
||||
ref: main
|
||||
token: ${{ secrets.NAPCAT_BUILD }}
|
||||
- name: Use Node.js 20.X
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20.x
|
||||
|
||||
- name: Build NuCat Linux
|
||||
run: |
|
||||
export NAPCAT_BUILDSYS=${{ matrix.target_platform }}
|
||||
export NAPCAT_BUILDARCH=${{ matrix.target_arch }}
|
||||
npm i --arch=${{ matrix.target_arch }} --platform=${{ matrix.target_platform }}
|
||||
npm run build:prod
|
||||
cd dist
|
||||
npm i --omit=dev --arch=${{ matrix.target_arch }} --platform=${{ matrix.target_platform }}
|
||||
cd ..
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: NapCat.${{ matrix.target_platform }}.${{ matrix.target_arch }}
|
||||
path: dist
|
||||
build-win32:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [check-version]
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
target_platform: [win32]
|
||||
target_arch: [x64]
|
||||
steps:
|
||||
- name: Clone Main Repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: 'NapNeko/NapCatQQ'
|
||||
submodules: true
|
||||
ref: main
|
||||
token: ${{ secrets.NAPCAT_BUILD }}
|
||||
|
||||
- name: Use Node.js 20.X
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20.x
|
||||
|
||||
- name: Build NuCat Linux
|
||||
run: |
|
||||
export NAPCAT_BUILDSYS=${{ matrix.target_platform }}
|
||||
export NAPCAT_BUILDARCH=${{ matrix.target_arch }}
|
||||
npm i --arch=${{ matrix.target_arch }} --platform=${{ matrix.target_platform }}
|
||||
npm run build:prod
|
||||
cd dist
|
||||
npm i --omit=dev --arch=${{ matrix.target_arch }} --platform=${{ matrix.target_platform }}
|
||||
cd ..
|
||||
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: NapCat.${{ matrix.target_platform }}.${{ matrix.target_arch }}
|
||||
path: dist
|
||||
|
||||
release-napcat:
|
||||
needs: [build-win32,build-linux]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Download All Artifact
|
||||
uses: actions/download-artifact@v4
|
||||
|
||||
- name: Compress subdirectories
|
||||
run: |
|
||||
for dir in */; do
|
||||
base=$(basename "$dir")
|
||||
zip -r "${base}.zip" "$dir"
|
||||
done
|
||||
|
||||
- name: Extract version from tag
|
||||
run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_ENV
|
||||
|
||||
- name: Clone Changes Log
|
||||
run: curl -o CHANGELOG.md https://fastly.jsdelivr.net/gh/NapNeko/NapCatQQ@main/docs/changelogs/CHANGELOG.v${{ env.VERSION }}.md
|
||||
|
||||
- name: Create Release Draft and Upload Artifacts
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
name: NapCat V${{ env.VERSION }}
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
body_path: CHANGELOG.md
|
||||
files: |
|
||||
NapCat.win32.x64.zip
|
||||
NapCat.linux.x64.zip
|
||||
NapCat.linux.arm64.zip
|
||||
# NapCat.darwin.x64.zip
|
||||
# NapCat.darwin.arm64.zip
|
||||
draft: true
|
||||
17
.gitignore
vendored
Normal file
17
.gitignore
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
# Develop
|
||||
node_modules/
|
||||
package-lock.json
|
||||
pnpm-lock.yaml
|
||||
out/
|
||||
dist/
|
||||
/src/core.lib/common/
|
||||
/localdebug/
|
||||
|
||||
# Editor
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea/*
|
||||
|
||||
# Build
|
||||
*.db
|
||||
checkVersion.sh
|
||||
4
.gitmodules
vendored
Normal file
4
.gitmodules
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
[submodule "src/core"]
|
||||
path = src/core
|
||||
url = https://github.com/NapNeko/core.git
|
||||
branch = master
|
||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 NapCatQQ
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
39
README.md
39
README.md
@@ -1,14 +1,33 @@
|
||||
# NapCatQQ
|
||||
<div align="center">
|
||||
<img src="https://socialify.git.ci/NapNeko/NapCatQQ/image?description=1&language=1&logo=https%3A%2F%2Fraw.githubusercontent.com%2FNapNeko%2FNapCatQQ%2Fmain%2Flogo.png&name=1&stargazers=1&theme=Auto" alt="NapCatQQ" width="640" height="320" />
|
||||
</div>
|
||||
|
||||
## 介绍
|
||||
无
|
||||
## 项目介绍
|
||||
|
||||
## 下载与安装
|
||||
前往release获取
|
||||
NapCatQQ 是基于 PC NTQQ 本体实现一套无头 Bot 框架。
|
||||
|
||||
## 使用与配置
|
||||
参考文档
|
||||
名字寓意 瞌睡猫QQ,像睡着了一样在后台低占用运行的无需GUI界面的NTQQ。
|
||||
|
||||
## 开源与安全
|
||||
为了防止过于扩散与违规使用,未来 NapCat 发版都会不公布源码,在未来形势有所转变下可能会发布源码。
|
||||
代码将进行混淆与插桩,请不要违法使用与宣传本项目。
|
||||
## 如何使用
|
||||
|
||||
可前往 [Release](https://github.com/NapNeko/NapCatQQ/releases/) 页面下载最新版本
|
||||
|
||||
**首次使用** 请务必前往 [官方文档](https://napneko.github.io/) 查看使用文档与教程
|
||||
|
||||
|
||||
## 项目声明
|
||||
|
||||
* 请不要在无关地方宣传NapCatQQ,本项目只是用于学习 node 相关知识,切勿用于违法用途
|
||||
|
||||
* NapCat 不会收集用户隐私信息,但是未来可能会为了更好的利于 NapCat 的优化会收集一些设备信息,如 cpu 架构,系统版本等
|
||||
|
||||
## 相关链接
|
||||
[Telegram Link](https://t.me/+nLZEnpne-pQ1OWFl)
|
||||
|
||||
## 鸣谢名单
|
||||
|
||||
[Lagrange](https://github.com/LagrangeDev/Lagrange.Core)
|
||||
|
||||
<!--
|
||||
QQ群:545402644
|
||||
-->
|
||||
|
||||
12
docs/changelogs/CHANGELOG.v1.5.1.md
Normal file
12
docs/changelogs/CHANGELOG.v1.5.1.md
Normal file
@@ -0,0 +1,12 @@
|
||||
# v1.5.1
|
||||
|
||||
QQ Version: Windows 9.9.10-24108 / Linux 3.2.7-23361
|
||||
|
||||
## 修复与优化
|
||||
* 支持 新Api: set_self_profile 可设置个性签名
|
||||
* 修复 Api: get_group_system_msg
|
||||
* 整理日志、添加颜色、使用统一的日志函数以提高日志可读性
|
||||
## 新增与调整
|
||||
|
||||
|
||||
新增的 API 详细见[API文档](https://napneko.github.io/zh-CN/develop/extends_api)
|
||||
17
docs/changelogs/old/CHANGELOG.v1.3.3.md
Normal file
17
docs/changelogs/old/CHANGELOG.v1.3.3.md
Normal file
@@ -0,0 +1,17 @@
|
||||
# v1.3.3
|
||||
|
||||
QQ Version: Windows 9.9.9-23424 / Linux 3.2.7-23361
|
||||
|
||||
## 修复与优化
|
||||
* 尝试修复多开崩溃问题
|
||||
* 修复群列表更新问题
|
||||
* 修复兼容性问题支持Win7
|
||||
* 修复下载 http 资源缺少UA
|
||||
* 优化少量消息合并转发速度
|
||||
* 修复加载群通知时出现 getUserDetailInfo timeout 导致程序崩溃
|
||||
|
||||
## 新增与调整
|
||||
* 新增设置群公告 Api: /_send_group_notice
|
||||
* 新增重启实现 包括重启快速登录/普通重启 副作用: 原进程 无法清理
|
||||
|
||||
新增的 API 详细见[API文档](https://napneko.github.io/zh-CN/develop/extends_api)
|
||||
18
docs/changelogs/old/CHANGELOG.v1.3.5.md
Normal file
18
docs/changelogs/old/CHANGELOG.v1.3.5.md
Normal file
@@ -0,0 +1,18 @@
|
||||
# v1.3.5
|
||||
|
||||
QQ Version: Windows 9.9.9-23424 / Linux 3.2.7-23361
|
||||
|
||||
## 修复与优化
|
||||
* 优化启动脚本
|
||||
* 修复非管理时群成员减少事件上报 **无法获取操作者与操作类型**
|
||||
* 修复快速重启进程清理问题
|
||||
* 优化配置文件格式 支持自动更新配置 但仍然建议 **备份配置**
|
||||
* 修复正向反向ws多个客户端周期多次心跳问题
|
||||
|
||||
## 新增与调整
|
||||
* 支持WebUi热重载
|
||||
* 新增启动输出WEBUI秘钥
|
||||
* 新增群荣誉信息 /get_group_honor_info
|
||||
* 支持获取群系统消息 /get_group_system_msg
|
||||
|
||||
新增的 API 详细见[API文档](https://napneko.github.io/zh-CN/develop/extends_api)
|
||||
11
docs/changelogs/old/CHANGELOG.v1.3.6.md
Normal file
11
docs/changelogs/old/CHANGELOG.v1.3.6.md
Normal file
@@ -0,0 +1,11 @@
|
||||
# v1.3.6
|
||||
|
||||
QQ Version: Windows 9.9.9-23424 / Linux 3.2.7-23361
|
||||
|
||||
## 修复与优化
|
||||
* 修复戳一戳多次上报问题
|
||||
|
||||
## 新增与调整
|
||||
|
||||
|
||||
新增的 API 详细见[API文档](https://napneko.github.io/zh-CN/develop/extends_api)
|
||||
15
docs/changelogs/old/CHANGELOG.v1.3.8.md
Normal file
15
docs/changelogs/old/CHANGELOG.v1.3.8.md
Normal file
@@ -0,0 +1,15 @@
|
||||
# v1.3.8
|
||||
|
||||
QQ Version: Windows 9.9.9-23873 / Linux 3.2.7-23361
|
||||
|
||||
## 修复与优化
|
||||
* 优化打包后体积问题
|
||||
* 修复QQ等级获取
|
||||
* 兼容 9.7.x 版本换行符 统一为 \n
|
||||
* 修复处理加群请求 字段异常情况
|
||||
* 修复退群通知问题
|
||||
|
||||
## 新增与调整
|
||||
|
||||
|
||||
新增的 API 详细见[API文档](https://napneko.github.io/zh-CN/develop/extends_api)
|
||||
11
docs/changelogs/old/CHANGELOG.v1.3.9.md
Normal file
11
docs/changelogs/old/CHANGELOG.v1.3.9.md
Normal file
@@ -0,0 +1,11 @@
|
||||
# v1.3.9
|
||||
|
||||
QQ Version: Windows 9.9.10-23873 / Linux 3.2.7-23361
|
||||
|
||||
## 修复与优化
|
||||
* 修复QQ等级获取与兼容性问题
|
||||
|
||||
## 新增与调整
|
||||
|
||||
|
||||
新增的 API 详细见[API文档](https://napneko.github.io/zh-CN/develop/extends_api)
|
||||
12
docs/changelogs/old/CHANGELOG.v1.4.0.md
Normal file
12
docs/changelogs/old/CHANGELOG.v1.4.0.md
Normal file
@@ -0,0 +1,12 @@
|
||||
# v1.4.0
|
||||
|
||||
QQ Version: Windows 9.9.10-24108 / Linux 3.2.7-23361
|
||||
|
||||
## 修复与优化
|
||||
|
||||
## 新增与调整
|
||||
* 支持空间Cookies获取
|
||||
* 支持获取在线设备 API /get_online_clients
|
||||
* 支持图片OCR API: /.ocr_image /ocr_image
|
||||
|
||||
新增的 API 详细见[API文档](https://napneko.github.io/zh-CN/develop/extends_api)
|
||||
14
docs/changelogs/old/CHANGELOG.v1.4.1.md
Normal file
14
docs/changelogs/old/CHANGELOG.v1.4.1.md
Normal file
@@ -0,0 +1,14 @@
|
||||
# v1.4.1
|
||||
|
||||
QQ Version: Windows 9.9.10-24108 / Linux 3.2.7-23361
|
||||
|
||||
## 修复与优化
|
||||
* 提高部分Api兼容性
|
||||
* 优化日志膨胀问题
|
||||
* 在线状态刷新问题修复
|
||||
## 新增与调整
|
||||
* 支持非管理群 本地记录时间数据 (建议 **备份配置 清空配置 重新配置**)
|
||||
* 新增英译中接口 Api: /translate_en2zh
|
||||
* 新增群文件管理相关扩展接口 Api: /get_group_file_count /get_group_file_list /set_group_file_folder /del_group_file /del_group_file_folder
|
||||
|
||||
新增的 API 详细见[API文档](https://napneko.github.io/zh-CN/develop/extends_api)
|
||||
12
docs/changelogs/old/CHANGELOG.v1.4.2.md
Normal file
12
docs/changelogs/old/CHANGELOG.v1.4.2.md
Normal file
@@ -0,0 +1,12 @@
|
||||
# v1.4.2
|
||||
|
||||
QQ Version: Windows 9.9.10-24108 / Linux 3.2.7-23361
|
||||
|
||||
## 修复与优化
|
||||
* 修复获取群文件列表Api
|
||||
* 修复退群通知问题
|
||||
|
||||
## 新增与调整
|
||||
|
||||
|
||||
新增的 API 详细见[API文档](https://napneko.github.io/zh-CN/develop/extends_api)
|
||||
11
docs/changelogs/old/CHANGELOG.v1.4.3.md
Normal file
11
docs/changelogs/old/CHANGELOG.v1.4.3.md
Normal file
@@ -0,0 +1,11 @@
|
||||
# v1.4.3
|
||||
|
||||
QQ Version: Windows 9.9.10-24108 / Linux 3.2.7-23361
|
||||
|
||||
## 修复与优化
|
||||
* 修复名片通知
|
||||
|
||||
## 新增与调整
|
||||
|
||||
|
||||
新增的 API 详细见[API文档](https://napneko.github.io/zh-CN/develop/extends_api)
|
||||
10
docs/changelogs/old/CHANGELOG.v1.4.4.md
Normal file
10
docs/changelogs/old/CHANGELOG.v1.4.4.md
Normal file
@@ -0,0 +1,10 @@
|
||||
# v1.4.4
|
||||
|
||||
QQ Version: Windows 9.9.10-24108 / Linux 3.2.7-23361
|
||||
|
||||
## 更新
|
||||
* **重大更新:**更新了版本号
|
||||
|
||||
|
||||
新增的 API 详细见[API文档](https://napneko.github.io/zh-CN/develop/extends_api)
|
||||
|
||||
12
docs/changelogs/old/CHANGELOG.v1.4.5.md
Normal file
12
docs/changelogs/old/CHANGELOG.v1.4.5.md
Normal file
@@ -0,0 +1,12 @@
|
||||
# v1.4.5
|
||||
|
||||
QQ Version: Windows 9.9.10-24108 / Linux 3.2.7-23361
|
||||
|
||||
## 修复与优化
|
||||
* 紧急修复二维扫码问题
|
||||
|
||||
## 新增与调整
|
||||
|
||||
|
||||
新增的 API 详细见[API文档](https://napneko.github.io/zh-CN/develop/extends_api)
|
||||
|
||||
12
docs/changelogs/old/CHANGELOG.v1.4.6.md
Normal file
12
docs/changelogs/old/CHANGELOG.v1.4.6.md
Normal file
@@ -0,0 +1,12 @@
|
||||
# v1.4.6
|
||||
|
||||
QQ Version: Windows 9.9.10-24108 / Linux 3.2.7-23361
|
||||
|
||||
## 修复与优化
|
||||
* 优化整体稳定性
|
||||
|
||||
## 新增与调整
|
||||
|
||||
|
||||
新增的 API 详细见[API文档](https://napneko.github.io/zh-CN/develop/extends_api)
|
||||
|
||||
11
docs/changelogs/old/CHANGELOG.v1.4.7.md
Normal file
11
docs/changelogs/old/CHANGELOG.v1.4.7.md
Normal file
@@ -0,0 +1,11 @@
|
||||
# v1.4.7
|
||||
|
||||
QQ Version: Windows 9.9.10-24108 / Linux 3.2.7-23361
|
||||
|
||||
## 修复与优化
|
||||
* 临时扩展 Api: GoCQHTTPUploadGroupFile folder_id字段 用于选择文件夹
|
||||
|
||||
## 新增与调整
|
||||
|
||||
|
||||
新增的 API 详细见[API文档](https://napneko.github.io/zh-CN/develop/extends_api)
|
||||
12
docs/changelogs/old/CHANGELOG.v1.4.8.md
Normal file
12
docs/changelogs/old/CHANGELOG.v1.4.8.md
Normal file
@@ -0,0 +1,12 @@
|
||||
# v1.4.8
|
||||
|
||||
QQ Version: Windows 9.9.10-24108 / Linux 3.2.7-23361
|
||||
|
||||
## 修复与优化
|
||||
* 优化Guid的生成方式
|
||||
* 支持临时消息获取群来源
|
||||
|
||||
## 新增与调整
|
||||
|
||||
|
||||
新增的 API 详细见[API文档](https://napneko.github.io/zh-CN/develop/extends_api)
|
||||
11
docs/changelogs/old/CHANGELOG.v1.4.9.md
Normal file
11
docs/changelogs/old/CHANGELOG.v1.4.9.md
Normal file
@@ -0,0 +1,11 @@
|
||||
# v1.4.9
|
||||
|
||||
QQ Version: Windows 9.9.10-24108 / Linux 3.2.7-23361
|
||||
|
||||
## 修复与优化
|
||||
* 修复接口调用问题 接口标准化 API:set_group_add_request
|
||||
|
||||
## 新增与调整
|
||||
|
||||
|
||||
新增的 API 详细见[API文档](https://napneko.github.io/zh-CN/develop/extends_api)
|
||||
11
docs/changelogs/old/CHANGELOG.v1.5.0.md
Normal file
11
docs/changelogs/old/CHANGELOG.v1.5.0.md
Normal file
@@ -0,0 +1,11 @@
|
||||
# v1.5.0
|
||||
|
||||
QQ Version: Windows 9.9.10-24108 / Linux 3.2.7-23361
|
||||
|
||||
## 修复与优化
|
||||
* 修正各Api默认值
|
||||
|
||||
## 新增与调整
|
||||
|
||||
|
||||
新增的 API 详细见[API文档](https://napneko.github.io/zh-CN/develop/extends_api)
|
||||
67
package.json
Normal file
67
package.json
Normal file
@@ -0,0 +1,67 @@
|
||||
{
|
||||
"name": "napcat",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"version": "1.5.1",
|
||||
"scripts": {
|
||||
"watch:dev": "vite --mode development",
|
||||
"watch:prod": "vite --mode production",
|
||||
"build:dev": "vite build --mode development",
|
||||
"build:prod": "vite build --mode production",
|
||||
"build": "npm run build:dev",
|
||||
"build:core": "cd ./src/core && npm run build && cd ../.. && node ./script/copy-core.cjs",
|
||||
"build:webui": "cd ./src/webui && vite build",
|
||||
"watch": "npm run watch:dev",
|
||||
"debug-win": "powershell dist/napcat.ps1",
|
||||
"lint": "eslint --fix src/**/*.{js,ts}",
|
||||
"release": "npm run build:prod",
|
||||
"depend": "cd dist && npm install --omit=dev"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@log4js-node/log4js-api": "^1.0.2",
|
||||
"@protobuf-ts/plugin": "^2.9.4",
|
||||
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||
"@rollup/plugin-typescript": "^11.1.6",
|
||||
"@types/cors": "^2.8.17",
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/figlet": "^1.5.8",
|
||||
"@types/fluent-ffmpeg": "^2.1.24",
|
||||
"@types/node": "^20.11.30",
|
||||
"@types/qrcode-terminal": "^0.12.2",
|
||||
"@types/uuid": "^9.0.8",
|
||||
"@types/ws": "^8.5.10",
|
||||
"@typescript-eslint/eslint-plugin": "^7.4.0",
|
||||
"@typescript-eslint/parser": "^7.4.0",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-import-resolver-typescript": "^3.6.1",
|
||||
"eslint-plugin-import": "^2.29.1",
|
||||
"i": "^0.3.7",
|
||||
"javascript-obfuscator": "^4.1.0",
|
||||
"rollup": "^4.13.2",
|
||||
"rollup-plugin-dts": "^6.1.0",
|
||||
"rollup-plugin-obfuscator": "^1.1.0",
|
||||
"typescript": "^5.3.3",
|
||||
"vite": "^5.2.6",
|
||||
"vite-plugin-cp": "^4.0.8",
|
||||
"vite-plugin-dts": "^3.8.2",
|
||||
"vite-tsconfig-paths": "^4.3.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"ajv": "^8.13.0",
|
||||
"chalk": "^5.3.0",
|
||||
"commander": "^12.0.0",
|
||||
"cors": "^2.8.5",
|
||||
"express": "^5.0.0-beta.2",
|
||||
"fast-xml-parser": "^4.3.6",
|
||||
"file-type": "^19.0.0",
|
||||
"fluent-ffmpeg": "^2.1.2",
|
||||
"image-size": "^1.1.1",
|
||||
"json-schema-to-ts": "^3.1.0",
|
||||
"log4js": "^6.9.1",
|
||||
"qrcode-terminal": "^0.12.0",
|
||||
"silk-wasm": "^3.3.4",
|
||||
"sqlite3": "^5.1.7",
|
||||
"uuid": "^9.0.1",
|
||||
"ws": "^8.16.0"
|
||||
}
|
||||
}
|
||||
42
script/checkVersion.cjs
Normal file
42
script/checkVersion.cjs
Normal file
@@ -0,0 +1,42 @@
|
||||
const fs = require("fs");
|
||||
const process = require("process");
|
||||
|
||||
console.log("[NapCat] [CheckVersion] 开始检测当前仓库版本...");
|
||||
try {
|
||||
const packageJson = require("../package.json");
|
||||
const currentVersion = packageJson.version;
|
||||
const targetVersion = process.env.VERSION;
|
||||
|
||||
console.log("[NapCat] [CheckVersion] currentVersion:", currentVersion, "targetVersion:", targetVersion);
|
||||
|
||||
// 验证 targetVersion 格式
|
||||
if (!targetVersion || typeof targetVersion !== 'string') {
|
||||
console.error("[NapCat] [CheckVersion] 目标版本格式不正确或未设置!");
|
||||
return;
|
||||
}
|
||||
|
||||
// 写入脚本文件的统一函数
|
||||
const writeScriptToFile = (content) => {
|
||||
fs.writeFileSync("./checkVersion.sh", content, { flag: 'w' });
|
||||
console.log("[NapCat] [CheckVersion] checkVersion.sh 文件已更新。");
|
||||
};
|
||||
|
||||
if (currentVersion === targetVersion) {
|
||||
// 不需要更新版本,写入一个简单的脚本
|
||||
const simpleScript = "#!/bin/bash\necho \"CheckVersion Is Done\"";
|
||||
writeScriptToFile(simpleScript);
|
||||
} else {
|
||||
// 更新版本,构建安全的sed命令
|
||||
const safeScriptContent = `
|
||||
#!/bin/bash
|
||||
git config --global user.email "bot@test.wumiao.wang"
|
||||
git config --global user.name "Version"
|
||||
sed -i "s/\\\"version\\\": \\\"${currentVersion}\\\"/\\\"version\\\": \\\"${targetVersion}\\\"/g" package.json
|
||||
git add .
|
||||
git commit -m "chore:version change"
|
||||
git push -u origin main`;
|
||||
writeScriptToFile(safeScriptContent);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("[NapCat] [CheckVersion] 检测过程中发生错误:", error);
|
||||
}
|
||||
32
script/copy-core.cjs
Normal file
32
script/copy-core.cjs
Normal file
@@ -0,0 +1,32 @@
|
||||
let fs = require('fs');
|
||||
let path = require('path');
|
||||
|
||||
const coreDistDir = path.join(path.resolve(__dirname, '../'), 'src/core/dist/core/src');
|
||||
const coreLibDir = path.join(path.resolve(__dirname, '../'), 'src/core.lib/src');
|
||||
|
||||
function copyDir(currentPath, outputDir) {
|
||||
fs.readdir(currentPath, { withFileTypes: true }, (err, entries) => {
|
||||
if (err?.errno === -4058) return;
|
||||
|
||||
entries.forEach(entry => {
|
||||
const localBasePath = path.join(currentPath, entry.name);
|
||||
const outputLocalBasePath = path.join(outputDir, entry.name);
|
||||
|
||||
if (entry.isDirectory()) {
|
||||
// 如果是目录,递归调用
|
||||
if (!fs.existsSync(outputLocalBasePath)) {
|
||||
fs.mkdirSync(outputLocalBasePath, { recursive: true });
|
||||
}
|
||||
copyDir(localBasePath, outputLocalBasePath);
|
||||
}
|
||||
else{
|
||||
// 如果是文件,直接复制
|
||||
fs.copyFile(localBasePath, outputLocalBasePath, (err) => {
|
||||
if (err) throw err;
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
copyDir(coreDistDir, coreLibDir);
|
||||
21
script/gen-version.ts
Normal file
21
script/gen-version.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import { version } from '../src/onebot11/version'
|
||||
|
||||
const manifestPath = path.join(__dirname, '../package.json')
|
||||
|
||||
function readManifest (): any {
|
||||
if (fs.existsSync(manifestPath)) {
|
||||
return JSON.parse(fs.readFileSync(manifestPath, 'utf-8'))
|
||||
}
|
||||
}
|
||||
|
||||
function writeManifest (manifest: any) {
|
||||
fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2))
|
||||
}
|
||||
|
||||
const manifest = readManifest()
|
||||
if (version !== manifest.version) {
|
||||
manifest.version = version
|
||||
writeManifest(manifest)
|
||||
}
|
||||
3
script/napcat-custom.bat
Normal file
3
script/napcat-custom.bat
Normal file
@@ -0,0 +1,3 @@
|
||||
chcp 65001
|
||||
set ELECTRON_RUN_AS_NODE=1
|
||||
"H:\Program Files\QQNT最新版\QQ.exe" %~dp0/napcat.cjs %*
|
||||
15
script/napcat-gc.ps1
Normal file
15
script/napcat-gc.ps1
Normal file
@@ -0,0 +1,15 @@
|
||||
function Get-QQpath {
|
||||
try {
|
||||
$key = Get-ItemProperty -Path "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\QQ"
|
||||
$uninstallString = $key.UninstallString
|
||||
return [System.IO.Path]::GetDirectoryName($uninstallString) + "\QQ.exe"
|
||||
}
|
||||
catch {
|
||||
throw "Error getting UninstallString: $_"
|
||||
}
|
||||
}
|
||||
$params = $args -join " "
|
||||
$QQpath = Get-QQpath
|
||||
$Bootfile = Join-Path (Get-Location) "\dist\inject.cjs"
|
||||
$env:ELECTRON_RUN_AS_NODE = 1
|
||||
Start-Process powershell -ArgumentList "-noexit", "-noprofile", "-command &{& '$QQpath' --expose-gc $Bootfile $params}"
|
||||
17
script/napcat-log.ps1
Normal file
17
script/napcat-log.ps1
Normal file
@@ -0,0 +1,17 @@
|
||||
function Get-QQpath {
|
||||
try {
|
||||
$key = Get-ItemProperty -Path "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\QQ"
|
||||
$uninstallString = $key.UninstallString
|
||||
return [System.IO.Path]::GetDirectoryName($uninstallString) + "\QQ.exe"
|
||||
}
|
||||
catch {
|
||||
return "D:\QQ.exe"
|
||||
}
|
||||
}
|
||||
$params = $args -join " "
|
||||
$QQpath = Get-QQpath
|
||||
$Bootfile = Join-Path $PSScriptRoot "napcat.cjs"
|
||||
$env:ELECTRON_RUN_AS_NODE = 1
|
||||
$argumentList = '-noexit', '-noprofile', "-command `"$QQpath`" `"$Bootfile`" $params"
|
||||
Start-Process powershell -ArgumentList $argumentList -RedirectStandardOutput "log.txt" -RedirectStandardError "error.txt"
|
||||
powershell Get-Content -Wait -Encoding UTF8 log.txt
|
||||
18
script/napcat-utf8.bat
Normal file
18
script/napcat-utf8.bat
Normal file
@@ -0,0 +1,18 @@
|
||||
@echo off
|
||||
setlocal enabledelayedexpansion
|
||||
chcp 65001
|
||||
:loop_read
|
||||
for /f "tokens=2*" %%a in ('reg query "HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\QQ" /v "UninstallString"') do (
|
||||
set "RetString=%%b"
|
||||
goto :napcat_boot
|
||||
)
|
||||
|
||||
:napcat_boot
|
||||
for %%a in ("!RetString!") do (
|
||||
set "pathWithoutUninstall=%%~dpa"
|
||||
)
|
||||
|
||||
set "QQPath=!pathWithoutUninstall!QQ.exe"
|
||||
set ELECTRON_RUN_AS_NODE=1
|
||||
echo !QQPath!
|
||||
"!QQPath!" ./napcat.mjs %*
|
||||
43
script/napcat-utf8.ps1
Normal file
43
script/napcat-utf8.ps1
Normal file
@@ -0,0 +1,43 @@
|
||||
function Get-QQpath {
|
||||
try {
|
||||
$key = Get-ItemProperty -Path "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\QQ"
|
||||
$uninstallString = $key.UninstallString
|
||||
return [System.IO.Path]::GetDirectoryName($uninstallString) + "\QQ.exe"
|
||||
}
|
||||
catch {
|
||||
throw "get QQ path error: $_"
|
||||
}
|
||||
}
|
||||
function Select-QQPath {
|
||||
Add-Type -AssemblyName System.Windows.Forms
|
||||
[System.Windows.Forms.Application]::EnableVisualStyles()
|
||||
|
||||
$dialogTitle = "Select QQ.exe"
|
||||
|
||||
$filePicker = New-Object System.Windows.Forms.OpenFileDialog
|
||||
$filePicker.Title = $dialogTitle
|
||||
$filePicker.Filter = "Executable Files (*.exe)|*.exe|All Files (*.*)|*.*"
|
||||
$filePicker.FilterIndex = 1
|
||||
$null = $filePicker.ShowDialog()
|
||||
if (-not ($filePicker.FileName)) {
|
||||
throw "User did not select an .exe file."
|
||||
}
|
||||
return $filePicker.FileName
|
||||
}
|
||||
|
||||
$params = $args -join " "
|
||||
Try {
|
||||
$QQpath = Get-QQpath
|
||||
}
|
||||
Catch {
|
||||
$QQpath = Select-QQPath
|
||||
}
|
||||
|
||||
if (!(Test-Path $QQpath)) {
|
||||
throw "provided QQ path is invalid: $QQpath"
|
||||
}
|
||||
|
||||
$Bootfile = Join-Path $PSScriptRoot "napcat.mjs"
|
||||
$env:ELECTRON_RUN_AS_NODE = 1
|
||||
$commandInfo = Get-Command $QQpath -ErrorAction Stop
|
||||
Start-Process powershell -ArgumentList "-noexit", "-noprofile", "-command &{& chcp 65001;& '$($commandInfo.Path)' $Bootfile $params}"
|
||||
17
script/napcat.bat
Normal file
17
script/napcat.bat
Normal file
@@ -0,0 +1,17 @@
|
||||
@echo off
|
||||
setlocal enabledelayedexpansion
|
||||
:loop_read
|
||||
for /f "tokens=2*" %%a in ('reg query "HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\QQ" /v "UninstallString"') do (
|
||||
set "RetString=%%b"
|
||||
goto :napcat_boot
|
||||
)
|
||||
|
||||
:napcat_boot
|
||||
for %%a in ("!RetString!") do (
|
||||
set "pathWithoutUninstall=%%~dpa"
|
||||
)
|
||||
|
||||
set "QQPath=!pathWithoutUninstall!QQ.exe"
|
||||
set ELECTRON_RUN_AS_NODE=1
|
||||
echo !QQPath!
|
||||
"!QQPath!" ./napcat.mjs %*
|
||||
43
script/napcat.ps1
Normal file
43
script/napcat.ps1
Normal file
@@ -0,0 +1,43 @@
|
||||
function Get-QQpath {
|
||||
try {
|
||||
$key = Get-ItemProperty -Path "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\QQ"
|
||||
$uninstallString = $key.UninstallString
|
||||
return [System.IO.Path]::GetDirectoryName($uninstallString) + "\QQ.exe"
|
||||
}
|
||||
catch {
|
||||
throw "get QQ path error: $_"
|
||||
}
|
||||
}
|
||||
function Select-QQPath {
|
||||
Add-Type -AssemblyName System.Windows.Forms
|
||||
[System.Windows.Forms.Application]::EnableVisualStyles()
|
||||
|
||||
$dialogTitle = "Select QQ.exe"
|
||||
|
||||
$filePicker = New-Object System.Windows.Forms.OpenFileDialog
|
||||
$filePicker.Title = $dialogTitle
|
||||
$filePicker.Filter = "Executable Files (*.exe)|*.exe|All Files (*.*)|*.*"
|
||||
$filePicker.FilterIndex = 1
|
||||
$null = $filePicker.ShowDialog()
|
||||
if (-not ($filePicker.FileName)) {
|
||||
throw "User did not select an .exe file."
|
||||
}
|
||||
return $filePicker.FileName
|
||||
}
|
||||
|
||||
$params = $args -join " "
|
||||
Try {
|
||||
$QQpath = Get-QQpath
|
||||
}
|
||||
Catch {
|
||||
$QQpath = Select-QQPath
|
||||
}
|
||||
|
||||
if (!(Test-Path $QQpath)) {
|
||||
throw "provided QQ path is invalid: $QQpath"
|
||||
}
|
||||
|
||||
$Bootfile = Join-Path $PSScriptRoot "napcat.mjs"
|
||||
$env:ELECTRON_RUN_AS_NODE = 1
|
||||
$commandInfo = Get-Command $QQpath -ErrorAction Stop
|
||||
Start-Process powershell -ArgumentList "-noexit", "-noprofile", "-command &{& '$($commandInfo.Path)' $Bootfile $params}"
|
||||
21
script/napcat.sh
Normal file
21
script/napcat.sh
Normal file
@@ -0,0 +1,21 @@
|
||||
#!/bin/bash
|
||||
|
||||
get_script_dir() {
|
||||
local script_path="${1:-$0}"
|
||||
local script_dir
|
||||
script_path=$(readlink -f "$script_path")
|
||||
script_dir=$(dirname "$script_path")
|
||||
|
||||
echo "$script_dir"
|
||||
}
|
||||
|
||||
SCRIPT_DIR=$(get_script_dir)
|
||||
|
||||
export ELECTRON_RUN_AS_NODE=1
|
||||
|
||||
if ! [ -x /opt/QQ/qq ]; then
|
||||
echo "Error: /opt/QQ/qq is not executable or does not exist." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
/opt/QQ/qq "${SCRIPT_DIR}/napcat.mjs" "$@"
|
||||
124
src/common/server/http.ts
Normal file
124
src/common/server/http.ts
Normal file
@@ -0,0 +1,124 @@
|
||||
import express, { Express, Request, Response } from 'express';
|
||||
import cors from 'cors';
|
||||
import http from 'http';
|
||||
import { log, logDebug, logError } from '../utils/log';
|
||||
import { ob11Config } from '@/onebot11/config';
|
||||
|
||||
type RegisterHandler = (res: Response, payload: any) => Promise<any>
|
||||
|
||||
export abstract class HttpServerBase {
|
||||
name: string = 'NapCatQQ';
|
||||
private readonly expressAPP: Express;
|
||||
private server: http.Server | null = null;
|
||||
|
||||
constructor() {
|
||||
this.expressAPP = express();
|
||||
this.expressAPP.use(cors());
|
||||
this.expressAPP.use(express.urlencoded({ extended: true, limit: '5000mb' }));
|
||||
this.expressAPP.use((req, res, next) => {
|
||||
// 兼容处理没有带content-type的请求
|
||||
// log("req.headers['content-type']", req.headers['content-type'])
|
||||
req.headers['content-type'] = 'application/json';
|
||||
const originalJson = express.json({ limit: '5000mb' });
|
||||
// 调用原始的express.json()处理器
|
||||
originalJson(req, res, (err) => {
|
||||
if (err) {
|
||||
logError('Error parsing JSON:', err);
|
||||
return res.status(400).send('Invalid JSON');
|
||||
}
|
||||
next();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
authorize(req: Request, res: Response, next: () => void) {
|
||||
const serverToken = ob11Config.token;
|
||||
let clientToken = '';
|
||||
const authHeader = req.get('authorization');
|
||||
if (authHeader) {
|
||||
clientToken = authHeader.split('Bearer ').pop() || '';
|
||||
//logDebug('receive http header token', clientToken);
|
||||
} else if (req.query.access_token) {
|
||||
if (Array.isArray(req.query.access_token)) {
|
||||
clientToken = req.query.access_token[0].toString();
|
||||
} else {
|
||||
clientToken = req.query.access_token.toString();
|
||||
}
|
||||
//logDebug('receive http url token', clientToken);
|
||||
}
|
||||
|
||||
if (serverToken && clientToken != serverToken) {
|
||||
return res.status(403).send(JSON.stringify({ message: 'token verify failed!' }));
|
||||
}
|
||||
next();
|
||||
}
|
||||
|
||||
start(port: number, host: string) {
|
||||
try {
|
||||
this.expressAPP.get('/', (req: Request, res: Response) => {
|
||||
res.send(`${this.name}已启动`);
|
||||
});
|
||||
this.listen(port, host);
|
||||
} catch (e: any) {
|
||||
logError('HTTP服务启动失败', e.toString());
|
||||
// httpServerError = "HTTP服务启动失败, " + e.toString()
|
||||
}
|
||||
}
|
||||
|
||||
stop() {
|
||||
// httpServerError = ""
|
||||
if (this.server) {
|
||||
this.server.close();
|
||||
this.server = null;
|
||||
}
|
||||
}
|
||||
|
||||
restart(port: number, host: string) {
|
||||
this.stop();
|
||||
this.start(port, host);
|
||||
}
|
||||
|
||||
abstract handleFailed(res: Response, payload: any, err: any): void
|
||||
|
||||
registerRouter(method: 'post' | 'get' | string, url: string, handler: RegisterHandler) {
|
||||
if (!url.startsWith('/')) {
|
||||
url = '/' + url;
|
||||
}
|
||||
|
||||
// @ts-expect-error wait fix
|
||||
if (!this.expressAPP[method]) {
|
||||
const err = `${this.name} register router failed,${method} not exist`;
|
||||
logError(err);
|
||||
throw err;
|
||||
}
|
||||
// @ts-expect-error wait fix
|
||||
this.expressAPP[method](url, this.authorize, async (req: Request, res: Response) => {
|
||||
let payload = req.body;
|
||||
if (method == 'get') {
|
||||
payload = req.query;
|
||||
} else if (req.query) {
|
||||
payload = { ...req.query, ...req.body };
|
||||
}
|
||||
logDebug('收到http请求', url, payload);
|
||||
try {
|
||||
res.send(await handler(res, payload));
|
||||
} catch (e: any) {
|
||||
this.handleFailed(res, payload, e.stack.toString());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected listen(port: number, host: string = '0.0.0.0') {
|
||||
host = host || '0.0.0.0';
|
||||
try {
|
||||
this.server = this.expressAPP.listen(port, host, () => {
|
||||
const info = `${this.name} started ${host}:${port}`;
|
||||
log(info);
|
||||
}).on('error', (err) => {
|
||||
logError('HTTP服务启动失败', err.toString());
|
||||
});
|
||||
} catch (e: any) {
|
||||
logError('HTTP服务启动失败, 请检查监听的ip地址和端口', e.stack.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
105
src/common/server/websocket.ts
Normal file
105
src/common/server/websocket.ts
Normal file
@@ -0,0 +1,105 @@
|
||||
import { WebSocket, WebSocketServer } from 'ws';
|
||||
import urlParse from 'url';
|
||||
import { IncomingMessage } from 'node:http';
|
||||
import { log } from '@/common/utils/log';
|
||||
|
||||
class WebsocketClientBase {
|
||||
private wsClient: WebSocket | undefined;
|
||||
|
||||
constructor() {
|
||||
}
|
||||
|
||||
send(msg: string) {
|
||||
if (this.wsClient && this.wsClient.readyState == WebSocket.OPEN) {
|
||||
this.wsClient.send(msg);
|
||||
}
|
||||
}
|
||||
|
||||
onMessage(msg: string) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
export class WebsocketServerBase {
|
||||
private ws: WebSocketServer | null = null;
|
||||
public token: string = '';
|
||||
|
||||
constructor() {
|
||||
}
|
||||
|
||||
start(port: number, host: string = '') {
|
||||
try {
|
||||
this.ws = new WebSocketServer({
|
||||
port,
|
||||
host: '',
|
||||
maxPayload: 1024 * 1024 * 1024
|
||||
}).on('error', () => {
|
||||
});
|
||||
log(`ws服务启动成功, ${host}:${port}`);
|
||||
} catch (e: any) {
|
||||
throw Error('ws服务启动失败, 请检查监听的ip和端口' + e.toString());
|
||||
}
|
||||
this.ws.on('connection', (wsClient, req) => {
|
||||
const url: string = req.url!.split('?').shift() || '/';
|
||||
this.authorize(wsClient, req);
|
||||
this.onConnect(wsClient, url, req);
|
||||
wsClient.on('message', async (msg) => {
|
||||
this.onMessage(wsClient, url, msg.toString());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
stop() {
|
||||
this.ws && this.ws.close((err) => {
|
||||
log('ws server close failed!', err);
|
||||
});
|
||||
this.ws = null;
|
||||
}
|
||||
|
||||
restart(port: number) {
|
||||
this.stop();
|
||||
this.start(port);
|
||||
}
|
||||
|
||||
authorize(wsClient: WebSocket, req: IncomingMessage) {
|
||||
const url = req.url!.split('?').shift();
|
||||
log('ws connect', url);
|
||||
let clientToken: string = '';
|
||||
const authHeader = req.headers['authorization'];
|
||||
if (authHeader) {
|
||||
clientToken = authHeader.split('Bearer ').pop() || '';
|
||||
log('receive ws header token', clientToken);
|
||||
} else {
|
||||
const parsedUrl = urlParse.parse(req.url || '/', true);
|
||||
const urlToken = parsedUrl.query.access_token;
|
||||
if (urlToken) {
|
||||
if (Array.isArray(urlToken)) {
|
||||
clientToken = urlToken[0];
|
||||
} else {
|
||||
clientToken = urlToken;
|
||||
}
|
||||
log('receive ws url token', clientToken);
|
||||
}
|
||||
}
|
||||
if (this.token && clientToken != this.token) {
|
||||
this.authorizeFailed(wsClient);
|
||||
return wsClient.close();
|
||||
}
|
||||
}
|
||||
|
||||
authorizeFailed(wsClient: WebSocket) {
|
||||
|
||||
}
|
||||
|
||||
onConnect(wsClient: WebSocket, url: string, req: IncomingMessage) {
|
||||
|
||||
}
|
||||
|
||||
onMessage(wsClient: WebSocket, url: string, msg: string) {
|
||||
|
||||
}
|
||||
|
||||
sendHeart() {
|
||||
|
||||
}
|
||||
}
|
||||
36
src/common/utils/AsyncQueue.ts
Normal file
36
src/common/utils/AsyncQueue.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { sleep } from '@/common/utils/helper';
|
||||
import { logError } from './log';
|
||||
type AsyncQueueTask = (() => void) | (()=>Promise<void>);
|
||||
|
||||
|
||||
export class AsyncQueue {
|
||||
private tasks: (AsyncQueueTask)[] = [];
|
||||
|
||||
public addTask(task: AsyncQueueTask) {
|
||||
this.tasks.push(task);
|
||||
// console.log('addTask', this.tasks.length);
|
||||
if (this.tasks.length === 1) {
|
||||
this.runQueue().then().catch(()=>{});
|
||||
}
|
||||
}
|
||||
|
||||
private async runQueue() {
|
||||
// console.log('runQueue', this.tasks.length);
|
||||
while (this.tasks.length > 0) {
|
||||
const task = this.tasks[0];
|
||||
// console.log('typeof task', typeof task);
|
||||
try {
|
||||
const taskRet = task();
|
||||
// console.log('type of taskRet', typeof taskRet, taskRet);
|
||||
if (taskRet instanceof Promise) {
|
||||
await taskRet;
|
||||
}
|
||||
} catch (e) {
|
||||
// console.error(e);
|
||||
logError(e);
|
||||
}
|
||||
this.tasks.shift();
|
||||
await sleep(100);
|
||||
}
|
||||
}
|
||||
}
|
||||
73
src/common/utils/ConfigBase.ts
Normal file
73
src/common/utils/ConfigBase.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import path from 'node:path';
|
||||
import fs from 'node:fs';
|
||||
import { log, logDebug, logError } from '@/common/utils/log';
|
||||
import { dirname } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
const configDir = path.resolve(__dirname, 'config');
|
||||
fs.mkdirSync(configDir, { recursive: true });
|
||||
|
||||
|
||||
export class ConfigBase<T>{
|
||||
|
||||
constructor() {
|
||||
}
|
||||
|
||||
protected getKeys(): string[] | null {
|
||||
// 决定 key 在json配置文件中的顺序
|
||||
return null;
|
||||
}
|
||||
|
||||
getConfigDir(){
|
||||
const configDir = path.resolve(__dirname, 'config');
|
||||
fs.mkdirSync(configDir, { recursive: true });
|
||||
return configDir;
|
||||
}
|
||||
getConfigPath(): string {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
read() {
|
||||
const configPath = this.getConfigPath();
|
||||
if (!fs.existsSync(configPath)) {
|
||||
try{
|
||||
fs.writeFileSync(configPath, JSON.stringify(this, this.getKeys(), 2));
|
||||
log(`配置文件${configPath}已创建\n如果修改此文件后需要重启 NapCat 生效`);
|
||||
}
|
||||
catch (e: any) {
|
||||
logError(`创建配置文件 ${configPath} 时发生错误:`, e.message);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
try {
|
||||
const data = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
||||
logDebug(`配置文件${configPath}已加载`, data);
|
||||
Object.assign(this, data);
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-expect-error
|
||||
this.save(this); // 保存一次,让新版本的字段写入
|
||||
return this;
|
||||
} catch (e: any) {
|
||||
if (e instanceof SyntaxError) {
|
||||
logError(`配置文件 ${configPath} 格式错误,请检查配置文件:`, e.message);
|
||||
} else {
|
||||
logError(`读取配置文件 ${configPath} 时发生错误:`, e.message);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
save(config: T) {
|
||||
Object.assign(this, config);
|
||||
const configPath = this.getConfigPath();
|
||||
try {
|
||||
fs.writeFileSync(configPath, JSON.stringify(this, this.getKeys(), 2));
|
||||
} catch (e: any) {
|
||||
logError(`保存配置文件 ${configPath} 时发生错误:`, e.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
182
src/common/utils/EventTask.ts
Normal file
182
src/common/utils/EventTask.ts
Normal file
@@ -0,0 +1,182 @@
|
||||
import { NodeIKernelMsgListener } from '@/core';
|
||||
import { NodeIQQNTWrapperSession } from '@/core/wrapper';
|
||||
import { randomUUID } from 'crypto';
|
||||
|
||||
interface Internal_MapKey {
|
||||
timeout: number,
|
||||
createtime: number,
|
||||
func: (...arg: any[]) => any,
|
||||
}
|
||||
|
||||
export class ListenerClassBase {
|
||||
[key: string]: string;
|
||||
}
|
||||
|
||||
export interface ListenerIBase {
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-new
|
||||
new(listener: any): ListenerClassBase;
|
||||
}
|
||||
|
||||
export class NTEventWrapper {
|
||||
|
||||
private ListenerMap: { [key: string]: ListenerIBase } | undefined;//ListenerName-Unique -> Listener构造函数
|
||||
private WrapperSession: NodeIQQNTWrapperSession | undefined;//WrapperSession
|
||||
private ListenerManger: Map<string, ListenerClassBase> = new Map<string, ListenerClassBase>(); //ListenerName-Unique -> Listener实例
|
||||
private EventTask = new Map<string, Map<string, Map<string, Internal_MapKey>>>();//tasks ListenerMainName -> ListenerSubName-> uuid -> {timeout,createtime,func}
|
||||
constructor() {
|
||||
|
||||
}
|
||||
createProxyDispatch(ListenerMainName: string) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
||||
const current = this;
|
||||
return new Proxy({}, {
|
||||
get(target: any, prop: any, receiver: any) {
|
||||
// console.log('get', prop, typeof target[prop]);
|
||||
if (typeof target[prop] === 'undefined') {
|
||||
// 如果方法不存在,返回一个函数,这个函数调用existentMethod
|
||||
return (...args: any[]) => {
|
||||
current.DispatcherListener.apply(current, [ListenerMainName, prop, ...args]).then();
|
||||
};
|
||||
}
|
||||
// 如果方法存在,正常返回
|
||||
return Reflect.get(target, prop, receiver);
|
||||
}
|
||||
});
|
||||
}
|
||||
init({ ListenerMap, WrapperSession }: { ListenerMap: { [key: string]: typeof ListenerClassBase }, WrapperSession: NodeIQQNTWrapperSession }) {
|
||||
this.ListenerMap = ListenerMap;
|
||||
this.WrapperSession = WrapperSession;
|
||||
}
|
||||
CreatEventFunction<T extends (...args: any) => any>(eventName: string): T | undefined {
|
||||
const eventNameArr = eventName.split('/');
|
||||
type eventType = {
|
||||
[key: string]: () => { [key: string]: (...params: Parameters<T>) => Promise<ReturnType<T>> }
|
||||
}
|
||||
if (eventNameArr.length > 1) {
|
||||
const serviceName = 'get' + eventNameArr[0].replace('NodeIKernel', '');
|
||||
const eventName = eventNameArr[1];
|
||||
//getNodeIKernelGroupListener,GroupService
|
||||
//console.log('2', eventName);
|
||||
const services = (this.WrapperSession as unknown as eventType)[serviceName]();
|
||||
let event = services[eventName];
|
||||
//重新绑定this
|
||||
event = event.bind(services);
|
||||
if (event) {
|
||||
return event as T;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
}
|
||||
CreatListenerFunction<T>(listenerMainName: string, uniqueCode: string = ''): T {
|
||||
const ListenerType = this.ListenerMap![listenerMainName];
|
||||
let Listener = this.ListenerManger.get(listenerMainName + uniqueCode);
|
||||
if (!Listener && ListenerType) {
|
||||
Listener = new ListenerType(this.createProxyDispatch(listenerMainName));
|
||||
const ServiceSubName = listenerMainName.match(/^NodeIKernel(.*?)Listener$/)![1];
|
||||
const Service = 'NodeIKernel' + ServiceSubName + 'Service/addKernel' + ServiceSubName + 'Listener';
|
||||
const addfunc = this.CreatEventFunction<(listener: T) => number>(Service);
|
||||
addfunc!(Listener as T);
|
||||
//console.log(addfunc!(Listener as T));
|
||||
this.ListenerManger.set(listenerMainName + uniqueCode, Listener);
|
||||
}
|
||||
return Listener as T;
|
||||
}
|
||||
//统一回调清理事件
|
||||
async DispatcherListener(ListenerMainName: string, ListenerSubName: string, ...args: any[]) {
|
||||
//console.log(ListenerMainName, this.EventTask.get(ListenerMainName), ListenerSubName, this.EventTask.get(ListenerMainName)?.get(ListenerSubName));
|
||||
this.EventTask.get(ListenerMainName)?.get(ListenerSubName)?.forEach((task, uuid) => {
|
||||
//console.log(task.func, uuid, task.createtime, task.timeout);
|
||||
if (task.createtime + task.timeout < Date.now()) {
|
||||
this.EventTask.get(ListenerMainName)?.get(ListenerSubName)?.delete(uuid);
|
||||
return;
|
||||
}
|
||||
task.func(...args);
|
||||
});
|
||||
}
|
||||
async CallNoListenerEvent<EventType extends (...args: any[]) => Promise<any>,>(EventName = '', timeout: number = 3000, ...args: Parameters<EventType>) {
|
||||
return new Promise<ReturnType<EventType>>(async (resolve, reject) => {
|
||||
const EventFunc = this.CreatEventFunction<EventType>(EventName);
|
||||
let complete = false;
|
||||
const Timeouter = setTimeout(() => {
|
||||
if (!complete) {
|
||||
reject(new Error('NTEvent EventName:' + EventName + ' timeout'));
|
||||
}
|
||||
}, timeout);
|
||||
const retData = await EventFunc!(...args);
|
||||
complete = true;
|
||||
resolve(retData);
|
||||
});
|
||||
}
|
||||
async CallNormalEvent<EventType extends (...args: any[]) => Promise<any>, ListenerType extends (...args: any[]) => void>(EventName = '', ListenerName = '', waitTimes = 1, timeout: number = 3000, ...args: Parameters<EventType>) {
|
||||
return new Promise<[EventRet: Awaited<ReturnType<EventType>>, ...Parameters<ListenerType>]>(async (resolve, reject) => {
|
||||
const id = randomUUID();
|
||||
let complete = 0;
|
||||
let retData: Parameters<ListenerType> | undefined = undefined;
|
||||
let retEvent: any = {};
|
||||
const databack = () => {
|
||||
if (complete < waitTimes) {
|
||||
reject(new Error('NTEvent EventName:' + EventName + ' ListenerName:' + ListenerName + ' timeout'));
|
||||
} else {
|
||||
resolve([retEvent as Awaited<ReturnType<EventType>>, ...retData!]);
|
||||
}
|
||||
};
|
||||
const Timeouter = setTimeout(databack, timeout);
|
||||
|
||||
const ListenerNameList = ListenerName.split('/');
|
||||
const ListenerMainName = ListenerNameList[0];
|
||||
const ListenerSubName = ListenerNameList[1];
|
||||
const eventCallbak = {
|
||||
timeout: timeout,
|
||||
createtime: Date.now(),
|
||||
func: (...args: any[]) => {
|
||||
complete++;
|
||||
//console.log('func', ...args);
|
||||
retData = args as Parameters<ListenerType>;
|
||||
if (complete >= waitTimes) {
|
||||
clearTimeout(Timeouter);
|
||||
databack();
|
||||
}
|
||||
}
|
||||
};
|
||||
if (!this.EventTask.get(ListenerMainName)) {
|
||||
this.EventTask.set(ListenerMainName, new Map());
|
||||
}
|
||||
if (!(this.EventTask.get(ListenerMainName)?.get(ListenerSubName))) {
|
||||
this.EventTask.get(ListenerMainName)?.set(ListenerSubName, new Map());
|
||||
}
|
||||
this.EventTask.get(ListenerMainName)?.get(ListenerSubName)?.set(id, eventCallbak);
|
||||
this.CreatListenerFunction(ListenerMainName);
|
||||
const EventFunc = this.CreatEventFunction<EventType>(EventName);
|
||||
//console.log("测试打点", args);
|
||||
retEvent = await EventFunc!(...(args as any[]));
|
||||
});
|
||||
}
|
||||
}
|
||||
export const NTEventDispatch = new NTEventWrapper();
|
||||
|
||||
// 示例代码 快速创建事件
|
||||
// let NTEvent = new NTEventWrapper();
|
||||
// let TestEvent = NTEvent.CreatEventFunction<(force: boolean) => Promise<Number>>('NodeIKernelProfileLikeService/GetTest');
|
||||
// if (TestEvent) {
|
||||
// TestEvent(true);
|
||||
// }
|
||||
|
||||
// 示例代码 快速创建监听Listener类
|
||||
// let NTEvent = new NTEventWrapper();
|
||||
// NTEvent.CreatListenerFunction<NodeIKernelMsgListener>('NodeIKernelMsgListener', 'core')
|
||||
|
||||
|
||||
// 调用接口
|
||||
//let NTEvent = new NTEventWrapper();
|
||||
//let ret = await NTEvent.CallNormalEvent<(force: boolean) => Promise<Number>, (data1: string, data2: number) => void>('NodeIKernelProfileLikeService/GetTest', 'NodeIKernelMsgListener/onAddSendMsg', 1, 3000, true);
|
||||
|
||||
// 注册监听 解除监听
|
||||
// NTEventDispatch.RigisterListener('NodeIKernelMsgListener/onAddSendMsg','core',cb);
|
||||
// NTEventDispatch.UnRigisterListener('NodeIKernelMsgListener/onAddSendMsg','core');
|
||||
|
||||
// let GetTest = NTEventDispatch.CreatEvent('NodeIKernelProfileLikeService/GetTest','NodeIKernelMsgListener/onAddSendMsg',Mode);
|
||||
// GetTest('test');
|
||||
|
||||
// always模式
|
||||
// NTEventDispatch.CreatEvent('NodeIKernelProfileLikeService/GetTest','NodeIKernelMsgListener/onAddSendMsg',Mode,(...args:any[])=>{ console.log(args) });
|
||||
145
src/common/utils/LRUCache.ts
Normal file
145
src/common/utils/LRUCache.ts
Normal file
@@ -0,0 +1,145 @@
|
||||
import { logError, logDebug } from '@/common/utils/log';
|
||||
|
||||
type group_id = number;
|
||||
type user_id = number;
|
||||
|
||||
class cacheNode<T> {
|
||||
value: T;
|
||||
groupId: group_id;
|
||||
userId: user_id;
|
||||
prev: cacheNode<T> | null;
|
||||
next: cacheNode<T> | null;
|
||||
timestamp: number;
|
||||
|
||||
constructor(groupId: group_id, userId: user_id, value: T) {
|
||||
this.groupId = groupId;
|
||||
this.userId = userId;
|
||||
this.value = value;
|
||||
this.prev = null;
|
||||
this.next = null;
|
||||
this.timestamp = Date.now();
|
||||
}
|
||||
}
|
||||
|
||||
type cache<T> = { [key: group_id]: { [key: user_id]: cacheNode<T> } };
|
||||
class LRU<T> {
|
||||
private maxAge: number;
|
||||
private maxSize: number;
|
||||
private currentSize: number;
|
||||
private cache: cache<T>;
|
||||
private head: cacheNode<T> | null = null;
|
||||
private tail: cacheNode<T> | null = null;
|
||||
private onFuncs: ((node: cacheNode<T>) => void)[] = [];
|
||||
|
||||
constructor(maxAge: number = 2e4, maxSize: number = 5e3) {
|
||||
this.maxAge = maxAge;
|
||||
this.maxSize = maxSize;
|
||||
this.cache = Object.create(null);
|
||||
this.currentSize = 0;
|
||||
|
||||
if (maxSize == 0) return;
|
||||
setInterval(() => this.removeExpired(), this.maxAge);
|
||||
}
|
||||
|
||||
// 移除LRU节点
|
||||
private removeLRUNode(node: cacheNode<T>) {
|
||||
logDebug(
|
||||
'removeLRUNode',
|
||||
node.groupId,
|
||||
node.userId,
|
||||
node.value,
|
||||
this.currentSize
|
||||
);
|
||||
node.prev = node.next = null;
|
||||
delete this.cache[node.groupId][node.userId];
|
||||
this.removeNode(node);
|
||||
this.onFuncs.forEach((func) => func(node));
|
||||
this.currentSize--;
|
||||
}
|
||||
|
||||
public on(func: (node: cacheNode<T>) => void) {
|
||||
this.onFuncs.push(func);
|
||||
}
|
||||
|
||||
private removeExpired() {
|
||||
const now = Date.now();
|
||||
let current = this.tail;
|
||||
const nodesToRemove: cacheNode<T>[] = [];
|
||||
let removedCount = 0;
|
||||
|
||||
// 收集需要删除的节点
|
||||
while (current && now - current.timestamp > this.maxAge) {
|
||||
nodesToRemove.push(current);
|
||||
current = current.prev;
|
||||
removedCount++;
|
||||
if (removedCount >= 100) break;
|
||||
}
|
||||
|
||||
// 更新链表指向
|
||||
if (nodesToRemove.length > 0) {
|
||||
const newTail = nodesToRemove[nodesToRemove.length - 1].prev;
|
||||
if (newTail) {
|
||||
newTail.next = null;
|
||||
} else {
|
||||
this.head = null;
|
||||
}
|
||||
this.tail = newTail;
|
||||
}
|
||||
|
||||
nodesToRemove.forEach((node) => {
|
||||
node.prev = node.next = null;
|
||||
delete this.cache[node.groupId][node.userId];
|
||||
|
||||
this.currentSize--;
|
||||
this.onFuncs.forEach((func) => func(node));
|
||||
});
|
||||
}
|
||||
|
||||
private addNode(node: cacheNode<T>) {
|
||||
node.next = this.head;
|
||||
if (this.head) this.head.prev = node;
|
||||
if (!this.tail) this.tail = node;
|
||||
this.head = node;
|
||||
}
|
||||
|
||||
private removeNode(node: cacheNode<T>) {
|
||||
if (node.prev) node.prev.next = node.next;
|
||||
if (node.next) node.next.prev = node.prev;
|
||||
if (node === this.head) this.head = node.next;
|
||||
if (node === this.tail) this.tail = node.prev;
|
||||
}
|
||||
|
||||
private moveToHead(node: cacheNode<T>) {
|
||||
if (this.head === node) return;
|
||||
|
||||
this.removeNode(node);
|
||||
this.addNode(node);
|
||||
node.prev = null;
|
||||
}
|
||||
|
||||
public set(groupId: group_id, userId: user_id, value: T) {
|
||||
if (!this.cache[groupId]) {
|
||||
this.cache[groupId] = Object.create(null);
|
||||
}
|
||||
|
||||
const groupObject = this.cache[groupId];
|
||||
|
||||
if (groupObject[userId]) {
|
||||
const node = groupObject[userId];
|
||||
node.value = value;
|
||||
node.timestamp = Date.now();
|
||||
this.moveToHead(node);
|
||||
} else {
|
||||
const node = new cacheNode(groupId, userId, value);
|
||||
groupObject[userId] = node;
|
||||
this.currentSize++;
|
||||
this.addNode(node);
|
||||
if (this.currentSize > this.maxSize) {
|
||||
const tail = this.tail!;
|
||||
this.removeLRUNode(tail);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default LRU;
|
||||
73
src/common/utils/QQBasicInfo.ts
Normal file
73
src/common/utils/QQBasicInfo.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import path from 'node:path';
|
||||
import fs from 'node:fs';
|
||||
import os from 'node:os';
|
||||
import { systemPlatform } from '@/common/utils/system';
|
||||
import { logError } from '@/common/utils/log';
|
||||
|
||||
export const exePath = process.execPath;
|
||||
|
||||
export const pkgInfoPath = path.join(path.dirname(exePath), 'resources', 'app', 'package.json');
|
||||
let configVersionInfoPath;
|
||||
|
||||
if (os.platform() !== 'linux') {
|
||||
configVersionInfoPath = path.join(path.dirname(exePath), 'resources', 'app', 'versions', 'config.json');
|
||||
} else {
|
||||
const userPath = os.homedir();
|
||||
const appDataPath = path.resolve(userPath, './.config/QQ');
|
||||
configVersionInfoPath = path.resolve(appDataPath, './versions/config.json');
|
||||
}
|
||||
|
||||
if (typeof configVersionInfoPath !== 'string') {
|
||||
throw new Error('Something went wrong when load QQ info path');
|
||||
}
|
||||
|
||||
export { configVersionInfoPath };
|
||||
|
||||
type QQPkgInfo = {
|
||||
version: string;
|
||||
buildVersion: string;
|
||||
platform: string;
|
||||
eleArch: string;
|
||||
}
|
||||
type QQVersionConfigInfo = {
|
||||
baseVersion: string;
|
||||
curVersion: string;
|
||||
prevVersion: string;
|
||||
onErrorVersions: Array<any>;
|
||||
buildId: string;
|
||||
}
|
||||
|
||||
let _qqVersionConfigInfo: QQVersionConfigInfo = {
|
||||
'baseVersion': '9.9.9-23361',
|
||||
'curVersion': '9.9.9-23361',
|
||||
'prevVersion': '',
|
||||
'onErrorVersions': [],
|
||||
'buildId': '23361'
|
||||
};
|
||||
|
||||
if (fs.existsSync(configVersionInfoPath)) {
|
||||
try {
|
||||
const _ =JSON.parse(fs.readFileSync(configVersionInfoPath).toString());
|
||||
_qqVersionConfigInfo = Object.assign(_qqVersionConfigInfo, _);
|
||||
} catch (e) {
|
||||
logError('Load QQ version config info failed, Use default version', e);
|
||||
}
|
||||
}
|
||||
|
||||
export const qqVersionConfigInfo: QQVersionConfigInfo = _qqVersionConfigInfo;
|
||||
|
||||
export const qqPkgInfo: QQPkgInfo = JSON.parse(fs.readFileSync(pkgInfoPath).toString());
|
||||
// platform_type: 3,
|
||||
// app_type: 4,
|
||||
// app_version: '9.9.9-23159',
|
||||
// qua: 'V1_WIN_NQ_9.9.9_23159_GW_B',
|
||||
// appid: '537213764',
|
||||
// platVer: '10.0.26100',
|
||||
// clientVer: '9.9.9-23159',
|
||||
|
||||
let _appid: string = '537213803'; // 默认为 Windows 平台的 appid
|
||||
if (systemPlatform === 'linux') {
|
||||
_appid = '537213827';
|
||||
}
|
||||
// todo: mac 平台的 appid
|
||||
export const appid = _appid;
|
||||
135
src/common/utils/audio.ts
Normal file
135
src/common/utils/audio.ts
Normal file
@@ -0,0 +1,135 @@
|
||||
import fs from 'fs';
|
||||
import { encode, getDuration, getWavFileInfo, isWav } from 'silk-wasm';
|
||||
import fsPromise from 'fs/promises';
|
||||
import { log, logError } from './log';
|
||||
import path from 'node:path';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { spawn } from 'node:child_process';
|
||||
import { getTempDir } from '@/common/utils/file';
|
||||
|
||||
let TEMP_DIR = './';
|
||||
setTimeout(() => {
|
||||
TEMP_DIR = getTempDir();
|
||||
}, 100);
|
||||
export async function encodeSilk(filePath: string) {
|
||||
function getFileHeader(filePath: string) {
|
||||
// 定义要读取的字节数
|
||||
const bytesToRead = 7;
|
||||
try {
|
||||
const buffer = fs.readFileSync(filePath, {
|
||||
encoding: null,
|
||||
flag: 'r',
|
||||
});
|
||||
|
||||
const fileHeader = buffer.toString('hex', 0, bytesToRead);
|
||||
return fileHeader;
|
||||
} catch (err) {
|
||||
logError('读取文件错误:', err);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
async function isWavFile(filePath: string) {
|
||||
return isWav(fs.readFileSync(filePath));
|
||||
}
|
||||
|
||||
async function guessDuration(pttPath: string) {
|
||||
const pttFileInfo = await fsPromise.stat(pttPath);
|
||||
let duration = pttFileInfo.size / 1024 / 3; // 3kb/s
|
||||
duration = Math.floor(duration);
|
||||
duration = Math.max(1, duration);
|
||||
log('通过文件大小估算语音的时长:', duration);
|
||||
return duration;
|
||||
}
|
||||
|
||||
// function verifyDuration(oriDuration: number, guessDuration: number) {
|
||||
// // 单位都是秒
|
||||
// if (oriDuration - guessDuration > 10) {
|
||||
// return guessDuration
|
||||
// }
|
||||
// oriDuration = Math.max(1, oriDuration)
|
||||
// return oriDuration
|
||||
// }
|
||||
// async function getAudioSampleRate(filePath: string) {
|
||||
// try {
|
||||
// const mm = await import('music-metadata');
|
||||
// const metadata = await mm.parseFile(filePath);
|
||||
// log(`${filePath}采样率`, metadata.format.sampleRate);
|
||||
// return metadata.format.sampleRate;
|
||||
// } catch (error) {
|
||||
// log(`${filePath}采样率获取失败`, error.stack);
|
||||
// // console.error(error);
|
||||
// }
|
||||
// }
|
||||
|
||||
try {
|
||||
const pttPath = path.join(TEMP_DIR, uuidv4());
|
||||
if (getFileHeader(filePath) !== '02232153494c4b') {
|
||||
log(`语音文件${filePath}需要转换成silk`);
|
||||
const _isWav = await isWavFile(filePath);
|
||||
const pcmPath = pttPath + '.pcm';
|
||||
let sampleRate = 0;
|
||||
const convert = () => {
|
||||
return new Promise<Buffer>((resolve, reject) => {
|
||||
// todo: 通过配置文件获取ffmpeg路径
|
||||
const ffmpegPath = process.env.FFMPEG_PATH || 'ffmpeg';
|
||||
const cp = spawn(ffmpegPath, ['-y', '-i', filePath, '-ar', '24000', '-ac', '1', '-f', 's16le', pcmPath]);
|
||||
cp.on('error', err => {
|
||||
log('FFmpeg处理转换出错: ', err.message);
|
||||
return reject(err);
|
||||
});
|
||||
cp.on('exit', (code, signal) => {
|
||||
const EXIT_CODES = [0, 255];
|
||||
if (code == null || EXIT_CODES.includes(code)) {
|
||||
sampleRate = 24000;
|
||||
const data = fs.readFileSync(pcmPath);
|
||||
fs.unlink(pcmPath, (err) => {
|
||||
});
|
||||
return resolve(data);
|
||||
}
|
||||
log(`FFmpeg exit: code=${code ?? 'unknown'} sig=${signal ?? 'unknown'}`);
|
||||
reject(Error('FFmpeg处理转换失败'));
|
||||
});
|
||||
});
|
||||
};
|
||||
let input: Buffer;
|
||||
if (!_isWav) {
|
||||
input = await convert();
|
||||
} else {
|
||||
input = fs.readFileSync(filePath);
|
||||
const allowSampleRate = [8000, 12000, 16000, 24000, 32000, 44100, 48000];
|
||||
const { fmt } = getWavFileInfo(input);
|
||||
// log(`wav文件信息`, fmt)
|
||||
if (!allowSampleRate.includes(fmt.sampleRate)) {
|
||||
input = await convert();
|
||||
}
|
||||
}
|
||||
const silk = await encode(input, sampleRate);
|
||||
fs.writeFileSync(pttPath, silk.data);
|
||||
log(`语音文件${filePath}转换成功!`, pttPath, '时长:', silk.duration);
|
||||
return {
|
||||
converted: true,
|
||||
path: pttPath,
|
||||
duration: silk.duration / 1000
|
||||
};
|
||||
} else {
|
||||
const silk = fs.readFileSync(filePath);
|
||||
let duration = 0;
|
||||
try {
|
||||
duration = getDuration(silk) / 1000;
|
||||
} catch (e: any) {
|
||||
log('获取语音文件时长失败, 使用文件大小推测时长', filePath, e.stack);
|
||||
duration = await guessDuration(filePath);
|
||||
}
|
||||
|
||||
return {
|
||||
converted: false,
|
||||
path: filePath,
|
||||
duration,
|
||||
};
|
||||
}
|
||||
} catch (error: any) {
|
||||
logError('convert silk failed', error.stack);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
23
src/common/utils/cpmodule.ts
Normal file
23
src/common/utils/cpmodule.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import * as os from 'os';
|
||||
import path from 'node:path';
|
||||
import fs from 'fs';
|
||||
import { dirname } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
export function getModuleWithArchName(moduleName: string) {
|
||||
const systemPlatform = os.platform();
|
||||
const cpuArch = os.arch();
|
||||
return `${moduleName}-${systemPlatform}-${cpuArch}.node`;
|
||||
}
|
||||
|
||||
export function cpModule(moduleName: string) {
|
||||
const currentDir = path.resolve(__dirname);
|
||||
const fileName = `./${getModuleWithArchName(moduleName)}`;
|
||||
try {
|
||||
fs.copyFileSync(path.join(currentDir, fileName), path.join(currentDir, `${moduleName}.node`));
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
509
src/common/utils/db.ts
Normal file
509
src/common/utils/db.ts
Normal file
@@ -0,0 +1,509 @@
|
||||
import { ElementType, FileElement, PicElement, PttElement, RawMessage, VideoElement } from '../../core/src/entities';
|
||||
|
||||
import sqlite3 from 'sqlite3';
|
||||
import { log, logDebug, logError } from '@/common/utils/log';
|
||||
import { NTQQMsgApi } from '@/core';
|
||||
import LRU from '@/common/utils/LRUCache';
|
||||
|
||||
export interface IRember {
|
||||
last_sent_time: number;
|
||||
join_time: number;
|
||||
user_id: number;
|
||||
}
|
||||
|
||||
|
||||
type DBMsg = {
|
||||
id: number,
|
||||
shortId: number,
|
||||
longId: string,
|
||||
seq: number,
|
||||
peerUid: string,
|
||||
chatType: number,
|
||||
}
|
||||
|
||||
type DBFile = {
|
||||
name: string; // 文件名
|
||||
path: string;
|
||||
url: string;
|
||||
size: number;
|
||||
uuid: string;
|
||||
msgId: string;
|
||||
elementId: string;
|
||||
element: PicElement | VideoElement | FileElement | PttElement;
|
||||
elementType: ElementType.PIC | ElementType.VIDEO | ElementType.FILE | ElementType.PTT;
|
||||
}
|
||||
|
||||
|
||||
class DBUtilBase {
|
||||
protected db: sqlite3.Database | undefined;
|
||||
|
||||
async init(dbPath: string) {
|
||||
if (this.db) {
|
||||
return;
|
||||
}
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
this.db = new sqlite3.Database(dbPath, sqlite3.OPEN_READWRITE | sqlite3.OPEN_CREATE, (err) => {
|
||||
if (err) {
|
||||
logError('Could not connect to database', err);
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
this.createTable();
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
protected createTable() {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
close() {
|
||||
this.db?.close();
|
||||
}
|
||||
}
|
||||
|
||||
class DBUtil extends DBUtilBase {
|
||||
private msgCache: Map<string | number, RawMessage> = new Map<string | number, RawMessage>();
|
||||
private globalMsgShortId = -2147483640;
|
||||
private groupIds: number[] = [];
|
||||
private LURCache = new LRU<number>();
|
||||
private LastSentCache = new (class {
|
||||
private cache: { gid: number; uid: number }[] = [];
|
||||
private maxSize: number;
|
||||
|
||||
constructor(maxSize: number = 5000) {
|
||||
this.maxSize = maxSize;
|
||||
}
|
||||
|
||||
get(gid: number, uid: number): boolean {
|
||||
const exists = this.cache.some(
|
||||
(entry) => entry.gid === gid && entry.uid === uid
|
||||
);
|
||||
if (!exists) {
|
||||
this.cache.push({ gid, uid });
|
||||
if (this.cache.length > this.maxSize) {
|
||||
this.cache.shift();
|
||||
}
|
||||
}
|
||||
|
||||
return exists;
|
||||
}
|
||||
})();
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
const interval = 1000 * 60 * 10; // 10分钟清理一次缓存
|
||||
setInterval(() => {
|
||||
logDebug('清理消息缓存');
|
||||
this.msgCache.forEach((msg, key) => {
|
||||
if ((Date.now() - parseInt(msg.msgTime) * 1000) > interval) {
|
||||
this.msgCache.delete(key);
|
||||
}
|
||||
});
|
||||
}, interval);
|
||||
}
|
||||
|
||||
async init(dbPath: string) {
|
||||
await super.init(dbPath);
|
||||
this.globalMsgShortId = await this.getCurrentMaxShortId();
|
||||
|
||||
|
||||
// 初始化群缓存列表
|
||||
this.db!.serialize(() => {
|
||||
const sql = 'SELECT * FROM sqlite_master WHERE type=\'table\'';
|
||||
this.db!.all(sql, [], (err, rows: { name: string }[]) => {
|
||||
if (err) return logError(err);
|
||||
rows.forEach((row) => this.groupIds.push(parseInt(row.name)));
|
||||
//logDebug(`已加载 ${groupIds.length} 个群`);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
this.LURCache.on(async (node) => {
|
||||
const { value: time, groupId, userId } = node;
|
||||
|
||||
logDebug('插入发言时间', userId, groupId);
|
||||
await this.createGroupInfoTimeTableIfNotExist(groupId);
|
||||
|
||||
const method = await this.getDataSetMethod(groupId, userId);
|
||||
logDebug('插入发言时间方法判断', userId, groupId, method);
|
||||
|
||||
const sql =
|
||||
method == 'update'
|
||||
? `UPDATE "${groupId}" SET last_sent_time = ? WHERE user_id = ?`
|
||||
: `INSERT INTO "${groupId}" (last_sent_time, user_id) VALUES (?, ?)`;
|
||||
|
||||
this.db!.all(sql, [time, userId], (err) => {
|
||||
if (err) {
|
||||
return logError('插入/更新发言时间失败', userId, groupId);
|
||||
}
|
||||
logDebug('插入/更新发言时间成功', userId, groupId);
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
async getDataSetMethod(groupId: number, userId: number) {
|
||||
// 缓存记录
|
||||
if (this.LastSentCache.get(groupId, userId)) {
|
||||
logDebug('缓存命中', userId, groupId);
|
||||
return 'update';
|
||||
}
|
||||
|
||||
// 数据库判断
|
||||
return new Promise<'insert' | 'update'>((resolve, reject) => {
|
||||
this.db!.all(
|
||||
`SELECT * FROM "${groupId}" WHERE user_id = ?`,
|
||||
[userId],
|
||||
(err, rows) => {
|
||||
if (err) {
|
||||
logError('查询发言时间存在失败', userId, groupId, err);
|
||||
return logError('插入发言时间失败', userId, groupId, err);
|
||||
}
|
||||
|
||||
if (rows.length === 0) {
|
||||
logDebug('查询发言时间不存在', userId, groupId);
|
||||
return resolve('insert');
|
||||
}
|
||||
|
||||
logDebug('查询发言时间存在', userId, groupId);
|
||||
resolve('update');
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
async createGroupInfoTimeTableIfNotExist(groupId: number) {
|
||||
const createTableSQL = (groupId: number) =>
|
||||
`CREATE TABLE IF NOT EXISTS "${groupId}" (
|
||||
user_id INTEGER,
|
||||
last_sent_time INTEGER,
|
||||
join_time INTEGER,
|
||||
PRIMARY KEY (user_id)
|
||||
);`;
|
||||
|
||||
if (this.groupIds.includes(groupId)) {
|
||||
return;
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
const sql = createTableSQL(groupId);
|
||||
this.db!.all(sql, (err) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
this.groupIds.push(groupId);
|
||||
resolve(true);
|
||||
});
|
||||
});
|
||||
}
|
||||
protected createTable() {
|
||||
// 消息记录
|
||||
const createTableSQL = `
|
||||
CREATE TABLE IF NOT EXISTS msgs (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
shortId INTEGER NOT NULL UNIQUE,
|
||||
longId TEXT NOT NULL UNIQUE,
|
||||
seq INTEGER NOT NULL,
|
||||
peerUid TEXT NOT NULL,
|
||||
chatType INTEGER NOT NULL
|
||||
)`;
|
||||
this.db!.run(createTableSQL, function (err) {
|
||||
if (err) {
|
||||
logError('Could not create table msgs', err.stack);
|
||||
}
|
||||
});
|
||||
|
||||
// 文件缓存
|
||||
const createFileTableSQL = `
|
||||
CREATE TABLE IF NOT EXISTS files (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
path TEXT NOT NULL,
|
||||
url TEXT,
|
||||
size INTEGER NOT NULL,
|
||||
uuid TEXT,
|
||||
elementType INTEGER,
|
||||
element TEXT NOT NULL,
|
||||
elementId TEXT NOT NULL,
|
||||
msgId TEXT NOT NULL
|
||||
)`;
|
||||
this.db!.run(createFileTableSQL, function (err) {
|
||||
if (err) {
|
||||
logError('Could not create table files', err);
|
||||
}
|
||||
});
|
||||
|
||||
// 接收到的临时会话消息uid
|
||||
const createTempUinTableSQL = `
|
||||
CREATE TABLE IF NOT EXISTS temp_uins (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
uid TEXT,
|
||||
uin TEXT
|
||||
)`;
|
||||
this.db!.run(createTempUinTableSQL, function (err) {
|
||||
if (err) {
|
||||
logError('Could not create table temp_uins', err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private async getCurrentMaxShortId() {
|
||||
return new Promise<number>((resolve, reject) => {
|
||||
this.db!.get('SELECT MAX(shortId) as maxId FROM msgs', (err, row: { maxId: number }) => {
|
||||
if (err) {
|
||||
logDebug('Could not get max short id, Use default -2147483640', err);
|
||||
return resolve(-2147483640);
|
||||
}
|
||||
logDebug('数据库中消息最大短id', row?.maxId);
|
||||
resolve(row?.maxId ?? -2147483640);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private async getMsg(query: string, params: any[]) {
|
||||
const stmt = this.db!.prepare(query);
|
||||
return new Promise<RawMessage | null>((resolve, reject) => {
|
||||
stmt.get(...params, (err: any, row: DBMsg) => {
|
||||
// log("getMsg", row, err);
|
||||
if (err) {
|
||||
logError('Could not get msg', err, query, params);
|
||||
return resolve(null);
|
||||
}
|
||||
if (!row) {
|
||||
// logDebug('不存在数据库中的消息,不进行处理', query, params);
|
||||
resolve(null);
|
||||
return;
|
||||
}
|
||||
const msgId = row.longId;
|
||||
NTQQMsgApi.getMsgsByMsgId({ peerUid: row.peerUid, chatType: row.chatType }, [msgId]).then(res => {
|
||||
const msg = res.msgList[0];
|
||||
if (!msg) {
|
||||
resolve(null);
|
||||
return;
|
||||
}
|
||||
msg.id = row.shortId;
|
||||
resolve(msg);
|
||||
}).catch(e => {
|
||||
resolve(null);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async getMsgByShortId(shortId: number): Promise<RawMessage | null> {
|
||||
if (this.msgCache.has(shortId)) {
|
||||
return this.msgCache.get(shortId)!;
|
||||
}
|
||||
const getStmt = 'SELECT * FROM msgs WHERE shortId = ?';
|
||||
return this.getMsg(getStmt, [shortId]);
|
||||
}
|
||||
|
||||
async getMsgByLongId(longId: string): Promise<RawMessage | null> {
|
||||
if (this.msgCache.has(longId)) {
|
||||
return this.msgCache.get(longId)!;
|
||||
}
|
||||
return this.getMsg('SELECT * FROM msgs WHERE longId = ?', [longId]);
|
||||
}
|
||||
|
||||
async getMsgBySeq(peerUid: string, seq: string): Promise<RawMessage | null> {
|
||||
const stmt = 'SELECT * FROM msgs WHERE peerUid = ? AND seq = ?';
|
||||
return this.getMsg(stmt, [peerUid, seq]);
|
||||
}
|
||||
|
||||
async addMsg(msg: RawMessage, update = true): Promise<number> {
|
||||
const existMsg = await this.getMsgByLongId(msg.msgId);
|
||||
if (existMsg) {
|
||||
// logDebug('消息已存在,更新数据库', msg.msgId);
|
||||
if (update) this.updateMsg(msg).then();
|
||||
return existMsg.id!;
|
||||
}
|
||||
const stmt = this.db!.prepare('INSERT INTO msgs (shortId, longId, seq, peerUid, chatType) VALUES (?, ?, ?, ?, ?)');
|
||||
// const runAsync = promisify(stmt.run.bind(stmt));
|
||||
const shortId = ++this.globalMsgShortId;
|
||||
msg.id = shortId;
|
||||
//logDebug(`记录消息到数据库, 消息长id: ${msg.msgId}, 短id: ${msg.id}`);
|
||||
this.msgCache.set(shortId, msg);
|
||||
this.msgCache.set(msg.msgId, msg);
|
||||
stmt.run(this.globalMsgShortId, msg.msgId, msg.msgSeq.toString(), msg.peerUid, msg.chatType, (err: any) => {
|
||||
if (err) {
|
||||
if (err.errno === 19) {
|
||||
this.getMsgByLongId(msg.msgId).then((msg: RawMessage | null) => {
|
||||
if (msg) {
|
||||
this.msgCache.set(shortId, msg);
|
||||
this.msgCache.set(msg.msgId, msg);
|
||||
// logDebug('获取消息短id成功', msg.id);
|
||||
} else {
|
||||
logError('db could not get msg by long id', err);
|
||||
}
|
||||
}).catch(e => logError('db getMsgByLongId error', e));
|
||||
} else {
|
||||
logError('db could not add msg', err);
|
||||
}
|
||||
}
|
||||
});
|
||||
return shortId;
|
||||
}
|
||||
|
||||
async updateMsg(msg: RawMessage) {
|
||||
const existMsg = this.msgCache.get(msg.msgId);
|
||||
if (existMsg) {
|
||||
Object.assign(existMsg, msg);
|
||||
}
|
||||
//logDebug(`更新消息, shortId:${msg.id}, seq: ${msg.msgSeq}, msgId: ${msg.msgId}`);
|
||||
const stmt = this.db!.prepare('UPDATE msgs SET seq=? WHERE longId=?');
|
||||
stmt.run(msg.msgSeq, msg.msgId, (err: any) => {
|
||||
if (err) {
|
||||
logError('updateMsg db error', err);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
async addFileCache(file: DBFile) {
|
||||
const stmt = this.db!.prepare('INSERT INTO files (name, path, url, size, uuid, elementType ,element, elementId, msgId) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)');
|
||||
return new Promise((resolve, reject) => {
|
||||
stmt.run(file.name, file.path, file.url, file.size, file.uuid,
|
||||
file.elementType,
|
||||
JSON.stringify(file.element),
|
||||
file.elementId,
|
||||
file.msgId,
|
||||
function (err: any) {
|
||||
if (err) {
|
||||
logError('db could not add file', err);
|
||||
reject(err);
|
||||
}
|
||||
resolve(null);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private async getFileCache(query: string, params: any[]) {
|
||||
const stmt = this.db!.prepare(query);
|
||||
return new Promise<DBFile | null>((resolve, reject) => {
|
||||
stmt.get(...params, (err: any, row: DBFile & { element: string }) => {
|
||||
if (err) {
|
||||
logError('db could not get file cache', err);
|
||||
reject(err);
|
||||
}
|
||||
if (row) {
|
||||
row.element = JSON.parse(row.element);
|
||||
}
|
||||
resolve(row);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async getFileCacheByName(name: string): Promise<DBFile | null> {
|
||||
return this.getFileCache('SELECT * FROM files WHERE name = ?', [name]);
|
||||
}
|
||||
|
||||
async getFileCacheByUuid(uuid: string): Promise<DBFile | null> {
|
||||
return this.getFileCache('SELECT * FROM files WHERE uuid = ?', [uuid]);
|
||||
}
|
||||
|
||||
// todo: 是否所有的文件都有uuid?语音消息有没有uuid?
|
||||
async updateFileCache(file: DBFile) {
|
||||
const stmt = this.db!.prepare('UPDATE files SET path = ?, url = ? WHERE uuid = ?');
|
||||
return new Promise((resolve, reject) => {
|
||||
stmt.run(file.path, file.url, file.uuid, function (err: any) {
|
||||
if (err) {
|
||||
logError('db could not update file cache', err);
|
||||
reject(err);
|
||||
}
|
||||
resolve(null);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 被动收到的临时会话消息uin->uid
|
||||
async getReceivedTempUinMap() {
|
||||
const stmt = 'SELECT * FROM temp_uins';
|
||||
return new Promise<Record<string, string>>((resolve, reject) => {
|
||||
this.db!.all(stmt, (err, rows: { uin: string, uid: string }[]) => {
|
||||
if (err) {
|
||||
logError('db could not get temp uin map', err);
|
||||
reject(err);
|
||||
}
|
||||
const map: Record<string, string> = {};
|
||||
rows.forEach(row => {
|
||||
map[row.uin] = row.uid;
|
||||
});
|
||||
resolve(map);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 通过uin获取临时会话消息uid
|
||||
async getUidByTempUin(uid: string) {
|
||||
const stmt = 'SELECT * FROM temp_uins WHERE uin = ?';
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
this.db!.get(stmt, [uid], (err, row: { uin: string, uid: string }) => {
|
||||
if (err) {
|
||||
logError('db could not get temp uin map', err);
|
||||
reject(err);
|
||||
}
|
||||
resolve(row?.uid);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async addTempUin(uin: string, uid: string) {
|
||||
const existUid = await this.getUidByTempUin(uin);
|
||||
if (!existUid) {
|
||||
const stmt = this.db!.prepare('INSERT INTO temp_uins (uin, uid) VALUES (?, ?)');
|
||||
return new Promise((resolve, reject) => {
|
||||
stmt.run(uin, uid, function (err: any) {
|
||||
if (err) {
|
||||
logError('db could not add temp uin', err);
|
||||
reject(err);
|
||||
}
|
||||
resolve(null);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
async getLastSentTimeAndJoinTime(
|
||||
groupId: number
|
||||
): Promise<IRember[]> {
|
||||
logDebug('读取发言时间', groupId);
|
||||
return new Promise<IRember[]>((resolve, reject) => {
|
||||
this.db!.all(`SELECT * FROM "${groupId}" `, (err, rows: IRember[]) => {
|
||||
if (err) {
|
||||
logError('查询发言时间失败', groupId);
|
||||
return resolve([]);
|
||||
}
|
||||
logDebug('查询发言时间成功', groupId, rows);
|
||||
resolve(rows);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
insertLastSentTime(
|
||||
groupId: number,
|
||||
userId: number,
|
||||
time: number
|
||||
) {
|
||||
this.LURCache.set(groupId, userId, time);
|
||||
}
|
||||
async insertJoinTime(
|
||||
groupId: number,
|
||||
userId: number,
|
||||
time: number
|
||||
) {
|
||||
await this.createGroupInfoTimeTableIfNotExist(groupId);
|
||||
this.db!.all(
|
||||
`INSERT OR REPLACE INTO "${groupId}" (user_id, last_sent_time, join_time) VALUES (?,?,?)`,
|
||||
[userId, time, time],
|
||||
(err) => {
|
||||
if (err)
|
||||
logError(err),
|
||||
Promise.reject(),
|
||||
logError('插入入群时间失败', userId, groupId);
|
||||
}
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export const dbUtil = new DBUtil();
|
||||
273
src/common/utils/file.ts
Normal file
273
src/common/utils/file.ts
Normal file
@@ -0,0 +1,273 @@
|
||||
import fs from 'fs';
|
||||
import fsPromise from 'fs/promises';
|
||||
import crypto from 'crypto';
|
||||
import util from 'util';
|
||||
import path from 'node:path';
|
||||
import { log, logError } from './log';
|
||||
import { dbUtil } from '@/common/utils/db';
|
||||
import * as fileType from 'file-type';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { napCatCore } from '@/core';
|
||||
|
||||
export const getNapCatDir = () => {
|
||||
const p = path.join(napCatCore.dataPath, 'NapCat');
|
||||
fs.mkdirSync(p, { recursive: true });
|
||||
return p;
|
||||
};
|
||||
export const getTempDir = () => {
|
||||
const p = path.join(getNapCatDir(), 'temp');
|
||||
// 创建临时目录
|
||||
if (!fs.existsSync(p)) {
|
||||
fs.mkdirSync(p, { recursive: true });
|
||||
}
|
||||
return p;
|
||||
};
|
||||
|
||||
|
||||
export function isGIF(path: string) {
|
||||
const buffer = Buffer.alloc(4);
|
||||
const fd = fs.openSync(path, 'r');
|
||||
fs.readSync(fd, buffer, 0, 4, 0);
|
||||
fs.closeSync(fd);
|
||||
return buffer.toString() === 'GIF8';
|
||||
}
|
||||
|
||||
// 定义一个异步函数来检查文件是否存在
|
||||
export function checkFileReceived(path: string, timeout: number = 3000): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const startTime = Date.now();
|
||||
|
||||
function check() {
|
||||
if (fs.existsSync(path)) {
|
||||
resolve();
|
||||
} else if (Date.now() - startTime > timeout) {
|
||||
reject(new Error(`文件不存在: ${path}`));
|
||||
} else {
|
||||
setTimeout(check, 100);
|
||||
}
|
||||
}
|
||||
|
||||
check();
|
||||
});
|
||||
}
|
||||
|
||||
export async function file2base64(path: string) {
|
||||
const readFile = util.promisify(fs.readFile);
|
||||
const result = {
|
||||
err: '',
|
||||
data: ''
|
||||
};
|
||||
try {
|
||||
// 读取文件内容
|
||||
// if (!fs.existsSync(path)){
|
||||
// path = path.replace("\\Ori\\", "\\Thumb\\");
|
||||
// }
|
||||
try {
|
||||
await checkFileReceived(path, 5000);
|
||||
} catch (e: any) {
|
||||
result.err = e.toString();
|
||||
return result;
|
||||
}
|
||||
const data = await readFile(path);
|
||||
// 转换为Base64编码
|
||||
result.data = data.toString('base64');
|
||||
} catch (err: any) {
|
||||
result.err = err.toString();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
export function calculateFileMD5(filePath: string): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
// 创建一个流式读取器
|
||||
const stream = fs.createReadStream(filePath);
|
||||
const hash = crypto.createHash('md5');
|
||||
|
||||
stream.on('data', (data: Buffer) => {
|
||||
// 当读取到数据时,更新哈希对象的状态
|
||||
hash.update(data);
|
||||
});
|
||||
|
||||
stream.on('end', () => {
|
||||
// 文件读取完成,计算哈希
|
||||
const md5 = hash.digest('hex');
|
||||
resolve(md5);
|
||||
});
|
||||
|
||||
stream.on('error', (err: Error) => {
|
||||
// 处理可能的读取错误
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export interface HttpDownloadOptions {
|
||||
url: string;
|
||||
headers?: Record<string, string> | string;
|
||||
}
|
||||
|
||||
export async function httpDownload(options: string | HttpDownloadOptions): Promise<Buffer> {
|
||||
const chunks: Buffer[] = [];
|
||||
let url: string;
|
||||
let headers: Record<string, string> = {
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.71 Safari/537.36'
|
||||
};
|
||||
if (typeof options === 'string') {
|
||||
url = options;
|
||||
} else {
|
||||
url = options.url;
|
||||
if (options.headers) {
|
||||
if (typeof options.headers === 'string') {
|
||||
headers = JSON.parse(options.headers);
|
||||
} else {
|
||||
headers = options.headers;
|
||||
}
|
||||
}
|
||||
}
|
||||
const fetchRes = await fetch(url, { headers });
|
||||
if (!fetchRes.ok) throw new Error(`下载文件失败: ${fetchRes.statusText}`);
|
||||
|
||||
const blob = await fetchRes.blob();
|
||||
const buffer = await blob.arrayBuffer();
|
||||
return Buffer.from(buffer);
|
||||
}
|
||||
|
||||
type Uri2LocalRes = {
|
||||
success: boolean,
|
||||
errMsg: string,
|
||||
fileName: string,
|
||||
ext: string,
|
||||
path: string,
|
||||
isLocal: boolean
|
||||
}
|
||||
|
||||
export async function uri2local(uri: string, fileName: string | null = null): Promise<Uri2LocalRes> {
|
||||
const res = {
|
||||
success: false,
|
||||
errMsg: '',
|
||||
fileName: '',
|
||||
ext: '',
|
||||
path: '',
|
||||
isLocal: false
|
||||
};
|
||||
if (!fileName) {
|
||||
fileName = uuidv4();
|
||||
}
|
||||
let filePath = path.join(getTempDir(), fileName);
|
||||
let url = null;
|
||||
try {
|
||||
url = new URL(uri);
|
||||
} catch (e: any) {
|
||||
res.errMsg = `uri ${uri} 解析失败,` + e.toString() + ` 可能${uri}不存在`;
|
||||
return res;
|
||||
}
|
||||
|
||||
// log("uri protocol", url.protocol, uri);
|
||||
if (url.protocol == 'base64:') {
|
||||
// base64转成文件
|
||||
const base64Data = uri.split('base64://')[1];
|
||||
try {
|
||||
const buffer = Buffer.from(base64Data, 'base64');
|
||||
fs.writeFileSync(filePath, buffer);
|
||||
|
||||
} catch (e: any) {
|
||||
res.errMsg = 'base64文件下载失败,' + e.toString();
|
||||
return res;
|
||||
}
|
||||
} else if (url.protocol == 'http:' || url.protocol == 'https:') {
|
||||
// 下载文件
|
||||
let buffer: Buffer | null = null;
|
||||
try {
|
||||
buffer = await httpDownload(uri);
|
||||
} catch (e: any) {
|
||||
res.errMsg = `${url}下载失败,` + e.toString();
|
||||
return res;
|
||||
}
|
||||
try {
|
||||
const pathInfo = path.parse(decodeURIComponent(url.pathname));
|
||||
if (pathInfo.name) {
|
||||
fileName = pathInfo.name;
|
||||
if (pathInfo.ext) {
|
||||
fileName += pathInfo.ext;
|
||||
// res.ext = pathInfo.ext
|
||||
}
|
||||
}
|
||||
fileName = fileName.replace(/[/\\:*?"<>|]/g, '_');
|
||||
res.fileName = fileName;
|
||||
filePath = path.join(getTempDir(), uuidv4() + fileName);
|
||||
fs.writeFileSync(filePath, buffer);
|
||||
} catch (e: any) {
|
||||
res.errMsg = `${url}下载失败,` + e.toString();
|
||||
return res;
|
||||
}
|
||||
} else {
|
||||
let pathname: string;
|
||||
if (url.protocol === 'file:') {
|
||||
// await fs.copyFile(url.pathname, filePath);
|
||||
pathname = decodeURIComponent(url.pathname);
|
||||
if (process.platform === 'win32') {
|
||||
filePath = pathname.slice(1);
|
||||
} else {
|
||||
filePath = pathname;
|
||||
}
|
||||
} else {
|
||||
const cache = await dbUtil.getFileCacheByName(uri);
|
||||
if (cache) {
|
||||
filePath = cache.path;
|
||||
} else {
|
||||
filePath = uri;
|
||||
}
|
||||
}
|
||||
|
||||
res.isLocal = true;
|
||||
}
|
||||
// else{
|
||||
// res.errMsg = `不支持的file协议,` + url.protocol
|
||||
// return res
|
||||
// }
|
||||
// if (isGIF(filePath) && !res.isLocal) {
|
||||
// await fs.rename(filePath, filePath + ".gif");
|
||||
// filePath += ".gif";
|
||||
// }
|
||||
if (!res.isLocal && !res.ext) {
|
||||
try {
|
||||
const ext: string | undefined = (await fileType.fileTypeFromFile(filePath))?.ext;
|
||||
if (ext) {
|
||||
log('获取文件类型', ext, filePath);
|
||||
fs.renameSync(filePath, filePath + `.${ext}`);
|
||||
filePath += `.${ext}`;
|
||||
res.fileName += `.${ext}`;
|
||||
res.ext = ext;
|
||||
}
|
||||
} catch (e) {
|
||||
// log("获取文件类型失败", filePath,e.stack)
|
||||
}
|
||||
}
|
||||
res.success = true;
|
||||
res.path = filePath;
|
||||
return res;
|
||||
}
|
||||
|
||||
export async function copyFolder(sourcePath: string, destPath: string) {
|
||||
try {
|
||||
const entries = await fsPromise.readdir(sourcePath, { withFileTypes: true });
|
||||
await fsPromise.mkdir(destPath, { recursive: true });
|
||||
for (const entry of entries) {
|
||||
const srcPath = path.join(sourcePath, entry.name);
|
||||
const dstPath = path.join(destPath, entry.name);
|
||||
if (entry.isDirectory()) {
|
||||
await copyFolder(srcPath, dstPath);
|
||||
} else {
|
||||
try {
|
||||
await fsPromise.copyFile(srcPath, dstPath);
|
||||
} catch (error) {
|
||||
logError(`无法复制文件 '${srcPath}' 到 '${dstPath}': ${error}`);
|
||||
// 这里可以决定是否要继续复制其他文件
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
logError('复制文件夹时出错:', error);
|
||||
}
|
||||
}
|
||||
184
src/common/utils/helper.ts
Normal file
184
src/common/utils/helper.ts
Normal file
@@ -0,0 +1,184 @@
|
||||
import crypto from 'node:crypto';
|
||||
import path from 'node:path';
|
||||
import fs from 'fs/promises';
|
||||
import { log, logDebug } from './log';
|
||||
import { dirname } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
export function sleep(ms: number): Promise<void> {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
export function getMd5(s: string) {
|
||||
|
||||
const h = crypto.createHash('md5');
|
||||
h.update(s);
|
||||
return h.digest('hex');
|
||||
}
|
||||
|
||||
export function isNull(value: any) {
|
||||
return value === undefined || value === null;
|
||||
}
|
||||
|
||||
export function isNumeric(str: string) {
|
||||
return /^\d+$/.test(str);
|
||||
}
|
||||
|
||||
export function truncateString(obj: any, maxLength = 500) {
|
||||
if (obj !== null && typeof obj === 'object') {
|
||||
Object.keys(obj).forEach(key => {
|
||||
if (typeof obj[key] === 'string') {
|
||||
// 如果是字符串且超过指定长度,则截断
|
||||
if (obj[key].length > maxLength) {
|
||||
obj[key] = obj[key].substring(0, maxLength) + '...';
|
||||
}
|
||||
} else if (typeof obj[key] === 'object') {
|
||||
// 如果是对象或数组,则递归调用
|
||||
truncateString(obj[key], maxLength);
|
||||
}
|
||||
});
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 函数缓存装饰器,根据方法名、参数、自定义key生成缓存键,在一定时间内返回缓存结果
|
||||
* @param ttl 超时时间,单位毫秒
|
||||
* @param customKey 自定义缓存键前缀,可为空,防止方法名参数名一致时导致缓存键冲突
|
||||
* @returns 处理后缓存或调用原方法的结果
|
||||
*/
|
||||
export function cacheFunc(ttl: number, customKey: string = '') {
|
||||
const cache = new Map<string, { expiry: number; value: any }>();
|
||||
|
||||
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor): PropertyDescriptor {
|
||||
const originalMethod = descriptor.value;
|
||||
const className = target.constructor.name; // 获取类名
|
||||
const methodName = propertyKey; // 获取方法名
|
||||
descriptor.value = async function (...args: any[]) {
|
||||
const cacheKey = `${customKey}${className}.${methodName}:${JSON.stringify(args)}`;
|
||||
const cached = cache.get(cacheKey);
|
||||
if (cached && cached.expiry > Date.now()) {
|
||||
return cached.value;
|
||||
} else {
|
||||
const result = await originalMethod.apply(this, args);
|
||||
cache.set(cacheKey, { value: result, expiry: Date.now() + ttl });
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
return descriptor;
|
||||
};
|
||||
}
|
||||
export function isValidOldConfig(config: any) {
|
||||
if (typeof config !== 'object') {
|
||||
return false;
|
||||
}
|
||||
const requiredKeys = [
|
||||
'httpHost', 'httpPort', 'httpPostUrls', 'httpSecret',
|
||||
'wsHost', 'wsPort', 'wsReverseUrls', 'enableHttp',
|
||||
'enableHttpHeart', 'enableHttpPost', 'enableWs', 'enableWsReverse',
|
||||
'messagePostFormat', 'reportSelfMessage', 'enableLocalFile2Url',
|
||||
'debug', 'heartInterval', 'token', 'musicSignUrl'
|
||||
];
|
||||
for (const key of requiredKeys) {
|
||||
if (!(key in config)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!Array.isArray(config.httpPostUrls) || !Array.isArray(config.wsReverseUrls)) {
|
||||
return false;
|
||||
}
|
||||
if (config.httpPostUrls.some((url: any) => typeof url !== 'string')) {
|
||||
return false;
|
||||
}
|
||||
if (config.wsReverseUrls.some((url: any) => typeof url !== 'string')) {
|
||||
return false;
|
||||
}
|
||||
if (typeof config.httpPort !== 'number' || typeof config.wsPort !== 'number' || typeof config.heartInterval !== 'number') {
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
typeof config.enableHttp !== 'boolean' ||
|
||||
typeof config.enableHttpHeart !== 'boolean' ||
|
||||
typeof config.enableHttpPost !== 'boolean' ||
|
||||
typeof config.enableWs !== 'boolean' ||
|
||||
typeof config.enableWsReverse !== 'boolean' ||
|
||||
typeof config.enableLocalFile2Url !== 'boolean' ||
|
||||
typeof config.reportSelfMessage !== 'boolean'
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (config.messagePostFormat !== 'array' && config.messagePostFormat !== 'string') {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
export function migrateConfig(oldConfig: any) {
|
||||
const newConfig = {
|
||||
http: {
|
||||
enable: oldConfig.enableHttp,
|
||||
host: oldConfig.httpHost,
|
||||
port: oldConfig.httpPort,
|
||||
secret: oldConfig.httpSecret,
|
||||
enableHeart: oldConfig.enableHttpHeart,
|
||||
enablePost: oldConfig.enableHttpPost,
|
||||
postUrls: oldConfig.httpPostUrls,
|
||||
},
|
||||
ws: {
|
||||
enable: oldConfig.enableWs,
|
||||
host: oldConfig.wsHost,
|
||||
port: oldConfig.wsPort,
|
||||
},
|
||||
reverseWs: {
|
||||
enable: oldConfig.enableWsReverse,
|
||||
urls: oldConfig.wsReverseUrls,
|
||||
},
|
||||
GroupLocalTime: {
|
||||
Record: false,
|
||||
RecordList: []
|
||||
},
|
||||
debug: oldConfig.debug,
|
||||
heartInterval: oldConfig.heartInterval,
|
||||
messagePostFormat: oldConfig.messagePostFormat,
|
||||
enableLocalFile2Url: oldConfig.enableLocalFile2Url,
|
||||
musicSignUrl: oldConfig.musicSignUrl,
|
||||
reportSelfMessage: oldConfig.reportSelfMessage,
|
||||
token: oldConfig.token,
|
||||
|
||||
};
|
||||
return newConfig;
|
||||
}
|
||||
// 升级旧的配置到新的
|
||||
export async function UpdateConfig() {
|
||||
const configFiles = await fs.readdir(path.join(__dirname, 'config'));
|
||||
for (const file of configFiles) {
|
||||
if (file.match(/^onebot11_\d+.json$/)) {
|
||||
const CurrentConfig = JSON.parse(await fs.readFile(path.join(__dirname, 'config', file), 'utf8'));
|
||||
if (isValidOldConfig(CurrentConfig)) {
|
||||
log('正在迁移旧配置到新配置 File:', file);
|
||||
const NewConfig = migrateConfig(CurrentConfig);
|
||||
await fs.writeFile(path.join(__dirname, 'config', file), JSON.stringify(NewConfig, null, 2));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
136
src/common/utils/log.ts
Normal file
136
src/common/utils/log.ts
Normal file
@@ -0,0 +1,136 @@
|
||||
import log4js, { Configuration } from 'log4js';
|
||||
import { truncateString } from '@/common/utils/helper';
|
||||
import path from 'node:path';
|
||||
import { SelfInfo } from '@/core';
|
||||
import { dirname } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import chalk from 'chalk';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
export enum LogLevel {
|
||||
DEBUG = 'debug',
|
||||
INFO = 'info',
|
||||
WARN = 'warn',
|
||||
ERROR = 'error',
|
||||
FATAL = 'fatal',
|
||||
}
|
||||
|
||||
const logDir = path.join(path.resolve(__dirname), 'logs');
|
||||
|
||||
function getFormattedTimestamp() {
|
||||
const now = new Date();
|
||||
const year = now.getFullYear();
|
||||
const month = (now.getMonth() + 1).toString().padStart(2, '0');
|
||||
const day = now.getDate().toString().padStart(2, '0');
|
||||
const hours = now.getHours().toString().padStart(2, '0');
|
||||
const minutes = now.getMinutes().toString().padStart(2, '0');
|
||||
const seconds = now.getSeconds().toString().padStart(2, '0');
|
||||
const milliseconds = now.getMilliseconds().toString().padStart(3, '0');
|
||||
return `${year}-${month}-${day}_${hours}-${minutes}-${seconds}.${milliseconds}`;
|
||||
}
|
||||
|
||||
const filename = `${getFormattedTimestamp()}.log`;
|
||||
const logPath = path.join(logDir, filename);
|
||||
|
||||
const logConfig: Configuration = {
|
||||
appenders: {
|
||||
FileAppender: { // 输出到文件的appender
|
||||
type: 'file',
|
||||
filename: logPath, // 指定日志文件的位置和文件名
|
||||
maxLoogSize: 10485760, // 日志文件的最大大小(单位:字节),这里设置为10MB
|
||||
layout: {
|
||||
type: 'pattern',
|
||||
pattern: '%d{yyyy-MM-dd hh:mm:ss} [%p] %X{userInfo} | %m'
|
||||
}
|
||||
},
|
||||
ConsoleAppender: { // 输出到控制台的appender
|
||||
type: 'console',
|
||||
layout: {
|
||||
type: 'pattern',
|
||||
pattern: `%d{yyyy-MM-dd hh:mm:ss} [%[%p%]] ${chalk.magenta('%X{userInfo}')} | %m`
|
||||
}
|
||||
}
|
||||
},
|
||||
categories: {
|
||||
default: { appenders: ['FileAppender', 'ConsoleAppender'], level: 'debug' }, // 默认情况下同时输出到文件和控制台
|
||||
file: { appenders: ['FileAppender'], level: 'debug' },
|
||||
console: { appenders: ['ConsoleAppender'], level: 'debug' }
|
||||
}
|
||||
};
|
||||
|
||||
log4js.configure(logConfig);
|
||||
const loggerConsole = log4js.getLogger('console');
|
||||
const loggerFile = log4js.getLogger('file');
|
||||
const loggerDefault = log4js.getLogger('default');
|
||||
|
||||
export function setLogLevel(fileLogLevel: LogLevel, consoleLogLevel: LogLevel) {
|
||||
logConfig.categories.file.level = fileLogLevel;
|
||||
logConfig.categories.console.level = consoleLogLevel;
|
||||
log4js.configure(logConfig);
|
||||
}
|
||||
|
||||
export function setLogSelfInfo(selfInfo: SelfInfo) {
|
||||
const userInfo = `${selfInfo.nick}(${selfInfo.uin})`;
|
||||
loggerConsole.addContext('userInfo', userInfo);
|
||||
loggerFile.addContext('userInfo', userInfo);
|
||||
loggerDefault.addContext('userInfo', userInfo);
|
||||
}
|
||||
setLogSelfInfo({ nick: '', uin: '', uid: '' });
|
||||
|
||||
let fileLogEnabled = true;
|
||||
let consoleLogEnabled = true;
|
||||
export function enableFileLog(enable: boolean) {
|
||||
fileLogEnabled = enable;
|
||||
}
|
||||
export function enableConsoleLog(enable: boolean) {
|
||||
consoleLogEnabled = enable;
|
||||
}
|
||||
|
||||
function formatMsg(msg: any[]) {
|
||||
let logMsg = '';
|
||||
for (const msgItem of msg) {
|
||||
// 判断是否是对象
|
||||
if (typeof msgItem === 'object') {
|
||||
const obj = JSON.parse(JSON.stringify(msgItem, null, 2));
|
||||
logMsg += JSON.stringify(truncateString(obj)) + ' ';
|
||||
continue;
|
||||
}
|
||||
logMsg += msgItem + ' ';
|
||||
}
|
||||
return logMsg;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-control-regex
|
||||
const colorEscape = /\x1B[@-_][0-?]*[ -/]*[@-~]/g;
|
||||
|
||||
function _log(level: LogLevel, ...args: any[]) {
|
||||
if (consoleLogEnabled) {
|
||||
loggerConsole[level](formatMsg(args));
|
||||
}
|
||||
if (fileLogEnabled) {
|
||||
loggerFile[level](formatMsg(args).replace(colorEscape, ''));
|
||||
}
|
||||
}
|
||||
|
||||
export function log(...args: any[]) {
|
||||
// info 等级
|
||||
_log(LogLevel.INFO, ...args);
|
||||
}
|
||||
|
||||
export function logDebug(...args: any[]) {
|
||||
_log(LogLevel.DEBUG, ...args);
|
||||
}
|
||||
|
||||
export function logError(...args: any[]) {
|
||||
_log(LogLevel.ERROR, ...args);
|
||||
}
|
||||
|
||||
export function logWarn(...args: any[]) {
|
||||
_log(LogLevel.WARN, ...args);
|
||||
}
|
||||
|
||||
export function logFatal(...args: any[]) {
|
||||
_log(LogLevel.FATAL, ...args);
|
||||
}
|
||||
7
src/common/utils/qqlevel.ts
Normal file
7
src/common/utils/qqlevel.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
// QQ等级换算
|
||||
import { QQLevel } from '@/core/entities';
|
||||
|
||||
export function calcQQLevel(level: QQLevel) {
|
||||
const { crownNum, sunNum, moonNum, starNum } = level;
|
||||
return crownNum * 64 + sunNum * 16 + moonNum * 4 + starNum;
|
||||
}
|
||||
44
src/common/utils/reboot.ts
Normal file
44
src/common/utils/reboot.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import { resolve } from 'node:path';
|
||||
import { spawn } from 'node:child_process';
|
||||
import { pid, ppid, exit } from 'node:process';
|
||||
import { dirname } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
export async function rebootWithQuickLogin(uin: string) {
|
||||
const batScript = resolve(__dirname, './napcat.bat');
|
||||
const batUtf8Script = resolve(__dirname, './napcat-utf8.bat');
|
||||
const bashScript = resolve(__dirname, './napcat.sh');
|
||||
if (process.platform === 'win32') {
|
||||
const subProcess = spawn(`start ${batUtf8Script} -q ${uin}`, { detached: true, windowsHide: false, env: process.env, shell: true, stdio: 'ignore' });
|
||||
subProcess.unref();
|
||||
// 子父进程一起送走 有点效果
|
||||
spawn('cmd /c taskkill /t /f /pid ' + pid.toString(), { detached: true, shell: true, stdio: 'ignore' });
|
||||
spawn('cmd /c taskkill /t /f /pid ' + ppid.toString(), { detached: true, shell: true, stdio: 'ignore' });
|
||||
} else if (process.platform === 'linux') {
|
||||
const subProcess = spawn(`${bashScript} -q ${uin}`, { detached: true, windowsHide: false, env: process.env, shell: true, stdio: 'ignore' });
|
||||
//还没兼容
|
||||
subProcess.unref();
|
||||
exit(0);
|
||||
}
|
||||
//exit(0);
|
||||
}
|
||||
export async function rebootWithNormolLogin() {
|
||||
const batScript = resolve(__dirname, './napcat.bat');
|
||||
const batUtf8Script = resolve(__dirname, './napcat-utf8.bat');
|
||||
const bashScript = resolve(__dirname, './napcat.sh');
|
||||
if (process.platform === 'win32') {
|
||||
const subProcess = spawn(`start ${batUtf8Script} `, { detached: true, windowsHide: false, env: process.env, shell: true, stdio: 'ignore' });
|
||||
subProcess.unref();
|
||||
// 子父进程一起送走 有点效果
|
||||
spawn('cmd /c taskkill /t /f /pid ' + pid.toString(), { detached: true, shell: true, stdio: 'ignore' });
|
||||
spawn('cmd /c taskkill /t /f /pid ' + ppid.toString(), { detached: true, shell: true, stdio: 'ignore' });
|
||||
} else if (process.platform === 'linux') {
|
||||
const subProcess = spawn(`${bashScript}`, { detached: true, windowsHide: false, env: process.env, shell: true });
|
||||
subProcess.unref();
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
106
src/common/utils/request.ts
Normal file
106
src/common/utils/request.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
import https from 'node:https';
|
||||
import http from 'node:http';
|
||||
|
||||
export class RequestUtil {
|
||||
// 适用于获取服务器下发cookies时获取,仅GET
|
||||
static async HttpsGetCookies(url: string): Promise<{ [key: string]: string }> {
|
||||
const client = url.startsWith('https') ? https : http;
|
||||
return new Promise((resolve, reject) => {
|
||||
client.get(url, (res) => {
|
||||
let cookies: { [key: string]: string } = {};
|
||||
const handleRedirect = (res: http.IncomingMessage) => {
|
||||
//console.log(res.headers.location);
|
||||
if (res.statusCode === 301 || res.statusCode === 302) {
|
||||
if (res.headers.location) {
|
||||
const redirectUrl = new URL(res.headers.location, url);
|
||||
RequestUtil.HttpsGetCookies(redirectUrl.href).then((redirectCookies) => {
|
||||
// 合并重定向过程中的cookies
|
||||
cookies = { ...cookies, ...redirectCookies };
|
||||
resolve(cookies);
|
||||
});
|
||||
} else {
|
||||
resolve(cookies);
|
||||
}
|
||||
} else {
|
||||
resolve(cookies);
|
||||
}
|
||||
};
|
||||
res.on('data', () => { }); // Necessary to consume the stream
|
||||
res.on('end', () => {
|
||||
handleRedirect(res);
|
||||
});
|
||||
if (res.headers['set-cookie']) {
|
||||
//console.log(res.headers['set-cookie']);
|
||||
res.headers['set-cookie'].forEach((cookie) => {
|
||||
const parts = cookie.split(';')[0].split('=');
|
||||
const key = parts[0];
|
||||
const value = parts[1];
|
||||
if (key && value && key.length > 0 && value.length > 0) {
|
||||
cookies[key] = value;
|
||||
}
|
||||
});
|
||||
}
|
||||
}).on('error', (err) => {
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 请求和回复都是JSON data传原始内容 自动编码json
|
||||
static async HttpGetJson<T>(url: string, method: string = 'GET', data?: any, headers: Record<string, string> = {}, isJsonRet: boolean = true, isArgJson: boolean = true): Promise<T> {
|
||||
const option = new URL(url);
|
||||
const protocol = url.startsWith('https://') ? https : http;
|
||||
const options = {
|
||||
hostname: option.hostname,
|
||||
port: option.port,
|
||||
path: option.href,
|
||||
method: method,
|
||||
headers: headers
|
||||
};
|
||||
return new Promise((resolve, reject) => {
|
||||
const req = protocol.request(options, (res: any) => {
|
||||
let responseBody = '';
|
||||
res.on('data', (chunk: string | Buffer) => {
|
||||
responseBody += chunk.toString();
|
||||
});
|
||||
|
||||
res.on('end', () => {
|
||||
try {
|
||||
if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
|
||||
if (isJsonRet) {
|
||||
const responseJson = JSON.parse(responseBody);
|
||||
resolve(responseJson as T);
|
||||
} else {
|
||||
resolve(responseBody as T);
|
||||
}
|
||||
} else {
|
||||
reject(new Error(`Unexpected status code: ${res.statusCode}`));
|
||||
}
|
||||
} catch (parseError) {
|
||||
reject(parseError);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
req.on('error', (error: any) => {
|
||||
reject(error);
|
||||
});
|
||||
if (method === 'POST' || method === 'PUT' || method === 'PATCH') {
|
||||
if (isArgJson) {
|
||||
req.write(JSON.stringify(data));
|
||||
} else {
|
||||
req.write(data);
|
||||
}
|
||||
|
||||
}
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
// 请求返回都是原始内容
|
||||
static async HttpGetText(url: string, method: string = 'GET', data?: any, headers: Record<string, string> = {}) {
|
||||
return this.HttpGetJson<string>(url, method, data, headers, false, false);
|
||||
}
|
||||
}
|
||||
74
src/common/utils/system.ts
Normal file
74
src/common/utils/system.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
import { networkInterfaces } from 'os';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
// 缓解Win7设备兼容性问题
|
||||
let osName: string;
|
||||
// 设备ID
|
||||
let machineId: Promise<string>;
|
||||
|
||||
try {
|
||||
osName = os.hostname();
|
||||
} catch (e) {
|
||||
osName = 'NapCat'; // + crypto.randomUUID().substring(0, 4);
|
||||
}
|
||||
|
||||
const invalidMacAddresses = new Set([
|
||||
'00:00:00:00:00:00',
|
||||
'ff:ff:ff:ff:ff:ff',
|
||||
'ac:de:48:00:11:22'
|
||||
]);
|
||||
|
||||
function validateMacAddress(candidate: string): boolean {
|
||||
// eslint-disable-next-line no-useless-escape
|
||||
const tempCandidate = candidate.replace(/\-/g, ':').toLowerCase();
|
||||
return !invalidMacAddresses.has(tempCandidate);
|
||||
}
|
||||
|
||||
export async function getMachineId(): Promise<string> {
|
||||
if (!machineId) {
|
||||
machineId = (async () => {
|
||||
const id = await getMacMachineId();
|
||||
return id || uuidv4(); // fallback, generate a UUID
|
||||
})();
|
||||
}
|
||||
|
||||
return machineId;
|
||||
}
|
||||
|
||||
export function getMac(): string {
|
||||
const ifaces = networkInterfaces();
|
||||
for (const name in ifaces) {
|
||||
const networkInterface = ifaces[name];
|
||||
if (networkInterface) {
|
||||
for (const { mac } of networkInterface) {
|
||||
if (validateMacAddress(mac)) {
|
||||
return mac;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error('Unable to retrieve mac address (unexpected format)');
|
||||
}
|
||||
|
||||
async function getMacMachineId(): Promise<string | undefined> {
|
||||
try {
|
||||
const crypto = await import('crypto');
|
||||
const macAddress = getMac();
|
||||
return crypto.createHash('sha256').update(macAddress, 'utf8').digest('hex');
|
||||
} catch (err) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
const homeDir = os.homedir();
|
||||
|
||||
|
||||
export const systemPlatform = os.platform();
|
||||
export const cpuArch = os.arch();
|
||||
export const systemVersion = os.release();
|
||||
export const hostname = osName;
|
||||
export const downloadsPath = path.join(homeDir, 'Downloads');
|
||||
export const systemName = os.type();
|
||||
31
src/common/utils/type.ts
Normal file
31
src/common/utils/type.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* 运行时类型转换与检查类
|
||||
*/
|
||||
export class TypeCheck {
|
||||
static isEmpty(value: any): boolean {
|
||||
return value === null || value === undefined || value === '' ||
|
||||
(Array.isArray(value) && value.length === 0) || (typeof value === 'object' && Object.keys(value).length === 0);
|
||||
}
|
||||
}
|
||||
|
||||
export class TypeConvert {
|
||||
static toNumber(value: any): number {
|
||||
const num = Number(value);
|
||||
if (isNaN(num)) {
|
||||
throw new Error(`无法将输入转换为数字: ${value}`);
|
||||
}
|
||||
return num;
|
||||
}
|
||||
|
||||
static toString(value: any): string {
|
||||
return String(value);
|
||||
}
|
||||
|
||||
static toBoolean(value: any): boolean {
|
||||
return Boolean(value);
|
||||
}
|
||||
|
||||
static toArray(value: any): any[] {
|
||||
return Array.isArray(value) ? value : [value];
|
||||
}
|
||||
}
|
||||
25
src/common/utils/version.ts
Normal file
25
src/common/utils/version.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { logDebug } from './log';
|
||||
import { RequestUtil } from './request';
|
||||
export async function checkVersion(): Promise<string> {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const MirrorList =
|
||||
[
|
||||
'https://fastly.jsdelivr.net/gh/NapNeko/NapCatQQ@main/package.json',
|
||||
'https://gcore.jsdelivr.net/gh/NapNeko/NapCatQQ@main/package.json',
|
||||
'https://cdn.jsdelivr.us/gh/NapNeko/NapCatQQ@main/package.json',
|
||||
'https://jsd.cdn.zzko.cn/gh/NapNeko/NapCatQQ@main/package.json'
|
||||
];
|
||||
let version = undefined;
|
||||
for (const url of MirrorList) {
|
||||
try {
|
||||
version = (await RequestUtil.HttpGetJson<{ version: string }>(url)).version;
|
||||
} catch (e) {
|
||||
logDebug('检测更新异常',e);
|
||||
}
|
||||
if (version) {
|
||||
resolve(version);
|
||||
}
|
||||
}
|
||||
reject('get verison error!');
|
||||
});
|
||||
}
|
||||
87
src/common/utils/video.ts
Normal file
87
src/common/utils/video.ts
Normal file
File diff suppressed because one or more lines are too long
1
src/core
Submodule
1
src/core
Submodule
Submodule src/core added at 4e93949d20
22
src/core.lib/package.json
Normal file
22
src/core.lib/package.json
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"name": "@napneko/core",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"type": "module",
|
||||
"main": "./index.js",
|
||||
"files": [
|
||||
"lib"
|
||||
],
|
||||
"scripts": {
|
||||
"lint": "eslint --fix ./src/**/*.ts",
|
||||
"build:dev": "vite build --mode development",
|
||||
"build:prod": "vite build --mode production",
|
||||
"build": "npm run build:dev"
|
||||
},
|
||||
"author": "NapNeko",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/NapNeko/NapCatQQ/issues"
|
||||
},
|
||||
"homepage": "https://github.com/NapNeko/NapCatQQ#readme"
|
||||
}
|
||||
14
src/core.lib/src/adapters/NodeIDependsAdapter.d.ts
vendored
Normal file
14
src/core.lib/src/adapters/NodeIDependsAdapter.d.ts
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
interface IDependsAdapter {
|
||||
onMSFStatusChange(arg1: number, arg2: number): void;
|
||||
onMSFSsoError(args: unknown): void;
|
||||
getGroupCode(args: unknown): void;
|
||||
}
|
||||
export interface NodeIDependsAdapter extends IDependsAdapter {
|
||||
new (adapter: IDependsAdapter): NodeIDependsAdapter;
|
||||
}
|
||||
export declare class DependsAdapter implements IDependsAdapter {
|
||||
onMSFStatusChange(arg1: number, arg2: number): void;
|
||||
onMSFSsoError(args: unknown): void;
|
||||
getGroupCode(args: unknown): void;
|
||||
}
|
||||
export {};
|
||||
1
src/core.lib/src/adapters/NodeIDependsAdapter.js
Normal file
1
src/core.lib/src/adapters/NodeIDependsAdapter.js
Normal file
@@ -0,0 +1 @@
|
||||
var _0x3f82b3=_0x22dd;function _0x22dd(_0x57fb9f,_0x29c8f7){var _0x27d81f=_0x27d8();return _0x22dd=function(_0x22dd9a,_0x236d49){_0x22dd9a=_0x22dd9a-0xb1;var _0x688351=_0x27d81f[_0x22dd9a];return _0x688351;},_0x22dd(_0x57fb9f,_0x29c8f7);}(function(_0x721ecc,_0x256ba3){var _0x22350d=_0x22dd,_0x1120eb=_0x721ecc();while(!![]){try{var _0x17283f=parseInt(_0x22350d(0xb3))/0x1+-parseInt(_0x22350d(0xb9))/0x2+-parseInt(_0x22350d(0xb4))/0x3+-parseInt(_0x22350d(0xba))/0x4*(-parseInt(_0x22350d(0xb6))/0x5)+parseInt(_0x22350d(0xb8))/0x6*(parseInt(_0x22350d(0xb5))/0x7)+-parseInt(_0x22350d(0xb7))/0x8+parseInt(_0x22350d(0xb2))/0x9;if(_0x17283f===_0x256ba3)break;else _0x1120eb['push'](_0x1120eb['shift']());}catch(_0x37aa46){_0x1120eb['push'](_0x1120eb['shift']());}}}(_0x27d8,0xabd2e));function _0x27d8(){var _0x4731f0=['302115EVHqky','49SrRYMd','325rbNSYW','4494344GRWKRp','1001514hnIbbs','1467374CEWAdQ','1048BsLaKG','getGroupCode','7466850IUbYlJ','84862ALbAcM'];_0x27d8=function(){return _0x4731f0;};return _0x27d8();}export class DependsAdapter{['onMSFStatusChange'](_0x3b272e,_0x12a362){}['onMSFSsoError'](_0x37047d){}[_0x3f82b3(0xb1)](_0x263af0){}}
|
||||
14
src/core.lib/src/adapters/NodeIDispatcherAdapter.d.ts
vendored
Normal file
14
src/core.lib/src/adapters/NodeIDispatcherAdapter.d.ts
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
interface IDispatcherAdapter {
|
||||
dispatchRequest(arg: unknown): void;
|
||||
dispatchCall(arg: unknown): void;
|
||||
dispatchCallWithJson(arg: unknown): void;
|
||||
}
|
||||
export interface NodeIDispatcherAdapter extends IDispatcherAdapter {
|
||||
new (adapter: IDispatcherAdapter): NodeIDispatcherAdapter;
|
||||
}
|
||||
export declare class DispatcherAdapter implements IDispatcherAdapter {
|
||||
dispatchRequest(arg: unknown): void;
|
||||
dispatchCall(arg: unknown): void;
|
||||
dispatchCallWithJson(arg: unknown): void;
|
||||
}
|
||||
export {};
|
||||
1
src/core.lib/src/adapters/NodeIDispatcherAdapter.js
Normal file
1
src/core.lib/src/adapters/NodeIDispatcherAdapter.js
Normal file
@@ -0,0 +1 @@
|
||||
var _0x4c2a17=_0x185b;(function(_0x3a242d,_0x321c4b){var _0xa97144=_0x185b,_0x435d20=_0x3a242d();while(!![]){try{var _0x453a9a=parseInt(_0xa97144(0x19a))/0x1+parseInt(_0xa97144(0x1a4))/0x2+-parseInt(_0xa97144(0x19f))/0x3+-parseInt(_0xa97144(0x19e))/0x4+-parseInt(_0xa97144(0x1a1))/0x5*(-parseInt(_0xa97144(0x1a2))/0x6)+-parseInt(_0xa97144(0x1a3))/0x7+parseInt(_0xa97144(0x1a0))/0x8*(parseInt(_0xa97144(0x19d))/0x9);if(_0x453a9a===_0x321c4b)break;else _0x435d20['push'](_0x435d20['shift']());}catch(_0xb294d1){_0x435d20['push'](_0x435d20['shift']());}}}(_0x5701,0x2b8dd));export class DispatcherAdapter{['dispatchRequest'](_0x4860de){}[_0x4c2a17(0x19c)](_0x3bc209){}[_0x4c2a17(0x19b)](_0x2837ab){}}function _0x185b(_0x742677,_0x5c30a9){var _0x5701d8=_0x5701();return _0x185b=function(_0x185bb3,_0x427dd5){_0x185bb3=_0x185bb3-0x19a;var _0x4c71bf=_0x5701d8[_0x185bb3];return _0x4c71bf;},_0x185b(_0x742677,_0x5c30a9);}function _0x5701(){var _0xe65c8f=['10zFJgtE','666654FEXrQO','1613570uMuAkM','27436mLgyFg','170753HkFFqH','dispatchCallWithJson','dispatchCall','171378JbeOfg','212092JBbkyw','576915UWobAl','104ynzZuI'];_0x5701=function(){return _0xe65c8f;};return _0x5701();}
|
||||
24
src/core.lib/src/adapters/NodeIGlobalAdapter.d.ts
vendored
Normal file
24
src/core.lib/src/adapters/NodeIGlobalAdapter.d.ts
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
interface IGlobalAdapter {
|
||||
onLog(...args: unknown[]): void;
|
||||
onGetSrvCalTime(...args: unknown[]): void;
|
||||
onShowErrUITips(...args: unknown[]): void;
|
||||
fixPicImgType(...args: unknown[]): void;
|
||||
getAppSetting(...args: unknown[]): void;
|
||||
onInstallFinished(...args: unknown[]): void;
|
||||
onUpdateGeneralFlag(...args: unknown[]): void;
|
||||
onGetOfflineMsg(...args: unknown[]): void;
|
||||
}
|
||||
export interface NodeIGlobalAdapter extends IGlobalAdapter {
|
||||
new (adapter: IGlobalAdapter): NodeIGlobalAdapter;
|
||||
}
|
||||
export declare class GlobalAdapter implements IGlobalAdapter {
|
||||
onLog(...args: unknown[]): void;
|
||||
onGetSrvCalTime(...args: unknown[]): void;
|
||||
onShowErrUITips(...args: unknown[]): void;
|
||||
fixPicImgType(...args: unknown[]): void;
|
||||
getAppSetting(...args: unknown[]): void;
|
||||
onInstallFinished(...args: unknown[]): void;
|
||||
onUpdateGeneralFlag(...args: unknown[]): void;
|
||||
onGetOfflineMsg(...args: unknown[]): void;
|
||||
}
|
||||
export {};
|
||||
1
src/core.lib/src/adapters/NodeIGlobalAdapter.js
Normal file
1
src/core.lib/src/adapters/NodeIGlobalAdapter.js
Normal file
@@ -0,0 +1 @@
|
||||
function _0x275e(){var _0x3e20d6=['fixPicImgType','346456AgfTja','927216RGmngc','4605084HeUpRA','onGetOfflineMsg','370eNCBsi','546290deLSYq','1312143FFHPIJ','1127dRkIEP','68280WuUXyh','onInstallFinished','40zkWsrA'];_0x275e=function(){return _0x3e20d6;};return _0x275e();}function _0x16de(_0x38e95c,_0x4a1aa8){var _0x275e8c=_0x275e();return _0x16de=function(_0x16de29,_0x51ad25){_0x16de29=_0x16de29-0xb8;var _0x856b25=_0x275e8c[_0x16de29];return _0x856b25;},_0x16de(_0x38e95c,_0x4a1aa8);}var _0x2df007=_0x16de;(function(_0x39b367,_0x1f01be){var _0x5cacef=_0x16de,_0x59bc9e=_0x39b367();while(!![]){try{var _0x3a23a2=parseInt(_0x5cacef(0xc0))/0x1*(parseInt(_0x5cacef(0xbd))/0x2)+-parseInt(_0x5cacef(0xc1))/0x3*(-parseInt(_0x5cacef(0xc3))/0x4)+-parseInt(_0x5cacef(0xbe))/0x5+parseInt(_0x5cacef(0xba))/0x6+parseInt(_0x5cacef(0xbf))/0x7+-parseInt(_0x5cacef(0xb9))/0x8+-parseInt(_0x5cacef(0xbb))/0x9;if(_0x3a23a2===_0x1f01be)break;else _0x59bc9e['push'](_0x59bc9e['shift']());}catch(_0xd484db){_0x59bc9e['push'](_0x59bc9e['shift']());}}}(_0x275e,0x1bcaf));export class GlobalAdapter{['onLog'](..._0x390fcc){}['onGetSrvCalTime'](..._0xdcf846){}['onShowErrUITips'](..._0x5f5817){}[_0x2df007(0xb8)](..._0x2005e4){}['getAppSetting'](..._0x23ae95){}[_0x2df007(0xc2)](..._0x4b6ed8){}['onUpdateGeneralFlag'](..._0x21cc0e){}[_0x2df007(0xbc)](..._0x5dcff5){}}
|
||||
3
src/core.lib/src/adapters/index.d.ts
vendored
Normal file
3
src/core.lib/src/adapters/index.d.ts
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
export * from './NodeIDependsAdapter';
|
||||
export * from './NodeIDispatcherAdapter';
|
||||
export * from './NodeIGlobalAdapter';
|
||||
1
src/core.lib/src/adapters/index.js
Normal file
1
src/core.lib/src/adapters/index.js
Normal file
@@ -0,0 +1 @@
|
||||
(function(_0x4453b5,_0x26728a){var _0x2e552d=_0x1eba,_0x32fa15=_0x4453b5();while(!![]){try{var _0x36a0ad=-parseInt(_0x2e552d(0x87))/0x1*(-parseInt(_0x2e552d(0x7e))/0x2)+parseInt(_0x2e552d(0x89))/0x3*(-parseInt(_0x2e552d(0x82))/0x4)+-parseInt(_0x2e552d(0x86))/0x5+-parseInt(_0x2e552d(0x7f))/0x6*(-parseInt(_0x2e552d(0x80))/0x7)+parseInt(_0x2e552d(0x83))/0x8*(-parseInt(_0x2e552d(0x88))/0x9)+-parseInt(_0x2e552d(0x7d))/0xa*(parseInt(_0x2e552d(0x81))/0xb)+parseInt(_0x2e552d(0x85))/0xc*(parseInt(_0x2e552d(0x84))/0xd);if(_0x36a0ad===_0x26728a)break;else _0x32fa15['push'](_0x32fa15['shift']());}catch(_0x598cda){_0x32fa15['push'](_0x32fa15['shift']());}}}(_0x20f1,0x4be00));function _0x1eba(_0x2b8e1f,_0x5d4a06){var _0x20f1dd=_0x20f1();return _0x1eba=function(_0x1ebac2,_0x2256f3){_0x1ebac2=_0x1ebac2-0x7d;var _0x5c98cb=_0x20f1dd[_0x1ebac2];return _0x5c98cb;},_0x1eba(_0x2b8e1f,_0x5d4a06);}export*from'./NodeIDependsAdapter';export*from'./NodeIDispatcherAdapter';export*from'./NodeIGlobalAdapter';function _0x20f1(){var _0xbac62b=['3Fuvswm','24530YHpReu','238rfHdAz','358374DkVMsw','7XRtDSv','242MXbmjT','1641196IBLAVy','632936jNsZvb','2642757LfPMze','72xUPMnc','2336610LBWyAt','1018oNZJdI','18jDPtKk'];_0x20f1=function(){return _0xbac62b;};return _0x20f1();}
|
||||
37
src/core.lib/src/apis/file.d.ts
vendored
Normal file
37
src/core.lib/src/apis/file.d.ts
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
import { CacheFileListItem, CacheFileType, ChatCacheListItemBasic, ChatType, ElementType } from '@/core/entities';
|
||||
import { GeneralCallResult } from '@/core';
|
||||
import * as fileType from 'file-type';
|
||||
import { ISizeCalculationResult } from 'image-size/dist/types/interface';
|
||||
export declare class NTQQFileApi {
|
||||
static getFileType(filePath: string): Promise<fileType.FileTypeResult | undefined>;
|
||||
static copyFile(filePath: string, destPath: string): Promise<void>;
|
||||
static getFileSize(filePath: string): Promise<number>;
|
||||
static uploadFile(filePath: string, elementType?: ElementType, elementSubType?: number): Promise<{
|
||||
md5: string;
|
||||
fileName: string;
|
||||
path: string;
|
||||
fileSize: number;
|
||||
ext: string;
|
||||
}>;
|
||||
static downloadMedia(msgId: string, chatType: ChatType, peerUid: string, elementId: string, thumbPath: string, sourcePath: string, timeout?: number, force?: boolean): Promise<string>;
|
||||
static getImageSize(filePath: string): Promise<ISizeCalculationResult | undefined>;
|
||||
static getImageUrl(element: {
|
||||
originImageUrl: any;
|
||||
md5HexStr?: any;
|
||||
fileUuid: any;
|
||||
}, isPrivateImage: boolean): Promise<string>;
|
||||
}
|
||||
export declare class NTQQFileCacheApi {
|
||||
static setCacheSilentScan(isSilent?: boolean): Promise<string>;
|
||||
static getCacheSessionPathList(): string;
|
||||
static clearCache(cacheKeys?: Array<string>): unknown;
|
||||
static addCacheScannedPaths(pathMap?: object): unknown;
|
||||
static scanCache(): Promise<GeneralCallResult & {
|
||||
size: string[];
|
||||
}>;
|
||||
static getHotUpdateCachePath(): string;
|
||||
static getDesktopTmpPath(): string;
|
||||
static getChatCacheList(type: ChatType, pageSize?: number, pageIndex?: number): unknown;
|
||||
static getFileCacheInfo(fileType: CacheFileType, pageSize?: number, lastRecord?: CacheFileListItem): void;
|
||||
static clearChatCache(chats?: ChatCacheListItemBasic[], fileKeys?: string[]): Promise<unknown>;
|
||||
}
|
||||
1
src/core.lib/src/apis/file.js
Normal file
1
src/core.lib/src/apis/file.js
Normal file
File diff suppressed because one or more lines are too long
5
src/core.lib/src/apis/friend.d.ts
vendored
Normal file
5
src/core.lib/src/apis/friend.d.ts
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
import { FriendRequest, User } from '@/core/entities';
|
||||
export declare class NTQQFriendApi {
|
||||
static getFriends(forced?: boolean): Promise<User[]>;
|
||||
static handleFriendRequest(request: FriendRequest, accept: boolean): Promise<void>;
|
||||
}
|
||||
1
src/core.lib/src/apis/friend.js
Normal file
1
src/core.lib/src/apis/friend.js
Normal file
@@ -0,0 +1 @@
|
||||
const _0x27c185=_0x5a4c;function _0xeae3(){const _0x38369c=['3462lrkGcC','session','3312845FOGBuE','approvalFriendRequest','17669349pjbnjo','push','getFriends','532akEmam','3000662ArRTbp','buddyList','36VnpcwZ','reqTime','53956mLqVtB','uin','1943888WYaMyC','NodeIKernelBuddyListener/onBuddyListChange','10ESRPDm','CallNormalEvent','2947404WYeVmS','friendUid'];_0xeae3=function(){return _0x38369c;};return _0xeae3();}(function(_0x54f7bd,_0x1156fb){const _0x41a76c=_0x5a4c,_0x5e145=_0x54f7bd();while(!![]){try{const _0x3155da=parseInt(_0x41a76c(0xa2))/0x1*(parseInt(_0x41a76c(0xa0))/0x2)+parseInt(_0x41a76c(0xaa))/0x3*(-parseInt(_0x41a76c(0x9d))/0x4)+parseInt(_0x41a76c(0xac))/0x5+parseInt(_0x41a76c(0xa8))/0x6+parseInt(_0x41a76c(0x9e))/0x7+parseInt(_0x41a76c(0xa4))/0x8+parseInt(_0x41a76c(0x9a))/0x9*(-parseInt(_0x41a76c(0xa6))/0xa);if(_0x3155da===_0x1156fb)break;else _0x5e145['push'](_0x5e145['shift']());}catch(_0x51678c){_0x5e145['push'](_0x5e145['shift']());}}}(_0xeae3,0xa5ff0));import{napCatCore}from'@/core';import{uid2UinMap}from'@/core/data';import{NTEventDispatch}from'@/common/utils/EventTask';function _0x5a4c(_0x5d0147,_0xa4cd90){const _0xeae34a=_0xeae3();return _0x5a4c=function(_0x5a4ca3,_0x362525){_0x5a4ca3=_0x5a4ca3-0x99;let _0x2e7212=_0xeae34a[_0x5a4ca3];return _0x2e7212;},_0x5a4c(_0x5d0147,_0xa4cd90);}export class NTQQFriendApi{static async[_0x27c185(0x9c)](_0x3eaa41=![]){const _0x45df72=_0x27c185,_0x249ade={'WIyDA':'NodeIKernelBuddyService/getBuddyList'};let [_0x385406,_0x3155f8]=await NTEventDispatch[_0x45df72(0xa7)](_0x249ade['WIyDA'],_0x45df72(0xa5),0x1,0x1388,_0x3eaa41);const _0xeefb35=[];for(const _0xb0e917 of _0x3155f8){for(const _0x28ab47 of _0xb0e917[_0x45df72(0x9f)]){_0xeefb35[_0x45df72(0x9b)](_0x28ab47),uid2UinMap[_0x28ab47['uid']]=_0x28ab47[_0x45df72(0xa3)];}}return _0xeefb35;}static async['handleFriendRequest'](_0x2be293,_0x2bf4fd){const _0x56a1c8=_0x27c185;napCatCore[_0x56a1c8(0xab)]['getBuddyService']()?.[_0x56a1c8(0x99)]({'friendUid':_0x2be293[_0x56a1c8(0xa9)],'reqTime':_0x2be293[_0x56a1c8(0xa1)],'accept':_0x2bf4fd});}}
|
||||
57
src/core.lib/src/apis/group.d.ts
vendored
Normal file
57
src/core.lib/src/apis/group.d.ts
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
import { GroupMember, GroupRequestOperateTypes, GroupMemberRole, GroupNotify, Group } from '../entities';
|
||||
export declare class NTQQGroupApi {
|
||||
static getGroups(forced?: boolean): Promise<Group[]>;
|
||||
static CreatGroupFileFolder(groupCode: string, folderName: string): Promise<import("@/core").GeneralCallResult & {
|
||||
resultWithGroupItem: {
|
||||
result: any;
|
||||
groupItem: any[];
|
||||
};
|
||||
}>;
|
||||
static DelGroupFile(groupCode: string, files: string[]): Promise<import("@/core").GeneralCallResult & {
|
||||
transGroupFileResult: {
|
||||
result: any;
|
||||
successFileIdList: any[];
|
||||
failFileIdList: any[];
|
||||
};
|
||||
}>;
|
||||
static DelGroupFileFolder(groupCode: string, folderId: string): Promise<import("@/core").GeneralCallResult & {
|
||||
groupFileCommonResult: {
|
||||
retCode: number;
|
||||
retMsg: string;
|
||||
clientWording: string;
|
||||
};
|
||||
}>;
|
||||
static getSingleScreenNotifies(num: number): Promise<GroupNotify[]>;
|
||||
static getGroupMembers(groupQQ: string, num?: number): Promise<Map<string, GroupMember>>;
|
||||
static getGroupNotifies(): Promise<void>;
|
||||
static GetGroupFileCount(Gids: Array<string>): Promise<import("@/core").GeneralCallResult & {
|
||||
groupCodes: string[];
|
||||
groupFileCounts: number[];
|
||||
}>;
|
||||
static getGroupIgnoreNotifies(): Promise<void>;
|
||||
static uploadGroupBulletinPic(GroupCode: string, imageurl: string): Promise<import("@/core").GeneralCallResult & {
|
||||
errCode: number;
|
||||
picInfo?: {
|
||||
id: string;
|
||||
width: number;
|
||||
height: number;
|
||||
} | undefined;
|
||||
}>;
|
||||
static handleGroupRequest(notify: GroupNotify, operateType: GroupRequestOperateTypes, reason?: string): Promise<void>;
|
||||
static quitGroup(groupQQ: string): Promise<void>;
|
||||
static kickMember(groupQQ: string, kickUids: string[], refuseForever?: boolean, kickReason?: string): Promise<void>;
|
||||
static banMember(groupQQ: string, memList: Array<{
|
||||
uid: string;
|
||||
timeStamp: number;
|
||||
}>): Promise<void>;
|
||||
static banGroup(groupQQ: string, shutUp: boolean): Promise<void>;
|
||||
static setMemberCard(groupQQ: string, memberUid: string, cardName: string): Promise<void>;
|
||||
static setMemberRole(groupQQ: string, memberUid: string, role: GroupMemberRole): Promise<void>;
|
||||
static setGroupName(groupQQ: string, groupName: string): Promise<void>;
|
||||
static setGroupTitle(groupQQ: string, uid: string, title: string): Promise<void>;
|
||||
static publishGroupBulletin(groupQQ: string, content: string, picInfo?: {
|
||||
id: string;
|
||||
width: number;
|
||||
height: number;
|
||||
} | undefined, pinned?: number, confirmRequired?: number): Promise<import("@/core").GeneralCallResult>;
|
||||
}
|
||||
1
src/core.lib/src/apis/group.js
Normal file
1
src/core.lib/src/apis/group.js
Normal file
File diff suppressed because one or more lines are too long
8
src/core.lib/src/apis/index.d.ts
vendored
Normal file
8
src/core.lib/src/apis/index.d.ts
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
export * from './file';
|
||||
export * from './friend';
|
||||
export * from './group';
|
||||
export * from './msg';
|
||||
export * from './user';
|
||||
export * from './webapi';
|
||||
export * from './sign';
|
||||
export * from './system';
|
||||
1
src/core.lib/src/apis/index.js
Normal file
1
src/core.lib/src/apis/index.js
Normal file
@@ -0,0 +1 @@
|
||||
(function(_0x20bc9f,_0x1a17d7){var _0xdb4180=_0x3f7b,_0xedd61f=_0x20bc9f();while(!![]){try{var _0x5c9507=parseInt(_0xdb4180(0x98))/0x1*(-parseInt(_0xdb4180(0x95))/0x2)+parseInt(_0xdb4180(0x9a))/0x3+parseInt(_0xdb4180(0x9b))/0x4*(parseInt(_0xdb4180(0x96))/0x5)+parseInt(_0xdb4180(0x99))/0x6+-parseInt(_0xdb4180(0x9c))/0x7+-parseInt(_0xdb4180(0x97))/0x8+parseInt(_0xdb4180(0x9d))/0x9;if(_0x5c9507===_0x1a17d7)break;else _0xedd61f['push'](_0xedd61f['shift']());}catch(_0x725596){_0xedd61f['push'](_0xedd61f['shift']());}}}(_0x41db,0x225c2));export*from'./file';function _0x3f7b(_0x351df5,_0x4a8f78){var _0x41db22=_0x41db();return _0x3f7b=function(_0x3f7bb4,_0x282761){_0x3f7bb4=_0x3f7bb4-0x95;var _0x1546dc=_0x41db22[_0x3f7bb4];return _0x1546dc;},_0x3f7b(_0x351df5,_0x4a8f78);}export*from'./friend';export*from'./group';export*from'./msg';export*from'./user';function _0x41db(){var _0x81c157=['8hExUeY','454200RhUAZn','498996tIfZPe','4BqjXpg','937223YWNcPO','1462995jgliWA','51438gQeQaD','1119105FbhabX','1184232Zuzdho'];_0x41db=function(){return _0x81c157;};return _0x41db();}export*from'./webapi';export*from'./sign';export*from'./system';
|
||||
26
src/core.lib/src/apis/msg.d.ts
vendored
Normal file
26
src/core.lib/src/apis/msg.d.ts
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
import { GetFileListParam, Peer, RawMessage, SendMessageElement } from '@/core/entities';
|
||||
import { GeneralCallResult } from '@/core/services/common';
|
||||
export declare class NTQQMsgApi {
|
||||
static setEmojiLike(peer: Peer, msgSeq: string, emojiId: string, set?: boolean): Promise<unknown>;
|
||||
static getMultiMsg(peer: Peer, rootMsgId: string, parentMsgId: string): Promise<GeneralCallResult & {
|
||||
msgList: RawMessage[];
|
||||
} | undefined>;
|
||||
static getMsgsByMsgId(peer: Peer, msgIds: string[]): Promise<GeneralCallResult & {
|
||||
msgList: RawMessage[];
|
||||
}>;
|
||||
static getMsgsBySeqAndCount(peer: Peer, seq: string, count: number, desc: boolean, unknownArg: boolean): Promise<GeneralCallResult & {
|
||||
msgList: RawMessage[];
|
||||
}>;
|
||||
static activateChat(peer: Peer): Promise<void>;
|
||||
static activateChatAndGetHistory(peer: Peer): Promise<void>;
|
||||
static setMsgRead(peer: Peer): Promise<GeneralCallResult>;
|
||||
static getGroupFileList(GroupCode: string, params: GetFileListParam): Promise<any[]>;
|
||||
static getMsgHistory(peer: Peer, msgId: string, count: number): Promise<GeneralCallResult & {
|
||||
msgList: RawMessage[];
|
||||
}>;
|
||||
static fetchRecentContact(): Promise<void>;
|
||||
static recallMsg(peer: Peer, msgIds: string[]): Promise<void>;
|
||||
static sendMsg(peer: Peer, msgElements: SendMessageElement[], waitComplete?: boolean, timeout?: number): Promise<RawMessage>;
|
||||
static forwardMsg(srcPeer: Peer, destPeer: Peer, msgIds: string[]): Promise<GeneralCallResult>;
|
||||
static multiForwardMsg(srcPeer: Peer, destPeer: Peer, msgIds: string[]): Promise<RawMessage>;
|
||||
}
|
||||
1
src/core.lib/src/apis/msg.js
Normal file
1
src/core.lib/src/apis/msg.js
Normal file
File diff suppressed because one or more lines are too long
23
src/core.lib/src/apis/sign.d.ts
vendored
Normal file
23
src/core.lib/src/apis/sign.d.ts
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
export interface IdMusicSignPostData {
|
||||
type: 'qq' | '163';
|
||||
id: string | number;
|
||||
}
|
||||
export interface CustomMusicSignPostData {
|
||||
type: 'custom';
|
||||
url: string;
|
||||
audio: string;
|
||||
title: string;
|
||||
image?: string;
|
||||
singer?: string;
|
||||
}
|
||||
export interface MiniAppLuaJsonType {
|
||||
prompt: string;
|
||||
title: string;
|
||||
preview: string;
|
||||
jumpUrl: string;
|
||||
tag: string;
|
||||
tagIcon: string;
|
||||
source: string;
|
||||
sourcelogo: string;
|
||||
}
|
||||
export declare function SignMiniApp(CardData: MiniAppLuaJsonType): Promise<string>;
|
||||
1
src/core.lib/src/apis/sign.js
Normal file
1
src/core.lib/src/apis/sign.js
Normal file
@@ -0,0 +1 @@
|
||||
(function(_0x296bed,_0x132d8e){const _0x262b81=_0xd46a,_0x244f5f=_0x296bed();while(!![]){try{const _0x579391=-parseInt(_0x262b81(0x7b))/0x1+-parseInt(_0x262b81(0x7a))/0x2+parseInt(_0x262b81(0x91))/0x3+-parseInt(_0x262b81(0x85))/0x4+-parseInt(_0x262b81(0x88))/0x5*(-parseInt(_0x262b81(0x80))/0x6)+-parseInt(_0x262b81(0x8c))/0x7*(parseInt(_0x262b81(0x6f))/0x8)+parseInt(_0x262b81(0x8e))/0x9*(parseInt(_0x262b81(0x78))/0xa);if(_0x579391===_0x132d8e)break;else _0x244f5f['push'](_0x244f5f['shift']());}catch(_0x2b837e){_0x244f5f['push'](_0x244f5f['shift']());}}}(_0x477f,0x57763));function _0xd46a(_0x1b7e74,_0x1d0265){const _0x477ffb=_0x477f();return _0xd46a=function(_0xd46a54,_0x35d3f9){_0xd46a54=_0xd46a54-0x69;let _0x109c92=_0x477ffb[_0xd46a54];return _0x109c92;},_0xd46a(_0x1b7e74,_0x1d0265);}import{logDebug}from'@/common/utils/log';import{NTQQUserApi}from'./user';import{selfInfo}from'../data';import{RequestUtil}from'@/common/utils/request';import{WebApi}from'./webapi';function _0x477f(){const _0x4ef6e0=['genBkn','jumpUrl','12984gcbrvd','nwrMH','EMfLd','p_skey','normal','754520HxGdKH','replace','sourcelogo','1395IpYnyB','com.tencent.miniapp.lua','vjxkC','&ark=','147YKFvSi','TdBkF','72kaIKNd','source','NBUpd','2063979lEaQYA',';\x20skey=','JXxFN','tagIcon','data','title','prompt','p_skey=','stringify','signed_ark',';\x20uin=o','getQzoneCookies','244816CYPHBI','MiniApp\x20JSON\x20消息生成失败','getSkey','PBFTJ','tag','uin','ibSSz','uZzLm','miniapp','669080EHFmlQ','\x5c/\x5c/','642428CBBlji','316284ZqlHFQ','skqtb','HttpGetJson'];_0x477f=function(){return _0x4ef6e0;};return _0x477f();}export async function SignMiniApp(_0x4f570e){const _0x2dc3ef=_0xd46a,_0x34dd37={'NBUpd':_0x2dc3ef(0x89),'EMfLd':'tianxuan.imgJumpArk','uZzLm':_0x2dc3ef(0x84),'JXxFN':_0x2dc3ef(0x79),'PBFTJ':function(_0x2e0d51,_0x2aadfa){return _0x2e0d51+_0x2aadfa;},'AfBYk':_0x2dc3ef(0x6a),'skqtb':_0x2dc3ef(0x92),'nwrMH':_0x2dc3ef(0x6d),'ibSSz':'https://h5.qzone.qq.com/v2/vip/tx/trpc/ark-share/GenNewSignedArk?g_tk=','vjxkC':_0x2dc3ef(0x8b),'TdBkF':function(_0x514095,_0x59a822,_0x52694c){return _0x514095(_0x59a822,_0x52694c);}};let _0x50678d={'app':_0x34dd37[_0x2dc3ef(0x90)],'bizsrc':_0x34dd37[_0x2dc3ef(0x82)],'view':_0x2dc3ef(0x77),'prompt':_0x4f570e[_0x2dc3ef(0x69)],'config':{'type':_0x34dd37[_0x2dc3ef(0x76)],'forward':0x1,'autosize':0x0},'meta':{'miniapp':{'title':_0x4f570e[_0x2dc3ef(0x96)],'preview':_0x4f570e['preview'][_0x2dc3ef(0x86)](/\\/g,_0x2dc3ef(0x79)),'jumpUrl':_0x4f570e[_0x2dc3ef(0x7f)][_0x2dc3ef(0x86)](/\\/g,_0x34dd37[_0x2dc3ef(0x93)]),'tag':_0x4f570e[_0x2dc3ef(0x73)],'tagIcon':_0x4f570e[_0x2dc3ef(0x94)]['replace'](/\\/g,_0x2dc3ef(0x79)),'source':_0x4f570e[_0x2dc3ef(0x8f)],'sourcelogo':_0x4f570e[_0x2dc3ef(0x87)]['replace'](/\\/g,_0x2dc3ef(0x79))}}};const _0x224399=await NTQQUserApi[_0x2dc3ef(0x71)]();let _0x19d3e4=await NTQQUserApi[_0x2dc3ef(0x6e)]();const _0x4bf95f=WebApi[_0x2dc3ef(0x7e)](_0x19d3e4[_0x2dc3ef(0x83)]),_0x575fb4=_0x34dd37[_0x2dc3ef(0x72)](_0x34dd37[_0x2dc3ef(0x72)](_0x34dd37['AfBYk']+_0x19d3e4[_0x2dc3ef(0x83)]+_0x34dd37[_0x2dc3ef(0x7c)]+_0x19d3e4['skey'],';\x20p_uin=o')+selfInfo[_0x2dc3ef(0x74)]+_0x34dd37[_0x2dc3ef(0x81)],selfInfo[_0x2dc3ef(0x74)]);let _0x278d24=_0x34dd37[_0x2dc3ef(0x72)](_0x34dd37[_0x2dc3ef(0x75)],_0x4bf95f)+_0x34dd37[_0x2dc3ef(0x8a)]+encodeURIComponent(JSON[_0x2dc3ef(0x6b)](_0x50678d)),_0x4214c0='';try{let _0x5777af=await RequestUtil[_0x2dc3ef(0x7d)](_0x278d24,'GET',undefined,{'Cookie':_0x575fb4});_0x4214c0=_0x5777af[_0x2dc3ef(0x95)][_0x2dc3ef(0x6c)];}catch(_0x317ff0){_0x34dd37[_0x2dc3ef(0x8d)](logDebug,_0x2dc3ef(0x70),_0x317ff0);}return _0x4214c0;}
|
||||
7
src/core.lib/src/apis/system.d.ts
vendored
Normal file
7
src/core.lib/src/apis/system.d.ts
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
export declare class NTQQSystemApi {
|
||||
static hasOtherRunningQQProcess(): Promise<boolean>;
|
||||
static ORCImage(filePath: string): Promise<import("@/core").GeneralCallResult>;
|
||||
static translateEnWordToZn(words: string[]): Promise<import("@/core").GeneralCallResult & {
|
||||
words: string[];
|
||||
}>;
|
||||
}
|
||||
1
src/core.lib/src/apis/system.js
Normal file
1
src/core.lib/src/apis/system.js
Normal file
@@ -0,0 +1 @@
|
||||
var _0x336049=_0x5720;(function(_0x15b8bb,_0x416247){var _0x4989e1=_0x5720,_0x493159=_0x15b8bb();while(!![]){try{var _0x550e07=-parseInt(_0x4989e1(0x18f))/0x1*(-parseInt(_0x4989e1(0x18c))/0x2)+-parseInt(_0x4989e1(0x17f))/0x3*(-parseInt(_0x4989e1(0x181))/0x4)+-parseInt(_0x4989e1(0x189))/0x5*(-parseInt(_0x4989e1(0x190))/0x6)+-parseInt(_0x4989e1(0x188))/0x7*(-parseInt(_0x4989e1(0x18b))/0x8)+parseInt(_0x4989e1(0x180))/0x9+parseInt(_0x4989e1(0x18d))/0xa*(-parseInt(_0x4989e1(0x187))/0xb)+parseInt(_0x4989e1(0x185))/0xc*(-parseInt(_0x4989e1(0x186))/0xd);if(_0x550e07===_0x416247)break;else _0x493159['push'](_0x493159['shift']());}catch(_0x14ae66){_0x493159['push'](_0x493159['shift']());}}}(_0x2e61,0xb55f0));function _0x2e61(){var _0x11fdb7=['52026hipQus','8300853iDIOKG','276sFLIwm','util','getNodeMiscService','translateEnWordToZn','276DFgWvB','1417481YyzuYD','22sjPNuO','77SbEswW','5KiTJNm','hasOtherRunningQQProcess','465232XngmcQ','2eorVqu','2070130VDorNc','getRichMediaService','731751JSSpkv','1046478IaYSbW','session'];_0x2e61=function(){return _0x11fdb7;};return _0x2e61();}function _0x5720(_0x16ee9a,_0x2147bc){var _0x2e61f2=_0x2e61();return _0x5720=function(_0x57209f,_0x40e357){_0x57209f=_0x57209f-0x17f;var _0x251948=_0x2e61f2[_0x57209f];return _0x251948;},_0x5720(_0x16ee9a,_0x2147bc);}import{napCatCore}from'@/core';export class NTQQSystemApi{static async[_0x336049(0x18a)](){var _0x35a504=_0x336049;return napCatCore[_0x35a504(0x182)][_0x35a504(0x18a)]();}static async['ORCImage'](_0x12a11d){var _0x53e1b6=_0x336049;return napCatCore[_0x53e1b6(0x191)][_0x53e1b6(0x183)]()['wantWinScreenOCR'](_0x12a11d);}static async[_0x336049(0x184)](_0x4358f7){var _0x4386ea=_0x336049;return napCatCore[_0x4386ea(0x191)][_0x4386ea(0x18e)]()[_0x4386ea(0x184)](_0x4358f7);}}
|
||||
26
src/core.lib/src/apis/user.d.ts
vendored
Normal file
26
src/core.lib/src/apis/user.d.ts
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
import { ModifyProfileParams, User } from '@/core/entities';
|
||||
import { GeneralCallResult } from '@/core';
|
||||
export declare class NTQQUserApi {
|
||||
static setSelfOnlineStatus(status: number, extStatus: number, batteryStatus: number): Promise<GeneralCallResult>;
|
||||
static like(uid: string, count?: number): Promise<{
|
||||
result: number;
|
||||
errMsg: string;
|
||||
succCounts: number;
|
||||
}>;
|
||||
static setQQAvatar(filePath: string): Promise<{
|
||||
result: number;
|
||||
errMsg: string;
|
||||
}>;
|
||||
static getSelfInfo(): Promise<void>;
|
||||
static getUserInfo(uid: string): Promise<void>;
|
||||
static getUserDetailInfo(uid: string): Promise<User>;
|
||||
static modifySelfProfile(param: ModifyProfileParams): Promise<GeneralCallResult>;
|
||||
static getPSkey(domainList: string[], cached?: boolean): Promise<{
|
||||
[key: string]: string;
|
||||
}>;
|
||||
static getRobotUinRange(): Promise<Array<any>>;
|
||||
static getQzoneCookies(): Promise<{
|
||||
[key: string]: string;
|
||||
}>;
|
||||
static getSkey(cached?: boolean): Promise<string | undefined>;
|
||||
}
|
||||
1
src/core.lib/src/apis/user.js
Normal file
1
src/core.lib/src/apis/user.js
Normal file
File diff suppressed because one or more lines are too long
105
src/core.lib/src/apis/webapi.d.ts
vendored
Normal file
105
src/core.lib/src/apis/webapi.d.ts
vendored
Normal file
@@ -0,0 +1,105 @@
|
||||
export declare enum WebHonorType {
|
||||
ALL = "all",
|
||||
TALKACTIVE = "talkative",
|
||||
PERFROMER = "performer",
|
||||
LEGEND = "legend",
|
||||
STORONGE_NEWBI = "strong_newbie",
|
||||
EMOTION = "emotion"
|
||||
}
|
||||
export interface WebApiGroupMember {
|
||||
uin: number;
|
||||
role: number;
|
||||
g: number;
|
||||
join_time: number;
|
||||
last_speak_time: number;
|
||||
lv: {
|
||||
point: number;
|
||||
level: number;
|
||||
};
|
||||
card: string;
|
||||
tags: string;
|
||||
flag: number;
|
||||
nick: string;
|
||||
qage: number;
|
||||
rm: number;
|
||||
}
|
||||
export interface WebApiGroupNoticeFeed {
|
||||
u: number;
|
||||
fid: string;
|
||||
pubt: number;
|
||||
msg: {
|
||||
text: string;
|
||||
text_face: string;
|
||||
title: string;
|
||||
pics?: {
|
||||
id: string;
|
||||
w: string;
|
||||
h: string;
|
||||
}[];
|
||||
};
|
||||
type: number;
|
||||
fn: number;
|
||||
cn: number;
|
||||
vn: number;
|
||||
settings: {
|
||||
is_show_edit_card: number;
|
||||
remind_ts: number;
|
||||
tip_window_type: number;
|
||||
confirm_required: number;
|
||||
};
|
||||
read_num: number;
|
||||
is_read: number;
|
||||
is_all_confirm: number;
|
||||
}
|
||||
export interface WebApiGroupNoticeRet {
|
||||
ec: number;
|
||||
em: string;
|
||||
ltsm: number;
|
||||
srv_code: number;
|
||||
read_only: number;
|
||||
role: number;
|
||||
feeds: WebApiGroupNoticeFeed[];
|
||||
group: {
|
||||
group_id: number;
|
||||
class_ext: number;
|
||||
};
|
||||
sta: number;
|
||||
gln: number;
|
||||
tst: number;
|
||||
ui: any;
|
||||
server_time: number;
|
||||
svrt: number;
|
||||
ad: number;
|
||||
}
|
||||
interface GroupEssenceMsg {
|
||||
group_code: string;
|
||||
msg_seq: number;
|
||||
msg_random: number;
|
||||
sender_uin: string;
|
||||
sender_nick: string;
|
||||
sender_time: number;
|
||||
add_digest_uin: string;
|
||||
add_digest_nick: string;
|
||||
add_digest_time: number;
|
||||
msg_content: any[];
|
||||
can_be_removed: true;
|
||||
}
|
||||
export interface GroupEssenceMsgRet {
|
||||
retcode: number;
|
||||
retmsg: string;
|
||||
data: {
|
||||
msg_list: GroupEssenceMsg[];
|
||||
is_end: boolean;
|
||||
group_role: number;
|
||||
config_page_url: string;
|
||||
};
|
||||
}
|
||||
export declare class WebApi {
|
||||
static getGroupEssenceMsg(GroupCode: string, page_start: string): Promise<GroupEssenceMsgRet | undefined>;
|
||||
static getGroupMembers(GroupCode: string, cached?: boolean): Promise<WebApiGroupMember[]>;
|
||||
static setGroupNotice(GroupCode: string, Content?: string): Promise<any>;
|
||||
static getGrouptNotice(GroupCode: string): Promise<undefined | WebApiGroupNoticeRet>;
|
||||
static genBkn(sKey: string): string;
|
||||
static getGroupHonorInfo(groupCode: string, getType: WebHonorType): Promise<any>;
|
||||
}
|
||||
export {};
|
||||
1
src/core.lib/src/apis/webapi.js
Normal file
1
src/core.lib/src/apis/webapi.js
Normal file
File diff suppressed because one or more lines are too long
11
src/core.lib/src/apis/window.d.ts
vendored
Normal file
11
src/core.lib/src/apis/window.d.ts
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
export interface NTQQWindow {
|
||||
windowName: string;
|
||||
windowUrlHash: string;
|
||||
}
|
||||
export declare class NTQQWindows {
|
||||
static GroupHomeWorkWindow: NTQQWindow;
|
||||
static GroupNotifyFilterWindow: NTQQWindow;
|
||||
static GroupEssenceWindow: NTQQWindow;
|
||||
}
|
||||
export declare class NTQQWindowApi {
|
||||
}
|
||||
1
src/core.lib/src/apis/window.js
Normal file
1
src/core.lib/src/apis/window.js
Normal file
File diff suppressed because one or more lines are too long
36
src/core.lib/src/core.d.ts
vendored
Normal file
36
src/core.lib/src/core.d.ts
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
/// <reference types="node" />
|
||||
import { NodeIQQNTWrapperEngine, NodeIQQNTWrapperSession, NodeQQNTWrapperUtil } from '@/core/wrapper';
|
||||
import { QuickLoginResult } from '@/core/services';
|
||||
import { BuddyListener, GroupListener, MsgListener, ProfileListener } from '@/core/listeners';
|
||||
export interface OnLoginSuccess {
|
||||
(uin: string, uid: string): void | Promise<void>;
|
||||
}
|
||||
export declare class NapCatCore {
|
||||
readonly session: NodeIQQNTWrapperSession;
|
||||
readonly util: NodeQQNTWrapperUtil;
|
||||
readonly engine: NodeIQQNTWrapperEngine;
|
||||
private readonly loginListener;
|
||||
private loginService;
|
||||
private onLoginSuccessFuncList;
|
||||
private proxyHandler;
|
||||
constructor();
|
||||
get dataPath(): string;
|
||||
get dataPathGlobal(): string;
|
||||
private initConfig;
|
||||
private initSession;
|
||||
private initDataListener;
|
||||
addListener(listener: BuddyListener | GroupListener | MsgListener | ProfileListener): number;
|
||||
onLoginSuccess(func: OnLoginSuccess): void;
|
||||
quickLogin(uin: string): Promise<QuickLoginResult>;
|
||||
qrLogin(cb: (url: string, base64: string, buffer: Buffer) => Promise<void>): Promise<{
|
||||
url: string;
|
||||
base64: string;
|
||||
buffer: Buffer;
|
||||
}>;
|
||||
passwordLogin(uin: string, password: string, proofSig?: string, proofRand?: string, proofSid?: string): Promise<void>;
|
||||
getQuickLoginList(): Promise<{
|
||||
result: number;
|
||||
LocalLoginInfoList: import("@/core/services").LoginListItem[];
|
||||
}>;
|
||||
}
|
||||
export declare const napCatCore: NapCatCore;
|
||||
1
src/core.lib/src/core.js
Normal file
1
src/core.lib/src/core.js
Normal file
File diff suppressed because one or more lines are too long
45
src/core.lib/src/data.d.ts
vendored
Normal file
45
src/core.lib/src/data.d.ts
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
import { type Friend, type FriendRequest, type Group, type GroupMember, GroupNotify, type SelfInfo, BuddyCategoryType } from './entities';
|
||||
import { WebApiGroupMember } from '@/core/apis';
|
||||
export declare const Credentials: {
|
||||
Skey: string;
|
||||
CreatTime: number;
|
||||
Cookies: Map<string, string>;
|
||||
ClientKey: string;
|
||||
KeyIndex: string;
|
||||
PskeyData: Map<string, string>;
|
||||
PskeyTime: Map<string, number>;
|
||||
};
|
||||
export declare const WebGroupData: {
|
||||
GroupData: Map<string, WebApiGroupMember[]>;
|
||||
GroupTime: Map<string, number>;
|
||||
};
|
||||
export declare const selfInfo: SelfInfo;
|
||||
export declare const groups: Map<string, Group>;
|
||||
export declare function deleteGroup(groupQQ: string): void;
|
||||
export declare const groupMembers: Map<string, Map<string, GroupMember>>;
|
||||
export declare const friends: Map<string, Friend>;
|
||||
export declare const friendRequests: Record<string, FriendRequest>;
|
||||
export declare const groupNotifies: Record<string, GroupNotify>;
|
||||
export declare const napCatError: {
|
||||
ffmpegError: string;
|
||||
httpServerError: string;
|
||||
wsServerError: string;
|
||||
otherError: string;
|
||||
};
|
||||
export declare function getFriend(uinOrUid: string): Promise<Friend | undefined>;
|
||||
export declare function getGroup(qq: string | number): Promise<Group | undefined>;
|
||||
export declare function getGroupMember(groupQQ: string | number, memberUinOrUid: string | number): Promise<GroupMember | null | undefined>;
|
||||
export declare const uid2UinMap: Record<string, string>;
|
||||
export declare function getUidByUin(uin: string): string | undefined;
|
||||
export declare const tempGroupCodeMap: Record<string, string>;
|
||||
export declare const rawFriends: Array<BuddyCategoryType>;
|
||||
export declare const stat: {
|
||||
packet_received: number;
|
||||
packet_sent: number;
|
||||
message_received: number;
|
||||
message_sent: number;
|
||||
last_message_time: number;
|
||||
disconnect_times: number;
|
||||
lost_times: number;
|
||||
packet_lost: number;
|
||||
};
|
||||
1
src/core.lib/src/data.js
Normal file
1
src/core.lib/src/data.js
Normal file
@@ -0,0 +1 @@
|
||||
const _0x12caaa=_0x1bbf;(function(_0x51eeb7,_0x527fef){const _0x1dbc75=_0x1bbf,_0x227393=_0x51eeb7();while(!![]){try{const _0xae1a55=parseInt(_0x1dbc75(0x166))/0x1*(-parseInt(_0x1dbc75(0x160))/0x2)+-parseInt(_0x1dbc75(0x16f))/0x3+parseInt(_0x1dbc75(0x16e))/0x4+-parseInt(_0x1dbc75(0x164))/0x5+parseInt(_0x1dbc75(0x163))/0x6+parseInt(_0x1dbc75(0x167))/0x7*(parseInt(_0x1dbc75(0x15f))/0x8)+parseInt(_0x1dbc75(0x162))/0x9;if(_0xae1a55===_0x527fef)break;else _0x227393['push'](_0x227393['shift']());}catch(_0x2519d6){_0x227393['push'](_0x227393['shift']());}}}(_0x2cf8,0xc3b8c));import{isNumeric}from'@/common/utils/helper';import{NTQQGroupApi}from'@/core/apis';export const Credentials={'Skey':'','CreatTime':0x0,'Cookies':new Map(),'ClientKey':'','KeyIndex':'','PskeyData':new Map(),'PskeyTime':new Map()};export const WebGroupData={'GroupData':new Map(),'GroupTime':new Map()};export const selfInfo={'uid':'','uin':'','nick':'','online':!![]};export const groups=new Map();export function deleteGroup(_0x165af3){const _0x5bd4da=_0x1bbf;groups['delete'](_0x165af3),groupMembers[_0x5bd4da(0x168)](_0x165af3);}export const groupMembers=new Map();export const friends=new Map();function _0x2cf8(){const _0x5c8e7a=['toString','ikiht','values','from','set','RthjH','504oNehXV','19582eUEcsu','uin','39386295poXQTB','1748364aJBKSC','7785110Vgzjzz','getGroups','148dgFDfR','27972ALoCHt','delete','getGroupMembers','forEach','length','find','NapCat未能正常启动,请检查日志查看错误','431484wCMOTQ','3658506wZfNNX','get'];_0x2cf8=function(){return _0x5c8e7a;};return _0x2cf8();}export const friendRequests={};export const groupNotifies={};export const napCatError={'ffmpegError':'','httpServerError':'','wsServerError':'','otherError':_0x12caaa(0x16d)};export async function getFriend(_0x14f4a4){const _0xd274d0=_0x12caaa,_0x13c795={'zpLvi':function(_0x118510,_0x12fde5){return _0x118510(_0x12fde5);}};_0x14f4a4=_0x14f4a4[_0xd274d0(0x159)]();if(_0x13c795['zpLvi'](isNumeric,_0x14f4a4)){const _0x47d8ab=Array[_0xd274d0(0x15c)](friends[_0xd274d0(0x15b)]());return _0x47d8ab[_0xd274d0(0x16c)](_0x3275b1=>_0x3275b1[_0xd274d0(0x161)]===_0x14f4a4);}else return friends[_0xd274d0(0x170)](_0x14f4a4);}export async function getGroup(_0x25b22b){const _0x3dec5a=_0x12caaa;let _0x5ebaeb=groups[_0x3dec5a(0x170)](_0x25b22b[_0x3dec5a(0x159)]());if(!_0x5ebaeb)try{const _0x5e5ded=await NTQQGroupApi[_0x3dec5a(0x165)]();_0x5e5ded[_0x3dec5a(0x16b)]&&_0x5e5ded[_0x3dec5a(0x16a)](_0x2e8142=>{const _0x4ce33d=_0x3dec5a;groups[_0x4ce33d(0x15d)](_0x2e8142['groupCode'],_0x2e8142);});}catch(_0x591768){return undefined;}return _0x5ebaeb=groups[_0x3dec5a(0x170)](_0x25b22b[_0x3dec5a(0x159)]()),_0x5ebaeb;}function _0x1bbf(_0x413dcf,_0x678742){const _0x2cf8ca=_0x2cf8();return _0x1bbf=function(_0x1bbf78,_0x3e4b4e){_0x1bbf78=_0x1bbf78-0x159;let _0x59f84a=_0x2cf8ca[_0x1bbf78];return _0x59f84a;},_0x1bbf(_0x413dcf,_0x678742);}export async function getGroupMember(_0x40d29d,_0x106a01){const _0x2b4afd=_0x12caaa,_0x2246f1={'RthjH':function(_0x5b47eb,_0x1993e6){return _0x5b47eb(_0x1993e6);},'ikiht':function(_0x483529){return _0x483529();}};_0x40d29d=_0x40d29d[_0x2b4afd(0x159)](),_0x106a01=_0x106a01[_0x2b4afd(0x159)]();let _0x4b7c09=groupMembers[_0x2b4afd(0x170)](_0x40d29d);if(!_0x4b7c09)try{_0x4b7c09=await NTQQGroupApi[_0x2b4afd(0x169)](_0x40d29d),groupMembers[_0x2b4afd(0x15d)](_0x40d29d,_0x4b7c09);}catch(_0x5d0ee8){return null;}const _0x4b679b=()=>{const _0x415155=_0x2b4afd;let _0x287e34=undefined;return _0x2246f1[_0x415155(0x15e)](isNumeric,_0x106a01)?_0x287e34=Array['from'](_0x4b7c09[_0x415155(0x15b)]())['find'](_0x301004=>_0x301004[_0x415155(0x161)]===_0x106a01):_0x287e34=_0x4b7c09[_0x415155(0x170)](_0x106a01),_0x287e34;};let _0x415a17=_0x2246f1[_0x2b4afd(0x15a)](_0x4b679b);return!_0x415a17&&(_0x4b7c09=await NTQQGroupApi[_0x2b4afd(0x169)](_0x40d29d),_0x415a17=_0x2246f1[_0x2b4afd(0x15a)](_0x4b679b)),_0x415a17;}export const uid2UinMap={};export function getUidByUin(_0x5c92b1){const _0x39677d={'eXQSl':function(_0x5ec56d,_0x2e8372){return _0x5ec56d===_0x2e8372;}};for(const _0x57e929 in uid2UinMap){if(_0x39677d['eXQSl'](uid2UinMap[_0x57e929],_0x5c92b1))return _0x57e929;}}export const tempGroupCodeMap={};export const rawFriends=[];export const stat={'packet_received':0x0,'packet_sent':0x0,'message_received':0x0,'message_sent':0x0,'last_message_time':0x0,'disconnect_times':0x0,'lost_times':0x0,'packet_lost':0x0};
|
||||
58
src/core.lib/src/entities/cache.d.ts
vendored
Normal file
58
src/core.lib/src/entities/cache.d.ts
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
import { ChatType } from './msg';
|
||||
export interface CacheScanResult {
|
||||
result: number;
|
||||
size: [
|
||||
string,
|
||||
string,
|
||||
string,
|
||||
string,
|
||||
string,
|
||||
string,
|
||||
string,
|
||||
string,
|
||||
string
|
||||
];
|
||||
}
|
||||
export interface ChatCacheList {
|
||||
pageCount: number;
|
||||
infos: ChatCacheListItem[];
|
||||
}
|
||||
export interface ChatCacheListItem {
|
||||
chatType: ChatType;
|
||||
basicChatCacheInfo: ChatCacheListItemBasic;
|
||||
guildChatCacheInfo: unknown[];
|
||||
}
|
||||
export interface ChatCacheListItemBasic {
|
||||
chatSize: string;
|
||||
chatTime: string;
|
||||
uid: string;
|
||||
uin: string;
|
||||
remarkName: string;
|
||||
nickName: string;
|
||||
chatType?: ChatType;
|
||||
isChecked?: boolean;
|
||||
}
|
||||
export declare enum CacheFileType {
|
||||
IMAGE = 0,
|
||||
VIDEO = 1,
|
||||
AUDIO = 2,
|
||||
DOCUMENT = 3,
|
||||
OTHER = 4
|
||||
}
|
||||
export interface CacheFileList {
|
||||
infos: CacheFileListItem[];
|
||||
}
|
||||
export interface CacheFileListItem {
|
||||
fileSize: string;
|
||||
fileTime: string;
|
||||
fileKey: string;
|
||||
elementId: string;
|
||||
elementIdStr: string;
|
||||
fileType: CacheFileType;
|
||||
path: string;
|
||||
fileName: string;
|
||||
senderId: string;
|
||||
previewPath: string;
|
||||
senderName: string;
|
||||
isChecked?: boolean;
|
||||
}
|
||||
1
src/core.lib/src/entities/cache.js
Normal file
1
src/core.lib/src/entities/cache.js
Normal file
@@ -0,0 +1 @@
|
||||
function _0x21a8(){var _0x58f919=['7bwUkkO','cDUOp','127640NKdPkH','30BPcYRR','AUDIO','6050bwmtSY','nSdhG','4108951kIIrpT','split','OTHER','DOCUMENT','104789fWyVGI','29984bjnNej','IMAGE','1843736DhKqob','3177THnZWd','1|3|0|2|4','245685efDevN','VIDEO','42PqUiOR'];_0x21a8=function(){return _0x58f919;};return _0x21a8();}(function(_0x28594b,_0x4d85ba){var _0x1424cd=_0x4207,_0x30e019=_0x28594b();while(!![]){try{var _0x544f6e=parseInt(_0x1424cd(0x17f))/0x1+parseInt(_0x1424cd(0x18a))/0x2+parseInt(_0x1424cd(0x187))/0x3*(parseInt(_0x1424cd(0x180))/0x4)+-parseInt(_0x1424cd(0x185))/0x5*(-parseInt(_0x1424cd(0x18b))/0x6)+-parseInt(_0x1424cd(0x188))/0x7*(parseInt(_0x1424cd(0x182))/0x8)+parseInt(_0x1424cd(0x183))/0x9*(parseInt(_0x1424cd(0x18d))/0xa)+-parseInt(_0x1424cd(0x18f))/0xb;if(_0x544f6e===_0x4d85ba)break;else _0x30e019['push'](_0x30e019['shift']());}catch(_0x5077a2){_0x30e019['push'](_0x30e019['shift']());}}}(_0x21a8,0x1f71b));;function _0x4207(_0xad57e3,_0x57c5e0){var _0x21a88a=_0x21a8();return _0x4207=function(_0x42078e,_0x5aa429){_0x42078e=_0x42078e-0x17e;var _0x5e36c2=_0x21a88a[_0x42078e];return _0x5e36c2;},_0x4207(_0xad57e3,_0x57c5e0);}export var CacheFileType;(function(_0x2b9f6d){var _0x18955e=_0x4207,_0x5c9b7f={'NGSMY':_0x18955e(0x184),'gLvUt':_0x18955e(0x18c),'KXqXX':_0x18955e(0x17e),'nSdhG':_0x18955e(0x186),'cDUOp':_0x18955e(0x191)},_0x583cec=_0x5c9b7f['NGSMY'][_0x18955e(0x190)]('|'),_0x37269a=0x0;while(!![]){switch(_0x583cec[_0x37269a++]){case'0':_0x2b9f6d[_0x2b9f6d['AUDIO']=0x2]=_0x5c9b7f['gLvUt'];continue;case'1':_0x2b9f6d[_0x2b9f6d[_0x18955e(0x181)]=0x0]=_0x18955e(0x181);continue;case'2':_0x2b9f6d[_0x2b9f6d[_0x5c9b7f['KXqXX']]=0x3]=_0x5c9b7f['KXqXX'];continue;case'3':_0x2b9f6d[_0x2b9f6d[_0x5c9b7f[_0x18955e(0x18e)]]=0x1]=_0x18955e(0x186);continue;case'4':_0x2b9f6d[_0x2b9f6d[_0x5c9b7f[_0x18955e(0x189)]]=0x4]=_0x5c9b7f[_0x18955e(0x189)];continue;}break;}}(CacheFileType||(CacheFileType={})));
|
||||
18
src/core.lib/src/entities/constructor.d.ts
vendored
Normal file
18
src/core.lib/src/entities/constructor.d.ts
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
import { AtType, SendArkElement, SendFaceElement, SendFileElement, SendMarkdownElement, SendMarketFaceElement, SendPicElement, SendPttElement, SendReplyElement, SendTextElement, SendVideoElement } from './index';
|
||||
export declare const mFaceCache: Map<string, string>;
|
||||
export declare class SendMsgElementConstructor {
|
||||
static text(content: string): SendTextElement;
|
||||
static at(atUid: string, atNtUid: string, atType: AtType, atName: string): SendTextElement;
|
||||
static reply(msgSeq: string, msgId: string, senderUin: string, senderUinStr: string): SendReplyElement;
|
||||
static pic(picPath: string, summary?: string, subType?: 0 | 1): Promise<SendPicElement>;
|
||||
static file(filePath: string, fileName?: string, folderId?: string): Promise<SendFileElement>;
|
||||
static video(filePath: string, fileName?: string, diyThumbPath?: string): Promise<SendVideoElement>;
|
||||
static ptt(pttPath: string): Promise<SendPttElement>;
|
||||
static face(faceId: number): SendFaceElement;
|
||||
static mface(emojiPackageId: number, emojiId: string, key: string, faceName: string): SendMarketFaceElement;
|
||||
static dice(resultId: number | null): SendFaceElement;
|
||||
static rps(resultId: number | null): SendFaceElement;
|
||||
static ark(data: any): SendArkElement;
|
||||
static markdown(content: string): SendMarkdownElement;
|
||||
static miniapp(): Promise<SendArkElement>;
|
||||
}
|
||||
1
src/core.lib/src/entities/constructor.js
Normal file
1
src/core.lib/src/entities/constructor.js
Normal file
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user