Compare commits

...

532 Commits

Author SHA1 Message Date
手瓜一十雪
0637882fbc release: rnm tx 2024-09-18 10:53:37 +08:00
手瓜一十雪
3f785bab20 feat: NodeIO3MiscService 2024-09-18 10:44:35 +08:00
手瓜一十雪
a4ca89bdd6 fi: 2.6.4 2024-09-17 23:40:44 +08:00
手瓜一十雪
1a64e796bd release: 2.6.4 2024-09-17 23:17:19 +08:00
手瓜一十雪
a8b85a34f7 feat: 追平NT逻辑 2024-09-17 23:17:02 +08:00
手瓜一十雪
e7bec7d6b0 feat: systemPlatform标准化 2024-09-17 22:24:09 +08:00
手瓜一十雪
a582026037 release:2.6.3 2024-09-17 13:57:54 +08:00
手瓜一十雪
1a67a001c5 style: lint 2024-09-17 13:15:12 +08:00
手瓜一十雪
406deac592 fix:api外的推送事件 2024-09-17 13:14:18 +08:00
手瓜一十雪
e719ae0676 release: 2.6.2 2024-09-17 13:01:02 +08:00
手瓜一十雪
d8b7726440 release:2.6.1 2024-09-17 11:46:20 +08:00
手瓜一十雪
49f642e712 Merge pull request #379 from NapNeko/2.6.0
推进2.6.0大幅度重写
2024-09-17 11:43:13 +08:00
手瓜一十雪
70117016ce shell: 移除旧代码实现 2024-09-17 11:23:41 +08:00
手瓜一十雪
a4738f6281 feat: 开发依赖清理 2024-09-17 11:14:23 +08:00
手瓜一十雪
b1fc72d696 chore: 移除旧版本逻辑 2024-09-17 11:08:47 +08:00
手瓜一十雪
457c2c2b50 推进2.6.0大幅度重写 2024-09-17 11:06:07 +08:00
手瓜一十雪
48848d7d1a feat: 暂时砍掉V2Event 2024-09-17 10:59:28 +08:00
手瓜一十雪
55b07ca3ab feat: 彻底移除event旧实现 2024-09-17 09:23:19 +08:00
手瓜一十雪
a1d4882e18 feat: 规范化Promise 2024-09-17 09:19:45 +08:00
手瓜一十雪
3843795d8f Merge branch 'main' of https://github.com/NapNeko/NapCatQQ 2024-09-17 08:56:53 +08:00
手瓜一十雪
f2bf8d42da release: 2.5.5 2024-09-17 08:56:43 +08:00
Alen
a3b244e114 Merge pull request #378 from cnxysoft/upmain
fix: 下载文件失败
2024-09-17 03:50:22 +08:00
Alen
3093bdbc68 fix: 下载失败
优化下载逻辑
2024-09-17 03:39:43 +08:00
Alen
9ab0799283 Merge branch 'main' into upmain 2024-09-16 20:59:41 +08:00
手瓜一十雪
236bec11ed release: 2.5.4 2024-09-16 20:57:56 +08:00
手瓜一十雪
de48b0f940 Merge pull request #370 from NapNeko/28060
for: 28060
2024-09-16 20:54:54 +08:00
手瓜一十雪
4885d4db86 support: linux28060 2024-09-16 20:33:27 +08:00
手瓜一十雪
0c7bbda936 feat: Linux28060Appid 2024-09-16 20:29:04 +08:00
手瓜一十雪
fa07c2c1fb update: appid 2024-09-16 19:31:24 +08:00
手瓜一十雪
5d17a191f6 Merge branch 'main' into 28060 2024-09-16 19:12:10 +08:00
手瓜一十雪
67fb74d3c2 fix 2024-09-16 19:07:05 +08:00
手瓜一十雪
dc04cfc1b3 Revert "chore: workflow"
This reverts commit 58cd38c4a8.
2024-09-16 19:03:14 +08:00
手瓜一十雪
d61d481965 Merge branch 'main' into 28060 2024-09-16 19:02:01 +08:00
手瓜一十雪
6b346ee1de fix 2024-09-16 19:01:01 +08:00
手瓜一十雪
d0f248aaf9 fix 2024-09-16 18:53:26 +08:00
手瓜一十雪
85c9227515 Merge branch 'main' into 28060 2024-09-16 18:52:32 +08:00
手瓜一十雪
73b6d3be84 release: 2.5.3 2024-09-16 18:51:26 +08:00
手瓜一十雪
1ff6ce2343 feat: FetchOtherProfileLike 2024-09-16 18:51:05 +08:00
手瓜一十雪
c145935d46 feat: contact 2024-09-16 18:47:51 +08:00
手瓜一十雪
e9ede6924e release: 2.5.2 2024-09-16 18:12:00 +08:00
手瓜一十雪
515a21761d back: linux to 27254 2024-09-16 18:11:33 +08:00
Alen
8d6397028b Revert "style"
This reverts commit 7e74578312.
2024-09-15 17:31:01 +08:00
Alen
eb4828d81f Merge branch 'main' into upmain 2024-09-15 17:24:43 +08:00
Alen
7e74578312 style 2024-09-15 17:24:02 +08:00
Alen
640e3516d4 style 2024-09-15 17:23:10 +08:00
手瓜一十雪
bd295a4632 Merge branch 'main' into 28060 2024-09-15 17:15:03 +08:00
Alen
166c30fe2c Merge pull request #375 from cnxysoft/test
fix: friend_add
2024-09-15 16:49:10 +08:00
Alen
66c1bab629 fix: friend_add
修复该事件中user_id为0的问题
2024-09-15 16:47:46 +08:00
手瓜一十雪
66656304f9 fix 2024-09-15 16:24:08 +08:00
手瓜一十雪
07f66e379d Merge branch 'main' into 28060 2024-09-15 16:20:37 +08:00
手瓜一十雪
7ae8fd60c4 release: 2.5.1 2024-09-15 16:20:26 +08:00
手瓜一十雪
7275066994 feat: skip Qrcode When Login 2024-09-15 16:18:43 +08:00
手瓜一十雪
385adec186 Merge branch 'main' into 28060 2024-09-15 16:04:19 +08:00
手瓜一十雪
96b5bec5ab feat: revert 2024-09-15 16:04:10 +08:00
手瓜一十雪
6a9ec4e5f0 Merge branch 'main' into 28060 2024-09-15 15:52:52 +08:00
手瓜一十雪
d9851493df fix: #361 2024-09-15 15:51:23 +08:00
手瓜一十雪
efdb520414 Merge branch 'main' into 28060 2024-09-15 15:38:32 +08:00
Alen
5548644aeb Merge pull request #373 from cnxysoft/test
fix: bugs
2024-09-15 15:37:09 +08:00
Alen
e3fcd91b2d Merge branch 'main' into test 2024-09-15 15:29:50 +08:00
手瓜一十雪
2cae30ba88 Merge branch 'main' into 28060 2024-09-15 15:20:52 +08:00
手瓜一十雪
58cd38c4a8 chore: workflow 2024-09-15 15:20:44 +08:00
手瓜一十雪
3300304feb Merge branch 'main' into 28060 2024-09-15 15:00:42 +08:00
手瓜一十雪
f0e376d06b fix: 移除错误action 2024-09-15 14:55:18 +08:00
手瓜一十雪
16f7bb48f2 fix: launcher 28060 2024-09-15 14:34:47 +08:00
手瓜一十雪
7f383dd29b Merge branch 'main' into 28060 2024-09-15 09:49:49 +08:00
手瓜一十雪
3dc529edf4 fix: #369 2024-09-15 09:39:17 +08:00
手瓜一十雪
45dedb4872 fix: 28060 2024-09-15 09:35:10 +08:00
手瓜一十雪
afcdd01c0d fix: typo 9.9.15-28060 2024-09-14 19:22:36 +08:00
手瓜一十雪
1164877e9a Merge branch 'main' of https://github.com/NapNeko/NapCatQQ 2024-09-14 19:10:34 +08:00
手瓜一十雪
fe92a449ba feat: 准备适配9.9.15-28060版本 2024-09-14 19:10:17 +08:00
Alen
401b0e2bd0 fix: 部分语音播放速率异常 2024-09-14 17:50:09 +08:00
手瓜一十雪
cf9c71fcc1 feat: 准备适配9.9.15-28606 2024-09-14 17:24:52 +08:00
Alen
15a2400069 fix: 修复文件删除失败
此处为重复插入待删列表
2024-09-14 11:12:43 +08:00
Alen
d68a39b49e fix: 定义错误 2024-09-14 10:49:43 +08:00
Alen
066ca22e24 Merge pull request #362 from cnxysoft/upmain
fix: 点赞通知解析
2024-09-14 01:24:20 +08:00
Alen
0418b926fe fix: 点赞通知解析失败 2024-09-14 00:42:10 +08:00
手瓜一十雪
be40bbdf40 release: 2.5.0 2024-09-13 17:42:11 +08:00
手瓜一十雪
df4f42e79e fix: video name 2024-09-13 17:38:11 +08:00
Alen
5f80058f70 Merge pull request #360 from cnxysoft/upmain
fix: bugs
2024-09-13 17:30:23 +08:00
Alen
0cbe59052d Merge branch 'main' into upmain 2024-09-13 17:25:59 +08:00
Alen
af28a26e37 fix: 无法发送url视频 2024-09-13 17:22:32 +08:00
Alen
70c596df93 fix: headers分割 2024-09-13 16:11:15 +08:00
手瓜一十雪
748b51428c feat: createUidFromTinyId 2024-09-13 16:05:25 +08:00
手瓜一十雪
8ad746397c feat: JoinDragonGroupEmoji 2024-09-13 15:35:05 +08:00
手瓜一十雪
45baed2f9a tag: deprecated 2024-09-13 15:30:04 +08:00
手瓜一十雪
74185f2d33 fix 2024-09-13 14:05:04 +08:00
手瓜一十雪
90a91e4105 fix 2024-09-13 13:54:12 +08:00
手瓜一十雪
11aa3a0315 feat: fetchOtherProfileLike 2024-09-12 20:05:46 +08:00
手瓜一十雪
0c2e39214f style: lint 2024-09-12 19:55:56 +08:00
手瓜一十雪
d89620d7a6 style: folder 2024-09-12 19:55:26 +08:00
手瓜一十雪
edf80775b7 release: v2.4.9 2024-09-12 19:47:57 +08:00
手瓜一十雪
46e56ac726 remove: polyFill 2024-09-12 19:35:54 +08:00
手瓜一十雪
40b2f6bfd6 release: 2.4.7 2024-09-12 18:31:11 +08:00
手瓜一十雪
911e4921e2 fix: 删除旧文件 2024-09-12 18:15:38 +08:00
手瓜一十雪
1db9bb419d fix: 一处异常字段 2024-09-12 10:55:18 +08:00
手瓜一十雪
c6241a94e3 style: lint 2024-09-12 09:28:41 +08:00
手瓜一十雪
1cbf75ca36 style: lint 2024-09-12 09:28:26 +08:00
手瓜一十雪
8f85c897c8 refactor: SysMsg 2024-09-12 09:20:10 +08:00
手瓜一十雪
29c31b7aba release: 2.4.6 2024-09-12 09:01:13 +08:00
手瓜一十雪
402919d6f2 feat: qucikLogin 2024-09-12 09:00:53 +08:00
手瓜一十雪
82608dd5ff fix: build 2024-09-12 00:17:35 +08:00
手瓜一十雪
f312368df2 build: fix2 2024-09-11 23:40:05 +08:00
手瓜一十雪
374fc64427 feat: delFile 2024-09-11 23:29:26 +08:00
手瓜一十雪
95bd74bb0d BUILD: TEST 2024-09-11 23:18:38 +08:00
手瓜一十雪
a9f5069649 Revert "build: debug"
This reverts commit 957f7ffd8d.
2024-09-11 22:41:59 +08:00
手瓜一十雪
957f7ffd8d build: debug 2024-09-11 22:28:28 +08:00
手瓜一十雪
336dd3ce10 chore: 扩展 2024-09-11 22:13:45 +08:00
手瓜一十雪
47a7295477 fix: typo
copilot
2024-09-11 20:24:50 +08:00
手瓜一十雪
341a0e1c2a chore: code 2024-09-11 20:10:52 +08:00
手瓜一十雪
c4f73d0eb8 Merge branch 'main' of https://github.com/NapNeko/NapCatQQ 2024-09-11 20:01:36 +08:00
手瓜一十雪
bd9258bae4 release: 2.4.5 2024-09-11 20:01:27 +08:00
手瓜一十雪
e3b3260aa0 Merge pull request #357 from cnxysoft/test
feat: 被点赞事件
2024-09-11 20:00:11 +08:00
手瓜一十雪
676766c99e refactor: protobuf 2024-09-11 19:56:51 +08:00
Alen
1025a07593 revert 2024-09-11 17:17:49 +08:00
Alen
00c3fcd033 Merge branch 'main' into test 2024-09-11 16:32:45 +08:00
Alen
b8457d4aff feat: 被点赞事件 2024-09-11 16:32:28 +08:00
手瓜一十雪
a2ecf10d19 feat: 全面迁移V2 2024-09-11 15:44:41 +08:00
Alen
1e63a2a7e7 test: 被赞事件(未完成) 2024-09-11 02:28:47 +08:00
手瓜一十雪
964014fc5c fix: #355 2024-09-10 22:27:06 +08:00
手瓜一十雪
fc2bb6d8c3 docs: 移除注释 2024-09-10 18:58:27 +08:00
手瓜一十雪
1b10252d76 remove: NTQQCacheApi 2024-09-10 18:42:49 +08:00
手瓜一十雪
ad8af12a10 refactor: fsPromise catch 2024-09-10 18:41:01 +08:00
手瓜一十雪
b040c9b118 refactor: audio 2024-09-10 18:39:14 +08:00
Alen
f6da7da90b Merge pull request #352 from cnxysoft/upmain
fix: 踢官方机器人报错
2024-09-10 00:40:58 +08:00
Alen
a745185408 fix: 踢官方机器人报错 2024-09-10 00:38:10 +08:00
手瓜一十雪
d3336f9027 release: 2.4.3 2024-09-09 21:37:22 +08:00
手瓜一十雪
daf42c8203 release: 2.4.2 2024-09-09 15:04:19 +08:00
手瓜一十雪
0a18bae3b5 fix: #351 2024-09-09 15:04:00 +08:00
手瓜一十雪
919705966c build: 2.4.1 2024-09-09 09:19:23 +08:00
手瓜一十雪
2c54aee63e build: test 2024-09-08 21:39:53 +08:00
手瓜一十雪
3f80bdf2a3 fix 2024-09-08 18:29:21 +08:00
手瓜一十雪
1c429b8dd3 fix: type 2024-09-08 18:26:07 +08:00
手瓜一十雪
5669e2b0b7 fix: 跟进实际逻辑 2024-09-08 18:21:55 +08:00
手瓜一十雪
1a6a43babf release: 2.4.0 2024-09-08 10:50:30 +08:00
手瓜一十雪
2650db5ddc fix: 字段V2 2024-09-08 10:48:33 +08:00
手瓜一十雪
255491a107 fix: hex计算问题 2024-09-08 10:41:30 +08:00
手瓜一十雪
5c64147dfa fix: encodeFile 2024-09-08 10:34:49 +08:00
手瓜一十雪
39f4118577 fix: #347 2024-09-08 10:30:30 +08:00
手瓜一十雪
f7f6e4736a fix #349 2024-09-08 10:24:36 +08:00
手瓜一十雪
c635da7ebb style: lint 2024-09-08 10:10:47 +08:00
手瓜一十雪
58124b006a Merge pull request #346 from NapNeko/Refactor-2.4.x
refactor: v2.4.0
2024-09-08 10:08:41 +08:00
手瓜一十雪
563aeccd0f refactor: fileNameEncode 2024-09-08 10:07:49 +08:00
手瓜一十雪
bd1a95a7f5 style: apis 2024-09-07 23:48:41 +08:00
手瓜一十雪
cdb25828f2 chore: code 2024-09-07 23:34:52 +08:00
手瓜一十雪
45803b3b23 Merge branch 'main' into Refactor-2.4.x 2024-09-07 23:34:23 +08:00
手瓜一十雪
0e5e3d3383 refactor: Service 2024-09-07 23:33:40 +08:00
Wesley F. Young
4672930037 update: move to recallMsg(V2) 2024-09-07 23:28:35 +08:00
手瓜一十雪
09be7131c3 refactor: 整理Service 2024-09-07 23:07:24 +08:00
手瓜一十雪
a804f90b9c refactor: adapter 2024-09-07 22:58:29 +08:00
手瓜一十雪
264cb6bbd2 release: 2.3.7 2024-09-07 21:29:22 +08:00
手瓜一十雪
b7772e867b Merge pull request #344 from qhy040404/patch-1
feat: 允许保存的token自动填写
2024-09-07 20:05:25 +08:00
Alen
cc0e77abfb Merge pull request #345 from cnxysoft/upmain
fix: 群成员列表获取
2024-09-07 19:55:41 +08:00
Alen
537d1c6f4f fix: 群成员列表获取
修复部分群成员列表返回为空的问题
2024-09-07 19:54:46 +08:00
Seijo Cecilia
80facadd67 chore: fix prefer-const 2024-09-07 19:49:54 +08:00
Seijo Cecilia
ba097dad23 update: recallMsgV2 2024-09-07 19:49:18 +08:00
qhy040404
c13c15d046 feat: 允许保存的token自动填写 2024-09-07 19:37:39 +08:00
手瓜一十雪
4f52128a06 fix 2024-09-07 17:17:07 +08:00
手瓜一十雪
500b2d0e6d release: 2.3.6 2024-09-07 13:31:32 +08:00
手瓜一十雪
e59d094feb feat: 提升使用体验 2024-09-07 10:45:45 +08:00
手瓜一十雪
a8372f14f8 fix: member info 2024-09-07 09:18:29 +08:00
Wesley F. Young
5174ff422d update: optimize message logging to console 2024-09-07 01:29:50 +08:00
Wesley F. Young
5c06751c3b fix: reference error 2024-09-07 00:04:35 +08:00
手瓜一十雪
ac2b0118a6 release: 2.3.5 2024-09-06 12:13:22 +08:00
手瓜一十雪
3eb8fd4abe Merge pull request #343 from drsanwujiang/patch-1
Fix bug: active HTTP adapter
2024-09-06 11:42:05 +08:00
Drsanwujiang
48b389ebe3 Fix bug: active HTTP adapter 2024-09-06 11:02:41 +08:00
手瓜一十雪
065adeb2cd fix 2024-09-06 07:40:33 +08:00
Wesley F. Young
269d0a06fe docs: deprecated features 2024-09-06 01:03:04 +08:00
手瓜一十雪
8eca26b1a5 fix 2024-09-05 18:03:32 +08:00
手瓜一十雪
3019ef7de4 release: 2.3.4 2024-09-05 16:15:21 +08:00
手瓜一十雪
522311b547 release: 2.3.4 2024-09-05 16:00:13 +08:00
手瓜一十雪
21061561ec fix: 字段兼容 2024-09-05 15:06:11 +08:00
手瓜一十雪
b83c41ad56 fix: #339 2024-09-05 14:55:11 +08:00
Version
e80a1cc64a chore:version change 2024-09-05 06:03:22 +00:00
手瓜一十雪
a01e4ca89f release: 2.3.1 2024-09-05 14:03:03 +08:00
手瓜一十雪
c20362e9b6 release: 2.3.0 2024-09-05 14:00:57 +08:00
手瓜一十雪
c90cfb99bd Merge pull request #340 from 123233513/main
%RetString% 增加引号,解决QQ目录包含空格的问题
2024-09-05 13:56:58 +08:00
123233513
7bcea14799 Update launcher.bat
%RetString% 增加引号,解决QQ目录包含空格的问题,比如安装在:C:\Program Files\Tencent\QQNT时,获取不到正确的路径
2024-09-05 10:41:04 +08:00
Wesley F. Young
b415c1a6d1 fix: file encoding 2024-09-04 23:26:55 +08:00
手瓜一十雪
452c72d280 release: 2.2.47 2024-09-04 23:12:38 +08:00
手瓜一十雪
48350be625 fix: 进一步筛选 2024-09-04 18:05:44 +08:00
手瓜一十雪
ab824fb219 Revert "update: normalize log"
This reverts commit d36a28fa81.
2024-09-04 18:05:01 +08:00
手瓜一十雪
043d8a1861 Revert "rollback unlink -> unlinkSync"
This reverts commit 074ac15d0f.
2024-09-04 18:04:58 +08:00
Seijo Cecilia
074ac15d0f rollback unlink -> unlinkSync 2024-09-04 15:50:23 +08:00
Seijo Cecilia
d36a28fa81 update: normalize log 2024-09-04 15:47:14 +08:00
手瓜一十雪
ba12bc6c91 docs: fix 2024-09-04 14:03:55 +08:00
手瓜一十雪
87332778e5 docs: fix 2024-09-04 13:26:38 +08:00
手瓜一十雪
453feb8473 release: 2.2.46 2024-09-04 13:21:40 +08:00
手瓜一十雪
8ff469974c build: test2 2024-09-04 13:19:02 +08:00
手瓜一十雪
994ec5ac0f fix: 极端情况下uin暴毙的情况 2024-09-04 12:40:59 +08:00
手瓜一十雪
43f7f9a363 build: test 2024-09-04 12:37:23 +08:00
Wesley F. Young
4a11ebc9b9 Revert "chore: optimize vite.config.ts"
This reverts commit 6a0d592491.
2024-09-04 10:38:10 +08:00
Wesley F. Young
d76a1305e7 chore: optimize import 2024-09-04 10:06:09 +08:00
Wesley F. Young
6a0d592491 chore: optimize vite.config.ts 2024-09-04 10:05:24 +08:00
Wesley F. Young
9898c2196d chore: optimize tsconfig.json 2024-09-04 00:29:55 +08:00
Wesley F. Young
41a8dc840f chore: completely comment onRecvSysMsg 2024-09-03 23:54:46 +08:00
Wesley F. Young
c3eaae9d88 chore: optimize imports 2024-09-03 23:46:10 +08:00
手瓜一十雪
3ca959b7a6 docs: update 2024-09-03 21:51:16 +08:00
手瓜一十雪
1d2e2b6e5c release: 2.2.44 2024-09-03 21:33:48 +08:00
手瓜一十雪
31d963c4d1 Merge pull request #336 from hguandl/feat/macos
feat: 支持 macOS
2024-09-03 21:16:03 +08:00
Hao Guan
7e96118cdc feat: support macOS 2024-09-03 20:17:10 +08:00
Hao Guan
709a0744bd chore: refactor path config 2024-09-03 20:14:35 +08:00
手瓜一十雪
f59248cc5a release: 2.2.43 2024-09-03 18:49:45 +08:00
手瓜一十雪
8647c5c607 fix: echo丢失问题 2024-09-03 18:37:28 +08:00
手瓜一十雪
6699ff38a1 Revert "fix: Error Handle"
This reverts commit d79b98bd55.
2024-09-03 18:36:20 +08:00
手瓜一十雪
d79b98bd55 fix: Error Handle 2024-09-03 18:34:33 +08:00
手瓜一十雪
5065a052fb release: 2.2.42 2024-09-03 18:12:59 +08:00
Wesley F. Young
45603bb78c fix(docs): unexpected spaces 2024-09-03 15:48:08 +08:00
手瓜一十雪
40948995b4 build: test 2024-09-03 14:09:17 +08:00
手瓜一十雪
4ccdd8d1d3 docs: update 2024-09-03 13:37:19 +08:00
手瓜一十雪
30d0174f47 fix: #334 2024-09-03 13:20:10 +08:00
手瓜一十雪
5a986ba25c release: 2.2.40 2024-09-03 13:01:37 +08:00
手瓜一十雪
fe63c24ac3 release: 2.2.39 2024-09-03 12:40:13 +08:00
手瓜一十雪
c384bd6875 release: 2.2.38 2024-09-02 17:31:27 +08:00
手瓜一十雪
dcbff3f569 release: 2.2.37 2024-09-02 16:31:42 +08:00
手瓜一十雪
7d91e05a69 fix: #332 2024-09-02 16:31:18 +08:00
手瓜一十雪
a5ce424a40 release: 2.2.36 2024-09-01 18:44:23 +08:00
手瓜一十雪
47c36ca062 release: 2.2.35 2024-09-01 18:24:49 +08:00
手瓜一十雪
c4c5b3bf8b fix: remark 2024-09-01 18:24:29 +08:00
手瓜一十雪
b1a81b0d12 release: 2.2.32 2024-09-01 16:32:13 +08:00
手瓜一十雪
ad9fe64850 release: 2.2.32 2024-09-01 16:13:41 +08:00
手瓜一十雪
f236349dc6 Revert "release:2.2.31"
This reverts commit 309d8a9f18.
2024-09-01 16:13:14 +08:00
手瓜一十雪
5f56c8a7d4 fix 2024-09-01 16:10:16 +08:00
手瓜一十雪
309d8a9f18 release:2.2.31 2024-09-01 15:59:02 +08:00
手瓜一十雪
2981799803 fix: file api 2024-09-01 14:11:28 +08:00
手瓜一十雪
00f8e1c0da Revert "fix: fileId"
This reverts commit ae009f98c1.
2024-09-01 13:41:19 +08:00
手瓜一十雪
e9482e2ec4 Revert "fix: encode fileId"
This reverts commit 9bff327377.
2024-09-01 13:41:14 +08:00
手瓜一十雪
9bff327377 fix: encode fileId 2024-09-01 12:17:42 +08:00
手瓜一十雪
ae009f98c1 fix: fileId 2024-09-01 12:17:17 +08:00
手瓜一十雪
77505a6f5b release: 2.2.31 2024-09-01 09:31:59 +08:00
手瓜一十雪
19c729aa23 chore: appid 2024-09-01 09:30:38 +08:00
Wesley F. Young
595888128a release: 2.2.30 2024-08-31 23:43:01 +08:00
手瓜一十雪
51589d0eae fix: 群精华上限修改 2024-08-31 22:11:18 +08:00
Wesley F. Young
f1643ac549 fix: get file way 01 get by msg & seq id 2024-08-31 20:30:42 +08:00
手瓜一十雪
3f24461612 feat: support 27597 2024-08-31 19:01:25 +08:00
Wesley F. Young
b5deb198de refactor: inline all NTQQXxxApi uses 2024-08-31 16:00:03 +08:00
Wesley F. Young
78452cf6a9 chore: clean code for webapi.ts 2024-08-31 14:18:11 +08:00
Wesley F. Young
4b4a784f56 chore: clean code for user.ts 2024-08-31 14:11:22 +08:00
Wesley F. Young
3e53cbcf8f chore: clean code for system.ts 2024-08-31 14:09:25 +08:00
Wesley F. Young
f34740f1f0 chore: clean code for group.ts 2024-08-31 14:07:48 +08:00
Wesley F. Young
b406bdfc37 chore: clean code for group.ts 2024-08-31 14:02:36 +08:00
Wesley F. Young
03c056702c chore: clean code for friend.ts 2024-08-31 13:53:30 +08:00
Wesley F. Young
9c5f3f1946 chore: clean code for collection.ts 2024-08-31 13:37:22 +08:00
Wesley F. Young
b50d7c24e7 refactor: move CacheApi to cache.ts 2024-08-31 13:36:21 +08:00
Wesley F. Young
f05cf68945 chore: clean code for file.ts 2024-08-31 13:35:29 +08:00
Alen
efc1875e35 release: 2.2.29 2024-08-31 12:53:50 +08:00
Alen
df063e6762 Merge pull request #326 from cnxysoft/upmain
fix: 群成员信息
2024-08-31 11:55:56 +08:00
Alen
e5c55b4339 fix: 群成员信息 2024-08-31 11:53:07 +08:00
Wesley F. Young
bee9095d6f release: 2.2.28 2024-08-31 10:35:33 +08:00
Wesley F. Young
92f8eaaac9 fix: get file by msgId and elemId 2024-08-31 10:34:19 +08:00
Wesley F. Young
f5e7288fe5 fix: report encoded msgId+elemId in upload event 2024-08-30 20:47:48 +08:00
Seijo Cecilia
214aa7b6e4 update(workflow): 'build' can only be triggered manually 2024-08-30 16:00:33 +08:00
Seijo Cecilia
5b5d5b41f5 build: snapshot-fix-get-file 2024-08-30 15:47:18 +08:00
Seijo Cecilia
23d613321e Revert "fix: arg3 no longer needed for downloadFileForModelId"
This reverts commit e1e4d038d9.
2024-08-30 15:41:50 +08:00
Wesley F. Young
0b6be0923f release: 2.2.27 2024-08-30 12:02:50 +08:00
Wesley F. Young
aba748ea13 Merge pull request #323 from LingLambda/main
fix: 规范 setSelfOnlineStatus 接口的参数命名
2024-08-30 11:55:22 +08:00
Wesley F. Young
f1f1ac582d chore: optimize imports 2024-08-30 11:45:58 +08:00
Wesley F. Young
54a7cbc3f4 feat: go-cqhttp style group file apis 2024-08-30 11:44:15 +08:00
ling
2f4dbaec4c fix: 规范setSelfOnlineStatus接口参数命名 2024-08-30 11:38:15 +08:00
Wesley F. Young
578f518aaf fix: others invited by others 2024-08-30 10:04:48 +08:00
Wesley F. Young
077ba74b22 fix: missing parameter for file searching 2024-08-30 09:31:18 +08:00
手瓜一十雪
e0efe635c7 release: 2.2.26 2024-08-29 22:47:24 +08:00
手瓜一十雪
1a06841de0 feat: notify.type == GroupNotifyMsgType.INVITED_NEED_ADMINI_STRATOR_PASS 2024-08-29 22:46:55 +08:00
手瓜一十雪
3987e0ee0b feat: 2.2.25 2024-08-29 22:38:48 +08:00
Wesley F. Young
9f53bea02f fix: duplicate type definition 2024-08-29 22:30:19 +08:00
Wesley F. Young
737709f9e7 Merge remote-tracking branch 'origin/main' 2024-08-29 22:27:57 +08:00
Wesley F. Young
39477aa6a0 fix: try to fix '搜索名字模式' of GetFile 2024-08-29 22:27:51 +08:00
手瓜一十雪
f097050b56 chore: 移除测试 2024-08-29 21:57:05 +08:00
手瓜一十雪
f14726ed1a fix: getfile 2024-08-29 21:55:44 +08:00
Wesley F. Young
e1e4d038d9 fix: arg3 no longer needed for downloadFileForModelId 2024-08-29 21:21:45 +08:00
Wesley F. Young
d2db4cf887 fix: 有笨蛋塞了 console.log 忘记删掉 2024-08-29 20:58:39 +08:00
手瓜一十雪
2f3ece9ca3 build: 2.2.25-test 2024-08-29 20:50:09 +08:00
手瓜一十雪
9f82007116 revert: eslint 2024-08-29 20:40:33 +08:00
手瓜一十雪
f79198a472 release: 2.2.24 2024-08-29 20:35:30 +08:00
手瓜一十雪
ce3d35d7ec fix: modelId 2024-08-29 20:34:24 +08:00
手瓜一十雪
f4d40f0466 release: 2.2.23 2024-08-29 20:14:40 +08:00
手瓜一十雪
a2fa085d5f fix: getfile 2024-08-29 20:14:20 +08:00
手瓜一十雪
a598266a6e fix: #320 2024-08-29 19:32:37 +08:00
手瓜一十雪
f5fe33cee7 fix: GroupEssenceMsg 2024-08-29 19:25:16 +08:00
手瓜一十雪
200c7226ef fix: getGroupEssenceMsgAll 2024-08-29 19:21:03 +08:00
Wesley F. Young
53475a6a0e fix: filter emoji un-like by operation 2024-08-29 18:15:52 +08:00
Wesley F. Young
b4ec1ad6c0 Merge remote-tracking branch 'origin/main' 2024-08-29 17:10:27 +08:00
Wesley F. Young
ef511a729d fix: solve export conflict 2024-08-29 17:10:14 +08:00
Wesley F. Young
275c4ce226 feat: GetGroupIgnoredNotifies 2024-08-29 17:08:36 +08:00
手瓜一十雪
45f9c029c8 Merge pull request #319 from NapNeko/dependabot/npm_and_yarn/eslint-9.9.1
build(deps-dev): bump eslint from 8.57.0 to 9.9.1
2024-08-29 17:01:34 +08:00
dependabot[bot]
db5e4ad5d9 build(deps-dev): bump eslint from 8.57.0 to 9.9.1
Bumps [eslint](https://github.com/eslint/eslint) from 8.57.0 to 9.9.1.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v8.57.0...v9.9.1)

---
updated-dependencies:
- dependency-name: eslint
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-29 08:54:04 +00:00
Wesley F. Young
f05d0a9727 chore: run eslint 2024-08-29 08:37:10 +08:00
Wesley F. Young
04593e9d9a feat: code style 2024-08-29 00:28:53 +08:00
手瓜一十雪
b1ecf13f8e release: 2.2.22 2024-08-29 00:10:52 +08:00
手瓜一十雪
e91e054f20 refactor: GetGroupEssence 2024-08-29 00:10:29 +08:00
手瓜一十雪
130ff7517e refactor: parseEssence 2024-08-29 00:02:24 +08:00
手瓜一十雪
c7042d9684 Merge branch 'main' of https://github.com/NapNeko/NapCatQQ 2024-08-29 00:01:48 +08:00
手瓜一十雪
5752e45dd1 refactor: GetFile 2024-08-28 23:45:33 +08:00
Alen
1a034ecb53 Merge pull request #317 from cnxysoft/upmain
fix: 转发消息报错
2024-08-28 23:30:04 +08:00
手瓜一十雪
025da8fb76 refactor: getFile 2024-08-28 23:27:29 +08:00
Alen
2027da1db5 chore: 优化代码 2024-08-28 23:24:44 +08:00
Alen
7732f28ca8 fix: reply消息转换 2024-08-28 22:51:54 +08:00
手瓜一十雪
7f9da8cc2d feat: support folder_id 2024-08-28 21:19:17 +08:00
手瓜一十雪
c6342b80a7 release: 2.2.21 2024-08-28 20:51:59 +08:00
Wesley F. Young
f99c82de4b fix: unexpected return in buddy req parsing 2024-08-28 19:32:07 +08:00
Wesley F. Young
56fa57ea02 refactor: rename createMsg -> createUniqueMsgId to prevent ambiguity 2024-08-28 19:03:19 +08:00
手瓜一十雪
cc85985d08 feat: 设置noify已读 2024-08-28 18:18:40 +08:00
手瓜一十雪
bd1751903e chore: clearGroupNotifiesUnreadCount 2024-08-28 18:01:32 +08:00
手瓜一十雪
03a298a70f release: 2.2.20 2024-08-28 17:48:51 +08:00
手瓜一十雪
2722ca2b0e feat: getGroupInfoEx 2024-08-28 17:05:00 +08:00
手瓜一十雪
179c4b800e Merge pull request #314 from NapNeko/dependabot/npm_and_yarn/typescript-eslint/parser-8.3.0
build(deps-dev): bump @typescript-eslint/parser from 7.18.0 to 8.3.0
2024-08-28 17:04:37 +08:00
dependabot[bot]
6bdf14223d build(deps-dev): bump @typescript-eslint/parser from 7.18.0 to 8.3.0
Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 7.18.0 to 8.3.0.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.3.0/packages/parser)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-28 08:58:48 +00:00
dependabot[bot]
1b8252aa4f Merge pull request #312 from NapNeko/dependabot/npm_and_yarn/typescript-eslint/eslint-plugin-8.3.0 2024-08-28 08:27:34 +00:00
dependabot[bot]
8219889154 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.3.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.3.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-28 08:26:00 +00:00
手瓜一十雪
df4ac5dcce release: 2.2.19 2024-08-28 16:19:30 +08:00
Seijo Cecilia
738eaf9de9 refactor: directory structure of core 2024-08-28 15:29:13 +08:00
Seijo Cecilia
c483ccbbbc fix: ob11 config file location 2024-08-28 15:27:38 +08:00
Seijo Cecilia
0d65f846ae refactor: remove helper directory in onebot 2024-08-28 15:09:17 +08:00
Seijo Cecilia
f47e75c423 refactor: make methods in event.ts instance methods 2024-08-28 15:02:44 +08:00
Wesley F. Young
c008e58fb8 Merge remote-tracking branch 'origin/main' 2024-08-28 13:05:15 +08:00
Wesley F. Young
26e0f17bc5 feat: emoji like event from other to other 2024-08-28 13:05:02 +08:00
Wesley F. Young
6543f28bdb feat: proto files of messages 2024-08-28 13:04:27 +08:00
手瓜一十雪
a86851b338 feat: 提高效率 2024-08-28 13:04:05 +08:00
Wesley F. Young
3a03e455c6 refactor: extract emoji like parsing logic 2024-08-28 10:50:39 +08:00
手瓜一十雪
3d39fd1580 Merge branch 'main' of https://github.com/NapNeko/NapCatQQ 2024-08-27 22:51:14 +08:00
手瓜一十雪
601b0add26 release: 2.2.18 2024-08-27 22:50:53 +08:00
Version
4f974cc913 chore:version change 2024-08-27 14:50:42 +00:00
手瓜一十雪
f691320453 fix: handleQuickOperation 2024-08-27 22:47:30 +08:00
手瓜一十雪
be39fc3a21 chore: 移除 2024-08-27 22:17:54 +08:00
手瓜一十雪
d2fafaf33a chore: rename nekodoge 2024-08-27 20:53:07 +08:00
手瓜一十雪
27ae331352 chore: new adapter 2024-08-27 20:37:23 +08:00
Seijo Cecilia
3f2dcfbacc (partially) fix: 'throw' of exception caught locally 2024-08-27 15:05:51 +08:00
Alen
8565aee8b6 Merge pull request #310 from cnxysoft/upmain
style: 规范代码
2024-08-27 14:11:33 +08:00
Alen
f983add599 style: 规范代码 2024-08-27 14:09:28 +08:00
Seijo Cecilia
030192afeb Merge remote-tracking branch 'origin/main' 2024-08-27 14:00:05 +08:00
Seijo Cecilia
c8b6a158f1 chore: optimize imports 2024-08-27 13:59:47 +08:00
Alen
e71f7849a7 Merge pull request #309 from cnxysoft/upmain
fix: 群员获取异常
2024-08-27 13:54:33 +08:00
Alen
b64d1ff4ff revert: 入群事件
因getGroupMembers切换到V2,恢复原本逻辑
2024-08-27 13:52:23 +08:00
Alen
5a0028be26 chore: 去除无用代码 2024-08-27 13:41:30 +08:00
Alen
926d7deb43 Merge branch 'main' into upmain 2024-08-27 13:39:38 +08:00
Seijo Cecilia
6384b50bae chore: run eslint 2024-08-27 13:35:25 +08:00
手瓜一十雪
9feb0f4b53 release: v2.2.16 2024-08-27 12:18:20 +08:00
手瓜一十雪
43ec1b7cfd feat: 更高效率的识别 2024-08-27 12:16:57 +08:00
Wesley F. Young
05b7a59f8d refactor: move version info into version.ts 2024-08-27 10:30:38 +08:00
Wesley F. Young
17e680f7af refactor: move all utility files to /common 2024-08-27 10:18:31 +08:00
Wesley F. Young
035d256d4e refactor: rename event-legacy -> event (currently in use) 2024-08-27 10:15:46 +08:00
Wesley F. Young
8939adf886 refactor: rename event -> event-v2 (not ready for use) 2024-08-27 10:14:55 +08:00
Wesley F. Young
027ffbffa6 chore: remove redundant eslint-disable 2024-08-27 10:05:23 +08:00
Alen
3cca06712b fix: 群成员拉取失败(实验性) 2024-08-27 02:43:27 +08:00
手瓜一十雪
2b9359dbf4 fix: Type
NodeIKernelSessionListener/onNTSessionCreate->NodeIKernelGroupListener/onGroupListInited
2024-08-26 21:33:33 +08:00
手瓜一十雪
c0f5d3bd2e release:2.2.15 2024-08-26 19:56:16 +08:00
手瓜一十雪
2a2d5382e1 Merge pull request #308 from LingLambda/main
规范了_send_group_notice接口参数命名,适当的规范了部分变量命名
2024-08-26 19:30:55 +08:00
ling
2e4986024c 规范了_send_group_notice接口参数命名,适当的规范了部分变量命名 2024-08-26 18:53:33 +08:00
手瓜一十雪
8a9c605dae release: 2.2.15 2024-08-26 18:02:21 +08:00
手瓜一十雪
44f51a93c8 release: 2.2.13 2024-08-26 17:46:17 +08:00
手瓜一十雪
66c8537b41 feat: support->27391 2024-08-26 17:44:04 +08:00
手瓜一十雪
86ae6dd332 fix: music sign 2024-08-26 17:23:39 +08:00
Seijo Cecilia
69380c9c73 Merge remote-tracking branch 'origin/main' 2024-08-26 16:27:43 +08:00
Seijo Cecilia
3d3759137c fix: remove redundant uid in GetStrangerInfo 2024-08-26 16:27:29 +08:00
手瓜一十雪
9b9b8f6f6f fix: type 2024-08-26 16:26:10 +08:00
手瓜一十雪
8ff87a8245 feat: getGroupExtFE0Info 2024-08-26 16:12:29 +08:00
Seijo Cecilia
d1896da171 update: promise executor functions should not be async 2024-08-26 15:40:27 +08:00
Seijo Cecilia
0bc4f6fd96 refactor: enhanced type definition for other methods 2024-08-26 15:37:17 +08:00
Seijo Cecilia
b16a429686 chore: reformat code style 2024-08-26 14:58:04 +08:00
Seijo Cecilia
fa1d266696 Merge remote-tracking branch 'origin/main' 2024-08-26 14:36:41 +08:00
Seijo Cecilia
d5dd2e9551 update: optimize entity factory (formerly converter.ts) 2024-08-26 14:36:10 +08:00
手瓜一十雪
be57c312c4 Merge branch 'main' of https://github.com/NapNeko/NapCatQQ 2024-08-26 14:30:29 +08:00
手瓜一十雪
f180687ba3 refactor: action param handle 2024-08-26 14:30:11 +08:00
Seijo Cecilia
3f3d9cc6f1 refactor: move calls & clean code 2024-08-26 14:22:08 +08:00
Seijo Cecilia
4f98c0d045 refactor: make quick action apis instance methods 2024-08-26 14:14:49 +08:00
Seijo Cecilia
c254441d40 Merge remote-tracking branch 'origin/main' 2024-08-26 14:01:21 +08:00
手瓜一十雪
17cbe74fa3 Merge branch 'main' of https://github.com/NapNeko/NapCatQQ 2024-08-26 14:01:06 +08:00
Seijo Cecilia
7aa0bd9b79 refactor: move all callNormalEvent calls to V2 substitutes 2024-08-26 14:00:48 +08:00
手瓜一十雪
2553cf6b72 style: lint 2024-08-26 13:58:28 +08:00
linyuchen
fe9050aeda Update README.md 2024-08-26 13:52:00 +08:00
Wesley F. Young
7092894d22 refactor: normalize file names in common/utils 2024-08-26 12:48:45 +08:00
Wesley F. Young
af6ac26664 chore: lint & style 2024-08-26 12:44:44 +08:00
Wesley F. Young
a22ef67486 fix: type definition for json data 2024-08-26 12:39:32 +08:00
Wesley F. Young
7bb57cd78a fix: use allSettled instead of all when parsing raw message 2024-08-26 12:38:39 +08:00
Wesley F. Young
89b69bbdf8 Merge remote-tracking branch 'origin/main' 2024-08-26 12:02:47 +08:00
Wesley F. Young
e21c779d06 refactor: enhanced type definition for callNormalEvent
类型体操,伟大,无需多言!
2024-08-26 12:02:41 +08:00
手瓜一十雪
dfa3553b71 chore: 谁打的不规范日志 2024-08-26 11:35:18 +08:00
手瓜一十雪
19097388d0 chore: onRecvSysMsg 2024-08-26 11:33:17 +08:00
Wesley F. Young
a71eddbed2 fix: reference error 2024-08-26 11:25:37 +08:00
手瓜一十雪
65bbed0c26 Merge pull request #304 from intling-luo/main
修改群公告的参数名 feed_id 为 notice_id
2024-08-26 11:11:32 +08:00
ling
871cc61dfc 修改群公告的参数名 feed_id 为 notice_id 2024-08-26 10:37:11 +08:00
Wesley F. Young
bc62feb71b refactor: normalize naming 2024-08-26 10:04:31 +08:00
Wesley F. Young
0bba329999 refactor: make sendMsg(WithOb11UniqueId) an instance method 2024-08-26 09:34:51 +08:00
Wesley F. Young
b1a1fdbeee refactor: rename all coreContext -> core 2024-08-26 09:19:50 +08:00
Wesley F. Young
542c5beb1b chore: suppress type check 2024-08-26 09:09:33 +08:00
手瓜一十雪
7b87b0919b release: 2.2.12 2024-08-26 02:57:08 +08:00
手瓜一十雪
9c34f558d3 release: 2.2.11 2024-08-26 02:16:24 +08:00
手瓜一十雪
3e2da3b490 release: v2.2.10 2024-08-26 01:58:41 +08:00
手瓜一十雪
fb4a4f50be release: v2.2.9 2024-08-26 01:52:09 +08:00
手瓜一十雪
6596e9cab6 style: 27333 类型跟进 2024-08-26 01:24:22 +08:00
手瓜一十雪
f1b137f2e1 style: 移除老旧代码 2024-08-26 01:07:01 +08:00
手瓜一十雪
535720d0fe style: lint 2024-08-26 01:00:07 +08:00
手瓜一十雪
f063cf4a16 style: 移除无用代码 2024-08-26 00:50:17 +08:00
手瓜一十雪
90bbdbf2fe style: fix 2024-08-26 00:46:58 +08:00
手瓜一十雪
5f1d8fb99d style: RegExec match->exec 2024-08-26 00:45:12 +08:00
手瓜一十雪
5486ffcdcc chore: 移除无用代码 2024-08-26 00:38:19 +08:00
手瓜一十雪
adfd123970 style: lint 2024-08-26 00:29:52 +08:00
手瓜一十雪
f1a364bfa2 style: lint 2024-08-26 00:21:49 +08:00
手瓜一十雪
9da714bf15 style: fix 2024-08-26 00:17:42 +08:00
手瓜一十雪
fc73295520 build: 2.2.9 2024-08-26 00:09:07 +08:00
手瓜一十雪
ab955e41fb style: 代码质量提高 2024-08-26 00:05:33 +08:00
手瓜一十雪
c64367335c Merge branch 'main' of https://github.com/NapNeko/NapCatQQ 2024-08-26 00:01:47 +08:00
手瓜一十雪
edc787eb3e style: lint 2024-08-26 00:01:44 +08:00
手瓜一十雪
f2c69fc68b style: remove 2024-08-25 23:59:34 +08:00
手瓜一十雪
d947fe743b Merge pull request #303 from NapNeko/dev/RefactoredMsgParsers
style: Error Object
2024-08-25 23:57:58 +08:00
手瓜一十雪
5b37ae9026 fix 2024-08-25 23:57:16 +08:00
手瓜一十雪
ec9e042b29 Merge pull request #301 from NapNeko/dev/RefactoredMsgParsers
refactor: make static functions dynamic
2024-08-25 23:50:44 +08:00
手瓜一十雪
337ac0eab9 fix: 过滤无效null类型 2024-08-25 23:50:34 +08:00
手瓜一十雪
f6a1b784c4 fix: type 2024-08-25 23:42:48 +08:00
手瓜一十雪
332fcecb78 Merge branch 'main' into dev/RefactoredMsgParsers 2024-08-25 23:16:49 +08:00
手瓜一十雪
18590be1e7 fix: 修正正确类型 2024-08-25 23:04:34 +08:00
手瓜一十雪
b76edcaf1d chore: merge main 2024-08-25 22:43:29 +08:00
手瓜一十雪
6024cabb69 Merge branch 'main' of https://github.com/NapNeko/NapCatQQ 2024-08-25 22:36:53 +08:00
手瓜一十雪
08446e648e release: 2.2.8 2024-08-25 22:36:42 +08:00
Alen
14af7a3572 Merge pull request #302 from cnxysoft/upmain
fix: 多个问题
2024-08-25 22:23:58 +08:00
Alen
cdc4275f81 fix: 多个问题
修复group_increase事件上报
修复启动时加载群员信息失败
修复文件发送失败
2024-08-25 22:23:06 +08:00
手瓜一十雪
a9ade98315 style: remove unless 2024-08-25 22:12:38 +08:00
手瓜一十雪
f3ae6fa70f style: fix 2024-08-25 22:09:30 +08:00
手瓜一十雪
96457bbec3 Merge branch 'main' into dev/RefactoredMsgParsers 2024-08-25 22:00:20 +08:00
手瓜一十雪
8f465e376e style: type 2024-08-25 21:59:07 +08:00
手瓜一十雪
adc366a959 style: 清理不规范代码 2024-08-25 21:54:20 +08:00
手瓜一十雪
a20a6bc8bb Merge branch 'main' into dev/RefactoredMsgParsers 2024-08-25 21:49:28 +08:00
手瓜一十雪
b176fa66d4 style: 样式处理 2024-08-25 21:47:55 +08:00
手瓜一十雪
f81b1926fb Merge branch 'main' into dev/RefactoredMsgParsers 2024-08-25 21:37:56 +08:00
手瓜一十雪
7b7609a068 style: 标准化样式 2024-08-25 21:37:36 +08:00
Seijo Cecilia
670d4108e6 fix: typo 2024-08-25 20:46:20 +08:00
Seijo Cecilia
a5c7b88a40 fix: createSendElements reference 2024-08-25 20:46:03 +08:00
Seijo Cecilia
e52b2e6d69 fix: createSendElements reference 2024-08-25 20:00:14 +08:00
Seijo Cecilia
e4066fb8df refactor: completely remove genMessage.ts 2024-08-25 19:53:20 +08:00
Seijo Cecilia
f7a0fb22b4 refactor: make SendMsg a single file again 2024-08-25 19:45:14 +08:00
Seijo Cecilia
cad2ae723c refactor: normalize method name 2024-08-25 19:43:41 +08:00
Seijo Cecilia
889a8c6093 fix: sync fixes in handling forwarded nodes 2024-08-25 19:43:01 +08:00
Seijo Cecilia
573418914f Merge branch 'main' into dev/RefactoredMsgParsers 2024-08-25 19:40:40 +08:00
Seijo Cecilia
d7fb6f9c05 refactor: ob11 to raw message constructors 2024-08-25 19:38:35 +08:00
手瓜一十雪
136e27d655 fix: 消息组合 2024-08-25 19:38:19 +08:00
Seijo Cecilia
d5ff2d7099 fix: from || to ?? 2024-08-25 18:20:58 +08:00
Seijo Cecilia
2a7f8d0c99 refactor: raw message parsers 2024-08-25 17:54:50 +08:00
手瓜一十雪
e3ca5df713 Delete .github/workflows/codacy.yml 2024-08-25 12:52:31 +08:00
手瓜一十雪
bda32f3e8f chore:update 2024-08-25 12:08:36 +08:00
手瓜一十雪
a7c6e45a92 chore: codacy 2024-08-25 12:02:03 +08:00
手瓜一十雪
7c20ca9b64 style:lint 2024-08-25 11:45:50 +08:00
手瓜一十雪
a201461eff chore: lint 2024-08-25 11:18:11 +08:00
Seijo Cecilia
e5d9df37c5 Merge remote-tracking branch 'origin/main' 2024-08-25 10:14:42 +08:00
Seijo Cecilia
106fbaf086 refactor: make parseMessage an instance method 2024-08-25 10:14:11 +08:00
Alen
a0024c98d5 release: 2.2.7 2024-08-25 09:59:07 +08:00
Alen
684a702638 Merge pull request #300 from cnxysoft/upmain
fix: 多处修复
2024-08-25 09:47:07 +08:00
Alen
aec4a009d1 fix: 撤回重复上报 2024-08-25 09:44:40 +08:00
Alen
822af575c9 fix: 群相关
group_admin事件上报
群成员信息/群列表缓存
ProfileService新增事件
2024-08-25 02:08:14 +08:00
Alen
485efa7d44 Merge branch 'main' into upmain 2024-08-25 01:25:12 +08:00
手瓜一十雪
3d09d45423 build: 2.2.7
DelGroupNotice
2024-08-24 23:50:05 +08:00
手瓜一十雪
4c69c6d9fd fix: v2.2.6 2024-08-24 23:24:24 +08:00
手瓜一十雪
920a41acef build: 2.2.6-test 2024-08-24 23:22:32 +08:00
Alen
0cf13a284c Merge branch 'main' into upmain 2024-08-24 22:26:56 +08:00
手瓜一十雪
a89cdef436 chore: kickMemberV2Inner 2024-08-24 22:23:44 +08:00
手瓜一十雪
881d88f4ad feat: quitGroupV2 2024-08-24 22:12:14 +08:00
Alen
a72c96f56d Merge branch 'main' into upmain 2024-08-24 21:54:43 +08:00
手瓜一十雪
bc8235b209 fix: 移除无用代码 2024-08-24 21:54:11 +08:00
手瓜一十雪
0087495749 fix: 类型推断 2024-08-24 21:50:29 +08:00
手瓜一十雪
9560afd4a7 fix: 一处错误推断 2024-08-24 21:47:39 +08:00
手瓜一十雪
56ec8559a0 fix: type 2024-08-24 21:42:49 +08:00
手瓜一十雪
99ca79ac7d chore: 去掉无用注释 2024-08-24 12:07:40 +08:00
手瓜一十雪
24564f4c74 release: 2.2.5 2024-08-24 12:05:07 +08:00
手瓜一十雪
212c802a1e fix: BuddyReq 2024-08-24 11:52:50 +08:00
手瓜一十雪
984b5d6c40 fix: 好友申请重复推送 2024-08-24 11:26:05 +08:00
手瓜一十雪
0e3a4191a9 Merge pull request #298 from shengwang52005/readme
docs: 增强胡言乱语水平
2024-08-24 01:03:21 +08:00
Miaowing
570a34bca5 docs: 增强胡言乱语水平
增强胡言乱语水平
2024-08-24 00:16:16 +08:00
手瓜一十雪
c9a0c29286 build: 2.2.0 2024-08-23 20:46:45 +08:00
手瓜一十雪
b5f804ec22 build: CQ码回滚 提高兼容 2024-08-23 20:46:23 +08:00
手瓜一十雪
dadbb83271 docs: 提高胡言乱语水平 2024-08-23 12:54:04 +08:00
手瓜一十雪
848aacdbbf releas: 2.2.4 2024-08-23 11:34:26 +08:00
手瓜一十雪
da3665a167 release: 2.2.0
release: 2.2.1

release: 2.2.2

chore

chore: 扩大范围

release: 2.2.3
2024-08-23 11:33:22 +08:00
手瓜一十雪
dcf0a06217 chore: 扩大范围 2024-08-23 11:20:29 +08:00
手瓜一十雪
3b5e6553cd chore 2024-08-23 11:04:47 +08:00
手瓜一十雪
509390af20 release: 2.2.2 2024-08-23 11:01:24 +08:00
手瓜一十雪
9ad511a9c0 release: 2.2.1 2024-08-23 10:57:13 +08:00
手瓜一十雪
89c102513d release: 2.2.0 2024-08-23 10:55:19 +08:00
手瓜一十雪
5e65ae76ad chore: 移除无用代码 2024-08-22 15:58:23 +08:00
手瓜一十雪
b6c364cd78 fix: 误操作 2024-08-22 15:56:01 +08:00
手瓜一十雪
e086b8707f refactor: chattype 2024-08-22 15:53:27 +08:00
手瓜一十雪
90dddd10a9 chore: 移除类型 2024-08-22 15:42:45 +08:00
手瓜一十雪
2dd0907565 fix: type 2024-08-22 15:42:07 +08:00
手瓜一十雪
f31b0d0c71 chore: 进一步拉高版本 2024-08-22 15:40:10 +08:00
手瓜一十雪
a0825b75f7 chore: parseMsg 重构 2024-08-22 15:28:54 +08:00
手瓜一十雪
a3bd4c0f73 chore: 推动重构 2024-08-22 15:06:49 +08:00
手瓜一十雪
a3e8c9b28a chore: 重构 2024-08-22 14:58:05 +08:00
手瓜一十雪
d7fb850b4a chore: 丢弃空消息 2024-08-22 14:35:48 +08:00
手瓜一十雪
d084778a6e chore: re 2024-08-22 14:34:09 +08:00
手瓜一十雪
8ca30de760 chore: 兼容性提高 2024-08-22 14:26:49 +08:00
手瓜一十雪
8a10b81bd9 chore: fix 2024-08-22 14:15:29 +08:00
手瓜一十雪
4a93c4e584 chore: 结构性调整 2024-08-22 14:13:52 +08:00
手瓜一十雪
50177cd6bd chore: style&&lint 2024-08-22 14:12:24 +08:00
手瓜一十雪
71a2e52739 chore: 解耦 2024-08-22 14:11:20 +08:00
手瓜一十雪
4fac6d5aa3 chore: 解耦 2024-08-22 14:05:01 +08:00
手瓜一十雪
37d061b602 chore: 进一步解耦 2024-08-22 13:53:07 +08:00
手瓜一十雪
40193e4edc chore: 解耦 2024-08-22 13:46:47 +08:00
手瓜一十雪
b4e9d61871 chore: 解耦代码 2024-08-22 13:39:53 +08:00
Wesley F. Young
d44b589e55 docs: update version range 2024-08-21 09:44:11 +08:00
手瓜一十雪
68216415b6 release: 2.1.0 2024-08-21 08:16:59 +08:00
手瓜一十雪
ba53da18d1 release: 2.1.0 2024-08-21 08:13:53 +08:00
手瓜一十雪
9b76fa3582 chore: LLNC Deprecated 2024-08-21 08:12:52 +08:00
手瓜一十雪
13d8d10a7f Merge pull request #289 from NapNeko/extend
Support: 9.9.15-27254
2024-08-21 08:09:37 +08:00
手瓜一十雪
5c6c1bb09d Merge branch 'main' into extend 2024-08-20 20:36:53 +08:00
手瓜一十雪
12105d96ea release: v2.0.37 2024-08-20 20:33:06 +08:00
手瓜一十雪
4054756035 Merge branch 'main' into extend 2024-08-20 20:27:22 +08:00
手瓜一十雪
16769c7838 fix: 提高兼容性 2024-08-20 20:26:49 +08:00
手瓜一十雪
cd076c5959 fix 2024-08-20 20:22:44 +08:00
手瓜一十雪
f52e1aa131 Merge branch 'main' into extend 2024-08-20 20:07:02 +08:00
手瓜一十雪
fdc1ef7e9a fix: error 2024-08-20 20:03:41 +08:00
手瓜一十雪
9cccf2d47b Merge branch 'main' of https://github.com/NapNeko/NapCatQQ 2024-08-20 20:03:20 +08:00
手瓜一十雪
0796f27f2a fix: file ext and blank data 2024-08-20 20:03:02 +08:00
手瓜一十雪
6c84014e0d doc: 2024-08-20 19:36:13 +08:00
手瓜一十雪
cd496a22bf chore: 调整appid 2024-08-20 17:47:14 +08:00
手瓜一十雪
0200343780 chore: 调整appid 2024-08-20 17:12:07 +08:00
手瓜一十雪
47fb629d26 Merge branch 'main' into extend 2024-08-20 16:59:17 +08:00
Alen
71ae08706b release: v2.0.35 2024-08-20 16:49:16 +08:00
Alen
50dd798757 Merge pull request #285 from cnxysoft/upmain
refactor: 接口兼容
2024-08-20 16:39:51 +08:00
Alen
0c8cf73746 refactor: 接口兼容
调整SetGroupHeader接口为SetGroupPortrait,使其兼容gocq标准
2024-08-20 16:35:23 +08:00
Alen
1bee811312 refactor: 接口兼容
调整SetSelfProfile接口为SetQQProfile,使其兼容gocq标准
2024-08-20 16:05:23 +08:00
Alen
b4c0068637 Merge pull request #284 from cnxysoft/upmain
fix: 多处修复
2024-08-20 14:31:02 +08:00
Alen
f484c6e5fe fix: 多处修复
1.修复group_card事件上报
2.修复group_admin事件上报
2024-08-20 14:28:32 +08:00
Alen
7a08187c5f fix: Uid转Uin
修复Uid转Uin兜底逻辑
2024-08-20 07:28:25 +08:00
Alen
c4d7d5a0d4 fix: 多处修改
1.修改(疑似)旧设备回复消息验证失败的解决方案
2.修复Base64发送文件失败
3.修复群时间监听器未注册
2024-08-20 01:02:03 +08:00
手瓜一十雪
5b75e753a7 Util 2024-08-19 21:38:31 +08:00
手瓜一十雪
326e9b86ce Merge branch 'main' into extend 2024-08-19 20:44:49 +08:00
手瓜一十雪
d22f5d369c releas: v2.0.34 2024-08-19 20:27:04 +08:00
手瓜一十雪
d76503995c fix: ws心跳实现 2024-08-19 20:24:26 +08:00
手瓜一十雪
eab930c083 doc: 小tip 2024-08-19 19:33:58 +08:00
手瓜一十雪
e430cc54f2 Merge branch 'main' into extend 2024-08-19 19:03:31 +08:00
手瓜一十雪
fd26a9c698 fix 2024-08-19 18:53:47 +08:00
手瓜一十雪
e79b608f77 support: 27206 2024-08-19 18:47:12 +08:00
Wesley F. Young
42b23a6c9c docs: clarify version range 2024-08-19 16:17:33 +08:00
手瓜一十雪
03098ee024 chore: util 2024-08-17 15:21:47 +08:00
手瓜一十雪
a2bfdd003c fix: getNTUserDataInfoConfig 2024-08-17 15:18:33 +08:00
246 changed files with 6124 additions and 6882 deletions

View File

@@ -17,5 +17,8 @@ charset = utf-8
indent_style = space
indent_size = 4
[*.bat]
charset = latin1
# Unfortunately, EditorConfig doesn't support space configuration inside import braces directly.
# You'll need to rely on your linter/formatter like ESLint or Prettier for that.

View File

@@ -4,7 +4,7 @@ module.exports = {
'es2021': true,
'node': true
},
'ignorePatterns': ['src/proto/'],
'ignorePatterns': ['src/core/proto/'],
'extends': [
'eslint:recommended',
'plugin:@typescript-eslint/recommended'

View File

@@ -10,6 +10,7 @@ body:
在提交新的 Bug 反馈前,请确保您:
* 已经搜索了现有的 issues并且没有找到可以解决您问题的方法
* 不与现有的某一 issue 重复
* 不涉及[已经停止维护的特性](https://github.com/NapNeko/NapCatQQ?tab=readme-ov-file#挥别昨日),例如 CQ 码
- type: input
id: system-version
attributes:
@@ -78,4 +79,4 @@ body:
attributes:
label: OneBot 客户端运行日志
description: 粘贴 OneBot 客户端的相关日志内容到此处
render: shell
render: shell

View File

@@ -1,15 +1,11 @@
name: "Build Action"
on:
workflow_dispatch:
push:
branches:
- main
permissions: write-all
jobs:
Build-LiteLoader:
if: ${{ startsWith(github.event.head_commit.message, 'build:') }}
runs-on: ubuntu-latest
steps:
- name: Clone Main Repository
@@ -37,7 +33,6 @@ jobs:
name: NapCat.Framework
path: dist
Build-Shell:
if: ${{ startsWith(github.event.head_commit.message, 'build:') }}
runs-on: ubuntu-latest
steps:
- name: Clone Main Repository
@@ -63,4 +58,4 @@ jobs:
uses: actions/upload-artifact@v4
with:
name: NapCat.Shell
path: dist
path: dist

View File

@@ -95,7 +95,13 @@ jobs:
steps:
- name: Download All Artifact
uses: actions/download-artifact@v4
# - name: Compress subdirectories
# run: |
# cd ./NapCat.Shell/
# zip -q -r NapCat.Shell.zip *
# cd ..
# rm ./NapCat.Shell.zip -rf
# mv ./NapCat.Shell/NapCat.Shell.zip ./
- name: Compress subdirectories
run: |
cd ./NapCat.Shell/

View File

@@ -3,35 +3,41 @@
</div>
---
## To Be Continued
当前版本请使用低于27187 (不包含) 高于26702 (包含) 的QQ版本运行
## 欢迎回来
NapCatQQ (aka 猫猫框架) 是现代化的基于 NTQQ 的 Bot 协议端实现。
## 项目介绍
NapCatQQ 是现代化的基于 NTQQ 的 Bot 协议端实现。
## 项目优势
## 猫猫技能
- [x] **高性能**1K+ 群聊数目、20 线程并行发送消息毫无压力
- [x] **多种启动方式**支持以无头、LiteLoader 插件、仅 QQ GUI 三种方式启动
- [x] **多平台支持**: 覆盖 Windows / Linux (可选 Docker) / Android Termux / MacOS
- [x] **安装简单**: 支持一键脚本/程序自动部署/镜像部署等多种覆盖范围
- [x] **低占用**:无头模式占用资源极低,适合在服务器上运行
- [x] **超多接口**:实现大部分 OneBot 和 go-cqhttp 接口,超多扩展 API
- [x] **WebUI**:自带 WebUI 支持,远程管理更加便捷
- [x] **低故障率**:快速适配最新版本,日常保证 0 Issue
## 如何使用
## 使用猫猫
可前往 [Release](https://github.com/NapNeko/NapCatQQ/releases/) 页面下载最新版本
**首次使用**请务必前往[官方文档](https://napneko.github.io/)查看使用教程。
## 相关链接
## 回家旅途
[QQ Group](https://qm.qq.com/q/VfjAq5HIMS)
[Telegram Link](https://t.me/+nLZEnpne-pQ1OWFl)
## 鸣谢名单
感谢 [LLOneBot](https://github.com/LLOneBot/LLOneBot) 提供初始版本基础
## 猫猫朋友
感谢 [LLOneBot](https://github.com/LLOneBot/LLOneBot) 提供部分参考
感谢 [Lagrange](https://github.com/LagrangeDev/Lagrange.Core) 对本项目的大力支持
不过最最重要的 还是需要感谢屏幕前的你哦~
---
## 使用许可
## 约法三章
> [!CAUTION]\
> **请不要在 QQ 官方群聊和任何影响力较大的简中互联网平台(包括但不限于: 哔哩哔哩,微博,知乎,抖音等)发布和讨论*任何*与本项目存在相关性的信息**
任何使用本仓库代码的地方,都应当严格遵守[本仓库开源许可](./LICENSE)。**此外,禁止任何项目未经授权二次分发或基于 [core](./src/core) 部分代码开发。**
任何使用本仓库代码的地方,都应当严格遵守[本仓库开源许可](./LICENSE)。**此外,禁止任何项目未经授权二次分发或基于 NapCat 代码开发。**

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,40 @@
@echo off
chcp 65001
net session >nul 2>&1
if %errorLevel% == 0 (
echo Administrator mode detected.
) else (
echo Please run this script in administrator mode.
powershell -Command "Start-Process 'cmd.exe' -ArgumentList '/c cd /d \"%cd%\" && \"%~f0\" %1' -Verb runAs"
exit
)
set NAPCAT_PATCH_PACKAGE=%cd%\qqnt.json
set NAPCAT_LOAD_PATH=%cd%\loadNapCat.js
set NAPCAT_INJECT_PATH=%cd%\NapCatWinBootHook.dll
set NAPCAT_LAUNCHER_PATH=%cd%\NapCatWinBootMain.exe
set NAPCAT_MAIN_PATH=%cd%\napcat.mjs
:loop_read
for /f "tokens=2*" %%a in ('reg query "HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\QQ" /v "UninstallString"') do (
set RetString=%%b
goto :napcat_boot
)
:napcat_boot
for %%a in ("%RetString%") do (
set "pathWithoutUninstall=%%~dpa"
)
SET QQPath=%pathWithoutUninstall%QQ.exe
if not exist "%QQpath%" (
echo provided QQ path is invalid: %QQpath%
pause
exit /b
)
set NAPCAT_MAIN_PATH=%NAPCAT_MAIN_PATH:\=/%
echo (async () =^> {await import("file:///%NAPCAT_MAIN_PATH%")})() > %NAPCAT_LOAD_PATH%
"%NAPCAT_LAUNCHER_PATH%" "%QQPath%" "%NAPCAT_INJECT_PATH%" %1
REM "%NAPCAT_LAUNCHER_PATH%" "%QQPath%" "%NAPCAT_INJECT_PATH%" 123456

39
launcher/launcher.bat Normal file
View File

@@ -0,0 +1,39 @@
@echo off
chcp 65001
net session >nul 2>&1
if %errorLevel% == 0 (
echo Administrator mode detected.
) else (
echo Please run this script in administrator mode.
powershell -Command "Start-Process 'wt.exe' -ArgumentList 'cmd /c cd /d \"%cd%\" && \"%~f0\" %1' -Verb runAs"
exit
)
set NAPCAT_PATCH_PACKAGE=%cd%\qqnt.json
set NAPCAT_LOAD_PATH=%cd%\loadNapCat.js
set NAPCAT_INJECT_PATH=%cd%\NapCatWinBootHook.dll
set NAPCAT_LAUNCHER_PATH=%cd%\NapCatWinBootMain.exe
set NAPCAT_MAIN_PATH=%cd%\napcat.mjs
:loop_read
for /f "tokens=2*" %%a in ('reg query "HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\QQ" /v "UninstallString"') do (
set RetString=%%b
goto :napcat_boot
)
:napcat_boot
for %%a in ("%RetString%") do (
set "pathWithoutUninstall=%%~dpa"
)
SET QQPath=%pathWithoutUninstall%QQ.exe
if not exist "%QQpath%" (
echo provided QQ path is invalid: %QQpath%
pause
exit /b
)
set NAPCAT_MAIN_PATH=%NAPCAT_MAIN_PATH:\=/%
echo (async () =^> {await import("file:///%NAPCAT_MAIN_PATH%")})() > %NAPCAT_LOAD_PATH%
"%NAPCAT_LAUNCHER_PATH%" "%QQPath%" "%NAPCAT_INJECT_PATH%" %1

5
launcher/loadNapCat.js Normal file
View File

@@ -0,0 +1,5 @@
const path = require('path');
const CurrentPath = path.dirname(__filename);
(async () => {
await import("file://" + path.join(CurrentPath, './napcat/napcat.mjs'));
})();

23
launcher/qqnt.json Normal file
View File

@@ -0,0 +1,23 @@
{
"name": "qq-chat",
"version": "9.9.15-28060",
"type": "module",
"private": true,
"description": "QQ",
"productName": "QQ",
"author": {
"name": "Tencent",
"email": "QQ-Team@tencent.com"
},
"homepage": "https://im.qq.com",
"sideEffects": true,
"bin": {
"qd": "externals/devtools/cli/index.js"
},
"main": "./loadNapCat.js",
"buildVersion": "28060",
"isPureShell": true,
"isByteCodeShell": true,
"platform": "win32",
"eleArch": "x64"
}

View File

@@ -0,0 +1,4 @@
@echo off
REM ./launcher.bat 123456
REM ./launcher-win10.bat 123456
REM 带有REM的为注释 删掉你需要的系统的那行REM这三个单词 修改QQ本脚本启动即可

View File

@@ -4,7 +4,7 @@
"name": "NapCatQQ",
"slug": "NapCat.Framework",
"description": "高性能的 OneBot 11 协议实现",
"version": "2.0.33",
"version": "2.6.6",
"icon": "./logo.png",
"authors": [
{

View File

@@ -2,7 +2,7 @@
"name": "napcat",
"private": true,
"type": "module",
"version": "2.0.33",
"version": "2.6.6",
"scripts": {
"build:framework": "vite build --mode framework",
"build:shell": "vite build --mode shell",
@@ -11,9 +11,6 @@
"depend": "cd dist && npm install --omit=dev"
},
"devDependencies": {
"@babel/core": "^7.24.7",
"@babel/plugin-proposal-class-properties": "^7.18.6",
"@babel/plugin-proposal-decorators": "^7.24.7",
"@babel/preset-typescript": "^7.24.7",
"@log4js-node/log4js-api": "^1.0.2",
"@protobuf-ts/plugin": "^2.9.4",
@@ -21,25 +18,17 @@
"@rollup/plugin-typescript": "^11.1.6",
"@types/cors": "^2.8.17",
"@types/express": "^4.17.21",
"@types/figlet": "^1.5.8",
"@types/fluent-ffmpeg": "^2.1.24",
"@types/jest": "^29.5.12",
"@types/node": "^22.0.1",
"@types/qrcode-terminal": "^0.12.2",
"@types/ws": "^8.5.12",
"@typescript-eslint/eslint-plugin": "^7.4.0",
"@typescript-eslint/parser": "^7.4.0",
"@typescript-eslint/eslint-plugin": "^8.3.0",
"@typescript-eslint/parser": "^8.3.0",
"eslint": "^8.57.0",
"eslint-import-resolver-typescript": "^3.6.1",
"eslint-plugin-import": "^2.29.1",
"i": "^0.3.7",
"javascript-obfuscator": "^4.1.0",
"rollup": "^4.13.2",
"rollup-plugin-dts": "^6.1.0",
"rollup-plugin-obfuscator": "^1.1.0",
"typescript": "^5.3.3",
"vite": "^5.2.6",
"vite-plugin-babel": "^1.2.0",
"vite-plugin-cp": "^4.0.8",
"vite-plugin-dts": "^3.8.2",
"vite-tsconfig-paths": "^4.3.2"
@@ -57,6 +46,7 @@
"image-size": "^1.1.1",
"json-schema-to-ts": "^3.1.0",
"log4js": "^6.9.1",
"protobufjs": "~7.4.0",
"qrcode-terminal": "^0.12.0",
"silk-wasm": "^3.6.1",
"strtok3": "8.0.1",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

2
script/KillQQ.bat Normal file
View File

@@ -0,0 +1,2 @@
@echo off
taskkill /f /im QQ.exe

Binary file not shown.

89
src/common/audio.ts Normal file
View File

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

View File

@@ -4,13 +4,13 @@ import type { NapCatCore } from '@/core';
export abstract class ConfigBase<T> {
name: string;
coreContext: NapCatCore;
core: NapCatCore;
configPath: string;
configData: T = {} as T;
protected constructor(name: string, coreContext: NapCatCore, configPath: string) {
protected constructor(name: string, core: NapCatCore, configPath: string) {
this.name = name;
this.coreContext = coreContext;
this.core = core;
this.configPath = configPath;
fs.mkdirSync(this.configPath, { recursive: true });
this.read();
@@ -22,14 +22,19 @@ export abstract class ConfigBase<T> {
}
getConfigPath(pathName: string | undefined): string {
const suffix = pathName ? `_${pathName}` : '';
const filename = `${this.name}${suffix}.json`;
return path.join(this.configPath, filename);
if (!pathName) {
const filename = `${this.name}.json`;
const mainPath = this.core.context.pathWrapper.binaryPath;
return path.join(mainPath, 'config', filename);
} else {
const filename = `${this.name}_${pathName}.json`;
return path.join(this.configPath, filename);
}
}
read(): T {
const logger = this.coreContext.context.logger;
const configPath = this.getConfigPath(this.coreContext.selfInfo.uin);
const logger = this.core.context.logger;
const configPath = this.getConfigPath(this.core.selfInfo.uin);
if (!fs.existsSync(configPath)) {
try {
fs.writeFileSync(configPath, fs.readFileSync(this.getConfigPath(undefined), 'utf-8'));
@@ -53,9 +58,9 @@ export abstract class ConfigBase<T> {
}
save(newConfigData: T = this.configData as T) {
const logger = this.coreContext.context.logger;
const selfInfo = this.coreContext.selfInfo;
save(newConfigData: T = this.configData) {
const logger = this.core.context.logger;
const selfInfo = this.core.selfInfo;
this.configData = newConfigData;
const configPath = this.getConfigPath(selfInfo.uin);
try {

View File

@@ -1,7 +1,8 @@
import { NodeIQQNTWrapperSession } from '@/core/wrapper/wrapper';
import { NodeIQQNTWrapperSession } from '@/core/wrapper';
import { randomUUID } from 'crypto';
import { ListenerNamingMapping, ServiceNamingMapping } from '@/core';
interface Internal_MapKey {
interface InternalMapKey {
timeout: number;
createtime: number;
func: (...arg: any[]) => any;
@@ -10,37 +11,27 @@ interface Internal_MapKey {
export type ListenerClassBase = Record<string, string>;
export interface ListenerIBase {
// eslint-disable-next-line @typescript-eslint/no-misused-new
new(listener: any): ListenerClassBase;
}
export class LegacyNTEventWrapper {
private listenerMapping: Record<string, ListenerIBase>; //ListenerName-Unique -> Listener构造函数
export class NTEventWrapper {
private WrapperSession: NodeIQQNTWrapperSession | undefined; //WrapperSession
private listenerManager: Map<string, ListenerClassBase> = new Map<string, ListenerClassBase>(); //ListenerName-Unique -> Listener实例
private EventTask = new Map<string, Map<string, Map<string, Internal_MapKey>>>(); //tasks ListenerMainName -> ListenerSubName-> uuid -> {timeout,createtime,func}
private EventTask = new Map<string, Map<string, Map<string, InternalMapKey>>>(); //tasks ListenerMainName -> ListenerSubName-> uuid -> {timeout,createtime,func}
constructor(
listenerMapping: Record<string, ListenerIBase>,
wrapperSession: NodeIQQNTWrapperSession,
) {
this.listenerMapping = listenerMapping;
this.WrapperSession = wrapperSession;
}
createProxyDispatch(ListenerMainName: string) {
// eslint-disable-next-line @typescript-eslint/no-this-alias
const current = this;
const dispatcherListenerFunc = this.dispatcherListener.bind(this);
return new Proxy(
{},
{
get(target: any, prop: any, receiver: any) {
// console.log('get', prop, typeof target[prop]);
if (typeof target[prop] === 'undefined') {
// 如果方法不存在返回一个函数这个函数调用existentMethod
return (...args: any[]) => {
current.dispatcherListener.apply(current, [ListenerMainName, prop, ...args]).then();
dispatcherListenerFunc(ListenerMainName, prop, ...args).then();
};
}
// 如果方法存在,正常返回
@@ -50,7 +41,13 @@ export class LegacyNTEventWrapper {
);
}
createEventFunction<T extends (...args: any) => any>(eventName: string): T | undefined {
createEventFunction<
Service extends keyof ServiceNamingMapping,
ServiceMethod extends Exclude<keyof ServiceNamingMapping[Service], symbol>,
// eslint-disable-next-line
// @ts-ignore
T extends (...args: any) => any = ServiceNamingMapping[Service][ServiceMethod],
>(eventName: `${Service}/${ServiceMethod}`): T | undefined {
const eventNameArr = eventName.split('/');
type eventType = {
[key: string]: () => { [key: string]: (...params: Parameters<T>) => Promise<ReturnType<T>> };
@@ -58,8 +55,6 @@ export class LegacyNTEventWrapper {
if (eventNameArr.length > 1) {
const serviceName = 'get' + eventNameArr[0].replace('NodeIKernel', '');
const eventName = eventNameArr[1];
//getNodeIKernelGroupListener,GroupService
//console.log('2', eventName);
const services = (this.WrapperSession as unknown as eventType)[serviceName]();
let event = services[eventName];
//重新绑定this
@@ -72,78 +67,78 @@ export class LegacyNTEventWrapper {
}
createListenerFunction<T>(listenerMainName: string, uniqueCode: string = ''): T {
const ListenerType = this.listenerMapping![listenerMainName];
let Listener = this.listenerManager.get(listenerMainName + uniqueCode);
if (!Listener && ListenerType) {
Listener = new ListenerType(this.createProxyDispatch(listenerMainName));
const ServiceSubName = listenerMainName.match(/^NodeIKernel(.*?)Listener$/)![1];
const Service = 'NodeIKernel' + ServiceSubName + 'Service/addKernel' + ServiceSubName + 'Listener';
const addfunc = this.createEventFunction<(listener: T) => number>(Service);
addfunc!(Listener as T);
//console.log(addfunc!(Listener as T));
const existListener = this.listenerManager.get(listenerMainName + uniqueCode);
if (!existListener) {
const Listener = this.createProxyDispatch(listenerMainName);
const ServiceSubName = /^NodeIKernel(.*?)Listener$/.exec(listenerMainName)![1];
const Service = `NodeIKernel${ServiceSubName}Service/addKernel${ServiceSubName}Listener`;
// eslint-disable-next-line
// @ts-ignore
this.createEventFunction(Service)(Listener as T);
this.listenerManager.set(listenerMainName + uniqueCode, Listener);
return Listener as T;
}
return Listener as T;
return existListener as T;
}
//统一回调清理事件
async dispatcherListener(ListenerMainName: string, ListenerSubName: string, ...args: any[]) {
//console.log("[EventDispatcher]",ListenerMainName, ListenerSubName, ...args);
this.EventTask.get(ListenerMainName)
?.get(ListenerSubName)
?.forEach((task, uuid) => {
//console.log(task.func, uuid, task.createtime, task.timeout);
if (task.createtime + task.timeout < Date.now()) {
this.EventTask.get(ListenerMainName)?.get(ListenerSubName)?.delete(uuid);
return;
}
if (task.checker && task.checker(...args)) {
if (task?.checker?.(...args)) {
task.func(...args);
}
});
}
async callNoListenerEvent<EventType extends (...args: any[]) => Promise<any> | any>(
EventName = '',
timeout: number = 3000,
async callNoListenerEvent<
Service extends keyof ServiceNamingMapping,
ServiceMethod extends Exclude<keyof ServiceNamingMapping[Service], symbol>,
// eslint-disable-next-line
// @ts-ignore
EventType extends (...args: any) => any = ServiceNamingMapping[Service][ServiceMethod],
>(
serviceAndMethod: `${Service}/${ServiceMethod}`,
...args: Parameters<EventType>
) {
return new Promise<Awaited<ReturnType<EventType>>>(async (resolve, reject) => {
const EventFunc = this.createEventFunction<EventType>(EventName);
let complete = false;
setTimeout(() => {
if (!complete) {
reject(new Error('NTEvent EventName:' + EventName + ' timeout'));
}
}, timeout);
const retData = await EventFunc!(...args);
complete = true;
resolve(retData);
});
): Promise<Awaited<ReturnType<EventType>>> {
return (this.createEventFunction(serviceAndMethod))!(...args);
}
async RegisterListen<ListenerType extends (...args: any[]) => void>(
ListenerName = '',
async registerListen<
Listener extends keyof ListenerNamingMapping,
ListenerMethod extends Exclude<keyof ListenerNamingMapping[Listener], symbol>,
// eslint-disable-next-line
// @ts-ignore
ListenerType extends (...args: any) => any = ListenerNamingMapping[Listener][ListenerMethod],
>(
listenerAndMethod: `${Listener}/${ListenerMethod}`,
waitTimes = 1,
timeout = 5000,
checker: (...args: Parameters<ListenerType>) => boolean,
) {
return new Promise<Parameters<ListenerType>>((resolve, reject) => {
const ListenerNameList = ListenerName.split('/');
const ListenerNameList = listenerAndMethod.split('/');
const ListenerMainName = ListenerNameList[0];
const ListenerSubName = ListenerNameList[1];
const id = randomUUID();
let complete = 0;
let retData: Parameters<ListenerType> | undefined = undefined;
const databack = () => {
function sendDataCallback() {
if (complete == 0) {
reject(new Error(' ListenerName:' + ListenerName + ' timeout'));
reject(new Error(' ListenerName:' + listenerAndMethod + ' timeout'));
} else {
resolve(retData!);
}
};
const timeoutRef = setTimeout(databack, timeout);
const eventCallbak = {
}
const timeoutRef = setTimeout(sendDataCallback, timeout);
const eventCallback = {
timeout: timeout,
createtime: Date.now(),
checker: checker,
@@ -152,7 +147,7 @@ export class LegacyNTEventWrapper {
retData = args;
if (complete >= waitTimes) {
clearTimeout(timeoutRef);
databack();
sendDataCallback();
}
},
};
@@ -162,36 +157,46 @@ export class LegacyNTEventWrapper {
if (!this.EventTask.get(ListenerMainName)?.get(ListenerSubName)) {
this.EventTask.get(ListenerMainName)?.set(ListenerSubName, new Map());
}
this.EventTask.get(ListenerMainName)?.get(ListenerSubName)?.set(id, eventCallbak);
this.EventTask.get(ListenerMainName)?.get(ListenerSubName)?.set(id, eventCallback);
this.createListenerFunction(ListenerMainName);
});
}
async CallNormalEvent<
EventType extends (...args: any[]) => Promise<any>,
ListenerType extends (...args: any[]) => void
async callNormalEventV2<
Service extends keyof ServiceNamingMapping,
ServiceMethod extends Exclude<keyof ServiceNamingMapping[Service], symbol>,
Listener extends keyof ListenerNamingMapping,
ListenerMethod extends Exclude<keyof ListenerNamingMapping[Listener], symbol>,
// eslint-disable-next-line
// @ts-ignore
EventType extends (...args: any) => any = ServiceNamingMapping[Service][ServiceMethod],
// eslint-disable-next-line
// @ts-ignore
ListenerType extends (...args: any) => any = ListenerNamingMapping[Listener][ListenerMethod]
>(
EventName = '',
ListenerName = '',
waitTimes = 1,
timeout: number = 3000,
checker: (...args: Parameters<ListenerType>) => boolean,
...args: Parameters<EventType>
serviceAndMethod: `${Service}/${ServiceMethod}`,
listenerAndMethod: `${Listener}/${ListenerMethod}`,
args: Parameters<EventType>,
checkerEvent: (ret: Awaited<ReturnType<EventType>>) => boolean = () => true,
checkerListener: (...args: Parameters<ListenerType>) => boolean = () => true,
callbackTimesToWait = 1,
timeout = 5000,
) {
return new Promise<[EventRet: Awaited<ReturnType<EventType>>, ...Parameters<ListenerType>]>(
async (resolve, reject) => {
(resolve, reject) => {
const id = randomUUID();
let complete = 0;
let retData: Parameters<ListenerType> | undefined = undefined;
let retEvent: any = {};
const databack = () => {
const retEvent: any = {};
function sendDataCallback() {
if (complete == 0) {
reject(
new Error(
'Timeout: NTEvent EventName:' +
EventName +
'Timeout: NTEvent serviceAndMethod:' +
serviceAndMethod +
' ListenerName:' +
ListenerName +
listenerAndMethod +
' EventRet:\n' +
JSON.stringify(retEvent, null, 4) +
'\n',
@@ -200,25 +205,24 @@ export class LegacyNTEventWrapper {
} else {
resolve([retEvent as Awaited<ReturnType<EventType>>, ...retData!]);
}
};
}
const ListenerNameList = ListenerName.split('/');
const ListenerNameList = listenerAndMethod.split('/');
const ListenerMainName = ListenerNameList[0];
const ListenerSubName = ListenerNameList[1];
const Timeouter = setTimeout(databack, timeout);
const timeoutRef = setTimeout(sendDataCallback, timeout);
const eventCallbak = {
const eventCallback = {
timeout: timeout,
createtime: Date.now(),
checker: checker,
checker: checkerListener,
func: (...args: any[]) => {
complete++;
//console.log('func', ...args);
retData = args as Parameters<ListenerType>;
if (complete >= waitTimes) {
clearTimeout(Timeouter);
databack();
if (complete >= callbackTimesToWait) {
clearTimeout(timeoutRef);
sendDataCallback();
}
},
};
@@ -228,36 +232,26 @@ export class LegacyNTEventWrapper {
if (!this.EventTask.get(ListenerMainName)?.get(ListenerSubName)) {
this.EventTask.get(ListenerMainName)?.set(ListenerSubName, new Map());
}
this.EventTask.get(ListenerMainName)?.get(ListenerSubName)?.set(id, eventCallbak);
this.EventTask.get(ListenerMainName)?.get(ListenerSubName)?.set(id, eventCallback);
this.createListenerFunction(ListenerMainName);
const EventFunc = this.createEventFunction<EventType>(EventName);
retEvent = await EventFunc!(...(args as any[]));
const eventFunction = this.createEventFunction(serviceAndMethod);
if (eventFunction) eventFunction(...(args)).then((retEvent: Awaited<ReturnType<EventType>>) => {
if (!checkerEvent(retEvent) && timeoutRef.hasRef()) {
clearTimeout(timeoutRef);
reject(
new Error(
'EventChecker Failed: NTEvent serviceAndMethod:' +
serviceAndMethod +
' ListenerName:' +
listenerAndMethod +
' EventRet:\n' +
JSON.stringify(retEvent, null, 4) +
'\n',
),
);
}
});
},
);
}
}
// 示例代码 快速创建事件
// let NTEvent = new NTEventWrapper();
// let TestEvent = NTEvent.CreatEventFunction<(force: boolean) => Promise<Number>>('NodeIKernelProfileLikeService/GetTest');
// if (TestEvent) {
// TestEvent(true);
// }
// 示例代码 快速创建监听Listener类
// let NTEvent = new NTEventWrapper();
// NTEvent.CreatListenerFunction<NodeIKernelMsgListener>('NodeIKernelMsgListener', 'core')
// 调用接口
//let NTEvent = new NTEventWrapper();
//let ret = await NTEvent.CallNormalEvent<(force: boolean) => Promise<Number>, (data1: string, data2: number) => void>('NodeIKernelProfileLikeService/GetTest', 'NodeIKernelMsgListener/onAddSendMsg', 1, 3000, true);
// 注册监听 解除监听
// NTEventDispatch.RigisterListener('NodeIKernelMsgListener/onAddSendMsg','core',cb);
// NTEventDispatch.UnRigisterListener('NodeIKernelMsgListener/onAddSendMsg','core');
// let GetTest = NTEventDispatch.CreatEvent('NodeIKernelProfileLikeService/GetTest','NodeIKernelMsgListener/onAddSendMsg',Mode);
// GetTest('test');
// always模式
// NTEventDispatch.CreatEvent('NodeIKernelProfileLikeService/GetTest','NodeIKernelMsgListener/onAddSendMsg',Mode,(...args:any[])=>{ console.log(args) });

View File

@@ -4,6 +4,7 @@ import crypto, { randomUUID } from 'crypto';
import util from 'util';
import path from 'node:path';
import * as fileType from 'file-type';
import { solveProblem } from './helper';
export function isGIF(path: string) {
const buffer = Buffer.alloc(4);
@@ -12,6 +13,7 @@ export function isGIF(path: string) {
fs.closeSync(fd);
return buffer.toString() === 'GIF8';
}
// 定义一个异步函数来检查文件是否存在
export function checkFileReceived(path: string, timeout: number = 3000): Promise<void> {
return new Promise((resolve, reject) => {
@@ -121,16 +123,15 @@ export interface HttpDownloadOptions {
headers?: Record<string, string> | string;
}
export async function httpDownload(options: string | HttpDownloadOptions): Promise<Buffer> {
const chunks: Buffer[] = [];
async function tryDownload(options: string | HttpDownloadOptions, useReferer: boolean = false): Promise<Response> {
// const chunks: Buffer[] = [];
let url: string;
let headers: Record<string, string> = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.71 Safari/537.36',
};
if (typeof options === 'string') {
url = options;
const host = new URL(url).hostname;
headers['Host'] = host;
headers['Host'] = new URL(url).hostname;
} else {
url = options.url;
if (options.headers) {
@@ -141,15 +142,26 @@ export async function httpDownload(options: string | HttpDownloadOptions): Promi
}
}
}
if (useReferer && !headers['Referer']) {
headers['Referer'] = url;
}
const fetchRes = await fetch(url, { headers }).catch((err) => {
if (err.cause) {
throw err.cause;
}
throw err;
});
if (!fetchRes.ok) throw new Error(`下载文件失败: ${fetchRes.statusText}`);
return fetchRes;
}
const blob = await fetchRes.blob();
export async function httpDownload(options: string | HttpDownloadOptions): Promise<Buffer> {
const useReferer = typeof options === 'string';
let resp = await tryDownload(options);
if (resp.status === 403 && useReferer) {
resp = await tryDownload(options, true);
}
if (!resp.ok) throw new Error(`下载文件失败: ${resp.statusText}`);
const blob = await resp.blob();
const buffer = await blob.arrayBuffer();
return Buffer.from(buffer);
}
@@ -159,8 +171,7 @@ type Uri2LocalRes = {
errMsg: string,
fileName: string,
ext: string,
path: string,
isLocal: boolean
path: string
}
export async function checkFileV2(filePath: string) {
@@ -185,68 +196,83 @@ export enum FileUriType {
}
export async function checkUriType(Uri: string) {
//先判断是否是本地文件
try {
if (fs.existsSync(Uri)) return { Uri: Uri, Type: FileUriType.Local };
} catch (error) {
}
try {
const LocalFileRet = await solveProblem((uri: string) => {
if (fs.existsSync(uri)) {
return { Uri: uri, Type: FileUriType.Local };
}
return undefined;
}, Uri);
if (LocalFileRet) return LocalFileRet;
const OtherFileRet = await solveProblem((uri: string) => {
//再判断是否是Http
if (Uri.startsWith('http://') || Uri.startsWith('https://')) {
return { Uri: Uri, Type: FileUriType.Remote };
if (uri.startsWith('http://') || uri.startsWith('https://')) {
return { Uri: uri, Type: FileUriType.Remote };
}
//再判断是否是Base64
if (Uri.startsWith('base64://')) {
return { Uri: Uri, Type: FileUriType.Base64 };
if (uri.startsWith('base64://')) {
return { Uri: uri, Type: FileUriType.Base64 };
}
if (Uri.startsWith('file://')) {
let pathname: string;
if (uri.startsWith('file://')) {
let filePath: string;
// await fs.copyFile(url.pathname, filePath);
pathname = decodeURIComponent(new URL(Uri).pathname);
const pathname = decodeURIComponent(new URL(uri).pathname);
if (process.platform === 'win32') {
filePath = pathname.slice(1);
} else {
filePath = pathname;
}
return { Uri: filePath, Type: FileUriType.Local };
}
} catch (error) {
}
if (uri.startsWith('data:')) {
const data = uri.split(',')[1];
if (data) return { Uri: data, Type: FileUriType.Base64 };
}
}, Uri);
if (OtherFileRet) return OtherFileRet;
return { Uri: Uri, Type: FileUriType.Unknown };
}
export async function uri2local(dir: string, uri: string, filename: string | undefined = undefined): Promise<Uri2LocalRes> {
const { Uri: HandledUri, Type: UriType } = await checkUriType(uri);
//解析失败
const tempName = randomUUID();
if (!filename) filename = randomUUID();
//解析Http和Https协议
if (UriType == FileUriType.Unknown) {
return { success: false, errMsg: '未知文件类型', fileName: '', ext: '', path: '', isLocal: false };
return { success: false, errMsg: '未知文件类型', fileName: '', ext: '', path: '' };
}
//解析File协议和本地文件
if (UriType == FileUriType.Local) {
const fileExt = path.extname(HandledUri);
const filename = path.basename(HandledUri, fileExt);
return { success: true, errMsg: '', fileName: filename, ext: fileExt, path: HandledUri, isLocal: true };
let filename = path.basename(HandledUri, fileExt);
filename += fileExt;
//复制文件到临时文件并保持后缀
const filenameTemp = tempName + fileExt;
const filePath = path.join(dir, filenameTemp);
fs.copyFileSync(HandledUri, filePath);
return { success: true, errMsg: '', fileName: filename, ext: fileExt, path: filePath };
}
//接下来都要有文件名
if (!filename) filename = randomUUID();
//解析Http和Https协议
if (UriType == FileUriType.Remote) {
const pathInfo = path.parse(decodeURIComponent(new URL(HandledUri).pathname));
if (pathInfo.name) {
filename = pathInfo.name;
if (pathInfo.ext) {
filename += pathInfo.ext;
}
const pathlen = 200 - dir.length - pathInfo.name.length;
filename = pathlen > 0 ? pathInfo.name.substring(0, pathlen) : pathInfo.name.substring(pathInfo.name.length, pathInfo.name.length - 10);//过长截断
if (pathInfo.ext) {
filename += pathInfo.ext;
}
}
filename = filename.replace(/[/\\:*?"<>|]/g, '_');
const fileExt = path.extname(HandledUri);
const filePath = path.join(dir, filename);
const fileExt = path.extname(HandledUri).replace(/[/\\:*?"<>|]/g, '_').substring(0, 10);
const filePath = path.join(dir, tempName + fileExt);
const buffer = await httpDownload(HandledUri);
fs.writeFileSync(filePath, buffer);
return { success: true, errMsg: '', fileName: filename, ext: fileExt, path: filePath, isLocal: true };
//没有文件就创建
fs.writeFileSync(filePath, buffer, { flag: 'wx' });
return { success: true, errMsg: '', fileName: filename, ext: fileExt, path: filePath };
}
//解析Base64
if (UriType == FileUriType.Base64) {
@@ -259,9 +285,9 @@ export async function uri2local(dir: string, uri: string, filename: string | und
if (success) {
filePath = fileTypePath;
fileExt = ext;
filename = path.basename(filePath, fileExt);
filename = filename + '.' + ext;
}
return { success: true, errMsg: '', fileName: filename, ext: fileExt, path: filePath, isLocal: true };
return { success: true, errMsg: '', fileName: filename, ext: fileExt, path: filePath };
}
return { success: false, errMsg: '未知文件类型', fileName: '', ext: '', path: '', isLocal: false };
}
return { success: false, errMsg: '未知文件类型', fileName: '', ext: '', path: '' };
}

View File

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

237
src/common/helper.ts Normal file
View File

@@ -0,0 +1,237 @@
import path from 'node:path';
import fs from 'fs';
import os from 'node:os';
import { Peer, QQLevel } from '@/core';
export async function solveProblem<T extends (...arg: any[]) => any>(func: T, ...args: Parameters<T>): Promise<ReturnType<T> | undefined> {
return new Promise<ReturnType<T> | undefined>((resolve) => {
try {
const result = func(...args);
resolve(result);
} catch (e) {
resolve(undefined);
}
});
}
export async function solveAsyncProblem<T extends (...args: any[]) => Promise<any>>(func: T, ...args: Parameters<T>): Promise<Awaited<ReturnType<T>> | undefined> {
return new Promise<Awaited<ReturnType<T>> | undefined>((resolve) => {
func(...args).then((result) => {
resolve(result);
}).catch(() => {
resolve(undefined);
});
});
}
export class FileNapCatOneBotUUID {
static encodeModelId(peer: Peer, modelId: string, fileId: string, endString: string = ""): string {
const data = `NapCatOneBot|ModelIdFile|${peer.chatType}|${peer.peerUid}|${modelId}|${fileId}`;
//前四个字节塞data长度
const length = Buffer.alloc(4 + data.length);
length.writeUInt32BE(data.length * 2, 0);//储存data的hex长度
length.write(data, 4);
return length.toString('hex') + endString;
}
static decodeModelId(uuid: string): undefined | {
peer: Peer,
modelId: string,
fileId: string
} {
//前四个字节是data长度
const length = Buffer.from(uuid.slice(0, 8), 'hex').readUInt32BE(0);
//根据length计算需要读取的长度
const dataId = uuid.slice(8, 8 + length);
//hex还原为string
const realData = Buffer.from(dataId, 'hex').toString();
if (!realData.startsWith('NapCatOneBot|ModelIdFile|')) return undefined;
const data = realData.split('|');
if (data.length !== 6) return undefined;
const [, , chatType, peerUid, modelId, fileId] = data;
return {
peer: {
chatType: chatType as any,
peerUid: peerUid,
},
modelId,
fileId
};
}
static encode(peer: Peer, msgId: string, elementId: string, endString: string = ""): string {
const data = `NapCatOneBot|MsgFile|${peer.chatType}|${peer.peerUid}|${msgId}|${elementId}`;
//前四个字节塞data长度
//一个字节8位 一个ascii字符1字节 一个hex字符4位 表示一个ascii字符需要两个hex字符
const length = Buffer.alloc(4 + data.length);
length.writeUInt32BE(data.length * 2, 0);
length.write(data, 4);
return length.toString('hex') + endString;
}
static decode(uuid: string): undefined | {
peer: Peer,
msgId: string,
elementId: string
} {
//前四个字节是data长度
const length = Buffer.from(uuid.slice(0, 8), 'hex').readUInt32BE(0);
//根据length计算需要读取的长度
const dataId = uuid.slice(8, 8 + length);
//hex还原为string
const realData = Buffer.from(dataId, 'hex').toString();
if (!realData.startsWith('NapCatOneBot|MsgFile|')) return undefined;
const data = realData.split('|');
if (data.length !== 6) return undefined;
const [, , chatType, peerUid, msgId, elementId] = data;
return {
peer: {
chatType: chatType as any,
peerUid: peerUid,
},
msgId,
elementId,
};
}
}
export function sleep(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}
export function PromiseTimer<T>(promise: Promise<T>, ms: number): Promise<T> {
const timeoutPromise = new Promise<T>((_, reject) =>
setTimeout(() => reject(new Error('PromiseTimer: Operation timed out')), ms),
);
return Promise.race([promise, timeoutPromise]);
}
export async function runAllWithTimeout<T>(tasks: Promise<T>[], timeout: number): Promise<T[]> {
const wrappedTasks = tasks.map((task) =>
PromiseTimer(task, timeout).then(
(result) => ({ status: 'fulfilled', value: result }),
(error) => ({ status: 'rejected', reason: error }),
),
);
const results = await Promise.all(wrappedTasks);
return results
.filter((result) => result.status === 'fulfilled')
.map((result) => (result as { status: 'fulfilled'; value: T }).value);
}
export function isNull(value: any) {
return value === undefined || value === null;
}
export function isNumeric(str: string) {
return /^\d+$/.test(str);
}
export function truncateString(obj: any, maxLength = 500) {
if (obj !== null && typeof obj === 'object') {
Object.keys(obj).forEach((key) => {
if (typeof obj[key] === 'string') {
// 如果是字符串且超过指定长度,则截断
if (obj[key].length > maxLength) {
obj[key] = obj[key].substring(0, maxLength) + '...';
}
} else if (typeof obj[key] === 'object') {
// 如果是对象或数组,则递归调用
truncateString(obj[key], maxLength);
}
});
}
return obj;
}
export function isEqual(obj1: any, obj2: any) {
if (obj1 === obj2) return true;
if (obj1 == null || obj2 == null) return false;
if (typeof obj1 !== 'object' || typeof obj2 !== 'object') return obj1 === obj2;
const keys1 = Object.keys(obj1);
const keys2 = Object.keys(obj2);
if (keys1.length !== keys2.length) return false;
for (const key of keys1) {
if (!isEqual(obj1[key], obj2[key])) return false;
}
return true;
}
export function getDefaultQQVersionConfigInfo(): QQVersionConfigType {
if (os.platform() === 'linux') {
return {
baseVersion: '3.2.12.28060',
curVersion: '3.2.12.28060',
prevVersion: '',
onErrorVersions: [],
buildId: '27254',
};
}
if (os.platform() === 'darwin') {
return {
baseVersion: '6.9.53.28060',
curVersion: '6.9.53.28060',
prevVersion: '',
onErrorVersions: [],
buildId: '28060',
};
}
return {
baseVersion: '9.9.15-28060',
curVersion: '9.9.15-28060',
prevVersion: '',
onErrorVersions: [],
buildId: '28060',
};
}
export function getQQPackageInfoPath(exePath: string = '', version?: string): string {
let packagePath;
if (os.platform() === 'darwin') {
packagePath = path.join(path.dirname(exePath), '..', 'Resources', 'app', 'package.json');
} else if (os.platform() === 'linux') {
packagePath = path.join(path.dirname(exePath), './resources/app/package.json');
} else {
packagePath = path.join(path.dirname(exePath), './versions/' + version + '/resources/app/package.json');
}
//下面是老版本兼容 未来去掉
if (!fs.existsSync(packagePath)) {
packagePath = path.join(path.dirname(exePath), './resources/app/versions/' + version + '/package.json');
}
return packagePath;
}
export function getQQVersionConfigPath(exePath: string = ''): string | undefined {
let configVersionInfoPath;
if (os.platform() === 'win32') {
configVersionInfoPath = path.join(path.dirname(exePath), 'versions', 'config.json');
} else if (os.platform() === 'darwin') {
const userPath = os.homedir();
const appDataPath = path.resolve(userPath, './Library/Application Support/QQ');
configVersionInfoPath = path.resolve(appDataPath, './versions/config.json');
} else {
const userPath = os.homedir();
const appDataPath = path.resolve(userPath, './.config/QQ');
configVersionInfoPath = path.resolve(appDataPath, './versions/config.json');
}
if (typeof configVersionInfoPath !== 'string') {
return undefined;
}
//老版本兼容 未来去掉
if (!fs.existsSync(configVersionInfoPath)) {
configVersionInfoPath = path.join(path.dirname(exePath), './resources/app/versions/config.json');
}
if (!fs.existsSync(configVersionInfoPath)) {
return undefined;
}
return configVersionInfoPath;
}
export function calcQQLevel(level?: QQLevel) {
if (!level) return 0;
const { crownNum, sunNum, moonNum, starNum } = level;
return crownNum * 64 + sunNum * 16 + moonNum * 4 + starNum;
}

View File

@@ -1,8 +1,8 @@
import log4js, { Configuration } from 'log4js';
import { truncateString } from '@/common/utils/helper';
import { truncateString } from '@/common/helper';
import path from 'node:path';
import chalk from 'chalk';
import { AtType, ChatType, ElementType, ElementWrapper, RawMessage, SelfInfo } from '@/core';
import { AtType, ChatType, ElementType, MessageElement, RawMessage, SelfInfo } from '@/core';
export enum LogLevel {
DEBUG = 'debug',
@@ -152,11 +152,11 @@ export function rawMessageToText(msg: RawMessage, recursiveLevel = 0): string {
const tokens: string[] = [];
if (msg.chatType == ChatType.friend) {
if (msg.chatType == ChatType.KCHATTYPEC2C) {
tokens.push(`私聊 (${msg.peerUin})`);
} else if (msg.chatType == ChatType.group) {
} else if (msg.chatType == ChatType.KCHATTYPEGROUP) {
tokens.push(`群聊 (群 ${msg.peerUin}${msg.senderUin})`);
} else if (msg.chatType == ChatType.chatDevice) {
} else if (msg.chatType == ChatType.KCHATTYPEDATALINE) {
tokens.push('移动设备');
} else /* temp */ {
tokens.push(`临时消息 (${msg.peerUin})`);
@@ -164,10 +164,11 @@ export function rawMessageToText(msg: RawMessage, recursiveLevel = 0): string {
// message content
function msgElementToText(element: ElementWrapper) {
function msgElementToText(element: MessageElement) {
if (element.textElement) {
if (element.textElement.atType === AtType.notAt) {
return element.textElement.content;
const originalContentLines = element.textElement.content.split('\n');
return `${originalContentLines[0]}${originalContentLines.length > 1 ? ' ...' : ''}`;
} else if (element.textElement.atType === AtType.atAll) {
return `@全体成员`;
} else if (element.textElement.atType === AtType.atUser) {
@@ -177,19 +178,19 @@ export function rawMessageToText(msg: RawMessage, recursiveLevel = 0): string {
if (element.replyElement) {
const recordMsgOrNull = msg.records.find(
record => element.replyElement!.sourceMsgIdInRecords === record.msgId
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}]`;
return '[图片]';
}
if (element.fileElement) {
@@ -197,7 +198,7 @@ export function rawMessageToText(msg: RawMessage, recursiveLevel = 0): string {
}
if (element.videoElement) {
return `[视频 ${element.videoElement.fileName}]`;
return '[视频]';
}
if (element.pttElement) {
@@ -205,7 +206,7 @@ export function rawMessageToText(msg: RawMessage, recursiveLevel = 0): string {
}
if (element.arkElement) {
return `[卡片消息 ${element.arkElement.bytesData}]`;
return '[卡片消息]';
}
if (element.faceElement) {
@@ -213,19 +214,19 @@ export function rawMessageToText(msg: RawMessage, recursiveLevel = 0): string {
}
if (element.marketFaceElement) {
return `[商城表情 ${element.marketFaceElement.faceName}]`;
return element.marketFaceElement.faceName;
}
if (element.markdownElement) {
return `[Markdown ${element.markdownElement.content}]`;
return '[Markdown 消息]';
}
if (element.multiForwardMsgElement) {
return `[转发消息]`;
return '[转发消息]';
}
if (element.elementType === ElementType.GreyTip) {
return `[灰条消息]`; // TODO: resolve the text
return '[灰条消息]';
}
return `[未实现 (ElementType = ${element.elementType})]`;

View File

@@ -15,23 +15,13 @@ export class LimitedHashTable<K, V> {
}
set(key: K, value: V): void {
// const isExist = this.keyToValue.get(key);
// if (isExist && isExist === value) {
// return;
// }
this.keyToValue.set(key, value);
this.valueToKey.set(value, key);
while (this.keyToValue.size !== this.valueToKey.size) {
//console.log('keyToValue.size !== valueToKey.size Error Atom');
this.keyToValue.clear();
this.valueToKey.clear();
}
// console.log('---------------');
// console.log(this.keyToValue);
// console.log(this.valueToKey);
// console.log('---------------');
while (this.keyToValue.size > this.maxSize || this.valueToKey.size > this.maxSize) {
//console.log(this.keyToValue.size > this.maxSize, this.valueToKey.size > this.maxSize);
const oldestKey = this.keyToValue.keys().next().value;
this.valueToKey.delete(this.keyToValue.get(oldestKey)!);
this.keyToValue.delete(oldestKey);
@@ -101,17 +91,13 @@ class MessageUniqueWrapper {
return ret.map((t) => t?.MsgId).filter((t) => t !== undefined);
}
createMsg(peer: Peer, msgId: string): number | undefined {
createUniqueMsgId(peer: Peer, msgId: string) {
const key = `${msgId}|${peer.chatType}|${peer.peerUid}`;
const hash = crypto.createHash('md5').update(key).digest();
//设置第一个bit为0 保证shortId为正数
hash[0] &= 0x7f;
const shortId = hash.readInt32BE(0);
//减少性能损耗
// const isExist = this.msgIdMap.getKey(shortId);
// if (isExist && isExist === msgId) {
// return shortId;
// }
this.msgIdMap.set(msgId, shortId);
this.msgDataMap.set(key, shortId);
return shortId;

View File

@@ -1,8 +1,7 @@
import path, { dirname } from 'path';
import { fileURLToPath } from 'url';
import fs from 'fs';
export const napcat_version = '2.0.33';
import os from 'os';
export class NapCatPathWrapper {
binaryPath: string;
@@ -13,17 +12,23 @@ export class NapCatPathWrapper {
constructor(mainPath: string = dirname(fileURLToPath(import.meta.url))) {
this.binaryPath = mainPath;
this.logsPath = path.join(this.binaryPath, 'logs');
this.configPath = path.join(this.binaryPath, 'config');
this.cachePath = path.join(this.binaryPath, 'cache');
let writePath: string;
if (os.platform() === 'darwin') {
writePath = path.join(os.homedir(), 'Library', 'Application Support', 'QQ', 'NapCat');
} else {
writePath = this.binaryPath;
}
this.logsPath = path.join(writePath, 'logs');
this.configPath = path.join(writePath, 'config');
this.cachePath = path.join(writePath, 'cache');
this.staticPath = path.join(this.binaryPath, 'static');
if (fs.existsSync(this.logsPath)) {
if (!fs.existsSync(this.logsPath)) {
fs.mkdirSync(this.logsPath, { recursive: true });
}
if (fs.existsSync(this.configPath)) {
if (!fs.existsSync(this.configPath)) {
fs.mkdirSync(this.configPath, { recursive: true });
}
if (fs.existsSync(this.cachePath)) {
if (!fs.existsSync(this.cachePath)) {
fs.mkdirSync(this.cachePath, { recursive: true });
}
}

View File

@@ -3,7 +3,6 @@ import { LogWrapper } from './log';
export function proxyHandlerOf(logger: LogWrapper) {
return {
get(target: any, prop: any, receiver: any) {
// console.log('get', prop, typeof target[prop]);
if (typeof target[prop] === 'undefined') {
// 如果方法不存在返回一个函数这个函数调用existentMethod
return (..._args: unknown[]) => {

View File

@@ -1,7 +1,6 @@
import path from 'node:path';
import fs from 'node:fs';
import { systemPlatform } from '@/common/utils/system';
import { getDefaultQQVersionConfigInfo, getQQVersionConfigPath } from './helper';
import { systemPlatform } from '@/common/system';
import { getDefaultQQVersionConfigInfo, getQQPackageInfoPath, getQQVersionConfigPath } from './helper';
import AppidTable from '@/core/external/appid.json';
import { LogWrapper } from './log';
@@ -20,14 +19,16 @@ export class QQBasicInfoWrapper {
//基础目录获取
this.context = context;
this.QQMainPath = process.execPath;
this.QQPackageInfoPath = path.join(path.dirname(this.QQMainPath), 'resources', 'app', 'package.json');
this.QQVersionConfigPath = getQQVersionConfigPath(this.QQMainPath);
//基础信息获取 无快更则启用默认模板填充
this.isQuickUpdate = !!this.QQVersionConfigPath;
this.QQVersionConfig = this.isQuickUpdate
? JSON.parse(fs.readFileSync(this.QQVersionConfigPath!).toString())
: getDefaultQQVersionConfigInfo();
this.QQPackageInfoPath = getQQPackageInfoPath(this.QQMainPath, this.QQVersionConfig?.curVersion);
this.QQPackageInfo = JSON.parse(fs.readFileSync(this.QQPackageInfoPath).toString());
const { appid: IQQVersionAppid, qua: IQQVersionQua } = this.getAppidV2();
this.QQVersionAppid = IQQVersionAppid;
@@ -46,36 +47,47 @@ export class QQBasicInfoWrapper {
}
requireMinNTQQBuild(buildStr: string) {
const currentBuild = parseInt(this.getQQBuildStr() || '0');
const currentBuild = +(this.getQQBuildStr() ?? '0');
if (currentBuild == 0) throw new Error('QQBuildStr获取失败');
return currentBuild >= parseInt(buildStr);
}
//此方法不要直接使用
getQUAInternal() {
return systemPlatform === 'linux'
? `V1_LNX_NQ_${this.getFullQQVesion()}_${this.getQQBuildStr()}_GW_B`
: `V1_WIN_NQ_${this.getFullQQVesion()}_${this.getQQBuildStr()}_GW_B`;
switch (systemPlatform) {
case 'linux':
return `V1_LNX_${this.getFullQQVesion()}_${this.getQQBuildStr()}_GW_B`;
case 'darwin':
return `V1_MAC_${this.getFullQQVesion()}_${this.getQQBuildStr()}_GW_B`;
default:
return `V1_WIN_${this.getFullQQVesion()}_${this.getQQBuildStr()}_GW_B`;
}
}
getAppidInternal() {
switch (systemPlatform) {
case 'linux':
return '537246140';
case 'darwin':
return '537246140';
default:
return '537246092';
}
}
getAppidV2(): { appid: string; qua: string } {
const appidTbale = AppidTable as unknown as QQAppidTableType;
try {
const fullVersion = this.getFullQQVesion();
if (!fullVersion) throw new Error('QQ版本获取失败');
const fullVersion = this.getFullQQVesion();
if (fullVersion) {
const data = appidTbale[fullVersion];
if (data) {
return data;
}
} catch (e) {
this.context.logger.log(`[QQ版本兼容性检测] 获取Appid异常 请检测NapCat/QQNT是否正常`);
}
// 以下是兜底措施
this.context.logger.log(
`[QQ版本兼容性检测] ${this.getFullQQVesion()} 版本兼容性不佳,可能会导致一些功能无法正常使用`,
);
return { appid: systemPlatform === 'linux' ? '537237950' : '537237765', qua: this.getQUAInternal() };
// else
this.context.logger.log(`[QQ版本兼容性检测] 获取Appid异常 请检测NapCat/QQNT是否正常`);
this.context.logger.log(`[QQ版本兼容性检测] ${fullVersion} 版本兼容性不佳,可能会导致一些功能无法正常使用`,);
return { appid: this.getAppidInternal(), qua: this.getQUAInternal() };
}
}
export let QQBasicInfo: QQBasicInfoWrapper | undefined;

View File

@@ -30,7 +30,7 @@ export async function getMachineId(): Promise<string> {
if (!machineId) {
machineId = (async () => {
const id = await getMacMachineId();
return id || randomUUID(); // fallback, generate a UUID
return id ?? randomUUID(); // fallback, generate a UUID
})();
}

View File

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

View File

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

File diff suppressed because one or more lines are too long

1
src/common/version.ts Normal file
View File

@@ -0,0 +1 @@
export const napCatVersion = '2.6.6';

63
src/common/video.ts Normal file

File diff suppressed because one or more lines are too long

View File

@@ -1,22 +1,8 @@
interface IDependsAdapter {
onMSFStatusChange(arg1: number, arg2: number): void;
import { MsfChangeReasonType, MsfStatusType } from "../entities/adapter";
onMSFSsoError(args: unknown): void;
export class NodeIDependsAdapter {
onMSFStatusChange(statusType: MsfStatusType, changeReasonType: MsfChangeReasonType) {
getGroupCode(args: unknown): void;
}
export interface NodeIDependsAdapter extends IDependsAdapter {
// eslint-disable-next-line @typescript-eslint/no-misused-new
new(adapter: IDependsAdapter): NodeIDependsAdapter;
}
export class DependsAdapter implements IDependsAdapter {
onMSFStatusChange(arg1: number, arg2: number) {
// console.log(arg1, arg2);
// if (arg1 == 2 && arg2 == 2) {
// log("NapCat丢失网络连接,请检查网络")
// }
}
onMSFSsoError(args: unknown) {

View File

@@ -1,17 +1,4 @@
interface IDispatcherAdapter {
dispatchRequest(arg: unknown): void;
dispatchCall(arg: unknown): void;
dispatchCallWithJson(arg: unknown): void;
}
export interface NodeIDispatcherAdapter extends IDispatcherAdapter {
// eslint-disable-next-line @typescript-eslint/no-misused-new
new(adapter: IDispatcherAdapter): NodeIDispatcherAdapter;
}
export class DispatcherAdapter implements IDispatcherAdapter {
export class NodeIDispatcherAdapter {
dispatchRequest(arg: unknown) {
}

View File

@@ -1,27 +1,4 @@
interface IGlobalAdapter {
onLog(...args: unknown[]): void;
onGetSrvCalTime(...args: unknown[]): void;
onShowErrUITips(...args: unknown[]): void;
fixPicImgType(...args: unknown[]): void;
getAppSetting(...args: unknown[]): void;
onInstallFinished(...args: unknown[]): void;
onUpdateGeneralFlag(...args: unknown[]): void;
onGetOfflineMsg(...args: unknown[]): void;
}
export interface NodeIGlobalAdapter extends IGlobalAdapter {
// eslint-disable-next-line @typescript-eslint/no-misused-new
new(adapter: IGlobalAdapter): NodeIGlobalAdapter;
}
export class GlobalAdapter implements IGlobalAdapter {
export class NodeIGlobalAdapter {
onLog(...args: unknown[]) {
}

View File

@@ -10,7 +10,7 @@ export class NTQQCollectionApi {
}
async createCollection(authorUin: string, authorUid: string, authorName: string, brief: string, rawData: string) {
const param = {
return this.context.session.getCollectionService().createNewCollectionItem({
commInfo: {
bid: 1,
category: 2,
@@ -43,12 +43,11 @@ export class NTQQCollectionApi {
fileList: [],
},
need_share_url: false,
};
return this.context.session.getCollectionService().createNewCollectionItem(param);
});
}
async getAllCollection(category: number = 0, count: number = 50) {
const param = {
return this.context.session.getCollectionService().getCollectionItemList({
category: category,
groupId: -1,
forceSync: true,
@@ -56,7 +55,6 @@ export class NTQQCollectionApi {
timeStamp: '0',
count: count,
searchDown: true,
};
return this.context.session.getCollectionService().getCollectionItemList(param);
});
}
}

View File

@@ -1,25 +1,30 @@
import {
CacheFileListItem,
CacheFileType,
ChatCacheListItemBasic,
ChatType,
ElementType,
IMAGE_HTTP_HOST,
IMAGE_HTTP_HOST_NT,
Peer,
PicElement,
PicType,
SendFileElement,
SendPicElement,
SendPttElement,
SendVideoElement,
} from '@/core/entities';
import path from 'path';
import fs from 'fs';
import fsPromises from 'fs/promises';
import { InstanceContext, NapCatCore, OnRichMediaDownloadCompleteParams } from '@/core';
import { InstanceContext, NapCatCore, SearchResultItem } from '@/core';
import * as fileType from 'file-type';
import imageSize from 'image-size';
import { ISizeCalculationResult } from 'image-size/dist/types/interface';
import { NodeIKernelSearchService } from '../services/NodeIKernelSearchService';
import { RkeyManager } from '../helper/rkey';
import { calculateFileMD5 } from '@/common/utils/file';
import { calculateFileMD5, isGIF } from '@/common/file';
import pathLib from 'node:path';
import { defaultVideoThumbB64, getVideoInfo } from '@/common/video';
import ffmpeg from 'fluent-ffmpeg';
import { encodeSilk } from '@/common/audio';
import { MessageContext } from '@/onebot/api';
export class NTQQFileApi {
context: InstanceContext;
@@ -29,11 +34,7 @@ export class NTQQFileApi {
constructor(context: InstanceContext, core: NapCatCore) {
this.context = context;
this.core = core;
this.rkeyManager = new RkeyManager('http://napcat-sign.wumiao.wang:2082/rkey', this.context.logger);
}
async getFileType(filePath: string) {
return fileType.fileTypeFromFile(filePath);
this.rkeyManager = new RkeyManager(['https://llob.linyuchen.net/rkey', 'http://napcat-sign.wumiao.wang:2082/rkey'], this.context.logger);
}
async copyFile(filePath: string, destPath: string) {
@@ -51,18 +52,15 @@ export class NTQQFileApi {
})).urlResult.domainUrl;
}
// 上传文件到QQ的文件夹
async uploadFile(filePath: string, elementType: ElementType = ElementType.PIC, elementSubType: number = 0) {
// napCatCore.wrapper.util.
const fileMd5 = await calculateFileMD5(filePath);
let ext: string = (await this.getFileType(filePath))?.ext as string || '';
if (ext) {
ext = '.' + ext;
}
const extOrEmpty = (await fileType.fileTypeFromFile(filePath))?.ext;
const ext = extOrEmpty ? `.${extOrEmpty}` : '';
let fileName = `${path.basename(filePath)}`;
if (fileName.indexOf('.') === -1) {
fileName += ext;
}
const mediaPath = this.context.session.getMsgService().getRichMediaFilePathForGuild({
md5HexStr: fileMd5,
fileName: fileName,
@@ -73,7 +71,8 @@ export class NTQQFileApi {
downloadType: 1,
file_uuid: '',
});
await this.copyFile(filePath, mediaPath!);
await this.copyFile(filePath, mediaPath);
const fileSize = await this.getFileSize(filePath);
return {
md5: fileMd5,
@@ -84,12 +83,190 @@ export class NTQQFileApi {
};
}
async downloadMediaByUuid() {
//napCatCore.session.getRichMediaService().downloadFileForFileUuid();
async createValidSendFileElement(context: MessageContext, filePath: string, fileName: string = '', folderId: string = '',): Promise<SendFileElement> {
const {
fileName: _fileName,
path,
fileSize,
} = await this.core.apis.FileApi.uploadFile(filePath, ElementType.FILE);
if (fileSize === 0) {
throw new Error('文件异常大小为0');
}
context.deleteAfterSentFiles.push(path);
return {
elementType: ElementType.FILE,
elementId: '',
fileElement: {
fileName: fileName || _fileName,
folderId: folderId,
filePath: path,
fileSize: (fileSize).toString(),
},
};
}
async createValidSendPicElement(context: MessageContext, picPath: string, summary: string = '', subType: 0 | 1 = 0,): Promise<SendPicElement> {
const { md5, fileName, path, fileSize } = await this.core.apis.FileApi.uploadFile(picPath, ElementType.PIC, subType);
if (fileSize === 0) {
throw new Error('文件异常大小为0');
}
const imageSize = await this.core.apis.FileApi.getImageSize(picPath);
context.deleteAfterSentFiles.push(path);
return {
elementType: ElementType.PIC,
elementId: '',
picElement: {
md5HexStr: md5,
fileSize: fileSize.toString(),
picWidth: imageSize.width,
picHeight: imageSize.height,
fileName: fileName,
sourcePath: path,
original: true,
picType: isGIF(picPath) ? PicType.gif : PicType.jpg,
picSubType: subType,
fileUuid: '',
fileSubId: '',
thumbFileSize: 0,
summary,
} as PicElement,
};
}
async createValidSendVideoElement(context: MessageContext, filePath: string, fileName: string = '', diyThumbPath: string = ''): Promise<SendVideoElement> {
const logger = this.core.context.logger;
let videoInfo = {
width: 1920, height: 1080,
time: 15,
format: 'mp4',
size: 0,
filePath,
};
try {
videoInfo = await getVideoInfo(filePath, logger);
} catch (e) {
logger.logError('获取视频信息失败,将使用默认值', e);
}
let fileExt = 'mp4';
try {
const tempExt = (await fileType.fileTypeFromFile(filePath))?.ext;
if (tempExt) fileExt = tempExt;
} catch (e) {
this.context.logger.logError('获取文件类型失败', e);
}
const newFilePath = filePath + '.' + fileExt;
fs.copyFileSync(filePath, newFilePath);
context.deleteAfterSentFiles.push(newFilePath);
filePath = newFilePath;
const { fileName: _fileName, path, fileSize, md5 } = await this.core.apis.FileApi.uploadFile(filePath, ElementType.VIDEO);
if (fileSize === 0) {
throw new Error('文件异常大小为0');
}
videoInfo.size = fileSize;
let thumb = path.replace(`${pathLib.sep}Ori${pathLib.sep}`, `${pathLib.sep}Thumb${pathLib.sep}`);
thumb = pathLib.dirname(thumb);
const thumbPath = new Map();
const _thumbPath = await new Promise<string | undefined>((resolve, reject) => {
const thumbFileName = `${md5}_0.png`;
const thumbPath = pathLib.join(thumb, thumbFileName);
ffmpeg(filePath)
.on('error', (err) => {
logger.logDebug('获取视频封面失败,使用默认封面', err);
if (diyThumbPath) {
fsPromises.copyFile(diyThumbPath, thumbPath).then(() => {
resolve(thumbPath);
}).catch(reject);
} else {
fs.writeFileSync(thumbPath, Buffer.from(defaultVideoThumbB64, 'base64'));
resolve(thumbPath);
}
})
.screenshots({
timestamps: [0],
filename: thumbFileName,
folder: thumb,
size: videoInfo.width + 'x' + videoInfo.height,
})
.on('end', () => {
resolve(thumbPath);
});
});
const thumbSize = _thumbPath ? (await fsPromises.stat(_thumbPath)).size : 0;
thumbPath.set(0, _thumbPath);
const thumbMd5 = _thumbPath ? await calculateFileMD5(_thumbPath) : '';
context.deleteAfterSentFiles.push(path);
const uploadName = (fileName || _fileName).toLocaleLowerCase().endsWith('.' + fileExt.toLocaleLowerCase()) ? (fileName || _fileName) : (fileName || _fileName) + '.' + fileExt;
return {
elementType: ElementType.VIDEO,
elementId: '',
videoElement: {
fileName: uploadName,
filePath: path,
videoMd5: md5,
thumbMd5,
fileTime: videoInfo.time,
thumbPath: thumbPath,
thumbSize,
thumbWidth: videoInfo.width,
thumbHeight: videoInfo.height,
fileSize: '' + fileSize,
},
};
}
async createValidSendPttElement(pttPath: string): Promise<SendPttElement> {
const { converted, path: silkPath, duration } = await encodeSilk(pttPath, this.core.NapCatTempPath, this.core.context.logger);
if (!silkPath) {
throw new Error('语音转换失败, 请检查语音文件是否正常');
}
const { md5, fileName, path, fileSize } = await this.core.apis.FileApi.uploadFile(silkPath, ElementType.PTT);
if (fileSize === 0) {
throw new Error('文件异常大小为0');
}
if (converted) {
fsPromises.unlink(silkPath).then().catch(
(e) => this.context.logger.logError('删除临时文件失败', e)
);
}
return {
elementType: ElementType.PTT,
elementId: '',
pttElement: {
fileName: fileName,
filePath: path,
md5HexStr: md5,
fileSize: fileSize,
duration: duration ?? 1,
formatType: 1,
voiceType: 1,
voiceChangeType: 0,
canConvert2Text: true,
waveAmplitudes: [
0, 18, 9, 23, 16, 17, 16, 15, 44, 17, 24, 20, 14, 15, 17,
],
fileSubId: '',
playState: 1,
autoConvertText: 0,
},
};
}
async downloadFileForModelId(peer: Peer, modelId: string, unknown: string, timeout = 1000 * 60 * 2) {
const [, fileTransNotifyInfo] = await this.core.eventWrapper.callNormalEventV2(
'NodeIKernelRichMediaService/downloadFileForModelId',
'NodeIKernelMsgListener/onRichMediaDownloadComplete',
[peer, [modelId], unknown],
() => true,
(arg) => arg?.commonFileInfo?.fileModelId === modelId,
1,
timeout,
);
return fileTransNotifyInfo.filePath;
}
async downloadMedia(msgId: string, chatType: ChatType, peerUid: string, elementId: string, thumbPath: string, sourcePath: string, timeout = 1000 * 60 * 2, force: boolean = false) {
//logDebug('receive downloadMedia task', msgId, chatType, peerUid, elementId, thumbPath, sourcePath, timeout, force);
// 用于下载收到的消息中的图片等
if (sourcePath && fs.existsSync(sourcePath)) {
if (force) {
@@ -102,279 +279,110 @@ export class NTQQFileApi {
return sourcePath;
}
}
const data = await this.core.eventWrapper.CallNormalEvent<
(
params: {
fileModelId: string,
downloadSourceType: number,
triggerType: number,
msgId: string,
chatType: ChatType,
peerUid: string,
elementId: string,
thumbSize: number,
downloadType: number,
filePath: string
}) => Promise<unknown>,
(fileTransNotifyInfo: OnRichMediaDownloadCompleteParams) => void
>(
'NodeIKernelMsgService/downloadRichMedia',
'NodeIKernelMsgListener/onRichMediaDownloadComplete',
1,
timeout,
(arg: OnRichMediaDownloadCompleteParams) => {
if (arg.msgId === msgId) {
return true;
}
return false;
},
{
fileModelId: '0',
downloadSourceType: 0,
triggerType: 1,
msgId: msgId,
chatType: chatType,
peerUid: peerUid,
elementId: elementId,
thumbSize: 0,
downloadType: 1,
filePath: thumbPath,
},
);
const msg = await this.core.apis.MsgApi.getMsgsByMsgId({
guildId: '',
chatType: chatType,
peerUid: peerUid,
}, [msgId]);
if (msg.msgList.length === 0) {
return data[1].filePath;
}
//获取原始消息
const FileElements = msg?.msgList[0]?.elements?.find(e => e.elementId === elementId);
if (!FileElements) {
//失败则就乱来 Todo
return data[1].filePath;
}
//从原始消息获取文件路径
const filePath =
FileElements?.fileElement?.filePath ||
FileElements?.pttElement?.filePath ||
FileElements?.videoElement?.filePath ||
FileElements?.picElement?.sourcePath;
return filePath;
const [, completeRetData] = await this.core.eventWrapper.callNormalEventV2(
'NodeIKernelMsgService/downloadRichMedia',
'NodeIKernelMsgListener/onRichMediaDownloadComplete',
[{
fileModelId: '0',
downloadSourceType: 0,
triggerType: 1,
msgId: msgId,
chatType: chatType,
peerUid: peerUid,
elementId: elementId,
thumbSize: 0,
downloadType: 1,
filePath: thumbPath,
}],
() => true,
(arg) => arg.msgId === msgId,
1,
timeout,
);
return completeRetData.filePath;
}
async getImageSize(filePath: string): Promise<ISizeCalculationResult | undefined> {
async getImageSize(filePath: string): Promise<ISizeCalculationResult> {
return new Promise((resolve, reject) => {
imageSize(filePath, (err, dimensions) => {
if (err) {
reject(err);
} else {
resolve(dimensions);
if (!dimensions) {
reject(new Error('获取图片尺寸失败'));
} else {
resolve(dimensions);
}
}
});
});
}
async addFileCache(peer: Peer, msgId: string, msgSeq: string, senderUid: string, elemId: string, elemType: string, fileSize: string, fileName: string) {
let GroupData;
let BuddyData;
if (peer.chatType === ChatType.group) {
GroupData =
[{
groupCode: peer.peerUid,
isConf: false,
hasModifyConfGroupFace: true,
hasModifyConfGroupName: true,
groupName: 'NapCat.Cached',
remark: 'NapCat.Cached',
}];
} else if (peer.chatType === ChatType.friend) {
BuddyData = [{
category_name: 'NapCat.Cached',
peerUid: peer.peerUid,
peerUin: peer.peerUid,
remark: 'NapCat.Cached',
}];
} else {
return undefined;
}
return this.context.session.getSearchService().addSearchHistory({
type: 4,
contactList: [],
id: -1,
groupInfos: [],
msgs: [],
fileInfos: [
{
chatType: peer.chatType,
buddyChatInfo: BuddyData || [],
discussChatInfo: [],
groupChatInfo: GroupData || [],
dataLineChatInfo: [],
tmpChatInfo: [],
msgId: msgId,
msgSeq: msgSeq,
msgTime: Math.floor(Date.now() / 1000).toString(),
senderUid: senderUid,
senderNick: 'NapCat.Cached',
senderRemark: 'NapCat.Cached',
senderCard: 'NapCat.Cached',
elemId: elemId,
elemType: elemType,
fileSize: fileSize,
filePath: '',
fileName: fileName,
hits: [{
start: 12,
end: 14,
}],
},
async searchForFile(keys: string[]): Promise<SearchResultItem | undefined> {
const randomResultId = 100000 + Math.floor(Math.random() * 10000);
let searchId = 0;
const [, searchResult] = await this.core.eventWrapper.callNormalEventV2(
'NodeIKernelFileAssistantService/searchFile',
'NodeIKernelFileAssistantListener/onFileSearch',
[
keys,
{ resultType: 2, pageLimit: 1 },
randomResultId,
],
});
(ret) => {
searchId = ret;
return true;
},
result => result.searchId === searchId && result.resultId === randomResultId,
);
return searchResult.resultItems[0];
}
async searchfile(keys: string[]) {
type EventType = NodeIKernelSearchService['searchFileWithKeywords'];
interface OnListener {
searchId: string,
hasMore: boolean,
resultItems: {
chatType: ChatType,
buddyChatInfo: any[],
discussChatInfo: any[],
groupChatInfo:
{
groupCode: string,
isConf: boolean,
hasModifyConfGroupFace: boolean,
hasModifyConfGroupName: boolean,
groupName: string,
remark: string
}[],
dataLineChatInfo: any[],
tmpChatInfo: any[],
msgId: string,
msgSeq: string,
msgTime: string,
senderUid: string,
senderNick: string,
senderRemark: string,
senderCard: string,
elemId: string,
elemType: number,
fileSize: string,
filePath: string,
fileName: string,
hits:
{
start: number,
end: number
}[]
}[]
}
const Event = this.core.eventWrapper.createEventFunction<EventType>('NodeIKernelSearchService/searchFileWithKeywords');
let id = '';
const Listener = this.core.eventWrapper.RegisterListen<(params: OnListener) => void>
(
'NodeIKernelSearchListener/onSearchFileKeywordsResult',
async downloadFileById(
fileId: string,
fileSize: number = 1024576,
estimatedTime: number = (fileSize * 1000 / 1024576) + 5000,
) {
const [, fileData] = await this.core.eventWrapper.callNormalEventV2(
'NodeIKernelFileAssistantService/downloadFile',
'NodeIKernelFileAssistantListener/onFileStatusChanged',
[[fileId]],
ret => ret.result === 0,
status => status.fileStatus === 2 && status.fileProgress === '0',
1,
20000,
(params) => id !== '' && params.searchId == id,
);
id = await Event!(keys, 12);
const [ret] = (await Listener);
return ret;
estimatedTime, // estimate 1MB/s
);
return fileData.filePath!;
}
async getImageUrl(element: PicElement) {
if (!element) {
return '';
}
const url: string = element.originImageUrl!; // 没有域名
const url: string = element.originImageUrl ?? '';
const md5HexStr = element.md5HexStr;
const fileMd5 = element.md5HexStr;
const fileUuid = element.fileUuid;
if (url) {
const UrlParse = new URL(IMAGE_HTTP_HOST + url);//临时解析拼接
const imageAppid = UrlParse.searchParams.get('appid');
const isNewPic = imageAppid && ['1406', '1407'].includes(imageAppid);
if (isNewPic) {
let UrlRkey = UrlParse.searchParams.get('rkey');
if (UrlRkey) {
const parsedUrl = new URL(IMAGE_HTTP_HOST + url);
const imageAppid = parsedUrl.searchParams.get('appid');
const isNTV2 = imageAppid && ['1406', '1407'].includes(imageAppid);
if (isNTV2) {
let rkey = parsedUrl.searchParams.get('rkey');
if (rkey) {
return IMAGE_HTTP_HOST_NT + url;
}
const rkeyData = await this.rkeyManager.getRkey();
UrlRkey = imageAppid === '1406' ? rkeyData.private_rkey : rkeyData.group_rkey;
return IMAGE_HTTP_HOST_NT + url + `${UrlRkey}`;
rkey = imageAppid === '1406' ? rkeyData.private_rkey : rkeyData.group_rkey;
return IMAGE_HTTP_HOST_NT + url + `${rkey}`;
} else {
// 老的图片url不需要rkey
return IMAGE_HTTP_HOST + url;
}
} else if (fileMd5 || md5HexStr) {
// 没有url需要自己拼接
return `${IMAGE_HTTP_HOST}/gchatpic_new/0/0-0-${(fileMd5 || md5HexStr)!.toUpperCase()}/0`;
return `${IMAGE_HTTP_HOST}/gchatpic_new/0/0-0-${(fileMd5 ?? md5HexStr)!.toUpperCase()}/0`;
}
this.context.logger.logDebug('图片url获取失败', element);
return '';
}
}
export class NTQQFileCacheApi {
context: InstanceContext;
core: NapCatCore;
constructor(context: InstanceContext, core: NapCatCore) {
this.context = context;
this.core = core;
}
async setCacheSilentScan(isSilent: boolean = true) {
return '';
}
getCacheSessionPathList() {
return '';
}
clearCache(cacheKeys: Array<string> = ['tmp', 'hotUpdate']) {
// 参数未验证
return this.context.session.getStorageCleanService().clearCacheDataByKeys(cacheKeys);
}
addCacheScannedPaths(pathMap: object = {}) {
return this.context.session.getStorageCleanService().addCacheScanedPaths(pathMap);
}
scanCache() {
//return (await this.context.session.getStorageCleanService().scanCache()).size;
}
getHotUpdateCachePath() {
// 未实现
return '';
}
getDesktopTmpPath() {
// 未实现
return '';
}
getChatCacheList(type: ChatType, pageSize: number = 1000, pageIndex: number = 0) {
return this.context.session.getStorageCleanService().getChatCacheInfo(type, pageSize, 1, pageIndex);
}
getFileCacheInfo(fileType: CacheFileType, pageSize: number = 1000, lastRecord?: CacheFileListItem) {
const _lastRecord = lastRecord ? lastRecord : { fileType: fileType };
//需要五个参数
//return napCatCore.session.getStorageCleanService().getFileCacheInfo();
}
async clearChatCache(chats: ChatCacheListItemBasic[] = [], fileKeys: string[] = []) {
return this.context.session.getStorageCleanService().clearChatCacheInfo(chats, fileKeys);
}
}

View File

@@ -1,61 +1,52 @@
import { Friend, FriendV2, User } from '@/core/entities';
import { BuddyListReqType, InstanceContext, NapCatCore, NodeIKernelProfileService, OnBuddyChangeParams } from '@/core';
import { LimitedHashTable } from '@/common/utils/MessageUnique';
import { FriendV2 } from '@/core/entities';
import { BuddyListReqType, InstanceContext, NapCatCore } from '@/core';
import { LimitedHashTable } from '@/common/message-unique';
export class NTQQFriendApi {
context: InstanceContext;
core: NapCatCore;
//friends: Map<string, Friend> = new Map<string, FriendV2>();
constructor(context: InstanceContext, core: NapCatCore) {
this.context = context;
this.core = core;
// if (!this.context.basicInfoWrapper.requireMinNTQQBuild('26702')) {
// this.getFriends(true);
// }
}
async getBuddyV2SimpleInfoMap(refresh = false) {
const buddyService = this.context.session.getBuddyService();
const buddyListV2 = refresh ? await buddyService.getBuddyListV2('0', BuddyListReqType.KNOMAL) : await buddyService.getBuddyListV2('0', BuddyListReqType.KNOMAL);
const uids = buddyListV2.data.flatMap(item => item.buddyUids);
return await this.core.eventWrapper.callNoListenerEvent(
'NodeIKernelProfileService/getCoreAndBaseInfo',
'nodeStore',
uids,
);
}
async getBuddyV2(refresh = false): Promise<FriendV2[]> {
const uids: string[] = [];
const buddyService = this.context.session.getBuddyService();
const buddyListV2 = refresh ? await buddyService.getBuddyListV2('0', BuddyListReqType.KNOMAL) : await buddyService.getBuddyListV2('0', BuddyListReqType.KNOMAL);
uids.push(...buddyListV2.data.flatMap(item => item.buddyUids));
const data = await this.core.eventWrapper.callNoListenerEvent<NodeIKernelProfileService['getCoreAndBaseInfo']>(
'NodeIKernelProfileService/getCoreAndBaseInfo', 5000, 'nodeStore', uids,
);
return Array.from(data.values());
return Array.from((await this.getBuddyV2SimpleInfoMap(refresh)).values());
}
async getBuddyIdMap(refresh = false): Promise<LimitedHashTable<string, string>> {
const uids: string[] = [];
const retMap: LimitedHashTable<string, string> = new LimitedHashTable<string, string>(5000);
const buddyService = this.context.session.getBuddyService();
const buddyListV2 = refresh ? await buddyService.getBuddyListV2('0', BuddyListReqType.KNOMAL) : await buddyService.getBuddyListV2('0', BuddyListReqType.KNOMAL);
uids.push(...buddyListV2.data.flatMap(item => item.buddyUids));
const data = await this.core.eventWrapper.callNoListenerEvent<NodeIKernelProfileService['getCoreAndBaseInfo']>(
'NodeIKernelProfileService/getCoreAndBaseInfo', 5000, 'nodeStore', uids,
);
data.forEach((value, key) => {
retMap.set(value.uin!, value.uid!);
});
//console.log('getBuddyIdMap', retMap.getValue);
const data = await this.getBuddyV2SimpleInfoMap(refresh);
data.forEach((value) => retMap.set(value.uin!, value.uid!));
return retMap;
}
async getBuddyV2ExWithCate(refresh = false) {
const uids: string[] = [];
const categoryMap: Map<string, any> = new Map();
const buddyService = this.context.session.getBuddyService();
const buddyListV2 = refresh ? (await buddyService.getBuddyListV2('0', BuddyListReqType.KNOMAL)).data : (await buddyService.getBuddyListV2('0', BuddyListReqType.KNOMAL)).data;
uids.push(
...buddyListV2.flatMap(item => {
item.buddyUids.forEach(uid => {
categoryMap.set(uid, { categoryId: item.categoryId, categoryName: item.categroyName });
});
return item.buddyUids;
}));
const data = await this.core.eventWrapper.callNoListenerEvent<NodeIKernelProfileService['getCoreAndBaseInfo']>(
'NodeIKernelProfileService/getCoreAndBaseInfo', 5000, 'nodeStore', uids,
const uids = buddyListV2.flatMap(item => {
item.buddyUids.forEach(uid => {
categoryMap.set(uid, { categoryId: item.categoryId, categoryName: item.categroyName });
});
return item.buddyUids;
});
const data = await this.core.eventWrapper.callNoListenerEvent(
'NodeIKernelProfileService/getCoreAndBaseInfo',
'nodeStore',
uids,
);
return buddyListV2.map(category => ({
categoryId: category.categoryId,
@@ -71,28 +62,17 @@ export class NTQQFriendApi {
return this.context.session.getBuddyService().isBuddy(uid);
}
/**
* @deprecated
* @param forced
* @returns
*/
async getFriends(forced = false): Promise<User[]> {
const [_retData, _BuddyArg] = await this.core.eventWrapper.CallNormalEvent<(force: boolean) => Promise<any>, (arg: OnBuddyChangeParams) => void>
(
'NodeIKernelBuddyService/getBuddyList',
'NodeIKernelBuddyListener/onBuddyListChange',
1,
5000,
() => true,
forced,
);
const friends: User[] = [];
for (const categoryItem of _BuddyArg) {
for (const friend of categoryItem.buddyList) {
friends.push(friend);
}
}
return friends;
async clearBuddyReqUnreadCnt() {
return this.context.session.getBuddyService().clearBuddyReqUnreadCnt();
}
async getBuddyReq() {
const [, ret] = await this.core.eventWrapper.callNormalEventV2(
'NodeIKernelBuddyService/getBuddyReq',
'NodeIKernelBuddyListener/onBuddyReqChange',
[],
);
return ret;
}
async handleFriendRequest(flag: string, accept: boolean) {

View File

@@ -1,18 +1,17 @@
import {
ChatType,
GeneralCallResult,
Group,
GroupMember,
GroupMemberRole,
GroupNotify,
GroupRequestOperateTypes,
InstanceContext,
KickMemberV2Req,
MemberExtSourceType,
NapCatCore,
NodeIKernelGroupListener,
NodeIKernelGroupService,
} from '@/core';
import { isNumeric, runAllWithTimeout } from '@/common/utils/helper';
import { isNumeric, solveAsyncProblem } from '@/common/helper';
import { LimitedHashTable } from '@/common/message-unique';
import { NTEventWrapper } from '@/common/event';
export class NTQQGroupApi {
context: InstanceContext;
@@ -20,38 +19,96 @@ export class NTQQGroupApi {
groupCache: Map<string, Group> = new Map<string, Group>();
groupMemberCache: Map<string, Map<string, GroupMember>> = new Map<string, Map<string, GroupMember>>();
groups: Group[] = [];
essenceLRU = new LimitedHashTable<number, string>(1000);
constructor(context: InstanceContext, core: NapCatCore) {
this.context = context;
this.core = core;
this.initCache().then().catch(context.logger.logError);
this.initCache().then().catch(context.logger.logError.bind(context.logger));
}
async initCache() {
this.groups = await this.getGroups();
for (const group of this.groups) {
this.groupCache.set(group.groupCode, group);
const data = await this.getGroupMembers(group.groupCode, 3000);
this.groupMemberCache.set(group.groupCode, data);
}
this.context.logger.logDebug(`加载${this.groups.length}个群组缓存完成`);
}
async getCoreAndBaseInfo(uids: string[]) {
return await this.core.eventWrapper.callNoListenerEvent(
'NodeIKernelProfileService/getCoreAndBaseInfo',
'nodeStore',
uids,
);
}
async fetchGroupEssenceList(groupCode: string) {
const pskey = (await this.core.apis.UserApi.getPSkey(['qun.qq.com'])).domainPskeyMap.get('qun.qq.com')!;
return this.context.session.getGroupService().fetchGroupEssenceList({
groupCode: groupCode,
pageStart: 0,
pageLimit: 300,
}, pskey);
}
async clearGroupNotifiesUnreadCount(uk: boolean) {
return this.context.session.getGroupService().clearGroupNotifiesUnreadCount(uk);
}
async setGroupAvatar(gc: string, filePath: string) {
return this.context.session.getGroupService().setHeader(gc, filePath);
}
async getGroups(forced = false) {
type ListenerType = NodeIKernelGroupListener['onGroupListUpdate'];
const [_retData, _updateType, groupList] = await this.core.eventWrapper.CallNormalEvent<(force: boolean) => Promise<any>, ListenerType>
(
const [, , groupList] = await this.core.eventWrapper.callNormalEventV2(
'NodeIKernelGroupService/getGroupList',
'NodeIKernelGroupListener/onGroupListUpdate',
1,
5000,
() => true,
forced,
);
[forced],
);
return groupList;
}
async getGroupExtFE0Info(groupCode: string[], forced = true) {
return this.context.session.getGroupService().getGroupExt0xEF0Info(
groupCode,
[],
{
bindGuildId: 1,
blacklistExpireTime: 1,
companyId: 1,
essentialMsgPrivilege: 1,
essentialMsgSwitch: 1,
fullGroupExpansionSeq: 1,
fullGroupExpansionSwitch: 1,
gangUpId: 1,
groupAioBindGuildId: 1,
groupBindGuildIds: 1,
groupBindGuildSwitch: 1,
groupExcludeGuildIds: 1,
groupExtFlameData: 1,
groupFlagPro1: 1,
groupInfoExtSeq: 1,
groupOwnerId: 1,
groupSquareSwitch: 1,
hasGroupCustomPortrait: 1,
inviteRobotMemberExamine: 1,
inviteRobotMemberSwitch: 1,
inviteRobotSwitch: 1,
isLimitGroupRtc: 1,
lightCharNum: 1,
luckyWord: 1,
luckyWordId: 1,
msgEventSeq: 1,
qqMusicMedalSwitch: 1,
reserve: 1,
showPlayTogetherSwitch: 1,
starId: 1,
todoSeq: 1,
viewedMsgDisappearTime: 1,
},
forced,
);
}
async getGroup(groupCode: string, forced = false) {
let group = this.groupCache.get(groupCode.toString());
if (!group) {
@@ -70,64 +127,8 @@ export class NTQQGroupApi {
return group;
}
async getGroupMemberLatestSendTimeCache(GroupCode: string, uids: string[]) {
return this.getGroupMemberLatestSendTime(GroupCode, uids);
}
/**
* 通过QQ自带数据库获取群成员最后发言时间(仅返回有效数据 且消耗延迟大 需要进行缓存)
* @param GroupCode 群号
* @param uids QQ号
* @returns Map<string, string> key: uin value: sendTime
* @example
* let ret = await NTQQGroupApi.getGroupMemberLastestSendTime('123456');
* for (let [uin, sendTime] of ret) {
* console.log(uin, sendTime);
* }
*/
async getGroupMemberLatestSendTime(GroupCode: string, uids: string[]) {
const getdata = async (uid: string) => {
const NTRet = await this.getLatestMsgByUids(GroupCode, [uid]);
if (NTRet.result != 0 && NTRet.msgList.length < 1) {
return undefined;
}
return { sendUin: NTRet.msgList[0].senderUin, sendTime: NTRet.msgList[0].msgTime };
};
const PromiseData: Promise<({
sendUin: string;
sendTime: string;
} | undefined)>[] = [];
const ret: Map<string, string> = new Map();
for (const uid of uids) {
PromiseData.push(getdata(uid).catch(() => undefined));
}
const allRet = await runAllWithTimeout(PromiseData, 2500);
for (const PromiseDo of allRet) {
if (PromiseDo) {
ret.set(PromiseDo.sendUin, PromiseDo.sendTime);
}
}
return ret;
}
async getLatestMsgByUids(GroupCode: string, uids: string[]) {
return await this.context.session.getMsgService().queryMsgsWithFilterEx('0', '0', '0', {
chatInfo: {
peerUid: GroupCode,
chatType: ChatType.group,
},
filterMsgType: [],
filterSendersUid: uids,
filterMsgToTime: '0',
filterMsgFromTime: '0',
isReverseOrder: false,
isIncludeCurrent: true,
pageLimit: 1,
});
}
async getGroupMemberAll(GroupCode: string, forced = false) {
return this.context.session.getGroupService().getAllMemberList(GroupCode, forced);
async getGroupMemberAll(groupCode: string, forced = false) {
return this.context.session.getGroupService().getAllMemberList(groupCode, forced);
}
async getGroupMember(groupCode: string | number, memberUinOrUid: string | number) {
@@ -136,11 +137,10 @@ export class NTQQGroupApi {
let members = this.groupMemberCache.get(groupCodeStr);
if (!members) {
try {
members = await this.getGroupMembers(groupCodeStr);
members = await this.getGroupMembersV2(groupCodeStr);
// 更新群成员列表
this.groupMemberCache.set(groupCodeStr, members);
}
catch (e) {
} catch (e) {
return null;
}
}
@@ -158,37 +158,13 @@ export class NTQQGroupApi {
let member = getMember();
if (!member) {
members = await this.getGroupMembers(groupCodeStr);
members = await this.getGroupMembersV2(groupCodeStr);
member = getMember();
}
return member;
}
async getLatestMsg(GroupCode: string, uins: string[]) {
const uids: Array<string> = [];
for (const uin of uins) {
const uid = await this.core.apis.UserApi.getUidByUinV2(uin);
if (uid) {
uids.push(uid);
}
}
return await this.context.session.getMsgService().queryMsgsWithFilterEx('0', '0', '0', {
chatInfo: {
peerUid: GroupCode,
chatType: ChatType.group,
},
filterMsgType: [],
filterSendersUid: uids,
filterMsgToTime: '0',
filterMsgFromTime: '0',
isReverseOrder: false,
isIncludeCurrent: true,
pageLimit: 1,
});
}
async getGroupRecommendContactArkJson(GroupCode: string) {
return this.context.session.getGroupService().getGroupRecommendContactArkJson(GroupCode);
async getGroupRecommendContactArkJson(groupCode: string) {
return this.context.session.getGroupService().getGroupRecommendContactArkJson(groupCode);
}
async CreatGroupFileFolder(groupCode: string, folderName: string) {
@@ -204,8 +180,6 @@ export class NTQQGroupApi {
}
async addGroupEssence(GroupCode: string, msgId: string) {
// 代码没测过
// 需要 ob11msgid->msgId + (peer) -> msgSeq + msgRandom
const MsgData = await this.context.session.getMsgService().getMsgsIncludeSelf({
chatType: 2,
guildId: '',
@@ -216,13 +190,36 @@ export class NTQQGroupApi {
msgRandom: parseInt(MsgData.msgList[0].msgRandom),
msgSeq: parseInt(MsgData.msgList[0].msgSeq),
};
// GetMsgByShoretID(ShoretID); -> MsgService.getMsgs(Peer,MsgId,1,false); -> 组出参数
return this.context.session.getGroupService().addGroupEssence(param);
}
async kickMemberV2Inner(param: KickMemberV2Req) {
return this.context.session.getGroupService().kickMemberV2(param);
}
async deleteGroupBulletin(GroupCode: string, noticeId: string) {
const psKey = (await this.core.apis.UserApi.getPSkey(['qun.qq.com'])).domainPskeyMap.get('qun.qq.com')!;
return this.context.session.getGroupService().deleteGroupBulletin(GroupCode, psKey, noticeId);
}
async quitGroupV2(GroupCode: string, needDeleteLocalMsg: boolean) {
const param = {
groupCode: GroupCode,
needDeleteLocalMsg: needDeleteLocalMsg,
};
return this.context.session.getGroupService().quitGroupV2(param);
}
async removeGroupEssenceBySeq(GroupCode: string, msgRandom: string, msgSeq: string) {
const param = {
groupCode: GroupCode,
msgRandom: parseInt(msgRandom),
msgSeq: parseInt(msgSeq),
};
return this.context.session.getGroupService().removeGroupEssence(param);
}
async removeGroupEssence(GroupCode: string, msgId: string) {
// 代码没测过
// 需要 ob11msgid->msgId + (peer) -> msgSeq + msgRandom
const MsgData = await this.context.session.getMsgService().getMsgsIncludeSelf({
chatType: 2,
guildId: '',
@@ -233,65 +230,112 @@ export class NTQQGroupApi {
msgRandom: parseInt(MsgData.msgList[0].msgRandom),
msgSeq: parseInt(MsgData.msgList[0].msgSeq),
};
// GetMsgByShoretID(ShoretID); -> MsgService.getMsgs(Peer,MsgId,1,false); -> 组出参数
return this.context.session.getGroupService().removeGroupEssence(param);
}
async getSingleScreenNotifies(num: number) {
const [_retData, _doubt, _seq, notifies] = await this.core.eventWrapper.CallNormalEvent<(arg1: boolean, arg2: string, arg3: number) => Promise<any>, (doubt: boolean, seq: string, notifies: GroupNotify[]) => void>
(
async getSingleScreenNotifies(doubt: boolean, num: number) {
const [, , , notifies] = await this.core.eventWrapper.callNormalEventV2(
'NodeIKernelGroupService/getSingleScreenNotifies',
'NodeIKernelGroupListener/onGroupSingleScreenNotifies',
1,
5000,
() => true,
false,
'',
num,
);
[
doubt,
'',
num,
],
);
return notifies;
}
async getGroupMemberV2(GroupCode: string, uid: string, forced = false) {
//type ListenerType = NodeIKernelGroupListener['onMemberInfoChange'];
type EventType = NodeIKernelGroupService['getMemberInfo'];
// NTEventDispatch.CreatListenerFunction('NodeIKernelGroupListener/onGroupMemberInfoUpdate',
//return napCatCore.session.getGroupService().getMemberInfo(GroupCode, [uid], forced);
const Listener = this.core.eventWrapper.RegisterListen<(params: any) => void>
(
const Listener = this.core.eventWrapper.registerListen(
'NodeIKernelGroupListener/onMemberInfoChange',
1,
forced ? 5000 : 250,
(params) => {
return params === GroupCode;
},
(params, _, members) => params === GroupCode && members.size > 0,
);
const EventFunc = this.core.eventWrapper.createEventFunction<EventType>('NodeIKernelGroupService/getMemberInfo');
const retData = await EventFunc!(GroupCode, [uid], forced);
const retData = await (
this.core.eventWrapper
.createEventFunction('NodeIKernelGroupService/getMemberInfo')
)!(GroupCode, [uid], forced);
if (retData.result !== 0) {
throw new Error(`${retData.errMsg}`);
}
const result = await Listener as unknown;
let member: GroupMember | undefined;
if (Array.isArray(result) && result?.[2] instanceof Map) {
let members = result[2] as Map<string, GroupMember>;
const members = result[2] as Map<string, GroupMember>;
member = members.get(uid);
};
}
return member;
// 原本的方法: (no_cache 下效率很高, cache 下效率一致)
// const [, , , _members] = await this.core.eventWrapper.CallNormalEvent<EventType, ListenerType>
// (
// 'NodeIKernelGroupService/getMemberInfo',
// 'NodeIKernelGroupListener/onMemberInfoChange',
// 1,
// 5000,
// (groupCode: string, changeType: number, members: Map<string, GroupMember>) => {
// return groupCode == GroupCode && members.has(uid);
// },
// GroupCode, [uid], forced,
// );
// return _members.get(uid);
}
async searchGroup(groupCode: string) {
const [, ret] = await this.core.eventWrapper.callNormalEventV2(
'NodeIKernelSearchService/searchGroup',
'NodeIKernelSearchListener/onSearchGroupResult',
[{
keyWords: groupCode,
groupNum: 25,
exactSearch: false,
penetrate: ''
}],
(ret) => ret.result === 0,
(params) => !!params.groupInfos.find(g => g.groupCode === groupCode),
1,
5000
);
return ret.groupInfos.find(g => g.groupCode === groupCode);
}
async getGroupMemberEx(GroupCode: string, uid: string, forced = false, retry = 2) {
const data = await solveAsyncProblem((eventWrapper: NTEventWrapper, GroupCode: string, uid: string, forced = false) => {
return eventWrapper.callNormalEventV2(
'NodeIKernelGroupService/getMemberInfo',
'NodeIKernelGroupListener/onMemberInfoChange',
[GroupCode, [uid], forced],
(ret) => ret.result === 0,
(params, _, members) => params === GroupCode && members.size > 0 && members.has(uid),
1,
forced ? 2500 : 250
);
}, this.core.eventWrapper, GroupCode, uid, forced);
if (data && data[3] instanceof Map && data[3].has(uid)) {
return data[3].get(uid);
}
if (retry > 0) {
const trydata = await this.getGroupMemberEx(GroupCode, uid, true, retry - 1) as GroupMember | undefined;
if (trydata) return trydata;
}
return undefined;
}
async getGroupMembersV2(groupQQ: string, num = 3000): Promise<Map<string, GroupMember>> {
const groupService = this.context.session.getGroupService();
const sceneId = groupService.createMemberListScene(groupQQ, 'groupMemberList_MainWindow');
const listener = this.core.eventWrapper.registerListen(
'NodeIKernelGroupListener/onMemberListChange',
1,
5000,
(params) => params.sceneId === sceneId,
);
try {
const [membersFromFunc, membersFromListener] = await Promise.allSettled([
groupService.getNextMemberList(sceneId, undefined, num),
listener,
]);
if (membersFromFunc.status === 'fulfilled' && membersFromListener.status === 'fulfilled') {
return new Map([
...membersFromFunc.value.result.infos,
...membersFromListener.value[0].infos,
]);
}
if (membersFromFunc.status === 'fulfilled') {
return membersFromFunc.value.result.infos;
}
if (membersFromListener.status === 'fulfilled') {
return membersFromListener.value[0].infos;
}
throw new Error('获取群成员列表失败');
} finally {
groupService.destroyMemberListScene(sceneId);
}
}
async getGroupMembers(groupQQ: string, num = 3000): Promise<Map<string, GroupMember>> {
@@ -299,41 +343,21 @@ export class NTQQGroupApi {
const sceneId = groupService.createMemberListScene(groupQQ, 'groupMemberList_MainWindow');
const result = await groupService.getNextMemberList(sceneId!, undefined, num);
if (result.errCode !== 0) {
throw ('获取群成员列表出错,' + result.errMsg);
throw new Error('获取群成员列表出错,' + result.errMsg);
}
//logDebug(`获取群(${groupQQ})成员列表结果:`, `finish: ${result.result.finish}`); //, Array.from(result.result.infos.values()));
this.context.logger.logDebug(`获取群(${groupQQ})成员列表结果:`, `members: ${result.result.infos.size}`);
return result.result.infos;
/*
console.log(sceneId);
const result = await napCatCore.getGroupService().getNextMemberList(sceneId, num);
console.log(result);
return result;
*/
}
async getGroupNotifies() {
// 获取管理员变更
// 加群通知,退出通知,需要管理员权限
}
async GetGroupFileCount(Gids: Array<string>) {
async getGroupFileCount(Gids: Array<string>) {
return this.context.session.getRichMediaService().batchGetGroupFileCount(Gids);
}
async getGroupIgnoreNotifies() {
}
async getArkJsonGroupShare(GroupCode: string) {
const ret = await this.core.eventWrapper.callNoListenerEvent<(GroupId: string) => Promise<GeneralCallResult & {
arkJson: string
}>>(
const ret = await this.core.eventWrapper.callNoListenerEvent(
'NodeIKernelGroupService/getGroupRecommendContactArkJson',
5000,
GroupCode,
);
) as GeneralCallResult & { arkJson: string };
return ret.arkJson;
}
@@ -352,12 +376,12 @@ export class NTQQGroupApi {
return this.context.session.getGroupService().operateSysNotify(
false,
{
'operateType': operateType, // 2 拒绝
'targetMsg': {
'seq': seq, // 通知序列号
'type': type,
'groupCode': groupCode,
'postscript': reason || ' ', // 仅传空值可能导致处理失败,故默认给个空格
operateType: operateType, // 2 拒绝
targetMsg: {
seq: seq, // 通知序列号
type: type,
groupCode: groupCode,
postscript: reason ?? ' ', // 仅传空值可能导致处理失败,故默认给个空格
},
});
}
@@ -391,19 +415,12 @@ export class NTQQGroupApi {
return this.context.session.getGroupService().modifyGroupName(groupQQ, groupName, false);
}
// 头衔不可用
/*
async setGroupTitle(groupQQ: string, uid: string, title: string) {
}
*/
async publishGroupBulletin(groupQQ: string, content: string, picInfo: {
id: string,
width: number,
height: number
} | undefined = undefined, pinned: number = 0, confirmRequired: number = 0) {
const _Pskey = (await this.core.apis.UserApi.getPSkey(['qun.qq.com'])).domainPskeyMap.get('qun.qq.com');
const psKey = (await this.core.apis.UserApi.getPSkey(['qun.qq.com'])).domainPskeyMap.get('qun.qq.com');
//text是content内容url编码
const data = {
text: encodeURI(content),
@@ -412,7 +429,7 @@ export class NTQQGroupApi {
pinned: pinned,
confirmRequired: confirmRequired,
};
return this.context.session.getGroupService().publishGroupBulletin(groupQQ, _Pskey!, data);
return this.context.session.getGroupService().publishGroupBulletin(groupQQ, psKey!, data);
}
async getGroupRemainAtTimes(GroupCode: string) {
@@ -420,7 +437,6 @@ export class NTQQGroupApi {
}
async getMemberExtInfo(groupCode: string, uin: string) {
// 仅NTQQ 9.9.11 24568测试 容易炸开谨慎使用
return this.context.session.getGroupService().getMemberExtInfo(
{
groupCode: groupCode,

View File

@@ -1,9 +1,12 @@
import { ChatType, GetFileListParam, Peer, RawMessage, SendMessageElement, SendStatusType } from '@/core/entities';
import { InstanceContext, NapCatCore } from '@/core';
import { onGroupFileInfoUpdateParamType } from '@/core/listeners';
import { GeneralCallResult } from '@/core/services/common';
export class NTQQMsgApi {
// nt_qq//global//nt_data//Emoji//emoji-resource//sysface_res/apng/ 下可以看到所有QQ表情预览
// nt_qq\global\nt_data\Emoji\emoji-resource\face_config.json 里面有所有表情的id, 自带表情id是QSid, 标准emoji表情id是QCid
// 其实以官方文档为准是最好的https://bot.q.qq.com/wiki/develop/api-v2/openapi/emoji/model.html#EmojiType
context: InstanceContext;
core: NapCatCore;
@@ -12,34 +15,24 @@ export class NTQQMsgApi {
this.core = core;
}
async FetchLongMsg(peer: Peer, msgId: string) {
return this.context.session.getMsgService().fetchLongMsg(peer, msgId);
async getAioFirstViewLatestMsgs(peer: Peer, MsgCount: number) {
return this.context.session.getMsgService().getAioFirstViewLatestMsgs(peer, MsgCount);
}
async sendShowInputStatusReq(peer: Peer, eventType: number) {
return this.context.session.getMsgService().sendShowInputStatusReq(peer.chatType, eventType, peer.peerUid);
}
async getMsgEmojiLikesList(peer: Peer, msgSeq: string, emojiId: string, emojiType: string, count: number = 20) {
//console.log(peer, msgSeq, emojiId, emojiType, count);
//注意此处emojiType 可选值一般为1-2 2好像是unicode表情dec值 大部分情况 Taged M likiowa
return this.context.session.getMsgService().getMsgEmojiLikesList(peer, msgSeq, emojiId, emojiType, '', false, 20);
//注意此处emojiType 可选值一般为1-2 2好像是unicode表情dec值 大部分情况 Taged Mlikiowa
return this.context.session.getMsgService().getMsgEmojiLikesList(peer, msgSeq, emojiId, emojiType, '', false, count);
}
// napCatCore: NapCatCore | null = null;
// enum BaseEmojiType {
// NORMAL_EMOJI,
// SUPER_EMOJI,
// RANDOM_SUPER_EMOJI,
// CHAIN_SUPER_EMOJI,
// EMOJI_EMOJI
// }
async setEmojiLike(peer: Peer, msgSeq: string, emojiId: string, set: boolean = true) {
// nt_qq//global//nt_data//Emoji//emoji-resource//sysface_res/apng/ 下可以看到所有QQ表情预览
// nt_qq\global\nt_data\Emoji\emoji-resource\face_config.json 里面有所有表情的id, 自带表情id是QSid, 标准emoji表情id是QCid
// 其实以官方文档为准是最好的https://bot.q.qq.com/wiki/develop/api-v2/openapi/emoji/model.html#EmojiType
emojiId = emojiId.toString();
return this.context.session.getMsgService().setMsgEmojiLikes(peer, msgSeq, emojiId, emojiId.length > 3 ? '2' : '1', set);
}
async getMultiMsg(peer: Peer, rootMsgId: string, parentMsgId: string): Promise<GeneralCallResult & {
msgList: RawMessage[]
} | undefined> {
@@ -50,24 +43,10 @@ export class NTQQMsgApi {
return this.context.session.getMsgService().forwardMsg(msgIds, peer, [peer], new Map());
}
async getLastestMsgByUids(peer: Peer, count: number = 20, isReverseOrder: boolean = false) {
const ret = await this.context.session.getMsgService().queryMsgsWithFilterEx('0', '0', '0', {
chatInfo: peer,
filterMsgType: [],
filterSendersUid: [],
filterMsgToTime: '0',
filterMsgFromTime: '0',
isReverseOrder: isReverseOrder,//此参数有点离谱 注意不是本次查询的排序 而是全部消历史信息的排序 默认false 从新消息拉取到旧消息
isIncludeCurrent: true,
pageLimit: count,
});
return ret;
}
async getMsgsByMsgId(peer: Peer | undefined, msgIds: string[] | undefined) {
if (!peer) throw new Error('peer is not allowed');
if (!msgIds) throw new Error('msgIds is not allowed');
//Mlikiowa 参数不合规会导致NC异常崩溃 原因是TX未对进入参数判断 对应Android标记@NotNull AndroidJADX分析可得
//MliKiowa: 参数不合规会导致NC异常崩溃 原因是TX未对进入参数判断 对应Android标记@NotNull AndroidJADX分析可得
return await this.context.session.getMsgService().getMsgsByMsgId(peer, msgIds);
}
@@ -80,8 +59,8 @@ export class NTQQMsgApi {
}
async queryMsgsWithFilterExWithSeq(peer: Peer, msgSeq: string) {
const ret = await this.context.session.getMsgService().queryMsgsWithFilterEx('0', '0', msgSeq, {
chatInfo: peer,//此处为Peer 为关键查询参数 没有啥也没有 by mlik iowa
return await this.context.session.getMsgService().queryMsgsWithFilterEx('0', '0', msgSeq, {
chatInfo: peer,
filterMsgType: [],
filterSendersUid: [],
filterMsgToTime: '0',
@@ -90,9 +69,32 @@ export class NTQQMsgApi {
isIncludeCurrent: true,
pageLimit: 1,
});
return ret;
}
async queryMsgsWithFilterExWithSeqV2(peer: Peer, msgSeq: string, MsgTime: string, SendersUid: string[]) {
return await this.context.session.getMsgService().queryMsgsWithFilterEx('0', '0', msgSeq, {
chatInfo: peer,
filterMsgType: [],
filterSendersUid: SendersUid,
filterMsgToTime: MsgTime,
filterMsgFromTime: MsgTime,
isReverseOrder: false,
isIncludeCurrent: true,
pageLimit: 1,
});
}
async queryFirstMsgBySeq(peer: Peer, msgSeq: string) {
return await this.context.session.getMsgService().queryMsgsWithFilterEx('0', '0', msgSeq, {
chatInfo: peer,
filterMsgType: [],
filterSendersUid: [],
filterMsgToTime: '0',
filterMsgFromTime: '0',
isReverseOrder: true,
isIncludeCurrent: true,
pageLimit: 1,
});
}
//@deprecated
async getMsgsBySeqAndCount(peer: Peer, seq: string, count: number, desc: boolean, z: boolean) {
return await this.context.session.getMsgService().getMsgsBySeqAndCount(peer, seq, count, desc, z);
}
@@ -100,7 +102,7 @@ export class NTQQMsgApi {
const DateNow = Math.floor(Date.now() / 1000);
const filterMsgFromTime = (DateNow - 300).toString();
const filterMsgToTime = DateNow.toString();
const ret = await this.context.session.getMsgService().queryMsgsWithFilterEx('0', '0', msgSeq, {
return await this.context.session.getMsgService().queryMsgsWithFilterEx('0', '0', msgSeq, {
chatInfo: peer,//此处为Peer 为关键查询参数 没有啥也没有 by mlik iowa
filterMsgType: [],
filterSendersUid: [],
@@ -110,29 +112,26 @@ export class NTQQMsgApi {
isIncludeCurrent: true,
pageLimit: 100,
});
return ret;
}
async setMsgRead(peer: Peer) {
return this.context.session.getMsgService().setMsgRead(peer);
}
async getGroupFileList(GroupCode: string, params: GetFileListParam) {
const data = await this.core.eventWrapper.CallNormalEvent<
(GroupCode: string, params: GetFileListParam) => Promise<unknown>,
(groupFileListResult: onGroupFileInfoUpdateParamType) => void
>(
const [, groupFileListResult] = await this.core.eventWrapper.callNormalEventV2(
'NodeIKernelRichMediaService/getGroupFileList',
'NodeIKernelMsgListener/onGroupFileInfoUpdate',
[
GroupCode,
params,
],
() => true,
() => true, // Todo: 应当通过 groupFileListResult 判断
1,
5000,
(groupFileListResult: onGroupFileInfoUpdateParamType) => {
//Developer Mlikiowa Todo: 此处有问题 无法判断是否成功
return true;
},
GroupCode,
params,
);
return data[1].item;
return groupFileListResult.item;
}
async getMsgHistory(peer: Peer, msgId: string, count: number, isReverseOrder: boolean = false) {
@@ -140,54 +139,62 @@ export class NTQQMsgApi {
return this.context.session.getMsgService().getMsgsIncludeSelf(peer, msgId, count, isReverseOrder);
}
async recallMsg(peer: Peer, msgIds: string[]) {
await this.context.session.getMsgService().recallMsg({
chatType: peer.chatType,
peerUid: peer.peerUid,
}, msgIds);
async recallMsg(peer: Peer, msgId: string) {
await this.core.eventWrapper.callNormalEventV2(
'NodeIKernelMsgService/recallMsg',
'NodeIKernelMsgListener/onMsgInfoListUpdate',
[peer, [msgId]],
() => true,
(updatedList) => updatedList.find(m => m.msgId === msgId && m.recallTime !== '0') !== undefined,
1,
1000,
);
}
async PrepareTempChat(toUserUid: string, GroupCode: string, nickname: string) {
//By Jadx/Ida Mlikiowa
const TempGameSession = {
nickname: "",
gameAppId: "",
selfTinyId: "",
peerRoleId: "",
peerOpenId: "",
};
return this.context.session.getMsgService().prepareTempChat({
chatType: ChatType.temp,
chatType: ChatType.KCHATTYPETEMPC2CFROMGROUP,
peerUid: toUserUid,
peerNickname: nickname,
fromGroupCode: GroupCode,
sig: "",
selfPhone: "",
sig: '',
selfPhone: '',
selfUid: this.core.selfInfo.uid,
gameSession: TempGameSession
gameSession: {
nickname: '',
gameAppId: '',
selfTinyId: '',
peerRoleId: '',
peerOpenId: '',
},
});
}
async getTempChatInfo(chatType: ChatType, peerUid: string) {
return this.context.session.getMsgService().getTempChatInfo(chatType, peerUid);
}
async sendMsg(peer: Peer, msgElements: SendMessageElement[], waitComplete = true, timeout = 10000) {
//唉? !我有个想法
if (peer.chatType === ChatType.temp && peer.guildId && peer.guildId !== '') {
//唉?!我有个想法
if (peer.chatType === ChatType.KCHATTYPETEMPC2CFROMGROUP && peer.guildId && peer.guildId !== '') {
const member = await this.core.apis.GroupApi.getGroupMember(peer.guildId, peer.peerUid!);
if (member) {
await this.PrepareTempChat(peer.peerUid, peer.guildId, member.nick);
}
}
const msgId = await this.generateMsgUniqueId(peer.chatType, await this.getServerTime());
const msgId = await this.generateMsgUniqueId(peer.chatType);
peer.guildId = msgId;
const data = await this.core.eventWrapper.CallNormalEvent<
(msgId: string, peer: Peer, msgElements: SendMessageElement[], map: Map<any, any>) => Promise<unknown>,
(msgList: RawMessage[]) => void
>(
const [, msgList] = await this.core.eventWrapper.callNormalEventV2(
'NodeIKernelMsgService/sendMsg',
'NodeIKernelMsgListener/onMsgInfoListUpdate',
1,
timeout,
(msgRecords: RawMessage[]) => {
[
'0',
peer,
msgElements,
new Map(),
],
(ret) => ret.result === 0,
msgRecords => {
for (const msgRecord of msgRecords) {
if (msgRecord.guildId === msgId && msgRecord.sendStatus === SendStatusType.KSEND_STATUS_SUCCESS) {
return true;
@@ -195,25 +202,14 @@ export class NTQQMsgApi {
}
return false;
},
'0',
peer,
msgElements,
new Map(),
1,
timeout,
);
const retMsg = data[1].find(msgRecord => {
if (msgRecord.guildId === msgId) {
return true;
}
});
return retMsg;
return msgList.find(msgRecord => msgRecord.guildId === msgId);
}
async generateMsgUniqueId(chatType: number, time: string) {
return this.context.session.getMsgService().generateMsgUniqueId(chatType, time);
}
async getServerTime() {
return this.context.session.getMSFService().getServerTime();
async generateMsgUniqueId(chatType: number) {
return this.context.session.getMsgService().generateMsgUniqueId(chatType, this.context.session.getMSFService().getServerTime());
}
async forwardMsg(srcPeer: Peer, destPeer: Peer, msgIds: string[]) {
@@ -224,34 +220,28 @@ export class NTQQMsgApi {
const msgInfos = msgIds.map(id => {
return { msgId: id, senderShowName: this.core.selfInfo.nick };
});
const data = await this.core.eventWrapper.CallNormalEvent<
(msgInfo: typeof msgInfos, srcPeer: Peer, destPeer: Peer, comment: Array<any>, attr: Map<any, any>) => Promise<unknown>,
(msgList: RawMessage[]) => void
>(
const [, msgList] = await this.core.eventWrapper.callNormalEventV2(
'NodeIKernelMsgService/multiForwardMsgWithComment',
'NodeIKernelMsgListener/onMsgInfoListUpdate',
1,
5000,
(msgRecords: RawMessage[]) => {
for (const msgRecord of msgRecords) {
if (msgRecord.peerUid == destPeer.peerUid && msgRecord.senderUid == this.core.selfInfo.uid) {
return true;
}
}
return false;
},
msgInfos,
srcPeer,
destPeer,
[],
new Map(),
[
msgInfos,
srcPeer,
destPeer,
[],
new Map(),
],
() => true,
(msgRecords) => msgRecords.some(
msgRecord => msgRecord.peerUid === destPeer.peerUid
&& msgRecord.senderUid === this.core.selfInfo.uid
),
);
for (const msg of data[1]) {
for (const msg of msgList) {
const arkElement = msg.elements.find(ele => ele.arkElement);
if (!arkElement) {
continue;
}
const forwardData: any = JSON.parse(arkElement.arkElement?.bytesData || "");
const forwardData: any = JSON.parse(arkElement.arkElement?.bytesData ?? '');
if (forwardData.app != 'com.tencent.multimsg') {
continue;
}
@@ -262,7 +252,7 @@ export class NTQQMsgApi {
throw new Error('转发消息超时');
}
async markallMsgAsRead() {
async markAllMsgAsRead() {
return this.context.session.getMsgService().setAllC2CAndGroupMsgRead();
}
}

View File

@@ -1,78 +1,7 @@
import { RequestUtil } from '@/common/utils/request';
import { RequestUtil } from '@/common/request';
import { MiniAppLuaJsonType } from '@/core';
import { InstanceContext, NapCatCore } from '..';
// let t = await napCatCore.session.getGroupService().shareDigest({
// appId: "100497308",
// appType: 1,
// msgStyle: 0,
// recvUin: "726067488",
// sendType: 1,
// clientInfo: {
// platform: 1
// },
// richMsg: {
// usingArk: true,
// title: "Bot测试title",
// summary: "Bot测试summary",
// url: "https://www.bilibili.com",
// pictureUrl: "https://y.qq.com/music/photo_new/T002R300x300M0000035DC6W4ZpSqf_1.jpg?max_age=2592000",
// brief: "Bot测试brief",
// }
// });
// {
// errCode: 0,
// errMsg: '',
// rsp: {
// sendType: 1,
// recvUin: '726067488',
// recvOpenId: '',
// errCode: 901501,
// errMsg: 'imagent service_error:150_OIDB_NO_PRIV',
// extInfo: {
// wording: '消息下发失败(错误码901501)',
// jumpResult: 0,
// jumpUrl: '',
// level: 0,
// subLevel: 0,
// developMsg: 'imagent error'
// }
// }
// }
// export class MusicSign {
// private readonly url: string;
// constructor(url: string) {
// this.url = url;
// }
// sign(postData: CustomMusicSignPostData | IdMusicSignPostData): Promise<any> {
// return new Promise((resolve, reject) => {
// fetch(this.url, {
// method: 'POST', // 指定请求方法为 POST
// headers: {
// 'Content-Type': 'application/json' // 设置请求头,指明发送的数据类型为 JSON
// },
// body: JSON.stringify(postData) // 将 JavaScript 对象转换为 JSON 字符串作为请求体
// })
// .then(response => {
// if (!response.ok) {
// reject(response.statusText); // 请求失败,返回错误信息
// }
// return response.json(); // 解析 JSON 格式的响应体
// })
// .then(data => {
// logDebug('音乐消息生成成功', data);
// resolve(data);
// })
// .catch(error => {
// reject(error);
// });
// });
// }
// }
export class NTQQMusicSignApi {
context: InstanceContext;
core: NapCatCore;
@@ -285,11 +214,6 @@ export class NTQQMusicSignApi {
//console.log(MusicReal);
return { ...MusicReal.data, mid: signedMid };
}
async CreateMusicThirdWay1(id: string = '', mid: string = '') {
}
//转换外域名为 https://qq.ugcimg.cn/v1/cpqcbu4b8870i61bde6k7cbmjgejq8mr3in82qir4qi7ielffv5slv8ck8g42novtmev26i233ujtuab6tvu2l2sjgtupfr389191v00s1j5oh5325j5eqi40774jv1i/khovifoh7jrqd6eahoiv7koh8o
//https://cgi.connect.qq.com/qqconnectopen/openapi/change_image_url?url=https://th.bing.com/th?id=OSK.b8ed36f1fb1889de6dc84fd81c187773&w=46&h=46&c=11&rs=1&qlt=80&o=6&dpr=2&pid=SANGAM

View File

@@ -1,4 +1,4 @@
import { GeneralCallResult, InstanceContext, NapCatCore } from '@/core';
import { InstanceContext, NapCatCore } from '@/core';
export class NTQQSystemApi {
context: InstanceContext;
@@ -13,7 +13,7 @@ export class NTQQSystemApi {
return this.core.util.hasOtherRunningQQProcess();
}
async ORCImage(filePath: string) {
async ocrImage(filePath: string) {
return this.context.session.getNodeMiscService().wantWinScreenOCR(filePath);
}
@@ -21,27 +21,16 @@ export class NTQQSystemApi {
return this.context.session.getRichMediaService().translateEnWordToZn(words);
}
//调用会超时 没灯用 (好像是通知listener的) onLineDev
async getOnlineDev() {
return this.context.session.getMsgService().getOnLineDev();
this.context.session.getMsgService().getOnLineDev();
}
//1-2-162b9b42-65b9-4405-a8ed-2e256ec8aa50
async getArkJsonCollection(cid: string) {
const ret = await this.core.eventWrapper.callNoListenerEvent<(cid: string) => Promise<GeneralCallResult & {
arkJson: string
}>>(
'NodeIKernelCollectionService/collectionArkShare',
5000,
'1717662698058',
);
return ret;
return await this.core.eventWrapper.callNoListenerEvent('NodeIKernelCollectionService/collectionArkShare', '1717662698058');
}
async BootMiniApp(appfile: string, params: string) {
async bootMiniApp(appFile: string, params: string) {
await this.context.session.getNodeMiscService().setMiniAppVersion('2.16.4');
const c = await this.context.session.getNodeMiscService().getMiniAppPath();
return this.context.session.getNodeMiscService().startNewMiniApp(appfile, params);
return this.context.session.getNodeMiscService().startNewMiniApp(appFile, params);
}
}

View File

@@ -1,8 +1,7 @@
import type { ModifyProfileParams, User, UserDetailInfoByUin, UserDetailInfoByUinV2 } from '@/core/entities';
import { NodeIKernelProfileListener } from '@/core/listeners';
import { RequestUtil } from '@/common/utils/request';
import { NodeIKernelProfileService, ProfileBizType, UserDetailSource } from '@/core/services';
import { InstanceContext, NapCatCore } from '..';
import { ModifyProfileParams, User, UserDetailSource } from '@/core/entities';
import { RequestUtil } from '@/common/request';
import { InstanceContext, NapCatCore, ProfileBizType } from '..';
import { solveAsyncProblem } from '@/common/helper';
export class NTQQUserApi {
context: InstanceContext;
@@ -12,12 +11,16 @@ export class NTQQUserApi {
this.context = context;
this.core = core;
}
//self_tind格式
async createUidFromTinyId(tinyId: string) {
return this.context.session.getMsgService().createUidFromTinyId(this.core.selfInfo.uin, tinyId);
}
async getStatusByUid(uid: string) {
return this.context.session.getProfileService().getStatus(uid);
}
async getProfileLike(uid: string) {
return this.context.session.getProfileLikeService().getBuddyProfileLike({
friendUids: [
uid,
],
friendUids: [uid],
basic: 1,
vote: 1,
favorite: 0,
@@ -27,7 +30,18 @@ export class NTQQUserApi {
limit: 20,
});
}
async fetchOtherProfileLike(uid: string) {
return this.context.session.getProfileLikeService().getBuddyProfileLike({
friendUids: [uid],
basic: 1,
vote: 1,
favorite: 0,
userProfile: 0,
type: 1,
start: 0,
limit: 20,
});
}
async setLongNick(longNick: string) {
return this.context.session.getProfileService().setLongNick(longNick);
}
@@ -63,59 +77,20 @@ export class NTQQUserApi {
return this.context.session.getGroupService().setHeader(gc, filePath);
}
async fetchUserDetailInfos(uids: string[]) {
//26702 以上使用新接口 .Dev Mlikiowa
type EventService = NodeIKernelProfileService['fetchUserDetailInfo'];
type EventListener = NodeIKernelProfileListener['onUserDetailInfoChanged'];
const retData: User[] = [];
const [_retData, _retListener] = await this.core.eventWrapper.CallNormalEvent<
EventService, EventListener
>(
'NodeIKernelProfileService/fetchUserDetailInfo',
'NodeIKernelProfileListener/onUserDetailInfoChanged',
uids.length,
5000,
(profile) => {
if (uids.includes(profile.uid)) {
const RetUser: User = {
...profile.simpleInfo.coreInfo,
...profile.simpleInfo.status,
...profile.simpleInfo.vasInfo,
...profile.commonExt,
...profile.simpleInfo.baseInfo,
qqLevel: profile.commonExt.qqLevel,
pendantId: '',
};
retData.push(RetUser);
return true;
}
return false;
},
'BuddyProfileStore',
uids,
UserDetailSource.KSERVER,
[ProfileBizType.KALL],
);
return retData;
}
async fetchUserDetailInfo(uid: string, mode: UserDetailSource = UserDetailSource.KDB) {
type EventService = NodeIKernelProfileService['fetchUserDetailInfo'];
type EventListener = NodeIKernelProfileListener['onUserDetailInfoChanged'];
const [_retData, profile] = await this.core.eventWrapper.CallNormalEvent<EventService, EventListener>(
const [_retData, profile] = await this.core.eventWrapper.callNormalEventV2(
'NodeIKernelProfileService/fetchUserDetailInfo',
'NodeIKernelProfileListener/onUserDetailInfoChanged',
1,
5000,
[
'BuddyProfileStore',
[uid],
mode,
[ProfileBizType.KALL],
],
() => true,
(profile) => profile.uid === uid,
'BuddyProfileStore',
[uid],
mode,
[ProfileBizType.KALL],
);
const RetUser: User = {
...profile.simpleInfo.coreInfo,
...profile.simpleInfo.status,
...profile.simpleInfo.vasInfo,
...profile.commonExt,
@@ -123,17 +98,22 @@ export class NTQQUserApi {
qqLevel: profile.commonExt?.qqLevel,
age: profile.simpleInfo.baseInfo.age,
pendantId: '',
...profile.simpleInfo.coreInfo
};
return RetUser;
}
async getUserDetailInfo(uid: string) {
const ret = await this.fetchUserDetailInfo(uid, UserDetailSource.KDB);
if (ret.uin === '0') {
this.context.logger.logDebug('[NapCat] [Mark] getUserDetailInfo Mode1 Failed.')
return await this.fetchUserDetailInfo(uid, UserDetailSource.KSERVER);
async getUserDetailInfo(uid: string): Promise<User> {
let retUser = await solveAsyncProblem(async (uid) => this.fetchUserDetailInfo(uid, UserDetailSource.KDB), uid);
if (retUser && retUser.uin !== '0') {
return retUser;
}
return ret;
this.context.logger.logDebug('[NapCat] [Mark] getUserDetailInfo Mode1 Failed.');
retUser = await this.fetchUserDetailInfo(uid, UserDetailSource.KSERVER);
if (retUser && retUser.uin === '0') {
retUser.uin = await this.core.apis.UserApi.getUidByUinV2(uid) ?? '0';
}
return retUser;
}
async modifySelfProfile(param: ModifyProfileParams) {
@@ -145,8 +125,7 @@ export class NTQQUserApi {
const ClientKeyData = await this.forceFetchClientKey();
const requestUrl = 'https://ssl.ptlogin2.qq.com/jump?ptlang=1033&clientuin=' + this.core.selfInfo.uin +
'&clientkey=' + ClientKeyData.clientKey + '&u1=https%3A%2F%2F' + domain + '%2F' + this.core.selfInfo.uin + '%2Finfocenter&keyindex=19%27';
const cookies: { [key: string]: string; } = await RequestUtil.HttpsGetCookies(requestUrl);
return cookies;
return await RequestUtil.HttpsGetCookies(requestUrl);
}
async getPSkey(domainList: string[]) {
@@ -160,7 +139,6 @@ export class NTQQUserApi {
version: 0,
aioKeywordVersion: 0,
});
// console.log(robotUinRanges?.response?.robotUinRanges);
return robotUinRanges?.response?.robotUinRanges;
}
@@ -169,24 +147,23 @@ export class NTQQUserApi {
async getQzoneCookies() {
const ClientKeyData = await this.forceFetchClientKey();
const requestUrl = 'https://ssl.ptlogin2.qq.com/jump?ptlang=1033&clientuin=' + this.core.selfInfo.uin + '&clientkey=' + ClientKeyData.clientKey + '&u1=https%3A%2F%2Fuser.qzone.qq.com%2F' + this.core.selfInfo.uin + '%2Finfocenter&keyindex=19%27';
const cookies: { [key: string]: string; } = await RequestUtil.HttpsGetCookies(requestUrl);
return cookies;
return await RequestUtil.HttpsGetCookies(requestUrl);
}
//需要异常处理
async getSkey(): Promise<string | undefined> {
async getSKey(): Promise<string | undefined> {
const ClientKeyData = await this.forceFetchClientKey();
if (ClientKeyData.result !== 0) {
throw new Error('getClientKey Error');
}
const clientKey = ClientKeyData.clientKey;
const keyIndex = ClientKeyData.keyIndex;
// const keyIndex = ClientKeyData.keyIndex;
const requestUrl = 'https://ssl.ptlogin2.qq.com/jump?ptlang=1033&clientuin=' + this.core.selfInfo.uin + '&clientkey=' + clientKey + '&u1=https%3A%2F%2Fh5.qzone.qq.com%2Fqqnt%2Fqzoneinpcqq%2Ffriend%3Frefresh%3D0%26clientuin%3D0%26darkMode%3D0&keyindex=19%27';
const cookies: { [key: string]: string; } = await RequestUtil.HttpsGetCookies(requestUrl);
const skey = cookies['skey'];
if (!skey) {
throw new Error('getSkey Skey is Empty');
throw new Error('SKey is Empty');
}
return skey;
}
@@ -199,8 +176,8 @@ export class NTQQUserApi {
if (uid) return uid;
uid = (await this.context.session.getUixConvertService().getUid([Uin])).uidInfo.get(Uin);
if (uid) return uid;
const unveifyUid = (await this.getUserDetailInfoByUinV2(Uin)).detail.uid;//从QQ Native 特殊转换
if (unveifyUid.indexOf('*') == -1) uid = unveifyUid;
const unverifiedUid = (await this.getUserDetailInfoByUin(Uin)).detail.uid;//从QQ Native 特殊转换
if (unverifiedUid.indexOf('*') == -1) uid = unverifiedUid;
//if (uid) return uid;
return uid;
}
@@ -235,9 +212,11 @@ export class NTQQUserApi {
return await this.context.session.getRecentContactService().getRecentContactList();
}
async getUserDetailInfoByUinV2(Uin: string) {
return await this.core.eventWrapper.callNoListenerEvent<(Uin: string) => Promise<UserDetailInfoByUinV2>>
('NodeIKernelProfileService/getUserDetailInfoByUin', 5000, Uin);
async getUserDetailInfoByUin(Uin: string) {
return await this.core.eventWrapper.callNoListenerEvent(
'NodeIKernelProfileService/getUserDetailInfoByUin',
Uin
);
}
async forceFetchClientKey() {

View File

@@ -1,4 +1,4 @@
import { RequestUtil } from '@/common/utils/request';
import { RequestUtil } from '@/common/request';
import {
GroupEssenceMsgRet,
InstanceContext,
@@ -20,43 +20,48 @@ export class NTQQWebApi {
async shareDigest(groupCode: string, msgSeq: string, msgRandom: string, targetGroupCode: string) {
const cookieObject = await this.core.apis.UserApi.getCookies('qun.qq.com');
const url = `https://qun.qq.com/cgi-bin/group_digest/share_digest?${
new URLSearchParams({
bkn: this.getBknFromCookie(cookieObject),
group_code: groupCode,
msg_seq: msgSeq,
msg_random: msgRandom,
target_group_code: targetGroupCode,
}).toString()
}`;
const url = `https://qun.qq.com/cgi-bin/group_digest/share_digest?${new URLSearchParams({
bkn: this.getBknFromCookie(cookieObject),
group_code: groupCode,
msg_seq: msgSeq,
msg_random: msgRandom,
target_group_code: targetGroupCode,
}).toString()}`;
try {
return RequestUtil.HttpGetText(url, 'GET', '', { 'Cookie': this.cookieToString(cookieObject) });
} catch (e) {
return undefined;
}
}
async getGroupEssenceMsg(GroupCode: string, page_start: string) {
async getGroupEssenceMsgAll(GroupCode: string) {
const ret: GroupEssenceMsgRet[] = [];
for (let i = 0; i < 20; i++) {
const data = await this.getGroupEssenceMsg(GroupCode, i, 50);
if (!data) break;
ret.push(data);
if (data.data.is_end) break;
}
return ret;
}
async getGroupEssenceMsg(GroupCode: string, page_start: number = 0, page_limit: number = 50) {
const cookieObject = await this.core.apis.UserApi.getCookies('qun.qq.com');
const url = `https://qun.qq.com/cgi-bin/group_digest/digest_list?${
new URLSearchParams({
bkn: this.getBknFromCookie(cookieObject),
group_code: GroupCode,
page_start,
page_limit: '20',
}).toString()
}`;
let ret;
const url = `https://qun.qq.com/cgi-bin/group_digest/digest_list?${new URLSearchParams({
bkn: this.getBknFromCookie(cookieObject),
page_start: page_start.toString(),
page_limit: page_limit.toString(),
group_code: GroupCode,
}).toString()}`;
try {
ret = await RequestUtil.HttpGetJson<GroupEssenceMsgRet>
(url, 'GET', '', { 'Cookie': this.cookieToString(cookieObject) });
const ret = await RequestUtil.HttpGetJson<GroupEssenceMsgRet>(
url,
'GET',
'',
{ 'Cookie': this.cookieToString(cookieObject) }
);
return ret.retcode === 0 ? ret : undefined;
} catch {
return undefined;
}
if (ret.retcode !== 0) {
return undefined;
}
return ret;
}
async getGroupMembers(GroupCode: string, cached: boolean = true): Promise<WebApiGroupMember[]> {
@@ -64,16 +69,18 @@ export class NTQQWebApi {
const memberData: Array<WebApiGroupMember> = new Array<WebApiGroupMember>();
const cookieObject = await this.core.apis.UserApi.getCookies('qun.qq.com');
const retList: Promise<WebApiGroupMemberRet>[] = [];
const fastRet = await RequestUtil.HttpGetJson<WebApiGroupMemberRet>
(`https://qun.qq.com/cgi-bin/qun_mgr/search_group_members?${
new URLSearchParams({
const fastRet = await RequestUtil.HttpGetJson<WebApiGroupMemberRet>(
`https://qun.qq.com/cgi-bin/qun_mgr/search_group_members?${new URLSearchParams({
st: '0',
end: '40',
sort: '1',
gc: GroupCode,
bkn: this.getBknFromCookie(cookieObject),
}).toString()
}`, 'POST', '', { 'Cookie': this.cookieToString(cookieObject) });
}).toString()}`,
'POST',
'',
{ 'Cookie': this.cookieToString(cookieObject) }
);
if (!fastRet?.count || fastRet?.errcode !== 0 || !fastRet?.mems) {
return [];
} else {
@@ -85,16 +92,18 @@ export class NTQQWebApi {
const PageNum = Math.ceil(fastRet.count / 40);
//遍历批量请求
for (let i = 2; i <= PageNum; i++) {
const ret = RequestUtil.HttpGetJson<WebApiGroupMemberRet>
(`https://qun.qq.com/cgi-bin/qun_mgr/search_group_members?${
new URLSearchParams({
const ret = RequestUtil.HttpGetJson<WebApiGroupMemberRet>(
`https://qun.qq.com/cgi-bin/qun_mgr/search_group_members?${new URLSearchParams({
st: ((i - 1) * 40).toString(),
end: (i * 40).toString(),
sort: '1',
gc: GroupCode,
bkn: this.getBknFromCookie(cookieObject),
}).toString()
}`, 'POST', '', { 'Cookie': this.cookieToString(cookieObject) });
}).toString()}`,
'POST',
'',
{ 'Cookie': this.cookieToString(cookieObject) }
);
retList.push(ret);
}
//批量等待
@@ -122,21 +131,56 @@ export class NTQQWebApi {
// return await res.json();
// }
async setGroupNotice(GroupCode: string, Content: string) {
async setGroupNotice(
GroupCode: string,
Content: string,
pinned: number = 0,
type: number = 1,
is_show_edit_card: number = 1,
tip_window_type: number = 1,
confirm_required: number = 1,
picId: string = '',
imgWidth: number = 540,
imgHeight: number = 300,
) {
interface SetNoticeRetSuccess {
ec: number;
em: string;
id: number;
ltsm: number;
new_fid: string;
read_only: number;
role: number;
srv_code: number;
}
const cookieObject = await this.core.apis.UserApi.getCookies('qun.qq.com');
let ret: any = undefined;
try {
ret = await RequestUtil.HttpGetJson<any>
(`https://web.qun.qq.com/cgi-bin/announce/add_qun_notice${
new URLSearchParams({
const settings = JSON.stringify({
is_show_edit_card: is_show_edit_card,
tip_window_type: tip_window_type,
confirm_required: confirm_required
});
const externalParam = {
pic: picId,
imgWidth: imgWidth.toString(),
imgHeight: imgHeight.toString(),
};
const ret: SetNoticeRetSuccess = await RequestUtil.HttpGetJson<SetNoticeRetSuccess>(
`https://web.qun.qq.com/cgi-bin/announce/add_qun_notice?${new URLSearchParams({
bkn: this.getBknFromCookie(cookieObject),
qid: GroupCode,
text: Content,
pinned: '0',
type: '1',
settings: '{"is_show_edit_card":1,"tip_window_type":1,"confirm_required":1}',
}).toString()
}`, 'GET', '', { 'Cookie': this.cookieToString(cookieObject) });
pinned: pinned.toString(),
type: type.toString(),
settings: settings,
...(picId === '' ? {} : externalParam)
}).toString()}`,
'POST',
'',
{ 'Cookie': this.cookieToString(cookieObject) }
);
return ret;
} catch (e) {
return undefined;
@@ -145,21 +189,24 @@ export class NTQQWebApi {
async getGroupNotice(GroupCode: string): Promise<undefined | WebApiGroupNoticeRet> {
const cookieObject = await this.core.apis.UserApi.getCookies('qun.qq.com');
let ret: WebApiGroupNoticeRet | undefined = undefined;
try {
ret = await RequestUtil.HttpGetJson<WebApiGroupNoticeRet>(`https://web.qun.qq.com/cgi-bin/announce/get_t_list?${
new URLSearchParams({
const ret = await RequestUtil.HttpGetJson<WebApiGroupNoticeRet>(
`https://web.qun.qq.com/cgi-bin/announce/get_t_list?${new URLSearchParams({
bkn: this.getBknFromCookie(cookieObject),
qid: GroupCode,
type: '1',
start: '0',
num: '1',
}).toString()
}`, 'GET', '', { 'Cookie': this.cookieToString(cookieObject) });
if (ret?.ec !== 0) {
return undefined;
}
return ret;
ft: '23',
ni: '1',
n: '1',
i: '1',
log_read: '1',
platform: '1',
s: '-1',
}).toString()}&n=20`,
'GET',
'',
{ 'Cookie': this.cookieToString(cookieObject) }
);
return ret?.ec === 0 ? ret : undefined;
} catch (e) {
return undefined;
}
@@ -168,16 +215,18 @@ export class NTQQWebApi {
async getGroupHonorInfo(groupCode: string, getType: WebHonorType) {
const cookieObject = await this.core.apis.UserApi.getCookies('qun.qq.com');
const getDataInternal = async (Internal_groupCode: string, Internal_type: number) => {
const url = `https://qun.qq.com/interactive/honorlist?${
new URLSearchParams({
gc: Internal_groupCode,
type: Internal_type.toString(),
}).toString()
}`;
let resJson;
try {
const res = await RequestUtil.HttpGetText(url, 'GET', '', { 'Cookie': this.cookieToString(cookieObject) });
const match = res.match(/window\.__INITIAL_STATE__=(.*?);/);
const res = await RequestUtil.HttpGetText(
`https://qun.qq.com/interactive/honorlist?${new URLSearchParams({
gc: Internal_groupCode,
type: Internal_type.toString(),
}).toString()}`,
'GET',
'',
{ 'Cookie': this.cookieToString(cookieObject) }
);
const match = /window\.__INITIAL_STATE__=(.*?);/.exec(res);
if (match) {
resJson = JSON.parse(match[1].trim());
}
@@ -187,7 +236,7 @@ export class NTQQWebApi {
return resJson?.actorList;
}
} catch (e) {
this.context.logger.logDebug('获取当前群荣耀失败', url, e);
this.context.logger.logDebug('获取当前群荣耀失败', e);
}
return undefined;
};
@@ -195,11 +244,8 @@ export class NTQQWebApi {
const HonorInfo: any = { group_id: groupCode };
if (getType === WebHonorType.TALKATIVE || getType === WebHonorType.ALL) {
try {
const RetInternal = await getDataInternal(groupCode, 1);
if (!RetInternal) {
throw new Error('获取龙王信息失败');
}
const RetInternal = await getDataInternal(groupCode, 1);
if (RetInternal) {
HonorInfo.current_talkative = {
user_id: RetInternal[0]?.uin,
avatar: RetInternal[0]?.avatar,
@@ -217,16 +263,13 @@ export class NTQQWebApi {
nickname: talkative_ele?.name,
});
}
} catch (e) {
this.context.logger.logDebug(e);
} else {
this.context.logger.logError('获取龙王信息失败');
}
}
if (getType === WebHonorType.PERFORMER || getType === WebHonorType.ALL) {
try {
const RetInternal = await getDataInternal(groupCode, 2);
if (!RetInternal) {
throw new Error('获取群聊之火失败');
}
const RetInternal = await getDataInternal(groupCode, 2);
if (RetInternal) {
HonorInfo.performer_list = [];
for (const performer_ele of RetInternal) {
HonorInfo.performer_list.push({
@@ -236,16 +279,13 @@ export class NTQQWebApi {
description: performer_ele?.desc,
});
}
} catch (e) {
this.context.logger.logDebug(e);
} else {
this.context.logger.logError('获取群聊之火失败');
}
}
if (getType === WebHonorType.PERFORMER || getType === WebHonorType.ALL) {
try {
const RetInternal = await getDataInternal(groupCode, 3);
if (!RetInternal) {
throw new Error('获取群聊炽焰失败');
}
const RetInternal = await getDataInternal(groupCode, 3);
if (RetInternal) {
HonorInfo.legend_list = [];
for (const legend_ele of RetInternal) {
HonorInfo.legend_list.push({
@@ -255,16 +295,13 @@ export class NTQQWebApi {
desc: legend_ele?.description,
});
}
} catch (e) {
this.context.logger.logDebug('获取群聊炽焰失败', e);
} else {
this.context.logger.logError('获取群聊炽焰失败');
}
}
if (getType === WebHonorType.EMOTION || getType === WebHonorType.ALL) {
try {
const RetInternal = await getDataInternal(groupCode, 6);
if (!RetInternal) {
throw new Error('获取快乐源泉失败');
}
const RetInternal = await getDataInternal(groupCode, 6);
if (RetInternal) {
HonorInfo.emotion_list = [];
for (const emotion_ele of RetInternal) {
HonorInfo.emotion_list.push({
@@ -274,14 +311,16 @@ export class NTQQWebApi {
desc: emotion_ele.description,
});
}
} catch (e) {
this.context.logger.logDebug('获取快乐源泉失败', e);
} else {
this.context.logger.logError('获取快乐源泉失败');
}
}
//冒尖小春笋好像已经被tx扬了
// 冒尖小春笋好像已经被tx扬了 R.I.P.
if (getType === WebHonorType.EMOTION || getType === WebHonorType.ALL) {
HonorInfo.strong_newbie_list = [];
}
return HonorInfo;
}

View File

@@ -1,207 +0,0 @@
import { NodeQQNTWrapperUtil, NTApiContext, WrapperNodeApi } from '@/core/wrapper';
import path from 'node:path';
import fs from 'node:fs';
import { InstanceContext } from './wrapper';
import { proxiedListenerOf } from '@/common/utils/proxy-handler';
import { GroupListener, MsgListener, ProfileListener } from './listeners';
import { GroupMember, SelfInfo } from './entities';
import { LegacyNTEventWrapper } from '@/common/framework/event-legacy';
import { NTQQFileApi, NTQQFriendApi, NTQQGroupApi, NTQQMsgApi, NTQQSystemApi, NTQQUserApi, NTQQWebApi } from './apis';
import os from 'node:os';
import { NTQQCollectionApi } from './apis/collection';
import { NapCatConfigLoader } from './helper/config';
import { LogLevel } from '@/common/utils/log';
export enum NapCatCoreWorkingEnv {
Unknown = 0,
Shell = 1,
Framework = 2,
}
export function loadQQWrapper(QQVersion: string): WrapperNodeApi {
let wrapperNodePath = path.resolve(path.dirname(process.execPath), './resources/app/wrapper.node');
if (!fs.existsSync(wrapperNodePath)) {
wrapperNodePath = path.join(path.dirname(process.execPath), `resources/app/versions/${QQVersion}/wrapper.node`);
}
const nativemodule: any = { exports: {} };
process.dlopen(nativemodule, wrapperNodePath);
return nativemodule.exports;
}
export class NapCatCore {
readonly context: InstanceContext;
readonly apis: NTApiContext;
readonly eventWrapper: LegacyNTEventWrapper;
// readonly eventChannel: NTEventChannel;
NapCatDataPath: string;
NapCatTempPath: string;
// runtime info, not readonly
selfInfo: SelfInfo;
util: NodeQQNTWrapperUtil;
configLoader: NapCatConfigLoader;
// 通过构造器递过去的 runtime info 应该尽量少
constructor(context: InstanceContext, selfInfo: SelfInfo) {
this.selfInfo = selfInfo;
this.context = context;
this.util = new this.context.wrapper.NodeQQNTWrapperUtil();
this.eventWrapper = new LegacyNTEventWrapper(context.wrapper, context.session);
this.apis = {
FileApi: new NTQQFileApi(this.context, this),
SystemApi: new NTQQSystemApi(this.context, this),
CollectionApi: new NTQQCollectionApi(this.context, this),
WebApi: new NTQQWebApi(this.context, this),
FriendApi: new NTQQFriendApi(this.context, this),
MsgApi: new NTQQMsgApi(this.context, this),
UserApi: new NTQQUserApi(this.context, this),
GroupApi: new NTQQGroupApi(this.context, this),
};
this.configLoader = new NapCatConfigLoader(this, this.context.pathWrapper.configPath);
this.NapCatDataPath = path.join(this.dataPath, 'NapCat');
fs.mkdirSync(this.NapCatDataPath, { recursive: true });
this.NapCatTempPath = path.join(this.NapCatDataPath, 'temp');
// 创建临时目录
if (!fs.existsSync(this.NapCatTempPath)) {
fs.mkdirSync(this.NapCatTempPath, { recursive: true });
}
this.initNapCatCoreListeners().then().catch(this.context.logger.logError);
this.context.logger.setFileLogEnabled(
this.configLoader.configData.fileLog,
);
this.context.logger.setConsoleLogEnabled(
this.configLoader.configData.consoleLog,
);
this.context.logger.setFileAndConsoleLogLevel(
this.configLoader.configData.fileLogLevel as LogLevel,
this.configLoader.configData.consoleLogLevel as LogLevel,
);
}
get dataPath(): string {
let result = this.util.getNTUserDataInfoConfig();
if (!result) {
result = path.resolve(os.homedir(), './.config/QQ');
fs.mkdirSync(result, { recursive: true });
}
return result;
}
// Renamed from 'InitDataListener'
async initNapCatCoreListeners() {
const msgListener = new MsgListener();
msgListener.onRecvMsg = (msgs) => {
msgs.forEach(msg => this.context.logger.logMessage(msg, this.selfInfo));
};
msgListener.onAddSendMsg = (msg) => {
this.context.logger.logMessage(msg, this.selfInfo);
};
//await sleep(2500);
this.context.session.getMsgService().addKernelMsgListener(
new this.context.wrapper.NodeIKernelMsgListener(proxiedListenerOf(msgListener, this.context.logger)),
);
const profileListener = new ProfileListener();
profileListener.onProfileDetailInfoChanged = (profile) => {
if (profile.uid === this.selfInfo.uid) {
Object.assign(this.selfInfo, profile);
}
};
profileListener.onSelfStatusChanged = (/* Info: SelfStatusInfo */) => {
// if (Info.status == 20) {
// log("账号状态变更为离线")
// }
};
this.context.session.getProfileService().addKernelProfileListener(
new this.context.wrapper.NodeIKernelProfileListener(proxiedListenerOf(profileListener, this.context.logger)),
);
// 群相关
const groupListener = new GroupListener();
groupListener.onGroupListUpdate = (updateType, groupList) => {
// console.log("onGroupListUpdate", updateType, groupList)
groupList.map(g => {
const existGroup = this.apis.GroupApi.groupCache.get(g.groupCode);
//群成员数量变化 应该刷新缓存
if (existGroup && g.memberCount === existGroup.memberCount) {
Object.assign(existGroup, g);
}
else {
this.apis.GroupApi.groupCache.set(g.groupCode, g);
// 获取群成员
}
const sceneId = this.context.session.getGroupService().createMemberListScene(g.groupCode, 'groupMemberList_MainWindow');
this.context.session.getGroupService().getNextMemberList(sceneId!, undefined, 3000).then( /* r => {
// console.log(`get group ${g.groupCode} members`, r);
// r.result.infos.forEach(member => {
// });
// groupMembers.set(g.groupCode, r.result.infos);
} */);
});
};
groupListener.onMemberListChange = (arg) => {
// todo: 应该加一个内部自己维护的成员变动callback用于判断成员变化通知
const groupCode = arg.sceneId.split('_')[0];
if (this.apis.GroupApi.groupMemberCache.has(groupCode)) {
const existMembers = this.apis.GroupApi.groupMemberCache.get(groupCode)!;
arg.infos.forEach((member, uid) => {
//console.log('onMemberListChange', member);
const existMember = existMembers.get(uid);
if (existMember) {
Object.assign(existMember, member);
}
else {
existMembers!.set(uid, member);
}
//移除成员
if (member.isDelete) {
existMembers.delete(uid);
}
});
}
else {
this.apis.GroupApi.groupMemberCache.set(groupCode, arg.infos);
}
// console.log('onMemberListChange', groupCode, arg);
};
groupListener.onMemberInfoChange = (groupCode, changeType, members) => {
//console.log('onMemberInfoChange', groupCode, changeType, members);
if (changeType === 0 && members.get(this.selfInfo.uid)?.isDelete) {
// 自身退群或者被踢退群 5s用于Api操作 之后不再出现
setTimeout(() => {
this.apis.GroupApi.groupCache.delete(groupCode);
}, 5000);
}
const existMembers = this.apis.GroupApi.groupMemberCache.get(groupCode);
if (existMembers) {
members.forEach((member, uid) => {
const existMember = existMembers.get(uid);
if (existMember) {
// 检查管理变动
member.isChangeRole = this.checkAdminEvent(groupCode, member, existMember);
// 更新成员信息
Object.assign(existMember, member);
}
else {
existMembers.set(uid, member);
}
//移除成员
if (member.isDelete) {
existMembers.delete(uid);
}
});
}
else {
this.apis.GroupApi.groupMemberCache.set(groupCode, members);
}
};
}
checkAdminEvent(groupCode: string, memberNew: GroupMember, memberOld: GroupMember | undefined): boolean {
if (memberNew.role !== memberOld?.role) {
this.context.logger.log(`${groupCode} ${memberNew.nick} 角色变更为 ${memberNew.role === 3 ? '管理员' : '群员'}`);
return true;
}
return false;
}
}

View File

@@ -0,0 +1,11 @@
export enum MsfStatusType {
KUNKNOWN,
KDISCONNECTED,
KCONNECTED
}
export enum MsfChangeReasonType {
KUNKNOWN,
KUSERLOGININ,
KUSERLOGINOUT,
KAUTO
}

View File

@@ -0,0 +1,12 @@
export interface FSABRecentContactParams {
anchorPointContact: {
contactId: string;
sortField: string;
pos: number;
};
relativeMoveCount: number;
listType: number;
count: number;
fetchOld: boolean;
}

View File

@@ -1,4 +1,68 @@
import { QQLevel, Sex, User } from './user';
import { QQLevel, Sex } from './user';
export interface KickMemberInfo {
optFlag: number,
optOperate: number,
optMemberUid: string,
optBytesMsg: string,
}
//getGroupDetailInfo GroupCode,GroupInfoSource
export enum GroupInfoSource {
KUNSPECIFIED,
KBIGDATACARD,
KDATACARD,
KNOTICE,
KAIO,
KRECENTCONTACT,
KMOREPANEL
}
export interface GroupExt0xEF0InfoFilter {
bindGuildId: number;
blacklistExpireTime: number;
companyId: number;
essentialMsgPrivilege: number;
essentialMsgSwitch: number;
fullGroupExpansionSeq: number;
fullGroupExpansionSwitch: number;
gangUpId: number;
groupAioBindGuildId: number;
groupBindGuildIds: number;
groupBindGuildSwitch: number;
groupExcludeGuildIds: number;
groupExtFlameData: number;
groupFlagPro1: number;
groupInfoExtSeq: number;
groupOwnerId: number;
groupSquareSwitch: number;
hasGroupCustomPortrait: number;
inviteRobotMemberExamine: number;
inviteRobotMemberSwitch: number;
inviteRobotSwitch: number;
isLimitGroupRtc: number;
lightCharNum: number;
luckyWord: number;
luckyWordId: number;
msgEventSeq: number;
qqMusicMedalSwitch: number;
reserve: number;
showPlayTogetherSwitch: number;
starId: number;
todoSeq: number;
viewedMsgDisappearTime: number;
}
export interface KickMemberV2Req {
groupCode: string,
kickFlag: number,
kickList: Array<KickMemberInfo>,
kickListUids: Array<string>,
kickMsg: string
}
export enum DataSource {
LOCAL,
REMOTE
}
export enum GroupListUpdateType {
REFRESHALL,
@@ -6,13 +70,15 @@ export enum GroupListUpdateType {
MODIFIED,
REMOVE
}
export interface GroupMemberCache {
group: {
data: GroupMember[];
isExpired: boolean;
}
};
isExpired: boolean;
}
export interface Group {
groupCode: string,
createTime?: string,//高版本才有

View File

@@ -1,4 +1,4 @@
import { GroupMemberRole } from './group';
import { GroupMemberRole } from '@/core';
export interface Peer {
chatType: ChatType;
@@ -22,6 +22,7 @@ export interface GetFileListParam {
startIndex: number;
sortOrder: number;
showOnlinedocFolder: number;
folderId?: string;
}
export enum ElementType {
@@ -145,6 +146,7 @@ export interface TaskTopMsgElement {
iconUrl: string;
topMsgType: number;
}
export enum NTMsgType {
KMSGTYPEARKSTRUCT = 11,
KMSGTYPEFACEBUBBLE = 24,
@@ -168,6 +170,7 @@ export enum NTMsgType {
KMSGTYPEVIDEO = 7,
KMSGTYPEWALLET = 10
}
export interface SendTaskTopMsgElement {
elementType: ElementType.TASKTOPMSG;
elementId: string;
@@ -355,6 +358,7 @@ export enum NTMsgAtType {
ATTYPESUMMONROLE = 256,
ATTYPEUNKNOWN = 0
}
export interface SendPicElement {
elementType: ElementType.PIC;
elementId: string;
@@ -362,10 +366,12 @@ export interface SendPicElement {
}
export interface ReplyElement {
sourceMsgIdInRecords?: string;
replayMsgSeq: string;
replayMsgId: string;
senderUin: string;
senderUinStr: string;
senderUidStr?: string;
replyMsgTime?: string;
}
export interface SendReplyElement {
@@ -410,7 +416,7 @@ export interface ShareLocationElement {
ext: string;
}
export interface sendShareLocationElement {
export interface SendShareLocationElement {
elementType: ElementType.SHARELOCATION;
elementId: string;
shareLocationElement?: ShareLocationElement;
@@ -460,7 +466,7 @@ export interface SendMarkdownElement {
export type SendMessageElement = SendTextElement | SendPttElement |
SendPicElement | SendReplyElement | SendFaceElement | SendMarketFaceElement | SendFileElement |
SendVideoElement | SendArkElement | SendMarkdownElement | sendShareLocationElement;
SendVideoElement | SendArkElement | SendMarkdownElement | SendShareLocationElement;
export interface TextElement {
content: string;
@@ -476,7 +482,7 @@ export interface MessageElement {
extBufForUI: string,//"0x",
textElement?: TextElement;
faceElement?: FaceElement,
marketFaceElement?: MarkdownElement,
marketFaceElement?: MarketFaceElement,
replyElement?: ReplyElement,
picElement?: PicElement,
pttElement?: PttElement,
@@ -511,16 +517,8 @@ export enum AtType {
atUser = 2
}
export enum ChatType {
friend = 1,
group = 2,
chatDevice = 8, //移动设备?
temp = 100
}
// 来自Android分析
export enum ChatType2 {
export enum ChatType {
KCHATTYPEADELIE = 42,
KCHATTYPEBUDDYNOTIFY = 5,
KCHATTYPEC2C = 1,
@@ -597,6 +595,7 @@ export const IMAGE_HTTP_HOST_NT = 'https://multimedia.nt.qq.com.cn';
export interface PicElement {
md5HexStr?: string;
filePath?: string;
fileSize: number | string;//number
picWidth: number;
picHeight: number;
@@ -634,6 +633,7 @@ export enum NTGrayTipElementSubTypeV2 {
GRAYTIP_ELEMENT_SUBTYPE_WALLET = 16,
GRAYTIP_ELEMENT_SUBTYPE_XMLMSG = 12,
}
export interface GrayTipElement {
subElementType: NTGrayTipElementSubTypeV2;
revokeElement: {
@@ -856,47 +856,14 @@ export interface MultiForwardMsgElement {
resId: string;
fileName: string;
}
export enum NTSubMsgType {
KMSGSUBTYPEARKGROUPANNOUNCE = 3,
KMSGSUBTYPEARKGROUPANNOUNCECONFIRMREQUIRED = 4,
KMSGSUBTYPEARKGROUPGIFTATME = 5,
KMSGSUBTYPEARKGROUPTASKATALL = 6,
KMSGSUBTYPEARKMULTIMSG = 7,
KMSGSUBTYPEARKNORMAL = 0,
KMSGSUBTYPEARKTENCENTDOCFROMMINIAPP = 1,
KMSGSUBTYPEARKTENCENTDOCFROMPLUSPANEL = 2,
KMSGSUBTYPEEMOTICON = 15,
KMSGSUBTYPEFILEAPP = 11,
KMSGSUBTYPEFILEAUDIO = 3,
KMSGSUBTYPEFILEDOC = 4,
KMSGSUBTYPEFILEEXCEL = 6,
KMSGSUBTYPEFILEFOLDER = 13,
KMSGSUBTYPEFILEHTML = 10,
KMSGSUBTYPEFILEIPA = 14,
KMSGSUBTYPEFILENORMAL = 0,
KMSGSUBTYPEFILEPDF = 7,
KMSGSUBTYPEFILEPIC = 1,
KMSGSUBTYPEFILEPPT = 5,
KMSGSUBTYPEFILEPSD = 12,
KMSGSUBTYPEFILETXT = 8,
KMSGSUBTYPEFILEVIDEO = 2,
KMSGSUBTYPEFILEZIP = 9,
KMSGSUBTYPELINK = 5,
KMSGSUBTYPEMARKETFACE = 1,
KMSGSUBTYPEMIXEMOTICON = 7,
KMSGSUBTYPEMIXFACE = 3,
KMSGSUBTYPEMIXMARKETFACE = 2,
KMSGSUBTYPEMIXPIC = 1,
KMSGSUBTYPEMIXREPLY = 4,
KMSGSUBTYPEMIXTEXT = 0,
KMSGSUBTYPETENCENTDOC = 6
}
export enum SendStatusType {
KSEND_STATUS_FAILED = 0,
KSEND_STATUS_SENDING = 1,
KSEND_STATUS_SUCCESS = 2,
KSEND_STATUS_SUCCESS_NOSEQ = 3
}
export interface RawMessage {
parentMsgPeer: Peer;
@@ -922,7 +889,7 @@ export interface RawMessage {
msgType: NTMsgType;
subMsgType: NTSubMsgType;
subMsgType: number;
senderUid: string;
@@ -965,76 +932,45 @@ export interface RawMessage {
records: RawMessage[];
elements: ElementWrapper[];
elements: MessageElement[];
}
export interface QueryMsgsParams {
chatInfo: Peer;
filterMsgType: [];
filterSendersUid: string[];
filterMsgFromTime: string;
filterMsgToTime: string;
pageLimit: number;
isReverseOrder: boolean;
isIncludeCurrent: boolean;
}
/**
* 并非原生接口类型,故以 type 包装
*/
export type ElementWrapper = {
elementId: string;
elementType: ElementType;
replyElement?: {
sourceMsgIdInRecords: string;
/**
* 源消息发送者 QQ 号
*/
senderUid: string;
/**
* 源消息是否有图片
*/
sourceMsgIsIncPic: boolean;
/**
* 源消息文本
*/
sourceMsgText: string;
/**
* 源消息的 msgSeq可以通过这个找到源消息的 msgId
*/
replayMsgSeq: string;
};
textElement?: {
atType: AtType;
/**
* 被 @ 的 QQ 号
*/
atUid: string;
content: string;
/**
* 被 @ 的 UID从这里可以看出来 UID 的概念是 NT 才引入的)
*/
atNtUid: string;
};
picElement?: PicElement;
pttElement?: PttElement;
arkElement?: ArkElement;
grayTipElement?: GrayTipElement;
faceElement?: FaceElement;
videoElement?: VideoElement;
fileElement?: FileElement;
marketFaceElement?: MarketFaceElement;
inlineKeyboardElement?: InlineKeyboardElement;
markdownElement?: MarkdownElement;
multiForwardMsgElement?: MultiForwardMsgElement;
export interface TmpChatInfoApi {
errMsg: string;
result: number;
tmpChatInfo?: TmpChatInfo;
}
export interface TmpChatInfo {
chatType: number;
fromNick: string;
groupCode: string;
peerUid: string;
sessionType: number;
sig: string;
}
export interface MsgReqType {
peer: Peer,
byType: number,
msgId: string,
msgSeq: string,
msgTime: string,
clientSeq: string,
cnt: number,
queryOrder: boolean,
includeSelf: boolean,
includeDeleteMsg: boolean,
extraCnt: number
}
//getMsgsIncludeSelf Peer必须 byType 1
//getMsgsWithMsgTimeAndClientSeqForC2C Peer必须 byType 3

View File

@@ -1,12 +1,20 @@
export enum GroupNotifyTypes {
INVITE_ME = 1,
INVITED_JOIN = 4, // 有人接受了邀请入群
JOIN_REQUEST = 7,
ADMIN_SET = 8,
KICK_MEMBER = 9,
MEMBER_EXIT = 11, // 主动退出
ADMIN_UNSET = 12,
ADMIN_UNSET_OTHER = 13, // 其他人取消管理员
export enum GroupNotifyMsgType {
UN_SPECIFIED,
INVITED_BY_MEMBER,
REFUSE_INVITED,
REFUSED_BY_ADMINI_STRATOR,
AGREED_TOJOIN_DIRECT,// 有人接受了邀请入群
INVITED_NEED_ADMINI_STRATOR_PASS,
AGREED_TO_JOIN_BY_ADMINI_STRATOR,
REQUEST_JOIN_NEED_ADMINI_STRATOR_PASS,
SET_ADMIN,
KICK_MEMBER_NOTIFY_ADMIN,
KICK_MEMBER_NOTIFY_KICKED,
MEMBER_LEAVE_NOTIFY_ADMIN,// 主动退出
CANCEL_ADMIN_NOTIFY_CANCELED,
CANCEL_ADMIN_NOTIFY_ADMIN,// 其他人取消管理员
TRANSFER_GROUP_NOTIFY_OLDOWNER,
TRANSFER_GROUP_NOTIFY_ADMIN
}
export interface GroupNotifies {
@@ -15,26 +23,40 @@ export interface GroupNotifies {
notifies: GroupNotify[];
}
export enum GroupNotifyStatus {
IGNORE = 0,
WAIT_HANDLE = 1,
APPROVE = 2,
REJECT = 3
export enum GroupNotifyMsgStatus {
KINIT,//初始化
KUNHANDLE,//未处理
KAGREED,//同意
KREFUSED,//拒绝
KIGNORED//忽略
}
export enum GroupInviteStatus {
INIT,
WAIT_TO_APPROVE,
JOINED,
REFUSED_BY_ADMINI_STRATOR
}
export enum GroupInviteType {
BYBUDDY,
BYGROUPMEMBER,
BYDISCUSSMEMBER
}
export interface GroupNotify {
time: number; // 自己添加的字段,时间戳,毫秒, 用于判断收到短时间内收到重复的notify
seq: string; // 唯一标识符转成数字再除以1000应该就是时间戳
type: GroupNotifyTypes;
status: GroupNotifyStatus; // 0是已忽略1是未处理2是已同意
seq: string; // 通知序列号
type: GroupNotifyMsgType;
status: GroupNotifyMsgStatus;
group: { groupCode: string; groupName: string };
user1: { uid: string; nickName: string }; // 被设置管理员的人
user2: { uid: string; nickName: string }; // 操作者
actionUser: { uid: string; nickName: string }; //未知
actionTime: string;
invitationExt: {
srcType: number; // 0?未知
groupCode: string; waitStatus: number
srcType: GroupInviteType; // 邀请来源
groupCode: string;
waitStatus: GroupInviteStatus
};
postscript: string; // 加群用户填写的验证信息
repeatSeqs: [];
@@ -64,6 +86,7 @@ export enum BuddyReqType {
}
export interface FriendRequest {
isBuddy?: boolean;
isInitiator?: boolean;
isDecide: boolean;
friendUid: string;

View File

@@ -153,7 +153,10 @@ interface CommonExt {
labels: any[];
qqLevel: QQLevel;
}
export enum BuddyListReqType {
KNOMAL,
KLETTER
}
interface Pic {
picId: string;
picTime: number;
@@ -213,7 +216,7 @@ export interface BuddyProfileLikeReq {
userProfile: number;
type: number;
start: number;
limit: number;
limit?: number;
}
export interface QQLevel {
@@ -294,7 +297,7 @@ export enum BizKey {
KPHOTOWALL
}
export interface UserDetailInfoByUinV2 {
export interface UserDetailInfoByUin {
result: number,
errMsg: string,
detail: {
@@ -305,62 +308,15 @@ export interface UserDetailInfoByUinV2 {
photoWall: null
}
}
export interface UserDetailInfoByUin {
result: number,
errMsg: string,
info: {
uid: string,//这个没办法用
qid: string,
uin: string,
nick: string,
remark: string,
longNick: string,
avatarUrl: string,
birthday_year: number,
birthday_month: number,
birthday_day: number,
sex: number,//0
topTime: string,
constellation: number,
shengXiao: number,
kBloodType: number,
homeTown: string,
makeFriendCareer: number,
pos: string,
eMail: string,
phoneNum: string,
college: string,
country: string,
province: string,
city: string,
postCode: string,
address: string,
isBlock: boolean,
isSpecialCareOpen: boolean,
isSpecialCareZone: boolean,
ringId: string,
regTime: number,
interest: string,
termType: number,
labels: any[],
qqLevel: { crownNum: number, sunNum: number, moonNum: number, starNum: number },
isHideQQLevel: number,
privilegeIcon: { jumpUrl: string, openIconList: any[], closeIconList: any[] },
isHidePrivilegeIcon: number,
photoWall: { picList: any[] },
vipFlag: boolean,
yearVipFlag: boolean,
svipFlag: boolean,
vipLevel: number,
status: number,
qidianMasterFlag: number,
qidianCrewFlag: number,
qidianCrewFlag2: number,
extStatus: number,
recommendImgFlag: number,
disableEmojiShortCuts: number,
pendantId: string,
vipNameColorId: string
}
export enum UserDetailSource {
KDB,
KSERVER
}
export enum ProfileBizType {
KALL,
KBASEEXTEND,
KVAS,
KQZONE,
KOTHER
}

View File

@@ -57,7 +57,7 @@ export interface WebApiGroupMemberRet {
export interface WebApiGroupNoticeFeed {
u: number;//发送者
fid: string;//fid
fid: string;//fid,notice_id
pubt: number;//时间
msg: {
text: string

View File

@@ -1,58 +1,10 @@
{
"3.1.2-13107": {
"appid": 537146866,
"qua": "V1_LNX_NQ_3.1.2-13107_RDM_B"
"9.9.15-28060":{
"appid": 537246092,
"qua": "V1_WIN_NQ_9.9.15_28060_GW_B"
},
"3.2.10-25765": {
"appid": 537234773,
"qua": "V1_LNX_NQ_3.2.10_25765_GW_B"
},
"3.2.12-26702": {
"appid": 537237950,
"qua": "V1_LNX_NQ_3.2.12_26702_GW_B"
},
"3.2.12-26740": {
"appid": 537237950,
"qua": "V1_WIN_NQ_9.9.15_26740_GW_B"
},
"3.2.12-26909": {
"appid": 537237923,
"qua": "V1_LNX_NQ_3.2.12_26909_GW_B"
},
"9.9.11-24815": {
"appid": 537226656,
"qua": "V1_WIN_NQ_9.9.11_24815_GW_B"
},
"9.9.12-25493": {
"appid": 537231759,
"qua": "V1_WIN_NQ_9.9.12_25493_GW_B"
},
"9.9.12-25765": {
"appid": 537234702,
"qua": "V1_WIN_NQ_9.9.12_25765_GW_B"
},
"9.9.12-26299": {
"appid": 537234826,
"qua": "V1_WIN_NQ_9.9.12_26299_GW_B"
},
"9.9.12-26339": {
"appid": 537234826,
"qua": "V1_WIN_NQ_9.9.12_26339_GW_B"
},
"9.9.12-26466": {
"appid": 537234826,
"qua": "V1_WIN_NQ_9.9.12_26466_GW_B"
},
"9.9.15-26702": {
"appid": 537237765,
"qua": "V1_WIN_NQ_9.9.15_26702_GW_B"
},
"9.9.15-26740": {
"appid": 537237765,
"qua": "V1_WIN_NQ_9.9.15_26702_GW_B"
},
"9.9.15-26909": {
"appid": 537237802,
"qua": "V1_WIN_NQ_9.9.15_26909_GW_B"
"3.2.12-28060":{
"appid": 537246140,
"qua": "V1_LNX_NQ_3.2.12_28060_GW_B"
}
}
}

View File

@@ -0,0 +1,31 @@
syntax = 'proto3';
package SysMessage;
message EmojiLikeToOthersWrapper1 {
EmojiLikeToOthersWrapper2 wrapper = 1;
}
message EmojiLikeToOthersWrapper2 {
EmojiLikeToOthersWrapper3 body = 1;
}
message EmojiLikeToOthersWrapper3 {
EmojiLikeToOthersMsgSpec msgSpec = 2;
EmojiLikeToOthersAttributes attributes = 3;
}
message EmojiLikeToOthersMsgSpec {
uint32 msgSeq = 1;
}
message EmojiLikeToOthersAttributes {
enum Operation {
FALLBACK = 0;
LIKE = 1;
UNLIKE = 2;
}
string emojiId = 1;
string senderUid = 4;
Operation operation = 5;
}

View File

@@ -0,0 +1,9 @@
syntax = 'proto3';
package SysMessage;
message GreyTipWrapper {
uint32 subTypeId = 1;
uint32 groupCode = 4;
uint32 subTypeIdMinusOne = 13;
bytes rest = 44;
}

View File

@@ -0,0 +1,18 @@
syntax = "proto3";
package SysMessage;
message likeDetail {
string txt = 1;
int64 uin = 3;
string nickname = 5;
}
message likeMsg {
int32 times = 1;
int32 time = 2;
likeDetail detail = 3;
}
message profileLikeTip {
likeMsg msg = 14;
}

View File

@@ -0,0 +1,36 @@
syntax = 'proto3';
package SysMessage;
message SysMessage {
repeated SysMessageHeader header = 1;
repeated SysMessageMsgSpec msgSpec = 2;
SysMessageBodyWrapper bodyWrapper = 3;
}
message SysMessageHeader {
uint32 PeerNumber = 1;
string PeerString = 2;
uint32 Uin = 5;
optional string Uid = 6;
}
message SysMessageMsgSpec {
uint32 msgType = 1;
uint32 subType = 2;
uint32 subSubType = 3;
uint32 msgSeq = 5;
uint32 time = 6;
uint64 msgId = 12;
uint32 other = 13;
}
message SysMessageBodyWrapper {
bytes wrappedBody = 2;
// Find the first [08], or ignore the first 7 bytes?
// And it becomes another ProtoBuf message.
}
message KeyValuePair {
string key = 1;
string value = 2;
}

View File

@@ -1,13 +1,11 @@
import { ConfigBase } from "@/common/utils/ConfigBase";
import { ConfigBase } from '@/common/config-base';
import napCatDefaultConfig from '@/core/external/napcat.json';
import { NapCatCore } from '@/core';
// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging
export type NapCatConfig = typeof napCatDefaultConfig;
// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging
export class NapCatConfigLoader extends ConfigBase<NapCatConfig> {
constructor(coreContext: NapCatCore, configPath: string) {
super('napcat', coreContext, configPath);
constructor(core: NapCatCore, configPath: string) {
super('napcat', core, configPath);
}
}

View File

@@ -1,5 +1,5 @@
import { LogWrapper } from '@/common/utils/log';
import { RequestUtil } from '@/common/utils/request';
import { LogWrapper } from '@/common/log';
import { RequestUtil } from '@/common/request';
interface ServerRkeyData {
group_rkey: string;
@@ -8,7 +8,7 @@ interface ServerRkeyData {
}
export class RkeyManager {
serverUrl: string = '';
serverUrl: string[] = [];
logger: LogWrapper;
private rkeyData: ServerRkeyData = {
group_rkey: '',
@@ -16,7 +16,7 @@ export class RkeyManager {
expired_time: 0,
};
constructor(serverUrl: string, logger: LogWrapper) {
constructor(serverUrl: string[], logger: LogWrapper) {
this.logger = logger;
this.serverUrl = serverUrl;
}
@@ -40,6 +40,13 @@ export class RkeyManager {
async refreshRkey(): Promise<any> {
//刷新rkey
this.rkeyData = await RequestUtil.HttpGetJson<ServerRkeyData>(this.serverUrl, 'GET');
for (const url of this.serverUrl) {
try {
this.rkeyData = await RequestUtil.HttpGetJson<ServerRkeyData>(url, 'GET');
} catch (e) {
this.logger.logError(`[Rkey] Get Rkey ${url} Error `, e);
}
}
}
}

View File

@@ -1,5 +1,332 @@
export * from './core';
import {
NTQQFileApi,
NTQQFriendApi,
NTQQGroupApi,
NTQQMsgApi,
NTQQSystemApi,
NTQQUserApi,
NTQQWebApi,
} from '@/core/apis';
import { NTQQCollectionApi } from '@/core/apis/collection';
import {
NodeIQQNTWrapperSession,
NodeQQNTWrapperUtil,
PlatformType,
VendorType,
WrapperNodeApi,
WrapperSessionInitConfig,
} from '@/core/wrapper';
import { LogLevel, LogWrapper } from '@/common/log';
import { NodeIKernelLoginService } from '@/core/services';
import { QQBasicInfoWrapper } from '@/common/qq-basic-info';
import { NapCatPathWrapper } from '@/common/path';
import path from 'node:path';
import fs from 'node:fs';
import { getMachineId, hostname, systemName, systemVersion } from '@/common/system';
import { NTEventWrapper } from '@/common/event';
import { DataSource, GroupMember, KickedOffLineInfo, SelfInfo, SelfStatusInfo } from '@/core/entities';
import { NapCatConfigLoader } from '@/core/helper/config';
import os from 'node:os';
import { NodeIKernelGroupListener, NodeIKernelMsgListener, NodeIKernelProfileListener } from '@/core/listeners';
import { proxiedListenerOf } from '@/common/proxy-handler';
export * from './wrapper';
export * from './entities';
export * from './services';
export * from './listeners';
export enum NapCatCoreWorkingEnv {
Unknown = 0,
Shell = 1,
Framework = 2,
}
export function loadQQWrapper(QQVersion: string): WrapperNodeApi {
let appPath;
if (os.platform() === 'darwin') {
appPath = path.resolve(path.dirname(process.execPath), '../Resources/app');
} else if (os.platform() === 'linux') {
appPath = path.resolve(path.dirname(process.execPath), './resources/app');
} else {
appPath = path.resolve(path.dirname(process.execPath), `./versions/${QQVersion}/`);
}
let wrapperNodePath = path.resolve(appPath, 'wrapper.node');
if (!fs.existsSync(wrapperNodePath)) {
wrapperNodePath = path.join(appPath, `./resources/app/wrapper.node`);
}
//老版本兼容 未来去掉
if (!fs.existsSync(wrapperNodePath)) {
wrapperNodePath = path.join(path.dirname(process.execPath), `./resources/app/versions/${QQVersion}/wrapper.node`);
}
const nativemodule: any = { exports: {} };
process.dlopen(nativemodule, wrapperNodePath);
return nativemodule.exports;
}
export class NapCatCore {
readonly context: InstanceContext;
readonly apis: StableNTApiWrapper;
readonly eventWrapper: NTEventWrapper;
// readonly eventChannel: NTEventChannel;
NapCatDataPath: string;
NapCatTempPath: string;
// runtime info, not readonly
selfInfo: SelfInfo;
util: NodeQQNTWrapperUtil;
configLoader: NapCatConfigLoader;
// 通过构造器递过去的 runtime info 应该尽量少
constructor(context: InstanceContext, selfInfo: SelfInfo) {
this.selfInfo = selfInfo;
this.context = context;
this.util = this.context.wrapper.NodeQQNTWrapperUtil;
this.eventWrapper = new NTEventWrapper(context.session);
this.apis = {
FileApi: new NTQQFileApi(this.context, this),
SystemApi: new NTQQSystemApi(this.context, this),
CollectionApi: new NTQQCollectionApi(this.context, this),
WebApi: new NTQQWebApi(this.context, this),
FriendApi: new NTQQFriendApi(this.context, this),
MsgApi: new NTQQMsgApi(this.context, this),
UserApi: new NTQQUserApi(this.context, this),
GroupApi: new NTQQGroupApi(this.context, this),
};
this.configLoader = new NapCatConfigLoader(this, this.context.pathWrapper.configPath);
this.NapCatDataPath = path.join(this.dataPath, 'NapCat');
fs.mkdirSync(this.NapCatDataPath, { recursive: true });
this.NapCatTempPath = path.join(this.NapCatDataPath, 'temp');
// 创建临时目录
if (!fs.existsSync(this.NapCatTempPath)) {
fs.mkdirSync(this.NapCatTempPath, { recursive: true });
}
this.initNapCatCoreListeners().then().catch(this.context.logger.logError);
this.context.logger.setFileLogEnabled(
this.configLoader.configData.fileLog,
);
this.context.logger.setConsoleLogEnabled(
this.configLoader.configData.consoleLog,
);
this.context.logger.setFileAndConsoleLogLevel(
this.configLoader.configData.fileLogLevel as LogLevel,
this.configLoader.configData.consoleLogLevel as LogLevel,
);
}
get dataPath(): string {
let result = this.context.wrapper.NodeQQNTWrapperUtil.getNTUserDataInfoConfig();
if (!result) {
result = path.resolve(os.homedir(), './.config/QQ');
fs.mkdirSync(result, { recursive: true });
}
return result;
}
// Renamed from 'InitDataListener'
async initNapCatCoreListeners() {
const msgListener = new NodeIKernelMsgListener();
msgListener.onKickedOffLine = (Info: KickedOffLineInfo) => {
// 下线通知
this.context.logger.logError('[KickedOffLine] [' + Info.tipsTitle + '] ' + Info.tipsDesc);
this.selfInfo.online = false;
};
msgListener.onRecvMsg = (msgs) => {
msgs.forEach(msg => this.context.logger.logMessage(msg, this.selfInfo));
};
msgListener.onAddSendMsg = (msg) => {
this.context.logger.logMessage(msg, this.selfInfo);
};
//await sleep(2500);
this.context.session.getMsgService().addKernelMsgListener(
proxiedListenerOf(msgListener, this.context.logger) as any,
);
const profileListener = new NodeIKernelProfileListener();
profileListener.onProfileDetailInfoChanged = (profile) => {
if (profile.uid === this.selfInfo.uid) {
Object.assign(this.selfInfo, profile);
}
};
profileListener.onSelfStatusChanged = (Info: SelfStatusInfo) => {
if (Info.status == 20) {
this.selfInfo.online = false;
this.context.logger.log("账号状态变更为离线");
} else {
this.selfInfo.online = true;
}
};
this.context.session.getProfileService().addKernelProfileListener(
proxiedListenerOf(profileListener, this.context.logger),
);
// 群相关
const groupListener = new NodeIKernelGroupListener();
groupListener.onGroupListUpdate = (updateType, groupList) => {
// console.log("onGroupListUpdate", updateType, groupList)
groupList.map(g => {
const existGroup = this.apis.GroupApi.groupCache.get(g.groupCode);
//群成员数量变化 应该刷新缓存
if (existGroup && g.memberCount === existGroup.memberCount) {
Object.assign(existGroup, g);
} else {
this.apis.GroupApi.groupCache.set(g.groupCode, g);
// 获取群成员
}
const sceneId = this.context.session.getGroupService().createMemberListScene(g.groupCode, 'groupMemberList_MainWindow');
this.context.session.getGroupService().getNextMemberList(sceneId, undefined, 3000).then( /* r => {
// console.log(`get group ${g.groupCode} members`, r);
// r.result.infos.forEach(member => {
// });
// groupMembers.set(g.groupCode, r.result.infos);
} */);
this.context.session.getGroupService().destroyMemberListScene(sceneId);
});
};
groupListener.onMemberListChange = (arg) => {
// todo: 应该加一个内部自己维护的成员变动callback用于判断成员变化通知
const groupCode = arg.sceneId.split('_')[0];
if (this.apis.GroupApi.groupMemberCache.has(groupCode)) {
const existMembers = this.apis.GroupApi.groupMemberCache.get(groupCode)!;
arg.infos.forEach((member, uid) => {
//console.log('onMemberListChange', member);
const existMember = existMembers.get(uid);
if (existMember) {
Object.assign(existMember, member);
} else {
existMembers!.set(uid, member);
}
//移除成员
if (member.isDelete) {
existMembers.delete(uid);
}
});
} else {
this.apis.GroupApi.groupMemberCache.set(groupCode, arg.infos);
}
};
groupListener.onMemberInfoChange = (groupCode, dataSource, members) => {
if (dataSource === DataSource.LOCAL && members.get(this.selfInfo.uid)?.isDelete) {
// 自身退群或者被踢退群 5s用于Api操作 之后不再出现
setTimeout(() => {
this.apis.GroupApi.groupCache.delete(groupCode);
}, 5000);
}
const existMembers = this.apis.GroupApi.groupMemberCache.get(groupCode);
if (existMembers) {
members.forEach((member, uid) => {
const existMember = existMembers.get(uid);
if (existMember) {
// 检查管理变动
member.isChangeRole = this.checkAdminEvent(groupCode, member, existMember);
// 更新成员信息
Object.assign(existMember, member);
} else {
existMembers.set(uid, member);
}
//移除成员
if (member.isDelete) {
existMembers.delete(uid);
}
});
} else {
this.apis.GroupApi.groupMemberCache.set(groupCode, members);
}
};
this.context.session.getGroupService().addKernelGroupListener(
proxiedListenerOf(groupListener, this.context.logger) as any,
);
}
checkAdminEvent(groupCode: string, memberNew: GroupMember, memberOld: GroupMember | undefined): boolean {
if (memberNew.role !== memberOld?.role) {
this.context.logger.logDebug(`${groupCode} ${memberNew.nick} 角色变更为 ${memberNew.role === 3 ? '管理员' : '群员'}`);
return true;
}
return false;
}
}
export async function genSessionConfig(QQVersionAppid: string, QQVersion: string, selfUin: string, selfUid: string, account_path: string): Promise<WrapperSessionInitConfig> {
const downloadPath = path.join(account_path, 'NapCat', 'temp');
fs.mkdirSync(downloadPath, { recursive: true });
const guid: string = await getMachineId();//26702 支持JS获取guid值 在LoginService中获取 TODO mlikiow a
//os.platform()
let systemPlatform = PlatformType.KWINDOWS;
switch (os.platform()) {
case 'win32':
systemPlatform = PlatformType.KWINDOWS;
break;
case 'darwin':
systemPlatform = PlatformType.KMAC;
break;
case 'linux':
systemPlatform = PlatformType.KLINUX;
break;
}
return {
selfUin,
selfUid,
desktopPathConfig: {
account_path, // 可以通过NodeQQNTWrapperUtil().getNTUserDataInfoConfig()获取
},
clientVer: QQVersion,
a2: '',
d2: '',
d2Key: '',
machineId: '',
platform: systemPlatform, // 3是Windows?
platVer: systemVersion, // 系统版本号, 应该可以固定
appid: QQVersionAppid,
rdeliveryConfig: {
appKey: '',
systemId: 0,
appId: '',
logicEnvironment: '',
platform: systemPlatform,
language: '',
sdkVersion: '',
userId: '',
appVersion: '',
osVersion: '',
bundleId: '',
serverUrl: '',
fixedAfterHitKeys: [''],
},
defaultFileDownloadPath: downloadPath,
deviceInfo: {
guid,
buildVer: QQVersion,
localId: 2052,
devName: hostname,
devType: systemName,
vendorName: '',
osVer: systemVersion,
vendorOsName: systemName,
setMute: false,
vendorType: VendorType.KNOSETONIOS,
},
deviceConfig: '{"appearance":{"isSplitViewMode":true},"msg":{}}',
};
}
export interface InstanceContext {
readonly workingEnv: NapCatCoreWorkingEnv;
readonly wrapper: WrapperNodeApi;
readonly session: NodeIQQNTWrapperSession;
readonly logger: LogWrapper;
readonly loginService: NodeIKernelLoginService;
readonly basicInfoWrapper: QQBasicInfoWrapper;
readonly pathWrapper: NapCatPathWrapper;
}
export interface StableNTApiWrapper {
FileApi: NTQQFileApi,
SystemApi: NTQQSystemApi,
CollectionApi: NTQQCollectionApi,
WebApi: NTQQWebApi,
FriendApi: NTQQFriendApi,
MsgApi: NTQQMsgApi,
UserApi: NTQQUserApi,
GroupApi: NTQQGroupApi
}

View File

@@ -1,53 +1,9 @@
import { BuddyCategoryType, FriendRequestNotify } from '@/core/entities';
export type OnBuddyChangeParams = BuddyCategoryType[]
export type OnBuddyChangeParams = BuddyCategoryType[];
interface IBuddyListener {
onBuddyListChangedV2(arg: unknown): void,//V2版本 还没兼容
onBuddyListChange(arg: OnBuddyChangeParams): void,
onBuddyInfoChange(arg: unknown): void,
onBuddyDetailInfoChange(arg: unknown): void,
onNickUpdated(arg: unknown): void,
onBuddyRemarkUpdated(arg: unknown): void,
onAvatarUrlUpdated(arg: unknown): void,
onBuddyReqChange(arg: FriendRequestNotify): void,
onBuddyReqUnreadCntChange(arg: unknown): void,
onCheckBuddySettingResult(arg: unknown): void,
onAddBuddyNeedVerify(arg: unknown): void,
onSmartInfos(arg: unknown): void,
onSpacePermissionInfos(arg: unknown): void,
onDoubtBuddyReqChange(arg: unknown): void,
onDoubtBuddyReqUnreadNumChange(arg: unknown): void,
onBlockChanged(arg: unknown): void,
onAddMeSettingChanged(arg: unknown): void,
onDelBatchBuddyInfos(arg: unknown): void
}
export interface NodeIKernelBuddyListener extends IBuddyListener {
// eslint-disable-next-line @typescript-eslint/no-misused-new
new(listener: IBuddyListener): NodeIKernelBuddyListener;
}
export class BuddyListener implements IBuddyListener {
export class NodeIKernelBuddyListener {
onBuddyListChangedV2(arg: unknown): void {
//throw new Error('Method not implemented.');
}
onAddBuddyNeedVerify(arg: unknown) {

View File

@@ -1,22 +1,13 @@
export interface IKernelFileAssistantListener {
onFileStatusChanged(...args: unknown[]): unknown;
onSessionListChanged(...args: unknown[]): unknown;
onSessionChanged(...args: unknown[]): unknown;
onFileListChanged(...args: unknown[]): unknown;
onFileSearch(...args: unknown[]): unknown;
}
export interface NodeIKernelFileAssistantListener extends IKernelFileAssistantListener {
// eslint-disable-next-line @typescript-eslint/no-misused-new
new(adapter: IKernelFileAssistantListener): NodeIKernelFileAssistantListener;
}
export class KernelFileAssistantListener implements IKernelFileAssistantListener {
onFileStatusChanged(...args: unknown[]) {
export class NodeIKernelFileAssistantListener {
onFileStatusChanged(fileStatus: {
id: string,
fileStatus: number,
fileProgress: `${number}`,
fileSize: `${number}`,
fileSpeed: number,
thumbPath: string | null,
filePath: string | null,
}) {
}
onSessionListChanged(...args: unknown[]) {
@@ -28,6 +19,42 @@ export class KernelFileAssistantListener implements IKernelFileAssistantListener
onFileListChanged(...args: unknown[]) {
}
onFileSearch(...args: unknown[]) {
onFileSearch(searchResult: SearchResultWrapper) {
}
}
export type SearchResultWrapper = {
searchId: number,
resultId: number,
hasMore: boolean,
resultItems: SearchResultItem[],
};
export type SearchResultItem = {
id: string,
fileName: string,
fileNameHits: string[],
fileStatus: number,
fileSize: string,
isSend: boolean,
source: number,
fileTime: string,
expTime: string,
session: {
context: null,
uid: string,
nick: string,
remark: string,
memberCard: string,
groupCode: string,
groupName: string,
groupRemark: string,
count: number,
},
thumbPath: string,
filePath: string,
msgId: string,
chatType: number,
peerUid: string,
fileType: number,
};

View File

@@ -1,68 +1,7 @@
import { Group, GroupListUpdateType, GroupMember, GroupNotify } from '@/core/entities';
import { DataSource, Group, GroupListUpdateType, GroupMember, GroupNotify } from '@/core/entities';
interface IGroupListener {
onGroupListUpdate(updateType: GroupListUpdateType, groupList: Group[]): void;
onGroupExtListUpdate(...args: unknown[]): void;
onGroupSingleScreenNotifies(doubt: boolean, seq: string, notifies: GroupNotify[]): void;
onGroupNotifiesUpdated(dboubt: boolean, notifies: GroupNotify[]): void;
onGroupNotifiesUnreadCountUpdated(...args: unknown[]): void;
onGroupDetailInfoChange(...args: unknown[]): void;
onGroupAllInfoChange(...args: unknown[]): void;
onGroupsMsgMaskResult(...args: unknown[]): void;
onGroupConfMemberChange(...args: unknown[]): void;
onGroupBulletinChange(...args: unknown[]): void;
onGetGroupBulletinListResult(...args: unknown[]): void;
onMemberListChange(arg: {
sceneId: string,
ids: string[],
infos: Map<string, GroupMember>,
finish: boolean,
hasRobot: boolean
}): void;
onMemberInfoChange(groupCode: string, changeType: number, members: Map<string, GroupMember>): void;
onSearchMemberChange(...args: unknown[]): void;
onGroupBulletinRichMediaDownloadComplete(...args: unknown[]): void;
onGroupBulletinRichMediaProgressUpdate(...args: unknown[]): void;
onGroupStatisticInfoChange(...args: unknown[]): void;
onJoinGroupNotify(...args: unknown[]): void;
onShutUpMemberListChanged(...args: unknown[]): void;
onGroupBulletinRemindNotify(...args: unknown[]): void;
onGroupFirstBulletinNotify(...args: unknown[]): void;
onJoinGroupNoVerifyFlag(...args: unknown[]): void;
onGroupArkInviteStateResult(...args: unknown[]): void;
// 发现于Win 9.9.9 23159
onGroupMemberLevelInfoChange(...args: unknown[]): void;
}
export interface NodeIKernelGroupListener extends IGroupListener {
// eslint-disable-next-line @typescript-eslint/no-misused-new
new(listener: IGroupListener): NodeIKernelGroupListener;
}
export class GroupListener implements IGroupListener {
export class NodeIKernelGroupListener {
onGroupListInited(listEmpty: boolean): void { }
// 发现于Win 9.9.9 23159
onGroupMemberLevelInfoChange(...args: unknown[]): void {
@@ -125,7 +64,7 @@ export class GroupListener implements IGroupListener {
onJoinGroupNoVerifyFlag(...args: unknown[]) {
}
onMemberInfoChange(groupCode: string, changeType: number, members: Map<string, GroupMember>) {
onMemberInfoChange(groupCode: string, dateSource: DataSource, members: Map<string, GroupMember>) {
}
onMemberListChange(arg: {
@@ -142,102 +81,4 @@ export class GroupListener implements IGroupListener {
onShutUpMemberListChanged(...args: unknown[]) {
}
}
export class DebugGroupListener implements IGroupListener {
onGroupMemberLevelInfoChange(...args: unknown[]): void {
console.log('onGroupMemberLevelInfoChange:', ...args);
}
onGetGroupBulletinListResult(...args: unknown[]) {
console.log('onGetGroupBulletinListResult:', ...args);
}
onGroupAllInfoChange(...args: unknown[]) {
console.log('onGroupAllInfoChange:', ...args);
}
onGroupBulletinChange(...args: unknown[]) {
console.log('onGroupBulletinChange:', ...args);
}
onGroupBulletinRemindNotify(...args: unknown[]) {
console.log('onGroupBulletinRemindNotify:', ...args);
}
onGroupArkInviteStateResult(...args: unknown[]) {
console.log('onGroupArkInviteStateResult:', ...args);
}
onGroupBulletinRichMediaDownloadComplete(...args: unknown[]) {
console.log('onGroupBulletinRichMediaDownloadComplete:', ...args);
}
onGroupConfMemberChange(...args: unknown[]) {
console.log('onGroupConfMemberChange:', ...args);
}
onGroupDetailInfoChange(...args: unknown[]) {
console.log('onGroupDetailInfoChange:', ...args);
}
onGroupExtListUpdate(...args: unknown[]) {
console.log('onGroupExtListUpdate:', ...args);
}
onGroupFirstBulletinNotify(...args: unknown[]) {
console.log('onGroupFirstBulletinNotify:', ...args);
}
onGroupListUpdate(...args: unknown[]) {
console.log('onGroupListUpdate:', ...args);
}
onGroupNotifiesUpdated(...args: unknown[]) {
console.log('onGroupNotifiesUpdated:', ...args);
}
onGroupBulletinRichMediaProgressUpdate(...args: unknown[]) {
console.log('onGroupBulletinRichMediaProgressUpdate:', ...args);
}
onGroupNotifiesUnreadCountUpdated(...args: unknown[]) {
console.log('onGroupNotifiesUnreadCountUpdated:', ...args);
}
onGroupSingleScreenNotifies(doubt: boolean, seq: string, notifies: GroupNotify[]) {
console.log('onGroupSingleScreenNotifies:');
}
onGroupsMsgMaskResult(...args: unknown[]) {
console.log('onGroupsMsgMaskResult:', ...args);
}
onGroupStatisticInfoChange(...args: unknown[]) {
console.log('onGroupStatisticInfoChange:', ...args);
}
onJoinGroupNotify(...args: unknown[]) {
console.log('onJoinGroupNotify:', ...args);
}
onJoinGroupNoVerifyFlag(...args: unknown[]) {
console.log('onJoinGroupNoVerifyFlag:', ...args);
}
onMemberInfoChange(groupCode: string, changeType: number, members: Map<string, GroupMember>) {
console.log('onMemberInfoChange:', groupCode, changeType, members);
}
onMemberListChange(...args: unknown[]) {
console.log('onMemberListChange:', ...args);
}
onSearchMemberChange(...args: unknown[]) {
console.log('onSearchMemberChange:', ...args);
}
onShutUpMemberListChanged(...args: unknown[]) {
console.log('onShutUpMemberListChanged:', ...args);
}
}
}

View File

@@ -1,45 +1,4 @@
export interface IKernelLoginListener {
onLoginConnected(...args: any[]): void;
onLoginDisConnected(...args: any[]): void;
onLoginConnecting(...args: any[]): void;
onQRCodeGetPicture(...args: any[]): void;
onQRCodeLoginPollingStarted(...args: any[]): void;
onQRCodeSessionUserScaned(...args: any[]): void;
onQRCodeLoginSucceed(...args: any[]): void;
onQRCodeSessionFailed(...args: any[]): void;
onLoginFailed(...args: any[]): void;
onLogoutSucceed(...args: any[]): void;
onLogoutFailed(...args: any[]): void;
onUserLoggedIn(...args: any[]): void;
onQRCodeSessionQuickLoginFailed(...args: any[]): void;
onPasswordLoginFailed(...args: any[]): void;
OnConfirmUnusualDeviceFailed(...args: any[]): void;
onQQLoginNumLimited(...args: any[]): void;
onLoginState(...args: any[]): void;
}
export interface NodeIKernelLoginListener {
// eslint-disable-next-line @typescript-eslint/no-misused-new
new(listener: IKernelLoginListener): NodeIKernelLoginListener;
}
export class LoginListener implements IKernelLoginListener {
export class NodeIKernelLoginListener {
onLoginConnected(...args: any[]): void {
}

View File

@@ -1,4 +1,5 @@
import { ChatType, RawMessage } from '@/core/entities';
import { CommonFileInfo } from '@/core';
export interface OnRichMediaDownloadCompleteParams {
fileModelId: string,
@@ -15,7 +16,7 @@ export interface OnRichMediaDownloadCompleteParams {
totalSize: string,
trasferStatus: number,
step: number,
commonFileInfo: unknown | null,
commonFileInfo?: CommonFileInfo,
fileSrvErrCode: string,
clientMsg: string,
businessId: number,
@@ -23,12 +24,52 @@ export interface OnRichMediaDownloadCompleteParams {
userUsedSpacePerDay: unknown | null
}
export interface onGroupFileInfoUpdateParamType {
export interface GroupFileInfoUpdateParamType {
retCode: number;
retMsg: string;
clientWording: string;
isEnd: boolean;
item: Array<any>;
item: Array<{
peerId: string;
type: number;
folderInfo?: {
folderId: string;
parentFolderId: string;
folderName: string;
createTime: number;
modifyTime: number;
createUin: string;
creatorName: string;
totalFileCount: number;
modifyUin: string;
modifyName: string;
usedSpace: string;
},
fileInfo?: {
fileModelId: string;
fileId: string;
fileName: string;
fileSize: string;
busId: number;
uploadedSize: string;
uploadTime: number;
deadTime: number;
modifyTime: number;
downloadTimes: number;
sha: string;
sha3: string;
md5: string;
uploaderLocalPath: string;
uploaderName: string;
uploaderUin: string;
parentFolderId: string;
localPath: string;
transStatus: number;
transType: number;
elementId: string;
isFolder: boolean;
},
}>;
allFileCount: string;
nextIndex: string;
reqId: string;
@@ -52,199 +93,7 @@ export interface TempOnRecvParams {
}
export interface IKernelMsgListener {
onAddSendMsg(msgRecord: RawMessage): void;
onBroadcastHelperDownloadComplete(broadcastHelperTransNotifyInfo: unknown): void;
onBroadcastHelperProgressUpdate(broadcastHelperTransNotifyInfo: unknown): void;
onChannelFreqLimitInfoUpdate(contact: unknown, z: unknown, freqLimitInfo: unknown): void;
onContactUnreadCntUpdate(hashMap: unknown): void;
onCustomWithdrawConfigUpdate(customWithdrawConfig: unknown): void;
onDraftUpdate(contact: unknown, arrayList: unknown, j2: unknown): void;
onEmojiDownloadComplete(emojiNotifyInfo: unknown): void;
onEmojiResourceUpdate(emojiResourceInfo: unknown): void;
onFeedEventUpdate(firstViewDirectMsgNotifyInfo: unknown): void;
onFileMsgCome(arrayList: unknown): void;
onFirstViewDirectMsgUpdate(firstViewDirectMsgNotifyInfo: unknown): void;
onFirstViewGroupGuildMapping(arrayList: unknown): void;
onGrabPasswordRedBag(i2: unknown, str: unknown, i3: unknown, recvdOrder: unknown, msgRecord: unknown): void;
onGroupFileInfoAdd(groupItem: unknown): void;
onGroupFileInfoUpdate(groupFileListResult: onGroupFileInfoUpdateParamType): void;
onGroupGuildUpdate(groupGuildNotifyInfo: unknown): void;
onGroupTransferInfoAdd(groupItem: unknown): void;
onGroupTransferInfoUpdate(groupFileListResult: unknown): void;
onGuildInteractiveUpdate(guildInteractiveNotificationItem: unknown): void;
onGuildMsgAbFlagChanged(guildMsgAbFlag: unknown): void;
onGuildNotificationAbstractUpdate(guildNotificationAbstractInfo: unknown): void;
onHitCsRelatedEmojiResult(downloadRelateEmojiResultInfo: unknown): void;
onHitEmojiKeywordResult(hitRelatedEmojiWordsResult: unknown): void;
onHitRelatedEmojiResult(relatedWordEmojiInfo: unknown): void;
onImportOldDbProgressUpdate(importOldDbMsgNotifyInfo: unknown): void;
onInputStatusPush(inputStatusInfo: {
chatType: number;
eventType: number;
fromUin: string;
interval: string;
showTime: string;
statusText: string;
timestamp: string;
toUin: string;
}): void;
onKickedOffLine(kickedInfo: unknown): void;
onLineDev(arrayList: unknown): void;
onLogLevelChanged(j2: unknown): void;
onMsgAbstractUpdate(arrayList: unknown): void;
onMsgBoxChanged(arrayList: unknown): void;
onMsgDelete(contact: unknown, arrayList: unknown): void;
onMsgEventListUpdate(hashMap: unknown): void;
onMsgInfoListAdd(arrayList: unknown): void;
onMsgInfoListUpdate(msgList: RawMessage[]): void;
onMsgQRCodeStatusChanged(i2: unknown): void;
onMsgRecall(i2: unknown, str: unknown, j2: unknown): void;
onMsgSecurityNotify(msgRecord: unknown): void;
onMsgSettingUpdate(msgSetting: unknown): void;
onNtFirstViewMsgSyncEnd(): void;
onNtMsgSyncEnd(): void;
onNtMsgSyncStart(): void;
onReadFeedEventUpdate(firstViewDirectMsgNotifyInfo: unknown): void;
onRecvGroupGuildFlag(i2: unknown): void;
onRecvMsg(...arrayList: unknown[]): void;
onRecvMsgSvrRspTransInfo(j2: unknown, contact: unknown, i2: unknown, i3: unknown, str: unknown, bArr: unknown): void;
onRecvOnlineFileMsg(arrayList: unknown): void;
onRecvS2CMsg(arrayList: unknown): void;
onRecvSysMsg(arrayList: unknown): void;
onRecvUDCFlag(i2: unknown): void;
onRichMediaDownloadComplete(fileTransNotifyInfo: OnRichMediaDownloadCompleteParams): void;
onRichMediaProgerssUpdate(fileTransNotifyInfo: unknown): void;
onRichMediaUploadComplete(fileTransNotifyInfo: unknown): void;
onSearchGroupFileInfoUpdate(searchGroupFileResult:
{
result: {
retCode: number,
retMsg: string,
clientWording: string
},
syncCookie: string,
totalMatchCount: number,
ownerMatchCount: number,
isEnd: boolean,
reqId: number,
item: Array<{
groupCode: string,
groupName: string,
uploaderUin: string,
uploaderName: string,
matchUin: string,
matchWords: Array<unknown>,
fileNameHits: Array<{
start: number,
end: number
}>,
fileModelId: string,
fileId: string,
fileName: string,
fileSize: string,
busId: number,
uploadTime: number,
modifyTime: number,
deadTime: number,
downloadTimes: number,
localPath: string
}>
}): void;
onSendMsgError(j2: unknown, contact: unknown, i2: unknown, str: unknown): void;
onSysMsgNotification(i2: unknown, j2: unknown, j3: unknown, arrayList: unknown): void;
onTempChatInfoUpdate(tempChatInfo: TempOnRecvParams): void;
onUnreadCntAfterFirstView(hashMap: unknown): void;
onUnreadCntUpdate(hashMap: unknown): void;
onUserChannelTabStatusChanged(z: unknown): void;
onUserOnlineStatusChanged(z: unknown): void;
onUserTabStatusChanged(arrayList: unknown): void;
onlineStatusBigIconDownloadPush(i2: unknown, j2: unknown, str: unknown): void;
onlineStatusSmallIconDownloadPush(i2: unknown, j2: unknown, str: unknown): void;
// 第一次发现于Linux
onUserSecQualityChanged(...args: unknown[]): void;
onMsgWithRichLinkInfoUpdate(...args: unknown[]): void;
onRedTouchChanged(...args: unknown[]): void;
// 第一次发现于Win 9.9.9 23159
onBroadcastHelperProgerssUpdate(...args: unknown[]): void;
}
export interface NodeIKernelMsgListener extends IKernelMsgListener {
// eslint-disable-next-line @typescript-eslint/no-misused-new
new(listener: IKernelMsgListener): NodeIKernelMsgListener;
}
export class MsgListener implements IKernelMsgListener {
export class NodeIKernelMsgListener {
onAddSendMsg(msgRecord: RawMessage) {
}
@@ -305,7 +154,7 @@ export class MsgListener implements IKernelMsgListener {
}
onGroupFileInfoUpdate(groupFileListResult: onGroupFileInfoUpdateParamType) {
onGroupFileInfoUpdate(groupFileListResult: GroupFileInfoUpdateParamType) {
}
@@ -451,7 +300,7 @@ export class MsgListener implements IKernelMsgListener {
}
onRecvSysMsg(arrayList: unknown) {
onRecvSysMsg(arrayList: Array<number>) {
}

View File

@@ -1,25 +1,6 @@
import { User, UserDetailInfoListenerArg } from '@/core/entities';
interface IProfileListener {
onProfileSimpleChanged(...args: unknown[]): void;
onUserDetailInfoChanged(arg: UserDetailInfoListenerArg): void;
onProfileDetailInfoChanged(profile: User): void;
onStatusUpdate(...args: unknown[]): void;
onSelfStatusChanged(...args: unknown[]): void;
onStrangerRemarkChanged(...args: unknown[]): void;
}
export interface NodeIKernelProfileListener extends IProfileListener {
// eslint-disable-next-line @typescript-eslint/no-misused-new
new(listener: IProfileListener): NodeIKernelProfileListener;
}
export class ProfileListener implements IProfileListener {
export class NodeIKernelProfileListener {
onUserDetailInfoChanged(arg: UserDetailInfoListenerArg): void {
}
@@ -43,4 +24,48 @@ export class ProfileListener implements IProfileListener {
onStrangerRemarkChanged(...args: unknown[]) {
}
onMemberListChange(...args: unknown[]) {
}
onMemberInfoChange(...args: unknown[]) {
}
onGroupListUpdate(...args: unknown[]) {
}
onGroupAllInfoChange(...args: unknown[]) {
}
onGroupDetailInfoChange(...args: unknown[]) {
}
onGroupConfMemberChange(...args: unknown[]) {
}
onGroupExtListUpdate(...args: unknown[]) {
}
onGroupNotifiesUpdated(...args: unknown[]) {
}
onGroupNotifiesUnreadCountUpdated(...args: unknown[]) {
}
onGroupMemberLevelInfoChange(...args: unknown[]) {
}
onGroupBulletinChange(...args: unknown[]) {
}
}

View File

@@ -1,28 +1,9 @@
interface IKernelRecentContactListener {
onDeletedContactsNotify(...args: unknown[]): unknown;
onRecentContactNotification(...args: unknown[]): unknown;
onMsgUnreadCountUpdate(...args: unknown[]): unknown;
onGuildDisplayRecentContactListChanged(...args: unknown[]): unknown;
onRecentContactListChanged(...args: unknown[]): unknown;
onRecentContactListChangedVer2(...args: unknown[]): unknown;
}
export interface NodeIKernelRecentContactListener extends IKernelRecentContactListener {
// eslint-disable-next-line @typescript-eslint/no-misused-new
new(listener: IKernelRecentContactListener): NodeIKernelRecentContactListener;
}
export class KernelRecentContactListener implements IKernelRecentContactListener {
export class NodeIKernelRecentContactListener {
onDeletedContactsNotify(...args: unknown[]) {
}
onRecentContactNotification(...args: unknown[]) {
onRecentContactNotification(msgList: any, arg0: { msgListUnreadCnt: string }, arg1: number) {
}

View File

@@ -1,17 +1,4 @@
export interface IKernelRobotListener {
onRobotFriendListChanged(...args: unknown[]): void;
onRobotListChanged(...args: unknown[]): void;
onRobotProfileChanged(...args: unknown[]): void;
}
export interface NodeIKernelRobotListener extends IKernelRobotListener {
// eslint-disable-next-line @typescript-eslint/no-misused-new
new(adapter: IKernelRobotListener): NodeIKernelRobotListener;
}
export class KernelRobotListener implements IKernelRobotListener {
export class NodeIKernelRobotListener {
onRobotFriendListChanged(...args: unknown[]) {
}

View File

@@ -0,0 +1,97 @@
import { ChatType } from '@/core';
export interface SearchGroupInfo {
groupCode: string;
ownerUid: string;
groupFlag: number;
groupFlagExt: number;
maxMemberNum: number;
memberNum: number;
groupOption: number;
classExt: number;
groupName: string;
fingerMemo: string;
groupQuestion: string;
certType: number;
shutUpAllTimestamp: number;
shutUpMeTimestamp: number;
groupTypeFlag: number;
privilegeFlag: number;
groupSecLevel: number;
groupFlagExt3: number;
isConfGroup: number;
isModifyConfGroupFace: number;
isModifyConfGroupName: number;
noFigerOpenFlag: number;
noCodeFingerOpenFlag: number;
groupFlagExt4: number;
groupMemo: string;
cmdUinMsgSeq: number;
cmdUinJoinTime: number;
cmdUinUinFlag: number;
cmdUinMsgMask: number;
groupSecLevelInfo: number;
cmdUinPrivilege: number;
cmdUinFlagEx2: number;
appealDeadline: number;
remarkName: string;
isTop: boolean;
richFingerMemo: string;
groupAnswer: string;
joinGroupAuth: string;
isAllowModifyConfGroupName: number;
}
export interface GroupInfo {
groupCode: string;
searchGroupInfo: SearchGroupInfo;
privilege: number;
}
export interface GroupSearchResult {
keyWord: string;
errorCode: number;
groupInfos: GroupInfo[];
penetrate: string;
isEnd: boolean;
nextPos: number;
}
export interface NodeIKernelSearchListener {
onSearchGroupResult(params: GroupSearchResult): void;
onSearchFileKeywordsResult(params: {
searchId: string,
hasMore: boolean,
resultItems: {
chatType: ChatType,
buddyChatInfo: any[],
discussChatInfo: any[],
groupChatInfo: {
groupCode: string,
isConf: boolean,
hasModifyConfGroupFace: boolean,
hasModifyConfGroupName: boolean,
groupName: string,
remark: string
}[],
dataLineChatInfo: any[],
tmpChatInfo: any[],
msgId: string,
msgSeq: string,
msgTime: string,
senderUid: string,
senderNick: string,
senderRemark: string,
senderCard: string,
elemId: string,
elemType: number,
fileSize: string,
filePath: string,
fileName: string,
hits: {
start: number,
end: number
}[]
}[]
}): void;
}

View File

@@ -1,23 +1,4 @@
export interface ISessionListener {
onNTSessionCreate(args: unknown): void;
onGProSessionCreate(args: unknown): void;
onSessionInitComplete(args: unknown): void;
onOpentelemetryInit(args: unknown): void;
onUserOnlineResult(args: unknown): void;
onGetSelfTinyId(args: unknown): void;
}
export interface NodeIKernelSessionListener extends ISessionListener {
// eslint-disable-next-line @typescript-eslint/no-misused-new
new(adapter: ISessionListener): NodeIKernelSessionListener;
}
export class SessionListener implements ISessionListener {
export class NodeIKernelSessionListener {
onNTSessionCreate(args: unknown) {
}

View File

@@ -1,22 +1,4 @@
export interface IStorageCleanListener {
onCleanCacheProgressChanged(args: unknown): void;
onScanCacheProgressChanged(args: unknown): void;
onCleanCacheStorageChanged(args: unknown): void;
onFinishScan(args: unknown): void;
onChatCleanDone(args: unknown): void;
}
export interface NodeIKernelStorageCleanListener extends IStorageCleanListener {
// eslint-disable-next-line @typescript-eslint/no-misused-new
new(adapter: IStorageCleanListener): NodeIKernelStorageCleanListener;
}
export class StorageCleanListener implements IStorageCleanListener {
export class NodeIKernelStorageCleanListener {
onCleanCacheProgressChanged(args: unknown) {
}

View File

@@ -1,10 +1,2 @@
export interface IKernelTicketListener {
}
export interface NodeIKernelTicketListener extends IKernelTicketListener {
// eslint-disable-next-line @typescript-eslint/no-misused-new
new(adapter: IKernelTicketListener): NodeIKernelTicketListener;
}
export class KernelTicketListener implements IKernelTicketListener {
export class NodeIKernelTicketListener {
}

View File

@@ -0,0 +1,5 @@
export class NodeIO3MiscListener {
getOnAmgomDataPiece(...arg: unknown[]) {
}
}

View File

@@ -9,4 +9,32 @@ export * from './NodeIKernelProfileListener';
export * from './NodeIKernelTicketListener';
export * from './NodeIKernelStorageCleanListener';
export * from './NodeIKernelFileAssistantListener';
export * from './NodeIKernelSearchListener';
import type {
NodeIKernelBuddyListener,
NodeIKernelFileAssistantListener,
NodeIKernelGroupListener,
NodeIKernelLoginListener,
NodeIKernelMsgListener,
NodeIKernelProfileListener,
NodeIKernelRobotListener,
NodeIKernelSessionListener,
NodeIKernelStorageCleanListener,
NodeIKernelTicketListener,
} from '.';
import { NodeIKernelSearchListener } from './NodeIKernelSearchListener';
export type ListenerNamingMapping = {
NodeIKernelSessionListener: NodeIKernelSessionListener;
NodeIKernelLoginListener: NodeIKernelLoginListener;
NodeIKernelMsgListener: NodeIKernelMsgListener;
NodeIKernelGroupListener: NodeIKernelGroupListener;
NodeIKernelBuddyListener: NodeIKernelBuddyListener;
NodeIKernelProfileListener: NodeIKernelProfileListener;
NodeIKernelRobotListener: NodeIKernelRobotListener;
NodeIKernelTicketListener: NodeIKernelTicketListener;
NodeIKernelStorageCleanListener: NodeIKernelStorageCleanListener;
NodeIKernelFileAssistantListener: NodeIKernelFileAssistantListener;
NodeIKernelSearchListener: NodeIKernelSearchListener;
};

View File

@@ -0,0 +1,95 @@
import * as pb from 'protobufjs';
// Proto: from src/core/proto/ProfileLike.proto
// Author: Mlikiowa
export interface LikeDetailType {
txt: string;
uin: pb.Long;
nickname: string;
}
export interface LikeMsgType {
times: number;
time: number;
detail: LikeDetailType;
}
export interface profileLikeSubTipType {
msg: LikeMsgType;
}
export interface ProfileLikeTipType {
msgType: number;
subType: number;
content: profileLikeSubTipType;
}
export interface SysMessageHeaderType {
id: string;
timestamp: number;
sender: string;
}
export interface SysMessageMsgSpecType {
msgType: number;
subType: number;
subSubType: number;
msgSeq: number;
time: number;
msgId: pb.Long;
other: number;
}
export interface SysMessageBodyWrapperType {
wrappedBody: Uint8Array;
}
export interface SysMessageType {
header: SysMessageHeaderType[];
msgSpec: SysMessageMsgSpecType[];
bodyWrapper: SysMessageBodyWrapperType;
}
export const SysMessageHeader = new pb.Type("SysMessageHeader")
.add(new pb.Field("PeerNumber", 1, "uint32"))
.add(new pb.Field("PeerString", 2, "string"))
.add(new pb.Field("Uin", 5, "uint32"))
.add(new pb.Field("Uid", 6, "string", "optional"));
export const SysMessageMsgSpec = new pb.Type("SysMessageMsgSpec")
.add(new pb.Field("msgType", 1, "uint32"))
.add(new pb.Field("subType", 2, "uint32"))
.add(new pb.Field("subSubType", 3, "uint32"))
.add(new pb.Field("msgSeq", 5, "uint32"))
.add(new pb.Field("time", 6, "uint32"))
.add(new pb.Field("msgId", 12, "uint64"))
.add(new pb.Field("other", 13, "uint32"));
export const SysMessageBodyWrapper = new pb.Type("SysMessageBodyWrapper")
.add(new pb.Field("wrappedBody", 2, "bytes"));
export const SysMessage = new pb.Type("SysMessage")
.add(SysMessageHeader)
.add(SysMessageMsgSpec)
.add(SysMessageBodyWrapper)
.add(new pb.Field("header", 1, "SysMessageHeader", "repeated"))
.add(new pb.Field("msgSpec", 2, "SysMessageMsgSpec", "repeated"))
.add(new pb.Field("bodyWrapper", 3, "SysMessageBodyWrapper"));
export const likeDetail = new pb.Type("likeDetail")
.add(new pb.Field("txt", 1, "string"))
.add(new pb.Field("uin", 3, "int64"))
.add(new pb.Field("nickname", 5, "string"));
export const likeMsg = new pb.Type("likeMsg")
.add(likeDetail)
.add(new pb.Field("times", 1, "int32"))
.add(new pb.Field("time", 2, "int32"))
.add(new pb.Field("detail", 3, "likeDetail"));
export const profileLikeSubTip = new pb.Type("profileLikeSubTip")
.add(likeMsg)
.add(new pb.Field("msg", 14, "likeMsg"))
export const profileLikeTip = new pb.Type("profileLikeTip")
.add(profileLikeSubTip)
.add(new pb.Field("msgType", 1, "int32"))
.add(new pb.Field("subType", 2, "int32"))
.add(new pb.Field("content", 203, "profileLikeSubTip"));

View File

@@ -1,4 +1,5 @@
export interface NodeIKernelAlbumService {
setAlbumServiceInfo(...args: any[]): unknown;// needs 3 arguments
getMainPage(...args: any[]): unknown;// needs 2 arguments

View File

@@ -1,7 +1,7 @@
export interface NodeIKernelAvatarService {
addAvatarListener(arg: unknown): unknown;
addAvatarListener(listener: unknown): void;
removeAvatarListener(arg: unknown): unknown;
removeAvatarListener(listenerId: number): void;
getAvatarPath(arg1: unknown, arg2: unknown): unknown;

View File

@@ -1,13 +1,8 @@
import { GeneralCallResult } from '@/core/services/common';
import { NodeIKernelBuddyListener } from '@/core/listeners';
export enum BuddyListReqType {
KNOMAL,
KLETTER
}
import { BuddyListReqType } from '../entities/user';
export interface NodeIKernelBuddyService {
// 26702 以上
getBuddyListV2(callFrom: string, reqType: BuddyListReqType): Promise<GeneralCallResult & {
data: Array<{
categoryId: number,
@@ -19,8 +14,7 @@ export interface NodeIKernelBuddyService {
}>
}>;
//26702 以上
getBuddyListFromCache(callFrom: string): Promise<Array<
getBuddyListFromCache(reqType: BuddyListReqType): Promise<Array<
{
categoryId: number,//9999应该跳过 那是兜底数据吧
categorySortId: number,//排序方式
@@ -30,18 +24,13 @@ export interface NodeIKernelBuddyService {
buddyUids: Array<string>//Uids
}>>;
// 以下为原生方法
addKernelBuddyListener(listener: NodeIKernelBuddyListener): number;
getAllBuddyCount(): number;
removeKernelBuddyListener(listener: unknown): void;
removeKernelBuddyListener(listenerId: number): void;
/**
* @deprecated
* @param nocache 使用缓存
*/
getBuddyList(nocache: boolean): Promise<GeneralCallResult>;
//getBuddyList(nocache: boolean): Promise<GeneralCallResult>;
getBuddyNick(uid: number): string;
@@ -61,11 +50,11 @@ export interface NodeIKernelBuddyService {
getBuddyReqUnreadCnt(): number;
getBuddyReq(): unknown;
getBuddyReq(): Promise<GeneralCallResult>;
delBuddyReq(uid: number): void;
clearBuddyReqUnreadCnt(): void;
clearBuddyReqUnreadCnt(): Promise<GeneralCallResult>;
reqToAddFriends(uid: number, msg: string): void;
@@ -125,7 +114,7 @@ export interface NodeIKernelBuddyService {
reportDoubtBuddyReqUnread(): void;
getBuddyRecommendContactArkJson(uid: string, phoneNumber: string): Promise<unknown>;
getBuddyRecommendContactArkJson(uid: string, phoneNumber: string): Promise<GeneralCallResult & { arkMsg: string }>;
isNull(): boolean;
}

View File

@@ -1,9 +1,9 @@
import { GeneralCallResult } from './common';
export interface NodeIKernelCollectionService {
addKernelCollectionListener(...args: any[]): unknown;//needs 1 arguments
addKernelCollectionListener(...args: any[]): void;//needs 1 arguments
removeKernelCollectionListener(...args: any[]): unknown;//needs 1 arguments
removeKernelCollectionListener(listenerId: number): void;
getCollectionItemList(param: {
category: number,
@@ -14,46 +14,46 @@ export interface NodeIKernelCollectionService {
count: number,
searchDown: boolean
}): Promise<GeneralCallResult &
{
collectionSearchList: {
collectionItemList: Array<
{
cid: string,
{
collectionSearchList: {
collectionItemList: Array<
{
cid: string,
type: number,
status: number,
author: {
type: number,
status: number,
author: {
type: number,
numId: string,
strId: string,
groupId: string,
groupName: string,
uid: string
},
bid: number,
category: number,
createTime: string,
collectTime: string,
modifyTime: string,
sequence: string,
shareUrl: string,
customGroupId: number,
securityBeat: boolean,
summary: {
textSummary: unknown,
linkSummary: unknown,
gallerySummary: unknown,
audioSummary: unknown,
videoSummary: unknown,
fileSummary: unknown,
locationSummary: unknown,
richMediaSummary: unknown,
}
}>,
hasMore: boolean,
bottomTimeStamp: string
}
numId: string,
strId: string,
groupId: string,
groupName: string,
uid: string
},
bid: number,
category: number,
createTime: string,
collectTime: string,
modifyTime: string,
sequence: string,
shareUrl: string,
customGroupId: number,
securityBeat: boolean,
summary: {
textSummary: unknown,
linkSummary: unknown,
gallerySummary: unknown,
audioSummary: unknown,
videoSummary: unknown,
fileSummary: unknown,
locationSummary: unknown,
richMediaSummary: unknown,
}
}>,
hasMore: boolean,
bottomTimeStamp: string
}
>;//needs 1 arguments
}
>;
getCollectionContent(...args: any[]): unknown;//needs 5 arguments

View File

@@ -1,7 +1,9 @@
export interface NodeIKernelDbToolsService {
depositDatabase(...args: unknown[]): unknown;
backupDatabase(...args: unknown[]): unknown;
retrieveDatabase(...args: unknown[]): unknown;
}

View File

@@ -1,3 +1,2 @@
export interface NodeIKernelECDHService {
}

View File

@@ -1,5 +1,7 @@
import { NodeIKernelFileAssistantListener } from '@/core';
export interface NodeIKernelFileAssistantService {
addKernelFileAssistantListener(arg1: unknown[]): unknown;
addKernelFileAssistantListener(listener: NodeIKernelFileAssistantListener): unknown;
removeKernelFileAssistantListener(arg1: unknown[]): unknown;
@@ -9,7 +11,7 @@ export interface NodeIKernelFileAssistantService {
getFileSessionList(): unknown;
searchFile(arg1: unknown, arg2: unknown, arg3: unknown): unknown;
searchFile(keywords: string[], params: { resultType: number, pageLimit: number }, resultId: number): number;
resetSearchFileSortType(arg1: unknown, arg2: unknown, arg3: unknown): unknown;
@@ -17,7 +19,7 @@ export interface NodeIKernelFileAssistantService {
cancelSearchFile(arg1: unknown, arg2: unknown, arg3: unknown): unknown;
downloadFile(arg1: unknown[]): unknown;
downloadFile(fileIds: string[]): { result: number, errMsg: string };
forwardFile(arg1: unknown, arg2: unknown, arg3: unknown): unknown;
@@ -32,4 +34,4 @@ export interface NodeIKernelFileAssistantService {
saveAsWithRename(arg1: unknown, arg2: unknown, arg3: unknown): unknown;
isNull(): boolean;
}
}

View File

@@ -1,16 +1,24 @@
import { NodeIKernelGroupListener } from '@/core/listeners/NodeIKernelGroupListener';
import {
GroupExt0xEF0InfoFilter,
GroupExtParam,
GroupInfoSource,
GroupMember,
GroupMemberRole,
GroupNotifyTypes,
GroupNotifyMsgType,
GroupRequestOperateTypes,
KickMemberV2Req,
} from '@/core/entities';
import { GeneralCallResult } from '@/core/services/common';
//高版本的接口不应该随意使用 使用应该严格进行pr审核 同时部分ipc中未出现的接口不要过于依赖 应该做好数据兜底
export interface NodeIKernelGroupService {
getGroupExt0xEF0Info(enableGroupCodes: string[], bannedGroupCodes: string[], filter: GroupExt0xEF0InfoFilter, forceFetch: boolean):
Promise<GeneralCallResult & { result: { groupExtInfos: Map<string, any> } }>;
kickMemberV2(param: KickMemberV2Req): Promise<GeneralCallResult>;
quitGroupV2(param: { groupCode: string; needDeleteLocalMsg: boolean; }): Promise<GeneralCallResult>;
getMemberCommonInfo(Req: {
groupCode: string,
startUin: string,
@@ -30,11 +38,11 @@ export interface NodeIKernelGroupService {
realSpecialTitleFlag: number
}): Promise<unknown>;
//26702
getGroupMemberLevelInfo(groupCode: string): Promise<unknown>;
//26702
getGroupHonorList(groupCodes: Array<string>): unknown;
getGroupInfoForJoinGroup(groupCode: string, needPrivilegeFlag: boolean, serviceType: number): Promise<unknown>;
getGroupHonorList(req: { groupCodes: Array<string> }): Promise<unknown>;
getUinByUids(uins: string[]): Promise<{
errCode: number,
@@ -48,13 +56,10 @@ export interface NodeIKernelGroupService {
uids: Map<string, string>
}>;
//26702(其实更早 但是我不知道)
checkGroupMemberCache(arrayList: Array<string>): Promise<unknown>;
//26702(其实更早 但是我不知道)
getGroupLatestEssenceList(groupCode: string): Promise<unknown>;
//26702(其实更早 但是我不知道)
shareDigest(Req: {
appId: string,
appType: number,
@@ -74,20 +79,17 @@ export interface NodeIKernelGroupService {
}
}): Promise<unknown>;
//26702(其实更早 但是我不知道)
isEssenceMsg(Req: { groupCode: string, msgRandom: number, msgSeq: number }): Promise<unknown>;
//26702(其实更早 但是我不知道)
queryCachedEssenceMsg(Req: { groupCode: string, msgRandom: number, msgSeq: number }): Promise<unknown>;
isEssenceMsg(req: { groupCode: string, msgRandom: number, msgSeq: number }): Promise<unknown>;
//26702(其实更早 但是我不知道)
fetchGroupEssenceList(Req: {
queryCachedEssenceMsg(req: { groupCode: string, msgRandom: number, msgSeq: number }): Promise<unknown>;
fetchGroupEssenceList(req: {
groupCode: string,
pageStart: number,
pageLimit: number
}, Arg: unknown): Promise<unknown>;
//26702
getAllMemberList(groupCode: string, forceFetch: boolean): Promise<{
errCode: number,
errMsg: string,
@@ -96,7 +98,7 @@ export interface NodeIKernelGroupService {
uid: string,
index: number//0
}>,
infos: {},
infos: unknown,
finish: true,
hasRobot: false
}
@@ -106,13 +108,12 @@ export interface NodeIKernelGroupService {
addKernelGroupListener(listener: NodeIKernelGroupListener): number;
removeKernelGroupListener(listenerId: unknown): void;
removeKernelGroupListener(listenerId: number): void;
createMemberListScene(groupCode: string, scene: string): string;
destroyMemberListScene(SceneId: string): void;
//About Arg (a) name: lastId 根据手Q来看为object {index:?(number),uid:string}
getNextMemberList(sceneId: string, a: undefined, num: number): Promise<{
errCode: number, errMsg: string,
result: { ids: string[], infos: Map<string, GroupMember>, finish: boolean, hasRobot: boolean }
@@ -126,8 +127,6 @@ export interface NodeIKernelGroupService {
getMemberInfo(group_id: string, uids: string[], forceFetch: boolean): Promise<GeneralCallResult>;
//getMemberInfo [ '56729xxxx', [ 'u_4Nj08cwW5Hxxxxx' ], true ]
kickMember(groupCode: string, memberUids: string[], refuseForever: boolean, kickReason: string): Promise<void>;
modifyMemberRole(groupCode: string, uid: string, role: GroupMemberRole): void;
@@ -142,7 +141,7 @@ export interface NodeIKernelGroupService {
getGroupExtList(force: boolean): Promise<GeneralCallResult>;
getGroupDetailInfo(groupCode: string): unknown;
getGroupDetailInfo(groupCode: string, groupInfoSource: GroupInfoSource): Promise<unknown>;
getMemberExtInfo(param: GroupExtParam): Promise<unknown>;//req
@@ -180,14 +179,13 @@ export interface NodeIKernelGroupService {
destroyGroup(groupCode: string): void;
//获取单屏群通知列表
getSingleScreenNotifies(force: boolean, start_seq: string, num: number): Promise<GeneralCallResult>;
getSingleScreenNotifies(doubted: boolean, start_seq: string, num: number): Promise<GeneralCallResult>;
clearGroupNotifies(groupCode: string): void;
getGroupNotifiesUnreadCount(unknown: boolean): Promise<GeneralCallResult>;
clearGroupNotifiesUnreadCount(groupCode: string): void;
clearGroupNotifiesUnreadCount(unknown: boolean): void;
operateSysNotify(
doubt: boolean,
@@ -195,7 +193,7 @@ export interface NodeIKernelGroupService {
operateType: GroupRequestOperateTypes, // 2 拒绝
targetMsg: {
seq: string, // 通知序列号
type: GroupNotifyTypes,
type: GroupNotifyMsgType,
groupCode: string,
postscript: string
}
@@ -205,7 +203,7 @@ export interface NodeIKernelGroupService {
getGroupBulletin(groupCode: string): unknown;
deleteGroupBulletin(groupCode: string, seq: string): void;
deleteGroupBulletin(groupCode: string, seq: string, noticeId: string): void;
publishGroupBulletin(groupCode: string, pskey: string, data: any): Promise<GeneralCallResult>;
@@ -246,14 +244,12 @@ export interface NodeIKernelGroupService {
modifyGroupExtInfo(groupCode: string, arg: unknown): void;
//需要提前判断是否存在 高版本新增
addGroupEssence(param: {
groupCode: string
msgRandom: number,
msgSeq: number
}): Promise<unknown>;
//需要提前判断是否存在 高版本新增
removeGroupEssence(param: {
groupCode: string
msgRandom: number,

View File

@@ -9,7 +9,7 @@ export interface LoginInitConfig {
hostName: string;
}
export interface passwordLoginRetType {
export interface PasswordLoginRetType {
result: string,
loginErrorInfo: {
step: number;
@@ -23,7 +23,7 @@ export interface passwordLoginRetType {
}
}
export interface passwordLoginArgType {
export interface PasswordLoginArgType {
uin: string;
passwordMd5: string;//passwMD5
step: number;//猜测是需要二次认证 参数 一次为0
@@ -59,8 +59,9 @@ export interface QuickLoginResult {
}
export interface NodeIKernelLoginService {
// eslint-disable-next-line @typescript-eslint/no-misused-new
new(): NodeIKernelLoginService;
get(): NodeIKernelLoginService;
connect(): boolean;
addKernelLoginListener(listener: NodeIKernelLoginListener): number;
@@ -77,7 +78,7 @@ export interface NodeIKernelLoginService {
quickLoginWithUin(uin: string): Promise<QuickLoginResult>;
passwordLogin(param: passwordLoginArgType): Promise<any>;
passwordLogin(param: PasswordLoginArgType): Promise<any>;
getQRCodePicture(): boolean;
}

View File

@@ -1,7 +1,7 @@
export interface NodeIKernelMsgBackupService {
addKernelMsgBackupListener(...args: any[]): unknown;// needs 1 arguments
addKernelMsgBackupListener(listener: unknown): number;
removeKernelMsgBackupListener(...args: any[]): unknown;// needs 1 arguments
removeKernelMsgBackupListener(listenerId: number): void;
getMsgBackupLocation(...args: any[]): unknown;// needs 0 arguments

View File

@@ -1,31 +1,7 @@
import { ElementType, MessageElement, Peer, RawMessage, SendMessageElement } from '@/core/entities';
import { NodeIKernelMsgListener } from '@/core/listeners/NodeIKernelMsgListener';
import { GeneralCallResult } from '@/core/services/common';
export interface QueryMsgsParams {
chatInfo: Peer,
filterMsgType: [],
filterSendersUid: string[],
filterMsgFromTime: string,
filterMsgToTime: string,
pageLimit: number,
isReverseOrder: boolean,
isIncludeCurrent: boolean
}
export interface TmpChatInfoApi {
errMsg: string;
result: number;
tmpChatInfo?: TmpChatInfo;
}
export interface TmpChatInfo {
chatType: number;
fromNick: string;
groupCode: string;
peerUid: string;
sessionType: number;
sig: string;
}
import { MsgReqType, QueryMsgsParams, TmpChatInfoApi } from '../entities/msg';
export interface NodeIKernelMsgService {
@@ -37,7 +13,7 @@ export interface NodeIKernelMsgService {
recallMsg(peer: Peer, msgIds: string[]): Promise<GeneralCallResult>;
addKernelMsgImportToolListener(arg: Object): unknown;
addKernelMsgImportToolListener(arg: unknown): unknown;
removeKernelMsgListener(args: unknown): unknown;
@@ -51,7 +27,7 @@ export interface NodeIKernelMsgService {
getOnLineDev(): void;
kickOffLine(DevInfo: Object): unknown;
kickOffLine(DevInfo: unknown): unknown;
setStatus(args: { status: number, extStatus: number, batteryStatus: number }): Promise<GeneralCallResult>;
@@ -75,18 +51,12 @@ export interface NodeIKernelMsgService {
downloadOnlineStatusCommonByUrl(arg0: string, arg1: string): unknown;
// this.tokenType = i2;
// this.apnsToken = bArr;
// this.voipToken = bArr2;
// this.profileId = str;
setToken(arg: Object): unknown;
setToken(arg: unknown): unknown;
switchForeGround(): unknown;
switchBackGround(arg: Object): unknown;
switchBackGround(arg: unknown): unknown;
//hex
setTokenForMqq(token: string): unknown;
switchForeGroundForMqq(...args: unknown[]): unknown;
@@ -123,7 +93,6 @@ export interface NodeIKernelMsgService {
forwardFile(...args: unknown[]): unknown;
//Array<Msg>, Peer from, Peer to
multiForwardMsg(...args: unknown[]): unknown;
multiForwardMsgWithComment(...args: unknown[]): unknown;
@@ -166,23 +135,27 @@ export interface NodeIKernelMsgService {
getAllOnlineFileMsgs(...args: unknown[]): unknown;
getLatestDbMsgs(peer: Peer, cnt: number): Promise<unknown>;
getLatestDbMsgs(peer: Peer, cnt: number): Promise<GeneralCallResult & {
msgList: RawMessage[]
}>;
getLastMessageList(peer: Peer[]): Promise<unknown>;
getLastMessageList(peer: Peer[]): Promise<GeneralCallResult & {
msgList: RawMessage[]
}>;
getAioFirstViewLatestMsgs(peer: Peer, num: number): unknown;
getAioFirstViewLatestMsgs(peer: Peer, num: number): Promise<GeneralCallResult & {
msgList: RawMessage[]
}>;
//@deprecated
getMsgs(peer: Peer, msgId: string, count: unknown, queryOrder: boolean): Promise<unknown>;
//@deprecated
getMsgsIncludeSelf(peer: Peer, msgId: string, count: number, queryOrder: boolean): Promise<GeneralCallResult & {
msgList: RawMessage[]
}>;
// this.$peer = contact;
// this.$msgTime = j2;
// this.$clientSeq = j3;
// this.$cnt = i2;
//@deprecated
getMsgsWithMsgTimeAndClientSeqForC2C(...args: unknown[]): Promise<GeneralCallResult & { msgList: RawMessage[] }>;
getMsgsWithStatus(params: {
@@ -198,7 +171,7 @@ export interface NodeIKernelMsgService {
getMsgsBySeqRange(peer: Peer, startSeq: string, endSeq: string): Promise<GeneralCallResult & {
msgList: RawMessage[]
}>;
//@deprecated
getMsgsBySeqAndCount(peer: Peer, seq: string, count: number, desc: boolean, unknownArg: boolean): Promise<GeneralCallResult & {
msgList: RawMessage[]
}>;
@@ -209,6 +182,8 @@ export interface NodeIKernelMsgService {
getMsgsBySeqList(peer: Peer, seqList: string[]): Promise<GeneralCallResult & { msgList: RawMessage[] }>;
getMsgsExt(msgReq: MsgReqType): Promise<GeneralCallResult & { msgList: RawMessage[] }>;
getSingleMsg(Peer: Peer, msgSeq: string): Promise<GeneralCallResult & { msgList: RawMessage[] }>;
getSourceOfReplyMsg(peer: Peer, MsgId: string, SourceSeq: string): unknown;
@@ -219,7 +194,6 @@ export interface NodeIKernelMsgService {
getSourceOfReplyMsgByClientSeqAndTime(peer: Peer, clientSeq: string, time: string): unknown;
//cnt clientSeq?并不是吧
getMsgsByTypeFilter(peer: Peer, msgId: string, cnt: unknown, queryOrder: boolean, typeFilter: {
type: number,
subtype: Array<number>
@@ -234,49 +208,15 @@ export interface NodeIKernelMsgService {
queryMsgsWithFilter(...args: unknown[]): unknown;
/**
* @deprecated 该函数已被标记为废弃,请使用新的替代方法。
* 使用过滤条件查询消息列表的版本2接口。
*
* 该函数通过一系列过滤条件来查询特定聊天中的消息列表。这些条件包括消息类型、发送者、时间范围等。
* 函数返回一个Promise解析为查询结果的未知类型对象。
*
* @param MsgId 消息ID用于特定消息的查询。
* @param MsgTime 消息时间,用于指定消息的时间范围。
* @param param 查询参数对象,包含详细的过滤条件和分页信息。
* @param param.chatInfo 聊天信息包括聊天类型和对方用户ID。
* @param param.filterMsgType 需要过滤的消息类型数组,留空表示不过滤。
* @param param.filterSendersUid 需要过滤的发送者用户ID数组。
* @param param.filterMsgFromTime 查询消息的起始时间。
* @param param.filterMsgToTime 查询消息的结束时间。
* @param param.pageLimit 每页的消息数量限制。
* @param param.isReverseOrder 是否按时间顺序倒序返回消息。
* @param param.isIncludeCurrent 是否包含当前页码。
* @returns 返回一个Promise解析为查询结果的未知类型对象。
*/
queryMsgsWithFilterVer2(MsgId: string, MsgTime: string, param: QueryMsgsParams): Promise<unknown>;
//queryMsgsWithFilterVer2(MsgId: string, MsgTime: string, param: QueryMsgsParams): Promise<unknown>;
// this.chatType = i2;
// this.peerUid = str;
// this.chatInfo = new ChatInfo();
// this.filterMsgType = new ArrayList<>();
// this.filterSendersUid = new ArrayList<>();
// this.chatInfo = chatInfo;
// this.filterMsgType = arrayList;
// this.filterSendersUid = arrayList2;
// this.filterMsgFromTime = j2;
// this.filterMsgToTime = j3;
// this.pageLimit = i2;
// this.isReverseOrder = z;
// this.isIncludeCurrent = z2;
//queryMsgsWithFilterEx(0L, 0L, 0L, new QueryMsgsParams(new ChatInfo(2, str), new ArrayList(), new ArrayList(), 0L, 0L, 250, false, true))
queryMsgsWithFilterEx(msgId: string, msgTime: string, megSeq: string, param: QueryMsgsParams): Promise<GeneralCallResult & {
msgList: RawMessage[]
}>;
//queryMsgsWithFilterEx(this.$msgId, this.$msgTime, this.$msgSeq, this.$param)
queryFileMsgsDesktop(...args: unknown[]): unknown;
queryFileMsgsDesktop(msgId: string, msgTime: string, msgSeq: string, param: QueryMsgsParams): Promise<GeneralCallResult & {
msgList: RawMessage[]
}>;
setMsgRichInfoFlag(...args: unknown[]): unknown;
@@ -383,17 +323,11 @@ export interface NodeIKernelMsgService {
getFileThumbSavePath(...args: unknown[]): unknown;
//猜测居多
translatePtt2Text(MsgId: string, Peer: {}, MsgElement: {}): unknown;
translatePtt2Text(msgId: string, peer: Peer, msgElement: MessageElement): unknown;
setPttPlayedState(...args: unknown[]): unknown;
// NodeIQQNTWrapperSession fetchFavEmojiList [
// "",
// 48,
// true,
// true
// ]
//uk1 uk2 true
fetchFavEmojiList(str: string, num: number, uk1: boolean, uk2: boolean): Promise<GeneralCallResult & {
emojiInfoList: Array<{
uin: string,
@@ -462,7 +396,12 @@ export interface NodeIKernelMsgService {
getEmojiResourcePath(...args: unknown[]): unknown;
JoinDragonGroupEmoji(JoinDragonGroupEmojiReq: any/*joinDragonGroupEmojiReq*/): unknown;
JoinDragonGroupEmoji(JoinDragonGroupEmojiReq: {
latestMsgSeq: string,
manageEmojiId: number,
manageMsgSeq: string,
peerContact: Peer
}): Promise<unknown>;
getMsgAbstracts(...args: unknown[]): unknown;
@@ -489,7 +428,7 @@ export interface NodeIKernelMsgService {
getFirstUnreadMsgSeq(args: {
peerUid: string
guildId: string
}): unknown;
}): Promise<unknown>;
getFirstUnreadCommonMsg(...args: unknown[]): unknown;
@@ -583,13 +522,13 @@ export interface NodeIKernelMsgService {
getFirstUnreadAtMsg(peer: Peer): unknown;
clearMsgRecords(...args: unknown[]): unknown;//设置已读后调用我觉得比较好 清理记录 现在别了
clearMsgRecords(...args: unknown[]): unknown;
IsExistOldDb(...args: unknown[]): unknown;
canImportOldDbMsg(...args: unknown[]): unknown;
setPowerStatus(z: boolean): unknown;
setPowerStatus(isPowerOn: boolean): unknown;
canProcessDataMigration(...args: unknown[]): unknown;
@@ -619,25 +558,10 @@ export interface NodeIKernelMsgService {
enterOrExitAio(...args: unknown[]): unknown;
// this.peerUid = "";
// this.peerNickname = "";
// this.fromGroupCode = "";
// this.sig = new byte[0];
// this.selfUid = "";
// this.selfPhone = "";
// this.chatType = i2;
// this.peerUid = str;
// this.peerNickname = str2;
// this.fromGroupCode = str3;
// this.sig = bArr;
// this.selfUid = str4;
// this.selfPhone = str5;
// this.gameSession = tempChatGameSession;
prepareTempChat(args: unknown): unknown;//主动临时消息 不做
prepareTempChat(args: unknown): unknown;
sendSsoCmdReqByContend(cmd: string, param: string): Promise<unknown>;
//chattype,uid->Promise<any>
getTempChatInfo(ChatType: number, Uid: string): Promise<TmpChatInfoApi>;
setContactLocalTop(...args: unknown[]): unknown;
@@ -668,7 +592,7 @@ export interface NodeIKernelMsgService {
recordEmoji(...args: unknown[]): unknown;
fetchGetHitEmotionsByWord(args: Object): Promise<unknown>;//表情推荐?
fetchGetHitEmotionsByWord(args: unknown): Promise<unknown>;//表情推荐?
deleteAllRoamMsgs(...args: unknown[]): unknown;//漫游消息?
@@ -693,7 +617,7 @@ export interface NodeIKernelMsgService {
setIKernelPublicAccountAdapter(...args: unknown[]): unknown;
//tempChatGameSession有关
createUidFromTinyId(fromTinyId: string, toTinyId: string): unknown;
createUidFromTinyId(fromTinyId: string, toTinyId: string): string;
dataMigrationGetDataAvaiableContactList(...args: unknown[]): unknown;
@@ -701,7 +625,6 @@ export interface NodeIKernelMsgService {
dataMigrationStopOperation(...args: unknown[]): unknown;
//新的希望
dataMigrationImportMsgPbRecord(DataMigrationMsgInfo: Array<{
extensionData: string//"Hex"
extraData: string //""
@@ -753,6 +676,6 @@ export interface NodeIKernelMsgService {
getGuildMsgAbFlag(...args: unknown[]): unknown;
getGroupMsgStorageTime(): unknown;//这是嘛啊
getGroupMsgStorageTime(): unknown;
}

View File

@@ -1,6 +1,5 @@
import { GeneralCallResult } from './common';
//没扒干净 因为用不着
export interface NodeIKernelNodeMiscService {
getMiniAppPath(): unknown;
@@ -11,7 +10,4 @@ export interface NodeIKernelNodeMiscService {
SendMiniAppMsg(arg1: string, arg2: string, arg3: string): unknown;
startNewMiniApp(appfile: string, params: string): unknown;
// 我的计划是转发给一个新程序避免吃掉Electron_AS_Node的环境 然后重写启动MiniApp 挂载相应JS脚本 这样有个问题
// 需要自己转发ipc参数 然后必须处在gui环境 且完成校验破解 才能实现发包 有点抽象了
}

View File

@@ -1,8 +1,8 @@
export interface NodeIKernelOnlineStatusService {
addKernelOnlineStatusListener(listener: unknown): void;
addKernelOnlineStatusListener(listener: unknown): number;
removeKernelOnlineStatusListener(listenerId: unknown): void;
removeKernelOnlineStatusListener(listenerId: number): void;
getShouldShowAIOStatusAnimation(arg: unknown): unknown;

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