mirror of
http://shenjack.top:5100/shenjack/icalingua-python-bot.git
synced 2025-02-23 06:40:00 +08:00
疑似有点过于代码多了
This commit is contained in:
parent
d12773981d
commit
5636c8e1d9
17
Cargo.lock
generated
17
Cargo.lock
generated
|
@ -678,6 +678,7 @@ dependencies = [
|
|||
"thiserror",
|
||||
"tokio",
|
||||
"toml",
|
||||
"toml_edit",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
@ -1391,9 +1392,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "0.6.6"
|
||||
version = "0.6.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "79e674e01f999af37c49f70a6ede167a8a60b2503e56c5599532a65baa5969a0"
|
||||
checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
@ -1713,18 +1714,18 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.6.6"
|
||||
version = "0.6.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf"
|
||||
checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.22.13"
|
||||
version = "0.22.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c127785850e8c20836d49732ae6abfa47616e60bf9d9f57c43c250361a9db96c"
|
||||
checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde",
|
||||
|
@ -2197,9 +2198,9 @@ checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"
|
|||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.6.9"
|
||||
version = "0.6.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "86c949fede1d13936a99f14fafd3e76fd642b556dd2ce96287fbe2e0151bfac6"
|
||||
checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
|
|
@ -51,3 +51,4 @@ anyhow = { version = "1.0", features = ["backtrace"] }
|
|||
tracing = "0.1.40"
|
||||
tracing-subscriber = { version = "0.3.18", features = ["time"] }
|
||||
thiserror = "1.0.63"
|
||||
toml_edit = "0.22.20"
|
||||
|
|
|
@ -60,7 +60,7 @@ pub fn get_func<'py>(
|
|||
}
|
||||
}
|
||||
|
||||
pub fn verify_plugins() {
|
||||
pub fn verify_and_reload_plugins() {
|
||||
let mut need_reload_files: Vec<PathBuf> = Vec::new();
|
||||
let plugin_path = MainStatus::global_config().py().plugin_path.clone();
|
||||
|
||||
|
@ -77,14 +77,20 @@ pub fn verify_plugins() {
|
|||
return;
|
||||
}
|
||||
info!("file change list: {:?}", need_reload_files);
|
||||
let exist_plugins = PyStatus::get_map_mut();
|
||||
for reload_file in need_reload_files {
|
||||
match PyPlugin::new_from_path(&reload_file) {
|
||||
Some(plugin) => {
|
||||
PyStatus::add_file(reload_file.clone(), plugin);
|
||||
info!("重载 Python 插件: {:?}", reload_file);
|
||||
}
|
||||
None => {
|
||||
warn!("重载 Python 插件: {:?} 失败", reload_file);
|
||||
if let Some(plugin) = exist_plugins.get_mut(&reload_file) {
|
||||
plugin.reload_from_file();
|
||||
event!(Level::INFO, "重载 Python 插件: {:?} 完成", reload_file);
|
||||
} else {
|
||||
match PyPlugin::new_from_path(&reload_file) {
|
||||
Some(plugin) => {
|
||||
PyStatus::add_file(reload_file.clone(), plugin);
|
||||
info!("加载 Python 插件: {:?} 完成", reload_file);
|
||||
}
|
||||
None => {
|
||||
warn!("加载 Python 插件: {:?} 失败", reload_file);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -133,7 +139,7 @@ macro_rules! call_py_func {
|
|||
/// 执行 new message 的 python 插件
|
||||
pub async fn ica_new_message_py(message: &ica::messages::NewMessage, client: &Client) {
|
||||
// 验证插件是否改变
|
||||
verify_plugins();
|
||||
verify_and_reload_plugins();
|
||||
|
||||
let plugins = PyStatus::get_map();
|
||||
for (path, plugin) in plugins.iter().filter(|(_, plugin)| plugin.enabled) {
|
||||
|
@ -146,7 +152,7 @@ pub async fn ica_new_message_py(message: &ica::messages::NewMessage, client: &Cl
|
|||
}
|
||||
|
||||
pub async fn ica_delete_message_py(msg_id: ica::MessageId, client: &Client) {
|
||||
verify_plugins();
|
||||
verify_and_reload_plugins();
|
||||
|
||||
let plugins = PyStatus::get_map();
|
||||
for (path, plugin) in plugins.iter().filter(|(_, plugin)| plugin.enabled) {
|
||||
|
@ -161,7 +167,7 @@ pub async fn tailchat_new_message_py(
|
|||
message: &tailchat::messages::ReceiveMessage,
|
||||
client: &Client,
|
||||
) {
|
||||
verify_plugins();
|
||||
verify_and_reload_plugins();
|
||||
|
||||
let plugins = PyStatus::get_map();
|
||||
for (path, plugin) in plugins.iter().filter(|(_, plugin)| plugin.enabled) {
|
||||
|
|
|
@ -1 +1,109 @@
|
|||
use std::{path::{Path, PathBuf}, str::FromStr};
|
||||
|
||||
use toml_edit::{value, DocumentMut, Key, Table, TomlError, Value};
|
||||
|
||||
use crate::py::PyStatus;
|
||||
|
||||
/// ```toml
|
||||
/// # 这个文件是由 shenbot 自动生成的, 请 **谨慎** 修改
|
||||
/// # 请不要修改这个文件, 除非你知道你在做什么
|
||||
///
|
||||
/// [plugins]
|
||||
/// "xxxxxxx" = false # 被禁用的插件
|
||||
/// "xxxxxxx" = true # 被启用的插件
|
||||
/// ```
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PluginConfigFile {
|
||||
pub data: DocumentMut,
|
||||
}
|
||||
|
||||
const CONFIG_KEY: &str = "plugins";
|
||||
pub const CONFIG_FILE_NAME: &str = "/plugins.toml";
|
||||
pub const DEFAULT_CONFIG: &str = r#"
|
||||
# 这个文件是由 shenbot 自动生成的, 请 **谨慎** 修改
|
||||
# 请不要修改这个文件, 除非你知道你在做什么
|
||||
[plugins]
|
||||
"#;
|
||||
|
||||
impl PluginConfigFile {
|
||||
pub fn from_str(data: &str) -> Result<Self, TomlError> {
|
||||
let data = DocumentMut::from_str(data)?;
|
||||
Ok(Self { data })
|
||||
}
|
||||
|
||||
pub fn from_config_path(path: &Path) -> anyhow::Result<Self> {
|
||||
let config_path = path.join(CONFIG_FILE_NAME);
|
||||
if !config_path.exists() {
|
||||
std::fs::write(&config_path, DEFAULT_CONFIG)?;
|
||||
Ok(Self::from_str(DEFAULT_CONFIG)?)
|
||||
} else {
|
||||
let data = std::fs::read_to_string(&config_path)?;
|
||||
Ok(Self::from_str(&data)?)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn verify_and_init(&mut self) {
|
||||
if self.data.get(CONFIG_KEY).is_none() {
|
||||
self.data.insert_formatted(
|
||||
&Key::from_str(CONFIG_KEY).unwrap(),
|
||||
toml_edit::Item::Table(Table::new()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 获取插件状态
|
||||
/// 默认为 true
|
||||
pub fn get_status(&self, path: &Path) -> bool {
|
||||
let path_str = path.to_str().unwrap();
|
||||
if let Some(item) = self.data.get(CONFIG_KEY) {
|
||||
if let Some(table) = item.as_table() {
|
||||
if let Some(item) = table.get(path_str) {
|
||||
if let Some(bool) = item.as_bool() {
|
||||
return bool;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
/// 设置插件状态
|
||||
pub fn set_status(&mut self, path: &Path, status: bool) {
|
||||
self.verify_and_init();
|
||||
let path_str = path.to_str().unwrap();
|
||||
let table = self.data.get_mut(CONFIG_KEY).unwrap().as_table_mut().unwrap();
|
||||
if table.contains_key(path_str) {
|
||||
match table.get_mut(path_str).unwrap().as_value_mut() {
|
||||
Some(value) => *value = Value::from(status),
|
||||
None => {
|
||||
table.insert(path_str, value(status));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
table.insert(path_str, value(status));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sync_status_from_config(&mut self) {
|
||||
let plugins = PyStatus::get_map_mut();
|
||||
self.verify_and_init();
|
||||
plugins.iter_mut().for_each(|(path, status)| {
|
||||
status.enabled = self.get_status(path);
|
||||
});
|
||||
}
|
||||
|
||||
pub fn sync_status_to_config(&mut self) {
|
||||
let plugins = PyStatus::get_map();
|
||||
self.verify_and_init();
|
||||
let table = self.data.get_mut(CONFIG_KEY).unwrap().as_table_mut().unwrap();
|
||||
table.clear();
|
||||
plugins.iter().for_each(|(path, status)| {
|
||||
table.insert(path.to_str().unwrap(), value(status.enabled));
|
||||
});
|
||||
}
|
||||
|
||||
pub fn write_to_file(&self, path: &PathBuf) -> Result<(), std::io::Error> {
|
||||
std::fs::write(path, self.data.to_string())?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@ pub mod call;
|
|||
pub mod class;
|
||||
pub mod config;
|
||||
|
||||
use std::fmt::Display;
|
||||
use std::path::Path;
|
||||
use std::time::SystemTime;
|
||||
use std::{collections::HashMap, path::PathBuf};
|
||||
|
@ -10,13 +9,14 @@ use std::{collections::HashMap, path::PathBuf};
|
|||
use colored::Colorize;
|
||||
use pyo3::prelude::*;
|
||||
use pyo3::types::PyTuple;
|
||||
use tracing::{debug, event, info, span, warn, Level};
|
||||
use tracing::{event, info, span, warn, Level};
|
||||
|
||||
use crate::MainStatus;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PyStatus {
|
||||
pub files: Option<PyPlugins>,
|
||||
pub config: Option<config::PluginConfigFile>,
|
||||
}
|
||||
|
||||
pub type PyPlugins = HashMap<PathBuf, PyPlugin>;
|
||||
|
@ -28,16 +28,24 @@ impl PyStatus {
|
|||
if PYSTATUS.files.is_none() {
|
||||
PYSTATUS.files = Some(HashMap::new());
|
||||
}
|
||||
if PYSTATUS.config.is_none() {
|
||||
let plugin_path = MainStatus::global_config().py().plugin_path;
|
||||
let mut config =
|
||||
config::PluginConfigFile::from_config_path(&PathBuf::from(plugin_path))
|
||||
.unwrap();
|
||||
config.verify_and_init();
|
||||
PYSTATUS.config = Some(config);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_file(path: PathBuf, plugin: PyPlugin) { Self::get_mut_map().insert(path, plugin); }
|
||||
pub fn add_file(path: PathBuf, plugin: PyPlugin) { Self::get_map_mut().insert(path, plugin); }
|
||||
|
||||
pub fn verify_file(path: &PathBuf) -> bool {
|
||||
Self::get_map().get(path).map_or(false, |plugin| plugin.verifiy())
|
||||
}
|
||||
|
||||
fn get_map() -> &'static PyPlugins {
|
||||
pub fn get_map() -> &'static PyPlugins {
|
||||
unsafe {
|
||||
match PYSTATUS.files.as_ref() {
|
||||
Some(files) => files,
|
||||
|
@ -49,7 +57,7 @@ impl PyStatus {
|
|||
}
|
||||
}
|
||||
|
||||
fn get_mut_map() -> &'static mut PyPlugins {
|
||||
pub fn get_map_mut() -> &'static mut PyPlugins {
|
||||
unsafe {
|
||||
match PYSTATUS.files.as_mut() {
|
||||
Some(files) => files,
|
||||
|
@ -61,11 +69,43 @@ impl PyStatus {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn list_plugins() -> Vec<PathBuf> { Self::get_map().keys().cloned().collect() }
|
||||
pub fn get_config() -> &'static config::PluginConfigFile {
|
||||
unsafe {
|
||||
match PYSTATUS.config.as_ref() {
|
||||
Some(config) => config,
|
||||
None => {
|
||||
Self::init();
|
||||
PYSTATUS.config.as_ref().unwrap()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_config_mut() -> &'static mut config::PluginConfigFile {
|
||||
unsafe {
|
||||
match PYSTATUS.config.as_mut() {
|
||||
Some(config) => config,
|
||||
None => {
|
||||
Self::init();
|
||||
PYSTATUS.config.as_mut().unwrap()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_status(path: &Path) -> bool { Self::get_config().get_status(path) }
|
||||
|
||||
// pub fn list_plugins() -> Vec<PathBuf> { Self::get_map().keys().cloned().collect() }
|
||||
|
||||
pub fn display() -> String {
|
||||
let map = Self::get_map();
|
||||
format!("Python 插件 {{ {} }}", (",".join(map.keys().map(|x| x.to_string()))))
|
||||
format!(
|
||||
"Python 插件 {{ {} }}",
|
||||
map.iter()
|
||||
.map(|(k, v)| format!("{:?}: {:?}", k, v))
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -90,6 +130,7 @@ pub struct PyPlugin {
|
|||
}
|
||||
|
||||
impl PyPlugin {
|
||||
/// 从文件创建一个新的
|
||||
pub fn new_from_path(path: &PathBuf) -> Option<Self> {
|
||||
let raw_file = load_py_file(path);
|
||||
match raw_file {
|
||||
|
@ -111,6 +152,34 @@ impl PyPlugin {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 从文件更新
|
||||
pub fn reload_from_file(&mut self) {
|
||||
let raw_file = load_py_file(&self.file_path);
|
||||
match raw_file {
|
||||
Ok(raw_file) => match Self::try_from(raw_file) {
|
||||
Ok(plugin) => {
|
||||
self.py_module = plugin.py_module;
|
||||
self.changed_time = plugin.changed_time;
|
||||
self.enabled = PyStatus::get_status(self.file_path.as_path());
|
||||
event!(Level::INFO, "更新 Python 插件文件 {:?} 完成", self.file_path);
|
||||
}
|
||||
Err(e) => {
|
||||
warn!(
|
||||
"更新 Python 插件文件{:?}: {:?} 失败\n{}",
|
||||
self.file_path,
|
||||
e,
|
||||
get_py_err_traceback(&e)
|
||||
);
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
warn!("更新插件 {:?}: {:?} 失败", self.file_path, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 检查文件是否被修改
|
||||
pub fn verifiy(&self) -> bool {
|
||||
match get_change_time(&self.file_path) {
|
||||
None => false,
|
||||
|
@ -175,29 +244,26 @@ impl TryFrom<RawPyPlugin> for PyPlugin {
|
|||
}
|
||||
let py_config = py_config.unwrap();
|
||||
// 先判定一下原来有没有
|
||||
match module.hasattr(CONFIG_DATA_NAME) {
|
||||
Ok(true) => {
|
||||
// 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) => {
|
||||
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 插件 {:?} 的配置文件信息已经存在, 但获取失败:{:?}",
|
||||
path, e
|
||||
"Python 插件 {:?} 的配置文件信息已经存在\n原始内容: {}",
|
||||
path, old_config
|
||||
);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
warn!(
|
||||
"Python 插件 {:?} 的配置文件信息已经存在, 但获取失败:{:?}",
|
||||
path, e
|
||||
);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
match module.setattr(CONFIG_DATA_NAME, py_config) {
|
||||
Ok(()) => Ok(PyPlugin {
|
||||
|
@ -266,11 +332,14 @@ impl TryFrom<RawPyPlugin> for PyPlugin {
|
|||
}
|
||||
}
|
||||
|
||||
pub static mut PYSTATUS: PyStatus = PyStatus { files: None };
|
||||
pub static mut PYSTATUS: PyStatus = PyStatus {
|
||||
files: None,
|
||||
config: None,
|
||||
};
|
||||
|
||||
pub fn load_py_plugins(path: &PathBuf) {
|
||||
if path.exists() {
|
||||
event!(Level::INFO, "finding plugins in: {:?}", path);
|
||||
event!(Level::INFO, "找到位于 {:?} 的插件", path);
|
||||
// 搜索所有的 py 文件 和 文件夹单层下面的 py 文件
|
||||
match path.read_dir() {
|
||||
Err(e) => {
|
||||
|
@ -293,6 +362,7 @@ pub fn load_py_plugins(path: &PathBuf) {
|
|||
} else {
|
||||
event!(Level::WARN, "插件加载目录不存在: {:?}", path);
|
||||
}
|
||||
PyStatus::get_config_mut().sync_status_from_config();
|
||||
event!(
|
||||
Level::INFO,
|
||||
"python 插件目录: {:?} 加载完成, 加载到 {} 个插件",
|
||||
|
@ -328,15 +398,16 @@ pub fn load_py_file(path: &PathBuf) -> std::io::Result<RawPyPlugin> {
|
|||
/// Python 侧初始化
|
||||
pub fn init_py() {
|
||||
// 从 全局配置中获取 python 插件路径
|
||||
let span = span!(Level::INFO, "Init Python Plugin");
|
||||
let span = span!(Level::INFO, "初始化 python 及其插件.ing");
|
||||
let _enter = span.enter();
|
||||
|
||||
let global_config = MainStatus::global_config().py();
|
||||
let plugin_path = MainStatus::global_config().py().plugin_path;
|
||||
|
||||
event!(Level::INFO, "正在初始化 python");
|
||||
pyo3::prepare_freethreaded_python();
|
||||
|
||||
let plugin_path = PathBuf::from(global_config.plugin_path);
|
||||
PyStatus::init();
|
||||
let plugin_path = PathBuf::from(plugin_path);
|
||||
load_py_plugins(&plugin_path);
|
||||
event!(Level::DEBUG, "python 插件列表: {}", PyStatus::display());
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user