Compare commits

...

233 Commits

Author SHA1 Message Date
手瓜一十雪
a272feda6a release: 1.7.8 2024-08-05 13:52:34 +08:00
手瓜一十雪
bc936a0ca7 build: 178发言时间与加入时间完全兜底 2024-08-05 13:45:07 +08:00
手瓜一十雪
28030c2d13 chore: test 2024-08-05 13:01:26 +08:00
手瓜一十雪
7fe9176286 feat: richmsg failed 2024-08-05 12:45:06 +08:00
手瓜一十雪
1105f9b8d6 docs: 补充 2024-08-05 01:11:11 +08:00
手瓜一十雪
e4653defa8 LICENSE: break 2024-08-05 00:48:41 +08:00
Alen
1c62a1e839 Merge pull request #208 from cnxysoft/main
BUG修复
2024-08-05 00:20:17 +08:00
Alen
3a3bbfe201 BUG修复
修复(群聊/私聊)转发合并消息错误的问题
2024-08-05 00:18:42 +08:00
手瓜一十雪
1c38833998 fix: 177 2024-08-04 23:26:51 +08:00
手瓜一十雪
38894177ee fix: get_stranger_info 2024-08-04 22:21:24 +08:00
手瓜一十雪
dce8416942 fix: 177 2024-08-04 21:40:44 +08:00
手瓜一十雪
14219e9b42 release: v1.7.7 2024-08-04 21:23:55 +08:00
手瓜一十雪
7b459e7502 refactor: get_group_member_list 2024-08-04 21:21:36 +08:00
手瓜一十雪
31824c0504 refactor: get_group_list 2024-08-04 20:56:49 +08:00
手瓜一十雪
e203abae85 refactor: /get_group_member_info 2024-08-04 20:53:23 +08:00
手瓜一十雪
faf83b680b 《NTQQ参数全解》 2024-08-04 20:19:34 +08:00
手瓜一十雪
67dcbcb842 refactor: 开始重构群成员信息获取 2024-08-04 20:10:21 +08:00
手瓜一十雪
6533a25404 chore: 移除调试代码 2024-08-04 19:12:18 +08:00
手瓜一十雪
4dc760b0e9 feat: shareDigest 2024-08-04 19:11:44 +08:00
手瓜一十雪
25933b9043 chore: 移除旧代码 2024-08-04 18:50:40 +08:00
手瓜一十雪
a53aaa456e fix: 一处很久很久的看错的的问题 2024-08-04 18:49:40 +08:00
手瓜一十雪
e8a7ea07a5 refactor: Id转换 2024-08-04 18:45:00 +08:00
手瓜一十雪
8817dc6b10 refactor: Uid/Uin转换V2版本 2024-08-04 18:01:31 +08:00
手瓜一十雪
491ec04b46 fix: 准备第二次重构uid/uin 2024-08-04 16:50:23 +08:00
手瓜一十雪
8a5d4a683b feat: getBuddyV2ExWithCate 2024-08-04 16:45:14 +08:00
手瓜一十雪
dfc7c7357a Refactor Api: GetFriendsWithCategory 2024-08-04 16:37:15 +08:00
手瓜一十雪
690a2f7d34 refctor: getBuddyV2 支持分类 2024-08-04 16:27:25 +08:00
手瓜一十雪
58f22b24e4 refactor: api getbuddyv2 2024-08-04 16:05:07 +08:00
手瓜一十雪
3cce9f528b build: 1.7.7 2024-08-04 15:16:45 +08:00
手瓜一十雪
20fd5ac8cb chore: Todo 2024-08-04 15:12:55 +08:00
手瓜一十雪
9e05e086eb fix: 初始化问题 2024-08-04 15:01:43 +08:00
手瓜一十雪
056e0adddf build: 1.7.7-refactor 2024-08-04 14:39:45 +08:00
手瓜一十雪
b36388200d Merge branch 'main' of https://github.com/NapNeko/NapCatQQ 2024-08-04 14:35:47 +08:00
手瓜一十雪
9793e5741a refactor: Appid的获取 2024-08-04 14:35:26 +08:00
手瓜一十雪
143380c012 Merge pull request #206 from pohgxz/main
修复webui快捷登录失败
2024-08-04 14:15:53 +08:00
手瓜一十雪
4b92254945 chore: 订正类型 2024-08-04 14:15:40 +08:00
手瓜一十雪
f9c1d8b4a6 build: 1.7.7 refactor 2024-08-04 14:06:30 +08:00
手瓜一十雪
c0c469339b refactor: versionGet 2024-08-04 14:03:28 +08:00
Nepenthe
0ca6343ed7 修复webui快捷登录失败 2024-08-04 13:04:31 +08:00
手瓜一十雪
3db74c3427 refactor: BuddyList 2024-08-04 12:26:55 +08:00
手瓜一十雪
48d5cb53bd release: 1.7.6 2024-08-03 22:45:36 +08:00
手瓜一十雪
fd7d2dbf53 release: 1.7.5 2024-08-03 20:48:13 +08:00
手瓜一十雪
6609697752 release: 1.7.5 2024-08-03 20:47:05 +08:00
手瓜一十雪
dcd6e1973e build:1.7.5For9.9.15 2024-08-03 16:23:55 +08:00
手瓜一十雪
3614a6e932 chore: 9.9.15 support 2024-08-03 16:08:23 +08:00
手瓜一十雪
931a0210e5 chore: 兼容9.915 信息获取 2024-08-03 15:36:56 +08:00
手瓜一十雪
f9e7de4b42 build: 1.7.5For9.9.15 2024-08-03 15:07:51 +08:00
手瓜一十雪
8e0b79594e style: lint 2024-08-03 15:06:02 +08:00
手瓜一十雪
17122c4360 feat: 精简历史获取 2024-08-03 15:03:41 +08:00
手瓜一十雪
154f7b6a30 chore: 清除老旧代码 2024-08-03 14:57:24 +08:00
手瓜一十雪
52e5543d0b chore: queryEmoticonMsgs 2024-08-03 14:44:48 +08:00
手瓜一十雪
3c304bd2ae feat: 补全类型 开始对9.9.15针对优化 2024-08-03 14:25:26 +08:00
手瓜一十雪
26609bb8fd chore: 9.9.15兼容sendmsg 2024-08-03 13:11:25 +08:00
手瓜一十雪
de3fa9aaa4 Merge branch 'main' of https://github.com/NapNeko/NapCatQQ 2024-08-03 12:28:41 +08:00
手瓜一十雪
788665f84c chore: support win 9.9.15 2024-08-03 12:28:25 +08:00
手瓜一十雪
3943782971 Merge pull request #201 from idranme/main
feat: at segment add name
2024-08-03 07:08:25 +08:00
idranme
8f899c40f2 chore 2024-08-02 15:49:01 +00:00
idranme
a1f582399e feat: at segment add name 2024-08-02 15:45:37 +00:00
手瓜一十雪
440b63f662 release: v1.7.4 2024-08-01 23:35:35 +08:00
手瓜一十雪
7d2cc3b56b fix: 多次上报自身消息 2024-08-01 22:00:40 +08:00
手瓜一十雪
5fe3422469 #176 revert 2024-08-01 21:48:17 +08:00
手瓜一十雪
6c02cedb1e build: 1.7.4 2024-08-01 19:44:28 +08:00
手瓜一十雪
3cc2f1dcad fix #183 2024-08-01 19:43:29 +08:00
手瓜一十雪
773cdc5877 build: test 2024-08-01 17:14:15 +08:00
手瓜一十雪
361a7329d7 Revert "build(deps-dev): bump @typescript-eslint/eslint-plugin"
This reverts commit 2562a38fa1.
2024-08-01 17:13:54 +08:00
手瓜一十雪
29910f1236 build: test api 2024-08-01 17:12:56 +08:00
手瓜一十雪
4a164016f5 chore: test 2024-08-01 17:08:22 +08:00
手瓜一十雪
cebd3e62a4 Merge pull request #192 from NapNeko/dependabot/npm_and_yarn/typescript-eslint/eslint-plugin-8.0.0
build(deps-dev): bump @typescript-eslint/eslint-plugin from 7.18.0 to 8.0.0
2024-08-01 16:26:50 +08:00
dependabot[bot]
2562a38fa1 build(deps-dev): bump @typescript-eslint/eslint-plugin
Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 7.18.0 to 8.0.0.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.0.0/packages/eslint-plugin)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-01 08:22:34 +00:00
手瓜一十雪
d46c922bbf Merge branch 'main' of https://github.com/NapNeko/NapCatQQ 2024-08-01 13:11:19 +08:00
手瓜一十雪
66b59982f7 补充类型 移除无用代码 2024-08-01 13:11:09 +08:00
手瓜一十雪
ad397ccf7f Merge pull request #188 from Fripine/feat/FriendAddNoticeEvent
feat: support FriendAddNoticeEvent
2024-08-01 08:46:50 +08:00
Fripine
bdef80ede7 feat: support FriendAddNoticeEvent 2024-08-01 01:03:33 +08:00
手瓜一十雪
385dcbc75a fix #186 2024-07-31 22:23:12 +08:00
手瓜一十雪
74cf501c8f release: 1.7.3 2024-07-31 22:21:23 +08:00
手瓜一十雪
0c200d6748 build: 1.7.2 beta0 2024-07-31 22:20:39 +08:00
手瓜一十雪
e65a36c517 chore: Ver2SendMsg 2024-07-31 22:19:35 +08:00
手瓜一十雪
126b54ad40 fix #186 2024-07-31 21:46:12 +08:00
手瓜一十雪
78637751af fix #187 2024-07-31 21:41:31 +08:00
手瓜一十雪
f96526ee3a fix #184 2024-07-31 21:36:13 +08:00
手瓜一十雪
b3c7a91f3d refactor: getfile 2024-07-31 16:40:34 +08:00
手瓜一十雪
b8daeef0c4 fix #173 2024-07-31 16:02:08 +08:00
手瓜一十雪
2b662944cf typo fix #178 2024-07-31 15:58:27 +08:00
手瓜一十雪
3d516df01e try fix #183 2024-07-31 15:52:28 +08:00
手瓜一十雪
26b4a9b15b chore: 兼容wt 2024-07-31 14:21:05 +08:00
手瓜一十雪
0f8af273ae release: 1.7.2 2024-07-31 10:51:12 +08:00
手瓜一十雪
fa29a31da9 release: 1.7.2 2024-07-31 10:46:23 +08:00
手瓜一十雪
9e0d2606d8 build: 1.7.2 no log 2024-07-31 10:35:53 +08:00
手瓜一十雪
338dedd6e0 build: 1.7.1 双消息队列 2024-07-31 10:33:38 +08:00
手瓜一十雪
1f893b1393 build: 1.7.2 消息队列重构 2024-07-31 10:25:46 +08:00
手瓜一十雪
b783d6f928 refatcor: sendmsg 2024-07-31 01:12:46 +08:00
手瓜一十雪
8ca0d40f05 fix: error 2024-07-30 23:34:29 +08:00
手瓜一十雪
2f40a80434 fix: error 2024-07-30 23:33:32 +08:00
手瓜一十雪
812d8eb5bb feat: 对msgId兜底 2024-07-30 23:31:42 +08:00
手瓜一十雪
bf5f548349 fix: msghash性能问题 2024-07-30 23:06:58 +08:00
手瓜一十雪
ebf90e72b9 Merge pull request #180 from cnxysoft/main
BW5启动脚本修改
2024-07-30 22:30:04 +08:00
Alen
31d7d42edf BW5启动脚本修复
修改脚本为 UTF-8 with BOM 格式,解决添加注释后执行报错的问题
2024-07-30 19:56:17 +08:00
Alen
833875b42f Merge remote-tracking branch 'upstream/main' 2024-07-30 17:56:48 +08:00
Alen
b901c10f3c 修改BW5启动脚本
支持脚本自动提权
自动覆盖dbghelp.dll
修改默认启用UTF8
修改不再从新窗口运行NC
2024-07-30 17:56:28 +08:00
手瓜一十雪
8ab678bd97 chore: remove log 2024-07-30 16:45:03 +08:00
手瓜一十雪
55d5072f46 fix: setMsg 2024-07-30 16:34:33 +08:00
手瓜一十雪
a379ffd0f2 Merge pull request #175 from pohgxz/main
修复Way05无法带参
2024-07-30 09:12:15 +08:00
Nepenthe
4501d73134 修复Way05无法带参 2024-07-29 23:32:34 +08:00
手瓜一十雪
5f15774ec7 fix: script 2024-07-29 20:58:27 +08:00
手瓜一十雪
c9e057599e fix: error 2024-07-29 20:49:35 +08:00
手瓜一十雪
66851f5625 fix 2024-07-29 20:39:01 +08:00
手瓜一十雪
b33c235b4d release: 1.7.1 2024-07-29 20:13:09 +08:00
手瓜一十雪
d6693b6114 Merge pull request #171 from NapNeko/dependabot/npm_and_yarn/types/node-22.0.0
build(deps-dev): bump @types/node from 20.14.13 to 22.0.0
2024-07-29 17:23:02 +08:00
dependabot[bot]
36b4d26c78 build(deps-dev): bump @types/node from 20.14.13 to 22.0.0
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 20.14.13 to 22.0.0.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-29 08:23:55 +00:00
手瓜一十雪
617592d90a fix: 多次post 2024-07-29 11:32:55 +08:00
手瓜一十雪
15a77b8070 fix: bug 2024-07-29 10:24:02 +08:00
手瓜一十雪
606eccd22b refactor: 精简逻辑 2024-07-29 10:12:41 +08:00
手瓜一十雪
5613450313 refactor: 回复 2024-07-29 10:07:59 +08:00
手瓜一十雪
c59b5564af release: 1.7.0 2024-07-29 09:24:17 +08:00
手瓜一十雪
330b086b8b fix #98 2024-07-29 09:10:52 +08:00
手瓜一十雪
9837ef4f36 fix #125 2024-07-29 08:59:26 +08:00
手瓜一十雪
add46b3251 chore: 落地标准化 2024-07-29 08:43:37 +08:00
手瓜一十雪
e169199107 feat: #162 2024-07-29 08:29:24 +08:00
手瓜一十雪
92fe654850 fix #157 2024-07-29 08:12:49 +08:00
手瓜一十雪
b257486404 fix #158 2024-07-29 08:02:18 +08:00
手瓜一十雪
bdf2e33f40 Merge branch 'main' of https://github.com/NapNeko/NapCatQQ 2024-07-29 07:51:28 +08:00
手瓜一十雪
224d361923 fix #167 2024-07-29 07:51:18 +08:00
手瓜一十雪
3452fa56df build: 😭版本满天飞版本 2024-07-29 00:39:34 +08:00
手瓜一十雪
cd256235da refactor: selfMsgPost 2024-07-29 00:16:24 +08:00
手瓜一十雪
361a164f2a fix: typo 2024-07-28 23:54:50 +08:00
手瓜一十雪
0e60d4b198 fix: 延迟调整 2024-07-28 23:53:45 +08:00
手瓜一十雪
67b47e39b4 fix: typo 2024-07-28 23:49:04 +08:00
手瓜一十雪
8f54310f63 refactor: reply 2024-07-28 23:35:58 +08:00
手瓜一十雪
c7a7494d7e fix: 保证NC回复的消息一致性 2024-07-28 22:33:50 +08:00
手瓜一十雪
af88b3166d build: 169-test 2024-07-28 17:55:34 +08:00
手瓜一十雪
b7837b2a14 chore:test 2024-07-28 17:38:48 +08:00
手瓜一十雪
950ddc749e chore: 🥹LocalMsg不会写入数据库 2024-07-28 16:23:56 +08:00
手瓜一十雪
df081ef0cf chore: 移除调试代码 2024-07-28 15:40:44 +08:00
手瓜一十雪
7b24f90d9f rector: 离线文件重构初步完成 2024-07-28 15:37:34 +08:00
手瓜一十雪
f2e4579fd8 feat: 缓存文件 2024-07-28 15:34:08 +08:00
手瓜一十雪
97cb351827 fix: typo 2024-07-28 14:55:30 +08:00
手瓜一十雪
c1ec53fdbb refactor: fileget 2024-07-28 14:25:13 +08:00
手瓜一十雪
98214aa429 refactor: video element 2024-07-28 13:36:03 +08:00
手瓜一十雪
ce7deac2dd refactor: video element 2024-07-28 13:28:21 +08:00
手瓜一十雪
612092b867 build: 170 re 2024-07-28 13:15:14 +08:00
手瓜一十雪
92579d5949 Merge pull request #163 from cnxysoft/main
修复提交疏漏
2024-07-28 13:00:12 +08:00
手瓜一十雪
9ab07060ae fix: default 2024-07-28 12:59:20 +08:00
手瓜一十雪
0d45125d79 fix: uid && latestMsg 2024-07-28 09:56:00 +08:00
手瓜一十雪
9ced152778 fix: 修复uid转换异常问题 2024-07-28 09:24:03 +08:00
Alen
3685ab2e3e Merge remote-tracking branch 'upstream/main' 2024-07-27 15:01:23 +08:00
Alen
be605f11f2 修复提交疏漏
修改在查询群历史消息时,如未提供msg_seq,则返回最新消息
2024-07-27 15:01:08 +08:00
手瓜一十雪
8cca8df976 Merge pull request #159 from cnxysoft/main
bug修复和标准兼容
2024-07-27 06:46:25 +08:00
Alen
990a31e961 标准兼容
根据GOCQ标准将获取群历史消息中的msg_seq改为非必要参数,默认为0
2024-07-27 04:10:01 +08:00
Alen
5db201c342 BUG修复
修复创建reply消息体时向NTQQMsgApi.getMsgsByMsgId提交空值查询会导致QQ崩溃的BUG
2024-07-27 04:01:22 +08:00
手瓜一十雪
a625e30dd4 fix: search file 2024-07-26 17:37:04 +08:00
手瓜一十雪
b236cdd060 refactor: search file 2024-07-26 17:15:28 +08:00
手瓜一十雪
2db9899184 chore: type 2024-07-26 16:26:47 +08:00
手瓜一十雪
fe5d6db986 chore: wait release 1.7.0 2024-07-26 16:10:05 +08:00
手瓜一十雪
7c7bf8fecf chore: 类型+++++ 2024-07-26 16:08:28 +08:00
手瓜一十雪
76e3a46378 fix: type 2024-07-26 16:02:23 +08:00
手瓜一十雪
16f3897fec chore: 类型补全计划 2024-07-26 15:55:05 +08:00
手瓜一十雪
045e120854 refactor: type 2024-07-26 15:38:43 +08:00
Version
2b7fcce9b2 chore:version change 2024-07-26 05:25:49 +00:00
手瓜一十雪
9685931694 fix: uint 2024-07-26 13:25:19 +08:00
手瓜一十雪
1dc844435a Merge pull request #153 from Guation/main
feat: WebUI支持放置到二级目录中
2024-07-26 13:04:26 +08:00
手瓜一十雪
18892379de fix: search file 2024-07-26 12:27:02 +08:00
手瓜一十雪
620d61c8dc docs: v1.6.8 2024-07-26 11:59:18 +08:00
手瓜一十雪
9f91398875 build: 再次优化发送速度 2024-07-26 11:30:04 +08:00
挂神
a29b1154a9 feat: WebUI支持放置到二级目录中 2024-07-26 11:12:09 +08:00
手瓜一十雪
34d19a471a refactor: 回滚 2024-07-26 10:58:56 +08:00
手瓜一十雪
2ef6477d7c build: log info 2024-07-25 20:22:03 +08:00
手瓜一十雪
26e6800836 fix: 退群推送 2024-07-25 18:08:49 +08:00
手瓜一十雪
9dbbcf3872 build: 1.6.8 - parse appid 2024-07-25 17:57:39 +08:00
手瓜一十雪
6b3343e1e4 build: 1.6.8 beta6 2024-07-25 10:59:06 +08:00
手瓜一十雪
ef48f754a5 docs: 整理当前进度 2024-07-25 10:44:53 +08:00
手瓜一十雪
3be1ede847 refactor: sendtime/join time 2024-07-25 10:32:44 +08:00
手瓜一十雪
7bff1b61e8 refactor: SendTime 2024-07-25 10:02:16 +08:00
手瓜一十雪
6affd0eb68 feat: GetSendTime 2024-07-24 17:49:03 +08:00
手瓜一十雪
0a112d15e0 fix: typo 2024-07-24 15:37:23 +08:00
手瓜一十雪
4e03f582bb fix: richmeida name 2024-07-24 14:43:10 +08:00
手瓜一十雪
8f186c1c5e chore: action clean 2024-07-24 14:37:48 +08:00
手瓜一十雪
cd1bae9a1f fix: setGroupAvatar 2024-07-24 14:35:12 +08:00
手瓜一十雪
60796c26ca Merge pull request #147 from serfend/default-config
fix[config]support overwrite by user #145
2024-07-24 14:28:42 +08:00
汉广
28927f950d fix[config]support overwrite by user 2024-07-24 14:25:58 +08:00
手瓜一十雪
95f16ebc8c Merge pull request #144 from Guation/main
feat: http与ws允许监听同一端口,快速登录允许自动选择QQ号,允许禁用webUI
2024-07-24 14:09:19 +08:00
挂神
25bca8385d feat: http与ws共站支持热重载 2024-07-24 13:14:35 +08:00
手瓜一十雪
965c7f23b4 feat: 群头像设置 2024-07-24 11:37:12 +08:00
手瓜一十雪
33082af9cc feat: searchFile 2024-07-24 11:23:27 +08:00
手瓜一十雪
1f7f3565b0 build: 1.6.8 beta05 2024-07-24 10:45:11 +08:00
手瓜一十雪
f784363696 refactor: UUID 2024-07-24 10:44:55 +08:00
手瓜一十雪
37bd51e138 refactor: getUserInfo 2024-07-24 10:42:22 +08:00
手瓜一十雪
c367728c43 Merge branch 'main' of https://github.com/NapNeko/NapCatQQ 2024-07-24 10:23:48 +08:00
手瓜一十雪
61f0f5d884 refactor: 改造接口调用 2024-07-24 10:23:41 +08:00
手瓜一十雪
5f2ebeead7 docs: update 2024-07-23 18:32:07 +08:00
手瓜一十雪
7646037fc7 docs: 砍掉 2024-07-23 18:21:19 +08:00
手瓜一十雪
eac6d285ff chore: debug 2024-07-23 17:39:00 +08:00
手瓜一十雪
b921d5e734 refactor: downloadMedia 2024-07-23 16:15:23 +08:00
手瓜一十雪
831d808e63 chore: remove 2024-07-23 16:03:06 +08:00
手瓜一十雪
451b88d7e3 refactor: video type 2024-07-23 15:51:57 +08:00
手瓜一十雪
f916682a71 build: 1.6.8 beta07 2024-07-23 15:38:41 +08:00
手瓜一十雪
2d76bcf0cf refactor: message id 2024-07-23 15:10:39 +08:00
手瓜一十雪
8db294efe6 refactor: 转发消息修复 2024-07-23 14:54:05 +08:00
手瓜一十雪
5cc5149aed fix: 合并转发 2024-07-23 14:19:26 +08:00
手瓜一十雪
7ecb01dc9f docs: 规划 2024-07-23 12:34:20 +08:00
手瓜一十雪
8bf1a545d9 chore: remove debug 2024-07-23 10:12:20 +08:00
手瓜一十雪
efb2be2f94 fix: timeout 2024-07-23 09:50:31 +08:00
手瓜一十雪
bd2edda494 refactor: sendMsg 2024-07-23 09:45:00 +08:00
手瓜一十雪
991172eae4 Merge branch 'main' of https://github.com/NapNeko/NapCatQQ 2024-07-23 09:21:36 +08:00
手瓜一十雪
fc7631f9aa refactor: sendmsg 2024-07-23 09:21:22 +08:00
手瓜一十雪
a21efb7d2f Merge pull request #145 from serfend/default-config
fix[default-config]config name check #138
2024-07-22 21:36:54 +08:00
汉广
03bc844ad0 fix[default-config]config name check 2024-07-22 20:12:24 +08:00
挂神
f7bdc35ed6 feat: http与ws允许监听同一端口,快速登录允许自动选择QQ号,允许禁用webUI 2024-07-22 19:47:23 +08:00
手瓜一十雪
0efdffd857 Merge branch 'main' of https://github.com/NapNeko/NapCatQQ 2024-07-22 18:49:36 +08:00
手瓜一十雪
6a16a42d0c feat: refactor send 2024-07-22 18:49:25 +08:00
手瓜一十雪
e9c00c72b1 Merge pull request #138 from serfend/main
feat[config]support use default-template
2024-07-22 18:22:49 +08:00
手瓜一十雪
ab22f36b8a refactor: NTEvent Checker 2024-07-22 18:21:29 +08:00
手瓜一十雪
c57497cd91 feat: remove debug 2024-07-22 18:17:33 +08:00
手瓜一十雪
ba123236e5 feat:msgid generate 2024-07-22 18:17:03 +08:00
手瓜一十雪
338b6e4607 fix: QRCode 2024-07-22 15:46:48 +08:00
手瓜一十雪
f88c717560 build: 1.6.8-beta03 2024-07-22 15:40:41 +08:00
手瓜一十雪
f8ffc92db5 feat: remove LineDev&&Protobuf 2024-07-22 15:40:23 +08:00
手瓜一十雪
98c23c172c build: 1.6.8-无数据库版本 2024-07-22 15:13:38 +08:00
手瓜一十雪
781c107d8c feat: 拉取重启消息 2024-07-22 15:12:25 +08:00
手瓜一十雪
186668c075 fix: Login 2024-07-22 14:18:04 +08:00
手瓜一十雪
cf9f785193 style: lint 2024-07-22 14:12:03 +08:00
手瓜一十雪
72d2d3f224 feat: 破坏file/db相关接口 2024-07-22 14:09:37 +08:00
手瓜一十雪
087c76b394 refactor: msgId stage-2 2024-07-22 11:34:18 +08:00
手瓜一十雪
4f9fb2c8c3 Merge pull request #141 from cnxysoft/main
修复提交疏漏
2024-07-22 11:15:28 +08:00
手瓜一十雪
334e43e764 refactor: MsgId 2024-07-22 11:15:01 +08:00
Alen
7843256402 修复提交疏漏
修复变量类型未断言的问题
2024-07-22 11:07:33 +08:00
手瓜一十雪
0522ba35fe refactor: jest test 2024-07-22 10:24:55 +08:00
手瓜一十雪
24d3b52e0b refactor: Message Unique 2024-07-22 09:56:08 +08:00
手瓜一十雪
3177110f0f feat: RecentContact 2024-07-22 09:24:16 +08:00
手瓜一十雪
e1b8243a67 Merge pull request #140 from cnxysoft/main
BUG修复
2024-07-22 08:40:21 +08:00
Alen
b1c6ce3885 BUG修复
1.尝试让所有人能收到group_admin事件
2.修复请求API: delete_msg(POST请求网址传参)将负数判定为文本导致无法调用的问题
2024-07-22 01:22:38 +08:00
手瓜一十雪
0b4b25a11e feat: LineDev for Develop-0 2024-07-21 19:31:13 +08:00
手瓜一十雪
1176fe984a add: RecentListener 2024-07-21 19:01:47 +08:00
汉广
6ca768c3ee feat[config]support use default-template 2024-07-20 23:43:32 +08:00
131 changed files with 4415 additions and 2470 deletions

386
LICENSE
View File

@@ -1,21 +1,373 @@
MIT License Mozilla Public License Version 2.0
==================================
Copyright (c) 2024 NapCatQQ 1. Definitions
--------------
Permission is hereby granted, free of charge, to any person obtaining a copy 1.1. "Contributor"
of this software and associated documentation files (the "Software"), to deal means each individual or legal entity that creates, contributes to
in the Software without restriction, including without limitation the rights the creation of, or owns Covered Software.
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all 1.2. "Contributor Version"
copies or substantial portions of the Software. means the combination of the Contributions of others (if any) used
by a Contributor and that particular Contributor's Contribution.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 1.3. "Contribution"
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, means Covered Software of a particular Contributor.
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 1.4. "Covered Software"
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, means Source Code Form to which the initial Contributor has attached
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE the notice in Exhibit A, the Executable Form of such Source Code
SOFTWARE. Form, and Modifications of such Source Code Form, in each case
including portions thereof.
1.5. "Incompatible With Secondary Licenses"
means
(a) that the initial Contributor has attached the notice described
in Exhibit B to the Covered Software; or
(b) that the Covered Software was made available under the terms of
version 1.1 or earlier of the License, but not also under the
terms of a Secondary License.
1.6. "Executable Form"
means any form of the work other than Source Code Form.
1.7. "Larger Work"
means a work that combines Covered Software with other material, in
a separate file or files, that is not Covered Software.
1.8. "License"
means this document.
1.9. "Licensable"
means having the right to grant, to the maximum extent possible,
whether at the time of the initial grant or subsequently, any and
all of the rights conveyed by this License.
1.10. "Modifications"
means any of the following:
(a) any file in Source Code Form that results from an addition to,
deletion from, or modification of the contents of Covered
Software; or
(b) any new file in Source Code Form that contains any Covered
Software.
1.11. "Patent Claims" of a Contributor
means any patent claim(s), including without limitation, method,
process, and apparatus claims, in any patent Licensable by such
Contributor that would be infringed, but for the grant of the
License, by the making, using, selling, offering for sale, having
made, import, or transfer of either its Contributions or its
Contributor Version.
1.12. "Secondary License"
means either the GNU General Public License, Version 2.0, the GNU
Lesser General Public License, Version 2.1, the GNU Affero General
Public License, Version 3.0, or any later versions of those
licenses.
1.13. "Source Code Form"
means the form of the work preferred for making modifications.
1.14. "You" (or "Your")
means an individual or a legal entity exercising rights under this
License. For legal entities, "You" includes any entity that
controls, is controlled by, or is under common control with You. For
purposes of this definition, "control" means (a) the power, direct
or indirect, to cause the direction or management of such entity,
whether by contract or otherwise, or (b) ownership of more than
fifty percent (50%) of the outstanding shares or beneficial
ownership of such entity.
2. License Grants and Conditions
--------------------------------
2.1. Grants
Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:
(a) under intellectual property rights (other than patent or trademark)
Licensable by such Contributor to use, reproduce, make available,
modify, display, perform, distribute, and otherwise exploit its
Contributions, either on an unmodified basis, with Modifications, or
as part of a Larger Work; and
(b) under Patent Claims of such Contributor to make, use, sell, offer
for sale, have made, import, and otherwise transfer either its
Contributions or its Contributor Version.
2.2. Effective Date
The licenses granted in Section 2.1 with respect to any Contribution
become effective for each Contribution on the date the Contributor first
distributes such Contribution.
2.3. Limitations on Grant Scope
The licenses granted in this Section 2 are the only rights granted under
this License. No additional rights or licenses will be implied from the
distribution or licensing of Covered Software under this License.
Notwithstanding Section 2.1(b) above, no patent license is granted by a
Contributor:
(a) for any code that a Contributor has removed from Covered Software;
or
(b) for infringements caused by: (i) Your and any other third party's
modifications of Covered Software, or (ii) the combination of its
Contributions with other software (except as part of its Contributor
Version); or
(c) under Patent Claims infringed by Covered Software in the absence of
its Contributions.
This License does not grant any rights in the trademarks, service marks,
or logos of any Contributor (except as may be necessary to comply with
the notice requirements in Section 3.4).
2.4. Subsequent Licenses
No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this
License (see Section 10.2) or under the terms of a Secondary License (if
permitted under the terms of Section 3.3).
2.5. Representation
Each Contributor represents that the Contributor believes its
Contributions are its original creation(s) or it has sufficient rights
to grant the rights to its Contributions conveyed by this License.
2.6. Fair Use
This License is not intended to limit any rights You have under
applicable copyright doctrines of fair use, fair dealing, or other
equivalents.
2.7. Conditions
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
in Section 2.1.
3. Responsibilities
-------------------
3.1. Distribution of Source Form
All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under
the terms of this License. You must inform recipients that the Source
Code Form of the Covered Software is governed by the terms of this
License, and how they can obtain a copy of this License. You may not
attempt to alter or restrict the recipients' rights in the Source Code
Form.
3.2. Distribution of Executable Form
If You distribute Covered Software in Executable Form then:
(a) such Covered Software must also be made available in Source Code
Form, as described in Section 3.1, and You must inform recipients of
the Executable Form how they can obtain a copy of such Source Code
Form by reasonable means in a timely manner, at a charge no more
than the cost of distribution to the recipient; and
(b) You may distribute such Executable Form under the terms of this
License, or sublicense it under different terms, provided that the
license for the Executable Form does not attempt to limit or alter
the recipients' rights in the Source Code Form under this License.
3.3. Distribution of a Larger Work
You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for
the Covered Software. If the Larger Work is a combination of Covered
Software with a work governed by one or more Secondary Licenses, and the
Covered Software is not Incompatible With Secondary Licenses, this
License permits You to additionally distribute such Covered Software
under the terms of such Secondary License(s), so that the recipient of
the Larger Work may, at their option, further distribute the Covered
Software under the terms of either this License or such Secondary
License(s).
3.4. Notices
You may not remove or alter the substance of any license notices
(including copyright notices, patent notices, disclaimers of warranty,
or limitations of liability) contained within the Source Code Form of
the Covered Software, except that You may alter any license notices to
the extent required to remedy known factual inaccuracies.
3.5. Application of Additional Terms
You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on
behalf of any Contributor. You must make it absolutely clear that any
such warranty, support, indemnity, or liability obligation is offered by
You alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.
4. Inability to Comply Due to Statute or Regulation
---------------------------------------------------
If it is impossible for You to comply with any of the terms of this
License with respect to some or all of the Covered Software due to
statute, judicial order, or regulation then You must: (a) comply with
the terms of this License to the maximum extent possible; and (b)
describe the limitations and the code they affect. Such description must
be placed in a text file included with all distributions of the Covered
Software under this License. Except to the extent prohibited by statute
or regulation, such description must be sufficiently detailed for a
recipient of ordinary skill to be able to understand it.
5. Termination
--------------
5.1. The rights granted under this License will terminate automatically
if You fail to comply with any of its terms. However, if You become
compliant, then the rights granted under this License from a particular
Contributor are reinstated (a) provisionally, unless and until such
Contributor explicitly and finally terminates Your grants, and (b) on an
ongoing basis, if such Contributor fails to notify You of the
non-compliance by some reasonable means prior to 60 days after You have
come back into compliance. Moreover, Your grants from a particular
Contributor are reinstated on an ongoing basis if such Contributor
notifies You of the non-compliance by some reasonable means, this is the
first time You have received notice of non-compliance with this License
from such Contributor, and You become compliant prior to 30 days after
Your receipt of the notice.
5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions,
counter-claims, and cross-claims) alleging that a Contributor Version
directly or indirectly infringes any patent, then the rights granted to
You by any and all Contributors for the Covered Software under Section
2.1 of this License shall terminate.
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
end user license agreements (excluding distributors and resellers) which
have been validly granted by You or Your distributors under this License
prior to termination shall survive termination.
************************************************************************
* *
* 6. Disclaimer of Warranty *
* ------------------------- *
* *
* Covered Software is provided under this License on an "as is" *
* basis, without warranty of any kind, either expressed, implied, or *
* statutory, including, without limitation, warranties that the *
* Covered Software is free of defects, merchantable, fit for a *
* particular purpose or non-infringing. The entire risk as to the *
* quality and performance of the Covered Software is with You. *
* Should any Covered Software prove defective in any respect, You *
* (not any Contributor) assume the cost of any necessary servicing, *
* repair, or correction. This disclaimer of warranty constitutes an *
* essential part of this License. No use of any Covered Software is *
* authorized under this License except under this disclaimer. *
* *
************************************************************************
************************************************************************
* *
* 7. Limitation of Liability *
* -------------------------- *
* *
* Under no circumstances and under no legal theory, whether tort *
* (including negligence), contract, or otherwise, shall any *
* Contributor, or anyone who distributes Covered Software as *
* permitted above, be liable to You for any direct, indirect, *
* special, incidental, or consequential damages of any character *
* including, without limitation, damages for lost profits, loss of *
* goodwill, work stoppage, computer failure or malfunction, or any *
* and all other commercial damages or losses, even if such party *
* shall have been informed of the possibility of such damages. This *
* limitation of liability shall not apply to liability for death or *
* personal injury resulting from such party's negligence to the *
* extent applicable law prohibits such limitation. Some *
* jurisdictions do not allow the exclusion or limitation of *
* incidental or consequential damages, so this exclusion and *
* limitation may not apply to You. *
* *
************************************************************************
8. Litigation
-------------
Any litigation relating to this License may be brought only in the
courts of a jurisdiction where the defendant maintains its principal
place of business and such litigation shall be governed by laws of that
jurisdiction, without reference to its conflict-of-law provisions.
Nothing in this Section shall prevent a party's ability to bring
cross-claims or counter-claims.
9. Miscellaneous
----------------
This License represents the complete agreement concerning the subject
matter hereof. If any provision of this License is held to be
unenforceable, such provision shall be reformed only to the extent
necessary to make it enforceable. Any law or regulation which provides
that the language of a contract shall be construed against the drafter
shall not be used to construe this License against a Contributor.
10. Versions of the License
---------------------------
10.1. New Versions
Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a
distinguishing version number.
10.2. Effect of New Versions
You may distribute the Covered Software under the terms of the version
of the License under which You originally received the Covered Software,
or under the terms of any subsequent version published by the license
steward.
10.3. Modified Versions
If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a
modified version of this License if you rename the license and remove
any references to the name of the license steward (except to note that
such modified license differs from this License).
10.4. Distributing Source Code Form that is Incompatible With Secondary
Licenses
If You choose to distribute Source Code Form that is Incompatible With
Secondary Licenses under the terms of this version of the License, the
notice described in Exhibit B of this License must be attached.
Exhibit A - Source Code Form License Notice
-------------------------------------------
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
If it is not possible or desirable to put the notice in a particular
file, then You may include the notice in a location (such as a LICENSE
file in a relevant directory) where a recipient would be likely to look
for such a notice.
You may add additional accurate notices of copyright ownership.
Exhibit B - "Incompatible With Secondary Licenses" Notice
---------------------------------------------------------
This Source Code Form is "Incompatible With Secondary Licenses", as
defined by the Mozilla Public License, v. 2.0.

View File

@@ -1,25 +0,0 @@
# v1.6.7
QQ Version: Windows 9.9.12-26000 / Linux 3.2.9-26000
## 使用前警告
1. 在最近版本由于QQ本体大幅变动为了保证NapCat可用性NapCat近期启动与安装方式将将大幅变动请关注文档和社群获取。
2. 在Core上完全执行开源请不要用于违法用途如此可能造成NapCat完全停止更新。
3. 针对原启动方式的围堵NapCat研发了多种方式除此其余理论与扩展的分析和思路将部分展示于Docs以便各位参与开发与维护NapCat。
## 其余·备注
启动方式: WayBoot.03 Electron Main进程为Node 直接注入代码 同理项目: LiteLoader
## 修复与优化
1. 尝试 修复 卡顿问题
2. 尝试 修复 精华消息被设置/一起听 接收时的报错
3. 优化 Uin与Uid 转换速度
4. 修复CQCode可能存在的解码问题
## 新增与调整
1. 戳一戳上报raw
2. 精华消息设置通知事件
3. 新增设置/删除群精华API
4. 新增最近联系列表APIRAW 不稳定)
5. 新增设置所有消息已读API非标准
6. 新增获取点赞信息获取API非标准
新增的 API 详细见[API文档](https://napneko.github.io/zh-CN/develop/extends_api)

View File

@@ -0,0 +1,12 @@
# v1.7.8
QQ Version: Windows 9.9.15-26702 / Linux 3.2.12-26702
## 启动的方式
Way03/Way05
## 新增与调整
1. 彻底支持发言时间与入群时间 For 26702
2. 修复转发接口异常问题
新增的 API 详细见[API文档](https://napneko.github.io/zh-CN/develop/extends_api)

View File

@@ -1,3 +1,4 @@
public static final int C2C_PIC_DOWNLOAD = 1004; public static final int C2C_PIC_DOWNLOAD = 1004;
public static final String C2C_PIC_DOWNLOAD_DOMAIN = "c2cpicdw.qpic.cn"; public static final String C2C_PIC_DOWNLOAD_DOMAIN = "c2cpicdw.qpic.cn";
public static final String C2C_PIC_DOWNLOAD_QUIC_DOMAIN = "c2cpicdw.quic.qpic.cn"; public static final String C2C_PIC_DOWNLOAD_QUIC_DOMAIN = "c2cpicdw.quic.qpic.cn";

View File

@@ -1 +1,8 @@
# Api方向
## getMsgUniqueId √ 已应用
getMsgUniqueId 传入时间 产出一个唯一ID 发送消息作为一个参数 getMsgUniqueId 传入时间 产出一个唯一ID 发送消息作为一个参数
# Native方向
## magic_load
## api_caller
## NodeMain

View File

@@ -2,7 +2,7 @@
"name": "napcat", "name": "napcat",
"private": true, "private": true,
"type": "module", "type": "module",
"version": "1.6.7", "version": "1.7.8",
"scripts": { "scripts": {
"watch:dev": "vite --mode development", "watch:dev": "vite --mode development",
"watch:prod": "vite --mode production", "watch:prod": "vite --mode production",
@@ -19,10 +19,9 @@
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.24.7", "@babel/core": "^7.24.7",
"@babel/preset-typescript": "^7.24.7",
"vite-plugin-babel": "^1.2.0",
"@babel/plugin-proposal-class-properties": "^7.18.6", "@babel/plugin-proposal-class-properties": "^7.18.6",
"@babel/plugin-proposal-decorators": "^7.24.7", "@babel/plugin-proposal-decorators": "^7.24.7",
"@babel/preset-typescript": "^7.24.7",
"@log4js-node/log4js-api": "^1.0.2", "@log4js-node/log4js-api": "^1.0.2",
"@protobuf-ts/plugin": "^2.9.4", "@protobuf-ts/plugin": "^2.9.4",
"@rollup/plugin-node-resolve": "^15.2.3", "@rollup/plugin-node-resolve": "^15.2.3",
@@ -31,9 +30,9 @@
"@types/express": "^4.17.21", "@types/express": "^4.17.21",
"@types/figlet": "^1.5.8", "@types/figlet": "^1.5.8",
"@types/fluent-ffmpeg": "^2.1.24", "@types/fluent-ffmpeg": "^2.1.24",
"@types/node": "^20.11.30", "@types/jest": "^29.5.12",
"@types/node": "^22.0.0",
"@types/qrcode-terminal": "^0.12.2", "@types/qrcode-terminal": "^0.12.2",
"@types/uuid": "^10.0.0",
"@types/ws": "^8.5.10", "@types/ws": "^8.5.10",
"@typescript-eslint/eslint-plugin": "^7.4.0", "@typescript-eslint/eslint-plugin": "^7.4.0",
"@typescript-eslint/parser": "^7.4.0", "@typescript-eslint/parser": "^7.4.0",
@@ -47,6 +46,7 @@
"rollup-plugin-obfuscator": "^1.1.0", "rollup-plugin-obfuscator": "^1.1.0",
"typescript": "^5.3.3", "typescript": "^5.3.3",
"vite": "^5.2.6", "vite": "^5.2.6",
"vite-plugin-babel": "^1.2.0",
"vite-plugin-cp": "^4.0.8", "vite-plugin-cp": "^4.0.8",
"vite-plugin-dts": "^3.8.2", "vite-plugin-dts": "^3.8.2",
"vite-tsconfig-paths": "^4.3.2" "vite-tsconfig-paths": "^4.3.2"
@@ -65,8 +65,6 @@
"log4js": "^6.9.1", "log4js": "^6.9.1",
"qrcode-terminal": "^0.12.0", "qrcode-terminal": "^0.12.0",
"silk-wasm": "^3.6.1", "silk-wasm": "^3.6.1",
"sqlite3": "^5.1.7",
"uuid": "^10.0.0",
"ws": "^8.16.0" "ws": "^8.16.0"
} }
} }

View File

@@ -42,4 +42,4 @@ if (!(Test-Path $QQpath)) {
$Bootfile = Join-Path $PSScriptRoot "napcat.mjs" $Bootfile = Join-Path $PSScriptRoot "napcat.mjs"
$env:ELECTRON_RUN_AS_NODE = 1 $env:ELECTRON_RUN_AS_NODE = 1
$commandInfo = Get-Command $QQpath -ErrorAction Stop $commandInfo = Get-Command $QQpath -ErrorAction Stop
Start-Process powershell -ArgumentList "-noexit", "-noprofile", "-command &{& chcp 65001;& '$($commandInfo.Path)' --enable-logging $params}" Start-Process powershell -ArgumentList "-noexit", "-noprofile", "-command &{& chcp 65001;& '$($commandInfo.Path)' --enable-logging }"

123
script/BootWay05.ps1 Normal file
View File

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

BIN
script/dbghelp.dll Normal file

Binary file not shown.

View File

@@ -9,7 +9,15 @@ type RegisterHandler = (res: Response, payload: any) => Promise<any>
export abstract class HttpServerBase { export abstract class HttpServerBase {
name: string = 'NapCatQQ'; name: string = 'NapCatQQ';
private readonly expressAPP: Express; private readonly expressAPP: Express;
private server: http.Server | null = null; private _server: http.Server | null = null;
public get server(): http.Server | null {
return this._server;
}
private set server(value: http.Server | null) {
this._server = value;
}
constructor() { constructor() {
this.expressAPP = express(); this.expressAPP = express();
@@ -78,7 +86,7 @@ export abstract class HttpServerBase {
this.start(port, host); this.start(port, host);
} }
abstract handleFailed(res: Response, payload: any, err: any): void abstract handleFailed(res: Response, payload: any, err: Error): void
registerRouter(method: 'post' | 'get' | string, url: string, handler: RegisterHandler) { registerRouter(method: 'post' | 'get' | string, url: string, handler: RegisterHandler) {
if (!url.startsWith('/')) { if (!url.startsWith('/')) {
@@ -103,7 +111,7 @@ export abstract class HttpServerBase {
try { try {
res.send(await handler(res, payload)); res.send(await handler(res, payload));
} catch (e: any) { } catch (e: any) {
this.handleFailed(res, payload, e.stack.toString()); this.handleFailed(res, payload, e);
} }
}); });
} }

View File

@@ -1,4 +1,5 @@
import { WebSocket, WebSocketServer } from 'ws'; import { WebSocket, WebSocketServer } from 'ws';
import http from 'http';
import urlParse from 'url'; import urlParse from 'url';
import { IncomingMessage } from 'node:http'; import { IncomingMessage } from 'node:http';
import { log } from '@/common/utils/log'; import { log } from '@/common/utils/log';
@@ -27,17 +28,36 @@ export class WebsocketServerBase {
constructor() { constructor() {
} }
start(port: number, host: string = '') { start(port: number | http.Server, host: string = '') {
try { if (port instanceof http.Server) {
this.ws = new WebSocketServer({ try {
port, const wss = new WebSocketServer({
host: '', noServer: true,
maxPayload: 1024 * 1024 * 1024 maxPayload: 1024 * 1024 * 1024
}).on('error', () => { }).on('error', () => {
}); });
log(`ws服务启动成功, ${host}:${port}`); this.ws = wss;
} catch (e: any) { port.on('upgrade', function upgrade(request, socket, head) {
throw Error('ws服务启动失败, 请检查监听的ip和端口' + e.toString()); wss.handleUpgrade(request, socket, head, function done(ws) {
wss.emit('connection', ws, request);
});
});
log('ws服务启动成功, 绑定到HTTP服务');
} catch (e: any) {
throw Error('ws服务启动失败, 可能是绑定的HTTP服务异常' + e.toString());
}
} else {
try {
this.ws = new WebSocketServer({
port,
host: '',
maxPayload: 1024 * 1024 * 1024
}).on('error', () => {
});
log(`ws服务启动成功, ${host}:${port}`);
} catch (e: any) {
throw Error('ws服务启动失败, 请检查监听的ip和端口' + e.toString());
}
} }
this.ws.on('connection', (wsClient, req) => { this.ws.on('connection', (wsClient, req) => {
const url: string = req.url!.split('?').shift() || '/'; const url: string = req.url!.split('?').shift() || '/';
@@ -50,10 +70,12 @@ export class WebsocketServerBase {
} }
stop() { stop() {
this.ws && this.ws.close((err) => { if (this.ws) {
log('ws server close failed!', err); this.ws.close((err) => {
}); if (err) log('ws server close failed!', err);
this.ws = null; });
this.ws = null;
}
} }
restart(port: number) { restart(port: number) {

View File

@@ -1,36 +0,0 @@
import { sleep } from '@/common/utils/helper';
import { logError } from './log';
type AsyncQueueTask = (() => void) | (()=>Promise<void>);
// 2024.7.13 废弃
export class AsyncQueue {
private tasks: (AsyncQueueTask)[] = [];
public addTask(task: AsyncQueueTask) {
this.tasks.push(task);
// console.log('addTask', this.tasks.length);
if (this.tasks.length === 1) {
this.runQueue().then().catch(()=>{});
}
}
private async runQueue() {
// console.log('runQueue', this.tasks.length);
while (this.tasks.length > 0) {
const task = this.tasks[0];
// console.log('typeof task', typeof task);
try {
const taskRet = task();
// console.log('type of taskRet', typeof taskRet, taskRet);
if (taskRet instanceof Promise) {
await taskRet;
}
} catch (e) {
// console.error(e);
logError(e);
}
this.tasks.shift();
await sleep(100);
}
}
}

View File

@@ -3,6 +3,7 @@ import fs from 'node:fs';
import { log, logDebug, logError } from '@/common/utils/log'; import { log, logDebug, logError } from '@/common/utils/log';
import { dirname } from 'node:path'; import { dirname } from 'node:path';
import { fileURLToPath } from 'node:url'; import { fileURLToPath } from 'node:url';
import { selfInfo } from '@/core/data';
const __filename = fileURLToPath(import.meta.url); const __filename = fileURLToPath(import.meta.url);
@@ -12,8 +13,9 @@ const configDir = path.resolve(__dirname, 'config');
fs.mkdirSync(configDir, { recursive: true }); fs.mkdirSync(configDir, { recursive: true });
export class ConfigBase<T>{ export class ConfigBase<T> {
public name: string = 'default_config';
private pathName: string | null = null; // 本次读取的文件路径
constructor() { constructor() {
} }
@@ -22,19 +24,28 @@ export class ConfigBase<T>{
return null; return null;
} }
getConfigDir(){ getConfigDir() {
const configDir = path.resolve(__dirname, 'config'); const configDir = path.resolve(__dirname, 'config');
fs.mkdirSync(configDir, { recursive: true }); fs.mkdirSync(configDir, { recursive: true });
return configDir; return configDir;
} }
getConfigPath(): string { getConfigPath(pathName: string | null): string {
throw new Error('Method not implemented.'); const suffix = pathName ? `_${pathName}` : '';
const filename = `${this.name}${suffix}.json`;
return path.join(this.getConfigDir(), filename);
} }
read() { read() {
const configPath = this.getConfigPath(); // 尝试加载当前账号配置
if (this.read_from_file(selfInfo.uin, false)) return this;
// 尝试加载默认配置
return this.read_from_file('', true);
}
read_from_file(pathName: string, createIfNotExist: boolean) {
const configPath = this.getConfigPath(pathName);
if (!fs.existsSync(configPath)) { if (!fs.existsSync(configPath)) {
try{ if (!createIfNotExist) return null;
this.pathName = pathName; // 记录有效的设置文件
try {
fs.writeFileSync(configPath, JSON.stringify(this, this.getKeys(), 2)); fs.writeFileSync(configPath, JSON.stringify(this, this.getKeys(), 2));
log(`配置文件${configPath}已创建\n如果修改此文件后需要重启 NapCat 生效`); log(`配置文件${configPath}已创建\n如果修改此文件后需要重启 NapCat 生效`);
} }
@@ -43,6 +54,7 @@ export class ConfigBase<T>{
} }
return this; return this;
} }
try { try {
const data = JSON.parse(fs.readFileSync(configPath, 'utf-8')); const data = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
logDebug(`配置文件${configPath}已加载`, data); logDebug(`配置文件${configPath}已加载`, data);
@@ -61,9 +73,13 @@ export class ConfigBase<T>{
} }
} }
save(config: T) { save(config: T, overwrite: boolean = false) {
Object.assign(this, config); Object.assign(this, config);
const configPath = this.getConfigPath(); if (overwrite) {
// 用户要求强制写入,则变更当前文件为目标文件
this.pathName = `${selfInfo.uin}`;
}
const configPath = this.getConfigPath(this.pathName);
try { try {
fs.writeFileSync(configPath, JSON.stringify(this, this.getKeys(), 2)); fs.writeFileSync(configPath, JSON.stringify(this, this.getKeys(), 2));
} catch (e: any) { } catch (e: any) {

View File

@@ -5,6 +5,7 @@ interface Internal_MapKey {
timeout: number, timeout: number,
createtime: number, createtime: number,
func: (...arg: any[]) => any, func: (...arg: any[]) => any,
checker: ((...args: any[]) => boolean) | undefined,
} }
export class ListenerClassBase { export class ListenerClassBase {
@@ -83,17 +84,19 @@ export class NTEventWrapper {
} }
//统一回调清理事件 //统一回调清理事件
async DispatcherListener(ListenerMainName: string, ListenerSubName: string, ...args: any[]) { async DispatcherListener(ListenerMainName: string, ListenerSubName: string, ...args: any[]) {
//console.log(ListenerMainName, this.EventTask.get(ListenerMainName), ListenerSubName, this.EventTask.get(ListenerMainName)?.get(ListenerSubName)); //console.log("[EventDispatcher]",ListenerMainName, ListenerSubName, ...args);
this.EventTask.get(ListenerMainName)?.get(ListenerSubName)?.forEach((task, uuid) => { this.EventTask.get(ListenerMainName)?.get(ListenerSubName)?.forEach((task, uuid) => {
//console.log(task.func, uuid, task.createtime, task.timeout); //console.log(task.func, uuid, task.createtime, task.timeout);
if (task.createtime + task.timeout < Date.now()) { if (task.createtime + task.timeout < Date.now()) {
this.EventTask.get(ListenerMainName)?.get(ListenerSubName)?.delete(uuid); this.EventTask.get(ListenerMainName)?.get(ListenerSubName)?.delete(uuid);
return; return;
} }
task.func(...args); if (task.checker && task.checker(...args)) {
task.func(...args);
}
}); });
} }
async CallNoListenerEvent<EventType extends (...args: any[]) => Promise<any>,>(EventName = '', timeout: number = 3000, ...args: Parameters<EventType>) { 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) => { return new Promise<Awaited<ReturnType<EventType>>>(async (resolve, reject) => {
const EventFunc = this.CreatEventFunction<EventType>(EventName); const EventFunc = this.CreatEventFunction<EventType>(EventName);
let complete = false; let complete = false;
@@ -107,27 +110,71 @@ export class NTEventWrapper {
resolve(retData); resolve(retData);
}); });
} }
async CallNormalEvent<EventType extends (...args: any[]) => Promise<any>, ListenerType extends (...args: any[]) => void>(EventName = '', ListenerName = '', waitTimes = 1, timeout: number = 3000, ...args: Parameters<EventType>) { async RegisterListen<ListenerType extends (...args: any[]) => void>(ListenerName = '', waitTimes = 1, timeout = 5000, checker: (...args: Parameters<ListenerType>) => boolean) {
return new Promise<Parameters<ListenerType>>((resolve, reject) => {
const ListenerNameList = ListenerName.split('/');
const ListenerMainName = ListenerNameList[0];
const ListenerSubName = ListenerNameList[1];
const id = randomUUID();
let complete = 0;
let retData: Parameters<ListenerType> | undefined = undefined;
const databack = () => {
if (complete == 0) {
reject(new Error(' ListenerName:' + ListenerName + ' timeout'));
} else {
resolve(retData!);
}
};
const Timeouter = setTimeout(databack, timeout);
const eventCallbak = {
timeout: timeout,
createtime: Date.now(),
checker: checker,
func: (...args: Parameters<ListenerType>) => {
complete++;
retData = args;
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.CreatListenerFunction(ListenerMainName);
});
}
async CallNormalEvent<EventType extends (...args: any[]) => Promise<any>, ListenerType extends (...args: any[]) => void>
(EventName = '', ListenerName = '', waitTimes = 1, timeout: number = 3000, checker: (...args: Parameters<ListenerType>) => boolean, ...args: Parameters<EventType>) {
return new Promise<[EventRet: Awaited<ReturnType<EventType>>, ...Parameters<ListenerType>]>(async (resolve, reject) => { return new Promise<[EventRet: Awaited<ReturnType<EventType>>, ...Parameters<ListenerType>]>(async (resolve, reject) => {
const id = randomUUID(); const id = randomUUID();
let complete = 0; let complete = 0;
let retData: Parameters<ListenerType> | undefined = undefined; let retData: Parameters<ListenerType> | undefined = undefined;
let retEvent: any = {}; let retEvent: any = {};
const databack = () => { const databack = () => {
if (complete < waitTimes) { if (complete == 0) {
reject(new Error('NTEvent EventName:' + EventName + ' ListenerName:' + ListenerName + ' timeout')); reject(new Error('Timeout: NTEvent EventName:' + EventName + ' ListenerName:' + ListenerName + ' EventRet:\n' + JSON.stringify(retEvent, null, 4) + '\n'));
} else { } else {
resolve([retEvent as Awaited<ReturnType<EventType>>, ...retData!]); resolve([retEvent as Awaited<ReturnType<EventType>>, ...retData!]);
} }
}; };
const Timeouter = setTimeout(databack, timeout);
const ListenerNameList = ListenerName.split('/'); const ListenerNameList = ListenerName.split('/');
const ListenerMainName = ListenerNameList[0]; const ListenerMainName = ListenerNameList[0];
const ListenerSubName = ListenerNameList[1]; const ListenerSubName = ListenerNameList[1];
const Timeouter = setTimeout(databack, timeout);
const eventCallbak = { const eventCallbak = {
timeout: timeout, timeout: timeout,
createtime: Date.now(), createtime: Date.now(),
checker: checker,
func: (...args: any[]) => { func: (...args: any[]) => {
complete++; complete++;
//console.log('func', ...args); //console.log('func', ...args);
@@ -147,7 +194,6 @@ export class NTEventWrapper {
this.EventTask.get(ListenerMainName)?.get(ListenerSubName)?.set(id, eventCallbak); this.EventTask.get(ListenerMainName)?.get(ListenerSubName)?.set(id, eventCallbak);
this.CreatListenerFunction(ListenerMainName); this.CreatListenerFunction(ListenerMainName);
const EventFunc = this.CreatEventFunction<EventType>(EventName); const EventFunc = this.CreatEventFunction<EventType>(EventName);
//console.log("测试打点", args);
retEvent = await EventFunc!(...(args as any[])); retEvent = await EventFunc!(...(args as any[]));
}); });
} }

View File

@@ -1,161 +0,0 @@
import { logError, logDebug } from '@/common/utils/log';
type group_id = number;
type user_id = number;
class cacheNode<T> {
value: T;
groupId: group_id;
userId: user_id;
prev: cacheNode<T> | null;
next: cacheNode<T> | null;
timestamp: number;
constructor(groupId: group_id, userId: user_id, value: T) {
this.groupId = groupId;
this.userId = userId;
this.value = value;
this.prev = null;
this.next = null;
this.timestamp = Date.now();
}
}
type cache<T, K = { [key: user_id]: cacheNode<T> }> = { [key: group_id]: K };
type removeObject<T> = cache<T, { userId: user_id, value: T }[]>
class LRU<T> {
private maxAge: number;
private maxSize: number;
private currentSize: number;
private cache: cache<T>;
private head: cacheNode<T> | null = null;
private tail: cacheNode<T> | null = null;
private onFuncs: ((node: removeObject<T>) => void)[] = [];
constructor(maxAge: number = 6e4 * 3, maxSize: number = 1e4) {
this.maxAge = maxAge;
this.maxSize = maxSize;
this.cache = Object.create(null);
this.currentSize = 0;
if (maxSize == 0) return;
setInterval(() => this.removeExpired(), this.maxAge);
}
// 移除LRU节点
private removeLRUNode(node: cacheNode<T>) {
logDebug(
'removeLRUNode',
node.groupId,
node.userId,
node.value,
this.currentSize
);
node.prev = node.next = null;
delete this.cache[node.groupId][node.userId];
this.removeNode(node);
this.onFuncs.forEach((func) => func({ [node.groupId]: [node] }));
this.currentSize--;
}
public on(func: (node: removeObject<T>) => void) {
this.onFuncs.push(func);
}
private removeExpired() {
const now = Date.now();
let current = this.tail;
let totalNodeNum = 0;
const removeObject: cache<T, { userId: user_id, value: T }[]> = {};
while (current && now - current.timestamp > this.maxAge) {
// 收集节点
if (!removeObject[current.groupId]) removeObject[current.groupId] = [];
removeObject[current.groupId].push({ userId: current.userId, value: current.value });
// 删除LRU节点
delete this.cache[current.groupId][current.userId];
current = current.prev;
totalNodeNum++;
this.currentSize--;
}
if (!totalNodeNum) return;
// 跟新链表指向
if (current) { current.next = null; } else { this.head = null; }
this.tail = current;
this.onFuncs.forEach(func => func(removeObject));
}
private addNode(node: cacheNode<T>) {
node.next = this.head;
if (this.head) this.head.prev = node;
if (!this.tail) this.tail = node;
this.head = node;
}
private removeNode(node: cacheNode<T>) {
if (node.prev) node.prev.next = node.next;
if (node.next) node.next.prev = node.prev;
if (node === this.head) this.head = node.next;
if (node === this.tail) this.tail = node.prev;
}
private moveToHead(node: cacheNode<T>) {
if (this.head === node) return;
this.removeNode(node);
this.addNode(node);
node.prev = null;
}
public set(groupId: group_id, userId: user_id, value: T) {
if (!this.cache[groupId]) {
this.cache[groupId] = Object.create(null);
}
const groupObject = this.cache[groupId];
if (groupObject[userId]) {
const node = groupObject[userId];
node.value = value;
node.timestamp = Date.now();
this.moveToHead(node);
} else {
const node = new cacheNode(groupId, userId, value);
groupObject[userId] = node;
this.currentSize++;
this.addNode(node);
if (this.currentSize > this.maxSize) {
const tail = this.tail!;
this.removeLRUNode(tail);
}
}
}
public get(groupId: group_id): { userId: user_id; value: T }[];
public get(groupId: group_id, userId: user_id): null | { userId: user_id; value: T };
public get(groupId: group_id, userId?: user_id): any {
const groupObject = this.cache[groupId];
if (!groupObject) return userId === undefined ? [] : null;
if (userId === undefined) {
return Object.entries(groupObject).map(([userId, { value }]) => ({
userId: Number(userId),
value,
}));
}
if (groupObject[userId]) {
return { userId, value: groupObject[userId].value };
}
return null;
}
}
export default LRU;

View File

@@ -1,20 +1,40 @@
import crypto from 'crypto'; import { Peer } from '@/core';
import crypto, { randomInt, randomUUID } from 'crypto';
import { logError } from './log';
class LimitedHashTable<K, V> { export class LimitedHashTable<K, V> {
private keyToValue: Map<K, V> = new Map(); private keyToValue: Map<K, V> = new Map();
private valueToKey: Map<V, K> = new Map(); private valueToKey: Map<V, K> = new Map();
private maxSize: number; private maxSize: number;
private KeyQueneList: K[] = [];
private ValueQueneList: V[] = [];
constructor(maxSize: number) { constructor(maxSize: number) {
this.maxSize = maxSize; this.maxSize = maxSize;
} }
resize(count: number) {
this.maxSize = count;
}
set(key: K, value: V): void { set(key: K, value: V): void {
// const isExist = this.keyToValue.get(key);
// if (isExist && isExist === value) {
// return;
// }
this.keyToValue.set(key, value); this.keyToValue.set(key, value);
this.valueToKey.set(value, key); this.valueToKey.set(value, key);
if (this.KeyQueneList.length >= this.maxSize || this.ValueQueneList.length >= this.maxSize) { while (this.keyToValue.size !== this.valueToKey.size) {
this.KeyQueneList.shift(); console.log('keyToValue.size !== valueToKey.size Error Atom');
this.ValueQueneList.shift(); 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);
} }
} }
@@ -26,28 +46,97 @@ class LimitedHashTable<K, V> {
return this.valueToKey.get(value); return this.valueToKey.get(value);
} }
delete(key: K): void { deleteByValue(value: V): void {
const key = this.valueToKey.get(value);
if (key !== undefined) {
this.keyToValue.delete(key);
this.valueToKey.delete(value);
}
}
deleteByKey(key: K): void {
const value = this.keyToValue.get(key); const value = this.keyToValue.get(key);
if (value !== undefined) { if (value !== undefined) {
this.keyToValue.delete(key); this.keyToValue.delete(key);
this.valueToKey.delete(value); this.valueToKey.delete(value);
} }
} }
getKeyList(): K[] {
return Array.from(this.keyToValue.keys());
}
//获取最近刚写入的几个值
getHeads(size: number): { key: K; value: V }[] | undefined {
const keyList = this.getKeyList();
if (keyList.length === 0) {
return undefined;
}
const result: { key: K; value: V }[] = [];
const listSize = Math.min(size, keyList.length);
for (let i = 0; i < listSize; i++) {
const key = keyList[listSize - i];
result.push({ key, value: this.keyToValue.get(key)! });
}
return result;
}
} }
class MessageUniqueWrapper { class MessageUniqueWrapper {
private msgIdMap: LimitedHashTable<number, string> = new LimitedHashTable(1000); private msgDataMap: LimitedHashTable<string, number>;
createMsg(MsgId: string) { private msgIdMap: LimitedHashTable<string, number>;
const ShortId = parseInt(crypto.createHash('sha1').update('2345').digest('hex').slice(0, 8), 16); constructor(maxMap: number = 1000) {
this.msgIdMap.set(ShortId, MsgId); this.msgIdMap = new LimitedHashTable<string, number>(maxMap);
return ShortId; this.msgDataMap = new LimitedHashTable<string, number>(maxMap);
} }
getMsgIdByShortId(ShortId: number) { getRecentMsgIds(Peer: Peer, size: number): string[] {
return this.msgIdMap.getValue(ShortId); const heads = this.msgIdMap.getHeads(size);
if (!heads) {
return [];
}
const data = heads.map((t) => MessageUnique.getMsgIdAndPeerByShortId(t.value));
const ret = data.filter((t) => t?.Peer.chatType === Peer.chatType && t?.Peer.peerUid === Peer.peerUid);
return ret.map((t) => t?.MsgId).filter((t) => t !== undefined);
} }
getShortIdByMsgId(MsgId: string) { createMsg(peer: Peer, msgId: string): number | undefined {
return this.msgIdMap.getKey(MsgId); const key = `${msgId}|${peer.chatType}|${peer.peerUid}`;
const hash = crypto.createHash('md5').update(key);
const shortId = hash.digest().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;
}
getMsgIdAndPeerByShortId(shortId: number): { MsgId: string; Peer: Peer } | undefined {
const data = this.msgDataMap.getKey(shortId);
if (data) {
const [msgId, chatTypeStr, peerUid] = data.split('|');
const peer: Peer = {
chatType: parseInt(chatTypeStr),
peerUid,
guildId: '',
};
return { MsgId: msgId, Peer: peer };
}
return undefined;
}
getShortIdByMsgId(msgId: string): number | undefined {
return this.msgIdMap.getValue(msgId);
}
getPeerByMsgId(msgId: string) {
const shortId = this.msgIdMap.getValue(msgId);
if (!shortId) return undefined;
return this.getMsgIdAndPeerByShortId(shortId);
}
resize(maxSize: number): void {
this.msgIdMap.resize(maxSize);
this.msgDataMap.resize(maxSize);
} }
} }
export const MessageUnique = new MessageUniqueWrapper(); export const MessageUnique: MessageUniqueWrapper = new MessageUniqueWrapper();

View File

@@ -1,62 +1,49 @@
import path from 'node:path'; import path from 'node:path';
import fs from 'node:fs'; import fs from 'node:fs';
import os from 'node:os';
import { systemPlatform } from '@/common/utils/system'; import { systemPlatform } from '@/common/utils/system';
import { logError } from '@/common/utils/log'; import { getDefaultQQVersionConfigInfo, getQQVersionConfigPath } from './helper';
import AppidTable from '@/core/external/appid.json';
import { log } from './log';
export const exePath = process.execPath; //基础目录获取
export let QQMainPath = process.execPath;
export let QQPackageInfoPath: string = path.join(path.dirname(QQMainPath), 'resources', 'app', 'package.json');
export let QQVersionConfigPath: string | undefined = getQQVersionConfigPath(QQMainPath);
export const pkgInfoPath = path.join(path.dirname(exePath), 'resources', 'app', 'package.json'); //基础信息获取 无快更则启用默认模板填充
let configVersionInfoPath; export let isQuickUpdate: boolean = !!QQVersionConfigPath;
export let QQVersionConfig: QQVersionConfigType = isQuickUpdate ? JSON.parse(fs.readFileSync(QQVersionConfigPath!).toString()) : getDefaultQQVersionConfigInfo();
export let QQPackageInfo: QQPackageInfoType = JSON.parse(fs.readFileSync(QQPackageInfoPath).toString());
export let { appid: QQVersionAppid, qua: QQVersionQua } = getAppidV2();
if (os.platform() !== 'linux') { //基础函数
configVersionInfoPath = path.join(path.dirname(exePath), 'resources', 'app', 'versions', 'config.json'); export function getQQBuildStr() {
} else { return isQuickUpdate ? QQVersionConfig.buildId : QQPackageInfo.buildVersion;
const userPath = os.homedir();
const appDataPath = path.resolve(userPath, './.config/QQ');
configVersionInfoPath = path.resolve(appDataPath, './versions/config.json');
} }
export function getFullQQVesion() {
if (typeof configVersionInfoPath !== 'string') { return isQuickUpdate ? QQVersionConfig.curVersion : QQPackageInfo.version;
throw new Error('Something went wrong when load QQ info path');
} }
export function requireMinNTQQBuild(buildStr: string) {
export { configVersionInfoPath }; return parseInt(getQQBuildStr()) >= parseInt(buildStr);
type QQPkgInfo = {
version: string;
buildVersion: string;
platform: string;
eleArch: string;
} }
type QQVersionConfigInfo = { //此方法不要直接使用
baseVersion: string; export function getQUAInternal() {
curVersion: string; return systemPlatform === 'linux' ? `V1_LNX_NQ_${getFullQQVesion()}_${getQQBuildStr()}_GW_B` : `V1_WIN_NQ_${getFullQQVesion()}_${getQQBuildStr()}_GW_B`;
prevVersion: string;
onErrorVersions: Array<any>;
buildId: string;
} }
export function getAppidV2(): { appid: string, qua: string } {
let _qqVersionConfigInfo: QQVersionConfigInfo = { let appidTbale = AppidTable as unknown as QQAppidTableType;
'baseVersion': '9.9.12-25765',
'curVersion': '9.9.12-25765',
'prevVersion': '',
'onErrorVersions': [],
'buildId': '25765'
};
if (fs.existsSync(configVersionInfoPath)) {
try { try {
const _ =JSON.parse(fs.readFileSync(configVersionInfoPath).toString()); let data = appidTbale[getFullQQVesion()];
_qqVersionConfigInfo = Object.assign(_qqVersionConfigInfo, _); if (data) {
} catch (e) { return data;
logError('Load QQ version config info failed, Use default version', e); }
} }
catch (e) {
log('[QQ版本兼容性检测] 版本兼容性不佳,可能会导致一些功能无法正常使用', e);
}
// 以下是兜底措施
return { appid: systemPlatform === 'linux' ? '537237950' : '537237765', qua: getQUAInternal() };
} }
export const qqVersionConfigInfo: QQVersionConfigInfo = _qqVersionConfigInfo;
//V1_WIN_NQ_9.9.12_25765_GW_B
export const qqPkgInfo: QQPkgInfo = JSON.parse(fs.readFileSync(pkgInfoPath).toString());
// platform_type: 3, // platform_type: 3,
// app_type: 4, // app_type: 4,
// app_version: '9.9.12-25765', // app_version: '9.9.12-25765',
@@ -64,14 +51,6 @@ export const qqPkgInfo: QQPkgInfo = JSON.parse(fs.readFileSync(pkgInfoPath).toSt
// appid: '537234702', // appid: '537234702',
// platVer: '10.0.26100', // platVer: '10.0.26100',
// clientVer: '9.9.9-25765', // clientVer: '9.9.9-25765',
// Linux // Linux
// app_version: '3.2.9-25765', // app_version: '3.2.9-25765',
// qua: 'V1_LNX_NQ_3.2.10_25765_GW_B', // qua: 'V1_LNX_NQ_3.2.10_25765_GW_B',
let _appid: string = '537234702'; // 默认为 Windows 平台的 appid
if (systemPlatform === 'linux') {
_appid = '537234773';
}
// todo: mac 平台的 appid
export const appid = _appid;

View File

@@ -3,7 +3,7 @@ import { encode, getDuration, getWavFileInfo, isWav, isSilk } from 'silk-wasm';
import fsPromise from 'fs/promises'; import fsPromise from 'fs/promises';
import { log, logError } from './log'; import { log, logError } from './log';
import path from 'node:path'; import path from 'node:path';
import { v4 as uuidv4 } from 'uuid'; import { randomUUID } from 'crypto';
import { spawn } from 'node:child_process'; import { spawn } from 'node:child_process';
import { getTempDir } from '@/common/utils/file'; import { getTempDir } from '@/common/utils/file';
@@ -64,7 +64,7 @@ export async function encodeSilk(filePath: string) {
try { try {
const file = await fsPromise.readFile(filePath); const file = await fsPromise.readFile(filePath);
const pttPath = path.join(TEMP_DIR, uuidv4()); const pttPath = path.join(TEMP_DIR, randomUUID());
if (!isSilk(file)) { if (!isSilk(file)) {
log(`语音文件${filePath}需要转换成silk`); log(`语音文件${filePath}需要转换成silk`);
const _isWav = isWav(file); const _isWav = isWav(file);

View File

@@ -1,477 +0,0 @@
import { ElementType, FileElement, PicElement, PttElement, RawMessage, VideoElement } from '../../core/src/entities';
import sqlite3 from 'sqlite3';
import { log, logDebug, logError } from '@/common/utils/log';
import { NTQQMsgApi } from '@/core';
import LRU from '@/common/utils/LRUCache';
export interface IRember {
last_sent_time: number;
join_time: number;
user_id: number;
}
type DBMsg = {
id: number,
shortId: number,
longId: string,
seq: number,
peerUid: string,
chatType: number,
}
type DBFile = {
name: string; // 文件名
path: string;
url: string;
size: number;
uuid: string;
msgId: string;
elementId: string;
element: PicElement | VideoElement | FileElement | PttElement;
elementType: ElementType.PIC | ElementType.VIDEO | ElementType.FILE | ElementType.PTT;
}
class DBUtilBase {
protected db: sqlite3.Database | undefined;
async init(dbPath: string) {
if (this.db) {
return;
}
return new Promise<void>((resolve, reject) => {
this.db = new sqlite3.Database(dbPath, sqlite3.OPEN_READWRITE | sqlite3.OPEN_CREATE, (err) => {
if (err) {
logError('Could not connect to database', err);
reject(err);
return;
}
this.createTable();
resolve();
});
});
}
protected createTable() {
throw new Error('Method not implemented.');
}
close() {
this.db?.close();
}
}
class DBUtil extends DBUtilBase {
private msgCache: Map<string | number, RawMessage> = new Map<string | number, RawMessage>();
private globalMsgShortId = -2147483640;
private groupIds: number[] = [];
private LURCache = new LRU<number>();
private LastSentCache = new (class {
private cache: { gid: number; uid: number }[] = [];
private maxSize: number;
constructor(maxSize: number = 50000) {
this.maxSize = maxSize;
}
get(gid: number, uid: number): boolean {
const exists = this.cache.some(
(entry) => entry.gid === gid && entry.uid === uid
);
if (!exists) {
this.cache.push({ gid, uid });
if (this.cache.length > this.maxSize) {
this.cache.shift();
}
}
return exists;
}
})();
constructor() {
super();
const interval = 1000 * 60 * 10; // 10分钟清理一次缓存
setInterval(() => {
logDebug('清理消息缓存');
this.msgCache.forEach((msg, key) => {
if ((Date.now() - parseInt(msg.msgTime) * 1000) > interval) {
this.msgCache.delete(key);
}
});
}, interval);
}
async init(dbPath: string) {
await super.init(dbPath);
this.globalMsgShortId = await this.getCurrentMaxShortId();
// 初始化群缓存列表
this.db!.serialize(() => {
const sql = 'SELECT * FROM sqlite_master WHERE type=\'table\'';
this.db!.all(sql, [], (err, rows: { name: string }[]) => {
if (err) return logError(err);
rows.forEach((row) => this.groupIds.push(parseInt(row.name)));
//logDebug(`已加载 ${groupIds.length} 个群`);
});
});
this.LURCache.on(async (nodeObject) => {
Object.entries(nodeObject).forEach(async ([_groupId, datas]) => {
const userIds = datas.map(v => v.userId);
const groupId = Number(_groupId);
logDebug('插入发言时间', _groupId);
await this.createGroupInfoTimeTableIfNotExist(groupId);
const needCreatUsers = await this.getNeedCreatList(groupId, userIds);
const updateList = needCreatUsers.length > 0 ? datas.filter(user => !needCreatUsers.includes(user.userId)) : datas;
const insertList = needCreatUsers.map(userId => datas.find(e => userId == e.userId)!);
logDebug('updateList', updateList);
logDebug('insertList', insertList);
if (insertList.length) {
const insertSql = `INSERT INTO "${groupId}" (last_sent_time, user_id) VALUES ${insertList.map(() => '(?, ?)').join(', ')};`;
this.db!.all(insertSql, insertList.map(v => [v.value, v.userId]).flat(), err => {
if (err) {
logError(`${groupId} 插入失败`);
logError(`更新Sql : ${insertSql}`);
}
});
}
if (updateList.length) {
const updateSql =
`UPDATE "${groupId}" SET last_sent_time = CASE ` +
updateList.map(v => `WHEN user_id = ${v.userId} THEN ${v.value}`).join(' ') +
' ELSE last_sent_time END WHERE user_id IN ' +
`(${updateList.map(v => v.userId).join(', ')});`;
this.db!.all(updateSql, [], err => {
if (err) {
logError(`${groupId} 跟新失败`);
logError(`更新Sql : ${updateSql}`);
}
});
}
});
});
}
async getNeedCreatList(groupId: number, userIds: number[]) {
// 获取缓存中没有的
const unhas = userIds.filter(userId => !this.LastSentCache.get(groupId, userId));
if (unhas.length == 0) {
logDebug('缓存全部命中');
return [];
}
logDebug('缓存未全部命中');
const sql = `SELECT * FROM "${groupId}" WHERE user_id IN (${unhas.map(() => '?').join(',')})`;
return new Promise<number[]>((resolve) => {
this.db!.all(sql, unhas, (err, rows: { user_id: number }[]) => {
const has = rows.map(v => v.user_id);
const needCreatUsers = unhas.filter(userId => !has.includes(userId));
if (needCreatUsers.length == 0) {
logDebug('数据库全部命中');
} else {
logDebug('数据库未全部命中');
}
resolve(needCreatUsers);
});
});
}
async createGroupInfoTimeTableIfNotExist(groupId: number) {
const createTableSQL = (groupId: number) =>
`CREATE TABLE IF NOT EXISTS "${groupId}" (
user_id INTEGER,
last_sent_time INTEGER,
join_time INTEGER,
PRIMARY KEY (user_id)
);`;
if (this.groupIds.includes(groupId)) {
return;
}
return new Promise((resolve, reject) => {
const sql = createTableSQL(groupId);
this.db!.all(sql, (err) => {
if (err) {
reject(err);
return;
}
this.groupIds.push(groupId);
resolve(true);
});
});
}
protected createTable() {
// 消息记录
const createTableSQL = `
CREATE TABLE IF NOT EXISTS msgs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
shortId INTEGER NOT NULL UNIQUE,
longId TEXT NOT NULL UNIQUE,
seq INTEGER NOT NULL,
peerUid TEXT NOT NULL,
chatType INTEGER NOT NULL
)`;
this.db!.run(createTableSQL, function (err) {
if (err) {
logError('Could not create table msgs', err.stack);
}
});
// 文件缓存
const createFileTableSQL = `
CREATE TABLE IF NOT EXISTS files (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
path TEXT NOT NULL,
url TEXT,
size INTEGER NOT NULL,
uuid TEXT,
elementType INTEGER,
element TEXT NOT NULL,
elementId TEXT NOT NULL,
msgId TEXT NOT NULL
)`;
this.db!.run(createFileTableSQL, function (err) {
if (err) {
logError('Could not create table files', err);
}
});
}
private async getCurrentMaxShortId() {
return new Promise<number>((resolve, reject) => {
this.db!.get('SELECT MAX(shortId) as maxId FROM msgs', (err, row: { maxId: number }) => {
if (err) {
logDebug('Could not get max short id, Use default -2147483640', err);
return resolve(-2147483640);
}
logDebug('数据库中消息最大短id', row?.maxId);
resolve(row?.maxId ?? -2147483640);
});
});
}
private async getMsg(query: string, params: any[]) {
const stmt = this.db!.prepare(query);
return new Promise<RawMessage | null>((resolve, reject) => {
stmt.get(...params, (err: any, row: DBMsg) => {
// log("getMsg", row, err);
if (err) {
logError('Could not get msg', err, query, params);
return resolve(null);
}
if (!row) {
// logDebug('不存在数据库中的消息,不进行处理', query, params);
resolve(null);
return;
}
const msgId = row.longId;
NTQQMsgApi.getMsgsByMsgId({ peerUid: row.peerUid, chatType: row.chatType }, [msgId]).then(res => {
const msg = res.msgList[0];
if (!msg) {
resolve(null);
return;
}
msg.id = row.shortId;
resolve(msg);
}).catch(e => {
resolve(null);
});
});
});
}
async getMsgByShortId(shortId: number): Promise<RawMessage | null> {
if (this.msgCache.has(shortId)) {
return this.msgCache.get(shortId)!;
}
const getStmt = 'SELECT * FROM msgs WHERE shortId = ?';
return this.getMsg(getStmt, [shortId]);
}
async getMsgByLongId(longId: string): Promise<RawMessage | null> {
if (this.msgCache.has(longId)) {
return this.msgCache.get(longId)!;
}
return this.getMsg('SELECT * FROM msgs WHERE longId = ?', [longId]);
}
async getMsgBySeq(peerUid: string, seq: string): Promise<RawMessage | null> {
const stmt = 'SELECT * FROM msgs WHERE peerUid = ? AND seq = ?';
return this.getMsg(stmt, [peerUid, seq]);
}
async addMsg(msg: RawMessage, update = true): Promise<number> {
const existMsg = await this.getMsgByLongId(msg.msgId);
if (existMsg) {
// logDebug('消息已存在,更新数据库', msg.msgId);
if (update) this.updateMsg(msg).then();
return existMsg.id!;
}
const stmt = this.db!.prepare('INSERT INTO msgs (shortId, longId, seq, peerUid, chatType) VALUES (?, ?, ?, ?, ?)');
// const runAsync = promisify(stmt.run.bind(stmt));
const shortId = ++this.globalMsgShortId;
msg.id = shortId;
//logDebug(`记录消息到数据库, 消息长id: ${msg.msgId}, 短id: ${msg.id}`);
this.msgCache.set(shortId, msg);
this.msgCache.set(msg.msgId, msg);
stmt.run(this.globalMsgShortId, msg.msgId, msg.msgSeq.toString(), msg.peerUid, msg.chatType, (err: any) => {
if (err) {
if (err.errno === 19) {
this.getMsgByLongId(msg.msgId).then((msg: RawMessage | null) => {
if (msg) {
this.msgCache.set(shortId, msg);
this.msgCache.set(msg.msgId, msg);
// logDebug('获取消息短id成功', msg.id);
} else {
logError('db could not get msg by long id', err);
}
}).catch(e => logError('db getMsgByLongId error', e));
} else {
logError('db could not add msg', err);
}
}
});
return shortId;
}
async updateMsg(msg: RawMessage) {
const existMsg = this.msgCache.get(msg.msgId);
if (existMsg) {
Object.assign(existMsg, msg);
}
//logDebug(`更新消息, shortId:${msg.id}, seq: ${msg.msgSeq}, msgId: ${msg.msgId}`);
const stmt = this.db!.prepare('UPDATE msgs SET seq=? WHERE longId=?');
stmt.run(msg.msgSeq, msg.msgId, (err: any) => {
if (err) {
logError('updateMsg db error', err);
}
});
}
async addFileCache(file: DBFile) {
const stmt = this.db!.prepare('INSERT INTO files (name, path, url, size, uuid, elementType ,element, elementId, msgId) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)');
return new Promise((resolve, reject) => {
stmt.run(file.name, file.path, file.url, file.size, file.uuid,
file.elementType,
JSON.stringify(file.element),
file.elementId,
file.msgId,
function (err: any) {
if (err) {
logError('db could not add file', err);
reject(err);
}
resolve(null);
});
});
}
private async getFileCache(query: string, params: any[]) {
const stmt = this.db!.prepare(query);
return new Promise<DBFile | null>((resolve, reject) => {
stmt.get(...params, (err: any, row: DBFile & { element: string }) => {
if (err) {
logError('db could not get file cache', err);
reject(err);
}
if (row) {
row.element = JSON.parse(row.element);
}
resolve(row);
});
});
}
async getFileCacheByName(name: string): Promise<DBFile | null> {
return this.getFileCache('SELECT * FROM files WHERE name = ?', [name]);
}
async getFileCacheByUuid(uuid: string): Promise<DBFile | null> {
return this.getFileCache('SELECT * FROM files WHERE uuid = ?', [uuid]);
}
// todo: 是否所有的文件都有uuid语音消息有没有uuid
async updateFileCache(file: DBFile) {
const stmt = this.db!.prepare('UPDATE files SET path = ?, url = ? WHERE uuid = ?');
return new Promise((resolve, reject) => {
stmt.run(file.path, file.url, file.uuid, function (err: any) {
if (err) {
logError('db could not update file cache', err);
reject(err);
}
resolve(null);
});
});
}
async getLastSentTimeAndJoinTime(
groupId: number
): Promise<IRember[]> {
logDebug('读取发言时间', groupId);
return new Promise<IRember[]>((resolve, reject) => {
this.db!.all(`SELECT * FROM "${groupId}" `, (err, rows: IRember[]) => {
const cache = this.LURCache.get(groupId).map(e => ({ user_id: e.userId, last_sent_time: e.value }));
if (err) {
logError('查询发言时间失败', groupId);
return resolve(cache.map(e => ({ ...e, join_time: 0 })));
}
Object.assign(rows, cache);
logDebug('查询发言时间成功', groupId, rows);
resolve(rows);
});
});
}
insertLastSentTime(
groupId: number,
userId: number,
time: number
) {
this.LURCache.set(groupId, userId, time);
}
async insertJoinTime(
groupId: number,
userId: number,
time: number
) {
await this.createGroupInfoTimeTableIfNotExist(groupId);
this.db!.all(
`INSERT OR REPLACE INTO "${groupId}" (user_id, last_sent_time, join_time) VALUES (?,?,?)`,
[userId, time, time],
(err) => {
if (err)
logError(err),
Promise.reject(),
logError('插入入群时间失败', userId, groupId);
}
);
}
}
export const dbUtil = new DBUtil();

View File

@@ -4,9 +4,8 @@ import crypto from 'crypto';
import util from 'util'; import util from 'util';
import path from 'node:path'; import path from 'node:path';
import { log, logError } from './log'; import { log, logError } from './log';
import { dbUtil } from '@/common/utils/db';
import * as fileType from 'file-type'; import * as fileType from 'file-type';
import { v4 as uuidv4 } from 'uuid'; import { randomUUID } from 'crypto';
import { napCatCore } from '@/core'; import { napCatCore } from '@/core';
export const getNapCatDir = () => { export const getNapCatDir = () => {
@@ -192,7 +191,7 @@ export async function uri2local(uri: string, fileName: string | null = null): Pr
isLocal: false isLocal: false
}; };
if (!fileName) { if (!fileName) {
fileName = uuidv4(); fileName = randomUUID();
} }
let filePath = path.join(getTempDir(), fileName); let filePath = path.join(getTempDir(), fileName);
let url = null; let url = null;
@@ -235,7 +234,7 @@ export async function uri2local(uri: string, fileName: string | null = null): Pr
} }
fileName = fileName.replace(/[/\\:*?"<>|]/g, '_'); fileName = fileName.replace(/[/\\:*?"<>|]/g, '_');
res.fileName = fileName; res.fileName = fileName;
filePath = path.join(getTempDir(), uuidv4() + fileName); filePath = path.join(getTempDir(), randomUUID() + fileName);
fs.writeFileSync(filePath, buffer); fs.writeFileSync(filePath, buffer);
} catch (e: any) { } catch (e: any) {
res.errMsg = `${url}下载失败,` + e.toString(); res.errMsg = `${url}下载失败,` + e.toString();
@@ -251,13 +250,14 @@ export async function uri2local(uri: string, fileName: string | null = null): Pr
} else { } else {
filePath = pathname; filePath = pathname;
} }
} else { }
const cache = await dbUtil.getFileCacheByName(uri); else {
if (cache) { // const cache = await dbUtil.getFileCacheByName(uri);
filePath = cache.path; // if (cache) {
} else { // filePath = cache.path;
filePath = uri; // } else {
} // filePath = uri;
// }
} }
res.isLocal = true; res.isLocal = true;

View File

@@ -1,17 +1,58 @@
import crypto from 'node:crypto'; import crypto from 'node:crypto';
import path from 'node:path'; import path from 'node:path';
import fs from 'fs/promises'; import fs from 'fs';
import { log, logDebug } from './log'; import { log, logDebug } from './log';
import { dirname } from 'node:path'; import { dirname } from 'node:path';
import { fileURLToPath } from 'node:url'; import { fileURLToPath } from 'node:url';
import * as fsPromise from 'node:fs/promises'; import * as fsPromise from 'node:fs/promises';
import os from 'node:os';
const __filename = fileURLToPath(import.meta.url); const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename); const __dirname = dirname(__filename);
//下面这个类是用于将uid+msgid合并的类
export class UUIDConverter {
static encode(highStr: string, lowStr: string): string {
const high = BigInt(highStr);
const low = BigInt(lowStr);
const highHex = high.toString(16).padStart(16, '0');
const lowHex = low.toString(16).padStart(16, '0');
const combinedHex = highHex + lowHex;
const uuid = `${combinedHex.substring(0, 8)}-${combinedHex.substring(8, 12)}-${combinedHex.substring(12, 16)}-${combinedHex.substring(16, 20)}-${combinedHex.substring(20)}`;
return uuid;
}
static decode(uuid: string): { high: string, low: string } {
const hex = uuid.replace(/-/g, '');
const high = BigInt('0x' + hex.substring(0, 16));
const low = BigInt('0x' + hex.substring(16));
return { high: high.toString(), low: low.toString() };
}
}
export function sleep(ms: number): Promise<void> { export function sleep(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms)); return new Promise(resolve => setTimeout(resolve, ms));
} }
export function PromiseTimer<T>(promise: Promise<T>, ms: number): Promise<T> {
const timeoutPromise = new Promise<T>((_, reject) =>
setTimeout(() => reject(new Error('PromiseTimer: Operation timed out')), ms)
);
return Promise.race([promise, timeoutPromise]);
}
export async function runAllWithTimeout<T>(tasks: Promise<T>[], timeout: number): Promise<T[]> {
const wrappedTasks = tasks.map(task =>
PromiseTimer(task, timeout).then(
result => ({ status: 'fulfilled', value: result }),
error => ({ status: 'rejected', reason: error })
)
);
const results = await Promise.all(wrappedTasks);
return results
.filter(result => result.status === 'fulfilled')
.map(result => (result as { status: 'fulfilled'; value: T }).value);
}
export function getMd5(s: string) { export function getMd5(s: string) {
const h = crypto.createHash('md5'); const h = crypto.createHash('md5');
@@ -264,14 +305,14 @@ export function migrateConfig(oldConfig: any) {
} }
// 升级旧的配置到新的 // 升级旧的配置到新的
export async function UpdateConfig() { export async function UpdateConfig() {
const configFiles = await fs.readdir(path.join(__dirname, 'config')); const configFiles = await fsPromise.readdir(path.join(__dirname, 'config'));
for (const file of configFiles) { for (const file of configFiles) {
if (file.match(/^onebot11_\d+.json$/)) { if (file.match(/^onebot11_\d+.json$/)) {
const CurrentConfig = JSON.parse(await fs.readFile(path.join(__dirname, 'config', file), 'utf8')); const CurrentConfig = JSON.parse(await fsPromise.readFile(path.join(__dirname, 'config', file), 'utf8'));
if (isValidOldConfig(CurrentConfig)) { if (isValidOldConfig(CurrentConfig)) {
log('正在迁移旧配置到新配置 File:', file); log('正在迁移旧配置到新配置 File:', file);
const NewConfig = migrateConfig(CurrentConfig); const NewConfig = migrateConfig(CurrentConfig);
await fs.writeFile(path.join(__dirname, 'config', file), JSON.stringify(NewConfig, null, 2)); await fsPromise.writeFile(path.join(__dirname, 'config', file), JSON.stringify(NewConfig, null, 2));
} }
} }
} }
@@ -291,7 +332,56 @@ export function isEqual(obj1: any, obj2: any) {
} }
return true; return true;
} }
export function getDefaultQQVersionConfigInfo(): QQVersionConfigType {
if (os.platform() === 'linux') {
return {
baseVersion: '3.2.12-26702',
curVersion: '3.2.12-26702',
prevVersion: '',
onErrorVersions: [],
buildId: '26702'
};
}
return {
baseVersion: '9.9.15-26702',
curVersion: '9.9.15-26702',
prevVersion: '',
onErrorVersions: [],
buildId: '26702'
};
}
export async function promisePipeline(promises: Promise<any>[], callback: (result: any) => boolean): Promise<void> {
let callbackCalled = false;
for (const promise of promises) {
if (callbackCalled) break;
try {
const result = await promise;
if (!callbackCalled) {
callbackCalled = callback(result);
}
} catch (error) {
console.error("Error in promise pipeline:", error);
}
}
}
export function getQQVersionConfigPath(exePath: string = ""): string | undefined {
let configVersionInfoPath;
if (os.platform() !== 'linux') {
configVersionInfoPath = path.join(path.dirname(exePath), 'resources', 'app', 'versions', 'config.json');
} else {
const userPath = os.homedir();
const appDataPath = path.resolve(userPath, './.config/QQ');
configVersionInfoPath = path.resolve(appDataPath, './versions/config.json');
}
if (typeof configVersionInfoPath !== 'string') {
return undefined;
}
if (!fs.existsSync(configVersionInfoPath)) {
return undefined;
}
return configVersionInfoPath;
}
export async function deleteOldFiles(directoryPath: string, daysThreshold: number) { export async function deleteOldFiles(directoryPath: string, daysThreshold: number) {
try { try {
const files = await fsPromise.readdir(directoryPath); const files = await fsPromise.readdir(directoryPath);

View File

@@ -91,8 +91,10 @@ export function enableConsoleLog(enable: boolean) {
function formatMsg(msg: any[]) { function formatMsg(msg: any[]) {
let logMsg = ''; let logMsg = '';
for (const msgItem of msg) { for (const msgItem of msg) {
// 判断是否是对象 if (msgItem instanceof Error) { // 判断是否是错误
if (typeof msgItem === 'object') { logMsg += msgItem.stack + ' ';
continue;
} else if (typeof msgItem === 'object') { // 判断是否是对象
const obj = JSON.parse(JSON.stringify(msgItem, null, 2)); const obj = JSON.parse(JSON.stringify(msgItem, null, 2));
logMsg += JSON.stringify(truncateString(obj)) + ' '; logMsg += JSON.stringify(truncateString(obj)) + ' ';
continue; continue;

View File

@@ -1,7 +1,7 @@
import os from 'node:os'; import os from 'node:os';
import path from 'node:path'; import path from 'node:path';
import { networkInterfaces } from 'os'; import { networkInterfaces } from 'os';
import { v4 as uuidv4 } from 'uuid'; import { randomUUID } from 'crypto';
// 缓解Win7设备兼容性问题 // 缓解Win7设备兼容性问题
let osName: string; let osName: string;
@@ -30,7 +30,7 @@ export async function getMachineId(): Promise<string> {
if (!machineId) { if (!machineId) {
machineId = (async () => { machineId = (async () => {
const id = await getMacMachineId(); const id = await getMacMachineId();
return id || uuidv4(); // fallback, generate a UUID return id || randomUUID(); // fallback, generate a UUID
})(); })();
} }

View File

@@ -1,31 +1,17 @@
/** //QQVersionType
* 运行时类型转换与检查类 type QQPackageInfoType = {
*/ version: string;
export class TypeCheck { buildVersion: string;
static isEmpty(value: any): boolean { platform: string;
return value === null || value === undefined || value === '' || eleArch: string;
(Array.isArray(value) && value.length === 0) || (typeof value === 'object' && Object.keys(value).length === 0);
}
} }
type QQVersionConfigType = {
export class TypeConvert { baseVersion: string;
static toNumber(value: any): number { curVersion: string;
const num = Number(value); prevVersion: string;
if (isNaN(num)) { onErrorVersions: Array<any>;
throw new Error(`无法将输入转换为数字: ${value}`); buildId: string;
} }
return num; type QQAppidTableType = {
} [key: string]: { appid: string, qua: string };
static toString(value: any): string {
return String(value);
}
static toBoolean(value: any): boolean {
return Boolean(value);
}
static toArray(value: any): any[] {
return Array.isArray(value) ? value : [value];
}
} }

View File

@@ -4,10 +4,10 @@ export async function checkVersion(): Promise<string> {
return new Promise(async (resolve, reject) => { return new Promise(async (resolve, reject) => {
const MirrorList = const MirrorList =
[ [
'https://jsd.cdn.zzko.cn/gh/NapNeko/NapCatQQ@main/package.json',
'https://fastly.jsdelivr.net/gh/NapNeko/NapCatQQ@main/package.json', 'https://fastly.jsdelivr.net/gh/NapNeko/NapCatQQ@main/package.json',
'https://gcore.jsdelivr.net/gh/NapNeko/NapCatQQ@main/package.json', 'https://gcore.jsdelivr.net/gh/NapNeko/NapCatQQ@main/package.json',
'https://cdn.jsdelivr.us/gh/NapNeko/NapCatQQ@main/package.json', 'https://cdn.jsdelivr.net/gh/NapNeko/NapCatQQ@main/package.json'
'https://jsd.cdn.zzko.cn/gh/NapNeko/NapCatQQ@main/package.json'
]; ];
let version = undefined; let version = undefined;
for (const url of MirrorList) { for (const url of MirrorList) {

File diff suppressed because one or more lines are too long

View File

@@ -1,12 +1,9 @@
import { import {
CacheFileList,
CacheFileListItem, CacheFileListItem,
CacheFileType, CacheFileType,
CacheScanResult,
ChatCacheList,
ChatCacheListItemBasic, ChatCacheListItemBasic,
ChatType, ChatType,
ElementType, IMAGE_HTTP_HOST, IMAGE_HTTP_HOST_NT, RawMessage ElementType, IMAGE_HTTP_HOST, IMAGE_HTTP_HOST_NT, Peer, PicElement, RawMessage
} from '@/core/entities'; } from '@/core/entities';
import path from 'path'; import path from 'path';
import fs from 'fs'; import fs from 'fs';
@@ -15,28 +12,15 @@ import { log, logDebug, logError } from '@/common/utils/log';
import { GeneralCallResult, napCatCore, OnRichMediaDownloadCompleteParams } from '@/core'; import { GeneralCallResult, napCatCore, OnRichMediaDownloadCompleteParams } from '@/core';
import { calculateFileMD5 } from '@/common/utils/file'; import { calculateFileMD5 } from '@/common/utils/file';
import * as fileType from 'file-type'; import * as fileType from 'file-type';
import { MsgListener } from '@/core/listeners';
import imageSize from 'image-size'; import imageSize from 'image-size';
import { ISizeCalculationResult } from 'image-size/dist/types/interface'; import { ISizeCalculationResult } from 'image-size/dist/types/interface';
import { sessionConfig } from '@/core/sessionConfig'; import { sessionConfig } from '@/core/sessionConfig';
import { randomUUID } from 'crypto';
import { rkeyManager } from '../utils/rkey'; import { rkeyManager } from '../utils/rkey';
import { NTEventDispatch } from '@/common/utils/EventTask';
import { NodeIKernelSearchService } from '../services/NodeIKernelSearchService';
import { selfInfo } from '../data';
const downloadMediaTasks: Map<string, (arg: OnRichMediaDownloadCompleteParams) => void> = new Map<string, (arg: OnRichMediaDownloadCompleteParams) => void>();
const downloadMediaListener = new MsgListener();
downloadMediaListener.onRichMediaDownloadComplete = arg => {
for (const [uuid, cb] of downloadMediaTasks) {
cb(arg);
downloadMediaTasks.delete(uuid);
}
};
setTimeout(() => {
napCatCore.onLoginSuccess(() => {
napCatCore.addListener(downloadMediaListener);
});
}, 100);
export class NTQQFileApi { export class NTQQFileApi {
static async getFileType(filePath: string) { static async getFileType(filePath: string) {
return fileType.fileTypeFromFile(filePath); return fileType.fileTypeFromFile(filePath);
@@ -49,12 +33,8 @@ export class NTQQFileApi {
static async getFileSize(filePath: string): Promise<number> { static async getFileSize(filePath: string): Promise<number> {
return await napCatCore.util.getFileSize(filePath); return await napCatCore.util.getFileSize(filePath);
} }
static async getVideoUrl(msg: RawMessage, element: any) { static async getVideoUrl(peer: Peer, msgId: string, elementId: string) {
return (await napCatCore.session.getRichMediaService().getVideoPlayUrlV2({ return (await napCatCore.session.getRichMediaService().getVideoPlayUrlV2(peer, msgId, elementId, 0, { downSourceType: 1, triggerType: 1 })).urlResult.domainUrl;
chatType: msg.chatType,
peerUid: msg.peerUid,
guildId: '0'
}, msg.msgId, element.elementId, 0, { downSourceType: 1, triggerType: 1 })).urlResult.domainUrl[0].url;
} }
// 上传文件到QQ的文件夹 // 上传文件到QQ的文件夹
static async uploadFile(filePath: string, elementType: ElementType = ElementType.PIC, elementSubType: number = 0) { static async uploadFile(filePath: string, elementType: ElementType = ElementType.PIC, elementSubType: number = 0) {
@@ -88,7 +68,9 @@ export class NTQQFileApi {
ext ext
}; };
} }
static async downloadMediaByUuid() {
//napCatCore.session.getRichMediaService().downloadFileForFileUuid();
}
static async downloadMedia(msgId: string, chatType: ChatType, peerUid: string, elementId: string, thumbPath: string, sourcePath: string, timeout = 1000 * 60 * 2, force: boolean = false) { static async downloadMedia(msgId: string, chatType: ChatType, peerUid: string, elementId: string, thumbPath: string, sourcePath: string, timeout = 1000 * 60 * 2, force: boolean = false) {
//logDebug('receive downloadMedia task', msgId, chatType, peerUid, elementId, thumbPath, sourcePath, timeout, force); //logDebug('receive downloadMedia task', msgId, chatType, peerUid, elementId, thumbPath, sourcePath, timeout, force);
// 用于下载收到的消息中的图片等 // 用于下载收到的消息中的图片等
@@ -103,31 +85,33 @@ export class NTQQFileApi {
return sourcePath; return sourcePath;
} }
} }
//logDebug('start downloadMedia', msgId, chatType, peerUid, elementId, thumbPath, sourcePath, timeout, force); let data = await NTEventDispatch.CallNormalEvent<
return new Promise<string>((resolve, reject) => { (
let completed = false; params: {
const cb = (arg: OnRichMediaDownloadCompleteParams) => { fileModelId: string,
//logDebug('downloadMedia complete', arg, msgId); downloadSourceType: number,
triggerType: number,
msgId: string,
chatType: ChatType,
peerUid: string,
elementId: string,
thumbSize: number,
downloadType: number,
filePath: string
}) => Promise<unknown>,
(fileTransNotifyInfo: OnRichMediaDownloadCompleteParams) => void
>(
'NodeIKernelMsgService/downloadRichMedia',
'NodeIKernelMsgListener/onRichMediaDownloadComplete',
1,
timeout,
(arg: OnRichMediaDownloadCompleteParams) => {
if (arg.msgId === msgId) { if (arg.msgId === msgId) {
completed = true; return true;
let filePath = arg.filePath;
if (filePath.startsWith('\\')) {
// log('filePath start with \\');
const downloadPath = sessionConfig.defaultFileDownloadPath;
//logDebug('downloadPath', downloadPath);
filePath = path.join(downloadPath, filePath);
// 下载路径是下载文件夹的相对路径
}
resolve(filePath);
} }
}; return false;
downloadMediaTasks.set(randomUUID(), cb); },
setTimeout(() => { {
if (!completed) {
reject('下载超时');
}
}, timeout);
napCatCore.session.getMsgService().downloadRichMedia({
fileModelId: '0', fileModelId: '0',
downloadSourceType: 0, downloadSourceType: 0,
triggerType: 1, triggerType: 1,
@@ -137,9 +121,18 @@ export class NTQQFileApi {
elementId: elementId, elementId: elementId,
thumbSize: 0, thumbSize: 0,
downloadType: 1, downloadType: 1,
filePath: thumbPath, filePath: thumbPath
}); }
}); );
let filePath = data[1].filePath;
if (filePath.startsWith('\\')) {
// log('filePath start with \\');
const downloadPath = sessionConfig.defaultFileDownloadPath;
//logDebug('downloadPath', downloadPath);
filePath = path.join(downloadPath, filePath);
// 下载路径是下载文件夹的相对路径
}
return filePath;
} }
static async getImageSize(filePath: string): Promise<ISizeCalculationResult | undefined> { static async getImageSize(filePath: string): Promise<ISizeCalculationResult | undefined> {
@@ -153,27 +146,137 @@ export class NTQQFileApi {
}); });
}); });
} }
static async addFileCache(peer: Peer, msgId: string, msgSeq: string, senderUid: string, elemId: string, elemType: string, fileSize: string, fileName: string) {
let GroupData;
let BuddyData;
if (peer.chatType === ChatType.group) {
GroupData =
[{
groupCode: peer.peerUid,
isConf: false,
hasModifyConfGroupFace: true,
hasModifyConfGroupName: true,
groupName: "NapCat.Cached",
remark: "NapCat.Cached"
}];
} else if (peer.chatType === ChatType.friend) {
BuddyData = [{
category_name: 'NapCat.Cached',
peerUid: peer.peerUid,
peerUin: peer.peerUid,
remark: 'NapCat.Cached'
}];
} else {
return undefined;
}
static async getImageUrl(element: { originImageUrl: any; md5HexStr?: any; fileUuid: any; }, isPrivateImage: boolean) { return napCatCore.session.getSearchService().addSearchHistory({
type: 4,
contactList: [],
id: -1,
groupInfos: [],
msgs: [],
fileInfos: [
{
chatType: peer.chatType,
buddyChatInfo: BuddyData || [],
discussChatInfo: [],
groupChatInfo: GroupData || [],
dataLineChatInfo: [],
tmpChatInfo: [],
msgId: msgId,
msgSeq: msgSeq,
msgTime: Math.floor(Date.now() / 1000).toString(),
senderUid: senderUid,
senderNick: 'NapCat.Cached',
senderRemark: 'NapCat.Cached',
senderCard: 'NapCat.Cached',
elemId: elemId,
elemType: elemType,
fileSize: fileSize,
filePath: '',
fileName: fileName,
hits: [{
start: 12,
end: 14
}]
}
]
});
}
static async searchfile(keys: string[]) {
type EventType = NodeIKernelSearchService['searchFileWithKeywords'];
interface OnListener {
searchId: string,
hasMore: boolean,
resultItems: {
chatType: ChatType,
buddyChatInfo: any[],
discussChatInfo: any[],
groupChatInfo:
{
groupCode: string,
isConf: boolean,
hasModifyConfGroupFace: boolean,
hasModifyConfGroupName: boolean,
groupName: string,
remark: string
}[]
,
dataLineChatInfo: any[],
tmpChatInfo: any[],
msgId: string,
msgSeq: string,
msgTime: string,
senderUid: string,
senderNick: string,
senderRemark: string,
senderCard: string,
elemId: string,
elemType: number,
fileSize: string,
filePath: string,
fileName: string,
hits:
{
start: number,
end: number
}[]
}[]
};
const Event = await NTEventDispatch.CreatEventFunction<EventType>('NodeIKernelSearchService/searchFileWithKeywords');
let id = '';
const Listener = NTEventDispatch.RegisterListen<(params: OnListener) => void>('NodeIKernelSearchListener/onSearchFileKeywordsResult', 1, 20000, (params) => {
if (id !== '' && params.searchId == id) {
return true
}
return false;
});
id = await Event!(keys, 12);
let [ret] = (await Listener);
return ret;
}
static async getImageUrl(element: PicElement) {
if (!element) { if (!element) {
return ''; return '';
} }
const url = element.originImageUrl; // 没有域名 const url: string = element.originImageUrl!; // 没有域名
const md5HexStr = element.md5HexStr; const md5HexStr = element.md5HexStr;
const fileMd5 = element.md5HexStr; const fileMd5 = element.md5HexStr;
const fileUuid = element.fileUuid; const fileUuid = element.fileUuid;
if (url) { if (url) {
if (url.startsWith('/download')) { let UrlParse = new URL(IMAGE_HTTP_HOST + url);//临时解析拼接
if (url.includes('&rkey=')) { let imageAppid = UrlParse.searchParams.get('appid');
let isNewPic = imageAppid && ['1406', '1407'].includes(imageAppid);
if (isNewPic) {
let UrlRkey = UrlParse.searchParams.get('rkey');
if (UrlRkey) {
return IMAGE_HTTP_HOST_NT + url; return IMAGE_HTTP_HOST_NT + url;
} }
const rkeyData = await rkeyManager.getRkey(); const rkeyData = await rkeyManager.getRkey();
UrlRkey = imageAppid === '1406' ? rkeyData.private_rkey : rkeyData.group_rkey;
const existsRKey = isPrivateImage ? rkeyData.private_rkey : rkeyData.group_rkey; return IMAGE_HTTP_HOST_NT + url + `${UrlRkey}`;
return IMAGE_HTTP_HOST_NT + url + `${existsRKey}`;
} else { } else {
// 老的图片url不需要rkey // 老的图片url不需要rkey
return IMAGE_HTTP_HOST + url; return IMAGE_HTTP_HOST + url;

View File

@@ -1,11 +1,66 @@
import { FriendRequest, User } from '@/core/entities'; import { FriendRequest, FriendV2, SimpleInfo, User } from '@/core/entities';
import { napCatCore, OnBuddyChangeParams } from '@/core'; import { BuddyListReqType, napCatCore, NodeIKernelBuddyListener, NodeIKernelProfileService, OnBuddyChangeParams } from '@/core';
import { NTEventDispatch } from '@/common/utils/EventTask'; import { NTEventDispatch } from '@/common/utils/EventTask';
import { LimitedHashTable } from '@/common/utils/MessageUnique';
import { CacheClassFuncAsyncExtend } from '@/common/utils/helper';
export class NTQQFriendApi { export class NTQQFriendApi {
static async getBuddyV2(refresh = false): Promise<FriendV2[]> {
let uids: string[] = [];
const buddyService = napCatCore.session.getBuddyService();
const buddyListV2 = refresh ? await buddyService.getBuddyListV2('0', BuddyListReqType.KNOMAL) : await buddyService.getBuddyListV2('0', BuddyListReqType.KNOMAL);
uids.push(...buddyListV2.data.flatMap(item => item.buddyUids));
const data = await NTEventDispatch.CallNoListenerEvent<NodeIKernelProfileService['getCoreAndBaseInfo']>(
'NodeIKernelProfileService/getCoreAndBaseInfo', 5000, 'nodeStore', uids
);
return Array.from(data.values());
}
@CacheClassFuncAsyncExtend(3600 * 1000, 'getBuddyIdMap', () => true)
static async getBuddyIdMapCache(refresh = false): Promise<LimitedHashTable<string, string>> {
return await NTQQFriendApi.getBuddyIdMap(refresh);
}
static async getBuddyIdMap(refresh = false): Promise<LimitedHashTable<string, string>> {
let uids: string[] = [];
let retMap: LimitedHashTable<string, string> = new LimitedHashTable<string, string>(5000);
const buddyService = napCatCore.session.getBuddyService();
const buddyListV2 = refresh ? await buddyService.getBuddyListV2('0', BuddyListReqType.KNOMAL) : await buddyService.getBuddyListV2('0', BuddyListReqType.KNOMAL);
uids.push(...buddyListV2.data.flatMap(item => item.buddyUids));
const data = await NTEventDispatch.CallNoListenerEvent<NodeIKernelProfileService['getCoreAndBaseInfo']>(
'NodeIKernelProfileService/getCoreAndBaseInfo', 5000, 'nodeStore', uids
);
data.forEach((value, key) => {
retMap.set(value.uin!, value.uid!);
});
//console.log('getBuddyIdMap', retMap.getValue);
return retMap;
}
static async getBuddyV2ExWithCate(refresh = false) {
let uids: string[] = [];
let categoryMap: Map<string, any> = new Map();
const buddyService = napCatCore.session.getBuddyService();
const buddyListV2 = refresh ? (await buddyService.getBuddyListV2('0', BuddyListReqType.KNOMAL)).data : (await buddyService.getBuddyListV2('0', BuddyListReqType.KNOMAL)).data;
uids.push(
...buddyListV2.flatMap(item => {
item.buddyUids.forEach(uid => {
categoryMap.set(uid, { categoryId: item.categoryId, categroyName: item.categroyName });
});
return item.buddyUids
}));
const data = await NTEventDispatch.CallNoListenerEvent<NodeIKernelProfileService['getCoreAndBaseInfo']>(
'NodeIKernelProfileService/getCoreAndBaseInfo', 5000, 'nodeStore', uids
);
return Array.from(data).map(([key, value]) => {
const category = categoryMap.get(key);
return category ? { ...value, categoryId: category.categoryId, categroyName: category.categroyName } : value;
});
}
static async isBuddy(uid: string) { static async isBuddy(uid: string) {
return napCatCore.session.getBuddyService().isBuddy(uid); return napCatCore.session.getBuddyService().isBuddy(uid);
} }
/**
* @deprecated
* @param forced
* @returns
*/
static async getFriends(forced = false): Promise<User[]> { static async getFriends(forced = false): Promise<User[]> {
let [_retData, _BuddyArg] = await NTEventDispatch.CallNormalEvent let [_retData, _BuddyArg] = await NTEventDispatch.CallNormalEvent
<(force: boolean) => Promise<any>, (arg: OnBuddyChangeParams) => void> <(force: boolean) => Promise<any>, (arg: OnBuddyChangeParams) => void>
@@ -14,6 +69,7 @@ export class NTQQFriendApi {
'NodeIKernelBuddyListener/onBuddyListChange', 'NodeIKernelBuddyListener/onBuddyListChange',
1, 1,
5000, 5000,
() => true,
forced forced
); );
const friends: User[] = []; const friends: User[] = [];

View File

@@ -1,24 +1,112 @@
import { GroupMember, GroupRequestOperateTypes, GroupMemberRole, GroupNotify, Group, MemberExtSourceType, GroupNotifyTypes } from '../entities'; import { GroupMember, GroupRequestOperateTypes, GroupMemberRole, GroupNotify, Group, MemberExtSourceType, GroupNotifyTypes, ChatType, Peer, GroupListUpdateType } from '../entities';
import { GeneralCallResult, NTQQUserApi, napCatCore } from '@/core'; import { GeneralCallResult, NTQQUserApi, NodeIKernelGroupListener, NodeIKernelGroupService, napCatCore } from '@/core';
import { NTEventDispatch } from '@/common/utils/EventTask'; import { NTEventDispatch } from '@/common/utils/EventTask';
import { logDebug } from '@/common/utils/log'; import { log } from '@/common/utils/log';
// console.log(process.pid); import { groupMembers } from '../data';
// setTimeout(async () => { import { CacheClassFuncAsyncExtend, runAllWithTimeout } from '@/common/utils/helper';
// console.log(JSON.stringify(await NTQQGroupApi.getMemberExtInfo(), null, 2));
// }, 20000);
export class NTQQGroupApi { export class NTQQGroupApi {
static async setGroupAvatar(gc: string, filePath: string) {
return napCatCore.session.getGroupService().setHeader(gc, filePath);
}
static async getGroups(forced = false) { static async getGroups(forced = false) {
type ListenerType = NodeIKernelGroupListener['onGroupListUpdate'];
let [_retData, _updateType, groupList] = await NTEventDispatch.CallNormalEvent let [_retData, _updateType, groupList] = await NTEventDispatch.CallNormalEvent
<(force: boolean) => Promise<any>, (updateType: number, groupList: Group[]) => void> <(force: boolean) => Promise<any>, ListenerType>
( (
'NodeIKernelGroupService/getGroupList', 'NodeIKernelGroupService/getGroupList',
'NodeIKernelGroupListener/onGroupListUpdate', 'NodeIKernelGroupListener/onGroupListUpdate',
1, 1,
5000, 5000,
(updateType) => true,
forced forced
); );
return groupList; return groupList;
} }
@CacheClassFuncAsyncExtend(3600 * 1000, "LastestSendTime", () => true)
static async getGroupMemberLastestSendTimeCache(GroupCode: string) {
return NTQQGroupApi.getGroupMemberLastestSendTime(GroupCode);
}
/**
* 通过QQ自带数据库获取群成员最后发言时间(仅返回有效数据 且消耗延迟大 需要进行缓存)
* @param GroupCode 群号
* @returns Map<string, string> key: uin value: sendTime
* @example
* let ret = await NTQQGroupApi.getGroupMemberLastestSendTime('123456');
* for (let [uin, sendTime] of ret) {
* console.log(uin, sendTime);
* }
*/
static async getGroupMemberLastestSendTime(GroupCode: string) {
async function getdata(uid: string) {
let NTRet = await NTQQGroupApi.getLastestMsgByUids(GroupCode, [uid]);
if (NTRet.result != 0 && NTRet.msgList.length < 1) {
return undefined;
}
return { sendUin: NTRet.msgList[0].senderUin, sendTime: NTRet.msgList[0].msgTime }
}
let currentGroupMembers = groupMembers.get(GroupCode);
let PromiseData: Promise<({
sendUin: string;
sendTime: string;
} | undefined)>[] = [];
let ret: Map<string, string> = new Map();
if (!currentGroupMembers) {
return ret;
}
for (let member of currentGroupMembers.values()) {
PromiseData.push(getdata(member.uid).catch(() => undefined));
}
let allRet = await runAllWithTimeout(PromiseData, 2500);
for (let PromiseDo of allRet) {
if (PromiseDo) {
ret.set(PromiseDo.sendUin, PromiseDo.sendTime);
}
}
return ret;
}
static async getLastestMsgByUids(GroupCode: string, uids: string[]) {
let ret = await napCatCore.session.getMsgService().queryMsgsWithFilterEx('0', '0', '0', {
chatInfo: {
peerUid: GroupCode,
chatType: ChatType.group,
},
filterMsgType: [],
filterSendersUid: uids,
filterMsgToTime: '0',
filterMsgFromTime: '0',
isReverseOrder: false,
isIncludeCurrent: true,
pageLimit: 1,
});
return ret;
}
static async getGroupMemberAll(GroupCode: string, forced = false) {
return napCatCore.session.getGroupService().getAllMemberList(GroupCode, forced);
}
static async getLastestMsg(GroupCode: string, uins: string[]) {
let uids: Array<string> = [];
for (let uin of uins) {
let uid = await NTQQUserApi.getUidByUin(uin)
if (uid) {
uids.push(uid);
}
}
let ret = await napCatCore.session.getMsgService().queryMsgsWithFilterEx('0', '0', '0', {
chatInfo: {
peerUid: GroupCode,
chatType: ChatType.group,
},
filterMsgType: [],
filterSendersUid: uids,
filterMsgToTime: '0',
filterMsgFromTime: '0',
isReverseOrder: false,
isIncludeCurrent: true,
pageLimit: 1,
});
return ret;
}
static async getGroupRecommendContactArkJson(GroupCode: string) { static async getGroupRecommendContactArkJson(GroupCode: string) {
return napCatCore.session.getGroupService().getGroupRecommendContactArkJson(GroupCode); return napCatCore.session.getGroupService().getGroupRecommendContactArkJson(GroupCode);
} }
@@ -63,12 +151,32 @@ export class NTQQGroupApi {
'NodeIKernelGroupListener/onGroupSingleScreenNotifies', 'NodeIKernelGroupListener/onGroupSingleScreenNotifies',
1, 1,
5000, 5000,
() => true,
false, false,
'', '',
num num
); );
return notifies; return notifies;
} }
static 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 [ret, _groupCode, _changeType, _members] = await NTEventDispatch.CallNormalEvent
<EventType, ListenerType>
(
'NodeIKernelGroupService/getMemberInfo',
'NodeIKernelGroupListener/onMemberInfoChange',
1,
5000,
(groupCode: string, changeType: number, members: Map<string, GroupMember>) => {
return groupCode == GroupCode && members.has(uid);
},
GroupCode, [uid], forced
);
return _members.get(uid);
}
static async getGroupMembers(groupQQ: string, num = 3000): Promise<Map<string, GroupMember>> { static async getGroupMembers(groupQQ: string, num = 3000): Promise<Map<string, GroupMember>> {
const groupService = napCatCore.session.getGroupService(); const groupService = napCatCore.session.getGroupService();
const sceneId = groupService.createMemberListScene(groupQQ, 'groupMemberList_MainWindow'); const sceneId = groupService.createMemberListScene(groupQQ, 'groupMemberList_MainWindow');
@@ -76,6 +184,7 @@ export class NTQQGroupApi {
if (result.errCode !== 0) { if (result.errCode !== 0) {
throw ('获取群成员列表出错,' + result.errMsg); throw ('获取群成员列表出错,' + result.errMsg);
} }
//logDebug(`获取群(${groupQQ})成员列表结果:`, `finish: ${result.result.finish}`); //, Array.from(result.result.infos.values())); //logDebug(`获取群(${groupQQ})成员列表结果:`, `finish: ${result.result.finish}`); //, Array.from(result.result.infos.values()));
return result.result.infos; return result.result.infos;
/* /*

View File

@@ -1,83 +1,80 @@
import { GetFileListParam, Peer, RawMessage, SendMessageElement } from '@/core/entities'; import { GetFileListParam, Peer, RawMessage, SendMessageElement, SendMsgElementConstructor } from '@/core/entities';
import { selfInfo } from '@/core/data'; import { friends, groups, selfInfo } from '@/core/data';
import { log, logError } from '@/common/utils/log'; import { log, logWarn } from '@/common/utils/log';
import { sleep } from '@/common/utils/helper'; import { sleep } from '@/common/utils/helper';
import { napCatCore } from '@/core'; import { napCatCore, NTQQUserApi } from '@/core';
import { MsgListener, onGroupFileInfoUpdateParamType } from '@/core/listeners'; import { onGroupFileInfoUpdateParamType } from '@/core/listeners';
import { GeneralCallResult } from '@/core/services/common'; import { GeneralCallResult } from '@/core/services/common';
import { randomUUID } from 'crypto'; import { MessageUnique } from '../../../common/utils/MessageUnique';
import { NTEventDispatch } from '@/common/utils/EventTask';
import { requireMinNTQQBuild } from '@/common/utils/QQBasicInfo';
async function LoadMessageIdList(Peer: Peer, msgId: string) {
const sendMessagePool: Record<string, ((sendSuccessMsg: RawMessage) => void | Promise<void>) | null> = {};// peerUid: callbackFunc let msgList = await NTQQMsgApi.getMsgHistory(Peer, msgId, 50);
for (let j = 0; j < msgList.msgList.length; j++) {
const sendSuccessCBMap: Record<string, ((sendSuccessMsg: RawMessage) => boolean | Promise<boolean>) | null> = {};// uuid: callbackFunc let shortId = MessageUnique.createMsg(Peer, msgList.msgList[j].msgId);
const GroupFileInfoUpdateTasks: Map<string, ((groupFileListResult: onGroupFileInfoUpdateParamType) => void)> = new Map();
const sentMsgTasks: Map<string, (msg: RawMessage) => void> = new Map();
const msgListener = new MsgListener();
msgListener.onGroupFileInfoUpdate = (groupFileListResult: onGroupFileInfoUpdateParamType) => {
for (const [uuid, cb] of GroupFileInfoUpdateTasks) {
cb(groupFileListResult);
GroupFileInfoUpdateTasks.delete(uuid);
} }
}; }
async function loadMessageUnique() {
msgListener.onAddSendMsg = (msgRecord: RawMessage) => { if (groups.size > 100) {
// console.log("sent msg", msgRecord, sendMessagePool); logWarn('[性能检测] 群数量大于100可能会导致性能问题');
for (const [uuid, cb] of sentMsgTasks) {
cb(msgRecord);
sentMsgTasks.delete(uuid);
} }
if (sendMessagePool[msgRecord.peerUid]) { let predict = (groups.size + friends.size / 2) / 5;
const r = sendMessagePool[msgRecord.peerUid]?.(msgRecord); predict = predict < 20 ? 20 : predict;
if (r instanceof Promise) { predict = predict > 50 ? 50 : predict;
r.then().catch(logError); //let waitpromise: Array<Promise<{ msgList: RawMessage[]; }>> = [];
predict = Math.floor(predict * 50);
MessageUnique.resize(predict);
let RecentContact = await NTQQUserApi.getRecentContactListSnapShot(predict);
let LoadMessageIdDo: Array<Promise<void>> = new Array<Promise<void>>();
if (RecentContact?.info?.changedList && RecentContact?.info?.changedList?.length > 0) {
for (let i = 0; i < RecentContact.info.changedList.length; i++) {
let Peer: Peer = { chatType: RecentContact.info.changedList[i].chatType, peerUid: RecentContact.info.changedList[i].peerUid, guildId: '' };
LoadMessageIdDo.push(LoadMessageIdList(Peer, RecentContact.info.changedList[i].msgId));
} }
} }
}; await Promise.all(LoadMessageIdDo).then(() => {
log(`[消息序列] 加载 ${predict} 条历史消息记录完成`);
msgListener.onMsgInfoListUpdate = (msgInfoList: RawMessage[]) => {
msgInfoList.forEach(msg => {
new Promise((resolve, reject) => {
for (const cbId in sendSuccessCBMap) {
const cb = sendSuccessCBMap[cbId]!;
const cbResult = cb(msg);
const checkResult = (result: boolean) => {
if (result) {
delete sendSuccessCBMap[cbId];
}
};
if (cbResult instanceof Promise) {
cbResult.then(checkResult);
} else {
checkResult(cbResult);
}
}
}).then().catch(log);
}); });
}; }
setTimeout(() => { setTimeout(() => {
napCatCore.onLoginSuccess(() => { napCatCore.onLoginSuccess(async () => {
napCatCore.addListener(msgListener); await sleep(100);
// NTQQMsgApi.CheckSendMode().then().catch();
loadMessageUnique().then().catch();
//let data = await napCatCore.session.getMsgService().sendSsoCmdReqByContend("LightAppSvc.mini_app_growguard.ReportExecute","1124343");
//console.log(data);
}); });
}, 100); }, 100);
//歇菜LocalMsg压根不写Db
// setTimeout(async () => {
// let ele: MessageElement = { extBufForUI: '0x', ...SendMsgElementConstructor.text('测试消息') };
// let MsgId = await NTQQMsgApi.getMsgUniqueEx();
// let peer = { chatType: 2, peerUid: '', guildId: '' };
// console.log(await napCatCore.session.getTestPerformanceService().insertMsg(
// {
// peer: peer,
// msgTime: Math.floor(Date.now() / 1000).toString(),
// msgId: MsgId,
// msgSeq: '56564',
// batchNums: 1,
// timesPerBatch: 1,
// numPerTime: 1
// }, [ele]
// ));
// console.log(await NTQQMsgApi.multiForwardMsg(peer, peer, [MsgId]));
// }, 25000)
export class NTQQMsgApi { export class NTQQMsgApi {
// static napCatCore: NapCatCore | null = null; // static napCatCore: NapCatCore | null = null;
// enum BaseEmojiType { // enum BaseEmojiType {
// NORMAL_EMOJI, // NORMAL_EMOJI,
// SUPER_EMOJI, // SUPER_EMOJI,
// RANDOM_SUPER_EMOJI, // RANDOM_SUPER_EMOJI,
// CHAIN_SUPER_EMOJI, // CHAIN_SUPER_EMOJI,
// EMOJI_EMOJI // EMOJI_EMOJI
// } // }
static async setEmojiLike(peer: Peer, msgSeq: string, emojiId: string, set: boolean = true) { static async setEmojiLike(peer: Peer, msgSeq: string, emojiId: string, set: boolean = true) {
// nt_qq//global//nt_data//Emoji//emoji-resource//sysface_res/apng/ 下可以看到所有QQ表情预览 // nt_qq//global//nt_data//Emoji//emoji-resource//sysface_res/apng/ 下可以看到所有QQ表情预览
// nt_qq\global\nt_data\Emoji\emoji-resource\face_config.json 里面有所有表情的id, 自带表情id是QSid, 标准emoji表情id是QCid // nt_qq\global\nt_data\Emoji\emoji-resource\face_config.json 里面有所有表情的id, 自带表情id是QSid, 标准emoji表情id是QCid
@@ -90,143 +87,206 @@ export class NTQQMsgApi {
} | undefined> { } | undefined> {
return napCatCore.session.getMsgService().getMultiMsg(peer, rootMsgId, parentMsgId); return napCatCore.session.getMsgService().getMultiMsg(peer, rootMsgId, parentMsgId);
} }
static async getLastestMsgByUids(peer: Peer, count: number = 20) {
let ret = await napCatCore.session.getMsgService().queryMsgsWithFilterEx('0', '0', '0', {
chatInfo: peer,
filterMsgType: [],
filterSendersUid: [],
filterMsgToTime: '0',
filterMsgFromTime: '0',
isReverseOrder: false,
isIncludeCurrent: true,
pageLimit: 1,
});
return ret;
}
static async getMsgsByMsgId(peer: Peer, msgIds: string[]) { static async getMsgsByMsgId(peer: Peer, msgIds: string[]) {
return await napCatCore.session.getMsgService().getMsgsByMsgId(peer, msgIds); return await napCatCore.session.getMsgService().getMsgsByMsgId(peer, msgIds);
} }
static async getSingleMsg(peer: Peer, seq: string) {
return await napCatCore.session.getMsgService().getSingleMsg(peer, seq);
}
static async fetchFavEmojiList(num: number) {
return napCatCore.session.getMsgService().fetchFavEmojiList("", num, true, true)
}
static async queryMsgsWithFilterExWithSeq(peer: Peer, msgSeq: string) {
let ret = await napCatCore.session.getMsgService().queryMsgsWithFilterEx('0', '0', msgSeq, {
chatInfo: peer,
filterMsgType: [],
filterSendersUid: [],
filterMsgToTime: '0',
filterMsgFromTime: '0',
isReverseOrder: false,
isIncludeCurrent: true,
pageLimit: 1,
});
return ret;
}
static async getMsgsBySeqAndCount(peer: Peer, seq: string, count: number, desc: boolean, z: boolean) { static async getMsgsBySeqAndCount(peer: Peer, seq: string, count: number, desc: boolean, z: boolean) {
return await napCatCore.session.getMsgService().getMsgsBySeqAndCount(peer, seq, count, desc, z); return await napCatCore.session.getMsgService().getMsgsBySeqAndCount(peer, seq, count, desc, z);
} }
static async activateChat(peer: Peer) {
// await this.fetchRecentContact();
// await sleep(500);
}
static async activateChatAndGetHistory(peer: Peer) {
}
static async setMsgRead(peer: Peer) { static async setMsgRead(peer: Peer) {
return napCatCore.session.getMsgService().setMsgRead(peer); return napCatCore.session.getMsgService().setMsgRead(peer);
} }
static async getGroupFileList(GroupCode: string, params: GetFileListParam) { static async getGroupFileList(GroupCode: string, params: GetFileListParam) {
return new Promise<Array<any>>(async (resolve, reject) => { let data = await NTEventDispatch.CallNormalEvent<
let complete = false; (GroupCode: string, params: GetFileListParam) => Promise<unknown>,
setTimeout(() => { (groupFileListResult: onGroupFileInfoUpdateParamType) => void
if (!complete) { >(
reject('获取群文件列表超时'); 'NodeIKernelRichMediaService/getGroupFileList',
} 'NodeIKernelMsgListener/onGroupFileInfoUpdate',
}, 5000); 1,
const GroupFileInfoUpdateCB = (groupFileListResult: onGroupFileInfoUpdateParamType) => { 5000,
complete = true; (groupFileListResult: onGroupFileInfoUpdateParamType) => {
resolve(groupFileListResult.item); return true;
}; },
GroupFileInfoUpdateTasks.set(randomUUID(), GroupFileInfoUpdateCB); GroupCode,
await napCatCore.session.getRichMediaService().getGroupFileList(GroupCode, params); params
}); );
return data[1].item;
} }
static async getMsgHistory(peer: Peer, msgId: string, count: number) { static async getMsgHistory(peer: Peer, msgId: string, count: number) {
// 消息时间从旧到新 // 消息时间从旧到新
return napCatCore.session.getMsgService().getMsgsIncludeSelf(peer, msgId, count, true); return napCatCore.session.getMsgService().getMsgsIncludeSelf(peer, msgId, count, true);
} }
static async fetchRecentContact() {
}
static async recallMsg(peer: Peer, msgIds: string[]) { static async recallMsg(peer: Peer, msgIds: string[]) {
await napCatCore.session.getMsgService().recallMsg({ await napCatCore.session.getMsgService().recallMsg({
chatType: peer.chatType, chatType: peer.chatType,
peerUid: peer.peerUid peerUid: peer.peerUid
}, msgIds); }, msgIds);
} }
static async sendMsgV2(peer: Peer, msgElements: SendMessageElement[], waitComplete = true, timeout = 10000) {
static async sendMsg(peer: Peer, msgElements: SendMessageElement[], waitComplete = true, timeout = 10000): Promise<RawMessage> { // function generateMsgId() {
const peerUid = peer.peerUid; // const timestamp = Math.floor(Date.now() / 1000);
// 等待上一个相同的peer发送完 // const random = Math.floor(Math.random() * Math.pow(2, 32));
let checkLastSendUsingTime = 0; // const buffer = Buffer.alloc(8);
const waitLastSend: () => Promise<void> = async () => { // buffer.writeUInt32BE(timestamp, 0);
if (checkLastSendUsingTime > timeout) { // buffer.writeUInt32BE(random, 4);
throw ('发送超时'); // const msgId = BigInt("0x" + buffer.toString('hex')).toString();
} // return msgId;
const lastSending = sendMessagePool[peer.peerUid]; // }
if (lastSending) { // 此处有采用Hack方法 利用数据返回正确得到对应消息
// log("有正在发送的消息,等待中...") // 与之前 Peer队列 MsgSeq队列 真正的MsgId并发不同
await sleep(500); // 谨慎采用 目前测试暂无问题 Developer.Mlikiowa
checkLastSendUsingTime += 500; let msgId = await NTQQMsgApi.getMsgUnique(peer.chatType, await NTQQMsgApi.getServerTime());
return await waitLastSend(); let data = await NTEventDispatch.CallNormalEvent<
} else { (msgId: string, peer: Peer, msgElements: SendMessageElement[], map: Map<any, any>) => Promise<unknown>,
return; (msgList: RawMessage[]) => void
} >(
}; 'NodeIKernelMsgService/sendMsg',
await waitLastSend(); 'NodeIKernelMsgListener/onMsgInfoListUpdate',
1,
return new Promise((resolve, reject) => { timeout,
let completed = false; (msgRecords: RawMessage[]) => {
let sentMessage: RawMessage | null = null; for (let msgRecord of msgRecords) {
const sendSuccessCBId = randomUUID() as string; if (msgRecord.msgId === msgId && msgRecord.sendStatus === 2) {
sendSuccessCBMap[sendSuccessCBId] = (msgRecord: RawMessage) => {
if (msgRecord.msgId === sentMessage?.msgId) {
if (msgRecord.sendStatus === 2) {
delete sendSuccessCBMap[sendSuccessCBId];
completed = true;
resolve(msgRecord);
return true; return true;
} }
return false;
} }
return false; return false;
}; },
sendMessagePool[peerUid] = async (rawMessage: RawMessage) => { msgId,
// console.log('收到sent 消息', rawMessage.msgId); peer,
delete sendMessagePool[peerUid]; msgElements,
sentMessage = rawMessage; new Map()
}; );
setTimeout(() => { let retMsg = data[1].find(msgRecord => {
if (completed) return; if (msgRecord.msgId === msgId) {
delete sendMessagePool[peerUid]; return true;
delete sendSuccessCBMap[sendSuccessCBId]; }
reject('发送超时');
}, timeout);
const result = napCatCore.session.getMsgService().sendMsg('0', peer, msgElements, new Map());
}); });
return retMsg;
}
static sendMsgEx(peer: Peer, msgElements: SendMessageElement[], waitComplete = true, timeout = 10000) {
//return NTQQMsgApi.sendMsgV1(peer, msgElements, waitComplete, timeout);
}
static async sendMsg(peer: Peer, msgElements: SendMessageElement[], waitComplete = true, timeout = 10000) {
//唉? !我有个想法
let msgId = await NTQQMsgApi.getMsgUnique(peer.chatType, await NTQQMsgApi.getServerTime());
peer.guildId = msgId;
let data = await NTEventDispatch.CallNormalEvent<
(msgId: string, peer: Peer, msgElements: SendMessageElement[], map: Map<any, any>) => Promise<unknown>,
(msgList: RawMessage[]) => void
>(
'NodeIKernelMsgService/sendMsg',
'NodeIKernelMsgListener/onMsgInfoListUpdate',
1,
timeout,
(msgRecords: RawMessage[]) => {
for (let msgRecord of msgRecords) {
if (msgRecord.guildId === msgId && msgRecord.sendStatus === 2) {
return true;
}
}
return false;
},
"0",
peer,
msgElements,
new Map()
);
let retMsg = data[1].find(msgRecord => {
if (msgRecord.guildId === msgId) {
return true;
}
});
return retMsg;
}
static async getMsgUnique(chatType: number, time: string) {
if (requireMinNTQQBuild('26702')) {
return napCatCore.session.getMsgService().generateMsgUniqueId(chatType, time);
}
return napCatCore.session.getMsgService().getMsgUniqueId(time);
}
static async getServerTime() {
return napCatCore.session.getMSFService().getServerTime();
}
static async getServerTimeV2() {
return NTEventDispatch.CallNoListenerEvent<() => string>('NodeIKernelMsgService/getServerTime', 5000);
} }
static async forwardMsg(srcPeer: Peer, destPeer: Peer, msgIds: string[]) { static async forwardMsg(srcPeer: Peer, destPeer: Peer, msgIds: string[]) {
return napCatCore.session.getMsgService().forwardMsg(msgIds, srcPeer, [destPeer], new Map()); return napCatCore.session.getMsgService().forwardMsg(msgIds, srcPeer, [destPeer], new Map());
} }
static async multiForwardMsg(srcPeer: Peer, destPeer: Peer, msgIds: string[]): Promise<RawMessage> { static async multiForwardMsg(srcPeer: Peer, destPeer: Peer, msgIds: string[]): Promise<RawMessage> {
const msgInfos = msgIds.map(id => { const msgInfos = msgIds.map(id => {
return { msgId: id, senderShowName: selfInfo.nick }; return { msgId: id, senderShowName: selfInfo.nick };
}); });
let data = await NTEventDispatch.CallNormalEvent<
return new Promise((resolve, reject) => { (msgInfo: typeof msgInfos, srcPeer: Peer, destPeer: Peer, comment: Array<any>, attr: Map<any, any>,) => Promise<unknown>,
let complete = false; (msgList: RawMessage[]) => void
const onSentCB = (msg: RawMessage) => { >(
const arkElement = msg.elements.find(ele => ele.arkElement); 'NodeIKernelMsgService/multiForwardMsgWithComment',
if (!arkElement) { 'NodeIKernelMsgListener/onMsgInfoListUpdate',
// log("收到的不是转发消息") 1,
return; 5000,
(msgRecords: RawMessage[]) => {
for (let msgRecord of msgRecords) {
if (msgRecord.peerUid == destPeer.peerUid && msgRecord.senderUid == selfInfo.uid) {
return true;
}
} }
const forwardData: any = JSON.parse(arkElement.arkElement.bytesData); return false;
if (forwardData.app != 'com.tencent.multimsg') { },
return; msgInfos,
} srcPeer,
if (msg.peerUid == destPeer.peerUid && msg.senderUid == selfInfo.uid) { destPeer,
complete = true; [],
resolve(msg); new Map()
}
};
sentMsgTasks.set(randomUUID(), onSentCB);
setTimeout(() => {
if (!complete) {
reject('转发消息超时');
}
}, 5000);
napCatCore.session.getMsgService().multiForwardMsgWithComment(msgInfos, srcPeer, destPeer, [], new Map());
}
); );
for (let msg of data[1]) {
const arkElement = msg.elements.find(ele => ele.arkElement);
if (!arkElement) {
continue;
}
const forwardData: any = JSON.parse(arkElement.arkElement.bytesData);
if (forwardData.app != 'com.tencent.multimsg') {
continue;
}
if (msg.peerUid == destPeer.peerUid && msg.senderUid == selfInfo.uid) {
return msg;
}
}
throw new Error('转发消息超时');
} }
static async markallMsgAsRead() { static async markallMsgAsRead() {
return napCatCore.session.getMsgService().setAllC2CAndGroupMsgRead(); return napCatCore.session.getMsgService().setAllC2CAndGroupMsgRead();

View File

@@ -19,6 +19,43 @@ export interface CustomMusicSignPostData {
image?: string, image?: string,
singer?: string singer?: string
} }
// 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 { // export class MusicSign {
// private readonly url: string; // private readonly url: string;

View File

@@ -1,11 +1,7 @@
import { NTEventDispatch } from '@/common/utils/EventTask'; import { NTEventDispatch } from '@/common/utils/EventTask';
import { GeneralCallResult, NTQQUserApi, napCatCore } from '@/core'; import { GeneralCallResult, NTQQFileApi, NTQQUserApi, napCatCore } from '@/core';
// setTimeout(async () => {
// let ret = await NTQQSystemApi.getArkJsonCollection('1-2-162b9b42-65b9-4405-a8ed-2e256ec8aa50');
// console.log(ret);
// }, 20000)
export class NTQQSystemApi { export class NTQQSystemApi {
static async hasOtherRunningQQProcess() { static async hasOtherRunningQQProcess() {
return napCatCore.util.hasOtherRunningQQProcess(); return napCatCore.util.hasOtherRunningQQProcess();
@@ -16,7 +12,7 @@ export class NTQQSystemApi {
static async translateEnWordToZn(words: string[]) { static async translateEnWordToZn(words: string[]) {
return napCatCore.session.getRichMediaService().translateEnWordToZn(words); return napCatCore.session.getRichMediaService().translateEnWordToZn(words);
} }
//调用会超时 没灯用 //调用会超时 没灯用 (好像是通知listener的) onLineDev
static async getOnlineDev() { static async getOnlineDev() {
return napCatCore.session.getMsgService().getOnLineDev(); return napCatCore.session.getMsgService().getOnLineDev();
} }
@@ -33,7 +29,7 @@ export class NTQQSystemApi {
static async BootMiniApp(appfile: string, params: string) { static async BootMiniApp(appfile: string, params: string) {
await napCatCore.session.getNodeMiscService().setMiniAppVersion('2.16.4'); await napCatCore.session.getNodeMiscService().setMiniAppVersion('2.16.4');
let c = await napCatCore.session.getNodeMiscService().getMiniAppPath(); let c = await napCatCore.session.getNodeMiscService().getMiniAppPath();
console.log(c);
return napCatCore.session.getNodeMiscService().startNewMiniApp(appfile, params); return napCatCore.session.getNodeMiscService().startNewMiniApp(appfile, params);
} }
} }

View File

@@ -1,52 +1,27 @@
import { ModifyProfileParams, SelfInfo, User, UserDetailInfoByUin } from '@/core/entities'; import { ModifyProfileParams, SelfInfo, User, UserDetailInfoByUin, UserDetailInfoByUinV2 } from '@/core/entities';
import { friends, selfInfo } from '@/core/data'; import { friends, groupMembers, selfInfo } from '@/core/data';
import { CacheClassFuncAsync, CacheClassFuncAsyncExtend } from '@/common/utils/helper'; import { CacheClassFuncAsync, CacheClassFuncAsyncExtend } from '@/common/utils/helper';
import { GeneralCallResult, napCatCore, NTQQFriendApi } from '@/core'; import { napCatCore, NTQQFriendApi } from '@/core';
import { ProfileListener } from '@/core/listeners'; import { NodeIKernelProfileListener, ProfileListener } from '@/core/listeners';
import { rejects } from 'assert';
import { randomUUID } from 'crypto';
import { RequestUtil } from '@/common/utils/request'; import { RequestUtil } from '@/common/utils/request';
import { log, logDebug, logError, logWarn } from '@/common/utils/log'; import { logWarn } from '@/common/utils/log';
import { NTEventDispatch } from '@/common/utils/EventTask'; import { NTEventDispatch } from '@/common/utils/EventTask';
const userInfoCache: Record<string, User> = {}; // uid: User import { NodeIKernelProfileService, ProfileBizType, UserDetailSource } from '@/core/services';
import { requireMinNTQQBuild } from '@/common/utils/QQBasicInfo';
const profileListener = new ProfileListener();
const userDetailHandlers: Map<string, ((profile: User) => void)> = new Map();
profileListener.onProfileDetailInfoChanged = (profile) => {
userInfoCache[profile.uid] = profile;
userDetailHandlers.forEach(handler => handler(profile));
};
setTimeout(() => {
napCatCore.onLoginSuccess(() => {
napCatCore.addListener(profileListener);
});
}, 100);
// 老版本逻辑现已移除
// console.log('onProfileDetailInfoChanged', profile);
// recevCount++;
// firstProfile = profile;
// if (recevCount === 2) {
// profileService.removeKernelProfileListener(listenerId);
// // if (!completed) {
// completed = true;
// resolve(profile);
// // }
// }
// };
export class NTQQUserApi { export class NTQQUserApi {
static async getProfileLike(uid: string) { static async getProfileLike(uid: string) {
return napCatCore.session.getProfileLikeService().getBuddyProfileLike({ return napCatCore.session.getProfileLikeService().getBuddyProfileLike({
"friendUids": [ friendUids: [
uid uid
], ],
"basic": 1, basic: 1,
"vote": 1, vote: 1,
"favorite": 0, favorite: 0,
"userProfile": 1, userProfile: 1,
"type": 2, type: 2,
"start": 0, start: 0,
"limit": 20 limit: 20
}); });
} }
static async setLongNick(longNick: string) { static async setLongNick(longNick: string) {
@@ -72,60 +47,110 @@ export class NTQQUserApi {
const ret = await napCatCore.session.getProfileService().setHeader(filePath) as setQQAvatarRet; const ret = await napCatCore.session.getProfileService().setHeader(filePath) as setQQAvatarRet;
return { result: ret?.result, errMsg: ret?.errMsg }; return { result: ret?.result, errMsg: ret?.errMsg };
} }
static async setGroupAvatar(gc: string, filePath: string) {
static async getSelfInfo() { return napCatCore.session.getGroupService().setHeader(gc, filePath);
} }
static async getUserInfo(uid: string) { static async fetchUserDetailInfos(uids: string[]) {
//26702 以上使用新接口 .Dev Mlikiowa
type EventService = NodeIKernelProfileService['fetchUserDetailInfo'];
type EventListener = NodeIKernelProfileListener['onUserDetailInfoChanged'];
let retData: User[] = [];
let [_retData, _retListener] = await NTEventDispatch.CallNormalEvent
<EventService, EventListener>
(
'NodeIKernelProfileService/fetchUserDetailInfo',
'NodeIKernelProfileListener/onUserDetailInfoChanged',
uids.length,
5000,
(profile) => {
if (uids.includes(profile.uid)) {
let RetUser: User = {
...profile.simpleInfo.coreInfo,
...profile.simpleInfo.status,
...profile.simpleInfo.vasInfo,
...profile.commonExt,
...profile.simpleInfo.baseInfo,
qqLevel: profile.commonExt.qqLevel,
pendantId: ""
};
retData.push(RetUser);
return true;
}
return false;
},
"BuddyProfileStore",
uids,
UserDetailSource.KSERVER,
[
ProfileBizType.KALL
]
);
return retData;
} }
// enum ProfileBizType { static async fetchUserDetailInfo(uid: string) {
// KALL, type EventService = NodeIKernelProfileService['fetchUserDetailInfo'];
// KBASEEXTEND, type EventListener = NodeIKernelProfileListener['onUserDetailInfoChanged'];
// KVAS, let [_retData, profile] = await NTEventDispatch.CallNormalEvent
// KQZONE, <EventService, EventListener>
// KOTHER (
// } 'NodeIKernelProfileService/fetchUserDetailInfo',
static async getUserDetailInfo(uid: string): Promise<User> { 'NodeIKernelProfileListener/onUserDetailInfoChanged',
// const existUser = userInfoCache[uid]; 1,
// if (existUser) { 5000,
// return existUser; (profile) => {
// } if (profile.uid === uid) {
const profileService = napCatCore.session.getProfileService(); return true;
// console.log('getUserDetailInfo', result);
return new Promise((resolve, reject) => {
const uuid = randomUUID();
let completed = false;
let retData: User | undefined = undefined;
let isFirst = true;
// 不管返回几次 超时有数据就该返回 兼容就好了
setTimeout(() => {
if (!completed) {
if (retData) {
resolve(retData);
} else {
reject('getUserDetailInfo timeout');
} }
} return false;
userDetailHandlers.delete(uuid); },
}, 5000); "BuddyProfileStore",
userDetailHandlers.set(uuid, (profile) => { [
if (profile.uid === uid) { uid
if (isFirst) { ],
retData = profile; UserDetailSource.KSERVER,
isFirst = false; [
// console.log('getUserDetailInfo', profile); ProfileBizType.KALL
} else { ]
completed = true; );
resolve(profile); let RetUser: User = {
...profile.simpleInfo.coreInfo,
...profile.simpleInfo.status,
...profile.simpleInfo.vasInfo,
...profile.commonExt,
...profile.simpleInfo.baseInfo,
qqLevel: profile.commonExt.qqLevel,
pendantId: ""
};
return RetUser;
}
static async getUserDetailInfo(uid: string) {
if (requireMinNTQQBuild('26702')) {
return this.fetchUserDetailInfo(uid);
}
return this.getUserDetailInfoOld(uid);
}
static async getUserDetailInfoOld(uid: string) {
type EventService = NodeIKernelProfileService['getUserDetailInfoWithBizInfo'];
type EventListener = NodeIKernelProfileListener['onProfileDetailInfoChanged'];
let [_retData, profile] = await NTEventDispatch.CallNormalEvent
<EventService, EventListener>
(
'NodeIKernelProfileService/getUserDetailInfoWithBizInfo',
'NodeIKernelProfileListener/onProfileDetailInfoChanged',
2,
5000,
(profile: User) => {
if (profile.uid === uid) {
return true;
} }
} return false;
}); },
profileService.getUserDetailInfoWithBizInfo(uid, [0]).then(result => { uid,
// console.log('getUserDetailInfo', result); [0]
}); );
}); return profile;
} }
static async modifySelfProfile(param: ModifyProfileParams) { static async modifySelfProfile(param: ModifyProfileParams) {
return napCatCore.session.getProfileService().modifyDesktopMiniProfile(param); return napCatCore.session.getProfileService().modifyDesktopMiniProfile(param);
@@ -177,38 +202,90 @@ export class NTQQUserApi {
} }
return skey; return skey;
} }
@CacheClassFuncAsyncExtend(3600, 'Uin2Uid', (Uin: string, Uid: string | undefined) => { @CacheClassFuncAsyncExtend(3600 * 1000, 'Uin2Uid', (Uin: string, Uid: string | undefined) => {
if (Uid && Uid.indexOf('u_') != -1) { if (Uid && Uid.indexOf('u_') != -1) {
return true return true
} }
logWarn("uin转换到uid时异常", Uin); logWarn("uin转换到uid时异常", Uin, Uid);
return false; return false;
}) })
static async getUidByUin(Uin: string) { static async getUidByUin(Uin: string) {
let ret = await NTEventDispatch.CallNoListenerEvent //此代码仅临时使用,后期会被废弃
<(Uin: string[]) => Promise<{ uidInfo: Map<string, string> }>>( if (requireMinNTQQBuild('26702')) {
'NodeIKernelUixConvertService/getUid', return await NTQQUserApi.getUidByUinV2(Uin);
5000, }
[Uin] return await NTQQUserApi.getUidByUinV1(Uin);
); }
let uid = ret.uidInfo.get(Uin); //通过QQ默认方式转换 @CacheClassFuncAsyncExtend(3600 * 1000, 'Uid2Uin', (Uid: string | undefined, Uin: number | undefined) => {
if (Uin && Uin != 0 && !isNaN(Uin)) {
return true
}
logWarn("uid转换到uin时异常", Uid, Uin);
return false;
})
static async getUinByUid(Uid: string) {
//此代码仅临时使用,后期会被废弃
if (requireMinNTQQBuild('26702')) {
return await NTQQUserApi.getUinByUidV2(Uid);
}
return await NTQQUserApi.getUinByUidV1(Uid);
}
//后期改成流水线处理
static async getUidByUinV2(Uin: string) {
let uid = (await napCatCore.session.getProfileService().getUidByUin('FriendsServiceImpl', [Uin])).get(Uin);
if (uid) return uid;
uid = (await napCatCore.session.getGroupService().getUidByUins([Uin])).uids.get(Uin);
if (uid) return uid;
uid = (await napCatCore.session.getUixConvertService().getUid([Uin])).uidInfo.get(Uin);
if (uid) return uid;
console.log((await NTQQFriendApi.getBuddyIdMapCache(true)));
uid = (await NTQQFriendApi.getBuddyIdMapCache(true)).getValue(Uin);//从Buddy缓存获取Uid
if (uid) return uid;
uid = (await NTQQFriendApi.getBuddyIdMap(true)).getValue(Uin);
if (uid) return uid;
let unveifyUid = (await NTQQUserApi.getUserDetailInfoByUinV2(Uin)).detail.uid;//从QQ Native 特殊转换
if (unveifyUid.indexOf("*") == -1) uid = unveifyUid;
//if (uid) return uid;
return uid;
}
//后期改成流水线处理
static async getUinByUidV2(Uid: string) {
let uin = (await napCatCore.session.getProfileService().getUinByUid('FriendsServiceImpl', [Uid])).get(Uid);
if (uin) return uin;
uin = (await napCatCore.session.getGroupService().getUinByUids([Uid])).uins.get(Uid);
if (uin) return uin;
uin = (await napCatCore.session.getUixConvertService().getUin([Uid])).uinInfo.get(Uid);
if (uin) return uin;
uin = (await NTQQFriendApi.getBuddyIdMapCache(true)).getKey(Uid);//从Buddy缓存获取Uin
if (uin) return uin;
uin = (await NTQQFriendApi.getBuddyIdMap(true)).getKey(Uid);
if (uin) return uin;
uin = (await NTQQUserApi.getUserDetailInfo(Uid)).uin; //从QQ Native 转换
return uin;
}
static async getUidByUinV1(Uin: string) {
// 通用转换开始尝试
let uid = (await napCatCore.session.getUixConvertService().getUid([Uin])).uidInfo.get(Uin);
// Uid 好友转
if (!uid) { if (!uid) {
Array.from(friends.values()).forEach((t) => { Array.from(friends.values()).forEach((t) => {
if (t.uin == Uin) { if (t.uin == Uin) {
//logDebug('getUidByUin', t.uid, t.uin, Uin);
uid = t.uid; uid = t.uid;
} }
//console.log(t.uid, t.uin, Uin);
}); });
//uid = Array.from(friends.values()).find((t) => { t.uin == Uin })?.uid; // 从NC维护的QQ Buddy缓存 转换
} }
//Uid 群友列表转
// if (!uid) { if (!uid) {
// uid = (await NTQQFriendApi.getFriends(false)).find((t) => { t.uin == Uin })?.uid; //从QQ Native 缓存转换 方法一 for (let groupMembersList of groupMembers.values()) {
// } for (let GroupMember of groupMembersList.values()) {
// if (!uid) { if (GroupMember.uin == Uin) {
// uid = (await NTQQFriendApi.getFriends(true)).find((t) => { t.uin == Uin })?.uid; //从QQ Native 非缓存转换 方法二 uid = GroupMember.uid;
// } }
}
}
}
if (!uid) { if (!uid) {
let unveifyUid = (await NTQQUserApi.getUserDetailInfoByUin(Uin)).info.uid;//从QQ Native 特殊转换 方法三 let unveifyUid = (await NTQQUserApi.getUserDetailInfoByUin(Uin)).info.uid;//从QQ Native 特殊转换 方法三
if (unveifyUid.indexOf("*") == -1) { if (unveifyUid.indexOf("*") == -1) {
@@ -217,17 +294,7 @@ export class NTQQUserApi {
} }
return uid; return uid;
} }
@CacheClassFuncAsyncExtend(3600, 'Uid2Uin', (Uid: string | undefined, Uin: number | undefined) => { static async getUinByUidV1(Uid: string) {
if (Uin && Uin != 0 && !isNaN(Uin)) {
return true
}
logWarn("uid转换到uin时异常", Uid);
return false;
})
static async getUinByUid(Uid: string | undefined) {
if (!Uid) {
return '';
}
let ret = await NTEventDispatch.CallNoListenerEvent let ret = await NTEventDispatch.CallNoListenerEvent
<(Uin: string[]) => Promise<{ uinInfo: Map<string, string> }>>( <(Uin: string[]) => Promise<{ uinInfo: Map<string, string> }>>(
'NodeIKernelUixConvertService/getUin', 'NodeIKernelUixConvertService/getUin',
@@ -255,9 +322,26 @@ export class NTQQUserApi {
// } // }
return uin; return uin;
} }
static async getRecentContactListSnapShot(count: number) {
return await napCatCore.session.getRecentContactService().getRecentContactListSnapShot(count);
}
static async getRecentContactListSyncLimit(count: number) {
return await napCatCore.session.getRecentContactService().getRecentContactListSyncLimit(count);
}
static async getRecentContactListSync() {
return await napCatCore.session.getRecentContactService().getRecentContactListSync();
}
static async getRecentContactList() { static async getRecentContactList() {
return await napCatCore.session.getRecentContactService().getRecentContactList(); return await napCatCore.session.getRecentContactService().getRecentContactList();
} }
static async getUserDetailInfoByUinV2(Uin: string) {
return await NTEventDispatch.CallNoListenerEvent
<(Uin: string) => Promise<UserDetailInfoByUinV2>>(
'NodeIKernelProfileService/getUserDetailInfoByUin',
5000,
Uin
);
}
static async getUserDetailInfoByUin(Uin: string) { static async getUserDetailInfoByUin(Uin: string) {
return NTEventDispatch.CallNoListenerEvent return NTEventDispatch.CallNoListenerEvent
<(Uin: string) => Promise<UserDetailInfoByUin>>( <(Uin: string) => Promise<UserDetailInfoByUin>>(

View File

@@ -114,6 +114,22 @@ export interface GroupEssenceMsgRet {
} }
} }
export class WebApi { export class WebApi {
static async shareDigest(groupCode: string, msgSeq: string, msgRandom: string, targetGroupCode: string) {
const CookiesObject = await NTQQUserApi.getCookies('qun.qq.com');
const CookieValue = Object.entries(CookiesObject).map(([key, value]) => `${key}=${value}`).join('; ');
const Bkn = WebApi.genBkn(CookiesObject.skey);
let ret: any = undefined;
const data = 'group_code=' + groupCode + '&msg_seq=' + msgSeq + '&msg_random=' + msgRandom + '&target_group_code=' + targetGroupCode;
const url = 'https://qun.qq.com/cgi-bin/group_digest/share_digest?bkn=' + Bkn + "&" + data;
//console.log(url);
try {
ret = await RequestUtil.HttpGetText(url, 'GET', '', { 'Cookie': CookieValue });
return ret;
} catch (e) {
return undefined;
}
return undefined;
}
@CacheClassFuncAsync(3600 * 1000, 'webapi_get_group_members') @CacheClassFuncAsync(3600 * 1000, 'webapi_get_group_members')
static async getGroupEssenceMsg(GroupCode: string, page_start: string) { static async getGroupEssenceMsg(GroupCode: string, page_start: string) {
const CookiesObject = await NTQQUserApi.getCookies('qun.qq.com'); const CookiesObject = await NTQQUserApi.getCookies('qun.qq.com');

View File

@@ -1,4 +1,5 @@
import QQWrapper, { NodeIQQNTWrapperEngine, NodeIQQNTWrapperSession, NodeQQNTWrapperUtil } from '@/core/wrapper'; import QQWrapper, { NodeIQQNTWrapperEngine, NodeIQQNTWrapperSession, NodeQQNTWrapperUtil } from '@/core/wrapper';
import { DeviceList } from '@/onebot11/main';
import { import {
NodeIKernelLoginService, NodeIKernelLoginService,
NodeIKernelBuddyService, NodeIKernelBuddyService,
@@ -14,14 +15,13 @@ import { DependsAdapter, DispatcherAdapter, GlobalAdapter, NodeIGlobalAdapter }
import path from 'node:path'; import path from 'node:path';
import os from 'node:os'; import os from 'node:os';
import fs from 'node:fs'; import fs from 'node:fs';
import { appid, qqVersionConfigInfo } from '@/common/utils/QQBasicInfo'; import { getFullQQVesion, QQVersionAppid, QQVersionQua, requireMinNTQQBuild } from '@/common/utils/QQBasicInfo';
import { hostname, systemVersion } from '@/common/utils/system'; import { hostname, systemVersion } from '@/common/utils/system';
import { genSessionConfig } from '@/core/sessionConfig'; import { genSessionConfig } from '@/core/sessionConfig';
import { dbUtil } from '@/common/utils/db';
import { sleep } from '@/common/utils/helper'; import { sleep } from '@/common/utils/helper';
import crypto from 'node:crypto'; import crypto from 'node:crypto';
import { rawFriends, friends, groupMembers, groups, selfInfo, stat } from '@/core/data'; import { groupMembers, groups, selfInfo, stat } from '@/core/data';
import { RawMessage } from '@/core/entities'; import { GroupMember, RawMessage } from '@/core/entities';
import { NTEventDispatch } from '@/common/utils/EventTask'; import { NTEventDispatch } from '@/common/utils/EventTask';
import { import {
enableConsoleLog, enableConsoleLog,
@@ -33,6 +33,7 @@ import {
setLogSelfInfo setLogSelfInfo
} from '@/common/utils/log'; } from '@/common/utils/log';
import { napCatConfig } from '@/core/utils/config'; import { napCatConfig } from '@/core/utils/config';
import { NTQQFriendApi } from './apis';
export interface OnLoginSuccess { export interface OnLoginSuccess {
(uin: string, uid: string): void | Promise<void>; (uin: string, uid: string): void | Promise<void>;
@@ -84,20 +85,15 @@ export class NapCatCore {
const dataPath = path.resolve(this.dataPath, './NapCat/data'); const dataPath = path.resolve(this.dataPath, './NapCat/data');
fs.mkdirSync(dataPath, { recursive: true }); fs.mkdirSync(dataPath, { recursive: true });
logDebug('本账号数据/缓存目录:', dataPath); logDebug('本账号数据/缓存目录:', dataPath);
dbUtil.init(path.resolve(dataPath, `./${arg.uin}-v2.db`)).then(() => { this.initDataListener();
this.initDataListener(); this.onLoginSuccessFuncList.map(cb => {
this.onLoginSuccessFuncList.map(cb => { new Promise((resolve, reject) => {
new Promise((resolve, reject) => { const result = cb(arg.uin, arg.uid);
const result = cb(arg.uin, arg.uid); if (result instanceof Promise) {
if (result instanceof Promise) { result.then(resolve).catch(reject);
result.then(resolve).catch(reject); }
} }).then();
}).then();
});
}).catch((e) => {
logError('数据库初始化失败', e);
}); });
// this.initDataListener();
}).catch((e) => { }).catch((e) => {
logError('initSession failed', e); logError('initSession failed', e);
throw new Error(`启动失败: ${JSON.stringify(e)}`); throw new Error(`启动失败: ${JSON.stringify(e)}`);
@@ -108,8 +104,8 @@ export class NapCatCore {
logError('登录失败(onQRCodeSessionFailed)', errMsg); logError('登录失败(onQRCodeSessionFailed)', errMsg);
if (errType == 1 && errCode == 3) { if (errType == 1 && errCode == 3) {
// 二维码过期刷新 // 二维码过期刷新
this.loginService.getQRCodePicture();
} }
this.loginService.getQRCodePicture();
}; };
this.loginListener.onLoginFailed = (args) => { this.loginListener.onLoginFailed = (args) => {
logError('登录失败(onLoginFailed)', args); logError('登录失败(onLoginFailed)', args);
@@ -138,10 +134,10 @@ export class NapCatCore {
base_path_prefix: '', base_path_prefix: '',
platform_type: 3, platform_type: 3,
app_type: 4, app_type: 4,
app_version: qqVersionConfigInfo.curVersion, app_version: getFullQQVesion(),
os_version: 'Windows 10 Pro', os_version: 'Windows 10 Pro',
use_xlog: true, use_xlog: true,
qua: `V1_WIN_NQ_${qqVersionConfigInfo.curVersion.replace('-', '_')}_GW_B`, qua: QQVersionQua,
global_path_config: { global_path_config: {
desktopGlobalPath: this.dataPathGlobal, desktopGlobalPath: this.dataPathGlobal,
}, },
@@ -149,10 +145,10 @@ export class NapCatCore {
}, new QQWrapper.NodeIGlobalAdapter(new GlobalAdapter())); }, new QQWrapper.NodeIGlobalAdapter(new GlobalAdapter()));
this.loginService.initConfig({ this.loginService.initConfig({
machineId: '', machineId: '',
appid, appid: QQVersionAppid,
platVer: systemVersion, platVer: systemVersion,
commonPath: this.dataPathGlobal, commonPath: this.dataPathGlobal,
clientVer: qqVersionConfigInfo.curVersion, clientVer: getFullQQVesion(),
hostName: hostname hostName: hostname
}); });
} }
@@ -189,7 +185,6 @@ export class NapCatCore {
} }
}); });
} }
private initDataListener() { private initDataListener() {
// 消息相关 // 消息相关
interface LineDevice { interface LineDevice {
@@ -208,10 +203,17 @@ export class NapCatCore {
} }
const msgListener = new MsgListener(); const msgListener = new MsgListener();
msgListener.onLineDev = (Devices: LineDevice[]) => { msgListener.onLineDev = (Devices: LineDevice[]) => {
DeviceList.splice(0, DeviceList.length);
Devices.map((Device: LineDevice) => { Devices.map((Device: LineDevice) => {
if (Device.clientType === 2) { let DeviceData = {
log('账号设备(' + Device.devUid + ') 在线状态变更'); app_id: Device.devUid,
} device_name: Device.clientType.toString(),
device_kind: Device.clientType.toString(),
};
DeviceList.push(DeviceData);
// if (Device.clientType === 2) {
// log('账号设备(' + Device.devUid + ') 在线状态变更');
// }
}); });
}; };
msgListener.onKickedOffLine = (Info: KickedOffLineInfo) => { msgListener.onKickedOffLine = (Info: KickedOffLineInfo) => {
@@ -242,6 +244,7 @@ export class NapCatCore {
stat.last_message_time = Math.floor(Date.now() / 1000); stat.last_message_time = Math.floor(Date.now() / 1000);
}; };
msgListener.onRecvMsg = (msgList: RawMessage[]) => { msgListener.onRecvMsg = (msgList: RawMessage[]) => {
// console.log(JSON.stringify(msgList[0],null,2));
stat.packet_received += 1; stat.packet_received += 1;
stat.message_received += msgList.length; stat.message_received += msgList.length;
stat.last_message_time = Math.floor(Date.now() / 1000); stat.last_message_time = Math.floor(Date.now() / 1000);
@@ -250,31 +253,23 @@ export class NapCatCore {
stat.packet_received += 1; stat.packet_received += 1;
}; };
this.addListener(msgListener); this.addListener(msgListener);
// 好友相关 // 好友相关
const buddyListener = new BuddyListener(); const buddyListener = new BuddyListener();
buddyListener.onBuddyListChange = arg => {
rawFriends.length = 0;
rawFriends.push(...arg);
// console.log('onBuddyListChange', arg);
for (const categoryItem of arg) {
for (const friend of categoryItem.buddyList) {
// console.log("onBuddyListChange", friend)
const existFriend = friends.get(friend.uid);
if (existFriend) {
Object.assign(existFriend, friend);
}
else {
friends.set(friend.uid, friend);
}
}
// console.log("onBuddyListChange", friend)
}
};
this.addListener(buddyListener); this.addListener(buddyListener);
// 刷新一次好友列表 // 刷新一次好友列表 26702版本以下需要手动刷新一次获取 高版本NTQQ自带缓存
this.session.getBuddyService().getBuddyList(true).then(arg => { if (!requireMinNTQQBuild('26702')) {
// console.log('getBuddyList', arg); this.session.getBuddyService().getBuddyList(true).then(arg => {
}); // console.log('getBuddyList', arg);
});
} else {
// NTQQFriendApi.getBuddyV2(true).then((res) => {
// res.forEach((item) => {
// CachedIdMap.set(item.uid!, item.uin!);
// });
// }).catch();
}
interface SelfStatusInfo { interface SelfStatusInfo {
uid: string uid: string
status: number status: number
@@ -327,6 +322,7 @@ export class NapCatCore {
if (groupMembers.has(groupCode)) { if (groupMembers.has(groupCode)) {
const existMembers = groupMembers.get(groupCode)!; const existMembers = groupMembers.get(groupCode)!;
arg.infos.forEach((member, uid) => { arg.infos.forEach((member, uid) => {
//console.log('onMemberListChange', member);
const existMember = existMembers.get(uid); const existMember = existMembers.get(uid);
if (existMember) { if (existMember) {
Object.assign(existMember, member); Object.assign(existMember, member);
@@ -346,7 +342,7 @@ export class NapCatCore {
// console.log('onMemberListChange', groupCode, arg); // console.log('onMemberListChange', groupCode, arg);
}; };
groupListener.onMemberInfoChange = (groupCode, changeType, members) => { groupListener.onMemberInfoChange = (groupCode, changeType, members) => {
// console.log('onMemberInfoChange', arg); //console.log('onMemberInfoChange', groupCode, changeType, members);
if (changeType === 0 && members.get(selfInfo.uid)?.isDelete) { if (changeType === 0 && members.get(selfInfo.uid)?.isDelete) {
// 自身退群或者被踢退群 5s用于Api操作 之后不再出现 // 自身退群或者被踢退群 5s用于Api操作 之后不再出现
setTimeout(() => { setTimeout(() => {
@@ -359,6 +355,9 @@ export class NapCatCore {
members.forEach((member, uid) => { members.forEach((member, uid) => {
const existMember = existMembers.get(uid); const existMember = existMembers.get(uid);
if (existMember) { if (existMember) {
// 检查管理变动
member.isChangeRole = this.checkAdminEvent(groupCode, member, existMember);
// 更新成员信息
Object.assign(existMember, member); Object.assign(existMember, member);
} }
else { else {
@@ -471,6 +470,13 @@ export class NapCatCore {
const loginList = await this.loginService.getLoginList(); const loginList = await this.loginService.getLoginList();
return loginList; return loginList;
} }
checkAdminEvent(groupCode: string, memberNew: GroupMember, memberOld: GroupMember | undefined): boolean {
if (memberNew.role !== memberOld?.role) {
log(`${groupCode} ${memberNew.nick} 角色变更为 ${memberNew.role === 3 ? '管理员' : '群员'}`);
return true;
}
return false;
}
} }
export const napCatCore = new NapCatCore(); export const napCatCore = new NapCatCore();

View File

@@ -3,9 +3,11 @@ import {
type Group, type Group,
type GroupMember, type GroupMember,
type SelfInfo, type SelfInfo,
type BuddyCategoryType type BuddyCategoryType,
FriendV2
} from './entities'; } from './entities';
import { isNumeric } from '@/common/utils/helper'; import { isNumeric } from '@/common/utils/helper';
import { LimitedHashTable } from '@/common/utils/MessageUnique';
import { NTQQGroupApi } from '@/core/apis'; import { NTQQGroupApi } from '@/core/apis';
export const selfInfo: SelfInfo = { export const selfInfo: SelfInfo = {
@@ -14,8 +16,6 @@ export const selfInfo: SelfInfo = {
nick: '', nick: '',
online: true online: true
}; };
// 未来只在此处保留 selfInfo stat
// groupCode -> Group
export const groups: Map<string, Group> = new Map<string, Group>(); export const groups: Map<string, Group> = new Map<string, Group>();
export function deleteGroup(groupQQ: string) { export function deleteGroup(groupQQ: string) {
@@ -26,9 +26,10 @@ export function deleteGroup(groupQQ: string) {
// 群号 -> 群成员map(uid=>GroupMember) // 群号 -> 群成员map(uid=>GroupMember)
export const groupMembers: Map<string, Map<string, GroupMember>> = new Map<string, Map<string, GroupMember>>(); export const groupMembers: Map<string, Map<string, GroupMember>> = new Map<string, Map<string, GroupMember>>();
// uid -> Friend 下面这俩个准备移除 QQ里面自带缓存
export const friends: Map<string, Friend> = new Map<string, Friend>(); export const friends: Map<string, Friend> = new Map<string, Friend>();
export const rawFriends: Array<BuddyCategoryType> = []; // 带分组的好友列表
//转换列表
//export const CachedIdMap = new LimitedHashTable<string, string>(1000);
export async function getGroup(qq: string | number): Promise<Group | undefined> { export async function getGroup(qq: string | number): Promise<Group | undefined> {
let group = groups.get(qq.toString()); let group = groups.get(qq.toString());

View File

@@ -10,7 +10,8 @@ import {
SendReplyElement, SendReplyElement,
sendShareLocationElement, sendShareLocationElement,
SendTextElement, SendTextElement,
SendVideoElement SendVideoElement,
viedo_type
} from './index'; } from './index';
import { promises as fs } from 'node:fs'; import { promises as fs } from 'node:fs';
import ffmpeg from 'fluent-ffmpeg'; import ffmpeg from 'fluent-ffmpeg';
@@ -127,7 +128,7 @@ export class SendMsgElementConstructor {
return element; return element;
} }
static async video(filePath: string, fileName: string = '', diyThumbPath: string = ''): Promise<SendVideoElement> { static async video(filePath: string, fileName: string = '', diyThumbPath: string = '', videotype: viedo_type = viedo_type.VIDEO_FORMAT_MP4): Promise<SendVideoElement> {
const { fileName: _fileName, path, fileSize, md5 } = await NTQQFileApi.uploadFile(filePath, ElementType.VIDEO); const { fileName: _fileName, path, fileSize, md5 } = await NTQQFileApi.uploadFile(filePath, ElementType.VIDEO);
if (fileSize === 0) { if (fileSize === 0) {
throw '文件异常大小为0'; throw '文件异常大小为0';
@@ -195,6 +196,7 @@ export class SendMsgElementConstructor {
thumbWidth: videoInfo.width, thumbWidth: videoInfo.width,
thumbHeight: videoInfo.height, thumbHeight: videoInfo.height,
fileSize: '' + fileSize, fileSize: '' + fileSize,
//fileFormat: videotype
// fileUuid: "", // fileUuid: "",
// transferStatus: 0, // transferStatus: 0,
// progress: 0, // progress: 0,
@@ -246,7 +248,28 @@ export class SendMsgElementConstructor {
} }
}; };
} }
// NodeIQQNTWrapperSession sendMsg [
// "0",
// {
// "peerUid": "u_e_RIxgTs2NaJ68h0PwOPSg",
// "chatType": 1,
// "guildId": ""
// },
// [
// {
// "elementId": "0",
// "elementType": 6,
// "faceElement": {
// "faceIndex": 0,
// "faceType": 5,
// "msgType": 0,
// "pokeType": 1,
// "pokeStrength": 0
// }
// }
// ],
// {}
// ]
static face(faceId: number): SendFaceElement { static face(faceId: number): SendFaceElement {
// 从face_config.json中获取表情名称 // 从face_config.json中获取表情名称
const sysFaces = faceConfig.sysface; const sysFaces = faceConfig.sysface;

View File

@@ -1,12 +1,18 @@
import { QQLevel, Sex } from './user'; import { QQLevel, Sex } from './user';
export enum GroupListUpdateType {
REFRESHALL,
GETALL,
MODIFIED,
REMOVE
}
export interface Group { export interface Group {
groupCode: string, groupCode: string,
createTime?: string,//高版本才有
maxMember: number, maxMember: number,
memberCount: number, memberCount: number,
groupName: string, groupName: string,
groupStatus: 0, groupStatus: number,
memberRole: 2, memberRole: number,
isTop: boolean, isTop: boolean,
toppedTimestamp: string, toppedTimestamp: string,
privilegeFlag: number, //65760 privilegeFlag: number, //65760
@@ -52,4 +58,7 @@ export interface GroupMember {
isRobot: boolean; isRobot: boolean;
sex?: Sex sex?: Sex
qqLevel?: QQLevel qqLevel?: QQLevel
isChangeRole: boolean;
joinTime: string;
lastSpeakTime: string;
} }

View File

@@ -55,22 +55,24 @@ export enum ElementType {
TASKTOPMSG = 29, TASKTOPMSG = 29,
RECOMMENDEDMSG = 43, RECOMMENDEDMSG = 43,
ACTIONBAR = 44 ACTIONBAR = 44
}
export interface ActionBarElement {
rows: InlineKeyboardRow[];
botAppid: string;
} }
export interface SendActionBarElement { export interface SendActionBarElement {
elementType: ElementType.ACTIONBAR; elementType: ElementType.ACTIONBAR;
elementId: string; elementId: string;
actionBarElement: { actionBarElement: ActionBarElement;
rows: InlineKeyboardRow[]; }
botAppid: string; export interface RecommendedMsgElement {
} rows: InlineKeyboardRow[];
botAppid: string;
} }
export interface SendRecommendedMsgElement { export interface SendRecommendedMsgElement {
elementType: ElementType.RECOMMENDEDMSG; elementType: ElementType.RECOMMENDEDMSG;
elementId: string; elementId: string;
recommendedMsgElement: { recommendedMsgElement: RecommendedMsgElement;
rows: InlineKeyboardRow[];
botAppid: string;
}
} }
export interface InlineKeyboardButton { export interface InlineKeyboardButton {
id: string; id: string;
@@ -93,67 +95,72 @@ export interface TofuElementContent {
color: string; color: string;
tittle: string; tittle: string;
} }
export interface TaskTopMsgElement {
msgTitle: string;
msgSummary: string;
iconUrl: string;
topMsgType: number;
}
export interface SendTaskTopMsgElement { export interface SendTaskTopMsgElement {
elementType: ElementType.TASKTOPMSG; elementType: ElementType.TASKTOPMSG;
elementId: string; elementId: string;
taskTopMsgElement: { taskTopMsgElement: TaskTopMsgElement;
msgTitle: string; }
msgSummary: string; export interface TofuRecordElement {
iconUrl: string; type: number;
topMsgType: number; busiid: string;
} busiuuid: string;
descriptionContent: string;
contentlist: TofuElementContent[],
background: string;
icon: string;
uinlist: string[],
uidlist: string[],
busiExtra: string;
updateTime: string;
dependedmsgid: string;
msgtime: string;
onscreennotify: boolean;
} }
export interface SendTofuRecordElement { export interface SendTofuRecordElement {
elementType: ElementType.TOFURECORD; elementType: ElementType.TOFURECORD;
elementId: string; elementId: string;
tofuRecordElement: { tofuRecordElement: TofuRecordElement;
type: number; }
busiid: string;
busiuuid: string; export interface FaceBubbleElement {
descriptionContent: string; faceCount: number;
contentlist: TofuElementContent[], faceSummary: string;
background: string; faceFlag: number;
icon: string; content: string;
uinlist: string[], oldVersionStr: string;
uidlist: string[], faceType: number;
busiExtra: string; others: string;
updateTime: string; yellowFaceInfo: {
dependedmsgid: string; index: number;
msgtime: string; buf: string;
onscreennotify: boolean; compatibleText: string;
text: string;
} }
} }
export interface SendFaceBubbleElement { export interface SendFaceBubbleElement {
elementType: ElementType.FACEBUBBLE; elementType: ElementType.FACEBUBBLE;
elementId: string; elementId: string;
faceBubbleElement: { faceBubbleElement: FaceBubbleElement;
faceCount: number;
faceSummary: string;
faceFlag: number;
content: string;
oldVersionStr: string;
faceType: number;
others: string;
yellowFaceInfo: {
index: number;
buf: string;
compatibleText: string;
text: string;
}
}
} }
export interface AvRecordElement {
type: number;
time: string;
text: string;
mainType: number;
hasRead: boolean;
extraType: number;
}
export interface SendavRecordElement { export interface SendavRecordElement {
elementType: ElementType.AVRECORD; elementType: ElementType.AVRECORD;
elementId: string; elementId: string;
avRecordElement: { avRecordElement: AvRecordElement;
type: number;
time: string;
text: string;
mainType: number;
hasRead: boolean;
extraType: number;
}
} }
export interface YoloUserInfo { export interface YoloUserInfo {
uid: string; uid: string;
@@ -170,37 +177,40 @@ export interface SendInlineKeyboardElement {
} }
} }
export interface YoloGameResultElement {
UserInfo: YoloUserInfo[];
}
export interface SendYoloGameResultElement { export interface SendYoloGameResultElement {
elementType: ElementType.YOLOGAMERESULT; elementType: ElementType.YOLOGAMERESULT;
yoloGameResultElement: { yoloGameResultElement: YoloGameResultElement
UserInfo: YoloUserInfo[]; }
} export interface GiphyElement {
id: string;
isClip: boolean;
width: number;
height: number;
} }
export interface SendGiphyElement { export interface SendGiphyElement {
elementType: ElementType.GIPHY; elementType: ElementType.GIPHY;
elementId: string; elementId: string;
giphyElement: { giphyElement: GiphyElement;
id: string;
isClip: boolean;
width: number;
height: number;
}
} }
export interface SendWalletElement { export interface SendWalletElement {
elementType: ElementType.UNKNOWN;//不做 设置位置 elementType: ElementType.UNKNOWN;//不做 设置位置
elementId: string; elementId: string;
walletElement: {} walletElement: {}
} }
export interface CalendarElement {
summary: string;
msg: string;
expireTimeMs: string;
schemaType: number;
schema: string
}
export interface SendCalendarElement { export interface SendCalendarElement {
elementType: ElementType.CALENDAR; elementType: ElementType.CALENDAR;
elementId: string; elementId: string;
calendarElement: { calendarElement: CalendarElement;
summary: string;
msg: string;
expireTimeMs: string;
schemaType: number;
schema: string
}
} }
export interface SendliveGiftElement { export interface SendliveGiftElement {
elementType: ElementType.LIVEGIFT; elementType: ElementType.LIVEGIFT;
@@ -252,32 +262,18 @@ export enum PicSubType {
export interface SendPicElement { export interface SendPicElement {
elementType: ElementType.PIC; elementType: ElementType.PIC;
elementId: string; elementId: string;
picElement: { picElement:PicElement
md5HexStr: string; }
fileSize: number | string; export interface ReplyElement {
picWidth: number; replayMsgSeq: string;
picHeight: number; replayMsgId: string;
fileName: string; senderUin: string;
sourcePath: string; senderUinStr: string;
original: boolean;
picType: PicType;
picSubType: PicSubType;
fileUuid: string;
fileSubId: string;
thumbFileSize: number;
summary: string;
};
} }
export interface SendReplyElement { export interface SendReplyElement {
elementType: ElementType.REPLY; elementType: ElementType.REPLY;
elementId: string; elementId: string;
replyElement: { replyElement: ReplyElement
replayMsgSeq: string;
replayMsgId: string;
senderUin: string;
senderUinStr: string;
}
} }
export interface SendFaceElement { export interface SendFaceElement {
@@ -293,10 +289,11 @@ export interface SendMarketFaceElement {
export interface SendstructLongMsgElement { export interface SendstructLongMsgElement {
elementType: ElementType.STRUCTLONGMSG; elementType: ElementType.STRUCTLONGMSG;
elementId: string; elementId: string;
structLongMsgElement: { structLongMsgElement: StructLongMsgElement;
xmlContent: string; }
resId: string; export interface StructLongMsgElement {
} xmlContent: string;
resId: string;
} }
export interface SendactionBarElement { export interface SendactionBarElement {
elementType: ElementType.ACTIONBAR; elementType: ElementType.ACTIONBAR;
@@ -306,13 +303,14 @@ export interface SendactionBarElement {
botAppid: string; botAppid: string;
} }
} }
export interface ShareLocationElement {
text: string;
ext: string;
}
export interface sendShareLocationElement { export interface sendShareLocationElement {
elementType: ElementType.SHARELOCATION; elementType: ElementType.SHARELOCATION;
elementId: string; elementId: string;
shareLocationElement: { shareLocationElement?: ShareLocationElement;
text: string;
ext: string;
}
} }
export interface FileElement { export interface FileElement {
@@ -356,10 +354,51 @@ export interface SendMarkdownElement {
elementId: string; elementId: string;
markdownElement: MarkdownElement; markdownElement: MarkdownElement;
} }
export type SendMessageElement = SendTextElement | SendPttElement | export type SendMessageElement = SendTextElement | SendPttElement |
SendPicElement | SendReplyElement | SendFaceElement | SendMarketFaceElement | SendFileElement | SendVideoElement | SendArkElement | SendMarkdownElement | sendShareLocationElement SendPicElement | SendReplyElement | SendFaceElement | SendMarketFaceElement | SendFileElement |
SendVideoElement | SendArkElement | SendMarkdownElement | sendShareLocationElement;
export interface TextElement {
content: string;
atType: number;
atUid: string;
atTinyId: string;
atNtUid: string;
}
export interface MessageElement {
elementType: ElementType,
elementId: string,
extBufForUI: string,//"0x",
textElement?: TextElement;
faceElement?: FaceElement,
marketFaceElement?: MarkdownElement,
replyElement?: ReplyElement,
picElement?: PicElement,
pttElement?: PttElement,
videoElement?: VideoElement,
grayTipElement?: GrayTipElement,
arkElement?: ArkElement,
fileElement?: FileElement,
liveGiftElement?: null,
markdownElement?: MarkdownElement,
structLongMsgElement?: StructLongMsgElement,
multiForwardMsgElement?: MultiForwardMsgElement,
giphyElement?: GiphyElement,
walletElement?: null,
inlineKeyboardElement?: InlineKeyboardElement,
textGiftElement?: null,//????
calendarElement?: CalendarElement,
yoloGameResultElement?: YoloGameResultElement,
avRecordElement?: AvRecordElement,
structMsgElement?: null,
faceBubbleElement?: FaceBubbleElement,
shareLocationElement?: ShareLocationElement,
tofuRecordElement?: TofuRecordElement,
taskTopMsgElement?: TaskTopMsgElement,
recommendedMsgElement?: RecommendedMsgElement,
actionBarElement?:ActionBarElement
}
export enum AtType { export enum AtType {
notAt = 0, notAt = 0,
atAll = 1, atAll = 1,
@@ -450,18 +489,23 @@ export const IMAGE_HTTP_HOST = 'https://gchat.qpic.cn';
export const IMAGE_HTTP_HOST_NT = 'https://multimedia.nt.qq.com.cn'; export const IMAGE_HTTP_HOST_NT = 'https://multimedia.nt.qq.com.cn';
export interface PicElement { export interface PicElement {
picSubType?: number; md5HexStr?: string;
originImageUrl: string; // http url, 没有hosthost是https://gchat.qpic.cn/, 带download参数的是https://multimedia.nt.qq.com.cn fileSize: number | string;//number
originImageMd5?: string;
sourcePath: string; // 图片本地路径
thumbPath: Map<number, string>;
picWidth: number; picWidth: number;
picHeight: number; picHeight: number;
fileSize: number;
fileName: string; fileName: string;
sourcePath: string;
original: boolean;
picType: PicType;
picSubType?: PicSubType;
fileUuid: string; fileUuid: string;
md5HexStr?: string; fileSubId: string;
} thumbFileSize: number;
summary: string;
thumbPath: Map<number, string>;
originImageMd5?: string;
originImageUrl?: string; // http url, 没有hosthost是https://gchat.qpic.cn/, 带download参数的是https://multimedia.nt.qq.com.cn
};
export enum GrayTipElementSubType { export enum GrayTipElementSubType {
INVITE_NEW_MEMBER = 12, INVITE_NEW_MEMBER = 12,
@@ -526,7 +570,7 @@ export interface VideoElement {
thumbMd5?: string thumbMd5?: string
fileTime?: number; // second fileTime?: number; // second
thumbSize?: number; // byte thumbSize?: number; // byte
fileFormat?: number; // 2表示mp4 参考下面条目 fileFormat?: viedo_type; // 2表示mp4 参考下面条目
fileSize?: string; // byte fileSize?: string; // byte
thumbWidth?: number; thumbWidth?: number;
thumbHeight?: number; thumbHeight?: number;
@@ -687,6 +731,7 @@ export interface MultiForwardMsgElement {
} }
export interface RawMessage { export interface RawMessage {
guildId: string;
msgRandom: string; msgRandom: string;
// int32, 自己维护的消息id // int32, 自己维护的消息id
id?: number; id?: number;
@@ -708,10 +753,12 @@ export interface RawMessage {
chatType: ChatType; chatType: ChatType;
sendStatus?: number; // 消息状态别人发的2是已撤回自己发的2是已发送 sendStatus?: number; // 消息状态别人发的2是已撤回自己发的2是已发送
recallTime: string; // 撤回时间, "0"是没有撤回 recallTime: string; // 撤回时间, "0"是没有撤回
records: RawMessage[];
elements: { elements: {
elementId: string; elementId: string;
elementType: ElementType; elementType: ElementType;
replyElement: { replyElement: {
sourceMsgIdInRecords: string;
senderUid: string; // 原消息发送者QQ号 senderUid: string; // 原消息发送者QQ号
sourceMsgIsIncPic: boolean; // 原消息是否有图片 sourceMsgIsIncPic: boolean; // 原消息是否有图片
sourceMsgText: string; sourceMsgText: string;

View File

@@ -62,6 +62,7 @@ export enum BuddyReqType {
KMEINITIATORWAITPEERCONFIRM KMEINITIATORWAITPEERCONFIRM
} }
export interface FriendRequest { export interface FriendRequest {
isInitiator?: boolean;
isDecide: boolean; isDecide: boolean;
friendUid: string; friendUid: string;
reqType: BuddyReqType, reqType: BuddyReqType,

View File

@@ -9,7 +9,181 @@ export interface BuddyCategoryType {
categroyMbCount: number; categroyMbCount: number;
buddyList: User[]; buddyList: User[];
} }
export interface CoreInfo {
uid: string;
uin: string;
nick: string;
remark: string;
}
export interface BaseInfo {
qid: string;
longNick: string;
birthday_year: number;
birthday_month: number;
birthday_day: number;
age: number;
sex: number;
eMail: string;
phoneNum: string;
categoryId: number;
richTime: number;
richBuffer: string;
}
interface MusicInfo {
buf: string;
}
interface VideoBizInfo {
cid: string;
tvUrl: string;
synchType: string;
}
interface VideoInfo {
name: string;
}
interface ExtOnlineBusinessInfo {
buf: string;
customStatus: any;
videoBizInfo: VideoBizInfo;
videoInfo: VideoInfo;
}
interface ExtBuffer {
buf: string;
}
interface UserStatus {
uid: string;
uin: string;
status: number;
extStatus: number;
batteryStatus: number;
termType: number;
netType: number;
iconType: number;
customStatus: any;
setTime: string;
specialFlag: number;
abiFlag: number;
eNetworkType: number;
showName: string;
termDesc: string;
musicInfo: MusicInfo;
extOnlineBusinessInfo: ExtOnlineBusinessInfo;
extBuffer: ExtBuffer;
}
interface PrivilegeIcon {
jumpUrl: string;
openIconList: any[];
closeIconList: any[];
}
interface VasInfo {
vipFlag: boolean;
yearVipFlag: boolean;
svipFlag: boolean;
vipLevel: number;
bigClub: boolean;
bigClubLevel: number;
nameplateVipType: number;
grayNameplateFlag: number;
superVipTemplateId: number;
diyFontId: number;
pendantId: number;
pendantDiyId: number;
faceId: number;
vipFont: number;
vipFontType: number;
magicFont: number;
fontEffect: number;
newLoverDiamondFlag: number;
extendNameplateId: number;
diyNameplateIDs: any[];
vipStartFlag: number;
vipDataFlag: number;
gameNameplateId: string;
gameLastLoginTime: string;
gameRank: number;
gameIconShowFlag: boolean;
gameCardId: string;
vipNameColorId: string;
privilegeIcon: PrivilegeIcon;
}
interface RelationFlags {
topTime: string;
isBlock: boolean;
isMsgDisturb: boolean;
isSpecialCareOpen: boolean;
isSpecialCareZone: boolean;
ringId: string;
isBlocked: boolean;
recommendImgFlag: number;
disableEmojiShortCuts: number;
qidianMasterFlag: number;
qidianCrewFlag: number;
qidianCrewFlag2: number;
isHideQQLevel: number;
isHidePrivilegeIcon: number;
}
interface CommonExt {
constellation: number;
shengXiao: number;
kBloodType: number;
homeTown: string;
makeFriendCareer: number;
pos: string;
college: string;
country: string;
province: string;
city: string;
postCode: string;
address: string;
regTime: number;
interest: string;
labels: any[];
qqLevel: QQLevel;
}
interface Pic {
picId: string;
picTime: number;
picUrlMap: Record<string, string>;
}
interface PhotoWall {
picList: Pic[];
}
export interface SimpleInfo {
uid?: string;
uin?: string;
coreInfo: CoreInfo;
baseInfo: BaseInfo;
status: UserStatus | null;
vasInfo: VasInfo | null;
relationFlags: RelationFlags | null;
otherFlags: any | null;
intimate: any | null;
}
export interface FriendV2 extends SimpleInfo {
categoryId?: number;
categroyName?: string;
}
export interface UserDetailInfoListenerArg {
uid: string;
uin: string;
simpleInfo: SimpleInfo;
commonExt: CommonExt;
photoWall: PhotoWall;
}
export interface ModifyProfileParams { export interface ModifyProfileParams {
nick: string, nick: string,
longNick: string, longNick: string,
@@ -101,7 +275,18 @@ export interface Friend extends User { }
export enum BizKey { export enum BizKey {
KPRIVILEGEICON, KPRIVILEGEICON,
KPHOTOWALL KPHOTOWALL
} }
export interface UserDetailInfoByUinV2 {
result: number,
errMsg: string,
detail: {
uid: string,
uin: string,
simpleInfo: SimpleInfo,
commonExt: CommonExt,
photoWall: null
}
}
export interface UserDetailInfoByUin { export interface UserDetailInfoByUin {
result: number, result: number,
errMsg: string, errMsg: string,

42
src/core/src/external/appid.json vendored Normal file
View File

@@ -0,0 +1,42 @@
{
"3.1.2-13107": {
"appid": 537146866,
"qua": "V1_LNX_NQ_3.1.2-13107_RDM_B"
},
"3.2.10-25765": {
"appid": 537234773,
"qua": "V1_LNX_NQ_3.2.10_25765_GW_B"
},
"3.2.12-26702": {
"appid": 537237950,
"qua": "V1_LNX_NQ_3.2.12_26702_GW_B"
},
"9.9.11-24815": {
"appid": 537226656,
"qua": "V1_WIN_NQ_9.9.11_24815_GW_B"
},
"9.9.12-25493": {
"appid": 537231759,
"qua": "V1_WIN_NQ_9.9.12_25493_GW_B"
},
"9.9.12-25765": {
"appid": 537234702,
"qua": "V1_WIN_NQ_9.9.12_25765_GW_B"
},
"9.9.12-26299": {
"appid": 537234826,
"qua": "V1_WIN_NQ_9.9.12_26299_GW_B"
},
"9.9.12-26339": {
"appid": 537234826,
"qua": "V1_WIN_NQ_9.9.12_26339_GW_B"
},
"9.9.12-26466": {
"appid": 537234826,
"qua": "V1_WIN_NQ_9.9.12_26466_GW_B"
},
"9.9.15-26702": {
"appid": 537237765,
"qua": "V1_WIN_NQ_9.9.15_26702_GW_B"
}
}

View File

@@ -3,6 +3,8 @@ import { BuddyCategoryType, FriendRequestNotify } from '@/core/entities';
export type OnBuddyChangeParams = BuddyCategoryType[] export type OnBuddyChangeParams = BuddyCategoryType[]
interface IBuddyListener { interface IBuddyListener {
onBuddyListChangedV2(arg: unknown): void,//V2版本 还没兼容
onBuddyListChange(arg: OnBuddyChangeParams): void, onBuddyListChange(arg: OnBuddyChangeParams): void,
onBuddyInfoChange(arg: unknown): void, onBuddyInfoChange(arg: unknown): void,
@@ -44,6 +46,9 @@ export interface NodeIKernelBuddyListener extends IBuddyListener {
} }
export class BuddyListener implements IBuddyListener { export class BuddyListener implements IBuddyListener {
onBuddyListChangedV2(arg: unknown): void {
//throw new Error('Method not implemented.');
}
onAddBuddyNeedVerify(arg: unknown) { onAddBuddyNeedVerify(arg: unknown) {
} }

View File

@@ -1,7 +1,7 @@
import { Group, GroupMember, GroupNotify } from '@/core/entities'; import { Group, GroupListUpdateType, GroupMember, GroupNotify } from '@/core/entities';
interface IGroupListener { interface IGroupListener {
onGroupListUpdate(updateType: number, groupList: Group[]): void; onGroupListUpdate(updateType: GroupListUpdateType, groupList: Group[]): void;
onGroupExtListUpdate(...args: unknown[]): void; onGroupExtListUpdate(...args: unknown[]): void;
@@ -96,7 +96,7 @@ export class GroupListener implements IGroupListener {
onGroupFirstBulletinNotify(...args: unknown[]) { onGroupFirstBulletinNotify(...args: unknown[]) {
} }
onGroupListUpdate(updateType: number, groupList: Group[]) { onGroupListUpdate(updateType: GroupListUpdateType, groupList: Group[]) {
} }
onGroupNotifiesUpdated(dboubt: boolean, notifies: GroupNotify[]) { onGroupNotifiesUpdated(dboubt: boolean, notifies: GroupNotify[]) {
@@ -202,7 +202,7 @@ export class DebugGroupListener implements IGroupListener {
console.log('onGroupNotifiesUnreadCountUpdated:', ...args); console.log('onGroupNotifiesUnreadCountUpdated:', ...args);
} }
onGroupSingleScreenNotifies(doubt: boolean, seq: string, notifies: GroupNotify[]){ onGroupSingleScreenNotifies(doubt: boolean, seq: string, notifies: GroupNotify[]) {
console.log('onGroupSingleScreenNotifies:'); console.log('onGroupSingleScreenNotifies:');
} }

View File

@@ -40,14 +40,14 @@ export interface onGroupFileInfoUpdateParamType {
// fromNick: '拾xxxx, // fromNick: '拾xxxx,
// sig: '0x' // sig: '0x'
// } // }
export interface TempOnRecvParams{ export interface TempOnRecvParams {
sessionType: number,//1 sessionType: number,//1
chatType: ChatType,//100 chatType: ChatType,//100
peerUid: string,//uid peerUid: string,//uid
groupCode: string,//gc groupCode: string,//gc
fromNick: string,//gc name fromNick: string,//gc name
sig: string, sig: string,
} }
export interface IKernelMsgListener { export interface IKernelMsgListener {
onAddSendMsg(msgRecord: RawMessage): void; onAddSendMsg(msgRecord: RawMessage): void;
@@ -158,7 +158,41 @@ export interface IKernelMsgListener {
onRichMediaUploadComplete(fileTransNotifyInfo: unknown): void; onRichMediaUploadComplete(fileTransNotifyInfo: unknown): void;
onSearchGroupFileInfoUpdate(searchGroupFileResult: 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; onSendMsgError(j2: unknown, contact: unknown, i2: unknown, str: unknown): void;
@@ -192,7 +226,7 @@ export interface IKernelMsgListener {
} }
export interface NodeIKernelMsgListener { export interface NodeIKernelMsgListener extends IKernelMsgListener {
// eslint-disable-next-line @typescript-eslint/no-misused-new // eslint-disable-next-line @typescript-eslint/no-misused-new
new(listener: IKernelMsgListener): NodeIKernelMsgListener; new(listener: IKernelMsgListener): NodeIKernelMsgListener;
} }

View File

@@ -1,8 +1,8 @@
import { User } from '@/core/entities'; import { User, UserDetailInfoListenerArg } from '@/core/entities';
interface IProfileListener { interface IProfileListener {
onProfileSimpleChanged(...args: unknown[]): void; onProfileSimpleChanged(...args: unknown[]): void;
onUserDetailInfoChanged(arg: UserDetailInfoListenerArg): void;
onProfileDetailInfoChanged(profile: User): void; onProfileDetailInfoChanged(profile: User): void;
onStatusUpdate(...args: unknown[]): void; onStatusUpdate(...args: unknown[]): void;
@@ -18,6 +18,9 @@ export interface NodeIKernelProfileListener extends IProfileListener {
} }
export class ProfileListener implements IProfileListener { export class ProfileListener implements IProfileListener {
onUserDetailInfoChanged(arg: UserDetailInfoListenerArg): void {
}
onProfileSimpleChanged(...args: unknown[]) { onProfileSimpleChanged(...args: unknown[]) {
} }

View File

@@ -0,0 +1,44 @@
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 {
onDeletedContactsNotify(...args: unknown[]) {
}
onRecentContactNotification(...args: unknown[]) {
}
onMsgUnreadCountUpdate(...args: unknown[]) {
}
onGuildDisplayRecentContactListChanged(...args: unknown[]) {
}
onRecentContactListChanged(...args: unknown[]) {
}
onRecentContactListChangedVer2(...args: unknown[]) {
}
}

View File

@@ -1,19 +1,44 @@
import { Friend } from '@/core/entities'; import { Friend } from '@/core/entities';
import { GeneralCallResult } from '@/core/services/common'; import { GeneralCallResult } from '@/core/services/common';
import { NodeIKernelBuddyListener } from '@/core/listeners'; import { NodeIKernelBuddyListener } from '@/core/listeners';
export enum BuddyListReqType {
KNOMAL,
KLETTER
}
export interface NodeIKernelBuddyService { export interface NodeIKernelBuddyService {
// 以下为自行添加的wrapper.node中并没有这些方法,目的是简化调用 // 26702 以上
friends: Friend[]; getBuddyListV2(callFrom: string, reqType: BuddyListReqType): Promise<GeneralCallResult & {
data: Array<{
getFriend(uidOrUin: string): Promise<Friend>; categoryId: number,
categorySortId: number,
categroyName: string,
categroyMbCount: number,
onlineCount: number,
buddyUids: Array<string>
}>
}>;
//26702 以上
getBuddyListFromCache(callFrom: string): Promise<Array<
{
categoryId: number,//9999应该跳过 那是兜底数据吧
categorySortId: number,//排序方式
categroyName: string,//分类名
categroyMbCount: number,//不懂
onlineCount: number,//在线数目
buddyUids: Array<string>//Uids
}>>;
// 以下为原生方法 // 以下为原生方法
addKernelBuddyListener(listener: NodeIKernelBuddyListener): number; addKernelBuddyListener(listener: NodeIKernelBuddyListener): number;
getAllBuddyCount(): number;
removeKernelBuddyListener(listener: unknown): void; removeKernelBuddyListener(listener: unknown): void;
getBuddyList(bool: boolean): Promise<GeneralCallResult>; /**
* @deprecated
* @param nocache 使用缓存
*/
getBuddyList(nocache: boolean): Promise<GeneralCallResult>;
getBuddyNick(uid: number): string; getBuddyNick(uid: number): string;

View File

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

View File

@@ -8,7 +8,90 @@ import {
} from '@/core/entities'; } from '@/core/entities';
import { GeneralCallResult } from '@/core/services/common'; import { GeneralCallResult } from '@/core/services/common';
//高版本的接口不应该随意使用 使用应该严格进行pr审核 同时部分ipc中未出现的接口不要过于依赖 应该做好数据兜底
export interface NodeIKernelGroupService { export interface NodeIKernelGroupService {
getMemberCommonInfo(Req: {
groupCode: string,
startUin: string,
identifyFlag: string,
uinList: string[],
memberCommonFilter: {
memberUin: number,
uinFlag: number,
uinFlagExt: number,
uinMobileFlag: number,
shutUpTime: number,
privilege: number,
},
memberNum: number,
filterMethod: string,
onlineFlag: string,
realSpecialTitleFlag: number
}): Promise<unknown>;
//26702
getGroupMemberLevelInfo(groupCode: string): Promise<unknown>;
//26702
getGroupHonorList(groupCodes: Array<string>): unknown;
getUinByUids(uins: string[]): Promise<{
errCode: number,
errMsg: string,
uins: Map<string, string>
}>;
getUidByUins(uins: string[]): Promise<{
errCode: number,
errMsg: string,
uids: Map<string, string>
}>;
//26702(其实更早 但是我不知道)
checkGroupMemberCache(arrayList: Array<string>): Promise<unknown>;
//26702(其实更早 但是我不知道)
getGroupLatestEssenceList(groupCode: string): Promise<unknown>;
//26702(其实更早 但是我不知道)
shareDigest(Req: {
appId: string,
appType: number,
msgStyle: number,
recvUin: string,
sendType: number,
clientInfo: {
platform: number
},
richMsg: {
usingArk: boolean,
title: string,
summary: string,
url: string,
pictureUrl: string,
brief: string
}
}): Promise<unknown>;
//26702(其实更早 但是我不知道)
isEssenceMsg(Req: { groupCode: string, msgRandom: number, msgSeq: number }): Promise<unknown>;
//26702(其实更早 但是我不知道)
queryCachedEssenceMsg(Req: { groupCode: string, msgRandom: number, msgSeq: number }): Promise<unknown>;
//26702(其实更早 但是我不知道)
fetchGroupEssenceList(Req: { groupCode: string, pageStart: number, pageLimit: number }, Arg: unknown): Promise<unknown>;
//26702
getAllMemberList(groupCode: string, forceFetch: boolean): Promise<{
errCode: number,
errMsg: string,
result: {
ids: Array<{
uid: string,
index: number//0
}>,
infos: {},
finish: true,
hasRobot: false
}
}>;
setHeader(uid: string, path: string): unknown;
addKernelGroupListener(listener: NodeIKernelGroupListener): number; addKernelGroupListener(listener: NodeIKernelGroupListener): number;
@@ -16,7 +99,7 @@ export interface NodeIKernelGroupService {
createMemberListScene(groupCode: string, scene: string): string; createMemberListScene(groupCode: string, scene: string): string;
destroyMemberListScene(): void; destroyMemberListScene(SceneId:string): void;
//About Arg (a) name: lastId 根据手Q来看为object {index:?(number),uid:string} //About Arg (a) name: lastId 根据手Q来看为object {index:?(number),uid:string}
getNextMemberList(sceneId: string, a: undefined, num: number): Promise<{ getNextMemberList(sceneId: string, a: undefined, num: number): Promise<{
errCode: number, errMsg: string, errCode: number, errMsg: string,
@@ -27,9 +110,9 @@ export interface NodeIKernelGroupService {
monitorMemberList(): unknown; monitorMemberList(): unknown;
searchMember(uid: string): unknown; searchMember(sceneId: string, keywords: string[]): unknown;
getMemberInfo(uid: string): unknown; getMemberInfo(group_id: string, uids: string[], forceFetch: boolean): Promise<GeneralCallResult>;
//getMemberInfo [ '56729xxxx', [ 'u_4Nj08cwW5Hxxxxx' ], true ] //getMemberInfo [ '56729xxxx', [ 'u_4Nj08cwW5Hxxxxx' ], true ]
kickMember(groupCode: string, memberUids: string[], refuseForever: boolean, kickReason: string): Promise<void>; kickMember(groupCode: string, memberUids: string[], refuseForever: boolean, kickReason: string): Promise<void>;
@@ -38,7 +121,7 @@ export interface NodeIKernelGroupService {
modifyMemberCardName(groupCode: string, uid: string, cardName: string): void; modifyMemberCardName(groupCode: string, uid: string, cardName: string): void;
getTransferableMemberInfo(uid: string): unknown; getTransferableMemberInfo(groupCode: string): unknown;//获取整个群的
transferGroup(uid: string): void; transferGroup(uid: string): void;

View File

@@ -0,0 +1,3 @@
export interface NodeIKernelMSFService {
getServerTime(): string;
}

View File

@@ -1,11 +1,24 @@
import { ChatType, ElementType, Peer, RawMessage, SendMessageElement } from '@/core/entities'; import { ElementType, MessageElement, Peer, RawMessage, SendMessageElement } from '@/core/entities';
import { NodeIKernelMsgListener } from '@/core/listeners/NodeIKernelMsgListener'; import { NodeIKernelMsgListener } from '@/core/listeners/NodeIKernelMsgListener';
import { GeneralCallResult } from '@/core/services/common'; import { GeneralCallResult } from '@/core/services/common';
export interface QueryMsgsParams {
chatInfo: Peer,
filterMsgType: [],
filterSendersUid: string[],
filterMsgFromTime: string,
filterMsgToTime: string,
pageLimit: number,
isReverseOrder: boolean,
isIncludeCurrent: boolean
}
export interface NodeIKernelMsgService { export interface NodeIKernelMsgService {
generateMsgUniqueId(chatType: number, time: string): string;
addKernelMsgListener(nodeIKernelMsgListener: NodeIKernelMsgListener): number; addKernelMsgListener(nodeIKernelMsgListener: NodeIKernelMsgListener): number;
sendMsg(msgId: string, peer: Peer, msgElements: SendMessageElement[], map: Map<any, any>): Promise<unknown>; sendMsg(msgId: string, peer: Peer, msgElements: SendMessageElement[], map: Map<any, any>): Promise<GeneralCallResult>;
recallMsg(peer: Peer, msgIds: string[]): Promise<GeneralCallResult>; recallMsg(peer: Peer, msgIds: string[]): Promise<GeneralCallResult>;
@@ -21,7 +34,7 @@ export interface NodeIKernelMsgService {
getAutoReplyTextList(...args: unknown[]): unknown; getAutoReplyTextList(...args: unknown[]): unknown;
getOnLineDev(): Promise<any>; getOnLineDev(): void;
kickOffLine(DevInfo: Object): unknown; kickOffLine(DevInfo: Object): unknown;
@@ -73,7 +86,7 @@ export interface NodeIKernelMsgService {
cancelSendMsg(...args: unknown[]): unknown; cancelSendMsg(...args: unknown[]): unknown;
switchToOfflineSendMsg(...args: unknown[]): unknown; switchToOfflineSendMsg(peer: Peer, MsgId: string): unknown;
reqToOfflineSendMsg(...args: unknown[]): unknown; reqToOfflineSendMsg(...args: unknown[]): unknown;
@@ -115,9 +128,9 @@ export interface NodeIKernelMsgService {
addLocalTofuRecordMsg(...args: unknown[]): unknown; addLocalTofuRecordMsg(...args: unknown[]): unknown;
addLocalRecordMsg(...args: unknown[]): unknown; addLocalRecordMsg(Peer: Peer, msgId: string, ele: MessageElement, attr: Array<any> | number, front: boolean): Promise<unknown>;
deleteMsg(...args: unknown[]): unknown; deleteMsg(Peer: Peer, msgIds: Array<string>): Promise<any>;
updateElementExtBufForUI(...args: unknown[]): unknown; updateElementExtBufForUI(...args: unknown[]): unknown;
@@ -141,7 +154,7 @@ export interface NodeIKernelMsgService {
getLastMessageList(peer: Peer[]): Promise<unknown>; getLastMessageList(peer: Peer[]): Promise<unknown>;
getAioFirstViewLatestMsgs(...args: unknown[]): unknown; getAioFirstViewLatestMsgs(peer: Peer, num: number): unknown;
getMsgs(peer: Peer, msgId: string, count: unknown, queryOrder: boolean): Promise<unknown>; getMsgs(peer: Peer, msgId: string, count: unknown, queryOrder: boolean): Promise<unknown>;
@@ -154,7 +167,7 @@ export interface NodeIKernelMsgService {
// this.$clientSeq = j3; // this.$clientSeq = j3;
// this.$cnt = i2; // this.$cnt = i2;
getMsgsWithMsgTimeAndClientSeqForC2C(...args: unknown[]): Promise<unknown>; getMsgsWithMsgTimeAndClientSeqForC2C(...args: unknown[]): Promise<GeneralCallResult & { msgList: RawMessage[] }>;
getMsgsWithStatus(params: { getMsgsWithStatus(params: {
peer: Peer peer: Peer
@@ -164,9 +177,9 @@ export interface NodeIKernelMsgService {
queryOrder: boolean queryOrder: boolean
isIncludeSelf: boolean isIncludeSelf: boolean
appid: unknown appid: unknown
}): Promise<unknown>; }): Promise<GeneralCallResult & { msgList: RawMessage[] }>;
getMsgsBySeqRange(peer: Peer, startSeq: string, endSeq: string): Promise<unknown>; getMsgsBySeqRange(peer: Peer, startSeq: string, endSeq: string): Promise<GeneralCallResult & { msgList: RawMessage[] }>;
getMsgsBySeqAndCount(peer: Peer, seq: string, count: number, desc: boolean, unknownArg: boolean): Promise<GeneralCallResult & { msgList: RawMessage[] }>; getMsgsBySeqAndCount(peer: Peer, seq: string, count: number, desc: boolean, unknownArg: boolean): Promise<GeneralCallResult & { msgList: RawMessage[] }>;
@@ -174,39 +187,48 @@ export interface NodeIKernelMsgService {
getRecallMsgsByMsgId(peer: Peer, MsgId: string[]): Promise<unknown>; getRecallMsgsByMsgId(peer: Peer, MsgId: string[]): Promise<unknown>;
getMsgsBySeqList(peer: Peer, seqList: string[]): Promise<unknown>; getMsgsBySeqList(peer: Peer, seqList: string[]): Promise<GeneralCallResult & { msgList: RawMessage[] }>;
getSingleMsg(Peer: Peer, msgSeq: string): unknown; getSingleMsg(Peer: Peer, msgSeq: string): Promise<GeneralCallResult & { msgList: RawMessage[] }>;
getSourceOfReplyMsg(...args: unknown[]): unknown; getSourceOfReplyMsg(peer: Peer, MsgId: string, SourceSeq: string): unknown;
getSourceOfReplyMsgV2(...args: unknown[]): unknown; getSourceOfReplyMsgV2(peer: Peer, RootMsgId: string, ReplyMsgId: string): unknown;
getMsgByClientSeqAndTime(...args: unknown[]): unknown; getMsgByClientSeqAndTime(peer: Peer, clientSeq: string, time: string): unknown;
getSourceOfReplyMsgByClientSeqAndTime(...args: unknown[]): unknown; getSourceOfReplyMsgByClientSeqAndTime(peer: Peer, clientSeq: string, time: string): unknown;
//cnt clientSeq?并不是吧
getMsgsByTypeFilter(peer: Peer, msgId: string, cnt: unknown, queryOrder: boolean, typeFilter: { type: number, subtype: Array<number> }): unknown;
getMsgsByTypeFilter(peer: Peer, msgId: string, cnt: unknown, queryOrder: boolean, typeFilters: unknown): unknown; getMsgsByTypeFilters(peer: Peer, msgId: string, cnt: unknown, queryOrder: boolean, typeFilters: Array<{ type: number, subtype: Array<number> }>): unknown;
getMsgsByTypeFilters(...args: unknown[]): unknown;
getMsgWithAbstractByFilterParam(...args: unknown[]): unknown; getMsgWithAbstractByFilterParam(...args: unknown[]): unknown;
queryMsgsWithFilter(...args: unknown[]): unknown; queryMsgsWithFilter(...args: unknown[]): unknown;
queryMsgsWithFilterVer2(MsgId: string, MsgTime: string, param: { /**
chatInfo: { * @deprecated 该函数已被标记为废弃,请使用新的替代方法。
chatType: number, * 使用过滤条件查询消息列表的版本2接口。
peerUid: string *
}, * 该函数通过一系列过滤条件来查询特定聊天中的消息列表。这些条件包括消息类型、发送者、时间范围等。
filterMsgType: [], * 函数返回一个Promise解析为查询结果的未知类型对象。
filterSendersUid: [], *
filterMsgFromTime: string, * @param MsgId 消息ID用于特定消息的查询。
filterMsgToTime: string, * @param MsgTime 消息时间,用于指定消息的时间范围。
pageLimit: number, * @param param 查询参数对象,包含详细的过滤条件和分页信息。
isReverseOrder: boolean, * @param param.chatInfo 聊天信息包括聊天类型和对方用户ID。
isIncludeCurrent: boolean * @param param.filterMsgType 需要过滤的消息类型数组,留空表示不过滤。
}): Promise<unknown>; * @param param.filterSendersUid 需要过滤的发送者用户ID数组。
* @param param.filterMsgFromTime 查询消息的起始时间。
* @param param.filterMsgToTime 查询消息的结束时间。
* @param param.pageLimit 每页的消息数量限制。
* @param param.isReverseOrder 是否按时间顺序倒序返回消息。
* @param param.isIncludeCurrent 是否包含当前页码。
* @returns 返回一个Promise解析为查询结果的未知类型对象。
*/
queryMsgsWithFilterVer2(MsgId: string, MsgTime: string, param: QueryMsgsParams): Promise<unknown>;
// this.chatType = i2; // this.chatType = i2;
// this.peerUid = str; // this.peerUid = str;
@@ -222,46 +244,23 @@ export interface NodeIKernelMsgService {
// this.isReverseOrder = z; // this.isReverseOrder = z;
// this.isIncludeCurrent = z2; // this.isIncludeCurrent = z2;
//queryMsgsWithFilterEx(0L, 0L, 0L, new QueryMsgsParams(new ChatInfo(2, str), new ArrayList(), new ArrayList(), 0L, 0L, 250, false, true)) //queryMsgsWithFilterEx(0L, 0L, 0L, new QueryMsgsParams(new ChatInfo(2, str), new ArrayList(), new ArrayList(), 0L, 0L, 250, false, true))
queryMsgsWithFilterEx(msgId: string, msgTime: string, megSeq: string, param: { queryMsgsWithFilterEx(msgId: string, msgTime: string, megSeq: string, param: QueryMsgsParams): Promise<GeneralCallResult & {
chatInfo: { msgList: RawMessage[]
chatType: number, }>;
peerUid: string
},
filterMsgType: [],
filterSendersUid: [],
filterMsgFromTime: string,
filterMsgToTime: string,
pageLimit: number,
isReverseOrder: boolean,
isIncludeCurrent: boolean
}): Promise<unknown>;
//queryMsgsWithFilterEx(this.$msgId, this.$msgTime, this.$msgSeq, this.$param) //queryMsgsWithFilterEx(this.$msgId, this.$msgTime, this.$msgSeq, this.$param)
queryFileMsgsDesktop(...args: unknown[]): unknown; queryFileMsgsDesktop(...args: unknown[]): unknown;
setMsgRichInfoFlag(...args: unknown[]): unknown; setMsgRichInfoFlag(...args: unknown[]): unknown;
queryPicOrVideoMsgs(msgId: string, msgTime: string, megSeq: string, param: { queryPicOrVideoMsgs(msgId: string, msgTime: string, megSeq: string, param: QueryMsgsParams): Promise<unknown>;
chatInfo: {
chatType: number,
peerUid: string
},
filterMsgType: [],
filterSendersUid: [],
filterMsgFromTime: string,
filterMsgToTime: string,
pageLimit: number,
isReverseOrder: boolean,
isIncludeCurrent: boolean
}): Promise<unknown>;
queryPicOrVideoMsgsDesktop(...args: unknown[]): unknown; queryPicOrVideoMsgsDesktop(...args: unknown[]): unknown;
queryEmoticonMsgs(...args: unknown[]): unknown; queryEmoticonMsgs(msgId: string, msgTime: string, msgSeq: string, Params: QueryMsgsParams): Promise<unknown>;
queryTroopEmoticonMsgs(...args: unknown[]): unknown; queryTroopEmoticonMsgs(msgId: string, msgTime: string, msgSeq: string, Params: QueryMsgsParams): Promise<unknown>;
queryMsgsAndAbstractsWithFilter(...args: unknown[]): unknown; queryMsgsAndAbstractsWithFilter(msgId: string, msgTime: string, megSeq: string, param: QueryMsgsParams): unknown;
setFocusOnGuild(...args: unknown[]): unknown; setFocusOnGuild(...args: unknown[]): unknown;
@@ -327,7 +326,7 @@ export interface NodeIKernelMsgService {
setGuildTabUserFlag(...args: unknown[]): unknown; setGuildTabUserFlag(...args: unknown[]): unknown;
setBuildMode(...args: unknown[]): unknown; setBuildMode(flag: number/*0 1 3*/): unknown;
setConfigurationServiceData(...args: unknown[]): unknown; setConfigurationServiceData(...args: unknown[]): unknown;
@@ -359,8 +358,35 @@ export interface NodeIKernelMsgService {
translatePtt2Text(MsgId: string, Peer: {}, MsgElement: {}): unknown; translatePtt2Text(MsgId: string, Peer: {}, MsgElement: {}): unknown;
setPttPlayedState(...args: unknown[]): unknown; setPttPlayedState(...args: unknown[]): unknown;
// NodeIQQNTWrapperSession fetchFavEmojiList [
fetchFavEmojiList(...args: unknown[]): unknown; // "",
// 48,
// true,
// true
// ]
fetchFavEmojiList(str: string, num: number, uk1: boolean, uk2: boolean): Promise<GeneralCallResult & {
emojiInfoList: Array<{
uin: string,
emoId: number,
emoPath: string,
isExist: boolean,
resId: string,
url: string,
md5: string,
emoOriginalPath: string,
thumbPath: string,
RomaingType: string,
isAPNG: false,
isMarkFace: false,
eId: string,
epId: string,
ocrWord: string,
modifyWord: string,
exposeNum: number,
clickNum: number,
desc: string
}>
}>;
addFavEmoji(...args: unknown[]): unknown; addFavEmoji(...args: unknown[]): unknown;
@@ -430,7 +456,10 @@ export interface NodeIKernelMsgService {
downloadRichMedia(...args: unknown[]): unknown; downloadRichMedia(...args: unknown[]): unknown;
getFirstUnreadMsgSeq(...args: unknown[]): unknown; getFirstUnreadMsgSeq(args: {
peerUid: string
guildId: string
}): unknown;
getFirstUnreadCommonMsg(...args: unknown[]): unknown; getFirstUnreadCommonMsg(...args: unknown[]): unknown;
@@ -448,7 +477,7 @@ export interface NodeIKernelMsgService {
setMsgEmojiLikes(...args: unknown[]): unknown; setMsgEmojiLikes(...args: unknown[]): unknown;
getMsgEmojiLikesList(...args: unknown[]): unknown; getMsgEmojiLikesList(peer: Peer, msgSeq: string, emojiId: string, emojiType: string, cookie: string, bForward: boolean, number: number): Promise<unknown>;
setMsgEmojiLikesForRole(...args: unknown[]): unknown; setMsgEmojiLikesForRole(...args: unknown[]): unknown;
@@ -470,13 +499,13 @@ export interface NodeIKernelMsgService {
queryCalendar(...args: unknown[]): unknown; queryCalendar(...args: unknown[]): unknown;
queryFirstMsgSeq(...args: unknown[]): unknown; queryFirstMsgSeq(peer: Peer, ...args: unknown[]): unknown;
queryRoamCalendar(...args: unknown[]): unknown; queryRoamCalendar(...args: unknown[]): unknown;
queryFirstRoamMsg(...args: unknown[]): unknown; queryFirstRoamMsg(...args: unknown[]): unknown;
fetchLongMsg(...args: unknown[]): unknown; fetchLongMsg(peer: Peer, msgId: string): unknown;
fetchLongMsgWithCb(...args: unknown[]): unknown; fetchLongMsgWithCb(...args: unknown[]): unknown;
@@ -510,9 +539,9 @@ export interface NodeIKernelMsgService {
deleteReplyDraft(...args: unknown[]): unknown; deleteReplyDraft(...args: unknown[]): unknown;
getFirstUnreadAtMsg(...args: unknown[]): unknown; getFirstUnreadAtMsg(peer: Peer): unknown;
clearMsgRecords(...args: unknown[]): unknown;//设置已读后调用我觉得比较好 清理记录 clearMsgRecords(...args: unknown[]): unknown;//设置已读后调用我觉得比较好 清理记录 现在别了
IsExistOldDb(...args: unknown[]): unknown; IsExistOldDb(...args: unknown[]): unknown;
@@ -532,7 +561,7 @@ export interface NodeIKernelMsgService {
getCurChatImportStatusByUin(...args: unknown[]): unknown; getCurChatImportStatusByUin(...args: unknown[]): unknown;
getDataImportUserLevel(...args: unknown[]): unknown; getDataImportUserLevel(): unknown;
getMsgQRCode(...args: unknown[]): unknown; getMsgQRCode(...args: unknown[]): unknown;
@@ -563,6 +592,8 @@ export interface NodeIKernelMsgService {
// this.selfPhone = str5; // this.selfPhone = str5;
// this.gameSession = tempChatGameSession; // this.gameSession = tempChatGameSession;
prepareTempChat(args: unknown): unknown;//主动临时消息 不做 prepareTempChat(args: unknown): unknown;//主动临时消息 不做
sendSsoCmdReqByContend(cmd: string, param: string): Promise<unknown>;
//chattype,uid->Promise<any> //chattype,uid->Promise<any>
getTempChatInfo(ChatType: number, Uid: string): unknown; getTempChatInfo(ChatType: number, Uid: string): unknown;
@@ -587,7 +618,7 @@ export interface NodeIKernelMsgService {
getABatchOfContactMsgBoxInfo(...args: unknown[]): unknown; getABatchOfContactMsgBoxInfo(...args: unknown[]): unknown;
insertMsgToMsgBox(...args: unknown[]): unknown; insertMsgToMsgBox(peer: Peer, msgId: string, arg: 2006): unknown;
isHitEmojiKeyword(...args: unknown[]): unknown; isHitEmojiKeyword(...args: unknown[]): unknown;
@@ -627,7 +658,25 @@ export interface NodeIKernelMsgService {
dataMigrationStopOperation(...args: unknown[]): unknown; dataMigrationStopOperation(...args: unknown[]): unknown;
dataMigrationImportMsgPbRecord(...args: unknown[]): unknown; //新的希望
dataMigrationImportMsgPbRecord(DataMigrationMsgInfo: Array<{
extensionData: string//"Hex"
extraData: string //""
chatType: number
chatUin: string
msgType: number
msgTime: string
msgSeq: string
msgRandom: string
}>, DataMigrationResourceInfo: {
extraData: string
filePath: string
fileSize: string
msgRandom: string
msgSeq: string
msgSubType: number
msgType: number
}): unknown;
dataMigrationGetResourceLocalDestinyPath(...args: unknown[]): unknown; dataMigrationGetResourceLocalDestinyPath(...args: unknown[]): unknown;

View File

@@ -1,10 +1,36 @@
import { AnyCnameRecord } from 'node:dns'; import { AnyCnameRecord } from 'node:dns';
import { BizKey, ModifyProfileParams, UserDetailInfoByUin } from '../entities'; import { BaseInfo, BizKey, CoreInfo, ModifyProfileParams, SimpleInfo, UserDetailInfoByUin } from '../entities';
import { NodeIKernelProfileListener } from '../listeners'; import { NodeIKernelProfileListener } from '../listeners';
import { GeneralCallResult } from '@/core/services/common'; import { GeneralCallResult } from '@/core/services/common';
export enum UserDetailSource {
KDB,
KSERVER
}
export enum ProfileBizType {
KALL,
KBASEEXTEND,
KVAS,
KQZONE,
KOTHER
}
export interface NodeIKernelProfileService { export interface NodeIKernelProfileService {
getUidByUin(callfrom: string, uin: Array<string>): Promise<Map<string,string>>;//uin->uid
getUinByUid(callfrom: string, uid: Array<string>): Promise<Map<string,string>>;
// {
// coreInfo: CoreInfo,
// baseInfo: BaseInfo,
// status: null,
// vasInfo: null,
// relationFlags: null,
// otherFlags: null,
// intimate: null
// }
getCoreAndBaseInfo(callfrom: string, uids: string[]): Promise<Map<string, SimpleInfo>>;
fetchUserDetailInfo(trace: string, uids: string[], arg2: number, arg3: number[]): Promise<unknown>;
addKernelProfileListener(listener: NodeIKernelProfileListener): number; addKernelProfileListener(listener: NodeIKernelProfileListener): number;
removeKernelProfileListener(listenerId: number): void; removeKernelProfileListener(listenerId: number): void;

View File

@@ -1,5 +1,28 @@
import { Peer } from "../entities"; import { Peer } from "../entities";
import { NodeIKernelRecentContactListener } from "../listeners/NodeIKernelRecentContactListener";
import { GeneralCallResult } from "./common";
export interface FSABRecentContactParams {
anchorPointContact: {
contactId: string;
sortField: string;
pos: number;
},
relativeMoveCount: number;
listType: number;
count: number;
fetchOld: boolean;
}
// {
// "anchorPointContact": {
// "contactId": "",
// "sortField": "",
// "pos": 0
// },
// "relativeMoveCount": 0,
// "listType": 1,
// "count": 200,
// "fetchOld": true
// }
export interface NodeIKernelRecentContactService { export interface NodeIKernelRecentContactService {
setGuildDisplayStatus(...args: unknown[]): unknown; // 2 arguments setGuildDisplayStatus(...args: unknown[]): unknown; // 2 arguments
@@ -11,7 +34,14 @@ export interface NodeIKernelRecentContactService {
enterOrExitMsgList(...args: unknown[]): unknown; // 1 arguments enterOrExitMsgList(...args: unknown[]): unknown; // 1 arguments
getRecentContactListSnapShot(...args: unknown[]): unknown; // 1 arguments /*!---!*/getRecentContactListSnapShot(count: number): Promise<GeneralCallResult & {
info: {
errCode: number,
errMsg: string,
sortedContactList: Array<number>,
changedList: Array<any>
}
}>; // 1 arguments
clearMsgUnreadCount(...args: unknown[]): unknown; // 1 arguments clearMsgUnreadCount(...args: unknown[]): unknown; // 1 arguments
@@ -19,7 +49,7 @@ export interface NodeIKernelRecentContactService {
jumpToSpecifyRecentContact(...args: unknown[]): unknown; // 1 arguments jumpToSpecifyRecentContact(...args: unknown[]): unknown; // 1 arguments
fetchAndSubscribeABatchOfRecentContact(...args: unknown[]): unknown; // 1 arguments /*!---!*/fetchAndSubscribeABatchOfRecentContact(params: FSABRecentContactParams): unknown; // 1 arguments
addRecentContact(peer: Peer): unknown; addRecentContact(peer: Peer): unknown;
@@ -31,9 +61,9 @@ export interface NodeIKernelRecentContactService {
updateGameMsgConfigs(...args: unknown[]): unknown; // 1 arguments updateGameMsgConfigs(...args: unknown[]): unknown; // 1 arguments
removeKernelRecentContactListener(...args: unknown[]): unknown; // 1 arguments removeKernelRecentContactListener(listenerid: number): unknown; // 1 arguments
addKernelRecentContactListener(...args: unknown[]): unknown; // 1 arguments addKernelRecentContactListener(listener: NodeIKernelRecentContactListener): void;
clearRecentContactsByChatType(...args: unknown[]): unknown; // 1 arguments clearRecentContactsByChatType(...args: unknown[]): unknown; // 1 arguments

View File

@@ -1,6 +1,50 @@
import { GetFileListParam, Peer } from "../entities"; import { GetFileListParam, MessageElement, Peer, SendMessageElement } from "../entities";
import { GeneralCallResult } from "./common"; import { GeneralCallResult } from "./common";
export enum UrlFileDownloadType {
KUNKNOWN,
KURLFILEDOWNLOADPRIVILEGEICON,
KURLFILEDOWNLOADPHOTOWALL,
KURLFILEDOWNLOADQZONE,
KURLFILEDOWNLOADCOMMON,
KURLFILEDOWNLOADINSTALLAPP
}
export enum RMBizTypeEnum {
KUNKNOWN,
KC2CFILE,
KGROUPFILE,
KC2CPIC,
KGROUPPIC,
KDISCPIC,
KC2CVIDEO,
KGROUPVIDEO,
KC2CPTT,
KGROUPPTT,
KFEEDCOMMENTPIC,
KGUILDFILE,
KGUILDPIC,
KGUILDPTT,
KGUILDVIDEO
}
export interface CommonFileInfo {
bizType: number;
chatType: number;
elemId: string;
favId: string;
fileModelId: string;
fileName: string;
fileSize: string;
md5: string;
md510m: string;
msgId: string;
msgTime: string;
parent: string;
peerUid: string;
picThumbPath: Array<string>
sha: string;
sha3: string;
subId: string;
uuid: string;
}
export interface NodeIKernelRichMediaService { export interface NodeIKernelRichMediaService {
//getVideoPlayUrl(peer, msgId, elemId, videoCodecFormat, VideoRequestWay.KHAND, cb); //getVideoPlayUrl(peer, msgId, elemId, videoCodecFormat, VideoRequestWay.KHAND, cb);
// public enum VideoCodecFormatType { // public enum VideoCodecFormatType {
@@ -46,7 +90,7 @@ export interface NodeIKernelRichMediaService {
} }
}>; }>;
getRichMediaFileDir(arg1: unknown, arg2: unknown, arg3: unknown): unknown; getRichMediaFileDir(elementType: number, downType: number, isTemp: boolean): unknown;
// this.senderUid = ""; // this.senderUid = "";
// this.peerUid = ""; // this.peerUid = "";
@@ -65,16 +109,43 @@ export interface NodeIKernelRichMediaService {
// this.elem = msgElement; // this.elem = msgElement;
// this.useHttps = num; // this.useHttps = num;
getVideoPlayUrlInVisit(arg: unknown): unknown; getVideoPlayUrlInVisit(arg: {
downloadType: number,
thumbSize: number,
msgId: string,
msgRandom: string,
msgSeq: string,
msgTime: string,
chatType: number,
senderUid: string,
peerUid: string,
guildId: string,
ele: MessageElement,
useHttps: boolean
}): Promise<unknown>;
isFileExpired(arg: unknown): unknown; //arg双端number
isFileExpired(arg: number): unknown;
deleteGroupFolder(GroupCode: string, FolderId: string): Promise<GeneralCallResult & { groupFileCommonResult: { retCode: number, retMsg: string, clientWording: string } }>; deleteGroupFolder(GroupCode: string, FolderId: string): Promise<GeneralCallResult & { groupFileCommonResult: { retCode: number, retMsg: string, clientWording: string } }>;
//参数与getVideoPlayUrlInVisit一样 //参数与getVideoPlayUrlInVisit一样
downloadRichMediaInVisit(arg: unknown): unknown; downloadRichMediaInVisit(arg: {
downloadType: number,
downloadFileForModelId(peer: Peer, arg: unknown[], arg3: string): unknown; thumbSize: number,
msgId: string,
msgRandom: string,
msgSeq: string,
msgTime: string,
chatType: number,
senderUid: string,
peerUid: string,
guildId: string,
ele: MessageElement,
useHttps: boolean
}): unknown;
//arg3为“”
downloadFileForModelId(peer: Peer, ModelId: string[], arg3: string): unknown;
//第三个参数 Array<Type> //第三个参数 Array<Type>
// this.fileId = ""; // this.fileId = "";
// this.fileName = ""; // this.fileName = "";
@@ -83,25 +154,30 @@ export interface NodeIKernelRichMediaService {
// this.fileSize = j2; // this.fileSize = j2;
// this.fileModelId = j3; // this.fileModelId = j3;
downloadFileForFileUuid(peer: Peer, arg1: string, arg3: unknown[]): unknown; downloadFileForFileUuid(peer: Peer, uuid: string, arg3: {
fileId: string,
fileName: string,
fileSize: string,
fileModelId: string
}[]): Promise<unknown>;
downloadFileByUrlListtransgroupfile(arg1: unknown, arg2: unknown): unknown; downloadFileByUrlList(fileDownloadTyp: UrlFileDownloadType, urlList: Array<string>): unknown;
downloadFileForFileInfotransgroupfile(arg1: unknown, arg2: unknown): unknown; downloadFileForFileInfo(fileInfo: CommonFileInfo[], savePath: string): unknown;
createGroupFolder(GroupCode: string, FolderName: string): Promise<GeneralCallResult & { resultWithGroupItem: { result: any, groupItem: Array<any> } }> createGroupFolder(GroupCode: string, FolderName: string): Promise<GeneralCallResult & { resultWithGroupItem: { result: any, groupItem: Array<any> } }>
downloadFile(arg1: unknown, arg2: unknown, arg3: unknown, arg4: unknown): unknown; downloadFile(commonFile: CommonFileInfo, arg2: unknown, arg3: unknown, savePath: string): unknown;
createGroupFoldertransgroupfile(arg1: unknown, arg2: unknown): unknown; createGroupFolder(arg1: unknown, arg2: unknown): unknown;
downloadGroupFolder(arg1: unknown, arg2: unknown, arg3: unknown): unknown; downloadGroupFolder(arg1: unknown, arg2: unknown, arg3: unknown): unknown;
renameGroupFolder(arg1: unknown, arg2: unknown, arg3: unknown): unknown; renameGroupFolder(arg1: unknown, arg2: unknown, arg3: unknown): unknown;
deleteGroupFoldertransgroupfile(arg1: unknown, arg2: unknown): unknown; deleteGroupFolder(arg1: unknown, arg2: unknown): unknown;
deleteTransferInfotransgroupfile(arg1: unknown, arg2: unknown): unknown; deleteTransferInfo(arg1: unknown, arg2: unknown): unknown;
cancelTransferTask(arg1: unknown, arg2: unknown, arg3: unknown): unknown; cancelTransferTask(arg1: unknown, arg2: unknown, arg3: unknown): unknown;
@@ -122,11 +198,9 @@ export interface NodeIKernelRichMediaService {
} }
}>; }>;
getGroupFileInfotransgroupfile(arg1: unknown, arg2: unknown): unknown; getGroupFileInfo(arg1: unknown, arg2: unknown): unknown;
getGroupFileListtransgroupfile(arg1: unknown, arg2: unknown): unknown; getGroupTransferList(arg1: unknown, arg2: unknown): unknown;
getGroupTransferListtransgroupfile(arg1: unknown, arg2: unknown): unknown;
renameGroupFile(arg1: unknown, arg2: unknown, arg3: unknown, arg4: unknown, arg5: unknown): unknown; renameGroupFile(arg1: unknown, arg2: unknown, arg3: unknown, arg4: unknown, arg5: unknown): unknown;
@@ -134,6 +208,16 @@ export interface NodeIKernelRichMediaService {
transGroupFile(arg1: unknown, arg2: unknown): unknown; transGroupFile(arg1: unknown, arg2: unknown): unknown;
searchGroupFile(
keywords: Array<string>,
param: {
groupIds: Array<string>,
fileType: number,
context: string,
count: number,
sortType: number,
groupNames: Array<string>
}): Promise<unknown>;
searchGroupFileByWord(arg1: unknown, arg2: unknown, arg3: unknown, arg4: unknown, arg5: unknown): unknown; searchGroupFileByWord(arg1: unknown, arg2: unknown, arg3: unknown, arg4: unknown, arg5: unknown): unknown;
deleteGroupFile(GroupCode: string, params: Array<number>, Files: Array<string>): Promise<GeneralCallResult & { deleteGroupFile(GroupCode: string, params: Array<number>, Files: Array<string>): Promise<GeneralCallResult & {
@@ -152,19 +236,31 @@ export interface NodeIKernelRichMediaService {
queryPicDownloadSize(arg: unknown): unknown; queryPicDownloadSize(arg: unknown): unknown;
searchGroupFiletransgroupfile(arg1: unknown, arg2: unknown): unknown; searchGroupFile(arg1: unknown, arg2: unknown): unknown;
searchMoreGroupFile(arg: unknown): unknown; searchMoreGroupFile(arg: unknown): unknown;
cancelSearcheGroupFile(arg1: unknown, arg2: unknown, arg3: unknown): unknown; cancelSearcheGroupFile(arg1: unknown, arg2: unknown, arg3: unknown): unknown;
onlyDownloadFile(arg1: unknown, arg2: unknown, arg3: unknown): unknown; onlyDownloadFile(peer: Peer, arg2: unknown, arg3: Array<{
fileId: string,
fileName: string,
fileSize: string,
fileModelId: string
}
>): unknown;
onlyUploadFiletransgroupfile(arg1: unknown, arg2: unknown): unknown; onlyUploadFile(arg1: unknown, arg2: unknown): unknown;
isExtraLargePic(arg1: unknown, arg2: unknown, arg3: unknown): unknown; isExtraLargePic(arg1: unknown, arg2: unknown, arg3: unknown): unknown;
uploadRMFileWithoutMsg(arg: unknown): unknown; uploadRMFileWithoutMsg(arg: {
bizType: RMBizTypeEnum,
filePath: string,
peerUid: string,
transferId: string
useNTV2: string
}): Promise<unknown>;
isNull(): boolean; isNull(): boolean;
} }

View File

@@ -21,7 +21,7 @@ export interface NodeIKernelRobotService {
removeKernelRobotListener(ListenerId: number): unknown; removeKernelRobotListener(ListenerId: number): unknown;
getAllRobotFriendsFromCache(): unknown; getAllRobotFriendsFromCache(): Promise<unknown>;
fetchAllRobots(arg1: unknown, arg2: unknown): unknown; fetchAllRobots(arg1: unknown, arg2: unknown): unknown;

View File

@@ -1,79 +1,129 @@
export interface NodeIKernelSearchService{ import { ChatType } from "../entities";
addKernelSearchListenerr(...args: any[]): unknown;// needs 1 arguments
removeKernelSearchListenerr(...args: any[]): unknown;// needs 1 arguments export interface NodeIKernelSearchService {
addKernelSearchListener(...args: any[]): unknown;// needs 1 arguments
searchStrangerr(...args: any[]): unknown;// needs 3 arguments removeKernelSearchListener(...args: any[]): unknown;// needs 1 arguments
searchGroupr(...args: any[]): unknown;// needs 1 arguments searchStranger(...args: any[]): unknown;// needs 3 arguments
searchLocalInfor(...args: any[]): unknown;// needs 2 arguments searchGroup(...args: any[]): unknown;// needs 1 arguments
cancelSearchLocalInfor(...args: any[]): unknown;// needs 3 arguments searchLocalInfo(keywords: string, unknown: number/*4*/): unknown;
searchBuddyChatInfor(...args: any[]): unknown;// needs 2 arguments cancelSearchLocalInfo(...args: any[]): unknown;// needs 3 arguments
searchMoreBuddyChatInfor(...args: any[]): unknown;// needs 1 arguments searchBuddyChatInfo(...args: any[]): unknown;// needs 2 arguments
cancelSearchBuddyChatInfor(...args: any[]): unknown;// needs 3 arguments searchMoreBuddyChatInfo(...args: any[]): unknown;// needs 1 arguments
searchContactr(...args: any[]): unknown;// needs 2 arguments cancelSearchBuddyChatInfo(...args: any[]): unknown;// needs 3 arguments
searchMoreContactr(...args: any[]): unknown;// needs 1 arguments searchContact(...args: any[]): unknown;// needs 2 arguments
cancelSearchContactr(...args: any[]): unknown;// needs 3 arguments searchMoreContact(...args: any[]): unknown;// needs 1 arguments
searchGroupChatInfor(...args: any[]): unknown;// needs 3 arguments cancelSearchContact(...args: any[]): unknown;// needs 3 arguments
resetSearchGroupChatInfoSortTyper(...args: any[]): unknown;// needs 3 arguments searchGroupChatInfo(...args: any[]): unknown;// needs 3 arguments
resetSearchGroupChatInfoFilterMembersr(...args: any[]): unknown;// needs 3 arguments resetSearchGroupChatInfoSortType(...args: any[]): unknown;// needs 3 arguments
searchMoreGroupChatInfor(...args: any[]): unknown;// needs 1 arguments resetSearchGroupChatInfoFilterMembers(...args: any[]): unknown;// needs 3 arguments
cancelSearchGroupChatInfor(...args: any[]): unknown;// needs 3 arguments searchMoreGroupChatInfo(...args: any[]): unknown;// needs 1 arguments
searchChatsWithKeywordsr(...args: any[]): unknown;// needs 3 arguments cancelSearchGroupChatInfo(...args: any[]): unknown;// needs 3 arguments
searchMoreChatsWithKeywordsr(...args: any[]): unknown;// needs 1 arguments searchChatsWithKeywords(...args: any[]): unknown;// needs 3 arguments
cancelSearchChatsWithKeywordsr(...args: any[]): unknown;// needs 3 arguments searchMoreChatsWithKeywords(...args: any[]): unknown;// needs 1 arguments
searchChatMsgsr(...args: any[]): unknown;// needs 2 arguments cancelSearchChatsWithKeywords(...args: any[]): unknown;// needs 3 arguments
searchMoreChatMsgsr(...args: any[]): unknown;// needs 1 arguments searchChatMsgs(...args: any[]): unknown;// needs 2 arguments
cancelSearchChatMsgsr(...args: any[]): unknown;// needs 3 arguments searchMoreChatMsgs(...args: any[]): unknown;// needs 1 arguments
searchMsgWithKeywordsr(...args: any[]): unknown;// needs 2 arguments cancelSearchChatMsgs(...args: any[]): unknown;// needs 3 arguments
searchMoreMsgWithKeywordsr(...args: any[]): unknown;// needs 1 arguments searchMsgWithKeywords(...args: any[]): unknown;// needs 2 arguments
cancelSearchMsgWithKeywordsr(...args: any[]): unknown;// needs 3 arguments
searchFileWithKeywordsr(...args: any[]): unknown;// needs 2 arguments searchMoreMsgWithKeywords(...args: any[]): unknown;// needs 1 arguments
searchMoreFileWithKeywordsr(...args: any[]): unknown;// needs 1 arguments cancelSearchMsgWithKeywords(...args: any[]): unknown;// needs 3 arguments
cancelSearchFileWithKeywordsr(...args: any[]): unknown;// needs 3 arguments searchFileWithKeywords(keywords: string[], source: number): Promise<string>;// needs 2 arguments
searchAtMeChatsr(...args: any[]): unknown;// needs 3 arguments searchMoreFileWithKeywords(...args: any[]): unknown;// needs 1 arguments
searchMoreAtMeChatsr(...args: any[]): unknown;// needs 1 arguments cancelSearchFileWithKeywords(...args: any[]): unknown;// needs 3 arguments
cancelSearchAtMeChatsr(...args: any[]): unknown;// needs 3 arguments searchAtMeChats(...args: any[]): unknown;// needs 3 arguments
searchChatAtMeMsgsr(...args: any[]): unknown;// needs 1 arguments searchMoreAtMeChats(...args: any[]): unknown;// needs 1 arguments
searchMoreChatAtMeMsgsr(...args: any[]): unknown;// needs 1 arguments cancelSearchAtMeChats(...args: any[]): unknown;// needs 3 arguments
cancelSearchChatAtMeMsgsr(...args: any[]): unknown;// needs 3 arguments searchChatAtMeMsgs(...args: any[]): unknown;// needs 1 arguments
addSearchHistoryr(...args: any[]): unknown;// needs 1 arguments searchMoreChatAtMeMsgs(...args: any[]): unknown;// needs 1 arguments
removeSearchHistoryr(...args: any[]): unknown;// needs 1 arguments cancelSearchChatAtMeMsgs(...args: any[]): unknown;// needs 3 arguments
searchCacher(...args: any[]): unknown;// needs 3 arguments addSearchHistory(param: {
type: number,//4
contactList: [],
id: number,//-1
groupInfos: [],
msgs: [],
fileInfos: [
{
chatType: ChatType,
buddyChatInfo: Array<{ category_name: string, peerUid: string, peerUin: string, remark: string }>,
discussChatInfo: [],
groupChatInfo: Array<
{
groupCode: string,
isConf: boolean,
hasModifyConfGroupFace: boolean,
hasModifyConfGroupName: boolean,
groupName: string,
remark: string
}>
,
dataLineChatInfo: [],
tmpChatInfo: [],
msgId: string,
msgSeq: string,
msgTime: string,
senderUid: string,
senderNick: string,
senderRemark: string,
senderCard: string,
elemId: string,
elemType: string,//3
fileSize: string,
filePath: string,
fileName: string,
hits: Array<
{
start: 12,
end: 14
}
>
}
]
clearSearchCacher(...args: any[]): unknown;// needs 1 arguments }): Promise<{
result: number,
errMsg: string,
id?: number
}>;
removeSearchHistory(...args: any[]): unknown;// needs 1 arguments
searchCache(...args: any[]): unknown;// needs 3 arguments
clearSearchCache(...args: any[]): unknown;// needs 1 arguments
} }

View File

@@ -1,5 +1,5 @@
export interface NodeIKernelUixConvertService { export interface NodeIKernelUixConvertService {
getUin(uid: string[]): Promise<{ uidInfo: Map<string, string> }>; getUin(uid: string[]): Promise<{ uinInfo: Map<string, string> }>;
getUid(uin: string[]): Promise<{ uinInfo: Map<string, string> }>; getUid(uin: string[]): Promise<{ uidInfo: Map<string, string> }>;
} }

View File

@@ -0,0 +1,14 @@
import { MessageElement, Peer } from "../entities";
export interface NodeIkernelTestPerformanceService {
insertMsg(MsgParam: {
peer: Peer
msgTime: string
msgId: string
msgSeq: string
batchNums: number
timesPerBatch: number
numPerTime: number
}, msg: Array<MessageElement>): Promise<unknown>;
}

View File

@@ -1,4 +1,4 @@
import { appid, qqPkgInfo, qqVersionConfigInfo } from '@/common/utils/QQBasicInfo'; import { getFullQQVesion, QQVersionAppid } from '@/common/utils/QQBasicInfo';
import { hostname, systemName, systemVersion } from '@/common/utils/system'; import { hostname, systemName, systemVersion } from '@/common/utils/system';
import path from 'node:path'; import path from 'node:path';
import fs from 'node:fs'; import fs from 'node:fs';
@@ -34,42 +34,42 @@ export interface WrapperSessionInitConfig {
account_path: string // 可以通过NodeQQNTWrapperUtil().getNTUserDataInfoConfig()获取 account_path: string // 可以通过NodeQQNTWrapperUtil().getNTUserDataInfoConfig()获取
} }
clientVer: string // 9.9.8-22355 clientVer: string // 9.9.8-22355
a2: '', a2: string,
d2: '', d2: string,
d2Key: '', d2Key: string,
machineId: '', machineId: string,
platform: 3, // 3是Windows? platform: PlatformType, // 3是Windows?
platVer: string, // 系统版本号, 应该可以固定 platVer: string, // 系统版本号, 应该可以固定
appid: string, appid: string,
rdeliveryConfig: { rdeliveryConfig: {
appKey: '', appKey: string,
systemId: 0, systemId: number,
appId: '', appId: string,
logicEnvironment: '', logicEnvironment: string,
platform: 3, platform: PlatformType,
language: '', language: string,
sdkVersion: '', sdkVersion: string,
userId: '', userId: string,
appVersion: '', appVersion: string,
osVersion: '', osVersion: string,
bundleId: '', bundleId: string,
serverUrl: '', serverUrl: string,
fixedAfterHitKeys: [''] fixedAfterHitKeys: string[]
} }
'defaultFileDownloadPath': string, // 这个可以通过环境变量获取? defaultFileDownloadPath: string, // 这个可以通过环境变量获取?
'deviceInfo': { deviceInfo: {
'guid': string, guid: string,
'buildVer': string, buildVer: string,
'localId': 2052, localId: number,
'devName': string, devName: string,
'devType': string, devType: string,
'vendorName': '', vendorName: string,
'osVer': string, osVer: string,
'vendorOsName': string, vendorOsName: string,
'setMute': false, setMute: boolean,
'vendorType': 0 vendorType: VendorType
}, },
'deviceConfig': '{"appearance":{"isSplitViewMode":true},"msg":{}}' deviceConfig: '{"appearance":{"isSplitViewMode":true},"msg":{}}'
} }
export const sessionConfig: WrapperSessionInitConfig | any = {}; export const sessionConfig: WrapperSessionInitConfig | any = {};
@@ -79,7 +79,7 @@ export async function genSessionConfig(selfUin: string, selfUid: string, account
fs.mkdirSync(downloadPath, { recursive: true }); fs.mkdirSync(downloadPath, { recursive: true });
let guid: string = await getMachineId(); let guid: string = await getMachineId();
//console.log(guid); //console.log(guid);
// guid = '52afb776-82f6-4e59-9d38-44705b112d0a'; // guid = '52afb776-82f6-4e59-9d38-44705b112d0a';
//let guid: string = await getMachineId(); //let guid: string = await getMachineId();
const config: WrapperSessionInitConfig = { const config: WrapperSessionInitConfig = {
selfUin, selfUin,
@@ -87,20 +87,20 @@ export async function genSessionConfig(selfUin: string, selfUid: string, account
desktopPathConfig: { desktopPathConfig: {
account_path // 可以通过NodeQQNTWrapperUtil().getNTUserDataInfoConfig()获取 account_path // 可以通过NodeQQNTWrapperUtil().getNTUserDataInfoConfig()获取
}, },
clientVer: qqVersionConfigInfo.curVersion, // 9.9.8-22355 clientVer: getFullQQVesion(), // 9.9.8-22355
a2: '', a2: '',
d2: '', d2: '',
d2Key: '', d2Key: '',
machineId: '', machineId: '',
platform: 3, // 3是Windows? platform: PlatformType.KWINDOWS, // 3是Windows?
platVer: systemVersion, // 系统版本号, 应该可以固定 platVer: systemVersion, // 系统版本号, 应该可以固定
appid: appid, appid: QQVersionAppid,
rdeliveryConfig: { rdeliveryConfig: {
appKey: '', appKey: '',
systemId: 0, systemId: 0,
appId: '', appId: '',
logicEnvironment: '', logicEnvironment: '',
platform: 3, platform: PlatformType.KWINDOWS,
language: '', language: '',
sdkVersion: '', sdkVersion: '',
userId: '', userId: '',
@@ -110,22 +110,21 @@ export async function genSessionConfig(selfUin: string, selfUid: string, account
serverUrl: '', serverUrl: '',
fixedAfterHitKeys: [''] fixedAfterHitKeys: ['']
}, },
'defaultFileDownloadPath': downloadPath, defaultFileDownloadPath: downloadPath,
'deviceInfo': { deviceInfo: {
guid, guid,
'buildVer': qqPkgInfo.version, buildVer: getFullQQVesion(),
'localId': 2052, localId: 2052,
'devName': hostname, devName: hostname,
'devType': systemName, devType: systemName,
'vendorName': '', vendorName: '',
'osVer': systemVersion, osVer: systemVersion,
'vendorOsName': systemName, vendorOsName: systemName,
'setMute': false, setMute: false,
'vendorType': 0 vendorType: VendorType.KNOSETONIOS
}, },
'deviceConfig': '{"appearance":{"isSplitViewMode":true},"msg":{}}' deviceConfig: '{"appearance":{"isSplitViewMode":true},"msg":{}}'
}; };
Object.assign(sessionConfig, config); Object.assign(sessionConfig, config);
// log(sessionConfig);
return config; return config;
} }

View File

@@ -12,18 +12,16 @@ export interface NapCatConfig {
consoleLogLevel: LogLevel, consoleLogLevel: LogLevel,
} }
class Config extends ConfigBase<NapCatConfig> implements NapCatConfig{ class Config extends ConfigBase<NapCatConfig> implements NapCatConfig {
name: string = 'napcat'
fileLog = true; fileLog = true;
consoleLog = true; consoleLog = true;
fileLogLevel = LogLevel.DEBUG; fileLogLevel = LogLevel.DEBUG;
consoleLogLevel = LogLevel.INFO; consoleLogLevel = LogLevel.INFO;
constructor() { constructor() {
super(); super();
} }
getConfigPath() {
return path.join(this.getConfigDir(), `napcat_${selfInfo.uin}.json`);
}
} }
export const napCatConfig = new Config(); export const napCatConfig = new Config();

View File

@@ -26,7 +26,6 @@ import {
NodeIKernelRichMediaService, NodeIKernelRichMediaService,
NodeIKernelAvatarService, NodeIKernelAvatarService,
} from './services'; } from './services';
import { qqVersionConfigInfo } from '@/common/utils/QQBasicInfo';
import { NodeIKernelStorageCleanService } from './services/NodeIKernelStorageCleanService'; import { NodeIKernelStorageCleanService } from './services/NodeIKernelStorageCleanService';
import { NodeIKernelRobotService } from './services/NodeIKernelRobotService'; import { NodeIKernelRobotService } from './services/NodeIKernelRobotService';
import { dirname } from "node:path" import { dirname } from "node:path"
@@ -40,6 +39,10 @@ import { NodeIKernelUnitedConfigService } from './services/NodeIKernelUnitedConf
import { NodeIKernelSearchService } from './services/NodeIKernelSearchService'; import { NodeIKernelSearchService } from './services/NodeIKernelSearchService';
import { NodeIKernelCollectionService } from './services/NodeIKernelCollectionService'; import { NodeIKernelCollectionService } from './services/NodeIKernelCollectionService';
import { NodeIKernelRecentContactService } from './services/NodeIKernelRecentContactService'; import { NodeIKernelRecentContactService } from './services/NodeIKernelRecentContactService';
import { NodeIKernelMSFService } from './services/NodeIKernelMSFService';
import { NodeIkernelTestPerformanceService } from './services/NodeIkernelTestPerformanceService';
import { NodeIKernelECDHService } from './services/NodeIKernelECDHService';
import { getFullQQVesion } from '@/common/utils/QQBasicInfo';
const __filename = fileURLToPath(import.meta.url); const __filename = fileURLToPath(import.meta.url);
@@ -155,6 +158,10 @@ export interface NodeIQQNTWrapperSession {
startNT(): void; startNT(): void;
getBdhUploadService(): unknown;
getECDHService(): NodeIKernelECDHService;
getMsgService(): NodeIKernelMsgService; getMsgService(): NodeIKernelMsgService;
getProfileService(): NodeIKernelProfileService; getProfileService(): NodeIKernelProfileService;
@@ -209,7 +216,7 @@ export interface NodeIQQNTWrapperSession {
getSkinService(): unknown; getSkinService(): unknown;
getTestPerformanceService(): unknown; getTestPerformanceService(): NodeIkernelTestPerformanceService;
getQQPlayService(): unknown; getQQPlayService(): unknown;
@@ -231,7 +238,7 @@ export interface NodeIQQNTWrapperSession {
getLockService(): unknown; getLockService(): unknown;
getMSFService(): unknown getMSFService(): NodeIKernelMSFService;
getGuildHotUpdateService(): unknown; getGuildHotUpdateService(): unknown;
@@ -285,7 +292,7 @@ export interface WrapperNodeApi {
let wrapperNodePath = path.resolve(path.dirname(process.execPath), './resources/app/wrapper.node'); let wrapperNodePath = path.resolve(path.dirname(process.execPath), './resources/app/wrapper.node');
if (!fs.existsSync(wrapperNodePath)) { if (!fs.existsSync(wrapperNodePath)) {
wrapperNodePath = path.join(path.dirname(process.execPath), `resources/app/versions/${qqVersionConfigInfo.curVersion}/wrapper.node`); wrapperNodePath = path.join(path.dirname(process.execPath), `resources/app/versions/${getFullQQVesion()}/wrapper.node`);
} }
const nativemodule: any = { exports: {} }; const nativemodule: any = { exports: {} };
process.dlopen(nativemodule, wrapperNodePath); process.dlopen(nativemodule, wrapperNodePath);

View File

@@ -13,13 +13,15 @@ import { deleteOldFiles, UpdateConfig } from './common/utils/helper';
import { dirname } from 'node:path'; import { dirname } from 'node:path';
import { fileURLToPath } from 'node:url'; import { fileURLToPath } from 'node:url';
import chalk from 'chalk'; import chalk from 'chalk';
import { randomInt } from 'crypto';
import { MessageUnique } from './common/utils/MessageUnique';
const __filename = fileURLToPath(import.meta.url); const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename); const __dirname = dirname(__filename);
const tagColor = chalk.cyan; const tagColor = chalk.cyan;
program program
.option('-q, --qq <type>', 'QQ号') .option('-q, --qq [type]', 'QQ号')
.parse(process.argv); .parse(process.argv);
//deleteOldFiles(path.join(__dirname, 'logs'), 3).then().catch(); //deleteOldFiles(path.join(__dirname, 'logs'), 3).then().catch();
@@ -67,7 +69,18 @@ const showQRCode = async (url: string, base64: string, buffer: Buffer) => {
}); });
}); });
}; };
const quickLoginQQ = cmdOptions.qq;
let quickLoginQQ = cmdOptions.qq; // undefine、true、string
const QuickLoginList = await napCatCore.getQuickLoginList();
if (quickLoginQQ == true) {
if (QuickLoginList.LocalLoginInfoList.length > 0) {
quickLoginQQ = QuickLoginList.LocalLoginInfoList[0].uin;
log('-q 指令指定使用最近的QQ进行快速登录');
} else {
quickLoginQQ = '';
}
}
// napCatCore.on('system.login.error', (result) => { // napCatCore.on('system.login.error', (result) => {
// console.error('登录失败', result); // console.error('登录失败', result);
// napCatCore.qrLogin().then().catch(console.error); // napCatCore.qrLogin().then().catch(console.error);
@@ -79,9 +92,9 @@ napCatCore.getQuickLoginList().then((res) => {
WebUiDataRuntime.setQQQuickLoginCall(async (uin: string) => { WebUiDataRuntime.setQQQuickLoginCall(async (uin: string) => {
const QuickLogin: Promise<{ result: boolean, message: string }> = new Promise((resolve, reject) => { const QuickLogin: Promise<{ result: boolean, message: string }> = new Promise((resolve, reject) => {
if (quickLoginQQ) { if (uin) {
log('正在快速登录 ', quickLoginQQ); log('正在快速登录 ', uin);
napCatCore.quickLogin(quickLoginQQ).then(res => { napCatCore.quickLogin(uin).then(res => {
if (res.loginErrorInfo.errMsg) { if (res.loginErrorInfo.errMsg) {
resolve({ result: false, message: res.loginErrorInfo.errMsg }); resolve({ result: false, message: res.loginErrorInfo.errMsg });
} }
@@ -105,11 +118,14 @@ if (quickLoginQQ) {
logError('快速登录错误:', res.loginErrorInfo.errMsg); logError('快速登录错误:', res.loginErrorInfo.errMsg);
} }
}).catch((e) => { }).catch((e) => {
logError(e); logError('快速登录错误:', e);
napCatCore.qrLogin(showQRCode); napCatCore.qrLogin(showQRCode);
}); });
} else { } else {
log('没有 -q 参数指定快速登录的QQ,将使用二维码登录方式'); log('没有 -q 指令指定快速登录,将使用二维码登录方式');
if (QuickLoginList.LocalLoginInfoList.length > 0) {
log(`可用于快速登录的QQ${QuickLoginList.LocalLoginInfoList.map((u, index) => `\n${index}: ${u.uin} ${u.nickName}`)}`);
}
napCatCore.qrLogin(showQRCode); napCatCore.qrLogin(showQRCode);
} }

View File

@@ -15,6 +15,6 @@ export class SetConfigAction extends BaseAction<OB11Config, void> {
actionName = ActionName.SetConfig; actionName = ActionName.SetConfig;
protected async _handle(payload: OB11Config): Promise<void> { protected async _handle(payload: OB11Config): Promise<void> {
ob11Config.save(payload); ob11Config.save(payload, true);
} }
} }

View File

@@ -0,0 +1,21 @@
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import BaseAction from '../BaseAction';
import { ActionName } from '../types';
import { NTQQMsgApi } from '@/core/apis';
const SchemaData = {
type: 'object',
properties: {
count: { type: 'number' },
}
} as const satisfies JSONSchema;
type Payload = FromSchema<typeof SchemaData>;
export class FetchCustomFace extends BaseAction<Payload, string[]> {
actionName = ActionName.FetchCustomFace;
PayloadSchema = SchemaData;
protected async _handle(payload: Payload) {
const ret = await NTQQMsgApi.fetchFavEmojiList(payload.count || 48);
return ret.emojiInfoList.map(e => e.url);
}
}

View File

@@ -1,12 +1,19 @@
import { rawFriends } from '@/core/data'; import { requireMinNTQQBuild } from '@/common/utils/QQBasicInfo';
import BaseAction from '../BaseAction'; import BaseAction from '../BaseAction';
import { ActionName } from '../types'; import { ActionName } from '../types';
import { BuddyCategoryType } from '@/core/entities/'; import { BuddyCategoryType } from '@/core/entities/';
import { NTQQFriendApi } from '@/core';
import { OB11Constructor } from '@/onebot11/constructor';
export class GetFriendWithCategory extends BaseAction<void, Array<BuddyCategoryType>> { export class GetFriendWithCategory extends BaseAction<void, any> {
actionName = ActionName.GetFriendsWithCategory; actionName = ActionName.GetFriendsWithCategory;
protected async _handle(payload: void) { protected async _handle(payload: void) {
return rawFriends; if (requireMinNTQQBuild('26702')) {
//全新逻辑
return OB11Constructor.friendsV2(await NTQQFriendApi.getBuddyV2ExWithCate(true));
} else {
throw new Error('this ntqq version not support, must be 26702 or later');
}
} }
} }

View File

@@ -4,13 +4,13 @@ import { ActionName } from '../types';
import { NTQQUserApi } from '@/core/apis'; import { NTQQUserApi } from '@/core/apis';
export class GetProfileLike extends BaseAction<void, any> { export class GetProfileLike extends BaseAction<void, any> {
actionName = ActionName.GetProfileLike; actionName = ActionName.GetProfileLike;
protected async _handle(payload: void) { protected async _handle(payload: void) {
let ret = await NTQQUserApi.getProfileLike(selfInfo.uid); const ret = await NTQQUserApi.getProfileLike(selfInfo.uid);
let listdata: any[] = ret.info.userLikeInfos[0].favoriteInfo.userInfos; const listdata: any[] = ret.info.userLikeInfos[0].favoriteInfo.userInfos;
for (let i = 0; i < listdata.length; i++) { for (let i = 0; i < listdata.length; i++) {
listdata[i].uin = parseInt((await NTQQUserApi.getUinByUid(listdata[i].uid)) || ''); listdata[i].uin = parseInt((await NTQQUserApi.getUinByUid(listdata[i].uid)) || '');
}
return listdata;
} }
return listdata;
}
} }

View File

@@ -0,0 +1,57 @@
import BaseAction from '../BaseAction';
import { ActionName, BaseCheckResult } from '../types';
import * as fs from 'node:fs';
import { NTQQUserApi } from '@/core/apis/user';
import { checkFileReceived, uri2local } from '@/common/utils/file';
import { NTQQGroupApi } from '@/core';
// import { log } from "../../../common/utils";
interface Payload {
file: string,
groupCode: string
}
export default class SetGroupHeader extends BaseAction<Payload, any> {
actionName = ActionName.SetGroupHeader;
// 用不着复杂检测
protected async check(payload: Payload): Promise<BaseCheckResult> {
if (!payload.file || typeof payload.file != 'string' || !payload.groupCode || typeof payload.groupCode != 'string') {
return {
valid: false,
message: 'file和groupCode字段不能为空或者类型错误',
};
}
return {
valid: true,
};
}
protected async _handle(payload: Payload): Promise<any> {
const { path, isLocal, errMsg } = (await uri2local(payload.file));
if (errMsg) {
throw `头像${payload.file}设置失败,file字段可能格式不正确`;
}
if (path) {
await checkFileReceived(path, 5000); // 文件不存在QQ会崩溃需要提前判断
const ret = await NTQQGroupApi.setGroupAvatar(payload.groupCode,path);
if (!isLocal) {
fs.unlink(path, () => { });
}
if (!ret) {
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']}`;
// }
return ret;
} else {
if (!isLocal) {
fs.unlink(path, () => { });
}
throw `头像${payload.file}设置失败,无法获取头像,文件可能不存在`;
}
return null;
}
}

View File

@@ -0,0 +1,32 @@
import BaseAction from '../BaseAction';
import { ActionName, BaseCheckResult } from '../types';
import * as fs from 'node:fs';
import { NTQQUserApi } from '@/core/apis/user';
import { checkFileReceived, uri2local } from '@/common/utils/file';
import { napCatCore, NTQQGroupApi } from '@/core';
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
// import { log } from "../../../common/utils";
const SchemaData = {
type: 'object',
properties: {
cmd: { type: 'string' },
param: { type: 'string' }
},
required: ['cmd', 'param'],
} as const satisfies JSONSchema;
type Payload = FromSchema<typeof SchemaData>;
export default class TestApi01 extends BaseAction<Payload, any> {
actionName = ActionName.TestApi01;
// 用不着复杂检测
protected async check(payload: Payload): Promise<BaseCheckResult> {
return {
valid: true,
};
}
protected async _handle(payload: Payload): Promise<any> {
return await napCatCore.session.getMsgService().sendSsoCmdReqByContend(payload.cmd, payload.param);
}
}

View File

@@ -1,15 +1,12 @@
import BaseAction from '../BaseAction'; import BaseAction from '../BaseAction';
import fs from 'fs/promises'; import fs from 'fs/promises';
import { dbUtil } from '@/common/utils/db';
import { ob11Config } from '@/onebot11/config'; import { ob11Config } from '@/onebot11/config';
import { log, logDebug } from '@/common/utils/log'; import { UUIDConverter } from '@/common/utils/helper';
import { sleep } from '@/common/utils/helper';
import { uri2local } from '@/common/utils/file';
import { ActionName, BaseCheckResult } from '../types'; import { ActionName, BaseCheckResult } from '../types';
import { FileElement, RawMessage, VideoElement } from '@/core/entities'; import { ChatType, ElementType, FileElement, Peer, RawMessage, VideoElement } from '@/core/entities';
import { NTQQFileApi } from '@/core/apis'; import { NTQQFileApi, NTQQFriendApi, NTQQMsgApi, NTQQUserApi } from '@/core/apis';
import { FromSchema, JSONSchema } from 'json-schema-to-ts'; import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import Ajv from 'ajv'; import { getGroup } from '@/core/data';
export interface GetFilePayload { export interface GetFilePayload {
file: string; // 文件名或者fileUuid file: string; // 文件名或者fileUuid
@@ -45,69 +42,163 @@ export class GetFileBase extends BaseAction<GetFilePayload, GetFileResponse> {
return { id: element.elementId, element: element.fileElement }; return { id: element.elementId, element: element.fileElement };
} }
protected async _handle(payload: GetFilePayload): Promise<GetFileResponse> { protected async _handle(payload: GetFilePayload): Promise<GetFileResponse> {
let cache = await dbUtil.getFileCacheByName(payload.file);
if (!cache) {
cache = await dbUtil.getFileCacheByUuid(payload.file);
}
if (!cache) {
throw new Error('file not found');
}
const { enableLocalFile2Url } = ob11Config; const { enableLocalFile2Url } = ob11Config;
let UuidData: {
high: string;
low: string;
} | undefined;
try { try {
await fs.access(cache.path, fs.constants.F_OK); UuidData = UUIDConverter.decode(payload.file);
} catch (e) { if (UuidData) {
logDebug('local file not found, start download...'); const peerUin = UuidData.high;
// if (cache.url) { const msgId = UuidData.low;
// const downloadResult = await uri2local(cache.url); const isGroup = await getGroup(peerUin);
// if (downloadResult.success) { let peer: Peer | undefined;
// cache.path = downloadResult.path; //识别Peer
// dbUtil.updateFileCache(cache).then(); if (isGroup) {
// } else { peer = { chatType: ChatType.group, peerUid: peerUin };
// throw new Error('file download failed. ' + downloadResult.errMsg); }
// } const PeerUid = await NTQQUserApi.getUidByUin(peerUin);
// } else { if (PeerUid) {
// // 没有url的可能是私聊文件或者群文件需要自己下载 const isBuddy = await NTQQFriendApi.isBuddy(PeerUid);
// log('需要调用 NTQQ 下载文件api'); if (isBuddy) {
let msg = await dbUtil.getMsgByLongId(cache.msgId); peer = { chatType: ChatType.friend, peerUid: PeerUid };
// log('文件 msg', msg); } else {
if (msg) { peer = { chatType: ChatType.temp, peerUid: PeerUid };
// 构建下载函数 }
const downloadPath = await NTQQFileApi.downloadMedia(msg.msgId, msg.chatType, msg.peerUid, }
cache.elementId, '', ''); if (!peer) {
// await sleep(1000); throw new Error('chattype not support');
}
// log('download result', downloadPath); const msgList = await NTQQMsgApi.getMsgsByMsgId(peer, [msgId]);
msg = await dbUtil.getMsgByLongId(cache.msgId); if (msgList.msgList.length == 0) {
// log('下载完成后的msg', msg); throw new Error('msg not found');
cache.path = downloadPath!; }
dbUtil.updateFileCache(cache).then(); const msg = msgList.msgList[0];
// log('下载完成后的msg', msg); const findEle = msg.elements.find(e => e.elementType == ElementType.VIDEO || e.elementType == ElementType.FILE || e.elementType == ElementType.PTT);
// } if (!findEle) {
throw new Error('element not found');
}
const downloadPath = await NTQQFileApi.downloadMedia(msgId, msg.chatType, msg.peerUid, findEle.elementId, '', '');
const fileSize = findEle?.videoElement?.fileSize || findEle?.fileElement?.fileSize || findEle?.pttElement?.fileSize || '0';
const fileName = findEle?.videoElement?.fileName || findEle?.fileElement?.fileName || findEle?.pttElement?.fileName || '';
const res: GetFileResponse = {
file: downloadPath,
url: downloadPath,
file_size: fileSize,
file_name: fileName
};
if (enableLocalFile2Url) {
try {
res.base64 = await fs.readFile(downloadPath, 'base64');
} catch (e) {
throw new Error('文件下载失败. ' + e);
}
}
//不手动删除?文件持久化了
return res;
} }
} catch {
} }
// log('file found', cache);
const res: GetFileResponse = { const NTSearchNameResult = (await NTQQFileApi.searchfile([payload.file])).resultItems;
file: cache.path, if (NTSearchNameResult.length !== 0) {
url: cache.url, const MsgId = NTSearchNameResult[0].msgId;
file_size: cache.size.toString(), let peer: Peer | undefined = undefined;
file_name: cache.name if (NTSearchNameResult[0].chatType == ChatType.group) {
}; peer = { chatType: ChatType.group, peerUid: NTSearchNameResult[0].groupChatInfo[0].groupCode };
if (enableLocalFile2Url) { }
if (!cache.url) { if (!peer) {
throw new Error('chattype not support');
}
const msgList: RawMessage[] = (await NTQQMsgApi.getMsgsByMsgId(peer, [MsgId]))?.msgList;
if (!msgList || msgList.length == 0) {
throw new Error('msg not found');
}
const msg = msgList[0];
const file = msg.elements.filter(e => e.elementType == NTSearchNameResult[0].elemType);
if (file.length == 0) {
throw new Error('file not found');
}
const downloadPath = await NTQQFileApi.downloadMedia(msg.msgId, msg.chatType, msg.peerUid, file[0].elementId, '', '');
const res: GetFileResponse = {
file: downloadPath,
url: downloadPath,
file_size: NTSearchNameResult[0].fileSize.toString(),
file_name: NTSearchNameResult[0].fileName
};
if (enableLocalFile2Url) {
try { try {
res.base64 = await fs.readFile(cache.path, 'base64'); res.base64 = await fs.readFile(downloadPath, 'base64');
} catch (e) { } catch (e) {
throw new Error('文件下载失败. ' + e); throw new Error('文件下载失败. ' + e);
} }
} }
//不手动删除?文件持久化了
return res;
} }
// if (autoDeleteFile) { throw new Error('file not found');
// setTimeout(() => { // let cache = await dbUtil.getFileCacheByName(payload.file);
// fs.unlink(cache.filePath) // if (!cache) {
// }, autoDeleteFileSecond * 1000) // cache = await dbUtil.getFileCacheByUuid(payload.file);
// } // }
return res; // if (!cache) {
// throw new Error('file not found');
// }
// const { enableLocalFile2Url } = ob11Config;
// try {
// await fs.access(cache.path, fs.constants.F_OK);
// } catch (e) {
// logDebug('local file not found, start download...');
// // if (cache.url) {
// // const downloadResult = await uri2local(cache.url);
// // if (downloadResult.success) {
// // cache.path = downloadResult.path;
// // dbUtil.updateFileCache(cache).then();
// // } else {
// // throw new Error('file download failed. ' + downloadResult.errMsg);
// // }
// // } else {
// // // 没有url的可能是私聊文件或者群文件需要自己下载
// // log('需要调用 NTQQ 下载文件api');
// let peer = MessageUnique.getPeerByMsgId(cache.msgId);
// let msg = await NTQQMsgApi.getMsgsByMsgId(peer?.Peer!,cache.msgId);
// // log('文件 msg', msg);
// if (msg) {
// // 构建下载函数
// const downloadPath = await NTQQFileApi.downloadMedia(msg.msgId, msg.chatType, msg.peerUid,
// cache.elementId, '', '');
// // await sleep(1000);
// // log('download result', downloadPath);
// let peer = MessageUnique.getPeerByMsgId(cache.msgId);
// msg = await NTQQMsgApi.getMsgsByMsgId(peer?.Peer!,cache.msgId);
// // log('下载完成后的msg', msg);
// cache.path = downloadPath!;
// dbUtil.updateFileCache(cache).then();
// // log('下载完成后的msg', msg);
// // }
// }
// }
// // log('file found', cache);
// const res: GetFileResponse = {
// file: cache.path,
// url: cache.url,
// file_size: cache.size.toString(),
// file_name: cache.name
// };
// if (enableLocalFile2Url) {
// if (!cache.url) {
// try {
// res.base64 = await fs.readFile(cache.path, 'base64');
// } catch (e) {
// throw new Error('文件下载失败. ' + e);
// }
// }
// }
//return res;
} }
} }

View File

@@ -1,11 +1,10 @@
import BaseAction from '../BaseAction'; import BaseAction from '../BaseAction';
import { ActionName, BaseCheckResult } from '../types'; import { ActionName } from '../types';
import fs from 'fs'; import fs from 'fs';
import { join as joinPath } from 'node:path'; import { join as joinPath } from 'node:path';
import { calculateFileMD5, getTempDir, httpDownload } from '@/common/utils/file'; import { calculateFileMD5, getTempDir, httpDownload } from '@/common/utils/file';
import { v4 as uuid4 } from 'uuid'; import { randomUUID } from 'crypto';
import { FromSchema, JSONSchema } from 'json-schema-to-ts'; import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import Ajv from 'ajv';
interface FileResponse { interface FileResponse {
file: string; file: string;
} }
@@ -32,7 +31,7 @@ export default class GoCQHTTPDownloadFile extends BaseAction<Payload, FileRespon
PayloadSchema = SchemaData; PayloadSchema = SchemaData;
protected async _handle(payload: Payload): Promise<FileResponse> { protected async _handle(payload: Payload): Promise<FileResponse> {
const isRandomName = !payload.name; const isRandomName = !payload.name;
const name = payload.name || uuid4(); const name = payload.name || randomUUID();
const filePath = joinPath(getTempDir(), name); const filePath = joinPath(getTempDir(), name);
if (payload.base64) { if (payload.base64) {

View File

@@ -1,11 +1,10 @@
import BaseAction from '../BaseAction'; import BaseAction from '../BaseAction';
import { OB11ForwardMessage, OB11Message, OB11MessageData } from '../../types'; import { OB11ForwardMessage, OB11Message, OB11MessageData } from '../../types';
import { NTQQMsgApi } from '@/core/apis'; import { NTQQMsgApi } from '@/core/apis';
import { dbUtil } from '@/common/utils/db';
import { OB11Constructor } from '../../constructor'; import { OB11Constructor } from '../../constructor';
import { ActionName, BaseCheckResult } from '../types'; import { ActionName, BaseCheckResult } from '../types';
import { FromSchema, JSONSchema } from 'json-schema-to-ts'; import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import Ajv from 'ajv'; import { MessageUnique } from '@/common/utils/MessageUnique';
const SchemaData = { const SchemaData = {
type: 'object', type: 'object',
@@ -29,24 +28,19 @@ export class GoCQHTTPGetForwardMsgAction extends BaseAction<Payload, any> {
if (!msgId) { if (!msgId) {
throw Error('message_id is required'); throw Error('message_id is required');
} }
let rootMsg = await dbUtil.getMsgByLongId(msgId); const rootMsgId = MessageUnique.getShortIdByMsgId(msgId);
const rootMsg = MessageUnique.getMsgIdAndPeerByShortId(rootMsgId || parseInt(msgId));
if (!rootMsg) { if (!rootMsg) {
rootMsg = await dbUtil.getMsgByShortId(parseInt(msgId)); throw Error('msg not found');
if (!rootMsg) {
throw Error('msg not found');
}
} }
const data = await NTQQMsgApi.getMultiMsg({ const data = await NTQQMsgApi.getMultiMsg(rootMsg.Peer, rootMsg.MsgId, rootMsg.MsgId);
chatType: rootMsg.chatType,
peerUid: rootMsg.peerUid
}, rootMsg.msgId, rootMsg.msgId);
if (!data || data.result !== 0) { if (!data || data.result !== 0) {
throw Error('找不到相关的聊天记录' + data?.errMsg); throw Error('找不到相关的聊天记录' + data?.errMsg);
} }
const msgList = data.msgList; const msgList = data.msgList;
const messages = await Promise.all(msgList.map(async msg => { const messages = await Promise.all(msgList.map(async msg => {
const resMsg = await OB11Constructor.message(msg); const resMsg = await OB11Constructor.message(msg);
resMsg.message_id = await dbUtil.addMsg(msg); resMsg.message_id = await MessageUnique.createMsg({ guildId:'',chatType:msg.chatType,peerUid:msg.peerUid },msg.msgId)!;
return resMsg; return resMsg;
})); }));
messages.map(msg => { messages.map(msg => {

View File

@@ -2,11 +2,11 @@ import BaseAction from '../BaseAction';
import { OB11Message, OB11User } from '../../types'; import { OB11Message, OB11User } from '../../types';
import { ActionName } from '../types'; import { ActionName } from '../types';
import { ChatType } from '@/core/entities'; import { ChatType } from '@/core/entities';
import { dbUtil } from '@/common/utils/db';
import { NTQQMsgApi } from '@/core/apis/msg'; import { NTQQMsgApi } from '@/core/apis/msg';
import { OB11Constructor } from '../../constructor'; import { OB11Constructor } from '../../constructor';
import { FromSchema, JSONSchema } from 'json-schema-to-ts'; import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import { NTQQFriendApi, NTQQUserApi } from '@/core'; import { NTQQFriendApi, NTQQUserApi } from '@/core';
import { MessageUnique } from '@/common/utils/MessageUnique';
interface Response { interface Response {
messages: OB11Message[]; messages: OB11Message[];
@@ -32,7 +32,7 @@ export default class GetFriendMsgHistory extends BaseAction<Payload, Response> {
if (!uid) { if (!uid) {
throw `记录${payload.user_id}不存在`; throw `记录${payload.user_id}不存在`;
} }
const startMsgId = (await dbUtil.getMsgByShortId(payload.message_seq))?.msgId || '0'; const startMsgId = (await MessageUnique.getMsgIdAndPeerByShortId(payload.message_seq))?.MsgId || '0';
const friend = await NTQQFriendApi.isBuddy(uid); const friend = await NTQQFriendApi.isBuddy(uid);
const historyResult = (await NTQQMsgApi.getMsgHistory({ const historyResult = (await NTQQMsgApi.getMsgHistory({
chatType: friend ? ChatType.friend : ChatType.temp, chatType: friend ? ChatType.friend : ChatType.temp,
@@ -41,7 +41,7 @@ export default class GetFriendMsgHistory extends BaseAction<Payload, Response> {
//logDebug(historyResult); //logDebug(historyResult);
const msgList = historyResult.msgList; const msgList = historyResult.msgList;
await Promise.all(msgList.map(async msg => { await Promise.all(msgList.map(async msg => {
msg.id = await dbUtil.addMsg(msg); msg.id = await MessageUnique.createMsg({ guildId: '', chatType: msg.chatType, peerUid: msg.peerUid }, msg.msgId);
})); }));
const ob11MsgList = await Promise.all(msgList.map(msg => OB11Constructor.message(msg))); const ob11MsgList = await Promise.all(msgList.map(msg => OB11Constructor.message(msg)));
return { 'messages': ob11MsgList }; return { 'messages': ob11MsgList };

View File

@@ -2,12 +2,11 @@ import BaseAction from '../BaseAction';
import { OB11Message, OB11User } from '../../types'; import { OB11Message, OB11User } from '../../types';
import { getGroup, groups } from '@/core/data'; import { getGroup, groups } from '@/core/data';
import { ActionName } from '../types'; import { ActionName } from '../types';
import { ChatType } from '@/core/entities'; import { ChatType, RawMessage } from '@/core/entities';
import { dbUtil } from '@/common/utils/db';
import { NTQQMsgApi } from '@/core/apis/msg'; import { NTQQMsgApi } from '@/core/apis/msg';
import { OB11Constructor } from '../../constructor'; import { OB11Constructor } from '../../constructor';
import { logDebug } from '@/common/utils/log';
import { FromSchema, JSONSchema } from 'json-schema-to-ts'; import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import { MessageUnique } from '@/common/utils/MessageUnique';
interface Response { interface Response {
messages: OB11Message[]; messages: OB11Message[];
} }
@@ -15,11 +14,11 @@ interface Response {
const SchemaData = { const SchemaData = {
type: 'object', type: 'object',
properties: { properties: {
group_id: { type: [ 'number' , 'string' ] }, group_id: { type: ['number', 'string'] },
message_seq: { type: 'number' }, message_seq: { type: 'number' },
count: { type: 'number' } count: { type: 'number' }
}, },
required: ['group_id', 'message_seq', 'count'] required: ['group_id']
} as const satisfies JSONSchema; } as const satisfies JSONSchema;
type Payload = FromSchema<typeof SchemaData>; type Payload = FromSchema<typeof SchemaData>;
@@ -32,16 +31,20 @@ export default class GoCQHTTPGetGroupMsgHistory extends BaseAction<Payload, Resp
if (!group) { if (!group) {
throw `${payload.group_id}不存在`; throw `${payload.group_id}不存在`;
} }
const startMsgId = (await dbUtil.getMsgByShortId(payload.message_seq))?.msgId || '0'; let targetMsgShortId, count = parseInt(payload.count?.toString() ?? '20');
// log("startMsgId", startMsgId) const peer = {
const historyResult = (await NTQQMsgApi.getMsgHistory({
chatType: ChatType.group, chatType: ChatType.group,
peerUid: group.groupCode peerUid: group.groupCode
}, startMsgId, parseInt(payload.count?.toString()) || 20)); };
//logDebug(historyResult); let msgList: RawMessage[];
const msgList = historyResult.msgList; if (!payload.message_seq) {
msgList = (await NTQQMsgApi.getLastestMsgByUids(peer, count)).msgList;
} else {
const startMsgId = (await MessageUnique.getMsgIdAndPeerByShortId(targetMsgShortId ?? (payload.message_seq ?? 0)))?.MsgId || '0';
msgList = (await NTQQMsgApi.getMsgHistory(peer, startMsgId, count)).msgList;
}
await Promise.all(msgList.map(async msg => { await Promise.all(msgList.map(async msg => {
msg.id = await dbUtil.addMsg(msg); msg.id = await MessageUnique.createMsg({ guildId: '', chatType: msg.chatType, peerUid: msg.peerUid }, msg.msgId);
})); }));
const ob11MsgList = await Promise.all(msgList.map(msg => OB11Constructor.message(msg))); const ob11MsgList = await Promise.all(msgList.map(msg => OB11Constructor.message(msg)));
return { 'messages': ob11MsgList }; return { 'messages': ob11MsgList };

View File

@@ -3,6 +3,7 @@ import BaseAction from '../BaseAction';
import { ActionName } from '../types'; import { ActionName } from '../types';
import { JSONSchema } from 'json-schema-to-ts'; import { JSONSchema } from 'json-schema-to-ts';
import { NTQQSystemApi } from '@/core'; import { NTQQSystemApi } from '@/core';
import { sleep } from '@/common/utils/helper';
const SchemaData = { const SchemaData = {
type: 'object', type: 'object',
@@ -15,7 +16,8 @@ export class GetOnlineClient extends BaseAction<void, Array<any>> {
actionName = ActionName.GetOnlineClient; actionName = ActionName.GetOnlineClient;
protected async _handle(payload: void) { protected async _handle(payload: void) {
//console.log(await NTQQSystemApi.getOnlineDev()); NTQQSystemApi.getOnlineDev();
await sleep(500);
return DeviceList; return DeviceList;
} }
} }

View File

@@ -5,6 +5,7 @@ import { ActionName } from '../types';
import { NTQQUserApi } from '@/core/apis/user'; import { NTQQUserApi } from '@/core/apis/user';
import { FromSchema, JSONSchema } from 'json-schema-to-ts'; import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import { calcQQLevel } from '@/common/utils/qqlevel'; import { calcQQLevel } from '@/common/utils/qqlevel';
import { requireMinNTQQBuild } from '@/common/utils/QQBasicInfo';
const SchemaData = { const SchemaData = {
type: 'object', type: 'object',
@@ -20,24 +21,46 @@ export default class GoCQHTTPGetStrangerInfo extends BaseAction<Payload, OB11Use
actionName = ActionName.GoCQHTTP_GetStrangerInfo; actionName = ActionName.GoCQHTTP_GetStrangerInfo;
protected async _handle(payload: Payload): Promise<OB11User> { protected async _handle(payload: Payload): Promise<OB11User> {
const user_id = payload.user_id.toString(); if (!requireMinNTQQBuild('26702')) {
const extendData = await NTQQUserApi.getUserDetailInfoByUin(user_id); const user_id = payload.user_id.toString();
const uid = (await NTQQUserApi.getUidByUin(user_id))!; const extendData = await NTQQUserApi.getUserDetailInfoByUin(user_id);
if (!uid || uid.indexOf('*') != -1) { const uid = (await NTQQUserApi.getUidByUin(user_id))!;
const ret = { if (!uid || uid.indexOf('*') != -1) {
...extendData, const ret = {
user_id: parseInt(extendData.info.uin) || 0, ...extendData,
nickname: extendData.info.nick, user_id: parseInt(extendData.info.uin) || 0,
sex: OB11UserSex.unknown, nickname: extendData.info.nick,
age: (extendData.info.birthday_year == 0) ? 0 : new Date().getFullYear() - extendData.info.birthday_year, sex: OB11UserSex.unknown,
qid: extendData.info.qid, age: (extendData.info.birthday_year == 0) ? 0 : new Date().getFullYear() - extendData.info.birthday_year,
level: extendData.info.qqLevel && calcQQLevel(extendData.info.qqLevel) || 0, qid: extendData.info.qid,
login_days: 0, level: extendData.info.qqLevel && calcQQLevel(extendData.info.qqLevel) || 0,
uid: '' login_days: 0,
}; uid: ''
return ret; };
return ret;
}
const data = { ...extendData, ...(await NTQQUserApi.getUserDetailInfo(uid)) };
return OB11Constructor.stranger(data);
} else {
const user_id = payload.user_id.toString();
const extendData = await NTQQUserApi.getUserDetailInfoByUinV2(user_id);
//console.log(extendData);
const uid = (await NTQQUserApi.getUidByUin(user_id))!;
if (!uid || uid.indexOf('*') != -1) {
const ret = {
...extendData,
user_id: parseInt(extendData.detail.uin) || 0,
nickname: extendData.detail.simpleInfo.coreInfo.nick,
sex: OB11UserSex.unknown,
age: 0,
level: extendData.detail.commonExt.qqLevel && calcQQLevel(extendData.detail.commonExt.qqLevel) || 0,
login_days: 0,
uid: ''
};
return ret;
}
const data = { ...extendData, ...(await NTQQUserApi.getUserDetailInfo(uid)) };
return OB11Constructor.stranger(data);
} }
const data = { ...extendData, ...(await NTQQUserApi.getUserDetailInfo(uid)) };
return OB11Constructor.stranger(data);
} }
} }

View File

@@ -0,0 +1,51 @@
import BaseAction from '../BaseAction';
import { getGroup } from '@/core/data';
import { ActionName } from '../types';
import { SendMsgElementConstructor } from '@/core/entities/constructor';
import { ChatType, Peer, SendFileElement } from '@/core/entities';
import fs from 'fs';
import { SendMsg, sendMsg } from '@/onebot11/action/msg/SendMsg';
import { uri2local } from '@/common/utils/file';
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import { NTQQFriendApi, NTQQUserApi } from '@/core';
const SchemaData = {
type: 'object',
properties: {
user_id: { type: ['number', 'string'] },
file: { type: 'string' },
name: { type: 'string' }
},
required: ['user_id', 'file', 'name']
} as const satisfies JSONSchema;
type Payload = FromSchema<typeof SchemaData>;
export default class GoCQHTTPUploadPrivateFile extends BaseAction<Payload, null> {
actionName = ActionName.GOCQHTTP_UploadPrivateFile;
PayloadSchema = SchemaData;
async getPeer(payload: Payload): Promise<Peer> {
if (payload.user_id) {
const peerUid = await NTQQUserApi.getUidByUin(payload.user_id.toString());
if (!peerUid) {
throw `私聊${payload.user_id}不存在`;
}
const isBuddy = await NTQQFriendApi.isBuddy(peerUid);
return { chatType: isBuddy ? ChatType.friend : ChatType.temp, peerUid };
}
throw '缺少参数 user_id';
}
protected async _handle(payload: Payload): Promise<null> {
const peer = await this.getPeer(payload);
let file = payload.file;
if (fs.existsSync(file)) {
file = `file://${file}`;
}
const downloadResult = await uri2local(file);
if (downloadResult.errMsg) {
throw new Error(downloadResult.errMsg);
}
const sendFileEle: SendFileElement = await SendMsgElementConstructor.file(downloadResult.path, payload.name);
await sendMsg(peer, [sendFileEle], [], true);
return null;
}
}

View File

@@ -1,31 +1,31 @@
import { dbUtil } from '@/common/utils/db';
import BaseAction from '../BaseAction'; import BaseAction from '../BaseAction';
import { ActionName } from '../types'; import { ActionName } from '../types';
import { FromSchema, JSONSchema } from 'json-schema-to-ts'; import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import { NTQQGroupApi } from '@/core'; import { NTQQGroupApi } from '@/core';
import { MessageUnique } from '@/common/utils/MessageUnique';
const SchemaData = { const SchemaData = {
type: 'object', type: 'object',
properties: { properties: {
message_id: { type: ['number', 'string'] } message_id: { type: ['number', 'string'] }
}, },
required: ['message_id'] required: ['message_id']
} as const satisfies JSONSchema; } as const satisfies JSONSchema;
type Payload = FromSchema<typeof SchemaData>; type Payload = FromSchema<typeof SchemaData>;
export default class DelEssenceMsg extends BaseAction<Payload, any> { export default class DelEssenceMsg extends BaseAction<Payload, any> {
actionName = ActionName.DelEssenceMsg; actionName = ActionName.DelEssenceMsg;
PayloadSchema = SchemaData; PayloadSchema = SchemaData;
protected async _handle(payload: Payload): Promise<any> { protected async _handle(payload: Payload): Promise<any> {
const msg = await dbUtil.getMsgByShortId(parseInt(payload.message_id.toString())); const msg = await MessageUnique.getMsgIdAndPeerByShortId(parseInt(payload.message_id.toString()));
if (!msg) { if (!msg) {
throw new Error('msg not found'); throw new Error('msg not found');
}
return await NTQQGroupApi.removeGroupEssence(
msg.peerUin,
msg.msgId
);
} }
return await NTQQGroupApi.removeGroupEssence(
msg.Peer.peerUid,
msg.MsgId
);
}
} }

View File

@@ -2,10 +2,8 @@ import { OB11Group } from '../../types';
import { OB11Constructor } from '../../constructor'; import { OB11Constructor } from '../../constructor';
import BaseAction from '../BaseAction'; import BaseAction from '../BaseAction';
import { ActionName } from '../types'; import { ActionName } from '../types';
import { groups } from '@/core/data';
import { NTQQGroupApi } from '@/core/apis'; import { NTQQGroupApi } from '@/core/apis';
import { Group } from '@/core/entities'; import { Group } from '@/core/entities';
import { log } from '@/common/utils/log';
import { FromSchema, JSONSchema } from 'json-schema-to-ts'; import { FromSchema, JSONSchema } from 'json-schema-to-ts';
// no_cache get时传字符串 // no_cache get时传字符串
const SchemaData = { const SchemaData = {
@@ -21,11 +19,7 @@ class GetGroupList extends BaseAction<Payload, OB11Group[]> {
actionName = ActionName.GetGroupList; actionName = ActionName.GetGroupList;
PayloadSchema = SchemaData; PayloadSchema = SchemaData;
protected async _handle(payload: Payload) { protected async _handle(payload: Payload) {
let groupList: Group[] = Array.from(groups.values()); let groupList: Group[] = await NTQQGroupApi.getGroups(payload?.no_cache === true || payload.no_cache === 'true');
if (groupList.length === 0 || payload?.no_cache === true || payload.no_cache === 'true') {
groupList = await NTQQGroupApi.getGroups(true);
// log('get groups', groups);
}
return OB11Constructor.groups(groupList); return OB11Constructor.groups(groupList);
} }
} }

View File

@@ -1,16 +1,14 @@
import { OB11GroupMember } from '../../types'; import { OB11GroupMember } from '../../types';
import { getGroup, getGroupMember, groupMembers } from '@/core/data';
import { OB11Constructor } from '../../constructor'; import { OB11Constructor } from '../../constructor';
import BaseAction from '../BaseAction'; import BaseAction from '../BaseAction';
import { ActionName } from '../types'; import { ActionName } from '../types';
import { NTQQUserApi } from '@/core/apis/user'; import { NTQQUserApi } from '@/core/apis/user';
import { log, logDebug } from '@/common/utils/log'; import { logDebug } from '@/common/utils/log';
import { isNull } from '../../../common/utils/helper';
import { WebApi } from '@/core/apis/webapi'; import { WebApi } from '@/core/apis/webapi';
import { NTQQGroupApi } from '@/core'; import { NTQQGroupApi } from '@/core';
import { FromSchema, JSONSchema } from 'json-schema-to-ts'; import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import { getGroupMember, selfInfo } from '@/core/data';
// no_cache get时传字符串 import { requireMinNTQQBuild } from '@/common/utils/QQBasicInfo';
const SchemaData = { const SchemaData = {
type: 'object', type: 'object',
properties: { properties: {
@@ -27,39 +25,55 @@ class GetGroupMemberInfo extends BaseAction<Payload, OB11GroupMember> {
actionName = ActionName.GetGroupMemberInfo; actionName = ActionName.GetGroupMemberInfo;
PayloadSchema = SchemaData; PayloadSchema = SchemaData;
protected async _handle(payload: Payload) { protected async _handle(payload: Payload) {
const group = await getGroup(payload.group_id.toString()); const isNocache = payload.no_cache == true || payload.no_cache === 'true';
if (!group) { let uid = await NTQQUserApi.getUidByUin(payload.user_id.toString());
throw (`群(${payload.group_id})不存在`); if (!uid) {
throw (`Uin2Uid Error ${payload.user_id}不存在`);
} }
const webGroupMembers = await WebApi.getGroupMembers(payload.group_id.toString()); let member = await NTQQGroupApi.getGroupMemberV2(payload.group_id.toString(), uid, isNocache);
if (payload.no_cache == true || payload.no_cache === 'true') { if (!member) {
groupMembers.set(group.groupCode, await NTQQGroupApi.getGroupMembers(payload.group_id.toString()));
}
const member = await getGroupMember(payload.group_id.toString(), payload.user_id.toString());
// log(member);
if (member) {
logDebug('获取群成员详细信息');
try {
const info = (await NTQQUserApi.getUserDetailInfo(member.uid));
logDebug('群成员详细信息结果', info);
Object.assign(member, info);
} catch (e) {
logDebug('获取群成员详细信息失败, 只能返回基础信息', e);
}
const retMember = OB11Constructor.groupMember(payload.group_id.toString(), member);
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();
}
}
return retMember;
} else {
throw (`群(${payload.group_id})成员${payload.user_id}不存在`); throw (`群(${payload.group_id})成员${payload.user_id}不存在`);
} }
try {
const info = (await NTQQUserApi.getUserDetailInfo(member.uid));
logDebug('群成员详细信息结果', info);
Object.assign(member, info);
} catch (e) {
logDebug('获取群成员详细信息失败, 只能返回基础信息', e);
}
const date = Math.round(Date.now() / 1000);
const retMember = OB11Constructor.groupMember(payload.group_id.toString(), member);
if (!requireMinNTQQBuild('26702')) {
let SelfInfoInGroup = await NTQQGroupApi.getGroupMemberV2(payload.group_id.toString(), selfInfo.uid, isNocache);
let isPrivilege = false;
if (SelfInfoInGroup) {
isPrivilege = SelfInfoInGroup.role === 3 || SelfInfoInGroup.role === 4;
}
if (isPrivilege) {
const webGroupMembers = await WebApi.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();
}
}
} else {
const LastestMsgList = await NTQQGroupApi.getLastestMsg(payload.group_id.toString(), [payload.user_id.toString()]);
if (LastestMsgList?.msgList?.length && LastestMsgList?.msgList?.length > 0) {
const last_send_time = LastestMsgList.msgList[0].msgTime;
if (last_send_time && last_send_time != '0' && last_send_time != '') {
retMember.last_sent_time = parseInt(last_send_time);
retMember.join_time = Math.round(Date.now() / 1000);//兜底数据 防止群管乱杀
}
}
}
} else {
retMember.last_sent_time = parseInt((await getGroupMember(payload.group_id.toString(), retMember.user_id))?.lastSpeakTime || date.toString());
retMember.join_time = parseInt((await getGroupMember(payload.group_id.toString(), retMember.user_id))?.joinTime || date.toString());
}
return retMember;
} }
} }
export default GetGroupMemberInfo;
export default GetGroupMemberInfo;

View File

@@ -0,0 +1,84 @@
import { OB11GroupMember } from '../../types';
import { getGroup, getGroupMember, groupMembers, selfInfo } from '@/core/data';
import { OB11Constructor } from '../../constructor';
import BaseAction from '../BaseAction';
import { ActionName } from '../types';
import { NTQQUserApi } from '@/core/apis/user';
import { logDebug } from '@/common/utils/log';
import { WebApi } from '@/core/apis/webapi';
import { NTQQGroupApi } from '@/core';
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import { requireMinNTQQBuild } from '@/common/utils/QQBasicInfo';
// no_cache get时传字符串
const SchemaData = {
type: 'object',
properties: {
group_id: { type: ['number', 'string'] },
user_id: { type: ['number', 'string'] },
no_cache: { type: ['boolean', 'string'] },
},
required: ['group_id', 'user_id']
} as const satisfies JSONSchema;
type Payload = FromSchema<typeof SchemaData>;
class GetGroupMemberInfo extends BaseAction<Payload, OB11GroupMember> {
actionName = ActionName.GetGroupMemberInfo;
PayloadSchema = SchemaData;
protected async _handle(payload: Payload) {
if (requireMinNTQQBuild('26702')) {
let V2Data = await NTQQGroupApi.getGroupMemberV2(payload.group_id.toString(), payload.user_id.toString(), payload.no_cache == true || payload.no_cache === 'true');
if (V2Data) {
return OB11Constructor.groupMember(payload.group_id.toString(), V2Data);
} else {
throw (`群(${payload.group_id})成员${payload.user_id}不存在`);
}
}
const group = await getGroup(payload.group_id.toString());
const role = (await getGroupMember(payload.group_id, selfInfo.uin))?.role;
const isPrivilege = role === 3 || role === 4;
if (!group) {
throw (`群(${payload.group_id})不存在`);
}
if (payload.no_cache == true || payload.no_cache === 'true') {
groupMembers.set(group.groupCode, await NTQQGroupApi.getGroupMembers(payload.group_id.toString()));
}
const member = await getGroupMember(payload.group_id.toString(), payload.user_id.toString());
//早返回
if (!member) {
throw (`群(${payload.group_id})成员${payload.user_id}不存在`);
}
//console.log('GetGroupMemberInfo', JSON.stringify(await NTQQGroupApi.getGroupMemberV2(payload.group_id.toString(), member.uid, true), null, 4));
try {
const info = (await NTQQUserApi.getUserDetailInfo(member.uid));
logDebug('群成员详细信息结果', info);
Object.assign(member, info);
} catch (e) {
logDebug('获取群成员详细信息失败, 只能返回基础信息', e);
}
const retMember = OB11Constructor.groupMember(payload.group_id.toString(), member);
if (isPrivilege) {
const webGroupMembers = await WebApi.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();
}
}
} else {
const LastestMsgList = await NTQQGroupApi.getLastestMsg(payload.group_id.toString(), [payload.user_id.toString()]);
if (LastestMsgList?.msgList?.length && LastestMsgList?.msgList?.length > 0) {
const last_send_time = LastestMsgList.msgList[0].msgTime;
if (last_send_time && last_send_time != '0' && last_send_time != '') {
retMember.last_sent_time = parseInt(last_send_time);
retMember.join_time = Math.round(Date.now() / 1000);//兜底数据 防止群管乱杀
}
}
}
return retMember;
}
}
export default GetGroupMemberInfo;

View File

@@ -1,15 +1,12 @@
import { getGroup, getGroupMember, groupMembers, selfInfo } from '@/core/data'; import { getGroup, getGroupMember, selfInfo } from '@/core/data';
import { OB11GroupMember } from '../../types'; import { OB11GroupMember } from '../../types';
import { OB11Constructor } from '../../constructor'; import { OB11Constructor } from '../../constructor';
import BaseAction from '../BaseAction'; import BaseAction from '../BaseAction';
import { ActionName } from '../types'; import { ActionName } from '../types';
import { napCatCore, NTQQGroupApi, NTQQUserApi } from '@/core'; import { NTQQGroupApi } from '@/core';
import { WebApi } from '@/core/apis/webapi'; import { WebApi } from '@/core/apis/webapi';
import { logDebug } from '@/common/utils/log';
import { FromSchema, JSONSchema } from 'json-schema-to-ts'; import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import { ob11Config } from '@/onebot11/config'; import { requireMinNTQQBuild } from '@/common/utils/QQBasicInfo';
import { dbUtil } from '@/common/utils/db';
import { TypeConvert } from '@/common/utils/type';
const SchemaData = { const SchemaData = {
type: 'object', type: 'object',
@@ -26,51 +23,67 @@ class GetGroupMemberList extends BaseAction<Payload, OB11GroupMember[]> {
actionName = ActionName.GetGroupMemberList; actionName = ActionName.GetGroupMemberList;
PayloadSchema = SchemaData; PayloadSchema = SchemaData;
protected async _handle(payload: Payload) { protected async _handle(payload: Payload) {
const role = (await getGroupMember(payload.group_id, selfInfo.uin))?.role; const isNocache = payload.no_cache == true || payload.no_cache === 'true';
const group = await getGroup(payload.group_id.toString()); const GroupList = await NTQQGroupApi.getGroups(isNocache);
const group = GroupList.find(item => item.groupCode == payload.group_id);
if (!group) { if (!group) {
throw (`${payload.group_id}不存在`); throw (`${payload.group_id}不存在`);
} }
let groupMembers = await NTQQGroupApi.getGroupMembers(payload.group_id.toString());
// 从Data里面获取 let _groupMembers = Array.from(groupMembers.values())
let _groupMembers: OB11GroupMember[] = OB11Constructor.groupMembers(group); .map(item => { return OB11Constructor.groupMember(group.groupCode, item); });
if (payload.no_cache == true || payload.no_cache === 'true') {
// webGroupMembers = await WebApi.getGroupMembers(payload.group_id.toString());'
const _groupMembers = await NTQQGroupApi.getGroupMembers(payload.group_id.toString());
groupMembers.set(group.groupCode, _groupMembers);
}
const MemberMap: Map<number, OB11GroupMember> = new Map<number, OB11GroupMember>(); const MemberMap: Map<number, OB11GroupMember> = new Map<number, OB11GroupMember>();
// 转为Map 方便索引 // 转为Map 方便索引
const date = Math.round(Date.now() / 1000);
for (let i = 0, len = _groupMembers.length; i < len; i++) { for (let i = 0, len = _groupMembers.length; i < len; i++) {
// 保证基础数据有这个 同时避免群管插件过于依赖这个杀了
_groupMembers[i].join_time = date;
_groupMembers[i].last_sent_time = date;
MemberMap.set(_groupMembers[i].user_id, _groupMembers[i]); MemberMap.set(_groupMembers[i].user_id, _groupMembers[i]);
} }
const isPrivilege = role === 3 || role === 4; if (!requireMinNTQQBuild('26702')) {
if (isPrivilege) { const selfRole = groupMembers.get(selfInfo.uid)?.role;
const webGroupMembers = await WebApi.getGroupMembers(payload.group_id.toString()); const isPrivilege = selfRole === 3 || selfRole === 4;
for (let i = 0, len = webGroupMembers.length; i < len; i++) {
if (!webGroupMembers[i]?.uin) { if (isPrivilege) {
continue; const webGroupMembers = await WebApi.getGroupMembers(payload.group_id.toString());
for (let i = 0, len = webGroupMembers.length; i < len; i++) {
if (!webGroupMembers[i]?.uin) {
continue;
}
const MemberData = MemberMap.get(webGroupMembers[i]?.uin);
if (MemberData) {
MemberData.join_time = webGroupMembers[i]?.join_time;
MemberData.last_sent_time = webGroupMembers[i]?.last_speak_time;
MemberData.qage = webGroupMembers[i]?.qage;
MemberData.level = webGroupMembers[i]?.lv.level.toString();
MemberMap.set(webGroupMembers[i]?.uin, MemberData);
}
} }
const MemberData = MemberMap.get(webGroupMembers[i]?.uin); } else {
if (MemberData) { if (isNocache) {
MemberData.join_time = webGroupMembers[i]?.join_time; const DateMap = await NTQQGroupApi.getGroupMemberLastestSendTimeCache(payload.group_id.toString());//开始从本地拉取
MemberData.last_sent_time = webGroupMembers[i]?.last_speak_time; for (const DateUin of DateMap.keys()) {
MemberData.qage = webGroupMembers[i]?.qage; const MemberData = MemberMap.get(parseInt(DateUin));
MemberData.level = webGroupMembers[i]?.lv.level.toString(); if (MemberData) {
MemberMap.set(webGroupMembers[i]?.uin, MemberData); MemberData.last_sent_time = parseInt(DateMap.get(DateUin)!);
//join_time 有基础数据兜底
}
}
} else {
_groupMembers.forEach(item => {
item.last_sent_time = date;
item.join_time = date;
});
} }
} }
} else if (ob11Config.GroupLocalTime.Record && ob11Config.GroupLocalTime.RecordList[0] === '-1' || ob11Config.GroupLocalTime.RecordList.includes(payload.group_id.toString())) { } else {
const _sendAndJoinRember = await dbUtil.getLastSentTimeAndJoinTime(TypeConvert.toNumber(payload.group_id)); _groupMembers.forEach(async item => {
_sendAndJoinRember.forEach((element) => { item.last_sent_time = parseInt((await getGroupMember(payload.group_id.toString(), item.user_id))?.lastSpeakTime || date.toString());
const MemberData = MemberMap.get(element.user_id); item.join_time = parseInt((await getGroupMember(payload.group_id.toString(), item.user_id))?.joinTime || date.toString());
if (MemberData) {
MemberData.join_time = element.join_time;
MemberData.last_sent_time = element.last_sent_time;
}
}); });
} }
// 还原索引到Array 一同返回 // 还原索引到Array 一同返回
@@ -82,7 +95,7 @@ class GetGroupMemberList extends BaseAction<Payload, OB11GroupMember[]> {
// } // }
// _groupMembers = Array.from(retData); // _groupMembers = Array.from(retData);
_groupMembers = Array.from(MemberMap.values()); _groupMembers = Array.from(MemberMap.values());
return _groupMembers; return _groupMembers;
} }

View File

@@ -1,8 +1,8 @@
import { dbUtil } from '@/common/utils/db';
import BaseAction from '../BaseAction'; import BaseAction from '../BaseAction';
import { ActionName } from '../types'; import { ActionName } from '../types';
import { FromSchema, JSONSchema } from 'json-schema-to-ts'; import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import { NTQQGroupApi, NTQQMsgApi } from '@/core'; import { NTQQGroupApi, NTQQMsgApi } from '@/core';
import { MessageUnique } from '@/common/utils/MessageUnique';
const SchemaData = { const SchemaData = {
type: 'object', type: 'object',
@@ -18,13 +18,13 @@ export default class SetEssenceMsg extends BaseAction<Payload, any> {
actionName = ActionName.SetEssenceMsg; actionName = ActionName.SetEssenceMsg;
PayloadSchema = SchemaData; PayloadSchema = SchemaData;
protected async _handle(payload: Payload): Promise<any> { protected async _handle(payload: Payload): Promise<any> {
const msg = await dbUtil.getMsgByShortId(parseInt(payload.message_id.toString())); const msg = await MessageUnique.getMsgIdAndPeerByShortId(parseInt(payload.message_id.toString()));
if (!msg) { if (!msg) {
throw new Error('msg not found'); throw new Error('msg not found');
} }
return await NTQQGroupApi.addGroupEssence( return await NTQQGroupApi.addGroupEssence(
msg.peerUin, msg.Peer.peerUid,
msg.msgId msg.MsgId
); );
} }
} }

View File

@@ -74,6 +74,10 @@ import DelEssenceMsg from './group/DelEssenceMsg';
import SetEssenceMsg from './group/SetEssenceMsg'; import SetEssenceMsg from './group/SetEssenceMsg';
import GetRecentContact from './user/GetRecentContact'; import GetRecentContact from './user/GetRecentContact';
import { GetProfileLike } from './extends/GetProfileLike'; import { GetProfileLike } from './extends/GetProfileLike';
import SetGroupHeader from './extends/SetGroupHeader';
import { FetchCustomFace } from './extends/FetchCustomFace';
import GoCQHTTPUploadPrivateFile from './go-cqhttp/UploadPrivareFile';
import TestApi01 from './extends/TestApi01';
export const actionHandlers = [ export const actionHandlers = [
new RebootNormal(), new RebootNormal(),
@@ -85,10 +89,6 @@ export const actionHandlers = [
new sharePeer(), new sharePeer(),
new CreateCollection(), new CreateCollection(),
new SetLongNick(), new SetLongNick(),
// new GetConfigAction(),
// new SetConfigAction(),
// new GetGroupAddRequest(),
// TranslateEnWordToZn = "translate_en2zh",
new ForwardFriendSingleMsg(), new ForwardFriendSingleMsg(),
new ForwardGroupSingleMsg(), new ForwardGroupSingleMsg(),
new MarkGroupMsgAsRead(), new MarkGroupMsgAsRead(),
@@ -105,9 +105,13 @@ export const actionHandlers = [
new GetMsg(), new GetMsg(),
new GetLoginInfo(), new GetLoginInfo(),
new GetFriendList(), new GetFriendList(),
new GetGroupList(), new GetGroupInfo(), new GetGroupList(),
new GetGroupMemberList(), new GetGroupMemberInfo(), new GetGroupInfo(),
new SendGroupMsg(), new SendPrivateMsg(), new SendMsg(), new GetGroupMemberList(),
new GetGroupMemberInfo(),
new SendGroupMsg(),
new SendPrivateMsg(),
new SendMsg(),
new DeleteMsg(), new DeleteMsg(),
new SetGroupAddRequest(), new SetGroupAddRequest(),
new SetFriendAddRequest(), new SetFriendAddRequest(),
@@ -125,9 +129,7 @@ export const actionHandlers = [
new GetImage(), new GetImage(),
new GetRecord(), new GetRecord(),
new SetMsgEmojiLike(), new SetMsgEmojiLike(),
// new CleanCache(),
new GetCookies(), new GetCookies(),
//
new SetOnlineStatus(), new SetOnlineStatus(),
new GetRobotUinRange(), new GetRobotUinRange(),
new GetFriendWithCategory(), new GetFriendWithCategory(),
@@ -156,9 +158,12 @@ export const actionHandlers = [
new SetEssenceMsg(), new SetEssenceMsg(),
new GetRecentContact(), new GetRecentContact(),
new MarkAllMsgAsRead(), new MarkAllMsgAsRead(),
new GetProfileLike() new GetProfileLike(),
new SetGroupHeader(),
new FetchCustomFace(),
new GoCQHTTPUploadPrivateFile(),
new TestApi01()
]; ];
function initActionMap() { function initActionMap() {
const actionMap = new Map<string, BaseAction<any, any>>(); const actionMap = new Map<string, BaseAction<any, any>>();
for (const action of actionHandlers) { for (const action of actionHandlers) {

View File

@@ -1,13 +1,18 @@
import { NTQQMsgApi } from '@/core/apis'; import { NTQQMsgApi } from '@/core/apis';
import { ActionName } from '../types'; import { ActionName } from '../types';
import BaseAction from '../BaseAction'; import BaseAction from '../BaseAction';
import { dbUtil } from '@/common/utils/db';
import { FromSchema, JSONSchema } from 'json-schema-to-ts'; import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import { MessageUnique } from '@/common/utils/MessageUnique';
const SchemaData = { const SchemaData = {
type: 'object', type: 'object',
properties: { properties: {
message_id: { type: 'number' }, message_id: {
oneOf:[
{ type: 'number' },
{ type: 'string' }
]
}
}, },
required: ['message_id'] required: ['message_id']
} as const satisfies JSONSchema; } as const satisfies JSONSchema;
@@ -18,9 +23,9 @@ class DeleteMsg extends BaseAction<Payload, void> {
actionName = ActionName.DeleteMsg; actionName = ActionName.DeleteMsg;
PayloadSchema = SchemaData; PayloadSchema = SchemaData;
protected async _handle(payload: Payload) { protected async _handle(payload: Payload) {
const msg = await dbUtil.getMsgByShortId(payload.message_id); const msg = await MessageUnique.getMsgIdAndPeerByShortId(Number(payload.message_id));
if (msg) { if (msg) {
await NTQQMsgApi.recallMsg({ peerUid: msg.peerUid, chatType: msg.chatType }, [msg.msgId]); await NTQQMsgApi.recallMsg(msg.Peer, [msg.MsgId]);
} }
} }
} }

View File

@@ -1,16 +1,16 @@
import BaseAction from '../BaseAction'; import BaseAction from '../BaseAction';
import { NTQQMsgApi, NTQQUserApi } from '@/core/apis'; import { NTQQMsgApi, NTQQUserApi } from '@/core/apis';
import { ChatType, Peer } from '@/core/entities'; import { ChatType, Peer } from '@/core/entities';
import { dbUtil } from '@/common/utils/db';
import { ActionName } from '../types'; import { ActionName } from '../types';
import { FromSchema, JSONSchema } from 'json-schema-to-ts'; import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import { MessageUnique } from '@/common/utils/MessageUnique';
const SchemaData = { const SchemaData = {
type: 'object', type: 'object',
properties: { properties: {
message_id: { type: 'number' }, message_id: { type: 'number' },
group_id: { type: [ 'number' , 'string' ] }, group_id: { type: ['number', 'string'] },
user_id: { type: [ 'number' , 'string' ] } user_id: { type: ['number', 'string'] }
}, },
required: ['message_id'] required: ['message_id']
} as const satisfies JSONSchema; } as const satisfies JSONSchema;
@@ -30,18 +30,14 @@ class ForwardSingleMsg extends BaseAction<Payload, null> {
} }
protected async _handle(payload: Payload): Promise<null> { protected async _handle(payload: Payload): Promise<null> {
const msg = await dbUtil.getMsgByShortId(payload.message_id); const msg = await MessageUnique.getMsgIdAndPeerByShortId(payload.message_id);
if (!msg) { if (!msg) {
throw new Error(`无法找到消息${payload.message_id}`); throw new Error(`无法找到消息${payload.message_id}`);
} }
const peer = await this.getTargetPeer(payload); const peer = await this.getTargetPeer(payload);
const ret = await NTQQMsgApi.forwardMsg( const ret = await NTQQMsgApi.forwardMsg(msg.Peer,
{
chatType: msg.chatType,
peerUid: msg.peerUid,
},
peer, peer,
[msg.msgId], [msg.MsgId],
); );
if (ret.result !== 0) { if (ret.result !== 0) {
throw new Error(`转发消息失败 ${ret.errMsg}`); throw new Error(`转发消息失败 ${ret.errMsg}`);

View File

@@ -2,8 +2,9 @@ import { OB11Message } from '../../types';
import { OB11Constructor } from '../../constructor'; import { OB11Constructor } from '../../constructor';
import BaseAction from '../BaseAction'; import BaseAction from '../BaseAction';
import { ActionName } from '../types'; import { ActionName } from '../types';
import { dbUtil } from '@/common/utils/db';
import { FromSchema, JSONSchema } from 'json-schema-to-ts'; import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import { MessageUnique } from '@/common/utils/MessageUnique';
import { NTQQMsgApi } from '@/core';
export type ReturnDataType = OB11Message export type ReturnDataType = OB11Message
@@ -11,7 +12,7 @@ export type ReturnDataType = OB11Message
const SchemaData = { const SchemaData = {
type: 'object', type: 'object',
properties: { properties: {
message_id: { type: ['number','string'] }, message_id: { type: ['number', 'string'] },
}, },
required: ['message_id'] required: ['message_id']
} as const satisfies JSONSchema; } as const satisfies JSONSchema;
@@ -26,14 +27,23 @@ class GetMsg extends BaseAction<Payload, OB11Message> {
if (!payload.message_id) { if (!payload.message_id) {
throw Error('参数message_id不能为空'); throw Error('参数message_id不能为空');
} }
let msg = await dbUtil.getMsgByShortId(parseInt(payload.message_id.toString())); const MsgShortId = await MessageUnique.getShortIdByMsgId(payload.message_id.toString());
if (!msg) { const msgIdWithPeer = await MessageUnique.getMsgIdAndPeerByShortId(MsgShortId || parseInt(payload.message_id.toString()));
msg = await dbUtil.getMsgByLongId(payload.message_id.toString()); if (!msgIdWithPeer) {
}
if (!msg) {
throw ('消息不存在'); throw ('消息不存在');
} }
return await OB11Constructor.message(msg); 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(msg.msgList[0]);
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) {
}
return retMsg;
} }
} }

View File

@@ -5,51 +5,29 @@ import {
Group, Group,
IdMusicSignPostData, IdMusicSignPostData,
NTQQFileApi, NTQQFileApi,
NTQQMsgApi,
SendArkElement, SendArkElement,
SendMessageElement, SendMessageElement,
SendMsgElementConstructor, SendMsgElementConstructor,
SignMusicWrapper SignMusicWrapper
} from '@/core'; } from '@/core';
import { getGroupMember } from '@/core/data'; import { getGroupMember } from '@/core/data';
import { dbUtil } from '@/common/utils/db'; import { logError, logWarn } from '@/common/utils/log';
import { logDebug, logError } from '@/common/utils/log';
import { uri2local } from '@/common/utils/file'; import { uri2local } from '@/common/utils/file';
import { ob11Config } from '@/onebot11/config'; import { ob11Config } from '@/onebot11/config';
import { RequestUtil } from '@/common/utils/request'; import { RequestUtil } from '@/common/utils/request';
import fs from 'node:fs'; import { MessageUnique } from '@/common/utils/MessageUnique';
export type MessageContext = { export type MessageContext = {
group?: Group, group?: Group,
deleteAfterSentFiles: string[], deleteAfterSentFiles: string[],
} }
async function handleOb11FileLikeMessage( async function handleOb11FileLikeMessage(
{ data: { file, name: payloadFileName } }: OB11MessageFileBase, { data: inputdata }: OB11MessageFileBase,
{ deleteAfterSentFiles }: MessageContext { deleteAfterSentFiles }: MessageContext
) { ) {
let uri = file; //有的奇怪的框架将url作为参数 而不是file 此时优先url
const { path, isLocal, fileName, errMsg } = (await uri2local(inputdata?.url || inputdata.file));
const cache = await dbUtil.getFileCacheByName(file);
if (cache) {
if (fs.existsSync(cache.path)) {
uri = 'file://' + cache.path;
} else if (cache.url) {
uri = cache.url;
} else {
const fileMsg = await dbUtil.getMsgByLongId(cache.msgId);
if (fileMsg) {
cache.path = await NTQQFileApi.downloadMedia(
fileMsg.msgId, fileMsg.chatType, fileMsg.peerUid,
cache.elementId, '', ''
);
uri = 'file://' + cache.path;
dbUtil.updateFileCache(cache);
}
}
logDebug('找到文件缓存', uri);
}
const { path, isLocal, fileName, errMsg } = (await uri2local(uri));
if (errMsg) { if (errMsg) {
logError('文件下载失败', errMsg); logError('文件下载失败', errMsg);
@@ -60,7 +38,7 @@ async function handleOb11FileLikeMessage(
deleteAfterSentFiles.push(path); deleteAfterSentFiles.push(path);
} }
return { path, fileName: payloadFileName || fileName }; return { path, fileName: inputdata.name || fileName };
} }
const _handlers: { const _handlers: {
@@ -69,9 +47,9 @@ const _handlers: {
// This picks the correct message type out // This picks the correct message type out
// How great the type system of TypeScript is! // How great the type system of TypeScript is!
context: MessageContext context: MessageContext
) => SendMessageElement | undefined | Promise<SendMessageElement | undefined> ) => Promise<SendMessageElement | undefined>
} = { } = {
[OB11MessageDataType.text]: ({ data: { text } }) => SendMsgElementConstructor.text(text), [OB11MessageDataType.text]: async ({ data: { text } }) => SendMsgElementConstructor.text(text),
[OB11MessageDataType.at]: async ({ data: { qq: atQQ } }, context) => { [OB11MessageDataType.at]: async ({ data: { qq: atQQ } }, context) => {
if (!context.group) return undefined; if (!context.group) return undefined;
@@ -84,17 +62,21 @@ const _handlers: {
SendMsgElementConstructor.at(atQQ, atMember.uid, AtType.atUser, atMember.cardName || atMember.nick) : SendMsgElementConstructor.at(atQQ, atMember.uid, AtType.atUser, atMember.cardName || atMember.nick) :
undefined; undefined;
}, },
[OB11MessageDataType.reply]: async ({ data: { id } }) => { [OB11MessageDataType.reply]: async ({ data: { id } }) => {
const replyMsg = await dbUtil.getMsgByShortId(parseInt(id)); const replyMsgM = MessageUnique.getMsgIdAndPeerByShortId(parseInt(id));
if (!replyMsgM) {
logWarn('回复消息不存在', id);
return undefined;
}
const replyMsg = (await NTQQMsgApi.getMsgsByMsgId(replyMsgM?.Peer!, [replyMsgM?.MsgId!])).msgList[0];
return replyMsg ? return replyMsg ?
SendMsgElementConstructor.reply(replyMsg.msgSeq, replyMsg.msgId, replyMsg.senderUin!, replyMsg.senderUin!) : SendMsgElementConstructor.reply(replyMsg.msgSeq, replyMsg.msgId, replyMsg.senderUin!, replyMsg.senderUin!) :
undefined; undefined;
}, },
[OB11MessageDataType.face]: ({ data: { id } }) => SendMsgElementConstructor.face(parseInt(id)), [OB11MessageDataType.face]: async ({ data: { id } }) => SendMsgElementConstructor.face(parseInt(id)),
[OB11MessageDataType.mface]: ({ [OB11MessageDataType.mface]: async ({
data: { data: {
emoji_package_id, emoji_package_id,
emoji_id, emoji_id,
@@ -144,13 +126,13 @@ const _handlers: {
[OB11MessageDataType.voice]: async (sendMsg, context) => [OB11MessageDataType.voice]: async (sendMsg, context) =>
SendMsgElementConstructor.ptt((await handleOb11FileLikeMessage(sendMsg, context)).path), SendMsgElementConstructor.ptt((await handleOb11FileLikeMessage(sendMsg, context)).path),
[OB11MessageDataType.json]: ({ data: { data } }) => SendMsgElementConstructor.ark(data), [OB11MessageDataType.json]: async ({ data: { data } }) => SendMsgElementConstructor.ark(data),
[OB11MessageDataType.dice]: ({ data: { result } }) => SendMsgElementConstructor.dice(result), [OB11MessageDataType.dice]: async ({ data: { result } }) => SendMsgElementConstructor.dice(result),
[OB11MessageDataType.RPS]: ({ data: { result } }) => SendMsgElementConstructor.rps(result), [OB11MessageDataType.RPS]: async ({ data: { result } }) => SendMsgElementConstructor.rps(result),
[OB11MessageDataType.markdown]: ({ data: { content } }) => SendMsgElementConstructor.markdown(content), [OB11MessageDataType.markdown]: async ({ data: { content } }) => SendMsgElementConstructor.markdown(content),
[OB11MessageDataType.music]: async ({ data }) => { [OB11MessageDataType.music]: async ({ data }) => {
// 保留, 直到...找到更好的解决方案 // 保留, 直到...找到更好的解决方案
@@ -202,13 +184,13 @@ const _handlers: {
} }
}, },
[OB11MessageDataType.node]: () => undefined, [OB11MessageDataType.node]: async () => undefined,
[OB11MessageDataType.forward]: () => undefined, [OB11MessageDataType.forward]: async () => undefined,
[OB11MessageDataType.xml]: () => undefined, [OB11MessageDataType.xml]: async () => undefined,
[OB11MessageDataType.poke]: () => undefined, [OB11MessageDataType.poke]: async () => undefined,
[OB11MessageDataType.Location]: async () => { [OB11MessageDataType.Location]: async () => {
return SendMsgElementConstructor.location(); return SendMsgElementConstructor.location();
@@ -219,7 +201,7 @@ const handlers = <{
[Key in OB11MessageDataType]: ( [Key in OB11MessageDataType]: (
sendMsg: OB11MessageData, sendMsg: OB11MessageData,
context: MessageContext context: MessageContext
) => SendMessageElement | undefined | Promise<SendMessageElement | undefined> ) => Promise<SendMessageElement | undefined>
}>_handlers; }>_handlers;
export default async function createSendElements( export default async function createSendElements(
@@ -227,18 +209,20 @@ export default async function createSendElements(
group?: Group, group?: Group,
ignoreTypes: OB11MessageDataType[] = [] ignoreTypes: OB11MessageDataType[] = []
) { ) {
const sendElements: SendMessageElement[] = [];
const deleteAfterSentFiles: string[] = []; const deleteAfterSentFiles: string[] = [];
const callResultList: Array<Promise<SendMessageElement | undefined>> = [];
for (const sendMsg of messageData) { for (const sendMsg of messageData) {
if (ignoreTypes.includes(sendMsg.type)) { if (ignoreTypes.includes(sendMsg.type)) {
continue; continue;
} }
const callResult = await handlers[sendMsg.type]( const callResult = handlers[sendMsg.type](
sendMsg, sendMsg,
{ group, deleteAfterSentFiles } { group, deleteAfterSentFiles }
); )?.catch(undefined);
if (callResult) sendElements.push(callResult); callResultList.push(callResult);
} }
const ret = await Promise.all(callResultList);
const sendElements: SendMessageElement[] = ret.filter(ele => ele) as SendMessageElement[];
return { sendElements, deleteAfterSentFiles }; return { sendElements, deleteAfterSentFiles };
} }

View File

@@ -1,12 +1,11 @@
import { ChatType, ElementType, Group, NTQQMsgApi, Peer, RawMessage, SendMessageElement } from '@/core'; import { ChatType, ElementType, Group, NTQQMsgApi, Peer, RawMessage, SendMessageElement } from '@/core';
import { OB11MessageNode } from '@/onebot11/types'; import { OB11MessageNode } from '@/onebot11/types';
import { selfInfo } from '@/core/data'; import { selfInfo } from '@/core/data';
import { dbUtil } from '@/common/utils/db';
import createSendElements from '@/onebot11/action/msg/SendMsg/create-send-elements'; import createSendElements from '@/onebot11/action/msg/SendMsg/create-send-elements';
import { logDebug, logError } from '@/common/utils/log'; import { logDebug, logError } from '@/common/utils/log';
import { sleep } from '@/common/utils/helper'; import { sleep } from '@/common/utils/helper';
import fs from 'node:fs';
import { normalize, sendMsg } from '@/onebot11/action/msg/SendMsg/index'; import { normalize, sendMsg } from '@/onebot11/action/msg/SendMsg/index';
import { MessageUnique } from '@/common/utils/MessageUnique';
async function cloneMsg(msg: RawMessage): Promise<RawMessage | undefined> { async function cloneMsg(msg: RawMessage): Promise<RawMessage | undefined> {
const selfPeer = { const selfPeer = {
@@ -14,7 +13,7 @@ async function cloneMsg(msg: RawMessage): Promise<RawMessage | undefined> {
peerUid: selfInfo.uid peerUid: selfInfo.uid
}; };
// logDebug('克隆的目标消息', msg); //logDebug('克隆的目标消息', msg);
const sendElements: SendMessageElement[] = []; const sendElements: SendMessageElement[] = [];
@@ -54,13 +53,14 @@ export async function handleForwardNode(destPeer: Peer, messageNodes: OB11Messag
const nodeId = messageNode.data.id; const nodeId = messageNode.data.id;
// 有nodeId表示一个子转发消息卡片 // 有nodeId表示一个子转发消息卡片
if (nodeId) { if (nodeId) {
const nodeMsg = await dbUtil.getMsgByShortId(parseInt(nodeId)); const nodeMsg = MessageUnique.getPeerByMsgId(nodeId);
if (!needClone) { if (!needClone) {
nodeMsgIds.push(nodeMsg!.msgId); nodeMsgIds.push(nodeMsg!.MsgId);
} else { } else {
if (nodeMsg!.peerUid !== selfInfo.uid) { if (nodeMsg!.Peer.peerUid !== selfInfo.uid) {
// need cloning // need cloning
const clonedMsg = await cloneMsg(nodeMsg!); const rawClone = await NTQQMsgApi.getMsgsByMsgId(nodeMsg?.Peer!, [nodeMsg?.MsgId!]);
const clonedMsg = await cloneMsg(rawClone.msgList[0]);
if (clonedMsg) { if (clonedMsg) {
nodeMsgIds.push(clonedMsg.msgId); nodeMsgIds.push(clonedMsg.msgId);
} }
@@ -91,7 +91,7 @@ export async function handleForwardNode(destPeer: Peer, messageNodes: OB11Messag
//logDebug(sendElementsSplit); //logDebug(sendElementsSplit);
} }
// log("分割后的转发节点", sendElementsSplit) // log("分割后的转发节点", sendElementsSplit)
const MsgNodeList: Promise<RawMessage>[] = []; const MsgNodeList: Promise<RawMessage | undefined>[] = [];
for (const sendElementsSplitElement of sendElementsSplit) { for (const sendElementsSplitElement of sendElementsSplit) {
MsgNodeList.push(sendMsg(selfPeer, sendElementsSplitElement, [], true)); MsgNodeList.push(sendMsg(selfPeer, sendElementsSplitElement, [], true));
await sleep(Math.trunc(sendElementsSplit.length / 10) * 100); await sleep(Math.trunc(sendElementsSplit.length / 10) * 100);
@@ -99,7 +99,9 @@ export async function handleForwardNode(destPeer: Peer, messageNodes: OB11Messag
} }
for (const msgNode of MsgNodeList) { for (const msgNode of MsgNodeList) {
const result = await msgNode; const result = await msgNode;
nodeMsgIds.push(result.msgId); if (result) {
nodeMsgIds.push(result.msgId);
}
//logDebug('转发节点生成成功', result.msgId); //logDebug('转发节点生成成功', result.msgId);
} }
} catch (e) { } catch (e) {
@@ -107,13 +109,14 @@ export async function handleForwardNode(destPeer: Peer, messageNodes: OB11Messag
} }
} }
} }
// 检查srcPeer是否一致不一致则需要克隆成自己的消息, 让所有srcPeer都变成自己的使其保持一致才能够转发 // 检查srcPeer是否一致不一致则需要克隆成自己的消息, 让所有srcPeer都变成自己的使其保持一致才能够转发
const nodeMsgArray: Array<RawMessage> = []; const nodeMsgArray: Array<RawMessage> = [];
let srcPeer: Peer | undefined = undefined; let srcPeer: Peer | undefined = undefined;
let needSendSelf = false; let needSendSelf = false;
for (const msgId of nodeMsgIds) { for (const msgId of nodeMsgIds) {
const nodeMsg = await dbUtil.getMsgByLongId(msgId); const nodeMsgPeer = await MessageUnique.getPeerByMsgId(msgId);
const nodeMsg = (await NTQQMsgApi.getMsgsByMsgId(nodeMsgPeer?.Peer!, [msgId])).msgList[0];
if (nodeMsg) { if (nodeMsg) {
nodeMsgArray.push(nodeMsg); nodeMsgArray.push(nodeMsg);
if (!srcPeer) { if (!srcPeer) {
@@ -124,7 +127,7 @@ export async function handleForwardNode(destPeer: Peer, messageNodes: OB11Messag
} }
} }
} }
//logDebug('nodeMsgArray', nodeMsgArray); // logDebug('nodeMsgArray', nodeMsgArray);
nodeMsgIds = nodeMsgArray.map(msg => msg.msgId); nodeMsgIds = nodeMsgArray.map(msg => msg.msgId);
if (needSendSelf) { if (needSendSelf) {
//logDebug('需要克隆转发消息'); //logDebug('需要克隆转发消息');

View File

@@ -8,13 +8,14 @@ import {
} from '@/onebot11/types'; } from '@/onebot11/types';
import { ActionName, BaseCheckResult } from '@/onebot11/action/types'; import { ActionName, BaseCheckResult } from '@/onebot11/action/types';
import { getGroup } from '@/core/data'; import { getGroup } from '@/core/data';
import { dbUtil } from '@/common/utils/db';
import { ChatType, ElementType, Group, NTQQFileApi, NTQQFriendApi, NTQQMsgApi, NTQQUserApi, Peer, SendMessageElement, } from '@/core'; import { ChatType, ElementType, Group, NTQQFileApi, NTQQFriendApi, NTQQMsgApi, NTQQUserApi, Peer, SendMessageElement, } from '@/core';
import fs from 'node:fs'; import fs from 'node:fs';
import fsPromise from 'node:fs/promises';
import { logDebug, logError } from '@/common/utils/log'; import { logDebug, logError } from '@/common/utils/log';
import { decodeCQCode } from '@/onebot11/cqcode'; import { decodeCQCode } from '@/onebot11/cqcode';
import createSendElements from './create-send-elements'; import createSendElements from './create-send-elements';
import { handleForwardNode } from '@/onebot11/action/msg/SendMsg/handle-forward-node'; import { handleForwardNode } from '@/onebot11/action/msg/SendMsg/handle-forward-node';
import { MessageUnique } from '@/common/utils/MessageUnique';
export interface ReturnDataType { export interface ReturnDataType {
message_id: number; message_id: number;
@@ -40,7 +41,7 @@ export async function sendMsg(peer: Peer, sendElements: SendMessageElement[], de
throw ('消息体无法解析, 请检查是否发送了不支持的消息类型'); throw ('消息体无法解析, 请检查是否发送了不支持的消息类型');
} }
let totalSize = 0; let totalSize = 0;
let timeout = 5000; let timeout = 10000;
try { try {
for (const fileElement of sendElements) { for (const fileElement of sendElements) {
if (fileElement.elementType === ElementType.PTT) { if (fileElement.elementType === ElementType.PTT) {
@@ -59,26 +60,19 @@ export async function sendMsg(peer: Peer, sendElements: SendMessageElement[], de
//且 PredictTime ((totalSize / 1024 / 512) * 1000)不等于Nan //且 PredictTime ((totalSize / 1024 / 512) * 1000)不等于Nan
const PredictTime = totalSize / 1024 / 256 * 1000; const PredictTime = totalSize / 1024 / 256 * 1000;
if (!Number.isNaN(PredictTime)) { if (!Number.isNaN(PredictTime)) {
timeout += PredictTime;// 5S Basic Timeout + PredictTime( For File 512kb/s ) timeout += PredictTime;// 10S Basic Timeout + PredictTime( For File 512kb/s )
} }
} catch (e) { } catch (e) {
logError('发送消息计算预计时间异常', e); logError('发送消息计算预计时间异常', e);
} }
const returnMsg = await NTQQMsgApi.sendMsg(peer, sendElements, waitComplete, timeout); const returnMsg = await NTQQMsgApi.sendMsg(peer, sendElements, waitComplete, timeout);
try { try {
returnMsg.id = await dbUtil.addMsg(returnMsg, false); returnMsg!.id = await MessageUnique.createMsg({ chatType: peer.chatType, guildId: '', peerUid: peer.peerUid }, returnMsg!.msgId);
} catch (e: any) { } catch (e: any) {
logDebug('发送消息id获取失败', e); logDebug('发送消息id获取失败', e);
returnMsg.id = 0; returnMsg!.id = 0;
} }
deleteAfterSentFiles.map((f) => { fsPromise.unlink(f).then().catch(e => logError('发送消息删除文件失败', e)); });
deleteAfterSentFiles.map((f) => {
try {
fs.unlinkSync(f);
} catch (e) {
logError('发送消息删除文件失败', e);
}
});
return returnMsg; return returnMsg;
} }
@@ -134,7 +128,7 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
return { valid: false, message: `${payload.group_id}不存在` }; return { valid: false, message: `${payload.group_id}不存在` };
} }
if (payload.user_id && payload.message_type !== 'group') { if (payload.user_id && payload.message_type !== 'group') {
const uid = await NTQQUserApi.getUidByUin(payload.user_id); const uid = await NTQQUserApi.getUidByUin(payload.user_id.toString());
const isBuddy = await NTQQFriendApi.isBuddy(uid!); const isBuddy = await NTQQFriendApi.isBuddy(uid!);
// 此处有问题 // 此处有问题
if (!isBuddy) { if (!isBuddy) {
@@ -155,8 +149,8 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
if (getSpecialMsgNum(payload, OB11MessageDataType.node)) { if (getSpecialMsgNum(payload, OB11MessageDataType.node)) {
const returnMsg = await handleForwardNode(peer, messages as OB11MessageNode[], group); const returnMsg = await handleForwardNode(peer, messages as OB11MessageNode[], group);
if (returnMsg) { if (returnMsg) {
const msgShortId = await dbUtil.addMsg(returnMsg!, false); const msgShortId = await MessageUnique.createMsg({ guildId: '', peerUid: peer.peerUid, chatType: peer.chatType }, returnMsg!.msgId);
return { message_id: msgShortId }; return { message_id: msgShortId! };
} else { } else {
throw Error('发送转发消息失败'); throw Error('发送转发消息失败');
} }
@@ -172,7 +166,7 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
const { sendElements, deleteAfterSentFiles } = await createSendElements(messages, group); const { sendElements, deleteAfterSentFiles } = await createSendElements(messages, group);
//console.log(peer, JSON.stringify(sendElements,null,2)); //console.log(peer, JSON.stringify(sendElements,null,2));
const returnMsg = await sendMsg(peer, sendElements, deleteAfterSentFiles); const returnMsg = await sendMsg(peer, sendElements, deleteAfterSentFiles);
return { message_id: returnMsg.id! }; return { message_id: returnMsg!.id! };
} }
} }

View File

@@ -1,14 +1,14 @@
import { ActionName } from '../types'; import { ActionName } from '../types';
import BaseAction from '../BaseAction'; import BaseAction from '../BaseAction';
import { dbUtil } from '@/common/utils/db';
import { NTQQMsgApi } from '@/core/apis'; import { NTQQMsgApi } from '@/core/apis';
import { FromSchema, JSONSchema } from 'json-schema-to-ts'; import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import { MessageUnique } from '@/common/utils/MessageUnique';
const SchemaData = { const SchemaData = {
type: 'object', type: 'object',
properties: { properties: {
message_id: { type: ['string','number'] }, message_id: { type: ['string', 'number'] },
emoji_id: { type: ['string','number'] } emoji_id: { type: ['string', 'number'] }
}, },
required: ['message_id', 'emoji_id'] required: ['message_id', 'emoji_id']
} as const satisfies JSONSchema; } as const satisfies JSONSchema;
@@ -19,16 +19,17 @@ export class SetMsgEmojiLike extends BaseAction<Payload, any> {
actionName = ActionName.SetMsgEmojiLike; actionName = ActionName.SetMsgEmojiLike;
PayloadSchema = SchemaData; PayloadSchema = SchemaData;
protected async _handle(payload: Payload) { protected async _handle(payload: Payload) {
const msg = await dbUtil.getMsgByShortId(parseInt(payload.message_id.toString())); const msg = MessageUnique.getMsgIdAndPeerByShortId(parseInt(payload.message_id.toString()));
if (!msg) { if (!msg) {
throw new Error('msg not found'); throw new Error('msg not found');
} }
if (!payload.emoji_id){ if (!payload.emoji_id) {
throw new Error('emojiId not found'); throw new Error('emojiId not found');
} }
return await NTQQMsgApi.setEmojiLike({ const msgData = (await NTQQMsgApi.getMsgsByMsgId(msg.Peer, [msg.MsgId])).msgList;
chatType: msg.chatType, if (!msgData || msgData.length == 0 || !msgData[0].msgSeq) {
peerUid: msg.peerUid throw new Error('find msg by msgid error');
}, msg.msgSeq, payload.emoji_id.toString(), true); }
return await NTQQMsgApi.setEmojiLike(msg.Peer, msgData[0].msgSeq, payload.emoji_id.toString(), true);
} }
} }

View File

@@ -7,7 +7,6 @@ import {
ChatCacheListItemBasic, ChatCacheListItemBasic,
CacheFileType CacheFileType
} from '@/core/entities'; } from '@/core/entities';
import { dbUtil } from '@/common/utils/db';
import { NTQQFileApi, NTQQFileCacheApi } from '@/core/apis/file'; import { NTQQFileApi, NTQQFileCacheApi } from '@/core/apis/file';
import { logError } from '@/common/utils/log'; import { logError } from '@/common/utils/log';

View File

@@ -94,9 +94,13 @@ export enum ActionName {
CreateCollection = 'create_collection', CreateCollection = 'create_collection',
GetCollectionList = 'get_collection_list', GetCollectionList = 'get_collection_list',
SetLongNick = 'set_self_longnick', SetLongNick = 'set_self_longnick',
SetEssenceMsg = "set_essence_msg", SetEssenceMsg = 'set_essence_msg',
DelEssenceMsg = "delete_essence_msg", DelEssenceMsg = 'delete_essence_msg',
GetRecentContact = "get_recent_contact", GetRecentContact = 'get_recent_contact',
_MarkAllMsgAsRead = "_mark_all_as_read", _MarkAllMsgAsRead = '_mark_all_as_read',
GetProfileLike = "get_profile_like" GetProfileLike = 'get_profile_like',
SetGroupHeader = 'set_group_head',
FetchCustomFace = 'fetch_custom_face',
GOCQHTTP_UploadPrivateFile = 'upload_private_file',
TestApi01 = 'test_api_01'
} }

View File

@@ -5,6 +5,7 @@ import BaseAction from '../BaseAction';
import { ActionName } from '../types'; import { ActionName } from '../types';
import { NTQQFriendApi } from '@/core'; import { NTQQFriendApi } from '@/core';
import { FromSchema, JSONSchema } from 'json-schema-to-ts'; import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import { requireMinNTQQBuild } from '@/common/utils/QQBasicInfo';
// no_cache get时传字符串 // no_cache get时传字符串
@@ -20,6 +21,10 @@ export default class GetFriendList extends BaseAction<Payload, OB11User[]> {
actionName = ActionName.GetFriendList; actionName = ActionName.GetFriendList;
PayloadSchema = SchemaData; PayloadSchema = SchemaData;
protected async _handle(payload: Payload) { protected async _handle(payload: Payload) {
if (requireMinNTQQBuild('26702')) {
//全新逻辑
return OB11Constructor.friendsV2(await NTQQFriendApi.getBuddyV2(payload?.no_cache === true || payload?.no_cache === 'true'));
}
if (friends.size === 0 || payload?.no_cache === true || payload?.no_cache === 'true') { if (friends.size === 0 || payload?.no_cache === true || payload?.no_cache === 'true') {
const _friends = await NTQQFriendApi.getFriends(true); const _friends = await NTQQFriendApi.getFriends(true);
// log('强制刷新好友列表,结果: ', _friends) // log('强制刷新好友列表,结果: ', _friends)

View File

@@ -6,6 +6,7 @@ import { NTQQUserApi } from '@/core';
export default class GetRecentContact extends BaseAction<void, any> { export default class GetRecentContact extends BaseAction<void, any> {
actionName = ActionName.GetRecentContact; actionName = ActionName.GetRecentContact;
protected async _handle(payload: void) { protected async _handle(payload: void) {
return await NTQQUserApi.getRecentContactList() //没有效果
return await NTQQUserApi.getRecentContactListSnapShot(10);
} }
} }

View File

@@ -42,6 +42,7 @@ export interface OB11Config {
} }
class Config extends ConfigBase<OB11Config> implements OB11Config { class Config extends ConfigBase<OB11Config> implements OB11Config {
name: string = 'onebot11';
http = { http = {
enable: false, enable: false,
host: '', host: '',
@@ -72,10 +73,6 @@ class Config extends ConfigBase<OB11Config> implements OB11Config {
RecordList: [] as Array<string> RecordList: [] as Array<string>
}; };
getConfigPath() {
return path.join(this.getConfigDir(), `onebot11_${selfInfo.uin}.json`);
}
protected getKeys(): string[] | null { protected getKeys(): string[] | null {
return null; return null;
} }

View File

@@ -12,30 +12,32 @@ import {
import { import {
AtType, AtType,
ChatType, ChatType,
ElementType, FaceIndex, FaceIndex,
Friend, Friend,
FriendV2,
GrayTipElementSubType, GrayTipElementSubType,
Group, Group,
GroupMember, GroupMember,
IMAGE_HTTP_HOST, IMAGE_HTTP_HOST_NT, mFaceCache, mFaceCache,
Peer, Peer,
RawMessage, RawMessage,
SelfInfo, SelfInfo,
Sex, Sex,
SimpleInfo,
TipGroupElementType, TipGroupElementType,
User User,
VideoElement
} from '@/core/entities'; } from '@/core/entities';
import { EventType } from './event/OB11BaseEvent'; import { EventType } from './event/OB11BaseEvent';
import { encodeCQCode } from './cqcode'; import { encodeCQCode } from './cqcode';
import { dbUtil } from '@/common/utils/db';
import { OB11GroupIncreaseEvent } from './event/notice/OB11GroupIncreaseEvent'; import { OB11GroupIncreaseEvent } from './event/notice/OB11GroupIncreaseEvent';
import { OB11GroupBanEvent } from './event/notice/OB11GroupBanEvent'; import { OB11GroupBanEvent } from './event/notice/OB11GroupBanEvent';
import { OB11GroupUploadNoticeEvent } from './event/notice/OB11GroupUploadNoticeEvent'; import { OB11GroupUploadNoticeEvent } from './event/notice/OB11GroupUploadNoticeEvent';
import { OB11GroupNoticeEvent } from './event/notice/OB11GroupNoticeEvent'; import { OB11GroupNoticeEvent } from './event/notice/OB11GroupNoticeEvent';
import { calcQQLevel } from '../common/utils/qqlevel'; import { calcQQLevel } from '../common/utils/qqlevel';
import { log, logDebug, logError } from '../common/utils/log'; import { log, logDebug, logError, logWarn } from '../common/utils/log';
import { sleep } from '../common/utils/helper'; import { sleep, UUIDConverter } from '../common/utils/helper';
import { OB11GroupTitleEvent } from './event/notice/OB11GroupTitleEvent'; import { OB11GroupTitleEvent } from './event/notice/OB11GroupTitleEvent';
import { OB11GroupCardEvent } from './event/notice/OB11GroupCardEvent'; import { OB11GroupCardEvent } from './event/notice/OB11GroupCardEvent';
import { OB11GroupDecreaseEvent } from './event/notice/OB11GroupDecreaseEvent'; import { OB11GroupDecreaseEvent } from './event/notice/OB11GroupDecreaseEvent';
@@ -43,10 +45,11 @@ import { ob11Config } from '@/onebot11/config';
import { deleteGroup, getGroupMember, groupMembers, selfInfo, tempGroupCodeMap } from '@/core/data'; import { deleteGroup, getGroupMember, groupMembers, selfInfo, tempGroupCodeMap } from '@/core/data';
import { NTQQFileApi, NTQQGroupApi, NTQQMsgApi, NTQQUserApi } from '@/core/apis'; import { NTQQFileApi, NTQQGroupApi, NTQQMsgApi, NTQQUserApi } from '@/core/apis';
import { OB11GroupMsgEmojiLikeEvent } from '@/onebot11/event/notice/OB11MsgEmojiLikeEvent'; import { OB11GroupMsgEmojiLikeEvent } from '@/onebot11/event/notice/OB11MsgEmojiLikeEvent';
import { napCatCore } from '@/core';
import { OB11FriendPokeEvent, OB11GroupPokeEvent } from './event/notice/OB11PokeEvent'; import { OB11FriendPokeEvent, OB11GroupPokeEvent } from './event/notice/OB11PokeEvent';
import { OB11FriendAddNoticeEvent } from './event/notice/OB11FriendAddNoticeEvent';
import { OB11BaseNoticeEvent } from './event/notice/OB11BaseNoticeEvent'; import { OB11BaseNoticeEvent } from './event/notice/OB11BaseNoticeEvent';
import { OB11GroupEssenceEvent } from './event/notice/OB11GroupEssenceEvent'; import { OB11GroupEssenceEvent } from './event/notice/OB11GroupEssenceEvent';
import { MessageUnique } from '@/common/utils/MessageUnique';
export class OB11Constructor { export class OB11Constructor {
@@ -76,7 +79,12 @@ export class OB11Constructor {
if (msg.chatType == ChatType.group) { if (msg.chatType == ChatType.group) {
resMsg.sub_type = 'normal'; // 这里go-cqhttp是group而onebot11标准是normal, 蛋疼 resMsg.sub_type = 'normal'; // 这里go-cqhttp是group而onebot11标准是normal, 蛋疼
resMsg.group_id = parseInt(msg.peerUin); resMsg.group_id = parseInt(msg.peerUin);
const member = await getGroupMember(msg.peerUin, msg.senderUin!); let member = await getGroupMember(msg.peerUin, msg.senderUin!);
if (!member) {
//直接去QQNative取
const memberList = await NTQQGroupApi.getGroupMembers(msg.peerUin);
member = memberList.get(msg.senderUin!);
}
if (member) { if (member) {
resMsg.sender.role = OB11Constructor.groupMemberRole(member.role); resMsg.sender.role = OB11Constructor.groupMemberRole(member.role);
resMsg.sender.nickname = member.nick; resMsg.sender.nickname = member.nick;
@@ -84,8 +92,9 @@ export class OB11Constructor {
} }
else if (msg.chatType == ChatType.friend) { else if (msg.chatType == ChatType.friend) {
resMsg.sub_type = 'friend'; resMsg.sub_type = 'friend';
const user = await NTQQUserApi.getUserDetailInfoByUin(msg.senderUin!); resMsg.sender.nickname = (await NTQQUserApi.getUserDetailInfo(msg.senderUid)).nick;
resMsg.sender.nickname = user.info.nick; //const user = await NTQQUserApi.getUserDetailInfoByUin(msg.senderUin!);
//resMsg.sender.nickname = user.info.nick;
} }
else if (msg.chatType == ChatType.temp) { else if (msg.chatType == ChatType.temp) {
resMsg.sub_type = 'group'; resMsg.sub_type = 'group';
@@ -95,33 +104,40 @@ export class OB11Constructor {
} }
} }
for (const element of msg.elements) { for (const element of msg.elements) {
const message_data: OB11MessageData | any = { let message_data: OB11MessageData = {
data: {}, data: {} as any,
type: 'unknown' type: 'unknown' as any
}; };
if (element.textElement && element.textElement?.atType !== AtType.notAt) { if (element.textElement && element.textElement?.atType !== AtType.notAt) {
message_data['type'] = OB11MessageDataType.at; let qq: `${number}` | 'all';
let name: string | undefined;
if (element.textElement.atType == AtType.atAll) { if (element.textElement.atType == AtType.atAll) {
// message_data["data"]["mention"] = "all" qq = 'all';
message_data['data']['qq'] = 'all';
} }
else { else {
const atUid = element.textElement.atNtUid; const { atNtUid, content } = element.textElement;
let atQQ = element.textElement.atUid; let atQQ = element.textElement.atUid;
if (!atQQ || atQQ === '0') { if (!atQQ || atQQ === '0') {
const atMember = await getGroupMember(msg.peerUin, atUid); const atMember = await getGroupMember(msg.peerUin, atNtUid);
if (atMember) { if (atMember) {
atQQ = atMember.uin; atQQ = atMember.uin;
} }
} }
if (atQQ) { if (atQQ) {
// message_data["data"]["mention"] = atQQ qq = atQQ as `${number}`;
message_data['data']['qq'] = atQQ; name = content.replace('@', '');
} }
} }
message_data = {
type: OB11MessageDataType.at,
data: {
qq: qq!,
name
}
};
} }
else if (element.textElement) { else if (element.textElement) {
message_data['type'] = 'text'; message_data['type'] = OB11MessageDataType.text;
let text = element.textElement.content; let text = element.textElement.content;
if (!text.trim()) { if (!text.trim()) {
@@ -134,106 +150,142 @@ export class OB11Constructor {
message_data['data']['text'] = text; message_data['data']['text'] = text;
} }
else if (element.replyElement) { else if (element.replyElement) {
message_data['type'] = 'reply'; message_data['type'] = OB11MessageDataType.reply;
// log("收到回复消息", element.replyElement.replayMsgSeq) //log("收到回复消息", element.replyElement);
try { try {
// let retData = await NTQQMsgApi.getMsgsBySeqAndCount( //做这么多都是因为NC速度太快 可能nt还没有写入数据库
// { //const records = msg.records.find(msgRecord => msgRecord.msgId === element.replyElement.sourceMsgIdInRecords);
// chatType: msg.chatType, const peer = {
// peerUid: msg.peerUid, chatType: msg.chatType,
// guildId: '', peerUid: msg.peerUid,
// }, guildId: '',
// element.replyElement.replayMsgSeq, };
// 1, let replyMsg: RawMessage | undefined;
// false,
// true replyMsg = (await NTQQMsgApi.getMsgsBySeqAndCount({ peerUid: msg.peerUid, guildId: '', chatType: msg.chatType }, element.replyElement.replayMsgSeq, 1, true, true)).msgList[0];
// ); if (!replyMsg || element.replyElement.replayMsgSeq !== replyMsg.msgSeq) {
// console.log(JSON.stringify(retData, null, 2)); replyMsg = (await NTQQMsgApi.getSingleMsg(peer, element.replyElement.replayMsgSeq)).msgList[0];
const replyMsg = await dbUtil.getMsgBySeq(msg.peerUid, element.replyElement.replayMsgSeq);
// log("找到回复消息", replyMsg.msgShortId, replyMsg.msgId)
if (replyMsg && replyMsg.id) {
message_data['data']['id'] = replyMsg.id!.toString();
} }
else {
continue; if (!replyMsg || element.replyElement.replayMsgSeq !== replyMsg.msgSeq) {
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) { } catch (e: any) {
message_data['type'] = 'unknown' as any;
message_data['data'] = undefined;
logError('获取不到引用的消息', e.stack, element.replyElement.replayMsgSeq); logError('获取不到引用的消息', e.stack, element.replyElement.replayMsgSeq);
} }
} }
else if (element.picElement) { else if (element.picElement) {
message_data['type'] = 'image'; message_data['type'] = OB11MessageDataType.image;
// message_data["data"]["file"] = element.picElement.sourcePath // message_data["data"]["file"] = element.picElement.sourcePath
message_data['data']['file'] = element.picElement.fileName; message_data['data']['file'] = element.picElement.fileName;
message_data['subType'] = element.picElement.picSubType; 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 // message_data["data"]["path"] = element.picElement.sourcePath
// let currentRKey = "CAQSKAB6JWENi5LMk0kc62l8Pm3Jn1dsLZHyRLAnNmHGoZ3y_gDZPqZt-64"
try { try {
message_data['data']['url'] = await NTQQFileApi.getImageUrl(element.picElement, msg.chatType !== ChatType.group); message_data['data']['url'] = await NTQQFileApi.getImageUrl(element.picElement);
} catch (e: any) { } catch (e: any) {
logError('获取图片url失败', e.stack); logError('获取图片url失败', e.stack);
} }
//console.log(message_data['data']['url']) //console.log(message_data['data']['url'])
// message_data["data"]["file_id"] = element.picElement.fileUuid // message_data["data"]["file_id"] = element.picElement.fileUuid
message_data['data']['file_size'] = element.picElement.fileSize; message_data['data']['file_size'] = element.picElement.fileSize;
dbUtil.addFileCache({
name: element.picElement.fileName,
path: element.picElement.sourcePath,
size: element.picElement.fileSize,
url: message_data['data']['url'],
uuid: element.picElement.fileUuid || '',
msgId: msg.msgId,
element: element.picElement,
elementType: ElementType.PIC,
elementId: element.elementId
}).then();
// 不自动下载图片
} }
else if (element.videoElement || element.fileElement) { else if (element.fileElement) {
const videoOrFileElement = element.videoElement || element.fileElement; const FileElement = element.fileElement;
const ob11MessageDataType = element.videoElement ? OB11MessageDataType.video : OB11MessageDataType.file; message_data['type'] = OB11MessageDataType.file;
const videoDownUrl = element.videoElement ? await NTQQFileApi.getVideoUrl(msg, element) : videoOrFileElement.filePath; message_data['data']['file'] = FileElement.fileName;
message_data['type'] = ob11MessageDataType; message_data['data']['path'] = FileElement.filePath;
message_data['data']['file'] = videoOrFileElement.fileName; 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
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) => { if (url.url) { return true; } return false; });
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']['path'] = videoDownUrl;
message_data['data']['url'] = videoDownUrl; message_data['data']['url'] = videoDownUrl;
message_data['data']['file_id'] = videoOrFileElement.fileUuid; message_data['data']['file_id'] = UUIDConverter.encode(msg.peerUin, msg.msgId);
message_data['data']['file_size'] = videoOrFileElement.fileSize; message_data['data']['file_size'] = videoElement.fileSize;
if (!element.videoElement) {
dbUtil.addFileCache({ await NTQQFileApi.addFileCache({
msgId: msg.msgId, peerUid: msg.peerUid,
name: videoOrFileElement.fileName, chatType: msg.chatType,
path: videoOrFileElement.filePath, guildId: '',
size: parseInt(videoOrFileElement.fileSize || '0'), },
uuid: videoOrFileElement.fileUuid || '', msg.msgId,
url: '', msg.msgSeq,
element: element.videoElement || element.fileElement, msg.senderUid,
elementType: element.videoElement ? ElementType.VIDEO : ElementType.FILE, element.elementId,
elementId: element.elementId element.elementType.toString(),
}).then(); videoElement.fileSize || '0',
} videoElement.fileName
);
} }
else if (element.pttElement) { else if (element.pttElement) {
message_data['type'] = OB11MessageDataType.voice; message_data['type'] = OB11MessageDataType.voice;
message_data['data']['file'] = element.pttElement.fileName; message_data['data']['file'] = element.pttElement.fileName;
message_data['data']['path'] = element.pttElement.filePath; message_data['data']['path'] = element.pttElement.filePath;
// message_data["data"]["file_id"] = element.pttElement.fileUuid //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; message_data['data']['file_size'] = element.pttElement.fileSize;
dbUtil.addFileCache({ await NTQQFileApi.addFileCache({
name: element.pttElement.fileName, peerUid: msg.peerUid,
path: element.pttElement.filePath, chatType: msg.chatType,
size: parseInt(element.pttElement.fileSize) || 0, guildId: '',
url: '', },
uuid: element.pttElement.fileUuid || '', msg.msgId,
msgId: msg.msgId, msg.msgSeq,
element: element.pttElement, msg.senderUid,
elementType: ElementType.PTT, element.elementId,
elementId: element.elementId element.elementType.toString(),
}).then(); element.pttElement.fileSize || '0',
element.pttElement.fileUuid || ''
);
//以uuid作为文件名
} }
else if (element.arkElement) { else if (element.arkElement) {
message_data['type'] = OB11MessageDataType.json; message_data['type'] = OB11MessageDataType.json;
@@ -277,14 +329,16 @@ export class OB11Constructor {
message_data['type'] = OB11MessageDataType.forward; message_data['type'] = OB11MessageDataType.forward;
message_data['data']['id'] = msg.msgId; message_data['data']['id'] = msg.msgId;
} }
if (message_data.type !== 'unknown' && message_data.data) { if ((message_data.type as string) !== 'unknown' && message_data.data) {
const cqCode = encodeCQCode(message_data); const cqCode = encodeCQCode(message_data);
if (messagePostFormat === 'string') { if (messagePostFormat === 'string') {
(resMsg.message as string) += cqCode; (resMsg.message as string) += cqCode;
} }
else (resMsg.message as OB11MessageData[]).push(message_data); else (resMsg.message as OB11MessageData[]).push(message_data);
resMsg.raw_message += cqCode; resMsg.raw_message += cqCode;
} }
} }
resMsg.raw_message = resMsg.raw_message.trim(); resMsg.raw_message = resMsg.raw_message.trim();
return resMsg; return resMsg;
@@ -297,6 +351,7 @@ export class OB11Constructor {
if (element.grayTipElement) { if (element.grayTipElement) {
if (element.grayTipElement.subElementType == GrayTipElementSubType.MEMBER_NEW_TITLE) { if (element.grayTipElement.subElementType == GrayTipElementSubType.MEMBER_NEW_TITLE) {
const json = JSON.parse(element.grayTipElement.jsonGrayTipElement.jsonStr); const json = JSON.parse(element.grayTipElement.jsonGrayTipElement.jsonStr);
if (element.grayTipElement.jsonGrayTipElement.busiId == 1061) { if (element.grayTipElement.jsonGrayTipElement.busiId == 1061) {
//判断业务类型 //判断业务类型
//Poke事件 //Poke事件
@@ -305,11 +360,17 @@ export class OB11Constructor {
pokedetail = pokedetail.filter(item => 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)); //console.log("[NapCat] 群拍一拍 群:", pokedetail, parseInt(msg.peerUid), " ", await NTQQUserApi.getUinByUid(pokedetail[0].uid), "拍了拍", await NTQQUserApi.getUinByUid(pokedetail[1].uid));
if (pokedetail.length == 2) { if (pokedetail.length == 2) {
return new OB11FriendPokeEvent(parseInt((await NTQQUserApi.getUinByUid(pokedetail[0].uid))!), parseInt((await NTQQUserApi.getUinByUid(pokedetail[1].uid))!)); return new OB11FriendPokeEvent(parseInt((await NTQQUserApi.getUinByUid(pokedetail[0].uid))!), parseInt((await NTQQUserApi.getUinByUid(pokedetail[1].uid))!), pokedetail);
} }
} }
//下面得改 上面也是错的grayTipElement.subElementType == GrayTipElementSubType.MEMBER_NEW_TITLE //下面得改 上面也是错的grayTipElement.subElementType == GrayTipElementSubType.MEMBER_NEW_TITLE
} }
if (element.grayTipElement.subElementType == GrayTipElementSubType.INVITE_NEW_MEMBER) {
//好友添加成功事件
if (element.grayTipElement.xmlElement.templId === '10229' && msg.peerUin !== '') {
return new OB11FriendAddNoticeEvent(parseInt(msg.peerUin));
}
}
} }
} }
} }
@@ -408,11 +469,12 @@ export class OB11Constructor {
const senderUin = emojiLikeData.gtip.qq.jp; const senderUin = emojiLikeData.gtip.qq.jp;
const msgSeq = emojiLikeData.gtip.url.msgseq; const msgSeq = emojiLikeData.gtip.url.msgseq;
const emojiId = emojiLikeData.gtip.face.id; const emojiId = emojiLikeData.gtip.face.id;
const replyMsg = await dbUtil.getMsgBySeq(msg.peerUid, msgSeq); const replyMsgList = (await NTQQMsgApi.getMsgsBySeqAndCount({ chatType: ChatType.group, guildId: '', peerUid: msg.peerUid }, msgSeq, 1, true, true)).msgList;
if (!replyMsg) { if (replyMsgList.length < 1) {
return; return;
} }
return new OB11GroupMsgEmojiLikeEvent(parseInt(msg.peerUid), parseInt(senderUin), replyMsg.id!, [{ const replyMsg = replyMsgList[0];
return new OB11GroupMsgEmojiLikeEvent(parseInt(msg.peerUid), parseInt(senderUin), MessageUnique.getShortIdByMsgId(replyMsg?.msgId!)!, [{
emoji_id: emojiId, emoji_id: emojiId,
count: 1 count: 1
}]); }]);
@@ -453,17 +515,17 @@ export class OB11Constructor {
} }
} }
if (grayTipElement.jsonGrayTipElement.busiId == 2401) { if (grayTipElement.jsonGrayTipElement.busiId == 2401) {
let searchParams = new URL(json.items[0].jp).searchParams; const searchParams = new URL(json.items[0].jp).searchParams;
let msgSeq = searchParams.get('msgSeq')!; const msgSeq = searchParams.get('msgSeq')!;
let Group = searchParams.get('groupCode'); const Group = searchParams.get('groupCode');
let Businessid = searchParams.get('businessid'); const Businessid = searchParams.get('businessid');
let Peer: Peer = { const Peer: Peer = {
guildId: '', guildId: '',
chatType: ChatType.group, chatType: ChatType.group,
peerUid: Group! peerUid: Group!
}; };
let msgData = await NTQQMsgApi.getMsgsBySeqAndCount(Peer, msgSeq.toString(), 1, true, true); const msgData = await NTQQMsgApi.getMsgsBySeqAndCount(Peer, msgSeq.toString(), 1, true, true);
return new OB11GroupEssenceEvent(parseInt(msg.peerUid), await dbUtil.addMsg(msgData.msgList[0]), parseInt(msgData.msgList[0].senderUin)); return new OB11GroupEssenceEvent(parseInt(msg.peerUid), MessageUnique.getShortIdByMsgId(msgData.msgList[0].msgId)!, parseInt(msgData.msgList[0].senderUin));
// 获取MsgSeq+Peer可获取具体消息 // 获取MsgSeq+Peer可获取具体消息
} }
if (grayTipElement.jsonGrayTipElement.busiId == 2407) { if (grayTipElement.jsonGrayTipElement.busiId == 2407) {
@@ -493,7 +555,24 @@ export class OB11Constructor {
nickname: selfInfo.nick, 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,
categroyName: friend.categroyName,
categoryId: friend.categoryId
});
});
return data;
}
static friends(friends: Friend[]): OB11User[] { static friends(friends: Friend[]): OB11User[] {
const data: OB11User[] = []; const data: OB11User[] = [];
friends.forEach(friend => { friends.forEach(friend => {

View File

@@ -67,15 +67,15 @@ export function encodeCQCode(data: OB11MessageData) {
let result = '[CQ:' + data.type; let result = '[CQ:' + data.type;
for (const name in data.data) { for (const name in data.data) {
const value = data.data[name]; const value = data.data[name];
try { if (value === undefined) {
// Check if the value can be converted to a string
value.toString();
} catch (error) {
// If it can't be converted, skip this name-value pair
// console.warn(`Skipping problematic name-value pair. Name: ${name}, Value: ${value}`);
continue; continue;
} }
result += `,${name}=${CQCodeEscape(value)}`; try {
const text = value.toString();
result += `,${name}=${CQCodeEscape(text)}`;
} catch (error) {
// If it can't be converted, skip this name-value pair
}
} }
result += ']'; result += ']';
return result; return result;

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