Compare commits

...

161 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
挂神
a29b1154a9 feat: WebUI支持放置到二级目录中 2024-07-26 11:12:09 +08:00
88 changed files with 3032 additions and 991 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
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
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:
1.1. "Contributor"
means each individual or legal entity that creates, contributes to
the creation of, or owns Covered Software.
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
1.2. "Contributor Version"
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
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
1.3. "Contribution"
means Covered Software of a particular Contributor.
1.4. "Covered Software"
means Source Code Form to which the initial Contributor has attached
the notice in Exhibit A, the Executable Form of such Source Code
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,20 +0,0 @@
# v1.6.8
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. WebUi配置热重载优化 ~ 修复问题
4. 修复偶现崩溃问题 ~ 修复问题
## 新增与调整
1. 最后发言时间重构 入群时间失效 ~ 替换功能
新增的 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

@@ -2,7 +2,7 @@
"name": "napcat",
"private": true,
"type": "module",
"version": "1.6.8",
"version": "1.7.8",
"scripts": {
"watch:dev": "vite --mode development",
"watch:prod": "vite --mode production",
@@ -31,7 +31,7 @@
"@types/figlet": "^1.5.8",
"@types/fluent-ffmpeg": "^2.1.24",
"@types/jest": "^29.5.12",
"@types/node": "^20.11.30",
"@types/node": "^22.0.0",
"@types/qrcode-terminal": "^0.12.2",
"@types/ws": "^8.5.10",
"@typescript-eslint/eslint-plugin": "^7.4.0",

View File

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

@@ -86,7 +86,7 @@ export abstract class HttpServerBase {
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) {
if (!url.startsWith('/')) {
@@ -111,7 +111,7 @@ export abstract class HttpServerBase {
try {
res.send(await handler(res, payload));
} catch (e: any) {
this.handleFailed(res, payload, e.stack.toString());
this.handleFailed(res, payload, e);
}
});
}

View File

@@ -32,17 +32,17 @@ export class WebsocketServerBase {
if (port instanceof http.Server) {
try {
const wss = new WebSocketServer({
noServer: true,
maxPayload: 1024 * 1024 * 1024
}).on('error', () => {
});
noServer: true,
maxPayload: 1024 * 1024 * 1024
}).on('error', () => {
});
this.ws = wss;
port.on('upgrade', function upgrade(request, socket, head) {
wss.handleUpgrade(request, socket, head, function done(ws) {
wss.emit('connection', ws, request);
});
});
log(`ws服务启动成功, 绑定到HTTP服务`);
log('ws服务启动成功, 绑定到HTTP服务');
} catch (e: any) {
throw Error('ws服务启动失败, 可能是绑定的HTTP服务异常' + e.toString());
}

View File

@@ -14,8 +14,8 @@ fs.mkdirSync(configDir, { recursive: true });
export class ConfigBase<T> {
public name: string = 'default_config'
private pathName: string | null = null // 本次读取的文件路径
public name: string = 'default_config';
private pathName: string | null = null; // 本次读取的文件路径
constructor() {
}
@@ -30,21 +30,21 @@ export class ConfigBase<T> {
return configDir;
}
getConfigPath(pathName: string | null): string {
const suffix = pathName ? `_${pathName}` : ''
const filename = `${this.name}${suffix}.json`
const suffix = pathName ? `_${pathName}` : '';
const filename = `${this.name}${suffix}.json`;
return path.join(this.getConfigDir(), filename);
}
read() {
// 尝试加载当前账号配置
if (this.read_from_file(selfInfo.uin, false)) return this
if (this.read_from_file(selfInfo.uin, false)) return this;
// 尝试加载默认配置
return this.read_from_file('', true)
return this.read_from_file('', true);
}
read_from_file(pathName: string, createIfNotExist: boolean) {
const configPath = this.getConfigPath(pathName);
if (!fs.existsSync(configPath)) {
if (!createIfNotExist) return null
this.pathName = pathName // 记录有效的设置文件
if (!createIfNotExist) return null;
this.pathName = pathName; // 记录有效的设置文件
try {
fs.writeFileSync(configPath, JSON.stringify(this, this.getKeys(), 2));
log(`配置文件${configPath}已创建\n如果修改此文件后需要重启 NapCat 生效`);
@@ -77,7 +77,7 @@ export class ConfigBase<T> {
Object.assign(this, config);
if (overwrite) {
// 用户要求强制写入,则变更当前文件为目标文件
this.pathName = `${selfInfo.uin}`
this.pathName = `${selfInfo.uin}`;
}
const configPath = this.getConfigPath(this.pathName);
try {

View File

@@ -84,7 +84,7 @@ export class NTEventWrapper {
}
//统一回调清理事件
async DispatcherListener(ListenerMainName: string, ListenerSubName: string, ...args: any[]) {
//console.log(ListenerMainName, ListenerSubName, ...args,this.EventTask.get(ListenerMainName)?.get(ListenerSubName));
//console.log("[EventDispatcher]",ListenerMainName, ListenerSubName, ...args);
this.EventTask.get(ListenerMainName)?.get(ListenerSubName)?.forEach((task, uuid) => {
//console.log(task.func, uuid, task.createtime, task.timeout);
if (task.createtime + task.timeout < Date.now()) {
@@ -96,7 +96,7 @@ export class NTEventWrapper {
}
});
}
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) => {
const EventFunc = this.CreatEventFunction<EventType>(EventName);
let complete = false;
@@ -110,8 +110,47 @@ export class NTEventWrapper {
resolve(retData);
});
}
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>) {
(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) => {
const id = randomUUID();
let complete = 0;
@@ -119,16 +158,19 @@ export class NTEventWrapper {
let retEvent: any = {};
const databack = () => {
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 {
resolve([retEvent as Awaited<ReturnType<EventType>>, ...retData!]);
}
};
const Timeouter = setTimeout(databack, timeout);
const ListenerNameList = ListenerName.split('/');
const ListenerMainName = ListenerNameList[0];
const ListenerSubName = ListenerNameList[1];
const Timeouter = setTimeout(databack, timeout);
const eventCallbak = {
timeout: timeout,
createtime: Date.now(),
@@ -152,7 +194,6 @@ export class NTEventWrapper {
this.EventTask.get(ListenerMainName)?.get(ListenerSubName)?.set(id, eventCallbak);
this.CreatListenerFunction(ListenerMainName);
const EventFunc = this.CreatEventFunction<EventType>(EventName);
//console.log("测试打点", args);
retEvent = await EventFunc!(...(args as any[]));
});
}

View File

@@ -2,7 +2,7 @@ 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 valueToKey: Map<V, K> = new Map();
private maxSize: number;
@@ -15,10 +15,10 @@ class LimitedHashTable<K, V> {
}
set(key: K, value: V): void {
const isExist = this.keyToValue.get(key);
if (isExist && isExist === value) {
return;
}
// const isExist = this.keyToValue.get(key);
// if (isExist && isExist === value) {
// return;
// }
this.keyToValue.set(key, value);
this.valueToKey.set(value, key);
while (this.keyToValue.size !== this.valueToKey.size) {
@@ -61,6 +61,24 @@ class LimitedHashTable<K, V> {
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 {
@@ -70,16 +88,24 @@ class MessageUniqueWrapper {
this.msgIdMap = new LimitedHashTable<string, number>(maxMap);
this.msgDataMap = new LimitedHashTable<string, number>(maxMap);
}
getRecentMsgIds(Peer: Peer, size: number): string[] {
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);
}
createMsg(peer: Peer, msgId: string): number | undefined {
const key = `${msgId}|${peer.chatType}|${peer.peerUid}`;
const hash = crypto.createHash('sha1').update(key);
const shortId = parseInt(hash.digest('hex').slice(0, 8), 16);
const isExist = this.msgIdMap.getKey(shortId);
//console.log(`${peer.peerUid} ${msgId} ------- ${shortId}`);
if (isExist && isExist === msgId) {
return shortId;
}
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;

View File

@@ -1,62 +1,49 @@
import path from 'node:path';
import fs from 'node:fs';
import os from 'node:os';
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');
} else {
const userPath = os.homedir();
const appDataPath = path.resolve(userPath, './.config/QQ');
configVersionInfoPath = path.resolve(appDataPath, './versions/config.json');
//基础函数
export function getQQBuildStr() {
return isQuickUpdate ? QQVersionConfig.buildId : QQPackageInfo.buildVersion;
}
if (typeof configVersionInfoPath !== 'string') {
throw new Error('Something went wrong when load QQ info path');
export function getFullQQVesion() {
return isQuickUpdate ? QQVersionConfig.curVersion : QQPackageInfo.version;
}
export { configVersionInfoPath };
type QQPkgInfo = {
version: string;
buildVersion: string;
platform: string;
eleArch: string;
export function requireMinNTQQBuild(buildStr: string) {
return parseInt(getQQBuildStr()) >= parseInt(buildStr);
}
type QQVersionConfigInfo = {
baseVersion: string;
curVersion: string;
prevVersion: string;
onErrorVersions: Array<any>;
buildId: string;
//此方法不要直接使用
export function getQUAInternal() {
return systemPlatform === 'linux' ? `V1_LNX_NQ_${getFullQQVesion()}_${getQQBuildStr()}_GW_B` : `V1_WIN_NQ_${getFullQQVesion()}_${getQQBuildStr()}_GW_B`;
}
let _qqVersionConfigInfo: QQVersionConfigInfo = {
'baseVersion': '9.9.12-25765',
'curVersion': '9.9.12-25765',
'prevVersion': '',
'onErrorVersions': [],
'buildId': '25765'
};
if (fs.existsSync(configVersionInfoPath)) {
export function getAppidV2(): { appid: string, qua: string } {
let appidTbale = AppidTable as unknown as QQAppidTableType;
try {
const _ =JSON.parse(fs.readFileSync(configVersionInfoPath).toString());
_qqVersionConfigInfo = Object.assign(_qqVersionConfigInfo, _);
} catch (e) {
logError('Load QQ version config info failed, Use default version', e);
let data = appidTbale[getFullQQVesion()];
if (data) {
return data;
}
}
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,
// app_type: 4,
// app_version: '9.9.12-25765',
@@ -64,14 +51,6 @@ export const qqPkgInfo: QQPkgInfo = JSON.parse(fs.readFileSync(pkgInfoPath).toSt
// appid: '537234702',
// platVer: '10.0.26100',
// clientVer: '9.9.9-25765',
// Linux
// app_version: '3.2.9-25765',
// 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

@@ -1,20 +1,42 @@
import crypto from 'node:crypto';
import path from 'node:path';
import fs from 'fs/promises';
import fs from 'fs';
import { log, logDebug } from './log';
import { dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
import * as fsPromise from 'node:fs/promises';
import os from 'node:os';
const __filename = fileURLToPath(import.meta.url);
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> {
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)
setTimeout(() => reject(new Error('PromiseTimer: Operation timed out')), ms)
);
return Promise.race([promise, timeoutPromise]);
}
@@ -283,14 +305,14 @@ export function migrateConfig(oldConfig: any) {
}
// 升级旧的配置到新的
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) {
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)) {
log('正在迁移旧配置到新配置 File:', file);
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));
}
}
}
@@ -310,7 +332,56 @@ export function isEqual(obj1: any, obj2: any) {
}
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) {
try {
const files = await fsPromise.readdir(directoryPath);

View File

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

View File

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

View File

@@ -3,7 +3,7 @@ import {
CacheFileType,
ChatCacheListItemBasic,
ChatType,
ElementType, IMAGE_HTTP_HOST, IMAGE_HTTP_HOST_NT, RawMessage
ElementType, IMAGE_HTTP_HOST, IMAGE_HTTP_HOST_NT, Peer, PicElement, RawMessage
} from '@/core/entities';
import path from 'path';
import fs from 'fs';
@@ -18,6 +18,8 @@ import { sessionConfig } from '@/core/sessionConfig';
import { rkeyManager } from '../utils/rkey';
import { NTEventDispatch } from '@/common/utils/EventTask';
import { NodeIKernelSearchService } from '../services/NodeIKernelSearchService';
import { selfInfo } from '../data';
export class NTQQFileApi {
static async getFileType(filePath: string) {
@@ -31,12 +33,8 @@ export class NTQQFileApi {
static async getFileSize(filePath: string): Promise<number> {
return await napCatCore.util.getFileSize(filePath);
}
static async getVideoUrl(msg: RawMessage, element: any) {
return (await napCatCore.session.getRichMediaService().getVideoPlayUrlV2({
chatType: msg.chatType,
peerUid: msg.peerUid,
guildId: '0'
}, msg.msgId, element.elementId, 0, { downSourceType: 1, triggerType: 1 })).urlResult.domainUrl[0].url;
static async getVideoUrl(peer: Peer, msgId: string, elementId: string) {
return (await napCatCore.session.getRichMediaService().getVideoPlayUrlV2(peer, msgId, elementId, 0, { downSourceType: 1, triggerType: 1 })).urlResult.domainUrl;
}
// 上传文件到QQ的文件夹
static async uploadFile(filePath: string, elementType: ElementType = ElementType.PIC, elementSubType: number = 0) {
@@ -70,7 +68,9 @@ export class NTQQFileApi {
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) {
//logDebug('receive downloadMedia task', msgId, chatType, peerUid, elementId, thumbPath, sourcePath, timeout, force);
// 用于下载收到的消息中的图片等
@@ -146,8 +146,65 @@ export class NTQQFileApi {
});
});
}
static async searchfile(keys: string[]) {
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;
}
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,
@@ -187,22 +244,23 @@ export class NTQQFileApi {
}[]
}[]
};
const [id, data] = await NTEventDispatch.CallNormalEvent<EventType, (params: OnListener) => void>(
'NodeIKernelSearchService/searchFileWithKeywords',
'NodeIKernelSearchListener/onSearchFileKeywordsResult',
1,
10000,
(arg): boolean => { return id == data.searchId },
keys,
12
);
return data.resultItems[0];
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: { originImageUrl: any; md5HexStr?: any; fileUuid: any; }) {
static async getImageUrl(element: PicElement) {
if (!element) {
return '';
}
const url: string = element.originImageUrl; // 没有域名
const url: string = element.originImageUrl!; // 没有域名
const md5HexStr = element.md5HexStr;
const fileMd5 = element.md5HexStr;
const fileUuid = element.fileUuid;

View File

@@ -1,11 +1,66 @@
import { FriendRequest, User } from '@/core/entities';
import { napCatCore, OnBuddyChangeParams } from '@/core';
import { FriendRequest, FriendV2, SimpleInfo, User } from '@/core/entities';
import { BuddyListReqType, napCatCore, NodeIKernelBuddyListener, NodeIKernelProfileService, OnBuddyChangeParams } from '@/core';
import { NTEventDispatch } from '@/common/utils/EventTask';
import { LimitedHashTable } from '@/common/utils/MessageUnique';
import { CacheClassFuncAsyncExtend } from '@/common/utils/helper';
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) {
return napCatCore.session.getBuddyService().isBuddy(uid);
}
/**
* @deprecated
* @param forced
* @returns
*/
static async getFriends(forced = false): Promise<User[]> {
let [_retData, _BuddyArg] = await NTEventDispatch.CallNormalEvent
<(force: boolean) => Promise<any>, (arg: OnBuddyChangeParams) => void>

View File

@@ -1,5 +1,5 @@
import { GroupMember, GroupRequestOperateTypes, GroupMemberRole, GroupNotify, Group, MemberExtSourceType, GroupNotifyTypes, ChatType } from '../entities';
import { GeneralCallResult, NTQQUserApi, napCatCore } from '@/core';
import { GroupMember, GroupRequestOperateTypes, GroupMemberRole, GroupNotify, Group, MemberExtSourceType, GroupNotifyTypes, ChatType, Peer, GroupListUpdateType } from '../entities';
import { GeneralCallResult, NTQQUserApi, NodeIKernelGroupListener, NodeIKernelGroupService, napCatCore } from '@/core';
import { NTEventDispatch } from '@/common/utils/EventTask';
import { log } from '@/common/utils/log';
import { groupMembers } from '../data';
@@ -9,24 +9,24 @@ export class NTQQGroupApi {
return napCatCore.session.getGroupService().setHeader(gc, filePath);
}
static async getGroups(forced = false) {
type ListenerType = NodeIKernelGroupListener['onGroupListUpdate'];
let [_retData, _updateType, groupList] = await NTEventDispatch.CallNormalEvent
<(force: boolean) => Promise<any>, (updateType: number, groupList: Group[]) => void>
<(force: boolean) => Promise<any>, ListenerType>
(
'NodeIKernelGroupService/getGroupList',
'NodeIKernelGroupListener/onGroupListUpdate',
1,
5000,
() => true,
(updateType) => true,
forced
);
return groupList;
}
@CacheClassFuncAsyncExtend(600, "LastestSendTime", () => true)
@CacheClassFuncAsyncExtend(3600 * 1000, "LastestSendTime", () => true)
static async getGroupMemberLastestSendTimeCache(GroupCode: string) {
return NTQQGroupApi.getGroupMemberLastestSendTime(GroupCode);
}
/**
* 通过QQ自带数据库获取群成员最后发言时间(仅返回有效数据 且消耗延迟大 需要进行缓存)
* @param GroupCode 群号
@@ -81,17 +81,15 @@ export class NTQQGroupApi {
});
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) {
try {
let uid = await NTQQUserApi.getUidByUin(uin)
if (uid) {
uids.push(uid);
}
} catch (error) {
log("getLastestMsg--->", error);
return undefined;
let uid = await NTQQUserApi.getUidByUin(uin)
if (uid) {
uids.push(uid);
}
}
let ret = await napCatCore.session.getMsgService().queryMsgsWithFilterEx('0', '0', '0', {
@@ -160,6 +158,25 @@ export class NTQQGroupApi {
);
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>> {
const groupService = napCatCore.session.getGroupService();
const sceneId = groupService.createMemberListScene(groupQQ, 'groupMemberList_MainWindow');
@@ -167,6 +184,7 @@ export class NTQQGroupApi {
if (result.errCode !== 0) {
throw ('获取群成员列表出错,' + result.errMsg);
}
//logDebug(`获取群(${groupQQ})成员列表结果:`, `finish: ${result.result.finish}`); //, Array.from(result.result.infos.values()));
return result.result.infos;
/*

View File

@@ -1,4 +1,4 @@
import { GetFileListParam, Peer, RawMessage, SendMessageElement } from '@/core/entities';
import { GetFileListParam, Peer, RawMessage, SendMessageElement, SendMsgElementConstructor } from '@/core/entities';
import { friends, groups, selfInfo } from '@/core/data';
import { log, logWarn } from '@/common/utils/log';
import { sleep } from '@/common/utils/helper';
@@ -7,6 +7,8 @@ import { onGroupFileInfoUpdateParamType } from '@/core/listeners';
import { GeneralCallResult } from '@/core/services/common';
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) {
let msgList = await NTQQMsgApi.getMsgHistory(Peer, msgId, 50);
for (let j = 0; j < msgList.msgList.length; j++) {
@@ -15,13 +17,14 @@ async function LoadMessageIdList(Peer: Peer, msgId: string) {
}
async function loadMessageUnique() {
if (groups.size > 100) {
logWarn('群数量大于100可能会导致性能问题');
logWarn('[性能检测] 群数量大于100可能会导致性能问题');
}
let predict = (groups.size + friends.size) / 5;
let predict = (groups.size + friends.size / 2) / 5;
predict = predict < 20 ? 20 : predict;
predict = predict > 50 ? 50 : predict;
//let waitpromise: Array<Promise<{ msgList: RawMessage[]; }>> = [];
MessageUnique.resize(predict * 50);
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) {
@@ -31,16 +34,37 @@ async function loadMessageUnique() {
}
}
await Promise.all(LoadMessageIdDo).then(() => {
log(`[消息序列] 加载 ${predict * 50} 条历史消息记录完成`);
log(`[消息序列] 加载 ${predict} 条历史消息记录完成`);
});
}
setTimeout(() => {
napCatCore.onLoginSuccess(async () => {
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);
//歇菜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 {
// static napCatCore: NapCatCore | null = null;
@@ -63,10 +87,41 @@ export class NTQQMsgApi {
} | undefined> {
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[]) {
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) {
return await napCatCore.session.getMsgService().getMsgsBySeqAndCount(peer, seq, count, desc, z);
}
@@ -78,7 +133,7 @@ export class NTQQMsgApi {
(GroupCode: string, params: GetFileListParam) => Promise<unknown>,
(groupFileListResult: onGroupFileInfoUpdateParamType) => void
>(
'NodeIKernelRichMediaService/sendMsg',
'NodeIKernelRichMediaService/getGroupFileList',
'NodeIKernelMsgListener/onGroupFileInfoUpdate',
1,
5000,
@@ -100,8 +155,20 @@ export class NTQQMsgApi {
peerUid: peer.peerUid
}, msgIds);
}
static async sendMsg(peer: Peer, msgElements: SendMessageElement[], waitComplete = true, timeout = 10000) {
let msgId = await NTQQMsgApi.getMsgUnique(await NTQQMsgApi.getServerTime());
static async sendMsgV2(peer: Peer, msgElements: SendMessageElement[], waitComplete = true, timeout = 10000) {
// function generateMsgId() {
// const timestamp = Math.floor(Date.now() / 1000);
// const random = Math.floor(Math.random() * Math.pow(2, 32));
// const buffer = Buffer.alloc(8);
// buffer.writeUInt32BE(timestamp, 0);
// buffer.writeUInt32BE(random, 4);
// const msgId = BigInt("0x" + buffer.toString('hex')).toString();
// return msgId;
// }
// 此处有采用Hack方法 利用数据返回正确得到对应消息
// 与之前 Peer队列 MsgSeq队列 真正的MsgId并发不同
// 谨慎采用 目前测试暂无问题 Developer.Mlikiowa
let msgId = await NTQQMsgApi.getMsgUnique(peer.chatType, await NTQQMsgApi.getServerTime());
let data = await NTEventDispatch.CallNormalEvent<
(msgId: string, peer: Peer, msgElements: SendMessageElement[], map: Map<any, any>) => Promise<unknown>,
(msgList: RawMessage[]) => void
@@ -130,16 +197,53 @@ export class NTQQMsgApi {
});
return retMsg;
}
static async getMsgUniqueEx(){
let msgId = await NTQQMsgApi.getMsgUnique(await NTQQMsgApi.getServerTime());
return msgId;
static sendMsgEx(peer: Peer, msgElements: SendMessageElement[], waitComplete = true, timeout = 10000) {
//return NTQQMsgApi.sendMsgV1(peer, msgElements, waitComplete, timeout);
}
static async getMsgUnique(time: string) {
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[]) {
return napCatCore.session.getMsgService().forwardMsg(msgIds, srcPeer, [destPeer], new Map());
}

View File

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

View File

@@ -1,11 +1,7 @@
import { NTEventDispatch } from '@/common/utils/EventTask';
import { GeneralCallResult, NTQQUserApi, napCatCore } from '@/core';
// setTimeout(async () => {
// let ret = await NTQQSystemApi.getArkJsonCollection('1-2-162b9b42-65b9-4405-a8ed-2e256ec8aa50');
// console.log(ret);
// }, 20000)
import { GeneralCallResult, NTQQFileApi, NTQQUserApi, napCatCore } from '@/core';
export class NTQQSystemApi {
static async hasOtherRunningQQProcess() {
return napCatCore.util.hasOtherRunningQQProcess();

View File

@@ -1,12 +1,13 @@
import { ModifyProfileParams, SelfInfo, User, UserDetailInfoByUin } from '@/core/entities';
import { friends, selfInfo } from '@/core/data';
import { ModifyProfileParams, SelfInfo, User, UserDetailInfoByUin, UserDetailInfoByUinV2 } from '@/core/entities';
import { friends, groupMembers, selfInfo } from '@/core/data';
import { CacheClassFuncAsync, CacheClassFuncAsyncExtend } from '@/common/utils/helper';
import { napCatCore } from '@/core';
import { napCatCore, NTQQFriendApi } from '@/core';
import { NodeIKernelProfileListener, ProfileListener } from '@/core/listeners';
import { RequestUtil } from '@/common/utils/request';
import { logWarn } from '@/common/utils/log';
import { NTEventDispatch } from '@/common/utils/EventTask';
import { NodeIKernelProfileService } from '@/core/services';
import { NodeIKernelProfileService, ProfileBizType, UserDetailSource } from '@/core/services';
import { requireMinNTQQBuild } from '@/common/utils/QQBasicInfo';
export class NTQQUserApi {
static async getProfileLike(uid: string) {
@@ -50,21 +51,87 @@ export class NTQQUserApi {
return napCatCore.session.getGroupService().setHeader(gc, filePath);
}
static async getSelfInfo() {
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;
}
static async getUserInfo(uid: string) {
static async fetchUserDetailInfo(uid: string) {
type EventService = NodeIKernelProfileService['fetchUserDetailInfo'];
type EventListener = NodeIKernelProfileListener['onUserDetailInfoChanged'];
let [_retData, profile] = await NTEventDispatch.CallNormalEvent
<EventService, EventListener>
(
'NodeIKernelProfileService/fetchUserDetailInfo',
'NodeIKernelProfileListener/onUserDetailInfoChanged',
1,
5000,
(profile) => {
if (profile.uid === uid) {
return true;
}
return false;
},
"BuddyProfileStore",
[
uid
],
UserDetailSource.KSERVER,
[
ProfileBizType.KALL
]
);
let RetUser: User = {
...profile.simpleInfo.coreInfo,
...profile.simpleInfo.status,
...profile.simpleInfo.vasInfo,
...profile.commonExt,
...profile.simpleInfo.baseInfo,
qqLevel: profile.commonExt.qqLevel,
pendantId: ""
};
return RetUser;
}
// enum ProfileBizType {
// KALL,
// KBASEEXTEND,
// KVAS,
// KQZONE,
// KOTHER
// }
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
@@ -135,38 +202,90 @@ export class NTQQUserApi {
}
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) {
return true
}
logWarn("uin转换到uid时异常", Uin);
logWarn("uin转换到uid时异常", Uin, Uid);
return false;
})
static async getUidByUin(Uin: string) {
let ret = await NTEventDispatch.CallNoListenerEvent
<(Uin: string[]) => Promise<{ uidInfo: Map<string, string> }>>(
'NodeIKernelUixConvertService/getUid',
5000,
[Uin]
);
let uid = ret.uidInfo.get(Uin); //通过QQ默认方式转换
//此代码仅临时使用,后期会被废弃
if (requireMinNTQQBuild('26702')) {
return await NTQQUserApi.getUidByUinV2(Uin);
}
return await NTQQUserApi.getUidByUinV1(Uin);
}
@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) {
Array.from(friends.values()).forEach((t) => {
if (t.uin == Uin) {
//logDebug('getUidByUin', t.uid, t.uin, Uin);
uid = t.uid;
}
//console.log(t.uid, t.uin, Uin);
});
//uid = Array.from(friends.values()).find((t) => { t.uin == Uin })?.uid; // 从NC维护的QQ Buddy缓存 转换
}
// if (!uid) {
// uid = (await NTQQFriendApi.getFriends(false)).find((t) => { t.uin == Uin })?.uid; //从QQ Native 缓存转换 方法一
// }
// if (!uid) {
// uid = (await NTQQFriendApi.getFriends(true)).find((t) => { t.uin == Uin })?.uid; //从QQ Native 非缓存转换 方法二
// }
//Uid 群友列表转
if (!uid) {
for (let groupMembersList of groupMembers.values()) {
for (let GroupMember of groupMembersList.values()) {
if (GroupMember.uin == Uin) {
uid = GroupMember.uid;
}
}
}
}
if (!uid) {
let unveifyUid = (await NTQQUserApi.getUserDetailInfoByUin(Uin)).info.uid;//从QQ Native 特殊转换 方法三
if (unveifyUid.indexOf("*") == -1) {
@@ -175,17 +294,7 @@ export class NTQQUserApi {
}
return uid;
}
@CacheClassFuncAsyncExtend(3600, 'Uid2Uin', (Uid: string | undefined, Uin: number | undefined) => {
if (Uin && Uin != 0 && !isNaN(Uin)) {
return true
}
logWarn("uid转换到uin时异常", Uid);
return false;
})
static async getUinByUid(Uid: string | undefined) {
if (!Uid) {
return '';
}
static async getUinByUidV1(Uid: string) {
let ret = await NTEventDispatch.CallNoListenerEvent
<(Uin: string[]) => Promise<{ uinInfo: Map<string, string> }>>(
'NodeIKernelUixConvertService/getUin',
@@ -225,6 +334,14 @@ export class NTQQUserApi {
static async 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) {
return NTEventDispatch.CallNoListenerEvent
<(Uin: string) => Promise<UserDetailInfoByUin>>(

View File

@@ -114,6 +114,22 @@ export interface GroupEssenceMsgRet {
}
}
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')
static async getGroupEssenceMsg(GroupCode: string, page_start: string) {
const CookiesObject = await NTQQUserApi.getCookies('qun.qq.com');

View File

@@ -15,12 +15,12 @@ import { DependsAdapter, DispatcherAdapter, GlobalAdapter, NodeIGlobalAdapter }
import path from 'node:path';
import os from 'node:os';
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 { genSessionConfig } from '@/core/sessionConfig';
import { sleep } from '@/common/utils/helper';
import crypto from 'node:crypto';
import { rawFriends, friends, groupMembers, groups, selfInfo, stat } from '@/core/data';
import { groupMembers, groups, selfInfo, stat } from '@/core/data';
import { GroupMember, RawMessage } from '@/core/entities';
import { NTEventDispatch } from '@/common/utils/EventTask';
import {
@@ -33,6 +33,7 @@ import {
setLogSelfInfo
} from '@/common/utils/log';
import { napCatConfig } from '@/core/utils/config';
import { NTQQFriendApi } from './apis';
export interface OnLoginSuccess {
(uin: string, uid: string): void | Promise<void>;
@@ -84,7 +85,6 @@ export class NapCatCore {
const dataPath = path.resolve(this.dataPath, './NapCat/data');
fs.mkdirSync(dataPath, { recursive: true });
logDebug('本账号数据/缓存目录:', dataPath);
// dbUtil.init(path.resolve(dataPath, `./${arg.uin}-v2.db`)).then(() => {
this.initDataListener();
this.onLoginSuccessFuncList.map(cb => {
new Promise((resolve, reject) => {
@@ -94,11 +94,6 @@ export class NapCatCore {
}
}).then();
});
// }).catch((e) => {
// logError('数据库初始化失败', e);
// });
// this.initDataListener();
}).catch((e) => {
logError('initSession failed', e);
throw new Error(`启动失败: ${JSON.stringify(e)}`);
@@ -139,10 +134,10 @@ export class NapCatCore {
base_path_prefix: '',
platform_type: 3,
app_type: 4,
app_version: qqVersionConfigInfo.curVersion,
app_version: getFullQQVesion(),
os_version: 'Windows 10 Pro',
use_xlog: true,
qua: `V1_WIN_NQ_${qqVersionConfigInfo.curVersion.replace('-', '_')}_GW_B`,
qua: QQVersionQua,
global_path_config: {
desktopGlobalPath: this.dataPathGlobal,
},
@@ -150,10 +145,10 @@ export class NapCatCore {
}, new QQWrapper.NodeIGlobalAdapter(new GlobalAdapter()));
this.loginService.initConfig({
machineId: '',
appid,
appid: QQVersionAppid,
platVer: systemVersion,
commonPath: this.dataPathGlobal,
clientVer: qqVersionConfigInfo.curVersion,
clientVer: getFullQQVesion(),
hostName: hostname
});
}
@@ -258,31 +253,23 @@ export class NapCatCore {
stat.packet_received += 1;
};
this.addListener(msgListener);
// 好友相关
// 好友相关
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.session.getBuddyService().getBuddyList(true).then(arg => {
// console.log('getBuddyList', arg);
});
// 刷新一次好友列表 26702版本以下需要手动刷新一次获取 高版本NTQQ自带缓存
if (!requireMinNTQQBuild('26702')) {
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 {
uid: string
status: number
@@ -335,6 +322,7 @@ export class NapCatCore {
if (groupMembers.has(groupCode)) {
const existMembers = groupMembers.get(groupCode)!;
arg.infos.forEach((member, uid) => {
//console.log('onMemberListChange', member);
const existMember = existMembers.get(uid);
if (existMember) {
Object.assign(existMember, member);

View File

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

View File

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

View File

@@ -55,22 +55,24 @@ export enum ElementType {
TASKTOPMSG = 29,
RECOMMENDEDMSG = 43,
ACTIONBAR = 44
}
export interface ActionBarElement {
rows: InlineKeyboardRow[];
botAppid: string;
}
export interface SendActionBarElement {
elementType: ElementType.ACTIONBAR;
elementId: string;
actionBarElement: {
rows: InlineKeyboardRow[];
botAppid: string;
}
actionBarElement: ActionBarElement;
}
export interface RecommendedMsgElement {
rows: InlineKeyboardRow[];
botAppid: string;
}
export interface SendRecommendedMsgElement {
elementType: ElementType.RECOMMENDEDMSG;
elementId: string;
recommendedMsgElement: {
rows: InlineKeyboardRow[];
botAppid: string;
}
recommendedMsgElement: RecommendedMsgElement;
}
export interface InlineKeyboardButton {
id: string;
@@ -93,67 +95,72 @@ export interface TofuElementContent {
color: string;
tittle: string;
}
export interface TaskTopMsgElement {
msgTitle: string;
msgSummary: string;
iconUrl: string;
topMsgType: number;
}
export interface SendTaskTopMsgElement {
elementType: ElementType.TASKTOPMSG;
elementId: string;
taskTopMsgElement: {
msgTitle: string;
msgSummary: string;
iconUrl: string;
topMsgType: number;
}
taskTopMsgElement: TaskTopMsgElement;
}
export interface TofuRecordElement {
type: 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 {
elementType: ElementType.TOFURECORD;
elementId: string;
tofuRecordElement: {
type: 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;
tofuRecordElement: TofuRecordElement;
}
export interface 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 SendFaceBubbleElement {
elementType: ElementType.FACEBUBBLE;
elementId: string;
faceBubbleElement: {
faceCount: number;
faceSummary: string;
faceFlag: number;
content: string;
oldVersionStr: string;
faceType: number;
others: string;
yellowFaceInfo: {
index: number;
buf: string;
compatibleText: string;
text: string;
}
}
faceBubbleElement: FaceBubbleElement;
}
export interface AvRecordElement {
type: number;
time: string;
text: string;
mainType: number;
hasRead: boolean;
extraType: number;
}
export interface SendavRecordElement {
elementType: ElementType.AVRECORD;
elementId: string;
avRecordElement: {
type: number;
time: string;
text: string;
mainType: number;
hasRead: boolean;
extraType: number;
}
avRecordElement: AvRecordElement;
}
export interface YoloUserInfo {
uid: string;
@@ -170,37 +177,40 @@ export interface SendInlineKeyboardElement {
}
}
export interface YoloGameResultElement {
UserInfo: YoloUserInfo[];
}
export interface SendYoloGameResultElement {
elementType: ElementType.YOLOGAMERESULT;
yoloGameResultElement: {
UserInfo: YoloUserInfo[];
}
yoloGameResultElement: YoloGameResultElement
}
export interface GiphyElement {
id: string;
isClip: boolean;
width: number;
height: number;
}
export interface SendGiphyElement {
elementType: ElementType.GIPHY;
elementId: string;
giphyElement: {
id: string;
isClip: boolean;
width: number;
height: number;
}
giphyElement: GiphyElement;
}
export interface SendWalletElement {
elementType: ElementType.UNKNOWN;//不做 设置位置
elementId: string;
walletElement: {}
}
export interface CalendarElement {
summary: string;
msg: string;
expireTimeMs: string;
schemaType: number;
schema: string
}
export interface SendCalendarElement {
elementType: ElementType.CALENDAR;
elementId: string;
calendarElement: {
summary: string;
msg: string;
expireTimeMs: string;
schemaType: number;
schema: string
}
calendarElement: CalendarElement;
}
export interface SendliveGiftElement {
elementType: ElementType.LIVEGIFT;
@@ -252,32 +262,18 @@ export enum PicSubType {
export interface SendPicElement {
elementType: ElementType.PIC;
elementId: string;
picElement: {
md5HexStr: string;
fileSize: number | string;
picWidth: number;
picHeight: number;
fileName: string;
sourcePath: string;
original: boolean;
picType: PicType;
picSubType: PicSubType;
fileUuid: string;
fileSubId: string;
thumbFileSize: number;
summary: string;
};
picElement:PicElement
}
export interface ReplyElement {
replayMsgSeq: string;
replayMsgId: string;
senderUin: string;
senderUinStr: string;
}
export interface SendReplyElement {
elementType: ElementType.REPLY;
elementId: string;
replyElement: {
replayMsgSeq: string;
replayMsgId: string;
senderUin: string;
senderUinStr: string;
}
replyElement: ReplyElement
}
export interface SendFaceElement {
@@ -293,10 +289,11 @@ export interface SendMarketFaceElement {
export interface SendstructLongMsgElement {
elementType: ElementType.STRUCTLONGMSG;
elementId: string;
structLongMsgElement: {
xmlContent: string;
resId: string;
}
structLongMsgElement: StructLongMsgElement;
}
export interface StructLongMsgElement {
xmlContent: string;
resId: string;
}
export interface SendactionBarElement {
elementType: ElementType.ACTIONBAR;
@@ -306,13 +303,14 @@ export interface SendactionBarElement {
botAppid: string;
}
}
export interface ShareLocationElement {
text: string;
ext: string;
}
export interface sendShareLocationElement {
elementType: ElementType.SHARELOCATION;
elementId: string;
shareLocationElement: {
text: string;
ext: string;
}
shareLocationElement?: ShareLocationElement;
}
export interface FileElement {
@@ -356,10 +354,51 @@ export interface SendMarkdownElement {
elementId: string;
markdownElement: MarkdownElement;
}
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 {
notAt = 0,
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 interface PicElement {
picSubType?: number;
originImageUrl: string; // http url, 没有hosthost是https://gchat.qpic.cn/, 带download参数的是https://multimedia.nt.qq.com.cn
originImageMd5?: string;
sourcePath: string; // 图片本地路径
thumbPath: Map<number, string>;
md5HexStr?: string;
fileSize: number | string;//number
picWidth: number;
picHeight: number;
fileSize: number;
fileName: string;
sourcePath: string;
original: boolean;
picType: PicType;
picSubType?: PicSubType;
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 {
INVITE_NEW_MEMBER = 12,
@@ -687,6 +731,7 @@ export interface MultiForwardMsgElement {
}
export interface RawMessage {
guildId: string;
msgRandom: string;
// int32, 自己维护的消息id
id?: number;
@@ -708,10 +753,12 @@ export interface RawMessage {
chatType: ChatType;
sendStatus?: number; // 消息状态别人发的2是已撤回自己发的2是已发送
recallTime: string; // 撤回时间, "0"是没有撤回
records: RawMessage[];
elements: {
elementId: string;
elementType: ElementType;
replyElement: {
sourceMsgIdInRecords: string;
senderUid: string; // 原消息发送者QQ号
sourceMsgIsIncPic: boolean; // 原消息是否有图片
sourceMsgText: string;

View File

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

View File

@@ -9,7 +9,181 @@ export interface BuddyCategoryType {
categroyMbCount: number;
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 {
nick: string,
longNick: string,
@@ -101,7 +275,18 @@ export interface Friend extends User { }
export enum BizKey {
KPRIVILEGEICON,
KPHOTOWALL
}
}
export interface UserDetailInfoByUinV2 {
result: number,
errMsg: string,
detail: {
uid: string,
uin: string,
simpleInfo: SimpleInfo,
commonExt: CommonExt,
photoWall: null
}
}
export interface UserDetailInfoByUin {
result: number,
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[]
interface IBuddyListener {
onBuddyListChangedV2(arg: unknown): void,//V2版本 还没兼容
onBuddyListChange(arg: OnBuddyChangeParams): void,
onBuddyInfoChange(arg: unknown): void,
@@ -44,6 +46,9 @@ export interface NodeIKernelBuddyListener extends IBuddyListener {
}
export class BuddyListener implements IBuddyListener {
onBuddyListChangedV2(arg: unknown): void {
//throw new Error('Method not implemented.');
}
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 {
onGroupListUpdate(updateType: number, groupList: Group[]): void;
onGroupListUpdate(updateType: GroupListUpdateType, groupList: Group[]): void;
onGroupExtListUpdate(...args: unknown[]): void;
@@ -96,7 +96,7 @@ export class GroupListener implements IGroupListener {
onGroupFirstBulletinNotify(...args: unknown[]) {
}
onGroupListUpdate(updateType: number, groupList: Group[]) {
onGroupListUpdate(updateType: GroupListUpdateType, groupList: Group[]) {
}
onGroupNotifiesUpdated(dboubt: boolean, notifies: GroupNotify[]) {
@@ -202,7 +202,7 @@ export class DebugGroupListener implements IGroupListener {
console.log('onGroupNotifiesUnreadCountUpdated:', ...args);
}
onGroupSingleScreenNotifies(doubt: boolean, seq: string, notifies: GroupNotify[]){
onGroupSingleScreenNotifies(doubt: boolean, seq: string, notifies: GroupNotify[]) {
console.log('onGroupSingleScreenNotifies:');
}

View File

@@ -226,7 +226,7 @@ export interface IKernelMsgListener {
}
export interface NodeIKernelMsgListener {
export interface NodeIKernelMsgListener extends IKernelMsgListener {
// eslint-disable-next-line @typescript-eslint/no-misused-new
new(listener: IKernelMsgListener): NodeIKernelMsgListener;
}

View File

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

View File

@@ -1,19 +1,44 @@
import { Friend } from '@/core/entities';
import { GeneralCallResult } from '@/core/services/common';
import { NodeIKernelBuddyListener } from '@/core/listeners';
export enum BuddyListReqType {
KNOMAL,
KLETTER
}
export interface NodeIKernelBuddyService {
// 以下为自行添加的wrapper.node中并没有这些方法,目的是简化调用
friends: Friend[];
getFriend(uidOrUin: string): Promise<Friend>;
// 26702 以上
getBuddyListV2(callFrom: string, reqType: BuddyListReqType): Promise<GeneralCallResult & {
data: Array<{
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;
getAllBuddyCount(): number;
removeKernelBuddyListener(listener: unknown): void;
getBuddyList(bool: boolean): Promise<GeneralCallResult>;
/**
* @deprecated
* @param nocache 使用缓存
*/
getBuddyList(nocache: boolean): Promise<GeneralCallResult>;
getBuddyNick(uid: number): string;

View File

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

View File

@@ -8,8 +8,90 @@ import {
} from '@/core/entities';
import { GeneralCallResult } from '@/core/services/common';
//高版本的接口不应该随意使用 使用应该严格进行pr审核 同时部分ipc中未出现的接口不要过于依赖 应该做好数据兜底
export interface NodeIKernelGroupService {
setHeader(uid:string,path:string): unknown;
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;
@@ -17,7 +99,7 @@ export interface NodeIKernelGroupService {
createMemberListScene(groupCode: string, scene: string): string;
destroyMemberListScene(): void;
destroyMemberListScene(SceneId:string): void;
//About Arg (a) name: lastId 根据手Q来看为object {index:?(number),uid:string}
getNextMemberList(sceneId: string, a: undefined, num: number): Promise<{
errCode: number, errMsg: string,
@@ -28,9 +110,9 @@ export interface NodeIKernelGroupService {
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 ]
kickMember(groupCode: string, memberUids: string[], refuseForever: boolean, kickReason: string): Promise<void>;
@@ -39,7 +121,7 @@ export interface NodeIKernelGroupService {
modifyMemberCardName(groupCode: string, uid: string, cardName: string): void;
getTransferableMemberInfo(uid: string): unknown;
getTransferableMemberInfo(groupCode: string): unknown;//获取整个群的
transferGroup(uid: string): void;

View File

@@ -1,11 +1,24 @@
import { ElementType, Peer, RawMessage, SendMessageElement } from '@/core/entities';
import { ElementType, MessageElement, Peer, RawMessage, SendMessageElement } from '@/core/entities';
import { NodeIKernelMsgListener } from '@/core/listeners/NodeIKernelMsgListener';
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 {
generateMsgUniqueId(chatType: number, time: string): string;
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>;
@@ -73,7 +86,7 @@ export interface NodeIKernelMsgService {
cancelSendMsg(...args: unknown[]): unknown;
switchToOfflineSendMsg(...args: unknown[]): unknown;
switchToOfflineSendMsg(peer: Peer, MsgId: string): unknown;
reqToOfflineSendMsg(...args: unknown[]): unknown;
@@ -115,9 +128,9 @@ export interface NodeIKernelMsgService {
addLocalTofuRecordMsg(...args: unknown[]): unknown;
addLocalRecordMsg(Peer: Peer, msgId: string, rawMessage: RawMessage, attr: Array<any> | number, front: boolean): Promise<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;
@@ -141,7 +154,7 @@ export interface NodeIKernelMsgService {
getLastMessageList(peer: Peer[]): Promise<unknown>;
getAioFirstViewLatestMsgs(peer: Peer, unknown: number): unknown;
getAioFirstViewLatestMsgs(peer: Peer, num: number): unknown;
getMsgs(peer: Peer, msgId: string, count: unknown, queryOrder: boolean): Promise<unknown>;
@@ -154,7 +167,7 @@ export interface NodeIKernelMsgService {
// this.$clientSeq = j3;
// this.$cnt = i2;
getMsgsWithMsgTimeAndClientSeqForC2C(...args: unknown[]): Promise<unknown>;
getMsgsWithMsgTimeAndClientSeqForC2C(...args: unknown[]): Promise<GeneralCallResult & { msgList: RawMessage[] }>;
getMsgsWithStatus(params: {
peer: Peer
@@ -164,9 +177,9 @@ export interface NodeIKernelMsgService {
queryOrder: boolean
isIncludeSelf: boolean
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[] }>;
@@ -174,18 +187,18 @@ export interface NodeIKernelMsgService {
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;
getSourceOfReplyMsgByClientSeqAndTime(...args: unknown[]): unknown;
getMsgByClientSeqAndTime(peer: Peer, clientSeq: string, time: string): 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;
getMsgsByTypeFilters(peer: Peer, msgId: string, cnt: unknown, queryOrder: boolean, typeFilters: Array<{ type: number, subtype: Array<number> }>): unknown;
@@ -214,19 +227,7 @@ export interface NodeIKernelMsgService {
* @param param.isIncludeCurrent 是否包含当前页码。
* @returns 返回一个Promise解析为查询结果的未知类型对象。
*/
queryMsgsWithFilterVer2(MsgId: string, MsgTime: string, param: {
chatInfo: {
chatType: number,
peerUid: string
},
filterMsgType: [],
filterSendersUid: Array<string>,
filterMsgFromTime: string,
filterMsgToTime: string,
pageLimit: number,
isReverseOrder: boolean,
isIncludeCurrent: boolean
}): Promise<unknown>;
queryMsgsWithFilterVer2(MsgId: string, MsgTime: string, param: QueryMsgsParams): Promise<unknown>;
// this.chatType = i2;
// this.peerUid = str;
@@ -243,19 +244,7 @@ export interface NodeIKernelMsgService {
// this.isReverseOrder = z;
// this.isIncludeCurrent = z2;
//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: {
chatInfo: {
chatType: number,
peerUid: string
},
filterMsgType: [],
filterSendersUid: string[],
filterMsgFromTime: string,
filterMsgToTime: string,
pageLimit: number,
isReverseOrder: boolean,
isIncludeCurrent: boolean
}): Promise<GeneralCallResult & {
queryMsgsWithFilterEx(msgId: string, msgTime: string, megSeq: string, param: QueryMsgsParams): Promise<GeneralCallResult & {
msgList: RawMessage[]
}>;
//queryMsgsWithFilterEx(this.$msgId, this.$msgTime, this.$msgSeq, this.$param)
@@ -263,39 +252,15 @@ export interface NodeIKernelMsgService {
setMsgRichInfoFlag(...args: unknown[]): unknown;
queryPicOrVideoMsgs(msgId: string, msgTime: string, megSeq: string, param: {
chatInfo: {
chatType: number,
peerUid: string
},
filterMsgType: [],
filterSendersUid: [],
filterMsgFromTime: string,
filterMsgToTime: string,
pageLimit: number,
isReverseOrder: boolean,
isIncludeCurrent: boolean
}): Promise<unknown>;
queryPicOrVideoMsgs(msgId: string, msgTime: string, megSeq: string, param: QueryMsgsParams): Promise<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(msgId: string, msgTime: string, megSeq: string, param: {
chatInfo: {
chatType: number,
peerUid: string
},
filterMsgType: [],
filterSendersUid: [],
filterMsgFromTime: string,
filterMsgToTime: string,
pageLimit: number,
isReverseOrder: boolean,
isIncludeCurrent: boolean
}): unknown;
queryMsgsAndAbstractsWithFilter(msgId: string, msgTime: string, megSeq: string, param: QueryMsgsParams): unknown;
setFocusOnGuild(...args: unknown[]): unknown;
@@ -361,7 +326,7 @@ export interface NodeIKernelMsgService {
setGuildTabUserFlag(...args: unknown[]): unknown;
setBuildMode(...args: unknown[]): unknown;
setBuildMode(flag: number/*0 1 3*/): unknown;
setConfigurationServiceData(...args: unknown[]): unknown;
@@ -393,8 +358,35 @@ export interface NodeIKernelMsgService {
translatePtt2Text(MsgId: string, Peer: {}, MsgElement: {}): unknown;
setPttPlayedState(...args: unknown[]): unknown;
fetchFavEmojiList(...args: unknown[]): unknown;
// NodeIQQNTWrapperSession fetchFavEmojiList [
// "",
// 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;
@@ -485,7 +477,7 @@ export interface NodeIKernelMsgService {
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;
@@ -507,13 +499,13 @@ export interface NodeIKernelMsgService {
queryCalendar(...args: unknown[]): unknown;
queryFirstMsgSeq(...args: unknown[]): unknown;
queryFirstMsgSeq(peer: Peer, ...args: unknown[]): unknown;
queryRoamCalendar(...args: unknown[]): unknown;
queryFirstRoamMsg(...args: unknown[]): unknown;
fetchLongMsg(...args: unknown[]): unknown;
fetchLongMsg(peer: Peer, msgId: string): unknown;
fetchLongMsgWithCb(...args: unknown[]): unknown;
@@ -547,9 +539,9 @@ export interface NodeIKernelMsgService {
deleteReplyDraft(...args: unknown[]): unknown;
getFirstUnreadAtMsg(...args: unknown[]): unknown;
getFirstUnreadAtMsg(peer: Peer): unknown;
clearMsgRecords(...args: unknown[]): unknown;//设置已读后调用我觉得比较好 清理记录
clearMsgRecords(...args: unknown[]): unknown;//设置已读后调用我觉得比较好 清理记录 现在别了
IsExistOldDb(...args: unknown[]): unknown;
@@ -569,7 +561,7 @@ export interface NodeIKernelMsgService {
getCurChatImportStatusByUin(...args: unknown[]): unknown;
getDataImportUserLevel(...args: unknown[]): unknown;
getDataImportUserLevel(): unknown;
getMsgQRCode(...args: unknown[]): unknown;
@@ -600,6 +592,8 @@ export interface NodeIKernelMsgService {
// this.selfPhone = str5;
// this.gameSession = tempChatGameSession;
prepareTempChat(args: unknown): unknown;//主动临时消息 不做
sendSsoCmdReqByContend(cmd: string, param: string): Promise<unknown>;
//chattype,uid->Promise<any>
getTempChatInfo(ChatType: number, Uid: string): unknown;
@@ -624,7 +618,7 @@ export interface NodeIKernelMsgService {
getABatchOfContactMsgBoxInfo(...args: unknown[]): unknown;
insertMsgToMsgBox(...args: unknown[]): unknown;
insertMsgToMsgBox(peer: Peer, msgId: string, arg: 2006): unknown;
isHitEmojiKeyword(...args: unknown[]): unknown;
@@ -664,7 +658,25 @@ export interface NodeIKernelMsgService {
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;

View File

@@ -1,10 +1,36 @@
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 { GeneralCallResult } from '@/core/services/common';
export enum UserDetailSource {
KDB,
KSERVER
}
export enum ProfileBizType {
KALL,
KBASEEXTEND,
KVAS,
KQZONE,
KOTHER
}
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;
removeKernelProfileListener(listenerId: number): void;

View File

@@ -1,6 +1,50 @@
import { GetFileListParam, Peer } from "../entities";
import { GetFileListParam, MessageElement, Peer, SendMessageElement } from "../entities";
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 {
//getVideoPlayUrl(peer, msgId, elemId, videoCodecFormat, VideoRequestWay.KHAND, cb);
// 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.peerUid = "";
@@ -65,16 +109,43 @@ export interface NodeIKernelRichMediaService {
// this.elem = msgElement;
// 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 } }>;
//参数与getVideoPlayUrlInVisit一样
downloadRichMediaInVisit(arg: unknown): unknown;
downloadFileForModelId(peer: Peer, arg: unknown[], arg3: string): unknown;
downloadRichMediaInVisit(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
}): unknown;
//arg3为“”
downloadFileForModelId(peer: Peer, ModelId: string[], arg3: string): unknown;
//第三个参数 Array<Type>
// this.fileId = "";
// this.fileName = "";
@@ -83,15 +154,20 @@ export interface NodeIKernelRichMediaService {
// this.fileSize = j2;
// 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>;
downloadFileByUrlList(arg1: unknown, arg2: unknown): unknown;
downloadFileByUrlList(fileDownloadTyp: UrlFileDownloadType, urlList: Array<string>): unknown;
downloadFileForFileInfo(arg1: unknown, arg2: unknown): unknown;
downloadFileForFileInfo(fileInfo: CommonFileInfo[], savePath: string): unknown;
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;
createGroupFolder(arg1: unknown, arg2: unknown): unknown;
@@ -124,8 +200,6 @@ export interface NodeIKernelRichMediaService {
getGroupFileInfo(arg1: unknown, arg2: unknown): unknown;
getGroupFileList(arg1: unknown, arg2: unknown): unknown;
getGroupTransferList(arg1: unknown, arg2: unknown): unknown;
renameGroupFile(arg1: unknown, arg2: unknown, arg3: unknown, arg4: unknown, arg5: unknown): unknown;
@@ -133,7 +207,7 @@ export interface NodeIKernelRichMediaService {
moveGroupFile(arg1: unknown, arg2: unknown, arg3: unknown, arg4: unknown, arg5: unknown): unknown;
transGroupFile(arg1: unknown, arg2: unknown): unknown;
searchGroupFile(
keywords: Array<string>,
param: {
@@ -168,13 +242,25 @@ export interface NodeIKernelRichMediaService {
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;
onlyUploadFile(arg1: unknown, arg2: 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;
}

View File

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

View File

@@ -1,79 +1,129 @@
import { ChatType } from "../entities";
export interface NodeIKernelSearchService {
addKernelSearchListenerr(...args: any[]): unknown;// needs 1 arguments
addKernelSearchListener(...args: any[]): unknown;// needs 1 arguments
removeKernelSearchListenerr(...args: any[]): unknown;// needs 1 arguments
removeKernelSearchListener(...args: any[]): unknown;// needs 1 arguments
searchStrangerr(...args: any[]): unknown;// needs 3 arguments
searchStranger(...args: any[]): unknown;// needs 3 arguments
searchGroupr(...args: any[]): unknown;// needs 1 arguments
searchGroup(...args: any[]): unknown;// needs 1 arguments
searchLocalInfor(...args: any[]): unknown;// needs 2 arguments
searchLocalInfo(keywords: string, unknown: number/*4*/): unknown;
cancelSearchLocalInfor(...args: any[]): unknown;// needs 3 arguments
cancelSearchLocalInfo(...args: any[]): unknown;// needs 3 arguments
searchBuddyChatInfor(...args: any[]): unknown;// needs 2 arguments
searchBuddyChatInfo(...args: any[]): unknown;// needs 2 arguments
searchMoreBuddyChatInfor(...args: any[]): unknown;// needs 1 arguments
searchMoreBuddyChatInfo(...args: any[]): unknown;// needs 1 arguments
cancelSearchBuddyChatInfor(...args: any[]): unknown;// needs 3 arguments
cancelSearchBuddyChatInfo(...args: any[]): unknown;// needs 3 arguments
searchContactr(...args: any[]): unknown;// needs 2 arguments
searchContact(...args: any[]): unknown;// needs 2 arguments
searchMoreContactr(...args: any[]): unknown;// needs 1 arguments
searchMoreContact(...args: any[]): unknown;// needs 1 arguments
cancelSearchContactr(...args: any[]): unknown;// needs 3 arguments
cancelSearchContact(...args: any[]): unknown;// needs 3 arguments
searchGroupChatInfor(...args: any[]): unknown;// needs 3 arguments
searchGroupChatInfo(...args: any[]): unknown;// needs 3 arguments
resetSearchGroupChatInfoSortTyper(...args: any[]): unknown;// needs 3 arguments
resetSearchGroupChatInfoSortType(...args: any[]): unknown;// needs 3 arguments
resetSearchGroupChatInfoFilterMembersr(...args: any[]): unknown;// needs 3 arguments
resetSearchGroupChatInfoFilterMembers(...args: any[]): unknown;// needs 3 arguments
searchMoreGroupChatInfor(...args: any[]): unknown;// needs 1 arguments
searchMoreGroupChatInfo(...args: any[]): unknown;// needs 1 arguments
cancelSearchGroupChatInfor(...args: any[]): unknown;// needs 3 arguments
cancelSearchGroupChatInfo(...args: any[]): unknown;// needs 3 arguments
searchChatsWithKeywordsr(...args: any[]): unknown;// needs 3 arguments
searchChatsWithKeywords(...args: any[]): unknown;// needs 3 arguments
searchMoreChatsWithKeywordsr(...args: any[]): unknown;// needs 1 arguments
searchMoreChatsWithKeywords(...args: any[]): unknown;// needs 1 arguments
cancelSearchChatsWithKeywordsr(...args: any[]): unknown;// needs 3 arguments
cancelSearchChatsWithKeywords(...args: any[]): unknown;// needs 3 arguments
searchChatMsgsr(...args: any[]): unknown;// needs 2 arguments
searchChatMsgs(...args: any[]): unknown;// needs 2 arguments
searchMoreChatMsgsr(...args: any[]): unknown;// needs 1 arguments
searchMoreChatMsgs(...args: any[]): unknown;// needs 1 arguments
cancelSearchChatMsgsr(...args: any[]): unknown;// needs 3 arguments
cancelSearchChatMsgs(...args: any[]): unknown;// needs 3 arguments
searchMsgWithKeywordsr(...args: any[]): unknown;// needs 2 arguments
searchMsgWithKeywords(...args: any[]): unknown;// needs 2 arguments
searchMoreMsgWithKeywordsr(...args: any[]): unknown;// needs 1 arguments
searchMoreMsgWithKeywords(...args: any[]): unknown;// needs 1 arguments
cancelSearchMsgWithKeywords(...args: any[]): unknown;// needs 3 arguments
searchFileWithKeywords(keywords: string[], source: number): Promise<unknown>;// needs 2 arguments
searchFileWithKeywords(keywords: string[], source: number): Promise<string>;// needs 2 arguments
searchMoreFileWithKeywordsr(...args: any[]): unknown;// needs 1 arguments
searchMoreFileWithKeywords(...args: any[]): unknown;// needs 1 arguments
cancelSearchFileWithKeywordsr(...args: any[]): unknown;// needs 3 arguments
cancelSearchFileWithKeywords(...args: any[]): unknown;// needs 3 arguments
searchAtMeChatsr(...args: any[]): unknown;// needs 3 arguments
searchAtMeChats(...args: any[]): unknown;// needs 3 arguments
searchMoreAtMeChatsr(...args: any[]): unknown;// needs 1 arguments
searchMoreAtMeChats(...args: any[]): unknown;// needs 1 arguments
cancelSearchAtMeChatsr(...args: any[]): unknown;// needs 3 arguments
cancelSearchAtMeChats(...args: any[]): unknown;// needs 3 arguments
searchChatAtMeMsgsr(...args: any[]): unknown;// needs 1 arguments
searchChatAtMeMsgs(...args: any[]): unknown;// needs 1 arguments
searchMoreChatAtMeMsgsr(...args: any[]): unknown;// needs 1 arguments
searchMoreChatAtMeMsgs(...args: any[]): unknown;// needs 1 arguments
cancelSearchChatAtMeMsgsr(...args: any[]): unknown;// needs 3 arguments
cancelSearchChatAtMeMsgs(...args: any[]): unknown;// needs 3 arguments
addSearchHistoryr(...args: any[]): unknown;// needs 1 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
}
>
}
]
removeSearchHistoryr(...args: any[]): unknown;// needs 1 arguments
}): Promise<{
result: number,
errMsg: string,
id?: number
}>;
searchCacher(...args: any[]): unknown;// needs 3 arguments
removeSearchHistory(...args: any[]): unknown;// needs 1 arguments
clearSearchCacher(...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 {
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 path from 'node:path';
import fs from 'node:fs';
@@ -34,42 +34,42 @@ export interface WrapperSessionInitConfig {
account_path: string // 可以通过NodeQQNTWrapperUtil().getNTUserDataInfoConfig()获取
}
clientVer: string // 9.9.8-22355
a2: '',
d2: '',
d2Key: '',
machineId: '',
platform: 3, // 3是Windows?
a2: string,
d2: string,
d2Key: string,
machineId: string,
platform: PlatformType, // 3是Windows?
platVer: string, // 系统版本号, 应该可以固定
appid: string,
rdeliveryConfig: {
appKey: '',
systemId: 0,
appId: '',
logicEnvironment: '',
platform: 3,
language: '',
sdkVersion: '',
userId: '',
appVersion: '',
osVersion: '',
bundleId: '',
serverUrl: '',
fixedAfterHitKeys: ['']
appKey: string,
systemId: number,
appId: string,
logicEnvironment: string,
platform: PlatformType,
language: string,
sdkVersion: string,
userId: string,
appVersion: string,
osVersion: string,
bundleId: string,
serverUrl: string,
fixedAfterHitKeys: string[]
}
'defaultFileDownloadPath': string, // 这个可以通过环境变量获取?
'deviceInfo': {
'guid': string,
'buildVer': string,
'localId': 2052,
'devName': string,
'devType': string,
'vendorName': '',
'osVer': string,
'vendorOsName': string,
'setMute': false,
'vendorType': 0
defaultFileDownloadPath: string, // 这个可以通过环境变量获取?
deviceInfo: {
guid: string,
buildVer: string,
localId: number,
devName: string,
devType: string,
vendorName: string,
osVer: string,
vendorOsName: string,
setMute: boolean,
vendorType: VendorType
},
'deviceConfig': '{"appearance":{"isSplitViewMode":true},"msg":{}}'
deviceConfig: '{"appearance":{"isSplitViewMode":true},"msg":{}}'
}
export const sessionConfig: WrapperSessionInitConfig | any = {};
@@ -79,7 +79,7 @@ export async function genSessionConfig(selfUin: string, selfUid: string, account
fs.mkdirSync(downloadPath, { recursive: true });
let guid: string = await getMachineId();
//console.log(guid);
// guid = '52afb776-82f6-4e59-9d38-44705b112d0a';
// guid = '52afb776-82f6-4e59-9d38-44705b112d0a';
//let guid: string = await getMachineId();
const config: WrapperSessionInitConfig = {
selfUin,
@@ -87,20 +87,20 @@ export async function genSessionConfig(selfUin: string, selfUid: string, account
desktopPathConfig: {
account_path // 可以通过NodeQQNTWrapperUtil().getNTUserDataInfoConfig()获取
},
clientVer: qqVersionConfigInfo.curVersion, // 9.9.8-22355
clientVer: getFullQQVesion(), // 9.9.8-22355
a2: '',
d2: '',
d2Key: '',
machineId: '',
platform: 3, // 3是Windows?
platform: PlatformType.KWINDOWS, // 3是Windows?
platVer: systemVersion, // 系统版本号, 应该可以固定
appid: appid,
appid: QQVersionAppid,
rdeliveryConfig: {
appKey: '',
systemId: 0,
appId: '',
logicEnvironment: '',
platform: 3,
platform: PlatformType.KWINDOWS,
language: '',
sdkVersion: '',
userId: '',
@@ -110,22 +110,21 @@ export async function genSessionConfig(selfUin: string, selfUid: string, account
serverUrl: '',
fixedAfterHitKeys: ['']
},
'defaultFileDownloadPath': downloadPath,
'deviceInfo': {
defaultFileDownloadPath: downloadPath,
deviceInfo: {
guid,
'buildVer': qqPkgInfo.version,
'localId': 2052,
'devName': hostname,
'devType': systemName,
'vendorName': '',
'osVer': systemVersion,
'vendorOsName': systemName,
'setMute': false,
'vendorType': 0
buildVer: getFullQQVesion(),
localId: 2052,
devName: hostname,
devType: systemName,
vendorName: '',
osVer: systemVersion,
vendorOsName: systemName,
setMute: false,
vendorType: VendorType.KNOSETONIOS
},
'deviceConfig': '{"appearance":{"isSplitViewMode":true},"msg":{}}'
deviceConfig: '{"appearance":{"isSplitViewMode":true},"msg":{}}'
};
Object.assign(sessionConfig, config);
// log(sessionConfig);
return config;
}

View File

@@ -26,7 +26,6 @@ import {
NodeIKernelRichMediaService,
NodeIKernelAvatarService,
} from './services';
import { qqVersionConfigInfo } from '@/common/utils/QQBasicInfo';
import { NodeIKernelStorageCleanService } from './services/NodeIKernelStorageCleanService';
import { NodeIKernelRobotService } from './services/NodeIKernelRobotService';
import { dirname } from "node:path"
@@ -41,6 +40,9 @@ import { NodeIKernelSearchService } from './services/NodeIKernelSearchService';
import { NodeIKernelCollectionService } from './services/NodeIKernelCollectionService';
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);
@@ -156,6 +158,10 @@ export interface NodeIQQNTWrapperSession {
startNT(): void;
getBdhUploadService(): unknown;
getECDHService(): NodeIKernelECDHService;
getMsgService(): NodeIKernelMsgService;
getProfileService(): NodeIKernelProfileService;
@@ -210,7 +216,7 @@ export interface NodeIQQNTWrapperSession {
getSkinService(): unknown;
getTestPerformanceService(): unknown;
getTestPerformanceService(): NodeIkernelTestPerformanceService;
getQQPlayService(): unknown;
@@ -286,7 +292,7 @@ export interface WrapperNodeApi {
let wrapperNodePath = path.resolve(path.dirname(process.execPath), './resources/app/wrapper.node');
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: {} };
process.dlopen(nativemodule, wrapperNodePath);

View File

@@ -92,9 +92,9 @@ napCatCore.getQuickLoginList().then((res) => {
WebUiDataRuntime.setQQQuickLoginCall(async (uin: string) => {
const QuickLogin: Promise<{ result: boolean, message: string }> = new Promise((resolve, reject) => {
if (quickLoginQQ) {
log('正在快速登录 ', quickLoginQQ);
napCatCore.quickLogin(quickLoginQQ).then(res => {
if (uin) {
log('正在快速登录 ', uin);
napCatCore.quickLogin(uin).then(res => {
if (res.loginErrorInfo.errMsg) {
resolve({ result: false, message: res.loginErrorInfo.errMsg });
}

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 { ActionName } from '../types';
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;
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

@@ -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 fs from 'fs/promises';
import { ob11Config } from '@/onebot11/config';
import { log, logDebug } from '@/common/utils/log';
import { sleep } from '@/common/utils/helper';
import { uri2local } from '@/common/utils/file';
import { UUIDConverter } from '@/common/utils/helper';
import { ActionName, BaseCheckResult } from '../types';
import { FileElement, RawMessage, VideoElement } from '@/core/entities';
import { NTQQFileApi, NTQQMsgApi } from '@/core/apis';
import { ChatType, ElementType, FileElement, Peer, RawMessage, VideoElement } from '@/core/entities';
import { NTQQFileApi, NTQQFriendApi, NTQQMsgApi, NTQQUserApi } from '@/core/apis';
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import Ajv from 'ajv';
import { MessageUnique } from '@/common/utils/MessageUnique';
import { getGroup } from '@/core/data';
export interface GetFilePayload {
file: string; // 文件名或者fileUuid
@@ -45,6 +42,102 @@ export class GetFileBase extends BaseAction<GetFilePayload, GetFileResponse> {
return { id: element.elementId, element: element.fileElement };
}
protected async _handle(payload: GetFilePayload): Promise<GetFileResponse> {
const { enableLocalFile2Url } = ob11Config;
let UuidData: {
high: string;
low: string;
} | undefined;
try {
UuidData = UUIDConverter.decode(payload.file);
if (UuidData) {
const peerUin = UuidData.high;
const msgId = UuidData.low;
const isGroup = await getGroup(peerUin);
let peer: Peer | undefined;
//识别Peer
if (isGroup) {
peer = { chatType: ChatType.group, peerUid: peerUin };
}
const PeerUid = await NTQQUserApi.getUidByUin(peerUin);
if (PeerUid) {
const isBuddy = await NTQQFriendApi.isBuddy(PeerUid);
if (isBuddy) {
peer = { chatType: ChatType.friend, peerUid: PeerUid };
} else {
peer = { chatType: ChatType.temp, peerUid: PeerUid };
}
}
if (!peer) {
throw new Error('chattype not support');
}
const msgList = await NTQQMsgApi.getMsgsByMsgId(peer, [msgId]);
if (msgList.msgList.length == 0) {
throw new Error('msg not found');
}
const msg = msgList.msgList[0];
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 {
}
const NTSearchNameResult = (await NTQQFileApi.searchfile([payload.file])).resultItems;
if (NTSearchNameResult.length !== 0) {
const MsgId = NTSearchNameResult[0].msgId;
let peer: Peer | undefined = undefined;
if (NTSearchNameResult[0].chatType == ChatType.group) {
peer = { chatType: ChatType.group, peerUid: NTSearchNameResult[0].groupChatInfo[0].groupCode };
}
if (!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 {
res.base64 = await fs.readFile(downloadPath, 'base64');
} catch (e) {
throw new Error('文件下载失败. ' + e);
}
}
//不手动删除?文件持久化了
return res;
}
throw new Error('file not found');
// let cache = await dbUtil.getFileCacheByName(payload.file);
// if (!cache) {

View File

@@ -2,7 +2,7 @@ import BaseAction from '../BaseAction';
import { OB11Message, OB11User } from '../../types';
import { getGroup, groups } from '@/core/data';
import { ActionName } from '../types';
import { ChatType } from '@/core/entities';
import { ChatType, RawMessage } from '@/core/entities';
import { NTQQMsgApi } from '@/core/apis/msg';
import { OB11Constructor } from '../../constructor';
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
@@ -18,7 +18,7 @@ const SchemaData = {
message_seq: { type: 'number' },
count: { type: 'number' }
},
required: ['group_id', 'message_seq', 'count']
required: ['group_id']
} as const satisfies JSONSchema;
type Payload = FromSchema<typeof SchemaData>;
@@ -31,14 +31,18 @@ export default class GoCQHTTPGetGroupMsgHistory extends BaseAction<Payload, Resp
if (!group) {
throw `${payload.group_id}不存在`;
}
const startMsgId = (await MessageUnique.getMsgIdAndPeerByShortId(payload.message_seq))?.MsgId || '0';
// log("startMsgId", startMsgId)
const historyResult = (await NTQQMsgApi.getMsgHistory({
let targetMsgShortId, count = parseInt(payload.count?.toString() ?? '20');
const peer = {
chatType: ChatType.group,
peerUid: group.groupCode
}, startMsgId, parseInt(payload.count?.toString()) || 20));
//logDebug(historyResult);
const msgList = historyResult.msgList;
};
let msgList: RawMessage[];
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 => {
msg.id = await MessageUnique.createMsg({ guildId: '', chatType: msg.chatType, peerUid: msg.peerUid }, msg.msgId);
}));

View File

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

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

View File

@@ -1,5 +1,4 @@
import { OB11GroupMember } from '../../types';
import { getGroup, getGroupMember, groupMembers, selfInfo } from '@/core/data';
import { OB11Constructor } from '../../constructor';
import BaseAction from '../BaseAction';
import { ActionName } from '../types';
@@ -8,8 +7,8 @@ import { logDebug } from '@/common/utils/log';
import { WebApi } from '@/core/apis/webapi';
import { NTQQGroupApi } from '@/core';
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
// no_cache get时传字符串
import { getGroupMember, selfInfo } from '@/core/data';
import { requireMinNTQQBuild } from '@/common/utils/QQBasicInfo';
const SchemaData = {
type: 'object',
properties: {
@@ -26,17 +25,12 @@ class GetGroupMemberInfo extends BaseAction<Payload, OB11GroupMember> {
actionName = ActionName.GetGroupMemberInfo;
PayloadSchema = SchemaData;
protected async _handle(payload: Payload) {
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})不存在`);
const isNocache = payload.no_cache == true || payload.no_cache === 'true';
let uid = await NTQQUserApi.getUidByUin(payload.user_id.toString());
if (!uid) {
throw (`Uin2Uid Error ${payload.user_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());
//早返回
let member = await NTQQGroupApi.getGroupMemberV2(payload.group_id.toString(), uid, isNocache);
if (!member) {
throw (`群(${payload.group_id})成员${payload.user_id}不存在`);
}
@@ -47,26 +41,37 @@ class GetGroupMemberInfo extends BaseAction<Payload, OB11GroupMember> {
} catch (e) {
logDebug('获取群成员详细信息失败, 只能返回基础信息', e);
}
const date = Math.round(Date.now() / 1000);
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();
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 {
let LastestMsgList = await NTQQGroupApi.getLastestMsg(payload.group_id.toString(), [payload.user_id.toString()]);
if (LastestMsgList?.msgList?.length && LastestMsgList?.msgList?.length > 0) {
let 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);//兜底数据 防止群管乱杀
}
}
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;
}

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,4 +1,4 @@
import { getGroup, getGroupMember, groupMembers, selfInfo } from '@/core/data';
import { getGroup, getGroupMember, selfInfo } from '@/core/data';
import { OB11GroupMember } from '../../types';
import { OB11Constructor } from '../../constructor';
import BaseAction from '../BaseAction';
@@ -6,6 +6,7 @@ import { ActionName } from '../types';
import { NTQQGroupApi } from '@/core';
import { WebApi } from '@/core/apis/webapi';
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import { requireMinNTQQBuild } from '@/common/utils/QQBasicInfo';
const SchemaData = {
type: 'object',
@@ -22,24 +23,20 @@ class GetGroupMemberList extends BaseAction<Payload, OB11GroupMember[]> {
actionName = ActionName.GetGroupMemberList;
PayloadSchema = SchemaData;
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) {
throw (`${payload.group_id}不存在`);
}
// 从Data里面获取
let _groupMembers: OB11GroupMember[] = OB11Constructor.groupMembers(group);
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);
}
let groupMembers = await NTQQGroupApi.getGroupMembers(payload.group_id.toString());
let _groupMembers = Array.from(groupMembers.values())
.map(item => { return OB11Constructor.groupMember(group.groupCode, item); });
const MemberMap: Map<number, OB11GroupMember> = new Map<number, OB11GroupMember>();
// 转为Map 方便索引
let date = Math.round(Date.now() / 1000);
const date = Math.round(Date.now() / 1000);
for (let i = 0, len = _groupMembers.length; i < len; i++) {
// 保证基础数据有这个 同时避免群管插件过于依赖这个杀了
_groupMembers[i].join_time = date;
@@ -47,31 +44,47 @@ class GetGroupMemberList extends BaseAction<Payload, OB11GroupMember[]> {
MemberMap.set(_groupMembers[i].user_id, _groupMembers[i]);
}
const isPrivilege = role === 3 || 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) {
continue;
if (!requireMinNTQQBuild('26702')) {
const selfRole = groupMembers.get(selfInfo.uid)?.role;
const isPrivilege = selfRole === 3 || selfRole === 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) {
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);
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);
} else {
if (isNocache) {
const DateMap = await NTQQGroupApi.getGroupMemberLastestSendTimeCache(payload.group_id.toString());//开始从本地拉取
for (const DateUin of DateMap.keys()) {
const MemberData = MemberMap.get(parseInt(DateUin));
if (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 {
const DateMap = await NTQQGroupApi.getGroupMemberLastestSendTimeCache(payload.group_id.toString());//开始从本地拉取
for (let DateUin of DateMap.keys()) {
const MemberData = MemberMap.get(parseInt(DateUin));
if (MemberData) {
MemberData.last_sent_time = parseInt(DateMap.get(DateUin)!);
//join_time 有基础数据兜底
}
}
_groupMembers.forEach(async item => {
item.last_sent_time = parseInt((await getGroupMember(payload.group_id.toString(), item.user_id))?.lastSpeakTime || date.toString());
item.join_time = parseInt((await getGroupMember(payload.group_id.toString(), item.user_id))?.joinTime || date.toString());
});
}
// 还原索引到Array 一同返回

View File

@@ -75,6 +75,9 @@ import SetEssenceMsg from './group/SetEssenceMsg';
import GetRecentContact from './user/GetRecentContact';
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 = [
new RebootNormal(),
@@ -156,9 +159,11 @@ export const actionHandlers = [
new GetRecentContact(),
new MarkAllMsgAsRead(),
new GetProfileLike(),
new SetGroupHeader()
new SetGroupHeader(),
new FetchCustomFace(),
new GoCQHTTPUploadPrivateFile(),
new TestApi01()
];
function initActionMap() {
const actionMap = new Map<string, BaseAction<any, any>>();
for (const action of actionHandlers) {

View File

@@ -32,10 +32,18 @@ class GetMsg extends BaseAction<Payload, OB11Message> {
if (!msgIdWithPeer) {
throw ('消息不存在');
}
const peer = { guildId: '', peerUid: msgIdWithPeer?.Peer.peerUid, chatType: msgIdWithPeer.Peer.chatType };
const msg = await NTQQMsgApi.getMsgsByMsgId(
{ guildId: '', peerUid: msgIdWithPeer?.Peer.peerUid, chatType: msgIdWithPeer.Peer.chatType },
peer,
[msgIdWithPeer?.MsgId || payload.message_id.toString()]);
return await OB11Constructor.message(msg.msgList[0]);
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

@@ -12,45 +12,22 @@ import {
SignMusicWrapper
} from '@/core';
import { getGroupMember } from '@/core/data';
import { logDebug, logError } from '@/common/utils/log';
import { logError, logWarn } from '@/common/utils/log';
import { uri2local } from '@/common/utils/file';
import { ob11Config } from '@/onebot11/config';
import { RequestUtil } from '@/common/utils/request';
import fs from 'node:fs';
import { MessageUnique } from '@/common/utils/MessageUnique';
export type MessageContext = {
group?: Group,
deleteAfterSentFiles: string[],
}
async function handleOb11FileLikeMessage(
{ data: { file, name: payloadFileName } }: OB11MessageFileBase,
{ data: inputdata }: OB11MessageFileBase,
{ deleteAfterSentFiles }: MessageContext
) {
const uri = 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 fileMsgPeer = MessageUnique.getPeerByMsgId(cache.msgId);
// if (fileMsgPeer) {
// cache.path = await NTQQFileApi.downloadMedia(
// fileMsgPeer.MsgId, fileMsgPeer.Peer.chatType, fileMsgPeer.Peer.peerUid,
// cache.elementId, '', ''
// );
// uri = 'file://' + cache.path;
// dbUtil.updateFileCache(cache);
// }
// }
// logDebug('找到文件缓存', uri);
// }
const { path, isLocal, fileName, errMsg } = (await uri2local(uri));
//有的奇怪的框架将url作为参数 而不是file 此时优先url
const { path, isLocal, fileName, errMsg } = (await uri2local(inputdata?.url || inputdata.file));
if (errMsg) {
logError('文件下载失败', errMsg);
@@ -61,7 +38,7 @@ async function handleOb11FileLikeMessage(
deleteAfterSentFiles.push(path);
}
return { path, fileName: payloadFileName || fileName };
return { path, fileName: inputdata.name || fileName };
}
const _handlers: {
@@ -85,9 +62,12 @@ const _handlers: {
SendMsgElementConstructor.at(atQQ, atMember.uid, AtType.atUser, atMember.cardName || atMember.nick) :
undefined;
},
[OB11MessageDataType.reply]: async ({ data: { 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 ?
SendMsgElementConstructor.reply(replyMsg.msgSeq, replyMsg.msgId, replyMsg.senderUin!, replyMsg.senderUin!) :
@@ -230,7 +210,7 @@ export default async function createSendElements(
ignoreTypes: OB11MessageDataType[] = []
) {
const deleteAfterSentFiles: string[] = [];
let callResultList: Array<Promise<SendMessageElement | undefined>> = [];
const callResultList: Array<Promise<SendMessageElement | undefined>> = [];
for (const sendMsg of messageData) {
if (ignoreTypes.includes(sendMsg.type)) {
continue;
@@ -241,7 +221,7 @@ export default async function createSendElements(
)?.catch(undefined);
callResultList.push(callResult);
}
let ret = await Promise.all(callResultList);
const ret = await Promise.all(callResultList);
const sendElements: SendMessageElement[] = ret.filter(ele => ele) as SendMessageElement[];
return { sendElements, deleteAfterSentFiles };
}

View File

@@ -53,7 +53,7 @@ export async function handleForwardNode(destPeer: Peer, messageNodes: OB11Messag
const nodeId = messageNode.data.id;
// 有nodeId表示一个子转发消息卡片
if (nodeId) {
const nodeMsg = MessageUnique.getMsgIdAndPeerByShortId(parseInt(nodeId));
const nodeMsg = MessageUnique.getPeerByMsgId(nodeId);
if (!needClone) {
nodeMsgIds.push(nodeMsg!.MsgId);
} else {
@@ -127,7 +127,7 @@ export async function handleForwardNode(destPeer: Peer, messageNodes: OB11Messag
}
}
}
// logDebug('nodeMsgArray', nodeMsgArray);
// logDebug('nodeMsgArray', nodeMsgArray);
nodeMsgIds = nodeMsgArray.map(msg => msg.msgId);
if (needSendSelf) {
//logDebug('需要克隆转发消息');

View File

@@ -10,6 +10,7 @@ import { ActionName, BaseCheckResult } from '@/onebot11/action/types';
import { getGroup } from '@/core/data';
import { ChatType, ElementType, Group, NTQQFileApi, NTQQFriendApi, NTQQMsgApi, NTQQUserApi, Peer, SendMessageElement, } from '@/core';
import fs from 'node:fs';
import fsPromise from 'node:fs/promises';
import { logDebug, logError } from '@/common/utils/log';
import { decodeCQCode } from '@/onebot11/cqcode';
import createSendElements from './create-send-elements';
@@ -40,7 +41,7 @@ export async function sendMsg(peer: Peer, sendElements: SendMessageElement[], de
throw ('消息体无法解析, 请检查是否发送了不支持的消息类型');
}
let totalSize = 0;
let timeout = 5000;
let timeout = 10000;
try {
for (const fileElement of sendElements) {
if (fileElement.elementType === ElementType.PTT) {
@@ -59,7 +60,7 @@ export async function sendMsg(peer: Peer, sendElements: SendMessageElement[], de
//且 PredictTime ((totalSize / 1024 / 512) * 1000)不等于Nan
const PredictTime = totalSize / 1024 / 256 * 1000;
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) {
logError('发送消息计算预计时间异常', e);
@@ -71,14 +72,7 @@ export async function sendMsg(peer: Peer, sendElements: SendMessageElement[], de
logDebug('发送消息id获取失败', e);
returnMsg!.id = 0;
}
deleteAfterSentFiles.map((f) => {
try {
fs.unlinkSync(f);
} catch (e) {
logError('发送消息删除文件失败', e);
}
});
deleteAfterSentFiles.map((f) => { fsPromise.unlink(f).then().catch(e => logError('发送消息删除文件失败', e)); });
return returnMsg;
}
@@ -134,7 +128,7 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
return { valid: false, message: `${payload.group_id}不存在` };
}
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!);
// 此处有问题
if (!isBuddy) {

View File

@@ -7,8 +7,8 @@ import { MessageUnique } from '@/common/utils/MessageUnique';
const SchemaData = {
type: 'object',
properties: {
message_id: { type: ['string','number'] },
emoji_id: { type: ['string','number'] }
message_id: { type: ['string', 'number'] },
emoji_id: { type: ['string', 'number'] }
},
required: ['message_id', 'emoji_id']
} as const satisfies JSONSchema;
@@ -23,10 +23,13 @@ export class SetMsgEmojiLike extends BaseAction<Payload, any> {
if (!msg) {
throw new Error('msg not found');
}
if (!payload.emoji_id){
if (!payload.emoji_id) {
throw new Error('emojiId not found');
}
const msgSeq = (await NTQQMsgApi.getMsgsByMsgId(msg.Peer, [msg.MsgId])).msgList[0].msgSeq;
return await NTQQMsgApi.setEmojiLike(msg.Peer, msgSeq, payload.emoji_id.toString(), true);
const msgData = (await NTQQMsgApi.getMsgsByMsgId(msg.Peer, [msg.MsgId])).msgList;
if (!msgData || msgData.length == 0 || !msgData[0].msgSeq) {
throw new Error('find msg by msgid error');
}
return await NTQQMsgApi.setEmojiLike(msg.Peer, msgData[0].msgSeq, payload.emoji_id.toString(), true);
}
}

View File

@@ -99,5 +99,8 @@ export enum ActionName {
GetRecentContact = 'get_recent_contact',
_MarkAllMsgAsRead = '_mark_all_as_read',
GetProfileLike = 'get_profile_like',
SetGroupHeader = "set_group_head"
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 { NTQQFriendApi } from '@/core';
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import { requireMinNTQQBuild } from '@/common/utils/QQBasicInfo';
// no_cache get时传字符串
@@ -20,6 +21,10 @@ export default class GetFriendList extends BaseAction<Payload, OB11User[]> {
actionName = ActionName.GetFriendList;
PayloadSchema = SchemaData;
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') {
const _friends = await NTQQFriendApi.getFriends(true);
// log('强制刷新好友列表,结果: ', _friends)

View File

@@ -42,7 +42,7 @@ export interface OB11Config {
}
class Config extends ConfigBase<OB11Config> implements OB11Config {
name: string = 'onebot11'
name: string = 'onebot11';
http = {
enable: false,
host: '',

View File

@@ -12,18 +12,21 @@ import {
import {
AtType,
ChatType,
ElementType, FaceIndex,
FaceIndex,
Friend,
FriendV2,
GrayTipElementSubType,
Group,
GroupMember,
IMAGE_HTTP_HOST, IMAGE_HTTP_HOST_NT, mFaceCache,
mFaceCache,
Peer,
RawMessage,
SelfInfo,
Sex,
SimpleInfo,
TipGroupElementType,
User
User,
VideoElement
} from '@/core/entities';
import { EventType } from './event/OB11BaseEvent';
import { encodeCQCode } from './cqcode';
@@ -33,8 +36,8 @@ import { OB11GroupUploadNoticeEvent } from './event/notice/OB11GroupUploadNotice
import { OB11GroupNoticeEvent } from './event/notice/OB11GroupNoticeEvent';
import { calcQQLevel } from '../common/utils/qqlevel';
import { log, logDebug, logError } from '../common/utils/log';
import { sleep } from '../common/utils/helper';
import { log, logDebug, logError, logWarn } from '../common/utils/log';
import { sleep, UUIDConverter } from '../common/utils/helper';
import { OB11GroupTitleEvent } from './event/notice/OB11GroupTitleEvent';
import { OB11GroupCardEvent } from './event/notice/OB11GroupCardEvent';
import { OB11GroupDecreaseEvent } from './event/notice/OB11GroupDecreaseEvent';
@@ -43,6 +46,7 @@ import { deleteGroup, getGroupMember, groupMembers, selfInfo, tempGroupCodeMap }
import { NTQQFileApi, NTQQGroupApi, NTQQMsgApi, NTQQUserApi } from '@/core/apis';
import { OB11GroupMsgEmojiLikeEvent } from '@/onebot11/event/notice/OB11MsgEmojiLikeEvent';
import { OB11FriendPokeEvent, OB11GroupPokeEvent } from './event/notice/OB11PokeEvent';
import { OB11FriendAddNoticeEvent } from './event/notice/OB11FriendAddNoticeEvent';
import { OB11BaseNoticeEvent } from './event/notice/OB11BaseNoticeEvent';
import { OB11GroupEssenceEvent } from './event/notice/OB11GroupEssenceEvent';
import { MessageUnique } from '@/common/utils/MessageUnique';
@@ -75,7 +79,12 @@ export class OB11Constructor {
if (msg.chatType == ChatType.group) {
resMsg.sub_type = 'normal'; // 这里go-cqhttp是group而onebot11标准是normal, 蛋疼
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) {
resMsg.sender.role = OB11Constructor.groupMemberRole(member.role);
resMsg.sender.nickname = member.nick;
@@ -83,8 +92,9 @@ export class OB11Constructor {
}
else if (msg.chatType == ChatType.friend) {
resMsg.sub_type = 'friend';
const user = await NTQQUserApi.getUserDetailInfoByUin(msg.senderUin!);
resMsg.sender.nickname = user.info.nick;
resMsg.sender.nickname = (await NTQQUserApi.getUserDetailInfo(msg.senderUid)).nick;
//const user = await NTQQUserApi.getUserDetailInfoByUin(msg.senderUin!);
//resMsg.sender.nickname = user.info.nick;
}
else if (msg.chatType == ChatType.temp) {
resMsg.sub_type = 'group';
@@ -94,33 +104,40 @@ export class OB11Constructor {
}
}
for (const element of msg.elements) {
const message_data: OB11MessageData | any = {
data: {},
type: 'unknown'
let message_data: OB11MessageData = {
data: {} as any,
type: 'unknown' as any
};
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) {
// message_data["data"]["mention"] = "all"
message_data['data']['qq'] = 'all';
qq = 'all';
}
else {
const atUid = element.textElement.atNtUid;
const { atNtUid, content } = element.textElement;
let atQQ = element.textElement.atUid;
if (!atQQ || atQQ === '0') {
const atMember = await getGroupMember(msg.peerUin, atUid);
const atMember = await getGroupMember(msg.peerUin, atNtUid);
if (atMember) {
atQQ = atMember.uin;
}
}
if (atQQ) {
// message_data["data"]["mention"] = atQQ
message_data['data']['qq'] = atQQ;
qq = atQQ as `${number}`;
name = content.replace('@', '');
}
}
message_data = {
type: OB11MessageDataType.at,
data: {
qq: qq!,
name
}
};
}
else if (element.textElement) {
message_data['type'] = 'text';
message_data['type'] = OB11MessageDataType.text;
let text = element.textElement.content;
if (!text.trim()) {
@@ -133,41 +150,42 @@ export class OB11Constructor {
message_data['data']['text'] = text;
}
else if (element.replyElement) {
message_data['type'] = 'reply';
// log("收到回复消息", element.replyElement.replayMsgSeq)
message_data['type'] = OB11MessageDataType.reply;
//log("收到回复消息", element.replyElement);
try {
let replyMsg = await NTQQMsgApi.getMsgsBySeqAndCount(
{
chatType: msg.chatType,
peerUid: msg.peerUid,
guildId: '',
},
element.replyElement.replayMsgSeq,
1,
true,
true
);
// console.log(JSON.stringify(retData, null, 2));
// const replyMsg = await NTQQMsgApi.getMsgsBySeqAndCount({ peerUid: msg.peerUid, guildId: '', chatType: msg.chatType }, element.replyElement.replayMsgSeq, 1, true, true);
if (replyMsg) {
message_data['data']['id'] = MessageUnique.createMsg({ peerUid: msg.peerUid, guildId: '', chatType: msg.chatType }, replyMsg.msgList[0].msgId)?.toString();
//做这么多都是因为NC速度太快 可能nt还没有写入数据库
//const records = msg.records.find(msgRecord => msgRecord.msgId === element.replyElement.sourceMsgIdInRecords);
const peer = {
chatType: msg.chatType,
peerUid: msg.peerUid,
guildId: '',
};
let replyMsg: RawMessage | undefined;
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) {
replyMsg = (await NTQQMsgApi.getSingleMsg(peer, element.replyElement.replayMsgSeq)).msgList[0];
}
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) {
message_data['type'] = 'unknown' as any;
message_data['data'] = undefined;
logError('获取不到引用的消息', e.stack, element.replyElement.replayMsgSeq);
}
}
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.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
// let currentRKey = "CAQSKAB6JWENi5LMk0kc62l8Pm3Jn1dsLZHyRLAnNmHGoZ3y_gDZPqZt-64"
try {
message_data['data']['url'] = await NTQQFileApi.getImageUrl(element.picElement);
@@ -177,63 +195,97 @@ export class OB11Constructor {
//console.log(message_data['data']['url'])
// message_data["data"]["file_id"] = element.picElement.fileUuid
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) {
const videoOrFileElement = element.videoElement || element.fileElement;
const ob11MessageDataType = element.videoElement ? OB11MessageDataType.video : OB11MessageDataType.file;
const videoDownUrl = element.videoElement ? await NTQQFileApi.getVideoUrl(msg, element) : videoOrFileElement.filePath;
message_data['type'] = ob11MessageDataType;
message_data['data']['file'] = videoOrFileElement.fileName;
else if (element.fileElement) {
const FileElement = element.fileElement;
message_data['type'] = OB11MessageDataType.file;
message_data['data']['file'] = FileElement.fileName;
message_data['data']['path'] = FileElement.filePath;
message_data['data']['url'] = FileElement.filePath;
message_data['data']['file_id'] = UUIDConverter.encode(msg.peerUin, msg.msgId);
message_data['data']['file_size'] = FileElement.fileSize;
await NTQQFileApi.addFileCache({
peerUid: msg.peerUid,
chatType: msg.chatType,
guildId: '',
},
msg.msgId,
msg.msgSeq,
msg.senderUid,
element.elementId,
element.elementType.toString(),
FileElement.fileSize,
FileElement.fileName
);
}
else if (element.videoElement) {
const videoElement: VideoElement = element.videoElement;
//读取视频链接并兜底
let videoUrl;//Array
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']['url'] = videoDownUrl;
message_data['data']['file_id'] = videoOrFileElement.fileUuid;
message_data['data']['file_size'] = videoOrFileElement.fileSize;
if (!element.videoElement) {
// dbUtil.addFileCache({
// msgId: msg.msgId,
// name: videoOrFileElement.fileName,
// path: videoOrFileElement.filePath,
// size: parseInt(videoOrFileElement.fileSize || '0'),
// uuid: videoOrFileElement.fileUuid || '',
// url: '',
// element: element.videoElement || element.fileElement,
// elementType: element.videoElement ? ElementType.VIDEO : ElementType.FILE,
// elementId: element.elementId
// }).then();
// }
}
message_data['data']['file_id'] = UUIDConverter.encode(msg.peerUin, msg.msgId);
message_data['data']['file_size'] = videoElement.fileSize;
await NTQQFileApi.addFileCache({
peerUid: msg.peerUid,
chatType: msg.chatType,
guildId: '',
},
msg.msgId,
msg.msgSeq,
msg.senderUid,
element.elementId,
element.elementType.toString(),
videoElement.fileSize || '0',
videoElement.fileName
);
}
else if (element.pttElement) {
message_data['type'] = OB11MessageDataType.voice;
message_data['data']['file'] = element.pttElement.fileName;
message_data['data']['path'] = element.pttElement.filePath;
// message_data["data"]["file_id"] = element.pttElement.fileUuid
//message_data['data']['file_id'] = element.pttElement.fileUuid;
message_data['data']['file_id'] = UUIDConverter.encode(msg.peerUin, msg.msgId);
message_data['data']['file_size'] = element.pttElement.fileSize;
// dbUtil.addFileCache({
// name: element.pttElement.fileName,
// path: element.pttElement.filePath,
// size: parseInt(element.pttElement.fileSize) || 0,
// url: '',
// uuid: element.pttElement.fileUuid || '',
// msgId: msg.msgId,
// element: element.pttElement,
// elementType: ElementType.PTT,
// elementId: element.elementId
// }).then();
await NTQQFileApi.addFileCache({
peerUid: msg.peerUid,
chatType: msg.chatType,
guildId: '',
},
msg.msgId,
msg.msgSeq,
msg.senderUid,
element.elementId,
element.elementType.toString(),
element.pttElement.fileSize || '0',
element.pttElement.fileUuid || ''
);
//以uuid作为文件名
}
else if (element.arkElement) {
message_data['type'] = OB11MessageDataType.json;
@@ -277,15 +329,16 @@ export class OB11Constructor {
message_data['type'] = OB11MessageDataType.forward;
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);
if (messagePostFormat === 'string') {
(resMsg.message as string) += cqCode;
}
else (resMsg.message as OB11MessageData[]).push(message_data);
resMsg.raw_message += cqCode;
}
}
resMsg.raw_message = resMsg.raw_message.trim();
return resMsg;
@@ -298,6 +351,7 @@ export class OB11Constructor {
if (element.grayTipElement) {
if (element.grayTipElement.subElementType == GrayTipElementSubType.MEMBER_NEW_TITLE) {
const json = JSON.parse(element.grayTipElement.jsonGrayTipElement.jsonStr);
if (element.grayTipElement.jsonGrayTipElement.busiId == 1061) {
//判断业务类型
//Poke事件
@@ -306,11 +360,17 @@ export class OB11Constructor {
pokedetail = pokedetail.filter(item => item.uid);
//console.log("[NapCat] 群拍一拍 群:", pokedetail, parseInt(msg.peerUid), " ", await NTQQUserApi.getUinByUid(pokedetail[0].uid), "拍了拍", await NTQQUserApi.getUinByUid(pokedetail[1].uid));
if (pokedetail.length == 2) {
return new OB11FriendPokeEvent(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
}
if (element.grayTipElement.subElementType == GrayTipElementSubType.INVITE_NEW_MEMBER) {
//好友添加成功事件
if (element.grayTipElement.xmlElement.templId === '10229' && msg.peerUin !== '') {
return new OB11FriendAddNoticeEvent(parseInt(msg.peerUin));
}
}
}
}
}
@@ -495,7 +555,24 @@ export class OB11Constructor {
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[] {
const data: OB11User[] = [];
friends.forEach(friend => {

View File

@@ -67,15 +67,15 @@ export function encodeCQCode(data: OB11MessageData) {
let result = '[CQ:' + data.type;
for (const name in data.data) {
const value = data.data[name];
try {
// 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}`);
if (value === undefined) {
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 += ']';
return result;

View File

@@ -0,0 +1,11 @@
import { OB11BaseNoticeEvent } from './OB11BaseNoticeEvent';
export class OB11FriendAddNoticeEvent extends OB11BaseNoticeEvent {
notice_type = 'friend_add';
user_id: number;
public constructor(user_Id: number) {
super();
this.user_id = user_Id;
}
}

View File

@@ -10,22 +10,25 @@ class OB11PokeEvent extends OB11BaseNoticeEvent {
}
export class OB11FriendPokeEvent extends OB11PokeEvent {
constructor(user_id: number, target_id: number) {
raw_info: any;
//raw_message nb等框架标准为string
constructor(user_id: number, target_id: number, raw_message: any) {
super();
this.target_id = target_id;
this.user_id = user_id;
this.raw_info = raw_message;
}
}
export class OB11GroupPokeEvent extends OB11PokeEvent {
group_id: number;
raw_message: any;
raw_info: any;
//raw_message nb等框架标准为string
constructor(group_id: number, user_id: number = 0, target_id: number = 0, raw_message: any) {
super();
this.group_id = group_id;
this.target_id = target_id;
this.user_id = user_id;
this.raw_message = raw_message;
this.raw_info = raw_message;
}
}

View File

@@ -22,7 +22,7 @@ import { ob11ReverseWebsockets } from '@/onebot11/server/ws/ReverseWebsocket';
import { getGroup, getGroupMember, groupMembers, selfInfo, tempGroupCodeMap } from '@/core/data';
import { BuddyListener, GroupListener, NodeIKernelBuddyListener } from '@/core/listeners';
import { OB11FriendRequestEvent } from '@/onebot11/event/request/OB11FriendRequest';
import { NTQQGroupApi, NTQQUserApi } from '@/core/apis';
import { NTQQGroupApi, NTQQUserApi, WebApi } from '@/core/apis';
import { log, logDebug, logError, setLogSelfInfo } from '@/common/utils/log';
import { OB11GroupRequestEvent } from '@/onebot11/event/request/OB11GroupRequest';
import { OB11GroupAdminNoticeEvent } from '@/onebot11/event/notice/OB11GroupAdminNoticeEvent';
@@ -41,7 +41,7 @@ export interface LineDevice {
device_name: string;
device_kind: string;
}
export let DeviceList = new Array<LineDevice>();
export const DeviceList = new Array<LineDevice>();
//peer->cached(boolen)
// const PokeCache = new Map<string, boolean>();
@@ -209,6 +209,7 @@ export class NapCatOnebot11 {
// 临时会话更新 tempGroupCodeMap uid -> source/GroupCode
};
msgListener.onRecvMsg = async (msg) => {
//console.log('ob11 onRecvMsg', JSON.stringify(msg, null, 2));
// logDebug('收到消息', msg);
for (const m of msg) {
@@ -240,16 +241,25 @@ export class NapCatOnebot11 {
};
msgListener.onMsgInfoListUpdate = (msgList) => {
this.postRecallMsg(msgList).then().catch(logError);
for (const msg of msgList.filter(e => e.senderUin == selfInfo.uin)) {
// console.log(msg);
if (msg.sendStatus == 2) {
//完成后再post
OB11Constructor.message(msg).then((_msg) => {
_msg.target_id = parseInt(msg.peerUin);
if (ob11Config.reportSelfMessage) {
msg.id = MessageUnique.createMsg({ chatType: msg.chatType, peerUid: msg.peerUid, guildId: '' }, msg.msgId);
this.postReceiveMsg([msg]).then().catch(logError);
} else {
logMessage(_msg as OB11Message).then().catch(logError);
}
}).catch(logError);
}
}
};
msgListener.onAddSendMsg = (msg) => {
OB11Constructor.message(msg).then((_msg) => {
_msg.target_id = parseInt(msg.peerUin);
logMessage(_msg as OB11Message).then().catch(logError);
}).catch(logError);
if (ob11Config.reportSelfMessage) {
msg.id = MessageUnique.createMsg({ chatType: msg.chatType, peerUid: msg.peerUid, guildId: '' }, msg.msgId);
this.postReceiveMsg([msg]).then().catch(logError);
}
};
napCatCore.addListener(msgListener);
logDebug('ob11 msg listener added');
@@ -281,7 +291,7 @@ export class NapCatOnebot11 {
}
};
groupListener.onMemberInfoChange = async (groupCode: string, changeType: number, members: Map<string, GroupMember>) => {
//console.log("ob11 onMemberInfoChange", groupCode, changeType, members)
// console.log("ob11 onMemberInfoChange", groupCode, changeType, members)
if (changeType === 1) {
let member;
for (const [key, value] of members) {
@@ -323,11 +333,12 @@ export class NapCatOnebot11 {
const isPrivilege = role === 3 || role === 4;
for (const member of members.values()) {
//console.log(member?.isDelete, role, isPrivilege);
if (member?.isDelete && !isPrivilege && selfInfo.uin !== member.uin) {
log('[群聊] 群组 ', groupCode, ' 成员' + member.uin + '退出');
const groupDecreaseEvent = new OB11GroupDecreaseEvent(parseInt(groupCode), parseInt(member.uin), 0, 'leave');// 不知道怎么出去的
postOB11Event(groupDecreaseEvent, true);
}
// Develop Mlikiowa Taged: 暂时屏蔽这个方案 考虑onMemberListChange
// if (member?.isDelete && !isPrivilege && selfInfo.uin !== member.uin) {
// log('[群聊] 群组 ', groupCode, ' 成员' + member.uin + '退出');
// const groupDecreaseEvent = new OB11GroupDecreaseEvent(parseInt(groupCode), parseInt(member.uin), 0, 'leave');// 不知道怎么出去的
// postOB11Event(groupDecreaseEvent, true);
// }
}
};
groupListener.onJoinGroupNotify = (...notify) => {
@@ -415,7 +426,7 @@ export class NapCatOnebot11 {
if (check_http_ws_equal(NewOb11) || check_http_ws_equal(OldConfig)) {
// http与ws共站 需要同步重启
if (isHttpChanged || isWsChanged) {
log("http与ws进行热重载")
log('http与ws进行热重载');
ob11WebsocketServer.stop();
ob11HTTPServer.stop();
if (NewOb11.http.enable) {
@@ -432,7 +443,7 @@ export class NapCatOnebot11 {
} else {
// http重启逻辑
if (isHttpChanged) {
log("http进行热重载")
log('http进行热重载');
ob11HTTPServer.stop();
if (NewOb11.http.enable) {
ob11HTTPServer.start(NewOb11.http.port, NewOb11.http.host);
@@ -441,7 +452,7 @@ export class NapCatOnebot11 {
// ws重启逻辑
if (isWsChanged) {
log("ws进行热重载")
log('ws进行热重载');
ob11WebsocketServer.stop();
if (NewOb11.ws.enable) {
ob11WebsocketServer.start(NewOb11.ws.port, NewOb11.ws.host);
@@ -451,7 +462,7 @@ export class NapCatOnebot11 {
// 反向ws重启逻辑
if (isWsReverseChanged) {
log("反向ws进行热重载")
log('反向ws进行热重载');
ob11ReverseWebsockets.stop();
if (NewOb11.reverseWs.enable) {
ob11ReverseWebsockets.start();
@@ -514,7 +525,8 @@ export class NapCatOnebot11 {
} catch (e: any) {
logError('获取群通知的成员信息失败', notify, e.stack.toString());
}
} else if ([GroupNotifyTypes.JOIN_REQUEST].includes(notify.type)) {
//notify.status == 1 表示未处理 2表示处理完成
} else if ([GroupNotifyTypes.JOIN_REQUEST].includes(notify.type) && notify.status == 1) {
logDebug('有加群请求');
const groupRequestEvent = new OB11GroupRequestEvent();
groupRequestEvent.group_id = parseInt(notify.group.groupCode);
@@ -583,7 +595,7 @@ export class NapCatOnebot11 {
async postFriendRequest(reqs: FriendRequest[]) {
for (const req of reqs) {
if (req.isDecide && req.reqType !== BuddyReqType.KMEINITIATORWAITPEERCONFIRM) {
if (!!req.isInitiator || (req.isDecide && req.reqType !== BuddyReqType.KMEINITIATORWAITPEERCONFIRM)) {
continue;
}
const friendRequestEvent = new OB11FriendRequestEvent();

View File

@@ -10,8 +10,8 @@ import { postOB11Event } from '@/onebot11/server/postOB11Event';
class OB11HTTPServer extends HttpServerBase {
name = 'OneBot V11 server';
handleFailed(res: Response, payload: any, e: any) {
res.send(OB11Response.error(e.stack.toString(), 200));
handleFailed(res: Response, payload: any, e: Error) {
res.send(OB11Response.error(e?.stack?.toString() || e.message || "Error Handle", 200));
}
protected listen(port: number, host: string) {

View File

@@ -7,6 +7,8 @@ export interface OB11User {
age?: number;
qid?: string;
login_days?: number;
categroyName?:string;
categoryId?:number;
}
export enum OB11UserSex {

View File

@@ -114,6 +114,7 @@ export interface OB11MessageAt {
type: OB11MessageDataType.at
data: {
qq: `${number}` | 'all'
name?: string
}
}
@@ -177,13 +178,20 @@ export interface OB11MessageMarkdown {
}
}
export interface OB11MessageForward {
type: OB11MessageDataType.forward
data: {
id: string
}
}
export type OB11MessageData =
OB11MessageText |
OB11MessageFace | OB11MessageMFace |
OB11MessageAt | OB11MessageReply |
OB11MessageImage | OB11MessageRecord | OB11MessageFile | OB11MessageVideo |
OB11MessageNode | OB11MessageIdMusic | OB11MessageCustomMusic | OB11MessageJson |
OB11MessageDice | OB11MessageRPS | OB11MessageMarkdown
OB11MessageDice | OB11MessageRPS | OB11MessageMarkdown | OB11MessageForward
export interface OB11PostSendMsg {
message_type?: 'private' | 'group'

View File

@@ -1 +1 @@
export const version = '1.6.7';
export const version = '1.7.8';

View File

@@ -27,18 +27,19 @@ export async function InitWebUi() {
}
app.use(express.json());
// 初始服务
app.all('/', (_req, res) => {
// WebUI只在config.prefix所示路径上提供服务可配合Nginx挂载到子目录中
app.all(config.prefix + '/', (_req, res) => {
res.json({
msg: 'NapCat WebAPI is now running!',
});
});
// 配置静态文件服务,提供./static目录下的文件服务访问路径为/webui
app.use('/webui', express.static(resolve(__dirname, './static')));
app.use(config.prefix + '/webui', express.static(resolve(__dirname, './static')));
//挂载API接口
app.use('/api', ALLRouter);
app.listen(config.port, async () => {
log(`[NapCat] [WebUi] Current WebUi is running at IP:${config.port}`);
app.use(config.prefix + '/api', ALLRouter);
app.listen(config.port, config.host, async () => {
log(`[NapCat] [WebUi] Current WebUi is running at http://${config.host}:${config.port}${config.prefix}`);
log(`[NapCat] [WebUi] Login URL is http://${config.host}:${config.port}${config.prefix}/webui`);
log(`[NapCat] [WebUi] Login Token is ${config.token}`);
});
}
}

View File

@@ -12,7 +12,33 @@ const __dirname = dirname(__filename);
// 限制尝试端口的次数,避免死循环
const MAX_PORT_TRY = 100;
async function tryUsePort(port: number, tryCount: number = 0): Promise<number> {
async function tryUseHost(host: string): Promise<string> {
return new Promise(async (resolve, reject) => {
try {
const server = net.createServer();
server.on('listening', () => {
server.close();
resolve(host);
});
server.on('error', (err: any) => {
if (err.code === 'EADDRNOTAVAIL') {
reject('主机地址验证失败,可能为非本机地址');
} else {
reject(`遇到错误: ${err.code}`);
}
});
// 尝试监听 让系统随机分配一个端口
server.listen(0, host);
} catch (error) {
// 这里捕获到的错误应该是启动服务器时的同步错误
reject(`服务器启动时发生错误: ${error}`);
}
});
}
async function tryUsePort(port: number, host: string, tryCount: number = 0): Promise<number> {
return new Promise(async (resolve, reject) => {
try {
const server = net.createServer();
@@ -25,7 +51,7 @@ async function tryUsePort(port: number, tryCount: number = 0): Promise<number> {
if (err.code === 'EADDRINUSE') {
if (tryCount < MAX_PORT_TRY) {
// 使用循环代替递归
resolve(tryUsePort(port + 1, tryCount + 1));
resolve(tryUsePort(port + 1, host, tryCount + 1));
} else {
reject(`端口尝试失败,达到最大尝试次数: ${MAX_PORT_TRY}`);
}
@@ -35,7 +61,7 @@ async function tryUsePort(port: number, tryCount: number = 0): Promise<number> {
});
// 尝试监听端口
server.listen(port);
server.listen(port, host);
} catch (error) {
// 这里捕获到的错误应该是启动服务器时的同步错误
reject(`服务器启动时发生错误: ${error}`);
@@ -44,44 +70,73 @@ async function tryUsePort(port: number, tryCount: number = 0): Promise<number> {
}
export interface WebUiConfigType {
host: string;
port: number;
prefix: string;
token: string;
loginRate: number
}
// 读取当前目录下名为 webui.json 的配置文件,如果不存在则创建初始化配置文件
class WebUiConfigWrapper {
WebUiConfigData: WebUiConfigType | undefined = undefined;
private applyDefaults<T>(obj: Partial<T>, defaults: T): T {
return { ...defaults, ...obj };
}
async GetWebUIConfig(): Promise<WebUiConfigType> {
if (this.WebUiConfigData) {
return this.WebUiConfigData;
}
const defaultconfig: WebUiConfigType = {
host: '0.0.0.0',
port: 6099,
prefix: '',
token: '', // 默认先填空,空密码无法登录
loginRate: 3
};
try {
defaultconfig.token = Math.random().toString(36).slice(2); //生成随机密码
} catch (e) {
logError('随机密码生成失败', e);
}
try {
const configPath = resolve(__dirname, './config/webui.json');
const config: WebUiConfigType = {
port: 6099,
token: Math.random().toString(36).slice(2),//生成随机密码
loginRate: 3
};
if (!existsSync(configPath)) {
writeFileSync(configPath, JSON.stringify(config, null, 4));
writeFileSync(configPath, JSON.stringify(defaultconfig, null, 4));
}
const fileContent = readFileSync(configPath, 'utf-8');
const parsedConfig = JSON.parse(fileContent) as WebUiConfigType;
// 更新配置字段后新增字段可能会缺失,同步一下
const parsedConfig = this.applyDefaults(JSON.parse(fileContent) as Partial<WebUiConfigType>, defaultconfig);
// 修正端口占用情况
const [err, data] = await tryUsePort(parsedConfig.port).then(data => [null, data as number]).catch(err => [err, null]);
parsedConfig.port = data;
if (err) {
//一般没那么离谱 如果真有这么离谱 考虑下 向外抛出异常
if (!parsedConfig.prefix.startsWith('/')) parsedConfig.prefix = '/' + parsedConfig.prefix;
if (parsedConfig.prefix.endsWith('/')) parsedConfig.prefix = parsedConfig.prefix.slice(0, -1);
// 配置已经被操作过了,还是回写一下吧,不然新配置不会出现在配置文件里
writeFileSync(configPath, JSON.stringify(parsedConfig, null, 4));
// 不希望回写的配置放后面
// 查询主机地址是否可用
const [host_err, host] = await tryUseHost(parsedConfig.host).then(data => [null, data as string]).catch(err => [err, null]);
if (host_err) {
logError('host不可用', host_err);
parsedConfig.port = 0; // 设置为0禁用WebUI
} else {
parsedConfig.host = host;
// 修正端口占用情况
const [port_err, port] = await tryUsePort(parsedConfig.port, parsedConfig.host).then(data => [null, data as number]).catch(err => [err, null]);
if (port_err) {
logError('port不可用', port_err);
parsedConfig.port = 0; // 设置为0禁用WebUI
} else {
parsedConfig.port = port;
}
}
this.WebUiConfigData = parsedConfig;
return this.WebUiConfigData;
} catch (e) {
logError('读取配置文件失败', e);
}
return {} as WebUiConfigType; // 理论上这行代码到不了,为了保持函数完整性而保留
return defaultconfig; // 理论上这行代码到不了,到了只能返回默认配置了
}
}
export const WebUiConfig = new WebUiConfigWrapper();
export const WebUiConfig = new WebUiConfigWrapper();

View File

@@ -15,7 +15,7 @@ async function onSettingWindowCreated(view: Element) {
} else if (configKey.length === 3) {
ob11Config[configKey[1]][configKey[2]] = value;
}
OB11ConfigWrapper.SetOB11Config(ob11Config);
// OB11ConfigWrapper.SetOB11Config(ob11Config); // 只有当点保存时才下发配置,而不是在修改值后立即下发
};
const parser = new DOMParser();
@@ -29,7 +29,7 @@ async function onSettingWindowCreated(view: Element) {
SettingItem(
'<span id="napcat-update-title">Napcat</span>',
undefined,
SettingButton('V1.6.8', 'napcat-update-button', 'secondary')
SettingButton('V1.7.8', 'napcat-update-button', 'secondary')
),
]),
SettingList([
@@ -192,7 +192,7 @@ async function onSettingWindowCreated(view: Element) {
// 外链按钮
doc.querySelector('#open-github')?.addEventListener('click', () => {
window.open('https://napneko.github.io/', '_blank');
window.open('https://github.com/NapNeko/NapCatQQ', '_blank');
});
doc.querySelector('#open-telegram')?.addEventListener('click', () => {
window.open('https://t.me/+nLZEnpne-pQ1OWFl');
@@ -201,7 +201,7 @@ async function onSettingWindowCreated(view: Element) {
window.open('https://qm.qq.com/q/bDnHRG38aI');
});
doc.querySelector('#open-docs')?.addEventListener('click', () => {
window.open('https://github.com/NapNeko/NapCatQQ');
window.open('https://napneko.github.io/', '_blank');
});
// 生成反向地址列表
const buildHostListItem = (

View File

@@ -38,7 +38,7 @@ class WebUiApiOB11ConfigWrapper {
this.retCredential = Credential;
}
async GetOB11Config(): Promise<OB11Config> {
const ConfigResponse = await fetch('/api/OB11Config/GetConfig', {
const ConfigResponse = await fetch('../api/OB11Config/GetConfig', {
method: 'POST',
headers: {
Authorization: 'Bearer ' + this.retCredential,
@@ -54,7 +54,7 @@ class WebUiApiOB11ConfigWrapper {
return {} as OB11Config;
}
async SetOB11Config(config: OB11Config): Promise<boolean> {
const ConfigResponse = await fetch('/api/OB11Config/SetConfig', {
const ConfigResponse = await fetch('../api/OB11Config/SetConfig', {
method: 'POST',
headers: {
Authorization: 'Bearer ' + this.retCredential,

View File

@@ -1,5 +1,7 @@
{
"host": "0.0.0.0",
"port": 6099,
"prefix": "",
"token": "random",
"loginRate": 3

View File

@@ -160,7 +160,7 @@
</div>
<script>
async function GetQQLoginQrcode(retCredential) {
let QQLoginResponse = await fetch('/api/QQLogin/GetQQLoginQrcode', {
let QQLoginResponse = await fetch('../api/QQLogin/GetQQLoginQrcode', {
method: 'POST',
headers: {
'Authorization': "Bearer " + retCredential,
@@ -180,7 +180,7 @@
return "";
}
async function CheckQQLoginStatus(retCredential) {
let QQLoginResponse = await fetch('/api/QQLogin/CheckLoginStatus', {
let QQLoginResponse = await fetch('../api/QQLogin/CheckLoginStatus', {
method: 'POST',
headers: {
'Authorization': "Bearer " + retCredential,
@@ -200,7 +200,7 @@
return false;
}
async function GetQQQucickLoginList(retCredential) {
let QQLoginResponse = await fetch('/api/QQLogin/GetQuickLoginList', {
let QQLoginResponse = await fetch('../api/QQLogin/GetQuickLoginList', {
method: 'POST',
headers: {
'Authorization': "Bearer " + retCredential,
@@ -216,7 +216,7 @@
return [];
}
async function SetQuickLogin(uin, retCredential) {
let QQLoginResponse = await fetch('/api/QQLogin/SetQuickLogin', {
let QQLoginResponse = await fetch('../api/QQLogin/SetQuickLogin', {
method: 'POST',
headers: {
'Authorization': "Bearer " + retCredential,

View File

@@ -1,7 +1,7 @@
const SettingList = (items, title, isCollapsible = false, direction = "column") => {
return `<setting-section ${title && !isCollapsible ? `data-title="${title}"` : ""}>
return `<setting-section ${""}>
<setting-panel>
<setting-list ${direction ? `data-direction="${direction}"` : ""} ${isCollapsible ? "is-collapsible" : ""} ${title && isCollapsible ? `data-title="${title}"` : ""}>
<setting-list ${direction ? `data-direction="${direction}"` : ""} ${isCollapsible ? "is-collapsible" : ""} ${""}>
${items.join("")}
</setting-list>
</setting-panel>
@@ -66,16 +66,13 @@ window.customElements.define(
window[`${isHidden ? "remove" : "add"}EventListener`]("pointerdown", windowPointerDown);
};
const windowPointerDown = ({ target }) => {
if (!this.contains(target))
buttonClick();
if (!this.contains(target)) buttonClick();
};
this._button.addEventListener("click", buttonClick);
this._context.addEventListener("click", ({ target }) => {
if (target.tagName !== "SETTING-OPTION")
return;
if (target.tagName !== "SETTING-OPTION") return;
buttonClick();
if (target.hasAttribute("is-selected"))
return;
if (target.hasAttribute("is-selected")) return;
this.querySelectorAll("setting-option[is-selected]").forEach((dom) => dom.toggleAttribute("is-selected"));
target.toggleAttribute("is-selected");
this._text.value = target.textContent;
@@ -95,9 +92,9 @@ window.customElements.define(
}
);
const SettingSelect = (items, configKey, configValue) => {
return `<ob-setting-select ${configKey ? `data-config-key="${configKey}"` : ""}>
return `<ob-setting-select ${`data-config-key="${configKey}"` }>
${items.map((e, i) => {
return SettingOption(e.text, e.value, configKey && configValue ? configValue === e.value : i === 0);
return SettingOption(e.text, e.value, configValue ? configValue === e.value : i === 0);
}).join("")}
</ob-setting-select>`;
};
@@ -108,7 +105,7 @@ class WebUiApiOB11ConfigWrapper {
this.retCredential = Credential;
}
async GetOB11Config() {
const ConfigResponse = await fetch("/api/OB11Config/GetConfig", {
const ConfigResponse = await fetch("../api/OB11Config/GetConfig", {
method: "POST",
headers: {
Authorization: "Bearer " + this.retCredential,
@@ -124,7 +121,7 @@ class WebUiApiOB11ConfigWrapper {
return {};
}
async SetOB11Config(config) {
const ConfigResponse = await fetch("/api/OB11Config/SetConfig", {
const ConfigResponse = await fetch("../api/OB11Config/SetConfig", {
method: "POST",
headers: {
Authorization: "Bearer " + this.retCredential,
@@ -154,7 +151,6 @@ async function onSettingWindowCreated(view) {
} else if (configKey.length === 3) {
ob11Config[configKey[1]][configKey[2]] = value;
}
OB11ConfigWrapper.SetOB11Config(ob11Config);
};
const parser = new DOMParser();
const doc = parser.parseFromString(
@@ -167,7 +163,7 @@ async function onSettingWindowCreated(view) {
SettingItem(
'<span id="napcat-update-title">Napcat</span>',
void 0,
SettingButton("V1.6.8", "napcat-update-button", "secondary")
SettingButton("V1.7.8", "napcat-update-button", "secondary")
)
]),
SettingList([
@@ -326,7 +322,7 @@ async function onSettingWindowCreated(view) {
"text/html"
);
doc.querySelector("#open-github")?.addEventListener("click", () => {
window.open("https://napneko.github.io/", "_blank");
window.open("https://github.com/NapNeko/NapCatQQ", "_blank");
});
doc.querySelector("#open-telegram")?.addEventListener("click", () => {
window.open("https://t.me/+nLZEnpne-pQ1OWFl");
@@ -335,7 +331,7 @@ async function onSettingWindowCreated(view) {
window.open("https://qm.qq.com/q/bDnHRG38aI");
});
doc.querySelector("#open-docs")?.addEventListener("click", () => {
window.open("https://github.com/NapNeko/NapCatQQ");
window.open("https://napneko.github.io/", "_blank");
});
const buildHostListItem = (type, host, index, inputAttrs = {}) => {
const dom = {
@@ -431,19 +427,15 @@ async function onSettingWindowCreated(view) {
dom.addEventListener("click", () => {
const active = dom.getAttribute("is-active") == void 0;
setOB11Config(dom.dataset.configKey, active);
if (active)
dom.setAttribute("is-active", "");
else
dom.removeAttribute("is-active");
if (active) dom.setAttribute("is-active", "");
else dom.removeAttribute("is-active");
if (!isEmpty(dom.dataset.controlDisplayId)) {
const displayDom = document.querySelector(
//@ts-expect-error 等待修复
`#${dom.dataset.controlDisplayId}`
);
if (active)
displayDom?.removeAttribute("is-hidden");
else
displayDom?.setAttribute("is-hidden", "");
if (active) displayDom?.removeAttribute("is-hidden");
else displayDom?.setAttribute("is-hidden", "");
}
});
});

View File

@@ -10,7 +10,7 @@
<body>
<script>
async function CheckQQLoginStatus(retCredential) {
let QQLoginResponse = await fetch('/api/QQLogin/CheckLoginStatus', {
let QQLoginResponse = await fetch('../api/QQLogin/CheckLoginStatus', {
method: 'POST',
headers: {
'Authorization': "Bearer " + retCredential,
@@ -30,7 +30,7 @@
return false;
}
async function CheckWebUiLogined(retCredential) {
let LoginResponse = await fetch('/api/auth/check', {
let LoginResponse = await fetch('../api/auth/check', {
method: 'POST',
headers: {
'Authorization': "Bearer " + retCredential,

View File

@@ -86,7 +86,7 @@
let data = "";
try {
let loginResponse = await fetch('/api/auth/login', {
let loginResponse = await fetch('../api/auth/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
@@ -99,7 +99,7 @@
//登录成功
let retCredential = loginResponseJson.data.Credential;
localStorage.setItem('auth', retCredential);
let QQLoginResponse = await fetch('/api/QQLogin/CheckLoginStatus', {
let QQLoginResponse = await fetch('../api/QQLogin/CheckLoginStatus', {
method: 'POST',
headers: {
'Authorization': "Bearer " + retCredential,

View File

@@ -1,6 +1,5 @@
describe('MessageUniqueWrapper', () => {
let messageUniqueWrapper: MessageUniqueWrapper;
let messageUniqueWrapper: MessageUniqueWrapper;
beforeEach(() => {
messageUniqueWrapper = new MessageUniqueWrapper();
});
@@ -91,4 +90,4 @@ describe('MessageUniqueWrapper', () => {
expect(shortId).toBeUndefined();
});
});
});

View File

@@ -26,9 +26,9 @@ if (process.env.NAPCAT_BUILDSYS == 'linux') {
} else if (process.env.NAPCAT_BUILDSYS == 'win32') {
if (process.env.NAPCAT_BUILDARCH == 'x64') {
}
startScripts = ['./script/napcat.ps1', './script/napcat.bat', './script/napcat-utf8.bat', './script/napcat-utf8.ps1', './script/napcat-log.ps1', './script/NapCat.164.bat', './script/napcat-9912.ps1', './script/napcat-9912-utf8.ps1', './script/napcat-9912.bat', './script/napcat-9912-utf8.bat'];
startScripts = ['./script/dbghelp.dll', './script/BootWay05.ps1', './script/napcat-9912.ps1', './script/napcat-9912-utf8.ps1', './script/napcat-9912.bat', './script/napcat-9912-utf8.bat'];
} else {
startScripts = ['./script/napcat.sh', './script/napcat.ps1', './script/napcat.bat', './script/napcat-utf8.bat', './script/napcat-utf8.ps1', './script/napcat-log.ps1', './script/napcat-9912.ps1', './script/napcat-9912-utf8.ps1', './script/napcat-9912.bat', './script/napcat-9912-utf8.bat'];
startScripts = ['./script/dbghelp.dll', './script/BootWay05.ps1', './script/napcat.sh', './script/napcat.ps1', './script/napcat.bat', './script/napcat-utf8.bat', './script/napcat-utf8.ps1', './script/napcat-log.ps1', './script/napcat-9912.ps1', './script/napcat-9912-utf8.ps1', './script/napcat-9912.bat', './script/napcat-9912-utf8.bat'];
}
const baseConfigPlugin: PluginOption[] = [