diff --git a/ica-rs/src/data_struct/mod.rs b/ica-rs/src/data_struct/mod.rs new file mode 100644 index 0000000..b0aa8bd --- /dev/null +++ b/ica-rs/src/data_struct/mod.rs @@ -0,0 +1 @@ +pub mod online_data; diff --git a/ica-rs/src/data_struct/online_data.rs b/ica-rs/src/data_struct/online_data.rs new file mode 100644 index 0000000..69f8744 --- /dev/null +++ b/ica-rs/src/data_struct/online_data.rs @@ -0,0 +1,226 @@ +use serde_json::Value as JsonValue; +use tracing::warn; + +#[derive(Debug, Clone, Hash)] +pub struct IcalinguaInfo { + pub ica_version: String, + pub os_info: String, + pub resident_set_size: String, + pub heap_used: String, + pub load: String, + /// 服务器 nodejs 版本 + pub server_node: String, + /// 客户端计数 + /// 我还不新 u16 (u16::MAX) 会溢出 + pub client_count: u16, +} + +impl IcalinguaInfo { + pub fn new_from_str(s: &str) -> IcalinguaInfo { + let mut ica_version = None; + let mut os_info = None; + let mut resident_set_size = None; + let mut heap_used = None; + let mut load = None; + let mut server_node = None; + let mut client_count = None; + let info_list = s.split("\n").collect::>(); + for info in info_list { + if info.starts_with("icalingua-bridge-oicq") { + ica_version = Some(info.split_at(22).1.to_string()); + } else if info.starts_with("Running on") { + os_info = Some(info.split_at(11).1.to_string()); + } else if info.starts_with("Resident Set Size") { + resident_set_size = Some(info.split_at(18).1.to_string()); + } else if info.starts_with("Heap used") { + heap_used = Some(info.split_at(10).1.to_string()); + } else if info.starts_with("Load") { + load = Some(info.split_at(5).1.to_string()); + } else if info.starts_with("Server Node") { + server_node = Some(info.split_at(12).1.to_string()); + } else if info.ends_with("clients connected") { + client_count = Some( + info.split(" ") + .collect::>() + .get(0) + .unwrap_or(&"1") + .parse::() + .unwrap_or_else(|e| { + warn!("client_count parse error: {}|raw: {}", e, info); + 1_u16 + }), + ); + } + } + IcalinguaInfo { + ica_version: ica_version.unwrap_or_else(|| { + warn!("ica_version faild to parse"); + "UNKNOWN".to_string() + }), + os_info: os_info.unwrap_or_else(|| { + warn!("os_info faild to parse"); + "UNKNOWN".to_string() + }), + resident_set_size: resident_set_size.unwrap_or_else(|| { + warn!("resident_set_size faild to parse"); + "UNKNOWN".to_string() + }), + heap_used: heap_used.unwrap_or_else(|| { + warn!("heap_used faild to parse"); + "UNKNOWN".to_string() + }), + load: load.unwrap_or_else(|| { + warn!("load faild to parse"); + "UNKNOWN".to_string() + }), + server_node: server_node.unwrap_or_else(|| { + warn!("server_node faild to parse"); + "UNKNOWN".to_string() + }), + client_count: client_count.unwrap_or_else(|| { + warn!("client_count faild to parse"); + 1_u16 + }), + } + } +} + +/// {"bkn":, +/// "nick":, +/// "online":true, +/// "sysInfo":"icalingua-bridge-oicq 2.11.1\n +/// Running on Linux c038fad79f13 4.4.302+\n +/// Resident Set Size 95.43MB\n +/// Heap used 37.31MB\n +/// Load 4.23 2.15 1.59\n +/// Server Node 18.19.0\n +/// 2 clients connected", +/// "uin": } +#[derive(Debug, Clone, Hash)] +pub struct OnlineData { + pub bkn: i64, + pub nick: String, + pub online: bool, + pub qqid: i64, + pub icalingua_info: IcalinguaInfo, +} + +impl OnlineData { + pub fn new_from_json(value: &JsonValue) -> OnlineData { + let bkn = value["bkn"].as_i64().unwrap_or_else(|| { + warn!("bkn not found in online data"); + -1 + }); + let nick = value["nick"] + .as_str() + .unwrap_or_else(|| { + warn!("nick not found in online data"); + "UNKNOWN" + }) + .to_string(); + let online = value["online"].as_bool().unwrap_or_else(|| { + warn!("online not found in online data"); + false + }); + let qqid = value["uin"].as_i64().unwrap_or_else(|| { + warn!("uin not found in online data"); + -1 + }); + let sys_info = value["sysInfo"].as_str().unwrap_or_else(|| { + warn!("sysInfo not found in online data"); + "" + }); + let icalingua_info = IcalinguaInfo::new_from_str(sys_info); + OnlineData { + bkn, + nick, + online, + qqid, + icalingua_info, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parse_empty_str() { + let icalingua_info = IcalinguaInfo::new_from_str(""); + assert_eq!(icalingua_info.ica_version, "UNKNOWN"); + assert_eq!(icalingua_info.os_info, "UNKNOWN"); + assert_eq!(icalingua_info.resident_set_size, "UNKNOWN"); + assert_eq!(icalingua_info.heap_used, "UNKNOWN"); + assert_eq!(icalingua_info.load, "UNKNOWN"); + assert_eq!(icalingua_info.server_node, "UNKNOWN"); + assert_eq!(icalingua_info.client_count, 1); + } + + #[test] + fn parse_str() { + let icalingua_info = IcalinguaInfo::new_from_str( + "icalingua-bridge-oicq 2.11.1\n\ + Running on Linux c038fad79f13 4.4.302+\n\ + Resident Set Size 95.43MB\n\ + Heap used 37.31MB\n\ + Load 4.23 2.15 1.59\n\ + Server Node 18.19.0\n\ + 2 clients connected", + ); + assert_eq!(icalingua_info.ica_version, "2.11.1"); + assert_eq!(icalingua_info.os_info, "Linux c038fad79f13 4.4.302+"); + assert_eq!(icalingua_info.resident_set_size, "95.43MB"); + assert_eq!(icalingua_info.heap_used, "37.31MB"); + assert_eq!(icalingua_info.load, "4.23 2.15 1.59"); + assert_eq!(icalingua_info.server_node, "18.19.0"); + assert_eq!(icalingua_info.client_count, 2); + } + + #[test] + fn parse_online_data() { + let online_data = OnlineData::new_from_json(&serde_json::json!({ + "bkn": 123, + "nick": "test", + "online": true, + "sysInfo": "icalingua-bridge-oicq 2.11.1\n\ + Running on Linux c038fad79f13 4.4.302+\n\ + Resident Set Size 95.43MB\n\ + Heap used 37.31MB\n\ + Load 4.23 2.15 1.59\n\ + Server Node 18.19.0\n\ + 2 clients connected", + "uin": 123456 + })); + assert_eq!(online_data.bkn, 123); + assert_eq!(online_data.nick, "test"); + assert_eq!(online_data.online, true); + assert_eq!(online_data.qqid, 123456); + assert_eq!(online_data.icalingua_info.ica_version, "2.11.1"); + assert_eq!( + online_data.icalingua_info.os_info, + "Linux c038fad79f13 4.4.302+" + ); + assert_eq!(online_data.icalingua_info.resident_set_size, "95.43MB"); + assert_eq!(online_data.icalingua_info.heap_used, "37.31MB"); + assert_eq!(online_data.icalingua_info.load, "4.23 2.15 1.59"); + assert_eq!(online_data.icalingua_info.server_node, "18.19.0"); + assert_eq!(online_data.icalingua_info.client_count, 2); + } + + #[test] + fn parse_online_data_empty() { + let online_data = OnlineData::new_from_json(&serde_json::json!({})); + assert_eq!(online_data.bkn, -1); + assert_eq!(online_data.nick, "UNKNOWN"); + assert_eq!(online_data.online, false); + assert_eq!(online_data.qqid, -1); + assert_eq!(online_data.icalingua_info.ica_version, "UNKNOWN"); + assert_eq!(online_data.icalingua_info.os_info, "UNKNOWN"); + assert_eq!(online_data.icalingua_info.resident_set_size, "UNKNOWN"); + assert_eq!(online_data.icalingua_info.heap_used, "UNKNOWN"); + assert_eq!(online_data.icalingua_info.load, "UNKNOWN"); + assert_eq!(online_data.icalingua_info.server_node, "UNKNOWN"); + assert_eq!(online_data.icalingua_info.client_count, 1); + } +} diff --git a/ica-rs/src/events.rs b/ica-rs/src/events.rs new file mode 100644 index 0000000..ec8d7d2 --- /dev/null +++ b/ica-rs/src/events.rs @@ -0,0 +1,72 @@ +use colored::Colorize; +use rust_socketio::{Event, Payload, RawClient}; +use tracing::{info, warn}; + +use crate::data_struct::online_data::OnlineData; +use crate::py; + +pub fn get_online_data(payload: Payload, _client: RawClient) { + if let Payload::Text(values) = payload { + if let Some(value) = values.first() { + let online_data = OnlineData::new_from_json(value); + info!("update_online_data {}", format!("{:#?}", online_data).cyan()); + } + } +} + +pub fn any_event(event: Event, payload: Payload, _client: RawClient) { + let handled = vec![ + "authSucceed", + "authFailed", + "authRequired", + "requireAuth", + "onlineData", + ]; + match &event { + Event::Custom(event_name) => { + if handled.contains(&event_name.as_str()) { + return; + } + } + _ => (), + } + match payload { + Payload::Binary(ref data) => { + println!("event: {} |{:?}", event, data) + } + Payload::Text(ref data) => { + print!("event: {}", event.as_str().purple()); + for value in data { + println!("|{}", value.to_string()); + } + } + _ => (), + } +} + +pub fn connect_callback(payload: Payload, _client: RawClient) { + match payload { + Payload::Text(values) => { + if let Some(value) = values.first() { + // if let Some("authSucceed") = value.as_str() { + // println!("{}", "已经登录到 icalingua!".green()); + // } + match value.as_str() { + Some("authSucceed") => { + py::run(); + info!("{}", "已经登录到 icalingua!".green()) + } + Some("authFailed") => { + info!("{}", "登录到 icalingua 失败!".red()); + panic!("登录失败") + } + Some("authRequired") => { + warn!("{}", "需要登录到 icalingua!".yellow()) + } + _ => (), + } + } + } + _ => (), + } +} diff --git a/ica-rs/src/main.rs b/ica-rs/src/main.rs index b322618..58b88cc 100644 --- a/ica-rs/src/main.rs +++ b/ica-rs/src/main.rs @@ -1,67 +1,30 @@ -mod client; -mod config; -mod py; - -use colored::Colorize; -use rust_socketio::{ClientBuilder, Event, Payload, RawClient}; use std::time::Duration; -#[allow(unused)] -fn any_event(event: Event, payload: Payload, _client: RawClient) { - match payload { - Payload::Binary(ref data) => { - println!("event: {} |{:?}", event, data) - } - Payload::Text(ref data) => { - print!("event: {}", event.as_str().purple()); - for value in data { - println!("|{}", value.to_string()); - } - } - _ => (), - } - // println!("event: {} |{:?}|id{:?}", event, payload, id) -} +use rust_socketio::ClientBuilder; + +mod client; +mod config; +mod data_struct; +mod events; +mod py; fn ws_main() { py::init_py(); - // define a callback which is called when a payload is received - // this callback gets the payload as well as an instance of the - // socket to communicate with the server - let connect_call_back = |payload: Payload, _client: RawClient| match payload { - Payload::Text(values) => { - if let Some(value) = values.first() { - // if let Some("authSucceed") = value.as_str() { - // println!("{}", "已经登录到 icalingua!".green()); - // } - match value.as_str() { - Some("authSucceed") => { - py::run(); - println!("{}", "已经登录到 icalingua!".green()) - } - Some("authFailed") => { - println!("{}", "登录到 icalingua 失败!".red()); - panic!("登录失败") - } - Some("authRequired") => println!("{}", "需要登录到 icalingua!".yellow()), - _ => (), - } - } - } - _ => (), - }; + // 从命令行获取 host 和 key // 从命令行获取配置文件路径 let ica_config = config::IcaConfig::new_from_cli(); let ica_singer = client::IcalinguaSinger::new_from_config(ica_config); - // get a socket that is connected to the admin namespace - let socket = ClientBuilder::new(ica_singer.host.clone()) .transport_type(rust_socketio::TransportType::Websocket) - .on_any(any_event) - .on("message", connect_call_back) + .on_any(events::any_event) + .on("onlineData", events::get_online_data) + .on("message", events::connect_callback) .on("requireAuth", move |a, b| ica_singer.sign_callback(a, b)) + .on("authRequired", events::connect_callback) + .on("authSucceed", events::connect_callback) + .on("authFailed", events::connect_callback) .connect() .expect("Connection failed");