From 65b8e92ce14ccfe11c82d57ff2ebc293c819b686 Mon Sep 17 00:00:00 2001 From: shenjack <3695888@qq.com> Date: Wed, 12 Feb 2025 13:43:14 +0800 Subject: [PATCH] 2.0.0 --- ica-rs/src/main.rs | 4 +- ica-rs/src/py/mod.rs | 246 ++++++++++++++++++++++++++----------------- news.md | 14 +++ 3 files changed, 163 insertions(+), 101 deletions(-) diff --git a/ica-rs/src/main.rs b/ica-rs/src/main.rs index fb0d828..6d38423 100644 --- a/ica-rs/src/main.rs +++ b/ica-rs/src/main.rs @@ -29,8 +29,8 @@ pub type MainStatus = status::BotStatus; pub type StopGetter = tokio::sync::oneshot::Receiver<()>; pub const VERSION: &str = env!("CARGO_PKG_VERSION"); -pub const ICA_VERSION: &str = "1.6.7"; -pub const TAILCHAT_VERSION: &str = "1.2.6"; +pub const ICA_VERSION: &str = "2.0.0"; +pub const TAILCHAT_VERSION: &str = "2.0.0"; const HELP_MSG: &str = r#"/bot-rs 展示 rust 侧信息 diff --git a/ica-rs/src/py/mod.rs b/ica-rs/src/py/mod.rs index 8725aa3..3dc6874 100644 --- a/ica-rs/src/py/mod.rs +++ b/ica-rs/src/py/mod.rs @@ -10,12 +10,16 @@ use std::time::SystemTime; use std::{collections::HashMap, path::PathBuf}; use colored::Colorize; -use pyo3::prelude::*; +use pyo3::exceptions::PyTypeError; use pyo3::types::PyTuple; +use pyo3::{intern, prelude::*}; use tracing::{event, span, warn, Level}; use crate::MainStatus; +const REQUIRE_CONFIG_FUNC_NAME: &str = "require_config"; +const ON_CONFIG_FUNC_NAME: &str = "on_config"; + #[derive(Debug, Clone)] pub struct PyStatus { pub files: PyPlugins, @@ -198,11 +202,125 @@ impl Display for PyPlugin { pub const CONFIG_DATA_NAME: &str = "CONFIG_DATA"; +fn set_str_cfg_default_plugin( + module: &Bound<'_, PyModule>, + default: String, + path: String, +) -> PyResult<()> { + let base_path = MainStatus::global_config().py().config_path; + + let mut base_path: PathBuf = PathBuf::from(base_path); + + if !base_path.exists() { + event!(Level::WARN, "python 插件路径不存在, 创建: {:?}", base_path); + std::fs::create_dir_all(&base_path)?; + } + base_path.push(&path); + + let config_str: String = if base_path.exists() { + event!(Level::INFO, "加载 {:?} 的配置文件 {:?} 中", path, base_path); + match std::fs::read_to_string(&base_path) { + Ok(v) => v, + Err(e) => { + event!(Level::WARN, "配置文件 {:?} 读取失败 {}, 创建默认配置", base_path, e); + // 写入默认配置 + std::fs::write(&base_path, &default)?; + default + } + } + } else { + event!(Level::WARN, "配置文件 {:?} 不存在, 创建默认配置", base_path); + // 写入默认配置 + std::fs::write(base_path, &default)?; + default + }; + + if let Err(e) = module.setattr(intern!(module.py(), CONFIG_DATA_NAME), &config_str) { + event!(Level::WARN, "Python 插件 {:?} 的配置文件信息设置失败:{:?}", path, e); + return Err(PyTypeError::new_err(format!( + "Python 插件 {:?} 的配置文件信息设置失败:{:?}", + path, e + ))); + } + + // 给到 on config + if let Ok(attr) = module.getattr(intern!(module.py(), ON_CONFIG_FUNC_NAME)) { + if !attr.is_callable() { + event!(Level::WARN, "Python 插件 {:?} 的 {} 函数不是 Callable", path, ON_CONFIG_FUNC_NAME); + return Ok(()); + } + let args = (config_str.as_bytes(), ); + if let Err(e) = attr.call1(args) { + event!(Level::WARN, "Python 插件 {:?} 的 {} 函数返回了一个报错 {}", path, ON_CONFIG_FUNC_NAME, e); + } + } + + Ok(()) +} + +fn set_bytes_cfg_default_plugin( + module: &Bound<'_, PyModule>, + default: Vec, + path: String, +) -> PyResult<()> { + let base_path = MainStatus::global_config().py().config_path; + + let mut base_path: PathBuf = PathBuf::from(base_path); + + if !base_path.exists() { + event!(Level::WARN, "python 插件路径不存在, 创建: {:?}", base_path); + std::fs::create_dir_all(&base_path)?; + } + base_path.push(&path); + + let config_vec: Vec = if base_path.exists() { + event!(Level::INFO, "加载 {:?} 的配置文件 {:?} 中", path, base_path); + match std::fs::read(&base_path) { + Ok(v) => v, + Err(e) => { + event!(Level::WARN, "配置文件 {:?} 读取失败 {}, 创建默认配置", base_path, e); + // 写入默认配置 + std::fs::write(&base_path, &default)?; + default + } + } + } else { + event!(Level::WARN, "配置文件 {:?} 不存在, 创建默认配置", base_path); + // 写入默认配置 + std::fs::write(base_path, &default)?; + default + }; + + match module.setattr(intern!(module.py(), CONFIG_DATA_NAME), &config_vec) { + Ok(()) => (), + Err(e) => { + warn!("Python 插件 {:?} 的配置文件信息设置失败:{:?}", path, e); + return Err(PyTypeError::new_err(format!( + "Python 插件 {:?} 的配置文件信息设置失败:{:?}", + path, e + ))); + } + } + + // 给到 on config + if let Ok(attr) = module.getattr(intern!(module.py(), ON_CONFIG_FUNC_NAME)) { + if !attr.is_callable() { + event!(Level::WARN, "Python 插件 {:?} 的 {} 函数不是 Callable", path, ON_CONFIG_FUNC_NAME); + return Ok(()); + } + let args = (&config_vec, ); + if let Err(e) = attr.call1(args) { + event!(Level::WARN, "Python 插件 {:?} 的 {} 函数返回了一个报错 {}", path, ON_CONFIG_FUNC_NAME, e); + } + } + Ok(()) +} + impl TryFrom for PyPlugin { type Error = PyErr; fn try_from(value: RawPyPlugin) -> Result { let (path, changed_time, content) = value; - let py_module = match py_module_from_code(&content, &path) { + let py_module: Py = match py_module_from_code(&content, &path) { Ok(module) => module, Err(e) => { warn!("加载 Python 插件: {:?} 失败", e); @@ -211,109 +329,39 @@ impl TryFrom for PyPlugin { }; Python::with_gil(|py| { let module = py_module.bind(py); - if let Ok(config_func) = call::get_func(module, "on_config") { + if let Ok(config_func) = call::get_func(module, REQUIRE_CONFIG_FUNC_NAME) { match config_func.call0() { Ok(config) => { if config.is_instance_of::() { - let (config, default) = config.extract::<(String, String)>().unwrap(); - let base_path = MainStatus::global_config().py().config_path; - - let mut base_path: PathBuf = PathBuf::from(base_path); - - if !base_path.exists() { - event!(Level::WARN, "python 插件路径不存在, 创建: {:?}", base_path); - std::fs::create_dir_all(&base_path)?; - } - base_path.push(&config); - - let config_value = if base_path.exists() { - event!( - Level::INFO, - "加载 {:?} 的配置文件 {:?} 中", - path, - base_path - ); - let content = std::fs::read_to_string(&base_path)?; - toml::from_str(&content) + // let (config, default) = config.extract::<(String, Vec)>().unwrap(); + // let (config, default) = config.extract::<(String, String)>().unwrap(); + if let Ok((config, default)) = config.extract::<(String, String)>() { + set_str_cfg_default_plugin(module, default, config)?; + } else if let Ok((config, default)) = + config.extract::<(String, Vec)>() + { + set_bytes_cfg_default_plugin(module, default, config)?; } else { - event!( - Level::WARN, - "配置文件 {:?} 不存在, 创建默认配置", - base_path + warn!( + "加载 Python 插件 {:?} 的配置文件信息时失败:返回的不是 [str, bytes | str]", + path ); - // 写入默认配置 - std::fs::write(base_path, &default)?; - toml::from_str(&default) - }; - match config_value { - Ok(config) => { - let py_config = - Bound::new(py, class::ConfigDataPy::new(config)); - if let Err(e) = py_config { - event!(Level::WARN, "添加配置文件信息失败: {:?}", e); - return Err(e); - } - let py_config = py_config.unwrap(); - // 先判定一下原来有没有 - if let Ok(true) = module.hasattr(CONFIG_DATA_NAME) { - // get 过来, 后面直接覆盖, 这里用于发个警告 - match module.getattr(CONFIG_DATA_NAME) { - Ok(old_config) => { - // 先判断是不是 None, 直接忽略掉 None - // 毕竟有可能有占位 - if !old_config.is_none() { - warn!( - "Python 插件 {:?} 的配置文件信息已经存在\n原始内容: {}", - path, old_config - ); - } - } - Err(e) => { - warn!( - "Python 插件 {:?} 的配置文件信息已经存在, 但获取失败:{:?}", - path, e - ); - } - } - } - match module.setattr(CONFIG_DATA_NAME, py_config) { - Ok(()) => Ok(PyPlugin { - file_path: path, - changed_time, - py_module: module.into_py(py), - enabled: true, - }), - Err(e) => { - warn!( - "Python 插件 {:?} 的配置文件信息设置失败:{:?}", - path, e - ); - Err(PyErr::new::( - format!( - "Python 插件 {:?} 的配置文件信息设置失败:{:?}", - path, e - ), - )) - } - } - } - Err(e) => { - warn!( - "加载 Python 插件 {:?} 的配置文件信息时失败:{:?}", - path, e - ); - Err(PyErr::new::(format!( - "加载 Python 插件 {:?} 的配置文件信息时失败:{:?}", - path, e - ))) - } + return Err(PyErr::new::( + "返回的不是 [str, bytes | str]".to_string(), + )); } + Ok(PyPlugin { + file_path: path, + changed_time, + py_module: module.clone().into_any().unbind(), + enabled: true, + }) } else if config.is_none() { // 没有配置文件 Ok(PyPlugin { file_path: path, changed_time, - py_module: module.into_py(py), + py_module: module.clone().into_any().unbind(), enabled: true, }) } else { @@ -335,7 +383,7 @@ impl TryFrom for PyPlugin { Ok(PyPlugin { file_path: path, changed_time, - py_module: module.into_py(py), + py_module: module.clone().into_any().unbind(), enabled: true, }) } @@ -390,9 +438,9 @@ pub fn load_py_plugins(path: &PathBuf) { pub fn get_change_time(path: &Path) -> Option { path.metadata().ok()?.modified().ok() } -pub fn py_module_from_code(content: &str, path: &Path) -> PyResult> { - Python::with_gil(|py| -> PyResult> { - let module: PyResult> = PyModule::from_code( +pub fn py_module_from_code(content: &str, path: &Path) -> PyResult> { + Python::with_gil(|py| -> PyResult> { + let module = PyModule::from_code( py, CString::new(content).unwrap().as_c_str(), CString::new(path.to_string_lossy().as_bytes()).unwrap().as_c_str(), @@ -401,7 +449,7 @@ pub fn py_module_from_code(content: &str, path: &Path) -> PyResult> { .as_c_str(), // !!!! 请注意, 一定要给他一个名字, cpython 会自动把后面的重名模块覆盖掉前面的 ) - .map(|module| module.into()); + .map(|module| module.unbind()); module }) } diff --git a/news.md b/news.md index e3b2c7c..edb5018 100644 --- a/news.md +++ b/news.md @@ -17,6 +17,20 @@ - 改进了一下 ica 的新消息显示 - 添加了 ica 链接用时的显示 +### ica&tailchat 2.0.0 + +- BREAKING CHANGE + - 现在 `CONFIG_DATA` 为一个 `str | bytes` + - 用于存储插件的配置信息 + - 需要自行解析 + - 现在 `on_config` 函数签名为 `on_config = Callable[[bytes], None]` + - 用于接收配置信息 + - 现在使用 `require_config = Callable[[None], str, bytes | str]` 函数来请求配置信息 + - 用于请求配置信息 + - `str` 为配置文件名 + - `bytes | str` 为配置文件默认内容 + - 以 `bytes` 形式或者 `str` 形式均可 + ### ica 1.6.7 - 为 `IcaClinet` 添加了 `py_tasks_count -> int` 属性