Compare commits

...

246 Commits

Author SHA1 Message Date
手瓜一十雪
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
120 changed files with 5002 additions and 719 deletions

View File

@@ -93,15 +93,18 @@ jobs:
needs: [Build-LiteLoader,Build-Shell] needs: [Build-LiteLoader,Build-Shell]
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: 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 - name: Download All Artifact
uses: actions/download-artifact@v4 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 - name: Compress subdirectories
run: | run: |
cd ./NapCat.Shell/ cd ./NapCat.Shell/
@@ -114,6 +117,16 @@ jobs:
rm ./NapCat.Framework.zip -rf rm ./NapCat.Framework.zip -rf
mv ./NapCat.Shell/NapCat.Shell.zip ./ mv ./NapCat.Shell/NapCat.Shell.zip ./
mv ./NapCat.Framework/NapCat.Framework.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 - name: Extract version from tag
run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_ENV run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_ENV
@@ -129,4 +142,5 @@ jobs:
files: | files: |
NapCat.Framework.zip NapCat.Framework.zip
NapCat.Shell.zip NapCat.Shell.zip
NapCat.Framework.Windows.Once.zip
draft: true draft: true

View File

@@ -1,26 +1,35 @@
<div align="center"> <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" /> <img src="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" alt="NapCatQQ" width="640" height="320" />
</div> </div>
--- ---
## 欢迎回来 ## 欢迎回来
NapCatQQ (aka 猫猫框架) 是现代化的基于 NTQQ 的 Bot 协议端实现 NapCatQQ (aka 猫猫框架) 是现代化的基于 NTQQ 的 Bot 协议端实现
## 猫猫技能 ## 猫猫技能
- [x] **高性能**1K+ 群聊数目、20 线程并行发送消息毫无压力 - [x] **高性能**轻松数千群聊 独创消息队列
- [x] **多种启动方式**支持以无头、LiteLoader 插件、仅 QQ GUI 三种方式启动 - [x] **启动方式**支持以无头、LiteLoader 插件、仅 QQ GUI 三种方式启动
- [x] **多平台支持**: 覆盖 Windows / Linux (可选 Docker) / Android Termux / MacOS - [x] **覆盖平台**: 覆盖 Windows / Linux (可选 Docker) / Android Termux / MacOS
- [x] **安装简单**: 支持一键脚本/程序自动部署/镜像部署等多种覆盖范围 - [x] **安装简单**: 支持一键脚本/程序自动部署/镜像部署等多种覆盖范围
- [x] **低占用**:无头模式占用资源极低,适合在服务器上运行 - [x] **低占用**:无头模式占用资源极低,适合在服务器上运行
- [x] **超多接口**:实现大部分 OneBot 和 go-cqhttp 接口,超多扩展 API - [x] **超多接口**:实现大部分 OneBot 和 go-cqhttp 接口,超多扩展 API
- [x] **WebUI**:自带 WebUI 支持,远程管理更加便捷 - [x] **远程管理**:自带 WebUI 支持,远程管理更加便捷
- [x] **低故障率**:快速适配最新版本,日常保证 0 Issue - [x] **扩展支持**:基于 MoeHoo 的Native 可实现发包与收包
## 使用猫猫 ## 使用猫猫
可前往 [Release](https://github.com/NapNeko/NapCatQQ/releases/) 页面下载最新版本 可前往 [Release](https://github.com/NapNeko/NapCatQQ/releases/) 页面下载最新版本
**首次使用**请务必前往[官方文档](https://napneko.github.io/)查看使用教程 **首次使用**请务必查看如下文档看使用教程
### 文档地址
[Github.IO](https://napneko.github.io/)
[Cloudflare.Worker](https://doc.napneko.icu/)
[Cloudflare.HKServer](https://napcat.napneko.icu/)
[Cloudflare.Pages](https://napneko.pages.dev/)
## 回家旅途 ## 回家旅途
[QQ Group](https://qm.qq.com/q/VfjAq5HIMS) [QQ Group](https://qm.qq.com/q/VfjAq5HIMS)
@@ -28,7 +37,7 @@ NapCatQQ (aka 猫猫框架) 是现代化的基于 NTQQ 的 Bot 协议端实现
[Telegram Link](https://t.me/+nLZEnpne-pQ1OWFl) [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) 对本项目的大力支持

BIN
external/LiteLoaderWrapper.zip vendored Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -33,7 +33,7 @@ if not exist "%QQpath%" (
exit /b exit /b
) )
set NAPCAT_MAIN_PATH=%NAPCAT_MAIN_PATH:\=/% 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 "%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:\=/% 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 "%NAPCAT_LAUNCHER_PATH%" "%QQPath%" "%NAPCAT_INJECT_PATH%" %1

View File

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

View File

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

View File

@@ -2,7 +2,7 @@
"name": "napcat", "name": "napcat",
"private": true, "private": true,
"type": "module", "type": "module",
"version": "2.6.6", "version": "3.0.1",
"scripts": { "scripts": {
"build:framework": "vite build --mode framework", "build:framework": "vite build --mode framework",
"build:shell": "vite build --mode shell", "build:shell": "vite build --mode shell",
@@ -13,11 +13,10 @@
"devDependencies": { "devDependencies": {
"@babel/preset-typescript": "^7.24.7", "@babel/preset-typescript": "^7.24.7",
"@log4js-node/log4js-api": "^1.0.2", "@log4js-node/log4js-api": "^1.0.2",
"@protobuf-ts/plugin": "^2.9.4",
"@rollup/plugin-node-resolve": "^15.2.3", "@rollup/plugin-node-resolve": "^15.2.3",
"@rollup/plugin-typescript": "^11.1.6", "@rollup/plugin-typescript": "^11.1.6",
"@types/cors": "^2.8.17", "@types/cors": "^2.8.17",
"@types/express": "^4.17.21", "@types/express": "^5.0.0",
"@types/fluent-ffmpeg": "^2.1.24", "@types/fluent-ffmpeg": "^2.1.24",
"@types/node": "^22.0.1", "@types/node": "^22.0.1",
"@types/qrcode-terminal": "^0.12.2", "@types/qrcode-terminal": "^0.12.2",
@@ -30,26 +29,24 @@
"typescript": "^5.3.3", "typescript": "^5.3.3",
"vite": "^5.2.6", "vite": "^5.2.6",
"vite-plugin-cp": "^4.0.8", "vite-plugin-cp": "^4.0.8",
"vite-plugin-dts": "^3.8.2", "vite-tsconfig-paths": "^4.3.2",
"vite-tsconfig-paths": "^4.3.2" "@protobuf-ts/runtime": "^2.9.4",
},
"dependencies": {
"ajv": "^8.13.0", "ajv": "^8.13.0",
"async-mutex": "^0.5.0", "fast-xml-parser": "^4.3.6",
"chalk": "^5.3.0", "chalk": "^5.3.0",
"commander": "^12.1.0", "commander": "^12.1.0",
"cors": "^2.8.5", "async-mutex": "^0.5.0",
"express": "^5.0.0-beta.2",
"fast-xml-parser": "^4.3.6",
"file-type": "^19.0.0", "file-type": "^19.0.0",
"fluent-ffmpeg": "^2.1.2",
"image-size": "^1.1.1",
"json-schema-to-ts": "^3.1.0", "json-schema-to-ts": "^3.1.0",
"log4js": "^6.9.1", "image-size": "^1.1.1",
"protobufjs": "~7.4.0", "cors": "^2.8.5"
},
"dependencies": {
"qrcode-terminal": "^0.12.0", "qrcode-terminal": "^0.12.0",
"fluent-ffmpeg": "^2.1.2",
"express": "^5.0.0-beta.2",
"log4js": "^6.9.1",
"silk-wasm": "^3.6.1", "silk-wasm": "^3.6.1",
"strtok3": "8.0.1",
"ws": "^8.18.0" "ws": "^8.18.0"
} }
} }

View File

@@ -83,7 +83,7 @@ export async function encodeSilk(filePath: string, TEMP_DIR: string, logger: Log
}; };
} }
} catch (error: any) { } catch (error: any) {
logger.logError('convert silk failed', error.stack); logger.logError.bind(logger)('convert silk failed', error.stack);
return {}; return {};
} }
} }

View File

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

View File

@@ -183,11 +183,11 @@ export class NTEventWrapper {
timeout = 5000, timeout = 5000,
) { ) {
return new Promise<[EventRet: Awaited<ReturnType<EventType>>, ...Parameters<ListenerType>]>( return new Promise<[EventRet: Awaited<ReturnType<EventType>>, ...Parameters<ListenerType>]>(
(resolve, reject) => { async (resolve, reject) => {
const id = randomUUID(); const id = randomUUID();
let complete = 0; let complete = 0;
let retData: Parameters<ListenerType> | undefined = undefined; let retData: Parameters<ListenerType> | undefined = undefined;
const retEvent: any = {}; let retEvent: any = {};
function sendDataCallback() { function sendDataCallback() {
if (complete == 0) { if (complete == 0) {
@@ -235,7 +235,7 @@ export class NTEventWrapper {
this.EventTask.get(ListenerMainName)?.get(ListenerSubName)?.set(id, eventCallback); this.EventTask.get(ListenerMainName)?.get(ListenerSubName)?.set(id, eventCallback);
this.createListenerFunction(ListenerMainName); this.createListenerFunction(ListenerMainName);
const eventFunction = this.createEventFunction(serviceAndMethod); const eventFunction = this.createEventFunction(serviceAndMethod);
if (eventFunction) eventFunction(...(args)).then((retEvent: Awaited<ReturnType<EventType>>) => { retEvent = await eventFunction!(...(args));
if (!checkerEvent(retEvent) && timeoutRef.hasRef()) { if (!checkerEvent(retEvent) && timeoutRef.hasRef()) {
clearTimeout(timeoutRef); clearTimeout(timeoutRef);
reject( reject(
@@ -250,7 +250,7 @@ export class NTEventWrapper {
), ),
); );
} }
});
}, },
); );
} }

View File

@@ -25,8 +25,8 @@ export async function solveAsyncProblem<T extends (...args: any[]) => Promise<an
} }
export class FileNapCatOneBotUUID { export class FileNapCatOneBotUUID {
static encodeModelId(peer: Peer, modelId: string, fileId: string, endString: string = ""): string { static encodeModelId(peer: Peer, modelId: string, fileId: string, fileUUID: string = "", endString: string = ""): string {
const data = `NapCatOneBot|ModelIdFile|${peer.chatType}|${peer.peerUid}|${modelId}|${fileId}`; const data = `NapCatOneBot|ModelIdFile|${peer.chatType}|${peer.peerUid}|${modelId}|${fileId}|${fileUUID}`;
//前四个字节塞data长度 //前四个字节塞data长度
const length = Buffer.alloc(4 + data.length); const length = Buffer.alloc(4 + data.length);
length.writeUInt32BE(data.length * 2, 0);//储存data的hex长度 length.writeUInt32BE(data.length * 2, 0);//储存data的hex长度
@@ -37,7 +37,8 @@ export class FileNapCatOneBotUUID {
static decodeModelId(uuid: string): undefined | { static decodeModelId(uuid: string): undefined | {
peer: Peer, peer: Peer,
modelId: string, modelId: string,
fileId: string fileId: string,
fileUUID?: string
} { } {
//前四个字节是data长度 //前四个字节是data长度
const length = Buffer.from(uuid.slice(0, 8), 'hex').readUInt32BE(0); 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(); const realData = Buffer.from(dataId, 'hex').toString();
if (!realData.startsWith('NapCatOneBot|ModelIdFile|')) return undefined; if (!realData.startsWith('NapCatOneBot|ModelIdFile|')) return undefined;
const data = realData.split('|'); const data = realData.split('|');
if (data.length !== 6) return undefined; if (data.length < 6) return undefined; // compatibility requirement
const [, , chatType, peerUid, modelId, fileId] = data; const [, , chatType, peerUid, modelId, fileId, fileUUID = undefined] = data;
return { return {
peer: { peer: {
chatType: chatType as any, chatType: chatType as any,
peerUid: peerUid, peerUid: peerUid,
}, },
modelId, modelId,
fileId fileId,
fileUUID
}; };
} }
static encode(peer: Peer, msgId: string, elementId: string, endString: string = ""): string { static encode(peer: Peer, msgId: string, elementId: string, fileUUID: string = "", endString: string = ""): string {
const data = `NapCatOneBot|MsgFile|${peer.chatType}|${peer.peerUid}|${msgId}|${elementId}`; const data = `NapCatOneBot|MsgFile|${peer.chatType}|${peer.peerUid}|${msgId}|${elementId}|${fileUUID}`;
//前四个字节塞data长度 //前四个字节塞data长度
//一个字节8位 一个ascii字符1字节 一个hex字符4位 表示一个ascii字符需要两个hex字符 //一个字节8位 一个ascii字符1字节 一个hex字符4位 表示一个ascii字符需要两个hex字符
const length = Buffer.alloc(4 + data.length); const length = Buffer.alloc(4 + data.length);
@@ -72,7 +74,8 @@ export class FileNapCatOneBotUUID {
static decode(uuid: string): undefined | { static decode(uuid: string): undefined | {
peer: Peer, peer: Peer,
msgId: string, msgId: string,
elementId: string elementId: string,
fileUUID?: string
} { } {
//前四个字节是data长度 //前四个字节是data长度
const length = Buffer.from(uuid.slice(0, 8), 'hex').readUInt32BE(0); const length = Buffer.from(uuid.slice(0, 8), 'hex').readUInt32BE(0);
@@ -82,8 +85,8 @@ export class FileNapCatOneBotUUID {
const realData = Buffer.from(dataId, 'hex').toString(); const realData = Buffer.from(dataId, 'hex').toString();
if (!realData.startsWith('NapCatOneBot|MsgFile|')) return undefined; if (!realData.startsWith('NapCatOneBot|MsgFile|')) return undefined;
const data = realData.split('|'); const data = realData.split('|');
if (data.length !== 6) return undefined; if (data.length < 6) return undefined; // compatibility requirement
const [, , chatType, peerUid, msgId, elementId] = data; const [, , chatType, peerUid, msgId, elementId, fileUUID = undefined] = data;
return { return {
peer: { peer: {
chatType: chatType as any, chatType: chatType as any,
@@ -91,6 +94,7 @@ export class FileNapCatOneBotUUID {
}, },
msgId, msgId,
elementId, elementId,
fileUUID
}; };
} }
} }
@@ -180,11 +184,11 @@ export function getDefaultQQVersionConfigInfo(): QQVersionConfigType {
}; };
} }
return { return {
baseVersion: '9.9.15-28060', baseVersion: '9.9.15-28131',
curVersion: '9.9.15-28060', curVersion: '9.9.15-28131',
prevVersion: '', prevVersion: '',
onErrorVersions: [], onErrorVersions: [],
buildId: '28060', buildId: '28131',
}; };
} }

View File

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

View File

@@ -24,8 +24,10 @@ export class LRUCache<K, V> {
} else if (this.cache.size >= this.capacity) { } else if (this.cache.size >= this.capacity) {
// If the cache is full, remove the least recently used key (the first one in the map) // If the cache is full, remove the least recently used key (the first one in the map)
const firstKey = this.cache.keys().next().value; const firstKey = this.cache.keys().next().value;
if (firstKey !== undefined) {
this.cache.delete(firstKey); this.cache.delete(firstKey);
} }
}
this.cache.set(key, value); this.cache.set(key, value);
} }
} }

View File

@@ -23,7 +23,9 @@ export class LimitedHashTable<K, V> {
} }
while (this.keyToValue.size > this.maxSize || this.valueToKey.size > this.maxSize) { while (this.keyToValue.size > this.maxSize || this.valueToKey.size > this.maxSize) {
const oldestKey = this.keyToValue.keys().next().value; const oldestKey = this.keyToValue.keys().next().value;
// @ts-ignore
this.valueToKey.delete(this.keyToValue.get(oldestKey)!); this.valueToKey.delete(this.keyToValue.get(oldestKey)!);
// @ts-ignore
this.keyToValue.delete(oldestKey); this.keyToValue.delete(oldestKey);
} }
} }

View File

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

View File

@@ -61,7 +61,7 @@ export class RequestUtil {
const options = { const options = {
hostname: option.hostname, hostname: option.hostname,
port: option.port, port: option.port,
path: option.href, path: option.pathname + option.search,
method: method, method: method,
headers: headers, headers: headers,
}; };

View File

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

View File

@@ -1 +1 @@
export const napCatVersion = '2.6.6'; export const napCatVersion = '3.0.1';

View File

@@ -30,6 +30,7 @@ export class NTQQFileApi {
context: InstanceContext; context: InstanceContext;
core: NapCatCore; core: NapCatCore;
rkeyManager: RkeyManager; rkeyManager: RkeyManager;
packetRkey: Array<{ rkey: string; time: number; type: number; }> | undefined;
constructor(context: InstanceContext, core: NapCatCore) { constructor(context: InstanceContext, core: NapCatCore) {
this.context = context; this.context = context;
@@ -145,7 +146,7 @@ export class NTQQFileApi {
try { try {
videoInfo = await getVideoInfo(filePath, logger); videoInfo = await getVideoInfo(filePath, logger);
} catch (e) { } catch (e) {
logger.logError('获取视频信息失败,将使用默认值', e); logger.logError.bind(logger)('获取视频信息失败,将使用默认值', e);
} }
let fileExt = 'mp4'; let fileExt = 'mp4';
@@ -153,7 +154,7 @@ export class NTQQFileApi {
const tempExt = (await fileType.fileTypeFromFile(filePath))?.ext; const tempExt = (await fileType.fileTypeFromFile(filePath))?.ext;
if (tempExt) fileExt = tempExt; if (tempExt) fileExt = tempExt;
} catch (e) { } catch (e) {
this.context.logger.logError('获取文件类型失败', e); this.context.logger.logError.bind(logger)('获取文件类型失败', e);
} }
const newFilePath = filePath + '.' + fileExt; const newFilePath = filePath + '.' + fileExt;
fs.copyFileSync(filePath, newFilePath); fs.copyFileSync(filePath, newFilePath);
@@ -227,7 +228,7 @@ export class NTQQFileApi {
} }
if (converted) { if (converted) {
fsPromises.unlink(silkPath).then().catch( fsPromises.unlink(silkPath).then().catch(
(e) => this.context.logger.logError('删除临时文件失败', e) (e) => this.context.logger.logError.bind(this.context.logger)('删除临时文件失败', e)
); );
} }
return { return {
@@ -365,22 +366,57 @@ export class NTQQFileApi {
if (url) { if (url) {
const parsedUrl = new URL(IMAGE_HTTP_HOST + url); const parsedUrl = new URL(IMAGE_HTTP_HOST + url);
const urlRkey = parsedUrl.searchParams.get('rkey');
const imageAppid = parsedUrl.searchParams.get('appid'); const imageAppid = parsedUrl.searchParams.get('appid');
const isNTV2 = imageAppid && ['1406', '1407'].includes(imageAppid); const isNTV2 = imageAppid && ['1406', '1407'].includes(imageAppid);
if (isNTV2) { const imageFileId = parsedUrl.searchParams.get('fileid');
let rkey = parsedUrl.searchParams.get('rkey');
if (rkey) { const rkeyData = {
return IMAGE_HTTP_HOST_NT + url; private_rkey: 'CAQSKAB6JWENi5LM_xp9vumLbuThJSaYf-yzMrbZsuq7Uz2qEc3Rbib9LP4',
group_rkey: 'CAQSKAB6JWENi5LM_xp9vumLbuThJSaYf-yzMrbZsuq7Uz2qffcqm614gds',
online_rkey: false
};
try {
if (this.core.apis.PacketApi.available) {
if ((!this.packetRkey || this.packetRkey[0].time > Date.now() / 1000)) {
this.packetRkey = await this.core.apis.PacketApi.sendRkeyPacket();
} }
const rkeyData = await this.rkeyManager.getRkey(); if (this.packetRkey.length > 0) {
rkey = imageAppid === '1406' ? rkeyData.private_rkey : rkeyData.group_rkey; rkeyData.group_rkey = this.packetRkey[1].rkey.slice(6);
return IMAGE_HTTP_HOST_NT + url + `${rkey}`; rkeyData.private_rkey = this.packetRkey[0].rkey.slice(6);
} else { rkeyData.online_rkey = true;
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);
}
}
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 + url + `&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}`;
}
}
//到这里说明可能是旧客户端
if (fileMd5 || md5HexStr) {
return `${IMAGE_HTTP_HOST}/gchatpic_new/0/0-0-${(fileMd5 ?? md5HexStr)!.toUpperCase()}/0`; return `${IMAGE_HTTP_HOST}/gchatpic_new/0/0-0-${(fileMd5 ?? md5HexStr)!.toUpperCase()}/0`;
} }
this.context.logger.logDebug('图片url获取失败', element); this.context.logger.logDebug('图片url获取失败', element);
return ''; return '';
} }

View File

@@ -10,7 +10,9 @@ export class NTQQFriendApi {
this.context = context; this.context = context;
this.core = core; this.core = core;
} }
async setBuddyRemark(uid: string, remark: string) {
return this.context.session.getBuddyService().setBuddyRemark({ uid, remark });
}
async getBuddyV2SimpleInfoMap(refresh = false) { async getBuddyV2SimpleInfoMap(refresh = false) {
const buddyService = this.context.session.getBuddyService(); const buddyService = this.context.session.getBuddyService();
const buddyListV2 = refresh ? await buddyService.getBuddyListV2('0', BuddyListReqType.KNOMAL) : await buddyService.getBuddyListV2('0', BuddyListReqType.KNOMAL); const buddyListV2 = refresh ? await buddyService.getBuddyListV2('0', BuddyListReqType.KNOMAL) : await buddyService.getBuddyListV2('0', BuddyListReqType.KNOMAL);

View File

@@ -20,6 +20,7 @@ export class NTQQGroupApi {
groupMemberCache: Map<string, Map<string, GroupMember>> = new Map<string, Map<string, GroupMember>>(); groupMemberCache: Map<string, Map<string, GroupMember>> = new Map<string, Map<string, GroupMember>>();
groups: Group[] = []; groups: Group[] = [];
essenceLRU = new LimitedHashTable<number, string>(1000); essenceLRU = new LimitedHashTable<number, string>(1000);
session: any;
constructor(context: InstanceContext, core: NapCatCore) { constructor(context: InstanceContext, core: NapCatCore) {
this.context = context; this.context = context;
@@ -33,7 +34,9 @@ export class NTQQGroupApi {
this.groupCache.set(group.groupCode, group); this.groupCache.set(group.groupCode, group);
} }
this.context.logger.logDebug(`加载${this.groups.length}个群组缓存完成`); this.context.logger.logDebug(`加载${this.groups.length}个群组缓存完成`);
// process.pid 调试点
} }
async getCoreAndBaseInfo(uids: string[]) { async getCoreAndBaseInfo(uids: string[]) {
return await this.core.eventWrapper.callNoListenerEvent( return await this.core.eventWrapper.callNoListenerEvent(
'NodeIKernelProfileService/getCoreAndBaseInfo', 'NodeIKernelProfileService/getCoreAndBaseInfo',
@@ -41,6 +44,7 @@ export class NTQQGroupApi {
uids, uids,
); );
} }
async fetchGroupEssenceList(groupCode: string) { async fetchGroupEssenceList(groupCode: string) {
const pskey = (await this.core.apis.UserApi.getPSkey(['qun.qq.com'])).domainPskeyMap.get('qun.qq.com')!; const pskey = (await this.core.apis.UserApi.getPSkey(['qun.qq.com'])).domainPskeyMap.get('qun.qq.com')!;
return this.context.session.getGroupService().fetchGroupEssenceList({ return this.context.session.getGroupService().fetchGroupEssenceList({
@@ -49,7 +53,9 @@ export class NTQQGroupApi {
pageLimit: 300, pageLimit: 300,
}, pskey); }, pskey);
} }
async getGroupShutUpMemberList(groupCode: string) {
return this.context.session.getGroupService().getGroupShutUpMemberList(groupCode);
}
async clearGroupNotifiesUnreadCount(uk: boolean) { async clearGroupNotifiesUnreadCount(uk: boolean) {
return this.context.session.getGroupService().clearGroupNotifiesUnreadCount(uk); return this.context.session.getGroupService().clearGroupNotifiesUnreadCount(uk);
} }
@@ -137,7 +143,7 @@ export class NTQQGroupApi {
let members = this.groupMemberCache.get(groupCodeStr); let members = this.groupMemberCache.get(groupCodeStr);
if (!members) { if (!members) {
try { try {
members = await this.getGroupMembersV2(groupCodeStr); members = await this.getGroupMembers(groupCodeStr);
// 更新群成员列表 // 更新群成员列表
this.groupMemberCache.set(groupCodeStr, members); this.groupMemberCache.set(groupCodeStr, members);
} catch (e) { } catch (e) {
@@ -158,11 +164,12 @@ export class NTQQGroupApi {
let member = getMember(); let member = getMember();
if (!member) { if (!member) {
members = await this.getGroupMembersV2(groupCodeStr); members = await this.getGroupMembers(groupCodeStr);
member = getMember(); member = getMember();
} }
return member; return member;
} }
async getGroupRecommendContactArkJson(groupCode: string) { async getGroupRecommendContactArkJson(groupCode: string) {
return this.context.session.getGroupService().getGroupRecommendContactArkJson(groupCode); return this.context.session.getGroupService().getGroupRecommendContactArkJson(groupCode);
} }
@@ -268,6 +275,7 @@ export class NTQQGroupApi {
} }
return member; return member;
} }
async searchGroup(groupCode: string) { async searchGroup(groupCode: string) {
const [, ret] = await this.core.eventWrapper.callNormalEventV2( const [, ret] = await this.core.eventWrapper.callNormalEventV2(
'NodeIKernelSearchService/searchGroup', 'NodeIKernelSearchService/searchGroup',
@@ -285,6 +293,7 @@ export class NTQQGroupApi {
); );
return ret.groupInfos.find(g => g.groupCode === groupCode); return ret.groupInfos.find(g => g.groupCode === groupCode);
} }
async getGroupMemberEx(GroupCode: string, uid: string, forced = false, retry = 2) { async getGroupMemberEx(GroupCode: string, uid: string, forced = false, retry = 2) {
const data = await solveAsyncProblem((eventWrapper: NTEventWrapper, GroupCode: string, uid: string, forced = false) => { const data = await solveAsyncProblem((eventWrapper: NTEventWrapper, GroupCode: string, uid: string, forced = false) => {
return eventWrapper.callNormalEventV2( return eventWrapper.callNormalEventV2(
@@ -306,36 +315,19 @@ export class NTQQGroupApi {
} }
return undefined; return undefined;
} }
async getGroupMembersV2(groupQQ: string, num = 3000): Promise<Map<string, GroupMember>> { async getGroupMembersV2(groupQQ: string, num = 3000): Promise<Map<string, GroupMember>> {
const groupService = this.context.session.getGroupService(); const sceneId = this.context.session.getGroupService().createMemberListScene(groupQQ, 'groupMemberList_MainWindow');
const sceneId = groupService.createMemberListScene(groupQQ, 'groupMemberList_MainWindow'); let once = this.core.eventWrapper.registerListen('NodeIKernelGroupListener/onMemberListChange', 1, 2000, (params) => params.sceneId === sceneId)
const listener = this.core.eventWrapper.registerListen( .catch();
'NodeIKernelGroupListener/onMemberListChange', const result = await this.context.session.getGroupService().getNextMemberList(sceneId!, undefined, num);
1, if (result.errCode !== 0) {
5000, throw new Error('获取群成员列表出错,' + result.errMsg);
(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') { if (result.result.infos.size === 0) {
return membersFromFunc.value.result.infos; return (await once)[0].infos;
}
if (membersFromListener.status === 'fulfilled') {
return membersFromListener.value[0].infos;
}
throw new Error('获取群成员列表失败');
} finally {
groupService.destroyMemberListScene(sceneId);
} }
return result.result.infos;
} }
async getGroupMembers(groupQQ: string, num = 3000): Promise<Map<string, GroupMember>> { async getGroupMembers(groupQQ: string, num = 3000): Promise<Map<string, GroupMember>> {

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

@@ -0,0 +1,143 @@
import * as os from 'os';
import {ChatType, InstanceContext, NapCatCore} from '..';
import offset from '@/core/external/offset.json';
import {PacketClient, RecvPacketData} from '@/core/packet/client';
import {PacketSession} from "@/core/packet/session";
import {PacketHexStr} from "@/core/packet/packer";
import {NapProtoMsg} from '@/core/packet/proto/NapProto';
import {OidbSvcTrpcTcp0X9067_202_Rsp_Body} from '@/core/packet/proto/oidb/Oidb.0x9067_202';
import {OidbSvcTrpcTcpBase, OidbSvcTrpcTcpBaseRsp} from '@/core/packet/proto/oidb/OidbBase';
import {OidbSvcTrpcTcp0XFE1_2RSP} from '@/core/packet/proto/oidb/Oidb.0XFE1_2';
import {LogWrapper} from "@/common/log";
import {SendLongMsgResp} from "@/core/packet/proto/message/action";
import {PacketMsg} from "@/core/packet/msg/message";
import {OidbSvcTrpcTcp0x6D6Response} from "@/core/packet/proto/oidb/Oidb.0x6D6";
import {PacketMsgPicElement} from "@/core/packet/msg/element";
interface OffsetType {
[key: string]: {
recv: string;
send: string;
};
}
const typedOffset: OffsetType = offset;
export class NTQQPacketApi {
context: InstanceContext;
core: NapCatCore;
logger: LogWrapper
serverUrl: string | undefined;
qqVersion: string | undefined;
packetSession: PacketSession | undefined;
constructor(context: InstanceContext, core: NapCatCore) {
this.context = context;
this.core = core;
this.logger = core.context.logger;
this.packetSession = undefined;
const config = this.core.configLoader.configData;
if (config && config.packetServer && config.packetServer.length > 0) {
const serverUrl = this.core.configLoader.configData.packetServer ?? '127.0.0.1:8086';
this.InitSendPacket(serverUrl, this.context.basicInfoWrapper.getFullQQVesion())
.then()
.catch(this.core.context.logger.logError.bind(this.core.context.logger));
} else {
this.core.context.logger.logWarn('PacketServer is not set, will not init NapCat.Packet!');
}
}
get available(): boolean {
return this.packetSession?.client.available ?? false;
}
async InitSendPacket(serverUrl: string, qqversion: string) {
this.serverUrl = serverUrl;
this.qqVersion = qqversion;
const offsetTable: OffsetType = offset;
const table = offsetTable[qqversion + '-' + os.arch()];
if (!table) return false;
const url = 'ws://' + this.serverUrl + '/ws';
this.packetSession = new PacketSession(this.core.context.logger, new PacketClient(url, this.core));
await this.packetSession.client.connect();
await this.packetSession.client.init(process.pid, table.recv, table.send);
return true;
}
async sendPacket(cmd: string, data: PacketHexStr, rsp = false): Promise<RecvPacketData> {
return this.packetSession!.client.sendPacket(cmd, data, rsp);
}
async sendPokePacket(group: number, peer: number) {
const data = this.packetSession?.packer.packPokePacket(group, peer);
await this.sendPacket('OidbSvcTrpcTcp.0xed3_1', data!, false);
}
async sendRkeyPacket() {
const packet = this.packetSession?.packer.packRkeyPacket();
const ret = await this.sendPacket('OidbSvcTrpcTcp.0x9067_202', packet!, true);
if (!ret?.hex_data) return [];
const body = new NapProtoMsg(OidbSvcTrpcTcpBaseRsp).decode(Buffer.from(ret.hex_data, 'hex')).body;
const retData = new NapProtoMsg(OidbSvcTrpcTcp0X9067_202_Rsp_Body).decode(body);
return retData.data.rkeyList;
}
async sendStatusPacket(uin: number): Promise<{ status: number; ext_status: number; } | undefined> {
let status = 0;
try {
const packet = this.packetSession?.packer.packStatusPacket(uin);
const ret = await this.sendPacket('OidbSvcTrpcTcp.0xfe1_2', packet!, true);
const data = Buffer.from(ret.hex_data, 'hex');
const ext = new NapProtoMsg(OidbSvcTrpcTcp0XFE1_2RSP).decode(new NapProtoMsg(OidbSvcTrpcTcpBase).decode(data).body).data.status.value;
// ext & 0xff00 + ext >> 16 & 0xff
const extBigInt = BigInt(ext); // 转换为 BigInt
if (extBigInt <= 10n) {
return { status: Number(extBigInt) * 10, ext_status: 0 };
}
status = Number((extBigInt & 0xff00n) + ((extBigInt >> 16n) & 0xffn)); // 使用 BigInt 操作符
return { status: 10, ext_status: status };
} catch (error) {
return undefined;
}
}
async sendSetSpecialTittlePacket(groupCode: string, uid: string, tittle: string) {
const data = this.packetSession?.packer.packSetSpecialTittlePacket(groupCode, uid, tittle);
await this.sendPacket('OidbSvcTrpcTcp.0x8fc_2', data!, true);
}
private 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.packetSession?.highwaySession.uploadImage({
chatType: groupUin ? ChatType.KCHATTYPEGROUP : ChatType.KCHATTYPEC2C,
peerUid: String(groupUin) ? String(groupUin) : this.core.selfInfo.uid
}, e));
}
}
}
return Promise.all(reqList);
}
async sendUploadForwardMsg(msg: PacketMsg[], groupUin: number = 0) {
await this.uploadResources(msg, groupUin);
const data = await this.packetSession?.packer.packUploadForwardMsg(this.core.selfInfo.uid, msg, groupUin);
const ret = await this.sendPacket('trpc.group.long_msg_interface.MsgService.SsoSendLongMsg', data!, true);
this.logger.logDebug('sendUploadForwardMsg', ret);
const resp = new NapProtoMsg(SendLongMsgResp).decode(Buffer.from(ret.hex_data, 'hex'));
return resp.result.resId;
}
async sendGroupFileDownloadReq(groupUin: number, fileUUID: string) {
const data = this.packetSession?.packer.packGroupFileDownloadReq(groupUin, fileUUID);
const ret = await this.sendPacket('OidbSvcTrpcTcp.0x6d6_2', data!, true);
const body = new NapProtoMsg(OidbSvcTrpcTcpBaseRsp).decode(Buffer.from(ret.hex_data, 'hex')).body;
const resp = new NapProtoMsg(OidbSvcTrpcTcp0x6D6Response).decode(body);
if (resp.download.retCode !== 0){
throw new Error(`sendGroupFileDownloadReq error: ${resp.download.clientWording}`);
}
return `https://${resp.download.downloadDns}/ftn_handler/${Buffer.from(resp.download.downloadUrl).toString('hex')}/?fname=`
}
}

View File

@@ -68,8 +68,7 @@ export class NTQQUserApi {
} }
async setQQAvatar(filePath: string) { async setQQAvatar(filePath: string) {
type setQQAvatarRet = { result: number, errMsg: string }; const ret = await this.context.session.getProfileService().setHeader(filePath);
const ret = await this.context.session.getProfileService().setHeader(filePath) as setQQAvatarRet;
return { result: ret?.result, errMsg: ret?.errMsg }; return { result: ret?.result, errMsg: ret?.errMsg };
} }
@@ -120,12 +119,20 @@ export class NTQQUserApi {
return this.context.session.getProfileService().modifyDesktopMiniProfile(param); return this.context.session.getProfileService().modifyDesktopMiniProfile(param);
} }
//需要异常处理
async getCookies(domain: string) { async getCookies(domain: string) {
const ClientKeyData = await this.forceFetchClientKey(); const ClientKeyData = await this.forceFetchClientKey();
const requestUrl = 'https://ssl.ptlogin2.qq.com/jump?ptlang=1033&clientuin=' + this.core.selfInfo.uin + 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'; '&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[]) { async getPSkey(domainList: string[]) {

View File

@@ -264,7 +264,7 @@ export class NTQQWebApi {
}); });
} }
} else { } else {
this.context.logger.logError('获取龙王信息失败'); this.context.logger.logError.bind(this.context.logger)('获取龙王信息失败');
} }
} }
if (getType === WebHonorType.PERFORMER || getType === WebHonorType.ALL) { if (getType === WebHonorType.PERFORMER || getType === WebHonorType.ALL) {
@@ -280,7 +280,7 @@ export class NTQQWebApi {
}); });
} }
} else { } else {
this.context.logger.logError('获取群聊之火失败'); this.context.logger.logError.bind(this.context.logger)('获取群聊之火失败');
} }
} }
if (getType === WebHonorType.PERFORMER || getType === WebHonorType.ALL) { if (getType === WebHonorType.PERFORMER || getType === WebHonorType.ALL) {
@@ -296,7 +296,7 @@ export class NTQQWebApi {
}); });
} }
} else { } else {
this.context.logger.logError('获取群聊炽焰失败'); this.context.logger.logError.bind(this.context.logger)('获取群聊炽焰失败');
} }
} }
if (getType === WebHonorType.EMOTION || getType === WebHonorType.ALL) { if (getType === WebHonorType.EMOTION || getType === WebHonorType.ALL) {
@@ -312,7 +312,7 @@ export class NTQQWebApi {
}); });
} }
} else { } else {
this.context.logger.logError('获取快乐源泉失败'); this.context.logger.logError.bind(this.context.logger)('获取快乐源泉失败');
} }
} }

View File

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

View File

@@ -372,6 +372,7 @@ export interface ReplyElement {
senderUin: string; senderUin: string;
senderUidStr?: string; senderUidStr?: string;
replyMsgTime?: string; replyMsgTime?: string;
replyMsgClientSeq?: string;
} }
export interface SendReplyElement { export interface SendReplyElement {
@@ -391,7 +392,7 @@ export interface SendMarketFaceElement {
marketFaceElement: MarketFaceElement; marketFaceElement: MarketFaceElement;
} }
export interface SendstructLongMsgElement { export interface SendStructLongMsgElement {
elementType: ElementType.STRUCTLONGMSG; elementType: ElementType.STRUCTLONGMSG;
elementId: string; elementId: string;
structLongMsgElement: StructLongMsgElement; structLongMsgElement: StructLongMsgElement;
@@ -516,6 +517,12 @@ export enum AtType {
atAll = 1, atAll = 1,
atUser = 2 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分析 // 来自Android分析
export enum ChatType { export enum ChatType {
@@ -659,7 +666,8 @@ export interface GrayTipElement {
export enum FaceType { export enum FaceType {
normal = 1, // 小黄脸 normal = 1, // 小黄脸
normal2 = 2, // 新小黄脸, 从faceIndex 222开始 normal2 = 2, // 新小黄脸, 从faceIndex 222开始
dice = 3 // 骰子 dice = 3, // 骰子
poke = 5 // 拍一拍
} }
export enum FaceIndex { export enum FaceIndex {
@@ -872,6 +880,8 @@ export interface RawMessage {
/** /**
* 扩展字段,与 Ob11 msg ID 有关 * 扩展字段,与 Ob11 msg ID 有关
*/ */
id?: number; id?: number;
guildId: string; guildId: string;
@@ -908,11 +918,26 @@ export interface RawMessage {
*/ */
peerUin: string; peerUin: string;
/**
* 好友备注(如果是好友消息)
*/
remark?: string;
/**
* 群名(如果是群消息)
*/
peerName: string;
/** /**
* 发送者昵称(如果是好友消息) * 发送者昵称(如果是好友消息)
*/ */
sendNickName: string; sendNickName: string;
/**
* 发送者好友备注(如果是群消息并且有发送者好友)
*/
sendRemarkName: string;
/** /**
* 发送者群名片(如果是群消息) * 发送者群名片(如果是群消息)
*/ */
@@ -933,6 +958,8 @@ export interface RawMessage {
records: RawMessage[]; records: RawMessage[];
elements: MessageElement[]; elements: MessageElement[];
sourceType: MsgSourceType;
} }
export interface QueryMsgsParams { export interface QueryMsgsParams {
chatInfo: Peer; chatInfo: Peer;

View File

@@ -1,14 +1,15 @@
export interface IdMusicSignPostData { export interface IdMusicSignPostData {
type: 'qq' | '163', type: 'qq' | '163' | 'kugou' | 'migu' | 'kuwo',
id: string | number, id: string | number,
} }
export interface CustomMusicSignPostData { export interface CustomMusicSignPostData {
type: 'custom', type: 'qq' | '163' | 'kugou' | 'migu' | 'kuwo' | 'custom',
id: undefined,
url: string, url: string,
audio: string, audio?: string,
title: string, title?: string,
image?: string, image: string,
singer?: string singer?: string
} }

View File

@@ -288,9 +288,9 @@ export interface User {
export interface SelfInfo extends User { export interface SelfInfo extends User {
online?: boolean; online?: boolean;
} }
export type Friend = User;
export interface Friend extends User { // 本来是 Friend extends User 现在用不到
}
export enum BizKey { export enum BizKey {
KPRIVILEGEICON, KPRIVILEGEICON,

View File

@@ -1,10 +1,54 @@
{ {
"9.9.15-28060":{ "9.9.15-28060": {
"appid": 537246092, "appid": 537246092,
"qua": "V1_WIN_NQ_9.9.15_28060_GW_B" "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, "appid": 537246140,
"qua": "V1_LNX_NQ_3.2.12_28060_GW_B" "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"
} }
} }

View File

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

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

@@ -0,0 +1,22 @@
{
"3.2.12-28418-x64": {
"recv": "A0723E0",
"send": "A06EAE0"
},
"9.9.15-28418-x64": {
"recv": "37A9004",
"send": "37A4BD0"
},
"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"
}
}

View File

@@ -26,7 +26,8 @@ export class RkeyManager {
try { try {
await this.refreshRkey(); await this.refreshRkey();
} catch (e) { } catch (e) {
this.logger.logError('获取rkey失败', e); throw new Error(`获取rkey失败: ${e}`);//外抛
//this.logger.logError.bind(this.logger)('获取rkey失败', e);
} }
} }
return this.rkeyData; return this.rkeyData;
@@ -42,9 +43,18 @@ export class RkeyManager {
//刷新rkey //刷新rkey
for (const url of this.serverUrl) { for (const url of this.serverUrl) {
try { 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) { } 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 { NapCatPathWrapper } from '@/common/path';
import path from 'node:path'; import path from 'node:path';
import fs from 'node:fs'; 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 { NTEventWrapper } from '@/common/event';
import { DataSource, GroupMember, KickedOffLineInfo, SelfInfo, SelfStatusInfo } from '@/core/entities'; import { DataSource, GroupMember, KickedOffLineInfo, SelfInfo, SelfStatusInfo } from '@/core/entities';
import { NapCatConfigLoader } from '@/core/helper/config'; import { NapCatConfigLoader } from '@/core/helper/config';
import os from 'node:os'; import os from 'node:os';
import { NodeIKernelGroupListener, NodeIKernelMsgListener, NodeIKernelProfileListener } from '@/core/listeners'; import { NodeIKernelGroupListener, NodeIKernelMsgListener, NodeIKernelProfileListener } from '@/core/listeners';
import { proxiedListenerOf } from '@/common/proxy-handler'; import { proxiedListenerOf } from '@/common/proxy-handler';
import { NTQQPacketApi } from './apis/packet';
export * from './wrapper'; export * from './wrapper';
export * from './entities'; export * from './entities';
export * from './services'; export * from './services';
@@ -81,17 +81,18 @@ export class NapCatCore {
this.context = context; this.context = context;
this.util = this.context.wrapper.NodeQQNTWrapperUtil; this.util = this.context.wrapper.NodeQQNTWrapperUtil;
this.eventWrapper = new NTEventWrapper(context.session); this.eventWrapper = new NTEventWrapper(context.session);
this.configLoader = new NapCatConfigLoader(this, this.context.pathWrapper.configPath);
this.apis = { this.apis = {
FileApi: new NTQQFileApi(this.context, this), FileApi: new NTQQFileApi(this.context, this),
SystemApi: new NTQQSystemApi(this.context, this), SystemApi: new NTQQSystemApi(this.context, this),
CollectionApi: new NTQQCollectionApi(this.context, this), CollectionApi: new NTQQCollectionApi(this.context, this),
PacketApi: new NTQQPacketApi(this.context, this),
WebApi: new NTQQWebApi(this.context, this), WebApi: new NTQQWebApi(this.context, this),
FriendApi: new NTQQFriendApi(this.context, this), FriendApi: new NTQQFriendApi(this.context, this),
MsgApi: new NTQQMsgApi(this.context, this), MsgApi: new NTQQMsgApi(this.context, this),
UserApi: new NTQQUserApi(this.context, this), UserApi: new NTQQUserApi(this.context, this),
GroupApi: new NTQQGroupApi(this.context, this), GroupApi: new NTQQGroupApi(this.context, this),
}; };
this.configLoader = new NapCatConfigLoader(this, this.context.pathWrapper.configPath);
this.NapCatDataPath = path.join(this.dataPath, 'NapCat'); this.NapCatDataPath = path.join(this.dataPath, 'NapCat');
fs.mkdirSync(this.NapCatDataPath, { recursive: true }); fs.mkdirSync(this.NapCatDataPath, { recursive: true });
this.NapCatTempPath = path.join(this.NapCatDataPath, 'temp'); this.NapCatTempPath = path.join(this.NapCatDataPath, 'temp');
@@ -99,7 +100,8 @@ export class NapCatCore {
if (!fs.existsSync(this.NapCatTempPath)) { if (!fs.existsSync(this.NapCatTempPath)) {
fs.mkdirSync(this.NapCatTempPath, { recursive: true }); fs.mkdirSync(this.NapCatTempPath, { recursive: true });
} }
this.initNapCatCoreListeners().then().catch(this.context.logger.logError);
this.initNapCatCoreListeners().then().catch(this.context.logger.logError.bind(this.context.logger));
this.context.logger.setFileLogEnabled( this.context.logger.setFileLogEnabled(
this.configLoader.configData.fileLog, this.configLoader.configData.fileLog,
@@ -127,7 +129,7 @@ export class NapCatCore {
const msgListener = new NodeIKernelMsgListener(); const msgListener = new NodeIKernelMsgListener();
msgListener.onKickedOffLine = (Info: KickedOffLineInfo) => { 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; this.selfInfo.online = false;
}; };
msgListener.onRecvMsg = (msgs) => { msgListener.onRecvMsg = (msgs) => {
@@ -247,23 +249,22 @@ 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'); const downloadPath = path.join(account_path, 'NapCat', 'temp');
fs.mkdirSync(downloadPath, { recursive: true }); fs.mkdirSync(downloadPath, { recursive: true });
const guid: string = await getMachineId();//26702 支持JS获取guid值 在LoginService中获取 TODO mlikiow a const platformMapping: Partial<Record<NodeJS.Platform, PlatformType>> = {
//os.platform() win32: PlatformType.KWINDOWS,
let systemPlatform = PlatformType.KWINDOWS; darwin: PlatformType.KMAC,
switch (os.platform()) { linux: PlatformType.KLINUX,
case 'win32': };
systemPlatform = PlatformType.KWINDOWS; const systemPlatform = platformMapping[os.platform()] ?? PlatformType.KWINDOWS;
break;
case 'darwin':
systemPlatform = PlatformType.KMAC;
break;
case 'linux':
systemPlatform = PlatformType.KLINUX;
break;
}
return { return {
selfUin, selfUin,
selfUid, selfUid,
@@ -323,6 +324,7 @@ export interface InstanceContext {
export interface StableNTApiWrapper { export interface StableNTApiWrapper {
FileApi: NTQQFileApi, FileApi: NTQQFileApi,
SystemApi: NTQQSystemApi, SystemApi: NTQQSystemApi,
PacketApi: NTQQPacketApi,
CollectionApi: NTQQCollectionApi, CollectionApi: NTQQCollectionApi,
WebApi: NTQQWebApi, WebApi: NTQQWebApi,
FriendApi: NTQQFriendApi, FriendApi: NTQQFriendApi,

179
src/core/packet/client.ts Normal file
View File

@@ -0,0 +1,179 @@
import { LogWrapper } from "@/common/log";
import { LRUCache } from "@/common/lru-cache";
import WebSocket, { Data } from "ws";
import crypto, { createHash } from "crypto";
import { NapCatCore } from "@/core";
import { PacketHexStr } from "@/core/packet/packer";
import { sleep } from "@/common/helper";
export interface RecvPacket {
type: string, // 仅recv
trace_id_md5?: string,
data: RecvPacketData
}
export interface RecvPacketData {
seq: number
cmd: string
hex_data: string
}
export class PacketClient {
private websocket: WebSocket | undefined;
private isConnected: boolean = false;
private reconnectAttempts: number = 0;
private readonly maxReconnectAttempts: number = 5;//现在暂时不可配置
private readonly cb = new LRUCache<string, (json: RecvPacketData) => Promise<void>>(500); // trace_id-type callback
private readonly clientUrl: string = '';
readonly napCatCore: NapCatCore;
private readonly logger: LogWrapper;
constructor(url: string, core: NapCatCore) {
this.clientUrl = url;
this.napCatCore = core;
this.logger = core.context.logger;
}
get available(): boolean {
return this.isConnected && this.websocket !== undefined;
}
private randText(len: number) {
let text = '';
const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
for (let i = 0; i < len; i++) {
text += possible.charAt(Math.floor(Math.random() * possible.length));
}
return text;
}
connect(): Promise<void> {
return new Promise((resolve, reject) => {
//this.logger.log.bind(this.logger)(`[Core] [Packet Server] Attempting to connect to ${this.clientUrl}`);
this.websocket = new WebSocket(this.clientUrl);
this.websocket.on('error', (err) => {}/*this.logger.logError.bind(this.logger)('[Core] [Packet Server] Error:', err.message)*/);
this.websocket.onopen = () => {
this.isConnected = true;
this.reconnectAttempts = 0;
this.logger.log.bind(this.logger)(`[Core] [Packet Server] Connected to ${this.clientUrl}`);
resolve();
};
this.websocket.onerror = (error) => {
//this.logger.logError.bind(this.logger)(`WebSocket error: ${error}`);
reject(new Error(`${error.message}`));
};
this.websocket.onmessage = (event) => {
// const message = JSON.parse(event.data.toString());
// console.log("Received message:", message);
this.handleMessage(event.data).then().catch();
};
this.websocket.onclose = () => {
this.isConnected = false;
//this.logger.logWarn.bind(this.logger)(`[Core] [Packet Server] Disconnected from ${this.clientUrl}`);
this.attemptReconnect();
};
});
}
private attemptReconnect(): void {
try {
if (this.reconnectAttempts < this.maxReconnectAttempts) {
this.reconnectAttempts++;
setTimeout(() => {
this.connect().catch((error) => {
this.logger.logError.bind(this.logger)(`[Core] [Packet Server] Reconnecting attempt failed,${error.message}`);
});
}, 5000 * this.reconnectAttempts);
} else {
this.logger.logError.bind(this.logger)(`[Core] [Packet Server] Max reconnect attempts reached. ${this.clientUrl}`);
}
} catch (error: any) {
this.logger.logError.bind(this.logger)(`Error attempting to reconnect: ${error.message}`);
}
}
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);
}
async init(pid: number, recv: string, send: string): Promise<void> {
if (!this.isConnected || !this.websocket) {
throw new Error("WebSocket is not connected");
}
const initMessage = {
action: 'init',
pid: pid,
recv: recv,
send: send
};
this.websocket.send(JSON.stringify(initMessage));
}
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.isConnected || !this.websocket) {
throw new Error("WebSocket is not connected");
}
const commandMessage = {
action: 'send',
cmd: cmd,
data: data,
trace_id: trace_id
};
this.websocket.send(JSON.stringify(commandMessage));
if (rsp) {
this.registerCallback(trace_id, 'recv', async (json: RecvPacketData) => {
clearTimeout(timeoutHandle);
resolve(json);
});
}
this.registerCallback(trace_id, 'send', async (json: RecvPacketData) => {
sendcb(json);
if (!rsp) {
clearTimeout(timeoutHandle);
resolve(json);
}
});
const timeoutHandle = setTimeout(() => {
reject(new Error(`sendCommand timed out after ${timeout} ms for ${cmd} with trace_id ${trace_id}`));
}, timeout);
});
}
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);
}
//console.log("Received message:", json);
} catch (error) {
this.logger.logError.bind(this.logger)(`Error parsing message: ${error}`);
}
}
async sendPacket(cmd: string, data: PacketHexStr, rsp = false): Promise<RecvPacketData> {
// wtfk tx
// 校验失败和异常 可能返回undefined
return new Promise((resolve, reject) => {
if (!this.available) {
this.logger.logError('NapCat.Packet is not init');
return undefined;
}
const md5 = crypto.createHash('md5').update(data).digest('hex');
const trace_id = (this.randText(4) + md5 + data).slice(0, data.length / 2);
this.sendCommand(cmd, data, trace_id, rsp, 20000, async () => {
// await sleep(10);
await this.napCatCore.context.session.getMsgService().sendSsoCmdReqByContend(cmd, trace_id);
}).then((res) => resolve(res)).catch((e: Error) => reject(e));
});
}
}

View File

@@ -0,0 +1,72 @@
import * as stream from 'node:stream';
import {ReadStream} from "node:fs";
import {PacketHighwaySig} from "@/core/packet/highway/session";
import {HighwayHttpUploader, HighwayTcpUploader} from "@/core/packet/highway/uploader";
import {LogWrapper} from "@/common/log";
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: LogWrapper;
constructor(sig: PacketHighwaySig, logger: LogWrapper, 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 = 3600): 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.logError(`[Highway] upload failed: ${e}, fallback to http upload`);
try {
const httpUploader = new HighwayHttpUploader(trans, this.logger);
await httpUploader.upload();
} catch (e) {
this.logger.logError(`[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,171 @@
import * as fs from "node:fs";
import {ChatType, Peer} from "@/core";
import {LogWrapper} from "@/common/log";
import {PacketClient} from "@/core/packet/client";
import {PacketPacker} from "@/core/packet/packer";
import {NapProtoMsg} from "@/core/packet/proto/NapProto";
import {HttpConn0x6ff_501Response} from "@/core/packet/proto/action/action";
import {PacketHighwayClient} from "@/core/packet/highway/client";
import {NTV2RichMediaResp} from "@/core/packet/proto/oidb/common/Ntv2.RichMediaResp";
import {OidbSvcTrpcTcpBaseRsp} from "@/core/packet/proto/oidb/OidbBase";
import {PacketMsgPicElement} from "@/core/packet/msg/element";
import {NTV2RichMediaHighwayExt} from "@/core/packet/proto/highway/highway";
import {int32ip2str, oidbIpv4s2HighwayIpv4s} from "@/core/packet/highway/utils";
export const BlockSize = 1024 * 1024;
interface HighwayServerAddr {
ip: string
port: number
}
export interface PacketHighwaySig {
uin: string;
sigSession: Uint8Array | null
sessionKey: Uint8Array | null
serverAddr: HighwayServerAddr[]
}
export class PacketHighwaySession {
protected packetClient: PacketClient;
protected packetHighwayClient: PacketHighwayClient;
protected sig: PacketHighwaySig;
protected logger: LogWrapper;
protected packer: PacketPacker;
private cachedPrepareReq: Promise<void> | null = null;
constructor(logger: LogWrapper, client: PacketClient, packer: PacketPacker) {
this.packetClient = client;
this.logger = logger;
this.sig = {
uin: this.packetClient.napCatCore.selfInfo.uin,
sigSession: null,
sessionKey: null,
serverAddr: [],
}
this.packer = packer;
this.packetHighwayClient = new PacketHighwayClient(this.sig, this.logger);
}
private async checkAvailable() {
if (!this.packetClient.available) {
this.logger.logError('[Highway] packetServer not available!');
throw new Error('packetServer不可用请参照文档 https://napneko.github.io/config/advanced 检查packetServer状态或进行配置');
}
if (this.sig.sigSession === null || this.sig.sessionKey === null) {
this.logger.logWarn('[Highway] sigSession or sessionKey not available!');
if (this.cachedPrepareReq === null) {
this.cachedPrepareReq = this.prepareUpload().finally(() => {
this.cachedPrepareReq = null;
});
}
await this.cachedPrepareReq;
}
}
private async prepareUpload(): Promise<void> {
const packet = this.packer.packHttp0x6ff_501();
const req = await this.packetClient.sendPacket('HttpConn.0x6ff_501', packet, true);
const rsp = new NapProtoMsg(HttpConn0x6ff_501Response).decode(
Buffer.from(req.hex_data, 'hex')
);
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.logDebug(`[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.uploadGroupImageReq(Number(peer.peerUid), img);
} else if (peer.chatType === ChatType.KCHATTYPEC2C) {
await this.uploadC2CImageReq(peer.peerUid, img);
} else {
throw new Error(`[Highway] unsupported chatType: ${peer.chatType}`);
}
}
private async uploadGroupImageReq(groupUin: number, img: PacketMsgPicElement): Promise<void> {
const preReq = await this.packer.packUploadGroupImgReq(groupUin, img);
const preRespRaw = await this.packetClient.sendPacket('OidbSvcTrpcTcp.0x11c4_100', preReq, true);
const preResp = new NapProtoMsg(OidbSvcTrpcTcpBaseRsp).decode(
Buffer.from(preRespRaw.hex_data, 'hex')
);
const preRespData = new NapProtoMsg(NTV2RichMediaResp).decode(preResp.body);
const ukey = preRespData.upload.uKey;
if (ukey && ukey != "") {
this.logger.logDebug(`[Highway] 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(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.packetHighwayClient.upload(
1004,
fs.createReadStream(img.path, {highWaterMark: BlockSize}),
img.size,
md5,
extend
);
} else {
this.logger.logDebug(`[Highway] 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 uploadC2CImageReq(peerUid: string, img: PacketMsgPicElement): Promise<void> {
const preReq = await this.packer.packUploadC2CImgReq(peerUid, img);
const preRespRaw = await this.packetClient.sendPacket('OidbSvcTrpcTcp.0x11c5_100', preReq, true);
const preResp = new NapProtoMsg(OidbSvcTrpcTcpBaseRsp).decode(
Buffer.from(preRespRaw.hex_data, 'hex')
);
const preRespData = new NapProtoMsg(NTV2RichMediaResp).decode(preResp.body);
const ukey = preRespData.upload.uKey;
if (ukey && ukey != "") {
this.logger.logDebug(`[Highway] 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(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.packetHighwayClient.upload(
1003,
fs.createReadStream(img.path, {highWaterMark: BlockSize}),
img.size,
md5,
extend
);
}
img.msgInfo = preRespData.upload.msgInfo;
}
}

View File

@@ -0,0 +1,196 @@
import * as net from "node:net";
import * as crypto from "node:crypto";
import * as http from "node:http";
import * as stream from "node:stream";
import {LogWrapper} from "@/common/log";
import * as tea from "@/core/packet/utils/crypto/tea";
import {NapProtoMsg} from "@/core/packet/proto/NapProto";
import {ReqDataHighwayHead, RespDataHighwayHead} from "@/core/packet/proto/highway/highway";
import {BlockSize} from "@/core/packet/highway/session";
import {PacketHighwayTrans} from "@/core/packet/highway/client";
import {Frame} from "@/core/packet/highway/frame";
abstract class HighwayUploader {
readonly trans: PacketHighwayTrans;
readonly logger: LogWrapper;
constructor(trans: PacketHighwayTrans, logger: LogWrapper) {
this.trans = trans;
this.logger = logger;
}
encryptTransExt(key: Uint8Array) {
if (!this.trans.encrypt) return;
this.trans.ext = tea.encrypt(Buffer.from(this.trans.ext), Buffer.from(key));
}
buildPicUpHead(offset: number, bodyLength: number, bodyMd5: Uint8Array): Uint8Array {
return new NapProtoMsg(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>;
}
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 HighwayUploader {
async upload(): Promise<void> {
const highwayTransForm = new HighwayTcpUploaderTransform(this);
const upload = new Promise<void>((resolve, _) => {
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(RespDataHighwayHead).decode(header);
if (rsp.errorCode !== 0) {
this.logger.logWarn(`[Highway] tcpUpload failed (code: ${rsp.errorCode})`);
}
const percent = ((Number(rsp.msgSegHead?.dataOffset) + Number(rsp.msgSegHead?.dataLength)) / Number(rsp.msgSegHead?.filesize)).toFixed(2);
this.logger.logDebug(`[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.logDebug('[Highway] tcpUpload finished.');
socket.end();
resolve();
}
};
socket.on('data', (chunk: Buffer) => {
try {
const [head, _] = Frame.unpack(chunk);
handleRspHeader(head);
} catch (e) {
this.logger.logError(`[Highway] tcpUpload parse response error: ${e}`);
}
})
socket.on('close', () => {
this.logger.logDebug('[Highway] tcpUpload socket closed.');
resolve();
})
socket.on('error', (err) => {
this.logger.logError('[Highway] tcpUpload socket.on error:', err);
})
this.trans.data.on('error', (err) => {
this.logger.logError('[Highway] tcpUpload readable error:', err);
socket.end();
})
})
const timeout = new Promise<void>((_, reject) => {
setTimeout(() => {
reject(new Error(`[Highway] tcpUpload timeout after ${this.trans.timeout}s`))
}, (this.trans.timeout ?? Infinity) * 1000
)
})
await Promise.race([upload, timeout]);
}
}
// TODO: timeout impl
export class HighwayHttpUploader extends HighwayUploader {
async upload(): Promise<void> {
let offset = 0;
for await (const chunk of this.trans.data) {
let block = chunk as Buffer;
try {
await this.uploadBlock(block, offset);
} catch (err) {
this.logger.logError(`[Highway] httpUpload Error uploading block at offset ${offset}: ${err}`);
throw err;
}
offset += block.length;
}
}
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(RespDataHighwayHead).decode(head);
this.logger.logDebug(`[Highway] httpUploadBlock: ${headData.errorCode} | ${headData.msgSegHead?.retCode} | ${headData.bytesRspExtendInfo} | ${head.toString('hex')} | ${body.toString('hex')}`);
if (headData.errorCode !== 0) {
this.logger.logError(`[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) => {
let data = Buffer.alloc(0);
res.on('data', (chunk) => {
data = Buffer.concat([data, chunk]);
});
res.on('end', () => {
resolve(data);
});
});
req.write(frame);
req.on('error', (error) => {
reject(error);
});
} catch (error) {
reject(error);
}
});
}
}

View File

@@ -0,0 +1,20 @@
import {NapProtoEncodeStructType} from "@/core/packet/proto/NapProto";
import {IPv4} from "@/core/packet/proto/oidb/common/Ntv2.RichMediaResp";
import {NTHighwayIPv4} from "@/core/packet/proto/highway/highway";
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 IPv4>[]): NapProtoEncodeStructType<typeof NTHighwayIPv4>[] =>{
return ipv4s.map((ip) => {
return {
domain: {
isEnable: true,
ip: int32ip2str(ip.outIP!),
},
port: ip.outPort!
} as NapProtoEncodeStructType<typeof NTHighwayIPv4>
})
}

View File

@@ -0,0 +1,58 @@
import * as crypto from "crypto";
import {PushMsgBody} from "@/core/packet/proto/message/message";
import {NapProtoEncodeStructType} from "@/core/packet/proto/NapProto";
import {LogWrapper} from "@/common/log";
import {PacketMsg} from "@/core/packet/msg/message";
export class PacketMsgBuilder {
private logger: LogWrapper;
constructor(logger: LogWrapper) {
this.logger = logger;
}
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 msgElement = node.msg.flatMap(msg => msg.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: Math.floor(Date.now() / 1000),
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
}
}
};
});
}
}

View File

@@ -0,0 +1,131 @@
import {
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/msg/element";
import {PacketMsg, PacketSendMsgElement} from "@/core/packet/msg/message";
import {LogWrapper} from "@/common/log";
type SendMessageElementMap = {
textElement: SendTextElement,
picElement: SendPicElement,
replyElement: SendReplyElement,
faceElement: SendFaceElement,
marketFaceElement: SendMarketFaceElement,
videoElement: SendVideoElement,
fileElement: SendFileElement,
pttElement: SendPttElement,
arkElement: SendArkElement,
markdownElement: SendMarkdownElement,
structLongMsgElement: SendStructLongMsgElement
};
type RawToPacketMsgConverters = {
[K in keyof SendMessageElementMap]: (
element: SendMessageElementMap[K],
msg?: RawMessage,
elementWrapper?: MessageElement,
) => IPacketMsgElement<SendMessageElementMap[K]> | null;
};
export type rawMsgWithSendMsg = {
senderUin: number;
senderUid?: string;
senderName: string;
groupId?: number;
time: number;
msg: PacketSendMsgElement[]
}
export class PacketMsgConverter {
private logger: LogWrapper;
constructor(logger: LogWrapper) {
this.logger = logger;
}
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) => {
const key = (Object.keys(this.rawToPacketMsgConverters) as Array<keyof SendMessageElementMap>).find(
(k) => (element as any)[k] !== undefined // TODO:
);
if (key) {
const elementData = (element as any)[key]; // TODO:
if (elementData) return this.rawToPacketMsgConverters[key](element as any)
}
return null;
}).filter((e) => e !== null)
}
}
private rawToPacketMsgConverters: RawToPacketMsgConverters = {
textElement: (element: SendTextElement) => {
if (element.textElement.atType) {
return new PacketMsgAtElement(element)
}
return new PacketMsgTextElement(element)
},
picElement: (element: SendPicElement) => {
return new PacketMsgPicElement(element)
},
replyElement: (element: SendReplyElement) => {
return new PacketMsgReplyElement(element)
},
faceElement: (element: SendFaceElement) => {
return new PacketMsgFaceElement(element)
},
marketFaceElement: (element: SendMarketFaceElement) => {
return new PacketMsgMarkFaceElement(element)
},
videoElement: (element: SendVideoElement) => {
return new PacketMsgVideoElement(element)
},
fileElement: (element: SendFileElement) => {
return new PacketMsgFileElement(element)
},
pttElement: (element: SendPttElement) => {
return new PacketMsgPttElement(element)
},
arkElement: (element: SendArkElement) => {
return new PacketMsgLightAppElement(element)
},
markdownElement: (element: SendMarkdownElement) => {
return new PacketMsgMarkDownElement(element)
},
// TODO: check this logic, move it in arkElement?
structLongMsgElement: (element: SendStructLongMsgElement) => {
return new PacketMultiMsgElement(element)
}
}
}

View File

@@ -0,0 +1,423 @@
import assert from "node:assert";
import * as zlib from "node:zlib";
import * as crypto from "node:crypto";
import {NapProtoEncodeStructType, NapProtoMsg} from "@/core/packet/proto/NapProto";
import {
CustomFace,
Elem,
MarkdownData,
MentionExtra,
NotOnlineImage,
QBigFaceExtra,
QSmallFaceExtra
} from "@/core/packet/proto/message/element";
import {
AtType,
PicType,
SendArkElement,
SendFaceElement,
SendFileElement,
SendMarkdownElement,
SendMarketFaceElement,
SendPicElement,
SendPttElement,
SendReplyElement,
SendStructLongMsgElement,
SendTextElement,
SendVideoElement
} from "@/core";
import {MsgInfo} from "@/core/packet/proto/oidb/common/Ntv2.RichMediaReq";
import {PacketMsg, PacketSendMsgElement} from "@/core/packet/msg/message";
// raw <-> packet
// TODO: check ob11 -> raw impl!
// TODO: parse to raw element
// TODO: SendStructLongMsgElement
export abstract class IPacketMsgElement<T extends PacketSendMsgElement> {
protected constructor(rawElement: T) {
}
buildContent(): Uint8Array | undefined {
return undefined;
}
buildElement(): NapProtoEncodeStructType<typeof Elem>[] | undefined {
return undefined;
}
toPreview(): string {
return '[nya~]';
}
}
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,
}
)
}
}];
}
toPreview(): string {
return `@${this.targetUid} ${this.text}`;
}
}
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 = Number(element.picElement.fileSize);
this.md5 = element.picElement.md5HexStr ?? '';
this.width = element.picElement.picWidth;
this.height = element.picElement.picHeight;
this.picType = element.picElement.picType;
}
buildElement(): NapProtoEncodeStructType<typeof Elem>[] {
assert(this.msgInfo !== null, 'msgInfo is null, expected not null');
return [{
commonElem: {
serviceType: 48,
pbElem: new NapProtoMsg(MsgInfo).encode(this.msgInfo),
businessType: 10,
}
}]
}
toPreview(): string {
return "[图片]";
}
}
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 = Number(element.replyElement.replayMsgSeq ?? 0);
this.messageClientSeq = Number(element.replyElement.replyMsgClientSeq ?? 0);
this.targetUin = Number(element.replyElement.senderUin ?? 0);
this.targetUid = element.replyElement.senderUidStr ?? '';
this.time = Number(element.replyElement.replyMsgTime ?? 0);
this.elems = []; // TODO: 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: [], // TODO: 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 PacketMsgVideoElement extends IPacketMsgElement<SendVideoElement> {
constructor(element: SendVideoElement) {
super(element);
}
}
export class PacketMsgFileElement extends IPacketMsgElement<SendFileElement> {
constructor(element: SendFileElement) {
super(element);
}
}
export class PacketMsgPttElement extends IPacketMsgElement<SendPttElement> {
constructor(element: SendPttElement) {
super(element);
}
}
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 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 ?? [];
}
get isGroupMsg(): boolean {
return this.message.some(msg => msg.groupId !== undefined);
}
get JSON() {
const id = crypto.randomUUID();
return {
app: "com.tencent.multimsg",
config: {
autosize: 1,
forward: 1,
round: 1,
type: "normal",
width: 300
},
desc: "[聊天记录]",
extra: {
filename: id,
tsum: this.message.length,
},
meta: {
detail: {
news: this.message.length === 0 ? [{
text: "[Nya~ This message is send from NapCat.Packet!]",
}] : this.message.map(packetMsg => ({
text: `${packetMsg.senderName}: ${packetMsg.msg.map(msg => msg.toPreview()).join('')}`,
})),
resid: this.resid,
source: this.isGroupMsg ? "群聊的聊天记录" :
this.message.length
? Array.from(new Set(this.message.map(msg => msg.senderName)))
.join('和') + '的聊天记录'
: '聊天记录',
summary: `查看${this.message.length}条转发消息`,
uniseq: id,
}
},
prompt: "[聊天记录]",
ver: "0.0.0.5",
view: "contact",
}
}
buildElement(): NapProtoEncodeStructType<typeof Elem>[] {
return [{
lightAppElem: {
data: Buffer.concat([
Buffer.from([0x01]),
zlib.deflateSync(Buffer.from(JSON.stringify(this.JSON), 'utf-8'))
])
}
}]
}
toPreview(): string {
return "[聊天记录]";
}
}

View File

@@ -0,0 +1,15 @@
import {IPacketMsgElement} from "@/core/packet/msg/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>[]
}

324
src/core/packet/packer.ts Normal file
View File

@@ -0,0 +1,324 @@
import * as zlib from "node:zlib";
import * as crypto from "node:crypto";
import {calculateSha1} from "@/core/packet/utils/crypto/hash"
import {NapProtoMsg} from "@/core/packet/proto/NapProto";
import {OidbSvcTrpcTcpBase} from "@/core/packet/proto/oidb/OidbBase";
import {OidbSvcTrpcTcp0X9067_202} from "@/core/packet/proto/oidb/Oidb.0x9067_202";
import {OidbSvcTrpcTcp0X8FC_2, OidbSvcTrpcTcp0X8FC_2_Body} from "@/core/packet/proto/oidb/Oidb.0x8FC_2";
import {OidbSvcTrpcTcp0XFE1_2} from "@/core/packet/proto/oidb/Oidb.0XFE1_2";
import {OidbSvcTrpcTcp0XED3_1} from "@/core/packet/proto/oidb/Oidb.0xED3_1";
import {NTV2RichMediaReq} from "@/core/packet/proto/oidb/common/Ntv2.RichMediaReq";
import {HttpConn0x6ff_501} from "@/core/packet/proto/action/action";
import {LongMsgResult, SendLongMsgReq} from "@/core/packet/proto/message/action";
import {PacketMsgBuilder} from "@/core/packet/msg/builder";
import {PacketMsgPicElement} from "@/core/packet/msg/element";
import {LogWrapper} from "@/common/log";
import {PacketMsg} from "@/core/packet/msg/message";
import {OidbSvcTrpcTcp0x6D6} from "@/core/packet/proto/oidb/Oidb.0x6D6";
import {OidbSvcTrpcTcp0XE37_1200} from "@/core/packet/proto/oidb/Oidb.0xE37_1200";
import {PacketMsgConverter} from "@/core/packet/msg/converter";
import {PacketClient} from "@/core/packet/client";
export type PacketHexStr = string & { readonly hexNya: unique symbol };
export class PacketPacker {
readonly logger: LogWrapper;
readonly client: PacketClient;
readonly packetBuilder: PacketMsgBuilder;
readonly packetConverter: PacketMsgConverter;
constructor(logger: LogWrapper, client: PacketClient) {
this.logger = logger;
this.client = client;
this.packetBuilder = new PacketMsgBuilder(logger);
this.packetConverter = new PacketMsgConverter(logger);
}
private toHexStr(byteArray: Uint8Array): PacketHexStr {
return Buffer.from(byteArray).toString('hex') as PacketHexStr;
}
packOidbPacket(cmd: number, subCmd: number, body: Uint8Array, isUid: boolean = true, isLafter: boolean = false): Uint8Array {
return new NapProtoMsg(OidbSvcTrpcTcpBase).encode({
command: cmd,
subCommand: subCmd,
body: body,
isReserved: isUid ? 1 : 0
});
}
packPokePacket(group: number, peer: number): PacketHexStr {
const oidb_0xed3 = new NapProtoMsg(OidbSvcTrpcTcp0XED3_1).encode({
uin: peer,
groupUin: group,
friendUin: group,
ext: 0
});
return this.toHexStr(this.packOidbPacket(0xed3, 1, oidb_0xed3));
}
packRkeyPacket(): PacketHexStr {
const oidb_0x9067_202 = new NapProtoMsg(OidbSvcTrpcTcp0X9067_202).encode({
reqHead: {
common: {
requestId: 1,
command: 202
},
scene: {
requestType: 2,
businessType: 1,
sceneType: 0
},
client: {
agentType: 2
}
},
downloadRKeyReq: {
key: [10, 20, 2]
},
});
return this.toHexStr(this.packOidbPacket(0x9067, 202, oidb_0x9067_202));
}
packSetSpecialTittlePacket(groupCode: string, uid: string, tittle: string): PacketHexStr {
const oidb_0x8FC_2_body = new NapProtoMsg(OidbSvcTrpcTcp0X8FC_2_Body).encode({
targetUid: uid,
specialTitle: tittle,
expiredTime: -1,
uinName: tittle
});
const oidb_0x8FC_2 = new NapProtoMsg(OidbSvcTrpcTcp0X8FC_2).encode({
groupUin: +groupCode,
body: oidb_0x8FC_2_body
});
return this.toHexStr(this.packOidbPacket(0x8FC, 2, oidb_0x8FC_2, false, false));
}
packStatusPacket(uin: number): PacketHexStr {
const oidb_0xfe1_2 = new NapProtoMsg(OidbSvcTrpcTcp0XFE1_2).encode({
uin: uin,
key: [{key: 27372}]
});
return this.toHexStr(this.packOidbPacket(0xfe1, 2, oidb_0xfe1_2));
}
async packUploadForwardMsg(selfUid: string, msg: PacketMsg[], groupUin: number = 0): Promise<PacketHexStr> {
const msgBody = this.packetBuilder.buildFakeMsg(selfUid, msg);
const longMsgResultData = new NapProtoMsg(LongMsgResult).encode(
{
action: {
actionCommand: "MultiMsg",
actionData: {
msgBody: msgBody
}
}
}
);
const payload = zlib.gzipSync(Buffer.from(longMsgResultData));
const req = new NapProtoMsg(SendLongMsgReq).encode(
{
info: {
type: groupUin === 0 ? 1 : 3,
uid: {
uid: groupUin === 0 ? selfUid : groupUin.toString(),
},
groupUin: groupUin,
payload: payload
},
settings: {
field1: 4, field2: 1, field3: 7, field4: 0
}
}
);
// this.logger.logDebug("packUploadForwardMsg REQ!!!", req);
return this.toHexStr(req);
}
// highway part
packHttp0x6ff_501(): PacketHexStr {
return this.toHexStr(new NapProtoMsg(HttpConn0x6ff_501).encode({
httpConn: {
field1: 0,
field2: 0,
field3: 16,
field4: 1,
field6: 3,
serviceTypes: [1, 5, 10, 21],
// tgt: "", // TODO: do we really need tgt? seems not
field9: 2,
field10: 9,
field11: 8,
ver: "1.0.1"
}
}));
}
async packUploadGroupImgReq(groupUin: number, img: PacketMsgPicElement): Promise<PacketHexStr> {
const req = new NapProtoMsg(NTV2RichMediaReq).encode(
{
reqHead: {
common: {
requestId: 1,
command: 100
},
scene: {
requestType: 2,
businessType: 1,
sceneType: 2,
group: {
groupUin: groupUin
},
},
client: {
agentType: 2
}
},
upload: {
uploadInfo: [
{
fileInfo: {
fileSize: Number(img.size),
fileHash: img.md5,
fileSha1: this.toHexStr(await calculateSha1(img.path)),
fileName: img.name,
type: {
type: 1,
picFormat: img.picType, //TODO: extend NapCat imgType /cc @MliKiowa
videoFormat: 0,
voiceFormat: 0,
},
width: img.width,
height: img.height,
time: 0,
original: 1
},
subFileType: 0,
}
],
tryFastUploadCompleted: true,
srvSendMsg: false,
clientRandomId: crypto.randomBytes(8).readBigUInt64BE() & BigInt('0x7FFFFFFFFFFFFFFF'),
compatQMsgSceneType: 2,
extBizInfo: {
pic: {
bytesPbReserveTroop: Buffer.from("0800180020004200500062009201009a0100a2010c080012001800200028003a00", 'hex'),
textSummary: "Nya~", // TODO:
},
video: {
bytesPbReserve: Buffer.alloc(0),
},
ptt: {
bytesPbReserve: Buffer.alloc(0),
bytesReserve: Buffer.alloc(0),
bytesGeneralFlags: Buffer.alloc(0),
}
},
clientSeq: 0,
noNeedCompatMsg: false,
}
}
)
return this.toHexStr(this.packOidbPacket(0x11c4, 100, req, true, false));
}
async packUploadC2CImgReq(peerUin: string, img: PacketMsgPicElement): Promise<PacketHexStr> {
const req = new NapProtoMsg(NTV2RichMediaReq).encode({
reqHead: {
common: {
requestId: 1,
command: 100
},
scene: {
requestType: 2,
businessType: 1,
sceneType: 1,
c2C: {
accountType: 2,
targetUid: peerUin
},
},
client: {
agentType: 2,
}
},
upload: {
uploadInfo: [
{
fileInfo: {
fileSize: Number(img.size),
fileHash: img.md5,
fileSha1: this.toHexStr(await calculateSha1(img.path)),
fileName: img.name,
type: {
type: 1,
picFormat: img.picType, //TODO: extend NapCat imgType /cc @MliKiowa
videoFormat: 0,
voiceFormat: 0,
},
width: img.width,
height: img.height,
time: 0,
original: 1
},
subFileType: 0,
}
],
tryFastUploadCompleted: true,
srvSendMsg: false,
clientRandomId: crypto.randomBytes(8).readBigUInt64BE() & BigInt('0x7FFFFFFFFFFFFFFF'),
compatQMsgSceneType: 1,
extBizInfo: {
pic: {
bytesPbReserveTroop: Buffer.from("0800180020004200500062009201009a0100a2010c080012001800200028003a00", 'hex'),
textSummary: "Nya~", // TODO:
},
video: {
bytesPbReserve: Buffer.alloc(0),
},
ptt: {
bytesPbReserve: Buffer.alloc(0),
bytesReserve: Buffer.alloc(0),
bytesGeneralFlags: Buffer.alloc(0),
}
},
clientSeq: 0,
noNeedCompatMsg: false,
}
}
)
return this.toHexStr(this.packOidbPacket(0x11c5, 100, req, true, false));
}
packGroupFileDownloadReq(groupUin: number, fileUUID: string): PacketHexStr {
return this.toHexStr(
this.packOidbPacket(0x6D6, 2, new NapProtoMsg(OidbSvcTrpcTcp0x6D6).encode({
download: {
groupUin: groupUin,
appId: 7,
busId: 102,
fileId: fileUUID
}
}), true, false)
)
}
packC2CFileDownloadReq(selfUid: string, fileUUID: string, fileHash: string): PacketHexStr {
return this.toHexStr(
new NapProtoMsg(OidbSvcTrpcTcp0XE37_1200).encode({
subCommand: 1200,
field2: 1,
body: {
receiverUid: selfUid,
fileUuid: fileUUID,
type: 2,
fileHash: fileHash,
t2: 0
},
field101: 3,
field102: 103,
field200: 1,
field99999: Buffer.from([0xc0, 0x85, 0x2c, 0x01])
})
)
}
}

View File

@@ -0,0 +1,139 @@
import { MessageType, PartialMessage, RepeatType, ScalarType } from '@protobuf-ts/runtime';
import { PartialFieldInfo } from "@protobuf-ts/runtime/build/types/reflection-info";
type LowerCamelCase<S extends string> = CamelCaseHelper<S, false, true>;
type CamelCaseHelper<
S extends string,
CapNext extends boolean,
IsFirstChar extends boolean
> = S extends `${infer F}${infer R}`
? F extends '_'
? CamelCaseHelper<R, true, false>
: F extends `${number}`
? `${F}${CamelCaseHelper<R, true, false>}`
: CapNext extends true
? `${Uppercase<F>}${CamelCaseHelper<R, false, false>}`
: IsFirstChar extends true
? `${Lowercase<F>}${CamelCaseHelper<R, false, false>}`
: `${F}${CamelCaseHelper<R, false, false>}`
: '';
type ScalarTypeToTsType<T extends ScalarType> =
T extends ScalarType.DOUBLE | ScalarType.FLOAT | ScalarType.INT32 | ScalarType.FIXED32 | ScalarType.UINT32 | ScalarType.SFIXED32 | ScalarType.SINT32 ? number :
T extends ScalarType.INT64 | ScalarType.UINT64 | ScalarType.FIXED64 | ScalarType.SFIXED64 | ScalarType.SINT64 ? bigint :
T extends ScalarType.BOOL ? boolean :
T extends ScalarType.STRING ? string :
T extends ScalarType.BYTES ? Uint8Array :
never;
interface BaseProtoFieldType<T, O extends boolean, R extends O extends true ? false : boolean> {
kind: 'scalar' | 'message';
no: number;
type: T;
optional: O;
repeat: R;
}
interface ScalarProtoFieldType<T extends ScalarType, O extends boolean, R extends O extends true ? false : boolean> extends BaseProtoFieldType<T, O, R> {
kind: 'scalar';
}
interface MessageProtoFieldType<T extends () => ProtoMessageType, O extends boolean, R extends O extends true ? false : boolean> extends BaseProtoFieldType<T, O, R> {
kind: 'message';
}
type ProtoFieldType =
| ScalarProtoFieldType<ScalarType, boolean, boolean>
| MessageProtoFieldType<() => ProtoMessageType, boolean, boolean>;
type ProtoMessageType = {
[key: string]: ProtoFieldType;
};
export function ProtoField<T extends ScalarType, O extends boolean = false, R extends O extends true ? false : boolean = false>(no: number, type: T, optional?: O, repeat?: R): ScalarProtoFieldType<T, O, R>;
export function ProtoField<T extends () => ProtoMessageType, O extends boolean = false, R extends O extends true ? false : boolean = false>(no: number, type: T, optional?: O, repeat?: R): MessageProtoFieldType<T, O, R>;
export function ProtoField(no: number, type: ScalarType | (() => ProtoMessageType), optional?: boolean, repeat?: boolean): ProtoFieldType {
if (typeof type === 'function') {
return { kind: 'message', no: no, type: type, optional: optional ?? false, repeat: repeat ?? false };
} else {
return { kind: 'scalar', no: no, type: type, optional: optional ?? false, repeat: repeat ?? false };
}
}
type ProtoFieldReturnType<T extends unknown, E extends boolean> = NonNullable<T> extends ScalarProtoFieldType<infer S, infer O, infer R>
? ScalarTypeToTsType<S>
: T extends NonNullable<MessageProtoFieldType<infer S, infer O, infer R>>
? NonNullable<NapProtoStructType<ReturnType<S>, E>>
: never;
type RequiredFieldsBaseType<T extends unknown, E extends boolean> = {
[K in keyof T as T[K] extends { optional: true } ? never : LowerCamelCase<K & string>]:
T[K] extends { repeat: true }
? ProtoFieldReturnType<T[K], E>[]
: ProtoFieldReturnType<T[K], E>
}
type OptionalFieldsBaseType<T extends unknown, E extends boolean> = {
[K in keyof T as T[K] extends { optional: true } ? LowerCamelCase<K & string> : never]?:
T[K] extends { repeat: true }
? ProtoFieldReturnType<T[K], E>[]
: ProtoFieldReturnType<T[K], E>
}
type RequiredFieldsType<T extends unknown, E extends boolean> = E extends true ? Partial<RequiredFieldsBaseType<T, E>> : RequiredFieldsBaseType<T, E>;
type OptionalFieldsType<T extends unknown, E extends boolean> = E extends true ? Partial<OptionalFieldsBaseType<T, E>> : OptionalFieldsBaseType<T, E>;
type NapProtoStructType<T extends unknown, E extends boolean> = RequiredFieldsType<T, E> & OptionalFieldsType<T, E>;
export type NapProtoEncodeStructType<T extends unknown> = NapProtoStructType<T, true>;
export type NapProtoDecodeStructType<T extends unknown> = NapProtoStructType<T, false>;
const NapProtoMsgCache = new Map<ProtoMessageType, MessageType<NapProtoStructType<ProtoMessageType, boolean>>>();
export class NapProtoMsg<T extends ProtoMessageType> {
private readonly _msg: T;
private readonly _field: PartialFieldInfo[];
private readonly _proto_msg: MessageType<NapProtoStructType<T, boolean>>;
constructor(fields: T) {
this._msg = fields;
this._field = Object.keys(fields).map(key => {
const field = fields[key];
if (field.kind === 'scalar') {
const repeatType = field.repeat
? [ScalarType.STRING, ScalarType.BYTES].includes(field.type)
? RepeatType.UNPACKED
: RepeatType.PACKED
: RepeatType.NO;
return {
no: field.no,
name: key,
kind: 'scalar',
T: field.type,
opt: field.optional,
repeat: repeatType,
};
} else if (field.kind === 'message') {
return {
no: field.no,
name: key,
kind: 'message',
repeat: field.repeat ? RepeatType.PACKED : RepeatType.NO,
T: () => new NapProtoMsg(field.type())._proto_msg,
};
}
}) as PartialFieldInfo[];
this._proto_msg = new MessageType<NapProtoStructType<T, boolean>>('nya', this._field);
}
encode(data: NapProtoEncodeStructType<T>): Uint8Array {
return this._proto_msg.toBinary(this._proto_msg.create(data as PartialMessage<NapProtoEncodeStructType<T>>));
}
decode(data: Uint8Array): NapProtoDecodeStructType<T> {
return this._proto_msg.fromBinary(data) as NapProtoDecodeStructType<T>;
}
}

View File

@@ -0,0 +1,114 @@
import { ScalarType } from "@protobuf-ts/runtime";
import { ProtoField } from "../NapProto";
import {ContentHead, MessageBody, MessageControl, RoutingHead} from "@/core/packet/proto/message/message";
export const FaceRoamRequest = {
comm: ProtoField(1, () => PlatInfo, true),
selfUin: ProtoField(2, ScalarType.UINT32),
subCmd: ProtoField(3, ScalarType.UINT32),
field6: ProtoField(6, ScalarType.UINT32),
};
export const PlatInfo = {
imPlat: ProtoField(1, ScalarType.UINT32),
osVersion: ProtoField(2, ScalarType.STRING, true),
qVersion: ProtoField(3, ScalarType.STRING, true),
};
export const FaceRoamResponse = {
retCode: ProtoField(1, ScalarType.UINT32),
errMsg: ProtoField(2, ScalarType.STRING),
subCmd: ProtoField(3, ScalarType.UINT32),
userInfo: ProtoField(6, () => FaceRoamUserInfo),
};
export const FaceRoamUserInfo = {
fileName: ProtoField(1, ScalarType.STRING, false, true),
deleteFile: ProtoField(2, ScalarType.STRING, false, true),
bid: ProtoField(3, ScalarType.STRING),
maxRoamSize: ProtoField(4, ScalarType.UINT32),
emojiType: ProtoField(5, ScalarType.UINT32, false, true),
};
export const SendMessageRequest = {
state: ProtoField(1, ScalarType.INT32),
sizeCache: ProtoField(2, ScalarType.INT32),
unknownFields: ProtoField(3, ScalarType.BYTES),
routingHead: ProtoField(4, () => RoutingHead),
contentHead: ProtoField(5, () => ContentHead),
messageBody: ProtoField(6, () => MessageBody),
msgSeq: ProtoField(7, ScalarType.INT32),
msgRand: ProtoField(8, ScalarType.INT32),
syncCookie: ProtoField(9, ScalarType.BYTES),
msgVia: ProtoField(10, ScalarType.INT32),
dataStatist: ProtoField(11, ScalarType.INT32),
messageControl: ProtoField(12, () => MessageControl),
multiSendSeq: ProtoField(13, ScalarType.INT32),
};
export const SendMessageResponse = {
result: ProtoField(1, ScalarType.INT32),
errMsg: ProtoField(2, ScalarType.STRING, true),
timestamp1: ProtoField(3, ScalarType.UINT32),
field10: ProtoField(10, ScalarType.UINT32),
groupSequence: ProtoField(11, ScalarType.UINT32, true),
timestamp2: ProtoField(12, ScalarType.UINT32),
privateSequence: ProtoField(14, ScalarType.UINT32),
};
export const SetStatus = {
status: ProtoField(1, ScalarType.UINT32),
extStatus: ProtoField(2, ScalarType.UINT32),
batteryStatus: ProtoField(3, ScalarType.UINT32),
customExt: ProtoField(4, () => SetStatusCustomExt, true),
};
export const SetStatusCustomExt = {
faceId: ProtoField(1, ScalarType.UINT32),
text: ProtoField(2, ScalarType.STRING, true),
field3: ProtoField(3, ScalarType.UINT32),
};
export const SetStatusResponse = {
message: ProtoField(2, ScalarType.STRING),
};
export const HttpConn = {
field1: ProtoField(1, ScalarType.INT32),
field2: ProtoField(2, ScalarType.INT32),
field3: ProtoField(3, ScalarType.INT32),
field4: ProtoField(4, ScalarType.INT32),
tgt: ProtoField(5, ScalarType.STRING),
field6: ProtoField(6, ScalarType.INT32),
serviceTypes: ProtoField(7, ScalarType.INT32, false, true),
field9: ProtoField(9, ScalarType.INT32),
field10: ProtoField(10, ScalarType.INT32),
field11: ProtoField(11, ScalarType.INT32),
ver: ProtoField(15, ScalarType.STRING),
};
export const HttpConn0x6ff_501 = {
httpConn: ProtoField(0x501, () => HttpConn),
};
export const HttpConn0x6ff_501Response = {
httpConn: ProtoField(0x501, () => HttpConnResponse),
};
export const HttpConnResponse = {
sigSession: ProtoField(1, ScalarType.BYTES),
sessionKey: ProtoField(2, ScalarType.BYTES),
serverInfos: ProtoField(3, () => ServerInfo, false, true),
};
export const ServerAddr = {
type: ProtoField(1, ScalarType.UINT32),
ip: ProtoField(2, ScalarType.FIXED32),
port: ProtoField(3, ScalarType.UINT32),
area: ProtoField(4, ScalarType.UINT32),
};
export const ServerInfo = {
serviceType: ProtoField(1, ScalarType.UINT32),
serverAddrs: ProtoField(2, () => ServerAddr, false, true),
};

View File

@@ -0,0 +1,155 @@
import {ScalarType} from "@protobuf-ts/runtime";
import {ProtoField} from "../NapProto";
import {MsgInfo, MsgInfoBody} from "@/core/packet/proto/oidb/common/Ntv2.RichMediaReq";
export const DataHighwayHead = {
version: ProtoField(1, ScalarType.UINT32),
uin: ProtoField(2, ScalarType.STRING, true),
command: ProtoField(3, ScalarType.STRING, true),
seq: ProtoField(4, ScalarType.UINT32, true),
retryTimes: ProtoField(5, ScalarType.UINT32, true),
appId: ProtoField(6, ScalarType.UINT32),
dataFlag: ProtoField(7, ScalarType.UINT32),
commandId: ProtoField(8, ScalarType.UINT32),
buildVer: ProtoField(9, ScalarType.BYTES, true),
}
export const FileUploadExt = {
unknown1: ProtoField(1, ScalarType.INT32),
unknown2: ProtoField(2, ScalarType.INT32),
unknown3: ProtoField(3, ScalarType.INT32),
entry: ProtoField(100, () => FileUploadEntry),
unknown200: ProtoField(200, ScalarType.INT32),
}
export const FileUploadEntry = {
busiBuff: ProtoField(100, () => ExcitingBusiInfo),
fileEntry: ProtoField(200, () => ExcitingFileEntry),
clientInfo: ProtoField(300, () => ExcitingClientInfo),
fileNameInfo: ProtoField(400, () => ExcitingFileNameInfo),
host: ProtoField(500, () => ExcitingHostConfig),
}
export const ExcitingBusiInfo = {
busId: ProtoField(1, ScalarType.INT32),
senderUin: ProtoField(100, ScalarType.UINT64),
receiverUin: ProtoField(200, ScalarType.UINT64),
groupCode: ProtoField(400, ScalarType.UINT64),
}
export const ExcitingFileEntry = {
fileSize: ProtoField(100, ScalarType.UINT64),
md5: ProtoField(200, ScalarType.BYTES),
checkKey: ProtoField(300, ScalarType.BYTES),
md5S2: ProtoField(400, ScalarType.BYTES),
fileId: ProtoField(600, ScalarType.STRING),
uploadKey: ProtoField(700, ScalarType.BYTES),
}
export const ExcitingClientInfo = {
clientType: ProtoField(100, ScalarType.INT32),
appId: ProtoField(200, ScalarType.STRING),
terminalType: ProtoField(300, ScalarType.INT32),
clientVer: ProtoField(400, ScalarType.STRING),
unknown: ProtoField(600, ScalarType.INT32),
}
export const ExcitingFileNameInfo = {
fileName: ProtoField(100, ScalarType.STRING),
}
export const ExcitingHostConfig = {
hosts: ProtoField(200, () => ExcitingHostInfo, false, true),
}
export const ExcitingHostInfo = {
url: ProtoField(1, () => ExcitingUrlInfo),
port: ProtoField(2, ScalarType.UINT32),
}
export const ExcitingUrlInfo = {
unknown: ProtoField(1, ScalarType.INT32),
host: ProtoField(2, ScalarType.STRING),
}
export const LoginSigHead = {
uint32LoginSigType: ProtoField(1, ScalarType.UINT32),
bytesLoginSig: ProtoField(2, ScalarType.BYTES),
appId: ProtoField(3, ScalarType.UINT32),
}
export const NTV2RichMediaHighwayExt = {
fileUuid: ProtoField(1, ScalarType.STRING),
uKey: ProtoField(2, ScalarType.STRING),
network: ProtoField(5, () => NTHighwayNetwork),
msgInfoBody: ProtoField(6, () => MsgInfoBody, false, true),
blockSize: ProtoField(10, ScalarType.UINT32),
hash: ProtoField(11, () => NTHighwayHash),
}
export const NTHighwayHash = {
fileSha1: ProtoField(1, ScalarType.BYTES, false, true),
}
export const NTHighwayNetwork = {
ipv4s: ProtoField(1, () => NTHighwayIPv4, false, true),
}
export const NTHighwayIPv4 = {
domain: ProtoField(1, () => NTHighwayDomain),
port: ProtoField(2, ScalarType.UINT32),
}
export const NTHighwayDomain = {
isEnable: ProtoField(1, ScalarType.BOOL),
ip: ProtoField(2, ScalarType.STRING),
}
export const ReqDataHighwayHead = {
msgBaseHead: ProtoField(1, () => DataHighwayHead, true),
msgSegHead: ProtoField(2, () => SegHead, true),
bytesReqExtendInfo: ProtoField(3, ScalarType.BYTES, true),
timestamp: ProtoField(4, ScalarType.UINT64),
msgLoginSigHead: ProtoField(5, () => LoginSigHead, true),
}
export const RespDataHighwayHead = {
msgBaseHead: ProtoField(1, () => DataHighwayHead, true),
msgSegHead: ProtoField(2, () => SegHead, true),
errorCode: ProtoField(3, ScalarType.UINT32),
allowRetry: ProtoField(4, ScalarType.UINT32),
cacheCost: ProtoField(5, ScalarType.UINT32),
htCost: ProtoField(6, ScalarType.UINT32),
bytesRspExtendInfo: ProtoField(7, ScalarType.BYTES, true),
timestamp: ProtoField(8, ScalarType.UINT64),
range: ProtoField(9, ScalarType.UINT64),
isReset: ProtoField(10, ScalarType.UINT32),
}
export const SegHead = {
serviceId: ProtoField(1, ScalarType.UINT32, true),
filesize: ProtoField(2, ScalarType.UINT64),
dataOffset: ProtoField(3, ScalarType.UINT64, true),
dataLength: ProtoField(4, ScalarType.UINT32),
retCode: ProtoField(5, ScalarType.UINT32, true),
serviceTicket: ProtoField(6, ScalarType.BYTES),
flag: ProtoField(7, ScalarType.UINT32, true),
md5: ProtoField(8, ScalarType.BYTES),
fileMd5: ProtoField(9, ScalarType.BYTES),
cacheAddr: ProtoField(10, ScalarType.UINT32, true),
queryTimes: ProtoField(11, ScalarType.UINT32),
updateCacheIp: ProtoField(12, ScalarType.UINT32),
cachePort: ProtoField(13, ScalarType.UINT32, true),
}
export const GroupAvatarExtra = {
type: ProtoField(1, ScalarType.UINT32),
groupUin: ProtoField(2, ScalarType.UINT32),
field3: ProtoField(3, () => GroupAvatarExtraField3),
field5: ProtoField(5, ScalarType.UINT32),
field6: ProtoField(6, ScalarType.UINT32),
}
export const GroupAvatarExtraField3 = {
field1: ProtoField(1, ScalarType.UINT32),
}

View File

@@ -0,0 +1,117 @@
import { ScalarType } from "@protobuf-ts/runtime";
import { ProtoField } from "../NapProto";
import { PushMsgBody } from "@/core/packet/proto/message/message";
export const LongMsgResult = {
action: ProtoField(2, () => LongMsgAction)
};
export const LongMsgAction = {
actionCommand: ProtoField(1, ScalarType.STRING),
actionData: ProtoField(2, () => LongMsgContent)
};
export const LongMsgContent = {
msgBody: ProtoField(1, () => PushMsgBody, false, true)
};
export const RecvLongMsgReq = {
info: ProtoField(1, () => RecvLongMsgInfo, true),
settings: ProtoField(15, () => LongMsgSettings, true)
};
export const RecvLongMsgInfo = {
uid: ProtoField(1, () => LongMsgUid, true),
resId: ProtoField(2, ScalarType.STRING, true),
acquire: ProtoField(3, ScalarType.BOOL)
};
export const LongMsgUid = {
uid: ProtoField(2, ScalarType.STRING, true)
};
export const LongMsgSettings = {
field1: ProtoField(1, ScalarType.UINT32),
field2: ProtoField(2, ScalarType.UINT32),
field3: ProtoField(3, ScalarType.UINT32),
field4: ProtoField(4, ScalarType.UINT32)
};
export const RecvLongMsgResp = {
result: ProtoField(1, () => RecvLongMsgResult),
settings: ProtoField(15, () => LongMsgSettings)
};
export const RecvLongMsgResult = {
resId: ProtoField(3, ScalarType.STRING),
payload: ProtoField(4, ScalarType.BYTES)
};
export const SendLongMsgReq = {
info: ProtoField(2, () => SendLongMsgInfo),
settings: ProtoField(15, () => LongMsgSettings)
};
export const SendLongMsgInfo = {
type: ProtoField(1, ScalarType.UINT32),
uid: ProtoField(2, () => LongMsgUid, true),
groupUin: ProtoField(3, ScalarType.UINT32, true),
payload: ProtoField(4, ScalarType.BYTES, true)
};
export const SendLongMsgResp = {
result: ProtoField(2, () => SendLongMsgResult),
settings: ProtoField(15, () => LongMsgSettings)
};
export const SendLongMsgResult = {
resId: ProtoField(3, ScalarType.STRING)
};
export const SsoGetGroupMsg = {
info: ProtoField(1, () => SsoGetGroupMsgInfo),
direction: ProtoField(2, ScalarType.BOOL)
};
export const SsoGetGroupMsgInfo = {
groupUin: ProtoField(1, ScalarType.UINT32),
startSequence: ProtoField(2, ScalarType.UINT32),
endSequence: ProtoField(3, ScalarType.UINT32)
};
export const SsoGetGroupMsgResponse = {
body: ProtoField(3, () => SsoGetGroupMsgResponseBody)
};
export const SsoGetGroupMsgResponseBody = {
groupUin: ProtoField(3, ScalarType.UINT32),
startSequence: ProtoField(4, ScalarType.UINT32),
endSequence: ProtoField(5, ScalarType.UINT32),
messages: ProtoField(6, () => PushMsgBody, false, true)
};
export const SsoGetRoamMsg = {
friendUid: ProtoField(1, ScalarType.STRING, true),
time: ProtoField(2, ScalarType.UINT32),
random: ProtoField(3, ScalarType.UINT32),
count: ProtoField(4, ScalarType.UINT32),
direction: ProtoField(5, ScalarType.BOOL)
};
export const SsoGetRoamMsgResponse = {
friendUid: ProtoField(3, ScalarType.STRING),
timestamp: ProtoField(5, ScalarType.UINT32),
random: ProtoField(6, ScalarType.UINT32),
messages: ProtoField(7, () => PushMsgBody, false, true)
};
export const SsoGetC2cMsg = {
friendUid: ProtoField(2, ScalarType.STRING, true),
startSequence: ProtoField(3, ScalarType.UINT32),
endSequence: ProtoField(4, ScalarType.UINT32)
};
export const SsoGetC2cMsgResponse = {
friendUid: ProtoField(4, ScalarType.STRING),
messages: ProtoField(7, () => PushMsgBody, false, true)
};

View File

@@ -0,0 +1,11 @@
import { ScalarType } from "@protobuf-ts/runtime";
import { ProtoField } from "../NapProto";
export const C2C = {
uin: ProtoField(1, ScalarType.UINT32, true),
uid: ProtoField(2, ScalarType.STRING, true),
field3: ProtoField(3, ScalarType.UINT32, true),
sig: ProtoField(4, ScalarType.UINT32, true),
receiverUin: ProtoField(5, ScalarType.UINT32, true),
receiverUid: ProtoField(6, ScalarType.STRING, true),
};

View File

@@ -0,0 +1,147 @@
import { ScalarType } from "@protobuf-ts/runtime";
import { ProtoField } from "../NapProto";
import { Elem } from "@/core/packet/proto/message/element";
export const Attr = {
codePage: ProtoField(1, ScalarType.INT32),
time: ProtoField(2, ScalarType.INT32),
random: ProtoField(3, ScalarType.INT32),
color: ProtoField(4, ScalarType.INT32),
size: ProtoField(5, ScalarType.INT32),
effect: ProtoField(6, ScalarType.INT32),
charSet: ProtoField(7, ScalarType.INT32),
pitchAndFamily: ProtoField(8, ScalarType.INT32),
fontName: ProtoField(9, ScalarType.STRING),
reserveData: ProtoField(10, ScalarType.BYTES),
};
export const NotOnlineFile = {
fileType: ProtoField(1, ScalarType.INT32, true),
sig: ProtoField(2, ScalarType.BYTES, true),
fileUuid: ProtoField(3, ScalarType.STRING, true),
fileMd5: ProtoField(4, ScalarType.BYTES, true),
fileName: ProtoField(5, ScalarType.STRING, true),
fileSize: ProtoField(6, ScalarType.INT64, true),
note: ProtoField(7, ScalarType.BYTES, true),
reserved: ProtoField(8, ScalarType.INT32, true),
subcmd: ProtoField(9, ScalarType.INT32, true),
microCloud: ProtoField(10, ScalarType.INT32, true),
bytesFileUrls: ProtoField(11, ScalarType.BYTES, false, true),
downloadFlag: ProtoField(12, ScalarType.INT32, true),
dangerEvel: ProtoField(50, ScalarType.INT32, true),
lifeTime: ProtoField(51, ScalarType.INT32, true),
uploadTime: ProtoField(52, ScalarType.INT32, true),
absFileType: ProtoField(53, ScalarType.INT32, true),
clientType: ProtoField(54, ScalarType.INT32, true),
expireTime: ProtoField(55, ScalarType.INT32, true),
pbReserve: ProtoField(56, ScalarType.BYTES, true),
fileHash: ProtoField(57, ScalarType.STRING, true),
};
export const Ptt = {
fileType: ProtoField(1, ScalarType.INT32),
srcUin: ProtoField(2, ScalarType.UINT64),
fileUuid: ProtoField(3, ScalarType.STRING),
fileMd5: ProtoField(4, ScalarType.BYTES),
fileName: ProtoField(5, ScalarType.STRING),
fileSize: ProtoField(6, ScalarType.INT32),
reserve: ProtoField(7, ScalarType.BYTES),
fileId: ProtoField(8, ScalarType.INT32),
serverIp: ProtoField(9, ScalarType.INT32),
serverPort: ProtoField(10, ScalarType.INT32),
boolValid: ProtoField(11, ScalarType.BOOL),
signature: ProtoField(12, ScalarType.BYTES),
shortcut: ProtoField(13, ScalarType.BYTES),
fileKey: ProtoField(14, ScalarType.BYTES),
magicPttIndex: ProtoField(15, ScalarType.INT32),
voiceSwitch: ProtoField(16, ScalarType.INT32),
pttUrl: ProtoField(17, ScalarType.BYTES),
groupFileKey: ProtoField(18, ScalarType.STRING),
time: ProtoField(19, ScalarType.INT32),
downPara: ProtoField(20, ScalarType.BYTES),
format: ProtoField(29, ScalarType.INT32),
pbReserve: ProtoField(30, ScalarType.BYTES),
bytesPttUrls: ProtoField(31, ScalarType.BYTES, false, true),
downloadFlag: ProtoField(32, ScalarType.INT32),
};
export const RichText = {
attr: ProtoField(1, () => Attr, true),
elems: ProtoField(2, () => Elem, false, true),
notOnlineFile: ProtoField(3, () => NotOnlineFile, true),
ptt: ProtoField(4, () => Ptt, true),
};
export const ButtonExtra = {
data: ProtoField(1, () => KeyboardData),
};
export const KeyboardData = {
rows: ProtoField(1, () => Row, false, true),
};
export const Row = {
buttons: ProtoField(1, () => Button, false, true),
};
export const Button = {
id: ProtoField(1, ScalarType.STRING),
renderData: ProtoField(2, () => RenderData),
action: ProtoField(3, () => Action),
};
export const RenderData = {
label: ProtoField(1, ScalarType.STRING),
visitedLabel: ProtoField(2, ScalarType.STRING),
style: ProtoField(3, ScalarType.INT32),
};
export const Action = {
type: ProtoField(1, ScalarType.INT32),
permission: ProtoField(2, () => Permission),
unsupportTips: ProtoField(4, ScalarType.STRING),
data: ProtoField(5, ScalarType.STRING),
reply: ProtoField(7, ScalarType.BOOL),
enter: ProtoField(8, ScalarType.BOOL),
};
export const Permission = {
type: ProtoField(1, ScalarType.INT32),
specifyRoleIds: ProtoField(2, ScalarType.STRING, false, true),
specifyUserIds: ProtoField(3, ScalarType.STRING, false, true),
};
export const FileExtra = {
file: ProtoField(1, () => NotOnlineFile),
};
export const GroupFileExtra = {
field1: ProtoField(1, ScalarType.UINT32),
fileName: ProtoField(2, ScalarType.STRING),
display: ProtoField(3, ScalarType.STRING),
inner: ProtoField(7, () => GroupFileExtraInner),
};
export const GroupFileExtraInner = {
info: ProtoField(2, () => GroupFileExtraInfo),
};
export const GroupFileExtraInfo = {
busId: ProtoField(1, ScalarType.UINT32),
fileId: ProtoField(2, ScalarType.STRING),
fileSize: ProtoField(3, ScalarType.UINT64),
fileName: ProtoField(4, ScalarType.STRING),
field5: ProtoField(5, ScalarType.UINT32),
field7: ProtoField(7, ScalarType.STRING),
fileMd5: ProtoField(8, ScalarType.STRING),
};
export const ImageExtraUrl = {
origUrl: ProtoField(30, ScalarType.STRING),
};
export const PokeExtra = {
type: ProtoField(1, ScalarType.UINT32),
field7: ProtoField(7, ScalarType.UINT32),
field8: ProtoField(8, ScalarType.UINT32),
};

View File

@@ -0,0 +1,361 @@
import {ScalarType} from "@protobuf-ts/runtime";
import {ProtoField} from "../NapProto";
export const Elem = {
text: ProtoField(1, () => Text, true),
face: ProtoField(2, () => Face, true),
onlineImage: ProtoField(3, () => OnlineImage, true),
notOnlineImage: ProtoField(4, () => NotOnlineImage, true),
transElem: ProtoField(5, () => TransElem, true),
marketFace: ProtoField(6, () => MarketFace, true),
customFace: ProtoField(8, () => CustomFace, true),
elemFlags2: ProtoField(9, () => ElemFlags2, true),
richMsg: ProtoField(12, () => RichMsg, true),
groupFile: ProtoField(13, () => GroupFile, true),
extraInfo: ProtoField(16, () => ExtraInfo, true),
videoFile: ProtoField(19, () => VideoFile, true),
anonymousGroupMessage: ProtoField(21, () => AnonymousGroupMessage, true),
customElem: ProtoField(31, () => CustomElem, true),
generalFlags: ProtoField(37, () => GeneralFlags, true),
srcMsg: ProtoField(45, () => SrcMsg, true),
lightAppElem: ProtoField(51, () => LightAppElem, true),
commonElem: ProtoField(53, () => CommonElem, true),
};
export const Text = {
str: ProtoField(1, ScalarType.STRING, true),
lint: ProtoField(2, ScalarType.STRING, true),
attr6Buf: ProtoField(3, ScalarType.BYTES, true),
attr7Buf: ProtoField(4, ScalarType.BYTES, true),
buf: ProtoField(11, ScalarType.BYTES, true),
pbReserve: ProtoField(12, ScalarType.BYTES, true),
};
export const Face = {
index: ProtoField(1, ScalarType.INT32, true),
old: ProtoField(2, ScalarType.BYTES, true),
buf: ProtoField(11, ScalarType.BYTES, true),
};
export const OnlineImage = {
guid: ProtoField(1, ScalarType.BYTES),
filePath: ProtoField(2, ScalarType.BYTES),
oldVerSendFile: ProtoField(3, ScalarType.BYTES),
};
export const NotOnlineImage = {
filePath: ProtoField(1, ScalarType.STRING),
fileLen: ProtoField(2, ScalarType.UINT32),
downloadPath: ProtoField(3, ScalarType.STRING),
oldVerSendFile: ProtoField(4, ScalarType.BYTES),
imgType: ProtoField(5, ScalarType.INT32),
previewsImage: ProtoField(6, ScalarType.BYTES),
picMd5: ProtoField(7, ScalarType.BYTES),
picHeight: ProtoField(8, ScalarType.UINT32),
picWidth: ProtoField(9, ScalarType.UINT32),
resId: ProtoField(10, ScalarType.STRING),
flag: ProtoField(11, ScalarType.BYTES),
thumbUrl: ProtoField(12, ScalarType.STRING),
original: ProtoField(13, ScalarType.INT32),
bigUrl: ProtoField(14, ScalarType.STRING),
origUrl: ProtoField(15, ScalarType.STRING),
bizType: ProtoField(16, ScalarType.INT32),
result: ProtoField(17, ScalarType.INT32),
index: ProtoField(18, ScalarType.INT32),
opFaceBuf: ProtoField(19, ScalarType.BYTES),
oldPicMd5: ProtoField(20, ScalarType.BOOL),
thumbWidth: ProtoField(21, ScalarType.INT32),
thumbHeight: ProtoField(22, ScalarType.INT32),
fileId: ProtoField(23, ScalarType.INT32),
showLen: ProtoField(24, ScalarType.UINT32),
downloadLen: ProtoField(25, ScalarType.UINT32),
x400Url: ProtoField(26, ScalarType.STRING),
x400Width: ProtoField(27, ScalarType.INT32),
x400Height: ProtoField(28, ScalarType.INT32),
pbRes: ProtoField(29, () => NotOnlineImage_PbReserve),
};
export const NotOnlineImage_PbReserve = {
subType: ProtoField(1, ScalarType.INT32),
field3: ProtoField(3, ScalarType.INT32),
field4: ProtoField(4, ScalarType.INT32),
summary: ProtoField(8, ScalarType.STRING),
field10: ProtoField(10, ScalarType.INT32),
field20: ProtoField(20, () => NotOnlineImage_PbReserve2),
url: ProtoField(30, ScalarType.STRING),
md5Str: ProtoField(31, ScalarType.STRING),
};
export const NotOnlineImage_PbReserve2 = {
field1: ProtoField(1, ScalarType.INT32),
field2: ProtoField(2, ScalarType.STRING),
field3: ProtoField(3, ScalarType.INT32),
field4: ProtoField(4, ScalarType.INT32),
field5: ProtoField(5, ScalarType.INT32),
field7: ProtoField(7, ScalarType.STRING),
};
export const TransElem = {
elemType: ProtoField(1, ScalarType.INT32),
elemValue: ProtoField(2, ScalarType.BYTES),
};
export const MarketFace = {
faceName: ProtoField(1, ScalarType.STRING),
itemType: ProtoField(2, ScalarType.INT32),
faceInfo: ProtoField(3, ScalarType.INT32),
faceId: ProtoField(4, ScalarType.BYTES),
tabId: ProtoField(5, ScalarType.INT32),
subType: ProtoField(6, ScalarType.INT32),
key: ProtoField(7, ScalarType.STRING),
param: ProtoField(8, ScalarType.BYTES),
mediaType: ProtoField(9, ScalarType.INT32),
imageWidth: ProtoField(10, ScalarType.INT32),
imageHeight: ProtoField(11, ScalarType.INT32),
mobileparam: ProtoField(12, ScalarType.BYTES),
pbReserve: ProtoField(13, () => MarketFacePbRes),
};
export const MarketFacePbRes = {
field8: ProtoField(8, ScalarType.INT32)
}
export const CustomFace = {
guid: ProtoField(1, ScalarType.BYTES),
filePath: ProtoField(2, ScalarType.STRING),
shortcut: ProtoField(3, ScalarType.STRING),
buffer: ProtoField(4, ScalarType.BYTES),
flag: ProtoField(5, ScalarType.BYTES),
oldData: ProtoField(6, ScalarType.BYTES, true),
fileId: ProtoField(7, ScalarType.UINT32),
serverIp: ProtoField(8, ScalarType.INT32, true),
serverPort: ProtoField(9, ScalarType.INT32, true),
fileType: ProtoField(10, ScalarType.INT32),
signature: ProtoField(11, ScalarType.BYTES),
useful: ProtoField(12, ScalarType.INT32),
md5: ProtoField(13, ScalarType.BYTES),
thumbUrl: ProtoField(14, ScalarType.STRING),
bigUrl: ProtoField(15, ScalarType.STRING),
origUrl: ProtoField(16, ScalarType.STRING),
bizType: ProtoField(17, ScalarType.INT32),
repeatIndex: ProtoField(18, ScalarType.INT32),
repeatImage: ProtoField(19, ScalarType.INT32),
imageType: ProtoField(20, ScalarType.INT32),
index: ProtoField(21, ScalarType.INT32),
width: ProtoField(22, ScalarType.INT32),
height: ProtoField(23, ScalarType.INT32),
source: ProtoField(24, ScalarType.INT32),
size: ProtoField(25, ScalarType.UINT32),
origin: ProtoField(26, ScalarType.INT32),
thumbWidth: ProtoField(27, ScalarType.INT32, true),
thumbHeight: ProtoField(28, ScalarType.INT32, true),
showLen: ProtoField(29, ScalarType.INT32),
downloadLen: ProtoField(30, ScalarType.INT32),
x400Url: ProtoField(31, ScalarType.STRING, true),
x400Width: ProtoField(32, ScalarType.INT32),
x400Height: ProtoField(33, ScalarType.INT32),
pbRes: ProtoField(34, () => CustomFace_PbReserve, true),
};
export const CustomFace_PbReserve = {
subType: ProtoField(1, ScalarType.INT32),
summary: ProtoField(9, ScalarType.STRING),
};
export const ElemFlags2 = {
colorTextId: ProtoField(1, ScalarType.UINT32),
msgId: ProtoField(2, ScalarType.UINT64),
whisperSessionId: ProtoField(3, ScalarType.UINT32),
pttChangeBit: ProtoField(4, ScalarType.UINT32),
vipStatus: ProtoField(5, ScalarType.UINT32),
compatibleId: ProtoField(6, ScalarType.UINT32),
insts: ProtoField(7, () => Instance, false, true),
msgRptCnt: ProtoField(8, ScalarType.UINT32),
srcInst: ProtoField(9, () => Instance),
longtitude: ProtoField(10, ScalarType.UINT32),
latitude: ProtoField(11, ScalarType.UINT32),
customFont: ProtoField(12, ScalarType.UINT32),
pcSupportDef: ProtoField(13, () => PcSupportDef),
crmFlags: ProtoField(14, ScalarType.UINT32, true),
};
export const PcSupportDef = {
pcPtlBegin: ProtoField(1, ScalarType.UINT32),
pcPtlEnd: ProtoField(2, ScalarType.UINT32),
macPtlBegin: ProtoField(3, ScalarType.UINT32),
macPtlEnd: ProtoField(4, ScalarType.UINT32),
ptlsSupport: ProtoField(5, ScalarType.INT32, false, true),
ptlsNotSupport: ProtoField(6, ScalarType.UINT32, false, true),
};
export const Instance = {
appId: ProtoField(1, ScalarType.UINT32),
instId: ProtoField(2, ScalarType.UINT32),
};
export const RichMsg = {
template1: ProtoField(1, ScalarType.BYTES, true),
serviceId: ProtoField(2, ScalarType.INT32, true),
msgResId: ProtoField(3, ScalarType.BYTES, true),
rand: ProtoField(4, ScalarType.INT32, true),
seq: ProtoField(5, ScalarType.UINT32, true),
};
export const GroupFile = {
filename: ProtoField(1, ScalarType.BYTES),
fileSize: ProtoField(2, ScalarType.UINT64),
fileId: ProtoField(3, ScalarType.BYTES),
batchId: ProtoField(4, ScalarType.BYTES),
fileKey: ProtoField(5, ScalarType.BYTES),
mark: ProtoField(6, ScalarType.BYTES),
sequence: ProtoField(7, ScalarType.UINT64),
batchItemId: ProtoField(8, ScalarType.BYTES),
feedMsgTime: ProtoField(9, ScalarType.INT32),
pbReserve: ProtoField(10, ScalarType.BYTES),
};
export const ExtraInfo = {
nick: ProtoField(1, ScalarType.BYTES),
groupCard: ProtoField(2, ScalarType.BYTES),
level: ProtoField(3, ScalarType.INT32),
flags: ProtoField(4, ScalarType.INT32),
groupMask: ProtoField(5, ScalarType.INT32),
msgTailId: ProtoField(6, ScalarType.INT32),
senderTitle: ProtoField(7, ScalarType.BYTES),
apnsTips: ProtoField(8, ScalarType.BYTES),
uin: ProtoField(9, ScalarType.UINT64),
msgStateFlag: ProtoField(10, ScalarType.INT32),
apnsSoundType: ProtoField(11, ScalarType.INT32),
newGroupFlag: ProtoField(12, ScalarType.INT32),
};
export const VideoFile = {
fileUuid: ProtoField(1, ScalarType.STRING),
fileMd5: ProtoField(2, ScalarType.BYTES),
fileName: ProtoField(3, ScalarType.STRING),
fileFormat: ProtoField(4, ScalarType.INT32),
fileTime: ProtoField(5, ScalarType.INT32),
fileSize: ProtoField(6, ScalarType.INT32),
thumbWidth: ProtoField(7, ScalarType.INT32),
thumbHeight: ProtoField(8, ScalarType.INT32),
thumbFileMd5: ProtoField(9, ScalarType.BYTES),
source: ProtoField(10, ScalarType.BYTES),
thumbFileSize: ProtoField(11, ScalarType.INT32),
busiType: ProtoField(12, ScalarType.INT32),
fromChatType: ProtoField(13, ScalarType.INT32),
toChatType: ProtoField(14, ScalarType.INT32),
boolSupportProgressive: ProtoField(15, ScalarType.BOOL),
fileWidth: ProtoField(16, ScalarType.INT32),
fileHeight: ProtoField(17, ScalarType.INT32),
subBusiType: ProtoField(18, ScalarType.INT32),
videoAttr: ProtoField(19, ScalarType.INT32),
bytesThumbFileUrls: ProtoField(20, ScalarType.BYTES, false, true),
bytesVideoFileUrls: ProtoField(21, ScalarType.BYTES, false, true),
thumbDownloadFlag: ProtoField(22, ScalarType.INT32),
videoDownloadFlag: ProtoField(23, ScalarType.INT32),
pbReserve: ProtoField(24, ScalarType.BYTES),
};
export const AnonymousGroupMessage = {
flags: ProtoField(1, ScalarType.INT32),
anonId: ProtoField(2, ScalarType.BYTES),
anonNick: ProtoField(3, ScalarType.BYTES),
headPortrait: ProtoField(4, ScalarType.INT32),
expireTime: ProtoField(5, ScalarType.INT32),
bubbleId: ProtoField(6, ScalarType.INT32),
rankColor: ProtoField(7, ScalarType.BYTES),
};
export const CustomElem = {
desc: ProtoField(1, ScalarType.BYTES),
data: ProtoField(2, ScalarType.BYTES),
enumType: ProtoField(3, ScalarType.INT32),
ext: ProtoField(4, ScalarType.BYTES),
sound: ProtoField(5, ScalarType.BYTES),
};
export const GeneralFlags = {
bubbleDiyTextId: ProtoField(1, ScalarType.INT32),
groupFlagNew: ProtoField(2, ScalarType.INT32),
uin: ProtoField(3, ScalarType.UINT64),
rpId: ProtoField(4, ScalarType.BYTES),
prpFold: ProtoField(5, ScalarType.INT32),
longTextFlag: ProtoField(6, ScalarType.INT32),
longTextResId: ProtoField(7, ScalarType.STRING, true),
groupType: ProtoField(8, ScalarType.INT32),
toUinFlag: ProtoField(9, ScalarType.INT32),
glamourLevel: ProtoField(10, ScalarType.INT32),
memberLevel: ProtoField(11, ScalarType.INT32),
groupRankSeq: ProtoField(12, ScalarType.UINT64),
olympicTorch: ProtoField(13, ScalarType.INT32),
babyqGuideMsgCookie: ProtoField(14, ScalarType.BYTES),
uin32ExpertFlag: ProtoField(15, ScalarType.INT32),
bubbleSubId: ProtoField(16, ScalarType.INT32),
pendantId: ProtoField(17, ScalarType.UINT64),
rpIndex: ProtoField(18, ScalarType.BYTES),
pbReserve: ProtoField(19, ScalarType.BYTES),
};
export const SrcMsg = {
origSeqs: ProtoField(1, ScalarType.UINT32, false, true),
senderUin: ProtoField(2, ScalarType.UINT64),
time: ProtoField(3, ScalarType.INT32, true),
flag: ProtoField(4, ScalarType.INT32, true),
elems: ProtoField(5, () => Elem, false, true),
type: ProtoField(6, ScalarType.INT32, true),
richMsg: ProtoField(7, ScalarType.BYTES, true),
pbReserve: ProtoField(8, () => SrcMsgPbRes, true),
sourceMsg: ProtoField(9, ScalarType.BYTES, true),
toUin: ProtoField(10, ScalarType.UINT64, true),
troopName: ProtoField(11, ScalarType.BYTES, true),
};
export const SrcMsgPbRes = {
messageId: ProtoField(3, ScalarType.UINT64),
senderUid: ProtoField(6, ScalarType.STRING, true),
receiverUid: ProtoField(7, ScalarType.STRING, true),
friendSeq: ProtoField(8, ScalarType.UINT32, true),
}
export const LightAppElem = {
data: ProtoField(1, ScalarType.BYTES),
msgResid: ProtoField(2, ScalarType.BYTES, true),
};
export const CommonElem = {
serviceType: ProtoField(1, ScalarType.INT32),
pbElem: ProtoField(2, ScalarType.BYTES),
businessType: ProtoField(3, ScalarType.UINT32),
};
export const FaceExtra = {
faceId: ProtoField(1, ScalarType.INT32, true),
};
export const MentionExtra = {
type: ProtoField(3, ScalarType.INT32, true),
uin: ProtoField(4, ScalarType.UINT32, true),
field5: ProtoField(5, ScalarType.INT32, true),
uid: ProtoField(9, ScalarType.STRING, true),
};
export const QBigFaceExtra = {
AniStickerPackId: ProtoField(1, ScalarType.STRING, true),
AniStickerId: ProtoField(2, ScalarType.STRING, true),
faceId: ProtoField(3, ScalarType.INT32, true),
Field4: ProtoField(4, ScalarType.INT32, true),
AniStickerType: ProtoField(5, ScalarType.INT32, true),
field6: ProtoField(6, ScalarType.STRING, true),
preview: ProtoField(7, ScalarType.STRING, true),
field9: ProtoField(9, ScalarType.INT32, true),
};
export const QSmallFaceExtra = {
faceId: ProtoField(1, ScalarType.UINT32),
preview: ProtoField(2, ScalarType.STRING),
preview2: ProtoField(3, ScalarType.STRING),
};
export const MarkdownData = {
content: ProtoField(1, ScalarType.STRING)
}

View File

@@ -0,0 +1,19 @@
import { ScalarType } from "@protobuf-ts/runtime";
import { ProtoField } from "../NapProto";
export const GroupRecallMsg = {
type: ProtoField(1, ScalarType.UINT32),
groupUin: ProtoField(2, ScalarType.UINT32),
field3: ProtoField(3, () => GroupRecallMsgField3),
field4: ProtoField(4, () => GroupRecallMsgField4),
};
export const GroupRecallMsgField3 = {
sequence: ProtoField(1, ScalarType.UINT32),
random: ProtoField(2, ScalarType.UINT32),
field3: ProtoField(3, ScalarType.UINT32),
};
export const GroupRecallMsgField4 = {
field1: ProtoField(1, ScalarType.UINT32),
};

View File

@@ -0,0 +1,75 @@
import { ScalarType } from "@protobuf-ts/runtime";
import { ProtoField } from "../NapProto";
import { ForwardHead, Grp, GrpTmp, ResponseForward, ResponseGrp, Trans0X211, WPATmp } from "@/core/packet/proto/message/routing";
import { RichText } from "@/core/packet/proto/message/component";
import { C2C } from "@/core/packet/proto/message/c2c";
export const ContentHead = {
type: ProtoField(1, ScalarType.UINT32),
subType: ProtoField(2, ScalarType.UINT32, true),
divSeq: ProtoField(3, ScalarType.UINT32, true),
msgId: ProtoField(4, ScalarType.UINT32, true),
sequence: ProtoField(5, ScalarType.UINT32, true),
timeStamp: ProtoField(6, ScalarType.UINT32, true),
field7: ProtoField(7, ScalarType.UINT64, true),
field8: ProtoField(8, ScalarType.UINT32, true),
field9: ProtoField(9, ScalarType.UINT32, true),
newId: ProtoField(12, ScalarType.UINT64, true),
forward: ProtoField(15, () => ForwardHead, true),
};
export const MessageBody = {
richText: ProtoField(1, () => RichText, true),
msgContent: ProtoField(2, ScalarType.BYTES, true),
msgEncryptContent: ProtoField(3, ScalarType.BYTES, true),
};
export const Message = {
routingHead: ProtoField(1, () => RoutingHead, true),
contentHead: ProtoField(2, () => ContentHead, true),
body: ProtoField(3, () => MessageBody, true),
clientSequence: ProtoField(4, ScalarType.UINT32, true),
random: ProtoField(5, ScalarType.UINT32, true),
syncCookie: ProtoField(6, ScalarType.BYTES, true),
via: ProtoField(8, ScalarType.UINT32, true),
dataStatist: ProtoField(9, ScalarType.UINT32, true),
ctrl: ProtoField(12, () => MessageControl, true),
multiSendSeq: ProtoField(14, ScalarType.UINT32),
};
export const MessageControl = {
msgFlag: ProtoField(1, ScalarType.INT32),
};
export const PushMsg = {
message: ProtoField(1, () => PushMsgBody),
status: ProtoField(3, ScalarType.INT32, true),
pingFlag: ProtoField(5, ScalarType.INT32, true),
generalFlag: ProtoField(9, ScalarType.INT32, true),
};
export const PushMsgBody = {
responseHead: ProtoField(1, () => ResponseHead),
contentHead: ProtoField(2, () => ContentHead),
body: ProtoField(3, () => MessageBody, true),
};
export const ResponseHead = {
fromUin: ProtoField(1, ScalarType.UINT32),
fromUid: ProtoField(2, ScalarType.STRING, true),
type: ProtoField(3, ScalarType.UINT32),
sigMap: ProtoField(4, ScalarType.UINT32),
toUin: ProtoField(5, ScalarType.UINT32),
toUid: ProtoField(6, ScalarType.STRING, true),
forward: ProtoField(7, () => ResponseForward, true),
grp: ProtoField(8, () => ResponseGrp, true),
};
export const RoutingHead = {
c2c: ProtoField(1, () => C2C, true),
grp: ProtoField(2, () => Grp, true),
grpTmp: ProtoField(3, () => GrpTmp, true),
wpaTmp: ProtoField(6, () => WPATmp, true),
trans0X211: ProtoField(15, () => Trans0X211, true),
};

View File

@@ -0,0 +1,22 @@
import { ScalarType } from "@protobuf-ts/runtime";
import { ProtoField } from "../NapProto";
export const FriendRecall = {
info: ProtoField(1, () => FriendRecallInfo),
instId: ProtoField(2, ScalarType.UINT32),
appId: ProtoField(3, ScalarType.UINT32),
longMessageFlag: ProtoField(4, ScalarType.UINT32),
reserved: ProtoField(5, ScalarType.BYTES),
};
export const FriendRecallInfo = {
fromUid: ProtoField(1, ScalarType.STRING),
toUid: ProtoField(2, ScalarType.STRING),
sequence: ProtoField(3, ScalarType.UINT32),
newId: ProtoField(4, ScalarType.UINT64),
time: ProtoField(5, ScalarType.UINT32),
random: ProtoField(6, ScalarType.UINT32),
pkgNum: ProtoField(7, ScalarType.UINT32),
pkgIndex: ProtoField(8, ScalarType.UINT32),
divSeq: ProtoField(9, ScalarType.UINT32),
};

View File

@@ -0,0 +1,41 @@
import { ScalarType } from "@protobuf-ts/runtime";
import { ProtoField } from "../NapProto";
export const ForwardHead = {
field1: ProtoField(1, ScalarType.UINT32, true),
field2: ProtoField(2, ScalarType.UINT32, true),
field3: ProtoField(3, ScalarType.UINT32, true),
unknownBase64: ProtoField(5, ScalarType.STRING, true),
avatar: ProtoField(6, ScalarType.STRING, true),
};
export const Grp = {
groupCode: ProtoField(1, ScalarType.UINT32, true),
};
export const GrpTmp = {
groupUin: ProtoField(1, ScalarType.UINT32, true),
toUin: ProtoField(2, ScalarType.UINT32, true),
};
export const ResponseForward = {
friendName: ProtoField(6, ScalarType.STRING, true),
};
export const ResponseGrp = {
groupUin: ProtoField(1, ScalarType.UINT32),
memberName: ProtoField(4, ScalarType.STRING),
unknown5: ProtoField(5, ScalarType.UINT32),
groupName: ProtoField(7, ScalarType.STRING),
};
export const Trans0X211 = {
toUin: ProtoField(1, ScalarType.UINT64, true),
ccCmd: ProtoField(2, ScalarType.UINT32, true),
uid: ProtoField(8, ScalarType.STRING, true),
};
export const WPATmp = {
toUin: ProtoField(1, ScalarType.UINT64),
sig: ProtoField(2, ScalarType.BYTES),
};

View File

@@ -0,0 +1,23 @@
import { ScalarType } from "@protobuf-ts/runtime";
import { ProtoField } from "../NapProto";
export const OidbSvcTrpcTcp0XFE1_2 = {
uin: ProtoField(1, ScalarType.UINT32),
key: ProtoField(3, () => OidbSvcTrpcTcp0XFE1_2Key, false, true),
};
export const OidbSvcTrpcTcp0XFE1_2Key = {
key: ProtoField(1, ScalarType.UINT32)
};
export const OidbSvcTrpcTcp0XFE1_2RSP_Status = {
key: ProtoField(1, ScalarType.UINT32),
value: ProtoField(2, ScalarType.UINT64)
};
export const OidbSvcTrpcTcp0XFE1_2RSP_Data = {
status: ProtoField(2, () => OidbSvcTrpcTcp0XFE1_2RSP_Status)
};
export const OidbSvcTrpcTcp0XFE1_2RSP = {
data: ProtoField(1, () => OidbSvcTrpcTcp0XFE1_2RSP_Data)
};

View File

@@ -0,0 +1,100 @@
import { ScalarType } from "@protobuf-ts/runtime";
import { ProtoField } from "../NapProto";
export const OidbSvcTrpcTcp0x6D6 = {
file: ProtoField(1, () => OidbSvcTrpcTcp0x6D6Upload, true),
download: ProtoField(3, () => OidbSvcTrpcTcp0x6D6Download, true),
delete: ProtoField(4, () => OidbSvcTrpcTcp0x6D6Delete, true),
rename: ProtoField(5, () => OidbSvcTrpcTcp0x6D6Rename, true),
move: ProtoField(6, () => OidbSvcTrpcTcp0x6D6Move, true),
};
export const OidbSvcTrpcTcp0x6D6Upload = {
groupUin: ProtoField(1, ScalarType.UINT32),
appId: ProtoField(2, ScalarType.UINT32),
busId: ProtoField(3, ScalarType.UINT32),
entrance: ProtoField(4, ScalarType.UINT32),
targetDirectory: ProtoField(5, ScalarType.STRING),
fileName: ProtoField(6, ScalarType.STRING),
localDirectory: ProtoField(7, ScalarType.STRING),
fileSize: ProtoField(8, ScalarType.UINT64),
fileSha1: ProtoField(9, ScalarType.BYTES),
fileSha3: ProtoField(10, ScalarType.BYTES),
fileMd5: ProtoField(11, ScalarType.BYTES),
field15: ProtoField(15, ScalarType.BOOL),
};
export const OidbSvcTrpcTcp0x6D6Download = {
groupUin: ProtoField(1, ScalarType.UINT32),
appId: ProtoField(2, ScalarType.UINT32),
busId: ProtoField(3, ScalarType.UINT32),
fileId: ProtoField(4, ScalarType.STRING),
};
export const OidbSvcTrpcTcp0x6D6Delete = {
groupUin: ProtoField(1, ScalarType.UINT32),
busId: ProtoField(3, ScalarType.UINT32),
fileId: ProtoField(5, ScalarType.STRING),
};
export const OidbSvcTrpcTcp0x6D6Rename = {
groupUin: ProtoField(1, ScalarType.UINT32),
busId: ProtoField(3, ScalarType.UINT32),
fileId: ProtoField(4, ScalarType.STRING),
parentFolder: ProtoField(5, ScalarType.STRING),
newFileName: ProtoField(6, ScalarType.STRING),
};
export const OidbSvcTrpcTcp0x6D6Move = {
groupUin: ProtoField(1, ScalarType.UINT32),
appId: ProtoField(2, ScalarType.UINT32),
busId: ProtoField(3, ScalarType.UINT32),
fileId: ProtoField(4, ScalarType.STRING),
parentDirectory: ProtoField(5, ScalarType.STRING),
targetDirectory: ProtoField(6, ScalarType.STRING),
};
export const OidbSvcTrpcTcp0x6D6Response = {
upload: ProtoField(1, () => OidbSvcTrpcTcp0x6D6_0Response),
download: ProtoField(3, () => OidbSvcTrpcTcp0x6D6_2Response),
delete: ProtoField(4, () => OidbSvcTrpcTcp0x6D6_3_4_5Response),
rename: ProtoField(5, () => OidbSvcTrpcTcp0x6D6_3_4_5Response),
move: ProtoField(6, () => OidbSvcTrpcTcp0x6D6_3_4_5Response),
};
export const OidbSvcTrpcTcp0x6D6_0Response = {
retCode: ProtoField(1, ScalarType.INT32),
retMsg: ProtoField(2, ScalarType.STRING),
clientWording: ProtoField(3, ScalarType.STRING),
uploadIp: ProtoField(4, ScalarType.STRING),
serverDns: ProtoField(5, ScalarType.STRING),
busId: ProtoField(6, ScalarType.INT32),
fileId: ProtoField(7, ScalarType.STRING),
checkKey: ProtoField(8, ScalarType.BYTES),
fileKey: ProtoField(9, ScalarType.BYTES),
boolFileExist: ProtoField(10, ScalarType.BOOL),
uploadIpLanV4: ProtoField(12, ScalarType.STRING, false, true),
uploadIpLanV6: ProtoField(13, ScalarType.STRING, false, true),
uploadPort: ProtoField(14, ScalarType.UINT32),
};
export const OidbSvcTrpcTcp0x6D6_2Response = {
retCode: ProtoField(1, ScalarType.INT32),
retMsg: ProtoField(2, ScalarType.STRING),
clientWording: ProtoField(3, ScalarType.STRING),
downloadIp: ProtoField(4, ScalarType.STRING),
downloadDns: ProtoField(5, ScalarType.STRING),
downloadUrl: ProtoField(6, ScalarType.BYTES),
fileSha1: ProtoField(7, ScalarType.BYTES),
fileSha3: ProtoField(8, ScalarType.BYTES),
fileMd5: ProtoField(9, ScalarType.BYTES),
cookieVal: ProtoField(10, ScalarType.BYTES),
saveFileName: ProtoField(11, ScalarType.STRING),
previewPort: ProtoField(12, ScalarType.UINT32),
};
export const OidbSvcTrpcTcp0x6D6_3_4_5Response = {
retCode: ProtoField(1, ScalarType.INT32),
retMsg: ProtoField(2, ScalarType.STRING),
clientWording: ProtoField(3, ScalarType.STRING),
};

View File

@@ -0,0 +1,16 @@
import { ScalarType } from "@protobuf-ts/runtime";
import { ProtoField } from "../NapProto";
//设置群头衔 OidbSvcTrpcTcp.0x8fc_2
export const OidbSvcTrpcTcp0X8FC_2_Body = {
targetUid: ProtoField(1, ScalarType.STRING),
specialTitle: ProtoField(5, ScalarType.STRING),
expiredTime: ProtoField(6, ScalarType.SINT32),
uinName: ProtoField(7, ScalarType.STRING),
targetName: ProtoField(8, ScalarType.STRING),
};
export const OidbSvcTrpcTcp0X8FC_2 = {
groupUin: ProtoField(1, ScalarType.UINT32),
body: ProtoField(3, ScalarType.BYTES),
};

View File

@@ -0,0 +1,26 @@
import { ScalarType } from "@protobuf-ts/runtime";
import { ProtoField } from "../NapProto";
import { MultiMediaReqHead } from "./common/Ntv2.RichMediaReq";
//Req
export const OidbSvcTrpcTcp0X9067_202 = {
ReqHead: ProtoField(1, () => MultiMediaReqHead),
DownloadRKeyReq: ProtoField(4, () => OidbSvcTrpcTcp0X9067_202Key),
};
export const OidbSvcTrpcTcp0X9067_202Key = {
key: ProtoField(1, ScalarType.INT32, false, true),
};
//Rsp
export const OidbSvcTrpcTcp0X9067_202_RkeyList = {
rkey: ProtoField(1, ScalarType.STRING),
time: ProtoField(4, ScalarType.UINT32),
type: ProtoField(5, ScalarType.UINT32),
};
export const OidbSvcTrpcTcp0X9067_202_Data = {
rkeyList: ProtoField(1, () => OidbSvcTrpcTcp0X9067_202_RkeyList, false, true),
};
export const OidbSvcTrpcTcp0X9067_202_Rsp_Body = {
data: ProtoField(4, () => OidbSvcTrpcTcp0X9067_202_Data),
};

View File

@@ -0,0 +1,61 @@
import { ScalarType } from "@protobuf-ts/runtime";
import { ProtoField } from "../NapProto";
export const OidbSvcTrpcTcp0XE37_1200 = {
subCommand: ProtoField(1, ScalarType.UINT32, true),
field2: ProtoField(2, ScalarType.INT32, true),
body: ProtoField(14, () => OidbSvcTrpcTcp0XE37_1200Body, true),
field101: ProtoField(101, ScalarType.INT32, true),
field102: ProtoField(102, ScalarType.INT32, true),
field200: ProtoField(200, ScalarType.INT32, true),
field99999: ProtoField(99999, ScalarType.BYTES, true),
};
export const OidbSvcTrpcTcp0XE37_1200Body = {
receiverUid: ProtoField(10, ScalarType.STRING, true),
fileUuid: ProtoField(20, ScalarType.STRING, true),
type: ProtoField(30, ScalarType.INT32, true),
fileHash: ProtoField(60, ScalarType.STRING, true),
t2: ProtoField(601, ScalarType.INT32, true),
};
export const OidbSvcTrpcTcp0XE37_1200Response = {
command: ProtoField(1, ScalarType.UINT32, true),
subCommand: ProtoField(2, ScalarType.UINT32, true),
body: ProtoField(14, () => OidbSvcTrpcTcp0XE37_1200ResponseBody, true),
field50: ProtoField(50, ScalarType.UINT32, true),
};
export const OidbSvcTrpcTcp0XE37_1200ResponseBody = {
field10: ProtoField(10, ScalarType.UINT32, true),
state: ProtoField(20, ScalarType.STRING, true),
result: ProtoField(30, () => OidbSvcTrpcTcp0XE37_1200Result, true),
metadata: ProtoField(40, () => OidbSvcTrpcTcp0XE37_1200Metadata, true),
};
export const OidbSvcTrpcTcp0XE37_1200Result = {
server: ProtoField(20, ScalarType.STRING, true),
port: ProtoField(40, ScalarType.UINT32, true),
url: ProtoField(50, ScalarType.STRING, true),
additionalServer: ProtoField(60, ScalarType.STRING, false, true),
ssoPort: ProtoField(80, ScalarType.UINT32, true),
ssoUrl: ProtoField(90, ScalarType.STRING, true),
extra: ProtoField(120, ScalarType.BYTES, true),
};
export const OidbSvcTrpcTcp0XE37_1200Metadata = {
uin: ProtoField(1, ScalarType.UINT32, true),
field2: ProtoField(2, ScalarType.UINT32, true),
field3: ProtoField(3, ScalarType.UINT32, true),
size: ProtoField(4, ScalarType.UINT32, true),
timestamp: ProtoField(5, ScalarType.UINT32, true),
fileUuid: ProtoField(6, ScalarType.STRING, true),
fileName: ProtoField(7, ScalarType.STRING, true),
field100: ProtoField(100, ScalarType.BYTES, true),
field101: ProtoField(101, ScalarType.BYTES, true),
field110: ProtoField(110, ScalarType.UINT32, true),
timestamp1: ProtoField(130, ScalarType.UINT32, true),
fileHash: ProtoField(140, ScalarType.STRING, true),
field141: ProtoField(141, ScalarType.BYTES, true),
field142: ProtoField(142, ScalarType.BYTES, true),
};

View File

@@ -0,0 +1,10 @@
import { ScalarType } from "@protobuf-ts/runtime";
import { ProtoField } from "../NapProto";
// Send Poke
export const OidbSvcTrpcTcp0XED3_1 = {
uin: ProtoField(1, ScalarType.UINT32),
groupUin: ProtoField(2, ScalarType.UINT32),
friendUin: ProtoField(5, ScalarType.UINT32),
ext: ProtoField(6, ScalarType.UINT32, true)
};

View File

@@ -0,0 +1,13 @@
import { ScalarType } from "@protobuf-ts/runtime";
import { ProtoField } from "../NapProto";
export const OidbSvcTrpcTcpBase = {
command: ProtoField(1, ScalarType.UINT32),
subCommand: ProtoField(2, ScalarType.UINT32),
body: ProtoField(4, ScalarType.BYTES),
errorMsg: ProtoField(5, ScalarType.STRING, true),
isReserved: ProtoField(12, ScalarType.UINT32)
};
export const OidbSvcTrpcTcpBaseRsp = {
body: ProtoField(4, ScalarType.BYTES)
};

View File

@@ -0,0 +1,214 @@
import {ScalarType} from "@protobuf-ts/runtime";
import {ProtoField} from "../../NapProto";
export const NTV2RichMediaReq = {
ReqHead: ProtoField(1, () => MultiMediaReqHead),
Upload: ProtoField(2, () => UploadReq),
Download: ProtoField(3, () => DownloadReq),
DownloadRKey: ProtoField(4, () => DownloadRKeyReq),
Delete: ProtoField(5, () => DeleteReq),
UploadCompleted: ProtoField(6, () => UploadCompletedReq),
MsgInfoAuth: ProtoField(7, () => MsgInfoAuthReq),
UploadKeyRenewal: ProtoField(8, () => UploadKeyRenewalReq),
DownloadSafe: ProtoField(9, () => DownloadSafeReq),
Extension: ProtoField(99, ScalarType.BYTES, true),
};
export const MultiMediaReqHead = {
Common: ProtoField(1, () => CommonHead),
Scene: ProtoField(2, () => SceneInfo),
Client: ProtoField(3, () => ClientMeta),
};
export const CommonHead = {
RequestId: ProtoField(1, ScalarType.UINT32),
Command: ProtoField(2, ScalarType.UINT32),
};
export const SceneInfo = {
RequestType: ProtoField(101, ScalarType.UINT32),
BusinessType: ProtoField(102, ScalarType.UINT32),
SceneType: ProtoField(200, ScalarType.UINT32),
C2C: ProtoField(201, () => C2CUserInfo, true),
Group: ProtoField(202, () => NTGroupInfo, true),
};
export const C2CUserInfo = {
AccountType: ProtoField(1, ScalarType.UINT32),
TargetUid: ProtoField(2, ScalarType.STRING),
};
export const NTGroupInfo = {
GroupUin: ProtoField(1, ScalarType.UINT32),
};
export const ClientMeta = {
AgentType: ProtoField(1, ScalarType.UINT32),
};
export const DownloadReq = {
Node: ProtoField(1, () => IndexNode),
Download: ProtoField(2, () => DownloadExt),
};
export const IndexNode = {
Info: ProtoField(1, () => FileInfo),
FileUuid: ProtoField(2, ScalarType.STRING),
StoreId: ProtoField(3, ScalarType.UINT32),
UploadTime: ProtoField(4, ScalarType.UINT32),
Ttl: ProtoField(5, ScalarType.UINT32),
SubType: ProtoField(6, ScalarType.UINT32),
};
export const FileInfo = {
FileSize: ProtoField(1, ScalarType.UINT32),
FileHash: ProtoField(2, ScalarType.STRING),
FileSha1: ProtoField(3, ScalarType.STRING),
FileName: ProtoField(4, ScalarType.STRING),
Type: ProtoField(5, () => FileType),
Width: ProtoField(6, ScalarType.UINT32),
Height: ProtoField(7, ScalarType.UINT32),
Time: ProtoField(8, ScalarType.UINT32),
Original: ProtoField(9, ScalarType.UINT32),
};
export const FileType = {
Type: ProtoField(1, ScalarType.UINT32),
PicFormat: ProtoField(2, ScalarType.UINT32),
VideoFormat: ProtoField(3, ScalarType.UINT32),
VoiceFormat: ProtoField(4, ScalarType.UINT32),
};
export const DownloadExt = {
Pic: ProtoField(1, () => PicDownloadExt),
Video: ProtoField(2, () => VideoDownloadExt),
Ptt: ProtoField(3, () => PttDownloadExt),
};
export const VideoDownloadExt = {
BusiType: ProtoField(1, ScalarType.UINT32),
SceneType: ProtoField(2, ScalarType.UINT32),
SubBusiType: ProtoField(3, ScalarType.UINT32),
};
export const PicDownloadExt = {};
export const PttDownloadExt = {};
export const DownloadRKeyReq = {
Types: ProtoField(1, ScalarType.INT32, false, true),
};
export const DeleteReq = {
Index: ProtoField(1, () => IndexNode, false, true),
NeedRecallMsg: ProtoField(2, ScalarType.BOOL),
MsgSeq: ProtoField(3, ScalarType.UINT64),
MsgRandom: ProtoField(4, ScalarType.UINT64),
MsgTime: ProtoField(5, ScalarType.UINT64),
};
export const UploadCompletedReq = {
SrvSendMsg: ProtoField(1, ScalarType.BOOL),
ClientRandomId: ProtoField(2, ScalarType.UINT64),
MsgInfo: ProtoField(3, () => MsgInfo),
ClientSeq: ProtoField(4, ScalarType.UINT32),
};
export const MsgInfoAuthReq = {
Msg: ProtoField(1, ScalarType.BYTES),
AuthTime: ProtoField(2, ScalarType.UINT64),
};
export const DownloadSafeReq = {
Index: ProtoField(1, () => IndexNode),
};
export const UploadKeyRenewalReq = {
OldUKey: ProtoField(1, ScalarType.STRING),
SubType: ProtoField(2, ScalarType.UINT32),
};
export const MsgInfo = {
MsgInfoBody: ProtoField(1, () => MsgInfoBody, false, true),
ExtBizInfo: ProtoField(2, () => ExtBizInfo),
};
export const MsgInfoBody = {
Index: ProtoField(1, () => IndexNode),
Picture: ProtoField(2, () => PictureInfo),
Video: ProtoField(3, () => VideoInfo),
Audio: ProtoField(4, () => AudioInfo),
FileExist: ProtoField(5, ScalarType.BOOL),
HashSum: ProtoField(6, ScalarType.BYTES),
};
export const VideoInfo = {};
export const AudioInfo = {};
export const PictureInfo = {
UrlPath: ProtoField(1, ScalarType.STRING),
Ext: ProtoField(2, () => PicUrlExtInfo),
Domain: ProtoField(3, ScalarType.STRING),
};
export const PicUrlExtInfo = {
OriginalParameter: ProtoField(1, ScalarType.STRING),
BigParameter: ProtoField(2, ScalarType.STRING),
ThumbParameter: ProtoField(3, ScalarType.STRING),
};
export const VideoExtInfo = {
VideoCodecFormat: ProtoField(1, ScalarType.UINT32),
}
export const ExtBizInfo = {
Pic: ProtoField(1, () => PicExtBizInfo),
Video: ProtoField(2, () => VideoExtBizInfo),
Ptt: ProtoField(3, () => PttExtBizInfo),
BusiType: ProtoField(10, ScalarType.UINT32),
};
export const PttExtBizInfo = {
SrcUin: ProtoField(1, ScalarType.UINT64),
PttScene: ProtoField(2, ScalarType.UINT32),
PttType: ProtoField(3, ScalarType.UINT32),
ChangeVoice: ProtoField(4, ScalarType.UINT32),
Waveform: ProtoField(5, ScalarType.BYTES),
AutoConvertText: ProtoField(6, ScalarType.UINT32),
BytesReserve: ProtoField(11, ScalarType.BYTES),
BytesPbReserve: ProtoField(12, ScalarType.BYTES),
BytesGeneralFlags: ProtoField(13, ScalarType.BYTES),
};
export const VideoExtBizInfo = {
FromScene: ProtoField(1, ScalarType.UINT32),
ToScene: ProtoField(2, ScalarType.UINT32),
BytesPbReserve: ProtoField(3, ScalarType.BYTES),
};
export const PicExtBizInfo = {
BizType: ProtoField(1, ScalarType.UINT32),
TextSummary: ProtoField(2, ScalarType.STRING),
BytesPbReserveC2c: ProtoField(11, ScalarType.BYTES),
BytesPbReserveTroop: ProtoField(12, ScalarType.BYTES),
FromScene: ProtoField(1001, ScalarType.UINT32),
ToScene: ProtoField(1002, ScalarType.UINT32),
OldFileId: ProtoField(1003, ScalarType.UINT32),
};
export const UploadReq = {
UploadInfo: ProtoField(1, () => UploadInfo, false, true),
TryFastUploadCompleted: ProtoField(2, ScalarType.BOOL),
SrvSendMsg: ProtoField(3, ScalarType.BOOL),
ClientRandomId: ProtoField(4, ScalarType.UINT64),
CompatQMsgSceneType: ProtoField(5, ScalarType.UINT32),
ExtBizInfo: ProtoField(6, () => ExtBizInfo),
ClientSeq: ProtoField(7, ScalarType.UINT32),
NoNeedCompatMsg: ProtoField(8, ScalarType.BOOL),
};
export const UploadInfo = {
FileInfo: ProtoField(1, () => FileInfo),
SubFileType: ProtoField(2, ScalarType.UINT32),
};

View File

@@ -0,0 +1,114 @@
import {ScalarType} from "@protobuf-ts/runtime";
import {ProtoField} from "../../NapProto";
import {CommonHead, MsgInfo, PicUrlExtInfo, VideoExtInfo} from "@/core/packet/proto/oidb/common/Ntv2.RichMediaReq";
export const NTV2RichMediaResp = {
respHead: ProtoField(1, () => MultiMediaRespHead),
upload: ProtoField(2, () => UploadResp),
download: ProtoField(3, () => DownloadResp),
downloadRKey: ProtoField(4, () => DownloadRKeyResp),
delete: ProtoField(5, () => DeleteResp),
uploadCompleted: ProtoField(6, () => UploadCompletedResp),
msgInfoAuth: ProtoField(7, () => MsgInfoAuthResp),
uploadKeyRenewal: ProtoField(8, () => UploadKeyRenewalResp),
downloadSafe: ProtoField(9, () => DownloadSafeResp),
extension: ProtoField(99, ScalarType.BYTES, true),
}
export const MultiMediaRespHead = {
common: ProtoField(1, () => CommonHead),
retCode: ProtoField(2, ScalarType.UINT32),
message: ProtoField(3, ScalarType.STRING),
}
export const DownloadResp = {
rKeyParam: ProtoField(1, ScalarType.STRING),
rKeyTtlSecond: ProtoField(2, ScalarType.UINT32),
info: ProtoField(3, () => DownloadInfo),
rKeyCreateTime: ProtoField(4, ScalarType.UINT32),
}
export const DownloadInfo = {
domain: ProtoField(1, ScalarType.STRING),
urlPath: ProtoField(2, ScalarType.STRING),
httpsPort: ProtoField(3, ScalarType.UINT32),
ipv4s: ProtoField(4, () => IPv4, false, true),
ipv6s: ProtoField(5, () => IPv6, false, true),
picUrlExtInfo: ProtoField(6, () => PicUrlExtInfo),
videoExtInfo: ProtoField(7, () => VideoExtInfo),
}
export const IPv4 = {
outIP: ProtoField(1, ScalarType.UINT32),
outPort: ProtoField(2, ScalarType.UINT32),
inIP: ProtoField(3, ScalarType.UINT32),
inPort: ProtoField(4, ScalarType.UINT32),
ipType: ProtoField(5, ScalarType.UINT32),
}
export const IPv6 = {
outIP: ProtoField(1, ScalarType.BYTES),
outPort: ProtoField(2, ScalarType.UINT32),
inIP: ProtoField(3, ScalarType.BYTES),
inPort: ProtoField(4, ScalarType.UINT32),
ipType: ProtoField(5, ScalarType.UINT32),
}
export const UploadResp = {
uKey: ProtoField(1, ScalarType.STRING, true),
uKeyTtlSecond: ProtoField(2, ScalarType.UINT32),
ipv4s: ProtoField(3, () => IPv4, false, true),
ipv6s: ProtoField(4, () => IPv6, false, true),
msgSeq: ProtoField(5, ScalarType.UINT64),
msgInfo: ProtoField(6, () => MsgInfo),
ext: ProtoField(7, () => RichMediaStorageTransInfo, false, true),
compatQMsg: ProtoField(8, ScalarType.BYTES),
subFileInfos: ProtoField(10, () => SubFileInfo, false, true),
}
export const RichMediaStorageTransInfo = {
subType: ProtoField(1, ScalarType.UINT32),
extType: ProtoField(2, ScalarType.UINT32),
extValue: ProtoField(3, ScalarType.BYTES),
}
export const SubFileInfo = {
subType: ProtoField(1, ScalarType.UINT32),
uKey: ProtoField(2, ScalarType.STRING),
uKeyTtlSecond: ProtoField(3, ScalarType.UINT32),
ipv4s: ProtoField(4, () => IPv4, false, true),
ipv6s: ProtoField(5, () => IPv6, false, true),
}
export const DownloadSafeResp = {
}
export const UploadKeyRenewalResp = {
ukey: ProtoField(1, ScalarType.STRING),
ukeyTtlSec: ProtoField(2, ScalarType.UINT64),
}
export const MsgInfoAuthResp = {
authCode: ProtoField(1, ScalarType.UINT32),
msg: ProtoField(2, ScalarType.BYTES),
resultTime: ProtoField(3, ScalarType.UINT64),
}
export const UploadCompletedResp = {
msgSeq: ProtoField(1, ScalarType.UINT64),
}
export const DeleteResp = {
}
export const DownloadRKeyResp = {
rKeys: ProtoField(1, () => RKeyInfo, false, true),
}
export const RKeyInfo = {
rkey: ProtoField(1, ScalarType.STRING),
rkeyTtlSec: ProtoField(2, ScalarType.UINT64),
storeId: ProtoField(3, ScalarType.UINT32),
rkeyCreateTime: ProtoField(4, ScalarType.UINT32, true),
type: ProtoField(5, ScalarType.UINT32, true),
}

View File

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

View File

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

View File

@@ -0,0 +1,18 @@
import { PacketClient } from "@/core/packet/client";
import { PacketHighwaySession } from "@/core/packet/highway/session";
import { LogWrapper } from "@/common/log";
import {PacketPacker} from "@/core/packet/packer";
export class PacketSession {
readonly logger: LogWrapper;
readonly client: PacketClient;
readonly packer: PacketPacker;
readonly highwaySession: PacketHighwaySession;
constructor(logger: LogWrapper, client: PacketClient) {
this.logger = logger;
this.client = client;
this.packer = new PacketPacker(this.logger, this.client);
this.highwaySession = new PacketHighwaySession(this.logger, this.client, this.packer);
}
}

View File

@@ -0,0 +1,16 @@
// love from https://github.com/LagrangeDev/lagrangejs & https://github.com/takayama-lily/oicq
import * as crypto from 'crypto';
import * as stream from 'stream';
import * as fs from 'fs';
function sha1Stream(readable: stream.Readable) {
return new Promise((resolve, reject) => {
readable.on('error', reject);
readable.pipe(crypto.createHash('sha1').on('error', reject).on('data', resolve));
}) as Promise<Buffer>;
}
export function calculateSha1(filePath: string): Promise<Buffer> {
const readable = fs.createReadStream(filePath);
return sha1Stream(readable);
}

View File

@@ -0,0 +1,86 @@
// love from https://github.com/LagrangeDev/lagrangejs/blob/main/src/core/tea.ts & https://github.com/takayama-lily/oicq/blob/main/lib/core/tea.ts
const BUF7 = Buffer.alloc(7);
const deltas = [
0x9e3779b9, 0x3c6ef372, 0xdaa66d2b, 0x78dde6e4, 0x1715609d, 0xb54cda56, 0x5384540f, 0xf1bbcdc8, 0x8ff34781,
0x2e2ac13a, 0xcc623af3, 0x6a99b4ac, 0x08d12e65, 0xa708a81e, 0x454021d7, 0xe3779b90,
];
function _toUInt32(num: number) {
return num >>> 0;
}
function _encrypt(x: number, y: number, k0: number, k1: number, k2: number, k3: number): [number, number] {
for (let i = 0; i < 16; ++i) {
let aa = ((_toUInt32(((y << 4) >>> 0) + k0) ^ _toUInt32(y + deltas[i])) >>> 0) ^ _toUInt32(~~(y / 32) + k1);
aa >>>= 0;
x = _toUInt32(x + aa);
let bb = ((_toUInt32(((x << 4) >>> 0) + k2) ^ _toUInt32(x + deltas[i])) >>> 0) ^ _toUInt32(~~(x / 32) + k3);
bb >>>= 0;
y = _toUInt32(y + bb);
}
return [x, y];
}
export function encrypt(data: Buffer, key: Buffer) {
let n = (6 - data.length) >>> 0;
n = (n % 8) + 2;
const v = Buffer.concat([Buffer.from([(n - 2) | 0xf8]), Buffer.allocUnsafe(n), data, BUF7]);
const k0 = key.readUInt32BE(0);
const k1 = key.readUInt32BE(4);
const k2 = key.readUInt32BE(8);
const k3 = key.readUInt32BE(12);
let r1 = 0, r2 = 0, t1 = 0, t2 = 0;
for (let i = 0; i < v.length; i += 8) {
const a1 = v.readUInt32BE(i);
const a2 = v.readUInt32BE(i + 4);
const b1 = a1 ^ r1;
const b2 = a2 ^ r2;
const [x, y] = _encrypt(b1 >>> 0, b2 >>> 0, k0, k1, k2, k3);
r1 = x ^ t1;
r2 = y ^ t2;
t1 = b1;
t2 = b2;
v.writeInt32BE(r1, i);
v.writeInt32BE(r2, i + 4);
}
return v;
}
function _decrypt(x: number, y: number, k0: number, k1: number, k2: number, k3: number) {
for (let i = 15; i >= 0; --i) {
const aa = ((_toUInt32(((x << 4) >>> 0) + k2) ^ _toUInt32(x + deltas[i])) >>> 0) ^ _toUInt32(~~(x / 32) + k3);
y = (y - aa) >>> 0;
const bb = ((_toUInt32(((y << 4) >>> 0) + k0) ^ _toUInt32(y + deltas[i])) >>> 0) ^ _toUInt32(~~(y / 32) + k1);
x = (x - bb) >>> 0;
}
return [x, y];
}
export function decrypt(encrypted: Buffer, key: Buffer) {
if (encrypted.length % 8) throw ERROR_ENCRYPTED_LENGTH;
const k0 = key.readUInt32BE(0);
const k1 = key.readUInt32BE(4);
const k2 = key.readUInt32BE(8);
const k3 = key.readUInt32BE(12);
let r1 = 0, r2 = 0, t1 = 0, t2 = 0, x = 0, y = 0;
for (let i = 0; i < encrypted.length; i += 8) {
const a1 = encrypted.readUInt32BE(i);
const a2 = encrypted.readUInt32BE(i + 4);
const b1 = a1 ^ x;
const b2 = a2 ^ y;
[x, y] = _decrypt(b1 >>> 0, b2 >>> 0, k0, k1, k2, k3);
r1 = x ^ t1;
r2 = y ^ t2;
t1 = a1;
t2 = a2;
encrypted.writeInt32BE(r1, i);
encrypted.writeInt32BE(r2, i + 4);
}
if (Buffer.compare(encrypted.subarray(encrypted.length - 7), BUF7) !== 0) throw ERROR_ENCRYPTED_ILLEGAL
// if (Buffer.compare(encrypted.slice(encrypted.length - 7), BUF7) !== 0) throw ERROR_ENCRYPTED_ILLEGAL;
return encrypted.subarray((encrypted[0] & 0x07) + 3, encrypted.length - 7);
// return encrypted.slice((encrypted[0] & 0x07) + 3, encrypted.length - 7);
}
const ERROR_ENCRYPTED_LENGTH = new Error('length of encrypted data must be a multiple of 8');
const ERROR_ENCRYPTED_ILLEGAL = new Error('encrypted data is illegal');

View File

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

View File

@@ -36,7 +36,7 @@ export interface NodeIKernelBuddyService {
getBuddyRemark(uid: number): string; getBuddyRemark(uid: number): string;
setBuddyRemark(uid: number, remark: string): void; setBuddyRemark(param: { uid: string, remark: string, signInfo?: unknown }): void;
getAvatarUrl(uid: number): string; getAvatarUrl(uid: number): string;

View File

@@ -1,2 +1,3 @@
export interface NodeIKernelECDHService { export interface NodeIKernelECDHService {
sendOIDBECRequest: (data: Uint8Array) => Promise<Uint8Array>;
} }

View File

@@ -115,7 +115,8 @@ export interface NodeIKernelGroupService {
destroyMemberListScene(SceneId: string): void; destroyMemberListScene(SceneId: string): void;
getNextMemberList(sceneId: string, a: undefined, num: number): Promise<{ getNextMemberList(sceneId: string, a: undefined, num: number): Promise<{
errCode: number, errMsg: string, errCode: number,
errMsg: string,
result: { ids: string[], infos: Map<string, GroupMember>, finish: boolean, hasRobot: boolean } result: { ids: string[], infos: Map<string, GroupMember>, finish: boolean, hasRobot: boolean }
}>; }>;
@@ -145,7 +146,7 @@ export interface NodeIKernelGroupService {
getMemberExtInfo(param: GroupExtParam): Promise<unknown>;//req getMemberExtInfo(param: GroupExtParam): Promise<unknown>;//req
getGroupAllInfo(): unknown; getGroupAllInfo(groupId: string, sourceId: number): Promise<any>;
getDiscussExistInfo(): unknown; getDiscussExistInfo(): unknown;
@@ -234,13 +235,18 @@ export interface NodeIKernelGroupService {
setGroupShutUp(groupCode: string, shutUp: boolean): void; setGroupShutUp(groupCode: string, shutUp: boolean): void;
getGroupShutUpMemberList(groupCode: string): unknown[]; getGroupShutUpMemberList(groupCode: string): Promise<any>;
setMemberShutUp(groupCode: string, memberTimes: { uid: string, timeStamp: number }[]): Promise<void>; setMemberShutUp(groupCode: string, memberTimes: { uid: string, timeStamp: number }[]): Promise<void>;
getGroupRecommendContactArkJson(groupCode: string): unknown; getGroupRecommendContactArkJson(groupCode: string): unknown;
getJoinGroupLink(groupCode: string): unknown; getJoinGroupLink(param: {
groupCode: string,
srcId: number,//73
needShortUrl: boolean,//true
additionalParam: string//''
}): Promise<GeneralCallResult & { url?: string }>;
modifyGroupExtInfo(groupCode: string, arg: unknown): void; modifyGroupExtInfo(groupCode: string, arg: unknown): void;

View File

@@ -1,4 +1,5 @@
import { NodeIKernelLoginListener } from '@/core/listeners/NodeIKernelLoginListener'; import { NodeIKernelLoginListener } from '@/core/listeners/NodeIKernelLoginListener';
import { GeneralCallResult } from './common';
export interface LoginInitConfig { export interface LoginInitConfig {
machineId: ''; machineId: '';
@@ -59,6 +60,9 @@ export interface QuickLoginResult {
} }
export interface NodeIKernelLoginService { export interface NodeIKernelLoginService {
setLoginMiscData(arg0: string, value: string): unknown;
getMachineGuid(): string;
get(): NodeIKernelLoginService; get(): NodeIKernelLoginService;
connect(): boolean; connect(): boolean;
@@ -69,7 +73,7 @@ export interface NodeIKernelLoginService {
initConfig(config: LoginInitConfig): void; initConfig(config: LoginInitConfig): void;
getLoginMiscData(cb: (r: unknown) => void): void; getLoginMiscData(data: string): Promise<GeneralCallResult & { value: string }>;
getLoginList(): Promise<{ getLoginList(): Promise<{
result: number, // 0是ok result: number, // 0是ok

View File

@@ -1,3 +1,30 @@
import { GeneralCallResult } from "./common";
export interface NodeIKernelMSFService { export interface NodeIKernelMSFService {
getServerTime(): string; getServerTime(): string;
setNetworkProxy(param: {
userName: string,
userPwd: string,
address: string,
port: number,
proxyType: number,
domain: string,
isSocket: boolean
}): Promise<GeneralCallResult>;
//http
// userName: '',
// userPwd: '',
// address: '127.0.0.1',
// port: 5666,
// proxyType: 1,
// domain: '',
// isSocket: false
//socket
// userName: '',
// userPwd: '',
// address: '127.0.0.1',
// port: 5667,
// proxyType: 2,
// domain: '',
// isSocket: true
} }

View File

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

View File

@@ -45,7 +45,7 @@ export interface NodeIKernelProfileService {
setGander(...args: unknown[]): Promise<unknown>; setGander(...args: unknown[]): Promise<unknown>;
setHeader(arg: string): Promise<unknown>; setHeader(arg: string): Promise<GeneralCallResult>;
setRecommendImgFlag(...args: unknown[]): Promise<unknown>; setRecommendImgFlag(...args: unknown[]): Promise<unknown>;

View File

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

View File

@@ -6,4 +6,6 @@ export interface NodeIO3MiscService {
addO3MiscListener(listeners: NodeIO3MiscListener): number; addO3MiscListener(listeners: NodeIO3MiscListener): number;
setAmgomDataPiece(appid: string, dataPiece: Uint8Array): void; setAmgomDataPiece(appid: string, dataPiece: Uint8Array): void;
reportAmgomWeather(type: string, uk2: string, arg: Array<string>): void;
} }

View File

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

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

Binary file not shown.

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

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

View File

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

View File

@@ -0,0 +1,11 @@
import { ActionName } from '../types';
import { GetPacketStatusDepends } from "@/onebot/action/packet/GetPacketStatus";
export class GetRkey extends GetPacketStatusDepends<null, Array<any>> {
actionName = ActionName.GetRkey;
async _handle() {
return await this.core.apis.PacketApi.sendRkeyPacket();
}
}

View File

@@ -0,0 +1,22 @@
import { ActionName } from '../types';
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import {GetPacketStatusDepends} from "@/onebot/action/packet/GetPacketStatus";
// no_cache get时传字符串
const SchemaData = {
type: 'object',
properties: {
user_id: { type: ['number', 'string'] },
},
required: ['user_id'],
} as const satisfies JSONSchema;
type Payload = FromSchema<typeof SchemaData>;
export class GetUserStatus extends GetPacketStatusDepends<Payload, { status: number; ext_status: number; } | undefined> {
actionName = ActionName.GetUserStatus;
payloadSchema = SchemaData;
async _handle(payload: Payload) {
return await this.core.apis.PacketApi.sendStatusPacket(+payload.user_id);
}
}

View File

@@ -38,6 +38,7 @@ export default class SetAvatar extends BaseAction<Payload, null> {
throw `头像${payload.file}设置失败,api无返回`; throw `头像${payload.file}设置失败,api无返回`;
} }
// log(`头像设置返回:${JSON.stringify(ret)}`) // log(`头像设置返回:${JSON.stringify(ret)}`)
// @ts-ignore
if (ret['result'] == 1004022) { if (ret['result'] == 1004022) {
throw `头像${payload.file}设置失败,文件可能不是图片格式`; throw `头像${payload.file}设置失败,文件可能不是图片格式`;
} else if (ret['result'] != 0) { } else if (ret['result'] != 0) {

View File

@@ -0,0 +1,25 @@
import { ActionName } from '../types';
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import { GetPacketStatusDepends } from "@/onebot/action/packet/GetPacketStatus";
const SchemaData = {
type: 'object',
properties: {
group_id: { type: ['number', 'string'] },
user_id: { type: ['number', 'string'] },
special_title: { type: 'string' },
},
required: ['group_id', 'user_id', 'special_title'],
} as const satisfies JSONSchema;
type Payload = FromSchema<typeof SchemaData>;
export class SetSpecialTittle extends GetPacketStatusDepends<Payload, any> {
actionName = ActionName.SetSpecialTittle;
payloadSchema = SchemaData;
async _handle(payload: Payload) {
const uid = await this.core.apis.UserApi.getUidByUinV2(payload.user_id.toString());
if(!uid) throw new Error('User not found');
await this.core.apis.PacketApi.sendSetSpecialTittlePacket(payload.group_id.toString(), uid, payload.special_title);
}
}

View File

@@ -0,0 +1,34 @@
import { ActionName } from '../types';
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import { FileNapCatOneBotUUID } from "@/common/helper";
import { GetPacketStatusDepends } from "@/onebot/action/packet/GetPacketStatus";
const SchemaData = {
type: 'object',
properties: {
group_id: { type: ['number', 'string'] },
file_id: { type: ['string'] },
},
required: ['group_id', 'file_id'],
} as const satisfies JSONSchema;
type Payload = FromSchema<typeof SchemaData>;
interface GetGroupFileUrlResponse {
url?: string;
}
export class GetGroupFileUrl extends GetPacketStatusDepends<Payload, GetGroupFileUrlResponse> {
actionName = ActionName.GOCQHTTP_GetGroupFileUrl;
payloadSchema = SchemaData;
async _handle(payload: Payload) {
const contextMsgFile = FileNapCatOneBotUUID.decode(payload.file_id) || FileNapCatOneBotUUID.decodeModelId(payload.file_id);
if (contextMsgFile?.fileUUID) {
return {
url: await this.core.apis.PacketApi.sendGroupFileDownloadReq(+payload.group_id, contextMsgFile.fileUUID)
}
}
throw new Error('real fileUUID not found!');
}
}

View File

@@ -1,5 +1,9 @@
import { GetFileBase, GetFilePayload, GetFileResponse } from './GetFile'; import { GetFileBase, GetFilePayload, GetFileResponse } from './GetFile';
import { ActionName } from '../types'; import { ActionName } from '../types';
import { spawn } from 'node:child_process';
import { promises as fs } from 'fs';
import { decode } from 'silk-wasm';
const FFMPEG_PATH = process.env.FFMPEG_PATH || 'ffmpeg';
interface Payload extends GetFilePayload { interface Payload extends GetFilePayload {
out_format: 'mp3' | 'amr' | 'wma' | 'm4a' | 'spx' | 'ogg' | 'wav' | 'flac'; out_format: 'mp3' | 'amr' | 'wma' | 'm4a' | 'spx' | 'ogg' | 'wav' | 'flac';
@@ -9,7 +13,54 @@ export default class GetRecord extends GetFileBase {
actionName = ActionName.GetRecord; actionName = ActionName.GetRecord;
async _handle(payload: Payload): Promise<GetFileResponse> { async _handle(payload: Payload): Promise<GetFileResponse> {
const res = super._handle(payload); const res = await super._handle(payload);
if (payload.out_format && typeof payload.out_format === 'string') {
const inputFile = res.file;
if (!inputFile) throw new Error('file not found');
const pcmFile = `${inputFile}.pcm`;
const outputFile = `${inputFile}.${payload.out_format}`;
try {
await fs.access(inputFile);
await this.decodeFile(inputFile, pcmFile);
await this.convertFile(pcmFile, outputFile, payload.out_format);
const base64Data = await fs.readFile(outputFile, { encoding: 'base64' });
res.file = outputFile;
res.url = outputFile;
res.base64 = base64Data;
} catch (error) {
console.error('Error processing file:', error);
throw error; // 重新抛出错误以便调用者可以处理
}
}
return res; return res;
} }
private async decodeFile(inputFile: string, outputFile: string): Promise<void> {
try {
const inputData = await fs.readFile(inputFile);
const decodedData = await decode(inputData, 24000);
await fs.writeFile(outputFile, Buffer.from(decodedData.data));
} catch (error) {
console.error('Error decoding file:', error);
throw error; // 重新抛出错误以便调用者可以处理
}
}
private convertFile(inputFile: string, outputFile: string, format: string): Promise<void> {
return new Promise((resolve, reject) => {
const ffmpeg = spawn(FFMPEG_PATH, ['-f', 's16le', '-ar', '24000', '-ac', '1', '-i', inputFile, outputFile]);
ffmpeg.on('close', (code) => {
if (code === 0) {
resolve();
} else {
reject(new Error(`ffmpeg process exited with code ${code}`));
}
});
ffmpeg.on('error', (error: Error) => {
reject(error);
});
});
}
} }

View File

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

View File

@@ -21,27 +21,30 @@ class GetGroupMemberInfo extends BaseAction<Payload, OB11GroupMember> {
actionName = ActionName.GetGroupMemberInfo; actionName = ActionName.GetGroupMemberInfo;
payloadSchema = SchemaData; payloadSchema = SchemaData;
private parseBoolean(value: boolean | string): boolean {
return typeof value === 'string' ? value === 'true' : value;
}
private async getUid(userId: string | number): Promise<string> {
const uid = await this.core.apis.UserApi.getUidByUinV2(userId.toString());
if (!uid) throw new Error(`Uin2Uid Error: 用户ID ${userId} 不存在`);
return uid;
}
async _handle(payload: Payload) { async _handle(payload: Payload) {
const isNocache = typeof payload.no_cache === 'string' ? payload.no_cache === 'true' : !!payload.no_cache; const isNocache = this.parseBoolean(payload.no_cache ?? true);
const uid = await this.core.apis.UserApi.getUidByUinV2(payload.user_id.toString()); const uid = await this.getUid(payload.user_id);
if (!uid) throw new Error(`Uin2Uid Error ${payload.user_id}不存在`); const [member, info] = await Promise.all([
const [member, info] = await Promise.allSettled([
this.core.apis.GroupApi.getGroupMemberEx(payload.group_id.toString(), uid, isNocache), this.core.apis.GroupApi.getGroupMemberEx(payload.group_id.toString(), uid, isNocache),
this.core.apis.UserApi.getUserDetailInfo(uid), this.core.apis.UserApi.getUserDetailInfo(uid),
]); ]);
if (member.status !== 'fulfilled') throw new Error(`群(${payload.group_id})成员${payload.user_id}获取失败 ${member.reason}`); if (!member) throw new Error(`群(${payload.group_id})成员${payload.user_id}不存在`);
if (!member.value) throw new Error(`群(${payload.group_id})成员${payload.user_id}不存在`); if (info) {
if (info.status === 'fulfilled') { Object.assign(member, info);
Object.assign(member.value, info.value);
} else { } else {
this.core.context.logger.logDebug(`获取群成员详细信息失败, 只能返回基础信息 ${info.reason}`); this.core.context.logger.logDebug(`获取群成员详细信息失败, 只能返回基础信息`);
} }
const date = Math.round(Date.now() / 1000); return OB11Entities.groupMember(payload.group_id.toString(), member as GroupMember);
const retMember = OB11Entities.groupMember(payload.group_id.toString(), member.value as GroupMember);
const Member = await this.core.apis.GroupApi.getGroupMember(payload.group_id.toString(), retMember.user_id);
retMember.last_sent_time = parseInt(Member?.lastSpeakTime ?? date.toString());
retMember.join_time = parseInt(Member?.joinTime ?? date.toString());
return retMember;
} }
} }

View File

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

View File

@@ -0,0 +1,24 @@
import { OB11Group } from '@/onebot';
import BaseAction from '../BaseAction';
import { ActionName } from '../types';
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
const SchemaData = {
type: 'object',
properties: {
group_id: { type: ['number', 'string'] },
},
required: ['group_id'],
} as const satisfies JSONSchema;
type Payload = FromSchema<typeof SchemaData>;
export class GetGroupShutList extends BaseAction<Payload, OB11Group> {
actionName = ActionName.GetGroupShutList;
payloadSchema = SchemaData;
async _handle(payload: Payload) {
return await this.core.apis.GroupApi.getGroupShutUpMemberList(payload.group_id.toString());
}
}

View File

@@ -0,0 +1,23 @@
import { ActionName } from '../types';
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import {GetPacketStatusDepends} from "@/onebot/action/packet/GetPacketStatus";
// no_cache get时传字符串
const SchemaData = {
type: 'object',
properties: {
group_id: { type: ['number', 'string'] },
user_id: { type: ['number', 'string'] },
},
required: ['group_id', 'user_id'],
} as const satisfies JSONSchema;
type Payload = FromSchema<typeof SchemaData>;
export class GroupPoke extends GetPacketStatusDepends<Payload, any> {
actionName = ActionName.GroupPoke;
payloadSchema = SchemaData;
async _handle(payload: Payload) {
await this.core.apis.PacketApi.sendPokePacket(+payload.group_id, +payload.user_id);
}
}

View File

@@ -3,7 +3,6 @@ import GetLoginInfo from './system/GetLoginInfo';
import GetFriendList from './user/GetFriendList'; import GetFriendList from './user/GetFriendList';
import GetGroupList from './group/GetGroupList'; import GetGroupList from './group/GetGroupList';
import GetGroupInfo from './group/GetGroupInfo'; import GetGroupInfo from './group/GetGroupInfo';
import GetGroupMemberList from './group/GetGroupMemberList';
import GetGroupMemberInfo from './group/GetGroupMemberInfo'; import GetGroupMemberInfo from './group/GetGroupMemberInfo';
import SendGroupMsg from './group/SendGroupMsg'; import SendGroupMsg from './group/SendGroupMsg';
import SendPrivateMsg from './msg/SendPrivateMsg'; import SendPrivateMsg from './msg/SendPrivateMsg';
@@ -84,6 +83,14 @@ import { GetGroupFileSystemInfo } from '@/onebot/action/go-cqhttp/GetGroupFileSy
import { GetGroupRootFiles } from '@/onebot/action/go-cqhttp/GetGroupRootFiles'; import { GetGroupRootFiles } from '@/onebot/action/go-cqhttp/GetGroupRootFiles';
import { GetGroupFilesByFolder } from '@/onebot/action/go-cqhttp/GetGroupFilesByFolder'; import { GetGroupFilesByFolder } from '@/onebot/action/go-cqhttp/GetGroupFilesByFolder';
import { GetGroupSystemMsg } from './system/GetSystemMsg'; import { GetGroupSystemMsg } from './system/GetSystemMsg';
import { GroupPoke } from './group/GroupPoke';
import { GetUserStatus } from './extends/GetUserStatus';
import { GetRkey } from './extends/GetRkey';
import { SetSpecialTittle } from './extends/SetSpecialTittle';
import { GetGroupShutList } from './group/GetGroupShutList';
import { GetGroupMemberList } from './group/GetGroupMemberList';
import { GetGroupFileUrl } from "@/onebot/action/file/GetGroupFileUrl";
import {GetPacketStatus} from "@/onebot/action/packet/GetPacketStatus";
export type ActionMap = Map<string, BaseAction<any, any>>; export type ActionMap = Map<string, BaseAction<any, any>>;
@@ -180,6 +187,14 @@ export function createActionMap(obContext: NapCatOneBot11Adapter, core: NapCatCo
new GetGroupFilesByFolder(obContext, core), new GetGroupFilesByFolder(obContext, core),
new GetGroupSystemMsg(obContext, core), new GetGroupSystemMsg(obContext, core),
new FetchUserProfileLike(obContext, core), new FetchUserProfileLike(obContext, core),
new GetPacketStatus(obContext, core),
new GroupPoke(obContext, core),
new GetUserStatus(obContext, core),
new GetRkey(obContext, core),
new SetSpecialTittle(obContext, core),
// new UploadForwardMsg(obContext, core),
new GetGroupShutList(obContext, core),
new GetGroupFileUrl(obContext, core),
]; ];
const actionMap = new Map(); const actionMap = new Map();
for (const action of actionHandlers) { for (const action of actionHandlers) {

View File

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

View File

@@ -6,14 +6,18 @@ import {
OB11PostContext, OB11PostContext,
OB11PostSendMsg, OB11PostSendMsg,
} from '@/onebot/types'; } from '@/onebot/types';
import { ActionName, BaseCheckResult } from '@/onebot/action/types'; import {ActionName, BaseCheckResult} from '@/onebot/action/types';
import { decodeCQCode } from '@/onebot/cqcode'; import {decodeCQCode} from '@/onebot/cqcode';
import { MessageUnique } from '@/common/message-unique'; import {MessageUnique} from '@/common/message-unique';
import { ChatType, ElementType, NapCatCore, Peer, RawMessage, SendMessageElement } from '@/core'; import {ChatType, ElementType, NapCatCore, Peer, RawMessage, SendArkElement, SendMessageElement} from '@/core';
import BaseAction from '../BaseAction'; import BaseAction from '../BaseAction';
import {rawMsgWithSendMsg} from "@/core/packet/msg/converter";
import {PacketMsg} from "@/core/packet/msg/message";
import {PacketMultiMsgElement} from "@/core/packet/msg/element";
export interface ReturnDataType { export interface ReturnDataType {
message_id: number; message_id: number;
res_id?: string;
} }
export enum ContextMode { export enum ContextMode {
@@ -26,7 +30,7 @@ export enum ContextMode {
export function normalize(message: OB11MessageMixType, autoEscape = false): OB11MessageData[] { export function normalize(message: OB11MessageMixType, autoEscape = false): OB11MessageData[] {
return typeof message === 'string' ? ( return typeof message === 'string' ? (
autoEscape ? autoEscape ?
[{ type: OB11MessageDataType.text, data: { text: message } }] : [{type: OB11MessageDataType.text, data: {text: message}}] :
decodeCQCode(message) decodeCQCode(message)
) : Array.isArray(message) ? message : [message]; ) : Array.isArray(message) ? message : [message];
} }
@@ -69,7 +73,7 @@ export async function createContext(core: NapCatCore, payload: OB11PostContext,
} }
return { return {
chatType: ChatType.KCHATTYPEC2C, chatType: ChatType.KCHATTYPEC2C,
peerUid: Uid!, peerUid: Uid,
guildId: '', guildId: '',
}; };
} }
@@ -96,10 +100,11 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
message: '转发消息不能和普通消息混在一起发送,转发需要保证message只有type为node的元素', message: '转发消息不能和普通消息混在一起发送,转发需要保证message只有type为node的元素',
}; };
} }
return { valid: true }; return {valid: true};
} }
async _handle(payload: OB11PostSendMsg): Promise<{ message_id: number }> { async _handle(payload: OB11PostSendMsg): Promise<ReturnDataType> {
this.contextMode = ContextMode.Normal;
if (payload.message_type === 'group') this.contextMode = ContextMode.Group; if (payload.message_type === 'group') this.contextMode = ContextMode.Group;
if (payload.message_type === 'private') this.contextMode = ContextMode.Private; if (payload.message_type === 'private') this.contextMode = ContextMode.Private;
const peer = await createContext(this.core, payload, this.contextMode); const peer = await createContext(this.core, payload, this.contextMode);
@@ -110,17 +115,19 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
); );
if (getSpecialMsgNum(payload, OB11MessageDataType.node)) { if (getSpecialMsgNum(payload, OB11MessageDataType.node)) {
const returnMsg = await this.handleForwardedNodes(peer, messages as OB11MessageNode[]); const packetMode = this.core.apis.PacketApi.available
if (returnMsg) { const returnMsgAndResId = packetMode
? await this.handleForwardedNodesPacket(peer, messages as OB11MessageNode[])
: await this.handleForwardedNodes(peer, messages as OB11MessageNode[]);
if (returnMsgAndResId.message) {
const msgShortId = MessageUnique.createUniqueMsgId({ const msgShortId = MessageUnique.createUniqueMsgId({
guildId: '', guildId: '',
peerUid: peer.peerUid, peerUid: peer.peerUid,
chatType: peer.chatType, chatType: peer.chatType,
}, returnMsg!.msgId); }, (returnMsgAndResId.message)!.msgId);
return { message_id: msgShortId! }; return {message_id: msgShortId!, res_id: returnMsgAndResId.res_id};
} else {
throw Error('发送转发消息失败');
} }
throw Error('发送转发消息失败');
} else { } else {
// if (getSpecialMsgNum(payload, OB11MessageDataType.music)) { // if (getSpecialMsgNum(payload, OB11MessageDataType.music)) {
// const music: OB11MessageCustomMusic = messages[0] as OB11MessageCustomMusic; // const music: OB11MessageCustomMusic = messages[0] as OB11MessageCustomMusic;
@@ -130,13 +137,61 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
} }
// log("send msg:", peer, sendElements) // log("send msg:", peer, sendElements)
const { sendElements, deleteAfterSentFiles } = await this.obContext.apis.MsgApi const {sendElements, deleteAfterSentFiles} = await this.obContext.apis.MsgApi
.createSendElements(messages, peer); .createSendElements(messages, peer);
const returnMsg = await this.obContext.apis.MsgApi.sendMsgWithOb11UniqueId(peer, sendElements, deleteAfterSentFiles); const returnMsg = await this.obContext.apis.MsgApi.sendMsgWithOb11UniqueId(peer, sendElements, deleteAfterSentFiles);
return { message_id: returnMsg!.id! }; return {message_id: returnMsg!.id!};
} }
private async handleForwardedNodes(destPeer: Peer, messageNodes: OB11MessageNode[]): Promise<RawMessage | null> { private async handleForwardedNodesPacket(msgPeer: Peer, messageNodes: OB11MessageNode[]): Promise<{
message: RawMessage | null,
res_id?: string
}> {
const logger = this.core.context.logger;
const packetMsg: PacketMsg[] = [];
for (const node of messageNodes) {
if ((node.data.id && typeof node.data.content !== "string") || !node.data.id) {
const OB11Data = normalize(node.data.content);
const {sendElements} = await this.obContext.apis.MsgApi.createSendElements(OB11Data, msgPeer);
const packetMsgElements: rawMsgWithSendMsg = {
senderUin: node.data.user_id ?? +this.core.selfInfo.uin,
senderName: node.data.nickname,
groupId: msgPeer.chatType === ChatType.KCHATTYPEGROUP ? +msgPeer.peerUid : undefined,
time: Date.now(),
msg: sendElements,
}
logger.logDebug(`handleForwardedNodesPacket 开始转换 ${JSON.stringify(packetMsgElements)}`);
const transformedMsg = this.core.apis.PacketApi.packetSession?.packer.packetConverter.rawMsgWithSendMsgToPacketMsg(packetMsgElements);
logger.logDebug(`handleForwardedNodesPacket 转换为 ${JSON.stringify(transformedMsg)}`);
packetMsg.push(transformedMsg!);
} else {
logger.logDebug(`handleForwardedNodesPacket 跳过元素 ${JSON.stringify(node)}`);
}
}
const resid = await this.core.apis.PacketApi.sendUploadForwardMsg(packetMsg, msgPeer.chatType === ChatType.KCHATTYPEGROUP ? +msgPeer.peerUid : 0);
const forwardJson = new PacketMultiMsgElement({
elementType: ElementType.STRUCTLONGMSG,
elementId: "",
structLongMsgElement: {
xmlContent: "",
resId: resid
}
}, packetMsg).JSON;
const finallySendElements = {
elementType: ElementType.ARK,
elementId: "",
arkElement: {
bytesData: JSON.stringify(forwardJson),
},
} as SendArkElement
const returnMsg = await this.obContext.apis.MsgApi.sendMsgWithOb11UniqueId(msgPeer, [finallySendElements], [], true).catch(_ => undefined)
return {message: returnMsg ?? null, res_id: resid};
}
private async handleForwardedNodes(destPeer: Peer, messageNodes: OB11MessageNode[]): Promise<{
message: RawMessage | null,
res_id?: string
}> {
const selfPeer = { const selfPeer = {
chatType: ChatType.KCHATTYPEC2C, chatType: ChatType.KCHATTYPEC2C,
peerUid: this.core.selfInfo.uid, peerUid: this.core.selfInfo.uid,
@@ -146,10 +201,10 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
for (const messageNode of messageNodes) { for (const messageNode of messageNodes) {
const nodeId = messageNode.data.id; const nodeId = messageNode.data.id;
if (nodeId) { if (nodeId) {
//对Mgsid和OB11ID混用情况兜底 // 对Msgid和OB11ID混用情况兜底
const nodeMsg = MessageUnique.getMsgIdAndPeerByShortId(parseInt(nodeId)) || MessageUnique.getPeerByMsgId(nodeId); const nodeMsg = MessageUnique.getMsgIdAndPeerByShortId(parseInt(nodeId)) || MessageUnique.getPeerByMsgId(nodeId);
if (!nodeMsg) { if (!nodeMsg) {
logger.logError('转发消息失败,未找到消息', nodeId); logger.logError.bind(this.core.context.logger)('转发消息失败,未找到消息', nodeId);
continue; continue;
} }
nodeMsgIds.push(nodeMsg.MsgId); nodeMsgIds.push(nodeMsg.MsgId);
@@ -161,18 +216,18 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
const isNodeMsg = OB11Data.filter(e => e.type === OB11MessageDataType.node).length;//找到子转发消息 const isNodeMsg = OB11Data.filter(e => e.type === OB11MessageDataType.node).length;//找到子转发消息
if (isNodeMsg !== 0) { if (isNodeMsg !== 0) {
if (isNodeMsg !== OB11Data.length) { if (isNodeMsg !== OB11Data.length) {
logger.logError('子消息中包含非node消息 跳过不合法部分'); logger.logError.bind(this.core.context.logger)('子消息中包含非node消息 跳过不合法部分');
continue; continue;
} }
const nodeMsg = await this.handleForwardedNodes(selfPeer, OB11Data.filter(e => e.type === OB11MessageDataType.node)); const nodeMsg = await this.handleForwardedNodes(selfPeer, OB11Data.filter(e => e.type === OB11MessageDataType.node));
if (nodeMsg) { if (nodeMsg) {
nodeMsgIds.push(nodeMsg.msgId); nodeMsgIds.push(nodeMsg.message!.msgId);
MessageUnique.createUniqueMsgId(selfPeer, nodeMsg.msgId); MessageUnique.createUniqueMsgId(selfPeer, nodeMsg.message!.msgId);
} }
//完成子卡片生成跳过后续 //完成子卡片生成跳过后续
continue; continue;
} }
const { sendElements } = await this.obContext.apis.MsgApi const {sendElements} = await this.obContext.apis.MsgApi
.createSendElements(OB11Data, destPeer); .createSendElements(OB11Data, destPeer);
//拆分消息 //拆分消息
@@ -209,11 +264,11 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
for (const msgId of nodeMsgIds) { for (const msgId of nodeMsgIds) {
const nodeMsgPeer = MessageUnique.getPeerByMsgId(msgId); const nodeMsgPeer = MessageUnique.getPeerByMsgId(msgId);
if (!nodeMsgPeer) { if (!nodeMsgPeer) {
logger.logError('转发消息失败,未找到消息', msgId); logger.logError.bind(this.core.context.logger)('转发消息失败,未找到消息', msgId);
continue; continue;
} }
const nodeMsg = (await this.core.apis.MsgApi.getMsgsByMsgId(nodeMsgPeer.Peer, [msgId])).msgList[0]; const nodeMsg = (await this.core.apis.MsgApi.getMsgsByMsgId(nodeMsgPeer.Peer, [msgId])).msgList[0];
srcPeer = srcPeer ?? { chatType: nodeMsg.chatType, peerUid: nodeMsg.peerUid }; srcPeer = srcPeer ?? {chatType: nodeMsg.chatType, peerUid: nodeMsg.peerUid};
if (srcPeer.peerUid !== nodeMsg.peerUid) { if (srcPeer.peerUid !== nodeMsg.peerUid) {
needSendSelf = true; needSendSelf = true;
} }
@@ -236,10 +291,14 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
if (retMsgIds.length === 0) throw Error('转发消息失败,生成节点为空'); if (retMsgIds.length === 0) throw Error('转发消息失败,生成节点为空');
try { try {
logger.logDebug('开发转发', srcPeer, destPeer, retMsgIds); logger.logDebug('开发转发', srcPeer, destPeer, retMsgIds);
return await this.core.apis.MsgApi.multiForwardMsg(srcPeer!, destPeer, retMsgIds); return {
message: await this.core.apis.MsgApi.multiForwardMsg(srcPeer!, destPeer, retMsgIds)
};
} catch (e) { } catch (e) {
logger.logError('forward failed', e); logger.logError.bind(this.core.context.logger)('forward failed', e);
return null; return {
message: null
};
} }
} }
@@ -262,7 +321,7 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
try { try {
return await this.core.apis.MsgApi.sendMsg(selfPeer, sendElements, true); return await this.core.apis.MsgApi.sendMsg(selfPeer, sendElements, true);
} catch (e) { } catch (e) {
logger.logError(e, '克隆转发消息失败,将忽略本条消息', msg); logger.logError.bind(this.core.context.logger)(e, '克隆转发消息失败,将忽略本条消息', msg);
} }
} }
} }

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