Compare commits

...

258 Commits

Author SHA1 Message Date
手瓜一十雪
d35a19b4fd release: olpush remove 2024-10-11 23:06:18 +08:00
手瓜一十雪
39c4473367 feat: poke oidb.0xed3_1 2024-10-10 19:04:29 +08:00
手瓜一十雪
b882bc721d release: v2.6.24 2024-10-09 20:50:00 +08:00
手瓜一十雪
405cace489 feat: 9.9.15-28498 2024-10-09 20:14:42 +08:00
手瓜一十雪
402a7b7fc9 docs: 移除误导语句 2024-10-09 14:12:13 +08:00
手瓜一十雪
8ad805e654 docs: 修正 2024-10-08 14:26:03 +08:00
手瓜一十雪
b23c357f73 release: 2.6.24 2024-10-06 10:07:39 +08:00
Wesley F. Young
f561c2b0fa refactor: rewrite switch with object mapping or if-else 2024-10-05 17:03:02 +08:00
手瓜一十雪
5a8eea668f release: 2.6.23 2024-10-02 13:23:07 +08:00
手瓜一十雪
777143e502 feat: new log 2024-10-02 13:13:55 +08:00
手瓜一十雪
0d8c9a82fe fix: 空格目录 2024-10-02 11:45:19 +08:00
手瓜一十雪
d10ab1cce3 feat: 依赖调整 2024-10-02 11:29:05 +08:00
手瓜一十雪
ec25e09d73 release: 2.6.22 2024-10-02 10:10:01 +08:00
手瓜一十雪
cba9c78ab1 release: v2.6.21 2024-10-02 10:05:01 +08:00
手瓜一十雪
c32db4a881 fix: rkey v2 2024-10-02 10:03:48 +08:00
手瓜一十雪
871add3071 Merge pull request #419 from hguandl/macos
Fix Protobuf Dependencies & macOS Support
2024-10-01 22:59:07 +08:00
Hao Guan
e661c617a3 update: macOS support 2024-10-01 22:48:51 +08:00
Hao Guan
d4bf721540 fix: protobuf dependencies 2024-10-01 22:48:31 +08:00
Wesley F. Young
d91b55faed update: log group name & sender nickname onto console 2024-10-01 11:01:32 +08:00
Alen
9687832d4d chore: 拉高linuxQQ版本 2024-10-01 02:37:44 +08:00
手瓜一十雪
fc3e436744 fix: log 2024-09-30 17:07:30 +08:00
手瓜一十雪
da90245f7b release: v2.6.20 2024-09-30 17:05:02 +08:00
手瓜一十雪
410d6a85d7 fix: protobuf #417 2024-09-30 08:30:39 +08:00
手瓜一十雪
b693342e4f fix 2024-09-30 08:18:02 +08:00
手瓜一十雪
acca361f2e release: v2.6.18 2024-09-29 20:11:30 +08:00
手瓜一十雪
b663f47713 style: ScalarType 2024-09-29 20:10:14 +08:00
手瓜一十雪
d332b199b5 refactor: protobufjs给我似! 2024-09-29 20:06:11 +08:00
Alen
78bac1dbd1 Merge pull request #416 from cnxysoft/upmain
fix: 28418下载HASH
2024-09-29 16:21:52 +08:00
Alen
724ff215f9 fix: 28418下载HASH 2024-09-29 16:20:01 +08:00
手瓜一十雪
68ea146469 release: v2.6.17 2024-09-29 13:07:52 +08:00
手瓜一十雪
82583e616f fix: #415 2024-09-29 12:57:50 +08:00
手瓜一十雪
bfc339c58d refactor: #415 2024-09-29 12:53:18 +08:00
手瓜一十雪
fe4427c076 feat: message字段返回 #415 2024-09-29 12:30:29 +08:00
手瓜一十雪
5745f388a9 feat: 简化代码 #415 2024-09-29 12:19:04 +08:00
手瓜一十雪
377e3c253f feat: parseForward for array 2024-09-29 11:26:45 +08:00
手瓜一十雪
3007a0c00e feat: nativeNode 2024-09-28 23:00:47 +08:00
手瓜一十雪
f51ffc091d Merge pull request #410 from NapNeko/hook
[Hook] NapcatNative
2024-09-28 21:38:04 +08:00
手瓜一十雪
c37c364a08 release: 2.6.16 2024-09-28 21:37:38 +08:00
手瓜一十雪
331a106e9a Merge branch 'hook' of https://github.com/NapNeko/NapCatQQ into hook 2024-09-28 21:37:17 +08:00
手瓜一十雪
cd74687b7b fix: getMsg 2024-09-28 21:36:54 +08:00
Alen
b3e145c1e6 fix: 解析增加字段 2024-09-28 21:29:18 +08:00
Alen
d8e1547736 Merge pull request #414 from NapNeko/hook_test
fix: 撤回SEQ
2024-09-28 21:00:32 +08:00
Alen
8617f01924 fix: 撤回SEQ 2024-09-28 20:58:09 +08:00
手瓜一十雪
55f9e75e6a Merge branch 'main' into hook 2024-09-28 20:17:02 +08:00
手瓜一十雪
b93e7b7ed1 feat: get pskey 2024-09-28 20:16:47 +08:00
手瓜一十雪
89cc79ad60 fix 2024-09-28 19:08:18 +08:00
手瓜一十雪
8dd0e60eea fix 2024-09-28 19:04:11 +08:00
手瓜一十雪
df6113fdf6 fix:#如好 2024-09-28 17:38:26 +08:00
手瓜一十雪
3a3095d15a feat:28418 2024-09-28 17:13:06 +08:00
手瓜一十雪
fb4d07391e Hook: GroupRecall 2024-09-28 15:41:11 +08:00
手瓜一十雪
9bef9c85cf fix 2024-09-28 15:36:25 +08:00
Alen
b77b3f227f Merge pull request #411 from cnxysoft/upmain
chore: qqnt.json增加linux版本
2024-09-28 14:25:42 +08:00
Alen
6a065f0a34 Merge branch 'main' into upmain 2024-09-28 14:22:10 +08:00
手瓜一十雪
4e1e190797 Merge pull request #409 from Zengfanqiang06/patch-1
Update README.md
2024-09-28 13:28:58 +08:00
手瓜一十雪
1ce8cd2100 napcat native 2024-09-28 13:27:13 +08:00
Alen
c03af6b9ad Merge branch 'main' into upmain 2024-09-28 13:24:47 +08:00
Alen
adca850075 chore: 增加linux目标QQ版本配置 2024-09-28 13:20:19 +08:00
Qiao
e3616b484e Update README.md
删除句号让其更统一(
2024-09-28 13:05:03 +08:00
手瓜一十雪
cfd7808169 Merge pull request #404 from NapNeko/dependabot/npm_and_yarn/types/express-5.0.0
chore(deps-dev): bump @types/express from 4.17.21 to 5.0.0
2024-09-27 17:08:57 +08:00
手瓜一十雪
addcedc588 Merge pull request #402 from NapNeko/v3
[Refactor] 推进版本重构
2024-09-27 16:47:48 +08:00
Alen
bfea786088 Merge pull request #405 from cnxysoft/upmain
chore: Once去除LL默认config
2024-09-26 17:21:31 +08:00
手瓜一十雪
50e84c3c9e Revert "feat: FrameWork调整"
This reverts commit 652fe8d21e.
2024-09-26 17:01:18 +08:00
手瓜一十雪
dc92ace85e Revert "feat: 调整"
This reverts commit 1a543928b1.
2024-09-26 17:01:13 +08:00
手瓜一十雪
1a543928b1 feat: 调整 2024-09-26 16:33:04 +08:00
手瓜一十雪
652fe8d21e feat: FrameWork调整 2024-09-26 16:22:21 +08:00
dependabot[bot]
199690f45f chore(deps-dev): bump @types/express from 4.17.21 to 5.0.0
Bumps [@types/express](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/express) from 4.17.21 to 5.0.0.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/express)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-26 08:20:36 +00:00
Alen
37a4dd4b00 chore: Once去除LL默认config 2024-09-26 16:16:46 +08:00
手瓜一十雪
34d4358bfc feat: 依赖简化 2024-09-26 16:10:52 +08:00
手瓜一十雪
90906b9019 style: lint 2024-09-26 16:08:50 +08:00
手瓜一十雪
1c212ff2b4 feat: new 2024-09-26 15:54:24 +08:00
手瓜一十雪
7d709f44a8 fix: 调整逻辑 2024-09-26 15:37:07 +08:00
手瓜一十雪
ea9e88a18a fix: 不可抗力 2024-09-26 15:31:50 +08:00
Alen
0be8a9c805 chore: 拉高目标QQ版本 2024-09-26 13:00:49 +08:00
手瓜一十雪
fcf8139afe release: 2.6.15 2024-09-25 15:51:25 +08:00
手瓜一十雪
62f969b50b feat: ver28327 2024-09-25 15:33:19 +08:00
Alen
6726062500 Merge pull request #397 from cnxysoft/upmain
chore: 增加下载链接HASH
2024-09-24 13:45:59 +08:00
Alen
cf1f4bdcaf chore: 增加下载链接HASH 2024-09-24 13:44:24 +08:00
手瓜一十雪
b09a14ad4e fix 2024-09-23 16:52:35 +08:00
手瓜一十雪
1dc62c9ca3 release:2.6.14 2024-09-23 16:46:39 +08:00
手瓜一十雪
beaa89a2dc release: v2.6.14 2024-09-23 16:42:34 +08:00
手瓜一十雪
f39a000b49 fix 2024-09-23 16:39:26 +08:00
手瓜一十雪
013a74fb14 fix2 2024-09-23 16:35:54 +08:00
手瓜一十雪
7c4964753b release: 2.6.14 2024-09-23 16:33:02 +08:00
手瓜一十雪
8353533d60 v2.6.13 2024-09-23 15:52:40 +08:00
手瓜一十雪
c06df27424 feat: 修复空格与中文问题 2024-09-23 15:52:16 +08:00
手瓜一十雪
ad82919ddf fix: 2.6.12 2024-09-23 09:35:08 +08:00
手瓜一十雪
44dbba17e1 rollup 2024-09-23 09:34:44 +08:00
手瓜一十雪
5ba110e1da feat: bind 2024-09-22 16:59:45 +08:00
手瓜一十雪
b6e392fdb2 release: v2.6.11 2024-09-22 11:42:13 +08:00
手瓜一十雪
2280e83aa2 fix: type 2024-09-21 17:40:42 +08:00
手瓜一十雪
f49b94edb9 Merge pull request #392 from Fripine/feat/more-music-types
feat: support more types of music cards
2024-09-21 15:00:59 +08:00
Fripine
2428a12221 chore 2024-09-21 12:30:05 +08:00
Fripine
9c353f3760 feat: support more types of music cards 2024-09-21 12:17:05 +08:00
手瓜一十雪
5b86d25d7f Merge pull request #389 from Fripine/fix/FriendAdd
fix: FriendAddNoticeEvent
2024-09-20 21:18:14 +08:00
Fripine
2b168e8bbc fix: FriendAdd 2024-09-20 15:50:31 +08:00
手瓜一十雪
537db32847 Merge pull request #388 from NapNeko/revert-387-fix/friendAddEvent
Revert "fix: 好友添加成功事件"
2024-09-20 15:36:32 +08:00
手瓜一十雪
498b7f9f2b Revert "fix: 好友添加成功事件" 2024-09-20 14:33:52 +08:00
手瓜一十雪
9935568597 Merge pull request #387 from Fripine/fix/friendAddEvent
fix: 好友添加成功事件
2024-09-20 13:00:13 +08:00
Fripine
467003af8c chore 2024-09-20 10:24:19 +08:00
Fripine
4c9edcc47b chore 2024-09-20 10:21:26 +08:00
Fripine
24bf9cf121 chore: 换一种方法 2024-09-20 10:17:30 +08:00
手瓜一十雪
e06f6f39a9 Merge pull request #386 from 123233513/main
增加处理消息段时的检查,过滤无效消息段。
2024-09-20 07:54:52 +08:00
123233513
98ee0c307b Merge branch 'main' of https://github.com/123233513/NapCatQQ 2024-09-20 04:57:41 +08:00
Fripine
5e53ea0bc3 fix: cant emit FriendAddNoticeEvent 2024-09-20 04:38:09 +08:00
123233513
847d88ea77 Update msg.ts
处理消息段时的检查,过滤无效消息段。
2024-09-19 22:14:18 +08:00
123233513
d5046cc2b3 Merge branch 'main' of https://githubfast.com/123233513/NapCatQQ 2024-09-19 21:36:52 +08:00
123233513
3ad64b7cbb 增加处理消息段时的检查,过滤无效消息段。 2024-09-19 21:31:33 +08:00
手瓜一十雪
0dbfe8ca55 feat: 拦截不合法消息 2024-09-19 20:49:33 +08:00
手瓜一十雪
91b794d66d release: 2.6.10 2024-09-19 20:45:09 +08:00
手瓜一十雪
0d65e1e314 release: 2.6.9 2024-09-18 21:52:16 +08:00
手瓜一十雪
2d8f58c6d8 feat: close xlog 2024-09-18 20:22:21 +08:00
手瓜一十雪
65888fa816 feat: close log 2024-09-18 20:18:35 +08:00
手瓜一十雪
857e882c6e release: fk tx 2024-09-18 11:20:58 +08:00
手瓜一十雪
add2931834 remove: debug 2024-09-18 11:19:59 +08:00
手瓜一十雪
cdda5f45ee refactor: guid fk tx 2024-09-18 11:19:29 +08:00
手瓜一十雪
5f73d6a913 feat: reportAmgomWeather a1 rnm tx 2024-09-18 11:01:12 +08:00
手瓜一十雪
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
113 changed files with 1674 additions and 2507 deletions

View File

@@ -93,15 +93,18 @@ jobs:
needs: [Build-LiteLoader,Build-Shell]
runs-on: ubuntu-latest
steps:
- name: Clone Main Repository
uses: actions/checkout@v4
with:
repository: 'NapNeko/NapCatQQ'
submodules: true
ref: main
token: ${{ secrets.NAPCAT_BUILD }}
- 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/
@@ -114,6 +117,16 @@ jobs:
rm ./NapCat.Framework.zip -rf
mv ./NapCat.Shell/NapCat.Shell.zip ./
mv ./NapCat.Framework/NapCat.Framework.zip ./
mkdir ./NapCat.Framework.Windows.Once
unzip -q ./external/LiteLoaderWrapper.zip -d ./NapCat.Framework.Windows.Once
cd ./NapCat.Framework.Windows.Once
ls
mkdir -p ./LL/plugins/NapCatQQ
unzip -q ../NapCat.Framework.zip -d ./LL/plugins/NapCatQQ
zip -q -r NapCat.Framework.Windows.Once.zip *
cd ..
mv ./NapCat.Framework.Windows.Once/NapCat.Framework.Windows.Once.zip ./
- name: Extract version from tag
run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_ENV
@@ -129,4 +142,5 @@ jobs:
files: |
NapCat.Framework.zip
NapCat.Shell.zip
NapCat.Framework.Windows.Once.zip
draft: true

View File

@@ -4,23 +4,23 @@
---
## 欢迎回来
NapCatQQ (aka 猫猫框架) 是现代化的基于 NTQQ 的 Bot 协议端实现
NapCatQQ (aka 猫猫框架) 是现代化的基于 NTQQ 的 Bot 协议端实现
## 猫猫技能
- [x] **高性能**1K+ 群聊数目、20 线程并行发送消息毫无压力
- [x] **多种启动方式**支持以无头、LiteLoader 插件、仅 QQ GUI 三种方式启动
- [x] **多平台支持**: 覆盖 Windows / Linux (可选 Docker) / Android Termux / MacOS
- [x] **高性能**轻松数千群聊 独创消息队列
- [x] **启动方式**支持以无头、LiteLoader 插件、仅 QQ GUI 三种方式启动
- [x] **覆盖平台**: 覆盖 Windows / Linux (可选 Docker) / Android Termux / MacOS
- [x] **安装简单**: 支持一键脚本/程序自动部署/镜像部署等多种覆盖范围
- [x] **低占用**:无头模式占用资源极低,适合在服务器上运行
- [x] **低占用**:无头模式占用资源极低,适合在服务器上运行
- [x] **超多接口**:实现大部分 OneBot 和 go-cqhttp 接口,超多扩展 API
- [x] **WebUI**:自带 WebUI 支持,远程管理更加便捷
- [x] **低故障率**:快速适配最新版本,日常保证 0 Issue
- [x] **远程管理**:自带 WebUI 支持,远程管理更加便捷
- [x] **扩展支持**:基于 MoeHoo 的Native 可实现发包与收包
## 使用猫猫
可前往 [Release](https://github.com/NapNeko/NapCatQQ/releases/) 页面下载最新版本
**首次使用**请务必前往[官方文档](https://napneko.github.io/)查看使用教程
**首次使用**请务必前往[官方文档](https://napneko.github.io/)查看使用教程
## 回家旅途
[QQ Group](https://qm.qq.com/q/VfjAq5HIMS)
@@ -28,7 +28,7 @@ NapCatQQ (aka 猫猫框架) 是现代化的基于 NTQQ 的 Bot 协议端实现
[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) 对本项目的大力支持
@@ -40,4 +40,4 @@ NapCatQQ (aka 猫猫框架) 是现代化的基于 NTQQ 的 Bot 协议端实现
> [!CAUTION]\
> **请不要在 QQ 官方群聊和任何影响力较大的简中互联网平台(包括但不限于: 哔哩哔哩,微博,知乎,抖音等)发布和讨论*任何*与本项目存在相关性的信息**
任何使用本仓库代码的地方,都应当严格遵守[本仓库开源许可](./LICENSE)。**此外,禁止任何项目未经授权二次分发或基于 NapCat 代码开发。**
任何使用本仓库代码的地方,都应当严格遵守[本仓库开源许可](./LICENSE)。**此外,禁止任何项目未经授权二次分发或基于 NapCat 代码开发。**

BIN
external/LiteLoaderWrapper.zip vendored Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -5,11 +5,11 @@ 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\"' -Verb runAs"
powershell -Command "Start-Process 'cmd.exe' -ArgumentList '/c cd /d \"%cd%\" && \"%~f0\" %1' -Verb runAs"
exit
)
set NAPCAT_PATCH_PATH=%cd%\patchNapCat.js
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
@@ -33,8 +33,8 @@ if not exist "%QQpath%" (
exit /b
)
set NAPCAT_MAIN_PATH=%NAPCAT_MAIN_PATH:\=/%
echo (async () =^> {await import("file:///%NAPCAT_MAIN_PATH%")})() > %NAPCAT_LOAD_PATH%
echo (async () =^> {await import("file:///%NAPCAT_MAIN_PATH%")})() > "%NAPCAT_LOAD_PATH%"
"%NAPCAT_LAUNCHER_PATH%" "%QQPath%" "%NAPCAT_INJECT_PATH%"
"%NAPCAT_LAUNCHER_PATH%" "%QQPath%" "%NAPCAT_INJECT_PATH%" %1
REM "%NAPCAT_LAUNCHER_PATH%" "%QQPath%" "%NAPCAT_INJECT_PATH%" 123456

View File

@@ -5,11 +5,11 @@ 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\"' -Verb runAs"
powershell -Command "Start-Process 'wt.exe' -ArgumentList 'cmd /c cd /d \"%cd%\" && \"%~f0\" %1' -Verb runAs"
exit
)
set NAPCAT_PATCH_PATH=%cd%\patchNapCat.js
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
@@ -34,8 +34,6 @@ if not exist "%QQpath%" (
)
set NAPCAT_MAIN_PATH=%NAPCAT_MAIN_PATH:\=/%
echo (async () =^> {await import("file:///%NAPCAT_MAIN_PATH%")})() > %NAPCAT_LOAD_PATH%
echo (async () =^> {await import("file:///%NAPCAT_MAIN_PATH%")})() > "%NAPCAT_LOAD_PATH%"
"%NAPCAT_LAUNCHER_PATH%" "%QQPath%" "%NAPCAT_INJECT_PATH%"
REM "%NAPCAT_LAUNCHER_PATH%" "%QQPath%" "%NAPCAT_INJECT_PATH%" 123456
"%NAPCAT_LAUNCHER_PATH%" "%QQPath%" "%NAPCAT_INJECT_PATH%" %1

View File

@@ -1 +0,0 @@
require('./launcher.node').load('external_index', module);

26
launcher/qqnt.json Normal file
View File

@@ -0,0 +1,26 @@
{
"name": "qq-chat",
"version": "9.9.15-28418",
"verHash": "206bfa62",
"linuxVersion": "3.2.12-28418",
"linuxVerHash": "0256c948",
"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": "28418",
"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.3.7",
"version": "2.6.26",
"icon": "./logo.png",
"authors": [
{

View File

@@ -1,65 +1,52 @@
{
"name": "napcat",
"private": true,
"type": "module",
"version": "2.3.7",
"scripts": {
"build:framework": "vite build --mode framework",
"build:shell": "vite build --mode shell",
"build:webui": "cd ./src/webui && vite build",
"lint": "eslint --fix src/**/*.{js,ts}",
"depend": "cd dist && npm install --omit=dev"
},
"devDependencies": {
"@babel/core": "^7.24.7",
"@babel/plugin-proposal-class-properties": "^7.18.6",
"@babel/plugin-proposal-decorators": "^7.24.7",
"@babel/preset-typescript": "^7.24.7",
"@log4js-node/log4js-api": "^1.0.2",
"@protobuf-ts/plugin": "^2.9.4",
"@rollup/plugin-node-resolve": "^15.2.3",
"@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": "^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"
},
"dependencies": {
"ajv": "^8.13.0",
"async-mutex": "^0.5.0",
"chalk": "^5.3.0",
"commander": "^12.1.0",
"cors": "^2.8.5",
"express": "^5.0.0-beta.2",
"fast-xml-parser": "^4.3.6",
"file-type": "^19.0.0",
"fluent-ffmpeg": "^2.1.2",
"image-size": "^1.1.1",
"json-schema-to-ts": "^3.1.0",
"log4js": "^6.9.1",
"qrcode-terminal": "^0.12.0",
"silk-wasm": "^3.6.1",
"strtok3": "8.0.1",
"ws": "^8.18.0"
}
}
{
"name": "napcat",
"private": true,
"type": "module",
"version": "2.6.26",
"scripts": {
"build:framework": "vite build --mode framework",
"build:shell": "vite build --mode shell",
"build:webui": "cd ./src/webui && vite build",
"lint": "eslint --fix src/**/*.{js,ts}",
"depend": "cd dist && npm install --omit=dev"
},
"devDependencies": {
"@babel/preset-typescript": "^7.24.7",
"@log4js-node/log4js-api": "^1.0.2",
"@rollup/plugin-node-resolve": "^15.2.3",
"@rollup/plugin-typescript": "^11.1.6",
"@types/cors": "^2.8.17",
"@types/express": "^5.0.0",
"@types/fluent-ffmpeg": "^2.1.24",
"@types/node": "^22.0.1",
"@types/qrcode-terminal": "^0.12.2",
"@types/ws": "^8.5.12",
"@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",
"typescript": "^5.3.3",
"vite": "^5.2.6",
"vite-plugin-cp": "^4.0.8",
"vite-tsconfig-paths": "^4.3.2",
"@protobuf-ts/runtime": "^2.9.4",
"ajv": "^8.13.0",
"fast-xml-parser": "^4.3.6",
"chalk": "^5.3.0",
"commander": "^12.1.0",
"async-mutex": "^0.5.0",
"file-type": "^19.0.0",
"json-schema-to-ts": "^3.1.0",
"image-size": "^1.1.1",
"cors": "^2.8.5"
},
"dependencies": {
"qrcode-terminal": "^0.12.0",
"fluent-ffmpeg": "^2.1.2",
"express": "^5.0.0-beta.2",
"log4js": "^6.9.1",
"silk-wasm": "^3.6.1",
"ws": "^8.18.0"
}
}

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 %*

Binary file not shown.

View File

@@ -1,66 +1,67 @@
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 { encode, getDuration, getWavFileInfo, isSilk, isWav } from 'silk-wasm';
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;
}
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 _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 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);
fs.writeFileSync(pttPath, silk.data);
await fsPromise.writeFile(pttPath, silk.data);
logger.log(`语音文件${filePath}转换成功!`, pttPath, '时长:', silk.duration);
return {
converted: true,
@@ -68,15 +69,13 @@ export async function encodeSilk(filePath: string, TEMP_DIR: string, logger: Log
duration: silk.duration / 1000,
};
} else {
const silk = file;
let duration = 0;
try {
duration = getDuration(silk) / 1000;
duration = getDuration(file) / 1000;
} catch (e: any) {
logger.log('获取语音文件时长失败, 使用文件大小推测时长', filePath, e.stack);
duration = await guessDuration(filePath);
duration = await guessDuration(filePath, logger);
}
return {
converted: false,
path: filePath,
@@ -84,7 +83,7 @@ export async function encodeSilk(filePath: string, TEMP_DIR: string, logger: Log
};
}
} catch (error: any) {
logger.logError('convert silk failed', error.stack);
logger.logError.bind(logger)('convert silk failed', error.stack);
return {};
}
}
}

View File

@@ -40,7 +40,7 @@ export abstract class ConfigBase<T> {
fs.writeFileSync(configPath, fs.readFileSync(this.getConfigPath(undefined), 'utf-8'));
logger.log(`[Core] [Config] 配置文件创建成功!\n`);
} catch (e: any) {
logger.logError(`[Core] [Config] 创建配置文件时发生错误:`, e.message);
logger.logError.bind(logger)(`[Core] [Config] 创建配置文件时发生错误:`, e.message);
}
}
try {
@@ -49,9 +49,9 @@ export abstract class ConfigBase<T> {
return this.configData;
} catch (e: any) {
if (e instanceof SyntaxError) {
logger.logError(`[Core] [Config] 配置文件格式错误,请检查配置文件:`, e.message);
logger.logError.bind(logger)(`[Core] [Config] 配置文件格式错误,请检查配置文件:`, e.message);
} else {
logger.logError(`[Core] [Config] 读取配置文件时发生错误:`, e.message);
logger.logError.bind(logger)(`[Core] [Config] 读取配置文件时发生错误:`, e.message);
}
return {} as T;
}
@@ -66,7 +66,7 @@ export abstract class ConfigBase<T> {
try {
fs.writeFileSync(configPath, JSON.stringify(newConfigData, this.getKeys(), 2));
} catch (e: any) {
logger.logError(`保存配置文件 ${configPath} 时发生错误:`, e.message);
logger.logError.bind(logger)(`保存配置文件 ${configPath} 时发生错误:`, e.message);
}
}
}

View File

@@ -1,133 +0,0 @@
import type { NodeIQQNTWrapperSession, WrapperNodeApi } from '@/core/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;
[key: string]: any;
}
export class NTEventWrapperV2 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) {
const dispatcherListener = this.dispatcherListener.bind(this);
return new Proxy({}, {
get(_target: any, prop: any, _receiver: any) {
return (...args: any[]) => {
dispatcherListener(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 = /^NodeIKernel(.*?)Listener$/.exec(listenerMainName)![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>>>((resolve) => {
const EventFunc = this.createEventFunction<EventType>(EventName);
EventFunc!(...args).then((retData: Awaited<ReturnType<EventType>> | PromiseLike<Awaited<ReturnType<EventType>>>) => resolve(retData));
});
}
}
//NTEvent2.0

View File

@@ -236,7 +236,7 @@ export class NTEventWrapper {
this.createListenerFunction(ListenerMainName);
const eventFunction = this.createEventFunction(serviceAndMethod);
retEvent = await eventFunction!(...(args));
if (!checkerEvent(retEvent)) {
if (!checkerEvent(retEvent) && timeoutRef.hasRef()) {
clearTimeout(timeoutRef);
reject(
new Error(
@@ -250,86 +250,8 @@ export class NTEventWrapper {
),
);
}
},
);
}
/*
async callNormalEvent<
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]
>(
serviceAndMethod: `${Service}/${ServiceMethod}`,
listenerAndMethod: `${Listener}/${ListenerMethod}`,
waitTimes = 1,
timeout: number = 3000,
checker: (...args: Parameters<ListenerType>) => boolean,
...args: Parameters<EventType>
) {
return new Promise<[EventRet: Awaited<ReturnType<EventType>>, ...Parameters<ListenerType>]>(
async (resolve, reject) => {
const id = randomUUID();
let complete = 0;
let retData: Parameters<ListenerType> | undefined = undefined;
let retEvent: any = {};
const databack = () => {
if (complete == 0) {
reject(
new Error(
'Timeout: NTEvent EventName:' +
serviceAndMethod +
' ListenerName:' +
listenerAndMethod +
' EventRet:\n' +
JSON.stringify(retEvent, null, 4) +
'\n',
),
);
} else {
resolve([retEvent as Awaited<ReturnType<EventType>>, ...retData!]);
}
};
const ListenerNameList = listenerAndMethod.split('/');
const ListenerMainName = ListenerNameList[0];
const ListenerSubName = ListenerNameList[1];
const Timeouter = setTimeout(databack, timeout);
const eventCallbak = {
timeout: timeout,
createtime: Date.now(),
checker: checker,
func: (...args: any[]) => {
complete++;
//console.log('func', ...args);
retData = args as Parameters<ListenerType>;
if (complete >= waitTimes) {
clearTimeout(Timeouter);
databack();
}
},
};
if (!this.EventTask.get(ListenerMainName)) {
this.EventTask.set(ListenerMainName, new Map());
}
if (!this.EventTask.get(ListenerMainName)?.get(ListenerSubName)) {
this.EventTask.get(ListenerMainName)?.set(ListenerSubName, new Map());
}
this.EventTask.get(ListenerMainName)?.get(ListenerSubName)?.set(id, eventCallbak);
this.createListenerFunction(ListenerMainName);
const EventFunc = this.createEventFunction<EventType>(serviceAndMethod);
retEvent = await EventFunc!(...(args as any[]));
},
);
}
*/
}

View File

@@ -123,7 +123,7 @@ export interface HttpDownloadOptions {
headers?: Record<string, string> | string;
}
export async function httpDownload(options: string | HttpDownloadOptions): Promise<Buffer> {
async function tryDownload(options: string | HttpDownloadOptions, useReferer: boolean = false): Promise<Response> {
// const chunks: Buffer[] = [];
let url: string;
let headers: Record<string, string> = {
@@ -142,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);
}
@@ -160,8 +171,7 @@ type Uri2LocalRes = {
errMsg: string,
fileName: string,
ext: string,
path: string,
isLocal: boolean
path: string
}
export async function checkFileV2(filePath: string) {
@@ -194,7 +204,6 @@ export async function checkUriType(Uri: string) {
return undefined;
}, Uri);
if (LocalFileRet) return LocalFileRet;
const OtherFileRet = await solveProblem((uri: string) => {
//再判断是否是Http
if (uri.startsWith('http://') || uri.startsWith('https://')) {
@@ -206,15 +215,19 @@ export async function checkUriType(Uri: string) {
}
if (uri.startsWith('file://')) {
let filePath: string;
// await fs.copyFile(url.pathname, filePath);
const pathname = decodeURIComponent(new URL(uri).pathname);
if (process.platform === 'win32') {
filePath = pathname.slice(1);
} else {
filePath = pathname;
}
return { Uri: filePath, Type: FileUriType.Local };
}
if (uri.startsWith('data:')) {
const data = uri.split(',')[1];
if (data) return { Uri: data, Type: FileUriType.Base64 };
}
}, Uri);
if (OtherFileRet) return OtherFileRet;
@@ -224,34 +237,42 @@ export async function checkUriType(Uri: string) {
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;
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) {
@@ -266,7 +287,7 @@ export async function uri2local(dir: string, uri: string, filename: string | und
fileExt = ext;
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

@@ -25,8 +25,13 @@ export async function solveAsyncProblem<T extends (...args: any[]) => Promise<an
}
export class FileNapCatOneBotUUID {
static encodeModelId(peer: Peer, modelId: string, fileId: string): string {
return `NapCatOneBot|ModelIdFile|${peer.chatType}|${peer.peerUid}|${modelId}|${fileId}`;
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 | {
@@ -34,8 +39,14 @@ export class FileNapCatOneBotUUID {
modelId: string,
fileId: string
} {
if (!uuid.startsWith('NapCatOneBot|ModelIdFile|')) return undefined;
const data = uuid.split('|');
//前四个字节是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 {
@@ -48,8 +59,14 @@ export class FileNapCatOneBotUUID {
};
}
static encode(peer: Peer, msgId: string, elementId: string): string {
return `NapCatOneBot|MsgFile|${peer.chatType}|${peer.peerUid}|${msgId}|${elementId}`;
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 | {
@@ -57,8 +74,14 @@ export class FileNapCatOneBotUUID {
msgId: string,
elementId: string
} {
if (!uuid.startsWith('NapCatOneBot|MsgFile|')) return undefined;
const data = uuid.split('|');
//前四个字节是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 {
@@ -140,34 +163,51 @@ export function isEqual(obj1: any, obj2: any) {
export function getDefaultQQVersionConfigInfo(): QQVersionConfigType {
if (os.platform() === 'linux') {
return {
baseVersion: '3.2.12-27597',
curVersion: '3.2.12-27597',
baseVersion: '3.2.12.28060',
curVersion: '3.2.12.28060',
prevVersion: '',
onErrorVersions: [],
buildId: '27597',
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-27597',
curVersion: '9.9.15-27597',
baseVersion: '9.9.15-28131',
curVersion: '9.9.15-28131',
prevVersion: '',
onErrorVersions: [],
buildId: '27597',
buildId: '28131',
};
}
export function getQQPackageInfoPath(exePath: string = ''): string {
export function getQQPackageInfoPath(exePath: string = '', version?: string): string {
let packagePath;
if (os.platform() === 'darwin') {
return path.join(path.dirname(exePath), '..', 'Resources', 'app', 'package.json');
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 {
return path.join(path.dirname(exePath), 'resources', 'app', 'package.json');
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), 'resources', 'app', 'versions', 'config.json');
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');
@@ -180,13 +220,18 @@ export function getQQVersionConfigPath(exePath: string = ''): string | undefined
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) {
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

@@ -139,9 +139,13 @@ export class LogWrapper {
logMessage(msg: RawMessage, selfInfo: SelfInfo) {
const isSelfSent = msg.senderUin === selfInfo.uin;
this.log(`${
isSelfSent ? '发送 ->' : '接收 <-'
} ${rawMessageToText(msg)}`);
// Intercept grey tip
if (msg.elements[0]?.elementType === ElementType.GreyTip) {
return;
}
this.log(`${isSelfSent ? '发送 ->' : '接收 <-' } ${rawMessageToText(msg)}`);
}
}
@@ -155,7 +159,12 @@ export function rawMessageToText(msg: RawMessage, recursiveLevel = 0): string {
if (msg.chatType == ChatType.KCHATTYPEC2C) {
tokens.push(`私聊 (${msg.peerUin})`);
} else if (msg.chatType == ChatType.KCHATTYPEGROUP) {
tokens.push(`群聊 (群 ${msg.peerUin}${msg.senderUin})`);
if (recursiveLevel < 1) {
tokens.push(`群聊 [${msg.peerName}(${msg.peerUin})]`);
}
if (msg.senderUin !== '0') {
tokens.push(`[${msg.sendMemberName || msg.sendRemarkName || msg.sendNickName}(${msg.senderUin})]`);
}
} else if (msg.chatType == ChatType.KCHATTYPEDATALINE) {
tokens.push('移动设备');
} else /* temp */ {
@@ -180,12 +189,11 @@ export function rawMessageToText(msg: RawMessage, recursiveLevel = 0): string {
const recordMsgOrNull = msg.records.find(
record => element.replyElement!.sourceMsgIdInRecords === record.msgId,
);
return `[回复消息 ${
recordMsgOrNull &&
recordMsgOrNull.peerUin != '284840486' // 非转发消息; 否则定位不到
?
rawMessageToText(recordMsgOrNull, recursiveLevel + 1) :
`未找到消息记录 (MsgId = ${element.replyElement.sourceMsgIdInRecords})`
return `[回复消息 ${recordMsgOrNull &&
recordMsgOrNull.peerUin != '284840486' && recordMsgOrNull.peerUin != '1094950020'// 非转发消息; 否则定位不到
?
rawMessageToText(recordMsgOrNull, recursiveLevel + 1) :
`未找到消息记录 (MsgId = ${element.replyElement.sourceMsgIdInRecords})`
}]`;
}

View File

@@ -19,14 +19,16 @@ export class QQBasicInfoWrapper {
//基础目录获取
this.context = context;
this.QQMainPath = process.execPath;
this.QQPackageInfoPath = getQQPackageInfoPath(this.QQMainPath);
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;
@@ -51,26 +53,22 @@ export class QQBasicInfoWrapper {
}
//此方法不要直接使用
getQUAInternal() {
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`;
}
getQUAFallback() {
const platformMapping: Partial<Record<NodeJS.Platform, string>> = {
win32: `V1_WIN_${this.getFullQQVesion()}_${this.getQQBuildStr()}_GW_B`,
darwin: `V1_MAC_${this.getFullQQVesion()}_${this.getQQBuildStr()}_GW_B`,
linux: `V1_LNX_${this.getFullQQVesion()}_${this.getQQBuildStr()}_GW_B`,
};
return platformMapping[systemPlatform] ?? (platformMapping.win32)!;
}
getAppidInternal() {
switch (systemPlatform) {
case 'linux':
return '537243600';
case 'darwin':
return '537243441';
default:
return '537243538';
}
getAppIdFallback() {
const platformMapping: Partial<Record<NodeJS.Platform, string>> = {
win32: '537246092',
darwin: '537246140',
linux: '537246140',
};
return platformMapping[systemPlatform] ?? '537246092';
}
getAppidV2(): { appid: string; qua: string } {
@@ -86,6 +84,6 @@ export class QQBasicInfoWrapper {
// else
this.context.logger.log(`[QQ版本兼容性检测] 获取Appid异常 请检测NapCat/QQNT是否正常`);
this.context.logger.log(`[QQ版本兼容性检测] ${fullVersion} 版本兼容性不佳,可能会导致一些功能无法正常使用`,);
return { appid: this.getAppidInternal(), qua: this.getQUAInternal() };
return { appid: this.getAppIdFallback(), qua: this.getQUAFallback() };
}
}

View File

@@ -1,12 +1,8 @@
import os from 'node:os';
import path from 'node:path';
import { networkInterfaces } from 'os';
import { randomUUID } from 'crypto';
// 缓解Win7设备兼容性问题
let osName: string;
// 设备ID
let machineId: Promise<string>;
try {
osName = os.hostname();
@@ -14,54 +10,6 @@ try {
osName = 'NapCat'; // + crypto.randomUUID().substring(0, 4);
}
const invalidMacAddresses = new Set([
'00:00:00:00:00:00',
'ff:ff:ff:ff:ff:ff',
'ac:de:48:00:11:22',
]);
function validateMacAddress(candidate: string): boolean {
// eslint-disable-next-line no-useless-escape
const tempCandidate = candidate.replace(/\-/g, ':').toLowerCase();
return !invalidMacAddresses.has(tempCandidate);
}
export async function getMachineId(): Promise<string> {
if (!machineId) {
machineId = (async () => {
const id = await getMacMachineId();
return id ?? randomUUID(); // fallback, generate a UUID
})();
}
return machineId;
}
export function getMac(): string {
const ifaces = networkInterfaces();
for (const name in ifaces) {
const networkInterface = ifaces[name];
if (networkInterface) {
for (const { mac } of networkInterface) {
if (validateMacAddress(mac)) {
return mac;
}
}
}
}
throw new Error('Unable to retrieve mac address (unexpected format)');
}
async function getMacMachineId(): Promise<string | undefined> {
try {
const crypto = await import('crypto');
const macAddress = getMac();
return crypto.createHash('sha256').update(macAddress, 'utf8').digest('hex');
} catch (err) {
return undefined;
}
}
const homeDir = os.homedir();

View File

@@ -1 +1 @@
export const napCatVersion = '2.3.7';
export const napCatVersion = '2.6.26';

View File

@@ -1,63 +0,0 @@
import {
CacheFileListItem,
CacheFileType,
ChatCacheListItemBasic,
ChatType,
InstanceContext,
NapCatCore,
} from '@/core';
export class NTQQCacheApi {
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

@@ -24,6 +24,7 @@ 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;
@@ -33,7 +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);
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) {
@@ -71,7 +72,7 @@ export class NTQQFileApi {
file_uuid: '',
});
await this.copyFile(filePath, mediaPath!);
await this.copyFile(filePath, mediaPath);
const fileSize = await this.getFileSize(filePath);
return {
md5: fileMd5,
@@ -82,7 +83,7 @@ export class NTQQFileApi {
};
}
async createValidSendFileElement(filePath: string, fileName: string = '', folderId: string = '',): Promise<SendFileElement> {
async createValidSendFileElement(context: MessageContext, filePath: string, fileName: string = '', folderId: string = '',): Promise<SendFileElement> {
const {
fileName: _fileName,
path,
@@ -91,6 +92,7 @@ export class NTQQFileApi {
if (fileSize === 0) {
throw new Error('文件异常大小为0');
}
context.deleteAfterSentFiles.push(path);
return {
elementType: ElementType.FILE,
elementId: '',
@@ -103,12 +105,13 @@ export class NTQQFileApi {
};
}
async createValidSendPicElement(picPath: string, summary: string = '', subType: 0 | 1 = 0,): Promise<SendPicElement> {
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: '',
@@ -130,26 +133,40 @@ export class NTQQFileApi {
};
}
async createValidSendVideoElement(filePath: string, fileName: string = '', diyThumbPath: string = ''): Promise<SendVideoElement> {
async createValidSendVideoElement(context: MessageContext, filePath: string, fileName: string = '', diyThumbPath: string = ''): Promise<SendVideoElement> {
const logger = this.core.context.logger;
const { fileName: _fileName, path, fileSize, md5 } = await this.core.apis.FileApi.uploadFile(filePath, ElementType.VIDEO);
if (fileSize === 0) {
throw new Error('文件异常大小为0');
}
let thumb = path.replace(`${pathLib.sep}Ori${pathLib.sep}`, `${pathLib.sep}Thumb${pathLib.sep}`);
thumb = pathLib.dirname(thumb);
let videoInfo = {
width: 1920, height: 1080,
time: 15,
format: 'mp4',
size: fileSize,
size: 0,
filePath,
};
try {
videoInfo = await getVideoInfo(path, logger);
videoInfo = await getVideoInfo(filePath, logger);
} catch (e) {
logger.logError('获取视频信息失败,将使用默认值', e);
logger.logError.bind(logger)('获取视频信息失败,将使用默认值', e);
}
let fileExt = 'mp4';
try {
const tempExt = (await fileType.fileTypeFromFile(filePath))?.ext;
if (tempExt) fileExt = tempExt;
} catch (e) {
this.context.logger.logError.bind(logger)('获取文件类型失败', 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`;
@@ -179,11 +196,13 @@ export class NTQQFileApi {
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: fileName || _fileName,
fileName: uploadName,
filePath: path,
videoMd5: md5,
thumbMd5,
@@ -207,7 +226,9 @@ export class NTQQFileApi {
throw new Error('文件异常大小为0');
}
if (converted) {
fsPromises.unlink(silkPath);
fsPromises.unlink(silkPath).then().catch(
(e) => this.context.logger.logError.bind(this.context.logger)('删除临时文件失败', e)
);
}
return {
elementType: ElementType.PTT,
@@ -246,7 +267,6 @@ export class NTQQFileApi {
}
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) {
@@ -345,24 +365,43 @@ export class NTQQFileApi {
if (url) {
const parsedUrl = new URL(IMAGE_HTTP_HOST + url);
const urlRkey = parsedUrl.searchParams.get('rkey');
const imageAppid = parsedUrl.searchParams.get('appid');
const isNTFlavoredPic = imageAppid && ['1406', '1407'].includes(imageAppid);
if (isNTFlavoredPic) {
let rkey = parsedUrl.searchParams.get('rkey');
if (rkey) {
return IMAGE_HTTP_HOST_NT + url;
}
const rkeyData = await this.rkeyManager.getRkey();
rkey = imageAppid === '1406' ? rkeyData.private_rkey : rkeyData.group_rkey;
return IMAGE_HTTP_HOST_NT + url + `${rkey}`;
} else {
// 老的图片url不需要rkey
return IMAGE_HTTP_HOST + url;
const isNTV2 = imageAppid && ['1406', '1407'].includes(imageAppid);
const imageFileId = parsedUrl.searchParams.get('fileid');
let rkeyData = {
private_rkey: 'CAQSKAB6JWENi5LM_xp9vumLbuThJSaYf-yzMrbZsuq7Uz2qEc3Rbib9LP4',
group_rkey: 'CAQSKAB6JWENi5LM_xp9vumLbuThJSaYf-yzMrbZsuq7Uz2qffcqm614gds',
online_rkey: false
};
try {
let tempRkeyData = await this.rkeyManager.getRkey();
rkeyData.group_rkey = tempRkeyData.group_rkey;
rkeyData.private_rkey = tempRkeyData.private_rkey;
rkeyData.online_rkey = tempRkeyData.expired_time > Date.now() / 1000;
} catch (e) {
this.context.logger.logError.bind(this.context.logger)('获取rkey失败 Fallback Old Mode', e);
}
} else if (fileMd5 || md5HexStr) {
// 没有url需要自己拼接
if (isNTV2 && urlRkey) {
return IMAGE_HTTP_HOST_NT + urlRkey;
} else if (isNTV2 && rkeyData.online_rkey) {
let rkey = imageAppid === '1406' ? rkeyData.private_rkey : rkeyData.group_rkey;
return IMAGE_HTTP_HOST_NT + url + `&rkey=${rkey}`;
} else if (isNTV2 && imageFileId) {
let rkey = imageAppid === '1406' ? rkeyData.private_rkey : rkeyData.group_rkey;
return IMAGE_HTTP_HOST + `/download?appid=${imageAppid}&fileid=${imageFileId}&rkey=${rkey}`;
}
}
//到这里说明可能是旧客户端
if (fileMd5 || md5HexStr) {
return `${IMAGE_HTTP_HOST}/gchatpic_new/0/0-0-${(fileMd5 ?? md5HexStr)!.toUpperCase()}/0`;
}
this.context.logger.logDebug('图片url获取失败', element);
return '';
}

View File

@@ -24,7 +24,7 @@ export class NTQQGroupApi {
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() {
@@ -34,7 +34,13 @@ export class NTQQGroupApi {
}
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({
@@ -262,8 +268,25 @@ export class NTQQGroupApi {
}
return member;
}
async searchGroup(groupCode: string) {
const [, ret] = await this.core.eventWrapper.callNormalEventV2(
'NodeIKernelSearchService/searchGroup',
'NodeIKernelSearchListener/onSearchGroupResult',
[{
keyWords: groupCode,
groupNum: 25,
exactSearch: false,
penetrate: ''
}],
(ret) => ret.result === 0,
(params) => !!params.groupInfos.find(g => g.groupCode === groupCode),
1,
5000
);
return ret.groupInfos.find(g => g.groupCode === groupCode);
}
async getGroupMemberEx(GroupCode: string, uid: string, forced = false, retry = 2) {
let data = await solveAsyncProblem((eventWrapper: NTEventWrapper, GroupCode: string, uid: string, forced = false) => {
const data = await solveAsyncProblem((eventWrapper: NTEventWrapper, GroupCode: string, uid: string, forced = false) => {
return eventWrapper.callNormalEventV2(
'NodeIKernelGroupService/getMemberInfo',
'NodeIKernelGroupListener/onMemberInfoChange',
@@ -278,7 +301,7 @@ export class NTQQGroupApi {
return data[3].get(uid);
}
if (retry > 0) {
let trydata = await this.getGroupMemberEx(GroupCode, uid, true, retry - 1) as GroupMember | undefined;
const trydata = await this.getGroupMemberEx(GroupCode, uid, true, retry - 1) as GroupMember | undefined;
if (trydata) return trydata;
}
return undefined;

View File

@@ -5,5 +5,4 @@ export * from './msg';
export * from './user';
export * from './webapi';
export * from './sign';
export * from './system';
export * from './cache';
export * from './system';

View File

@@ -94,6 +94,7 @@ export class NTQQMsgApi {
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);
}

View File

@@ -11,7 +11,13 @@ 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],
@@ -24,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);
}
@@ -103,12 +120,20 @@ export class NTQQUserApi {
return this.context.session.getProfileService().modifyDesktopMiniProfile(param);
}
//需要异常处理
async getCookies(domain: string) {
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';
return await RequestUtil.HttpsGetCookies(requestUrl);
let data = await RequestUtil.HttpsGetCookies(requestUrl);
if (!data.p_skey || data.p_skey.length == 0) {
try {
let pskey = (await this.getPSkey([domain])).domainPskeyMap.get(domain);
if (pskey) data.p_skey = pskey;
} catch {
return data;
}
}
return data;
}
async getPSkey(domainList: string[]) {
@@ -159,7 +184,7 @@ export class NTQQUserApi {
if (uid) return uid;
uid = (await this.context.session.getUixConvertService().getUid([Uin])).uidInfo.get(Uin);
if (uid) return uid;
const unverifiedUid = (await this.getUserDetailInfoByUinV2(Uin)).detail.uid;//从QQ Native 特殊转换
const unverifiedUid = (await this.getUserDetailInfoByUin(Uin)).detail.uid;//从QQ Native 特殊转换
if (unverifiedUid.indexOf('*') == -1) uid = unverifiedUid;
//if (uid) return uid;
return uid;
@@ -195,7 +220,7 @@ export class NTQQUserApi {
return await this.context.session.getRecentContactService().getRecentContactList();
}
async getUserDetailInfoByUinV2(Uin: string) {
async getUserDetailInfoByUin(Uin: string) {
return await this.core.eventWrapper.callNoListenerEvent(
'NodeIKernelProfileService/getUserDetailInfoByUin',
Uin

View File

@@ -157,7 +157,7 @@ export class NTQQWebApi {
const cookieObject = await this.core.apis.UserApi.getCookies('qun.qq.com');
try {
let settings = JSON.stringify({
const settings = JSON.stringify({
is_show_edit_card: is_show_edit_card,
tip_window_type: tip_window_type,
confirm_required: confirm_required
@@ -167,7 +167,7 @@ export class NTQQWebApi {
imgWidth: imgWidth.toString(),
imgHeight: imgHeight.toString(),
};
let ret: SetNoticeRetSuccess = await RequestUtil.HttpGetJson<SetNoticeRetSuccess>(
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,
@@ -264,7 +264,7 @@ export class NTQQWebApi {
});
}
} else {
this.context.logger.logError('获取龙王信息失败');
this.context.logger.logError.bind(this.context.logger)('获取龙王信息失败');
}
}
if (getType === WebHonorType.PERFORMER || getType === WebHonorType.ALL) {
@@ -280,7 +280,7 @@ export class NTQQWebApi {
});
}
} else {
this.context.logger.logError('获取群聊之火失败');
this.context.logger.logError.bind(this.context.logger)('获取群聊之火失败');
}
}
if (getType === WebHonorType.PERFORMER || getType === WebHonorType.ALL) {
@@ -296,7 +296,7 @@ export class NTQQWebApi {
});
}
} else {
this.context.logger.logError('获取群聊炽焰失败');
this.context.logger.logError.bind(this.context.logger)('获取群聊炽焰失败');
}
}
if (getType === WebHonorType.EMOTION || getType === WebHonorType.ALL) {
@@ -312,7 +312,7 @@ export class NTQQWebApi {
});
}
} else {
this.context.logger.logError('获取快乐源泉失败');
this.context.logger.logError.bind(this.context.logger)('获取快乐源泉失败');
}
}

View File

@@ -1,4 +1,4 @@
import { GroupMemberRole, Peer } from '@/core';
import { GroupMemberRole } from '@/core';
export interface Peer {
chatType: ChatType;
@@ -659,7 +659,8 @@ export interface GrayTipElement {
export enum FaceType {
normal = 1, // 小黄脸
normal2 = 2, // 新小黄脸, 从faceIndex 222开始
dice = 3 // 骰子
dice = 3, // 骰子
poke = 5 // 拍一拍
}
export enum FaceIndex {
@@ -908,11 +909,26 @@ export interface RawMessage {
*/
peerUin: string;
/**
* 好友备注(如果是好友消息)
*/
remark?: string;
/**
* 群名(如果是群消息)
*/
peerName: string;
/**
* 发送者昵称(如果是好友消息)
*/
sendNickName: string;
/**
* 发送者好友备注(如果是群消息并且有发送者好友)
*/
sendRemarkName: string;
/**
* 发送者群名片(如果是群消息)
*/
@@ -959,3 +975,18 @@ export interface TmpChatInfo {
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,14 +1,15 @@
export interface IdMusicSignPostData {
type: 'qq' | '163',
type: 'qq' | '163' | 'kugou' | 'migu' | 'kuwo',
id: string | number,
}
export interface CustomMusicSignPostData {
type: 'custom',
type: 'qq' | '163' | 'kugou' | 'migu' | 'kuwo' | 'custom',
id: undefined,
url: string,
audio: string,
title: string,
image?: string,
audio?: string,
title?: string,
image: string,
singer?: 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,65 +308,6 @@ 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
@@ -375,8 +319,4 @@ export enum ProfileBizType {
KVAS,
KQZONE,
KOTHER
}export enum BuddyListReqType {
KNOMAL,
KLETTER
}
}

View File

@@ -1,14 +1,46 @@
{
"3.2.12-27597": {
"appid": 537243600,
"qua": "V1_LNX_NQ_3.2.12_27597_GW_B"
"9.9.15-28060": {
"appid": 537246092,
"qua": "V1_WIN_NQ_9.9.15_28060_GW_B"
},
"9.9.15-27597": {
"appid": 537243441,
"qua": "V1_WIN_NQ_9.9.15_27597_GW_B"
"9.9.15-28131": {
"appid": 537246092,
"qua": "V1_WIN_NQ_9.9.15_28131_GW_B"
},
"6.9.53-27597": {
"appid": 537243538,
"qua": "V1_MAC_NQ_6.9.53_27597_GW_B"
"3.2.12-28060": {
"appid": 537246140,
"qua": "V1_LNX_NQ_3.2.12_28060_GW_B"
},
"3.2.12-28131": {
"appid": 537246140,
"qua": "V1_LNX_NQ_3.2.12_28131_GW_B"
},
"6.9.55-28131": {
"appid": 537246115,
"qua": "V1_MAC_NQ_6.9.55_28131_GW_B"
},
"9.9.15-28327":{
"appid": 537249321,
"qua": "V1_WIN_NQ_9.9.15_28327_GW_B"
},
"3.2.12-28327":{
"appid": 537249393,
"qua": "V1_LNX_NQ_3.2.12_28327_GW_B"
},
"9.9.15-28418":{
"appid": 537249321,
"qua": "V1_WIN_NQ_9.9.15_28418_GW_B"
},
"3.2.12-28418":{
"appid": 537249393,
"qua": "V1_LNX_NQ_3.2.12_28418_GW_B"
},
"6.9.56-28418": {
"appid": 537249367,
"qua": "V1_MAC_NQ_6.9.56_28418_GW_B"
},
"9.9.15-28498":{
"appid": 537249321,
"qua": "V1_WIN_NQ_9.9.15_28498_GW_B"
}
}

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

@@ -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;
}
@@ -26,7 +26,8 @@ export class RkeyManager {
try {
await this.refreshRkey();
} catch (e) {
this.logger.logError('获取rkey失败', e);
throw new Error(`获取rkey失败: ${e}`);//外抛
//this.logger.logError.bind(this.logger)('获取rkey失败', e);
}
}
return this.rkeyData;
@@ -40,6 +41,22 @@ export class RkeyManager {
async refreshRkey(): Promise<any> {
//刷新rkey
this.rkeyData = await RequestUtil.HttpGetJson<ServerRkeyData>(this.serverUrl, 'GET');
for (const url of this.serverUrl) {
try {
let temp = await RequestUtil.HttpGetJson<ServerRkeyData>(url, 'GET');
this.rkeyData = {
group_rkey: temp.group_rkey.slice(6),
private_rkey: temp.private_rkey.slice(6),
expired_time: temp.expired_time
};
} catch (e) {
this.logger.logError.bind(this.logger)(`[Rkey] Get Rkey ${url} Error `, e);
//是否为最后一个url
if (url === this.serverUrl[this.serverUrl.length - 1]) {
throw new Error(`获取rkey失败: ${e}`);//外抛
}
}
}
}
}

View File

@@ -22,14 +22,13 @@ 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 { 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';
@@ -45,12 +44,18 @@ export function loadQQWrapper(QQVersion: string): WrapperNodeApi {
let appPath;
if (os.platform() === 'darwin') {
appPath = path.resolve(path.dirname(process.execPath), '../Resources/app');
} else {
} 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, `versions/${QQVersion}/wrapper.node`);
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);
@@ -93,7 +98,8 @@ export class NapCatCore {
if (!fs.existsSync(this.NapCatTempPath)) {
fs.mkdirSync(this.NapCatTempPath, { recursive: true });
}
this.initNapCatCoreListeners().then().catch(this.context.logger.logError);
this.initNapCatCoreListeners().then().catch(this.context.logger.logError.bind(this.context.logger));
this.context.logger.setFileLogEnabled(
this.configLoader.configData.fileLog,
@@ -121,7 +127,7 @@ export class NapCatCore {
const msgListener = new NodeIKernelMsgListener();
msgListener.onKickedOffLine = (Info: KickedOffLineInfo) => {
// 下线通知
this.context.logger.logError('[KickedOffLine] [' + Info.tipsTitle + '] ' + Info.tipsDesc);
this.context.logger.logError.bind(this.context.logger)('[KickedOffLine] [' + Info.tipsTitle + '] ' + Info.tipsDesc);
this.selfInfo.online = false;
};
msgListener.onRecvMsg = (msgs) => {
@@ -145,8 +151,9 @@ export class NapCatCore {
if (Info.status == 20) {
this.selfInfo.online = false;
this.context.logger.log("账号状态变更为离线");
} else {
this.selfInfo.online = true;
}
this.selfInfo.online = true;
};
this.context.session.getProfileService().addKernelProfileListener(
proxiedListenerOf(profileListener, this.context.logger),
@@ -240,22 +247,34 @@ export class NapCatCore {
}
}
export async function genSessionConfig(QQVersionAppid: string, QQVersion: string, selfUin: string, selfUid: string, account_path: string): Promise<WrapperSessionInitConfig> {
export async function genSessionConfig(
guid: string,
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
const platformMapping: Partial<Record<NodeJS.Platform, PlatformType>> = {
win32: PlatformType.KWINDOWS,
darwin: PlatformType.KMAC,
linux: PlatformType.KLINUX,
};
const systemPlatform = platformMapping[os.platform()] ?? PlatformType.KWINDOWS;
return {
selfUin,
selfUid,
desktopPathConfig: {
account_path, // 可以通过NodeQQNTWrapperUtil().getNTUserDataInfoConfig()获取
},
clientVer: QQVersion, // 9.9.8-22355
clientVer: QQVersion,
a2: '',
d2: '',
d2Key: '',
machineId: '',
platform: PlatformType.KWINDOWS, // 3是Windows?
platform: systemPlatform, // 3是Windows?
platVer: systemVersion, // 系统版本号, 应该可以固定
appid: QQVersionAppid,
rdeliveryConfig: {
@@ -263,7 +282,7 @@ export async function genSessionConfig(QQVersionAppid: string, QQVersion: string
systemId: 0,
appId: '',
logicEnvironment: '',
platform: PlatformType.KWINDOWS,
platform: systemPlatform,
language: '',
sdkVersion: '',
userId: '',

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,39 +0,0 @@
import { ChatType } from '@/core';
export interface NodeIKernelSearchListener_Polyfill {
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

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

View File

@@ -9,7 +9,7 @@ export * from './NodeIKernelProfileListener';
export * from './NodeIKernelTicketListener';
export * from './NodeIKernelStorageCleanListener';
export * from './NodeIKernelFileAssistantListener';
export * from './NodeIKernelSearchListener_Polyfill';
export * from './NodeIKernelSearchListener';
import type {
NodeIKernelBuddyListener,
@@ -19,11 +19,11 @@ import type {
NodeIKernelMsgListener,
NodeIKernelProfileListener,
NodeIKernelRobotListener,
NodeIKernelSearchListener_Polyfill,
NodeIKernelSessionListener,
NodeIKernelStorageCleanListener,
NodeIKernelTicketListener,
} from '.';
import { NodeIKernelSearchListener } from './NodeIKernelSearchListener';
export type ListenerNamingMapping = {
NodeIKernelSessionListener: NodeIKernelSessionListener;
@@ -36,5 +36,5 @@ export type ListenerNamingMapping = {
NodeIKernelTicketListener: NodeIKernelTicketListener;
NodeIKernelStorageCleanListener: NodeIKernelStorageCleanListener;
NodeIKernelFileAssistantListener: NodeIKernelFileAssistantListener;
NodeIKernelSearchListener: NodeIKernelSearchListener_Polyfill;
NodeIKernelSearchListener: NodeIKernelSearchListener;
};

View File

@@ -1,341 +0,0 @@
// @generated by protobuf-ts 2.9.4
// @generated from protobuf file "EmojiLikeToOthers.proto" (package "SysMessage", syntax proto3)
// tslint:disable
import type {
BinaryReadOptions,
BinaryWriteOptions,
IBinaryReader,
IBinaryWriter,
PartialMessage,
} from '@protobuf-ts/runtime';
import { MessageType, reflectionMergePartial, UnknownFieldHandler, WireType } from '@protobuf-ts/runtime';
/**
* @generated from protobuf message SysMessage.EmojiLikeToOthersWrapper1
*/
export interface EmojiLikeToOthersWrapper1 {
/**
* @generated from protobuf field: SysMessage.EmojiLikeToOthersWrapper2 wrapper = 1;
*/
wrapper?: EmojiLikeToOthersWrapper2;
}
/**
* @generated from protobuf message SysMessage.EmojiLikeToOthersWrapper2
*/
export interface EmojiLikeToOthersWrapper2 {
/**
* @generated from protobuf field: SysMessage.EmojiLikeToOthersWrapper3 body = 1;
*/
body?: EmojiLikeToOthersWrapper3;
}
/**
* @generated from protobuf message SysMessage.EmojiLikeToOthersWrapper3
*/
export interface EmojiLikeToOthersWrapper3 {
/**
* @generated from protobuf field: SysMessage.EmojiLikeToOthersMsgSpec msgSpec = 2;
*/
msgSpec?: EmojiLikeToOthersMsgSpec;
/**
* @generated from protobuf field: SysMessage.EmojiLikeToOthersAttributes attributes = 3;
*/
attributes?: EmojiLikeToOthersAttributes;
}
/**
* @generated from protobuf message SysMessage.EmojiLikeToOthersMsgSpec
*/
export interface EmojiLikeToOthersMsgSpec {
/**
* @generated from protobuf field: uint32 msgSeq = 1;
*/
msgSeq: number;
}
/**
* @generated from protobuf message SysMessage.EmojiLikeToOthersAttributes
*/
export interface EmojiLikeToOthersAttributes {
/**
* @generated from protobuf field: string emojiId = 1;
*/
emojiId: string;
/**
* @generated from protobuf field: string senderUid = 4;
*/
senderUid: string;
/**
* @generated from protobuf field: SysMessage.EmojiLikeToOthersAttributes.Operation operation = 5;
*/
operation: EmojiLikeToOthersAttributes_Operation;
}
/**
* @generated from protobuf enum SysMessage.EmojiLikeToOthersAttributes.Operation
*/
export enum EmojiLikeToOthersAttributes_Operation {
/**
* @generated from protobuf enum value: FALLBACK = 0;
*/
FALLBACK = 0,
/**
* @generated from protobuf enum value: LIKE = 1;
*/
LIKE = 1,
/**
* @generated from protobuf enum value: UNLIKE = 2;
*/
UNLIKE = 2
}
// @generated message type with reflection information, may provide speed optimized methods
class EmojiLikeToOthersWrapper1$Type extends MessageType<EmojiLikeToOthersWrapper1> {
constructor() {
super("SysMessage.EmojiLikeToOthersWrapper1", [
{ no: 1, name: "wrapper", kind: "message", T: () => EmojiLikeToOthersWrapper2 }
]);
}
create(value?: PartialMessage<EmojiLikeToOthersWrapper1>): EmojiLikeToOthersWrapper1 {
const message = globalThis.Object.create((this.messagePrototype!));
if (value !== undefined)
reflectionMergePartial<EmojiLikeToOthersWrapper1>(this, message, value);
return message;
}
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: EmojiLikeToOthersWrapper1): EmojiLikeToOthersWrapper1 {
let message = target ?? this.create(), end = reader.pos + length;
while (reader.pos < end) {
let [fieldNo, wireType] = reader.tag();
switch (fieldNo) {
case /* SysMessage.EmojiLikeToOthersWrapper2 wrapper */ 1:
message.wrapper = EmojiLikeToOthersWrapper2.internalBinaryRead(reader, reader.uint32(), options, message.wrapper);
break;
default:
let u = options.readUnknownField;
if (u === "throw")
throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
let d = reader.skip(wireType);
if (u !== false)
(u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
}
}
return message;
}
internalBinaryWrite(message: EmojiLikeToOthersWrapper1, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
/* SysMessage.EmojiLikeToOthersWrapper2 wrapper = 1; */
if (message.wrapper)
EmojiLikeToOthersWrapper2.internalBinaryWrite(message.wrapper, writer.tag(1, WireType.LengthDelimited).fork(), options).join();
let u = options.writeUnknownFields;
if (u !== false)
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
return writer;
}
}
/**
* @generated MessageType for protobuf message SysMessage.EmojiLikeToOthersWrapper1
*/
export const EmojiLikeToOthersWrapper1 = new EmojiLikeToOthersWrapper1$Type();
// @generated message type with reflection information, may provide speed optimized methods
class EmojiLikeToOthersWrapper2$Type extends MessageType<EmojiLikeToOthersWrapper2> {
constructor() {
super("SysMessage.EmojiLikeToOthersWrapper2", [
{ no: 1, name: "body", kind: "message", T: () => EmojiLikeToOthersWrapper3 }
]);
}
create(value?: PartialMessage<EmojiLikeToOthersWrapper2>): EmojiLikeToOthersWrapper2 {
const message = globalThis.Object.create((this.messagePrototype!));
if (value !== undefined)
reflectionMergePartial<EmojiLikeToOthersWrapper2>(this, message, value);
return message;
}
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: EmojiLikeToOthersWrapper2): EmojiLikeToOthersWrapper2 {
let message = target ?? this.create(), end = reader.pos + length;
while (reader.pos < end) {
let [fieldNo, wireType] = reader.tag();
switch (fieldNo) {
case /* SysMessage.EmojiLikeToOthersWrapper3 body */ 1:
message.body = EmojiLikeToOthersWrapper3.internalBinaryRead(reader, reader.uint32(), options, message.body);
break;
default:
let u = options.readUnknownField;
if (u === "throw")
throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
let d = reader.skip(wireType);
if (u !== false)
(u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
}
}
return message;
}
internalBinaryWrite(message: EmojiLikeToOthersWrapper2, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
/* SysMessage.EmojiLikeToOthersWrapper3 body = 1; */
if (message.body)
EmojiLikeToOthersWrapper3.internalBinaryWrite(message.body, writer.tag(1, WireType.LengthDelimited).fork(), options).join();
let u = options.writeUnknownFields;
if (u !== false)
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
return writer;
}
}
/**
* @generated MessageType for protobuf message SysMessage.EmojiLikeToOthersWrapper2
*/
export const EmojiLikeToOthersWrapper2 = new EmojiLikeToOthersWrapper2$Type();
// @generated message type with reflection information, may provide speed optimized methods
class EmojiLikeToOthersWrapper3$Type extends MessageType<EmojiLikeToOthersWrapper3> {
constructor() {
super("SysMessage.EmojiLikeToOthersWrapper3", [
{ no: 2, name: "msgSpec", kind: "message", T: () => EmojiLikeToOthersMsgSpec },
{ no: 3, name: "attributes", kind: "message", T: () => EmojiLikeToOthersAttributes }
]);
}
create(value?: PartialMessage<EmojiLikeToOthersWrapper3>): EmojiLikeToOthersWrapper3 {
const message = globalThis.Object.create((this.messagePrototype!));
if (value !== undefined)
reflectionMergePartial<EmojiLikeToOthersWrapper3>(this, message, value);
return message;
}
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: EmojiLikeToOthersWrapper3): EmojiLikeToOthersWrapper3 {
let message = target ?? this.create(), end = reader.pos + length;
while (reader.pos < end) {
let [fieldNo, wireType] = reader.tag();
switch (fieldNo) {
case /* SysMessage.EmojiLikeToOthersMsgSpec msgSpec */ 2:
message.msgSpec = EmojiLikeToOthersMsgSpec.internalBinaryRead(reader, reader.uint32(), options, message.msgSpec);
break;
case /* SysMessage.EmojiLikeToOthersAttributes attributes */ 3:
message.attributes = EmojiLikeToOthersAttributes.internalBinaryRead(reader, reader.uint32(), options, message.attributes);
break;
default:
let u = options.readUnknownField;
if (u === "throw")
throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
let d = reader.skip(wireType);
if (u !== false)
(u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
}
}
return message;
}
internalBinaryWrite(message: EmojiLikeToOthersWrapper3, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
/* SysMessage.EmojiLikeToOthersMsgSpec msgSpec = 2; */
if (message.msgSpec)
EmojiLikeToOthersMsgSpec.internalBinaryWrite(message.msgSpec, writer.tag(2, WireType.LengthDelimited).fork(), options).join();
/* SysMessage.EmojiLikeToOthersAttributes attributes = 3; */
if (message.attributes)
EmojiLikeToOthersAttributes.internalBinaryWrite(message.attributes, writer.tag(3, WireType.LengthDelimited).fork(), options).join();
let u = options.writeUnknownFields;
if (u !== false)
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
return writer;
}
}
/**
* @generated MessageType for protobuf message SysMessage.EmojiLikeToOthersWrapper3
*/
export const EmojiLikeToOthersWrapper3 = new EmojiLikeToOthersWrapper3$Type();
// @generated message type with reflection information, may provide speed optimized methods
class EmojiLikeToOthersMsgSpec$Type extends MessageType<EmojiLikeToOthersMsgSpec> {
constructor() {
super("SysMessage.EmojiLikeToOthersMsgSpec", [
{ no: 1, name: "msgSeq", kind: "scalar", T: 13 /*ScalarType.UINT32*/ }
]);
}
create(value?: PartialMessage<EmojiLikeToOthersMsgSpec>): EmojiLikeToOthersMsgSpec {
const message = globalThis.Object.create((this.messagePrototype!));
message.msgSeq = 0;
if (value !== undefined)
reflectionMergePartial<EmojiLikeToOthersMsgSpec>(this, message, value);
return message;
}
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: EmojiLikeToOthersMsgSpec): EmojiLikeToOthersMsgSpec {
let message = target ?? this.create(), end = reader.pos + length;
while (reader.pos < end) {
let [fieldNo, wireType] = reader.tag();
switch (fieldNo) {
case /* uint32 msgSeq */ 1:
message.msgSeq = reader.uint32();
break;
default:
let u = options.readUnknownField;
if (u === "throw")
throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
let d = reader.skip(wireType);
if (u !== false)
(u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
}
}
return message;
}
internalBinaryWrite(message: EmojiLikeToOthersMsgSpec, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
/* uint32 msgSeq = 1; */
if (message.msgSeq !== 0)
writer.tag(1, WireType.Varint).uint32(message.msgSeq);
let u = options.writeUnknownFields;
if (u !== false)
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
return writer;
}
}
/**
* @generated MessageType for protobuf message SysMessage.EmojiLikeToOthersMsgSpec
*/
export const EmojiLikeToOthersMsgSpec = new EmojiLikeToOthersMsgSpec$Type();
// @generated message type with reflection information, may provide speed optimized methods
class EmojiLikeToOthersAttributes$Type extends MessageType<EmojiLikeToOthersAttributes> {
constructor() {
super("SysMessage.EmojiLikeToOthersAttributes", [
{ no: 1, name: "emojiId", kind: "scalar", T: 9 /*ScalarType.STRING*/ },
{ no: 4, name: "senderUid", kind: "scalar", T: 9 /*ScalarType.STRING*/ },
{ no: 5, name: "operation", kind: "enum", T: () => ["SysMessage.EmojiLikeToOthersAttributes.Operation", EmojiLikeToOthersAttributes_Operation] }
]);
}
create(value?: PartialMessage<EmojiLikeToOthersAttributes>): EmojiLikeToOthersAttributes {
const message = globalThis.Object.create((this.messagePrototype!));
message.emojiId = "";
message.senderUid = "";
message.operation = 0;
if (value !== undefined)
reflectionMergePartial<EmojiLikeToOthersAttributes>(this, message, value);
return message;
}
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: EmojiLikeToOthersAttributes): EmojiLikeToOthersAttributes {
let message = target ?? this.create(), end = reader.pos + length;
while (reader.pos < end) {
let [fieldNo, wireType] = reader.tag();
switch (fieldNo) {
case /* string emojiId */ 1:
message.emojiId = reader.string();
break;
case /* string senderUid */ 4:
message.senderUid = reader.string();
break;
case /* SysMessage.EmojiLikeToOthersAttributes.Operation operation */ 5:
message.operation = reader.int32();
break;
default:
let u = options.readUnknownField;
if (u === "throw")
throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
let d = reader.skip(wireType);
if (u !== false)
(u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
}
}
return message;
}
internalBinaryWrite(message: EmojiLikeToOthersAttributes, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
/* string emojiId = 1; */
if (message.emojiId !== "")
writer.tag(1, WireType.LengthDelimited).string(message.emojiId);
/* string senderUid = 4; */
if (message.senderUid !== "")
writer.tag(4, WireType.LengthDelimited).string(message.senderUid);
/* SysMessage.EmojiLikeToOthersAttributes.Operation operation = 5; */
if (message.operation !== 0)
writer.tag(5, WireType.Varint).int32(message.operation);
let u = options.writeUnknownFields;
if (u !== false)
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
return writer;
}
}
/**
* @generated MessageType for protobuf message SysMessage.EmojiLikeToOthersAttributes
*/
export const EmojiLikeToOthersAttributes = new EmojiLikeToOthersAttributes$Type();

View File

@@ -1,104 +0,0 @@
// @generated by protobuf-ts 2.9.4
// @generated from protobuf file "GreyTipWrapper.proto" (package "SysMessage", syntax proto3)
// tslint:disable
import type {
BinaryReadOptions,
BinaryWriteOptions,
IBinaryReader,
IBinaryWriter,
PartialMessage,
} from '@protobuf-ts/runtime';
import { MessageType, reflectionMergePartial, UnknownFieldHandler, WireType } from '@protobuf-ts/runtime';
/**
* @generated from protobuf message SysMessage.GreyTipWrapper
*/
export interface GreyTipWrapper {
/**
* @generated from protobuf field: uint32 subTypeId = 1;
*/
subTypeId: number;
/**
* @generated from protobuf field: uint32 groupCode = 4;
*/
groupCode: number;
/**
* @generated from protobuf field: uint32 subTypeIdMinusOne = 13;
*/
subTypeIdMinusOne: number;
/**
* @generated from protobuf field: bytes rest = 44;
*/
rest: Uint8Array;
}
// @generated message type with reflection information, may provide speed optimized methods
class GreyTipWrapper$Type extends MessageType<GreyTipWrapper> {
constructor() {
super("SysMessage.GreyTipWrapper", [
{ no: 1, name: "subTypeId", kind: "scalar", T: 13 /*ScalarType.UINT32*/ },
{ no: 4, name: "groupCode", kind: "scalar", T: 13 /*ScalarType.UINT32*/ },
{ no: 13, name: "subTypeIdMinusOne", kind: "scalar", T: 13 /*ScalarType.UINT32*/ },
{ no: 44, name: "rest", kind: "scalar", T: 12 /*ScalarType.BYTES*/ }
]);
}
create(value?: PartialMessage<GreyTipWrapper>): GreyTipWrapper {
const message = globalThis.Object.create((this.messagePrototype!));
message.subTypeId = 0;
message.groupCode = 0;
message.subTypeIdMinusOne = 0;
message.rest = new Uint8Array(0);
if (value !== undefined)
reflectionMergePartial<GreyTipWrapper>(this, message, value);
return message;
}
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: GreyTipWrapper): GreyTipWrapper {
let message = target ?? this.create(), end = reader.pos + length;
while (reader.pos < end) {
let [fieldNo, wireType] = reader.tag();
switch (fieldNo) {
case /* uint32 subTypeId */ 1:
message.subTypeId = reader.uint32();
break;
case /* uint32 groupCode */ 4:
message.groupCode = reader.uint32();
break;
case /* uint32 subTypeIdMinusOne */ 13:
message.subTypeIdMinusOne = reader.uint32();
break;
case /* bytes rest */ 44:
message.rest = reader.bytes();
break;
default:
let u = options.readUnknownField;
if (u === "throw")
throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
let d = reader.skip(wireType);
if (u !== false)
(u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
}
}
return message;
}
internalBinaryWrite(message: GreyTipWrapper, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
/* uint32 subTypeId = 1; */
if (message.subTypeId !== 0)
writer.tag(1, WireType.Varint).uint32(message.subTypeId);
/* uint32 groupCode = 4; */
if (message.groupCode !== 0)
writer.tag(4, WireType.Varint).uint32(message.groupCode);
/* uint32 subTypeIdMinusOne = 13; */
if (message.subTypeIdMinusOne !== 0)
writer.tag(13, WireType.Varint).uint32(message.subTypeIdMinusOne);
/* bytes rest = 44; */
if (message.rest.length)
writer.tag(44, WireType.LengthDelimited).bytes(message.rest);
let u = options.writeUnknownFields;
if (u !== false)
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
return writer;
}
}
/**
* @generated MessageType for protobuf message SysMessage.GreyTipWrapper
*/
export const GreyTipWrapper = new GreyTipWrapper$Type();

View File

@@ -0,0 +1,21 @@
import { MessageType, BinaryReader, ScalarType, BinaryWriter } from '@protobuf-ts/runtime';
export const FileId = new MessageType("FileId", [
{ no: 2, name: "sha1", kind: "scalar", T: ScalarType.BYTES },
{ no: 4, name: "appid", kind: "scalar", T: ScalarType.UINT32 },
]);
export function encodePBFileId(message: any) {
return FileId.internalBinaryWrite(message, new BinaryWriter(), {
writerFactory: () => new BinaryWriter(),
writeUnknownFields: false
}).finish();
}
export function decodePBFileId(buffer: Uint8Array): any {
const reader = new BinaryReader(buffer);
return FileId.internalBinaryRead(reader, reader.len, {
readUnknownField: true,
readerFactory: () => new BinaryReader(buffer)
});
}

48
src/core/proto/Message.ts Normal file
View File

@@ -0,0 +1,48 @@
import { MessageType, BinaryReader, ScalarType } from '@protobuf-ts/runtime';
export const BodyInner = new MessageType("BodyInner", [
{ no: 1, name: "msgType", kind: "scalar", T: ScalarType.UINT32 /* uint32 */, opt: true },
{ no: 2, name: "subType", kind: "scalar", T: ScalarType.UINT32 /* uint32 */, opt: true }
]);
export const NoifyData = new MessageType("NoifyData", [
{ no: 1, name: "skip", kind: "scalar", T: ScalarType.BYTES /* bytes */, opt: true },
{ no: 2, name: "innerData", kind: "scalar", T: ScalarType.BYTES /* bytes */, opt: true }
]);
export const MsgHead = new MessageType("MsgHead", [
{ no: 2, name: "bodyInner", kind: "message", T: () => BodyInner, opt: true },
{ no: 3, name: "noifyData", kind: "message", T: () => NoifyData, opt: true }
]);
export const Message = new MessageType("Message", [
{ no: 1, name: "msgHead", kind: "message", T: () => MsgHead }
]);
export const SubDetail = new MessageType("SubDetail", [
{ no: 1, name: "msgSeq", kind: "scalar", T: ScalarType.UINT32 /* uint32 */ },
{ no: 2, name: "msgTime", kind: "scalar", T: ScalarType.UINT32 /* uint32 */ },
{ no: 6, name: "senderUid", kind: "scalar", T: ScalarType.STRING /* string */ }
]);
export const RecallDetails = new MessageType("RecallDetails", [
{ no: 1, name: "operatorUid", kind: "scalar", T: ScalarType.STRING /* string */ },
{ no: 3, name: "subDetail", kind: "message", T: () => SubDetail }
]);
export const RecallGroup = new MessageType("RecallGroup", [
{ no: 1, name: "type", kind: "scalar", T: ScalarType.INT32 /* int32 */ },
{ no: 4, name: "peerUid", kind: "scalar", T: ScalarType.UINT32 /* uint32 */ },
{ no: 11, name: "recallDetails", kind: "message", T: () => RecallDetails },
{ no: 37, name: "grayTipsSeq", kind: "scalar", T: ScalarType.UINT32 /* uint32 */ }
]);
export function decodeMessage(buffer: Uint8Array): any {
const reader = new BinaryReader(buffer);
return Message.internalBinaryRead(reader, reader.len, { readUnknownField: true, readerFactory: () => new BinaryReader(buffer) });
}
export function decodeRecallGroup(buffer: Uint8Array): any {
const reader = new BinaryReader(buffer);
return RecallGroup.internalBinaryRead(reader, reader.len, { readUnknownField: true, readerFactory: () => new BinaryReader(buffer) });
}

30
src/core/proto/Poke.ts Normal file
View File

@@ -0,0 +1,30 @@
import { MessageType, ScalarType, BinaryWriter } from '@protobuf-ts/runtime';
export const OidbSvcTrpcTcpBase = new MessageType("oidb_svc_trpctcp_base", [
{ no: 1, name: "command", kind: "scalar", T: ScalarType.UINT32 },
{ no: 2, name: "subcommand", kind: "scalar", T: ScalarType.UINT32, opt: true },
{ no: 4, name: "body", kind: "scalar", T: ScalarType.BYTES, opt: true }
]);
export const OidbSvcTrpcTcp0XED3_1 = new MessageType("oidb_svc_trpctcp_0xed3_1", [
{ no: 1, name: "uin", kind: "scalar", T: ScalarType.UINT32 },
{ no: 2, name: "groupuin", kind: "scalar", T: ScalarType.UINT32, opt: true },
{ no: 5, name: "frienduin", kind: "scalar", T: ScalarType.UINT32, opt: true },
{ no: 6, name: "ext", kind: "scalar", T: ScalarType.UINT32 }
]);
export function encodeGroupPoke(groupUin: string, PeerUin: string) {
let Body = OidbSvcTrpcTcp0XED3_1.toBinary
({
uin: parseInt(PeerUin),
groupuin: parseInt(groupUin),
ext: 0
});
//console.log(Body)
return OidbSvcTrpcTcpBase.toBinary
({
command: 0xed3,
subcommand: 1,
body: Body
});
}

View File

@@ -0,0 +1,58 @@
import { MessageType, BinaryReader, ScalarType, RepeatType } from '@protobuf-ts/runtime';
export const LikeDetail = new MessageType("likeDetail", [
{ no: 1, name: "txt", kind: "scalar", T: ScalarType.STRING /* string */ },
{ no: 3, name: "uin", kind: "scalar", T: ScalarType.INT64 /* int64 */ },
{ no: 5, name: "nickname", kind: "scalar", T: ScalarType.STRING /* string */ }
]);
export const LikeMsg = new MessageType("likeMsg", [
{ no: 1, name: "times", kind: "scalar", T: ScalarType.INT32 /* int32 */ },
{ no: 2, name: "time", kind: "scalar", T: ScalarType.INT32 /* int32 */ },
{ no: 3, name: "detail", kind: "message", T: () => LikeDetail }
]);
export const ProfileLikeSubTip = new MessageType("profileLikeSubTip", [
{ no: 14, name: "msg", kind: "message", T: () => LikeMsg }
]);
export const ProfileLikeTip = new MessageType("profileLikeTip", [
{ no: 1, name: "msgType", kind: "scalar", T: ScalarType.INT32 /* int32 */ },
{ no: 2, name: "subType", kind: "scalar", T: ScalarType.INT32 /* int32 */ },
{ no: 203, name: "content", kind: "message", T: () => ProfileLikeSubTip }
]);
export const SysMessageHeader = new MessageType("SysMessageHeader", [
{ no: 1, name: "PeerNumber", kind: "scalar", T: ScalarType.UINT32 /* uint32 */ },
{ no: 2, name: "PeerString", kind: "scalar", T: ScalarType.STRING /* string */ },
{ no: 5, name: "Uin", kind: "scalar", T: ScalarType.UINT32 /* uint32 */ },
{ no: 6, name: "Uid", kind: "scalar", T: ScalarType.STRING /* string */, opt: true }
]);
export const SysMessageMsgSpec = new MessageType("SysMessageMsgSpec", [
{ no: 1, name: "msgType", kind: "scalar", T: ScalarType.UINT32 /* uint32 */ },
{ no: 2, name: "subType", kind: "scalar", T: ScalarType.UINT32 /* uint32 */ },
{ no: 3, name: "subSubType", kind: "scalar", T: ScalarType.UINT32 /* uint32 */ },
{ no: 5, name: "msgSeq", kind: "scalar", T: ScalarType.UINT32 /* uint32 */ },
{ no: 6, name: "time", kind: "scalar", T: ScalarType.UINT32 /* uint32 */ },
{ no: 12, name: "msgId", kind: "scalar", T: ScalarType.UINT64 /* uint64 */ },
{ no: 13, name: "other", kind: "scalar", T: ScalarType.UINT32 /* uint32 */ }
]);
export const SysMessageBodyWrapper = new MessageType("SysMessageBodyWrapper", [
{ no: 2, name: "wrappedBody", kind: "scalar", T: ScalarType.BYTES /* bytes */ }
]);
export const SysMessage = new MessageType("SysMessage", [
{ no: 1, name: "header", kind: "message", T: () => SysMessageHeader, repeat: RepeatType.UNPACKED },
{ no: 2, name: "msgSpec", kind: "message", T: () => SysMessageMsgSpec, repeat: RepeatType.UNPACKED },
{ no: 3, name: "bodyWrapper", kind: "message", T: () => SysMessageBodyWrapper }
]);
export function decodeProfileLikeTip(buffer: Uint8Array): any {
const reader = new BinaryReader(buffer);
return ProfileLikeTip.internalBinaryRead(reader, reader.len, { readUnknownField: true, readerFactory: () => new BinaryReader(buffer) });
}
export function decodeSysMessage(buffer: Uint8Array): any {
const reader = new BinaryReader(buffer);
return SysMessage.internalBinaryRead(reader, reader.len, { readUnknownField: true, readerFactory: () => new BinaryReader(buffer) });
}

View File

@@ -1,435 +0,0 @@
// @generated by protobuf-ts 2.9.4
// @generated from protobuf file "SysMessage.proto" (package "SysMessage", syntax proto3)
// tslint:disable
import type {
BinaryReadOptions,
BinaryWriteOptions,
IBinaryReader,
IBinaryWriter,
PartialMessage,
} from '@protobuf-ts/runtime';
import { MessageType, reflectionMergePartial, UnknownFieldHandler, WireType } from '@protobuf-ts/runtime';
/**
* @generated from protobuf message SysMessage.SysMessage
*/
export interface SysMessage {
/**
* @generated from protobuf field: repeated SysMessage.SysMessageHeader header = 1;
*/
header: SysMessageHeader[];
/**
* @generated from protobuf field: repeated SysMessage.SysMessageMsgSpec msgSpec = 2;
*/
msgSpec: SysMessageMsgSpec[];
/**
* @generated from protobuf field: SysMessage.SysMessageBodyWrapper bodyWrapper = 3;
*/
bodyWrapper?: SysMessageBodyWrapper;
}
/**
* @generated from protobuf message SysMessage.SysMessageHeader
*/
export interface SysMessageHeader {
/**
* @generated from protobuf field: uint32 PeerNumber = 1 [json_name = "PeerNumber"];
*/
peerNumber: number;
/**
* @generated from protobuf field: string PeerString = 2 [json_name = "PeerString"];
*/
peerString: string;
/**
* @generated from protobuf field: uint32 Uin = 5 [json_name = "Uin"];
*/
uin: number;
/**
* @generated from protobuf field: optional string Uid = 6 [json_name = "Uid"];
*/
uid?: string;
}
/**
* @generated from protobuf message SysMessage.SysMessageMsgSpec
*/
export interface SysMessageMsgSpec {
/**
* @generated from protobuf field: uint32 msgType = 1;
*/
msgType: number;
/**
* @generated from protobuf field: uint32 subType = 2;
*/
subType: number;
/**
* @generated from protobuf field: uint32 subSubType = 3;
*/
subSubType: number;
/**
* @generated from protobuf field: uint32 msgSeq = 5;
*/
msgSeq: number;
/**
* @generated from protobuf field: uint32 time = 6;
*/
time: number;
/**
* @generated from protobuf field: uint64 msgId = 12;
*/
msgId: bigint;
/**
* @generated from protobuf field: uint32 other = 13;
*/
other: number;
}
/**
* @generated from protobuf message SysMessage.SysMessageBodyWrapper
*/
export interface SysMessageBodyWrapper {
/**
* @generated from protobuf field: bytes wrappedBody = 2;
*/
wrappedBody: Uint8Array; // Find the first [08], or ignore the first 7 bytes?
// And it becomes another ProtoBuf message.
}
/**
* @generated from protobuf message SysMessage.KeyValuePair
*/
export interface KeyValuePair {
/**
* @generated from protobuf field: string key = 1;
*/
key: string;
/**
* @generated from protobuf field: string value = 2;
*/
value: string;
}
// @generated message type with reflection information, may provide speed optimized methods
class SysMessage$Type extends MessageType<SysMessage> {
constructor() {
super("SysMessage.SysMessage", [
{ no: 1, name: "header", kind: "message", repeat: 1 /*RepeatType.PACKED*/, T: () => SysMessageHeader },
{ no: 2, name: "msgSpec", kind: "message", repeat: 1 /*RepeatType.PACKED*/, T: () => SysMessageMsgSpec },
{ no: 3, name: "bodyWrapper", kind: "message", T: () => SysMessageBodyWrapper }
]);
}
create(value?: PartialMessage<SysMessage>): SysMessage {
const message = globalThis.Object.create((this.messagePrototype!));
message.header = [];
message.msgSpec = [];
if (value !== undefined)
reflectionMergePartial<SysMessage>(this, message, value);
return message;
}
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: SysMessage): SysMessage {
let message = target ?? this.create(), end = reader.pos + length;
while (reader.pos < end) {
let [fieldNo, wireType] = reader.tag();
switch (fieldNo) {
case /* repeated SysMessage.SysMessageHeader header */ 1:
message.header.push(SysMessageHeader.internalBinaryRead(reader, reader.uint32(), options));
break;
case /* repeated SysMessage.SysMessageMsgSpec msgSpec */ 2:
message.msgSpec.push(SysMessageMsgSpec.internalBinaryRead(reader, reader.uint32(), options));
break;
case /* SysMessage.SysMessageBodyWrapper bodyWrapper */ 3:
message.bodyWrapper = SysMessageBodyWrapper.internalBinaryRead(reader, reader.uint32(), options, message.bodyWrapper);
break;
default:
let u = options.readUnknownField;
if (u === "throw")
throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
let d = reader.skip(wireType);
if (u !== false)
(u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
}
}
return message;
}
internalBinaryWrite(message: SysMessage, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
/* repeated SysMessage.SysMessageHeader header = 1; */
for (let i = 0; i < message.header.length; i++)
SysMessageHeader.internalBinaryWrite(message.header[i], writer.tag(1, WireType.LengthDelimited).fork(), options).join();
/* repeated SysMessage.SysMessageMsgSpec msgSpec = 2; */
for (let i = 0; i < message.msgSpec.length; i++)
SysMessageMsgSpec.internalBinaryWrite(message.msgSpec[i], writer.tag(2, WireType.LengthDelimited).fork(), options).join();
/* SysMessage.SysMessageBodyWrapper bodyWrapper = 3; */
if (message.bodyWrapper)
SysMessageBodyWrapper.internalBinaryWrite(message.bodyWrapper, writer.tag(3, WireType.LengthDelimited).fork(), options).join();
let u = options.writeUnknownFields;
if (u !== false)
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
return writer;
}
}
/**
* @generated MessageType for protobuf message SysMessage.SysMessage
*/
export const SysMessage = new SysMessage$Type();
// @generated message type with reflection information, may provide speed optimized methods
class SysMessageHeader$Type extends MessageType<SysMessageHeader> {
constructor() {
super("SysMessage.SysMessageHeader", [
{ no: 1, name: "PeerNumber", kind: "scalar", jsonName: "PeerNumber", T: 13 /*ScalarType.UINT32*/ },
{ no: 2, name: "PeerString", kind: "scalar", jsonName: "PeerString", T: 9 /*ScalarType.STRING*/ },
{ no: 5, name: "Uin", kind: "scalar", jsonName: "Uin", T: 13 /*ScalarType.UINT32*/ },
{ no: 6, name: "Uid", kind: "scalar", jsonName: "Uid", opt: true, T: 9 /*ScalarType.STRING*/ }
]);
}
create(value?: PartialMessage<SysMessageHeader>): SysMessageHeader {
const message = globalThis.Object.create((this.messagePrototype!));
message.peerNumber = 0;
message.peerString = "";
message.uin = 0;
if (value !== undefined)
reflectionMergePartial<SysMessageHeader>(this, message, value);
return message;
}
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: SysMessageHeader): SysMessageHeader {
let message = target ?? this.create(), end = reader.pos + length;
while (reader.pos < end) {
let [fieldNo, wireType] = reader.tag();
switch (fieldNo) {
case /* uint32 PeerNumber = 1 [json_name = "PeerNumber"];*/ 1:
message.peerNumber = reader.uint32();
break;
case /* string PeerString = 2 [json_name = "PeerString"];*/ 2:
message.peerString = reader.string();
break;
case /* uint32 Uin = 5 [json_name = "Uin"];*/ 5:
message.uin = reader.uint32();
break;
case /* optional string Uid = 6 [json_name = "Uid"];*/ 6:
message.uid = reader.string();
break;
default:
let u = options.readUnknownField;
if (u === "throw")
throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
let d = reader.skip(wireType);
if (u !== false)
(u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
}
}
return message;
}
internalBinaryWrite(message: SysMessageHeader, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
/* uint32 PeerNumber = 1 [json_name = "PeerNumber"]; */
if (message.peerNumber !== 0)
writer.tag(1, WireType.Varint).uint32(message.peerNumber);
/* string PeerString = 2 [json_name = "PeerString"]; */
if (message.peerString !== "")
writer.tag(2, WireType.LengthDelimited).string(message.peerString);
/* uint32 Uin = 5 [json_name = "Uin"]; */
if (message.uin !== 0)
writer.tag(5, WireType.Varint).uint32(message.uin);
/* optional string Uid = 6 [json_name = "Uid"]; */
if (message.uid !== undefined)
writer.tag(6, WireType.LengthDelimited).string(message.uid);
let u = options.writeUnknownFields;
if (u !== false)
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
return writer;
}
}
/**
* @generated MessageType for protobuf message SysMessage.SysMessageHeader
*/
export const SysMessageHeader = new SysMessageHeader$Type();
// @generated message type with reflection information, may provide speed optimized methods
class SysMessageMsgSpec$Type extends MessageType<SysMessageMsgSpec> {
constructor() {
super("SysMessage.SysMessageMsgSpec", [
{ no: 1, name: "msgType", kind: "scalar", T: 13 /*ScalarType.UINT32*/ },
{ no: 2, name: "subType", kind: "scalar", T: 13 /*ScalarType.UINT32*/ },
{ no: 3, name: "subSubType", kind: "scalar", T: 13 /*ScalarType.UINT32*/ },
{ no: 5, name: "msgSeq", kind: "scalar", T: 13 /*ScalarType.UINT32*/ },
{ no: 6, name: "time", kind: "scalar", T: 13 /*ScalarType.UINT32*/ },
{ no: 12, name: "msgId", kind: "scalar", T: 4 /*ScalarType.UINT64*/, L: 0 /*LongType.BIGINT*/ },
{ no: 13, name: "other", kind: "scalar", T: 13 /*ScalarType.UINT32*/ }
]);
}
create(value?: PartialMessage<SysMessageMsgSpec>): SysMessageMsgSpec {
const message = globalThis.Object.create((this.messagePrototype!));
message.msgType = 0;
message.subType = 0;
message.subSubType = 0;
message.msgSeq = 0;
message.time = 0;
message.msgId = 0n;
message.other = 0;
if (value !== undefined)
reflectionMergePartial<SysMessageMsgSpec>(this, message, value);
return message;
}
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: SysMessageMsgSpec): SysMessageMsgSpec {
let message = target ?? this.create(), end = reader.pos + length;
while (reader.pos < end) {
let [fieldNo, wireType] = reader.tag();
switch (fieldNo) {
case /* uint32 msgType */ 1:
message.msgType = reader.uint32();
break;
case /* uint32 subType */ 2:
message.subType = reader.uint32();
break;
case /* uint32 subSubType */ 3:
message.subSubType = reader.uint32();
break;
case /* uint32 msgSeq */ 5:
message.msgSeq = reader.uint32();
break;
case /* uint32 time */ 6:
message.time = reader.uint32();
break;
case /* uint64 msgId */ 12:
message.msgId = reader.uint64().toBigInt();
break;
case /* uint32 other */ 13:
message.other = reader.uint32();
break;
default:
let u = options.readUnknownField;
if (u === "throw")
throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
let d = reader.skip(wireType);
if (u !== false)
(u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
}
}
return message;
}
internalBinaryWrite(message: SysMessageMsgSpec, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
/* uint32 msgType = 1; */
if (message.msgType !== 0)
writer.tag(1, WireType.Varint).uint32(message.msgType);
/* uint32 subType = 2; */
if (message.subType !== 0)
writer.tag(2, WireType.Varint).uint32(message.subType);
/* uint32 subSubType = 3; */
if (message.subSubType !== 0)
writer.tag(3, WireType.Varint).uint32(message.subSubType);
/* uint32 msgSeq = 5; */
if (message.msgSeq !== 0)
writer.tag(5, WireType.Varint).uint32(message.msgSeq);
/* uint32 time = 6; */
if (message.time !== 0)
writer.tag(6, WireType.Varint).uint32(message.time);
/* uint64 msgId = 12; */
if (message.msgId !== 0n)
writer.tag(12, WireType.Varint).uint64(message.msgId);
/* uint32 other = 13; */
if (message.other !== 0)
writer.tag(13, WireType.Varint).uint32(message.other);
let u = options.writeUnknownFields;
if (u !== false)
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
return writer;
}
}
/**
* @generated MessageType for protobuf message SysMessage.SysMessageMsgSpec
*/
export const SysMessageMsgSpec = new SysMessageMsgSpec$Type();
// @generated message type with reflection information, may provide speed optimized methods
class SysMessageBodyWrapper$Type extends MessageType<SysMessageBodyWrapper> {
constructor() {
super("SysMessage.SysMessageBodyWrapper", [
{ no: 2, name: "wrappedBody", kind: "scalar", T: 12 /*ScalarType.BYTES*/ }
]);
}
create(value?: PartialMessage<SysMessageBodyWrapper>): SysMessageBodyWrapper {
const message = globalThis.Object.create((this.messagePrototype!));
message.wrappedBody = new Uint8Array(0);
if (value !== undefined)
reflectionMergePartial<SysMessageBodyWrapper>(this, message, value);
return message;
}
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: SysMessageBodyWrapper): SysMessageBodyWrapper {
let message = target ?? this.create(), end = reader.pos + length;
while (reader.pos < end) {
let [fieldNo, wireType] = reader.tag();
switch (fieldNo) {
case /* bytes wrappedBody */ 2:
message.wrappedBody = reader.bytes();
break;
default:
let u = options.readUnknownField;
if (u === "throw")
throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
let d = reader.skip(wireType);
if (u !== false)
(u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
}
}
return message;
}
internalBinaryWrite(message: SysMessageBodyWrapper, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
/* bytes wrappedBody = 2; */
if (message.wrappedBody.length)
writer.tag(2, WireType.LengthDelimited).bytes(message.wrappedBody);
let u = options.writeUnknownFields;
if (u !== false)
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
return writer;
}
}
/**
* @generated MessageType for protobuf message SysMessage.SysMessageBodyWrapper
*/
export const SysMessageBodyWrapper = new SysMessageBodyWrapper$Type();
// @generated message type with reflection information, may provide speed optimized methods
class KeyValuePair$Type extends MessageType<KeyValuePair> {
constructor() {
super("SysMessage.KeyValuePair", [
{ no: 1, name: "key", kind: "scalar", T: 9 /*ScalarType.STRING*/ },
{ no: 2, name: "value", kind: "scalar", T: 9 /*ScalarType.STRING*/ }
]);
}
create(value?: PartialMessage<KeyValuePair>): KeyValuePair {
const message = globalThis.Object.create((this.messagePrototype!));
message.key = "";
message.value = "";
if (value !== undefined)
reflectionMergePartial<KeyValuePair>(this, message, value);
return message;
}
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: KeyValuePair): KeyValuePair {
let message = target ?? this.create(), end = reader.pos + length;
while (reader.pos < end) {
let [fieldNo, wireType] = reader.tag();
switch (fieldNo) {
case /* string key */ 1:
message.key = reader.string();
break;
case /* string value */ 2:
message.value = reader.string();
break;
default:
let u = options.readUnknownField;
if (u === "throw")
throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
let d = reader.skip(wireType);
if (u !== false)
(u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
}
}
return message;
}
internalBinaryWrite(message: KeyValuePair, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
/* string key = 1; */
if (message.key !== "")
writer.tag(1, WireType.LengthDelimited).string(message.key);
/* string value = 2; */
if (message.value !== "")
writer.tag(2, WireType.LengthDelimited).string(message.value);
let u = options.writeUnknownFields;
if (u !== false)
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
return writer;
}
}
/**
* @generated MessageType for protobuf message SysMessage.KeyValuePair
*/
export const KeyValuePair = new KeyValuePair$Type();

View File

@@ -14,7 +14,7 @@ export interface NodeIKernelBuddyService {
}>
}>;
getBuddyListFromCache(callFrom: string): Promise<Array<
getBuddyListFromCache(reqType: BuddyListReqType): Promise<Array<
{
categoryId: number,//9999应该跳过 那是兜底数据吧
categorySortId: number,//排序方式
@@ -23,7 +23,7 @@ export interface NodeIKernelBuddyService {
onlineCount: number,//在线数目
buddyUids: Array<string>//Uids
}>>;
addKernelBuddyListener(listener: NodeIKernelBuddyListener): number;
getAllBuddyCount(): number;
@@ -114,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

@@ -240,7 +240,12 @@ export interface NodeIKernelGroupService {
getGroupRecommendContactArkJson(groupCode: string): unknown;
getJoinGroupLink(groupCode: string): unknown;
getJoinGroupLink(param: {
groupCode: string,
srcId: number,//73
needShortUrl: boolean,//true
additionalParam: string//''
}): Promise<GeneralCallResult & { url?: string }>;
modifyGroupExtInfo(groupCode: string, arg: unknown): void;

View File

@@ -1,4 +1,5 @@
import { NodeIKernelLoginListener } from '@/core/listeners/NodeIKernelLoginListener';
import { GeneralCallResult } from './common';
export interface LoginInitConfig {
machineId: '';
@@ -59,8 +60,12 @@ export interface QuickLoginResult {
}
export interface NodeIKernelLoginService {
// eslint-disable-next-line @typescript-eslint/no-misused-new
new(): NodeIKernelLoginService;
setLoginMiscData(arg0: string, value: string): unknown;
getMachineGuid(): string;
get(): NodeIKernelLoginService;
connect(): boolean;
addKernelLoginListener(listener: NodeIKernelLoginListener): number;
@@ -68,7 +73,7 @@ export interface NodeIKernelLoginService {
initConfig(config: LoginInitConfig): void;
getLoginMiscData(cb: (r: unknown) => void): void;
getLoginMiscData(data: string): Promise<GeneralCallResult & { value: string }>;
getLoginList(): Promise<{
result: number, // 0是ok

View File

@@ -1,7 +1,7 @@
import { ElementType, MessageElement, Peer, RawMessage, SendMessageElement } from '@/core/entities';
import { NodeIKernelMsgListener } from '@/core/listeners/NodeIKernelMsgListener';
import { GeneralCallResult } from '@/core/services/common';
import { QueryMsgsParams, TmpChatInfoApi } from '../entities/msg';
import { MsgReqType, QueryMsgsParams, TmpChatInfoApi } from '../entities/msg';
export interface NodeIKernelMsgService {
@@ -147,12 +147,15 @@ export interface NodeIKernelMsgService {
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[]
}>;
//@deprecated
getMsgsWithMsgTimeAndClientSeqForC2C(...args: unknown[]): Promise<GeneralCallResult & { msgList: RawMessage[] }>;
getMsgsWithStatus(params: {
@@ -168,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[]
}>;
@@ -179,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;
@@ -318,7 +323,7 @@ export interface NodeIKernelMsgService {
getFileThumbSavePath(...args: unknown[]): unknown;
translatePtt2Text(msgId: string, peer: Peer, msgElement: unknown): unknown;
translatePtt2Text(msgId: string, peer: Peer, msgElement: MessageElement): unknown;
setPttPlayedState(...args: unknown[]): unknown;
@@ -391,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;
@@ -518,7 +528,7 @@ export interface NodeIKernelMsgService {
canImportOldDbMsg(...args: unknown[]): unknown;
setPowerStatus(z: boolean): unknown;
setPowerStatus(isPowerOn: boolean): unknown;
canProcessDataMigration(...args: unknown[]): unknown;
@@ -607,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;

View File

@@ -1,6 +1,8 @@
import { GeneralCallResult } from './common';
export interface NodeIKernelNodeMiscService {
writeVersionToRegistry(version: string): void;
getMiniAppPath(): unknown;
setMiniAppVersion(version: string): unknown;

View File

@@ -1,8 +1,13 @@
import { AnyCnameRecord } from 'node:dns';
import { BizKey, ModifyProfileParams, NodeIKernelProfileListener, ProfileBizType, SimpleInfo, UserDetailInfoByUinV2, UserDetailSource } from '@/core';
import { BizKey, ModifyProfileParams, NodeIKernelProfileListener, ProfileBizType, SimpleInfo, UserDetailInfoByUin, UserDetailSource } from '@/core';
import { GeneralCallResult } from '@/core/services/common';
export interface NodeIKernelProfileService {
getOtherFlag(callfrom: string, uids: string[]): Promise<Map<string, any>>;
getVasInfo(callfrom: string, uids: string[]): Promise<Map<string, any>>;
getRelationFlag(callfrom: string, uids: string[]): Promise<Map<string, any>>;
getUidByUin(callfrom: string, uin: Array<string>): Promise<Map<string, string>>;
@@ -50,7 +55,7 @@ export interface NodeIKernelProfileService {
getUserDetailInfoWithBizInfo(uid: string, Biz: BizKey[]): Promise<GeneralCallResult>;
getUserDetailInfoByUin(uin: string): Promise<UserDetailInfoByUinV2>;
getUserDetailInfoByUin(uin: string): Promise<UserDetailInfoByUin>;
getZplanAvatarInfos(args: string[]): Promise<unknown>;

View File

@@ -1,4 +1,5 @@
import { ChatType } from '../entities';
import { GeneralCallResult } from './common';
export interface NodeIKernelSearchService {
@@ -8,7 +9,12 @@ export interface NodeIKernelSearchService {
searchStranger(unknown: string, searchStranger: unknown, searchParams: unknown): Promise<unknown>;
searchGroup(...args: any[]): unknown;// needs 1 arguments
searchGroup(param: {
keyWords: string,
groupNum: number,
exactSearch: boolean,
penetrate: string
}): Promise<GeneralCallResult>;// needs 1 arguments
searchLocalInfo(keywords: string, unknown: number/*4*/): unknown;

View File

@@ -4,14 +4,14 @@ export interface NodeIKernelUnitedConfigService {
removeKernelUnitedConfigListener(listenerId:number): void;
fetchUnitedCommendConfig(...args: any[]): unknown;// needs 1 arguments
fetchUnitedSwitchConfig(...args: any[]): unknown;// needs 1 arguments
loadUnitedConfig(...args: any[]): unknown;// needs 1 arguments
isUnitedConfigSwitchOn(...args: any[]): unknown;// needs 1 arguments
registerUnitedConfigPushGroupList(...args: any[]): unknown;// needs 1 arguments
fetchUnitedCommendConfig(ids: `${string}`[]): void
loadUnitedConfig(id: string): Promise<unknown>
}

View File

@@ -0,0 +1,11 @@
import { NodeIO3MiscListener } from "../listeners/NodeIO3MiscListener";
export interface NodeIO3MiscService {
get(): NodeIO3MiscService;
addO3MiscListener(listeners: NodeIO3MiscListener): number;
setAmgomDataPiece(appid: string, dataPiece: Uint8Array): void;
reportAmgomWeather(type: string, uk2: string, arg: Array<string>): void;
}

View File

@@ -26,6 +26,7 @@ import { NodeIKernelRecentContactService } from './services/NodeIKernelRecentCon
import { NodeIKernelMSFService } from './services/NodeIKernelMSFService';
import { NodeIkernelTestPerformanceService } from './services/NodeIkernelTestPerformanceService';
import { NodeIKernelECDHService } from './services/NodeIKernelECDHService';
import { NodeIO3MiscService } from './services/NodeIO3MiscService';
export interface NodeQQNTWrapperUtil {
get(): unknown;
@@ -143,8 +144,7 @@ export interface NodeQQNTWrapperUtil {
}
export interface NodeIQQNTWrapperSession {
// eslint-disable-next-line @typescript-eslint/no-misused-new
new(): NodeIQQNTWrapperSession;
create(): NodeIQQNTWrapperSession;
init(
wrapperSessionInitConfig: WrapperSessionInitConfig,
@@ -250,11 +250,11 @@ export interface NodeIQQNTWrapperSession {
export interface EnginInitDesktopConfig {
base_path_prefix: string;
platform_type: 3;
platform_type: PlatformType;
app_type: 4;
app_version: string;
os_version: string;
use_xlog: true;
use_xlog: boolean;
qua: string;
global_path_config: {
desktopGlobalPath: string;
@@ -263,28 +263,26 @@ export interface EnginInitDesktopConfig {
}
export interface NodeIQQNTWrapperEngine {
// eslint-disable-next-line @typescript-eslint/no-misused-new
new(): NodeIQQNTWrapperEngine;
get(): NodeIQQNTWrapperEngine;
initWithDeskTopConfig(config: EnginInitDesktopConfig, nodeIGlobalAdapter: NodeIGlobalAdapter): void;
}
export interface WrapperNodeApi {
[key: string]: any;
NodeIO3MiscService: NodeIO3MiscService;
NodeQQNTWrapperUtil: NodeQQNTWrapperUtil;
NodeIQQNTWrapperSession: NodeIQQNTWrapperSession;
NodeIQQNTWrapperEngine: NodeIQQNTWrapperEngine;
NodeIKernelLoginService: NodeIKernelLoginService;
NodeIKernelProfileService: NodeIKernelProfileService;
}
}
export enum PlatformType {
KUNKNOWN,
KANDROID,
KIOS,
KWINDOWS,
KMAC,
KLINUX
}
export enum DeviceType {

View File

@@ -49,7 +49,7 @@ export async function NCoreInitFramework(
const loaderObject = new NapCatFramework(wrapper, session, logger, loginService, selfInfo, basicInfoWrapper, pathWrapper);
//启动WebUi
InitWebUi(logger, pathWrapper).then().catch(logger.logError);
InitWebUi(logger, pathWrapper).then().catch(logger.logError.bind(logger));
//初始化LLNC的Onebot实现
new NapCatOneBot11Adapter(loaderObject.core, loaderObject.context, pathWrapper);
}

BIN
src/native/external/MoeHoo.win32.node vendored Normal file

Binary file not shown.

40
src/native/index.ts Normal file
View File

@@ -0,0 +1,40 @@
import { constants } from "node:os";
import path from "path";
import { dlopen } from "process";
import fs from "fs";
export class Native {
platform: string;
supportedPlatforms = [];
MoeHooExport: any = { exports: {} };
recallHookEnabled: boolean = false;
inited = true;
constructor(nodePath: string, platform: string = process.platform) {
this.platform = platform;
try {
if (!this.supportedPlatforms.includes(this.platform)) {
throw new Error(`Platform ${this.platform} is not supported`);
}
let nativeNode = path.join(nodePath, './native/MoeHoo.win32.node');
if (fs.existsSync(nativeNode)) {
dlopen(this.MoeHooExport, nativeNode, constants.dlopen.RTLD_LAZY);
}
} catch (error) {
this.inited = false;
}
}
isSetReCallEnabled(): boolean {
return this.recallHookEnabled && this.inited;
}
registerRecallCallback(callback: (hex: string) => any): void {
try {
if (!this.inited) throw new Error('Native Not Init');
if (this.MoeHooExport.exports?.registMsgPush) {
this.MoeHooExport.exports.registMsgPush(callback);
this.recallHookEnabled = true;
}
} catch (error) {
this.recallHookEnabled = false;
}
}
}

View File

@@ -46,7 +46,7 @@ abstract class BaseAction<PayloadType, ReturnDataType> {
const resData = await this._handle(payload);
return OB11Response.ok(resData);
} catch (e: any) {
this.core.context.logger.logError('发生错误', e);
this.core.context.logger.logError.bind(this.core.context.logger)('发生错误', e);
return OB11Response.error(e?.toString() || e?.stack?.toString() || '未知错误,可能操作超时', 200);
}
}
@@ -60,7 +60,7 @@ abstract class BaseAction<PayloadType, ReturnDataType> {
const resData = await this._handle(payload);
return OB11Response.ok(resData, echo);
} catch (e: any) {
this.core.context.logger.logError('发生错误', e);
this.core.context.logger.logError.bind(this.core.context.logger)('发生错误', e);
return OB11Response.error(e.stack?.toString() || e.toString(), 1200, echo);
}
}

View File

@@ -0,0 +1,11 @@
import BaseAction from '../BaseAction';
import { ActionName } from '../types';
export class FetchUserProfileLike extends BaseAction<{ qq: number }, any> {
actionName = ActionName.FetchUserProfileLike;
async _handle(payload: { qq: number }) {
if (!payload.qq) throw new Error('qq is required');
return await this.core.apis.UserApi.getUidByUinV2(payload.qq.toString());
}
}

View File

@@ -19,26 +19,21 @@ export class OCRImage extends BaseAction<Payload, any> {
payloadSchema = SchemaData;
async _handle(payload: Payload) {
const { path, isLocal, success } = (await uri2local(this.core.NapCatTempPath, payload.image));
const { path, success } = (await uri2local(this.core.NapCatTempPath, payload.image));
if (!success) {
throw `OCR ${payload.image}失败,image字段可能格式不正确`;
}
if (path) {
await checkFileReceived(path, 5000); // 文件不存在QQ会崩溃需要提前判断
const ret = await this.core.apis.SystemApi.ocrImage(path);
if (!isLocal) {
fs.unlink(path, () => {
});
}
fs.unlink(path, () => { });
if (!ret) {
throw `OCR ${payload.file}失败`;
}
return ret.result;
}
if (!isLocal) {
fs.unlink(path, () => {
});
}
fs.unlink(path, () => { });
throw `OCR ${payload.file}失败,文件可能不存在`;
}
}

View File

@@ -24,17 +24,16 @@ export default class SetAvatar extends BaseAction<Payload, null> {
}
async _handle(payload: Payload): Promise<null> {
const { path, isLocal, success } = (await uri2local(this.core.NapCatTempPath, payload.file));
const { path, success } = (await uri2local(this.core.NapCatTempPath, payload.file));
if (!success) {
throw `头像${payload.file}设置失败,file字段可能格式不正确`;
}
if (path) {
await checkFileReceived(path, 5000); // 文件不存在QQ会崩溃需要提前判断
const ret = await this.core.apis.UserApi.setQQAvatar(path);
if (!isLocal) {
fs.unlink(path, () => {
});
}
fs.unlink(path, () => {
});
if (!ret) {
throw `头像${payload.file}设置失败,api无返回`;
}
@@ -45,10 +44,8 @@ export default class SetAvatar extends BaseAction<Payload, null> {
throw `头像${payload.file}设置失败,未知的错误,${ret['result']}:${ret['errMsg']}`;
}
} else {
if (!isLocal) {
fs.unlink(path, () => {
});
}
fs.unlink(path, () => { });
throw `头像${payload.file}设置失败,无法获取头像,文件可能不存在`;
}
return null;

View File

@@ -43,12 +43,12 @@ export class GetFileBase extends BaseAction<GetFilePayload, GetFileResponse> {
const fileName = mixElementInner.fileName ?? '';
let url = '';
if (mixElement?.picElement && rawMessage) {
let tempData =
const tempData =
await this.obContext.apis.MsgApi.rawToOb11Converters.picElement?.(mixElement?.picElement, rawMessage, mixElement) as OB11MessageImage | undefined;
url = tempData?.data.url ?? '';
}
if (mixElement?.videoElement && rawMessage) {
let tempData =
const tempData =
await this.obContext.apis.MsgApi.rawToOb11Converters.videoElement?.(mixElement?.videoElement, rawMessage, mixElement) as OB11MessageVideo | undefined;
url = tempData?.data.url ?? '';
}

View File

@@ -7,17 +7,18 @@ const SchemaData = {
properties: {
group_id: { type: ['string', 'number'] },
folder_id: { type: 'string' },
folder: { type: 'string' }
},
required: ['group_id', 'folder_id'],
required: ['group_id'],
} as const satisfies JSONSchema;
type Payload = FromSchema<typeof SchemaData>;
export class DeleteGroupFileFolder extends BaseAction<Payload, any> {
export class DeleteGroupFileFolder extends BaseAction<Payload, any> {
actionName = ActionName.GoCQHTTP_DeleteGroupFileFolder;
payloadSchema = SchemaData;
async _handle(payload: Payload) {
return (await this.core.apis.GroupApi.DelGroupFileFolder(
payload.group_id.toString(), payload.folder_id)).groupFileCommonResult;
payload.group_id.toString(), payload.folder ?? payload.folder_id ?? '')).groupFileCommonResult;
}
}

View File

@@ -73,7 +73,7 @@ export default class GoCQHTTPDownloadFile extends BaseAction<Payload, FileRespon
headers[headerItem] = '';
} else {
const key = headerItem.substring(0, spilt);
headers[key] = headerItem.substring(0, spilt + 1);
headers[key] = headerItem.substring(spilt + 1);
}
}
}

View File

@@ -1,9 +1,16 @@
import BaseAction from '../BaseAction';
import { OB11ForwardMessage } from '@/onebot';
import { OB11Message, OB11MessageData, OB11MessageDataType, OB11MessageForward, OB11MessageNode as OriginalOB11MessageNode } from '@/onebot';
import { ActionName } from '../types';
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import { MessageUnique } from '@/common/message-unique';
type OB11MessageNode = OriginalOB11MessageNode & {
data: {
content?: Array<OB11MessageData>;
message: Array<OB11MessageData>;
};
};
const SchemaData = {
type: 'object',
properties: {
@@ -18,36 +25,69 @@ export class GoCQHTTPGetForwardMsgAction extends BaseAction<Payload, any> {
actionName = ActionName.GoCQHTTP_GetForwardMsg;
payloadSchema = SchemaData;
private createTemplateNode(message: OB11Message): OB11MessageNode {
return {
type: OB11MessageDataType.node,
data: {
user_id: message.user_id,
nickname: message.sender.nickname,
message: [],
content: []
}
};
}
async parseForward(messages: OB11Message[]): Promise<OB11MessageNode[]> {
const retMsg: OB11MessageNode[] = [];
for (const message of messages) {
const templateNode = this.createTemplateNode(message);
for (const msgdata of message.message) {
if ((msgdata as OB11MessageData).type === OB11MessageDataType.forward) {
const newNode = this.createTemplateNode(message);
newNode.data.message = await this.parseForward((msgdata as OB11MessageForward).data.content);
templateNode.data.message.push(newNode);
} else {
templateNode.data.message.push(msgdata as OB11MessageData);
}
}
retMsg.push(templateNode);
}
return retMsg;
}
async _handle(payload: Payload): Promise<any> {
const msgId = payload.message_id || payload.id;
if (!msgId) {
throw Error('message_id is required');
throw new Error('message_id is required');
}
const rootMsgId = MessageUnique.getShortIdByMsgId(msgId);
const rootMsg = MessageUnique.getMsgIdAndPeerByShortId(rootMsgId || parseInt(msgId));
if (!rootMsg) {
throw Error('msg not found');
throw new Error('msg not found');
}
const data = await this.core.apis.MsgApi.getMultiMsg(rootMsg.Peer, rootMsg.MsgId, rootMsg.MsgId);
const data = await this.core.apis.MsgApi.getMsgsByMsgId(rootMsg.Peer, [rootMsg.MsgId]);
if (!data || data.result !== 0) {
throw Error('找不到相关的聊天记录' + data?.errMsg);
throw new Error('找不到相关的聊天记录' + data?.errMsg);
}
const msgList = data.msgList;
const messages = (await Promise.all(msgList.map(async msg => {
const resMsg = await this.obContext.apis.MsgApi
.parseMessage(msg);
if (!resMsg) return;
resMsg.message_id = MessageUnique.createUniqueMsgId({
guildId: '',
chatType: msg.chatType,
peerUid: msg.peerUid,
}, msg.msgId)!;
return resMsg;
}))).filter(msg => !!msg);
messages.map(msg => {
(<OB11ForwardMessage>msg).content = msg.message;
delete (<any>msg).message;
});
return { messages };
const singleMsg = data.msgList[0];
const resMsg = await this.obContext.apis.MsgApi.parseMessage(singleMsg, 'array');//强制array 以便处理
if (!resMsg) {
throw new Error('找不到相关的聊天记录');
}
//if (this.obContext.configLoader.configData.messagePostFormat === 'array') {
//提取
let realmsg = ((await this.parseForward([resMsg]))[0].data.message as OB11MessageNode[])[0].data.message;
//里面都是offline消息 id都是0 没得说话
return { message: realmsg };
//}
// return { message: resMsg };
}
}
}

View File

@@ -8,9 +8,10 @@ const SchemaData = {
properties: {
group_id: { type: ['string', 'number'] },
folder_id: { type: 'string' },
folder: { type: 'string' },
file_count: { type: ['string', 'number'] },
},
required: ['group_id', 'folder_id'],
required: ['group_id'],
} as const satisfies JSONSchema;
type Payload = FromSchema<typeof SchemaData>;
@@ -26,7 +27,7 @@ export class GetGroupFilesByFolder extends BaseAction<any, any> {
startIndex: 0,
sortOrder: 2,
showOnlinedocFolder: 0,
folderId: payload.folder_id,
folderId: payload.folder ?? payload.folder_id ?? '',
}).catch(() => []);
return {
files: ret.filter(item => item.fileInfo)

View File

@@ -20,24 +20,26 @@ export default class GoCQHTTPGetStrangerInfo extends BaseAction<Payload, OB11Use
async _handle(payload: Payload): Promise<OB11User> {
const user_id = payload.user_id.toString();
const extendData = await this.core.apis.UserApi.getUserDetailInfoByUinV2(user_id);
const uid = (await this.core.apis.UserApi.getUidByUinV2(user_id))!;
if (!uid || uid.indexOf('*') != -1) {
return {
...extendData.detail.simpleInfo.coreInfo,
...extendData.detail.commonExt,
...extendData.detail.simpleInfo.baseInfo,
...extendData.detail.simpleInfo.relationFlags,
user_id: parseInt(extendData.detail.uin) || 0,
nickname: extendData.detail.simpleInfo.coreInfo.nick,
sex: OB11UserSex.unknown,
age: extendData.detail.simpleInfo.baseInfo.age || 0,
qid: extendData.detail.simpleInfo.baseInfo.qid,
level: calcQQLevel(extendData.detail.commonExt?.qqLevel ?? 0) || 0,
login_days: 0,
};
}
const data = { ...extendData, ...(await this.core.apis.UserApi.getUserDetailInfo(uid)) };
return OB11Entities.stranger(data);
const extendData = await this.core.apis.UserApi.getUserDetailInfoByUin(user_id);
let uid = (await this.core.apis.UserApi.getUidByUinV2(user_id));
if (!uid) uid = extendData.detail.uid;
const info = (await this.core.apis.UserApi.getUserDetailInfo(uid));
return {
user_id: parseInt(extendData.detail.uin) ?? 0,
uid: info.uid ?? uid,
nickname: extendData.detail.simpleInfo.coreInfo.nick,
age: extendData.detail.simpleInfo.baseInfo.age ?? info.age,
qid: extendData.detail.simpleInfo.baseInfo.qid,
qqLevel: calcQQLevel(extendData.detail.commonExt?.qqLevel ?? info.qqLevel),
sex: OB11Entities.sex(extendData.detail.simpleInfo.baseInfo.sex) ?? OB11UserSex.unknown,
long_nick: extendData.detail.simpleInfo.baseInfo.longNick ?? info.longNick,
reg_time: extendData.detail.commonExt.regTime ?? info.regTime,
is_vip: extendData.detail.simpleInfo.vasInfo?.svipFlag,
is_years_vip: extendData.detail.simpleInfo.vasInfo?.yearVipFlag,
vip_level: extendData.detail.simpleInfo.vasInfo?.vipLevel,
remark: extendData.detail.simpleInfo.coreInfo.remark ?? info.remark,
status: extendData.detail.simpleInfo.status?.status ?? info.status,
login_days: 0,//失效
};
}
}

View File

@@ -31,7 +31,6 @@ export class SendGroupNotice extends BaseAction<Payload, null> {
//公告图逻辑
const {
path,
isLocal,
success,
} = (await uri2local(this.core.NapCatTempPath, payload.image));
if (!success) {
@@ -45,10 +44,10 @@ export class SendGroupNotice extends BaseAction<Payload, null> {
if (ImageUploadResult.errCode != 0) {
throw `群公告${payload.image}设置失败,图片上传失败`;
}
if (!isLocal) {
unlink(path, () => {
});
}
unlink(path, () => {
});
UploadImage = ImageUploadResult.picInfo;
}
@@ -58,7 +57,6 @@ export class SendGroupNotice extends BaseAction<Payload, null> {
const noticeShowEditCard = +(payload.is_show_edit_card ?? 0);
const noticeTipWindowType = +(payload.tip_window_type ?? 0);
const noticeConfirmRequired = +(payload.confirm_required ?? 1);
//const publishGroupBulletinResult = await this.core.apis.GroupApi.publishGroupBulletin(payload.group_id.toString(), payload.content, UploadImage, noticePinned, noticeConfirmRequired);
const publishGroupBulletinResult = await this.core.apis.WebApi.setGroupNotice(
payload.group_id.toString(),
payload.content,
@@ -72,7 +70,7 @@ export class SendGroupNotice extends BaseAction<Payload, null> {
UploadImage?.height
);
if (!publishGroupBulletinResult || publishGroupBulletinResult.ec != 0) {
throw `设置群公告失败,错误信息:${publishGroupBulletinResult?.em}`;
throw new Error(`设置群公告失败,错误信息:${publishGroupBulletinResult?.em}`);
}
return null;
}

View File

@@ -25,17 +25,14 @@ export default class SetGroupPortrait extends BaseAction<Payload, any> {
}
async _handle(payload: Payload): Promise<any> {
const { path, isLocal, success } = (await uri2local(this.core.NapCatTempPath, payload.file));
const { path, success } = (await uri2local(this.core.NapCatTempPath, payload.file));
if (!success) {
throw `头像${payload.file}设置失败,file字段可能格式不正确`;
}
if (path) {
await checkFileReceived(path, 5000); // 文件不存在QQ会崩溃需要提前判断
const ret = await this.core.apis.GroupApi.setGroupAvatar(payload.group_id.toString(), path) as any;
if (!isLocal) {
fs.unlink(path, () => {
});
}
fs.unlink(path, () => { });
if (!ret) {
throw `头像${payload.file}设置失败,api无返回`;
}
@@ -46,10 +43,7 @@ export default class SetGroupPortrait extends BaseAction<Payload, any> {
}
return ret;
} else {
if (!isLocal) {
fs.unlink(path, () => {
});
}
fs.unlink(path, () => {});
throw `头像${payload.file}设置失败,无法获取头像,文件可能不存在`;
}
}

View File

@@ -1,9 +1,10 @@
import BaseAction from '../BaseAction';
import { ActionName } from '../types';
import { ChatType } from '@/core/entities';
import { ChatType, Peer } from '@/core/entities';
import fs from 'fs';
import { uri2local } from '@/common/file';
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import { MessageContext } from '@/onebot/api';
const SchemaData = {
type: 'object',
@@ -29,14 +30,19 @@ export default class GoCQHTTPUploadGroupFile extends BaseAction<Payload, null> {
file = `file://${file}`;
}
const downloadResult = await uri2local(this.core.NapCatTempPath, file);
const peer: Peer = {
chatType: ChatType.KCHATTYPEGROUP,
peerUid: payload.group_id.toString(),
};
if (!downloadResult.success) {
throw new Error(downloadResult.errMsg);
}
const sendFileEle = await this.core.apis.FileApi.createValidSendFileElement(downloadResult.path, payload.name, payload.folder_id);
await this.obContext.apis.MsgApi.sendMsgWithOb11UniqueId({
chatType: ChatType.KCHATTYPEGROUP,
peerUid: payload.group_id.toString(),
}, [sendFileEle], [], true);
const msgContext: MessageContext = {
peer: peer,
deleteAfterSentFiles: []
};
const sendFileEle = await this.core.apis.FileApi.createValidSendFileElement(msgContext, downloadResult.path, payload.name, payload.folder ?? payload.folder_id);
await this.obContext.apis.MsgApi.sendMsgWithOb11UniqueId(peer, [sendFileEle], [], true);
return null;
}
}

View File

@@ -4,6 +4,8 @@ import { ChatType, Peer, SendFileElement } from '@/core/entities';
import fs from 'fs';
import { uri2local } from '@/common/file';
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import { MessageContext } from '@/onebot/api';
import { ContextMode, createContext } from '../msg/SendMsg';
const SchemaData = {
type: 'object',
@@ -42,7 +44,15 @@ export default class GoCQHTTPUploadPrivateFile extends BaseAction<Payload, null>
if (!downloadResult.success) {
throw new Error(downloadResult.errMsg);
}
const sendFileEle: SendFileElement = await this.core.apis.FileApi.createValidSendFileElement(downloadResult.path, payload.name);
const msgContext: MessageContext = {
peer: await createContext(this.core, {
user_id: payload.user_id.toString(),
group_id: undefined,
}, ContextMode.Private),
deleteAfterSentFiles: []
};
const sendFileEle: SendFileElement = await this.core.apis.FileApi.createValidSendFileElement(msgContext, downloadResult.path, payload.name);
await this.obContext.apis.MsgApi.sendMsgWithOb11UniqueId(await this.getPeer(payload), [sendFileEle], [], true);
return null;
}

View File

@@ -51,7 +51,7 @@ export class GetGroupEssence extends BaseAction<Payload, any> {
operator_nick: msg.add_digest_nick,
message_id: message_id,
operator_time: msg.add_digest_time,
content: (await this.obContext.apis.MsgApi.parseMessage(rawMessage, 'array'))?.message
content: (await this.obContext.apis.MsgApi.parseMessage(rawMessage))?.message
};
}
const msgTempData = JSON.stringify({

View File

@@ -20,7 +20,17 @@ class GetGroupInfo extends BaseAction<Payload, OB11Group> {
async _handle(payload: Payload) {
const group = (await this.core.apis.GroupApi.getGroups()).find(e => e.groupCode == payload.group_id.toString());
if (!group) throw `${payload.group_id}不存在`;
if (!group) {
const data = await this.core.apis.GroupApi.searchGroup(payload.group_id.toString());
if (!data) throw new Error('Group not found');
return {
...data.searchGroupInfo,
group_id: +payload.group_id,
group_name: data.searchGroupInfo.groupName,
member_count: data.searchGroupInfo.memberNum,
max_member_count: data.searchGroupInfo.maxMemberNum,
};
}
return OB11Entities.group(group);
}
}

View File

@@ -3,6 +3,7 @@ import { OB11Entities } from '@/onebot/entities';
import BaseAction from '../BaseAction';
import { ActionName } from '../types';
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import { calcQQLevel } from '@/common/helper';
const SchemaData = {
type: 'object',
@@ -22,7 +23,8 @@ class GetGroupMemberList extends BaseAction<Payload, OB11GroupMember[]> {
async _handle(payload: Payload) {
const groupMembers = await this.core.apis.GroupApi.getGroupMembersV2(payload.group_id.toString());
const groupMembersArr = Array.from(groupMembers.values());
const uids = groupMembersArr.map(item => item.uid);
//let CoreAndBase = await this.core.apis.GroupApi.getCoreAndBaseInfo(uids)
let _groupMembers = groupMembersArr.map(item => {
return OB11Entities.groupMember(payload.group_id.toString(), item);
});
@@ -32,34 +34,38 @@ class GetGroupMemberList extends BaseAction<Payload, OB11GroupMember[]> {
for (let i = 0, len = _groupMembers.length; i < len; i++) {
// 保证基础数据有这个 同时避免群管插件过于依赖这个杀了
_groupMembers[i].join_time = date;
_groupMembers[i].last_sent_time = date;
const Member = await this.core.apis.GroupApi.getGroupMember(payload.group_id.toString(), _groupMembers[i].user_id);
_groupMembers[i].join_time = +(Member?.joinTime ?? date);
_groupMembers[i].last_sent_time = +(Member?.lastSpeakTime ?? date);
MemberMap.set(_groupMembers[i].user_id, _groupMembers[i]);
}
const selfRole = groupMembers.get(this.core.selfInfo.uid)?.role;
const isPrivilege = selfRole === 3 || selfRole === 4;
_groupMembers.forEach(item => {
item.last_sent_time = date;
item.join_time = date;
});
if (isPrivilege) {
const webGroupMembers = await this.core.apis.WebApi.getGroupMembers(payload.group_id.toString());
for (let i = 0, len = webGroupMembers.length; i < len; i++) {
if (!webGroupMembers[i]?.uin) {
continue;
}
const MemberData = MemberMap.get(webGroupMembers[i]?.uin);
if (MemberData) {
MemberData.join_time = webGroupMembers[i]?.join_time;
MemberData.last_sent_time = webGroupMembers[i]?.last_speak_time;
MemberData.qage = webGroupMembers[i]?.qage;
MemberData.level = webGroupMembers[i]?.lv.level.toString();
MemberMap.set(webGroupMembers[i]?.uin, MemberData);
try {
const webGroupMembers = await this.core.apis.WebApi.getGroupMembers(payload.group_id.toString());
for (let i = 0, len = webGroupMembers.length; i < len; i++) {
if (!webGroupMembers[i]?.uin) {
continue;
}
const MemberData = MemberMap.get(webGroupMembers[i]?.uin);
if (MemberData) {
MemberData.join_time = webGroupMembers[i]?.join_time;
MemberData.last_sent_time = webGroupMembers[i]?.last_speak_time;
MemberData.qage = webGroupMembers[i]?.qage;
MemberData.level = webGroupMembers[i]?.lv.level.toString();
MemberMap.set(webGroupMembers[i]?.uin, MemberData);
}
}
} catch (e) {
const logger = this.core.context.logger;
logger.logError.bind(logger)('GetGroupMemberList', e);
}
}
_groupMembers = Array.from(MemberMap.values());

View File

@@ -68,8 +68,8 @@ import SetGroupPortrait from './go-cqhttp/SetGroupPortrait';
import { FetchCustomFace } from './extends/FetchCustomFace';
import GoCQHTTPUploadPrivateFile from './go-cqhttp/UploadPrivateFile';
import { FetchEmojiLike } from './extends/FetchEmojiLike';
import { FetchUserProfileLike } from './extends/FetchUserProfileLike';
import { NapCatCore } from '@/core';
import { NapCatOneBot11Adapter } from '@/onebot';
import GetGuildProfile from './guild/GetGuildProfile';
import SetModelShow from './go-cqhttp/SetModelShow';
@@ -83,6 +83,8 @@ import { DeleteGroupFileFolder } from '@/onebot/action/go-cqhttp/DeleteGroupFile
import { GetGroupFileSystemInfo } from '@/onebot/action/go-cqhttp/GetGroupFileSystemInfo';
import { GetGroupRootFiles } from '@/onebot/action/go-cqhttp/GetGroupRootFiles';
import { GetGroupFilesByFolder } from '@/onebot/action/go-cqhttp/GetGroupFilesByFolder';
import { GetGroupSystemMsg } from './system/GetSystemMsg';
export type ActionMap = Map<string, BaseAction<any, any>>;
@@ -176,6 +178,8 @@ export function createActionMap(obContext: NapCatOneBot11Adapter, core: NapCatCo
new DeleteGroupFileFolder(obContext, core),
new GetGroupFileSystemInfo(obContext, core),
new GetGroupFilesByFolder(obContext, core),
new GetGroupSystemMsg(obContext, core),
new FetchUserProfileLike(obContext, core),
];
const actionMap = new Map();
for (const action of actionHandlers) {

View File

@@ -3,6 +3,7 @@ import BaseAction from '../BaseAction';
import { ActionName } from '../types';
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import { MessageUnique } from '@/common/message-unique';
import { RawMessage } from '@/core';
export type ReturnDataType = OB11Message
@@ -32,13 +33,17 @@ class GetMsg extends BaseAction<Payload, OB11Message> {
throw new Error('消息不存在');
}
const peer = { guildId: '', peerUid: msgIdWithPeer?.Peer.peerUid, chatType: msgIdWithPeer.Peer.chatType };
const msg = await this.core.apis.MsgApi.getMsgsByMsgId(
peer,
[msgIdWithPeer?.MsgId || payload.message_id.toString()]);
const retMsg = await this.obContext.apis.MsgApi.parseMessage(msg.msgList[0], 'array');
let orimsg = this.obContext.recallMsgCache.get(msgIdWithPeer.MsgId);
let msg: RawMessage;
if (orimsg) {
msg = orimsg;
} else {
msg = (await this.core.apis.MsgApi.getMsgsByMsgId(peer, [msgIdWithPeer?.MsgId || payload.message_id.toString()])).msgList[0];
}
const retMsg = await this.obContext.apis.MsgApi.parseMessage(msg);
if (!retMsg) throw Error('消息为空');
try {
retMsg.message_id = MessageUnique.createUniqueMsgId(peer, msg.msgList[0].msgId)!;
retMsg.message_id = MessageUnique.createUniqueMsgId(peer, msg.msgId)!;
retMsg.message_seq = retMsg.message_id;
retMsg.real_id = retMsg.message_id;
} catch (e) {

View File

@@ -3,6 +3,7 @@ import {
OB11MessageDataType,
OB11MessageMixType,
OB11MessageNode,
OB11PostContext,
OB11PostSendMsg,
} from '@/onebot/types';
import { ActionName, BaseCheckResult } from '@/onebot/action/types';
@@ -30,7 +31,7 @@ export function normalize(message: OB11MessageMixType, autoEscape = false): OB11
) : Array.isArray(message) ? message : [message];
}
export async function createContext(core: NapCatCore, payload: OB11PostSendMsg, contextMode: ContextMode): Promise<Peer> {
export async function createContext(core: NapCatCore, payload: OB11PostContext, contextMode: ContextMode): Promise<Peer> {
// This function determines the type of message by the existence of user_id / group_id,
// not message_type.
// This redundant design of Ob11 here should be blamed.
@@ -148,7 +149,7 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
//对Mgsid和OB11ID混用情况兜底
const nodeMsg = MessageUnique.getMsgIdAndPeerByShortId(parseInt(nodeId)) || MessageUnique.getPeerByMsgId(nodeId);
if (!nodeMsg) {
logger.logError('转发消息失败,未找到消息', nodeId);
logger.logError.bind(this.core.context.logger)('转发消息失败,未找到消息', nodeId);
continue;
}
nodeMsgIds.push(nodeMsg.MsgId);
@@ -160,7 +161,7 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
const isNodeMsg = OB11Data.filter(e => e.type === OB11MessageDataType.node).length;//找到子转发消息
if (isNodeMsg !== 0) {
if (isNodeMsg !== OB11Data.length) {
logger.logError('子消息中包含非node消息 跳过不合法部分');
logger.logError.bind(this.core.context.logger)('子消息中包含非node消息 跳过不合法部分');
continue;
}
const nodeMsg = await this.handleForwardedNodes(selfPeer, OB11Data.filter(e => e.type === OB11MessageDataType.node));
@@ -208,7 +209,7 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
for (const msgId of nodeMsgIds) {
const nodeMsgPeer = MessageUnique.getPeerByMsgId(msgId);
if (!nodeMsgPeer) {
logger.logError('转发消息失败,未找到消息', msgId);
logger.logError.bind(this.core.context.logger)('转发消息失败,未找到消息', msgId);
continue;
}
const nodeMsg = (await this.core.apis.MsgApi.getMsgsByMsgId(nodeMsgPeer.Peer, [msgId])).msgList[0];
@@ -237,7 +238,7 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
logger.logDebug('开发转发', srcPeer, destPeer, retMsgIds);
return await this.core.apis.MsgApi.multiForwardMsg(srcPeer!, destPeer, retMsgIds);
} catch (e) {
logger.logError('forward failed', e);
logger.logError.bind(this.core.context.logger)('forward failed', e);
return null;
}
}
@@ -261,7 +262,7 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
try {
return await this.core.apis.MsgApi.sendMsg(selfPeer, sendElements, true);
} catch (e) {
logger.logError(e, '克隆转发消息失败,将忽略本条消息', msg);
logger.logError.bind(this.core.context.logger)(e, '克隆转发消息失败,将忽略本条消息', msg);
}
}
}

View File

@@ -0,0 +1,50 @@
import { GroupNotifyMsgStatus } from '@/core';
import BaseAction from '../BaseAction';
import { ActionName } from '../types';
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
const SchemaData = {
type: 'object',
properties: {
group_id: { type: ['number', 'string'] },
},
} as const satisfies JSONSchema;
type Payload = FromSchema<typeof SchemaData>;
export class GetGroupSystemMsg extends BaseAction<void, any> {
actionName = ActionName.GetGroupSystemMsg;
async _handle(payload: void) {
const NTQQUserApi = this.core.apis.UserApi;
const NTQQGroupApi = this.core.apis.GroupApi;
// 默认10条 该api未完整实现 包括响应数据规范化 类型规范化
const SingleScreenNotifies = await NTQQGroupApi.getSingleScreenNotifies(false,10);
const retData: any = { InvitedRequest: [], join_requests: [] };
for (const SSNotify of SingleScreenNotifies) {
if (SSNotify.type == 1) {
retData.InvitedRequest.push({
request_id: SSNotify.seq,
invitor_uin: await NTQQUserApi.getUinByUidV2(SSNotify.user1?.uid),
invitor_nick: SSNotify.user1?.nickName,
group_id: SSNotify.group?.groupCode,
group_name: SSNotify.group?.groupName,
checked: SSNotify.status === GroupNotifyMsgStatus.KUNHANDLE ? false : true,
actor: await NTQQUserApi.getUinByUidV2(SSNotify.user2?.uid) || 0,
});
} else if (SSNotify.type == 7) {
retData.join_requests.push({
request_id: SSNotify.seq,
requester_uin: await NTQQUserApi.getUinByUidV2(SSNotify.user1?.uid),
requester_nick: SSNotify.user1?.nickName,
group_id: SSNotify.group?.groupCode,
group_name: SSNotify.group?.groupName,
checked: SSNotify.status === GroupNotifyMsgStatus.KUNHANDLE ? false : true,
actor: await NTQQUserApi.getUinByUidV2(SSNotify.user2?.uid) || 0,
});
}
}
return retData;
}
}

View File

@@ -16,8 +16,8 @@ export interface InvalidCheckResult {
export enum ActionName {
// 以下为扩展napcat扩展
Unknown = 'unknown',
SharePeer = 'ArkShareGroup',
ShareGroupEx = 'ArkSharePeer',
SharePeer = 'ArkSharePeer',
ShareGroupEx = 'ArkShareGroup',
RebootNormal = 'reboot_normal',//无快速登录重新启动
GetRobotUinRange = 'get_robot_uin_range',
SetOnlineStatus = 'set_online_status',
@@ -116,5 +116,7 @@ export enum ActionName {
SetInputStatus = 'set_input_status',
GetCSRF = 'get_csrf_token',
DelGroupNotice = '_del_group_notice',
GetGroupInfoEx = "get_group_info_ex"
GetGroupInfoEx = "get_group_info_ex",
GetGroupSystemMsg = 'get_group_system_msg',
FetchUserProfileLike = "fetch_user_profile_like",
}

View File

@@ -21,7 +21,7 @@ export default class GetRecentContact extends BaseAction<Payload, any> {
const FastMsg = await this.core.apis.MsgApi.getMsgsByMsgId({ chatType: t.chatType, peerUid: t.peerUid }, [t.msgId]);
if (FastMsg.msgList.length > 0) {
//扩展ret.info.changedList
const lastestMsg = await this.obContext.apis.MsgApi.parseMessage(FastMsg.msgList[0], 'array');
const lastestMsg = await this.obContext.apis.MsgApi.parseMessage(FastMsg.msgList[0]);
return {
lastestMsg: lastestMsg,
peerUin: t.peerUin,

View File

@@ -15,14 +15,14 @@ export class OneBotFriendApi {
//使用前预先判断 busiId 1061
async parsePrivatePokeEvent(grayTipElement: GrayTipElement) {
const json = JSON.parse(grayTipElement.jsonGrayTipElement.jsonStr);
let pokedetail: any[] = json.items;
let pokedetail: Array<{ uid: string }> = json.items;
//筛选item带有uid的元素
pokedetail = pokedetail.filter(item => item.uid);
if (pokedetail.length == 2) {
return new OB11FriendPokeEvent(
this.core,
parseInt((await this.core.apis.UserApi.getUinByUidV2(pokedetail[0].uid))!),
parseInt((await this.core.apis.UserApi.getUinByUidV2(pokedetail[1].uid))!),
parseInt((await this.core.apis.UserApi.getUinByUidV2(pokedetail[0].uid))),
parseInt((await this.core.apis.UserApi.getUinByUidV2(pokedetail[1].uid))),
pokedetail,
);
}

View File

@@ -19,6 +19,7 @@ import { OB11GroupPokeEvent } from '@/onebot/event/notice/OB11PokeEvent';
import { OB11GroupEssenceEvent } from '@/onebot/event/notice/OB11GroupEssenceEvent';
import { OB11GroupTitleEvent } from '@/onebot/event/notice/OB11GroupTitleEvent';
import { FileNapCatOneBotUUID } from '@/common/helper';
import { pathToFileURL } from 'node:url';
export class OneBotGroupApi {
obContext: NapCatOneBot11Adapter;
@@ -77,7 +78,8 @@ export class OneBotGroupApi {
id: FileNapCatOneBotUUID.encode({
chatType: ChatType.KCHATTYPEGROUP,
peerUid: msg.peerUid,
}, msg.msgId, element.elementId),
}, msg.msgId, element.elementId, "." + element.fileElement.fileName),
url: pathToFileURL(element.fileElement.filePath).href,
name: element.fileElement.fileName,
size: parseInt(element.fileElement.fileSize),
busid: element.fileElement.fileBizId || 0,
@@ -94,7 +96,6 @@ export class OneBotGroupApi {
if (GroupIncreaseEvent) return GroupIncreaseEvent;
}
//代码歧义 GrayTipElementSubType.MEMBER_NEW_TITLE
else if (element.grayTipElement.subElementType == NTGrayTipElementSubTypeV2.GRAYTIP_ELEMENT_SUBTYPE_JSON) {
const json = JSON.parse(element.grayTipElement.jsonGrayTipElement.jsonStr);
if (element.grayTipElement.jsonGrayTipElement.busiId == 1061) {
@@ -137,16 +138,23 @@ export class OneBotGroupApi {
// 获取MsgSeq+Peer可获取具体消息
}
if (element.grayTipElement.jsonGrayTipElement.busiId == 2407) {
//下面得改 上面也是错的grayTipElement.subElementType == GrayTipElementSubType.MEMBER_NEW_TITLE
const memberUin = json.items[1].param[0];
const title = json.items[3].txt;
logger.logDebug('收到群成员新头衔消息', json);
return new OB11GroupTitleEvent(
this.core,
parseInt(msg.peerUid),
parseInt(memberUin),
title,
);
const type = json.items[json.items.length - 1]?.txt;
if (type === "头衔") {
const memberUin = json.items[1].param[0];
const title = json.items[3].txt;
logger.logDebug('收到群成员新头衔消息', json);
return new OB11GroupTitleEvent(
this.core,
parseInt(msg.peerUid),
parseInt(memberUin),
title,
);
} else if (type === "移出") {
logger.logDebug('收到机器人被踢消息', json);
return;
} else {
logger.logWarn('收到未知的灰条消息', json);
}
}
}
}
@@ -195,7 +203,6 @@ export class OneBotGroupApi {
while ((match = regex.exec(xmlElement.content)) !== null) {
matches.push(match[1]);
}
// log("新人进群匹配到的QQ号", matches)
if (matches.length === 2) {
const [inviter, invitee] = matches;
return new OB11GroupIncreaseEvent(
@@ -277,7 +284,7 @@ export class OneBotGroupApi {
}
const replyMsg = replyMsgList[0];
if (!replyMsg) {
this.core.context.logger.logError('解析表情回应消息失败: 未找到回应消息');
this.core.context.logger.logError.bind(this.core.context.logger)('解析表情回应消息失败: 未找到回应消息');
return undefined;
}
return new OB11GroupMsgEmojiLikeEvent(

View File

@@ -1,5 +1,6 @@
import { FileNapCatOneBotUUID } from '@/common/helper';
import { MessageUnique } from '@/common/message-unique';
import { pathToFileURL } from 'node:url';
import {
AtType,
ChatType,
@@ -33,6 +34,7 @@ import { RequestUtil } from '@/common/request';
import fs from 'node:fs';
import fsPromise from 'node:fs/promises';
import { OB11FriendAddNoticeEvent } from '@/onebot/event/notice/OB11FriendAddNoticeEvent';
import { decodeSysMessage } from '@/core/proto/ProfileLike';
type RawToOb11Converters = {
[Key in keyof MessageElement as Key extends `${string}Element` ? Key : never]: (
@@ -106,7 +108,7 @@ export class OneBotMsgApi {
peerUid: msg.peerUid,
guildId: '',
};
const encodedFileId = FileNapCatOneBotUUID.encode(peer, msg.msgId, elementWrapper.elementId);
const encodedFileId = FileNapCatOneBotUUID.encode(peer, msg.msgId, elementWrapper.elementId, "." + element.fileName);
return {
type: OB11MessageDataType.image,
data: {
@@ -114,12 +116,13 @@ export class OneBotMsgApi {
sub_type: element.picSubType,
file_id: encodedFileId,
url: await this.core.apis.FileApi.getImageUrl(element),
path: element.filePath,
file_size: element.fileSize,
file_unique: element.fileName
},
};
} catch (e: any) {
this.core.context.logger.logError('获取图片url失败', e.stack);
this.core.context.logger.logError.bind(this.core.context.logger)('获取图片url失败', e.stack);
return null;
}
},
@@ -135,8 +138,8 @@ export class OneBotMsgApi {
data: {
file: element.fileName,
path: element.filePath,
url: element.filePath,
file_id: FileNapCatOneBotUUID.encode(peer, msg.msgId, elementWrapper.elementId),
url: pathToFileURL(element.filePath).href,
file_id: FileNapCatOneBotUUID.encode(peer, msg.msgId, elementWrapper.elementId, "." + element.fileName),
file_size: element.fileSize,
file_unique: element.fileName,
},
@@ -175,13 +178,16 @@ export class OneBotMsgApi {
peerUid: msg.peerUid,
guildId: '',
};
const { emojiId } = _;
const dir = emojiId.substring(0, 2);
const url = `https://gxh.vip.qq.com/club/item/parcel/item/${dir}/${emojiId}/raw300.gif`;
return {
type: OB11MessageDataType.image,
data: {
file: 'marketface',
file_id: FileNapCatOneBotUUID.encode(peer, msg.msgId, elementWrapper.elementId),
path: elementWrapper.elementId,
url: elementWrapper.elementId,
file_id: FileNapCatOneBotUUID.encode(peer, msg.msgId, elementWrapper.elementId, "." + _.key + ".jpg"),
path: url,
url: url,
file_unique: _.key
},
};
@@ -195,7 +201,7 @@ export class OneBotMsgApi {
guildId: '',
};
if (!records || !element.replyMsgTime || !element.senderUidStr) {
this.core.context.logger.logError('获取不到引用的消息', element.replayMsgSeq);
this.core.context.logger.logError.bind(this.core.context.logger)('获取不到引用的消息', element.replayMsgSeq);
return null;
}
@@ -206,14 +212,14 @@ export class OneBotMsgApi {
},
});
if (records.peerUin === '284840486') {
if (records.peerUin === '284840486' || records.peerUin === '1094950020') {
return createReplyData(records.msgId);
}
let replyMsg = (await this.core.apis.MsgApi.queryMsgsWithFilterExWithSeqV2(peer, element.replayMsgSeq, element.replyMsgTime, [element.senderUidStr]))
const replyMsg = (await this.core.apis.MsgApi.queryMsgsWithFilterExWithSeqV2(peer, element.replayMsgSeq, element.replyMsgTime, [element.senderUidStr]))
.msgList.find(msg => msg.msgRandom === records.msgRandom);
if (!replyMsg || records.msgRandom !== replyMsg.msgRandom) {
this.core.context.logger.logError('获取不到引用的消息', element.replayMsgSeq);
this.core.context.logger.logError.bind(this.core.context.logger)('获取不到引用的消息', element.replayMsgSeq);
return null;
}
return createReplyData(replyMsg.msgId);
@@ -228,19 +234,29 @@ export class OneBotMsgApi {
//读取视频链接并兜底
let videoUrlWrappers: Awaited<ReturnType<typeof this.core.apis.FileApi.getVideoUrl>> | undefined;
if (msg.peerUin === '284840486') {
//TODO: 合并消息内部 应该进行特殊处理 可能需要重写peer 待测试与研究 Mlikiowa Tagged
}
try {
videoUrlWrappers = await this.core.apis.FileApi.getVideoUrl({
chatType: msg.chatType,
peerUid: msg.peerUid,
guildId: '0',
}, msg.msgId, elementWrapper.elementId);
} catch (error) {
this.core.context.logger.logWarn('获取视频 URL 失败');
if (msg.peerUin === '284840486' || msg.peerUin === '1094950020') {
try {
videoUrlWrappers = await this.core.apis.FileApi.getVideoUrl({
chatType: msg.chatType,
peerUid: msg.peerUid,
guildId: '0',
}, msg.parentMsgIdList[0] ?? msg.msgId, elementWrapper.elementId);
} catch (error) {
this.core.context.logger.logWarn('合并获取视频 URL 失败');
}
} else {
try {
videoUrlWrappers = await this.core.apis.FileApi.getVideoUrl({
chatType: msg.chatType,
peerUid: msg.peerUid,
guildId: '0',
}, msg.msgId, elementWrapper.elementId);
} catch (error) {
this.core.context.logger.logWarn('获取视频 URL 失败');
}
}
//读取在线URL
let videoDownUrl: string | undefined;
@@ -257,14 +273,14 @@ export class OneBotMsgApi {
if (!videoDownUrl) {
videoDownUrl = element.filePath;
}
const fileCode = FileNapCatOneBotUUID.encode(peer, msg.msgId, elementWrapper.elementId, "." + element.fileName);
return {
type: OB11MessageDataType.video,
data: {
file: element.fileName,
file: fileCode,
path: videoDownUrl,
url: videoDownUrl,
file_id: FileNapCatOneBotUUID.encode(peer, msg.msgId, elementWrapper.elementId),
url: videoDownUrl ?? pathToFileURL(element.filePath).href,
file_id: fileCode,
file_size: element.fileSize,
file_unique: element.fileName,
},
@@ -277,13 +293,16 @@ export class OneBotMsgApi {
peerUid: msg.peerUid,
guildId: '',
};
const fileCode = FileNapCatOneBotUUID.encode(peer, msg.msgId, elementWrapper.elementId, "." + element.fileName);
return {
type: OB11MessageDataType.voice,
data: {
file: element.fileName,
file: fileCode,
path: element.filePath,
file_id: FileNapCatOneBotUUID.encode(peer, msg.msgId, elementWrapper.elementId),
url: pathToFileURL(element.filePath).href,
file_id: fileCode,
file_size: element.fileSize,
file_unique: element.fileName
},
};
},
@@ -412,6 +431,10 @@ export class OneBotMsgApi {
// 从face_config.json中获取表情名称
const sysFaces = faceConfig.sysface;
const face: any = sysFaces.find((systemFace) => systemFace.QSid === parsedFaceId.toString());
if (!face) {
this.core.context.logger.logError.bind(this.core.context.logger)('不支持的ID', id);
return undefined;
}
parsedFaceId = parseInt(parsedFaceId.toString());
let faceType = 1;
if (parsedFaceId >= 222) {
@@ -434,7 +457,6 @@ export class OneBotMsgApi {
},
};
},
[OB11MessageDataType.mface]: async ({
data: {
emoji_package_id, emoji_id, key, summary,
@@ -452,17 +474,17 @@ export class OneBotMsgApi {
// File service
[OB11MessageDataType.image]: async (sendMsg, context) => {
const sendPicElement = await this.core.apis.FileApi.createValidSendPicElement(
context,
(await this.handleOb11FileLikeMessage(sendMsg, context)).path,
sendMsg.data.summary,
sendMsg.data.sub_type,
);
context.deleteAfterSentFiles.push(sendPicElement.picElement.sourcePath);
return sendPicElement;
},
[OB11MessageDataType.file]: async (sendMsg, context) => {
const { path, fileName } = await this.handleOb11FileLikeMessage(sendMsg, context);
return await this.core.apis.FileApi.createValidSendFileElement(path, fileName);
return await this.core.apis.FileApi.createValidSendFileElement(context, path, fileName);
},
[OB11MessageDataType.video]: async (sendMsg, context) => {
@@ -473,9 +495,7 @@ export class OneBotMsgApi {
const uri2LocalRes = await uri2local(this.core.NapCatTempPath, thumb);
if (uri2LocalRes.success) thumb = uri2LocalRes.path;
}
const videoEle = await this.core.apis.FileApi.createValidSendVideoElement(path, fileName, thumb);
context.deleteAfterSentFiles.push(videoEle.videoElement.filePath);
const videoEle = await this.core.apis.FileApi.createValidSendVideoElement(context, path, fileName, thumb);
return videoEle;
},
@@ -499,13 +519,12 @@ export class OneBotMsgApi {
faceElement: {
faceIndex: FaceIndex.dice,
faceType: FaceType.dice,
'faceText': '[骰子]',
'packId': '1',
'stickerId': '33',
'sourceType': 1,
'stickerType': 2,
// resultId: resultId.toString(),
'surpriseId': '',
faceText: '[骰子]',
packId: '1',
stickerId: '33',
sourceType: 1,
stickerType: 2,
surpriseId: '',
// "randomType": 1,
},
}),
@@ -514,15 +533,14 @@ export class OneBotMsgApi {
elementType: ElementType.FACE,
elementId: '',
faceElement: {
'faceIndex': FaceIndex.RPS,
'faceText': '[包剪锤]',
'faceType': 3,
'packId': '1',
'stickerId': '34',
'sourceType': 1,
'stickerType': 2,
// 'resultId': resultId.toString(),
'surpriseId': '',
faceIndex: FaceIndex.RPS,
faceText: '[包剪锤]',
faceType: 3,
packId: '1',
stickerId: '34',
sourceType: 1,
stickerType: 2,
surpriseId: '',
// "randomType": 1,
},
}),
@@ -536,40 +554,37 @@ export class OneBotMsgApi {
[OB11MessageDataType.music]: async ({ data }, context) => {
// 保留, 直到...找到更好的解决方案
if (data.type === 'custom') {
if (!data.url) {
this.core.context.logger.logError('自定义音卡缺少参数url');
return undefined;
}
if (!data.audio) {
this.core.context.logger.logError('自定义音卡缺少参数audio');
return undefined;
}
if (!data.title) {
this.core.context.logger.logError('自定义音卡缺少参数title');
if (data.id !== undefined) {
if (!['qq', '163', 'kugou', 'kuwo', 'migu'].includes(data.type)) {
this.core.context.logger.logError.bind(this.core.context.logger)('音乐卡片type错误, 只支持qq、163、kugou、kuwo、migu当前type:', data.type);
return undefined;
}
} else {
if (!['qq', '163'].includes(data.type)) {
this.core.context.logger.logError('音乐卡片type错误, 只支持qq、163、custom当前type:', data.type);
if (!['qq', '163', 'kugou', 'kuwo', 'migu', 'custom'].includes(data.type)) {
this.core.context.logger.logError.bind(this.core.context.logger)('音乐卡片type错误, 只支持qq、163、kugou、kuwo、migu、custom当前type:', data.type);
return undefined;
}
if (!data.id) {
this.core.context.logger.logError('音乐卡片缺少参数id');
if (!data.url) {
this.core.context.logger.logError.bind(this.core.context.logger)('自定义音卡缺少参数url');
return undefined;
}
if (!data.image) {
this.core.context.logger.logError.bind(this.core.context.logger)('自定义音卡缺少参数image');
return undefined;
}
}
let postData: IdMusicSignPostData | CustomMusicSignPostData;
if (data.type === 'custom' && data.content) {
if (data.id === undefined && data.content) {
const { content, ...others } = data;
postData = { singer: content, ...others };
} else {
postData = data;
}
const signUrl = this.obContext.configLoader.configData.musicSignUrl;
let signUrl = this.obContext.configLoader.configData.musicSignUrl;
if (!signUrl) {
throw Error('音乐消息签名地址未配置');
signUrl = 'https://ss.xingzhige.com/music_card/card';//感谢思思!已获思思许可 其余地方使用请自行询问
//throw Error('音乐消息签名地址未配置');
}
try {
const musicJson = await RequestUtil.HttpGetJson<any>(signUrl, 'POST', postData);
@@ -578,7 +593,7 @@ export class OneBotMsgApi {
type: OB11MessageDataType.json
}, context);
} catch (e) {
this.core.context.logger.logError('生成音乐消息失败', e);
this.core.context.logger.logError.bind(this.core.context.logger)('生成音乐消息失败', e);
}
},
@@ -600,6 +615,14 @@ export class OneBotMsgApi {
}),
[OB11MessageDataType.miniapp]: async () => undefined,
[OB11MessageDataType.contact]: async ({ data }, context) => {
const arkJson = await this.core.apis.UserApi.getBuddyRecommendContactArkJson(data.id.toString(), '');
return this.ob11ToRawConverters.json({
data: { data: arkJson.arkMsg },
type: OB11MessageDataType.json
}, context);
}
};
constructor(obContext: NapCatOneBot11Adapter, core: NapCatCore) {
@@ -612,18 +635,14 @@ export class OneBotMsgApi {
return;
}
for (const element of msg.elements) {
if (element.grayTipElement) {
if (element.grayTipElement.subElementType == NTGrayTipElementSubTypeV2.GRAYTIP_ELEMENT_SUBTYPE_JSON) {
if (element.grayTipElement.jsonGrayTipElement.busiId == 1061) {
const PokeEvent = await this.obContext.apis.FriendApi.parsePrivatePokeEvent(element.grayTipElement);
if (PokeEvent) return PokeEvent;
}
if (element.grayTipElement && element.grayTipElement.subElementType == NTGrayTipElementSubTypeV2.GRAYTIP_ELEMENT_SUBTYPE_JSON) {
if (element.grayTipElement.jsonGrayTipElement.busiId == 1061) {
const PokeEvent = await this.obContext.apis.FriendApi.parsePrivatePokeEvent(element.grayTipElement);
if (PokeEvent) return PokeEvent;
}
if (element.grayTipElement.subElementType == NTGrayTipElementSubTypeV2.GRAYTIP_ELEMENT_SUBTYPE_XMLMSG) {
//好友添加成功事件
if (element.grayTipElement.xmlElement.templId === '10229' && msg.peerUin !== '') {
return new OB11FriendAddNoticeEvent(this.core, parseInt(msg.peerUin));
}
//好友添加成功事件
if (element.grayTipElement.jsonGrayTipElement.busiId == 19324 && msg.peerUid !== '') {
return new OB11FriendAddNoticeEvent(this.core, Number(await this.core.apis.UserApi.getUinByUidV2(msg.peerUid)));
}
}
}
@@ -685,36 +704,46 @@ export class OneBotMsgApi {
}
}
const msgSegments = (await Promise.allSettled(msg.elements.map(
// 处理消息段
const msgSegments = await Promise.allSettled(msg.elements.map(
async (element) => {
for (const key in element) {
if (keyCanBeParsed(key, this.rawToOb11Converters) && element[key]) {
return await this.rawToOb11Converters[key]?.(
const parsedElement = await this.rawToOb11Converters[key]?.(
// eslint-disable-next-line
// @ts-ignore
element[key],
msg,
element,
);
// 对于 face 类型的消息,检查是否存在
if (key === 'faceElement' && !parsedElement) {
return null; // 如果没有找到对应的表情,返回 null
}
return parsedElement;
}
}
},
))).filter(entry => {
));
// 过滤掉无效的消息段
const validSegments = msgSegments.filter(entry => {
if (entry.status === 'fulfilled') {
return !!entry.value;
} else {
this.core.context.logger.logError('消息段解析失败', entry.reason);
this.core.context.logger.logError.bind(this.core.context.logger)('消息段解析失败', entry.reason);
return false;
}
}).map((entry) => (<PromiseFulfilledResult<OB11MessageData>>entry).value);
}).map((entry) => (<PromiseFulfilledResult<OB11MessageData>>entry).value).filter(value => value != null);
const msgAsCQCode = msgSegments.map(msg => encodeCQCode(msg)).join('').trim();
const msgAsCQCode = validSegments.map(msg => encodeCQCode(msg)).join('').trim();
if (messagePostFormat === 'string') {
resMsg.message = msgAsCQCode;
resMsg.raw_message = msgAsCQCode;
} else {
resMsg.message = msgSegments;
resMsg.message = validSegments;
resMsg.raw_message = msgAsCQCode;
}
return resMsg;
@@ -771,7 +800,7 @@ export class OneBotMsgApi {
timeout += PredictTime;// 10S Basic Timeout + PredictTime( For File 512kb/s )
}
} catch (e) {
this.core.context.logger.logError('发送消息计算预计时间异常', e);
this.core.context.logger.logError.bind(this.core.context.logger)('发送消息计算预计时间异常', e);
}
const returnMsg = await this.core.apis.MsgApi.sendMsg(peer, sendElements, waitComplete, timeout);
if (!returnMsg) throw new Error('发送消息失败');
@@ -781,7 +810,7 @@ export class OneBotMsgApi {
peerUid: peer.peerUid,
}, returnMsg.msgId);
deleteAfterSentFiles.forEach(file => {
fsPromise.unlink(file).then().catch(e => this.core.context.logger.logError('发送消息删除文件失败', e));
fsPromise.unlink(file).then().catch(e => this.core.context.logger.logError.bind(this.core.context.logger)('发送消息删除文件失败', e));
});
return returnMsg;
}
@@ -790,30 +819,59 @@ export class OneBotMsgApi {
{ data: inputdata }: OB11MessageFileBase,
{ deleteAfterSentFiles }: MessageContext,
) {
const isBlankUrl = !inputdata.url || inputdata.url === '';
const isBlankFile = !inputdata.file || inputdata.file === '';
if (isBlankUrl && isBlankFile) {
this.core.context.logger.logError('文件消息缺少参数', inputdata);
const realUri = inputdata.url || inputdata.file || inputdata.path || '';
if (realUri.length === 0) {
this.core.context.logger.logError.bind(this.core.context.logger)('文件消息缺少参数', inputdata);
throw Error('文件消息缺少参数');
}
const fileOrUrl = (isBlankUrl ? inputdata.file : inputdata.url) ?? '';
const {
path,
isLocal,
fileName,
errMsg,
success,
} = (await uri2local(this.core.NapCatTempPath, fileOrUrl));
} = (await uri2local(this.core.NapCatTempPath, realUri));
if (!success) {
this.core.context.logger.logError('文件下载失败', errMsg);
this.core.context.logger.logError.bind(this.core.context.logger)('文件下载失败', errMsg);
throw Error('文件下载失败' + errMsg);
}
if (!isLocal) { // 只删除http和base64转过来的文件
deleteAfterSentFiles.push(path);
}
deleteAfterSentFiles.push(path);
return { path, fileName: inputdata.name ?? fileName };
}
async parseSysMessage(msg: number[]) {
const sysMsg = decodeSysMessage(Uint8Array.from(msg));
if (sysMsg.msgSpec.length === 0) {
return;
}
const { msgType, subType, subSubType } = sysMsg.msgSpec[0];
if (msgType === 528 && subType === 39 && subSubType === 39) {
if (!sysMsg.bodyWrapper) return;
const event = await this.obContext.apis.UserApi.parseLikeEvent(sysMsg.bodyWrapper.wrappedBody);
return event;
}
/*
if (msgType === 732 && subType === 16 && subSubType === 16) {
const greyTip = GreyTipWrapper.fromBinary(Uint8Array.from(sysMsg.bodyWrapper!.wrappedBody.slice(7)));
if (greyTip.subTypeId === 36) {
const emojiLikeToOthers = EmojiLikeToOthersWrapper1
.fromBinary(greyTip.rest)
.wrapper!
.body!;
if (emojiLikeToOthers.attributes?.operation !== 1) { // Un-like
return;
}
const eventOrEmpty = await this.apis.GroupApi.createGroupEmojiLikeEvent(
greyTip.groupCode.toString(),
await this.core.apis.UserApi.getUinByUidV2(emojiLikeToOthers.attributes!.senderUid),
emojiLikeToOthers.msgSpec!.msgSeq.toString(),
emojiLikeToOthers.attributes!.emojiId,
);
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
eventOrEmpty && await this.networkManager.emitEvent(eventOrEmpty);
}
}
*/
}
}

View File

@@ -47,7 +47,7 @@ export class OneBotQuickActionApi {
const peerContextMode = msg.message_type == 'private' ? ContextMode.Private : ContextMode.Group;
const peer: Peer = await createContext(this.core, {
message: "",
message_type: undefined,
group_id: msg.group_id?.toString(),
user_id: msg.user_id?.toString(),
}, peerContextMode);

View File

@@ -1,6 +1,8 @@
import { NapCatCore } from '@/core';
import { decodeProfileLikeTip } from '@/core/proto/ProfileLike';
import { NapCatOneBot11Adapter } from '@/onebot';
import { OB11ProfileLikeEvent } from '../event/notice/OB11ProfileLikeEvent';
export class OneBotUserApi {
obContext: NapCatOneBot11Adapter;
@@ -10,4 +12,21 @@ export class OneBotUserApi {
this.obContext = obContext;
this.core = core;
}
async parseLikeEvent(wrappedBody: Uint8Array): Promise<OB11ProfileLikeEvent | undefined> {
const likeTip = decodeProfileLikeTip(Uint8Array.from(wrappedBody));
if (likeTip?.msgType !== 0 || likeTip?.subType !== 203) return;
this.core.context.logger.logDebug("收到点赞通知消息");
const likeMsg = likeTip.content.msg;
if (!likeMsg) return;
const detail = likeMsg.detail;
if (!detail) return;
const times = detail.txt.match(/\d+/) ?? "0";
return new OB11ProfileLikeEvent(
this.core,
Number(detail.uin),
detail.nickname,
parseInt(times[0], 10),
likeMsg.time,
);
}
}

View File

@@ -48,7 +48,8 @@ export class OB11Entities {
}[role];
}
static sex(sex: Sex): OB11UserSex {
static sex(sex?: Sex): OB11UserSex {
if (!sex) return OB11UserSex.unknown;
return {
[Sex.male]: OB11UserSex.male,
[Sex.female]: OB11UserSex.female,
@@ -109,7 +110,7 @@ export class OB11Entities {
static file(peerId: string, file: Exclude<GroupFileInfoUpdateParamType['item'][0]['fileInfo'], undefined>): OB11GroupFile {
return {
group_id: parseInt(peerId),
file_id: FileNapCatOneBotUUID.encodeModelId({ chatType: 2, peerUid: peerId }, file.fileModelId, file.fileId),
file_id: FileNapCatOneBotUUID.encodeModelId({ chatType: 2, peerUid: peerId }, file.fileModelId, file.fileId, file.fileName),
file_name: file.fileName,
busid: file.busId,
size: parseInt(file.fileSize),
@@ -126,6 +127,7 @@ export class OB11Entities {
return {
group_id: parseInt(peerId),
folder_id: folder.folderId,
folder: folder.folderId,
folder_name: folder.folderName,
create_time: folder.createTime,
creator: parseInt(folder.createUin),

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