mirror of
http://shenjack.top:5100/shenjack/icalingua-python-bot.git
synced 2025-04-20 05:59:53 +08:00
Compare commits
3 Commits
8c52d898ff
...
5636c8e1d9
Author | SHA1 | Date | |
---|---|---|---|
5636c8e1d9 | |||
d12773981d | |||
4da93570c9 |
26
Cargo.lock
generated
26
Cargo.lock
generated
|
@ -675,8 +675,10 @@ dependencies = [
|
||||||
"rust_socketio",
|
"rust_socketio",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
"toml",
|
"toml",
|
||||||
|
"toml_edit",
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
]
|
]
|
||||||
|
@ -1390,9 +1392,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_spanned"
|
name = "serde_spanned"
|
||||||
version = "0.6.6"
|
version = "0.6.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "79e674e01f999af37c49f70a6ede167a8a60b2503e56c5599532a65baa5969a0"
|
checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
@ -1557,18 +1559,18 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror"
|
name = "thiserror"
|
||||||
version = "1.0.61"
|
version = "1.0.63"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709"
|
checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"thiserror-impl",
|
"thiserror-impl",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror-impl"
|
name = "thiserror-impl"
|
||||||
version = "1.0.61"
|
version = "1.0.63"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533"
|
checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
@ -1712,18 +1714,18 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml_datetime"
|
name = "toml_datetime"
|
||||||
version = "0.6.6"
|
version = "0.6.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf"
|
checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml_edit"
|
name = "toml_edit"
|
||||||
version = "0.22.13"
|
version = "0.22.20"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c127785850e8c20836d49732ae6abfa47616e60bf9d9f57c43c250361a9db96c"
|
checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"serde",
|
"serde",
|
||||||
|
@ -2196,9 +2198,9 @@ checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winnow"
|
name = "winnow"
|
||||||
version = "0.6.9"
|
version = "0.6.18"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "86c949fede1d13936a99f14fafd3e76fd642b556dd2ce96287fbe2e0151bfac6"
|
checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
|
@ -50,3 +50,5 @@ anyhow = { version = "1.0", features = ["backtrace"] }
|
||||||
# log
|
# log
|
||||||
tracing = "0.1.40"
|
tracing = "0.1.40"
|
||||||
tracing-subscriber = { version = "0.3.18", features = ["time"] }
|
tracing-subscriber = { version = "0.3.18", features = ["time"] }
|
||||||
|
thiserror = "1.0.63"
|
||||||
|
toml_edit = "0.22.20"
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
// use thiserror::Error;
|
||||||
|
|
||||||
pub type ClientResult<T, E> = Result<T, E>;
|
pub type ClientResult<T, E> = Result<T, E>;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|
|
@ -10,7 +10,8 @@ use crate::config::IcaConfig;
|
||||||
use crate::error::{ClientResult, IcaError};
|
use crate::error::{ClientResult, IcaError};
|
||||||
use crate::StopGetter;
|
use crate::StopGetter;
|
||||||
|
|
||||||
const ICA_PROTOCOL_VERSION: &str = "2.12.11";
|
/// icalingua 客户端的兼容版本号
|
||||||
|
pub const ICA_PROTOCOL_VERSION: &str = "2.12.12";
|
||||||
|
|
||||||
pub async fn start_ica(config: &IcaConfig, stop_reciver: StopGetter) -> ClientResult<(), IcaError> {
|
pub async fn start_ica(config: &IcaConfig, stop_reciver: StopGetter) -> ClientResult<(), IcaError> {
|
||||||
let span = span!(Level::INFO, "Icalingua Client");
|
let span = span!(Level::INFO, "Icalingua Client");
|
||||||
|
@ -80,7 +81,7 @@ pub async fn start_ica(config: &IcaConfig, stop_reciver: StopGetter) -> ClientRe
|
||||||
event!(Level::INFO, "socketio client stopped");
|
event!(Level::INFO, "socketio client stopped");
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
event!(Level::ERROR, "socketio client stopped with error: {:?}", inner_e);
|
event!(Level::ERROR, "socketio 客户端出现了 Error: {:?}", inner_e);
|
||||||
Err(IcaError::SocketIoError(
|
Err(IcaError::SocketIoError(
|
||||||
rust_socketio::Error::IncompleteResponseFromEngineIo(inner_e),
|
rust_socketio::Error::IncompleteResponseFromEngineIo(inner_e),
|
||||||
))
|
))
|
||||||
|
|
|
@ -7,7 +7,7 @@ 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;
|
use serde_json::Value;
|
||||||
use tracing::{debug, info, span, warn, Level};
|
use tracing::{debug, event, span, warn, Level};
|
||||||
|
|
||||||
/// "安全" 的 发送一条消息
|
/// "安全" 的 发送一条消息
|
||||||
pub async fn send_message(client: &Client, message: &SendMessage) -> bool {
|
pub async fn send_message(client: &Client, message: &SendMessage) -> bool {
|
||||||
|
@ -56,7 +56,12 @@ async fn inner_sign(payload: Payload, client: Client) -> ClientResult<(), IcaErr
|
||||||
|
|
||||||
let (auth_key, version) = (&require_data[0], &require_data[1]);
|
let (auth_key, version) = (&require_data[0], &require_data[1]);
|
||||||
|
|
||||||
info!("auth_key: {:?}, server_version: {:?}", auth_key, version);
|
event!(
|
||||||
|
Level::INFO,
|
||||||
|
"服务器发过来的待签名key: {:?}, 服务端版本号: {:?}",
|
||||||
|
auth_key,
|
||||||
|
version
|
||||||
|
);
|
||||||
// 判定和自己的兼容版本号是否 一致
|
// 判定和自己的兼容版本号是否 一致
|
||||||
let server_protocol_version = version
|
let server_protocol_version = version
|
||||||
.get("protocolVersion")
|
.get("protocolVersion")
|
||||||
|
@ -81,15 +86,15 @@ async fn inner_sign(payload: Payload, client: Client) -> ClientResult<(), IcaErr
|
||||||
let private_key = MainStatus::global_config().ica().private_key.clone();
|
let private_key = MainStatus::global_config().ica().private_key.clone();
|
||||||
|
|
||||||
let array_key: [u8; 32] = hex::decode(private_key)
|
let array_key: [u8; 32] = hex::decode(private_key)
|
||||||
.expect("Not a vaild pub key")
|
.expect("配置文件设置的私钥不是一个有效的私钥, 无法使用hex解析")
|
||||||
.try_into()
|
.try_into()
|
||||||
.expect("Not a vaild pub key");
|
.expect("配置文件设置的私钥不是一个有效的私钥, 无法转换为[u8; 32]数组");
|
||||||
let signing_key: SigningKey = SigningKey::from_bytes(&array_key);
|
let signing_key: SigningKey = SigningKey::from_bytes(&array_key);
|
||||||
let signature: Signature = signing_key.sign(salt.as_slice());
|
let signature: Signature = signing_key.sign(salt.as_slice());
|
||||||
|
|
||||||
// 发送签名
|
// 发送签名
|
||||||
let sign = signature.to_bytes().to_vec();
|
let sign = signature.to_bytes().to_vec();
|
||||||
client.emit("auth", sign).await.expect("Faild to send signin data");
|
client.emit("auth", sign).await.expect("发送签名信息失败");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,8 +25,6 @@ pub async fn get_online_data(payload: Payload, _client: Client) {
|
||||||
pub async fn add_message(payload: Payload, client: Client) {
|
pub async fn add_message(payload: Payload, client: Client) {
|
||||||
if let Payload::Text(values) = payload {
|
if let Payload::Text(values) = payload {
|
||||||
if let Some(value) = values.first() {
|
if let Some(value) = values.first() {
|
||||||
let span = span!(Level::INFO, "ica new_msg");
|
|
||||||
let _enter = span.enter();
|
|
||||||
let message: NewMessage = serde_json::from_value(value.clone()).unwrap();
|
let message: NewMessage = serde_json::from_value(value.clone()).unwrap();
|
||||||
// 检测是否在过滤列表内
|
// 检测是否在过滤列表内
|
||||||
if MainStatus::global_config().ica().filter_list.contains(&message.msg.sender_id) {
|
if MainStatus::global_config().ica().filter_list.contains(&message.msg.sender_id) {
|
||||||
|
@ -184,7 +182,7 @@ pub async fn connect_callback(payload: Payload, _client: Client) {
|
||||||
Some(msg) => {
|
Some(msg) => {
|
||||||
event!(Level::INFO, "{}{}", "未知消息".yellow(), msg);
|
event!(Level::INFO, "{}{}", "未知消息".yellow(), msg);
|
||||||
}
|
}
|
||||||
None => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,10 @@ pub const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||||
pub const ICA_VERSION: &str = "1.6.1";
|
pub const ICA_VERSION: &str = "1.6.1";
|
||||||
pub const TAILCHAT_VERSION: &str = "1.2.1";
|
pub const TAILCHAT_VERSION: &str = "1.2.1";
|
||||||
|
|
||||||
|
/// 是否为稳定版本
|
||||||
|
/// 会在 release 的时候设置为 true
|
||||||
|
pub const STABLE: bool = false;
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! async_callback_with_state {
|
macro_rules! async_callback_with_state {
|
||||||
($f:expr, $state:expr) => {{
|
($f:expr, $state:expr) => {{
|
||||||
|
@ -69,7 +73,10 @@ async fn main() {
|
||||||
let span = span!(Level::INFO, "Shenbot Main");
|
let span = span!(Level::INFO, "Shenbot Main");
|
||||||
let _enter = span.enter();
|
let _enter = span.enter();
|
||||||
|
|
||||||
event!(Level::INFO, "shenbot-async-rs v{} starting", VERSION);
|
event!(Level::INFO, "shenbot-rs v{} starting", VERSION);
|
||||||
|
if !STABLE {
|
||||||
|
event!(Level::WARN, "这是一个开发版本, 有问题记得找 shenjack");
|
||||||
|
}
|
||||||
|
|
||||||
let bot_config = BotConfig::new_from_cli();
|
let bot_config = BotConfig::new_from_cli();
|
||||||
MainStatus::static_init(bot_config);
|
MainStatus::static_init(bot_config);
|
||||||
|
|
|
@ -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 mut need_reload_files: Vec<PathBuf> = Vec::new();
|
||||||
let plugin_path = MainStatus::global_config().py().plugin_path.clone();
|
let plugin_path = MainStatus::global_config().py().plugin_path.clone();
|
||||||
|
|
||||||
|
@ -77,14 +77,20 @@ pub fn verify_plugins() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
info!("file change list: {:?}", need_reload_files);
|
info!("file change list: {:?}", need_reload_files);
|
||||||
|
let exist_plugins = PyStatus::get_map_mut();
|
||||||
for reload_file in need_reload_files {
|
for reload_file in need_reload_files {
|
||||||
match PyPlugin::new_from_path(&reload_file) {
|
if let Some(plugin) = exist_plugins.get_mut(&reload_file) {
|
||||||
Some(plugin) => {
|
plugin.reload_from_file();
|
||||||
PyStatus::add_file(reload_file.clone(), plugin);
|
event!(Level::INFO, "重载 Python 插件: {:?} 完成", reload_file);
|
||||||
info!("重载 Python 插件: {:?}", reload_file);
|
} else {
|
||||||
}
|
match PyPlugin::new_from_path(&reload_file) {
|
||||||
None => {
|
Some(plugin) => {
|
||||||
warn!("重载 Python 插件: {:?} 失败", reload_file);
|
PyStatus::add_file(reload_file.clone(), plugin);
|
||||||
|
info!("加载 Python 插件: {:?} 完成", reload_file);
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
warn!("加载 Python 插件: {:?} 失败", reload_file);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -133,9 +139,9 @@ macro_rules! call_py_func {
|
||||||
/// 执行 new message 的 python 插件
|
/// 执行 new message 的 python 插件
|
||||||
pub async fn ica_new_message_py(message: &ica::messages::NewMessage, client: &Client) {
|
pub async fn ica_new_message_py(message: &ica::messages::NewMessage, client: &Client) {
|
||||||
// 验证插件是否改变
|
// 验证插件是否改变
|
||||||
verify_plugins();
|
verify_and_reload_plugins();
|
||||||
|
|
||||||
let plugins = PyStatus::get_files();
|
let plugins = PyStatus::get_map();
|
||||||
for (path, plugin) in plugins.iter().filter(|(_, plugin)| plugin.enabled) {
|
for (path, plugin) in plugins.iter().filter(|(_, plugin)| plugin.enabled) {
|
||||||
let msg = class::ica::NewMessagePy::new(message);
|
let msg = class::ica::NewMessagePy::new(message);
|
||||||
let client = class::ica::IcaClientPy::new(client);
|
let client = class::ica::IcaClientPy::new(client);
|
||||||
|
@ -146,9 +152,9 @@ 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) {
|
pub async fn ica_delete_message_py(msg_id: ica::MessageId, client: &Client) {
|
||||||
verify_plugins();
|
verify_and_reload_plugins();
|
||||||
|
|
||||||
let plugins = PyStatus::get_files();
|
let plugins = PyStatus::get_map();
|
||||||
for (path, plugin) in plugins.iter().filter(|(_, plugin)| plugin.enabled) {
|
for (path, plugin) in plugins.iter().filter(|(_, plugin)| plugin.enabled) {
|
||||||
let msg_id = msg_id.clone();
|
let msg_id = msg_id.clone();
|
||||||
let client = class::ica::IcaClientPy::new(client);
|
let client = class::ica::IcaClientPy::new(client);
|
||||||
|
@ -161,9 +167,9 @@ pub async fn tailchat_new_message_py(
|
||||||
message: &tailchat::messages::ReceiveMessage,
|
message: &tailchat::messages::ReceiveMessage,
|
||||||
client: &Client,
|
client: &Client,
|
||||||
) {
|
) {
|
||||||
verify_plugins();
|
verify_and_reload_plugins();
|
||||||
|
|
||||||
let plugins = PyStatus::get_files();
|
let plugins = PyStatus::get_map();
|
||||||
for (path, plugin) in plugins.iter().filter(|(_, plugin)| plugin.enabled) {
|
for (path, plugin) in plugins.iter().filter(|(_, plugin)| plugin.enabled) {
|
||||||
let msg = class::tailchat::TailchatReceiveMessagePy::from_recive_message(message);
|
let msg = class::tailchat::TailchatReceiveMessagePy::from_recive_message(message);
|
||||||
let client = class::tailchat::TailchatClientPy::new(client);
|
let client = class::tailchat::TailchatClientPy::new(client);
|
||||||
|
|
|
@ -0,0 +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(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,18 +9,106 @@ use std::{collections::HashMap, path::PathBuf};
|
||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
use pyo3::prelude::*;
|
use pyo3::prelude::*;
|
||||||
use pyo3::types::PyTuple;
|
use pyo3::types::PyTuple;
|
||||||
use tracing::{debug, info, span, warn, Level};
|
use tracing::{event, info, span, warn, Level};
|
||||||
|
|
||||||
use crate::MainStatus;
|
use crate::MainStatus;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct PyStatus {
|
pub struct PyStatus {
|
||||||
pub files: Option<HashMap<PathBuf, PyPlugin>>,
|
pub files: Option<PyPlugins>,
|
||||||
|
pub config: Option<config::PluginConfigFile>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type PyPluginData = HashMap<PathBuf, PyPlugin>;
|
pub type PyPlugins = HashMap<PathBuf, PyPlugin>;
|
||||||
pub type RawPyPlugin = (PathBuf, Option<SystemTime>, String);
|
pub type RawPyPlugin = (PathBuf, Option<SystemTime>, String);
|
||||||
|
|
||||||
|
impl PyStatus {
|
||||||
|
pub fn init() {
|
||||||
|
unsafe {
|
||||||
|
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_map_mut().insert(path, plugin); }
|
||||||
|
|
||||||
|
pub fn verify_file(path: &PathBuf) -> bool {
|
||||||
|
Self::get_map().get(path).map_or(false, |plugin| plugin.verifiy())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_map() -> &'static PyPlugins {
|
||||||
|
unsafe {
|
||||||
|
match PYSTATUS.files.as_ref() {
|
||||||
|
Some(files) => files,
|
||||||
|
None => {
|
||||||
|
Self::init();
|
||||||
|
PYSTATUS.files.as_ref().unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_map_mut() -> &'static mut PyPlugins {
|
||||||
|
unsafe {
|
||||||
|
match PYSTATUS.files.as_mut() {
|
||||||
|
Some(files) => files,
|
||||||
|
None => {
|
||||||
|
Self::init();
|
||||||
|
PYSTATUS.files.as_mut().unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 插件 {{ {} }}",
|
||||||
|
map.iter()
|
||||||
|
.map(|(k, v)| format!("{:?}: {:?}", k, v))
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join(", ")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_py_err_traceback(py_err: &PyErr) -> String {
|
pub fn get_py_err_traceback(py_err: &PyErr) -> String {
|
||||||
Python::with_gil(|py| match py_err.traceback_bound(py) {
|
Python::with_gil(|py| match py_err.traceback_bound(py) {
|
||||||
Some(traceback) => match traceback.format() {
|
Some(traceback) => match traceback.format() {
|
||||||
|
@ -42,6 +130,7 @@ pub struct PyPlugin {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PyPlugin {
|
impl PyPlugin {
|
||||||
|
/// 从文件创建一个新的
|
||||||
pub fn new_from_path(path: &PathBuf) -> Option<Self> {
|
pub fn new_from_path(path: &PathBuf) -> Option<Self> {
|
||||||
let raw_file = load_py_file(path);
|
let raw_file = load_py_file(path);
|
||||||
match raw_file {
|
match raw_file {
|
||||||
|
@ -63,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 {
|
pub fn verifiy(&self) -> bool {
|
||||||
match get_change_time(&self.file_path) {
|
match get_change_time(&self.file_path) {
|
||||||
None => false,
|
None => false,
|
||||||
|
@ -127,29 +244,26 @@ impl TryFrom<RawPyPlugin> for PyPlugin {
|
||||||
}
|
}
|
||||||
let py_config = py_config.unwrap();
|
let py_config = py_config.unwrap();
|
||||||
// 先判定一下原来有没有
|
// 先判定一下原来有没有
|
||||||
match module.hasattr(CONFIG_DATA_NAME) {
|
if let Ok(true) = module.hasattr(CONFIG_DATA_NAME) {
|
||||||
Ok(true) => {
|
// get 过来, 后面直接覆盖, 这里用于发个警告
|
||||||
// get 过来, 后面直接覆盖, 这里用于发个警告
|
match module.getattr(CONFIG_DATA_NAME) {
|
||||||
match module.getattr(CONFIG_DATA_NAME) {
|
Ok(old_config) => {
|
||||||
Ok(old_config) => {
|
// 先判断是不是 None, 直接忽略掉 None
|
||||||
// 先判断是不是 None, 直接忽略掉 None
|
// 毕竟有可能有占位
|
||||||
// 毕竟有可能有占位
|
if !old_config.is_none() {
|
||||||
if !old_config.is_none() {
|
|
||||||
warn!(
|
|
||||||
"Python 插件 {:?} 的配置文件信息已经存在\n原始内容: {}",
|
|
||||||
path, old_config
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
warn!(
|
warn!(
|
||||||
"Python 插件 {:?} 的配置文件信息已经存在, 但获取失败:{:?}",
|
"Python 插件 {:?} 的配置文件信息已经存在\n原始内容: {}",
|
||||||
path, e
|
path, old_config
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Err(e) => {
|
||||||
|
warn!(
|
||||||
|
"Python 插件 {:?} 的配置文件信息已经存在, 但获取失败:{:?}",
|
||||||
|
path, e
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
match module.setattr(CONFIG_DATA_NAME, py_config) {
|
match module.setattr(CONFIG_DATA_NAME, py_config) {
|
||||||
Ok(()) => Ok(PyPlugin {
|
Ok(()) => Ok(PyPlugin {
|
||||||
|
@ -218,56 +332,18 @@ impl TryFrom<RawPyPlugin> for PyPlugin {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PyStatus {
|
pub static mut PYSTATUS: PyStatus = PyStatus {
|
||||||
pub fn get_files() -> &'static PyPluginData {
|
files: None,
|
||||||
unsafe {
|
config: None,
|
||||||
match PYSTATUS.files.as_ref() {
|
};
|
||||||
Some(files) => files,
|
|
||||||
None => {
|
|
||||||
PYSTATUS.files = Some(HashMap::new());
|
|
||||||
PYSTATUS.files.as_ref().unwrap()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_file(path: PathBuf, plugin: PyPlugin) {
|
|
||||||
unsafe {
|
|
||||||
match PYSTATUS.files.as_mut() {
|
|
||||||
Some(files) => {
|
|
||||||
files.insert(path, plugin);
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
let mut files: PyPluginData = HashMap::new();
|
|
||||||
files.insert(path, plugin);
|
|
||||||
PYSTATUS.files = Some(files);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn verify_file(path: &PathBuf) -> bool {
|
|
||||||
unsafe {
|
|
||||||
match PYSTATUS.files.as_ref() {
|
|
||||||
Some(files) => match files.get(path) {
|
|
||||||
Some(plugin) => plugin.verifiy(),
|
|
||||||
None => false,
|
|
||||||
},
|
|
||||||
None => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub static mut PYSTATUS: PyStatus = PyStatus { files: None };
|
|
||||||
|
|
||||||
pub fn load_py_plugins(path: &PathBuf) {
|
pub fn load_py_plugins(path: &PathBuf) {
|
||||||
if path.exists() {
|
if path.exists() {
|
||||||
info!("finding plugins in: {:?}", path);
|
event!(Level::INFO, "找到位于 {:?} 的插件", path);
|
||||||
// 搜索所有的 py 文件 和 文件夹单层下面的 py 文件
|
// 搜索所有的 py 文件 和 文件夹单层下面的 py 文件
|
||||||
match path.read_dir() {
|
match path.read_dir() {
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
warn!("failed to read plugin path: {:?}", e);
|
event!(Level::WARN, "failed to read plugin path: {:?}", e);
|
||||||
}
|
}
|
||||||
Ok(dir) => {
|
Ok(dir) => {
|
||||||
for entry in dir {
|
for entry in dir {
|
||||||
|
@ -284,12 +360,14 @@ pub fn load_py_plugins(path: &PathBuf) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
warn!("plugin path not exists: {:?}", path);
|
event!(Level::WARN, "插件加载目录不存在: {:?}", path);
|
||||||
}
|
}
|
||||||
info!(
|
PyStatus::get_config_mut().sync_status_from_config();
|
||||||
|
event!(
|
||||||
|
Level::INFO,
|
||||||
"python 插件目录: {:?} 加载完成, 加载到 {} 个插件",
|
"python 插件目录: {:?} 加载完成, 加载到 {} 个插件",
|
||||||
path,
|
path,
|
||||||
PyStatus::get_files().len()
|
PyStatus::get_map().len()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -320,17 +398,18 @@ pub fn load_py_file(path: &PathBuf) -> std::io::Result<RawPyPlugin> {
|
||||||
/// Python 侧初始化
|
/// Python 侧初始化
|
||||||
pub fn init_py() {
|
pub fn init_py() {
|
||||||
// 从 全局配置中获取 python 插件路径
|
// 从 全局配置中获取 python 插件路径
|
||||||
let span = span!(Level::INFO, "Init Python Plugin");
|
let span = span!(Level::INFO, "初始化 python 及其插件.ing");
|
||||||
let _enter = span.enter();
|
let _enter = span.enter();
|
||||||
|
|
||||||
let global_config = MainStatus::global_config().py();
|
let plugin_path = MainStatus::global_config().py().plugin_path;
|
||||||
|
|
||||||
debug!("initing python threads");
|
event!(Level::INFO, "正在初始化 python");
|
||||||
pyo3::prepare_freethreaded_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);
|
load_py_plugins(&plugin_path);
|
||||||
debug!("python 插件列表: {:#?}", PyStatus::get_files());
|
event!(Level::DEBUG, "python 插件列表: {}", PyStatus::display());
|
||||||
|
|
||||||
info!("python inited")
|
info!("python inited")
|
||||||
}
|
}
|
||||||
|
|
2
news.md
2
news.md
|
@ -6,6 +6,8 @@
|
||||||
- 现在会在插件加载时警告你的插件原来定义了 `CONFIG_DATA` 这一项
|
- 现在会在插件加载时警告你的插件原来定义了 `CONFIG_DATA` 这一项
|
||||||
- `IcaNewMessage` 添加了新的 api
|
- `IcaNewMessage` 添加了新的 api
|
||||||
- `get_sender_name` 获取发送人昵称
|
- `get_sender_name` 获取发送人昵称
|
||||||
|
- `ica` 兼容版本号 `2.12.11` -> `2.12.12`
|
||||||
|
- 加入了 `STABLE` 信息, 用于标记稳定版本
|
||||||
|
|
||||||
## 0.6.10
|
## 0.6.10
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user