mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2025-07-19 12:03:37 +00:00
Compare commits
101 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
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 | ||
![]() |
6ca1ac21e4 | ||
![]() |
381ee1c30e |
208
LICENSE
208
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.
|
||||||
|
|
||||||
"License" shall mean the terms and conditions for use, reproduction,
|
2. Redistribution:
|
||||||
and distribution as defined by Sections 1 through 9 of this document.
|
- 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.
|
||||||
|
|
||||||
"Licensor" shall mean the copyright owner or entity authorized by
|
3. Non-Commercial Use:
|
||||||
the copyright owner that is granting the License.
|
- This code is not to be used for any commercial purposes.
|
||||||
|
|
||||||
"Legal Entity" shall mean the union of the acting entity and all
|
4. Additional Permissions:
|
||||||
other entities that control, are controlled by, or are under common
|
- Any rights not explicitly addressed in this license must be requested from and granted by the main author of the NapCat repository.
|
||||||
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
|
5. Disclaimer:
|
||||||
exercising permissions granted by this License.
|
- 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.
|
||||||
|
|
||||||
"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.
@@ -4,7 +4,7 @@
|
|||||||
"name": "NapCatQQ",
|
"name": "NapCatQQ",
|
||||||
"slug": "NapCat.Framework",
|
"slug": "NapCat.Framework",
|
||||||
"description": "高性能的 OneBot 11 协议实现",
|
"description": "高性能的 OneBot 11 协议实现",
|
||||||
"version": "4.1.12",
|
"version": "4.2.5",
|
||||||
"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;
|
@@ -2,7 +2,7 @@
|
|||||||
"name": "napcat",
|
"name": "napcat",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "4.1.12",
|
"version": "4.2.5",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"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",
|
||||||
@@ -44,7 +44,7 @@
|
|||||||
"json-schema-to-ts": "^3.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"
|
||||||
@@ -52,9 +52,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"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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,22 +190,26 @@ 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);
|
// 兼容file:///
|
||||||
if (process.platform === 'win32') {
|
// file:///C:/1.jpg
|
||||||
filePath = pathname.slice(1);
|
if (uri.startsWith('file:///') && process.platform === 'win32') {
|
||||||
} else {
|
const filePath: string = uri.slice(8);
|
||||||
filePath = pathname;
|
return { Uri: filePath, Type: FileUriType.Local };
|
||||||
}
|
}
|
||||||
|
// 处理默认规范
|
||||||
|
// file://C:\1.jpg
|
||||||
|
// file:///test/1.jpg
|
||||||
|
const filePath: string = uri.slice(7);
|
||||||
|
|
||||||
return { Uri: filePath, Type: FileUriType.Local };
|
return { Uri: filePath, Type: FileUriType.Local };
|
||||||
}
|
}
|
||||||
@@ -230,14 +225,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 +246,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 +265,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 +1 @@
|
|||||||
export const napCatVersion = '4.1.12';
|
export const napCatVersion = '4.2.5';
|
||||||
|
@@ -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,7 +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;
|
const logger = this.core.context.logger;
|
||||||
let videoInfo = {
|
let videoInfo = {
|
||||||
width: 1920,
|
width: 1920,
|
||||||
@@ -306,18 +307,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++;
|
||||||
}
|
}
|
||||||
|
@@ -18,30 +18,19 @@ 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) {
|
// 默认获取自己的 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);
|
||||||
}
|
}
|
||||||
|
12
src/core/external/appid.json
vendored
12
src/core/external/appid.json
vendored
@@ -86,5 +86,17 @@
|
|||||||
"6.9.59-29456": {
|
"6.9.59-29456": {
|
||||||
"appid": 537249961,
|
"appid": 537249961,
|
||||||
"qua": "V1_MAC_NQ_6.9.59_29456_GW_B"
|
"qua": "V1_MAC_NQ_6.9.59_29456_GW_B"
|
||||||
|
},
|
||||||
|
"9.9.16-29927": {
|
||||||
|
"appid": 537255812,
|
||||||
|
"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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
20
src/core/external/offset.json
vendored
20
src/core/external/offset.json
vendored
@@ -82,5 +82,25 @@
|
|||||||
"6.9.59-29456-arm64": {
|
"6.9.59-29456-arm64": {
|
||||||
"send": "4005FE8",
|
"send": "4005FE8",
|
||||||
"recv": "4008800"
|
"recv": "4008800"
|
||||||
|
},
|
||||||
|
"9.9.16-29927-x64": {
|
||||||
|
"send": "3869C50",
|
||||||
|
"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;
|
||||||
|
}
|
@@ -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>[] {
|
||||||
|
@@ -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 & {
|
||||||
|
@@ -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 {
|
||||||
|
@@ -8,3 +8,5 @@ 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;
|
||||||
@@ -533,3 +554,15 @@ export interface MsgReqType {
|
|||||||
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,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 {
|
||||||
@@ -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,18 +1,31 @@
|
|||||||
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';
|
||||||
|
|
||||||
interface Payload {
|
const SchemaData = {
|
||||||
start: number,
|
type: 'object',
|
||||||
count: number
|
properties: {
|
||||||
}
|
user_id: { type: ['number', 'string'] },
|
||||||
|
start: { type: ['number', 'string'] },
|
||||||
|
count: { type: ['number', 'string'] },
|
||||||
|
type: { type: ['number', 'string'] },
|
||||||
|
},
|
||||||
|
} as const satisfies JSONSchema;
|
||||||
|
|
||||||
|
type Payload = FromSchema<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 start = payload.start ? Number(payload.start) : 0;
|
||||||
const count = payload.count ? Number(payload.count) : 10;
|
const count = payload.count ? Number(payload.count) : 10;
|
||||||
const ret = await this.core.apis.UserApi.getProfileLike(this.core.selfInfo.uid, start, count);
|
const type = payload.count ? Number(payload.count) : 2;
|
||||||
|
const user_uid =
|
||||||
|
this.core.selfInfo.uin === payload.user_id || !payload.user_id ?
|
||||||
|
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, start, count, 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 = parseInt((await this.core.apis.UserApi.getUinByUidV2(item.uid)) || '');
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
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 { FromSchema, JSONSchema } from 'json-schema-to-ts';
|
||||||
|
|
||||||
|
@@ -41,7 +41,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 {
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import SendMsg, { normalize } from '../msg/SendMsg';
|
import SendMsg, { normalize } from '@/onebot/action/msg/SendMsg';
|
||||||
import { OB11PostSendMsg } from '../../types';
|
import { OB11PostSendMsg } from '@/onebot/types';
|
||||||
import { ActionName } from '@/onebot/action/router';
|
import { ActionName } from '@/onebot/action/router';
|
||||||
|
|
||||||
// 未验证
|
// 未验证
|
||||||
|
@@ -5,7 +5,7 @@ import fs from 'fs';
|
|||||||
import { uri2local } from '@/common/file';
|
import { uri2local } from '@/common/file';
|
||||||
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
|
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
|
||||||
import { MessageContext } from '@/onebot/api';
|
import { MessageContext } from '@/onebot/api';
|
||||||
import { ContextMode, createContext } from '../msg/SendMsg';
|
import { ContextMode, createContext } from '@/onebot/action/msg/SendMsg';
|
||||||
|
|
||||||
const SchemaData = {
|
const SchemaData = {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import SendMsg, { ContextMode } from '../msg/SendMsg';
|
import SendMsg, { ContextMode } from '@/onebot/action/msg/SendMsg';
|
||||||
import { ActionName, BaseCheckResult } from '../router';
|
import { ActionName, BaseCheckResult } from '@/onebot/action/router';
|
||||||
import { OB11PostSendMsg } from '../../types';
|
import { OB11PostSendMsg } from '@/onebot/types';
|
||||||
|
|
||||||
// 未检测参数
|
// 未检测参数
|
||||||
class SendGroupMsg extends SendMsg {
|
class SendGroupMsg extends SendMsg {
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import SendMsg, { ContextMode } from './SendMsg';
|
import SendMsg, { ContextMode } from './SendMsg';
|
||||||
import { ActionName, BaseCheckResult } from '../router';
|
import { ActionName, BaseCheckResult } from '@/onebot/action/router';
|
||||||
import { OB11PostSendMsg } from '../../types';
|
import { OB11PostSendMsg } from '@/onebot/types';
|
||||||
|
|
||||||
// 未检测参数
|
// 未检测参数
|
||||||
class SendPrivateMsg extends SendMsg {
|
class SendPrivateMsg extends SendMsg {
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { OneBotAction } from '@/onebot/action/OneBotAction';
|
import { OneBotAction } from '@/onebot/action/OneBotAction';
|
||||||
import { ActionName, BaseCheckResult } from '../router';
|
import { ActionName, BaseCheckResult } from '@/onebot/action/router';
|
||||||
|
|
||||||
|
|
||||||
export abstract class GetPacketStatusDepends<PT, RT> extends OneBotAction<PT, RT> {
|
export abstract class GetPacketStatusDepends<PT, RT> extends OneBotAction<PT, RT> {
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
import { GrayTipElement, NapCatCore } from '@/core';
|
import { GrayTipElement, NapCatCore } from '@/core';
|
||||||
|
|
||||||
import { NapCatOneBot11Adapter } from '@/onebot';
|
import { NapCatOneBot11Adapter } from '@/onebot';
|
||||||
import { OB11FriendPokeEvent } from '../event/notice/OB11PokeEvent';
|
import { OB11FriendPokeEvent } from '@/onebot/event/notice/OB11PokeEvent';
|
||||||
|
|
||||||
export class OneBotFriendApi {
|
export class OneBotFriendApi {
|
||||||
obContext: NapCatOneBot11Adapter;
|
obContext: NapCatOneBot11Adapter;
|
||||||
|
@@ -1,26 +1,31 @@
|
|||||||
import {
|
import {
|
||||||
ChatType,
|
ChatType,
|
||||||
|
FileElement,
|
||||||
GrayTipElement,
|
GrayTipElement,
|
||||||
|
InstanceContext,
|
||||||
|
JsonGrayBusiId,
|
||||||
|
MessageElement,
|
||||||
NapCatCore,
|
NapCatCore,
|
||||||
NTGrayTipElementSubTypeV2,
|
NTGrayTipElementSubTypeV2,
|
||||||
|
NTMsgType,
|
||||||
RawMessage,
|
RawMessage,
|
||||||
|
TipGroupElement,
|
||||||
TipGroupElementType,
|
TipGroupElementType,
|
||||||
} from '@/core';
|
} from '@/core';
|
||||||
import { NapCatOneBot11Adapter } from '@/onebot';
|
import { NapCatOneBot11Adapter } from '@/onebot';
|
||||||
import { OB11GroupBanEvent } from '../event/notice/OB11GroupBanEvent';
|
import { OB11GroupBanEvent } from '@/onebot/event/notice/OB11GroupBanEvent';
|
||||||
import { OB11GroupIncreaseEvent } from '../event/notice/OB11GroupIncreaseEvent';
|
import { OB11GroupIncreaseEvent } from '@/onebot/event/notice/OB11GroupIncreaseEvent';
|
||||||
import { OB11GroupDecreaseEvent } from '../event/notice/OB11GroupDecreaseEvent';
|
import { OB11GroupDecreaseEvent } from '@/onebot/event/notice/OB11GroupDecreaseEvent';
|
||||||
import fastXmlParser from 'fast-xml-parser';
|
import fastXmlParser from 'fast-xml-parser';
|
||||||
import { OB11GroupMsgEmojiLikeEvent } from '../event/notice/OB11MsgEmojiLikeEvent';
|
import { OB11GroupMsgEmojiLikeEvent } from '@/onebot/event/notice/OB11MsgEmojiLikeEvent';
|
||||||
import { MessageUnique } from '@/common/message-unique';
|
import { MessageUnique } from '@/common/message-unique';
|
||||||
import { OB11GroupCardEvent } from '@/onebot/event/notice/OB11GroupCardEvent';
|
import { OB11GroupCardEvent } from '@/onebot/event/notice/OB11GroupCardEvent';
|
||||||
import { OB11GroupUploadNoticeEvent } from '@/onebot/event/notice/OB11GroupUploadNoticeEvent';
|
|
||||||
import { OB11GroupPokeEvent } from '@/onebot/event/notice/OB11PokeEvent';
|
import { OB11GroupPokeEvent } from '@/onebot/event/notice/OB11PokeEvent';
|
||||||
import { OB11GroupEssenceEvent } from '@/onebot/event/notice/OB11GroupEssenceEvent';
|
import { OB11GroupEssenceEvent } from '@/onebot/event/notice/OB11GroupEssenceEvent';
|
||||||
import { OB11GroupTitleEvent } from '@/onebot/event/notice/OB11GroupTitleEvent';
|
import { OB11GroupTitleEvent } from '@/onebot/event/notice/OB11GroupTitleEvent';
|
||||||
import { FileNapCatOneBotUUID } from '@/common/helper';
|
import { OB11GroupUploadNoticeEvent } from '../event/notice/OB11GroupUploadNoticeEvent';
|
||||||
import { pathToFileURL } from 'node:url';
|
import { pathToFileURL } from 'node:url';
|
||||||
|
import { FileNapCatOneBotUUID } from '@/common/helper';
|
||||||
|
|
||||||
export class OneBotGroupApi {
|
export class OneBotGroupApi {
|
||||||
obContext: NapCatOneBot11Adapter;
|
obContext: NapCatOneBot11Adapter;
|
||||||
@@ -31,137 +36,6 @@ export class OneBotGroupApi {
|
|||||||
this.core = core;
|
this.core = core;
|
||||||
}
|
}
|
||||||
|
|
||||||
async parseGroupEvent(msg: RawMessage) {
|
|
||||||
const logger = this.core.context.logger;
|
|
||||||
if (msg.chatType !== ChatType.KCHATTYPEGROUP) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
//log("group msg", msg);
|
|
||||||
if (msg.senderUin && msg.senderUin !== '0') {
|
|
||||||
const member = await this.core.apis.GroupApi.getGroupMember(msg.peerUid, msg.senderUin);
|
|
||||||
if (member && member.cardName !== msg.sendMemberName) {
|
|
||||||
const newCardName = msg.sendMemberName ?? '';
|
|
||||||
const event = new OB11GroupCardEvent(this.core, parseInt(msg.peerUid), parseInt(msg.senderUin), newCardName, member.cardName);
|
|
||||||
member.cardName = newCardName;
|
|
||||||
return event;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const element of msg.elements) {
|
|
||||||
if (element.grayTipElement?.groupElement) {
|
|
||||||
const groupElement = element.grayTipElement.groupElement;
|
|
||||||
if (groupElement.type == TipGroupElementType.KMEMBERADD) {
|
|
||||||
const MemberIncreaseEvent = await this.obContext.apis.GroupApi.parseGroupMemberIncreaseEvent(msg.peerUid, element.grayTipElement);
|
|
||||||
if (MemberIncreaseEvent) return MemberIncreaseEvent;
|
|
||||||
} else if (groupElement.type === TipGroupElementType.KSHUTUP) {
|
|
||||||
const BanEvent = await this.obContext.apis.GroupApi.parseGroupBanEvent(msg.peerUid, element.grayTipElement);
|
|
||||||
if (BanEvent) return BanEvent;
|
|
||||||
} else if (groupElement.type == TipGroupElementType.KQUITTE) {
|
|
||||||
this.core.apis.GroupApi.quitGroup(msg.peerUid).then();
|
|
||||||
try {
|
|
||||||
const KickEvent = await this.obContext.apis.GroupApi.parseGroupKickEvent(msg.peerUid, element.grayTipElement);
|
|
||||||
if (KickEvent) return KickEvent;
|
|
||||||
} catch (e) {
|
|
||||||
return new OB11GroupDecreaseEvent(
|
|
||||||
this.core,
|
|
||||||
parseInt(msg.peerUid),
|
|
||||||
parseInt(this.core.selfInfo.uin),
|
|
||||||
0,
|
|
||||||
'leave',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (element.fileElement) {
|
|
||||||
return new OB11GroupUploadNoticeEvent(
|
|
||||||
this.core,
|
|
||||||
parseInt(msg.peerUid), parseInt(msg.senderUin || ''),
|
|
||||||
{
|
|
||||||
id: FileNapCatOneBotUUID.encode({
|
|
||||||
chatType: ChatType.KCHATTYPEGROUP,
|
|
||||||
peerUid: msg.peerUid,
|
|
||||||
}, msg.msgId, element.elementId, element.fileElement.fileUuid, "." + element.fileElement.fileName),
|
|
||||||
url: pathToFileURL(element.fileElement.filePath).href,
|
|
||||||
name: element.fileElement.fileName,
|
|
||||||
size: parseInt(element.fileElement.fileSize),
|
|
||||||
busid: element.fileElement.fileBizId ?? 0,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (element.grayTipElement) {
|
|
||||||
if (element.grayTipElement.xmlElement?.templId === '10382') {
|
|
||||||
const emojiLikeEvent = await this.obContext.apis.GroupApi.parseGroupEmojiLikeEventByGrayTip(msg.peerUid, element.grayTipElement);
|
|
||||||
if (emojiLikeEvent) return emojiLikeEvent;
|
|
||||||
}
|
|
||||||
if (element.grayTipElement.subElementType == NTGrayTipElementSubTypeV2.GRAYTIP_ELEMENT_SUBTYPE_XMLMSG) {
|
|
||||||
const GroupIncreaseEvent = await this.obContext.apis.GroupApi.parseGroupIncreaseEvent(msg.peerUid, element.grayTipElement);
|
|
||||||
if (GroupIncreaseEvent) return GroupIncreaseEvent;
|
|
||||||
}
|
|
||||||
|
|
||||||
else if (element.grayTipElement.subElementType == NTGrayTipElementSubTypeV2.GRAYTIP_ELEMENT_SUBTYPE_JSON) {
|
|
||||||
const json = JSON.parse(element.grayTipElement.jsonGrayTipElement.jsonStr);
|
|
||||||
if (element.grayTipElement.jsonGrayTipElement.busiId == 1061) {
|
|
||||||
//判断业务类型
|
|
||||||
//Poke事件
|
|
||||||
const pokedetail: any[] = json.items;
|
|
||||||
//筛选item带有uid的元素
|
|
||||||
const poke_uid = pokedetail.filter(item => item.uid);
|
|
||||||
if (poke_uid.length == 2) {
|
|
||||||
return new OB11GroupPokeEvent(
|
|
||||||
this.core,
|
|
||||||
parseInt(msg.peerUid),
|
|
||||||
+await this.core.apis.UserApi.getUinByUidV2(poke_uid[0].uid),
|
|
||||||
+await this.core.apis.UserApi.getUinByUidV2(poke_uid[1].uid),
|
|
||||||
pokedetail,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (element.grayTipElement.jsonGrayTipElement.busiId == 2401) {
|
|
||||||
const searchParams = new URL(json.items[0].jp).searchParams;
|
|
||||||
const msgSeq = searchParams.get('msgSeq')!;
|
|
||||||
const Group = searchParams.get('groupCode');
|
|
||||||
if (!Group) return;
|
|
||||||
// const businessId = searchParams.get('businessid');
|
|
||||||
const Peer = {
|
|
||||||
guildId: '',
|
|
||||||
chatType: ChatType.KCHATTYPEGROUP,
|
|
||||||
peerUid: Group,
|
|
||||||
};
|
|
||||||
const msgData = await this.core.apis.MsgApi.getMsgsBySeqAndCount(Peer, msgSeq.toString(), 1, true, true);
|
|
||||||
const msgList = (await this.core.apis.WebApi.getGroupEssenceMsgAll(Group)).flatMap((e) => e.data.msg_list);
|
|
||||||
const realMsg = msgList.find((e) => e.msg_seq.toString() == msgSeq);
|
|
||||||
return new OB11GroupEssenceEvent(
|
|
||||||
this.core,
|
|
||||||
parseInt(msg.peerUid),
|
|
||||||
MessageUnique.getShortIdByMsgId(msgData.msgList[0].msgId)!,
|
|
||||||
parseInt(msgData.msgList[0].senderUin),
|
|
||||||
parseInt(realMsg?.add_digest_uin ?? '0'),
|
|
||||||
);
|
|
||||||
// 获取MsgSeq+Peer可获取具体消息
|
|
||||||
}
|
|
||||||
if (element.grayTipElement.jsonGrayTipElement.busiId == 2407) {
|
|
||||||
const type = json.items[json.items.length - 1]?.txt;
|
|
||||||
if (type === "头衔") {
|
|
||||||
const memberUin = json.items[1].param[0];
|
|
||||||
const title = json.items[3].txt;
|
|
||||||
logger.logDebug('收到群成员新头衔消息', json);
|
|
||||||
return new OB11GroupTitleEvent(
|
|
||||||
this.core,
|
|
||||||
parseInt(msg.peerUid),
|
|
||||||
parseInt(memberUin),
|
|
||||||
title,
|
|
||||||
);
|
|
||||||
} else if (type === "移出") {
|
|
||||||
logger.logDebug('收到机器人被踢消息', json);
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
logger.logWarn('收到未知的灰条消息', json);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async parseGroupBanEvent(GroupCode: string, grayTipElement: GrayTipElement) {
|
async parseGroupBanEvent(GroupCode: string, grayTipElement: GrayTipElement) {
|
||||||
const groupElement = grayTipElement?.groupElement;
|
const groupElement = grayTipElement?.groupElement;
|
||||||
if (!groupElement?.shutUp) return undefined;
|
if (!groupElement?.shutUp) return undefined;
|
||||||
@@ -192,66 +66,66 @@ export class OneBotGroupApi {
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
async parseGroupIncreaseEvent(GroupCode: string, grayTipElement: GrayTipElement) {
|
// async parseGroupIncreaseEvent(GroupCode: string, grayTipElement: GrayTipElement) {
|
||||||
this.core.context.logger.logDebug('收到新人被邀请进群消息', grayTipElement);
|
// this.core.context.logger.logDebug('收到新人被邀请进群消息', grayTipElement);
|
||||||
const xmlElement = grayTipElement.xmlElement;
|
// const xmlElement = grayTipElement.xmlElement;
|
||||||
if (xmlElement?.content) {
|
// if (xmlElement?.content) {
|
||||||
const regex = /jp="(\d+)"/g;
|
// const regex = /jp="(\d+)"/g;
|
||||||
|
|
||||||
const matches = [];
|
// const matches = [];
|
||||||
let match = null;
|
// let match = null;
|
||||||
|
|
||||||
while ((match = regex.exec(xmlElement.content)) !== null) {
|
// while ((match = regex.exec(xmlElement.content)) !== null) {
|
||||||
matches.push(match[1]);
|
// matches.push(match[1]);
|
||||||
}
|
// }
|
||||||
if (matches.length === 2) {
|
// if (matches.length === 2) {
|
||||||
const [inviter, invitee] = matches;
|
// const [inviter, invitee] = matches;
|
||||||
return new OB11GroupIncreaseEvent(
|
// return new OB11GroupIncreaseEvent(
|
||||||
this.core,
|
// this.core,
|
||||||
parseInt(GroupCode),
|
// parseInt(GroupCode),
|
||||||
parseInt(invitee),
|
// parseInt(invitee),
|
||||||
parseInt(inviter),
|
// parseInt(inviter),
|
||||||
'invite',
|
// 'invite',
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
return undefined;
|
// return undefined;
|
||||||
}
|
// }
|
||||||
|
|
||||||
async parseGroupMemberIncreaseEvent(GroupCode: string, grayTipElement: GrayTipElement) {
|
// async parseGroupMemberIncreaseEvent(GroupCode: string, grayTipElement: GrayTipElement) {
|
||||||
const groupElement = grayTipElement?.groupElement;
|
// const groupElement = grayTipElement?.groupElement;
|
||||||
if (!groupElement) return undefined;
|
// if (!groupElement) return undefined;
|
||||||
const member = await this.core.apis.UserApi.getUserDetailInfo(groupElement.memberUid);
|
// const member = await this.core.apis.UserApi.getUserDetailInfo(groupElement.memberUid);
|
||||||
const memberUin = member?.uin;
|
// const memberUin = member?.uin;
|
||||||
const adminMember = await this.core.apis.GroupApi.getGroupMember(GroupCode, groupElement.adminUid);
|
// const adminMember = await this.core.apis.GroupApi.getGroupMember(GroupCode, groupElement.adminUid);
|
||||||
if (memberUin) {
|
// if (memberUin) {
|
||||||
const operatorUin = adminMember?.uin ?? memberUin;
|
// const operatorUin = adminMember?.uin ?? memberUin;
|
||||||
return new OB11GroupIncreaseEvent(
|
// return new OB11GroupIncreaseEvent(
|
||||||
this.core,
|
// this.core,
|
||||||
parseInt(GroupCode),
|
// parseInt(GroupCode),
|
||||||
parseInt(memberUin),
|
// parseInt(memberUin),
|
||||||
parseInt(operatorUin),
|
// parseInt(operatorUin),
|
||||||
);
|
// );
|
||||||
} else {
|
// } else {
|
||||||
return undefined;
|
// return undefined;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
async parseGroupKickEvent(GroupCode: string, grayTipElement: GrayTipElement) {
|
// async parseGroupKickEvent(GroupCode: string, grayTipElement: GrayTipElement) {
|
||||||
const groupElement = grayTipElement?.groupElement;
|
// const groupElement = grayTipElement?.groupElement;
|
||||||
if (!groupElement) return undefined;
|
// if (!groupElement) return undefined;
|
||||||
const adminUin = (await this.core.apis.GroupApi.getGroupMember(GroupCode, groupElement.adminUid))?.uin ?? (await this.core.apis.UserApi.getUidByUinV2(groupElement.adminUid));
|
// const adminUin = (await this.core.apis.GroupApi.getGroupMember(GroupCode, groupElement.adminUid))?.uin ?? (await this.core.apis.UserApi.getUidByUinV2(groupElement.adminUid));
|
||||||
if (adminUin) {
|
// if (adminUin) {
|
||||||
return new OB11GroupDecreaseEvent(
|
// return new OB11GroupDecreaseEvent(
|
||||||
this.core,
|
// this.core,
|
||||||
parseInt(GroupCode),
|
// parseInt(GroupCode),
|
||||||
parseInt(this.core.selfInfo.uin),
|
// parseInt(this.core.selfInfo.uin),
|
||||||
parseInt(adminUin),
|
// parseInt(adminUin),
|
||||||
'kick_me',
|
// 'kick_me',
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
return undefined;
|
// return undefined;
|
||||||
}
|
// }
|
||||||
|
|
||||||
async parseGroupEmojiLikeEventByGrayTip(
|
async parseGroupEmojiLikeEventByGrayTip(
|
||||||
groupCode: string,
|
groupCode: string,
|
||||||
@@ -299,4 +173,150 @@ export class OneBotGroupApi {
|
|||||||
}],
|
}],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async parseCardChangedEvent(msg: RawMessage) {
|
||||||
|
if (msg.senderUin && msg.senderUin !== '0') {
|
||||||
|
const member = await this.core.apis.GroupApi.getGroupMember(msg.peerUid, msg.senderUin);
|
||||||
|
if (member && member.cardName !== msg.sendMemberName) {
|
||||||
|
const newCardName = msg.sendMemberName ?? '';
|
||||||
|
const event = new OB11GroupCardEvent(this.core, parseInt(msg.peerUid), parseInt(msg.senderUin), newCardName, member.cardName);
|
||||||
|
member.cardName = newCardName;
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
// async parseGroupElement(msg: RawMessage, groupElement: TipGroupElement, elementWrapper: GrayTipElement) {
|
||||||
|
// if (groupElement.type == TipGroupElementType.KMEMBERADD) {
|
||||||
|
// const MemberIncreaseEvent = await this.obContext.apis.GroupApi.parseGroupMemberIncreaseEvent(msg.peerUid, elementWrapper);
|
||||||
|
// if (MemberIncreaseEvent) return MemberIncreaseEvent;
|
||||||
|
// } else if (groupElement.type === TipGroupElementType.KSHUTUP) {
|
||||||
|
// const BanEvent = await this.obContext.apis.GroupApi.parseGroupBanEvent(msg.peerUid, elementWrapper);
|
||||||
|
// if (BanEvent) return BanEvent;
|
||||||
|
// } else if (groupElement.type == TipGroupElementType.KQUITTE) {
|
||||||
|
// this.core.apis.GroupApi.quitGroup(msg.peerUid).then();
|
||||||
|
// try {
|
||||||
|
// const KickEvent = await this.obContext.apis.GroupApi.parseGroupKickEvent(msg.peerUid, elementWrapper);
|
||||||
|
// if (KickEvent) return KickEvent;
|
||||||
|
// } catch (e) {
|
||||||
|
// return new OB11GroupDecreaseEvent(
|
||||||
|
// this.core,
|
||||||
|
// parseInt(msg.peerUid),
|
||||||
|
// parseInt(this.core.selfInfo.uin),
|
||||||
|
// 0,
|
||||||
|
// 'leave',
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// return undefined;
|
||||||
|
// }
|
||||||
|
|
||||||
|
async parsePaiYiPai(msg: RawMessage, jsonStr: string) {
|
||||||
|
const json = JSON.parse(jsonStr);
|
||||||
|
|
||||||
|
//判断业务类型
|
||||||
|
//Poke事件
|
||||||
|
const pokedetail: any[] = json.items;
|
||||||
|
//筛选item带有uid的元素
|
||||||
|
const poke_uid = pokedetail.filter(item => item.uid);
|
||||||
|
if (poke_uid.length == 2) {
|
||||||
|
return new OB11GroupPokeEvent(
|
||||||
|
this.core,
|
||||||
|
parseInt(msg.peerUid),
|
||||||
|
+await this.core.apis.UserApi.getUinByUidV2(poke_uid[0].uid),
|
||||||
|
+await this.core.apis.UserApi.getUinByUidV2(poke_uid[1].uid),
|
||||||
|
pokedetail,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
async parseOtherJsonEvent(msg: RawMessage, jsonStr: string, context: InstanceContext) {
|
||||||
|
const json = JSON.parse(jsonStr);
|
||||||
|
const type = json.items[json.items.length - 1]?.txt;
|
||||||
|
if (type === "头衔") {
|
||||||
|
const memberUin = json.items[1].param[0];
|
||||||
|
const title = json.items[3].txt;
|
||||||
|
context.logger.logDebug('收到群成员新头衔消息', json);
|
||||||
|
return new OB11GroupTitleEvent(
|
||||||
|
this.core,
|
||||||
|
parseInt(msg.peerUid),
|
||||||
|
parseInt(memberUin),
|
||||||
|
title,
|
||||||
|
);
|
||||||
|
} else if (type === "移出") {
|
||||||
|
context.logger.logDebug('收到机器人被踢消息', json);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
context.logger.logWarn('收到未知的灰条消息', json);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async parseEssenceMsg(msg: RawMessage, jsonStr: string) {
|
||||||
|
const json = JSON.parse(jsonStr);
|
||||||
|
const searchParams = new URL(json.items[0].jp).searchParams;
|
||||||
|
const msgSeq = searchParams.get('msgSeq')!;
|
||||||
|
const Group = searchParams.get('groupCode');
|
||||||
|
if (!Group) return;
|
||||||
|
// const businessId = searchParams.get('businessid');
|
||||||
|
const Peer = {
|
||||||
|
guildId: '',
|
||||||
|
chatType: ChatType.KCHATTYPEGROUP,
|
||||||
|
peerUid: Group,
|
||||||
|
};
|
||||||
|
const msgData = await this.core.apis.MsgApi.getMsgsBySeqAndCount(Peer, msgSeq.toString(), 1, true, true);
|
||||||
|
const msgList = (await this.core.apis.WebApi.getGroupEssenceMsgAll(Group)).flatMap((e) => e.data.msg_list);
|
||||||
|
const realMsg = msgList.find((e) => e.msg_seq.toString() == msgSeq);
|
||||||
|
return new OB11GroupEssenceEvent(
|
||||||
|
this.core,
|
||||||
|
parseInt(msg.peerUid),
|
||||||
|
MessageUnique.getShortIdByMsgId(msgData.msgList[0].msgId)!,
|
||||||
|
parseInt(msgData.msgList[0].senderUin),
|
||||||
|
parseInt(realMsg?.add_digest_uin ?? '0'),
|
||||||
|
);
|
||||||
|
// 获取MsgSeq+Peer可获取具体消息
|
||||||
|
}
|
||||||
|
|
||||||
|
async parseGroupUploadFileEvene(msg: RawMessage, element: FileElement, elementWrapper: MessageElement) {
|
||||||
|
return new OB11GroupUploadNoticeEvent(
|
||||||
|
this.core,
|
||||||
|
parseInt(msg.peerUid), parseInt(msg.senderUin || ''),
|
||||||
|
{
|
||||||
|
id: FileNapCatOneBotUUID.encode({
|
||||||
|
chatType: ChatType.KCHATTYPEGROUP,
|
||||||
|
peerUid: msg.peerUid,
|
||||||
|
}, msg.msgId, elementWrapper.elementId, elementWrapper?.fileElement?.fileUuid, "." + element.fileName),
|
||||||
|
url: pathToFileURL(element.filePath).href,
|
||||||
|
name: element.fileName,
|
||||||
|
size: parseInt(element.fileSize),
|
||||||
|
busid: element.fileBizId ?? 0,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async parseGrayTipElement(msg: RawMessage, grayTipElement: GrayTipElement) {
|
||||||
|
if (grayTipElement.subElementType === NTGrayTipElementSubTypeV2.GRAYTIP_ELEMENT_SUBTYPE_GROUP) {
|
||||||
|
// 解析群组事件 由sysmsg解析
|
||||||
|
// return await this.parseGroupElement(msg, grayTipElement.groupElement, grayTipElement);
|
||||||
|
|
||||||
|
} else if (grayTipElement.subElementType === NTGrayTipElementSubTypeV2.GRAYTIP_ELEMENT_SUBTYPE_XMLMSG) {
|
||||||
|
// 筛选出表情回应 事件
|
||||||
|
if (grayTipElement.xmlElement?.templId === '10382') {
|
||||||
|
return await this.obContext.apis.GroupApi.parseGroupEmojiLikeEventByGrayTip(msg.peerUid, grayTipElement);
|
||||||
|
} else {
|
||||||
|
//return await this.obContext.apis.GroupApi.parseGroupIncreaseEvent(msg.peerUid, grayTipElement);
|
||||||
|
}
|
||||||
|
} else if (grayTipElement.subElementType == NTGrayTipElementSubTypeV2.GRAYTIP_ELEMENT_SUBTYPE_JSON) {
|
||||||
|
// 解析json事件
|
||||||
|
if (grayTipElement.jsonGrayTipElement.busiId == 1061) {
|
||||||
|
return await this.parsePaiYiPai(msg, grayTipElement.jsonGrayTipElement.jsonStr);
|
||||||
|
} else if (grayTipElement.jsonGrayTipElement.busiId == JsonGrayBusiId.AIO_GROUP_ESSENCE_MSG_TIP) {
|
||||||
|
return await this.parseEssenceMsg(msg, grayTipElement.jsonGrayTipElement.jsonStr);
|
||||||
|
} else {
|
||||||
|
return await this.parseOtherJsonEvent(msg, grayTipElement.jsonGrayTipElement.jsonStr, this.core.context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -7,7 +7,6 @@ import {
|
|||||||
CustomMusicSignPostData,
|
CustomMusicSignPostData,
|
||||||
ElementType,
|
ElementType,
|
||||||
FaceIndex,
|
FaceIndex,
|
||||||
FaceType,
|
|
||||||
IdMusicSignPostData,
|
IdMusicSignPostData,
|
||||||
MessageElement,
|
MessageElement,
|
||||||
NapCatCore,
|
NapCatCore,
|
||||||
@@ -16,9 +15,12 @@ import {
|
|||||||
RawMessage,
|
RawMessage,
|
||||||
SendMessageElement,
|
SendMessageElement,
|
||||||
SendTextElement,
|
SendTextElement,
|
||||||
|
BaseEmojiType,
|
||||||
|
FaceType,
|
||||||
|
GrayTipElement,
|
||||||
} from '@/core';
|
} from '@/core';
|
||||||
import faceConfig from '@/core/external/face_config.json';
|
import faceConfig from '@/core/external/face_config.json';
|
||||||
import { NapCatOneBot11Adapter, OB11Message, OB11MessageData, OB11MessageDataType, OB11MessageFileBase, } from '@/onebot';
|
import { NapCatOneBot11Adapter, OB11Message, OB11MessageData, OB11MessageDataType, OB11MessageFileBase, OB11MessageForward, } from '@/onebot';
|
||||||
import { OB11Construct } from '@/onebot/helper/data';
|
import { OB11Construct } from '@/onebot/helper/data';
|
||||||
import { EventType } from '@/onebot/event/OneBotEvent';
|
import { EventType } from '@/onebot/event/OneBotEvent';
|
||||||
import { encodeCQCode } from '@/onebot/helper/cqcode';
|
import { encodeCQCode } from '@/onebot/helper/cqcode';
|
||||||
@@ -30,27 +32,36 @@ import { OB11FriendAddNoticeEvent } from '@/onebot/event/notice/OB11FriendAddNot
|
|||||||
// import { decodeSysMessage } from '@/core/packet/proto/old/ProfileLike';
|
// import { decodeSysMessage } from '@/core/packet/proto/old/ProfileLike';
|
||||||
import { ForwardMsgBuilder } from "@/common/forward-msg-builder";
|
import { ForwardMsgBuilder } from "@/common/forward-msg-builder";
|
||||||
import { decodeSysMessage } from "@/core/helper/adaptDecoder";
|
import { decodeSysMessage } from "@/core/helper/adaptDecoder";
|
||||||
|
import { GroupChange, PushMsgBody } from "@/core/packet/transformer/proto";
|
||||||
|
import { NapProtoMsg } from '@napneko/nap-proto-core';
|
||||||
|
import { OB11GroupIncreaseEvent } from '../event/notice/OB11GroupIncreaseEvent';
|
||||||
|
import { OB11GroupDecreaseEvent, GroupDecreaseSubType } from '../event/notice/OB11GroupDecreaseEvent';
|
||||||
|
|
||||||
type RawToOb11Converters = {
|
type RawToOb11Converters = {
|
||||||
[Key in keyof MessageElement as Key extends `${string}Element` ? Key : never]: (
|
[Key in keyof MessageElement as Key extends `${string}Element` ? Key : never]: (
|
||||||
element: Exclude<MessageElement[Key], null | undefined>,
|
element: Exclude<MessageElement[Key], null | undefined>,
|
||||||
msg: RawMessage,
|
msg: RawMessage,
|
||||||
elementWrapper: MessageElement,
|
elementWrapper: MessageElement,
|
||||||
|
context: RecvMessageContext
|
||||||
) => PromiseLike<OB11MessageData | null>
|
) => PromiseLike<OB11MessageData | null>
|
||||||
}
|
}
|
||||||
|
|
||||||
type Ob11ToRawConverters = {
|
type Ob11ToRawConverters = {
|
||||||
[Key in OB11MessageDataType]: (
|
[Key in OB11MessageDataType]: (
|
||||||
sendMsg: Extract<OB11MessageData, { type: Key }>,
|
sendMsg: Extract<OB11MessageData, { type: Key }>,
|
||||||
context: MessageContext,
|
context: SendMessageContext,
|
||||||
) => Promise<SendMessageElement | undefined>
|
) => Promise<SendMessageElement | undefined>
|
||||||
}
|
}
|
||||||
|
|
||||||
export type MessageContext = {
|
export type SendMessageContext = {
|
||||||
deleteAfterSentFiles: string[],
|
deleteAfterSentFiles: string[],
|
||||||
peer: Peer
|
peer: Peer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type RecvMessageContext = {
|
||||||
|
parseMultMsg: boolean
|
||||||
|
}
|
||||||
|
|
||||||
function keyCanBeParsed(key: string, parser: RawToOb11Converters): key is keyof RawToOb11Converters {
|
function keyCanBeParsed(key: string, parser: RawToOb11Converters): key is keyof RawToOb11Converters {
|
||||||
return key in parser;
|
return key in parser;
|
||||||
}
|
}
|
||||||
@@ -107,6 +118,7 @@ export class OneBotMsgApi {
|
|||||||
return {
|
return {
|
||||||
type: OB11MessageDataType.image,
|
type: OB11MessageDataType.image,
|
||||||
data: {
|
data: {
|
||||||
|
pic_type: element.picType,
|
||||||
summary: element.summary,
|
summary: element.summary,
|
||||||
file: encodedFileId,
|
file: encodedFileId,
|
||||||
sub_type: element.picSubType,
|
sub_type: element.picSubType,
|
||||||
@@ -144,14 +156,14 @@ export class OneBotMsgApi {
|
|||||||
|
|
||||||
faceElement: async element => {
|
faceElement: async element => {
|
||||||
const faceIndex = element.faceIndex;
|
const faceIndex = element.faceIndex;
|
||||||
if (faceIndex === FaceIndex.dice) {
|
if (faceIndex === FaceIndex.DICE) {
|
||||||
return {
|
return {
|
||||||
type: OB11MessageDataType.dice,
|
type: OB11MessageDataType.dice,
|
||||||
data: {
|
data: {
|
||||||
result: element.resultId!,
|
result: element.resultId!,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
} else if (faceIndex === FaceIndex.rps) {
|
} else if (faceIndex === FaceIndex.RPS) {
|
||||||
return {
|
return {
|
||||||
type: OB11MessageDataType.rps,
|
type: OB11MessageDataType.rps,
|
||||||
data: {
|
data: {
|
||||||
@@ -337,42 +349,26 @@ export class OneBotMsgApi {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
multiForwardMsgElement: async (_, msg) => {
|
multiForwardMsgElement: async (_, msg, wrapper, context) => {
|
||||||
// const message_data: OB11MessageForward = {
|
|
||||||
// data: {} as any,
|
|
||||||
// type: OB11MessageDataType.forward,
|
|
||||||
// };
|
|
||||||
// message_data.data.id = msg.msgId;
|
|
||||||
const parentMsgPeer = msg.parentMsgPeer ?? {
|
const parentMsgPeer = msg.parentMsgPeer ?? {
|
||||||
chatType: msg.chatType,
|
chatType: msg.chatType,
|
||||||
guildId: '',
|
guildId: '',
|
||||||
peerUid: msg.peerUid,
|
peerUid: msg.peerUid,
|
||||||
};
|
};
|
||||||
//判断是否在合并消息内
|
const multiMsgs = await this.getMultiMessages(msg, parentMsgPeer);
|
||||||
msg.parentMsgIdList = msg.parentMsgIdList ?? [];
|
// 拉取失败则跳过
|
||||||
//首次列表不存在则开始创建
|
|
||||||
msg.parentMsgIdList.push(msg.msgId);
|
|
||||||
//let parentMsgId = msg.parentMsgIdList[msg.parentMsgIdList.length - 2 < 0 ? 0 : msg.parentMsgIdList.length - 2];
|
|
||||||
//加入自身MsgId
|
|
||||||
const multiMsgs = (await this.core.apis.MsgApi.getMultiMsg(parentMsgPeer, msg.parentMsgIdList[0], msg.msgId))?.msgList;
|
|
||||||
//拉取下级消息
|
|
||||||
if (!multiMsgs) return null;
|
if (!multiMsgs) return null;
|
||||||
//拉取失败则跳过
|
const forward: OB11MessageForward = {
|
||||||
|
|
||||||
return {
|
|
||||||
type: OB11MessageDataType.forward,
|
type: OB11MessageDataType.forward,
|
||||||
data: {
|
data: { id: msg.msgId }
|
||||||
id: msg.msgId,
|
|
||||||
content: (await Promise.all(multiMsgs.map(
|
|
||||||
async multiMsgItem => {
|
|
||||||
multiMsgItem.parentMsgPeer = parentMsgPeer;
|
|
||||||
multiMsgItem.parentMsgIdList = msg.parentMsgIdList;
|
|
||||||
multiMsgItem.id = MessageUnique.createUniqueMsgId(parentMsgPeer, multiMsgItem.msgId); //该ID仅用查看 无法调用
|
|
||||||
return await this.parseMessage(multiMsgItem, 'array');
|
|
||||||
},
|
|
||||||
))).filter(item => item !== undefined),
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
if (!context.parseMultMsg) return forward;
|
||||||
|
forward.data.content = await this.parseMultiMessageContent(
|
||||||
|
multiMsgs,
|
||||||
|
parentMsgPeer,
|
||||||
|
msg.parentMsgIdList
|
||||||
|
);
|
||||||
|
return forward;
|
||||||
},
|
},
|
||||||
|
|
||||||
arkElement: async (element) => {
|
arkElement: async (element) => {
|
||||||
@@ -546,8 +542,8 @@ export class OneBotMsgApi {
|
|||||||
elementType: ElementType.FACE,
|
elementType: ElementType.FACE,
|
||||||
elementId: '',
|
elementId: '',
|
||||||
faceElement: {
|
faceElement: {
|
||||||
faceIndex: FaceIndex.dice,
|
faceIndex: FaceIndex.DICE,
|
||||||
faceType: FaceType.dice,
|
faceType: FaceType.AniSticke,
|
||||||
faceText: '[骰子]',
|
faceText: '[骰子]',
|
||||||
packId: '1',
|
packId: '1',
|
||||||
stickerId: '33',
|
stickerId: '33',
|
||||||
@@ -562,9 +558,9 @@ export class OneBotMsgApi {
|
|||||||
elementType: ElementType.FACE,
|
elementType: ElementType.FACE,
|
||||||
elementId: '',
|
elementId: '',
|
||||||
faceElement: {
|
faceElement: {
|
||||||
faceIndex: FaceIndex.rps,
|
faceIndex: FaceIndex.RPS,
|
||||||
faceText: '[包剪锤]',
|
faceText: '[包剪锤]',
|
||||||
faceType: 3,
|
faceType: FaceType.AniSticke,
|
||||||
packId: '1',
|
packId: '1',
|
||||||
stickerId: '34',
|
stickerId: '34',
|
||||||
sourceType: 1,
|
sourceType: 1,
|
||||||
@@ -673,41 +669,89 @@ export class OneBotMsgApi {
|
|||||||
this.core = core;
|
this.core = core;
|
||||||
}
|
}
|
||||||
|
|
||||||
async parsePrivateMsgEvent(msg: RawMessage) {
|
async parsePrivateMsgEvent(msg: RawMessage, grayTipElement: GrayTipElement) {
|
||||||
if (msg.chatType !== ChatType.KCHATTYPEC2C) {
|
if (grayTipElement.subElementType == NTGrayTipElementSubTypeV2.GRAYTIP_ELEMENT_SUBTYPE_JSON) {
|
||||||
return;
|
if (grayTipElement.jsonGrayTipElement.busiId == 1061) {
|
||||||
}
|
const PokeEvent = await this.obContext.apis.FriendApi.parsePrivatePokeEvent(grayTipElement);
|
||||||
for (const element of msg.elements) {
|
if (PokeEvent) { return PokeEvent; };
|
||||||
if (element.grayTipElement && element.grayTipElement.subElementType == NTGrayTipElementSubTypeV2.GRAYTIP_ELEMENT_SUBTYPE_JSON) {
|
} else if (grayTipElement.jsonGrayTipElement.busiId == 19324 && msg.peerUid !== '') {
|
||||||
if (element.grayTipElement.jsonGrayTipElement.busiId == 1061) {
|
return new OB11FriendAddNoticeEvent(this.core, Number(await this.core.apis.UserApi.getUinByUidV2(msg.peerUid)));
|
||||||
const PokeEvent = await this.obContext.apis.FriendApi.parsePrivatePokeEvent(element.grayTipElement);
|
|
||||||
if (PokeEvent) return PokeEvent;
|
|
||||||
}
|
|
||||||
//好友添加成功事件
|
|
||||||
if (element.grayTipElement.jsonGrayTipElement.busiId == 19324 && msg.peerUid !== '') {
|
|
||||||
return new OB11FriendAddNoticeEvent(this.core, Number(await this.core.apis.UserApi.getUinByUidV2(msg.peerUid)));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async getMultiMessages(msg: RawMessage, parentMsgPeer: Peer) {
|
||||||
|
//判断是否在合并消息内
|
||||||
|
msg.parentMsgIdList = msg.parentMsgIdList ?? [];
|
||||||
|
//首次列表不存在则开始创建
|
||||||
|
msg.parentMsgIdList.push(msg.msgId);
|
||||||
|
//拉取下级消息
|
||||||
|
return (await this.core.apis.MsgApi.getMultiMsg(
|
||||||
|
parentMsgPeer,
|
||||||
|
msg.parentMsgIdList[0],
|
||||||
|
msg.msgId
|
||||||
|
))?.msgList;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async parseMultiMessageContent(
|
||||||
|
multiMsgs: RawMessage[],
|
||||||
|
parentMsgPeer: Peer,
|
||||||
|
parentMsgIdList: string[]
|
||||||
|
) {
|
||||||
|
const parsed = await Promise.all(multiMsgs.map(async msg => {
|
||||||
|
msg.parentMsgPeer = parentMsgPeer;
|
||||||
|
msg.parentMsgIdList = parentMsgIdList;
|
||||||
|
msg.id = MessageUnique.createUniqueMsgId(parentMsgPeer, msg.msgId);
|
||||||
|
//该ID仅用查看 无法调用
|
||||||
|
return await this.parseMessage(msg, 'array', true);
|
||||||
|
}));
|
||||||
|
return parsed.filter(item => item !== undefined);
|
||||||
|
}
|
||||||
|
|
||||||
async parseMessage(
|
async parseMessage(
|
||||||
msg: RawMessage,
|
msg: RawMessage,
|
||||||
messagePostFormat: string,
|
messagePostFormat: string,
|
||||||
|
parseMultMsg: boolean = true
|
||||||
) {
|
) {
|
||||||
if (messagePostFormat === 'string') {
|
if (messagePostFormat === 'string') {
|
||||||
return (await this.parseMessageV2(msg))?.stringMsg;
|
return (await this.parseMessageV2(msg, parseMultMsg))?.stringMsg;
|
||||||
}
|
}
|
||||||
return (await this.parseMessageV2(msg))?.arrayMsg;
|
return (await this.parseMessageV2(msg, parseMultMsg))?.arrayMsg;
|
||||||
}
|
}
|
||||||
|
|
||||||
async parseMessageV2(
|
async parseMessageV2(
|
||||||
msg: RawMessage,
|
msg: RawMessage,
|
||||||
|
parseMultMsg: boolean = true
|
||||||
) {
|
) {
|
||||||
if (msg.senderUin == '0' || msg.senderUin == '') return;
|
if (msg.senderUin == '0' || msg.senderUin == '') return;
|
||||||
if (msg.peerUin == '0' || msg.peerUin == '') return;
|
if (msg.peerUin == '0' || msg.peerUin == '') return;
|
||||||
//跳过空消息
|
|
||||||
const resMsg: OB11Message = {
|
const resMsg = this.initializeMessage(msg);
|
||||||
|
|
||||||
|
if (this.core.selfInfo.uin == msg.senderUin) {
|
||||||
|
resMsg.message_sent_type = 'self';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (msg.chatType == ChatType.KCHATTYPEGROUP) {
|
||||||
|
await this.handleGroupMessage(resMsg, msg);
|
||||||
|
} else if (msg.chatType == ChatType.KCHATTYPEC2C) {
|
||||||
|
await this.handlePrivateMessage(resMsg, msg);
|
||||||
|
} else if (msg.chatType == ChatType.KCHATTYPETEMPC2CFROMGROUP) {
|
||||||
|
await this.handleTempGroupMessage(resMsg, msg);
|
||||||
|
} else {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const validSegments = await this.parseMessageSegments(msg, parseMultMsg);
|
||||||
|
resMsg.message = validSegments;
|
||||||
|
resMsg.raw_message = validSegments.map(msg => encodeCQCode(msg)).join('').trim();
|
||||||
|
|
||||||
|
const stringMsg = await this.convertArrayToStringMessage(resMsg);
|
||||||
|
return { stringMsg, arrayMsg: resMsg };
|
||||||
|
}
|
||||||
|
|
||||||
|
private initializeMessage(msg: RawMessage): OB11Message {
|
||||||
|
return {
|
||||||
self_id: parseInt(this.core.selfInfo.uin),
|
self_id: parseInt(this.core.selfInfo.uin),
|
||||||
user_id: parseInt(msg.senderUin),
|
user_id: parseInt(msg.senderUin),
|
||||||
time: parseInt(msg.msgTime) || Date.now(),
|
time: parseInt(msg.msgTime) || Date.now(),
|
||||||
@@ -727,37 +771,40 @@ export class OneBotMsgApi {
|
|||||||
message_format: 'array',
|
message_format: 'array',
|
||||||
post_type: this.core.selfInfo.uin == msg.senderUin ? EventType.MESSAGE_SENT : EventType.MESSAGE,
|
post_type: this.core.selfInfo.uin == msg.senderUin ? EventType.MESSAGE_SENT : EventType.MESSAGE,
|
||||||
};
|
};
|
||||||
if (this.core.selfInfo.uin == msg.senderUin) {
|
}
|
||||||
resMsg.message_sent_type = 'self';
|
|
||||||
}
|
|
||||||
if (msg.chatType == ChatType.KCHATTYPEGROUP) {
|
|
||||||
resMsg.sub_type = 'normal'; // 这里go-cqhttp是group,而onebot11标准是normal, 蛋疼
|
|
||||||
resMsg.group_id = parseInt(msg.peerUin);
|
|
||||||
let member = await this.core.apis.GroupApi.getGroupMember(msg.peerUin, msg.senderUin);
|
|
||||||
if (!member) member = await this.core.apis.GroupApi.getGroupMember(msg.peerUin, msg.senderUin);
|
|
||||||
if (member) {
|
|
||||||
resMsg.sender.role = OB11Construct.groupMemberRole(member.role);
|
|
||||||
resMsg.sender.nickname = member.nick;
|
|
||||||
}
|
|
||||||
} else if (msg.chatType == ChatType.KCHATTYPEC2C) {
|
|
||||||
resMsg.sub_type = 'friend';
|
|
||||||
resMsg.sender.nickname = (await this.core.apis.UserApi.getUserDetailInfo(msg.senderUid)).nick;
|
|
||||||
} else if (msg.chatType == ChatType.KCHATTYPETEMPC2CFROMGROUP) {
|
|
||||||
resMsg.sub_type = 'group';
|
|
||||||
const ret = await this.core.apis.MsgApi.getTempChatInfo(ChatType.KCHATTYPETEMPC2CFROMGROUP, msg.senderUid);
|
|
||||||
if (ret.result === 0) {
|
|
||||||
const member = await this.core.apis.GroupApi.getGroupMember(msg.peerUin, msg.senderUin);
|
|
||||||
resMsg.group_id = parseInt(ret.tmpChatInfo!.groupCode);
|
|
||||||
resMsg.sender.nickname = member?.nick ?? member?.cardName ?? '临时会话';
|
|
||||||
resMsg.temp_source = resMsg.group_id;
|
|
||||||
} else {
|
|
||||||
resMsg.group_id = 284840486; //兜底数据
|
|
||||||
resMsg.temp_source = resMsg.group_id;
|
|
||||||
resMsg.sender.nickname = '临时会话';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理消息段
|
private async handleGroupMessage(resMsg: OB11Message, msg: RawMessage) {
|
||||||
|
resMsg.sub_type = 'normal';
|
||||||
|
resMsg.group_id = parseInt(msg.peerUin);
|
||||||
|
let member = await this.core.apis.GroupApi.getGroupMember(msg.peerUin, msg.senderUin);
|
||||||
|
if (!member) member = await this.core.apis.GroupApi.getGroupMember(msg.peerUin, msg.senderUin);
|
||||||
|
if (member) {
|
||||||
|
resMsg.sender.role = OB11Construct.groupMemberRole(member.role);
|
||||||
|
resMsg.sender.nickname = member.nick;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async handlePrivateMessage(resMsg: OB11Message, msg: RawMessage) {
|
||||||
|
resMsg.sub_type = 'friend';
|
||||||
|
resMsg.sender.nickname = (await this.core.apis.UserApi.getUserDetailInfo(msg.senderUid)).nick;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async handleTempGroupMessage(resMsg: OB11Message, msg: RawMessage) {
|
||||||
|
resMsg.sub_type = 'group';
|
||||||
|
const ret = await this.core.apis.MsgApi.getTempChatInfo(ChatType.KCHATTYPETEMPC2CFROMGROUP, msg.senderUid);
|
||||||
|
if (ret.result === 0) {
|
||||||
|
const member = await this.core.apis.GroupApi.getGroupMember(msg.peerUin, msg.senderUin);
|
||||||
|
resMsg.group_id = parseInt(ret.tmpChatInfo!.groupCode);
|
||||||
|
resMsg.sender.nickname = member?.nick ?? member?.cardName ?? '临时会话';
|
||||||
|
resMsg.temp_source = resMsg.group_id;
|
||||||
|
} else {
|
||||||
|
resMsg.group_id = 284840486;
|
||||||
|
resMsg.temp_source = resMsg.group_id;
|
||||||
|
resMsg.sender.nickname = '临时会话';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async parseMessageSegments(msg: RawMessage, parseMultMsg: boolean): Promise<OB11MessageData[]> {
|
||||||
const msgSegments = await Promise.allSettled(msg.elements.map(
|
const msgSegments = await Promise.allSettled(msg.elements.map(
|
||||||
async (element) => {
|
async (element) => {
|
||||||
for (const key in element) {
|
for (const key in element) {
|
||||||
@@ -766,45 +813,47 @@ export class OneBotMsgApi {
|
|||||||
element: Exclude<MessageElement[keyof RawToOb11Converters], null | undefined>,
|
element: Exclude<MessageElement[keyof RawToOb11Converters], null | undefined>,
|
||||||
msg: RawMessage,
|
msg: RawMessage,
|
||||||
elementWrapper: MessageElement,
|
elementWrapper: MessageElement,
|
||||||
|
context: RecvMessageContext
|
||||||
) => PromiseLike<OB11MessageData | null>;
|
) => PromiseLike<OB11MessageData | null>;
|
||||||
const parsedElement = await converters?.(
|
const parsedElement = await converters?.(
|
||||||
element[key],
|
element[key],
|
||||||
msg,
|
msg,
|
||||||
element,
|
element,
|
||||||
|
{ parseMultMsg }
|
||||||
);
|
);
|
||||||
// 对于 face 类型的消息,检查是否存在
|
|
||||||
if (key === 'faceElement' && !parsedElement) {
|
if (key === 'faceElement' && !parsedElement) {
|
||||||
return null; // 如果没有找到对应的表情,返回 null
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return parsedElement;
|
return parsedElement;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
|
|
||||||
// 过滤掉无效的消息段
|
return msgSegments.filter(entry => {
|
||||||
const validSegments = msgSegments.filter(entry => {
|
|
||||||
if (entry.status === 'fulfilled') {
|
if (entry.status === 'fulfilled') {
|
||||||
return !!entry.value;
|
return !!entry.value;
|
||||||
} else {
|
} else {
|
||||||
this.core.context.logger.logError.bind(this.core.context.logger)('消息段解析失败', entry.reason);
|
this.core.context.logger.logError('消息段解析失败', entry.reason);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}).map((entry) => (<PromiseFulfilledResult<OB11MessageData>>entry).value).filter(value => value != null);
|
}).map((entry) => (<PromiseFulfilledResult<OB11MessageData>>entry).value).filter(value => value != null);
|
||||||
|
|
||||||
const msgAsCQCode = validSegments.map(msg => encodeCQCode(msg)).join('').trim();
|
|
||||||
resMsg.message = validSegments;
|
|
||||||
resMsg.raw_message = msgAsCQCode;
|
|
||||||
let stringMsg = structuredClone(resMsg);
|
|
||||||
stringMsg = await this.importArrayTostringMsg(stringMsg);
|
|
||||||
return { stringMsg: stringMsg, arrayMsg: resMsg };
|
|
||||||
}
|
}
|
||||||
async importArrayTostringMsg(msg: OB11Message) {
|
|
||||||
|
private async convertArrayToStringMessage(originMsg: OB11Message): Promise<OB11Message> {
|
||||||
|
const msg = structuredClone(originMsg);
|
||||||
msg.message_format = 'string';
|
msg.message_format = 'string';
|
||||||
msg.message = msg.raw_message;
|
msg.message = msg.raw_message;
|
||||||
return msg;
|
return msg;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async importArrayTostringMsg(originMsg: OB11Message) {
|
||||||
|
const msg = structuredClone(originMsg);
|
||||||
|
msg.message_format = 'string';
|
||||||
|
msg.message = msg.raw_message;
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
|
||||||
async createSendElements(
|
async createSendElements(
|
||||||
messageData: OB11MessageData[],
|
messageData: OB11MessageData[],
|
||||||
peer: Peer,
|
peer: Peer,
|
||||||
@@ -818,7 +867,7 @@ export class OneBotMsgApi {
|
|||||||
}
|
}
|
||||||
const converter = this.ob11ToRawConverters[sendMsg.type] as (
|
const converter = this.ob11ToRawConverters[sendMsg.type] as (
|
||||||
sendMsg: Extract<OB11MessageData, { type: OB11MessageData['type'] }>,
|
sendMsg: Extract<OB11MessageData, { type: OB11MessageData['type'] }>,
|
||||||
context: MessageContext,
|
context: SendMessageContext,
|
||||||
) => Promise<SendMessageElement | undefined>;
|
) => Promise<SendMessageElement | undefined>;
|
||||||
const callResult = converter(
|
const callResult = converter(
|
||||||
sendMsg,
|
sendMsg,
|
||||||
@@ -867,17 +916,25 @@ export class OneBotMsgApi {
|
|||||||
guildId: '',
|
guildId: '',
|
||||||
peerUid: peer.peerUid,
|
peerUid: peer.peerUid,
|
||||||
}, returnMsg.msgId);
|
}, returnMsg.msgId);
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
deleteAfterSentFiles.forEach(file => {
|
deleteAfterSentFiles.forEach(file => {
|
||||||
fsPromise.unlink(file).then().catch(e => this.core.context.logger.logError.bind(this.core.context.logger)('发送消息删除文件失败', e));
|
try {
|
||||||
|
if (fs.existsSync(file)) {
|
||||||
|
fsPromise.unlink(file).then().catch(e => this.core.context.logger.logError.bind(this.core.context.logger)('发送消息删除文件失败', e));
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
this.core.context.logger.logError.bind(this.core.context.logger)('发送消息删除文件失败', (error as Error).message);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}, 60000);
|
}, 60000);
|
||||||
|
|
||||||
return returnMsg;
|
return returnMsg;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async handleOb11FileLikeMessage(
|
private async handleOb11FileLikeMessage(
|
||||||
{ data: inputdata }: OB11MessageFileBase,
|
{ data: inputdata }: OB11MessageFileBase,
|
||||||
{ deleteAfterSentFiles }: MessageContext,
|
{ deleteAfterSentFiles }: SendMessageContext,
|
||||||
) {
|
) {
|
||||||
const realUri = inputdata.url || inputdata.file || inputdata.path || '';
|
const realUri = inputdata.url || inputdata.file || inputdata.path || '';
|
||||||
if (realUri.length === 0) {
|
if (realUri.length === 0) {
|
||||||
@@ -900,17 +957,45 @@ export class OneBotMsgApi {
|
|||||||
|
|
||||||
return { path, fileName: inputdata.name ?? fileName };
|
return { path, fileName: inputdata.name ?? fileName };
|
||||||
}
|
}
|
||||||
|
groupChangDecreseType2String(type: number): GroupDecreaseSubType {
|
||||||
|
switch (type) {
|
||||||
|
case 130:
|
||||||
|
return 'leave';
|
||||||
|
case 131:
|
||||||
|
return 'kick';
|
||||||
|
case 3:
|
||||||
|
return 'kick_me';
|
||||||
|
default:
|
||||||
|
return 'kick';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async parseSysMessage(msg: number[]) {
|
async parseSysMessage(msg: number[]) {
|
||||||
const sysMsg = decodeSysMessage(Uint8Array.from(msg));
|
// Todo Refactor
|
||||||
if (sysMsg.msgSpec.length === 0) {
|
const SysMessage = new NapProtoMsg(PushMsgBody).decode(Uint8Array.from(msg));
|
||||||
return;
|
if (SysMessage.contentHead.type == 33 && SysMessage.body?.msgContent) {
|
||||||
}
|
const groupChange = new NapProtoMsg(GroupChange).decode(SysMessage.body.msgContent);
|
||||||
const { msgType, subType, subSubType } = sysMsg.msgSpec[0];
|
console.log(JSON.stringify(groupChange));
|
||||||
if (msgType === 528 && subType === 39 && subSubType === 39) {
|
return new OB11GroupIncreaseEvent(
|
||||||
if (!sysMsg.bodyWrapper) return;
|
this.core,
|
||||||
return await this.obContext.apis.UserApi.parseLikeEvent(sysMsg.bodyWrapper.wrappedBody);
|
groupChange.groupUin,
|
||||||
|
groupChange.memberUid ? +await this.core.apis.UserApi.getUinByUidV2(groupChange.memberUid) : 0,
|
||||||
|
groupChange.operatorUid ? +await this.core.apis.UserApi.getUinByUidV2(groupChange.operatorUid) : 0,
|
||||||
|
groupChange.decreaseType == 131 ? 'invite' : 'approve',
|
||||||
|
);
|
||||||
|
} else if (SysMessage.contentHead.type == 34 && SysMessage.body?.msgContent) {
|
||||||
|
const groupChange = new NapProtoMsg(GroupChange).decode(SysMessage.body.msgContent);
|
||||||
|
return new OB11GroupDecreaseEvent(
|
||||||
|
this.core,
|
||||||
|
groupChange.groupUin,
|
||||||
|
groupChange.memberUid ? +await this.core.apis.UserApi.getUinByUidV2(groupChange.memberUid) : 0,
|
||||||
|
groupChange.operatorUid ? +await this.core.apis.UserApi.getUinByUidV2(groupChange.operatorUid) : 0,
|
||||||
|
this.groupChangDecreseType2String(groupChange.decreaseType),
|
||||||
|
);
|
||||||
|
} else if (SysMessage.contentHead.type == 528 && SysMessage.contentHead.subType == 39 && SysMessage.body?.msgContent) {
|
||||||
|
return await this.obContext.apis.UserApi.parseLikeEvent(SysMessage.body?.msgContent);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
if (msgType === 732 && subType === 16 && subSubType === 16) {
|
if (msgType === 732 && subType === 16 && subSubType === 16) {
|
||||||
const greyTip = GreyTipWrapper.fromBinary(Uint8Array.from(sysMsg.bodyWrapper!.wrappedBody.slice(7)));
|
const greyTip = GreyTipWrapper.fromBinary(Uint8Array.from(sysMsg.bodyWrapper!.wrappedBody.slice(7)));
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import { NapCatCore } from '@/core';
|
import { GrayTipRovokeElement, NapCatCore, RawMessage } from '@/core';
|
||||||
import { NapCatOneBot11Adapter } from '@/onebot';
|
import { NapCatOneBot11Adapter } from '@/onebot';
|
||||||
import { OB11ProfileLikeEvent } from '../event/notice/OB11ProfileLikeEvent';
|
import { OB11ProfileLikeEvent } from '@/onebot/event/notice/OB11ProfileLikeEvent';
|
||||||
import { decodeProfileLikeTip } from "@/core/helper/adaptDecoder";
|
import { decodeProfileLikeTip } from "@/core/helper/adaptDecoder";
|
||||||
|
|
||||||
export class OneBotUserApi {
|
export class OneBotUserApi {
|
||||||
@@ -13,7 +13,7 @@ export class OneBotUserApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async parseLikeEvent(wrappedBody: Uint8Array): Promise<OB11ProfileLikeEvent | undefined> {
|
async parseLikeEvent(wrappedBody: Uint8Array): Promise<OB11ProfileLikeEvent | undefined> {
|
||||||
const likeTip = decodeProfileLikeTip(Uint8Array.from(wrappedBody));
|
const likeTip = decodeProfileLikeTip(wrappedBody);
|
||||||
if (likeTip?.msgType !== 0 || likeTip?.subType !== 203) return;
|
if (likeTip?.msgType !== 0 || likeTip?.subType !== 203) return;
|
||||||
this.core.context.logger.logDebug("收到点赞通知消息");
|
this.core.context.logger.logDebug("收到点赞通知消息");
|
||||||
const likeMsg = likeTip.content.msg;
|
const likeMsg = likeTip.content.msg;
|
||||||
|
@@ -103,6 +103,7 @@ export interface OneBotConfig {
|
|||||||
network: NetworkConfig; // 网络配置
|
network: NetworkConfig; // 网络配置
|
||||||
musicSignUrl: string; // 音乐签名地址
|
musicSignUrl: string; // 音乐签名地址
|
||||||
enableLocalFile2Url: boolean;
|
enableLocalFile2Url: boolean;
|
||||||
|
parseMultMsg: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const createDefaultConfig = <T>(config: T): T => config;
|
const createDefaultConfig = <T>(config: T): T => config;
|
||||||
@@ -116,6 +117,7 @@ export const defaultOneBotConfigs = createDefaultConfig<OneBotConfig>({
|
|||||||
},
|
},
|
||||||
musicSignUrl: '',
|
musicSignUrl: '',
|
||||||
enableLocalFile2Url: false,
|
enableLocalFile2Url: false,
|
||||||
|
parseMultMsg: true
|
||||||
});
|
});
|
||||||
|
|
||||||
export const mergeNetworkDefaultConfig = {
|
export const mergeNetworkDefaultConfig = {
|
||||||
@@ -149,9 +151,12 @@ export function mergeOneBotConfigs(
|
|||||||
if (userConfig.musicSignUrl !== undefined) {
|
if (userConfig.musicSignUrl !== undefined) {
|
||||||
mergedConfig.musicSignUrl = userConfig.musicSignUrl;
|
mergedConfig.musicSignUrl = userConfig.musicSignUrl;
|
||||||
}
|
}
|
||||||
if(userConfig.enableLocalFile2Url !== undefined) {
|
if (userConfig.enableLocalFile2Url !== undefined) {
|
||||||
mergedConfig.enableLocalFile2Url = userConfig.enableLocalFile2Url;
|
mergedConfig.enableLocalFile2Url = userConfig.enableLocalFile2Url;
|
||||||
}
|
}
|
||||||
|
if (userConfig.parseMultMsg !== undefined) {
|
||||||
|
mergedConfig.parseMultMsg = userConfig.parseMultMsg;
|
||||||
|
}
|
||||||
return mergedConfig;
|
return mergedConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { EventType, OneBotEvent } from '../OneBotEvent';
|
import { EventType, OneBotEvent } from '@/onebot/event/OneBotEvent';
|
||||||
|
|
||||||
export abstract class OB11BaseMessageEvent extends OneBotEvent {
|
export abstract class OB11BaseMessageEvent extends OneBotEvent {
|
||||||
post_type = EventType.MESSAGE;
|
post_type = EventType.MESSAGE;
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { OB11BaseNoticeEvent } from '../notice/OB11BaseNoticeEvent';
|
import { OB11BaseNoticeEvent } from '@/onebot/event/notice/OB11BaseNoticeEvent';
|
||||||
import { EventType } from '../OneBotEvent';
|
import { EventType } from '@/onebot/event/OneBotEvent';
|
||||||
import { NapCatCore } from '@/core';
|
import { NapCatCore } from '@/core';
|
||||||
|
|
||||||
export class OB11FriendRequestEvent extends OB11BaseNoticeEvent {
|
export class OB11FriendRequestEvent extends OB11BaseNoticeEvent {
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { OB11GroupNoticeEvent } from '../notice/OB11GroupNoticeEvent';
|
import { OB11GroupNoticeEvent } from '@/onebot/event/notice/OB11GroupNoticeEvent';
|
||||||
import { EventType } from '../OneBotEvent';
|
import { EventType } from '@/onebot/event/OneBotEvent';
|
||||||
import { NapCatCore } from '@/core';
|
import { NapCatCore } from '@/core';
|
||||||
|
|
||||||
export class OB11GroupRequestEvent extends OB11GroupNoticeEvent {
|
export class OB11GroupRequestEvent extends OB11GroupNoticeEvent {
|
||||||
|
@@ -13,6 +13,8 @@ import {
|
|||||||
Peer,
|
Peer,
|
||||||
RawMessage,
|
RawMessage,
|
||||||
SendStatusType,
|
SendStatusType,
|
||||||
|
NTMsgType,
|
||||||
|
MessageElement,
|
||||||
} from '@/core';
|
} from '@/core';
|
||||||
import { OB11ConfigLoader } from '@/onebot/config';
|
import { OB11ConfigLoader } from '@/onebot/config';
|
||||||
import {
|
import {
|
||||||
@@ -40,7 +42,7 @@ import { MessageUnique } from '@/common/message-unique';
|
|||||||
import { proxiedListenerOf } from '@/common/proxy-handler';
|
import { proxiedListenerOf } from '@/common/proxy-handler';
|
||||||
import { OB11FriendRequestEvent } from '@/onebot/event/request/OB11FriendRequest';
|
import { OB11FriendRequestEvent } from '@/onebot/event/request/OB11FriendRequest';
|
||||||
import { OB11GroupAdminNoticeEvent } from '@/onebot/event/notice/OB11GroupAdminNoticeEvent';
|
import { OB11GroupAdminNoticeEvent } from '@/onebot/event/notice/OB11GroupAdminNoticeEvent';
|
||||||
import { GroupDecreaseSubType, OB11GroupDecreaseEvent } from '@/onebot/event/notice/OB11GroupDecreaseEvent';
|
// import { GroupDecreaseSubType, OB11GroupDecreaseEvent } from '@/onebot/event/notice/OB11GroupDecreaseEvent';
|
||||||
import { OB11GroupRequestEvent } from '@/onebot/event/request/OB11GroupRequest';
|
import { OB11GroupRequestEvent } from '@/onebot/event/request/OB11GroupRequest';
|
||||||
import { OB11FriendRecallNoticeEvent } from '@/onebot/event/notice/OB11FriendRecallNoticeEvent';
|
import { OB11FriendRecallNoticeEvent } from '@/onebot/event/notice/OB11FriendRecallNoticeEvent';
|
||||||
import { OB11GroupRecallNoticeEvent } from '@/onebot/event/notice/OB11GroupRecallNoticeEvent';
|
import { OB11GroupRecallNoticeEvent } from '@/onebot/event/notice/OB11GroupRecallNoticeEvent';
|
||||||
@@ -176,67 +178,48 @@ export class NapCatOneBot11Adapter {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private async reloadNetwork(prev: OneBotConfig, now: OneBotConfig) {
|
private async reloadNetwork(prev: OneBotConfig, now: OneBotConfig): Promise<void> {
|
||||||
const prevLog = await this.creatOneBotLog(prev);
|
const prevLog = await this.creatOneBotLog(prev);
|
||||||
const newLog = await this.creatOneBotLog(now);
|
const newLog = await this.creatOneBotLog(now);
|
||||||
this.context.logger.log(`[Notice] [OneBot11] 配置变更前:\n${prevLog}`);
|
this.context.logger.log(`[Notice] [OneBot11] 配置变更前:\n${prevLog}`);
|
||||||
this.context.logger.log(`[Notice] [OneBot11] 配置变更后:\n${newLog}`);
|
this.context.logger.log(`[Notice] [OneBot11] 配置变更后:\n${newLog}`);
|
||||||
|
|
||||||
const { added: addedHttpServers, removed: removedHttpServers } = this.findDifference(prev.network.httpServers, now.network.httpServers);
|
await this.handleConfigChange(prev.network.httpServers, now.network.httpServers, OB11PassiveHttpAdapter);
|
||||||
const { added: addedHttpClients, removed: removedHttpClients } = this.findDifference(prev.network.httpClients, now.network.httpClients);
|
await this.handleConfigChange(prev.network.httpClients, now.network.httpClients, OB11ActiveHttpAdapter);
|
||||||
const { added: addedWebSocketServers, removed: removedWebSocketServers } = this.findDifference(prev.network.websocketServers, now.network.websocketServers);
|
await this.handleConfigChange(prev.network.websocketServers, now.network.websocketServers, OB11PassiveWebSocketAdapter);
|
||||||
const { added: addedWebSocketClients, removed: removedWebSocketClients } = this.findDifference(prev.network.websocketClients, now.network.websocketClients);
|
await this.handleConfigChange(prev.network.websocketClients, now.network.websocketClients, OB11ActiveWebSocketAdapter);
|
||||||
|
|
||||||
await this.handleRemovedAdapters(removedHttpServers);
|
|
||||||
await this.handleRemovedAdapters(removedHttpClients);
|
|
||||||
await this.handleRemovedAdapters(removedWebSocketServers);
|
|
||||||
await this.handleRemovedAdapters(removedWebSocketClients);
|
|
||||||
|
|
||||||
await this.handlerConfigChange(now.network.httpServers);
|
|
||||||
await this.handlerConfigChange(now.network.httpClients);
|
|
||||||
await this.handlerConfigChange(now.network.websocketServers);
|
|
||||||
await this.handlerConfigChange(now.network.websocketClients);
|
|
||||||
|
|
||||||
await this.handleAddedAdapters(addedHttpServers, OB11PassiveHttpAdapter);
|
|
||||||
await this.handleAddedAdapters(addedHttpClients, OB11ActiveHttpAdapter);
|
|
||||||
await this.handleAddedAdapters(addedWebSocketServers, OB11PassiveWebSocketAdapter);
|
|
||||||
await this.handleAddedAdapters(addedWebSocketClients, OB11ActiveWebSocketAdapter);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async handlerConfigChange(adapters: Array<NetworkConfigAdapter>) {
|
private async handleConfigChange(
|
||||||
for (const adapterConfig of adapters) {
|
prevConfig: NetworkConfigAdapter[],
|
||||||
|
nowConfig: NetworkConfigAdapter[],
|
||||||
|
adapterClass: new (...args: any[]) => IOB11NetworkAdapter
|
||||||
|
): Promise<void> {
|
||||||
|
// 比较旧的在新的找不到的回收
|
||||||
|
for (const adapterConfig of prevConfig) {
|
||||||
|
const existingAdapter = nowConfig.find((e) => e.name === adapterConfig.name);
|
||||||
|
if (!existingAdapter) {
|
||||||
|
const existingAdapter = this.networkManager.findSomeAdapter(adapterConfig.name);
|
||||||
|
if (existingAdapter) {
|
||||||
|
await this.networkManager.closeSomeAdaterWhenOpen([existingAdapter]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 通知新配置重载 删除关闭的 加入新开的
|
||||||
|
for (const adapterConfig of nowConfig) {
|
||||||
const existingAdapter = this.networkManager.findSomeAdapter(adapterConfig.name);
|
const existingAdapter = this.networkManager.findSomeAdapter(adapterConfig.name);
|
||||||
if (existingAdapter) {
|
if (existingAdapter) {
|
||||||
const networkChange = await existingAdapter.reload(adapterConfig);
|
const networkChange = await existingAdapter.reload(adapterConfig);
|
||||||
if (networkChange === OB11NetworkReloadType.NetWorkClose) {
|
if (networkChange === OB11NetworkReloadType.NetWorkClose) {
|
||||||
this.networkManager.closeSomeAdapters([existingAdapter]);
|
await this.networkManager.closeSomeAdaterWhenOpen([existingAdapter]);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
} else if (adapterConfig.enable) {
|
||||||
|
const newAdapter = new adapterClass(adapterConfig.name, adapterConfig, this.core, this.actions);
|
||||||
|
await this.networkManager.registerAdapterAndOpen(newAdapter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async handleRemovedAdapters(adapters: Array<{ name: string }>): Promise<void> {
|
|
||||||
for (const adapter of adapters) {
|
|
||||||
await this.networkManager.closeAdapterByPredicate((existingAdapter) => existingAdapter.name === adapter.name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async handleAddedAdapters<T extends new (...args: any[]) => IOB11NetworkAdapter>(addedAdapters: Array<NetworkConfigAdapter>, AdapterClass: T) {
|
|
||||||
for (const adapter of addedAdapters) {
|
|
||||||
if (adapter.enable) {
|
|
||||||
const newAdapter = new AdapterClass(adapter.name, adapter, this.core, this.actions);
|
|
||||||
await newAdapter.open();
|
|
||||||
this.networkManager.registerAdapter(newAdapter);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private findDifference<T>(prev: T[], now: T[]): { added: T[]; removed: T[] } {
|
|
||||||
const added = now.filter((item) => !prev.includes(item));
|
|
||||||
const removed = prev.filter((item) => !now.includes(item));
|
|
||||||
return { added, removed };
|
|
||||||
}
|
|
||||||
|
|
||||||
private initMsgListener() {
|
private initMsgListener() {
|
||||||
const msgListener = new NodeIKernelMsgListener();
|
const msgListener = new NodeIKernelMsgListener();
|
||||||
msgListener.onRecvSysMsg = (msg) => {
|
msgListener.onRecvSysMsg = (msg) => {
|
||||||
@@ -282,30 +265,49 @@ export class NapCatOneBot11Adapter {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const msgIdSend = new LRUCache<string, number>(100);
|
|
||||||
const recallMsgs = new LRUCache<string, boolean>(100);
|
|
||||||
msgListener.onAddSendMsg = async (msg) => {
|
msgListener.onAddSendMsg = async (msg) => {
|
||||||
if (msg.sendStatus == SendStatusType.KSEND_STATUS_SENDING) {
|
try {
|
||||||
msgIdSend.put(msg.msgId, 0);
|
if (msg.sendStatus == SendStatusType.KSEND_STATUS_SENDING) {
|
||||||
|
const [updatemsgs] = await this.core.eventWrapper.registerListen('NodeIKernelMsgListener/onMsgInfoListUpdate', (msgList: RawMessage[]) => {
|
||||||
|
const report = msgList.find((e) =>
|
||||||
|
e.senderUin == this.core.selfInfo.uin && e.sendStatus !== SendStatusType.KSEND_STATUS_SENDING && e.msgId === msg.msgId
|
||||||
|
);
|
||||||
|
return !!report;
|
||||||
|
}, 1, 10 * 60 * 1000);
|
||||||
|
// 10分钟 超时
|
||||||
|
const updatemsg = updatemsgs.find((e) => e.msgId === msg.msgId);
|
||||||
|
if (updatemsg?.sendStatus == SendStatusType.KSEND_STATUS_SUCCESS || updatemsg?.sendStatus == SendStatusType.KSEND_STATUS_SUCCESS_NOSEQ) {
|
||||||
|
updatemsg.id = MessageUnique.createUniqueMsgId(
|
||||||
|
{
|
||||||
|
chatType: updatemsg.chatType,
|
||||||
|
peerUid: updatemsg.peerUid,
|
||||||
|
guildId: '',
|
||||||
|
},
|
||||||
|
updatemsg.msgId
|
||||||
|
);
|
||||||
|
this.emitMsg(updatemsg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
this.context.logger.logError('处理发送消息失败', error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
msgListener.onMsgInfoListUpdate = async (msgList) => {
|
msgListener.onMsgRecall = async (chatType: ChatType, uid: string, msgSeq: string) => {
|
||||||
this.emitRecallMsg(msgList, recallMsgs).catch((e) =>
|
const peer: Peer = {
|
||||||
this.context.logger.logError.bind(this.context.logger)('处理消息失败', e)
|
chatType: chatType,
|
||||||
);
|
peerUid: uid,
|
||||||
for (const msg of msgList.filter((e) => e.senderUin == this.core.selfInfo.uin)) {
|
guildId: ''
|
||||||
if (msg.sendStatus == SendStatusType.KSEND_STATUS_SUCCESS && msgIdSend.get(msg.msgId) == 0) {
|
};
|
||||||
msgIdSend.put(msg.msgId, 1);
|
const msg = (await this.core.apis.MsgApi.queryMsgsWithFilterExWithSeq(peer, msgSeq)).msgList.find(e => e.msgType == NTMsgType.KMSGTYPEGRAYTIPS);
|
||||||
// 完成后再post
|
const element = msg?.elements[0];
|
||||||
msg.id = MessageUnique.createUniqueMsgId(
|
if (msg && element) {
|
||||||
{
|
const recallEvent = await this.emitRecallMsg(msg, element);
|
||||||
chatType: msg.chatType,
|
try {
|
||||||
peerUid: msg.peerUid,
|
if (recallEvent) {
|
||||||
guildId: '',
|
await this.networkManager.emitEvent(recallEvent);
|
||||||
},
|
}
|
||||||
msg.msgId
|
} catch (e) {
|
||||||
);
|
this.context.logger.logError('处理消息撤回失败', e);
|
||||||
this.emitMsg(msg);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -411,102 +413,104 @@ export class NapCatOneBot11Adapter {
|
|||||||
this.core.apis.GroupApi.getGroup(notify.group.groupCode)
|
this.core.apis.GroupApi.getGroup(notify.group.groupCode)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else if (
|
} else
|
||||||
notify.type == GroupNotifyMsgType.MEMBER_LEAVE_NOTIFY_ADMIN ||
|
// if (
|
||||||
notify.type == GroupNotifyMsgType.KICK_MEMBER_NOTIFY_ADMIN
|
// notify.type == GroupNotifyMsgType.MEMBER_LEAVE_NOTIFY_ADMIN ||
|
||||||
) {
|
// notify.type == GroupNotifyMsgType.KICK_MEMBER_NOTIFY_ADMIN
|
||||||
this.context.logger.logDebug('有成员退出通知', notify);
|
// ) {
|
||||||
const member1Uin = await this.core.apis.UserApi.getUinByUidV2(notify.user1.uid);
|
// this.context.logger.logDebug('有成员退出通知', notify);
|
||||||
let operatorId = member1Uin;
|
// const member1Uin = await this.core.apis.UserApi.getUinByUidV2(notify.user1.uid);
|
||||||
let subType: GroupDecreaseSubType = 'leave';
|
// let operatorId = member1Uin;
|
||||||
if (notify.user2.uid) {
|
// let subType: GroupDecreaseSubType = 'leave';
|
||||||
// 是被踢的
|
// if (notify.user2.uid) {
|
||||||
const member2Uin = await this.core.apis.UserApi.getUinByUidV2(notify.user2.uid);
|
// // 是被踢的
|
||||||
if (member2Uin) {
|
// const member2Uin = await this.core.apis.UserApi.getUinByUidV2(notify.user2.uid);
|
||||||
operatorId = member2Uin;
|
// if (member2Uin) {
|
||||||
|
// operatorId = member2Uin;
|
||||||
|
// }
|
||||||
|
// subType = 'kick';
|
||||||
|
// }
|
||||||
|
// const groupDecreaseEvent = new OB11GroupDecreaseEvent(
|
||||||
|
// this.core,
|
||||||
|
// parseInt(notify.group.groupCode),
|
||||||
|
// parseInt(member1Uin),
|
||||||
|
// parseInt(operatorId),
|
||||||
|
// subType
|
||||||
|
// );
|
||||||
|
// this.networkManager
|
||||||
|
// .emitEvent(groupDecreaseEvent)
|
||||||
|
// .catch((e) =>
|
||||||
|
// this.context.logger.logError.bind(this.context.logger)('处理群成员退出失败', e)
|
||||||
|
// );
|
||||||
|
// // notify.status == 1 表示未处理 2表示处理完成
|
||||||
|
// } else
|
||||||
|
if (
|
||||||
|
[GroupNotifyMsgType.REQUEST_JOIN_NEED_ADMINI_STRATOR_PASS].includes(notify.type) &&
|
||||||
|
notify.status == GroupNotifyMsgStatus.KUNHANDLE
|
||||||
|
) {
|
||||||
|
this.context.logger.logDebug('有加群请求');
|
||||||
|
try {
|
||||||
|
let requestUin = await this.core.apis.UserApi.getUinByUidV2(notify.user1.uid);
|
||||||
|
if (isNaN(parseInt(requestUin))) {
|
||||||
|
requestUin = (await this.core.apis.UserApi.getUserDetailInfo(notify.user1.uid)).uin;
|
||||||
|
}
|
||||||
|
const groupRequestEvent = new OB11GroupRequestEvent(
|
||||||
|
this.core,
|
||||||
|
parseInt(notify.group.groupCode),
|
||||||
|
parseInt(requestUin),
|
||||||
|
'add',
|
||||||
|
notify.postscript,
|
||||||
|
flag
|
||||||
|
);
|
||||||
|
this.networkManager
|
||||||
|
.emitEvent(groupRequestEvent)
|
||||||
|
.catch((e) =>
|
||||||
|
this.context.logger.logError.bind(this.context.logger)('处理加群请求失败', e)
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
this.context.logger.logError.bind(this.context.logger)(
|
||||||
|
'获取加群人QQ号失败 Uid:',
|
||||||
|
notify.user1.uid,
|
||||||
|
e
|
||||||
|
);
|
||||||
}
|
}
|
||||||
subType = 'kick';
|
} else if (
|
||||||
}
|
notify.type == GroupNotifyMsgType.INVITED_BY_MEMBER &&
|
||||||
const groupDecreaseEvent = new OB11GroupDecreaseEvent(
|
notify.status == GroupNotifyMsgStatus.KUNHANDLE
|
||||||
this.core,
|
) {
|
||||||
parseInt(notify.group.groupCode),
|
this.context.logger.logDebug(`收到邀请我加群通知:${notify}`);
|
||||||
parseInt(member1Uin),
|
const groupInviteEvent = new OB11GroupRequestEvent(
|
||||||
parseInt(operatorId),
|
|
||||||
subType
|
|
||||||
);
|
|
||||||
this.networkManager
|
|
||||||
.emitEvent(groupDecreaseEvent)
|
|
||||||
.catch((e) =>
|
|
||||||
this.context.logger.logError.bind(this.context.logger)('处理群成员退出失败', e)
|
|
||||||
);
|
|
||||||
// notify.status == 1 表示未处理 2表示处理完成
|
|
||||||
} else if (
|
|
||||||
[GroupNotifyMsgType.REQUEST_JOIN_NEED_ADMINI_STRATOR_PASS].includes(notify.type) &&
|
|
||||||
notify.status == GroupNotifyMsgStatus.KUNHANDLE
|
|
||||||
) {
|
|
||||||
this.context.logger.logDebug('有加群请求');
|
|
||||||
try {
|
|
||||||
let requestUin = await this.core.apis.UserApi.getUinByUidV2(notify.user1.uid);
|
|
||||||
if (isNaN(parseInt(requestUin))) {
|
|
||||||
requestUin = (await this.core.apis.UserApi.getUserDetailInfo(notify.user1.uid)).uin;
|
|
||||||
}
|
|
||||||
const groupRequestEvent = new OB11GroupRequestEvent(
|
|
||||||
this.core,
|
this.core,
|
||||||
parseInt(notify.group.groupCode),
|
parseInt(notify.group.groupCode),
|
||||||
parseInt(requestUin),
|
parseInt(await this.core.apis.UserApi.getUinByUidV2(notify.user2.uid)),
|
||||||
|
'invite',
|
||||||
|
notify.postscript,
|
||||||
|
flag
|
||||||
|
);
|
||||||
|
this.networkManager
|
||||||
|
.emitEvent(groupInviteEvent)
|
||||||
|
.catch((e) =>
|
||||||
|
this.context.logger.logError.bind(this.context.logger)('处理邀请本人加群失败', e)
|
||||||
|
);
|
||||||
|
} else if (
|
||||||
|
notify.type == GroupNotifyMsgType.INVITED_NEED_ADMINI_STRATOR_PASS &&
|
||||||
|
notify.status == GroupNotifyMsgStatus.KUNHANDLE
|
||||||
|
) {
|
||||||
|
this.context.logger.logDebug(`收到群员邀请加群通知:${notify}`);
|
||||||
|
const groupInviteEvent = new OB11GroupRequestEvent(
|
||||||
|
this.core,
|
||||||
|
parseInt(notify.group.groupCode),
|
||||||
|
parseInt(await this.core.apis.UserApi.getUinByUidV2(notify.user1.uid)),
|
||||||
'add',
|
'add',
|
||||||
notify.postscript,
|
notify.postscript,
|
||||||
flag
|
flag
|
||||||
);
|
);
|
||||||
this.networkManager
|
this.networkManager
|
||||||
.emitEvent(groupRequestEvent)
|
.emitEvent(groupInviteEvent)
|
||||||
.catch((e) =>
|
.catch((e) =>
|
||||||
this.context.logger.logError.bind(this.context.logger)('处理加群请求失败', e)
|
this.context.logger.logError.bind(this.context.logger)('处理邀请本人加群失败', e)
|
||||||
);
|
);
|
||||||
} catch (e) {
|
|
||||||
this.context.logger.logError.bind(this.context.logger)(
|
|
||||||
'获取加群人QQ号失败 Uid:',
|
|
||||||
notify.user1.uid,
|
|
||||||
e
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
} else if (
|
|
||||||
notify.type == GroupNotifyMsgType.INVITED_BY_MEMBER &&
|
|
||||||
notify.status == GroupNotifyMsgStatus.KUNHANDLE
|
|
||||||
) {
|
|
||||||
this.context.logger.logDebug(`收到邀请我加群通知:${notify}`);
|
|
||||||
const groupInviteEvent = new OB11GroupRequestEvent(
|
|
||||||
this.core,
|
|
||||||
parseInt(notify.group.groupCode),
|
|
||||||
parseInt(await this.core.apis.UserApi.getUinByUidV2(notify.user2.uid)),
|
|
||||||
'invite',
|
|
||||||
notify.postscript,
|
|
||||||
flag
|
|
||||||
);
|
|
||||||
this.networkManager
|
|
||||||
.emitEvent(groupInviteEvent)
|
|
||||||
.catch((e) =>
|
|
||||||
this.context.logger.logError.bind(this.context.logger)('处理邀请本人加群失败', e)
|
|
||||||
);
|
|
||||||
} else if (
|
|
||||||
notify.type == GroupNotifyMsgType.INVITED_NEED_ADMINI_STRATOR_PASS &&
|
|
||||||
notify.status == GroupNotifyMsgStatus.KUNHANDLE
|
|
||||||
) {
|
|
||||||
this.context.logger.logDebug(`收到群员邀请加群通知:${notify}`);
|
|
||||||
const groupInviteEvent = new OB11GroupRequestEvent(
|
|
||||||
this.core,
|
|
||||||
parseInt(notify.group.groupCode),
|
|
||||||
parseInt(await this.core.apis.UserApi.getUinByUidV2(notify.user1.uid)),
|
|
||||||
'add',
|
|
||||||
notify.postscript,
|
|
||||||
flag
|
|
||||||
);
|
|
||||||
this.networkManager
|
|
||||||
.emitEvent(groupInviteEvent)
|
|
||||||
.catch((e) =>
|
|
||||||
this.context.logger.logError.bind(this.context.logger)('处理邀请本人加群失败', e)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -545,13 +549,15 @@ export class NapCatOneBot11Adapter {
|
|||||||
private async emitMsg(message: RawMessage) {
|
private async emitMsg(message: RawMessage) {
|
||||||
const network = Object.values(this.configLoader.configData.network).flat() as Array<AdapterConfigWrap>;
|
const network = Object.values(this.configLoader.configData.network).flat() as Array<AdapterConfigWrap>;
|
||||||
this.context.logger.logDebug('收到新消息 RawMessage', message);
|
this.context.logger.logDebug('收到新消息 RawMessage', message);
|
||||||
await this.handleMsg(message, network);
|
await Promise.allSettled([
|
||||||
await this.handleGroupEvent(message);
|
this.handleMsg(message, network),
|
||||||
await this.handlePrivateMsgEvent(message);
|
message.chatType == ChatType.KCHATTYPEGROUP ? this.handleGroupEvent(message) : this.handlePrivateMsgEvent(message)
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async handleMsg(message: RawMessage, network: Array<AdapterConfigWrap>) {
|
private async handleMsg(message: RawMessage, network: Array<AdapterConfigWrap>) {
|
||||||
try {
|
try {
|
||||||
const ob11Msg = await this.apis.MsgApi.parseMessageV2(message);
|
const ob11Msg = await this.apis.MsgApi.parseMessageV2(message, this.configLoader.configData.parseMultMsg);
|
||||||
if (ob11Msg) {
|
if (ob11Msg) {
|
||||||
const isSelfMsg = this.isSelfMessage(ob11Msg);
|
const isSelfMsg = this.isSelfMessage(ob11Msg);
|
||||||
this.context.logger.logDebug('转化为 OB11Message', ob11Msg);
|
this.context.logger.logDebug('转化为 OB11Message', ob11Msg);
|
||||||
@@ -616,9 +622,25 @@ export class NapCatOneBot11Adapter {
|
|||||||
|
|
||||||
private async handleGroupEvent(message: RawMessage) {
|
private async handleGroupEvent(message: RawMessage) {
|
||||||
try {
|
try {
|
||||||
const groupEvent = await this.apis.GroupApi.parseGroupEvent(message);
|
// 群名片修改事件解析 任何都该判断
|
||||||
if (groupEvent) {
|
if (message.senderUin && message.senderUin !== '0') {
|
||||||
this.networkManager.emitEvent(groupEvent);
|
const cardChangedEvent = await this.apis.GroupApi.parseCardChangedEvent(message);
|
||||||
|
cardChangedEvent && await this.networkManager.emitEvent(cardChangedEvent);
|
||||||
|
}
|
||||||
|
if (message.msgType === NTMsgType.KMSGTYPEFILE) {
|
||||||
|
// 文件为单元素消息
|
||||||
|
const elementWrapper = message.elements.find(e => !!e.fileElement);
|
||||||
|
if (elementWrapper?.fileElement) {
|
||||||
|
const uploadGroupFileEvent = await this.apis.GroupApi.parseGroupUploadFileEvene(message, elementWrapper.fileElement, elementWrapper);
|
||||||
|
uploadGroupFileEvent && await this.networkManager.emitEvent(uploadGroupFileEvent);
|
||||||
|
}
|
||||||
|
} else if (message.msgType === NTMsgType.KMSGTYPEGRAYTIPS) {
|
||||||
|
// 灰条为单元素消息
|
||||||
|
const grayTipElement = message.elements[0].grayTipElement;
|
||||||
|
if (grayTipElement) {
|
||||||
|
const event = await this.apis.GroupApi.parseGrayTipElement(message, grayTipElement);
|
||||||
|
event && await this.networkManager.emitEvent(event);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.context.logger.logError('constructGroupEvent error: ', e);
|
this.context.logger.logError('constructGroupEvent error: ', e);
|
||||||
@@ -627,59 +649,51 @@ export class NapCatOneBot11Adapter {
|
|||||||
|
|
||||||
private async handlePrivateMsgEvent(message: RawMessage) {
|
private async handlePrivateMsgEvent(message: RawMessage) {
|
||||||
try {
|
try {
|
||||||
const privateEvent = await this.apis.MsgApi.parsePrivateMsgEvent(message);
|
if (message.msgType === NTMsgType.KMSGTYPEGRAYTIPS) {
|
||||||
if (privateEvent) {
|
// 灰条为单元素消息
|
||||||
this.networkManager.emitEvent(privateEvent);
|
const grayTipElement = message.elements[0].grayTipElement;
|
||||||
|
if (grayTipElement) {
|
||||||
|
const event = await this.apis.MsgApi.parsePrivateMsgEvent(message, grayTipElement);
|
||||||
|
event && await this.networkManager.emitEvent(event);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.context.logger.logError('constructPrivateEvent error: ', e);
|
this.context.logger.logError('constructPrivateEvent error: ', e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private async emitRecallMsg(msgList: RawMessage[], cache: LRUCache<string, boolean>) {
|
|
||||||
for (const message of msgList) {
|
private async emitRecallMsg(message: RawMessage, element: MessageElement) {
|
||||||
// log("message update", message.sendStatus, message.msgId, message.msgSeq)
|
const peer: Peer = { chatType: message.chatType, peerUid: message.peerUid, guildId: '' };
|
||||||
const peer: Peer = { chatType: message.chatType, peerUid: message.peerUid, guildId: '' };
|
const oriMessageId = MessageUnique.getShortIdByMsgId(message.msgId) ?? MessageUnique.createUniqueMsgId(peer, message.msgId);
|
||||||
if (message.recallTime != '0' && !cache.get(message.msgId)) {
|
if (message.chatType == ChatType.KCHATTYPEC2C) {
|
||||||
//TODO: 这个判断方法不太好,应该使用灰色消息元素来判断?
|
return await this.emitFriendRecallMsg(message, oriMessageId, element);
|
||||||
cache.put(message.msgId, true);
|
} else if (message.chatType == ChatType.KCHATTYPEGROUP) {
|
||||||
// 撤回消息上报
|
return await this.emitGroupRecallMsg(message, oriMessageId, element);
|
||||||
let oriMessageId = MessageUnique.getShortIdByMsgId(message.msgId);
|
|
||||||
if (!oriMessageId) {
|
|
||||||
oriMessageId = MessageUnique.createUniqueMsgId(peer, message.msgId);
|
|
||||||
}
|
|
||||||
if (message.chatType == ChatType.KCHATTYPEC2C) {
|
|
||||||
const friendRecallEvent = new OB11FriendRecallNoticeEvent(
|
|
||||||
this.core,
|
|
||||||
+message.senderUin,
|
|
||||||
oriMessageId
|
|
||||||
);
|
|
||||||
this.networkManager
|
|
||||||
.emitEvent(friendRecallEvent)
|
|
||||||
.catch((e) =>
|
|
||||||
this.context.logger.logError.bind(this.context.logger)('处理好友消息撤回失败', e)
|
|
||||||
);
|
|
||||||
} else if (message.chatType == ChatType.KCHATTYPEGROUP) {
|
|
||||||
let operatorId = message.senderUin;
|
|
||||||
for (const element of message.elements) {
|
|
||||||
const operatorUid = element.grayTipElement?.revokeElement.operatorUid;
|
|
||||||
if (!operatorUid) return;
|
|
||||||
const operator = await this.core.apis.GroupApi.getGroupMember(message.peerUin, operatorUid);
|
|
||||||
operatorId = operator?.uin ?? message.senderUin;
|
|
||||||
}
|
|
||||||
const groupRecallEvent = new OB11GroupRecallNoticeEvent(
|
|
||||||
this.core,
|
|
||||||
+message.peerUin,
|
|
||||||
+message.senderUin,
|
|
||||||
+operatorId,
|
|
||||||
oriMessageId
|
|
||||||
);
|
|
||||||
this.networkManager
|
|
||||||
.emitEvent(groupRecallEvent)
|
|
||||||
.catch((e) => this.context.logger.logError.bind(this.context.logger)('处理群消息撤回失败', e));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async emitFriendRecallMsg(message: RawMessage, oriMessageId: number, element: MessageElement) {
|
||||||
|
return new OB11FriendRecallNoticeEvent(
|
||||||
|
this.core,
|
||||||
|
+message.senderUin,
|
||||||
|
oriMessageId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async emitGroupRecallMsg(message: RawMessage, oriMessageId: number, element: MessageElement) {
|
||||||
|
const operatorUid = element.grayTipElement?.revokeElement.operatorUid;
|
||||||
|
if (!operatorUid) return undefined;
|
||||||
|
const operatorId = message.senderUin ?? await this.core.apis.UserApi.getUinByUidV2(operatorUid);
|
||||||
|
return new OB11GroupRecallNoticeEvent(
|
||||||
|
this.core,
|
||||||
|
+message.peerUin,
|
||||||
|
+message.senderUin,
|
||||||
|
+operatorId,
|
||||||
|
oriMessageId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export * from './types';
|
export * from './types';
|
||||||
|
@@ -5,8 +5,8 @@ import { QuickAction, QuickActionEvent } from '@/onebot/types';
|
|||||||
import { NapCatCore } from '@/core';
|
import { NapCatCore } from '@/core';
|
||||||
import { NapCatOneBot11Adapter } from '..';
|
import { NapCatOneBot11Adapter } from '..';
|
||||||
import { RequestUtil } from '@/common/request';
|
import { RequestUtil } from '@/common/request';
|
||||||
import { HttpClientConfig } from '../config/config';
|
import { HttpClientConfig } from '@/onebot/config/config';
|
||||||
import { ActionMap } from '../action';
|
import { ActionMap } from '@/onebot/action';
|
||||||
|
|
||||||
export class OB11ActiveHttpAdapter implements IOB11NetworkAdapter {
|
export class OB11ActiveHttpAdapter implements IOB11NetworkAdapter {
|
||||||
logger: LogWrapper;
|
logger: LogWrapper;
|
||||||
|
@@ -1,13 +1,13 @@
|
|||||||
import { IOB11NetworkAdapter, OB11EmitEventContent, OB11NetworkReloadType } from '@/onebot/network/index';
|
import { IOB11NetworkAdapter, OB11EmitEventContent, OB11NetworkReloadType } from '@/onebot/network/index';
|
||||||
import { WebSocket } from 'ws';
|
import { WebSocket } from 'ws';
|
||||||
import { OB11HeartbeatEvent } from '../event/meta/OB11HeartbeatEvent';
|
import { OB11HeartbeatEvent } from '@/onebot/event/meta/OB11HeartbeatEvent';
|
||||||
import { NapCatCore } from '@/core';
|
import { NapCatCore } from '@/core';
|
||||||
import { ActionName } from '@/onebot/action/router';
|
import { ActionName } from '@/onebot/action/router';
|
||||||
import { OB11Response } from '@/onebot/action/OneBotAction';
|
import { OB11Response } from '@/onebot/action/OneBotAction';
|
||||||
import { LogWrapper } from '@/common/log';
|
import { LogWrapper } from '@/common/log';
|
||||||
import { ActionMap } from '@/onebot/action';
|
import { ActionMap } from '@/onebot/action';
|
||||||
import { LifeCycleSubType, OB11LifeCycleEvent } from '../event/meta/OB11LifeCycleEvent';
|
import { LifeCycleSubType, OB11LifeCycleEvent } from '@/onebot/event/meta/OB11LifeCycleEvent';
|
||||||
import { WebsocketClientConfig } from '../config/config';
|
import { WebsocketClientConfig } from '@/onebot/config/config';
|
||||||
|
|
||||||
export class OB11ActiveWebSocketAdapter implements IOB11NetworkAdapter {
|
export class OB11ActiveWebSocketAdapter implements IOB11NetworkAdapter {
|
||||||
isEnable: boolean = false;
|
isEnable: boolean = false;
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import { OneBotEvent } from '@/onebot/event/OneBotEvent';
|
import { OneBotEvent } from '@/onebot/event/OneBotEvent';
|
||||||
import { OB11Message } from '@/onebot';
|
import { OB11Message } from '@/onebot';
|
||||||
import { ActionMap } from '@/onebot/action';
|
import { ActionMap } from '@/onebot/action';
|
||||||
import { NetworkConfigAdapter } from '../config/config';
|
import { NetworkConfigAdapter } from '@/onebot/config/config';
|
||||||
|
|
||||||
export type OB11EmitEventContent = OneBotEvent | OB11Message;
|
export type OB11EmitEventContent = OneBotEvent | OB11Message;
|
||||||
export enum OB11NetworkReloadType {
|
export enum OB11NetworkReloadType {
|
||||||
@@ -37,6 +37,10 @@ export class OB11NetworkManager {
|
|||||||
return Promise.all(Array.from(this.adapters.values()).map(adapter => adapter.onEvent(event)));
|
return Promise.all(Array.from(this.adapters.values()).map(adapter => adapter.onEvent(event)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async emitEvents(events: OB11EmitEventContent[]) {
|
||||||
|
return Promise.all(events.map(event => this.emitEvent(event)));
|
||||||
|
}
|
||||||
|
|
||||||
async emitEventByName(names: string[], event: OB11EmitEventContent) {
|
async emitEventByName(names: string[], event: OB11EmitEventContent) {
|
||||||
return Promise.all(names.map(name => {
|
return Promise.all(names.map(name => {
|
||||||
const adapter = this.adapters.get(name);
|
const adapter = this.adapters.get(name);
|
||||||
@@ -68,6 +72,14 @@ export class OB11NetworkManager {
|
|||||||
await adapter.close();
|
await adapter.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
async closeSomeAdaterWhenOpen(adaptersToClose: IOB11NetworkAdapter[]) {
|
||||||
|
for (const adapter of adaptersToClose) {
|
||||||
|
this.adapters.delete(adapter.name);
|
||||||
|
if (adapter.isEnable) {
|
||||||
|
await adapter.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
findSomeAdapter(name: string) {
|
findSomeAdapter(name: string) {
|
||||||
return this.adapters.get(name);
|
return this.adapters.get(name);
|
||||||
|
@@ -5,7 +5,7 @@ import { NapCatCore } from '@/core';
|
|||||||
import { OB11Response } from '@/onebot/action/OneBotAction';
|
import { OB11Response } from '@/onebot/action/OneBotAction';
|
||||||
import { ActionMap } from '@/onebot/action';
|
import { ActionMap } from '@/onebot/action';
|
||||||
import cors from 'cors';
|
import cors from 'cors';
|
||||||
import { HttpServerConfig } from '../config/config';
|
import { HttpServerConfig } from '@/onebot/config/config';
|
||||||
|
|
||||||
export class OB11PassiveHttpAdapter implements IOB11NetworkAdapter {
|
export class OB11PassiveHttpAdapter implements IOB11NetworkAdapter {
|
||||||
private app: Express | undefined;
|
private app: Express | undefined;
|
||||||
@@ -102,7 +102,7 @@ export class OB11PassiveHttpAdapter implements IOB11NetworkAdapter {
|
|||||||
if (req.path === '' || req.path === '/') {
|
if (req.path === '' || req.path === '/') {
|
||||||
const hello = OB11Response.ok({});
|
const hello = OB11Response.ok({});
|
||||||
hello.message = 'NapCat4 Ss Running';
|
hello.message = 'NapCat4 Ss Running';
|
||||||
return res.json(hello)
|
return res.json(hello);
|
||||||
}
|
}
|
||||||
const actionName = req.path.split('/')[1];
|
const actionName = req.path.split('/')[1];
|
||||||
const action = this.actions.get(actionName);
|
const action = this.actions.get(actionName);
|
||||||
|
@@ -3,14 +3,14 @@ import urlParse from 'url';
|
|||||||
import { WebSocket, WebSocketServer } from 'ws';
|
import { WebSocket, WebSocketServer } from 'ws';
|
||||||
import { Mutex } from 'async-mutex';
|
import { Mutex } from 'async-mutex';
|
||||||
import { OB11Response } from '@/onebot/action/OneBotAction';
|
import { OB11Response } from '@/onebot/action/OneBotAction';
|
||||||
import { ActionName } from '../action/router';
|
import { ActionName } from '@/onebot/action/router';
|
||||||
import { NapCatCore } from '@/core';
|
import { NapCatCore } from '@/core';
|
||||||
import { LogWrapper } from '@/common/log';
|
import { LogWrapper } from '@/common/log';
|
||||||
import { OB11HeartbeatEvent } from '../event/meta/OB11HeartbeatEvent';
|
import { OB11HeartbeatEvent } from '@/onebot/event/meta/OB11HeartbeatEvent';
|
||||||
import { IncomingMessage } from 'http';
|
import { IncomingMessage } from 'http';
|
||||||
import { ActionMap } from '@/onebot/action';
|
import { ActionMap } from '@/onebot/action';
|
||||||
import { LifeCycleSubType, OB11LifeCycleEvent } from '../event/meta/OB11LifeCycleEvent';
|
import { LifeCycleSubType, OB11LifeCycleEvent } from '@/onebot/event/meta/OB11LifeCycleEvent';
|
||||||
import { WebsocketServerConfig } from '../config/config';
|
import { WebsocketServerConfig } from '@/onebot/config/config';
|
||||||
|
|
||||||
export class OB11PassiveWebSocketAdapter implements IOB11NetworkAdapter {
|
export class OB11PassiveWebSocketAdapter implements IOB11NetworkAdapter {
|
||||||
wsServer: WebSocketServer;
|
wsServer: WebSocketServer;
|
||||||
@@ -31,13 +31,9 @@ export class OB11PassiveWebSocketAdapter implements IOB11NetworkAdapter {
|
|||||||
) {
|
) {
|
||||||
this.config = structuredClone(config);
|
this.config = structuredClone(config);
|
||||||
this.logger = core.context.logger;
|
this.logger = core.context.logger;
|
||||||
if (this.config.host === '0.0.0.0') {
|
|
||||||
//兼容配置同时处理0.0.0.0逻辑
|
|
||||||
this.config.host = '';
|
|
||||||
}
|
|
||||||
this.wsServer = new WebSocketServer({
|
this.wsServer = new WebSocketServer({
|
||||||
port: this.config.port,
|
port: this.config.port,
|
||||||
host: this.config.host,
|
host: this.config.host === '0.0.0.0' ? '' : this.config.host,
|
||||||
maxPayload: 1024 * 1024 * 1024,
|
maxPayload: 1024 * 1024 * 1024,
|
||||||
});
|
});
|
||||||
this.wsServer.on('connection', async (wsClient, wsReq) => {
|
this.wsServer.on('connection', async (wsClient, wsReq) => {
|
||||||
|
@@ -236,7 +236,7 @@ export interface OB11MessageForward {
|
|||||||
type: OB11MessageDataType.forward;
|
type: OB11MessageDataType.forward;
|
||||||
data: {
|
data: {
|
||||||
id: string;
|
id: string;
|
||||||
content: OB11Message[];
|
content?: OB11Message[];
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import { OB11BaseMetaEvent } from '../event/meta/OB11BaseMetaEvent';
|
import { OB11BaseMetaEvent } from '@/onebot/event/meta/OB11BaseMetaEvent';
|
||||||
import { OB11BaseNoticeEvent } from '../event/notice/OB11BaseNoticeEvent';
|
import { OB11BaseNoticeEvent } from '@/onebot/event/notice/OB11BaseNoticeEvent';
|
||||||
import { OB11Message } from './message';
|
import { OB11Message } from '@/onebot/types/message';
|
||||||
|
|
||||||
export type QuickActionEvent = OB11Message | OB11BaseMetaEvent | OB11BaseNoticeEvent;
|
export type QuickActionEvent = OB11Message | OB11BaseMetaEvent | OB11BaseNoticeEvent;
|
||||||
export type PostEventType = OB11Message | OB11BaseMetaEvent | OB11BaseNoticeEvent;
|
export type PostEventType = OB11Message | OB11BaseMetaEvent | OB11BaseNoticeEvent;
|
||||||
|
@@ -1,11 +1,20 @@
|
|||||||
|
/**
|
||||||
|
* @file WebUI服务入口文件
|
||||||
|
*/
|
||||||
|
|
||||||
import express from 'express';
|
import express from 'express';
|
||||||
import { ALLRouter } from './src/router';
|
|
||||||
import { LogWrapper } from '@/common/log';
|
import { LogWrapper } from '@/common/log';
|
||||||
import { NapCatPathWrapper } from '@/common/path';
|
import { NapCatPathWrapper } from '@/common/path';
|
||||||
import { WebUiConfigWrapper } from './src/helper/config';
|
|
||||||
import { RequestUtil } from '@/common/request';
|
import { RequestUtil } from '@/common/request';
|
||||||
import { isIP } from "node:net";
|
|
||||||
|
|
||||||
|
import { WebUiConfigWrapper } from '@webapi/helper/config';
|
||||||
|
import { ALLRouter } from '@webapi/router';
|
||||||
|
import { cors } from '@webapi/middleware/cors';
|
||||||
|
import { createUrl } from '@webapi/utils/url';
|
||||||
|
import { sendSuccess } from '@webapi/utils/response';
|
||||||
|
|
||||||
|
// 实例化Express
|
||||||
const app = express();
|
const app = express();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -26,49 +35,51 @@ export async function InitWebUi(logger: LogWrapper, pathWrapper: NapCatPathWrapp
|
|||||||
log('[NapCat] [WebUi] Current WebUi is not run.');
|
log('[NapCat] [WebUi] Current WebUi is not run.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ------------注册中间件------------
|
||||||
|
// 使用express的json中间件
|
||||||
app.use(express.json());
|
app.use(express.json());
|
||||||
// 初始服务
|
|
||||||
|
// CORS中间件
|
||||||
|
// TODO:
|
||||||
|
app.use(cors);
|
||||||
|
// ------------中间件结束------------
|
||||||
|
|
||||||
|
// ------------挂载路由------------
|
||||||
|
// 挂载静态路由(前端),路径为 [/前缀]/webui
|
||||||
|
app.use(config.prefix + '/webui', express.static(pathWrapper.staticPath));
|
||||||
|
// 挂载API接口
|
||||||
|
app.use(config.prefix + '/api', ALLRouter);
|
||||||
|
|
||||||
|
// 初始服务(先放个首页)
|
||||||
// WebUI只在config.prefix所示路径上提供服务,可配合Nginx挂载到子目录中
|
// WebUI只在config.prefix所示路径上提供服务,可配合Nginx挂载到子目录中
|
||||||
app.all(config.prefix + '/', (_req, res) => {
|
app.all(config.prefix + '/', (_req, res) => {
|
||||||
res.json({
|
sendSuccess(res, null, 'NapCat WebAPI is now running!');
|
||||||
msg: 'NapCat WebAPI is now running!',
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
// 配置静态文件服务,提供./static目录下的文件服务,访问路径为/webui
|
// ------------路由挂载结束------------
|
||||||
app.use(config.prefix + '/webui', express.static(pathWrapper.staticPath));
|
|
||||||
//挂载API接口
|
// ------------启动服务------------
|
||||||
// 添加CORS支持
|
|
||||||
// TODO:
|
|
||||||
app.use((req, res, next) => {
|
|
||||||
res.header('Access-Control-Allow-Origin', '*');
|
|
||||||
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
|
|
||||||
res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, Authorization');
|
|
||||||
next();
|
|
||||||
});
|
|
||||||
app.use(config.prefix + '/api', ALLRouter);
|
|
||||||
app.listen(config.port, config.host, async () => {
|
app.listen(config.port, config.host, async () => {
|
||||||
const normalizeHost = (host: string) => {
|
// 启动后打印出相关地址
|
||||||
if (host === '0.0.0.0') return '127.0.0.1';
|
|
||||||
if (isIP(host) === 6) return `[${host}]`;
|
const port = config.port.toString(),
|
||||||
return host;
|
searchParams = { token: config.token },
|
||||||
};
|
path = `${config.prefix}/webui`;
|
||||||
const createUrl = (host: string, path: string, token: string) => {
|
|
||||||
const url = new URL(`http://${normalizeHost(host)}`);
|
// 打印日志(地址、token)
|
||||||
url.port = config.port.toString();
|
|
||||||
url.pathname = `${config.prefix}${path}`;
|
|
||||||
url.searchParams.set('token', token);
|
|
||||||
return url.toString();
|
|
||||||
};
|
|
||||||
log(`[NapCat] [WebUi] Current WebUi is running at http://${config.host}:${config.port}${config.prefix}`);
|
log(`[NapCat] [WebUi] Current WebUi is running at http://${config.host}:${config.port}${config.prefix}`);
|
||||||
log(`[NapCat] [WebUi] Login Token is ${config.token}`);
|
log(`[NapCat] [WebUi] Login Token is ${config.token}`);
|
||||||
log(`[NapCat] [WebUi] WebUi User Panel Url: ${createUrl(config.host, '/webui', config.token)}`);
|
log(`[NapCat] [WebUi] WebUi User Panel Url: ${createUrl(config.host, port, path, searchParams)}`);
|
||||||
log(`[NapCat] [WebUi] WebUi Local Panel Url: ${createUrl('127.0.0.1', '/webui', config.token)}`);
|
log(`[NapCat] [WebUi] WebUi Local Panel Url: ${createUrl('127.0.0.1', port, path, searchParams)}`);
|
||||||
|
|
||||||
|
// 获取公网地址
|
||||||
try {
|
try {
|
||||||
const publishUrl = 'https://ip.011102.xyz/';
|
const publishUrl = 'https://ip.011102.xyz/';
|
||||||
const data = await RequestUtil.HttpGetJson<{ IP: { IP: string } }>(publishUrl, 'GET', {}, {}, true, true);
|
const data = await RequestUtil.HttpGetJson<{ IP: { IP: string } }>(publishUrl, 'GET', {}, {}, true, true);
|
||||||
log(`[NapCat] [WebUi] WebUi Publish Panel Url: ${createUrl(data.IP.IP, '/webui', config.token)}`);
|
log(`[NapCat] [WebUi] WebUi Publish Panel Url: ${createUrl(data.IP.IP, port, path, searchParams)}`);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.logError(`[NapCat] [WebUi] Get Publish Panel Url Error: ${err}`);
|
logger.logError(`[NapCat] [WebUi] Get Publish Panel Url Error: ${err}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
// ------------Over!------------
|
||||||
}
|
}
|
||||||
|
@@ -1,69 +1,64 @@
|
|||||||
import { RequestHandler } from 'express';
|
import { RequestHandler } from 'express';
|
||||||
import { AuthHelper } from '../helper/SignToken';
|
|
||||||
import { WebUiDataRuntime } from '../helper/Data';
|
|
||||||
import { WebUiConfig } from '@/webui';
|
import { WebUiConfig } from '@/webui';
|
||||||
|
|
||||||
const isEmpty = (data: any) => data === undefined || data === null || data === '';
|
import { AuthHelper } from '@webapi/helper/SignToken';
|
||||||
|
import { WebUiDataRuntime } from '@webapi/helper/Data';
|
||||||
|
import { sendSuccess, sendError } from '@webapi/utils/response';
|
||||||
|
import { isEmpty } from '@webapi/utils/check';
|
||||||
|
|
||||||
|
// 登录
|
||||||
export const LoginHandler: RequestHandler = async (req, res) => {
|
export const LoginHandler: RequestHandler = async (req, res) => {
|
||||||
|
// 获取WebUI配置
|
||||||
const WebUiConfigData = await WebUiConfig.GetWebUIConfig();
|
const WebUiConfigData = await WebUiConfig.GetWebUIConfig();
|
||||||
|
// 获取请求体中的token
|
||||||
const { token } = req.body;
|
const { token } = req.body;
|
||||||
|
// 如果token为空,返回错误信息
|
||||||
if (isEmpty(token)) {
|
if (isEmpty(token)) {
|
||||||
res.json({
|
return sendError(res, 'token is empty');
|
||||||
code: -1,
|
|
||||||
message: 'token is empty',
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
if (!await WebUiDataRuntime.checkLoginRate(WebUiConfigData.loginRate)) {
|
// 检查登录频率
|
||||||
res.json({
|
if (!(await WebUiDataRuntime.checkLoginRate(WebUiConfigData.loginRate))) {
|
||||||
code: -1,
|
return sendError(res, 'login rate limit');
|
||||||
message: 'login rate limit',
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
//验证config.token是否等于token
|
//验证config.token是否等于token
|
||||||
if (WebUiConfigData.token !== token) {
|
if (WebUiConfigData.token !== token) {
|
||||||
res.json({
|
return sendError(res, 'token is invalid');
|
||||||
code: -1,
|
|
||||||
message: 'token is invalid',
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
const signCredential = Buffer.from(JSON.stringify(await AuthHelper.signCredential(WebUiConfigData.token))).toString('base64');
|
// 签发凭证
|
||||||
res.json({
|
const signCredential = Buffer.from(JSON.stringify(await AuthHelper.signCredential(WebUiConfigData.token))).toString(
|
||||||
code: 0,
|
'base64'
|
||||||
message: 'success',
|
);
|
||||||
data: {
|
// 返回成功信息
|
||||||
'Credential': signCredential,
|
return sendSuccess(res, {
|
||||||
},
|
Credential: signCredential,
|
||||||
});
|
});
|
||||||
return;
|
|
||||||
};
|
};
|
||||||
export const LogoutHandler: RequestHandler = (req, res) => {
|
|
||||||
// 这玩意无状态销毁个灯 得想想办法
|
// 退出登录
|
||||||
res.json({
|
export const LogoutHandler: RequestHandler = (_, res) => {
|
||||||
code: 0,
|
// TODO: 这玩意无状态销毁个灯 得想想办法
|
||||||
message: 'success',
|
return sendSuccess(res, null);
|
||||||
});
|
|
||||||
return;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 检查登录状态
|
||||||
export const checkHandler: RequestHandler = async (req, res) => {
|
export const checkHandler: RequestHandler = async (req, res) => {
|
||||||
|
// 获取WebUI配置
|
||||||
const WebUiConfigData = await WebUiConfig.GetWebUIConfig();
|
const WebUiConfigData = await WebUiConfig.GetWebUIConfig();
|
||||||
|
// 获取请求头中的Authorization
|
||||||
const authorization = req.headers.authorization;
|
const authorization = req.headers.authorization;
|
||||||
|
// 检查凭证
|
||||||
try {
|
try {
|
||||||
|
// 从Authorization中获取凭证
|
||||||
const CredentialBase64: string = authorization?.split(' ')[1] as string;
|
const CredentialBase64: string = authorization?.split(' ')[1] as string;
|
||||||
|
// 解析凭证
|
||||||
const Credential = JSON.parse(Buffer.from(CredentialBase64, 'base64').toString());
|
const Credential = JSON.parse(Buffer.from(CredentialBase64, 'base64').toString());
|
||||||
|
// 验证凭证是否在一小时内有效
|
||||||
await AuthHelper.validateCredentialWithinOneHour(WebUiConfigData.token, Credential);
|
await AuthHelper.validateCredentialWithinOneHour(WebUiConfigData.token, Credential);
|
||||||
res.json({
|
// 返回成功信息
|
||||||
code: 0,
|
return sendSuccess(res, null);
|
||||||
message: 'success',
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
res.json({
|
// 返回错误信息
|
||||||
code: -1,
|
return sendError(res, 'Authorization Faild');
|
||||||
message: 'failed',
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
return;
|
|
||||||
};
|
};
|
||||||
|
@@ -1,15 +1,15 @@
|
|||||||
import { RequestHandler } from 'express';
|
import { RequestHandler } from 'express';
|
||||||
import { WebUiDataRuntime } from '../helper/Data';
|
|
||||||
|
|
||||||
export const LogFileListHandler: RequestHandler = async (req, res) => {
|
import { sendSuccess } from '@webapi/utils/response';
|
||||||
res.send({
|
|
||||||
code: 0,
|
// TODO: Implement LogFileListHandler
|
||||||
data: {
|
export const LogFileListHandler: RequestHandler = async (_, res) => {
|
||||||
uin: 0,
|
const fakeData = {
|
||||||
nick: 'NapCat',
|
uin: 0,
|
||||||
avatar: 'https://q1.qlogo.cn/g?b=qq&nk=0&s=640',
|
nick: 'NapCat',
|
||||||
status: 'online',
|
avatar: 'https://q1.qlogo.cn/g?b=qq&nk=0&s=640',
|
||||||
boottime: Date.now()
|
status: 'online',
|
||||||
}
|
boottime: Date.now(),
|
||||||
});
|
};
|
||||||
|
sendSuccess(res, fakeData);
|
||||||
};
|
};
|
||||||
|
@@ -1,79 +1,58 @@
|
|||||||
import { RequestHandler } from 'express';
|
import { RequestHandler } from 'express';
|
||||||
import { WebUiDataRuntime } from '../helper/Data';
|
|
||||||
import { existsSync, readFileSync } from 'node:fs';
|
import { existsSync, readFileSync } from 'node:fs';
|
||||||
import { OneBotConfig } from '@/onebot/config/config';
|
|
||||||
import { resolve } from 'node:path';
|
import { resolve } from 'node:path';
|
||||||
import { webUiPathWrapper } from '@/webui';
|
|
||||||
|
|
||||||
const isEmpty = (data: any) => data === undefined || data === null || data === '';
|
import { OneBotConfig } from '@/onebot/config/config';
|
||||||
export const OB11GetConfigHandler: RequestHandler = async (req, res) => {
|
|
||||||
|
import { webUiPathWrapper } from '@/webui';
|
||||||
|
import { WebUiDataRuntime } from '@webapi/helper/Data';
|
||||||
|
import { sendError, sendSuccess } from '@webapi/utils/response';
|
||||||
|
import { isEmpty } from '@webapi/utils/check';
|
||||||
|
|
||||||
|
// 获取OneBot11配置
|
||||||
|
export const OB11GetConfigHandler: RequestHandler = async (_, res) => {
|
||||||
|
// 获取QQ登录状态
|
||||||
const isLogin = await WebUiDataRuntime.getQQLoginStatus();
|
const isLogin = await WebUiDataRuntime.getQQLoginStatus();
|
||||||
|
// 如果未登录,返回错误
|
||||||
if (!isLogin) {
|
if (!isLogin) {
|
||||||
res.send({
|
return sendError(res, 'Not Login');
|
||||||
code: -1,
|
|
||||||
message: 'Not Login',
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
// 获取登录的QQ号
|
||||||
const uin = await WebUiDataRuntime.getQQLoginUin();
|
const uin = await WebUiDataRuntime.getQQLoginUin();
|
||||||
|
// 读取配置文件
|
||||||
const configFilePath = resolve(webUiPathWrapper.configPath, `./onebot11_${uin}.json`);
|
const configFilePath = resolve(webUiPathWrapper.configPath, `./onebot11_${uin}.json`);
|
||||||
//console.log(configFilePath);
|
// 尝试解析配置文件
|
||||||
let data: OneBotConfig;
|
|
||||||
try {
|
try {
|
||||||
data = JSON.parse(
|
// 读取配置文件
|
||||||
|
const data = JSON.parse(
|
||||||
existsSync(configFilePath)
|
existsSync(configFilePath)
|
||||||
? readFileSync(configFilePath).toString()
|
? readFileSync(configFilePath).toString()
|
||||||
: readFileSync(resolve(webUiPathWrapper.configPath, './onebot11.json')).toString()
|
: readFileSync(resolve(webUiPathWrapper.configPath, './onebot11.json')).toString()
|
||||||
);
|
) as OneBotConfig;
|
||||||
|
// 返回配置文件
|
||||||
|
return sendSuccess(res, data);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
data = {} as OneBotConfig;
|
return sendError(res, 'Config Get Error');
|
||||||
res.send({
|
|
||||||
code: -1,
|
|
||||||
message: 'Config Get Error',
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
res.send({
|
|
||||||
code: 0,
|
|
||||||
message: 'success',
|
|
||||||
data: data,
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 写入OneBot11配置
|
||||||
export const OB11SetConfigHandler: RequestHandler = async (req, res) => {
|
export const OB11SetConfigHandler: RequestHandler = async (req, res) => {
|
||||||
|
// 获取QQ登录状态
|
||||||
const isLogin = await WebUiDataRuntime.getQQLoginStatus();
|
const isLogin = await WebUiDataRuntime.getQQLoginStatus();
|
||||||
|
// 如果未登录,返回错误
|
||||||
if (!isLogin) {
|
if (!isLogin) {
|
||||||
res.send({
|
return sendError(res, 'Not Login');
|
||||||
code: -1,
|
|
||||||
message: 'Not Login',
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
// 如果配置为空,返回错误
|
||||||
if (isEmpty(req.body.config)) {
|
if (isEmpty(req.body.config)) {
|
||||||
res.send({
|
return sendError(res, 'config is empty');
|
||||||
code: -1,
|
|
||||||
message: 'config is empty',
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
let SetResult;
|
// 写入配置
|
||||||
try {
|
try {
|
||||||
await WebUiDataRuntime.setOB11Config(JSON.parse(req.body.config));
|
await WebUiDataRuntime.setOB11Config(JSON.parse(req.body.config));
|
||||||
SetResult = true;
|
return sendSuccess(res, null);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
SetResult = false;
|
return sendError(res, 'Config Set Error');
|
||||||
}
|
}
|
||||||
if (SetResult) {
|
|
||||||
res.send({
|
|
||||||
code: 0,
|
|
||||||
message: 'success',
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
res.send({
|
|
||||||
code: -1,
|
|
||||||
message: 'Config Set Error',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
};
|
};
|
||||||
|
@@ -1,77 +1,64 @@
|
|||||||
import { RequestHandler } from 'express';
|
import { RequestHandler } from 'express';
|
||||||
import { WebUiDataRuntime } from '../helper/Data';
|
|
||||||
|
|
||||||
const isEmpty = (data: any) => data === undefined || data === null || data === '';
|
import { WebUiDataRuntime } from '@webapi/helper/Data';
|
||||||
|
import { isEmpty } from '@webapi/utils/check';
|
||||||
|
import { sendError, sendSuccess } from '@webapi/utils/response';
|
||||||
|
|
||||||
|
// 获取QQ登录二维码
|
||||||
export const QQGetQRcodeHandler: RequestHandler = async (req, res) => {
|
export const QQGetQRcodeHandler: RequestHandler = async (req, res) => {
|
||||||
|
// 判断是否已经登录
|
||||||
if (await WebUiDataRuntime.getQQLoginStatus()) {
|
if (await WebUiDataRuntime.getQQLoginStatus()) {
|
||||||
res.send({
|
// 已经登录
|
||||||
code: -1,
|
return sendError(res, 'QQ Is Logined');
|
||||||
message: 'QQ Is Logined',
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
// 获取二维码
|
||||||
const qrcodeUrl = await WebUiDataRuntime.getQQLoginQrcodeURL();
|
const qrcodeUrl = await WebUiDataRuntime.getQQLoginQrcodeURL();
|
||||||
|
// 判断二维码是否为空
|
||||||
if (isEmpty(qrcodeUrl)) {
|
if (isEmpty(qrcodeUrl)) {
|
||||||
res.send({
|
return sendError(res, 'QRCode Get Error');
|
||||||
code: -1,
|
|
||||||
message: 'QRCode Get Error',
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
res.send({
|
// 返回二维码URL
|
||||||
code: 0,
|
const data = {
|
||||||
message: 'success',
|
qrcode: qrcodeUrl,
|
||||||
data: {
|
};
|
||||||
qrcode: qrcodeUrl,
|
return sendSuccess(res, data);
|
||||||
},
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 获取QQ登录状态
|
||||||
export const QQCheckLoginStatusHandler: RequestHandler = async (req, res) => {
|
export const QQCheckLoginStatusHandler: RequestHandler = async (req, res) => {
|
||||||
res.send({
|
const data = {
|
||||||
code: 0,
|
isLogin: await WebUiDataRuntime.getQQLoginStatus(),
|
||||||
message: 'success',
|
qrcodeurl: await WebUiDataRuntime.getQQLoginQrcodeURL(),
|
||||||
data: {
|
};
|
||||||
isLogin: await WebUiDataRuntime.getQQLoginStatus(),
|
return sendSuccess(res, data);
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 快速登录
|
||||||
export const QQSetQuickLoginHandler: RequestHandler = async (req, res) => {
|
export const QQSetQuickLoginHandler: RequestHandler = async (req, res) => {
|
||||||
|
// 获取QQ号
|
||||||
const { uin } = req.body;
|
const { uin } = req.body;
|
||||||
|
// 判断是否已经登录
|
||||||
const isLogin = await WebUiDataRuntime.getQQLoginStatus();
|
const isLogin = await WebUiDataRuntime.getQQLoginStatus();
|
||||||
if (isLogin) {
|
if (isLogin) {
|
||||||
res.send({
|
return sendError(res, 'QQ Is Logined');
|
||||||
code: -1,
|
|
||||||
message: 'QQ Is Logined',
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
// 判断QQ号是否为空
|
||||||
if (isEmpty(uin)) {
|
if (isEmpty(uin)) {
|
||||||
res.send({
|
return sendError(res, 'uin is empty');
|
||||||
code: -1,
|
|
||||||
message: 'uin is empty',
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取快速登录状态
|
||||||
const { result, message } = await WebUiDataRuntime.requestQuickLogin(uin);
|
const { result, message } = await WebUiDataRuntime.requestQuickLogin(uin);
|
||||||
if (!result) {
|
if (!result) {
|
||||||
res.send({
|
return sendError(res, message);
|
||||||
code: -1,
|
|
||||||
message: message,
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
//本来应该验证 但是http不宜这么搞 建议前端验证
|
//本来应该验证 但是http不宜这么搞 建议前端验证
|
||||||
//isLogin = await WebUiDataRuntime.getQQLoginStatus();
|
//isLogin = await WebUiDataRuntime.getQQLoginStatus();
|
||||||
res.send({
|
return sendSuccess(res, null);
|
||||||
code: 0,
|
|
||||||
message: 'success',
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
export const QQGetQuickLoginListHandler: RequestHandler = async (req, res) => {
|
|
||||||
|
// 获取快速登录列表
|
||||||
|
export const QQGetQuickLoginListHandler: RequestHandler = async (_, res) => {
|
||||||
const quickLoginList = await WebUiDataRuntime.getQQQuickLoginList();
|
const quickLoginList = await WebUiDataRuntime.getQQQuickLoginList();
|
||||||
res.send({
|
return sendSuccess(res, quickLoginList);
|
||||||
code: 0,
|
|
||||||
data: quickLoginList,
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
13
src/webui/src/const/status.ts
Normal file
13
src/webui/src/const/status.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
export enum HttpStatusCode {
|
||||||
|
OK = 200,
|
||||||
|
BadRequest = 400,
|
||||||
|
Unauthorized = 401,
|
||||||
|
Forbidden = 403,
|
||||||
|
NotFound = 404,
|
||||||
|
InternalServerError = 500,
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum ResponseCode {
|
||||||
|
Success = 0,
|
||||||
|
Error = -1,
|
||||||
|
}
|
@@ -1,18 +1,5 @@
|
|||||||
import { OneBotConfig } from '@/onebot/config/config';
|
import { OneBotConfig } from '@/onebot/config/config';
|
||||||
|
|
||||||
interface LoginRuntimeType {
|
|
||||||
LoginCurrentTime: number;
|
|
||||||
LoginCurrentRate: number;
|
|
||||||
QQLoginStatus: boolean;
|
|
||||||
QQQRCodeURL: string;
|
|
||||||
QQLoginUin: string;
|
|
||||||
NapCatHelper: {
|
|
||||||
onQuickLoginRequested: (uin: string) => Promise<{ result: boolean; message: string }>;
|
|
||||||
onOB11ConfigChanged: (ob11: OneBotConfig) => Promise<void>;
|
|
||||||
QQLoginList: string[];
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const LoginRuntime: LoginRuntimeType = {
|
const LoginRuntime: LoginRuntimeType = {
|
||||||
LoginCurrentTime: Date.now(),
|
LoginCurrentTime: Date.now(),
|
||||||
LoginCurrentRate: 0,
|
LoginCurrentRate: 0,
|
||||||
|
@@ -1,15 +1,5 @@
|
|||||||
import crypto from 'crypto';
|
import crypto from 'crypto';
|
||||||
|
|
||||||
interface WebUiCredentialInnerJson {
|
|
||||||
CreatedTime: number;
|
|
||||||
TokenEncoded: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface WebUiCredentialJson {
|
|
||||||
Data: WebUiCredentialInnerJson;
|
|
||||||
Hmac: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class AuthHelper {
|
export class AuthHelper {
|
||||||
private static readonly secretKey = Math.random().toString(36).slice(2);
|
private static readonly secretKey = Math.random().toString(36).slice(2);
|
||||||
|
|
||||||
@@ -24,9 +14,7 @@ export class AuthHelper {
|
|||||||
TokenEncoded: token,
|
TokenEncoded: token,
|
||||||
};
|
};
|
||||||
const jsonString = JSON.stringify(innerJson);
|
const jsonString = JSON.stringify(innerJson);
|
||||||
const hmac = crypto.createHmac('sha256', AuthHelper.secretKey)
|
const hmac = crypto.createHmac('sha256', AuthHelper.secretKey).update(jsonString, 'utf8').digest('hex');
|
||||||
.update(jsonString, 'utf8')
|
|
||||||
.digest('hex');
|
|
||||||
return { Data: innerJson, Hmac: hmac };
|
return { Data: innerJson, Hmac: hmac };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,7 +26,8 @@ export class AuthHelper {
|
|||||||
public static async checkCredential(credentialJson: WebUiCredentialJson): Promise<boolean> {
|
public static async checkCredential(credentialJson: WebUiCredentialJson): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
const jsonString = JSON.stringify(credentialJson.Data);
|
const jsonString = JSON.stringify(credentialJson.Data);
|
||||||
const calculatedHmac = crypto.createHmac('sha256', AuthHelper.secretKey)
|
const calculatedHmac = crypto
|
||||||
|
.createHmac('sha256', AuthHelper.secretKey)
|
||||||
.update(jsonString, 'utf8')
|
.update(jsonString, 'utf8')
|
||||||
.digest('hex');
|
.digest('hex');
|
||||||
return calculatedHmac === credentialJson.Hmac;
|
return calculatedHmac === credentialJson.Hmac;
|
||||||
@@ -53,7 +42,10 @@ export class AuthHelper {
|
|||||||
* @param credentialJson 已签名的凭证JSON对象。
|
* @param credentialJson 已签名的凭证JSON对象。
|
||||||
* @returns 布尔值,表示凭证是否有效且token匹配。
|
* @returns 布尔值,表示凭证是否有效且token匹配。
|
||||||
*/
|
*/
|
||||||
public static async validateCredentialWithinOneHour(token: string, credentialJson: WebUiCredentialJson): Promise<boolean> {
|
public static async validateCredentialWithinOneHour(
|
||||||
|
token: string,
|
||||||
|
credentialJson: WebUiCredentialJson
|
||||||
|
): Promise<boolean> {
|
||||||
const isValid = await AuthHelper.checkCredential(credentialJson);
|
const isValid = await AuthHelper.checkCredential(credentialJson);
|
||||||
if (!isValid) {
|
if (!isValid) {
|
||||||
return false;
|
return false;
|
||||||
|
@@ -3,7 +3,6 @@ import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|||||||
import * as net from 'node:net';
|
import * as net from 'node:net';
|
||||||
import { resolve } from 'node:path';
|
import { resolve } from 'node:path';
|
||||||
|
|
||||||
|
|
||||||
// 限制尝试端口的次数,避免死循环
|
// 限制尝试端口的次数,避免死循环
|
||||||
const MAX_PORT_TRY = 100;
|
const MAX_PORT_TRY = 100;
|
||||||
|
|
||||||
@@ -64,14 +63,6 @@ async function tryUsePort(port: number, host: string, tryCount: number = 0): Pro
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface WebUiConfigType {
|
|
||||||
host: string;
|
|
||||||
port: number;
|
|
||||||
prefix: string;
|
|
||||||
token: string;
|
|
||||||
loginRate: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 读取当前目录下名为 webui.json 的配置文件,如果不存在则创建初始化配置文件
|
// 读取当前目录下名为 webui.json 的配置文件,如果不存在则创建初始化配置文件
|
||||||
export class WebUiConfigWrapper {
|
export class WebUiConfigWrapper {
|
||||||
WebUiConfigData: WebUiConfigType | undefined = undefined;
|
WebUiConfigData: WebUiConfigType | undefined = undefined;
|
||||||
@@ -114,14 +105,18 @@ export class WebUiConfigWrapper {
|
|||||||
// 不希望回写的配置放后面
|
// 不希望回写的配置放后面
|
||||||
|
|
||||||
// 查询主机地址是否可用
|
// 查询主机地址是否可用
|
||||||
const [host_err, host] = await tryUseHost(parsedConfig.host).then(data => [null, data]).catch(err => [err, null]);
|
const [host_err, host] = await tryUseHost(parsedConfig.host)
|
||||||
|
.then((data) => [null, data])
|
||||||
|
.catch((err) => [err, null]);
|
||||||
if (host_err) {
|
if (host_err) {
|
||||||
console.log('host不可用', host_err);
|
console.log('host不可用', host_err);
|
||||||
parsedConfig.port = 0; // 设置为0,禁用WebUI
|
parsedConfig.port = 0; // 设置为0,禁用WebUI
|
||||||
} else {
|
} else {
|
||||||
parsedConfig.host = host;
|
parsedConfig.host = host;
|
||||||
// 修正端口占用情况
|
// 修正端口占用情况
|
||||||
const [port_err, port] = await tryUsePort(parsedConfig.port, parsedConfig.host).then(data => [null, data]).catch(err => [err, null]);
|
const [port_err, port] = await tryUsePort(parsedConfig.port, parsedConfig.host)
|
||||||
|
.then((data) => [null, data])
|
||||||
|
.catch((err) => [err, null]);
|
||||||
if (port_err) {
|
if (port_err) {
|
||||||
console.log('port不可用', port_err);
|
console.log('port不可用', port_err);
|
||||||
parsedConfig.port = 0; // 设置为0,禁用WebUI
|
parsedConfig.port = 0; // 设置为0,禁用WebUI
|
||||||
@@ -137,4 +132,3 @@ export class WebUiConfigWrapper {
|
|||||||
return defaultconfig; // 理论上这行代码到不了,到了只能返回默认配置了
|
return defaultconfig; // 理论上这行代码到不了,到了只能返回默认配置了
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
46
src/webui/src/middleware/auth.ts
Normal file
46
src/webui/src/middleware/auth.ts
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import { NextFunction, Request, Response } from 'express';
|
||||||
|
|
||||||
|
import { WebUiConfig } from '@/webui';
|
||||||
|
|
||||||
|
import { AuthHelper } from '@webapi/helper/SignToken';
|
||||||
|
import { sendError } from '@webapi/utils/response';
|
||||||
|
|
||||||
|
// 鉴权中间件
|
||||||
|
export async function auth(req: Request, res: Response, next: NextFunction) {
|
||||||
|
// 判断当前url是否为/login 如果是跳过鉴权
|
||||||
|
if (req.url == '/auth/login') {
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 判断是否有Authorization头
|
||||||
|
if (req.headers?.authorization) {
|
||||||
|
// 切割参数以获取token
|
||||||
|
const authorization = req.headers.authorization.split(' ');
|
||||||
|
// 当Bearer后面没有参数时
|
||||||
|
if (authorization.length < 2) {
|
||||||
|
return sendError(res, 'Unauthorized');
|
||||||
|
}
|
||||||
|
// 获取token
|
||||||
|
const token = authorization[1];
|
||||||
|
// 解析token
|
||||||
|
let Credential: WebUiCredentialJson;
|
||||||
|
try {
|
||||||
|
Credential = JSON.parse(Buffer.from(token, 'base64').toString('utf-8'));
|
||||||
|
} catch (e) {
|
||||||
|
return sendError(res, 'Unauthorized');
|
||||||
|
}
|
||||||
|
// 获取配置
|
||||||
|
const config = await WebUiConfig.GetWebUIConfig();
|
||||||
|
// 验证凭证在1小时内有效且token与原始token相同
|
||||||
|
const credentialJson = await AuthHelper.validateCredentialWithinOneHour(config.token, Credential);
|
||||||
|
if (credentialJson) {
|
||||||
|
// 通过验证
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
// 验证失败
|
||||||
|
return sendError(res, 'Unauthorized');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 没有Authorization头
|
||||||
|
return sendError(res, 'Unauthorized');
|
||||||
|
}
|
9
src/webui/src/middleware/cors.ts
Normal file
9
src/webui/src/middleware/cors.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import type { RequestHandler } from 'express';
|
||||||
|
|
||||||
|
// CORS 中间件,跨域用
|
||||||
|
export const cors: RequestHandler = (_, res, next) => {
|
||||||
|
res.header('Access-Control-Allow-Origin', '*');
|
||||||
|
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
|
||||||
|
res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, Authorization');
|
||||||
|
next();
|
||||||
|
};
|
@@ -1,7 +1,11 @@
|
|||||||
import { Router } from 'express';
|
import { Router } from 'express';
|
||||||
import { OB11GetConfigHandler, OB11SetConfigHandler } from '../api/OB11Config';
|
|
||||||
|
import { OB11GetConfigHandler, OB11SetConfigHandler } from '@webapi/api/OB11Config';
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
// router:读取配置
|
||||||
router.post('/GetConfig', OB11GetConfigHandler);
|
router.post('/GetConfig', OB11GetConfigHandler);
|
||||||
|
// router:写入配置
|
||||||
router.post('/SetConfig', OB11SetConfigHandler);
|
router.post('/SetConfig', OB11SetConfigHandler);
|
||||||
|
|
||||||
export { router as OB11ConfigRouter };
|
export { router as OB11ConfigRouter };
|
||||||
|
@@ -1,14 +1,20 @@
|
|||||||
import { Router } from 'express';
|
import { Router } from 'express';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
QQCheckLoginStatusHandler,
|
QQCheckLoginStatusHandler,
|
||||||
QQGetQRcodeHandler,
|
QQGetQRcodeHandler,
|
||||||
QQGetQuickLoginListHandler,
|
QQGetQuickLoginListHandler,
|
||||||
QQSetQuickLoginHandler,
|
QQSetQuickLoginHandler,
|
||||||
} from '../api/QQLogin';
|
} from '@webapi/api/QQLogin';
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
// router:获取快速登录列表
|
||||||
router.all('/GetQuickLoginList', QQGetQuickLoginListHandler);
|
router.all('/GetQuickLoginList', QQGetQuickLoginListHandler);
|
||||||
|
// router:检查QQ登录状态
|
||||||
router.post('/CheckLoginStatus', QQCheckLoginStatusHandler);
|
router.post('/CheckLoginStatus', QQCheckLoginStatusHandler);
|
||||||
|
// router:获取QQ登录二维码
|
||||||
router.post('/GetQQLoginQrcode', QQGetQRcodeHandler);
|
router.post('/GetQQLoginQrcode', QQGetQRcodeHandler);
|
||||||
|
// router:设置QQ快速登录
|
||||||
router.post('/SetQuickLogin', QQSetQuickLoginHandler);
|
router.post('/SetQuickLogin', QQSetQuickLoginHandler);
|
||||||
|
|
||||||
export { router as QQLoginRouter };
|
export { router as QQLoginRouter };
|
||||||
|
@@ -1,9 +1,13 @@
|
|||||||
import { Router } from 'express';
|
import { Router } from 'express';
|
||||||
import { checkHandler, LoginHandler, LogoutHandler } from '../api/Auth';
|
|
||||||
|
import { checkHandler, LoginHandler, LogoutHandler } from '@webapi/api/Auth';
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
// router:登录
|
||||||
router.post('/login', LoginHandler);
|
router.post('/login', LoginHandler);
|
||||||
|
// router:检查登录状态
|
||||||
router.post('/check', checkHandler);
|
router.post('/check', checkHandler);
|
||||||
|
// router:注销
|
||||||
router.post('/logout', LogoutHandler);
|
router.post('/logout', LogoutHandler);
|
||||||
|
|
||||||
export { router as AuthRouter };
|
export { router as AuthRouter };
|
||||||
|
@@ -1,67 +1,30 @@
|
|||||||
import { NextFunction, Request, Response, Router } from 'express';
|
/**
|
||||||
import { AuthHelper } from '../../src/helper/SignToken';
|
* @file 所有路由的入口文件
|
||||||
import { QQLoginRouter } from './QQLogin';
|
*/
|
||||||
import { AuthRouter } from './auth';
|
|
||||||
import { OB11ConfigRouter } from './OB11Config';
|
import { Router } from 'express';
|
||||||
import { WebUiConfig } from '@/webui';
|
|
||||||
|
import { OB11ConfigRouter } from '@webapi/router/OB11Config';
|
||||||
|
import { auth } from '@webapi/middleware/auth';
|
||||||
|
import { sendSuccess } from '@webapi/utils/response';
|
||||||
|
|
||||||
|
import { QQLoginRouter } from '@webapi/router/QQLogin';
|
||||||
|
import { AuthRouter } from '@webapi/router/auth';
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
export async function AuthApi(req: Request, res: Response, next: NextFunction) {
|
// 鉴权中间件
|
||||||
//判断当前url是否为/login 如果是跳过鉴权
|
router.use(auth);
|
||||||
if (req.url == '/auth/login') {
|
|
||||||
next();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (req.headers?.authorization) {
|
|
||||||
const authorization = req.headers.authorization.split(' ');
|
|
||||||
if (authorization.length < 2) {
|
|
||||||
res.json({
|
|
||||||
code: -1,
|
|
||||||
msg: 'Unauthorized',
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const token = authorization[1];
|
|
||||||
let Credential: any;
|
|
||||||
try {
|
|
||||||
Credential = JSON.parse(Buffer.from(token, 'base64').toString('utf-8'));
|
|
||||||
} catch (e) {
|
|
||||||
res.json({
|
|
||||||
code: -1,
|
|
||||||
msg: 'Unauthorized',
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const config = await WebUiConfig.GetWebUIConfig();
|
|
||||||
const credentialJson = await AuthHelper.validateCredentialWithinOneHour(config.token, Credential);
|
|
||||||
if (credentialJson) {
|
|
||||||
//通过验证
|
|
||||||
next();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
res.json({
|
|
||||||
code: -1,
|
|
||||||
msg: 'Unauthorized',
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
res.json({
|
// router:测试用
|
||||||
code: -1,
|
router.all('/test', (_, res) => {
|
||||||
msg: 'Server Error',
|
return sendSuccess(res);
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
router.use(AuthApi);
|
|
||||||
router.all('/test', (req, res) => {
|
|
||||||
res.json({
|
|
||||||
code: 0,
|
|
||||||
msg: 'ok',
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
// router:WebUI登录相关路由
|
||||||
router.use('/auth', AuthRouter);
|
router.use('/auth', AuthRouter);
|
||||||
|
// router:QQ登录相关路由
|
||||||
router.use('/QQLogin', QQLoginRouter);
|
router.use('/QQLogin', QQLoginRouter);
|
||||||
|
// router:OB11配置相关路由
|
||||||
router.use('/OB11Config', OB11ConfigRouter);
|
router.use('/OB11Config', OB11ConfigRouter);
|
||||||
|
|
||||||
export { router as ALLRouter };
|
export { router as ALLRouter };
|
||||||
|
7
src/webui/src/types/config.d.ts
vendored
Normal file
7
src/webui/src/types/config.d.ts
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
interface WebUiConfigType {
|
||||||
|
host: string;
|
||||||
|
port: number;
|
||||||
|
prefix: string;
|
||||||
|
token: string;
|
||||||
|
loginRate: number;
|
||||||
|
}
|
12
src/webui/src/types/data.d.ts
vendored
Normal file
12
src/webui/src/types/data.d.ts
vendored
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
interface LoginRuntimeType {
|
||||||
|
LoginCurrentTime: number;
|
||||||
|
LoginCurrentRate: number;
|
||||||
|
QQLoginStatus: boolean;
|
||||||
|
QQQRCodeURL: string;
|
||||||
|
QQLoginUin: string;
|
||||||
|
NapCatHelper: {
|
||||||
|
onQuickLoginRequested: (uin: string) => Promise<{ result: boolean; message: string }>;
|
||||||
|
onOB11ConfigChanged: (ob11: OneBotConfig) => Promise<void>;
|
||||||
|
QQLoginList: string[];
|
||||||
|
};
|
||||||
|
}
|
7
src/webui/src/types/server.d.ts
vendored
Normal file
7
src/webui/src/types/server.d.ts
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
interface APIResponse<T> {
|
||||||
|
code: number;
|
||||||
|
message: string;
|
||||||
|
data: T;
|
||||||
|
}
|
||||||
|
|
||||||
|
type Protocol = 'http' | 'https' | 'ws' | 'wss';
|
9
src/webui/src/types/sign_token.d.ts
vendored
Normal file
9
src/webui/src/types/sign_token.d.ts
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
interface WebUiCredentialInnerJson {
|
||||||
|
CreatedTime: number;
|
||||||
|
TokenEncoded: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface WebUiCredentialJson {
|
||||||
|
Data: WebUiCredentialInnerJson;
|
||||||
|
Hmac: string;
|
||||||
|
}
|
1
src/webui/src/utils/check.ts
Normal file
1
src/webui/src/utils/check.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export const isEmpty = <T>(data: T) => data === undefined || data === null || data === '';
|
26
src/webui/src/utils/response.ts
Normal file
26
src/webui/src/utils/response.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import type { Response } from 'express';
|
||||||
|
|
||||||
|
import { ResponseCode, HttpStatusCode } from '@webapi/const/status';
|
||||||
|
|
||||||
|
export const sendResponse = <T>(res: Response, data?: T, code: ResponseCode = 0, message = 'success') => {
|
||||||
|
res.status(HttpStatusCode.OK).json({
|
||||||
|
code,
|
||||||
|
message,
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const sendError = (res: Response, message = 'error') => {
|
||||||
|
res.status(HttpStatusCode.OK).json({
|
||||||
|
code: ResponseCode.Error,
|
||||||
|
message,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const sendSuccess = <T>(res: Response, data?: T, message = 'success') => {
|
||||||
|
res.status(HttpStatusCode.OK).json({
|
||||||
|
code: ResponseCode.Success,
|
||||||
|
data,
|
||||||
|
message,
|
||||||
|
});
|
||||||
|
};
|
47
src/webui/src/utils/url.ts
Normal file
47
src/webui/src/utils/url.ts
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
/**
|
||||||
|
* @file URL工具
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { isIP } from 'node:net';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将 host(主机地址) 转换为标准格式
|
||||||
|
* @param host 主机地址
|
||||||
|
* @returns 标准格式的IP地址
|
||||||
|
* @example normalizeHost('10.0.3.2') => '10.0.3.2'
|
||||||
|
* @example normalizeHost('0.0.0.0') => '127.0.0.1'
|
||||||
|
* @example normalizeHost('2001:4860:4801:51::27') => '[2001:4860:4801:51::27]'
|
||||||
|
*/
|
||||||
|
export const normalizeHost = (host: string) => {
|
||||||
|
if (host === '0.0.0.0') return '127.0.0.1';
|
||||||
|
if (isIP(host) === 6) return `[${host}]`;
|
||||||
|
return host;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建URL
|
||||||
|
* @param host 主机地址
|
||||||
|
* @param port 端口
|
||||||
|
* @param path URL路径
|
||||||
|
* @param search URL参数
|
||||||
|
* @returns 完整URL
|
||||||
|
* @example createUrl('127.0.0.1', '8080', '/api', { token: '123456' }) => 'http://127.0.0.1:8080/api?token=123456'
|
||||||
|
* @example createUrl('baidu.com', '80', void 0, void 0, 'https') => 'https://baidu.com:80/'
|
||||||
|
*/
|
||||||
|
export const createUrl = (
|
||||||
|
host: string,
|
||||||
|
port: string,
|
||||||
|
path = '/',
|
||||||
|
search?: Record<string, any>,
|
||||||
|
protocol: Protocol = 'http'
|
||||||
|
) => {
|
||||||
|
const url = new URL(`${protocol}://${normalizeHost(host)}`);
|
||||||
|
url.port = port;
|
||||||
|
url.pathname = path;
|
||||||
|
if (search) {
|
||||||
|
for (const key in search) {
|
||||||
|
url.searchParams.set(key, search[key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return url.toString();
|
||||||
|
};
|
@@ -1,34 +1,37 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "ES2020",
|
"target": "ES2020",
|
||||||
"useDefineForClassFields": true,
|
"useDefineForClassFields": true,
|
||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
"lib": [
|
"lib": [
|
||||||
"ES2020",
|
"ES2020",
|
||||||
"DOM",
|
"DOM",
|
||||||
"DOM.Iterable"
|
"DOM.Iterable"
|
||||||
],
|
],
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"moduleResolution": "Node",
|
"moduleResolution": "Node",
|
||||||
"experimentalDecorators": true,
|
"experimentalDecorators": true,
|
||||||
"allowImportingTsExtensions": false,
|
"allowImportingTsExtensions": false,
|
||||||
"allowSyntheticDefaultImports": true,
|
"allowSyntheticDefaultImports": true,
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
"jsx": "preserve",
|
"jsx": "preserve",
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"noUnusedLocals": false,
|
"noUnusedLocals": false,
|
||||||
"noUnusedParameters": false,
|
"noUnusedParameters": false,
|
||||||
"noFallthroughCasesInSwitch": true,
|
"noFallthroughCasesInSwitch": true,
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"paths": {
|
"paths": {
|
||||||
"@*": [
|
"@/*": [
|
||||||
"./src*"
|
"./src/*"
|
||||||
]
|
],
|
||||||
}
|
"@webapi/*": [
|
||||||
},
|
"./src/webui/src/*"
|
||||||
"include": [
|
],
|
||||||
"src/**/*.ts"
|
}
|
||||||
]
|
},
|
||||||
|
"include": [
|
||||||
|
"src/**/*.ts"
|
||||||
|
]
|
||||||
}
|
}
|
@@ -50,59 +50,63 @@ const ShellBaseConfigPlugin: PluginOption[] = [
|
|||||||
nodeResolve(),
|
nodeResolve(),
|
||||||
];
|
];
|
||||||
|
|
||||||
const ShellBaseConfig = () => defineConfig({
|
const ShellBaseConfig = () =>
|
||||||
resolve: {
|
defineConfig({
|
||||||
conditions: ['node', 'default'],
|
resolve: {
|
||||||
alias: {
|
conditions: ['node', 'default'],
|
||||||
'@/core': resolve(__dirname, './src/core'),
|
alias: {
|
||||||
'@': resolve(__dirname, './src'),
|
'@/core': resolve(__dirname, './src/core'),
|
||||||
'./lib-cov/fluent-ffmpeg': './lib/fluent-ffmpeg',
|
'@': resolve(__dirname, './src'),
|
||||||
},
|
'./lib-cov/fluent-ffmpeg': './lib/fluent-ffmpeg',
|
||||||
},
|
'@webapi': resolve(__dirname, './src/webui/src'),
|
||||||
build: {
|
|
||||||
sourcemap: false,
|
|
||||||
target: 'esnext',
|
|
||||||
minify: false,
|
|
||||||
lib: {
|
|
||||||
entry: {
|
|
||||||
'napcat': 'src/shell/napcat.ts',
|
|
||||||
'audio-worker': 'src/common/audio-worker.ts',
|
|
||||||
},
|
},
|
||||||
formats: ['es'],
|
|
||||||
fileName: (_, entryName) => `${entryName}.mjs`,
|
|
||||||
},
|
},
|
||||||
rollupOptions: {
|
build: {
|
||||||
external: [...nodeModules, ...external],
|
sourcemap: false,
|
||||||
|
target: 'esnext',
|
||||||
|
minify: false,
|
||||||
|
lib: {
|
||||||
|
entry: {
|
||||||
|
napcat: 'src/shell/napcat.ts',
|
||||||
|
'audio-worker': 'src/common/audio-worker.ts',
|
||||||
|
},
|
||||||
|
formats: ['es'],
|
||||||
|
fileName: (_, entryName) => `${entryName}.mjs`,
|
||||||
|
},
|
||||||
|
rollupOptions: {
|
||||||
|
external: [...nodeModules, ...external],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
});
|
||||||
});
|
|
||||||
|
|
||||||
const FrameworkBaseConfig = () => defineConfig({
|
const FrameworkBaseConfig = () =>
|
||||||
resolve: {
|
defineConfig({
|
||||||
conditions: ['node', 'default'],
|
resolve: {
|
||||||
alias: {
|
conditions: ['node', 'default'],
|
||||||
'@/core': resolve(__dirname, './src/core'),
|
alias: {
|
||||||
'@': resolve(__dirname, './src'),
|
'@/core': resolve(__dirname, './src/core'),
|
||||||
'./lib-cov/fluent-ffmpeg': './lib/fluent-ffmpeg',
|
'@': resolve(__dirname, './src'),
|
||||||
},
|
'./lib-cov/fluent-ffmpeg': './lib/fluent-ffmpeg',
|
||||||
},
|
'@webapi': resolve(__dirname, './src/webui/src'),
|
||||||
build: {
|
|
||||||
sourcemap: false,
|
|
||||||
target: 'esnext',
|
|
||||||
minify: false,
|
|
||||||
lib: {
|
|
||||||
entry: {
|
|
||||||
'napcat': 'src/framework/napcat.ts',
|
|
||||||
'audio-worker': 'src/common/audio-worker.ts',
|
|
||||||
},
|
},
|
||||||
formats: ['es'],
|
|
||||||
fileName: (_, entryName) => `${entryName}.mjs`,
|
|
||||||
},
|
},
|
||||||
rollupOptions: {
|
build: {
|
||||||
external: [...nodeModules, ...external],
|
sourcemap: false,
|
||||||
|
target: 'esnext',
|
||||||
|
minify: false,
|
||||||
|
lib: {
|
||||||
|
entry: {
|
||||||
|
napcat: 'src/framework/napcat.ts',
|
||||||
|
'audio-worker': 'src/common/audio-worker.ts',
|
||||||
|
},
|
||||||
|
formats: ['es'],
|
||||||
|
fileName: (_, entryName) => `${entryName}.mjs`,
|
||||||
|
},
|
||||||
|
rollupOptions: {
|
||||||
|
external: [...nodeModules, ...external],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
});
|
||||||
});
|
|
||||||
|
|
||||||
export default defineConfig(({ mode }): UserConfig => {
|
export default defineConfig(({ mode }): UserConfig => {
|
||||||
if (mode === 'shell') {
|
if (mode === 'shell') {
|
||||||
|
Reference in New Issue
Block a user