diff --git a/ica-rs/src/config.rs b/ica-rs/src/config.rs index 9082259..650b986 100644 --- a/ica-rs/src/config.rs +++ b/ica-rs/src/config.rs @@ -4,7 +4,6 @@ use std::fs; use colored::Colorize; use serde::Deserialize; use toml::from_str; -use tracing::warn; use crate::data_struct::{ica, tailchat}; @@ -18,12 +17,16 @@ pub struct IcaConfig { /// bot 的 qq pub self_id: ica::UserId, /// 提醒的房间 + #[serde(default = "default_empty_i64_vec")] pub notice_room: Vec, /// 是否提醒 + #[serde(default = "default_false")] pub notice_start: bool, /// 管理员列表 + #[serde(default = "default_empty_i64_vec")] pub admin_list: Vec, /// 过滤列表 + #[serde(default = "default_empty_i64_vec")] pub filter_list: Vec, } @@ -38,36 +41,51 @@ pub struct TailchatConfig { /// 提醒的房间 pub notice_room: Vec<(tailchat::GroupId, tailchat::ConverseId)>, /// 是否提醒 + #[serde(default = "default_false")] pub notice_start: bool, /// 管理员列表 + #[serde(default = "default_empty_str_vec")] pub admin_list: Vec, /// 过滤列表 + #[serde(default = "default_empty_str_vec")] pub filter_list: Vec, } +fn default_plugin_path() -> String { "./plugins".to_string() } +fn default_config_path() -> String { "./config".to_string() } + #[derive(Debug, Clone, Deserialize)] pub struct PyConfig { /// 插件路径 + #[serde(default = "default_plugin_path")] pub plugin_path: String, - /// 配置文件路径 + /// 配置文件夹路径 + #[serde(default = "default_config_path")] pub config_path: String, } +fn default_empty_i64_vec() -> Vec { Vec::new() } +fn default_empty_str_vec() -> Vec { Vec::new() } +fn default_false() -> bool { false } + /// 主配置 #[derive(Debug, Clone, Deserialize)] pub struct BotConfig { /// 是否启用 icalingua - pub enable_ica: Option, + #[serde(default = "default_false")] + pub enable_ica: bool, /// Ica 配置 pub ica: Option, /// 是否启用 Tailchat - pub enable_tailchat: Option, + #[serde(default = "default_false")] + pub enable_tailchat: bool, /// Tailchat 配置 pub tailchat: Option, /// 是否启用 Python 插件 - pub enable_py: Option, + #[serde(default = "default_false")] + pub enable_py: bool, /// Python 插件配置 pub py: Option, } @@ -88,10 +106,9 @@ impl BotConfig { let mut args = env::args(); while let Some(arg) = args.next() { if arg == "-c" { - config_file_path = args.next().expect(&format!( - "{}", - "No config path given\nUsage: -c ".red() - )); + config_file_path = args.next().unwrap_or_else(|| { + panic!("{}", "No config path given\nUsage: -c ".red()) + }); break; } } @@ -99,64 +116,13 @@ impl BotConfig { } /// 检查是否启用 ica - pub fn check_ica(&self) -> bool { - match self.enable_ica { - Some(enable) => { - if enable && self.ica.is_none() { - warn!("enable_ica 为 true 但未填写 [ica] 配置\n将不启用 ica"); - false - } else { - enable - } - } - None => { - if self.ica.is_some() { - warn!("未填写 enable_ica 但填写了 [ica] 配置\n将不启用 ica"); - } - false - } - } - } + pub fn check_ica(&self) -> bool { self.enable_ica } /// 检查是否启用 Tailchat - pub fn check_tailchat(&self) -> bool { - match self.enable_tailchat { - Some(enable) => { - if enable && self.tailchat.is_none() { - warn!("enable_tailchat 为 true 但未填写 [tailchat] 配置\n将不启用 Tailchat"); - false - } else { - enable - } - } - None => { - if self.tailchat.is_some() { - warn!("未填写 enable_tailchat 但填写了 [tailchat] 配置\n将不启用 Tailchat"); - } - false - } - } - } + pub fn check_tailchat(&self) -> bool { self.enable_tailchat } /// 检查是否启用 Python 插件 - pub fn check_py(&self) -> bool { - match self.enable_py { - Some(enable) => { - if enable && self.py.is_none() { - warn!("enable_py 为 true 但未填写 [py] 配置\n将不启用 Python 插件"); - false - } else { - true - } - } - None => { - if self.py.is_some() { - warn!("未填写 enable_py 但填写了 [py] 配置\n将不启用 Python 插件"); - } - false - } - } - } + pub fn check_py(&self) -> bool { self.enable_py } pub fn ica(&self) -> IcaConfig { self.ica.clone().expect("No ica config found") } pub fn tailchat(&self) -> TailchatConfig { diff --git a/ica-rs/src/ica/events.rs b/ica-rs/src/ica/events.rs index d5709f0..124e248 100644 --- a/ica-rs/src/ica/events.rs +++ b/ica-rs/src/ica/events.rs @@ -1,3 +1,5 @@ +use std::path::PathBuf; + use colored::Colorize; use rust_socketio::asynchronous::Client; use rust_socketio::{Event, Payload}; @@ -41,6 +43,63 @@ pub async fn add_message(payload: Payload, client: Client) { VERSION, ICA_VERSION )); send_message(&client, &reply).await; + } else if message.content() == "/bot-ls" { + let reply = message.reply_with(&format!( + "shenbot-py v{}\n{}", + VERSION, + if MainStatus::global_config().check_py() { + py::PyStatus::display() + } else { + "未启用 Python 插件".to_string() + } + )); + send_message(&client, &reply).await; + } + if MainStatus::global_config().ica().admin_list.contains(&message.sender_id()) { + // admin 区 + if message.content().starts_with("/bot-enable") { + // 先判定是否为 admin + // 尝试获取后面的信息 + let mut content = message.content().split_whitespace(); + content.next(); + if let Some(name) = content.next() { + let path_name = PathBuf::from(name); + if py::PyStatus::get_map().contains_key(&path_name) { + if py::PyStatus::get_config().get_status(path_name.as_path()) { + let reply = message.reply_with("无变化, 插件已经启用"); + send_message(&client, &reply).await; + return; + } + py::PyStatus::get_config_mut() + .set_status(path_name.as_path(), true); + let reply = message.reply_with("启用插件完成"); + send_message(&client, &reply).await; + } else { + let reply = message.reply_with("未找到插件"); + send_message(&client, &reply).await; + } + } + } else if message.content().starts_with("/bot-disable") { + let mut content = message.content().split_whitespace(); + content.next(); + if let Some(name) = content.next() { + let path_name = PathBuf::from(name); + if py::PyStatus::get_map().contains_key(&path_name) { + if !py::PyStatus::get_config().get_status(path_name.as_path()) { + let reply = message.reply_with("无变化, 插件已经禁用"); + send_message(&client, &reply).await; + return; + } + py::PyStatus::get_config_mut() + .set_status(path_name.as_path(), false); + let reply = message.reply_with("已经禁用插件"); + send_message(&client, &reply).await; + } else { + let reply = message.reply_with("未找到插件"); + send_message(&client, &reply).await; + } + } + } } } // python 插件 diff --git a/ica-rs/src/main.rs b/ica-rs/src/main.rs index 454263d..444b0a3 100644 --- a/ica-rs/src/main.rs +++ b/ica-rs/src/main.rs @@ -29,6 +29,16 @@ pub const VERSION: &str = env!("CARGO_PKG_VERSION"); pub const ICA_VERSION: &str = "1.6.1"; pub const TAILCHAT_VERSION: &str = "1.2.1"; +pub fn version_str() -> String { + format!( + "shenbot-rs v{}-{} ica v{} tailchat v{}", + VERSION, + if STABLE { "" } else { "开发版" }, + ICA_VERSION, + TAILCHAT_VERSION + ) +} + /// 是否为稳定版本 /// 会在 release 的时候设置为 true pub const STABLE: bool = false; @@ -54,7 +64,9 @@ macro_rules! async_any_callback_with_state { } #[tokio::main] -async fn main() { +async fn main() -> anyhow::Result<()> { inner_main().await } + +async fn inner_main() -> anyhow::Result<()> { // -d -> debug // none -> info let level = { @@ -82,7 +94,9 @@ async fn main() { MainStatus::static_init(bot_config); let bot_config = MainStatus::global_config(); - py::init_py(); + if bot_config.check_py() { + py::init_py(); + } // 准备一个用于停止 socket 的变量 event!(Level::INFO, "启动 ICA"); @@ -120,9 +134,14 @@ async fn main() { tailchat_send.send(()).ok(); event!(Level::INFO, "Disconnected"); + + py::post_py()?; + + Ok(()) } #[allow(dead_code, unused_variables)] +#[cfg(test)] #[tokio::test] async fn test_macro() { use std::sync::Arc; diff --git a/ica-rs/src/py/config.rs b/ica-rs/src/py/config.rs index e9ea529..522f6d3 100644 --- a/ica-rs/src/py/config.rs +++ b/ica-rs/src/py/config.rs @@ -1,6 +1,10 @@ -use std::{path::{Path, PathBuf}, str::FromStr}; +use std::{ + path::{Path, PathBuf}, + str::FromStr, +}; use toml_edit::{value, DocumentMut, Key, Table, TomlError, Value}; +use tracing::{event, Level}; use crate::py::PyStatus; @@ -18,7 +22,7 @@ pub struct PluginConfigFile { } const CONFIG_KEY: &str = "plugins"; -pub const CONFIG_FILE_NAME: &str = "/plugins.toml"; +pub const CONFIG_FILE_NAME: &str = "plugins.toml"; pub const DEFAULT_CONFIG: &str = r#" # 这个文件是由 shenbot 自动生成的, 请 **谨慎** 修改 # 请不要修改这个文件, 除非你知道你在做什么 @@ -34,6 +38,7 @@ impl PluginConfigFile { pub fn from_config_path(path: &Path) -> anyhow::Result { let config_path = path.join(CONFIG_FILE_NAME); if !config_path.exists() { + event!(Level::INFO, "插件配置文件不存在, 正在创建"); std::fs::write(&config_path, DEFAULT_CONFIG)?; Ok(Self::from_str(DEFAULT_CONFIG)?) } else { @@ -44,6 +49,7 @@ impl PluginConfigFile { pub fn verify_and_init(&mut self) { if self.data.get(CONFIG_KEY).is_none() { + event!(Level::INFO, "插件配置文件缺少 plugins 字段, 正在初始化"); self.data.insert_formatted( &Key::from_str(CONFIG_KEY).unwrap(), toml_edit::Item::Table(Table::new()), @@ -88,7 +94,9 @@ impl PluginConfigFile { let plugins = PyStatus::get_map_mut(); self.verify_and_init(); plugins.iter_mut().for_each(|(path, status)| { - status.enabled = self.get_status(path); + let config_status = self.get_status(path); + event!(Level::INFO, "插件状态: {:?} {} -> {}", path, status.enabled, config_status); + status.enabled = config_status; }); } @@ -103,7 +111,8 @@ impl PluginConfigFile { } pub fn write_to_file(&self, path: &PathBuf) -> Result<(), std::io::Error> { - std::fs::write(path, self.data.to_string())?; + let config_path = path.join(CONFIG_FILE_NAME); + std::fs::write(config_path, self.data.to_string())?; Ok(()) } } diff --git a/ica-rs/src/py/mod.rs b/ica-rs/src/py/mod.rs index 5cc3e07..341bd91 100644 --- a/ica-rs/src/py/mod.rs +++ b/ica-rs/src/py/mod.rs @@ -29,7 +29,7 @@ impl PyStatus { PYSTATUS.files = Some(HashMap::new()); } if PYSTATUS.config.is_none() { - let plugin_path = MainStatus::global_config().py().plugin_path; + let plugin_path = MainStatus::global_config().py().config_path.clone(); let mut config = config::PluginConfigFile::from_config_path(&PathBuf::from(plugin_path)) .unwrap(); @@ -93,18 +93,19 @@ impl PyStatus { } } - pub fn get_status(path: &Path) -> bool { Self::get_config().get_status(path) } + /// 获取某个插件的状态 + /// 以 config 优先 + pub fn get_status(path: &PathBuf) -> Option { + let local_plugin = Self::get_map_mut().get_mut(path).map(|p| p.enabled)?; + } - // pub fn list_plugins() -> Vec { Self::get_map().keys().cloned().collect() } + pub fn set_status(path: &Path, status: bool) { Self::get_config_mut().set_status(path, status); } pub fn display() -> String { let map = Self::get_map(); format!( "Python 插件 {{ {} }}", - map.iter() - .map(|(k, v)| format!("{:?}: {:?}", k, v)) - .collect::>() - .join(", ") + map.iter().map(|(k, v)| format!("{:?}-{}", k, v.enabled)).collect::>().join("\n") ) } } @@ -161,7 +162,7 @@ impl PyPlugin { Ok(plugin) => { self.py_module = plugin.py_module; self.changed_time = plugin.changed_time; - self.enabled = PyStatus::get_status(self.file_path.as_path()); + self.enabled = PyStatus::get_config().get_status(self.file_path.as_path()); event!(Level::INFO, "更新 Python 插件文件 {:?} 完成", self.file_path); } Err(e) => { @@ -343,7 +344,7 @@ pub fn load_py_plugins(path: &PathBuf) { // 搜索所有的 py 文件 和 文件夹单层下面的 py 文件 match path.read_dir() { Err(e) => { - event!(Level::WARN, "failed to read plugin path: {:?}", e); + event!(Level::WARN, "读取插件路径失败 {:?}", e); } Ok(dir) => { for entry in dir { @@ -413,3 +414,10 @@ pub fn init_py() { info!("python inited") } + +pub fn post_py() -> anyhow::Result<()> { + PyStatus::get_config_mut().sync_status_to_config(); + PyStatus::get_config() + .write_to_file(&PathBuf::from(MainStatus::global_config().py().config_path))?; + Ok(()) +} diff --git a/ica-rs/src/tailchat/events.rs b/ica-rs/src/tailchat/events.rs index 0db9cdd..87b4cfd 100644 --- a/ica-rs/src/tailchat/events.rs +++ b/ica-rs/src/tailchat/events.rs @@ -8,6 +8,7 @@ use tracing::{event, info, Level}; use crate::data_struct::tailchat::messages::ReceiveMessage; use crate::data_struct::tailchat::status::{BotStatus, UpdateDMConverse}; use crate::tailchat::client::{emit_join_room, send_message}; +use crate::{py, MainStatus, TAILCHAT_VERSION, VERSION}; /// 所有 pub async fn any_event(event: Event, payload: Payload, _client: Client, _status: Arc) { @@ -79,13 +80,23 @@ pub async fn on_message(payload: Payload, client: Client, _status: Arc `2.12.12` - 加入了 `STABLE` 信息, 用于标记稳定版本 +- 去掉了 `enable_py` 选项 + - 原来这玩意压根没读取过 ## 0.6.10