diff --git a/ica-rs/Cargo.toml b/ica-rs/Cargo.toml index fe0bdeb..27efa33 100644 --- a/ica-rs/Cargo.toml +++ b/ica-rs/Cargo.toml @@ -9,6 +9,7 @@ edition = "2021" ed25519 = "2.2.3" ed25519-dalek = "2.1.1" hex = "0.4.3" +blake3 = "1.5.0" rust_socketio = "0.4.4" serde = { version = "1.0", features = ["derive"] } diff --git a/ica-rs/ica_typing.py b/ica-rs/ica_typing.py new file mode 100644 index 0000000..edf3400 --- /dev/null +++ b/ica-rs/ica_typing.py @@ -0,0 +1,45 @@ +# Python 兼容版本 3.8+ + +from typing import Optional + + +class IcaStatus: + @property + def login(self) -> bool: + ... + @property + def online(self) -> bool: + ... + @property + def self_id(self) -> Optional[bool]: + ... + @property + def nick_name(self) -> Optional[str]: + ... + @property + def ica_version(self) -> Optional[str]: + ... + @property + def os_info(self) -> Optional[str]: + ... + @property + def resident_set_size(self) -> Optional[str]: + ... + @property + def head_used(self) -> Optional[str]: + ... + @property + def load_average(self) -> Optional[str]: + ... + + +class NewMessage: + ... + + +class ReplyMessage: + ... + + +class SendMessage: + ... diff --git a/ica-rs/plugins/test.py b/ica-rs/plugins/test.py new file mode 100644 index 0000000..e69de29 diff --git a/ica-rs/src/config.rs b/ica-rs/src/config.rs index 10614b1..af6f02e 100644 --- a/ica-rs/src/config.rs +++ b/ica-rs/src/config.rs @@ -20,7 +20,7 @@ pub struct IcaConfig { /// 管理员列表 pub admin_list: Vec, /// Python 插件路径 - pub py_plugin_path: String, + pub py_plugin_path: Option, } impl IcaConfig { diff --git a/ica-rs/src/main.rs b/ica-rs/src/main.rs index 7ae804b..8bd6966 100644 --- a/ica-rs/src/main.rs +++ b/ica-rs/src/main.rs @@ -21,7 +21,6 @@ fn main() { tracing_subscriber::fmt() .with_max_level(tracing::Level::DEBUG) .init(); - py::init_py(); // 从命令行获取 host 和 key // 从命令行获取配置文件路径 @@ -29,6 +28,7 @@ fn main() { unsafe { ClientStatus.update_config(ica_config.clone()); } + py::init_py(&ica_config); let ica_singer = client::IcalinguaSinger::new_from_config(&ica_config); let socket = ClientBuilder::new(ica_singer.host.clone()) diff --git a/ica-rs/src/py/class.rs b/ica-rs/src/py/class.rs index 3464455..b68f335 100644 --- a/ica-rs/src/py/class.rs +++ b/ica-rs/src/py/class.rs @@ -19,6 +19,16 @@ impl IcaStatusPy { unsafe { ClientStatus.login } } + #[getter] + pub fn get_online(&self) -> bool { + unsafe { + match ClientStatus.online_data.as_ref() { + Some(data) => data.online, + None => false, + } + } + } + #[getter] pub fn get_self_id(&self) -> Option { unsafe { @@ -30,7 +40,7 @@ impl IcaStatusPy { } #[getter] - pub fn get_nicn_name(&self) -> Option { + pub fn get_nick_name(&self) -> Option { unsafe { match ClientStatus.online_data.as_ref() { Some(data) => Some(data.nick.clone()), @@ -39,16 +49,6 @@ impl IcaStatusPy { } } - #[getter] - pub fn get_online(&self) -> bool { - unsafe { - match ClientStatus.online_data.as_ref() { - Some(data) => data.online, - None => false, - } - } - } - #[getter] pub fn get_ica_version(&self) -> Option { unsafe { diff --git a/ica-rs/src/py/mod.rs b/ica-rs/src/py/mod.rs index 8843ffe..57e0d49 100644 --- a/ica-rs/src/py/mod.rs +++ b/ica-rs/src/py/mod.rs @@ -1,7 +1,63 @@ pub mod class; +use std::{collections::HashMap, path::PathBuf}; + use pyo3::{prelude::*, types::IntoPyDict}; -use tracing::{debug, info}; +use tracing::{debug, info, warn}; +use blake3::Hasher; + +use crate::config::IcaConfig; + +#[derive(Debug, Clone)] +pub struct PyStatus { + pub files: Option, String)>>, +} + +impl PyStatus { + pub fn get_files() -> &'static HashMap, String)> { + unsafe { + match PYSTATUS.files.as_ref() { + Some(files) => files, + None => { + debug!("No files in py status"); + PYSTATUS.files = Some(HashMap::new()); + PYSTATUS.files.as_ref().unwrap() + }, + } + } + } + + pub fn add_file(path: PathBuf, content: Vec, hash: String) { + unsafe { + match PYSTATUS.files.as_mut() { + Some(files) => { + files.insert(path, (content, hash)); + }, + None => { + let mut files = HashMap::new(); + files.insert(path, (content, hash)); + PYSTATUS.files = Some(files); + }, + } + } + } + + pub fn verify_file(path: &PathBuf, hash: &String) -> bool { + unsafe { + match PYSTATUS.files.as_ref() { + Some(files) => { + match files.get(path) { + Some((_, file_hash)) => file_hash == hash, + None => false, + } + }, + None => false, + } + } + } +} + +pub static mut PYSTATUS: PyStatus = PyStatus { files: None }; pub fn run() { Python::with_gil(|py| { @@ -9,12 +65,47 @@ pub fn run() { let _bot_status: &PyCell<_> = PyCell::new(py, bot_status).unwrap(); let locals = [("state", _bot_status)].into_py_dict(py); - py.run("print(state)", None, Some(locals)).unwrap(); + py.run("from pathlib import Path\nprint(Path.cwd())\nprint(state)", None, Some(locals)).unwrap(); }); } -pub fn init_py() { +pub fn load_py_file(path: &PathBuf) -> (Vec, String) { + let mut hasher = Hasher::new(); + let content = std::fs::read(path).unwrap(); + hasher.update(&content); + let hash = hasher.finalize().as_bytes().to_vec(); + (content, hex::encode(hash)) +} + +pub fn init_py(config: &IcaConfig) { debug!("initing python threads"); pyo3::prepare_freethreaded_python(); + if let Some(plugin_path) = &config.py_plugin_path { + let path = PathBuf::from(plugin_path); + if path.exists() { + info!("finding plugins in: {:?}", path); + // 搜索所有的 py 文件 和 文件夹单层下面的 py 文件 + match path.read_dir() { + Err(e) => { + warn!("failed to read plugin path: {:?}", e); + } + Ok(dir) => { + for entry in dir { + if let Ok(entry) = entry { + let path = entry.path(); + if let Some(ext) = path.extension() { + if ext == "py" { + + } + } + } + } + } + } + } else { + warn!("plugin path not exists: {:?}", path); + } + } + info!("python inited") }