Compare commits

...

609 Commits

Author SHA1 Message Date
手瓜一十雪
d2a2c1c39c fix: #50 2024-11-14 13:58:25 +08:00
手瓜一十雪
ce9b09e8d1 fix: 简化代码 2024-11-14 13:49:37 +08:00
手瓜一十雪
2f6dfe51f5 fix: error 2024-11-14 13:43:39 +08:00
手瓜一十雪
bd227cd0b8 refactor: getGroupHonorInfo 2024-11-14 13:42:03 +08:00
手瓜一十雪
96003724ab refactor: nc shell login 2024-11-14 13:40:01 +08:00
pk5ls20
6a08b15095 chore: eslint 2024-11-14 13:29:39 +08:00
手瓜一十雪
dab0f9ab45 refactor: getImageUrl 2024-11-14 13:26:12 +08:00
手瓜一十雪
e733a6b69a fix: error 2024-11-14 13:22:58 +08:00
手瓜一十雪
9aca98bf13 fix: type 2024-11-14 13:19:50 +08:00
手瓜一十雪
b7c95e53dc fix: unuse import 2024-11-14 13:14:27 +08:00
手瓜一十雪
f762c450ca fix: error 2024-11-14 13:10:52 +08:00
手瓜一十雪
d58bbe53da Merge pull request #521 from abc1763613206/main
feat: add `emoji_package_id` for MarketFace
2024-11-14 13:10:04 +08:00
abc1763613206
f32edd8af7 feat: add emoji_package_id for MarketFace 2024-11-14 13:07:57 +08:00
手瓜一十雪
c747a86e5b fix 2024-11-14 13:07:10 +08:00
手瓜一十雪
abfda0dd58 fix: || -> ?? 2024-11-14 13:00:25 +08:00
手瓜一十雪
f66d7b11a8 fix: error throw 2024-11-14 12:54:58 +08:00
手瓜一十雪
f425c9478e fix 2024-11-14 12:48:19 +08:00
手瓜一十雪
756dea71fc remove: todo -> work 2024-11-14 12:44:21 +08:00
手瓜一十雪
71a6c4ccc5 fix: error handle 2024-11-14 12:37:16 +08:00
手瓜一十雪
ae2f4777ec fix 2024-11-14 12:32:48 +08:00
手瓜一十雪
dcd9b8168a feat: any listener 2024-11-14 12:28:08 +08:00
手瓜一十雪
4bb03ae5ba fix 2024-11-14 12:22:28 +08:00
手瓜一十雪
8bd6f8397b fix: assertion is unnecessary 2024-11-14 12:07:26 +08:00
手瓜一十雪
096e52d93e fix: promise async 2024-11-14 12:06:45 +08:00
手瓜一十雪
037065291d fix: 代码精简 2024-11-14 12:02:24 +08:00
手瓜一十雪
4cf52e1b13 fix: error 2024-11-14 11:53:55 +08:00
手瓜一十雪
21b228552d fix 2024-11-14 11:46:37 +08:00
手瓜一十雪
76b404cdd8 rename: WsPacketClient 2024-11-14 11:40:16 +08:00
手瓜一十雪
937c594ff7 fix 2024-11-14 11:35:10 +08:00
手瓜一十雪
b463140de7 Merge branch 'main' of https://github.com/NapNeko/NapCatQQ 2024-11-14 11:30:56 +08:00
手瓜一十雪
f518fb9214 fix: readonly 2024-11-14 11:28:06 +08:00
手瓜一十雪
1092831718 Merge pull request #520 from NapNeko/refactor-4.0.0
refactor: 4.0.0
2024-11-14 11:24:57 +08:00
手瓜一十雪
6b377416da refactor: fix 2024-11-14 11:24:00 +08:00
手瓜一十雪
8f5baa47ec refactor: log最佳实践 2024-11-14 11:10:26 +08:00
手瓜一十雪
5494ff0553 refactor: build 2024-11-14 11:03:11 +08:00
手瓜一十雪
7a4805b464 refactor: registerListen 2024-11-14 10:57:57 +08:00
手瓜一十雪
8435375810 style: lint 2024-11-14 10:53:50 +08:00
手瓜一十雪
c893ec6030 refactor: apiInit Refactor 2024-11-14 10:52:03 +08:00
手瓜一十雪
e8bf6fa0a6 style: lint 2024-11-14 10:45:16 +08:00
手瓜一十雪
f228129c19 refactor: Init Core 2024-11-14 10:43:37 +08:00
手瓜一十雪
cbf98ffb89 refactor: async Init 2024-11-14 10:36:51 +08:00
手瓜一十雪
f6067b002f refactor: Shell Init 2024-11-14 10:34:38 +08:00
手瓜一十雪
636d1103e3 refactor: 删除反撤回模块 未来合并到MoeHoo 2024-11-14 10:30:01 +08:00
手瓜一十雪
bede517f7e refactor: package 2024-11-14 10:27:10 +08:00
手瓜一十雪
16e4891b7d fix 2024-11-13 22:54:56 +08:00
手瓜一十雪
3bcd79fbb7 docs: 文档精简 2024-11-13 22:46:48 +08:00
Mlikiowa
aacf6c2917 release: v3.7.0 2024-11-13 09:53:56 +00:00
pk5ls20
92d720cd57 Merge pull request #512 from NapNeko/refactor/packet-2
refactor: packet
2024-11-13 17:51:00 +08:00
pk5ls20
2ea025047f feat: comment out logic that is not currently needed 2024-11-13 17:48:42 +08:00
pk5ls20
f7f7e09cab feat: sysMessage oldProto adapter 2024-11-13 17:44:12 +08:00
pk5ls20
75866b435e feat: errorStack 2024-11-13 16:52:03 +08:00
手瓜一十雪
f07941685b Merge branch 'main' into refactor/packet-2 2024-11-13 16:00:45 +08:00
Mlikiowa
60a0539216 release: v3.6.17 2024-11-13 07:57:01 +00:00
手瓜一十雪
3dd4b6549f Merge branch 'main' of https://github.com/NapNeko/NapCatQQ 2024-11-13 15:56:31 +08:00
手瓜一十雪
0802c35dc1 feat: mface emoji_id 2024-11-13 15:56:17 +08:00
Mlikiowa
7d9d7226ec release: v3.6.16 2024-11-13 07:55:21 +00:00
手瓜一十雪
b5ef6ce6b0 feat: mface key 扩展 2024-11-13 15:54:31 +08:00
pk5ls20
49ec6181b0 fix: macos 2024-11-13 15:35:07 +08:00
pk5ls20
783a534768 fix: NativePacketClient 2024-11-13 14:16:42 +08:00
Mlikiowa
704ac11cbb release: v3.6.15 2024-11-13 05:16:36 +00:00
手瓜一十雪
aa9663d85e fix: 移除chalk 2024-11-13 13:16:09 +08:00
Mlikiowa
05291f34fb release: v3.6.14 2024-11-13 04:31:12 +00:00
手瓜一十雪
2260fe32a1 Merge pull request #514 from NapNeko/refactor-log4js--winston
refactor: log4js to winston
2024-11-13 12:29:35 +08:00
手瓜一十雪
2c398a6832 fix: error 2024-11-13 12:29:17 +08:00
手瓜一十雪
3e1f566699 feat: 异常处理与log4js替换 2024-11-13 12:26:23 +08:00
手瓜一十雪
4f89f184b8 refactor: package json 2024-11-13 12:19:18 +08:00
Mlikiowa
787685c937 release: v3.6.13 2024-11-12 10:45:11 +00:00
手瓜一十雪
ed9cd2fe38 fix: #513 2024-11-12 18:37:44 +08:00
pk5ls20
740d80e851 chore: minor adjust packet module 2024-11-12 04:14:08 +08:00
pk5ls20
4520a20bd4 chore: remove debug output 2024-11-12 04:08:51 +08:00
pk5ls20
98c65c4923 refactor: packet x1 2024-11-12 04:02:19 +08:00
Mlikiowa
e287906a9d release: v3.6.12 2024-11-11 13:03:29 +00:00
手瓜一十雪
8bae789020 Merge branch 'main' of https://github.com/NapNeko/NapCatQQ 2024-11-11 21:00:27 +08:00
手瓜一十雪
ce57b7b725 fix: #509 2024-11-11 21:00:12 +08:00
pk5ls20
1d9872195d fix: offset name 2024-11-11 20:57:30 +08:00
pk5ls20
98d1f8e29f feat: add some macOS offset 2024-11-11 20:54:57 +08:00
手瓜一十雪
221b3fb730 fix 2024-11-11 20:35:13 +08:00
Mlikiowa
90a834495a release: v3.6.11 2024-11-11 12:17:25 +00:00
手瓜一十雪
8bfd102232 fix: macos arm64 28971 2024-11-11 20:16:44 +08:00
Mlikiowa
65e784f169 release: v3.6.10 2024-11-11 02:45:39 +00:00
手瓜一十雪
0fc81c672f fix: once升级 2024-11-11 10:45:15 +08:00
手瓜一十雪
62ae0f4321 feat: 文档镜像 2024-11-10 12:40:18 +08:00
Mlikiowa
a01a0a1a18 release: v3.6.9 2024-11-10 04:11:16 +00:00
手瓜一十雪
4c30cc69ad fix: #73 2024-11-10 12:10:43 +08:00
Mlikiowa
1d43b75df4 release: v3.6.8 2024-11-09 10:22:17 +00:00
手瓜一十雪
d02afdfc3e fix: 缓存 2024-11-09 18:21:50 +08:00
Mlikiowa
5d6dee9fd0 release: v3.6.7 2024-11-09 04:36:58 +00:00
手瓜一十雪
60c67ef41c Merge branch 'main' of https://github.com/NapNeko/NapCatQQ 2024-11-09 12:36:21 +08:00
手瓜一十雪
917d7c1f19 feat: user boot script 2024-11-09 12:35:50 +08:00
Mlikiowa
ad19f2c99e release: v3.6.6 2024-11-09 04:22:55 +00:00
手瓜一十雪
8a61f5a03f fix: #503 2024-11-09 12:22:28 +08:00
手瓜一十雪
8c164910f6 docs: 因为优点太多所以只好去掉几条了 2024-11-08 16:23:20 +08:00
pk5ls20
a560d3d266 chore: eslint migrate 2024-11-08 15:43:27 +08:00
手瓜一十雪
532f739272 Merge pull request #501 from NapNeko/eslint9
feat: eslint9
2024-11-08 12:38:12 +08:00
手瓜一十雪
a120727f2d feat: eslint9 2024-11-08 12:36:25 +08:00
手瓜一十雪
a9bcb830a8 feat: 迁移29456为标准版本 2024-11-08 12:14:15 +08:00
Mlikiowa
56e5f0033f release: v3.6.5 2024-11-08 01:20:08 +00:00
手瓜一十雪
101106996a feat: 29456 2024-11-08 09:18:40 +08:00
pk5ls20
41a81534dc feat: support 29456 2024-11-08 00:26:09 +08:00
Mlikiowa
1425e8f229 release: v3.6.4 2024-11-07 08:14:56 +00:00
手瓜一十雪
75bb1d2193 Merge pull request #499 from kanocence/main
修复 config 页面样式
2024-11-07 16:14:01 +08:00
手瓜一十雪
2a23820f9b Merge pull request #500 from Stapxs/patch-1
fix: 在处理 file uri 时可能会意外忽略 fragment 段
2024-11-07 16:13:06 +08:00
林小槐
2ee0fed047 fix: 在处理 file uri 时可能会意外忽略 fragment 段
在处理类似 C:\\test\\test#1.txt 时 #1.txt 由于为 url fragment 而被意外截断
2024-11-07 16:10:59 +08:00
kanocence
40be6b9c43 Merge branch 'NapNeko:main' into main 2024-11-07 15:34:19 +08:00
kanocence
a06b3f0246 feat: 💄 修改页面样式 2024-11-07 15:33:57 +08:00
Mlikiowa
4787fa53b4 release: v3.6.3 2024-11-07 04:16:13 +00:00
手瓜一十雪
a06158bf01 fix: 标准化接口 2024-11-07 12:15:49 +08:00
pk5ls20
314e7485b8 chore: format 2024-11-07 10:33:01 +08:00
pk5ls20
aed5d2d9f0 chore: log 2024-11-07 10:31:50 +08:00
pk5ls20
f44e48a28b fix: remove useless import 2024-11-06 16:58:12 +08:00
手瓜一十雪
38be90450c feat: 兼容gocq标准 2024-11-06 16:48:18 +08:00
手瓜一十雪
2dd57d7676 Merge branch 'main' of https://github.com/NapNeko/NapCatQQ 2024-11-06 16:45:04 +08:00
手瓜一十雪
6b3b163fa8 fix: 错误代码 2024-11-06 16:45:01 +08:00
Mlikiowa
9792ebafdc release: v3.6.2 2024-11-06 08:00:05 +00:00
手瓜一十雪
d10e7c37cb fix: link 2024-11-06 15:59:39 +08:00
Mlikiowa
d38f1853a4 release: v3.6.1 2024-11-06 06:42:33 +00:00
手瓜一十雪
bdec16266e fix: #498 2024-11-06 14:42:05 +08:00
Mlikiowa
49ca698ab9 release: v3.6.0 2024-11-06 03:30:17 +00:00
pk5ls20
3efd8163c9 fix: MoeHoo-Linux Amd64 2024-11-06 11:28:57 +08:00
pk5ls20
cc2d11449c release: v3.5.2 2024-11-06 09:28:14 +08:00
pk5ls20
7e9c19ca5b fix: MoeHoo-Linux Arm64 2024-11-06 09:26:15 +08:00
Mlikiowa
3b01b6827f release: v3.5.1 2024-11-05 14:45:36 +00:00
手瓜一十雪
8d9ef851ba fix: linux arm64 2024-11-05 22:45:00 +08:00
手瓜一十雪
b070bc59bc fix: MoeHoo-Linux Amd64 2024-11-05 22:36:47 +08:00
Mlikiowa
8d663946e1 release: v3.5.0 2024-11-05 14:13:33 +00:00
pk5ls20
2a2328b029 feat: better edge case handling 2024-11-05 22:11:01 +08:00
pk5ls20
efc9064abb fix: better log 2024-11-05 21:54:52 +08:00
Mlikiowa
dd70adf071 release: v3.4.11 2024-11-05 13:52:22 +00:00
pk5ls20
0f427375cb fix: sendCommand 2024-11-05 21:48:39 +08:00
Mlikiowa
4001270b93 release: v3.4.10 2024-11-05 13:34:25 +00:00
手瓜一十雪
e7f5ed3bcc Merge branch 'main' of https://github.com/NapNeko/NapCatQQ 2024-11-05 21:33:51 +08:00
手瓜一十雪
05cdc37d0a fix: qqnt 29271 最新版兼容问题 2024-11-05 21:33:36 +08:00
Mlikiowa
27920e0bee release: v3.4.9 2024-11-05 13:22:43 +00:00
手瓜一十雪
ae409b7249 Merge branch 'main' of https://github.com/NapNeko/NapCatQQ 2024-11-05 21:22:10 +08:00
手瓜一十雪
8276258348 fix: Once 2024-11-05 21:21:59 +08:00
Mlikiowa
1bf96a97a5 release: v3.4.8 2024-11-05 13:18:35 +00:00
手瓜一十雪
d672680c4c feat: 复活吧我的arm64 2024-11-05 21:17:07 +08:00
手瓜一十雪
b89f2805e7 feat: linux.x64'packet 2024-11-05 21:12:19 +08:00
手瓜一十雪
78b4aa9295 feat: linux x64 support 2024-11-05 21:11:41 +08:00
手瓜一十雪
0a06637e78 style: lint 2024-11-05 20:59:18 +08:00
手瓜一十雪
13afa2c7ab fix: 去掉开发日志 2024-11-05 20:54:39 +08:00
手瓜一十雪
51d34d17cc feat: 去掉无用日志 2024-11-05 20:52:36 +08:00
手瓜一十雪
18a99341d5 Merge pull request #494 from NapNeko/dependabot/npm_and_yarn/vite-tsconfig-paths-5.1.0
chore(deps-dev): bump vite-tsconfig-paths from 4.3.2 to 5.1.0
2024-11-05 20:50:59 +08:00
手瓜一十雪
f01c8f0110 Merge pull request #493 from NapNeko/multi-packet
refactor: automatically select the optimal packet backend
2024-11-05 20:50:36 +08:00
手瓜一十雪
d8070eee2a fix: LL 2024-11-05 20:49:16 +08:00
手瓜一十雪
8519b7f4df update: LiteLoader 2024-11-05 20:48:28 +08:00
手瓜一十雪
591ab1b1df feat: 去掉开发输出 2024-11-05 20:40:25 +08:00
手瓜一十雪
393815b11e fix 2024-11-05 20:33:55 +08:00
dependabot[bot]
341a397bc4 chore(deps-dev): bump vite-tsconfig-paths from 4.3.2 to 5.1.0
Bumps [vite-tsconfig-paths](https://github.com/aleclarson/vite-tsconfig-paths) from 4.3.2 to 5.1.0.
- [Release notes](https://github.com/aleclarson/vite-tsconfig-paths/releases)
- [Commits](https://github.com/aleclarson/vite-tsconfig-paths/compare/v4.3.2...v5.1.0)

---
updated-dependencies:
- dependency-name: vite-tsconfig-paths
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-05 08:39:26 +00:00
pk5ls20
e46d274a75 feat: add new NapCat config key: packetBackend
- Acceptable values: `native`, `frida`, `auto`, `disable`
- Default value is set to `auto`
2024-11-05 14:45:02 +08:00
pk5ls20
ad6f21980c refactor: auto judge client 2024-11-05 14:24:54 +08:00
pk5ls20
017b8b7f15 chore: better log 2024-11-05 13:52:56 +08:00
pk5ls20
9b448b17e6 refactor: NapProto -> https://github.com/NapNeko/NapProto 2024-11-05 12:47:28 +08:00
手瓜一十雪
f9996a9987 fix: 日志乱飞版本 2024-11-05 11:24:36 +08:00
手瓜一十雪
000ef55273 fix: 一点小问题 2024-11-05 10:25:41 +08:00
手瓜一十雪
e1ac0f02b4 fix: 搞炸了 让我思考下 2024-11-05 10:22:52 +08:00
手瓜一十雪
b9297e3f1d fix 2024-11-05 10:18:11 +08:00
手瓜一十雪
34d0669ca8 fix 2024-11-05 10:16:06 +08:00
手瓜一十雪
25e42720cf fix: 开始初步调试 2024-11-05 10:14:00 +08:00
手瓜一十雪
f7c1951191 fix: 一些异常类型 2024-11-05 10:07:56 +08:00
pk5ls20
479b971b0c refactor: automatically select the optimal packet backend 2024-11-04 23:52:52 +08:00
手瓜一十雪
347ba5f354 feat: 初步封装 NativePacketClient 2024-11-04 21:09:11 +08:00
pk5ls20
81dbb9d980 perf: use cache in NapProto 2024-11-04 14:42:16 +08:00
pk5ls20
c4e1a3ab04 fix: SendGroupAiRecord wrong return id 2024-11-04 14:41:16 +08:00
pk5ls20
90ec774a21 feat: better mface toPreview 2024-11-03 17:29:50 +08:00
手瓜一十雪
db7a27e624 style: lint 2024-11-03 12:13:56 +08:00
Mlikiowa
f7d965eda2 release: v3.4.7 2024-11-03 01:50:09 +00:00
手瓜一十雪
74ca2e2e16 Merge pull request #485 from clansty/revert/get_forward_msg
revert: 还原 ob11 风格 get_forward_msg
2024-11-03 09:48:07 +08:00
Clansty
8ab550f2f5 revert: 还原 ob11 风格 get_forward_msg 2024-11-03 09:44:35 +08:00
pk5ls20
018aca4db2 fix: type hint 2024-11-03 02:45:58 +08:00
Mlikiowa
d4327166c1 release: v3.4.6 2024-11-02 05:20:34 +00:00
手瓜一十雪
fa25d2e779 feat: arm64 29271 2024-11-02 13:19:13 +08:00
手瓜一十雪
3ce1c3f0ec feat: support 3.2.13-29271-x64 2024-11-02 10:58:36 +08:00
手瓜一十雪
96dff5141e feat: packet 29271 2024-11-02 10:46:15 +08:00
手瓜一十雪
78d85d9965 feat: 29271 2024-11-02 10:02:39 +08:00
手瓜一十雪
37ec455b02 Merge pull request #484 from NapNeko/feat/ai-voice
feat: ai voice
2024-11-02 08:15:05 +08:00
pk5ls20
6ab82739a6 feat: ai voice 2024-11-02 01:51:57 +08:00
手瓜一十雪
a36917e7c0 Merge branch 'main' of https://github.com/NapNeko/NapCatQQ 2024-11-01 12:13:24 +08:00
手瓜一十雪
21f3428b36 feat: support 6.9.58-28971 2024-11-01 12:13:04 +08:00
Mlikiowa
f8a487db25 release: v3.4.5 2024-10-31 12:30:25 +00:00
手瓜一十雪
73a859be04 fix: report self 2024-10-31 20:30:02 +08:00
手瓜一十雪
63bcee01a1 fix: report self 2024-10-31 20:27:17 +08:00
Mlikiowa
85b4966ba8 release: v3.4.4 2024-10-31 11:32:42 +00:00
手瓜一十雪
36c2c567b7 fix: #444 2024-10-31 19:32:10 +08:00
Mlikiowa
7b1ac224f6 release: v3.4.3 2024-10-31 10:18:21 +00:00
手瓜一十雪
34d9f04f15 Merge branch 'main' of https://github.com/NapNeko/NapCatQQ 2024-10-31 18:17:53 +08:00
手瓜一十雪
be5da7cc6f fix: 正向ws异常推送事件问题 2024-10-31 18:17:41 +08:00
Mlikiowa
8d32ccb5d4 release: v3.4.2 2024-10-31 10:03:19 +00:00
手瓜一十雪
6acceb884c fix: #473 2024-10-31 18:00:55 +08:00
Hao Guan
4c834fd640 chore: Major获取Appid添加提示 (#480) 2024-10-31 16:10:27 +08:00
Mlikiowa
301278c7a9 release: v3.4.1 2024-10-31 00:36:50 +00:00
凌莞~(=^▽^=)
42ee83c54f feat: GetStrangerInfo 加回以前的完整信息 (#479) 2024-10-31 07:25:13 +08:00
Mlikiowa
e631f69621 release: v3.4.0 2024-10-30 13:11:06 +00:00
Nepenthe
ce8760a39a 修复<get_record>接口 (#478) 2024-10-30 21:09:32 +08:00
Mlikiowa
ff952956de release: v3.3.27 2024-10-30 07:28:17 +00:00
手瓜一十雪
28f3ff4971 fix: reply msg 大坐牢 #452 #477 2024-10-30 15:27:26 +08:00
手瓜一十雪
19e728c3cb feat: 提升全平台兼容性 2024-10-30 13:50:47 +08:00
Mlikiowa
269773ed6b release: v3.3.26 2024-10-30 01:23:01 +00:00
Hao Guan
e0d32417e1 chore: AppID for macOS 6.9.58-28971 (#476) 2024-10-30 09:20:32 +08:00
pk5ls20
9fa6083bed refactor: kill any (#475)
* refactor: kill any stage 1

* refactor: kill any stage 2

* refactor: kill any stage 3
2024-10-30 09:10:30 +08:00
手瓜一十雪
4d2fccdfb4 style: lint 2024-10-29 18:48:20 +08:00
Mlikiowa
c1c4bdfe94 release: v3.3.25 2024-10-29 10:42:12 +00:00
手瓜一十雪
8a0e9e8b61 release: v3.3.25 2024-10-29 18:41:39 +08:00
手瓜一十雪
1190e14171 docs: 调整文档优先级 2024-10-29 14:25:36 +08:00
Mlikiowa
00292b177a release: v3.3.22 2024-10-29 02:56:37 +00:00
手瓜一十雪
88de57f984 Merge pull request #472 from pohgxz/main
完善<set_input_status>接口
2024-10-29 10:53:29 +08:00
手瓜一十雪
61ddf38892 fix: Error 2024-10-29 10:52:50 +08:00
Nepenthe
52b3540ec3 修改<get_profile_like>接口 2024-10-29 07:51:16 +08:00
Nepenthe
5f831958c3 完善<set_input_status>接口 2024-10-28 23:21:49 +08:00
手瓜一十雪
c3d4698af3 try fix: error 2024-10-28 21:34:13 +08:00
Mlikiowa
bd6e83217d release: v3.3.21 2024-10-28 04:05:30 +00:00
pk5ls20
50ec49d9a2 feat: GetMiniAppArk 2024-10-28 10:12:24 +08:00
pk5ls20
dc3a089070 chore: rename msg to message in packet module 2024-10-28 07:59:24 +08:00
Mlikiowa
530e380178 release: v3.3.20 2024-10-27 14:46:24 +00:00
手瓜一十雪
10e4387add fix: script 2024-10-27 22:45:51 +08:00
手瓜一十雪
e925bc3aa8 Merge branch 'main' of https://github.com/NapNeko/NapCatQQ 2024-10-27 22:44:04 +08:00
手瓜一十雪
427b3a7560 release: v3.3.18 2024-10-27 22:43:55 +08:00
Version
c8da950725 chore:version change 2024-10-27 14:42:23 +00:00
手瓜一十雪
743c5b8196 Merge branch 'main' of https://github.com/NapNeko/NapCatQQ 2024-10-27 22:41:56 +08:00
手瓜一十雪
5e62abea57 fix: version 控制 2024-10-27 22:41:46 +08:00
Version
6bfc545582 chore:version change 2024-10-27 14:37:13 +00:00
手瓜一十雪
411108a2d2 fix: version check 2024-10-27 22:36:48 +08:00
Version
308a6fa9e4 chore:version change 2024-10-27 14:33:40 +00:00
Version
2dc7b785d0 chore:version change 2024-10-27 14:33:19 +00:00
手瓜一十雪
0e69e9e839 fix: checkVersion 2024-10-27 22:32:52 +08:00
手瓜一十雪
b83229b5da feat: 自动化版本发布控制 2024-10-27 22:30:01 +08:00
手瓜一十雪
6f053f5f7d feat: 我补药要手动release啦! 2024-10-27 22:20:11 +08:00
手瓜一十雪
c3dc53eaaf release: v3.3.12 2024-10-27 22:14:17 +08:00
手瓜一十雪
ffdc34cfe2 Merge pull request #470 from pohgxz/main
修复<get_group_at_all_remain>接口总是返回<Error: atInfo not found>
2024-10-27 22:10:55 +08:00
手瓜一十雪
4825a0e341 fix: type Error 2024-10-27 22:07:11 +08:00
Nepenthe
95a00d7f35 修复<get_group_at_all_remain>接口总是返回<Error: atInfo not found> 2024-10-27 22:04:48 +08:00
手瓜一十雪
d885bab426 feat: 类型修复 2024-10-27 22:03:22 +08:00
手瓜一十雪
e2a6a0bc02 release: v3.2.12 2024-10-27 20:56:27 +08:00
手瓜一十雪
ff7d8609ce fix: #452 修复seq搜索的老问题 可能修好了 2024-10-27 20:52:09 +08:00
手瓜一十雪
7507b90e03 fix: #458 2024-10-27 20:38:02 +08:00
手瓜一十雪
2b226a4b27 release: v3.1.11 2024-10-27 19:29:42 +08:00
手瓜一十雪
8b0232c4fe fix: error 2024-10-27 11:17:01 +08:00
手瓜一十雪
0728ee9ad6 Merge branch 'main' of https://github.com/NapNeko/NapCatQQ 2024-10-27 11:09:00 +08:00
手瓜一十雪
8c6f04d0bc feat: #469 回收连接(未测试) 2024-10-27 11:08:48 +08:00
pk5ls20
c67fad789e fix: compatibility bigint 2024-10-27 10:50:48 +08:00
手瓜一十雪
4072339d70 release: v3.1.10 2024-10-27 10:03:07 +08:00
手瓜一十雪
3a244f5804 style: lint 2024-10-27 10:02:42 +08:00
pk5ls20
f12cf59137 feat: enhance compatibility of upload_forward_msg with go-cqhttp 2024-10-27 09:59:38 +08:00
手瓜一十雪
c76f556a11 Merge branch 'main' of https://github.com/NapNeko/NapCatQQ 2024-10-27 09:44:26 +08:00
手瓜一十雪
e0f3d07b98 release: 3.1.9 2024-10-27 09:44:14 +08:00
手瓜一十雪
378d85dc67 Merge pull request #468 from NapNeko/refactor/msg-element
refactor: core msg entity & packet msg converter & resolve #455
2024-10-27 09:40:41 +08:00
pk5ls20
875e91fc0e chore: simplify logic 2024-10-27 09:37:17 +08:00
pk5ls20
15f7cd9814 feat: better fake forwardMsg logic & display 2024-10-27 09:33:20 +08:00
pk5ls20
1eb5cd6237 fix: downloadRawMsgMedia edge case 2024-10-27 09:04:24 +08:00
pk5ls20
ad2f843c8f fix: downloadRawMsgMedia 2024-10-27 07:31:32 +08:00
pk5ls20
8e550e216e chore: i18n for packet log messages 2024-10-27 07:04:53 +08:00
pk5ls20
9f07b07c82 feat: support node id in fake forward (with auto download richMedia in reference message) 2024-10-27 06:50:59 +08:00
pk5ls20
0be6effc32 feat: support node id in fake forward (broken impl) 2024-10-27 05:19:53 +08:00
pk5ls20
7ab6a10fc9 refactor & fix: refactor msg entity & adjust some wrong definition 2024-10-27 04:16:15 +08:00
手瓜一十雪
fb09af0e64 release: v3.1.8 2024-10-26 21:25:03 +08:00
手瓜一十雪
0d99d30b2d feat: version hint 2024-10-26 21:24:24 +08:00
手瓜一十雪
0000ec8b5b fix: fetchFavEmojiList 2024-10-26 21:15:11 +08:00
手瓜一十雪
0085bd8a1f fix: q-gate 2024-10-26 20:46:38 +08:00
手瓜一十雪
617139dfa4 Merge branch 'main' of https://github.com/NapNeko/NapCatQQ 2024-10-26 20:36:59 +08:00
手瓜一十雪
4eb4a612d0 fix: type 2024-10-26 20:25:34 +08:00
pk5ls20
cda5e784f6 fix: payload basic check in GetPacketStatusDepends 2024-10-26 19:51:43 +08:00
手瓜一十雪
d93a280ab3 fix: 进一步getNextMemberList 2024-10-26 18:40:21 +08:00
手瓜一十雪
f7e2b3a4a7 feat: new function 2024-10-26 18:10:08 +08:00
手瓜一十雪
39d9c8fa74 release: v3.1.7 2024-10-26 16:26:30 +08:00
手瓜一十雪
8823895a03 Merge pull request #466 from cnxysoft/upmain
perf: 群成员拉取
2024-10-26 16:18:17 +08:00
手瓜一十雪
b44a9e696c Merge branch 'main' into pr/466 2024-10-26 15:47:13 +08:00
手瓜一十雪
cf28a3dc17 fix: ai solve 2024-10-26 10:49:18 +08:00
手瓜一十雪
7416e6caf6 feat: GoCQHTTPDeleteFriend 2024-10-26 10:36:41 +08:00
手瓜一十雪
90f6896f3c feat: GoCQ兼容性提高 2024-10-26 10:22:04 +08:00
Alen
eebcd0700d Merge branch 'main' into upmain 2024-10-26 07:22:25 +08:00
Alen
133eee0c66 perf: 群成员拉取
getgroupmemberlist启用no_cache
2024-10-26 07:20:40 +08:00
pk5ls20
640fb75f74 feat: support for customizing the timestamp of fake forwardMsg 2024-10-26 04:06:42 +08:00
Alen
51dcc1add6 Merge branch 'main' into upmain 2024-10-25 23:58:18 +08:00
Alen
730c928f91 Merge pull request #465 from cnxysoft/upmain
refactor: 群成员列表获取
2024-10-25 23:49:27 +08:00
Alen
c3b7e111b9 style: 2024-10-25 22:26:18 +08:00
pk5ls20
1874e48925 Merge pull request #464 from clansty/feat/nested-forward
feat: 嵌套合并转发消息
2024-10-25 22:13:32 +08:00
pk5ls20
e7a082c91c feat: better recursive parsing with depth limits 2024-10-25 22:10:24 +08:00
Alen
5d4f45407e fix: 群成员拉取 2024-10-25 21:50:19 +08:00
Clansty
17c37ec32f feat: 嵌套合并转发消息 2024-10-25 19:37:04 +08:00
手瓜一十雪
b5f8140c79 feat: v3 Logo 2024-10-25 19:31:44 +08:00
手瓜一十雪
63f746c237 style: lint 2024-10-25 18:09:41 +08:00
手瓜一十雪
dac6709f27 feat: 6.9.56-28418-mac 2024-10-25 17:57:28 +08:00
手瓜一十雪
470c8d0b29 release: v3.1.6 2024-10-25 17:44:30 +08:00
Wesley F. Young
b0d35e803b update: bump express version to 5.0.0 (Why use beta.2?) 2024-10-25 09:59:02 +08:00
pk5ls20
a71475be8b feat: allow pass string user_id in handleForwardedNodesPacket 2024-10-25 09:17:16 +08:00
pk5ls20
b9f2cc5142 feat: reject >100MB video highway upload 2024-10-25 08:59:56 +08:00
pk5ls20
2d46e55b9b feat: better highway upload log 2024-10-25 08:54:18 +08:00
pk5ls20
684e254996 feat: make PacketMsgPttElement invalid 2024-10-25 08:33:26 +08:00
手瓜一十雪
a2f7903960 Merge pull request #460 from NapNeko/feat/packet-more
feat: support more element in proto
2024-10-25 08:26:52 +08:00
pk5ls20
c0c757d6bd Merge branch 'main' into feat/packet-more 2024-10-25 08:17:28 +08:00
pk5ls20
da0fad743d feat: maybe more stable fake forwardMsg 2024-10-25 08:09:17 +08:00
手瓜一十雪
80b10d6025 Merge pull request #463 from clansty/feat/custom-forward-display
feat: 自定义合并转发外显信息
2024-10-25 08:05:41 +08:00
pk5ls20
a27c2a69c4 feat: maybe more stable fake forwardMsg 2024-10-25 07:27:35 +08:00
pk5ls20
9ed2a2fd19 refactor: simplify oidb packet pack & send 2024-10-25 06:48:01 +08:00
pk5ls20
aa9d96718c refactor: outer calculation 2024-10-25 05:54:46 +08:00
pk5ls20
aa67a2b71c chore: cv多了( 2024-10-25 05:17:01 +08:00
pk5ls20
d3405edd42 refactor: packet highway & etc, kill some todo 2024-10-25 05:11:10 +08:00
Clansty
3612098d62 feat: 自定义合并转发外显信息 2024-10-25 02:42:50 +08:00
Alen
2f08b72d69 fix: 群成员拉取 2024-10-24 23:00:38 +08:00
手瓜一十雪
ab66904c1a feat: 3.2.13-28971-arm64 2024-10-24 21:57:54 +08:00
手瓜一十雪
55542a3dbe feat: 28971 Linux 2024-10-24 20:40:58 +08:00
手瓜一十雪
8569a45114 release: v3.1.5 2024-10-24 20:16:12 +08:00
手瓜一十雪
c790311fc3 release: v3.1.5 2024-10-24 20:11:39 +08:00
手瓜一十雪
3c45c8bd80 feat: 28971 2024-10-24 20:11:07 +08:00
手瓜一十雪
d5b7b3ae31 feat: ntappid 2024-10-24 17:55:33 +08:00
手瓜一十雪
43e73a5f24 doc: big Logo 2024-10-24 17:03:23 +08:00
手瓜一十雪
698947ed97 Merge branch 'main' into feat/packet-more 2024-10-24 14:00:17 +08:00
手瓜一十雪
f3d967ae07 release: 3.1.4 2024-10-24 13:39:05 +08:00
手瓜一十雪
dbe72fa07e feat: SetGroupSign 2024-10-24 13:38:22 +08:00
pk5ls20
801a97d85b chore: remove useless log 2024-10-24 04:58:53 +08:00
pk5ls20
9f8f938c47 feat: build & upload file 2024-10-24 04:53:41 +08:00
Wesley F. Young
8fe37d1c1e chore: reformat package.json 2024-10-23 17:54:12 +08:00
pk5ls20
5cca8457e7 chore: 有笨蛋 2024-10-23 16:33:05 +08:00
pk5ls20
e9332e7646 feat: add ptt msg pack & upload 2024-10-23 16:12:31 +08:00
手瓜一十雪
31365505d8 Merge pull request #461 from huankong233/main
优化 contact 支持群聊和私聊
2024-10-23 09:09:07 +08:00
huankong233
b3fbe9e34a 优化 contact 支持群聊和私聊 2024-10-23 09:05:45 +08:00
pk5ls20
4082b651c5 feat & fix: add video msg pack & upload, fix some bugs in uploading c2c elements 2024-10-23 06:14:48 +08:00
Alen
0081000ef0 Merge branch 'main' into upmain 2024-10-23 01:08:56 +08:00
Alen
ad4d6a1070 refactor: 群成员获取 2024-10-23 01:07:52 +08:00
手瓜一十雪
5190b26399 Merge pull request #457 from huankong233/main
删除一些过时的接口
2024-10-22 17:57:04 +08:00
手瓜一十雪
29a8db96f4 fix 2024-10-22 17:56:51 +08:00
huankong233
1a4c2cabfd 删除一些过时的接口 2024-10-22 16:45:52 +08:00
手瓜一十雪
ef9189055c release: 3.1.3 2024-10-22 12:43:54 +08:00
手瓜一十雪
5cc3719125 fix: rkey 2024-10-22 12:42:24 +08:00
手瓜一十雪
5d46f41348 fix: dep 2024-10-22 12:14:43 +08:00
手瓜一十雪
3c2c1963f4 release: 3.1.2 2024-10-22 12:11:02 +08:00
手瓜一十雪
4896ca9279 fix 2024-10-22 11:37:01 +08:00
手瓜一十雪
f0afba6cd9 fix: GetOnlineClient 2024-10-22 11:34:28 +08:00
手瓜一十雪
bd717c298a fix: get_online_clients 2024-10-22 11:17:39 +08:00
手瓜一十雪
baaa8a70dc release: 3.1.1 2024-10-22 11:08:24 +08:00
手瓜一十雪
6d561c6e6f Merge branch 'main' of https://github.com/NapNeko/NapCatQQ 2024-10-22 11:04:43 +08:00
手瓜一十雪
e6b6947d49 feat: 标准化凭据获取 2024-10-22 11:04:28 +08:00
手瓜一十雪
52e99a2175 Merge pull request #454 from huankong233/main
简单修复一些小问题
2024-10-22 10:17:08 +08:00
手瓜一十雪
052d17a46f fix: getfile 2024-10-22 10:15:16 +08:00
huankong233
1aa1f4c212 优化 getFile 处理逻辑 2024-10-22 09:48:55 +08:00
huankong233
c3a48e3344 修复标记好友/群聊信息已读逻辑 2024-10-21 19:51:17 +08:00
手瓜一十雪
1d5483dc28 Merge pull request #451 from huankong233/main
对接口顺序和文档同步
2024-10-21 16:47:46 +08:00
huankong233
54277fa0df CleanCache 未实现 2024-10-21 16:09:27 +08:00
huankong233
ab04bd262f 对接口顺序和文档同步 2024-10-21 15:49:57 +08:00
手瓜一十雪
fb23087b65 release: 3.1.0 2024-10-21 14:35:57 +08:00
手瓜一十雪
846fee7ac8 fix: error import 2024-10-21 14:33:12 +08:00
手瓜一十雪
977eacc679 try: fix arm64 2024-10-21 14:12:10 +08:00
手瓜一十雪
dacfefe644 style: lint 2024-10-21 10:17:31 +08:00
pk5ls20
345e941e11 chore: remove unnecessary comments 2024-10-21 04:10:53 +08:00
pk5ls20
6cb7d45464 feat & refactor: decouple the forwardMsg construction logic and implement the OB11 element conversion for the forward node. 2024-10-21 04:05:02 +08:00
pk5ls20
e7222653fa release: 3.0.6 2024-10-20 23:54:21 +08:00
pk5ls20
014f0758f5 chore: 部分回滚 https://github.com/NapNeko/NapCatQQ/commit/bb72d70b 2024-10-20 23:52:36 +08:00
pk5ls20
0e8b416f6d Merge pull request #448 from pk5ls20/feat/friend-poke
feat: add `friend_poke` OneBot11 API
2024-10-20 23:18:26 +08:00
pk5ls20
09a60a2204 feat: add friend_poke OneBot11 API 2024-10-20 23:09:38 +08:00
手瓜一十雪
b0eae307c2 release: 3.0.5 2024-10-20 22:18:57 +08:00
手瓜一十雪
f5d2b54cca fix: 兼容晚启动 2024-10-20 22:18:34 +08:00
手瓜一十雪
3eefec3899 release: v3.0.4 2024-10-20 19:52:23 +08:00
手瓜一十雪
b6a8094554 release: v3.0.3 2024-10-20 18:56:52 +08:00
Version
4083b35436 chore:version change 2024-10-20 10:55:12 +00:00
手瓜一十雪
bb72d70baf fix: #444 尝试修复 2024-10-20 18:52:18 +08:00
手瓜一十雪
95d1a77f52 fix: remark 2024-10-20 18:30:14 +08:00
手瓜一十雪
051729886e fix 2024-10-20 17:16:05 +08:00
手瓜一十雪
0f00123dc7 fix 2024-10-20 17:01:09 +08:00
手瓜一十雪
0b0a089d86 release: 3.0.1 2024-10-20 10:07:19 +08:00
手瓜一十雪
c711a7d99a fix: error 2024-10-20 10:05:58 +08:00
手瓜一十雪
43f1d8c88c Merge pull request #443 from pk5ls20/feat/i18n-packet-server-error-msg
feat: More user-friendly packetServer error message
2024-10-20 08:14:33 +08:00
手瓜一十雪
e818e79d20 Merge pull request #442 from pk5ls20/feat/better-forward-msg
feat: better fake forwardMsg display
2024-10-20 08:14:18 +08:00
pk5ls20
cbad3ff1de feat: More user-friendly packetServer error message x2 2024-10-20 07:46:57 +08:00
pk5ls20
16a2e5e996 feat: More user-friendly packetServer error message 2024-10-20 07:28:55 +08:00
pk5ls20
331c6a50d0 feat: better fake forwardMsg display 2024-10-20 07:06:13 +08:00
手瓜一十雪
31c4540ec6 fix: error 2024-10-19 23:00:39 +08:00
手瓜一十雪
1e6116554f fix: error Version 2024-10-19 22:53:32 +08:00
手瓜一十雪
a12ea0e761 Merge pull request #436 from pk5ls20/refactor/proto
Progressive NapCat.Packet
2024-10-19 22:20:40 +08:00
pk5ls20
c9e3bbcd9f feat: Implement complete transform & Build & Upload FakeForwardMsg 2024-10-19 22:13:31 +08:00
手瓜一十雪
9c17dc1b8f fix: error 2024-10-19 21:21:54 +08:00
手瓜一十雪
69d1cae686 fix: 进一步简化 2024-10-19 21:21:37 +08:00
手瓜一十雪
1c2404b6af fix: 再次简化日志 2024-10-19 21:19:14 +08:00
手瓜一十雪
b33b33739d fix: 日志梳理 2024-10-19 21:17:49 +08:00
手瓜一十雪
2b7886c682 fix: 日志有点乱 2024-10-19 19:33:01 +08:00
手瓜一十雪
106d1f6374 fix 2024-10-19 18:08:25 +08:00
手瓜一十雪
e601786bd7 fix: CloudFlare Url 2024-10-19 18:00:34 +08:00
手瓜一十雪
fda2a98b40 version: 3.0.0 2024-10-19 17:54:26 +08:00
手瓜一十雪
c01d70b8fc fix: Mirrror Docs 2024-10-19 17:47:05 +08:00
手瓜一十雪
eccbcc3e28 fix:docs 2024-10-19 09:25:40 +08:00
pk5ls20
7a4a255a89 refactor: simplify the PacketClient availability check process & add action nc_get_packet_status 2024-10-19 04:41:32 +08:00
pk5ls20
83bced82b1 feat: add action get_group_file_url 2024-10-19 04:14:01 +08:00
pk5ls20
f3033ce732 feat: remove 10ms delay in sendSsoCmdReqByContend 2024-10-19 02:12:22 +08:00
pk5ls20
5c21a1727c feat: add packGroupFileDownloadReq & packC2CFileDownloadReq 2024-10-19 02:05:46 +08:00
pk5ls20
93aab437b7 chore: standardize proto file naming 2024-10-19 01:59:11 +08:00
pk5ls20
34e797270f feat: adjust FileNapCatOneBotUUID to support encode fileUUID 2024-10-19 01:39:14 +08:00
手瓜一十雪
0f337a8d8c feat: 引导使用28788 2024-10-18 22:34:58 +08:00
手瓜一十雪
cc9b83089e Merge branch 'main' into pr/436 2024-10-18 21:44:34 +08:00
手瓜一十雪
a565929686 fix 2024-10-18 21:43:54 +08:00
手瓜一十雪
6adacea774 support linux x64 2024-10-18 21:07:32 +08:00
手瓜一十雪
47ab5421ed fix: appid-msf 2024-10-18 20:04:58 +08:00
pk5ls20
10c404d455 feat: adjust packetElement & packetMsg 2024-10-18 16:57:55 +08:00
pk5ls20
dfdca11155 feat & fix: revert assert import & support MFace element 2024-10-18 16:34:45 +08:00
pk5ls20
698e095364 feat & refactor: add more packetElement & refactor packetMsg 2024-10-18 16:01:54 +08:00
手瓜一十雪
524fd258d8 fix: 定义 2024-10-18 14:35:26 +08:00
pk5ls20
17e70a4360 feat: add more msgElement 2024-10-18 04:49:38 +08:00
pk5ls20
e4a533e7b7 feat: add more msgElement 2024-10-18 04:35:23 +08:00
pk5ls20
0cb68d3737 chore: remove useless comment 2024-10-17 23:38:44 +08:00
pk5ls20
9faeadbebe feat: parse trpc.group.long_msg_interface.MsgService.SsoSendLongMsg resp 2024-10-17 23:34:29 +08:00
pk5ls20
35d201cfb8 feat: support upload c2c pic 2024-10-17 23:29:53 +08:00
pk5ls20
205174255f feat: Introduce a 10ms delay to sendSsoCmdReqByContend and cache prepareUpload requests 2024-10-17 22:41:39 +08:00
pk5ls20
8873a030ab feat: packet highway (in right impl) 2024-10-17 19:45:21 +08:00
pk5ls20
0ab61bac12 refactor: optimised code 2024-10-17 03:29:36 +08:00
pk5ls20
b1157f60f5 feat & refactor: packet highway (in almost right impl) 2024-10-17 03:03:36 +08:00
手瓜一十雪
bb93df06b2 fix 2024-10-16 21:02:28 +08:00
手瓜一十雪
82e807fd80 fix 2024-10-16 21:02:09 +08:00
手瓜一十雪
29da539467 fix: cache 2024-10-16 20:50:44 +08:00
手瓜一十雪
659aa005b0 fix: 部分离谱情况 2024-10-16 20:49:12 +08:00
手瓜一十雪
3f20733e7e refactor: groupMember 2024-10-16 20:09:01 +08:00
手瓜一十雪
b15e1174d6 update: LL 2024-10-16 19:23:38 +08:00
pk5ls20
05b05fd74e Merge remote-tracking branch 'fork/refactor/proto' into refactor/proto 2024-10-16 11:59:57 +08:00
pk5ls20
d30d467a21 feat: broken highway 2024-10-16 11:58:47 +08:00
手瓜一十雪
cd62e8ca37 fix: memberLevel 2024-10-16 11:35:12 +08:00
手瓜一十雪
f9e44820c1 style: 标准化 2024-10-15 09:20:54 +08:00
手瓜一十雪
169ae6a4d0 style: 规范写法 2024-10-15 09:11:00 +08:00
手瓜一十雪
030ba15952 style: lint 2024-10-15 09:06:47 +08:00
手瓜一十雪
964874bdad fix: 临时会话上报问题 2024-10-15 08:58:28 +08:00
手瓜一十雪
7affa081ac fix #398 2024-10-14 22:13:41 +08:00
手瓜一十雪
10e281ed35 fix #428 2024-10-14 22:08:00 +08:00
手瓜一十雪
27081ae599 fix #391 2024-10-14 22:04:02 +08:00
手瓜一十雪
61cbcdffe8 fix #431 2024-10-14 21:56:51 +08:00
手瓜一十雪
eeb15ea564 fix: #429 2024-10-14 21:49:42 +08:00
手瓜一十雪
565c820925 fix: #408 2024-10-14 21:45:36 +08:00
pk5ls20
325dff5735 feat: minor feat client & add TODO 2024-10-14 18:03:30 +08:00
pk5ls20
397c2cf5f0 refactor: further decoupling of Packet and Core parts 2024-10-14 17:51:21 +08:00
pk5ls20
1fbc339a42 feat: update type define in packet 2024-10-14 17:05:04 +08:00
pk5ls20
f2c719c60d chore: format & minor refactor 2024-10-14 15:37:02 +08:00
pk5ls20
08505fcc9a feat: simplify code 2024-10-14 15:29:20 +08:00
pk5ls20
a79c933693 refactor: add available accessor property in PacketClient 2024-10-14 15:13:44 +08:00
pk5ls20
b4cb3ddf1c refactor: packet 2024-10-14 13:59:34 +08:00
手瓜一十雪
aa188a6e89 fix 2024-10-14 09:13:03 +08:00
手瓜一十雪
a04b6b8a70 fix 2024-10-14 09:09:29 +08:00
手瓜一十雪
11149d2743 fix 2024-10-14 09:07:03 +08:00
pk5ls20
86bfd990db feat: partly impl UploadForwardMsg 2024-10-14 02:25:56 +08:00
pk5ls20
9304430889 fix: deprecate the cache in constructor in NapProtoMsg 2024-10-14 01:07:14 +08:00
手瓜一十雪
095f1c270b Merge branch 'refactor/proto' of https://github.com/pk5ls20/NapCatQQ into pr/436 2024-10-13 19:39:25 +08:00
手瓜一十雪
d3f91a832b fix 2024-10-13 19:39:03 +08:00
pk5ls20
4790a1170f Merge remote-tracking branch 'fork/refactor/proto' into refactor/proto 2024-10-13 19:31:47 +08:00
pk5ls20
501c392028 feat: flexible NapProtoStructType 2024-10-13 19:31:32 +08:00
手瓜一十雪
9200520f70 fix: add test 2024-10-13 19:23:28 +08:00
手瓜一十雪
8122561337 fix 2024-10-13 19:06:12 +08:00
手瓜一十雪
c6dc86ef8d Merge branch 'refactor/proto' of https://github.com/pk5ls20/NapCatQQ into pr/436 2024-10-13 19:03:47 +08:00
手瓜一十雪
bea3b8485f fix 2024-10-13 17:32:49 +08:00
pk5ls20
b807b89cdc Merge remote-tracking branch 'fork/refactor/proto' into refactor/proto 2024-10-13 17:23:32 +08:00
pk5ls20
daac2f7fd9 refactor: packet 2024-10-13 17:23:07 +08:00
手瓜一十雪
f0a5523174 fix 2024-10-13 17:21:03 +08:00
手瓜一十雪
eda8fbb178 fix 2024-10-13 17:15:59 +08:00
手瓜一十雪
67ca6184e9 Revert "fix"
This reverts commit d79e91fc1e.
2024-10-13 17:09:23 +08:00
手瓜一十雪
d79e91fc1e fix 2024-10-13 17:05:19 +08:00
手瓜一十雪
1cdb93baa2 feat: get_rkey 2024-10-13 16:47:22 +08:00
手瓜一十雪
f91991e25c feat: Packet Rkey 2024-10-13 16:46:12 +08:00
pk5ls20
d21da47a7d feat & fix: feat proto & revert NapProto changes 2024-10-13 16:13:34 +08:00
手瓜一十雪
b4e22a345d feat:GetUserStatus 2024-10-13 14:18:35 +08:00
手瓜一十雪
30e594ae5f fix: arch 2024-10-13 14:07:04 +08:00
手瓜一十雪
ffba3573ba feat: 区分arch 2024-10-13 14:05:40 +08:00
手瓜一十雪
9df5bee8d3 fix 2024-10-13 13:55:20 +08:00
手瓜一十雪
71c0728622 feat: buildSetSpecialTittlePacket 2024-10-13 13:54:52 +08:00
手瓜一十雪
476d8ba14d fix 2024-10-13 13:35:17 +08:00
手瓜一十雪
274c956f16 fix 2024-10-13 11:58:08 +08:00
手瓜一十雪
3068f9ee3d fix: catch 2024-10-13 11:22:33 +08:00
手瓜一十雪
a0c49d5f7f feat: errorMsg opt 2024-10-13 10:57:03 +08:00
手瓜一十雪
a8534974fe fix 2024-10-13 10:18:45 +08:00
手瓜一十雪
c517790391 fix: oidb 2024-10-13 10:10:12 +08:00
pk5ls20
b7e875c77f feat: add more proto 2024-10-13 04:15:10 +08:00
pk5ls20
befd9c0624 refactor: adjust NapProto & proto structure 2024-10-13 02:57:12 +08:00
手瓜一十雪
7a46f11089 feat: oidb_0x9067_202 2024-10-12 23:09:33 +08:00
手瓜一十雪
dc168bf8b9 feat: proto 整理 2024-10-12 22:25:10 +08:00
手瓜一十雪
eef5293ca0 refactor: Poke 2024-10-12 22:17:40 +08:00
手瓜一十雪
a2c4498694 style: NapProto 2024-10-12 21:35:49 +08:00
pk5ls20
938a84a460 feat: introduce NapProtoMsg 2024-10-12 21:27:25 +08:00
手瓜一十雪
978d2c24ee fix: 注释掉无用代码 2024-10-12 19:57:51 +08:00
手瓜一十雪
cdd00d665d fix: packet send/recv 2024-10-12 19:51:29 +08:00
手瓜一十雪
bb8b06c044 feat: linux 2024-10-12 18:05:03 +08:00
手瓜一十雪
604c5dcdc1 Merge branch 'main' of https://github.com/NapNeko/NapCatQQ 2024-10-12 16:10:40 +08:00
手瓜一十雪
6bc2ecdbf0 Update onebot11.json 2024-10-12 16:10:28 +08:00
手瓜一十雪
e91c81def7 Merge pull request #435 from NapNeko/dev-packet
build: poke test
2024-10-12 16:09:52 +08:00
手瓜一十雪
bedd2fa15a build: poke test 2024-10-12 15:39:49 +08:00
手瓜一十雪
50465eef54 Merge pull request #434 from NapNeko/dev-packet
Dev packet
2024-10-12 15:38:16 +08:00
手瓜一十雪
07689adfcd fix 2024-10-12 15:36:30 +08:00
手瓜一十雪
8f4f898675 fix 2024-10-12 15:34:10 +08:00
手瓜一十雪
968bd7a437 fix 2024-10-12 15:27:58 +08:00
手瓜一十雪
eba5900ba8 fix: send packet 2024-10-12 15:18:20 +08:00
手瓜一十雪
69c477b104 fix: new server 2024-10-12 14:49:54 +08:00
手瓜一十雪
c8df8f4f54 release: 2.6.27 2024-10-11 23:07:26 +08:00
手瓜一十雪
d35a19b4fd release: olpush remove 2024-10-11 23:06:18 +08:00
手瓜一十雪
a97437a6e5 fix 2024-10-11 23:03:09 +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
246 changed files with 9679 additions and 2320 deletions

View File

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

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

@@ -1,43 +1,50 @@
<div align="center">
<img src="https://socialify.git.ci/NapNeko/NapCatQQ/image?description=1&language=1&logo=https%3A%2F%2Fraw.githubusercontent.com%2FNapNeko%2FNapCatQQ%2Fmain%2Flogo.png&name=1&stargazers=1&theme=Auto" alt="NapCatQQ" width="640" height="320" />
![Logo](https://socialify.git.ci/NapNeko/NapCatQQ/image?font=Jost&logo=https%3A%2F%2Fnapneko.github.io%2Fassets%2Flogo.png&name=1&owner=1&pattern=Diagonal%20Stripes&stargazers=1&theme=Auto)
</div>
---
## 欢迎回
NapCatQQ (aka 猫猫框架) 是现代化的基于 NTQQ 的 Bot 协议端实现
## 欢迎回
NapCatQQ 是现代化的基于 NTQQ 的 Bot 协议端实现
## 猫猫技能
- [x] **高性能**1K+ 群聊数目、20 线程并行发送消息毫无压力
- [x] **多种启动方式**支持以无头、LiteLoader 插件、仅 QQ GUI 三种方式启动
- [x] **多平台支持**: 覆盖 Windows / Linux (可选 Docker) / Android Termux / MacOS
- [x] **安装简单**: 支持一键脚本/程序自动部署/镜像部署等多种覆盖范围
- [x] **低占用**:无头模式占用资源极低,适合在服务器上运行
- [x] **超多接口**:实现大部分 OneBot 和 go-cqhttp 接口,超多扩展 API
- [x] **WebUI**:自带 WebUI 支持,远程管理更加便捷
- [x] **低故障率**:快速适配最新版本,日常保证 0 Issue
## 碎碎叨叨
- [x] **安装简单**:就算是笨蛋也能使用
- [x] **性能友好**:就算是低内存也能使用
- [x] **接口丰富**:就算是没有也能使用
- [x] **稳定好用**:就算是被捉也能使用
## 使用猫猫
## 使用框架
可前往 [Release](https://github.com/NapNeko/NapCatQQ/releases/) 页面下载最新版本
**首次使用**请务必前往[官方文档](https://napneko.github.io/)查看使用教程
**首次使用**请务必查看如下文档看使用教程
### 文档地址
[Cloudflare.Worker](https://doc.napneko.icu/)
[Cloudflare.HKServer](https://napcat.napneko.icu/)
[Cloudflare.Pages](https://napneko.pages.dev/)
[Server.China](https://napneko.com/)
[Server.Other](https://napcat.cyou/)
[Github.IO](https://napneko.github.io/)
## 回家旅途
[QQ Group](https://qm.qq.com/q/VfjAq5HIMS)
[Telegram Link](https://t.me/+nLZEnpne-pQ1OWFl)
## 感谢他们
感谢 [LLOneBot](https://github.com/LLOneBot/LLOneBot)
## 猫猫朋友
感谢 [LLOneBot](https://github.com/LLOneBot/LLOneBot) 提供部分参考
感谢 [Lagrange](https://github.com/LagrangeDev/Lagrange.Core) 对本项目的大力支持
感谢 [Lagrange](https://github.com/LagrangeDev/Lagrange.Core) 对本项目的大力支持 参考部分代码 已获授权
不过最最重要的 还是需要感谢屏幕前的你哦~
---
## 约法三章
> [!CAUTION]\
> **请不要在 QQ 官方群聊和任何影响力较大的简中互联网平台(包括但不限于: 哔哩哔哩,微博,知乎,抖音等)发布和讨论*任何*与本项目存在相关性的信息**
## 开源附加
任何使用本仓库代码的地方,都应当严格遵守[本仓库开源许可](./LICENSE)。**此外,禁止任何项目未经授权二次分发或基于 NapCat 代码开发。**
任何使用本仓库代码的地方,都应当严格遵守[本仓库开源许可](./LICENSE)。**此外,禁止任何项目未经仓库主作者授权二次分发或基于 NapCat 代码开发。**

70
eslint.config.mjs Normal file
View File

@@ -0,0 +1,70 @@
import typescriptEslint from "@typescript-eslint/eslint-plugin";
import _import from "eslint-plugin-import";
import { fixupPluginRules } from "@eslint/compat";
import globals from "globals";
import tsParser from "@typescript-eslint/parser";
import path from "node:path";
import { fileURLToPath } from "node:url";
import js from "@eslint/js";
import { FlatCompat } from "@eslint/eslintrc";
const filename = fileURLToPath(import.meta.url);
const dirname = path.dirname(filename);
const compat = new FlatCompat({
baseDirectory: dirname,
recommendedConfig: js.configs.recommended,
allConfig: js.configs.all
});
export default [{
ignores: ["src/core/proto/"],
}, ...compat.extends("eslint:recommended", "plugin:@typescript-eslint/recommended"), {
plugins: {
"@typescript-eslint": typescriptEslint,
import: fixupPluginRules(_import),
},
languageOptions: {
globals: {
...globals.browser,
...globals.node,
},
parser: tsParser,
ecmaVersion: "latest",
sourceType: "module",
},
settings: {
"import/parsers": {
"@typescript-eslint/parser": [".ts"],
},
"import/resolver": {
typescript: {
alwaysTryTypes: true,
},
},
},
rules: {
indent: ["error", 4],
semi: ["error", "always"],
"no-unused-vars": "off",
"no-async-promise-executor": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-unused-vars": "off",
"@typescript-eslint/no-var-requires": "off",
"object-curly-spacing": ["error", "always"],
},
}, {
files: ["**/.eslintrc.{js,cjs}"],
languageOptions: {
globals: {
...globals.node,
},
ecmaVersion: 5,
sourceType: "commonjs",
},
}];

BIN
external/LiteLoaderWrapper.zip vendored Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,32 @@
@echo off
chcp 65001
set NAPCAT_PATCH_PACKAGE=%cd%\qqnt.json
set NAPCAT_LOAD_PATH=%cd%\loadNapCat.js
set NAPCAT_INJECT_PATH=%cd%\NapCatWinBootHook.dll
set NAPCAT_LAUNCHER_PATH=%cd%\NapCatWinBootMain.exe
set NAPCAT_MAIN_PATH=%cd%\napcat.mjs
:loop_read
for /f "tokens=2*" %%a in ('reg query "HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\QQ" /v "UninstallString"') do (
set RetString=%%b
goto :napcat_boot
)
:napcat_boot
for %%a in ("%RetString%") do (
set "pathWithoutUninstall=%%~dpa"
)
SET QQPath=%pathWithoutUninstall%QQ.exe
if not exist "%QQpath%" (
echo provided QQ path is invalid: %QQpath%
pause
exit /b
)
set NAPCAT_MAIN_PATH=%NAPCAT_MAIN_PATH:\=/%
echo (async () =^> {await import("file:///%NAPCAT_MAIN_PATH%")})() > "%NAPCAT_LOAD_PATH%"
"%NAPCAT_LAUNCHER_PATH%" "%QQPath%" "%NAPCAT_INJECT_PATH%" %1
pause

View File

@@ -0,0 +1,33 @@
@echo off
chcp 65001
set NAPCAT_PATCH_PACKAGE=%cd%\qqnt.json
set NAPCAT_LOAD_PATH=%cd%\loadNapCat.js
set NAPCAT_INJECT_PATH=%cd%\NapCatWinBootHook.dll
set NAPCAT_LAUNCHER_PATH=%cd%\NapCatWinBootMain.exe
set NAPCAT_MAIN_PATH=%cd%\napcat.mjs
:loop_read
for /f "tokens=2*" %%a in ('reg query "HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\QQ" /v "UninstallString"') do (
set RetString=%%b
goto :napcat_boot
)
:napcat_boot
for %%a in ("%RetString%") do (
set "pathWithoutUninstall=%%~dpa"
)
SET QQPath=%pathWithoutUninstall%QQ.exe
if not exist "%QQpath%" (
echo provided QQ path is invalid: %QQpath%
pause
exit /b
)
set NAPCAT_MAIN_PATH=%NAPCAT_MAIN_PATH:\=/%
echo (async () =^> {await import("file:///%NAPCAT_MAIN_PATH%")})() > "%NAPCAT_LOAD_PATH%"
"%NAPCAT_LAUNCHER_PATH%" "%QQPath%" "%NAPCAT_INJECT_PATH%" %1
REM "%NAPCAT_LAUNCHER_PATH%" "%QQPath%" "%NAPCAT_INJECT_PATH%" 123456
pause

View File

@@ -33,7 +33,7 @@ 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%" %1

View File

@@ -34,6 +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%" %1

View File

@@ -1,6 +1,9 @@
{
"name": "qq-chat",
"version": "9.9.15-28060",
"version": "9.9.16-29456",
"verHash": "dd395162",
"linuxVersion": "3.2.13-29456",
"linuxVerHash": "e379390a",
"type": "module",
"private": true,
"description": "QQ",
@@ -15,9 +18,9 @@
"qd": "externals/devtools/cli/index.js"
},
"main": "./loadNapCat.js",
"buildVersion": "28060",
"buildVersion": "29456",
"isPureShell": true,
"isByteCodeShell": true,
"platform": "win32",
"eleArch": "x64"
}
}

BIN
logo.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 208 KiB

After

Width:  |  Height:  |  Size: 335 KiB

View File

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

View File

@@ -1,55 +1,56 @@
{
"name": "napcat",
"private": true,
"type": "module",
"version": "2.6.2",
"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",
"@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/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-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",
"protobufjs": "~7.4.0",
"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": "3.7.0",
"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",
"@eslint/compat": "^1.2.2",
"@eslint/eslintrc": "^3.1.0",
"@eslint/js": "^9.14.0",
"@log4js-node/log4js-api": "^1.0.2",
"@napneko/nap-proto-core": "^0.0.4",
"@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",
"ajv": "^8.13.0",
"async-mutex": "^0.5.0",
"commander": "^12.1.0",
"cors": "^2.8.5",
"eslint": "^9.14.0",
"eslint-import-resolver-typescript": "^3.6.1",
"eslint-plugin-import": "^2.29.1",
"fast-xml-parser": "^4.3.6",
"file-type": "^19.0.0",
"globals": "^15.12.0",
"image-size": "^1.1.1",
"json-schema-to-ts": "^3.1.1",
"typescript": "^5.3.3",
"typescript-eslint": "^8.13.0",
"vite": "^5.2.6",
"vite-plugin-cp": "^4.0.8",
"vite-tsconfig-paths": "^5.1.0",
"winston": "^3.17.0",
"fluent-ffmpeg": "^2.1.2"
},
"dependencies": {
"express": "^5.0.0",
"silk-wasm": "^3.6.1",
"ws": "^8.18.0",
"qrcode-terminal": "^0.12.0"
}
}

View File

@@ -4,16 +4,27 @@ const process = require("process");
console.log("[NapCat] [CheckVersion] 开始检测当前仓库版本...");
try {
const packageJson = require("../package.json");
const manifsetJson = require("../manifest.json");
const currentVersion = packageJson.version;
const targetVersion = process.env.VERSION;
const manifestCurrentVersion = manifsetJson.version;
const manifestTargetVersion = process.env.VERSION;
console.log("[NapCat] [CheckVersion] currentVersion:", currentVersion, "targetVersion:", targetVersion);
console.log("[NapCat] [CheckVersion] manifestCurrentVersion:", manifestCurrentVersion, "manifestTargetVersion:", manifestTargetVersion);
// 验证 targetVersion 格式
if (!targetVersion || typeof targetVersion !== 'string') {
console.log("[NapCat] [CheckVersion] 目标版本格式不正确或未设置!");
return;
}
// 验证 manifestTargetVersion 格式
if (!manifestTargetVersion || typeof manifestTargetVersion !== 'string') {
console.log("[NapCat] [CheckVersion] manifest目标版本格式不正确或未设置");
return;
}
// 写入脚本文件的统一函数
const writeScriptToFile = (content) => {
@@ -21,7 +32,7 @@ try {
console.log("[NapCat] [CheckVersion] checkVersion.sh 文件已更新。");
};
if (currentVersion === targetVersion) {
if (currentVersion === targetVersion && manifestCurrentVersion === manifestTargetVersion) {
// 不需要更新版本,写入一个简单的脚本
const simpleScript = "#!/bin/bash\necho \"CheckVersion Is Done\"";
writeScriptToFile(simpleScript);
@@ -29,11 +40,14 @@ try {
// 更新版本构建安全的sed命令
const safeScriptContent = `
#!/bin/bash
git config --global user.email "bot@test.wumiao.wang"
git config --global user.name "Version"
sed -i "s/\\\"version\\\": \\\"${currentVersion}\\\"/\\\"version\\\": \\\"${targetVersion}\\\"/g" package.json
git config --global user.email "nanaeonn@outlook.com"
git config --global user.name "Mlikiowa"
sed -i "s/\\"version\\": \\"${currentVersion}\\"/\\"version\\": \\"${targetVersion}\\"/g" package.json
sed -i "s/\\"version\\": \\"${manifestCurrentVersion}\\"/\\"version\\": \\"${targetVersion}\\"/g" manifest.json
sed -i "s/napCatVersion = '.*'/napCatVersion = '${targetVersion}'/g" ./src/common/version.ts
sed -i "s/SettingButton(\\"V.*\\", \\"napcat-update-button\\", \\"secondary\\")/SettingButton(\\"V${targetVersion}\\", \\"napcat-update-button\\", \\"secondary\\")/g" ./static/assets/renderer.js
git add .
git commit -m "chore:version change"
git commit -m "release: v${targetVersion}"
git push -u origin main`;
writeScriptToFile(safeScriptContent);
}

View File

@@ -45,9 +45,9 @@ async function handleWavFile(
): 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: await convert(filePath, pcmPath, logger), sampleRate: 24000 };
}
return {input: file, sampleRate: fmt.sampleRate};
return { input: file, sampleRate: fmt.sampleRate };
}
export async function encodeSilk(filePath: string, TEMP_DIR: string, logger: LogWrapper) {
@@ -59,7 +59,7 @@ export async function encodeSilk(filePath: string, TEMP_DIR: string, logger: Log
const pcmPath = `${pttPath}.pcm`;
const { input, sampleRate } = isWav(file)
? (await handleWavFile(file, filePath, pcmPath, logger))
: {input: await convert(filePath, pcmPath, logger), sampleRate: 24000};
: { input: await convert(filePath, pcmPath, logger), sampleRate: 24000 };
const silk = await encode(input, sampleRate);
await fsPromise.writeFile(pttPath, silk.data);
logger.log(`语音文件${filePath}转换成功!`, pttPath, '时长:', silk.duration);
@@ -83,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

@@ -9,12 +9,21 @@ interface InternalMapKey {
checker: ((...args: any[]) => boolean) | undefined;
}
type EnsureFunc<T> = T extends (...args: any) => any ? T : never;
type FuncKeys<T> = Extract<
{
[K in keyof T]: EnsureFunc<T[K]> extends never ? never : K;
}[keyof T],
string
>;
export type ListenerClassBase = Record<string, string>;
export class NTEventWrapper {
private WrapperSession: NodeIQQNTWrapperSession | undefined; //WrapperSession
private listenerManager: Map<string, ListenerClassBase> = new Map<string, ListenerClassBase>(); //ListenerName-Unique -> Listener实例
private EventTask = new Map<string, Map<string, Map<string, InternalMapKey>>>(); //tasks ListenerMainName -> ListenerSubName-> uuid -> {timeout,createtime,func}
private readonly WrapperSession: NodeIQQNTWrapperSession | undefined; //WrapperSession
private readonly listenerManager: Map<string, ListenerClassBase> = new Map<string, ListenerClassBase>(); //ListenerName-Unique -> Listener实例
private readonly EventTask = new Map<string, Map<string, Map<string, InternalMapKey>>>(); //tasks ListenerMainName -> ListenerSubName-> uuid -> {timeout,createtime,func}
constructor(
wrapperSession: NodeIQQNTWrapperSession,
@@ -43,10 +52,8 @@ export class NTEventWrapper {
createEventFunction<
Service extends keyof ServiceNamingMapping,
ServiceMethod extends Exclude<keyof ServiceNamingMapping[Service], symbol>,
// eslint-disable-next-line
// @ts-ignore
T extends (...args: any) => any = ServiceNamingMapping[Service][ServiceMethod],
ServiceMethod extends FuncKeys<ServiceNamingMapping[Service]>,
T extends (...args: any) => any = EnsureFunc<ServiceNamingMapping[Service][ServiceMethod]>,
>(eventName: `${Service}/${ServiceMethod}`): T | undefined {
const eventNameArr = eventName.split('/');
type eventType = {
@@ -98,10 +105,8 @@ export class NTEventWrapper {
async callNoListenerEvent<
Service extends keyof ServiceNamingMapping,
ServiceMethod extends Exclude<keyof ServiceNamingMapping[Service], symbol>,
// eslint-disable-next-line
// @ts-ignore
EventType extends (...args: any) => any = ServiceNamingMapping[Service][ServiceMethod],
ServiceMethod extends FuncKeys<ServiceNamingMapping[Service]>,
EventType extends (...args: any) => any = EnsureFunc<ServiceNamingMapping[Service][ServiceMethod]>,
>(
serviceAndMethod: `${Service}/${ServiceMethod}`,
...args: Parameters<EventType>
@@ -111,15 +116,13 @@ export class NTEventWrapper {
async registerListen<
Listener extends keyof ListenerNamingMapping,
ListenerMethod extends Exclude<keyof ListenerNamingMapping[Listener], symbol>,
// eslint-disable-next-line
// @ts-ignore
ListenerType extends (...args: any) => any = ListenerNamingMapping[Listener][ListenerMethod],
ListenerMethod extends FuncKeys<ListenerNamingMapping[Listener]>,
ListenerType extends (...args: any) => any = EnsureFunc<ListenerNamingMapping[Listener][ListenerMethod]>,
>(
listenerAndMethod: `${Listener}/${ListenerMethod}`,
checker: (...args: Parameters<ListenerType>) => boolean,
waitTimes = 1,
timeout = 5000,
checker: (...args: Parameters<ListenerType>) => boolean,
) {
return new Promise<Parameters<ListenerType>>((resolve, reject) => {
const ListenerNameList = listenerAndMethod.split('/');
@@ -164,15 +167,11 @@ export class NTEventWrapper {
async callNormalEventV2<
Service extends keyof ServiceNamingMapping,
ServiceMethod extends Exclude<keyof ServiceNamingMapping[Service], symbol>,
ServiceMethod extends FuncKeys<ServiceNamingMapping[Service]>,
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]
ListenerMethod extends FuncKeys<ListenerNamingMapping[Listener]>,
EventType extends (...args: any) => any = EnsureFunc<ServiceNamingMapping[Service][ServiceMethod]>,
ListenerType extends (...args: any) => any = EnsureFunc<ListenerNamingMapping[Listener][ListenerMethod]>
>(
serviceAndMethod: `${Service}/${ServiceMethod}`,
listenerAndMethod: `${Listener}/${ListenerMethod}`,
@@ -182,36 +181,36 @@ export class NTEventWrapper {
callbackTimesToWait = 1,
timeout = 5000,
) {
const id = randomUUID();
let complete = 0;
let retData: Parameters<ListenerType> | undefined = undefined;
let retEvent: any = {};
function sendDataCallback(resolve: any, reject: any) {
if (complete == 0) {
reject(
new Error(
'Timeout: NTEvent serviceAndMethod:' +
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];
return new Promise<[EventRet: Awaited<ReturnType<EventType>>, ...Parameters<ListenerType>]>(
(resolve, reject) => {
const id = randomUUID();
let complete = 0;
let retData: Parameters<ListenerType> | undefined = undefined;
let retEvent: any = {};
function sendDataCallback() {
if (complete == 0) {
reject(
new Error(
'Timeout: NTEvent serviceAndMethod:' +
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 timeoutRef = setTimeout(sendDataCallback, timeout);
const timeoutRef = setTimeout(() => sendDataCallback(resolve, reject), timeout);
const eventCallback = {
timeout: timeout,
@@ -222,7 +221,7 @@ export class NTEventWrapper {
retData = args as Parameters<ListenerType>;
if (complete >= callbackTimesToWait) {
clearTimeout(timeoutRef);
sendDataCallback();
sendDataCallback(resolve, reject);
}
},
};
@@ -234,23 +233,26 @@ export class NTEventWrapper {
}
this.EventTask.get(ListenerMainName)?.get(ListenerSubName)?.set(id, eventCallback);
this.createListenerFunction(ListenerMainName);
const eventFunction = this.createEventFunction(serviceAndMethod);
if (eventFunction) eventFunction(...(args)).then((retEvent: Awaited<ReturnType<EventType>>) => {
if (!checkerEvent(retEvent) && timeoutRef.hasRef()) {
clearTimeout(timeoutRef);
reject(
new Error(
'EventChecker Failed: NTEvent serviceAndMethod:' +
serviceAndMethod +
' ListenerName:' +
listenerAndMethod +
' EventRet:\n' +
JSON.stringify(retEvent, null, 4) +
'\n',
),
);
}
});
this.createEventFunction(serviceAndMethod)!(...(args))
.then((eventResult: any) => {
retEvent = eventResult;
if (!checkerEvent(retEvent) && timeoutRef.hasRef()) {
clearTimeout(timeoutRef);
reject(
new Error(
'EventChecker Failed: NTEvent serviceAndMethod:' +
serviceAndMethod +
' ListenerName:' +
listenerAndMethod +
' EventRet:\n' +
JSON.stringify(retEvent, null, 4) +
'\n',
),
);
}
})
.catch(reject);
},
);
}

View File

@@ -144,7 +144,7 @@ async function tryDownload(options: string | HttpDownloadOptions, useReferer: bo
}
if (useReferer && !headers['Referer']) {
headers['Referer'] = url;
};
}
const fetchRes = await fetch(url, { headers }).catch((err) => {
if (err.cause) {
throw err.cause;
@@ -215,7 +215,7 @@ export async function checkUriType(Uri: string) {
}
if (uri.startsWith('file://')) {
let filePath: string;
const pathname = decodeURIComponent(new URL(uri).pathname);
const pathname = decodeURIComponent(new URL(uri).pathname + new URL(uri).hash);
if (process.platform === 'win32') {
filePath = pathname.slice(1);
} else {

View File

@@ -0,0 +1,114 @@
import * as crypto from "node:crypto";
import { PacketMsg } from "@/core/packet/message/message";
interface ForwardMsgJson {
app: string
config: ForwardMsgJsonConfig,
desc: string,
extra: ForwardMsgJsonExtra,
meta: ForwardMsgJsonMeta,
prompt: string,
ver: string,
view: string
}
interface ForwardMsgJsonConfig {
autosize: number,
forward: number,
round: number,
type: string,
width: number
}
interface ForwardMsgJsonExtra {
filename: string,
tsum: number,
}
interface ForwardMsgJsonMeta {
detail: ForwardMsgJsonMetaDetail
}
interface ForwardMsgJsonMetaDetail {
news: {
text: string
}[],
resid: string,
source: string,
summary: string,
uniseq: string
}
interface ForwardAdaptMsg {
senderName?: string;
isGroupMsg?: boolean;
msg?: ForwardAdaptMsgElement[];
}
interface ForwardAdaptMsgElement {
preview?: string;
}
export class ForwardMsgBuilder {
private static build(resId: string, msg: ForwardAdaptMsg[], source?: string, news?: ForwardMsgJsonMetaDetail["news"], summary?: string, prompt?: string): ForwardMsgJson {
const id = crypto.randomUUID();
const isGroupMsg = msg.some(m => m.isGroupMsg);
if (!source) {
source = isGroupMsg ? "群聊的聊天记录" : msg.map(m => m.senderName).filter((v, i, a) => a.indexOf(v) === i).slice(0, 4).join('和') + '的聊天记录';
}
if (!news) {
news = msg.length === 0 ? [{
text: "Nya~ This message is send from NapCat.Packet!",
}] : msg.map(m => ({
text: `${m.senderName}: ${m.msg?.map(msg => msg.preview).join('')}`,
}));
}
if (!summary) {
summary = `查看${msg.length}条转发消息`;
}
if (!prompt) {
prompt = "[聊天记录]";
}
return {
app: "com.tencent.multimsg",
config: {
autosize: 1,
forward: 1,
round: 1,
type: "normal",
width: 300
},
desc: prompt,
extra: {
filename: id,
tsum: msg.length,
},
meta: {
detail: {
news,
resid: resId,
source,
summary,
uniseq: id,
}
},
prompt,
ver: "0.0.0.5",
view: "contact",
};
}
static fromResId(resId: string): ForwardMsgJson {
return this.build(resId, []);
}
static fromPacketMsg(resId: string, packetMsg: PacketMsg[], source?: string, news?: ForwardMsgJsonMetaDetail["news"], summary?: string, prompt?: string): ForwardMsgJson {
return this.build(resId, packetMsg.map(msg => ({
senderName: msg.senderName,
isGroupMsg: msg.groupId !== undefined,
msg: msg.msg.map(m => ({
preview: m.valid ? m.toPreview() : "[该消息类型暂不支持查看]",
}))
})), source, news, summary, prompt);
}
}

View File

@@ -25,8 +25,8 @@ export async function solveAsyncProblem<T extends (...args: any[]) => Promise<an
}
export class FileNapCatOneBotUUID {
static encodeModelId(peer: Peer, modelId: string, fileId: string, endString: string = ""): string {
const data = `NapCatOneBot|ModelIdFile|${peer.chatType}|${peer.peerUid}|${modelId}|${fileId}`;
static encodeModelId(peer: Peer, modelId: string, fileId: string, fileUUID: string = "", endString: string = ""): string {
const data = `NapCatOneBot|ModelIdFile|${peer.chatType}|${peer.peerUid}|${modelId}|${fileId}|${fileUUID}`;
//前四个字节塞data长度
const length = Buffer.alloc(4 + data.length);
length.writeUInt32BE(data.length * 2, 0);//储存data的hex长度
@@ -37,7 +37,8 @@ export class FileNapCatOneBotUUID {
static decodeModelId(uuid: string): undefined | {
peer: Peer,
modelId: string,
fileId: string
fileId: string,
fileUUID?: string
} {
//前四个字节是data长度
const length = Buffer.from(uuid.slice(0, 8), 'hex').readUInt32BE(0);
@@ -47,20 +48,21 @@ export class FileNapCatOneBotUUID {
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;
if (data.length < 6) return undefined; // compatibility requirement
const [, , chatType, peerUid, modelId, fileId, fileUUID = undefined] = data;
return {
peer: {
chatType: chatType as any,
chatType: +chatType,
peerUid: peerUid,
},
modelId,
fileId
fileId,
fileUUID
};
}
static encode(peer: Peer, msgId: string, elementId: string, endString: string = ""): string {
const data = `NapCatOneBot|MsgFile|${peer.chatType}|${peer.peerUid}|${msgId}|${elementId}`;
static encode(peer: Peer, msgId: string, elementId: string, fileUUID: string = "", endString: string = ""): string {
const data = `NapCatOneBot|MsgFile|${peer.chatType}|${peer.peerUid}|${msgId}|${elementId}|${fileUUID}`;
//前四个字节塞data长度
//一个字节8位 一个ascii字符1字节 一个hex字符4位 表示一个ascii字符需要两个hex字符
const length = Buffer.alloc(4 + data.length);
@@ -72,7 +74,8 @@ export class FileNapCatOneBotUUID {
static decode(uuid: string): undefined | {
peer: Peer,
msgId: string,
elementId: string
elementId: string,
fileUUID?: string
} {
//前四个字节是data长度
const length = Buffer.from(uuid.slice(0, 8), 'hex').readUInt32BE(0);
@@ -82,15 +85,16 @@ export class FileNapCatOneBotUUID {
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;
if (data.length < 6) return undefined; // compatibility requirement
const [, , chatType, peerUid, msgId, elementId, fileUUID = undefined] = data;
return {
peer: {
chatType: chatType as any,
chatType: +chatType,
peerUid: peerUid,
},
msgId,
elementId,
fileUUID
};
}
}
@@ -180,11 +184,11 @@ export function getDefaultQQVersionConfigInfo(): QQVersionConfigType {
};
}
return {
baseVersion: '9.9.15-28060',
curVersion: '9.9.15-28060',
baseVersion: '9.9.15-28131',
curVersion: '9.9.15-28131',
prevVersion: '',
onErrorVersions: [],
buildId: '28060',
buildId: '28131',
};
}
@@ -235,3 +239,42 @@ export function calcQQLevel(level?: QQLevel) {
const { crownNum, sunNum, moonNum, starNum } = level;
return crownNum * 64 + sunNum * 16 + moonNum * 4 + starNum;
}
export function stringifyWithBigInt(obj: any) {
return JSON.stringify(obj, (key, value) =>
typeof value === 'bigint' ? value.toString() : value
);
}
export function parseAppidFromMajor(nodeMajor: string): string | undefined {
const hexSequence = "A4 09 00 00 00 35";
const sequenceBytes = Buffer.from(hexSequence.replace(/ /g, ""), "hex");
const filePath = path.resolve(nodeMajor);
const fileContent = fs.readFileSync(filePath);
let searchPosition = 0;
while (true) {
const index = fileContent.indexOf(sequenceBytes, searchPosition);
if (index === -1) {
break;
}
const start = index + sequenceBytes.length - 1;
const end = fileContent.indexOf(0x00, start);
if (end === -1) {
break;
}
const content = fileContent.subarray(start, end);
if (!content.every(byte => byte === 0x00)) {
try {
return content.toString("utf-8");
} catch (error) {
break;
}
}
searchPosition = end + 1;
}
return undefined;
}

View File

@@ -1,7 +1,7 @@
import log4js, { Configuration } from 'log4js';
import winston, { format, transports } from 'winston';
import { truncateString } from '@/common/helper';
import path from 'node:path';
import chalk from 'chalk';
import fs from 'node:fs';
import { AtType, ChatType, ElementType, MessageElement, RawMessage, SelfInfo } from '@/core';
export enum LogLevel {
@@ -27,97 +27,137 @@ function getFormattedTimestamp() {
export class LogWrapper {
fileLogEnabled = true;
consoleLogEnabled = true;
logConfig: Configuration;
loggerConsole: log4js.Logger;
loggerFile: log4js.Logger;
loggerDefault: log4js.Logger;
// eslint-disable-next-line no-control-regex
colorEscape = /\x1B[@-_][0-?]*[ -/]*[@-~]/g;
logger: winston.Logger;
constructor(logDir: string) {
const filename = `${getFormattedTimestamp()}.log`;
const logPath = path.join(logDir, filename);
this.logConfig = {
appenders: {
FileAppender: { // 输出到文件的appender
type: 'file',
filename: logPath, // 指定日志文件的位置和文件名
maxLogSize: 10485760, // 日志文件的最大大小单位字节这里设置为10MB
layout: {
type: 'pattern',
pattern: '%d{yyyy-MM-dd hh:mm:ss} [%p] %X{userInfo} | %m',
},
},
ConsoleAppender: { // 输出到控制台的appender
type: 'console',
layout: {
type: 'pattern',
pattern: `%d{yyyy-MM-dd hh:mm:ss} [%[%p%]] ${chalk.magenta('%X{userInfo}')} | %m`,
},
},
},
categories: {
default: { appenders: ['FileAppender', 'ConsoleAppender'], level: 'debug' }, // 默认情况下同时输出到文件和控制台
file: { appenders: ['FileAppender'], level: 'debug' },
console: { appenders: ['ConsoleAppender'], level: 'debug' },
},
};
log4js.configure(this.logConfig);
this.loggerConsole = log4js.getLogger('console');
this.loggerFile = log4js.getLogger('file');
this.loggerDefault = log4js.getLogger('default');
this.logger = winston.createLogger({
level: 'debug',
format: format.combine(
format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
format.printf(({ timestamp, level, message, ...meta }) => {
const userInfo = meta.userInfo ? `${meta.userInfo} | ` : '';
return `${timestamp} [${level}] ${userInfo}${message}`;
})
),
transports: [
new transports.File({
filename: logPath,
level: 'debug',
maxsize: 5 * 1024 * 1024, // 5MB
maxFiles: 5
}),
new transports.Console({
format: format.combine(
format.colorize(),
format.printf(({ timestamp, level, message, ...meta }) => {
const userInfo = meta.userInfo ? `${meta.userInfo} | ` : '';
return `${timestamp} [${level}] ${userInfo}${message}`;
})
)
})
]
});
this.setLogSelfInfo({ nick: '', uin: '', uid: '' });
this.cleanOldLogs(logDir);
}
cleanOldLogs(logDir: string) {
const oneWeekAgo = Date.now() - 7 * 24 * 60 * 60 * 1000;
fs.readdir(logDir, (err, files) => {
if (err) {
this.logger.error('Failed to read log directory', err);
return;
}
files.forEach(file => {
const filePath = path.join(logDir, file);
this.deleteOldLogFile(filePath, oneWeekAgo);
});
});
}
private deleteOldLogFile(filePath: string, oneWeekAgo: number) {
fs.stat(filePath, (err, stats) => {
if (err) {
this.logger.error('Failed to get file stats', err);
return;
}
if (stats.mtime.getTime() < oneWeekAgo) {
fs.unlink(filePath, err => {
if (err) {
if (err.code === 'ENOENT') {
this.logger.warn(`File already deleted: ${filePath}`);
} else {
this.logger.error('Failed to delete old log file', err);
}
} else {
this.logger.info(`Deleted old log file: ${filePath}`);
}
});
}
});
}
setFileAndConsoleLogLevel(fileLogLevel: LogLevel, consoleLogLevel: LogLevel) {
this.logConfig.categories.file.level = fileLogLevel;
this.logConfig.categories.console.level = consoleLogLevel;
log4js.configure(this.logConfig);
this.logger.transports.forEach((transport) => {
if (transport instanceof transports.File) {
transport.level = fileLogLevel;
} else if (transport instanceof transports.Console) {
transport.level = consoleLogLevel;
}
});
}
setLogSelfInfo(selfInfo: { nick: string, uin: string, uid: string }) {
const userInfo = `${selfInfo.nick}(${selfInfo.uin})`;
this.loggerConsole.addContext('userInfo', userInfo);
this.loggerFile.addContext('userInfo', userInfo);
this.loggerDefault.addContext('userInfo', userInfo);
this.logger.defaultMeta = { userInfo };
}
setFileLogEnabled(isEnabled: boolean) {
this.fileLogEnabled = isEnabled;
this.logger.transports.forEach((transport) => {
if (transport instanceof transports.File) {
transport.silent = !isEnabled;
}
});
}
setConsoleLogEnabled(isEnabled: boolean) {
this.consoleLogEnabled = isEnabled;
this.logger.transports.forEach((transport) => {
if (transport instanceof transports.Console) {
transport.silent = !isEnabled;
}
});
}
formatMsg(msg: any[]) {
let logMsg = '';
for (const msgItem of msg) {
if (msgItem instanceof Error) { // 判断是否是错误
logMsg += msgItem.stack + ' ';
continue;
} else if (typeof msgItem === 'object') { // 判断是否是对象
const obj = JSON.parse(JSON.stringify(msgItem, null, 2));
logMsg += JSON.stringify(truncateString(obj)) + ' ';
continue;
return msg.map(msgItem => {
if (msgItem instanceof Error) {
return msgItem.stack;
} else if (typeof msgItem === 'object') {
return JSON.stringify(truncateString(JSON.parse(JSON.stringify(msgItem, null, 2))));
}
logMsg += msgItem + ' ';
}
return logMsg;
return msgItem;
}).join(' ');
}
_log(level: LogLevel, ...args: any[]) {
if (this.consoleLogEnabled) {
this.loggerConsole[level](this.formatMsg(args));
}
if (this.fileLogEnabled) {
this.loggerFile[level](this.formatMsg(args).replace(this.colorEscape, ''));
const message = this.formatMsg(args);
if (this.consoleLogEnabled && this.fileLogEnabled) {
this.logger.log(level, message);
} else if (this.consoleLogEnabled) {
this.logger.log(level, message);
} else if (this.fileLogEnabled) {
// eslint-disable-next-line no-control-regex
this.logger.log(level, message.replace(/\x1B[@-_][0-?]*[ -/]*[@-~]/g, ''));
}
}
log(...args: any[]) {
// info 等级
this._log(LogLevel.INFO, ...args);
}
@@ -139,9 +179,12 @@ export class LogWrapper {
logMessage(msg: RawMessage, selfInfo: SelfInfo) {
const isSelfSent = msg.senderUin === selfInfo.uin;
this.log(`${
isSelfSent ? '发送 ->' : '接收 <-'
} ${rawMessageToText(msg)}`);
if (msg.elements[0]?.elementType === ElementType.GreyTip) {
return;
}
this.log(`${isSelfSent ? '发送 ->' : '接收 <-'} ${rawMessageToText(msg)}`);
}
}
@@ -155,86 +198,97 @@ 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 */ {
} else {
tokens.push(`临时消息 (${msg.peerUin})`);
}
// message content
function msgElementToText(element: MessageElement) {
if (element.textElement) {
if (element.textElement.atType === AtType.notAt) {
const originalContentLines = element.textElement.content.split('\n');
return `${originalContentLines[0]}${originalContentLines.length > 1 ? ' ...' : ''}`;
} else if (element.textElement.atType === AtType.atAll) {
return `@全体成员`;
} else if (element.textElement.atType === AtType.atUser) {
return `${element.textElement.content} (${element.textElement.atUid})`;
}
}
if (element.replyElement) {
const recordMsgOrNull = msg.records.find(
record => element.replyElement!.sourceMsgIdInRecords === record.msgId,
);
return `[回复消息 ${
recordMsgOrNull &&
recordMsgOrNull.peerUin != '284840486' // 非转发消息; 否则定位不到
?
rawMessageToText(recordMsgOrNull, recursiveLevel + 1) :
`未找到消息记录 (MsgId = ${element.replyElement.sourceMsgIdInRecords})`
}]`;
}
if (element.picElement) {
return '[图片]';
}
if (element.fileElement) {
return `[文件 ${element.fileElement.fileName}]`;
}
if (element.videoElement) {
return '[视频]';
}
if (element.pttElement) {
return `[语音 ${element.pttElement.duration}s]`;
}
if (element.arkElement) {
return '[卡片消息]';
}
if (element.faceElement) {
return `[表情 ${element.faceElement.faceText ?? ''}]`;
}
if (element.marketFaceElement) {
return element.marketFaceElement.faceName;
}
if (element.markdownElement) {
return '[Markdown 消息]';
}
if (element.multiForwardMsgElement) {
return '[转发消息]';
}
if (element.elementType === ElementType.GreyTip) {
return '[灰条消息]';
}
return `[未实现 (ElementType = ${element.elementType})]`;
}
for (const element of msg.elements) {
tokens.push(msgElementToText(element));
tokens.push(msgElementToText(element, msg, recursiveLevel));
}
return tokens.join(' ');
}
function msgElementToText(element: MessageElement, msg: RawMessage, recursiveLevel: number): string {
if (element.textElement) {
return textElementToText(element.textElement);
}
if (element.replyElement) {
return replyElementToText(element.replyElement, msg, recursiveLevel);
}
if (element.picElement) {
return '[图片]';
}
if (element.fileElement) {
return `[文件 ${element.fileElement.fileName}]`;
}
if (element.videoElement) {
return '[视频]';
}
if (element.pttElement) {
return `[语音 ${element.pttElement.duration}s]`;
}
if (element.arkElement) {
return '[卡片消息]';
}
if (element.faceElement) {
return `[表情 ${element.faceElement.faceText ?? ''}]`;
}
if (element.marketFaceElement) {
return element.marketFaceElement.faceName;
}
if (element.markdownElement) {
return '[Markdown 消息]';
}
if (element.multiForwardMsgElement) {
return '[转发消息]';
}
if (element.elementType === ElementType.GreyTip) {
return '[灰条消息]';
}
return `[未实现 (ElementType = ${element.elementType})]`;
}
function textElementToText(textElement: any): string {
if (textElement.atType === AtType.notAt) {
const originalContentLines = textElement.content.split('\n');
return `${originalContentLines[0]}${originalContentLines.length > 1 ? ' ...' : ''}`;
} else if (textElement.atType === AtType.atAll) {
return `@全体成员`;
} else if (textElement.atType === AtType.atUser) {
return `${textElement.content} (${textElement.atUid})`;
}
return '';
}
function replyElementToText(replyElement: any, msg: RawMessage, recursiveLevel: number): string {
const recordMsgOrNull = msg.records.find(
record => replyElement.sourceMsgIdInRecords === record.msgId,
);
return `[回复消息 ${recordMsgOrNull &&
recordMsgOrNull.peerUin != '284840486' && recordMsgOrNull.peerUin != '1094950020'
?
rawMessageToText(recordMsgOrNull, recursiveLevel + 1) :
`未找到消息记录 (MsgId = ${replyElement.sourceMsgIdInRecords})`
}]`;
}

View File

@@ -1,6 +1,6 @@
export class LRUCache<K, V> {
private capacity: number;
private cache: Map<K, V>;
public cache: Map<K, V>;
constructor(capacity: number) {
this.capacity = capacity;
@@ -24,8 +24,19 @@ export class LRUCache<K, V> {
} else if (this.cache.size >= this.capacity) {
// If the cache is full, remove the least recently used key (the first one in the map)
const firstKey = this.cache.keys().next().value;
this.cache.delete(firstKey);
if (firstKey !== undefined) {
this.cache.delete(firstKey);
}
}
this.cache.set(key, value);
}
public resetCapacity(newCapacity: number): void {
this.capacity = newCapacity;
while (this.cache.size > this.capacity) {
const firstKey = this.cache.keys().next().value;
if (firstKey !== undefined) {
this.cache.delete(firstKey);
}
}
}
}

View File

@@ -2,8 +2,8 @@ import { Peer } from '@/core';
import crypto from 'crypto';
export class LimitedHashTable<K, V> {
private keyToValue: Map<K, V> = new Map();
private valueToKey: Map<V, K> = new Map();
private readonly keyToValue: Map<K, V> = new Map();
private readonly valueToKey: Map<V, K> = new Map();
private maxSize: number;
constructor(maxSize: number) {
@@ -23,8 +23,10 @@ export class LimitedHashTable<K, V> {
}
while (this.keyToValue.size > this.maxSize || this.valueToKey.size > this.maxSize) {
const oldestKey = this.keyToValue.keys().next().value;
this.valueToKey.delete(this.keyToValue.get(oldestKey)!);
this.keyToValue.delete(oldestKey);
if (oldestKey !== undefined) {
this.valueToKey.delete(this.keyToValue.get(oldestKey) as V);
this.keyToValue.delete(oldestKey);
}
}
}
@@ -73,8 +75,8 @@ export class LimitedHashTable<K, V> {
}
class MessageUniqueWrapper {
private msgDataMap: LimitedHashTable<string, number>;
private msgIdMap: LimitedHashTable<string, number>;
private readonly msgDataMap: LimitedHashTable<string, number>;
private readonly msgIdMap: LimitedHashTable<string, number>;
constructor(maxMap: number = 1000) {
this.msgIdMap = new LimitedHashTable<string, number>(maxMap);

View File

@@ -1,8 +1,9 @@
import fs from 'node:fs';
import { systemPlatform } from '@/common/system';
import { getDefaultQQVersionConfigInfo, getQQPackageInfoPath, getQQVersionConfigPath } from './helper';
import { getDefaultQQVersionConfigInfo, getQQPackageInfoPath, getQQVersionConfigPath, parseAppidFromMajor } from './helper';
import AppidTable from '@/core/external/appid.json';
import { LogWrapper } from './log';
import { getMajorPath } from '@/core';
export class QQBasicInfoWrapper {
QQMainPath: string | undefined;
@@ -53,29 +54,26 @@ 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 '537246140';
case 'darwin':
return '537246140';
default:
return '537246092';
}
getAppIdFallback() {
const platformMapping: Partial<Record<NodeJS.Platform, string>> = {
win32: '537246092',
darwin: '537246140',
linux: '537246140',
};
return platformMapping[systemPlatform] ?? '537246092';
}
getAppidV2(): { appid: string; qua: string } {
// 通过已有表 性能好
const appidTbale = AppidTable as unknown as QQAppidTableType;
const fullVersion = this.getFullQQVesion();
if (fullVersion) {
@@ -84,10 +82,25 @@ export class QQBasicInfoWrapper {
return data;
}
}
// else
// 通过Major拉取 性能差
try {
const majorAppid = this.getAppidV2ByMajor(fullVersion);
if (majorAppid) {
this.context.logger.log(`[QQ版本兼容性检测] 当前版本Appid未内置 通过Major获取 为了更好的性能请尝试更新NapCat`);
return { appid: majorAppid, qua: this.getQUAFallback() };
}
} catch (error) {
this.context.logger.log(`[QQ版本兼容性检测] 通过Major 获取Appid异常 请检测NapCat/QQNT是否正常`);
}
// 最终兜底为老版本
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() };
}
getAppidV2ByMajor(QQVersion: string) {
const majorPath = getMajorPath(QQVersion);
const appid = parseAppidFromMajor(majorPath);
return appid;
}
}

View File

@@ -8,49 +8,48 @@ export class RequestUtil {
const client = url.startsWith('https') ? https : http;
return new Promise((resolve, reject) => {
const req = client.get(url, (res) => {
let cookies: { [key: string]: string } = {};
const handleRedirect = (res: http.IncomingMessage) => {
//console.log(res.headers.location);
if (res.statusCode === 301 || res.statusCode === 302) {
if (res.headers.location) {
const redirectUrl = new URL(res.headers.location, url);
RequestUtil.HttpsGetCookies(redirectUrl.href).then((redirectCookies) => {
// 合并重定向过程中的cookies
cookies = { ...cookies, ...redirectCookies };
resolve(cookies);
}).catch((err) => {
reject(err);
});
} else {
resolve(cookies);
}
} else {
resolve(cookies);
}
};
res.on('data', () => {
}); // Necessary to consume the stream
const cookies: { [key: string]: string } = {};
res.on('data', () => { }); // Necessary to consume the stream
res.on('end', () => {
handleRedirect(res);
this.handleRedirect(res, url, cookies)
.then(resolve)
.catch(reject);
});
if (res.headers['set-cookie']) {
//console.log(res.headers['set-cookie']);
res.headers['set-cookie'].forEach((cookie) => {
const parts = cookie.split(';')[0].split('=');
const key = parts[0];
const value = parts[1];
if (key && value && key.length > 0 && value.length > 0) {
cookies[key] = value;
}
});
this.extractCookies(res.headers['set-cookie'], cookies);
}
});
req.on('error', (error: any) => {
req.on('error', (error: Error) => {
reject(error);
});
});
}
private static async handleRedirect(res: http.IncomingMessage, url: string, cookies: { [key: string]: string }): Promise<{ [key: string]: string }> {
if (res.statusCode === 301 || res.statusCode === 302) {
if (res.headers.location) {
const redirectUrl = new URL(res.headers.location, url);
const redirectCookies = await this.HttpsGetCookies(redirectUrl.href);
// 合并重定向过程中的cookies
return { ...cookies, ...redirectCookies };
}
}
return cookies;
}
private static extractCookies(setCookieHeaders: string[], cookies: { [key: string]: string }) {
setCookieHeaders.forEach((cookie) => {
const parts = cookie.split(';')[0].split('=');
const key = parts[0];
const value = parts[1];
if (key && value && key.length > 0 && value.length > 0) {
cookies[key] = value;
}
});
}
// 请求和回复都是JSON data传原始内容 自动编码json
static async HttpGetJson<T>(url: string, method: string = 'GET', data?: any, headers: {
@@ -61,7 +60,7 @@ export class RequestUtil {
const options = {
hostname: option.hostname,
port: option.port,
path: option.href,
path: option.pathname + option.search,
method: method,
headers: headers,
};
@@ -88,13 +87,13 @@ export class RequestUtil {
} else {
reject(new Error(`Unexpected status code: ${res.statusCode}`));
}
} catch (parseError) {
reject(parseError);
} catch (parseError: unknown) {
reject(new Error((parseError as Error).message));
}
});
});
req.on('error', (error: any) => {
req.on('error', (error: Error) => {
reject(error);
});
if (method === 'POST' || method === 'PUT' || method === 'PATCH') {
@@ -133,62 +132,4 @@ export class RequestUtil {
Buffer.from(footer, 'utf8'),
]);
}
static async uploadImageForOpenPlatform(filePath: string, cookies: string): Promise<string> {
return new Promise(async (resolve, reject) => {
type retType = { retcode: number, result?: { url: string } };
try {
const options = {
hostname: 'cgi.connect.qq.com',
port: 443,
path: '/qqconnectopen/upload_share_image',
method: 'POST',
headers: {
'Referer': 'https://cgi.connect.qq.com',
'Cookie': cookies,
'Accept': '*/*',
'Connection': 'keep-alive',
'Content-Type': 'multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW',
},
};
const req = https.request(options, async (res) => {
let responseBody = '';
res.on('data', (chunk: string | Buffer) => {
responseBody += chunk.toString();
});
res.on('end', () => {
try {
if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
const responseJson = JSON.parse(responseBody) as retType;
resolve(responseJson.result!.url!);
} else {
reject(new Error(`Unexpected status code: ${res.statusCode}`));
}
} catch (parseError) {
reject(parseError);
}
});
});
req.on('error', (error) => {
reject(error);
console.log('Error during upload:', error);
});
const body = await RequestUtil.createFormData('WebKitFormBoundary7MA4YWxkTrZu0gW', filePath);
// req.setHeader('Content-Length', Buffer.byteLength(body));
// console.log(`Prepared data size: ${Buffer.byteLength(body)} bytes`);
req.write(body);
req.end();
return;
} catch (error) {
reject(error);
}
return undefined;
});
}
}

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.6.2';
export const napCatVersion = '3.7.0';

View File

@@ -20,7 +20,7 @@ export async function getVideoInfo(filePath: string, logger: LogWrapper) {
ffmpeg.setFfmpegPath(ffmpegPath);
ffmpeg(filePath).ffprobe((err: any, metadata: ffmpeg.FfprobeData) => {
if (err) {
reject(err);
reject(new Error('无法获取视频信息。'));
} else {
const videoStream = metadata.streams.find((s: FfprobeStream) => s.codec_type === 'video');
if (videoStream) {

View File

@@ -6,8 +6,10 @@ export class NodeIDependsAdapter {
}
onMSFSsoError(args: unknown) {
}
getGroupCode(args: unknown) {
}
}

View File

@@ -6,6 +6,7 @@ import {
Peer,
PicElement,
PicType,
RawMessage,
SendFileElement,
SendPicElement,
SendPttElement,
@@ -30,6 +31,7 @@ export class NTQQFileApi {
context: InstanceContext;
core: NapCatCore;
rkeyManager: RkeyManager;
packetRkey: Array<{ rkey: string; time: number; type: number; ttl: bigint }> | undefined;
constructor(context: InstanceContext, core: NapCatCore) {
this.context = context;
@@ -145,7 +147,7 @@ export class NTQQFileApi {
try {
videoInfo = await getVideoInfo(filePath, logger);
} catch (e) {
logger.logError('获取视频信息失败,将使用默认值', e);
logger.logError.bind(logger)('获取视频信息失败,将使用默认值', e);
}
let fileExt = 'mp4';
@@ -153,7 +155,7 @@ export class NTQQFileApi {
const tempExt = (await fileType.fileTypeFromFile(filePath))?.ext;
if (tempExt) fileExt = tempExt;
} catch (e) {
this.context.logger.logError('获取文件类型失败', e);
this.context.logger.logError.bind(logger)('获取文件类型失败', e);
}
const newFilePath = filePath + '.' + fileExt;
fs.copyFileSync(filePath, newFilePath);
@@ -173,14 +175,18 @@ export class NTQQFileApi {
const thumbPath = pathLib.join(thumb, thumbFileName);
ffmpeg(filePath)
.on('error', (err) => {
logger.logDebug('获取视频封面失败,使用默认封面', err);
if (diyThumbPath) {
fsPromises.copyFile(diyThumbPath, thumbPath).then(() => {
try {
logger.logDebug('获取视频封面失败,使用默认封面', err);
if (diyThumbPath) {
fsPromises.copyFile(diyThumbPath, thumbPath).then(() => {
resolve(thumbPath);
}).catch(reject);
} else {
fs.writeFileSync(thumbPath, Buffer.from(defaultVideoThumbB64, 'base64'));
resolve(thumbPath);
}).catch(reject);
} else {
fs.writeFileSync(thumbPath, Buffer.from(defaultVideoThumbB64, 'base64'));
resolve(thumbPath);
}
} catch (error) {
logger.logError.bind(logger)('获取视频封面失败,使用默认封面失败', error);
}
})
.screenshots({
@@ -227,7 +233,7 @@ export class NTQQFileApi {
}
if (converted) {
fsPromises.unlink(silkPath).then().catch(
(e) => this.context.logger.logError('删除临时文件失败', e)
(e) => this.context.logger.logError.bind(this.context.logger)('删除临时文件失败', e)
);
}
return {
@@ -237,7 +243,7 @@ export class NTQQFileApi {
fileName: fileName,
filePath: path,
md5HexStr: md5,
fileSize: fileSize,
fileSize: fileSize.toString(),
duration: duration ?? 1,
formatType: 1,
voiceType: 1,
@@ -266,6 +272,53 @@ export class NTQQFileApi {
return fileTransNotifyInfo.filePath;
}
async downloadRawMsgMedia(msg: RawMessage[]) {
const res = await Promise.all(
msg.map(m =>
Promise.all(
m.elements
.filter(element =>
element.elementType === ElementType.PIC ||
element.elementType === ElementType.VIDEO ||
element.elementType === ElementType.PTT ||
element.elementType === ElementType.FILE
)
.map(element =>
this.downloadMedia(m.msgId, m.chatType, m.peerUid, element.elementId, '', '', 1000 * 60 * 2, true)
)
)
)
);
msg.forEach((m, msgIndex) => {
const elementResults = res[msgIndex];
let elementIndex = 0;
m.elements.forEach(element => {
if (
element.elementType === ElementType.PIC ||
element.elementType === ElementType.VIDEO ||
element.elementType === ElementType.PTT ||
element.elementType === ElementType.FILE
) {
switch (element.elementType) {
case ElementType.PIC:
element.picElement!.sourcePath = elementResults[elementIndex];
break;
case ElementType.VIDEO:
element.videoElement!.filePath = elementResults[elementIndex];
break;
case ElementType.PTT:
element.pttElement!.filePath = elementResults[elementIndex];
break;
case ElementType.FILE:
element.fileElement!.filePath = elementResults[elementIndex];
break;
}
elementIndex++;
}
});
});
}
async downloadMedia(msgId: string, chatType: ChatType, peerUid: string, elementId: string, thumbPath: string, sourcePath: string, timeout = 1000 * 60 * 2, force: boolean = false) {
// 用于下载收到的消息中的图片等
if (sourcePath && fs.existsSync(sourcePath)) {
@@ -295,7 +348,7 @@ export class NTQQFileApi {
filePath: thumbPath,
}],
() => true,
(arg) => arg.msgId === msgId,
(arg) => arg.msgElementId === elementId && arg.msgId === msgId,
1,
timeout,
);
@@ -304,15 +357,13 @@ export class NTQQFileApi {
async getImageSize(filePath: string): Promise<ISizeCalculationResult> {
return new Promise((resolve, reject) => {
imageSize(filePath, (err, dimensions) => {
imageSize(filePath, (err: Error | null, dimensions) => {
if (err) {
reject(err);
reject(new Error(err.message));
} else if (!dimensions) {
reject(new Error('获取图片尺寸失败'));
} else {
if (!dimensions) {
reject(new Error('获取图片尺寸失败'));
} else {
resolve(dimensions);
}
resolve(dimensions);
}
});
});
@@ -355,33 +406,87 @@ export class NTQQFileApi {
return fileData.filePath!;
}
async getImageUrl(element: PicElement) {
async getImageUrl(element: PicElement): Promise<string> {
if (!element) {
return '';
}
const url: string = element.originImageUrl ?? '';
const md5HexStr = element.md5HexStr;
const fileMd5 = element.md5HexStr;
if (url) {
const parsedUrl = new URL(IMAGE_HTTP_HOST + url);
const imageAppid = parsedUrl.searchParams.get('appid');
const isNTV2 = imageAppid && ['1406', '1407'].includes(imageAppid);
if (isNTV2) {
let rkey = parsedUrl.searchParams.get('rkey');
if (rkey) {
return IMAGE_HTTP_HOST_NT + url;
const rkeyData = await this.getRkeyData();
return this.getImageUrlFromParsedUrl(parsedUrl, rkeyData);
}
return this.getImageUrlFromMd5(fileMd5, md5HexStr);
}
private async getRkeyData() {
const rkeyData = {
private_rkey: 'CAQSKAB6JWENi5LM_xp9vumLbuThJSaYf-yzMrbZsuq7Uz2qEc3Rbib9LP4',
group_rkey: 'CAQSKAB6JWENi5LM_xp9vumLbuThJSaYf-yzMrbZsuq7Uz2qffcqm614gds',
online_rkey: false
};
try {
if (this.core.apis.PacketApi.available) {
const rkey_expired_private = !this.packetRkey || this.packetRkey[0].time + Number(this.packetRkey[0].ttl) < Date.now() / 1000;
const rkey_expired_group = !this.packetRkey || this.packetRkey[0].time + Number(this.packetRkey[0].ttl) < Date.now() / 1000;
if (rkey_expired_private || rkey_expired_group) {
this.packetRkey = await this.core.apis.PacketApi.pkt.operation.FetchRkey();
}
if (this.packetRkey && this.packetRkey.length > 0) {
rkeyData.group_rkey = this.packetRkey[1].rkey.slice(6);
rkeyData.private_rkey = this.packetRkey[0].rkey.slice(6);
rkeyData.online_rkey = true;
}
const rkeyData = await this.rkeyManager.getRkey();
rkey = imageAppid === '1406' ? rkeyData.private_rkey : rkeyData.group_rkey;
return IMAGE_HTTP_HOST_NT + url + `${rkey}`;
} else {
return IMAGE_HTTP_HOST + url;
}
} else if (fileMd5 || md5HexStr) {
} catch (error: any) {
this.context.logger.logError.bind(this.context.logger)('获取rkey失败', error.message);
}
if (!rkeyData.online_rkey) {
try {
const 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);
}
}
return rkeyData;
}
private getImageUrlFromParsedUrl(parsedUrl: URL, rkeyData: any): string {
const urlRkey = parsedUrl.searchParams.get('rkey');
const imageAppid = parsedUrl.searchParams.get('appid');
const isNTV2 = imageAppid && ['1406', '1407'].includes(imageAppid);
const imageFileId = parsedUrl.searchParams.get('fileid');
if (isNTV2 && urlRkey) {
return IMAGE_HTTP_HOST_NT + urlRkey;
} else if (isNTV2 && rkeyData.online_rkey) {
const rkey = imageAppid === '1406' ? rkeyData.private_rkey : rkeyData.group_rkey;
return IMAGE_HTTP_HOST_NT + parsedUrl.pathname + `&rkey=${rkey}`;
} else if (isNTV2 && imageFileId) {
const rkey = imageAppid === '1406' ? rkeyData.private_rkey : rkeyData.group_rkey;
return IMAGE_HTTP_HOST + `/download?appid=${imageAppid}&fileid=${imageFileId}&rkey=${rkey}`;
}
return '';
}
private getImageUrlFromMd5(fileMd5: string | undefined, md5HexStr: string | undefined): string {
if (fileMd5 || md5HexStr) {
return `${IMAGE_HTTP_HOST}/gchatpic_new/0/0-0-${(fileMd5 ?? md5HexStr)!.toUpperCase()}/0`;
}
this.context.logger.logDebug('图片url获取失败', element);
this.context.logger.logDebug('图片url获取失败', { fileMd5, md5HexStr });
return '';
}
}

View File

@@ -10,10 +10,12 @@ export class NTQQFriendApi {
this.context = context;
this.core = core;
}
async setBuddyRemark(uid: string, remark: string) {
return this.context.session.getBuddyService().setBuddyRemark({ uid, remark });
}
async getBuddyV2SimpleInfoMap(refresh = false) {
const buddyService = this.context.session.getBuddyService();
const buddyListV2 = refresh ? await buddyService.getBuddyListV2('0', BuddyListReqType.KNOMAL) : await buddyService.getBuddyListV2('0', BuddyListReqType.KNOMAL);
const buddyListV2 = await buddyService.getBuddyListV2('0', BuddyListReqType.KNOMAL);
const uids = buddyListV2.data.flatMap(item => item.buddyUids);
return await this.core.eventWrapper.callNoListenerEvent(
'NodeIKernelProfileService/getCoreAndBaseInfo',
@@ -32,15 +34,17 @@ export class NTQQFriendApi {
data.forEach((value) => retMap.set(value.uin!, value.uid!));
return retMap;
}
async getBuddyV2ExWithCate(refresh = false) {
const categoryMap: Map<string, any> = new Map();
async delBuudy(uid: string, tempBlock = false, tempBothDel = false) {
return this.context.session.getBuddyService().delBuddy({
friendUid: uid,
tempBlock: tempBlock,
tempBothDel: tempBothDel
});
}
async getBuddyV2ExWithCate() {
const buddyService = this.context.session.getBuddyService();
const buddyListV2 = refresh ? (await buddyService.getBuddyListV2('0', BuddyListReqType.KNOMAL)).data : (await buddyService.getBuddyListV2('0', BuddyListReqType.KNOMAL)).data;
const buddyListV2 = (await buddyService.getBuddyListV2('0', BuddyListReqType.KNOMAL)).data;
const uids = buddyListV2.flatMap(item => {
item.buddyUids.forEach(uid => {
categoryMap.set(uid, { categoryId: item.categoryId, categoryName: item.categroyName });
});
return item.buddyUids;
});
const data = await this.core.eventWrapper.callNoListenerEvent(

View File

@@ -20,20 +20,24 @@ export class NTQQGroupApi {
groupMemberCache: Map<string, Map<string, GroupMember>> = new Map<string, Map<string, GroupMember>>();
groups: Group[] = [];
essenceLRU = new LimitedHashTable<number, string>(1000);
session: any;
constructor(context: InstanceContext, core: NapCatCore) {
this.context = context;
this.core = core;
this.initCache().then().catch(context.logger.logError.bind(context.logger));
}
async initApi() {
this.initCache().then().catch(this.context.logger.logError.bind(this.context.logger));
}
async initCache() {
this.groups = await this.getGroups();
for (const group of this.groups) {
this.groupCache.set(group.groupCode, group);
}
this.context.logger.logDebug(`加载${this.groups.length}个群组缓存完成`);
// process.pid 调试点
}
async getCoreAndBaseInfo(uids: string[]) {
return await this.core.eventWrapper.callNoListenerEvent(
'NodeIKernelProfileService/getCoreAndBaseInfo',
@@ -41,6 +45,7 @@ export class NTQQGroupApi {
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({
@@ -49,7 +54,11 @@ export class NTQQGroupApi {
pageLimit: 300,
}, pskey);
}
async getGroupShutUpMemberList(groupCode: string) {
const data = this.core.eventWrapper.registerListen('NodeIKernelGroupListener/onShutUpMemberListChanged', (group_id) => group_id === groupCode, 1, 1000);
this.context.session.getGroupService().getGroupShutUpMemberList(groupCode);
return (await data)[1];
}
async clearGroupNotifiesUnreadCount(uk: boolean) {
return this.context.session.getGroupService().clearGroupNotifiesUnreadCount(uk);
}
@@ -137,7 +146,7 @@ export class NTQQGroupApi {
let members = this.groupMemberCache.get(groupCodeStr);
if (!members) {
try {
members = await this.getGroupMembersV2(groupCodeStr);
members = await this.getGroupMembers(groupCodeStr);
// 更新群成员列表
this.groupMemberCache.set(groupCodeStr, members);
} catch (e) {
@@ -158,11 +167,12 @@ export class NTQQGroupApi {
let member = getMember();
if (!member) {
members = await this.getGroupMembersV2(groupCodeStr);
members = await this.getGroupMembers(groupCodeStr);
member = getMember();
}
return member;
}
async getGroupRecommendContactArkJson(groupCode: string) {
return this.context.session.getGroupService().getGroupRecommendContactArkJson(groupCode);
}
@@ -249,9 +259,9 @@ export class NTQQGroupApi {
async getGroupMemberV2(GroupCode: string, uid: string, forced = false) {
const Listener = this.core.eventWrapper.registerListen(
'NodeIKernelGroupListener/onMemberInfoChange',
(params, _, members) => params === GroupCode && members.size > 0,
1,
forced ? 5000 : 250,
(params, _, members) => params === GroupCode && members.size > 0,
);
const retData = await (
this.core.eventWrapper
@@ -268,6 +278,7 @@ export class NTQQGroupApi {
}
return member;
}
async searchGroup(groupCode: string) {
const [, ret] = await this.core.eventWrapper.callNormalEventV2(
'NodeIKernelSearchService/searchGroup',
@@ -285,6 +296,7 @@ export class NTQQGroupApi {
);
return ret.groupInfos.find(g => g.groupCode === groupCode);
}
async getGroupMemberEx(GroupCode: string, uid: string, forced = false, retry = 2) {
const data = await solveAsyncProblem((eventWrapper: NTEventWrapper, GroupCode: string, uid: string, forced = false) => {
return eventWrapper.callNormalEventV2(
@@ -306,42 +318,84 @@ export class NTQQGroupApi {
}
return undefined;
}
async getGroupMembersV2(groupQQ: string, num = 3000): Promise<Map<string, GroupMember>> {
const groupService = this.context.session.getGroupService();
const sceneId = groupService.createMemberListScene(groupQQ, 'groupMemberList_MainWindow');
const listener = this.core.eventWrapper.registerListen(
'NodeIKernelGroupListener/onMemberListChange',
1,
5000,
(params) => params.sceneId === sceneId,
);
try {
const [membersFromFunc, membersFromListener] = await Promise.allSettled([
groupService.getNextMemberList(sceneId, undefined, num),
listener,
]);
if (membersFromFunc.status === 'fulfilled' && membersFromListener.status === 'fulfilled') {
return new Map([
...membersFromFunc.value.result.infos,
...membersFromListener.value[0].infos,
]);
}
if (membersFromFunc.status === 'fulfilled') {
return membersFromFunc.value.result.infos;
}
if (membersFromListener.status === 'fulfilled') {
return membersFromListener.value[0].infos;
}
throw new Error('获取群成员列表失败');
} finally {
groupService.destroyMemberListScene(sceneId);
async tryGetGroupMembersV2(groupQQ: string, modeListener = false, num = 30, timeout = 100): Promise<{
infos: Map<string, GroupMember>;
finish: boolean;
hasNext: boolean | undefined;
}> {
const sceneId = this.context.session.getGroupService().createMemberListScene(groupQQ, 'groupMemberList_MainWindow_1');
const once = this.core.eventWrapper.registerListen('NodeIKernelGroupListener/onMemberListChange', (params) => params.sceneId === sceneId, 0, timeout)
.catch(() => { });
const result = await this.context.session.getGroupService().getNextMemberList(sceneId, undefined, num);
if (result.errCode !== 0) {
throw new Error('获取群成员列表出错,' + result.errMsg);
}
let resMode2;
if (modeListener) {
const ret = (await once)?.[0];
if (ret) {
resMode2 = ret;
}
}
this.context.session.getGroupService().destroyMemberListScene(sceneId);
return {
infos: new Map([...(resMode2?.infos ?? []), ...result.result.infos]),
finish: result.result.finish,
hasNext: resMode2?.hasNext,
};
}
async GetGroupMembersV3(groupQQ: string, num = 3000, timeout = 2500): Promise<{
infos: Map<string, GroupMember>;
finish: boolean;
hasNext: boolean | undefined;
listenerMode: boolean;
}> {
const sceneId = this.context.session.getGroupService().createMemberListScene(groupQQ, 'groupMemberList_MainWindow_1');
const once = this.core.eventWrapper.registerListen('NodeIKernelGroupListener/onMemberListChange', (params) => params.sceneId === sceneId, 0, timeout)
.catch(() => { });
const result = await this.context.session.getGroupService().getNextMemberList(sceneId, undefined, num);
if (result.errCode !== 0) {
throw new Error('获取群成员列表出错,' + result.errMsg);
}
let resMode2;
if (result.result.finish && result.result.infos.size === 0) {
const ret = (await once)?.[0];
if (ret) {
resMode2 = ret;
}
}
this.context.session.getGroupService().destroyMemberListScene(sceneId);
//console.log('GetGroupMembersV3 len :', result.result.infos.size, resMode2?.infos.size, groupQQ);
return {
infos: new Map([...(resMode2?.infos ?? []), ...result.result.infos]),
finish: result.result.finish,
hasNext: resMode2?.hasNext,
listenerMode: resMode2?.hasNext !== undefined
};
}
async getGroupMembersV2(groupQQ: string, num = 3000, no_cache: boolean = false): Promise<Map<string, GroupMember>> {
if (no_cache) {
return (await this.getGroupMemberAll(groupQQ, true)).result.infos;
}
let res = await this.GetGroupMembersV3(groupQQ, num);
let ret = res.infos;
if (res.infos.size === 0 && !res.listenerMode) {
res = await this.GetGroupMembersV3(groupQQ, num);
ret = res.infos;
}
if (res.infos.size === 0) {
ret = (await this.getGroupMemberAll(groupQQ)).result.infos;
}
return ret;
}
async getGroupMembers(groupQQ: string, num = 3000): Promise<Map<string, GroupMember>> {
const groupService = this.context.session.getGroupService();
const sceneId = groupService.createMemberListScene(groupQQ, 'groupMemberList_MainWindow');
const result = await groupService.getNextMemberList(sceneId!, undefined, num);
const result = await groupService.getNextMemberList(sceneId, undefined, num);
if (result.errCode !== 0) {
throw new Error('获取群成员列表出错,' + result.errMsg);
}
@@ -349,8 +403,8 @@ export class NTQQGroupApi {
return result.result.infos;
}
async getGroupFileCount(Gids: Array<string>) {
return this.context.session.getRichMediaService().batchGetGroupFileCount(Gids);
async getGroupFileCount(group_ids: Array<string>) {
return this.context.session.getRichMediaService().batchGetGroupFileCount(group_ids);
}
async getArkJsonGroupShare(GroupCode: string) {
@@ -433,7 +487,7 @@ export class NTQQGroupApi {
}
async getGroupRemainAtTimes(GroupCode: string) {
this.context.session.getGroupService().getGroupRemainAtTimes(GroupCode);
return this.context.session.getGroupService().getGroupRemainAtTimes(GroupCode);
}
async getMemberExtInfo(groupCode: string, uin: string) {

View File

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

View File

@@ -3,6 +3,9 @@ import { InstanceContext, NapCatCore } from '@/core';
import { GeneralCallResult } from '@/core/services/common';
export class NTQQMsgApi {
getMsgByClientSeqAndTime(peer: Peer, replyMsgClientSeq: string, replyMsgTime: string) {
return this.context.session.getMsgService().getMsgByClientSeqAndTime(peer, replyMsgClientSeq, replyMsgTime);
}
// nt_qq//global//nt_data//Emoji//emoji-resource//sysface_res/apng/ 下可以看到所有QQ表情预览
// nt_qq\global\nt_data\Emoji\emoji-resource\face_config.json 里面有所有表情的id, 自带表情id是QSid, 标准emoji表情id是QCid
// 其实以官方文档为准是最好的https://bot.q.qq.com/wiki/develop/api-v2/openapi/emoji/model.html#EmojiType
@@ -22,7 +25,9 @@ export class NTQQMsgApi {
async sendShowInputStatusReq(peer: Peer, eventType: number) {
return this.context.session.getMsgService().sendShowInputStatusReq(peer.chatType, eventType, peer.peerUid);
}
async getSourceOfReplyMsgV2(peer: Peer, clientSeq: string, time: string) {
return this.context.session.getMsgService().getSourceOfReplyMsgV2(peer, clientSeq, time);
}
async getMsgEmojiLikesList(peer: Peer, msgSeq: string, emojiId: string, emojiType: string, count: number = 20) {
//注意此处emojiType 可选值一般为1-2 2好像是unicode表情dec值 大部分情况 Taged Mlikiowa
return this.context.session.getMsgService().getMsgEmojiLikesList(peer, msgSeq, emojiId, emojiType, '', false, count);
@@ -82,6 +87,18 @@ export class NTQQMsgApi {
pageLimit: 1,
});
}
async queryMsgsWithFilterExWithSeqV3(peer: Peer, msgSeq: string, SendersUid: string[]) {
return await this.context.session.getMsgService().queryMsgsWithFilterEx('0', '0', msgSeq, {
chatInfo: peer,
filterMsgType: [],
filterSendersUid: SendersUid,
filterMsgToTime: '0',
filterMsgFromTime: '0',
isReverseOrder: false,
isIncludeCurrent: true,
pageLimit: 1,
});
}
async queryFirstMsgBySeq(peer: Peer, msgSeq: string) {
return await this.context.session.getMsgService().queryMsgsWithFilterEx('0', '0', msgSeq, {
chatInfo: peer,
@@ -94,9 +111,9 @@ 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);
// 客户端还在用别慌
async getMsgsBySeqAndCount(peer: Peer, seq: string, count: number, desc: boolean, isReverseOrder: boolean) {
return await this.context.session.getMsgService().getMsgsBySeqAndCount(peer, seq, count, desc, isReverseOrder);
}
async getMsgExBySeq(peer: Peer, msgSeq: string) {
const DateNow = Math.floor(Date.now() / 1000);
@@ -127,7 +144,7 @@ export class NTQQMsgApi {
params,
],
() => true,
() => true, // Todo: 应当通过 groupFileListResult 判断
() => true, // 应当通过 groupFileListResult 判断
1,
5000,
);
@@ -177,7 +194,7 @@ export class NTQQMsgApi {
async sendMsg(peer: Peer, msgElements: SendMessageElement[], waitComplete = true, timeout = 10000) {
//唉?!我有个想法
if (peer.chatType === ChatType.KCHATTYPETEMPC2CFROMGROUP && peer.guildId && peer.guildId !== '') {
const member = await this.core.apis.GroupApi.getGroupMember(peer.guildId, peer.peerUid!);
const member = await this.core.apis.GroupApi.getGroupMember(peer.guildId, peer.peerUid);
if (member) {
await this.PrepareTempChat(peer.peerUid, peer.guildId, member.nick);
}

66
src/core/apis/packet.ts Normal file
View File

@@ -0,0 +1,66 @@
import * as os from 'os';
import offset from '@/core/external/offset.json';
import { InstanceContext, NapCatCore } from "@/core";
import { LogWrapper } from "@/common/log";
import { PacketClientSession } from "@/core/packet/clientSession";
import { napCatVersion } from "@/common/version";
interface OffsetType {
[key: string]: {
recv: string;
send: string;
};
}
const typedOffset: OffsetType = offset;
export class NTQQPacketApi {
context: InstanceContext;
core: NapCatCore;
logger: LogWrapper;
qqVersion: string | undefined;
pkt!: PacketClientSession;
errStack: string[] = [];
constructor(context: InstanceContext, core: NapCatCore) {
this.context = context;
this.core = core;
this.logger = core.context.logger;
}
async initApi() {
await this.InitSendPacket(this.context.basicInfoWrapper.getFullQQVesion())
.then()
.catch((err) => {
this.logger.logError.bind(this.core.context.logger);
this.errStack.push(err);
});
}
get available(): boolean {
return this.pkt?.available ?? false;
}
get clientLogStack() {
return this.pkt?.clientLogStack + '\n' + this.errStack.join('\n');
}
async InitSendPacket(qqVer: string) {
this.qqVersion = qqVer;
const table = typedOffset[qqVer + '-' + os.arch()];
if (!table) {
const err = `[Core] [Packet] PacketBackend 不支持当前QQ版本架构${qqVer}-${os.arch()}
请参照 https://github.com/NapNeko/NapCatQQ/releases/tag/v${napCatVersion} 配置正确的QQ版本`;
this.logger.logError(err);
this.errStack.push(err);
return false;
}
if (this.core.configLoader.configData.packetBackend === 'disable') {
const err = '[Core] [Packet] 已禁用PacketBackendNapCat.Packet将不会加载';
this.logger.logError(err);
this.errStack.push(err);
return false;
}
this.pkt = new PacketClientSession(this.core);
await this.pkt.init(process.pid, table.recv, table.send);
return true;
}
}

View File

@@ -1,5 +1,3 @@
import { RequestUtil } from '@/common/request';
import { MiniAppLuaJsonType } from '@/core';
import { InstanceContext, NapCatCore } from '..';
export class NTQQMusicSignApi {
@@ -10,210 +8,6 @@ export class NTQQMusicSignApi {
this.context = context;
this.core = core;
}
async signMiniApp(CardData: MiniAppLuaJsonType) {
// {
// "app": "com.tencent.miniapp.lua",
// "bizsrc": "tianxuan.imgJumpArk",
// "view": "miniapp",
// "prompt": "hi! 这里有我的日常故事,只想讲给你听",
// "config": {
// "type": "normal",
// "forward": 1,
// "autosize": 0
// },
// "meta": {
// "miniapp": {
// "title": "hi! 这里有我的日常故事,只想讲给你听",
// "preview": "https:\/\/tianquan.gtimg.cn\/qqAIAgent\/item\/7\/square.png",
// "jumpUrl": "https:\/\/club.vip.qq.com\/transfer?open_kuikly_info=%7B%22version%22%3A%20%221%22%2C%22src_type%22%3A%20%22web%22%2C%22kr_turbo_display%22%3A%20%221%22%2C%22page_name%22%3A%20%22vas_ai_persona_moments%22%2C%22bundle_name%22%3A%20%22vas_ai_persona_moments%22%7D&page_name=vas_ai_persona_moments&enteranceId=share&robot_uin=3889008584",
// "tag": "QQ智能体",
// "tagIcon": "https:\/\/tianquan.gtimg.cn\/shoal\/qqAIAgent\/3e9d70c9-d98c-45b8-80b4-79d82971b514.png",
// "source": "QQ智能体",
// "sourcelogo": "https:\/\/tianquan.gtimg.cn\/shoal\/qqAIAgent\/3e9d70c9-d98c-45b8-80b4-79d82971b514.png"
// }
// }
// }
// token : function(url,skey){
// var str = skey || cookie('skey') || cookie('rv2') || '',
// hash = 5381;
// if(url){
// var hostname = uri(url).hostname;
// if(hostname.indexOf('qun.qq.com') > -1 || (hostname.indexOf('qzone.qq.com') > -1 && hostname.indexOf('qun.qzone.qq.com') === -1)){
// str = cookie('p_skey') || str;
// }
// }
// for(var i = 0, len = str.length; i < len; ++i){
// hash += (hash << 5) + str.charAt(i).charCodeAt();
// }
// return hash & 0x7fffffff;
// },
//
// function signToken(skey: string) {
// let hash = 5381;
// for (let i = 0, len = skey.length; i < len; ++i) {
// hash += (hash << 5) + skey.charCodeAt(i);
// }
// return hash & 0x7fffffff;
// }
const signCard = {
'app': 'com.tencent.miniapp.lua',
'bizsrc': 'tianxuan.imgJumpArk',
'view': 'miniapp',
'prompt': CardData.prompt,
'config': {
'type': 'normal',
'forward': 1,
'autosize': 0,
},
'meta': {
'miniapp': {
'title': CardData.title,
'preview': (CardData.preview as string).replace(/\\/g, '\\/\\/'),
'jumpUrl': (CardData.jumpUrl as string).replace(/\\/g, '\\/\\/'),
'tag': CardData.tag,
'tagIcon': (CardData.tagIcon as string).replace(/\\/g, '\\/\\/'),
'source': CardData.source,
'sourcelogo': (CardData.sourcelogo as string).replace(/\\/g, '\\/\\/'),
},
},
};
// let signCard = {
// "app": "com.tencent.eventshare.lua",
// "prompt": "Bot Test",
// "bizsrc": "tianxuan.business",
// "meta": {
// "eventshare": {
// "button1URL": "https://www.bilibili.com",
// "button1disable": false,
// "button1title": "点我前往",
// "button2URL": "",
// "button2disable": false,
// "button2title": "",
// "buttonNum": 1,
// "jumpURL": "https://www.bilibili.com",
// "preview": "https://tianquan.gtimg.cn/shoal/card/9930bc4e-4a92-4da3-814f-8094a2421d9c.png",
// "tag": "QQ集卡",
// "tagIcon": "https://tianquan.gtimg.cn/shoal/card/c034854b-102d-40be-a545-5ca90a7c49c9.png",
// "title": "Bot Test"
// }
// },
// "config": {
// "autosize": 0,
// "collect": 0,
// "ctime": 1716568575,
// "forward": 1,
// "height": 336,
// "reply": 0,
// "round": 1,
// "type": "normal",
// "width": 263
// },
// "view": "eventshare",
// "ver": "0.0.0.1"
// };
const data = (await this.core.apis.UserApi.getQzoneCookies());
const Bkn = this.core.apis.WebApi.getBknFromCookie(data.p_skey);
const CookieValue = 'p_skey=' + data.p_skey + '; skey=' + data.skey + '; p_uin=o' + this.core.selfInfo.uin + '; uin=o' + this.core.selfInfo.uin;
const signurl = 'https://h5.qzone.qq.com/v2/vip/tx/trpc/ark-share/GenNewSignedArk?g_tk=' + Bkn + '&ark=' + encodeURIComponent(JSON.stringify(signCard));
let signed_ark = '';
try {
const retData = await RequestUtil.HttpGetJson<{
code: number,
data: { signed_ark: string }
}>(signurl, 'GET', undefined, { Cookie: CookieValue });
//logDebug('MiniApp JSON 消息生成成功', retData);
signed_ark = retData.data.signed_ark;
} catch (error) {
this.context.logger.logDebug('MiniApp JSON 消息生成失败', error);
}
return signed_ark;
}
async signInternal(songname: string, singer: string, cover: string, songmid: string, songmusic: string) {
//curl -X POST 'https://mqq.reader.qq.com/api/mqq/share/card?accessToken&_csrfToken&source=c0003' -H 'Content-Type: application/json' -H 'Cookie: uin=o10086' -d '{"app":"com.tencent.qqreader.share","config":{"ctime":1718634110,"forward":1,"token":"9a63343c32d5a16bcde653eb97faa25d","type":"normal"},"extra":{"app_type":1,"appid":100497308,"msg_seq":14386738075403815000.0,"uin":1733139081},"meta":{"music":{"action":"","android_pkg_name":"","app_type":1,"appid":100497308,"ctime":1718634110,"desc":"周杰伦","jumpUrl":"https://i.y.qq.com/v8/playsong.html?songmid=0039MnYb0qxYhV&type=0","musicUrl":"http://ws.stream.qqmusic.qq.com/http://isure6.stream.qqmusic.qq.com/M800002202B43Cq4V4.mp3?fromtag=810033622&guid=br_xzg&trace=23fe7bcbe2336bbf&uin=553&vkey=CF0F5CE8B0FA16F3001F8A88D877A217EB5E4F00BDCEF1021EB6C48969CA33C6303987AEECE9CC840122DD2F917A59D6130D8A8CA4577C87","preview":"https://y.qq.com/music/photo_new/T002R800x800M000000MkMni19ClKG.jpg","cover":"https://y.qq.com/music/photo_new/T002R800x800M000000MkMni19ClKG.jpg","sourceMsgId":"0","source_icon":"https://p.qpic.cn/qqconnect/0/app_100497308_1626060999/100?max-age=2592000&t=0","source_url":"","tag":"QQ音乐","title":"晴天","uin":10086}},"prompt":"[分享]晴天","ver":"0.0.0.1","view":"music"}'
const signurl = 'https://mqq.reader.qq.com/api/mqq/share/card?accessToken&_csrfToken&source=c0003';
//let = "https://y.qq.com/music/photo_new/T002R800x800M000000MkMni19ClKG.jpg";
const signCard = {
app: 'com.tencent.qqreader.share',
config: {
ctime: 1718634110,
forward: 1,
token: '9a63343c32d5a16bcde653eb97faa25d',
type: 'normal',
},
extra: {
app_type: 1,
appid: 100497308,
msg_seq: 14386738075403815000,
uin: 1733139081,
},
meta: {
music: {
action: '',
android_pkg_name: '',
app_type: 1,
appid: 100497308,
ctime: 1718634110,
desc: singer,
jumpUrl: 'https://i.y.qq.com/v8/playsong.html?songmid=' + songmid + '&type=0',
musicUrl: songmusic,
preview: cover,
cover: cover,
sourceMsgId: '0',
source_icon: 'https://p.qpic.cn/qqconnect/0/app_100497308_1626060999/100?max-age=2592000&t=0',
source_url: '',
tag: 'QQ音乐',
title: songname,
uin: 10086,
},
},
prompt: '[分享]' + songname,
ver: '0.0.0.1',
view: 'music',
};
//console.log(JSON.stringify(signCard, null, 2));
const data = await RequestUtil.HttpGetJson<{ code: number, data: { arkResult: string } }>
(signurl, 'POST', signCard, { 'Cookie': 'uin=o10086', 'Content-Type': 'application/json' });
return data;
}
//注意处理错误
async signWay03(id: string = '', mid: string = '') {
let signedMid;
if (mid == '') {
const MusicInfo = await RequestUtil.HttpGetJson<{
songinfo?: {
data?: {
track_info: {
mid: string
}
}
}
}>(
'https://u.y.qq.com/cgi-bin/musicu.fcg?format=json&inCharset=utf8&outCharset=utf-8&notice=0&platform=yqq.json&needNewCode=0&data={"comm":{"ct":24,"cv":0},"songinfo":{"method":"get_song_detail_yqq","param":{"song_type":0,"song_mid":"","song_id":' + id + '},"module":"music.pf_song_detail_svr"}}',
'GET',
undefined,
);
signedMid = MusicInfo.songinfo?.data?.track_info.mid;
}
//第三方接口 存在速率限制 现在勉强用
const MusicReal = await RequestUtil.HttpGetJson<{
code: number,
data?: {
name: string,
singer: string,
url: string,
cover: string
}
}>('https://api.leafone.cn/api/qqmusic?id=' + signedMid + '&type=8', 'GET');
//console.log(MusicReal);
return { ...MusicReal.data, mid: signedMid };
}
//转换外域名为 https://qq.ugcimg.cn/v1/cpqcbu4b8870i61bde6k7cbmjgejq8mr3in82qir4qi7ielffv5slv8ck8g42novtmev26i233ujtuab6tvu2l2sjgtupfr389191v00s1j5oh5325j5eqi40774jv1i/khovifoh7jrqd6eahoiv7koh8o
//https://cgi.connect.qq.com/qqconnectopen/openapi/change_image_url?url=https://th.bing.com/th?id=OSK.b8ed36f1fb1889de6dc84fd81c187773&w=46&h=46&c=11&rs=1&qlt=80&o=6&dpr=2&pid=SANGAM
@@ -227,10 +21,5 @@ export class NTQQMusicSignApi {
//https://y.gtimg.cn/music/photo_new/T002R800x800M000000y5gq7449K9I.jpg?max_age=2592000
//还有一处公告上传可以上传高质量图片 持久为qq域名
async SignMusicWrapper(id: string = '') {
const MusicInfo = await this.signWay03(id)!;
return await this.signInternal(MusicInfo.name!, MusicInfo.singer!, MusicInfo.cover!, MusicInfo.mid!, 'https://ws.stream.qqmusic.qq.com/' + MusicInfo.url!);
}
}

View File

@@ -18,7 +18,7 @@ export class NTQQUserApi {
async getStatusByUid(uid: string) {
return this.context.session.getProfileService().getStatus(uid);
}
async getProfileLike(uid: string) {
async getProfileLike(uid: string, start: number, count: number) {
return this.context.session.getProfileLikeService().getBuddyProfileLike({
friendUids: [uid],
basic: 1,
@@ -26,8 +26,8 @@ export class NTQQUserApi {
favorite: 0,
userProfile: 1,
type: 2,
start: 0,
limit: 20,
start: start,
limit: count,
});
}
async fetchOtherProfileLike(uid: string) {
@@ -68,8 +68,7 @@ export class NTQQUserApi {
}
async setQQAvatar(filePath: string) {
type setQQAvatarRet = { result: number, errMsg: string };
const ret = await this.context.session.getProfileService().setHeader(filePath) as setQQAvatarRet;
const ret = await this.context.session.getProfileService().setHeader(filePath);
return { result: ret?.result, errMsg: ret?.errMsg };
}
@@ -120,12 +119,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);
const data = await RequestUtil.HttpsGetCookies(requestUrl);
if (!data.p_skey || data.p_skey.length == 0) {
try {
const pskey = (await this.getPSkey([domain])).domainPskeyMap.get(domain);
if (pskey) data.p_skey = pskey;
} catch {
return data;
}
}
return data;
}
async getPSkey(domainList: string[]) {

View File

@@ -212,108 +212,65 @@ export class NTQQWebApi {
}
}
private async getDataInternal(cookieObject: any, groupCode: string, type: number) {
let resJson;
try {
const res = await RequestUtil.HttpGetText(
`https://qun.qq.com/interactive/honorlist?${new URLSearchParams({
gc: groupCode,
type: type.toString(),
}).toString()}`,
'GET',
'',
{ 'Cookie': this.cookieToString(cookieObject) }
);
const match = /window\.__INITIAL_STATE__=(.*?);/.exec(res);
if (match) {
resJson = JSON.parse(match[1].trim());
}
return type === 1 ? resJson?.talkativeList : resJson?.actorList;
} catch (e) {
this.context.logger.logDebug('获取当前群荣耀失败', e);
return undefined;
}
}
private async getHonorList(cookieObject: any, groupCode: string, type: number) {
const data = await this.getDataInternal(cookieObject, groupCode, type);
if (!data) {
this.context.logger.logError(`获取类型 ${type} 的荣誉信息失败`);
return [];
}
return data.map((item: any) => ({
user_id: item?.uin,
nickname: item?.name,
avatar: item?.avatar,
description: item?.desc,
}));
}
async getGroupHonorInfo(groupCode: string, getType: WebHonorType) {
const cookieObject = await this.core.apis.UserApi.getCookies('qun.qq.com');
const getDataInternal = async (Internal_groupCode: string, Internal_type: number) => {
let resJson;
try {
const res = await RequestUtil.HttpGetText(
`https://qun.qq.com/interactive/honorlist?${new URLSearchParams({
gc: Internal_groupCode,
type: Internal_type.toString(),
}).toString()}`,
'GET',
'',
{ 'Cookie': this.cookieToString(cookieObject) }
);
const match = /window\.__INITIAL_STATE__=(.*?);/.exec(res);
if (match) {
resJson = JSON.parse(match[1].trim());
}
if (Internal_type === 1) {
return resJson?.talkativeList;
} else {
return resJson?.actorList;
}
} catch (e) {
this.context.logger.logDebug('获取当前群荣耀失败', e);
}
return undefined;
};
const HonorInfo: any = { group_id: groupCode };
if (getType === WebHonorType.TALKATIVE || getType === WebHonorType.ALL) {
const RetInternal = await getDataInternal(groupCode, 1);
if (RetInternal) {
HonorInfo.current_talkative = {
user_id: RetInternal[0]?.uin,
avatar: RetInternal[0]?.avatar,
nickname: RetInternal[0]?.name,
day_count: 0,
description: RetInternal[0]?.desc,
};
HonorInfo.talkative_list = [];
for (const talkative_ele of RetInternal) {
HonorInfo.talkative_list.push({
user_id: talkative_ele?.uin,
avatar: talkative_ele?.avatar,
description: talkative_ele?.desc,
day_count: 0,
nickname: talkative_ele?.name,
});
}
} else {
this.context.logger.logError('获取龙王信息失败');
const talkativeList = await this.getHonorList(cookieObject, groupCode, 1);
if (talkativeList.length > 0) {
HonorInfo.current_talkative = talkativeList[0];
HonorInfo.talkative_list = talkativeList;
}
}
if (getType === WebHonorType.PERFORMER || getType === WebHonorType.ALL) {
const RetInternal = await getDataInternal(groupCode, 2);
if (RetInternal) {
HonorInfo.performer_list = [];
for (const performer_ele of RetInternal) {
HonorInfo.performer_list.push({
user_id: performer_ele?.uin,
nickname: performer_ele?.name,
avatar: performer_ele?.avatar,
description: performer_ele?.desc,
});
}
} else {
this.context.logger.logError('获取群聊之火失败');
}
HonorInfo.performer_list = await this.getHonorList(cookieObject, groupCode, 2);
}
if (getType === WebHonorType.PERFORMER || getType === WebHonorType.ALL) {
const RetInternal = await getDataInternal(groupCode, 3);
if (RetInternal) {
HonorInfo.legend_list = [];
for (const legend_ele of RetInternal) {
HonorInfo.legend_list.push({
user_id: legend_ele?.uin,
nickname: legend_ele?.name,
avatar: legend_ele?.avatar,
desc: legend_ele?.description,
});
}
} else {
this.context.logger.logError('获取群聊炽焰失败');
}
if (getType === WebHonorType.LEGEND || getType === WebHonorType.ALL) {
HonorInfo.legend_list = await this.getHonorList(cookieObject, groupCode, 3);
}
if (getType === WebHonorType.EMOTION || getType === WebHonorType.ALL) {
const RetInternal = await getDataInternal(groupCode, 6);
if (RetInternal) {
HonorInfo.emotion_list = [];
for (const emotion_ele of RetInternal) {
HonorInfo.emotion_list.push({
user_id: emotion_ele.uin,
nickname: emotion_ele.name,
avatar: emotion_ele.avatar,
desc: emotion_ele.description,
});
}
} else {
this.context.logger.logError('获取快乐源泉失败');
}
HonorInfo.emotion_list = await this.getHonorList(cookieObject, groupCode, 6);
}
// 冒尖小春笋好像已经被tx扬了 R.I.P.
@@ -338,4 +295,12 @@ export class NTQQWebApi {
}
return (hash & 0x7FFFFFFF).toString();
}
public getBknFromSKey(sKey: string) {
let hash = 5381;
for (let i = 0; i < sKey.length; i++) {
const code = sKey.charCodeAt(i);
hash = hash + (hash << 5) + code;
}
return (hash & 0x7FFFFFFF).toString();
}
}

View File

@@ -23,7 +23,7 @@ export interface ChatCacheList {
export interface ChatCacheListItem {
chatType: ChatType;
basicChatCacheInfo: ChatCacheListItemBasic;
guildChatCacheInfo: unknown[]; // TODO: 没用过频道所以不知道这里边的详细内容
guildChatCacheInfo: unknown[]; // work: 没用过频道所以不知道这里边的详细内容
}
export interface ChatCacheListItemBasic {

View File

@@ -117,6 +117,7 @@ export enum GroupMemberRole {
}
export interface GroupMember {
memberRealLevel: number | undefined;
memberSpecialTitle?: string;
avatarPath: string;
cardName: string;

View File

@@ -27,94 +27,70 @@ export interface GetFileListParam {
export enum ElementType {
UNKNOWN = 0,
TEXT = 1,
PIC = 2,
FILE = 3,
PTT = 4,
VIDEO = 5,
FACE = 6,
REPLY = 7,
GreyTip = 8, // “小灰条”,包括拍一拍 (Poke)、撤回提示等
WALLET = 9,
/**
* “小灰条”,包括拍一拍 (Poke)、撤回提示等
*/
GreyTip = 8,
ARK = 10,
MFACE = 11,
LIVEGIFT = 12,
STRUCTLONGMSG = 13,
MARKDOWN = 14,
GIPHY = 15,
MULTIFORWARD = 16,
INLINEKEYBOARD = 17,
INTEXTGIFT = 18,
CALENDAR = 19,
YOLOGAMERESULT = 20,
AVRECORD = 21,
FEED = 22,
TOFURECORD = 23,
ACEBUBBLE = 24,
ACTIVITY = 25,
TOFU = 26,
FACEBUBBLE = 27,
SHARELOCATION = 28,
TASKTOPMSG = 29,
RECOMMENDEDMSG = 43,
ACTIONBAR = 44
}
type ElementFullBase = Omit<MessageElement, 'elementType' | 'elementId' | 'extBufForUI'>;
type ElementBase<
K extends keyof ElementFullBase,
S extends Partial<{ [P in K]: keyof NonNullable<ElementFullBase[P]> | Array<keyof NonNullable<ElementFullBase[P]>> }> = object
> = {
[P in K]:
S[P] extends Array<infer U>
? Pick<NonNullable<ElementFullBase[P]>, U & keyof NonNullable<ElementFullBase[P]>>
: S[P] extends keyof NonNullable<ElementFullBase[P]>
? Pick<NonNullable<ElementFullBase[P]>, S[P]>
: NonNullable<ElementFullBase[P]>;
};
export interface SendElementBase<ET extends ElementType> {
elementType: ET;
elementId: string;
extBufForUI?: string;
}
export interface ActionBarElement {
rows: InlineKeyboardRow[];
botAppid: string;
}
export interface SendActionBarElement {
elementType: ElementType.ACTIONBAR;
elementId: string;
actionBarElement: ActionBarElement;
}
export interface RecommendedMsgElement {
rows: InlineKeyboardRow[];
botAppid: string;
}
export interface SendRecommendedMsgElement {
elementType: ElementType.RECOMMENDEDMSG;
elementId: string;
recommendedMsgElement: RecommendedMsgElement;
}
export type SendRecommendedMsgElement = SendElementBase<ElementType.RECOMMENDEDMSG> & ElementBase<'recommendedMsgElement'>;
export interface InlineKeyboardButton {
id: string;
@@ -171,11 +147,7 @@ export enum NTMsgType {
KMSGTYPEWALLET = 10
}
export interface SendTaskTopMsgElement {
elementType: ElementType.TASKTOPMSG;
elementId: string;
taskTopMsgElement: TaskTopMsgElement;
}
export type SendTaskTopMsgElement = SendElementBase<ElementType.TASKTOPMSG> & ElementBase<'taskTopMsgElement'>;
export interface TofuRecordElement {
type: number;
@@ -194,11 +166,7 @@ export interface TofuRecordElement {
onscreennotify: boolean;
}
export interface SendTofuRecordElement {
elementType: ElementType.TOFURECORD;
elementId: string;
tofuRecordElement: TofuRecordElement;
}
export type SendTofuRecordElement = SendElementBase<ElementType.TOFURECORD> & ElementBase<'tofuRecordElement'>;
export interface FaceBubbleElement {
faceCount: number;
@@ -216,12 +184,7 @@ export interface FaceBubbleElement {
};
}
export interface SendFaceBubbleElement {
elementType: ElementType.FACEBUBBLE;
elementId: string;
faceBubbleElement: FaceBubbleElement;
}
export type SendFaceBubbleElement = SendElementBase<ElementType.FACEBUBBLE> & ElementBase<'faceBubbleElement'>;
export interface AvRecordElement {
type: number;
@@ -232,11 +195,7 @@ export interface AvRecordElement {
extraType: number;
}
export interface SendavRecordElement {
elementType: ElementType.AVRECORD;
elementId: string;
avRecordElement: AvRecordElement;
}
export type SendAvRecordElement = SendElementBase<ElementType.AVRECORD> & ElementBase<'avRecordElement'>;
export interface YoloUserInfo {
uid: string;
@@ -245,24 +204,13 @@ export interface YoloUserInfo {
bizId: string;
}
export interface SendInlineKeyboardElement {
elementType: ElementType.INLINEKEYBOARD;
elementId: string;
inlineKeyboardElement: {
rows: number;
botAppid: string;
};
}
export type SendInlineKeyboardElement = SendElementBase<ElementType.INLINEKEYBOARD> & ElementBase<'inlineKeyboardElement'>;
export interface YoloGameResultElement {
UserInfo: YoloUserInfo[];
}
export interface SendYoloGameResultElement {
elementType: ElementType.YOLOGAMERESULT;
yoloGameResultElement: YoloGameResultElement;
}
export type SendYoloGameResultElement = SendElementBase<ElementType.YOLOGAMERESULT> & ElementBase<'yoloGameResultElement'>;
export interface GiphyElement {
id: string;
@@ -271,17 +219,9 @@ export interface GiphyElement {
height: number;
}
export interface SendGiphyElement {
elementType: ElementType.GIPHY;
elementId: string;
giphyElement: GiphyElement;
}
export type SendGiphyElement = SendElementBase<ElementType.GIPHY> & ElementBase<'giphyElement'>;
export interface SendWalletElement {
elementType: ElementType.UNKNOWN;//不做 设置位置
elementId: string;
walletElement: Record<string, never>;
}
export type SendWalletElement = SendElementBase<ElementType.UNKNOWN> & ElementBase<'walletElement'>;
export interface CalendarElement {
summary: string;
@@ -291,49 +231,16 @@ export interface CalendarElement {
schema: string;
}
export interface SendCalendarElement {
elementType: ElementType.CALENDAR;
elementId: string;
calendarElement: CalendarElement;
}
export type SendCalendarElement = SendElementBase<ElementType.CALENDAR> & ElementBase<'calendarElement'>;
export interface SendliveGiftElement {
elementType: ElementType.LIVEGIFT;
elementId: string;
liveGiftElement: Record<string, never>;
}
export type SendLiveGiftElement = SendElementBase<ElementType.LIVEGIFT> & ElementBase<'liveGiftElement'>;
export interface SendTextElement {
elementType: ElementType.TEXT;
elementId: string;
textElement: {
content: string;
atType: number;
atUid: string;
atTinyId: string;
atNtUid: string;
};
}
export type SendTextElement = SendElementBase<ElementType.TEXT> & ElementBase<'textElement'>;
export interface SendPttElement {
elementType: ElementType.PTT;
elementId: string;
pttElement: {
fileName: string;
filePath: string;
md5HexStr: string;
fileSize: number;
duration: number; // 单位是秒
formatType: number;
voiceType: number;
voiceChangeType: number;
canConvert2Text: boolean;
waveAmplitudes: number[];
fileSubId: string;
playState: number;
autoConvertText: number;
};
}
export type SendPttElement = SendElementBase<ElementType.PTT> & ElementBase<'pttElement', {
pttElement: ['fileName', 'filePath', 'md5HexStr', 'fileSize', 'duration', 'formatType', 'voiceType',
'voiceChangeType', 'canConvert2Text', 'waveAmplitudes', 'fileSubId', 'playState', 'autoConvertText']
}>;
export enum PicType {
gif = 2000,
@@ -359,11 +266,7 @@ export enum NTMsgAtType {
ATTYPEUNKNOWN = 0
}
export interface SendPicElement {
elementType: ElementType.PIC;
elementId: string;
picElement: PicElement;
}
export type SendPicElement = SendElementBase<ElementType.PIC> & ElementBase<'picElement'>;
export interface ReplyElement {
sourceMsgIdInRecords?: string;
@@ -372,55 +275,30 @@ export interface ReplyElement {
senderUin: string;
senderUidStr?: string;
replyMsgTime?: string;
replyMsgClientSeq?: string;
}
export interface SendReplyElement {
elementType: ElementType.REPLY;
elementId: string;
replyElement: ReplyElement;
}
export type SendReplyElement = SendElementBase<ElementType.REPLY> & ElementBase<'replyElement'>;
export interface SendFaceElement {
elementType: ElementType.FACE;
elementId: string;
faceElement: FaceElement;
}
export type SendFaceElement = SendElementBase<ElementType.FACE> & ElementBase<'faceElement'>;
export interface SendMarketFaceElement {
elementType: ElementType.MFACE;
marketFaceElement: MarketFaceElement;
}
export type SendMarketFaceElement = SendElementBase<ElementType.MFACE> & ElementBase<'marketFaceElement'>;
export interface SendstructLongMsgElement {
elementType: ElementType.STRUCTLONGMSG;
elementId: string;
structLongMsgElement: StructLongMsgElement;
}
export type SendStructLongMsgElement = SendElementBase<ElementType.STRUCTLONGMSG> & ElementBase<'structLongMsgElement'>;
export interface StructLongMsgElement {
xmlContent: string;
resId: string;
}
export interface SendactionBarElement {
elementType: ElementType.ACTIONBAR;
elementId: string;
actionBarElement: {
rows: number;
botAppid: string;
};
}
export type SendActionBarElement = SendElementBase<ElementType.ACTIONBAR> & ElementBase<'actionBarElement'>;
export interface ShareLocationElement {
text: string;
ext: string;
}
export interface SendShareLocationElement {
elementType: ElementType.SHARELOCATION;
elementId: string;
shareLocationElement?: ShareLocationElement;
}
export type SendShareLocationElement = SendElementBase<ElementType.SHARELOCATION> & ElementBase<'shareLocationElement'>;
export interface FileElement {
fileMd5?: string;
@@ -440,29 +318,13 @@ export interface FileElement {
fileBizId?: number;
}
export interface SendFileElement {
elementType: ElementType.FILE;
elementId: string;
fileElement: FileElement;
}
export type SendFileElement = SendElementBase<ElementType.FILE> & ElementBase<'fileElement'>;
export interface SendVideoElement {
elementType: ElementType.VIDEO;
elementId: string;
videoElement: VideoElement;
}
export type SendVideoElement = SendElementBase<ElementType.VIDEO> & ElementBase<'videoElement'>;
export interface SendArkElement {
elementType: ElementType.ARK;
elementId: string;
arkElement: ArkElement;
}
export type SendArkElement = SendElementBase<ElementType.ARK> & ElementBase<'arkElement'>;
export interface SendMarkdownElement {
elementType: ElementType.MARKDOWN;
elementId: string;
markdownElement: MarkdownElement;
}
export type SendMarkdownElement = SendElementBase<ElementType.MARKDOWN> & ElementBase<'markdownElement'>;
export type SendMessageElement = SendTextElement | SendPttElement |
SendPicElement | SendReplyElement | SendFaceElement | SendMarketFaceElement | SendFileElement |
@@ -479,7 +341,7 @@ export interface TextElement {
export interface MessageElement {
elementType: ElementType,
elementId: string,
extBufForUI: string,//"0x",
extBufForUI?: string, //"0x",
textElement?: TextElement;
faceElement?: FaceElement,
marketFaceElement?: MarketFaceElement,
@@ -508,7 +370,6 @@ export interface MessageElement {
taskTopMsgElement?: TaskTopMsgElement,
recommendedMsgElement?: RecommendedMsgElement,
actionBarElement?: ActionBarElement
}
export enum AtType {
@@ -516,6 +377,12 @@ export enum AtType {
atAll = 1,
atUser = 2
}
export enum MsgSourceType {
K_DOWN_SOURCETYPE_AIOINNER = 1,
K_DOWN_SOURCETYPE_BIGSCREEN = 2,
K_DOWN_SOURCETYPE_HISTORY = 3,
K_DOWN_SOURCETYPE_UNKNOWN = 0
}
// 来自Android分析
export enum ChatType {
@@ -571,7 +438,7 @@ export interface PttElement {
fileSize: string; // "4261"
fileSubId: string; // "0"
fileUuid: string; // "90j3z7rmRphDPrdVgP9udFBaYar#oK0TWZIV"
formatType: string; // 1
formatType: number; // 1
invalidState: number; // 0
md5HexStr: string; // "e4d09c784d5a2abcb2f9980bdc7acfe6"
playState: number; // 0
@@ -582,6 +449,7 @@ export interface PttElement {
voiceChangeType: number; // 0
voiceType: number; // 0
waveAmplitudes: number[];
autoConvertText: number;
}
export interface ArkElement {
@@ -659,7 +527,8 @@ export interface GrayTipElement {
export enum FaceType {
normal = 1, // 小黄脸
normal2 = 2, // 新小黄脸, 从faceIndex 222开始
dice = 3 // 骰子
dice = 3, // 骰子
poke = 5 // 拍一拍
}
export enum FaceIndex {
@@ -786,7 +655,8 @@ export interface InlineKeyboardElementRowButton {
export interface InlineKeyboardElement {
rows: [{
buttons: InlineKeyboardElementRowButton[]
}];
}],
botAppid: string;
}
export interface TipAioOpGrayTipElement { // 这是什么提示来着?
@@ -872,6 +742,8 @@ export interface RawMessage {
/**
* 扩展字段,与 Ob11 msg ID 有关
*/
id?: number;
guildId: string;
@@ -908,11 +780,26 @@ export interface RawMessage {
*/
peerUin: string;
/**
* 好友备注(如果是好友消息)
*/
remark?: string;
/**
* 群名(如果是群消息)
*/
peerName: string;
/**
* 发送者昵称(如果是好友消息)
*/
sendNickName: string;
/**
* 发送者好友备注(如果是群消息并且有发送者好友)
*/
sendRemarkName: string;
/**
* 发送者群名片(如果是群消息)
*/
@@ -933,6 +820,10 @@ export interface RawMessage {
records: RawMessage[];
elements: MessageElement[];
sourceType: MsgSourceType;
isOnlineMsg: boolean;
}
export interface QueryMsgsParams {
chatInfo: Peer;
@@ -973,4 +864,4 @@ export interface MsgReqType {
extraCnt: number
}
//getMsgsIncludeSelf Peer必须 byType 1
//getMsgsWithMsgTimeAndClientSeqForC2C Peer必须 byType 3
//getMsgsWithMsgTimeAndClientSeqForC2C Peer必须 byType 3

View File

@@ -43,6 +43,50 @@ export enum GroupInviteType {
BYGROUPMEMBER,
BYDISCUSSMEMBER
}
export interface ShutUpGroupHonor {
[key: string]: number;
}
export interface ShutUpGroupMember {
uid: string;
qid: string;
uin: string;
nick: string;
remark: string;
cardType: number;
cardName: string;
role: number;
avatarPath: string;
shutUpTime: number;
isDelete: boolean;
isSpecialConcerned: boolean;
isSpecialShield: boolean;
isRobot: boolean;
groupHonor: ShutUpGroupHonor;
memberRealLevel: number;
memberLevel: number;
globalGroupLevel: number;
globalGroupPoint: number;
memberTitleId: number;
memberSpecialTitle: string;
specialTitleExpireTime: string;
userShowFlag: number;
userShowFlagNew: number;
richFlag: number;
mssVipType: number;
bigClubLevel: number;
bigClubFlag: number;
autoRemark: string;
creditLevel: number;
joinTime: number;
lastSpeakTime: number;
memberFlag: number;
memberFlagExt: number;
memberMobileFlag: number;
memberFlagExt2: number;
isSpecialShielded: boolean;
cardNameId: number;
}
export interface GroupNotify {
seq: string; // 通知序列号

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

@@ -175,8 +175,8 @@ export interface SimpleInfo {
status: UserStatus | null;
vasInfo: VasInfo | null;
relationFlags: RelationFlags | null;
otherFlags: any | null;
intimate: any | null;
otherFlags: any;
intimate: any;
}
export type FriendV2 = SimpleInfo;
@@ -288,9 +288,9 @@ export interface User {
export interface SelfInfo extends User {
online?: boolean;
}
export type Friend = User;
export interface Friend extends User {
}
// 本来是 Friend extends User 现在用不到
export enum BizKey {
KPRIVILEGEICON,

View File

@@ -1,10 +1,90 @@
{
"9.9.15-28060":{
"9.9.15-28060": {
"appid": 537246092,
"qua": "V1_WIN_NQ_9.9.15_28060_GW_B"
},
"3.2.12-28060":{
"9.9.15-28131": {
"appid": 537246092,
"qua": "V1_WIN_NQ_9.9.15_28131_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"
},
"3.2.13-28788": {
"appid": 537249787,
"qua": "V1_LNX_NQ_3.2.13_28788_GW_B"
},
"9.9.16-28788": {
"appid": 537249739,
"qua": "V1_WIN_NQ_9.9.16_28788_GW_B"
},
"9.9.16-28971": {
"appid": 537249775,
"qua": "V1_WIN_NQ_9.9.16_28971_GW_B"
},
"3.2.13-28971": {
"appid": 537249848,
"qua": "V1_LNX_NQ_3.2.13_28971_GW_B"
},
"6.9.58-28971": {
"appid": 537249826,
"qua": "V1_MAC_NQ_6.9.58_28971_GW_B"
},
"9.9.16-29271": {
"appid": 537249813,
"qua": "V1_WIN_NQ_9.9.16_29271_GW_B"
},
"3.2.13-29271": {
"appid": 537249913,
"qua": "V1_LNX_NQ_3.2.13_29271_GW_B"
},
"6.9.59-29271": {
"appid": 537249863,
"qua": "V1_MAC_NQ_6.9.59_29271_GW_B"
},
"9.9.16-29456": {
"appid": 537249875,
"qua": "V1_WIN_NQ_9.9.16_29456_GW_B"
},
"3.2.13-29456": {
"appid": 537249996,
"qua": "V1_LNX_NQ_3.2.13_29456_GW_B"
},
"6.9.59-29456": {
"appid": 537249961,
"qua": "V1_MAC_NQ_6.9.59_29456_GW_B"
}
}

View File

@@ -2,5 +2,7 @@
"fileLog": true,
"consoleLog": true,
"fileLogLevel": "debug",
"consoleLogLevel": "info"
"consoleLogLevel": "info",
"packetBackend": "auto",
"packetServer": ""
}

86
src/core/external/offset.json vendored Normal file
View File

@@ -0,0 +1,86 @@
{
"3.2.12-28418-x64": {
"recv": "A0723E0",
"send": "A06EAE0"
},
"9.9.15-28418-x64": {
"recv": "37A9004",
"send": "37A4BD0"
},
"6.9.56-28418-x64": {
"send": "4471360",
"recv": "4473BCC"
},
"6.9.56-28418-arm64": {
"send": "3FBDBF8",
"recv": "3FC0410"
},
"9.9.15-28498-x64": {
"recv": "37A9004",
"send": "37A4BD0"
},
"9.9.16-28788-x64": {
"send": "38076D0",
"recv": "380BB04"
},
"3.2.13-28788-x64": {
"send": "A0CEC20",
"recv": "A0D2520"
},
"3.2.13-28788-arm64": {
"send": "6E91018",
"recv": "6E94850"
},
"9.9.16-28971-x64": {
"send": "38079F0",
"recv": "380BE24"
},
"3.2.13-28971-x64": {
"send": "A0CEF60",
"recv": "A0D2860"
},
"3.2.12-28971-arm64": {
"send": "6E91318",
"recv": "6E94B50"
},
"6.9.58-28971-x64": {
"send": "449ACA0",
"recv": "449D50C"
},
"6.9.58-28971-arm64": {
"send": "3FE0DB0",
"recv": "3FE35C8"
},
"9.9.16-29271-x64": {
"send": "3833510",
"recv": "3837944"
},
"3.2.13-29271-x64": {
"send": "A11E680",
"recv": "A121F80"
},
"3.2.13-29271-arm64": {
"send": "6ECA098",
"recv": "6ECD8D0"
},
"9.9.16-29456-x64": {
"send": "3835CD0",
"recv": "383A104"
},
"3.2.13-29456-x64": {
"send": "A11E820",
"recv": "A122120"
},
"3.2.13-29456-arm64": {
"send": "6ECA130",
"recv": "6ECD968"
},
"6.9.59-29456-x64": {
"send": "44C57A0",
"recv": "44C800C"
},
"6.9.59-29456-arm64": {
"send": "4005FE8",
"recv": "4008800"
}
}

View File

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

View File

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

View File

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

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

View File

@@ -0,0 +1,61 @@
// work:further refactor in NapCat.Packet v2
import { NapProtoMsg, ProtoField, ScalarType } from "@napneko/nap-proto-core";
const LikeDetail = {
txt: ProtoField(1, ScalarType.STRING),
uin: ProtoField(3, ScalarType.INT64),
nickname: ProtoField(5, ScalarType.STRING)
};
const LikeMsg = {
times: ProtoField(1, ScalarType.INT32),
time: ProtoField(2, ScalarType.INT32),
detail: ProtoField(3, () => LikeDetail)
};
const ProfileLikeSubTip = {
msg: ProtoField(14, () => LikeMsg)
};
const ProfileLikeTip = {
msgType: ProtoField(1, ScalarType.INT32),
subType: ProtoField(2, ScalarType.INT32),
content: ProtoField(203, () => ProfileLikeSubTip)
};
const SysMessageHeader = {
PeerNumber: ProtoField(1, ScalarType.UINT32),
PeerString: ProtoField(2, ScalarType.STRING),
Uin: ProtoField(5, ScalarType.UINT32),
Uid: ProtoField(6, ScalarType.STRING, true)
};
const SysMessageMsgSpec = {
msgType: ProtoField(1, ScalarType.UINT32),
subType: ProtoField(2, ScalarType.UINT32),
subSubType: ProtoField(3, ScalarType.UINT32),
msgSeq: ProtoField(5, ScalarType.UINT32),
time: ProtoField(6, ScalarType.UINT32),
msgId: ProtoField(12, ScalarType.UINT64),
other: ProtoField(13, ScalarType.UINT32)
};
const SysMessageBodyWrapper = {
wrappedBody: ProtoField(2, ScalarType.BYTES)
};
const SysMessage = {
header: ProtoField(1, () => SysMessageHeader, false, true),
msgSpec: ProtoField(2, () => SysMessageMsgSpec, false, true),
bodyWrapper: ProtoField(3, () => SysMessageBodyWrapper)
};
export function decodeProfileLikeTip(buffer: Uint8Array) {
const msg = new NapProtoMsg(ProfileLikeTip);
return msg.decode(buffer);
}
export function decodeSysMessage(buffer: Uint8Array) {
const msg = new NapProtoMsg(SysMessage);
return msg.decode(buffer);
}

View File

@@ -0,0 +1,49 @@
// work:further refactor in NapCat.Packet v2
import { NapProtoMsg, ProtoField, ScalarType } from "@napneko/nap-proto-core";
const BodyInner = {
msgType: ProtoField(1, ScalarType.UINT32, true),
subType: ProtoField(2, ScalarType.UINT32, true)
};
const NoifyData = {
skip: ProtoField(1, ScalarType.BYTES, true),
innerData: ProtoField(2, ScalarType.BYTES, true)
};
const MsgHead = {
bodyInner: ProtoField(2, () => BodyInner, true),
noifyData: ProtoField(3, () => NoifyData, true)
};
const Message = {
msgHead: ProtoField(1, () => MsgHead)
};
const SubDetail = {
msgSeq: ProtoField(1, ScalarType.UINT32),
msgTime: ProtoField(2, ScalarType.UINT32),
senderUid: ProtoField(6, ScalarType.STRING)
};
const RecallDetails = {
operatorUid: ProtoField(1, ScalarType.STRING),
subDetail: ProtoField(3, () => SubDetail)
};
const RecallGroup = {
type: ProtoField(1, ScalarType.INT32),
peerUid: ProtoField(4, ScalarType.UINT32),
recallDetails: ProtoField(11, () => RecallDetails),
grayTipsSeq: ProtoField(37, ScalarType.UINT32)
};
export function decodeMessage(buffer: Uint8Array) {
const msg = new NapProtoMsg(Message);
return msg.decode(buffer);
}
export function decodeRecallGroup(buffer: Uint8Array){
const msg = new NapProtoMsg(RecallGroup);
return msg.decode(buffer);
}

View File

@@ -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;
@@ -42,9 +43,18 @@ export class RkeyManager {
//刷新rkey
for (const url of this.serverUrl) {
try {
this.rkeyData = await RequestUtil.HttpGetJson<ServerRkeyData>(url, 'GET');
const 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(`[Rkey] Get Rkey ${url} Error `, 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,14 @@ 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';
import { NTQQPacketApi } from './apis/packet';
export * from './wrapper';
export * from './entities';
export * from './services';
@@ -62,14 +62,32 @@ export function loadQQWrapper(QQVersion: string): WrapperNodeApi {
process.dlopen(nativemodule, wrapperNodePath);
return nativemodule.exports;
}
export function getMajorPath(QQVersion: string): string {
// major.node
let appPath;
if (os.platform() === 'darwin') {
appPath = path.resolve(path.dirname(process.execPath), '../Resources/app');
} else if (os.platform() === 'linux') {
appPath = path.resolve(path.dirname(process.execPath), './resources/app');
} else {
appPath = path.resolve(path.dirname(process.execPath), `./versions/${QQVersion}/`);
}
let majorPath = path.resolve(appPath, 'major.node');
if (!fs.existsSync(majorPath)) {
majorPath = path.join(appPath, `./resources/app/major.node`);
}
//老版本兼容 未来去掉
if (!fs.existsSync(majorPath)) {
majorPath = path.join(path.dirname(process.execPath), `./resources/app/versions/${QQVersion}/major.node`);
}
return majorPath;
}
export class NapCatCore {
readonly context: InstanceContext;
readonly apis: StableNTApiWrapper;
readonly eventWrapper: NTEventWrapper;
// readonly eventChannel: NTEventChannel;
NapCatDataPath: string;
NapCatTempPath: string;
NapCatDataPath: string = '';
NapCatTempPath: string = '';
apis: StableNTApiWrapper;
// runtime info, not readonly
selfInfo: SelfInfo;
util: NodeQQNTWrapperUtil;
@@ -81,17 +99,20 @@ export class NapCatCore {
this.context = context;
this.util = this.context.wrapper.NodeQQNTWrapperUtil;
this.eventWrapper = new NTEventWrapper(context.session);
this.configLoader = new NapCatConfigLoader(this, this.context.pathWrapper.configPath);
this.apis = {
FileApi: new NTQQFileApi(this.context, this),
SystemApi: new NTQQSystemApi(this.context, this),
CollectionApi: new NTQQCollectionApi(this.context, this),
PacketApi: new NTQQPacketApi(this.context, this),
WebApi: new NTQQWebApi(this.context, this),
FriendApi: new NTQQFriendApi(this.context, this),
MsgApi: new NTQQMsgApi(this.context, this),
UserApi: new NTQQUserApi(this.context, this),
GroupApi: new NTQQGroupApi(this.context, this),
};
this.configLoader = new NapCatConfigLoader(this, this.context.pathWrapper.configPath);
}
async initCore() {
this.NapCatDataPath = path.join(this.dataPath, 'NapCat');
fs.mkdirSync(this.NapCatDataPath, { recursive: true });
this.NapCatTempPath = path.join(this.NapCatDataPath, 'temp');
@@ -99,7 +120,14 @@ export class NapCatCore {
if (!fs.existsSync(this.NapCatTempPath)) {
fs.mkdirSync(this.NapCatTempPath, { recursive: true });
}
this.initNapCatCoreListeners().then().catch(this.context.logger.logError);
//遍历this.apis[i].initApi 如果存在该函数进行async 调用
for (const apiKey in this.apis) {
const api = this.apis[apiKey as keyof StableNTApiWrapper];
if ('initApi' in api && typeof api.initApi === 'function') {
await api.initApi();
}
}
this.initNapCatCoreListeners().then().catch(this.context.logger.logError.bind(this.context.logger));
this.context.logger.setFileLogEnabled(
this.configLoader.configData.fileLog,
@@ -112,7 +140,6 @@ export class NapCatCore {
this.configLoader.configData.consoleLogLevel as LogLevel,
);
}
get dataPath(): string {
let result = this.context.wrapper.NodeQQNTWrapperUtil.getNTUserDataInfoConfig();
if (!result) {
@@ -127,7 +154,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) => {
@@ -138,7 +165,7 @@ export class NapCatCore {
};
//await sleep(2500);
this.context.session.getMsgService().addKernelMsgListener(
proxiedListenerOf(msgListener, this.context.logger) as any,
proxiedListenerOf(msgListener, this.context.logger),
);
const profileListener = new NodeIKernelProfileListener();
@@ -183,7 +210,7 @@ export class NapCatCore {
});
};
groupListener.onMemberListChange = (arg) => {
// todo: 应该加一个内部自己维护的成员变动callback用于判断成员变化通知
// work:应该加一个内部自己维护的成员变动callback用于判断成员变化通知
const groupCode = arg.sceneId.split('_')[0];
if (this.apis.GroupApi.groupMemberCache.has(groupCode)) {
const existMembers = this.apis.GroupApi.groupMemberCache.get(groupCode)!;
@@ -193,7 +220,7 @@ export class NapCatCore {
if (existMember) {
Object.assign(existMember, member);
} else {
existMembers!.set(uid, member);
existMembers.set(uid, member);
}
//移除成员
if (member.isDelete) {
@@ -234,7 +261,7 @@ export class NapCatCore {
}
};
this.context.session.getGroupService().addKernelGroupListener(
proxiedListenerOf(groupListener, this.context.logger) as any,
proxiedListenerOf(groupListener, this.context.logger),
);
}
@@ -247,22 +274,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: {
@@ -270,7 +309,7 @@ export async function genSessionConfig(QQVersionAppid: string, QQVersion: string
systemId: 0,
appId: '',
logicEnvironment: '',
platform: PlatformType.KWINDOWS,
platform: systemPlatform,
language: '',
sdkVersion: '',
userId: '',
@@ -310,6 +349,7 @@ export interface InstanceContext {
export interface StableNTApiWrapper {
FileApi: NTQQFileApi,
SystemApi: NTQQSystemApi,
PacketApi: NTQQPacketApi,
CollectionApi: NTQQCollectionApi,
WebApi: NTQQWebApi,
FriendApi: NTQQFriendApi,

View File

@@ -3,57 +3,57 @@ import { BuddyCategoryType, FriendRequestNotify } from '@/core/entities';
export type OnBuddyChangeParams = BuddyCategoryType[];
export class NodeIKernelBuddyListener {
onBuddyListChangedV2(arg: unknown): void {
onBuddyListChangedV2(arg: unknown): any {
}
onAddBuddyNeedVerify(arg: unknown) {
onAddBuddyNeedVerify(arg: unknown): any {
}
onAddMeSettingChanged(arg: unknown) {
onAddMeSettingChanged(arg: unknown): any {
}
onAvatarUrlUpdated(arg: unknown) {
onAvatarUrlUpdated(arg: unknown): any {
}
onBlockChanged(arg: unknown) {
onBlockChanged(arg: unknown): any {
}
onBuddyDetailInfoChange(arg: unknown) {
onBuddyDetailInfoChange(arg: unknown): any {
}
onBuddyInfoChange(arg: unknown) {
onBuddyInfoChange(arg: unknown): any {
}
onBuddyListChange(arg: OnBuddyChangeParams): void {
onBuddyListChange(arg: OnBuddyChangeParams): any {
}
onBuddyRemarkUpdated(arg: unknown): void {
onBuddyRemarkUpdated(arg: unknown): any {
}
onBuddyReqChange(arg: FriendRequestNotify): void {
onBuddyReqChange(arg: FriendRequestNotify): any {
}
onBuddyReqUnreadCntChange(arg: unknown): void {
onBuddyReqUnreadCntChange(arg: unknown): any {
}
onCheckBuddySettingResult(arg: unknown): void {
onCheckBuddySettingResult(arg: unknown): any {
}
onDelBatchBuddyInfos(arg: unknown): void {
onDelBatchBuddyInfos(arg: unknown): any {
}
onDoubtBuddyReqChange(arg: unknown): void {
onDoubtBuddyReqChange(arg: unknown): any {
}
onDoubtBuddyReqUnreadNumChange(arg: unknown): void {
onDoubtBuddyReqUnreadNumChange(arg: unknown): any {
}
onNickUpdated(arg: unknown): void {
onNickUpdated(arg: unknown): any {
}
onSmartInfos(arg: unknown): void {
onSmartInfos(arg: unknown): any {
}
onSpacePermissionInfos(arg: unknown): void {
onSpacePermissionInfos(arg: unknown): any {
}
}

View File

@@ -7,19 +7,19 @@ export class NodeIKernelFileAssistantListener {
fileSpeed: number,
thumbPath: string | null,
filePath: string | null,
}) {
}): any {
}
onSessionListChanged(...args: unknown[]) {
onSessionListChanged(...args: unknown[]): any {
}
onSessionChanged(...args: unknown[]) {
onSessionChanged(...args: unknown[]): any {
}
onFileListChanged(...args: unknown[]) {
onFileListChanged(...args: unknown[]): any {
}
onFileSearch(searchResult: SearchResultWrapper) {
onFileSearch(searchResult: SearchResultWrapper): any {
}
}

View File

@@ -1,84 +1,85 @@
import { DataSource, Group, GroupListUpdateType, GroupMember, GroupNotify } from '@/core/entities';
import { DataSource, Group, GroupListUpdateType, GroupMember, GroupNotify, ShutUpGroupMember } from '@/core/entities';
export class NodeIKernelGroupListener {
onGroupListInited(listEmpty: boolean): void { }
onGroupListInited(listEmpty: boolean): any { }
// 发现于Win 9.9.9 23159
onGroupMemberLevelInfoChange(...args: unknown[]): void {
onGroupMemberLevelInfoChange(...args: unknown[]): any {
}
onGetGroupBulletinListResult(...args: unknown[]) {
onGetGroupBulletinListResult(...args: unknown[]): any {
}
onGroupAllInfoChange(...args: unknown[]) {
onGroupAllInfoChange(...args: unknown[]): any {
}
onGroupBulletinChange(...args: unknown[]) {
onGroupBulletinChange(...args: unknown[]): any {
}
onGroupBulletinRemindNotify(...args: unknown[]) {
onGroupBulletinRemindNotify(...args: unknown[]): any {
}
onGroupArkInviteStateResult(...args: unknown[]) {
onGroupArkInviteStateResult(...args: unknown[]): any {
}
onGroupBulletinRichMediaDownloadComplete(...args: unknown[]) {
onGroupBulletinRichMediaDownloadComplete(...args: unknown[]): any {
}
onGroupConfMemberChange(...args: unknown[]) {
onGroupConfMemberChange(...args: unknown[]): any {
}
onGroupDetailInfoChange(...args: unknown[]) {
onGroupDetailInfoChange(...args: unknown[]): any {
}
onGroupExtListUpdate(...args: unknown[]) {
onGroupExtListUpdate(...args: unknown[]): any {
}
onGroupFirstBulletinNotify(...args: unknown[]) {
onGroupFirstBulletinNotify(...args: unknown[]): any {
}
onGroupListUpdate(updateType: GroupListUpdateType, groupList: Group[]) {
onGroupListUpdate(updateType: GroupListUpdateType, groupList: Group[]): any {
}
onGroupNotifiesUpdated(dboubt: boolean, notifies: GroupNotify[]) {
onGroupNotifiesUpdated(dboubt: boolean, notifies: GroupNotify[]): any {
}
onGroupBulletinRichMediaProgressUpdate(...args: unknown[]) {
onGroupBulletinRichMediaProgressUpdate(...args: unknown[]): any {
}
onGroupNotifiesUnreadCountUpdated(...args: unknown[]) {
onGroupNotifiesUnreadCountUpdated(...args: unknown[]): any {
}
onGroupSingleScreenNotifies(doubt: boolean, seq: string, notifies: GroupNotify[]) {
onGroupSingleScreenNotifies(doubt: boolean, seq: string, notifies: GroupNotify[]): any {
}
onGroupsMsgMaskResult(...args: unknown[]) {
onGroupsMsgMaskResult(...args: unknown[]): any {
}
onGroupStatisticInfoChange(...args: unknown[]) {
onGroupStatisticInfoChange(...args: unknown[]): any {
}
onJoinGroupNotify(...args: unknown[]) {
onJoinGroupNotify(...args: unknown[]): any {
}
onJoinGroupNoVerifyFlag(...args: unknown[]) {
onJoinGroupNoVerifyFlag(...args: unknown[]): any {
}
onMemberInfoChange(groupCode: string, dateSource: DataSource, members: Map<string, GroupMember>) {
onMemberInfoChange(groupCode: string, dateSource: DataSource, members: Map<string, GroupMember>): any {
}
onMemberListChange(arg: {
sceneId: string,
ids: string[],
infos: Map<string, GroupMember>, // uid -> GroupMember
finish: boolean,
hasPrev: boolean,
hasNext: boolean,
hasRobot: boolean
}) {
}): any {
}
onSearchMemberChange(...args: unknown[]) {
onSearchMemberChange(...args: unknown[]): any {
}
onShutUpMemberListChanged(...args: unknown[]) {
onShutUpMemberListChanged(groupCode: string, members: Array<ShutUpGroupMember>): any {
}
}

View File

@@ -1,57 +1,57 @@
export class NodeIKernelLoginListener {
onLoginConnected(...args: any[]): void {
onLoginConnected(...args: any[]): any {
}
onLoginDisConnected(...args: any[]): void {
onLoginDisConnected(...args: any[]): any {
}
onLoginConnecting(...args: any[]): void {
onLoginConnecting(...args: any[]): any {
}
onQRCodeGetPicture(arg: { pngBase64QrcodeData: string, qrcodeUrl: string }): void {
onQRCodeGetPicture(arg: { pngBase64QrcodeData: string, qrcodeUrl: string }): any {
// let base64Data: string = arg.pngBase64QrcodeData
// base64Data = base64Data.split("data:image/png;base64,")[1]
// let buffer = Buffer.from(base64Data, 'base64')
// console.log("onQRCodeGetPicture", arg);
}
onQRCodeLoginPollingStarted(...args: any[]): void {
onQRCodeLoginPollingStarted(...args: any[]): any {
}
onQRCodeSessionUserScaned(...args: any[]): void {
onQRCodeSessionUserScaned(...args: any[]): any {
}
onQRCodeLoginSucceed(arg: QRCodeLoginSucceedResult): void {
onQRCodeLoginSucceed(arg: QRCodeLoginSucceedResult): any {
}
onQRCodeSessionFailed(...args: any[]): void {
onQRCodeSessionFailed(...args: any[]): any {
}
onLoginFailed(...args: any[]): void {
onLoginFailed(...args: any[]): any {
}
onLogoutSucceed(...args: any[]): void {
onLogoutSucceed(...args: any[]): any {
}
onLogoutFailed(...args: any[]): void {
onLogoutFailed(...args: any[]): any {
}
onUserLoggedIn(...args: any[]): void {
onUserLoggedIn(...args: any[]): any {
}
onQRCodeSessionQuickLoginFailed(...args: any[]): void {
onQRCodeSessionQuickLoginFailed(...args: any[]): any {
}
onPasswordLoginFailed(...args: any[]): void {
onPasswordLoginFailed(...args: any[]): any {
}
OnConfirmUnusualDeviceFailed(...args: any[]): void {
OnConfirmUnusualDeviceFailed(...args: any[]): any {
}
onQQLoginNumLimited(...args: any[]): void {
onQQLoginNumLimited(...args: any[]): any {
}
onLoginState(...args: any[]): void {
onLoginState(...args: any[]): any {
}
}

View File

@@ -1,4 +1,4 @@
import { ChatType, RawMessage } from '@/core/entities';
import { ChatType, KickedOffLineInfo, RawMessage } from '@/core/entities';
import { CommonFileInfo } from '@/core';
export interface OnRichMediaDownloadCompleteParams {
@@ -20,8 +20,8 @@ export interface OnRichMediaDownloadCompleteParams {
fileSrvErrCode: string,
clientMsg: string,
businessId: number,
userTotalSpacePerDay: unknown | null,
userUsedSpacePerDay: unknown | null
userTotalSpacePerDay: unknown,
userUsedSpacePerDay: unknown
}
export interface GroupFileInfoUpdateParamType {
@@ -94,108 +94,108 @@ export interface TempOnRecvParams {
}
export class NodeIKernelMsgListener {
onAddSendMsg(msgRecord: RawMessage) {
onAddSendMsg(msgRecord: RawMessage): any {
}
onBroadcastHelperDownloadComplete(broadcastHelperTransNotifyInfo: unknown) {
onBroadcastHelperDownloadComplete(broadcastHelperTransNotifyInfo: unknown): any {
}
onBroadcastHelperProgressUpdate(broadcastHelperTransNotifyInfo: unknown) {
onBroadcastHelperProgressUpdate(broadcastHelperTransNotifyInfo: unknown): any {
}
onChannelFreqLimitInfoUpdate(contact: unknown, z: unknown, freqLimitInfo: unknown) {
onChannelFreqLimitInfoUpdate(contact: unknown, z: unknown, freqLimitInfo: unknown): any {
}
onContactUnreadCntUpdate(hashMap: unknown) {
onContactUnreadCntUpdate(hashMap: unknown): any {
}
onCustomWithdrawConfigUpdate(customWithdrawConfig: unknown) {
onCustomWithdrawConfigUpdate(customWithdrawConfig: unknown): any {
}
onDraftUpdate(contact: unknown, arrayList: unknown, j2: unknown) {
onDraftUpdate(contact: unknown, arrayList: unknown, j2: unknown): any {
}
onEmojiDownloadComplete(emojiNotifyInfo: unknown) {
onEmojiDownloadComplete(emojiNotifyInfo: unknown): any {
}
onEmojiResourceUpdate(emojiResourceInfo: unknown) {
onEmojiResourceUpdate(emojiResourceInfo: unknown): any {
}
onFeedEventUpdate(firstViewDirectMsgNotifyInfo: unknown) {
onFeedEventUpdate(firstViewDirectMsgNotifyInfo: unknown): any {
}
onFileMsgCome(arrayList: unknown) {
onFileMsgCome(arrayList: unknown): any {
}
onFirstViewDirectMsgUpdate(firstViewDirectMsgNotifyInfo: unknown) {
onFirstViewDirectMsgUpdate(firstViewDirectMsgNotifyInfo: unknown): any {
}
onFirstViewGroupGuildMapping(arrayList: unknown) {
onFirstViewGroupGuildMapping(arrayList: unknown): any {
}
onGrabPasswordRedBag(i2: unknown, str: unknown, i3: unknown, recvdOrder: unknown, msgRecord: unknown) {
onGrabPasswordRedBag(i2: unknown, str: unknown, i3: unknown, recvdOrder: unknown, msgRecord: unknown): any {
}
onGroupFileInfoAdd(groupItem: unknown) {
onGroupFileInfoAdd(groupItem: unknown): any {
}
onGroupFileInfoUpdate(groupFileListResult: GroupFileInfoUpdateParamType) {
onGroupFileInfoUpdate(groupFileListResult: GroupFileInfoUpdateParamType): any {
}
onGroupGuildUpdate(groupGuildNotifyInfo: unknown) {
onGroupGuildUpdate(groupGuildNotifyInfo: unknown): any {
}
onGroupTransferInfoAdd(groupItem: unknown) {
onGroupTransferInfoAdd(groupItem: unknown): any {
}
onGroupTransferInfoUpdate(groupFileListResult: unknown) {
onGroupTransferInfoUpdate(groupFileListResult: unknown): any {
}
onGuildInteractiveUpdate(guildInteractiveNotificationItem: unknown) {
onGuildInteractiveUpdate(guildInteractiveNotificationItem: unknown): any {
}
onGuildMsgAbFlagChanged(guildMsgAbFlag: unknown) {
onGuildMsgAbFlagChanged(guildMsgAbFlag: unknown): any {
}
onGuildNotificationAbstractUpdate(guildNotificationAbstractInfo: unknown) {
onGuildNotificationAbstractUpdate(guildNotificationAbstractInfo: unknown): any {
}
onHitCsRelatedEmojiResult(downloadRelateEmojiResultInfo: unknown) {
onHitCsRelatedEmojiResult(downloadRelateEmojiResultInfo: unknown): any {
}
onHitEmojiKeywordResult(hitRelatedEmojiWordsResult: unknown) {
onHitEmojiKeywordResult(hitRelatedEmojiWordsResult: unknown): any {
}
onHitRelatedEmojiResult(relatedWordEmojiInfo: unknown) {
onHitRelatedEmojiResult(relatedWordEmojiInfo: unknown): any {
}
onImportOldDbProgressUpdate(importOldDbMsgNotifyInfo: unknown) {
onImportOldDbProgressUpdate(importOldDbMsgNotifyInfo: unknown): any {
}
@@ -208,176 +208,176 @@ export class NodeIKernelMsgListener {
statusText: string;
timestamp: string;
toUin: string;
}) {
}): any {
}
onKickedOffLine(kickedInfo: unknown) {
onKickedOffLine(kickedInfo: KickedOffLineInfo): any {
}
onLineDev(arrayList: unknown) {
onLineDev(arrayList: unknown): any {
}
onLogLevelChanged(j2: unknown) {
onLogLevelChanged(j2: unknown): any {
}
onMsgAbstractUpdate(arrayList: unknown) {
onMsgAbstractUpdate(arrayList: unknown): any {
}
onMsgBoxChanged(arrayList: unknown) {
onMsgBoxChanged(arrayList: unknown): any {
}
onMsgDelete(contact: unknown, arrayList: unknown) {
onMsgDelete(contact: unknown, arrayList: unknown): any {
}
onMsgEventListUpdate(hashMap: unknown) {
onMsgEventListUpdate(hashMap: unknown): any {
}
onMsgInfoListAdd(arrayList: unknown) {
onMsgInfoListAdd(arrayList: unknown): any {
}
onMsgInfoListUpdate(msgList: RawMessage[]) {
onMsgInfoListUpdate(msgList: RawMessage[]): any {
}
onMsgQRCodeStatusChanged(i2: unknown) {
onMsgQRCodeStatusChanged(i2: unknown): any {
}
onMsgRecall(i2: unknown, str: unknown, j2: unknown) {
onMsgRecall(i2: unknown, str: unknown, j2: unknown): any {
}
onMsgSecurityNotify(msgRecord: unknown) {
onMsgSecurityNotify(msgRecord: unknown): any {
}
onMsgSettingUpdate(msgSetting: unknown) {
onMsgSettingUpdate(msgSetting: unknown): any {
}
onNtFirstViewMsgSyncEnd() {
onNtFirstViewMsgSyncEnd(): any {
}
onNtMsgSyncEnd() {
onNtMsgSyncEnd(): any {
}
onNtMsgSyncStart() {
onNtMsgSyncStart(): any {
}
onReadFeedEventUpdate(firstViewDirectMsgNotifyInfo: unknown) {
onReadFeedEventUpdate(firstViewDirectMsgNotifyInfo: unknown): any {
}
onRecvGroupGuildFlag(i2: unknown) {
onRecvGroupGuildFlag(i2: unknown): any {
}
onRecvMsg(arrayList: RawMessage[]) {
onRecvMsg(arrayList: RawMessage[]): any {
}
onRecvMsgSvrRspTransInfo(j2: unknown, contact: unknown, i2: unknown, i3: unknown, str: unknown, bArr: unknown) {
onRecvMsgSvrRspTransInfo(j2: unknown, contact: unknown, i2: unknown, i3: unknown, str: unknown, bArr: unknown): any {
}
onRecvOnlineFileMsg(arrayList: unknown) {
onRecvOnlineFileMsg(arrayList: unknown): any {
}
onRecvS2CMsg(arrayList: unknown) {
onRecvS2CMsg(arrayList: unknown): any {
}
onRecvSysMsg(arrayList: Array<number>) {
onRecvSysMsg(arrayList: Array<number>): any {
}
onRecvUDCFlag(i2: unknown) {
onRecvUDCFlag(i2: unknown): any {
}
onRichMediaDownloadComplete(fileTransNotifyInfo: OnRichMediaDownloadCompleteParams) {
onRichMediaDownloadComplete(fileTransNotifyInfo: OnRichMediaDownloadCompleteParams): any {
}
onRichMediaProgerssUpdate(fileTransNotifyInfo: unknown) {
onRichMediaProgerssUpdate(fileTransNotifyInfo: unknown): any {
}
onRichMediaUploadComplete(fileTransNotifyInfo: unknown) {
onRichMediaUploadComplete(fileTransNotifyInfo: unknown): any {
}
onSearchGroupFileInfoUpdate(searchGroupFileResult: unknown) {
onSearchGroupFileInfoUpdate(searchGroupFileResult: unknown): any {
}
onSendMsgError(j2: unknown, contact: unknown, i2: unknown, str: unknown) {
onSendMsgError(j2: unknown, contact: unknown, i2: unknown, str: unknown): any {
}
onSysMsgNotification(i2: unknown, j2: unknown, j3: unknown, arrayList: unknown) {
onSysMsgNotification(i2: unknown, j2: unknown, j3: unknown, arrayList: unknown): any {
}
onTempChatInfoUpdate(tempChatInfo: TempOnRecvParams) {
onTempChatInfoUpdate(tempChatInfo: TempOnRecvParams): any {
}
onUnreadCntAfterFirstView(hashMap: unknown) {
onUnreadCntAfterFirstView(hashMap: unknown): any {
}
onUnreadCntUpdate(hashMap: unknown) {
onUnreadCntUpdate(hashMap: unknown): any {
}
onUserChannelTabStatusChanged(z: unknown) {
onUserChannelTabStatusChanged(z: unknown): any {
}
onUserOnlineStatusChanged(z: unknown) {
onUserOnlineStatusChanged(z: unknown): any {
}
onUserTabStatusChanged(arrayList: unknown) {
onUserTabStatusChanged(arrayList: unknown): any {
}
onlineStatusBigIconDownloadPush(i2: unknown, j2: unknown, str: unknown) {
onlineStatusBigIconDownloadPush(i2: unknown, j2: unknown, str: unknown): any {
}
onlineStatusSmallIconDownloadPush(i2: unknown, j2: unknown, str: unknown) {
onlineStatusSmallIconDownloadPush(i2: unknown, j2: unknown, str: unknown): any {
}
// 第一次发现于Linux
onUserSecQualityChanged(...args: unknown[]) {
onUserSecQualityChanged(...args: unknown[]): any {
}
onMsgWithRichLinkInfoUpdate(...args: unknown[]) {
onMsgWithRichLinkInfoUpdate(...args: unknown[]): any {
}
onRedTouchChanged(...args: unknown[]) {
onRedTouchChanged(...args: unknown[]): any {
}
// 第一次发现于Win 9.9.9-23159
onBroadcastHelperProgerssUpdate(...args: unknown[]) {
onBroadcastHelperProgerssUpdate(...args: unknown[]): any {
}
}

View File

@@ -5,67 +5,67 @@ export class NodeIKernelProfileListener {
}
onProfileSimpleChanged(...args: unknown[]) {
onProfileSimpleChanged(...args: unknown[]): any {
}
onProfileDetailInfoChanged(profile: User) {
onProfileDetailInfoChanged(profile: User): any {
}
onStatusUpdate(...args: unknown[]) {
onStatusUpdate(...args: unknown[]): any {
}
onSelfStatusChanged(...args: unknown[]) {
onSelfStatusChanged(...args: unknown[]): any {
}
onStrangerRemarkChanged(...args: unknown[]) {
onStrangerRemarkChanged(...args: unknown[]): any {
}
onMemberListChange(...args: unknown[]) {
onMemberListChange(...args: unknown[]): any {
}
onMemberInfoChange(...args: unknown[]) {
onMemberInfoChange(...args: unknown[]): any {
}
onGroupListUpdate(...args: unknown[]) {
onGroupListUpdate(...args: unknown[]): any {
}
onGroupAllInfoChange(...args: unknown[]) {
onGroupAllInfoChange(...args: unknown[]): any {
}
onGroupDetailInfoChange(...args: unknown[]) {
onGroupDetailInfoChange(...args: unknown[]): any {
}
onGroupConfMemberChange(...args: unknown[]) {
onGroupConfMemberChange(...args: unknown[]): any {
}
onGroupExtListUpdate(...args: unknown[]) {
onGroupExtListUpdate(...args: unknown[]): any {
}
onGroupNotifiesUpdated(...args: unknown[]) {
onGroupNotifiesUpdated(...args: unknown[]): any {
}
onGroupNotifiesUnreadCountUpdated(...args: unknown[]) {
onGroupNotifiesUnreadCountUpdated(...args: unknown[]): any {
}
onGroupMemberLevelInfoChange(...args: unknown[]) {
onGroupMemberLevelInfoChange(...args: unknown[]): any {
}
onGroupBulletinChange(...args: unknown[]) {
onGroupBulletinChange(...args: unknown[]): any {
}
}

View File

@@ -1,25 +1,25 @@
export class NodeIKernelRecentContactListener {
onDeletedContactsNotify(...args: unknown[]) {
onDeletedContactsNotify(...args: unknown[]): any {
}
onRecentContactNotification(msgList: any, arg0: { msgListUnreadCnt: string }, arg1: number) {
onRecentContactNotification(msgList: any, arg0: { msgListUnreadCnt: string }, arg1: number): any {
}
onMsgUnreadCountUpdate(...args: unknown[]) {
onMsgUnreadCountUpdate(...args: unknown[]): any {
}
onGuildDisplayRecentContactListChanged(...args: unknown[]) {
onGuildDisplayRecentContactListChanged(...args: unknown[]): any {
}
onRecentContactListChanged(...args: unknown[]) {
onRecentContactListChanged(...args: unknown[]): any {
}
onRecentContactListChangedVer2(...args: unknown[]) {
onRecentContactListChangedVer2(...args: unknown[]): any {
}
}

View File

@@ -1,13 +1,13 @@
export class NodeIKernelRobotListener {
onRobotFriendListChanged(...args: unknown[]) {
onRobotFriendListChanged(...args: unknown[]): any {
}
onRobotListChanged(...args: unknown[]) {
onRobotListChanged(...args: unknown[]): any {
}
onRobotProfileChanged(...args: unknown[]) {
onRobotProfileChanged(...args: unknown[]): any {
}
}

View File

@@ -57,7 +57,7 @@ export interface GroupSearchResult {
}
export interface NodeIKernelSearchListener {
onSearchGroupResult(params: GroupSearchResult): void;
onSearchGroupResult(params: GroupSearchResult): any;
onSearchFileKeywordsResult(params: {
searchId: string,
@@ -93,5 +93,5 @@ export interface NodeIKernelSearchListener {
end: number
}[]
}[]
}): void;
}): any;
}

View File

@@ -1,25 +1,25 @@
export class NodeIKernelSessionListener {
onNTSessionCreate(args: unknown) {
onNTSessionCreate(args: unknown): any {
}
onGProSessionCreate(args: unknown) {
onGProSessionCreate(args: unknown): any {
}
onSessionInitComplete(args: unknown) {
onSessionInitComplete(args: unknown): any {
}
onOpentelemetryInit(args: unknown) {
onOpentelemetryInit(args: unknown): any {
}
onUserOnlineResult(args: unknown) {
onUserOnlineResult(args: unknown): any {
}
onGetSelfTinyId(args: unknown) {
onGetSelfTinyId(args: unknown): any {
}
}

View File

@@ -1,21 +1,21 @@
export class NodeIKernelStorageCleanListener {
onCleanCacheProgressChanged(args: unknown) {
onCleanCacheProgressChanged(args: unknown): any {
}
onScanCacheProgressChanged(args: unknown) {
onScanCacheProgressChanged(args: unknown): any {
}
onCleanCacheStorageChanged(args: unknown) {
onCleanCacheStorageChanged(args: unknown): any {
}
onFinishScan(args: unknown) {
onFinishScan(args: unknown): any {
}
onChatCleanDone(args: unknown) {
onChatCleanDone(args: unknown): any {
}
}

View File

@@ -1,2 +1,5 @@
export class NodeIKernelTicketListener {
listener(): any {
}
}

View File

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

View File

@@ -0,0 +1,91 @@
import { LRUCache } from "@/common/lru-cache";
import crypto, { createHash } from "crypto";
import { PacketContext } from "@/core/packet/context/packetContext";
import { OidbPacket, PacketHexStr } from "@/core/packet/transformer/base";
import { LogStack } from "@/core/packet/context/clientContext";
export interface RecvPacket {
type: string, // 仅recv
trace_id_md5?: string,
data: RecvPacketData
}
export interface RecvPacketData {
seq: number
cmd: string
hex_data: string
}
function randText(len: number): string {
let text = '';
const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
for (let i = 0; i < len; i++) {
text += possible.charAt(Math.floor(Math.random() * possible.length));
}
return text;
}
export abstract class IPacketClient {
protected readonly context: PacketContext;
protected readonly cb = new LRUCache<string, (json: RecvPacketData) => Promise<void>>(500); // trace_id-type callback
logStack: LogStack;
available: boolean = false;
protected constructor(context: PacketContext, logStack: LogStack) {
this.context = context;
this.logStack = logStack;
}
abstract check(): boolean;
abstract init(pid: number, recv: string, send: string): Promise<void>;
abstract sendCommandImpl(cmd: string, data: string, trace_id: string): void;
private async registerCallback(trace_id: string, type: string, callback: (json: RecvPacketData) => Promise<void>): Promise<void> {
this.cb.put(createHash('md5').update(trace_id).digest('hex') + type, callback);
}
private async sendCommand(cmd: string, data: string, trace_id: string, rsp: boolean = false, timeout: number = 20000, sendcb: (json: RecvPacketData) => void = () => {
}): Promise<RecvPacketData> {
return new Promise<RecvPacketData>((resolve, reject) => {
if (!this.available) {
reject(new Error('packetBackend 当前不可用!'));
}
const timeoutHandle = setTimeout(() => {
reject(new Error(`sendCommand timed out after ${timeout} ms for ${cmd} with trace_id ${trace_id}`));
}, timeout);
this.registerCallback(trace_id, 'send', async (json: RecvPacketData) => {
sendcb(json);
if (!rsp) {
clearTimeout(timeoutHandle);
resolve(json);
}
});
if (rsp) {
this.registerCallback(trace_id, 'recv', async (json: RecvPacketData) => {
clearTimeout(timeoutHandle);
resolve(json);
});
}
this.sendCommandImpl(cmd, data, trace_id);
});
}
async sendPacket(cmd: string, data: PacketHexStr, rsp = false): Promise<RecvPacketData> {
const md5 = crypto.createHash('md5').update(data).digest('hex');
const trace_id = (randText(4) + md5 + data).slice(0, data.length / 2);
return this.sendCommand(cmd, data, trace_id, rsp, 20000, async () => {
await this.context.napcore.sendSsoCmdReqByContend(cmd, trace_id);
});
}
async sendOidbPacket(pkt: OidbPacket, rsp = false): Promise<RecvPacketData> {
return this.sendPacket(pkt.cmd, pkt.data, rsp);
}
}

View File

@@ -0,0 +1,66 @@
import { createHash } from "crypto";
import path, { dirname } from "path";
import { fileURLToPath } from "url";
import fs from "fs";
import { IPacketClient } from "@/core/packet/client/baseClient";
import { constants } from "node:os";
import { LRUCache } from "@/common/lru-cache";
import { PacketContext } from "@/core/packet/context/packetContext";
import { LogStack } from "@/core/packet/context/clientContext";
// 0 send 1 recv
export interface NativePacketExportType {
InitHook?: (send: string, recv: string, callback: (type: number, uin: string, cmd: string, seq: number, hex_data: string) => void) => boolean;
SendPacket?: (cmd: string, data: string, trace_id: string) => void;
}
export class NativePacketClient extends IPacketClient {
private readonly supportedPlatforms = ['win32.x64', 'linux.x64', 'linux.arm64', 'darwin.x64', 'darwin.arm64'];
private readonly MoeHooExport: { exports: NativePacketExportType } = { exports: {} };
private readonly sendEvent = new LRUCache<number, string>(500); // seq->trace_id
constructor(context: PacketContext, logStack: LogStack) {
super(context, logStack);
}
check(): boolean {
const platform = process.platform + '.' + process.arch;
if (!this.supportedPlatforms.includes(platform)) {
this.logStack.pushLogWarn(`NativePacketClient: 不支持的平台: ${platform}`);
return false;
}
const moehoo_path = path.join(dirname(fileURLToPath(import.meta.url)), './moehoo/MoeHoo.' + platform + '.node');
if (!fs.existsSync(moehoo_path)) {
this.logStack.pushLogWarn(`NativePacketClient: 缺失运行时文件: ${moehoo_path}`);
return false;
}
return true;
}
async init(pid: number, recv: string, send: string): Promise<void> {
const platform = process.platform + '.' + process.arch;
const moehoo_path = path.join(dirname(fileURLToPath(import.meta.url)), './moehoo/MoeHoo.' + platform + '.node');
process.dlopen(this.MoeHooExport, moehoo_path, constants.dlopen.RTLD_LAZY);
this.MoeHooExport.exports.InitHook?.(send, recv, (type: number, uin: string, cmd: string, seq: number, hex_data: string) => {
const trace_id = createHash('md5').update(Buffer.from(hex_data, 'hex')).digest('hex');
if (type === 0 && this.cb.get(trace_id + 'recv')) {
//此时为send 提取seq
this.sendEvent.put(seq, trace_id);
}
if (type === 1 && this.sendEvent.get(seq)) {
//此时为recv 调用callback
const trace_id = this.sendEvent.get(seq);
const callback = this.cb.get(trace_id + 'recv');
// console.log('callback:', callback, trace_id);
callback?.({ seq, cmd, hex_data });
}
});
this.available = true;
}
sendCommandImpl(cmd: string, data: string, trace_id: string): void {
const trace_id_md5 = createHash('md5').update(trace_id).digest('hex');
this.MoeHooExport.exports.SendPacket?.(cmd, data, trace_id_md5);
this.cb.get(trace_id_md5 + 'send')?.({ seq: 0, cmd, hex_data: '' });
}
}

View File

@@ -0,0 +1,112 @@
import { Data, WebSocket, ErrorEvent } from "ws";
import { IPacketClient, RecvPacket } from "@/core/packet/client/baseClient";
import { PacketContext } from "@/core/packet/context/packetContext";
import { LogStack } from "@/core/packet/context/clientContext";
export class WsPacketClient extends IPacketClient {
private websocket: WebSocket | null = null;
private reconnectAttempts: number = 0;
private readonly maxReconnectAttempts: number = 60; // 现在暂时不可配置
private readonly clientUrl: string;
private readonly clientUrlWrap: (url: string) => string = (url: string) => `ws://${url}/ws`;
private isInitialized: boolean = false;
private initPayload: { pid: number, recv: string, send: string } | null = null;
constructor(context: PacketContext, logStack: LogStack) {
super(context, logStack);
this.clientUrl = this.context.napcore.config.packetServer
? this.clientUrlWrap(this.context.napcore.config.packetServer)
: this.clientUrlWrap('127.0.0.1:8083');
}
check(): boolean {
if (!this.context.napcore.config.packetServer) {
this.logStack.pushLogWarn(`wsPacketClient 未配置服务器地址`);
return false;
}
return true;
}
async init(pid: number, recv: string, send: string): Promise<void> {
this.initPayload = { pid, recv, send };
await this.connectWithRetry();
}
sendCommandImpl(cmd: string, data: string, trace_id: string): void {
if (this.websocket && this.websocket.readyState === WebSocket.OPEN) {
this.websocket.send(JSON.stringify({
action: 'send',
cmd,
data,
trace_id
}));
} else {
this.logStack.pushLogWarn(`WebSocket 未连接,无法发送命令: ${cmd}`);
}
}
private async connectWithRetry(): Promise<void> {
while (this.reconnectAttempts < this.maxReconnectAttempts) {
try {
await this.connect();
return;
} catch (error) {
this.reconnectAttempts++;
this.logStack.pushLogWarn(`${this.reconnectAttempts}/${this.maxReconnectAttempts} 次尝试重连失败!`);
await this.delay(5000);
}
}
this.logStack.pushLogError(`wsPacketClient 在 ${this.clientUrl} 达到最大重连次数 (${this.maxReconnectAttempts})`);
throw new Error(`无法连接到 WebSocket 服务器:${this.clientUrl}`);
}
private connect(): Promise<void> {
return new Promise((resolve, reject) => {
this.websocket = new WebSocket(this.clientUrl);
this.websocket.onopen = () => {
this.available = true;
this.reconnectAttempts = 0;
this.context.logger.info(`wsPacketClient 已连接到 ${this.clientUrl}`);
if (!this.isInitialized && this.initPayload) {
this.websocket!.send(JSON.stringify({
action: 'init',
...this.initPayload
}));
this.isInitialized = true;
}
resolve();
};
this.websocket.onclose = () => {
this.available = false;
this.context.logger.warn(`WebSocket 连接关闭,尝试重连...`);
reject(new Error('WebSocket 连接关闭'));
};
this.websocket.onmessage = (event) => this.handleMessage(event.data).catch(err => {
this.context.logger.error(`处理消息时出错: ${err}`);
});
this.websocket.onerror = (event: ErrorEvent) => {
this.available = false;
this.context.logger.error(`WebSocket 出错: ${event.message}`);
this.websocket?.close();
reject(new Error(`WebSocket 出错: ${event.message}`));
};
});
}
private delay(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
private async handleMessage(message: Data): Promise<void> {
try {
const json: RecvPacket = JSON.parse(message.toString());
const trace_id_md5 = json.trace_id_md5;
const action = json?.type ?? 'init';
const event = this.cb.get(`${trace_id_md5}${action}`);
if (event) await event(json.data);
} catch (error) {
this.context.logger.error(`解析ws消息时出错: ${(error as Error).message}`);
}
}
}

View File

@@ -0,0 +1,31 @@
import { PacketContext } from "@/core/packet/context/packetContext";
import { NapCatCore } from "@/core";
export class PacketClientSession {
private readonly context: PacketContext;
constructor(core: NapCatCore) {
this.context = new PacketContext(core);
}
init(pid: number, recv: string, send: string): Promise<void> {
return this.context.client.init(pid, recv, send);
}
get clientLogStack() {
return this.context.client.clientLogStack;
}
get available() {
return this.context.client.available;
}
get operation() {
return this.context.operation;
}
// work: global message element adapter (?
get msgConverter() {
return this.context.msgConverter;
}
}

View File

@@ -0,0 +1,126 @@
import { PacketContext } from "@/core/packet/context/packetContext";
import { IPacketClient } from "@/core/packet/client/baseClient";
import { NativePacketClient } from "@/core/packet/client/nativeClient";
import { WsPacketClient } from "@/core/packet/client/wsClient";
import { OidbPacket } from "@/core/packet/transformer/base";
import { PacketLogger } from "@/core/packet/context/loggerContext";
type clientPriority = {
[key: number]: (context: PacketContext, logStack: LogStack) => IPacketClient;
}
const clientPriority: clientPriority = {
10: (context: PacketContext, logStack: LogStack) => new NativePacketClient(context, logStack),
1: (context: PacketContext, logStack: LogStack) => new WsPacketClient(context, logStack),
};
export class LogStack {
private stack: string[] = [];
private readonly logger: PacketLogger;
constructor(logger: PacketLogger) {
this.logger = logger;
}
push(msg: string) {
this.stack.push(msg);
}
pushLogInfo(msg: string) {
this.logger.info(msg);
this.stack.push(`${new Date().toISOString()} [INFO] ${msg}`);
}
pushLogWarn(msg: string) {
this.logger.warn(msg);
this.stack.push(`${new Date().toISOString()} [WARN] ${msg}`);
}
pushLogError(msg: string) {
this.logger.error(msg);
this.stack.push(`${new Date().toISOString()} [ERROR] ${msg}`);
}
clear() {
this.stack = [];
}
content() {
return this.stack.join('\n');
}
}
export class PacketClientContext {
private readonly context: PacketContext;
private readonly logStack: LogStack;
private readonly _client: IPacketClient;
constructor(context: PacketContext) {
this.context = context;
this.logStack = new LogStack(context.logger);
this._client = this.newClient();
}
get available(): boolean {
return this._client.available;
}
get clientLogStack(): string {
return this._client.logStack.content();
}
async init(pid: number, recv: string, send: string): Promise<void> {
await this._client.init(pid, recv, send);
}
async sendOidbPacket<T extends boolean = false>(pkt: OidbPacket, rsp?: T): Promise<T extends true ? Buffer : void> {
const raw = await this._client.sendOidbPacket(pkt, rsp);
return (rsp ? Buffer.from(raw.hex_data, "hex") : undefined) as T extends true ? Buffer : void;
}
private newClient(): IPacketClient {
const prefer = this.context.napcore.config.packetBackend;
let client: IPacketClient | null;
switch (prefer) {
case "native":
this.context.logger.info("使用指定的 NativePacketClient 作为后端");
client = new NativePacketClient(this.context, this.logStack);
break;
case "frida":
this.context.logger.info("[Core] [Packet] 使用指定的 FridaPacketClient 作为后端");
client = new WsPacketClient(this.context, this.logStack);
break;
case "auto":
case undefined:
client = this.judgeClient();
break;
default:
this.context.logger.error(`未知的PacketBackend ${prefer},请检查配置文件!`);
client = null;
}
if (!client?.check()) {
throw new Error("[Core] [Packet] 无可用的后端NapCat.Packet将不会加载");
}
if (!client) {
throw new Error("[Core] [Packet] 后端异常NapCat.Packet将不会加载");
}
return client;
}
private judgeClient(): IPacketClient {
const sortedClients = Object.entries(clientPriority)
.map(([priority, clientFactory]) => {
const client = clientFactory(this.context, this.logStack);
const score = +priority * +client.check();
return { client, score };
})
.filter(({ score }) => score > 0)
.sort((a, b) => b.score - a.score);
const selectedClient = sortedClients[0]?.client;
if (!selectedClient) {
throw new Error("[Core] [Packet] 无可用的后端NapCat.Packet将不会加载");
}
this.context.logger.info(`自动选择 ${selectedClient.constructor.name} 作为后端`);
return selectedClient;
}
}

View File

@@ -0,0 +1,35 @@
import { LogLevel, LogWrapper } from "@/common/log";
import { PacketContext } from "@/core/packet/context/packetContext";
// work: check bind?
export class PacketLogger {
private readonly napLogger: LogWrapper;
constructor(context: PacketContext) {
this.napLogger = context.napcore.logger;
}
private _log(level: LogLevel, ...msg: any[]): void {
this.napLogger._log(level, "[Core] [Packet] " + msg);
}
debug(...msg: any[]): void {
this._log(LogLevel.DEBUG, msg);
}
info(...msg: any[]): void {
this._log(LogLevel.INFO, msg);
}
warn(...msg: any[]): void {
this._log(LogLevel.WARN, msg);
}
error(...msg: any[]): void {
this._log(LogLevel.ERROR, msg);
}
fatal(...msg: any[]): void {
this._log(LogLevel.FATAL, msg);
}
}

View File

@@ -0,0 +1,36 @@
import { NapCatCore } from "@/core";
export interface NapCoreCompatBasicInfo {
readonly uin: number;
readonly uid: string;
readonly uin2uid: (uin: number) => Promise<string>;
readonly uid2uin: (uid: string) => Promise<number>;
readonly sendSsoCmdReqByContend: (cmd: string, trace_id: string) => Promise<void>;
}
export class NapCoreContext {
private readonly core: NapCatCore;
constructor(core: NapCatCore) {
this.core = core;
}
get logger() {
return this.core.context.logger;
}
get basicInfo() {
return {
uin: +this.core.selfInfo.uin,
uid: this.core.selfInfo.uid,
uin2uid: (uin: number) => this.core.apis.UserApi.getUidByUinV2(String(uin)).then(res => res ?? ''),
uid2uin: (uid: string) => this.core.apis.UserApi.getUinByUidV2(uid).then(res => +res),
} as NapCoreCompatBasicInfo;
}
get config() {
return this.core.configLoader.configData;
}
sendSsoCmdReqByContend = (cmd: string, trace_id: string) => this.core.context.session.getMsgService().sendSsoCmdReqByContend(cmd, trace_id);
}

View File

@@ -0,0 +1,164 @@
import * as crypto from 'crypto';
import { PacketContext } from "@/core/packet/context/packetContext";
import * as trans from "@/core/packet/transformer";
import { PacketMsg } from "@/core/packet/message/message";
import {
PacketMsgFileElement,
PacketMsgPicElement,
PacketMsgPttElement,
PacketMsgVideoElement
} from "@/core/packet/message/element";
import { ChatType } from "@/core";
import { MiniAppRawData, MiniAppReqParams } from "@/core/packet/entities/miniApp";
import { AIVoiceChatType } from "@/core/packet/entities/aiChat";
import { NapProtoDecodeStructType, NapProtoEncodeStructType } from "@napneko/nap-proto-core";
import { IndexNode, MsgInfo } from "@/core/packet/transformer/proto";
export class PacketOperationContext {
private readonly context: PacketContext;
constructor(context: PacketContext) {
this.context = context;
}
async GroupPoke(groupUin: number, uin: number) {
const req = trans.SendPoke.build(groupUin, uin);
await this.context.client.sendOidbPacket(req);
}
async FriendPoke(uin: number) {
const req = trans.SendPoke.build(uin);
await this.context.client.sendOidbPacket(req);
}
async FetchRkey() {
const req = trans.FetchRkey.build();
const resp = await this.context.client.sendOidbPacket(req, true);
const res = trans.FetchRkey.parse(resp);
return res.data.rkeyList;
}
async GroupSign(groupUin: number) {
const req = trans.GroupSign.build(this.context.napcore.basicInfo.uin, groupUin);
await this.context.client.sendOidbPacket(req);
}
async GetStrangerStatus(uin: number) {
let status = 0;
try {
const req = trans.GetStrangerInfo.build(uin);
const resp = await this.context.client.sendOidbPacket(req, true);
const res = trans.GetStrangerInfo.parse(resp);
const extBigInt = BigInt(res.data.status.value);
if (extBigInt <= 10n) {
return { status: Number(extBigInt) * 10, ext_status: 0 };
}
status = Number((extBigInt & 0xff00n) + ((extBigInt >> 16n) & 0xffn));
return { status: 10, ext_status: status };
} catch (e) {
return undefined;
}
}
async SetGroupSpecialTitle(groupUin: number, uid: string, tittle: string) {
const req = trans.SetSpecialTitle.build(groupUin, uid, tittle);
await this.context.client.sendOidbPacket(req);
}
async UploadResources(msg: PacketMsg[], groupUin: number = 0) {
const reqList = [];
for (const m of msg) {
for (const e of m.msg) {
if (e instanceof PacketMsgPicElement) {
reqList.push(this.context.highway.uploadImage({
chatType: groupUin ? ChatType.KCHATTYPEGROUP : ChatType.KCHATTYPEC2C,
peerUid: groupUin ? String(groupUin) : this.context.napcore.basicInfo.uid
}, e));
}
if (e instanceof PacketMsgVideoElement) {
reqList.push(this.context.highway.uploadVideo({
chatType: groupUin ? ChatType.KCHATTYPEGROUP : ChatType.KCHATTYPEC2C,
peerUid: groupUin ? String(groupUin) : this.context.napcore.basicInfo.uid
}, e));
}
if (e instanceof PacketMsgPttElement) {
reqList.push(this.context.highway.uploadPtt({
chatType: groupUin ? ChatType.KCHATTYPEGROUP : ChatType.KCHATTYPEC2C,
peerUid: groupUin ? String(groupUin) : this.context.napcore.basicInfo.uid
}, e));
}
if (e instanceof PacketMsgFileElement) {
reqList.push(this.context.highway.uploadFile({
chatType: groupUin ? ChatType.KCHATTYPEGROUP : ChatType.KCHATTYPEC2C,
peerUid: groupUin ? String(groupUin) : this.context.napcore.basicInfo.uid
}, e));
}
}
}
const res = await Promise.allSettled(reqList);
this.context.logger.info(`上传资源${res.length}个,失败${res.filter(r => r.status === 'rejected').length}`);
res.forEach((result, index) => {
if (result.status === 'rejected') {
this.context.logger.error(`上传第${index + 1}个资源失败:${result.reason.stack}`);
}
});
}
async UploadForwardMsg(msg: PacketMsg[], groupUin: number = 0) {
await this.UploadResources(msg, groupUin);
const req = trans.UploadForwardMsg.build(this.context.napcore.basicInfo.uid, msg, groupUin);
const resp = await this.context.client.sendOidbPacket(req, true);
const res = trans.UploadForwardMsg.parse(resp);
return res.result.resId;
}
async GetGroupFileUrl(groupUin: number, fileUUID: string) {
const req = trans.DownloadGroupFile.build(groupUin, fileUUID);
const resp = await this.context.client.sendOidbPacket(req, true);
const res = trans.DownloadGroupFile.parse(resp);
return `https://${res.download.downloadDns}/ftn_handler/${Buffer.from(res.download.downloadUrl).toString('hex')}/?fname=`;
}
async GetGroupPttUrl(groupUin: number, node: NapProtoEncodeStructType<typeof IndexNode>) {
const req = trans.DownloadGroupPtt.build(groupUin, node);
const resp = await this.context.client.sendOidbPacket(req, true);
const res = trans.DownloadGroupPtt.parse(resp);
return `https://${res.download.info.domain}${res.download.info.urlPath}${res.download.rKeyParam}`;
}
async GetMiniAppAdaptShareInfo(param: MiniAppReqParams) {
const req = trans.GetMiniAppAdaptShareInfo.build(param);
const resp = await this.context.client.sendOidbPacket(req, true);
const res = trans.GetMiniAppAdaptShareInfo.parse(resp);
return JSON.parse(res.content.jsonContent) as MiniAppRawData;
}
async FetchAiVoiceList(groupUin: number, chatType: AIVoiceChatType) {
const req = trans.FetchAiVoiceList.build(groupUin, chatType);
const resp = await this.context.client.sendOidbPacket(req, true);
const res = trans.FetchAiVoiceList.parse(resp);
if (!res.content) return null;
return res.content.map((item) => {
return {
category: item.category,
voices: item.voices
};
});
}
async GetAiVoice(groupUin: number, voiceId: string, text: string, chatType: AIVoiceChatType): Promise<NapProtoDecodeStructType<typeof MsgInfo>> {
let reqTime = 0;
const reqMaxTime = 30;
const sessionId = crypto.randomBytes(4).readUInt32BE(0);
while (true) {
if (reqTime >= reqMaxTime) {
throw new Error(`sendAiVoiceChatReq failed after ${reqMaxTime} times`);
}
reqTime++;
const req = trans.GetAiVoice.build(groupUin, voiceId, text, sessionId, chatType);
const resp = await this.context.client.sendOidbPacket(req, true);
const res = trans.GetAiVoice.parse(resp);
if (!res.msgInfo) continue;
return res.msgInfo;
}
}
}

View File

@@ -0,0 +1,25 @@
import { PacketHighwayContext } from "@/core/packet/highway/highwayContext";
import { NapCatCore } from "@/core";
import { PacketLogger } from "@/core/packet/context/loggerContext";
import { NapCoreContext } from "@/core/packet/context/napCoreContext";
import { PacketClientContext } from "@/core/packet/context/clientContext";
import { PacketOperationContext } from "@/core/packet/context/operationContext";
import { PacketMsgConverter } from "@/core/packet/message/converter";
export class PacketContext {
readonly napcore: NapCoreContext;
readonly logger: PacketLogger;
readonly client: PacketClientContext;
readonly highway: PacketHighwayContext;
readonly msgConverter: PacketMsgConverter;
readonly operation: PacketOperationContext;
constructor(core: NapCatCore) {
this.napcore = new NapCoreContext(core);
this.logger = new PacketLogger(this);
this.client = new PacketClientContext(this);
this.highway = new PacketHighwayContext(this);
this.msgConverter = new PacketMsgConverter();
this.operation = new PacketOperationContext(this);
}
}

View File

@@ -0,0 +1,16 @@
export enum AIVoiceChatType {
Unknown = 0,
Sound = 1,
Sing = 2
}
export interface AIVoiceItem {
voiceId: string;
voiceDisplayName: string;
voiceExampleUrl: string;
}
export interface AIVoiceItemList {
category: string;
voices: AIVoiceItem[];
}

View File

@@ -0,0 +1,79 @@
export interface MiniAppReqCustomParams {
title: string;
desc: string;
picUrl: string;
jumpUrl: string;
}
export interface MiniAppReqTemplateParams {
sdkId: string;
appId: string;
scene: number;
iconUrl: string;
templateType: number;
businessType: number;
verType: number;
shareType: number;
versionId: string;
withShareTicket: number;
}
export interface MiniAppReqParams extends MiniAppReqCustomParams, MiniAppReqTemplateParams {}
export interface MiniAppData {
ver: string;
prompt: string;
config: Config;
app: string;
view: string;
meta: MetaData;
miniappShareOrigin: number;
miniappOpenRefer: string;
}
export interface MiniAppRawData {
appName: string;
appView: string;
ver: string;
desc: string;
prompt: string;
metaData: MetaData;
config: Config;
}
interface Config {
type: string;
width: number;
height: number;
forward: number;
autoSize: number;
ctime: number;
token: string;
}
interface Host {
uin: number;
nick: string;
}
interface Detail {
appid: string;
appType: number;
title: string;
desc: string;
icon: string;
preview: string;
url: string;
scene: number;
host: Host;
shareTemplateId: string;
shareTemplateData: Record<string, unknown>;
showLittleTail: string;
gamePoints: string;
gamePointsUrl: string;
shareOrigin: number;
}
interface MetaData {
detail_1: Detail;
}

View File

@@ -0,0 +1,73 @@
import * as stream from 'node:stream';
import { ReadStream } from "node:fs";
import { HighwayTcpUploader } from "@/core/packet/highway/uploader/highwayTcpUploader";
import { HighwayHttpUploader } from "@/core/packet/highway/uploader/highwayHttpUploader";
import { PacketHighwaySig } from "@/core/packet/highway/highwayContext";
import { PacketLogger } from "@/core/packet/context/loggerContext";
export interface PacketHighwayTrans {
uin: string;
cmd: number;
command: string;
data: stream.Readable;
sum: Uint8Array;
size: number;
ticket: Uint8Array;
loginSig?: Uint8Array;
ext: Uint8Array;
encrypt: boolean;
timeout?: number;
server: string;
port: number;
}
export class PacketHighwayClient {
sig: PacketHighwaySig;
server: string = 'htdata3.qq.com';
port: number = 80;
logger: PacketLogger;
constructor(sig: PacketHighwaySig, logger: PacketLogger, server: string = 'htdata3.qq.com', port: number = 80) {
this.sig = sig;
this.logger = logger;
}
changeServer(server: string, port: number) {
this.server = server;
this.port = port;
}
private buildDataUpTrans(cmd: number, data: ReadStream, fileSize: number, md5: Uint8Array, extendInfo: Uint8Array, timeout: number = 1200): PacketHighwayTrans {
return {
uin: this.sig.uin,
cmd: cmd,
command: 'PicUp.DataUp',
data: data,
sum: md5,
size: fileSize,
ticket: this.sig.sigSession!,
ext: extendInfo,
encrypt: false,
timeout: timeout,
server: this.server,
port: this.port,
} as PacketHighwayTrans;
}
async upload(cmd: number, data: ReadStream, fileSize: number, md5: Uint8Array, extendInfo: Uint8Array): Promise<void> {
const trans = this.buildDataUpTrans(cmd, data, fileSize, md5, extendInfo);
try {
const tcpUploader = new HighwayTcpUploader(trans, this.logger);
await tcpUploader.upload();
} catch (e) {
this.logger.error(`[Highway] upload failed: ${e}, fallback to http upload`);
try {
const httpUploader = new HighwayHttpUploader(trans, this.logger);
await httpUploader.upload();
} catch (e) {
this.logger.error(`[Highway] http upload failed: ${e}`);
throw e;
}
}
}
}

View File

@@ -0,0 +1,23 @@
import assert from "node:assert";
export class Frame{
static pack(head: Buffer, body: Buffer): Buffer {
const totalLength = 9 + head.length + body.length + 1;
const buffer = Buffer.allocUnsafe(totalLength);
buffer[0] = 0x28;
buffer.writeUInt32BE(head.length, 1);
buffer.writeUInt32BE(body.length, 5);
head.copy(buffer, 9);
body.copy(buffer, 9 + head.length);
buffer[totalLength - 1] = 0x29;
return buffer;
}
static unpack(frame: Buffer): [Buffer, Buffer] {
assert(frame[0] === 0x28 && frame[frame.length - 1] === 0x29, 'Invalid frame!');
const headLen = frame.readUInt32BE(1);
const bodyLen = frame.readUInt32BE(5);
// assert(frame.length === 9 + headLen + bodyLen + 1, `Frame ${frame.toString('hex')} length does not match head and body lengths!`);
return [frame.subarray(9, 9 + headLen), frame.subarray(9 + headLen, 9 + headLen + bodyLen)];
}
}

View File

@@ -0,0 +1,535 @@
import { PacketHighwayClient } from "@/core/packet/highway/client";
import { PacketContext } from "@/core/packet/context/packetContext";
import { PacketLogger } from "@/core/packet/context/loggerContext";
import FetchSessionKey from "@/core/packet/transformer/highway/FetchSessionKey";
import { int32ip2str, oidbIpv4s2HighwayIpv4s } from "@/core/packet/highway/utils";
import {
PacketMsgFileElement,
PacketMsgPicElement,
PacketMsgPttElement,
PacketMsgVideoElement
} from "@/core/packet/message/element";
import { ChatType, Peer } from "@/core";
import { calculateSha1, calculateSha1StreamBytes, computeMd5AndLengthWithLimit } from "@/core/packet/utils/crypto/hash";
import UploadGroupImage from "@/core/packet/transformer/highway/UploadGroupImage";
import { NapProtoMsg } from "@napneko/nap-proto-core";
import * as proto from "@/core/packet/transformer/proto";
import * as trans from "@/core/packet/transformer";
import fs from "fs";
export const BlockSize = 1024 * 1024;
interface HighwayServerAddr {
ip: string
port: number
}
export interface PacketHighwaySig {
uin: string;
uid: string;
sigSession: Uint8Array | null
sessionKey: Uint8Array | null
serverAddr: HighwayServerAddr[]
}
export class PacketHighwayContext {
private readonly context: PacketContext;
protected sig: PacketHighwaySig;
protected logger: PacketLogger;
protected hwClient: PacketHighwayClient;
private cachedPrepareReq: Promise<void> | null = null;
constructor(context: PacketContext) {
this.context = context;
this.sig = {
uin: String(context.napcore.basicInfo.uin),
uid: context.napcore.basicInfo.uid,
sigSession: null,
sessionKey: null,
serverAddr: [],
};
this.logger = context.logger;
this.hwClient = new PacketHighwayClient(this.sig, context.logger);
}
private async checkAvailable() {
if (this.sig.sigSession === null || this.sig.sessionKey === null) {
if (this.cachedPrepareReq === null) {
this.cachedPrepareReq = this.prepareUpload().finally(() => {
this.cachedPrepareReq = null;
});
}
await this.cachedPrepareReq;
}
}
private async prepareUpload(): Promise<void> {
this.logger.debug('[Highway] on prepareUpload!');
const packet = FetchSessionKey.build();
const req = await this.context.client.sendOidbPacket(packet, true);
const rsp = FetchSessionKey.parse(req);
this.sig.sigSession = rsp.httpConn.sigSession;
this.sig.sessionKey = rsp.httpConn.sessionKey;
for (const info of rsp.httpConn.serverInfos) {
if (info.serviceType !== 1) continue;
for (const addr of info.serverAddrs) {
this.logger.debug(`[Highway PrepareUpload] server addr add: ${int32ip2str(addr.ip)}:${addr.port}`);
this.sig.serverAddr.push({
ip: int32ip2str(addr.ip),
port: addr.port
});
}
}
}
async uploadImage(peer: Peer, img: PacketMsgPicElement): Promise<void> {
await this.checkAvailable();
if (peer.chatType === ChatType.KCHATTYPEGROUP) {
await this.uploadGroupImage(+peer.peerUid, img);
} else if (peer.chatType === ChatType.KCHATTYPEC2C) {
await this.uploadC2CImage(peer.peerUid, img);
} else {
throw new Error(`[Highway] unsupported chatType: ${peer.chatType}`);
}
}
async uploadVideo(peer: Peer, video: PacketMsgVideoElement): Promise<void> {
await this.checkAvailable();
if (+(video.fileSize ?? 0) > 1024 * 1024 * 100) {
throw new Error(`[Highway] 视频文件过大: ${(+(video.fileSize ?? 0) / (1024 * 1024)).toFixed(2)} MB > 100 MB请使用文件上传`);
}
if (peer.chatType === ChatType.KCHATTYPEGROUP) {
await this.uploadGroupVideo(+peer.peerUid, video);
} else if (peer.chatType === ChatType.KCHATTYPEC2C) {
await this.uploadC2CVideo(peer.peerUid, video);
} else {
throw new Error(`[Highway] unsupported chatType: ${peer.chatType}`);
}
}
async uploadPtt(peer: Peer, ptt: PacketMsgPttElement): Promise<void> {
await this.checkAvailable();
if (peer.chatType === ChatType.KCHATTYPEGROUP) {
await this.uploadGroupPtt(+peer.peerUid, ptt);
} else if (peer.chatType === ChatType.KCHATTYPEC2C) {
await this.uploadC2CPtt(peer.peerUid, ptt);
} else {
throw new Error(`[Highway] unsupported chatType: ${peer.chatType}`);
}
}
async uploadFile(peer: Peer, file: PacketMsgFileElement): Promise<void> {
await this.checkAvailable();
if (peer.chatType === ChatType.KCHATTYPEGROUP) {
await this.uploadGroupFile(+peer.peerUid, file);
} else if (peer.chatType === ChatType.KCHATTYPEC2C) {
await this.uploadC2CFile(peer.peerUid, file);
} else {
throw new Error(`[Highway] unsupported chatType: ${peer.chatType}`);
}
}
private async uploadGroupImage(groupUin: number, img: PacketMsgPicElement): Promise<void> {
img.sha1 = Buffer.from(await calculateSha1(img.path)).toString('hex');
const req = UploadGroupImage.build(groupUin, img);
const resp = await this.context.client.sendOidbPacket(req, true);
const preRespData = UploadGroupImage.parse(resp);
const ukey = preRespData.upload.uKey;
if (ukey && ukey != "") {
this.logger.debug(`[Highway] uploadGroupImageReq get upload ukey: ${ukey}, need upload!`);
const index = preRespData.upload.msgInfo.msgInfoBody[0].index;
const sha1 = Buffer.from(index.info.fileSha1, 'hex');
const md5 = Buffer.from(index.info.fileHash, 'hex');
const extend = new NapProtoMsg(proto.NTV2RichMediaHighwayExt).encode({
fileUuid: index.fileUuid,
uKey: ukey,
network: {
ipv4S: oidbIpv4s2HighwayIpv4s(preRespData.upload.ipv4S)
},
msgInfoBody: preRespData.upload.msgInfo.msgInfoBody,
blockSize: BlockSize,
hash: {
fileSha1: [sha1]
}
});
await this.hwClient.upload(
1004,
fs.createReadStream(img.path, { highWaterMark: BlockSize }),
img.size,
md5,
extend
);
} else {
this.logger.debug(`[Highway] uploadGroupImageReq get upload invalid ukey ${ukey}, don't need upload!`);
}
img.msgInfo = preRespData.upload.msgInfo;
// img.groupPicExt = new NapProtoMsg(CustomFace).decode(preRespData.tcpUpload.compatQMsg)
}
private async uploadC2CImage(peerUid: string, img: PacketMsgPicElement): Promise<void> {
img.sha1 = Buffer.from(await calculateSha1(img.path)).toString('hex');
const req = trans.UploadPrivateImage.build(peerUid, img);
const resp = await this.context.client.sendOidbPacket(req, true);
const preRespData = trans.UploadPrivateImage.parse(resp);
const ukey = preRespData.upload.uKey;
if (ukey && ukey != "") {
this.logger.debug(`[Highway] uploadC2CImageReq get upload ukey: ${ukey}, need upload!`);
const index = preRespData.upload.msgInfo.msgInfoBody[0].index;
const sha1 = Buffer.from(index.info.fileSha1, 'hex');
const md5 = Buffer.from(index.info.fileHash, 'hex');
const extend = new NapProtoMsg(proto.NTV2RichMediaHighwayExt).encode({
fileUuid: index.fileUuid,
uKey: ukey,
network: {
ipv4S: oidbIpv4s2HighwayIpv4s(preRespData.upload.ipv4S)
},
msgInfoBody: preRespData.upload.msgInfo.msgInfoBody,
blockSize: BlockSize,
hash: {
fileSha1: [sha1]
}
});
await this.hwClient.upload(
1003,
fs.createReadStream(img.path, { highWaterMark: BlockSize }),
img.size,
md5,
extend
);
} else {
this.logger.debug(`[Highway] uploadC2CImageReq get upload invalid ukey ${ukey}, don't need upload!`);
}
img.msgInfo = preRespData.upload.msgInfo;
}
private async uploadGroupVideo(groupUin: number, video: PacketMsgVideoElement): Promise<void> {
if (!video.filePath || !video.thumbPath) throw new Error("video.filePath or video.thumbPath is empty");
video.fileSha1 = Buffer.from(await calculateSha1(video.filePath)).toString('hex');
video.thumbSha1 = Buffer.from(await calculateSha1(video.thumbPath)).toString('hex');
const req = trans.UploadGroupVideo.build(groupUin, video);
const resp = await this.context.client.sendOidbPacket(req, true);
const preRespData = trans.UploadGroupVideo.parse(resp);
const ukey = preRespData.upload.uKey;
if (ukey && ukey != "") {
this.logger.debug(`[Highway] uploadGroupVideoReq get upload video ukey: ${ukey}, need upload!`);
const index = preRespData.upload.msgInfo.msgInfoBody[0].index;
const md5 = Buffer.from(index.info.fileHash, 'hex');
const extend = new NapProtoMsg(proto.NTV2RichMediaHighwayExt).encode({
fileUuid: index.fileUuid,
uKey: ukey,
network: {
ipv4S: oidbIpv4s2HighwayIpv4s(preRespData.upload.ipv4S)
},
msgInfoBody: preRespData.upload.msgInfo.msgInfoBody,
blockSize: BlockSize,
hash: {
fileSha1: await calculateSha1StreamBytes(video.filePath)
}
});
await this.hwClient.upload(
1005,
fs.createReadStream(video.filePath, { highWaterMark: BlockSize }),
+video.fileSize!,
md5,
extend
);
} else {
this.logger.debug(`[Highway] uploadGroupVideoReq get upload invalid ukey ${ukey}, don't need upload!`);
}
const subFile = preRespData.upload.subFileInfos[0];
if (subFile.uKey && subFile.uKey != "") {
this.logger.debug(`[Highway] uploadGroupVideoReq get upload video thumb ukey: ${subFile.uKey}, need upload!`);
const index = preRespData.upload.msgInfo.msgInfoBody[1].index;
const md5 = Buffer.from(index.info.fileHash, 'hex');
const sha1 = Buffer.from(index.info.fileSha1, 'hex');
const extend = new NapProtoMsg(proto.NTV2RichMediaHighwayExt).encode({
fileUuid: index.fileUuid,
uKey: subFile.uKey,
network: {
ipv4S: oidbIpv4s2HighwayIpv4s(subFile.ipv4S)
},
msgInfoBody: preRespData.upload.msgInfo.msgInfoBody,
blockSize: BlockSize,
hash: {
fileSha1: [sha1]
}
});
await this.hwClient.upload(
1006,
fs.createReadStream(video.thumbPath, { highWaterMark: BlockSize }),
+video.thumbSize!,
md5,
extend
);
} else {
this.logger.debug(`[Highway] uploadGroupVideoReq get upload invalid thumb ukey ${subFile.uKey}, don't need upload!`);
}
video.msgInfo = preRespData.upload.msgInfo;
}
private async uploadC2CVideo(peerUid: string, video: PacketMsgVideoElement): Promise<void> {
if (!video.filePath || !video.thumbPath) throw new Error("video.filePath or video.thumbPath is empty");
video.fileSha1 = Buffer.from(await calculateSha1(video.filePath)).toString('hex');
video.thumbSha1 = Buffer.from(await calculateSha1(video.thumbPath)).toString('hex');
const req = trans.UploadPrivateVideo.build(peerUid, video);
const resp = await this.context.client.sendOidbPacket(req, true);
const preRespData = trans.UploadPrivateVideo.parse(resp);
const ukey = preRespData.upload.uKey;
if (ukey && ukey != "") {
this.logger.debug(`[Highway] uploadC2CVideoReq get upload video ukey: ${ukey}, need upload!`);
const index = preRespData.upload.msgInfo.msgInfoBody[0].index;
const md5 = Buffer.from(index.info.fileHash, 'hex');
const extend = new NapProtoMsg(proto.NTV2RichMediaHighwayExt).encode({
fileUuid: index.fileUuid,
uKey: ukey,
network: {
ipv4S: oidbIpv4s2HighwayIpv4s(preRespData.upload.ipv4S)
},
msgInfoBody: preRespData.upload.msgInfo.msgInfoBody,
blockSize: BlockSize,
hash: {
fileSha1: await calculateSha1StreamBytes(video.filePath)
}
});
await this.hwClient.upload(
1001,
fs.createReadStream(video.filePath, { highWaterMark: BlockSize }),
+video.fileSize!,
md5,
extend
);
} else {
this.logger.debug(`[Highway] uploadC2CVideoReq get upload invalid ukey ${ukey}, don't need upload!`);
}
const subFile = preRespData.upload.subFileInfos[0];
if (subFile.uKey && subFile.uKey != "") {
this.logger.debug(`[Highway] uploadC2CVideoReq get upload video thumb ukey: ${subFile.uKey}, need upload!`);
const index = preRespData.upload.msgInfo.msgInfoBody[1].index;
const md5 = Buffer.from(index.info.fileHash, 'hex');
const sha1 = Buffer.from(index.info.fileSha1, 'hex');
const extend = new NapProtoMsg(proto.NTV2RichMediaHighwayExt).encode({
fileUuid: index.fileUuid,
uKey: subFile.uKey,
network: {
ipv4S: oidbIpv4s2HighwayIpv4s(subFile.ipv4S)
},
msgInfoBody: preRespData.upload.msgInfo.msgInfoBody,
blockSize: BlockSize,
hash: {
fileSha1: [sha1]
}
});
await this.hwClient.upload(
1002,
fs.createReadStream(video.thumbPath, { highWaterMark: BlockSize }),
+video.thumbSize!,
md5,
extend
);
} else {
this.logger.debug(`[Highway] uploadC2CVideoReq get upload invalid thumb ukey ${subFile.uKey}, don't need upload!`);
}
video.msgInfo = preRespData.upload.msgInfo;
}
private async uploadGroupPtt(groupUin: number, ptt: PacketMsgPttElement): Promise<void> {
ptt.fileSha1 = Buffer.from(await calculateSha1(ptt.filePath)).toString('hex');
const req = trans.UploadGroupPtt.build(groupUin, ptt);
const resp = await this.context.client.sendOidbPacket(req, true);
const preRespData = trans.UploadGroupPtt.parse(resp);
const ukey = preRespData.upload.uKey;
if (ukey && ukey != "") {
this.logger.debug(`[Highway] uploadGroupPttReq get upload ptt ukey: ${ukey}, need upload!`);
const index = preRespData.upload.msgInfo.msgInfoBody[0].index;
const md5 = Buffer.from(index.info.fileHash, 'hex');
const sha1 = Buffer.from(index.info.fileSha1, 'hex');
const extend = new NapProtoMsg(proto.NTV2RichMediaHighwayExt).encode({
fileUuid: index.fileUuid,
uKey: ukey,
network: {
ipv4S: oidbIpv4s2HighwayIpv4s(preRespData.upload.ipv4S)
},
msgInfoBody: preRespData.upload.msgInfo.msgInfoBody,
blockSize: BlockSize,
hash: {
fileSha1: [sha1]
}
});
await this.hwClient.upload(
1008,
fs.createReadStream(ptt.filePath, { highWaterMark: BlockSize }),
ptt.fileSize,
md5,
extend
);
} else {
this.logger.debug(`[Highway] uploadGroupPttReq get upload invalid ukey ${ukey}, don't need upload!`);
}
ptt.msgInfo = preRespData.upload.msgInfo;
}
private async uploadC2CPtt(peerUid: string, ptt: PacketMsgPttElement): Promise<void> {
ptt.fileSha1 = Buffer.from(await calculateSha1(ptt.filePath)).toString('hex');
const req = trans.UploadPrivatePtt.build(peerUid, ptt);
const resp = await this.context.client.sendOidbPacket(req, true);
const preRespData = trans.UploadPrivatePtt.parse(resp);
const ukey = preRespData.upload.uKey;
if (ukey && ukey != "") {
this.logger.debug(`[Highway] uploadC2CPttReq get upload ptt ukey: ${ukey}, need upload!`);
const index = preRespData.upload.msgInfo.msgInfoBody[0].index;
const md5 = Buffer.from(index.info.fileHash, 'hex');
const sha1 = Buffer.from(index.info.fileSha1, 'hex');
const extend = new NapProtoMsg(proto.NTV2RichMediaHighwayExt).encode({
fileUuid: index.fileUuid,
uKey: ukey,
network: {
ipv4S: oidbIpv4s2HighwayIpv4s(preRespData.upload.ipv4S)
},
msgInfoBody: preRespData.upload.msgInfo.msgInfoBody,
blockSize: BlockSize,
hash: {
fileSha1: [sha1]
}
});
await this.hwClient.upload(
1007,
fs.createReadStream(ptt.filePath, { highWaterMark: BlockSize }),
ptt.fileSize,
md5,
extend
);
} else {
this.logger.debug(`[Highway] uploadC2CPttReq get upload invalid ukey ${ukey}, don't need upload!`);
}
ptt.msgInfo = preRespData.upload.msgInfo;
}
private async uploadGroupFile(groupUin: number, file: PacketMsgFileElement): Promise<void> {
file.isGroupFile = true;
file.fileMd5 = await computeMd5AndLengthWithLimit(file.filePath);
file.fileSha1 = await calculateSha1(file.filePath);
const req = trans.UploadGroupFile.build(groupUin, file);
const resp = await this.context.client.sendOidbPacket(req, true);
const preRespData = trans.UploadGroupFile.parse(resp);
if (!preRespData?.upload?.boolFileExist) {
this.logger.debug(`[Highway] uploadGroupFileReq file not exist, need upload!`);
const ext = new NapProtoMsg(proto.FileUploadExt).encode({
unknown1: 100,
unknown2: 1,
entry: {
busiBuff: {
senderUin: BigInt(this.sig.uin),
receiverUin: BigInt(groupUin),
groupCode: BigInt(groupUin),
},
fileEntry: {
fileSize: BigInt(file.fileSize),
md5: file.fileMd5,
md5S2: file.fileMd5,
checkKey: preRespData.upload.checkKey,
fileId: preRespData.upload.fileId,
uploadKey: preRespData.upload.fileKey,
},
clientInfo: {
clientType: 3,
appId: "100",
terminalType: 3,
clientVer: "1.1.1",
unknown: 4
},
fileNameInfo: {
fileName: file.fileName
},
host: {
hosts: [
{
url: {
host: preRespData.upload.uploadIp,
unknown: 1,
},
port: preRespData.upload.uploadPort,
}
]
}
},
unknown200: 0,
});
await this.hwClient.upload(
71,
fs.createReadStream(file.filePath, { highWaterMark: BlockSize }),
file.fileSize,
file.fileMd5,
ext
);
} else {
this.logger.debug(`[Highway] uploadGroupFileReq file exist, don't need upload!`);
}
file.fileUuid = preRespData.upload.fileId;
}
private async uploadC2CFile(peerUid: string, file: PacketMsgFileElement): Promise<void> {
file.isGroupFile = false;
file.fileMd5 = await computeMd5AndLengthWithLimit(file.filePath);
file.fileSha1 = await calculateSha1(file.filePath);
const req = await trans.UploadPrivateFile.build(this.sig.uid, peerUid, file);
const res = await this.context.client.sendOidbPacket(req, true);
const preRespData = trans.UploadPrivateFile.parse(res);
if (!preRespData.upload?.boolFileExist) {
this.logger.debug(`[Highway] uploadC2CFileReq file not exist, need upload!`);
const ext = new NapProtoMsg(proto.FileUploadExt).encode({
unknown1: 100,
unknown2: 1,
entry: {
busiBuff: {
senderUin: BigInt(this.sig.uin),
},
fileEntry: {
fileSize: BigInt(file.fileSize),
md5: file.fileMd5,
md5S2: file.fileMd5,
checkKey: file.fileSha1,
fileId: preRespData.upload?.uuid,
uploadKey: preRespData.upload?.mediaPlatformUploadKey,
},
clientInfo: {
clientType: 3,
appId: "100",
terminalType: 3,
clientVer: "1.1.1",
unknown: 4
},
fileNameInfo: {
fileName: file.fileName
},
host: {
hosts: [
{
url: {
host: preRespData.upload?.uploadIp,
unknown: 1,
},
port: preRespData.upload?.uploadPort,
}
]
}
},
unknown200: 1,
unknown3: 0
});
await this.hwClient.upload(
95,
fs.createReadStream(file.filePath, { highWaterMark: BlockSize }),
file.fileSize,
file.fileMd5,
ext
);
}
file.fileUuid = preRespData.upload?.uuid;
file.fileHash = preRespData.upload?.fileAddon;
const fileExistReq = trans.DownloadOfflineFile.build(file.fileUuid!, file.fileHash!, this.sig.uid, peerUid);
const fileExistRes = await this.context.client.sendOidbPacket(fileExistReq, true);
file._e37_800_rsp = trans.DownloadOfflineFile.parse(fileExistRes);
file._private_send_uid = this.sig.uid;
file._private_recv_uid = peerUid;
}
}

View File

@@ -0,0 +1,75 @@
import crypto from "node:crypto";
import http from "node:http";
import { NapProtoMsg } from "@napneko/nap-proto-core";
import { IHighwayUploader } from "@/core/packet/highway/uploader/highwayUploader";
import { Frame } from "@/core/packet/highway/frame";
import * as proto from "@/core/packet/transformer/proto";
export class HighwayHttpUploader extends IHighwayUploader {
async upload(): Promise<void> {
const controller = new AbortController();
const { signal } = controller;
const upload = (async () => {
let offset = 0;
for await (const chunk of this.trans.data) {
if (signal.aborted) {
throw new Error('Upload aborted due to timeout');
}
const block = chunk as Buffer;
try {
await this.uploadBlock(block, offset);
} catch (err) {
throw new Error(`[Highway] httpUpload Error uploading block at offset ${offset}: ${err}`);
}
offset += block.length;
}
})();
const timeout = this.timeout().catch((err) => {
controller.abort();
throw new Error(err.message);
});
await Promise.race([upload, timeout]);
}
private async uploadBlock(block: Buffer, offset: number): Promise<void> {
const chunkMD5 = crypto.createHash('md5').update(block).digest();
const payload = this.buildPicUpHead(offset, block.length, chunkMD5);
const frame = Frame.pack(Buffer.from(payload), block);
const resp = await this.httpPostHighwayContent(frame, `http://${this.trans.server}:${this.trans.port}/cgi-bin/httpconn?htcmd=0x6FF0087&uin=${this.trans.uin}`);
const [head, body] = Frame.unpack(resp);
const headData = new NapProtoMsg(proto.RespDataHighwayHead).decode(head);
this.logger.debug(`[Highway] httpUploadBlock: ${headData.errorCode} | ${headData.msgSegHead?.retCode} | ${headData.bytesRspExtendInfo} | ${head.toString('hex')} | ${body.toString('hex')}`);
if (headData.errorCode !== 0) throw new Error(`[Highway] httpUploadBlock failed (code=${headData.errorCode})`);
}
private async httpPostHighwayContent(frame: Buffer, serverURL: string): Promise<Buffer> {
return new Promise((resolve, reject) => {
try {
const options: http.RequestOptions = {
method: 'POST',
headers: {
'Connection': 'keep-alive',
'Accept-Encoding': 'identity',
'User-Agent': 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2)',
'Content-Length': frame.length.toString(),
},
};
const req = http.request(serverURL, options, (res) => {
const data: Buffer[] = [];
res.on('data', (chunk) => {
data.push(chunk);
});
res.on('end', () => {
resolve(Buffer.concat(data));
});
});
req.write(frame);
req.on('error', (error: Error) => {
reject(error);
});
} catch (error: unknown) {
reject(new Error((error as Error).message));
}
});
}
}

View File

@@ -0,0 +1,85 @@
import net from "node:net";
import stream from "node:stream";
import crypto from "node:crypto";
import { NapProtoMsg } from "@napneko/nap-proto-core";
import { BlockSize } from "@/core/packet/highway/highwayContext";
import { Frame } from "@/core/packet/highway/frame";
import { IHighwayUploader } from "@/core/packet/highway/uploader/highwayUploader";
import * as proto from "@/core/packet/transformer/proto";
class HighwayTcpUploaderTransform extends stream.Transform {
uploader: HighwayTcpUploader;
offset: number;
constructor(uploader: HighwayTcpUploader) {
super();
this.uploader = uploader;
this.offset = 0;
}
_transform(data: Buffer, _: BufferEncoding, callback: stream.TransformCallback) {
let chunkOffset = 0;
while (chunkOffset < data.length) {
const chunkSize = Math.min(BlockSize, data.length - chunkOffset);
const chunk = data.subarray(chunkOffset, chunkOffset + chunkSize);
const chunkMd5 = crypto.createHash('md5').update(chunk).digest();
const head = this.uploader.buildPicUpHead(this.offset, chunk.length, chunkMd5);
chunkOffset += chunk.length;
this.offset += chunk.length;
this.push(Frame.pack(Buffer.from(head), chunk));
}
callback(null);
}
}
export class HighwayTcpUploader extends IHighwayUploader {
async upload(): Promise<void> {
const controller = new AbortController();
const { signal } = controller;
const upload = new Promise<void>((resolve, reject) => {
const highwayTransForm = new HighwayTcpUploaderTransform(this);
const socket = net.connect(this.trans.port, this.trans.server, () => {
this.trans.data.pipe(highwayTransForm).pipe(socket, { end: false });
});
const handleRspHeader = (header: Buffer) => {
const rsp = new NapProtoMsg(proto.RespDataHighwayHead).decode(header);
if (rsp.errorCode !== 0) {
socket.end();
reject(new Error(`[Highway] tcpUpload failed (code=${rsp.errorCode})`));
}
const percent = ((Number(rsp.msgSegHead?.dataOffset) + Number(rsp.msgSegHead?.dataLength)) / Number(rsp.msgSegHead?.filesize)).toFixed(2);
this.logger.debug(`[Highway] tcpUpload ${rsp.errorCode} | ${percent} | ${Buffer.from(header).toString('hex')}`);
if (Number(rsp.msgSegHead?.dataOffset) + Number(rsp.msgSegHead?.dataLength) >= Number(rsp.msgSegHead?.filesize)) {
this.logger.debug('[Highway] tcpUpload finished.');
socket.end();
resolve();
}
};
socket.on('data', (chunk: Buffer) => {
if (signal.aborted) {
socket.end();
reject(new Error('Upload aborted due to timeout'));
}
const [head, _] = Frame.unpack(chunk);
handleRspHeader(head);
});
socket.on('close', () => {
this.logger.debug('[Highway] tcpUpload socket closed.');
resolve();
});
socket.on('error', (err) => {
socket.end();
reject(new Error(`[Highway] tcpUpload socket.on error: ${err}`));
});
this.trans.data.on('error', (err) => {
socket.end();
reject(new Error(`[Highway] tcpUpload readable error: ${err}`));
});
});
const timeout = this.timeout().catch((err) => {
controller.abort();
throw new Error(err.message);
});
await Promise.race([upload, timeout]);
}
}

View File

@@ -0,0 +1,63 @@
import * as tea from "@/core/packet/utils/crypto/tea";
import { NapProtoMsg } from "@napneko/nap-proto-core";
import { PacketHighwayTrans } from "@/core/packet/highway/client";
import { PacketLogger } from "@/core/packet/context/loggerContext";
import * as proto from "@/core/packet/transformer/proto";
export abstract class IHighwayUploader {
readonly trans: PacketHighwayTrans;
readonly logger: PacketLogger;
constructor(trans: PacketHighwayTrans, logger: PacketLogger) {
this.trans = trans;
this.logger = logger;
}
private encryptTransExt(key: Uint8Array) {
if (!this.trans.encrypt) return;
this.trans.ext = tea.encrypt(Buffer.from(this.trans.ext), Buffer.from(key));
}
protected timeout(): Promise<void> {
return new Promise<void>((_, reject) => {
setTimeout(() => {
reject(new Error(`[Highway] timeout after ${this.trans.timeout}s`));
}, (this.trans.timeout ?? Infinity) * 1000
);
});
}
buildPicUpHead(offset: number, bodyLength: number, bodyMd5: Uint8Array): Uint8Array {
return new NapProtoMsg(proto.ReqDataHighwayHead).encode({
msgBaseHead: {
version: 1,
uin: this.trans.uin,
command: "PicUp.DataUp",
seq: 0,
retryTimes: 0,
appId: 1600001604,
dataFlag: 16,
commandId: this.trans.cmd,
},
msgSegHead: {
serviceId: 0,
filesize: BigInt(this.trans.size),
dataOffset: BigInt(offset),
dataLength: bodyLength,
serviceTicket: this.trans.ticket,
md5: bodyMd5,
fileMd5: this.trans.sum,
cacheAddr: 0,
cachePort: 0,
},
bytesReqExtendInfo: this.trans.ext,
timestamp: BigInt(0),
msgLoginSigHead: {
uint32LoginSigType: 8,
appId: 1600001604,
}
});
}
abstract upload(): Promise<void>;
}

View File

@@ -0,0 +1,20 @@
import { NapProtoEncodeStructType } from "@napneko/nap-proto-core";
import * as proto from "@/core/packet/transformer/proto";
export const int32ip2str = (ip: number) => {
ip = ip & 0xffffffff;
return [ip & 0xff, (ip & 0xff00) >> 8, (ip & 0xff0000) >> 16, ((ip & 0xff000000) >> 24) & 0xff].join('.');
};
export const oidbIpv4s2HighwayIpv4s = (ipv4s: NapProtoEncodeStructType<typeof proto.IPv4>[]): NapProtoEncodeStructType<typeof proto.NTHighwayIPv4>[] => {
return ipv4s.map((ip) => {
return {
domain: {
isEnable: true,
ip: int32ip2str(ip.outIP ?? 0),
},
port: ip.outPort!
} as NapProtoEncodeStructType<typeof proto.NTHighwayIPv4>;
});
};

View File

@@ -0,0 +1,66 @@
import * as crypto from "crypto";
import { PushMsgBody } from "@/core/packet/transformer/proto";
import { NapProtoEncodeStructType } from "@napneko/nap-proto-core";
import { PacketMsg, PacketSendMsgElement } from "@/core/packet/message/message";
import { IPacketMsgElement, PacketMsgTextElement } from "@/core/packet/message/element";
import { SendTextElement } from "@/core";
export class PacketMsgBuilder {
protected static failBackText = new PacketMsgTextElement(
{
textElement: { content: "[该消息类型暂不支持查看]" }
} as SendTextElement
);
buildFakeMsg(selfUid: string, element: PacketMsg[]): NapProtoEncodeStructType<typeof PushMsgBody>[] {
return element.map((node): NapProtoEncodeStructType<typeof PushMsgBody> => {
const avatar = `https://q.qlogo.cn/headimg_dl?dst_uin=${node.senderUin}&spec=640&img_type=jpg`;
const msgContent = node.msg.reduceRight((acc: undefined | Uint8Array, msg: IPacketMsgElement<PacketSendMsgElement>) => {
return acc ?? msg.buildContent();
}, undefined);
const msgElement = node.msg.flatMap(msg => msg.buildElement() ?? []);
if (!msgContent && !msgElement.length) {
msgElement.push(PacketMsgBuilder.failBackText.buildElement());
}
return {
responseHead: {
fromUid: "",
fromUin: node.senderUin,
toUid: node.groupId ? undefined : selfUid,
forward: node.groupId ? undefined : {
friendName: node.senderName,
},
grp: node.groupId ? {
groupUin: node.groupId,
memberName: node.senderName,
unknown5: 2
} : undefined,
},
contentHead: {
type: node.groupId ? 82 : 9,
subType: node.groupId ? undefined : 4,
divSeq: node.groupId ? undefined : 4,
msgId: crypto.randomBytes(4).readUInt32LE(0),
sequence: crypto.randomBytes(4).readUInt32LE(0),
timeStamp: +node.time.toString().substring(0, 10),
field7: BigInt(1),
field8: 0,
field9: 0,
forward: {
field1: 0,
field2: 0,
field3: node.groupId ? 0 : 2,
unknownBase64: avatar,
avatar: avatar
}
},
body: {
richText: {
elems: msgElement
},
msgContent: msgContent,
}
};
});
}
}

View File

@@ -0,0 +1,157 @@
import {
Peer,
ChatType,
ElementType,
MessageElement,
RawMessage,
SendArkElement,
SendFaceElement,
SendFileElement,
SendMarkdownElement,
SendMarketFaceElement,
SendPicElement,
SendPttElement,
SendReplyElement,
SendStructLongMsgElement,
SendTextElement,
SendVideoElement
} from "@/core";
import {
IPacketMsgElement,
PacketMsgAtElement,
PacketMsgFaceElement,
PacketMsgFileElement,
PacketMsgLightAppElement,
PacketMsgMarkDownElement,
PacketMsgMarkFaceElement,
PacketMsgPicElement,
PacketMsgPttElement,
PacketMsgReplyElement,
PacketMsgTextElement,
PacketMsgVideoElement,
PacketMultiMsgElement
} from "@/core/packet/message/element";
import { PacketMsg, PacketSendMsgElement } from "@/core/packet/message/message";
const SupportedElementTypes = [
ElementType.TEXT,
ElementType.PIC,
ElementType.REPLY,
ElementType.FACE,
ElementType.MFACE,
ElementType.VIDEO,
ElementType.FILE,
ElementType.PTT,
ElementType.ARK,
ElementType.MARKDOWN,
ElementType.STRUCTLONGMSG
];
type SendMessageTypeElementMap = {
[ElementType.TEXT]: SendTextElement,
[ElementType.PIC]: SendPicElement,
[ElementType.FILE]: SendFileElement,
[ElementType.PTT]: SendPttElement,
[ElementType.VIDEO]: SendVideoElement,
[ElementType.FACE]: SendFaceElement,
[ElementType.REPLY]: SendReplyElement,
[ElementType.ARK]: SendArkElement,
[ElementType.MFACE]: SendMarketFaceElement,
[ElementType.STRUCTLONGMSG]: SendStructLongMsgElement,
[ElementType.MARKDOWN]: SendMarkdownElement,
};
type ElementToPacketMsgConverters = {
[K in keyof SendMessageTypeElementMap]: (
sendElement: MessageElement
) => IPacketMsgElement<SendMessageTypeElementMap[K]>;
}
export type rawMsgWithSendMsg = {
senderUin: number;
senderUid?: string;
senderName: string;
groupId?: number;
time: number;
msg: PacketSendMsgElement[]
}
// work:make it become adapter?
export class PacketMsgConverter {
private isValidElementType(type: ElementType): type is keyof ElementToPacketMsgConverters {
return SupportedElementTypes.includes(type);
}
private readonly rawToPacketMsgConverters: ElementToPacketMsgConverters = {
[ElementType.TEXT]: (element) => {
if (element.textElement?.atType) {
return new PacketMsgAtElement(element as SendTextElement);
}
return new PacketMsgTextElement(element as SendTextElement);
},
[ElementType.PIC]: (element) => {
return new PacketMsgPicElement(element as SendPicElement);
},
[ElementType.REPLY]: (element) => {
return new PacketMsgReplyElement(element as SendReplyElement);
},
[ElementType.FACE]: (element) => {
return new PacketMsgFaceElement(element as SendFaceElement);
},
[ElementType.MFACE]: (element) => {
return new PacketMsgMarkFaceElement(element as SendMarketFaceElement);
},
[ElementType.VIDEO]: (element) => {
return new PacketMsgVideoElement(element as SendVideoElement);
},
[ElementType.FILE]: (element) => {
return new PacketMsgFileElement(element as SendFileElement);
},
[ElementType.PTT]: (element) => {
return new PacketMsgPttElement(element as SendPttElement);
},
[ElementType.ARK]: (element) => {
return new PacketMsgLightAppElement(element as SendArkElement);
},
[ElementType.MARKDOWN]: (element) => {
return new PacketMsgMarkDownElement(element as SendMarkdownElement);
},
// work:check this logic, move it in arkElement?
[ElementType.STRUCTLONGMSG]: (element) => {
return new PacketMultiMsgElement(element as SendStructLongMsgElement);
}
};
rawMsgWithSendMsgToPacketMsg(msg: rawMsgWithSendMsg): PacketMsg {
return {
senderUid: msg.senderUid ?? '',
senderUin: msg.senderUin,
senderName: msg.senderName,
groupId: msg.groupId,
time: msg.time,
msg: msg.msg.map((element) => {
if (!this.isValidElementType(element.elementType)) return null;
return this.rawToPacketMsgConverters[element.elementType](element as MessageElement);
}).filter((e) => e !== null)
};
}
rawMsgToPacketMsg(msg: RawMessage, ctxPeer: Peer): PacketMsg {
return {
seq: +msg.msgSeq,
groupId: ctxPeer.chatType === ChatType.KCHATTYPEGROUP ? +msg.peerUid : undefined,
senderUid: msg.senderUid,
senderUin: +msg.senderUin,
senderName: msg.sendMemberName && msg.sendMemberName !== ''
? msg.sendMemberName
: msg.sendNickName && msg.sendNickName !== ''
? msg.sendNickName
: "QQ用户",
time: +msg.msgTime,
msg: msg.elements.map((element) => {
if (!this.isValidElementType(element.elementType)) return null;
return this.rawToPacketMsgConverters[element.elementType](element);
}).filter((e) => e !== null)
};
}
}

View File

@@ -0,0 +1,535 @@
import * as zlib from "node:zlib";
import { NapProtoEncodeStructType, NapProtoMsg } from "@napneko/nap-proto-core";
import {
CustomFace,
Elem,
MarkdownData,
MentionExtra,
NotOnlineImage,
QBigFaceExtra,
QSmallFaceExtra,
MsgInfo,
OidbSvcTrpcTcp0XE37_800Response,
FileExtra,
GroupFileExtra
} from "@/core/packet/transformer/proto";
import {
AtType,
PicType,
SendArkElement,
SendFaceElement,
SendFileElement,
SendMarkdownElement,
SendMarketFaceElement,
SendPicElement,
SendPttElement,
SendReplyElement,
SendStructLongMsgElement,
SendTextElement,
SendVideoElement
} from "@/core";
import { ForwardMsgBuilder } from "@/common/forward-msg-builder";
import { PacketMsg, PacketSendMsgElement } from "@/core/packet/message/message";
// raw <-> packet
// work:SendStructLongMsgElement
export abstract class IPacketMsgElement<T extends PacketSendMsgElement> {
protected constructor(rawElement: T) {
}
get valid(): boolean {
return true;
}
buildContent(): Uint8Array | undefined {
return undefined;
}
buildElement(): NapProtoEncodeStructType<typeof Elem>[] {
return [];
}
toPreview(): string {
return '[暂不支持该消息类型喵~]';
}
}
export class PacketMsgTextElement extends IPacketMsgElement<SendTextElement> {
text: string;
constructor(element: SendTextElement) {
super(element);
this.text = element.textElement.content;
}
buildElement(): NapProtoEncodeStructType<typeof Elem>[] {
return [{
text: {
str: this.text
}
}];
}
toPreview(): string {
return this.text;
}
}
export class PacketMsgAtElement extends PacketMsgTextElement {
targetUid: string;
atAll: boolean;
constructor(element: SendTextElement) {
super(element);
this.targetUid = element.textElement.atNtUid;
this.atAll = element.textElement.atType === AtType.atAll;
}
buildElement(): NapProtoEncodeStructType<typeof Elem>[] {
return [{
text: {
str: this.text,
pbReserve: new NapProtoMsg(MentionExtra).encode({
type: this.atAll ? 1 : 2,
uin: 0,
field5: 0,
uid: this.targetUid,
}
)
}
}];
}
}
export class PacketMsgReplyElement extends IPacketMsgElement<SendReplyElement> {
messageId: bigint;
messageSeq: number;
messageClientSeq: number;
targetUin: number;
targetUid: string;
time: number;
elems: PacketMsg[];
constructor(element: SendReplyElement) {
super(element);
this.messageId = BigInt(element.replyElement.replayMsgId ?? 0);
this.messageSeq = +(element.replyElement.replayMsgSeq ?? 0);
this.messageClientSeq = +(element.replyElement.replyMsgClientSeq ?? 0);
this.targetUin = +(element.replyElement.senderUin ?? 0);
this.targetUid = element.replyElement.senderUidStr ?? '';
this.time = +(element.replyElement.replyMsgTime ?? 0);
this.elems = []; // work:in replyElement.sourceMsgTextElems
}
get isGroupReply(): boolean {
return this.messageClientSeq !== 0;
}
buildElement(): NapProtoEncodeStructType<typeof Elem>[] {
return [{
srcMsg: {
origSeqs: [this.isGroupReply ? this.messageClientSeq : this.messageSeq],
senderUin: BigInt(this.targetUin),
time: this.time,
elems: [], // work:in replyElement.sourceMsgTextElems
pbReserve: {
messageId: this.messageId,
},
toUin: BigInt(0),
}
}, {
text: this.isGroupReply ? {
str: 'nya~',
pbReserve: new NapProtoMsg(MentionExtra).encode({
type: this.targetUin === 0 ? 1 : 2,
uin: 0,
field5: 0,
uid: String(this.targetUid),
}),
} : undefined,
}];
}
toPreview(): string {
return "[回复消息]";
}
}
export class PacketMsgFaceElement extends IPacketMsgElement<SendFaceElement> {
faceId: number;
isLargeFace: boolean;
constructor(element: SendFaceElement) {
super(element);
this.faceId = element.faceElement.faceIndex;
this.isLargeFace = element.faceElement.faceType === 3;
}
buildElement(): NapProtoEncodeStructType<typeof Elem>[] {
if (this.isLargeFace) {
return [{
commonElem: {
serviceType: 37,
pbElem: new NapProtoMsg(QBigFaceExtra).encode({
aniStickerPackId: "1",
aniStickerId: "8",
faceId: this.faceId,
field4: 1,
field6: "",
preview: "",
field9: 1
}),
businessType: 1
}
}];
} else if (this.faceId < 260) {
return [{
face: {
index: this.faceId
}
}];
} else {
return [{
commonElem: {
serviceType: 33,
pbElem: new NapProtoMsg(QSmallFaceExtra).encode({
faceId: this.faceId,
preview: "",
preview2: ""
}),
businessType: 1
}
}];
}
}
toPreview(): string {
return "[表情]";
}
}
export class PacketMsgMarkFaceElement extends IPacketMsgElement<SendMarketFaceElement> {
emojiName: string;
emojiId: string;
emojiPackageId: number;
emojiKey: string;
constructor(element: SendMarketFaceElement) {
super(element);
this.emojiName = element.marketFaceElement.faceName;
this.emojiId = element.marketFaceElement.emojiId;
this.emojiPackageId = element.marketFaceElement.emojiPackageId;
this.emojiKey = element.marketFaceElement.key;
}
buildElement(): NapProtoEncodeStructType<typeof Elem>[] {
return [{
marketFace: {
faceName: this.emojiName,
itemType: 6,
faceInfo: 1,
faceId: Buffer.from(this.emojiId, 'hex'),
tabId: this.emojiPackageId,
subType: 3,
key: this.emojiKey,
imageWidth: 300,
imageHeight: 300,
pbReserve: {
field8: 1
}
}
}];
}
toPreview(): string {
return `${this.emojiName}`;
}
}
export class PacketMsgPicElement extends IPacketMsgElement<SendPicElement> {
path: string;
name: string;
size: number;
md5: string;
width: number;
height: number;
picType: PicType;
sha1: string | null = null;
msgInfo: NapProtoEncodeStructType<typeof MsgInfo> | null = null;
groupPicExt: NapProtoEncodeStructType<typeof CustomFace> | null = null;
c2cPicExt: NapProtoEncodeStructType<typeof NotOnlineImage> | null = null;
constructor(element: SendPicElement) {
super(element);
this.path = element.picElement.sourcePath;
this.name = element.picElement.fileName;
this.size = +element.picElement.fileSize;
this.md5 = element.picElement.md5HexStr ?? '';
this.width = element.picElement.picWidth;
this.height = element.picElement.picHeight;
this.picType = element.picElement.picType;
}
get valid(): boolean {
return !!this.msgInfo;
}
buildElement(): NapProtoEncodeStructType<typeof Elem>[] {
if (!this.msgInfo) return [];
return [{
commonElem: {
serviceType: 48,
pbElem: new NapProtoMsg(MsgInfo).encode(this.msgInfo),
businessType: 10,
}
}];
}
toPreview(): string {
return "[图片]";
}
}
export class PacketMsgVideoElement extends IPacketMsgElement<SendVideoElement> {
fileSize?: string;
filePath?: string;
thumbSize?: number;
thumbPath?: string;
fileMd5?: string;
fileSha1?: string;
thumbMd5?: string;
thumbSha1?: string;
thumbWidth?: number;
thumbHeight?: number;
msgInfo: NapProtoEncodeStructType<typeof MsgInfo> | null = null;
constructor(element: SendVideoElement) {
super(element);
this.fileSize = element.videoElement.fileSize;
this.filePath = element.videoElement.filePath;
this.thumbSize = element.videoElement.thumbSize;
this.thumbPath = element.videoElement.thumbPath?.get(0);
this.fileMd5 = element.videoElement.videoMd5;
this.thumbMd5 = element.videoElement.thumbMd5;
this.thumbWidth = element.videoElement.thumbWidth;
this.thumbHeight = element.videoElement.thumbHeight;
}
get valid(): boolean {
return !!this.msgInfo;
}
buildElement(): NapProtoEncodeStructType<typeof Elem>[] {
if (!this.msgInfo) return [];
return [{
commonElem: {
serviceType: 48,
pbElem: new NapProtoMsg(MsgInfo).encode(this.msgInfo),
businessType: 21,
}
}];
}
toPreview(): string {
return "[视频]";
}
}
export class PacketMsgPttElement extends IPacketMsgElement<SendPttElement> {
filePath: string;
fileSize: number;
fileMd5: string;
fileSha1?: string;
fileDuration: number;
msgInfo: NapProtoEncodeStructType<typeof MsgInfo> | null = null;
constructor(element: SendPttElement) {
super(element);
this.filePath = element.pttElement.filePath;
this.fileSize = +element.pttElement.fileSize; // work:cc
this.fileMd5 = element.pttElement.md5HexStr;
this.fileDuration = Math.round(element.pttElement.duration); // work:cc
}
get valid(): boolean {
return false;
}
buildElement(): NapProtoEncodeStructType<typeof Elem>[] {
return [];
// if (!this.msgInfo) return [];
// return [{
// commonElem: {
// serviceType: 48,
// pbElem: new NapProtoMsg(MsgInfo).encode(this.msgInfo),
// businessType: 22,
// }
// }];
}
toPreview(): string {
return "[语音]";
}
}
export class PacketMsgFileElement extends IPacketMsgElement<SendFileElement> {
fileName: string;
filePath: string;
fileSize: number;
fileSha1?: Uint8Array;
fileMd5?: Uint8Array;
fileUuid?: string;
fileHash?: string;
isGroupFile?: boolean;
_private_send_uid?: string;
_private_recv_uid?: string;
_e37_800_rsp?: NapProtoEncodeStructType<typeof OidbSvcTrpcTcp0XE37_800Response>;
constructor(element: SendFileElement) {
super(element);
this.fileName = element.fileElement.fileName;
this.filePath = element.fileElement.filePath;
this.fileSize = +element.fileElement.fileSize;
}
get valid(): boolean {
return this.isGroupFile || Boolean(this._e37_800_rsp);
}
buildContent(): Uint8Array | undefined {
if (this.isGroupFile || !this._e37_800_rsp) return undefined;
return new NapProtoMsg(FileExtra).encode({
file: {
fileType: 0,
fileUuid: this.fileUuid,
fileMd5: this.fileMd5,
fileName: this.fileName,
fileSize: BigInt(this.fileSize),
subcmd: 1,
dangerEvel: 0,
expireTime: Math.floor(Date.now() / 1000) + 7 * 24 * 60 * 60,
fileHash: this.fileHash,
},
field6: {
field2: {
field1: this._e37_800_rsp?.body?.field30?.field110,
fileUuid: this.fileUuid,
fileName: this.fileName,
field6: this._e37_800_rsp?.body?.field30?.field3,
field7: this._e37_800_rsp?.body?.field30?.field101,
field8: this._e37_800_rsp?.body?.field30?.field100,
timestamp1: this._e37_800_rsp?.body?.field30?.timestamp1,
fileHash: this.fileHash,
selfUid: this._private_send_uid,
destUid: this._private_recv_uid,
}
}
});
}
buildElement(): NapProtoEncodeStructType<typeof Elem>[] {
if (!this.isGroupFile) return [];
const lb = Buffer.alloc(2);
const transElemVal = new NapProtoMsg(GroupFileExtra).encode({
field1: 6,
fileName: this.fileName,
inner: {
info: {
busId: 102,
fileId: this.fileUuid,
fileSize: BigInt(this.fileSize),
fileName: this.fileName,
fileSha: this.fileSha1,
extInfoString: "",
fileMd5: this.fileMd5,
}
}
});
lb.writeUInt16BE(transElemVal.length);
return [{
transElem: {
elemType: 24,
elemValue: Buffer.concat([Buffer.from([0x01]), lb, transElemVal]) // TLV
}
}];
}
toPreview(): string {
return `[文件]${this.fileName}`;
}
}
export class PacketMsgLightAppElement extends IPacketMsgElement<SendArkElement> {
payload: string;
constructor(element: SendArkElement) {
super(element);
this.payload = element.arkElement.bytesData;
}
buildElement(): NapProtoEncodeStructType<typeof Elem>[] {
return [{
lightAppElem: {
data: Buffer.concat([
Buffer.from([0x01]),
zlib.deflateSync(Buffer.from(this.payload, 'utf-8'))
])
}
}];
}
toPreview(): string {
return "[卡片消息]";
}
}
export class PacketMsgMarkDownElement extends IPacketMsgElement<SendMarkdownElement> {
content: string;
constructor(element: SendMarkdownElement) {
super(element);
this.content = element.markdownElement.content;
}
buildElement(): NapProtoEncodeStructType<typeof Elem>[] {
return [{
commonElem: {
serviceType: 45,
pbElem: new NapProtoMsg(MarkdownData).encode({
content: this.content
}),
businessType: 1
}
}];
}
toPreview(): string {
return `[Markdown消息 ${this.content}]`;
}
}
export class PacketMultiMsgElement extends IPacketMsgElement<SendStructLongMsgElement> {
resid: string;
message: PacketMsg[];
constructor(rawElement: SendStructLongMsgElement, message?: PacketMsg[]) {
super(rawElement);
this.resid = rawElement.structLongMsgElement.resId;
this.message = message ?? [];
}
buildElement(): NapProtoEncodeStructType<typeof Elem>[] {
return [{
lightAppElem: {
data: Buffer.concat([
Buffer.from([0x01]),
zlib.deflateSync(Buffer.from(JSON.stringify(ForwardMsgBuilder.fromPacketMsg(this.resid, this.message)), 'utf-8'))
])
}
}];
}
toPreview(): string {
return "[聊天记录]";
}
}

View File

@@ -0,0 +1,15 @@
import { IPacketMsgElement } from "@/core/packet/message/element";
import { SendMessageElement, SendStructLongMsgElement } from "@/core";
export type PacketSendMsgElement = SendMessageElement | SendStructLongMsgElement
export interface PacketMsg {
seq?: number;
clientSeq?: number;
groupId?: number;
senderUid: string;
senderUin: number;
senderName: string;
time: number;
msg: IPacketMsgElement<PacketSendMsgElement>[]
}

View File

@@ -0,0 +1,26 @@
import * as proto from "@/core/packet/transformer/proto";
import { NapProtoMsg } from "@napneko/nap-proto-core";
import { OidbPacket, PacketTransformer } from "@/core/packet/transformer/base";
import OidbBase from "@/core/packet/transformer/oidb/oidbBase";
import { AIVoiceChatType } from "@/core/packet/entities/aiChat";
class FetchAiVoiceList extends PacketTransformer<typeof proto.OidbSvcTrpcTcp0X929D_0Resp> {
constructor() {
super();
}
build(groupUin: number, chatType: AIVoiceChatType): OidbPacket {
const data = new NapProtoMsg(proto.OidbSvcTrpcTcp0X929D_0).encode({
groupUin: groupUin,
chatType: chatType
});
return OidbBase.build(0x929D, 0, data);
}
parse(data: Buffer) {
const oidbBody = OidbBase.parse(data).body;
return new NapProtoMsg(proto.OidbSvcTrpcTcp0X929D_0Resp).decode(oidbBody);
}
}
export default new FetchAiVoiceList();

View File

@@ -0,0 +1,31 @@
import * as proto from "@/core/packet/transformer/proto";
import { NapProtoMsg } from "@napneko/nap-proto-core";
import { OidbPacket, PacketTransformer } from "@/core/packet/transformer/base";
import OidbBase from "@/core/packet/transformer/oidb/oidbBase";
import { AIVoiceChatType } from "@/core/packet/entities/aiChat";
class GetAiVoice extends PacketTransformer<typeof proto.OidbSvcTrpcTcp0X929B_0Resp> {
constructor() {
super();
}
build(groupUin: number, voiceId: string, text: string, sessionId: number, chatType: AIVoiceChatType): OidbPacket {
const data = new NapProtoMsg(proto.OidbSvcTrpcTcp0X929B_0).encode({
groupUin: groupUin,
voiceId: voiceId,
text: text,
chatType: chatType,
session: {
sessionId: sessionId
}
});
return OidbBase.build(0x929B, 0, data);
}
parse(data: Buffer) {
const oidbBody = OidbBase.parse(data).body;
return new NapProtoMsg(proto.OidbSvcTrpcTcp0X929B_0Resp).decode(oidbBody);
}
}
export default new GetAiVoice();

View File

@@ -0,0 +1,53 @@
import * as proto from "@/core/packet/transformer/proto";
import { NapProtoMsg } from "@napneko/nap-proto-core";
import { OidbPacket, PacketHexStrBuilder, PacketTransformer } from "@/core/packet/transformer/base";
import { MiniAppReqParams } from "@/core/packet/entities/miniApp";
class GetMiniAppAdaptShareInfo extends PacketTransformer<typeof proto.MiniAppAdaptShareInfoResp> {
constructor() {
super();
}
build(req: MiniAppReqParams): OidbPacket {
const data = new NapProtoMsg(proto.MiniAppAdaptShareInfoReq).encode({
appId: req.sdkId,
body: {
extInfo: {
field2: Buffer.alloc(0)
},
appid: req.appId,
title: req.title,
desc: req.desc,
time: BigInt(Date.now()),
scene: req.scene,
templateType: req.templateType,
businessType: req.businessType,
picUrl: req.picUrl,
vidUrl: "",
jumpUrl: req.jumpUrl,
iconUrl: req.iconUrl,
verType: req.verType,
shareType: req.shareType,
versionId: req.versionId,
withShareTicket: req.withShareTicket,
webURL: "",
appidRich: Buffer.alloc(0),
template: {
templateId: "",
templateData: ""
},
field20: ""
}
});
return {
cmd: "LightAppSvc.mini_app_share.AdaptShareInfo",
data: PacketHexStrBuilder(data)
};
}
parse(data: Buffer) {
return new NapProtoMsg(proto.MiniAppAdaptShareInfoResp).decode(data);
}
}
export default new GetMiniAppAdaptShareInfo();

View File

@@ -0,0 +1,25 @@
import * as proto from "@/core/packet/transformer/proto";
import { NapProtoMsg } from "@napneko/nap-proto-core";
import { OidbPacket, PacketTransformer } from "@/core/packet/transformer/base";
import OidbBase from "@/core/packet/transformer/oidb/oidbBase";
class GetStrangerInfo extends PacketTransformer<typeof proto.OidbSvcTrpcTcp0XFE1_2RSP> {
constructor() {
super();
}
build(uin: number): OidbPacket {
const body = new NapProtoMsg(proto.OidbSvcTrpcTcp0XFE1_2).encode({
uin: uin,
key: [{ key: 27372 }]
});
return OidbBase.build(0XFE1, 2, body);
}
parse(data: Buffer) {
const oidbBody = OidbBase.parse(data).body;
return new NapProtoMsg(proto.OidbSvcTrpcTcp0XFE1_2RSP).decode(oidbBody);
}
}
export default new GetStrangerInfo();

View File

@@ -0,0 +1,29 @@
import * as proto from "@/core/packet/transformer/proto";
import { NapProtoMsg } from "@napneko/nap-proto-core";
import { OidbPacket, PacketTransformer } from "@/core/packet/transformer/base";
import OidbBase from "@/core/packet/transformer/oidb/oidbBase";
class GroupSign extends PacketTransformer<typeof proto.OidbSvcTrpcTcpBase> {
constructor() {
super();
}
build(uin: number, groupCode: number): OidbPacket {
const body = new NapProtoMsg(proto.OidbSvcTrpcTcp0XEB7).encode(
{
body: {
uin: String(uin),
groupUin: String(groupCode),
version: "9.0.90"
}
}
);
return OidbBase.build(0XEB7, 1, body, false, false);
}
parse(data: Buffer) {
return OidbBase.parse(data);
}
}
export default new GroupSign();

View File

@@ -0,0 +1,26 @@
import * as proto from "@/core/packet/transformer/proto";
import { NapProtoMsg } from "@napneko/nap-proto-core";
import { OidbPacket, PacketTransformer } from "@/core/packet/transformer/base";
import OidbBase from "@/core/packet/transformer/oidb/oidbBase";
class SendPoke extends PacketTransformer<typeof proto.OidbSvcTrpcTcpBase> {
constructor() {
super();
}
build(peer: number, group?: number): OidbPacket {
const data = new NapProtoMsg(proto.OidbSvcTrpcTcp0XED3_1).encode({
uin: peer,
groupUin: group,
friendUin: group ?? peer,
ext: 0
});
return OidbBase.build(0xED3, 1, data);
}
parse(data: Buffer) {
return OidbBase.parse(data);
}
}
export default new SendPoke();

View File

@@ -0,0 +1,30 @@
import * as proto from "@/core/packet/transformer/proto";
import { NapProtoMsg } from "@napneko/nap-proto-core";
import { OidbPacket, PacketTransformer } from "@/core/packet/transformer/base";
import OidbBase from "@/core/packet/transformer/oidb/oidbBase";
class SetSpecialTitle extends PacketTransformer<typeof proto.OidbSvcTrpcTcpBase> {
constructor() {
super();
}
build(groupCode: number, uid: string, tittle: string): OidbPacket {
const oidb_0x8FC_2_body = new NapProtoMsg(proto.OidbSvcTrpcTcp0X8FC_2_Body).encode({
targetUid: uid,
specialTitle: tittle,
expiredTime: -1,
uinName: tittle
});
const oidb_0x8FC_2 = new NapProtoMsg(proto.OidbSvcTrpcTcp0X8FC_2).encode({
groupUin: +groupCode,
body: oidb_0x8FC_2_body
});
return OidbBase.build(0x8FC, 2, oidb_0x8FC_2, false, false);
}
parse(data: Buffer) {
return OidbBase.parse(data);
}
}
export default new SetSpecialTitle();

View File

@@ -0,0 +1,7 @@
export { default as FetchAiVoiceList } from './FetchAiVoiceList';
export { default as GetAiVoice } from './GetAiVoice';
export { default as GetMiniAppAdaptShareInfo } from './GetMiniAppAdaptShareInfo';
export { default as GroupSign } from './GroupSign';
export { default as GetStrangerInfo } from './GetStrangerInfo';
export { default as SendPoke } from './SendPoke';
export { default as SetSpecialTitle } from './SetSpecialTitle';

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