From aed63c34c98a63762cd981be0c0709d13dd1c290 Mon Sep 17 00:00:00 2001 From: snowy Date: Fri, 29 Mar 2024 14:58:24 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E9=85=8D=E7=BD=AE=E9=A1=B9=E7=9B=AE?= =?UTF-8?q?=E7=9A=84=E7=83=AD=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 69 +----------------------------- docs/deployment/fandq.md | 37 ++++++++++++++++ docs/usage/basic_command.md | 31 ++++++++------ docs/usage/extra_command.md | 17 +++++++- liteyuki/liteyuki_main/core.py | 61 +++++++++++++++++++++++++- liteyuki/resources/lang/en.lang | 1 + liteyuki/resources/lang/zh-CN.lang | 4 ++ liteyuki/utils/data.py | 6 +-- liteyuki/utils/data_manager.py | 9 +++- liteyuki/utils/message.py | 2 +- 10 files changed, 148 insertions(+), 89 deletions(-) create mode 100644 docs/deployment/fandq.md diff --git a/README.md b/README.md index f02679a..630f207 100644 --- a/README.md +++ b/README.md @@ -10,78 +10,11 @@ - 全新可视化`npm`包管理,支持一键安装插件 - 支持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.用户协议 diff --git a/docs/deployment/fandq.md b/docs/deployment/fandq.md new file mode 100644 index 0000000..5399491 --- /dev/null +++ b/docs/deployment/fandq.md @@ -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的开发者 \ No newline at end of file diff --git a/docs/usage/basic_command.md b/docs/usage/basic_command.md index 2ac571a..ea4838b 100644 --- a/docs/usage/basic_command.md +++ b/docs/usage/basic_command.md @@ -5,34 +5,41 @@ order: 1 category: 使用手册 --- +## 基础插件命令 -## 内置插件命令 +#### 命令前有[S]的表示仅超级用户可用,[O]和[A]分别为群主和群管可用 ### 轻雪`liteyuki` ```shell -reload-liteyuki # 重载轻雪 -update-liteyuki # 更新轻雪 -liteyuki # 查看轻雪信息 +[S]reload-liteyuki # 重载轻雪 +[S]update-liteyuki # 更新轻雪 +[S]liteyuki # 查看轻雪信息 +[S]config set value # 添加配置项,若存在则会覆盖,输入值会被执行,以便于转换为正确的值,"10"和10是不一样的 +[S]config get [key] # 查询配置项,不带key返回配置项列表,推荐私聊使用 +# 上述两个命令修改的配置项在数据库中保存,但是优先级低于配置文件,如果配置文件中存在相同的配置项,将会使用配置文件中的配置 +------ +别名: reload-liteyuki 重启轻雪, update-liteyuki 更新轻雪, config 配置, set 设置, get 查询 ``` ### 轻雪Nonebot插件管理 `liteyuki_npm` ```shell -npm update # 更新插件索引 -npm install # 安装插件 -npm uninstall # 卸载插件 -npm search # 搜索插件 +[S]npm update # 更新插件索引 +[S]npm install # 安装插件 +[S]npm uninstall # 卸载插件 +[S]npm search # 通过关键词搜索插件 ------ 别名: npm 插件, update 更新, install 安装, uninstall 卸载, search 搜索 ``` ```shell -enable # 启用插件 -disable # 禁用插件 -enable-global # 全局启用插件 -disable-global # 全局禁用插件 +[SOA]enable # 启用插件 +[SOA]disable # 禁用插件 +[S]enable-global # 全局启用插件 +[S]disable-global # 全局禁用插件 list-plugin # 列出所有插件 +# 受限于Nonebot的钩子函数,目前只能阻断消息事件的传入,对于主动推送消息的插件,无法将其阻止 ------ 别名: enable 启用, disable 停用, enable-global 全局启用, disable-global 全局停用, list-plugin 列出插件/插件列表 ``` diff --git a/docs/usage/extra_command.md b/docs/usage/extra_command.md index 98c0eb6..bd6359d 100644 --- a/docs/usage/extra_command.md +++ b/docs/usage/extra_command.md @@ -3,4 +3,19 @@ title: 功能命令 icon: laptop-code order: 2 category: 使用手册 ---- \ No newline at end of file +--- + + +## 功能插件命令 + +### 轻雪天气`liteyuki_weather` +配置项 +```yaml +weather-key # 和风天气的天气key +``` +命令 +```shell +weather # 查询目标地天气,例如:"天气 北京 海淀", "weather Tokyo Shinjuku" +bind-city # 绑定查询城市,个人全局生效 +别名:weather 天气 +``` \ No newline at end of file diff --git a/liteyuki/liteyuki_main/core.py b/liteyuki/liteyuki_main/core.py index 8315c49..85d4e3c 100644 --- a/liteyuki/liteyuki_main/core.py +++ b/liteyuki/liteyuki_main/core.py @@ -1,17 +1,23 @@ +import json +from typing import Any + +import aiofiles +import yaml from nonebot import require from nonebot.permission import SUPERUSER 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.language import get_user_lang from liteyuki.utils.message import Markdown as md, send_markdown from .reloader import Reloader +from liteyuki.utils.data_manager import StoredConfig, common_db 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( Alconna( @@ -34,6 +40,24 @@ reload_liteyuki = on_alconna( 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() async def _(bot: T_Bot): @@ -64,3 +88,36 @@ async def _(bot: T_Bot, event: T_MessageEvent): async def _(): await reload_liteyuki.send("Liteyuki reloading") 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) diff --git a/liteyuki/resources/lang/en.lang b/liteyuki/resources/lang/en.lang index 5559581..7e48720 100644 --- a/liteyuki/resources/lang/en.lang +++ b/liteyuki/resources/lang/en.lang @@ -9,6 +9,7 @@ log.success=Success liteyuki.restart=Restart liteyuki.restart_now=Restart now 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.enable_webdash=Web dashboard is enabled: {URL} diff --git a/liteyuki/resources/lang/zh-CN.lang b/liteyuki/resources/lang/zh-CN.lang index 416daa7..6edd17f 100644 --- a/liteyuki/resources/lang/zh-CN.lang +++ b/liteyuki/resources/lang/zh-CN.lang @@ -9,6 +9,10 @@ log.success=成功 liteyuki.restart=重启 liteyuki.restart_now=立即重启 liteyuki.update_restart=更新完成,你可以{RESTART}或稍后手动重启以应用这些更新 +liteyuki.current_config=当前配置项如下 +liteyuki.static_config=静态文件配置项 +liteyuki.stored_config=储存的配置项 +liteyuki.config_set_success=配置项 {KEY}={VAL} 设置成功 main.current_language=当前配置语言为: {LANG} main.enable_webdash=已启用网页监控面板: {URL} diff --git a/liteyuki/utils/data.py b/liteyuki/utils/data.py index 0c2d555..104a1b9 100644 --- a/liteyuki/utils/data.py +++ b/liteyuki/utils/data.py @@ -15,9 +15,9 @@ class LiteModel(BaseModel): def dump(self, *args, **kwargs): if pydantic.__version__ < "1.8.2": - return self.dict(by_alias=True) + return self.dict(*args, **kwargs) else: - return self.model_dump(by_alias=True) + return self.model_dump(*args, **kwargs) class Database: @@ -30,7 +30,7 @@ class Database: self.conn = sqlite3.connect(db_name) 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: model: 数据模型实例 diff --git a/liteyuki/utils/data_manager.py b/liteyuki/utils/data_manager.py index 71d1ec4..e2d14d0 100644 --- a/liteyuki/utils/data_manager.py +++ b/liteyuki/utils/data_manager.py @@ -43,9 +43,14 @@ class GlobalPlugin(LiteModel): enabled: bool = Field(True, alias="enabled") +class StoredConfig(LiteModel): + TABLE_NAME = "stored_config" + config: dict = {} + + def auto_migrate(): - print("Migrating databases...") user_db.auto_migrate(User()) group_db.auto_migrate(Group()) plugin_db.auto_migrate(InstalledPlugin(), GlobalPlugin()) - common_db.auto_migrate(GlobalPlugin()) + common_db.auto_migrate(GlobalPlugin(), StoredConfig()) + diff --git a/liteyuki/utils/message.py b/liteyuki/utils/message.py index b6f988d..a782767 100644 --- a/liteyuki/utils/message.py +++ b/liteyuki/utils/message.py @@ -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[ 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: message_type = event.message_type session_id = event.user_id if event.message_type == "private" else event.group_id