diff --git a/Cargo.lock b/Cargo.lock index 3cb2290..cb04a91 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -890,9 +890,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.101" +version = "0.9.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dda2b0f344e78efc2facf7d195d098df0dd72151b26ab98da807afc26c198dff" +checksum = "c597637d56fbc83893a35eb0dd04b2b8e7a50c91e64e9493e398b5df4fb45fa2" dependencies = [ "cc", "libc", diff --git a/ica-rs/Cargo.toml b/ica-rs/Cargo.toml index ea7dd71..f263ac3 100644 --- a/ica-rs/Cargo.toml +++ b/ica-rs/Cargo.toml @@ -26,9 +26,9 @@ rust_socketio = { version = "0.4.4", features = ["async"], optional = true } # data serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" -chrono = "0.4.37" -toml = "0.8.12" -colored = "2.1.0" +chrono = "0.4" +toml = "0.8" +colored = "2.1" # runtime tokio = { version = "1.37", features = ["full"] } diff --git a/ica-rs/src/config.rs b/ica-rs/src/config.rs index 7997421..dda3de3 100644 --- a/ica-rs/src/config.rs +++ b/ica-rs/src/config.rs @@ -24,7 +24,6 @@ pub struct IcaConfig { pub filter_list: Vec, } - #[derive(Debug, Clone, Deserialize)] pub struct PyConfig { /// 插件路径 diff --git a/ica-rs/src/data_struct/mod.rs b/ica-rs/src/data_struct/mod.rs index 3de6f6b..044fb6f 100644 --- a/ica-rs/src/data_struct/mod.rs +++ b/ica-rs/src/data_struct/mod.rs @@ -1 +1,2 @@ pub mod ica; +pub mod tailchat; diff --git a/ica-rs/src/data_struct/tailchat.rs b/ica-rs/src/data_struct/tailchat.rs new file mode 100644 index 0000000..5df09c8 --- /dev/null +++ b/ica-rs/src/data_struct/tailchat.rs @@ -0,0 +1,6 @@ +pub mod messages; + +pub type GroupId = String; +pub type ConverseId = String; +pub type UserId = String; +pub type MessageId = String; diff --git a/ica-rs/src/data_struct/tailchat/messages.rs b/ica-rs/src/data_struct/tailchat/messages.rs new file mode 100644 index 0000000..56fd06b --- /dev/null +++ b/ica-rs/src/data_struct/tailchat/messages.rs @@ -0,0 +1,119 @@ +use serde::{Deserialize, Serialize}; +use serde_json::{json, Value as JsonValue}; + +use crate::data_struct::tailchat::{ConverseId, GroupId, MessageId, UserId}; + +/*{'_id': '6606b3075163504389a6fc47', +'content': '光速!(', +'author': '6602e20d7b8d10675758e36b', +'groupId': '6602e331d31fd31b04aa0693', +'converseId': '6602f785928c4254f17726b2', +'hasRecall': False, +'meta': {'mentions': []}, +'reactions': [], +'createdAt': ExtType(code=0, data=b'\x00\x00\x01\x8e\x8a+TJ'), +'updatedAt': ExtType(code=0, data=b'\x00\x00\x01\x8e\x8a+TJ'), +'__v': 0} */ + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct ReciveMessage { + /// 消息ID + #[serde(rename = "_id")] + pub msg_id: MessageId, + /// 消息内容 + pub content: String, + /// 发送者ID + #[serde(rename = "author")] + pub sender_id: UserId, + /// 服务器ID + #[serde(rename = "groupId")] + pub group_id: GroupId, + /// 会话ID + #[serde(rename = "converseId")] + pub converse_id: ConverseId, + /// 是否有回复? + #[serde(rename = "hasRecall")] + pub has_recall: bool, + /// 暂时懒得解析这玩意 + pub meta: JsonValue, + /// 也懒得解析这玩意 + pub reactions: Vec, + /// 创建时间 + #[serde(rename = "createdAt")] + pub created_at: JsonValue, + /// 更新时间 + #[serde(rename = "updatedAt")] + pub updated_at: JsonValue, + /// 未知 + #[serde(rename = "__v")] + pub v: JsonValue, +} + +impl ReciveMessage { + pub fn as_reply(&self) -> ReplyMessage { + ReplyMessage { + content: self.content.clone(), + converse_id: self.converse_id.clone(), + group_id: self.group_id.clone(), + reply_id: self.msg_id.clone(), + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct SendingMessage { + /// 消息内容 + pub content: String, + /// 会话ID + #[serde(rename = "converseId")] + pub converse_id: ConverseId, + /// 服务器ID + #[serde(rename = "groupId")] + pub group_id: GroupId, +} + +#[derive(Debug, Clone)] +pub struct ReplyMeta { + /// 被回复的人的ID (可以是多个?) + pub mentions: Vec, + /// 被回复的消息ID + pub reply_id: MessageId, + /// 被回复的消息的发送者ID + pub reply_author: UserId, + /// 被回复的消息内容 + pub reply_content: String, +} + +impl Serialize for ReplyMeta { + fn serialize(&self, serializer: S) -> Result + where + S: serde::ser::Serializer, + { + let reply = json! { + { + "replyId": self.reply_id, + "replyAuthor": self.reply_author, + "replyContent": self.reply_content, + } + }; + let mut map = serde_json::Map::new(); + map.insert("mentions".to_string(), serde_json::to_value(&self.mentions).unwrap()); + map.insert("reply".to_string(), reply); + map.serialize(serializer) + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ReplyMessage { + /// 消息内容 + pub content: String, + /// 会话ID + #[serde(rename = "converseId")] + pub converse_id: ConverseId, + /// 服务器ID + #[serde(rename = "groupId")] + pub group_id: GroupId, + /// 回复的消息ID + #[serde(rename = "replyId")] + pub reply_id: MessageId, +} diff --git a/ica-rs/src/error.rs b/ica-rs/src/error.rs index 571ecb2..f1bf26f 100644 --- a/ica-rs/src/error.rs +++ b/ica-rs/src/error.rs @@ -6,6 +6,21 @@ pub enum IcaError { SocketIoError(rust_socketio::error::Error), } +#[derive(Debug)] +pub enum PyPluginError { + /// 插件内未找到指定函数 + /// 函数名, 模块名 + FuncNotFound(String, String), + /// 插件内函数获取错误 + /// pyerr, func_name, module_name + CouldNotGetFunc(pyo3::PyErr, String, String), + /// 插件内函数不可调用 + FuncNotCallable(String, String), + /// 插件内函数调用错误 + /// pyerr, func_name, module_name + FuncCallError(pyo3::PyErr, String, String), +} + impl From for IcaError { fn from(e: rust_socketio::Error) -> Self { IcaError::SocketIoError(e) } } @@ -18,6 +33,25 @@ impl std::fmt::Display for IcaError { } } +impl std::fmt::Display for PyPluginError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + PyPluginError::FuncNotFound(name, module) => { + write!(f, "插件内未找到函数: {} in {}", name, module) + } + PyPluginError::CouldNotGetFunc(py_err, name, module) => { + write!(f, "插件内函数获取错误: {:#?}|{} in {}", py_err, name, module) + } + PyPluginError::FuncNotCallable(name, module) => { + write!(f, "插件内函数不可调用: {} in {}", name, module) + } + PyPluginError::FuncCallError(py_err, name, module) => { + write!(f, "插件内函数调用错误: {:#?}|{} in {}", py_err, name, module) + } + } + } +} + impl std::error::Error for IcaError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { @@ -25,3 +59,14 @@ impl std::error::Error for IcaError { } } } + +impl std::error::Error for PyPluginError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + PyPluginError::FuncNotFound(_, _) => None, + PyPluginError::CouldNotGetFunc(e, _, _) => Some(e), + PyPluginError::FuncNotCallable(_, _) => None, + PyPluginError::FuncCallError(_, _) => None, + } + } +} diff --git a/ica-rs/src/main.rs b/ica-rs/src/main.rs index e050900..da8af32 100644 --- a/ica-rs/src/main.rs +++ b/ica-rs/src/main.rs @@ -3,13 +3,14 @@ use std::time::Duration; mod config; mod data_struct; mod error; -#[cfg(feature = "ica")] -mod ica; -// #[cfg(feature = "tailchat")] -// mod tailchat; mod py; mod status; +#[cfg(feature = "ica")] +mod ica; +#[cfg(feature = "tailchat")] +mod tailchat; + use config::BotConfig; use tracing::{event, info, span, Level}; diff --git a/ica-rs/src/py/call.rs b/ica-rs/src/py/call.rs index 2b6e470..d484197 100644 --- a/ica-rs/src/py/call.rs +++ b/ica-rs/src/py/call.rs @@ -4,12 +4,15 @@ use pyo3::prelude::*; use rust_socketio::asynchronous::Client; use tracing::{debug, info, warn}; -use crate::data_struct::ica::messages::NewMessage; -use crate::data_struct::ica::MessageId; +use crate::data_struct::{ica, tailchat}; +use crate::error::PyPluginError; use crate::py::{class, PyPlugin, PyStatus}; use crate::MainStatus; -pub fn get_func<'py>(py_module: &'py PyAny, path: &PathBuf, name: &'py str) -> Option<&'py PyAny> { +pub fn get_func<'py>( + py_module: &Bound<'py, PyAny>, + name: &'py str, +) -> Result, PyPluginError> { // 要处理的情况: // 1. 有这个函数 // 2. 没有这个函数 @@ -20,25 +23,39 @@ pub fn get_func<'py>(py_module: &'py PyAny, path: &PathBuf, name: &'py str) -> O match py_module.getattr(name) { Ok(func) => { if func.is_callable() { - Some(func) + Ok(func) } else { - warn!("function<{}>: {:#?} in {:?} is not callable", name, func, path); - None + // warn!("function<{}>: {:#?} in {:?} is not callable", name, func, path); + Err(PyPluginError::FuncNotCallable( + name.to_string(), + py_module.getattr("__name__").unwrap().extract::().unwrap(), + )) } } Err(e) => { - warn!("failed to get function<{}> from {:?}: {:?}", name, path, e); - None + // warn!("failed to get function<{}> from {:?}: {:?}", name, path, e); + Err(PyPluginError::CouldNotGetFunc( + e, + name.to_string(), + py_module.getattr("__name__").unwrap().extract::().unwrap(), + )) } } } else { - debug!("no function<{}> in module {:?}", name, path); - None + // debug!("no function<{}> in module {:?}", name, path); + Err(PyPluginError::FuncNotFound( + name.to_string(), + py_module.getattr("__name__").unwrap().extract::().unwrap(), + )) } } Err(e) => { - warn!("failed to check function<{}> from {:?}: {:?}", name, path, e); - None + // warn!("failed to check function<{}> from {:?}: {:?}", name, path, e); + Err(PyPluginError::CouldNotGetFunc( + e, + name.to_string(), + py_module.getattr("__name__").unwrap().extract::().unwrap(), + )) } } } @@ -79,23 +96,22 @@ pub const ICA_DELETE_MESSAGE_FUNC: &str = "on_ica_delete_message"; pub const TAILCHAT_NEW_MESSAGE_FUNC: &str = "on_tailchat_message"; /// 执行 new message 的 python 插件 -pub async fn ica_new_message_py(message: &NewMessage, client: &Client) { +pub async fn ica_new_message_py(message: &ica::messages::NewMessage, client: &Client) { // 验证插件是否改变 verify_plugins(); let plugins = PyStatus::get_files(); - for (path, plugin) in plugins.iter() { + for (_path, plugin) in plugins.iter() { let msg = class::ica::NewMessagePy::new(message); let client = class::ica::IcaClientPy::new(client); let args = (msg, client); // 甚至实际上压根不需要await这个spawn, 直接让他自己跑就好了(离谱) tokio::spawn(async move { Python::with_gil(|py| { - if let Some(py_func) = - get_func(plugin.py_module.as_ref(py), path, ICA_NEW_MESSAGE_FUNC) - { + if let Ok(py_func) = get_func(plugin.py_module.bind(py), ICA_NEW_MESSAGE_FUNC) { if let Err(e) = py_func.call1(args) { - warn!("failed to call function<{}>: {:?}", ICA_NEW_MESSAGE_FUNC, e); + let e = PyPluginError::FuncCallError(e, ICA_NEW_MESSAGE_FUNC.to_string()); + // warn!("failed to call function<{}>: {:?}", ICA_NEW_MESSAGE_FUNC, e); } } }) @@ -103,19 +119,17 @@ pub async fn ica_new_message_py(message: &NewMessage, client: &Client) { } } -pub async fn ica_delete_message_py(msg_id: MessageId, client: &Client) { +pub async fn ica_delete_message_py(msg_id: ica::MessageId, client: &Client) { verify_plugins(); let plugins = PyStatus::get_files(); - for (path, plugin) in plugins.iter() { + for (_path, plugin) in plugins.iter() { let msg_id = msg_id.clone(); let client = class::ica::IcaClientPy::new(client); let args = (msg_id.clone(), client); tokio::spawn(async move { Python::with_gil(|py| { - if let Some(py_func) = - get_func(plugin.py_module.as_ref(py), path, ICA_DELETE_MESSAGE_FUNC) - { + if let Ok(py_func) = get_func(plugin.py_module.bind(py), ICA_DELETE_MESSAGE_FUNC) { if let Err(e) = py_func.call1(args) { warn!("failed to call function<{}>: {:?}", ICA_DELETE_MESSAGE_FUNC, e); } @@ -124,3 +138,5 @@ pub async fn ica_delete_message_py(msg_id: MessageId, client: &Client) { }); } } + +// pub async fn tailchat_new_message_py(message: ) diff --git a/ica-rs/src/py/mod.rs b/ica-rs/src/py/mod.rs index 435303e..f50bae1 100644 --- a/ica-rs/src/py/mod.rs +++ b/ica-rs/src/py/mod.rs @@ -68,8 +68,8 @@ impl TryFrom for PyPlugin { }; let py_module = py_module.unwrap(); Python::with_gil(|py| { - let module = py_module.as_ref(py); - if let Some(config_func) = call::get_func(module, &path, "on_config") { + let module = py_module.bind(py); + if let Ok(config_func) = call::get_func(module, "on_config") { match config_func.call0() { Ok(config) => { if config.is_instance_of::() { @@ -96,8 +96,7 @@ impl TryFrom for PyPlugin { }; match config_value { Ok(config) => { - let py_config = class::ConfigDataPy::new(config); - let py_config = PyCell::new(py, py_config).unwrap(); + let py_config = Bound::new(py, class::ConfigDataPy::new(config)).unwrap(); module.setattr("CONFIG_DATA", py_config).unwrap(); Ok(PyPlugin { file_path: path, @@ -228,7 +227,7 @@ pub fn get_change_time(path: &Path) -> Option { path.metadata().ok() pub fn py_module_from_code(content: &str, path: &Path) -> PyResult> { Python::with_gil(|py| -> PyResult> { - let module: PyResult> = PyModule::from_code( + let module: PyResult> = PyModule::from_code_bound( py, content, &path.to_string_lossy(), diff --git a/ica-rs/src/tailchat.rs b/ica-rs/src/tailchat.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/ica-rs/src/tailchat.rs @@ -0,0 +1 @@ +