Compare commits

...

842 Commits

Author SHA1 Message Date
手瓜一十雪
caf23792cb Merge branch 'main' of https://github.com/NapNeko/NapCatQQ 2024-08-16 13:01:38 +08:00
手瓜一十雪
e430db20aa release: 2.0.29 2024-08-16 13:01:30 +08:00
手瓜一十雪
6fc5da9b67 Merge pull request #267 from Fripine/fix/OB11GroupRequestEvent
fix: wrong user_id in GroupRequestEvent
2024-08-16 13:00:32 +08:00
Fripine
f428e57724 fix: GroupRequestEvent 2024-08-16 12:57:42 +08:00
手瓜一十雪
14ab21fe9a Merge pull request #266 from Fripine/fix/OB11FriendRequestEvent
fix: wrong comment words in FriendRequestEvent
2024-08-16 12:44:33 +08:00
手瓜一十雪
85626e19da release: 2.0.28 2024-08-16 12:44:11 +08:00
Fripine
8712160fd7 fix: FriendRequestEvent 2024-08-16 12:21:33 +08:00
手瓜一十雪
75b33f5cb1 chore: fix 2024-08-16 12:16:21 +08:00
手瓜一十雪
f5e8ede847 release: 2.0.27 2024-08-16 10:49:22 +08:00
手瓜一十雪
3b3f684a8c chore: 清除废弃代码 2024-08-16 09:52:50 +08:00
手瓜一十雪
a78b60d40e chore: 进一步识别会话 2024-08-16 09:37:36 +08:00
手瓜一十雪
9ff06a3c44 release: 2.0.26 2024-08-15 23:08:31 +08:00
手瓜一十雪
8532dc486c fix: error 2024-08-15 23:07:59 +08:00
手瓜一十雪
861340f4bf release: 2.0.25 2024-08-15 22:17:58 +08:00
手瓜一十雪
cdcb51ebe4 build: v2.0.24 2024-08-15 20:11:45 +08:00
手瓜一十雪
0b11786d7d fix 2024-08-15 20:10:35 +08:00
手瓜一十雪
1742247a9a build: fix 2024-08-15 19:40:33 +08:00
手瓜一十雪
42bad123b2 chore: 优化一处逻辑 2024-08-15 19:37:06 +08:00
手瓜一十雪
2d1e87defc chore: 代码质量提高 2024-08-15 19:34:05 +08:00
手瓜一十雪
1c6f783a07 chore: 移除错误推断 2024-08-15 19:27:28 +08:00
手瓜一十雪
6aafc097d5 chore: 废弃无用代码 2024-08-15 19:26:32 +08:00
手瓜一十雪
4010f233dd chore: 移除废弃函数 2024-08-15 19:04:14 +08:00
手瓜一十雪
75f67caa1b release: v2.0.23 2024-08-15 18:46:26 +08:00
手瓜一十雪
d760ce54b7 chore: 重构文件处理 2024-08-15 18:44:29 +08:00
手瓜一十雪
956976ebd5 fix 2024-08-15 17:10:18 +08:00
手瓜一十雪
f9c2d4ca6c fix 2024-08-15 17:07:26 +08:00
手瓜一十雪
dd5cc3c38c fix 2024-08-15 17:04:34 +08:00
手瓜一十雪
daed4cc13e fix 2024-08-15 17:03:55 +08:00
手瓜一十雪
6ff614dd18 fix 2024-08-15 16:56:03 +08:00
手瓜一十雪
eb70ac4266 release 2024-08-15 16:51:31 +08:00
手瓜一十雪
a3a431adb7 fix 2024-08-15 16:45:24 +08:00
手瓜一十雪
e12c72ab98 action fix 2024-08-15 16:41:47 +08:00
手瓜一十雪
9f8549b831 build: 2.0.22 2024-08-15 16:34:50 +08:00
手瓜一十雪
b2de256f87 release: 2.0.22 2024-08-15 16:15:33 +08:00
手瓜一十雪
7f32a5cf9e build: test 2024-08-15 12:28:51 +08:00
手瓜一十雪
56f8314d29 chore: 进一步清理无用代码 2024-08-15 11:10:02 +08:00
手瓜一十雪
4ceb2a8669 release: 2.0.21 2024-08-15 09:35:38 +08:00
手瓜一十雪
c778d3b699 chore: 提高兼容性 2024-08-15 00:33:20 +08:00
手瓜一十雪
47eda9cdf2 chore: boolen值校验 2024-08-15 00:14:31 +08:00
手瓜一十雪
dcaec4d356 style: lint 2024-08-15 00:10:13 +08:00
手瓜一十雪
aee4f349c6 chore: 丢弃废弃代码 2024-08-15 00:06:41 +08:00
手瓜一十雪
daa2c39902 chore: 兼容性提高 2024-08-15 00:00:21 +08:00
手瓜一十雪
5770fc02a1 chore: extend get兼容 2024-08-14 23:56:45 +08:00
手瓜一十雪
47cafd295b chore: 清理无用代码 2024-08-14 23:46:17 +08:00
手瓜一十雪
3296f2daf8 chore: 弃用无用代码 2024-08-14 23:41:34 +08:00
手瓜一十雪
962616545c fix: 显示自身消息 2024-08-14 23:09:07 +08:00
手瓜一十雪
11ea92c078 chore: logger 2024-08-14 23:03:24 +08:00
Seijo Cecilia
1d64fa4817 Merge remote-tracking branch 'origin/main' 2024-08-14 22:04:38 +08:00
Seijo Cecilia
c46f2956c2 fix: plugin slug 2024-08-14 22:04:29 +08:00
Wesley F. Young
8f6d4298be Merge pull request #256 from canxin121/main
typo: OB11InputStatusEvent event_type
2024-08-14 21:44:21 +08:00
canxin121
3bce81326e typo: OB11InputStatusEvent event_type 2024-08-14 21:42:12 +08:00
Seijo Cecilia
2ae9f6d0fe docs: name, slug and description of plugin manifest 2024-08-14 20:11:06 +08:00
手瓜一十雪
9266828278 chore: logger 2024-08-14 19:42:22 +08:00
手瓜一十雪
a8a2ffc33e release: 2.0.20 2024-08-14 19:38:50 +08:00
手瓜一十雪
27c4543471 Merge pull request #254 from lgc2333/patch-1
fix at error
2024-08-14 19:13:48 +08:00
student_2333
50a02cb59e fix at error 2024-08-14 19:08:42 +08:00
手瓜一十雪
50579bb9e6 fix: 修复某个笨蛋写的 2024-08-14 10:46:59 +08:00
手瓜一十雪
50512ca63c Merge pull request #250 from cnxysoft/upmain
fix: 二维码登录
2024-08-14 10:42:46 +08:00
手瓜一十雪
8b3577b216 Merge pull request #249 from DiheChen/patch-1
🐛 generating QR code when quick login fails
2024-08-14 10:42:33 +08:00
Alen
7553aab932 fix: 二维码登录
修复在没找到登陆历史记录后不加载二维码的问题
2024-08-14 10:32:39 +08:00
DiheChen
5dacdcfe5e 🐛 generating QR code when quick login fails 2024-08-14 10:31:28 +08:00
手瓜一十雪
8645a412b7 Merge branch 'main' of https://github.com/NapNeko/NapCatQQ 2024-08-14 09:35:14 +08:00
手瓜一十雪
acad07a588 release: 2.0.18 2024-08-14 09:33:52 +08:00
手瓜一十雪
51bbb480bb Merge pull request #248 from cnxysoft/upmain
fix: GetCookie容错
2024-08-14 01:41:38 +08:00
Alen
f0306cd10a fix: GetCookie容错
修改 API提交不可用域名或不含skey返回时的容错
2024-08-14 01:32:32 +08:00
手瓜一十雪
25253ad9e7 fix: 异常 2024-08-14 01:22:08 +08:00
手瓜一十雪
51f2fb8e8b release: 2.0.17 2024-08-14 00:57:09 +08:00
手瓜一十雪
9e8d650cbd chore: 提高对trss兼容性 2024-08-14 00:56:42 +08:00
手瓜一十雪
d222ccfa58 fix: GetGuildProfile 2024-08-14 00:54:14 +08:00
手瓜一十雪
9a05aaa906 chore: 兼容guild api 2024-08-14 00:53:52 +08:00
手瓜一十雪
00fdce8876 style&&chore: lint 2024-08-14 00:47:50 +08:00
手瓜一十雪
29b51adf7d style&&chore: 逐步清退老版本代码 2024-08-14 00:47:31 +08:00
手瓜一十雪
8e14b39969 release: 2.0.16 2024-08-14 00:38:04 +08:00
手瓜一十雪
d9fb4d6c4d release: 2.0.15 2024-08-13 23:49:05 +08:00
手瓜一十雪
fcf2f4c5f2 Merge pull request #246 from cnxysoft/upmain
BUG修复
2024-08-13 23:48:19 +08:00
Alen
4e97501690 BUG修复
1.修改快速登陆提示使其更易辨别失败原因
2.修复API: Get_Cookie执行错误的问题
2024-08-13 23:46:21 +08:00
手瓜一十雪
b0402391fb chore: link 2024-08-13 23:11:34 +08:00
手瓜一十雪
f05a862cf9 fix 2024-08-13 22:18:24 +08:00
手瓜一十雪
3ea92d57c2 chore: 排除空消息 2024-08-13 22:11:28 +08:00
手瓜一十雪
254b85fbd8 release: v2.0.14 2024-08-13 22:02:56 +08:00
手瓜一十雪
16371c0cc4 fix: 统一消息格式 2024-08-13 21:00:22 +08:00
手瓜一十雪
2256d67e2b release: 2.0.12 2024-08-13 19:34:33 +08:00
手瓜一十雪
0af0bdede6 build: 2.0.12 test 2024-08-13 19:33:29 +08:00
手瓜一十雪
379f31b9de Merge branch 'main' of https://github.com/NapNeko/NapCatQQ 2024-08-13 16:22:24 +08:00
手瓜一十雪
771a524734 add log 2024-08-13 16:21:59 +08:00
Wesley F. Young
560e18a610 fix: no longer require admin when running after initialization 2024-08-13 16:09:15 +08:00
手瓜一十雪
147bdfab95 Revert "fix: 框架启动概率性失败"
This reverts commit 9d92270931.
2024-08-13 15:14:42 +08:00
手瓜一十雪
36b1b0f663 release: 2.0.11 2024-08-13 14:53:18 +08:00
手瓜一十雪
91511e4c3f fix: 某个笨蛋弄错的地方 2024-08-13 14:52:51 +08:00
手瓜一十雪
6a72056b25 release: 2.0.10 2024-08-13 13:58:53 +08:00
手瓜一十雪
62e0c57a50 Merge pull request #243 from cnxysoft/upmain
fix: 框架启动概率性失败
2024-08-13 13:50:08 +08:00
Alen
9d92270931 fix: 框架启动概率性失败
修复框架启动有概率失败的问题
2024-08-13 13:37:19 +08:00
手瓜一十雪
f61321d5a6 Merge branch 'main' of https://github.com/NapNeko/NapCatQQ 2024-08-13 13:09:16 +08:00
手瓜一十雪
08ab2f8649 chore: 切换为缓存实现 2024-08-13 13:09:14 +08:00
Wesley F. Young
82962c4b42 docs: copyright 2024-08-13 09:06:46 +08:00
Wesley F. Young
bd24e8a4ad docs: plugin description 2024-08-13 09:02:50 +08:00
手瓜一十雪
6224d9a292 fix: 不合格类型 2024-08-13 00:59:26 +08:00
手瓜一十雪
bbc58f3671 release v2.0.9 2024-08-13 00:53:21 +08:00
手瓜一十雪
fcd620283f fix: 处理边界条件 2024-08-13 00:48:51 +08:00
手瓜一十雪
a78def3d2d chore: 移除异常内容 2024-08-13 00:39:20 +08:00
手瓜一十雪
43e94a5db0 fix: 跳过空消息 2024-08-13 00:38:42 +08:00
手瓜一十雪
e77bcc1267 release: 2.0.8 2024-08-12 23:50:03 +08:00
手瓜一十雪
9b458958b8 Revert "try fix"
This reverts commit 35419ade29.
2024-08-12 23:03:58 +08:00
手瓜一十雪
35419ade29 try fix 2024-08-12 23:01:36 +08:00
手瓜一十雪
15bd2ee887 fix: 2.0.7 2024-08-12 20:51:17 +08:00
手瓜一十雪
9394bafa8e fix 2024-08-12 20:46:28 +08:00
手瓜一十雪
94150a0c48 Merge branch 'main' of https://github.com/NapNeko/NapCatQQ 2024-08-12 20:42:10 +08:00
手瓜一十雪
8955fdfc23 release: 2.0.7 2024-08-12 20:41:57 +08:00
手瓜一十雪
c13aa6a545 build: 快速登录修复 2024-08-12 20:34:34 +08:00
手瓜一十雪
c73b50bd4a fix 2024-08-12 20:28:25 +08:00
手瓜一十雪
0a17a38bf1 fix: renderer 2024-08-12 18:25:34 +08:00
手瓜一十雪
0f7bfe1d66 release: 2.0.6 2024-08-12 18:22:36 +08:00
手瓜一十雪
cf3f488663 release: new group 2024-08-12 18:22:06 +08:00
Wesley F. Young
5f536fdb73 Merge remote-tracking branch 'origin/main' 2024-08-12 17:53:45 +08:00
Wesley F. Young
99d0b13cce Merge remote-tracking branch 'origin/main'
# Conflicts:
#	src/onebot/index.ts
2024-08-12 17:53:16 +08:00
手瓜一十雪
b04937f012 chore: v2.0.5 2024-08-12 17:52:41 +08:00
Wesley F. Young
91c9b059cf feat: new message logging using raw msg object 2024-08-12 17:52:27 +08:00
手瓜一十雪
35cc643440 fix 2024-08-12 17:49:09 +08:00
Wesley F. Young
b23bb8c46a Merge remote-tracking branch 'origin/main' 2024-08-12 17:36:10 +08:00
手瓜一十雪
64fdf62c4b Merge branch 'main' of https://github.com/NapNeko/NapCatQQ 2024-08-12 17:17:00 +08:00
手瓜一十雪
1c8a808571 chore: 2.0.4 2024-08-12 17:16:41 +08:00
Version
d8f0295032 chore:version change 2024-08-12 09:15:06 +00:00
手瓜一十雪
d59771ac2f fix: error 2024-08-12 17:14:44 +08:00
Wesley F. Young
45df093fac docs: add correct comments on fields 2024-08-12 17:07:22 +08:00
手瓜一十雪
fba2078fc0 chore: 过滤重复消息 2024-08-12 17:04:32 +08:00
手瓜一十雪
20a37fe2de Merge pull request #239 from clansty/main
fix: revert get_friends_with_category return type
2024-08-12 16:40:41 +08:00
Clansty
8f6d26b65c fix: revert get_friends_with_category return type 2024-08-12 16:35:08 +08:00
Wesley F. Young
b58a194c8a fix: constructor signature mismatch 2024-08-12 16:15:39 +08:00
手瓜一十雪
52f1b0a0ce chore: 鸣谢 2024-08-12 14:57:12 +08:00
手瓜一十雪
c2b8fb223b chore: 鸣谢 2024-08-12 14:56:49 +08:00
手瓜一十雪
20e4eff899 chore: 2.0.1 2024-08-12 14:36:56 +08:00
手瓜一十雪
0efcca36d2 Merge branch 'main' of https://github.com/NapNeko/NapCatQQ 2024-08-12 14:20:38 +08:00
手瓜一十雪
ab417802a0 fix 2024-08-12 14:20:27 +08:00
Version
73d68cce4a chore:version change 2024-08-12 06:19:18 +00:00
手瓜一十雪
e4ac2de660 build: v2.0.0 twice 2024-08-12 13:32:12 +08:00
手瓜一十雪
8d1241808a chore: DownloadRichMedia 2024-08-12 13:29:59 +08:00
手瓜一十雪
b810040145 chore: Onebot context 2024-08-12 13:04:24 +08:00
手瓜一十雪
c01d7dae2d build: v2.0.0 twice 2024-08-12 12:45:00 +08:00
手瓜一十雪
dfca3c2483 Revert "fix: make hostname localhost when the configured host is 0.0.0.0"
This reverts commit 21fed5b25f.
2024-08-12 12:43:59 +08:00
手瓜一十雪
1bb4be086f Merge branch 'main' of https://github.com/NapNeko/NapCatQQ 2024-08-12 12:43:03 +08:00
手瓜一十雪
fd226c45f6 chore: Support 多账户 2024-08-12 12:41:59 +08:00
Wesley F. Young
21fed5b25f fix: make hostname localhost when the configured host is 0.0.0.0 2024-08-12 12:33:33 +08:00
Wesley F. Young
dde093d321 fix: hot reload 2024-08-12 12:29:40 +08:00
Wesley F. Young
b99fb247ac fix & refactor: pass actions through constructor 2024-08-12 12:08:53 +08:00
Wesley F. Young
28930fdad4 refactor: type definition for ActionMap 2024-08-12 11:35:17 +08:00
Wesley F. Young
ea4d1d3275 feat: logging for hot reload 2024-08-12 11:20:48 +08:00
Wesley F. Young
62e852d510 feat: hot reload 2024-08-12 11:16:15 +08:00
Wesley F. Young
7ddd4d6461 refactor: change method signature in legacy WebUi 2024-08-12 10:58:52 +08:00
Wesley F. Young
6b9307de2a refactor: move Ob11Adapter to index.ts 2024-08-12 10:45:55 +08:00
Wesley F. Young
234046ce10 fix: remove redundant sleep 2024-08-12 10:43:51 +08:00
手瓜一十雪
73b29cf1e2 chore: 开源许可变更 2024-08-12 10:34:05 +08:00
手瓜一十雪
4b3bf170c0 Update README.md 2024-08-12 10:33:09 +08:00
Wesley F. Young
a7fbaba2d7 docs: 语言中枢,你进化罢 2024-08-12 08:30:18 +08:00
Wesley F. Young
fc79241f3d Merge remote-tracking branch 'origin/main' 2024-08-12 08:20:39 +08:00
Wesley F. Young
a88c37ea56 fix: replace deprecated calls 2024-08-12 08:20:11 +08:00
手瓜一十雪
9b2358b7f1 build: V2.0.0 fast 2024-08-12 02:37:20 +08:00
手瓜一十雪
257135763f fix 2024-08-12 02:27:44 +08:00
手瓜一十雪
610a3499f2 Merge branch 'main' of https://github.com/NapNeko/NapCatQQ 2024-08-12 02:23:56 +08:00
手瓜一十雪
69752b8837 fix 2024-08-12 02:23:46 +08:00
Wesley F. Young
610473b57c Merge remote-tracking branch 'origin/main' 2024-08-12 02:22:50 +08:00
Wesley F. Young
1e5721d7d5 fix: sleep before loading 2024-08-12 02:22:20 +08:00
手瓜一十雪
6c2b45679a build: test 2024-08-12 02:21:37 +08:00
Wesley F. Young
6785922379 fix: auto retry of active ws 2024-08-12 02:12:18 +08:00
手瓜一十雪
4e85124aeb build: v2.0.0 nopackage 2024-08-12 01:27:24 +08:00
手瓜一十雪
6b30a03f55 build: v2.0.0 fast 2024-08-12 01:21:14 +08:00
手瓜一十雪
876894d8c6 build: v2.0.0 fast 2024-08-12 01:17:47 +08:00
手瓜一十雪
ea20d94146 build: v2.0.0 fast 2024-08-12 01:15:52 +08:00
手瓜一十雪
c7669777cb Merge branch 'main' of https://github.com/NapNeko/NapCatQQ 2024-08-12 01:15:00 +08:00
手瓜一十雪
3b43bba4a0 build: v2.0.0 fast test 2024-08-12 01:14:38 +08:00
Wesley F. Young
0ab4946bf1 Merge remote-tracking branch 'origin/main' 2024-08-12 01:13:36 +08:00
Wesley F. Young
a7fb18d5c0 chore: clean-up 2024-08-12 01:13:26 +08:00
手瓜一十雪
a0fbb0f861 chore: build 2024-08-12 01:13:03 +08:00
Wesley F. Young
e6c93ab1c0 Merge remote-tracking branch 'origin/main' 2024-08-12 01:12:36 +08:00
Wesley F. Young
7152213344 fix: inherit logging level from config 2024-08-12 01:12:27 +08:00
手瓜一十雪
a8e913cfde build: v2.0.0 test 2024-08-12 01:10:45 +08:00
手瓜一十雪
4ac074f3dd chore: build 2024-08-12 01:09:30 +08:00
Wesley F. Young
436249597d fix: use constructor to pass config name 2024-08-12 01:08:55 +08:00
手瓜一十雪
016a742d90 chore: build 2024-08-12 01:00:26 +08:00
Wesley F. Young
6d863ac29c Merge remote-tracking branch 'origin/main' 2024-08-12 00:50:23 +08:00
Wesley F. Young
ae981fe57d fix & refactor: no longer merge config data and loader; use static imports to represent config struct 2024-08-12 00:49:59 +08:00
手瓜一十雪
0c6a75b722 chore: 日志 2024-08-12 00:35:35 +08:00
手瓜一十雪
bfd9b1b7c7 fix: 异常未使用内容 2024-08-12 00:34:02 +08:00
Wesley F. Young
12f6b1ca45 chore: remove TODO since legacy WebUI has been compatible with shell 2024-08-12 00:32:10 +08:00
Wesley F. Young
04264110ee Merge remote-tracking branch 'origin/main' 2024-08-12 00:27:40 +08:00
Wesley F. Young
e4a112c329 refactor: move default config json files to inner folders 2024-08-12 00:27:14 +08:00
Wesley F. Young
ef4dee8886 refactor: make ConfigBase abstract 2024-08-12 00:26:44 +08:00
手瓜一十雪
e7ee21ca30 chore: 高版本appid 2024-08-12 00:26:37 +08:00
手瓜一十雪
23ee480c4f fix 2024-08-12 00:22:17 +08:00
手瓜一十雪
7816271302 Merge branch 'main' of https://github.com/NapNeko/NapCatQQ 2024-08-12 00:22:11 +08:00
手瓜一十雪
b57814f14a chore: log 2024-08-12 00:17:43 +08:00
Wesley F. Young
b18e86f81c fix: core config name 2024-08-12 00:15:51 +08:00
Wesley F. Young
7b1b503703 Merge remote-tracking branch 'origin/main' 2024-08-12 00:14:04 +08:00
手瓜一十雪
32d4febf10 chore: config 2024-08-12 00:12:52 +08:00
Wesley F. Young
814973af58 chore: suppress type merging error 2024-08-12 00:09:05 +08:00
手瓜一十雪
ecee642e10 chore: 日志美化 2024-08-12 00:08:49 +08:00
手瓜一十雪
9afc0f6667 chore: 鉴权和回复 2024-08-11 23:59:58 +08:00
手瓜一十雪
e9e517533a chore: 挂载逻辑 2024-08-11 23:53:12 +08:00
手瓜一十雪
4a531ccea1 fix 2024-08-11 23:39:25 +08:00
手瓜一十雪
fb8e0595c2 fix 2024-08-11 23:37:31 +08:00
手瓜一十雪
d748d6e400 fix: error 2024-08-11 23:34:59 +08:00
手瓜一十雪
6b99fa1f24 Merge branch 'main' of https://github.com/NapNeko/NapCatQQ 2024-08-11 23:27:31 +08:00
手瓜一十雪
ca5abc635c chore: 二维码登录与刷新1 2024-08-11 23:27:28 +08:00
手瓜一十雪
35e75be0d0 chore: 快速登录 2024-08-11 23:24:20 +08:00
Wesley F. Young
cf401a659d Merge remote-tracking branch 'origin/main'
# Conflicts:
#	src/onebot/network/active-websocket.ts
2024-08-11 23:15:37 +08:00
Wesley F. Young
bd56968efb Merge remote-tracking branch 'origin/main'
# Conflicts:
#	src/onebot/network/active-websocket.ts
2024-08-11 23:15:13 +08:00
手瓜一十雪
a78bc686cd chore: noThrowError 2024-08-11 23:13:47 +08:00
Wesley F. Young
ad8c962c25 Merge remote-tracking branch 'origin/main'
# Conflicts:
#	src/onebot/network/active-websocket.ts
2024-08-11 23:13:15 +08:00
手瓜一十雪
be91976498 chore: 初步完成 2024-08-11 23:10:56 +08:00
Wesley F. Young
57821b839e feat: implement ActiveHttp.open / close 2024-08-11 23:09:59 +08:00
手瓜一十雪
ad334ed09f Merge branch 'main' of https://github.com/NapNeko/NapCatQQ 2024-08-11 23:09:21 +08:00
Wesley F. Young
a955937e02 feat: complete active websocket 2024-08-11 23:09:00 +08:00
Wesley F. Young
3c42cc17c8 chore: clean-up 2024-08-11 23:08:41 +08:00
手瓜一十雪
deeab036d3 chore: token 2024-08-11 23:05:42 +08:00
Wesley F. Young
a1badcd9a1 Merge remote-tracking branch 'origin/main' 2024-08-11 22:54:40 +08:00
Wesley F. Young
52762438c6 chore: fix indentation 2024-08-11 22:54:24 +08:00
手瓜一十雪
3294079b72 chore: 去除无用链接 2024-08-11 22:50:47 +08:00
Wesley F. Young
1c6bdf20b6 Merge remote-tracking branch 'origin/main'
# Conflicts:
#	.env.production
#	README.md
#	package.json
#	src/common/utils/QQBasicInfo.ts
#	src/core/src/apis/group.ts
#	src/core/src/apis/msg.ts
#	src/core/src/data.ts
#	src/core/src/services/NodeIKernelMsgService.ts
#	src/onebot/action/types.ts
#	src/onebot11/action/extends/CreateCollection.ts
#	src/onebot11/action/extends/Debug.ts
#	src/onebot11/action/extends/FetchEmojioLike.ts
#	src/onebot11/action/extends/GetCollectionList.ts
#	src/onebot11/action/extends/GetRobotUinRange.ts
#	src/onebot11/action/extends/OCRImage.ts
#	src/onebot11/action/extends/SetGroupHeader.ts
#	src/onebot11/action/extends/SetLongNick.ts
#	src/onebot11/action/extends/SetOnlineStatus.ts
#	src/onebot11/action/extends/SetQQAvatar.ts
#	src/onebot11/action/extends/TestApi01.ts
#	src/onebot11/action/extends/TranslateEnWordToZn.ts
#	src/onebot11/action/file/DelGroupFile.ts
#	src/onebot11/action/file/GetFile.ts
#	src/onebot11/action/go-cqhttp/GetForwardMsg.ts
#	src/onebot11/action/go-cqhttp/GetFriendMsgHistory.ts
#	src/onebot11/action/go-cqhttp/GetGroupHonorInfo.ts
#	src/onebot11/action/go-cqhttp/GetStrangerInfo.ts
#	src/onebot11/action/go-cqhttp/SendGroupNotice.ts
#	src/onebot11/action/go-cqhttp/UploadGroupFile.ts
#	src/onebot11/action/go-cqhttp/UploadPrivareFile.ts
#	src/onebot11/action/group/GetGroupEssence.ts
#	src/onebot11/action/group/GetGroupList.ts
#	src/onebot11/action/group/SetEssenceMsg.ts
#	src/onebot11/action/group/SetGroupAddRequest.ts
#	src/onebot11/action/group/SetGroupBan.ts
#	src/onebot11/action/group/SetGroupKick.ts
#	src/onebot11/action/index.ts
#	src/onebot11/action/msg/DeleteMsg.ts
#	src/onebot11/action/msg/ForwardSingleMsg.ts
#	src/onebot11/action/msg/GetMsg.ts
#	src/onebot11/action/msg/SendMsg/create-send-elements.ts
#	src/onebot11/action/msg/SendMsg/handle-forward-node.ts
#	src/onebot11/action/msg/SendMsg/index.ts
#	src/onebot11/action/msg/SendPrivateMsg.ts
#	src/onebot11/action/msg/SetMsgEmojiLike.ts
#	src/onebot11/action/system/GetLoginInfo.ts
#	src/onebot11/action/user/GetFriendList.ts
#	src/onebot11/action/user/SendLike.ts
#	src/onebot11/config.ts
#	src/onebot11/constructor.ts
#	src/onebot11/event/notice/OB11GroupAdminNoticeEvent.ts
#	src/onebot11/event/notice/OB11InputStatusEvent.ts
#	src/onebot11/log.ts
#	src/onebot11/main.ts
#	src/onebot11/version.ts
#	src/webui/ui/NapCat.ts
2024-08-11 22:37:16 +08:00
手瓜一十雪
fac00be995 chore: 移除社交平台 2024-08-11 22:10:39 +08:00
Wesley F. Young
e7e8e99946 refactor: inline getApiContext() 2024-08-11 19:10:42 +08:00
Wesley F. Young
9f9749548a fix: type 2024-08-11 18:41:48 +08:00
Wesley F. Young
db1ac85acf fix: add missing catches 2024-08-11 18:39:23 +08:00
Wesley F. Young
d5eaeb429a feat: initGroupListener 2024-08-11 18:33:23 +08:00
Wesley F. Young
4e7595d8d1 feat: initBuddyListener 2024-08-11 18:11:27 +08:00
手瓜一十雪
f25fdcdc3d fix: webui this丢失 2024-08-11 18:04:31 +08:00
手瓜一十雪
7a4de75e07 chore: Adapter 2024-08-11 17:57:52 +08:00
手瓜一十雪
545b57a57d Merge branch 'main' of https://github.com/NapNeko/NapCatQQ 2024-08-11 17:53:25 +08:00
手瓜一十雪
32c3aa7979 fix: rm log error 2024-08-11 17:53:15 +08:00
Wesley F. Young
013f703241 Merge remote-tracking branch 'origin/main' 2024-08-11 17:52:18 +08:00
Wesley F. Young
c463ad5fd6 refactor: signature of internal methods 2024-08-11 17:44:24 +08:00
手瓜一十雪
412b8473fe Merge branch 'main' of https://github.com/NapNeko/NapCatQQ 2024-08-11 17:44:02 +08:00
手瓜一十雪
02df2132b4 chore: build 2024-08-11 17:43:59 +08:00
Wesley F. Young
a964d5c93f feat: onMsgInfoListUpdate 2024-08-11 17:38:31 +08:00
Wesley F. Young
eafc32a915 fix: remove redundant async calls 2024-08-11 17:24:29 +08:00
手瓜一十雪
df4b84b4b9 补全AT Type 2024-08-11 16:55:08 +08:00
手瓜一十雪
6e094eb4fc chore: 缓存维护 2024-08-11 16:18:34 +08:00
手瓜一十雪
9e7d7bcb4c chore: 防止炸了 2024-08-11 16:03:58 +08:00
手瓜一十雪
63b3cc8c02 chore: 原始脚本 2024-08-11 15:45:49 +08:00
手瓜一十雪
7a88786685 chore: 临时会话 2024-08-11 15:43:13 +08:00
手瓜一十雪
427889f8ca chore: 类型补全 2024-08-11 15:34:52 +08:00
手瓜一十雪
82c9c28439 chore: 开源声明 2024-08-11 14:21:04 +08:00
手瓜一十雪
c84c1f2e96 chore: 移除废弃日志 2024-08-11 13:55:46 +08:00
手瓜一十雪
a3ee8672ed chore: V2 2024-08-11 13:53:45 +08:00
手瓜一十雪
4cfde09016 chore: config保存 2024-08-11 13:52:55 +08:00
手瓜一十雪
0b8dcbebe9 chore: webui 2024-08-11 13:48:26 +08:00
手瓜一十雪
aa12506221 re: old webui 2024-08-11 13:10:31 +08:00
手瓜一十雪
39ed9dea01 chore: 消息post 2024-08-11 12:18:13 +08:00
手瓜一十雪
6f095470ad chore: NETWORK 2024-08-11 11:12:23 +08:00
手瓜一十雪
2a5d2cc146 chore: 鉴权认证优化 2024-08-11 11:07:11 +08:00
手瓜一十雪
b5e8218551 chore: 正向ws测试完成 2024-08-11 10:29:21 +08:00
手瓜一十雪
062cc307fb chore: 正向ws鉴权 2024-08-11 10:20:17 +08:00
手瓜一十雪
e99ff1be35 chore: 心跳 2024-08-11 10:12:53 +08:00
手瓜一十雪
404a213896 chore: 正向WS实现 2024-08-11 09:50:09 +08:00
手瓜一十雪
0a07f16ef6 chore: Server Create 2024-08-11 09:27:47 +08:00
手瓜一十雪
f9bf8f9901 fix 2024-08-11 00:54:26 +08:00
手瓜一十雪
b6ae67bf3e Merge branch 'v2' of https://github.com/NapNeko/NapCatQQ into v2 2024-08-11 00:53:23 +08:00
手瓜一十雪
191ce0798f chore: fix 2024-08-11 00:53:15 +08:00
Wesley F. Young
40362590c8 Merge remote-tracking branch 'origin/v2' into v2
# Conflicts:
#	src/onebot/action/extends/GetFriendWithCategory.ts
2024-08-11 00:46:17 +08:00
Wesley F. Young
87f6dc7c0b fix: reference problems 2024-08-11 00:45:47 +08:00
手瓜一十雪
2f2c1f263a chore:fix 2024-08-11 00:44:37 +08:00
手瓜一十雪
8841cbb3d0 Merge branch 'v2' of https://github.com/NapNeko/NapCatQQ into v2 2024-08-11 00:35:49 +08:00
手瓜一十雪
a2e20a8092 chore: HttpAdapter 2024-08-11 00:35:39 +08:00
Wesley F. Young
6a7c7a0ab5 fix: adapt to breaking changes in event constructor signature 2024-08-11 00:33:49 +08:00
手瓜一十雪
44a8c8e35d chore: network 初步完成 2024-08-11 00:24:00 +08:00
手瓜一十雪
1cbfccc4eb Merge branch 'v2' of https://github.com/NapNeko/NapCatQQ into v2 2024-08-11 00:19:38 +08:00
手瓜一十雪
e12c0b5536 chore: active-websocket 2024-08-11 00:19:28 +08:00
Wesley F. Young
b7a8781308 feat: onInputStatusPush & onRecvMsg 2024-08-11 00:16:26 +08:00
手瓜一十雪
73a8fcd35b chore: 初步完成network 2024-08-11 00:11:51 +08:00
手瓜一十雪
a2ad39f78d chore: websocket 2024-08-11 00:08:52 +08:00
Wesley F. Young
832635d6f5 Merge remote-tracking branch 'origin/v2' into v2 2024-08-11 00:00:58 +08:00
手瓜一十雪
dacb56bc20 Merge branch 'v2' of https://github.com/NapNeko/NapCatQQ into v2 2024-08-11 00:00:42 +08:00
Wesley F. Young
e5a9821027 fix: logMessage & method signature 2024-08-11 00:00:16 +08:00
Wesley F. Young
bbe666eb73 update: EmitEventContent = Message + Event 2024-08-10 23:54:20 +08:00
手瓜一十雪
000cb3d80c chore: Basic Info 2024-08-10 23:53:16 +08:00
Wesley F. Young
40f85dbf5f Revert "feat: logMessage for LogWrapper"
This reverts commit d6646ebadf.
2024-08-10 23:52:42 +08:00
Wesley F. Young
d6646ebadf feat: logMessage for LogWrapper 2024-08-10 23:31:23 +08:00
手瓜一十雪
e02bddc78f fix 2024-08-10 22:40:40 +08:00
手瓜一十雪
08e679184b Merge branch 'v2' of https://github.com/NapNeko/NapCatQQ into v2 2024-08-10 22:35:41 +08:00
手瓜一十雪
5c877e894b chore: GroupCache 2024-08-10 22:35:29 +08:00
Wesley F. Young
5918f03cb1 Merge remote-tracking branch 'origin/v2' into v2 2024-08-10 22:27:05 +08:00
手瓜一十雪
78263d716c Merge branch 'v2' of https://github.com/NapNeko/NapCatQQ into v2 2024-08-10 22:25:38 +08:00
手瓜一十雪
15f4841328 chore: config 2024-08-10 22:25:25 +08:00
Wesley F. Young
ae5d50141b fix: method signature of _handle 2024-08-10 22:22:52 +08:00
Wesley F. Young
8839563ff8 Merge remote-tracking branch 'origin/v2' into v2 2024-08-10 22:12:06 +08:00
Wesley F. Young
6d954b2d5d refactor: add core into all event constructors 2024-08-10 22:11:57 +08:00
手瓜一十雪
6e125f15a4 chore: config 2024-08-10 21:58:37 +08:00
手瓜一十雪
e344921a06 chore: config 2024-08-10 21:56:55 +08:00
手瓜一十雪
4cbaf0dc70 fix 2024-08-10 21:53:08 +08:00
手瓜一十雪
ef7d2f4a82 Merge branch 'v2' of https://github.com/NapNeko/NapCatQQ into v2 2024-08-10 21:48:08 +08:00
手瓜一十雪
5f7d998b0b chore: fix 2024-08-10 21:47:58 +08:00
Wesley F. Young
2c14281168 refactor: specify the arg type of onInputStatusPush 2024-08-10 20:56:46 +08:00
Wesley F. Young
9feab4bc79 chore: make BaseAction abstract 2024-08-10 20:53:29 +08:00
Wesley F. Young
63237bc112 refactor: rename postEvent -> emitEvent to prevent ambiguity 2024-08-10 20:43:50 +08:00
手瓜一十雪
99f4752c89 Merge branch 'v2' of https://github.com/NapNeko/NapCatQQ into v2 2024-08-10 20:28:52 +08:00
手瓜一十雪
ef1ed5aa8b chore: 移除错误日志 2024-08-10 20:28:43 +08:00
Wesley F. Young
1258270ac4 Merge remote-tracking branch 'origin/v2' into v2
# Conflicts:
#	src/core/apis/group.ts
2024-08-10 20:26:42 +08:00
Wesley F. Young
bb7a2f5f6c feat: get group & group member by uin 2024-08-10 20:25:32 +08:00
手瓜一十雪
c865d32d95 chore: add 2024-08-10 20:23:17 +08:00
Wesley F. Young
87c3b24488 chore: run a full eslint 2024-08-10 19:58:31 +08:00
Wesley F. Young
5a5257294b optimize: an ActiveHttpAdapter does not need to register actions 2024-08-10 19:33:34 +08:00
手瓜一十雪
a9ca951854 chore 2024-08-10 18:45:46 +08:00
手瓜一十雪
2a9353ee70 chore: 标准化 2024-08-10 18:18:04 +08:00
手瓜一十雪
18e134b92a chore: 核心缓存位置设计完成 2024-08-10 18:06:40 +08:00
手瓜一十雪
6c87e15a52 fix: 创建OB上下文 2024-08-10 18:03:21 +08:00
手瓜一十雪
a710821c35 chore: log 2024-08-10 17:53:48 +08:00
手瓜一十雪
de4aeedce5 fix: typo 2024-08-10 17:51:25 +08:00
手瓜一十雪
1f71a01453 chore: 调整出包方式 2024-08-10 17:50:23 +08:00
手瓜一十雪
6371d79d33 chore: 针对的并非框架 2024-08-10 17:45:17 +08:00
手瓜一十雪
80d2218aa6 chore: PostEvent 2024-08-10 17:33:17 +08:00
手瓜一十雪
bc636f109c chore: 去除无效代码 2024-08-10 17:31:12 +08:00
手瓜一十雪
76d58af4d8 chore: 小调整 2024-08-10 17:29:24 +08:00
手瓜一十雪
84e5417a8c chore: 管理网络适配器 2024-08-10 17:11:04 +08:00
手瓜一十雪
89188958ec chore: token 2024-08-10 17:07:23 +08:00
手瓜一十雪
b5d24d751d chore: HeartBeat 2024-08-10 16:58:40 +08:00
手瓜一十雪
3269061db4 Revert "chore: network context"
This reverts commit 9f576f43cc.
2024-08-10 16:35:58 +08:00
手瓜一十雪
9f576f43cc chore: network context 2024-08-10 16:28:26 +08:00
手瓜一十雪
505c6e0e0e chore 2024-08-10 16:21:56 +08:00
手瓜一十雪
9936b49ee0 fix: typo 2024-08-10 16:20:44 +08:00
手瓜一十雪
707bc765b9 chore: readme 2024-08-10 16:19:57 +08:00
手瓜一十雪
8780c987ea chore: HttpServer 2024-08-10 16:09:26 +08:00
手瓜一十雪
7aa01f786d feat; 初步完成 2024-08-10 15:52:05 +08:00
Wesley F. Young
340e94d54e feat: a framework of active (reverse, 反向) websocket 2024-08-10 15:13:26 +08:00
手瓜一十雪
509b123064 Merge branch 'v2' of https://github.com/NapNeko/NapCatQQ into v2 2024-08-10 14:42:09 +08:00
Wesley F. Young
8060b1c753 feat: a framework of passive (正向) websocket 2024-08-10 14:40:51 +08:00
Wesley F. Young
33ef3e7d59 update: interface IOB11NetworkAdapter 2024-08-10 14:00:56 +08:00
Wesley F. Young
704e5f7134 fix: typo of Emojio -> Emoji 2024-08-10 13:53:24 +08:00
手瓜一十雪
91bc3ab525 😊史山移出历史长河 2024-08-10 13:52:06 +08:00
Wesley F. Young
2e6bded9d0 fix: typo of Emojio -> Emoji 2024-08-10 13:51:49 +08:00
Wesley F. Young
c6e980ed96 begin refactoring ob11/server 2024-08-10 13:48:27 +08:00
手瓜一十雪
32a932ad5c chore: fix 2024-08-10 13:34:33 +08:00
手瓜一十雪
f6d2bd04e9 chore: 移除延迟 2024-08-10 13:32:38 +08:00
Wesley F. Young
b0266b470f fix: add pathWrapper into NC_LL constructor 2024-08-10 11:56:47 +08:00
Wesley F. Young
0ed969fa3f fix: hook session init when launching as LL plugin 2024-08-10 11:56:23 +08:00
Wesley F. Young
90a7b5e0d3 fix: remove debug info 2024-08-10 08:43:22 +08:00
Wesley F. Young
c5bf656fe7 update: add pathWrapper into instance context 2024-08-09 23:21:17 +08:00
Wesley F. Young
6bce4533a3 fix: login 2024-08-09 23:19:47 +08:00
手瓜一十雪
8a8aa0016e fix 2024-08-09 22:48:13 +08:00
Wesley F. Young
7e4ebd330c Merge remote-tracking branch 'origin/v2' into v2 2024-08-09 22:37:21 +08:00
Wesley F. Young
c304845117 feat: logging in through shell 2024-08-09 22:36:44 +08:00
手瓜一十雪
ee85f3e824 chore: defaultConfig 2024-08-09 22:29:51 +08:00
手瓜一十雪
3f6f1dcd78 chore: config 2024-08-09 22:29:01 +08:00
手瓜一十雪
c271a4b2cb fix 2024-08-09 22:14:45 +08:00
手瓜一十雪
fa07dfb720 feat: 注入OneBot上下文 2024-08-09 21:54:14 +08:00
手瓜一十雪
6f6b258f22 release: 1.8.6 2024-08-09 21:41:56 +08:00
手瓜一十雪
717b246cb6 release: 1.8.5 2024-08-09 21:38:36 +08:00
Wesley F. Young
5990e0c2eb chore: run eslint --fix in onebot module 2024-08-09 20:35:03 +08:00
手瓜一十雪
827df80ec8 fix: error 2024-08-09 20:34:31 +08:00
手瓜一十雪
7117fae2b2 chore: wait for v2 2024-08-09 19:41:40 +08:00
手瓜一十雪
15e9462140 LICENSE: MPL2 To BSD 2024-08-09 19:40:35 +08:00
手瓜一十雪
714f8327ea fix 2024-08-09 19:13:26 +08:00
手瓜一十雪
fedb77e304 chore: fix 2024-08-09 19:04:14 +08:00
手瓜一十雪
06d2884a88 chore config 2024-08-09 18:44:14 +08:00
手瓜一十雪
b50556802c chore: action基本就绪 2024-08-09 18:28:05 +08:00
手瓜一十雪
b18dfdb9ba chore: sendMsg 2024-08-09 18:23:37 +08:00
手瓜一十雪
173f83808e chore Api 2024-08-09 18:17:45 +08:00
手瓜一十雪
fbe2d78331 fix: 大部分异常 2024-08-09 18:06:11 +08:00
手瓜一十雪
e5fd9c6366 chore: sendMsg 2024-08-09 17:53:06 +08:00
手瓜一十雪
3623b991ff CHORE 2024-08-09 17:49:51 +08:00
手瓜一十雪
2cabd7879c chore: GroupApi Finish 2024-08-09 17:37:17 +08:00
手瓜一十雪
a1a378d6f5 chore: gocq接口完成 2024-08-09 17:27:44 +08:00
手瓜一十雪
b016268fdb refactor: 初步fileApi就绪 2024-08-09 17:12:57 +08:00
手瓜一十雪
dfb31b78d9 chore: 所有扩展接口就绪 2024-08-09 17:01:29 +08:00
手瓜一十雪
4eed603d36 chore: context 2024-08-09 16:52:03 +08:00
手瓜一十雪
cdc10d6c4b chore: server 2024-08-09 16:49:40 +08:00
手瓜一十雪
cb03501eff chore: http 2024-08-09 16:40:54 +08:00
手瓜一十雪
c2e28ab5a6 chore: version Api 2024-08-09 16:35:28 +08:00
手瓜一十雪
24a166cb94 chore 2024-08-09 16:30:54 +08:00
手瓜一十雪
1d3ac0c9b3 chore: actionMap 2024-08-09 16:28:38 +08:00
手瓜一十雪
ff29b62398 chore: OBAPI 2024-08-09 16:22:35 +08:00
手瓜一十雪
d9e016db8b chore:action 2024-08-09 16:15:17 +08:00
手瓜一十雪
6cfd50a7b8 Merge branch 'v2' of https://github.com/NapNeko/NapCatQQ into v2 2024-08-09 16:04:38 +08:00
手瓜一十雪
6605d3812a chore: obApi 2024-08-09 16:04:28 +08:00
Wesley F. Young
be71abe580 chore: fix indentation in files 2024-08-09 15:58:40 +08:00
手瓜一十雪
518ff48e97 chore: Action 2024-08-09 15:50:17 +08:00
手瓜一十雪
8a4add257f chore: OneBotApi 2024-08-09 15:44:45 +08:00
手瓜一十雪
7842cd0bc0 chore: NTApi Finish 2024-08-09 15:35:00 +08:00
手瓜一十雪
ffe480ad44 fix: 小问题 2024-08-09 15:31:41 +08:00
手瓜一十雪
e4d3f95257 Merge branch 'v2' of https://github.com/NapNeko/NapCatQQ into v2 2024-08-09 15:30:33 +08:00
手瓜一十雪
245eabe85f chore: 屏蔽部分代码 2024-08-09 15:30:04 +08:00
Wesley F. Young
22e7eb1ffc refactor: fix typo and add comments in WebHonorType 2024-08-09 15:22:34 +08:00
Wesley F. Young
d663a58d65 Merge remote-tracking branch 'origin/v2' into v2 2024-08-09 15:14:31 +08:00
Wesley F. Young
fa5d5f1bcc refactor: shared logic in webapi.ts 2024-08-09 15:14:11 +08:00
手瓜一十雪
7178095aef Merge branch 'v2' of https://github.com/NapNeko/NapCatQQ into v2 2024-08-09 14:39:27 +08:00
手瓜一十雪
4704faa011 chore: file utils 2024-08-09 14:39:06 +08:00
Wesley F. Young
0f77d9df1f Merge remote-tracking branch 'origin/v2' into v2 2024-08-09 14:32:43 +08:00
Wesley F. Young
e02c3fca8b fix: typo of lastest -> latest 2024-08-09 14:32:32 +08:00
手瓜一十雪
abe0838a63 fix: signApi 2024-08-09 14:24:48 +08:00
手瓜一十雪
6583e3d0c9 fix: system api 2024-08-09 14:14:45 +08:00
手瓜一十雪
2093d68bfb Merge branch 'v2' of https://github.com/NapNeko/NapCatQQ into v2 2024-08-09 14:12:11 +08:00
手瓜一十雪
d3a55d50c0 Merge branch 'v2' of https://github.com/NapNeko/NapCatQQ into v2 2024-08-09 14:11:59 +08:00
Wesley F. Young
471733b243 chore: add .prettierrc.json 2024-08-09 14:11:24 +08:00
手瓜一十雪
47fa717bce fix: webapi 2024-08-09 14:10:10 +08:00
Wesley F. Young
cfc68e70b6 chore: fix indentation and semi in core 2024-08-09 14:09:42 +08:00
手瓜一十雪
bd9ee62118 fix rkeyManager 2024-08-09 14:05:47 +08:00
手瓜一十雪
aaa874b099 chore: api 2024-08-09 13:58:26 +08:00
手瓜一十雪
958709faf2 chore: ntapi 2024-08-09 13:52:15 +08:00
手瓜一十雪
52ab93013c chore: NTAPI 2024-08-09 13:42:04 +08:00
手瓜一十雪
9203fa3df2 Merge pull request #230 from canxin121/main
Fix typo in eventType field name
2024-08-09 13:35:45 +08:00
手瓜一十雪
9ef3edabce chore: NTAPI 2024-08-09 13:33:58 +08:00
canxin121
c771d75a00 Fix typo in eventType field name 2024-08-09 13:12:11 +08:00
手瓜一十雪
1db27ab0e3 chore: 改成实例 2024-08-09 13:01:02 +08:00
手瓜一十雪
cd45f7051c chore: NTAPI 2024-08-09 12:58:00 +08:00
Wesley F. Young
588ea7978e rollback: use legacy event wrapper 2024-08-09 11:34:18 +08:00
手瓜一十雪
946f12cf6a remove: core不要干多余的事情 2024-08-09 11:11:45 +08:00
Wesley F. Young
7e49bfa984 refactor: make selfInfo a 'runtime info' 2024-08-09 11:03:25 +08:00
手瓜一十雪
7b10d75aeb chore: 1.8.4 2024-08-09 10:48:13 +08:00
手瓜一十雪
34e4963ccd Merge branch 'v2' of https://github.com/NapNeko/NapCatQQ into v2 2024-08-09 10:44:59 +08:00
手瓜一十雪
9fc9ed805c chore: type 2024-08-09 10:42:03 +08:00
Wesley F. Young
db4c5bc3a3 chore: fix indentation and semi in files 2024-08-09 10:37:09 +08:00
Wesley F. Young
024faa2561 Merge branch 'main' into v2 2024-08-08 23:20:15 +08:00
Wesley F. Young
b46459de5f docs: 给 v2 引流 2024-08-08 23:15:28 +08:00
Wesley F. Young
ac7f025223 docs: 进化的语言中枢 2024-08-08 23:13:21 +08:00
Wesley F. Young
e4343650c6 fix: remove debug log 2024-08-08 22:12:07 +08:00
Wesley F. Young
80c5259b05 feat: shell 2024-08-08 22:10:55 +08:00
Wesley F. Young
004a65d933 Merge remote-tracking branch 'origin/v2' into v2 2024-08-08 21:46:14 +08:00
手瓜一十雪
38289918c2 chore: requests 2024-08-08 21:42:34 +08:00
Wesley F. Young
0e46f9f213 chore: make indent 4 in .editorconfig 2024-08-08 21:40:09 +08:00
手瓜一十雪
be03b973e5 chore: docs 2024-08-08 21:34:25 +08:00
手瓜一十雪
a0bf2b3d3d chore: remove sleep 2024-08-08 21:31:00 +08:00
手瓜一十雪
755ab36e83 chore: loadfinish 2024-08-08 21:23:14 +08:00
手瓜一十雪
30975f7360 chore: onRecv 2024-08-08 21:19:42 +08:00
手瓜一十雪
828307fc52 Merge branch 'v2' of https://github.com/NapNeko/NapCatQQ into v2 2024-08-08 21:12:33 +08:00
手瓜一十雪
e79ca4fa4c chore: build
Co-Authored-By: Wesley F. Young <25684570+Wesley-Young@users.noreply.github.com>
2024-08-08 21:12:23 +08:00
Wesley F. Young
43288eb5c0 chore: make indent 4 in eslint 2024-08-08 21:06:27 +08:00
手瓜一十雪
9518595e48 chore: build
Co-Authored-By: Wesley F. Young <25684570+Wesley-Young@users.noreply.github.com>
2024-08-08 20:59:18 +08:00
手瓜一十雪
3c4cd3743f build: liteloader
Co-Authored-By: Wesley F. Young <25684570+Wesley-Young@users.noreply.github.com>
2024-08-08 20:57:10 +08:00
手瓜一十雪
b39cbffe14 chore: build
Co-Authored-By: Wesley F. Young <25684570+Wesley-Young@users.noreply.github.com>
2024-08-08 20:43:36 +08:00
手瓜一十雪
f588d3f35b chore: build
Co-Authored-By: Wesley F. Young <25684570+Wesley-Young@users.noreply.github.com>
2024-08-08 20:42:09 +08:00
手瓜一十雪
54b06872eb chore: old webui
Co-Authored-By: Wesley F. Young <25684570+Wesley-Young@users.noreply.github.com>
2024-08-08 20:21:44 +08:00
手瓜一十雪
0786c608a4 chore: vite build
Co-Authored-By: Wesley F. Young <25684570+Wesley-Young@users.noreply.github.com>
2024-08-08 19:26:27 +08:00
手瓜一十雪
c70b2eaa30 chore: 进度提交 2024-08-08 17:51:15 +08:00
手瓜一十雪
c417a95e1f chore: 改造为后初始化 2024-08-08 15:13:58 +08:00
手瓜一十雪
5ae9be0291 chore: 补全基础框架 2024-08-08 14:36:59 +08:00
手瓜一十雪
e2a6e3ea58 chore: 类型补全 2024-08-08 14:29:29 +08:00
手瓜一十雪
8fd5fa185b chore: Wrapper声明 2024-08-08 14:21:34 +08:00
手瓜一十雪
66707661e9 chore: refactor 2024-08-08 12:59:21 +08:00
手瓜一十雪
4e18ec5951 refactor: init 2024-08-08 12:44:10 +08:00
手瓜一十雪
05b77b5042 build: test 2024-08-08 11:11:38 +08:00
手瓜一十雪
d6cfe11d97 build: test 2024-08-08 10:59:49 +08:00
手瓜一十雪
dd4d59e4e7 build: test 2024-08-08 10:50:36 +08:00
手瓜一十雪
7cb8626e16 build:test 2024-08-08 10:29:53 +08:00
手瓜一十雪
89b5202adb Merge pull request #224 from NapNeko/newold
build: test
2024-08-08 10:28:19 +08:00
手瓜一十雪
40ae0c1449 build: test 2024-08-08 10:26:49 +08:00
手瓜一十雪
81a8115c56 release:1.8.3 2024-08-07 01:32:48 +08:00
手瓜一十雪
0ddd26bd51 release: 1.8.3 2024-08-07 01:28:22 +08:00
手瓜一十雪
69e2133a27 chore: BootWay05Script 2024-08-07 01:25:02 +08:00
手瓜一十雪
b04f85949b docs: todo 2024-08-06 23:42:21 +08:00
手瓜一十雪
667dba01ae build: OB11InputStatusEvent 2024-08-06 23:39:22 +08:00
手瓜一十雪
85a8cef628 chore: 移除部分无用内容 2024-08-06 21:14:11 +08:00
手瓜一十雪
3099acfd00 DOCS:TODO 2024-08-06 20:46:17 +08:00
手瓜一十雪
b00fe0b5f8 docs: 标记TODO和兜底接口 2024-08-06 20:38:49 +08:00
手瓜一十雪
9a64b8bdb6 release:1.8.2 2024-08-06 20:10:01 +08:00
手瓜一十雪
c01e493bd2 feat: 多层合并完成 2024-08-06 18:55:36 +08:00
手瓜一十雪
890236af23 chore: 去除风控延迟 2024-08-06 18:01:36 +08:00
手瓜一十雪
ea678d805d chore: 多层合并转发初步可用 2024-08-06 17:58:48 +08:00
手瓜一十雪
29699418ff fix: 对漏掉的部分加入 2024-08-06 17:02:26 +08:00
手瓜一十雪
87bb36c39f refactor: SendMessageContext 2024-08-06 17:00:27 +08:00
手瓜一十雪
7cb85ed73c fix: 再次优化nodeid 2024-08-06 16:43:06 +08:00
手瓜一十雪
c647771a6d fix: 合并转发Node节点异常问题 2024-08-06 16:27:06 +08:00
手瓜一十雪
6c3d737219 fix: 合并reply 2024-08-06 16:00:25 +08:00
手瓜一十雪
a1f38fed7a release: v1.8.1 2024-08-06 15:55:59 +08:00
手瓜一十雪
c36bb77286 feat: 合并消息初步解析完成 2024-08-06 14:48:20 +08:00
手瓜一十雪
1a1acdc3c9 feat: 转发着色 2024-08-06 11:40:36 +08:00
手瓜一十雪
ef8e60c405 build: 180test 2024-08-06 11:31:48 +08:00
手瓜一十雪
31c1cc47bf build: 180 2024-08-06 11:30:35 +08:00
手瓜一十雪
351fed7359 fix #102 2024-08-06 11:30:12 +08:00
手瓜一十雪
f49e7cbe57 chore: log 2024-08-06 11:15:44 +08:00
手瓜一十雪
7da8ea5e99 style:lint 2024-08-06 11:12:25 +08:00
手瓜一十雪
8fa6a12a7c build: 181test 2024-08-06 11:07:37 +08:00
手瓜一十雪
1070278eaf release: 1.8.0 2024-08-06 09:13:03 +08:00
手瓜一十雪
16b1a6b153 release: 1.7.9 2024-08-05 21:04:49 +08:00
手瓜一十雪
096a7534e0 build: 1.7.9-test 2024-08-05 20:36:08 +08:00
手瓜一十雪
b0897187d2 feat: fetch_emoji_like 2024-08-05 20:33:24 +08:00
手瓜一十雪
885d94882d remove: 😢意外加入的文件 2024-08-05 18:40:39 +08:00
手瓜一十雪
11a3341e13 feat: GetRecentContact 私有标准 2024-08-05 18:39:02 +08:00
手瓜一十雪
231890f78a refactor: msgRandom控制 2024-08-05 17:42:07 +08:00
手瓜一十雪
a272feda6a release: 1.7.8 2024-08-05 13:52:34 +08:00
手瓜一十雪
bc936a0ca7 build: 178发言时间与加入时间完全兜底 2024-08-05 13:45:07 +08:00
手瓜一十雪
28030c2d13 chore: test 2024-08-05 13:01:26 +08:00
手瓜一十雪
7fe9176286 feat: richmsg failed 2024-08-05 12:45:06 +08:00
手瓜一十雪
1105f9b8d6 docs: 补充 2024-08-05 01:11:11 +08:00
手瓜一十雪
e4653defa8 LICENSE: break 2024-08-05 00:48:41 +08:00
Alen
1c62a1e839 Merge pull request #208 from cnxysoft/main
BUG修复
2024-08-05 00:20:17 +08:00
Alen
3a3bbfe201 BUG修复
修复(群聊/私聊)转发合并消息错误的问题
2024-08-05 00:18:42 +08:00
手瓜一十雪
1c38833998 fix: 177 2024-08-04 23:26:51 +08:00
手瓜一十雪
38894177ee fix: get_stranger_info 2024-08-04 22:21:24 +08:00
手瓜一十雪
dce8416942 fix: 177 2024-08-04 21:40:44 +08:00
手瓜一十雪
14219e9b42 release: v1.7.7 2024-08-04 21:23:55 +08:00
手瓜一十雪
7b459e7502 refactor: get_group_member_list 2024-08-04 21:21:36 +08:00
手瓜一十雪
31824c0504 refactor: get_group_list 2024-08-04 20:56:49 +08:00
手瓜一十雪
e203abae85 refactor: /get_group_member_info 2024-08-04 20:53:23 +08:00
手瓜一十雪
faf83b680b 《NTQQ参数全解》 2024-08-04 20:19:34 +08:00
手瓜一十雪
67dcbcb842 refactor: 开始重构群成员信息获取 2024-08-04 20:10:21 +08:00
手瓜一十雪
6533a25404 chore: 移除调试代码 2024-08-04 19:12:18 +08:00
手瓜一十雪
4dc760b0e9 feat: shareDigest 2024-08-04 19:11:44 +08:00
手瓜一十雪
25933b9043 chore: 移除旧代码 2024-08-04 18:50:40 +08:00
手瓜一十雪
a53aaa456e fix: 一处很久很久的看错的的问题 2024-08-04 18:49:40 +08:00
手瓜一十雪
e8a7ea07a5 refactor: Id转换 2024-08-04 18:45:00 +08:00
手瓜一十雪
8817dc6b10 refactor: Uid/Uin转换V2版本 2024-08-04 18:01:31 +08:00
手瓜一十雪
491ec04b46 fix: 准备第二次重构uid/uin 2024-08-04 16:50:23 +08:00
手瓜一十雪
8a5d4a683b feat: getBuddyV2ExWithCate 2024-08-04 16:45:14 +08:00
手瓜一十雪
dfc7c7357a Refactor Api: GetFriendsWithCategory 2024-08-04 16:37:15 +08:00
手瓜一十雪
690a2f7d34 refctor: getBuddyV2 支持分类 2024-08-04 16:27:25 +08:00
手瓜一十雪
58f22b24e4 refactor: api getbuddyv2 2024-08-04 16:05:07 +08:00
手瓜一十雪
3cce9f528b build: 1.7.7 2024-08-04 15:16:45 +08:00
手瓜一十雪
20fd5ac8cb chore: Todo 2024-08-04 15:12:55 +08:00
手瓜一十雪
9e05e086eb fix: 初始化问题 2024-08-04 15:01:43 +08:00
手瓜一十雪
056e0adddf build: 1.7.7-refactor 2024-08-04 14:39:45 +08:00
手瓜一十雪
b36388200d Merge branch 'main' of https://github.com/NapNeko/NapCatQQ 2024-08-04 14:35:47 +08:00
手瓜一十雪
9793e5741a refactor: Appid的获取 2024-08-04 14:35:26 +08:00
手瓜一十雪
143380c012 Merge pull request #206 from pohgxz/main
修复webui快捷登录失败
2024-08-04 14:15:53 +08:00
手瓜一十雪
4b92254945 chore: 订正类型 2024-08-04 14:15:40 +08:00
手瓜一十雪
f9c1d8b4a6 build: 1.7.7 refactor 2024-08-04 14:06:30 +08:00
手瓜一十雪
c0c469339b refactor: versionGet 2024-08-04 14:03:28 +08:00
Nepenthe
0ca6343ed7 修复webui快捷登录失败 2024-08-04 13:04:31 +08:00
手瓜一十雪
3db74c3427 refactor: BuddyList 2024-08-04 12:26:55 +08:00
手瓜一十雪
48d5cb53bd release: 1.7.6 2024-08-03 22:45:36 +08:00
手瓜一十雪
fd7d2dbf53 release: 1.7.5 2024-08-03 20:48:13 +08:00
手瓜一十雪
6609697752 release: 1.7.5 2024-08-03 20:47:05 +08:00
手瓜一十雪
dcd6e1973e build:1.7.5For9.9.15 2024-08-03 16:23:55 +08:00
手瓜一十雪
3614a6e932 chore: 9.9.15 support 2024-08-03 16:08:23 +08:00
手瓜一十雪
931a0210e5 chore: 兼容9.915 信息获取 2024-08-03 15:36:56 +08:00
手瓜一十雪
f9e7de4b42 build: 1.7.5For9.9.15 2024-08-03 15:07:51 +08:00
手瓜一十雪
8e0b79594e style: lint 2024-08-03 15:06:02 +08:00
手瓜一十雪
17122c4360 feat: 精简历史获取 2024-08-03 15:03:41 +08:00
手瓜一十雪
154f7b6a30 chore: 清除老旧代码 2024-08-03 14:57:24 +08:00
手瓜一十雪
52e5543d0b chore: queryEmoticonMsgs 2024-08-03 14:44:48 +08:00
手瓜一十雪
3c304bd2ae feat: 补全类型 开始对9.9.15针对优化 2024-08-03 14:25:26 +08:00
手瓜一十雪
26609bb8fd chore: 9.9.15兼容sendmsg 2024-08-03 13:11:25 +08:00
手瓜一十雪
de3fa9aaa4 Merge branch 'main' of https://github.com/NapNeko/NapCatQQ 2024-08-03 12:28:41 +08:00
手瓜一十雪
788665f84c chore: support win 9.9.15 2024-08-03 12:28:25 +08:00
手瓜一十雪
3943782971 Merge pull request #201 from idranme/main
feat: at segment add name
2024-08-03 07:08:25 +08:00
idranme
8f899c40f2 chore 2024-08-02 15:49:01 +00:00
idranme
a1f582399e feat: at segment add name 2024-08-02 15:45:37 +00:00
手瓜一十雪
440b63f662 release: v1.7.4 2024-08-01 23:35:35 +08:00
手瓜一十雪
7d2cc3b56b fix: 多次上报自身消息 2024-08-01 22:00:40 +08:00
手瓜一十雪
5fe3422469 #176 revert 2024-08-01 21:48:17 +08:00
手瓜一十雪
6c02cedb1e build: 1.7.4 2024-08-01 19:44:28 +08:00
手瓜一十雪
3cc2f1dcad fix #183 2024-08-01 19:43:29 +08:00
手瓜一十雪
773cdc5877 build: test 2024-08-01 17:14:15 +08:00
手瓜一十雪
361a7329d7 Revert "build(deps-dev): bump @typescript-eslint/eslint-plugin"
This reverts commit 2562a38fa1.
2024-08-01 17:13:54 +08:00
手瓜一十雪
29910f1236 build: test api 2024-08-01 17:12:56 +08:00
手瓜一十雪
4a164016f5 chore: test 2024-08-01 17:08:22 +08:00
手瓜一十雪
cebd3e62a4 Merge pull request #192 from NapNeko/dependabot/npm_and_yarn/typescript-eslint/eslint-plugin-8.0.0
build(deps-dev): bump @typescript-eslint/eslint-plugin from 7.18.0 to 8.0.0
2024-08-01 16:26:50 +08:00
dependabot[bot]
2562a38fa1 build(deps-dev): bump @typescript-eslint/eslint-plugin
Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 7.18.0 to 8.0.0.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.0.0/packages/eslint-plugin)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/eslint-plugin"
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-01 08:22:34 +00:00
手瓜一十雪
d46c922bbf Merge branch 'main' of https://github.com/NapNeko/NapCatQQ 2024-08-01 13:11:19 +08:00
手瓜一十雪
66b59982f7 补充类型 移除无用代码 2024-08-01 13:11:09 +08:00
手瓜一十雪
ad397ccf7f Merge pull request #188 from Fripine/feat/FriendAddNoticeEvent
feat: support FriendAddNoticeEvent
2024-08-01 08:46:50 +08:00
Fripine
bdef80ede7 feat: support FriendAddNoticeEvent 2024-08-01 01:03:33 +08:00
手瓜一十雪
385dcbc75a fix #186 2024-07-31 22:23:12 +08:00
手瓜一十雪
74cf501c8f release: 1.7.3 2024-07-31 22:21:23 +08:00
手瓜一十雪
0c200d6748 build: 1.7.2 beta0 2024-07-31 22:20:39 +08:00
手瓜一十雪
e65a36c517 chore: Ver2SendMsg 2024-07-31 22:19:35 +08:00
手瓜一十雪
126b54ad40 fix #186 2024-07-31 21:46:12 +08:00
手瓜一十雪
78637751af fix #187 2024-07-31 21:41:31 +08:00
手瓜一十雪
f96526ee3a fix #184 2024-07-31 21:36:13 +08:00
手瓜一十雪
b3c7a91f3d refactor: getfile 2024-07-31 16:40:34 +08:00
手瓜一十雪
b8daeef0c4 fix #173 2024-07-31 16:02:08 +08:00
手瓜一十雪
2b662944cf typo fix #178 2024-07-31 15:58:27 +08:00
手瓜一十雪
3d516df01e try fix #183 2024-07-31 15:52:28 +08:00
手瓜一十雪
26b4a9b15b chore: 兼容wt 2024-07-31 14:21:05 +08:00
手瓜一十雪
0f8af273ae release: 1.7.2 2024-07-31 10:51:12 +08:00
手瓜一十雪
fa29a31da9 release: 1.7.2 2024-07-31 10:46:23 +08:00
手瓜一十雪
9e0d2606d8 build: 1.7.2 no log 2024-07-31 10:35:53 +08:00
手瓜一十雪
338dedd6e0 build: 1.7.1 双消息队列 2024-07-31 10:33:38 +08:00
手瓜一十雪
1f893b1393 build: 1.7.2 消息队列重构 2024-07-31 10:25:46 +08:00
手瓜一十雪
b783d6f928 refatcor: sendmsg 2024-07-31 01:12:46 +08:00
手瓜一十雪
8ca0d40f05 fix: error 2024-07-30 23:34:29 +08:00
手瓜一十雪
2f40a80434 fix: error 2024-07-30 23:33:32 +08:00
手瓜一十雪
812d8eb5bb feat: 对msgId兜底 2024-07-30 23:31:42 +08:00
手瓜一十雪
bf5f548349 fix: msghash性能问题 2024-07-30 23:06:58 +08:00
手瓜一十雪
ebf90e72b9 Merge pull request #180 from cnxysoft/main
BW5启动脚本修改
2024-07-30 22:30:04 +08:00
Alen
31d7d42edf BW5启动脚本修复
修改脚本为 UTF-8 with BOM 格式,解决添加注释后执行报错的问题
2024-07-30 19:56:17 +08:00
Alen
833875b42f Merge remote-tracking branch 'upstream/main' 2024-07-30 17:56:48 +08:00
Alen
b901c10f3c 修改BW5启动脚本
支持脚本自动提权
自动覆盖dbghelp.dll
修改默认启用UTF8
修改不再从新窗口运行NC
2024-07-30 17:56:28 +08:00
手瓜一十雪
8ab678bd97 chore: remove log 2024-07-30 16:45:03 +08:00
手瓜一十雪
55d5072f46 fix: setMsg 2024-07-30 16:34:33 +08:00
手瓜一十雪
a379ffd0f2 Merge pull request #175 from pohgxz/main
修复Way05无法带参
2024-07-30 09:12:15 +08:00
Nepenthe
4501d73134 修复Way05无法带参 2024-07-29 23:32:34 +08:00
手瓜一十雪
5f15774ec7 fix: script 2024-07-29 20:58:27 +08:00
手瓜一十雪
c9e057599e fix: error 2024-07-29 20:49:35 +08:00
手瓜一十雪
66851f5625 fix 2024-07-29 20:39:01 +08:00
手瓜一十雪
b33c235b4d release: 1.7.1 2024-07-29 20:13:09 +08:00
手瓜一十雪
d6693b6114 Merge pull request #171 from NapNeko/dependabot/npm_and_yarn/types/node-22.0.0
build(deps-dev): bump @types/node from 20.14.13 to 22.0.0
2024-07-29 17:23:02 +08:00
dependabot[bot]
36b4d26c78 build(deps-dev): bump @types/node from 20.14.13 to 22.0.0
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 20.14.13 to 22.0.0.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-29 08:23:55 +00:00
手瓜一十雪
617592d90a fix: 多次post 2024-07-29 11:32:55 +08:00
手瓜一十雪
15a77b8070 fix: bug 2024-07-29 10:24:02 +08:00
手瓜一十雪
606eccd22b refactor: 精简逻辑 2024-07-29 10:12:41 +08:00
手瓜一十雪
5613450313 refactor: 回复 2024-07-29 10:07:59 +08:00
手瓜一十雪
c59b5564af release: 1.7.0 2024-07-29 09:24:17 +08:00
手瓜一十雪
330b086b8b fix #98 2024-07-29 09:10:52 +08:00
手瓜一十雪
9837ef4f36 fix #125 2024-07-29 08:59:26 +08:00
手瓜一十雪
add46b3251 chore: 落地标准化 2024-07-29 08:43:37 +08:00
手瓜一十雪
e169199107 feat: #162 2024-07-29 08:29:24 +08:00
手瓜一十雪
92fe654850 fix #157 2024-07-29 08:12:49 +08:00
手瓜一十雪
b257486404 fix #158 2024-07-29 08:02:18 +08:00
手瓜一十雪
bdf2e33f40 Merge branch 'main' of https://github.com/NapNeko/NapCatQQ 2024-07-29 07:51:28 +08:00
手瓜一十雪
224d361923 fix #167 2024-07-29 07:51:18 +08:00
手瓜一十雪
3452fa56df build: 😭版本满天飞版本 2024-07-29 00:39:34 +08:00
手瓜一十雪
cd256235da refactor: selfMsgPost 2024-07-29 00:16:24 +08:00
手瓜一十雪
361a164f2a fix: typo 2024-07-28 23:54:50 +08:00
手瓜一十雪
0e60d4b198 fix: 延迟调整 2024-07-28 23:53:45 +08:00
手瓜一十雪
67b47e39b4 fix: typo 2024-07-28 23:49:04 +08:00
手瓜一十雪
8f54310f63 refactor: reply 2024-07-28 23:35:58 +08:00
手瓜一十雪
c7a7494d7e fix: 保证NC回复的消息一致性 2024-07-28 22:33:50 +08:00
手瓜一十雪
af88b3166d build: 169-test 2024-07-28 17:55:34 +08:00
手瓜一十雪
b7837b2a14 chore:test 2024-07-28 17:38:48 +08:00
手瓜一十雪
950ddc749e chore: 🥹LocalMsg不会写入数据库 2024-07-28 16:23:56 +08:00
手瓜一十雪
df081ef0cf chore: 移除调试代码 2024-07-28 15:40:44 +08:00
手瓜一十雪
7b24f90d9f rector: 离线文件重构初步完成 2024-07-28 15:37:34 +08:00
手瓜一十雪
f2e4579fd8 feat: 缓存文件 2024-07-28 15:34:08 +08:00
手瓜一十雪
97cb351827 fix: typo 2024-07-28 14:55:30 +08:00
手瓜一十雪
c1ec53fdbb refactor: fileget 2024-07-28 14:25:13 +08:00
手瓜一十雪
98214aa429 refactor: video element 2024-07-28 13:36:03 +08:00
手瓜一十雪
ce7deac2dd refactor: video element 2024-07-28 13:28:21 +08:00
手瓜一十雪
612092b867 build: 170 re 2024-07-28 13:15:14 +08:00
手瓜一十雪
92579d5949 Merge pull request #163 from cnxysoft/main
修复提交疏漏
2024-07-28 13:00:12 +08:00
手瓜一十雪
9ab07060ae fix: default 2024-07-28 12:59:20 +08:00
手瓜一十雪
0d45125d79 fix: uid && latestMsg 2024-07-28 09:56:00 +08:00
手瓜一十雪
9ced152778 fix: 修复uid转换异常问题 2024-07-28 09:24:03 +08:00
Alen
3685ab2e3e Merge remote-tracking branch 'upstream/main' 2024-07-27 15:01:23 +08:00
Alen
be605f11f2 修复提交疏漏
修改在查询群历史消息时,如未提供msg_seq,则返回最新消息
2024-07-27 15:01:08 +08:00
手瓜一十雪
8cca8df976 Merge pull request #159 from cnxysoft/main
bug修复和标准兼容
2024-07-27 06:46:25 +08:00
Alen
990a31e961 标准兼容
根据GOCQ标准将获取群历史消息中的msg_seq改为非必要参数,默认为0
2024-07-27 04:10:01 +08:00
Alen
5db201c342 BUG修复
修复创建reply消息体时向NTQQMsgApi.getMsgsByMsgId提交空值查询会导致QQ崩溃的BUG
2024-07-27 04:01:22 +08:00
手瓜一十雪
a625e30dd4 fix: search file 2024-07-26 17:37:04 +08:00
手瓜一十雪
b236cdd060 refactor: search file 2024-07-26 17:15:28 +08:00
手瓜一十雪
2db9899184 chore: type 2024-07-26 16:26:47 +08:00
手瓜一十雪
fe5d6db986 chore: wait release 1.7.0 2024-07-26 16:10:05 +08:00
手瓜一十雪
7c7bf8fecf chore: 类型+++++ 2024-07-26 16:08:28 +08:00
手瓜一十雪
76e3a46378 fix: type 2024-07-26 16:02:23 +08:00
手瓜一十雪
16f3897fec chore: 类型补全计划 2024-07-26 15:55:05 +08:00
手瓜一十雪
045e120854 refactor: type 2024-07-26 15:38:43 +08:00
Version
2b7fcce9b2 chore:version change 2024-07-26 05:25:49 +00:00
手瓜一十雪
9685931694 fix: uint 2024-07-26 13:25:19 +08:00
手瓜一十雪
1dc844435a Merge pull request #153 from Guation/main
feat: WebUI支持放置到二级目录中
2024-07-26 13:04:26 +08:00
手瓜一十雪
18892379de fix: search file 2024-07-26 12:27:02 +08:00
手瓜一十雪
620d61c8dc docs: v1.6.8 2024-07-26 11:59:18 +08:00
手瓜一十雪
9f91398875 build: 再次优化发送速度 2024-07-26 11:30:04 +08:00
挂神
a29b1154a9 feat: WebUI支持放置到二级目录中 2024-07-26 11:12:09 +08:00
手瓜一十雪
34d19a471a refactor: 回滚 2024-07-26 10:58:56 +08:00
手瓜一十雪
2ef6477d7c build: log info 2024-07-25 20:22:03 +08:00
手瓜一十雪
26e6800836 fix: 退群推送 2024-07-25 18:08:49 +08:00
手瓜一十雪
9dbbcf3872 build: 1.6.8 - parse appid 2024-07-25 17:57:39 +08:00
手瓜一十雪
6b3343e1e4 build: 1.6.8 beta6 2024-07-25 10:59:06 +08:00
手瓜一十雪
ef48f754a5 docs: 整理当前进度 2024-07-25 10:44:53 +08:00
手瓜一十雪
3be1ede847 refactor: sendtime/join time 2024-07-25 10:32:44 +08:00
手瓜一十雪
7bff1b61e8 refactor: SendTime 2024-07-25 10:02:16 +08:00
手瓜一十雪
6affd0eb68 feat: GetSendTime 2024-07-24 17:49:03 +08:00
手瓜一十雪
0a112d15e0 fix: typo 2024-07-24 15:37:23 +08:00
手瓜一十雪
4e03f582bb fix: richmeida name 2024-07-24 14:43:10 +08:00
手瓜一十雪
8f186c1c5e chore: action clean 2024-07-24 14:37:48 +08:00
手瓜一十雪
cd1bae9a1f fix: setGroupAvatar 2024-07-24 14:35:12 +08:00
手瓜一十雪
60796c26ca Merge pull request #147 from serfend/default-config
fix[config]support overwrite by user #145
2024-07-24 14:28:42 +08:00
汉广
28927f950d fix[config]support overwrite by user 2024-07-24 14:25:58 +08:00
手瓜一十雪
95f16ebc8c Merge pull request #144 from Guation/main
feat: http与ws允许监听同一端口,快速登录允许自动选择QQ号,允许禁用webUI
2024-07-24 14:09:19 +08:00
挂神
25bca8385d feat: http与ws共站支持热重载 2024-07-24 13:14:35 +08:00
手瓜一十雪
965c7f23b4 feat: 群头像设置 2024-07-24 11:37:12 +08:00
手瓜一十雪
33082af9cc feat: searchFile 2024-07-24 11:23:27 +08:00
手瓜一十雪
1f7f3565b0 build: 1.6.8 beta05 2024-07-24 10:45:11 +08:00
手瓜一十雪
f784363696 refactor: UUID 2024-07-24 10:44:55 +08:00
手瓜一十雪
37bd51e138 refactor: getUserInfo 2024-07-24 10:42:22 +08:00
手瓜一十雪
c367728c43 Merge branch 'main' of https://github.com/NapNeko/NapCatQQ 2024-07-24 10:23:48 +08:00
手瓜一十雪
61f0f5d884 refactor: 改造接口调用 2024-07-24 10:23:41 +08:00
手瓜一十雪
5f2ebeead7 docs: update 2024-07-23 18:32:07 +08:00
手瓜一十雪
7646037fc7 docs: 砍掉 2024-07-23 18:21:19 +08:00
手瓜一十雪
eac6d285ff chore: debug 2024-07-23 17:39:00 +08:00
手瓜一十雪
b921d5e734 refactor: downloadMedia 2024-07-23 16:15:23 +08:00
手瓜一十雪
831d808e63 chore: remove 2024-07-23 16:03:06 +08:00
手瓜一十雪
451b88d7e3 refactor: video type 2024-07-23 15:51:57 +08:00
手瓜一十雪
f916682a71 build: 1.6.8 beta07 2024-07-23 15:38:41 +08:00
手瓜一十雪
2d76bcf0cf refactor: message id 2024-07-23 15:10:39 +08:00
手瓜一十雪
8db294efe6 refactor: 转发消息修复 2024-07-23 14:54:05 +08:00
手瓜一十雪
5cc5149aed fix: 合并转发 2024-07-23 14:19:26 +08:00
手瓜一十雪
7ecb01dc9f docs: 规划 2024-07-23 12:34:20 +08:00
手瓜一十雪
8bf1a545d9 chore: remove debug 2024-07-23 10:12:20 +08:00
手瓜一十雪
efb2be2f94 fix: timeout 2024-07-23 09:50:31 +08:00
手瓜一十雪
bd2edda494 refactor: sendMsg 2024-07-23 09:45:00 +08:00
手瓜一十雪
991172eae4 Merge branch 'main' of https://github.com/NapNeko/NapCatQQ 2024-07-23 09:21:36 +08:00
手瓜一十雪
fc7631f9aa refactor: sendmsg 2024-07-23 09:21:22 +08:00
手瓜一十雪
a21efb7d2f Merge pull request #145 from serfend/default-config
fix[default-config]config name check #138
2024-07-22 21:36:54 +08:00
汉广
03bc844ad0 fix[default-config]config name check 2024-07-22 20:12:24 +08:00
挂神
f7bdc35ed6 feat: http与ws允许监听同一端口,快速登录允许自动选择QQ号,允许禁用webUI 2024-07-22 19:47:23 +08:00
手瓜一十雪
0efdffd857 Merge branch 'main' of https://github.com/NapNeko/NapCatQQ 2024-07-22 18:49:36 +08:00
手瓜一十雪
6a16a42d0c feat: refactor send 2024-07-22 18:49:25 +08:00
手瓜一十雪
e9c00c72b1 Merge pull request #138 from serfend/main
feat[config]support use default-template
2024-07-22 18:22:49 +08:00
手瓜一十雪
ab22f36b8a refactor: NTEvent Checker 2024-07-22 18:21:29 +08:00
手瓜一十雪
c57497cd91 feat: remove debug 2024-07-22 18:17:33 +08:00
手瓜一十雪
ba123236e5 feat:msgid generate 2024-07-22 18:17:03 +08:00
手瓜一十雪
338b6e4607 fix: QRCode 2024-07-22 15:46:48 +08:00
手瓜一十雪
f88c717560 build: 1.6.8-beta03 2024-07-22 15:40:41 +08:00
手瓜一十雪
f8ffc92db5 feat: remove LineDev&&Protobuf 2024-07-22 15:40:23 +08:00
手瓜一十雪
98c23c172c build: 1.6.8-无数据库版本 2024-07-22 15:13:38 +08:00
手瓜一十雪
781c107d8c feat: 拉取重启消息 2024-07-22 15:12:25 +08:00
手瓜一十雪
186668c075 fix: Login 2024-07-22 14:18:04 +08:00
手瓜一十雪
cf9f785193 style: lint 2024-07-22 14:12:03 +08:00
手瓜一十雪
72d2d3f224 feat: 破坏file/db相关接口 2024-07-22 14:09:37 +08:00
手瓜一十雪
087c76b394 refactor: msgId stage-2 2024-07-22 11:34:18 +08:00
手瓜一十雪
4f9fb2c8c3 Merge pull request #141 from cnxysoft/main
修复提交疏漏
2024-07-22 11:15:28 +08:00
手瓜一十雪
334e43e764 refactor: MsgId 2024-07-22 11:15:01 +08:00
Alen
7843256402 修复提交疏漏
修复变量类型未断言的问题
2024-07-22 11:07:33 +08:00
手瓜一十雪
0522ba35fe refactor: jest test 2024-07-22 10:24:55 +08:00
手瓜一十雪
24d3b52e0b refactor: Message Unique 2024-07-22 09:56:08 +08:00
手瓜一十雪
3177110f0f feat: RecentContact 2024-07-22 09:24:16 +08:00
手瓜一十雪
e1b8243a67 Merge pull request #140 from cnxysoft/main
BUG修复
2024-07-22 08:40:21 +08:00
Alen
b1c6ce3885 BUG修复
1.尝试让所有人能收到group_admin事件
2.修复请求API: delete_msg(POST请求网址传参)将负数判定为文本导致无法调用的问题
2024-07-22 01:22:38 +08:00
手瓜一十雪
0b4b25a11e feat: LineDev for Develop-0 2024-07-21 19:31:13 +08:00
手瓜一十雪
1176fe984a add: RecentListener 2024-07-21 19:01:47 +08:00
汉广
6ca768c3ee feat[config]support use default-template 2024-07-20 23:43:32 +08:00
手瓜一十雪
3da1659c8d fix: buddylike 2024-07-20 20:16:45 +08:00
手瓜一十雪
9aa4cd319c release: 1.6.7 2024-07-20 19:52:39 +08:00
手瓜一十雪
5af866cdca fix: error 2024-07-20 19:31:59 +08:00
手瓜一十雪
2b421fa447 release: 1.6.7 2024-07-20 17:43:56 +08:00
手瓜一十雪
30ec964325 feat: new api 2024-07-20 17:33:26 +08:00
手瓜一十雪
714d7d72eb feat: raw api add 2024-07-20 17:09:38 +08:00
手瓜一十雪
687aa0f363 chore: remove debug 2024-07-20 16:37:16 +08:00
手瓜一十雪
8363ab07a7 feat: 支持精华消息 2024-07-20 16:17:02 +08:00
手瓜一十雪
c46a757339 fix: error 2024-07-20 16:14:02 +08:00
手瓜一十雪
557864395b feat: support essence 2024-07-20 16:09:44 +08:00
手瓜一十雪
3f7a85d80b feat: essence get_sender 2024-07-20 16:00:01 +08:00
手瓜一十雪
8d18d2ce1f refactor: 标准化 2024-07-20 15:55:26 +08:00
手瓜一十雪
7141ba1587 refactor: essence and together listener 2024-07-20 15:53:39 +08:00
手瓜一十雪
44d350a225 docs: todo 2024-07-20 15:37:57 +08:00
手瓜一十雪
239b8e72d9 Merge pull request #134 from serfend/main
fix[group]handle_request reason empty
2024-07-20 15:24:39 +08:00
手瓜一十雪
279bdb6fb1 Merge pull request #135 from pohgxz/main
群戳一戳增加原始信息
2024-07-20 15:24:27 +08:00
Nepenthe
a0cea819da 群戳一戳增加原始信息
群消息log增加视频解析
2024-07-20 14:51:50 +08:00
汉广
9ab7f60544 fix[group]handle_request reason empty 2024-07-20 14:16:07 +08:00
手瓜一十雪
aaf7191bf3 build: 1.6.7-beta03 2024-07-20 10:45:26 +08:00
手瓜一十雪
628c9be0c8 feat: 2401 for 群精华设置 2024-07-20 10:44:57 +08:00
手瓜一十雪
733052720c build: 1.6.6-build02 2024-07-20 10:28:47 +08:00
手瓜一十雪
a10f007194 build: 1.6.7-beta0 2024-07-20 10:21:16 +08:00
手瓜一十雪
6fa50c58d3 feat: 优化接口转换速度 避免频繁读写 2024-07-17 15:03:10 +08:00
手瓜一十雪
c54a58d6e4 release: v1.6.6 2024-07-16 15:55:25 +08:00
手瓜一十雪
34cb1ea3fd Merge pull request #126 from cnxysoft/main
修复戳一戳
2024-07-16 15:52:39 +08:00
Alen
f640b0ca91 修复戳一戳 2024-07-16 15:47:23 +08:00
手瓜一十雪
60e16da42e Merge pull request #121 from pohgxz/main
增加winQQ-9912一键启动脚本
2024-07-14 08:44:55 +08:00
Nepenthe
0cdceb95d6 增加winQQ-9912一键启动脚本 2024-07-13 16:09:14 +00:00
手瓜一十雪
70bd22d925 fix: typo 2024-07-13 20:27:18 +08:00
手瓜一十雪
82462dd647 docs: 规划 2024-07-13 20:21:48 +08:00
手瓜一十雪
c0466e943d build: test 2024-07-13 19:37:39 +08:00
手瓜一十雪
b187b4695d refactor: uin<->uid 2024-07-13 19:37:02 +08:00
手瓜一十雪
b1956d2a37 refactor: poke 2024-07-13 19:10:47 +08:00
手瓜一十雪
590b622e5f build: test 2024-07-13 18:58:52 +08:00
手瓜一十雪
3d8174396a feat: LinuxQQ版本25765 2024-07-13 18:58:29 +08:00
手瓜一十雪
b8dc6e9bd9 feat: 再次提升版本 25765 2024-07-13 18:56:42 +08:00
手瓜一十雪
8ee99109dc chore: 整理代码 2024-07-13 18:20:44 +08:00
手瓜一十雪
902041d4ee refactor: 新增启动脚本 2024-07-13 18:15:00 +08:00
手瓜一十雪
cc34aef47e style: code lint 2024-07-13 18:12:38 +08:00
手瓜一十雪
0afbbe7c7a refactor: 废弃部分代码 2024-07-13 18:10:41 +08:00
手瓜一十雪
8004553ba7 refactor: groupNotifies 2024-07-13 18:04:55 +08:00
手瓜一十雪
0023b2846a feat: 第二次大致整理 2024-07-13 17:23:05 +08:00
手瓜一十雪
34775c1816 fix: 整理常量 2024-07-12 18:08:45 +08:00
手瓜一十雪
e0759e704b feat:大部分消息元素 2024-07-12 18:01:48 +08:00
手瓜一十雪
0aa225ca78 fix: typo 2024-07-12 17:04:28 +08:00
手瓜一十雪
b43b4ee5c0 feat: test code 2024-07-12 16:59:08 +08:00
手瓜一十雪
0cdb8cecbf feat: 群精华 代码未测试 2024-07-12 11:02:10 +08:00
手瓜一十雪
fd6a306742 feat: 懒得写了 2024-07-12 10:54:01 +08:00
手瓜一十雪
7f3b3d2277 feat: 群精华 2024-07-12 10:46:57 +08:00
手瓜一十雪
8be5b977bf Merge pull request #117 from po-lan/main
对缓存进一步优化
2024-07-12 09:52:17 +08:00
po-lan
d7ddb15f9c 对缓存进一步优化
LRUCache 将所有被移除的缓存数据作为事件参数传递给事件处理程序。

在数据库操作部分,优化了读写流程,以确保每个群组至多执行三次数据库操作:

读取:先判断缓存中是否存在用户记录,若不存在则读取数据库。
创建:如果用户记录在数据库中不存在,则新增记录。
修改:如果用户记录在数据库中存在,则进行修改。
即使单个群组内有大量用户,每种操作也只会执行一次。
2024-07-12 00:46:03 +08:00
手瓜一十雪
9a6a1798d0 build: poke能用25493 2024-07-11 12:44:42 +08:00
手瓜一十雪
14196fd349 build: 移除调试代码 2024-07-11 12:31:00 +08:00
手瓜一十雪
941b89a523 feat: uin转换优化&poke支持重写 2024-07-11 12:28:11 +08:00
手瓜一十雪
a5f9e5f8c0 Merge pull request #113 from idranme/main
perf: audio
2024-07-11 09:49:56 +08:00
idranme
80c3356c8f perf: audio 2024-07-10 17:44:17 +00:00
手瓜一十雪
914136b750 refactor: 移除异常代码 2024-07-10 21:39:03 +08:00
手瓜一十雪
f9a60795f5 feat: uid转换优化 2024-07-10 21:33:31 +08:00
手瓜一十雪
19640927c7 Merge branch 'main' of https://github.com/NapNeko/NapCatQQ 2024-07-10 21:11:43 +08:00
手瓜一十雪
22faac7e36 fix: friend uid 异常 2024-07-10 21:11:28 +08:00
手瓜一十雪
30d260ab32 Merge pull request #111 from idranme/main
fix: error catch
2024-07-10 11:55:53 +08:00
idranme
115120d066 Update file.ts 2024-07-10 11:35:55 +08:00
idranme
1327844736 fix: error catch 2024-07-10 03:25:25 +00:00
手瓜一十雪
29904f3cb7 feat: 164 way03启动脚本补充 2024-07-06 13:23:31 +08:00
手瓜一十雪
50395594b7 Merge pull request #106 from jetjinser/fix-editorconfig
fix: `.editorconfig` wrong pair `end_of_line`
2024-07-05 22:43:23 +08:00
Jinser Kafka
9360af88b3 fix: .editorconfig 2024-07-05 19:36:32 +08:00
手瓜一十雪
376370336c release: 1.6.5 2024-07-05 16:50:57 +08:00
手瓜一十雪
70df6e3302 Merge pull request #105 from po-lan/main
对缓存小优化
2024-07-05 16:49:30 +08:00
手瓜一十雪
0a1fc2dc12 feat: 1.6.5 2024-07-05 16:49:16 +08:00
手瓜一十雪
9857f6e437 feat: 优化载入流程 2024-07-05 16:47:08 +08:00
手瓜一十雪
56d6ebe916 refactor: 迁移到新库 2024-07-05 15:48:03 +08:00
po-lan
81134ea2d4 Update LRUCache.ts 2024-07-05 12:13:24 +08:00
po-lan
a9f3e7fc54 Update db.ts
通过读取缓存修复刚说话缺无法获取发言时间的问题
2024-07-05 12:12:40 +08:00
po-lan
eb84e2f8c9 Update LRUCache.ts
Add a get function to the cache
2024-07-05 12:09:59 +08:00
手瓜一十雪
61cfa0e86d release: 1.6.4 2024-07-03 14:30:55 +08:00
手瓜一十雪
0a01b8ade9 Merge pull request #97 from cnxysoft/main
修改下载函数
2024-07-02 10:13:15 +08:00
Alen
1457efa9a4 修改下载函数
为默认Headers增加Host,解决一些网站无法下载文件的问题
2024-07-02 01:07:30 +08:00
手瓜一十雪
fa5c7add7a refactor: new core 2024-07-01 18:45:48 +08:00
手瓜一十雪
d644eba4d1 refactor: napcat 2024-07-01 18:24:36 +08:00
手瓜一十雪
9c422c1a8f Merge pull request #95 from ahjsrhj/main
feat: ws反代请求添加UA: OneBot/11
2024-07-01 13:20:47 +08:00
手瓜一十雪
b6db37202f feat: core code 2024-07-01 13:18:59 +08:00
手瓜一十雪
4ca3891089 refactor: core 2024-07-01 13:18:19 +08:00
ahjsrhj
4c7ed01776 feat: ws反代请求添加UA: OneBot/11 2024-07-01 11:19:15 +08:00
手瓜一十雪
45c922c377 release: 1.6.3 2024-06-28 14:00:15 +08:00
手瓜一十雪
f854c258bd fix: 清除旧的反向ws 2024-06-28 13:42:09 +08:00
手瓜一十雪
a643fac073 refactor: msg context 2024-06-28 13:38:03 +08:00
手瓜一十雪
861f105bea fix: uid->uin 临时修复方案
uid uin转换需要优化
2024-06-28 13:11:06 +08:00
手瓜一十雪
bf10ce9f1e fix: error 2024-06-26 18:40:08 +08:00
手瓜一十雪
ccf521d0a8 feat: win ia32支持 2024-06-26 18:13:55 +08:00
手瓜一十雪
6075d98eaa release: v1.6.2 2024-06-26 18:09:41 +08:00
手瓜一十雪
a3801fc243 fix: script 2024-06-26 17:17:33 +08:00
手瓜一十雪
a1f0c05f3a chore: build script 2024-06-26 17:16:54 +08:00
手瓜一十雪
a568c96929 chore: build script 2024-06-26 17:15:46 +08:00
手瓜一十雪
d58bcf3c0e refactor: error catch 2024-06-26 17:14:23 +08:00
手瓜一十雪
985f2e6436 fix: 还原修改 2024-06-25 17:24:42 +08:00
手瓜一十雪
ad441fa793 docs: update 2024-06-24 21:38:28 +08:00
手瓜一十雪
316300cc86 Merge pull request #86 from NapNeko/dependabot/npm_and_yarn/types/uuid-10.0.0
build(deps-dev): bump @types/uuid from 9.0.8 to 10.0.0
2024-06-24 17:44:25 +08:00
dependabot[bot]
5c4f37b234 build(deps-dev): bump @types/uuid from 9.0.8 to 10.0.0
Bumps [@types/uuid](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/uuid) from 9.0.8 to 10.0.0.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/uuid)

---
updated-dependencies:
- dependency-name: "@types/uuid"
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-24 08:14:47 +00:00
手瓜一十雪
77b51a072d fix: 增加容错 2024-06-24 12:03:49 +08:00
手瓜一十雪
2536e1ae6a release: v1.6.1 2024-06-22 10:54:58 +08:00
手瓜一十雪
14822c9599 style&fix: lint & poke 2024-06-21 23:04:56 +08:00
手瓜一十雪
e53c37adc9 release: v1.6.0 2024-06-21 22:43:19 +08:00
手瓜一十雪
c37539354c docs: update 2024-06-21 22:35:10 +08:00
手瓜一十雪
ae0277f33c release: 1.5.8 2024-06-19 23:34:09 +08:00
手瓜一十雪
b863896249 refactor: log file limit 2024-06-19 23:26:05 +08:00
手瓜一十雪
5b42f8b743 refactor: qqmusic card & requests 2024-06-19 23:06:57 +08:00
手瓜一十雪
3883fab614 remove: debug 2024-06-19 21:37:13 +08:00
手瓜一十雪
61d6bcec4b refactor: qqmusic card & requests 2024-06-19 21:33:31 +08:00
手瓜一十雪
3b5902b033 refactor: requests 2024-06-19 16:45:20 +08:00
手瓜一十雪
3a88c21a3b refactor: 整理action & data 2024-06-19 13:35:42 +08:00
手瓜一十雪
91a5055dee refactor: qqmusic sign & http post 2024-06-19 13:20:52 +08:00
手瓜一十雪
7befd1469f Merge branch 'main' of https://github.com/NapNeko/NapCatQQ 2024-06-19 10:01:37 +08:00
手瓜一十雪
c72ebe495c refactor: remove debug 2024-06-19 10:01:24 +08:00
手瓜一十雪
19e06b97e6 docs: update 2024-06-18 23:57:47 +08:00
手瓜一十雪
7519825303 refactor: test 2024-06-18 23:23:19 +08:00
手瓜一十雪
d9315bf309 refactor: project 2024-06-18 22:49:06 +08:00
手瓜一十雪
8c36c809a0 docs: extend 2024-06-18 11:22:35 +08:00
手瓜一十雪
8138aa3cb2 docs: v1.5.8 2024-06-18 11:21:52 +08:00
手瓜一十雪
87aef3ca78 feat: Util HttpUploadFile 2024-06-18 11:17:09 +08:00
手瓜一十雪
a3f1d26d6b feat: refactor miniapp 2024-06-18 11:08:51 +08:00
手瓜一十雪
06cebc5670 build: try support ia32 2024-06-17 10:03:59 +08:00
手瓜一十雪
867fd62d77 fix: typo 2024-06-16 20:54:52 +08:00
手瓜一十雪
650cdf2916 feat: test 2024-06-16 19:24:42 +08:00
手瓜一十雪
ebf461f2fd feat: 加了一些暂时不能使用的代码 2024-06-16 16:35:09 +08:00
手瓜一十雪
27fa319b2a release: LinuxAppid 24568 2024-06-14 18:43:56 +08:00
手瓜一十雪
d95ac894f4 release: 1.5.6-复活下win 2024-06-14 17:00:35 +08:00
手瓜一十雪
ae84a8dd11 docs: v1.5.6 change 2024-06-14 16:53:26 +08:00
手瓜一十雪
2fc963f986 build: v1.5.6-紧急测试 2024-06-14 16:43:32 +08:00
手瓜一十雪
be1f938ebd fix 2024-06-14 16:42:39 +08:00
手瓜一十雪
cccf4d503d docs: v1.5.6 2024-06-14 15:50:22 +08:00
手瓜一十雪
9dad2a8ac6 remove: debug 2024-06-11 18:11:32 +08:00
手瓜一十雪
75af104f07 release: v1.5.5 2024-06-11 18:10:22 +08:00
手瓜一十雪
76ecba245b fix: error 2024-06-11 18:09:46 +08:00
手瓜一十雪
3697c2ced8 refactor: 移除无缝升级函数 2024-06-11 15:46:27 +08:00
545 changed files with 19088 additions and 14059 deletions

View File

@@ -5,7 +5,7 @@ root = true
# Unix-style newlines with a newline ending every file
[*]
end_of_line = lf|crlf
end_of_line = lf
insert_final_newline = true
# Matches multiple files with brace expansion notation
@@ -15,7 +15,7 @@ charset = utf-8
# 2 space indentation
[*.{cjs,mjs,js,jsx,ts,tsx,css,scss,sass,html,json}]
indent_style = space
indent_size = 2
indent_size = 4
# Unfortunately, EditorConfig doesn't support space configuration inside import braces directly.
# You'll need to rely on your linter/formatter like ESLint or Prettier for that.
# You'll need to rely on your linter/formatter like ESLint or Prettier for that.

View File

@@ -1 +0,0 @@
VITE_BUILD_TYPE = Development

2
.env.framework Normal file
View File

@@ -0,0 +1,2 @@
VITE_BUILD_TYPE = Production
VITE_BUILD_PLATFORM = Framework

View File

@@ -1 +1,2 @@
VITE_BUILD_TYPE = Production
VITE_BUILD_PLATFORM = Shell

View File

@@ -1,68 +1,64 @@
module.exports = {
'env': {
'browser': true,
'es2021': true,
'node': true
},
'ignorePatterns': ['src/core/', 'src/core.lib/','src/proto/'],
'extends': [
'eslint:recommended',
'plugin:@typescript-eslint/recommended'
],
'overrides': [
{
'env': {
'env': {
'browser': true,
'es2021': true,
'node': true
},
'files': [
'.eslintrc.{js,cjs}'
],
'parserOptions': {
'sourceType': 'script'
}
}
],
'parser': '@typescript-eslint/parser',
'parserOptions': {
'ecmaVersion': 'latest',
'sourceType': 'module'
},
'plugins': [
'@typescript-eslint',
'import'
],
'settings': {
'import/parsers': {
'@typescript-eslint/parser': ['.ts']
},
'import/resolver': {
'typescript': {
'alwaysTryTypes': true
}
'ignorePatterns': ['src/proto/'],
'extends': [
'eslint:recommended',
'plugin:@typescript-eslint/recommended'
],
'overrides': [
{
'env': {
'node': true
},
'files': [
'.eslintrc.{js,cjs}'
],
'parserOptions': {
'sourceType': 'script'
}
}
],
'parser': '@typescript-eslint/parser',
'parserOptions': {
'ecmaVersion': 'latest',
'sourceType': 'module'
},
'plugins': [
'@typescript-eslint',
'import'
],
'settings': {
'import/parsers': {
'@typescript-eslint/parser': ['.ts']
},
'import/resolver': {
'typescript': {
'alwaysTryTypes': true
}
}
},
'rules': {
'indent': [
'error',
4
],
'linebreak-style': [
'error',
'unix'
],
'semi': [
'error',
'always'
],
'no-unused-vars': 'off',
'no-async-promise-executor': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-unused-vars': 'off',
'@typescript-eslint/no-var-requires': 'off',
'object-curly-spacing': ['error', 'always'],
}
},
'rules': {
'indent': [
'error',
2
],
'linebreak-style': [
'error',
'unix'
],
'quotes': [
'error',
'single'
],
'semi': [
'error',
'always'
],
'no-unused-vars': 'off',
'no-async-promise-executor': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-unused-vars': 'off',
'@typescript-eslint/no-var-requires': 'off',
'object-curly-spacing': ['error', 'always'],
}
};

View File

@@ -1,4 +1,4 @@
name: "Build"
name: "Build Action"
on:
workflow_dispatch:
push:
@@ -8,14 +8,9 @@ on:
permissions: write-all
jobs:
build-linux:
Build-LiteLoader:
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
@@ -28,26 +23,22 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: 20.x
- name: Build NuCat Linux
- name: Build NuCat Framework
run: |
npm i --arch=${{ matrix.target_arch }} --platform=${{ matrix.target_platform }}
npm run build:prod
npm i
npm run build:framework
cd dist
npm i --omit=dev --arch=${{ matrix.target_arch }} --platform=${{ matrix.target_platform }}
npm i --omit=dev
rm package-lock.json
cd ..
- name: Upload Artifact
uses: actions/upload-artifact@v4
with:
name: NapCat.${{ matrix.target_platform }}.${{ matrix.target_arch }}
name: NapCat.Framework
path: dist
build-win32:
Build-Shell:
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
@@ -60,15 +51,16 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: 20.x
- name: Build NuCat Linux
- name: Build NuCat LiteLoader
run: |
npm i --arch=${{ matrix.target_arch }} --platform=${{ matrix.target_platform }}
npm run build:prod
npm i
npm run build:shell
cd dist
npm i --omit=dev --arch=${{ matrix.target_arch }} --platform=${{ matrix.target_platform }}
npm i --omit=dev
rm package-lock.json
cd ..
- name: Upload Artifact
uses: actions/upload-artifact@v4
with:
name: NapCat.${{ matrix.target_platform }}.${{ matrix.target_arch }}
path: dist
name: NapCat.Shell
path: dist

View File

@@ -1,4 +1,4 @@
name: "release"
name: "Build Release"
on:
push:
@@ -30,14 +30,9 @@ jobs:
ls
node ./script/checkVersion.cjs
sh ./checkVersion.sh
build-linux:
Build-LiteLoader:
needs: [check-version]
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
target_platform: [linux]
target_arch: [x64, arm64]
steps:
- name: Clone Main Repository
uses: actions/checkout@v4
@@ -51,28 +46,21 @@ jobs:
with:
node-version: 20.x
- name: Build NuCat Linux
- name: Build NuCat Framework
run: |
export NAPCAT_BUILDSYS=${{ matrix.target_platform }}
export NAPCAT_BUILDARCH=${{ matrix.target_arch }}
npm i --arch=${{ matrix.target_arch }} --platform=${{ matrix.target_platform }}
npm run build:prod
npm i
npm run build:framework
cd dist
npm i --omit=dev --arch=${{ matrix.target_arch }} --platform=${{ matrix.target_platform }}
npm i --omit=dev
cd ..
- name: Upload Artifact
uses: actions/upload-artifact@v4
with:
name: NapCat.${{ matrix.target_platform }}.${{ matrix.target_arch }}
name: NapCat.Framework
path: dist
build-win32:
Build-Shell:
runs-on: ubuntu-latest
needs: [check-version]
strategy:
fail-fast: false
matrix:
target_platform: [win32]
target_arch: [x64]
steps:
- name: Clone Main Repository
uses: actions/checkout@v4
@@ -87,24 +75,22 @@ jobs:
with:
node-version: 20.x
- name: Build NuCat Linux
- name: Build NuCat Shell
run: |
export NAPCAT_BUILDSYS=${{ matrix.target_platform }}
export NAPCAT_BUILDARCH=${{ matrix.target_arch }}
npm i --arch=${{ matrix.target_arch }} --platform=${{ matrix.target_platform }}
npm run build:prod
npm i
npm run build:shell
cd dist
npm i --omit=dev --arch=${{ matrix.target_arch }} --platform=${{ matrix.target_platform }}
npm i --omit=dev
cd ..
- name: Upload Artifact
uses: actions/upload-artifact@v4
with:
name: NapCat.${{ matrix.target_platform }}.${{ matrix.target_arch }}
name: NapCat.Shell
path: dist
release-napcat:
needs: [build-win32,build-linux]
needs: [Build-LiteLoader,Build-Shell]
runs-on: ubuntu-latest
steps:
- name: Download All Artifact
@@ -112,11 +98,16 @@ jobs:
- name: Compress subdirectories
run: |
for dir in */; do
base=$(basename "$dir")
zip -r "${base}.zip" "$dir"
done
cd ./NapCat.Shell/
zip -q -r NapCat.Shell.zip *
cd ..
cd ./NapCat.Framework/
zip -q -r NapCat.Framework.zip *
cd ..
rm ./NapCat.Shell.zip -rf
rm ./NapCat.Framework.zip -rf
mv ./NapCat.Shell/NapCat.Shell.zip ./
mv ./NapCat.Framework/NapCat.Framework.zip ./
- name: Extract version from tag
run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_ENV
@@ -130,9 +121,6 @@ jobs:
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
NapCat.Framework.zip
NapCat.Shell.zip
draft: true

3
.gitignore vendored
View File

@@ -14,4 +14,5 @@ dist/
# Build
*.db
checkVersion.sh
checkVersion.sh
bun.lockb

4
.gitmodules vendored
View File

@@ -1,4 +0,0 @@
[submodule "src/core"]
path = src/core
url = https://github.com/NapNeko/core.git
branch = master

10
.prettierrc.json Normal file
View File

@@ -0,0 +1,10 @@
{
"trailingComma": "es5",
"tabWidth": 4,
"semi": true,
"singleQuote": true,
"bracketSpacing": true,
"arrowParens": "always",
"printWidth": 120,
"endOfLine": "auto"
}

352
LICENSE
View File

@@ -1,21 +1,339 @@
MIT License
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (c) 2024 NapCatQQ
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
Preamble
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.

View File

@@ -2,32 +2,34 @@
<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>
---
## 项目介绍
NapCatQQ 是现代化的基于 NTQQ 的 Bot 协议端实现。
NapCatQQ 是基于 PC NTQQ 本体实现一套无头 Bot 框架。
名字寓意 瞌睡猫QQ像睡着了一样在后台低占用运行的无需GUI界面的NTQQ。
## 项目优势
- [x] **多种启动方式**支持以无头、LiteLoader 插件、仅 QQ GUI 三种方式启动
- [x] **低占用**:无头模式占用资源极低,适合在服务器上运行
- [x] **WebUI**:自带 WebUI 支持,远程管理更加便捷
## 如何使用
可前往 [Release](https://github.com/NapNeko/NapCatQQ/releases/) 页面下载最新版本
**首次使用** 请务必前往 [官方文档](https://napneko.github.io/) 查看使用文档与教程
**首次使用**请务必前往[官方文档](https://napneko.github.io/)查看使用教程
## 项目声明
* 请不要在无关地方宣传NapCatQQ本项目只是用于学习 node 相关知识,切勿用于违法用途
* NapCat 不会收集用户隐私信息,但是未来可能会为了更好的利于 NapCat 的优化会收集一些设备信息,如 cpu 架构,系统版本等
## 相关链接
[QQ Group](https://qm.qq.com/q/VfjAq5HIMS)
[Telegram Link](https://t.me/+nLZEnpne-pQ1OWFl)
## 鸣谢名单
感谢 [LLOneBot](https://github.com/LLOneBot/LLOneBot) 提供初始版本基础
[Lagrange](https://github.com/LagrangeDev/Lagrange.Core)
感谢 [Lagrange](https://github.com/LagrangeDev/Lagrange.Core) 对本项目的大力支持
<!--
QQ群545402644
-->
---
## 使用许可
任何使用本仓库代码的地方,都应当严格遵守[本仓库开源许可](./LICENSE)。**此外,禁止任何项目未经授权二次分发或基于 [core](./src/core) 部分代码开发。**

View File

@@ -1,11 +0,0 @@
# v1.5.4
QQ Version: Windows 9.9.11-24568 / Linux 3.2.9-23568
## 修复与优化
* 紧急修复视频与文件问题
## 新增与调整
新增的 API 详细见[API文档](https://napneko.github.io/zh-CN/develop/extends_api)

View File

@@ -1,17 +0,0 @@
# v1.3.3
QQ Version: Windows 9.9.9-23424 / Linux 3.2.7-23361
## 修复与优化
* 尝试修复多开崩溃问题
* 修复群列表更新问题
* 修复兼容性问题支持Win7
* 修复下载 http 资源缺少UA
* 优化少量消息合并转发速度
* 修复加载群通知时出现 getUserDetailInfo timeout 导致程序崩溃
## 新增与调整
* 新增设置群公告 Api: /_send_group_notice
* 新增重启实现 包括重启快速登录/普通重启 副作用: 原进程 无法清理
新增的 API 详细见[API文档](https://napneko.github.io/zh-CN/develop/extends_api)

View File

@@ -1,18 +0,0 @@
# v1.3.5
QQ Version: Windows 9.9.9-23424 / Linux 3.2.7-23361
## 修复与优化
* 优化启动脚本
* 修复非管理时群成员减少事件上报 **无法获取操作者与操作类型**
* 修复快速重启进程清理问题
* 优化配置文件格式 支持自动更新配置 但仍然建议 **备份配置**
* 修复正向反向ws多个客户端周期多次心跳问题
## 新增与调整
* 支持WebUi热重载
* 新增启动输出WEBUI秘钥
* 新增群荣誉信息 /get_group_honor_info
* 支持获取群系统消息 /get_group_system_msg
新增的 API 详细见[API文档](https://napneko.github.io/zh-CN/develop/extends_api)

View File

@@ -1,11 +0,0 @@
# v1.3.6
QQ Version: Windows 9.9.9-23424 / Linux 3.2.7-23361
## 修复与优化
* 修复戳一戳多次上报问题
## 新增与调整
新增的 API 详细见[API文档](https://napneko.github.io/zh-CN/develop/extends_api)

View File

@@ -1,15 +0,0 @@
# v1.3.8
QQ Version: Windows 9.9.9-23873 / Linux 3.2.7-23361
## 修复与优化
* 优化打包后体积问题
* 修复QQ等级获取
* 兼容 9.7.x 版本换行符 统一为 \n
* 修复处理加群请求 字段异常情况
* 修复退群通知问题
## 新增与调整
新增的 API 详细见[API文档](https://napneko.github.io/zh-CN/develop/extends_api)

View File

@@ -1,11 +0,0 @@
# v1.3.9
QQ Version: Windows 9.9.10-23873 / Linux 3.2.7-23361
## 修复与优化
* 修复QQ等级获取与兼容性问题
## 新增与调整
新增的 API 详细见[API文档](https://napneko.github.io/zh-CN/develop/extends_api)

View File

@@ -1,12 +0,0 @@
# v1.4.0
QQ Version: Windows 9.9.10-24108 / Linux 3.2.7-23361
## 修复与优化
## 新增与调整
* 支持空间Cookies获取
* 支持获取在线设备 API /get_online_clients
* 支持图片OCR API /.ocr_image /ocr_image
新增的 API 详细见[API文档](https://napneko.github.io/zh-CN/develop/extends_api)

View File

@@ -1,14 +0,0 @@
# v1.4.1
QQ Version: Windows 9.9.10-24108 / Linux 3.2.7-23361
## 修复与优化
* 提高部分Api兼容性
* 优化日志膨胀问题
* 在线状态刷新问题修复
## 新增与调整
* 支持非管理群 本地记录时间数据 (建议 **备份配置 清空配置 重新配置**)
* 新增英译中接口 Api: /translate_en2zh
* 新增群文件管理相关扩展接口 Api: /get_group_file_count /get_group_file_list /set_group_file_folder /del_group_file /del_group_file_folder
新增的 API 详细见[API文档](https://napneko.github.io/zh-CN/develop/extends_api)

View File

@@ -1,12 +0,0 @@
# v1.4.2
QQ Version: Windows 9.9.10-24108 / Linux 3.2.7-23361
## 修复与优化
* 修复获取群文件列表Api
* 修复退群通知问题
## 新增与调整
新增的 API 详细见[API文档](https://napneko.github.io/zh-CN/develop/extends_api)

View File

@@ -1,11 +0,0 @@
# v1.4.3
QQ Version: Windows 9.9.10-24108 / Linux 3.2.7-23361
## 修复与优化
* 修复名片通知
## 新增与调整
新增的 API 详细见[API文档](https://napneko.github.io/zh-CN/develop/extends_api)

View File

@@ -1,10 +0,0 @@
# v1.4.4
QQ Version: Windows 9.9.10-24108 / Linux 3.2.7-23361
## 更新
* **重大更新:**更新了版本号
新增的 API 详细见[API文档](https://napneko.github.io/zh-CN/develop/extends_api)

View File

@@ -1,12 +0,0 @@
# v1.4.5
QQ Version: Windows 9.9.10-24108 / Linux 3.2.7-23361
## 修复与优化
* 紧急修复二维扫码问题
## 新增与调整
新增的 API 详细见[API文档](https://napneko.github.io/zh-CN/develop/extends_api)

View File

@@ -1,12 +0,0 @@
# v1.4.6
QQ Version: Windows 9.9.10-24108 / Linux 3.2.7-23361
## 修复与优化
* 优化整体稳定性
## 新增与调整
新增的 API 详细见[API文档](https://napneko.github.io/zh-CN/develop/extends_api)

View File

@@ -1,11 +0,0 @@
# v1.4.7
QQ Version: Windows 9.9.10-24108 / Linux 3.2.7-23361
## 修复与优化
* 临时扩展 Api: GoCQHTTPUploadGroupFile folder_id字段 用于选择文件夹
## 新增与调整
新增的 API 详细见[API文档](https://napneko.github.io/zh-CN/develop/extends_api)

View File

@@ -1,12 +0,0 @@
# v1.4.8
QQ Version: Windows 9.9.10-24108 / Linux 3.2.7-23361
## 修复与优化
* 优化Guid的生成方式
* 支持临时消息获取群来源
## 新增与调整
新增的 API 详细见[API文档](https://napneko.github.io/zh-CN/develop/extends_api)

View File

@@ -1,11 +0,0 @@
# v1.4.9
QQ Version: Windows 9.9.10-24108 / Linux 3.2.7-23361
## 修复与优化
* 修复接口调用问题 接口标准化 APIset_group_add_request
## 新增与调整
新增的 API 详细见[API文档](https://napneko.github.io/zh-CN/develop/extends_api)

View File

@@ -1,11 +0,0 @@
# v1.5.0
QQ Version: Windows 9.9.10-24108 / Linux 3.2.7-23361
## 修复与优化
* 修正各Api默认值
## 新增与调整
新增的 API 详细见[API文档](https://napneko.github.io/zh-CN/develop/extends_api)

View File

@@ -1,12 +0,0 @@
# v1.5.1
QQ Version: Windows 9.9.10-24108 / Linux 3.2.7-23361
## 修复与优化
* 支持 新Api: set_self_profile 可设置个性签名
* 修复 Api: get_group_system_msg
* 整理日志、添加颜色、使用统一的日志函数以提高日志可读性
## 新增与调整
新增的 API 详细见[API文档](https://napneko.github.io/zh-CN/develop/extends_api)

View File

@@ -1,13 +0,0 @@
# v1.5.2
QQ Version: Windows 9.9.10-24108 / Linux 3.2.7-23361
## 修复与优化
* 替换Uid/Uin为内部实现
* 增加HttpApi调用稳定性
* 修复 GetMsg 兼容性
## 新增与调整
* 支持真正意义上的陌生人信息获取 Api: GoCQHTTP_GetStrangerInfo
新增的 API 详细见[API文档](https://napneko.github.io/zh-CN/develop/extends_api)

View File

@@ -1,15 +0,0 @@
# v1.5.3
QQ Version: Windows 9.9.11-24568 / Linux 3.2.9-23568
## 修复与优化
* 修复引用消息id问题
* 修复添加好友的通知
## 新增与调整
* 扩展群分享Json生成
* 扩展关于收藏的一系列接口
* 支持专属群头衔获取
* 支持视频获取直链
新增的 API 详细见[API文档](https://napneko.github.io/zh-CN/develop/extends_api)

View File

@@ -1,2 +0,0 @@
# 开始
jadx 跳转于 `com.tencent.qqnt.kernel.*`

View File

@@ -1,42 +0,0 @@
# Android
```java
GroupMemberExtReq groupMemberExtReq = new GroupMemberExtReq();
groupMemberExtReq.sourceType = MemberExtSourceType.TITLETYPE.ordinal();
groupMemberExtReq.groupCode = longOrNull.longValue();
groupMemberExtReq.beginUin = "0";
groupMemberExtReq.dataTime = "0";
Long[] lArr = new Long[1];
AppInterface a2 = dVar.a();
lArr[0] = Long.valueOf(a2 != null ? a2.getLongAccountUin() : 0L);
arrayListOf = CollectionsKt__CollectionsKt.arrayListOf(lArr);
groupMemberExtReq.uinList = arrayListOf;
MemberExtInfoFilter memberExtInfoFilter = new MemberExtInfoFilter();
memberExtInfoFilter.memberLevelInfoUin = 1;
memberExtInfoFilter.memberLevelInfoPoint = 1;
memberExtInfoFilter.memberLevelInfoActiveDay = 1;
memberExtInfoFilter.memberLevelInfoLevel = 1;
memberExtInfoFilter.levelName = 1;
memberExtInfoFilter.dataTime = 1;
memberExtInfoFilter.sysShowFlag = 1;
memberExtInfoFilter.userShowFlag = 1;
memberExtInfoFilter.userShowFlagNew = 1;
memberExtInfoFilter.levelNameNew = 1;
Unit unit = Unit.INSTANCE;
groupMemberExtReq.memberExtFilter = memberExtInfoFilter;
troopLevelFrequencyControl.f(troopUin, new TroopListRepo$fetchTroopLevelInfo$2(b2, groupMemberExtReq, troopUin, new com.tencent.qqnt.troopmemberlist.report.c("fetchTroopLevelInfo")));
```
# Win
参数解析位于 sub_181456A10(24108) -> wrapper.node(24108)+1456A10
IGroupService.GetMemberExt(param: object);
param展开如下
```
groupCode string
beginUin string
dataTime string
uinList Array<string>
uinNum string
groupType string
richCardNameVer string
sourceType number
memberExtFilter object// 参数解析位于 sub_18145A6D0(24108) -> wrapper.node(24108)+145A6D0
```

View File

@@ -1,48 +0,0 @@
public static final int C2C_PIC_DOWNLOAD = 1004;
public static final String C2C_PIC_DOWNLOAD_DOMAIN = "c2cpicdw.qpic.cn";
public static final String C2C_PIC_DOWNLOAD_QUIC_DOMAIN = "c2cpicdw.quic.qpic.cn";
public static final int FLAG_NOT_UPLOAD = 3;
public static final int FLAG_UPLOADINFO_ERROR = 4;
public static final int GROUP_PIC_DOWNLOAD = 1000;
public static final String GROUP_PIC_DOWNLOAD_DOMAIN = "gchat.qpic.cn";
public static final String GROUP_PIC_DOWNLOAD_QUIC_DOMAIN = "gchat.quic.qpic.cn";
public static final String GUILD_PIC_DOWNLOAD_DOMAIN = "gchat.qpic.cn/qmeetpic";
public static final boolean NEW_STORE_FLAG = true;
public static final String PTT_VIDEO_DOWNLOAD_DOMAIN = "grouptalk.c2c.qq.com";
protected static final int AVIF_DECODE_EXCEPTION = 4;
protected static final int AVIF_DECODE_FAIL = 1;
protected static final int AVIF_DECODE_FAIL_SO_FAIL = 2;
protected static final int AVIF_DECODE_FAIL_UNKNOWN = 6;
protected static final int AVIF_DECODE_FILETYPE_ERROR = 5;
protected static final int AVIF_DECODE_OOM = 3;
protected static final int AVIF_DECODE_RENAME_FAIL = 7;
protected static final int AVIF_DECODE_SUC = 0;
public static final String AVIF_FILE_SUFFIX = ".avif";
public static final int AVIF_REQ_APPRUNTIME_NULL = 12;
public static final int AVIF_REQ_CODEC_UNSURPPORT = 5;
protected static final int AVIF_REQ_DENSITY_UNSURPPORT = 10;
protected static final int AVIF_REQ_FLASH_PHOTO = 9;
protected static final int AVIF_REQ_HAS_TMP_AVIF = 7;
protected static final int AVIF_REQ_INVALID_MSG_RECORD = 2;
protected static final int AVIF_REQ_IS_RAW_PHOTO = 3;
protected static final int AVIF_REQ_OUTPUTSTREAM_UNSURPPORT = 11;
protected static final int AVIF_REQ_OVERSIZE = 6;
protected static final int AVIF_REQ_RETRY = 1;
public static final int AVIF_REQ_SO_DOWNLOAD_FAILED = 8;
protected static final int AVIF_REQ_SUC = 0;
public static final int AVIF_REQ_SWITCH_CLOSE = 4;
public static final String C2C_PIC_DOWNLOAD_ERROR_CODE = "C2CPicDownloadErrorCode";
static final int DOWNLOAD_ST_COMPLETE = 1;
static final int DOWNLOAD_ST_HEAD = 2;
static final int DOWNLOAD_ST_LEFT = 4;
static final int DOWNLOAD_ST_PART = 3;
private static final int ENCRYPT_APPID = 1600000226;
public static final String GROUP_PIC_DOWNLOAD_ERROR_CODE = "GroupPicDownloadErrorCode";
public static final String KEY_PIC_DOWNLOAD_ERROR_CODE = "param_detail_code";
protected static final int QUIC_FAIL_IP_LIST_EMPTY = 1;
protected static final int QUIC_FAIL_REQUEST_HTTPS = 3;
protected static final int QUIC_FAIL_REQUEST_QUIC = 2;
protected static final int QUIC_FAIL_SO_LOAD = 4;
public static final String REPORT_TAG_DIRECT_DOWNLOAD_FAIL = "report_direct_download_fail";
public static final String REQ_PARAM_AVIF = "tp=avif";

View File

@@ -1,444 +0,0 @@
```java
MsgConstant
int ARKSTRUCTELEMENTSUBTYPETENCENTDOCFROMMINIAPP = 1;
int ARKSTRUCTELEMENTSUBTYPETENCENTDOCFROMPLUSPANEL = 2;
int ARKSTRUCTELEMENTSUBTYPEUNKNOWN = 0;
int ATTYPEALL = 1;
int ATTYPECATEGORY = 512;
int ATTYPECHANNEL = 16;
int ATTYPEME = 4;
int ATTYPEONE = 2;
int ATTYPEONLINE = 64;
int ATTYPEROLE = 8;
int ATTYPESUMMON = 32;
int ATTYPESUMMONONLINE = 128;
int ATTYPESUMMONROLE = 256;
int ATTYPEUNKNOWN = 0;
int CALENDARELEMSUBTYPECOMMON = 3;
int CALENDARELEMSUBTYPESTRONG = 1;
int CALENDARELEMSUBTYPEUNKNOWN = 0;
int CALENDARELEMSUBTYPEWEAK = 2;
int FACEBUBBLEELEMSUBTYPENORMAL = 1;
int FACEBUBBLEELEMSUBTYPEUNKNOWN = 0;
int FETCHLONGMSGERRCODEMSGEXPIRED = 196;
int FILEELEMENTSUBTYPEAI = 16;
int FILEELEMENTSUBTYPEAPP = 11;
int FILEELEMENTSUBTYPEAUDIO = 3;
int FILEELEMENTSUBTYPEDOC = 4;
int FILEELEMENTSUBTYPEEMOTICON = 15;
int FILEELEMENTSUBTYPEEXCEL = 6;
int FILEELEMENTSUBTYPEFOLDER = 13;
int FILEELEMENTSUBTYPEHTML = 10;
int FILEELEMENTSUBTYPEIPA = 14;
int FILEELEMENTSUBTYPENORMAL = 0;
int FILEELEMENTSUBTYPEPDF = 7;
int FILEELEMENTSUBTYPEPIC = 1;
int FILEELEMENTSUBTYPEPPT = 5;
int FILEELEMENTSUBTYPEPSD = 12;
int FILEELEMENTSUBTYPETXT = 8;
int FILEELEMENTSUBTYPEVIDEO = 2;
int FILEELEMENTSUBTYPEZIP = 9;
int GRAYTIPELEMENTSUBTYPEAIOOP = 15;
int GRAYTIPELEMENTSUBTYPEBLOCK = 14;
int GRAYTIPELEMENTSUBTYPEBUDDY = 5;
int GRAYTIPELEMENTSUBTYPEBUDDYNOTIFY = 9;
int GRAYTIPELEMENTSUBTYPEEMOJIREPLY = 3;
int GRAYTIPELEMENTSUBTYPEESSENCE = 7;
int GRAYTIPELEMENTSUBTYPEFEED = 6;
int GRAYTIPELEMENTSUBTYPEFEEDCHANNELMSG = 11;
int GRAYTIPELEMENTSUBTYPEFILE = 10;
int GRAYTIPELEMENTSUBTYPEGROUP = 4;
int GRAYTIPELEMENTSUBTYPEGROUPNOTIFY = 8;
int GRAYTIPELEMENTSUBTYPEJSON = 17;
int GRAYTIPELEMENTSUBTYPELOCALMSG = 13;
int GRAYTIPELEMENTSUBTYPEPROCLAMATION = 2;
int GRAYTIPELEMENTSUBTYPEREVOKE = 1;
int GRAYTIPELEMENTSUBTYPEUNKNOWN = 0;
int GRAYTIPELEMENTSUBTYPEWALLET = 16;
int GRAYTIPELEMENTSUBTYPEXMLMSG = 12;
int INLINEKEYBOARDBUTTONRENDERSTYLEBLUEBLACKGROUND = 4;
int INLINEKEYBOARDBUTTONRENDERSTYLEBLUEBORDER = 1;
int INLINEKEYBOARDBUTTONRENDERSTYLEGRAYBORDER = 0;
int INLINEKEYBOARDBUTTONRENDERSTYLENOBORDER = 2;
int INLINEKEYBOARDBUTTONRENDERSTYLEREDCHARACTER = 3;
int INPUTSTATUSTYPECANCEL = 2;
int INPUTSTATUSTYPESPEAK = 3;
int INPUTSTATUSTYPETEXT = 1;
int KACTIVITYMSG = 22;
int KADDLOCALMSGEXTINFOTYPEPROLOGUEMSG = 1;
int KANONYMOUSATMEMSGTYPEINMSGBOX = 1001;
int KANONYMOUSFLAGFROMOTHERPEOPLE = 1;
int KANONYMOUSFLAGFROMOWN = 2;
int KANONYMOUSFLAGINVALID = 0;
int KAPPCHANNELMSG = 16;
int KATALLMSGTYPEINMSGBOX = 2000;
int KATMEMSGTYPEINMSGBOX = 1000;
int KATTRIBUTETYPEADELIEMSG = 16;
int KATTRIBUTETYPEEXTENDBUSINESS = 13;
int KATTRIBUTETYPEFEEDBACKSTATE = 17;
int KATTRIBUTETYPEGROUPHONOR = 2;
int KATTRIBUTETYPEKINGHONOR = 3;
int KATTRIBUTETYPELONGMSG = 8;
int KATTRIBUTETYPEMEMORYSTATEMSGINFO = 18;
int KATTRIBUTETYPEMSG = 0;
int KATTRIBUTETYPEMSGBOXEVENTTYPE = 14;
int KATTRIBUTETYPEPERSONAL = 1;
int KATTRIBUTETYPEPUBLICACCOUNT = 4;
int KATTRIBUTETYPEQQCONNECT = 12;
int KATTRIBUTETYPESENDMSGRSPTRANSSVRINFO = 15;
int KATTRIBUTETYPESHAREDMSGINFO = 5;
int KATTRIBUTETYPETEMPCHATGAMESESSION = 6;
int KATTRIBUTETYPETOROBOTMSG = 9;
int KATTRIBUTETYPEUININFO = 7;
int KATTRIBUTETYPEZPLAN = 11;
int KAUTOREPLYTEXTNONEINDEX = -1;
int KAVRECORDMSG = 19;
int KBUSINESSTYPGUILD = 1;
int KBUSINESSTYPNT = 0;
int KCHATTYPEADELIE = 42;
int KCHATTYPEBUDDYNOTIFY = 5;
int KCHATTYPEC2C = 1;
int KCHATTYPECIRCLE = 113;
int KCHATTYPEDATALINE = 8;
int KCHATTYPEDATALINEMQQ = 134;
int KCHATTYPEDISC = 3;
int KCHATTYPEFAV = 41;
int KCHATTYPEGAMEMESSAGE = 105;
int KCHATTYPEGAMEMESSAGEFOLDER = 116;
int KCHATTYPEGROUP = 2;
int KCHATTYPEGROUPBLESS = 133;
int KCHATTYPEGROUPGUILD = 9;
int KCHATTYPEGROUPHELPER = 7;
int KCHATTYPEGROUPNOTIFY = 6;
int KCHATTYPEGUILD = 4;
int KCHATTYPEGUILDMETA = 16;
int KCHATTYPEMATCHFRIEND = 104;
int KCHATTYPEMATCHFRIENDFOLDER = 109;
int KCHATTYPENEARBY = 106;
int KCHATTYPENEARBYASSISTANT = 107;
int KCHATTYPENEARBYFOLDER = 110;
int KCHATTYPENEARBYHELLOFOLDER = 112;
int KCHATTYPENEARBYINTERACT = 108;
int KCHATTYPEQQNOTIFY = 132;
int KCHATTYPERELATEACCOUNT = 131;
int KCHATTYPESERVICEASSISTANT = 118;
int KCHATTYPESERVICEASSISTANTSUB = 201;
int KCHATTYPESQUAREPUBLIC = 115;
int KCHATTYPESUBSCRIBEFOLDER = 30;
int KCHATTYPETEMPADDRESSBOOK = 111;
int KCHATTYPETEMPBUSSINESSCRM = 102;
int KCHATTYPETEMPC2CFROMGROUP = 100;
int KCHATTYPETEMPC2CFROMUNKNOWN = 99;
int KCHATTYPETEMPFRIENDVERIFY = 101;
int KCHATTYPETEMPNEARBYPRO = 119;
int KCHATTYPETEMPPUBLICACCOUNT = 103;
int KCHATTYPETEMPWPA = 117;
int KCHATTYPEUNKNOWN = 0;
int KCHATTYPEWEIYUN = 40;
int KCOMMONREDENVELOPEMSGTYPEINMSGBOX = 1007;
int KDOWNSOURCETYPEAIOINNER = 1;
int KDOWNSOURCETYPEBIGSCREEN = 2;
int KDOWNSOURCETYPEHISTORY = 3;
int KDOWNSOURCETYPEUNKNOWN = 0;
int KELEMTYPEACTIVITY = 25;
int KELEMTYPEACTIVITYSTATE = 41;
int KELEMTYPEACTIVITYSUBTYPECREATEMOBATEAM = 12;
int KELEMTYPEACTIVITYSUBTYPEDISBANDMOBATEAM = 11;
int KELEMTYPEACTIVITYSUBTYPEFEEDSQUARE = 10001;
int KELEMTYPEACTIVITYSUBTYPEFINISHGAME = 16;
int KELEMTYPEACTIVITYSUBTYPEFINISHMATCHTEAM = 14;
int KELEMTYPEACTIVITYSUBTYPEHOTCHAT = 10000;
int KELEMTYPEACTIVITYSUBTYPEMINIGAME = 18;
int KELEMTYPEACTIVITYSUBTYPEMUSICPLAY = 17;
int KELEMTYPEACTIVITYSUBTYPENEWSMOBA = 9;
int KELEMTYPEACTIVITYSUBTYPENOLIVE = 2;
int KELEMTYPEACTIVITYSUBTYPENOSCREENSHARE = 7;
int KELEMTYPEACTIVITYSUBTYPENOVOICE = 3;
int KELEMTYPEACTIVITYSUBTYPEONLIVE = 1;
int KELEMTYPEACTIVITYSUBTYPEONSCREENSHARE = 6;
int KELEMTYPEACTIVITYSUBTYPEONVOICE = 4;
int KELEMTYPEACTIVITYSUBTYPESTARTMATCHTEAM = 13;
int KELEMTYPEACTIVITYSUBTYPETARTGAME = 15;
int KELEMTYPEACTIVITYSUBTYPEUNKNOWN = 0;
int KELEMTYPEADELIEACTIONBAR = 44;
int KELEMTYPEADELIERECOMMENDEDMSG = 43;
int KELEMTYPEARKSTRUCT = 10;
int KELEMTYPEAVRECORD = 21;
int KELEMTYPECALENDAR = 19;
int KELEMTYPEFACE = 6;
int KELEMTYPEFACEBUBBLE = 27;
int KELEMTYPEFEED = 22;
int KELEMTYPEFILE = 3;
int KELEMTYPEGIPHY = 15;
int KELEMTYPEGRAYTIP = 8;
int KELEMTYPEINLINEKEYBOARD = 17;
int KELEMTYPEINTEXTGIFT = 18;
int KELEMTYPELIVEGIFT = 12;
int KELEMTYPEMARKDOWN = 14;
int KELEMTYPEMARKETFACE = 11;
int KELEMTYPEMULTIFORWARD = 16;
int KELEMTYPEONLINEFILE = 23;
int KELEMTYPEPIC = 2;
int KELEMTYPEPROLOGUE = 46;
int KELEMTYPEPTT = 4;
int KELEMTYPEREPLY = 7;
int KELEMTYPESHARELOCATION = 28;
int KELEMTYPESTRUCTLONGMSG = 13;
int KELEMTYPETASKTOPMSG = 29;
int KELEMTYPETEXT = 1;
int KELEMTYPETOFU = 26;
int KELEMTYPEUNKNOWN = 0;
int KELEMTYPEVIDEO = 5;
int KELEMTYPEWALLET = 9;
int KELEMTYPEYOLOGAMERESULT = 20;
int KENTERAIO = 1;
int KEXITAIO = 2;
int KFEEDBACKBUTTONTYPEDISLIKE = 2;
int KFEEDBACKBUTTONTYPELIKE = 1;
int KFEEDBACKBUTTONTYPEPROMPTCLICK = 5;
int KFEEDBACKBUTTONTYPEREGENERATE = 4;
int KFEEDBACKBUTTONTYPEUNKNOWN = 0;
int KFEEDBACKOPTLIKE = 1;
int KFEEDBACKOPTUNKNOWN = 0;
int KFEEDBACKOPTUNLIKE = 2;
int KFRIENDNEWADDEDMSGTYPEINMSGBOX = 1008;
int KGAMEBOXNEWMSGTYPEINMSGBOX = 3000;
int KGIFTATMEMSGTYPEINMSGBOX = 1005;
int KGROUPFILEATALLMSGTYPEINMSGBOX = 2001;
int KGROUPHOMEWORK = 20000;
int KGROUPHOMEWORKTASK = 20001;
int KGROUPKEYWORDMSGTYPEINMSGBOX = 2006;
int KGROUPMANNOUNCEATALLMSGTYPEINMSGBOX = 2004;
int KGROUPTASKATALLMSGTYPEINMSGBOX = 2003;
int KGROUPUNREADTYPEINMSGBOX = 2007;
int KGUILDCHANNELLIST = 10;
int KHIGHLIGHTWORDINTEMPCHATTYPEINMSGBOX = 1009;
int KHOMEWORKREMINDER = 10000;
int KLIKEORDISLIKESTATEDISLIKE = 2;
int KLIKEORDISLIKESTATELIKE = 1;
int KLIKEORDISLIKESTATENONESELECTED = 0;
int KMARKETFACE = 17;
int KMEMORYSTATEMSGTYPEADELIEWELCOME = 1;
int KMEMORYSTATEMSGTYPEUNKNOWN = 0;
int KMINIPROGRAMNOTICE = 114;
int KMSGSUBTYPEARKGROUPANNOUNCE = 3;
int KMSGSUBTYPEARKGROUPANNOUNCECONFIRMREQUIRED = 4;
int KMSGSUBTYPEARKGROUPGIFTATME = 5;
int KMSGSUBTYPEARKGROUPTASKATALL = 6;
int KMSGSUBTYPEARKMULTIMSG = 7;
int KMSGSUBTYPEARKNORMAL = 0;
int KMSGSUBTYPEARKTENCENTDOCFROMMINIAPP = 1;
int KMSGSUBTYPEARKTENCENTDOCFROMPLUSPANEL = 2;
int KMSGSUBTYPEEMOTICON = 15;
int KMSGSUBTYPEFILEAPP = 11;
int KMSGSUBTYPEFILEAUDIO = 3;
int KMSGSUBTYPEFILEDOC = 4;
int KMSGSUBTYPEFILEEXCEL = 6;
int KMSGSUBTYPEFILEFOLDER = 13;
int KMSGSUBTYPEFILEHTML = 10;
int KMSGSUBTYPEFILEIPA = 14;
int KMSGSUBTYPEFILENORMAL = 0;
int KMSGSUBTYPEFILEPDF = 7;
int KMSGSUBTYPEFILEPIC = 1;
int KMSGSUBTYPEFILEPPT = 5;
int KMSGSUBTYPEFILEPSD = 12;
int KMSGSUBTYPEFILETXT = 8;
int KMSGSUBTYPEFILEVIDEO = 2;
int KMSGSUBTYPEFILEZIP = 9;
int KMSGSUBTYPELINK = 5;
int KMSGSUBTYPEMARKETFACE = 1;
int KMSGSUBTYPEMIXEMOTICON = 7;
int KMSGSUBTYPEMIXFACE = 3;
int KMSGSUBTYPEMIXMARKETFACE = 2;
int KMSGSUBTYPEMIXPIC = 1;
int KMSGSUBTYPEMIXREPLY = 4;
int KMSGSUBTYPEMIXTEXT = 0;
int KMSGSUBTYPETENCENTDOC = 6;
int KMSGTYPEARKSTRUCT = 11;
int KMSGTYPEFACEBUBBLE = 24;
int KMSGTYPEFILE = 3;
int KMSGTYPEGIFT = 14;
int KMSGTYPEGIPHY = 13;
int KMSGTYPEGRAYTIPS = 5;
int KMSGTYPEMIX = 2;
int KMSGTYPEMULTIMSGFORWARD = 8;
int KMSGTYPENULL = 1;
int KMSGTYPEONLINEFILE = 21;
int KMSGTYPEONLINEFOLDER = 27;
int KMSGTYPEPROLOGUE = 29;
int KMSGTYPEPTT = 6;
int KMSGTYPEREPLY = 9;
int KMSGTYPESHARELOCATION = 25;
int KMSGTYPESTRUCT = 4;
int KMSGTYPESTRUCTLONGMSG = 12;
int KMSGTYPETEXTGIFT = 15;
int KMSGTYPEUNKNOWN = 0;
int KMSGTYPEVIDEO = 7;
int KMSGTYPEWALLET = 10;
int KNEEDCONFIRMGROUPMANNOUNCEATALLMSGTYPEINMSGBOX = 2005;
int KNOTPASSTHROUGHEVENTTYPEUPPERBOUNDARY = 9999;
int KPTTFORMATTYPEAMR = 0;
int KPTTFORMATTYPESILK = 1;
int KPTTTRANSLATESTATUSFAIL = 3;
int KPTTTRANSLATESTATUSSUC = 2;
int KPTTTRANSLATESTATUSTRANSLATING = 1;
int KPTTTRANSLATESTATUSUNKNOWN = 0;
int KPTTVIPLEVELTYPENONE = 0;
int KPTTVIPLEVELTYPEQQVIP = 0;
int KPTTVIPLEVELTYPESVIP = 0;
int KPTTVOICECHANGETYPEBEASTMACHINE = 7;
int KPTTVOICECHANGETYPEBOY = 2;
int KPTTVOICECHANGETYPECATCHCOLD = 13;
int KPTTVOICECHANGETYPEECHO = 5;
int KPTTVOICECHANGETYPEFATGUY = 16;
int KPTTVOICECHANGETYPEFLASHING = 9;
int KPTTVOICECHANGETYPEGIRL = 1;
int KPTTVOICECHANGETYPEHORRIBLE = 3;
int KPTTVOICECHANGETYPEKINDERGARTEN = 6;
int KPTTVOICECHANGETYPEMEDAROT = 15;
int KPTTVOICECHANGETYPENONE = 0;
int KPTTVOICECHANGETYPEOPTIMUSPRIME = 8;
int KPTTVOICECHANGETYPEOUTOFDATE = 14;
int KPTTVOICECHANGETYPEPAPI = 11;
int KPTTVOICECHANGETYPEQUICK = 4;
int KPTTVOICECHANGETYPESTUTTER = 10;
int KPTTVOICECHANGETYPETRAPPEDBEAST = 12;
int KPTTVOICETYPEINTERCOM = 1;
int KPTTVOICETYPESOUNDRECORD = 2;
int KPTTVOICETYPEUNKNOW = 0;
int KPTTVOICETYPEVOICECHANGE = 3;
int KPUBLICACCOUNTTIANSHUHIGHLIGHTWORDTYPEINMSGBOX = 1010;
int KREPLYABSELEMTYPEFACE = 2;
int KREPLYABSELEMTYPEPIC = 3;
int KREPLYABSELEMTYPETEXT = 1;
int KREPLYABSELEMTYPEUNKNOWN = 0;
int KREPLYATMEMSGTYPEINMSGBOX = 1002;
int KRMDOWNTYPEORIG = 1;
int KRMDOWNTYPETHUMB = 2;
int KRMDOWNTYPEUNKNOWN = 0;
int KRMFILETHUMBSIZE128 = 128;
int KRMFILETHUMBSIZE320 = 320;
int KRMFILETHUMBSIZE384 = 384;
int KRMFILETHUMBSIZE750 = 750;
int KRMPICAIOTHUMBSIZE = 0;
int KRMPICTHUMBSIZE198 = 198;
int KRMPICTHUMBSIZE720 = 720;
int KRMPICTYPEBMP = 3;
int KRMPICTYPECHECKOTHER = 900;
int KRMPICTYPEGIF = 2;
int KRMPICTYPEJPG = 0;
int KRMPICTYPENEWPICAPNG = 2001;
int KRMPICTYPENEWPICBMP = 1005;
int KRMPICTYPENEWPICGIF = 2000;
int KRMPICTYPENEWPICJPEG = 1000;
int KRMPICTYPENEWPICPNG = 1001;
int KRMPICTYPENEWPICPROGERSSIVJPEG = 1003;
int KRMPICTYPENEWPICSHARPP = 1004;
int KRMPICTYPENEWPICWEBP = 1002;
int KRMPICTYPEPNG = 1;
int KRMPICTYPEUNKOWN = 0;
int KRMTHUMBSIZEZERO = 0;
int KRMTRNASFERSTATUSDOWNLOADING = 3;
int KRMTRNASFERSTATUSFAIL = 5;
int KRMTRNASFERSTATUSINIT = 1;
int KRMTRNASFERSTATUSSUC = 4;
int KRMTRNASFERSTATUSUNKOW = 0;
int KRMTRNASFERSTATUSUPLOADING = 2;
int KRMTRNASFERSTATUSUSERCANCEL = 6;
int KSEEKINGPARTNERFLAGSEEKING = 1;
int KSEEKINGPARTNERFLAGUNKNOWN = 0;
int KSENDSTATUSFAILED = 0;
int KSENDSTATUSSENDING = 1;
int KSENDSTATUSSUCCESS = 2;
int KSENDSTATUSSUCCESSNOSEQ = 3;
int KSENDTYPEDROPPED = 6;
int KSENDTYPELOCAL = 3;
int KSENDTYPEOTHERDEVICE = 2;
int KSENDTYPERECV = 0;
int KSENDTYPESELF = 1;
int KSENDTYPESELFFORWARD = 4;
int KSENDTYPESELFMULTIFORWARD = 5;
int KSESSIONTYPEADDRESSBOOK = 5;
int KSESSIONTYPEC2C = 1;
int KSESSIONTYPEDISC = 3;
int KSESSIONTYPEFAV = 41;
int KSESSIONTYPEGROUP = 2;
int KSESSIONTYPEGROUPBLESS = 52;
int KSESSIONTYPEGUILD = 4;
int KSESSIONTYPEGUILDMETA = 16;
int KSESSIONTYPENEARBYPRO = 54;
int KSESSIONTYPEQQNOTIFY = 51;
int KSESSIONTYPERELATEACCOUNT = 50;
int KSESSIONTYPESERVICEASSISTANT = 19;
int KSESSIONTYPESUBSCRIBEFOLDER = 30;
int KSESSIONTYPETYPEBUDDYNOTIFY = 7;
int KSESSIONTYPETYPEGROUPHELPER = 9;
int KSESSIONTYPETYPEGROUPNOTIFY = 8;
int KSESSIONTYPEUNKNOWN = 0;
int KSESSIONTYPEWEIYUN = 40;
int KSPECIALCAREMSGTYPEINMSGBOX = 1006;
int KSPECIFIEDREDENVELOPEATMEMSGTYPEINMSGBOX = 1004;
int KSPECIFIEDREDENVELOPEATONEMSGTYPEINMSGBOX = 1003;
int KTENCENTDOCTYPEADDON = 110;
int KTENCENTDOCTYPEDOC = 0;
int KTENCENTDOCTYPEDRAWING = 89;
int KTENCENTDOCTYPEDRIVE = 101;
int KTENCENTDOCTYPEFILE = 100;
int KTENCENTDOCTYPEFLOWCHART = 91;
int KTENCENTDOCTYPEFOLDER = 3;
int KTENCENTDOCTYPEFORM = 2;
int KTENCENTDOCTYPEMIND = 90;
int KTENCENTDOCTYPENOTES = 5;
int KTENCENTDOCTYPEPDF = 6;
int KTENCENTDOCTYPEPROGRAM = 7;
int KTENCENTDOCTYPESHEET = 1;
int KTENCENTDOCTYPESLIDE = 4;
int KTENCENTDOCTYPESMARTCANVAS = 8;
int KTENCENTDOCTYPESMARTSHEET = 9;
int KTENCENTDOCTYPESPEECH = 102;
int KTENCENTDOCTYPEUNKNOWN = 10;
int KTOFURECORDMSG = 23;
int KTOPMSGTYPETASK = 1;
int KTOPMSGTYPEUNKNOWN = 0;
int KTRIGGERTYPEAUTO = 1;
int KTRIGGERTYPEMANUAL = 0;
int KUNKNOWN = 0;
int KUNKNOWNTYPEINMSGBOX = 0;
int KUNREADCNTUPTYPEALLDIRECTSESSION = 4;
int KUNREADCNTUPTYPEALLFEEDSINGUILD = 6;
int KUNREADCNTUPTYPEALLGUILD = 3;
int KUNREADCNTUPTYPECATEGORY = 5;
int KUNREADCNTUPTYPECHANNEL = 1;
int KUNREADCNTUPTYPECONTACT = 0;
int KUNREADCNTUPTYPEGUILD = 2;
int KUNREADCNTUPTYPEGUILDGROUP = 7;
int KUNREADSHOWTTYPEGRAYPOINT = 2;
int KUNREADSHOWTYPEREDPOINT = 1;
int KUNREADSHOWTYPESMALLGRAYPOINT = 4;
int KUNREADSHOWTYPESMALLREDPOINT = 3;
int KUNREADSHOWTYPEUNKNOWN = 0;
int KVASGIFTCOINTYPECOIN = 0;
int KVASGIFTCOINTYPEMARKETCOIN = 1;
int KYOLOGAMERESULTMSG = 18;
int PIC_800_RECOMMENDED = 7;
int PIC_AIGC_EMOJI = 14;
int PIC_ALBUM_GIF = 11;
int PIC_COMMERCIAL_ADVERTISING = 9;
int PIC_FIND = 10;
int PIC_HOT = 2;
int PIC_HOT_EMOJI = 13;
int PIC_NORMAL = 0;
int PIC_PK = 3;
int PIC_QQZONE = 5;
int PIC_SELFIE_GIF = 8;
int PIC_SEND_FROM_TAB_SEARCH_BOX = 12;
int PIC_USER = 1;
int PIC_WISDOM_FIGURE = 4;
int REPLYORIGINALMSGSTATEHASRECALL = 1;
int REPLYORIGINALMSGSTATEUNKNOWN = 0;
int SHARELOCATIONELEMSUBTYPENORMAL = 1;
int SHARELOCATIONELEMSUBTYPEUNKNOWN = 0;
int TEXTELEMENTSUBTYPELINK = 1;
int TEXTELEMENTSUBTYPETENCENTDOC = 2;
int TEXTELEMENTSUBTYPEUNKNOWN = 0;
```

View File

@@ -1,24 +0,0 @@
# 前排提示
由于Core未处于开源非组织人员无法参与Core开发此处为Core开发提示
# 准备工具
frida ida-pro jadx x64dbg ce 内部调试脚本
## ida-pro
1. 用于快速分析入参和返回类型
2. 通过静态QLog推测语义
3. 提取Listener与Service (常用)
## frida
1. 用于动态获取QLog推测语义
2. 捕捉Native函数 实际入参与数据 分析中间流程
## jadx
1. 通过其它平台实现 静态获取QLog推测语义
2. 提供部分未调用代码 参考
## x64dbg
1. 验证IDA的Hook点
## 内部脚本
1. 提取Listener与Service (不调用无类型 不推荐)
2. 获取NT调用流程

33
manifest.json Normal file
View File

@@ -0,0 +1,33 @@
{
"manifest_version": 4,
"type": "extension",
"name": "NapCatQQ",
"slug": "NapCat.Framework",
"description": "高性能的 OneBot 11 协议实现",
"version": "2.0.29",
"icon": "./logo.png",
"authors": [
{
"name": "MliKiowa",
"link": "https://github.com/MliKiowa"
},
{
"name": "Young",
"link": "https://github.com/Wesley-Young"
}
],
"repository": {
"repo": "NapNeko/NapCatQQ",
"branch": "main"
},
"platform": [
"win32",
"linux",
"darwin"
],
"injects": {
"renderer": "./renderer.js",
"main": "./liteloader.cjs",
"preload": "./preload.cjs"
}
}

View File

@@ -2,22 +2,19 @@
"name": "napcat",
"private": true,
"type": "module",
"version": "1.5.4",
"version": "2.0.29",
"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:framework": "vite build --mode framework",
"build:shell": "vite build --mode shell",
"build:webui": "cd ./src/webui && vite build",
"watch": "npm run watch:dev",
"debug-win": "powershell dist/napcat.ps1",
"lint": "eslint --fix src/**/*.{js,ts}",
"release": "npm run build:prod",
"depend": "cd dist && npm install --omit=dev"
},
"devDependencies": {
"@babel/core": "^7.24.7",
"@babel/plugin-proposal-class-properties": "^7.18.6",
"@babel/plugin-proposal-decorators": "^7.24.7",
"@babel/preset-typescript": "^7.24.7",
"@log4js-node/log4js-api": "^1.0.2",
"@protobuf-ts/plugin": "^2.9.4",
"@rollup/plugin-node-resolve": "^15.2.3",
@@ -26,10 +23,10 @@
"@types/express": "^4.17.21",
"@types/figlet": "^1.5.8",
"@types/fluent-ffmpeg": "^2.1.24",
"@types/node": "^20.11.30",
"@types/jest": "^29.5.12",
"@types/node": "^22.0.1",
"@types/qrcode-terminal": "^0.12.2",
"@types/uuid": "^9.0.8",
"@types/ws": "^8.5.10",
"@types/ws": "^8.5.12",
"@typescript-eslint/eslint-plugin": "^7.4.0",
"@typescript-eslint/parser": "^7.4.0",
"eslint": "^8.57.0",
@@ -42,14 +39,16 @@
"rollup-plugin-obfuscator": "^1.1.0",
"typescript": "^5.3.3",
"vite": "^5.2.6",
"vite-plugin-babel": "^1.2.0",
"vite-plugin-cp": "^4.0.8",
"vite-plugin-dts": "^3.8.2",
"vite-tsconfig-paths": "^4.3.2"
},
"dependencies": {
"ajv": "^8.13.0",
"async-mutex": "^0.5.0",
"chalk": "^5.3.0",
"commander": "^12.0.0",
"commander": "^12.1.0",
"cors": "^2.8.5",
"express": "^5.0.0-beta.2",
"fast-xml-parser": "^4.3.6",
@@ -59,9 +58,8 @@
"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": "^10.0.0",
"ws": "^8.16.0"
"silk-wasm": "^3.6.1",
"strtok3": "8.0.1",
"ws": "^8.18.0"
}
}

View File

@@ -1,3 +1,5 @@
# Dont Use This Script
# 2024.7.3
function Get-QQpath {
try {
$key = Get-ItemProperty -Path "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\QQ"
@@ -40,4 +42,4 @@ if (!(Test-Path $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}"
Start-Process powershell -ArgumentList "-noexit", "-noprofile", "-command &{& chcp 65001;& '$($commandInfo.Path)' --enable-logging }"

90
script/BootWay05.bat Normal file
View File

@@ -0,0 +1,90 @@
@echo off
REM 检查当前会话是否具有管理员权限
openfiles >nul 2>&1
if %errorlevel% neq 0 (
REM 如果不是管理员,则重新启动脚本以管理员模式运行
echo 请求管理员权限...
powershell -Command "Start-Process cmd -ArgumentList '/c %~f0 %*' -Verb RunAs"
exit /b
)
REM 设置当前工作目录
cd /d %~dp0
REM 获取当前目录路径
set currentPath=%cd%
set currentPath=%currentPath:\=/%
REM 生成JavaScript代码
set "jsCode=(async () =^>await import('file:///%currentPath%/napcat.mjs'))();"
REM 将JavaScript代码保存到文件中
echo %jsCode% > loadScript.js
echo JavaScript code has been generated and saved to loadScript.js
REM 设置NAPCAT_PATH环境变量为 当前目录的loadScript.js地址
set NAPCAT_PATH=%cd%\loadScript.js
REM 获取QQ路径
:loop_read
for /f "tokens=2*" %%a in ('reg query "HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\QQ" /v "UninstallString"') do (
set RetString=%%b
goto :napcat_boot
)
:napcat_boot
for %%a in (%RetString%) do (
set "pathWithoutUninstall=%%~dpa"
)
SET QQPath=%pathWithoutUninstall%QQ.exe
REM 拿不到QQ路径则退出
if not exist "%QQpath%" (
echo provided QQ path is invalid: %QQpath%
pause
exit /b
)
REM 收集dbghelp.dll路径和HASH信息
set QQdir=%~dp0
set oldDllPath=%QQdir%dbghelp.dll
set newDllPath=%currentPath%\dbghelp.dll
for /f "tokens=*" %%A in ('certutil -hashfile "%oldDllPath%" MD5') do (
if not defined oldDllHash set oldDllHash=%%A
)
for /f "tokens=*" %%A in ('certutil -hashfile "%newDllPath%" MD5') do (
if not defined newDllHash set newDllHash=%%A
)
REM 如果文件一致则跳过
if "%oldDllHash%" neq "%newDllHash%" (
tasklist /fi "imagename eq QQ.exe" 2>nul | find /i "QQ.exe" >nul
if %errorlevel% equ 0 (
REM 文件占用则退出
echo dbghelp.dll is in use, cannot continue.
) else (
REM 文件未占用则尝试覆盖
copy /y "%newDllPath%" "%oldDllPath%"
if %errorlevel% neq 0 (
echo Failed to copy dbghelp.dll
pause
exit /b
) else (
echo dbghelp.dll has been copied to %QQdir%
)
)
)
REM 带参数启动QQ
REM 判断wt是否存在存在则通过wt启动不存在则通过cmd启动
REM %QQPath% --enable-logging %*
where wt >nul 2>nul
if %errorlevel% equ 0 (
wt "cmd" /c "%QQPath%" --enable-logging %*
) else (
"%QQPath%" --enable-logging %*
)

123
script/BootWay05.ps1 Normal file
View File

@@ -0,0 +1,123 @@
# 检查当前会话是否具有管理员权限
function Test-Administrator {
$user = [Security.Principal.WindowsIdentity]::GetCurrent()
(New-Object Security.Principal.WindowsPrincipal $user).IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)
}
if (-not (Test-Administrator)) {
# 如果不是管理员,则重新启动脚本以管理员模式运行
$scriptPath = $myInvocation.MyCommand.Path
if (-not $scriptPath) {
$scriptPath = $PSCommandPath
}
$newProcess = New-Object System.Diagnostics.ProcessStartInfo "powershell";
$newProcess.Arguments = "-File `"$scriptPath`" $args"
$newProcess.Verb = "runas";
[System.Diagnostics.Process]::Start($newProcess);
exit
}
function Get-QQpath {
try {
$key = Get-ItemProperty -Path "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\QQ"
$uninstallString = $key.UninstallString
return [System.IO.Path]::GetDirectoryName($uninstallString) + "\QQ.exe"
}
catch {
throw "get QQ path error: $_"
}
}
function Select-QQPath {
Add-Type -AssemblyName System.Windows.Forms
[System.Windows.Forms.Application]::EnableVisualStyles()
$dialogTitle = "Select QQ.exe"
$filePicker = New-Object System.Windows.Forms.OpenFileDialog
$filePicker.Title = $dialogTitle
$filePicker.Filter = "Executable Files (*.exe)|*.exe|All Files (*.*)|*.*"
$filePicker.FilterIndex = 1
$null = $filePicker.ShowDialog()
if (-not ($filePicker.FileName)) {
throw "User did not select an .exe file."
}
return $filePicker.FileName
}
# 设置当前工作目录
$scriptDirectory = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent
Set-Location $scriptDirectory
# 获取当前目录路径
$currentPath = Get-Location
# 替换\为/
$currentPath = $currentPath -replace '\\', '/'
# 生成JavaScript代码
$jsCode = @"
(async () => {
await import('file:///$currentPath/napcat.mjs');
})();
"@
# 将JavaScript代码保存到文件中
$jsFilePath = Join-Path $currentPath "loadScript.js"
$jsCode | Out-File -FilePath $jsFilePath -Encoding UTF8
Write-Output "JavaScript code has been generated and saved to $jsFilePath"
# 设置NAPCAT_PATH环境变量为 当前目录的loadScript.js地址
$env:NAPCAT_PATH = $jsFilePath
$params = $args -join " "
Try {
$QQpath = Get-QQpath
}
Catch {
$QQpath = Select-QQPath
}
# 拿不到QQ路径则退出
if (!(Test-Path $QQpath)) {
Write-Output "provided QQ path is invalid: $QQpath"
Read-Host "Press any key to continue..."
exit
}
$commandInfo = Get-Command $QQpath -ErrorAction Stop
# 收集dbghelp.dll路径和HASH信息
$QQpath = Split-Path $QQpath
$oldDllPath = Join-Path $QQpath "dbghelp.dll"
$oldDllHash = Get-FileHash $oldDllPath -Algorithm MD5
$newDllPath = Join-Path $currentPath "dbghelp.dll"
$newDllHash = Get-FileHash $newDllPath -Algorithm MD5
# 如果文件一致则跳过
if ($oldDllHash.Hash -ne $newDllHash.Hash) {
$processes = Get-Process -Name QQ -ErrorAction SilentlyContinue
if ($processes) {
# 文件占用则退出
Write-Output "dbghelp.dll is in use by the following processes:"
$processes | ForEach-Object { Write-Output "$($_.Id) $($_.Name) $($_.Path)" }
Write-Output "dbghelp.dll is in use, cannot continue."
Read-Host "Press any key to continue..."
exit
} else {
# 文件未占用则尝试覆盖
try {
Copy-Item -Path "$newDllPath" -Destination "$oldDllPath" -Force
Write-Output "dbghelp.dll has been copied to $QQpath"
} catch {
Write-Output "Failed to copy dbghelp.dll: $_"
Read-Host "Press any key to continue..."
exit
}
}
}
# 带参数启动QQ
try {
Start-Process powershell -ArgumentList '-noexit', '-noprofile', "-command &{& chcp 65001;& '$($commandInfo.Path)' --enable-logging $params}" -NoNewWindow -ErrorAction Stop
} catch {
Write-Output "Failed to start process as administrator: $_"
Read-Host "Press any key to continue..."
}

93
script/BootWay05.utf8.bat Normal file
View File

@@ -0,0 +1,93 @@
@echo off
REM 检查当前会话是否具有管理员权限
openfiles >nul 2>&1
if %errorlevel% neq 0 (
REM 如果不是管理员,则重新启动脚本以管理员模式运行
echo 请求管理员权限...
where wt >nul 2>nul
if %errorlevel% equ 0 (
powershell -Command "Start-Process cmd -ArgumentList ' /c %~f0 %*' -Verb RunAs"
) else (
powershell -Command "Start-Process wt -ArgumentList 'cmd /c %~f0 %*' -Verb RunAs"
)
REM wt "cmd" /c "%~f0 %*"
exit /b
)
REM 设置当前工作目录
cd /d %~dp0
REM 获取当前目录路径
set currentPath=%cd%
set currentPath=%currentPath:\=/%
REM 生成JavaScript代码
set "jsCode=(async () =^>await import('file:///%currentPath%/napcat.mjs'))();"
REM 将JavaScript代码保存到文件中
echo %jsCode% > loadScript.js
echo JavaScript code has been generated and saved to loadScript.js
REM 设置NAPCAT_PATH环境变量为 当前目录的loadScript.js地址
set NAPCAT_PATH=%cd%\loadScript.js
REM 获取QQ路径
:loop_read
for /f "tokens=2*" %%a in ('reg query "HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\QQ" /v "UninstallString"') do (
set RetString=%%b
goto :napcat_boot
)
:napcat_boot
for %%a in (%RetString%) do (
set "pathWithoutUninstall=%%~dpa"
)
SET QQPath=%pathWithoutUninstall%QQ.exe
REM 拿不到QQ路径则退出
if not exist "%QQpath%" (
echo provided QQ path is invalid: %QQpath%
pause
exit /b
)
REM 收集dbghelp.dll路径和HASH信息
set QQdir=%~dp0
set oldDllPath=%QQdir%dbghelp.dll
set newDllPath=%currentPath%\dbghelp.dll
for /f "tokens=*" %%A in ('certutil -hashfile "%oldDllPath%" MD5') do (
if not defined oldDllHash set oldDllHash=%%A
)
for /f "tokens=*" %%A in ('certutil -hashfile "%newDllPath%" MD5') do (
if not defined newDllHash set newDllHash=%%A
)
REM 如果文件一致则跳过
if "%oldDllHash%" neq "%newDllHash%" (
tasklist /fi "imagename eq QQ.exe" 2>nul | find /i "QQ.exe" >nul
if %errorlevel% equ 0 (
REM 文件占用则退出
echo dbghelp.dll is in use, cannot continue.
) else (
REM 文件未占用则尝试覆盖
copy /y "%newDllPath%" "%oldDllPath%"
if %errorlevel% neq 0 (
echo Failed to copy dbghelp.dll
pause
exit /b
) else (
echo dbghelp.dll has been copied to %QQdir%
)
)
)
REM 带参数启动QQ
REM 判断wt是否存在存在则通过wt启动不存在则通过cmd启动
REM %QQPath% --enable-logging %*
chcp 65001
"%QQPath%" --enable-logging %*

77
script/BootWay05_init.bat Normal file
View File

@@ -0,0 +1,77 @@
@echo off
REM Check if the script is running as administrator
openfiles >nul 2>&1
if %errorlevel% neq 0 (
REM If not, restart the script in administrator mode
echo Requesting administrator privileges...
powershell -Command "Start-Process cmd -ArgumentList '/c %~f0 %*' -Verb RunAs"
exit /b
)
cd /d %~dp0
set currentPath=%cd%
set currentPath=%currentPath:\=/%
REM Generate JavaScript code
set "jsCode=(async () =^>await import('file:///%currentPath%/napcat.mjs'))();"
REM Save JavaScript code to a file
echo %jsCode% > loadScript.js
echo JavaScript code has been generated and saved to loadScript.js
REM Set NAPCAT_PATH environment variable to the address of loadScript.js in the current directory
set NAPCAT_PATH=%cd%\loadScript.js
REM Get QQ path and cache it
: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"
)
set "pathWithoutUninstall=%RetString:Uninstall.exe=%"
SET QQPath=%pathWithoutUninstall%QQ.exe
echo %QQPath%>qq_path_cache.txt
echo QQ path %QQPath% has been cached to qq_path_cache.txt
REM Exit if QQ path is invalid
if not exist "%QQpath%" (
echo provided QQ path is invalid: %QQpath%
pause
exit /b
)
REM Collect dbghelp.dll path and HASH information
set QQdir=%~dp0
set oldDllPath=%QQdir%dbghelp.dll
set newDllPath=%currentPath%\dbghelp.dll
for /f "tokens=*" %%A in ('certutil -hashfile "%oldDllPath%" MD5') do (
if not defined oldDllHash set oldDllHash=%%A
)
for /f "tokens=*" %%A in ('certutil -hashfile "%newDllPath%" MD5') do (
if not defined newDllHash set newDllHash=%%A
)
REM Compare the HASH of the old and new dbghelp.dll, and replace the old one if they are different
if "%oldDllHash%" neq "%newDllHash%" (
tasklist /fi "imagename eq QQ.exe" 2>nul | find /i "QQ.exe" >nul
if %errorlevel% equ 0 (
REM If the file is in use, prompt the user to close QQ
echo dbghelp.dll is in use, please close QQ first.
) else (
copy /y "%newDllPath%" "%oldDllPath%"
if %errorlevel% neq 0 (
echo Copy dbghelp.dll failed, please check and try again.
pause
exit /b
) else (
echo dbghelp.dll has been updated.
echo Please run BootWay05_run.bat to start QQ.
echo If you update QQ in the future, please run BootWay05_init.bat again.
pause
exit /b
)
)
)

10
script/BootWay05_run.bat Normal file
View File

@@ -0,0 +1,10 @@
@echo off
set /p QQPath=<qq_path_cache.txt
echo QQ path %QQPath% has been read from qq_path_cache.txt
echo If failed to start QQ, please try running this script in administrator mode.
set NAPCAT_PATH=%cd%\loadScript.js
REM Launch QQ.exe with params provided
"%QQPath%" --enable-logging %*

View File

@@ -0,0 +1,13 @@
@echo off
chcp 65001
set /p QQPath=<qq_path_cache.txt
echo QQ path %QQPath% has been read from qq_path_cache.txt
echo If failed to start QQ, please try running this script in administrator mode.
set NAPCAT_PATH=%cd%\loadScript.js
REM Launch QQ.exe with params provided
"%QQPath%" --enable-logging %*

View File

@@ -11,7 +11,7 @@ try {
// 验证 targetVersion 格式
if (!targetVersion || typeof targetVersion !== 'string') {
console.error("[NapCat] [CheckVersion] 目标版本格式不正确或未设置!");
console.log("[NapCat] [CheckVersion] 目标版本格式不正确或未设置!");
return;
}
@@ -38,5 +38,5 @@ try {
writeScriptToFile(safeScriptContent);
}
} catch (error) {
console.error("[NapCat] [CheckVersion] 检测过程中发生错误:", error);
console.log("[NapCat] [CheckVersion] 检测过程中发生错误:", error);
}

View File

@@ -1,32 +0,0 @@
let fs = require('fs');
let path = require('path');
const coreDistDir = path.join(path.resolve(__dirname, '../'), 'src/core/dist/core/src');
const coreLibDir = path.join(path.resolve(__dirname, '../'), 'src/core.lib/src');
function copyDir(currentPath, outputDir) {
fs.readdir(currentPath, { withFileTypes: true }, (err, entries) => {
if (err?.errno === -4058) return;
entries.forEach(entry => {
const localBasePath = path.join(currentPath, entry.name);
const outputLocalBasePath = path.join(outputDir, entry.name);
if (entry.isDirectory()) {
// 如果是目录,递归调用
if (!fs.existsSync(outputLocalBasePath)) {
fs.mkdirSync(outputLocalBasePath, { recursive: true });
}
copyDir(localBasePath, outputLocalBasePath);
}
else{
// 如果是文件,直接复制
fs.copyFile(localBasePath, outputLocalBasePath, (err) => {
if (err) throw err;
});
}
});
});
}
copyDir(coreDistDir, coreLibDir);

BIN
script/dbghelp.dll Normal file

Binary file not shown.

View File

@@ -1,21 +0,0 @@
import fs from 'fs'
import path from 'path'
import { version } from '../src/onebot11/version'
const manifestPath = path.join(__dirname, '../package.json')
function readManifest (): any {
if (fs.existsSync(manifestPath)) {
return JSON.parse(fs.readFileSync(manifestPath, 'utf-8'))
}
}
function writeManifest (manifest: any) {
fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2))
}
const manifest = readManifest()
if (version !== manifest.version) {
manifest.version = version
writeManifest(manifest)
}

View File

@@ -1,3 +0,0 @@
chcp 65001
set ELECTRON_RUN_AS_NODE=1
"H:\Program Files\QQNT最新版\QQ.exe" %~dp0/napcat.cjs %*

View File

@@ -1,15 +0,0 @@
function Get-QQpath {
try {
$key = Get-ItemProperty -Path "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\QQ"
$uninstallString = $key.UninstallString
return [System.IO.Path]::GetDirectoryName($uninstallString) + "\QQ.exe"
}
catch {
throw "Error getting UninstallString: $_"
}
}
$params = $args -join " "
$QQpath = Get-QQpath
$Bootfile = Join-Path (Get-Location) "\dist\inject.cjs"
$env:ELECTRON_RUN_AS_NODE = 1
Start-Process powershell -ArgumentList "-noexit", "-noprofile", "-command &{& '$QQpath' --expose-gc $Bootfile $params}"

View File

@@ -1,17 +0,0 @@
function Get-QQpath {
try {
$key = Get-ItemProperty -Path "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\QQ"
$uninstallString = $key.UninstallString
return [System.IO.Path]::GetDirectoryName($uninstallString) + "\QQ.exe"
}
catch {
return "D:\QQ.exe"
}
}
$params = $args -join " "
$QQpath = Get-QQpath
$Bootfile = Join-Path $PSScriptRoot "napcat.cjs"
$env:ELECTRON_RUN_AS_NODE = 1
$argumentList = '-noexit', '-noprofile', "-command `"$QQpath`" `"$Bootfile`" $params"
Start-Process powershell -ArgumentList $argumentList -RedirectStandardOutput "log.txt" -RedirectStandardError "error.txt"
powershell Get-Content -Wait -Encoding UTF8 log.txt

View File

@@ -1,18 +0,0 @@
@echo off
setlocal enabledelayedexpansion
chcp 65001
:loop_read
for /f "tokens=2*" %%a in ('reg query "HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\QQ" /v "UninstallString"') do (
set "RetString=%%b"
goto :napcat_boot
)
:napcat_boot
for %%a in ("!RetString!") do (
set "pathWithoutUninstall=%%~dpa"
)
set "QQPath=!pathWithoutUninstall!QQ.exe"
set ELECTRON_RUN_AS_NODE=1
echo !QQPath!
"!QQPath!" ./napcat.mjs %*

View File

@@ -1,17 +0,0 @@
@echo off
setlocal enabledelayedexpansion
:loop_read
for /f "tokens=2*" %%a in ('reg query "HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\QQ" /v "UninstallString"') do (
set "RetString=%%b"
goto :napcat_boot
)
:napcat_boot
for %%a in ("!RetString!") do (
set "pathWithoutUninstall=%%~dpa"
)
set "QQPath=!pathWithoutUninstall!QQ.exe"
set ELECTRON_RUN_AS_NODE=1
echo !QQPath!
"!QQPath!" ./napcat.mjs %*

View File

@@ -1,43 +0,0 @@
function Get-QQpath {
try {
$key = Get-ItemProperty -Path "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\QQ"
$uninstallString = $key.UninstallString
return [System.IO.Path]::GetDirectoryName($uninstallString) + "\QQ.exe"
}
catch {
throw "get QQ path error: $_"
}
}
function Select-QQPath {
Add-Type -AssemblyName System.Windows.Forms
[System.Windows.Forms.Application]::EnableVisualStyles()
$dialogTitle = "Select QQ.exe"
$filePicker = New-Object System.Windows.Forms.OpenFileDialog
$filePicker.Title = $dialogTitle
$filePicker.Filter = "Executable Files (*.exe)|*.exe|All Files (*.*)|*.*"
$filePicker.FilterIndex = 1
$null = $filePicker.ShowDialog()
if (-not ($filePicker.FileName)) {
throw "User did not select an .exe file."
}
return $filePicker.FileName
}
$params = $args -join " "
Try {
$QQpath = Get-QQpath
}
Catch {
$QQpath = Select-QQPath
}
if (!(Test-Path $QQpath)) {
throw "provided QQ path is invalid: $QQpath"
}
$Bootfile = Join-Path $PSScriptRoot "napcat.mjs"
$env:ELECTRON_RUN_AS_NODE = 1
$commandInfo = Get-Command $QQpath -ErrorAction Stop
Start-Process powershell -ArgumentList "-noexit", "-noprofile", "-command &{& '$($commandInfo.Path)' $Bootfile $params}"

View File

@@ -1,21 +0,0 @@
#!/bin/bash
get_script_dir() {
local script_path="${1:-$0}"
local script_dir
script_path=$(readlink -f "$script_path")
script_dir=$(dirname "$script_path")
echo "$script_dir"
}
SCRIPT_DIR=$(get_script_dir)
export ELECTRON_RUN_AS_NODE=1
if ! [ -x /opt/QQ/qq ]; then
echo "Error: /opt/QQ/qq is not executable or does not exist." >&2
exit 1
fi
/opt/QQ/qq "${SCRIPT_DIR}/napcat.mjs" "$@"

View File

@@ -0,0 +1,263 @@
import { NodeIQQNTWrapperSession } from '@/core/wrapper/wrapper';
import { randomUUID } from 'crypto';
interface Internal_MapKey {
timeout: number;
createtime: number;
func: (...arg: any[]) => any;
checker: ((...args: any[]) => boolean) | undefined;
}
export type ListenerClassBase = Record<string, string>;
export interface ListenerIBase {
// eslint-disable-next-line @typescript-eslint/no-misused-new
new(listener: any): ListenerClassBase;
}
export class LegacyNTEventWrapper {
private listenerMapping: Record<string, ListenerIBase>; //ListenerName-Unique -> Listener构造函数
private WrapperSession: NodeIQQNTWrapperSession | undefined; //WrapperSession
private listenerManager: Map<string, ListenerClassBase> = new Map<string, ListenerClassBase>(); //ListenerName-Unique -> Listener实例
private EventTask = new Map<string, Map<string, Map<string, Internal_MapKey>>>(); //tasks ListenerMainName -> ListenerSubName-> uuid -> {timeout,createtime,func}
constructor(
listenerMapping: Record<string, ListenerIBase>,
wrapperSession: NodeIQQNTWrapperSession,
) {
this.listenerMapping = listenerMapping;
this.WrapperSession = wrapperSession;
}
createProxyDispatch(ListenerMainName: string) {
// eslint-disable-next-line @typescript-eslint/no-this-alias
const current = this;
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);
},
},
);
}
createEventFunction<T extends (...args: any) => any>(eventName: string): T | undefined {
const eventNameArr = eventName.split('/');
type eventType = {
[key: string]: () => { [key: string]: (...params: Parameters<T>) => Promise<ReturnType<T>> };
};
if (eventNameArr.length > 1) {
const serviceName = 'get' + eventNameArr[0].replace('NodeIKernel', '');
const eventName = eventNameArr[1];
//getNodeIKernelGroupListener,GroupService
//console.log('2', eventName);
const services = (this.WrapperSession as unknown as eventType)[serviceName]();
let event = services[eventName];
//重新绑定this
event = event.bind(services);
if (event) {
return event as T;
}
return undefined;
}
}
createListenerFunction<T>(listenerMainName: string, uniqueCode: string = ''): T {
const ListenerType = this.listenerMapping![listenerMainName];
let Listener = this.listenerManager.get(listenerMainName + uniqueCode);
if (!Listener && ListenerType) {
Listener = new ListenerType(this.createProxyDispatch(listenerMainName));
const ServiceSubName = listenerMainName.match(/^NodeIKernel(.*?)Listener$/)![1];
const Service = 'NodeIKernel' + ServiceSubName + 'Service/addKernel' + ServiceSubName + 'Listener';
const addfunc = this.createEventFunction<(listener: T) => number>(Service);
addfunc!(Listener as T);
//console.log(addfunc!(Listener as T));
this.listenerManager.set(listenerMainName + uniqueCode, Listener);
}
return Listener as T;
}
//统一回调清理事件
async dispatcherListener(ListenerMainName: string, ListenerSubName: string, ...args: any[]) {
//console.log("[EventDispatcher]",ListenerMainName, ListenerSubName, ...args);
this.EventTask.get(ListenerMainName)
?.get(ListenerSubName)
?.forEach((task, uuid) => {
//console.log(task.func, uuid, task.createtime, task.timeout);
if (task.createtime + task.timeout < Date.now()) {
this.EventTask.get(ListenerMainName)?.get(ListenerSubName)?.delete(uuid);
return;
}
if (task.checker && task.checker(...args)) {
task.func(...args);
}
});
}
async callNoListenerEvent<EventType extends (...args: any[]) => Promise<any> | any>(
EventName = '',
timeout: number = 3000,
...args: Parameters<EventType>
) {
return new Promise<Awaited<ReturnType<EventType>>>(async (resolve, reject) => {
const EventFunc = this.createEventFunction<EventType>(EventName);
let complete = false;
setTimeout(() => {
if (!complete) {
reject(new Error('NTEvent EventName:' + EventName + ' timeout'));
}
}, timeout);
const retData = await EventFunc!(...args);
complete = true;
resolve(retData);
});
}
async RegisterListen<ListenerType extends (...args: any[]) => void>(
ListenerName = '',
waitTimes = 1,
timeout = 5000,
checker: (...args: Parameters<ListenerType>) => boolean,
) {
return new Promise<Parameters<ListenerType>>((resolve, reject) => {
const ListenerNameList = ListenerName.split('/');
const ListenerMainName = ListenerNameList[0];
const ListenerSubName = ListenerNameList[1];
const id = randomUUID();
let complete = 0;
let retData: Parameters<ListenerType> | undefined = undefined;
const databack = () => {
if (complete == 0) {
reject(new Error(' ListenerName:' + ListenerName + ' timeout'));
} else {
resolve(retData!);
}
};
const timeoutRef = setTimeout(databack, timeout);
const eventCallbak = {
timeout: timeout,
createtime: Date.now(),
checker: checker,
func: (...args: Parameters<ListenerType>) => {
complete++;
retData = args;
if (complete >= waitTimes) {
clearTimeout(timeoutRef);
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.createListenerFunction(ListenerMainName);
});
}
async CallNormalEvent<
EventType extends (...args: any[]) => Promise<any>,
ListenerType extends (...args: any[]) => void
>(
EventName = '',
ListenerName = '',
waitTimes = 1,
timeout: number = 3000,
checker: (...args: Parameters<ListenerType>) => boolean,
...args: Parameters<EventType>
) {
return new Promise<[EventRet: Awaited<ReturnType<EventType>>, ...Parameters<ListenerType>]>(
async (resolve, reject) => {
const id = randomUUID();
let complete = 0;
let retData: Parameters<ListenerType> | undefined = undefined;
let retEvent: any = {};
const databack = () => {
if (complete == 0) {
reject(
new Error(
'Timeout: NTEvent EventName:' +
EventName +
' ListenerName:' +
ListenerName +
' EventRet:\n' +
JSON.stringify(retEvent, null, 4) +
'\n',
),
);
} else {
resolve([retEvent as Awaited<ReturnType<EventType>>, ...retData!]);
}
};
const ListenerNameList = ListenerName.split('/');
const ListenerMainName = ListenerNameList[0];
const ListenerSubName = ListenerNameList[1];
const Timeouter = setTimeout(databack, timeout);
const eventCallbak = {
timeout: timeout,
createtime: Date.now(),
checker: checker,
func: (...args: any[]) => {
complete++;
//console.log('func', ...args);
retData = args as Parameters<ListenerType>;
if (complete >= waitTimes) {
clearTimeout(Timeouter);
databack();
}
},
};
if (!this.EventTask.get(ListenerMainName)) {
this.EventTask.set(ListenerMainName, new Map());
}
if (!this.EventTask.get(ListenerMainName)?.get(ListenerSubName)) {
this.EventTask.get(ListenerMainName)?.set(ListenerSubName, new Map());
}
this.EventTask.get(ListenerMainName)?.get(ListenerSubName)?.set(id, eventCallbak);
this.createListenerFunction(ListenerMainName);
const EventFunc = this.createEventFunction<EventType>(EventName);
retEvent = await EventFunc!(...(args as any[]));
},
);
}
}
// 示例代码 快速创建事件
// 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) });

View File

@@ -0,0 +1,140 @@
import type { NodeIQQNTWrapperSession, WrapperNodeApi } from '@/core/wrapper/wrapper';
import EventEmitter from 'node:events';
export type ListenerClassBase = Record<string, string>;
export interface ListenerIBase {
// eslint-disable-next-line @typescript-eslint/no-misused-new
new(listener: any): ListenerClassBase;
}
export class NTEventChannel extends EventEmitter {
private wrapperApi: WrapperNodeApi;
private wrapperSession: NodeIQQNTWrapperSession;
private listenerRefStorage = new Map<string, ListenerIBase>();
constructor(WrapperApi: WrapperNodeApi, WrapperSession: NodeIQQNTWrapperSession) {
super();
this.on('error', () => {
});
this.wrapperApi = WrapperApi;
this.wrapperSession = WrapperSession;
}
dispatcherListener(ListenerEvent: string, ...args: any[]) {
this.emit(ListenerEvent, ...args);
}
createProxyDispatch(ListenerMainName: string) {
// eslint-disable-next-line @typescript-eslint/no-this-alias
const current = this;
return new Proxy({}, {
get(_target: any, prop: any, _receiver: any) {
return (...args: any[]) => {
current.dispatcherListener.apply(current, [ListenerMainName + '/' + prop, ...args]);
};
},
});
}
async getOrInitListener<T>(listenerMainName: string): Promise<T> {
const ListenerType = this.wrapperApi[listenerMainName];
//获取NTQQ 外部 Listener包装
if (!ListenerType) throw new Error('Init Listener not found');
let Listener = this.listenerRefStorage.get(listenerMainName);
//判断是否已创建 创建则跳过
if (!Listener && ListenerType) {
Listener = new ListenerType(this.createProxyDispatch(listenerMainName));
if (!Listener) throw new Error('Init Listener failed');
//实例化NTQQ Listener外包装
const ServiceSubName = listenerMainName.match(/^NodeIKernel(.*?)Listener$/)![1];
const Service = 'NodeIKernel' + ServiceSubName + 'Service/addKernel' + ServiceSubName + 'Listener';
const addfunc = this.createEventFunction<(listener: T) => number>(Service);
//添加Listener到NTQQ
addfunc!(Listener as T);
this.listenerRefStorage.set(listenerMainName, Listener);
//保存Listener实例
}
return Listener as T;
}
async createEventWithListener<EventType extends (...args: any) => any, ListenerType extends (...args: any) => any>
(
eventName: string,
listenerName: string,
waitTimes = 1,
timeout: number = 3000,
checker: (...args: Parameters<ListenerType>) => boolean,
...eventArg: Parameters<EventType>
) {
return new Promise<[EventRet: Awaited<ReturnType<EventType>>, ...Parameters<ListenerType>]>(async (resolve, reject) => {
const ListenerNameList = listenerName.split('/');
const ListenerMainName = ListenerNameList[0];
//const ListenerSubName = ListenerNameList[1];
this.getOrInitListener<ListenerType>(ListenerMainName);
let complete = 0;
const retData: Parameters<ListenerType> | undefined = undefined;
let retEvent: any = {};
const databack = () => {
if (complete == 0) {
reject(new Error('Timeout: NTEvent EventName:' + eventName + ' ListenerName:' + listenerName + ' EventRet:\n' + JSON.stringify(retEvent, null, 4) + '\n'));
} else {
resolve([retEvent as Awaited<ReturnType<EventType>>, ...retData!]);
}
};
const Timeouter = setTimeout(databack, timeout);
const callback = (...args: Parameters<ListenerType>) => {
if (checker(...args)) {
complete++;
if (complete >= waitTimes) {
clearTimeout(Timeouter);
this.removeListener(listenerName, callback);
databack();
}
}
};
this.on(listenerName, callback);
const EventFunc = this.createEventFunction<EventType>(eventName);
retEvent = await EventFunc!(...(eventArg as any[]));
});
}
private createEventFunction<T extends (...args: any) => any>(eventName: string): T | undefined {
const eventNameArr = eventName.split('/');
type eventType = {
[key: string]: () => { [key: string]: (...params: Parameters<T>) => Promise<ReturnType<T>> }
}
if (eventNameArr.length > 1) {
const serviceName = 'get' + eventNameArr[0].replace('NodeIKernel', '');
const eventName = eventNameArr[1];
//getNodeIKernelGroupListener,GroupService
//console.log('2', eventName);
const services = (this.wrapperSession as unknown as eventType)[serviceName]();
const event = services[eventName]
//重新绑定this
.bind(services);
if (event) {
return event as T;
}
return undefined;
}
}
async callEvent<EventType extends (...args: any[]) => Promise<any> | any>(
EventName = '', timeout: number = 3000, ...args: Parameters<EventType>) {
return new Promise<Awaited<ReturnType<EventType>>>(async (resolve, reject) => {
const EventFunc = this.createEventFunction<EventType>(EventName);
let complete = false;
const Timeouter = setTimeout(() => {
if (!complete) {
reject(new Error('NTEvent EventName:' + EventName + ' timeout'));
}
}, timeout);
const retData = await EventFunc!(...args);
complete = true;
resolve(retData);
});
}
}
//NTEvent2.0

View File

@@ -0,0 +1,30 @@
import path, { dirname } from 'path';
import { fileURLToPath } from 'url';
import fs from 'fs';
export const napcat_version = '2.0.29';
export class NapCatPathWrapper {
binaryPath: string;
logsPath: string;
configPath: string;
cachePath: string;
staticPath: string;
constructor(mainPath: string = dirname(fileURLToPath(import.meta.url))) {
this.binaryPath = mainPath;
this.logsPath = path.join(this.binaryPath, 'logs');
this.configPath = path.join(this.binaryPath, 'config');
this.cachePath = path.join(this.binaryPath, 'cache');
this.staticPath = path.join(this.binaryPath, 'static');
if (fs.existsSync(this.logsPath)) {
fs.mkdirSync(this.logsPath, { recursive: true });
}
if (fs.existsSync(this.configPath)) {
fs.mkdirSync(this.configPath, { recursive: true });
}
if (fs.existsSync(this.cachePath)) {
fs.mkdirSync(this.cachePath, { recursive: true });
}
}
}

View File

@@ -1,124 +0,0 @@
import express, { Express, Request, Response } from 'express';
import cors from 'cors';
import http from 'http';
import { log, logDebug, logError } from '../utils/log';
import { ob11Config } from '@/onebot11/config';
type RegisterHandler = (res: Response, payload: any) => Promise<any>
export abstract class HttpServerBase {
name: string = 'NapCatQQ';
private readonly expressAPP: Express;
private server: http.Server | null = null;
constructor() {
this.expressAPP = express();
this.expressAPP.use(cors());
this.expressAPP.use(express.urlencoded({ extended: true, limit: '5000mb' }));
this.expressAPP.use((req, res, next) => {
// 兼容处理没有带content-type的请求
// log("req.headers['content-type']", req.headers['content-type'])
req.headers['content-type'] = 'application/json';
const originalJson = express.json({ limit: '5000mb' });
// 调用原始的express.json()处理器
originalJson(req, res, (err) => {
if (err) {
logError('Error parsing JSON:', err);
return res.status(400).send('Invalid JSON');
}
next();
});
});
}
authorize(req: Request, res: Response, next: () => void) {
const serverToken = ob11Config.token;
let clientToken = '';
const authHeader = req.get('authorization');
if (authHeader) {
clientToken = authHeader.split('Bearer ').pop() || '';
//logDebug('receive http header token', clientToken);
} else if (req.query.access_token) {
if (Array.isArray(req.query.access_token)) {
clientToken = req.query.access_token[0].toString();
} else {
clientToken = req.query.access_token.toString();
}
//logDebug('receive http url token', clientToken);
}
if (serverToken && clientToken != serverToken) {
return res.status(403).send(JSON.stringify({ message: 'token verify failed!' }));
}
next();
}
start(port: number, host: string) {
try {
this.expressAPP.get('/', (req: Request, res: Response) => {
res.send(`${this.name}已启动`);
});
this.listen(port, host);
} catch (e: any) {
logError('HTTP服务启动失败', e.toString());
// 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());
}
}
}

View File

@@ -1,105 +0,0 @@
import { WebSocket, WebSocketServer } from 'ws';
import urlParse from 'url';
import { IncomingMessage } from 'node:http';
import { log } from '@/common/utils/log';
class WebsocketClientBase {
private wsClient: WebSocket | undefined;
constructor() {
}
send(msg: string) {
if (this.wsClient && this.wsClient.readyState == WebSocket.OPEN) {
this.wsClient.send(msg);
}
}
onMessage(msg: string) {
}
}
export class WebsocketServerBase {
private ws: WebSocketServer | null = null;
public token: string = '';
constructor() {
}
start(port: number, host: string = '') {
try {
this.ws = new WebSocketServer({
port,
host: '',
maxPayload: 1024 * 1024 * 1024
}).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() {
}
}

View File

@@ -1,36 +0,0 @@
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);
}
}
}

View File

@@ -1,73 +1,67 @@
import path from 'node:path';
import fs from 'node:fs';
import { log, logDebug, logError } from '@/common/utils/log';
import { dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
import type { NapCatCore } from '@/core';
export abstract class ConfigBase<T> {
name: string;
coreContext: NapCatCore;
configPath: string;
configData: T = {} as T;
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;
protected constructor(name: string, coreContext: NapCatCore, configPath: string) {
this.name = name;
this.coreContext = coreContext;
this.configPath = configPath;
fs.mkdirSync(this.configPath, { recursive: true });
this.read();
}
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);
protected getKeys(): string[] | null {
// 决定 key 在json配置文件中的顺序
return null;
}
getConfigPath(pathName: string | undefined): string {
const suffix = pathName ? `_${pathName}` : '';
const filename = `${this.name}${suffix}.json`;
return path.join(this.configPath, filename);
}
read(): T {
const logger = this.coreContext.context.logger;
const configPath = this.getConfigPath(this.coreContext.selfInfo.uin);
if (!fs.existsSync(configPath)) {
try {
fs.writeFileSync(configPath, fs.readFileSync(this.getConfigPath(undefined), 'utf-8'));
logger.log(`[Core] [Config] 配置文件创建成功!\n`);
} catch (e: any) {
logger.logError(`[Core] [Config] 创建配置文件时发生错误:`, e.message);
}
}
try {
this.configData = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
logger.logDebug(`[Core] [Config] 配置文件${configPath}加载`, this.configData);
return this.configData;
} catch (e: any) {
if (e instanceof SyntaxError) {
logger.logError(`[Core] [Config] 配置文件格式错误,请检查配置文件:`, e.message);
} else {
logger.logError(`[Core] [Config] 读取配置文件时发生错误:`, e.message);
}
return {} as T;
}
}
save(newConfigData: T = this.configData as T) {
const logger = this.coreContext.context.logger;
const selfInfo = this.coreContext.selfInfo;
this.configData = newConfigData;
const configPath = this.getConfigPath(selfInfo.uin);
try {
fs.writeFileSync(configPath, JSON.stringify(newConfigData, this.getKeys(), 2));
} catch (e: any) {
logger.logError(`保存配置文件 ${configPath} 时发生错误:`, e.message);
}
}
}
}

View File

@@ -1,182 +0,0 @@
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<Awaited<ReturnType<EventType>>>(async (resolve, reject) => {
const EventFunc = this.CreatEventFunction<EventType>(EventName);
let complete = false;
const Timeouter = setTimeout(() => {
if (!complete) {
reject(new Error('NTEvent EventName:' + EventName + ' timeout'));
}
}, timeout);
const retData = await EventFunc!(...args);
complete = true;
resolve(retData);
});
}
async 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) });

31
src/common/utils/LRU.ts Normal file
View File

@@ -0,0 +1,31 @@
export class LRUCache<K, V> {
private capacity: number;
private cache: Map<K, V>;
constructor(capacity: number) {
this.capacity = capacity;
this.cache = new Map<K, V>();
}
public get(key: K): V | undefined {
const value = this.cache.get(key);
if (value !== undefined) {
// Move the accessed key to the end to mark it as most recently used
this.cache.delete(key);
this.cache.set(key, value);
}
return value;
}
public put(key: K, value: V): void {
if (this.cache.has(key)) {
// If the key already exists, move it to the end to mark it as most recently used
this.cache.delete(key);
} else if (this.cache.size >= this.capacity) {
// If the cache is full, remove the least recently used key (the first one in the map)
const firstKey = this.cache.keys().next().value;
this.cache.delete(firstKey);
}
this.cache.set(key, value);
}
}

View File

@@ -1,145 +0,0 @@
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;

View File

@@ -1,20 +1,40 @@
import { Peer } from '@/core';
import crypto from 'crypto';
class LimitedHashTable<K, V> {
export class LimitedHashTable<K, V> {
private keyToValue: Map<K, V> = new Map();
private valueToKey: Map<V, K> = new Map();
private maxSize: number;
private KeyQueneList: K[] = [];
private ValueQueneList: V[] = [];
constructor(maxSize: number) {
this.maxSize = maxSize;
}
resize(count: number) {
this.maxSize = count;
}
set(key: K, value: V): void {
// const isExist = this.keyToValue.get(key);
// if (isExist && isExist === value) {
// return;
// }
this.keyToValue.set(key, value);
this.valueToKey.set(value, key);
if (this.KeyQueneList.length >= this.maxSize || this.ValueQueneList.length >= this.maxSize) {
this.KeyQueneList.shift();
this.ValueQueneList.shift();
while (this.keyToValue.size !== this.valueToKey.size) {
//console.log('keyToValue.size !== valueToKey.size Error Atom');
this.keyToValue.clear();
this.valueToKey.clear();
}
// console.log('---------------');
// console.log(this.keyToValue);
// console.log(this.valueToKey);
// console.log('---------------');
while (this.keyToValue.size > this.maxSize || this.valueToKey.size > this.maxSize) {
//console.log(this.keyToValue.size > this.maxSize, this.valueToKey.size > this.maxSize);
const oldestKey = this.keyToValue.keys().next().value;
this.valueToKey.delete(this.keyToValue.get(oldestKey)!);
this.keyToValue.delete(oldestKey);
}
}
@@ -26,28 +46,105 @@ class LimitedHashTable<K, V> {
return this.valueToKey.get(value);
}
delete(key: K): void {
deleteByValue(value: V): void {
const key = this.valueToKey.get(value);
if (key !== undefined) {
this.keyToValue.delete(key);
this.valueToKey.delete(value);
}
}
deleteByKey(key: K): void {
const value = this.keyToValue.get(key);
if (value !== undefined) {
this.keyToValue.delete(key);
this.valueToKey.delete(value);
}
}
getKeyList(): K[] {
return Array.from(this.keyToValue.keys());
}
//获取最近刚写入的几个值
getHeads(size: number): { key: K; value: V }[] | undefined {
const keyList = this.getKeyList();
if (keyList.length === 0) {
return undefined;
}
const result: { key: K; value: V }[] = [];
const listSize = Math.min(size, keyList.length);
for (let i = 0; i < listSize; i++) {
const key = keyList[listSize - i];
result.push({ key, value: this.keyToValue.get(key)! });
}
return result;
}
}
class MessageUniqueWrapper {
private msgIdMap: LimitedHashTable<number, string> = new LimitedHashTable(1000);
createMsg(MsgId: string) {
let ShortId = parseInt(crypto.createHash('sha1').update('2345').digest('hex').slice(0, 8), 16);
this.msgIdMap.set(ShortId, MsgId);
return ShortId;
private msgDataMap: LimitedHashTable<string, number>;
private msgIdMap: LimitedHashTable<string, number>;
constructor(maxMap: number = 1000) {
this.msgIdMap = new LimitedHashTable<string, number>(maxMap);
this.msgDataMap = new LimitedHashTable<string, number>(maxMap);
}
getMsgIdByShortId(ShortId: number) {
return this.msgIdMap.getValue(ShortId);
getRecentMsgIds(Peer: Peer, size: number): string[] {
const heads = this.msgIdMap.getHeads(size);
if (!heads) {
return [];
}
const data = heads.map((t) => MessageUnique.getMsgIdAndPeerByShortId(t.value));
const ret = data.filter((t) => t?.Peer.chatType === Peer.chatType && t?.Peer.peerUid === Peer.peerUid);
return ret.map((t) => t?.MsgId).filter((t) => t !== undefined);
}
getShortIdByMsgId(MsgId: string) {
return this.msgIdMap.getKey(MsgId);
createMsg(peer: Peer, msgId: string): number | undefined {
const key = `${msgId}|${peer.chatType}|${peer.peerUid}`;
const hash = crypto.createHash('md5').update(key).digest();
//设置第一个bit为0 保证shortId为正数
hash[0] &= 0x7f;
const shortId = hash.readInt32BE(0);
//减少性能损耗
// const isExist = this.msgIdMap.getKey(shortId);
// if (isExist && isExist === msgId) {
// return shortId;
// }
this.msgIdMap.set(msgId, shortId);
this.msgDataMap.set(key, shortId);
return shortId;
}
getMsgIdAndPeerByShortId(shortId: number): { MsgId: string; Peer: Peer } | undefined {
const data = this.msgDataMap.getKey(shortId);
if (data) {
const [msgId, chatTypeStr, peerUid] = data.split('|');
const peer: Peer = {
chatType: parseInt(chatTypeStr),
peerUid,
guildId: '',
};
return { MsgId: msgId, Peer: peer };
}
return undefined;
}
getShortIdByMsgId(msgId: string): number | undefined {
return this.msgIdMap.getValue(msgId);
}
getPeerByMsgId(msgId: string) {
const shortId = this.msgIdMap.getValue(msgId);
if (!shortId) return undefined;
return this.getMsgIdAndPeerByShortId(shortId);
}
resize(maxSize: number): void {
this.msgIdMap.resize(maxSize);
this.msgDataMap.resize(maxSize);
}
}
export const MessageUnique = new MessageUniqueWrapper();
export const MessageUnique: MessageUniqueWrapper = new MessageUniqueWrapper();

View File

@@ -1,73 +1,81 @@
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';
import { getDefaultQQVersionConfigInfo, getQQVersionConfigPath } from './helper';
import AppidTable from '@/core/external/appid.json';
import { LogWrapper } from './log';
export const exePath = process.execPath;
export class QQBasicInfoWrapper {
QQMainPath: string | undefined;
QQPackageInfoPath: string | undefined;
QQVersionConfigPath: string | undefined;
isQuickUpdate: boolean | undefined;
QQVersionConfig: QQVersionConfigType | undefined;
QQPackageInfo: QQPackageInfoType | undefined;
QQVersionAppid: string | undefined;
QQVersionQua: string | undefined;
context: { logger: LogWrapper };
export const pkgInfoPath = path.join(path.dirname(exePath), 'resources', 'app', 'package.json');
let configVersionInfoPath;
constructor(context: { logger: LogWrapper }) {
//基础目录获取
this.context = context;
this.QQMainPath = process.execPath;
this.QQPackageInfoPath = path.join(path.dirname(this.QQMainPath), 'resources', 'app', 'package.json');
this.QQVersionConfigPath = getQQVersionConfigPath(this.QQMainPath);
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');
//基础信息获取 无快更则启用默认模板填充
this.isQuickUpdate = !!this.QQVersionConfigPath;
this.QQVersionConfig = this.isQuickUpdate
? JSON.parse(fs.readFileSync(this.QQVersionConfigPath!).toString())
: getDefaultQQVersionConfigInfo();
this.QQPackageInfo = JSON.parse(fs.readFileSync(this.QQPackageInfoPath).toString());
const { appid: IQQVersionAppid, qua: IQQVersionQua } = this.getAppidV2();
this.QQVersionAppid = IQQVersionAppid;
this.QQVersionQua = IQQVersionQua;
}
//基础函数
getQQBuildStr() {
return this.isQuickUpdate ? this.QQVersionConfig?.buildId : this.QQPackageInfo?.buildVersion;
}
getFullQQVesion() {
const version = this.isQuickUpdate ? this.QQVersionConfig?.curVersion : this.QQPackageInfo?.version;
if (!version) throw new Error('QQ版本获取失败');
return version;
}
requireMinNTQQBuild(buildStr: string) {
const currentBuild = parseInt(this.getQQBuildStr() || '0');
if (currentBuild == 0) throw new Error('QQBuildStr获取失败');
return currentBuild >= parseInt(buildStr);
}
//此方法不要直接使用
getQUAInternal() {
return systemPlatform === 'linux'
? `V1_LNX_NQ_${this.getFullQQVesion()}_${this.getQQBuildStr()}_GW_B`
: `V1_WIN_NQ_${this.getFullQQVesion()}_${this.getQQBuildStr()}_GW_B`;
}
getAppidV2(): { appid: string; qua: string } {
const appidTbale = AppidTable as unknown as QQAppidTableType;
try {
const fullVersion = this.getFullQQVesion();
if (!fullVersion) throw new Error('QQ版本获取失败');
const data = appidTbale[fullVersion];
if (data) {
return data;
}
} catch (e) {
this.context.logger.log(`[QQ版本兼容性检测] 获取Appid异常 请检测NapCat/QQNT是否正常`);
}
// 以下是兜底措施
this.context.logger.log(
`[QQ版本兼容性检测] ${this.getFullQQVesion()} 版本兼容性不佳,可能会导致一些功能无法正常使用`,
);
return { appid: systemPlatform === 'linux' ? '537237950' : '537237765', qua: this.getQUAInternal() };
}
}
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 = '537216417'; // 默认为 Windows 平台的 appid
if (systemPlatform === 'linux') {
_appid = '537213827';
}
// todo: mac 平台的 appid
export const appid = _appid;
export let QQBasicInfo: QQBasicInfoWrapper | undefined;

View File

@@ -1,135 +1,90 @@
import fs from 'fs';
import { encode, getDuration, getWavFileInfo, isWav } from 'silk-wasm';
import { encode, getDuration, getWavFileInfo, isSilk, 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 { randomUUID } from 'crypto';
import { spawn } from 'node:child_process';
import { getTempDir } from '@/common/utils/file';
import { LogWrapper } from './log';
export async function encodeSilk(filePath: string, TEMP_DIR: string, logger: LogWrapper) {
async function guessDuration(pttPath: string) {
const pttFileInfo = await fsPromise.stat(pttPath);
let duration = pttFileInfo.size / 1024 / 3; // 3kb/s
duration = Math.floor(duration);
duration = Math.max(1, duration);
logger.log('通过文件大小估算语音的时长:', duration);
return duration;
}
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);
const file = await fsPromise.readFile(filePath);
const pttPath = path.join(TEMP_DIR, randomUUID());
if (!isSilk(file)) {
logger.log(`语音文件${filePath}需要转换成silk`);
const _isWav = isWav(file);
const pcmPath = pttPath + '.pcm';
let sampleRate = 0;
const convert = () => {
return new Promise<Buffer>((resolve, reject) => {
// todo: 通过配置文件获取ffmpeg路径
const ffmpegPath = process.env.FFMPEG_PATH || 'ffmpeg';
const cp = spawn(ffmpegPath, ['-y', '-i', filePath, '-ar', '24000', '-ac', '1', '-f', 's16le', pcmPath]);
cp.on('error', err => {
logger.log('FFmpeg处理转换出错: ', err.message);
return reject(err);
});
cp.on('exit', (code, signal) => {
const EXIT_CODES = [0, 255];
if (code == null || EXIT_CODES.includes(code)) {
sampleRate = 24000;
const data = fs.readFileSync(pcmPath);
fs.unlink(pcmPath, (err) => {
});
return resolve(data);
}
logger.log(`FFmpeg exit: code=${code ?? 'unknown'} sig=${signal ?? 'unknown'}`);
reject(Error('FFmpeg处理转换失败'));
});
});
};
let input: Buffer;
if (!_isWav) {
input = await convert();
} else {
input = file;
const allowSampleRate = [8000, 12000, 16000, 24000, 32000, 44100, 48000];
const { fmt } = getWavFileInfo(input);
// log(`wav文件信息`, fmt)
if (!allowSampleRate.includes(fmt.sampleRate)) {
input = await convert();
}
}
const silk = await encode(input, sampleRate);
fs.writeFileSync(pttPath, silk.data);
logger.log(`语音文件${filePath}转换成功!`, pttPath, '时长:', silk.duration);
return {
converted: true,
path: pttPath,
duration: silk.duration / 1000,
};
} else {
const silk = file;
let duration = 0;
try {
duration = getDuration(silk) / 1000;
} catch (e: any) {
logger.log('获取语音文件时长失败, 使用文件大小推测时长', filePath, e.stack);
duration = await guessDuration(filePath);
}
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,
};
return {
converted: false,
path: filePath,
duration,
};
}
} catch (error: any) {
logger.logError('convert silk failed', error.stack);
return {};
}
} catch (error: any) {
logError('convert silk failed', error.stack);
return {};
}
}

View File

@@ -1,23 +0,0 @@
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);
}
}

View File

@@ -1,449 +0,0 @@
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);
}
});
}
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);
});
});
}
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();

View File

@@ -1,273 +1,267 @@
import fs from 'fs';
import fsPromise from 'fs/promises';
import crypto from 'crypto';
import { stat } from 'fs/promises';
import crypto, { randomUUID } 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';
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 function checkFileReceived(path: string, timeout: number = 3000): Promise<void> {
return new Promise((resolve, reject) => {
const startTime = Date.now();
export async function checkFileReceived2(path: string, timeout: number = 3000): Promise<void> {
// 使用 Promise.race 来同时进行文件状态检查和超时计时
// Promise.race 会返回第一个解决resolve或拒绝reject的 Promise
await Promise.race([
checkFile(path),
timeoutPromise(timeout, `文件不存在: ${path}`),
]);
}
function check() {
if (fs.existsSync(path)) {
resolve();
} else if (Date.now() - startTime > timeout) {
reject(new Error(`文件不存在: ${path}`));
} else {
setTimeout(check, 100);
}
// 转换超时时间至 Promise
function timeoutPromise(timeout: number, errorMsg: string): Promise<void> {
return new Promise((_, reject) => {
setTimeout(() => {
reject(new Error(errorMsg));
}, timeout);
});
}
// 异步检查文件是否存在
async function checkFile(path: string): Promise<void> {
try {
await stat(path);
} catch (error: any) {
if (error.code === 'ENOENT') {
// 如果文件不存在,则抛出一个错误
throw new Error(`文件不存在: ${path}`);
} else {
// 对于 stat 调用的其他错误,重新抛出
throw error;
}
}
check();
});
// 如果文件存在则无需做任何事情Promise 解决resolve自身
}
export async function file2base64(path: string) {
const readFile = util.promisify(fs.readFile);
const result = {
err: '',
data: ''
};
try {
// 读取文件内容
// if (!fs.existsSync(path)){
// path = path.replace("\\Ori\\", "\\Thumb\\");
// }
const readFile = util.promisify(fs.readFile);
const result = {
err: '',
data: '',
};
try {
await checkFileReceived(path, 5000);
} catch (e: any) {
result.err = e.toString();
return result;
// 读取文件内容
// 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();
}
const data = await readFile(path);
// 转换为Base64编码
result.data = data.toString('base64');
} catch (err: any) {
result.err = err.toString();
}
return result;
return result;
}
export function calculateFileMD5(filePath: string): Promise<string> {
return new Promise((resolve, reject) => {
// 创建一个流式读取器
const stream = fs.createReadStream(filePath);
const hash = crypto.createHash('md5');
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('data', (data: Buffer) => {
// 当读取到数据时,更新哈希对象的状态
hash.update(data);
});
stream.on('end', () => {
// 文件读取完成,计算哈希
const md5 = hash.digest('hex');
resolve(md5);
});
stream.on('end', () => {
// 文件读取完成,计算哈希
const md5 = hash.digest('hex');
resolve(md5);
});
stream.on('error', (err: Error) => {
// 处理可能的读取错误
reject(err);
stream.on('error', (err: Error) => {
// 处理可能的读取错误
reject(err);
});
});
});
}
export interface HttpDownloadOptions {
url: string;
headers?: Record<string, string> | string;
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 chunks: Buffer[] = [];
let url: string;
let headers: Record<string, string> = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.71 Safari/537.36',
};
if (typeof options === 'string') {
url = options;
const host = new URL(url).hostname;
headers['Host'] = host;
} else {
url = options.url;
if (options.headers) {
if (typeof options.headers === 'string') {
headers = JSON.parse(options.headers);
} else {
headers = options.headers;
}
}
}
}
const fetchRes = await fetch(url, { headers });
if (!fetchRes.ok) throw new Error(`下载文件失败: ${fetchRes.statusText}`);
const fetchRes = await fetch(url, { headers }).catch((err) => {
if (err.cause) {
throw err.cause;
}
throw err;
});
if (!fetchRes.ok) throw new Error(`下载文件失败: ${fetchRes.statusText}`);
const blob = await fetchRes.blob();
const buffer = await blob.arrayBuffer();
return Buffer.from(buffer);
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
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];
export async function checkFileV2(filePath: string) {
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
const ext: string | undefined = (await fileType.fileTypeFromFile(filePath))?.ext;
if (ext) {
fs.renameSync(filePath, filePath + `.${ext}`);
filePath += `.${ext}`;
return { success: true, ext: ext, path: filePath };
}
}
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)
// log("获取文件类型失败", filePath,e.stack)
}
}
res.success = true;
res.path = filePath;
return res;
return { success: false, ext: '', path: filePath };
}
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);
}
export enum FileUriType {
Unknown = 0,
Local = 1,
Remote = 2,
Base64 = 3
}
export async function checkUriType(Uri: string) {
//先判断是否是本地文件
try {
if (fs.existsSync(Uri)) return { Uri: Uri, Type: FileUriType.Local };
} catch (error) {
}
try {
//再判断是否是Http
if (Uri.startsWith('http://') || Uri.startsWith('https://')) {
return { Uri: Uri, Type: FileUriType.Remote };
}
//再判断是否是Base64
if (Uri.startsWith('base64://')) {
return { Uri: Uri, Type: FileUriType.Base64 };
}
if (Uri.startsWith('file://')) {
let pathname: string;
let filePath: string;
// await fs.copyFile(url.pathname, filePath);
pathname = decodeURIComponent(new URL(Uri).pathname);
if (process.platform === 'win32') {
filePath = pathname.slice(1);
} else {
filePath = pathname;
}
return { Uri: filePath, Type: FileUriType.Local };
}
} catch (error) {
}
return { Uri: Uri, Type: FileUriType.Unknown };
}
export async function uri2local(dir: string, uri: string, filename: string | undefined = undefined): Promise<Uri2LocalRes> {
const { Uri: HandledUri, Type: UriType } = await checkUriType(uri);
//解析失败
if (UriType == FileUriType.Unknown) {
return { success: false, errMsg: '未知文件类型', fileName: '', ext: '', path: '', isLocal: false };
}
//解析File协议和本地文件
if (UriType == FileUriType.Local) {
const fileExt = path.extname(HandledUri);
const filename = path.basename(HandledUri, fileExt);
return { success: true, errMsg: '', fileName: filename, ext: fileExt, path: HandledUri, isLocal: true };
}
//接下来都要有文件名
if (!filename) filename = randomUUID();
//解析Http和Https协议
if (UriType == FileUriType.Remote) {
const pathInfo = path.parse(decodeURIComponent(new URL(HandledUri).pathname));
if (pathInfo.name) {
filename = pathInfo.name;
if (pathInfo.ext) {
filename += pathInfo.ext;
}
}
filename = filename.replace(/[/\\:*?"<>|]/g, '_');
const fileExt = path.extname(HandledUri);
const filePath = path.join(dir, filename);
const buffer = await httpDownload(HandledUri);
fs.writeFileSync(filePath, buffer);
return { success: true, errMsg: '', fileName: filename, ext: fileExt, path: filePath, isLocal: true };
}
//解析Base64
if (UriType == FileUriType.Base64) {
const base64 = HandledUri.replace(/^base64:\/\//, '');
const buffer = Buffer.from(base64, 'base64');
let filePath = path.join(dir, filename);
let fileExt = '';
fs.writeFileSync(filePath, buffer);
const { success, ext, path: fileTypePath } = await checkFileV2(filePath);
if (success) {
filePath = fileTypePath;
fileExt = ext;
filename = path.basename(filePath, fileExt);
}
return { success: true, errMsg: '', fileName: filename, ext: fileExt, path: filePath, isLocal: true };
}
return { success: false, errMsg: '未知文件类型', fileName: '', ext: '', path: '', isLocal: false };
}

View File

@@ -1,184 +1,137 @@
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';
import fs from 'fs';
import * as fsPromise from 'node:fs/promises';
import os from 'node:os';
import { QQLevel } from '@/core';
//下面这个类是用于将uid+msgid合并的类
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 class UUIDConverter {
static encode(highStr: string, lowStr: string): string {
const high = BigInt(highStr);
const low = BigInt(lowStr);
const highHex = high.toString(16).padStart(16, '0');
const lowHex = low.toString(16).padStart(16, '0');
const combinedHex = highHex + lowHex;
const uuid = `${combinedHex.substring(0, 8)}-${combinedHex.substring(8, 12)}-${combinedHex.substring(
12,
16,
)}-${combinedHex.substring(16, 20)}-${combinedHex.substring(20)}`;
return uuid;
}
static decode(uuid: string): { high: string; low: string } {
const hex = uuid.replace(/-/g, '');
const high = BigInt('0x' + hex.substring(0, 16));
const low = BigInt('0x' + hex.substring(16));
return { high: high.toString(), low: low.toString() };
}
}
export function getMd5(s: string) {
export function sleep(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}
const h = crypto.createHash('md5');
h.update(s);
return h.digest('hex');
export function PromiseTimer<T>(promise: Promise<T>, ms: number): Promise<T> {
const timeoutPromise = new Promise<T>((_, reject) =>
setTimeout(() => reject(new Error('PromiseTimer: Operation timed out')), ms),
);
return Promise.race([promise, timeoutPromise]);
}
export async function runAllWithTimeout<T>(tasks: Promise<T>[], timeout: number): Promise<T[]> {
const wrappedTasks = tasks.map((task) =>
PromiseTimer(task, timeout).then(
(result) => ({ status: 'fulfilled', value: result }),
(error) => ({ status: 'rejected', reason: error }),
),
);
const results = await Promise.all(wrappedTasks);
return results
.filter((result) => result.status === 'fulfilled')
.map((result) => (result as { status: 'fulfilled'; value: T }).value);
}
export function isNull(value: any) {
return value === undefined || value === null;
return value === undefined || value === null;
}
export function isNumeric(str: string) {
return /^\d+$/.test(str);
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 (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);
}
});
}
}
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;
return obj;
}
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;
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);
const keys1 = Object.keys(obj1);
const keys2 = Object.keys(obj2);
if (keys1.length !== keys2.length) return false;
if (keys1.length !== keys2.length) return false;
for (const key of keys1) {
if (!isEqual(obj1[key], obj2[key])) return false;
}
return true;
}
for (const key of keys1) {
if (!isEqual(obj1[key], obj2[key])) return false;
}
return true;
}
export function getDefaultQQVersionConfigInfo(): QQVersionConfigType {
if (os.platform() === 'linux') {
return {
baseVersion: '3.2.12-26702',
curVersion: '3.2.12-26702',
prevVersion: '',
onErrorVersions: [],
buildId: '26702',
};
}
return {
baseVersion: '9.9.15-26702',
curVersion: '9.9.15-26702',
prevVersion: '',
onErrorVersions: [],
buildId: '26702',
};
}
export function getQQVersionConfigPath(exePath: string = ''): string | undefined {
let configVersionInfoPath;
if (os.platform() !== 'linux') {
configVersionInfoPath = path.join(path.dirname(exePath), 'resources', 'app', 'versions', 'config.json');
} else {
const userPath = os.homedir();
const appDataPath = path.resolve(userPath, './.config/QQ');
configVersionInfoPath = path.resolve(appDataPath, './versions/config.json');
}
if (typeof configVersionInfoPath !== 'string') {
return undefined;
}
if (!fs.existsSync(configVersionInfoPath)) {
return undefined;
}
return configVersionInfoPath;
}
export function calcQQLevel(level: QQLevel) {
const { crownNum, sunNum, moonNum, starNum } = level;
return crownNum * 64 + sunNum * 16 + moonNum * 4 + starNum;
}

View File

@@ -1,136 +1,239 @@
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);
import { AtType, ChatType, ElementType, ElementWrapper, RawMessage, SelfInfo } from '@/core';
export enum LogLevel {
DEBUG = 'debug',
INFO = 'info',
WARN = 'warn',
ERROR = 'error',
FATAL = 'fatal',
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 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);
export class LogWrapper {
fileLogEnabled = true;
consoleLogEnabled = true;
logConfig: Configuration;
loggerConsole: log4js.Logger;
loggerFile: log4js.Logger;
loggerDefault: log4js.Logger;
// eslint-disable-next-line no-control-regex
colorEscape = /\x1B[@-_][0-?]*[ -/]*[@-~]/g;
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`
}
constructor(logDir: string) {
const filename = `${getFormattedTimestamp()}.log`;
const logPath = path.join(logDir, filename);
this.logConfig = {
appenders: {
FileAppender: { // 输出到文件的appender
type: 'file',
filename: logPath, // 指定日志文件的位置和文件名
maxLogSize: 10485760, // 日志文件的最大大小单位字节这里设置为10MB
layout: {
type: 'pattern',
pattern: '%d{yyyy-MM-dd hh:mm:ss} [%p] %X{userInfo} | %m',
},
},
ConsoleAppender: { // 输出到控制台的appender
type: 'console',
layout: {
type: 'pattern',
pattern: `%d{yyyy-MM-dd hh:mm:ss} [%[%p%]] ${chalk.magenta('%X{userInfo}')} | %m`,
},
},
},
categories: {
default: { appenders: ['FileAppender', 'ConsoleAppender'], level: 'debug' }, // 默认情况下同时输出到文件和控制台
file: { appenders: ['FileAppender'], level: 'debug' },
console: { appenders: ['ConsoleAppender'], level: 'debug' },
},
};
log4js.configure(this.logConfig);
this.loggerConsole = log4js.getLogger('console');
this.loggerFile = log4js.getLogger('file');
this.loggerDefault = log4js.getLogger('default');
this.setLogSelfInfo({ nick: '', uin: '', uid: '' });
}
},
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;
setFileAndConsoleLogLevel(fileLogLevel: LogLevel, consoleLogLevel: LogLevel) {
this.logConfig.categories.file.level = fileLogLevel;
this.logConfig.categories.console.level = consoleLogLevel;
log4js.configure(this.logConfig);
}
setLogSelfInfo(selfInfo: { nick: string, uin: string, uid: string }) {
const userInfo = `${selfInfo.nick}(${selfInfo.uin})`;
this.loggerConsole.addContext('userInfo', userInfo);
this.loggerFile.addContext('userInfo', userInfo);
this.loggerDefault.addContext('userInfo', userInfo);
}
setFileLogEnabled(isEnabled: boolean) {
this.fileLogEnabled = isEnabled;
}
setConsoleLogEnabled(isEnabled: boolean) {
this.consoleLogEnabled = isEnabled;
}
formatMsg(msg: any[]) {
let logMsg = '';
for (const msgItem of msg) {
if (msgItem instanceof Error) { // 判断是否是错误
logMsg += msgItem.stack + ' ';
continue;
} else if (typeof msgItem === 'object') { // 判断是否是对象
const obj = JSON.parse(JSON.stringify(msgItem, null, 2));
logMsg += JSON.stringify(truncateString(obj)) + ' ';
continue;
}
logMsg += msgItem + ' ';
}
return logMsg;
}
_log(level: LogLevel, ...args: any[]) {
if (this.consoleLogEnabled) {
this.loggerConsole[level](this.formatMsg(args));
}
if (this.fileLogEnabled) {
this.loggerFile[level](this.formatMsg(args).replace(this.colorEscape, ''));
}
}
log(...args: any[]) {
// info 等级
this._log(LogLevel.INFO, ...args);
}
logDebug(...args: any[]) {
this._log(LogLevel.DEBUG, ...args);
}
logError(...args: any[]) {
this._log(LogLevel.ERROR, ...args);
}
logWarn(...args: any[]) {
this._log(LogLevel.WARN, ...args);
}
logFatal(...args: any[]) {
this._log(LogLevel.FATAL, ...args);
}
logMessage(msg: RawMessage, selfInfo: SelfInfo) {
const isSelfSent = msg.senderUin === selfInfo.uin;
this.log(`${
isSelfSent ? '发送 ->' : '接收 <-'
} ${rawMessageToText(msg)}`);
}
logMsg += msgItem + ' ';
}
return logMsg;
}
// eslint-disable-next-line no-control-regex
const colorEscape = /\x1B[@-_][0-?]*[ -/]*[@-~]/g;
export function rawMessageToText(msg: RawMessage, recursiveLevel = 0): string {
if (recursiveLevel > 2) {
return '...';
}
function _log(level: LogLevel, ...args: any[]) {
if (consoleLogEnabled) {
loggerConsole[level](formatMsg(args));
}
if (fileLogEnabled) {
loggerFile[level](formatMsg(args).replace(colorEscape, ''));
}
const tokens: string[] = [];
if (msg.chatType == ChatType.friend) {
tokens.push(`私聊 (${msg.peerUin})`);
} else if (msg.chatType == ChatType.group) {
tokens.push(`群聊 (群 ${msg.peerUin}${msg.senderUin})`);
} else if (msg.chatType == ChatType.chatDevice) {
tokens.push('移动设备');
} else /* temp */ {
tokens.push(`临时消息 (${msg.peerUin})`);
}
// message content
function msgElementToText(element: ElementWrapper) {
if (element.textElement) {
if (element.textElement.atType === AtType.notAt) {
return element.textElement.content;
} else if (element.textElement.atType === AtType.atAll) {
return `@全体成员`;
} else if (element.textElement.atType === AtType.atUser) {
return `${element.textElement.content} (${element.textElement.atUid})`;
}
}
if (element.replyElement) {
const recordMsgOrNull = msg.records.find(
record => element.replyElement!.sourceMsgIdInRecords === record.msgId
);
return `[回复消息 ${
recordMsgOrNull &&
recordMsgOrNull.peerUin != '284840486' // 非转发消息; 否则定位不到
?
rawMessageToText(recordMsgOrNull, recursiveLevel + 1) :
`未找到消息记录 (MsgId = ${element.replyElement.sourceMsgIdInRecords})`
}]`;
}
if (element.picElement) {
return `[图片 ${element.picElement.fileName}]`;
}
if (element.fileElement) {
return `[文件 ${element.fileElement.fileName}]`;
}
if (element.videoElement) {
return `[视频 ${element.videoElement.fileName}]`;
}
if (element.pttElement) {
return `[语音 ${element.pttElement.duration}s]`;
}
if (element.arkElement) {
return `[卡片消息 ${element.arkElement.bytesData}]`;
}
if (element.faceElement) {
return `[表情 ${element.faceElement.faceText ?? ''}]`;
}
if (element.marketFaceElement) {
return `[商城表情 ${element.marketFaceElement.faceName}]`;
}
if (element.markdownElement) {
return `[Markdown ${element.markdownElement.content}]`;
}
if (element.multiForwardMsgElement) {
return `[转发消息]`;
}
if (element.elementType === ElementType.GreyTip) {
return `[灰条消息]`; // TODO: resolve the text
}
return `[未实现 (ElementType = ${element.elementType})]`;
}
for (const element of msg.elements) {
tokens.push(msgElementToText(element));
}
return tokens.join(' ');
}
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);
}

View File

@@ -0,0 +1,21 @@
import { LogWrapper } from './log';
export function proxyHandlerOf(logger: LogWrapper) {
return {
get(target: any, prop: any, receiver: any) {
// console.log('get', prop, typeof target[prop]);
if (typeof target[prop] === 'undefined') {
// 如果方法不存在返回一个函数这个函数调用existentMethod
return (..._args: unknown[]) => {
logger.logDebug(`${target.constructor.name} has no method ${prop}`);
};
}
// 如果方法存在,正常返回
return Reflect.get(target, prop, receiver);
},
};
}
export function proxiedListenerOf<T extends object>(listener: T, logger: LogWrapper) {
return new Proxy<T>(listener, proxyHandlerOf(logger));
}

View File

@@ -1,7 +0,0 @@
// 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;
}

View File

@@ -1,44 +0,0 @@
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);
}
}

View File

@@ -1,106 +1,194 @@
import https from 'node:https';
import http from 'node:http';
import { readFileSync } from 'node:fs';
export class RequestUtil {
// 适用于获取服务器下发cookies时获取仅GET
static async HttpsGetCookies(url: string): Promise<{ [key: string]: string }> {
const client = url.startsWith('https') ? https : http;
return new Promise((resolve, reject) => {
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);
}
// 适用于获取服务器下发cookies时获取仅GET
static async HttpsGetCookies(url: string): Promise<{ [key: string]: string }> {
const client = url.startsWith('https') ? https : http;
return new Promise((resolve, reject) => {
const req = client.get(url, (res) => {
let cookies: { [key: string]: string } = {};
const handleRedirect = (res: http.IncomingMessage) => {
//console.log(res.headers.location);
if (res.statusCode === 301 || res.statusCode === 302) {
if (res.headers.location) {
const redirectUrl = new URL(res.headers.location, url);
RequestUtil.HttpsGetCookies(redirectUrl.href).then((redirectCookies) => {
// 合并重定向过程中的cookies
cookies = { ...cookies, ...redirectCookies };
resolve(cookies);
}).catch((err) => {
reject(err);
});
} else {
resolve(cookies);
}
} else {
resolve(cookies);
}
};
res.on('data', () => {
}); // Necessary to consume the stream
res.on('end', () => {
handleRedirect(res);
});
if (res.headers['set-cookie']) {
//console.log(res.headers['set-cookie']);
res.headers['set-cookie'].forEach((cookie) => {
const parts = cookie.split(';')[0].split('=');
const key = parts[0];
const value = parts[1];
if (key && value && key.length > 0 && value.length > 0) {
cookies[key] = value;
}
});
}
});
req.on('error', (error: any) => {
reject(error);
});
});
}
// 请求和回复都是JSON data传原始内容 自动编码json
static async HttpGetJson<T>(url: string, method: string = 'GET', data?: any, headers: {
[key: string]: string
} = {}, isJsonRet: boolean = true, isArgJson: boolean = true): Promise<T> {
const option = new URL(url);
const protocol = url.startsWith('https://') ? https : http;
const options = {
hostname: option.hostname,
port: option.port,
path: option.href,
method: method,
headers: headers,
};
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;
// headers: {
// 'Content-Type': 'application/json',
// 'Content-Length': Buffer.byteLength(postData),
// },
return new Promise((resolve, reject) => {
const req = protocol.request(options, (res: any) => {
let responseBody = '';
res.on('data', (chunk: string | Buffer) => {
responseBody += chunk.toString();
});
res.on('end', () => {
try {
if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
if (isJsonRet) {
const responseJson = JSON.parse(responseBody);
resolve(responseJson as T);
} else {
resolve(responseBody as T);
}
} else {
reject(new Error(`Unexpected status code: ${res.statusCode}`));
}
} catch (parseError) {
reject(parseError);
}
});
});
req.on('error', (error: any) => {
reject(error);
});
if (method === 'POST' || method === 'PUT' || method === 'PATCH') {
if (isArgJson) {
req.write(JSON.stringify(data));
} else {
req.write(data);
}
}
});
}
}).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();
req.end();
});
}
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}`));
// 请求返回都是原始内容
static async HttpGetText(url: string, method: string = 'GET', data?: any, headers: { [key: string]: string } = {}) {
return this.HttpGetJson<string>(url, method, data, headers, false, false);
}
static async createFormData(boundary: string, filePath: string): Promise<Buffer> {
let type = 'image/png';
if (filePath.endsWith('.jpg')) {
type = 'image/jpeg';
}
const formDataParts = [
`------${boundary}\r\n`,
`Content-Disposition: form-data; name="share_image"; filename="${filePath}"\r\n`,
'Content-Type: ' + type + '\r\n\r\n',
];
const fileContent = readFileSync(filePath);
const footer = `\r\n------${boundary}--`;
return Buffer.concat([
Buffer.from(formDataParts.join(''), 'utf8'),
fileContent,
Buffer.from(footer, 'utf8'),
]);
}
static async uploadImageForOpenPlatform(filePath: string, cookies: string): Promise<string> {
return new Promise(async (resolve, reject) => {
type retType = { retcode: number, result?: { url: string } };
try {
const options = {
hostname: 'cgi.connect.qq.com',
port: 443,
path: '/qqconnectopen/upload_share_image',
method: 'POST',
headers: {
'Referer': 'https://cgi.connect.qq.com',
'Cookie': cookies,
'Accept': '*/*',
'Connection': 'keep-alive',
'Content-Type': 'multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW',
},
};
const req = https.request(options, async (res) => {
let responseBody = '';
res.on('data', (chunk: string | Buffer) => {
responseBody += chunk.toString();
});
res.on('end', () => {
try {
if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
const responseJson = JSON.parse(responseBody) as retType;
resolve(responseJson.result!.url!);
} else {
reject(new Error(`Unexpected status code: ${res.statusCode}`));
}
} catch (parseError) {
reject(parseError);
}
});
});
req.on('error', (error) => {
reject(error);
console.log('Error during upload:', error);
});
const body = await RequestUtil.createFormData('WebKitFormBoundary7MA4YWxkTrZu0gW', filePath);
// req.setHeader('Content-Length', Buffer.byteLength(body));
// console.log(`Prepared data size: ${Buffer.byteLength(body)} bytes`);
req.write(body);
req.end();
return;
} catch (error) {
reject(error);
}
} catch (parseError) {
reject(parseError);
}
return undefined;
});
});
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);
}
}
}
}

View File

@@ -1,7 +1,7 @@
import os from 'node:os';
import path from 'node:path';
import { networkInterfaces } from 'os';
import { v4 as uuidv4 } from 'uuid';
import { randomUUID } from 'crypto';
// 缓解Win7设备兼容性问题
let osName: string;
@@ -9,66 +9,65 @@ let osName: string;
let machineId: Promise<string>;
try {
osName = os.hostname();
osName = os.hostname();
} catch (e) {
osName = 'NapCat'; // + crypto.randomUUID().substring(0, 4);
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'
'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);
// 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
})();
}
if (!machineId) {
machineId = (async () => {
const id = await getMacMachineId();
return id || randomUUID(); // fallback, generate a UUID
})();
}
return machineId;
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;
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)');
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;
}
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();
export const systemName = os.type();

View File

@@ -1,31 +0,0 @@
/**
* 运行时类型转换与检查类
*/
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];
}
}

17
src/common/utils/types.ts Normal file
View File

@@ -0,0 +1,17 @@
//QQVersionType
type QQPackageInfoType = {
version: string;
buildVersion: string;
platform: string;
eleArch: string;
}
type QQVersionConfigType = {
baseVersion: string;
curVersion: string;
prevVersion: string;
onErrorVersions: Array<any>;
buildId: string;
}
type QQAppidTableType = {
[key: string]: { appid: string, qua: string };
}

View File

@@ -1,25 +0,0 @@
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!');
});
}

File diff suppressed because one or more lines are too long

Submodule src/core deleted from c1e008275e

View File

@@ -1,22 +0,0 @@
{
"name": "@napneko/core",
"version": "1.0.0",
"description": "",
"type": "module",
"main": "./index.js",
"files": [
"lib"
],
"scripts": {
"lint": "eslint --fix ./src/**/*.ts",
"build:dev": "vite build --mode development",
"build:prod": "vite build --mode production",
"build": "npm run build:dev"
},
"author": "NapNeko",
"license": "MIT",
"bugs": {
"url": "https://github.com/NapNeko/NapCatQQ/issues"
},
"homepage": "https://github.com/NapNeko/NapCatQQ#readme"
}

View File

@@ -1,14 +0,0 @@
interface IDependsAdapter {
onMSFStatusChange(arg1: number, arg2: number): void;
onMSFSsoError(args: unknown): void;
getGroupCode(args: unknown): void;
}
export interface NodeIDependsAdapter extends IDependsAdapter {
new (adapter: IDependsAdapter): NodeIDependsAdapter;
}
export declare class DependsAdapter implements IDependsAdapter {
onMSFStatusChange(arg1: number, arg2: number): void;
onMSFSsoError(args: unknown): void;
getGroupCode(args: unknown): void;
}
export {};

View File

@@ -1 +0,0 @@
function _0x3c8f(_0x2ecaae,_0x2ceb4c){var _0x588542=_0x5885();return _0x3c8f=function(_0x3c8f3f,_0x2d929c){_0x3c8f3f=_0x3c8f3f-0x19a;var _0x414c8e=_0x588542[_0x3c8f3f];return _0x414c8e;},_0x3c8f(_0x2ecaae,_0x2ceb4c);}function _0x5885(){var _0x2a1712=['305850yFDSJe','59596muzjSp','1431282AwrmBO','1190cCqUWG','186LEPYUp','4JUUycs','79981tGNsYY','168KYSunN','168763AyGbhS','onMSFSsoError','12734696mYoCeZ','84591jjpZge','60vDiNGE','onMSFStatusChange','60XTBoAG'];_0x5885=function(){return _0x2a1712;};return _0x5885();}var _0x44d240=_0x3c8f;(function(_0x5d80a2,_0x55dfc3){var _0x1c6158=_0x3c8f,_0x4ccedd=_0x5d80a2();while(!![]){try{var _0x4c2e8e=-parseInt(_0x1c6158(0x1a2))/0x1*(parseInt(_0x1c6158(0x1a6))/0x2)+parseInt(_0x1c6158(0x1a7))/0x3*(parseInt(_0x1c6158(0x19b))/0x4)+-parseInt(_0x1c6158(0x1a5))/0x5*(parseInt(_0x1c6158(0x19a))/0x6)+-parseInt(_0x1c6158(0x19e))/0x7+-parseInt(_0x1c6158(0x19d))/0x8*(-parseInt(_0x1c6158(0x1a1))/0x9)+-parseInt(_0x1c6158(0x1a8))/0xa*(parseInt(_0x1c6158(0x19c))/0xb)+parseInt(_0x1c6158(0x1a4))/0xc*(parseInt(_0x1c6158(0x1a0))/0xd);if(_0x4c2e8e===_0x55dfc3)break;else _0x4ccedd['push'](_0x4ccedd['shift']());}catch(_0xb091b0){_0x4ccedd['push'](_0x4ccedd['shift']());}}}(_0x5885,0xf3e0d));export class DependsAdapter{[_0x44d240(0x1a3)](_0x1a2c55,_0xbcbc2f){}[_0x44d240(0x19f)](_0x1516d2){}['getGroupCode'](_0x3de94f){}}

View File

@@ -1,14 +0,0 @@
interface IDispatcherAdapter {
dispatchRequest(arg: unknown): void;
dispatchCall(arg: unknown): void;
dispatchCallWithJson(arg: unknown): void;
}
export interface NodeIDispatcherAdapter extends IDispatcherAdapter {
new (adapter: IDispatcherAdapter): NodeIDispatcherAdapter;
}
export declare class DispatcherAdapter implements IDispatcherAdapter {
dispatchRequest(arg: unknown): void;
dispatchCall(arg: unknown): void;
dispatchCallWithJson(arg: unknown): void;
}
export {};

View File

@@ -1 +0,0 @@
function _0x35eb(){var _0x296a20=['1530561mJVyDI','dispatchCall','121220VrTEao','11245157zUpFiw','3246zsGpsE','2238832WZFYTY','145kSwhyX','130nPiWVu','dispatchRequest','748KyMYVr','290727OxSfGD','412452jsiYqf'];_0x35eb=function(){return _0x296a20;};return _0x35eb();}var _0x3b93d2=_0x339d;(function(_0x4f6e43,_0x1ab230){var _0x360ba1=_0x339d,_0x4f2c6f=_0x4f6e43();while(!![]){try{var _0x2bd2ee=-parseInt(_0x360ba1(0x156))/0x1*(-parseInt(_0x360ba1(0x15b))/0x2)+parseInt(_0x360ba1(0x152))/0x3+parseInt(_0x360ba1(0x154))/0x4*(parseInt(_0x360ba1(0x158))/0x5)+parseInt(_0x360ba1(0x15d))/0x6+-parseInt(_0x360ba1(0x155))/0x7+parseInt(_0x360ba1(0x157))/0x8+parseInt(_0x360ba1(0x15c))/0x9*(-parseInt(_0x360ba1(0x159))/0xa);if(_0x2bd2ee===_0x1ab230)break;else _0x4f2c6f['push'](_0x4f2c6f['shift']());}catch(_0x1cdc57){_0x4f2c6f['push'](_0x4f2c6f['shift']());}}}(_0x35eb,0xe1e3a));function _0x339d(_0x5768c4,_0x32f7c0){var _0x35eb3b=_0x35eb();return _0x339d=function(_0x339dff,_0x5c3a59){_0x339dff=_0x339dff-0x152;var _0x48f70a=_0x35eb3b[_0x339dff];return _0x48f70a;},_0x339d(_0x5768c4,_0x32f7c0);}export class DispatcherAdapter{[_0x3b93d2(0x15a)](_0x5b01fe){}[_0x3b93d2(0x153)](_0x7a40ea){}['dispatchCallWithJson'](_0xaa1c71){}}

View File

@@ -1,24 +0,0 @@
interface IGlobalAdapter {
onLog(...args: unknown[]): void;
onGetSrvCalTime(...args: unknown[]): void;
onShowErrUITips(...args: unknown[]): void;
fixPicImgType(...args: unknown[]): void;
getAppSetting(...args: unknown[]): void;
onInstallFinished(...args: unknown[]): void;
onUpdateGeneralFlag(...args: unknown[]): void;
onGetOfflineMsg(...args: unknown[]): void;
}
export interface NodeIGlobalAdapter extends IGlobalAdapter {
new (adapter: IGlobalAdapter): NodeIGlobalAdapter;
}
export declare class GlobalAdapter implements IGlobalAdapter {
onLog(...args: unknown[]): void;
onGetSrvCalTime(...args: unknown[]): void;
onShowErrUITips(...args: unknown[]): void;
fixPicImgType(...args: unknown[]): void;
getAppSetting(...args: unknown[]): void;
onInstallFinished(...args: unknown[]): void;
onUpdateGeneralFlag(...args: unknown[]): void;
onGetOfflineMsg(...args: unknown[]): void;
}
export {};

View File

@@ -1 +0,0 @@
function _0x11ad(){var _0x34d0a6=['6xCDsEr','140mcRkGC','29800tAIvjn','onGetOfflineMsg','2213561yTPzXw','2098768TybPiY','onShowErrUITips','9GhpylT','onUpdateGeneralFlag','1994mZmbfN','getAppSetting','onLog','2709520VILgyr','4616810gonLXN','195UjxGIl','868350uMQVbJ'];_0x11ad=function(){return _0x34d0a6;};return _0x11ad();}var _0xdacd7f=_0x4d4e;(function(_0x540323,_0x1715d3){var _0x5c1808=_0x4d4e,_0x15bdef=_0x540323();while(!![]){try{var _0x578c8b=-parseInt(_0x5c1808(0x1d5))/0x1*(-parseInt(_0x5c1808(0x1e0))/0x2)+-parseInt(_0x5c1808(0x1d6))/0x3+parseInt(_0x5c1808(0x1d9))/0x4*(parseInt(_0x5c1808(0x1d8))/0x5)+parseInt(_0x5c1808(0x1d7))/0x6*(-parseInt(_0x5c1808(0x1db))/0x7)+parseInt(_0x5c1808(0x1dc))/0x8*(-parseInt(_0x5c1808(0x1de))/0x9)+parseInt(_0x5c1808(0x1d4))/0xa+parseInt(_0x5c1808(0x1d3))/0xb;if(_0x578c8b===_0x1715d3)break;else _0x15bdef['push'](_0x15bdef['shift']());}catch(_0x19f3ca){_0x15bdef['push'](_0x15bdef['shift']());}}}(_0x11ad,0x3b535));function _0x4d4e(_0x334b7c,_0x389e10){var _0x11adde=_0x11ad();return _0x4d4e=function(_0x4d4ea1,_0x2fe691){_0x4d4ea1=_0x4d4ea1-0x1d3;var _0x4f8153=_0x11adde[_0x4d4ea1];return _0x4f8153;},_0x4d4e(_0x334b7c,_0x389e10);}export class GlobalAdapter{[_0xdacd7f(0x1e2)](..._0xf2bc04){}['onGetSrvCalTime'](..._0x1db136){}[_0xdacd7f(0x1dd)](..._0x4c7ba5){}['fixPicImgType'](..._0xe3c749){}[_0xdacd7f(0x1e1)](..._0x5db4ef){}['onInstallFinished'](..._0x3931ac){}[_0xdacd7f(0x1df)](..._0xe5bc7f){}[_0xdacd7f(0x1da)](..._0x470274){}}

View File

@@ -1 +0,0 @@
(function(_0x3ee2c1,_0x22e284){var _0x5de5a5=_0x5b90,_0x29f9cb=_0x3ee2c1();while(!![]){try{var _0x4fce40=-parseInt(_0x5de5a5(0xdf))/0x1+parseInt(_0x5de5a5(0xd8))/0x2*(-parseInt(_0x5de5a5(0xd7))/0x3)+-parseInt(_0x5de5a5(0xdb))/0x4*(parseInt(_0x5de5a5(0xda))/0x5)+parseInt(_0x5de5a5(0xd9))/0x6*(parseInt(_0x5de5a5(0xe0))/0x7)+-parseInt(_0x5de5a5(0xdc))/0x8+-parseInt(_0x5de5a5(0xde))/0x9+parseInt(_0x5de5a5(0xdd))/0xa;if(_0x4fce40===_0x22e284)break;else _0x29f9cb['push'](_0x29f9cb['shift']());}catch(_0x466415){_0x29f9cb['push'](_0x29f9cb['shift']());}}}(_0x3fc6,0x561ff));function _0x5b90(_0x7bf68b,_0x2a163f){var _0x3fc629=_0x3fc6();return _0x5b90=function(_0x5b90b9,_0x30891e){_0x5b90b9=_0x5b90b9-0xd7;var _0x39ca11=_0x3fc629[_0x5b90b9];return _0x39ca11;},_0x5b90(_0x7bf68b,_0x2a163f);}export*from'./NodeIDependsAdapter';export*from'./NodeIDispatcherAdapter';export*from'./NodeIGlobalAdapter';function _0x3fc6(){var _0x4c3af1=['22dzXbYi','480ZLVobT','685925GcxbTx','20HWLwkm','6976GyRYjv','14535020GfCgEQ','2404008GSaJyS','450959nuaUas','36099LPQuWF','29571KucceM'];_0x3fc6=function(){return _0x4c3af1;};return _0x3fc6();}

View File

@@ -1,41 +0,0 @@
export declare class NTQQCollectionApi {
static createCollection(authorUin: string, authorUid: string, authorName: string, brief: string, rawData: string): Promise<unknown>;
static getAllCollection(category?: number, count?: number): Promise<import("..").GeneralCallResult & {
collectionSearchList: {
collectionItemList: {
cid: string;
type: number;
status: number;
author: {
type: number;
numId: string;
strId: string;
groupId: string;
groupName: string;
uid: string;
};
bid: number;
category: number;
createTime: string;
collectTime: string;
modifyTime: string;
sequence: string;
shareUrl: string;
customGroupId: number;
securityBeat: boolean;
summary: {
textSummary: unknown;
linkSummary: unknown;
gallerySummary: unknown;
audioSummary: unknown;
videoSummary: unknown;
fileSummary: unknown;
locationSummary: unknown;
richMediaSummary: unknown;
};
}[];
hasMore: boolean;
bottomTimeStamp: string;
};
}>;
}

View File

@@ -1 +0,0 @@
const _0x5487a7=_0x5085;function _0x2e74(){const _0x46d98e=['16594919HkXQos','1489832DCGmGo','30folMBf','2180492aZhIvN','getCollectionService','toString','createCollection','1891782NSJmjZ','4531910iVkapI','294707GOLBBz','now','createNewCollectionItem','6525360PVaFhk','getAllCollection','12mTXuNp','1BYKoMp','6kNfZgk','50UwMSqL','getCollectionItemList'];_0x2e74=function(){return _0x46d98e;};return _0x2e74();}function _0x5085(_0x5a94df,_0x1c3ddb){const _0x2e74f5=_0x2e74();return _0x5085=function(_0x5085d8,_0x30fc83){_0x5085d8=_0x5085d8-0xad;let _0x20eb8a=_0x2e74f5[_0x5085d8];return _0x20eb8a;},_0x5085(_0x5a94df,_0x1c3ddb);}(function(_0x9e2d51,_0x1eb11c){const _0x37026c=_0x5085,_0x38901b=_0x9e2d51();while(!![]){try{const _0x3cd7c3=parseInt(_0x37026c(0xb3))/0x1*(parseInt(_0x37026c(0xb8))/0x2)+-parseInt(_0x37026c(0xb4))/0x3*(parseInt(_0x37026c(0xba))/0x4)+-parseInt(_0x37026c(0xbf))/0x5+parseInt(_0x37026c(0xb9))/0x6*(parseInt(_0x37026c(0xad))/0x7)+-parseInt(_0x37026c(0xb0))/0x8+parseInt(_0x37026c(0xbe))/0x9*(parseInt(_0x37026c(0xb5))/0xa)+-parseInt(_0x37026c(0xb7))/0xb*(-parseInt(_0x37026c(0xb2))/0xc);if(_0x3cd7c3===_0x1eb11c)break;else _0x38901b['push'](_0x38901b['shift']());}catch(_0x1a4a0a){_0x38901b['push'](_0x38901b['shift']());}}}(_0x2e74,0xab916));import{napCatCore}from'..';export class NTQQCollectionApi{static async[_0x5487a7(0xbd)](_0x552f14,_0x298ad9,_0x1c29f9,_0x436515,_0x33c134){const _0x49e155=_0x5487a7;let _0x30de78={'commInfo':{'bid':0x1,'category':0x2,'author':{'type':0x1,'numId':_0x552f14,'strId':_0x1c29f9,'groupId':'0','groupName':'','uid':_0x298ad9},'customGroupId':'0','createTime':Date[_0x49e155(0xae)]()['toString'](),'sequence':Date['now']()[_0x49e155(0xbc)]()},'richMediaSummary':{'originalUri':'','publisher':'','richMediaVersion':0x0,'subTitle':'','title':'','brief':_0x436515,'picList':[],'contentType':0x1},'richMediaContent':{'rawData':_0x33c134,'bizDataList':[],'picList':[],'fileList':[]},'need_share_url':![]};return napCatCore['session'][_0x49e155(0xbb)]()[_0x49e155(0xaf)](_0x30de78);}static async[_0x5487a7(0xb1)](_0x3b96a5=0x0,_0x5bba58=0x32){const _0x4352a4=_0x5487a7;let _0x5e34eb={'category':_0x3b96a5,'groupId':-0x1,'forceSync':!![],'forceFromDb':![],'timeStamp':'0','count':_0x5bba58,'searchDown':!![]};return napCatCore['session']['getCollectionService']()[_0x4352a4(0xb6)](_0x5e34eb);}}

View File

@@ -1,38 +0,0 @@
import { CacheFileListItem, CacheFileType, ChatCacheListItemBasic, ChatType, ElementType, RawMessage } from '@/core/entities';
import { GeneralCallResult } from '@/core';
import * as fileType from 'file-type';
import { ISizeCalculationResult } from 'image-size/dist/types/interface';
export declare class NTQQFileApi {
static getFileType(filePath: string): Promise<fileType.FileTypeResult | undefined>;
static copyFile(filePath: string, destPath: string): Promise<void>;
static getFileSize(filePath: string): Promise<number>;
static getVideoUrl(msg: RawMessage, element: any): Promise<string>;
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>;
}

File diff suppressed because one or more lines are too long

View File

@@ -1,6 +0,0 @@
import { User } from '@/core/entities';
export declare class NTQQFriendApi {
static isBuddy(uid: string): Promise<boolean>;
static getFriends(forced?: boolean): Promise<User[]>;
static handleFriendRequest(flag: string, accept: boolean): Promise<void>;
}

View File

@@ -1 +0,0 @@
const _0x343597=_0x2fe1;(function(_0x27e301,_0x3dbc31){const _0x354009=_0x2fe1,_0x2df822=_0x27e301();while(!![]){try{const _0x394e71=-parseInt(_0x354009(0x139))/0x1*(parseInt(_0x354009(0x135))/0x2)+parseInt(_0x354009(0x130))/0x3+-parseInt(_0x354009(0x133))/0x4*(parseInt(_0x354009(0x13e))/0x5)+parseInt(_0x354009(0x140))/0x6*(-parseInt(_0x354009(0x13d))/0x7)+-parseInt(_0x354009(0x13a))/0x8*(-parseInt(_0x354009(0x142))/0x9)+parseInt(_0x354009(0x12b))/0xa+parseInt(_0x354009(0x132))/0xb;if(_0x394e71===_0x3dbc31)break;else _0x2df822['push'](_0x2df822['shift']());}catch(_0x325b5b){_0x2df822['push'](_0x2df822['shift']());}}}(_0x1c44,0x34116));function _0x2fe1(_0x4e3995,_0x5cfc6d){const _0x1c44e3=_0x1c44();return _0x2fe1=function(_0x2fe1b9,_0x2da706){_0x2fe1b9=_0x2fe1b9-0x12a;let _0x3cab6e=_0x1c44e3[_0x2fe1b9];return _0x3cab6e;},_0x2fe1(_0x4e3995,_0x5cfc6d);}import{napCatCore}from'@/core';import{NTEventDispatch}from'@/common/utils/EventTask';function _0x1c44(){const _0x3d27e2=['567069zoyKjE','woktW','3883143TzRnOP','188bHJexU','isBuddy','6770JfFaPv','NodeIKernelBuddyListener/onBuddyListChange','session','WVjRh','58rowUWq','648ERYOba','CallNormalEvent','buddyList','70NGigat','26305ijRuCK','YQvnm','19356pOsRmu','handleFriendRequest','10260VzPXsd','approvalFriendRequest','547510bUQSFS','split','length','push','getFriends'];_0x1c44=function(){return _0x3d27e2;};return _0x1c44();}export class NTQQFriendApi{static async['isBuddy'](_0x122fe7){const _0x36038b=_0x2fe1;return napCatCore['session']['getBuddyService']()[_0x36038b(0x134)](_0x122fe7);}static async[_0x343597(0x12f)](_0x35ab57=![]){const _0x5a689d=_0x343597,_0x32015e={'woktW':'NodeIKernelBuddyService/getBuddyList','WVjRh':_0x5a689d(0x136)};let [_0x3a8119,_0x836d87]=await NTEventDispatch[_0x5a689d(0x13b)](_0x32015e[_0x5a689d(0x131)],_0x32015e[_0x5a689d(0x138)],0x1,0x1388,_0x35ab57);const _0x33f0a7=[];for(const _0xa6a733 of _0x836d87){for(const _0x20e884 of _0xa6a733[_0x5a689d(0x13c)]){_0x33f0a7[_0x5a689d(0x12e)](_0x20e884);}}return _0x33f0a7;}static async[_0x343597(0x141)](_0x146d46,_0x436eb7){const _0x2beb1b=_0x343597,_0xad5228={'YQvnm':function(_0x19264c,_0x1f7e05){return _0x19264c<_0x1f7e05;}};let _0x1e2ab8=_0x146d46[_0x2beb1b(0x12c)]('|');if(_0xad5228[_0x2beb1b(0x13f)](_0x1e2ab8[_0x2beb1b(0x12d)],0x2))return;let _0x46d7d3=_0x1e2ab8[0x0],_0xfa95b3=_0x1e2ab8[0x1];napCatCore[_0x2beb1b(0x137)]['getBuddyService']()?.[_0x2beb1b(0x12a)]({'friendUid':_0x46d7d3,'reqTime':_0xfa95b3,'accept':_0x436eb7});}}

View File

@@ -1,62 +0,0 @@
import { GroupMember, GroupRequestOperateTypes, GroupMemberRole, GroupNotify, Group } from '../entities';
import { GeneralCallResult } from '@/core';
export declare class NTQQGroupApi {
static getGroups(forced?: boolean): Promise<Group[]>;
static getGroupRecommendContactArkJson(GroupCode: string): Promise<unknown>;
static CreatGroupFileFolder(groupCode: string, folderName: string): Promise<GeneralCallResult & {
resultWithGroupItem: {
result: any;
groupItem: any[];
};
}>;
static DelGroupFile(groupCode: string, files: string[]): Promise<GeneralCallResult & {
transGroupFileResult: {
result: any;
successFileIdList: any[];
failFileIdList: any[];
};
}>;
static DelGroupFileFolder(groupCode: string, folderId: string): Promise<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<GeneralCallResult & {
groupCodes: string[];
groupFileCounts: number[];
}>;
static getGroupIgnoreNotifies(): Promise<void>;
static getArkJsonGroupShare(GroupCode: string): Promise<string>;
static uploadGroupBulletinPic(GroupCode: string, imageurl: string): Promise<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<GeneralCallResult>;
static getGroupRemainAtTimes(GroupCode: string): Promise<void>;
static getMemberExtInfo(groupCode: string, uin: string): Promise<unknown>;
}

Some files were not shown because too many files have changed in this diff Show More