diff --git a/config-temp.toml b/config-temp.toml index 5b63501..b9fbf91 100644 --- a/config-temp.toml +++ b/config-temp.toml @@ -1,4 +1,12 @@ +# python 插件路径 +py_plugin_path = "/path/to/your/plugin" +py_config_path = "/path/to/your/config" + + +[ica] +enable = true + private_key = "" # 与 icalingua 客户端使用的 private_key 一致 host = "" # docker 版 icalingua 服务的地址 self_id = 0 # 机器人的 qq 号 @@ -12,7 +20,3 @@ notice_start = true # 是否在启动 bot 后通知 admin_list = [0] # 机器人的管理员 # 过滤的人 filter_list = [0] - -# python 插件路径 -py_plugin_path = "/path/to/your/plugin" -py_config_path = "/path/to/your/config" diff --git a/ica-rs/ica_typing.py b/ica-rs/ica_typing.py index c3a7ecb..34d31ae 100644 --- a/ica-rs/ica_typing.py +++ b/ica-rs/ica_typing.py @@ -146,6 +146,9 @@ on_delete_message = Callable[[MessageId, IcaClient], None] # def on_delete_message(msg_id: MessageId, client: IcaClient) -> None: # ... +# TODO: Matrix adapter +# on_matrix_room_message = Callable[[RoomId, NewMessage, IcaClient], None] + on_config = Callable[[None], Tuple[str, str]] CONFIG_DATA: ConfigData = ConfigData() diff --git a/ica-rs/src/client.rs b/ica-rs/src/client.rs index a000c6b..d30cc90 100644 --- a/ica-rs/src/client.rs +++ b/ica-rs/src/client.rs @@ -1,8 +1,8 @@ -use crate::config::IcaConfig; +use crate::config::{BotConfig, IcaConfig}; use crate::data_struct::all_rooms::Room; use crate::data_struct::messages::{DeleteMessage, SendMessage}; use crate::data_struct::online_data::OnlineData; -use crate::ClientStatus; +use crate::ClientStatus_Global; use colored::Colorize; use ed25519_dalek::{Signature, Signer, SigningKey}; @@ -47,73 +47,85 @@ pub async fn delete_message(client: &Client, message: &DeleteMessage) -> bool { // pub async fn fetch_history(client: &Client, roomd_id: RoomId) -> bool { false } #[derive(Debug, Clone)] -pub struct IcalinguaStatus { +pub struct BotStatus { pub login: bool, /// currentLoadedMessagesCount pub current_loaded_messages_count: u64, pub online_data: Option, pub rooms: Option>, - pub config: Option, + pub config: Option, } -impl IcalinguaStatus { +impl BotStatus { pub fn new() -> Self { Self { login: false, current_loaded_messages_count: 0, online_data: None, rooms: None, - config: Some(IcaConfig::new_from_cli()), + config: Some(BotConfig::new_from_cli()), } } #[inline] pub fn update_online_data(online_data: OnlineData) { unsafe { - ClientStatus.online_data = Some(online_data); + ClientStatus_Global.online_data = Some(online_data); } } #[inline] pub fn update_rooms(rooms: Vec) { unsafe { - ClientStatus.rooms = Some(rooms); + ClientStatus_Global.rooms = Some(rooms); } } #[inline] pub fn update_login_status(login: bool) { unsafe { - ClientStatus.login = login; + ClientStatus_Global.login = login; } } #[inline] - pub fn update_config(config: IcaConfig) { + pub fn update_config(config: BotConfig) { unsafe { - ClientStatus.config = Some(config); + ClientStatus_Global.config = Some(config); } } #[inline] pub fn update_loaded_messages_count(count: u64) { unsafe { - ClientStatus.current_loaded_messages_count = count; + ClientStatus_Global.current_loaded_messages_count = count; } } #[inline] - pub fn get_login_status() -> bool { unsafe { ClientStatus.login } } + pub fn get_login_status() -> bool { unsafe { ClientStatus_Global.login } } #[inline] pub fn get_rooms() -> &'static Vec { - unsafe { ClientStatus.rooms.as_ref().expect("rooms should be set") } + unsafe { ClientStatus_Global.rooms.as_ref().expect("rooms should be set") } } #[inline] pub fn get_loaded_messages_count() -> u64 { - unsafe { ClientStatus.current_loaded_messages_count } + unsafe { ClientStatus_Global.current_loaded_messages_count } } #[inline] pub fn get_online_data() -> &'static OnlineData { - unsafe { ClientStatus.online_data.as_ref().expect("online_data should be set") } + unsafe { ClientStatus_Global.online_data.as_ref().expect("online_data should be set") } } #[inline] - pub fn get_config() -> &'static IcaConfig { - unsafe { ClientStatus.config.as_ref().expect("config should be set") } + pub fn get_config() -> &'static BotConfig { + unsafe { ClientStatus_Global.config.as_ref().expect("config should be set") } + } + #[inline] + pub fn get_ica_config() -> &'static IcaConfig { + unsafe { + ClientStatus_Global + .config + .as_ref() + .expect("config should be set") + .ica + .as_ref() + .expect("ica should be set") + } } } @@ -134,7 +146,7 @@ pub async fn sign_callback(payload: Payload, client: Client) { .expect("auth_key should be string"); let salt = hex::decode(auth_key).expect("Got an invalid salt from the server"); // 签名 - let private_key = IcalinguaStatus::get_config().private_key.clone(); + let private_key = BotStatus::get_config().ica().private_key.clone(); let array_key: [u8; 32] = hex::decode(private_key) .expect("Not a vaild pub key") .try_into() diff --git a/ica-rs/src/config.rs b/ica-rs/src/config.rs index e580791..0edd8f8 100644 --- a/ica-rs/src/config.rs +++ b/ica-rs/src/config.rs @@ -7,6 +7,8 @@ use toml::from_str; /// Icalingua bot 的配置 #[derive(Debug, Clone, Deserialize)] pub struct IcaConfig { + /// 是否启用 icalingua + pub enable: bool, /// icalingua 私钥 pub private_key: String, /// icalingua 服务器地址 @@ -21,13 +23,22 @@ pub struct IcaConfig { pub admin_list: Vec, /// 过滤列表 pub filter_list: Vec, +} + +/// 主配置 +#[derive(Debug, Clone, Deserialize)] +pub struct BotConfig { + /// Ica 配置 + pub ica: Option, + /// Matrix 配置 + // TODO: MatrixConfig /// Python 插件路径 pub py_plugin_path: Option, /// Python 配置文件路径 pub py_config_path: Option, } -impl IcaConfig { +impl BotConfig { pub fn new_from_path(config_file_path: String) -> Self { // try read config from file let config = fs::read_to_string(&config_file_path).expect("Failed to read config file"); @@ -39,4 +50,6 @@ impl IcaConfig { let config_file_path = env::args().nth(1).expect("No config path given"); Self::new_from_path(config_file_path) } + + pub fn ica(&self) -> IcaConfig { self.ica.clone().expect("No ica config found") } } diff --git a/ica-rs/src/data_struct/messages/msg_trait.rs b/ica-rs/src/data_struct/messages/msg_trait.rs index f59945b..e078ed0 100644 --- a/ica-rs/src/data_struct/messages/msg_trait.rs +++ b/ica-rs/src/data_struct/messages/msg_trait.rs @@ -4,7 +4,7 @@ use chrono::NaiveDateTime; use serde::{Deserialize, Serialize}; use serde_json::Value as JsonValue; -use crate::client::IcalinguaStatus; +use crate::client::BotStatus; use crate::data_struct::messages::{At, Message, NewMessage}; use crate::data_struct::{MessageId, UserId}; @@ -34,7 +34,7 @@ impl<'de> Deserialize<'de> for At { pub trait MessageTrait { fn is_reply(&self) -> bool; fn is_from_self(&self) -> bool { - let qq_id = IcalinguaStatus::get_online_data().qqid; + let qq_id = BotStatus::get_online_data().qqid; self.sender_id() == qq_id } fn msg_id(&self) -> &MessageId; diff --git a/ica-rs/src/events.rs b/ica-rs/src/events.rs index e6b9cc1..51bef78 100644 --- a/ica-rs/src/events.rs +++ b/ica-rs/src/events.rs @@ -3,7 +3,7 @@ use rust_socketio::asynchronous::Client; use rust_socketio::{Event, Payload}; use tracing::{info, warn}; -use crate::client::{send_message, IcalinguaStatus}; +use crate::client::{send_message, BotStatus}; use crate::data_struct::all_rooms::Room; use crate::data_struct::messages::{Message, MessageTrait, NewMessage}; use crate::data_struct::online_data::OnlineData; @@ -15,7 +15,7 @@ pub async fn get_online_data(payload: Payload, _client: Client) { if let Some(value) = values.first() { let online_data = OnlineData::new_from_json(value); info!("update_online_data {}", format!("{:?}", online_data).cyan()); - IcalinguaStatus::update_online_data(online_data); + BotStatus::update_online_data(online_data); } } } @@ -26,7 +26,7 @@ pub async fn add_message(payload: Payload, client: Client) { if let Some(value) = values.first() { let message: NewMessage = serde_json::from_value(value.clone()).unwrap(); // 检测是否在过滤列表内 - if IcalinguaStatus::get_config().filter_list.contains(&message.msg.sender_id) { + if BotStatus::get_ica_config().filter_list.contains(&message.msg.sender_id) { return; } info!("add_message {}", message.to_string().cyan()); @@ -76,7 +76,7 @@ pub async fn update_all_room(payload: Payload, _client: Client) { if let Some(raw_rooms) = value.as_array() { let rooms: Vec = raw_rooms.iter().map(|room| Room::new_from_json(room)).collect(); - IcalinguaStatus::update_rooms(rooms.clone()); + BotStatus::update_rooms(rooms.clone()); info!("update_all_room {}", rooms.len()); } } diff --git a/ica-rs/src/main.rs b/ica-rs/src/main.rs index 03307b4..b112b7d 100644 --- a/ica-rs/src/main.rs +++ b/ica-rs/src/main.rs @@ -1,5 +1,6 @@ use std::time::Duration; +use config::{BotConfig, IcaConfig}; use futures_util::FutureExt; use rust_socketio::asynchronous::{Client, ClientBuilder}; use rust_socketio::{Event, Payload, TransportType}; @@ -9,10 +10,11 @@ mod client; mod config; mod data_struct; mod events; +mod matrix; mod py; #[allow(non_upper_case_globals)] -pub static mut ClientStatus: client::IcalinguaStatus = client::IcalinguaStatus { +pub static mut ClientStatus_Global: client::BotStatus = client::BotStatus { login: false, current_loaded_messages_count: 0, online_data: None, @@ -34,18 +36,8 @@ macro_rules! wrap_any_callback { }; } -#[tokio::main] -async fn main() { - tracing_subscriber::fmt().with_max_level(tracing::Level::DEBUG).init(); - info!("ica-async-rs v{}", VERSION); - - // 从命令行获取 host 和 key - // 从命令行获取配置文件路径 - let ica_config = config::IcaConfig::new_from_cli(); - client::IcalinguaStatus::update_config(ica_config.clone()); - py::init_py(&ica_config); - - let socket = ClientBuilder::new(ica_config.host.clone()) +async fn start_ica(config: &IcaConfig, stop_reciver: tokio::sync::oneshot::Receiver<()>) { + let socket = ClientBuilder::new(config.host.clone()) .transport_type(TransportType::Websocket) .on_any(wrap_any_callback!(events::any_event)) .on("requireAuth", wrap_callback!(client::sign_callback)) @@ -65,8 +57,8 @@ async fn main() { info!("Connected"); - if ica_config.notice_start { - for room in ica_config.notice_room.iter() { + if config.notice_start { + for room in config.notice_room.iter() { let startup_msg = crate::data_struct::messages::SendMessage::new( format!("ica-async-rs bot v{}", VERSION), room.clone(), @@ -81,6 +73,33 @@ async fn main() { } } } + // 等待停止信号 + stop_reciver.await.ok(); + socket.disconnect().await.expect("Disconnect failed"); +} + +#[tokio::main] +async fn main() { + tracing_subscriber::fmt().with_max_level(tracing::Level::DEBUG).init(); + info!("ica-async-rs v{}", VERSION); + + // 从命令行获取 host 和 key + // 从命令行获取配置文件路径 + let bot_config = config::BotConfig::new_from_cli(); + client::BotStatus::update_config(bot_config.clone()); + py::init_py(&bot_config); + + // 准备一个用于停止 socket 的变量 + let (send, recv) = tokio::sync::oneshot::channel::<()>(); + if bot_config.ica.is_some() && bot_config.ica().enable { + info!("启动 ica"); + let config = bot_config.ica(); + tokio::spawn(async move { + start_ica(&config, recv).await; + }); + } else { + info!("未启用 ica"); + } tokio::time::sleep(Duration::from_secs(2)).await; // 等待一个输入 @@ -88,6 +107,7 @@ async fn main() { let mut input = String::new(); std::io::stdin().read_line(&mut input).unwrap(); - socket.disconnect().await.expect("Disconnect failed"); + // socket.disconnect().await.expect("Disconnect failed"); + send.send(()).ok(); info!("Disconnected"); } diff --git a/ica-rs/src/matrix.rs b/ica-rs/src/matrix.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/ica-rs/src/matrix.rs @@ -0,0 +1 @@ + diff --git a/ica-rs/src/py/call.rs b/ica-rs/src/py/call.rs index 50d135a..b488dc6 100644 --- a/ica-rs/src/py/call.rs +++ b/ica-rs/src/py/call.rs @@ -4,7 +4,7 @@ use pyo3::prelude::*; use rust_socketio::asynchronous::Client; use tracing::{debug, info, warn}; -use crate::client::IcalinguaStatus; +use crate::client::BotStatus; use crate::data_struct::messages::NewMessage; use crate::data_struct::MessageId; use crate::py::{class, PyPlugin, PyStatus}; @@ -45,7 +45,7 @@ pub fn get_func<'py>(py_module: &'py PyAny, path: &PathBuf, name: &'py str) -> O pub fn verify_plugins() { let mut need_reload_files: Vec = Vec::new(); - let plugin_path = IcalinguaStatus::get_config().py_plugin_path.as_ref(); + let plugin_path = BotStatus::get_config().py_plugin_path.as_ref(); if let None = plugin_path { warn!("未配置 Python 插件路径"); return; diff --git a/ica-rs/src/py/class.rs b/ica-rs/src/py/class.rs index a3c6c37..360ef3b 100644 --- a/ica-rs/src/py/class.rs +++ b/ica-rs/src/py/class.rs @@ -4,12 +4,12 @@ use tokio::runtime::Runtime; use toml::Value as TomlValue; use tracing::{debug, info, warn}; -use crate::client::{delete_message, send_message, IcalinguaStatus}; +use crate::client::{delete_message, send_message, BotStatus}; use crate::data_struct::messages::{ DeleteMessage, MessageTrait, NewMessage, ReplyMessage, SendMessage, }; use crate::data_struct::MessageId; -use crate::ClientStatus; +use crate::ClientStatus_Global; #[pyclass] #[pyo3(name = "IcaStatus")] @@ -20,39 +20,37 @@ impl IcaStatusPy { #[new] pub fn py_new() -> Self { Self {} } #[getter] - pub fn get_login(&self) -> bool { unsafe { ClientStatus.login } } + pub fn get_login(&self) -> bool { unsafe { ClientStatus_Global.login } } #[getter] - pub fn get_online(&self) -> bool { IcalinguaStatus::get_online_data().online } + pub fn get_online(&self) -> bool { BotStatus::get_online_data().online } #[getter] - pub fn get_self_id(&self) -> i64 { IcalinguaStatus::get_online_data().qqid } + pub fn get_self_id(&self) -> i64 { BotStatus::get_online_data().qqid } #[getter] - pub fn get_nick_name(&self) -> String { IcalinguaStatus::get_online_data().nick.clone() } + pub fn get_nick_name(&self) -> String { BotStatus::get_online_data().nick.clone() } #[getter] - pub fn get_loaded_messages_count(&self) -> u64 { IcalinguaStatus::get_loaded_messages_count() } + pub fn get_loaded_messages_count(&self) -> u64 { BotStatus::get_loaded_messages_count() } #[getter] pub fn get_ica_version(&self) -> String { - IcalinguaStatus::get_online_data().icalingua_info.ica_version.clone() + BotStatus::get_online_data().icalingua_info.ica_version.clone() } #[getter] pub fn get_os_info(&self) -> String { - IcalinguaStatus::get_online_data().icalingua_info.os_info.clone() + BotStatus::get_online_data().icalingua_info.os_info.clone() } #[getter] pub fn get_resident_set_size(&self) -> String { - IcalinguaStatus::get_online_data().icalingua_info.resident_set_size.clone() + BotStatus::get_online_data().icalingua_info.resident_set_size.clone() } #[getter] pub fn get_heap_used(&self) -> String { - IcalinguaStatus::get_online_data().icalingua_info.heap_used.clone() + BotStatus::get_online_data().icalingua_info.heap_used.clone() } #[getter] - pub fn get_load(&self) -> String { - IcalinguaStatus::get_online_data().icalingua_info.load.clone() - } + pub fn get_load(&self) -> String { BotStatus::get_online_data().icalingua_info.load.clone() } } impl IcaStatusPy { diff --git a/ica-rs/src/py/mod.rs b/ica-rs/src/py/mod.rs index f57b47c..00c2525 100644 --- a/ica-rs/src/py/mod.rs +++ b/ica-rs/src/py/mod.rs @@ -8,8 +8,8 @@ use pyo3::prelude::*; use pyo3::types::PyTuple; use tracing::{debug, info, warn}; -use crate::client::IcalinguaStatus; -use crate::config::IcaConfig; +use crate::client::BotStatus; +use crate::config::{BotConfig, IcaConfig}; #[derive(Debug, Clone)] pub struct PyStatus { @@ -75,7 +75,7 @@ impl TryFrom for PyPlugin { if config.is_instance_of::() { let (config, default) = config.extract::<(String, String)>().unwrap(); let base_path = - IcalinguaStatus::get_config().py_config_path.as_ref().unwrap(); + BotStatus::get_config().py_config_path.as_ref().unwrap(); let mut base_path: PathBuf = PathBuf::from(base_path); @@ -244,7 +244,7 @@ pub fn load_py_file(path: &PathBuf) -> std::io::Result { Ok((path.clone(), changed_time, content)) } -pub fn init_py(config: &IcaConfig) { +pub fn init_py(config: &BotConfig) { debug!("initing python threads"); pyo3::prepare_freethreaded_python(); if let Some(plugin_path) = &config.py_plugin_path {