Compare commits

...

181 Commits

Author SHA1 Message Date
手瓜一十雪
9c34f558d3 release: 2.2.11 2024-08-26 02:16:24 +08:00
手瓜一十雪
3e2da3b490 release: v2.2.10 2024-08-26 01:58:41 +08:00
手瓜一十雪
fb4a4f50be release: v2.2.9 2024-08-26 01:52:09 +08:00
手瓜一十雪
6596e9cab6 style: 27333 类型跟进 2024-08-26 01:24:22 +08:00
手瓜一十雪
f1b137f2e1 style: 移除老旧代码 2024-08-26 01:07:01 +08:00
手瓜一十雪
535720d0fe style: lint 2024-08-26 01:00:07 +08:00
手瓜一十雪
f063cf4a16 style: 移除无用代码 2024-08-26 00:50:17 +08:00
手瓜一十雪
90bbdbf2fe style: fix 2024-08-26 00:46:58 +08:00
手瓜一十雪
5f1d8fb99d style: RegExec match->exec 2024-08-26 00:45:12 +08:00
手瓜一十雪
5486ffcdcc chore: 移除无用代码 2024-08-26 00:38:19 +08:00
手瓜一十雪
adfd123970 style: lint 2024-08-26 00:29:52 +08:00
手瓜一十雪
f1a364bfa2 style: lint 2024-08-26 00:21:49 +08:00
手瓜一十雪
9da714bf15 style: fix 2024-08-26 00:17:42 +08:00
手瓜一十雪
fc73295520 build: 2.2.9 2024-08-26 00:09:07 +08:00
手瓜一十雪
ab955e41fb style: 代码质量提高 2024-08-26 00:05:33 +08:00
手瓜一十雪
c64367335c Merge branch 'main' of https://github.com/NapNeko/NapCatQQ 2024-08-26 00:01:47 +08:00
手瓜一十雪
edc787eb3e style: lint 2024-08-26 00:01:44 +08:00
手瓜一十雪
f2c69fc68b style: remove 2024-08-25 23:59:34 +08:00
手瓜一十雪
d947fe743b Merge pull request #303 from NapNeko/dev/RefactoredMsgParsers
style: Error Object
2024-08-25 23:57:58 +08:00
手瓜一十雪
5b37ae9026 fix 2024-08-25 23:57:16 +08:00
手瓜一十雪
ec9e042b29 Merge pull request #301 from NapNeko/dev/RefactoredMsgParsers
refactor: make static functions dynamic
2024-08-25 23:50:44 +08:00
手瓜一十雪
337ac0eab9 fix: 过滤无效null类型 2024-08-25 23:50:34 +08:00
手瓜一十雪
f6a1b784c4 fix: type 2024-08-25 23:42:48 +08:00
手瓜一十雪
332fcecb78 Merge branch 'main' into dev/RefactoredMsgParsers 2024-08-25 23:16:49 +08:00
手瓜一十雪
18590be1e7 fix: 修正正确类型 2024-08-25 23:04:34 +08:00
手瓜一十雪
b76edcaf1d chore: merge main 2024-08-25 22:43:29 +08:00
手瓜一十雪
6024cabb69 Merge branch 'main' of https://github.com/NapNeko/NapCatQQ 2024-08-25 22:36:53 +08:00
手瓜一十雪
08446e648e release: 2.2.8 2024-08-25 22:36:42 +08:00
Alen
14af7a3572 Merge pull request #302 from cnxysoft/upmain
fix: 多个问题
2024-08-25 22:23:58 +08:00
Alen
cdc4275f81 fix: 多个问题
修复group_increase事件上报
修复启动时加载群员信息失败
修复文件发送失败
2024-08-25 22:23:06 +08:00
手瓜一十雪
a9ade98315 style: remove unless 2024-08-25 22:12:38 +08:00
手瓜一十雪
f3ae6fa70f style: fix 2024-08-25 22:09:30 +08:00
手瓜一十雪
96457bbec3 Merge branch 'main' into dev/RefactoredMsgParsers 2024-08-25 22:00:20 +08:00
手瓜一十雪
8f465e376e style: type 2024-08-25 21:59:07 +08:00
手瓜一十雪
adc366a959 style: 清理不规范代码 2024-08-25 21:54:20 +08:00
手瓜一十雪
a20a6bc8bb Merge branch 'main' into dev/RefactoredMsgParsers 2024-08-25 21:49:28 +08:00
手瓜一十雪
b176fa66d4 style: 样式处理 2024-08-25 21:47:55 +08:00
手瓜一十雪
f81b1926fb Merge branch 'main' into dev/RefactoredMsgParsers 2024-08-25 21:37:56 +08:00
手瓜一十雪
7b7609a068 style: 标准化样式 2024-08-25 21:37:36 +08:00
Seijo Cecilia
670d4108e6 fix: typo 2024-08-25 20:46:20 +08:00
Seijo Cecilia
a5c7b88a40 fix: createSendElements reference 2024-08-25 20:46:03 +08:00
Seijo Cecilia
e52b2e6d69 fix: createSendElements reference 2024-08-25 20:00:14 +08:00
Seijo Cecilia
e4066fb8df refactor: completely remove genMessage.ts 2024-08-25 19:53:20 +08:00
Seijo Cecilia
f7a0fb22b4 refactor: make SendMsg a single file again 2024-08-25 19:45:14 +08:00
Seijo Cecilia
cad2ae723c refactor: normalize method name 2024-08-25 19:43:41 +08:00
Seijo Cecilia
889a8c6093 fix: sync fixes in handling forwarded nodes 2024-08-25 19:43:01 +08:00
Seijo Cecilia
573418914f Merge branch 'main' into dev/RefactoredMsgParsers 2024-08-25 19:40:40 +08:00
Seijo Cecilia
d7fb6f9c05 refactor: ob11 to raw message constructors 2024-08-25 19:38:35 +08:00
手瓜一十雪
136e27d655 fix: 消息组合 2024-08-25 19:38:19 +08:00
Seijo Cecilia
d5ff2d7099 fix: from || to ?? 2024-08-25 18:20:58 +08:00
Seijo Cecilia
2a7f8d0c99 refactor: raw message parsers 2024-08-25 17:54:50 +08:00
手瓜一十雪
e3ca5df713 Delete .github/workflows/codacy.yml 2024-08-25 12:52:31 +08:00
手瓜一十雪
bda32f3e8f chore:update 2024-08-25 12:08:36 +08:00
手瓜一十雪
a7c6e45a92 chore: codacy 2024-08-25 12:02:03 +08:00
手瓜一十雪
7c20ca9b64 style:lint 2024-08-25 11:45:50 +08:00
手瓜一十雪
a201461eff chore: lint 2024-08-25 11:18:11 +08:00
Seijo Cecilia
e5d9df37c5 Merge remote-tracking branch 'origin/main' 2024-08-25 10:14:42 +08:00
Seijo Cecilia
106fbaf086 refactor: make parseMessage an instance method 2024-08-25 10:14:11 +08:00
Alen
a0024c98d5 release: 2.2.7 2024-08-25 09:59:07 +08:00
Alen
684a702638 Merge pull request #300 from cnxysoft/upmain
fix: 多处修复
2024-08-25 09:47:07 +08:00
Alen
aec4a009d1 fix: 撤回重复上报 2024-08-25 09:44:40 +08:00
Alen
822af575c9 fix: 群相关
group_admin事件上报
群成员信息/群列表缓存
ProfileService新增事件
2024-08-25 02:08:14 +08:00
Alen
485efa7d44 Merge branch 'main' into upmain 2024-08-25 01:25:12 +08:00
手瓜一十雪
3d09d45423 build: 2.2.7
DelGroupNotice
2024-08-24 23:50:05 +08:00
手瓜一十雪
4c69c6d9fd fix: v2.2.6 2024-08-24 23:24:24 +08:00
手瓜一十雪
920a41acef build: 2.2.6-test 2024-08-24 23:22:32 +08:00
Alen
0cf13a284c Merge branch 'main' into upmain 2024-08-24 22:26:56 +08:00
手瓜一十雪
a89cdef436 chore: kickMemberV2Inner 2024-08-24 22:23:44 +08:00
手瓜一十雪
881d88f4ad feat: quitGroupV2 2024-08-24 22:12:14 +08:00
Alen
a72c96f56d Merge branch 'main' into upmain 2024-08-24 21:54:43 +08:00
手瓜一十雪
bc8235b209 fix: 移除无用代码 2024-08-24 21:54:11 +08:00
手瓜一十雪
0087495749 fix: 类型推断 2024-08-24 21:50:29 +08:00
手瓜一十雪
9560afd4a7 fix: 一处错误推断 2024-08-24 21:47:39 +08:00
手瓜一十雪
56ec8559a0 fix: type 2024-08-24 21:42:49 +08:00
手瓜一十雪
99ca79ac7d chore: 去掉无用注释 2024-08-24 12:07:40 +08:00
手瓜一十雪
24564f4c74 release: 2.2.5 2024-08-24 12:05:07 +08:00
手瓜一十雪
212c802a1e fix: BuddyReq 2024-08-24 11:52:50 +08:00
手瓜一十雪
984b5d6c40 fix: 好友申请重复推送 2024-08-24 11:26:05 +08:00
手瓜一十雪
0e3a4191a9 Merge pull request #298 from shengwang52005/readme
docs: 增强胡言乱语水平
2024-08-24 01:03:21 +08:00
Miaowing
570a34bca5 docs: 增强胡言乱语水平
增强胡言乱语水平
2024-08-24 00:16:16 +08:00
手瓜一十雪
c9a0c29286 build: 2.2.0 2024-08-23 20:46:45 +08:00
手瓜一十雪
b5f804ec22 build: CQ码回滚 提高兼容 2024-08-23 20:46:23 +08:00
手瓜一十雪
dadbb83271 docs: 提高胡言乱语水平 2024-08-23 12:54:04 +08:00
手瓜一十雪
848aacdbbf releas: 2.2.4 2024-08-23 11:34:26 +08:00
手瓜一十雪
da3665a167 release: 2.2.0
release: 2.2.1

release: 2.2.2

chore

chore: 扩大范围

release: 2.2.3
2024-08-23 11:33:22 +08:00
手瓜一十雪
dcf0a06217 chore: 扩大范围 2024-08-23 11:20:29 +08:00
手瓜一十雪
3b5e6553cd chore 2024-08-23 11:04:47 +08:00
手瓜一十雪
509390af20 release: 2.2.2 2024-08-23 11:01:24 +08:00
手瓜一十雪
9ad511a9c0 release: 2.2.1 2024-08-23 10:57:13 +08:00
手瓜一十雪
89c102513d release: 2.2.0 2024-08-23 10:55:19 +08:00
手瓜一十雪
5e65ae76ad chore: 移除无用代码 2024-08-22 15:58:23 +08:00
手瓜一十雪
b6c364cd78 fix: 误操作 2024-08-22 15:56:01 +08:00
手瓜一十雪
e086b8707f refactor: chattype 2024-08-22 15:53:27 +08:00
手瓜一十雪
90dddd10a9 chore: 移除类型 2024-08-22 15:42:45 +08:00
手瓜一十雪
2dd0907565 fix: type 2024-08-22 15:42:07 +08:00
手瓜一十雪
f31b0d0c71 chore: 进一步拉高版本 2024-08-22 15:40:10 +08:00
手瓜一十雪
a0825b75f7 chore: parseMsg 重构 2024-08-22 15:28:54 +08:00
手瓜一十雪
a3bd4c0f73 chore: 推动重构 2024-08-22 15:06:49 +08:00
手瓜一十雪
a3e8c9b28a chore: 重构 2024-08-22 14:58:05 +08:00
手瓜一十雪
d7fb850b4a chore: 丢弃空消息 2024-08-22 14:35:48 +08:00
手瓜一十雪
d084778a6e chore: re 2024-08-22 14:34:09 +08:00
手瓜一十雪
8ca30de760 chore: 兼容性提高 2024-08-22 14:26:49 +08:00
手瓜一十雪
8a10b81bd9 chore: fix 2024-08-22 14:15:29 +08:00
手瓜一十雪
4a93c4e584 chore: 结构性调整 2024-08-22 14:13:52 +08:00
手瓜一十雪
50177cd6bd chore: style&&lint 2024-08-22 14:12:24 +08:00
手瓜一十雪
71a2e52739 chore: 解耦 2024-08-22 14:11:20 +08:00
手瓜一十雪
4fac6d5aa3 chore: 解耦 2024-08-22 14:05:01 +08:00
手瓜一十雪
37d061b602 chore: 进一步解耦 2024-08-22 13:53:07 +08:00
手瓜一十雪
40193e4edc chore: 解耦 2024-08-22 13:46:47 +08:00
手瓜一十雪
b4e9d61871 chore: 解耦代码 2024-08-22 13:39:53 +08:00
Wesley F. Young
d44b589e55 docs: update version range 2024-08-21 09:44:11 +08:00
手瓜一十雪
68216415b6 release: 2.1.0 2024-08-21 08:16:59 +08:00
手瓜一十雪
ba53da18d1 release: 2.1.0 2024-08-21 08:13:53 +08:00
手瓜一十雪
9b76fa3582 chore: LLNC Deprecated 2024-08-21 08:12:52 +08:00
手瓜一十雪
13d8d10a7f Merge pull request #289 from NapNeko/extend
Support: 9.9.15-27254
2024-08-21 08:09:37 +08:00
手瓜一十雪
5c6c1bb09d Merge branch 'main' into extend 2024-08-20 20:36:53 +08:00
手瓜一十雪
12105d96ea release: v2.0.37 2024-08-20 20:33:06 +08:00
手瓜一十雪
4054756035 Merge branch 'main' into extend 2024-08-20 20:27:22 +08:00
手瓜一十雪
16769c7838 fix: 提高兼容性 2024-08-20 20:26:49 +08:00
手瓜一十雪
cd076c5959 fix 2024-08-20 20:22:44 +08:00
手瓜一十雪
f52e1aa131 Merge branch 'main' into extend 2024-08-20 20:07:02 +08:00
手瓜一十雪
fdc1ef7e9a fix: error 2024-08-20 20:03:41 +08:00
手瓜一十雪
9cccf2d47b Merge branch 'main' of https://github.com/NapNeko/NapCatQQ 2024-08-20 20:03:20 +08:00
手瓜一十雪
0796f27f2a fix: file ext and blank data 2024-08-20 20:03:02 +08:00
手瓜一十雪
6c84014e0d doc: 2024-08-20 19:36:13 +08:00
手瓜一十雪
cd496a22bf chore: 调整appid 2024-08-20 17:47:14 +08:00
手瓜一十雪
0200343780 chore: 调整appid 2024-08-20 17:12:07 +08:00
手瓜一十雪
47fb629d26 Merge branch 'main' into extend 2024-08-20 16:59:17 +08:00
Alen
71ae08706b release: v2.0.35 2024-08-20 16:49:16 +08:00
Alen
50dd798757 Merge pull request #285 from cnxysoft/upmain
refactor: 接口兼容
2024-08-20 16:39:51 +08:00
Alen
0c8cf73746 refactor: 接口兼容
调整SetGroupHeader接口为SetGroupPortrait,使其兼容gocq标准
2024-08-20 16:35:23 +08:00
Alen
1bee811312 refactor: 接口兼容
调整SetSelfProfile接口为SetQQProfile,使其兼容gocq标准
2024-08-20 16:05:23 +08:00
Alen
b4c0068637 Merge pull request #284 from cnxysoft/upmain
fix: 多处修复
2024-08-20 14:31:02 +08:00
Alen
f484c6e5fe fix: 多处修复
1.修复group_card事件上报
2.修复group_admin事件上报
2024-08-20 14:28:32 +08:00
Alen
7a08187c5f fix: Uid转Uin
修复Uid转Uin兜底逻辑
2024-08-20 07:28:25 +08:00
Alen
c4d7d5a0d4 fix: 多处修改
1.修改(疑似)旧设备回复消息验证失败的解决方案
2.修复Base64发送文件失败
3.修复群时间监听器未注册
2024-08-20 01:02:03 +08:00
手瓜一十雪
5b75e753a7 Util 2024-08-19 21:38:31 +08:00
手瓜一十雪
326e9b86ce Merge branch 'main' into extend 2024-08-19 20:44:49 +08:00
手瓜一十雪
d22f5d369c releas: v2.0.34 2024-08-19 20:27:04 +08:00
手瓜一十雪
d76503995c fix: ws心跳实现 2024-08-19 20:24:26 +08:00
手瓜一十雪
eab930c083 doc: 小tip 2024-08-19 19:33:58 +08:00
手瓜一十雪
e430cc54f2 Merge branch 'main' into extend 2024-08-19 19:03:31 +08:00
手瓜一十雪
fd26a9c698 fix 2024-08-19 18:53:47 +08:00
手瓜一十雪
e79b608f77 support: 27206 2024-08-19 18:47:12 +08:00
Wesley F. Young
42b23a6c9c docs: clarify version range 2024-08-19 16:17:33 +08:00
Alen
8d94f24c71 release: v2.0.33 2024-08-19 15:43:41 +08:00
Alen
6ac74c39d9 Merge pull request #279 from cnxysoft/upmain
fix: 多处修复
2024-08-19 15:41:10 +08:00
Alen
836eb7b708 fix: 多处修复
1.修复部分Uin转换失败导致的相关API错误
2.继续改进get_group_member_info效率
3.增加fetchUserDetailInfo容错(暂时性/待观察)
2024-08-19 15:39:34 +08:00
Alen
698624b4dc release: v2.0.32 2024-08-19 10:09:53 +08:00
Alen
5c1df82076 Merge pull request #278 from cnxysoft/upmain
fix: 多处修改
2024-08-19 10:07:16 +08:00
Alen
5d649b3687 perf: 优化API效率
优化get_group_member_info效率
2024-08-19 00:55:32 +08:00
Alen
a6a3d71155 Revert "perf: API优化"
This reverts commit 1cc9d501ab.
2024-08-18 18:49:46 +08:00
Alen
1cc9d501ab perf: API优化
优化get_group_member_info在查询非群员时的效率
2024-08-18 14:33:35 +08:00
Alen
7a98025df8 fix: 引用消息失败
修复 (疑似)旧设备引用消息验证失败
2024-08-18 01:40:14 +08:00
Alen
44d6ed5e80 release: v2.0.31 2024-08-17 23:15:52 +08:00
手瓜一十雪
b5f2226bef Merge pull request #270 from cnxysoft/upmain
fix: Uid转Uin
2024-08-17 22:53:42 +08:00
Alen
ddbffe55d2 Merge branch 'main' into upmain 2024-08-17 22:19:49 +08:00
手瓜一十雪
9676b1d0e9 fix 2024-08-17 18:13:43 +08:00
手瓜一十雪
8142d3bfeb fix: 打错啦 2024-08-17 18:12:43 +08:00
手瓜一十雪
755ad27a0a Merge pull request #269 from gfhdhytghd/patch-1
修改许可证以禁止宣传
2024-08-17 15:45:20 +08:00
手瓜一十雪
5afa2dcdf1 chore: 复活赛打赢啦 2024-08-17 15:37:19 +08:00
手瓜一十雪
03098ee024 chore: util 2024-08-17 15:21:47 +08:00
手瓜一十雪
a2bfdd003c fix: getNTUserDataInfoConfig 2024-08-17 15:18:33 +08:00
lin
7eb80646ba Update LICENSE
修改License,从法律层面禁止在公共社交媒体宣传
2024-08-17 14:11:57 +08:00
Alen
6fd24e57d3 Merge branch 'main' into upmain 2024-08-17 13:24:36 +08:00
手瓜一十雪
22c90adb47 chore: docs 2024-08-17 12:27:53 +08:00
Alen
df0c6fafbe fix: Uid转Uin
修复设置管理、禁言等数个API失效
对查询企业陌生人信息进行容错
2024-08-17 00:17:51 +08:00
手瓜一十雪
dc30321b04 Update README.md 2024-08-17 00:16:23 +08:00
手瓜一十雪
63dd98d2df release: 2.0.30 2024-08-16 21:44:34 +08:00
手瓜一十雪
caaa6ed506 feat: support SetInputStatus 2024-08-16 20:35:05 +08:00
手瓜一十雪
caf23792cb Merge branch 'main' of https://github.com/NapNeko/NapCatQQ 2024-08-16 13:01:38 +08:00
手瓜一十雪
e430db20aa release: 2.0.29 2024-08-16 13:01:30 +08:00
手瓜一十雪
6fc5da9b67 Merge pull request #267 from Fripine/fix/OB11GroupRequestEvent
fix: wrong user_id in GroupRequestEvent
2024-08-16 13:00:32 +08:00
Fripine
f428e57724 fix: GroupRequestEvent 2024-08-16 12:57:42 +08:00
手瓜一十雪
14ab21fe9a Merge pull request #266 from Fripine/fix/OB11FriendRequestEvent
fix: wrong comment words in FriendRequestEvent
2024-08-16 12:44:33 +08:00
手瓜一十雪
85626e19da release: 2.0.28 2024-08-16 12:44:11 +08:00
Fripine
8712160fd7 fix: FriendRequestEvent 2024-08-16 12:21:33 +08:00
手瓜一十雪
75b33f5cb1 chore: fix 2024-08-16 12:16:21 +08:00
手瓜一十雪
f5e8ede847 release: 2.0.27 2024-08-16 10:49:22 +08:00
手瓜一十雪
3b3f684a8c chore: 清除废弃代码 2024-08-16 09:52:50 +08:00
手瓜一十雪
a78b60d40e chore: 进一步识别会话 2024-08-16 09:37:36 +08:00
112 changed files with 2679 additions and 3404 deletions

View File

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

View File

@@ -1,4 +1,4 @@
GNU GENERAL PUBLIC LICENSE
GNU GENERAL PUBLIC Without Social media promotion LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
@@ -111,6 +111,10 @@ above, provided that you also meet all of these conditions:
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
dYou may use this software in accordance with the above terms,
but you are not allowed to promote this project or your projects
based on this project on any public social media.
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in

View File

@@ -3,33 +3,33 @@
</div>
---
## 欢迎回来
NapCatQQ (aka 猫猫框架) 是现代化的基于 NTQQ 的 Bot 协议端实现。
## 项目介绍
NapCatQQ 是现代化的基于 NTQQ 的 Bot 协议端实现。
## 项目优势
## 猫猫技能
- [x] **多种启动方式**支持以无头、LiteLoader 插件、仅 QQ GUI 三种方式启动
- [x] **低占用**:无头模式占用资源极低,适合在服务器上运行
- [x] **超多接口**在实现大部分Onebot接口上扩展了一套私有API
- [x] **WebUI**:自带 WebUI 支持,远程管理更加便捷
## 如何使用
## 使用猫猫
可前往 [Release](https://github.com/NapNeko/NapCatQQ/releases/) 页面下载最新版本
**首次使用**请务必前往[官方文档](https://napneko.github.io/)查看使用教程。
## 相关链接
## 回家旅途
[QQ Group](https://qm.qq.com/q/VfjAq5HIMS)
[Telegram Link](https://t.me/+nLZEnpne-pQ1OWFl)
## 鸣谢名单
## 猫猫朋友
感谢 [LLOneBot](https://github.com/LLOneBot/LLOneBot) 提供初始版本基础
感谢 [Lagrange](https://github.com/LagrangeDev/Lagrange.Core) 对本项目的大力支持
---
## 使用许可
## 约法三章
任何使用本仓库代码的地方,都应当严格遵守[本仓库开源许可](./LICENSE)。**此外,禁止任何项目未经授权二次分发或基于 [core](./src/core) 部分代码开发。**
任何使用本仓库代码的地方,都应当严格遵守[本仓库开源许可](./LICENSE)。**此外,禁止任何项目未经授权二次分发或基于 NapCat 代码开发。**

View File

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

View File

@@ -2,7 +2,7 @@
"name": "napcat",
"private": true,
"type": "module",
"version": "2.0.26",
"version": "2.2.11",
"scripts": {
"build:framework": "vite build --mode framework",
"build:shell": "vite build --mode shell",

View File

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

View File

@@ -6,6 +6,7 @@ export type ListenerClassBase = Record<string, string>;
export interface ListenerIBase {
// eslint-disable-next-line @typescript-eslint/no-misused-new
new(listener: any): ListenerClassBase;
[key: string]: any;
}
export class NTEventChannel extends EventEmitter {
@@ -26,12 +27,11 @@ export class NTEventChannel extends EventEmitter {
}
createProxyDispatch(ListenerMainName: string) {
// eslint-disable-next-line @typescript-eslint/no-this-alias
const current = this;
const dispatcherListener = this.dispatcherListener.bind(this);
return new Proxy({}, {
get(_target: any, prop: any, _receiver: any) {
return (...args: any[]) => {
current.dispatcherListener.apply(current, [ListenerMainName + '/' + prop, ...args]);
dispatcherListener(ListenerMainName + '/' + prop, ...args);
};
},
});
@@ -47,7 +47,7 @@ export class NTEventChannel extends EventEmitter {
Listener = new ListenerType(this.createProxyDispatch(listenerMainName));
if (!Listener) throw new Error('Init Listener failed');
//实例化NTQQ Listener外包装
const ServiceSubName = listenerMainName.match(/^NodeIKernel(.*?)Listener$/)![1];
const ServiceSubName = /^NodeIKernel(.*?)Listener$/.exec(listenerMainName)![1];
const Service = 'NodeIKernel' + ServiceSubName + 'Service/addKernel' + ServiceSubName + 'Listener';
const addfunc = this.createEventFunction<(listener: T) => number>(Service);
//添加Listener到NTQQ
@@ -124,14 +124,7 @@ export class NTEventChannel extends EventEmitter {
EventName = '', timeout: number = 3000, ...args: Parameters<EventType>) {
return new Promise<Awaited<ReturnType<EventType>>>(async (resolve, reject) => {
const EventFunc = this.createEventFunction<EventType>(EventName);
let complete = false;
const Timeouter = setTimeout(() => {
if (!complete) {
reject(new Error('NTEvent EventName:' + EventName + ' timeout'));
}
}, timeout);
const retData = await EventFunc!(...args);
complete = true;
resolve(retData);
});
}

View File

@@ -2,7 +2,7 @@ import path, { dirname } from 'path';
import { fileURLToPath } from 'url';
import fs from 'fs';
export const napcat_version = '2.0.26';
export const napcat_version = '2.2.11';
export class NapCatPathWrapper {
binaryPath: string;

View File

@@ -53,7 +53,7 @@ export abstract class ConfigBase<T> {
}
save(newConfigData: T = this.configData as T) {
save(newConfigData: T = this.configData) {
const logger = this.coreContext.context.logger;
const selfInfo = this.coreContext.selfInfo;
this.configData = newConfigData;

View File

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

View File

@@ -46,7 +46,7 @@ export class QQBasicInfoWrapper {
}
requireMinNTQQBuild(buildStr: string) {
const currentBuild = parseInt(this.getQQBuildStr() || '0');
const currentBuild = +(this.getQQBuildStr() ?? '0');
if (currentBuild == 0) throw new Error('QQBuildStr获取失败');
return currentBuild >= parseInt(buildStr);
}
@@ -74,8 +74,6 @@ export class QQBasicInfoWrapper {
this.context.logger.log(
`[QQ版本兼容性检测] ${this.getFullQQVesion()} 版本兼容性不佳,可能会导致一些功能无法正常使用`,
);
return { appid: systemPlatform === 'linux' ? '537237950' : '537237765', qua: this.getQUAInternal() };
return { appid: systemPlatform === 'linux' ? '537240795' : '537240709', qua: this.getQUAInternal() };
}
}
export let QQBasicInfo: QQBasicInfoWrapper | undefined;

View File

@@ -4,6 +4,7 @@ import crypto, { randomUUID } from 'crypto';
import util from 'util';
import path from 'node:path';
import * as fileType from 'file-type';
import { solveProblem } from './helper';
export function isGIF(path: string) {
const buffer = Buffer.alloc(4);
@@ -185,25 +186,28 @@ export enum FileUriType {
}
export async function checkUriType(Uri: string) {
//先判断是否是本地文件
try {
if (fs.existsSync(Uri)) return { Uri: Uri, Type: FileUriType.Local };
} catch (error) {
const LocalFileRet = await solveProblem((uri: string) => {
if (fs.existsSync(uri)) {
return { Uri: uri, Type: FileUriType.Local };
}
try {
return undefined;
}, Uri);
if (LocalFileRet) return LocalFileRet;
const OtherFileRet = await solveProblem((uri: string) => {
//再判断是否是Http
if (Uri.startsWith('http://') || Uri.startsWith('https://')) {
return { Uri: Uri, Type: FileUriType.Remote };
if (uri.startsWith('http://') || uri.startsWith('https://')) {
return { Uri: uri, Type: FileUriType.Remote };
}
//再判断是否是Base64
if (Uri.startsWith('base64://')) {
return { Uri: Uri, Type: FileUriType.Base64 };
if (uri.startsWith('base64://')) {
return { Uri: uri, Type: FileUriType.Base64 };
}
if (Uri.startsWith('file://')) {
let pathname: string;
if (uri.startsWith('file://')) {
let filePath: string;
// await fs.copyFile(url.pathname, filePath);
pathname = decodeURIComponent(new URL(Uri).pathname);
const pathname = decodeURIComponent(new URL(uri).pathname);
if (process.platform === 'win32') {
filePath = pathname.slice(1);
} else {
@@ -211,8 +215,9 @@ export async function checkUriType(Uri: string) {
}
return { Uri: filePath, Type: FileUriType.Local };
}
} catch (error) {
}
}, Uri);
if (OtherFileRet) return OtherFileRet;
return { Uri: Uri, Type: FileUriType.Unknown };
}
@@ -259,7 +264,7 @@ export async function uri2local(dir: string, uri: string, filename: string | und
if (success) {
filePath = fileTypePath;
fileExt = ext;
filename = path.basename(filePath, fileExt);
filename = filename + '.' + ext;
}
return { success: true, errMsg: '', fileName: filename, ext: fileExt, path: filePath, isLocal: true };
}

View File

@@ -1,12 +1,29 @@
import crypto from 'node:crypto';
import path from 'node:path';
import fs from 'fs';
import * as fsPromise from 'node:fs/promises';
import os from 'node:os';
import { QQLevel } from '@/core';
//下面这个类是用于将uid+msgid合并的类
export async function solveProblem<T extends (...arg: any[]) => any>(func: T, ...args: Parameters<T>): Promise<ReturnType<T> | undefined> {
return new Promise<ReturnType<T> | undefined>((resolve) => {
try {
const result = func(...args);
resolve(result);
} catch (e) {
resolve(undefined);
}
});
}
export async function solveAsyncProblem<T extends (...args: any[]) => Promise<any>>(func: T, ...args: Parameters<T>): Promise<Awaited<ReturnType<T>> | undefined> {
return new Promise<Awaited<ReturnType<T>> | undefined>((resolve) => {
func(...args).then((result) => {
resolve(result);
}).catch((e) => {
resolve(undefined);
});
});
}
//下面这个类是用于将uid+msgid合并的类
export class UUIDConverter {
static encode(highStr: string, lowStr: string): string {
const high = BigInt(highStr);
@@ -97,19 +114,19 @@ export function isEqual(obj1: any, obj2: any) {
export function getDefaultQQVersionConfigInfo(): QQVersionConfigType {
if (os.platform() === 'linux') {
return {
baseVersion: '3.2.12-26702',
curVersion: '3.2.12-26702',
baseVersion: '3.2.12-27254',
curVersion: '3.2.12-27254',
prevVersion: '',
onErrorVersions: [],
buildId: '26702',
buildId: '27254',
};
}
return {
baseVersion: '9.9.15-26702',
curVersion: '9.9.15-26702',
baseVersion: '9.9.15-27254',
curVersion: '9.9.15-27254',
prevVersion: '',
onErrorVersions: [],
buildId: '26702',
buildId: '27254',
};
}

View File

@@ -2,7 +2,7 @@ import log4js, { Configuration } from 'log4js';
import { truncateString } from '@/common/utils/helper';
import path from 'node:path';
import chalk from 'chalk';
import { AtType, ChatType, ElementType, ElementWrapper, RawMessage, SelfInfo } from '@/core';
import { AtType, ChatType, ElementType, MessageElement, RawMessage, SelfInfo } from '@/core';
export enum LogLevel {
DEBUG = 'debug',
@@ -152,11 +152,11 @@ export function rawMessageToText(msg: RawMessage, recursiveLevel = 0): string {
const tokens: string[] = [];
if (msg.chatType == ChatType.friend) {
if (msg.chatType == ChatType.KCHATTYPEC2C) {
tokens.push(`私聊 (${msg.peerUin})`);
} else if (msg.chatType == ChatType.group) {
} else if (msg.chatType == ChatType.KCHATTYPEGROUP) {
tokens.push(`群聊 (群 ${msg.peerUin}${msg.senderUin})`);
} else if (msg.chatType == ChatType.chatDevice) {
} else if (msg.chatType == ChatType.KCHATTYPEDATALINE) {
tokens.push('移动设备');
} else /* temp */ {
tokens.push(`临时消息 (${msg.peerUin})`);
@@ -164,7 +164,7 @@ export function rawMessageToText(msg: RawMessage, recursiveLevel = 0): string {
// message content
function msgElementToText(element: ElementWrapper) {
function msgElementToText(element: MessageElement) {
if (element.textElement) {
if (element.textElement.atType === AtType.notAt) {
return element.textElement.content;

View File

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

View File

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

File diff suppressed because one or more lines are too long

View File

@@ -1,17 +1,4 @@
interface IDependsAdapter {
onMSFStatusChange(arg1: number, arg2: number): void;
onMSFSsoError(args: unknown): void;
getGroupCode(args: unknown): void;
}
export interface NodeIDependsAdapter extends IDependsAdapter {
// eslint-disable-next-line @typescript-eslint/no-misused-new
new(adapter: IDependsAdapter): NodeIDependsAdapter;
}
export class DependsAdapter implements IDependsAdapter {
export class NodeIDependsAdapter {
onMSFStatusChange(arg1: number, arg2: number) {
// console.log(arg1, arg2);
// if (arg1 == 2 && arg2 == 2) {

View File

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

View File

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

View File

@@ -8,6 +8,11 @@ import {
IMAGE_HTTP_HOST_NT,
Peer,
PicElement,
PicType,
SendFileElement,
SendPicElement,
SendPttElement,
SendVideoElement,
} from '@/core/entities';
import path from 'path';
import fs from 'fs';
@@ -18,7 +23,12 @@ import imageSize from 'image-size';
import { ISizeCalculationResult } from 'image-size/dist/types/interface';
import { NodeIKernelSearchService } from '../services/NodeIKernelSearchService';
import { RkeyManager } from '../helper/rkey';
import { calculateFileMD5 } from '@/common/utils/file';
import { calculateFileMD5, isGIF } from '@/common/utils/file';
import pathLib from 'node:path';
import { defaultVideoThumbB64, getVideoInfo } from '@/common/utils/video';
import ffmpeg from 'fluent-ffmpeg';
import fsnormal from 'node:fs';
import { encodeSilk } from '@/common/utils/audio';
export class NTQQFileApi {
@@ -84,6 +94,198 @@ export class NTQQFileApi {
};
}
async createValidSendFileElement(
filePath: string,
fileName: string = '',
folderId: string = ''
): Promise<SendFileElement> {
const { fileName: _fileName, path, fileSize } = await this.core.apis.FileApi.uploadFile(filePath, ElementType.FILE);
if (fileSize === 0) {
throw new Error('文件异常大小为0');
}
return {
elementType: ElementType.FILE,
elementId: '',
fileElement: {
fileName: fileName || _fileName,
folderId: folderId,
filePath: path,
fileSize: (fileSize).toString(),
},
};
}
async createValidSendPicElement(
picPath: string,
summary: string = '',
subType: 0 | 1 = 0
): Promise<SendPicElement> {
const { md5, fileName, path, fileSize } = await this.core.apis.FileApi.uploadFile(picPath, ElementType.PIC, subType);
if (fileSize === 0) {
throw new Error('文件异常大小为0');
}
const imageSize = await this.core.apis.FileApi.getImageSize(picPath);
const picElement: any = {
md5HexStr: md5,
fileSize: fileSize.toString(),
picWidth: imageSize?.width,
picHeight: imageSize?.height,
fileName: fileName,
sourcePath: path,
original: true,
picType: isGIF(picPath) ? PicType.gif : PicType.jpg,
picSubType: subType,
fileUuid: '',
fileSubId: '',
thumbFileSize: 0,
summary,
};
return {
elementType: ElementType.PIC,
elementId: '',
picElement,
};
}
async createValidSendVideoElement(
filePath: string,
fileName: string = '',
diyThumbPath: string = '',
): Promise<SendVideoElement> {
const logger = this.core.context.logger;
const { fileName: _fileName, path, fileSize, md5 } = await this.core.apis.FileApi.uploadFile(filePath, ElementType.VIDEO);
if (fileSize === 0) {
throw new Error('文件异常大小为0');
}
let thumb = path.replace(`${pathLib.sep}Ori${pathLib.sep}`, `${pathLib.sep}Thumb${pathLib.sep}`);
thumb = pathLib.dirname(thumb);
let videoInfo = {
width: 1920, height: 1080,
time: 15,
format: 'mp4',
size: fileSize,
filePath,
};
try {
videoInfo = await getVideoInfo(path, logger);
} catch (e) {
logger.logError('获取视频信息失败', e);
}
const createThumb = new Promise<string | undefined>((resolve, reject) => {
const thumbFileName = `${md5}_0.png`;
const thumbPath = pathLib.join(thumb, thumbFileName);
ffmpeg(filePath)
.on('error', (err) => {
logger.logDebug('获取视频封面失败,使用默认封面', err);
if (diyThumbPath) {
fsPromises.copyFile(diyThumbPath, thumbPath).then(() => {
resolve(thumbPath);
}).catch(reject);
} else {
fsnormal.writeFileSync(thumbPath, Buffer.from(defaultVideoThumbB64, 'base64'));
resolve(thumbPath);
}
})
.screenshots({
timestamps: [0],
filename: thumbFileName,
folder: thumb,
size: videoInfo.width + 'x' + videoInfo.height,
}).on('end', () => {
resolve(thumbPath);
});
});
const thumbPath = new Map();
const _thumbPath = await createThumb;
const thumbSize = _thumbPath ? (await fsPromises.stat(_thumbPath)).size : 0;
// log("生成缩略图", _thumbPath)
thumbPath.set(0, _thumbPath);
const thumbMd5 = _thumbPath ? await calculateFileMD5(_thumbPath) : "";
// "fileElement": {
// "fileMd5": "",
// "fileName": "1.mp4",
// "filePath": "C:\\Users\\nanae\\OneDrive\\Desktop\\1.mp4",
// "fileSize": "1847007",
// "picHeight": 1280,
// "picWidth": 720,
// "picThumbPath": {},
// "file10MMd5": "",
// "fileSha": "",
// "fileSha3": "",
// "fileUuid": "",
// "fileSubId": "",
// "thumbFileSize": 750
// }
return {
elementType: ElementType.VIDEO,
elementId: '',
videoElement: {
fileName: fileName || _fileName,
filePath: path,
videoMd5: md5,
thumbMd5,
fileTime: videoInfo.time,
thumbPath: thumbPath,
thumbSize,
thumbWidth: videoInfo.width,
thumbHeight: videoInfo.height,
fileSize: '' + fileSize,
// fileFormat: videotype
// fileUuid: "",
// transferStatus: 0,
// progress: 0,
// invalidState: 0,
// fileSubId: "",
// fileBizId: null,
// originVideoMd5: "",
// fileFormat: 2,
// import_rich_media_context: null,
// sourceVideoCodecFormat: 2
},
};
}
async createValidSendPttElement(pttPath: string): Promise<SendPttElement> {
const {
converted,
path: silkPath,
duration,
} = await encodeSilk(pttPath, this.core.NapCatTempPath, this.core.context.logger);
// 生成语音 Path: silkPath Time: duration
if (!silkPath) {
throw new Error('语音转换失败, 请检查语音文件是否正常');
}
const { md5, fileName, path, fileSize } = await this.core.apis.FileApi.uploadFile(silkPath, ElementType.PTT);
if (fileSize === 0) {
throw new Error('文件异常大小为0');
}
if (converted) {
fsPromises.unlink(silkPath);
}
return {
elementType: ElementType.PTT,
elementId: '',
pttElement: {
fileName: fileName,
filePath: path,
md5HexStr: md5,
fileSize: fileSize,
// duration: Math.max(1, Math.round(fileSize / 1024 / 3)), // 一秒钟大概是3kb大小, 小于1秒的按1秒算
duration: duration ?? 1,
formatType: 1,
voiceType: 1,
voiceChangeType: 0,
canConvert2Text: true,
waveAmplitudes: [
0, 18, 9, 23, 16, 17, 16, 15, 44, 17, 24, 20, 14, 15, 17,
],
fileSubId: '',
playState: 1,
autoConvertText: 0,
},
};
}
async downloadMediaByUuid() {
//napCatCore.session.getRichMediaService().downloadFileForFileUuid();
}
@@ -157,9 +359,9 @@ export class NTQQFileApi {
}
//从原始消息获取文件路径
const filePath =
FileElements?.fileElement?.filePath ||
FileElements?.pttElement?.filePath ||
FileElements?.videoElement?.filePath ||
FileElements?.fileElement?.filePath ??
FileElements?.pttElement?.filePath ??
FileElements?.videoElement?.filePath ??
FileElements?.picElement?.sourcePath;
return filePath;
}
@@ -179,7 +381,7 @@ export class NTQQFileApi {
async addFileCache(peer: Peer, msgId: string, msgSeq: string, senderUid: string, elemId: string, elemType: string, fileSize: string, fileName: string) {
let GroupData;
let BuddyData;
if (peer.chatType === ChatType.group) {
if (peer.chatType === ChatType.KCHATTYPEGROUP) {
GroupData =
[{
groupCode: peer.peerUid,
@@ -189,7 +391,7 @@ export class NTQQFileApi {
groupName: 'NapCat.Cached',
remark: 'NapCat.Cached',
}];
} else if (peer.chatType === ChatType.friend) {
} else if (peer.chatType === ChatType.KCHATTYPEC2C) {
BuddyData = [{
category_name: 'NapCat.Cached',
peerUid: peer.peerUid,
@@ -317,7 +519,7 @@ export class NTQQFileApi {
}
} else if (fileMd5 || md5HexStr) {
// 没有url需要自己拼接
return `${IMAGE_HTTP_HOST}/gchatpic_new/0/0-0-${(fileMd5 || md5HexStr)!.toUpperCase()}/0`;
return `${IMAGE_HTTP_HOST}/gchatpic_new/0/0-0-${(fileMd5 ?? md5HexStr)!.toUpperCase()}/0`;
}
this.context.logger.logDebug('图片url获取失败', element);
return '';

View File

@@ -1,5 +1,5 @@
import { Friend, FriendV2, User } from '@/core/entities';
import { BuddyListReqType, InstanceContext, NapCatCore, NodeIKernelProfileService, OnBuddyChangeParams } from '@/core';
import { BuddyListReqType, InstanceContext, NapCatCore, NodeIKernelBuddyListener, NodeIKernelBuddyService, NodeIKernelProfileService, OnBuddyChangeParams } from '@/core';
import { LimitedHashTable } from '@/common/utils/MessageUnique';
export class NTQQFriendApi {
@@ -21,7 +21,7 @@ export class NTQQFriendApi {
const buddyListV2 = refresh ? await buddyService.getBuddyListV2('0', BuddyListReqType.KNOMAL) : await buddyService.getBuddyListV2('0', BuddyListReqType.KNOMAL);
uids.push(...buddyListV2.data.flatMap(item => item.buddyUids));
const data = await this.core.eventWrapper.callNoListenerEvent<NodeIKernelProfileService['getCoreAndBaseInfo']>(
'NodeIKernelProfileService/getCoreAndBaseInfo', 5000, 'nodeStore', uids,
'NodeIKernelProfileService/getCoreAndBaseInfo', 'nodeStore', uids,
);
return Array.from(data.values());
}
@@ -33,7 +33,7 @@ export class NTQQFriendApi {
const buddyListV2 = refresh ? await buddyService.getBuddyListV2('0', BuddyListReqType.KNOMAL) : await buddyService.getBuddyListV2('0', BuddyListReqType.KNOMAL);
uids.push(...buddyListV2.data.flatMap(item => item.buddyUids));
const data = await this.core.eventWrapper.callNoListenerEvent<NodeIKernelProfileService['getCoreAndBaseInfo']>(
'NodeIKernelProfileService/getCoreAndBaseInfo', 5000, 'nodeStore', uids,
'NodeIKernelProfileService/getCoreAndBaseInfo', 'nodeStore', uids,
);
data.forEach((value, key) => {
retMap.set(value.uin!, value.uid!);
@@ -55,7 +55,7 @@ export class NTQQFriendApi {
return item.buddyUids;
}));
const data = await this.core.eventWrapper.callNoListenerEvent<NodeIKernelProfileService['getCoreAndBaseInfo']>(
'NodeIKernelProfileService/getCoreAndBaseInfo', 5000, 'nodeStore', uids,
'NodeIKernelProfileService/getCoreAndBaseInfo', 'nodeStore', uids,
);
return buddyListV2.map(category => ({
categoryId: category.categoryId,
@@ -70,29 +70,19 @@ export class NTQQFriendApi {
async isBuddy(uid: string) {
return this.context.session.getBuddyService().isBuddy(uid);
}
/**
* @deprecated
* @param forced
* @returns
*/
async getFriends(forced = false): Promise<User[]> {
const [_retData, _BuddyArg] = await this.core.eventWrapper.CallNormalEvent<(force: boolean) => Promise<any>, (arg: OnBuddyChangeParams) => void>
(
'NodeIKernelBuddyService/getBuddyList',
'NodeIKernelBuddyListener/onBuddyListChange',
async clearBuddyReqUnreadCnt() {
return this.context.session.getBuddyService().clearBuddyReqUnreadCnt();
}
async getBuddyReq() {
const [, ret] = await this.core.eventWrapper.CallNormalEventV2<
NodeIKernelBuddyService['getBuddyReq'],
NodeIKernelBuddyListener['onBuddyReqChange']
>(
'NodeIKernelBuddyService/getBuddyReq',
'NodeIKernelBuddyListener/onBuddyReqChange',
1,
5000,
() => true,
forced,
);
const friends: User[] = [];
for (const categoryItem of _BuddyArg) {
for (const friend of categoryItem.buddyList) {
friends.push(friend);
}
}
return friends;
5000);
return ret;
}
async handleFriendRequest(flag: string, accept: boolean) {

View File

@@ -7,12 +7,13 @@ import {
GroupNotify,
GroupRequestOperateTypes,
InstanceContext,
KickMemberV2Req,
MemberExtSourceType,
NapCatCore,
NodeIKernelGroupListener,
NodeIKernelGroupService,
} from '@/core';
import { isNumeric, runAllWithTimeout } from '@/common/utils/helper';
import { isNumeric, runAllWithTimeout, sleep } from '@/common/utils/helper';
export class NTQQGroupApi {
context: InstanceContext;
@@ -23,7 +24,9 @@ export class NTQQGroupApi {
constructor(context: InstanceContext, core: NapCatCore) {
this.context = context;
this.core = core;
sleep(1000).then(() => {
this.initCache().then().catch(context.logger.logError);
});
}
async initCache() {
this.groups = await this.getGroups();
@@ -114,7 +117,7 @@ export class NTQQGroupApi {
return await this.context.session.getMsgService().queryMsgsWithFilterEx('0', '0', '0', {
chatInfo: {
peerUid: GroupCode,
chatType: ChatType.group,
chatType: ChatType.KCHATTYPEGROUP,
},
filterMsgType: [],
filterSendersUid: uids,
@@ -175,7 +178,7 @@ export class NTQQGroupApi {
return await this.context.session.getMsgService().queryMsgsWithFilterEx('0', '0', '0', {
chatInfo: {
peerUid: GroupCode,
chatType: ChatType.group,
chatType: ChatType.KCHATTYPEGROUP,
},
filterMsgType: [],
filterSendersUid: uids,
@@ -219,7 +222,21 @@ export class NTQQGroupApi {
// GetMsgByShoretID(ShoretID); -> MsgService.getMsgs(Peer,MsgId,1,false); -> 组出参数
return this.context.session.getGroupService().addGroupEssence(param);
}
async kickMemberV2Inner(param: KickMemberV2Req) {
return this.context.session.getGroupService().kickMemberV2(param);
}
async deleteGroupBulletin(GroupCode: string, feedId: string) {
const _Pskey = (await this.core.apis.UserApi.getPSkey(['qun.qq.com'])).domainPskeyMap.get('qun.qq.com')!;
return this.context.session.getGroupService().deleteGroupBulletin(GroupCode, _Pskey, feedId);
}
async quitGroupV2(GroupCode: string, needDeleteLocalMsg: boolean) {
const param = {
groupCode: GroupCode,
needDeleteLocalMsg: needDeleteLocalMsg
};
//应该是直接返回不需要Listener的 未经测试 需测试再发布
return this.context.session.getGroupService().quitGroupV2(param);
}
async removeGroupEssence(GroupCode: string, msgId: string) {
// 代码没测过
// 需要 ob11msgid->msgId + (peer) -> msgSeq + msgRandom
@@ -253,22 +270,28 @@ export class NTQQGroupApi {
}
async getGroupMemberV2(GroupCode: string, uid: string, forced = false) {
type ListenerType = NodeIKernelGroupListener['onMemberInfoChange'];
type EventType = NodeIKernelGroupService['getMemberInfo'];
// NTEventDispatch.CreatListenerFunction('NodeIKernelGroupListener/onGroupMemberInfoUpdate',
//return napCatCore.session.getGroupService().getMemberInfo(GroupCode, [uid], forced);
const [, , , _members] = await this.core.eventWrapper.CallNormalEvent<EventType, ListenerType>
const Listener = this.core.eventWrapper.RegisterListen<(params: any) => void>
(
'NodeIKernelGroupService/getMemberInfo',
'NodeIKernelGroupListener/onMemberInfoChange',
1,
5000,
(groupCode: string, changeType: number, members: Map<string, GroupMember>) => {
return groupCode == GroupCode && members.has(uid);
forced ? 5000 : 250,
(params) => {
return params === GroupCode;
},
GroupCode, [uid], forced,
);
return _members.get(uid);
const EventFunc = this.core.eventWrapper.createEventFunction<EventType>('NodeIKernelGroupService/getMemberInfo');
const retData = await EventFunc!(GroupCode, [uid], forced);
if (retData.result !== 0) {
throw new Error(`${retData.errMsg}`);
}
const result = await Listener as unknown;
let member: GroupMember | undefined;
if (Array.isArray(result) && result?.[2] instanceof Map) {
const members = result[2] as Map<string, GroupMember>;
member = members.get(uid);
}
return member;
}
async getGroupMembers(groupQQ: string, num = 3000): Promise<Map<string, GroupMember>> {
@@ -276,10 +299,10 @@ export class NTQQGroupApi {
const sceneId = groupService.createMemberListScene(groupQQ, 'groupMemberList_MainWindow');
const result = await groupService.getNextMemberList(sceneId!, undefined, num);
if (result.errCode !== 0) {
throw ('获取群成员列表出错,' + result.errMsg);
throw new Error('获取群成员列表出错,' + result.errMsg);
}
//logDebug(`获取群(${groupQQ})成员列表结果:`, `finish: ${result.result.finish}`); //, Array.from(result.result.infos.values()));
this.context.logger.logDebug(`获取群(${groupQQ})成员列表结果:`, `members: ${result.result.infos.size}`); //, Array.from(result.result.infos.values()));
return result.result.infos;
/*
console.log(sceneId);
@@ -308,7 +331,6 @@ export class NTQQGroupApi {
arkJson: string
}>>(
'NodeIKernelGroupService/getGroupRecommendContactArkJson',
5000,
GroupCode,
);
return ret.arkJson;
@@ -334,7 +356,7 @@ export class NTQQGroupApi {
'seq': seq, // 通知序列号
'type': type,
'groupCode': groupCode,
'postscript': reason || ' ', // 仅传空值可能导致处理失败,故默认给个空格
'postscript': reason ?? ' ', // 仅传空值可能导致处理失败,故默认给个空格
},
});
}

View File

@@ -1,6 +1,6 @@
import { ChatType, GetFileListParam, Peer, RawMessage, SendMessageElement, SendStatusType } from '@/core/entities';
import { InstanceContext, NapCatCore } from '@/core';
import { onGroupFileInfoUpdateParamType } from '@/core/listeners';
import { GroupFileInfoUpdateParamType } from '@/core/listeners';
import { GeneralCallResult } from '@/core/services/common';
export class NTQQMsgApi {
@@ -16,6 +16,9 @@ export class NTQQMsgApi {
return this.context.session.getMsgService().fetchLongMsg(peer, msgId);
}
async sendShowInputStatusReq(peer: Peer, eventType: number) {
return this.context.session.getMsgService().sendShowInputStatusReq(peer.chatType, eventType, peer.peerUid);
}
async getMsgEmojiLikesList(peer: Peer, msgSeq: string, emojiId: string, emojiType: string, count: number = 20) {
//console.log(peer, msgSeq, emojiId, emojiType, count);
//注意此处emojiType 可选值一般为1-2 2好像是unicode表情dec值 大部分情况 Taged M likiowa
@@ -116,13 +119,13 @@ export class NTQQMsgApi {
async getGroupFileList(GroupCode: string, params: GetFileListParam) {
const data = await this.core.eventWrapper.CallNormalEvent<
(GroupCode: string, params: GetFileListParam) => Promise<unknown>,
(groupFileListResult: onGroupFileInfoUpdateParamType) => void
(groupFileListResult: GroupFileInfoUpdateParamType) => void
>(
'NodeIKernelRichMediaService/getGroupFileList',
'NodeIKernelMsgListener/onGroupFileInfoUpdate',
1,
5000,
(groupFileListResult: onGroupFileInfoUpdateParamType) => {
(groupFileListResult: GroupFileInfoUpdateParamType) => {
//Developer Mlikiowa Todo: 此处有问题 无法判断是否成功
return true;
},
@@ -153,7 +156,7 @@ export class NTQQMsgApi {
peerOpenId: "",
};
return this.context.session.getMsgService().prepareTempChat({
chatType: ChatType.temp,
chatType: ChatType.KCHATTYPETEMPC2CFROMGROUP,
peerUid: toUserUid,
peerNickname: nickname,
fromGroupCode: GroupCode,
@@ -168,7 +171,7 @@ export class NTQQMsgApi {
}
async sendMsg(peer: Peer, msgElements: SendMessageElement[], waitComplete = true, timeout = 10000) {
//唉? !我有个想法
if (peer.chatType === ChatType.temp && peer.guildId && peer.guildId !== '') {
if (peer.chatType === ChatType.KCHATTYPETEMPC2CFROMGROUP && peer.guildId && peer.guildId !== '') {
const member = await this.core.apis.GroupApi.getGroupMember(peer.guildId, peer.peerUid!);
if (member) {
await this.PrepareTempChat(peer.peerUid, peer.guildId, member.nick);
@@ -248,7 +251,7 @@ export class NTQQMsgApi {
if (!arkElement) {
continue;
}
const forwardData: any = JSON.parse(arkElement.arkElement?.bytesData || "");
const forwardData: any = JSON.parse(arkElement.arkElement?.bytesData ?? "");
if (forwardData.app != 'com.tencent.multimsg') {
continue;
}

View File

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

View File

@@ -32,7 +32,6 @@ export class NTQQSystemApi {
arkJson: string
}>>(
'NodeIKernelCollectionService/collectionArkShare',
5000,
'1717662698058',
);
return ret;

View File

@@ -3,6 +3,7 @@ import { NodeIKernelProfileListener } from '@/core/listeners';
import { RequestUtil } from '@/common/utils/request';
import { NodeIKernelProfileService, ProfileBizType, UserDetailSource } from '@/core/services';
import { InstanceContext, NapCatCore } from '..';
import { solveAsyncProblem } from '@/common/utils/helper';
export class NTQQUserApi {
context: InstanceContext;
@@ -100,7 +101,7 @@ export class NTQQUserApi {
return retData;
}
async fetchUserDetailInfo(uid: string) {
async fetchUserDetailInfo(uid: string, mode: UserDetailSource = UserDetailSource.KDB) {
type EventService = NodeIKernelProfileService['fetchUserDetailInfo'];
type EventListener = NodeIKernelProfileListener['onUserDetailInfoChanged'];
const [_retData, profile] = await this.core.eventWrapper.CallNormalEvent<EventService, EventListener>(
@@ -111,7 +112,7 @@ export class NTQQUserApi {
(profile) => profile.uid === uid,
'BuddyProfileStore',
[uid],
UserDetailSource.KSERVER,
mode,
[ProfileBizType.KALL],
);
const RetUser: User = {
@@ -120,14 +121,20 @@ export class NTQQUserApi {
...profile.simpleInfo.vasInfo,
...profile.commonExt,
...profile.simpleInfo.baseInfo,
qqLevel: profile.commonExt.qqLevel,
qqLevel: profile.commonExt?.qqLevel,
age: profile.simpleInfo.baseInfo.age,
pendantId: '',
};
return RetUser;
}
async getUserDetailInfo(uid: string) {
return this.fetchUserDetailInfo(uid);
async getUserDetailInfo(uid: string): Promise<User> {
const retUser = await solveAsyncProblem(async (uid) => this.fetchUserDetailInfo(uid, UserDetailSource.KDB), uid);
if (retUser && retUser.uin !== '0') {
return retUser;
}
this.context.logger.logDebug('[NapCat] [Mark] getUserDetailInfo Mode1 Failed.');
return this.fetchUserDetailInfo(uid, UserDetailSource.KSERVER);
}
async modifySelfProfile(param: ModifyProfileParams) {
@@ -187,9 +194,9 @@ export class NTQQUserApi {
//后期改成流水线处理
async getUidByUinV2(Uin: string) {
let uid = (await this.context.session.getProfileService().getUidByUin('FriendsServiceImpl', [Uin])).get(Uin);
let uid = (await this.context.session.getGroupService().getUidByUins([Uin])).uids.get(Uin);
if (uid) return uid;
uid = (await this.context.session.getGroupService().getUidByUins([Uin])).uids.get(Uin);
uid = (await this.context.session.getProfileService().getUidByUin('FriendsServiceImpl', [Uin])).get(Uin);
if (uid) return uid;
uid = (await this.context.session.getUixConvertService().getUid([Uin])).uidInfo.get(Uin);
if (uid) return uid;
@@ -201,9 +208,9 @@ export class NTQQUserApi {
//后期改成流水线处理
async getUinByUidV2(Uid: string) {
let uin = (await this.context.session.getProfileService().getUinByUid('FriendsServiceImpl', [Uid])).get(Uid);
let uin = (await this.context.session.getGroupService().getUinByUids([Uid])).uins.get(Uid);
if (uin) return uin;
uin = (await this.context.session.getGroupService().getUinByUids([Uid])).uins.get(Uid);
uin = (await this.context.session.getProfileService().getUinByUid('FriendsServiceImpl', [Uid])).get(Uid);
if (uin) return uin;
uin = (await this.context.session.getUixConvertService().getUin([Uid])).uinInfo.get(Uid);
if (uin) return uin;
@@ -231,12 +238,7 @@ export class NTQQUserApi {
async getUserDetailInfoByUinV2(Uin: string) {
return await this.core.eventWrapper.callNoListenerEvent<(Uin: string) => Promise<UserDetailInfoByUinV2>>
('NodeIKernelProfileService/getUserDetailInfoByUin', 5000, Uin);
}
async getUserDetailInfoByUin(Uin: string) {
return this.core.eventWrapper.callNoListenerEvent<(Uin: string) => Promise<UserDetailInfoByUin>>
('NodeIKernelProfileService/getUserDetailInfoByUin', 5000, Uin);
('NodeIKernelProfileService/getUserDetailInfoByUin', Uin);
}
async forceFetchClientKey() {

View File

@@ -20,8 +20,7 @@ export class NTQQWebApi {
async shareDigest(groupCode: string, msgSeq: string, msgRandom: string, targetGroupCode: string) {
const cookieObject = await this.core.apis.UserApi.getCookies('qun.qq.com');
const url = `https://qun.qq.com/cgi-bin/group_digest/share_digest?${
new URLSearchParams({
const url = `https://qun.qq.com/cgi-bin/group_digest/share_digest?${new URLSearchParams({
bkn: this.getBknFromCookie(cookieObject),
group_code: groupCode,
msg_seq: msgSeq,
@@ -38,8 +37,7 @@ export class NTQQWebApi {
async getGroupEssenceMsg(GroupCode: string, page_start: string) {
const cookieObject = await this.core.apis.UserApi.getCookies('qun.qq.com');
const url = `https://qun.qq.com/cgi-bin/group_digest/digest_list?${
new URLSearchParams({
const url = `https://qun.qq.com/cgi-bin/group_digest/digest_list?${new URLSearchParams({
bkn: this.getBknFromCookie(cookieObject),
group_code: GroupCode,
page_start,
@@ -65,8 +63,7 @@ export class NTQQWebApi {
const cookieObject = await this.core.apis.UserApi.getCookies('qun.qq.com');
const retList: Promise<WebApiGroupMemberRet>[] = [];
const fastRet = await RequestUtil.HttpGetJson<WebApiGroupMemberRet>
(`https://qun.qq.com/cgi-bin/qun_mgr/search_group_members?${
new URLSearchParams({
(`https://qun.qq.com/cgi-bin/qun_mgr/search_group_members?${new URLSearchParams({
st: '0',
end: '40',
sort: '1',
@@ -86,8 +83,7 @@ export class NTQQWebApi {
//遍历批量请求
for (let i = 2; i <= PageNum; i++) {
const ret = RequestUtil.HttpGetJson<WebApiGroupMemberRet>
(`https://qun.qq.com/cgi-bin/qun_mgr/search_group_members?${
new URLSearchParams({
(`https://qun.qq.com/cgi-bin/qun_mgr/search_group_members?${new URLSearchParams({
st: ((i - 1) * 40).toString(),
end: (i * 40).toString(),
sort: '1',
@@ -127,8 +123,7 @@ export class NTQQWebApi {
let ret: any = undefined;
try {
ret = await RequestUtil.HttpGetJson<any>
(`https://web.qun.qq.com/cgi-bin/announce/add_qun_notice${
new URLSearchParams({
(`https://web.qun.qq.com/cgi-bin/announce/add_qun_notice${new URLSearchParams({
bkn: this.getBknFromCookie(cookieObject),
qid: GroupCode,
text: Content,
@@ -147,15 +142,10 @@ export class NTQQWebApi {
const cookieObject = await this.core.apis.UserApi.getCookies('qun.qq.com');
let ret: WebApiGroupNoticeRet | undefined = undefined;
try {
ret = await RequestUtil.HttpGetJson<WebApiGroupNoticeRet>(`https://web.qun.qq.com/cgi-bin/announce/get_t_list?${
new URLSearchParams({
bkn: this.getBknFromCookie(cookieObject),
qid: GroupCode,
type: '1',
start: '0',
num: '1',
}).toString()
}`, 'GET', '', { 'Cookie': this.cookieToString(cookieObject) });
const url = 'https://web.qun.qq.com/cgi-bin/announce/get_t_list?bkn=' +
this.getBknFromCookie(cookieObject) + '&qid=' + GroupCode + '&ft=23&ni=1&n=1&i=1&log_read=1&platform=1&s=-1&n=20';
ret = await RequestUtil.HttpGetJson<WebApiGroupNoticeRet>(url, 'GET', '', { 'Cookie': this.cookieToString(cookieObject) });
if (ret?.ec !== 0) {
return undefined;
}
@@ -168,8 +158,7 @@ export class NTQQWebApi {
async getGroupHonorInfo(groupCode: string, getType: WebHonorType) {
const cookieObject = await this.core.apis.UserApi.getCookies('qun.qq.com');
const getDataInternal = async (Internal_groupCode: string, Internal_type: number) => {
const url = `https://qun.qq.com/interactive/honorlist?${
new URLSearchParams({
const url = `https://qun.qq.com/interactive/honorlist?${new URLSearchParams({
gc: Internal_groupCode,
type: Internal_type.toString(),
}).toString()
@@ -177,7 +166,7 @@ export class NTQQWebApi {
let resJson;
try {
const res = await RequestUtil.HttpGetText(url, 'GET', '', { 'Cookie': this.cookieToString(cookieObject) });
const match = res.match(/window\.__INITIAL_STATE__=(.*?);/);
const match = /window\.__INITIAL_STATE__=(.*?);/.exec(res);
if (match) {
resJson = JSON.parse(match[1].trim());
}

View File

@@ -3,8 +3,8 @@ import path from 'node:path';
import fs from 'node:fs';
import { InstanceContext } from './wrapper';
import { proxiedListenerOf } from '@/common/utils/proxy-handler';
import { GroupListener, MsgListener, ProfileListener } from './listeners';
import { GroupMember, SelfInfo } from './entities';
import { NodeIKernelMsgListener, NodeIKernelGroupListener, NodeIKernelProfileListener } from './listeners';
import { DataSource, GroupMember, SelfInfo } from './entities';
import { LegacyNTEventWrapper } from '@/common/framework/event-legacy';
import { NTQQFileApi, NTQQFriendApi, NTQQGroupApi, NTQQMsgApi, NTQQSystemApi, NTQQUserApi, NTQQWebApi } from './apis';
import os from 'node:os';
@@ -44,8 +44,8 @@ export class NapCatCore {
constructor(context: InstanceContext, selfInfo: SelfInfo) {
this.selfInfo = selfInfo;
this.context = context;
this.util = new this.context.wrapper.NodeQQNTWrapperUtil();
this.eventWrapper = new LegacyNTEventWrapper(context.wrapper, context.session);
this.util = this.context.wrapper.NodeQQNTWrapperUtil;
this.eventWrapper = new LegacyNTEventWrapper(context.session);
this.apis = {
FileApi: new NTQQFileApi(this.context, this),
SystemApi: new NTQQSystemApi(this.context, this),
@@ -79,7 +79,7 @@ export class NapCatCore {
}
get dataPath(): string {
let result = this.util.getNTUserDataInfoConfig();
let result = this.context.wrapper.NodeQQNTWrapperUtil.getNTUserDataInfoConfig();
if (!result) {
result = path.resolve(os.homedir(), './.config/QQ');
fs.mkdirSync(result, { recursive: true });
@@ -89,7 +89,7 @@ export class NapCatCore {
// Renamed from 'InitDataListener'
async initNapCatCoreListeners() {
const msgListener = new MsgListener();
const msgListener = new NodeIKernelMsgListener();
msgListener.onRecvMsg = (msgs) => {
msgs.forEach(msg => this.context.logger.logMessage(msg, this.selfInfo));
};
@@ -98,10 +98,10 @@ export class NapCatCore {
};
//await sleep(2500);
this.context.session.getMsgService().addKernelMsgListener(
new this.context.wrapper.NodeIKernelMsgListener(proxiedListenerOf(msgListener, this.context.logger)),
proxiedListenerOf(msgListener, this.context.logger) as any
);
const profileListener = new ProfileListener();
const profileListener = new NodeIKernelProfileListener();
profileListener.onProfileDetailInfoChanged = (profile) => {
if (profile.uid === this.selfInfo.uid) {
Object.assign(this.selfInfo, profile);
@@ -113,11 +113,11 @@ export class NapCatCore {
// }
};
this.context.session.getProfileService().addKernelProfileListener(
new this.context.wrapper.NodeIKernelProfileListener(proxiedListenerOf(profileListener, this.context.logger)),
proxiedListenerOf(profileListener, this.context.logger),
);
// 群相关
const groupListener = new GroupListener();
const groupListener = new NodeIKernelGroupListener();
groupListener.onGroupListUpdate = (updateType, groupList) => {
// console.log("onGroupListUpdate", updateType, groupList)
groupList.map(g => {
@@ -164,9 +164,9 @@ export class NapCatCore {
}
// console.log('onMemberListChange', groupCode, arg);
};
groupListener.onMemberInfoChange = (groupCode, changeType, members) => {
groupListener.onMemberInfoChange = (groupCode, dataSource, members) => {
//console.log('onMemberInfoChange', groupCode, changeType, members);
if (changeType === 0 && members.get(this.selfInfo.uid)?.isDelete) {
if (dataSource === DataSource.LOCAL && members.get(this.selfInfo.uid)?.isDelete) {
// 自身退群或者被踢退群 5s用于Api操作 之后不再出现
setTimeout(() => {
this.apis.GroupApi.groupCache.delete(groupCode);
@@ -196,10 +196,13 @@ export class NapCatCore {
this.apis.GroupApi.groupMemberCache.set(groupCode, members);
}
};
this.context.session.getGroupService().addKernelGroupListener(
proxiedListenerOf(groupListener, this.context.logger) as any
);
}
checkAdminEvent(groupCode: string, memberNew: GroupMember, memberOld: GroupMember | undefined): boolean {
if (memberNew.role !== memberOld?.role) {
this.context.logger.log(`${groupCode} ${memberNew.nick} 角色变更为 ${memberNew.role === 3 ? '管理员' : '群员'}`);
this.context.logger.logDebug(`${groupCode} ${memberNew.nick} 角色变更为 ${memberNew.role === 3 ? '管理员' : '群员'}`);
return true;
}
return false;

View File

@@ -1,5 +1,21 @@
import { QQLevel, Sex } from './user';
import { QQLevel, Sex, User } from './user';
export interface KickMemberInfo {
optFlag: number,
optOperate: number,
optMemberUid: string,
optBytesMsg: string,
}
export interface KickMemberV2Req{
groupCode: string,
kickFlag: number,
kickList: Array<KickMemberInfo>,
kickListUids: Array<string>,
kickMsg: string
}
export enum DataSource {
LOCAL,
REMOTE
}
export enum GroupListUpdateType {
REFRESHALL,
GETALL,
@@ -65,6 +81,7 @@ export interface GroupMember {
uin: string; // QQ号
isRobot: boolean;
sex?: Sex;
age?: number;
qqLevel?: QQLevel;
isChangeRole: boolean;
joinTime: string;

View File

@@ -362,6 +362,7 @@ export interface SendPicElement {
}
export interface ReplyElement {
sourceMsgIdInRecords?: string;
replayMsgSeq: string;
replayMsgId: string;
senderUin: string;
@@ -410,7 +411,7 @@ export interface ShareLocationElement {
ext: string;
}
export interface sendShareLocationElement {
export interface SendShareLocationElement {
elementType: ElementType.SHARELOCATION;
elementId: string;
shareLocationElement?: ShareLocationElement;
@@ -460,7 +461,7 @@ export interface SendMarkdownElement {
export type SendMessageElement = SendTextElement | SendPttElement |
SendPicElement | SendReplyElement | SendFaceElement | SendMarketFaceElement | SendFileElement |
SendVideoElement | SendArkElement | SendMarkdownElement | sendShareLocationElement;
SendVideoElement | SendArkElement | SendMarkdownElement | SendShareLocationElement;
export interface TextElement {
content: string;
@@ -476,7 +477,7 @@ export interface MessageElement {
extBufForUI: string,//"0x",
textElement?: TextElement;
faceElement?: FaceElement,
marketFaceElement?: MarkdownElement,
marketFaceElement?: MarketFaceElement,
replyElement?: ReplyElement,
picElement?: PicElement,
pttElement?: PttElement,
@@ -510,17 +511,8 @@ export enum AtType {
atAll = 1,
atUser = 2
}
export enum ChatType {
friend = 1,
group = 2,
chatDevice = 8, //移动设备?
temp = 100
}
// 来自Android分析
export enum ChatType2 {
export enum ChatType {
KCHATTYPEADELIE = 42,
KCHATTYPEBUDDYNOTIFY = 5,
KCHATTYPEC2C = 1,
@@ -856,41 +848,6 @@ export interface MultiForwardMsgElement {
resId: string;
fileName: string;
}
export enum NTSubMsgType {
KMSGSUBTYPEARKGROUPANNOUNCE = 3,
KMSGSUBTYPEARKGROUPANNOUNCECONFIRMREQUIRED = 4,
KMSGSUBTYPEARKGROUPGIFTATME = 5,
KMSGSUBTYPEARKGROUPTASKATALL = 6,
KMSGSUBTYPEARKMULTIMSG = 7,
KMSGSUBTYPEARKNORMAL = 0,
KMSGSUBTYPEARKTENCENTDOCFROMMINIAPP = 1,
KMSGSUBTYPEARKTENCENTDOCFROMPLUSPANEL = 2,
KMSGSUBTYPEEMOTICON = 15,
KMSGSUBTYPEFILEAPP = 11,
KMSGSUBTYPEFILEAUDIO = 3,
KMSGSUBTYPEFILEDOC = 4,
KMSGSUBTYPEFILEEXCEL = 6,
KMSGSUBTYPEFILEFOLDER = 13,
KMSGSUBTYPEFILEHTML = 10,
KMSGSUBTYPEFILEIPA = 14,
KMSGSUBTYPEFILENORMAL = 0,
KMSGSUBTYPEFILEPDF = 7,
KMSGSUBTYPEFILEPIC = 1,
KMSGSUBTYPEFILEPPT = 5,
KMSGSUBTYPEFILEPSD = 12,
KMSGSUBTYPEFILETXT = 8,
KMSGSUBTYPEFILEVIDEO = 2,
KMSGSUBTYPEFILEZIP = 9,
KMSGSUBTYPELINK = 5,
KMSGSUBTYPEMARKETFACE = 1,
KMSGSUBTYPEMIXEMOTICON = 7,
KMSGSUBTYPEMIXFACE = 3,
KMSGSUBTYPEMIXMARKETFACE = 2,
KMSGSUBTYPEMIXPIC = 1,
KMSGSUBTYPEMIXREPLY = 4,
KMSGSUBTYPEMIXTEXT = 0,
KMSGSUBTYPETENCENTDOC = 6
}
export enum SendStatusType {
KSEND_STATUS_FAILED = 0,
KSEND_STATUS_SENDING = 1,
@@ -922,7 +879,7 @@ export interface RawMessage {
msgType: NTMsgType;
subMsgType: NTSubMsgType;
subMsgType: number;
senderUid: string;
@@ -965,76 +922,5 @@ export interface RawMessage {
records: RawMessage[];
elements: ElementWrapper[];
}
/**
* 并非原生接口类型,故以 type 包装
*/
export type ElementWrapper = {
elementId: string;
elementType: ElementType;
replyElement?: {
sourceMsgIdInRecords: string;
/**
* 源消息发送者 QQ 号
*/
senderUid: string;
/**
* 源消息是否有图片
*/
sourceMsgIsIncPic: boolean;
/**
* 源消息文本
*/
sourceMsgText: string;
/**
* 源消息的 msgSeq可以通过这个找到源消息的 msgId
*/
replayMsgSeq: string;
};
textElement?: {
atType: AtType;
/**
* 被 @ 的 QQ 号
*/
atUid: string;
content: string;
/**
* 被 @ 的 UID从这里可以看出来 UID 的概念是 NT 才引入的)
*/
atNtUid: string;
};
picElement?: PicElement;
pttElement?: PttElement;
arkElement?: ArkElement;
grayTipElement?: GrayTipElement;
faceElement?: FaceElement;
videoElement?: VideoElement;
fileElement?: FileElement;
marketFaceElement?: MarketFaceElement;
inlineKeyboardElement?: InlineKeyboardElement;
markdownElement?: MarkdownElement;
multiForwardMsgElement?: MultiForwardMsgElement;
elements: MessageElement[];
}

View File

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

View File

@@ -231,6 +231,7 @@ export interface User {
longNick?: string; // 签名
remark?: string;
sex?: Sex;
age?: number;
qqLevel?: QQLevel;
qid?: string;
birthday_year?: number;

View File

@@ -1,58 +1,30 @@
{
"3.1.2-13107": {
"appid": 537146866,
"qua": "V1_LNX_NQ_3.1.2-13107_RDM_B"
"3.2.12-27187": {
"appid": 537240645,
"qua": "V1_LNX_NQ_3.2.12_27187_GW_B"
},
"3.2.10-25765": {
"appid": 537234773,
"qua": "V1_LNX_NQ_3.2.10_25765_GW_B"
"3.2.12-27206": {
"appid": 537240645,
"qua": "V1_LNX_NQ_3.2.12_27206_GW_B"
},
"3.2.12-26702": {
"appid": 537237950,
"qua": "V1_LNX_NQ_3.2.12_26702_GW_B"
"3.2.12-27254":{
"appid": 537240795,
"qua": "V1_LNX_NQ_3.2.12_27254_GW_B"
},
"3.2.12-26740": {
"appid": 537237950,
"qua": "V1_WIN_NQ_9.9.15_26740_GW_B"
"9.9.15-27187":{
"appid": 537240610,
"qua": "V1_WIN_NQ_9.9.15_27187_GW_B"
},
"3.2.12-26909": {
"appid": 537237923,
"qua": "V1_LNX_NQ_3.2.12_26909_GW_B"
"9.9.15-27206":{
"appid": 537240610,
"qua": "V1_WIN_NQ_9.9.15_27206_GW_B"
},
"9.9.11-24815": {
"appid": 537226656,
"qua": "V1_WIN_NQ_9.9.11_24815_GW_B"
"9.9.15-27254":{
"appid": 537240709,
"qua": "V1_WIN_NQ_9.9.15_27254_GW_B"
},
"9.9.12-25493": {
"appid": 537231759,
"qua": "V1_WIN_NQ_9.9.12_25493_GW_B"
},
"9.9.12-25765": {
"appid": 537234702,
"qua": "V1_WIN_NQ_9.9.12_25765_GW_B"
},
"9.9.12-26299": {
"appid": 537234826,
"qua": "V1_WIN_NQ_9.9.12_26299_GW_B"
},
"9.9.12-26339": {
"appid": 537234826,
"qua": "V1_WIN_NQ_9.9.12_26339_GW_B"
},
"9.9.12-26466": {
"appid": 537234826,
"qua": "V1_WIN_NQ_9.9.12_26466_GW_B"
},
"9.9.15-26702": {
"appid": 537237765,
"qua": "V1_WIN_NQ_9.9.15_26702_GW_B"
},
"9.9.15-26740": {
"appid": 537237765,
"qua": "V1_WIN_NQ_9.9.15_26702_GW_B"
},
"9.9.15-26909": {
"appid": 537237802,
"qua": "V1_WIN_NQ_9.9.15_26909_GW_B"
"9.9.15-27333": {
"appid": 537240709,
"qua": "V1_WIN_NQ_9.9.15_27333_GW_B"
}
}

View File

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

View File

@@ -1,21 +1,5 @@
export interface IKernelFileAssistantListener {
onFileStatusChanged(...args: unknown[]): unknown;
onSessionListChanged(...args: unknown[]): unknown;
onSessionChanged(...args: unknown[]): unknown;
onFileListChanged(...args: unknown[]): unknown;
onFileSearch(...args: unknown[]): unknown;
}
export interface NodeIKernelFileAssistantListener extends IKernelFileAssistantListener {
// eslint-disable-next-line @typescript-eslint/no-misused-new
new(adapter: IKernelFileAssistantListener): NodeIKernelFileAssistantListener;
}
export class KernelFileAssistantListener implements IKernelFileAssistantListener {
export class NodeIKernelFileAssistantListener {
onFileStatusChanged(...args: unknown[]) {
}

View File

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

View File

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

View File

@@ -23,7 +23,7 @@ export interface OnRichMediaDownloadCompleteParams {
userUsedSpacePerDay: unknown | null
}
export interface onGroupFileInfoUpdateParamType {
export interface GroupFileInfoUpdateParamType {
retCode: number;
retMsg: string;
clientWording: string;
@@ -52,199 +52,7 @@ export interface TempOnRecvParams {
}
export interface IKernelMsgListener {
onAddSendMsg(msgRecord: RawMessage): void;
onBroadcastHelperDownloadComplete(broadcastHelperTransNotifyInfo: unknown): void;
onBroadcastHelperProgressUpdate(broadcastHelperTransNotifyInfo: unknown): void;
onChannelFreqLimitInfoUpdate(contact: unknown, z: unknown, freqLimitInfo: unknown): void;
onContactUnreadCntUpdate(hashMap: unknown): void;
onCustomWithdrawConfigUpdate(customWithdrawConfig: unknown): void;
onDraftUpdate(contact: unknown, arrayList: unknown, j2: unknown): void;
onEmojiDownloadComplete(emojiNotifyInfo: unknown): void;
onEmojiResourceUpdate(emojiResourceInfo: unknown): void;
onFeedEventUpdate(firstViewDirectMsgNotifyInfo: unknown): void;
onFileMsgCome(arrayList: unknown): void;
onFirstViewDirectMsgUpdate(firstViewDirectMsgNotifyInfo: unknown): void;
onFirstViewGroupGuildMapping(arrayList: unknown): void;
onGrabPasswordRedBag(i2: unknown, str: unknown, i3: unknown, recvdOrder: unknown, msgRecord: unknown): void;
onGroupFileInfoAdd(groupItem: unknown): void;
onGroupFileInfoUpdate(groupFileListResult: onGroupFileInfoUpdateParamType): void;
onGroupGuildUpdate(groupGuildNotifyInfo: unknown): void;
onGroupTransferInfoAdd(groupItem: unknown): void;
onGroupTransferInfoUpdate(groupFileListResult: unknown): void;
onGuildInteractiveUpdate(guildInteractiveNotificationItem: unknown): void;
onGuildMsgAbFlagChanged(guildMsgAbFlag: unknown): void;
onGuildNotificationAbstractUpdate(guildNotificationAbstractInfo: unknown): void;
onHitCsRelatedEmojiResult(downloadRelateEmojiResultInfo: unknown): void;
onHitEmojiKeywordResult(hitRelatedEmojiWordsResult: unknown): void;
onHitRelatedEmojiResult(relatedWordEmojiInfo: unknown): void;
onImportOldDbProgressUpdate(importOldDbMsgNotifyInfo: unknown): void;
onInputStatusPush(inputStatusInfo: {
chatType: number;
eventType: number;
fromUin: string;
interval: string;
showTime: string;
statusText: string;
timestamp: string;
toUin: string;
}): void;
onKickedOffLine(kickedInfo: unknown): void;
onLineDev(arrayList: unknown): void;
onLogLevelChanged(j2: unknown): void;
onMsgAbstractUpdate(arrayList: unknown): void;
onMsgBoxChanged(arrayList: unknown): void;
onMsgDelete(contact: unknown, arrayList: unknown): void;
onMsgEventListUpdate(hashMap: unknown): void;
onMsgInfoListAdd(arrayList: unknown): void;
onMsgInfoListUpdate(msgList: RawMessage[]): void;
onMsgQRCodeStatusChanged(i2: unknown): void;
onMsgRecall(i2: unknown, str: unknown, j2: unknown): void;
onMsgSecurityNotify(msgRecord: unknown): void;
onMsgSettingUpdate(msgSetting: unknown): void;
onNtFirstViewMsgSyncEnd(): void;
onNtMsgSyncEnd(): void;
onNtMsgSyncStart(): void;
onReadFeedEventUpdate(firstViewDirectMsgNotifyInfo: unknown): void;
onRecvGroupGuildFlag(i2: unknown): void;
onRecvMsg(...arrayList: unknown[]): void;
onRecvMsgSvrRspTransInfo(j2: unknown, contact: unknown, i2: unknown, i3: unknown, str: unknown, bArr: unknown): void;
onRecvOnlineFileMsg(arrayList: unknown): void;
onRecvS2CMsg(arrayList: unknown): void;
onRecvSysMsg(arrayList: unknown): void;
onRecvUDCFlag(i2: unknown): void;
onRichMediaDownloadComplete(fileTransNotifyInfo: OnRichMediaDownloadCompleteParams): void;
onRichMediaProgerssUpdate(fileTransNotifyInfo: unknown): void;
onRichMediaUploadComplete(fileTransNotifyInfo: unknown): void;
onSearchGroupFileInfoUpdate(searchGroupFileResult:
{
result: {
retCode: number,
retMsg: string,
clientWording: string
},
syncCookie: string,
totalMatchCount: number,
ownerMatchCount: number,
isEnd: boolean,
reqId: number,
item: Array<{
groupCode: string,
groupName: string,
uploaderUin: string,
uploaderName: string,
matchUin: string,
matchWords: Array<unknown>,
fileNameHits: Array<{
start: number,
end: number
}>,
fileModelId: string,
fileId: string,
fileName: string,
fileSize: string,
busId: number,
uploadTime: number,
modifyTime: number,
deadTime: number,
downloadTimes: number,
localPath: string
}>
}): void;
onSendMsgError(j2: unknown, contact: unknown, i2: unknown, str: unknown): void;
onSysMsgNotification(i2: unknown, j2: unknown, j3: unknown, arrayList: unknown): void;
onTempChatInfoUpdate(tempChatInfo: TempOnRecvParams): void;
onUnreadCntAfterFirstView(hashMap: unknown): void;
onUnreadCntUpdate(hashMap: unknown): void;
onUserChannelTabStatusChanged(z: unknown): void;
onUserOnlineStatusChanged(z: unknown): void;
onUserTabStatusChanged(arrayList: unknown): void;
onlineStatusBigIconDownloadPush(i2: unknown, j2: unknown, str: unknown): void;
onlineStatusSmallIconDownloadPush(i2: unknown, j2: unknown, str: unknown): void;
// 第一次发现于Linux
onUserSecQualityChanged(...args: unknown[]): void;
onMsgWithRichLinkInfoUpdate(...args: unknown[]): void;
onRedTouchChanged(...args: unknown[]): void;
// 第一次发现于Win 9.9.9 23159
onBroadcastHelperProgerssUpdate(...args: unknown[]): void;
}
export interface NodeIKernelMsgListener extends IKernelMsgListener {
// eslint-disable-next-line @typescript-eslint/no-misused-new
new(listener: IKernelMsgListener): NodeIKernelMsgListener;
}
export class MsgListener implements IKernelMsgListener {
export class NodeIKernelMsgListener {
onAddSendMsg(msgRecord: RawMessage) {
}
@@ -305,7 +113,7 @@ export class MsgListener implements IKernelMsgListener {
}
onGroupFileInfoUpdate(groupFileListResult: onGroupFileInfoUpdateParamType) {
onGroupFileInfoUpdate(groupFileListResult: GroupFileInfoUpdateParamType) {
}

View File

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

View File

@@ -1,23 +1,4 @@
interface IKernelRecentContactListener {
onDeletedContactsNotify(...args: unknown[]): unknown;
onRecentContactNotification(...args: unknown[]): unknown;
onMsgUnreadCountUpdate(...args: unknown[]): unknown;
onGuildDisplayRecentContactListChanged(...args: unknown[]): unknown;
onRecentContactListChanged(...args: unknown[]): unknown;
onRecentContactListChangedVer2(...args: unknown[]): unknown;
}
export interface NodeIKernelRecentContactListener extends IKernelRecentContactListener {
// eslint-disable-next-line @typescript-eslint/no-misused-new
new(listener: IKernelRecentContactListener): NodeIKernelRecentContactListener;
}
export class KernelRecentContactListener implements IKernelRecentContactListener {
export class NodeIKernelRecentContactListener {
onDeletedContactsNotify(...args: unknown[]) {
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -61,11 +61,11 @@ export interface NodeIKernelBuddyService {
getBuddyReqUnreadCnt(): number;
getBuddyReq(): unknown;
getBuddyReq(): Promise<GeneralCallResult>;
delBuddyReq(uid: number): void;
clearBuddyReqUnreadCnt(): void;
clearBuddyReqUnreadCnt(): Promise<GeneralCallResult>;
reqToAddFriends(uid: number, msg: string): void;

View File

@@ -1,16 +1,20 @@
import { NodeIKernelGroupListener } from '@/core/listeners/NodeIKernelGroupListener';
import { IGroupListener } from '@/core/listeners/NodeIKernelGroupListener';
import {
GroupExtParam,
GroupMember,
GroupMemberRole,
GroupNotifyTypes,
GroupNotifyMsgType,
GroupRequestOperateTypes,
KickMemberV2Req
} from '@/core/entities';
import { GeneralCallResult } from '@/core/services/common';
//高版本的接口不应该随意使用 使用应该严格进行pr审核 同时部分ipc中未出现的接口不要过于依赖 应该做好数据兜底
export interface NodeIKernelGroupService {
kickMemberV2(param: KickMemberV2Req): Promise<GeneralCallResult>;
quitGroupV2(param: { groupCode: string; needDeleteLocalMsg: boolean; }): Promise<GeneralCallResult>;
getMemberCommonInfo(Req: {
groupCode: string,
startUin: string,
@@ -96,7 +100,7 @@ export interface NodeIKernelGroupService {
uid: string,
index: number//0
}>,
infos: {},
infos: unknown,
finish: true,
hasRobot: false
}
@@ -104,7 +108,7 @@ export interface NodeIKernelGroupService {
setHeader(uid: string, path: string): unknown;
addKernelGroupListener(listener: NodeIKernelGroupListener): number;
addKernelGroupListener(listener: IGroupListener): number;
removeKernelGroupListener(listenerId: unknown): void;
@@ -195,7 +199,7 @@ export interface NodeIKernelGroupService {
operateType: GroupRequestOperateTypes, // 2 拒绝
targetMsg: {
seq: string, // 通知序列号
type: GroupNotifyTypes,
type: GroupNotifyMsgType,
groupCode: string,
postscript: string
}
@@ -205,7 +209,7 @@ export interface NodeIKernelGroupService {
getGroupBulletin(groupCode: string): unknown;
deleteGroupBulletin(groupCode: string, seq: string): void;
deleteGroupBulletin(groupCode: string, seq: string, feedId: string): void;
publishGroupBulletin(groupCode: string, pskey: string, data: any): Promise<GeneralCallResult>;

View File

@@ -9,7 +9,7 @@ export interface LoginInitConfig {
hostName: string;
}
export interface passwordLoginRetType {
export interface PasswordLoginRetType {
result: string,
loginErrorInfo: {
step: number;
@@ -23,7 +23,7 @@ export interface passwordLoginRetType {
}
}
export interface passwordLoginArgType {
export interface PasswordLoginArgType {
uin: string;
passwordMd5: string;//passwMD5
step: number;//猜测是需要二次认证 参数 一次为0
@@ -77,7 +77,7 @@ export interface NodeIKernelLoginService {
quickLoginWithUin(uin: string): Promise<QuickLoginResult>;
passwordLogin(param: passwordLoginArgType): Promise<any>;
passwordLogin(param: PasswordLoginArgType): Promise<any>;
getQRCodePicture(): boolean;
}

View File

@@ -37,7 +37,7 @@ export interface NodeIKernelMsgService {
recallMsg(peer: Peer, msgIds: string[]): Promise<GeneralCallResult>;
addKernelMsgImportToolListener(arg: Object): unknown;
addKernelMsgImportToolListener(arg: unknown): unknown;
removeKernelMsgListener(args: unknown): unknown;
@@ -51,7 +51,7 @@ export interface NodeIKernelMsgService {
getOnLineDev(): void;
kickOffLine(DevInfo: Object): unknown;
kickOffLine(DevInfo: unknown): unknown;
setStatus(args: { status: number, extStatus: number, batteryStatus: number }): Promise<GeneralCallResult>;
@@ -80,11 +80,11 @@ export interface NodeIKernelMsgService {
// this.voipToken = bArr2;
// this.profileId = str;
setToken(arg: Object): unknown;
setToken(arg: unknown): unknown;
switchForeGround(): unknown;
switchBackGround(arg: Object): unknown;
switchBackGround(arg: unknown): unknown;
//hex
setTokenForMqq(token: string): unknown;
@@ -384,7 +384,7 @@ export interface NodeIKernelMsgService {
getFileThumbSavePath(...args: unknown[]): unknown;
//猜测居多
translatePtt2Text(MsgId: string, Peer: {}, MsgElement: {}): unknown;
translatePtt2Text(MsgId: string, Peer: Peer, MsgElement: unknown): unknown;
setPttPlayedState(...args: unknown[]): unknown;
@@ -668,7 +668,7 @@ export interface NodeIKernelMsgService {
recordEmoji(...args: unknown[]): unknown;
fetchGetHitEmotionsByWord(args: Object): Promise<unknown>;//表情推荐?
fetchGetHitEmotionsByWord(args: unknown): Promise<unknown>;//表情推荐?
deleteAllRoamMsgs(...args: unknown[]): unknown;//漫游消息?

View File

@@ -1,4 +1,4 @@
import { forceFetchClientKeyRetType } from './common';
import { ForceFetchClientKeyRetType } from './common';
export interface NodeIKernelTicketService {
@@ -6,7 +6,7 @@ export interface NodeIKernelTicketService {
removeKernelTicketListener(listenerId: unknown): void;
forceFetchClientKey(arg: string): Promise<forceFetchClientKeyRetType>;
forceFetchClientKey(arg: string): Promise<ForceFetchClientKeyRetType>;
isNull(): boolean;
}

View File

@@ -8,7 +8,7 @@ export interface GeneralCallResult {
errMsg: string
}
export interface forceFetchClientKeyRetType extends GeneralCallResult {
export interface ForceFetchClientKeyRetType extends GeneralCallResult {
url: string;
keyIndex: string;
clientKey: string;

View File

@@ -1,11 +1,6 @@
import { NodeIDependsAdapter, NodeIDispatcherAdapter, NodeIGlobalAdapter } from '../adapters';
import {
NodeIKernelBuddyListener,
NodeIKernelGroupListener,
NodeIKernelLoginListener,
NodeIKernelMsgListener,
NodeIKernelProfileListener,
NodeIKernelSessionListener,
NodeIKernelSessionListener
} from '../listeners';
import {
NodeIKernelAvatarService,
@@ -35,6 +30,7 @@ import { NodeIkernelTestPerformanceService } from '../services/NodeIkernelTestPe
import { NodeIKernelECDHService } from '../services/NodeIKernelECDHService';
export interface NodeQQNTWrapperUtil {
get(): unknown;
// eslint-disable-next-line @typescript-eslint/no-misused-new
new(): NodeQQNTWrapperUtil;
@@ -68,9 +64,9 @@ export interface NodeQQNTWrapperUtil {
genFileShaAndMd5Hex(path: string, unknown: number): unknown; //可能是错的
setTraceInfo(unknown: Object): unknown;
setTraceInfo(unknown: unknown): unknown;
encodeOffLine(unknown: Object): unknown;
encodeOffLine(unknown: unknown): unknown;
decodeOffLine(arg: string): unknown; //可能是错的 传递hex
@@ -88,7 +84,7 @@ export interface NodeQQNTWrapperUtil {
runProcessArgs(arg0: string, arg1: { [key: string]: string }, arg2: boolean): unknown;
calcThumbSize(arg0: number, arg1: number, arg2: Object): unknown;
calcThumbSize(arg0: number, arg1: number, arg2: unknown): unknown;
fullWordToHalfWord(arg0: string): unknown;
@@ -277,20 +273,11 @@ export interface NodeIQQNTWrapperEngine {
export interface WrapperNodeApi {
[key: string]: any;
NodeIKernelBuddyListener: NodeIKernelBuddyListener;
NodeIKernelGroupListener: NodeIKernelGroupListener;
NodeQQNTWrapperUtil: NodeQQNTWrapperUtil;
NodeIQQNTWrapperSession: NodeIQQNTWrapperSession;
NodeIKernelMsgListener: NodeIKernelMsgListener;
NodeIQQNTWrapperEngine: NodeIQQNTWrapperEngine;
NodeIGlobalAdapter: NodeIGlobalAdapter;
NodeIDependsAdapter: NodeIDependsAdapter;
NodeIDispatcherAdapter: NodeIDispatcherAdapter;
NodeIKernelSessionListener: NodeIKernelSessionListener;
NodeIKernelLoginService: NodeIKernelLoginService;
NodeIKernelLoginListener: NodeIKernelLoginListener;
NodeIKernelProfileService: NodeIKernelProfileService;
NodeIKernelProfileListener: NodeIKernelProfileListener;
}
export enum PlatformType {

File diff suppressed because one or more lines are too long

View File

@@ -5,7 +5,7 @@ import { QQBasicInfoWrapper } from '@/common/utils/QQBasicInfo';
import { loadQQWrapper, NapCatCore, NapCatCoreWorkingEnv } from '@/core/core';
import { InstanceContext } from '@/core';
import { SelfInfo } from '@/core/entities';
import { LoginListener } from '@/core/listeners';
import { NodeIKernelLoginListener } from '@/core/listeners';
import { NodeIKernelLoginService } from '@/core/services';
import { NodeIQQNTWrapperSession, WrapperNodeApi } from '@/core/wrapper/wrapper';
import { InitWebUi, WebUiConfig } from '@/webui';
@@ -29,7 +29,7 @@ export async function NCoreInitFramework(
const wrapper = loadQQWrapper(basicInfoWrapper.getFullQQVesion());
//直到登录成功后,执行下一步
const selfInfo = await new Promise<SelfInfo>((resolveSelfInfo) => {
const loginListener = new LoginListener();
const loginListener = new NodeIKernelLoginListener();
loginListener.onQRCodeLoginSucceed = async (loginResult) => {
await new Promise<void>(resolvePendingInit => {
registerInitCallback(() => resolvePendingInit());
@@ -41,8 +41,7 @@ export async function NCoreInitFramework(
online: true,
});
};
loginService.addKernelLoginListener(new wrapper.NodeIKernelLoginListener(
proxiedListenerOf(loginListener, logger)));
loginService.addKernelLoginListener(proxiedListenerOf(loginListener, logger) as any);
});
// 过早进入会导致addKernelMsgListener等Listener添加失败
// await sleep(2500);

View File

@@ -17,7 +17,7 @@ export class FetchCustomFace extends BaseAction<Payload, string[]> {
async _handle(payload: Payload) {
//48 可能正好是QQ需要的一个页面的数量 Tagged Mlikiowa
const ret = await this.CoreContext.apis.MsgApi.fetchFavEmojiList(parseInt((payload.count || '0').toString()) || 48);
const ret = await this.CoreContext.apis.MsgApi.fetchFavEmojiList(+(payload.count ?? 48));
return ret.emojiInfoList.map(e => e.url);
}
}

View File

@@ -27,6 +27,6 @@ export class FetchEmojiLike extends BaseAction<Payload, any> {
const msgIdPeer = MessageUnique.getMsgIdAndPeerByShortId(parseInt(payload.message_id.toString()));
if (!msgIdPeer) throw new Error('消息不存在');
const msg = (await NTQQMsgApi.getMsgsByMsgId(msgIdPeer.Peer, [msgIdPeer.MsgId])).msgList[0];
return await NTQQMsgApi.getMsgEmojiLikesList(msgIdPeer.Peer, msg.msgSeq, payload.emojiId, payload.emojiType, parseInt((payload.count || '0').toString()) || 20);
return await NTQQMsgApi.getMsgEmojiLikesList(msgIdPeer.Peer, msg.msgSeq, payload.emojiId, payload.emojiType, +(payload.count ?? 20));
}
}

View File

@@ -18,6 +18,6 @@ export class GetCollectionList extends BaseAction<Payload, any> {
PayloadSchema = SchemaData;
async _handle(payload: Payload) {
const NTQQCollectionApi = this.CoreContext.apis.CollectionApi;
return await NTQQCollectionApi.getAllCollection(parseInt(payload.category.toString()), parseInt(payload.count.toString()));
return await NTQQCollectionApi.getAllCollection(parseInt(payload.category.toString()), +(payload.count ?? 1));
}
}

View File

@@ -1,4 +1,4 @@
import { OB11Constructor } from '@/onebot/helper/data';
import { OB11Constructor } from '@/onebot/helper/converter';
import BaseAction from '../BaseAction';
import { ActionName } from '../types';

View File

@@ -0,0 +1,44 @@
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import BaseAction from '../BaseAction';
import { ActionName } from '../types';
import { ChatType, Peer } from '@/core';
const SchemaData = {
type: 'object',
properties: {
eventType: { type: 'string' },
group_id: { type: 'string' },
user_id: { type: 'string' }
},
required: ['eventType'],
} as const satisfies JSONSchema;
type Payload = FromSchema<typeof SchemaData>;
export class SetInputStatus extends BaseAction<Payload, any> {
actionName = ActionName.SetInputStatus;
async _handle(payload: Payload) {
const NTQQUserApi = this.CoreContext.apis.UserApi;
const NTQQMsgApi = this.CoreContext.apis.MsgApi;
let peer: Peer;
if (payload.group_id) {
peer = {
chatType: ChatType.KCHATTYPEGROUP,
peerUid: payload.group_id
};
} else if (payload.user_id) {
const uid = await NTQQUserApi.getUidByUinV2(payload.user_id);
if (!uid) throw new Error('uid is empty');
peer = {
chatType: ChatType.KCHATTYPEC2C,
peerUid: uid
};
} else {
throw new Error('请指定 group_id 或 user_id');
}
const ret = await NTQQMsgApi.sendShowInputStatusReq(peer, parseInt(payload.eventType));
return ret;
}
}

View File

@@ -1,32 +0,0 @@
import BaseAction from '../BaseAction';
import { ActionName } from '../types';
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
const SchemaData = {
type: 'object',
properties: {
nick: { type: 'string' },
longNick: { type: 'string' },
sex: { type: ['number', 'string'] },//传Sex值建议传0
},
required: ['nick', 'longNick', 'sex'],
} as const satisfies JSONSchema;
type Payload = FromSchema<typeof SchemaData>;
export class SetSelfProfile extends BaseAction<Payload, any | null> {
actionName = ActionName.SetSelfProfile;
PayloadSchema = SchemaData;
async _handle(payload: Payload) {
const NTQQUserApi = this.CoreContext.apis.UserApi;
const ret = await NTQQUserApi.modifySelfProfile({
nick: payload.nick,
longNick: payload.longNick,
sex: parseInt(payload.sex.toString()),
birthday: { birthday_year: '', birthday_month: '', birthday_day: '' },
location: undefined,
});
return ret;
}
}

View File

@@ -47,15 +47,15 @@ export class GetFileBase extends BaseAction<GetFilePayload, GetFileResponse> {
let peer: Peer | undefined;
//识别Peer
if (isGroup) {
peer = { chatType: ChatType.group, peerUid: peerUin };
peer = { chatType: ChatType.KCHATTYPEGROUP, peerUid: peerUin };
}
const PeerUid = await NTQQUserApi.getUidByUinV2(peerUin);
if (PeerUid) {
const isBuddy = await NTQQFriendApi.isBuddy(PeerUid);
if (isBuddy) {
peer = { chatType: ChatType.friend, peerUid: PeerUid };
peer = { chatType: ChatType.KCHATTYPEC2C, peerUid: PeerUid };
} else {
peer = { chatType: ChatType.temp, peerUid: PeerUid };
peer = { chatType: ChatType.KCHATTYPETEMPC2CFROMGROUP, peerUid: PeerUid };
}
}
if (!peer) {
@@ -90,15 +90,15 @@ export class GetFileBase extends BaseAction<GetFilePayload, GetFileResponse> {
return res;
}
} catch {
this.CoreContext.context.logger.logDebug('GetFileBase Mode - 1 Error');
}
const NTSearchNameResult = (await NTQQFileApi.searchfile([payload.file])).resultItems;
if (NTSearchNameResult.length !== 0) {
const MsgId = NTSearchNameResult[0].msgId;
let peer: Peer | undefined = undefined;
if (NTSearchNameResult[0].chatType == ChatType.group) {
peer = { chatType: ChatType.group, peerUid: NTSearchNameResult[0].groupChatInfo[0].groupCode };
if (NTSearchNameResult[0].chatType == ChatType.KCHATTYPEGROUP) {
peer = { chatType: ChatType.KCHATTYPEGROUP, peerUid: NTSearchNameResult[0].groupChatInfo[0].groupCode };
}
if (!peer) {
throw new Error('chattype not support');

View File

@@ -1,6 +1,5 @@
import BaseAction from '../BaseAction';
import { OB11ForwardMessage, OB11Message, OB11MessageData } from '../../types';
import { OB11Constructor } from '../../helper/data';
import { OB11ForwardMessage, OB11Message, OB11MessageData } from '@/onebot';
import { ActionName } from '../types';
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import { MessageUnique } from '@/common/utils/MessageUnique';
@@ -40,7 +39,8 @@ export class GoCQHTTPGetForwardMsgAction extends BaseAction<Payload, any> {
}
const msgList = data.msgList;
const messages = (await Promise.all(msgList.map(async msg => {
const resMsg = await OB11Constructor.message(this.CoreContext, this.OneBotContext, msg);
const resMsg = await this.OneBotContext.apiContext.MsgApi
.parseMessage(msg);
if (!resMsg) return;
resMsg.message_id = MessageUnique.createMsg({
guildId: '',

View File

@@ -1,8 +1,7 @@
import BaseAction from '../BaseAction';
import { OB11Message } from '../../types';
import { OB11Message } from '@/onebot';
import { ActionName } from '../types';
import { ChatType, RawMessage } from '@/core/entities';
import { OB11Constructor } from '../../helper/data';
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import { MessageUnique } from '@/common/utils/MessageUnique';
@@ -15,7 +14,7 @@ const SchemaData = {
properties: {
user_id: { type: ['number', 'string'] },
message_seq: { type: 'number' },
count: { type: 'number' },
count: { type: ['number', 'string'] },
reverseOrder: { type: 'boolean' },
},
required: ['user_id'],
@@ -33,11 +32,11 @@ export default class GetFriendMsgHistory extends BaseAction<Payload, Response> {
const NTQQFriendApi = this.CoreContext.apis.FriendApi;
//处理参数
const uid = await NTQQUserApi.getUidByUinV2(payload.user_id.toString());
const MsgCount = payload.count || 20;
const MsgCount = +(payload.count ?? 20);
const isReverseOrder = payload.reverseOrder || true;
if (!uid) throw `记录${payload.user_id}不存在`;
const friend = await NTQQFriendApi.isBuddy(uid);
const peer = { chatType: friend ? ChatType.friend : ChatType.temp, peerUid: uid };
const peer = { chatType: friend ? ChatType.KCHATTYPEC2C : ChatType.KCHATTYPETEMPC2CFROMGROUP, peerUid: uid };
//拉取消息
let msgList: RawMessage[];
@@ -53,7 +52,9 @@ export default class GetFriendMsgHistory extends BaseAction<Payload, Response> {
msg.id = MessageUnique.createMsg({ guildId: '', chatType: msg.chatType, peerUid: msg.peerUid }, msg.msgId);
}));
//转换消息
const ob11MsgList = (await Promise.all(msgList.map(msg => OB11Constructor.message(this.CoreContext, this.OneBotContext, msg)))).filter(msg => !!msg);
const ob11MsgList = (await Promise.all(
msgList.map(msg => this.OneBotContext.apiContext.MsgApi.parseMessage(msg)))
).filter(msg => msg !== undefined);
return { 'messages': ob11MsgList };
}
}

View File

@@ -1,8 +1,7 @@
import BaseAction from '../BaseAction';
import { OB11Message } from '../../types';
import { OB11Message } from '@/onebot';
import { ActionName } from '../types';
import { ChatType, Peer, RawMessage } from '@/core/entities';
import { OB11Constructor } from '../../helper/data';
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import { MessageUnique } from '@/common/utils/MessageUnique';
@@ -15,7 +14,7 @@ const SchemaData = {
properties: {
group_id: { type: ['number', 'string'] },
message_seq: { type: 'number' },
count: { type: 'number' },
count: { type: ['number', 'string'] },
reverseOrder: { type: 'boolean' },
},
required: ['group_id'],
@@ -31,8 +30,8 @@ export default class GoCQHTTPGetGroupMsgHistory extends BaseAction<Payload, Resp
const NTQQMsgApi = this.CoreContext.apis.MsgApi;
//处理参数
const isReverseOrder = payload.reverseOrder || true;
const MsgCount = payload.count || 20;
const peer: Peer = { chatType: ChatType.group, peerUid: payload.group_id.toString() };
const MsgCount = +(payload.count ?? 20);
const peer: Peer = { chatType: ChatType.KCHATTYPEGROUP, peerUid: payload.group_id.toString() };
//拉取消息
let msgList: RawMessage[];
if (!payload.message_seq || payload.message_seq == 0) {
@@ -48,7 +47,9 @@ export default class GoCQHTTPGetGroupMsgHistory extends BaseAction<Payload, Resp
}));
//转换消息
const ob11MsgList = (await Promise.all(msgList.map(msg => OB11Constructor.message(this.CoreContext, this.OneBotContext, msg)))).filter(msg => !!msg);
const ob11MsgList = (await Promise.all(
msgList.map(msg => this.OneBotContext.apiContext.MsgApi.parseMessage(msg)))
).filter(msg => msg !== undefined);
return { 'messages': ob11MsgList };
}
}

View File

@@ -1,6 +1,6 @@
import BaseAction from '../BaseAction';
import { OB11User, OB11UserSex } from '../../types';
import { OB11Constructor } from '../../helper/data';
import { OB11Constructor } from '@/onebot/helper/converter';
import { ActionName } from '../types';
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import { calcQQLevel } from '@/common/utils/helper';
@@ -21,19 +21,22 @@ export default class GoCQHTTPGetStrangerInfo extends BaseAction<Payload, OB11Use
async _handle(payload: Payload): Promise<OB11User> {
const NTQQUserApi = this.CoreContext.apis.UserApi;
const user_id = payload.user_id.toString();
const extendData = await NTQQUserApi.getUserDetailInfoByUin(user_id);
const extendData = await NTQQUserApi.getUserDetailInfoByUinV2(user_id);
const uid = (await NTQQUserApi.getUidByUinV2(user_id))!;
if (!uid || uid.indexOf('*') != -1) {
const ret = {
...extendData,
user_id: parseInt(extendData.info.uin) || 0,
nickname: extendData.info.nick,
...extendData.detail.simpleInfo.coreInfo,
...extendData.detail.commonExt,
...extendData.detail.simpleInfo.baseInfo,
...extendData.detail.simpleInfo.relationFlags,
user_id: parseInt(extendData.detail.uin) || 0,
nickname: extendData.detail.simpleInfo.coreInfo.nick,
sex: OB11UserSex.unknown,
age: (extendData.info.birthday_year == 0) ? 0 : new Date().getFullYear() - extendData.info.birthday_year,
qid: extendData.info.qid,
level: extendData.info.qqLevel && calcQQLevel(extendData.info.qqLevel) || 0,
age: extendData.detail.simpleInfo.baseInfo.age || 0,
qid: extendData.detail.simpleInfo.baseInfo.qid,
level: calcQQLevel(extendData.detail.commonExt?.qqLevel ?? 0) || 0,
login_days: 0,
uid: '',
uid: ''
};
return ret;
}

View File

@@ -62,8 +62,6 @@ export class SendGroupNotice extends BaseAction<Payload, null> {
if (PublishGroupBulletinResult.result != 0) {
throw `设置群公告失败,错误信息:${PublishGroupBulletinResult.errMsg}`;
}
// 下面实现扬了
//await WebApi.setGroupNotice(payload.group_id, payload.content) ;
return null;
}
}

View File

@@ -7,18 +7,18 @@ import { checkFileReceived, uri2local } from '@/common/utils/file';
interface Payload {
file: string,
groupCode: string
group_id: number
}
export default class SetGroupHeader extends BaseAction<Payload, any> {
actionName = ActionName.SetGroupHeader;
export default class SetGroupPortrait extends BaseAction<Payload, any> {
actionName = ActionName.SetGroupPortrait;
// 用不着复杂检测
protected async check(payload: Payload): Promise<BaseCheckResult> {
if (!payload.file || typeof payload.file != 'string' || !payload.groupCode || typeof payload.groupCode != 'string') {
if (!payload.file || typeof payload.file != 'string' || !payload.group_id || typeof payload.group_id != 'number') {
return {
valid: false,
message: 'file和groupCode字段不能为空或者类型错误',
message: 'file和group_id字段不能为空或者类型错误',
};
}
return {
@@ -34,7 +34,7 @@ export default class SetGroupHeader extends BaseAction<Payload, any> {
}
if (path) {
await checkFileReceived(path, 5000); // 文件不存在QQ会崩溃需要提前判断
const ret = await NTQQGroupApi.setGroupAvatar(payload.groupCode, path);
const ret = await NTQQGroupApi.setGroupAvatar(payload.group_id.toString(), path) as any;
if (!isLocal) {
fs.unlink(path, () => {
});
@@ -43,11 +43,11 @@ export default class SetGroupHeader extends BaseAction<Payload, any> {
throw `头像${payload.file}设置失败,api无返回`;
}
// log(`头像设置返回:${JSON.stringify(ret)}`)
// if (ret['result'] == 1004022) {
// throw `头像${payload.file}设置失败,文件可能不是图片格式`;
// } else if (ret['result'] != 0) {
// throw `头像${payload.file}设置失败,未知的错误,${ret['result']}:${ret['errMsg']}`;
// }
if (ret['result'] == 1004022) {
throw `头像${payload.file}设置失败,文件可能不是图片格式或权限不足`;
} else if (ret['result'] != 0) {
throw `头像${payload.file}设置失败,未知的错误,${ret['result']}:${ret['errMsg']}`;
}
return ret;
} else {
if (!isLocal) {

View File

@@ -0,0 +1,34 @@
import BaseAction from '../BaseAction';
import { ActionName } from '../types';
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
const SchemaData = {
type: 'object',
properties: {
nickname: { type: 'string' },
personal_note: { type: 'string' },
sex: { type: ['number', 'string'] },//传Sex值建议传0
},
required: ['nickname'],
} as const satisfies JSONSchema;
type Payload = FromSchema<typeof SchemaData>;
export class SetQQProfile extends BaseAction<Payload, any | null> {
actionName = ActionName.SetQQProfile;
PayloadSchema = SchemaData;
async _handle(payload: Payload) {
const NTQQUserApi = this.CoreContext.apis.UserApi;
const self = this.CoreContext.selfInfo;
const OldProfile = await NTQQUserApi.getUserDetailInfo(self.uid);
const ret = await NTQQUserApi.modifySelfProfile({
nick: payload.nickname,
longNick: (payload?.personal_note ?? OldProfile?.longNick) || '',
sex: parseInt(payload?.sex ? payload?.sex.toString() : OldProfile?.sex!.toString()),
birthday: { birthday_year: OldProfile?.birthday_year!.toString(), birthday_month: OldProfile?.birthday_month!.toString(), birthday_day: OldProfile?.birthday_day!.toString() },
location: undefined,
});
return ret;
}
}

View File

@@ -1,11 +1,10 @@
import BaseAction from '../BaseAction';
import { ActionName } from '../types';
import { ChatType, SendFileElement } from '@/core/entities';
import { ChatType } from '@/core/entities';
import fs from 'fs';
import { sendMsg } from '@/onebot/action/msg/SendMsg';
import { uri2local } from '@/common/utils/file';
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import { SendMsgElementConstructor } from '@/onebot/helper/msg';
const SchemaData = {
type: 'object',
@@ -34,9 +33,9 @@ export default class GoCQHTTPUploadGroupFile extends BaseAction<Payload, null> {
if (!downloadResult.success) {
throw new Error(downloadResult.errMsg);
}
const sendFileEle: SendFileElement = await SendMsgElementConstructor.file(this.CoreContext, downloadResult.path, payload.name, payload.folder_id);
const sendFileEle = await this.CoreContext.apis.FileApi.createValidSendFileElement(downloadResult.path, payload.name, payload.folder_id);
await sendMsg(this.CoreContext, {
chatType: ChatType.group,
chatType: ChatType.KCHATTYPEGROUP,
peerUid: payload.group_id.toString(),
}, [sendFileEle], [], true);
return null;

View File

@@ -5,7 +5,6 @@ import fs from 'fs';
import { sendMsg } from '@/onebot/action/msg/SendMsg';
import { uri2local } from '@/common/utils/file';
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import { SendMsgElementConstructor } from '@/onebot/helper/msg';
const SchemaData = {
type: 'object',
@@ -32,9 +31,9 @@ export default class GoCQHTTPUploadPrivateFile extends BaseAction<Payload, null>
throw `私聊${payload.user_id}不存在`;
}
const isBuddy = await NTQQFriendApi.isBuddy(peerUid);
return { chatType: isBuddy ? ChatType.friend : ChatType.temp, peerUid };
return { chatType: isBuddy ? ChatType.KCHATTYPEC2C : ChatType.KCHATTYPETEMPC2CFROMGROUP, peerUid };
}
throw '缺少参数 user_id';
throw new Error( '缺少参数 user_id');
}
async _handle(payload: Payload): Promise<null> {
@@ -47,7 +46,7 @@ export default class GoCQHTTPUploadPrivateFile extends BaseAction<Payload, null>
if (!downloadResult.success) {
throw new Error(downloadResult.errMsg);
}
const sendFileEle: SendFileElement = await SendMsgElementConstructor.file(this.CoreContext, downloadResult.path, payload.name);
const sendFileEle: SendFileElement = await this.CoreContext.apis.FileApi.createValidSendFileElement(downloadResult.path, payload.name);
await sendMsg(this.CoreContext, peer, [sendFileEle], [], true);
return null;
}

View File

@@ -0,0 +1,28 @@
import { WebApiGroupNoticeFeed } from '@/core';
import BaseAction from '../BaseAction';
import { ActionName } from '../types';
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
const SchemaData = {
type: 'object',
properties: {
group_id: { type: ['number', 'string'] },
feed_id: { type: 'string' },
},
required: ['group_id','feed_id'],
} as const satisfies JSONSchema;
type Payload = FromSchema<typeof SchemaData>;
export class DelGroupNotice extends BaseAction<Payload, any> {
actionName = ActionName.DelGroupNotice;
PayloadSchema = SchemaData;
async _handle(payload: Payload) {
const NTQQGroupApi = this.CoreContext.apis.GroupApi;
const group = payload.group_id.toString();
const feedId = payload.feed_id;
return await NTQQGroupApi.deleteGroupBulletin(group, feedId);
}
}

View File

@@ -1,5 +1,5 @@
import { OB11Group } from '../../types';
import { OB11Constructor } from '../../helper/data';
import { OB11Constructor } from '@/onebot/helper/converter';
import BaseAction from '../BaseAction';
import { ActionName } from '../types';
import { FromSchema, JSONSchema } from 'json-schema-to-ts';

View File

@@ -1,5 +1,5 @@
import { OB11Group } from '../../types';
import { OB11Constructor } from '../../helper/data';
import { OB11Constructor } from '@/onebot/helper/converter';
import BaseAction from '../BaseAction';
import { ActionName } from '../types';
import { Group } from '@/core/entities';

View File

@@ -1,8 +1,9 @@
import { OB11GroupMember } from '../../types';
import { OB11Constructor } from '../../helper/data';
import { OB11Constructor } from '@/onebot/helper/converter';
import BaseAction from '../BaseAction';
import { ActionName } from '../types';
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import { GroupMember } from '@/core';
const SchemaData = {
type: 'object',
@@ -23,39 +24,25 @@ class GetGroupMemberInfo extends BaseAction<Payload, OB11GroupMember> {
async _handle(payload: Payload) {
const NTQQUserApi = this.CoreContext.apis.UserApi;
const NTQQGroupApi = this.CoreContext.apis.GroupApi;
const NTQQWebApi = this.CoreContext.apis.WebApi;
const isNocache = typeof payload.no_cache === 'string' ? payload.no_cache === 'true' : !!payload.no_cache;
const uid = await NTQQUserApi.getUidByUinV2(payload.user_id.toString());
if (!uid) throw (`Uin2Uid Error ${payload.user_id}不存在`);
const member = await NTQQGroupApi.getGroupMemberV2(payload.group_id.toString(), uid, isNocache);
if (!member) throw (`群(${payload.group_id})成员${payload.user_id}不存在`);
try {
const info = (await NTQQUserApi.getUserDetailInfo(member.uid));
this.CoreContext.context.logger.logDebug('群成员详细信息结果', info);
Object.assign(member, info);
} catch (e) {
this.CoreContext.context.logger.logDebug('获取群成员详细信息失败, 只能返回基础信息', e);
if (!uid) throw new Error (`Uin2Uid Error ${payload.user_id}不存在`);
const [member, info] = await Promise.allSettled([
NTQQGroupApi.getGroupMemberV2(payload.group_id.toString(), uid, isNocache),
NTQQUserApi.getUserDetailInfo(uid),
]);
if (member.status !== 'fulfilled') throw new Error (`群(${payload.group_id})成员${payload.user_id}不存在 ${member.reason}`);
if (info.status === 'fulfilled') {
this.CoreContext.context.logger.logDebug("群成员详细信息结果", info.value);
Object.assign(member, info.value);
} else {
this.CoreContext.context.logger.logDebug(`获取群成员详细信息失败, 只能返回基础信息 ${info.reason}`);
}
const date = Math.round(Date.now() / 1000);
const retMember = OB11Constructor.groupMember(payload.group_id.toString(), member);
const SelfInfoInGroup = await NTQQGroupApi.getGroupMemberV2(payload.group_id.toString(), this.CoreContext.selfInfo.uid, isNocache);
let isPrivilege = false;
if (SelfInfoInGroup) {
isPrivilege = SelfInfoInGroup.role === 3 || SelfInfoInGroup.role === 4;
}
if (isPrivilege) {
const webGroupMembers = await NTQQWebApi.getGroupMembers(payload.group_id.toString());
for (let i = 0, len = webGroupMembers.length; i < len; i++) {
if (webGroupMembers[i]?.uin && webGroupMembers[i].uin === retMember.user_id) {
retMember.join_time = webGroupMembers[i]?.join_time;
retMember.last_sent_time = webGroupMembers[i]?.last_speak_time;
retMember.qage = webGroupMembers[i]?.qage;
retMember.level = webGroupMembers[i]?.lv.level.toString();
}
}
}
retMember.last_sent_time = parseInt((await this.CoreContext.apis.GroupApi.getGroupMember(payload.group_id.toString(), retMember.user_id))?.lastSpeakTime || date.toString());
retMember.join_time = parseInt((await this.CoreContext.apis.GroupApi.getGroupMember(payload.group_id.toString(), retMember.user_id))?.joinTime || date.toString());
const retMember = OB11Constructor.groupMember(payload.group_id.toString(), member.value as GroupMember);
const Member = await this.CoreContext.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

@@ -1,5 +1,5 @@
import { OB11GroupMember } from '../../types';
import { OB11Constructor } from '../../helper/data';
import { OB11Constructor } from '@/onebot/helper/converter';
import BaseAction from '../BaseAction';
import { ActionName } from '../types';
import { FromSchema, JSONSchema } from 'json-schema-to-ts';

View File

@@ -6,6 +6,7 @@ import { FromSchema, JSONSchema } from 'json-schema-to-ts';
interface GroupNotice {
sender_id: number;
publish_time: number;
feed_id: string;
message: {
text: string
image: Array<{
@@ -45,6 +46,7 @@ export class GetGroupNotice extends BaseAction<Payload, GroupNotice[]> {
const retApiNotice: WebApiGroupNoticeFeed = ret.feeds[key];
const retNotice: GroupNotice = {
//...ret.feeds[key],
feed_id: retApiNotice.fid,
sender_id: retApiNotice.u,
publish_time: retApiNotice.pubt,
message: {

View File

@@ -1,3 +1,4 @@
import { GroupNotifyMsgStatus } from '@/core';
import BaseAction from '../BaseAction';
import { ActionName } from '../types';
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
@@ -28,7 +29,7 @@ export class GetGroupSystemMsg extends BaseAction<void, any> {
invitor_nick: SSNotify.user1?.nickName,
group_id: SSNotify.group?.groupCode,
group_name: SSNotify.group?.groupName,
checked: SSNotify.status === 1 ? false : true,
checked: SSNotify.status === GroupNotifyMsgStatus.KUNHANDLE ? false : true,
actor: await NTQQUserApi.getUinByUidV2(SSNotify.user2?.uid) || 0,
});
} else if (SSNotify.type == 7) {
@@ -38,7 +39,7 @@ export class GetGroupSystemMsg extends BaseAction<void, any> {
requester_nick: SSNotify.user1?.nickName,
group_id: SSNotify.group?.groupCode,
group_name: SSNotify.group?.groupName,
checked: SSNotify.status === 1 ? false : true,
checked: SSNotify.status === GroupNotifyMsgStatus.KUNHANDLE ? false : true,
actor: await NTQQUserApi.getUinByUidV2(SSNotify.user2?.uid) || 0,
});
}

View File

@@ -25,7 +25,7 @@ export default class SetGroupAddRequest extends BaseAction<Payload, null> {
const approve = payload.approve?.toString() !== 'false';
await NTQQGroupApi.handleGroupRequest(flag,
approve ? GroupRequestOperateTypes.approve : GroupRequestOperateTypes.reject,
payload.reason || ' ',
payload.reason ?? ' ',
);
return null;
}

View File

@@ -61,7 +61,7 @@ import { TranslateEnWordToZn } from './extends/TranslateEnWordToZn';
import { SetGroupFileFolder } from './file/SetGroupFileFolder';
import { DelGroupFile } from './file/DelGroupFile';
import { DelGroupFileFolder } from './file/DelGroupFileFolder';
import { SetSelfProfile } from './extends/SetSelfProfile';
import { SetQQProfile } from './go-cqhttp/SetQQProfile';
import { ShareGroupEx, SharePeer } from './extends/ShareContact';
import { CreateCollection } from './extends/CreateCollection';
import { SetLongNick } from './extends/SetLongNick';
@@ -69,15 +69,18 @@ import DelEssenceMsg from './group/DelEssenceMsg';
import SetEssenceMsg from './group/SetEssenceMsg';
import GetRecentContact from './user/GetRecentContact';
import { GetProfileLike } from './extends/GetProfileLike';
import SetGroupHeader from './extends/SetGroupHeader';
import SetGroupPortrait from './go-cqhttp/SetGroupPortrait';
import { FetchCustomFace } from './extends/FetchCustomFace';
import GoCQHTTPUploadPrivateFile from './go-cqhttp/UploadPrivareFile';
import GoCQHTTPUploadPrivateFile from './go-cqhttp/UploadPrivateFile';
import { FetchEmojiLike } from './extends/FetchEmojiLike';
import { NapCatCore } from '@/core';
import { NapCatOneBot11Adapter } from '@/onebot';
import GetGuildProfile from './guild/GetGuildProfile';
import SetModelShow from './go-cqhttp/SetModelShow';
import { SetInputStatus } from './extends/SetInputStatus';
import { GetCSRF } from './system/GetCSRF';
import { DelGroupNotice } from './group/DelGroupNotice';
export type ActionMap = Map<string, BaseAction<any, any>>;
@@ -85,7 +88,7 @@ export function createActionMap(onebotContext: NapCatOneBot11Adapter, coreContex
const actionHandlers = [
new FetchEmojiLike(onebotContext, coreContext),
new GetFile(onebotContext, coreContext),
new SetSelfProfile(onebotContext, coreContext),
new SetQQProfile(onebotContext, coreContext),
new ShareGroupEx(onebotContext, coreContext),
new SharePeer(onebotContext, coreContext),
new CreateCollection(onebotContext, coreContext),
@@ -160,11 +163,14 @@ export function createActionMap(onebotContext: NapCatOneBot11Adapter, coreContex
new GetRecentContact(onebotContext, coreContext),
new MarkAllMsgAsRead(onebotContext, coreContext),
new GetProfileLike(onebotContext, coreContext),
new SetGroupHeader(onebotContext, coreContext),
new SetGroupPortrait(onebotContext, coreContext),
new FetchCustomFace(onebotContext, coreContext),
new GoCQHTTPUploadPrivateFile(onebotContext, coreContext),
new GetGuildProfile(onebotContext, coreContext),
new SetModelShow(onebotContext, coreContext),
new SetInputStatus(onebotContext, coreContext),
new GetCSRF(onebotContext, coreContext),
new DelGroupNotice(onebotContext, coreContext),
];
const actionMap = new Map();
for (const action of actionHandlers) {

View File

@@ -24,9 +24,9 @@ class ForwardSingleMsg extends BaseAction<Payload, null> {
if (!peerUid) {
throw new Error(`无法找到私聊对象${payload.user_id}`);
}
return { chatType: ChatType.friend, peerUid };
return { chatType: ChatType.KCHATTYPEC2C, peerUid };
}
return { chatType: ChatType.group, peerUid: payload.group_id!.toString() };
return { chatType: ChatType.KCHATTYPEGROUP, peerUid: payload.group_id!.toString() };
}
async _handle(payload: Payload): Promise<null> {

View File

@@ -1,5 +1,4 @@
import { OB11Message } from '../../types';
import { OB11Constructor } from '../../helper/data';
import { OB11Message } from '@/onebot';
import BaseAction from '../BaseAction';
import { ActionName } from '../types';
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
@@ -31,19 +30,20 @@ class GetMsg extends BaseAction<Payload, OB11Message> {
const MsgShortId = MessageUnique.getShortIdByMsgId(payload.message_id.toString());
const msgIdWithPeer = MessageUnique.getMsgIdAndPeerByShortId(MsgShortId || parseInt(payload.message_id.toString()));
if (!msgIdWithPeer) {
throw ('消息不存在');
throw new Error('消息不存在');
}
const peer = { guildId: '', peerUid: msgIdWithPeer?.Peer.peerUid, chatType: msgIdWithPeer.Peer.chatType };
const msg = await NTQQMsgApi.getMsgsByMsgId(
peer,
[msgIdWithPeer?.MsgId || payload.message_id.toString()]);
const retMsg = await OB11Constructor.message(this.CoreContext, this.OneBotContext, msg.msgList[0], 'array');
const retMsg = await this.OneBotContext.apiContext.MsgApi.parseMessage(msg.msgList[0], 'array');
if (!retMsg) throw Error('消息为空');
try {
retMsg.message_id = MessageUnique.createMsg(peer, msg.msgList[0].msgId)!;
retMsg.message_seq = retMsg.message_id;
retMsg.real_id = retMsg.message_id;
} catch (e) {
// ignored
}
return retMsg;
}

View File

@@ -23,12 +23,12 @@ class MarkMsgAsRead extends BaseAction<PlayloadType, null> {
throw `私聊${payload.user_id}不存在`;
}
const isBuddy = await NTQQFriendApi.isBuddy(peerUid);
return { chatType: isBuddy ? ChatType.friend : ChatType.temp, peerUid };
return { chatType: isBuddy ? ChatType.KCHATTYPEC2C : ChatType.KCHATTYPETEMPC2CFROMGROUP, peerUid };
}
if (!payload.group_id) {
throw '缺少参数 group_id 或 user_id';
throw new Error( '缺少参数 group_id 或 user_id');
}
return { chatType: ChatType.group, peerUid: payload.group_id.toString() };
return { chatType: ChatType.KCHATTYPEGROUP, peerUid: payload.group_id.toString() };
}
async _handle(payload: PlayloadType): Promise<null> {
@@ -36,7 +36,7 @@ class MarkMsgAsRead extends BaseAction<PlayloadType, null> {
// 调用API
const ret = await NTQQMsgApi.setMsgRead(await this.getPeer(payload));
if (ret.result != 0) {
throw ('设置已读失败,' + ret.errMsg);
throw new Error('设置已读失败,' + ret.errMsg);
}
return null;
}

View File

@@ -0,0 +1,321 @@
import {
OB11MessageData,
OB11MessageDataType,
OB11MessageMixType,
OB11MessageNode,
OB11PostSendMsg,
} from '@/onebot/types';
import { ActionName, BaseCheckResult } from '@/onebot/action/types';
import fs from 'node:fs';
import fsPromise from 'node:fs/promises';
import { decodeCQCode } from '@/onebot/helper/cqcode';
import { MessageUnique } from '@/common/utils/MessageUnique';
import { ChatType, ElementType, NapCatCore, Peer, RawMessage, SendMessageElement } from '@/core';
import BaseAction from '../BaseAction';
export interface ReturnDataType {
message_id: number;
}
export enum ContextMode {
Normal = 0,
Private = 1,
Group = 2
}
// Normalizes a mixed type (CQCode/a single segment/segment array) into a segment array.
export function normalize(message: OB11MessageMixType, autoEscape = false): OB11MessageData[] {
return typeof message === 'string' ? (
autoEscape ?
[{ type: OB11MessageDataType.text, data: { text: message } }] :
decodeCQCode(message)
) : Array.isArray(message) ? message : [message];
}
export async function sendMsg(coreContext: NapCatCore, peer: Peer, sendElements: SendMessageElement[], deleteAfterSentFiles: string[], waitComplete = true) {
const NTQQMsgApi = coreContext.apis.MsgApi;
const logger = coreContext.context.logger;
if (!sendElements.length) {
throw new Error('消息体无法解析, 请检查是否发送了不支持的消息类型');
}
let totalSize = 0;
let timeout = 10000;
try {
for (const fileElement of sendElements) {
if (fileElement.elementType === ElementType.PTT) {
totalSize += fs.statSync(fileElement.pttElement.filePath).size;
}
if (fileElement.elementType === ElementType.FILE) {
totalSize += fs.statSync(fileElement.fileElement.filePath).size;
}
if (fileElement.elementType === ElementType.VIDEO) {
totalSize += fs.statSync(fileElement.videoElement.filePath).size;
}
if (fileElement.elementType === ElementType.PIC) {
totalSize += fs.statSync(fileElement.picElement.sourcePath).size;
}
}
//且 PredictTime ((totalSize / 1024 / 512) * 1000)不等于Nan
const PredictTime = totalSize / 1024 / 256 * 1000;
if (!Number.isNaN(PredictTime)) {
timeout += PredictTime;// 10S Basic Timeout + PredictTime( For File 512kb/s )
}
} catch (e) {
logger.logError('发送消息计算预计时间异常', e);
}
const returnMsg = await NTQQMsgApi.sendMsg(peer, sendElements, waitComplete, timeout);
try {
returnMsg!.id = MessageUnique.createMsg({
chatType: peer.chatType,
guildId: '',
peerUid: peer.peerUid,
}, returnMsg!.msgId);
} catch (e: any) {
logger.logDebug('发送消息id获取失败', e);
returnMsg!.id = 0;
}
deleteAfterSentFiles.map((f) => {
fsPromise.unlink(f).then().catch(e => logger.logError('发送消息删除文件失败', e));
});
return returnMsg;
}
async function createContext(coreContext: NapCatCore, payload: OB11PostSendMsg, contextMode: ContextMode): Promise<Peer> {
// This function determines the type of message by the existence of user_id / group_id,
// not message_type.
// This redundant design of Ob11 here should be blamed.
const NTQQFriendApi = coreContext.apis.FriendApi;
const NTQQUserApi = coreContext.apis.UserApi;
const NTQQMsgApi = coreContext.apis.MsgApi;
if ((contextMode === ContextMode.Group || contextMode === ContextMode.Normal) && payload.group_id) {
return {
chatType: ChatType.KCHATTYPEGROUP,
peerUid: payload.group_id.toString(),
};
}
if ((contextMode === ContextMode.Private || contextMode === ContextMode.Normal) && payload.user_id) {
const Uid = await NTQQUserApi.getUidByUinV2(payload.user_id.toString());
if (!Uid) throw new Error('无法获取用户信息');
const isBuddy = await NTQQFriendApi.isBuddy(Uid);
if (!isBuddy) {
const ret = await NTQQMsgApi.getTempChatInfo(ChatType.KCHATTYPETEMPC2CFROMGROUP, Uid);
if (ret.tmpChatInfo?.groupCode) {
return {
chatType: ChatType.KCHATTYPETEMPC2CFROMGROUP,
peerUid: Uid,
guildId: '',
};
}
if (payload.group_id) {
return {
chatType: ChatType.KCHATTYPETEMPC2CFROMGROUP,
peerUid: Uid,
guildId: payload.group_id.toString(),
};
}
return {
chatType: ChatType.KCHATTYPEC2C,
peerUid: Uid!,
guildId: '',
};
}
return {
chatType: ChatType.KCHATTYPEC2C,
peerUid: Uid!,
guildId: '',
};
}
throw new Error('请指定 group_id 或 user_id');
}
function getSpecialMsgNum(payload: OB11PostSendMsg, msgType: OB11MessageDataType): number {
if (Array.isArray(payload.message)) {
return payload.message.filter(msg => msg.type == msgType).length;
}
return 0;
}
export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
actionName = ActionName.SendMsg;
contextMode = ContextMode.Normal;
protected async check(payload: OB11PostSendMsg): Promise<BaseCheckResult> {
const messages = normalize(payload.message);
const nodeElementLength = getSpecialMsgNum(payload, OB11MessageDataType.node);
if (nodeElementLength > 0 && nodeElementLength != messages.length) {
return {
valid: false,
message: '转发消息不能和普通消息混在一起发送,转发需要保证message只有type为node的元素',
};
}
if (payload.user_id && payload.message_type !== 'group') {
// const uid = await this.CoreContext.apis.UserApi.getUidByUinV2(payload.user_id.toString());
// const isBuddy = await NTQQFriendApi.isBuddy(uid!);
// if (!isBuddy) { }
}
return { valid: true };
}
async _handle(payload: OB11PostSendMsg): Promise<{ message_id: number }> {
if (payload.message_type === 'group') this.contextMode = ContextMode.Group;
if (payload.message_type === 'private') this.contextMode = ContextMode.Private;
const peer = await createContext(this.CoreContext, payload, this.contextMode);
const messages = normalize(
payload.message,
typeof payload.auto_escape === 'string' ? payload.auto_escape === 'true' : !!payload.auto_escape
);
if (getSpecialMsgNum(payload, OB11MessageDataType.node)) {
const returnMsg = await this.handleForwardedNodes(peer, messages as OB11MessageNode[]);
if (returnMsg) {
const msgShortId = MessageUnique.createMsg({
guildId: '',
peerUid: peer.peerUid,
chatType: peer.chatType,
}, returnMsg!.msgId);
return { message_id: msgShortId! };
} else {
throw Error('发送转发消息失败');
}
} else {
// if (getSpecialMsgNum(payload, OB11MessageDataType.music)) {
// const music: OB11MessageCustomMusic = messages[0] as OB11MessageCustomMusic;
// if (music) {
// }
// }
}
// log("send msg:", peer, sendElements)
const { sendElements, deleteAfterSentFiles } = await this.OneBotContext.apiContext.MsgApi
.createSendElements(messages, peer);
const returnMsg = await sendMsg(this.CoreContext, peer, sendElements, deleteAfterSentFiles);
return { message_id: returnMsg!.id! };
}
private async handleForwardedNodes(destPeer: Peer, messageNodes: OB11MessageNode[]): Promise<RawMessage | null> {
const NTQQMsgApi = this.CoreContext.apis.MsgApi;
const selfPeer = {
chatType: ChatType.KCHATTYPEC2C,
peerUid: this.CoreContext.selfInfo.uid,
};
let nodeMsgIds: string[] = [];
const logger = this.CoreContext.context.logger;
for (const messageNode of messageNodes) {
const nodeId = messageNode.data.id;
if (nodeId) {
//对Mgsid和OB11ID混用情况兜底
const nodeMsg = MessageUnique.getMsgIdAndPeerByShortId(parseInt(nodeId)) || MessageUnique.getPeerByMsgId(nodeId);
if (!nodeMsg) {
logger.logError('转发消息失败,未找到消息', nodeId);
continue;
}
nodeMsgIds.push(nodeMsg.MsgId);
} else {
// 自定义的消息
try {
const OB11Data = normalize(messageNode.data.content);
//筛选node消息
const isNodeMsg = OB11Data.filter(e => e.type === OB11MessageDataType.node).length;//找到子转发消息
if (isNodeMsg !== 0) {
if (isNodeMsg !== OB11Data.length) {
logger.logError('子消息中包含非node消息 跳过不合法部分');
continue;
}
const nodeMsg = await this.handleForwardedNodes(selfPeer, OB11Data.filter(e => e.type === OB11MessageDataType.node));
if (nodeMsg) {
nodeMsgIds.push(nodeMsg.msgId);
MessageUnique.createMsg(selfPeer, nodeMsg.msgId);
}
//完成子卡片生成跳过后续
continue;
}
const { sendElements } = await this.OneBotContext.apiContext.MsgApi
.createSendElements(OB11Data, destPeer);
//拆分消息
const MixElement = sendElements.filter(element => element.elementType !== ElementType.FILE && element.elementType !== ElementType.VIDEO);
const SingleElement = sendElements.filter(element => element.elementType === ElementType.FILE || element.elementType === ElementType.VIDEO).map(e => [e]);
const AllElement: SendMessageElement[][] = [MixElement, ...SingleElement].filter(e => e !== undefined && e.length !== 0);
const MsgNodeList: Promise<RawMessage | undefined>[] = [];
for (const sendElementsSplitElement of AllElement) {
MsgNodeList.push(sendMsg(this.CoreContext, selfPeer, sendElementsSplitElement, [], true).catch(_ => undefined));
}
(await Promise.allSettled(MsgNodeList)).map((result) => {
if (result.status === 'fulfilled' && result.value) {
nodeMsgIds.push(result.value.msgId);
MessageUnique.createMsg(selfPeer, result.value.msgId);
}
});
} catch (e) {
logger.logDebug('生成转发消息节点失败', e);
}
}
}
const nodeMsgArray: Array<RawMessage> = [];
let srcPeer: Peer | undefined = undefined;
let needSendSelf = false;
//检测是否处于同一个Peer 不在同一个peer则全部消息由自身发送
for (const msgId of nodeMsgIds) {
const nodeMsgPeer = MessageUnique.getPeerByMsgId(msgId);
if (!nodeMsgPeer) {
logger.logError('转发消息失败,未找到消息', msgId);
continue;
}
const nodeMsg = (await NTQQMsgApi.getMsgsByMsgId(nodeMsgPeer.Peer, [msgId])).msgList[0];
srcPeer = srcPeer ?? { chatType: nodeMsg.chatType, peerUid: nodeMsg.peerUid };
if (srcPeer.peerUid !== nodeMsg.peerUid) {
needSendSelf = true;
}
nodeMsgArray.push(nodeMsg);
}
nodeMsgIds = nodeMsgArray.map(msg => msg.msgId);
let retMsgIds: string[] = [];
if (needSendSelf) {
for (const [, msg] of nodeMsgArray.entries()) {
if (msg.peerUid === this.CoreContext.selfInfo.uid) {
retMsgIds.push(msg.msgId);
continue;
}
const ClonedMsg = await this.cloneMsg(msg);
if (ClonedMsg) retMsgIds.push(ClonedMsg.msgId);
}
} else {
retMsgIds = nodeMsgIds;
}
if (retMsgIds.length === 0) throw Error('转发消息失败,生成节点为空');
try {
logger.logDebug('开发转发', srcPeer, destPeer, retMsgIds);
return await NTQQMsgApi.multiForwardMsg(srcPeer!, destPeer, retMsgIds);
} catch (e) {
logger.logError('forward failed', e);
return null;
}
}
async cloneMsg(msg: RawMessage): Promise<RawMessage | undefined> {
const selfPeer = {
chatType: ChatType.KCHATTYPEC2C,
peerUid: this.CoreContext.selfInfo.uid,
};
const logger = this.CoreContext.context.logger;
const NTQQMsgApi = this.CoreContext.apis.MsgApi;
//msg 为待克隆消息
const sendElements: SendMessageElement[] = [];
for (const element of msg.elements) {
sendElements.push(element as SendMessageElement);
}
if (sendElements.length === 0) {
logger.logDebug('需要clone的消息无法解析将会忽略掉', msg);
}
try {
return await NTQQMsgApi.sendMsg(selfPeer, sendElements, true);
} catch (e) {
logger.logError(e, '克隆转发消息失败,将忽略本条消息', msg);
}
}
}
export default SendMsg;

View File

@@ -1,36 +0,0 @@
import { OB11MessageData } from '@/onebot/types';
function checkSendMessage(sendMsgList: OB11MessageData[]) {
function checkUri(uri: string): boolean {
const pattern = /^(file:\/\/|http:\/\/|https:\/\/|base64:\/\/)/;
return pattern.test(uri);
}
for (const msg of sendMsgList) {
if (msg['type'] && msg['data']) {
const type = msg['type'];
const data = msg['data'];
if (type === 'text' && !data['text']) {
return 400;
} else if (['image', 'voice', 'record'].includes(type)) {
if (!data['file']) {
return 400;
} else {
if (checkUri(data['file'])) {
return 200;
} else {
return 400;
}
}
} else if (type === 'at' && !data['qq']) {
return 400;
} else if (type === 'reply' && !data['id']) {
return 400;
}
} else {
return 400;
}
}
return 200;
}

View File

@@ -1,252 +0,0 @@
import { OB11MessageData, OB11MessageDataType, OB11MessageFileBase } from '@/onebot/types';
import { uri2local } from '@/common/utils/file';
import { RequestUtil } from '@/common/utils/request';
import { MessageUnique } from '@/common/utils/MessageUnique';
import { AtType, CustomMusicSignPostData, IdMusicSignPostData, NapCatCore, Peer, SendMessageElement } from '@/core';
import { SendMsgElementConstructor } from '@/onebot/helper/msg';
import { NapCatOneBot11Adapter } from '@/onebot';
export type MessageContext = {
deleteAfterSentFiles: string[],
peer: Peer
}
async function handleOb11FileLikeMessage(
coreContext: NapCatCore,
obContext: NapCatOneBot11Adapter,
{ data: inputdata }: OB11MessageFileBase,
{ deleteAfterSentFiles }: MessageContext,
) {
//有的奇怪的框架将url作为参数 而不是file 此时优先url 同时注意可能传入的是非file://开头的目录 By Mlikiowa
const {
path,
isLocal,
fileName,
errMsg,
success,
} = (await uri2local(coreContext.NapCatTempPath, inputdata?.url || inputdata.file));
if (!success) {
coreContext.context.logger.logError('文件下载失败', errMsg);
throw Error('文件下载失败' + errMsg);
}
if (!isLocal) { // 只删除http和base64转过来的文件
deleteAfterSentFiles.push(path);
}
return { path, fileName: inputdata.name || fileName };
}
const _handlers: {
[Key in OB11MessageDataType]: (
CoreContext: NapCatCore,
obContext: NapCatOneBot11Adapter,
sendMsg: Extract<OB11MessageData, { type: Key }>,
// This picks the correct message type out
// How great the type system of TypeScript is!
context: MessageContext,
) => Promise<SendMessageElement | undefined>
} = {
[OB11MessageDataType.text]: async (coreContext, obContext: NapCatOneBot11Adapter, { data: { text } }) => SendMsgElementConstructor.text(coreContext, text),
[OB11MessageDataType.at]: async (coreContext, obContext: NapCatOneBot11Adapter, { data: { qq: atQQ } }, context) => {
if (!context.peer) return undefined;
if (atQQ === 'all') return SendMsgElementConstructor.at(coreContext, atQQ, atQQ, AtType.atAll, '全体成员');
// then the qq is a group member
// Mlikiowa V2.0.26 Refactor Todo
const uid = await coreContext.apis.UserApi.getUidByUinV2(`${atQQ}`);
if (!uid) throw new Error('Get Uid Error');
return SendMsgElementConstructor.at(coreContext, atQQ, uid, AtType.atUser, '');
},
[OB11MessageDataType.reply]: async (coreContext, obContext: NapCatOneBot11Adapter, { data: { id } }) => {
const replyMsgM = MessageUnique.getMsgIdAndPeerByShortId(parseInt(id));
if (!replyMsgM) {
coreContext.context.logger.logWarn('回复消息不存在', id);
return undefined;
}
const NTQQMsgApi = coreContext.apis.MsgApi;
const replyMsg = (await NTQQMsgApi.getMsgsByMsgId(
replyMsgM.Peer, [replyMsgM.MsgId!])).msgList[0];
return replyMsg ?
SendMsgElementConstructor.reply(coreContext, replyMsg.msgSeq, replyMsg.msgId, replyMsg.senderUin!, replyMsg.senderUin!) :
undefined;
},
[OB11MessageDataType.face]: async (coreContext, obContext: NapCatOneBot11Adapter, { data: { id } }) => SendMsgElementConstructor.face(coreContext, parseInt(id)),
[OB11MessageDataType.mface]: async (coreContext, obContext: NapCatOneBot11Adapter, {
data: {
emoji_package_id, emoji_id, key, summary,
},
}) => SendMsgElementConstructor.mface(coreContext, emoji_package_id, emoji_id, key, summary),
// File service
[OB11MessageDataType.image]: async (coreContext, obContext: NapCatOneBot11Adapter, sendMsg, context) => {
const PicEle = await SendMsgElementConstructor.pic(
coreContext,
(await handleOb11FileLikeMessage(coreContext, obContext, sendMsg, context)).path,
sendMsg.data.summary || '',
sendMsg.data.subType || 0,
);
context.deleteAfterSentFiles.push(PicEle.picElement.sourcePath);
return PicEle;
}, // currently not supported
[OB11MessageDataType.file]: async (coreContext, obContext: NapCatOneBot11Adapter, sendMsg, context) => {
const { path, fileName } = await handleOb11FileLikeMessage(coreContext, obContext, sendMsg, context);
//logDebug('发送文件', path, fileName);
const FileEle = await SendMsgElementConstructor.file(coreContext, path, fileName);
// 清除Upload的应该
// context.deleteAfterSentFiles.push(fileName || FileEle.fileElement.filePath);
return FileEle;
},
[OB11MessageDataType.video]: async (coreContext, obContext, sendMsg, context) => {
const { path, fileName } = await handleOb11FileLikeMessage(coreContext, obContext, sendMsg, context);
//logDebug('发送视频', path, fileName);
let thumb = sendMsg.data.thumb;
if (thumb) {
const uri2LocalRes = await uri2local(coreContext.NapCatTempPath, thumb);
if (uri2LocalRes.success) thumb = uri2LocalRes.path;
}
const videoEle = await SendMsgElementConstructor.video(coreContext, path, fileName, thumb);
//未测试
context.deleteAfterSentFiles.push(videoEle.videoElement.filePath);
return videoEle;
},
[OB11MessageDataType.voice]: async (coreContext, obContext: NapCatOneBot11Adapter, sendMsg, context) => SendMsgElementConstructor.ptt(coreContext, (await handleOb11FileLikeMessage(coreContext, obContext, sendMsg, context)).path),
[OB11MessageDataType.json]: async (coreContext, obContext: NapCatOneBot11Adapter, { data: { data } }) => SendMsgElementConstructor.ark(coreContext, data),
[OB11MessageDataType.dice]: async (coreContext, obContext: NapCatOneBot11Adapter, { data: { result } }) => SendMsgElementConstructor.dice(coreContext, result),
[OB11MessageDataType.RPS]: async (coreContext, obContext: NapCatOneBot11Adapter, { data: { result } }) => SendMsgElementConstructor.rps(coreContext, result),
[OB11MessageDataType.markdown]: async (coreContext, obContext: NapCatOneBot11Adapter, { data: { content } }) => SendMsgElementConstructor.markdown(coreContext, content),
[OB11MessageDataType.music]: async (coreContext, obContext: NapCatOneBot11Adapter, { data }) => {
// 保留, 直到...找到更好的解决方案
if (data.type === 'custom') {
if (!data.url) {
coreContext.context.logger.logError('自定义音卡缺少参数url');
return undefined;
}
if (!data.audio) {
coreContext.context.logger.logError('自定义音卡缺少参数audio');
return undefined;
}
if (!data.title) {
coreContext.context.logger.logError('自定义音卡缺少参数title');
return undefined;
}
} else {
if (!['qq', '163'].includes(data.type)) {
coreContext.context.logger.logError('音乐卡片type错误, 只支持qq、163、custom当前type:', data.type);
return undefined;
}
if (!data.id) {
coreContext.context.logger.logError('音乐卡片缺少参数id');
return undefined;
}
}
let postData: IdMusicSignPostData | CustomMusicSignPostData;
if (data.type === 'custom' && data.content) {
const { content, ...others } = data;
postData = { singer: content, ...others };
} else {
postData = data;
}
// Mlikiowa V2.0.26 Refactor Todo
const signUrl = obContext.configLoader.configData.musicSignUrl;
if (!signUrl) {
if (data.type === 'qq') {
//const musicJson = (await SignMusicWrapper(data.id.toString())).data.arkResult.slice(0, -1);
//return SendMsgElementConstructor.ark(musicJson);
}
throw Error('音乐消息签名地址未配置');
}
try {
const musicJson = await RequestUtil.HttpGetJson<any>(signUrl, 'POST', postData);
return SendMsgElementConstructor.ark(coreContext, musicJson);
} catch (e) {
coreContext.context.logger.logError('生成音乐消息失败', e);
}
},
[OB11MessageDataType.node]: async (coreContext, obContext: NapCatOneBot11Adapter) => undefined,
[OB11MessageDataType.forward]: async (coreContext, obContext: NapCatOneBot11Adapter) => undefined,
[OB11MessageDataType.xml]: async (coreContext, obContext: NapCatOneBot11Adapter) => undefined,
[OB11MessageDataType.poke]: async (coreContext, obContext: NapCatOneBot11Adapter) => undefined,
[OB11MessageDataType.Location]: async (coreContext, obContext: NapCatOneBot11Adapter) => {
return SendMsgElementConstructor.location(coreContext);
},
[OB11MessageDataType.miniapp]: function (CoreContext: NapCatCore, obContext: NapCatOneBot11Adapter, sendMsg: never, context: MessageContext): Promise<SendMessageElement | undefined> {
throw new Error('Function not implemented.');
},
};
const handlers = <{
[Key in OB11MessageDataType]: (
coreContext: NapCatCore,
obContext: NapCatOneBot11Adapter,
sendMsg: OB11MessageData,
context: MessageContext,
) => Promise<SendMessageElement | undefined>
}>_handlers;
export default async function createSendElements(
CoreContext: NapCatCore,
obContext: NapCatOneBot11Adapter,
messageData: OB11MessageData[],
peer: Peer,
ignoreTypes: OB11MessageDataType[] = [],
) {
const deleteAfterSentFiles: string[] = [];
const callResultList: Array<Promise<SendMessageElement | undefined>> = [];
for (const sendMsg of messageData) {
if (ignoreTypes.includes(sendMsg.type)) {
continue;
}
const callResult = handlers[sendMsg.type](
CoreContext,
obContext,
sendMsg,
{ peer, deleteAfterSentFiles },
)?.catch(undefined);
callResultList.push(callResult);
}
const ret = await Promise.all(callResultList);
const sendElements: SendMessageElement[] = ret.filter(ele => ele) as SendMessageElement[];
return { sendElements, deleteAfterSentFiles };
}
export async function createSendElementsParallel(
CoreContext: NapCatCore,
obContext: NapCatOneBot11Adapter,
messageData: OB11MessageData[],
peer: Peer,
ignoreTypes: OB11MessageDataType[] = [],
) {
const deleteAfterSentFiles: string[] = [];
const sendElements = <SendMessageElement[]>(
await Promise.all(
messageData.map(async sendMsg => ignoreTypes.includes(sendMsg.type) ?
undefined :
handlers[sendMsg.type](CoreContext, obContext, sendMsg, { peer, deleteAfterSentFiles })),
).then(
results => results.filter(
element => element !== undefined,
),
)
);
return { sendElements, deleteAfterSentFiles };
}

View File

@@ -1,129 +0,0 @@
import { ChatType, ElementType, NapCatCore, Peer, RawMessage, SendMessageElement } from '@/core';
import { MessageUnique } from '@/common/utils/MessageUnique';
import { OB11MessageDataType, OB11MessageNode } from '@/onebot/types';
import createSendElements from './create-send-elements';
import { normalize, sendMsg } from '../SendMsg/index';
import { NapCatOneBot11Adapter } from '@/onebot';
async function cloneMsg(coreContext: NapCatCore, msg: RawMessage): Promise<RawMessage | undefined> {
const selfPeer = {
chatType: ChatType.friend,
peerUid: coreContext.selfInfo.uid,
};
const logger = coreContext.context.logger;
const NTQQMsgApi = coreContext.apis.MsgApi;
//logDebug('克隆的目标消息', msg);
const sendElements: SendMessageElement[] = [];
for (const element of msg.elements) {
sendElements.push(element as SendMessageElement);
}
if (sendElements.length === 0) {
logger.logDebug('需要clone的消息无法解析将会忽略掉', msg);
}
try {
const nodeMsg = await NTQQMsgApi.sendMsg(selfPeer, sendElements, true);
return nodeMsg;
} catch (e) {
logger.logError(e, '克隆转发消息失败,将忽略本条消息', msg);
}
}
export async function handleForwardNode(coreContext: NapCatCore, obContext: NapCatOneBot11Adapter, destPeer: Peer, messageNodes: OB11MessageNode[]): Promise<RawMessage | null> {
const NTQQMsgApi = coreContext.apis.MsgApi;
const selfPeer = {
chatType: ChatType.friend,
peerUid: coreContext.selfInfo.uid,
};
let nodeMsgIds: string[] = [];
const logger = coreContext.context.logger;
for (const messageNode of messageNodes) {
const nodeId = messageNode.data.id;
if (nodeId) {
//对Mgsid和OB11ID混用情况兜底
const nodeMsg = MessageUnique.getMsgIdAndPeerByShortId(parseInt(nodeId)) || MessageUnique.getPeerByMsgId(nodeId);
if (!nodeMsg) {
logger.logError('转发消息失败,未找到消息', nodeId);
continue;
}
nodeMsgIds.push(nodeMsg.MsgId);
} else {
// 自定义的消息
try {
const OB11Data = normalize(messageNode.data.content);
//筛选node消息
const isNodeMsg = OB11Data.filter(e => e.type === OB11MessageDataType.node).length;//找到子转发消息
if (isNodeMsg !== 0) {
if (isNodeMsg !== OB11Data.length) {
logger.logError('子消息中包含非node消息 跳过不合法部分');
continue;
}
const nodeMsg = await handleForwardNode(coreContext, obContext, selfPeer, OB11Data.filter(e => e.type === OB11MessageDataType.node));
if (nodeMsg) {
nodeMsgIds.push(nodeMsg.msgId);
MessageUnique.createMsg(selfPeer, nodeMsg.msgId);
}
//完成子卡片生成跳过后续
continue;
}
const { sendElements } = await createSendElements(coreContext, obContext, OB11Data, destPeer);
//拆分消息
const MixElement = sendElements.filter(element => element.elementType !== ElementType.FILE && element.elementType !== ElementType.VIDEO);
const SingleElement = sendElements.filter(element => element.elementType === ElementType.FILE || element.elementType === ElementType.VIDEO).map(e => [e]);
const AllElement: SendMessageElement[][] = [MixElement, ...SingleElement].filter(e => e !== undefined && e.length !== 0);
const MsgNodeList: Promise<RawMessage | undefined>[] = [];
for (const sendElementsSplitElement of AllElement) {
MsgNodeList.push(sendMsg(coreContext, selfPeer, sendElementsSplitElement, [], true).catch(e => new Promise((resolve, reject) => {
resolve(undefined);
})));
}
(await Promise.allSettled(MsgNodeList)).map((result) => {
if (result.status === 'fulfilled' && result.value) {
nodeMsgIds.push(result.value.msgId);
MessageUnique.createMsg(selfPeer, result.value.msgId);
}
});
} catch (e) {
logger.logDebug('生成转发消息节点失败', e);
}
}
}
const nodeMsgArray: Array<RawMessage> = [];
let srcPeer: Peer | undefined = undefined;
let needSendSelf = false;
//检测是否处于同一个Peer 不在同一个peer则全部消息由自身发送
for (const msgId of nodeMsgIds) {
const nodeMsgPeer = MessageUnique.getPeerByMsgId(msgId);
if (!nodeMsgPeer) {
logger.logError('转发消息失败,未找到消息', msgId);
continue;
}
const nodeMsg = (await NTQQMsgApi.getMsgsByMsgId(nodeMsgPeer.Peer, [msgId])).msgList[0];
srcPeer = srcPeer ?? { chatType: nodeMsg.chatType, peerUid: nodeMsg.peerUid };
if (srcPeer.peerUid !== nodeMsg.peerUid) {
needSendSelf = true;
}
nodeMsgArray.push(nodeMsg);
}
nodeMsgIds = nodeMsgArray.map(msg => msg.msgId);
let retMsgIds: string[] = [];
if (needSendSelf) {
for (const [index, msg] of nodeMsgArray.entries()) {
if (msg.peerUid === coreContext.selfInfo.uid) continue;
const ClonedMsg = await cloneMsg(coreContext, msg);
if (ClonedMsg) retMsgIds.push(ClonedMsg.msgId);
}
} else {
retMsgIds = nodeMsgIds;
}
if (nodeMsgIds.length === 0) throw Error('转发消息失败,生成节点为空');
try {
logger.logDebug('开发转发', srcPeer, destPeer, nodeMsgIds);
return await NTQQMsgApi.multiForwardMsg(srcPeer!, destPeer, nodeMsgIds);
} catch (e) {
logger.logError('forward failed', e);
return null;
}
}

View File

@@ -1,187 +0,0 @@
import {
OB11MessageData,
OB11MessageDataType,
OB11MessageMixType,
OB11MessageNode,
OB11PostSendMsg,
} from '@/onebot/types';
import { ActionName, BaseCheckResult } from '@/onebot/action/types';
import fs from 'node:fs';
import fsPromise from 'node:fs/promises';
import { decodeCQCode } from '@/onebot/helper/cqcode';
import createSendElements from './create-send-elements';
import { MessageUnique } from '@/common/utils/MessageUnique';
import { ChatType, ElementType, NapCatCore, Peer, SendMessageElement } from '@/core';
import BaseAction from '../../BaseAction';
import { handleForwardNode } from './handle-forward-node';
export interface ReturnDataType {
message_id: number;
}
export enum ContextMode {
Normal = 0,
Private = 1,
Group = 2
}
// Normalizes a mixed type (CQCode/a single segment/segment array) into a segment array.
export function normalize(message: OB11MessageMixType, autoEscape = false): OB11MessageData[] {
return typeof message === 'string' ? (
autoEscape ?
[{ type: OB11MessageDataType.text, data: { text: message } }] :
decodeCQCode(message)
) : Array.isArray(message) ? message : [message];
}
export { createSendElements };
export async function sendMsg(coreContext: NapCatCore, peer: Peer, sendElements: SendMessageElement[], deleteAfterSentFiles: string[], waitComplete = true) {
const NTQQMsgApi = coreContext.apis.MsgApi;
const logger = coreContext.context.logger;
if (!sendElements.length) {
throw ('消息体无法解析, 请检查是否发送了不支持的消息类型');
}
let totalSize = 0;
let timeout = 10000;
try {
for (const fileElement of sendElements) {
if (fileElement.elementType === ElementType.PTT) {
totalSize += fs.statSync(fileElement.pttElement.filePath).size;
}
if (fileElement.elementType === ElementType.FILE) {
totalSize += fs.statSync(fileElement.fileElement.filePath).size;
}
if (fileElement.elementType === ElementType.VIDEO) {
totalSize += fs.statSync(fileElement.videoElement.filePath).size;
}
if (fileElement.elementType === ElementType.PIC) {
totalSize += fs.statSync(fileElement.picElement.sourcePath).size;
}
}
//且 PredictTime ((totalSize / 1024 / 512) * 1000)不等于Nan
const PredictTime = totalSize / 1024 / 256 * 1000;
if (!Number.isNaN(PredictTime)) {
timeout += PredictTime;// 10S Basic Timeout + PredictTime( For File 512kb/s )
}
} catch (e) {
logger.logError('发送消息计算预计时间异常', e);
}
const returnMsg = await NTQQMsgApi.sendMsg(peer, sendElements, waitComplete, timeout);
try {
returnMsg!.id = MessageUnique.createMsg({
chatType: peer.chatType,
guildId: '',
peerUid: peer.peerUid,
}, returnMsg!.msgId);
} catch (e: any) {
logger.logDebug('发送消息id获取失败', e);
returnMsg!.id = 0;
}
deleteAfterSentFiles.map((f) => {
fsPromise.unlink(f).then().catch(e => logger.logError('发送消息删除文件失败', e));
});
return returnMsg;
}
async function createContext(coreContext: NapCatCore, payload: OB11PostSendMsg, contextMode: ContextMode): Promise<Peer> {
// This function determines the type of message by the existence of user_id / group_id,
// not message_type.
// This redundant design of Ob11 here should be blamed.
const NTQQGroupApi = coreContext.apis.GroupApi;
const NTQQFriendApi = coreContext.apis.FriendApi;
const NTQQUserApi = coreContext.apis.UserApi;
if ((contextMode === ContextMode.Group || contextMode === ContextMode.Normal) && payload.group_id) {
const group = (await NTQQGroupApi.getGroups()).find(e => e.groupCode == payload.group_id?.toString());
return {
chatType: ChatType.group,
peerUid: payload.group_id.toString(),
};
}
if ((contextMode === ContextMode.Private || contextMode === ContextMode.Normal) && payload.user_id) {
const Uid = await NTQQUserApi.getUidByUinV2(payload.user_id.toString());
const isBuddy = await NTQQFriendApi.isBuddy(Uid!);
//console.log("[调试代码] UIN:", payload.user_id, " UID:", Uid, " IsBuddy:", isBuddy);
return {
chatType: isBuddy ? ChatType.friend : ChatType.temp,
peerUid: Uid!,
guildId: payload.group_id?.toString() || '',
};
}
throw '请指定 group_id 或 user_id';
}
function getSpecialMsgNum(payload: OB11PostSendMsg, msgType: OB11MessageDataType): number {
if (Array.isArray(payload.message)) {
return payload.message.filter(msg => msg.type == msgType).length;
}
return 0;
}
export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
actionName = ActionName.SendMsg;
contextMode = ContextMode.Normal;
protected async check(payload: OB11PostSendMsg): Promise<BaseCheckResult> {
const NTQQGroupApi = this.CoreContext.apis.GroupApi;
const NTQQFriendApi = this.CoreContext.apis.FriendApi;
const NTQQUserApi = this.CoreContext.apis.UserApi;
const messages = normalize(payload.message);
const nodeElementLength = getSpecialMsgNum(payload, OB11MessageDataType.node);
if (nodeElementLength > 0 && nodeElementLength != messages.length) {
return {
valid: false,
message: '转发消息不能和普通消息混在一起发送,转发需要保证message只有type为node的元素',
};
}
// if (payload.message_type !== 'private' && payload.group_id && !(await getGroup(payload.group_id))) {
// return { valid: false, message: `群${payload.group_id}不存在` };
// }
if (payload.user_id && payload.message_type !== 'group') {
const uid = await NTQQUserApi.getUidByUinV2(payload.user_id.toString());
const isBuddy = await NTQQFriendApi.isBuddy(uid!);
// 此处有问题
if (!isBuddy) {
//return { valid: false, message: '异常消息' };
}
}
return { valid: true };
}
async _handle(payload: OB11PostSendMsg): Promise<{ message_id: number }> {
const peer = await createContext(this.CoreContext, payload, this.contextMode);
const messages = normalize(
payload.message,
typeof payload.auto_escape === 'string' ? payload.auto_escape === 'true' : !!payload.auto_escape
);
if (getSpecialMsgNum(payload, OB11MessageDataType.node)) {
const returnMsg = await handleForwardNode(this.CoreContext, this.OneBotContext, peer, messages as OB11MessageNode[]);
if (returnMsg) {
const msgShortId = MessageUnique.createMsg({
guildId: '',
peerUid: peer.peerUid,
chatType: peer.chatType,
}, returnMsg!.msgId);
return { message_id: msgShortId! };
} else {
throw Error('发送转发消息失败');
}
} else {
// if (getSpecialMsgNum(payload, OB11MessageDataType.music)) {
// const music: OB11MessageCustomMusic = messages[0] as OB11MessageCustomMusic;
// if (music) {
// }
// }
}
// log("send msg:", peer, sendElements)
const { sendElements, deleteAfterSentFiles } = await createSendElements(this.CoreContext, this.OneBotContext, messages, peer);
//console.log(peer, JSON.stringify(sendElements,null,2));
const returnMsg = await sendMsg(this.CoreContext, peer, sendElements, deleteAfterSentFiles);
return { message_id: returnMsg!.id! };
}
}
export default SendMsg;

View File

@@ -0,0 +1,12 @@
import BaseAction from '../BaseAction';
import { ActionName } from '../types';
export class GetCSRF extends BaseAction<any, any> {
actionName = ActionName.GetCSRF;
async _handle(payload: any) {
return {
token: "",
};
}
}

View File

@@ -1,5 +1,5 @@
import { OB11User } from '../../types';
import { OB11Constructor } from '../../helper/data';
import { OB11Constructor } from '@/onebot/helper/converter';
import BaseAction from '../BaseAction';
import { ActionName } from '../types';

View File

@@ -91,7 +91,7 @@ export enum ActionName {
GetOnlineClient = 'get_online_clients',
OCRImage = 'ocr_image',
IOCRImage = '.ocr_image',
SetSelfProfile = 'set_self_profile',
SetQQProfile = 'set_qq_profile',
CreateCollection = 'create_collection',
GetCollectionList = 'get_collection_list',
SetLongNick = 'set_self_longnick',
@@ -100,11 +100,14 @@ export enum ActionName {
GetRecentContact = 'get_recent_contact',
_MarkAllMsgAsRead = '_mark_all_as_read',
GetProfileLike = 'get_profile_like',
SetGroupHeader = 'set_group_head',
SetGroupPortrait = 'set_group_portrait',
FetchCustomFace = 'fetch_custom_face',
GOCQHTTP_UploadPrivateFile = 'upload_private_file',
TestApi01 = 'test_api_01',
FetchEmojiLike = 'fetch_emoji_like',
GetGuildProfile = "get_guild_service_profile",
SetModelShow = "_set_model_show"
SetModelShow = "_set_model_show",
SetInputStatus = "set_input_status",
GetCSRF = "get_csrf_token",
DelGroupNotice = "_del_group_notice",
}

View File

@@ -1,5 +1,5 @@
import { OB11User } from '../../types';
import { OB11Constructor } from '../../helper/data';
import { OB11Constructor } from '@/onebot/helper/converter';
import BaseAction from '../BaseAction';
import { ActionName } from '../types';
import { FromSchema, JSONSchema } from 'json-schema-to-ts';

View File

@@ -1,7 +1,6 @@
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import BaseAction from '../BaseAction';
import { ActionName } from '../types';
import { OB11Constructor } from '@/onebot/helper/data';
const SchemaData = {
type: 'object',
@@ -19,12 +18,12 @@ export default class GetRecentContact extends BaseAction<Payload, any> {
async _handle(payload: Payload) {
const NTQQUserApi = this.CoreContext.apis.UserApi;
const NTQQMsgApi = this.CoreContext.apis.MsgApi;
const ret = await NTQQUserApi.getRecentContactListSnapShot(parseInt((payload.count || 10).toString()));
const data = await Promise.all(ret.info.changedList.map(async (t) => {
const ret = await NTQQUserApi.getRecentContactListSnapShot(+(payload.count || 10));
return await Promise.all(ret.info.changedList.map(async (t) => {
const FastMsg = await NTQQMsgApi.getMsgsByMsgId({ chatType: t.chatType, peerUid: t.peerUid }, [t.msgId]);
if (FastMsg.msgList.length > 0) {
//扩展ret.info.changedList
const lastestMsg = await OB11Constructor.message(this.CoreContext, this.OneBotContext,FastMsg.msgList[0], 'array');
const lastestMsg = await this.OneBotContext.apiContext.MsgApi.parseMessage(FastMsg.msgList[0], 'array');
return {
lastestMsg: lastestMsg,
peerUin: t.peerUin,
@@ -48,6 +47,5 @@ export default class GetRecentContact extends BaseAction<Payload, any> {
peerName: t.peerName,
};
}));
return data;
}
}

View File

@@ -1,6 +1,7 @@
import { NapCatCore } from '@/core';
import { GrayTipElement, NapCatCore } from '@/core';
import { NapCatOneBot11Adapter } from '@/onebot';
import { OB11FriendPokeEvent } from '../event/notice/OB11PokeEvent';
export class OneBotFriendApi {
obContext: NapCatOneBot11Adapter;
@@ -10,4 +11,22 @@ export class OneBotFriendApi {
this.obContext = obContext;
this.coreContext = coreContext;
}
//使用前预先判断 busiId 1061
async parsePrivatePokeEvent(grayTipElement: GrayTipElement) {
const NTQQUserApi = this.coreContext.apis.UserApi;
const json = JSON.parse(grayTipElement.jsonGrayTipElement.jsonStr);
let pokedetail: any[] = json.items;
//筛选item带有uid的元素
pokedetail = pokedetail.filter(item => item.uid);
//console.log("[NapCat] 群拍一拍 群:", pokedetail, parseInt(msg.peerUid), " ", await NTQQUserApi.getUinByUid(pokedetail[0].uid), "拍了拍", await NTQQUserApi.getUinByUid(pokedetail[1].uid));
if (pokedetail.length == 2) {
return new OB11FriendPokeEvent(
this.coreContext,
parseInt((await NTQQUserApi.getUinByUidV2(pokedetail[0].uid))!),
parseInt((await NTQQUserApi.getUinByUidV2(pokedetail[1].uid))!),
pokedetail
);
}
return undefined;
}
}

View File

@@ -1,6 +1,11 @@
import { NapCatCore } from '@/core';
import { ChatType, GrayTipElement, NapCatCore } from '@/core';
import { NapCatOneBot11Adapter } from '@/onebot';
import { OB11GroupBanEvent } from '../event/notice/OB11GroupBanEvent';
import { OB11GroupIncreaseEvent } from '../event/notice/OB11GroupIncreaseEvent';
import { OB11GroupDecreaseEvent } from '../event/notice/OB11GroupDecreaseEvent';
import fastXmlParser from 'fast-xml-parser';
import { OB11GroupMsgEmojiLikeEvent } from '../event/notice/OB11MsgEmojiLikeEvent';
import { MessageUnique } from '@/common/utils/MessageUnique';
export class OneBotGroupApi {
obContext: NapCatOneBot11Adapter;
@@ -10,4 +15,133 @@ export class OneBotGroupApi {
this.obContext = obContext;
this.coreContext = coreContext;
}
async parseGroupBanEvent(GroupCode: string, grayTipElement: GrayTipElement) {
const groupElement = grayTipElement?.groupElement;
const NTQQGroupApi = this.coreContext.apis.GroupApi;
if (!groupElement?.shutUp) return undefined;
const memberUid = groupElement.shutUp!.member.uid;
const adminUid = groupElement.shutUp!.admin.uid;
let memberUin: string = '';
let duration = parseInt(groupElement.shutUp!.duration);
const subType: 'ban' | 'lift_ban' = duration > 0 ? 'ban' : 'lift_ban';
if (memberUid) {
memberUin = (await NTQQGroupApi.getGroupMember(GroupCode, memberUid))?.uin || '';
} else {
memberUin = '0'; // 0表示全员禁言
if (duration > 0) {
duration = -1;
}
}
const adminUin = (await NTQQGroupApi.getGroupMember(GroupCode, adminUid))?.uin;
if (memberUin && adminUin) {
return new OB11GroupBanEvent(
this.coreContext,
parseInt(GroupCode),
parseInt(memberUin),
parseInt(adminUin),
duration,
subType
);
}
return undefined;
}
async parseGroupIncreaseEvent(GroupCode: string, grayTipElement: GrayTipElement) {
this.coreContext.context.logger.logDebug('收到新人被邀请进群消息', grayTipElement);
const xmlElement = grayTipElement.xmlElement;
if (xmlElement?.content) {
const regex = /jp="(\d+)"/g;
const matches = [];
let match = null;
while ((match = regex.exec(xmlElement.content)) !== null) {
matches.push(match[1]);
}
// log("新人进群匹配到的QQ号", matches)
if (matches.length === 2) {
const [inviter, invitee] = matches;
return new OB11GroupIncreaseEvent(
this.coreContext,
parseInt(GroupCode),
parseInt(invitee),
parseInt(inviter),
'invite'
);
}
}
return undefined;
}
async parseGroupMemberIncreaseEvent(GroupCode: string, grayTipElement: GrayTipElement) {
const NTQQGroupApi = this.coreContext.apis.GroupApi;
const groupElement = grayTipElement?.groupElement;
if (!groupElement) return undefined;
const member = await NTQQGroupApi.getGroupMemberV2(GroupCode, groupElement.memberUid);
const memberUin = member?.uin;
const adminMember = await NTQQGroupApi.getGroupMember(GroupCode, groupElement.adminUid);
if (memberUin) {
const operatorUin = adminMember?.uin || memberUin;
return new OB11GroupIncreaseEvent(
this.coreContext,
parseInt(GroupCode),
parseInt(memberUin),
parseInt(operatorUin)
);
}
return undefined;
}
async parseGroupKickEvent(GroupCode: string, grayTipElement: GrayTipElement) {
const NTQQGroupApi = this.coreContext.apis.GroupApi;
const NTQQUserApi = this.coreContext.apis.UserApi;
const groupElement = grayTipElement?.groupElement;
if (!groupElement) return undefined;
const adminUin = (await NTQQGroupApi.getGroupMember(GroupCode, groupElement.adminUid))?.uin || (await NTQQUserApi.getUidByUinV2(groupElement.adminUid));
if (adminUin) {
return new OB11GroupDecreaseEvent(
this.coreContext,
parseInt(GroupCode),
parseInt(this.coreContext.selfInfo.uin),
parseInt(adminUin),
'kick_me'
);
}
return undefined;
}
async parseGroupEmjioLikeEvent(GroupCode: string, grayTipElement: GrayTipElement) {
const NTQQMsgApi = this.coreContext.apis.MsgApi;
const emojiLikeData = new fastXmlParser.XMLParser({
ignoreAttributes: false,
attributeNamePrefix: '',
}).parse(grayTipElement.xmlElement.content);
this.coreContext.context.logger.logDebug('收到表情回应我的消息', emojiLikeData);
try {
const senderUin = emojiLikeData.gtip.qq.jp;
const msgSeq = emojiLikeData.gtip.url.msgseq;
const emojiId = emojiLikeData.gtip.face.id;
const peer = {
chatType: ChatType.KCHATTYPEGROUP,
guildId: '',
peerUid: GroupCode
};
const replyMsgList = (await NTQQMsgApi.getMsgExBySeq(peer, msgSeq)).msgList;
if (replyMsgList.length < 1) {
return;
}
const replyMsg = replyMsgList.filter(e => e.msgSeq == msgSeq).sort((a, b) => parseInt(a.msgTime) - parseInt(b.msgTime))[0];
//console.log("表情回应消息长度检测", msgSeq, replyMsg.elements);
if (!replyMsg) throw new Error('找不到回应消息');
return new OB11GroupMsgEmojiLikeEvent(
this.coreContext,
parseInt(GroupCode),
parseInt(senderUin),
MessageUnique.getShortIdByMsgId(replyMsg.msgId)!,
[{
emoji_id: emojiId,
count: 1,
}],
);
} catch (e: any) {
this.coreContext.context.logger.logError('解析表情回应消息失败', e.stack);
}
return undefined;
}
}

View File

@@ -1,3 +1,4 @@
export * from './friend';
export * from './group';
export * from './user';
export * from './msg';

788
src/onebot/api/msg.ts Normal file
View File

@@ -0,0 +1,788 @@
import { UUIDConverter } from '@/common/utils/helper';
import { MessageUnique } from '@/common/utils/MessageUnique';
import {
AtType,
ChatType,
CustomMusicSignPostData,
ElementType,
FaceIndex,
FaceType,
IdMusicSignPostData,
MessageElement,
NapCatCore,
Peer,
RawMessage,
SendMessageElement, SendTextElement,
} from '@/core';
import faceConfig from '@/core/external/face_config.json';
import {
NapCatOneBot11Adapter,
OB11Message,
OB11MessageData,
OB11MessageDataType,
OB11MessageFileBase,
OB11MessageForward
} from '@/onebot';
import { OB11Constructor } from '../helper';
import { EventType } from '@/onebot/event/OB11BaseEvent';
import { encodeCQCode } from '@/onebot/helper/cqcode';
import { uri2local } from '@/common/utils/file';
import { RequestUtil } from '@/common/utils/request';
type RawToOb11Converters = {
[Key in keyof MessageElement as Key extends `${string}Element` ? Key : never]: (
element: Exclude<MessageElement[Key], null | undefined>,
msg: RawMessage,
elementWrapper: MessageElement
) => PromiseLike<OB11MessageData | null>
}
type Ob11ToRawConverters = {
[Key in OB11MessageDataType]: (
sendMsg: Extract<OB11MessageData, { type: Key }>,
context: MessageContext,
) => Promise<SendMessageElement | undefined>
}
export type MessageContext = {
deleteAfterSentFiles: string[],
peer: Peer
}
function keyCanBeParsed(key: string, parser: RawToOb11Converters): key is keyof RawToOb11Converters {
return key in parser;
}
export class OneBotMsgApi {
obContext: NapCatOneBot11Adapter;
coreContext: NapCatCore;
rawToOb11Converters: RawToOb11Converters = {
textElement: async element => {
if (element.atType === AtType.notAt) {
let text = element.content;
if (!text.trim()) {
return null;
}
// 兼容 9.7.x 换行符
if (text.indexOf('\n') === -1 && text.indexOf('\r\n') === -1) {
text = text.replace(/\r/g, '\n');
}
return {
type: OB11MessageDataType.text,
data: { text }
};
} else {
let qq: string = 'all';
if (element.atType !== AtType.atAll) {
const { atNtUid, /* content */ } = element;
let atQQ = element.atUid;
if (!atQQ || atQQ === '0') {
atQQ = await this.coreContext.apis.UserApi.getUinByUidV2(atNtUid);
}
if (atQQ) {
qq = atQQ as `${number}`;
}
}
return {
type: OB11MessageDataType.at,
data: {
qq: qq,
// name: content.slice(1);
},
};
}
},
picElement: async (element, msg) => {
try {
return {
type: OB11MessageDataType.image,
data: {
file: element.fileName,
sub_type: element.picSubType,
file_id: UUIDConverter.encode(msg.peerUin, msg.msgId),
url: await this.coreContext.apis.FileApi.getImageUrl(element),
file_size: element.fileSize,
},
};
} catch (e: any) {
this.coreContext.context.logger.logError('获取图片url失败', e.stack);
return null;
}
},
fileElement: async (element, msg, elementWrapper) => {
await this.coreContext.apis.FileApi.addFileCache(
{
peerUid: msg.peerUid,
chatType: msg.chatType,
guildId: '',
},
msg.msgId,
msg.msgSeq,
msg.senderUid,
elementWrapper.elementId,
elementWrapper.elementType.toString(),
element.fileSize,
element.fileName,
);
return {
type: OB11MessageDataType.file,
data: {
file: element.fileName,
path: element.filePath,
url: element.filePath,
file_id: UUIDConverter.encode(msg.peerUin, msg.msgId),
file_size: element.fileSize,
}
};
},
faceElement: async element => {
const faceIndex = element.faceIndex;
if (faceIndex === FaceIndex.dice) {
return {
type: OB11MessageDataType.dice,
data: {
result: element.resultId!,
}
};
} else if (faceIndex === FaceIndex.RPS) {
return {
type: OB11MessageDataType.RPS,
data: {
result: element.resultId!,
}
};
} else {
return {
type: OB11MessageDataType.face,
data: {
id: element.faceIndex.toString()
}
};
}
},
marketFaceElement: async (_, msg, elementWrapper) => {
await this.coreContext.apis.FileApi.addFileCache(
{
peerUid: msg.peerUid,
chatType: msg.chatType,
guildId: '',
},
msg.msgId,
msg.msgSeq,
msg.senderUid,
elementWrapper.elementId,
elementWrapper.elementType.toString(),
'0',
'marketface',
);
return {
type: OB11MessageDataType.image,
data: {
file: 'marketface',
file_id: UUIDConverter.encode(msg.peerUin, msg.msgId),
path: elementWrapper.elementId,
url: elementWrapper.elementId,
}
};
},
replyElement: async (element, msg) => {
const NTQQMsgApi = this.coreContext.apis.MsgApi;
const records = msg.records.find(msgRecord => msgRecord.msgId === element?.sourceMsgIdInRecords);
const peer = {
chatType: msg.chatType,
peerUid: msg.peerUid,
guildId: '',
};
if (!records) {
this.coreContext.context.logger.logError('获取不到引用的消息', element.replayMsgSeq);
return null;
}
let replyMsg: RawMessage | undefined;
// Attempt 1
replyMsg = (await NTQQMsgApi.getMsgsBySeqAndCount({
peerUid: msg.peerUid,
guildId: '',
chatType: msg.chatType,
}, element.replayMsgSeq, 1, true, true))
.msgList
.find(msg => msg.msgRandom === records.msgRandom);
if (!replyMsg || records.msgRandom !== replyMsg.msgRandom) {
// Attempt 2
replyMsg = (await NTQQMsgApi.getSingleMsg(peer, element.replayMsgSeq)).msgList[0];
if ((!replyMsg || records.msgRandom !== replyMsg.msgRandom) && msg.peerUin !== '284840486') {
// Attempt 3
const replyMsgList = (await NTQQMsgApi.getMsgExBySeq(peer, records.msgSeq)).msgList;
if (replyMsgList.length < 1) {
this.coreContext.context.logger.logError('回复消息消息验证失败', element.replayMsgSeq);
return null;
}
replyMsg = replyMsgList.filter(e => e.msgSeq == records.msgSeq)
.sort((a, b) => parseInt(a.msgTime) - parseInt(b.msgTime))[0];
}
}
return {
type: OB11MessageDataType.reply,
data: {
id: MessageUnique.createMsg({
peerUid: msg.peerUid,
guildId: '',
chatType: msg.chatType,
}, replyMsg.msgId).toString()
}
};
},
videoElement: async (element, msg, elementWrapper) => {
const NTQQFileApi = this.coreContext.apis.FileApi;
//读取视频链接并兜底
let videoUrlWrappers: Awaited<ReturnType<typeof NTQQFileApi.getVideoUrl>> | undefined;
if (msg.peerUin === '284840486') {
//TODO: 合并消息内部 应该进行特殊处理 可能需要重写peer 待测试与研究 Mlikiowa Tagged
}
try {
videoUrlWrappers = await NTQQFileApi.getVideoUrl({
chatType: msg.chatType,
peerUid: msg.peerUid,
guildId: '0',
}, msg.msgId, elementWrapper.elementId);
} catch (error) {
this.coreContext.context.logger.logWarn('获取视频 URL 失败');
}
//读取在线URL
let videoDownUrl: string | undefined;
if (videoUrlWrappers) {
const videoDownUrlTemp = videoUrlWrappers.find((urlWrapper) => {
return !!(urlWrapper.url);
});
if (videoDownUrlTemp) {
videoDownUrl = videoDownUrlTemp.url;
}
}
//开始兜底
if (!videoDownUrl) {
videoDownUrl = element.filePath;
}
await NTQQFileApi.addFileCache(
{
peerUid: msg.peerUid,
chatType: msg.chatType,
guildId: '',
},
msg.msgId,
msg.msgSeq,
msg.senderUid,
elementWrapper.elementId,
elementWrapper.elementType.toString(),
element.fileSize ?? '0',
element.fileName,
);
return {
type: OB11MessageDataType.video,
data: {
file: element.fileName,
path: videoDownUrl,
url: videoDownUrl,
file_id: UUIDConverter.encode(msg.peerUin, msg.msgId),
file_size: element.fileSize,
}
};
},
pttElement: async (element, msg, elementWrapper) => {
await this.coreContext.apis.FileApi.addFileCache(
{
peerUid: msg.peerUid,
chatType: msg.chatType,
guildId: '',
},
msg.msgId,
msg.msgSeq,
msg.senderUid,
elementWrapper.elementId,
elementWrapper.elementType.toString(),
element.fileSize || '0',
element.fileUuid || '',
);
return {
type: OB11MessageDataType.voice,
data: {
file: element.fileName,
path: element.filePath,
file_id: UUIDConverter.encode(msg.peerUin, msg.msgId),
file_size: element.fileSize,
}
};
},
multiForwardMsgElement: async (_, msg) => {
const NTQQMsgApi = this.coreContext.apis.MsgApi;
const message_data: OB11MessageForward = {
data: {} as any,
type: OB11MessageDataType.forward,
};
message_data.data.id = msg.msgId;
const parentMsgPeer = msg.parentMsgPeer ?? {
chatType: msg.chatType,
guildId: '',
peerUid: msg.peerUid,
};
//判断是否在合并消息内
msg.parentMsgIdList = msg.parentMsgIdList ?? [];
//首次列表不存在则开始创建
msg.parentMsgIdList.push(msg.msgId);
//let parentMsgId = msg.parentMsgIdList[msg.parentMsgIdList.length - 2 < 0 ? 0 : msg.parentMsgIdList.length - 2];
//加入自身MsgId
const multiMsgs = (await NTQQMsgApi.getMultiMsg(parentMsgPeer, msg.parentMsgIdList[0], msg.msgId))?.msgList;
//拉取下级消息
if (!multiMsgs) return null;
//拉取失败则跳过
return {
type: OB11MessageDataType.forward,
data: {
id: msg.msgId,
content: (await Promise.all(multiMsgs.map(
async multiMsgItem => {
multiMsgItem.parentMsgPeer = parentMsgPeer;
multiMsgItem.parentMsgIdList = msg.parentMsgIdList;
multiMsgItem.id = MessageUnique.createMsg(parentMsgPeer, multiMsgItem.msgId); //该ID仅用查看 无法调用
return await this.parseMessage(multiMsgItem);
}
))).filter(item => item !== undefined),
}
};
},
arkElement: async (element) => {
return {
type: OB11MessageDataType.json,
data: {
data: element.bytesData
}
};
},
markdownElement: async (element) => {
return {
type: OB11MessageDataType.markdown,
data: {
content: element.content
}
};
}
};
ob11ToRawConverters: Ob11ToRawConverters = {
[OB11MessageDataType.text]: async ({ data: { text } }) => ({
elementType: ElementType.TEXT,
elementId: '',
textElement: {
content: text,
atType: AtType.notAt,
atUid: '',
atTinyId: '',
atNtUid: '',
},
}),
[OB11MessageDataType.at]: async ({ data: { qq: atQQ } }, context) => {
function at(atUid: string, atNtUid: string, atType: AtType, atName: string): SendTextElement {
return {
elementType: ElementType.TEXT,
elementId: '',
textElement: {
content: `@${atName}`,
atType,
atUid,
atTinyId: '',
atNtUid,
},
};
}
if (!context.peer || context.peer.chatType == ChatType.KCHATTYPEC2C) return undefined;
if (atQQ === 'all') return at(atQQ, atQQ, AtType.atAll, '全体成员');
const NTQQGroupApi = this.coreContext.apis.GroupApi;
const NTQQUserApi = this.coreContext.apis.UserApi;
const atMember = await NTQQGroupApi.getGroupMember(context.peer.peerUid, atQQ);
if (atMember) {
return at(atQQ, atMember.uid, AtType.atUser, atMember.nick || atMember.cardName);
}
const uid = await NTQQUserApi.getUidByUinV2(`${atQQ}`);
if (!uid) throw new Error('Get Uid Error');
const info = await NTQQUserApi.getUserDetailInfo(uid);
return at(atQQ, uid, AtType.atUser, info.nick || '');
},
[OB11MessageDataType.reply]: async ({ data: { id } }) => {
const replyMsgM = MessageUnique.getMsgIdAndPeerByShortId(parseInt(id));
if (!replyMsgM) {
this.coreContext.context.logger.logWarn('回复消息不存在', id);
return undefined;
}
const NTQQMsgApi = this.coreContext.apis.MsgApi;
const replyMsg = (await NTQQMsgApi.getMsgsByMsgId(
replyMsgM.Peer, [replyMsgM.MsgId])).msgList[0];
return replyMsg ?
{
elementType: ElementType.REPLY,
elementId: '',
replyElement: {
replayMsgSeq: replyMsg.msgSeq, // raw.msgSeq
replayMsgId: replyMsg.msgId, // raw.msgId
senderUin: replyMsg.senderUin,
senderUinStr: replyMsg.senderUin,
},
} :
undefined;
},
[OB11MessageDataType.face]: async ({ data: { id } }) => {
let parsedFaceId = parseInt(id);
// 从face_config.json中获取表情名称
const sysFaces = faceConfig.sysface;
const face: any = sysFaces.find((systemFace) => systemFace.QSid === parsedFaceId.toString());
parsedFaceId = parseInt(parsedFaceId.toString());
let faceType = 1;
if (parsedFaceId >= 222) {
faceType = 2;
}
if (face.AniStickerType) {
faceType = 3;
}
return {
elementType: ElementType.FACE,
elementId: '',
faceElement: {
faceIndex: parsedFaceId,
faceType,
faceText: face.QDes,
stickerId: face.AniStickerId,
stickerType: face.AniStickerType,
packId: face.AniStickerPackId,
sourceType: 1,
},
};
},
[OB11MessageDataType.mface]: async ({
data: {
emoji_package_id, emoji_id, key, summary,
},
}) => ({
elementType: ElementType.MFACE,
marketFaceElement: {
emojiPackageId: emoji_package_id,
emojiId: emoji_id,
key,
faceName: summary || '[商城表情]',
},
}),
// File service
[OB11MessageDataType.image]: async (sendMsg, context) => {
const sendPicElement = await this.coreContext.apis.FileApi.createValidSendPicElement(
(await this.handleOb11FileLikeMessage(sendMsg, context)).path,
sendMsg.data.summary,
sendMsg.data.sub_type,
);
context.deleteAfterSentFiles.push(sendPicElement.picElement.sourcePath);
return sendPicElement;
},
[OB11MessageDataType.file]: async (sendMsg, context) => {
const { path, fileName } = await this.handleOb11FileLikeMessage(sendMsg, context);
return await this.coreContext.apis.FileApi.createValidSendFileElement(path, fileName);
},
[OB11MessageDataType.video]: async (sendMsg, context) => {
const { path, fileName } = await this.handleOb11FileLikeMessage(sendMsg, context);
let thumb = sendMsg.data.thumb;
if (thumb) {
const uri2LocalRes = await uri2local(this.coreContext.NapCatTempPath, thumb);
if (uri2LocalRes.success) thumb = uri2LocalRes.path;
}
const videoEle = await this.coreContext.apis.FileApi.createValidSendVideoElement(path, fileName, thumb);
context.deleteAfterSentFiles.push(videoEle.videoElement.filePath);
return videoEle;
},
[OB11MessageDataType.voice]: async (sendMsg, context) =>
this.coreContext.apis.FileApi.createValidSendPttElement(
(await this.handleOb11FileLikeMessage(sendMsg, context)).path),
[OB11MessageDataType.json]: async ({ data: { data } }) => ({
elementType: ElementType.ARK,
elementId: '',
arkElement: {
bytesData: typeof data === 'string' ? data : JSON.stringify(data),
linkInfo: null,
subElementType: null,
},
}),
[OB11MessageDataType.dice]: async () => ({
elementType: ElementType.FACE,
elementId: '',
faceElement: {
faceIndex: FaceIndex.dice,
faceType: FaceType.dice,
'faceText': '[骰子]',
'packId': '1',
'stickerId': '33',
'sourceType': 1,
'stickerType': 2,
// resultId: resultId.toString(),
'surpriseId': '',
// "randomType": 1,
},
}),
[OB11MessageDataType.RPS]: async () => ({
elementType: ElementType.FACE,
elementId: '',
faceElement: {
'faceIndex': FaceIndex.RPS,
'faceText': '[包剪锤]',
'faceType': 3,
'packId': '1',
'stickerId': '34',
'sourceType': 1,
'stickerType': 2,
// 'resultId': resultId.toString(),
'surpriseId': '',
// "randomType": 1,
},
}),
// Need signing
[OB11MessageDataType.markdown]: async ({ data: { content } }) => ({
elementType: ElementType.MARKDOWN,
elementId: '',
markdownElement: { content },
}),
[OB11MessageDataType.music]: async ({ data }, context) => {
// 保留, 直到...找到更好的解决方案
if (data.type === 'custom') {
if (!data.url) {
this.coreContext.context.logger.logError('自定义音卡缺少参数url');
return undefined;
}
if (!data.audio) {
this.coreContext.context.logger.logError('自定义音卡缺少参数audio');
return undefined;
}
if (!data.title) {
this.coreContext.context.logger.logError('自定义音卡缺少参数title');
return undefined;
}
} else {
if (!['qq', '163'].includes(data.type)) {
this.coreContext.context.logger.logError('音乐卡片type错误, 只支持qq、163、custom当前type:', data.type);
return undefined;
}
if (!data.id) {
this.coreContext.context.logger.logError('音乐卡片缺少参数id');
return undefined;
}
}
let postData: IdMusicSignPostData | CustomMusicSignPostData;
if (data.type === 'custom' && data.content) {
const { content, ...others } = data;
postData = { singer: content, ...others };
} else {
postData = data;
}
const signUrl = this.obContext.configLoader.configData.musicSignUrl;
if (!signUrl) {
throw Error('音乐消息签名地址未配置');
}
try {
const musicJson = await RequestUtil.HttpGetJson<any>(signUrl, 'POST', postData);
return this.ob11ToRawConverters.json(musicJson, context);
} catch (e) {
this.coreContext.context.logger.logError('生成音乐消息失败', e);
}
},
[OB11MessageDataType.node]: async () => undefined,
[OB11MessageDataType.forward]: async () => undefined,
[OB11MessageDataType.xml]: async () => undefined,
[OB11MessageDataType.poke]: async () => undefined,
[OB11MessageDataType.Location]: async () => ({
elementType: ElementType.SHARELOCATION,
elementId: '',
shareLocationElement: {
text: '测试',
ext: '',
},
}),
[OB11MessageDataType.miniapp]: async () => undefined,
};
constructor(obContext: NapCatOneBot11Adapter, coreContext: NapCatCore) {
this.obContext = obContext;
this.coreContext = coreContext;
}
async parseMessage(
msg: RawMessage,
messagePostFormat: string = this.obContext.configLoader.configData.messagePostFormat,
) {
if (msg.senderUin == '0' || msg.senderUin == '') return;
if (msg.peerUin == '0' || msg.peerUin == '') return;
//跳过空消息
const NTQQGroupApi = this.coreContext.apis.GroupApi;
const NTQQUserApi = this.coreContext.apis.UserApi;
const NTQQMsgApi = this.coreContext.apis.MsgApi;
const resMsg: OB11Message = {
self_id: parseInt(this.coreContext.selfInfo.uin),
user_id: parseInt(msg.senderUin!),
time: parseInt(msg.msgTime) || Date.now(),
message_id: msg.id!,
message_seq: msg.id!,
real_id: msg.id!,
message_type: msg.chatType == ChatType.KCHATTYPEGROUP ? 'group' : 'private',
sender: {
user_id: parseInt(msg.senderUin || '0'),
nickname: msg.sendNickName,
card: msg.sendMemberName || '',
},
raw_message: '',
font: 14,
sub_type: 'friend',
message: messagePostFormat === 'string' ? '' : [],
message_format: messagePostFormat === 'string' ? 'string' : 'array',
post_type: this.coreContext.selfInfo.uin == msg.senderUin ? EventType.MESSAGE_SENT : EventType.MESSAGE,
};
if (msg.chatType == ChatType.KCHATTYPEGROUP) {
resMsg.sub_type = 'normal'; // 这里go-cqhttp是group而onebot11标准是normal, 蛋疼
resMsg.group_id = parseInt(msg.peerUin);
let member = await NTQQGroupApi.getGroupMember(msg.peerUin, msg.senderUin);
if (!member) member = await NTQQGroupApi.getGroupMember(msg.peerUin, msg.senderUin);
if (member) {
resMsg.sender.role = OB11Constructor.groupMemberRole(member.role);
resMsg.sender.nickname = member.nick;
}
} else if (msg.chatType == ChatType.KCHATTYPEC2C) {
resMsg.sub_type = 'friend';
resMsg.sender.nickname = (await NTQQUserApi.getUserDetailInfo(msg.senderUid)).nick;
} else if (msg.chatType == ChatType.KCHATTYPETEMPC2CFROMGROUP) {
resMsg.sub_type = 'group';
const ret = await NTQQMsgApi.getTempChatInfo(ChatType.KCHATTYPETEMPC2CFROMGROUP, msg.senderUid);
if (ret.result === 0) {
resMsg.group_id = parseInt(ret.tmpChatInfo!.groupCode);
resMsg.sender.nickname = ret.tmpChatInfo!.fromNick;
} else {
resMsg.group_id = 284840486; //兜底数据
resMsg.sender.nickname = '临时会话';
}
}
const msgSegments = (await Promise.all(msg.elements.map(
async (element) => {
for (const key in element) {
if (keyCanBeParsed(key, this.rawToOb11Converters) && element[key]) {
return await this.rawToOb11Converters[key]?.(
// eslint-disable-next-line
// @ts-ignore
element[key],
msg,
element
);
}
}
}
))).filter(entry => !!entry);
const msgAsCQCode = msgSegments.map(msg => encodeCQCode(msg)).join('').trim();
if (messagePostFormat === 'string') {
resMsg.message = msgAsCQCode;
} else {
resMsg.message = msgSegments;
resMsg.raw_message = msgAsCQCode;
}
return resMsg;
}
async createSendElements(
messageData: OB11MessageData[],
peer: Peer,
ignoreTypes: OB11MessageDataType[] = [],
) {
const deleteAfterSentFiles: string[] = [];
const callResultList: Array<Promise<SendMessageElement | undefined>> = [];
for (const sendMsg of messageData) {
if (ignoreTypes.includes(sendMsg.type)) {
continue;
}
const callResult = this.ob11ToRawConverters[sendMsg.type](
// eslint-disable-next-line
// @ts-ignore
sendMsg,
{ peer, deleteAfterSentFiles },
)?.catch(undefined);
callResultList.push(callResult);
}
const ret = await Promise.all(callResultList);
const sendElements: SendMessageElement[] = ret.filter(ele => !!ele);
return { sendElements, deleteAfterSentFiles };
}
private async handleOb11FileLikeMessage(
{ data: inputdata }: OB11MessageFileBase,
{ deleteAfterSentFiles }: MessageContext,
) {
const isBlankUrl = !inputdata.url || inputdata.url === '';
const isBlankFile = !inputdata.file || inputdata.file === '';
if (isBlankUrl && isBlankFile) {
this.coreContext.context.logger.logError('文件消息缺少参数', inputdata);
throw Error('文件消息缺少参数');
}
const fileOrUrl = (isBlankUrl ? inputdata.file : inputdata.url) ?? "";
const {
path,
isLocal,
fileName,
errMsg,
success,
} = (await uri2local(this.coreContext.NapCatTempPath, fileOrUrl));
if (!success) {
this.coreContext.context.logger.logError('文件下载失败', errMsg);
throw Error('文件下载失败' + errMsg);
}
if (!isLocal) { // 只删除http和base64转过来的文件
deleteAfterSentFiles.push(path);
}
return { path, fileName: inputdata.name ?? fileName };
}
}

View File

@@ -0,0 +1,113 @@
import { calcQQLevel } from '@/common/utils/helper';
import { SelfInfo, FriendV2, Friend, Sex, GroupMember, User, Group } from '@/core';
import { OB11User, OB11GroupMemberRole, OB11UserSex, OB11GroupMember, OB11Group } from '../types';
export class OB11Constructor {
static selfInfo(selfInfo: SelfInfo): OB11User {
return {
user_id: parseInt(selfInfo.uin),
nickname: selfInfo.nick,
};
}
static friendsV2(friends: FriendV2[]): OB11User[] {
const data: OB11User[] = [];
friends.forEach(friend => {
const sexValue = this.sex(friend.baseInfo.sex!);
data.push({
...friend.baseInfo,
...friend.coreInfo,
user_id: parseInt(friend.coreInfo.uin),
nickname: friend.coreInfo.nick,
remark: friend.coreInfo.nick,
sex: sexValue,
level: 0,
});
});
return data;
}
static friends(friends: Friend[]): OB11User[] {
const data: OB11User[] = [];
friends.forEach(friend => {
const sexValue = this.sex(friend.sex!);
data.push({
user_id: parseInt(friend.uin),
nickname: friend.nick,
remark: friend.remark,
sex: sexValue,
level: 0,
});
});
return data;
}
static groupMemberRole(role: number): OB11GroupMemberRole | undefined {
return {
4: OB11GroupMemberRole.owner,
3: OB11GroupMemberRole.admin,
2: OB11GroupMemberRole.member,
}[role];
}
static sex(sex: Sex): OB11UserSex {
const sexMap = {
[Sex.male]: OB11UserSex.male,
[Sex.female]: OB11UserSex.female,
[Sex.unknown]: OB11UserSex.unknown,
};
return sexMap[sex] || OB11UserSex.unknown;
}
static groupMember(group_id: string, member: GroupMember): OB11GroupMember {
return {
group_id: parseInt(group_id),
user_id: parseInt(member.uin),
nickname: member.nick,
card: member.cardName,
sex: OB11Constructor.sex(member.sex!),
age: member.age ?? 0,
area: '',
level: '0',
qq_level: member.qqLevel && calcQQLevel(member.qqLevel) || 0,
join_time: 0, // 暂时没法获取
last_sent_time: 0, // 暂时没法获取
title_expire_time: 0,
unfriendly: false,
card_changeable: true,
is_robot: member.isRobot,
shut_up_timestamp: member.shutUpTime,
role: OB11Constructor.groupMemberRole(member.role),
title: member.memberSpecialTitle || '',
};
}
static stranger(user: User): OB11User {
//logDebug('construct ob11 stranger', user);
return {
...user,
user_id: parseInt(user.uin),
nickname: user.nick,
sex: OB11Constructor.sex(user.sex!),
age: 0,
qid: user.qid,
login_days: 0,
level: user.qqLevel && calcQQLevel(user.qqLevel) || 0,
};
}
static group(group: Group): OB11Group {
return {
group_id: parseInt(group.groupCode),
group_name: group.groupName,
member_count: group.memberCount,
max_member_count: group.maxMember,
};
}
static groups(groups: Group[]): OB11Group[] {
return groups.map(OB11Constructor.group);
}
}

View File

@@ -80,7 +80,3 @@ export function encodeCQCode(data: OB11MessageData) {
result += ']';
return result;
}
// const result = parseCQCode("[CQ:at,qq=114514]早上好啊[CQ:image,file=http://baidu.com/1.jpg,type=show,id=40004]")
// const result = parseCQCode("好好好")
// console.log(JSON.stringify(result))

View File

@@ -1,761 +0,0 @@
import fastXmlParser from 'fast-xml-parser';
import {
OB11Group,
OB11GroupMember,
OB11GroupMemberRole,
OB11Message,
OB11MessageData,
OB11MessageDataType,
OB11User,
OB11UserSex,
} from '../types';
import {
AtType,
ChatType,
FaceIndex,
Friend,
FriendV2,
Group,
GroupMember,
NTGrayTipElementSubTypeV2,
Peer,
RawMessage,
SelfInfo,
Sex,
TipGroupElementType,
User,
VideoElement,
} from '@/core/entities';
import { EventType } from '../event/OB11BaseEvent';
import { encodeCQCode } from './cqcode';
import { OB11GroupIncreaseEvent } from '../event/notice/OB11GroupIncreaseEvent';
import { OB11GroupBanEvent } from '../event/notice/OB11GroupBanEvent';
import { OB11GroupUploadNoticeEvent } from '../event/notice/OB11GroupUploadNoticeEvent';
import { OB11GroupNoticeEvent } from '../event/notice/OB11GroupNoticeEvent';
import { calcQQLevel, sleep, UUIDConverter } from '@/common/utils/helper';
import { OB11GroupTitleEvent } from '../event/notice/OB11GroupTitleEvent';
import { OB11GroupDecreaseEvent } from '../event/notice/OB11GroupDecreaseEvent';
import { OB11GroupMsgEmojiLikeEvent } from '@/onebot/event/notice/OB11MsgEmojiLikeEvent';
import { OB11FriendPokeEvent, OB11GroupPokeEvent } from '../event/notice/OB11PokeEvent';
import { OB11FriendAddNoticeEvent } from '../event/notice/OB11FriendAddNoticeEvent';
import { OB11BaseNoticeEvent } from '../event/notice/OB11BaseNoticeEvent';
import { OB11GroupEssenceEvent } from '../event/notice/OB11GroupEssenceEvent';
import { MessageUnique } from '@/common/utils/MessageUnique';
import { NapCatCore } from '@/core';
import { NapCatOneBot11Adapter } from '..';
export class OB11Constructor {
static async message(
core: NapCatCore,
obcore: NapCatOneBot11Adapter,
msg: RawMessage,
messagePostFormat: string = obcore.configLoader.configData.messagePostFormat
): Promise<OB11Message | undefined> {
if (msg.senderUin == "0" || msg.senderUin == "") return;
if (msg.peerUin == "0" || msg.peerUin == "") return;
//跳过空消息
const NTQQGroupApi = core.apis.GroupApi;
const NTQQUserApi = core.apis.UserApi;
const NTQQFileApi = core.apis.FileApi;
const NTQQMsgApi = core.apis.MsgApi;
const logger = core.context.logger;
const resMsg: OB11Message = {
self_id: parseInt(core.selfInfo.uin),
user_id: parseInt(msg.senderUin!),
time: parseInt(msg.msgTime) || Date.now(),
message_id: msg.id!,
message_seq: msg.id!,
real_id: msg.id!,
message_type: msg.chatType == ChatType.group ? 'group' : 'private',
sender: {
user_id: parseInt(msg.senderUin || '0'),
nickname: msg.sendNickName,
card: msg.sendMemberName || '',
},
raw_message: '',
font: 14,
sub_type: 'friend',
message: messagePostFormat === 'string' ? '' : [],
message_format: messagePostFormat === 'string' ? 'string' : 'array',
post_type: core.selfInfo.uin == msg.senderUin ? EventType.MESSAGE_SENT : EventType.MESSAGE,
};
if (msg.chatType == ChatType.group) {
resMsg.sub_type = 'normal'; // 这里go-cqhttp是group而onebot11标准是normal, 蛋疼
resMsg.group_id = parseInt(msg.peerUin);
//直接去QQNative取
let member = await NTQQGroupApi.getGroupMember(msg.peerUin, msg.senderUin);
if (!member) member = await NTQQGroupApi.getGroupMember(msg.peerUin, msg.senderUin);
if (member) {
resMsg.sender.role = OB11Constructor.groupMemberRole(member.role);
resMsg.sender.nickname = member.nick;
}
} else if (msg.chatType == ChatType.friend) {
resMsg.sub_type = 'friend';
resMsg.sender.nickname = (await NTQQUserApi.getUserDetailInfo(msg.senderUid)).nick;
//const user = await NTQQUserApi.getUserDetailInfoByUin(msg.senderUin!);
//resMsg.sender.nickname = user.info.nick;
} else if (msg.chatType == ChatType.temp) {
resMsg.sub_type = 'group';
const ret = await NTQQMsgApi.getTempChatInfo(ChatType.temp, msg.senderUid);
if (ret.result === 0) {
resMsg.group_id = parseInt(ret.tmpChatInfo!.groupCode);
resMsg.sender.nickname = ret.tmpChatInfo!.fromNick;
} else {
resMsg.group_id = 284840486;//兜底数据
resMsg.sender.nickname = "临时会话";
}
}
for (const element of msg.elements) {
let message_data: OB11MessageData = {
data: {} as any,
type: 'unknown' as any,
};
if (element.textElement && element.textElement?.atType !== AtType.notAt) {
let qq: `${number}` | 'all';
let name: string | undefined;
if (element.textElement.atType == AtType.atAll) {
qq = 'all';
} else {
const { atNtUid, content } = element.textElement;
let atQQ = element.textElement.atUid;
if (!atQQ || atQQ === '0') {
atQQ = await NTQQUserApi.getUinByUidV2(atNtUid);
}
if (atQQ) {
qq = atQQ as `${number}`;
name = content.replace('@', '');
}
}
message_data = {
type: OB11MessageDataType.at,
data: {
qq: qq!,
name,
},
};
} else if (element.textElement) {
message_data['type'] = OB11MessageDataType.text;
let text = element.textElement.content;
if (!text.trim()) {
continue;
}
// 兼容 9.7.x 换行符
if (text.indexOf('\n') === -1 && text.indexOf('\r\n') === -1) {
text = text.replace(/\r/g, '\n');
}
message_data['data']['text'] = text;
} else if (element.replyElement) {
message_data['type'] = OB11MessageDataType.reply;
//log("收到回复消息", element.replyElement);
try {
const records = msg.records.find(msgRecord => msgRecord.msgId === element?.replyElement?.sourceMsgIdInRecords);
const peer = {
chatType: msg.chatType,
peerUid: msg.peerUid,
guildId: '',
};
let replyMsg: RawMessage | undefined;
if (!records) throw new Error('找不到回复消息');
replyMsg = (await NTQQMsgApi.getMsgsBySeqAndCount({
peerUid: msg.peerUid,
guildId: '',
chatType: msg.chatType,
}, element.replyElement.replayMsgSeq, 1, true, true)).msgList.find(msg => msg.msgRandom === records.msgRandom);
if (!replyMsg || records.msgRandom !== replyMsg.msgRandom) {
replyMsg = (await NTQQMsgApi.getSingleMsg(peer, element.replyElement.replayMsgSeq)).msgList[0];
}
if (msg.peerUin == '284840486') {
//合并消息内侧 消息具体定位不到
}
if ((!replyMsg || records.msgRandom !== replyMsg.msgRandom) && msg.peerUin !== '284840486') {
throw new Error('回复消息消息验证失败');
}
message_data['data']['id'] = MessageUnique.createMsg({
peerUid: msg.peerUid,
guildId: '',
chatType: msg.chatType,
}, replyMsg.msgId)?.toString();
//log("找到回复消息", message_data['data']['id'], replyMsg.msgList[0].msgId)
} catch (e: any) {
message_data['type'] = 'unknown' as any;
message_data['data'] = undefined;
logger.logError('获取不到引用的消息', e.stack, element.replyElement.replayMsgSeq);
}
} else if (element.picElement) {
message_data['type'] = OB11MessageDataType.image;
// message_data["data"]["file"] = element.picElement.sourcePath
message_data['data']['file'] = element.picElement.fileName;
message_data['data']['subType'] = element.picElement.picSubType;
message_data['data']['file_id'] = UUIDConverter.encode(msg.peerUin, msg.msgId);
// message_data["data"]["path"] = element.picElement.sourcePath
try {
message_data['data']['url'] = await NTQQFileApi.getImageUrl(element.picElement);
} catch (e: any) {
logger.logError('获取图片url失败', e.stack);
}
//console.log(message_data['data']['url'])
// message_data["data"]["file_id"] = element.picElement.fileUuid
message_data['data']['file_size'] = element.picElement.fileSize;
} else if (element.fileElement) {
const FileElement = element.fileElement;
message_data['type'] = OB11MessageDataType.file;
message_data['data']['file'] = FileElement.fileName;
message_data['data']['path'] = FileElement.filePath;
message_data['data']['url'] = FileElement.filePath;
message_data['data']['file_id'] = UUIDConverter.encode(msg.peerUin, msg.msgId);
message_data['data']['file_size'] = FileElement.fileSize;
await NTQQFileApi.addFileCache(
{
peerUid: msg.peerUid,
chatType: msg.chatType,
guildId: '',
},
msg.msgId,
msg.msgSeq,
msg.senderUid,
element.elementId,
element.elementType.toString(),
FileElement.fileSize,
FileElement.fileName,
);
} else if (element.videoElement) {
const videoElement: VideoElement = element.videoElement;
//读取视频链接并兜底
let videoUrl;//Array
if (msg.peerUin === '284840486') {
//合并消息内部 应该进行特殊处理 可能需要重写peer 待测试与研究 Mlikiowa Taged TODO
}
try {
videoUrl = await NTQQFileApi.getVideoUrl({
chatType: msg.chatType,
peerUid: msg.peerUid,
guildId: '0',
}, msg.msgId, element.elementId);
} catch (error) {
videoUrl = undefined;
}
//读取在线URL
let videoDownUrl = undefined;
if (videoUrl) {
const videoDownUrlTemp = videoUrl.find((url) => {
return !!url.url;
});
if (videoDownUrlTemp) {
videoDownUrl = videoDownUrlTemp.url;
}
}
//开始兜底
if (!videoDownUrl) {
videoDownUrl = videoElement.filePath;
}
message_data['type'] = OB11MessageDataType.video;
message_data['data']['file'] = videoElement.fileName;
message_data['data']['path'] = videoDownUrl;
message_data['data']['url'] = videoDownUrl;
message_data['data']['file_id'] = UUIDConverter.encode(msg.peerUin, msg.msgId);
message_data['data']['file_size'] = videoElement.fileSize;
await NTQQFileApi.addFileCache(
{
peerUid: msg.peerUid,
chatType: msg.chatType,
guildId: '',
},
msg.msgId,
msg.msgSeq,
msg.senderUid,
element.elementId,
element.elementType.toString(),
videoElement.fileSize || '0',
videoElement.fileName,
);
} else if (element.pttElement) {
message_data['type'] = OB11MessageDataType.voice;
message_data['data']['file'] = element.pttElement.fileName;
message_data['data']['path'] = element.pttElement.filePath;
//message_data['data']['file_id'] = element.pttElement.fileUuid;
message_data['data']['file_id'] = UUIDConverter.encode(msg.peerUin, msg.msgId);
message_data['data']['file_size'] = element.pttElement.fileSize;
await NTQQFileApi.addFileCache({
peerUid: msg.peerUid,
chatType: msg.chatType,
guildId: '',
},
msg.msgId,
msg.msgSeq,
msg.senderUid,
element.elementId,
element.elementType.toString(),
element.pttElement.fileSize || '0',
element.pttElement.fileUuid || '',
);
//以uuid作为文件名
} else if (element.arkElement) {
message_data['type'] = OB11MessageDataType.json;
message_data['data']['data'] = element.arkElement.bytesData;
} else if (element.faceElement) {
const faceId = element.faceElement.faceIndex;
if (faceId === FaceIndex.dice) {
message_data['type'] = OB11MessageDataType.dice;
message_data['data']['result'] = element.faceElement.resultId;
} else if (faceId === FaceIndex.RPS) {
message_data['type'] = OB11MessageDataType.RPS;
message_data['data']['result'] = element.faceElement.resultId;
} else {
message_data['type'] = OB11MessageDataType.face;
message_data['data']['id'] = element.faceElement.faceIndex.toString();
}
} else if (element.marketFaceElement) {
message_data['type'] = OB11MessageDataType.mface;
message_data['data']['summary'] = element.marketFaceElement.faceName;
const md5 = element.marketFaceElement.emojiId;
// 取md5的前两位
const dir = md5.substring(0, 2);
// 获取组装url
// const url = `https://p.qpic.cn/CDN_STATIC/0/data/imgcache/htdocs/club/item/parcel/item/${dir}/${md5}/300x300.gif?max_age=31536000`;
message_data['data']['url'] = `https://gxh.vip.qq.com/club/item/parcel/item/${dir}/${md5}/raw300.gif`;
message_data['data']['emoji_id'] = element.marketFaceElement.emojiId;
message_data['data']['emoji_package_id'] = String(element.marketFaceElement.emojiPackageId);
message_data['data']['key'] = element.marketFaceElement.key;
//mFaceCache.set(md5, element.marketFaceElement.faceName);
} else if (element.markdownElement) {
message_data['type'] = OB11MessageDataType.markdown;
message_data['data']['data'] = element.markdownElement.content;
} else if (element.multiForwardMsgElement) {
message_data['type'] = OB11MessageDataType.forward;
message_data['data']['id'] = msg.msgId;
const ParentMsgPeer = msg.parentMsgPeer ?? {
chatType: msg.chatType,
guildId: '',
peerUid: msg.peerUid,
};
//判断是否在合并消息内
msg.parentMsgIdList = msg.parentMsgIdList ?? [];
//首次列表不存在则开始创建
msg.parentMsgIdList.push(msg.msgId);
//let parentMsgId = msg.parentMsgIdList[msg.parentMsgIdList.length - 2 < 0 ? 0 : msg.parentMsgIdList.length - 2];
//加入自身MsgId
const MultiMsgs = (await NTQQMsgApi.getMultiMsg(ParentMsgPeer, msg.parentMsgIdList[0], msg.msgId))?.msgList;
//拉取下级消息
if (!MultiMsgs) continue;
//拉取失败则跳过
message_data['data']['content'] = [];
for (const MultiMsg of MultiMsgs) {
//对每条拉取的消息传递ParentMsgPeer修正Peer
MultiMsg.parentMsgPeer = ParentMsgPeer;
MultiMsg.parentMsgIdList = msg.parentMsgIdList;
MultiMsg.id = MessageUnique.createMsg(ParentMsgPeer, MultiMsg.msgId);//该ID仅用查看 无法调用
const msgList = await OB11Constructor.message(core, obcore, MultiMsg, messagePostFormat);
if (!msgList) continue;
message_data['data']['content'].push(msgList);
//console.log("合并消息", msgList);
}
}
if ((message_data.type as string) !== 'unknown' && message_data.data) {
const cqCode = encodeCQCode(message_data);
if (messagePostFormat === 'string') {
(resMsg.message as string) += cqCode;
} else (resMsg.message as OB11MessageData[]).push(message_data);
resMsg.raw_message += cqCode;
}
}
resMsg.raw_message = resMsg.raw_message.trim();
return resMsg;
}
static async PrivateEvent(core: NapCatCore, msg: RawMessage): Promise<OB11BaseNoticeEvent | undefined> {
const NTQQUserApi = core.apis.UserApi;
if (msg.chatType !== ChatType.friend) {
return;
}
for (const element of msg.elements) {
if (element.grayTipElement) {
if (element.grayTipElement.subElementType == NTGrayTipElementSubTypeV2.GRAYTIP_ELEMENT_SUBTYPE_JSON) {
const json = JSON.parse(element.grayTipElement.jsonGrayTipElement.jsonStr);
if (element.grayTipElement.jsonGrayTipElement.busiId == 1061) {
//判断业务类型
//Poke事件
let pokedetail: any[] = json.items;
//筛选item带有uid的元素
pokedetail = pokedetail.filter(item => item.uid);
//console.log("[NapCat] 群拍一拍 群:", pokedetail, parseInt(msg.peerUid), " ", await NTQQUserApi.getUinByUid(pokedetail[0].uid), "拍了拍", await NTQQUserApi.getUinByUid(pokedetail[1].uid));
if (pokedetail.length == 2) {
return new OB11FriendPokeEvent(
core,
parseInt((await NTQQUserApi.getUinByUidV2(pokedetail[0].uid))!),
parseInt((await NTQQUserApi.getUinByUidV2(pokedetail[1].uid))!),
pokedetail
);
}
}
//下面得改 上面也是错的grayTipElement.subElementType == GrayTipElementSubType.MEMBER_NEW_TITLE
}
if (element.grayTipElement.subElementType == NTGrayTipElementSubTypeV2.GRAYTIP_ELEMENT_SUBTYPE_XMLMSG) {
//好友添加成功事件
if (element.grayTipElement.xmlElement.templId === '10229' && msg.peerUin !== '') {
return new OB11FriendAddNoticeEvent(core, parseInt(msg.peerUin));
}
}
}
}
}
static async GroupEvent(core: NapCatCore, msg: RawMessage): Promise<OB11GroupNoticeEvent | undefined> {
const NTQQGroupApi = core.apis.GroupApi;
const NTQQUserApi = core.apis.UserApi;
const NTQQMsgApi = core.apis.MsgApi;
const logger = core.context.logger;
if (msg.chatType !== ChatType.group) {
return;
}
//log("group msg", msg);
// Mlikiowa V2.0.26 Refactor Todo
// if (msg.senderUin && msg.senderUin !== '0') {
// const member = await getGroupMember(msg.peerUid, msg.senderUin);
// if (member && member.cardName !== msg.sendMemberName) {
// const newCardName = msg.sendMemberName || '';
// const event = new OB11GroupCardEvent(parseInt(msg.peerUid), parseInt(msg.senderUin), newCardName, member.cardName);
// member.cardName = newCardName;
// return event;
// }
// }
for (const element of msg.elements) {
const grayTipElement = element.grayTipElement;
const groupElement = grayTipElement?.groupElement;
if (groupElement) {
// log("收到群提示消息", groupElement)
if (groupElement.type == TipGroupElementType.memberIncrease) {
logger.logDebug('收到群成员增加消息', groupElement);
await sleep(1000);
const member = await NTQQGroupApi.getGroupMember(msg.peerUid, groupElement.memberUid);
const memberUin = member?.uin;
// if (!memberUin) {
// memberUin = (await NTQQUserApi.getUserDetailInfo(groupElement.memberUid)).uin
// }
// log("获取新群成员QQ", memberUin)
const adminMember = await NTQQGroupApi.getGroupMember(msg.peerUid, groupElement.adminUid);
// log("获取同意新成员入群的管理员", adminMember)
if (memberUin) {
const operatorUin = adminMember?.uin || memberUin;
// log("构造群增加事件", event)
return new OB11GroupIncreaseEvent(
core,
parseInt(msg.peerUid),
parseInt(memberUin),
parseInt(operatorUin)
);
}
} else if (groupElement.type === TipGroupElementType.ban) {
logger.logDebug('收到群群员禁言提示', groupElement);
const memberUid = groupElement.shutUp!.member.uid;
const adminUid = groupElement.shutUp!.admin.uid;
let memberUin: string = '';
let duration = parseInt(groupElement.shutUp!.duration);
const subType: 'ban' | 'lift_ban' = duration > 0 ? 'ban' : 'lift_ban';
// log('OB11被禁言事件', adminUid);
if (memberUid) {
memberUin = (await NTQQGroupApi.getGroupMember(msg.peerUid, memberUid))?.uin || ''; // || (await NTQQUserApi.getUserDetailInfo(memberUid))?.uin
} else {
memberUin = '0'; // 0表示全员禁言
if (duration > 0) {
duration = -1;
}
}
const adminUin = (await NTQQGroupApi.getGroupMember(msg.peerUid, adminUid))?.uin; // || (await NTQQUserApi.getUserDetailInfo(adminUid))?.uin
// log('OB11被禁言事件', memberUin, adminUin, duration, subType);
if (memberUin && adminUin) {
return new OB11GroupBanEvent(
core,
parseInt(msg.peerUid),
parseInt(memberUin),
parseInt(adminUin),
duration,
subType
);
}
} else if (groupElement.type == TipGroupElementType.kicked) {
logger.logDebug(`收到我被踢出或退群提示, 群${msg.peerUid}`, groupElement);
NTQQGroupApi.quitGroup(msg.peerUid).then();
try {
const adminUin = (await NTQQGroupApi.getGroupMember(msg.peerUid, groupElement.adminUid))?.uin || (await NTQQUserApi.getUidByUinV2(groupElement.adminUid));
if (adminUin) {
return new OB11GroupDecreaseEvent(
core,
parseInt(msg.peerUid),
parseInt(core.selfInfo.uin),
parseInt(adminUin),
'kick_me'
);
}
} catch (e) {
return new OB11GroupDecreaseEvent(
core,
parseInt(msg.peerUid),
parseInt(core.selfInfo.uin),
0,
'leave'
);
}
}
} else if (element.fileElement) {
return new OB11GroupUploadNoticeEvent(
core,
parseInt(msg.peerUid), parseInt(msg.senderUin || ''),
{
id: element.fileElement.fileUuid!,
name: element.fileElement.fileName,
size: parseInt(element.fileElement.fileSize),
busid: element.fileElement.fileBizId || 0,
}
);
}
if (grayTipElement) {
//console.log('收到群提示消息', grayTipElement);
if (grayTipElement.xmlElement?.templId === '10382') {
const emojiLikeData = new fastXmlParser.XMLParser({
ignoreAttributes: false,
attributeNamePrefix: '',
}).parse(grayTipElement.xmlElement.content);
logger.logDebug('收到表情回应我的消息', emojiLikeData);
try {
const senderUin = emojiLikeData.gtip.qq.jp;
const msgSeq = emojiLikeData.gtip.url.msgseq;
const emojiId = emojiLikeData.gtip.face.id;
const peer = {
chatType: ChatType.group,
guildId: '',
peerUid: msg.peerUid
}
// const replyMsgList = (await NTQQMsgApi.getMsgsBySeqAndCount({
// chatType: ChatType.group,
// guildId: '',
// peerUid: msg.peerUid,
// }, msgSeq, 1, true, true)).msgList;
const replyMsgList = (await NTQQMsgApi.getMsgExBySeq(peer, msgSeq)).msgList;
//console.log("表情回应消息长度检测", replyMsgList.length)
if (replyMsgList.length < 1) {
return;
}
const replyMsg = replyMsgList.reverse()[0];//获取最顶层消息
//console.log('表情回应消息', msgSeq, ' 结算ID', replyMsg.msgId);
return new OB11GroupMsgEmojiLikeEvent(
core,
parseInt(msg.peerUid),
parseInt(senderUin),
MessageUnique.getShortIdByMsgId(replyMsg.msgId)!,
[{
emoji_id: emojiId,
count: 1,
}],
);
} catch (e: any) {
logger.logError('解析表情回应消息失败', e.stack);
}
}
if (grayTipElement.subElementType == NTGrayTipElementSubTypeV2.GRAYTIP_ELEMENT_SUBTYPE_XMLMSG) {
logger.logDebug('收到新人被邀请进群消息', grayTipElement);
const xmlElement = grayTipElement.xmlElement;
if (xmlElement?.content) {
const regex = /jp="(\d+)"/g;
const matches = [];
let match = null;
while ((match = regex.exec(xmlElement.content)) !== null) {
matches.push(match[1]);
}
// log("新人进群匹配到的QQ号", matches)
if (matches.length === 2) {
const [inviter, invitee] = matches;
return new OB11GroupIncreaseEvent(
core,
parseInt(msg.peerUid),
parseInt(invitee),
parseInt(inviter),
'invite'
);
}
}
}
//代码歧义 GrayTipElementSubType.MEMBER_NEW_TITLE
else if (grayTipElement.subElementType == NTGrayTipElementSubTypeV2.GRAYTIP_ELEMENT_SUBTYPE_JSON) {
const json = JSON.parse(grayTipElement.jsonGrayTipElement.jsonStr);
if (grayTipElement.jsonGrayTipElement.busiId == 1061) {
//判断业务类型
//Poke事件
const pokedetail: any[] = json.items;
//筛选item带有uid的元素
const poke_uid = pokedetail.filter(item => item.uid);
if (poke_uid.length == 2) {
return new OB11GroupPokeEvent(
core,
parseInt(msg.peerUid),
parseInt((await NTQQUserApi.getUinByUidV2(poke_uid[0].uid))!),
parseInt((await NTQQUserApi.getUinByUidV2(poke_uid[1].uid))!),
pokedetail
);
}
}
if (grayTipElement.jsonGrayTipElement.busiId == 2401) {
const searchParams = new URL(json.items[0].jp).searchParams;
const msgSeq = searchParams.get('msgSeq')!;
const Group = searchParams.get('groupCode');
// const businessId = searchParams.get('businessid');
const Peer: Peer = {
guildId: '',
chatType: ChatType.group,
peerUid: Group!,
};
const msgData = await NTQQMsgApi.getMsgsBySeqAndCount(Peer, msgSeq.toString(), 1, true, true);
return new OB11GroupEssenceEvent(
core,
parseInt(msg.peerUid),
MessageUnique.getShortIdByMsgId(msgData.msgList[0].msgId)!,
parseInt(msgData.msgList[0].senderUin)
);
// 获取MsgSeq+Peer可获取具体消息
}
if (grayTipElement.jsonGrayTipElement.busiId == 2407) {
//下面得改 上面也是错的grayTipElement.subElementType == GrayTipElementSubType.MEMBER_NEW_TITLE
const memberUin = json.items[1].param[0];
const title = json.items[3].txt;
logger.logDebug('收到群成员新头衔消息', json);
return new OB11GroupTitleEvent(
core,
parseInt(msg.peerUid),
parseInt(memberUin),
title
);
}
}
}
}
}
static friend(friend: User): OB11User {
return {
user_id: parseInt(friend.uin),
nickname: friend.nick,
remark: friend.remark,
sex: OB11Constructor.sex(friend.sex!),
level: friend.qqLevel && calcQQLevel(friend.qqLevel) || 0,
};
}
static selfInfo(selfInfo: SelfInfo): OB11User {
return {
user_id: parseInt(selfInfo.uin),
nickname: selfInfo.nick,
};
}
static friendsV2(friends: FriendV2[]): OB11User[] {
const data: OB11User[] = [];
friends.forEach(friend => {
const sexValue = this.sex(friend.baseInfo.sex!);
data.push({
...friend.baseInfo,
...friend.coreInfo,
user_id: parseInt(friend.coreInfo.uin),
nickname: friend.coreInfo.nick,
remark: friend.coreInfo.nick,
sex: sexValue,
level: 0,
});
});
return data;
}
static friends(friends: Friend[]): OB11User[] {
const data: OB11User[] = [];
friends.forEach(friend => {
const sexValue = this.sex(friend.sex!);
data.push({
user_id: parseInt(friend.uin),
nickname: friend.nick,
remark: friend.remark,
sex: sexValue,
level: 0,
});
});
return data;
}
static groupMemberRole(role: number): OB11GroupMemberRole | undefined {
return {
4: OB11GroupMemberRole.owner,
3: OB11GroupMemberRole.admin,
2: OB11GroupMemberRole.member,
}[role];
}
static sex(sex: Sex): OB11UserSex {
const sexMap = {
[Sex.male]: OB11UserSex.male,
[Sex.female]: OB11UserSex.female,
[Sex.unknown]: OB11UserSex.unknown,
};
return sexMap[sex] || OB11UserSex.unknown;
}
static groupMember(group_id: string, member: GroupMember): OB11GroupMember {
return {
group_id: parseInt(group_id),
user_id: parseInt(member.uin),
nickname: member.nick,
card: member.cardName,
sex: OB11Constructor.sex(member.sex!),
age: 0,
area: '',
level: '0',
qq_level: member.qqLevel && calcQQLevel(member.qqLevel) || 0,
join_time: 0, // 暂时没法获取
last_sent_time: 0, // 暂时没法获取
title_expire_time: 0,
unfriendly: false,
card_changeable: true,
is_robot: member.isRobot,
shut_up_timestamp: member.shutUpTime,
role: OB11Constructor.groupMemberRole(member.role),
title: member.memberSpecialTitle || '',
};
}
static stranger(user: User): OB11User {
//logDebug('construct ob11 stranger', user);
return {
...user,
user_id: parseInt(user.uin),
nickname: user.nick,
sex: OB11Constructor.sex(user.sex!),
age: 0,
qid: user.qid,
login_days: 0,
level: user.qqLevel && calcQQLevel(user.qqLevel) || 0,
};
}
static group(group: Group): OB11Group {
return {
group_id: parseInt(group.groupCode),
group_name: group.groupName,
member_count: group.memberCount,
max_member_count: group.maxMember,
};
}
static groups(groups: Group[]): OB11Group[] {
return groups.map(OB11Constructor.group);
}
}

154
src/onebot/helper/event.ts Normal file
View File

@@ -0,0 +1,154 @@
import { NapCatOneBot11Adapter } from "..";
import { OB11BaseNoticeEvent } from "../event/notice/OB11BaseNoticeEvent";
import { OB11FriendAddNoticeEvent } from "../event/notice/OB11FriendAddNoticeEvent";
import { OB11GroupNoticeEvent } from "../event/notice/OB11GroupNoticeEvent";
import { OB11GroupCardEvent } from "../event/notice/OB11GroupCardEvent";
import { OB11GroupDecreaseEvent } from "../event/notice/OB11GroupDecreaseEvent";
import { OB11GroupUploadNoticeEvent } from "../event/notice/OB11GroupUploadNoticeEvent";
import { OB11GroupPokeEvent } from "../event/notice/OB11PokeEvent";
import { OB11GroupEssenceEvent } from "../event/notice/OB11GroupEssenceEvent";
import { MessageUnique } from "@/common/utils/MessageUnique";
import { OB11GroupTitleEvent } from "../event/notice/OB11GroupTitleEvent";
import { NapCatCore, RawMessage, ChatType, NTGrayTipElementSubTypeV2, TipGroupElementType, Peer } from '@/core';
export async function NT2PrivateEvent(core: NapCatCore, obContext: NapCatOneBot11Adapter, msg: RawMessage): Promise<OB11BaseNoticeEvent | undefined> {
if (msg.chatType !== ChatType.KCHATTYPEC2C) {
return;
}
for (const element of msg.elements) {
if (element.grayTipElement) {
if (element.grayTipElement.subElementType == NTGrayTipElementSubTypeV2.GRAYTIP_ELEMENT_SUBTYPE_JSON) {
if (element.grayTipElement.jsonGrayTipElement.busiId == 1061) {
const PokeEvent = await obContext.apiContext.FriendApi.parsePrivatePokeEvent(element.grayTipElement);
if (PokeEvent) return PokeEvent;
}
}
if (element.grayTipElement.subElementType == NTGrayTipElementSubTypeV2.GRAYTIP_ELEMENT_SUBTYPE_XMLMSG) {
//好友添加成功事件
if (element.grayTipElement.xmlElement.templId === '10229' && msg.peerUin !== '') {
return new OB11FriendAddNoticeEvent(core, parseInt(msg.peerUin));
}
}
}
}
}
export async function NT2GroupEvent(core: NapCatCore, obContext: NapCatOneBot11Adapter, msg: RawMessage): Promise<OB11GroupNoticeEvent | undefined> {
const NTQQGroupApi = core.apis.GroupApi;
const NTQQUserApi = core.apis.UserApi;
const NTQQMsgApi = core.apis.MsgApi;
const logger = core.context.logger;
if (msg.chatType !== ChatType.KCHATTYPEGROUP) {
return;
}
//log("group msg", msg);
if (msg.senderUin && msg.senderUin !== '0') {
const member = await NTQQGroupApi.getGroupMember(msg.peerUid, msg.senderUin);
if (member && member.cardName !== msg.sendMemberName) {
const newCardName = msg.sendMemberName || '';
const event = new OB11GroupCardEvent(core, parseInt(msg.peerUid), parseInt(msg.senderUin), newCardName, member.cardName);
member.cardName = newCardName;
return event;
}
}
for (const element of msg.elements) {
if (element.grayTipElement && element.grayTipElement.groupElement) {
const groupElement = element.grayTipElement.groupElement;
if (groupElement.type == TipGroupElementType.memberIncrease) {
const MemberIncreaseEvent = await obContext.apiContext.GroupApi.parseGroupMemberIncreaseEvent(msg.peerUid, element.grayTipElement);
if (MemberIncreaseEvent) return MemberIncreaseEvent;
} else if (groupElement.type === TipGroupElementType.ban) {
const BanEvent = await obContext.apiContext.GroupApi.parseGroupBanEvent(msg.peerUid, element.grayTipElement);
if (BanEvent) return BanEvent;
} else if (groupElement.type == TipGroupElementType.kicked) {
NTQQGroupApi.quitGroup(msg.peerUid).then();
try {
const KickEvent = await obContext.apiContext.GroupApi.parseGroupKickEvent(msg.peerUid, element.grayTipElement);
if (KickEvent) return KickEvent;
} catch (e) {
return new OB11GroupDecreaseEvent(
core,
parseInt(msg.peerUid),
parseInt(core.selfInfo.uin),
0,
'leave'
);
}
}
} else if (element.fileElement) {
return new OB11GroupUploadNoticeEvent(
core,
parseInt(msg.peerUid), parseInt(msg.senderUin || ''),
{
id: element.fileElement.fileUuid!,
name: element.fileElement.fileName,
size: parseInt(element.fileElement.fileSize),
busid: element.fileElement.fileBizId || 0,
}
);
}
if (element.grayTipElement) {
if (element.grayTipElement.xmlElement?.templId === '10382') {
const emojiLikeEvent = await obContext.apiContext.GroupApi.parseGroupEmjioLikeEvent(msg.peerUid, element.grayTipElement);
if (emojiLikeEvent) return emojiLikeEvent;
}
if (element.grayTipElement.subElementType == NTGrayTipElementSubTypeV2.GRAYTIP_ELEMENT_SUBTYPE_XMLMSG) {
const GroupIncreaseEvent = await obContext.apiContext.GroupApi.parseGroupIncreaseEvent(msg.peerUid, element.grayTipElement);
if (GroupIncreaseEvent) return GroupIncreaseEvent;
}
//代码歧义 GrayTipElementSubType.MEMBER_NEW_TITLE
else if (element.grayTipElement.subElementType == NTGrayTipElementSubTypeV2.GRAYTIP_ELEMENT_SUBTYPE_JSON) {
const json = JSON.parse(element.grayTipElement.jsonGrayTipElement.jsonStr);
if (element.grayTipElement.jsonGrayTipElement.busiId == 1061) {
//判断业务类型
//Poke事件
const pokedetail: any[] = json.items;
//筛选item带有uid的元素
const poke_uid = pokedetail.filter(item => item.uid);
if (poke_uid.length == 2) {
return new OB11GroupPokeEvent(
core,
parseInt(msg.peerUid),
parseInt((await NTQQUserApi.getUinByUidV2(poke_uid[0].uid))!),
parseInt((await NTQQUserApi.getUinByUidV2(poke_uid[1].uid))!),
pokedetail
);
}
}
if (element.grayTipElement.jsonGrayTipElement.busiId == 2401) {
const searchParams = new URL(json.items[0].jp).searchParams;
const msgSeq = searchParams.get('msgSeq')!;
const Group = searchParams.get('groupCode');
// const businessId = searchParams.get('businessid');
const Peer: Peer = {
guildId: '',
chatType: ChatType.KCHATTYPEGROUP,
peerUid: Group!,
};
const msgData = await NTQQMsgApi.getMsgsBySeqAndCount(Peer, msgSeq.toString(), 1, true, true);
return new OB11GroupEssenceEvent(
core,
parseInt(msg.peerUid),
MessageUnique.getShortIdByMsgId(msgData.msgList[0].msgId)!,
parseInt(msgData.msgList[0].senderUin)
);
// 获取MsgSeq+Peer可获取具体消息
}
if (element.grayTipElement.jsonGrayTipElement.busiId == 2407) {
//下面得改 上面也是错的grayTipElement.subElementType == GrayTipElementSubType.MEMBER_NEW_TITLE
const memberUin = json.items[1].param[0];
const title = json.items[3].txt;
logger.logDebug('收到群成员新头衔消息', json);
return new OB11GroupTitleEvent(
core,
parseInt(msg.peerUid),
parseInt(memberUin),
title
);
}
}
}
}
}

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