Compare commits

...

854 Commits

Author SHA1 Message Date
手瓜一十雪
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
手瓜一十雪
b9d1d84716 release: v1.5.4 2024-06-11 15:20:11 +08:00
手瓜一十雪
64b2d547ce refactor: friend 2024-06-11 12:34:04 +08:00
手瓜一十雪
d8d2ff7e4e chore:appid 2024-06-10 19:00:05 +08:00
手瓜一十雪
8aa5dc6482 Merge pull request #62 from NapNeko/dependabot/npm_and_yarn/uuid-10.0.0
build(deps): bump uuid from 9.0.1 to 10.0.0
2024-06-10 16:46:05 +08:00
dependabot[bot]
474ba20e61 build(deps): bump uuid from 9.0.1 to 10.0.0
Bumps [uuid](https://github.com/uuidjs/uuid) from 9.0.1 to 10.0.0.
- [Changelog](https://github.com/uuidjs/uuid/blob/main/CHANGELOG.md)
- [Commits](https://github.com/uuidjs/uuid/compare/v9.0.1...v10.0.0)

---
updated-dependencies:
- dependency-name: uuid
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-10 08:12:09 +00:00
手瓜一十雪
bdea2d02a9 release: v1.5.3 2024-06-09 20:09:14 +08:00
手瓜一十雪
c4307481f1 build: 1.5.3-beta4 2024-06-09 19:47:16 +08:00
手瓜一十雪
b8ac1b28bd build: v1.5.3-beta3 2024-06-09 19:41:27 +08:00
手瓜一十雪
24038cda95 refactor: video url 2024-06-09 19:40:34 +08:00
手瓜一十雪
86c82e9608 build: 1.5.3-beta2 2024-06-09 19:14:27 +08:00
手瓜一十雪
daab5d150b refactor: friend add 2024-06-09 19:13:49 +08:00
手瓜一十雪
9ff82bdb90 feat: support SetLongNick 2024-06-09 18:28:09 +08:00
手瓜一十雪
c6d70ef1cf build: 1.5.3-beta1 2024-06-09 17:46:32 +08:00
手瓜一十雪
15d4bb3c76 feat: new api 2024-06-09 17:30:30 +08:00
手瓜一十雪
3e698981fd chore: sync core 2024-06-09 14:26:02 +08:00
手瓜一十雪
9d45c934a5 chore: sync core 2024-06-08 22:13:05 +08:00
手瓜一十雪
c2bf9cf93e chore: sync core 2024-06-08 12:11:04 +08:00
手瓜一十雪
b3c6fd7f26 chore: sync core 2024-06-07 17:59:33 +08:00
手瓜一十雪
ccd155de71 feat: 推荐联系人ArkJson 2024-06-07 17:57:33 +08:00
手瓜一十雪
1f90d2e46b feat: 支持专属头衔获取 2024-06-07 17:26:00 +08:00
手瓜一十雪
4c5d974c22 feat: 支持专属头衔获取 2024-06-07 17:25:34 +08:00
手瓜一十雪
392eda1cbc fix: typo 2024-06-07 13:19:22 +08:00
手瓜一十雪
a9da3279e8 docs: change 2024-06-07 13:01:15 +08:00
手瓜一十雪
1ce8351180 docs: develop 2024-06-07 09:49:31 +08:00
手瓜一十雪
96c334478a docs: todo 2024-06-07 00:39:09 +08:00
手瓜一十雪
f1b0875b05 feat: ArkJsonGroupShare 2024-06-06 17:20:34 +08:00
手瓜一十雪
cea9e11c83 docs: add 2024-06-06 14:31:45 +08:00
手瓜一十雪
f098b39200 refactor: uins 2024-06-06 13:34:05 +08:00
手瓜一十雪
012d948b59 chore: sync 2024-06-06 13:23:15 +08:00
手瓜一十雪
3334cd0a71 docs: change 2024-06-06 12:18:17 +08:00
手瓜一十雪
d63d53fd88 docs: move 2024-06-06 11:05:30 +08:00
手瓜一十雪
a7fa39b2fd fix: message_id error 2024-06-06 11:03:29 +08:00
Version
40bb42e193 chore:version change 2024-06-05 10:30:26 +00:00
手瓜一十雪
9c382c639b build: v1.5.2-api兼容 2024-06-05 15:04:04 +08:00
手瓜一十雪
a43cde38f1 refactor: SetMsgEmojiLike 2024-06-05 15:03:14 +08:00
手瓜一十雪
c35d2e08cd refactor: reboot req params 2024-06-05 14:25:21 +08:00
手瓜一十雪
3377c383c1 build: v1.5.2 - GetMsg兼容 2024-06-05 12:14:41 +08:00
手瓜一十雪
c00e6d95cd build: v1.5.2-beta3 2024-06-05 11:23:42 +08:00
手瓜一十雪
725fccf4ed refactor: GoCQHTTP_GetStrangerInfo 2024-06-05 11:19:34 +08:00
手瓜一十雪
13129bd219 build: v1.5.2-beta2 2024-06-05 10:41:40 +08:00
手瓜一十雪
4561977bcf chore: sync core 2024-06-05 10:36:39 +08:00
手瓜一十雪
40be8a91f5 fix 2024-06-05 10:33:36 +08:00
手瓜一十雪
2a04d5830b fix: kick member 2024-06-04 23:52:46 +08:00
手瓜一十雪
82a38574f3 build: v1.5.2-beta 2024-06-04 23:16:33 +08:00
手瓜一十雪
fea3a33c2b refactor: Uid <-> Uin & Remove Cached 2024-06-04 23:13:18 +08:00
手瓜一十雪
9a502cdf6f refactor: uin - > uid 2024-06-04 23:06:53 +08:00
手瓜一十雪
4b616299cf refactor: uin -> uid 2024-06-04 22:50:16 +08:00
手瓜一十雪
102243e064 sync: core 2024-06-04 21:15:29 +08:00
手瓜一十雪
4b21ac5ebe fix: nt event 2024-06-04 21:09:19 +08:00
手瓜一十雪
4dd7363dd3 Merge branch 'main' of https://github.com/NapNeko/NapCatQQ 2024-06-04 12:39:08 +08:00
手瓜一十雪
3d5e5ab78f release: v1.5.1 2024-06-04 12:38:54 +08:00
手瓜一十雪
73045a1b21 Merge pull request #57 from Icexbb/main
fix: 整理日志、添加颜色、使用统一的日志函数以提高日志可读性
2024-06-04 12:37:54 +08:00
手瓜一十雪
871173a7cf fix: 二维码显示 2024-06-04 12:37:35 +08:00
手瓜一十雪
0002313093 Merge branch 'main' into pr/57 2024-06-04 12:31:43 +08:00
手瓜一十雪
948cf5cca6 refactror: cpmoudle
此处非NC运行时的代码 用于打包时运行
2024-06-04 12:26:19 +08:00
手瓜一十雪
d40230879c fix: 玄学的问题 2024-06-04 12:20:42 +08:00
XBB
ab22b775f1 fix: 整理日志、添加颜色、使用统一的日志函数以提高日志可读性 2024-06-04 02:59:35 +08:00
手瓜一十雪
42c85224ba build: v1.5.1-beta1 2024-06-03 21:18:24 +08:00
手瓜一十雪
e57444a353 sync: core 2024-06-03 21:11:21 +08:00
手瓜一十雪
3c6503d495 feat: support SetSelfProfile 2024-06-03 21:02:43 +08:00
手瓜一十雪
149b518f48 release: v1.5.0 2024-06-03 17:15:43 +08:00
手瓜一十雪
74621447ff fix: 提高兼容性 2024-06-03 17:12:53 +08:00
手瓜一十雪
3280952931 fix: 提高Api兼容性 2024-06-03 17:09:23 +08:00
手瓜一十雪
9e670e2736 Merge branch 'main' of https://github.com/NapNeko/NapCatQQ 2024-06-03 16:56:28 +08:00
手瓜一十雪
9fc6347a2f fix: 进一步标准化 2024-06-03 16:56:08 +08:00
Version
ec7a15a192 chore:version change 2024-06-03 08:50:25 +00:00
手瓜一十雪
7f99982810 release: v1.4.9 2024-06-03 16:49:08 +08:00
Version
935d83aaf8 chore:version change 2024-06-02 13:01:32 +00:00
手瓜一十雪
0ff6edd546 style: lint 2024-06-02 20:49:09 +08:00
手瓜一十雪
94f629585a build: v1.4.8-beta2 2024-06-02 20:40:41 +08:00
手瓜一十雪
89c04be02f fix 2024-06-02 13:58:51 +08:00
手瓜一十雪
3151965ea8 build: 1.3.8-beta1 2024-06-02 13:45:48 +08:00
手瓜一十雪
bdf5159be1 refactor: guid 2024-06-02 13:38:33 +08:00
手瓜一十雪
0499ebbea3 release: v1.4.7 2024-06-01 15:52:06 +08:00
手瓜一十雪
d5843b7236 refactor: v1.4.6 2024-06-01 14:09:16 +08:00
手瓜一十雪
1c9c574a90 refactor: v1.4.6 2024-06-01 14:08:10 +08:00
手瓜一十雪
39acf20e48 release: v1.4.6 2024-06-01 14:05:44 +08:00
手瓜一十雪
52eb6ed5ab refactor: group call 2024-06-01 14:02:43 +08:00
手瓜一十雪
ee78d2d59d fix: type hint 2024-06-01 12:14:37 +08:00
手瓜一十雪
60dc5c4a38 refactor: re groupList 2024-06-01 12:10:50 +08:00
手瓜一十雪
50a0dc0355 refactor: GroupListGet 2024-06-01 11:38:50 +08:00
手瓜一十雪
3f681ec914 refactor:NT Event Finish 2024-05-31 23:33:23 +08:00
手瓜一十雪
0bf499f191 release: v1.4.5 2024-05-31 21:25:47 +08:00
手瓜一十雪
389695a0d6 fix: 1.4.5 2024-05-31 21:22:17 +08:00
手瓜一十雪
07f1afb312 fix 2024-05-31 21:03:00 +08:00
手瓜一十雪
ae91e61304 refactor:NTEvent 2024-05-31 21:02:39 +08:00
手瓜一十雪
6248991b01 refactor: NTEvent 2024-05-31 20:38:31 +08:00
手瓜一十雪
7f2d57ef62 refactor: event 2024-05-31 20:29:01 +08:00
手瓜一十雪
31f8f884f1 refactor: NTEvent 2024-05-31 19:09:03 +08:00
手瓜一十雪
4f4af5985a fix: type check & type output 2024-05-31 18:55:18 +08:00
手瓜一十雪
a716fdf6d4 refactor:NTEventDispatch 2024-05-31 14:07:35 +08:00
手瓜一十雪
9717f64abd refactor:NTEvent 2024-05-31 13:55:28 +08:00
手瓜一十雪
adf239183a docs: change 2024-05-31 10:12:59 +08:00
手瓜一十雪
6cf209c79c release: v1.4.4 2024-05-30 22:45:57 +08:00
手瓜一十雪
decc5fb3c0 refactor: checkDate 2024-05-30 22:41:51 +08:00
手瓜一十雪
1e0820d613 refactor: send rate 2024-05-30 22:38:02 +08:00
手瓜一十雪
70124d5177 refactor: GoCQHTTPUploadGroupFile 2024-05-30 22:32:09 +08:00
手瓜一十雪
269de65201 fix: undel 2024-05-30 20:53:58 +08:00
手瓜一十雪
1d11abbfb6 refactor: NTEvent 2024-05-30 19:40:40 +08:00
手瓜一十雪
700f308d6e feat: wrap NT-Event 2024-05-30 17:28:08 +08:00
手瓜一十雪
21b6928ca6 chore: sync core 2024-05-30 16:24:09 +08:00
手瓜一十雪
998c67a649 release: v1.4.3 2024-05-30 16:21:39 +08:00
Version
fb99e878b0 chore:version change 2024-05-30 04:36:42 +00:00
手瓜一十雪
1619adfc27 release: v1.4.2 2024-05-30 12:36:15 +08:00
手瓜一十雪
5510fb473f fix: typo 2024-05-30 12:02:47 +08:00
手瓜一十雪
be1878cb2b build: 1.4.2-fix:file list 2024-05-30 11:01:34 +08:00
手瓜一十雪
15ab121cbd fix: config 2024-05-29 14:26:45 +08:00
手瓜一十雪
aa79b0e861 fix: ocr 2024-05-29 14:18:43 +08:00
手瓜一十雪
b80e550bcd docs: 1.4.2 2024-05-29 12:14:17 +08:00
507 changed files with 19469 additions and 12762 deletions

View File

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

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_TYPE = Production
VITE_BUILD_PLATFORM = Shell

View File

@@ -1,68 +1,64 @@
module.exports = { module.exports = {
'env': { 'env': {
'browser': true, 'browser': true,
'es2021': true, 'es2021': true,
'node': true
},
'ignorePatterns': ['src/core/', 'src/core.lib/','src/proto/'],
'extends': [
'eslint:recommended',
'plugin:@typescript-eslint/recommended'
],
'overrides': [
{
'env': {
'node': 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': { 'ignorePatterns': ['src/proto/'],
'typescript': { 'extends': [
'alwaysTryTypes': true '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: on:
workflow_dispatch: workflow_dispatch:
push: push:
@@ -8,14 +8,9 @@ on:
permissions: write-all permissions: write-all
jobs: jobs:
build-linux: Build-LiteLoader:
if: ${{ startsWith(github.event.head_commit.message, 'build:') }} if: ${{ startsWith(github.event.head_commit.message, 'build:') }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
target_platform: [linux]
target_arch: [x64, arm64]
steps: steps:
- name: Clone Main Repository - name: Clone Main Repository
uses: actions/checkout@v4 uses: actions/checkout@v4
@@ -28,26 +23,22 @@ jobs:
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
node-version: 20.x node-version: 20.x
- name: Build NuCat Linux - name: Build NuCat Framework
run: | run: |
npm i --arch=${{ matrix.target_arch }} --platform=${{ matrix.target_platform }} npm i
npm run build:prod npm run build:framework
cd dist cd dist
npm i --omit=dev --arch=${{ matrix.target_arch }} --platform=${{ matrix.target_platform }} npm i --omit=dev
rm package-lock.json
cd .. cd ..
- name: Upload Artifact - name: Upload Artifact
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: NapCat.${{ matrix.target_platform }}.${{ matrix.target_arch }} name: NapCat.Framework
path: dist path: dist
build-win32: Build-Shell:
if: ${{ startsWith(github.event.head_commit.message, 'build:') }} if: ${{ startsWith(github.event.head_commit.message, 'build:') }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
target_platform: [win32]
target_arch: [x64]
steps: steps:
- name: Clone Main Repository - name: Clone Main Repository
uses: actions/checkout@v4 uses: actions/checkout@v4
@@ -60,15 +51,16 @@ jobs:
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
node-version: 20.x node-version: 20.x
- name: Build NuCat Linux - name: Build NuCat LiteLoader
run: | run: |
npm i --arch=${{ matrix.target_arch }} --platform=${{ matrix.target_platform }} npm i
npm run build:prod npm run build:shell
cd dist cd dist
npm i --omit=dev --arch=${{ matrix.target_arch }} --platform=${{ matrix.target_platform }} npm i --omit=dev
rm package-lock.json
cd .. cd ..
- name: Upload Artifact - name: Upload Artifact
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: NapCat.${{ matrix.target_platform }}.${{ matrix.target_arch }} name: NapCat.Shell
path: dist path: dist

View File

@@ -1,4 +1,4 @@
name: "release" name: "Build Release"
on: on:
push: push:
@@ -30,14 +30,9 @@ jobs:
ls ls
node ./script/checkVersion.cjs node ./script/checkVersion.cjs
sh ./checkVersion.sh sh ./checkVersion.sh
build-linux: Build-LiteLoader:
needs: [check-version] needs: [check-version]
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
target_platform: [linux]
target_arch: [x64, arm64]
steps: steps:
- name: Clone Main Repository - name: Clone Main Repository
uses: actions/checkout@v4 uses: actions/checkout@v4
@@ -51,28 +46,21 @@ jobs:
with: with:
node-version: 20.x node-version: 20.x
- name: Build NuCat Linux - name: Build NuCat Framework
run: | run: |
export NAPCAT_BUILDSYS=${{ matrix.target_platform }} npm i
export NAPCAT_BUILDARCH=${{ matrix.target_arch }} npm run build:framework
npm i --arch=${{ matrix.target_arch }} --platform=${{ matrix.target_platform }}
npm run build:prod
cd dist cd dist
npm i --omit=dev --arch=${{ matrix.target_arch }} --platform=${{ matrix.target_platform }} npm i --omit=dev
cd .. cd ..
- name: Upload Artifact - name: Upload Artifact
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: NapCat.${{ matrix.target_platform }}.${{ matrix.target_arch }} name: NapCat.Framework
path: dist path: dist
build-win32: Build-Shell:
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: [check-version] needs: [check-version]
strategy:
fail-fast: false
matrix:
target_platform: [win32]
target_arch: [x64]
steps: steps:
- name: Clone Main Repository - name: Clone Main Repository
uses: actions/checkout@v4 uses: actions/checkout@v4
@@ -87,24 +75,22 @@ jobs:
with: with:
node-version: 20.x node-version: 20.x
- name: Build NuCat Linux - name: Build NuCat Shell
run: | run: |
export NAPCAT_BUILDSYS=${{ matrix.target_platform }} npm i
export NAPCAT_BUILDARCH=${{ matrix.target_arch }} npm run build:shell
npm i --arch=${{ matrix.target_arch }} --platform=${{ matrix.target_platform }}
npm run build:prod
cd dist cd dist
npm i --omit=dev --arch=${{ matrix.target_arch }} --platform=${{ matrix.target_platform }} npm i --omit=dev
cd .. cd ..
- name: Upload Artifact - name: Upload Artifact
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: NapCat.${{ matrix.target_platform }}.${{ matrix.target_arch }} name: NapCat.Shell
path: dist path: dist
release-napcat: release-napcat:
needs: [build-win32,build-linux] needs: [Build-LiteLoader,Build-Shell]
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Download All Artifact - name: Download All Artifact
@@ -130,9 +116,8 @@ jobs:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
body_path: CHANGELOG.md body_path: CHANGELOG.md
files: | files: |
NapCat.win32.x64.zip NapCat.Framework.zip
NapCat.linux.x64.zip NapCat.Shell.zip
NapCat.linux.arm64.zip
# NapCat.darwin.x64.zip # NapCat.darwin.x64.zip
# NapCat.darwin.arm64.zip # NapCat.darwin.arm64.zip
draft: true draft: true

3
.gitignore vendored
View File

@@ -14,4 +14,5 @@ dist/
# Build # Build
*.db *.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 Preamble
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all The licenses for most software are designed to take away your
copies or substantial portions of the Software. freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR When we speak of free software, we are referring to freedom, not
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, price. Our General Public Licenses are designed to make sure that you
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE have the freedom to distribute copies of free software (and charge for
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER this service if you wish), that you receive source code or can get it
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, if you want it, that you can change the software or use pieces of it
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE in new free programs; and that you know you can do these things.
SOFTWARE.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
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,33 @@
<img src="https://socialify.git.ci/NapNeko/NapCatQQ/image?description=1&language=1&logo=https%3A%2F%2Fraw.githubusercontent.com%2FNapNeko%2FNapCatQQ%2Fmain%2Flogo.png&name=1&stargazers=1&theme=Auto" alt="NapCatQQ" width="640" height="320" /> <img src="https://socialify.git.ci/NapNeko/NapCatQQ/image?description=1&language=1&logo=https%3A%2F%2Fraw.githubusercontent.com%2FNapNeko%2FNapCatQQ%2Fmain%2Flogo.png&name=1&stargazers=1&theme=Auto" alt="NapCatQQ" width="640" height="320" />
</div> </div>
---
## 项目介绍 ## 项目介绍
NapCatQQ 是现代化的基于 NTQQ 的 Bot 协议端实现。
NapCatQQ 是基于 PC NTQQ 本体实现一套无头 Bot 框架。 ## 项目优势
- [x] **多种启动方式**支持以无头、LiteLoader 插件、仅 QQ GUI 三种方式启动
名字寓意 瞌睡猫QQ像睡着了一样在后台低占用运行的无需GUI界面的NTQQ。 - [x] **低占用**:无头模式占用资源极低,适合在服务器上运行
- [x] **WebUI**:自带 WebUI 支持,远程管理更加便捷
## 如何使用 ## 如何使用
可前往 [Release](https://github.com/NapNeko/NapCatQQ/releases/) 页面下载最新版本 可前往 [Release](https://github.com/NapNeko/NapCatQQ/releases/) 页面下载最新版本
**首次使用** 请务必前往 [官方文档](https://napneko.github.io/) 查看使用文档与教程 **首次使用**请务必前往[官方文档](https://napneko.github.io/)查看使用教程
## 项目声明
* 请不要在无关地方宣传NapCatQQ本项目只是用于学习 node 相关知识,切勿用于违法用途
* NapCat 不会收集用户隐私信息,但是未来可能会为了更好的利于 NapCat 的优化会收集一些设备信息,如 cpu 架构,系统版本等
## 相关链接 ## 相关链接
[Telegram Link](https://t.me/+nLZEnpne-pQ1OWFl) [Telegram Link](https://t.me/+nLZEnpne-pQ1OWFl)
## 附加协议
禁止未授权任何项目使用Core部分代码用于二次开发与分发
## 鸣谢名单 ## 鸣谢名单
感谢 [LLOneBot](https://github.com/LLOneBot/LLOneBot) 提供初始版本基础
[Lagrange](https://github.com/LagrangeDev/Lagrange.Core) 感谢 [Lagrange](https://github.com/LagrangeDev/Lagrange.Core) 对本项目的大力支持
<!-- ---
QQ群545402644 **任何使用本仓库代码的地方,都应当严格遵守[本仓库开源许可](./LICENSE)。**
-->

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)

33
manifest.json Normal file
View File

@@ -0,0 +1,33 @@
{
"manifest_version": 4,
"type": "extension",
"name": "NapCat",
"slug": "NapCat",
"description": "OneBot v11 protocol implementation with NapCat logic",
"version": "2.0.9",
"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,33 +2,31 @@
"name": "napcat", "name": "napcat",
"private": true, "private": true,
"type": "module", "type": "module",
"version": "1.4.1", "version": "2.0.9",
"scripts": { "scripts": {
"watch:dev": "vite --mode development", "build:framework": "vite build --mode framework",
"watch:prod": "vite --mode production", "build:shell": "vite build --mode shell",
"build:dev": "vite build --mode development",
"build:prod": "vite build --mode production",
"build": "npm run build:dev",
"build:core": "cd ./src/core && npm run build && cd ../.. && node ./script/copy-core.cjs",
"build:webui": "cd ./src/webui && vite build", "build:webui": "cd ./src/webui && vite build",
"watch": "npm run watch:dev",
"debug-win": "powershell dist/napcat.ps1",
"lint": "eslint --fix src/**/*.{js,ts}", "lint": "eslint --fix src/**/*.{js,ts}",
"release": "npm run build:prod",
"depend": "cd dist && npm install --omit=dev" "depend": "cd dist && npm install --omit=dev"
}, },
"devDependencies": { "devDependencies": {
"@babel/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", "@log4js-node/log4js-api": "^1.0.2",
"@protobuf-ts/plugin": "^2.9.4",
"@rollup/plugin-node-resolve": "^15.2.3", "@rollup/plugin-node-resolve": "^15.2.3",
"@rollup/plugin-typescript": "^11.1.6", "@rollup/plugin-typescript": "^11.1.6",
"@types/cors": "^2.8.17", "@types/cors": "^2.8.17",
"@types/express": "^4.17.21", "@types/express": "^4.17.21",
"@types/figlet": "^1.5.8", "@types/figlet": "^1.5.8",
"@types/fluent-ffmpeg": "^2.1.24", "@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/qrcode-terminal": "^0.12.2",
"@types/uuid": "^9.0.8", "@types/ws": "^8.5.12",
"@types/ws": "^8.5.10",
"@typescript-eslint/eslint-plugin": "^7.4.0", "@typescript-eslint/eslint-plugin": "^7.4.0",
"@typescript-eslint/parser": "^7.4.0", "@typescript-eslint/parser": "^7.4.0",
"eslint": "^8.57.0", "eslint": "^8.57.0",
@@ -41,14 +39,16 @@
"rollup-plugin-obfuscator": "^1.1.0", "rollup-plugin-obfuscator": "^1.1.0",
"typescript": "^5.3.3", "typescript": "^5.3.3",
"vite": "^5.2.6", "vite": "^5.2.6",
"vite-plugin-babel": "^1.2.0",
"vite-plugin-cp": "^4.0.8", "vite-plugin-cp": "^4.0.8",
"vite-plugin-dts": "^3.8.2", "vite-plugin-dts": "^3.8.2",
"vite-tsconfig-paths": "^4.3.2", "vite-tsconfig-paths": "^4.3.2"
"@protobuf-ts/plugin": "^2.9.4"
}, },
"dependencies": { "dependencies": {
"ajv": "^8.13.0", "ajv": "^8.13.0",
"commander": "^12.0.0", "async-mutex": "^0.5.0",
"chalk": "^5.3.0",
"commander": "^12.1.0",
"cors": "^2.8.5", "cors": "^2.8.5",
"express": "^5.0.0-beta.2", "express": "^5.0.0-beta.2",
"fast-xml-parser": "^4.3.6", "fast-xml-parser": "^4.3.6",
@@ -58,9 +58,8 @@
"json-schema-to-ts": "^3.1.0", "json-schema-to-ts": "^3.1.0",
"log4js": "^6.9.1", "log4js": "^6.9.1",
"qrcode-terminal": "^0.12.0", "qrcode-terminal": "^0.12.0",
"silk-wasm": "^3.3.4", "silk-wasm": "^3.6.1",
"sqlite3": "^5.1.7", "strtok3": "8.0.1",
"uuid": "^9.0.1", "ws": "^8.18.0"
"ws": "^8.16.0"
} }
} }

View File

@@ -1,3 +1,5 @@
# Dont Use This Script
# 2024.7.3
function Get-QQpath { function Get-QQpath {
try { try {
$key = Get-ItemProperty -Path "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\QQ" $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" $Bootfile = Join-Path $PSScriptRoot "napcat.mjs"
$env:ELECTRON_RUN_AS_NODE = 1 $env:ELECTRON_RUN_AS_NODE = 1
$commandInfo = Get-Command $QQpath -ErrorAction Stop $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 %*

View File

@@ -11,7 +11,7 @@ try {
// 验证 targetVersion 格式 // 验证 targetVersion 格式
if (!targetVersion || typeof targetVersion !== 'string') { if (!targetVersion || typeof targetVersion !== 'string') {
console.error("[NapCat] [CheckVersion] 目标版本格式不正确或未设置!"); console.log("[NapCat] [CheckVersion] 目标版本格式不正确或未设置!");
return; return;
} }
@@ -38,5 +38,5 @@ try {
writeScriptToFile(safeScriptContent); writeScriptToFile(safeScriptContent);
} }
} catch (error) { } 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.9';
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,35 +0,0 @@
import { sleep } from '@/common/utils/helper';
type AsyncQueueTask = (() => void) | (()=>Promise<void>);
export class AsyncQueue {
private tasks: (AsyncQueueTask)[] = [];
public addTask(task: AsyncQueueTask) {
this.tasks.push(task);
// console.log('addTask', this.tasks.length);
if (this.tasks.length === 1) {
this.runQueue().then().catch(()=>{});
}
}
private async runQueue() {
// console.log('runQueue', this.tasks.length);
while (this.tasks.length > 0) {
const task = this.tasks[0];
// console.log('typeof task', typeof task);
try {
const taskRet = task();
// console.log('type of taskRet', typeof taskRet, taskRet);
if (taskRet instanceof Promise) {
await taskRet;
}
} catch (e) {
console.error(e);
}
this.tasks.shift();
await sleep(100);
}
}
}

View File

@@ -1,73 +1,67 @@
import path from 'node:path'; import path from 'node:path';
import fs from 'node:fs'; import fs from 'node:fs';
import { log, logDebug, logError } from '@/common/utils/log'; import type { NapCatCore } from '@/core';
import { dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
export abstract class ConfigBase<T> {
name: string;
coreContext: NapCatCore;
configPath: string;
configData: T = {} as T;
const __filename = fileURLToPath(import.meta.url); protected constructor(name: string, coreContext: NapCatCore, configPath: string) {
const __dirname = dirname(__filename); this.name = name;
this.coreContext = coreContext;
const configDir = path.resolve(__dirname, 'config'); this.configPath = configPath;
fs.mkdirSync(configDir, { recursive: true }); fs.mkdirSync(this.configPath, { recursive: true });
this.read();
export class ConfigBase<T>{
constructor() {
}
protected getKeys(): string[] | null {
// 决定 key 在json配置文件中的顺序
return null;
}
getConfigDir(){
const configDir = path.resolve(__dirname, 'config');
fs.mkdirSync(configDir, { recursive: true });
return configDir;
}
getConfigPath(): string {
throw new Error('Method not implemented.');
}
read() {
const configPath = this.getConfigPath();
if (!fs.existsSync(configPath)) {
try{
fs.writeFileSync(configPath, JSON.stringify(this, this.getKeys(), 2));
log(`配置文件${configPath}已创建\n如果修改此文件后需要重启 NapCat 生效`);
}
catch (e: any) {
logError(`创建配置文件 ${configPath} 时发生错误:`, e.message);
}
return this;
} }
try {
const data = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
logDebug(`配置文件${configPath}已加载`, data);
Object.assign(this, data);
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
this.save(this); // 保存一次,让新版本的字段写入
return this;
} catch (e: any) {
if (e instanceof SyntaxError) {
logError(`配置文件 ${configPath} 格式错误,请检查配置文件:`, e.message);
} else {
logError(`读取配置文件 ${configPath} 时发生错误:`, e.message);
}
return this;
}
}
save(config: T) { protected getKeys(): string[] | null {
Object.assign(this, config); // 决定 key 在json配置文件中的顺序
const configPath = this.getConfigPath(); return null;
try { }
fs.writeFileSync(configPath, JSON.stringify(this, this.getKeys(), 2));
} catch (e: any) { getConfigPath(pathName: string | undefined): string {
logError(`保存配置文件 ${configPath} 时发生错误:`, e.message); 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);
}
} }
}
} }

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

@@ -0,0 +1,150 @@
import { Peer } from '@/core';
import crypto from 'crypto';
export class LimitedHashTable<K, V> {
private keyToValue: Map<K, V> = new Map();
private valueToKey: Map<V, K> = new Map();
private maxSize: number;
constructor(maxSize: number) {
this.maxSize = maxSize;
}
resize(count: number) {
this.maxSize = count;
}
set(key: K, value: V): void {
// const isExist = this.keyToValue.get(key);
// if (isExist && isExist === value) {
// return;
// }
this.keyToValue.set(key, value);
this.valueToKey.set(value, key);
while (this.keyToValue.size !== this.valueToKey.size) {
//console.log('keyToValue.size !== valueToKey.size Error Atom');
this.keyToValue.clear();
this.valueToKey.clear();
}
// console.log('---------------');
// console.log(this.keyToValue);
// console.log(this.valueToKey);
// console.log('---------------');
while (this.keyToValue.size > this.maxSize || this.valueToKey.size > this.maxSize) {
//console.log(this.keyToValue.size > this.maxSize, this.valueToKey.size > this.maxSize);
const oldestKey = this.keyToValue.keys().next().value;
this.valueToKey.delete(this.keyToValue.get(oldestKey)!);
this.keyToValue.delete(oldestKey);
}
}
getValue(key: K): V | undefined {
return this.keyToValue.get(key);
}
getKey(value: V): K | undefined {
return this.valueToKey.get(value);
}
deleteByValue(value: V): void {
const key = this.valueToKey.get(value);
if (key !== undefined) {
this.keyToValue.delete(key);
this.valueToKey.delete(value);
}
}
deleteByKey(key: K): void {
const value = this.keyToValue.get(key);
if (value !== undefined) {
this.keyToValue.delete(key);
this.valueToKey.delete(value);
}
}
getKeyList(): K[] {
return Array.from(this.keyToValue.keys());
}
//获取最近刚写入的几个值
getHeads(size: number): { key: K; value: V }[] | undefined {
const keyList = this.getKeyList();
if (keyList.length === 0) {
return undefined;
}
const result: { key: K; value: V }[] = [];
const listSize = Math.min(size, keyList.length);
for (let i = 0; i < listSize; i++) {
const key = keyList[listSize - i];
result.push({ key, value: this.keyToValue.get(key)! });
}
return result;
}
}
class MessageUniqueWrapper {
private msgDataMap: LimitedHashTable<string, number>;
private msgIdMap: LimitedHashTable<string, number>;
constructor(maxMap: number = 1000) {
this.msgIdMap = new LimitedHashTable<string, number>(maxMap);
this.msgDataMap = new LimitedHashTable<string, number>(maxMap);
}
getRecentMsgIds(Peer: Peer, size: number): string[] {
const heads = this.msgIdMap.getHeads(size);
if (!heads) {
return [];
}
const data = heads.map((t) => MessageUnique.getMsgIdAndPeerByShortId(t.value));
const ret = data.filter((t) => t?.Peer.chatType === Peer.chatType && t?.Peer.peerUid === Peer.peerUid);
return ret.map((t) => t?.MsgId).filter((t) => t !== undefined);
}
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: MessageUniqueWrapper = new MessageUniqueWrapper();

View File

@@ -1,72 +1,81 @@
import path from 'node:path'; import path from 'node:path';
import fs from 'node:fs'; import fs from 'node:fs';
import os from 'node:os';
import { systemPlatform } from '@/common/utils/system'; import { systemPlatform } from '@/common/utils/system';
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'); constructor(context: { logger: LogWrapper }) {
let configVersionInfoPath; //基础目录获取
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'); this.isQuickUpdate = !!this.QQVersionConfigPath;
} else { this.QQVersionConfig = this.isQuickUpdate
const userPath = os.homedir(); ? JSON.parse(fs.readFileSync(this.QQVersionConfigPath!).toString())
const appDataPath = path.resolve(userPath, './.config/QQ'); : getDefaultQQVersionConfigInfo();
configVersionInfoPath = path.resolve(appDataPath, './versions/config.json'); 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') { export let QQBasicInfo: QQBasicInfoWrapper | undefined;
throw new Error('Something went wrong when load QQ info path');
}
export { configVersionInfoPath };
type QQPkgInfo = {
version: string;
buildVersion: string;
platform: string;
eleArch: string;
}
type QQVersionConfigInfo = {
baseVersion: string;
curVersion: string;
prevVersion: string;
onErrorVersions: Array<any>;
buildId: string;
}
let _qqVersionConfigInfo: QQVersionConfigInfo = {
'baseVersion': '9.9.9-23361',
'curVersion': '9.9.9-23361',
'prevVersion': '',
'onErrorVersions': [],
'buildId': '23361'
};
if (fs.existsSync(configVersionInfoPath)) {
try {
const _ =JSON.parse(fs.readFileSync(configVersionInfoPath).toString());
_qqVersionConfigInfo = Object.assign(_qqVersionConfigInfo, _);
} catch (e) {
console.error('Load QQ version config info failed, Use default version', e);
}
}
export const qqVersionConfigInfo: QQVersionConfigInfo = _qqVersionConfigInfo;
export const qqPkgInfo: QQPkgInfo = JSON.parse(fs.readFileSync(pkgInfoPath).toString());
// platform_type: 3,
// app_type: 4,
// app_version: '9.9.9-23159',
// qua: 'V1_WIN_NQ_9.9.9_23159_GW_B',
// appid: '537213764',
// platVer: '10.0.26100',
// clientVer: '9.9.9-23159',
let _appid: string = '537213803'; // 默认为 Windows 平台的 appid
if (systemPlatform === 'linux') {
_appid = '537213827';
}
// todo: mac 平台的 appid
export const appid = _appid;

View File

@@ -1,135 +1,90 @@
import fs from 'fs'; 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 fsPromise from 'fs/promises';
import { log, logError } from './log';
import path from 'node:path'; import path from 'node:path';
import { v4 as uuidv4 } from 'uuid'; import { randomUUID } from 'crypto';
import { spawn } from 'node:child_process'; 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 { try {
const buffer = fs.readFileSync(filePath, { const file = await fsPromise.readFile(filePath);
encoding: null, const pttPath = path.join(TEMP_DIR, randomUUID());
flag: 'r', if (!isSilk(file)) {
}); logger.log(`语音文件${filePath}需要转换成silk`);
const _isWav = isWav(file);
const fileHeader = buffer.toString('hex', 0, bytesToRead); const pcmPath = pttPath + '.pcm';
return fileHeader; let sampleRate = 0;
} catch (err) { const convert = () => {
console.error('读取文件错误:', err); return new Promise<Buffer>((resolve, reject) => {
return; // 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 => {
async function isWavFile(filePath: string) { logger.log('FFmpeg处理转换出错: ', err.message);
return isWav(fs.readFileSync(filePath)); return reject(err);
} });
cp.on('exit', (code, signal) => {
async function guessDuration(pttPath: string) { const EXIT_CODES = [0, 255];
const pttFileInfo = await fsPromise.stat(pttPath); if (code == null || EXIT_CODES.includes(code)) {
let duration = pttFileInfo.size / 1024 / 3; // 3kb/s sampleRate = 24000;
duration = Math.floor(duration); const data = fs.readFileSync(pcmPath);
duration = Math.max(1, duration); fs.unlink(pcmPath, (err) => {
log('通过文件大小估算语音的时长:', duration); });
return duration; return resolve(data);
} }
logger.log(`FFmpeg exit: code=${code ?? 'unknown'} sig=${signal ?? 'unknown'}`);
// function verifyDuration(oriDuration: number, guessDuration: number) { reject(Error('FFmpeg处理转换失败'));
// // 单位都是秒 });
// if (oriDuration - guessDuration > 10) { });
// return guessDuration };
// } let input: Buffer;
// oriDuration = Math.max(1, oriDuration) if (!_isWav) {
// return oriDuration input = await convert();
// } } else {
// async function getAudioSampleRate(filePath: string) { input = file;
// try { const allowSampleRate = [8000, 12000, 16000, 24000, 32000, 44100, 48000];
// const mm = await import('music-metadata'); const { fmt } = getWavFileInfo(input);
// const metadata = await mm.parseFile(filePath); // log(`wav文件信息`, fmt)
// log(`${filePath}采样率`, metadata.format.sampleRate); if (!allowSampleRate.includes(fmt.sampleRate)) {
// return metadata.format.sampleRate; input = await convert();
// } catch (error) { }
// log(`${filePath}采样率获取失败`, error.stack); }
// // console.error(error); const silk = await encode(input, sampleRate);
// } fs.writeFileSync(pttPath, silk.data);
// } logger.log(`语音文件${filePath}转换成功!`, pttPath, '时长:', silk.duration);
return {
try { converted: true,
const pttPath = path.join(TEMP_DIR, uuidv4()); path: pttPath,
if (getFileHeader(filePath) !== '02232153494c4b') { duration: silk.duration / 1000,
log(`语音文件${filePath}需要转换成silk`); };
const _isWav = await isWavFile(filePath); } else {
const pcmPath = pttPath + '.pcm'; const silk = file;
let sampleRate = 0; let duration = 0;
const convert = () => { try {
return new Promise<Buffer>((resolve, reject) => { duration = getDuration(silk) / 1000;
// todo: 通过配置文件获取ffmpeg路径 } catch (e: any) {
const ffmpegPath = process.env.FFMPEG_PATH || 'ffmpeg'; logger.log('获取语音文件时长失败, 使用文件大小推测时长', filePath, e.stack);
const cp = spawn(ffmpegPath, ['-y', '-i', filePath, '-ar', '24000', '-ac', '1', '-f', 's16le', pcmPath]); duration = await guessDuration(filePath);
cp.on('error', err => {
log('FFmpeg处理转换出错: ', err.message);
return reject(err);
});
cp.on('exit', (code, signal) => {
const EXIT_CODES = [0, 255];
if (code == null || EXIT_CODES.includes(code)) {
sampleRate = 24000;
const data = fs.readFileSync(pcmPath);
fs.unlink(pcmPath, (err) => {
});
return resolve(data);
} }
log(`FFmpeg exit: code=${code ?? 'unknown'} sig=${signal ?? 'unknown'}`);
reject(Error('FFmpeg处理转换失败'));
});
});
};
let input: Buffer;
if (!_isWav) {
input = await convert();
} else {
input = fs.readFileSync(filePath);
const allowSampleRate = [8000, 12000, 16000, 24000, 32000, 44100, 48000];
const { fmt } = getWavFileInfo(input);
// log(`wav文件信息`, fmt)
if (!allowSampleRate.includes(fmt.sampleRate)) {
input = await convert();
}
}
const silk = await encode(input, sampleRate);
fs.writeFileSync(pttPath, silk.data);
log(`语音文件${filePath}转换成功!`, pttPath, '时长:', silk.duration);
return {
converted: true,
path: pttPath,
duration: silk.duration / 1000
};
} else {
const silk = fs.readFileSync(filePath);
let duration = 0;
try {
duration = getDuration(silk) / 1000;
} catch (e: any) {
log('获取语音文件时长失败, 使用文件大小推测时长', filePath, e.stack);
duration = await guessDuration(filePath);
}
return { return {
converted: false, converted: false,
path: filePath, path: filePath,
duration, 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,24 +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,509 +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);
}
});
// 接收到的临时会话消息uid
const createTempUinTableSQL = `
CREATE TABLE IF NOT EXISTS temp_uins (
id INTEGER PRIMARY KEY AUTOINCREMENT,
uid TEXT,
uin TEXT
)`;
this.db!.run(createTempUinTableSQL, function (err) {
if (err) {
logError('Could not create table temp_uins', err);
}
});
}
private async getCurrentMaxShortId() {
return new Promise<number>((resolve, reject) => {
this.db!.get('SELECT MAX(shortId) as maxId FROM msgs', (err, row: { maxId: number }) => {
if (err) {
logDebug('Could not get max short id, Use default -2147483640', err);
return resolve(-2147483640);
}
logDebug('数据库中消息最大短id', row?.maxId);
resolve(row?.maxId ?? -2147483640);
});
});
}
private async getMsg(query: string, params: any[]) {
const stmt = this.db!.prepare(query);
return new Promise<RawMessage | null>((resolve, reject) => {
stmt.get(...params, (err: any, row: DBMsg) => {
// log("getMsg", row, err);
if (err) {
logError('Could not get msg', err, query, params);
return resolve(null);
}
if (!row) {
// logDebug('不存在数据库中的消息,不进行处理', query, params);
resolve(null);
return;
}
const msgId = row.longId;
NTQQMsgApi.getMsgsByMsgId({ peerUid: row.peerUid, chatType: row.chatType }, [msgId]).then(res => {
const msg = res.msgList[0];
if (!msg) {
resolve(null);
return;
}
msg.id = row.shortId;
resolve(msg);
}).catch(e => {
resolve(null);
});
});
});
}
async getMsgByShortId(shortId: number): Promise<RawMessage | null> {
if (this.msgCache.has(shortId)) {
return this.msgCache.get(shortId)!;
}
const getStmt = 'SELECT * FROM msgs WHERE shortId = ?';
return this.getMsg(getStmt, [shortId]);
}
async getMsgByLongId(longId: string): Promise<RawMessage | null> {
if (this.msgCache.has(longId)) {
return this.msgCache.get(longId)!;
}
return this.getMsg('SELECT * FROM msgs WHERE longId = ?', [longId]);
}
async getMsgBySeq(peerUid: string, seq: string): Promise<RawMessage | null> {
const stmt = 'SELECT * FROM msgs WHERE peerUid = ? AND seq = ?';
return this.getMsg(stmt, [peerUid, seq]);
}
async addMsg(msg: RawMessage, update = true): Promise<number> {
const existMsg = await this.getMsgByLongId(msg.msgId);
if (existMsg) {
// logDebug('消息已存在,更新数据库', msg.msgId);
if (update) this.updateMsg(msg).then();
return existMsg.id!;
}
const stmt = this.db!.prepare('INSERT INTO msgs (shortId, longId, seq, peerUid, chatType) VALUES (?, ?, ?, ?, ?)');
// const runAsync = promisify(stmt.run.bind(stmt));
const shortId = ++this.globalMsgShortId;
msg.id = shortId;
//logDebug(`记录消息到数据库, 消息长id: ${msg.msgId}, 短id: ${msg.id}`);
this.msgCache.set(shortId, msg);
this.msgCache.set(msg.msgId, msg);
stmt.run(this.globalMsgShortId, msg.msgId, msg.msgSeq.toString(), msg.peerUid, msg.chatType, (err: any) => {
if (err) {
if (err.errno === 19) {
this.getMsgByLongId(msg.msgId).then((msg: RawMessage | null) => {
if (msg) {
this.msgCache.set(shortId, msg);
this.msgCache.set(msg.msgId, msg);
// logDebug('获取消息短id成功', msg.id);
} else {
logError('db could not get msg by long id', err);
}
}).catch(e => logError('db getMsgByLongId error', e));
} else {
logError('db could not add msg', err);
}
}
});
return shortId;
}
async updateMsg(msg: RawMessage) {
const existMsg = this.msgCache.get(msg.msgId);
if (existMsg) {
Object.assign(existMsg, msg);
}
//logDebug(`更新消息, shortId:${msg.id}, seq: ${msg.msgSeq}, msgId: ${msg.msgId}`);
const stmt = this.db!.prepare('UPDATE msgs SET seq=? WHERE longId=?');
stmt.run(msg.msgSeq, msg.msgId, (err: any) => {
if (err) {
logError('updateMsg db error', err);
}
});
}
async addFileCache(file: DBFile) {
const stmt = this.db!.prepare('INSERT INTO files (name, path, url, size, uuid, elementType ,element, elementId, msgId) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)');
return new Promise((resolve, reject) => {
stmt.run(file.name, file.path, file.url, file.size, file.uuid,
file.elementType,
JSON.stringify(file.element),
file.elementId,
file.msgId,
function (err: any) {
if (err) {
logError('db could not add file', err);
reject(err);
}
resolve(null);
});
});
}
private async getFileCache(query: string, params: any[]) {
const stmt = this.db!.prepare(query);
return new Promise<DBFile | null>((resolve, reject) => {
stmt.get(...params, (err: any, row: DBFile & { element: string }) => {
if (err) {
logError('db could not get file cache', err);
reject(err);
}
if (row) {
row.element = JSON.parse(row.element);
}
resolve(row);
});
});
}
async getFileCacheByName(name: string): Promise<DBFile | null> {
return this.getFileCache('SELECT * FROM files WHERE name = ?', [name]);
}
async getFileCacheByUuid(uuid: string): Promise<DBFile | null> {
return this.getFileCache('SELECT * FROM files WHERE uuid = ?', [uuid]);
}
// todo: 是否所有的文件都有uuid语音消息有没有uuid
async updateFileCache(file: DBFile) {
const stmt = this.db!.prepare('UPDATE files SET path = ?, url = ? WHERE uuid = ?');
return new Promise((resolve, reject) => {
stmt.run(file.path, file.url, file.uuid, function (err: any) {
if (err) {
logError('db could not update file cache', err);
reject(err);
}
resolve(null);
});
});
}
// 被动收到的临时会话消息uin->uid
async getReceivedTempUinMap() {
const stmt = 'SELECT * FROM temp_uins';
return new Promise<Record<string, string>>((resolve, reject) => {
this.db!.all(stmt, (err, rows: { uin: string, uid: string }[]) => {
if (err) {
logError('db could not get temp uin map', err);
reject(err);
}
const map: Record<string, string> = {};
rows.forEach(row => {
map[row.uin] = row.uid;
});
resolve(map);
});
});
}
// 通过uin获取临时会话消息uid
async getUidByTempUin(uid: string) {
const stmt = 'SELECT * FROM temp_uins WHERE uin = ?';
return new Promise<string>((resolve, reject) => {
this.db!.get(stmt, [uid], (err, row: { uin: string, uid: string }) => {
if (err) {
logError('db could not get temp uin map', err);
reject(err);
}
resolve(row?.uid);
});
});
}
async addTempUin(uin: string, uid: string) {
const existUid = await this.getUidByTempUin(uin);
if (!existUid) {
const stmt = this.db!.prepare('INSERT INTO temp_uins (uin, uid) VALUES (?, ?)');
return new Promise((resolve, reject) => {
stmt.run(uin, uid, function (err: any) {
if (err) {
logError('db could not add temp uin', err);
reject(err);
}
resolve(null);
});
});
}
}
async getLastSentTimeAndJoinTime(
groupId: number
): Promise<IRember[]> {
logDebug("读取发言时间", groupId);
return new Promise<IRember[]>((resolve, reject) => {
this.db!.all(`SELECT * FROM "${groupId}" `, (err, rows: IRember[]) => {
if (err) {
logError("查询发言时间失败", groupId);
return resolve([]);
}
logDebug("查询发言时间成功", groupId, rows);
resolve(rows);
});
});
}
insertLastSentTime(
groupId: number,
userId: number,
time: number
) {
this.LURCache.set(groupId, userId, time)
}
async insertJoinTime(
groupId: number,
userId: number,
time: number
) {
await this.createGroupInfoTimeTableIfNotExist(groupId);
this.db!.all(
`INSERT OR REPLACE INTO "${groupId}" (user_id, last_sent_time, join_time) VALUES (?,?,?)`,
[userId, time, time],
(err) => {
if (err)
logError(err),
Promise.reject(),
console.log("插入入群时间失败", userId, groupId);
}
);
}
}
export const dbUtil = new DBUtil();

View File

@@ -1,273 +1,301 @@
import fs from 'fs'; import fs from 'fs';
import fsPromise from 'fs/promises'; import fsPromise, { stat } from 'fs/promises';
import crypto from 'crypto'; import crypto, { randomUUID } from 'crypto';
import util from 'util'; import util from 'util';
import path from 'node:path'; import path from 'node:path';
import { log } from './log';
import { dbUtil } from '@/common/utils/db';
import * as fileType from 'file-type'; import * as fileType from 'file-type';
import { v4 as uuidv4 } from 'uuid'; import { LogWrapper } from './log';
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) { export function isGIF(path: string) {
const buffer = Buffer.alloc(4); const buffer = Buffer.alloc(4);
const fd = fs.openSync(path, 'r'); const fd = fs.openSync(path, 'r');
fs.readSync(fd, buffer, 0, 4, 0); fs.readSync(fd, buffer, 0, 4, 0);
fs.closeSync(fd); fs.closeSync(fd);
return buffer.toString() === 'GIF8'; return buffer.toString() === 'GIF8';
} }
// 定义一个异步函数来检查文件是否存在 // 定义一个异步函数来检查文件是否存在
export function checkFileReceived(path: string, timeout: number = 3000): Promise<void> { export function checkFileReceived(path: string, timeout: number = 3000): Promise<void> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const startTime = Date.now(); const startTime = Date.now();
function check() { function check() {
if (fs.existsSync(path)) { if (fs.existsSync(path)) {
resolve(); resolve();
} else if (Date.now() - startTime > timeout) { } else if (Date.now() - startTime > timeout) {
reject(new Error(`文件不存在: ${path}`)); reject(new Error(`文件不存在: ${path}`));
} else { } else {
setTimeout(check, 100); setTimeout(check, 100);
} }
}
check();
});
}
// 定义一个异步函数来检查文件是否存在
export async function checkFileReceived2(path: string, timeout: number = 3000): Promise<void> {
// 使用 Promise.race 来同时进行文件状态检查和超时计时
// Promise.race 会返回第一个解决resolve或拒绝reject的 Promise
await Promise.race([
checkFile(path),
timeoutPromise(timeout, `文件不存在: ${path}`),
]);
}
// 转换超时时间至 Promise
function timeoutPromise(timeout: number, errorMsg: string): Promise<void> {
return new Promise((_, reject) => {
setTimeout(() => {
reject(new Error(errorMsg));
}, timeout);
});
}
// 异步检查文件是否存在
async function checkFile(path: string): Promise<void> {
try {
await stat(path);
} catch (error: any) {
if (error.code === 'ENOENT') {
// 如果文件不存在,则抛出一个错误
throw new Error(`文件不存在: ${path}`);
} else {
// 对于 stat 调用的其他错误,重新抛出
throw error;
}
} }
// 如果文件存在则无需做任何事情Promise 解决resolve自身
check();
});
} }
export async function file2base64(path: string) { export async function file2base64(path: string) {
const readFile = util.promisify(fs.readFile); const readFile = util.promisify(fs.readFile);
const result = { const result = {
err: '', err: '',
data: '' data: '',
}; };
try {
// 读取文件内容
// if (!fs.existsSync(path)){
// path = path.replace("\\Ori\\", "\\Thumb\\");
// }
try { try {
await checkFileReceived(path, 5000); // 读取文件内容
} catch (e: any) { // if (!fs.existsSync(path)){
result.err = e.toString(); // path = path.replace("\\Ori\\", "\\Thumb\\");
return result; // }
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); return result;
// 转换为Base64编码
result.data = data.toString('base64');
} catch (err: any) {
result.err = err.toString();
}
return result;
} }
export function calculateFileMD5(filePath: string): Promise<string> { export function calculateFileMD5(filePath: string): Promise<string> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
// 创建一个流式读取器 // 创建一个流式读取器
const stream = fs.createReadStream(filePath); const stream = fs.createReadStream(filePath);
const hash = crypto.createHash('md5'); const hash = crypto.createHash('md5');
stream.on('data', (data: Buffer) => { stream.on('data', (data: Buffer) => {
// 当读取到数据时,更新哈希对象的状态 // 当读取到数据时,更新哈希对象的状态
hash.update(data); hash.update(data);
}); });
stream.on('end', () => { stream.on('end', () => {
// 文件读取完成,计算哈希 // 文件读取完成,计算哈希
const md5 = hash.digest('hex'); const md5 = hash.digest('hex');
resolve(md5); resolve(md5);
}); });
stream.on('error', (err: Error) => { stream.on('error', (err: Error) => {
// 处理可能的读取错误 // 处理可能的读取错误
reject(err); reject(err);
});
}); });
});
} }
export interface HttpDownloadOptions { export interface HttpDownloadOptions {
url: string; url: string;
headers?: Record<string, string> | string; headers?: Record<string, string> | string;
} }
export async function httpDownload(options: string | HttpDownloadOptions): Promise<Buffer> { export async function httpDownload(options: string | HttpDownloadOptions): Promise<Buffer> {
const chunks: Buffer[] = []; const chunks: Buffer[] = [];
let url: string; let url: string;
let headers: Record<string, 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' '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') { if (typeof options === 'string') {
url = options; url = options;
} else { const host = new URL(url).hostname;
url = options.url; headers['Host'] = host;
if (options.headers) { } else {
if (typeof options.headers === 'string') { url = options.url;
headers = JSON.parse(options.headers); if (options.headers) {
} else { if (typeof options.headers === 'string') {
headers = options.headers; headers = JSON.parse(options.headers);
} } else {
headers = options.headers;
}
}
} }
} const fetchRes = await fetch(url, { headers }).catch((err) => {
const fetchRes = await fetch(url, { headers }); if (err.cause) {
if (!fetchRes.ok) throw new Error(`下载文件失败: ${fetchRes.statusText}`); throw err.cause;
}
throw err;
});
if (!fetchRes.ok) throw new Error(`下载文件失败: ${fetchRes.statusText}`);
const blob = await fetchRes.blob(); const blob = await fetchRes.blob();
const buffer = await blob.arrayBuffer(); const buffer = await blob.arrayBuffer();
return Buffer.from(buffer); return Buffer.from(buffer);
} }
type Uri2LocalRes = { type Uri2LocalRes = {
success: boolean, success: boolean,
errMsg: string, errMsg: string,
fileName: string, fileName: string,
ext: string, ext: string,
path: string, path: string,
isLocal: boolean isLocal: boolean
} }
export async function uri2local(uri: string, fileName: string | null = null): Promise<Uri2LocalRes> { export async function uri2local(TempDir: string, UriOrPath: string, fileName: string | null = null): Promise<Uri2LocalRes> {
const res = { const res = {
success: false, success: false,
errMsg: '', errMsg: '',
fileName: '', fileName: '',
ext: '', ext: '',
path: '', path: '',
isLocal: false isLocal: false,
}; };
if (!fileName) { if (!fileName) fileName = randomUUID();
fileName = uuidv4(); let filePath = path.join(TempDir, fileName);//临时目录
} let url = null;
let filePath = path.join(getTempDir(), fileName); //区分path和uri
let url = null;
try {
url = new URL(uri);
} catch (e: any) {
res.errMsg = `uri ${uri} 解析失败,` + e.toString() + ` 可能${uri}不存在`;
return res;
}
// log("uri protocol", url.protocol, uri);
if (url.protocol == 'base64:') {
// base64转成文件
const base64Data = uri.split('base64://')[1];
try { try {
const buffer = Buffer.from(base64Data, 'base64'); if (fs.existsSync(UriOrPath)) url = new URL('file://' + UriOrPath);
fs.writeFileSync(filePath, buffer); } catch (error: any) {
} 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 { try {
const pathInfo = path.parse(decodeURIComponent(url.pathname)); url = new URL(UriOrPath);
if (pathInfo.name) { } catch (error: any) {
fileName = pathInfo.name;
if (pathInfo.ext) {
fileName += pathInfo.ext;
// res.ext = pathInfo.ext
}
}
fileName = fileName.replace(/[/\\:*?"<>|]/g, '_');
res.fileName = fileName;
filePath = path.join(getTempDir(), uuidv4() + fileName);
fs.writeFileSync(filePath, buffer);
} catch (e: any) {
res.errMsg = `${url}下载失败,` + e.toString();
return res;
}
} else {
let pathname: string;
if (url.protocol === 'file:') {
// await fs.copyFile(url.pathname, filePath);
pathname = decodeURIComponent(url.pathname);
if (process.platform === 'win32') {
filePath = pathname.slice(1);
} else {
filePath = pathname;
}
} else {
const cache = await dbUtil.getFileCacheByName(uri);
if (cache) {
filePath = cache.path;
} else {
filePath = uri;
}
} }
res.isLocal = true; //验证url
} if (!url) {
// else{ res.errMsg = `UriOrPath ${UriOrPath} 解析失败,可能${UriOrPath}不存在`;
// res.errMsg = `不支持的file协议,` + url.protocol return res;
// return res
// }
// if (isGIF(filePath) && !res.isLocal) {
// await fs.rename(filePath, filePath + ".gif");
// filePath += ".gif";
// }
if (!res.isLocal && !res.ext) {
try {
const ext: string | undefined = (await fileType.fileTypeFromFile(filePath))?.ext;
if (ext) {
log('获取文件类型', ext, filePath);
fs.renameSync(filePath, filePath + `.${ext}`);
filePath += `.${ext}`;
res.fileName += `.${ext}`;
res.ext = ext;
}
} catch (e) {
// log("获取文件类型失败", filePath,e.stack)
} }
}
res.success = true;
res.path = filePath;
return res;
}
export async function copyFolder(sourcePath: string, destPath: string) { if (url.protocol == 'base64:') {
try { // base64转成文件
const entries = await fsPromise.readdir(sourcePath, { withFileTypes: true }); const base64Data = UriOrPath.split('base64://')[1];
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 { try {
await fsPromise.copyFile(srcPath, dstPath); const buffer = Buffer.from(base64Data, 'base64');
} catch (error) { fs.writeFileSync(filePath, buffer);
console.error(`无法复制文件 '${srcPath}' 到 '${dstPath}': ${error}`); } catch (e: any) {
// 这里可以决定是否要继续复制其他文件 res.errMsg = 'base64文件下载失败,' + e.toString();
return res;
} }
} } else if (url.protocol == 'http:' || url.protocol == 'https:') {
// 下载文件
let buffer: Buffer | null = null;
try {
buffer = await httpDownload(UriOrPath);
} catch (e: any) {
res.errMsg = `${url}下载失败,` + e.toString();
return res;
}
try {
const pathInfo = path.parse(decodeURIComponent(url.pathname));
if (pathInfo.name) {
fileName = pathInfo.name;
if (pathInfo.ext) {
fileName += pathInfo.ext;
// res.ext = pathInfo.ext
}
}
fileName = fileName.replace(/[/\\:*?"<>|]/g, '_');
res.fileName = fileName;
filePath = path.join(TempDir, randomUUID() + fileName);
fs.writeFileSync(filePath, buffer);
} catch (e: any) {
res.errMsg = `${url}下载失败,` + e.toString();
return res;
}
} else {
let pathname: string;
if (url.protocol === 'file:') {
// await fs.copyFile(url.pathname, filePath);
pathname = decodeURIComponent(url.pathname);
if (process.platform === 'win32') {
filePath = pathname.slice(1);
} else {
filePath = pathname;
}
} else {
// 26702执行forword file文件操作 不应该在这里乱来
// const cache = await dbUtil.getFileCacheByName(uri);
// if (cache) {
// filePath = cache.path;
// } else {
// filePath = uri;
// }
}
res.isLocal = true;
}
// else{
// res.errMsg = `不支持的file协议,` + url.protocol
// return res
// }
// if (isGIF(filePath) && !res.isLocal) {
// await fs.rename(filePath, filePath + ".gif");
// filePath += ".gif";
// }
if (!res.isLocal && !res.ext) {
try {
const ext: string | undefined = (await fileType.fileTypeFromFile(filePath))?.ext;
if (ext) {
fs.renameSync(filePath, filePath + `.${ext}`);
filePath += `.${ext}`;
res.fileName += `.${ext}`;
res.ext = ext;
}
} catch (e) {
// log("获取文件类型失败", filePath,e.stack)
}
}
res.success = true;
res.path = filePath;
return res;
}
export async function copyFolder(sourcePath: string, destPath: string, logger: LogWrapper) {
try {
const entries = await fsPromise.readdir(sourcePath, { withFileTypes: true });
await fsPromise.mkdir(destPath, { recursive: true });
for (const entry of entries) {
const srcPath = path.join(sourcePath, entry.name);
const dstPath = path.join(destPath, entry.name);
if (entry.isDirectory()) {
await copyFolder(srcPath, dstPath, logger);
} else {
try {
await fsPromise.copyFile(srcPath, dstPath);
} catch (error) {
logger.logError(`无法复制文件 '${srcPath}' 到 '${dstPath}': ${error}`);
// 这里可以决定是否要继续复制其他文件
}
}
}
} catch (error) {
logger.logError('复制文件夹时出错:', error);
} }
} catch (error) {
console.error('复制文件夹时出错:', error);
}
} }

View File

@@ -1,184 +1,165 @@
import crypto from 'node:crypto'; import crypto from 'node:crypto';
import path from 'node:path'; import path from 'node:path';
import fs from 'fs/promises'; import fs from 'fs';
import { log, logDebug } from './log'; import * as fsPromise from 'node:fs/promises';
import { dirname } from 'node:path'; import os from 'node:os';
import { fileURLToPath } from 'node:url'; import { QQLevel } from '@/core';
//下面这个类是用于将uid+msgid合并的类
export class UUIDConverter {
static encode(highStr: string, lowStr: string): string {
const high = BigInt(highStr);
const low = BigInt(lowStr);
const highHex = high.toString(16).padStart(16, '0');
const lowHex = low.toString(16).padStart(16, '0');
const combinedHex = highHex + lowHex;
const uuid = `${combinedHex.substring(0, 8)}-${combinedHex.substring(8, 12)}-${combinedHex.substring(
12,
16,
)}-${combinedHex.substring(16, 20)}-${combinedHex.substring(20)}`;
return uuid;
}
static decode(uuid: string): { high: string; low: string } {
const hex = uuid.replace(/-/g, '');
const high = BigInt('0x' + hex.substring(0, 16));
const low = BigInt('0x' + hex.substring(16));
return { high: high.toString(), low: low.toString() };
}
}
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
export function sleep(ms: number): Promise<void> { export function sleep(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms)); return new Promise((resolve) => setTimeout(resolve, ms));
}
export function PromiseTimer<T>(promise: Promise<T>, ms: number): Promise<T> {
const timeoutPromise = new Promise<T>((_, reject) =>
setTimeout(() => reject(new Error('PromiseTimer: Operation timed out')), ms),
);
return Promise.race([promise, timeoutPromise]);
}
export async function runAllWithTimeout<T>(tasks: Promise<T>[], timeout: number): Promise<T[]> {
const wrappedTasks = tasks.map((task) =>
PromiseTimer(task, timeout).then(
(result) => ({ status: 'fulfilled', value: result }),
(error) => ({ status: 'rejected', reason: error }),
),
);
const results = await Promise.all(wrappedTasks);
return results
.filter((result) => result.status === 'fulfilled')
.map((result) => (result as { status: 'fulfilled'; value: T }).value);
} }
export function getMd5(s: string) { export function getMd5(s: string) {
const h = crypto.createHash('md5');
const h = crypto.createHash('md5'); h.update(s);
h.update(s); return h.digest('hex');
return h.digest('hex');
} }
export function isNull(value: any) { export function isNull(value: any) {
return value === undefined || value === null; return value === undefined || value === null;
} }
export function isNumeric(str: string) { export function isNumeric(str: string) {
return /^\d+$/.test(str); return /^\d+$/.test(str);
} }
export function truncateString(obj: any, maxLength = 500) { export function truncateString(obj: any, maxLength = 500) {
if (obj !== null && typeof obj === 'object') { if (obj !== null && typeof obj === 'object') {
Object.keys(obj).forEach(key => { Object.keys(obj).forEach((key) => {
if (typeof obj[key] === 'string') { if (typeof obj[key] === 'string') {
// 如果是字符串且超过指定长度,则截断 // 如果是字符串且超过指定长度,则截断
if (obj[key].length > maxLength) { if (obj[key].length > maxLength) {
obj[key] = obj[key].substring(0, maxLength) + '...'; obj[key] = obj[key].substring(0, maxLength) + '...';
} }
} else if (typeof obj[key] === 'object') { } else if (typeof obj[key] === 'object') {
// 如果是对象或数组,则递归调用 // 如果是对象或数组,则递归调用
truncateString(obj[key], maxLength); 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;
} }
} return obj;
if (!Array.isArray(config.httpPostUrls) || !Array.isArray(config.wsReverseUrls)) {
return false;
}
if (config.httpPostUrls.some((url: any) => typeof url !== 'string')) {
return false;
}
if (config.wsReverseUrls.some((url: any) => typeof url !== 'string')) {
return false;
}
if (typeof config.httpPort !== 'number' || typeof config.wsPort !== 'number' || typeof config.heartInterval !== 'number') {
return false;
}
if (
typeof config.enableHttp !== 'boolean' ||
typeof config.enableHttpHeart !== 'boolean' ||
typeof config.enableHttpPost !== 'boolean' ||
typeof config.enableWs !== 'boolean' ||
typeof config.enableWsReverse !== 'boolean' ||
typeof config.enableLocalFile2Url !== 'boolean' ||
typeof config.reportSelfMessage !== 'boolean'
) {
return false;
}
if (config.messagePostFormat !== 'array' && config.messagePostFormat !== 'string') {
return false;
}
return true;
} }
export function migrateConfig(oldConfig: any) {
const newConfig = {
http: {
enable: oldConfig.enableHttp,
host: oldConfig.httpHost,
port: oldConfig.httpPort,
secret: oldConfig.httpSecret,
enableHeart: oldConfig.enableHttpHeart,
enablePost: oldConfig.enableHttpPost,
postUrls: oldConfig.httpPostUrls,
},
ws: {
enable: oldConfig.enableWs,
host: oldConfig.wsHost,
port: oldConfig.wsPort,
},
reverseWs: {
enable: oldConfig.enableWsReverse,
urls: oldConfig.wsReverseUrls,
},
GroupLocalTime: {
Record: false,
RecordList: []
},
debug: oldConfig.debug,
heartInterval: oldConfig.heartInterval,
messagePostFormat: oldConfig.messagePostFormat,
enableLocalFile2Url: oldConfig.enableLocalFile2Url,
musicSignUrl: oldConfig.musicSignUrl,
reportSelfMessage: oldConfig.reportSelfMessage,
token: oldConfig.token,
};
return newConfig;
}
// 升级旧的配置到新的
export async function UpdateConfig() {
const configFiles = await fs.readdir(path.join(__dirname, 'config'));
for (const file of configFiles) {
if (file.match(/^onebot11_\d+.json$/)) {
const CurrentConfig = JSON.parse(await fs.readFile(path.join(__dirname, 'config', file), 'utf8'));
if (isValidOldConfig(CurrentConfig)) {
log('正在迁移旧配置到新配置 File:', file);
const NewConfig = migrateConfig(CurrentConfig);
await fs.writeFile(path.join(__dirname, 'config', file), JSON.stringify(NewConfig, null, 2));
}
}
}
}
export function isEqual(obj1: any, obj2: any) { export function isEqual(obj1: any, obj2: any) {
if (obj1 === obj2) return true; if (obj1 === obj2) return true;
if (obj1 == null || obj2 == null) return false; if (obj1 == null || obj2 == null) return false;
if (typeof obj1 !== 'object' || typeof obj2 !== 'object') return obj1 === obj2; if (typeof obj1 !== 'object' || typeof obj2 !== 'object') return obj1 === obj2;
const keys1 = Object.keys(obj1); const keys1 = Object.keys(obj1);
const keys2 = Object.keys(obj2); const keys2 = Object.keys(obj2);
if (keys1.length !== keys2.length) return false; if (keys1.length !== keys2.length) return false;
for (const key of keys1) { for (const key of keys1) {
if (!isEqual(obj1[key], obj2[key])) return false; if (!isEqual(obj1[key], obj2[key])) return false;
} }
return true; return true;
} }
export function getDefaultQQVersionConfigInfo(): QQVersionConfigType {
if (os.platform() === 'linux') {
return {
baseVersion: '3.2.12-26702',
curVersion: '3.2.12-26702',
prevVersion: '',
onErrorVersions: [],
buildId: '26702',
};
}
return {
baseVersion: '9.9.15-26702',
curVersion: '9.9.15-26702',
prevVersion: '',
onErrorVersions: [],
buildId: '26702',
};
}
export function getQQVersionConfigPath(exePath: string = ''): string | undefined {
let configVersionInfoPath;
if (os.platform() !== 'linux') {
configVersionInfoPath = path.join(path.dirname(exePath), 'resources', 'app', 'versions', 'config.json');
} else {
const userPath = os.homedir();
const appDataPath = path.resolve(userPath, './.config/QQ');
configVersionInfoPath = path.resolve(appDataPath, './versions/config.json');
}
if (typeof configVersionInfoPath !== 'string') {
return undefined;
}
if (!fs.existsSync(configVersionInfoPath)) {
return undefined;
}
return configVersionInfoPath;
}
export async function deleteOldFiles(directoryPath: string, daysThreshold: number) {
try {
const files = await fsPromise.readdir(directoryPath);
for (const file of files) {
const filePath = path.join(directoryPath, file);
const stats = await fsPromise.stat(filePath);
const lastModifiedTime = stats.mtimeMs;
const currentTime = Date.now();
const timeDifference = currentTime - lastModifiedTime;
const daysDifference = timeDifference / (1000 * 60 * 60 * 24);
if (daysDifference > daysThreshold) {
await fsPromise.unlink(filePath); // Delete the file
//console.log(`Deleted: ${filePath}`);
}
}
} catch (error) {
//console.error('Error deleting files:', error);
}
}
export function calcQQLevel(level: QQLevel) {
const { crownNum, sunNum, moonNum, starNum } = level;
return crownNum * 64 + sunNum * 16 + moonNum * 4 + starNum;
}

View File

@@ -1,123 +1,239 @@
import log4js, { Configuration } from 'log4js'; import log4js, { Configuration } from 'log4js';
import { truncateString } from '@/common/utils/helper'; import { truncateString } from '@/common/utils/helper';
import path from 'node:path'; import path from 'node:path';
import { SelfInfo } from '@/core'; import chalk from 'chalk';
import { dirname } from 'node:path'; import { AtType, ChatType, ElementType, ElementWrapper, RawMessage, SelfInfo } from '@/core';
import { fileURLToPath } from 'node:url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
export enum LogLevel { export enum LogLevel {
DEBUG = 'debug', DEBUG = 'debug',
INFO = 'info', INFO = 'info',
WARN = 'warn', WARN = 'warn',
ERROR = 'error', ERROR = 'error',
FATAL = 'fatal', FATAL = 'fatal',
} }
const logDir = path.join(path.resolve(__dirname), 'logs');
function getFormattedTimestamp() { function getFormattedTimestamp() {
const now = new Date(); const now = new Date();
const year = now.getFullYear(); const year = now.getFullYear();
const month = (now.getMonth() + 1).toString().padStart(2, '0'); const month = (now.getMonth() + 1).toString().padStart(2, '0');
const day = now.getDate().toString().padStart(2, '0'); const day = now.getDate().toString().padStart(2, '0');
const hours = now.getHours().toString().padStart(2, '0'); const hours = now.getHours().toString().padStart(2, '0');
const minutes = now.getMinutes().toString().padStart(2, '0'); const minutes = now.getMinutes().toString().padStart(2, '0');
const seconds = now.getSeconds().toString().padStart(2, '0'); const seconds = now.getSeconds().toString().padStart(2, '0');
const milliseconds = now.getMilliseconds().toString().padStart(3, '0'); const milliseconds = now.getMilliseconds().toString().padStart(3, '0');
return `${year}-${month}-${day}_${hours}-${minutes}-${seconds}.${milliseconds}`; return `${year}-${month}-${day}_${hours}-${minutes}-${seconds}.${milliseconds}`;
} }
const filename = `${getFormattedTimestamp()}.log`; export class LogWrapper {
const logPath = path.join(logDir, filename); 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 = { constructor(logDir: string) {
appenders: { const filename = `${getFormattedTimestamp()}.log`;
FileAppender: { // 输出到文件的appender const logPath = path.join(logDir, filename);
type: 'file', this.logConfig = {
filename: logPath, // 指定日志文件的位置和文件名 appenders: {
maxLoogSize: 10485760, // 日志文件的最大大小单位字节这里设置为10MB FileAppender: { // 输出到文件的appender
layout: { type: 'file',
type: 'pattern', filename: logPath, // 指定日志文件的位置和文件名
pattern: '%d{yyyy-MM-dd hh:mm:ss} [%p] - %m' maxLogSize: 10485760, // 日志文件的最大大小单位字节这里设置为10MB
} layout: {
}, type: 'pattern',
ConsoleAppender: { // 输出到控制台的appender pattern: '%d{yyyy-MM-dd hh:mm:ss} [%p] %X{userInfo} | %m',
type: 'console', },
layout: { },
type: 'pattern', ConsoleAppender: { // 输出到控制台的appender
pattern: '%d{yyyy-MM-dd hh:mm:ss} [%p] - %m' 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); setFileAndConsoleLogLevel(fileLogLevel: LogLevel, consoleLogLevel: LogLevel) {
this.logConfig.categories.file.level = fileLogLevel;
this.logConfig.categories.console.level = consoleLogLevel;
export function setLogLevel(fileLogLevel: LogLevel, consoleLogLevel: LogLevel) { log4js.configure(this.logConfig);
logConfig.categories.file.level = fileLogLevel; }
logConfig.categories.console.level = consoleLogLevel;
log4js.configure(logConfig); setLogSelfInfo(selfInfo: { nick: string, uin: string, uid: string }) {
} const userInfo = `${selfInfo.nick}(${selfInfo.uin})`;
this.loggerConsole.addContext('userInfo', userInfo);
export function setLogSelfInfo(selfInfo: SelfInfo) { this.loggerFile.addContext('userInfo', userInfo);
// eslint-disable-next-line @typescript-eslint/ban-ts-comment this.loggerDefault.addContext('userInfo', userInfo);
// @ts-expect-error }
logConfig.appenders.FileAppender.layout.pattern = logConfig.appenders.ConsoleAppender.layout.pattern =
`%d{yyyy-MM-dd hh:mm:ss} [%p] ${selfInfo.nick}(${selfInfo.uin}) %m`; setFileLogEnabled(isEnabled: boolean) {
log4js.configure(logConfig); this.fileLogEnabled = isEnabled;
} }
let fileLogEnabled = true; setConsoleLogEnabled(isEnabled: boolean) {
let consoleLogEnabled = true; this.consoleLogEnabled = isEnabled;
export function enableFileLog(enable: boolean) { }
fileLogEnabled = enable;
} formatMsg(msg: any[]) {
export function enableConsoleLog(enable: boolean) { let logMsg = '';
consoleLogEnabled = enable; for (const msgItem of msg) {
} if (msgItem instanceof Error) { // 判断是否是错误
logMsg += msgItem.stack + ' ';
function formatMsg(msg: any[]){ continue;
let logMsg = ''; } else if (typeof msgItem === 'object') { // 判断是否是对象
for (const msgItem of msg) { const obj = JSON.parse(JSON.stringify(msgItem, null, 2));
// 判断是否是对象 logMsg += JSON.stringify(truncateString(obj)) + ' ';
if (typeof msgItem === 'object') { continue;
const obj = JSON.parse(JSON.stringify(msgItem, null, 2)); }
logMsg += JSON.stringify(truncateString(obj)) + ' '; logMsg += msgItem + ' ';
continue; }
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 '\n' + logMsg + '\n';
} }
function _log(level: LogLevel, ...args: any[]){ export function rawMessageToText(msg: RawMessage, recursiveLevel = 0): string {
if (consoleLogEnabled){ if (recursiveLevel > 2) {
log4js.getLogger('console')[level](formatMsg(args)); return '...';
} }
if (fileLogEnabled){
log4js.getLogger('file')[level](formatMsg(args));
}
}
export function log(...args: any[]) { const tokens: string[] = [];
// info 等级
_log(LogLevel.INFO, ...args);
}
export function logDebug(...args: any[]) { if (msg.chatType == ChatType.friend) {
_log(LogLevel.DEBUG, ...args); 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})`);
}
export function logError(...args: any[]) { // message content
_log(LogLevel.ERROR, ...args);
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(' ');
} }

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 https from 'node:https';
import http from 'node:http'; import http from 'node:http';
import { readFileSync } from 'node:fs';
export class RequestUtil { export class RequestUtil {
// 适用于获取服务器下发cookies时获取仅GET // 适用于获取服务器下发cookies时获取仅GET
static async HttpsGetCookies(url: string): Promise<{ [key: string]: string }> { static async HttpsGetCookies(url: string): Promise<{ [key: string]: string }> {
const client = url.startsWith('https') ? https : http; const client = url.startsWith('https') ? https : http;
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
client.get(url, (res) => { const req = client.get(url, (res) => {
let cookies: { [key: string]: string } = {}; let cookies: { [key: string]: string } = {};
const handleRedirect = (res: http.IncomingMessage) => { const handleRedirect = (res: http.IncomingMessage) => {
//console.log(res.headers.location); //console.log(res.headers.location);
if (res.statusCode === 301 || res.statusCode === 302) { if (res.statusCode === 301 || res.statusCode === 302) {
if (res.headers.location) { if (res.headers.location) {
const redirectUrl = new URL(res.headers.location, url); const redirectUrl = new URL(res.headers.location, url);
RequestUtil.HttpsGetCookies(redirectUrl.href).then((redirectCookies) => { RequestUtil.HttpsGetCookies(redirectUrl.href).then((redirectCookies) => {
// 合并重定向过程中的cookies // 合并重定向过程中的cookies
cookies = { ...cookies, ...redirectCookies }; cookies = { ...cookies, ...redirectCookies };
resolve(cookies); resolve(cookies);
}); }).catch((err) => {
} else { reject(err);
resolve(cookies); });
} } else {
} else { resolve(cookies);
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 // headers: {
res.on('end', () => { // 'Content-Type': 'application/json',
handleRedirect(res); // 'Content-Length': Buffer.byteLength(postData),
}); // },
if (res.headers['set-cookie']) { return new Promise((resolve, reject) => {
//console.log(res.headers['set-cookie']); const req = protocol.request(options, (res: any) => {
res.headers['set-cookie'].forEach((cookie) => { let responseBody = '';
const parts = cookie.split(';')[0].split('='); res.on('data', (chunk: string | Buffer) => {
const key = parts[0]; responseBody += chunk.toString();
const value = parts[1]; });
if (key && value && key.length > 0 && value.length > 0) {
cookies[key] = value; res.on('end', () => {
try {
if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
if (isJsonRet) {
const responseJson = JSON.parse(responseBody);
resolve(responseJson as T);
} else {
resolve(responseBody as T);
}
} else {
reject(new Error(`Unexpected status code: ${res.statusCode}`));
}
} catch (parseError) {
reject(parseError);
}
});
});
req.on('error', (error: any) => {
reject(error);
});
if (method === 'POST' || method === 'PUT' || method === 'PATCH') {
if (isArgJson) {
req.write(JSON.stringify(data));
} else {
req.write(data);
}
} }
}); req.end();
}
}).on('error', (err) => {
reject(err);
});
});
}
// 请求和回复都是JSON data传原始内容 自动编码json
static async HttpGetJson<T>(url: string, method: string = 'GET', data?: any, headers: Record<string, string> = {}, isJsonRet: boolean = true, isArgJson: boolean = true): Promise<T> {
const option = new URL(url);
const protocol = url.startsWith('https://') ? https : http;
const options = {
hostname: option.hostname,
port: option.port,
path: option.href,
method: method,
headers: headers
};
return new Promise((resolve, reject) => {
const req = protocol.request(options, (res: any) => {
let responseBody = '';
res.on('data', (chunk: string | Buffer) => {
responseBody += chunk.toString();
}); });
}
res.on('end', () => { // 请求返回都是原始内容
try { static async HttpGetText(url: string, method: string = 'GET', data?: any, headers: { [key: string]: string } = {}) {
if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) { return this.HttpGetJson<string>(url, method, data, headers, false, false);
if (isJsonRet) { }
const responseJson = JSON.parse(responseBody);
resolve(responseJson as T); static async createFormData(boundary: string, filePath: string): Promise<Buffer> {
} else { let type = 'image/png';
resolve(responseBody as T); if (filePath.endsWith('.jpg')) {
} type = 'image/jpeg';
} else { }
reject(new Error(`Unexpected status code: ${res.statusCode}`)); 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) { return undefined;
reject(parseError);
}
}); });
}); }
}
req.on('error', (error: any) => {
reject(error);
});
if (method === 'POST' || method === 'PUT' || method === 'PATCH') {
if (isArgJson) {
req.write(JSON.stringify(data));
} else {
req.write(data);
}
}
req.end();
});
}
// 请求返回都是原始内容
static async HttpGetText(url: string, method: string = 'GET', data?: any, headers: Record<string, string> = {}) {
return this.HttpGetJson<string>(url, method, data, headers, false, false);
}
}

View File

@@ -1,17 +1,73 @@
import os from 'node:os'; import os from 'node:os';
import path from 'node:path'; import path from 'node:path';
import { networkInterfaces } from 'os';
import { randomUUID } from 'crypto';
// 缓解Win7设备兼容性问题 // 缓解Win7设备兼容性问题
let osName: string; let osName: string;
// 设备ID
let machineId: Promise<string>;
try { try {
osName = os.hostname(); osName = os.hostname();
} catch (e) { } 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',
]);
function validateMacAddress(candidate: string): boolean {
// eslint-disable-next-line no-useless-escape
const tempCandidate = candidate.replace(/\-/g, ':').toLowerCase();
return !invalidMacAddresses.has(tempCandidate);
}
export async function getMachineId(): Promise<string> {
if (!machineId) {
machineId = (async () => {
const id = await getMacMachineId();
return id || randomUUID(); // fallback, generate a UUID
})();
}
return machineId;
}
export function getMac(): string {
const ifaces = networkInterfaces();
for (const name in ifaces) {
const networkInterface = ifaces[name];
if (networkInterface) {
for (const { mac } of networkInterface) {
if (validateMacAddress(mac)) {
return mac;
}
}
}
}
throw new Error('Unable to retrieve mac address (unexpected format)');
}
async function getMacMachineId(): Promise<string | undefined> {
try {
const crypto = await import('crypto');
const macAddress = getMac();
return crypto.createHash('sha256').update(macAddress, 'utf8').digest('hex');
} catch (err) {
return undefined;
}
}
const homeDir = os.homedir();
export const systemPlatform = os.platform(); export const systemPlatform = os.platform();
export const cpuArch = os.arch(); export const cpuArch = os.arch();
export const systemVersion = os.release(); export const systemVersion = os.release();
export const hostname = osName; export const hostname = osName;
const homeDir = os.homedir();
export const downloadsPath = path.join(homeDir, 'Downloads'); export const downloadsPath = path.join(homeDir, 'Downloads');
export const systemName = os.type(); export const systemName = os.type();

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 845f6d2a15

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 @@
var _0x53ad75=_0x4803;function _0xe478(){var _0x5596f4=['onMSFStatusChange','8478DSeblD','120CBHLmq','getGroupCode','8142540ClqKUq','12034690uHLFGZ','428571uSIPMn','338876izjvuv','233PrhtpI','18JfPLcz','4504910cOvOVi','onMSFSsoError','2284698NHCKkY'];_0xe478=function(){return _0x5596f4;};return _0xe478();}function _0x4803(_0x455fa5,_0x3374cc){var _0xe478fc=_0xe478();return _0x4803=function(_0x4803d1,_0x5c7d64){_0x4803d1=_0x4803d1-0x166;var _0x407b69=_0xe478fc[_0x4803d1];return _0x407b69;},_0x4803(_0x455fa5,_0x3374cc);}(function(_0xd52527,_0x276b3b){var _0x29e235=_0x4803,_0x318582=_0xd52527();while(!![]){try{var _0x4398a7=parseInt(_0x29e235(0x16d))/0x1*(-parseInt(_0x29e235(0x166))/0x2)+parseInt(_0x29e235(0x16e))/0x3*(parseInt(_0x29e235(0x16c))/0x4)+-parseInt(_0x29e235(0x16f))/0x5+parseInt(_0x29e235(0x171))/0x6+parseInt(_0x29e235(0x169))/0x7+parseInt(_0x29e235(0x167))/0x8*(-parseInt(_0x29e235(0x16b))/0x9)+parseInt(_0x29e235(0x16a))/0xa;if(_0x4398a7===_0x276b3b)break;else _0x318582['push'](_0x318582['shift']());}catch(_0x35cc58){_0x318582['push'](_0x318582['shift']());}}}(_0xe478,0x9f620));export class DependsAdapter{[_0x53ad75(0x172)](_0x5352ea,_0x25f40e){}[_0x53ad75(0x170)](_0x522fec){}[_0x53ad75(0x168)](_0x247a93){}}

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 _0x158b(){var _0x40fbd5=['3062871vunmFV','3zYOYxk','844044tpZmIc','749852nrnXeE','dispatchRequest','1290RHPCdr','10woqzDv','18728BYyKUV','dispatchCallWithJson','2442069YPAYtZ','dispatchCall','15945sSktrD','5819128TvdfDs'];_0x158b=function(){return _0x40fbd5;};return _0x158b();}var _0x43a50b=_0x1196;function _0x1196(_0x215c22,_0x3dcb48){var _0x158b1e=_0x158b();return _0x1196=function(_0x1196d1,_0x32c0b9){_0x1196d1=_0x1196d1-0x1c1;var _0x461c24=_0x158b1e[_0x1196d1];return _0x461c24;},_0x1196(_0x215c22,_0x3dcb48);}(function(_0xde87af,_0x11e6b0){var _0x4524e8=_0x1196,_0x2ac0b2=_0xde87af();while(!![]){try{var _0x14e82f=-parseInt(_0x4524e8(0x1c7))/0x1+-parseInt(_0x4524e8(0x1c6))/0x2*(-parseInt(_0x4524e8(0x1c5))/0x3)+-parseInt(_0x4524e8(0x1cb))/0x4+parseInt(_0x4524e8(0x1c2))/0x5*(parseInt(_0x4524e8(0x1c9))/0x6)+-parseInt(_0x4524e8(0x1cd))/0x7+parseInt(_0x4524e8(0x1c3))/0x8+-parseInt(_0x4524e8(0x1c4))/0x9*(parseInt(_0x4524e8(0x1ca))/0xa);if(_0x14e82f===_0x11e6b0)break;else _0x2ac0b2['push'](_0x2ac0b2['shift']());}catch(_0x1db3ad){_0x2ac0b2['push'](_0x2ac0b2['shift']());}}}(_0x158b,0x5f8a0));export class DispatcherAdapter{[_0x43a50b(0x1c8)](_0x663338){}[_0x43a50b(0x1c1)](_0x53028f){}[_0x43a50b(0x1cc)](_0x4257c9){}}

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 _0x238a(){var _0x2f782d=['1000848yukvGL','onInstallFinished','5585885gLEaQu','4VYrsqU','onLog','2218657SXADhe','8271816fbxQav','1210040ynNaGd','167270boAwyT','1109100ybCVbQ','765byyBwI','onGetSrvCalTime','12bTBpih','onUpdateGeneralFlag','onShowErrUITips'];_0x238a=function(){return _0x2f782d;};return _0x238a();}var _0x54dfea=_0x46f2;function _0x46f2(_0x1c572b,_0x400d30){var _0x238a4f=_0x238a();return _0x46f2=function(_0x46f2c3,_0x1890a0){_0x46f2c3=_0x46f2c3-0x1cc;var _0x2dbfad=_0x238a4f[_0x46f2c3];return _0x2dbfad;},_0x46f2(_0x1c572b,_0x400d30);}(function(_0x268b2c,_0x2ea7c2){var _0x41b9cc=_0x46f2,_0x3d5952=_0x268b2c();while(!![]){try{var _0x513579=parseInt(_0x41b9cc(0x1ce))/0x1+-parseInt(_0x41b9cc(0x1d5))/0x2+-parseInt(_0x41b9cc(0x1d7))/0x3+-parseInt(_0x41b9cc(0x1d1))/0x4*(-parseInt(_0x41b9cc(0x1d0))/0x5)+-parseInt(_0x41b9cc(0x1da))/0x6*(parseInt(_0x41b9cc(0x1d3))/0x7)+-parseInt(_0x41b9cc(0x1d4))/0x8+-parseInt(_0x41b9cc(0x1d8))/0x9*(-parseInt(_0x41b9cc(0x1d6))/0xa);if(_0x513579===_0x2ea7c2)break;else _0x3d5952['push'](_0x3d5952['shift']());}catch(_0x1ffa66){_0x3d5952['push'](_0x3d5952['shift']());}}}(_0x238a,0xdb0c5));export class GlobalAdapter{[_0x54dfea(0x1d2)](..._0x23a9ba){}[_0x54dfea(0x1d9)](..._0xc9932c){}[_0x54dfea(0x1cd)](..._0x1c2c5e){}['fixPicImgType'](..._0x247bd1){}['getAppSetting'](..._0x4bfe6c){}[_0x54dfea(0x1cf)](..._0x29783c){}[_0x54dfea(0x1cc)](..._0x38d085){}['onGetOfflineMsg'](..._0x51024e){}}

View File

@@ -1 +0,0 @@
(function(_0x4e675e,_0x33381c){var _0x149ce6=_0x5a58,_0x3870c1=_0x4e675e();while(!![]){try{var _0x1bfc29=parseInt(_0x149ce6(0x70))/0x1*(-parseInt(_0x149ce6(0x6e))/0x2)+-parseInt(_0x149ce6(0x73))/0x3*(-parseInt(_0x149ce6(0x71))/0x4)+-parseInt(_0x149ce6(0x72))/0x5+parseInt(_0x149ce6(0x75))/0x6+-parseInt(_0x149ce6(0x76))/0x7+parseInt(_0x149ce6(0x74))/0x8+parseInt(_0x149ce6(0x6f))/0x9;if(_0x1bfc29===_0x33381c)break;else _0x3870c1['push'](_0x3870c1['shift']());}catch(_0x557e6b){_0x3870c1['push'](_0x3870c1['shift']());}}}(_0x15ef,0xa25f5));function _0x5a58(_0x5859b3,_0x35f5bc){var _0x15ef17=_0x15ef();return _0x5a58=function(_0x5a58c0,_0x1337cb){_0x5a58c0=_0x5a58c0-0x6e;var _0x4ec18e=_0x15ef17[_0x5a58c0];return _0x4ec18e;},_0x5a58(_0x5859b3,_0x35f5bc);}function _0x15ef(){var _0x56c869=['2087340rclyQF','9plctPm','9984688aEkshO','854592heAZeF','5016291dEMrLe','5826EGMCIE','4201965RXVteZ','246KeDUoo','877804cXkxwW'];_0x15ef=function(){return _0x56c869;};return _0x15ef();}export*from'./NodeIDependsAdapter';export*from'./NodeIDispatcherAdapter';export*from'./NodeIGlobalAdapter';

View File

@@ -1,37 +0,0 @@
import { CacheFileListItem, CacheFileType, ChatCacheListItemBasic, ChatType, ElementType } from '@/core/entities';
import { GeneralCallResult } from '@/core';
import * as fileType from 'file-type';
import { ISizeCalculationResult } from 'image-size/dist/types/interface';
export declare class NTQQFileApi {
static getFileType(filePath: string): Promise<fileType.FileTypeResult | undefined>;
static copyFile(filePath: string, destPath: string): Promise<void>;
static getFileSize(filePath: string): Promise<number>;
static uploadFile(filePath: string, elementType?: ElementType, elementSubType?: number): Promise<{
md5: string;
fileName: string;
path: string;
fileSize: number;
ext: string;
}>;
static downloadMedia(msgId: string, chatType: ChatType, peerUid: string, elementId: string, thumbPath: string, sourcePath: string, timeout?: number, force?: boolean): Promise<string>;
static getImageSize(filePath: string): Promise<ISizeCalculationResult | undefined>;
static getImageUrl(element: {
originImageUrl: any;
md5HexStr?: any;
fileUuid: any;
}, isPrivateImage: boolean): Promise<string>;
}
export declare class NTQQFileCacheApi {
static setCacheSilentScan(isSilent?: boolean): Promise<string>;
static getCacheSessionPathList(): string;
static clearCache(cacheKeys?: Array<string>): unknown;
static addCacheScannedPaths(pathMap?: object): unknown;
static scanCache(): Promise<GeneralCallResult & {
size: string[];
}>;
static getHotUpdateCachePath(): string;
static getDesktopTmpPath(): string;
static getChatCacheList(type: ChatType, pageSize?: number, pageIndex?: number): unknown;
static getFileCacheInfo(fileType: CacheFileType, pageSize?: number, lastRecord?: CacheFileListItem): void;
static clearChatCache(chats?: ChatCacheListItemBasic[], fileKeys?: string[]): Promise<unknown>;
}

File diff suppressed because one or more lines are too long

View File

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

View File

@@ -1 +0,0 @@
const _0xd4c679=_0x4272;(function(_0x496a3b,_0xb73cdd){const _0x1e4b08=_0x4272,_0x5a6bc5=_0x496a3b();while(!![]){try{const _0x288afd=parseInt(_0x1e4b08(0x1be))/0x1*(parseInt(_0x1e4b08(0x1b1))/0x2)+parseInt(_0x1e4b08(0x1ba))/0x3*(parseInt(_0x1e4b08(0x1bb))/0x4)+-parseInt(_0x1e4b08(0x1b4))/0x5+-parseInt(_0x1e4b08(0x1aa))/0x6*(parseInt(_0x1e4b08(0x1c1))/0x7)+-parseInt(_0x1e4b08(0x1a5))/0x8*(parseInt(_0x1e4b08(0x1b8))/0x9)+parseInt(_0x1e4b08(0x1a9))/0xa+parseInt(_0x1e4b08(0x1c0))/0xb;if(_0x288afd===_0xb73cdd)break;else _0x5a6bc5['push'](_0x5a6bc5['shift']());}catch(_0x34e642){_0x5a6bc5['push'](_0x5a6bc5['shift']());}}}(_0x5a33,0xa8480));import{BuddyListener,napCatCore}from'@/core';import{logDebug}from'@/common/utils/log';import{uid2UinMap}from'@/core/data';function _0x5a33(){const _0x281cfb=['459332UUuQfM','uid','16328070ExNyrh','7ktzxku','getBuddyService','WksIN','set','获取好友列表超时','delete','8GJFJeL','reqTime','XcrxL','friendUid','2853640wHvxGr','6450762meNpzT','session','handleFriendRequest','iclee','onLoginSuccess','push','approvalFriendRequest','2EPJvRC','onBuddyListChange','buddyList','445080rFelIA','uin','aSDoe','LfWBK','5875533aSnnxI','开始获取好友列表','18BXJLCi','184796tBuDBv','JsMRs','JxKJH'];_0x5a33=function(){return _0x281cfb;};return _0x5a33();}import{randomUUID}from'crypto';const buddyChangeTasks=new Map(),buddyListener=new BuddyListener();function _0x4272(_0x4f3826,_0x29a058){const _0x5a3375=_0x5a33();return _0x4272=function(_0x427267,_0x3643e1){_0x427267=_0x427267-0x1a0;let _0x33abc8=_0x5a3375[_0x427267];return _0x33abc8;},_0x4272(_0x4f3826,_0x29a058);}buddyListener[_0xd4c679(0x1b2)]=_0x590332=>{const _0x224a7a=_0xd4c679,_0x1d210f={'jjksZ':function(_0x41fb46,_0x4f5f29){return _0x41fb46(_0x4f5f29);}};for(const [_0x1a3d0f,_0xff7093]of buddyChangeTasks){_0x1d210f['jjksZ'](_0xff7093,_0x590332),buddyChangeTasks[_0x224a7a(0x1a4)](_0x1a3d0f);}},setTimeout(()=>{const _0x51828c=_0xd4c679;napCatCore[_0x51828c(0x1ae)](()=>{napCatCore['addListener'](buddyListener);});},0x64);export class NTQQFriendApi{static async['getFriends'](_0x2f4c84=![]){const _0xaf62f3=_0xd4c679,_0x32fb99={'JxKJH':function(_0x49e7d8,_0x3fb2b2){return _0x49e7d8(_0x3fb2b2);},'WksIN':_0xaf62f3(0x1a3),'LfWBK':function(_0x16b79a,_0x4cf5f9){return _0x16b79a(_0x4cf5f9);},'JsMRs':function(_0x45e55f,_0x3912f8){return _0x45e55f(_0x3912f8);},'AxTme':function(_0x384c13,_0x39c1b9,_0x2ef241){return _0x384c13(_0x39c1b9,_0x2ef241);},'iclee':function(_0x4e72f3){return _0x4e72f3();}};return new Promise((_0xf6b6df,_0xed31ba)=>{const _0x5d83fd=_0xaf62f3,_0x4ea5e1={'aSDoe':function(_0x45cfac,_0x55e521,_0x43ae7f){return _0x32fb99['AxTme'](_0x45cfac,_0x55e521,_0x43ae7f);},'XcrxL':_0x5d83fd(0x1b9)};let _0x548f69=![];setTimeout(()=>{const _0x132875=_0x5d83fd;!_0x548f69&&(_0x32fb99[_0x132875(0x1bd)](logDebug,_0x32fb99[_0x132875(0x1a1)]),_0x32fb99[_0x132875(0x1b7)](_0xed31ba,_0x32fb99[_0x132875(0x1a1)]));},0x1388);const _0x17d3e2=[],_0x32e5a0=_0x2da32a=>{const _0x4d3a2e=_0x5d83fd;for(const _0x8e01ff of _0x2da32a){for(const _0x457644 of _0x8e01ff[_0x4d3a2e(0x1b3)]){_0x17d3e2[_0x4d3a2e(0x1af)](_0x457644),uid2UinMap[_0x457644[_0x4d3a2e(0x1bf)]]=_0x457644[_0x4d3a2e(0x1b5)];}}_0x548f69=!![],_0x32fb99[_0x4d3a2e(0x1bc)](_0xf6b6df,_0x17d3e2);};buddyChangeTasks[_0x5d83fd(0x1a2)](_0x32fb99[_0x5d83fd(0x1ad)](randomUUID),_0x32e5a0),napCatCore['session'][_0x5d83fd(0x1a0)]()['getBuddyList'](_0x2f4c84)['then'](_0x50f2a9=>{const _0x39d904=_0x5d83fd;_0x4ea5e1[_0x39d904(0x1b6)](logDebug,_0x4ea5e1[_0x39d904(0x1a7)],_0x50f2a9);});});}static async[_0xd4c679(0x1ac)](_0x47c73f,_0x41bf22){const _0x436052=_0xd4c679;napCatCore[_0x436052(0x1ab)][_0x436052(0x1a0)]()?.[_0x436052(0x1b0)]({'friendUid':_0x47c73f[_0x436052(0x1a8)],'reqTime':_0x47c73f[_0x436052(0x1a6)],'accept':_0x41bf22});}}

View File

@@ -1,57 +0,0 @@
import { GroupMember, GroupRequestOperateTypes, GroupMemberRole, GroupNotify, Group } from '../entities';
export declare class NTQQGroupApi {
static getGroups(forced?: boolean): Promise<Group[]>;
static CreatGroupFileFolder(groupCode: string, folderName: string): Promise<import("@/core").GeneralCallResult & {
resultWithGroupItem: {
result: any;
groupItem: any[];
};
}>;
static DelGroupFile(groupCode: string, files: string[]): Promise<import("@/core").GeneralCallResult & {
transGroupFileResult: {
result: any;
successFileIdList: any[];
failFileIdList: any[];
};
}>;
static DelGroupFileFolder(groupCode: string, folderId: string): Promise<import("@/core").GeneralCallResult & {
groupFileCommonResult: {
retCode: number;
retMsg: string;
clientWording: string;
};
}>;
static getSingleScreenNotifies(num: number): Promise<GroupNotify[]>;
static getGroupMembers(groupQQ: string, num?: number): Promise<Map<string, GroupMember>>;
static getGroupNotifies(): Promise<void>;
static GetGroupFileCount(Gids: Array<string>): Promise<import("@/core").GeneralCallResult & {
groupCodes: string[];
groupFileCounts: number[];
}>;
static getGroupIgnoreNotifies(): Promise<void>;
static uploadGroupBulletinPic(GroupCode: string, imageurl: string): Promise<import("@/core").GeneralCallResult & {
errCode: number;
picInfo?: {
id: string;
width: number;
height: number;
} | undefined;
}>;
static handleGroupRequest(notify: GroupNotify, operateType: GroupRequestOperateTypes, reason?: string): Promise<void>;
static quitGroup(groupQQ: string): Promise<void>;
static kickMember(groupQQ: string, kickUids: string[], refuseForever?: boolean, kickReason?: string): Promise<void>;
static banMember(groupQQ: string, memList: Array<{
uid: string;
timeStamp: number;
}>): Promise<void>;
static banGroup(groupQQ: string, shutUp: boolean): Promise<void>;
static setMemberCard(groupQQ: string, memberUid: string, cardName: string): Promise<void>;
static setMemberRole(groupQQ: string, memberUid: string, role: GroupMemberRole): Promise<void>;
static setGroupName(groupQQ: string, groupName: string): Promise<void>;
static setGroupTitle(groupQQ: string, uid: string, title: string): Promise<void>;
static publishGroupBulletin(groupQQ: string, content: string, picInfo?: {
id: string;
width: number;
height: number;
} | undefined, pinned?: number, confirmRequired?: number): Promise<import("@/core").GeneralCallResult>;
}

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
function _0x30de(){var _0x4dfdea=['30892fYcrRX','1978106FrErvJ','6017192UzyTuC','1143429tFSDZD','801784hQOjok','1050YkgOKc','32736447tmErVs','36awLbjC','1516245WndRLq'];_0x30de=function(){return _0x4dfdea;};return _0x30de();}(function(_0x1de97e,_0x53b6d7){var _0x5147ea=_0x3127,_0x10a0e3=_0x1de97e();while(!![]){try{var _0x49fe07=-parseInt(_0x5147ea(0xa3))/0x1+parseInt(_0x5147ea(0xa0))/0x2+parseInt(_0x5147ea(0x9e))/0x3+parseInt(_0x5147ea(0x9f))/0x4*(-parseInt(_0x5147ea(0xa4))/0x5)+-parseInt(_0x5147ea(0x9d))/0x6*(parseInt(_0x5147ea(0xa2))/0x7)+-parseInt(_0x5147ea(0xa1))/0x8+parseInt(_0x5147ea(0xa5))/0x9;if(_0x49fe07===_0x53b6d7)break;else _0x10a0e3['push'](_0x10a0e3['shift']());}catch(_0x3d504e){_0x10a0e3['push'](_0x10a0e3['shift']());}}}(_0x30de,0xee486));export*from'./file';export*from'./friend';export*from'./group';function _0x3127(_0x110d8c,_0x21de71){var _0x30de91=_0x30de();return _0x3127=function(_0x3127d4,_0x291243){_0x3127d4=_0x3127d4-0x9d;var _0x30a550=_0x30de91[_0x3127d4];return _0x30a550;},_0x3127(_0x110d8c,_0x21de71);}export*from'./msg';export*from'./user';export*from'./webapi';export*from'./sign';export*from'./system';

View File

@@ -1,31 +0,0 @@
import { GetFileListParam, Peer, RawMessage, SendMessageElement } from '@/core/entities';
import { GeneralCallResult } from '@/core/services/common';
export declare class NTQQMsgApi {
static setEmojiLike(peer: Peer, msgSeq: string, emojiId: string, set?: boolean): Promise<unknown>;
static getMultiMsg(peer: Peer, rootMsgId: string, parentMsgId: string): Promise<GeneralCallResult & {
msgList: RawMessage[];
} | undefined>;
static getMsgsByMsgId(peer: Peer, msgIds: string[]): Promise<GeneralCallResult & {
msgList: RawMessage[];
}>;
static getMsgsBySeqAndCount(peer: Peer, seq: string, count: number, desc: boolean, unknownArg: boolean): Promise<GeneralCallResult & {
msgList: RawMessage[];
}>;
static activateChat(peer: Peer): Promise<void>;
static activateChatAndGetHistory(peer: Peer): Promise<void>;
static setMsgRead(peer: Peer): Promise<GeneralCallResult>;
static getGroupFileList(GroupCode: string, params: GetFileListParam): Promise<{
FileList: Array<any>;
totalSpace: number;
usedSpace: number;
allUpload: boolean;
}>;
static getMsgHistory(peer: Peer, msgId: string, count: number): Promise<GeneralCallResult & {
msgList: RawMessage[];
}>;
static fetchRecentContact(): Promise<void>;
static recallMsg(peer: Peer, msgIds: string[]): Promise<void>;
static sendMsg(peer: Peer, msgElements: SendMessageElement[], waitComplete?: boolean, timeout?: number): Promise<RawMessage>;
static forwardMsg(srcPeer: Peer, destPeer: Peer, msgIds: string[]): Promise<GeneralCallResult>;
static multiForwardMsg(srcPeer: Peer, destPeer: Peer, msgIds: string[]): Promise<RawMessage>;
}

File diff suppressed because one or more lines are too long

View File

@@ -1,23 +0,0 @@
export interface IdMusicSignPostData {
type: 'qq' | '163';
id: string | number;
}
export interface CustomMusicSignPostData {
type: 'custom';
url: string;
audio: string;
title: string;
image?: string;
singer?: string;
}
export interface MiniAppLuaJsonType {
prompt: string;
title: string;
preview: string;
jumpUrl: string;
tag: string;
tagIcon: string;
source: string;
sourcelogo: string;
}
export declare function SignMiniApp(CardData: MiniAppLuaJsonType): Promise<string>;

View File

@@ -1 +0,0 @@
function _0x1cdc(){const _0x52229f=[';\x20skey=','genBkn','WNsrP','306FNUjyz','preview','signed_ark','\x5c/\x5c/','sourcelogo','4195qRUdIj','miniapp','title','uin','aWjNX','tagIcon','replace','https://h5.qzone.qq.com/v2/vip/tx/trpc/ark-share/GenNewSignedArk?g_tk=','&ark=','51tnItBa','stringify','jumpUrl','GET','21690uqUhhx','yKRsG','195oyiJTX','AaAVw','skey','normal','tag','22224XRNelA','173916uuvDFJ','source','tianxuan.imgJumpArk','20fyQjiM','p_skey=','2012346iZcXuj','p_skey','MiniApp\x20JSON\x20消息生成失败','42IhwlPM','data','157264BCSwTW','getQzoneCookies',';\x20p_uin=o','2773727oWKvpG','gljct','com.tencent.miniapp.lua','HttpGetJson','prompt','VEBFq','fcQLU','WccSO','38UHMRLY'];_0x1cdc=function(){return _0x52229f;};return _0x1cdc();}(function(_0x9cb42e,_0x1d6622){const _0x5868f1=_0x18e2,_0x1d3ea2=_0x9cb42e();while(!![]){try{const _0x213a11=parseInt(_0x5868f1(0x82))/0x1*(-parseInt(_0x5868f1(0x9f))/0x2)+parseInt(_0x5868f1(0x7e))/0x3*(parseInt(_0x5868f1(0x89))/0x4)+-parseInt(_0x5868f1(0x75))/0x5*(parseInt(_0x5868f1(0x70))/0x6)+-parseInt(_0x5868f1(0x92))/0x7*(-parseInt(_0x5868f1(0x94))/0x8)+parseInt(_0x5868f1(0x8f))/0x9+-parseInt(_0x5868f1(0x8d))/0xa*(-parseInt(_0x5868f1(0x97))/0xb)+parseInt(_0x5868f1(0x8a))/0xc*(-parseInt(_0x5868f1(0x84))/0xd);if(_0x213a11===_0x1d6622)break;else _0x1d3ea2['push'](_0x1d3ea2['shift']());}catch(_0x13da4e){_0x1d3ea2['push'](_0x1d3ea2['shift']());}}}(_0x1cdc,0x416ee));import{logDebug}from'@/common/utils/log';function _0x18e2(_0x227ff2,_0x15bf56){const _0x1cdc26=_0x1cdc();return _0x18e2=function(_0x18e234,_0x2c6981){_0x18e234=_0x18e234-0x6f;let _0x3ab75f=_0x1cdc26[_0x18e234];return _0x3ab75f;},_0x18e2(_0x227ff2,_0x15bf56);}import{NTQQUserApi}from'./user';import{selfInfo}from'../data';import{RequestUtil}from'@/common/utils/request';import{WebApi}from'./webapi';export async function SignMiniApp(_0x30846b){const _0x74356d=_0x18e2,_0x162349={'aWjNX':_0x74356d(0x99),'qjkHF':_0x74356d(0x8c),'YuczR':_0x74356d(0x76),'WccSO':_0x74356d(0x73),'yKRsG':function(_0x4c1f56,_0x22b76c){return _0x4c1f56+_0x22b76c;},'MsQyW':function(_0x7767d5,_0x2f9e11){return _0x7767d5+_0x2f9e11;},'IdOiF':_0x74356d(0x8e),'VEBFq':_0x74356d(0xa0),'MyRvd':_0x74356d(0x96),'AaAVw':function(_0x20266c,_0x486322){return _0x20266c+_0x486322;},'fcQLU':_0x74356d(0x7c),'gljct':_0x74356d(0x7d),'WNsrP':function(_0x51f71c,_0x45429d){return _0x51f71c(_0x45429d);},'MasKP':_0x74356d(0x81),'ZQJik':_0x74356d(0x91)};let _0x1c22ef={'app':_0x162349[_0x74356d(0x79)],'bizsrc':_0x162349['qjkHF'],'view':_0x162349['YuczR'],'prompt':_0x30846b[_0x74356d(0x9b)],'config':{'type':_0x74356d(0x87),'forward':0x1,'autosize':0x0},'meta':{'miniapp':{'title':_0x30846b[_0x74356d(0x77)],'preview':_0x30846b[_0x74356d(0x71)]['replace'](/\\/g,_0x162349[_0x74356d(0x9e)]),'jumpUrl':_0x30846b[_0x74356d(0x80)][_0x74356d(0x7b)](/\\/g,_0x74356d(0x73)),'tag':_0x30846b[_0x74356d(0x88)],'tagIcon':_0x30846b[_0x74356d(0x7a)][_0x74356d(0x7b)](/\\/g,_0x162349[_0x74356d(0x9e)]),'source':_0x30846b[_0x74356d(0x8b)],'sourcelogo':_0x30846b[_0x74356d(0x74)][_0x74356d(0x7b)](/\\/g,_0x162349[_0x74356d(0x9e)])}}};const _0x1095c4=await NTQQUserApi['getSkey']();let _0x2c7887=await NTQQUserApi[_0x74356d(0x95)]();const _0x1ca673=WebApi[_0x74356d(0xa1)](_0x2c7887['p_skey']),_0x6ec545=_0x162349[_0x74356d(0x83)](_0x162349[_0x74356d(0x83)](_0x162349[_0x74356d(0x83)](_0x162349['yKRsG'](_0x162349['MsQyW'](_0x162349[_0x74356d(0x83)](_0x162349['IdOiF'],_0x2c7887[_0x74356d(0x90)]),_0x162349[_0x74356d(0x9c)])+_0x2c7887[_0x74356d(0x86)],_0x162349['MyRvd']),selfInfo[_0x74356d(0x78)]),';\x20uin=o'),selfInfo['uin']);let _0x432849=_0x162349[_0x74356d(0x85)](_0x162349[_0x74356d(0x9d)],_0x1ca673)+_0x162349[_0x74356d(0x98)]+_0x162349[_0x74356d(0x6f)](encodeURIComponent,JSON[_0x74356d(0x7f)](_0x1c22ef)),_0x5027ac='';try{let _0x2edab5=await RequestUtil[_0x74356d(0x9a)](_0x432849,_0x162349['MasKP'],undefined,{'Cookie':_0x6ec545});_0x5027ac=_0x2edab5[_0x74356d(0x93)][_0x74356d(0x72)];}catch(_0x642965){logDebug(_0x162349['ZQJik'],_0x642965);}return _0x5027ac;}

View File

@@ -1,7 +0,0 @@
export declare class NTQQSystemApi {
static hasOtherRunningQQProcess(): Promise<boolean>;
static ORCImage(filePath: string): Promise<import("@/core").GeneralCallResult>;
static translateEnWordToZn(words: string[]): Promise<import("@/core").GeneralCallResult & {
words: string[];
}>;
}

View File

@@ -1 +0,0 @@
var _0x3dc00c=_0x4e9e;(function(_0x46fb3f,_0x2f04e2){var _0x2cf4a2=_0x4e9e,_0x5aac15=_0x46fb3f();while(!![]){try{var _0x46fd5b=parseInt(_0x2cf4a2(0x1b2))/0x1+-parseInt(_0x2cf4a2(0x1ad))/0x2*(-parseInt(_0x2cf4a2(0x1ba))/0x3)+parseInt(_0x2cf4a2(0x1ae))/0x4*(-parseInt(_0x2cf4a2(0x1bb))/0x5)+parseInt(_0x2cf4a2(0x1b4))/0x6*(parseInt(_0x2cf4a2(0x1af))/0x7)+-parseInt(_0x2cf4a2(0x1ac))/0x8*(parseInt(_0x2cf4a2(0x1bc))/0x9)+-parseInt(_0x2cf4a2(0x1b8))/0xa*(-parseInt(_0x2cf4a2(0x1b7))/0xb)+-parseInt(_0x2cf4a2(0x1b3))/0xc;if(_0x46fd5b===_0x2f04e2)break;else _0x5aac15['push'](_0x5aac15['shift']());}catch(_0x955f77){_0x5aac15['push'](_0x5aac15['shift']());}}}(_0x1f9a,0xb28da));import{napCatCore}from'@/core';function _0x1f9a(){var _0x47a198=['hasOtherRunningQQProcess','ORCImage','4206213FkCUVC','30ZKSLmf','getNodeMiscService','3WXhsli','24515NJgMSU','39852OyufwS','util','560FjGvKF','2142714VPFzRs','616SNoHcU','24626KRooHD','session','wantWinScreenOCR','899562TokBzb','24599016FXjJJy','1242RlefLV'];_0x1f9a=function(){return _0x47a198;};return _0x1f9a();}function _0x4e9e(_0x37259c,_0x1cbff9){var _0x1f9a7c=_0x1f9a();return _0x4e9e=function(_0x4e9e97,_0x7bc1b0){_0x4e9e97=_0x4e9e97-0x1ab;var _0x5aad0d=_0x1f9a7c[_0x4e9e97];return _0x5aad0d;},_0x4e9e(_0x37259c,_0x1cbff9);}export class NTQQSystemApi{static async[_0x3dc00c(0x1b5)](){var _0x55cb72=_0x3dc00c;return napCatCore[_0x55cb72(0x1ab)]['hasOtherRunningQQProcess']();}static async[_0x3dc00c(0x1b6)](_0x4c71ab){var _0x1f31c6=_0x3dc00c;return napCatCore[_0x1f31c6(0x1b0)][_0x1f31c6(0x1b9)]()[_0x1f31c6(0x1b1)](_0x4c71ab);}static async['translateEnWordToZn'](_0x1b7b7e){return napCatCore['session']['getRichMediaService']()['translateEnWordToZn'](_0x1b7b7e);}}

View File

@@ -1,25 +0,0 @@
import { User } from '@/core/entities';
import { GeneralCallResult } from '@/core';
export declare class NTQQUserApi {
static setSelfOnlineStatus(status: number, extStatus: number, batteryStatus: number): Promise<GeneralCallResult>;
static like(uid: string, count?: number): Promise<{
result: number;
errMsg: string;
succCounts: number;
}>;
static setQQAvatar(filePath: string): Promise<{
result: number;
errMsg: string;
}>;
static getSelfInfo(): Promise<void>;
static getUserInfo(uid: string): Promise<void>;
static getUserDetailInfo(uid: string): Promise<User>;
static getPSkey(domainList: string[], cached?: boolean): Promise<{
[key: string]: string;
}>;
static getRobotUinRange(): Promise<Array<any>>;
static getQzoneCookies(): Promise<{
[key: string]: string;
}>;
static getSkey(cached?: boolean): Promise<string | undefined>;
}

File diff suppressed because one or more lines are too long

View File

@@ -1,105 +0,0 @@
export declare enum WebHonorType {
ALL = "all",
TALKACTIVE = "talkative",
PERFROMER = "performer",
LEGEND = "legend",
STORONGE_NEWBI = "strong_newbie",
EMOTION = "emotion"
}
export interface WebApiGroupMember {
uin: number;
role: number;
g: number;
join_time: number;
last_speak_time: number;
lv: {
point: number;
level: number;
};
card: string;
tags: string;
flag: number;
nick: string;
qage: number;
rm: number;
}
export interface WebApiGroupNoticeFeed {
u: number;
fid: string;
pubt: number;
msg: {
text: string;
text_face: string;
title: string;
pics?: {
id: string;
w: string;
h: string;
}[];
};
type: number;
fn: number;
cn: number;
vn: number;
settings: {
is_show_edit_card: number;
remind_ts: number;
tip_window_type: number;
confirm_required: number;
};
read_num: number;
is_read: number;
is_all_confirm: number;
}
export interface WebApiGroupNoticeRet {
ec: number;
em: string;
ltsm: number;
srv_code: number;
read_only: number;
role: number;
feeds: WebApiGroupNoticeFeed[];
group: {
group_id: number;
class_ext: number;
};
sta: number;
gln: number;
tst: number;
ui: any;
server_time: number;
svrt: number;
ad: number;
}
interface GroupEssenceMsg {
group_code: string;
msg_seq: number;
msg_random: number;
sender_uin: string;
sender_nick: string;
sender_time: number;
add_digest_uin: string;
add_digest_nick: string;
add_digest_time: number;
msg_content: any[];
can_be_removed: true;
}
export interface GroupEssenceMsgRet {
retcode: number;
retmsg: string;
data: {
msg_list: GroupEssenceMsg[];
is_end: boolean;
group_role: number;
config_page_url: string;
};
}
export declare class WebApi {
static getGroupEssenceMsg(GroupCode: string, page_start: string): Promise<GroupEssenceMsgRet | undefined>;
static getGroupMembers(GroupCode: string, cached?: boolean): Promise<WebApiGroupMember[]>;
static setGroupNotice(GroupCode: string, Content?: string): Promise<any>;
static getGrouptNotice(GroupCode: string): Promise<undefined | WebApiGroupNoticeRet>;
static genBkn(sKey: string): string;
static getGroupHonorInfo(groupCode: string, getType: WebHonorType): Promise<any>;
}
export {};

File diff suppressed because one or more lines are too long

View File

@@ -1,11 +0,0 @@
export interface NTQQWindow {
windowName: string;
windowUrlHash: string;
}
export declare class NTQQWindows {
static GroupHomeWorkWindow: NTQQWindow;
static GroupNotifyFilterWindow: NTQQWindow;
static GroupEssenceWindow: NTQQWindow;
}
export declare class NTQQWindowApi {
}

File diff suppressed because one or more lines are too long

View File

@@ -1,36 +0,0 @@
/// <reference types="node" />
import { NodeIQQNTWrapperEngine, NodeIQQNTWrapperSession, NodeQQNTWrapperUtil } from '@/core/wrapper';
import { QuickLoginResult } from '@/core/services';
import { BuddyListener, GroupListener, MsgListener, ProfileListener } from '@/core/listeners';
export interface OnLoginSuccess {
(uin: string, uid: string): void | Promise<void>;
}
export declare class NapCatCore {
readonly session: NodeIQQNTWrapperSession;
readonly util: NodeQQNTWrapperUtil;
readonly engine: NodeIQQNTWrapperEngine;
private readonly loginListener;
private loginService;
private onLoginSuccessFuncList;
private proxyHandler;
constructor();
get dataPath(): string;
get dataPathGlobal(): string;
private initConfig;
private initSession;
private initDataListener;
addListener(listener: BuddyListener | GroupListener | MsgListener | ProfileListener): number;
onLoginSuccess(func: OnLoginSuccess): void;
quickLogin(uin: string): Promise<QuickLoginResult>;
qrLogin(cb: (url: string, base64: string, buffer: Buffer) => Promise<void>): Promise<{
url: string;
base64: string;
buffer: Buffer;
}>;
passwordLogin(uin: string, password: string, proofSig?: string, proofRand?: string, proofSid?: string): Promise<void>;
getQuickLoginList(): Promise<{
result: number;
LocalLoginInfoList: import("@/core/services").LoginListItem[];
}>;
}
export declare const napCatCore: NapCatCore;

File diff suppressed because one or more lines are too long

View File

@@ -1,45 +0,0 @@
import { type Friend, type FriendRequest, type Group, type GroupMember, GroupNotify, type SelfInfo, BuddyCategoryType } from './entities';
import { WebApiGroupMember } from '@/core/apis';
export declare const Credentials: {
Skey: string;
CreatTime: number;
Cookies: Map<string, string>;
ClientKey: string;
KeyIndex: string;
PskeyData: Map<string, string>;
PskeyTime: Map<string, number>;
};
export declare const WebGroupData: {
GroupData: Map<string, WebApiGroupMember[]>;
GroupTime: Map<string, number>;
};
export declare const selfInfo: SelfInfo;
export declare const groups: Map<string, Group>;
export declare function deleteGroup(groupQQ: string): void;
export declare const groupMembers: Map<string, Map<string, GroupMember>>;
export declare const friends: Map<string, Friend>;
export declare const friendRequests: Record<string, FriendRequest>;
export declare const groupNotifies: Record<string, GroupNotify>;
export declare const napCatError: {
ffmpegError: string;
httpServerError: string;
wsServerError: string;
otherError: string;
};
export declare function getFriend(uinOrUid: string): Promise<Friend | undefined>;
export declare function getGroup(qq: string | number): Promise<Group | undefined>;
export declare function getGroupMember(groupQQ: string | number, memberUinOrUid: string | number): Promise<GroupMember | null | undefined>;
export declare const uid2UinMap: Record<string, string>;
export declare function getUidByUin(uin: string): string | undefined;
export declare const tempGroupCodeMap: Record<string, string>;
export declare const rawFriends: Array<BuddyCategoryType>;
export declare const stat: {
packet_received: number;
packet_sent: number;
message_received: number;
message_sent: number;
last_message_time: number;
disconnect_times: number;
lost_times: number;
packet_lost: number;
};

View File

@@ -1 +0,0 @@
(function(_0x5a094b,_0x53bca0){const _0x3456d1=_0x31c6,_0x4f983c=_0x5a094b();while(!![]){try{const _0x4ae09a=parseInt(_0x3456d1(0x154))/0x1*(-parseInt(_0x3456d1(0x167))/0x2)+parseInt(_0x3456d1(0x15a))/0x3*(parseInt(_0x3456d1(0x16b))/0x4)+-parseInt(_0x3456d1(0x169))/0x5*(parseInt(_0x3456d1(0x163))/0x6)+-parseInt(_0x3456d1(0x16c))/0x7*(-parseInt(_0x3456d1(0x159))/0x8)+-parseInt(_0x3456d1(0x15f))/0x9*(-parseInt(_0x3456d1(0x15e))/0xa)+parseInt(_0x3456d1(0x16a))/0xb*(parseInt(_0x3456d1(0x153))/0xc)+-parseInt(_0x3456d1(0x161))/0xd*(parseInt(_0x3456d1(0x15d))/0xe);if(_0x4ae09a===_0x53bca0)break;else _0x4f983c['push'](_0x4f983c['shift']());}catch(_0x4caddb){_0x4f983c['push'](_0x4f983c['shift']());}}}(_0x59e3,0x8936e));import{isNumeric}from'@/common/utils/helper';import{NTQQGroupApi}from'@/core/apis';export const Credentials={'Skey':'','CreatTime':0x0,'Cookies':new Map(),'ClientKey':'','KeyIndex':'','PskeyData':new Map(),'PskeyTime':new Map()};function _0x31c6(_0x35d7d7,_0x2b617d){const _0x59e372=_0x59e3();return _0x31c6=function(_0x31c682,_0x1e4a6f){_0x31c682=_0x31c682-0x152;let _0x42b77b=_0x59e372[_0x31c682];return _0x42b77b;},_0x31c6(_0x35d7d7,_0x2b617d);}export const WebGroupData={'GroupData':new Map(),'GroupTime':new Map()};export const selfInfo={'uid':'','uin':'','nick':'','online':!![]};export const groups=new Map();export function deleteGroup(_0x38683a){const _0x5e820b=_0x31c6;groups[_0x5e820b(0x164)](_0x38683a),groupMembers[_0x5e820b(0x164)](_0x38683a);}export const groupMembers=new Map();export const friends=new Map();export const friendRequests={};function _0x59e3(){const _0xfa9a31=['forEach','values','from','47744NGjoxX','15lfbwQC','toString','wtogw','20818drtaZM','10ockpIG','7553727kuARYX','vLIBs','20059KEEnWe','set','441186NHzJPt','delete','getGroupMembers','find','12AeFSrV','get','45CpSUlf','71621suZMQH','713308iGUJgo','1036GmiFwy','uin','2064TjVpoN','35974zPmWzG','getGroups'];_0x59e3=function(){return _0xfa9a31;};return _0x59e3();}export const groupNotifies={};export const napCatError={'ffmpegError':'','httpServerError':'','wsServerError':'','otherError':'NapCat未能正常启动请检查日志查看错误'};export async function getFriend(_0x241e6e){const _0x340a30=_0x31c6,_0x33670b={'wtogw':function(_0x33b955,_0x412eaa){return _0x33b955(_0x412eaa);}};_0x241e6e=_0x241e6e[_0x340a30(0x15b)]();if(_0x33670b[_0x340a30(0x15c)](isNumeric,_0x241e6e)){const _0x887b8a=Array[_0x340a30(0x158)](friends[_0x340a30(0x157)]());return _0x887b8a[_0x340a30(0x166)](_0x1e9b17=>_0x1e9b17['uin']===_0x241e6e);}else return friends[_0x340a30(0x168)](_0x241e6e);}export async function getGroup(_0xa20a5d){const _0x5ea1e2=_0x31c6;let _0x12a308=groups[_0x5ea1e2(0x168)](_0xa20a5d[_0x5ea1e2(0x15b)]());if(!_0x12a308)try{const _0x10a3e8=await NTQQGroupApi[_0x5ea1e2(0x155)]();_0x10a3e8['length']&&_0x10a3e8[_0x5ea1e2(0x156)](_0x3246a2=>{const _0x41b173=_0x5ea1e2;groups[_0x41b173(0x162)](_0x3246a2['groupCode'],_0x3246a2);});}catch(_0x5453c1){return undefined;}return _0x12a308=groups['get'](_0xa20a5d[_0x5ea1e2(0x15b)]()),_0x12a308;}export async function getGroupMember(_0x42d756,_0x4ebf44){const _0x28311c=_0x31c6,_0x1223dc={'vLIBs':function(_0x4eb939){return _0x4eb939();}};_0x42d756=_0x42d756[_0x28311c(0x15b)](),_0x4ebf44=_0x4ebf44['toString']();let _0x550c2d=groupMembers[_0x28311c(0x168)](_0x42d756);if(!_0x550c2d)try{_0x550c2d=await NTQQGroupApi['getGroupMembers'](_0x42d756),groupMembers['set'](_0x42d756,_0x550c2d);}catch(_0x4f4d31){return null;}const _0x1b91eb=()=>{const _0x430760=_0x28311c;let _0x481c1e=undefined;return isNumeric(_0x4ebf44)?_0x481c1e=Array[_0x430760(0x158)](_0x550c2d['values']())['find'](_0x34adef=>_0x34adef[_0x430760(0x152)]===_0x4ebf44):_0x481c1e=_0x550c2d[_0x430760(0x168)](_0x4ebf44),_0x481c1e;};let _0x5ce91f=_0x1223dc[_0x28311c(0x160)](_0x1b91eb);return!_0x5ce91f&&(_0x550c2d=await NTQQGroupApi[_0x28311c(0x165)](_0x42d756),_0x5ce91f=_0x1223dc['vLIBs'](_0x1b91eb)),_0x5ce91f;}export const uid2UinMap={};export function getUidByUin(_0xb040d4){for(const _0x235ea2 in uid2UinMap){if(uid2UinMap[_0x235ea2]===_0xb040d4)return _0x235ea2;}}export const tempGroupCodeMap={};export const rawFriends=[];export const stat={'packet_received':0x0,'packet_sent':0x0,'message_received':0x0,'message_sent':0x0,'last_message_time':0x0,'disconnect_times':0x0,'lost_times':0x0,'packet_lost':0x0};

View File

@@ -1 +0,0 @@
function _0x5c31(){var _0x1db4e6=['42294ppkGbi','zGRyP','3076eaeMyR','4454028cTVHPk','103085wXZQBT','dQpho','9459828FMoOhf','AUDIO','378mgINbc','OTHER','IMAGE','329540lgQAeX','1203ukPxVd','116VYCWoS','144XuCJoS','402788rQsLKv','nSEAa','aYVHC','33BEEUPR','QRnTE'];_0x5c31=function(){return _0x1db4e6;};return _0x5c31();}(function(_0x147917,_0x1cff81){var _0x4e2075=_0x82aa,_0xd3b3a3=_0x147917();while(!![]){try{var _0x361792=parseInt(_0x4e2075(0x1c7))/0x1+-parseInt(_0x4e2075(0x1ba))/0x2*(parseInt(_0x4e2075(0x1c4))/0x3)+-parseInt(_0x4e2075(0x1c5))/0x4*(-parseInt(_0x4e2075(0x1bc))/0x5)+-parseInt(_0x4e2075(0x1bb))/0x6+-parseInt(_0x4e2075(0x1b8))/0x7*(-parseInt(_0x4e2075(0x1c6))/0x8)+parseInt(_0x4e2075(0x1c0))/0x9*(-parseInt(_0x4e2075(0x1c3))/0xa)+-parseInt(_0x4e2075(0x1b6))/0xb*(-parseInt(_0x4e2075(0x1be))/0xc);if(_0x361792===_0x1cff81)break;else _0xd3b3a3['push'](_0xd3b3a3['shift']());}catch(_0x628cb6){_0xd3b3a3['push'](_0xd3b3a3['shift']());}}}(_0x5c31,0xb2872));;function _0x82aa(_0x28f252,_0x2953f2){var _0x5c31ab=_0x5c31();return _0x82aa=function(_0x82aa27,_0x4e839c){_0x82aa27=_0x82aa27-0x1b4;var _0x116513=_0x5c31ab[_0x82aa27];return _0x116513;},_0x82aa(_0x28f252,_0x2953f2);}export var CacheFileType;(function(_0x55408e){var _0x5eaa9a=_0x82aa,_0x3be7a7={'zGRyP':_0x5eaa9a(0x1c2),'dQpho':'VIDEO','QRnTE':_0x5eaa9a(0x1bf),'aYVHC':'DOCUMENT','nSEAa':_0x5eaa9a(0x1c1)};_0x55408e[_0x55408e[_0x5eaa9a(0x1c2)]=0x0]=_0x3be7a7[_0x5eaa9a(0x1b9)],_0x55408e[_0x55408e[_0x3be7a7['dQpho']]=0x1]=_0x3be7a7[_0x5eaa9a(0x1bd)],_0x55408e[_0x55408e[_0x3be7a7[_0x5eaa9a(0x1b7)]]=0x2]=_0x3be7a7[_0x5eaa9a(0x1b7)],_0x55408e[_0x55408e[_0x3be7a7['aYVHC']]=0x3]=_0x3be7a7[_0x5eaa9a(0x1b5)],_0x55408e[_0x55408e[_0x3be7a7['nSEAa']]=0x4]=_0x3be7a7[_0x5eaa9a(0x1b4)];}(CacheFileType||(CacheFileType={})));

View File

@@ -1,18 +0,0 @@
import { AtType, SendArkElement, SendFaceElement, SendFileElement, SendMarkdownElement, SendMarketFaceElement, SendPicElement, SendPttElement, SendReplyElement, SendTextElement, SendVideoElement } from './index';
export declare const mFaceCache: Map<string, string>;
export declare class SendMsgElementConstructor {
static text(content: string): SendTextElement;
static at(atUid: string, atNtUid: string, atType: AtType, atName: string): SendTextElement;
static reply(msgSeq: string, msgId: string, senderUin: string, senderUinStr: string): SendReplyElement;
static pic(picPath: string, summary?: string, subType?: 0 | 1): Promise<SendPicElement>;
static file(filePath: string, fileName?: string): Promise<SendFileElement>;
static video(filePath: string, fileName?: string, diyThumbPath?: string): Promise<SendVideoElement>;
static ptt(pttPath: string): Promise<SendPttElement>;
static face(faceId: number): SendFaceElement;
static mface(emojiPackageId: number, emojiId: string, key: string, faceName: string): SendMarketFaceElement;
static dice(resultId: number | null): SendFaceElement;
static rps(resultId: number | null): SendFaceElement;
static ark(data: any): SendArkElement;
static markdown(content: string): SendMarkdownElement;
static miniapp(): Promise<SendArkElement>;
}

File diff suppressed because one or more lines are too long

View File

@@ -1,52 +0,0 @@
import { QQLevel, Sex } from './user';
export interface Group {
groupCode: string;
maxMember: number;
memberCount: number;
groupName: string;
groupStatus: 0;
memberRole: 2;
isTop: boolean;
toppedTimestamp: string;
privilegeFlag: number;
isConf: boolean;
hasModifyConfGroupFace: boolean;
hasModifyConfGroupName: boolean;
remarkName: string;
hasMemo: boolean;
groupShutupExpireTime: string;
personShutupExpireTime: string;
discussToGroupUin: string;
discussToGroupMaxMsgSeq: number;
discussToGroupTime: number;
groupFlagExt: number;
authGroupType: number;
groupCreditLevel: number;
groupFlagExt3: number;
groupOwnerId: {
memberUin: string;
memberUid: string;
};
}
export declare enum GroupMemberRole {
normal = 2,
admin = 3,
owner = 4
}
export interface GroupMember {
memberSpecialTitle?: string;
avatarPath: string;
cardName: string;
cardType: number;
isDelete: boolean;
nick: string;
qid: string;
remark: string;
role: GroupMemberRole;
shutUpTime: number;
uid: string;
uin: string;
isRobot: boolean;
sex?: Sex;
qqLevel?: QQLevel;
}

View File

@@ -1 +0,0 @@
(function(_0x21e602,_0x20ce31){var _0x5070c1=_0x4f36,_0x24284f=_0x21e602();while(!![]){try{var _0x1b0065=-parseInt(_0x5070c1(0x18d))/0x1+-parseInt(_0x5070c1(0x192))/0x2+parseInt(_0x5070c1(0x18e))/0x3*(parseInt(_0x5070c1(0x190))/0x4)+parseInt(_0x5070c1(0x18b))/0x5*(-parseInt(_0x5070c1(0x195))/0x6)+parseInt(_0x5070c1(0x194))/0x7*(parseInt(_0x5070c1(0x193))/0x8)+-parseInt(_0x5070c1(0x189))/0x9*(-parseInt(_0x5070c1(0x198))/0xa)+-parseInt(_0x5070c1(0x197))/0xb*(-parseInt(_0x5070c1(0x18f))/0xc);if(_0x1b0065===_0x20ce31)break;else _0x24284f['push'](_0x24284f['shift']());}catch(_0x44ab1e){_0x24284f['push'](_0x24284f['shift']());}}}(_0x218f,0x5878d));export var GroupMemberRole;function _0x4f36(_0x932d05,_0x20b0b6){var _0x218f4f=_0x218f();return _0x4f36=function(_0x4f36fa,_0xdbf4ef){_0x4f36fa=_0x4f36fa-0x189;var _0x32705e=_0x218f4f[_0x4f36fa];return _0x32705e;},_0x4f36(_0x932d05,_0x20b0b6);}function _0x218f(){var _0x3b067f=['9264wBKBRJ','203NEPQSe','303882riRAbd','normal','11035706KLZHsO','475990ngzrJM','9CIdMsO','owner','15aqXlzA','yNiHR','426515ShFUZe','7965OZOQsy','12QHeqKR','308kQnQSR','zYAFj','696050mVQzqR'];_0x218f=function(){return _0x3b067f;};return _0x218f();}(function(_0x4d8d33){var _0x4a30fe=_0x4f36,_0x530a38={'yNiHR':_0x4a30fe(0x196),'zYAFj':'admin'};_0x4d8d33[_0x4d8d33[_0x530a38[_0x4a30fe(0x18c)]]=0x2]=_0x530a38['yNiHR'],_0x4d8d33[_0x4d8d33[_0x530a38[_0x4a30fe(0x191)]]=0x3]=_0x530a38[_0x4a30fe(0x191)],_0x4d8d33[_0x4d8d33[_0x4a30fe(0x18a)]=0x4]=_0x4a30fe(0x18a);}(GroupMemberRole||(GroupMemberRole={})));

View File

@@ -1 +0,0 @@
(function(_0x35a77f,_0x50495b){var _0x3c50eb=_0x5b6d,_0x27878c=_0x35a77f();while(!![]){try{var _0x4cad93=-parseInt(_0x3c50eb(0x1f0))/0x1*(parseInt(_0x3c50eb(0x1f2))/0x2)+-parseInt(_0x3c50eb(0x1e7))/0x3*(-parseInt(_0x3c50eb(0x1e8))/0x4)+-parseInt(_0x3c50eb(0x1eb))/0x5*(-parseInt(_0x3c50eb(0x1ed))/0x6)+-parseInt(_0x3c50eb(0x1ee))/0x7*(-parseInt(_0x3c50eb(0x1ec))/0x8)+-parseInt(_0x3c50eb(0x1e6))/0x9+parseInt(_0x3c50eb(0x1ef))/0xa*(parseInt(_0x3c50eb(0x1e9))/0xb)+parseInt(_0x3c50eb(0x1f1))/0xc*(parseInt(_0x3c50eb(0x1ea))/0xd);if(_0x4cad93===_0x50495b)break;else _0x27878c['push'](_0x27878c['shift']());}catch(_0x4824b8){_0x27878c['push'](_0x27878c['shift']());}}}(_0x4cd4,0x56fae));export*from'./user';export*from'./group';export*from'./msg';export*from'./notify';function _0x4cd4(){var _0x1a761d=['2kUbpqo','10658244eBHabY','608666GijGFn','6334884aJEHwE','3jaFJnT','961892Koqsex','22HxEwqH','13PgFnVN','5hATDtz','8oIJVNj','1001094ySLKGo','127771SiwLTQ','1775250DvwJvF'];_0x4cd4=function(){return _0x1a761d;};return _0x4cd4();}function _0x5b6d(_0x8a273c,_0x5e326a){var _0x4cd44d=_0x4cd4();return _0x5b6d=function(_0x5b6dfa,_0x26492b){_0x5b6dfa=_0x5b6dfa-0x1e6;var _0x18858e=_0x4cd44d[_0x5b6dfa];return _0x18858e;},_0x5b6d(_0x8a273c,_0x5e326a);}export*from'./cache';export*from'./constructor';

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