能跑了吗?

This commit is contained in:
shenjack 2025-01-06 20:12:35 +08:00
parent 16fee092ba
commit fcf88f0ebb
Signed by: shenjack
GPG Key ID: 7B1134A979775551
7 changed files with 97 additions and 63 deletions

View File

@ -13,7 +13,7 @@ use crate::error::{ClientResult, IcaError};
use crate::{version_str, StopGetter}; use crate::{version_str, StopGetter};
/// icalingua 客户端的兼容版本号 /// icalingua 客户端的兼容版本号
pub const ICA_PROTOCOL_VERSION: &str = "2.12.24"; pub const ICA_PROTOCOL_VERSION: &str = "2.12.26";
mod status { mod status {
use crate::data_struct::ica::all_rooms::Room; use crate::data_struct::ica::all_rooms::Room;

View File

@ -7,7 +7,7 @@ use colored::Colorize;
use ed25519_dalek::{Signature, Signer, SigningKey}; use ed25519_dalek::{Signature, Signer, SigningKey};
use rust_socketio::asynchronous::Client; use rust_socketio::asynchronous::Client;
use rust_socketio::Payload; use rust_socketio::Payload;
use serde_json::{Value, json}; use serde_json::{json, Value};
use tracing::{debug, event, span, warn, Level}; use tracing::{debug, event, span, warn, Level};
/// "安全" 的 发送一条消息 /// "安全" 的 发送一条消息
@ -130,9 +130,7 @@ pub async fn send_room_sign_in(client: &Client, room_id: RoomId) -> bool {
/// 向某个群/私聊的某个人发送戳一戳 /// 向某个群/私聊的某个人发送戳一戳
pub async fn send_poke(client: &Client, room_id: RoomId, target: UserId) -> bool { pub async fn send_poke(client: &Client, room_id: RoomId, target: UserId) -> bool {
let data = json!([room_id, target]); let data = json!([room_id, target]);
match client.emit( match client.emit("sendGroupPoke", data).await {
"sendGroupPoke", data
).await {
Ok(_) => { Ok(_) => {
event!(Level::INFO, "已向 {} 的 {} 发送戳一戳", room_id, target); event!(Level::INFO, "已向 {} 的 {} 发送戳一戳", room_id, target);
true true

View File

@ -72,8 +72,7 @@ pub async fn add_message(payload: Payload, client: Client) {
if message.content().starts_with(&format!("/bot-enable-{}", client_id)) { if message.content().starts_with(&format!("/bot-enable-{}", client_id)) {
// 尝试获取后面的信息 // 尝试获取后面的信息
if let Some((_, name)) = message.content().split_once(" ") { if let Some((_, name)) = message.content().split_once(" ") {
let path_name = PathBuf::from(name); match py::PyStatus::get().get_status(name) {
match py::PyStatus::get().get_status(&path_name) {
None => { None => {
let reply = message.reply_with("未找到插件"); let reply = message.reply_with("未找到插件");
send_message(&client, &reply).await; send_message(&client, &reply).await;
@ -83,7 +82,7 @@ pub async fn add_message(payload: Payload, client: Client) {
send_message(&client, &reply).await; send_message(&client, &reply).await;
} }
Some(false) => { Some(false) => {
py::PyStatus::get_mut().set_status(&path_name, true); py::PyStatus::get_mut().set_status(name, true);
let reply = message.reply_with("启用插件完成"); let reply = message.reply_with("启用插件完成");
send_message(&client, &reply).await; send_message(&client, &reply).await;
} }
@ -92,8 +91,7 @@ pub async fn add_message(payload: Payload, client: Client) {
} else if message.content().starts_with(&format!("/bot-disable-{}", client_id)) } else if message.content().starts_with(&format!("/bot-disable-{}", client_id))
{ {
if let Some((_, name)) = message.content().split_once(" ") { if let Some((_, name)) = message.content().split_once(" ") {
let path_name = PathBuf::from(name); match py::PyStatus::get().get_status(name) {
match py::PyStatus::get().get_status(&path_name) {
None => { None => {
let reply = message.reply_with("未找到插件"); let reply = message.reply_with("未找到插件");
send_message(&client, &reply).await; send_message(&client, &reply).await;
@ -103,7 +101,7 @@ pub async fn add_message(payload: Payload, client: Client) {
send_message(&client, &reply).await; send_message(&client, &reply).await;
} }
Some(true) => { Some(true) => {
py::PyStatus::get_mut().set_status(&path_name, false); py::PyStatus::get_mut().set_status(name, false);
let reply = message.reply_with("禁用插件完成"); let reply = message.reply_with("禁用插件完成");
send_message(&client, &reply).await; send_message(&client, &reply).await;
} }

View File

@ -7,6 +7,7 @@ use toml_edit::{value, DocumentMut, Key, Table, TomlError, Value};
use tracing::{event, Level}; use tracing::{event, Level};
use crate::py::PyStatus; use crate::py::PyStatus;
use crate::MainStatus;
/// ```toml /// ```toml
/// # 这个文件是由 shenbot 自动生成的, 请 **谨慎** 修改 /// # 这个文件是由 shenbot 自动生成的, 请 **谨慎** 修改
@ -31,14 +32,37 @@ pub const DEFAULT_CONFIG: &str = r#"
impl PluginConfigFile { impl PluginConfigFile {
pub fn from_str(data: &str) -> Result<Self, TomlError> { pub fn from_str(data: &str) -> Result<Self, TomlError> {
let data = DocumentMut::from_str(data)?; let mut data = DocumentMut::from_str(data)?;
if data.get(CONFIG_KEY).is_none() {
event!(Level::WARN, "插件配置文件缺少 plugins 字段, 正在初始化");
data.insert_formatted(
&Key::from_str(CONFIG_KEY).unwrap(),
toml_edit::Item::Table(Table::new()),
);
}
Ok(Self { data }) Ok(Self { data })
} }
pub fn default_init() -> anyhow::Result<Self> {
let config_path = MainStatus::global_config().py().config_path.clone();
let path = Path::new(&config_path);
Self::from_config_path(&path)
}
pub fn reload_from_default(&mut self) {
let new_config = Self::default_init();
if let Err(e) = new_config {
event!(Level::ERROR, "从配置文件重加载时遇到错误: {}", e);
return;
}
let new_config = new_config.unwrap();
self.data = new_config.data;
}
pub fn from_config_path(path: &Path) -> anyhow::Result<Self> { pub fn from_config_path(path: &Path) -> anyhow::Result<Self> {
let config_path = path.join(CONFIG_FILE_NAME); let config_path = path.join(CONFIG_FILE_NAME);
if !config_path.exists() { if !config_path.exists() {
event!(Level::INFO, "插件配置文件不存在, 正在创建"); event!(Level::WARN, "插件配置文件不存在, 正在创建");
std::fs::write(&config_path, DEFAULT_CONFIG)?; std::fs::write(&config_path, DEFAULT_CONFIG)?;
Ok(Self::from_str(DEFAULT_CONFIG)?) Ok(Self::from_str(DEFAULT_CONFIG)?)
} else { } else {
@ -47,16 +71,6 @@ 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()),
);
}
}
fn get_table(&self) -> Option<&Table> { fn get_table(&self) -> Option<&Table> {
self.data.get(CONFIG_KEY).and_then(|item| item.as_table()) self.data.get(CONFIG_KEY).and_then(|item| item.as_table())
} }
@ -67,11 +81,10 @@ impl PluginConfigFile {
/// 获取插件状态 /// 获取插件状态
/// 默认为 true /// 默认为 true
pub fn get_status(&self, path: &Path) -> bool { pub fn get_status(&self, plugin_id: &str) -> bool {
let path_str = path.to_str().unwrap();
if let Some(item) = self.data.get(CONFIG_KEY) { if let Some(item) = self.data.get(CONFIG_KEY) {
if let Some(table) = item.as_table() { if let Some(table) = item.as_table() {
if let Some(item) = table.get(path_str) { if let Some(item) = table.get(plugin_id) {
if let Some(bool) = item.as_bool() { if let Some(bool) = item.as_bool() {
return bool; return bool;
} }
@ -100,7 +113,6 @@ impl PluginConfigFile {
/// 设置插件状态 /// 设置插件状态
pub fn set_status(&mut self, path: &Path, status: bool) { pub fn set_status(&mut self, path: &Path, status: bool) {
self.verify_and_init();
let path_str = path.to_str().unwrap(); let path_str = path.to_str().unwrap();
let table = self.data.get_mut(CONFIG_KEY).unwrap().as_table_mut().unwrap(); let table = self.data.get_mut(CONFIG_KEY).unwrap().as_table_mut().unwrap();
if table.contains_key(path_str) { if table.contains_key(path_str) {
@ -115,23 +127,31 @@ impl PluginConfigFile {
} }
} }
pub fn sync_status_from_config(&mut self) { pub fn read_status_from_file(&mut self) {
self.reload_from_default();
event!(Level::INFO, "同步插件状态");
let plugins = PyStatus::get_mut(); let plugins = PyStatus::get_mut();
self.verify_and_init();
plugins.files.iter_mut().for_each(|(path, status)| { plugins.files.iter_mut().for_each(|(path, status)| {
let config_status = self.get_status(path); let plugin_id = status.get_id();
event!(Level::INFO, "插件状态: {:?} {} -> {}", path, status.enabled, config_status); let config_status = self.get_status(&plugin_id);
event!(
Level::INFO,
"插件状态: {}({:?}) {} -> {}",
status.get_id(),
path,
status.enabled,
config_status
);
status.enabled = config_status; status.enabled = config_status;
}); });
} }
pub fn sync_status_to_config(&mut self) { pub fn sync_status_to_config(&mut self) {
let plugins = PyStatus::get(); let plugins = PyStatus::get();
self.verify_and_init();
let table = self.data.get_mut(CONFIG_KEY).unwrap().as_table_mut().unwrap(); let table = self.data.get_mut(CONFIG_KEY).unwrap().as_table_mut().unwrap();
table.clear(); table.clear();
plugins.files.iter().for_each(|(path, status)| { plugins.files.iter().for_each(|(_, status)| {
table.insert(path.to_str().unwrap(), value(status.enabled)); table.insert(&status.get_id(), value(status.enabled));
}); });
} }

View File

@ -29,10 +29,8 @@ static mut PyPluginStatus: OnceLock<PyStatus> = OnceLock::new();
impl PyStatus { impl PyStatus {
pub fn init() { pub fn init() {
let plugin_path = MainStatus::global_config().py().plugin_path.clone(); let config =
let mut config = config::PluginConfigFile::default_init().expect("初始化 Python 插件配置文件失败");
config::PluginConfigFile::from_config_path(&PathBuf::from(plugin_path)).unwrap();
config.verify_and_init();
let status = PyStatus { let status = PyStatus {
files: HashMap::new(), files: HashMap::new(),
config, config,
@ -44,37 +42,40 @@ impl PyStatus {
pub fn get_mut() -> &'static mut PyStatus { unsafe { PyPluginStatus.get_mut().unwrap() } } pub fn get_mut() -> &'static mut PyStatus { unsafe { PyPluginStatus.get_mut().unwrap() } }
/// 添加一个插件
pub fn add_file(&mut self, path: PathBuf, plugin: PyPlugin) { self.files.insert(path, plugin); } pub fn add_file(&mut self, path: PathBuf, plugin: PyPlugin) { self.files.insert(path, plugin); }
/// 删除一个插件 /// 删除一个插件
pub fn delete_file(&mut self, path: &PathBuf) -> Option<PyPlugin> { self.files.remove(path) } pub fn delete_file(&mut self, path: &PathBuf) -> Option<PyPlugin> { self.files.remove(path) }
pub fn get_status(&self, pluging_id: &str) -> Option<bool> {
self.files.iter().find_map(|(_, plugin)| {
if plugin.get_id() == pluging_id {
return Some(plugin.enabled);
}
None
})
}
pub fn set_status(&mut self, pluging_id: &str, status: bool) {
self.files.iter_mut().for_each(|(_, plugin)| {
if plugin.get_id() == pluging_id {
plugin.enabled = status;
}
});
}
pub fn verify_file(&self, path: &PathBuf) -> bool { pub fn verify_file(&self, path: &PathBuf) -> bool {
self.files.get(path).is_some_and(|plugin| plugin.verifiy()) self.files.get(path).is_some_and(|plugin| plugin.verifiy())
} }
/// 获取某个插件的状态
/// 以 config 优先
pub fn get_status(&self, path: &PathBuf) -> Option<bool> {
self.files.get(path).map(|plugin| plugin.enabled)
}
pub fn sync_status(&mut self) { self.config.sync_status_from_config(); }
pub fn set_status(&mut self, path: &Path, status: bool) {
self.config.set_status(path, status);
if let Some(plugin) = self.files.get_mut(path) {
plugin.enabled = status;
}
}
pub fn display() -> String { pub fn display() -> String {
format!( format!(
"Python 插件 {{ {} }}", "Python 插件 {{ {} }}",
Self::get() Self::get()
.files .files
.iter() .iter()
.map(|(k, v)| format!("{:?}-{}", k, v.enabled)) .map(|(_, v)| v.to_string())
.collect::<Vec<String>>() .collect::<Vec<String>>()
.join("\n") .join("\n")
) )
@ -133,7 +134,7 @@ impl PyPlugin {
Ok(plugin) => { Ok(plugin) => {
self.py_module = plugin.py_module; self.py_module = plugin.py_module;
self.changed_time = plugin.changed_time; self.changed_time = plugin.changed_time;
self.enabled = PyStatus::get().config.get_status(self.file_path.as_path()); self.enabled = PyStatus::get().config.get_status(&self.get_id());
event!(Level::INFO, "更新 Python 插件文件 {:?} 完成", self.file_path); event!(Level::INFO, "更新 Python 插件文件 {:?} 完成", self.file_path);
} }
Err(e) => { Err(e) => {
@ -164,6 +165,14 @@ impl PyPlugin {
} }
} }
} }
pub fn get_id(&self) -> String { plugin_path_as_id(&self.file_path) }
}
impl ToString for PyPlugin {
fn to_string(&self) -> String {
format!("{}({:?})-{}", self.get_id(), self.file_path, self.enabled)
}
} }
pub const CONFIG_DATA_NAME: &str = "CONFIG_DATA"; pub const CONFIG_DATA_NAME: &str = "CONFIG_DATA";
@ -304,6 +313,15 @@ impl TryFrom<RawPyPlugin> for PyPlugin {
} }
} }
/// 插件路径转换为 id
pub fn plugin_path_as_id(path: &PathBuf) -> String {
path.file_name()
.unwrap_or_default()
.to_str()
.unwrap_or("decode-failed")
.to_string()
}
pub fn load_py_plugins(path: &PathBuf) { pub fn load_py_plugins(path: &PathBuf) {
let plugins = PyStatus::get_mut(); let plugins = PyStatus::get_mut();
if path.exists() { if path.exists() {
@ -330,7 +348,7 @@ pub fn load_py_plugins(path: &PathBuf) {
} else { } else {
event!(Level::WARN, "插件加载目录不存在: {:?}", path); event!(Level::WARN, "插件加载目录不存在: {:?}", path);
} }
plugins.config.sync_status_from_config(); plugins.config.read_status_from_file();
event!( event!(
Level::INFO, Level::INFO,
"python 插件目录: {:?} 加载完成, 加载到 {} 个插件", "python 插件目录: {:?} 加载完成, 加载到 {} 个插件",

View File

@ -115,8 +115,7 @@ pub async fn on_message(payload: Payload, client: Client, _status: Arc<BotStatus
// 先判定是否为 admin // 先判定是否为 admin
// 尝试获取后面的信息 // 尝试获取后面的信息
if let Some((_, name)) = message.content.split_once(" ") { if let Some((_, name)) = message.content.split_once(" ") {
let path_name = PathBuf::from(name); match PyStatus::get().get_status(name) {
match PyStatus::get().get_status(&path_name) {
None => { None => {
let reply = message.reply_with("未找到插件"); let reply = message.reply_with("未找到插件");
send_message(&client, &reply).await; send_message(&client, &reply).await;
@ -126,7 +125,7 @@ pub async fn on_message(payload: Payload, client: Client, _status: Arc<BotStatus
send_message(&client, &reply).await; send_message(&client, &reply).await;
} }
Some(false) => { Some(false) => {
PyStatus::get_mut().set_status(&path_name, true); PyStatus::get_mut().set_status(name, true);
let reply = message.reply_with("启用插件完成"); let reply = message.reply_with("启用插件完成");
send_message(&client, &reply).await; send_message(&client, &reply).await;
} }
@ -134,8 +133,7 @@ pub async fn on_message(payload: Payload, client: Client, _status: Arc<BotStatus
} }
} else if message.content.starts_with(&format!("/bot-disable-{}", client_id)) { } else if message.content.starts_with(&format!("/bot-disable-{}", client_id)) {
if let Some((_, name)) = message.content.split_once(" ") { if let Some((_, name)) = message.content.split_once(" ") {
let path_name = PathBuf::from(name); match PyStatus::get().get_status(name) {
match PyStatus::get().get_status(&path_name) {
None => { None => {
let reply = message.reply_with("未找到插件"); let reply = message.reply_with("未找到插件");
send_message(&client, &reply).await; send_message(&client, &reply).await;
@ -145,7 +143,7 @@ pub async fn on_message(payload: Payload, client: Client, _status: Arc<BotStatus
send_message(&client, &reply).await; send_message(&client, &reply).await;
} }
Some(true) => { Some(true) => {
PyStatus::get_mut().set_status(&path_name, false); PyStatus::get_mut().set_status(name, false);
let reply = message.reply_with("禁用插件完成"); let reply = message.reply_with("禁用插件完成");
send_message(&client, &reply).await; send_message(&client, &reply).await;
} }

View File

@ -2,9 +2,10 @@
## 0.8.0 ## 0.8.0
- ica 兼容版本号更新到 `2.12.24` - ica 兼容版本号更新到 ~~`2.12.24`~~ `2.12.26`
- 从 `py::PyStatus` 开始进行一个 `static mut` -> `static mut OnceLock` 的改造 - 从 `py::PyStatus` 开始进行一个 `static mut` -> `static mut OnceLock` 的改造
- 用于看着更舒服(逃) - 用于看着更舒服(逃)
- 部分重构了一下 读取插件启用状态 的配置文件的代码
### ica 1.6.5 ### ica 1.6.5
@ -136,6 +137,7 @@
好耶! 好耶!
[!note] [!note]
```text ```text
notice_room = [] notice_room = []
notice_start = true notice_start = true