mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2025-07-19 12:03:37 +00:00
Compare commits
151 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
41748c0b3f | ||
![]() |
1ce8be3c7e | ||
![]() |
32778acf57 | ||
![]() |
a3c71473ae | ||
![]() |
aceece7e90 | ||
![]() |
52efb4f9ef | ||
![]() |
6b0d96fe8d | ||
![]() |
ad052821b0 | ||
![]() |
da7636e60c | ||
![]() |
ef01dd0d77 | ||
![]() |
03f7d4673f | ||
![]() |
94e9c87978 | ||
![]() |
501bbbe4df | ||
![]() |
c9122a3fee | ||
![]() |
8a289d014e | ||
![]() |
ddadd38151 | ||
![]() |
0b8d0e3cac | ||
![]() |
eeb27d38bc | ||
![]() |
491a79ec96 | ||
![]() |
f429db61af | ||
![]() |
2881099602 | ||
![]() |
672ae8decf | ||
![]() |
2abc7e541d | ||
![]() |
45b1f369ac | ||
![]() |
3b5d2c8f6f | ||
![]() |
5376e16c9f | ||
![]() |
af052242fa | ||
![]() |
85e0b71545 | ||
![]() |
1206d1fcf6 | ||
![]() |
f7534dc438 | ||
![]() |
97f317254e | ||
![]() |
9eaf51e15f | ||
![]() |
7221f4ac02 | ||
![]() |
1bb6dce239 | ||
![]() |
d13db5e8eb | ||
![]() |
040b5535f3 | ||
![]() |
b44e1618fb | ||
![]() |
1e13483bc3 | ||
![]() |
f9519d3923 | ||
![]() |
86cdfbb79b | ||
![]() |
a70585e854 | ||
![]() |
040d0a8635 | ||
![]() |
efa512ab21 | ||
![]() |
9b04aed8b3 | ||
![]() |
7087eafe37 | ||
![]() |
c81c4af653 | ||
![]() |
c05cc9dd02 | ||
![]() |
1a0da00f2d | ||
![]() |
31b0c1d3d7 | ||
![]() |
53c1d40bcf | ||
![]() |
97cacb4383 | ||
![]() |
e03905abaf | ||
![]() |
06eba28b4c | ||
![]() |
bbfeac46dd | ||
![]() |
2fe4da094a | ||
![]() |
b454d8c0f9 | ||
![]() |
1f9b5453cc | ||
![]() |
3261791e99 | ||
![]() |
3bb12e3f45 | ||
![]() |
1dc2f7e5a2 | ||
![]() |
2531b08538 | ||
![]() |
9fcfb5493c | ||
![]() |
4576354c51 | ||
![]() |
1dcf2ef0c6 | ||
![]() |
3642c65e8c | ||
![]() |
40e105994a | ||
![]() |
f2ee973882 | ||
![]() |
3aa30792bf | ||
![]() |
6e336fa78e | ||
![]() |
900027a6b7 | ||
![]() |
38bdca2409 | ||
![]() |
7196e476bf | ||
![]() |
e0fd3785d9 | ||
![]() |
b53ebb6c2a | ||
![]() |
1ea80f4447 | ||
![]() |
627d3c0a7a | ||
![]() |
182cccfc71 | ||
![]() |
6a3713e86c | ||
![]() |
788da4e4f1 | ||
![]() |
fd26d34e19 | ||
![]() |
e9fcdc7d2e | ||
![]() |
0fe4911d01 | ||
![]() |
d4fb09fa80 | ||
![]() |
e6d5a37236 | ||
![]() |
79fd10ac10 | ||
![]() |
a2e6095e44 | ||
![]() |
64530471a0 | ||
![]() |
e31e831309 | ||
![]() |
cf6871df9b | ||
![]() |
482e7f1c75 | ||
![]() |
aab501e31e | ||
![]() |
ceec9e5e1b | ||
![]() |
aadebb3cc5 | ||
![]() |
657ddd3341 | ||
![]() |
62127b6d48 | ||
![]() |
f5f405796f | ||
![]() |
39873947a3 | ||
![]() |
a1079dd948 | ||
![]() |
4eeabcc9e0 | ||
![]() |
c3568d07e8 | ||
![]() |
1adb4a4ba8 | ||
![]() |
6d0020533c | ||
![]() |
4e6af0a655 | ||
![]() |
00f726b515 | ||
![]() |
035aa32305 | ||
![]() |
62ea4b98e1 | ||
![]() |
4be821137d | ||
![]() |
7fba9960bf | ||
![]() |
876bfbd3cb | ||
![]() |
edde2c210b | ||
![]() |
f956d96d94 | ||
![]() |
c2296fd900 | ||
![]() |
0feed5b640 | ||
![]() |
93904dcb1b | ||
![]() |
86cbdf793a | ||
![]() |
56b1b9b598 | ||
![]() |
f7ec3ae131 | ||
![]() |
01d11d6213 | ||
![]() |
74a316e758 | ||
![]() |
d20c5185a4 | ||
![]() |
da965e7b39 | ||
![]() |
3fbed815a5 | ||
![]() |
152be29739 | ||
![]() |
e521740a44 | ||
![]() |
ee047e8bc1 | ||
![]() |
5eaa9ca347 | ||
![]() |
40f79ee816 | ||
![]() |
f0dcef7981 | ||
![]() |
3c09ff13d0 | ||
![]() |
7158f25f37 | ||
![]() |
54f805b6e4 | ||
![]() |
70c4651fbf | ||
![]() |
962d3c064f | ||
![]() |
c6a459a111 | ||
![]() |
b0242ccb62 | ||
![]() |
53f5277b08 | ||
![]() |
90b54435b5 | ||
![]() |
12a1681b42 | ||
![]() |
4277cb3f3c | ||
![]() |
8353d53589 | ||
![]() |
9e94d98cfb | ||
![]() |
b6ec1aaa9b | ||
![]() |
e7e8763f1c | ||
![]() |
515c1af676 | ||
![]() |
6fa7a973ba | ||
![]() |
3e63f509bc | ||
![]() |
b3b02e781a | ||
![]() |
6d83921e20 | ||
![]() |
30bd372d45 | ||
![]() |
63254b7e55 | ||
![]() |
f4c08d93f4 |
2
.env.universal
Normal file
2
.env.universal
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
VITE_BUILD_TYPE = Production
|
||||||
|
VITE_BUILD_PLATFORM = Universal
|
11
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
11
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -10,13 +10,12 @@ body:
|
|||||||
在提交新的 Bug 反馈前,请确保您:
|
在提交新的 Bug 反馈前,请确保您:
|
||||||
* 已经搜索了现有的 issues,并且没有找到可以解决您问题的方法
|
* 已经搜索了现有的 issues,并且没有找到可以解决您问题的方法
|
||||||
* 不与现有的某一 issue 重复
|
* 不与现有的某一 issue 重复
|
||||||
* 不涉及[已经停止维护的特性](https://github.com/NapNeko/NapCatQQ?tab=readme-ov-file#挥别昨日),例如 CQ 码
|
|
||||||
- type: input
|
- type: input
|
||||||
id: system-version
|
id: system-version
|
||||||
attributes:
|
attributes:
|
||||||
label: 系统版本
|
label: 系统版本
|
||||||
description: 运行 QQNT 的系统版本
|
description: 运行 QQNT 的系统版本
|
||||||
placeholder: Windows 10 Pro Workstation 22H2
|
placeholder: Windows 11 24H2
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: input
|
- type: input
|
||||||
@@ -24,7 +23,7 @@ body:
|
|||||||
attributes:
|
attributes:
|
||||||
label: QQNT 版本
|
label: QQNT 版本
|
||||||
description: 可在 QQNT 的「关于」的设置页中找到
|
description: 可在 QQNT 的「关于」的设置页中找到
|
||||||
placeholder: 9.9.7-21804
|
placeholder: 9.9.16-29927
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: input
|
- type: input
|
||||||
@@ -40,21 +39,21 @@ body:
|
|||||||
attributes:
|
attributes:
|
||||||
label: OneBot 客户端
|
label: OneBot 客户端
|
||||||
description: 连接至 NapCat 的客户端版本信息
|
description: 连接至 NapCat 的客户端版本信息
|
||||||
placeholder: Overflow 2.16.0-2cf7991-SNAPSHOT
|
placeholder: Karin 1.0.0
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: textarea
|
- type: textarea
|
||||||
id: what-happened
|
id: what-happened
|
||||||
attributes:
|
attributes:
|
||||||
label: 发生了什么?
|
label: 发生了什么?
|
||||||
description: 填写你认为的 NapCat 的不正常行为
|
description: 填写你认为的 NapCat 的异常行为
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: textarea
|
- type: textarea
|
||||||
id: how-reproduce
|
id: how-reproduce
|
||||||
attributes:
|
attributes:
|
||||||
label: 如何复现
|
label: 如何复现
|
||||||
description: 填写应当如何操作才能触发这个不正常行为
|
description: 填写应当如何操作才能触发这个异常行为
|
||||||
placeholder: |
|
placeholder: |
|
||||||
1. xxx
|
1. xxx
|
||||||
2. xxx
|
2. xxx
|
||||||
|
11
.github/dependabot.yml
vendored
11
.github/dependabot.yml
vendored
@@ -1,11 +1,6 @@
|
|||||||
# To get started with Dependabot version updates, you'll need to specify which
|
|
||||||
# package ecosystems to update and where the package manifests are located.
|
|
||||||
# Please see the documentation for all configuration options:
|
|
||||||
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
|
|
||||||
|
|
||||||
version: 2
|
version: 2
|
||||||
updates:
|
updates:
|
||||||
- package-ecosystem: "npm" # See documentation for possible values
|
- package-ecosystem: "npm"
|
||||||
directory: "/" # Location of package manifests
|
directory: "/"
|
||||||
schedule:
|
schedule:
|
||||||
interval: "daily"
|
interval: "weekly"
|
||||||
|
210
LICENSE
210
LICENSE
@@ -1,201 +1,19 @@
|
|||||||
Apache License
|
Limited Redistribution License for NapCat
|
||||||
Version 2.0, January 2004
|
|
||||||
http://www.apache.org/licenses/
|
|
||||||
|
|
||||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
Copyright © 2024 Mlikiowa
|
||||||
|
|
||||||
1. Definitions.
|
1. Usage and Reproduction:
|
||||||
|
- Unauthorized use, reproduction, modification, or distribution of this code is prohibited without explicit permission from the main author of the NapCat repository.
|
||||||
|
|
||||||
|
2. Redistribution:
|
||||||
|
- Redistribution of this code is permitted, provided that the full text of this license is included, and the source and copyright information is clearly stated.
|
||||||
|
- Minor modifications and extensions are allowed for redistribution purposes, but the modified code must not be publicly released.
|
||||||
|
|
||||||
"License" shall mean the terms and conditions for use, reproduction,
|
3. Non-Commercial Use:
|
||||||
and distribution as defined by Sections 1 through 9 of this document.
|
- This code is not to be used for any commercial purposes.
|
||||||
|
|
||||||
"Licensor" shall mean the copyright owner or entity authorized by
|
4. Additional Permissions:
|
||||||
the copyright owner that is granting the License.
|
- Any rights not explicitly addressed in this license must be requested from and granted by the main author of the NapCat repository.
|
||||||
|
|
||||||
"Legal Entity" shall mean the union of the acting entity and all
|
5. Disclaimer:
|
||||||
other entities that control, are controlled by, or are under common
|
- This code is provided "as is," without any express or implied warranties, including but not limited to the implied warranties of merchantability and fitness for a particular purpose. In no event shall the author be liable for any damages or other liability arising from, out of, or in connection with the use or distribution of this code.
|
||||||
control with that entity. For the purposes of this definition,
|
|
||||||
"control" means (i) the power, direct or indirect, to cause the
|
|
||||||
direction or management of such entity, whether by contract or
|
|
||||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
||||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
||||||
|
|
||||||
"You" (or "Your") shall mean an individual or Legal Entity
|
|
||||||
exercising permissions granted by this License.
|
|
||||||
|
|
||||||
"Source" form shall mean the preferred form for making modifications,
|
|
||||||
including but not limited to software source code, documentation
|
|
||||||
source, and configuration files.
|
|
||||||
|
|
||||||
"Object" form shall mean any form resulting from mechanical
|
|
||||||
transformation or translation of a Source form, including but
|
|
||||||
not limited to compiled object code, generated documentation,
|
|
||||||
and conversions to other media types.
|
|
||||||
|
|
||||||
"Work" shall mean the work of authorship, whether in Source or
|
|
||||||
Object form, made available under the License, as indicated by a
|
|
||||||
copyright notice that is included in or attached to the work
|
|
||||||
(an example is provided in the Appendix below).
|
|
||||||
|
|
||||||
"Derivative Works" shall mean any work, whether in Source or Object
|
|
||||||
form, that is based on (or derived from) the Work and for which the
|
|
||||||
editorial revisions, annotations, elaborations, or other modifications
|
|
||||||
represent, as a whole, an original work of authorship. For the purposes
|
|
||||||
of this License, Derivative Works shall not include works that remain
|
|
||||||
separable from, or merely link (or bind by name) to the interfaces of,
|
|
||||||
the Work and Derivative Works thereof.
|
|
||||||
|
|
||||||
"Contribution" shall mean any work of authorship, including
|
|
||||||
the original version of the Work and any modifications or additions
|
|
||||||
to that Work or Derivative Works thereof, that is intentionally
|
|
||||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
||||||
or by an individual or Legal Entity authorized to submit on behalf of
|
|
||||||
the copyright owner. For the purposes of this definition, "submitted"
|
|
||||||
means any form of electronic, verbal, or written communication sent
|
|
||||||
to the Licensor or its representatives, including but not limited to
|
|
||||||
communication on electronic mailing lists, source code control systems,
|
|
||||||
and issue tracking systems that are managed by, or on behalf of, the
|
|
||||||
Licensor for the purpose of discussing and improving the Work, but
|
|
||||||
excluding communication that is conspicuously marked or otherwise
|
|
||||||
designated in writing by the copyright owner as "Not a Contribution."
|
|
||||||
|
|
||||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
||||||
on behalf of whom a Contribution has been received by Licensor and
|
|
||||||
subsequently incorporated within the Work.
|
|
||||||
|
|
||||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
copyright license to reproduce, prepare Derivative Works of,
|
|
||||||
publicly display, publicly perform, sublicense, and distribute the
|
|
||||||
Work and such Derivative Works in Source or Object form.
|
|
||||||
|
|
||||||
3. Grant of Patent License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
(except as stated in this section) patent license to make, have made,
|
|
||||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
||||||
where such license applies only to those patent claims licensable
|
|
||||||
by such Contributor that are necessarily infringed by their
|
|
||||||
Contribution(s) alone or by combination of their Contribution(s)
|
|
||||||
with the Work to which such Contribution(s) was submitted. If You
|
|
||||||
institute patent litigation against any entity (including a
|
|
||||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
||||||
or a Contribution incorporated within the Work constitutes direct
|
|
||||||
or contributory patent infringement, then any patent licenses
|
|
||||||
granted to You under this License for that Work shall terminate
|
|
||||||
as of the date such litigation is filed.
|
|
||||||
|
|
||||||
4. Redistribution. You may reproduce and distribute copies of the
|
|
||||||
Work or Derivative Works thereof in any medium, with or without
|
|
||||||
modifications, and in Source or Object form, provided that You
|
|
||||||
meet the following conditions:
|
|
||||||
|
|
||||||
(a) You must give any other recipients of the Work or
|
|
||||||
Derivative Works a copy of this License; and
|
|
||||||
|
|
||||||
(b) You must cause any modified files to carry prominent notices
|
|
||||||
stating that You changed the files; and
|
|
||||||
|
|
||||||
(c) You must retain, in the Source form of any Derivative Works
|
|
||||||
that You distribute, all copyright, patent, trademark, and
|
|
||||||
attribution notices from the Source form of the Work,
|
|
||||||
excluding those notices that do not pertain to any part of
|
|
||||||
the Derivative Works; and
|
|
||||||
|
|
||||||
(d) If the Work includes a "NOTICE" text file as part of its
|
|
||||||
distribution, then any Derivative Works that You distribute must
|
|
||||||
include a readable copy of the attribution notices contained
|
|
||||||
within such NOTICE file, excluding those notices that do not
|
|
||||||
pertain to any part of the Derivative Works, in at least one
|
|
||||||
of the following places: within a NOTICE text file distributed
|
|
||||||
as part of the Derivative Works; within the Source form or
|
|
||||||
documentation, if provided along with the Derivative Works; or,
|
|
||||||
within a display generated by the Derivative Works, if and
|
|
||||||
wherever such third-party notices normally appear. The contents
|
|
||||||
of the NOTICE file are for informational purposes only and
|
|
||||||
do not modify the License. You may add Your own attribution
|
|
||||||
notices within Derivative Works that You distribute, alongside
|
|
||||||
or as an addendum to the NOTICE text from the Work, provided
|
|
||||||
that such additional attribution notices cannot be construed
|
|
||||||
as modifying the License.
|
|
||||||
|
|
||||||
You may add Your own copyright statement to Your modifications and
|
|
||||||
may provide additional or different license terms and conditions
|
|
||||||
for use, reproduction, or distribution of Your modifications, or
|
|
||||||
for any such Derivative Works as a whole, provided Your use,
|
|
||||||
reproduction, and distribution of the Work otherwise complies with
|
|
||||||
the conditions stated in this License.
|
|
||||||
|
|
||||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
||||||
any Contribution intentionally submitted for inclusion in the Work
|
|
||||||
by You to the Licensor shall be under the terms and conditions of
|
|
||||||
this License, without any additional terms or conditions.
|
|
||||||
Notwithstanding the above, nothing herein shall supersede or modify
|
|
||||||
the terms of any separate license agreement you may have executed
|
|
||||||
with Licensor regarding such Contributions.
|
|
||||||
|
|
||||||
6. Trademarks. This License does not grant permission to use the trade
|
|
||||||
names, trademarks, service marks, or product names of the Licensor,
|
|
||||||
except as required for reasonable and customary use in describing the
|
|
||||||
origin of the Work and reproducing the content of the NOTICE file.
|
|
||||||
|
|
||||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
||||||
agreed to in writing, Licensor provides the Work (and each
|
|
||||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
||||||
implied, including, without limitation, any warranties or conditions
|
|
||||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
||||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
||||||
appropriateness of using or redistributing the Work and assume any
|
|
||||||
risks associated with Your exercise of permissions under this License.
|
|
||||||
|
|
||||||
8. Limitation of Liability. In no event and under no legal theory,
|
|
||||||
whether in tort (including negligence), contract, or otherwise,
|
|
||||||
unless required by applicable law (such as deliberate and grossly
|
|
||||||
negligent acts) or agreed to in writing, shall any Contributor be
|
|
||||||
liable to You for damages, including any direct, indirect, special,
|
|
||||||
incidental, or consequential damages of any character arising as a
|
|
||||||
result of this License or out of the use or inability to use the
|
|
||||||
Work (including but not limited to damages for loss of goodwill,
|
|
||||||
work stoppage, computer failure or malfunction, or any and all
|
|
||||||
other commercial damages or losses), even if such Contributor
|
|
||||||
has been advised of the possibility of such damages.
|
|
||||||
|
|
||||||
9. Accepting Warranty or Additional Liability. While redistributing
|
|
||||||
the Work or Derivative Works thereof, You may choose to offer,
|
|
||||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
||||||
or other liability obligations and/or rights consistent with this
|
|
||||||
License. However, in accepting such obligations, You may act only
|
|
||||||
on Your own behalf and on Your sole responsibility, not on behalf
|
|
||||||
of any other Contributor, and only if You agree to indemnify,
|
|
||||||
defend, and hold each Contributor harmless for any liability
|
|
||||||
incurred by, or claims asserted against, such Contributor by reason
|
|
||||||
of your accepting any such warranty or additional liability.
|
|
||||||
|
|
||||||
END OF TERMS AND CONDITIONS
|
|
||||||
|
|
||||||
APPENDIX: How to apply the Apache License to your work.
|
|
||||||
|
|
||||||
To apply the Apache License to your work, attach the following
|
|
||||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
||||||
replaced with your own identifying information. (Don't include
|
|
||||||
the brackets!) The text should be enclosed in the appropriate
|
|
||||||
comment syntax for the file format. We also recommend that a
|
|
||||||
file or class name and description of purpose be included on the
|
|
||||||
same "printed page" as the copyright notice for easier
|
|
||||||
identification within third-party archives.
|
|
||||||
|
|
||||||
Copyright [yyyy] [name of copyright owner]
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
|
10
README.md
10
README.md
@@ -34,7 +34,7 @@ NapCatQQ 是现代化的基于 NTQQ 的 Bot 协议端实现
|
|||||||
|
|
||||||
|
|
||||||
## 回家旅途
|
## 回家旅途
|
||||||
[QQ Group](https://qm.qq.com/q/NWP25OeV0c)
|
[QQ Group](https://qm.qq.com/q/I6LU87a0Yq)
|
||||||
|
|
||||||
## 感谢他们
|
## 感谢他们
|
||||||
感谢 [Lagrange](https://github.com/LagrangeDev/Lagrange.Core) 对本项目的大力支持 参考部分代码 已获授权
|
感谢 [Lagrange](https://github.com/LagrangeDev/Lagrange.Core) 对本项目的大力支持 参考部分代码 已获授权
|
||||||
@@ -45,12 +45,8 @@ NapCatQQ 是现代化的基于 NTQQ 的 Bot 协议端实现
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 延缓Native模块与NapCat对新版QQ适配
|
## 特殊感谢
|
||||||
为未来持续与高效的使用Native模块 模块代码转为完全非Git仓库的本地保存源码 并进行相关重构
|
[LLOneBot](https://github.com/LLOneBot/LLOneBot) 相关的开发曾参与本项目
|
||||||
|
|
||||||
同时为了保证稳定 NapCat 本体通常会在3 Week+的周期进行新版本适配
|
|
||||||
|
|
||||||
因此此时推荐使用release指定版本
|
|
||||||
|
|
||||||
## 开源附加
|
## 开源附加
|
||||||
|
|
||||||
|
BIN
external/LiteLoaderWrapper.zip
vendored
BIN
external/LiteLoaderWrapper.zip
vendored
Binary file not shown.
Binary file not shown.
@@ -1,9 +1,9 @@
|
|||||||
{
|
{
|
||||||
"name": "qq-chat",
|
"name": "qq-chat",
|
||||||
"version": "9.9.16-29456",
|
"version": "9.9.16-29927",
|
||||||
"verHash": "dd395162",
|
"verHash": "3e273e30",
|
||||||
"linuxVersion": "3.2.13-29456",
|
"linuxVersion": "3.2.13-29927",
|
||||||
"linuxVerHash": "e379390a",
|
"linuxVerHash": "833d113c",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "QQ",
|
"description": "QQ",
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
"qd": "externals/devtools/cli/index.js"
|
"qd": "externals/devtools/cli/index.js"
|
||||||
},
|
},
|
||||||
"main": "./loadNapCat.js",
|
"main": "./loadNapCat.js",
|
||||||
"buildVersion": "29456",
|
"buildVersion": "29927",
|
||||||
"isPureShell": true,
|
"isPureShell": true,
|
||||||
"isByteCodeShell": true,
|
"isByteCodeShell": true,
|
||||||
"platform": "win32",
|
"platform": "win32",
|
||||||
|
@@ -4,7 +4,7 @@
|
|||||||
"name": "NapCatQQ",
|
"name": "NapCatQQ",
|
||||||
"slug": "NapCat.Framework",
|
"slug": "NapCat.Framework",
|
||||||
"description": "高性能的 OneBot 11 协议实现",
|
"description": "高性能的 OneBot 11 协议实现",
|
||||||
"version": "4.1.13",
|
"version": "4.2.16",
|
||||||
"icon": "./logo.png",
|
"icon": "./logo.png",
|
||||||
"authors": [
|
"authors": [
|
||||||
{
|
{
|
||||||
|
@@ -1,13 +1,13 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/svg+xml" href="./vite.svg" />
|
<link rel="icon" type="image/svg+xml" href="./logo_webui.png" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>NapCat WebUI</title>
|
<title>NapCat WebUI</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
<script type="module" src="./src/main.ts"></script>
|
<script type="module" src="./src/main.ts"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@@ -14,7 +14,7 @@
|
|||||||
"qrcode": "^1.5.4",
|
"qrcode": "^1.5.4",
|
||||||
"tdesign-icons-vue-next": "^0.3.3",
|
"tdesign-icons-vue-next": "^0.3.3",
|
||||||
"tdesign-vue-next": "^1.10.3",
|
"tdesign-vue-next": "^1.10.3",
|
||||||
"vue": "^3.5.12",
|
"vue": "^3.5.13",
|
||||||
"vue-router": "^4.4.5"
|
"vue-router": "^4.4.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
BIN
napcat.webui/public/logo_webui.png
Normal file
BIN
napcat.webui/public/logo_webui.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 201 KiB |
@@ -1,7 +1,112 @@
|
|||||||
<template>
|
<template>
|
||||||
<div id="app">
|
<div id="app" theme-mode="dark">
|
||||||
<router-view />
|
<router-view />
|
||||||
</div>
|
</div>
|
||||||
|
<div v-if="show">
|
||||||
|
<t-sticky-tool shape="round" placement="right-bottom" :offset="[-50, 10]" @click="changeTheme">
|
||||||
|
<t-sticky-item label="浅色" popup="切换浅色模式">
|
||||||
|
<template #icon><sunny-icon /></template>
|
||||||
|
</t-sticky-item>
|
||||||
|
<t-sticky-item label="深色" popup="切换深色模式">
|
||||||
|
<template #icon><mode-dark-icon /></template>
|
||||||
|
</t-sticky-item>
|
||||||
|
<t-sticky-item label="自动" popup="跟随系统">
|
||||||
|
<template #icon><control-platform-icon /></template>
|
||||||
|
</t-sticky-item>
|
||||||
|
</t-sticky-tool>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts"></script>
|
<script setup lang="ts">
|
||||||
|
import { onBeforeUnmount, onMounted, onUnmounted, ref } from 'vue';
|
||||||
|
import { ControlPlatformIcon, ModeDarkIcon, SunnyIcon } from 'tdesign-icons-vue-next';
|
||||||
|
const smallScreen = window.matchMedia('(max-width: 768px)');
|
||||||
|
interface Item {
|
||||||
|
label: string;
|
||||||
|
popup: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Context {
|
||||||
|
item: Item;
|
||||||
|
}
|
||||||
|
enum ThemeMode {
|
||||||
|
Dark = 'dark',
|
||||||
|
Light = 'light',
|
||||||
|
Auto = 'auto',
|
||||||
|
}
|
||||||
|
const themeLabelMap: Record<string, ThemeMode> = {
|
||||||
|
"浅色": ThemeMode.Light,
|
||||||
|
"深色": ThemeMode.Dark,
|
||||||
|
"自动": ThemeMode.Auto,
|
||||||
|
};
|
||||||
|
const show = ref<boolean>(true);
|
||||||
|
const createSetThemeAttributeFunction = () => {
|
||||||
|
let mediaQueryForAutoTheme: MediaQueryList | null = null;
|
||||||
|
return (mode: ThemeMode | null) => {
|
||||||
|
const element = document.documentElement;
|
||||||
|
if (mode === ThemeMode.Dark) {
|
||||||
|
element.setAttribute('theme-mode', ThemeMode.Dark);
|
||||||
|
} else if (mode === ThemeMode.Light) {
|
||||||
|
element.removeAttribute('theme-mode');
|
||||||
|
} else if (mode === ThemeMode.Auto) {
|
||||||
|
mediaQueryForAutoTheme = window.matchMedia('(prefers-color-scheme: dark)');
|
||||||
|
const handleMediaChange = (e: MediaQueryListEvent) => {
|
||||||
|
if (e.matches) {
|
||||||
|
element.setAttribute('theme-mode', ThemeMode.Dark);
|
||||||
|
} else {
|
||||||
|
element.removeAttribute('theme-mode');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
mediaQueryForAutoTheme.addEventListener('change', handleMediaChange);
|
||||||
|
const event = new Event('change');
|
||||||
|
Object.defineProperty(event, 'matches', {
|
||||||
|
value: mediaQueryForAutoTheme.matches,
|
||||||
|
writable: false,
|
||||||
|
});
|
||||||
|
mediaQueryForAutoTheme.dispatchEvent(event);
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
if (mediaQueryForAutoTheme) {
|
||||||
|
mediaQueryForAutoTheme.removeEventListener('change', handleMediaChange);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const setThemeAttribute = createSetThemeAttributeFunction();
|
||||||
|
|
||||||
|
const getStoredTheme = (): ThemeMode | null => {
|
||||||
|
return localStorage.getItem('theme') as ThemeMode | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const initTheme = () => {
|
||||||
|
const storedTheme = getStoredTheme();
|
||||||
|
if (storedTheme === null) {
|
||||||
|
setThemeAttribute(ThemeMode.Auto);
|
||||||
|
} else {
|
||||||
|
setThemeAttribute(storedTheme);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const changeTheme = (context: Context) => {
|
||||||
|
const themeLabel = themeLabelMap[context.item.label] as ThemeMode;
|
||||||
|
console.log(themeLabel);
|
||||||
|
setThemeAttribute(themeLabel);
|
||||||
|
localStorage.setItem('theme', themeLabel);
|
||||||
|
};
|
||||||
|
const haddingFbars = () => {
|
||||||
|
show.value = !smallScreen.matches;
|
||||||
|
if (smallScreen.matches) {
|
||||||
|
localStorage.setItem('theme', 'auto');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
onMounted(() => {
|
||||||
|
initTheme();
|
||||||
|
haddingFbars();
|
||||||
|
window.addEventListener('resize', haddingFbars);
|
||||||
|
});
|
||||||
|
onUnmounted(() => {
|
||||||
|
window.removeEventListener('resize', haddingFbars);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<style scoped></style>
|
||||||
|
BIN
napcat.webui/src/assets/logo_webui.png
Normal file
BIN
napcat.webui/src/assets/logo_webui.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 201 KiB |
@@ -74,6 +74,26 @@ export class QQLoginManager {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
public async checkQQLoginStatusWithQrcode(): Promise<{ qrcodeurl: string; isLogin: string } | undefined> {
|
||||||
|
try {
|
||||||
|
const QQLoginResponse = await fetch(`${this.apiPrefix}/QQLogin/CheckLoginStatus`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
Authorization: 'Bearer ' + this.retCredential,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (QQLoginResponse.status == 200) {
|
||||||
|
const QQLoginResponseJson = await QQLoginResponse.json();
|
||||||
|
if (QQLoginResponseJson.code == 0) {
|
||||||
|
return QQLoginResponseJson.data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error checking QQ login status:', error);
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
public async checkWebUiLogined(): Promise<boolean> {
|
public async checkWebUiLogined(): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
|
@@ -1,16 +1,18 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="dashboard-container">
|
<t-layout class="dashboard-container">
|
||||||
<SidebarMenu :menu-items="menuItems" class="sidebar-menu" />
|
<div ref="menuRef">
|
||||||
<div class="content">
|
<SidebarMenu :menu-items="menuItems" class="sidebar-menu" />
|
||||||
<router-view />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<t-layout>
|
||||||
|
<router-view />
|
||||||
|
</t-layout>
|
||||||
|
</t-layout>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue';
|
import { onMounted, ref } from 'vue';
|
||||||
import SidebarMenu from './webui/Nav.vue';
|
import SidebarMenu from './webui/Nav.vue';
|
||||||
|
import emitter from '@/ts/event-bus';
|
||||||
interface MenuItem {
|
interface MenuItem {
|
||||||
value: string;
|
value: string;
|
||||||
icon: string;
|
icon: string;
|
||||||
@@ -25,6 +27,14 @@ const menuItems = ref<MenuItem[]>([
|
|||||||
{ value: 'item5', icon: 'system-log', label: '日志查看', route: '/dashboard/log-view' },
|
{ value: 'item5', icon: 'system-log', label: '日志查看', route: '/dashboard/log-view' },
|
||||||
{ value: 'item6', icon: 'info-circle', label: '关于我们', route: '/dashboard/about-us' },
|
{ value: 'item6', icon: 'info-circle', label: '关于我们', route: '/dashboard/about-us' },
|
||||||
]);
|
]);
|
||||||
|
const menuRef = ref<HTMLDivElement | null>(null);
|
||||||
|
emitter.on('sendMenu', (event) => {
|
||||||
|
emitter.emit('sendWidth', menuRef.value?.offsetWidth);
|
||||||
|
localStorage.setItem('menuWidth', menuRef.value?.offsetWidth?.toString() || '0');
|
||||||
|
});
|
||||||
|
onMounted(() => {
|
||||||
|
localStorage.setItem('menuWidth', menuRef.value?.offsetWidth?.toString() || '0');
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@@ -32,6 +42,7 @@ const menuItems = ref<MenuItem[]>([
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-menu {
|
.sidebar-menu {
|
||||||
@@ -39,14 +50,6 @@ const menuItems = ref<MenuItem[]>([
|
|||||||
z-index: 2;
|
z-index: 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
|
||||||
flex: 1;
|
|
||||||
/* padding: 20px; */
|
|
||||||
overflow: auto;
|
|
||||||
position: relative;
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.content {
|
.content {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
|
@@ -1,36 +1,43 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="login-container">
|
<t-card class="layout">
|
||||||
<h2 class="sotheby-font">QQ Login</h2>
|
<div class="login-container">
|
||||||
<div class="login-methods">
|
<h2 class="sotheby-font">QQ Login</h2>
|
||||||
<t-button
|
<div class="login-methods">
|
||||||
id="quick-login"
|
<t-tooltip content="快速登录">
|
||||||
class="login-method"
|
<t-button
|
||||||
:class="{ active: loginMethod === 'quick' }"
|
id="quick-login"
|
||||||
@click="loginMethod = 'quick'"
|
class="login-method"
|
||||||
>Quick Login</t-button
|
:class="{ active: loginMethod === 'quick' }"
|
||||||
>
|
@click="loginMethod = 'quick'"
|
||||||
<t-button
|
>Quick Login</t-button
|
||||||
id="qrcode-login"
|
>
|
||||||
class="login-method"
|
</t-tooltip>
|
||||||
:class="{ active: loginMethod === 'qrcode' }"
|
<t-tooltip content="二维码登录">
|
||||||
@click="loginMethod = 'qrcode'"
|
<t-button
|
||||||
>QR Code</t-button
|
id="qrcode-login"
|
||||||
>
|
class="login-method"
|
||||||
|
:class="{ active: loginMethod === 'qrcode' }"
|
||||||
|
@click="loginMethod = 'qrcode'"
|
||||||
|
>QR Code</t-button
|
||||||
|
>
|
||||||
|
</t-tooltip>
|
||||||
|
</div>
|
||||||
|
<div v-show="loginMethod === 'quick'" id="quick-login-dropdown" class="login-form">
|
||||||
|
<t-select
|
||||||
|
id="quick-login-select"
|
||||||
|
v-model="selectedAccount"
|
||||||
|
placeholder="Select Account"
|
||||||
|
@change="selectAccount"
|
||||||
|
>
|
||||||
|
<t-option v-for="account in quickLoginList" :key="account" :value="account">{{ account }}</t-option>
|
||||||
|
</t-select>
|
||||||
|
</div>
|
||||||
|
<div v-show="loginMethod === 'qrcode'" id="qrcode" class="qrcode">
|
||||||
|
<canvas ref="qrcodeCanvas"></canvas>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-show="loginMethod === 'quick'" id="quick-login-dropdown" class="login-form">
|
<t-footer class="footer">Power By NapCat.WebUi</t-footer>
|
||||||
<t-select
|
</t-card>
|
||||||
id="quick-login-select"
|
|
||||||
v-model="selectedAccount"
|
|
||||||
placeholder="Select Account"
|
|
||||||
@change="selectAccount"
|
|
||||||
>
|
|
||||||
<t-option v-for="account in quickLoginList" :key="account" :value="account">{{ account }}</t-option>
|
|
||||||
</t-select>
|
|
||||||
</div>
|
|
||||||
<div v-show="loginMethod === 'qrcode'" id="qrcode" class="qrcode">
|
|
||||||
<canvas ref="qrcodeCanvas"></canvas>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
@@ -47,10 +54,13 @@ const selectedAccount = ref<string>('');
|
|||||||
const qrcodeCanvas = ref<HTMLCanvasElement | null>(null);
|
const qrcodeCanvas = ref<HTMLCanvasElement | null>(null);
|
||||||
const qqLoginManager = new QQLoginManager(localStorage.getItem('auth') || '');
|
const qqLoginManager = new QQLoginManager(localStorage.getItem('auth') || '');
|
||||||
let heartBeatTimer: number | null = null;
|
let heartBeatTimer: number | null = null;
|
||||||
|
let qrcodeUrl: string = '';
|
||||||
const selectAccount = async (accountName: string): Promise<void> => {
|
const selectAccount = async (accountName: string): Promise<void> => {
|
||||||
const { result, errMsg } = await qqLoginManager.setQuickLogin(accountName);
|
const { result, errMsg } = await qqLoginManager.setQuickLogin(accountName);
|
||||||
if (result) {
|
if (result) {
|
||||||
|
if (heartBeatTimer) {
|
||||||
|
clearInterval(heartBeatTimer);
|
||||||
|
}
|
||||||
await MessagePlugin.success('登录成功即将跳转');
|
await MessagePlugin.success('登录成功即将跳转');
|
||||||
await router.push({ path: '/dashboard/basic-info' });
|
await router.push({ path: '/dashboard/basic-info' });
|
||||||
} else {
|
} else {
|
||||||
@@ -73,19 +83,27 @@ const generateQrCode = (data: string, canvas: HTMLCanvasElement | null): void =>
|
|||||||
};
|
};
|
||||||
|
|
||||||
const HeartBeat = async (): Promise<void> => {
|
const HeartBeat = async (): Promise<void> => {
|
||||||
const isLogined = await qqLoginManager.checkQQLoginStatus();
|
const isLogined = await qqLoginManager.checkQQLoginStatusWithQrcode();
|
||||||
if (isLogined) {
|
if (isLogined?.isLogin) {
|
||||||
if (heartBeatTimer) {
|
if (heartBeatTimer) {
|
||||||
clearInterval(heartBeatTimer);
|
clearInterval(heartBeatTimer);
|
||||||
}
|
}
|
||||||
|
// //判断是否已经调转
|
||||||
|
// if (router.currentRoute.value.path !== '/dashboard/basic-info') {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
await MessagePlugin.success('登录成功即将跳转');
|
||||||
await router.push({ path: '/dashboard/basic-info' });
|
await router.push({ path: '/dashboard/basic-info' });
|
||||||
|
} else if (isLogined?.qrcodeurl && qrcodeUrl !== isLogined.qrcodeurl) {
|
||||||
|
qrcodeUrl = isLogined.qrcodeurl;
|
||||||
|
generateQrCode(qrcodeUrl, qrcodeCanvas.value);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const InitPages = async (): Promise<void> => {
|
const InitPages = async (): Promise<void> => {
|
||||||
quickLoginList.value = await qqLoginManager.getQQQuickLoginList();
|
quickLoginList.value = await qqLoginManager.getQQQuickLoginList();
|
||||||
const qrcodeData = await qqLoginManager.getQQLoginQrcode();
|
qrcodeUrl = await qqLoginManager.getQQLoginQrcode();
|
||||||
generateQrCode(qrcodeData, qrcodeCanvas.value);
|
generateQrCode(qrcodeUrl, qrcodeCanvas.value);
|
||||||
heartBeatTimer = window.setInterval(HeartBeat, 3000);
|
heartBeatTimer = window.setInterval(HeartBeat, 3000);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -95,14 +113,16 @@ onMounted(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
.layout {
|
||||||
|
height: 100vh;
|
||||||
|
}
|
||||||
.login-container {
|
.login-container {
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
background-color: white;
|
|
||||||
max-width: 400px;
|
max-width: 400px;
|
||||||
min-width: 300px;
|
min-width: 300px;
|
||||||
position: relative;
|
position: relative;
|
||||||
margin: 0 auto;
|
margin: 50px auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 600px) {
|
@media (max-width: 600px) {
|
||||||
@@ -161,7 +181,5 @@ onMounted(() => {
|
|||||||
bottom: 20px;
|
bottom: 20px;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
width: 100%;
|
|
||||||
background-color: white;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@@ -1,20 +1,22 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="login-container">
|
<t-card class="layout">
|
||||||
<h2 class="sotheby-font">WebUi Login</h2>
|
<div class="login-container">
|
||||||
<t-form ref="form" :data="formData" colon :label-width="0" @submit="onSubmit">
|
<h2 class="sotheby-font">WebUi Login</h2>
|
||||||
<t-form-item name="password">
|
<t-form ref="form" :data="formData" colon :label-width="0" @submit="onSubmit">
|
||||||
<t-input v-model="formData.token" type="password" clearable placeholder="请输入Token">
|
<t-form-item name="password">
|
||||||
<template #prefix-icon>
|
<t-input v-model="formData.token" type="password" clearable placeholder="请输入Token">
|
||||||
<lock-on-icon />
|
<template #prefix-icon>
|
||||||
</template>
|
<lock-on-icon />
|
||||||
</t-input>
|
</template>
|
||||||
</t-form-item>
|
</t-input>
|
||||||
<t-form-item>
|
</t-form-item>
|
||||||
<t-button theme="primary" type="submit" block>登录</t-button>
|
<t-form-item>
|
||||||
</t-form-item>
|
<t-button theme="primary" type="submit" block>登录</t-button>
|
||||||
</t-form>
|
</t-form-item>
|
||||||
</div>
|
</t-form>
|
||||||
<div class="footer">Power By NapCat.WebUi</div>
|
</div>
|
||||||
|
<t-footer class="footer">Power By NapCat.WebUi</t-footer>
|
||||||
|
</t-card>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
@@ -94,14 +96,16 @@ const onSubmit = async ({ validateResult }: { validateResult: boolean }) => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
.layout {
|
||||||
|
height: 100vh;
|
||||||
|
}
|
||||||
.login-container {
|
.login-container {
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
background-color: white;
|
|
||||||
max-width: 400px;
|
max-width: 400px;
|
||||||
min-width: 300px;
|
min-width: 300px;
|
||||||
position: relative;
|
position: relative;
|
||||||
margin: 0 auto;
|
margin: 50px auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 600px) {
|
@media (max-width: 600px) {
|
||||||
@@ -145,7 +149,5 @@ const onSubmit = async ({ validateResult }: { validateResult: boolean }) => {
|
|||||||
bottom: 20px;
|
bottom: 20px;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
width: 100%;
|
|
||||||
background-color: white;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@@ -1,16 +1,31 @@
|
|||||||
<template>
|
<template>
|
||||||
<t-menu theme="light" default-value="2-1" :collapsed="collapsed" class="sidebar-menu">
|
<t-menu theme="light" default-value="2-1" :collapsed="collapsed" class="sidebar-menu">
|
||||||
<template #logo> </template>
|
<template #logo>
|
||||||
|
<div class="logo">
|
||||||
|
<img class="logo-img" :width="collapsed ? 35 : 'auto'" src="@/assets/logo_webui.png" alt="logo" />
|
||||||
|
<div class="logo-textBox">
|
||||||
|
<div class="logo-text">{{ collapsed ? '' : 'NapCat' }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
<router-link v-for="item in menuItems" :key="item.value" :to="item.route">
|
<router-link v-for="item in menuItems" :key="item.value" :to="item.route">
|
||||||
<t-menu-item :value="item.value" :disabled="item.disabled" class="menu-item">
|
<t-tooltip :disabled="!collapsed" :content="item.label" placement="right">
|
||||||
<template #icon>
|
<t-menu-item :value="item.value" :disabled="item.disabled" class="menu-item">
|
||||||
<t-icon :name="item.icon" />
|
<template #icon>
|
||||||
</template>
|
<t-icon :name="item.icon" />
|
||||||
{{ item.label }}
|
</template>
|
||||||
</t-menu-item>
|
{{ item.label }}
|
||||||
|
</t-menu-item>
|
||||||
|
</t-tooltip>
|
||||||
</router-link>
|
</router-link>
|
||||||
<template #operations>
|
<template #operations>
|
||||||
<t-button class="t-demo-collapse-btn" variant="text" shape="square" @click="changeCollapsed">
|
<t-button
|
||||||
|
:disabled="disBtn"
|
||||||
|
class="t-demo-collapse-btn"
|
||||||
|
variant="text"
|
||||||
|
shape="square"
|
||||||
|
@click="changeCollapsed"
|
||||||
|
>
|
||||||
<template #icon><t-icon :name="iconName" /></template>
|
<template #icon><t-icon :name="iconName" /></template>
|
||||||
</t-button>
|
</t-button>
|
||||||
</template>
|
</template>
|
||||||
@@ -18,7 +33,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, defineProps } from 'vue';
|
import { ref, defineProps, onMounted, watch } from 'vue';
|
||||||
|
import emitter from '@/ts/event-bus';
|
||||||
|
|
||||||
type MenuItem = {
|
type MenuItem = {
|
||||||
value: string;
|
value: string;
|
||||||
@@ -31,15 +47,39 @@ type MenuItem = {
|
|||||||
defineProps<{
|
defineProps<{
|
||||||
menuItems: MenuItem[];
|
menuItems: MenuItem[];
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const collapsed = ref<boolean>(localStorage.getItem('sidebar-collapsed') === 'true');
|
const collapsed = ref<boolean>(localStorage.getItem('sidebar-collapsed') === 'true');
|
||||||
const iconName = ref<string>(collapsed.value ? 'menu-unfold' : 'menu-fold');
|
const iconName = ref<string>(collapsed.value ? 'menu-unfold' : 'menu-fold');
|
||||||
|
const disBtn = ref<boolean>(false);
|
||||||
|
|
||||||
const changeCollapsed = (): void => {
|
const changeCollapsed = (): void => {
|
||||||
collapsed.value = !collapsed.value;
|
collapsed.value = !collapsed.value;
|
||||||
iconName.value = collapsed.value ? 'menu-unfold' : 'menu-fold';
|
iconName.value = collapsed.value ? 'menu-unfold' : 'menu-fold';
|
||||||
localStorage.setItem('sidebar-collapsed', collapsed.value.toString());
|
localStorage.setItem('sidebar-collapsed', collapsed.value.toString());
|
||||||
};
|
};
|
||||||
|
watch(collapsed, (newValue, oldValue) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
emitter.emit('sendMenu', collapsed.value);
|
||||||
|
}, 300);
|
||||||
|
});
|
||||||
|
onMounted(() => {
|
||||||
|
const mediaQuery = window.matchMedia('(max-width: 800px)');
|
||||||
|
const handleMediaChange = (e: MediaQueryListEvent) => {
|
||||||
|
disBtn.value = e.matches;
|
||||||
|
if (e.matches) {
|
||||||
|
collapsed.value = e.matches;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
mediaQuery.addEventListener('change', handleMediaChange);
|
||||||
|
const event = new Event('change');
|
||||||
|
Object.defineProperty(event, 'matches', {
|
||||||
|
value: mediaQuery.matches,
|
||||||
|
writable: false,
|
||||||
|
});
|
||||||
|
mediaQuery.dispatchEvent(event);
|
||||||
|
return () => {
|
||||||
|
mediaQuery.removeEventListener('change', handleMediaChange);
|
||||||
|
};
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@@ -57,12 +97,28 @@ const changeCollapsed = (): void => {
|
|||||||
width: 100px; /* 移动端侧边栏宽度 */
|
width: 100px; /* 移动端侧边栏宽度 */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.logo {
|
||||||
|
display: flex;
|
||||||
|
width: auto;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.logo-img {
|
||||||
|
object-fit: contain;
|
||||||
|
margin-top: 8px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
.logo-textBox {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
.logo-text {
|
.logo-text {
|
||||||
display: block;
|
display: block;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
|
font-size: 22px;
|
||||||
|
font-family: Sotheby, Helvetica, monospace;
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu-item {
|
.menu-item {
|
||||||
|
@@ -19,6 +19,10 @@ import {
|
|||||||
List as TList,
|
List as TList,
|
||||||
Alert as TAlert,
|
Alert as TAlert,
|
||||||
Tag as TTag,
|
Tag as TTag,
|
||||||
|
Descriptions as TDescriptionsProps,
|
||||||
|
DescriptionsItem as TDescriptionsItem,
|
||||||
|
Collapse as TCollapse,
|
||||||
|
CollapsePanel as TCollapsePanel,
|
||||||
ListItem as TListItem,
|
ListItem as TListItem,
|
||||||
Tabs as TTabs,
|
Tabs as TTabs,
|
||||||
TabPanel as TTabPanel,
|
TabPanel as TTabPanel,
|
||||||
@@ -27,10 +31,18 @@ import {
|
|||||||
Popup as TPopup,
|
Popup as TPopup,
|
||||||
Dialog as TDialog,
|
Dialog as TDialog,
|
||||||
Switch as TSwitch,
|
Switch as TSwitch,
|
||||||
|
Tooltip as Tooltip,
|
||||||
|
StickyTool as TStickyTool,
|
||||||
|
StickyItem as TStickyItem,
|
||||||
|
Layout as TLayout,
|
||||||
|
Content as TContent,
|
||||||
|
Footer as TFooter,
|
||||||
|
Aside as TAside,
|
||||||
|
Popconfirm as Tpopconfirm,
|
||||||
|
Empty as TEmpty,
|
||||||
} from 'tdesign-vue-next';
|
} from 'tdesign-vue-next';
|
||||||
import { router } from './router';
|
import { router } from './router';
|
||||||
import 'tdesign-vue-next/es/style/index.css';
|
import 'tdesign-vue-next/es/style/index.css';
|
||||||
|
|
||||||
const app = createApp(App);
|
const app = createApp(App);
|
||||||
app.use(router);
|
app.use(router);
|
||||||
app.use(TButton);
|
app.use(TButton);
|
||||||
@@ -51,6 +63,10 @@ app.use(TLink);
|
|||||||
app.use(TList);
|
app.use(TList);
|
||||||
app.use(TAlert);
|
app.use(TAlert);
|
||||||
app.use(TTag);
|
app.use(TTag);
|
||||||
|
app.use(TDescriptionsProps);
|
||||||
|
app.use(TDescriptionsItem);
|
||||||
|
app.use(TCollapse);
|
||||||
|
app.use(TCollapsePanel);
|
||||||
app.use(TListItem);
|
app.use(TListItem);
|
||||||
app.use(TTabs);
|
app.use(TTabs);
|
||||||
app.use(TTabPanel);
|
app.use(TTabPanel);
|
||||||
@@ -59,4 +75,13 @@ app.use(TCheckbox);
|
|||||||
app.use(TPopup);
|
app.use(TPopup);
|
||||||
app.use(TDialog);
|
app.use(TDialog);
|
||||||
app.use(TSwitch);
|
app.use(TSwitch);
|
||||||
|
app.use(Tooltip);
|
||||||
|
app.use(TStickyTool);
|
||||||
|
app.use(TStickyItem);
|
||||||
|
app.use(TLayout);
|
||||||
|
app.use(TContent);
|
||||||
|
app.use(TFooter);
|
||||||
|
app.use(TAside);
|
||||||
|
app.use(Tpopconfirm);
|
||||||
|
app.use(TEmpty);
|
||||||
app.mount('#app');
|
app.mount('#app');
|
||||||
|
@@ -1,122 +1,300 @@
|
|||||||
<template>
|
<template>
|
||||||
<t-space class="full-space">
|
<div ref="headerBox" class="title">
|
||||||
<template v-if="clientPanelData.length > 0">
|
<t-divider content="网络配置" align="left" />
|
||||||
<t-tabs
|
<t-divider align="right">
|
||||||
v-model="activeTab"
|
<t-button @click="addConfig()">
|
||||||
:addable="true"
|
<template #icon><add-icon /></template>
|
||||||
theme="card"
|
添加配置</t-button>
|
||||||
@add="showAddTabDialog"
|
</t-divider>
|
||||||
@remove="removeTab"
|
</div>
|
||||||
class="full-tabs"
|
<div v-if="loadPage" ref="setting" class="setting">
|
||||||
>
|
<t-tabs ref="tabsRef" :style="{ width: tabsWidth + 'px' }" default-value="all" @change="selectType">
|
||||||
<t-tab-panel
|
<t-tab-panel value="all" label="全部"></t-tab-panel>
|
||||||
v-for="(config, idx) in clientPanelData"
|
<t-tab-panel value="httpServers" label="HTTP 服务器"></t-tab-panel>
|
||||||
:key="idx"
|
<t-tab-panel value="httpClients" label="HTTP 客户端"></t-tab-panel>
|
||||||
:label="config.name"
|
<t-tab-panel value="websocketServers" label="WebSocket 服务器"></t-tab-panel>
|
||||||
:removable="true"
|
<t-tab-panel value="websocketClients" label="WebSocket 客户端"></t-tab-panel>
|
||||||
:value="idx"
|
</t-tabs>
|
||||||
class="full-tab-panel"
|
</div>
|
||||||
>
|
<div v-if="loadPage" class="card-box" :style="{ width: tabsWidth + 'px' }">
|
||||||
<component :is="resolveDynamicComponent(getComponent(config.key))" :config="config.data" />
|
<div class="setting-box" :style="{ maxHeight: cardHeight + 'px' }" v-if="cardConfig.length > 0">
|
||||||
<div class="button-container">
|
<div v-for="(item, index) in cardConfig" :key="index">
|
||||||
<t-button @click="saveConfig" style="width: 100px; height: 40px">保存</t-button>
|
<t-card :title="item.name" :description="item.type" :style="{ width: cardWidth + 'px' }"
|
||||||
|
:header-bordered="true" class="setting-card">
|
||||||
|
<template #actions>
|
||||||
|
<t-space>
|
||||||
|
<edit2-icon size="20px" @click="editConfig(item)"></edit2-icon>
|
||||||
|
<t-popconfirm theme="danger" content="确认删除" @confirm="delConfig(item)">
|
||||||
|
<delete-icon size="20px"></delete-icon>
|
||||||
|
</t-popconfirm>
|
||||||
|
</t-space>
|
||||||
|
</template>
|
||||||
|
<div class="setting-content">
|
||||||
|
<t-card class="card-address" :style="{
|
||||||
|
borderLeft: '7px solid ' + (item.enable ?
|
||||||
|
'var(--td-success-color)' :
|
||||||
|
'var(--td-error-color)')
|
||||||
|
}">
|
||||||
|
<div class="local-box" v-if="item.host&&item.port">
|
||||||
|
<server-filled-icon class="local-icon" size="20px"></server-filled-icon>
|
||||||
|
<strong class="local">{{ item.host }}:{{ item.port }}</strong>
|
||||||
|
<copy-icon class="copy-icon" size="20px" @click="copyText(item.host + ':' + item.port)"></copy-icon>
|
||||||
|
</div>
|
||||||
|
<div class="local-box" v-if="item.url">
|
||||||
|
<server-filled-icon class="local-icon" size="20px"></server-filled-icon>
|
||||||
|
<strong class="local" >{{ item.url }}</strong>
|
||||||
|
<copy-icon class="copy-icon" size="20px" @click="copyText(item.url)"></copy-icon>
|
||||||
|
</div>
|
||||||
|
</t-card>
|
||||||
|
<t-collapse :default-value="[0]" expand-mutex style="margin-top:10px;" class="info-coll">
|
||||||
|
<t-collapse-panel header="基础信息">
|
||||||
|
<t-descriptions size="small" :layout="infoOneCol ? 'vertical' : 'horizontal'"
|
||||||
|
class="setting-base-info">
|
||||||
|
<t-descriptions-item v-if="item.token" label="连接密钥">
|
||||||
|
<div v-if="mediumScreen.matches||largeScreen.matches" class="token-view">
|
||||||
|
<span>{{ showToken ? item.token : '******' }}</span>
|
||||||
|
<browse-icon class="browse-icon" v-if="showToken" size="18px"
|
||||||
|
@click="showToken = false"></browse-icon>
|
||||||
|
<browse-off-icon class="browse-icon" v-else size="18px"
|
||||||
|
@click="showToken = true"></browse-off-icon>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<t-popup :showArrow="true" trigger="click">
|
||||||
|
<t-tag theme="primary">点击查看</t-tag>
|
||||||
|
<template #content>
|
||||||
|
<div @click="copyText(item.token)">{{item.token}}</div>
|
||||||
|
</template>
|
||||||
|
</t-popup>
|
||||||
|
</div>
|
||||||
|
</t-descriptions-item>
|
||||||
|
<t-descriptions-item label="消息格式">{{ item.messagePostFormat }}</t-descriptions-item>
|
||||||
|
</t-descriptions>
|
||||||
|
</t-collapse-panel>
|
||||||
|
<t-collapse-panel header="状态信息">
|
||||||
|
<t-descriptions size="small" :layout="infoOneCol ? 'vertical' : 'horizontal'"
|
||||||
|
class="setting-base-info">
|
||||||
|
<t-descriptions-item v-if="item.hasOwnProperty('debug')" label="调试日志">
|
||||||
|
<t-tag class="tag-item" :theme="item.debug ? 'success' : 'danger'">
|
||||||
|
{{ item.debug ? '开启' : '关闭' }}</t-tag>
|
||||||
|
</t-descriptions-item>
|
||||||
|
<t-descriptions-item v-if="item.hasOwnProperty('enableWebsocket')"
|
||||||
|
label="Websocket 功能">
|
||||||
|
<t-tag class="tag-item" :theme="item.enableWebsocket ? 'success' : 'danger'">
|
||||||
|
{{ item.enableWebsocket ? '启用' : '禁用' }}</t-tag>
|
||||||
|
</t-descriptions-item>
|
||||||
|
<t-descriptions-item v-if="item.hasOwnProperty('enableCors')" label="跨域放行">
|
||||||
|
<t-tag class="tag-item" :theme="item.enableCors ? 'success' : 'danger'">
|
||||||
|
{{ item.enableCors ? '开启' : '关闭' }}</t-tag>
|
||||||
|
</t-descriptions-item>
|
||||||
|
<t-descriptions-item v-if="item.hasOwnProperty('enableForcePushEvent')"
|
||||||
|
label="上报自身消息">
|
||||||
|
<t-tag class="tag-item" :theme="item.reportSelfMessage ? 'success' : 'danger'">
|
||||||
|
{{ item.reportSelfMessage ? '开启' : '关闭' }}</t-tag>
|
||||||
|
</t-descriptions-item>
|
||||||
|
<t-descriptions-item v-if="item.hasOwnProperty('enableForcePushEvent')"
|
||||||
|
label="强制推送事件">
|
||||||
|
<t-tag class="tag-item"
|
||||||
|
:theme="item.enableForcePushEvent ? 'success' : 'danger'">
|
||||||
|
{{ item.enableForcePushEvent ? '开启' : '关闭' }}</t-tag>
|
||||||
|
</t-descriptions-item>
|
||||||
|
</t-descriptions>
|
||||||
|
</t-collapse-panel>
|
||||||
|
</t-collapse>
|
||||||
</div>
|
</div>
|
||||||
</t-tab-panel>
|
</t-card>
|
||||||
</t-tabs>
|
</div>
|
||||||
</template>
|
<div style="height: 20vh"></div>
|
||||||
<template v-else>
|
</div>
|
||||||
<EmptyStateComponent :showAddTabDialog="showAddTabDialog" />
|
<t-card v-else>
|
||||||
</template>
|
<t-empty class="card-none" title="暂无网络配置"> </t-empty>
|
||||||
<t-dialog
|
</t-card>
|
||||||
v-model:visible="isDialogVisible"
|
</div>
|
||||||
header="添加网络配置"
|
<t-dialog v-model:visible="visibleBody" :header="dialogTitle" :destroy-on-close="true"
|
||||||
@close="isDialogVisible = false"
|
:show-in-attached-element="true" placement="center" :on-confirm="saveConfig" class=".t-dialog__ctx .t-dialog--defaul">
|
||||||
@confirm="addTab"
|
<div slot="body" class="dialog-body" >
|
||||||
>
|
<t-form ref="form" :data="newTab" labelAlign="left" :model="newTab">
|
||||||
<t-form ref="form" :model="newTab">
|
<t-form-item style="text-align: left" :rules="[{ required: true, message: '请输入名称', trigger: 'blur' }]"
|
||||||
<t-form-item :rules="[{ required: true, message: '请输入名称' }]" label="名称" name="name">
|
label="名称" name="name">
|
||||||
<t-input v-model="newTab.name" />
|
<t-input v-model="newTab.name" />
|
||||||
</t-form-item>
|
</t-form-item>
|
||||||
<t-form-item :rules="[{ required: true, message: '请选择类型' }]" label="类型" name="type">
|
<t-form-item style="text-align: left" :rules="[{ required: true, message: '请选择类型', trigger: 'change' }]"
|
||||||
<t-select v-model="newTab.type">
|
label="类型" name="type">
|
||||||
|
<t-select v-model="newTab.type" @change="onloadDefault">
|
||||||
<t-option value="httpServers">HTTP 服务器</t-option>
|
<t-option value="httpServers">HTTP 服务器</t-option>
|
||||||
<t-option value="httpClients">HTTP 客户端</t-option>
|
<t-option value="httpClients">HTTP 客户端</t-option>
|
||||||
<t-option value="websocketServers">WebSocket 服务器</t-option>
|
<t-option value="websocketServers">WebSocket 服务器</t-option>
|
||||||
<t-option value="websocketClients">WebSocket 客户端</t-option>
|
<t-option value="websocketClients">WebSocket 客户端</t-option>
|
||||||
</t-select>
|
</t-select>
|
||||||
</t-form-item>
|
</t-form-item>
|
||||||
|
<div>
|
||||||
|
<component :is="resolveDynamicComponent(getComponent(newTab.type as ComponentKey))"
|
||||||
|
:config="newTab.data" />
|
||||||
|
</div>
|
||||||
</t-form>
|
</t-form>
|
||||||
</t-dialog>
|
</div>
|
||||||
</t-space>
|
</t-dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, resolveDynamicComponent, nextTick, Ref, onMounted } from 'vue';
|
import { AddIcon, DeleteIcon, Edit2Icon, ServerFilledIcon, CopyIcon, BrowseOffIcon, BrowseIcon } from 'tdesign-icons-vue-next';
|
||||||
import { MessagePlugin } from 'tdesign-vue-next';
|
import { onMounted, onUnmounted, ref, resolveDynamicComponent } from 'vue';
|
||||||
|
import emitter from '@/ts/event-bus';
|
||||||
import {
|
import {
|
||||||
httpServerDefaultConfigs,
|
mergeNetworkDefaultConfig,
|
||||||
httpClientDefaultConfigs,
|
mergeOneBotConfigs,
|
||||||
websocketServerDefaultConfigs,
|
|
||||||
websocketClientDefaultConfigs,
|
|
||||||
HttpClientConfig,
|
|
||||||
HttpServerConfig,
|
|
||||||
WebsocketClientConfig,
|
|
||||||
WebsocketServerConfig,
|
|
||||||
NetworkConfig,
|
NetworkConfig,
|
||||||
OneBotConfig,
|
OneBotConfig,
|
||||||
mergeOneBotConfigs,
|
|
||||||
} from '../../../src/onebot/config/config';
|
} from '../../../src/onebot/config/config';
|
||||||
import { QQLoginManager } from '@/backend/shell';
|
|
||||||
import HttpServerComponent from '@/pages/network/HttpServerComponent.vue';
|
import HttpServerComponent from '@/pages/network/HttpServerComponent.vue';
|
||||||
import HttpClientComponent from '@/pages/network/HttpClientComponent.vue';
|
import HttpClientComponent from '@/pages/network/HttpClientComponent.vue';
|
||||||
import WebsocketServerComponent from '@/pages/network/WebsocketServerComponent.vue';
|
import WebsocketServerComponent from '@/pages/network/WebsocketServerComponent.vue';
|
||||||
import WebsocketClientComponent from '@/pages/network/WebsocketClientComponent.vue';
|
import WebsocketClientComponent from '@/pages/network/WebsocketClientComponent.vue';
|
||||||
import EmptyStateComponent from '@/pages/network/EmptyStateComponent.vue';
|
import { MessagePlugin } from 'tdesign-vue-next';
|
||||||
|
import { QQLoginManager } from '@/backend/shell';
|
||||||
|
|
||||||
type ConfigKey = 'httpServers' | 'httpClients' | 'websocketServers' | 'websocketClients';
|
const showToken = ref<boolean>(false);
|
||||||
type ConfigUnion = HttpClientConfig | HttpServerConfig | WebsocketServerConfig | WebsocketClientConfig;
|
const infoOneCol = ref<boolean>(true);
|
||||||
type ComponentUnion =
|
const tabsWidth = ref<number>(0);
|
||||||
|
const menuWidth = ref<number>(0);
|
||||||
|
const cardWidth = ref<number>(0);
|
||||||
|
const cardHeight = ref<number>(0);
|
||||||
|
const mediumScreen = window.matchMedia('(min-width: 768px) and (max-width: 1024px)');
|
||||||
|
const largeScreen = window.matchMedia('(min-width: 1025px)');
|
||||||
|
const headerBox = ref<HTMLDivElement | null>(null);
|
||||||
|
const setting = ref<HTMLDivElement | null>(null);
|
||||||
|
const loadPage = ref<boolean>(false);
|
||||||
|
const visibleBody = ref<boolean>(false);
|
||||||
|
const newTab = ref<{ name: string; data: any; type: string }>({ name: '', data: {}, type: '' });
|
||||||
|
const dialogTitle = ref<string>('');
|
||||||
|
|
||||||
|
type ComponentKey = keyof typeof mergeNetworkDefaultConfig;
|
||||||
|
|
||||||
|
const componentMap: Record<
|
||||||
|
ComponentKey,
|
||||||
| typeof HttpServerComponent
|
| typeof HttpServerComponent
|
||||||
| typeof HttpClientComponent
|
| typeof HttpClientComponent
|
||||||
| typeof WebsocketServerComponent
|
| typeof WebsocketServerComponent
|
||||||
| typeof WebsocketClientComponent;
|
| typeof WebsocketClientComponent
|
||||||
|
> = {
|
||||||
const componentMap: Record<ConfigKey, ComponentUnion> = {
|
|
||||||
httpServers: HttpServerComponent,
|
httpServers: HttpServerComponent,
|
||||||
httpClients: HttpClientComponent,
|
httpClients: HttpClientComponent,
|
||||||
websocketServers: WebsocketServerComponent,
|
websocketServers: WebsocketServerComponent,
|
||||||
websocketClients: WebsocketClientComponent,
|
websocketClients: WebsocketClientComponent,
|
||||||
};
|
};
|
||||||
|
|
||||||
const defaultConfigMap: Record<ConfigKey, ConfigUnion> = {
|
//操作类型
|
||||||
httpServers: httpServerDefaultConfigs,
|
const operateType = ref<string>('');
|
||||||
httpClients: httpClientDefaultConfigs,
|
//配置项索引
|
||||||
websocketServers: websocketServerDefaultConfigs,
|
const configIndex = ref<number>(0);
|
||||||
websocketClients: websocketClientDefaultConfigs,
|
//保存时所用数据
|
||||||
|
const networkConfig: NetworkConfig & { [key: string]: any; } = {
|
||||||
|
websocketClients: [],
|
||||||
|
websocketServers: [],
|
||||||
|
httpClients: [],
|
||||||
|
httpServers: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
interface ConfigMap {
|
//挂载的数据
|
||||||
httpServers: HttpServerConfig;
|
const WebConfg = ref(
|
||||||
httpClients: HttpClientConfig;
|
new Map<string, Array<null>>([
|
||||||
websocketServers: WebsocketServerConfig;
|
['all', []],
|
||||||
websocketClients: WebsocketClientConfig;
|
['httpServers', []],
|
||||||
}
|
['httpClients', []],
|
||||||
|
['websocketServers', []],
|
||||||
interface ClientPanel<K extends ConfigKey = ConfigKey> {
|
['websocketClients', []],
|
||||||
name: string;
|
])
|
||||||
key: K;
|
);
|
||||||
data: ConfigMap[K];
|
const typeCh: Record<ComponentKey, string> = {
|
||||||
}
|
httpServers: 'HTTP 服务器',
|
||||||
|
httpClients: 'HTTP 客户端',
|
||||||
const activeTab = ref<number>(0);
|
websocketServers: 'WebSocket 服务器',
|
||||||
const isDialogVisible = ref(false);
|
websocketClients: 'WebSocket 客户端',
|
||||||
const newTab = ref<{ name: string; type: ConfigKey }>({ name: '', type: 'httpServers' });
|
};
|
||||||
const clientPanelData: Ref<ClientPanel[]> = ref([]);
|
const cardConfig = ref<any>([]);
|
||||||
|
const getComponent = (type: ComponentKey) => {
|
||||||
const getComponent = (type: ConfigKey) => {
|
|
||||||
return componentMap[type];
|
return componentMap[type];
|
||||||
};
|
};
|
||||||
|
const getKeyByValue = (obj: typeof typeCh, value: string): string | undefined => {
|
||||||
|
return Object.entries(obj).find(([_, v]) => v === value)?.[0];
|
||||||
|
};
|
||||||
|
|
||||||
|
const addConfig = () => {
|
||||||
|
dialogTitle.value = '添加配置';
|
||||||
|
newTab.value = { name: '', data: {}, type: '' };
|
||||||
|
operateType.value = 'add';
|
||||||
|
visibleBody.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const editConfig = (item: any) => {
|
||||||
|
dialogTitle.value = '修改配置';
|
||||||
|
const type = getKeyByValue(typeCh, item.type);
|
||||||
|
if (type) {
|
||||||
|
newTab.value = { name: item.name, data: item, type: type };
|
||||||
|
}
|
||||||
|
operateType.value = 'edit';
|
||||||
|
configIndex.value = networkConfig[newTab.value.type].findIndex((obj: any) => obj.name === item.name);
|
||||||
|
visibleBody.value = true;
|
||||||
|
};
|
||||||
|
const delConfig = (item: any) => {
|
||||||
|
const type = getKeyByValue(typeCh, item.type);
|
||||||
|
if (type) {
|
||||||
|
newTab.value = { name: item.name, data: item, type: type };
|
||||||
|
}
|
||||||
|
configIndex.value = configIndex.value = networkConfig[newTab.value.type].findIndex(
|
||||||
|
(obj: any) => obj.name === item.name
|
||||||
|
);
|
||||||
|
operateType.value = 'delete';
|
||||||
|
saveConfig();
|
||||||
|
};
|
||||||
|
|
||||||
|
const selectType = (key: ComponentKey) => {
|
||||||
|
cardConfig.value = WebConfg.value.get(key);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onloadDefault = (key: ComponentKey) => {
|
||||||
|
console.log(key);
|
||||||
|
newTab.value.data = structuredClone(mergeNetworkDefaultConfig[key]);
|
||||||
|
};
|
||||||
|
//检测重名
|
||||||
|
const checkName = (name: string) => {
|
||||||
|
const allConfigs = WebConfg.value.get('all')?.findIndex((obj: any) => obj.name === name);
|
||||||
|
if (newTab.value.name === '' || newTab.value.type === '') {
|
||||||
|
MessagePlugin.error('请填写完整信息');
|
||||||
|
return false;
|
||||||
|
} else if (allConfigs === -1 || newTab.value.data.name === name) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
MessagePlugin.error('名称已存在');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
//保存
|
||||||
|
const saveConfig = async () => {
|
||||||
|
if (operateType.value == 'add') {
|
||||||
|
if (!checkName(newTab.value.name)) return;
|
||||||
|
newTab.value.data.name = newTab.value.name;
|
||||||
|
networkConfig[newTab.value.type].push(newTab.value.data);
|
||||||
|
} else if (operateType.value == 'edit') {
|
||||||
|
if (!checkName(newTab.value.name)) return;
|
||||||
|
newTab.value.data.name = newTab.value.name;
|
||||||
|
networkConfig[newTab.value.type][configIndex.value] = newTab.value.data;
|
||||||
|
} else if (operateType.value == 'delete') {
|
||||||
|
networkConfig[newTab.value.type].splice(configIndex.value, 1);
|
||||||
|
}
|
||||||
|
const userConfig = await getOB11Config();
|
||||||
|
if (!userConfig) return;
|
||||||
|
userConfig.network = networkConfig;
|
||||||
|
const success = await setOB11Config(userConfig);
|
||||||
|
if (success) {
|
||||||
|
operateType.value = '';
|
||||||
|
configIndex.value = 0;
|
||||||
|
MessagePlugin.success('配置保存成功');
|
||||||
|
await loadConfig();
|
||||||
|
visibleBody.value = false;
|
||||||
|
} else {
|
||||||
|
MessagePlugin.error('配置保存失败');
|
||||||
|
}
|
||||||
|
};
|
||||||
const getOB11Config = async (): Promise<OneBotConfig | undefined> => {
|
const getOB11Config = async (): Promise<OneBotConfig | undefined> => {
|
||||||
const storedCredential = localStorage.getItem('auth');
|
const storedCredential = localStorage.getItem('auth');
|
||||||
if (!storedCredential) {
|
if (!storedCredential) {
|
||||||
@@ -137,27 +315,27 @@ const setOB11Config = async (config: OneBotConfig): Promise<boolean> => {
|
|||||||
return await loginManager.SetOB11Config(config);
|
return await loginManager.SetOB11Config(config);
|
||||||
};
|
};
|
||||||
|
|
||||||
const addToPanel = <K extends ConfigKey>(configs: ConfigMap[K][], key: K) => {
|
//获取卡片数据
|
||||||
configs.forEach((config) => clientPanelData.value.push({ name: config.name, data: config, key }));
|
const getAllData = (data: NetworkConfig) => {
|
||||||
};
|
cardConfig.value = [];
|
||||||
|
WebConfg.value.set('all', []);
|
||||||
const addConfigDataToPanel = (data: NetworkConfig) => {
|
for (const key in data) {
|
||||||
(Object.keys(data) as ConfigKey[]).forEach((key) => {
|
const configs = data[key as keyof NetworkConfig];
|
||||||
addToPanel(data[key], key);
|
if (key in mergeNetworkDefaultConfig) {
|
||||||
});
|
networkConfig[key] = [...configs];
|
||||||
};
|
const newConfigsArray = configs.map((config: any) => ({
|
||||||
|
...config,
|
||||||
const parsePanelData = (): NetworkConfig => {
|
type: typeCh[key as ComponentKey],
|
||||||
const result: NetworkConfig = {
|
}));
|
||||||
httpServers: [],
|
WebConfg.value.set(key, newConfigsArray);
|
||||||
httpClients: [],
|
const allConfigs = WebConfg.value.get('all');
|
||||||
websocketServers: [],
|
if (allConfigs) {
|
||||||
websocketClients: [],
|
const newAllConfigs = [...allConfigs, ...newConfigsArray];
|
||||||
};
|
WebConfg.value.set('all', newAllConfigs);
|
||||||
clientPanelData.value.forEach((panel) => {
|
}
|
||||||
(result[panel.key] as Array<typeof panel.data>).push(panel.data);
|
cardConfig.value = WebConfg.value.get('all');
|
||||||
});
|
}
|
||||||
return result;
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const loadConfig = async () => {
|
const loadConfig = async () => {
|
||||||
@@ -165,85 +343,198 @@ const loadConfig = async () => {
|
|||||||
const userConfig = await getOB11Config();
|
const userConfig = await getOB11Config();
|
||||||
if (!userConfig) return;
|
if (!userConfig) return;
|
||||||
const mergedConfig = mergeOneBotConfigs(userConfig);
|
const mergedConfig = mergeOneBotConfigs(userConfig);
|
||||||
addConfigDataToPanel(mergedConfig.network);
|
getAllData(mergedConfig.network);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error loading config:', error);
|
console.error('Error loading config:', error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const saveConfig = async () => {
|
const copyText = async (text: string) => {
|
||||||
const config = parsePanelData();
|
const input = document.createElement('input');
|
||||||
const userConfig = await getOB11Config();
|
input.value = text;
|
||||||
if (!userConfig) {
|
document.body.appendChild(input);
|
||||||
await MessagePlugin.error('无法获取配置!');
|
input.select();
|
||||||
return;
|
await navigator.clipboard.writeText(text);
|
||||||
}
|
document.body.removeChild(input);
|
||||||
userConfig.network = config;
|
MessagePlugin.success('复制成功');
|
||||||
const success = await setOB11Config(userConfig);
|
};
|
||||||
if (success) {
|
|
||||||
await MessagePlugin.success('配置保存成功');
|
const handleResize = () => {
|
||||||
|
// 得根据卡片宽度改,懒得改了;先不管了
|
||||||
|
// if(window.innerWidth < 540) {
|
||||||
|
// infoOneCol.value= true
|
||||||
|
// } else {
|
||||||
|
// infoOneCol.value= false
|
||||||
|
// }
|
||||||
|
tabsWidth.value = window.innerWidth - 41 - menuWidth.value;
|
||||||
|
if (mediumScreen.matches) {
|
||||||
|
cardWidth.value = (tabsWidth.value - 20) / 2;
|
||||||
|
} else if (largeScreen.matches) {
|
||||||
|
cardWidth.value = (tabsWidth.value - 40) / 3;
|
||||||
} else {
|
} else {
|
||||||
await MessagePlugin.error('配置保存失败');
|
cardWidth.value = tabsWidth.value;
|
||||||
}
|
}
|
||||||
|
loadPage.value = true;
|
||||||
|
setTimeout(() => {
|
||||||
|
cardHeight.value = window.innerHeight - (headerBox.value?.offsetHeight ?? 0) - (setting.value?.offsetHeight ?? 0) - 21;
|
||||||
|
}, 300);
|
||||||
};
|
};
|
||||||
|
emitter.on('sendWidth', (width) => {
|
||||||
const showAddTabDialog = () => {
|
if (typeof width === 'number' && !isNaN(width)) {
|
||||||
newTab.value = { name: '', type: 'httpServers' };
|
menuWidth.value = width;
|
||||||
isDialogVisible.value = true;
|
handleResize();
|
||||||
};
|
|
||||||
|
|
||||||
const addTab = async () => {
|
|
||||||
const { name, type } = newTab.value;
|
|
||||||
if (clientPanelData.value.some((panel) => panel.name === name)) {
|
|
||||||
await MessagePlugin.error('选项卡名称已存在');
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
const defaultConfig = structuredClone(defaultConfigMap[type]);
|
});
|
||||||
defaultConfig.name = name;
|
|
||||||
clientPanelData.value.push({ name, data: defaultConfig, key: type });
|
|
||||||
isDialogVisible.value = false;
|
|
||||||
await nextTick();
|
|
||||||
activeTab.value = clientPanelData.value.length - 1;
|
|
||||||
await MessagePlugin.success('选项卡添加成功');
|
|
||||||
};
|
|
||||||
|
|
||||||
const removeTab = async (payload: { value: string; index: number; e: PointerEvent }) => {
|
|
||||||
clientPanelData.value.splice(payload.index, 1);
|
|
||||||
activeTab.value = Math.max(0, activeTab.value - 1);
|
|
||||||
await saveConfig();
|
|
||||||
};
|
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
loadConfig();
|
loadConfig();
|
||||||
|
const cachedWidth = localStorage.getItem('menuWidth');
|
||||||
|
if (cachedWidth) {
|
||||||
|
menuWidth.value = parseInt(cachedWidth);
|
||||||
|
setTimeout(() => {
|
||||||
|
handleResize();
|
||||||
|
}, 300);
|
||||||
|
}
|
||||||
|
window.addEventListener('resize', handleResize);
|
||||||
|
});
|
||||||
|
onUnmounted(() => {
|
||||||
|
window.removeEventListener('resize', handleResize);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.full-space {
|
.title {
|
||||||
width: 100%;
|
padding: 20px 20px 0 20px;
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
justify-content: space-between;
|
||||||
align-items: flex-start;
|
|
||||||
justify-content: flex-start;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.full-tabs {
|
.setting {
|
||||||
width: 100%;
|
margin: 0 20px;
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.full-tab-panel {
|
.setting-box {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr 1fr;
|
||||||
|
gap: 20px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-card {
|
||||||
|
width: 100%;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-content {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-address svg {
|
||||||
|
fill: var(--td-brand-color);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.local-box {
|
||||||
|
display: flex;
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
.local-icon{
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
}
|
||||||
flex-direction: column;
|
.local {
|
||||||
|
flex: 6;
|
||||||
|
margin: 0 10px 0 10px;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button-container {
|
|
||||||
|
.copy-icon {
|
||||||
|
flex: 1;
|
||||||
|
cursor: pointer;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.token-view {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
align-items: center;
|
||||||
margin-top: 20px;
|
}
|
||||||
|
|
||||||
|
.token-view span {
|
||||||
|
flex: 5;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
.browse-icon{
|
||||||
|
flex: 2;
|
||||||
|
}
|
||||||
|
:global(.t-dialog__ctx .t-dialog--defaul) {
|
||||||
|
margin: 0 20px;
|
||||||
|
}
|
||||||
|
@media (max-width: 1024px) {
|
||||||
|
.setting-box {
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 786px) {
|
||||||
|
.setting-box {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-box {
|
||||||
|
margin: 10px 20px 0 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-none {
|
||||||
|
line-height: 400px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.dialog-body {
|
||||||
|
max-height: 60vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 0;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<style>
|
||||||
|
.setting-card .t-card__title {
|
||||||
|
text-align: left !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-card .t-card__description {
|
||||||
|
margin-bottom: 0;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-address .t-card__body {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-base-info .t-descriptions__header {
|
||||||
|
font-size: 15px;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-base-info .t-descriptions__label {
|
||||||
|
padding: 0 var(--td-comp-paddingLR-l) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-base-info tr>td:last-child {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-coll .t-collapse-panel__wrapper .t-collapse-panel__content {
|
||||||
|
padding: var(--td-comp-paddingTB-m) var(--td-comp-paddingLR-l);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@@ -1,22 +1,27 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div class="title">
|
||||||
<t-divider content="其余配置" align="left" />
|
<t-divider content="其余配置" align="left" />
|
||||||
</div>
|
</div>
|
||||||
<div class="other-config-container">
|
<t-card class="card">
|
||||||
<div class="other-config">
|
<div class="other-config-container">
|
||||||
<t-form ref="form" :model="otherConfig" class="form">
|
<div class="other-config">
|
||||||
<t-form-item label="音乐签名地址" name="musicSignUrl" class="form-item">
|
<t-form ref="form" :model="otherConfig" :label-align="labelAlign" label-width="auto" colon>
|
||||||
<t-input v-model="otherConfig.musicSignUrl" />
|
<t-form-item label="音乐签名地址" name="musicSignUrl" class="form-item">
|
||||||
</t-form-item>
|
<t-input v-model="otherConfig.musicSignUrl" />
|
||||||
<t-form-item label="启用本地文件到URL" name="enableLocalFile2Url" class="form-item">
|
</t-form-item>
|
||||||
<t-switch v-model="otherConfig.enableLocalFile2Url" />
|
<t-form-item label="启用本地文件到URL" name="enableLocalFile2Url" class="form-item">
|
||||||
</t-form-item>
|
<t-switch v-model="otherConfig.enableLocalFile2Url" />
|
||||||
</t-form>
|
</t-form-item>
|
||||||
<div class="button-container">
|
<t-form-item label="启用上报解析合并消息" name="parseMultMsg" class="form-item">
|
||||||
<t-button @click="saveConfig">保存</t-button>
|
<t-switch v-model="otherConfig.parseMultMsg" />
|
||||||
|
</t-form-item>
|
||||||
|
</t-form>
|
||||||
|
<div class="button-container">
|
||||||
|
<t-button @click="saveConfig">保存</t-button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</t-card>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
@@ -28,8 +33,10 @@ import { QQLoginManager } from '@/backend/shell';
|
|||||||
const otherConfig = ref<Partial<OneBotConfig>>({
|
const otherConfig = ref<Partial<OneBotConfig>>({
|
||||||
musicSignUrl: '',
|
musicSignUrl: '',
|
||||||
enableLocalFile2Url: false,
|
enableLocalFile2Url: false,
|
||||||
|
parseMultMsg: true
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const labelAlign = ref<string>();
|
||||||
const getOB11Config = async (): Promise<OneBotConfig | undefined> => {
|
const getOB11Config = async (): Promise<OneBotConfig | undefined> => {
|
||||||
const storedCredential = localStorage.getItem('auth');
|
const storedCredential = localStorage.getItem('auth');
|
||||||
if (!storedCredential) {
|
if (!storedCredential) {
|
||||||
@@ -56,6 +63,7 @@ const loadConfig = async () => {
|
|||||||
if (userConfig) {
|
if (userConfig) {
|
||||||
otherConfig.value.musicSignUrl = userConfig.musicSignUrl;
|
otherConfig.value.musicSignUrl = userConfig.musicSignUrl;
|
||||||
otherConfig.value.enableLocalFile2Url = userConfig.enableLocalFile2Url;
|
otherConfig.value.enableLocalFile2Url = userConfig.enableLocalFile2Url;
|
||||||
|
otherConfig.value.parseMultMsg = userConfig.parseMultMsg;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error loading config:', error);
|
console.error('Error loading config:', error);
|
||||||
@@ -68,6 +76,7 @@ const saveConfig = async () => {
|
|||||||
if (userConfig) {
|
if (userConfig) {
|
||||||
userConfig.musicSignUrl = otherConfig.value.musicSignUrl || '';
|
userConfig.musicSignUrl = otherConfig.value.musicSignUrl || '';
|
||||||
userConfig.enableLocalFile2Url = otherConfig.value.enableLocalFile2Url ?? false;
|
userConfig.enableLocalFile2Url = otherConfig.value.enableLocalFile2Url ?? false;
|
||||||
|
userConfig.parseMultMsg = otherConfig.value.parseMultMsg ?? true;
|
||||||
const success = await setOB11Config(userConfig);
|
const success = await setOB11Config(userConfig);
|
||||||
if (success) {
|
if (success) {
|
||||||
MessagePlugin.success('配置保存成功');
|
MessagePlugin.success('配置保存成功');
|
||||||
@@ -80,55 +89,60 @@ const saveConfig = async () => {
|
|||||||
MessagePlugin.error('配置保存失败');
|
MessagePlugin.error('配置保存失败');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
loadConfig();
|
loadConfig();
|
||||||
|
const mediaQuery = window.matchMedia('(max-width: 768px)');
|
||||||
|
const handleMediaChange = (e: MediaQueryListEvent) => {
|
||||||
|
if (e.matches) {
|
||||||
|
labelAlign.value = 'top';
|
||||||
|
} else {
|
||||||
|
labelAlign.value = 'left';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
mediaQuery.addEventListener('change', handleMediaChange);
|
||||||
|
const event = new Event('change');
|
||||||
|
Object.defineProperty(event, 'matches', {
|
||||||
|
value: mediaQuery.matches,
|
||||||
|
writable: false,
|
||||||
|
});
|
||||||
|
mediaQuery.dispatchEvent(event);
|
||||||
|
return () => {
|
||||||
|
mediaQuery.removeEventListener('change', handleMediaChange);
|
||||||
|
};
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
.title {
|
||||||
|
padding: 20px 20px 0 20px;
|
||||||
|
}
|
||||||
|
.card {
|
||||||
|
margin: 0 20px;
|
||||||
|
padding-top: 20px;
|
||||||
|
padding-bottom: 20px;
|
||||||
|
}
|
||||||
.other-config-container {
|
.other-config-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
padding: 20px;
|
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.other-config {
|
.other-config {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 600px;
|
max-width: 500px;
|
||||||
background: #fff;
|
|
||||||
padding: 20px;
|
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-item {
|
.form-item {
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button-container {
|
.button-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
margin-top: 20px;
|
||||||
|
|
||||||
@media (min-width: 768px) {
|
|
||||||
.form-item {
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-item t-input,
|
|
||||||
.form-item t-switch {
|
|
||||||
flex: 1;
|
|
||||||
margin-left: 20px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@@ -1,22 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="empty-state">
|
|
||||||
<p>当前没有网络配置</p>
|
|
||||||
<t-button @click="showAddTabDialog">添加网络配置</t-button>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { defineProps } from 'vue';
|
|
||||||
defineProps<{ showAddTabDialog: () => void }>();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.empty-state {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
height: 100%;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
</style>
|
|
@@ -1,28 +1,25 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="container">
|
<div>
|
||||||
<div class="form-container">
|
<t-form labelAlign="left">
|
||||||
<h3>HTTP Client 配置</h3>
|
<t-form-item label="启用">
|
||||||
<t-form>
|
<t-checkbox v-model="config.enable" />
|
||||||
<t-form-item label="启用">
|
</t-form-item>
|
||||||
<t-checkbox v-model="config.enable" />
|
<t-form-item label="URL">
|
||||||
</t-form-item>
|
<t-input v-model="config.url" />
|
||||||
<t-form-item label="URL">
|
</t-form-item>
|
||||||
<t-input v-model="config.url" />
|
<t-form-item label="消息格式">
|
||||||
</t-form-item>
|
<t-select v-model="config.messagePostFormat" :options="messageFormatOptions" />
|
||||||
<t-form-item label="消息格式">
|
</t-form-item>
|
||||||
<t-select v-model="config.messagePostFormat" :options="messageFormatOptions" />
|
<t-form-item label="报告自身消息">
|
||||||
</t-form-item>
|
<t-checkbox v-model="config.reportSelfMessage" />
|
||||||
<t-form-item label="报告自身消息">
|
</t-form-item>
|
||||||
<t-checkbox v-model="config.reportSelfMessage" />
|
<t-form-item label="Token">
|
||||||
</t-form-item>
|
<t-input v-model="config.token" />
|
||||||
<t-form-item label="Token">
|
</t-form-item>
|
||||||
<t-input v-model="config.token" />
|
<t-form-item label="调试模式">
|
||||||
</t-form-item>
|
<t-checkbox v-model="config.debug" />
|
||||||
<t-form-item label="调试模式">
|
</t-form-item>
|
||||||
<t-checkbox v-model="config.debug" />
|
</t-form>
|
||||||
</t-form-item>
|
|
||||||
</t-form>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -49,20 +46,4 @@ watch(
|
|||||||
);
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped></style>
|
||||||
.container {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: flex-start;
|
|
||||||
padding: 20px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-container {
|
|
||||||
width: 100%;
|
|
||||||
max-width: 600px;
|
|
||||||
background: #fff;
|
|
||||||
padding: 20px;
|
|
||||||
border-radius: 8px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
@@ -1,34 +1,31 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="container">
|
<div>
|
||||||
<div class="form-container">
|
<t-form labelAlign="left">
|
||||||
<h3>HTTP Server 配置</h3>
|
<t-form-item label="启用">
|
||||||
<t-form>
|
<t-checkbox v-model="config.enable" />
|
||||||
<t-form-item label="启用">
|
</t-form-item>
|
||||||
<t-checkbox v-model="config.enable" />
|
<t-form-item label="端口">
|
||||||
</t-form-item>
|
<t-input v-model.number="config.port" type="number" />
|
||||||
<t-form-item label="端口">
|
</t-form-item>
|
||||||
<t-input v-model.number="config.port" type="number" />
|
<t-form-item label="主机">
|
||||||
</t-form-item>
|
<t-input v-model="config.host" type="text" />
|
||||||
<t-form-item label="主机">
|
</t-form-item>
|
||||||
<t-input v-model="config.host" type="text" />
|
<t-form-item label="启用 CORS">
|
||||||
</t-form-item>
|
<t-checkbox v-model="config.enableCors" />
|
||||||
<t-form-item label="启用 CORS">
|
</t-form-item>
|
||||||
<t-checkbox v-model="config.enableCors" />
|
<t-form-item label="启用 WS">
|
||||||
</t-form-item>
|
<t-checkbox v-model="config.enableWebsocket" />
|
||||||
<t-form-item label="启用 WS">
|
</t-form-item>
|
||||||
<t-checkbox v-model="config.enableWebsocket" />
|
<t-form-item label="消息格式">
|
||||||
</t-form-item>
|
<t-select v-model="config.messagePostFormat" :options="messageFormatOptions" />
|
||||||
<t-form-item label="消息格式">
|
</t-form-item>
|
||||||
<t-select v-model="config.messagePostFormat" :options="messageFormatOptions" />
|
<t-form-item label="Token">
|
||||||
</t-form-item>
|
<t-input v-model="config.token" type="text" />
|
||||||
<t-form-item label="Token">
|
</t-form-item>
|
||||||
<t-input v-model="config.token" type="text" />
|
<t-form-item label="调试模式">
|
||||||
</t-form-item>
|
<t-checkbox v-model="config.debug" />
|
||||||
<t-form-item label="调试模式">
|
</t-form-item>
|
||||||
<t-checkbox v-model="config.debug" />
|
</t-form>
|
||||||
</t-form-item>
|
|
||||||
</t-form>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -55,20 +52,4 @@ watch(
|
|||||||
);
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped></style>
|
||||||
.container {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: flex-start;
|
|
||||||
padding: 20px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-container {
|
|
||||||
width: 100%;
|
|
||||||
max-width: 600px;
|
|
||||||
background: #fff;
|
|
||||||
padding: 20px;
|
|
||||||
border-radius: 8px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
@@ -1,31 +1,28 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="container">
|
<div>
|
||||||
<div class="form-container">
|
<t-form labelAlign="left">
|
||||||
<h3>WebSocket Client 配置</h3>
|
<t-form-item label="启用">
|
||||||
<t-form>
|
<t-checkbox v-model="config.enable" />
|
||||||
<t-form-item label="启用">
|
</t-form-item>
|
||||||
<t-checkbox v-model="config.enable" />
|
<t-form-item label="URL">
|
||||||
</t-form-item>
|
<t-input v-model="config.url" />
|
||||||
<t-form-item label="URL">
|
</t-form-item>
|
||||||
<t-input v-model="config.url" />
|
<t-form-item label="消息格式">
|
||||||
</t-form-item>
|
<t-select v-model="config.messagePostFormat" :options="messageFormatOptions" />
|
||||||
<t-form-item label="消息格式">
|
</t-form-item>
|
||||||
<t-select v-model="config.messagePostFormat" :options="messageFormatOptions" />
|
<t-form-item label="报告自身消息">
|
||||||
</t-form-item>
|
<t-checkbox v-model="config.reportSelfMessage" />
|
||||||
<t-form-item label="报告自身消息">
|
</t-form-item>
|
||||||
<t-checkbox v-model="config.reportSelfMessage" />
|
<t-form-item label="Token">
|
||||||
</t-form-item>
|
<t-input v-model="config.token" />
|
||||||
<t-form-item label="Token">
|
</t-form-item>
|
||||||
<t-input v-model="config.token" />
|
<t-form-item label="调试模式">
|
||||||
</t-form-item>
|
<t-checkbox v-model="config.debug" />
|
||||||
<t-form-item label="调试模式">
|
</t-form-item>
|
||||||
<t-checkbox v-model="config.debug" />
|
<t-form-item label="心跳间隔">
|
||||||
</t-form-item>
|
<t-input v-model.number="config.heartInterval" type="number" />
|
||||||
<t-form-item label="心跳间隔">
|
</t-form-item>
|
||||||
<t-input v-model.number="config.heartInterval" type="number" />
|
</t-form>
|
||||||
</t-form-item>
|
|
||||||
</t-form>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -52,20 +49,4 @@ watch(
|
|||||||
);
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped></style>
|
||||||
.container {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: flex-start;
|
|
||||||
padding: 20px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-container {
|
|
||||||
width: 100%;
|
|
||||||
max-width: 600px;
|
|
||||||
background: #fff;
|
|
||||||
padding: 20px;
|
|
||||||
border-radius: 8px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
@@ -1,37 +1,34 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="container">
|
<div>
|
||||||
<div class="form-container">
|
<t-form labelAlign="left">
|
||||||
<h3>WebSocket Server 配置</h3>
|
<t-form-item label="启用">
|
||||||
<t-form>
|
<t-checkbox v-model="config.enable" />
|
||||||
<t-form-item label="启用">
|
</t-form-item>
|
||||||
<t-checkbox v-model="config.enable" />
|
<t-form-item label="主机">
|
||||||
</t-form-item>
|
<t-input v-model="config.host" />
|
||||||
<t-form-item label="主机">
|
</t-form-item>
|
||||||
<t-input v-model="config.host" />
|
<t-form-item label="端口">
|
||||||
</t-form-item>
|
<t-input v-model.number="config.port" type="number" />
|
||||||
<t-form-item label="端口">
|
</t-form-item>
|
||||||
<t-input v-model.number="config.port" type="number" />
|
<t-form-item label="消息格式">
|
||||||
</t-form-item>
|
<t-select v-model="config.messagePostFormat" :options="messageFormatOptions" />
|
||||||
<t-form-item label="消息格式">
|
</t-form-item>
|
||||||
<t-select v-model="config.messagePostFormat" :options="messageFormatOptions" />
|
<t-form-item label="上报自身消息">
|
||||||
</t-form-item>
|
<t-checkbox v-model="config.reportSelfMessage" />
|
||||||
<t-form-item label="上报自身消息">
|
</t-form-item>
|
||||||
<t-checkbox v-model="config.reportSelfMessage" />
|
<t-form-item label="Token">
|
||||||
</t-form-item>
|
<t-input v-model="config.token" />
|
||||||
<t-form-item label="Token">
|
</t-form-item>
|
||||||
<t-input v-model="config.token" />
|
<t-form-item label="强制推送事件">
|
||||||
</t-form-item>
|
<t-checkbox v-model="config.enableForcePushEvent" />
|
||||||
<t-form-item label="强制推送事件">
|
</t-form-item>
|
||||||
<t-checkbox v-model="config.enableForcePushEvent" />
|
<t-form-item label="调试模式">
|
||||||
</t-form-item>
|
<t-checkbox v-model="config.debug" />
|
||||||
<t-form-item label="调试模式">
|
</t-form-item>
|
||||||
<t-checkbox v-model="config.debug" />
|
<t-form-item label="心跳间隔">
|
||||||
</t-form-item>
|
<t-input v-model.number="config.heartInterval" type="number" />
|
||||||
<t-form-item label="心跳间隔">
|
</t-form-item>
|
||||||
<t-input v-model.number="config.heartInterval" type="number" />
|
</t-form>
|
||||||
</t-form-item>
|
|
||||||
</t-form>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -58,20 +55,4 @@ watch(
|
|||||||
);
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped></style>
|
||||||
.container {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: flex-start;
|
|
||||||
padding: 20px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-container {
|
|
||||||
width: 100%;
|
|
||||||
max-width: 600px;
|
|
||||||
background: #fff;
|
|
||||||
padding: 20px;
|
|
||||||
border-radius: 8px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
3
napcat.webui/src/ts/event-bus.ts
Normal file
3
napcat.webui/src/ts/event-bus.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import mitt from 'mitt';
|
||||||
|
const emitter = mitt();
|
||||||
|
export default emitter;
|
14
package.json
14
package.json
@@ -2,11 +2,13 @@
|
|||||||
"name": "napcat",
|
"name": "napcat",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "4.1.13",
|
"version": "4.2.16",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
"build:universal": "npm run build:webui && vite build --mode universal || exit 1",
|
||||||
"build:framework": "npm run build:webui && vite build --mode framework || exit 1",
|
"build:framework": "npm run build:webui && vite build --mode framework || exit 1",
|
||||||
"build:shell": "npm run build:webui && vite build --mode shell || exit 1",
|
"build:shell": "npm run build:webui && vite build --mode shell || exit 1",
|
||||||
"build:webui": "cd napcat.webui && vite build",
|
"build:webui": "cd napcat.webui && vite build",
|
||||||
|
"dev:universal": "vite build --mode universal",
|
||||||
"dev:framework": "vite build --mode framework",
|
"dev:framework": "vite build --mode framework",
|
||||||
"dev:shell": "vite build --mode shell",
|
"dev:shell": "vite build --mode shell",
|
||||||
"dev:webui": "cd napcat.webui && npm run webui:dev",
|
"dev:webui": "cd napcat.webui && npm run webui:dev",
|
||||||
@@ -41,20 +43,20 @@
|
|||||||
"file-type": "^19.0.0",
|
"file-type": "^19.0.0",
|
||||||
"globals": "^15.12.0",
|
"globals": "^15.12.0",
|
||||||
"image-size": "^1.1.1",
|
"image-size": "^1.1.1",
|
||||||
"json-schema-to-ts": "^3.1.1",
|
|
||||||
"typescript": "^5.3.3",
|
"typescript": "^5.3.3",
|
||||||
"typescript-eslint": "^8.13.0",
|
"typescript-eslint": "^8.13.0",
|
||||||
"vite": "^5.2.6",
|
"vite": "^6.0.1",
|
||||||
"vite-plugin-cp": "^4.0.8",
|
"vite-plugin-cp": "^4.0.8",
|
||||||
"vite-tsconfig-paths": "^5.1.0",
|
"vite-tsconfig-paths": "^5.1.0",
|
||||||
"winston": "^3.17.0"
|
"winston": "^3.17.0",
|
||||||
|
"@sinclair/typebox": "^0.34.9"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"express": "^5.0.0",
|
"express": "^5.0.0",
|
||||||
"fluent-ffmpeg": "^2.1.2",
|
"fluent-ffmpeg": "^2.1.2",
|
||||||
|
"piscina": "^4.7.0",
|
||||||
"qrcode-terminal": "^0.12.0",
|
"qrcode-terminal": "^0.12.0",
|
||||||
"silk-wasm": "^3.6.1",
|
"silk-wasm": "^3.6.1",
|
||||||
"ws": "^8.18.0",
|
"ws": "^8.18.0"
|
||||||
"piscina": "^4.7.0"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -96,7 +96,7 @@ export async function encodeSilk(filePath: string, TEMP_DIR: string, logger: Log
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
logger.logError.bind(logger)('convert silk failed', error.stack);
|
logger.logError('convert silk failed', error.stack);
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -33,27 +33,27 @@ export abstract class ConfigBase<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
read(copy_default: boolean = true): T {
|
read(copy_default: boolean = true): T {
|
||||||
const logger = this.core.context.logger;
|
|
||||||
const configPath = this.getConfigPath(this.core.selfInfo.uin);
|
const configPath = this.getConfigPath(this.core.selfInfo.uin);
|
||||||
if (!fs.existsSync(configPath) && copy_default) {
|
if (!fs.existsSync(configPath) && copy_default) {
|
||||||
try {
|
try {
|
||||||
fs.writeFileSync(configPath, fs.readFileSync(this.getConfigPath(undefined), 'utf-8'));
|
fs.writeFileSync(configPath, fs.readFileSync(this.getConfigPath(undefined), 'utf-8'));
|
||||||
logger.log(`[Core] [Config] 配置文件创建成功!\n`);
|
this.core.context.logger.log(`[Core] [Config] 配置文件创建成功!\n`);
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
logger.logError.bind(logger)(`[Core] [Config] 创建配置文件时发生错误:`, e.message);
|
this.core.context.logger.logError(`[Core] [Config] 创建配置文件时发生错误:`, e.message);
|
||||||
}
|
}
|
||||||
} else if (!fs.existsSync(configPath) && !copy_default) {
|
} else if (!fs.existsSync(configPath) && !copy_default) {
|
||||||
fs.writeFileSync(configPath, '{}');
|
fs.writeFileSync(configPath, '{}');
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
this.configData = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
this.configData = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
||||||
logger.logDebug(`[Core] [Config] 配置文件${configPath}加载`, this.configData);
|
this.core.context.logger.logDebug(`[Core] [Config] 配置文件${configPath}加载`, this.configData);
|
||||||
return this.configData;
|
return this.configData;
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
if (e instanceof SyntaxError) {
|
if (e instanceof SyntaxError) {
|
||||||
logger.logError.bind(logger)(`[Core] [Config] 配置文件格式错误,请检查配置文件:`, e.message);
|
this.core.context.logger.logError(`[Core] [Config] 配置文件格式错误,请检查配置文件:`, e.message);
|
||||||
} else {
|
} else {
|
||||||
logger.logError.bind(logger)(`[Core] [Config] 读取配置文件时发生错误:`, e.message);
|
this.core.context.logger.logError(`[Core] [Config] 读取配置文件时发生错误:`, e.message);
|
||||||
}
|
}
|
||||||
return {} as T;
|
return {} as T;
|
||||||
}
|
}
|
||||||
@@ -61,14 +61,13 @@ export abstract class ConfigBase<T> {
|
|||||||
|
|
||||||
|
|
||||||
save(newConfigData: T = this.configData) {
|
save(newConfigData: T = this.configData) {
|
||||||
const logger = this.core.context.logger;
|
|
||||||
const selfInfo = this.core.selfInfo;
|
const selfInfo = this.core.selfInfo;
|
||||||
this.configData = newConfigData;
|
this.configData = newConfigData;
|
||||||
const configPath = this.getConfigPath(selfInfo.uin);
|
const configPath = this.getConfigPath(selfInfo.uin);
|
||||||
try {
|
try {
|
||||||
fs.writeFileSync(configPath, JSON.stringify(newConfigData, this.getKeys(), 2));
|
fs.writeFileSync(configPath, JSON.stringify(newConfigData, this.getKeys(), 2));
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
logger.logError.bind(logger)(`保存配置文件 ${configPath} 时发生错误:`, e.message);
|
this.core.context.logger.logError(`保存配置文件 ${configPath} 时发生错误:`, e.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -19,14 +19,6 @@ type Uri2LocalRes = {
|
|||||||
path: string
|
path: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isGIF(path: string) {
|
|
||||||
const buffer = Buffer.alloc(4);
|
|
||||||
const fd = fs.openSync(path, 'r');
|
|
||||||
fs.readSync(fd, buffer, 0, 4, 0);
|
|
||||||
fs.closeSync(fd);
|
|
||||||
return buffer.toString() === 'GIF8';
|
|
||||||
}
|
|
||||||
|
|
||||||
// 定义一个异步函数来检查文件是否存在
|
// 定义一个异步函数来检查文件是否存在
|
||||||
export function checkFileExist(path: string, timeout: number = 3000): Promise<void> {
|
export function checkFileExist(path: string, timeout: number = 3000): Promise<void> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
@@ -190,7 +182,6 @@ export enum FileUriType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function checkUriType(Uri: string) {
|
export async function checkUriType(Uri: string) {
|
||||||
|
|
||||||
const LocalFileRet = await solveProblem((uri: string) => {
|
const LocalFileRet = await solveProblem((uri: string) => {
|
||||||
if (fs.existsSync(uri)) {
|
if (fs.existsSync(uri)) {
|
||||||
return { Uri: uri, Type: FileUriType.Local };
|
return { Uri: uri, Type: FileUriType.Local };
|
||||||
@@ -199,23 +190,17 @@ export async function checkUriType(Uri: string) {
|
|||||||
}, Uri);
|
}, Uri);
|
||||||
if (LocalFileRet) return LocalFileRet;
|
if (LocalFileRet) return LocalFileRet;
|
||||||
const OtherFileRet = await solveProblem((uri: string) => {
|
const OtherFileRet = await solveProblem((uri: string) => {
|
||||||
//再判断是否是Http
|
// 再判断是否是Http
|
||||||
if (uri.startsWith('http://') || uri.startsWith('https://')) {
|
if (uri.startsWith('http:') || uri.startsWith('https:')) {
|
||||||
return { Uri: uri, Type: FileUriType.Remote };
|
return { Uri: uri, Type: FileUriType.Remote };
|
||||||
}
|
}
|
||||||
//再判断是否是Base64
|
// 再判断是否是Base64
|
||||||
if (uri.startsWith('base64://')) {
|
if (uri.startsWith('base64:')) {
|
||||||
return { Uri: uri, Type: FileUriType.Base64 };
|
return { Uri: uri, Type: FileUriType.Base64 };
|
||||||
}
|
}
|
||||||
if (uri.startsWith('file://')) {
|
// 默认file://
|
||||||
let filePath: string;
|
if (uri.startsWith('file:')) {
|
||||||
const pathname = decodeURIComponent(new URL(uri).pathname + new URL(uri).hash);
|
const filePath: string = decodeURIComponent(uri.startsWith('file:///') && process.platform === 'win32' ? uri.slice(8) : uri.slice(7));
|
||||||
if (process.platform === 'win32') {
|
|
||||||
filePath = pathname.slice(1);
|
|
||||||
} else {
|
|
||||||
filePath = pathname;
|
|
||||||
}
|
|
||||||
|
|
||||||
return { Uri: filePath, Type: FileUriType.Local };
|
return { Uri: filePath, Type: FileUriType.Local };
|
||||||
}
|
}
|
||||||
if (uri.startsWith('data:')) {
|
if (uri.startsWith('data:')) {
|
||||||
@@ -230,14 +215,16 @@ export async function checkUriType(Uri: string) {
|
|||||||
|
|
||||||
export async function uri2local(dir: string, uri: string, filename: string | undefined = undefined): Promise<Uri2LocalRes> {
|
export async function uri2local(dir: string, uri: string, filename: string | undefined = undefined): Promise<Uri2LocalRes> {
|
||||||
const { Uri: HandledUri, Type: UriType } = await checkUriType(uri);
|
const { Uri: HandledUri, Type: UriType } = await checkUriType(uri);
|
||||||
|
|
||||||
//解析失败
|
//解析失败
|
||||||
const tempName = randomUUID();
|
const tempName = randomUUID();
|
||||||
if (!filename) filename = randomUUID();
|
if (!filename) filename = randomUUID();
|
||||||
//解析Http和Https协议
|
|
||||||
|
|
||||||
|
//解析Http和Https协议
|
||||||
if (UriType == FileUriType.Unknown) {
|
if (UriType == FileUriType.Unknown) {
|
||||||
return { success: false, errMsg: `未知文件类型, uri= ${uri}`, fileName: '', ext: '', path: '' };
|
return { success: false, errMsg: `未知文件类型, uri= ${uri}`, fileName: '', ext: '', path: '' };
|
||||||
}
|
}
|
||||||
|
|
||||||
//解析File协议和本地文件
|
//解析File协议和本地文件
|
||||||
if (UriType == FileUriType.Local) {
|
if (UriType == FileUriType.Local) {
|
||||||
const fileExt = path.extname(HandledUri);
|
const fileExt = path.extname(HandledUri);
|
||||||
@@ -249,8 +236,8 @@ export async function uri2local(dir: string, uri: string, filename: string | und
|
|||||||
fs.copyFileSync(HandledUri, filePath);
|
fs.copyFileSync(HandledUri, filePath);
|
||||||
return { success: true, errMsg: '', fileName: filename, ext: fileExt, path: filePath };
|
return { success: true, errMsg: '', fileName: filename, ext: fileExt, path: filePath };
|
||||||
}
|
}
|
||||||
//接下来都要有文件名
|
|
||||||
|
|
||||||
|
//接下来都要有文件名
|
||||||
if (UriType == FileUriType.Remote) {
|
if (UriType == FileUriType.Remote) {
|
||||||
const pathInfo = path.parse(decodeURIComponent(new URL(HandledUri).pathname));
|
const pathInfo = path.parse(decodeURIComponent(new URL(HandledUri).pathname));
|
||||||
if (pathInfo.name) {
|
if (pathInfo.name) {
|
||||||
@@ -268,6 +255,7 @@ export async function uri2local(dir: string, uri: string, filename: string | und
|
|||||||
fs.writeFileSync(filePath, buffer, { flag: 'wx' });
|
fs.writeFileSync(filePath, buffer, { flag: 'wx' });
|
||||||
return { success: true, errMsg: '', fileName: filename, ext: fileExt, path: filePath };
|
return { success: true, errMsg: '', fileName: filename, ext: fileExt, path: filePath };
|
||||||
}
|
}
|
||||||
|
|
||||||
//解析Base64
|
//解析Base64
|
||||||
if (UriType == FileUriType.Base64) {
|
if (UriType == FileUriType.Base64) {
|
||||||
const base64 = HandledUri.replace(/^base64:\/\//, '');
|
const base64 = HandledUri.replace(/^base64:\/\//, '');
|
||||||
|
@@ -1,9 +1,9 @@
|
|||||||
import winston, { format, transports } from 'winston';
|
import winston, { format, transports } from 'winston';
|
||||||
import { truncateString } from '@/common/helper';
|
import { truncateString } from '@/common/helper';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import fs from 'node:fs';
|
import fs from 'node:fs/promises';
|
||||||
import { NTMsgAtType, ChatType, ElementType, MessageElement, RawMessage, SelfInfo } from '@/core';
|
import { NTMsgAtType, ChatType, ElementType, MessageElement, RawMessage, SelfInfo } from '@/core';
|
||||||
|
import EventEmitter from 'node:events';
|
||||||
export enum LogLevel {
|
export enum LogLevel {
|
||||||
DEBUG = 'debug',
|
DEBUG = 'debug',
|
||||||
INFO = 'info',
|
INFO = 'info',
|
||||||
@@ -24,6 +24,36 @@ function getFormattedTimestamp() {
|
|||||||
return `${year}-${month}-${day}_${hours}-${minutes}-${seconds}.${milliseconds}`;
|
return `${year}-${month}-${day}_${hours}-${minutes}-${seconds}.${milliseconds}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const logEmitter = new EventEmitter();
|
||||||
|
export type LogListener = (msg: string) => void;
|
||||||
|
class Subscription {
|
||||||
|
public static MAX_HISTORY = 100;
|
||||||
|
public static history: string[] = [];
|
||||||
|
|
||||||
|
subscribe(listener: LogListener) {
|
||||||
|
for (const history of Subscription.history) {
|
||||||
|
try {
|
||||||
|
listener(history);
|
||||||
|
} catch (_) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logEmitter.on('log', listener);
|
||||||
|
}
|
||||||
|
unsubscribe(listener: LogListener) {
|
||||||
|
logEmitter.off('log', listener);
|
||||||
|
}
|
||||||
|
notify(msg: string) {
|
||||||
|
logEmitter.emit('log', msg);
|
||||||
|
if (Subscription.history.length >= Subscription.MAX_HISTORY) {
|
||||||
|
Subscription.history.shift();
|
||||||
|
}
|
||||||
|
Subscription.history.push(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const logSubscription = new Subscription();
|
||||||
|
|
||||||
export class LogWrapper {
|
export class LogWrapper {
|
||||||
fileLogEnabled = true;
|
fileLogEnabled = true;
|
||||||
consoleLogEnabled = true;
|
consoleLogEnabled = true;
|
||||||
@@ -47,7 +77,7 @@ export class LogWrapper {
|
|||||||
filename: logPath,
|
filename: logPath,
|
||||||
level: 'debug',
|
level: 'debug',
|
||||||
maxsize: 5 * 1024 * 1024, // 5MB
|
maxsize: 5 * 1024 * 1024, // 5MB
|
||||||
maxFiles: 5
|
maxFiles: 5,
|
||||||
}),
|
}),
|
||||||
new transports.Console({
|
new transports.Console({
|
||||||
format: format.combine(
|
format: format.combine(
|
||||||
@@ -56,9 +86,9 @@ export class LogWrapper {
|
|||||||
const userInfo = meta.userInfo ? `${meta.userInfo} | ` : '';
|
const userInfo = meta.userInfo ? `${meta.userInfo} | ` : '';
|
||||||
return `${timestamp} [${level}] ${userInfo}${message}`;
|
return `${timestamp} [${level}] ${userInfo}${message}`;
|
||||||
})
|
})
|
||||||
)
|
),
|
||||||
})
|
}),
|
||||||
]
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
this.setLogSelfInfo({ nick: '', uid: '' });
|
this.setLogSelfInfo({ nick: '', uid: '' });
|
||||||
@@ -67,26 +97,20 @@ export class LogWrapper {
|
|||||||
|
|
||||||
cleanOldLogs(logDir: string) {
|
cleanOldLogs(logDir: string) {
|
||||||
const oneWeekAgo = Date.now() - 7 * 24 * 60 * 60 * 1000;
|
const oneWeekAgo = Date.now() - 7 * 24 * 60 * 60 * 1000;
|
||||||
fs.readdir(logDir, (err, files) => {
|
fs.readdir(logDir).then((files) => {
|
||||||
if (err) {
|
files.forEach((file) => {
|
||||||
this.logger.error('Failed to read log directory', err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
files.forEach(file => {
|
|
||||||
const filePath = path.join(logDir, file);
|
const filePath = path.join(logDir, file);
|
||||||
this.deleteOldLogFile(filePath, oneWeekAgo);
|
this.deleteOldLogFile(filePath, oneWeekAgo);
|
||||||
});
|
});
|
||||||
|
}).catch((err) => {
|
||||||
|
this.logger.error('Failed to read log directory', err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private deleteOldLogFile(filePath: string, oneWeekAgo: number) {
|
private deleteOldLogFile(filePath: string, oneWeekAgo: number) {
|
||||||
fs.stat(filePath, (err, stats) => {
|
fs.stat(filePath).then((stats) => {
|
||||||
if (err) {
|
|
||||||
this.logger.error('Failed to get file stats', err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (stats.mtime.getTime() < oneWeekAgo) {
|
if (stats.mtime.getTime() < oneWeekAgo) {
|
||||||
fs.unlink(filePath, err => {
|
fs.unlink(filePath).catch((err) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
if (err.code === 'ENOENT') {
|
if (err.code === 'ENOENT') {
|
||||||
this.logger.warn(`File already deleted: ${filePath}`);
|
this.logger.warn(`File already deleted: ${filePath}`);
|
||||||
@@ -98,6 +122,8 @@ export class LogWrapper {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}).catch((err) => {
|
||||||
|
this.logger.error('Failed to get file stats', err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,7 +137,7 @@ export class LogWrapper {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
setLogSelfInfo(selfInfo: { nick: string, uid: string }) {
|
setLogSelfInfo(selfInfo: { nick: string; uid: string }) {
|
||||||
const userInfo = `${selfInfo.nick}`;
|
const userInfo = `${selfInfo.nick}`;
|
||||||
this.logger.defaultMeta = { userInfo };
|
this.logger.defaultMeta = { userInfo };
|
||||||
}
|
}
|
||||||
@@ -135,14 +161,16 @@ export class LogWrapper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
formatMsg(msg: any[]) {
|
formatMsg(msg: any[]) {
|
||||||
return msg.map(msgItem => {
|
return msg
|
||||||
if (msgItem instanceof Error) {
|
.map((msgItem) => {
|
||||||
return msgItem.stack;
|
if (msgItem instanceof Error) {
|
||||||
} else if (typeof msgItem === 'object') {
|
return msgItem.stack;
|
||||||
return JSON.stringify(truncateString(JSON.parse(JSON.stringify(msgItem, null, 2))));
|
} else if (typeof msgItem === 'object') {
|
||||||
}
|
return JSON.stringify(truncateString(JSON.parse(JSON.stringify(msgItem, null, 2))));
|
||||||
return msgItem;
|
}
|
||||||
}).join(' ');
|
return msgItem;
|
||||||
|
})
|
||||||
|
.join(' ');
|
||||||
}
|
}
|
||||||
|
|
||||||
_log(level: LogLevel, ...args: any[]) {
|
_log(level: LogLevel, ...args: any[]) {
|
||||||
@@ -155,6 +183,7 @@ export class LogWrapper {
|
|||||||
// eslint-disable-next-line no-control-regex
|
// eslint-disable-next-line no-control-regex
|
||||||
this.logger.log(level, message.replace(/\x1B[@-_][0-?]*[ -/]*[@-~]/g, ''));
|
this.logger.log(level, message.replace(/\x1B[@-_][0-?]*[ -/]*[@-~]/g, ''));
|
||||||
}
|
}
|
||||||
|
logSubscription.notify(JSON.stringify({ level, message }));
|
||||||
}
|
}
|
||||||
|
|
||||||
log(...args: any[]) {
|
log(...args: any[]) {
|
||||||
@@ -282,13 +311,9 @@ function textElementToText(textElement: any): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function replyElementToText(replyElement: any, msg: RawMessage, recursiveLevel: number): string {
|
function replyElementToText(replyElement: any, msg: RawMessage, recursiveLevel: number): string {
|
||||||
const recordMsgOrNull = msg.records.find(
|
const recordMsgOrNull = msg.records.find((record) => replyElement.sourceMsgIdInRecords === record.msgId);
|
||||||
record => replyElement.sourceMsgIdInRecords === record.msgId,
|
return `[回复消息 ${recordMsgOrNull && recordMsgOrNull.peerUin != '284840486' && recordMsgOrNull.peerUin != '1094950020'
|
||||||
);
|
? rawMessageToText(recordMsgOrNull, recursiveLevel + 1)
|
||||||
return `[回复消息 ${recordMsgOrNull &&
|
: `未找到消息记录 (MsgId = ${replyElement.sourceMsgIdInRecords})`
|
||||||
recordMsgOrNull.peerUin != '284840486' && recordMsgOrNull.peerUin != '1094950020'
|
}]`;
|
||||||
?
|
}
|
||||||
rawMessageToText(recordMsgOrNull, recursiveLevel + 1) :
|
|
||||||
`未找到消息记录 (MsgId = ${replyElement.sourceMsgIdInRecords})`
|
|
||||||
}]`;
|
|
||||||
}
|
|
||||||
|
@@ -1,6 +1,5 @@
|
|||||||
import https from 'node:https';
|
import https from 'node:https';
|
||||||
import http from 'node:http';
|
import http from 'node:http';
|
||||||
import { readFileSync } from 'node:fs';
|
|
||||||
|
|
||||||
export class RequestUtil {
|
export class RequestUtil {
|
||||||
// 适用于获取服务器下发cookies时获取,仅GET
|
// 适用于获取服务器下发cookies时获取,仅GET
|
||||||
@@ -69,7 +68,7 @@ export class RequestUtil {
|
|||||||
// 'Content-Length': Buffer.byteLength(postData),
|
// 'Content-Length': Buffer.byteLength(postData),
|
||||||
// },
|
// },
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const req = protocol.request(options, (res: any) => {
|
const req = protocol.request(options, (res: http.IncomingMessage) => {
|
||||||
let responseBody = '';
|
let responseBody = '';
|
||||||
res.on('data', (chunk: string | Buffer) => {
|
res.on('data', (chunk: string | Buffer) => {
|
||||||
responseBody += chunk.toString();
|
responseBody += chunk.toString();
|
||||||
@@ -112,24 +111,4 @@ export class RequestUtil {
|
|||||||
static async HttpGetText(url: string, method: string = 'GET', data?: any, headers: { [key: string]: string } = {}) {
|
static async HttpGetText(url: string, method: string = 'GET', data?: any, headers: { [key: string]: string } = {}) {
|
||||||
return this.HttpGetJson<string>(url, method, data, headers, false, false);
|
return this.HttpGetJson<string>(url, method, data, headers, false, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
static async createFormData(boundary: string, filePath: string): Promise<Buffer> {
|
|
||||||
let type = 'image/png';
|
|
||||||
if (filePath.endsWith('.jpg')) {
|
|
||||||
type = 'image/jpeg';
|
|
||||||
}
|
|
||||||
const formDataParts = [
|
|
||||||
`------${boundary}\r\n`,
|
|
||||||
`Content-Disposition: form-data; name="share_image"; filename="${filePath}"\r\n`,
|
|
||||||
'Content-Type: ' + type + '\r\n\r\n',
|
|
||||||
];
|
|
||||||
|
|
||||||
const fileContent = readFileSync(filePath);
|
|
||||||
const footer = `\r\n------${boundary}--`;
|
|
||||||
return Buffer.concat([
|
|
||||||
Buffer.from(formDataParts.join(''), 'utf8'),
|
|
||||||
fileContent,
|
|
||||||
Buffer.from(footer, 'utf8'),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -1 +1 @@
|
|||||||
export const napCatVersion = '4.1.13';
|
export const napCatVersion = '4.2.16';
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { MsfChangeReasonType, MsfStatusType } from "../types/adapter";
|
import { MsfChangeReasonType, MsfStatusType } from "@/core/types/adapter";
|
||||||
|
|
||||||
export class NodeIDependsAdapter {
|
export class NodeIDependsAdapter {
|
||||||
onMSFStatusChange(statusType: MsfStatusType, changeReasonType: MsfChangeReasonType) {
|
onMSFStatusChange(statusType: MsfStatusType, changeReasonType: MsfChangeReasonType) {
|
||||||
|
@@ -20,13 +20,14 @@ import { InstanceContext, NapCatCore, SearchResultItem } from '@/core';
|
|||||||
import * as fileType from 'file-type';
|
import * as fileType from 'file-type';
|
||||||
import imageSize from 'image-size';
|
import imageSize from 'image-size';
|
||||||
import { ISizeCalculationResult } from 'image-size/dist/types/interface';
|
import { ISizeCalculationResult } from 'image-size/dist/types/interface';
|
||||||
import { RkeyManager } from '../helper/rkey';
|
import { RkeyManager } from '@/core/helper/rkey';
|
||||||
import { calculateFileMD5, isGIF } from '@/common/file';
|
import { calculateFileMD5 } from '@/common/file';
|
||||||
import pathLib from 'node:path';
|
import pathLib from 'node:path';
|
||||||
import { defaultVideoThumbB64, getVideoInfo } from '@/common/video';
|
import { defaultVideoThumbB64, getVideoInfo } from '@/common/video';
|
||||||
import ffmpeg from 'fluent-ffmpeg';
|
import ffmpeg from 'fluent-ffmpeg';
|
||||||
import { encodeSilk } from '@/common/audio';
|
import { encodeSilk } from '@/common/audio';
|
||||||
import { MessageContext } from '@/onebot/api';
|
import { SendMessageContext } from '@/onebot/api';
|
||||||
|
import { getFileTypeForSendType } from '../helper/msg';
|
||||||
|
|
||||||
export class NTQQFileApi {
|
export class NTQQFileApi {
|
||||||
context: InstanceContext;
|
context: InstanceContext;
|
||||||
@@ -40,7 +41,7 @@ export class NTQQFileApi {
|
|||||||
this.rkeyManager = new RkeyManager([
|
this.rkeyManager = new RkeyManager([
|
||||||
'https://rkey.napneko.icu/rkeys'
|
'https://rkey.napneko.icu/rkeys'
|
||||||
],
|
],
|
||||||
this.context.logger
|
this.context.logger
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,7 +91,7 @@ export class NTQQFileApi {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async createValidSendFileElement(context: MessageContext, filePath: string, fileName: string = '', folderId: string = '',): Promise<SendFileElement> {
|
async createValidSendFileElement(context: SendMessageContext, filePath: string, fileName: string = '', folderId: string = '',): Promise<SendFileElement> {
|
||||||
const {
|
const {
|
||||||
fileName: _fileName,
|
fileName: _fileName,
|
||||||
path,
|
path,
|
||||||
@@ -112,7 +113,7 @@ export class NTQQFileApi {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async createValidSendPicElement(context: MessageContext, picPath: string, summary: string = '', subType: PicSubType = 0): Promise<SendPicElement> {
|
async createValidSendPicElement(context: SendMessageContext, picPath: string, summary: string = '', subType: PicSubType = 0): Promise<SendPicElement> {
|
||||||
const { md5, fileName, path, fileSize } = await this.core.apis.FileApi.uploadFile(picPath, ElementType.PIC, subType);
|
const { md5, fileName, path, fileSize } = await this.core.apis.FileApi.uploadFile(picPath, ElementType.PIC, subType);
|
||||||
if (fileSize === 0) {
|
if (fileSize === 0) {
|
||||||
throw new Error('文件异常,大小为0');
|
throw new Error('文件异常,大小为0');
|
||||||
@@ -130,7 +131,7 @@ export class NTQQFileApi {
|
|||||||
fileName: fileName,
|
fileName: fileName,
|
||||||
sourcePath: path,
|
sourcePath: path,
|
||||||
original: true,
|
original: true,
|
||||||
picType: isGIF(picPath) ? PicType.NEWPIC_GIF : PicType.NEWPIC_JPEG,
|
picType: await getFileTypeForSendType(picPath),
|
||||||
picSubType: subType,
|
picSubType: subType,
|
||||||
fileUuid: '',
|
fileUuid: '',
|
||||||
fileSubId: '',
|
fileSubId: '',
|
||||||
@@ -140,8 +141,7 @@ export class NTQQFileApi {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async createValidSendVideoElement(context: MessageContext, filePath: string, fileName: string = '', diyThumbPath: string = ''): Promise<SendVideoElement> {
|
async createValidSendVideoElement(context: SendMessageContext, filePath: string, fileName: string = '', diyThumbPath: string = ''): Promise<SendVideoElement> {
|
||||||
const logger = this.core.context.logger;
|
|
||||||
let videoInfo = {
|
let videoInfo = {
|
||||||
width: 1920,
|
width: 1920,
|
||||||
height: 1080,
|
height: 1080,
|
||||||
@@ -151,9 +151,9 @@ export class NTQQFileApi {
|
|||||||
filePath,
|
filePath,
|
||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
videoInfo = await getVideoInfo(filePath, logger);
|
videoInfo = await getVideoInfo(filePath, this.context.logger);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.logError.bind(logger)('获取视频信息失败,将使用默认值', e);
|
this.context.logger.logError('获取视频信息失败,将使用默认值', e);
|
||||||
}
|
}
|
||||||
|
|
||||||
let fileExt = 'mp4';
|
let fileExt = 'mp4';
|
||||||
@@ -161,7 +161,7 @@ export class NTQQFileApi {
|
|||||||
const tempExt = (await fileType.fileTypeFromFile(filePath))?.ext;
|
const tempExt = (await fileType.fileTypeFromFile(filePath))?.ext;
|
||||||
if (tempExt) fileExt = tempExt;
|
if (tempExt) fileExt = tempExt;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.context.logger.logError.bind(logger)('获取文件类型失败', e);
|
this.context.logger.logError('获取文件类型失败', e);
|
||||||
}
|
}
|
||||||
const newFilePath = filePath + '.' + fileExt;
|
const newFilePath = filePath + '.' + fileExt;
|
||||||
fs.copyFileSync(filePath, newFilePath);
|
fs.copyFileSync(filePath, newFilePath);
|
||||||
@@ -182,7 +182,7 @@ export class NTQQFileApi {
|
|||||||
ffmpeg(filePath)
|
ffmpeg(filePath)
|
||||||
.on('error', (err) => {
|
.on('error', (err) => {
|
||||||
try {
|
try {
|
||||||
logger.logDebug('获取视频封面失败,使用默认封面', err);
|
this.context.logger.logDebug('获取视频封面失败,使用默认封面', err);
|
||||||
if (diyThumbPath) {
|
if (diyThumbPath) {
|
||||||
fsPromises.copyFile(diyThumbPath, thumbPath).then(() => {
|
fsPromises.copyFile(diyThumbPath, thumbPath).then(() => {
|
||||||
resolve(thumbPath);
|
resolve(thumbPath);
|
||||||
@@ -192,7 +192,7 @@ export class NTQQFileApi {
|
|||||||
resolve(thumbPath);
|
resolve(thumbPath);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.logError.bind(logger)('获取视频封面失败,使用默认封面失败', error);
|
this.context.logger.logError('获取视频封面失败,使用默认封面失败', error);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.screenshots({
|
.screenshots({
|
||||||
@@ -229,6 +229,7 @@ export class NTQQFileApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async createValidSendPttElement(pttPath: string): Promise<SendPttElement> {
|
async createValidSendPttElement(pttPath: string): Promise<SendPttElement> {
|
||||||
|
|
||||||
const { converted, path: silkPath, duration } = await encodeSilk(pttPath, this.core.NapCatTempPath, this.core.context.logger);
|
const { converted, path: silkPath, duration } = await encodeSilk(pttPath, this.core.NapCatTempPath, this.core.context.logger);
|
||||||
if (!silkPath) {
|
if (!silkPath) {
|
||||||
throw new Error('语音转换失败, 请检查语音文件是否正常');
|
throw new Error('语音转换失败, 请检查语音文件是否正常');
|
||||||
@@ -238,8 +239,7 @@ export class NTQQFileApi {
|
|||||||
throw new Error('文件异常,大小为0');
|
throw new Error('文件异常,大小为0');
|
||||||
}
|
}
|
||||||
if (converted) {
|
if (converted) {
|
||||||
fsPromises.unlink(silkPath).then().catch(
|
fsPromises.unlink(silkPath).then().catch((e) => this.context.logger.logError('删除临时文件失败', e)
|
||||||
(e) => this.context.logger.logError.bind(this.context.logger)('删除临时文件失败', e)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
@@ -306,18 +306,18 @@ export class NTQQFileApi {
|
|||||||
element.elementType === ElementType.FILE
|
element.elementType === ElementType.FILE
|
||||||
) {
|
) {
|
||||||
switch (element.elementType) {
|
switch (element.elementType) {
|
||||||
case ElementType.PIC:
|
case ElementType.PIC:
|
||||||
element.picElement!.sourcePath = elementResults[elementIndex];
|
element.picElement!.sourcePath = elementResults[elementIndex];
|
||||||
break;
|
break;
|
||||||
case ElementType.VIDEO:
|
case ElementType.VIDEO:
|
||||||
element.videoElement!.filePath = elementResults[elementIndex];
|
element.videoElement!.filePath = elementResults[elementIndex];
|
||||||
break;
|
break;
|
||||||
case ElementType.PTT:
|
case ElementType.PTT:
|
||||||
element.pttElement!.filePath = elementResults[elementIndex];
|
element.pttElement!.filePath = elementResults[elementIndex];
|
||||||
break;
|
break;
|
||||||
case ElementType.FILE:
|
case ElementType.FILE:
|
||||||
element.fileElement!.filePath = elementResults[elementIndex];
|
element.fileElement!.filePath = elementResults[elementIndex];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
elementIndex++;
|
elementIndex++;
|
||||||
}
|
}
|
||||||
@@ -453,7 +453,7 @@ export class NTQQFileApi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
this.context.logger.logError.bind(this.context.logger)('获取rkey失败', error.message);
|
this.context.logger.logError('获取rkey失败', error.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!rkeyData.online_rkey) {
|
if (!rkeyData.online_rkey) {
|
||||||
@@ -463,7 +463,7 @@ export class NTQQFileApi {
|
|||||||
rkeyData.private_rkey = tempRkeyData.private_rkey;
|
rkeyData.private_rkey = tempRkeyData.private_rkey;
|
||||||
rkeyData.online_rkey = tempRkeyData.expired_time > Date.now() / 1000;
|
rkeyData.online_rkey = tempRkeyData.expired_time > Date.now() / 1000;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.context.logger.logError.bind(this.context.logger)('获取rkey失败 Fallback Old Mode', e);
|
this.context.logger.logError('获取rkey失败 Fallback Old Mode', e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,6 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
GeneralCallResult,
|
GeneralCallResult,
|
||||||
Group,
|
|
||||||
GroupMember,
|
GroupMember,
|
||||||
NTGroupMemberRole,
|
NTGroupMemberRole,
|
||||||
NTGroupRequestOperateTypes,
|
NTGroupRequestOperateTypes,
|
||||||
@@ -16,34 +15,22 @@ import { NTEventWrapper } from '@/common/event';
|
|||||||
export class NTQQGroupApi {
|
export class NTQQGroupApi {
|
||||||
context: InstanceContext;
|
context: InstanceContext;
|
||||||
core: NapCatCore;
|
core: NapCatCore;
|
||||||
groupCache: Map<string, Group> = new Map<string, Group>();
|
|
||||||
groupMemberCache: Map<string, Map<string, GroupMember>> = new Map<string, Map<string, GroupMember>>();
|
groupMemberCache: Map<string, Map<string, GroupMember>> = new Map<string, Map<string, GroupMember>>();
|
||||||
groups: Group[] = [];
|
|
||||||
essenceLRU = new LimitedHashTable<number, string>(1000);
|
essenceLRU = new LimitedHashTable<number, string>(1000);
|
||||||
session: any;
|
|
||||||
|
|
||||||
constructor(context: InstanceContext, core: NapCatCore) {
|
constructor(context: InstanceContext, core: NapCatCore) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
this.core = core;
|
this.core = core;
|
||||||
}
|
}
|
||||||
|
|
||||||
async initApi() {
|
async initApi() {
|
||||||
this.initCache().then().catch(this.context.logger.logError.bind(this.context.logger));
|
this.initCache().then().catch(e => this.context.logger.logError(e));
|
||||||
}
|
|
||||||
async initCache() {
|
|
||||||
this.groups = await this.getGroups();
|
|
||||||
for (const group of this.groups) {
|
|
||||||
this.groupCache.set(group.groupCode, group);
|
|
||||||
}
|
|
||||||
this.context.logger.logDebug(`加载${this.groups.length}个群组缓存完成`);
|
|
||||||
// process.pid 调试点
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async getCoreAndBaseInfo(uids: string[]) {
|
async initCache() {
|
||||||
return await this.core.eventWrapper.callNoListenerEvent(
|
for (const group of await this.getGroups(true)) {
|
||||||
'NodeIKernelProfileService/getCoreAndBaseInfo',
|
this.refreshGroupMemberCache(group.groupCode).then().catch();
|
||||||
'nodeStore',
|
}
|
||||||
uids,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fetchGroupEssenceList(groupCode: string) {
|
async fetchGroupEssenceList(groupCode: string) {
|
||||||
@@ -54,20 +41,22 @@ export class NTQQGroupApi {
|
|||||||
pageLimit: 300,
|
pageLimit: 300,
|
||||||
}, pskey);
|
}, pskey);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getGroupShutUpMemberList(groupCode: string) {
|
async getGroupShutUpMemberList(groupCode: string) {
|
||||||
const data = this.core.eventWrapper.registerListen('NodeIKernelGroupListener/onShutUpMemberListChanged', (group_id) => group_id === groupCode, 1, 1000);
|
const data = this.core.eventWrapper.registerListen('NodeIKernelGroupListener/onShutUpMemberListChanged', (group_id) => group_id === groupCode, 1, 1000);
|
||||||
this.context.session.getGroupService().getGroupShutUpMemberList(groupCode);
|
this.context.session.getGroupService().getGroupShutUpMemberList(groupCode);
|
||||||
return (await data)[1];
|
return (await data)[1];
|
||||||
}
|
}
|
||||||
async clearGroupNotifiesUnreadCount(uk: boolean) {
|
|
||||||
return this.context.session.getGroupService().clearGroupNotifiesUnreadCount(uk);
|
async clearGroupNotifiesUnreadCount(doubt: boolean) {
|
||||||
|
return this.context.session.getGroupService().clearGroupNotifiesUnreadCount(doubt);
|
||||||
}
|
}
|
||||||
|
|
||||||
async setGroupAvatar(gc: string, filePath: string) {
|
async setGroupAvatar(groupCode: string, filePath: string) {
|
||||||
return this.context.session.getGroupService().setHeader(gc, filePath);
|
return this.context.session.getGroupService().setHeader(groupCode, filePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getGroups(forced = false) {
|
async getGroups(forced: boolean = false) {
|
||||||
const [, , groupList] = await this.core.eventWrapper.callNormalEventV2(
|
const [, , groupList] = await this.core.eventWrapper.callNormalEventV2(
|
||||||
'NodeIKernelGroupService/getGroupList',
|
'NodeIKernelGroupService/getGroupList',
|
||||||
'NodeIKernelGroupListener/onGroupListUpdate',
|
'NodeIKernelGroupListener/onGroupListUpdate',
|
||||||
@@ -76,9 +65,9 @@ export class NTQQGroupApi {
|
|||||||
return groupList;
|
return groupList;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getGroupExtFE0Info(groupCode: string[], forced = true) {
|
async getGroupExtFE0Info(groupCodes: Array<string>, forced = true) {
|
||||||
return this.context.session.getGroupService().getGroupExt0xEF0Info(
|
return this.context.session.getGroupService().getGroupExt0xEF0Info(
|
||||||
groupCode,
|
groupCodes,
|
||||||
[],
|
[],
|
||||||
{
|
{
|
||||||
bindGuildId: 1,
|
bindGuildId: 1,
|
||||||
@@ -118,53 +107,42 @@ export class NTQQGroupApi {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getGroup(groupCode: string, forced = false) {
|
|
||||||
let group = this.groupCache.get(groupCode.toString());
|
|
||||||
if (!group) {
|
|
||||||
try {
|
|
||||||
const groupList = await this.getGroups(forced);
|
|
||||||
if (groupList.length) {
|
|
||||||
groupList.forEach(g => {
|
|
||||||
this.groupCache.set(g.groupCode, g);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
group = this.groupCache.get(groupCode.toString());
|
|
||||||
return group;
|
|
||||||
}
|
|
||||||
|
|
||||||
async getGroupMemberAll(groupCode: string, forced = false) {
|
async getGroupMemberAll(groupCode: string, forced = false) {
|
||||||
return this.context.session.getGroupService().getAllMemberList(groupCode, forced);
|
return this.context.session.getGroupService().getAllMemberList(groupCode, forced);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async refreshGroupMemberCache(groupCode: string) {
|
||||||
|
try {
|
||||||
|
const members = await this.getGroupMemberAll(groupCode, true);
|
||||||
|
this.groupMemberCache.set(groupCode, members.result.infos);
|
||||||
|
} catch (e) {
|
||||||
|
this.context.logger.logError(`刷新群成员缓存失败, 群号: ${groupCode}, 错误: ${e}`);
|
||||||
|
}
|
||||||
|
return this.groupMemberCache;
|
||||||
|
}
|
||||||
|
|
||||||
async getGroupMember(groupCode: string | number, memberUinOrUid: string | number) {
|
async getGroupMember(groupCode: string | number, memberUinOrUid: string | number) {
|
||||||
const groupCodeStr = groupCode.toString();
|
const groupCodeStr = groupCode.toString();
|
||||||
const memberUinOrUidStr = memberUinOrUid.toString();
|
const memberUinOrUidStr = memberUinOrUid.toString();
|
||||||
|
|
||||||
|
// 获取群成员缓存
|
||||||
let members = this.groupMemberCache.get(groupCodeStr);
|
let members = this.groupMemberCache.get(groupCodeStr);
|
||||||
if (!members) {
|
if (!members) {
|
||||||
try {
|
members = (await this.refreshGroupMemberCache(groupCodeStr)).get(groupCodeStr);
|
||||||
members = await this.getGroupMembers(groupCodeStr);
|
|
||||||
this.groupMemberCache.set(groupCodeStr, members);
|
|
||||||
} catch (e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function getMember() {
|
|
||||||
let member: GroupMember | undefined;
|
|
||||||
if (isNumeric(memberUinOrUidStr)) {
|
|
||||||
member = Array.from(members!.values()).find(member => member.uin === memberUinOrUidStr);
|
|
||||||
} else {
|
|
||||||
member = members!.get(memberUinOrUidStr);
|
|
||||||
}
|
|
||||||
return member;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getMember = () => {
|
||||||
|
if (isNumeric(memberUinOrUidStr)) {
|
||||||
|
return Array.from(members!.values()).find(member => member.uin === memberUinOrUidStr);
|
||||||
|
} else {
|
||||||
|
return members!.get(memberUinOrUidStr);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let member = getMember();
|
let member = getMember();
|
||||||
|
// 如果缓存中不存在该成员,尝试刷新缓存
|
||||||
if (!member) {
|
if (!member) {
|
||||||
members = await this.getGroupMembers(groupCodeStr);
|
members = (await this.refreshGroupMemberCache(groupCodeStr)).get(groupCodeStr);
|
||||||
member = getMember();
|
member = getMember();
|
||||||
}
|
}
|
||||||
return member;
|
return member;
|
||||||
@@ -174,26 +152,26 @@ export class NTQQGroupApi {
|
|||||||
return this.context.session.getGroupService().getGroupRecommendContactArkJson(groupCode);
|
return this.context.session.getGroupService().getGroupRecommendContactArkJson(groupCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
async CreatGroupFileFolder(groupCode: string, folderName: string) {
|
async creatGroupFileFolder(groupCode: string, folderName: string) {
|
||||||
return this.context.session.getRichMediaService().createGroupFolder(groupCode, folderName);
|
return this.context.session.getRichMediaService().createGroupFolder(groupCode, folderName);
|
||||||
}
|
}
|
||||||
|
|
||||||
async DelGroupFile(groupCode: string, files: string[]) {
|
async delGroupFile(groupCode: string, files: Array<string>) {
|
||||||
return this.context.session.getRichMediaService().deleteGroupFile(groupCode, [102], files);
|
return this.context.session.getRichMediaService().deleteGroupFile(groupCode, [102], files);
|
||||||
}
|
}
|
||||||
|
|
||||||
async DelGroupFileFolder(groupCode: string, folderId: string) {
|
async delGroupFileFolder(groupCode: string, folderId: string) {
|
||||||
return this.context.session.getRichMediaService().deleteGroupFolder(groupCode, folderId);
|
return this.context.session.getRichMediaService().deleteGroupFolder(groupCode, folderId);
|
||||||
}
|
}
|
||||||
|
|
||||||
async addGroupEssence(GroupCode: string, msgId: string) {
|
async addGroupEssence(groupCode: string, msgId: string) {
|
||||||
const MsgData = await this.context.session.getMsgService().getMsgsIncludeSelf({
|
const MsgData = await this.context.session.getMsgService().getMsgsIncludeSelf({
|
||||||
chatType: 2,
|
chatType: 2,
|
||||||
guildId: '',
|
guildId: '',
|
||||||
peerUid: GroupCode,
|
peerUid: groupCode,
|
||||||
}, msgId, 1, false);
|
}, msgId, 1, false);
|
||||||
const param = {
|
const param = {
|
||||||
groupCode: GroupCode,
|
groupCode: groupCode,
|
||||||
msgRandom: parseInt(MsgData.msgList[0].msgRandom),
|
msgRandom: parseInt(MsgData.msgList[0].msgRandom),
|
||||||
msgSeq: parseInt(MsgData.msgList[0].msgSeq),
|
msgSeq: parseInt(MsgData.msgList[0].msgSeq),
|
||||||
};
|
};
|
||||||
@@ -204,9 +182,9 @@ export class NTQQGroupApi {
|
|||||||
return this.context.session.getGroupService().kickMemberV2(param);
|
return this.context.session.getGroupService().kickMemberV2(param);
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteGroupBulletin(GroupCode: string, noticeId: string) {
|
async deleteGroupBulletin(groupCode: string, noticeId: string) {
|
||||||
const psKey = (await this.core.apis.UserApi.getPSkey(['qun.qq.com'])).domainPskeyMap.get('qun.qq.com')!;
|
const psKey = (await this.core.apis.UserApi.getPSkey(['qun.qq.com'])).domainPskeyMap.get('qun.qq.com')!;
|
||||||
return this.context.session.getGroupService().deleteGroupBulletin(GroupCode, psKey, noticeId);
|
return this.context.session.getGroupService().deleteGroupBulletin(groupCode, psKey, noticeId);
|
||||||
}
|
}
|
||||||
|
|
||||||
async quitGroupV2(GroupCode: string, needDeleteLocalMsg: boolean) {
|
async quitGroupV2(GroupCode: string, needDeleteLocalMsg: boolean) {
|
||||||
@@ -217,65 +195,42 @@ export class NTQQGroupApi {
|
|||||||
return this.context.session.getGroupService().quitGroupV2(param);
|
return this.context.session.getGroupService().quitGroupV2(param);
|
||||||
}
|
}
|
||||||
|
|
||||||
async removeGroupEssenceBySeq(GroupCode: string, msgRandom: string, msgSeq: string) {
|
async removeGroupEssenceBySeq(groupCode: string, msgRandom: string, msgSeq: string) {
|
||||||
const param = {
|
const param = {
|
||||||
groupCode: GroupCode,
|
groupCode: groupCode,
|
||||||
msgRandom: parseInt(msgRandom),
|
msgRandom: parseInt(msgRandom),
|
||||||
msgSeq: parseInt(msgSeq),
|
msgSeq: parseInt(msgSeq),
|
||||||
};
|
};
|
||||||
return this.context.session.getGroupService().removeGroupEssence(param);
|
return this.context.session.getGroupService().removeGroupEssence(param);
|
||||||
}
|
}
|
||||||
|
|
||||||
async removeGroupEssence(GroupCode: string, msgId: string) {
|
async removeGroupEssence(groupCode: string, msgId: string) {
|
||||||
const MsgData = await this.context.session.getMsgService().getMsgsIncludeSelf({
|
const MsgData = await this.context.session.getMsgService().getMsgsIncludeSelf({
|
||||||
chatType: 2,
|
chatType: 2,
|
||||||
guildId: '',
|
guildId: '',
|
||||||
peerUid: GroupCode,
|
peerUid: groupCode,
|
||||||
}, msgId, 1, false);
|
}, msgId, 1, false);
|
||||||
const param = {
|
const param = {
|
||||||
groupCode: GroupCode,
|
groupCode: groupCode,
|
||||||
msgRandom: parseInt(MsgData.msgList[0].msgRandom),
|
msgRandom: parseInt(MsgData.msgList[0].msgRandom),
|
||||||
msgSeq: parseInt(MsgData.msgList[0].msgSeq),
|
msgSeq: parseInt(MsgData.msgList[0].msgSeq),
|
||||||
};
|
};
|
||||||
return this.context.session.getGroupService().removeGroupEssence(param);
|
return this.context.session.getGroupService().removeGroupEssence(param);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getSingleScreenNotifies(doubt: boolean, num: number) {
|
async getSingleScreenNotifies(doubt: boolean, count: number) {
|
||||||
const [, , , notifies] = await this.core.eventWrapper.callNormalEventV2(
|
const [, , , notifies] = await this.core.eventWrapper.callNormalEventV2(
|
||||||
'NodeIKernelGroupService/getSingleScreenNotifies',
|
'NodeIKernelGroupService/getSingleScreenNotifies',
|
||||||
'NodeIKernelGroupListener/onGroupSingleScreenNotifies',
|
'NodeIKernelGroupListener/onGroupSingleScreenNotifies',
|
||||||
[
|
[
|
||||||
doubt,
|
doubt,
|
||||||
'',
|
'',
|
||||||
num,
|
count,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
return notifies;
|
return notifies;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getGroupMemberV2(GroupCode: string, uid: string, forced = false) {
|
|
||||||
const Listener = this.core.eventWrapper.registerListen(
|
|
||||||
'NodeIKernelGroupListener/onMemberInfoChange',
|
|
||||||
(params, _, members) => params === GroupCode && members.size > 0,
|
|
||||||
1,
|
|
||||||
forced ? 5000 : 250,
|
|
||||||
);
|
|
||||||
const retData = await (
|
|
||||||
this.core.eventWrapper
|
|
||||||
.createEventFunction('NodeIKernelGroupService/getMemberInfo')
|
|
||||||
)!(GroupCode, [uid], forced);
|
|
||||||
if (retData.result !== 0) {
|
|
||||||
throw new Error(`${retData.errMsg}`);
|
|
||||||
}
|
|
||||||
const result = await Listener as unknown;
|
|
||||||
let member: GroupMember | undefined;
|
|
||||||
if (Array.isArray(result) && result?.[2] instanceof Map) {
|
|
||||||
const members = result[2] as Map<string, GroupMember>;
|
|
||||||
member = members.get(uid);
|
|
||||||
}
|
|
||||||
return member;
|
|
||||||
}
|
|
||||||
|
|
||||||
async searchGroup(groupCode: string) {
|
async searchGroup(groupCode: string) {
|
||||||
const [, ret] = await this.core.eventWrapper.callNormalEventV2(
|
const [, ret] = await this.core.eventWrapper.callNormalEventV2(
|
||||||
'NodeIKernelSearchService/searchGroup',
|
'NodeIKernelSearchService/searchGroup',
|
||||||
@@ -294,127 +249,43 @@ export class NTQQGroupApi {
|
|||||||
return ret.groupInfos.find(g => g.groupCode === groupCode);
|
return ret.groupInfos.find(g => g.groupCode === groupCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getGroupMemberEx(GroupCode: string, uid: string, forced = false, retry = 2) {
|
async getGroupMemberEx(groupCode: string, uid: string, forced: boolean = false, retry: number = 2) {
|
||||||
const data = await solveAsyncProblem((eventWrapper: NTEventWrapper, GroupCode: string, uid: string, forced = false) => {
|
const data = await solveAsyncProblem((eventWrapper: NTEventWrapper, GroupCode: string, uid: string, forced = false) => {
|
||||||
return eventWrapper.callNormalEventV2(
|
return eventWrapper.callNormalEventV2(
|
||||||
'NodeIKernelGroupService/getMemberInfo',
|
'NodeIKernelGroupService/getMemberInfo',
|
||||||
'NodeIKernelGroupListener/onMemberInfoChange',
|
'NodeIKernelGroupListener/onMemberInfoChange',
|
||||||
[GroupCode, [uid], forced],
|
[groupCode, [uid], forced],
|
||||||
(ret) => ret.result === 0,
|
(ret) => ret.result === 0,
|
||||||
(params, _, members) => params === GroupCode && members.size > 0 && members.has(uid),
|
(params, _, members) => params === GroupCode && members.size > 0 && members.has(uid),
|
||||||
1,
|
1,
|
||||||
forced ? 2500 : 250
|
forced ? 2500 : 250
|
||||||
);
|
);
|
||||||
}, this.core.eventWrapper, GroupCode, uid, forced);
|
}, this.core.eventWrapper, groupCode, uid, forced);
|
||||||
if (data && data[3] instanceof Map && data[3].has(uid)) {
|
if (data && data[3] instanceof Map && data[3].has(uid)) {
|
||||||
return data[3].get(uid);
|
return data[3].get(uid);
|
||||||
}
|
}
|
||||||
if (retry > 0) {
|
if (retry > 0) {
|
||||||
const trydata = await this.getGroupMemberEx(GroupCode, uid, true, retry - 1) as GroupMember | undefined;
|
const trydata = await this.getGroupMemberEx(groupCode, uid, true, retry - 1) as GroupMember | undefined;
|
||||||
if (trydata) return trydata;
|
if (trydata) return trydata;
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
async tryGetGroupMembersV2(groupQQ: string, modeListener = false, num = 30, timeout = 100): Promise<{
|
async getGroupFileCount(groupCodes: Array<string>) {
|
||||||
infos: Map<string, GroupMember>;
|
return this.context.session.getRichMediaService().batchGetGroupFileCount(groupCodes);
|
||||||
finish: boolean;
|
|
||||||
hasNext: boolean | undefined;
|
|
||||||
}> {
|
|
||||||
const sceneId = this.context.session.getGroupService().createMemberListScene(groupQQ, 'groupMemberList_MainWindow_1');
|
|
||||||
const once = this.core.eventWrapper.registerListen('NodeIKernelGroupListener/onMemberListChange', (params) => params.sceneId === sceneId, 0, timeout)
|
|
||||||
.catch(() => { });
|
|
||||||
const result = await this.context.session.getGroupService().getNextMemberList(sceneId, undefined, num);
|
|
||||||
if (result.errCode !== 0) {
|
|
||||||
throw new Error('获取群成员列表出错,' + result.errMsg);
|
|
||||||
}
|
|
||||||
let resMode2;
|
|
||||||
if (modeListener) {
|
|
||||||
const ret = (await once)?.[0];
|
|
||||||
if (ret) {
|
|
||||||
resMode2 = ret;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.context.session.getGroupService().destroyMemberListScene(sceneId);
|
|
||||||
return {
|
|
||||||
infos: new Map([...(resMode2?.infos ?? []), ...result.result.infos]),
|
|
||||||
finish: result.result.finish,
|
|
||||||
hasNext: resMode2?.hasNext,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async GetGroupMembersV3(groupQQ: string, num = 3000, timeout = 2500): Promise<{
|
async getArkJsonGroupShare(groupCode: string) {
|
||||||
infos: Map<string, GroupMember>;
|
|
||||||
finish: boolean;
|
|
||||||
hasNext: boolean | undefined;
|
|
||||||
listenerMode: boolean;
|
|
||||||
}> {
|
|
||||||
const sceneId = this.context.session.getGroupService().createMemberListScene(groupQQ, 'groupMemberList_MainWindow_1');
|
|
||||||
const once = this.core.eventWrapper.registerListen('NodeIKernelGroupListener/onMemberListChange', (params) => params.sceneId === sceneId, 0, timeout)
|
|
||||||
.catch(() => { });
|
|
||||||
const result = await this.context.session.getGroupService().getNextMemberList(sceneId, undefined, num);
|
|
||||||
if (result.errCode !== 0) {
|
|
||||||
throw new Error('获取群成员列表出错,' + result.errMsg);
|
|
||||||
}
|
|
||||||
let resMode2;
|
|
||||||
if (result.result.finish && result.result.infos.size === 0) {
|
|
||||||
const ret = (await once)?.[0];
|
|
||||||
if (ret) {
|
|
||||||
resMode2 = ret;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.context.session.getGroupService().destroyMemberListScene(sceneId);
|
|
||||||
return {
|
|
||||||
infos: new Map([...(resMode2?.infos ?? []), ...result.result.infos]),
|
|
||||||
finish: result.result.finish,
|
|
||||||
hasNext: resMode2?.hasNext,
|
|
||||||
listenerMode: resMode2?.hasNext !== undefined
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
async getGroupMembersV2(groupQQ: string, num = 3000, no_cache: boolean = false): Promise<Map<string, GroupMember>> {
|
|
||||||
if (no_cache) {
|
|
||||||
return (await this.getGroupMemberAll(groupQQ, true)).result.infos;
|
|
||||||
}
|
|
||||||
let res = await this.GetGroupMembersV3(groupQQ, num);
|
|
||||||
let ret = res.infos;
|
|
||||||
if (res.infos.size === 0 && !res.listenerMode) {
|
|
||||||
res = await this.GetGroupMembersV3(groupQQ, num);
|
|
||||||
ret = res.infos;
|
|
||||||
}
|
|
||||||
if (res.infos.size === 0) {
|
|
||||||
ret = (await this.getGroupMemberAll(groupQQ)).result.infos;
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
async getGroupMembers(groupQQ: string, num = 3000): Promise<Map<string, GroupMember>> {
|
|
||||||
const groupService = this.context.session.getGroupService();
|
|
||||||
const sceneId = groupService.createMemberListScene(groupQQ, 'groupMemberList_MainWindow');
|
|
||||||
const result = await groupService.getNextMemberList(sceneId, undefined, num);
|
|
||||||
if (result.errCode !== 0) {
|
|
||||||
throw new Error('获取群成员列表出错,' + result.errMsg);
|
|
||||||
}
|
|
||||||
this.context.logger.logDebug(`获取群(${groupQQ})成员列表结果:`, `members: ${result.result.infos.size}`);
|
|
||||||
return result.result.infos;
|
|
||||||
}
|
|
||||||
|
|
||||||
async getGroupFileCount(group_ids: Array<string>) {
|
|
||||||
return this.context.session.getRichMediaService().batchGetGroupFileCount(group_ids);
|
|
||||||
}
|
|
||||||
|
|
||||||
async getArkJsonGroupShare(GroupCode: string) {
|
|
||||||
const ret = await this.core.eventWrapper.callNoListenerEvent(
|
const ret = await this.core.eventWrapper.callNoListenerEvent(
|
||||||
'NodeIKernelGroupService/getGroupRecommendContactArkJson',
|
'NodeIKernelGroupService/getGroupRecommendContactArkJson',
|
||||||
GroupCode,
|
groupCode,
|
||||||
) as GeneralCallResult & { arkJson: string };
|
) as GeneralCallResult & { arkJson: string };
|
||||||
return ret.arkJson;
|
return ret.arkJson;
|
||||||
}
|
}
|
||||||
|
|
||||||
//需要异常处理
|
async uploadGroupBulletinPic(groupCode: string, imageurl: string) {
|
||||||
async uploadGroupBulletinPic(GroupCode: string, imageurl: string) {
|
|
||||||
const _Pskey = (await this.core.apis.UserApi.getPSkey(['qun.qq.com'])).domainPskeyMap.get('qun.qq.com')!;
|
const _Pskey = (await this.core.apis.UserApi.getPSkey(['qun.qq.com'])).domainPskeyMap.get('qun.qq.com')!;
|
||||||
return this.context.session.getGroupService().uploadGroupBulletinPic(GroupCode, _Pskey, imageurl);
|
return this.context.session.getGroupService().uploadGroupBulletinPic(groupCode, _Pskey, imageurl);
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleGroupRequest(flag: string, operateType: NTGroupRequestOperateTypes, reason?: string) {
|
async handleGroupRequest(flag: string, operateType: NTGroupRequestOperateTypes, reason?: string) {
|
||||||
@@ -436,36 +307,36 @@ export class NTQQGroupApi {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async quitGroup(groupQQ: string) {
|
async quitGroup(groupCode: string) {
|
||||||
return this.context.session.getGroupService().quitGroup(groupQQ);
|
return this.context.session.getGroupService().quitGroup(groupCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
async kickMember(groupQQ: string, kickUids: string[], refuseForever: boolean = false, kickReason: string = '') {
|
async kickMember(groupCode: string, kickUids: string[], refuseForever: boolean = false, kickReason: string = '') {
|
||||||
return this.context.session.getGroupService().kickMember(groupQQ, kickUids, refuseForever, kickReason);
|
return this.context.session.getGroupService().kickMember(groupCode, kickUids, refuseForever, kickReason);
|
||||||
}
|
}
|
||||||
|
|
||||||
async banMember(groupQQ: string, memList: Array<{ uid: string, timeStamp: number }>) {
|
async banMember(groupCode: string, memList: Array<{ uid: string, timeStamp: number }>) {
|
||||||
// timeStamp为秒数, 0为解除禁言
|
// timeStamp为秒数, 0为解除禁言
|
||||||
return this.context.session.getGroupService().setMemberShutUp(groupQQ, memList);
|
return this.context.session.getGroupService().setMemberShutUp(groupCode, memList);
|
||||||
}
|
}
|
||||||
|
|
||||||
async banGroup(groupQQ: string, shutUp: boolean) {
|
async banGroup(groupCode: string, shutUp: boolean) {
|
||||||
return this.context.session.getGroupService().setGroupShutUp(groupQQ, shutUp);
|
return this.context.session.getGroupService().setGroupShutUp(groupCode, shutUp);
|
||||||
}
|
}
|
||||||
|
|
||||||
async setMemberCard(groupQQ: string, memberUid: string, cardName: string) {
|
async setMemberCard(groupCode: string, memberUid: string, cardName: string) {
|
||||||
return this.context.session.getGroupService().modifyMemberCardName(groupQQ, memberUid, cardName);
|
return this.context.session.getGroupService().modifyMemberCardName(groupCode, memberUid, cardName);
|
||||||
}
|
}
|
||||||
|
|
||||||
async setMemberRole(groupQQ: string, memberUid: string, role: NTGroupMemberRole) {
|
async setMemberRole(groupCode: string, memberUid: string, role: NTGroupMemberRole) {
|
||||||
return this.context.session.getGroupService().modifyMemberRole(groupQQ, memberUid, role);
|
return this.context.session.getGroupService().modifyMemberRole(groupCode, memberUid, role);
|
||||||
}
|
}
|
||||||
|
|
||||||
async setGroupName(groupQQ: string, groupName: string) {
|
async setGroupName(groupCode: string, groupName: string) {
|
||||||
return this.context.session.getGroupService().modifyGroupName(groupQQ, groupName, false);
|
return this.context.session.getGroupService().modifyGroupName(groupCode, groupName, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
async publishGroupBulletin(groupQQ: string, content: string, picInfo: {
|
async publishGroupBulletin(groupCode: string, content: string, picInfo: {
|
||||||
id: string,
|
id: string,
|
||||||
width: number,
|
width: number,
|
||||||
height: number
|
height: number
|
||||||
@@ -479,11 +350,11 @@ export class NTQQGroupApi {
|
|||||||
pinned: pinned,
|
pinned: pinned,
|
||||||
confirmRequired: confirmRequired,
|
confirmRequired: confirmRequired,
|
||||||
};
|
};
|
||||||
return this.context.session.getGroupService().publishGroupBulletin(groupQQ, psKey!, data);
|
return this.context.session.getGroupService().publishGroupBulletin(groupCode, psKey!, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getGroupRemainAtTimes(GroupCode: string) {
|
async getGroupRemainAtTimes(groupCode: string) {
|
||||||
return this.context.session.getGroupService().getGroupRemainAtTimes(GroupCode);
|
return this.context.session.getGroupService().getGroupRemainAtTimes(groupCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getMemberExtInfo(groupCode: string, uin: string) {
|
async getMemberExtInfo(groupCode: string, uin: string) {
|
||||||
|
@@ -31,7 +31,7 @@ export class NTQQPacketApi {
|
|||||||
await this.InitSendPacket(this.context.basicInfoWrapper.getFullQQVesion())
|
await this.InitSendPacket(this.context.basicInfoWrapper.getFullQQVesion())
|
||||||
.then()
|
.then()
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
this.logger.logError.bind(this.core.context.logger);
|
this.logger.logError(err);
|
||||||
this.errStack.push(err);
|
this.errStack.push(err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@@ -18,30 +18,28 @@ export class NTQQUserApi {
|
|||||||
async getStatusByUid(uid: string) {
|
async getStatusByUid(uid: string) {
|
||||||
return this.context.session.getProfileService().getStatus(uid);
|
return this.context.session.getProfileService().getStatus(uid);
|
||||||
}
|
}
|
||||||
async getProfileLike(uid: string, start: number, count: number) {
|
|
||||||
|
async getCoreAndBaseInfo(uids: string[]) {
|
||||||
|
return await this.core.eventWrapper.callNoListenerEvent(
|
||||||
|
'NodeIKernelProfileService/getCoreAndBaseInfo',
|
||||||
|
'nodeStore',
|
||||||
|
uids,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 默认获取自己的 type = 2 获取别人 type = 1
|
||||||
|
async getProfileLike(uid: string, start: number, count: number, type: number = 2) {
|
||||||
return this.context.session.getProfileLikeService().getBuddyProfileLike({
|
return this.context.session.getProfileLikeService().getBuddyProfileLike({
|
||||||
friendUids: [uid],
|
friendUids: [uid],
|
||||||
basic: 1,
|
basic: 1,
|
||||||
vote: 1,
|
vote: 1,
|
||||||
favorite: 0,
|
favorite: 0,
|
||||||
userProfile: 1,
|
userProfile: 1,
|
||||||
type: 2,
|
type: type,
|
||||||
start: start,
|
start: start,
|
||||||
limit: count,
|
limit: count,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
async fetchOtherProfileLike(uid: string) {
|
|
||||||
return this.context.session.getProfileLikeService().getBuddyProfileLike({
|
|
||||||
friendUids: [uid],
|
|
||||||
basic: 1,
|
|
||||||
vote: 1,
|
|
||||||
favorite: 0,
|
|
||||||
userProfile: 0,
|
|
||||||
type: 1,
|
|
||||||
start: 0,
|
|
||||||
limit: 20,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
async setLongNick(longNick: string) {
|
async setLongNick(longNick: string) {
|
||||||
return this.context.session.getProfileService().setLongNick(longNick);
|
return this.context.session.getProfileService().setLongNick(longNick);
|
||||||
}
|
}
|
||||||
@@ -101,7 +99,7 @@ export class NTQQUserApi {
|
|||||||
};
|
};
|
||||||
return RetUser;
|
return RetUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getUserDetailInfo(uid: string): Promise<User> {
|
async getUserDetailInfo(uid: string): Promise<User> {
|
||||||
let retUser = await solveAsyncProblem(async (uid) => this.fetchUserDetailInfo(uid, UserDetailSource.KDB), uid);
|
let retUser = await solveAsyncProblem(async (uid) => this.fetchUserDetailInfo(uid, UserDetailSource.KDB), uid);
|
||||||
if (retUser && retUser.uin !== '0') {
|
if (retUser && retUser.uin !== '0') {
|
||||||
@@ -192,13 +190,13 @@ export class NTQQUserApi {
|
|||||||
//后期改成流水线处理
|
//后期改成流水线处理
|
||||||
async getUinByUidV2(Uid: string) {
|
async getUinByUidV2(Uid: string) {
|
||||||
let uin = (await this.context.session.getGroupService().getUinByUids([Uid])).uins.get(Uid);
|
let uin = (await this.context.session.getGroupService().getUinByUids([Uid])).uins.get(Uid);
|
||||||
if (uin) return uin;
|
if (uin && uin !== '0') return uin;
|
||||||
uin = (await this.context.session.getProfileService().getUinByUid('FriendsServiceImpl', [Uid])).get(Uid);
|
uin = (await this.context.session.getProfileService().getUinByUid('FriendsServiceImpl', [Uid])).get(Uid);
|
||||||
if (uin) return uin;
|
if (uin && uin !== '0') return uin;
|
||||||
uin = (await this.context.session.getUixConvertService().getUin([Uid])).uinInfo.get(Uid);
|
uin = (await this.context.session.getUixConvertService().getUin([Uid])).uinInfo.get(Uid);
|
||||||
if (uin) return uin;
|
if (uin && uin !== '0') return uin;
|
||||||
uin = (await this.core.apis.FriendApi.getBuddyIdMap(true)).getKey(Uid);
|
uin = (await this.core.apis.FriendApi.getBuddyIdMap(true)).getKey(Uid);
|
||||||
if (uin) return uin;
|
if (uin && uin !== '0') return uin;
|
||||||
uin = (await this.getUserDetailInfo(Uid)).uin; //从QQ Native 转换
|
uin = (await this.getUserDetailInfo(Uid)).uin; //从QQ Native 转换
|
||||||
return uin;
|
return uin;
|
||||||
}
|
}
|
||||||
|
10
src/core/external/appid.json
vendored
10
src/core/external/appid.json
vendored
@@ -90,5 +90,13 @@
|
|||||||
"9.9.16-29927": {
|
"9.9.16-29927": {
|
||||||
"appid": 537255812,
|
"appid": 537255812,
|
||||||
"qua": "V1_WIN_NQ_9.9.16_29927_GW_B"
|
"qua": "V1_WIN_NQ_9.9.16_29927_GW_B"
|
||||||
|
},
|
||||||
|
"3.2.13-29927": {
|
||||||
|
"appid": 537255847,
|
||||||
|
"qua": "V1_LNX_NQ_3.2.13_29927_GW_B"
|
||||||
|
},
|
||||||
|
"6.9.61-29927": {
|
||||||
|
"appid": 537255836,
|
||||||
|
"qua": "V1_MAC_NQ_6.9.61_29927_GW_B"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
18
src/core/external/offset.json
vendored
18
src/core/external/offset.json
vendored
@@ -86,5 +86,21 @@
|
|||||||
"9.9.16-29927-x64": {
|
"9.9.16-29927-x64": {
|
||||||
"send": "3869C50",
|
"send": "3869C50",
|
||||||
"recv": "386E084"
|
"recv": "386E084"
|
||||||
|
},
|
||||||
|
"3.2.13-29927-x64": {
|
||||||
|
"send": "A1913A0",
|
||||||
|
"recv": "A194CA0"
|
||||||
|
},
|
||||||
|
"3.2.13-29927-arm64": {
|
||||||
|
"send": "6F1C7E0",
|
||||||
|
"recv": "6F20018"
|
||||||
|
},
|
||||||
|
"6.9.61-29927-x64": {
|
||||||
|
"send": "44FCC60",
|
||||||
|
"recv": "44FF4CC"
|
||||||
|
},
|
||||||
|
"6.9.61-29927-arm64": {
|
||||||
|
"send": "4038740",
|
||||||
|
"recv": "403AF58"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
14
src/core/helper/msg.ts
Normal file
14
src/core/helper/msg.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import * as fileType from 'file-type';
|
||||||
|
import { PicType } from '../types';
|
||||||
|
export async function getFileTypeForSendType(picPath: string): Promise<PicType> {
|
||||||
|
const fileTypeResult = (await fileType.fileTypeFromFile(picPath))?.ext ?? 'jpg';
|
||||||
|
const picTypeMap: { [key: string]: PicType } = {
|
||||||
|
//'webp': PicType.NEWPIC_WEBP,
|
||||||
|
'gif': PicType.NEWPIC_GIF,
|
||||||
|
// 'png': PicType.NEWPIC_APNG,
|
||||||
|
// 'jpg': PicType.NEWPIC_JPEG,
|
||||||
|
// 'jpeg': PicType.NEWPIC_JPEG,
|
||||||
|
// 'bmp': PicType.NEWPIC_BMP,
|
||||||
|
};
|
||||||
|
return picTypeMap[fileTypeResult] ?? PicType.NEWPIC_JPEG;
|
||||||
|
}
|
@@ -27,7 +27,6 @@ export class RkeyManager {
|
|||||||
await this.refreshRkey();
|
await this.refreshRkey();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new Error(`获取rkey失败: ${e}`);//外抛
|
throw new Error(`获取rkey失败: ${e}`);//外抛
|
||||||
//this.logger.logError.bind(this.logger)('获取rkey失败', e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return this.rkeyData;
|
return this.rkeyData;
|
||||||
@@ -50,7 +49,7 @@ export class RkeyManager {
|
|||||||
expired_time: temp.expired_time
|
expired_time: temp.expired_time
|
||||||
};
|
};
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.logger.logError.bind(this.logger)(`[Rkey] Get Rkey ${url} Error `, e);
|
this.logger.logError(`[Rkey] Get Rkey ${url} Error `, e);
|
||||||
//是否为最后一个url
|
//是否为最后一个url
|
||||||
if (url === this.serverUrl[this.serverUrl.length - 1]) {
|
if (url === this.serverUrl[this.serverUrl.length - 1]) {
|
||||||
throw new Error(`获取rkey失败: ${e}`);//外抛
|
throw new Error(`获取rkey失败: ${e}`);//外抛
|
||||||
|
@@ -24,10 +24,10 @@ import path from 'node:path';
|
|||||||
import fs from 'node:fs';
|
import fs from 'node:fs';
|
||||||
import { hostname, systemName, systemVersion } from '@/common/system';
|
import { hostname, systemName, systemVersion } from '@/common/system';
|
||||||
import { NTEventWrapper } from '@/common/event';
|
import { NTEventWrapper } from '@/common/event';
|
||||||
import { DataSource, GroupMember, KickedOffLineInfo, SelfInfo, SelfStatusInfo } from '@/core/types';
|
import { GroupMember, KickedOffLineInfo, SelfInfo, SelfStatusInfo } from '@/core/types';
|
||||||
import { NapCatConfigLoader } from '@/core/helper/config';
|
import { NapCatConfigLoader } from '@/core/helper/config';
|
||||||
import os from 'node:os';
|
import os from 'node:os';
|
||||||
import { NodeIKernelGroupListener, NodeIKernelMsgListener, NodeIKernelProfileListener } from '@/core/listeners';
|
import { NodeIKernelMsgListener, NodeIKernelProfileListener } from '@/core/listeners';
|
||||||
import { proxiedListenerOf } from '@/common/proxy-handler';
|
import { proxiedListenerOf } from '@/common/proxy-handler';
|
||||||
import { NTQQPacketApi } from './apis/packet';
|
import { NTQQPacketApi } from './apis/packet';
|
||||||
export * from './wrapper';
|
export * from './wrapper';
|
||||||
@@ -127,7 +127,7 @@ export class NapCatCore {
|
|||||||
await api.initApi();
|
await api.initApi();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.initNapCatCoreListeners().then().catch(this.context.logger.logError.bind(this.context.logger));
|
this.initNapCatCoreListeners().then().catch((e) => this.context.logger.logError(e));
|
||||||
|
|
||||||
this.context.logger.setFileLogEnabled(
|
this.context.logger.setFileLogEnabled(
|
||||||
this.configLoader.configData.fileLog,
|
this.configLoader.configData.fileLog,
|
||||||
@@ -154,7 +154,7 @@ export class NapCatCore {
|
|||||||
const msgListener = new NodeIKernelMsgListener();
|
const msgListener = new NodeIKernelMsgListener();
|
||||||
msgListener.onKickedOffLine = (Info: KickedOffLineInfo) => {
|
msgListener.onKickedOffLine = (Info: KickedOffLineInfo) => {
|
||||||
// 下线通知
|
// 下线通知
|
||||||
this.context.logger.logError.bind(this.context.logger)('[KickedOffLine] [' + Info.tipsTitle + '] ' + Info.tipsDesc);
|
this.context.logger.logError('[KickedOffLine] [' + Info.tipsTitle + '] ' + Info.tipsDesc);
|
||||||
this.selfInfo.online = false;
|
this.selfInfo.online = false;
|
||||||
};
|
};
|
||||||
msgListener.onRecvMsg = (msgs) => {
|
msgListener.onRecvMsg = (msgs) => {
|
||||||
@@ -163,7 +163,6 @@ export class NapCatCore {
|
|||||||
msgListener.onAddSendMsg = (msg) => {
|
msgListener.onAddSendMsg = (msg) => {
|
||||||
this.context.logger.logMessage(msg, this.selfInfo);
|
this.context.logger.logMessage(msg, this.selfInfo);
|
||||||
};
|
};
|
||||||
//await sleep(2500);
|
|
||||||
this.context.session.getMsgService().addKernelMsgListener(
|
this.context.session.getMsgService().addKernelMsgListener(
|
||||||
proxiedListenerOf(msgListener, this.context.logger),
|
proxiedListenerOf(msgListener, this.context.logger),
|
||||||
);
|
);
|
||||||
@@ -185,92 +184,6 @@ export class NapCatCore {
|
|||||||
this.context.session.getProfileService().addKernelProfileListener(
|
this.context.session.getProfileService().addKernelProfileListener(
|
||||||
proxiedListenerOf(profileListener, this.context.logger),
|
proxiedListenerOf(profileListener, this.context.logger),
|
||||||
);
|
);
|
||||||
|
|
||||||
// 群相关
|
|
||||||
const groupListener = new NodeIKernelGroupListener();
|
|
||||||
groupListener.onGroupListUpdate = (updateType, groupList) => {
|
|
||||||
// console.log("onGroupListUpdate", updateType, groupList)
|
|
||||||
groupList.map(g => {
|
|
||||||
const existGroup = this.apis.GroupApi.groupCache.get(g.groupCode);
|
|
||||||
//群成员数量变化 应该刷新缓存
|
|
||||||
if (existGroup && g.memberCount === existGroup.memberCount) {
|
|
||||||
Object.assign(existGroup, g);
|
|
||||||
} else {
|
|
||||||
this.apis.GroupApi.groupCache.set(g.groupCode, g);
|
|
||||||
// 获取群成员
|
|
||||||
}
|
|
||||||
const sceneId = this.context.session.getGroupService().createMemberListScene(g.groupCode, 'groupMemberList_MainWindow');
|
|
||||||
this.context.session.getGroupService().getNextMemberList(sceneId, undefined, 3000).then( /* r => {
|
|
||||||
// console.log(`get group ${g.groupCode} members`, r);
|
|
||||||
// r.result.infos.forEach(member => {
|
|
||||||
// });
|
|
||||||
// groupMembers.set(g.groupCode, r.result.infos);
|
|
||||||
} */);
|
|
||||||
this.context.session.getGroupService().destroyMemberListScene(sceneId);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
groupListener.onMemberListChange = (arg) => {
|
|
||||||
// TODO: 应该加一个内部自己维护的成员变动callback,用于判断成员变化通知
|
|
||||||
const groupCode = arg.sceneId.split('_')[0];
|
|
||||||
if (this.apis.GroupApi.groupMemberCache.has(groupCode)) {
|
|
||||||
const existMembers = this.apis.GroupApi.groupMemberCache.get(groupCode)!;
|
|
||||||
arg.infos.forEach((member, uid) => {
|
|
||||||
//console.log('onMemberListChange', member);
|
|
||||||
const existMember = existMembers.get(uid);
|
|
||||||
if (existMember) {
|
|
||||||
Object.assign(existMember, member);
|
|
||||||
} else {
|
|
||||||
existMembers.set(uid, member);
|
|
||||||
}
|
|
||||||
//移除成员
|
|
||||||
if (member.isDelete) {
|
|
||||||
existMembers.delete(uid);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this.apis.GroupApi.groupMemberCache.set(groupCode, arg.infos);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
groupListener.onMemberInfoChange = (groupCode, dataSource, members) => {
|
|
||||||
if (dataSource === DataSource.LOCAL && members.get(this.selfInfo.uid)?.isDelete) {
|
|
||||||
// 自身退群或者被踢退群 5s用于Api操作 之后不再出现
|
|
||||||
setTimeout(() => {
|
|
||||||
this.apis.GroupApi.groupCache.delete(groupCode);
|
|
||||||
}, 5000);
|
|
||||||
|
|
||||||
}
|
|
||||||
const existMembers = this.apis.GroupApi.groupMemberCache.get(groupCode);
|
|
||||||
if (existMembers) {
|
|
||||||
members.forEach((member, uid) => {
|
|
||||||
const existMember = existMembers.get(uid);
|
|
||||||
if (existMember) {
|
|
||||||
// 检查管理变动
|
|
||||||
member.isChangeRole = this.checkAdminEvent(groupCode, member, existMember);
|
|
||||||
// 更新成员信息
|
|
||||||
Object.assign(existMember, member);
|
|
||||||
} else {
|
|
||||||
existMembers.set(uid, member);
|
|
||||||
}
|
|
||||||
//移除成员
|
|
||||||
if (member.isDelete) {
|
|
||||||
existMembers.delete(uid);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this.apis.GroupApi.groupMemberCache.set(groupCode, members);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
this.context.session.getGroupService().addKernelGroupListener(
|
|
||||||
proxiedListenerOf(groupListener, this.context.logger),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
checkAdminEvent(groupCode: string, memberNew: GroupMember, memberOld: GroupMember | undefined): boolean {
|
|
||||||
if (memberNew.role !== memberOld?.role) {
|
|
||||||
this.context.logger.logDebug(`群 ${groupCode} ${memberNew.nick} 角色变更为 ${memberNew.role === 3 ? '管理员' : '群员'}`);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -255,7 +255,7 @@ export class NodeIKernelMsgListener {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onMsgRecall(i2: unknown, str: unknown, j2: unknown): any {
|
onMsgRecall(chatType: ChatType, uid: string, msgSeq: string): any {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -14,6 +14,8 @@ import {
|
|||||||
GroupFileExtra
|
GroupFileExtra
|
||||||
} from "@/core/packet/transformer/proto";
|
} from "@/core/packet/transformer/proto";
|
||||||
import {
|
import {
|
||||||
|
BaseEmojiType,
|
||||||
|
FaceType,
|
||||||
NTMsgAtType,
|
NTMsgAtType,
|
||||||
PicType,
|
PicType,
|
||||||
SendArkElement,
|
SendArkElement,
|
||||||
@@ -162,7 +164,7 @@ export class PacketMsgFaceElement extends IPacketMsgElement<SendFaceElement> {
|
|||||||
constructor(element: SendFaceElement) {
|
constructor(element: SendFaceElement) {
|
||||||
super(element);
|
super(element);
|
||||||
this.faceId = element.faceElement.faceIndex;
|
this.faceId = element.faceElement.faceIndex;
|
||||||
this.isLargeFace = element.faceElement.faceType === 3;
|
this.isLargeFace = element.faceElement.faceType === FaceType.AniSticke;
|
||||||
}
|
}
|
||||||
|
|
||||||
buildElement(): NapProtoEncodeStructType<typeof Elem>[] {
|
buildElement(): NapProtoEncodeStructType<typeof Elem>[] {
|
||||||
|
18
src/core/packet/transformer/proto/message/groupAdmin.ts
Normal file
18
src/core/packet/transformer/proto/message/groupAdmin.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { ProtoField, ScalarType } from "@napneko/nap-proto-core";
|
||||||
|
|
||||||
|
export const GroupAdminExtra = {
|
||||||
|
adminUid: ProtoField(1, ScalarType.STRING),
|
||||||
|
isPromote: ProtoField(2, ScalarType.BOOL),
|
||||||
|
}
|
||||||
|
|
||||||
|
export const GroupAdminBody = {
|
||||||
|
extraDisable: ProtoField(1, () => GroupAdminExtra),
|
||||||
|
extraEnable: ProtoField(2, () => GroupAdminExtra),
|
||||||
|
}
|
||||||
|
|
||||||
|
export const GroupAdmin = {
|
||||||
|
groupUin: ProtoField(1, ScalarType.UINT32),
|
||||||
|
flag: ProtoField(2, ScalarType.UINT32),
|
||||||
|
isPromote: ProtoField(3, ScalarType.BOOL),
|
||||||
|
body: ProtoField(4, () => GroupAdminBody),
|
||||||
|
}
|
@@ -54,6 +54,16 @@ export const PushMsg = {
|
|||||||
generalFlag: ProtoField(9, ScalarType.INT32, true),
|
generalFlag: ProtoField(9, ScalarType.INT32, true),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const GroupChange = {
|
||||||
|
groupUin: ProtoField(1, ScalarType.UINT32),
|
||||||
|
flag: ProtoField(2, ScalarType.UINT32),
|
||||||
|
memberUid: ProtoField(3, ScalarType.STRING, true),
|
||||||
|
decreaseType: ProtoField(4, ScalarType.UINT32),
|
||||||
|
operatorUid: ProtoField(5, ScalarType.STRING, true),
|
||||||
|
increaseType: ProtoField(6, ScalarType.UINT32),
|
||||||
|
field7: ProtoField(7, ScalarType.BYTES, true),
|
||||||
|
};
|
||||||
|
|
||||||
export const PushMsgBody = {
|
export const PushMsgBody = {
|
||||||
responseHead: ProtoField(1, () => ResponseHead),
|
responseHead: ProtoField(1, () => ResponseHead),
|
||||||
contentHead: ProtoField(2, () => ContentHead),
|
contentHead: ProtoField(2, () => ContentHead),
|
||||||
|
17
src/core/services/NodeIKernelBaseEmojiService.ts
Normal file
17
src/core/services/NodeIKernelBaseEmojiService.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { DownloadBaseEmojiByIdReq, DownloadBaseEmojiByUrlReq, GetBaseEmojiPathReq, PullSysEmojisReq } from '../types';
|
||||||
|
|
||||||
|
export interface NodeIKernelBaseEmojiService {
|
||||||
|
removeKernelBaseEmojiListener(listenerId: number): void;
|
||||||
|
|
||||||
|
addKernelBaseEmojiListener(listener: unknown): number;
|
||||||
|
|
||||||
|
isBaseEmojiPathExist(args: Array<string>): unknown;
|
||||||
|
|
||||||
|
fetchFullSysEmojis(pullSysEmojisReq: PullSysEmojisReq): unknown;
|
||||||
|
|
||||||
|
getBaseEmojiPathByIds(getBaseEmojiPathReqs: Array<GetBaseEmojiPathReq>): unknown;
|
||||||
|
|
||||||
|
downloadBaseEmojiByIdWithUrl(downloadBaseEmojiByUrlReq: DownloadBaseEmojiByUrlReq): unknown;
|
||||||
|
|
||||||
|
downloadBaseEmojiById(downloadBaseEmojiByIdReq: DownloadBaseEmojiByIdReq): unknown;
|
||||||
|
}
|
@@ -1,6 +1,6 @@
|
|||||||
import { GeneralCallResult } from '@/core/services/common';
|
import { GeneralCallResult } from '@/core/services/common';
|
||||||
import { NodeIKernelBuddyListener } from '@/core/listeners';
|
import { NodeIKernelBuddyListener } from '@/core/listeners';
|
||||||
import { BuddyListReqType } from '../types/user';
|
import { BuddyListReqType } from '@/core/types/user';
|
||||||
|
|
||||||
export interface NodeIKernelBuddyService {
|
export interface NodeIKernelBuddyService {
|
||||||
getBuddyListV2(callFrom: string, reqType: BuddyListReqType): Promise<GeneralCallResult & {
|
getBuddyListV2(callFrom: string, reqType: BuddyListReqType): Promise<GeneralCallResult & {
|
||||||
|
@@ -163,7 +163,7 @@ export interface NodeIKernelGroupService {
|
|||||||
|
|
||||||
getGroupPortrait(): void;
|
getGroupPortrait(): void;
|
||||||
|
|
||||||
modifyGroupName(groupCode: string, groupName: string, arg: false): void;
|
modifyGroupName(groupCode: string, groupName: string, isNormalMember: boolean): Promise<GeneralCallResult>;
|
||||||
|
|
||||||
modifyGroupRemark(groupCode: string, remark: string): void;
|
modifyGroupRemark(groupCode: string, remark: string): void;
|
||||||
|
|
||||||
@@ -193,7 +193,7 @@ export interface NodeIKernelGroupService {
|
|||||||
|
|
||||||
getGroupNotifiesUnreadCount(unknown: boolean): Promise<GeneralCallResult>;
|
getGroupNotifiesUnreadCount(unknown: boolean): Promise<GeneralCallResult>;
|
||||||
|
|
||||||
clearGroupNotifiesUnreadCount(unknown: boolean): void;
|
clearGroupNotifiesUnreadCount(doubt: boolean): void;
|
||||||
|
|
||||||
operateSysNotify(
|
operateSysNotify(
|
||||||
doubt: boolean,
|
doubt: boolean,
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import { ElementType, MessageElement, Peer, RawMessage, SendMessageElement } from '@/core/types';
|
import { ElementType, MessageElement, Peer, RawMessage, SendMessageElement } from '@/core/types';
|
||||||
import { NodeIKernelMsgListener } from '@/core/listeners/NodeIKernelMsgListener';
|
import { NodeIKernelMsgListener } from '@/core/listeners/NodeIKernelMsgListener';
|
||||||
import { GeneralCallResult } from '@/core/services/common';
|
import { GeneralCallResult } from '@/core/services/common';
|
||||||
import { MsgReqType, QueryMsgsParams, TmpChatInfoApi } from '../types/msg';
|
import { MsgReqType, QueryMsgsParams, TmpChatInfoApi } from '@/core/types/msg';
|
||||||
|
|
||||||
export interface NodeIKernelMsgService {
|
export interface NodeIKernelMsgService {
|
||||||
|
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import { ChatType, Peer } from '@/core/types';
|
import { ChatType, Peer } from '@/core/types';
|
||||||
import { NodeIKernelRecentContactListener } from '../listeners/NodeIKernelRecentContactListener';
|
import { NodeIKernelRecentContactListener } from '@/core/listeners/NodeIKernelRecentContactListener';
|
||||||
import { GeneralCallResult } from './common';
|
import { GeneralCallResult } from '@/core/services/common';
|
||||||
import { FSABRecentContactParams } from '../types/contact';
|
import { FSABRecentContactParams } from '@/core/types/contact';
|
||||||
|
|
||||||
export interface NodeIKernelRecentContactService {
|
export interface NodeIKernelRecentContactService {
|
||||||
setGuildDisplayStatus(...args: unknown[]): unknown; // 2 arguments
|
setGuildDisplayStatus(...args: unknown[]): unknown; // 2 arguments
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { NodeIO3MiscListener } from "../listeners/NodeIO3MiscListener";
|
import { NodeIO3MiscListener } from "@/core/listeners/NodeIO3MiscListener";
|
||||||
|
|
||||||
export interface NodeIO3MiscService {
|
export interface NodeIO3MiscService {
|
||||||
get(): NodeIO3MiscService;
|
get(): NodeIO3MiscService;
|
||||||
|
@@ -1,11 +1,11 @@
|
|||||||
export enum MsfStatusType {
|
export enum MsfStatusType {
|
||||||
KUNKNOWN,
|
KUNKNOWN = 0,
|
||||||
KDISCONNECTED,
|
KDISCONNECTED = 1,
|
||||||
KCONNECTED
|
KCONNECTED = 2
|
||||||
}
|
}
|
||||||
export enum MsfChangeReasonType {
|
export enum MsfChangeReasonType {
|
||||||
KUNKNOWN,
|
KUNKNOWN = 0,
|
||||||
KUSERLOGININ,
|
KUSERLOGININ = 1,
|
||||||
KUSERLOGINOUT,
|
KUSERLOGINOUT = 2,
|
||||||
KAUTO
|
KAUTO = 3
|
||||||
}
|
}
|
@@ -1,4 +1,4 @@
|
|||||||
import { ElementType, FaceType, MessageElement, NTGrayTipElementSubTypeV2, PicSubType, PicType, TipAioOpGrayTipElement, TipGroupElement, NTVideoType } from "./msg";
|
import { ElementType, MessageElement, NTGrayTipElementSubTypeV2, PicSubType, PicType, TipAioOpGrayTipElement, TipGroupElement, NTVideoType, FaceType } from "./msg";
|
||||||
|
|
||||||
type ElementFullBase = Omit<MessageElement, 'elementType' | 'elementId' | 'extBufForUI'>;
|
type ElementFullBase = Omit<MessageElement, 'elementType' | 'elementId' | 'extBufForUI'>;
|
||||||
|
|
||||||
@@ -40,17 +40,18 @@ export interface FaceElement {
|
|||||||
surpriseId?: string;
|
surpriseId?: string;
|
||||||
randomType?: number;
|
randomType?: number;
|
||||||
}
|
}
|
||||||
|
export interface GrayTipRovokeElement {
|
||||||
|
operatorRole: string;
|
||||||
|
operatorUid: string;
|
||||||
|
operatorNick: string;
|
||||||
|
operatorRemark: string;
|
||||||
|
operatorMemRemark?: string;
|
||||||
|
wording: string; // 自定义的撤回提示语
|
||||||
|
}
|
||||||
|
|
||||||
export interface GrayTipElement {
|
export interface GrayTipElement {
|
||||||
subElementType: NTGrayTipElementSubTypeV2;
|
subElementType: NTGrayTipElementSubTypeV2;
|
||||||
revokeElement: {
|
revokeElement: GrayTipRovokeElement;
|
||||||
operatorRole: string;
|
|
||||||
operatorUid: string;
|
|
||||||
operatorNick: string;
|
|
||||||
operatorRemark: string;
|
|
||||||
operatorMemRemark?: string;
|
|
||||||
wording: string; // 自定义的撤回提示语
|
|
||||||
};
|
|
||||||
aioOpGrayTipElement: TipAioOpGrayTipElement;
|
aioOpGrayTipElement: TipAioOpGrayTipElement;
|
||||||
groupElement: TipGroupElement;
|
groupElement: TipGroupElement;
|
||||||
xmlElement: {
|
xmlElement: {
|
||||||
@@ -253,7 +254,7 @@ export interface FaceBubbleElement {
|
|||||||
faceFlag: number;
|
faceFlag: number;
|
||||||
content: string;
|
content: string;
|
||||||
oldVersionStr: string;
|
oldVersionStr: string;
|
||||||
faceType: number;
|
faceType: FaceType;
|
||||||
others: string;
|
others: string;
|
||||||
yellowFaceInfo: {
|
yellowFaceInfo: {
|
||||||
index: number;
|
index: number;
|
||||||
|
54
src/core/types/emoji.ts
Normal file
54
src/core/types/emoji.ts
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
export enum PullMomentType {
|
||||||
|
REINSTALL = 0,
|
||||||
|
RESTART_FIRST_AIO = 1,
|
||||||
|
LOGIN_APP = 2,
|
||||||
|
SINGEL_PULL_NOTIFY = 3,
|
||||||
|
TRIGGER_SPECIFIC_EMOJI_RANDOM_RESULT = 4
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PullSysEmojisReq {
|
||||||
|
fetchAdvaceSource: boolean;
|
||||||
|
fetchBaseSource: boolean;
|
||||||
|
pullMoment: PullMomentType;
|
||||||
|
pullType: number;
|
||||||
|
refresh: boolean;
|
||||||
|
thresholdValue: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum BaseEmojiType {
|
||||||
|
NORMAL_EMOJI = 0,
|
||||||
|
SUPER_EMOJI = 1,
|
||||||
|
RANDOM_SUPER_EMOJI = 2,
|
||||||
|
CHAIN_SUPER_EMOJI = 3,
|
||||||
|
EMOJI_EMOJI = 4
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GetBaseEmojiPathReq {
|
||||||
|
emojiId: string;
|
||||||
|
type: BaseEmojiType;
|
||||||
|
}
|
||||||
|
export enum EmojiPanelCategory {
|
||||||
|
OTHER_PANEL = 0,
|
||||||
|
NORMAL_PANEL = 1,
|
||||||
|
SUPER_PANEL = 2,
|
||||||
|
RED_HEART_PANEL = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DownloadBaseEmojiInfo {
|
||||||
|
baseResDownloadUrl: string;
|
||||||
|
advancedResDownloadUrl: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DownloadBaseEmojiByUrlReq {
|
||||||
|
emojiId: string;
|
||||||
|
groupName: string;
|
||||||
|
panelCategory: EmojiPanelCategory;
|
||||||
|
downloadInfo: DownloadBaseEmojiInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DownloadBaseEmojiByIdReq {
|
||||||
|
emojiId: string;
|
||||||
|
groupName: string;
|
||||||
|
panelCategory: EmojiPanelCategory;
|
||||||
|
qzoneCode: string;
|
||||||
|
}
|
74
src/core/types/graytip.ts
Normal file
74
src/core/types/graytip.ts
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
export enum JsonGrayBusiId {
|
||||||
|
AIO_AV_C2C_NOTICE = 2021,
|
||||||
|
AIO_AV_GROUP_NOTICE = 2022,
|
||||||
|
AIO_C2C_DONT_DISTURB = 2100,
|
||||||
|
AIO_CRM_FLAGS_TIPS = 2050,
|
||||||
|
AIO_GROUP_ESSENCE_MSG_TIP = 2401,
|
||||||
|
AIO_NUDGE_CUSTOM_GUIDE = 2041,
|
||||||
|
AIO_PUSH_GUIDE_GRAY_TIPS = 2701,
|
||||||
|
AIO_RECALL_MSGCUSTOM_WORDINGGUIDE = 2000,
|
||||||
|
AIO_ROBOT_SAFETY_TIP = 2201,
|
||||||
|
AIO_ZPLAN_EMOTICON_GUIDE = 2301,
|
||||||
|
AIO_ZPLAN_SCENE_LINKAGE = 2302,
|
||||||
|
AIO_ZPLAN_SEND_MEME = 2300,
|
||||||
|
DISBAND_DISCUSSION_GRAY_TIP_ID = 2603,
|
||||||
|
FILE_SENDING_SIZE_4GB_LIMIT = 3003,
|
||||||
|
GROUP_AIO_CONFIGURABLE_GRAY_TIPS = 2407,
|
||||||
|
GROUP_AIO_HOME_SCHOOL_WELCOME_GRAY_TIP_ID = 2404,
|
||||||
|
GROUP_AIO_MSG_FREQUENCY_GRAY_TIP_ID = 2406,
|
||||||
|
GROUP_AIO_SHUTUP_GRAY_TIP_ID = 2402,
|
||||||
|
GROUP_AIO_TEMPORARY_GRAY_TIP_ID = 2405,
|
||||||
|
GROUP_AIO_UNREAD_MSG_AI_SUMMARY = 2408,
|
||||||
|
GROUP_AIO_UPLOAD_PERMISSIONS_GRAY_TIP_ID = 2403,
|
||||||
|
LITE_ACTION = 86,
|
||||||
|
ONLINE_FILE_CANCEL_RECV_ON_RECVING = 4,
|
||||||
|
ONLINE_FILE_GO_OFFLINE = 11,
|
||||||
|
ONLINE_FILE_GO_OFFLINE_ALL = 12,
|
||||||
|
ONLINE_FILE_RECV_BY_MOBILE = 13,
|
||||||
|
ONLINE_FILE_RECV_ERROR = 10,
|
||||||
|
ONLINE_FILE_REFUSE_ALL_RECV = 7,
|
||||||
|
ONLINE_FILE_REFUSE_ALL_RECV_ON_RECVING = 8,
|
||||||
|
ONLINE_FILE_REFUSE_RECV = 3,
|
||||||
|
ONLINE_FILE_SEND_ERROR = 9,
|
||||||
|
ONLINE_FILE_STOP_ALL_SEND = 5,
|
||||||
|
ONLINE_FILE_STOP_ALL_SEND_ON_SENDING = 6,
|
||||||
|
ONLINE_FILE_STOP_SEND = 1,
|
||||||
|
ONLINE_FILE_STOP_SEND_ON_SENDING = 2,
|
||||||
|
ONLINE_GROUP_HOME_WORK = 51,
|
||||||
|
PTT_AUTO_CHANGE_GUIDE = 2060,
|
||||||
|
QCIRCLE_SHOW_FULE_TIPS = 2601,
|
||||||
|
QWALLET_GRAY_TIP_ID = 2602,
|
||||||
|
RED_BAG = 81,
|
||||||
|
RELATION_C2C_GROUP_AIO_SETUP_GROUP_AND_REMARK = 1005,
|
||||||
|
RELATION_C2C_LOVER_BONUS = 1003,
|
||||||
|
RELATION_C2C_MEMBER_ADD = 1017,
|
||||||
|
RELATION_C2C_REACTIVE_DEGRADE_MSG = 1019,
|
||||||
|
RELATION_C2C_REACTIVE_UPGRADE_MSG = 1018,
|
||||||
|
RELATION_C2C_SAY_HELLO = 1004,
|
||||||
|
RELATION_CHAIN_BLACKED = 1000,
|
||||||
|
RELATION_CHAIN_MATCH_FRIEND = 1007,
|
||||||
|
RELATION_CREATE_GROUP_GRAY_TIP_ID = 1009,
|
||||||
|
RELATION_EMOJIEGG_SHOW = 1001,
|
||||||
|
RELATION_EMOJIEGG_WILL_DEGRADE = 1002,
|
||||||
|
RELATION_FRIEND_CLONE_INFO = 1006,
|
||||||
|
RELATION_GROUP_BATCH_ADD_FRIEND = 1020,
|
||||||
|
RELATION_GROUP_MEMBER_ADD = 1022,
|
||||||
|
RELATION_GROUP_MEMBER_ADD_WITH_MODIFY_NAME = 1015,
|
||||||
|
RELATION_GROUP_MEMBER_ADD_WITH_WELCOME = 1016,
|
||||||
|
RELATION_GROUP_MEMBER_RECOMMEND = 1021,
|
||||||
|
RELATION_GROUP_SHUT_UP = 1014,
|
||||||
|
RELATION_LIMIT_TMP_CONVERSATION_SET = 1011,
|
||||||
|
RELATION_NEARBY_GOTO_VERIFY = 1008,
|
||||||
|
RELATION_ONEWAY_FRIEND_GRAY_TIP_ID = 1012,
|
||||||
|
RELATION_ONEWAY_FRIEND_NEW_GRAY_TIP_ID = 1013,
|
||||||
|
RELATION_YQT = 1010,
|
||||||
|
TROOP_ADD_FRIEND_ACTIVE = 19264,
|
||||||
|
TROOP_ADD_FRIEND_HOT_CHAT = 19265,
|
||||||
|
TROOP_ADD_FRIEND_NEW_MEMBER = 19267,
|
||||||
|
TROOP_ADD_FRIEND_REPLY_OR_AT = 19266,
|
||||||
|
TROOP_BREAK_ICE = 10405,
|
||||||
|
TROOP_FLAME_IGNITED = 19273,
|
||||||
|
UI_RESERVE_100000_110000 = 100000,
|
||||||
|
VAS_FILE_UPLOAD_OVER_1G = 3002,
|
||||||
|
VAS_FILE_UPLOAD_OVER_LIMIT = 3001,
|
||||||
|
}
|
@@ -63,16 +63,16 @@ export interface KickMemberV2Req {
|
|||||||
|
|
||||||
// 数据来源类型
|
// 数据来源类型
|
||||||
export enum DataSource {
|
export enum DataSource {
|
||||||
LOCAL,
|
LOCAL = 0,
|
||||||
REMOTE
|
REMOTE = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
// 群列表更新类型
|
// 群列表更新类型
|
||||||
export enum GroupListUpdateType {
|
export enum GroupListUpdateType {
|
||||||
REFRESHALL,
|
REFRESHALL = 0,
|
||||||
GETALL,
|
GETALL = 1,
|
||||||
MODIFIED,
|
MODIFIED = 2,
|
||||||
REMOVE
|
REMOVE = 3
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GroupMemberCache {
|
export interface GroupMemberCache {
|
||||||
|
@@ -7,4 +7,6 @@ export * from './system';
|
|||||||
export * from './webapi';
|
export * from './webapi';
|
||||||
export * from './sign';
|
export * from './sign';
|
||||||
export * from './element';
|
export * from './element';
|
||||||
export * from './constant';
|
export * from './constant';
|
||||||
|
export * from './graytip';
|
||||||
|
export * from './emoji';
|
@@ -1,6 +1,10 @@
|
|||||||
import { NTGroupMemberRole } from '@/core';
|
import { NTGroupMemberRole } from '@/core';
|
||||||
import { ActionBarElement, ArkElement, AvRecordElement, CalendarElement, FaceBubbleElement, FaceElement, FileElement, GiphyElement, GrayTipElement, MarketFaceElement, PicElement, PttElement, RecommendedMsgElement, ReplyElement, ShareLocationElement, StructLongMsgElement, TaskTopMsgElement, TextElement, TofuRecordElement, VideoElement, YoloGameResultElement } from './element';
|
import { ActionBarElement, ArkElement, AvRecordElement, CalendarElement, FaceBubbleElement, FaceElement, FileElement, GiphyElement, GrayTipElement, MarketFaceElement, PicElement, PttElement, RecommendedMsgElement, ReplyElement, ShareLocationElement, StructLongMsgElement, TaskTopMsgElement, TextElement, TofuRecordElement, VideoElement, YoloGameResultElement } from './element';
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 2024/11/22 Refactor Mlikiowa
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 表示对等方的信息
|
* 表示对等方的信息
|
||||||
*/
|
*/
|
||||||
@@ -127,7 +131,7 @@ export enum PicSubType {
|
|||||||
KRELATED = 7
|
KRELATED = 7
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* 消息@类型枚举
|
* 消息AT类型枚举
|
||||||
*/
|
*/
|
||||||
export enum NTMsgAtType {
|
export enum NTMsgAtType {
|
||||||
ATTYPEALL = 1,
|
ATTYPEALL = 1,
|
||||||
@@ -260,16 +264,6 @@ export enum NTGrayTipElementSubTypeV2 {
|
|||||||
GRAYTIP_ELEMENT_SUBTYPE_XMLMSG = 12,
|
GRAYTIP_ELEMENT_SUBTYPE_XMLMSG = 12,
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 表情类型枚举
|
|
||||||
*/
|
|
||||||
export enum FaceType {
|
|
||||||
normal = 1, // 小黄脸
|
|
||||||
normal2 = 2, // 新小黄脸
|
|
||||||
dice = 3, // 骰子
|
|
||||||
poke = 5 // 拍一拍
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Poke 类型枚举
|
* Poke 类型枚举
|
||||||
*/
|
*/
|
||||||
@@ -288,8 +282,8 @@ export enum PokeType {
|
|||||||
* 表情索引枚举
|
* 表情索引枚举
|
||||||
*/
|
*/
|
||||||
export enum FaceIndex {
|
export enum FaceIndex {
|
||||||
dice = 358,
|
DICE = 358,
|
||||||
rps = 359
|
RPS = 359
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -389,12 +383,39 @@ export enum MemberAddShowType {
|
|||||||
K_YOU_INVITE_OTHER = 7,
|
K_YOU_INVITE_OTHER = 7,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 群提示元素成员角色枚举
|
||||||
|
*/
|
||||||
|
export enum NTGroupGrayElementRole {
|
||||||
|
KOTHER = 0,
|
||||||
|
KMEMBER = 1,
|
||||||
|
KADMIN = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 群灰色提示成员接口
|
||||||
|
* */
|
||||||
|
|
||||||
|
export interface NTGroupGrayMember {
|
||||||
|
serialVersionUID: string;
|
||||||
|
uid: string;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 群灰色提示邀请者和被邀请者接口
|
||||||
|
*
|
||||||
|
* */
|
||||||
|
export interface NTGroupGrayInviterAndInvite {
|
||||||
|
invited: NTGroupGrayMember;
|
||||||
|
inviter: NTGroupGrayMember;
|
||||||
|
serialVersionUID: string;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* 群提示元素接口
|
* 群提示元素接口
|
||||||
*/
|
*/
|
||||||
export interface TipGroupElement {
|
export interface TipGroupElement {
|
||||||
type: TipGroupElementType;
|
type: TipGroupElementType;
|
||||||
role: 0;
|
role: NTGroupGrayElementRole;
|
||||||
groupName: string;
|
groupName: string;
|
||||||
memberUid: string;
|
memberUid: string;
|
||||||
memberNick: string;
|
memberNick: string;
|
||||||
@@ -405,13 +426,13 @@ export interface TipGroupElement {
|
|||||||
createGroup: null;
|
createGroup: null;
|
||||||
memberAdd?: {
|
memberAdd?: {
|
||||||
showType: MemberAddShowType;
|
showType: MemberAddShowType;
|
||||||
otherAdd: null;
|
otherAdd: NTGroupGrayMember;
|
||||||
otherAddByOtherQRCode: null;
|
otherAddByOtherQRCode: NTGroupGrayInviterAndInvite;
|
||||||
otherAddByYourQRCode: null;
|
otherAddByYourQRCode: NTGroupGrayMember;
|
||||||
youAddByOtherQRCode: null;
|
youAddByOtherQRCode: NTGroupGrayMember;
|
||||||
otherInviteOther: null;
|
otherInviteOther: NTGroupGrayInviterAndInvite;
|
||||||
otherInviteYou: null;
|
otherInviteYou: NTGroupGrayMember;
|
||||||
youInviteOther: null
|
youInviteOther: NTGroupGrayMember;
|
||||||
};
|
};
|
||||||
shutUp?: {
|
shutUp?: {
|
||||||
curTime: string;
|
curTime: string;
|
||||||
@@ -532,4 +553,16 @@ export interface MsgReqType {
|
|||||||
includeSelf: boolean,
|
includeSelf: boolean,
|
||||||
includeDeleteMsg: boolean,
|
includeDeleteMsg: boolean,
|
||||||
extraCnt: number
|
extraCnt: number
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 表情类型枚举
|
||||||
|
*/
|
||||||
|
export enum FaceType {
|
||||||
|
Unknown = 0,
|
||||||
|
OldFace = 1, // 老表情
|
||||||
|
Normal = 2, // 常规表情
|
||||||
|
AniSticke = 3, // 动画贴纸
|
||||||
|
Lottie = 4,// 新格式表情
|
||||||
|
Poke = 5 // 可变Poke
|
||||||
}
|
}
|
@@ -1,20 +1,20 @@
|
|||||||
export enum GroupNotifyMsgType {
|
export enum GroupNotifyMsgType {
|
||||||
UN_SPECIFIED,
|
UN_SPECIFIED = 0,
|
||||||
INVITED_BY_MEMBER,
|
INVITED_BY_MEMBER = 1,
|
||||||
REFUSE_INVITED,
|
REFUSE_INVITED = 2,
|
||||||
REFUSED_BY_ADMINI_STRATOR,
|
REFUSED_BY_ADMINI_STRATOR = 3,
|
||||||
AGREED_TOJOIN_DIRECT,// 有人接受了邀请入群
|
AGREED_TOJOIN_DIRECT = 4,// 有人接受了邀请入群
|
||||||
INVITED_NEED_ADMINI_STRATOR_PASS,
|
INVITED_NEED_ADMINI_STRATOR_PASS = 5,
|
||||||
AGREED_TO_JOIN_BY_ADMINI_STRATOR,
|
AGREED_TO_JOIN_BY_ADMINI_STRATOR = 6,
|
||||||
REQUEST_JOIN_NEED_ADMINI_STRATOR_PASS,
|
REQUEST_JOIN_NEED_ADMINI_STRATOR_PASS = 7,
|
||||||
SET_ADMIN,
|
SET_ADMIN = 8,
|
||||||
KICK_MEMBER_NOTIFY_ADMIN,
|
KICK_MEMBER_NOTIFY_ADMIN = 9,
|
||||||
KICK_MEMBER_NOTIFY_KICKED,
|
KICK_MEMBER_NOTIFY_KICKED = 10,
|
||||||
MEMBER_LEAVE_NOTIFY_ADMIN,// 主动退出
|
MEMBER_LEAVE_NOTIFY_ADMIN = 11,// 主动退出
|
||||||
CANCEL_ADMIN_NOTIFY_CANCELED,
|
CANCEL_ADMIN_NOTIFY_CANCELED = 12,
|
||||||
CANCEL_ADMIN_NOTIFY_ADMIN,// 其他人取消管理员
|
CANCEL_ADMIN_NOTIFY_ADMIN = 13,// 其他人取消管理员
|
||||||
TRANSFER_GROUP_NOTIFY_OLDOWNER,
|
TRANSFER_GROUP_NOTIFY_OLDOWNER = 14,
|
||||||
TRANSFER_GROUP_NOTIFY_ADMIN
|
TRANSFER_GROUP_NOTIFY_ADMIN = 15
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GroupNotifies {
|
export interface GroupNotifies {
|
||||||
@@ -24,24 +24,24 @@ export interface GroupNotifies {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export enum GroupNotifyMsgStatus {
|
export enum GroupNotifyMsgStatus {
|
||||||
KINIT,//初始化
|
KINIT = 0,//初始化
|
||||||
KUNHANDLE,//未处理
|
KUNHANDLE = 1,//未处理
|
||||||
KAGREED,//同意
|
KAGREED = 2,//同意
|
||||||
KREFUSED,//拒绝
|
KREFUSED = 3,//拒绝
|
||||||
KIGNORED//忽略
|
KIGNORED = 4//忽略
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum GroupInviteStatus {
|
export enum GroupInviteStatus {
|
||||||
INIT,
|
INIT = 0,
|
||||||
WAIT_TO_APPROVE,
|
WAIT_TO_APPROVE = 1,
|
||||||
JOINED,
|
JOINED = 2,
|
||||||
REFUSED_BY_ADMINI_STRATOR
|
REFUSED_BY_ADMINI_STRATOR = 3
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum GroupInviteType {
|
export enum GroupInviteType {
|
||||||
BYBUDDY,
|
BYBUDDY = 0,
|
||||||
BYGROUPMEMBER,
|
BYGROUPMEMBER = 1,
|
||||||
BYDISCUSSMEMBER
|
BYDISCUSSMEMBER = 2
|
||||||
}
|
}
|
||||||
export interface ShutUpGroupHonor {
|
export interface ShutUpGroupHonor {
|
||||||
[key: string]: number;
|
[key: string]: number;
|
||||||
@@ -116,20 +116,20 @@ export enum NTGroupRequestOperateTypes {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export enum BuddyReqType {
|
export enum BuddyReqType {
|
||||||
KMEINITIATOR,
|
KMEINITIATOR = 0,
|
||||||
KPEERINITIATOR,
|
KPEERINITIATOR = 1,
|
||||||
KMEAGREED,
|
KMEAGREED = 2,
|
||||||
KMEAGREEDANDADDED,
|
KMEAGREEDANDADDED = 3,
|
||||||
KPEERAGREED,
|
KPEERAGREED = 4,
|
||||||
KPEERAGREEDANDADDED,
|
KPEERAGREEDANDADDED = 5,
|
||||||
KPEERREFUSED,
|
KPEERREFUSED = 6,
|
||||||
KMEREFUSED,
|
KMEREFUSED = 7,
|
||||||
KMEIGNORED,
|
KMEIGNORED = 8,
|
||||||
KMEAGREEANYONE,
|
KMEAGREEANYONE = 9,
|
||||||
KMESETQUESTION,
|
KMESETQUESTION = 10,
|
||||||
KMEAGREEANDADDFAILED,
|
KMEAGREEANDADDFAILED = 11,
|
||||||
KMSGINFO,
|
KMSGINFO = 12,
|
||||||
KMEINITIATORWAITPEERCONFIRM
|
KMEINITIATORWAITPEERCONFIRM = 13
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FriendRequest {
|
export interface FriendRequest {
|
||||||
|
@@ -322,8 +322,8 @@ export type Friend = User;
|
|||||||
|
|
||||||
// 业务键枚举
|
// 业务键枚举
|
||||||
export enum BizKey {
|
export enum BizKey {
|
||||||
KPRIVILEGEICON,
|
KPRIVILEGEICON = 0,
|
||||||
KPHOTOWALL
|
KPHOTOWALL = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
// 根据UIN获取用户详细信息
|
// 根据UIN获取用户详细信息
|
||||||
@@ -347,9 +347,9 @@ export enum UserDetailSource {
|
|||||||
|
|
||||||
// 个人资料业务类型枚举
|
// 个人资料业务类型枚举
|
||||||
export enum ProfileBizType {
|
export enum ProfileBizType {
|
||||||
KALL,
|
KALL = 0,
|
||||||
KBASEEXTEND,
|
KBASEEXTEND = 1,
|
||||||
KVAS,
|
KVAS = 2,
|
||||||
KQZONE,
|
KQZONE = 3,
|
||||||
KOTHER
|
KOTHER = 4
|
||||||
}
|
}
|
@@ -1,6 +1,16 @@
|
|||||||
//LiteLoader需要提供部分IPC接口,以便于其他插件调用
|
//LiteLoader需要提供部分IPC接口,以便于其他插件调用
|
||||||
const { ipcMain } = require('electron');
|
const { ipcMain } = require('electron');
|
||||||
const napcat = require('./napcat.cjs');
|
const napcat = require('./napcat.cjs');
|
||||||
|
const { shell } = require('electron');
|
||||||
ipcMain.handle('napcat_get_webtoken', async (event, arg) => {
|
ipcMain.handle('napcat_get_webtoken', async (event, arg) => {
|
||||||
return napcat.NCgetWebUiUrl();
|
return napcat.NCgetWebUiUrl();
|
||||||
});
|
});
|
||||||
|
ipcMain.on('open_external_url', (event, url) => {
|
||||||
|
shell.openExternal(url);
|
||||||
|
});
|
||||||
|
ipcMain.handle('napcat_get_reactweb', async (event, arg) => {
|
||||||
|
let url = new URL(await napcat.NCgetWebUiUrl());
|
||||||
|
let port = url.port;
|
||||||
|
let token = url.searchParams.get('token');
|
||||||
|
return `https://napcat.152710.xyz/web_login?back=http://127.0.0.1:${port}&token=${token}`;
|
||||||
|
});
|
@@ -58,7 +58,7 @@ export async function NCoreInitFramework(
|
|||||||
await loaderObject.core.initCore();
|
await loaderObject.core.initCore();
|
||||||
|
|
||||||
//启动WebUi
|
//启动WebUi
|
||||||
InitWebUi(logger, pathWrapper).then().catch(logger.logError.bind(logger));
|
InitWebUi(logger, pathWrapper).then().catch(e => logger.logError(e));
|
||||||
//初始化LLNC的Onebot实现
|
//初始化LLNC的Onebot实现
|
||||||
await new NapCatOneBot11Adapter(loaderObject.core, loaderObject.context, pathWrapper).InitOneBot();
|
await new NapCatOneBot11Adapter(loaderObject.core, loaderObject.context, pathWrapper).InitOneBot();
|
||||||
}
|
}
|
||||||
|
@@ -1,10 +1,14 @@
|
|||||||
const { contextBridge } = require('electron');
|
const { contextBridge, ipcRenderer } = require('electron');
|
||||||
const { ipcRenderer } = require('electron');
|
|
||||||
|
|
||||||
const napcat = {
|
const napcat = {
|
||||||
getWebUiUrl: async () => {
|
getWebUiUrl: async () => {
|
||||||
return ipcRenderer.invoke('napcat_get_webtoken');
|
return ipcRenderer.invoke('napcat_get_webtoken');
|
||||||
},
|
},
|
||||||
|
openExternalUrl: async (url) => {
|
||||||
|
ipcRenderer.send('open_external_url', url);
|
||||||
|
},
|
||||||
|
getWebUiUrlReact: async () => {
|
||||||
|
return ipcRenderer.invoke('napcat_get_reactweb');
|
||||||
|
}
|
||||||
};
|
};
|
||||||
// 在window对象下导出只读对象
|
// 在window对象下导出只读对象
|
||||||
contextBridge.exposeInMainWorld('napcat', napcat);
|
contextBridge.exposeInMainWorld('napcat', napcat);
|
@@ -1,27 +1,20 @@
|
|||||||
export const onSettingWindowCreated = async (view) => {
|
export const onSettingWindowCreated = async (view) => {
|
||||||
|
|
||||||
// view.style.width = "100%";
|
|
||||||
// view.style.height = "100%";
|
|
||||||
// //添加iframe
|
|
||||||
// const iframe = document.createElement("iframe");
|
|
||||||
// iframe.src = await window.napcat.getWebUiUrl();
|
|
||||||
// iframe.width = "100%";
|
|
||||||
// iframe.height = "100%";
|
|
||||||
// iframe.style.border = "none";
|
|
||||||
// //去掉iframe滚动条
|
|
||||||
// //iframe.scrolling = "no";
|
|
||||||
// //有滚动条何尝不是一种美
|
|
||||||
// view.appendChild(iframe);
|
|
||||||
let webui = await window.napcat.getWebUiUrl();
|
let webui = await window.napcat.getWebUiUrl();
|
||||||
|
let webuiReact = await window.napcat.getWebUiUrlReact();
|
||||||
view.innerHTML = `
|
view.innerHTML = `
|
||||||
<setting-section data-title="">
|
<setting-section data-title="">
|
||||||
<setting-panel>
|
<setting-panel>
|
||||||
<setting-list data-direction="column">
|
<setting-list data-direction="column">
|
||||||
<setting-item>
|
<setting-item>
|
||||||
<setting-button data-type="primary" class="nc_openwebui">打开配置页面</setting-button>
|
<setting-button data-type="primary" class="nc_openwebui">在QQ内打开配置页面(VUE)</setting-button>
|
||||||
|
<setting-button data-type="primary" class="nc_openwebui_ex">在默认浏览器打开配置页面(VUE)</setting-button>
|
||||||
|
</setting-item>
|
||||||
|
<setting-item>
|
||||||
|
<setting-button data-type="primary" class="nc_openwebui_ex_react">在默认浏览器打开配置页面(React)</setting-button>
|
||||||
</setting-item>
|
</setting-item>
|
||||||
<setting-item>
|
<setting-item>
|
||||||
<div>
|
<div>
|
||||||
|
<setting-text>WebUi远程地址可以点击下方复制哦~</setting-text>
|
||||||
<setting-text class="nc_webui">WebUi</setting-text>
|
<setting-text class="nc_webui">WebUi</setting-text>
|
||||||
</div>
|
</div>
|
||||||
</setting-item>
|
</setting-item>
|
||||||
@@ -29,8 +22,27 @@ export const onSettingWindowCreated = async (view) => {
|
|||||||
</setting-panel>
|
</setting-panel>
|
||||||
</setting-section>
|
</setting-section>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
view.querySelector('.nc_openwebui').addEventListener('click', () => {
|
view.querySelector('.nc_openwebui').addEventListener('click', () => {
|
||||||
window.open(webui, '_blank');
|
window.open(webui, '_blank');
|
||||||
});
|
});
|
||||||
|
view.querySelector('.nc_openwebui_ex').addEventListener('click', () => {
|
||||||
|
window.napcat.openExternalUrl(webui);
|
||||||
|
});
|
||||||
|
|
||||||
|
view.querySelector('.nc_openwebui_ex_react').addEventListener('click', () => {
|
||||||
|
window.napcat.openExternalUrl(webuiReact);
|
||||||
|
});
|
||||||
|
|
||||||
view.querySelector('.nc_webui').innerText = webui;
|
view.querySelector('.nc_webui').innerText = webui;
|
||||||
};
|
|
||||||
|
// 添加点击复制功能
|
||||||
|
view.querySelector('.nc_webui').addEventListener('click', async () => {
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(webui);
|
||||||
|
alert('WebUi URL 已复制到剪贴板');
|
||||||
|
} catch (err) {
|
||||||
|
console.error('复制到剪贴板失败: ', err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
@@ -1,7 +1,6 @@
|
|||||||
import { ActionName, BaseCheckResult } from './router';
|
import { ActionName, BaseCheckResult } from './router';
|
||||||
import Ajv, { ErrorObject, ValidateFunction } from 'ajv';
|
import Ajv, { ErrorObject, ValidateFunction } from 'ajv';
|
||||||
import { NapCatCore } from '@/core';
|
import { NapCatCore } from '@/core';
|
||||||
import { isNull } from '@/common/helper';
|
|
||||||
import { NapCatOneBot11Adapter, OB11Return } from '@/onebot';
|
import { NapCatOneBot11Adapter, OB11Return } from '@/onebot';
|
||||||
|
|
||||||
export class OB11Response {
|
export class OB11Response {
|
||||||
@@ -43,7 +42,7 @@ export abstract class OneBotAction<PayloadType, ReturnDataType> {
|
|||||||
|
|
||||||
protected async check(payload: PayloadType): Promise<BaseCheckResult> {
|
protected async check(payload: PayloadType): Promise<BaseCheckResult> {
|
||||||
if (this.payloadSchema) {
|
if (this.payloadSchema) {
|
||||||
this.validate = new Ajv({ allowUnionTypes: true }).compile(this.payloadSchema);
|
this.validate = new Ajv({ allowUnionTypes: true, useDefaults: true }).compile(this.payloadSchema);
|
||||||
}
|
}
|
||||||
if (this.validate && !this.validate(payload)) {
|
if (this.validate && !this.validate(payload)) {
|
||||||
const errors = this.validate.errors as ErrorObject[];
|
const errors = this.validate.errors as ErrorObject[];
|
||||||
@@ -66,7 +65,7 @@ export abstract class OneBotAction<PayloadType, ReturnDataType> {
|
|||||||
return OB11Response.ok(resData);
|
return OB11Response.ok(resData);
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
this.core.context.logger.logError('发生错误', e);
|
this.core.context.logger.logError('发生错误', e);
|
||||||
return OB11Response.error(e?.toString() || e?.stack?.toString() || '未知错误,可能操作超时', 200);
|
return OB11Response.error((e as Error).message.toString() || e?.stack?.toString() || '未知错误,可能操作超时', 200);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,7 +79,7 @@ export abstract class OneBotAction<PayloadType, ReturnDataType> {
|
|||||||
return OB11Response.ok(resData, echo);
|
return OB11Response.ok(resData, echo);
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
this.core.context.logger.logError('发生错误', e);
|
this.core.context.logger.logError('发生错误', e);
|
||||||
return OB11Response.error(e.toString() || e.stack?.toString(), 1200, echo);
|
return OB11Response.error((e as Error).message.toString() || e.stack?.toString(), 1200, echo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,17 +1,13 @@
|
|||||||
import { OneBotAction } from '@/onebot/action/OneBotAction';
|
import { OneBotAction } from '@/onebot/action/OneBotAction';
|
||||||
import { ActionName } from '@/onebot/action/router';
|
import { ActionName } from '@/onebot/action/router';
|
||||||
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
|
import { Type, Static } from '@sinclair/typebox';
|
||||||
|
|
||||||
const SchemaData = {
|
const SchemaData = Type.Object({
|
||||||
type: 'object',
|
rawData: Type.String(),
|
||||||
properties: {
|
brief: Type.String(),
|
||||||
rawData: { type: 'string' },
|
});
|
||||||
brief: { type: 'string' },
|
|
||||||
},
|
|
||||||
required: ['brief', 'rawData'],
|
|
||||||
} as const satisfies JSONSchema;
|
|
||||||
|
|
||||||
type Payload = FromSchema<typeof SchemaData>;
|
type Payload = Static<typeof SchemaData>;
|
||||||
|
|
||||||
export class CreateCollection extends OneBotAction<Payload, any> {
|
export class CreateCollection extends OneBotAction<Payload, any> {
|
||||||
actionName = ActionName.CreateCollection;
|
actionName = ActionName.CreateCollection;
|
||||||
@@ -25,4 +21,4 @@ export class CreateCollection extends OneBotAction<Payload, any> {
|
|||||||
payload.brief, payload.rawData,
|
payload.brief, payload.rawData,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,23 +1,19 @@
|
|||||||
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
|
import { Type, Static } from '@sinclair/typebox';
|
||||||
import { OneBotAction } from '@/onebot/action/OneBotAction';
|
import { OneBotAction } from '@/onebot/action/OneBotAction';
|
||||||
import { ActionName } from '@/onebot/action/router';
|
import { ActionName } from '@/onebot/action/router';
|
||||||
|
|
||||||
const SchemaData = {
|
const SchemaData = Type.Object({
|
||||||
type: 'object',
|
count: Type.Union([Type.Number(), Type.String()], { default: 48 }),
|
||||||
properties: {
|
});
|
||||||
count: { type: ['number', 'string'] },
|
|
||||||
},
|
|
||||||
} as const satisfies JSONSchema;
|
|
||||||
|
|
||||||
type Payload = FromSchema<typeof SchemaData>;
|
type Payload = Static<typeof SchemaData>;
|
||||||
|
|
||||||
export class FetchCustomFace extends OneBotAction<Payload, string[]> {
|
export class FetchCustomFace extends OneBotAction<Payload, string[]> {
|
||||||
actionName = ActionName.FetchCustomFace;
|
actionName = ActionName.FetchCustomFace;
|
||||||
payloadSchema = SchemaData;
|
payloadSchema = SchemaData;
|
||||||
|
|
||||||
async _handle(payload: Payload) {
|
async _handle(payload: Payload) {
|
||||||
//48 可能正好是QQ需要的一个页面的数量 Tagged Mlikiowa
|
const ret = await this.core.apis.MsgApi.fetchFavEmojiList(+payload.count);
|
||||||
const ret = await this.core.apis.MsgApi.fetchFavEmojiList(+(payload.count ?? 48));
|
|
||||||
return ret.emojiInfoList.map(e => e.url);
|
return ret.emojiInfoList.map(e => e.url);
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,32 +1,27 @@
|
|||||||
//getMsgEmojiLikesList
|
import { Type, Static } from '@sinclair/typebox';
|
||||||
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
|
|
||||||
import { OneBotAction } from '@/onebot/action/OneBotAction';
|
import { OneBotAction } from '@/onebot/action/OneBotAction';
|
||||||
import { ActionName } from '@/onebot/action/router';
|
import { ActionName } from '@/onebot/action/router';
|
||||||
import { MessageUnique } from '@/common/message-unique';
|
import { MessageUnique } from '@/common/message-unique';
|
||||||
|
|
||||||
const SchemaData = {
|
const SchemaData = Type.Object({
|
||||||
type: 'object',
|
message_id: Type.Union([Type.Number(), Type.String()]),
|
||||||
properties: {
|
emojiId: Type.Union([Type.Number(), Type.String()]),
|
||||||
user_id: { type: 'string' },
|
emojiType: Type.Union([Type.Number(), Type.String()]),
|
||||||
group_id: { type: 'string' },
|
count: Type.Union([Type.Number(), Type.String()], { default: 20 }),
|
||||||
emojiId: { type: 'string' },
|
});
|
||||||
emojiType: { type: 'string' },
|
|
||||||
message_id: { type: ['string', 'number'] },
|
|
||||||
count: { type: ['string', 'number'] },
|
|
||||||
},
|
|
||||||
required: ['emojiId', 'emojiType', 'message_id'],
|
|
||||||
} as const satisfies JSONSchema;
|
|
||||||
|
|
||||||
type Payload = FromSchema<typeof SchemaData>;
|
type Payload = Static<typeof SchemaData>;
|
||||||
|
|
||||||
export class FetchEmojiLike extends OneBotAction<Payload, any> {
|
export class FetchEmojiLike extends OneBotAction<Payload, any> {
|
||||||
actionName = ActionName.FetchEmojiLike;
|
actionName = ActionName.FetchEmojiLike;
|
||||||
payloadSchema = SchemaData;
|
payloadSchema = SchemaData;
|
||||||
|
|
||||||
async _handle(payload: Payload) {
|
async _handle(payload: Payload) {
|
||||||
const msgIdPeer = MessageUnique.getMsgIdAndPeerByShortId(parseInt(payload.message_id.toString()));
|
const msgIdPeer = MessageUnique.getMsgIdAndPeerByShortId(+payload.message_id);
|
||||||
if (!msgIdPeer) throw new Error('消息不存在');
|
if (!msgIdPeer) throw new Error('消息不存在');
|
||||||
const msg = (await this.core.apis.MsgApi.getMsgsByMsgId(msgIdPeer.Peer, [msgIdPeer.MsgId])).msgList[0];
|
const msg = (await this.core.apis.MsgApi.getMsgsByMsgId(msgIdPeer.Peer, [msgIdPeer.MsgId])).msgList[0];
|
||||||
return await this.core.apis.MsgApi.getMsgEmojiLikesList(msgIdPeer.Peer, msg.msgSeq, payload.emojiId, payload.emojiType, +(payload.count ?? 20));
|
return await this.core.apis.MsgApi.getMsgEmojiLikesList(
|
||||||
|
msgIdPeer.Peer, msg.msgSeq, payload.emojiId.toString(), payload.emojiType.toString(), +payload.count
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,11 +1,17 @@
|
|||||||
import { OneBotAction } from '@/onebot/action/OneBotAction';
|
import { OneBotAction } from '@/onebot/action/OneBotAction';
|
||||||
import { ActionName } from '@/onebot/action/router';
|
import { ActionName } from '@/onebot/action/router';
|
||||||
|
import { Type, Static } from '@sinclair/typebox';
|
||||||
|
|
||||||
export class FetchUserProfileLike extends OneBotAction<{ qq: number }, any> {
|
const SchemaData = Type.Object({
|
||||||
|
user_id: Type.Union([Type.Number(), Type.String()]),
|
||||||
|
});
|
||||||
|
|
||||||
|
type Payload = Static<typeof SchemaData>;
|
||||||
|
|
||||||
|
export class FetchUserProfileLike extends OneBotAction<Payload, any> {
|
||||||
actionName = ActionName.FetchUserProfileLike;
|
actionName = ActionName.FetchUserProfileLike;
|
||||||
|
|
||||||
async _handle(payload: { qq: number }) {
|
async _handle(payload: Payload) {
|
||||||
if (!payload.qq) throw new Error('qq is required');
|
return await this.core.apis.UserApi.getUidByUinV2(payload.user_id.toString());
|
||||||
return await this.core.apis.UserApi.getUidByUinV2(payload.qq.toString());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,18 +1,14 @@
|
|||||||
import { ActionName } from '@/onebot/action/router';
|
import { ActionName } from '@/onebot/action/router';
|
||||||
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
|
|
||||||
import { GetPacketStatusDepends } from "@/onebot/action/packet/GetPacketStatus";
|
import { GetPacketStatusDepends } from "@/onebot/action/packet/GetPacketStatus";
|
||||||
import { AIVoiceChatType } from "@/core/packet/entities/aiChat";
|
import { AIVoiceChatType } from "@/core/packet/entities/aiChat";
|
||||||
|
import { Type, Static } from '@sinclair/typebox';
|
||||||
|
|
||||||
const SchemaData = {
|
const SchemaData = Type.Object({
|
||||||
type: 'object',
|
group_id: Type.Union([Type.Number(), Type.String()]),
|
||||||
properties: {
|
chat_type: Type.Union([Type.Union([Type.Number(), Type.String()])], { default: 1 }),
|
||||||
group_id: { type: ['number', 'string'] },
|
});
|
||||||
chat_type: { type: ['number', 'string'] },
|
|
||||||
},
|
|
||||||
required: ['group_id'],
|
|
||||||
} as const satisfies JSONSchema;
|
|
||||||
|
|
||||||
type Payload = FromSchema<typeof SchemaData>;
|
type Payload = Static<typeof SchemaData>;
|
||||||
|
|
||||||
interface GetAiCharactersResponse {
|
interface GetAiCharactersResponse {
|
||||||
type: string;
|
type: string;
|
||||||
@@ -28,7 +24,7 @@ export class GetAiCharacters extends GetPacketStatusDepends<Payload, GetAiCharac
|
|||||||
payloadSchema = SchemaData;
|
payloadSchema = SchemaData;
|
||||||
|
|
||||||
async _handle(payload: Payload) {
|
async _handle(payload: Payload) {
|
||||||
const rawList = await this.core.apis.PacketApi.pkt.operation.FetchAiVoiceList(+payload.group_id, +(payload.chat_type ?? 1) as AIVoiceChatType);
|
const rawList = await this.core.apis.PacketApi.pkt.operation.FetchAiVoiceList(+payload.group_id, +payload.chat_type as AIVoiceChatType);
|
||||||
return rawList?.map((item) => ({
|
return rawList?.map((item) => ({
|
||||||
type: item.category,
|
type: item.category,
|
||||||
characters: item.voices.map((voice) => ({
|
characters: item.voices.map((voice) => ({
|
||||||
|
@@ -1,23 +1,19 @@
|
|||||||
import { OneBotAction } from '@/onebot/action/OneBotAction';
|
import { OneBotAction } from '@/onebot/action/OneBotAction';
|
||||||
import { ActionName } from '@/onebot/action/router';
|
import { ActionName } from '@/onebot/action/router';
|
||||||
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
|
import { Type, Static } from '@sinclair/typebox';
|
||||||
|
|
||||||
const SchemaData = {
|
const SchemaData = Type.Object({
|
||||||
type: 'object',
|
category: Type.Union([Type.Number(), Type.String()]),
|
||||||
properties: {
|
count: Type.Union([Type.Union([Type.Number(), Type.String()])], { default: 1 }),
|
||||||
category: { type: ['number', 'string'] },
|
});
|
||||||
count: { type: ['number', 'string'] },
|
|
||||||
},
|
|
||||||
required: ['category', 'count'],
|
|
||||||
} as const satisfies JSONSchema;
|
|
||||||
|
|
||||||
type Payload = FromSchema<typeof SchemaData>;
|
type Payload = Static<typeof SchemaData>;
|
||||||
|
|
||||||
export class GetCollectionList extends OneBotAction<Payload, any> {
|
export class GetCollectionList extends OneBotAction<Payload, any> {
|
||||||
actionName = ActionName.GetCollectionList;
|
actionName = ActionName.GetCollectionList;
|
||||||
payloadSchema = SchemaData;
|
payloadSchema = SchemaData;
|
||||||
|
|
||||||
async _handle(payload: Payload) {
|
async _handle(payload: Payload) {
|
||||||
return await this.core.apis.CollectionApi.getAllCollection(parseInt(payload.category.toString()), +(payload.count ?? 1));
|
return await this.core.apis.CollectionApi.getAllCollection(+payload.category, +payload.count);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -18,7 +18,7 @@ export default class GetGroupAddRequest extends OneBotAction<null, OB11GroupRequ
|
|||||||
ignoredNotifies
|
ignoredNotifies
|
||||||
.filter(notify => notify.type === 7)
|
.filter(notify => notify.type === 7)
|
||||||
.map(async SSNotify => ({
|
.map(async SSNotify => ({
|
||||||
request_id: SSNotify.seq,
|
request_id: SSNotify.group.groupCode + '|' + SSNotify.seq + '|' + SSNotify.type,
|
||||||
requester_uin: await this.core.apis.UserApi.getUinByUidV2(SSNotify.user1?.uid),
|
requester_uin: await this.core.apis.UserApi.getUinByUidV2(SSNotify.user1?.uid),
|
||||||
requester_nick: SSNotify.user1?.nickName,
|
requester_nick: SSNotify.user1?.nickName,
|
||||||
group_id: SSNotify.group?.groupCode,
|
group_id: SSNotify.group?.groupCode,
|
||||||
|
@@ -1,16 +1,11 @@
|
|||||||
import { OneBotAction } from '@/onebot/action/OneBotAction';
|
import { OneBotAction } from '@/onebot/action/OneBotAction';
|
||||||
import { ActionName } from '@/onebot/action/router';
|
import { ActionName } from '@/onebot/action/router';
|
||||||
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
|
import { Type, Static } from '@sinclair/typebox';
|
||||||
|
const SchemaData = Type.Object({
|
||||||
|
group_id: Type.Union([Type.Number(), Type.String()]),
|
||||||
|
});
|
||||||
|
|
||||||
const SchemaData = {
|
type Payload = Static<typeof SchemaData>;
|
||||||
type: 'object',
|
|
||||||
properties: {
|
|
||||||
group_id: { type: ['number', 'string'] },
|
|
||||||
},
|
|
||||||
required: ['group_id'],
|
|
||||||
} as const satisfies JSONSchema;
|
|
||||||
|
|
||||||
type Payload = FromSchema<typeof SchemaData>;
|
|
||||||
|
|
||||||
export class GetGroupInfoEx extends OneBotAction<Payload, any> {
|
export class GetGroupInfoEx extends OneBotAction<Payload, any> {
|
||||||
actionName = ActionName.GetGroupInfoEx;
|
actionName = ActionName.GetGroupInfoEx;
|
||||||
|
@@ -1,47 +1,37 @@
|
|||||||
import { ActionName } from '@/onebot/action/router';
|
import { ActionName } from '@/onebot/action/router';
|
||||||
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
|
|
||||||
import { GetPacketStatusDepends } from "@/onebot/action/packet/GetPacketStatus";
|
import { GetPacketStatusDepends } from "@/onebot/action/packet/GetPacketStatus";
|
||||||
import { MiniAppInfo, MiniAppInfoHelper } from "@/core/packet/utils/helper/miniAppHelper";
|
import { MiniAppInfo, MiniAppInfoHelper } from "@/core/packet/utils/helper/miniAppHelper";
|
||||||
import { MiniAppData, MiniAppRawData, MiniAppReqCustomParams, MiniAppReqParams } from "@/core/packet/entities/miniApp";
|
import { MiniAppData, MiniAppRawData, MiniAppReqCustomParams, MiniAppReqParams } from "@/core/packet/entities/miniApp";
|
||||||
|
import { Static, Type } from '@sinclair/typebox';
|
||||||
|
|
||||||
const SchemaData = {
|
const SchemaData = Type.Union([
|
||||||
type: 'object',
|
Type.Object({
|
||||||
properties: {
|
type: Type.Union([Type.Literal('bili'), Type.Literal('weibo')]),
|
||||||
type: {
|
title: Type.String(),
|
||||||
type: 'string',
|
desc: Type.String(),
|
||||||
enum: ['bili', 'weibo']
|
picUrl: Type.String(),
|
||||||
},
|
jumpUrl: Type.String(),
|
||||||
title: { type: 'string' },
|
rawArkData: Type.Optional(Type.Union([Type.Boolean(), Type.String()]))
|
||||||
desc: { type: 'string' },
|
}),
|
||||||
picUrl: { type: 'string' },
|
Type.Object({
|
||||||
jumpUrl: { type: 'string' },
|
title: Type.String(),
|
||||||
iconUrl: { type: 'string' },
|
desc: Type.String(),
|
||||||
sdkId: { type: 'string' },
|
picUrl: Type.String(),
|
||||||
appId: { type: 'string' },
|
jumpUrl: Type.String(),
|
||||||
scene: { type: ['number', 'string'] },
|
iconUrl: Type.String(),
|
||||||
templateType: { type: ['number', 'string'] },
|
appId: Type.String(),
|
||||||
businessType: { type: ['number', 'string'] },
|
scene: Type.Union([Type.Number(), Type.String()]),
|
||||||
verType: { type: ['number', 'string'] },
|
templateType: Type.Union([Type.Number(), Type.String()]),
|
||||||
shareType: { type: ['number', 'string'] },
|
businessType: Type.Union([Type.Number(), Type.String()]),
|
||||||
versionId: { type: 'string' },
|
verType: Type.Union([Type.Number(), Type.String()]),
|
||||||
withShareTicket: { type: ['number', 'string'] },
|
shareType: Type.Union([Type.Number(), Type.String()]),
|
||||||
rawArkData: { type: ['boolean', 'string'] }
|
versionId: Type.String(),
|
||||||
},
|
sdkId: Type.String(),
|
||||||
oneOf: [
|
withShareTicket: Type.Union([Type.Number(), Type.String()]),
|
||||||
{
|
rawArkData: Type.Optional(Type.Union([Type.Boolean(), Type.String()]))
|
||||||
required: ['type', 'title', 'desc', 'picUrl', 'jumpUrl']
|
})
|
||||||
},
|
]);
|
||||||
{
|
type Payload = Static<typeof SchemaData>;
|
||||||
required: [
|
|
||||||
'title', 'desc', 'picUrl', 'jumpUrl',
|
|
||||||
'iconUrl', 'appId', 'scene', 'templateType', 'businessType',
|
|
||||||
'verType', 'shareType', 'versionId', 'withShareTicket'
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
} as const satisfies JSONSchema;
|
|
||||||
|
|
||||||
type Payload = FromSchema<typeof SchemaData>;
|
|
||||||
|
|
||||||
export class GetMiniAppArk extends GetPacketStatusDepends<Payload, {
|
export class GetMiniAppArk extends GetPacketStatusDepends<Payload, {
|
||||||
data: MiniAppData | MiniAppRawData
|
data: MiniAppData | MiniAppRawData
|
||||||
@@ -57,7 +47,7 @@ export class GetMiniAppArk extends GetPacketStatusDepends<Payload, {
|
|||||||
picUrl: payload.picUrl,
|
picUrl: payload.picUrl,
|
||||||
jumpUrl: payload.jumpUrl
|
jumpUrl: payload.jumpUrl
|
||||||
} as MiniAppReqCustomParams;
|
} as MiniAppReqCustomParams;
|
||||||
if (payload.type) {
|
if ('type' in payload) {
|
||||||
reqParam = MiniAppInfoHelper.generateReq(customParams, MiniAppInfo.get(payload.type)!.template);
|
reqParam = MiniAppInfoHelper.generateReq(customParams, MiniAppInfo.get(payload.type)!.template);
|
||||||
} else {
|
} else {
|
||||||
const { appId, scene, iconUrl, templateType, businessType, verType, shareType, versionId, withShareTicket } = payload;
|
const { appId, scene, iconUrl, templateType, businessType, verType, shareType, versionId, withShareTicket } = payload;
|
||||||
|
@@ -1,21 +1,28 @@
|
|||||||
import { OneBotAction } from '@/onebot/action/OneBotAction';
|
import { OneBotAction } from '@/onebot/action/OneBotAction';
|
||||||
import { ActionName } from '@/onebot/action/router';
|
import { ActionName } from '@/onebot/action/router';
|
||||||
|
import { Type, Static } from '@sinclair/typebox';
|
||||||
|
|
||||||
interface Payload {
|
const SchemaData = Type.Object({
|
||||||
start: number,
|
user_id: Type.Optional(Type.Union([Type.Number(), Type.String()])),
|
||||||
count: number
|
start: Type.Union([Type.Number(), Type.String()], { default: 0 }),
|
||||||
}
|
count: Type.Union([Type.Number(), Type.String()], { default: 10 }),
|
||||||
|
type: Type.Union([Type.Number(), Type.String()], { default: 2 }),
|
||||||
|
});
|
||||||
|
|
||||||
|
type Payload = Static<typeof SchemaData>;
|
||||||
|
|
||||||
export class GetProfileLike extends OneBotAction<Payload, any> {
|
export class GetProfileLike extends OneBotAction<Payload, any> {
|
||||||
actionName = ActionName.GetProfileLike;
|
actionName = ActionName.GetProfileLike;
|
||||||
|
payloadSchema = SchemaData;
|
||||||
async _handle(payload: Payload) {
|
async _handle(payload: Payload) {
|
||||||
const start = payload.start ? Number(payload.start) : 0;
|
const user_uid =
|
||||||
const count = payload.count ? Number(payload.count) : 10;
|
this.core.selfInfo.uin === payload.user_id || !payload.user_id ?
|
||||||
const ret = await this.core.apis.UserApi.getProfileLike(this.core.selfInfo.uid, start, count);
|
this.core.selfInfo.uid :
|
||||||
|
await this.core.apis.UserApi.getUidByUinV2(payload.user_id.toString());
|
||||||
|
const ret = await this.core.apis.UserApi.getProfileLike(user_uid ?? this.core.selfInfo.uid, +payload.start, +payload.count, +payload.type);
|
||||||
const listdata = ret.info.userLikeInfos[0].voteInfo.userInfos;
|
const listdata = ret.info.userLikeInfos[0].voteInfo.userInfos;
|
||||||
for (const item of listdata) {
|
for (const item of listdata) {
|
||||||
item.uin = parseInt((await this.core.apis.UserApi.getUinByUidV2(item.uid)) || '');
|
item.uin = +((await this.core.apis.UserApi.getUinByUidV2(item.uid)) ?? '');
|
||||||
}
|
}
|
||||||
return ret.info.userLikeInfos[0].voteInfo;
|
return ret.info.userLikeInfos[0].voteInfo;
|
||||||
}
|
}
|
||||||
|
@@ -1,8 +1,7 @@
|
|||||||
import { ActionName } from '@/onebot/action/router';
|
import { ActionName } from '@/onebot/action/router';
|
||||||
import { GetPacketStatusDepends } from "@/onebot/action/packet/GetPacketStatus";
|
import { GetPacketStatusDepends } from "@/onebot/action/packet/GetPacketStatus";
|
||||||
|
|
||||||
|
export class GetRkey extends GetPacketStatusDepends<void, Array<any>> {
|
||||||
export class GetRkey extends GetPacketStatusDepends<null, Array<any>> {
|
|
||||||
actionName = ActionName.GetRkey;
|
actionName = ActionName.GetRkey;
|
||||||
|
|
||||||
async _handle() {
|
async _handle() {
|
||||||
|
@@ -4,7 +4,7 @@ import { ActionName } from '@/onebot/action/router';
|
|||||||
export class GetRobotUinRange extends OneBotAction<void, Array<any>> {
|
export class GetRobotUinRange extends OneBotAction<void, Array<any>> {
|
||||||
actionName = ActionName.GetRobotUinRange;
|
actionName = ActionName.GetRobotUinRange;
|
||||||
|
|
||||||
async _handle(payload: void) {
|
async _handle() {
|
||||||
return await this.core.apis.UserApi.getRobotUinRange();
|
return await this.core.apis.UserApi.getRobotUinRange();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,16 +1,12 @@
|
|||||||
import { ActionName } from '@/onebot/action/router';
|
import { ActionName } from '@/onebot/action/router';
|
||||||
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
|
|
||||||
import { GetPacketStatusDepends } from "@/onebot/action/packet/GetPacketStatus";
|
import { GetPacketStatusDepends } from "@/onebot/action/packet/GetPacketStatus";
|
||||||
// no_cache get时传字符串
|
import { Static, Type } from '@sinclair/typebox';
|
||||||
const SchemaData = {
|
|
||||||
type: 'object',
|
|
||||||
properties: {
|
|
||||||
user_id: { type: ['number', 'string'] },
|
|
||||||
},
|
|
||||||
required: ['user_id'],
|
|
||||||
} as const satisfies JSONSchema;
|
|
||||||
|
|
||||||
type Payload = FromSchema<typeof SchemaData>;
|
const SchemaData = Type.Object({
|
||||||
|
user_id: Type.Union([Type.Number(), Type.String()]),
|
||||||
|
});
|
||||||
|
|
||||||
|
type Payload = Static<typeof SchemaData>;
|
||||||
|
|
||||||
export class GetUserStatus extends GetPacketStatusDepends<Payload, { status: number; ext_status: number; } | undefined> {
|
export class GetUserStatus extends GetPacketStatusDepends<Payload, { status: number; ext_status: number; } | undefined> {
|
||||||
actionName = ActionName.GetUserStatus;
|
actionName = ActionName.GetUserStatus;
|
||||||
|
@@ -1,18 +1,14 @@
|
|||||||
import { OneBotAction } from '@/onebot/action/OneBotAction';
|
import { OneBotAction } from '@/onebot/action/OneBotAction';
|
||||||
import { ActionName } from '@/onebot/action/router';
|
import { ActionName } from '@/onebot/action/router';
|
||||||
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
|
|
||||||
import { checkFileExist, uri2local } from '@/common/file';
|
import { checkFileExist, uri2local } from '@/common/file';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
|
import { Static, Type } from '@sinclair/typebox';
|
||||||
|
|
||||||
const SchemaData = {
|
const SchemaData = Type.Object({
|
||||||
type: 'object',
|
image: Type.String(),
|
||||||
properties: {
|
});
|
||||||
image: { type: 'string' },
|
|
||||||
},
|
|
||||||
required: ['image'],
|
|
||||||
} as const satisfies JSONSchema;
|
|
||||||
|
|
||||||
type Payload = FromSchema<typeof SchemaData>;
|
type Payload = Static<typeof SchemaData>;
|
||||||
|
|
||||||
export class OCRImage extends OneBotAction<Payload, any> {
|
export class OCRImage extends OneBotAction<Payload, any> {
|
||||||
actionName = ActionName.OCRImage;
|
actionName = ActionName.OCRImage;
|
||||||
@@ -29,12 +25,12 @@ export class OCRImage extends OneBotAction<Payload, any> {
|
|||||||
fs.unlink(path, () => { });
|
fs.unlink(path, () => { });
|
||||||
|
|
||||||
if (!ret) {
|
if (!ret) {
|
||||||
throw new Error(`OCR ${payload.file}失败`);
|
throw new Error(`OCR ${payload.image}失败`);
|
||||||
}
|
}
|
||||||
return ret.result;
|
return ret.result;
|
||||||
}
|
}
|
||||||
fs.unlink(path, () => { });
|
fs.unlink(path, () => { });
|
||||||
throw new Error(`OCR ${payload.file}失败,文件可能不存在`);
|
throw new Error(`OCR ${payload.image}失败,文件可能不存在`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,16 +1,12 @@
|
|||||||
import { GetPacketStatusDepends } from '../packet/GetPacketStatus';
|
import { GetPacketStatusDepends } from '@/onebot/action/packet/GetPacketStatus';
|
||||||
import { ActionName } from '@/onebot/action/router';
|
import { ActionName } from '@/onebot/action/router';
|
||||||
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
|
import { Static, Type } from '@sinclair/typebox';
|
||||||
|
|
||||||
const SchemaData = {
|
const SchemaData = Type.Object({
|
||||||
type: 'object',
|
group_id: Type.Union([Type.Number(), Type.String()]),
|
||||||
properties: {
|
});
|
||||||
group_id: { type: ['string', 'number'] },
|
|
||||||
},
|
|
||||||
required: ['group_id'],
|
|
||||||
} as const satisfies JSONSchema;
|
|
||||||
|
|
||||||
type Payload = FromSchema<typeof SchemaData>;
|
type Payload = Static<typeof SchemaData>;
|
||||||
|
|
||||||
export class SetGroupSign extends GetPacketStatusDepends<Payload, any> {
|
export class SetGroupSign extends GetPacketStatusDepends<Payload, any> {
|
||||||
actionName = ActionName.SetGroupSign;
|
actionName = ActionName.SetGroupSign;
|
||||||
|
@@ -1,18 +1,14 @@
|
|||||||
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
|
|
||||||
import { OneBotAction } from '@/onebot/action/OneBotAction';
|
import { OneBotAction } from '@/onebot/action/OneBotAction';
|
||||||
import { ActionName } from '@/onebot/action/router';
|
import { ActionName } from '@/onebot/action/router';
|
||||||
import { ChatType } from '@/core';
|
import { ChatType } from '@/core';
|
||||||
|
import { Static, Type } from '@sinclair/typebox';
|
||||||
|
|
||||||
const SchemaData = {
|
const SchemaData = Type.Object({
|
||||||
type: 'object',
|
user_id: Type.Union([Type.Number(), Type.String()]),
|
||||||
properties: {
|
event_type: Type.Number(),
|
||||||
event_type: { type: 'number' },
|
});
|
||||||
user_id: { type: ['number', 'string'] },
|
|
||||||
},
|
|
||||||
required: ['event_type', 'user_id'],
|
|
||||||
} as const satisfies JSONSchema;
|
|
||||||
|
|
||||||
type Payload = FromSchema<typeof SchemaData>;
|
type Payload = Static<typeof SchemaData>;
|
||||||
|
|
||||||
export class SetInputStatus extends OneBotAction<Payload, any> {
|
export class SetInputStatus extends OneBotAction<Payload, any> {
|
||||||
actionName = ActionName.SetInputStatus;
|
actionName = ActionName.SetInputStatus;
|
||||||
|
@@ -1,16 +1,12 @@
|
|||||||
import { OneBotAction } from '@/onebot/action/OneBotAction';
|
import { OneBotAction } from '@/onebot/action/OneBotAction';
|
||||||
import { ActionName } from '@/onebot/action/router';
|
import { ActionName } from '@/onebot/action/router';
|
||||||
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
|
import { Static, Type } from '@sinclair/typebox';
|
||||||
|
|
||||||
const SchemaData = {
|
const SchemaData = Type.Object({
|
||||||
type: 'object',
|
longNick: Type.String(),
|
||||||
properties: {
|
});
|
||||||
longNick: { type: 'string' },
|
|
||||||
},
|
|
||||||
required: ['longNick'],
|
|
||||||
} as const satisfies JSONSchema;
|
|
||||||
|
|
||||||
type Payload = FromSchema<typeof SchemaData>;
|
type Payload = Static<typeof SchemaData>;
|
||||||
|
|
||||||
export class SetLongNick extends OneBotAction<Payload, any> {
|
export class SetLongNick extends OneBotAction<Payload, any> {
|
||||||
actionName = ActionName.SetLongNick;
|
actionName = ActionName.SetLongNick;
|
||||||
|
@@ -1,19 +1,14 @@
|
|||||||
import { OneBotAction } from '@/onebot/action/OneBotAction';
|
import { OneBotAction } from '@/onebot/action/OneBotAction';
|
||||||
import { ActionName } from '@/onebot/action/router';
|
import { ActionName } from '@/onebot/action/router';
|
||||||
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
|
import { Static, Type } from '@sinclair/typebox';
|
||||||
// 设置在线状态
|
|
||||||
|
|
||||||
const SchemaData = {
|
const SchemaData = Type.Object({
|
||||||
type: 'object',
|
status: Type.Union([Type.Number(), Type.String()]),
|
||||||
properties: {
|
ext_status: Type.Union([Type.Number(), Type.String()]),
|
||||||
status: { type: ['number', 'string'] },
|
battery_status: Type.Union([Type.Number(), Type.String()]),
|
||||||
ext_status: { type: ['number', 'string'] },
|
});
|
||||||
battery_status: { type: ['number', 'string'] },
|
|
||||||
},
|
|
||||||
required: ['status', 'ext_status', 'battery_status'],
|
|
||||||
} as const satisfies JSONSchema;
|
|
||||||
|
|
||||||
type Payload = FromSchema<typeof SchemaData>;
|
type Payload = Static<typeof SchemaData>;
|
||||||
|
|
||||||
export class SetOnlineStatus extends OneBotAction<Payload, null> {
|
export class SetOnlineStatus extends OneBotAction<Payload, null> {
|
||||||
actionName = ActionName.SetOnlineStatus;
|
actionName = ActionName.SetOnlineStatus;
|
||||||
@@ -21,9 +16,9 @@ export class SetOnlineStatus extends OneBotAction<Payload, null> {
|
|||||||
|
|
||||||
async _handle(payload: Payload) {
|
async _handle(payload: Payload) {
|
||||||
const ret = await this.core.apis.UserApi.setSelfOnlineStatus(
|
const ret = await this.core.apis.UserApi.setSelfOnlineStatus(
|
||||||
parseInt(payload.status.toString()),
|
+payload.status,
|
||||||
parseInt(payload.ext_status.toString()),
|
+payload.ext_status,
|
||||||
parseInt(payload.battery_status.toString()),
|
+payload.battery_status,
|
||||||
);
|
);
|
||||||
if (ret.result !== 0) {
|
if (ret.result !== 0) {
|
||||||
throw new Error('设置在线状态失败');
|
throw new Error('设置在线状态失败');
|
||||||
|
@@ -1,28 +1,18 @@
|
|||||||
import { OneBotAction } from '@/onebot/action/OneBotAction';
|
import { OneBotAction } from '@/onebot/action/OneBotAction';
|
||||||
import { ActionName, BaseCheckResult } from '@/onebot/action/router';
|
import { ActionName } from '@/onebot/action/router';
|
||||||
import * as fs from 'node:fs';
|
import fs from 'node:fs/promises';
|
||||||
import { checkFileExist, uri2local } from '@/common/file';
|
import { checkFileExist, uri2local } from '@/common/file';
|
||||||
|
import { Static, Type } from '@sinclair/typebox';
|
||||||
|
|
||||||
interface Payload {
|
const SchemaData = Type.Object({
|
||||||
file: string;
|
file: Type.String(),
|
||||||
}
|
});
|
||||||
|
|
||||||
|
type Payload = Static<typeof SchemaData>;
|
||||||
|
|
||||||
export default class SetAvatar extends OneBotAction<Payload, null> {
|
export default class SetAvatar extends OneBotAction<Payload, null> {
|
||||||
actionName = ActionName.SetQQAvatar;
|
actionName = ActionName.SetQQAvatar;
|
||||||
|
payloadSchema = SchemaData;
|
||||||
// 用不着复杂检测
|
|
||||||
protected async check(payload: Payload): Promise<BaseCheckResult> {
|
|
||||||
if (!payload.file || typeof payload.file != 'string') {
|
|
||||||
return {
|
|
||||||
valid: false,
|
|
||||||
message: 'file字段不能为空或者类型错误',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
valid: true,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
async _handle(payload: Payload): Promise<null> {
|
async _handle(payload: Payload): Promise<null> {
|
||||||
const { path, success } = (await uri2local(this.core.NapCatTempPath, payload.file));
|
const { path, success } = (await uri2local(this.core.NapCatTempPath, payload.file));
|
||||||
if (!success) {
|
if (!success) {
|
||||||
@@ -31,9 +21,7 @@ export default class SetAvatar extends OneBotAction<Payload, null> {
|
|||||||
if (path) {
|
if (path) {
|
||||||
await checkFileExist(path, 5000);// 避免崩溃
|
await checkFileExist(path, 5000);// 避免崩溃
|
||||||
const ret = await this.core.apis.UserApi.setQQAvatar(path);
|
const ret = await this.core.apis.UserApi.setQQAvatar(path);
|
||||||
fs.unlink(path, () => {
|
fs.unlink(path).catch(() => { });
|
||||||
});
|
|
||||||
|
|
||||||
if (!ret) {
|
if (!ret) {
|
||||||
throw new Error(`头像${payload.file}设置失败,api无返回`);
|
throw new Error(`头像${payload.file}设置失败,api无返回`);
|
||||||
}
|
}
|
||||||
@@ -44,8 +32,7 @@ export default class SetAvatar extends OneBotAction<Payload, null> {
|
|||||||
throw new Error(`头像${payload.file}设置失败,未知的错误,${ret.result}:${ret.errMsg}`);
|
throw new Error(`头像${payload.file}设置失败,未知的错误,${ret.result}:${ret.errMsg}`);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
fs.unlink(path, () => { });
|
fs.unlink(path).catch(() => { });
|
||||||
|
|
||||||
throw new Error(`头像${payload.file}设置失败,无法获取头像,文件可能不存在`);
|
throw new Error(`头像${payload.file}设置失败,无法获取头像,文件可能不存在`);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
@@ -1,17 +1,14 @@
|
|||||||
import { ActionName } from '@/onebot/action/router';
|
import { ActionName } from '@/onebot/action/router';
|
||||||
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
|
|
||||||
import { GetPacketStatusDepends } from "@/onebot/action/packet/GetPacketStatus";
|
import { GetPacketStatusDepends } from "@/onebot/action/packet/GetPacketStatus";
|
||||||
const SchemaData = {
|
import { Static, Type } from '@sinclair/typebox';
|
||||||
type: 'object',
|
|
||||||
properties: {
|
|
||||||
group_id: { type: ['number', 'string'] },
|
|
||||||
user_id: { type: ['number', 'string'] },
|
|
||||||
special_title: { type: 'string' },
|
|
||||||
},
|
|
||||||
required: ['group_id', 'user_id', 'special_title'],
|
|
||||||
} as const satisfies JSONSchema;
|
|
||||||
|
|
||||||
type Payload = FromSchema<typeof SchemaData>;
|
const SchemaData = Type.Object({
|
||||||
|
group_id: Type.Union([Type.Number(), Type.String()]),
|
||||||
|
user_id: Type.Union([Type.Number(), Type.String()]),
|
||||||
|
special_title: Type.String(),
|
||||||
|
});
|
||||||
|
|
||||||
|
type Payload = Static<typeof SchemaData>;
|
||||||
|
|
||||||
export class SetSpecialTittle extends GetPacketStatusDepends<Payload, any> {
|
export class SetSpecialTittle extends GetPacketStatusDepends<Payload, any> {
|
||||||
actionName = ActionName.SetSpecialTittle;
|
actionName = ActionName.SetSpecialTittle;
|
||||||
|
@@ -1,18 +1,14 @@
|
|||||||
import { OneBotAction } from '@/onebot/action/OneBotAction';
|
import { OneBotAction } from '@/onebot/action/OneBotAction';
|
||||||
import { ActionName } from '@/onebot/action/router';
|
import { ActionName } from '@/onebot/action/router';
|
||||||
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
|
import { Static, Type } from '@sinclair/typebox';
|
||||||
|
|
||||||
const SchemaData = {
|
const SchemaData = Type.Object({
|
||||||
type: 'object',
|
user_id: Type.Optional(Type.Union([Type.Number(), Type.String()])),
|
||||||
properties: {
|
group_id: Type.Optional(Type.Union([Type.Number(), Type.String()])),
|
||||||
user_id: { type: 'string' },
|
phoneNumber: Type.String({ default: '' }),
|
||||||
group_id: { type: 'string' },
|
});
|
||||||
phoneNumber: { type: 'string' },
|
|
||||||
},
|
|
||||||
} as const satisfies JSONSchema;
|
|
||||||
|
|
||||||
type Payload = FromSchema<typeof SchemaData>;
|
|
||||||
|
|
||||||
|
type Payload = Static<typeof SchemaData>;
|
||||||
|
|
||||||
export class SharePeer extends OneBotAction<Payload, any> {
|
export class SharePeer extends OneBotAction<Payload, any> {
|
||||||
actionName = ActionName.SharePeer;
|
actionName = ActionName.SharePeer;
|
||||||
@@ -20,28 +16,24 @@ export class SharePeer extends OneBotAction<Payload, any> {
|
|||||||
|
|
||||||
async _handle(payload: Payload) {
|
async _handle(payload: Payload) {
|
||||||
if (payload.group_id) {
|
if (payload.group_id) {
|
||||||
return await this.core.apis.GroupApi.getGroupRecommendContactArkJson(payload.group_id);
|
return await this.core.apis.GroupApi.getGroupRecommendContactArkJson(payload.group_id.toString());
|
||||||
} else if (payload.user_id) {
|
} else if (payload.user_id) {
|
||||||
return await this.core.apis.UserApi.getBuddyRecommendContactArkJson(payload.user_id, payload.phoneNumber || '');
|
return await this.core.apis.UserApi.getBuddyRecommendContactArkJson(payload.user_id.toString(), payload.phoneNumber);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const SchemaDataGroupEx = {
|
const SchemaDataGroupEx = Type.Object({
|
||||||
type: 'object',
|
group_id: Type.Union([Type.Number(), Type.String()]),
|
||||||
properties: {
|
});
|
||||||
group_id: { type: 'string' },
|
|
||||||
},
|
|
||||||
required: ['group_id'],
|
|
||||||
} as const satisfies JSONSchema;
|
|
||||||
|
|
||||||
type PayloadGroupEx = FromSchema<typeof SchemaDataGroupEx>;
|
type PayloadGroupEx = Static<typeof SchemaDataGroupEx>;
|
||||||
|
|
||||||
export class ShareGroupEx extends OneBotAction<PayloadGroupEx, any> {
|
export class ShareGroupEx extends OneBotAction<PayloadGroupEx, any> {
|
||||||
actionName = ActionName.ShareGroupEx;
|
actionName = ActionName.ShareGroupEx;
|
||||||
payloadSchema = SchemaDataGroupEx;
|
payloadSchema = SchemaDataGroupEx;
|
||||||
|
|
||||||
async _handle(payload: PayloadGroupEx) {
|
async _handle(payload: PayloadGroupEx) {
|
||||||
return await this.core.apis.GroupApi.getArkJsonGroupShare(payload.group_id);
|
return await this.core.apis.GroupApi.getArkJsonGroupShare(payload.group_id.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,19 +1,12 @@
|
|||||||
import { OneBotAction } from '@/onebot/action/OneBotAction';
|
import { OneBotAction } from '@/onebot/action/OneBotAction';
|
||||||
import { ActionName } from '@/onebot/action/router';
|
import { ActionName } from '@/onebot/action/router';
|
||||||
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
|
import { Static, Type } from '@sinclair/typebox';
|
||||||
|
|
||||||
const SchemaData = {
|
const SchemaData = Type.Object({
|
||||||
type: 'object',
|
words: Type.Array(Type.String()),
|
||||||
properties: {
|
});
|
||||||
words: {
|
|
||||||
type: 'array',
|
|
||||||
items: { type: 'string' },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
required: ['words'],
|
|
||||||
} as const satisfies JSONSchema;
|
|
||||||
|
|
||||||
type Payload = FromSchema<typeof SchemaData>;
|
type Payload = Static<typeof SchemaData>;
|
||||||
|
|
||||||
export class TranslateEnWordToZn extends OneBotAction<Payload, Array<any> | null> {
|
export class TranslateEnWordToZn extends OneBotAction<Payload, Array<any> | null> {
|
||||||
actionName = ActionName.TranslateEnWordToZn;
|
actionName = ActionName.TranslateEnWordToZn;
|
||||||
|
@@ -2,12 +2,8 @@ import { OneBotAction } from '@/onebot/action/OneBotAction';
|
|||||||
import fs from 'fs/promises';
|
import fs from 'fs/promises';
|
||||||
import { FileNapCatOneBotUUID } from '@/common/helper';
|
import { FileNapCatOneBotUUID } from '@/common/helper';
|
||||||
import { ActionName } from '@/onebot/action/router';
|
import { ActionName } from '@/onebot/action/router';
|
||||||
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
|
|
||||||
import { OB11MessageImage, OB11MessageVideo } from '@/onebot/types';
|
import { OB11MessageImage, OB11MessageVideo } from '@/onebot/types';
|
||||||
|
import { Static, Type } from '@sinclair/typebox';
|
||||||
// interface GetFilePayload {
|
|
||||||
// file: string; // 文件名或者fileUuid
|
|
||||||
// }
|
|
||||||
|
|
||||||
export interface GetFileResponse {
|
export interface GetFileResponse {
|
||||||
file?: string; // path
|
file?: string; // path
|
||||||
@@ -16,19 +12,14 @@ export interface GetFileResponse {
|
|||||||
file_name?: string;
|
file_name?: string;
|
||||||
base64?: string;
|
base64?: string;
|
||||||
}
|
}
|
||||||
const GetFileBase_PayloadSchema = {
|
|
||||||
type: 'object',
|
|
||||||
properties: {
|
|
||||||
file: { type: 'string' },
|
|
||||||
file_id: { type: 'string' }
|
|
||||||
},
|
|
||||||
oneOf: [
|
|
||||||
{ required: ['file'] },
|
|
||||||
{ required: ['file_id'] }
|
|
||||||
]
|
|
||||||
} as const satisfies JSONSchema;
|
|
||||||
|
|
||||||
export type GetFilePayload = FromSchema<typeof GetFileBase_PayloadSchema>;
|
const GetFileBase_PayloadSchema = Type.Object({
|
||||||
|
file: Type.Optional(Type.String()),
|
||||||
|
file_id: Type.Optional(Type.String())
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
export type GetFilePayload = Static<typeof GetFileBase_PayloadSchema>;
|
||||||
|
|
||||||
export class GetFileBase extends OneBotAction<GetFilePayload, GetFileResponse> {
|
export class GetFileBase extends OneBotAction<GetFilePayload, GetFileResponse> {
|
||||||
payloadSchema = GetFileBase_PayloadSchema;
|
payloadSchema = GetFileBase_PayloadSchema;
|
||||||
@@ -50,12 +41,12 @@ export class GetFileBase extends OneBotAction<GetFilePayload, GetFileResponse> {
|
|||||||
let url = '';
|
let url = '';
|
||||||
if (mixElement?.picElement && rawMessage) {
|
if (mixElement?.picElement && rawMessage) {
|
||||||
const tempData =
|
const tempData =
|
||||||
await this.obContext.apis.MsgApi.rawToOb11Converters.picElement?.(mixElement?.picElement, rawMessage, mixElement) as OB11MessageImage | undefined;
|
await this.obContext.apis.MsgApi.rawToOb11Converters.picElement?.(mixElement?.picElement, rawMessage, mixElement, { parseMultMsg: false }) as OB11MessageImage | undefined;
|
||||||
url = tempData?.data.url ?? '';
|
url = tempData?.data.url ?? '';
|
||||||
}
|
}
|
||||||
if (mixElement?.videoElement && rawMessage) {
|
if (mixElement?.videoElement && rawMessage) {
|
||||||
const tempData =
|
const tempData =
|
||||||
await this.obContext.apis.MsgApi.rawToOb11Converters.videoElement?.(mixElement?.videoElement, rawMessage, mixElement) as OB11MessageVideo | undefined;
|
await this.obContext.apis.MsgApi.rawToOb11Converters.videoElement?.(mixElement?.videoElement, rawMessage, mixElement, { parseMultMsg: false }) as OB11MessageVideo | undefined;
|
||||||
url = tempData?.data.url ?? '';
|
url = tempData?.data.url ?? '';
|
||||||
}
|
}
|
||||||
const res: GetFileResponse = {
|
const res: GetFileResponse = {
|
||||||
|
@@ -1,18 +1,14 @@
|
|||||||
import { ActionName } from '@/onebot/action/router';
|
import { ActionName } from '@/onebot/action/router';
|
||||||
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
|
|
||||||
import { FileNapCatOneBotUUID } from "@/common/helper";
|
import { FileNapCatOneBotUUID } from "@/common/helper";
|
||||||
import { GetPacketStatusDepends } from "@/onebot/action/packet/GetPacketStatus";
|
import { GetPacketStatusDepends } from "@/onebot/action/packet/GetPacketStatus";
|
||||||
|
import { Static, Type } from '@sinclair/typebox';
|
||||||
|
|
||||||
const SchemaData = {
|
const SchemaData = Type.Object({
|
||||||
type: 'object',
|
group_id: Type.Union([Type.Number(), Type.String()]),
|
||||||
properties: {
|
file_id: Type.String(),
|
||||||
group_id: { type: ['number', 'string'] },
|
});
|
||||||
file_id: { type: ['string'] },
|
|
||||||
},
|
|
||||||
required: ['group_id', 'file_id'],
|
|
||||||
} as const satisfies JSONSchema;
|
|
||||||
|
|
||||||
type Payload = FromSchema<typeof SchemaData>;
|
type Payload = Static<typeof SchemaData>;
|
||||||
|
|
||||||
interface GetGroupFileUrlResponse {
|
interface GetGroupFileUrlResponse {
|
||||||
url?: string;
|
url?: string;
|
||||||
|
@@ -1,22 +1,18 @@
|
|||||||
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
|
|
||||||
import { OneBotAction } from '@/onebot/action/OneBotAction';
|
import { OneBotAction } from '@/onebot/action/OneBotAction';
|
||||||
import { ActionName } from '@/onebot/action/router';
|
import { ActionName } from '@/onebot/action/router';
|
||||||
|
import { Static, Type } from '@sinclair/typebox';
|
||||||
|
|
||||||
const SchemaData = {
|
const SchemaData = Type.Object({
|
||||||
type: 'object',
|
group_id: Type.Union([Type.Number(), Type.String()]),
|
||||||
properties: {
|
folder_name: Type.String(),
|
||||||
group_id: { type: ['string', 'number'] },
|
});
|
||||||
folder_name: { type: 'string' },
|
|
||||||
},
|
|
||||||
required: ['group_id', 'folder_name'],
|
|
||||||
} as const satisfies JSONSchema;
|
|
||||||
|
|
||||||
type Payload = FromSchema<typeof SchemaData>;
|
type Payload = Static<typeof SchemaData>;
|
||||||
|
|
||||||
export class CreateGroupFileFolder extends OneBotAction<Payload, any> {
|
export class CreateGroupFileFolder extends OneBotAction<Payload, any> {
|
||||||
actionName = ActionName.GoCQHTTP_CreateGroupFileFolder;
|
actionName = ActionName.GoCQHTTP_CreateGroupFileFolder;
|
||||||
payloadSchema = SchemaData;
|
payloadSchema = SchemaData;
|
||||||
async _handle(payload: Payload) {
|
async _handle(payload: Payload) {
|
||||||
return (await this.core.apis.GroupApi.CreatGroupFileFolder(payload.group_id.toString(), payload.folder_name)).resultWithGroupItem;
|
return (await this.core.apis.GroupApi.creatGroupFileFolder(payload.group_id.toString(), payload.folder_name)).resultWithGroupItem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,18 +1,15 @@
|
|||||||
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
|
|
||||||
import { OneBotAction } from '@/onebot/action/OneBotAction';
|
import { OneBotAction } from '@/onebot/action/OneBotAction';
|
||||||
import { ActionName } from '@/onebot/action/router';
|
import { ActionName } from '@/onebot/action/router';
|
||||||
import { FileNapCatOneBotUUID } from '@/common/helper';
|
import { FileNapCatOneBotUUID } from '@/common/helper';
|
||||||
|
import { Static, Type } from '@sinclair/typebox';
|
||||||
|
|
||||||
const SchemaData = {
|
const SchemaData = Type.Object({
|
||||||
type: 'object',
|
group_id: Type.Union([Type.Number(), Type.String()]),
|
||||||
properties: {
|
file_id: Type.String(),
|
||||||
group_id: { type: ['string', 'number'] },
|
});
|
||||||
file_id: { type: 'string' },
|
|
||||||
},
|
|
||||||
required: ['group_id', 'file_id'],
|
|
||||||
} as const satisfies JSONSchema;
|
|
||||||
|
|
||||||
type Payload = FromSchema<typeof SchemaData>;
|
type Payload = Static<typeof SchemaData>;
|
||||||
|
|
||||||
export class DeleteGroupFile extends OneBotAction<Payload, any> {
|
export class DeleteGroupFile extends OneBotAction<Payload, any> {
|
||||||
actionName = ActionName.GOCQHTTP_DeleteGroupFile;
|
actionName = ActionName.GOCQHTTP_DeleteGroupFile;
|
||||||
@@ -20,6 +17,6 @@ export class DeleteGroupFile extends OneBotAction<Payload, any> {
|
|||||||
async _handle(payload: Payload) {
|
async _handle(payload: Payload) {
|
||||||
const data = FileNapCatOneBotUUID.decodeModelId(payload.file_id);
|
const data = FileNapCatOneBotUUID.decodeModelId(payload.file_id);
|
||||||
if (!data) throw new Error('Invalid file_id');
|
if (!data) throw new Error('Invalid file_id');
|
||||||
return await this.core.apis.GroupApi.DelGroupFile(payload.group_id.toString(), [data.fileId]);
|
return await this.core.apis.GroupApi.delGroupFile(payload.group_id.toString(), [data.fileId]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,24 +1,20 @@
|
|||||||
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
|
|
||||||
import { ActionName } from '@/onebot/action/router';
|
import { ActionName } from '@/onebot/action/router';
|
||||||
import { OneBotAction } from '@/onebot/action/OneBotAction';
|
import { OneBotAction } from '@/onebot/action/OneBotAction';
|
||||||
|
import { Static, Type } from '@sinclair/typebox';
|
||||||
|
|
||||||
const SchemaData = {
|
const SchemaData = Type.Object({
|
||||||
type: 'object',
|
group_id: Type.Union([Type.Number(), Type.String()]),
|
||||||
properties: {
|
folder_id: Type.Optional(Type.String()),
|
||||||
group_id: { type: ['string', 'number'] },
|
folder: Type.Optional(Type.String()),
|
||||||
folder_id: { type: 'string' },
|
});
|
||||||
folder: { type: 'string' }
|
|
||||||
},
|
|
||||||
required: ['group_id'],
|
|
||||||
} as const satisfies JSONSchema;
|
|
||||||
|
|
||||||
type Payload = FromSchema<typeof SchemaData>;
|
type Payload = Static<typeof SchemaData>;
|
||||||
|
|
||||||
export class DeleteGroupFileFolder extends OneBotAction<Payload, any> {
|
export class DeleteGroupFileFolder extends OneBotAction<Payload, any> {
|
||||||
actionName = ActionName.GoCQHTTP_DeleteGroupFileFolder;
|
actionName = ActionName.GoCQHTTP_DeleteGroupFileFolder;
|
||||||
payloadSchema = SchemaData;
|
payloadSchema = SchemaData;
|
||||||
async _handle(payload: Payload) {
|
async _handle(payload: Payload) {
|
||||||
return (await this.core.apis.GroupApi.DelGroupFileFolder(
|
return (await this.core.apis.GroupApi.delGroupFileFolder(
|
||||||
payload.group_id.toString(), payload.folder ?? payload.folder_id ?? '')).groupFileCommonResult;
|
payload.group_id.toString(), payload.folder ?? payload.folder_id ?? '')).groupFileCommonResult;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -4,29 +4,20 @@ import fs from 'fs';
|
|||||||
import { join as joinPath } from 'node:path';
|
import { join as joinPath } from 'node:path';
|
||||||
import { calculateFileMD5, httpDownload } from '@/common/file';
|
import { calculateFileMD5, httpDownload } from '@/common/file';
|
||||||
import { randomUUID } from 'crypto';
|
import { randomUUID } from 'crypto';
|
||||||
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
|
import { Static, Type } from '@sinclair/typebox';
|
||||||
|
|
||||||
interface FileResponse {
|
interface FileResponse {
|
||||||
file: string;
|
file: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SchemaData = {
|
const SchemaData = Type.Object({
|
||||||
type: 'object',
|
url: Type.Optional(Type.String()),
|
||||||
properties: {
|
base64: Type.Optional(Type.String()),
|
||||||
thread_count: { type: ['number', 'string'] },
|
name: Type.Optional(Type.String()),
|
||||||
url: { type: 'string' },
|
headers: Type.Optional(Type.Union([Type.String(), Type.Array(Type.String())])),
|
||||||
base64: { type: 'string' },
|
});
|
||||||
name: { type: 'string' },
|
|
||||||
headers: {
|
|
||||||
type: ['string', 'array'],
|
|
||||||
items: {
|
|
||||||
type: 'string',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
} as const satisfies JSONSchema;
|
|
||||||
|
|
||||||
type Payload = FromSchema<typeof SchemaData>;
|
type Payload = Static<typeof SchemaData>;
|
||||||
|
|
||||||
export default class GoCQHTTPDownloadFile extends OneBotAction<Payload, FileResponse> {
|
export default class GoCQHTTPDownloadFile extends OneBotAction<Payload, FileResponse> {
|
||||||
actionName = ActionName.GoCQHTTP_DownloadFile;
|
actionName = ActionName.GoCQHTTP_DownloadFile;
|
||||||
|
@@ -1,20 +1,15 @@
|
|||||||
import { OneBotAction } from '@/onebot/action/OneBotAction';
|
import { OneBotAction } from '@/onebot/action/OneBotAction';
|
||||||
import { OB11Message, OB11MessageData, OB11MessageDataType, OB11MessageForward, OB11MessageNodePlain as OB11MessageNode } from '@/onebot';
|
import { OB11Message, OB11MessageData, OB11MessageDataType, OB11MessageForward, OB11MessageNodePlain as OB11MessageNode } from '@/onebot';
|
||||||
import { ActionName } from '@/onebot/action/router';
|
import { ActionName } from '@/onebot/action/router';
|
||||||
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
|
|
||||||
import { MessageUnique } from '@/common/message-unique';
|
import { MessageUnique } from '@/common/message-unique';
|
||||||
|
import { Static, Type } from '@sinclair/typebox';
|
||||||
|
|
||||||
|
const SchemaData = Type.Object({
|
||||||
|
message_id: Type.Optional(Type.Union([Type.Number(), Type.String()])),
|
||||||
|
id: Type.Optional(Type.Union([Type.Number(), Type.String()])),
|
||||||
|
});
|
||||||
|
|
||||||
|
type Payload = Static<typeof SchemaData>;
|
||||||
const SchemaData = {
|
|
||||||
type: 'object',
|
|
||||||
properties: {
|
|
||||||
message_id: { type: 'string' },
|
|
||||||
id: { type: 'string' },
|
|
||||||
},
|
|
||||||
} as const satisfies JSONSchema;
|
|
||||||
|
|
||||||
type Payload = FromSchema<typeof SchemaData>;
|
|
||||||
|
|
||||||
export class GoCQHTTPGetForwardMsgAction extends OneBotAction<Payload, any> {
|
export class GoCQHTTPGetForwardMsgAction extends OneBotAction<Payload, any> {
|
||||||
actionName = ActionName.GoCQHTTP_GetForwardMsg;
|
actionName = ActionName.GoCQHTTP_GetForwardMsg;
|
||||||
@@ -41,7 +36,7 @@ export class GoCQHTTPGetForwardMsgAction extends OneBotAction<Payload, any> {
|
|||||||
for (const msgdata of message.message) {
|
for (const msgdata of message.message) {
|
||||||
if ((msgdata as OB11MessageData).type === OB11MessageDataType.forward) {
|
if ((msgdata as OB11MessageData).type === OB11MessageDataType.forward) {
|
||||||
const newNode = this.createTemplateNode(message);
|
const newNode = this.createTemplateNode(message);
|
||||||
newNode.data.message = await this.parseForward((msgdata as OB11MessageForward).data.content);
|
newNode.data.message = await this.parseForward((msgdata as OB11MessageForward).data.content ?? []);
|
||||||
|
|
||||||
templateNode.data.message.push(newNode);
|
templateNode.data.message.push(newNode);
|
||||||
} else {
|
} else {
|
||||||
@@ -60,7 +55,7 @@ export class GoCQHTTPGetForwardMsgAction extends OneBotAction<Payload, any> {
|
|||||||
throw new Error('message_id is required');
|
throw new Error('message_id is required');
|
||||||
}
|
}
|
||||||
|
|
||||||
const rootMsgId = MessageUnique.getShortIdByMsgId(msgId);
|
const rootMsgId = MessageUnique.getShortIdByMsgId(msgId.toString());
|
||||||
const rootMsg = MessageUnique.getMsgIdAndPeerByShortId(rootMsgId ?? +msgId);
|
const rootMsg = MessageUnique.getMsgIdAndPeerByShortId(rootMsgId ?? +msgId);
|
||||||
if (!rootMsg) {
|
if (!rootMsg) {
|
||||||
throw new Error('msg not found');
|
throw new Error('msg not found');
|
||||||
|
@@ -2,26 +2,22 @@ import { OneBotAction } from '@/onebot/action/OneBotAction';
|
|||||||
import { OB11Message } from '@/onebot';
|
import { OB11Message } from '@/onebot';
|
||||||
import { ActionName } from '@/onebot/action/router';
|
import { ActionName } from '@/onebot/action/router';
|
||||||
import { ChatType } from '@/core/types';
|
import { ChatType } from '@/core/types';
|
||||||
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
|
|
||||||
import { MessageUnique } from '@/common/message-unique';
|
import { MessageUnique } from '@/common/message-unique';
|
||||||
import { AdapterConfigWrap } from '@/onebot/config/config';
|
import { AdapterConfigWrap } from '@/onebot/config/config';
|
||||||
|
import { Static, Type } from '@sinclair/typebox';
|
||||||
|
|
||||||
interface Response {
|
interface Response {
|
||||||
messages: OB11Message[];
|
messages: OB11Message[];
|
||||||
}
|
}
|
||||||
|
const SchemaData = Type.Object({
|
||||||
|
user_id: Type.Union([Type.Number(), Type.String()]),
|
||||||
|
message_seq: Type.Optional(Type.Union([Type.Number(), Type.String()])),
|
||||||
|
count: Type.Union([Type.Number(), Type.String()], { default: 20 }),
|
||||||
|
reverseOrder: Type.Optional(Type.Union([Type.Boolean(), Type.String()]))
|
||||||
|
});
|
||||||
|
|
||||||
const SchemaData = {
|
|
||||||
type: 'object',
|
|
||||||
properties: {
|
|
||||||
user_id: { type: ['number', 'string'] },
|
|
||||||
message_seq: { type: ['number', 'string'] },
|
|
||||||
count: { type: ['number', 'string'] },
|
|
||||||
reverseOrder: { type: ['boolean', 'string'] },
|
|
||||||
},
|
|
||||||
required: ['user_id'],
|
|
||||||
} as const satisfies JSONSchema;
|
|
||||||
|
|
||||||
type Payload = FromSchema<typeof SchemaData>;
|
type Payload = Static<typeof SchemaData>;
|
||||||
|
|
||||||
export default class GetFriendMsgHistory extends OneBotAction<Payload, Response> {
|
export default class GetFriendMsgHistory extends OneBotAction<Payload, Response> {
|
||||||
actionName = ActionName.GetFriendMsgHistory;
|
actionName = ActionName.GetFriendMsgHistory;
|
||||||
@@ -30,7 +26,7 @@ export default class GetFriendMsgHistory extends OneBotAction<Payload, Response>
|
|||||||
async _handle(payload: Payload, adapter: string): Promise<Response> {
|
async _handle(payload: Payload, adapter: string): Promise<Response> {
|
||||||
//处理参数
|
//处理参数
|
||||||
const uid = await this.core.apis.UserApi.getUidByUinV2(payload.user_id.toString());
|
const uid = await this.core.apis.UserApi.getUidByUinV2(payload.user_id.toString());
|
||||||
const MsgCount = +(payload.count ?? 20);
|
|
||||||
const isReverseOrder = typeof payload.reverseOrder === 'string' ? payload.reverseOrder === 'true' : !!payload.reverseOrder;
|
const isReverseOrder = typeof payload.reverseOrder === 'string' ? payload.reverseOrder === 'true' : !!payload.reverseOrder;
|
||||||
if (!uid) throw new Error(`记录${payload.user_id}不存在`);
|
if (!uid) throw new Error(`记录${payload.user_id}不存在`);
|
||||||
const friend = await this.core.apis.FriendApi.isBuddy(uid);
|
const friend = await this.core.apis.FriendApi.isBuddy(uid);
|
||||||
@@ -38,7 +34,7 @@ export default class GetFriendMsgHistory extends OneBotAction<Payload, Response>
|
|||||||
const hasMessageSeq = !payload.message_seq ? !!payload.message_seq : !(payload.message_seq?.toString() === '' || payload.message_seq?.toString() === '0');
|
const hasMessageSeq = !payload.message_seq ? !!payload.message_seq : !(payload.message_seq?.toString() === '' || payload.message_seq?.toString() === '0');
|
||||||
const startMsgId = hasMessageSeq ? (MessageUnique.getMsgIdAndPeerByShortId(+payload.message_seq!)?.MsgId ?? payload.message_seq!.toString()) : '0';
|
const startMsgId = hasMessageSeq ? (MessageUnique.getMsgIdAndPeerByShortId(+payload.message_seq!)?.MsgId ?? payload.message_seq!.toString()) : '0';
|
||||||
const msgList = hasMessageSeq ?
|
const msgList = hasMessageSeq ?
|
||||||
(await this.core.apis.MsgApi.getMsgHistory(peer, startMsgId, MsgCount)).msgList : (await this.core.apis.MsgApi.getAioFirstViewLatestMsgs(peer, MsgCount)).msgList;
|
(await this.core.apis.MsgApi.getMsgHistory(peer, startMsgId, +payload.count)).msgList : (await this.core.apis.MsgApi.getAioFirstViewLatestMsgs(peer, +payload.count)).msgList;
|
||||||
if (msgList.length === 0) throw new Error(`消息${payload.message_seq}不存在`);
|
if (msgList.length === 0) throw new Error(`消息${payload.message_seq}不存在`);
|
||||||
//翻转消息
|
//翻转消息
|
||||||
if (isReverseOrder) msgList.reverse();
|
if (isReverseOrder) msgList.reverse();
|
||||||
|
@@ -1,15 +1,12 @@
|
|||||||
import { OneBotAction } from '@/onebot/action/OneBotAction';
|
import { OneBotAction } from '@/onebot/action/OneBotAction';
|
||||||
import { ActionName } from '@/onebot/action/router';
|
import { ActionName } from '@/onebot/action/router';
|
||||||
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
|
import { Static, Type } from '@sinclair/typebox';
|
||||||
|
|
||||||
const SchemaData = {
|
const SchemaData = Type.Object({
|
||||||
type: 'object',
|
group_id: Type.Union([Type.Number(), Type.String()])
|
||||||
properties: {
|
});
|
||||||
group_id: { type: ['number', 'string'] }
|
|
||||||
},
|
type Payload = Static<typeof SchemaData>;
|
||||||
required: ['group_id'],
|
|
||||||
} as const satisfies JSONSchema;
|
|
||||||
type Payload = FromSchema<typeof SchemaData>;
|
|
||||||
|
|
||||||
export class GoCQHTTPGetGroupAtAllRemain extends OneBotAction<Payload, any> {
|
export class GoCQHTTPGetGroupAtAllRemain extends OneBotAction<Payload, any> {
|
||||||
actionName = ActionName.GoCQHTTP_GetGroupAtAllRemain;
|
actionName = ActionName.GoCQHTTP_GetGroupAtAllRemain;
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user