mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2025-07-19 12:03:37 +00:00
Compare commits
157 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
a849b5edc0 | ||
![]() |
be2f3be4bd | ||
![]() |
f7cc25adc1 | ||
![]() |
441a34e0bf | ||
![]() |
7a4c82bded | ||
![]() |
5fa2e9d8f5 | ||
![]() |
b40873ada7 | ||
![]() |
4db65cf860 | ||
![]() |
58d2bd3c81 | ||
![]() |
6534d05b76 | ||
![]() |
2d7de174c5 | ||
![]() |
79aa1dc67f | ||
![]() |
7792ad9ea0 | ||
![]() |
be6671923b | ||
![]() |
0fa1b3f044 | ||
![]() |
4ab751696b | ||
![]() |
dce4eedf7d | ||
![]() |
129b67b751 | ||
![]() |
9ab776d53a | ||
![]() |
2759a34d96 | ||
![]() |
2f9f42750e | ||
![]() |
30abd1f904 | ||
![]() |
008075466e | ||
![]() |
5b4035c320 | ||
![]() |
e3feb6a73c | ||
![]() |
40fe73317d | ||
![]() |
073745030c | ||
![]() |
c523437506 | ||
![]() |
9eef570d37 | ||
![]() |
be37b8cbbd | ||
![]() |
c635496677 | ||
![]() |
8753ecfd92 | ||
![]() |
5eda1f2870 | ||
![]() |
d5a60074f7 | ||
![]() |
91df57d932 | ||
![]() |
e27d4c4302 | ||
![]() |
55847f6e10 | ||
![]() |
b39d8bae27 | ||
![]() |
b0cf23f775 | ||
![]() |
c641246056 | ||
![]() |
1e5bc9bbea | ||
![]() |
99b504b5f6 | ||
![]() |
1146454fec | ||
![]() |
805e014a75 | ||
![]() |
d3acd1efc1 | ||
![]() |
9fcd218a5a | ||
![]() |
d6a0830cfe | ||
![]() |
40a63b9c66 | ||
![]() |
eeb19a04cc | ||
![]() |
91e457eb03 | ||
![]() |
78d1919d7f | ||
![]() |
8393acf173 | ||
![]() |
bca152a047 | ||
![]() |
6a15908a93 | ||
![]() |
c626bbab74 | ||
![]() |
c5c7dcc6f2 | ||
![]() |
03dafe727e | ||
![]() |
744921c45e | ||
![]() |
abc4a4dcba | ||
![]() |
7e0da2f929 | ||
![]() |
a3b70d0f1f | ||
![]() |
d291724f06 | ||
![]() |
122a9ca2cc | ||
![]() |
48aaddd32b | ||
![]() |
47401af856 | ||
![]() |
709adfd812 | ||
![]() |
038d0c5412 | ||
![]() |
6bb4362ed4 | ||
![]() |
e617f9452d | ||
![]() |
6d8bb49a37 | ||
![]() |
4f6073ee86 | ||
![]() |
2e7176304b | ||
![]() |
e36cf11004 | ||
![]() |
0e49e17f68 | ||
![]() |
524de45f6b | ||
![]() |
85741a4b60 | ||
![]() |
610e07ac32 | ||
![]() |
f9ccb8c978 | ||
![]() |
ea3d069e49 | ||
![]() |
3e6024f183 | ||
![]() |
337871693a | ||
![]() |
2d921c4577 | ||
![]() |
9accff7323 | ||
![]() |
88b1ee8c31 | ||
![]() |
3ac618bb4e | ||
![]() |
0051df3741 | ||
![]() |
7eb4e010b0 | ||
![]() |
33cc23ada3 | ||
![]() |
e5aee372e3 | ||
![]() |
6b6ce4a761 | ||
![]() |
8c4ea7f8f2 | ||
![]() |
c8b268b806 | ||
![]() |
cf5e0e0f14 | ||
![]() |
7b79f9cc17 | ||
![]() |
708d599966 | ||
![]() |
1ecd5b78e6 | ||
![]() |
fca2e3c51a | ||
![]() |
95ea761b2d | ||
![]() |
6b3bfa1ee9 | ||
![]() |
df3e302a9d | ||
![]() |
c88a68c9a8 | ||
![]() |
92d01b9cdd | ||
![]() |
fe04fa5986 | ||
![]() |
c382f541b4 | ||
![]() |
f420527207 | ||
![]() |
e0c83ebf79 | ||
![]() |
c7fb18fc08 | ||
![]() |
2db8ab937d | ||
![]() |
819f5dd8e5 | ||
![]() |
d4a8ed735e | ||
![]() |
f07e3bb4d5 | ||
![]() |
fa5ef0c221 | ||
![]() |
da7499ec0b | ||
![]() |
d2f4327e44 | ||
![]() |
2eba640180 | ||
![]() |
29ae55f340 | ||
![]() |
3d2bca3f9f | ||
![]() |
7fd8c0c822 | ||
![]() |
a9e9c81505 | ||
![]() |
e8cc68bdea | ||
![]() |
9e51a661a4 | ||
![]() |
a167aaf55f | ||
![]() |
a54ecbcaa0 | ||
![]() |
788462cdfa | ||
![]() |
45c5965b99 | ||
![]() |
ce7614de46 | ||
![]() |
9f78e1ce1e | ||
![]() |
2c7b0625e8 | ||
![]() |
c3a5da9be1 | ||
![]() |
ca796e1920 | ||
![]() |
7ce04cf781 | ||
![]() |
024a3eb760 | ||
![]() |
1702f429b4 | ||
![]() |
96d79cf495 | ||
![]() |
a6a11a7026 | ||
![]() |
970a49e2a5 | ||
![]() |
2e013ed4f5 | ||
![]() |
f8c396b1fe | ||
![]() |
b54870cb60 | ||
![]() |
84318acb18 | ||
![]() |
a11a042b93 | ||
![]() |
8a8aa8f62c | ||
![]() |
93f78f4db5 | ||
![]() |
404bfdd5e6 | ||
![]() |
e4577dc2f1 | ||
![]() |
5c932e5a27 | ||
![]() |
4bd63c6267 | ||
![]() |
aabe24f903 | ||
![]() |
69cebd7fbc | ||
![]() |
8da371176a | ||
![]() |
dd08adf1d1 | ||
![]() |
2f67bef139 | ||
![]() |
8968c51cdc | ||
![]() |
f2fdcc9289 | ||
![]() |
aa3a575cbe | ||
![]() |
11816d038d | ||
![]() |
6a990edb38 |
115
.vscode/launch.json
vendored
Normal file
115
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,115 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "dev:shell",
|
||||
"runtimeExecutable": "npm",
|
||||
"runtimeArgs": [
|
||||
"run",
|
||||
"dev:shell"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "build:shell",
|
||||
"runtimeExecutable": "npm",
|
||||
"runtimeArgs": [
|
||||
"run",
|
||||
"build:shell"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "build:universal",
|
||||
"runtimeExecutable": "npm",
|
||||
"runtimeArgs": [
|
||||
"run",
|
||||
"build:universal"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "build:framework",
|
||||
"runtimeExecutable": "npm",
|
||||
"runtimeArgs": [
|
||||
"run",
|
||||
"build:framework"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "build:webui",
|
||||
"runtimeExecutable": "npm",
|
||||
"runtimeArgs": [
|
||||
"run",
|
||||
"build:webui"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "dev:universal",
|
||||
"runtimeExecutable": "npm",
|
||||
"runtimeArgs": [
|
||||
"run",
|
||||
"dev:universal"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "dev:framework",
|
||||
"runtimeExecutable": "npm",
|
||||
"runtimeArgs": [
|
||||
"run",
|
||||
"dev:framework"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "dev:webui",
|
||||
"runtimeExecutable": "npm",
|
||||
"runtimeArgs": [
|
||||
"run",
|
||||
"dev:webui"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "lint",
|
||||
"runtimeExecutable": "npm",
|
||||
"runtimeArgs": [
|
||||
"run",
|
||||
"lint"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "depend",
|
||||
"runtimeExecutable": "npm",
|
||||
"runtimeArgs": [
|
||||
"run",
|
||||
"depend"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "dev:depend",
|
||||
"runtimeExecutable": "npm",
|
||||
"runtimeArgs": [
|
||||
"run",
|
||||
"dev:depend"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
4
.vscode/settings.json
vendored
4
.vscode/settings.json
vendored
@@ -6,5 +6,7 @@
|
||||
"tsconfig.json": "tsconfig.*.json, env.d.ts, vite.config.ts",
|
||||
"package.json": "package-lock.json, eslint*, .prettier*, .editorconfig, manifest.json, logo.png, .gitignore, LICENSE"
|
||||
},
|
||||
"css.customData": [".vscode/tailwindcss.json"],
|
||||
"css.customData": [
|
||||
".vscode/tailwindcss.json"
|
||||
],
|
||||
}
|
128
CODE_OF_CONDUCT.md
Normal file
128
CODE_OF_CONDUCT.md
Normal file
@@ -0,0 +1,128 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
We as members, contributors, and leaders pledge to make participation in our
|
||||
community a harassment-free experience for everyone, regardless of age, body
|
||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||
identity and expression, level of experience, education, socio-economic status,
|
||||
nationality, personal appearance, race, religion, or sexual identity
|
||||
and orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||
diverse, inclusive, and healthy community.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to a positive environment for our
|
||||
community include:
|
||||
|
||||
* Demonstrating empathy and kindness toward other people
|
||||
* Being respectful of differing opinions, viewpoints, and experiences
|
||||
* Giving and gracefully accepting constructive feedback
|
||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
* Focusing on what is best not just for us as individuals, but for the
|
||||
overall community
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
* The use of sexualized language or imagery, and sexual attention or
|
||||
advances of any kind
|
||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or email
|
||||
address, without their explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Enforcement Responsibilities
|
||||
|
||||
Community leaders are responsible for clarifying and enforcing our standards of
|
||||
acceptable behavior and will take appropriate and fair corrective action in
|
||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||
or harmful.
|
||||
|
||||
Community leaders have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||
decisions when appropriate.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all community spaces, and also applies when
|
||||
an individual is officially representing the community in public spaces.
|
||||
Examples of representing our community include using an official e-mail address,
|
||||
posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to the community leaders responsible for enforcement at
|
||||
nanaeonn@outlook.com.
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
reporter of any incident.
|
||||
|
||||
## Enforcement Guidelines
|
||||
|
||||
Community leaders will follow these Community Impact Guidelines in determining
|
||||
the consequences for any action they deem in violation of this Code of Conduct:
|
||||
|
||||
### 1. Correction
|
||||
|
||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||
unprofessional or unwelcome in the community.
|
||||
|
||||
**Consequence**: A private, written warning from community leaders, providing
|
||||
clarity around the nature of the violation and an explanation of why the
|
||||
behavior was inappropriate. A public apology may be requested.
|
||||
|
||||
### 2. Warning
|
||||
|
||||
**Community Impact**: A violation through a single incident or series
|
||||
of actions.
|
||||
|
||||
**Consequence**: A warning with consequences for continued behavior. No
|
||||
interaction with the people involved, including unsolicited interaction with
|
||||
those enforcing the Code of Conduct, for a specified period of time. This
|
||||
includes avoiding interactions in community spaces as well as external channels
|
||||
like social media. Violating these terms may lead to a temporary or
|
||||
permanent ban.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
**Community Impact**: A serious violation of community standards, including
|
||||
sustained inappropriate behavior.
|
||||
|
||||
**Consequence**: A temporary ban from any sort of interaction or public
|
||||
communication with the community for a specified period of time. No public or
|
||||
private interaction with the people involved, including unsolicited interaction
|
||||
with those enforcing the Code of Conduct, is allowed during this period.
|
||||
Violating these terms may lead to a permanent ban.
|
||||
|
||||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within
|
||||
the community.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||
version 2.0, available at
|
||||
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
||||
|
||||
Community Impact Guidelines were inspired by [Mozilla's code of conduct
|
||||
enforcement ladder](https://github.com/mozilla/diversity).
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at
|
||||
https://www.contributor-covenant.org/faq. Translations are available at
|
||||
https://www.contributor-covenant.org/translations.
|
83
README.md
83
README.md
@@ -1,67 +1,62 @@
|
||||
<div align="center">
|
||||
|
||||
|
||||
# NapCat
|
||||
|
||||

|
||||
|
||||
|
||||
_Modern protocol-side framework implemented based on NTQQ._
|
||||
|
||||
> 云起兮风生,心向远方兮路未曾至.
|
||||
|
||||
</div>
|
||||
|
||||
---
|
||||
## 欢迎回家
|
||||
NapCatQQ 是现代化的基于 NTQQ 的 Bot 协议端实现
|
||||
|
||||
## 特性介绍
|
||||
- [x] **安装简单**:就算是笨蛋也能使用
|
||||
- [x] **性能友好**:就算是低内存也能使用
|
||||
- [x] **接口丰富**:就算是没有也能使用
|
||||
- [x] **稳定好用**:就算是被捉也能使用
|
||||
## Welcome
|
||||
+ NapCatQQ is a modern implementation of the Bot protocol based on NTQQ.
|
||||
- NapCatQQ 是现代化的基于 NTQQ 的 Bot 协议端实现
|
||||
|
||||
## 使用框架
|
||||
## Feature
|
||||
|
||||
+ **Easy to Use**
|
||||
- 作为初学者能够轻松使用.
|
||||
+ **Quick and Efficient**
|
||||
- 在低内存操作系统长时运行.
|
||||
+ **Rich API Interface**
|
||||
- 完整实现了大部分标准接口.
|
||||
+ **Stable and Reliable**
|
||||
- 持续稳定的开发与维护.
|
||||
## Quick Start
|
||||
|
||||
可前往 [Release](https://github.com/NapNeko/NapCatQQ/releases/) 页面下载最新版本
|
||||
|
||||
**首次使用**请务必查看如下文档看使用教程
|
||||
|
||||
### 文档地址
|
||||
## Link
|
||||
|
||||
[Cloudflare.Worker](https://doc.napneko.icu/)
|
||||
| Docs | [](https://napneko.github.io/) | [](https://doc.napneko.icu/) | [](https://napcat.napneko.icu/) |
|
||||
|:-:|:-:|:-:|:-:|
|
||||
|
||||
[Cloudflare.HKServer](https://napcat.napneko.icu/)
|
||||
| Docs | [](https://napneko.pages.dev/) | [](https://docs.napcat.cyou/) | [](https://www.napcat.wiki) |
|
||||
|:-:|:-:|:-:|:-:|
|
||||
|
||||
[Github.IO](https://napneko.github.io/)
|
||||
| Contact | [](https://qm.qq.com/q/I6LU87a0Yq) | [](https://qm.qq.com/q/HaRcfrHpUk) | [](https://t.me/MelodicMoonlight) |
|
||||
|:-:|:-:|:-:|:-:|
|
||||
|
||||
[Cloudflare.Pages](https://napneko.pages.dev/)
|
||||
## Thanks
|
||||
|
||||
[Server.Other](https://docs.napcat.cyou/)
|
||||
+ [Lagrange](https://github.com/LagrangeDev/Lagrange.Core) 对本项目的大力支持 参考部分代码 已获授权
|
||||
|
||||
[NapCat.Wiki](https://www.napcat.wiki)
|
||||
+ [LLOneBot](https://github.com/LLOneBot/LLOneBot) 相关的开发曾参与本项目部分开发
|
||||
|
||||
## 回家旅途
|
||||
[QQ Group#1](https://qm.qq.com/q/I6LU87a0Yq)
|
||||
|
||||
[QQ Group#2](https://qm.qq.com/q/HaRcfrHpUk)
|
||||
|
||||
[Telegram](https://t.me/MelodicMoonlight)
|
||||
|
||||
> QQ Group#2 准许Bot / Telegram与QQ Group#2 为新建Group
|
||||
|
||||
## 性能设计/协议标准
|
||||
NapCat 已实现90%+的 OneBot / GoCQ 标准接口,并提供兼容性保留接口,其设计理念遵守 无数据库/异步优先/后台刷新 的性能思想。
|
||||
|
||||
由此设计带来一系列好处,在开发中,获取群员列表通常小于50Ms,单条文本消息发送在320Ms以内,在1k+的群聊流畅运行,同时带来一些副作用,消息Id无法持久,无法上报撤回消息原始内容。
|
||||
|
||||
NapCat 在设计理念下遵守 OneBot 规范大多数要求并且积极改进,任何合理的标准化 Issue 与 Pr 将被接收。
|
||||
|
||||
## 感谢他们
|
||||
感谢 [Lagrange](https://github.com/LagrangeDev/Lagrange.Core) 对本项目的大力支持 参考部分代码 已获授权
|
||||
|
||||
感谢 React 强力驱动 NapCat.WebUi
|
||||
|
||||
不过最最重要的 还是需要感谢屏幕前的你哦~
|
||||
+ 不过最最重要的 还是需要感谢屏幕前的你哦~
|
||||
|
||||
---
|
||||
|
||||
## 特殊感谢
|
||||
[LLOneBot](https://github.com/LLOneBot/LLOneBot) 相关的开发曾参与本项目
|
||||
## License
|
||||
本项目采用 混合协议 开源,因此使用本项目时,你需要注意以下几点:
|
||||
1. 第三方库代码或修改部分遵循其原始开源许可.
|
||||
2. 本项目获取部分项目授权而不受部分约束
|
||||
2. 项目其余逻辑代码采用[本仓库开源许可](./LICENSE).
|
||||
|
||||
## 开源附加
|
||||
|
||||
任何使用本仓库代码的地方,都应当严格遵守[本仓库开源许可](./LICENSE)。**本仓库仅用于提高IM易用性,实现类似Hook推送,此外,禁止任何项目未经仓库主作者授权二次分发或基于 NapCat 代码开发。使用请遵守当地法律法规,由此造成的问题由使用者和提供违规使用教程者负责。**
|
||||
**本仓库仅用于提高易用性,实现消息推送类功能,此外,禁止任何项目未经仓库主作者授权基于 NapCat 代码开发。使用请遵守当地法律法规,由此造成的问题由使用者和提供违规使用教程者负责。**
|
||||
|
11
SECURITY.md
Normal file
11
SECURITY.md
Normal file
@@ -0,0 +1,11 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
| Version | Supported |
|
||||
| ------- | ------------------ |
|
||||
| > 4.0 | :white_check_mark: |
|
||||
| < 4.0 | :x: |
|
||||
|
||||
## Reporting a Vulnerability
|
||||
you should open an issue
|
BIN
external/LiteLoaderWrapper.zip
vendored
BIN
external/LiteLoaderWrapper.zip
vendored
Binary file not shown.
@@ -19,7 +19,7 @@ for %%a in ("%RetString%") do (
|
||||
SET QQPath=%pathWithoutUninstall%QQ.exe
|
||||
|
||||
if not exist "%QQpath%" (
|
||||
echo provided QQ path is invalid: %QQpath%
|
||||
echo provided QQ path is invalid
|
||||
pause
|
||||
exit /b
|
||||
)
|
||||
|
@@ -19,7 +19,7 @@ for %%a in ("%RetString%") do (
|
||||
SET QQPath=%pathWithoutUninstall%QQ.exe
|
||||
|
||||
if not exist "%QQpath%" (
|
||||
echo provided QQ path is invalid: %QQpath%
|
||||
echo provided QQ path is invalid
|
||||
pause
|
||||
exit /b
|
||||
)
|
||||
|
@@ -27,8 +27,8 @@ for %%a in ("%RetString%") do (
|
||||
|
||||
SET QQPath=%pathWithoutUninstall%QQ.exe
|
||||
|
||||
if not exist "%QQpath%" (
|
||||
echo provided QQ path is invalid: %QQpath%
|
||||
if not exist "%QQPath%" (
|
||||
echo provided QQ path is invalid
|
||||
pause
|
||||
exit /b
|
||||
)
|
||||
|
@@ -27,8 +27,8 @@ for %%a in ("%RetString%") do (
|
||||
|
||||
SET QQPath=%pathWithoutUninstall%QQ.exe
|
||||
|
||||
if not exist "%QQpath%" (
|
||||
echo provided QQ path is invalid: %QQpath%
|
||||
if not exist "%QQPath%" (
|
||||
echo provided QQ path is invalid
|
||||
pause
|
||||
exit /b
|
||||
)
|
||||
|
@@ -1,10 +1,9 @@
|
||||
{
|
||||
"name": "qq-chat",
|
||||
"version": "9.9.17-30899",
|
||||
"verHash": "ececf273",
|
||||
"linuxVersion": "3.2.15-30899",
|
||||
"linuxVerHash": "63c751e8",
|
||||
"type": "module",
|
||||
"version": "9.9.18-32793",
|
||||
"verHash": "d43f097e",
|
||||
"linuxVersion": "3.2.16-32793",
|
||||
"linuxVerHash": "ee4bd910",
|
||||
"private": true,
|
||||
"description": "QQ",
|
||||
"productName": "QQ",
|
||||
@@ -17,10 +16,27 @@
|
||||
"bin": {
|
||||
"qd": "externals/devtools/cli/index.js"
|
||||
},
|
||||
"appid": {
|
||||
"win32": "537258389",
|
||||
"darwin": "537258412",
|
||||
"linux": "537258424"
|
||||
},
|
||||
"main": "./loadNapCat.js",
|
||||
"buildVersion": "30899",
|
||||
"peerDependenciesMeta": {
|
||||
"*": {
|
||||
"optional": true
|
||||
}
|
||||
},
|
||||
"pnpm": {
|
||||
"patchedDependencies": {
|
||||
"@vue/runtime-dom@3.5.12": "patches/@vue__runtime-dom@3.5.12.patch",
|
||||
"@swc/helpers@0.5.3": "patches/@swc__helpers@0.5.3.patch",
|
||||
"vuex@4.1.0": "patches/vuex@4.1.0.patch"
|
||||
}
|
||||
},
|
||||
"buildVersion": "32793",
|
||||
"isPureShell": true,
|
||||
"isByteCodeShell": true,
|
||||
"platform": "win32",
|
||||
"eleArch": "x64"
|
||||
}
|
||||
}
|
@@ -4,7 +4,7 @@
|
||||
"name": "NapCatQQ",
|
||||
"slug": "NapCat.Framework",
|
||||
"description": "高性能的 OneBot 11 协议实现",
|
||||
"version": "4.5.12",
|
||||
"version": "4.7.6",
|
||||
"icon": "./logo.png",
|
||||
"authors": [
|
||||
{
|
||||
|
@@ -13,6 +13,7 @@
|
||||
"@dnd-kit/core": "^6.3.1",
|
||||
"@dnd-kit/sortable": "^10.0.0",
|
||||
"@dnd-kit/utilities": "^3.2.2",
|
||||
"@heroui/accordion": "^2.2.8",
|
||||
"@heroui/avatar": "2.2.7",
|
||||
"@heroui/breadcrumbs": "2.2.7",
|
||||
"@heroui/button": "2.2.10",
|
||||
@@ -64,6 +65,7 @@
|
||||
"qrcode.react": "^4.2.0",
|
||||
"quill": "^2.0.3",
|
||||
"react": "^19.0.0",
|
||||
"react-color": "^2.19.3",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-dropzone": "^14.3.5",
|
||||
"react-error-boundary": "^5.0.0",
|
||||
|
36
napcat.webui/src/components/ColorPicker.tsx
Normal file
36
napcat.webui/src/components/ColorPicker.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@heroui/popover'
|
||||
import React from 'react'
|
||||
import { ColorResult, SketchPicker } from 'react-color'
|
||||
|
||||
// 假定 heroui 提供的 Popover组件
|
||||
|
||||
interface ColorPickerProps {
|
||||
color: string
|
||||
onChange: (color: ColorResult) => void
|
||||
}
|
||||
|
||||
const ColorPicker: React.FC<ColorPickerProps> = ({ color, onChange }) => {
|
||||
const handleChange = (colorResult: ColorResult) => {
|
||||
onChange(colorResult)
|
||||
}
|
||||
|
||||
return (
|
||||
<Popover triggerScaleOnOpen={false}>
|
||||
<PopoverTrigger>
|
||||
<div
|
||||
className="w-36 h-8 rounded-md cursor-pointer border border-content4"
|
||||
style={{ background: color }}
|
||||
/>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent>
|
||||
<SketchPicker
|
||||
color={color}
|
||||
onChange={handleChange}
|
||||
className="!bg-transparent !shadow-none"
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
)
|
||||
}
|
||||
|
||||
export default ColorPicker
|
@@ -1,4 +1,5 @@
|
||||
import { Button } from '@heroui/button'
|
||||
import clsx from 'clsx'
|
||||
import toast from 'react-hot-toast'
|
||||
import { IoMdRefresh } from 'react-icons/io'
|
||||
|
||||
@@ -7,15 +8,22 @@ export interface SaveButtonsProps {
|
||||
reset: () => void
|
||||
refresh?: () => void
|
||||
isSubmitting: boolean
|
||||
className?: string
|
||||
}
|
||||
|
||||
const SaveButtons: React.FC<SaveButtonsProps> = ({
|
||||
onSubmit,
|
||||
reset,
|
||||
isSubmitting,
|
||||
refresh
|
||||
refresh,
|
||||
className
|
||||
}) => (
|
||||
<div className="max-w-full mx-3 w-96 flex flex-col justify-center gap-3">
|
||||
<div
|
||||
className={clsx(
|
||||
'max-w-full mx-3 w-96 flex flex-col justify-center gap-3',
|
||||
className
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center justify-center gap-2 mt-5">
|
||||
<Button
|
||||
color="default"
|
||||
|
@@ -136,7 +136,7 @@ const OneBotApiDebug: React.FC<OneBotApiDebugProps> = (props) => {
|
||||
</div>
|
||||
<Card
|
||||
shadow="sm"
|
||||
className="my-4 bg-opacity-50 backdrop-blur-md overflow-visible z-20"
|
||||
className="my-4 bg-opacity-50 backdrop-blur-md overflow-visible"
|
||||
>
|
||||
<CardHeader className="font-bold text-lg gap-1 pb-0">
|
||||
<span className="mr-2">请求体</span>
|
||||
|
@@ -58,14 +58,13 @@ const renderItems = (items: MenuItem[], children = false) => {
|
||||
color="primary"
|
||||
endContent={
|
||||
canOpen ? (
|
||||
// div实现箭头V效果
|
||||
<div
|
||||
className={clsx(
|
||||
'ml-auto relative w-3 h-3 transition-transform',
|
||||
open && 'transform rotate-180',
|
||||
isActive
|
||||
? 'text-primary-500'
|
||||
: 'text-red-300 dark:text-white',
|
||||
: 'text-primary-200 dark:text-white',
|
||||
'before:rounded-full',
|
||||
'before:content-[""]',
|
||||
'before:block',
|
||||
@@ -98,7 +97,7 @@ const renderItems = (items: MenuItem[], children = false) => {
|
||||
'w-3 h-1.5 rounded-full ml-auto shadow-lg',
|
||||
isActive
|
||||
? 'bg-primary-500 animate-spinner-ease-spin'
|
||||
: 'bg-red-300 dark:bg-white'
|
||||
: 'bg-primary-200 dark:bg-white'
|
||||
)}
|
||||
/>
|
||||
)
|
||||
|
@@ -34,7 +34,7 @@ const SystemInfoItem: React.FC<SystemInfoItemProps> = ({
|
||||
endContent
|
||||
}) => {
|
||||
return (
|
||||
<div className="flex text-sm gap-1 p-2 items-center shadow-sm shadow-primary-50 dark:shadow-primary-100 rounded text-primary-400">
|
||||
<div className="flex text-sm gap-1 p-2 items-center shadow-sm shadow-primary-100 dark:shadow-primary-100 rounded text-primary-400">
|
||||
{icon}
|
||||
<div className="w-24">{title}</div>
|
||||
<div className="text-primary-200">{value}</div>
|
||||
@@ -234,7 +234,7 @@ const SystemInfo: React.FC<SystemInfoProps> = (props) => {
|
||||
error: qqVersionError
|
||||
} = useRequest(WebUIManager.getQQVersion)
|
||||
return (
|
||||
<Card className="bg-opacity-60 shadow-sm shadow-primary-50 dark:shadow-primary-100 overflow-visible flex-1">
|
||||
<Card className="bg-opacity-60 shadow-sm shadow-primary-100 dark:shadow-primary-100 overflow-visible flex-1">
|
||||
<CardHeader className="pb-0 items-center gap-1 text-primary-500 font-extrabold">
|
||||
<FaCircleInfo className="text-lg" />
|
||||
<span>系统信息</span>
|
||||
|
@@ -24,7 +24,7 @@ const SystemStatusItem: React.FC<SystemStatusItemProps> = ({
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
'shadow-sm p-2 rounded-md text-sm bg-content1 bg-opacity-30',
|
||||
'shadow-sm shadow-primary-100 p-2 rounded-md text-sm bg-content1 bg-opacity-30',
|
||||
size === 'lg' ? 'col-span-2' : 'col-span-1 flex justify-between'
|
||||
)}
|
||||
>
|
||||
@@ -55,7 +55,7 @@ const SystemStatusDisplay: React.FC<SystemStatusDisplayProps> = ({ data }) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<Card className="bg-opacity-60 shadow-sm shadow-primary-50 dark:shadow-primary-100 col-span-1 lg:col-span-2 relative overflow-hidden">
|
||||
<Card className="bg-opacity-60 shadow-sm shadow-primary-100 col-span-1 lg:col-span-2 relative overflow-hidden">
|
||||
<div className="absolute h-full right-0 top-0">
|
||||
<Image
|
||||
src={bkg}
|
||||
|
6
napcat.webui/src/const/themes.ts
Normal file
6
napcat.webui/src/const/themes.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import heroui from './themes/heroui'
|
||||
import nc_pink from './themes/nc_pink'
|
||||
|
||||
const themes: ThemeInfo[] = [nc_pink, heroui]
|
||||
|
||||
export default themes
|
256
napcat.webui/src/const/themes/heroui.ts
Normal file
256
napcat.webui/src/const/themes/heroui.ts
Normal file
@@ -0,0 +1,256 @@
|
||||
const theme: ThemeConfig = {
|
||||
dark: {
|
||||
'--heroui-background': '0 0% 0%',
|
||||
'--heroui-foreground-50': '240 5.88% 10%',
|
||||
'--heroui-foreground-100': '240 3.7% 15.88%',
|
||||
'--heroui-foreground-200': '240 5.26% 26.08%',
|
||||
'--heroui-foreground-300': '240 5.2% 33.92%',
|
||||
'--heroui-foreground-400': '240 3.83% 46.08%',
|
||||
'--heroui-foreground-500': '240 5.03% 64.9%',
|
||||
'--heroui-foreground-600': '240 4.88% 83.92%',
|
||||
'--heroui-foreground-700': '240 5.88% 90%',
|
||||
'--heroui-foreground-800': '240 4.76% 95.88%',
|
||||
'--heroui-foreground-900': '0 0% 98.04%',
|
||||
'--heroui-foreground': '210 5.56% 92.94%',
|
||||
'--heroui-focus': '212.01999999999998 100% 46.67%',
|
||||
'--heroui-overlay': '0 0% 0%',
|
||||
'--heroui-divider': '0 0% 100%',
|
||||
'--heroui-divider-opacity': '0.15',
|
||||
'--heroui-content1': '240 5.88% 10%',
|
||||
'--heroui-content1-foreground': '0 0% 98.04%',
|
||||
'--heroui-content2': '240 3.7% 15.88%',
|
||||
'--heroui-content2-foreground': '240 4.76% 95.88%',
|
||||
'--heroui-content3': '240 5.26% 26.08%',
|
||||
'--heroui-content3-foreground': '240 5.88% 90%',
|
||||
'--heroui-content4': '240 5.2% 33.92%',
|
||||
'--heroui-content4-foreground': '240 4.88% 83.92%',
|
||||
'--heroui-default-50': '240 5.88% 10%',
|
||||
'--heroui-default-100': '240 3.7% 15.88%',
|
||||
'--heroui-default-200': '240 5.26% 26.08%',
|
||||
'--heroui-default-300': '240 5.2% 33.92%',
|
||||
'--heroui-default-400': '240 3.83% 46.08%',
|
||||
'--heroui-default-500': '240 5.03% 64.9%',
|
||||
'--heroui-default-600': '240 4.88% 83.92%',
|
||||
'--heroui-default-700': '240 5.88% 90%',
|
||||
'--heroui-default-800': '240 4.76% 95.88%',
|
||||
'--heroui-default-900': '0 0% 98.04%',
|
||||
'--heroui-default-foreground': '0 0% 100%',
|
||||
'--heroui-default': '240 5.26% 26.08%',
|
||||
'--heroui-danger-50': '340 84.91% 10.39%',
|
||||
'--heroui-danger-100': '339.33 86.54% 20.39%',
|
||||
'--heroui-danger-200': '339.11 85.99% 30.78%',
|
||||
'--heroui-danger-300': '339 86.54% 40.78%',
|
||||
'--heroui-danger-400': '339.2 90.36% 51.18%',
|
||||
'--heroui-danger-500': '339 90% 60.78%',
|
||||
'--heroui-danger-600': '339.11 90.6% 70.78%',
|
||||
'--heroui-danger-700': '339.33 90% 80.39%',
|
||||
'--heroui-danger-800': '340 91.84% 90.39%',
|
||||
'--heroui-danger-900': '339.13 92% 95.1%',
|
||||
'--heroui-danger-foreground': '0 0% 100%',
|
||||
'--heroui-danger': '339.2 90.36% 51.18%',
|
||||
'--heroui-primary-50': '211.84 100% 9.61%',
|
||||
'--heroui-primary-100': '211.84 100% 19.22%',
|
||||
'--heroui-primary-200': '212.24 100% 28.82%',
|
||||
'--heroui-primary-300': '212.14 100% 38.43%',
|
||||
'--heroui-primary-400': '212.02 100% 46.67%',
|
||||
'--heroui-primary-500': '212.14 92.45% 58.43%',
|
||||
'--heroui-primary-600': '212.24 92.45% 68.82%',
|
||||
'--heroui-primary-700': '211.84 92.45% 79.22%',
|
||||
'--heroui-primary-800': '211.84 92.45% 89.61%',
|
||||
'--heroui-primary-900': '212.5 92.31% 94.9%',
|
||||
'--heroui-primary-foreground': '0 0% 100%',
|
||||
'--heroui-primary': '212.02 100% 46.67%',
|
||||
'--heroui-secondary-50': '270 66.67% 9.41%',
|
||||
'--heroui-secondary-100': '270 66.67% 18.82%',
|
||||
'--heroui-secondary-200': '270 66.67% 28.24%',
|
||||
'--heroui-secondary-300': '270 66.67% 37.65%',
|
||||
'--heroui-secondary-400': '270 66.67% 47.06%',
|
||||
'--heroui-secondary-500': '270 59.26% 57.65%',
|
||||
'--heroui-secondary-600': '270 59.26% 68.24%',
|
||||
'--heroui-secondary-700': '270 59.26% 78.82%',
|
||||
'--heroui-secondary-800': '270 59.26% 89.41%',
|
||||
'--heroui-secondary-900': '270 61.54% 94.9%',
|
||||
'--heroui-secondary-foreground': '0 0% 100%',
|
||||
'--heroui-secondary': '270 59.26% 57.65%',
|
||||
'--heroui-success-50': '145.71 77.78% 8.82%',
|
||||
'--heroui-success-100': '146.2 79.78% 17.45%',
|
||||
'--heroui-success-200': '145.79 79.26% 26.47%',
|
||||
'--heroui-success-300': '146.01 79.89% 35.1%',
|
||||
'--heroui-success-400': '145.96 79.46% 43.92%',
|
||||
'--heroui-success-500': '146.01 62.45% 55.1%',
|
||||
'--heroui-success-600': '145.79 62.57% 66.47%',
|
||||
'--heroui-success-700': '146.2 61.74% 77.45%',
|
||||
'--heroui-success-800': '145.71 61.4% 88.82%',
|
||||
'--heroui-success-900': '146.67 64.29% 94.51%',
|
||||
'--heroui-success-foreground': '0 0% 0%',
|
||||
'--heroui-success': '145.96 79.46% 43.92%',
|
||||
'--heroui-warning-50': '37.14 75% 10.98%',
|
||||
'--heroui-warning-100': '37.14 75% 21.96%',
|
||||
'--heroui-warning-200': '36.96 73.96% 33.14%',
|
||||
'--heroui-warning-300': '37.01 74.22% 44.12%',
|
||||
'--heroui-warning-400': '37.03 91.27% 55.1%',
|
||||
'--heroui-warning-500': '37.01 91.26% 64.12%',
|
||||
'--heroui-warning-600': '36.96 91.24% 73.14%',
|
||||
'--heroui-warning-700': '37.14 91.3% 81.96%',
|
||||
'--heroui-warning-800': '37.14 91.3% 90.98%',
|
||||
'--heroui-warning-900': '54.55 91.67% 95.29%',
|
||||
'--heroui-warning-foreground': '0 0% 0%',
|
||||
'--heroui-warning': '37.03 91.27% 55.1%',
|
||||
'--heroui-code-background': '240 5.56% 7.06%',
|
||||
'--heroui-strong': '190.14 94.67% 44.12%',
|
||||
'--heroui-code-mdx': '190.14 94.67% 44.12%',
|
||||
'--heroui-divider-weight': '1px',
|
||||
'--heroui-disabled-opacity': '.5',
|
||||
'--heroui-font-size-tiny': '0.75rem',
|
||||
'--heroui-font-size-small': '0.875rem',
|
||||
'--heroui-font-size-medium': '1rem',
|
||||
'--heroui-font-size-large': '1.125rem',
|
||||
'--heroui-line-height-tiny': '1rem',
|
||||
'--heroui-line-height-small': '1.25rem',
|
||||
'--heroui-line-height-medium': '1.5rem',
|
||||
'--heroui-line-height-large': '1.75rem',
|
||||
'--heroui-radius-small': '8px',
|
||||
'--heroui-radius-medium': '12px',
|
||||
'--heroui-radius-large': '14px',
|
||||
'--heroui-border-width-small': '1px',
|
||||
'--heroui-border-width-medium': '2px',
|
||||
'--heroui-border-width-large': '3px',
|
||||
'--heroui-box-shadow-small':
|
||||
'0px 0px 5px 0px rgba(0, 0, 0, .05), 0px 2px 10px 0px rgba(0, 0, 0, .2), inset 0px 0px 1px 0px hsla(0, 0%, 100%, .15)',
|
||||
'--heroui-box-shadow-medium':
|
||||
'0px 0px 15px 0px rgba(0, 0, 0, .06), 0px 2px 30px 0px rgba(0, 0, 0, .22), inset 0px 0px 1px 0px hsla(0, 0%, 100%, .15)',
|
||||
'--heroui-box-shadow-large':
|
||||
'0px 0px 30px 0px rgba(0, 0, 0, .07), 0px 30px 60px 0px rgba(0, 0, 0, .26), inset 0px 0px 1px 0px hsla(0, 0%, 100%, .15)',
|
||||
'--heroui-hover-opacity': '.9'
|
||||
},
|
||||
light: {
|
||||
'--heroui-background': '0 0% 100%',
|
||||
'--heroui-foreground-50': '240 5.88% 95%',
|
||||
'--heroui-foreground-100': '240 3.7% 90%',
|
||||
'--heroui-foreground-200': '240 5.26% 80%',
|
||||
'--heroui-foreground-300': '240 5.2% 70%',
|
||||
'--heroui-foreground-400': '240 3.83% 60%',
|
||||
'--heroui-foreground-500': '240 5.03% 50%',
|
||||
'--heroui-foreground-600': '240 4.88% 40%',
|
||||
'--heroui-foreground-700': '240 5.88% 30%',
|
||||
'--heroui-foreground-800': '240 4.76% 20%',
|
||||
'--heroui-foreground-900': '0 0% 10%',
|
||||
'--heroui-foreground': '210 5.56% 7.06%',
|
||||
'--heroui-focus': '212.01999999999998 100% 53.33%',
|
||||
'--heroui-overlay': '0 0% 100%',
|
||||
'--heroui-divider': '0 0% 0%',
|
||||
'--heroui-divider-opacity': '0.85',
|
||||
'--heroui-content1': '240 5.88% 95%',
|
||||
'--heroui-content1-foreground': '0 0% 10%',
|
||||
'--heroui-content2': '240 3.7% 90%',
|
||||
'--heroui-content2-foreground': '240 4.76% 20%',
|
||||
'--heroui-content3': '240 5.26% 80%',
|
||||
'--heroui-content3-foreground': '240 5.88% 30%',
|
||||
'--heroui-content4': '240 5.2% 70%',
|
||||
'--heroui-content4-foreground': '240 4.88% 40%',
|
||||
'--heroui-default-50': '240 5.88% 95%',
|
||||
'--heroui-default-100': '240 3.7% 90%',
|
||||
'--heroui-default-200': '240 5.26% 80%',
|
||||
'--heroui-default-300': '240 5.2% 70%',
|
||||
'--heroui-default-400': '240 3.83% 60%',
|
||||
'--heroui-default-500': '240 5.03% 50%',
|
||||
'--heroui-default-600': '240 4.88% 40%',
|
||||
'--heroui-default-700': '240 5.88% 30%',
|
||||
'--heroui-default-800': '240 4.76% 20%',
|
||||
'--heroui-default-900': '0 0% 10%',
|
||||
'--heroui-default-foreground': '0 0% 0%',
|
||||
'--heroui-default': '240 5.26% 80%',
|
||||
'--heroui-danger-50': '339.13 92% 95.1%',
|
||||
'--heroui-danger-100': '340 91.84% 90.39%',
|
||||
'--heroui-danger-200': '339.33 90% 80.39%',
|
||||
'--heroui-danger-300': '339.11 90.6% 70.78%',
|
||||
'--heroui-danger-400': '339 90% 60.78%',
|
||||
'--heroui-danger-500': '339.2 90.36% 51.18%',
|
||||
'--heroui-danger-600': '339 86.54% 40.78%',
|
||||
'--heroui-danger-700': '339.11 85.99% 30.78%',
|
||||
'--heroui-danger-800': '339.33 86.54% 20.39%',
|
||||
'--heroui-danger-900': '340 84.91% 10.39%',
|
||||
'--heroui-danger-foreground': '0 0% 100%',
|
||||
'--heroui-danger': '339.2 90.36% 51.18%',
|
||||
'--heroui-primary-50': '212.5 92.31% 94.9%',
|
||||
'--heroui-primary-100': '211.84 92.45% 89.61%',
|
||||
'--heroui-primary-200': '211.84 92.45% 79.22%',
|
||||
'--heroui-primary-300': '212.24 92.45% 68.82%',
|
||||
'--heroui-primary-400': '212.14 92.45% 58.43%',
|
||||
'--heroui-primary-500': '212.02 100% 46.67%',
|
||||
'--heroui-primary-600': '212.14 100% 38.43%',
|
||||
'--heroui-primary-700': '212.24 100% 28.82%',
|
||||
'--heroui-primary-800': '211.84 100% 19.22%',
|
||||
'--heroui-primary-900': '211.84 100% 9.61%',
|
||||
'--heroui-primary-foreground': '0 0% 100%',
|
||||
'--heroui-primary': '212.02 100% 46.67%',
|
||||
'--heroui-secondary-50': '270 61.54% 94.9%',
|
||||
'--heroui-secondary-100': '270 59.26% 89.41%',
|
||||
'--heroui-secondary-200': '270 59.26% 78.82%',
|
||||
'--heroui-secondary-300': '270 59.26% 68.24%',
|
||||
'--heroui-secondary-400': '270 59.26% 57.65%',
|
||||
'--heroui-secondary-500': '270 66.67% 47.06%',
|
||||
'--heroui-secondary-600': '270 66.67% 37.65%',
|
||||
'--heroui-secondary-700': '270 66.67% 28.24%',
|
||||
'--heroui-secondary-800': '270 66.67% 18.82%',
|
||||
'--heroui-secondary-900': '270 66.67% 9.41%',
|
||||
'--heroui-secondary-foreground': '0 0% 100%',
|
||||
'--heroui-secondary': '270 66.67% 47.06%',
|
||||
'--heroui-success-50': '146.67 64.29% 94.51%',
|
||||
'--heroui-success-100': '145.71 61.4% 88.82%',
|
||||
'--heroui-success-200': '146.2 61.74% 77.45%',
|
||||
'--heroui-success-300': '145.79 62.57% 66.47%',
|
||||
'--heroui-success-400': '146.01 62.45% 55.1%',
|
||||
'--heroui-success-500': '145.96 79.46% 43.92%',
|
||||
'--heroui-success-600': '146.01 79.89% 35.1%',
|
||||
'--heroui-success-700': '145.79 79.26% 26.47%',
|
||||
'--heroui-success-800': '146.2 79.78% 17.45%',
|
||||
'--heroui-success-900': '145.71 77.78% 8.82%',
|
||||
'--heroui-success-foreground': '0 0% 0%',
|
||||
'--heroui-success': '145.96 79.46% 43.92%',
|
||||
'--heroui-warning-50': '54.55 91.67% 95.29%',
|
||||
'--heroui-warning-100': '37.14 91.3% 90.98%',
|
||||
'--heroui-warning-200': '37.14 91.3% 81.96%',
|
||||
'--heroui-warning-300': '36.96 91.24% 73.14%',
|
||||
'--heroui-warning-400': '37.01 91.26% 64.12%',
|
||||
'--heroui-warning-500': '37.03 91.27% 55.1%',
|
||||
'--heroui-warning-600': '37.01 74.22% 44.12%',
|
||||
'--heroui-warning-700': '36.96 73.96% 33.14%',
|
||||
'--heroui-warning-800': '37.14 75% 21.96%',
|
||||
'--heroui-warning-900': '37.14 75% 10.98%',
|
||||
'--heroui-warning-foreground': '0 0% 0%',
|
||||
'--heroui-warning': '37.03 91.27% 55.1%',
|
||||
'--heroui-code-background': '221.25 17.39% 18.04%',
|
||||
'--heroui-strong': '316.95 100% 65.29%',
|
||||
'--heroui-code-mdx': '316.95 100% 65.29%',
|
||||
'--heroui-divider-weight': '1px',
|
||||
'--heroui-disabled-opacity': '.5',
|
||||
'--heroui-font-size-tiny': '0.75rem',
|
||||
'--heroui-font-size-small': '0.875rem',
|
||||
'--heroui-font-size-medium': '1rem',
|
||||
'--heroui-font-size-large': '1.125rem',
|
||||
'--heroui-line-height-tiny': '1rem',
|
||||
'--heroui-line-height-small': '1.25rem',
|
||||
'--heroui-line-height-medium': '1.5rem',
|
||||
'--heroui-line-height-large': '1.75rem',
|
||||
'--heroui-radius-small': '8px',
|
||||
'--heroui-radius-medium': '12px',
|
||||
'--heroui-radius-large': '14px',
|
||||
'--heroui-border-width-small': '1px',
|
||||
'--heroui-border-width-medium': '2px',
|
||||
'--heroui-border-width-large': '3px',
|
||||
'--heroui-box-shadow-small':
|
||||
'0px 0px 5px 0px rgba(0, 0, 0, .02), 0px 2px 10px 0px rgba(0, 0, 0, .06), 0px 0px 1px 0px rgba(0, 0, 0, .3)',
|
||||
'--heroui-box-shadow-medium':
|
||||
'0px 0px 15px 0px rgba(0, 0, 0, .03), 0px 2px 30px 0px rgba(0, 0, 0, .08), 0px 0px 1px 0px rgba(0, 0, 0, .3)',
|
||||
'--heroui-box-shadow-large':
|
||||
'0px 0px 30px 0px rgba(0, 0, 0, .04), 0px 30px 60px 0px rgba(0, 0, 0, .12), 0px 0px 1px 0px rgba(0, 0, 0, .3)',
|
||||
'--heroui-hover-opacity': '.8'
|
||||
}
|
||||
}
|
||||
export default {
|
||||
theme,
|
||||
author: 'HeroUI',
|
||||
name: 'heroui',
|
||||
description: 'HeroUI Default Theme'
|
||||
} satisfies ThemeInfo
|
256
napcat.webui/src/const/themes/nc_pink.ts
Normal file
256
napcat.webui/src/const/themes/nc_pink.ts
Normal file
@@ -0,0 +1,256 @@
|
||||
const theme: ThemeConfig = {
|
||||
dark: {
|
||||
'--heroui-background': '0 0% 0%',
|
||||
'--heroui-foreground-50': '240 5.88% 10%',
|
||||
'--heroui-foreground-100': '240 3.7% 15.88%',
|
||||
'--heroui-foreground-200': '240 5.26% 26.08%',
|
||||
'--heroui-foreground-300': '240 5.2% 33.92%',
|
||||
'--heroui-foreground-400': '240 3.83% 46.08%',
|
||||
'--heroui-foreground-500': '240 5.03% 64.9%',
|
||||
'--heroui-foreground-600': '240 4.88% 83.92%',
|
||||
'--heroui-foreground-700': '240 5.88% 90%',
|
||||
'--heroui-foreground-800': '240 4.76% 95.88%',
|
||||
'--heroui-foreground-900': '0 0% 98.04%',
|
||||
'--heroui-foreground': '210 5.56% 92.94%',
|
||||
'--heroui-focus': '212.01999999999998 100% 46.67%',
|
||||
'--heroui-overlay': '0 0% 0%',
|
||||
'--heroui-divider': '0 0% 100%',
|
||||
'--heroui-divider-opacity': '0.15',
|
||||
'--heroui-content1': '240 5.88% 10%',
|
||||
'--heroui-content1-foreground': '0 0% 98.04%',
|
||||
'--heroui-content2': '240 3.7% 15.88%',
|
||||
'--heroui-content2-foreground': '240 4.76% 95.88%',
|
||||
'--heroui-content3': '240 5.26% 26.08%',
|
||||
'--heroui-content3-foreground': '240 5.88% 90%',
|
||||
'--heroui-content4': '240 5.2% 33.92%',
|
||||
'--heroui-content4-foreground': '240 4.88% 83.92%',
|
||||
'--heroui-default-50': '240 5.88% 10%',
|
||||
'--heroui-default-100': '240 3.7% 15.88%',
|
||||
'--heroui-default-200': '240 5.26% 26.08%',
|
||||
'--heroui-default-300': '240 5.2% 33.92%',
|
||||
'--heroui-default-400': '240 3.83% 46.08%',
|
||||
'--heroui-default-500': '240 5.03% 64.9%',
|
||||
'--heroui-default-600': '240 4.88% 83.92%',
|
||||
'--heroui-default-700': '240 5.88% 90%',
|
||||
'--heroui-default-800': '240 4.76% 95.88%',
|
||||
'--heroui-default-900': '0 0% 98.04%',
|
||||
'--heroui-default-foreground': '0 0% 100%',
|
||||
'--heroui-default': '240 5.26% 26.08%',
|
||||
'--heroui-danger-50': '301.89 82.61% 22.55%',
|
||||
'--heroui-danger-100': '308.18 76.39% 28.24%',
|
||||
'--heroui-danger-200': '313.85 70.65% 36.08%',
|
||||
'--heroui-danger-300': '319.73 65.64% 44.51%',
|
||||
'--heroui-danger-400': '325.82 69.62% 53.53%',
|
||||
'--heroui-danger-500': '331.82 75% 65.49%',
|
||||
'--heroui-danger-600': '337.84 83.46% 73.92%',
|
||||
'--heroui-danger-700': '343.42 90.48% 83.53%',
|
||||
'--heroui-danger-800': '350.53 90.48% 91.76%',
|
||||
'--heroui-danger-900': '324 90.91% 95.69%',
|
||||
'--heroui-danger-foreground': '0 0% 100%',
|
||||
'--heroui-danger': '325.82 69.62% 53.53%',
|
||||
'--heroui-primary-50': '340 84.91% 10.39%',
|
||||
'--heroui-primary-100': '339.33 86.54% 20.39%',
|
||||
'--heroui-primary-200': '339.11 85.99% 30.78%',
|
||||
'--heroui-primary-300': '339 86.54% 40.78%',
|
||||
'--heroui-primary-400': '339.2 90.36% 51.18%',
|
||||
'--heroui-primary-500': '339 90% 60.78%',
|
||||
'--heroui-primary-600': '339.11 90.6% 70.78%',
|
||||
'--heroui-primary-700': '339.33 90% 80.39%',
|
||||
'--heroui-primary-800': '340 91.84% 90.39%',
|
||||
'--heroui-primary-900': '339.13 92% 95.1%',
|
||||
'--heroui-primary-foreground': '0 0% 100%',
|
||||
'--heroui-primary': '339.2 90.36% 51.18%',
|
||||
'--heroui-secondary-50': '270 66.67% 9.41%',
|
||||
'--heroui-secondary-100': '270 66.67% 18.82%',
|
||||
'--heroui-secondary-200': '270 66.67% 28.24%',
|
||||
'--heroui-secondary-300': '270 66.67% 37.65%',
|
||||
'--heroui-secondary-400': '270 66.67% 47.06%',
|
||||
'--heroui-secondary-500': '270 59.26% 57.65%',
|
||||
'--heroui-secondary-600': '270 59.26% 68.24%',
|
||||
'--heroui-secondary-700': '270 59.26% 78.82%',
|
||||
'--heroui-secondary-800': '270 59.26% 89.41%',
|
||||
'--heroui-secondary-900': '270 61.54% 94.9%',
|
||||
'--heroui-secondary-foreground': '0 0% 100%',
|
||||
'--heroui-secondary': '270 59.26% 57.65%',
|
||||
'--heroui-success-50': '145.71 77.78% 8.82%',
|
||||
'--heroui-success-100': '146.2 79.78% 17.45%',
|
||||
'--heroui-success-200': '145.79 79.26% 26.47%',
|
||||
'--heroui-success-300': '146.01 79.89% 35.1%',
|
||||
'--heroui-success-400': '145.96 79.46% 43.92%',
|
||||
'--heroui-success-500': '146.01 62.45% 55.1%',
|
||||
'--heroui-success-600': '145.79 62.57% 66.47%',
|
||||
'--heroui-success-700': '146.2 61.74% 77.45%',
|
||||
'--heroui-success-800': '145.71 61.4% 88.82%',
|
||||
'--heroui-success-900': '146.67 64.29% 94.51%',
|
||||
'--heroui-success-foreground': '0 0% 0%',
|
||||
'--heroui-success': '145.96 79.46% 43.92%',
|
||||
'--heroui-warning-50': '37.14 75% 10.98%',
|
||||
'--heroui-warning-100': '37.14 75% 21.96%',
|
||||
'--heroui-warning-200': '36.96 73.96% 33.14%',
|
||||
'--heroui-warning-300': '37.01 74.22% 44.12%',
|
||||
'--heroui-warning-400': '37.03 91.27% 55.1%',
|
||||
'--heroui-warning-500': '37.01 91.26% 64.12%',
|
||||
'--heroui-warning-600': '36.96 91.24% 73.14%',
|
||||
'--heroui-warning-700': '37.14 91.3% 81.96%',
|
||||
'--heroui-warning-800': '37.14 91.3% 90.98%',
|
||||
'--heroui-warning-900': '54.55 91.67% 95.29%',
|
||||
'--heroui-warning-foreground': '0 0% 0%',
|
||||
'--heroui-warning': '37.03 91.27% 55.1%',
|
||||
'--heroui-code-background': '240 5.56% 7.06%',
|
||||
'--heroui-strong': '190.14 94.67% 44.12%',
|
||||
'--heroui-code-mdx': '190.14 94.67% 44.12%',
|
||||
'--heroui-divider-weight': '1px',
|
||||
'--heroui-disabled-opacity': '.5',
|
||||
'--heroui-font-size-tiny': '0.75rem',
|
||||
'--heroui-font-size-small': '0.875rem',
|
||||
'--heroui-font-size-medium': '1rem',
|
||||
'--heroui-font-size-large': '1.125rem',
|
||||
'--heroui-line-height-tiny': '1rem',
|
||||
'--heroui-line-height-small': '1.25rem',
|
||||
'--heroui-line-height-medium': '1.5rem',
|
||||
'--heroui-line-height-large': '1.75rem',
|
||||
'--heroui-radius-small': '8px',
|
||||
'--heroui-radius-medium': '12px',
|
||||
'--heroui-radius-large': '14px',
|
||||
'--heroui-border-width-small': '1px',
|
||||
'--heroui-border-width-medium': '2px',
|
||||
'--heroui-border-width-large': '3px',
|
||||
'--heroui-box-shadow-small':
|
||||
'0px 0px 5px 0px rgba(0, 0, 0, .05), 0px 2px 10px 0px rgba(0, 0, 0, .2), inset 0px 0px 1px 0px hsla(0, 0%, 100%, .15)',
|
||||
'--heroui-box-shadow-medium':
|
||||
'0px 0px 15px 0px rgba(0, 0, 0, .06), 0px 2px 30px 0px rgba(0, 0, 0, .22), inset 0px 0px 1px 0px hsla(0, 0%, 100%, .15)',
|
||||
'--heroui-box-shadow-large':
|
||||
'0px 0px 30px 0px rgba(0, 0, 0, .07), 0px 30px 60px 0px rgba(0, 0, 0, .26), inset 0px 0px 1px 0px hsla(0, 0%, 100%, .15)',
|
||||
'--heroui-hover-opacity': '.9'
|
||||
},
|
||||
light: {
|
||||
'--heroui-background': '0 0% 100%',
|
||||
'--heroui-foreground-50': '240 5.88% 95%',
|
||||
'--heroui-foreground-100': '240 3.7% 90%',
|
||||
'--heroui-foreground-200': '240 5.26% 80%',
|
||||
'--heroui-foreground-300': '240 5.2% 70%',
|
||||
'--heroui-foreground-400': '240 3.83% 60%',
|
||||
'--heroui-foreground-500': '240 5.03% 50%',
|
||||
'--heroui-foreground-600': '240 4.88% 40%',
|
||||
'--heroui-foreground-700': '240 5.88% 30%',
|
||||
'--heroui-foreground-800': '240 4.76% 20%',
|
||||
'--heroui-foreground-900': '0 0% 10%',
|
||||
'--heroui-foreground': '210 5.56% 7.06%',
|
||||
'--heroui-focus': '212.01999999999998 100% 53.33%',
|
||||
'--heroui-overlay': '0 0% 100%',
|
||||
'--heroui-divider': '0 0% 0%',
|
||||
'--heroui-divider-opacity': '0.85',
|
||||
'--heroui-content1': '240 5.88% 95%',
|
||||
'--heroui-content1-foreground': '0 0% 10%',
|
||||
'--heroui-content2': '240 3.7% 90%',
|
||||
'--heroui-content2-foreground': '240 4.76% 20%',
|
||||
'--heroui-content3': '240 5.26% 80%',
|
||||
'--heroui-content3-foreground': '240 5.88% 30%',
|
||||
'--heroui-content4': '240 5.2% 70%',
|
||||
'--heroui-content4-foreground': '240 4.88% 40%',
|
||||
'--heroui-default-50': '240 5.88% 95%',
|
||||
'--heroui-default-100': '240 3.7% 90%',
|
||||
'--heroui-default-200': '240 5.26% 80%',
|
||||
'--heroui-default-300': '240 5.2% 70%',
|
||||
'--heroui-default-400': '240 3.83% 60%',
|
||||
'--heroui-default-500': '240 5.03% 50%',
|
||||
'--heroui-default-600': '240 4.88% 40%',
|
||||
'--heroui-default-700': '240 5.88% 30%',
|
||||
'--heroui-default-800': '240 4.76% 20%',
|
||||
'--heroui-default-900': '0 0% 10%',
|
||||
'--heroui-default-foreground': '0 0% 0%',
|
||||
'--heroui-default': '240 5.26% 80%',
|
||||
'--heroui-danger-50': '324 90.91% 95.69%',
|
||||
'--heroui-danger-100': '350.53 90.48% 91.76%',
|
||||
'--heroui-danger-200': '343.42 90.48% 83.53%',
|
||||
'--heroui-danger-300': '337.84 83.46% 73.92%',
|
||||
'--heroui-danger-400': '331.82 75% 65.49%',
|
||||
'--heroui-danger-500': '325.82 69.62% 53.53%',
|
||||
'--heroui-danger-600': '319.73 65.64% 44.51%',
|
||||
'--heroui-danger-700': '313.85 70.65% 36.08%',
|
||||
'--heroui-danger-800': '308.18 76.39% 28.24%',
|
||||
'--heroui-danger-900': '301.89 82.61% 22.55%',
|
||||
'--heroui-danger-foreground': '0 0% 100%',
|
||||
'--heroui-danger': '325.82 69.62% 53.53%',
|
||||
'--heroui-primary-50': '339.13 92% 95.1%',
|
||||
'--heroui-primary-100': '340 91.84% 90.39%',
|
||||
'--heroui-primary-200': '339.33 90% 80.39%',
|
||||
'--heroui-primary-300': '339.11 90.6% 70.78%',
|
||||
'--heroui-primary-400': '339 90% 60.78%',
|
||||
'--heroui-primary-500': '339.2 90.36% 51.18%',
|
||||
'--heroui-primary-600': '339 86.54% 40.78%',
|
||||
'--heroui-primary-700': '339.11 85.99% 30.78%',
|
||||
'--heroui-primary-800': '339.33 86.54% 20.39%',
|
||||
'--heroui-primary-900': '340 84.91% 10.39%',
|
||||
'--heroui-primary-foreground': '0 0% 100%',
|
||||
'--heroui-primary': '339.2 90.36% 51.18%',
|
||||
'--heroui-secondary-50': '270 61.54% 94.9%',
|
||||
'--heroui-secondary-100': '270 59.26% 89.41%',
|
||||
'--heroui-secondary-200': '270 59.26% 78.82%',
|
||||
'--heroui-secondary-300': '270 59.26% 68.24%',
|
||||
'--heroui-secondary-400': '270 59.26% 57.65%',
|
||||
'--heroui-secondary-500': '270 66.67% 47.06%',
|
||||
'--heroui-secondary-600': '270 66.67% 37.65%',
|
||||
'--heroui-secondary-700': '270 66.67% 28.24%',
|
||||
'--heroui-secondary-800': '270 66.67% 18.82%',
|
||||
'--heroui-secondary-900': '270 66.67% 9.41%',
|
||||
'--heroui-secondary-foreground': '0 0% 100%',
|
||||
'--heroui-secondary': '270 66.67% 47.06%',
|
||||
'--heroui-success-50': '146.67 64.29% 94.51%',
|
||||
'--heroui-success-100': '145.71 61.4% 88.82%',
|
||||
'--heroui-success-200': '146.2 61.74% 77.45%',
|
||||
'--heroui-success-300': '145.79 62.57% 66.47%',
|
||||
'--heroui-success-400': '146.01 62.45% 55.1%',
|
||||
'--heroui-success-500': '145.96 79.46% 43.92%',
|
||||
'--heroui-success-600': '146.01 79.89% 35.1%',
|
||||
'--heroui-success-700': '145.79 79.26% 26.47%',
|
||||
'--heroui-success-800': '146.2 79.78% 17.45%',
|
||||
'--heroui-success-900': '145.71 77.78% 8.82%',
|
||||
'--heroui-success-foreground': '0 0% 0%',
|
||||
'--heroui-success': '145.96 79.46% 43.92%',
|
||||
'--heroui-warning-50': '54.55 91.67% 95.29%',
|
||||
'--heroui-warning-100': '37.14 91.3% 90.98%',
|
||||
'--heroui-warning-200': '37.14 91.3% 81.96%',
|
||||
'--heroui-warning-300': '36.96 91.24% 73.14%',
|
||||
'--heroui-warning-400': '37.01 91.26% 64.12%',
|
||||
'--heroui-warning-500': '37.03 91.27% 55.1%',
|
||||
'--heroui-warning-600': '37.01 74.22% 44.12%',
|
||||
'--heroui-warning-700': '36.96 73.96% 33.14%',
|
||||
'--heroui-warning-800': '37.14 75% 21.96%',
|
||||
'--heroui-warning-900': '37.14 75% 10.98%',
|
||||
'--heroui-warning-foreground': '0 0% 0%',
|
||||
'--heroui-warning': '37.03 91.27% 55.1%',
|
||||
'--heroui-code-background': '221.25 17.39% 18.04%',
|
||||
'--heroui-strong': '316.95 100% 65.29%',
|
||||
'--heroui-code-mdx': '316.95 100% 65.29%',
|
||||
'--heroui-divider-weight': '1px',
|
||||
'--heroui-disabled-opacity': '.5',
|
||||
'--heroui-font-size-tiny': '0.75rem',
|
||||
'--heroui-font-size-small': '0.875rem',
|
||||
'--heroui-font-size-medium': '1rem',
|
||||
'--heroui-font-size-large': '1.125rem',
|
||||
'--heroui-line-height-tiny': '1rem',
|
||||
'--heroui-line-height-small': '1.25rem',
|
||||
'--heroui-line-height-medium': '1.5rem',
|
||||
'--heroui-line-height-large': '1.75rem',
|
||||
'--heroui-radius-small': '8px',
|
||||
'--heroui-radius-medium': '12px',
|
||||
'--heroui-radius-large': '14px',
|
||||
'--heroui-border-width-small': '1px',
|
||||
'--heroui-border-width-medium': '2px',
|
||||
'--heroui-border-width-large': '3px',
|
||||
'--heroui-box-shadow-small':
|
||||
'0px 0px 5px 0px rgba(0, 0, 0, .02), 0px 2px 10px 0px rgba(0, 0, 0, .06), 0px 0px 1px 0px rgba(0, 0, 0, .3)',
|
||||
'--heroui-box-shadow-medium':
|
||||
'0px 0px 15px 0px rgba(0, 0, 0, .03), 0px 2px 30px 0px rgba(0, 0, 0, .08), 0px 0px 1px 0px rgba(0, 0, 0, .3)',
|
||||
'--heroui-box-shadow-large':
|
||||
'0px 0px 30px 0px rgba(0, 0, 0, .04), 0px 30px 60px 0px rgba(0, 0, 0, .12), 0px 0px 1px 0px rgba(0, 0, 0, .3)',
|
||||
'--heroui-hover-opacity': '.8'
|
||||
}
|
||||
}
|
||||
export default {
|
||||
theme,
|
||||
author: 'NapCat',
|
||||
name: 'nc_pink',
|
||||
description: 'NapCat Pink Theme'
|
||||
} satisfies ThemeInfo
|
@@ -73,4 +73,17 @@ export default class QQManager {
|
||||
)
|
||||
return data.data.data
|
||||
}
|
||||
|
||||
public static async getQuickLoginQQ() {
|
||||
const { data } = await serverRequest.post<ServerResponse<string>>(
|
||||
'/QQLogin/GetQuickLoginQQ'
|
||||
)
|
||||
return data.data
|
||||
}
|
||||
|
||||
public static async setQuickLoginQQ(uin: string) {
|
||||
await serverRequest.post<ServerResponse<null>>('/QQLogin/SetQuickLoginQQ', {
|
||||
uin
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@@ -9,14 +9,6 @@ export interface Log {
|
||||
message: string
|
||||
}
|
||||
|
||||
export interface TerminalSession {
|
||||
id: string
|
||||
}
|
||||
|
||||
export interface TerminalInfo {
|
||||
id: string
|
||||
}
|
||||
|
||||
export default class WebUIManager {
|
||||
public static async checkWebUiLogined() {
|
||||
const { data } =
|
||||
@@ -40,6 +32,13 @@ export default class WebUIManager {
|
||||
return data.data
|
||||
}
|
||||
|
||||
public static async checkUsingDefaultToken() {
|
||||
const { data } = await serverRequest.get<ServerResponse<boolean>>(
|
||||
'/auth/check_using_default_token'
|
||||
)
|
||||
return data.data
|
||||
}
|
||||
|
||||
public static async proxy<T>(url = '') {
|
||||
const data = await serverRequest.get<ServerResponse<string>>(
|
||||
'/base/proxy?url=' + encodeURIComponent(url)
|
||||
@@ -60,6 +59,20 @@ export default class WebUIManager {
|
||||
return data.data
|
||||
}
|
||||
|
||||
public static async getThemeConfig() {
|
||||
const { data } =
|
||||
await serverRequest.get<ServerResponse<ThemeConfig>>('/base/Theme')
|
||||
return data.data
|
||||
}
|
||||
|
||||
public static async setThemeConfig(theme: ThemeConfig) {
|
||||
const { data } = await serverRequest.post<ServerResponse<boolean>>(
|
||||
'/base/SetTheme',
|
||||
{ theme }
|
||||
)
|
||||
return data.data
|
||||
}
|
||||
|
||||
public static async getLogList() {
|
||||
const { data } =
|
||||
await serverRequest.get<ServerResponse<string[]>>('/Log/GetLogList')
|
||||
|
@@ -8,6 +8,7 @@ import '@/styles/globals.css'
|
||||
|
||||
import key from './const/key'
|
||||
import WebUIManager from './controllers/webui_manager'
|
||||
import { loadTheme } from './utils/theme'
|
||||
|
||||
WebUIManager.checkWebUiLogined()
|
||||
|
||||
@@ -22,6 +23,8 @@ if (theme && !theme.startsWith('"')) {
|
||||
localStorage.setItem(key.theme, JSON.stringify(theme))
|
||||
}
|
||||
|
||||
loadTheme()
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||
// <React.StrictMode>
|
||||
<BrowserRouter basename="/webui/">
|
||||
|
@@ -1,20 +1,36 @@
|
||||
import { Card, CardBody } from '@heroui/card'
|
||||
import { Tab, Tabs } from '@heroui/tabs'
|
||||
import clsx from 'clsx'
|
||||
import { useMediaQuery } from 'react-responsive'
|
||||
import { useNavigate, useSearchParams } from 'react-router-dom'
|
||||
|
||||
import ChangePasswordCard from './change_password'
|
||||
import LoginConfigCard from './login'
|
||||
import OneBotConfigCard from './onebot'
|
||||
import ThemeConfigCard from './theme'
|
||||
import WebUIConfigCard from './webui'
|
||||
|
||||
export interface ConfigPageProps {
|
||||
children?: React.ReactNode
|
||||
size?: 'sm' | 'md' | 'lg'
|
||||
}
|
||||
|
||||
const ConfingPageItem: React.FC<ConfigPageProps> = ({ children }) => {
|
||||
const ConfingPageItem: React.FC<ConfigPageProps> = ({
|
||||
children,
|
||||
size = 'md'
|
||||
}) => {
|
||||
return (
|
||||
<Card className="bg-opacity-50 backdrop-blur-sm">
|
||||
<CardBody className="items-center py-5">
|
||||
<div className="w-96 max-w-full flex flex-col gap-2">{children}</div>
|
||||
<div
|
||||
className={clsx('max-w-full flex flex-col gap-2', {
|
||||
'w-72': size === 'sm',
|
||||
'w-96': size === 'md',
|
||||
'w-[32rem]': size === 'lg'
|
||||
})}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</CardBody>
|
||||
</Card>
|
||||
)
|
||||
@@ -22,6 +38,11 @@ const ConfingPageItem: React.FC<ConfigPageProps> = ({ children }) => {
|
||||
|
||||
export default function ConfigPage() {
|
||||
const isMediumUp = useMediaQuery({ minWidth: 768 })
|
||||
const navigate = useNavigate()
|
||||
const search = useSearchParams({
|
||||
tab: 'onebot'
|
||||
})[0]
|
||||
const tab = search.get('tab') ?? 'onebot'
|
||||
|
||||
return (
|
||||
<section className="w-[1000px] max-w-full md:mx-auto gap-4 py-8 px-2 md:py-10">
|
||||
@@ -30,6 +51,10 @@ export default function ConfigPage() {
|
||||
fullWidth
|
||||
className="w-full"
|
||||
isVertical={isMediumUp}
|
||||
selectedKey={tab}
|
||||
onSelectionChange={(key) => {
|
||||
navigate(`/config?tab=${key}`)
|
||||
}}
|
||||
classNames={{
|
||||
tabList: 'sticky flex top-14 bg-opacity-50 backdrop-blur-sm',
|
||||
panel: 'w-full relative',
|
||||
@@ -47,12 +72,22 @@ export default function ConfigPage() {
|
||||
<WebUIConfigCard />
|
||||
</ConfingPageItem>
|
||||
</Tab>
|
||||
|
||||
<Tab title="登录配置" key="login">
|
||||
<ConfingPageItem>
|
||||
<LoginConfigCard />
|
||||
</ConfingPageItem>
|
||||
</Tab>
|
||||
<Tab title="修改密码" key="token">
|
||||
<ConfingPageItem>
|
||||
<ChangePasswordCard />
|
||||
</ConfingPageItem>
|
||||
</Tab>
|
||||
|
||||
<Tab title="主题配置" key="theme">
|
||||
<ConfingPageItem size="lg">
|
||||
<ThemeConfigCard />
|
||||
</ConfingPageItem>
|
||||
</Tab>
|
||||
</Tabs>
|
||||
</section>
|
||||
)
|
||||
|
89
napcat.webui/src/pages/dashboard/config/login.tsx
Normal file
89
napcat.webui/src/pages/dashboard/config/login.tsx
Normal file
@@ -0,0 +1,89 @@
|
||||
import { Input } from '@heroui/input'
|
||||
import { useRequest } from 'ahooks'
|
||||
import { useEffect } from 'react'
|
||||
import { Controller, useForm } from 'react-hook-form'
|
||||
import toast from 'react-hot-toast'
|
||||
|
||||
import SaveButtons from '@/components/button/save_buttons'
|
||||
import PageLoading from '@/components/page_loading'
|
||||
|
||||
import QQManager from '@/controllers/qq_manager'
|
||||
|
||||
const LoginConfigCard = () => {
|
||||
const {
|
||||
data: quickLoginData,
|
||||
loading: quickLoginLoading,
|
||||
error: quickLoginError,
|
||||
refreshAsync: refreshQuickLogin
|
||||
} = useRequest(QQManager.getQuickLoginQQ)
|
||||
const {
|
||||
control,
|
||||
handleSubmit: handleOnebotSubmit,
|
||||
formState: { isSubmitting },
|
||||
setValue: setOnebotValue
|
||||
} = useForm<{
|
||||
quickLoginQQ: string
|
||||
}>({
|
||||
defaultValues: {
|
||||
quickLoginQQ: ''
|
||||
}
|
||||
})
|
||||
|
||||
const reset = () => {
|
||||
setOnebotValue('quickLoginQQ', quickLoginData ?? '')
|
||||
}
|
||||
|
||||
const onSubmit = handleOnebotSubmit(async (data) => {
|
||||
try {
|
||||
await QQManager.setQuickLoginQQ(data.quickLoginQQ)
|
||||
toast.success('保存成功')
|
||||
} catch (error) {
|
||||
const msg = (error as Error).message
|
||||
toast.error(`保存失败: ${msg}`)
|
||||
}
|
||||
})
|
||||
|
||||
const onRefresh = async () => {
|
||||
try {
|
||||
await refreshQuickLogin()
|
||||
toast.success('刷新成功')
|
||||
} catch (error) {
|
||||
const msg = (error as Error).message
|
||||
toast.error(`刷新失败: ${msg}`)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
reset()
|
||||
}, [quickLoginData])
|
||||
|
||||
if (quickLoginLoading) return <PageLoading loading={true} />
|
||||
|
||||
return (
|
||||
<>
|
||||
<title>OneBot配置 - NapCat WebUI</title>
|
||||
<div className="flex-shrink-0 w-full">快速登录QQ</div>
|
||||
<Controller
|
||||
control={control}
|
||||
name="quickLoginQQ"
|
||||
render={({ field }) => (
|
||||
<Input
|
||||
{...field}
|
||||
label="快速登录QQ"
|
||||
placeholder="请输入QQ号"
|
||||
isDisabled={!!quickLoginError}
|
||||
errorMessage={quickLoginError ? '获取快速登录QQ失败' : undefined}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<SaveButtons
|
||||
onSubmit={onSubmit}
|
||||
reset={reset}
|
||||
isSubmitting={isSubmitting || quickLoginLoading}
|
||||
refresh={onRefresh}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default LoginConfigCard
|
@@ -30,9 +30,9 @@ const OneBotConfigCard = () => {
|
||||
setOnebotValue('parseMultMsg', config.parseMultMsg)
|
||||
}
|
||||
|
||||
const onSubmit = handleOnebotSubmit((data) => {
|
||||
const onSubmit = handleOnebotSubmit(async (data) => {
|
||||
try {
|
||||
saveConfigWithoutNetwork(data)
|
||||
await saveConfigWithoutNetwork(data)
|
||||
toast.success('保存成功')
|
||||
} catch (error) {
|
||||
const msg = (error as Error).message
|
||||
|
279
napcat.webui/src/pages/dashboard/config/theme.tsx
Normal file
279
napcat.webui/src/pages/dashboard/config/theme.tsx
Normal file
@@ -0,0 +1,279 @@
|
||||
import { Accordion, AccordionItem } from '@heroui/accordion'
|
||||
import { Card, CardBody, CardHeader } from '@heroui/card'
|
||||
import { useRequest } from 'ahooks'
|
||||
import clsx from 'clsx'
|
||||
import { useEffect, useRef } from 'react'
|
||||
import { Controller, useForm, useWatch } from 'react-hook-form'
|
||||
import toast from 'react-hot-toast'
|
||||
import { FaUserAstronaut } from 'react-icons/fa'
|
||||
import { FaPaintbrush } from 'react-icons/fa6'
|
||||
import { IoIosColorPalette } from 'react-icons/io'
|
||||
import { MdDarkMode, MdLightMode } from 'react-icons/md'
|
||||
|
||||
import themes from '@/const/themes'
|
||||
|
||||
import ColorPicker from '@/components/ColorPicker'
|
||||
import SaveButtons from '@/components/button/save_buttons'
|
||||
import PageLoading from '@/components/page_loading'
|
||||
|
||||
import { colorKeys, generateTheme, loadTheme } from '@/utils/theme'
|
||||
|
||||
import WebUIManager from '@/controllers/webui_manager'
|
||||
|
||||
export type PreviewThemeCardProps = {
|
||||
theme: ThemeInfo
|
||||
onPreview: () => void
|
||||
}
|
||||
|
||||
const values = [
|
||||
'',
|
||||
'-50',
|
||||
'-100',
|
||||
'-200',
|
||||
'-300',
|
||||
'-400',
|
||||
'-500',
|
||||
'-600',
|
||||
'-700',
|
||||
'-800',
|
||||
'-900'
|
||||
]
|
||||
const colors = [
|
||||
'primary',
|
||||
'secondary',
|
||||
'success',
|
||||
'danger',
|
||||
'warning',
|
||||
'default'
|
||||
]
|
||||
|
||||
function PreviewThemeCard({ theme, onPreview }: PreviewThemeCardProps) {
|
||||
const style = document.createElement('style')
|
||||
style.innerHTML = generateTheme(theme.theme, theme.name)
|
||||
const cardRef = useRef<HTMLDivElement>(null)
|
||||
useEffect(() => {
|
||||
document.head.appendChild(style)
|
||||
return () => {
|
||||
document.head.removeChild(style)
|
||||
}
|
||||
}, [])
|
||||
return (
|
||||
<Card
|
||||
ref={cardRef}
|
||||
shadow="sm"
|
||||
radius="sm"
|
||||
isPressable
|
||||
onPress={onPreview}
|
||||
className={clsx('text-primary bg-primary-50', theme.name)}
|
||||
>
|
||||
<CardHeader className="pb-0 flex flex-col items-start gap-1">
|
||||
<div className="px-1 rounded-md bg-primary text-primary-foreground">
|
||||
{theme.name}
|
||||
</div>
|
||||
<div className="text-xs flex items-center gap-1 text-primary-300">
|
||||
<FaUserAstronaut />
|
||||
{theme.author ?? '未知'}
|
||||
</div>
|
||||
<div className="text-xs text-primary-200">{theme.description}</div>
|
||||
</CardHeader>
|
||||
<CardBody>
|
||||
<div className="flex flex-col gap-1">
|
||||
{colors.map((color) => (
|
||||
<div className="flex gap-1 items-center flex-wrap" key={color}>
|
||||
<div className="text-xs w-4 text-right">
|
||||
{color[0].toUpperCase()}
|
||||
</div>
|
||||
{values.map((value) => (
|
||||
<div
|
||||
key={value}
|
||||
className={clsx(
|
||||
'w-2 h-2 rounded-full shadow-small',
|
||||
`bg-${color}${value}`
|
||||
)}
|
||||
></div>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</CardBody>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
const ThemeConfigCard = () => {
|
||||
const { data, loading, error, refreshAsync } = useRequest(
|
||||
WebUIManager.getThemeConfig
|
||||
)
|
||||
const {
|
||||
control,
|
||||
handleSubmit: handleOnebotSubmit,
|
||||
formState: { isSubmitting },
|
||||
setValue: setOnebotValue
|
||||
} = useForm<{
|
||||
theme: ThemeConfig
|
||||
}>({
|
||||
defaultValues: {
|
||||
theme: {
|
||||
dark: {},
|
||||
light: {}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// 使用 useRef 存储 style 标签引用
|
||||
const styleTagRef = useRef<HTMLStyleElement | null>(null)
|
||||
|
||||
// 在组件挂载时创建 style 标签,并在卸载时清理
|
||||
useEffect(() => {
|
||||
const styleTag = document.createElement('style')
|
||||
document.head.appendChild(styleTag)
|
||||
styleTagRef.current = styleTag
|
||||
return () => {
|
||||
if (styleTagRef.current) {
|
||||
document.head.removeChild(styleTagRef.current)
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
const theme = useWatch({ control, name: 'theme' })
|
||||
|
||||
const reset = () => {
|
||||
if (data) setOnebotValue('theme', data)
|
||||
}
|
||||
|
||||
const onSubmit = handleOnebotSubmit(async (data) => {
|
||||
try {
|
||||
await WebUIManager.setThemeConfig(data.theme)
|
||||
toast.success('保存成功')
|
||||
loadTheme()
|
||||
} catch (error) {
|
||||
const msg = (error as Error).message
|
||||
toast.error(`保存失败: ${msg}`)
|
||||
}
|
||||
})
|
||||
|
||||
const onRefresh = async () => {
|
||||
try {
|
||||
await refreshAsync()
|
||||
toast.success('刷新成功')
|
||||
} catch (error) {
|
||||
const msg = (error as Error).message
|
||||
toast.error(`刷新失败: ${msg}`)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
reset()
|
||||
}, [data])
|
||||
|
||||
useEffect(() => {
|
||||
if (theme && styleTagRef.current) {
|
||||
const css = generateTheme(theme)
|
||||
styleTagRef.current.innerHTML = css
|
||||
}
|
||||
}, [theme])
|
||||
|
||||
if (loading) return <PageLoading loading={true} />
|
||||
|
||||
if (error)
|
||||
return (
|
||||
<div className="py-24 text-danger-500 text-center">{error.message}</div>
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
<title>主题配置 - NapCat WebUI</title>
|
||||
|
||||
<SaveButtons
|
||||
onSubmit={onSubmit}
|
||||
reset={reset}
|
||||
isSubmitting={isSubmitting}
|
||||
refresh={onRefresh}
|
||||
className="items-end w-full p-4"
|
||||
/>
|
||||
<div className="px-4 text-sm text-default-600">实时预览,记得保存!</div>
|
||||
<Accordion variant="splitted" defaultExpandedKeys={['select']}>
|
||||
<AccordionItem
|
||||
key="select"
|
||||
aria-label="Pick Color"
|
||||
title="选择主题"
|
||||
subtitle="可以切换夜间/白昼模式查看对应颜色"
|
||||
className="shadow-small"
|
||||
startContent={<IoIosColorPalette />}
|
||||
>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{themes.map((theme) => (
|
||||
<PreviewThemeCard
|
||||
key={theme.name}
|
||||
theme={theme}
|
||||
onPreview={() => {
|
||||
setOnebotValue('theme', theme.theme)
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</AccordionItem>
|
||||
|
||||
<AccordionItem
|
||||
key="pick"
|
||||
aria-label="Pick Color"
|
||||
title="自定义配色"
|
||||
className="shadow-small"
|
||||
startContent={<FaPaintbrush />}
|
||||
>
|
||||
<div className="space-y-2">
|
||||
{(['dark', 'light'] as const).map((mode) => (
|
||||
<div
|
||||
key={mode}
|
||||
className={clsx(
|
||||
'p-2 rounded-md',
|
||||
mode === 'dark' ? 'text-white' : 'text-black',
|
||||
mode === 'dark'
|
||||
? 'bg-content1-foreground dark:bg-content1'
|
||||
: 'bg-content1 dark:bg-content1-foreground'
|
||||
)}
|
||||
>
|
||||
<h3 className="text-center p-2 rounded-md bg-content2 mb-2 text-default-800 flex items-center justify-center">
|
||||
{mode === 'dark' ? (
|
||||
<MdDarkMode size={24} />
|
||||
) : (
|
||||
<MdLightMode size={24} />
|
||||
)}
|
||||
{mode === 'dark' ? '夜间模式主题' : '白昼模式主题'}
|
||||
</h3>
|
||||
{colorKeys.map((key) => (
|
||||
<div
|
||||
key={key}
|
||||
className="grid grid-cols-2 items-center mb-2 gap-2"
|
||||
>
|
||||
<label className="text-right">{key}</label>
|
||||
<Controller
|
||||
control={control}
|
||||
name={`theme.${mode}.${key}`}
|
||||
render={({ field: { value, onChange } }) => {
|
||||
const hslArray = value?.split(' ') ?? [0, 0, 0]
|
||||
const color = `hsl(${hslArray[0]}, ${hslArray[1]}, ${hslArray[2]})`
|
||||
return (
|
||||
<ColorPicker
|
||||
color={color}
|
||||
onChange={(result) => {
|
||||
onChange(
|
||||
`${result.hsl.h} ${result.hsl.s * 100}% ${result.hsl.l * 100}%`
|
||||
)
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default ThemeConfigCard
|
@@ -81,6 +81,10 @@ const WebUIConfigCard = () => {
|
||||
onDelete={async () => {
|
||||
try {
|
||||
await FileManager.deleteWebUIFont()
|
||||
toast.success('删除成功')
|
||||
setTimeout(() => {
|
||||
window.location.reload()
|
||||
}, 1000)
|
||||
} catch (error) {
|
||||
toast.error('删除失败: ' + (error as Error).message)
|
||||
}
|
||||
|
@@ -105,7 +105,7 @@ const DashboardIndexPage: React.FC = () => {
|
||||
<SystemStatusCard setArchInfo={setArchInfo} />
|
||||
</div>
|
||||
<Networks />
|
||||
<Card className="bg-opacity-60 shadow-sm shadow-primary-50">
|
||||
<Card className="bg-opacity-60 shadow-sm shadow-primary-100">
|
||||
<CardBody>
|
||||
<Hitokoto />
|
||||
</CardBody>
|
||||
|
@@ -1,14 +1,46 @@
|
||||
import { Spinner } from '@heroui/spinner'
|
||||
import { AnimatePresence, motion } from 'motion/react'
|
||||
import { Suspense } from 'react'
|
||||
import { Outlet, useLocation } from 'react-router-dom'
|
||||
import { Suspense, useEffect } from 'react'
|
||||
import { Outlet, useLocation, useNavigate } from 'react-router-dom'
|
||||
|
||||
import useAuth from '@/hooks/auth'
|
||||
import useDialog from '@/hooks/use-dialog'
|
||||
|
||||
import WebUIManager from '@/controllers/webui_manager'
|
||||
import DefaultLayout from '@/layouts/default'
|
||||
|
||||
const CheckDefaultPassword = () => {
|
||||
const { isAuth } = useAuth()
|
||||
const dialog = useDialog()
|
||||
const navigate = useNavigate()
|
||||
const checkDefaultPassword = async () => {
|
||||
const data = await WebUIManager.checkUsingDefaultToken()
|
||||
if (data) {
|
||||
dialog.confirm({
|
||||
title: '修改默认密码',
|
||||
content: '检测到当前密码为默认密码,请尽快修改密码。',
|
||||
confirmText: '前往修改',
|
||||
onConfirm: () => {
|
||||
navigate('/config?tab=token')
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (isAuth) {
|
||||
checkDefaultPassword()
|
||||
}
|
||||
}, [isAuth])
|
||||
return null
|
||||
}
|
||||
|
||||
export default function IndexPage() {
|
||||
const location = useLocation()
|
||||
|
||||
return (
|
||||
<DefaultLayout>
|
||||
<CheckDefaultPassword />
|
||||
<Suspense
|
||||
fallback={
|
||||
<div className="flex justify-center px-10">
|
||||
|
@@ -1,6 +1,6 @@
|
||||
@layer base {
|
||||
.shiny-text {
|
||||
@apply text-pink-400 text-opacity-60;
|
||||
@apply text-primary-400 text-opacity-60;
|
||||
background-size: 200% 100%;
|
||||
-webkit-background-clip: text;
|
||||
background-clip: text;
|
||||
@@ -10,7 +10,7 @@
|
||||
background-image: linear-gradient(
|
||||
120deg,
|
||||
rgba(255, 50, 50, 0) 40%,
|
||||
rgba(255, 76, 76, 0.8) 50%,
|
||||
hsl(var(--heroui-primary-400) / 0.8) 50%,
|
||||
rgba(255, 50, 50, 0) 60%
|
||||
);
|
||||
}
|
||||
@@ -18,11 +18,10 @@
|
||||
background-image: linear-gradient(
|
||||
120deg,
|
||||
rgba(255, 255, 255, 0) 40%,
|
||||
rgba(206, 21, 21, 0.8) 50%,
|
||||
hsl(var(--heroui-primary-600) / 0.8) 50%,
|
||||
rgba(255, 255, 255, 0) 60%
|
||||
);
|
||||
}
|
||||
|
||||
@keyframes shine {
|
||||
0% {
|
||||
background-position: 100%;
|
||||
|
133
napcat.webui/src/types/server.d.ts
vendored
133
napcat.webui/src/types/server.d.ts
vendored
@@ -48,3 +48,136 @@ interface SystemStatus {
|
||||
}
|
||||
arch: string
|
||||
}
|
||||
|
||||
interface ThemeConfigItem {
|
||||
'--heroui-background': string
|
||||
'--heroui-foreground-50': string
|
||||
'--heroui-foreground-100': string
|
||||
'--heroui-foreground-200': string
|
||||
'--heroui-foreground-300': string
|
||||
'--heroui-foreground-400': string
|
||||
'--heroui-foreground-500': string
|
||||
'--heroui-foreground-600': string
|
||||
'--heroui-foreground-700': string
|
||||
'--heroui-foreground-800': string
|
||||
'--heroui-foreground-900': string
|
||||
'--heroui-foreground': string
|
||||
'--heroui-focus': string
|
||||
'--heroui-overlay': string
|
||||
'--heroui-divider': string
|
||||
'--heroui-divider-opacity': string
|
||||
'--heroui-content1': string
|
||||
'--heroui-content1-foreground': string
|
||||
'--heroui-content2': string
|
||||
'--heroui-content2-foreground': string
|
||||
'--heroui-content3': string
|
||||
'--heroui-content3-foreground': string
|
||||
'--heroui-content4': string
|
||||
'--heroui-content4-foreground': string
|
||||
'--heroui-default-50': string
|
||||
'--heroui-default-100': string
|
||||
'--heroui-default-200': string
|
||||
'--heroui-default-300': string
|
||||
'--heroui-default-400': string
|
||||
'--heroui-default-500': string
|
||||
'--heroui-default-600': string
|
||||
'--heroui-default-700': string
|
||||
'--heroui-default-800': string
|
||||
'--heroui-default-900': string
|
||||
'--heroui-default-foreground': string
|
||||
'--heroui-default': string
|
||||
// 新增 danger
|
||||
'--heroui-danger-50': string
|
||||
'--heroui-danger-100': string
|
||||
'--heroui-danger-200': string
|
||||
'--heroui-danger-300': string
|
||||
'--heroui-danger-400': string
|
||||
'--heroui-danger-500': string
|
||||
'--heroui-danger-600': string
|
||||
'--heroui-danger-700': string
|
||||
'--heroui-danger-800': string
|
||||
'--heroui-danger-900': string
|
||||
'--heroui-danger-foreground': string
|
||||
'--heroui-danger': string
|
||||
// 新增 primary
|
||||
'--heroui-primary-50': string
|
||||
'--heroui-primary-100': string
|
||||
'--heroui-primary-200': string
|
||||
'--heroui-primary-300': string
|
||||
'--heroui-primary-400': string
|
||||
'--heroui-primary-500': string
|
||||
'--heroui-primary-600': string
|
||||
'--heroui-primary-700': string
|
||||
'--heroui-primary-800': string
|
||||
'--heroui-primary-900': string
|
||||
'--heroui-primary-foreground': string
|
||||
'--heroui-primary': string
|
||||
// 新增 secondary
|
||||
'--heroui-secondary-50': string
|
||||
'--heroui-secondary-100': string
|
||||
'--heroui-secondary-200': string
|
||||
'--heroui-secondary-300': string
|
||||
'--heroui-secondary-400': string
|
||||
'--heroui-secondary-500': string
|
||||
'--heroui-secondary-600': string
|
||||
'--heroui-secondary-700': string
|
||||
'--heroui-secondary-800': string
|
||||
'--heroui-secondary-900': string
|
||||
'--heroui-secondary-foreground': string
|
||||
'--heroui-secondary': string
|
||||
// 新增 success
|
||||
'--heroui-success-50': string
|
||||
'--heroui-success-100': string
|
||||
'--heroui-success-200': string
|
||||
'--heroui-success-300': string
|
||||
'--heroui-success-400': string
|
||||
'--heroui-success-500': string
|
||||
'--heroui-success-600': string
|
||||
'--heroui-success-700': string
|
||||
'--heroui-success-800': string
|
||||
'--heroui-success-900': string
|
||||
'--heroui-success-foreground': string
|
||||
'--heroui-success': string
|
||||
// 新增 warning
|
||||
'--heroui-warning-50': string
|
||||
'--heroui-warning-100': string
|
||||
'--heroui-warning-200': string
|
||||
'--heroui-warning-300': string
|
||||
'--heroui-warning-400': string
|
||||
'--heroui-warning-500': string
|
||||
'--heroui-warning-600': string
|
||||
'--heroui-warning-700': string
|
||||
'--heroui-warning-800': string
|
||||
'--heroui-warning-900': string
|
||||
'--heroui-warning-foreground': string
|
||||
'--heroui-warning': string
|
||||
// 其它配置
|
||||
'--heroui-code-background': string
|
||||
'--heroui-strong': string
|
||||
'--heroui-code-mdx': string
|
||||
'--heroui-divider-weight': string
|
||||
'--heroui-disabled-opacity': string
|
||||
'--heroui-font-size-tiny': string
|
||||
'--heroui-font-size-small': string
|
||||
'--heroui-font-size-medium': string
|
||||
'--heroui-font-size-large': string
|
||||
'--heroui-line-height-tiny': string
|
||||
'--heroui-line-height-small': string
|
||||
'--heroui-line-height-medium': string
|
||||
'--heroui-line-height-large': string
|
||||
'--heroui-radius-small': string
|
||||
'--heroui-radius-medium': string
|
||||
'--heroui-radius-large': string
|
||||
'--heroui-border-width-small': string
|
||||
'--heroui-border-width-medium': string
|
||||
'--heroui-border-width-large': string
|
||||
'--heroui-box-shadow-small': string
|
||||
'--heroui-box-shadow-medium': string
|
||||
'--heroui-box-shadow-large': string
|
||||
'--heroui-hover-opacity': string
|
||||
}
|
||||
|
||||
interface ThemeConfig {
|
||||
dark: ThemeConfigItem
|
||||
light: ThemeConfigItem
|
||||
}
|
||||
|
6
napcat.webui/src/types/theme.d.ts
vendored
Normal file
6
napcat.webui/src/types/theme.d.ts
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
interface ThemeInfo {
|
||||
theme: ThemeConfig
|
||||
name: string
|
||||
description?: string
|
||||
author?: string
|
||||
}
|
@@ -1,19 +1,21 @@
|
||||
import { PlayMode } from '@/const/enum'
|
||||
|
||||
import WebUIManager from '@/controllers/webui_manager'
|
||||
import type {
|
||||
FinalMusic,
|
||||
Music163ListResponse,
|
||||
Music163URLResponse
|
||||
} from '@/types/music'
|
||||
|
||||
import WebUIManager from '@/controllers/webui_manager'
|
||||
/**
|
||||
* 获取网易云音乐歌单
|
||||
* @param id 歌单id
|
||||
* @returns 歌单信息
|
||||
*/
|
||||
export const get163MusicList = async (id: string) => {
|
||||
let res = await WebUIManager.proxy<Music163ListResponse>('https://wavesgame.top/playlist/track/all?id=' + id);
|
||||
let res = await WebUIManager.proxy<Music163ListResponse>(
|
||||
'https://wavesgame.top/playlist/track/all?id=' + id
|
||||
)
|
||||
// const res = await request.get<Music163ListResponse>(
|
||||
// `https://wavesgame.top/playlist/track/all?id=${id}`
|
||||
// )
|
||||
@@ -71,7 +73,7 @@ export const get163MusicListSongs = async (id: string) => {
|
||||
if (songURL) {
|
||||
finalMusic.push({
|
||||
id: song.id,
|
||||
url: songURL,
|
||||
url: songURL.replace(/http:\/\//, '//').replace(/https:\/\//, '//'),
|
||||
title: song.name,
|
||||
artist: song.ar.map((p) => p.name).join('/'),
|
||||
cover: song.al.picUrl
|
||||
|
141
napcat.webui/src/utils/theme.ts
Normal file
141
napcat.webui/src/utils/theme.ts
Normal file
@@ -0,0 +1,141 @@
|
||||
import { request } from './request'
|
||||
|
||||
const style = document.createElement('style')
|
||||
document.head.appendChild(style)
|
||||
|
||||
export function loadTheme() {
|
||||
request('/files/theme.css?_t=' + Date.now())
|
||||
.then((res) => res.data)
|
||||
.then((css) => {
|
||||
style.innerHTML = css
|
||||
})
|
||||
.catch(() => {
|
||||
console.error('Failed to load theme.css')
|
||||
})
|
||||
}
|
||||
|
||||
export const colorKeys = [
|
||||
'--heroui-background',
|
||||
|
||||
'--heroui-foreground-50',
|
||||
'--heroui-foreground-100',
|
||||
'--heroui-foreground-200',
|
||||
'--heroui-foreground-300',
|
||||
'--heroui-foreground-400',
|
||||
'--heroui-foreground-500',
|
||||
'--heroui-foreground-600',
|
||||
'--heroui-foreground-700',
|
||||
'--heroui-foreground-800',
|
||||
'--heroui-foreground-900',
|
||||
'--heroui-foreground',
|
||||
|
||||
'--heroui-content1',
|
||||
'--heroui-content1-foreground',
|
||||
'--heroui-content2',
|
||||
'--heroui-content2-foreground',
|
||||
'--heroui-content3',
|
||||
'--heroui-content3-foreground',
|
||||
'--heroui-content4',
|
||||
'--heroui-content4-foreground',
|
||||
|
||||
'--heroui-default-50',
|
||||
'--heroui-default-100',
|
||||
'--heroui-default-200',
|
||||
'--heroui-default-300',
|
||||
'--heroui-default-400',
|
||||
'--heroui-default-500',
|
||||
'--heroui-default-600',
|
||||
'--heroui-default-700',
|
||||
'--heroui-default-800',
|
||||
'--heroui-default-900',
|
||||
'--heroui-default-foreground',
|
||||
'--heroui-default',
|
||||
|
||||
'--heroui-danger-50',
|
||||
'--heroui-danger-100',
|
||||
'--heroui-danger-200',
|
||||
'--heroui-danger-300',
|
||||
'--heroui-danger-400',
|
||||
'--heroui-danger-500',
|
||||
'--heroui-danger-600',
|
||||
'--heroui-danger-700',
|
||||
'--heroui-danger-800',
|
||||
'--heroui-danger-900',
|
||||
'--heroui-danger-foreground',
|
||||
'--heroui-danger',
|
||||
|
||||
'--heroui-primary-50',
|
||||
'--heroui-primary-100',
|
||||
'--heroui-primary-200',
|
||||
'--heroui-primary-300',
|
||||
'--heroui-primary-400',
|
||||
'--heroui-primary-500',
|
||||
'--heroui-primary-600',
|
||||
'--heroui-primary-700',
|
||||
'--heroui-primary-800',
|
||||
'--heroui-primary-900',
|
||||
'--heroui-primary-foreground',
|
||||
'--heroui-primary',
|
||||
|
||||
'--heroui-secondary-50',
|
||||
'--heroui-secondary-100',
|
||||
'--heroui-secondary-200',
|
||||
'--heroui-secondary-300',
|
||||
'--heroui-secondary-400',
|
||||
'--heroui-secondary-500',
|
||||
'--heroui-secondary-600',
|
||||
'--heroui-secondary-700',
|
||||
'--heroui-secondary-800',
|
||||
'--heroui-secondary-900',
|
||||
'--heroui-secondary-foreground',
|
||||
'--heroui-secondary',
|
||||
|
||||
'--heroui-success-50',
|
||||
'--heroui-success-100',
|
||||
'--heroui-success-200',
|
||||
'--heroui-success-300',
|
||||
'--heroui-success-400',
|
||||
'--heroui-success-500',
|
||||
'--heroui-success-600',
|
||||
'--heroui-success-700',
|
||||
'--heroui-success-800',
|
||||
'--heroui-success-900',
|
||||
'--heroui-success-foreground',
|
||||
'--heroui-success',
|
||||
|
||||
'--heroui-warning-50',
|
||||
'--heroui-warning-100',
|
||||
'--heroui-warning-200',
|
||||
'--heroui-warning-300',
|
||||
'--heroui-warning-400',
|
||||
'--heroui-warning-500',
|
||||
'--heroui-warning-600',
|
||||
'--heroui-warning-700',
|
||||
'--heroui-warning-800',
|
||||
'--heroui-warning-900',
|
||||
'--heroui-warning-foreground',
|
||||
'--heroui-warning',
|
||||
|
||||
'--heroui-focus',
|
||||
'--heroui-overlay',
|
||||
'--heroui-divider',
|
||||
'--heroui-code-background',
|
||||
'--heroui-strong',
|
||||
'--heroui-code-mdx'
|
||||
] as const
|
||||
|
||||
export const generateTheme = (theme: ThemeConfig, validField?: string) => {
|
||||
let css = `:root ${validField ? `.${validField}` : ''}, .light ${validField ? `.${validField}` : ''}, [data-theme="light"] ${validField ? `.${validField}` : ''} {`
|
||||
for (const key in theme.light) {
|
||||
const _key = key as keyof ThemeConfigItem
|
||||
css += `${_key}: ${theme.light[_key]};`
|
||||
}
|
||||
css += `}`
|
||||
css += `.dark ${validField ? `.${validField}` : ''}, [data-theme="dark"] ${validField ? `.${validField}` : ''} {`
|
||||
for (const key in theme.dark) {
|
||||
const _key = key as keyof ThemeConfigItem
|
||||
css += `${_key}: ${theme.dark[_key]};`
|
||||
}
|
||||
css += `}`
|
||||
return css
|
||||
}
|
@@ -9,6 +9,12 @@ export default {
|
||||
'./src/components/**/*.{js,ts,jsx,tsx,mdx}',
|
||||
'./node_modules/@heroui/theme/dist/**/*.{js,ts,jsx,tsx}'
|
||||
],
|
||||
safelist: [
|
||||
{
|
||||
pattern:
|
||||
/bg-(primary|secondary|success|danger|warning|default)-(50|100|200|300|400|500|600|700|800|900)/
|
||||
}
|
||||
],
|
||||
theme: {
|
||||
extend: {}
|
||||
},
|
||||
|
@@ -34,7 +34,8 @@ export default defineConfig(({ mode }) => {
|
||||
ws: true,
|
||||
changeOrigin: true
|
||||
},
|
||||
'/api': backendDebugUrl
|
||||
'/api': backendDebugUrl,
|
||||
'/files': backendDebugUrl
|
||||
}
|
||||
},
|
||||
build: {
|
||||
|
29
package.json
29
package.json
@@ -2,7 +2,7 @@
|
||||
"name": "napcat",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"version": "4.5.12",
|
||||
"version": "4.7.6",
|
||||
"scripts": {
|
||||
"build:universal": "npm run build:webui && vite build --mode universal || exit 1",
|
||||
"build:framework": "npm run build:webui && vite build --mode framework || exit 1",
|
||||
@@ -23,6 +23,7 @@
|
||||
"@eslint/js": "^9.14.0",
|
||||
"@ffmpeg.wasm/main": "^0.13.1",
|
||||
"@homebridge/node-pty-prebuilt-multiarch": "^0.12.0-beta.5",
|
||||
"@hono/node-server": "^1.13.8",
|
||||
"@log4js-node/log4js-api": "^1.0.2",
|
||||
"@napneko/nap-proto-core": "^0.0.4",
|
||||
"@rollup/plugin-node-resolve": "^16.0.0",
|
||||
@@ -32,38 +33,40 @@
|
||||
"@types/express": "^5.0.0",
|
||||
"@types/multer": "^1.4.12",
|
||||
"@types/node": "^22.0.1",
|
||||
"@types/on-finished": "^2.3.4",
|
||||
"@types/qrcode-terminal": "^0.12.2",
|
||||
"@types/react-color": "^3.0.13",
|
||||
"@types/type-is": "^1.6.7",
|
||||
"@types/ws": "^8.5.12",
|
||||
"@typescript-eslint/eslint-plugin": "^8.3.0",
|
||||
"@typescript-eslint/parser": "^8.3.0",
|
||||
"ajv": "^8.13.0",
|
||||
"async-mutex": "^0.5.0",
|
||||
"commander": "^13.0.0",
|
||||
"compressing": "^1.10.1",
|
||||
"cors": "^2.8.5",
|
||||
"esbuild": "0.24.0",
|
||||
"esbuild": "0.25.0",
|
||||
"eslint": "^9.14.0",
|
||||
"eslint-import-resolver-typescript": "^3.6.1",
|
||||
"eslint-import-resolver-typescript": "^4.0.0",
|
||||
"eslint-plugin-import": "^2.29.1",
|
||||
"express-rate-limit": "^7.5.0",
|
||||
"fast-xml-parser": "^4.3.6",
|
||||
"file-type": "^20.0.0",
|
||||
"globals": "^15.12.0",
|
||||
"globals": "^16.0.0",
|
||||
"hono": "^4.7.2",
|
||||
"image-size": "^1.1.1",
|
||||
"json5": "^2.2.3",
|
||||
"multer": "^1.4.5-lts.1",
|
||||
"napcat.protobuf": "^1.1.3",
|
||||
"typescript": "^5.3.3",
|
||||
"typescript-eslint": "^8.13.0",
|
||||
"vite": "^6.0.1",
|
||||
"vite-plugin-cp": "^4.0.8",
|
||||
"vite-plugin-wasm": "^3.4.1",
|
||||
"vite-tsconfig-paths": "^5.1.0",
|
||||
"silk-wasm": "^3.6.1",
|
||||
"@hono/node-ws": "^1.1.0",
|
||||
"winston": "^3.17.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ffmpeg.wasm/core-mt": "^0.13.2",
|
||||
"compressing": "^1.10.1",
|
||||
"express": "^5.0.0",
|
||||
"piscina": "^4.7.0",
|
||||
"silk-wasm": "^3.6.1",
|
||||
"ws": "^8.18.0"
|
||||
}
|
||||
}
|
||||
"dependencies": {}
|
||||
}
|
@@ -1,9 +1,20 @@
|
||||
import { encode } from 'silk-wasm';
|
||||
import { parentPort } from 'worker_threads';
|
||||
|
||||
export interface EncodeArgs {
|
||||
input: ArrayBufferView | ArrayBuffer
|
||||
sampleRate: number
|
||||
}
|
||||
export default async ({ input, sampleRate }: EncodeArgs) => {
|
||||
export function recvTask<T>(cb: (taskData: T) => Promise<unknown>) {
|
||||
parentPort?.on('message', async (taskData: T) => {
|
||||
try {
|
||||
let ret = await cb(taskData);
|
||||
parentPort?.postMessage(ret);
|
||||
} catch (error: unknown) {
|
||||
parentPort?.postMessage({ error: (error as Error).message });
|
||||
}
|
||||
});
|
||||
}
|
||||
recvTask<EncodeArgs>(async ({ input, sampleRate }) => {
|
||||
return await encode(input, sampleRate);
|
||||
};
|
||||
});
|
@@ -1,4 +1,3 @@
|
||||
import Piscina from 'piscina';
|
||||
import fsPromise from 'fs/promises';
|
||||
import path from 'node:path';
|
||||
import { randomUUID } from 'crypto';
|
||||
@@ -6,16 +5,16 @@ import { EncodeResult, getDuration, getWavFileInfo, isSilk, isWav } from 'silk-w
|
||||
import { LogWrapper } from '@/common/log';
|
||||
import { EncodeArgs } from '@/common/audio-worker';
|
||||
import { FFmpegService } from '@/common/ffmpeg';
|
||||
import { runTask } from './worker';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const ALLOW_SAMPLE_RATE = [8000, 12000, 16000, 24000, 32000, 44100, 48000];
|
||||
|
||||
async function getWorkerPath() {
|
||||
return new URL(/* @vite-ignore */ './audio-worker.mjs', import.meta.url).href;
|
||||
function getWorkerPath() {
|
||||
//return new URL(/* @vite-ignore */ './audio-worker.mjs', import.meta.url).href;
|
||||
return path.join(path.dirname(fileURLToPath(import.meta.url)), 'audio-worker.mjs');
|
||||
}
|
||||
|
||||
const piscina = new Piscina<EncodeArgs, EncodeResult>({
|
||||
filename: await getWorkerPath(),
|
||||
});
|
||||
|
||||
async function guessDuration(pttPath: string, logger: LogWrapper) {
|
||||
const pttFileInfo = await fsPromise.stat(pttPath);
|
||||
@@ -46,7 +45,7 @@ export async function encodeSilk(filePath: string, TEMP_DIR: string, logger: Log
|
||||
const { input, sampleRate } = isWav(file)
|
||||
? await handleWavFile(file, filePath, pcmPath)
|
||||
: { input: await FFmpegService.convert(filePath, pcmPath), sampleRate: 24000 };
|
||||
const silk = await piscina.run({ input: input, sampleRate: sampleRate });
|
||||
const silk = await runTask<EncodeArgs, EncodeResult>(getWorkerPath(), { input: input, sampleRate: sampleRate });
|
||||
fsPromise.unlink(pcmPath).catch((e) => logger.logError('删除临时文件失败', pcmPath, e));
|
||||
await fsPromise.writeFile(pttPath, Buffer.from(silk.data));
|
||||
logger.log(`语音文件${filePath}转换成功!`, pttPath, '时长:', silk.duration);
|
||||
|
@@ -5,6 +5,17 @@ import { readFileSync, statSync, writeFileSync } from 'fs';
|
||||
import type { VideoInfo } from './video';
|
||||
import { fileTypeFromFile } from 'file-type';
|
||||
import imageSize from 'image-size';
|
||||
import { parentPort } from 'worker_threads';
|
||||
export function recvTask<T>(cb: (taskData: T) => Promise<unknown>) {
|
||||
parentPort?.on('message', async (taskData: T) => {
|
||||
try {
|
||||
let ret = await cb(taskData);
|
||||
parentPort?.postMessage(ret);
|
||||
} catch (error: unknown) {
|
||||
parentPort?.postMessage({ error: (error as Error).message });
|
||||
}
|
||||
});
|
||||
}
|
||||
class FFmpegService {
|
||||
public static async extractThumbnail(videoPath: string, thumbnailPath: string): Promise<void> {
|
||||
const ffmpegInstance = await FFmpeg.create({ core: '@ffmpeg.wasm/core-mt' });
|
||||
@@ -137,15 +148,18 @@ interface FFmpegTask {
|
||||
}
|
||||
export default async function handleFFmpegTask({ method, args }: FFmpegTask): Promise<any> {
|
||||
switch (method) {
|
||||
case 'extractThumbnail':
|
||||
return await FFmpegService.extractThumbnail(...args as [string, string]);
|
||||
case 'convertFile':
|
||||
return await FFmpegService.convertFile(...args as [string, string, string]);
|
||||
case 'convert':
|
||||
return await FFmpegService.convert(...args as [string, string]);
|
||||
case 'getVideoInfo':
|
||||
return await FFmpegService.getVideoInfo(...args as [string, string]);
|
||||
default:
|
||||
throw new Error(`Unknown method: ${method}`);
|
||||
case 'extractThumbnail':
|
||||
return await FFmpegService.extractThumbnail(...args as [string, string]);
|
||||
case 'convertFile':
|
||||
return await FFmpegService.convertFile(...args as [string, string, string]);
|
||||
case 'convert':
|
||||
return await FFmpegService.convert(...args as [string, string]);
|
||||
case 'getVideoInfo':
|
||||
return await FFmpegService.getVideoInfo(...args as [string, string]);
|
||||
default:
|
||||
throw new Error(`Unknown method: ${method}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
recvTask<FFmpegTask>(async ({ method, args }: FFmpegTask) => {
|
||||
return await handleFFmpegTask({ method, args });
|
||||
});
|
@@ -1,6 +1,8 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import Piscina from 'piscina';
|
||||
import { VideoInfo } from './video';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { runTask } from './worker';
|
||||
|
||||
type EncodeArgs = {
|
||||
method: 'extractThumbnail' | 'convertFile' | 'convert' | 'getVideoInfo';
|
||||
@@ -9,42 +11,26 @@ type EncodeArgs = {
|
||||
|
||||
type EncodeResult = any;
|
||||
|
||||
async function getWorkerPath() {
|
||||
return new URL(/* @vite-ignore */ './ffmpeg-worker.mjs', import.meta.url).href;
|
||||
function getWorkerPath() {
|
||||
return path.join(path.dirname(fileURLToPath(import.meta.url)), './ffmpeg-worker.mjs');
|
||||
}
|
||||
|
||||
export class FFmpegService {
|
||||
public static async extractThumbnail(videoPath: string, thumbnailPath: string): Promise<void> {
|
||||
const piscina = new Piscina<EncodeArgs, EncodeResult>({
|
||||
filename: await getWorkerPath(),
|
||||
});
|
||||
await piscina.run({ method: 'extractThumbnail', args: [videoPath, thumbnailPath] });
|
||||
await piscina.destroy();
|
||||
await runTask<EncodeArgs, EncodeResult>(getWorkerPath(), { method: 'extractThumbnail', args: [videoPath, thumbnailPath] });
|
||||
}
|
||||
|
||||
public static async convertFile(inputFile: string, outputFile: string, format: string): Promise<void> {
|
||||
const piscina = new Piscina<EncodeArgs, EncodeResult>({
|
||||
filename: await getWorkerPath(),
|
||||
});
|
||||
await piscina.run({ method: 'convertFile', args: [inputFile, outputFile, format] });
|
||||
await piscina.destroy();
|
||||
await runTask<EncodeArgs, EncodeResult>(getWorkerPath(), { method: 'convertFile', args: [inputFile, outputFile, format] });
|
||||
}
|
||||
|
||||
public static async convert(filePath: string, pcmPath: string): Promise<Buffer> {
|
||||
const piscina = new Piscina<EncodeArgs, EncodeResult>({
|
||||
filename: await getWorkerPath(),
|
||||
});
|
||||
const result = await piscina.run({ method: 'convert', args: [filePath, pcmPath] });
|
||||
await piscina.destroy();
|
||||
const result = await runTask<EncodeArgs, EncodeResult>(getWorkerPath(), { method: 'convert', args: [filePath, pcmPath] });
|
||||
return result;
|
||||
}
|
||||
|
||||
public static async getVideoInfo(videoPath: string, thumbnailPath: string): Promise<VideoInfo> {
|
||||
const piscina = new Piscina<EncodeArgs, EncodeResult>({
|
||||
filename: await getWorkerPath(),
|
||||
});
|
||||
const result = await piscina.run({ method: 'getVideoInfo', args: [videoPath, thumbnailPath] });
|
||||
await piscina.destroy();
|
||||
const result = await await runTask<EncodeArgs, EncodeResult>(getWorkerPath(), { method: 'getVideoInfo', args: [videoPath, thumbnailPath] });
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
@@ -54,7 +54,7 @@ export class ForwardMsgBuilder {
|
||||
const id = crypto.randomUUID();
|
||||
const isGroupMsg = msg.some(m => m.isGroupMsg);
|
||||
if (!source) {
|
||||
source = isGroupMsg ? '群聊的聊天记录' : msg.map(m => m.senderName).filter((v, i, a) => a.indexOf(v) === i).slice(0, 4).join('和') + '的聊天记录';
|
||||
source = msg.length === 0 ? '聊天记录' : (isGroupMsg ? '群聊的聊天记录' : msg.map(m => m.senderName).filter((v, i, a) => a.indexOf(v) === i).slice(0, 4).join('和') + '的聊天记录');
|
||||
}
|
||||
if (!news) {
|
||||
news = msg.length === 0 ? [{
|
||||
|
@@ -232,7 +232,7 @@ export function rawMessageToText(msg: RawMessage, recursiveLevel = 0): string {
|
||||
tokens.push(`群聊 [${msg.peerName}(${msg.peerUin})]`);
|
||||
}
|
||||
if (msg.senderUin !== '0') {
|
||||
tokens.push(`[${msg.sendMemberName ?? msg.sendRemarkName ?? msg.sendNickName}(${msg.senderUin})]`);
|
||||
tokens.push(`[${msg.sendMemberName || msg.sendRemarkName || msg.sendNickName}(${msg.senderUin})]`);
|
||||
}
|
||||
} else if (msg.chatType == ChatType.KCHATTYPEDATALINE) {
|
||||
tokens.push('移动设备');
|
||||
|
@@ -163,7 +163,7 @@ class Store {
|
||||
const current = this.get<StoreValueType>(key);
|
||||
|
||||
if (current === null) {
|
||||
this.set(key, 1);
|
||||
this.set(key, 1, 60);
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -180,7 +180,7 @@ class Store {
|
||||
}
|
||||
|
||||
const newValue = numericValue + 1;
|
||||
this.set(key, newValue);
|
||||
this.set(key, newValue, 60);
|
||||
return newValue;
|
||||
}
|
||||
}
|
||||
|
@@ -1 +1 @@
|
||||
export const napCatVersion = '4.5.12';
|
||||
export const napCatVersion = '4.7.6';
|
||||
|
29
src/common/worker.ts
Normal file
29
src/common/worker.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { Worker } from 'worker_threads';
|
||||
|
||||
export async function runTask<T, R>(workerScript: string, taskData: T): Promise<R> {
|
||||
let worker = new Worker(workerScript);
|
||||
try {
|
||||
return await new Promise<R>((resolve, reject) => {
|
||||
worker.on('message', (result: R) => {
|
||||
resolve(result);
|
||||
});
|
||||
|
||||
worker.on('error', (error) => {
|
||||
reject(new Error(`Worker error: ${error.message}`));
|
||||
});
|
||||
|
||||
worker.on('exit', (code) => {
|
||||
if (code !== 0) {
|
||||
reject(new Error(`Worker stopped with exit code ${code}`));
|
||||
}
|
||||
});
|
||||
worker.postMessage(taskData);
|
||||
});
|
||||
} catch (error: unknown) {
|
||||
throw new Error(`Failed to run task: ${(error as Error).message}`);
|
||||
} finally {
|
||||
// Ensure the worker is terminated after the promise is settled
|
||||
worker.terminate();
|
||||
}
|
||||
}
|
||||
|
@@ -41,7 +41,8 @@ export class NTQQFileApi {
|
||||
this.context = context;
|
||||
this.core = core;
|
||||
this.rkeyManager = new RkeyManager([
|
||||
'https://rkey.napneko.icu/rkeys'
|
||||
'https://ss.xingzhige.com/music_card/rkey', // 国内
|
||||
'https://secret-service.bietiaop.com/rkeys',//国内
|
||||
],
|
||||
this.context.logger
|
||||
);
|
||||
@@ -434,9 +435,9 @@ export class NTQQFileApi {
|
||||
};
|
||||
|
||||
try {
|
||||
if (this.core.apis.PacketApi.available && this.packetRkey?.[0] && this.packetRkey?.[1]) {
|
||||
const rkey_expired_private = !this.packetRkey || this.packetRkey[0].time + Number(this.packetRkey[0].ttl) < Date.now() / 1000;
|
||||
const rkey_expired_group = !this.packetRkey || this.packetRkey[0].time + Number(this.packetRkey[0].ttl) < Date.now() / 1000;
|
||||
if (this.core.apis.PacketApi.available) {
|
||||
const rkey_expired_private = !this.packetRkey || (this.packetRkey[0] && this.packetRkey[0].time + Number(this.packetRkey[0].ttl) < Date.now() / 1000);
|
||||
const rkey_expired_group = !this.packetRkey || (this.packetRkey[0] && this.packetRkey[0].time + Number(this.packetRkey[0].ttl) < Date.now() / 1000);
|
||||
if (rkey_expired_private || rkey_expired_group) {
|
||||
this.packetRkey = await this.fetchRkeyWithRetry();
|
||||
}
|
||||
|
@@ -27,6 +27,9 @@ export class NTQQGroupApi {
|
||||
this.core = core;
|
||||
}
|
||||
|
||||
async setGroupRemark(groupCode: string, remark: string) {
|
||||
return this.context.session.getGroupService().modifyGroupRemark(groupCode, remark);
|
||||
}
|
||||
async fetchGroupDetail(groupCode: string) {
|
||||
const [, detailInfo] = await this.core.eventWrapper.callNormalEventV2(
|
||||
'NodeIKernelGroupService/getGroupDetailInfo',
|
||||
@@ -165,7 +168,13 @@ export class NTQQGroupApi {
|
||||
|
||||
return this.groupMemberCache.get(groupCode);
|
||||
}
|
||||
|
||||
async refreshGroupMemberCachePartial(groupCode: string, uid: string) {
|
||||
const member = await this.getGroupMemberEx(groupCode, uid, true);
|
||||
if (member) {
|
||||
this.groupMemberCache.get(groupCode)?.set(uid, member);
|
||||
}
|
||||
return member;
|
||||
}
|
||||
async getGroupMember(groupCode: string | number, memberUinOrUid: string | number) {
|
||||
const groupCodeStr = groupCode.toString();
|
||||
const memberUinOrUidStr = memberUinOrUid.toString();
|
||||
@@ -339,9 +348,9 @@ export class NTQQGroupApi {
|
||||
return this.context.session.getGroupService().uploadGroupBulletinPic(groupCode, _Pskey, imageurl);
|
||||
}
|
||||
|
||||
async handleGroupRequest(notify: GroupNotify, operateType: NTGroupRequestOperateTypes, reason?: string) {
|
||||
async handleGroupRequest(doubt: boolean, notify: GroupNotify, operateType: NTGroupRequestOperateTypes, reason?: string) {
|
||||
return this.context.session.getGroupService().operateSysNotify(
|
||||
false,
|
||||
doubt,
|
||||
{
|
||||
operateType: operateType,
|
||||
targetMsg: {
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { ChatType, GetFileListParam, Peer, RawMessage, SendMessageElement, SendStatusType } from '@/core/types';
|
||||
import { GroupFileInfoUpdateItem, InstanceContext, NapCatCore } from '@/core';
|
||||
import { GroupFileInfoUpdateItem, InstanceContext, NapCatCore, NodeIKernelMsgService } from '@/core';
|
||||
import { GeneralCallResult } from '@/core/services/common';
|
||||
|
||||
export class NTQQMsgApi {
|
||||
@@ -12,6 +12,11 @@ export class NTQQMsgApi {
|
||||
this.context = context;
|
||||
this.core = core;
|
||||
}
|
||||
|
||||
async clickInlineKeyboardButton(...params: Parameters<NodeIKernelMsgService['clickInlineKeyboardButton']>) {
|
||||
return this.context.session.getMsgService().clickInlineKeyboardButton(...params);
|
||||
}
|
||||
|
||||
getMsgByClientSeqAndTime(peer: Peer, replyMsgClientSeq: string, replyMsgTime: string) {
|
||||
// https://bot.q.qq.com/wiki/develop/api-v2/openapi/emoji/model.html#EmojiType 可以用过特殊方式拉取
|
||||
return this.context.session.getMsgService().getMsgByClientSeqAndTime(peer, replyMsgClientSeq, replyMsgTime);
|
||||
@@ -131,6 +136,20 @@ export class NTQQMsgApi {
|
||||
});
|
||||
}
|
||||
|
||||
async queryFirstMsgBySender(peer: Peer, SendersUid: string[]) {
|
||||
console.log(peer, SendersUid);
|
||||
return await this.context.session.getMsgService().queryMsgsWithFilterEx('0', '0', '0', {
|
||||
chatInfo: peer,
|
||||
filterMsgType: [],
|
||||
filterSendersUid: SendersUid,
|
||||
filterMsgToTime: '0',
|
||||
filterMsgFromTime: '0',
|
||||
isReverseOrder: true,
|
||||
isIncludeCurrent: true,
|
||||
pageLimit: 20000,
|
||||
});
|
||||
}
|
||||
|
||||
async setMsgRead(peer: Peer) {
|
||||
return this.context.session.getMsgService().setMsgRead(peer);
|
||||
}
|
||||
|
42
src/core/external/appid.json
vendored
42
src/core/external/appid.json
vendored
@@ -186,5 +186,45 @@
|
||||
"9.9.17-31363": {
|
||||
"appid": 537266500,
|
||||
"qua": "V1_WIN_NQ_9.9.17_31363_GW_B"
|
||||
},
|
||||
"3.2.16-32690": {
|
||||
"appid": 537271229,
|
||||
"qua": "V1_LNX_NQ_3.2.16_32690_GW_B"
|
||||
},
|
||||
"9.9.18-32690": {
|
||||
"appid": 537271194,
|
||||
"qua": "V1_WIN_NQ_9.9.18_32690_GW_B"
|
||||
},
|
||||
"6.9.66-32690": {
|
||||
"appid": 537271218,
|
||||
"qua": "V1_MAC_NQ_6.9.66_32690_GW_B"
|
||||
},
|
||||
"3.2.16-32721": {
|
||||
"appid": 537271229,
|
||||
"qua": "V1_LNX_NQ_3.2.16_32721_GW_B"
|
||||
},
|
||||
"9.9.18-32793": {
|
||||
"appid": 537271244,
|
||||
"qua": "V1_WIN_NQ_9.9.18_32793_GW_B"
|
||||
},
|
||||
"3.2.16-32793": {
|
||||
"appid": 537271279,
|
||||
"qua": "V1_LNX_NQ_3.2.16_32793_GW_B"
|
||||
},
|
||||
"3.2.16-32869": {
|
||||
"appid": 537271329,
|
||||
"qua": "V1_LNX_NQ_3.2.16_32869_GW_B"
|
||||
},
|
||||
"9.9.18-32869": {
|
||||
"appid": 537271294,
|
||||
"qua": "V1_WIN_NQ_9.9.18_32869_GW_B"
|
||||
},
|
||||
"3.2.16-33139": {
|
||||
"appid": 537273909,
|
||||
"qua": "V1_LNX_NQ_3.2.16_33139_GW_B"
|
||||
},
|
||||
"9.9.18-33139": {
|
||||
"appid": 537273874,
|
||||
"qua": "V1_WIN_NQ_9.9.18_33139_GW_B"
|
||||
}
|
||||
}
|
||||
}
|
5
src/core/external/napcat.json
vendored
5
src/core/external/napcat.json
vendored
@@ -4,5 +4,6 @@
|
||||
"fileLogLevel": "debug",
|
||||
"consoleLogLevel": "info",
|
||||
"packetBackend": "auto",
|
||||
"packetServer": ""
|
||||
}
|
||||
"packetServer": "",
|
||||
"o3HookMode": 1
|
||||
}
|
68
src/core/external/offset.json
vendored
68
src/core/external/offset.json
vendored
@@ -175,7 +175,7 @@
|
||||
"send": "713A318",
|
||||
"recv": "713DB50"
|
||||
},
|
||||
"6.9.63.30851-x64": {
|
||||
"6.9.63-30851-x64": {
|
||||
"send": "46C8040",
|
||||
"recv": "46CA8AC"
|
||||
},
|
||||
@@ -195,7 +195,7 @@
|
||||
"send": "713A318",
|
||||
"recv": "713DB50"
|
||||
},
|
||||
"6.9.63.30899-x64": {
|
||||
"6.9.63-30899-x64": {
|
||||
"send": "46C8040",
|
||||
"recv": "46CA8AC"
|
||||
},
|
||||
@@ -211,7 +211,7 @@
|
||||
"send": "39C1350",
|
||||
"recv": "39C5784"
|
||||
},
|
||||
"6.9.63.31245-x64": {
|
||||
"6.9.63-31245-x64": {
|
||||
"send": "4720A40",
|
||||
"recv": "47232AC"
|
||||
},
|
||||
@@ -239,12 +239,68 @@
|
||||
"send": "71BFD48",
|
||||
"recv": "71C3580"
|
||||
},
|
||||
"6.9.65.31363-x64": {
|
||||
"6.9.65-31363-x64": {
|
||||
"send": "4720E80",
|
||||
"recv": "47236EC"
|
||||
},
|
||||
"6.9.65.31363-arm64": {
|
||||
"6.9.65-31363-arm64": {
|
||||
"send": "422CEF8",
|
||||
"recv": "422F710"
|
||||
},
|
||||
"9.9.18-32690-x64": {
|
||||
"send": "39F9630",
|
||||
"recv": "39FDE30"
|
||||
},
|
||||
"3.2.16-32690-x64": {
|
||||
"send": "A5E24C0",
|
||||
"recv": "A5E5EE0"
|
||||
},
|
||||
"3.2.16-32690-arm64": {
|
||||
"send": "7226630",
|
||||
"recv": "7229F60"
|
||||
},
|
||||
"3.2.16-32721-x64": {
|
||||
"send": "A5E24C0",
|
||||
"recv": "A5E5EE0"
|
||||
},
|
||||
"3.2.16-32721-arm64": {
|
||||
"send": "7226630",
|
||||
"recv": "7229F60"
|
||||
},
|
||||
"9.9.18-32793-x64": {
|
||||
"send": "39F9A30",
|
||||
"recv": "39FE230"
|
||||
},
|
||||
"3.2.16-32793-x64": {
|
||||
"send": "A5E24C0",
|
||||
"recv": "A5E5EE0"
|
||||
},
|
||||
"3.2.16-32793-arm64": {
|
||||
"send": "7226630",
|
||||
"recv": "7229F60"
|
||||
},
|
||||
"9.9.18-32869-x64": {
|
||||
"send": "39F9A30",
|
||||
"recv": "39FE230"
|
||||
},
|
||||
"3.2.16-32869-x64": {
|
||||
"send": "A5E24C0",
|
||||
"recv": "A5E5EE0"
|
||||
},
|
||||
"3.2.16-32869-arm64": {
|
||||
"send": "7226630",
|
||||
"recv": "7229F60"
|
||||
},
|
||||
"9.9.18-33139-x64": {
|
||||
"send": "39F5870",
|
||||
"recv": "39FA070"
|
||||
},
|
||||
"3.2.16-33139-x64": {
|
||||
"send": "A634F60",
|
||||
"recv": "A638980"
|
||||
},
|
||||
"3.2.16-33139-arm64": {
|
||||
"send": "7262BB0",
|
||||
"recv": "72664E0"
|
||||
}
|
||||
}
|
||||
}
|
@@ -9,7 +9,8 @@ export const NapcatConfigSchema = Type.Object({
|
||||
fileLogLevel: Type.String({ default: 'debug' }),
|
||||
consoleLogLevel: Type.String({ default: 'info' }),
|
||||
packetBackend: Type.String({ default: 'auto' }),
|
||||
packetServer: Type.String({ default: '' })
|
||||
packetServer: Type.String({ default: '' }),
|
||||
o3HookMode: Type.Number({ default: 0 }),
|
||||
});
|
||||
|
||||
export type NapcatConfig = Static<typeof NapcatConfigSchema>;
|
||||
|
@@ -1,5 +1,5 @@
|
||||
export class NodeIKernelLoginListener {
|
||||
onLoginConnected(...args: any[]): any {
|
||||
onLoginConnected(): Promise<void> | void {
|
||||
}
|
||||
|
||||
onLoginDisConnected(...args: any[]): any {
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { ChatType } from '@/core';
|
||||
import { ChatType, RawMessage } from '@/core';
|
||||
export interface SearchGroupInfo {
|
||||
groupCode: string;
|
||||
ownerUid: string;
|
||||
@@ -56,7 +56,7 @@ export interface GroupSearchResult {
|
||||
nextPos: number;
|
||||
}
|
||||
export interface NodeIKernelSearchListener {
|
||||
|
||||
|
||||
onSearchGroupResult(params: GroupSearchResult): any;
|
||||
|
||||
onSearchFileKeywordsResult(params: {
|
||||
@@ -94,4 +94,27 @@ export interface NodeIKernelSearchListener {
|
||||
}[]
|
||||
}[]
|
||||
}): any;
|
||||
|
||||
onSearchMsgKeywordsResult(params: {
|
||||
searchId: string,
|
||||
hasMore: boolean,
|
||||
resultItems: Array<{
|
||||
msgId: string,
|
||||
msgSeq: string,
|
||||
msgTime: string,
|
||||
senderUid: string,
|
||||
senderUin: string,
|
||||
senderNick: string,
|
||||
senderNickHits: unknown[],
|
||||
senderRemark: string,
|
||||
senderRemarkHits: unknown[],
|
||||
senderCard: string,
|
||||
senderCardHits: unknown[],
|
||||
fieldType: number,
|
||||
fieldText: string,
|
||||
msgRecord: RawMessage;
|
||||
hitsInfo: Array<unknown>,
|
||||
msgAbstract: unknown,
|
||||
}>
|
||||
}): void | Promise<void>;
|
||||
}
|
||||
|
@@ -11,7 +11,7 @@ export class NodeIKernelSessionListener {
|
||||
|
||||
}
|
||||
|
||||
onOpentelemetryInit(args: unknown): any {
|
||||
onOpentelemetryInit(info: { is_init: boolean, is_report: boolean }): any {
|
||||
|
||||
}
|
||||
|
||||
|
@@ -11,7 +11,7 @@ import { PacketLogger } from '@/core/packet/context/loggerContext';
|
||||
|
||||
// 0 send 1 recv
|
||||
export interface NativePacketExportType {
|
||||
InitHook?: (send: string, recv: string, callback: (type: number, uin: string, cmd: string, seq: number, hex_data: string) => void) => boolean;
|
||||
InitHook?: (send: string, recv: string, callback: (type: number, uin: string, cmd: string, seq: number, hex_data: string) => void, o3_hook: boolean) => boolean;
|
||||
SendPacket?: (cmd: string, data: string, trace_id: string) => void;
|
||||
}
|
||||
|
||||
@@ -42,6 +42,7 @@ export class NativePacketClient extends IPacketClient {
|
||||
const platform = process.platform + '.' + process.arch;
|
||||
const moehoo_path = path.join(dirname(fileURLToPath(import.meta.url)), './moehoo/MoeHoo.' + platform + '.node');
|
||||
process.dlopen(this.MoeHooExport, moehoo_path, constants.dlopen.RTLD_LAZY);
|
||||
|
||||
this.MoeHooExport.exports.InitHook?.(send, recv, (type: number, uin: string, cmd: string, seq: number, hex_data: string) => {
|
||||
const trace_id = createHash('md5').update(Buffer.from(hex_data, 'hex')).digest('hex');
|
||||
if (type === 0 && this.cb.get(trace_id + 'recv')) {
|
||||
@@ -55,7 +56,7 @@ export class NativePacketClient extends IPacketClient {
|
||||
// console.log('callback:', callback, trace_id);
|
||||
callback?.({ seq, cmd, hex_data });
|
||||
}
|
||||
});
|
||||
}, this.napcore.config.o3HookMode == 1);
|
||||
this.available = true;
|
||||
}
|
||||
|
||||
|
@@ -1,4 +1,3 @@
|
||||
import { Data, WebSocket, ErrorEvent } from 'ws';
|
||||
import { IPacketClient, RecvPacket } from '@/core/packet/client/baseClient';
|
||||
import { LogStack } from '@/core/packet/context/clientContext';
|
||||
import { NapCoreContext } from '@/core/packet/context/napCoreContext';
|
||||
@@ -83,14 +82,14 @@ export class WsPacketClient extends IPacketClient {
|
||||
this.logger.warn('WebSocket 连接关闭,尝试重连...');
|
||||
reject(new Error('WebSocket 连接关闭'));
|
||||
};
|
||||
this.websocket.onmessage = (event) => this.handleMessage(event.data).catch(err => {
|
||||
this.websocket.onmessage = (ev: MessageEvent<any>) => this.handleMessage(ev).catch(err => {
|
||||
this.logger.error(`处理消息时出错: ${err}`);
|
||||
});
|
||||
this.websocket.onerror = (event: ErrorEvent) => {
|
||||
this.websocket.onerror = (event) => {
|
||||
this.available = false;
|
||||
this.logger.error(`WebSocket 出错: ${event.message}`);
|
||||
this.logger.error(`WebSocket 出错: ${event}`);
|
||||
this.websocket?.close();
|
||||
reject(new Error(`WebSocket 出错: ${event.message}`));
|
||||
reject(new Error(`WebSocket 出错: ${event}`));
|
||||
};
|
||||
});
|
||||
}
|
||||
@@ -99,9 +98,9 @@ export class WsPacketClient extends IPacketClient {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
private async handleMessage(message: Data): Promise<void> {
|
||||
private async handleMessage(message: MessageEvent): Promise<void> {
|
||||
try {
|
||||
const json: RecvPacket = JSON.parse(message.toString());
|
||||
const json: RecvPacket = JSON.parse(message.data.toString());
|
||||
const trace_id_md5 = json.trace_id_md5;
|
||||
const action = json?.type ?? 'init';
|
||||
const event = this.cb.get(`${trace_id_md5}${action}`);
|
||||
|
@@ -1,22 +1,22 @@
|
||||
import * as crypto from 'crypto';
|
||||
import {PacketContext} from '@/core/packet/context/packetContext';
|
||||
import { PacketContext } from '@/core/packet/context/packetContext';
|
||||
import * as trans from '@/core/packet/transformer';
|
||||
import {PacketMsg} from '@/core/packet/message/message';
|
||||
import { PacketMsg } from '@/core/packet/message/message';
|
||||
import {
|
||||
PacketMsgFileElement,
|
||||
PacketMsgPicElement,
|
||||
PacketMsgPttElement,
|
||||
PacketMsgVideoElement
|
||||
} from '@/core/packet/message/element';
|
||||
import {ChatType, MsgSourceType, NTMsgType, RawMessage} from '@/core';
|
||||
import {MiniAppRawData, MiniAppReqParams} from '@/core/packet/entities/miniApp';
|
||||
import {AIVoiceChatType} from '@/core/packet/entities/aiChat';
|
||||
import {NapProtoDecodeStructType, NapProtoEncodeStructType, NapProtoMsg} from '@napneko/nap-proto-core';
|
||||
import {IndexNode, LongMsgResult, MsgInfo} from '@/core/packet/transformer/proto';
|
||||
import {OidbPacket} from '@/core/packet/transformer/base';
|
||||
import {ImageOcrResult} from '@/core/packet/entities/ocrResult';
|
||||
import {gunzipSync} from 'zlib';
|
||||
import {PacketMsgConverter} from '@/core/packet/message/converter';
|
||||
import { ChatType, MsgSourceType, NTMsgType, RawMessage } from '@/core';
|
||||
import { MiniAppRawData, MiniAppReqParams } from '@/core/packet/entities/miniApp';
|
||||
import { AIVoiceChatType } from '@/core/packet/entities/aiChat';
|
||||
import { NapProtoDecodeStructType, NapProtoEncodeStructType, NapProtoMsg } from '@napneko/nap-proto-core';
|
||||
import { IndexNode, LongMsgResult, MsgInfo } from '@/core/packet/transformer/proto';
|
||||
import { OidbPacket } from '@/core/packet/transformer/base';
|
||||
import { ImageOcrResult } from '@/core/packet/entities/ocrResult';
|
||||
import { gunzipSync } from 'zlib';
|
||||
import { PacketMsgConverter } from '@/core/packet/message/converter';
|
||||
|
||||
export class PacketOperationContext {
|
||||
private readonly context: PacketContext;
|
||||
@@ -59,10 +59,10 @@ export class PacketOperationContext {
|
||||
const res = trans.GetStrangerInfo.parse(resp);
|
||||
const extBigInt = BigInt(res.data.status.value);
|
||||
if (extBigInt <= 10n) {
|
||||
return {status: Number(extBigInt) * 10, ext_status: 0};
|
||||
return { status: Number(extBigInt) * 10, ext_status: 0 };
|
||||
}
|
||||
status = Number((extBigInt & 0xff00n) + ((extBigInt >> 16n) & 0xffn));
|
||||
return {status: 10, ext_status: status};
|
||||
return { status: 10, ext_status: status };
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
@@ -79,13 +79,13 @@ export class PacketOperationContext {
|
||||
const reqList = msg.flatMap(m =>
|
||||
m.msg.map(e => {
|
||||
if (e instanceof PacketMsgPicElement) {
|
||||
return this.context.highway.uploadImage({chatType, peerUid}, e);
|
||||
return this.context.highway.uploadImage({ chatType, peerUid }, e);
|
||||
} else if (e instanceof PacketMsgVideoElement) {
|
||||
return this.context.highway.uploadVideo({chatType, peerUid}, e);
|
||||
return this.context.highway.uploadVideo({ chatType, peerUid }, e);
|
||||
} else if (e instanceof PacketMsgPttElement) {
|
||||
return this.context.highway.uploadPtt({chatType, peerUid}, e);
|
||||
return this.context.highway.uploadPtt({ chatType, peerUid }, e);
|
||||
} else if (e instanceof PacketMsgFileElement) {
|
||||
return this.context.highway.uploadFile({chatType, peerUid}, e);
|
||||
return this.context.highway.uploadFile({ chatType, peerUid }, e);
|
||||
}
|
||||
return null;
|
||||
}).filter(Boolean)
|
||||
@@ -160,6 +160,12 @@ export class PacketOperationContext {
|
||||
const res = trans.DownloadGroupFile.parse(resp);
|
||||
return `https://${res.download.downloadDns}/ftn_handler/${Buffer.from(res.download.downloadUrl).toString('hex')}/?fname=`;
|
||||
}
|
||||
async GetPrivateFileUrl(self_id: string, fileUUID: string, md5: string) {
|
||||
const req = trans.DownloadPrivateFile.build(self_id, fileUUID, md5);
|
||||
const resp = await this.context.client.sendOidbPacket(req, true);
|
||||
const res = trans.DownloadPrivateFile.parse(resp);
|
||||
return `http://${res.body?.result?.server}:${res.body?.result?.port}${res.body?.result?.url?.slice(8)}&isthumb=0`;
|
||||
}
|
||||
|
||||
async GetGroupPttUrl(groupUin: number, node: NapProtoEncodeStructType<typeof IndexNode>) {
|
||||
const req = trans.DownloadGroupPtt.build(groupUin, node);
|
||||
|
@@ -144,7 +144,7 @@ export class PacketHighwayContext {
|
||||
const ukey = preRespData.upload.uKey;
|
||||
if (ukey && ukey != '') {
|
||||
this.logger.debug(`[Highway] uploadGroupImageReq get upload ukey: ${ukey}, need upload!`);
|
||||
const index = preRespData.upload.msgInfo.msgInfoBody[0].index;
|
||||
const index = preRespData.upload.msgInfo.msgInfoBody[0]!.index;
|
||||
const sha1 = Buffer.from(index.info.fileSha1, 'hex');
|
||||
const md5 = Buffer.from(index.info.fileHash, 'hex');
|
||||
const extend = new NapProtoMsg(proto.NTV2RichMediaHighwayExt).encode({
|
||||
@@ -181,7 +181,7 @@ export class PacketHighwayContext {
|
||||
const ukey = preRespData.upload.uKey;
|
||||
if (ukey && ukey != '') {
|
||||
this.logger.debug(`[Highway] uploadC2CImageReq get upload ukey: ${ukey}, need upload!`);
|
||||
const index = preRespData.upload.msgInfo.msgInfoBody[0].index;
|
||||
const index = preRespData.upload.msgInfo.msgInfoBody[0]!.index;
|
||||
const sha1 = Buffer.from(index.info.fileSha1, 'hex');
|
||||
const md5 = Buffer.from(index.info.fileHash, 'hex');
|
||||
const extend = new NapProtoMsg(proto.NTV2RichMediaHighwayExt).encode({
|
||||
@@ -219,7 +219,7 @@ export class PacketHighwayContext {
|
||||
const ukey = preRespData.upload.uKey;
|
||||
if (ukey && ukey != '') {
|
||||
this.logger.debug(`[Highway] uploadGroupVideoReq get upload video ukey: ${ukey}, need upload!`);
|
||||
const index = preRespData.upload.msgInfo.msgInfoBody[0].index;
|
||||
const index = preRespData.upload.msgInfo.msgInfoBody[0]!.index;
|
||||
const md5 = Buffer.from(index.info.fileHash, 'hex');
|
||||
const extend = new NapProtoMsg(proto.NTV2RichMediaHighwayExt).encode({
|
||||
fileUuid: index.fileUuid,
|
||||
@@ -244,16 +244,16 @@ export class PacketHighwayContext {
|
||||
this.logger.debug(`[Highway] uploadGroupVideoReq get upload invalid ukey ${ukey}, don't need upload!`);
|
||||
}
|
||||
const subFile = preRespData.upload.subFileInfos[0];
|
||||
if (subFile.uKey && subFile.uKey != '') {
|
||||
this.logger.debug(`[Highway] uploadGroupVideoReq get upload video thumb ukey: ${subFile.uKey}, need upload!`);
|
||||
const index = preRespData.upload.msgInfo.msgInfoBody[1].index;
|
||||
if (subFile!.uKey && subFile!.uKey != '') {
|
||||
this.logger.debug(`[Highway] uploadGroupVideoReq get upload video thumb ukey: ${subFile!.uKey}, need upload!`);
|
||||
const index = preRespData.upload.msgInfo.msgInfoBody[1]!.index;
|
||||
const md5 = Buffer.from(index.info.fileHash, 'hex');
|
||||
const sha1 = Buffer.from(index.info.fileSha1, 'hex');
|
||||
const extend = new NapProtoMsg(proto.NTV2RichMediaHighwayExt).encode({
|
||||
fileUuid: index.fileUuid,
|
||||
uKey: subFile.uKey,
|
||||
uKey: subFile!.uKey,
|
||||
network: {
|
||||
ipv4S: oidbIpv4s2HighwayIpv4s(subFile.ipv4S)
|
||||
ipv4S: oidbIpv4s2HighwayIpv4s(subFile!.ipv4S)
|
||||
},
|
||||
msgInfoBody: preRespData.upload.msgInfo.msgInfoBody,
|
||||
blockSize: BlockSize,
|
||||
@@ -269,7 +269,7 @@ export class PacketHighwayContext {
|
||||
extend
|
||||
);
|
||||
} else {
|
||||
this.logger.debug(`[Highway] uploadGroupVideoReq get upload invalid thumb ukey ${subFile.uKey}, don't need upload!`);
|
||||
this.logger.debug(`[Highway] uploadGroupVideoReq get upload invalid thumb ukey ${subFile!.uKey}, don't need upload!`);
|
||||
}
|
||||
video.msgInfo = preRespData.upload.msgInfo;
|
||||
}
|
||||
@@ -284,7 +284,7 @@ export class PacketHighwayContext {
|
||||
const ukey = preRespData.upload.uKey;
|
||||
if (ukey && ukey != '') {
|
||||
this.logger.debug(`[Highway] uploadC2CVideoReq get upload video ukey: ${ukey}, need upload!`);
|
||||
const index = preRespData.upload.msgInfo.msgInfoBody[0].index;
|
||||
const index = preRespData.upload.msgInfo.msgInfoBody[0]!.index;
|
||||
const md5 = Buffer.from(index.info.fileHash, 'hex');
|
||||
const extend = new NapProtoMsg(proto.NTV2RichMediaHighwayExt).encode({
|
||||
fileUuid: index.fileUuid,
|
||||
@@ -309,16 +309,16 @@ export class PacketHighwayContext {
|
||||
this.logger.debug(`[Highway] uploadC2CVideoReq get upload invalid ukey ${ukey}, don't need upload!`);
|
||||
}
|
||||
const subFile = preRespData.upload.subFileInfos[0];
|
||||
if (subFile.uKey && subFile.uKey != '') {
|
||||
this.logger.debug(`[Highway] uploadC2CVideoReq get upload video thumb ukey: ${subFile.uKey}, need upload!`);
|
||||
const index = preRespData.upload.msgInfo.msgInfoBody[1].index;
|
||||
if (subFile!.uKey && subFile!.uKey != '') {
|
||||
this.logger.debug(`[Highway] uploadC2CVideoReq get upload video thumb ukey: ${subFile!.uKey}, need upload!`);
|
||||
const index = preRespData.upload.msgInfo.msgInfoBody[1]!.index;
|
||||
const md5 = Buffer.from(index.info.fileHash, 'hex');
|
||||
const sha1 = Buffer.from(index.info.fileSha1, 'hex');
|
||||
const extend = new NapProtoMsg(proto.NTV2RichMediaHighwayExt).encode({
|
||||
fileUuid: index.fileUuid,
|
||||
uKey: subFile.uKey,
|
||||
uKey: subFile!.uKey,
|
||||
network: {
|
||||
ipv4S: oidbIpv4s2HighwayIpv4s(subFile.ipv4S)
|
||||
ipv4S: oidbIpv4s2HighwayIpv4s(subFile!.ipv4S)
|
||||
},
|
||||
msgInfoBody: preRespData.upload.msgInfo.msgInfoBody,
|
||||
blockSize: BlockSize,
|
||||
@@ -334,7 +334,7 @@ export class PacketHighwayContext {
|
||||
extend
|
||||
);
|
||||
} else {
|
||||
this.logger.debug(`[Highway] uploadC2CVideoReq get upload invalid thumb ukey ${subFile.uKey}, don't need upload!`);
|
||||
this.logger.debug(`[Highway] uploadC2CVideoReq get upload invalid thumb ukey ${subFile!.uKey}, don't need upload!`);
|
||||
}
|
||||
video.msgInfo = preRespData.upload.msgInfo;
|
||||
}
|
||||
@@ -347,7 +347,7 @@ export class PacketHighwayContext {
|
||||
const ukey = preRespData.upload.uKey;
|
||||
if (ukey && ukey != '') {
|
||||
this.logger.debug(`[Highway] uploadGroupPttReq get upload ptt ukey: ${ukey}, need upload!`);
|
||||
const index = preRespData.upload.msgInfo.msgInfoBody[0].index;
|
||||
const index = preRespData.upload.msgInfo.msgInfoBody[0]!.index;
|
||||
const md5 = Buffer.from(index.info.fileHash, 'hex');
|
||||
const sha1 = Buffer.from(index.info.fileSha1, 'hex');
|
||||
const extend = new NapProtoMsg(proto.NTV2RichMediaHighwayExt).encode({
|
||||
@@ -383,7 +383,7 @@ export class PacketHighwayContext {
|
||||
const ukey = preRespData.upload.uKey;
|
||||
if (ukey && ukey != '') {
|
||||
this.logger.debug(`[Highway] uploadC2CPttReq get upload ptt ukey: ${ukey}, need upload!`);
|
||||
const index = preRespData.upload.msgInfo.msgInfoBody[0].index;
|
||||
const index = preRespData.upload.msgInfo.msgInfoBody[0]!.index;
|
||||
const md5 = Buffer.from(index.info.fileHash, 'hex');
|
||||
const sha1 = Buffer.from(index.info.fileSha1, 'hex');
|
||||
const extend = new NapProtoMsg(proto.NTV2RichMediaHighwayExt).encode({
|
||||
|
@@ -9,10 +9,10 @@ import {
|
||||
SendFileElement,
|
||||
SendMarkdownElement,
|
||||
SendMarketFaceElement,
|
||||
SendMultiForwardMsgElement,
|
||||
SendPicElement,
|
||||
SendPttElement,
|
||||
SendReplyElement,
|
||||
SendStructLongMsgElement,
|
||||
SendTextElement,
|
||||
SendVideoElement
|
||||
} from '@/core';
|
||||
@@ -46,7 +46,7 @@ const SupportedElementTypes = [
|
||||
ElementType.PTT,
|
||||
ElementType.ARK,
|
||||
ElementType.MARKDOWN,
|
||||
ElementType.STRUCTLONGMSG
|
||||
ElementType.MULTIFORWARD
|
||||
];
|
||||
|
||||
type SendMessageTypeElementMap = {
|
||||
@@ -59,7 +59,7 @@ type SendMessageTypeElementMap = {
|
||||
[ElementType.REPLY]: SendReplyElement,
|
||||
[ElementType.ARK]: SendArkElement,
|
||||
[ElementType.MFACE]: SendMarketFaceElement,
|
||||
[ElementType.STRUCTLONGMSG]: SendStructLongMsgElement,
|
||||
[ElementType.MULTIFORWARD]: SendMultiForwardMsgElement,
|
||||
[ElementType.MARKDOWN]: SendMarkdownElement,
|
||||
};
|
||||
|
||||
@@ -118,9 +118,8 @@ export class PacketMsgConverter {
|
||||
[ElementType.MARKDOWN]: (element) => {
|
||||
return new PacketMsgMarkDownElement(element as SendMarkdownElement);
|
||||
},
|
||||
// TODO: check this logic, move it in arkElement?
|
||||
[ElementType.STRUCTLONGMSG]: (element) => {
|
||||
return new PacketMultiMsgElement(element as SendStructLongMsgElement);
|
||||
[ElementType.MULTIFORWARD]: (element) => {
|
||||
return new PacketMultiMsgElement(element as SendMultiForwardMsgElement);
|
||||
}
|
||||
};
|
||||
|
||||
|
@@ -27,7 +27,7 @@ import {
|
||||
SendPicElement,
|
||||
SendPttElement,
|
||||
SendReplyElement,
|
||||
SendStructLongMsgElement,
|
||||
SendMultiForwardMsgElement,
|
||||
SendTextElement,
|
||||
SendVideoElement
|
||||
} from '@/core';
|
||||
@@ -661,13 +661,13 @@ export class PacketMsgMarkDownElement extends IPacketMsgElement<SendMarkdownElem
|
||||
}
|
||||
}
|
||||
|
||||
export class PacketMultiMsgElement extends IPacketMsgElement<SendStructLongMsgElement> {
|
||||
export class PacketMultiMsgElement extends IPacketMsgElement<SendMultiForwardMsgElement> {
|
||||
resid: string;
|
||||
message: PacketMsg[];
|
||||
|
||||
constructor(rawElement: SendStructLongMsgElement, message?: PacketMsg[]) {
|
||||
constructor(rawElement: SendMultiForwardMsgElement, message?: PacketMsg[]) {
|
||||
super(rawElement);
|
||||
this.resid = rawElement.structLongMsgElement.resId;
|
||||
this.resid = rawElement.multiForwardMsgElement.resId;
|
||||
this.message = message ?? [];
|
||||
}
|
||||
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import { IPacketMsgElement } from '@/core/packet/message/element';
|
||||
import { SendMessageElement, SendStructLongMsgElement } from '@/core';
|
||||
import {SendMessageElement, SendMultiForwardMsgElement} from '@/core';
|
||||
|
||||
export type PacketSendMsgElement = SendMessageElement | SendStructLongMsgElement
|
||||
export type PacketSendMsgElement = SendMessageElement | SendMultiForwardMsgElement
|
||||
|
||||
export interface PacketMsg {
|
||||
seq?: number;
|
||||
|
@@ -9,15 +9,14 @@ class SetSpecialTitle extends PacketTransformer<typeof proto.OidbSvcTrpcTcpBase>
|
||||
}
|
||||
|
||||
build(groupCode: number, uid: string, tittle: string): OidbPacket {
|
||||
const oidb_0x8FC_2_body = new NapProtoMsg(proto.OidbSvcTrpcTcp0X8FC_2_Body).encode({
|
||||
targetUid: uid,
|
||||
specialTitle: tittle,
|
||||
expiredTime: -1,
|
||||
uinName: tittle
|
||||
});
|
||||
const oidb_0x8FC_2 = new NapProtoMsg(proto.OidbSvcTrpcTcp0X8FC_2).encode({
|
||||
groupUin: +groupCode,
|
||||
body: oidb_0x8FC_2_body
|
||||
body: {
|
||||
targetUid: uid,
|
||||
specialTitle: tittle,
|
||||
expiredTime: -1,
|
||||
uinName: tittle
|
||||
}
|
||||
});
|
||||
return OidbBase.build(0x8FC, 2, oidb_0x8FC_2, false, false);
|
||||
}
|
||||
|
@@ -4,12 +4,12 @@ import { ProtoField, ScalarType } from '@napneko/nap-proto-core';
|
||||
//设置群头衔 OidbSvcTrpcTcp.0x8fc_2
|
||||
export const OidbSvcTrpcTcp0X8FC_2_Body = {
|
||||
targetUid: ProtoField(1, ScalarType.STRING),
|
||||
specialTitle: ProtoField(5, ScalarType.STRING),
|
||||
expiredTime: ProtoField(6, ScalarType.SINT32),
|
||||
uinName: ProtoField(7, ScalarType.STRING),
|
||||
specialTitle: ProtoField(5, ScalarType.STRING, true),
|
||||
expiredTime: ProtoField(6, ScalarType.INT32),
|
||||
uinName: ProtoField(7, ScalarType.STRING, true),
|
||||
targetName: ProtoField(8, ScalarType.STRING),
|
||||
};
|
||||
export const OidbSvcTrpcTcp0X8FC_2 = {
|
||||
groupUin: ProtoField(1, ScalarType.UINT32),
|
||||
body: ProtoField(3, ScalarType.BYTES),
|
||||
body: ProtoField(3, () => OidbSvcTrpcTcp0X8FC_2_Body),
|
||||
};
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import { DownloadBaseEmojiByIdReq, DownloadBaseEmojiByUrlReq, GetBaseEmojiPathReq, PullSysEmojisReq } from '../types';
|
||||
import { GeneralCallResult } from './common';
|
||||
|
||||
export interface NodeIKernelBaseEmojiService {
|
||||
removeKernelBaseEmojiListener(listenerId: number): void;
|
||||
@@ -7,7 +8,26 @@ export interface NodeIKernelBaseEmojiService {
|
||||
|
||||
isBaseEmojiPathExist(args: Array<string>): unknown;
|
||||
|
||||
fetchFullSysEmojis(pullSysEmojisReq: PullSysEmojisReq): unknown;
|
||||
fetchFullSysEmojis(pullSysEmojisReq: PullSysEmojisReq): Promise<GeneralCallResult & {
|
||||
rsp: {
|
||||
otherPanelResult: {
|
||||
SysEmojiGroupList: Array<unknown>,
|
||||
downloadInfo: Array<unknown>
|
||||
},
|
||||
normalPanelResult: {
|
||||
SysEmojiGroupList: Array<unknown>,
|
||||
downloadInfo: Array<unknown>
|
||||
},
|
||||
superPanelResult: {
|
||||
SysEmojiGroupList: Array<unknown>,
|
||||
downloadInfo: Array<unknown>
|
||||
},
|
||||
redHeartPanelResult: {
|
||||
SysEmojiGroupList: Array<unknown>,
|
||||
downloadInfo: Array<unknown>
|
||||
}
|
||||
}
|
||||
}>;
|
||||
|
||||
getBaseEmojiPathByIds(getBaseEmojiPathReqs: Array<GetBaseEmojiPathReq>): unknown;
|
||||
|
||||
|
@@ -165,7 +165,7 @@ export interface NodeIKernelGroupService {
|
||||
|
||||
modifyGroupName(groupCode: string, groupName: string, isNormalMember: boolean): Promise<GeneralCallResult>;
|
||||
|
||||
modifyGroupRemark(groupCode: string, remark: string): void;
|
||||
modifyGroupRemark(groupCode: string, remark: string): Promise<GeneralCallResult>;
|
||||
|
||||
modifyGroupDetailInfo(groupCode: string, arg: unknown): void;
|
||||
|
||||
@@ -253,7 +253,7 @@ export interface NodeIKernelGroupService {
|
||||
|
||||
getGroupShutUpMemberList(groupCode: string): Promise<GeneralCallResult>;
|
||||
|
||||
setMemberShutUp(groupCode: string, memberTimes: { uid: string, timeStamp: number }[]): Promise<void>;
|
||||
setMemberShutUp(groupCode: string, memberTimes: { uid: string, timeStamp: number }[]): Promise<GeneralCallResult>;
|
||||
|
||||
getGroupRecommendContactArkJson(groupCode: string): Promise<GeneralCallResult & { arkJson: string }>;
|
||||
|
||||
|
@@ -60,7 +60,10 @@ export interface QuickLoginResult {
|
||||
}
|
||||
|
||||
export interface NodeIKernelLoginService {
|
||||
getMsfStatus: () => number;
|
||||
|
||||
setLoginMiscData(arg0: string, value: string): unknown;
|
||||
|
||||
getMachineGuid(): string;
|
||||
|
||||
get(): NodeIKernelLoginService;
|
||||
|
@@ -1,5 +1,9 @@
|
||||
import { GeneralCallResult } from './common';
|
||||
|
||||
enum ProxyType {
|
||||
CLOSE = 0,
|
||||
HTTP = 1,
|
||||
SOCKET = 2
|
||||
}
|
||||
export interface NodeIKernelMSFService {
|
||||
getServerTime(): string;
|
||||
setNetworkProxy(param: {
|
||||
@@ -7,10 +11,19 @@ export interface NodeIKernelMSFService {
|
||||
userPwd: string,
|
||||
address: string,
|
||||
port: number,
|
||||
proxyType: number,
|
||||
proxyType: ProxyType,
|
||||
domain: string,
|
||||
isSocket: boolean
|
||||
}): Promise<GeneralCallResult>;
|
||||
getNetworkProxy(): Promise<{
|
||||
userName: string,
|
||||
userPwd: string,
|
||||
address: string,
|
||||
port: number,
|
||||
proxyType: ProxyType,
|
||||
domain: string,
|
||||
isSocket: boolean
|
||||
}>;
|
||||
//http
|
||||
// userName: '',
|
||||
// userPwd: '',
|
||||
|
@@ -464,11 +464,20 @@ export interface NodeIKernelMsgService {
|
||||
|
||||
setMsgEmojiLikesForRole(...args: unknown[]): unknown;
|
||||
|
||||
clickInlineKeyboardButton(...args: unknown[]): unknown;
|
||||
clickInlineKeyboardButton(params: {
|
||||
guildId?: string,
|
||||
peerId: string,
|
||||
botAppid: string,
|
||||
msgSeq: string,
|
||||
buttonId: string,
|
||||
callback_data: string,
|
||||
dmFlag: number,
|
||||
chatType: number // 1私聊 2群
|
||||
}): Promise<GeneralCallResult & { status: number, promptText: string, promptType: number, promptIcon: number }>;
|
||||
|
||||
setCurOnScreenMsg(...args: unknown[]): unknown;
|
||||
|
||||
setCurOnScreenMsgForMsgEvent(...args: unknown[]): unknown;
|
||||
setCurOnScreenMsgForMsgEvent(peer: Peer, msgRegList: Map<string, Uint8Array>): void;
|
||||
|
||||
getMiscData(key: string): unknown;
|
||||
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import { NodeIKernelRobotListener } from '@/core/listeners';
|
||||
import { GeneralCallResult, Peer } from '..';
|
||||
|
||||
export interface NodeIKernelRobotService {
|
||||
fetchGroupRobotStoreDiscovery(arg: unknown): unknown;
|
||||
@@ -31,5 +32,17 @@ export interface NodeIKernelRobotService {
|
||||
|
||||
getRobotUinRange(data: unknown): Promise<{ response: { robotUinRanges: Array<unknown> } }>;
|
||||
|
||||
getRobotFunctions(peer: Peer, params: {
|
||||
uins: Array<string>,
|
||||
num: 0,
|
||||
client_info: { platform: 4, version: '', build_num: 9999 },
|
||||
tinyids: [],
|
||||
page: 0,
|
||||
full_fetch: false,
|
||||
scene: 4,
|
||||
filter: 1,
|
||||
bkn: ''
|
||||
}): Promise<GeneralCallResult & { response: { bot_features: Array<unknown>, next_page: number } }>;
|
||||
|
||||
isNull(): boolean;
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { ChatType } from '@/core/types';
|
||||
import { ChatType, Peer } from '@/core/types';
|
||||
import { GeneralCallResult } from './common';
|
||||
|
||||
export interface NodeIKernelSearchService {
|
||||
@@ -54,7 +54,7 @@ export interface NodeIKernelSearchService {
|
||||
|
||||
cancelSearchChatMsgs(...args: unknown[]): unknown;// needs 3 arguments
|
||||
|
||||
searchMsgWithKeywords(...args: unknown[]): unknown;// needs 2 arguments
|
||||
searchMsgWithKeywords(keyWords: string[], param: Peer & { searchFields: number, pageLimit: number }): Promise<GeneralCallResult>;
|
||||
|
||||
searchMoreMsgWithKeywords(...args: unknown[]): unknown;// needs 1 arguments
|
||||
|
||||
|
@@ -347,6 +347,8 @@ export type SendMarkdownElement = SendElementBase<ElementType.MARKDOWN> & Elemen
|
||||
|
||||
export type SendShareLocationElement = SendElementBase<ElementType.SHARELOCATION> & ElementBase<'shareLocationElement'>;
|
||||
|
||||
export type SendMultiForwardMsgElement = SendElementBase<ElementType.MULTIFORWARD> & ElementBase<'multiForwardMsgElement'>;
|
||||
|
||||
export type SendMessageElement = SendTextElement | SendPttElement |
|
||||
SendPicElement | SendReplyElement | SendFaceElement | SendMarketFaceElement | SendFileElement |
|
||||
SendVideoElement | SendArkElement | SendMarkdownElement | SendShareLocationElement;
|
||||
|
@@ -7,13 +7,11 @@ import { SelfInfo } from '@/core/types';
|
||||
import { NodeIKernelLoginListener } from '@/core/listeners';
|
||||
import { NodeIKernelLoginService } from '@/core/services';
|
||||
import { NodeIQQNTWrapperSession, WrapperNodeApi } from '@/core/wrapper';
|
||||
import { InitWebUi, WebUiConfig } from '@/webui';
|
||||
import { NapCatOneBot11Adapter } from '@/onebot';
|
||||
|
||||
//Framework ES入口文件
|
||||
export async function getWebUiUrl() {
|
||||
const WebUiConfigData = (await WebUiConfig.GetWebUIConfig());
|
||||
return 'http://127.0.0.1:' + WebUiConfigData.port + '/webui/?token=' + WebUiConfigData.token;
|
||||
return 'http://127.0.0.1:' + 6099 + '/webui/?token=napcat';
|
||||
}
|
||||
|
||||
export async function NCoreInitFramework(
|
||||
@@ -58,8 +56,6 @@ export async function NCoreInitFramework(
|
||||
const loaderObject = new NapCatFramework(wrapper, session, logger, loginService, selfInfo, basicInfoWrapper, pathWrapper);
|
||||
await loaderObject.core.initCore();
|
||||
|
||||
//启动WebUi
|
||||
InitWebUi(logger, pathWrapper).then().catch(e => logger.logError(e));
|
||||
//初始化LLNC的Onebot实现
|
||||
await new NapCatOneBot11Adapter(loaderObject.core, loaderObject.context, pathWrapper).InitOneBot();
|
||||
}
|
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
10
src/onebot/action/extends/BotExit.ts
Normal file
10
src/onebot/action/extends/BotExit.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { ActionName } from '@/onebot/action/router';
|
||||
import { OneBotAction } from '../OneBotAction';
|
||||
|
||||
export class BotExit extends OneBotAction<void, void> {
|
||||
override actionName = ActionName.Exit;
|
||||
|
||||
async _handle() {
|
||||
process.exit(0);
|
||||
}
|
||||
}
|
30
src/onebot/action/extends/ClickInlineKeyboardButton.ts
Normal file
30
src/onebot/action/extends/ClickInlineKeyboardButton.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { ActionName } from '@/onebot/action/router';
|
||||
import { OneBotAction } from '../OneBotAction';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
const SchemaData = Type.Object({
|
||||
group_id: Type.Union([Type.Number(), Type.String()]),
|
||||
bot_appid: Type.String(),
|
||||
button_id: Type.String({ default: '' }),
|
||||
callback_data: Type.String({ default: '' }),
|
||||
msg_seq: Type.String({ default: '10086' }),
|
||||
});
|
||||
|
||||
type Payload = Static<typeof SchemaData>;
|
||||
|
||||
export class ClickInlineKeyboardButton extends OneBotAction<Payload, unknown> {
|
||||
override actionName = ActionName.ClickInlineKeyboardButton;
|
||||
override payloadSchema = SchemaData;
|
||||
|
||||
async _handle(payload: Payload) {
|
||||
return await this.core.apis.MsgApi.clickInlineKeyboardButton({
|
||||
buttonId: payload.button_id,
|
||||
peerId: payload.group_id.toString(),
|
||||
botAppid: payload.bot_appid,
|
||||
msgSeq: payload.msg_seq,
|
||||
callback_data: payload.callback_data,
|
||||
dmFlag: 0,
|
||||
chatType: 2
|
||||
})
|
||||
}
|
||||
}
|
56
src/onebot/action/extends/GetUnidirectionalFriendList.ts
Normal file
56
src/onebot/action/extends/GetUnidirectionalFriendList.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import { PacketHexStr } from '@/core/packet/transformer/base';
|
||||
import { OneBotAction } from '@/onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/onebot/action/router';
|
||||
import { ProtoBuf, ProtoBufBase, PBUint32, PBString } from 'napcat.protobuf';
|
||||
|
||||
interface Friend {
|
||||
uin: number;
|
||||
uid: string;
|
||||
nick_name: string;
|
||||
age: number;
|
||||
source: string;
|
||||
}
|
||||
|
||||
interface Block {
|
||||
str_uid: string;
|
||||
bytes_source: string;
|
||||
uint32_sex: number;
|
||||
uint32_age: number;
|
||||
bytes_nick: string;
|
||||
uint64_uin: number;
|
||||
}
|
||||
|
||||
export class GetUnidirectionalFriendList extends OneBotAction<void, Friend[]> {
|
||||
override actionName = ActionName.GetUnidirectionalFriendList;
|
||||
|
||||
async pack_data(data: string): Promise<Uint8Array> {
|
||||
return ProtoBuf(class extends ProtoBufBase {
|
||||
type = PBUint32(2, false, 0);
|
||||
data = PBString(3, false, data);
|
||||
}).encode();
|
||||
}
|
||||
|
||||
async _handle(): Promise<Friend[]> {
|
||||
const self_id = this.core.selfInfo.uin;
|
||||
const req_json = {
|
||||
uint64_uin: self_id,
|
||||
uint64_top: 0,
|
||||
uint32_req_num: 99,
|
||||
bytes_cookies: ""
|
||||
};
|
||||
const packed_data = await this.pack_data(JSON.stringify(req_json));
|
||||
const data = Buffer.from(packed_data).toString('hex');
|
||||
const rsq = { cmd: 'MQUpdateSvc_com_qq_ti.web.OidbSvc.0xe17_0', data: data as PacketHexStr };
|
||||
const rsp_data = await this.core.apis.PacketApi.pkt.operation.sendPacket(rsq, true);
|
||||
const block_json = ProtoBuf(class extends ProtoBufBase { data = PBString(4); }).decode(rsp_data);
|
||||
const block_list: Block[] = JSON.parse(block_json.data).rpt_block_list;
|
||||
|
||||
return block_list.map((block) => ({
|
||||
uin: block.uint64_uin,
|
||||
uid: block.str_uid,
|
||||
nick_name: Buffer.from(block.bytes_nick, 'base64').toString(),
|
||||
age: block.uint32_age,
|
||||
source: Buffer.from(block.bytes_source, 'base64').toString()
|
||||
}));
|
||||
}
|
||||
}
|
22
src/onebot/action/extends/SetGroupRemark.ts
Normal file
22
src/onebot/action/extends/SetGroupRemark.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { OneBotAction } from '@/onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/onebot/action/router';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
const SchemaData = Type.Object({
|
||||
group_id: Type.String(),
|
||||
remark: Type.String(),
|
||||
});
|
||||
|
||||
type Payload = Static<typeof SchemaData>;
|
||||
|
||||
export default class SetGroupRemark extends OneBotAction<Payload, null> {
|
||||
override actionName = ActionName.SetGroupRemark;
|
||||
override payloadSchema = SchemaData;
|
||||
async _handle(payload: Payload): Promise<null> {
|
||||
let ret = await this.core.apis.GroupApi.setGroupRemark(payload.group_id, payload.remark);
|
||||
if (ret.result != 0) {
|
||||
throw new Error(`设置群备注失败, ${ret.result}:${ret.errMsg}`);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
@@ -5,7 +5,7 @@ import { Static, Type } from '@sinclair/typebox';
|
||||
const SchemaData = Type.Object({
|
||||
group_id: Type.Union([Type.Number(), Type.String()]),
|
||||
user_id: Type.Union([Type.Number(), Type.String()]),
|
||||
special_title: Type.String(),
|
||||
special_title: Type.String({ default: '' }),
|
||||
});
|
||||
|
||||
type Payload = Static<typeof SchemaData>;
|
||||
@@ -16,7 +16,7 @@ export class SetSpecialTittle extends GetPacketStatusDepends<Payload, void> {
|
||||
|
||||
async _handle(payload: Payload) {
|
||||
const uid = await this.core.apis.UserApi.getUidByUinV2(payload.user_id.toString());
|
||||
if(!uid) throw new Error('User not found');
|
||||
if (!uid) throw new Error('User not found');
|
||||
await this.core.apis.PacketApi.pkt.operation.SetGroupSpecialTitle(+payload.group_id, uid, payload.special_title);
|
||||
}
|
||||
}
|
||||
|
36
src/onebot/action/file/GetPrivateFileUrl.ts
Normal file
36
src/onebot/action/file/GetPrivateFileUrl.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { ActionName } from '@/onebot/action/router';
|
||||
import { FileNapCatOneBotUUID } from '@/common/file-uuid';
|
||||
import { GetPacketStatusDepends } from '@/onebot/action/packet/GetPacketStatus';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
const SchemaData = Type.Object({
|
||||
file_id: Type.String(),
|
||||
});
|
||||
|
||||
type Payload = Static<typeof SchemaData>;
|
||||
|
||||
interface GetPrivateFileUrlResponse {
|
||||
url?: string;
|
||||
}
|
||||
|
||||
export class GetPrivateFileUrl extends GetPacketStatusDepends<Payload, GetPrivateFileUrlResponse> {
|
||||
override actionName = ActionName.NapCat_GetPrivateFileUrl;
|
||||
override payloadSchema = SchemaData;
|
||||
|
||||
async _handle(payload: Payload) {
|
||||
const contextMsgFile = FileNapCatOneBotUUID.decode(payload.file_id);
|
||||
|
||||
if (contextMsgFile?.fileUUID && contextMsgFile.msgId) {
|
||||
let msg = await this.core.apis.MsgApi.getMsgsByMsgId(contextMsgFile.peer, [contextMsgFile.msgId]);
|
||||
let self_id = this.core.selfInfo.uid;
|
||||
let file_hash = msg.msgList[0]?.elements.map(ele => ele.fileElement?.file10MMd5)[0];
|
||||
if (file_hash) {
|
||||
return {
|
||||
url: await this.core.apis.PacketApi.pkt.operation.GetPrivateFileUrl(self_id, contextMsgFile.fileUUID, file_hash)
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
throw new Error('real fileUUID not found!');
|
||||
}
|
||||
}
|
@@ -20,6 +20,7 @@ class GetGroupInfo extends OneBotAction<Payload, OB11Group> {
|
||||
const data = await this.core.apis.GroupApi.fetchGroupDetail(payload.group_id.toString());
|
||||
return {
|
||||
...data,
|
||||
group_remark: '',
|
||||
group_id: +payload.group_id,
|
||||
group_name: data.groupName,
|
||||
member_count: data.memberNum,
|
||||
|
@@ -20,11 +20,12 @@ export default class SetGroupAddRequest extends OneBotAction<Payload, null> {
|
||||
const approve = payload.approve?.toString() !== 'false';
|
||||
const reason = payload.reason ?? ' ';
|
||||
const invite_notify = this.obContext.apis.MsgApi.notifyGroupInvite.get(flag);
|
||||
const notify = invite_notify ?? await this.findNotify(flag);
|
||||
const { doubt, notify } = invite_notify ? { doubt: false, notify: invite_notify } : await this.findNotify(flag);
|
||||
if (!notify) {
|
||||
throw new Error('No such request');
|
||||
}
|
||||
await this.core.apis.GroupApi.handleGroupRequest(
|
||||
doubt,
|
||||
notify,
|
||||
approve ? NTGroupRequestOperateTypes.KAGREE : NTGroupRequestOperateTypes.KREFUSE,
|
||||
reason,
|
||||
@@ -36,7 +37,8 @@ export default class SetGroupAddRequest extends OneBotAction<Payload, null> {
|
||||
let notify = (await this.core.apis.GroupApi.getSingleScreenNotifies(false, 100)).find(e => e.seq == flag);
|
||||
if (!notify) {
|
||||
notify = (await this.core.apis.GroupApi.getSingleScreenNotifies(true, 100)).find(e => e.seq == flag);
|
||||
return { doubt: true, notify };
|
||||
}
|
||||
return notify;
|
||||
return { doubt: false, notify };
|
||||
}
|
||||
}
|
@@ -13,12 +13,15 @@ type Payload = Static<typeof SchemaData>;
|
||||
export default class SetGroupBan extends OneBotAction<Payload, null> {
|
||||
override actionName = ActionName.SetGroupBan;
|
||||
override payloadSchema = SchemaData;
|
||||
|
||||
async _handle(payload: Payload): Promise<null> {
|
||||
const uid = await this.core.apis.UserApi.getUidByUinV2(payload.user_id.toString());
|
||||
if (!uid) throw new Error('uid error');
|
||||
await this.core.apis.GroupApi.banMember(payload.group_id.toString(),
|
||||
let member_role = (await this.core.apis.GroupApi.getGroupMemberEx(payload.group_id.toString(), uid, true))?.role;
|
||||
if (member_role === 4) throw new Error('cannot ban owner');
|
||||
// 例如无管理员权限时 result为 120101005 errMsg为 'ERR_NOT_GROUP_ADMIN'
|
||||
let ret = await this.core.apis.GroupApi.banMember(payload.group_id.toString(),
|
||||
[{ uid: uid, timeStamp: +payload.duration }]);
|
||||
if (ret.result !== 0) throw new Error(ret.errMsg);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@@ -104,10 +104,16 @@ import { GetClientkey } from './extends/GetClientkey';
|
||||
import { SendPacket } from './extends/SendPacket';
|
||||
import { SendPoke } from '@/onebot/action/packet/SendPoke';
|
||||
import { SetDiyOnlineStatus } from './extends/SetDiyOnlineStatus';
|
||||
import { BotExit } from './extends/BotExit';
|
||||
import { ClickInlineKeyboardButton } from './extends/ClickInlineKeyboardButton';
|
||||
import { GetPrivateFileUrl } from './file/GetPrivateFileUrl';
|
||||
import { GetUnidirectionalFriendList } from './extends/GetUnidirectionalFriendList';
|
||||
import SetGroupRemark from './extends/SetGroupRemark';
|
||||
|
||||
export function createActionMap(obContext: NapCatOneBot11Adapter, core: NapCatCore) {
|
||||
|
||||
const actionHandlers = [
|
||||
new SetGroupRemark(obContext, core),
|
||||
new GetGroupInfoEx(obContext, core),
|
||||
new FetchEmojiLike(obContext, core),
|
||||
new GetFile(obContext, core),
|
||||
@@ -221,6 +227,10 @@ export function createActionMap(obContext: NapCatOneBot11Adapter, core: NapCatCo
|
||||
new SendPacket(obContext, core),
|
||||
new SendPoke(obContext, core),
|
||||
new GetGroupSystemMsg(obContext, core),
|
||||
new BotExit(obContext, core),
|
||||
new ClickInlineKeyboardButton(obContext, core),
|
||||
new GetPrivateFileUrl(obContext, core),
|
||||
new GetUnidirectionalFriendList(obContext, core),
|
||||
];
|
||||
|
||||
type HandlerUnion = typeof actionHandlers[number];
|
||||
|
@@ -10,6 +10,10 @@ export interface InvalidCheckResult {
|
||||
}
|
||||
|
||||
export const ActionName = {
|
||||
SetGroupRemark: 'set_group_remark',
|
||||
NapCat_GetPrivateFileUrl: 'get_private_file_url',
|
||||
ClickInlineKeyboardButton: 'click_inline_keyboard_button',
|
||||
GetUnidirectionalFriendList: 'get_unidirectional_friend_list',
|
||||
// onebot 11
|
||||
SendPrivateMsg: 'send_private_msg',
|
||||
SendGroupMsg: 'send_group_msg',
|
||||
@@ -49,7 +53,7 @@ export const ActionName = {
|
||||
GetVersionInfo: 'get_version_info',
|
||||
// Reboot : 'set_restart',
|
||||
// CleanCache : 'clean_cache',
|
||||
|
||||
Exit: 'bot_exit',
|
||||
// go-cqhttp
|
||||
SetQQProfile: 'set_qq_profile',
|
||||
// QidianGetAccountInfo : 'qidian_get_account_info',
|
||||
@@ -141,6 +145,6 @@ export const ActionName = {
|
||||
SendGroupAiRecord: 'send_group_ai_record',
|
||||
|
||||
GetClientkey: 'get_clientkey',
|
||||
|
||||
|
||||
SendPoke: 'send_poke',
|
||||
} as const;
|
||||
|
@@ -49,6 +49,7 @@ export class OneBotGroupApi {
|
||||
duration = -1;
|
||||
}
|
||||
}
|
||||
await this.core.apis.GroupApi.refreshGroupMemberCachePartial(GroupCode, memberUid);
|
||||
const adminUin = (await this.core.apis.GroupApi.getGroupMember(GroupCode, adminUid))?.uin;
|
||||
if (memberUin && adminUin) {
|
||||
return new OB11GroupBanEvent(
|
||||
@@ -113,12 +114,16 @@ 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);
|
||||
await this.core.apis.GroupApi.refreshGroupMemberCachePartial(msg.peerUid, msg.senderUid);
|
||||
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;
|
||||
}
|
||||
if (member && member.nick !== msg.sendNickName) {
|
||||
await this.core.apis.GroupApi.refreshGroupMemberCachePartial(msg.peerUid, msg.senderUid);
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import {FileNapCatOneBotUUID} from '@/common/file-uuid';
|
||||
import {MessageUnique} from '@/common/message-unique';
|
||||
import { FileNapCatOneBotUUID } from '@/common/file-uuid';
|
||||
import { MessageUnique } from '@/common/message-unique';
|
||||
import {
|
||||
ChatType,
|
||||
CustomMusicSignPostData,
|
||||
@@ -29,22 +29,22 @@ import {
|
||||
OB11MessageImage,
|
||||
OB11MessageVideo,
|
||||
} from '@/onebot';
|
||||
import {OB11Construct} from '@/onebot/helper/data';
|
||||
import {EventType} from '@/onebot/event/OneBotEvent';
|
||||
import {encodeCQCode} from '@/onebot/helper/cqcode';
|
||||
import {uriToLocalFile} from '@/common/file';
|
||||
import {RequestUtil} from '@/common/request';
|
||||
import fsPromise, {constants} from 'node:fs/promises';
|
||||
import {OB11FriendAddNoticeEvent} from '@/onebot/event/notice/OB11FriendAddNoticeEvent';
|
||||
import {ForwardMsgBuilder} from '@/common/forward-msg-builder';
|
||||
import {NapProtoMsg} from '@napneko/nap-proto-core';
|
||||
import {OB11GroupIncreaseEvent} from '../event/notice/OB11GroupIncreaseEvent';
|
||||
import {GroupDecreaseSubType, OB11GroupDecreaseEvent} from '../event/notice/OB11GroupDecreaseEvent';
|
||||
import {GroupAdmin} from '@/core/packet/transformer/proto/message/groupAdmin';
|
||||
import {OB11GroupAdminNoticeEvent} from '../event/notice/OB11GroupAdminNoticeEvent';
|
||||
import {GroupChange, GroupChangeInfo, GroupInvite, PushMsgBody} from '@/core/packet/transformer/proto';
|
||||
import {OB11GroupRequestEvent} from '../event/request/OB11GroupRequest';
|
||||
import {LRUCache} from '@/common/lru-cache';
|
||||
import { OB11Construct } from '@/onebot/helper/data';
|
||||
import { EventType } from '@/onebot/event/OneBotEvent';
|
||||
import { encodeCQCode } from '@/onebot/helper/cqcode';
|
||||
import { uriToLocalFile } from '@/common/file';
|
||||
import { RequestUtil } from '@/common/request';
|
||||
import fsPromise, { constants } from 'node:fs/promises';
|
||||
import { OB11FriendAddNoticeEvent } from '@/onebot/event/notice/OB11FriendAddNoticeEvent';
|
||||
import { ForwardMsgBuilder } from '@/common/forward-msg-builder';
|
||||
import { NapProtoMsg } from '@napneko/nap-proto-core';
|
||||
import { OB11GroupIncreaseEvent } from '../event/notice/OB11GroupIncreaseEvent';
|
||||
import { GroupDecreaseSubType, OB11GroupDecreaseEvent } from '../event/notice/OB11GroupDecreaseEvent';
|
||||
import { GroupAdmin } from '@/core/packet/transformer/proto/message/groupAdmin';
|
||||
import { OB11GroupAdminNoticeEvent } from '../event/notice/OB11GroupAdminNoticeEvent';
|
||||
import { GroupChange, GroupChangeInfo, GroupInvite, PushMsgBody } from '@/core/packet/transformer/proto';
|
||||
import { OB11GroupRequestEvent } from '../event/request/OB11GroupRequest';
|
||||
import { LRUCache } from '@/common/lru-cache';
|
||||
|
||||
type RawToOb11Converters = {
|
||||
[Key in keyof MessageElement as Key extends `${string}Element` ? Key : never]: (
|
||||
@@ -93,12 +93,12 @@ export class OneBotMsgApi {
|
||||
}
|
||||
return {
|
||||
type: OB11MessageDataType.text,
|
||||
data: {text},
|
||||
data: { text },
|
||||
};
|
||||
} else {
|
||||
let qq: string = 'all';
|
||||
if (element.atType !== NTMsgAtType.ATTYPEALL) {
|
||||
const {atNtUid, atUid} = element;
|
||||
const { atNtUid, atUid } = element;
|
||||
qq = !atUid || atUid === '0' ? await this.core.apis.UserApi.getUinByUidV2(atNtUid) : atUid;
|
||||
}
|
||||
return {
|
||||
@@ -132,7 +132,6 @@ export class OneBotMsgApi {
|
||||
file: element.fileName,
|
||||
sub_type: element.picSubType,
|
||||
url: await this.core.apis.FileApi.getImageUrl(element),
|
||||
path: element.filePath,
|
||||
file_size: element.fileSize,
|
||||
},
|
||||
};
|
||||
@@ -148,13 +147,13 @@ export class OneBotMsgApi {
|
||||
peerUid: msg.peerUid,
|
||||
guildId: '',
|
||||
};
|
||||
const file = FileNapCatOneBotUUID.encode(peer, msg.msgId, elementWrapper.elementId, element.fileUuid, element.fileName);
|
||||
FileNapCatOneBotUUID.encode(peer, msg.msgId, elementWrapper.elementId, element.fileUuid, element.fileUuid);
|
||||
FileNapCatOneBotUUID.encode(peer, msg.msgId, elementWrapper.elementId, element.fileUuid, element.fileName);
|
||||
return {
|
||||
type: OB11MessageDataType.file,
|
||||
data: {
|
||||
file: file,
|
||||
path: element.filePath,
|
||||
file_id: file,
|
||||
file: element.fileName,
|
||||
file_id: element.fileUuid,
|
||||
file_size: element.fileSize,
|
||||
},
|
||||
};
|
||||
@@ -206,7 +205,7 @@ export class OneBotMsgApi {
|
||||
peerUid: msg.peerUid,
|
||||
guildId: '',
|
||||
};
|
||||
const {emojiId} = _;
|
||||
const { emojiId } = _;
|
||||
const dir = emojiId.substring(0, 2);
|
||||
const url = `https://gxh.vip.qq.com/club/item/parcel/item/${dir}/${emojiId}/raw300.gif`;
|
||||
const filename = `${dir}-${emojiId}.gif`;
|
||||
@@ -216,7 +215,6 @@ export class OneBotMsgApi {
|
||||
data: {
|
||||
summary: _.faceName, // 商城表情名称
|
||||
file: filename,
|
||||
path: url,
|
||||
url: url,
|
||||
key: _.key,
|
||||
emoji_id: _.emojiId,
|
||||
@@ -339,7 +337,6 @@ export class OneBotMsgApi {
|
||||
type: OB11MessageDataType.video,
|
||||
data: {
|
||||
file: fileCode,
|
||||
path: videoDownUrl,
|
||||
url: videoDownUrl,
|
||||
file_size: element.fileSize,
|
||||
},
|
||||
@@ -357,8 +354,8 @@ export class OneBotMsgApi {
|
||||
type: OB11MessageDataType.voice,
|
||||
data: {
|
||||
file: fileCode,
|
||||
path: element.filePath,
|
||||
file_size: element.fileSize,
|
||||
path: element.filePath,
|
||||
},
|
||||
};
|
||||
},
|
||||
@@ -381,7 +378,7 @@ export class OneBotMsgApi {
|
||||
}
|
||||
const forward: OB11MessageForward = {
|
||||
type: OB11MessageDataType.forward,
|
||||
data: {id: msg.msgId}
|
||||
data: { id: msg.msgId }
|
||||
};
|
||||
if (!context.parseMultMsg) return forward;
|
||||
forward.data.content = await this.parseMultiMessageContent(
|
||||
@@ -412,7 +409,7 @@ export class OneBotMsgApi {
|
||||
};
|
||||
|
||||
ob11ToRawConverters: Ob11ToRawConverters = {
|
||||
[OB11MessageDataType.text]: async ({data: {text}}) => ({
|
||||
[OB11MessageDataType.text]: async ({ data: { text } }) => ({
|
||||
elementType: ElementType.TEXT,
|
||||
elementId: '',
|
||||
textElement: {
|
||||
@@ -424,7 +421,7 @@ export class OneBotMsgApi {
|
||||
},
|
||||
}),
|
||||
|
||||
[OB11MessageDataType.at]: async ({data: {qq: atQQ}}, context) => {
|
||||
[OB11MessageDataType.at]: async ({ data: { qq: atQQ } }, context) => {
|
||||
function at(atUid: string, atNtUid: string, atType: NTMsgAtType, atName: string): SendTextElement {
|
||||
return {
|
||||
elementType: ElementType.TEXT,
|
||||
@@ -451,7 +448,7 @@ export class OneBotMsgApi {
|
||||
return at(atQQ, uid, NTMsgAtType.ATTYPEONE, info.nick || '');
|
||||
},
|
||||
|
||||
[OB11MessageDataType.reply]: async ({data: {id}}) => {
|
||||
[OB11MessageDataType.reply]: async ({ data: { id } }) => {
|
||||
const replyMsgM = MessageUnique.getMsgIdAndPeerByShortId(parseInt(id));
|
||||
if (!replyMsgM) {
|
||||
this.core.context.logger.logWarn('回复消息不存在', id);
|
||||
@@ -473,7 +470,7 @@ export class OneBotMsgApi {
|
||||
undefined;
|
||||
},
|
||||
|
||||
[OB11MessageDataType.face]: async ({data: {id, resultId, chainCount}}) => {
|
||||
[OB11MessageDataType.face]: async ({ data: { id, resultId, chainCount } }) => {
|
||||
const parsedFaceId = +id;
|
||||
// 从face_config.json中获取表情名称
|
||||
const sysFaces = faceConfig.sysface;
|
||||
@@ -537,12 +534,12 @@ export class OneBotMsgApi {
|
||||
},
|
||||
|
||||
[OB11MessageDataType.file]: async (sendMsg, context) => {
|
||||
const {path, fileName} = await this.handleOb11FileLikeMessage(sendMsg, context);
|
||||
const { path, fileName } = await this.handleOb11FileLikeMessage(sendMsg, context);
|
||||
return await this.core.apis.FileApi.createValidSendFileElement(context, path, fileName);
|
||||
},
|
||||
|
||||
[OB11MessageDataType.video]: async (sendMsg, context) => {
|
||||
const {path, fileName} = await this.handleOb11FileLikeMessage(sendMsg, context);
|
||||
const { path, fileName } = await this.handleOb11FileLikeMessage(sendMsg, context);
|
||||
|
||||
let thumb = sendMsg.data.thumb;
|
||||
if (thumb) {
|
||||
@@ -560,7 +557,7 @@ export class OneBotMsgApi {
|
||||
this.core.apis.FileApi.createValidSendPttElement(
|
||||
(await this.handleOb11FileLikeMessage(sendMsg, context)).path),
|
||||
|
||||
[OB11MessageDataType.json]: async ({data: {data}}) => ({
|
||||
[OB11MessageDataType.json]: async ({ data: { data } }) => ({
|
||||
elementType: ElementType.ARK,
|
||||
elementId: '',
|
||||
arkElement: {
|
||||
@@ -603,13 +600,13 @@ export class OneBotMsgApi {
|
||||
}),
|
||||
|
||||
// Need signing
|
||||
[OB11MessageDataType.markdown]: async ({data: {content}}) => ({
|
||||
[OB11MessageDataType.markdown]: async ({ data: { content } }) => ({
|
||||
elementType: ElementType.MARKDOWN,
|
||||
elementId: '',
|
||||
markdownElement: {content},
|
||||
markdownElement: { content },
|
||||
}),
|
||||
|
||||
[OB11MessageDataType.music]: async ({data}, context) => {
|
||||
[OB11MessageDataType.music]: async ({ data }, context) => {
|
||||
// 保留, 直到...找到更好的解决方案
|
||||
if (data.id !== undefined) {
|
||||
if (!['qq', '163', 'kugou', 'kuwo', 'migu'].includes(data.type)) {
|
||||
@@ -633,8 +630,8 @@ export class OneBotMsgApi {
|
||||
|
||||
let postData: IdMusicSignPostData | CustomMusicSignPostData;
|
||||
if (data.id === undefined && data.content) {
|
||||
const {content, ...others} = data;
|
||||
postData = {singer: content, ...others};
|
||||
const { content, ...others } = data;
|
||||
postData = { singer: content, ...others };
|
||||
} else {
|
||||
postData = data;
|
||||
}
|
||||
@@ -646,7 +643,7 @@ export class OneBotMsgApi {
|
||||
try {
|
||||
const musicJson = await RequestUtil.HttpGetJson<string>(signUrl, 'POST', postData);
|
||||
return this.ob11ToRawConverters.json({
|
||||
data: {data: musicJson},
|
||||
data: { data: musicJson },
|
||||
type: OB11MessageDataType.json
|
||||
}, context);
|
||||
} catch (e) {
|
||||
@@ -657,10 +654,23 @@ export class OneBotMsgApi {
|
||||
|
||||
[OB11MessageDataType.node]: async () => undefined,
|
||||
|
||||
[OB11MessageDataType.forward]: async ({data}, context) => {
|
||||
[OB11MessageDataType.forward]: async ({ data }, context) => {
|
||||
// let id = data.id.toString();
|
||||
// let peer: Peer | undefined = context.peer;
|
||||
// if (isNumeric(id)) {
|
||||
// let msgid = '';
|
||||
// if (BigInt(data.id) > 2147483647n) {
|
||||
// peer = MessageUnique.getPeerByMsgId(id)?.Peer;
|
||||
// msgid = id;
|
||||
// } else {
|
||||
// let data = MessageUnique.getMsgIdAndPeerByShortId(parseInt(id));
|
||||
// msgid = data?.MsgId ?? '';
|
||||
// peer = data?.Peer;
|
||||
// }
|
||||
// }
|
||||
const jsonData = ForwardMsgBuilder.fromResId(data.id);
|
||||
return this.ob11ToRawConverters.json({
|
||||
data: {data: JSON.stringify(jsonData)},
|
||||
data: { data: JSON.stringify(jsonData) },
|
||||
type: OB11MessageDataType.json
|
||||
}, context);
|
||||
},
|
||||
@@ -680,17 +690,17 @@ export class OneBotMsgApi {
|
||||
|
||||
[OB11MessageDataType.miniapp]: async () => undefined,
|
||||
|
||||
[OB11MessageDataType.contact]: async ({data: {type = 'qq', id}}, context) => {
|
||||
[OB11MessageDataType.contact]: async ({ data: { type = 'qq', id } }, context) => {
|
||||
if (type === 'qq') {
|
||||
const arkJson = await this.core.apis.UserApi.getBuddyRecommendContactArkJson(id.toString(), '');
|
||||
return this.ob11ToRawConverters.json({
|
||||
data: {data: arkJson.arkMsg},
|
||||
data: { data: arkJson.arkMsg },
|
||||
type: OB11MessageDataType.json
|
||||
}, context);
|
||||
} else if (type === 'group') {
|
||||
const arkJson = await this.core.apis.GroupApi.getGroupRecommendContactArkJson(id.toString());
|
||||
return this.ob11ToRawConverters.json({
|
||||
data: {data: arkJson.arkJson},
|
||||
data: { data: arkJson.arkJson },
|
||||
type: OB11MessageDataType.json
|
||||
}, context);
|
||||
}
|
||||
@@ -799,6 +809,7 @@ export class OneBotMsgApi {
|
||||
message_id: msg.id!,
|
||||
message_seq: msg.id!,
|
||||
real_id: msg.id!,
|
||||
real_seq: msg.msgSeq,
|
||||
message_type: msg.chatType == ChatType.KCHATTYPEGROUP ? 'group' : 'private',
|
||||
sender: {
|
||||
user_id: +(msg.senderUin ?? 0),
|
||||
@@ -867,7 +878,7 @@ export class OneBotMsgApi {
|
||||
element[key],
|
||||
msg,
|
||||
element,
|
||||
{parseMultMsg}
|
||||
{ parseMultMsg }
|
||||
);
|
||||
if (key === 'faceElement' && !parsedElement) {
|
||||
return null;
|
||||
@@ -920,13 +931,13 @@ export class OneBotMsgApi {
|
||||
) => Promise<SendMessageElement | undefined>;
|
||||
const callResult = converter(
|
||||
sendMsg,
|
||||
{peer, deleteAfterSentFiles},
|
||||
{ peer, deleteAfterSentFiles },
|
||||
)?.catch(undefined);
|
||||
callResultList.push(callResult);
|
||||
}
|
||||
const ret = await Promise.all(callResultList);
|
||||
const sendElements: SendMessageElement[] = ret.filter(ele => !!ele);
|
||||
return {sendElements, deleteAfterSentFiles};
|
||||
return { sendElements, deleteAfterSentFiles };
|
||||
}
|
||||
|
||||
async sendMsgWithOb11UniqueId(peer: Peer, sendElements: SendMessageElement[], deleteAfterSentFiles: string[]) {
|
||||
@@ -937,16 +948,16 @@ export class OneBotMsgApi {
|
||||
const calculateTotalSize = async (elements: SendMessageElement[]): Promise<number> => {
|
||||
const sizePromises = elements.map(async element => {
|
||||
switch (element.elementType) {
|
||||
case ElementType.PTT:
|
||||
return (await fsPromise.stat(element.pttElement.filePath)).size;
|
||||
case ElementType.FILE:
|
||||
return (await fsPromise.stat(element.fileElement.filePath)).size;
|
||||
case ElementType.VIDEO:
|
||||
return (await fsPromise.stat(element.videoElement.filePath)).size;
|
||||
case ElementType.PIC:
|
||||
return (await fsPromise.stat(element.picElement.sourcePath)).size;
|
||||
default:
|
||||
return 0;
|
||||
case ElementType.PTT:
|
||||
return (await fsPromise.stat(element.pttElement.filePath)).size;
|
||||
case ElementType.FILE:
|
||||
return (await fsPromise.stat(element.fileElement.filePath)).size;
|
||||
case ElementType.VIDEO:
|
||||
return (await fsPromise.stat(element.videoElement.filePath)).size;
|
||||
case ElementType.PIC:
|
||||
return (await fsPromise.stat(element.picElement.sourcePath)).size;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
const sizes = await Promise.all(sizePromises);
|
||||
@@ -988,39 +999,32 @@ export class OneBotMsgApi {
|
||||
}
|
||||
|
||||
private async handleOb11FileLikeMessage(
|
||||
{data: inputdata}: OB11MessageFileBase,
|
||||
{deleteAfterSentFiles}: SendMessageContext
|
||||
{ data: inputdata }: OB11MessageFileBase,
|
||||
{ deleteAfterSentFiles }: SendMessageContext
|
||||
) {
|
||||
let realUri = [inputdata.url, inputdata.file, inputdata.path].find(uri => uri && uri.trim()) ?? '';
|
||||
if (!realUri) {
|
||||
this.core.context.logger.logError('文件消息缺少参数', inputdata);
|
||||
throw new Error('文件消息缺少参数');
|
||||
}
|
||||
|
||||
const downloadFile = async (uri: string) => {
|
||||
const {path, fileName, errMsg, success} = await uriToLocalFile(this.core.NapCatTempPath, uri);
|
||||
if (!success) {
|
||||
this.core.context.logger.logError('文件下载失败', errMsg);
|
||||
throw new Error('文件下载失败: ' + errMsg);
|
||||
}
|
||||
return {path, fileName};
|
||||
};
|
||||
realUri = await this.handleObfuckName(realUri) ?? realUri;
|
||||
try {
|
||||
const {path, fileName} = await downloadFile(realUri);
|
||||
const { path, fileName, errMsg, success } = await uriToLocalFile(this.core.NapCatTempPath, realUri);
|
||||
if (!success) {
|
||||
this.core.context.logger.logError('文件处理失败', errMsg);
|
||||
throw new Error('文件处理失败: ' + errMsg);
|
||||
}
|
||||
deleteAfterSentFiles.push(path);
|
||||
return {path, fileName: inputdata.name ?? fileName};
|
||||
} catch {
|
||||
realUri = await this.handleObfuckName(realUri);
|
||||
const {path, fileName} = await downloadFile(realUri);
|
||||
deleteAfterSentFiles.push(path);
|
||||
return {path, fileName: inputdata.name ?? fileName};
|
||||
return { path, fileName: inputdata.name ?? fileName };
|
||||
} catch (e: unknown) {
|
||||
throw new Error((e as Error).message);
|
||||
}
|
||||
}
|
||||
|
||||
async handleObfuckName(name: string) {
|
||||
const contextMsgFile = FileNapCatOneBotUUID.decode(name);
|
||||
if (contextMsgFile && contextMsgFile.msgId && contextMsgFile.elementId) {
|
||||
const {peer, msgId, elementId} = contextMsgFile;
|
||||
const { peer, msgId, elementId } = contextMsgFile;
|
||||
const rawMessage = (await this.core.apis.MsgApi.getMsgsByMsgId(peer, [msgId]))?.msgList.find(msg => msg.msgId === msgId);
|
||||
const mixElement = rawMessage?.elements.find(e => e.elementId === elementId);
|
||||
const mixElementInner = mixElement?.videoElement ?? mixElement?.fileElement ?? mixElement?.pttElement ?? mixElement?.picElement;
|
||||
@@ -1028,29 +1032,29 @@ export class OneBotMsgApi {
|
||||
let url = '';
|
||||
if (mixElement?.picElement && rawMessage) {
|
||||
const tempData =
|
||||
await this.obContext.apis.MsgApi.rawToOb11Converters.picElement?.(mixElement?.picElement, rawMessage, mixElement, {parseMultMsg: false}) as OB11MessageImage | undefined;
|
||||
await this.obContext.apis.MsgApi.rawToOb11Converters.picElement?.(mixElement?.picElement, rawMessage, mixElement, { parseMultMsg: false }) as OB11MessageImage | undefined;
|
||||
url = tempData?.data.url ?? '';
|
||||
}
|
||||
if (mixElement?.videoElement && rawMessage) {
|
||||
const tempData =
|
||||
await this.obContext.apis.MsgApi.rawToOb11Converters.videoElement?.(mixElement?.videoElement, rawMessage, mixElement, {parseMultMsg: false}) as OB11MessageVideo | undefined;
|
||||
await this.obContext.apis.MsgApi.rawToOb11Converters.videoElement?.(mixElement?.videoElement, rawMessage, mixElement, { parseMultMsg: false }) as OB11MessageVideo | undefined;
|
||||
url = tempData?.data.url ?? '';
|
||||
}
|
||||
return url !== '' ? url : await this.core.apis.FileApi.downloadMedia(msgId, peer.chatType, peer.peerUid, elementId, '', '');
|
||||
}
|
||||
throw new Error('文件名解析失败');
|
||||
return undefined;
|
||||
}
|
||||
|
||||
groupChangDecreseType2String(type: number): GroupDecreaseSubType {
|
||||
switch (type) {
|
||||
case 130:
|
||||
return 'leave';
|
||||
case 131:
|
||||
return 'kick';
|
||||
case 3:
|
||||
return 'kick_me';
|
||||
default:
|
||||
return 'kick';
|
||||
case 130:
|
||||
return 'leave';
|
||||
case 131:
|
||||
return 'kick';
|
||||
case 3:
|
||||
return 'kick_me';
|
||||
default:
|
||||
return 'kick';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1069,7 +1073,7 @@ export class OneBotMsgApi {
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}, 1, 1000).catch(undefined);
|
||||
}, 1, 1000).catch(() => undefined);
|
||||
if (dataNotify) {
|
||||
return !dataNotify.actionUser.uid ? dataNotify.user2.uid : dataNotify.actionUser.uid;
|
||||
}
|
||||
|
@@ -84,17 +84,19 @@ export class OneBotQuickActionApi {
|
||||
let notify = (await this.core.apis.GroupApi.getSingleScreenNotifies(false, 100)).find(e => e.seq == flag);
|
||||
if (!notify) {
|
||||
notify = (await this.core.apis.GroupApi.getSingleScreenNotifies(true, 100)).find(e => e.seq == flag);
|
||||
return { doubt: true, notify };
|
||||
}
|
||||
return notify;
|
||||
return { doubt: false, notify };
|
||||
}
|
||||
|
||||
async handleGroupRequest(request: OB11GroupRequestEvent, quickAction: QuickActionGroupRequest) {
|
||||
|
||||
const invite_notify = this.obContext.apis.MsgApi.notifyGroupInvite.get(request.flag);
|
||||
const notify = invite_notify ?? await this.findNotify(request.flag);
|
||||
const { doubt, notify } = invite_notify ? { doubt: false, notify: invite_notify } : await this.findNotify(request.flag);
|
||||
|
||||
if (!isNull(quickAction.approve) && notify) {
|
||||
this.core.apis.GroupApi.handleGroupRequest(
|
||||
doubt,
|
||||
notify,
|
||||
quickAction.approve ? NTGroupRequestOperateTypes.KAGREE : NTGroupRequestOperateTypes.KREFUSE,
|
||||
quickAction.reason,
|
||||
|
@@ -3,7 +3,7 @@ import { NapCatCore } from '@/core';
|
||||
|
||||
export class OB11GroupAdminNoticeEvent extends OB11GroupNoticeEvent {
|
||||
notice_type = 'group_admin';
|
||||
sub_type: 'set' | 'unset';
|
||||
sub_type: 'set' | 'unset';
|
||||
|
||||
constructor(core: NapCatCore, group_id: number, user_id: number, sub_type: 'set' | 'unset') {
|
||||
super(core, group_id, user_id);
|
||||
|
@@ -73,6 +73,7 @@ export class OB11Construct {
|
||||
|
||||
static group(group: Group): OB11Group {
|
||||
return {
|
||||
group_remark: group.remarkName,
|
||||
group_id: +group.groupCode,
|
||||
group_name: group.groupName,
|
||||
member_count: group.memberCount,
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user