1
0
forked from bot/app

feat: 配置项目的热修改

This commit is contained in:
远野千束 2024-03-29 14:58:24 +08:00
parent f23567194c
commit aed63c34c9
10 changed files with 148 additions and 89 deletions

View File

@ -10,78 +10,11 @@
- 全新可视化`npm`包管理,支持一键安装插件 - 全新可视化`npm`包管理,支持一键安装插件
- 支持OneBotv11/12标准通信且使用`Alconna`命令解析不再局限于OneBot - 支持OneBotv11/12标准通信且使用`Alconna`命令解析不再局限于OneBot
## 1.安装和部署 ### [文档](https://bot.liteyuki.icu)
1. 安装 `Git``Python3.10+` 环境
2. 克隆项目 `git clone https://github.com/snowykami/LiteyukiBot` (无法连接可以用`https://gitee.com/snowykami/LiteyukiBot`)
3. 切换目录`cd LiteyukiBot`
4. 安装依赖`pip install -r requirements.txt`(如果多个Python环境请指定后安装`pythonx -m pip install -r requirements.txt`)
5. 启动`python main.py`
## 2. 配置
### 轻雪配置项(Nonebot插件配置项也可以写在此与dotenv格式不同应为小写)
如果不确定字段的含义,请不要修改(部分在自动生成配置文件中未列出,需手动添加)
```yaml
# 生成文件的配置项
command_start: [ "/", " " ] # 指令前缀
host: 127.0.0.1 # 监听地址
port: 20216 # 绑定端口
nickname: [ "liteyuki" ] # 机器人昵称
superusers: [ "1919810" ] # 超级用户
# 未列出的配置项(如要自定义请手动修改)
onebot_access_token: "" # Onebot访问令牌[具体请看](https://onebot.adapters.nonebot.dev/docs/guide/configuration)
default_language: "zh-CN" # 默认语言
log_level: "INFO" # 日志等级
log_icon: true # 是否显示日志等级图标(某些控制台不可用)
auto_report: true # 是否自动上报问题给轻雪服务器,仅包含硬件信息和运行软件版本
# 其他Nonebot插件的配置项
custom_config_1: "custom_value1"
custom_config_2: "custom_value2"
...
```
### Onebot实现端配置
不同的实现端给出的字段可能不同,但是基本上都是一样的,这里给出一个参考值
| 字段 | 参考值 | 说明 |
|-------------|--------------------------|----------------------------------|
| 协议 | 反向WebSocket | 推荐使用反向ws协议进行通信即轻雪作为服务端 |
| 地址 | ws://`address`/onebot/v11 | 地址取决于配置文件,本机默认为`127.0.0.1:20216` |
| AccessToken | `""` | 如果你给轻雪配置了`AccessToken`,请在此填写相同的值 |
## 3.其他
### 常见问题
- 设备上Python环境太乱了pip和python不对应怎么办
- 请使用`/path/to/python -m pip install -r requirements.txt`来安装依赖,
然后用`/path/to/python main.py`来启动Bot
其中`/path/to/python`是你要用来运行Bot可执行文件
- 为什么我启动后机器人没有反应?
- 请检查配置文件的`command_start`或`superusers`,确认你有权限使用命令并按照正确的命令发送
- 怎么登录QQ等聊天平台
- 你有这个问题说明你不是很了解这个项目,本项目不负责实现登录功能,只负责处理消息
你需要使用Onebot标准的实现端来连接到轻雪并将消息上报给轻雪下面已经列出一些推荐的实现端
#### 推荐方案(QQ)
1. [Lagrange.OneBot](https://github.com/KonataDev/Lagrange.Core)目前点按交互目前仅支持Lagrange
2. [LiteLoaderNTQQ+LLOneBot](https://github.com/LLOneBot/LLOneBot)基于NTQQ的Onebot实现
3. 云崽的`icqq-plugin`和`ws-plugin`进行通信
4. `Go-cqhttp`(目前已经半死不活了)
5. 人工实现的`Onebot`协议自己整一个WebSocket客户端看着QQ的消息然后给轻雪传输数据
#### 推荐方案(Minecraft)
1. 我们有专门为Minecraft开发的服务器Bot支持OnebotV11/12标准详细请看[MinecraftOneBot](https://github.com/snowykami/MinecraftOnebot)
使用其他项目连接请先自行查阅文档若有困难请联系对应开发者而不是Liteyuki的开发者
## 4.用户协议 ## 4.用户协议

37
docs/deployment/fandq.md Normal file
View File

@ -0,0 +1,37 @@
---
title: 答疑
icon: object-group
order: 3
category: 使用指南
tag:
- 配置
- 部署
---
### 常见问题
- 设备上Python环境太乱了pip和python不对应怎么办
- 请使用`/path/to/python -m pip install -r requirements.txt`来安装依赖,
然后用`/path/to/python main.py`来启动Bot
其中`/path/to/python`是你要用来运行Bot的可执行文件
- 为什么我启动后机器人没有反应?
- 请检查配置文件的`command_start`或`superusers`,确认你有权限使用命令并按照正确的命令发送
- 怎么登录聊天平台例如QQ
- 你有这个问题说明你不是很了解这个项目,本项目不负责实现登录功能,只负责处理和回应消息,登录功能由实现端(协议端)提供,
实现端本身不负责处理响应逻辑将消息按照OneBot标准处理好上报给轻雪
你需要使用Onebot标准的实现端来连接到轻雪并将消息上报给轻雪下面已经列出一些推荐的实现端
#### 推荐方案(QQ)
1. [Lagrange.OneBot](https://github.com/KonataDev/Lagrange.Core)目前Markdown点按交互目前仅支持Lagrange
2. [LiteLoaderNTQQ+LLOneBot](https://github.com/LLOneBot/LLOneBot)基于NTQQ的Onebot实现
3. 云崽的`icqq-plugin`和`ws-plugin`进行通信
4. `Go-cqhttp`(目前已经半死不活了)
5. 人工实现的`Onebot`协议自己整一个WebSocket客户端看着QQ的消息然后给轻雪传输数据
#### 推荐方案(Minecraft)
1. 我们有专门为Minecraft开发的服务器Bot支持OnebotV11/12标准详细请看[MinecraftOneBot](https://github.com/snowykami/MinecraftOnebot)
使用其他项目连接请先自行查阅文档若有困难请联系对应开发者而不是Liteyuki的开发者

View File

@ -5,34 +5,41 @@ order: 1
category: 使用手册 category: 使用手册
--- ---
## 基础插件命令
## 内置插件命令 #### 命令前有[S]的表示仅超级用户可用,[O]和[A]分别为群主和群管可用
### 轻雪`liteyuki` ### 轻雪`liteyuki`
```shell ```shell
reload-liteyuki # 重载轻雪 [S]reload-liteyuki # 重载轻雪
update-liteyuki # 更新轻雪 [S]update-liteyuki # 更新轻雪
liteyuki # 查看轻雪信息 [S]liteyuki # 查看轻雪信息
[S]config set <key> value # 添加配置项,若存在则会覆盖,输入值会被执行,以便于转换为正确的值,"10"和10是不一样的
[S]config get [key] # 查询配置项不带key返回配置项列表推荐私聊使用
# 上述两个命令修改的配置项在数据库中保存,但是优先级低于配置文件,如果配置文件中存在相同的配置项,将会使用配置文件中的配置
------
别名: reload-liteyuki 重启轻雪, update-liteyuki 更新轻雪, config 配置, set 设置, get 查询
``` ```
### 轻雪Nonebot插件管理 `liteyuki_npm` ### 轻雪Nonebot插件管理 `liteyuki_npm`
```shell ```shell
npm update # 更新插件索引 [S]npm update # 更新插件索引
npm install <plugin_name> # 安装插件 [S]npm install <plugin_name> # 安装插件
npm uninstall <plugin_name> # 卸载插件 [S]npm uninstall <plugin_name> # 卸载插件
npm search <keywords...> # 搜索插件 [S]npm search <keywords...> # 通过关键词搜索插件
------ ------
别名: npm 插件, update 更新, install 安装, uninstall 卸载, search 搜索 别名: npm 插件, update 更新, install 安装, uninstall 卸载, search 搜索
``` ```
```shell ```shell
enable <plugin_name> # 启用插件 [SOA]enable <plugin_name> # 启用插件
disable <plugin_name> # 禁用插件 [SOA]disable <plugin_name> # 禁用插件
enable-global <plugin_name> # 全局启用插件 [S]enable-global <plugin_name> # 全局启用插件
disable-global <plugin_name> # 全局禁用插件 [S]disable-global <plugin_name> # 全局禁用插件
list-plugin # 列出所有插件 list-plugin # 列出所有插件
# 受限于Nonebot的钩子函数目前只能阻断消息事件的传入对于主动推送消息的插件无法将其阻止
------ ------
别名: enable 启用, disable 停用, enable-global 全局启用, disable-global 全局停用, list-plugin 列出插件/插件列表 别名: enable 启用, disable 停用, enable-global 全局启用, disable-global 全局停用, list-plugin 列出插件/插件列表
``` ```

View File

@ -3,4 +3,19 @@ title: 功能命令
icon: laptop-code icon: laptop-code
order: 2 order: 2
category: 使用手册 category: 使用手册
--- ---
## 功能插件命令
### 轻雪天气`liteyuki_weather`
配置项
```yaml
weather-key # 和风天气的天气key
```
命令
```shell
weather <keywords...> # 查询目标地天气,例如:"天气 北京 海淀", "weather Tokyo Shinjuku"
bind-city <keywords...> # 绑定查询城市,个人全局生效
别名weather 天气
```

View File

@ -1,17 +1,23 @@
import json
from typing import Any
import aiofiles
import yaml
from nonebot import require from nonebot import require
from nonebot.permission import SUPERUSER from nonebot.permission import SUPERUSER
from git import Repo from git import Repo
from liteyuki.utils.config import config from liteyuki.utils.config import config, load_from_yaml
from liteyuki.utils.ly_typing import T_Bot, T_MessageEvent from liteyuki.utils.ly_typing import T_Bot, T_MessageEvent
from liteyuki.utils.language import get_user_lang from liteyuki.utils.language import get_user_lang
from liteyuki.utils.message import Markdown as md, send_markdown from liteyuki.utils.message import Markdown as md, send_markdown
from .reloader import Reloader from .reloader import Reloader
from liteyuki.utils.data_manager import StoredConfig, common_db
require("nonebot_plugin_alconna") require("nonebot_plugin_alconna")
from nonebot_plugin_alconna import on_alconna, Alconna from nonebot_plugin_alconna import on_alconna, Alconna, Args, Subcommand, Arparma
cmd_liteyuki = on_alconna( cmd_liteyuki = on_alconna(
Alconna( Alconna(
@ -34,6 +40,24 @@ reload_liteyuki = on_alconna(
permission=SUPERUSER permission=SUPERUSER
) )
cmd_config = on_alconna(
Alconna(
["config", "配置"],
Subcommand(
"set",
Args["key", str]["value", Any],
alias=["设置"],
),
Subcommand(
"get",
Args["key", str, None],
alias=["查询"]
)
),
permission=SUPERUSER
)
@cmd_liteyuki.handle() @cmd_liteyuki.handle()
async def _(bot: T_Bot): async def _(bot: T_Bot):
@ -64,3 +88,36 @@ async def _(bot: T_Bot, event: T_MessageEvent):
async def _(): async def _():
await reload_liteyuki.send("Liteyuki reloading") await reload_liteyuki.send("Liteyuki reloading")
Reloader.reload(3) Reloader.reload(3)
@cmd_config.handle()
async def _(result: Arparma, event: T_MessageEvent, bot: T_Bot):
ulang = get_user_lang(str(event.user_id))
stored_config: StoredConfig = common_db.first(StoredConfig(), default=StoredConfig())
if result.subcommands.get("set"):
key, value = result.subcommands.get("set").args.get("key"), result.subcommands.get("set").args.get("value")
try:
value = eval(value)
except:
pass
stored_config.config[key] = value
common_db.upsert(stored_config)
await cmd_config.finish(f"{ulang.get('liteyuki.config_set_success', KEY=key, VAL=value)}")
elif result.subcommands.get("get"):
key = result.subcommands.get("get").args.get("key")
file_config = load_from_yaml("config.yml")
reply = f"{ulang.get('liteyuki.current_config')}"
if key:
reply += f"```dotenv\n{key}={file_config.get(key, stored_config.config.get(key))}\n```"
else:
reply = f"{ulang.get('liteyuki.current_config')}"
reply += f"\n{ulang.get('liteyuki.static_config')}\n```dotenv"
for k, v in file_config.items():
reply += f"\n{k}={v}"
reply += "\n```"
if len(stored_config.config) > 0:
reply += f"\n{ulang.get('liteyuki.stored_config')}\n```dotenv"
for k, v in stored_config.config.items():
reply += f"\n{k}={v}"
reply += "\n```"
await send_markdown(reply, bot, event=event)

View File

@ -9,6 +9,7 @@ log.success=Success
liteyuki.restart=Restart liteyuki.restart=Restart
liteyuki.restart_now=Restart now liteyuki.restart_now=Restart now
liteyuki.update_restart=Update completely, You can {RESTART} or later to apply the changes liteyuki.update_restart=Update completely, You can {RESTART} or later to apply the changes
liteyuki.current_config=Current Config
main.current_language=Current config language: {LANG} main.current_language=Current config language: {LANG}
main.enable_webdash=Web dashboard is enabled: {URL} main.enable_webdash=Web dashboard is enabled: {URL}

View File

@ -9,6 +9,10 @@ log.success=成功
liteyuki.restart=重启 liteyuki.restart=重启
liteyuki.restart_now=立即重启 liteyuki.restart_now=立即重启
liteyuki.update_restart=更新完成,你可以{RESTART}或稍后手动重启以应用这些更新 liteyuki.update_restart=更新完成,你可以{RESTART}或稍后手动重启以应用这些更新
liteyuki.current_config=当前配置项如下
liteyuki.static_config=静态文件配置项
liteyuki.stored_config=储存的配置项
liteyuki.config_set_success=配置项 {KEY}={VAL} 设置成功
main.current_language=当前配置语言为: {LANG} main.current_language=当前配置语言为: {LANG}
main.enable_webdash=已启用网页监控面板: {URL} main.enable_webdash=已启用网页监控面板: {URL}

View File

@ -15,9 +15,9 @@ class LiteModel(BaseModel):
def dump(self, *args, **kwargs): def dump(self, *args, **kwargs):
if pydantic.__version__ < "1.8.2": if pydantic.__version__ < "1.8.2":
return self.dict(by_alias=True) return self.dict(*args, **kwargs)
else: else:
return self.model_dump(by_alias=True) return self.model_dump(*args, **kwargs)
class Database: class Database:
@ -30,7 +30,7 @@ class Database:
self.conn = sqlite3.connect(db_name) self.conn = sqlite3.connect(db_name)
self.cursor = self.conn.cursor() self.cursor = self.conn.cursor()
def first(self, model: LiteModel, condition: str, *args: Any, default: Any = None) -> LiteModel | Any | None: def first(self, model: LiteModel, condition: str = "", *args: Any, default: Any = None) -> LiteModel | Any | None:
"""查询第一个 """查询第一个
Args: Args:
model: 数据模型实例 model: 数据模型实例

View File

@ -43,9 +43,14 @@ class GlobalPlugin(LiteModel):
enabled: bool = Field(True, alias="enabled") enabled: bool = Field(True, alias="enabled")
class StoredConfig(LiteModel):
TABLE_NAME = "stored_config"
config: dict = {}
def auto_migrate(): def auto_migrate():
print("Migrating databases...")
user_db.auto_migrate(User()) user_db.auto_migrate(User())
group_db.auto_migrate(Group()) group_db.auto_migrate(Group())
plugin_db.auto_migrate(InstalledPlugin(), GlobalPlugin()) plugin_db.auto_migrate(InstalledPlugin(), GlobalPlugin())
common_db.auto_migrate(GlobalPlugin()) common_db.auto_migrate(GlobalPlugin(), StoredConfig())

View File

@ -8,7 +8,7 @@ from .ly_typing import T_Bot, T_MessageEvent
async def send_markdown(markdown: str, bot: T_Bot, *, message_type: str = None, session_id: str | int = None, event: T_MessageEvent = None, **kwargs) -> dict[ async def send_markdown(markdown: str, bot: T_Bot, *, message_type: str = None, session_id: str | int = None, event: T_MessageEvent = None, **kwargs) -> dict[
str, Any]: str, Any]:
formatted_md = v11.unescape(markdown).replace("\n", r"\n").replace("\"", r'\\\"') formatted_md = v11.unescape(markdown).replace("\n", r"\n").replace('"', r'\\\"')
if event is not None and message_type is None: if event is not None and message_type is None:
message_type = event.message_type message_type = event.message_type
session_id = event.user_id if event.message_type == "private" else event.group_id session_id = event.user_id if event.message_type == "private" else event.group_id