Compare commits

..

17 Commits

Author SHA1 Message Date
9f5956e77a
优化计数格式化函数 2024-02-25 02:53:35 +08:00
f2624dbcca
更新消息格式和房间ID处理***
***更新消息格式和房间ID处理
2024-02-25 02:26:23 +08:00
b41617bb06
将py部分移到ica-py里 2024-02-25 02:13:22 +08:00
29f6b2efaf
优化代码结构和性能 2024-02-25 02:12:11 +08:00
5381ef598a
修复了Message解析replyMessage字段为null时解析失败的问题 2024-02-25 01:56:47 +08:00
8448b03d83
更新日志~ 2024-02-25 01:34:43 +08:00
4b3da3b85f
调整代码格式和样式 2024-02-25 01:33:56 +08:00
8b2a8ee8d2
更新代码,修复了一些bug和改进了功能。 2024-02-25 01:32:31 +08:00
fe06356bea
优化MessageFile结构体的get_name和get_fid方法 2024-02-25 01:32:15 +08:00
03fdcc300b
更新 IcalinguaStatus 结构体的方法实现 2024-02-25 01:31:56 +08:00
95c2cc377a
添加消息特性接口和实现 2024-02-25 01:31:48 +08:00
63e18e8eab
更新bmcl插件和在线数据模块 2024-02-25 01:31:39 +08:00
16ff8f534e
更新rustfmt配置文件***
***更新了rustfmt配置文件,将最大行长(max_width)从120修改为100,链式调用的最大长度(chain_width)从100修改为70,数组的最大长度(array_width)从100修改为70,函数参数的最大长度(attr_fn_like_width)从100修改为60。
2024-02-25 01:30:56 +08:00
4bad0c95c5
添加rustfmt.toml配置文件,设置代码格式化规则 2024-02-25 01:28:44 +08:00
559de2e2f6
更新bmcl.py插件中的wrap_request函数,添加了异常处理和错误提示功能 2024-02-25 00:29:48 +08:00
85608570bf
更新版本号和添加状态属性 2024-02-25 00:22:09 +08:00
0420cf36b2
更新了ica_typing.py和main.rs文件中的函数签名,添加了on_delete_message、messageSuccess和messageFailed回调函数。添加了delete_message_py和succes_message、failed_message函数来处理相应的事件。 2024-02-24 23:56:36 +08:00
23 changed files with 511 additions and 345 deletions

View File

@ -1,6 +1,6 @@
[package] [package]
name = "ica-rs" name = "ica-rs"
version = "0.4.9" version = "0.4.10"
edition = "2021" edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

View File

@ -94,6 +94,11 @@ class IcaClient:
""" """
def send_message(self, message: SendMessage) -> bool: def send_message(self, message: SendMessage) -> bool:
... ...
@property
def status() -> IcaStatus:
...
def debug(self, message: str) -> None: def debug(self, message: str) -> None:
"""向日志中输出调试信息""" """向日志中输出调试信息"""
def info(self, message: str) -> None: def info(self, message: str) -> None:
@ -110,4 +115,6 @@ on_message = Callable[[NewMessage, IcaClient], None]
# def on_message(msg: NewMessage, client: IcaClient) -> None: # def on_message(msg: NewMessage, client: IcaClient) -> None:
# ... # ...
on_delete_message = Callable[[int, IcaClient], None] on_delete_message = Callable[[MessageId, IcaClient], None]
# def on_delete_message(msg_id: MessageId, client: IcaClient) -> None:
# ...

View File

@ -44,11 +44,25 @@ def format_hit_count(count: int) -> str:
if count_len <= 4: if count_len <= 4:
return count_str return count_str
else: else:
return "_".join(count_str[i:i + 4] for i in range(0, count_len, 4)) # 先倒序
# 再插入
# 最后再倒序
count_str = count_str[::-1]
count_str = "_".join([count_str[i:i+4] for i in range(0, count_len, 4)])
count_str = count_str[::-1]
return count_str
def wrap_request(url: str, client: IcaClient) -> Optional[dict]: def wrap_request(url: str, msg: NewMessage, client: IcaClient) -> Optional[dict]:
try:
response = requests.get(url) response = requests.get(url)
except requests.exceptions.RequestException as e:
client.warn(
f"数据请求失败, 请检查网络\n{e}"
)
reply = msg.reply_with(f"数据请求失败, 请检查网络\n{e}")
client.send_message(reply)
return None
if not response.status_code == 200 or response.reason != "OK": if not response.status_code == 200 or response.reason != "OK":
client.warn( client.warn(
f"数据请求失败, 请检查网络\n{response.status}" f"数据请求失败, 请检查网络\n{response.status}"
@ -61,7 +75,7 @@ def wrap_request(url: str, client: IcaClient) -> Optional[dict]:
def bmcl_dashboard(msg: NewMessage, client: IcaClient) -> None: def bmcl_dashboard(msg: NewMessage, client: IcaClient) -> None:
req_time = time.time() req_time = time.time()
# 记录请求时间 # 记录请求时间
data = wrap_request("https://bd.bangbang93.com/openbmclapi/metric/dashboard", client) data = wrap_request("https://bd.bangbang93.com/openbmclapi/metric/dashboard", msg, client)
if data is None: if data is None:
return return
data_bytes: float = data["bytes"] data_bytes: float = data["bytes"]
@ -104,7 +118,7 @@ def parse_rank(data: dict) -> dict:
def bmcl_rank(msg: NewMessage, client: IcaClient, name: Optional[str]) -> None: def bmcl_rank(msg: NewMessage, client: IcaClient, name: Optional[str]) -> None:
req_time = time.time() req_time = time.time()
# 记录请求时间 # 记录请求时间
rank_data = wrap_request("https://bd.bangbang93.com/openbmclapi/metric/rank", client) rank_data = wrap_request("https://bd.bangbang93.com/openbmclapi/metric/rank", msg, client)
if rank_data is None: if rank_data is None:
return return
ranks = [parse_rank(data) for data in rank_data] ranks = [parse_rank(data) for data in rank_data]

23
ica-rs/rustfmt.toml Normal file
View File

@ -0,0 +1,23 @@
# cargo fmt config
# 最大行长
max_width = 100
# 链式调用的最大长度
chain_width = 80
# 数组的最大长度
array_width = 70
# 函数参数的最大长度
attr_fn_like_width = 60
# 函数调用参数的最大长度
fn_call_width = 80
# 简单函数格式化为单行
fn_single_line = true
# 自动对齐最大长度
enum_discrim_align_threshold = 5
# 字段初始化使用简写
use_field_init_shorthand = true
# 是否使用彩色输出
color = "Always"
edition = "2021"

View File

@ -47,25 +47,14 @@ impl IcalinguaStatus {
self.online_data = Some(online_data); self.online_data = Some(online_data);
} }
pub fn update_rooms(&mut self, rooms: Vec<Room>) { pub fn update_rooms(&mut self, rooms: Vec<Room>) { self.rooms = Some(rooms); }
self.rooms = Some(rooms);
}
pub fn update_login_status(&mut self, login: bool) { pub fn update_login_status(&mut self, login: bool) { self.login = login; }
self.login = login;
}
pub fn update_config(&mut self, config: IcaConfig) { pub fn update_config(&mut self, config: IcaConfig) { self.config = Some(config); }
self.config = Some(config);
}
pub fn get_online_data() -> &'static OnlineData { pub fn get_online_data() -> &'static OnlineData {
unsafe { unsafe { ClientStatus.online_data.as_ref().expect("online_data should be set") }
ClientStatus
.online_data
.as_ref()
.expect("online_data should be set")
}
} }
pub fn get_config() -> &'static IcaConfig { pub fn get_config() -> &'static IcaConfig {
@ -99,8 +88,5 @@ pub async fn sign_callback(payload: Payload, client: Client) {
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 client.emit("auth", sign).await.expect("Faild to send signin data");
.emit("auth", sign)
.await
.expect("Faild to send signin data");
} }

View File

@ -19,10 +19,6 @@ pub struct MessageFile {
} }
impl MessageFile { impl MessageFile {
pub fn get_name(&self) -> Option<&String> { pub fn get_name(&self) -> Option<&String> { self.name.as_ref() }
self.name.as_ref() pub fn get_fid(&self) -> Option<&String> { self.fid.as_ref() }
}
pub fn get_fid(&self) -> Option<&String> {
self.fid.as_ref()
}
} }

View File

@ -1,4 +1,3 @@
use crate::client::IcalinguaStatus;
use crate::data_struct::files::MessageFile; use crate::data_struct::files::MessageFile;
use crate::data_struct::{MessageId, RoomId, UserId}; use crate::data_struct::{MessageId, RoomId, UserId};
@ -7,6 +6,10 @@ use serde::{Deserialize, Serialize};
use serde_json::{json, Value as JsonValue}; use serde_json::{json, Value as JsonValue};
use tracing::warn; use tracing::warn;
pub mod msg_trait;
pub use msg_trait::MessageTrait;
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum At { pub enum At {
All, All,
@ -15,7 +18,7 @@ pub enum At {
} }
impl At { impl At {
/// new_from_json(&message["at"]) #[inline]
pub fn new_from_json(json: &JsonValue) -> Self { pub fn new_from_json(json: &JsonValue) -> Self {
match json { match json {
JsonValue::Bool(b) => Self::Bool(*b), JsonValue::Bool(b) => Self::Bool(*b),
@ -52,11 +55,40 @@ pub struct ReplyMessage {
pub sender_name: String, pub sender_name: String,
} }
/*
export default interface Message {
_id: string | number
senderId?: number
username: string
content: string
code?: string
timestamp?: string
date?: string
role?: string
file?: MessageFile
files: MessageFile[]
time?: number
replyMessage?: Message
at?: boolean | 'all'
deleted?: boolean
system?: boolean
mirai?: MessageMirai
reveal?: boolean
flash?: boolean
title?: string
anonymousId?: number
anonymousflag?: string
hide?: boolean
bubble_id?: number
subid?: number
head_img?: string
}*/
/// {"message": {"_id":"idddddd","anonymousId":null,"anonymousflag":null,"bubble_id":0,"content":"test","date":"2024/02/18","files":[],"role":"admin","senderId":123456,"subid":1,"time":1708267062000_i64,"timestamp":"22:37:42","title":"索引管理员","username":"shenjack"},"roomId":-123456} /// {"message": {"_id":"idddddd","anonymousId":null,"anonymousflag":null,"bubble_id":0,"content":"test","date":"2024/02/18","files":[],"role":"admin","senderId":123456,"subid":1,"time":1708267062000_i64,"timestamp":"22:37:42","title":"索引管理员","username":"shenjack"},"roomId":-123456}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct NewMessage { pub struct Message {
/// 房间 id // /// 房间 id
pub room_id: RoomId, // pub room_id: RoomId,
/// 消息 id /// 消息 id
pub msg_id: MessageId, pub msg_id: MessageId,
/// 发送者 id /// 发送者 id
@ -103,33 +135,29 @@ pub struct NewMessage {
pub raw_msg: JsonValue, pub raw_msg: JsonValue,
} }
impl NewMessage { impl Message {
pub fn new_from_json(json: &JsonValue) -> Self { pub fn new_from_json(json: &JsonValue) -> Self {
// room id 还是必定有的
let room_id = json["roomId"].as_i64().unwrap();
// message 本体也是
let message = json.get("message").unwrap();
// 消息 id // 消息 id
let msg_id = message["_id"].as_str().unwrap(); let msg_id = json["_id"].as_str().unwrap();
// 发送者 id (Optional) // 发送者 id (Optional)
let sender_id = message["senderId"].as_i64().unwrap_or(-1); let sender_id = json["senderId"].as_i64().unwrap_or(-1);
// 发送者名字 必有 // 发送者名字 必有
let sender_name = message["username"].as_str().unwrap(); let sender_name = json["username"].as_str().unwrap();
// 消息内容 // 消息内容
let content = message["content"].as_str().unwrap(); let content = json["content"].as_str().unwrap();
// xml / json 内容 // xml / json 内容
let code = message["code"].clone(); let code = json["code"].clone();
// 消息时间 (怎么这个也是可选啊(恼)) // 消息时间 (怎么这个也是可选啊(恼))
// 没有就取当前时间 // 没有就取当前时间
let current = chrono::Utc::now().naive_utc(); let current = chrono::Utc::now().naive_utc();
let time = message["time"] let time = json["time"]
.as_i64() .as_i64()
.map(|t| NaiveDateTime::from_timestamp_micros(t).unwrap_or(current)) .map(|t| NaiveDateTime::from_timestamp_micros(t).unwrap_or(current))
.unwrap_or(current); .unwrap_or(current);
// 身份 // 身份
let role = message["role"].as_str().unwrap_or("unknown"); let role = json["role"].as_str().unwrap_or("unknown");
// 文件 // 文件
let value_files = message["files"].as_array().unwrap_or(&Vec::new()).to_vec(); let value_files = json["files"].as_array().unwrap_or(&Vec::new()).to_vec();
let mut files = Vec::with_capacity(value_files.len()); let mut files = Vec::with_capacity(value_files.len());
for file in &value_files { for file in &value_files {
let file = serde_json::from_value::<MessageFile>(file.clone()); let file = serde_json::from_value::<MessageFile>(file.clone());
@ -138,44 +166,49 @@ impl NewMessage {
} }
} }
// 回复的消息 // 回复的消息
let reply: Option<ReplyMessage> = match message.get("replyMessage") { let reply: Option<ReplyMessage> = match json.get("replyMessage") {
Some(value) => match serde_json::from_value::<ReplyMessage>(value.clone()) { Some(value) => {
if !value.is_null() {
match serde_json::from_value::<ReplyMessage>(value.clone()) {
Ok(reply) => Some(reply), Ok(reply) => Some(reply),
Err(e) => { Err(e) => {
warn!("Failed to parse reply message: {}", e); warn!("Failed to parse reply message: {}", e);
None None
} }
}, }
} else {
None
}
}
None => None, None => None,
}; };
// At // At
let at = At::new_from_json(&message["at"]); let at = At::new_from_json(&json["at"]);
// 是否已撤回 // 是否已撤回
let deleted = message["deleted"].as_bool().unwrap_or(false); let deleted = json["deleted"].as_bool().unwrap_or(false);
// 是否是系统消息 // 是否是系统消息
let system = message["system"].as_bool().unwrap_or(false); let system = json["system"].as_bool().unwrap_or(false);
// mirai // mirai
let mirai = message["mirai"].clone(); let mirai = json["mirai"].clone();
// reveal // reveal
let reveal = message["reveal"].as_bool().unwrap_or(false); let reveal = json["reveal"].as_bool().unwrap_or(false);
// flash // flash
let flash = message["flash"].as_bool().unwrap_or(false); let flash = json["flash"].as_bool().unwrap_or(false);
// "群主授予的头衔" // "群主授予的头衔"
let title = message["title"].as_str().unwrap_or(""); let title = json["title"].as_str().unwrap_or("");
// anonymous id // anonymous id
let anonymous_id = message["anonymousId"].as_i64(); let anonymous_id = json["anonymousId"].as_i64();
// 是否已被隐藏 // 是否已被隐藏
let hide = message["hide"].as_bool().unwrap_or(false); let hide = json["hide"].as_bool().unwrap_or(false);
// 气泡 id // 气泡 id
let bubble_id = message["bubble_id"].as_i64().unwrap_or(1); let bubble_id = json["bubble_id"].as_i64().unwrap_or(1);
// 子? id // 子? id
let subid = message["subid"].as_i64().unwrap_or(1); let subid = json["subid"].as_i64().unwrap_or(1);
// 头像 img? // 头像 img?
let head_img = message["head_img"].clone(); let head_img = json["head_img"].clone();
// 原始消息 // 原始消息
let raw_msg = json["message"].clone(); let raw_msg = json["message"].clone();
Self { Self {
room_id,
msg_id: msg_id.to_string(), msg_id: msg_id.to_string(),
sender_id, sender_id,
sender_name: sender_name.to_string(), sender_name: sender_name.to_string(),
@ -204,8 +237,9 @@ impl NewMessage {
pub fn output(&self) -> String { pub fn output(&self) -> String {
format!( format!(
// >10 >10 >15 // >10 >10 >15
"{:>10}|{:>12}|{:<20}|{}", // >10 >15
self.room_id, self.sender_id, self.sender_name, self.content "{:>12}|{:<20}|{}",
self.sender_id, self.sender_name, self.content
) )
} }
@ -221,28 +255,27 @@ impl NewMessage {
} }
} }
/// 获取回复
pub fn get_reply(&self) -> Option<&ReplyMessage> { self.reply.as_ref() }
pub fn get_reply_mut(&mut self) -> Option<&mut ReplyMessage> { self.reply.as_mut() }
}
/// 这才是 NewMessage
#[derive(Debug, Clone, Deserialize)]
pub struct NewMessage {
#[serde(rename = "roomId")]
pub room_id: RoomId,
#[serde(rename = "message")]
pub msg: Message,
}
impl NewMessage {
pub fn new(room_id: RoomId, msg: Message) -> Self { Self { room_id, msg } }
/// 创建一条对这条消息的回复 /// 创建一条对这条消息的回复
pub fn reply_with(&self, content: &String) -> SendMessage { pub fn reply_with(&self, content: &String) -> SendMessage {
SendMessage::new(content.clone(), self.room_id, Some(self.as_reply())) SendMessage::new(content.clone(), self.room_id, Some(self.msg.as_reply()))
}
/// 是否是回复
pub fn is_reply(&self) -> bool {
self.reply.is_some()
}
pub fn is_from_self(&self) -> bool {
let qq_id = IcalinguaStatus::get_online_data().qqid;
self.sender_id == qq_id
}
/// 获取回复
pub fn get_reply(&self) -> Option<&ReplyMessage> {
self.reply.as_ref()
}
pub fn get_reply_mut(&mut self) -> Option<&mut ReplyMessage> {
self.reply.as_mut()
} }
} }
@ -267,58 +300,56 @@ impl SendMessage {
} }
} }
pub fn as_value(&self) -> JsonValue { pub fn as_value(&self) -> JsonValue { serde_json::to_value(self).unwrap() }
serde_json::to_value(self).unwrap()
}
} }
#[cfg(test)] // #[cfg(test)]
mod test { // mod test {
use serde_json::json; // use serde_json::json;
use super::*; // use super::*;
#[test] // #[test]
fn test_new_from_json() { // fn test_new_from_json() {
let value = json!({"message": {"_id":"idddddd","anonymousId":null,"anonymousflag":null,"bubble_id":0,"content":"test","date":"2024/02/18","files":[],"role":"admin","senderId":123456,"subid":1,"time":1708267062000_i64,"timestamp":"22:37:42","title":"索引管理员","username":"shenjack"},"roomId":-123456}); // let value = json!({"message": {"_id":"idddddd","anonymousId":null,"anonymousflag":null,"bubble_id":0,"content":"test","date":"2024/02/18","files":[],"role":"admin","senderId":123456,"subid":1,"time":1708267062000_i64,"timestamp":"22:37:42","title":"索引管理员","username":"shenjack"},"roomId":-123456});
let new_message = NewMessage::new_from_json(&value); // let new_message = Message::new_from_json(&value);
assert_eq!(new_message.msg_id, "idddddd"); // assert_eq!(new_message.msg_id, "idddddd");
assert_eq!(new_message.sender_id, 123456); // assert_eq!(new_message.sender_id, 123456);
assert_eq!(new_message.sender_name, "shenjack"); // assert_eq!(new_message.sender_name, "shenjack");
assert_eq!(new_message.content, "test"); // assert_eq!(new_message.content, "test");
assert_eq!(new_message.role, "admin"); // assert_eq!(new_message.role, "admin");
assert_eq!( // assert_eq!(
new_message.time, // new_message.time,
NaiveDateTime::from_timestamp_micros(1708267062000_i64).unwrap() // NaiveDateTime::from_timestamp_micros(1708267062000_i64).unwrap()
); // );
assert!(new_message.files.is_empty()); // assert!(new_message.files.is_empty());
assert!(new_message.get_reply().is_none()); // assert!(new_message.get_reply().is_none());
assert!(!new_message.is_reply()); // assert!(!new_message.is_reply());
assert!(!new_message.deleted); // assert!(!new_message.deleted);
assert!(!new_message.system); // assert!(!new_message.system);
assert!(!new_message.reveal); // assert!(!new_message.reveal);
assert!(!new_message.flash); // assert!(!new_message.flash);
assert_eq!(new_message.title, "索引管理员"); // assert_eq!(new_message.title, "索引管理员");
assert!(new_message.anonymous_id.is_none()); // assert!(new_message.anonymous_id.is_none());
assert!(!new_message.hide); // assert!(!new_message.hide);
assert_eq!(new_message.bubble_id, 0); // assert_eq!(new_message.bubble_id, 0);
assert_eq!(new_message.subid, 1); // assert_eq!(new_message.subid, 1);
assert!(new_message.head_img.is_null()); // assert!(new_message.head_img.is_null());
} // }
#[test] // #[test]
fn test_parse_reply() { // fn test_parse_reply() {
let value = json!({"message": {"_id":"idddddd","anonymousId":null,"anonymousflag":null,"bubble_id":0,"content":"test","date":"2024/02/18","files":[],"role":"admin","senderId":123456,"subid":1,"time":1708267062000_i64,"timestamp":"22:37:42","title":"索引管理员","username":"shenjack", "replyMessage": {"content": "test", "username": "jackyuanjie", "files": [], "_id": "adwadaw"}},"roomId":-123456}); // let value = json!({"message": {"_id":"idddddd","anonymousId":null,"anonymousflag":null,"bubble_id":0,"content":"test","date":"2024/02/18","files":[],"role":"admin","senderId":123456,"subid":1,"time":1708267062000_i64,"timestamp":"22:37:42","title":"索引管理员","username":"shenjack", "replyMessage": {"content": "test", "username": "jackyuanjie", "files": [], "_id": "adwadaw"}},"roomId":-123456});
let new_message = NewMessage::new_from_json(&value); // let new_message = Message::new_from_json(&value);
assert_eq!(new_message.get_reply().unwrap().sender_name, "jackyuanjie"); // assert_eq!(new_message.get_reply().unwrap().sender_name, "jackyuanjie");
assert_eq!(new_message.get_reply().unwrap().content, "test"); // assert_eq!(new_message.get_reply().unwrap().content, "test");
assert_eq!(new_message.get_reply().unwrap().msg_id, "adwadaw"); // assert_eq!(new_message.get_reply().unwrap().msg_id, "adwadaw");
assert!(new_message // assert!(new_message
.get_reply() // .get_reply()
.unwrap() // .unwrap()
.files // .files
.as_array() // .as_array()
.unwrap() // .unwrap()
.is_empty()); // .is_empty());
} // }
} // }

View File

@ -0,0 +1,126 @@
use std::fmt::Display;
use chrono::NaiveDateTime;
use serde::{Deserialize, Serialize};
use serde_json::Value as JsonValue;
use crate::client::IcalinguaStatus;
use crate::data_struct::messages::{At, Message, NewMessage};
use crate::data_struct::{MessageId, UserId};
impl Serialize for At {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::ser::Serializer,
{
match self {
At::All => serializer.serialize_str("all"),
At::Bool(b) => serializer.serialize_bool(*b),
At::None => serializer.serialize_none(),
}
}
}
impl<'de> Deserialize<'de> for At {
fn deserialize<D>(deserializer: D) -> Result<At, D::Error>
where
D: serde::de::Deserializer<'de>,
{
let value = JsonValue::deserialize(deserializer)?;
Ok(At::new_from_json(&value))
}
}
pub trait MessageTrait {
fn is_reply(&self) -> bool;
fn is_from_self(&self) -> bool {
let qq_id = IcalinguaStatus::get_online_data().qqid;
self.sender_id() == qq_id
}
fn msg_id(&self) -> &MessageId;
fn sender_id(&self) -> UserId;
fn sender_name(&self) -> &String;
fn content(&self) -> &String;
fn time(&self) -> &NaiveDateTime;
fn role(&self) -> &String;
fn has_files(&self) -> bool;
fn deleted(&self) -> bool;
fn system(&self) -> bool;
fn reveal(&self) -> bool;
fn flash(&self) -> bool;
fn title(&self) -> &String;
fn anonymous_id(&self) -> Option<i64>;
fn hide(&self) -> bool;
fn bubble_id(&self) -> i64;
fn subid(&self) -> i64;
}
impl MessageTrait for Message {
fn is_reply(&self) -> bool { self.reply.is_some() }
fn msg_id(&self) -> &MessageId { &self.msg_id }
fn sender_id(&self) -> UserId { self.sender_id }
fn sender_name(&self) -> &String { &self.sender_name }
fn content(&self) -> &String { &self.content }
fn time(&self) -> &NaiveDateTime { &self.time }
fn role(&self) -> &String { &self.role }
fn has_files(&self) -> bool { !self.files.is_empty() }
fn deleted(&self) -> bool { self.deleted }
fn system(&self) -> bool { self.system }
fn reveal(&self) -> bool { self.reveal }
fn flash(&self) -> bool { self.flash }
fn title(&self) -> &String { &self.title }
fn anonymous_id(&self) -> Option<i64> { self.anonymous_id }
fn hide(&self) -> bool { self.hide }
fn bubble_id(&self) -> i64 { self.bubble_id }
fn subid(&self) -> i64 { self.subid }
}
impl<'de> Deserialize<'de> for Message {
fn deserialize<D>(deserializer: D) -> Result<Message, D::Error>
where
D: serde::de::Deserializer<'de>,
{
let value = JsonValue::deserialize(deserializer)?;
Ok(Message::new_from_json(&value))
}
}
impl Display for Message {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}|{}|{}|{}", self.msg_id(), self.sender_id, self.sender_name, self.content)
}
}
impl MessageTrait for NewMessage {
fn is_reply(&self) -> bool { self.msg.reply.is_some() }
fn msg_id(&self) -> &MessageId { &self.msg.msg_id }
fn sender_id(&self) -> UserId { self.msg.sender_id }
fn sender_name(&self) -> &String { &self.msg.sender_name }
fn content(&self) -> &String { &self.msg.content }
fn time(&self) -> &NaiveDateTime { &self.msg.time }
fn role(&self) -> &String { &self.msg.role }
fn has_files(&self) -> bool { !self.msg.files.is_empty() }
fn deleted(&self) -> bool { self.msg.deleted }
fn system(&self) -> bool { self.msg.system }
fn reveal(&self) -> bool { self.msg.reveal }
fn flash(&self) -> bool { self.msg.flash }
fn title(&self) -> &String { &self.msg.title }
fn anonymous_id(&self) -> Option<i64> { self.msg.anonymous_id }
fn hide(&self) -> bool { self.msg.hide }
fn bubble_id(&self) -> i64 { self.msg.bubble_id }
fn subid(&self) -> i64 { self.msg.subid }
}
impl Display for NewMessage {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}|{}|{}|{}|{}",
self.msg_id(),
self.room_id,
self.msg.sender_id,
self.msg.sender_name,
self.msg.content
)
}
}

View File

@ -4,6 +4,22 @@ pub mod messages;
pub mod all_rooms; pub mod all_rooms;
pub mod online_data; pub mod online_data;
/// 房间 id
/// 群聊 < 0
/// 私聊 > 0
pub type RoomId = i64; pub type RoomId = i64;
pub type UserId = i64; pub type UserId = i64;
pub type MessageId = String; pub type MessageId = String;
pub trait RoomIdTrait {
fn is_room(&self) -> bool;
fn is_chat(&self) -> bool { !self.is_room() }
fn as_room_id(&self) -> RoomId;
fn as_chat_id(&self) -> RoomId;
}
impl RoomIdTrait for RoomId {
fn is_room(&self) -> bool { (*self).is_negative() }
fn as_room_id(&self) -> RoomId { -(*self).abs() }
fn as_chat_id(&self) -> RoomId { (*self).abs() }
}

View File

@ -197,10 +197,7 @@ mod tests {
assert_eq!(online_data.online, true); assert_eq!(online_data.online, true);
assert_eq!(online_data.qqid, 123456); assert_eq!(online_data.qqid, 123456);
assert_eq!(online_data.icalingua_info.ica_version, "2.11.1"); assert_eq!(online_data.icalingua_info.ica_version, "2.11.1");
assert_eq!( assert_eq!(online_data.icalingua_info.os_info, "Linux c038fad79f13 4.4.302+");
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.resident_set_size, "95.43MB");
assert_eq!(online_data.icalingua_info.heap_used, "37.31MB"); 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.load, "4.23 2.15 1.59");

View File

@ -5,7 +5,7 @@ use tracing::{info, warn};
use crate::client::{send_message, IcalinguaStatus}; use crate::client::{send_message, IcalinguaStatus};
use crate::data_struct::all_rooms::Room; use crate::data_struct::all_rooms::Room;
use crate::data_struct::messages::NewMessage; use crate::data_struct::messages::{Message, MessageTrait, NewMessage};
use crate::data_struct::online_data::OnlineData; use crate::data_struct::online_data::OnlineData;
use crate::{py, VERSION}; use crate::{py, VERSION};
@ -26,35 +26,47 @@ 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 message = NewMessage::new_from_json(value); let message: NewMessage = serde_json::from_value(value.clone()).unwrap();
// 检测是否在过滤列表内 // 检测是否在过滤列表内
if IcalinguaStatus::get_config() if IcalinguaStatus::get_config().filter_list.contains(&message.msg.sender_id) {
.filter_list
.contains(&message.sender_id)
{
return; return;
} }
info!("add_message {}", message.output().cyan()); info!("add_message {}", message.to_string().cyan());
// info!("add_message {}", format!("{:#?}", message).cyan()); // info!("add_message {}", format!("{:#?}", message).cyan());
// 就在这里处理掉最基本的消息 // 就在这里处理掉最基本的消息
// 之后的处理交给插件 // 之后的处理交给插件
if message.content.eq("/bot-rs") && !message.is_from_self() && !message.is_reply() { if message.content().eq("/bot-rs") && !message.is_from_self() && !message.is_reply() {
let reply = message.reply_with(&format!("ica-async-rs pong v{}", VERSION)); let reply = message.reply_with(&format!("ica-async-rs pong v{}", VERSION));
send_message(&client, &reply).await; send_message(&client, &reply).await;
} }
// python 插件 // python 插件
py::new_message_py(&message, &client).await; py::call::new_message_py(&message, &client).await;
}
}
}
/// 理论上不会用到 (因为依赖一个客户端去请求)
/// 但反正实际上还是我去请求, 所以只是暂时
/// 加载一个房间的所有消息
pub async fn set_messages(payload: Payload, _client: Client) {
if let Payload::Text(values) = payload {
if let Some(value) = values.first() {
let messages: Vec<Message> = serde_json::from_value(value["messages"].clone()).unwrap();
let room_id = value["roomId"].as_i64().unwrap();
info!("set_messages {} len: {}", room_id.to_string().cyan(), messages.len());
} }
} }
} }
/// 撤回消息 /// 撤回消息
pub async fn delete_message(payload: Payload, _client: Client) { pub async fn delete_message(payload: Payload, client: Client) {
if let Payload::Text(values) = payload { if let Payload::Text(values) = payload {
// 消息 id // 消息 id
if let Some(value) = values.first() { if let Some(value) = values.first() {
if let Some(msg_id) = value.as_str() { if let Some(msg_id) = value.as_str() {
info!("delete_message {}", format!("{}", msg_id).yellow()); info!("delete_message {}", format!("{}", msg_id).yellow());
py::call::delete_message_py(msg_id.to_string(), &client).await;
} }
} }
} }
@ -64,10 +76,8 @@ pub async fn update_all_room(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() {
if let Some(raw_rooms) = value.as_array() { if let Some(raw_rooms) = value.as_array() {
let rooms: Vec<Room> = raw_rooms let rooms: Vec<Room> =
.iter() raw_rooms.iter().map(|room| Room::new_from_json(room)).collect();
.map(|room| Room::new_from_json(room))
.collect();
unsafe { unsafe {
crate::ClientStatus.update_rooms(rooms.clone()); crate::ClientStatus.update_rooms(rooms.clone());
} }
@ -77,6 +87,22 @@ pub async fn update_all_room(payload: Payload, _client: Client) {
} }
} }
pub async fn succes_message(payload: Payload, _client: Client) {
if let Payload::Text(values) = payload {
if let Some(value) = values.first() {
info!("messageSuccess {}", value.to_string().green());
}
}
}
pub async fn failed_message(payload: Payload, _client: Client) {
if let Payload::Text(values) = payload {
if let Some(value) = values.first() {
warn!("messageFailed {}", value.to_string().red());
}
}
}
/// 所有 /// 所有
pub async fn any_event(event: Event, payload: Payload, _client: Client) { pub async fn any_event(event: Event, payload: Payload, _client: Client) {
let handled = vec![ let handled = vec![
@ -89,11 +115,16 @@ pub async fn any_event(event: Event, payload: Payload, _client: Client) {
"addMessage", "addMessage",
"deleteMessage", "deleteMessage",
"setAllRooms", "setAllRooms",
"setMessages",
// 也许以后会用到
"messageSuccess",
"messageFailed",
"setAllChatGroups",
// 忽略的 // 忽略的
"notify", "notify",
"closeLoading", // 发送消息/加载新聊天 有一个 loading "closeLoading", // 发送消息/加载新聊天 有一个 loading
"updateRoom", "updateRoom",
"syncRead", // "syncRead",
]; ];
match &event { match &event {
Event::Custom(event_name) => { Event::Custom(event_name) => {

View File

@ -35,9 +35,7 @@ macro_rules! wrap_any_callback {
#[tokio::main] #[tokio::main]
async fn main() { async fn main() {
tracing_subscriber::fmt() tracing_subscriber::fmt().with_max_level(tracing::Level::DEBUG).init();
.with_max_level(tracing::Level::DEBUG)
.init();
info!("ica-async-rs v{}", VERSION); info!("ica-async-rs v{}", VERSION);
// 从命令行获取 host 和 key // 从命令行获取 host 和 key
@ -55,8 +53,11 @@ async fn main() {
.on("message", wrap_callback!(events::connect_callback)) .on("message", wrap_callback!(events::connect_callback))
.on("authSucceed", wrap_callback!(events::connect_callback)) .on("authSucceed", wrap_callback!(events::connect_callback))
.on("authFailed", wrap_callback!(events::connect_callback)) .on("authFailed", wrap_callback!(events::connect_callback))
.on("messageSuccess", wrap_callback!(events::succes_message))
.on("messageFailed", wrap_callback!(events::failed_message))
.on("onlineData", wrap_callback!(events::get_online_data)) .on("onlineData", wrap_callback!(events::get_online_data))
.on("setAllRooms", wrap_callback!(events::update_all_room)) .on("setAllRooms", wrap_callback!(events::update_all_room))
.on("setMessages", wrap_callback!(events::set_messages))
.on("addMessage", wrap_callback!(events::add_message)) .on("addMessage", wrap_callback!(events::add_message))
.on("deleteMessage", wrap_callback!(events::delete_message)) .on("deleteMessage", wrap_callback!(events::delete_message))
.connect() .connect()
@ -74,9 +75,8 @@ async fn main() {
); );
std::thread::sleep(Duration::from_secs(1)); std::thread::sleep(Duration::from_secs(1));
info!("发送启动消息到房间: {}", room); info!("发送启动消息到房间: {}", room);
if let Err(e) = socket if let Err(e) =
.emit("sendMessage", serde_json::to_value(startup_msg).unwrap()) socket.emit("sendMessage", serde_json::to_value(startup_msg).unwrap()).await
.await
{ {
info!("启动信息发送失败 房间:{}|e:{}", room, e); info!("启动信息发送失败 房间:{}|e:{}", room, e);
} }

85
ica-rs/src/py/call.rs Normal file
View File

@ -0,0 +1,85 @@
use std::path::PathBuf;
use pyo3::prelude::*;
use rust_socketio::asynchronous::Client;
use tracing::{debug, warn};
use crate::data_struct::messages::NewMessage;
use crate::data_struct::MessageId;
use crate::py::{class, verify_plugins, PyStatus};
pub fn get_func<'py>(py_module: &'py PyAny, path: &PathBuf, name: &'py str) -> Option<&'py PyAny> {
// 要处理的情况:
// 1. 有这个函数
// 2. 没有这个函数
// 3. 函数不是 Callable
match py_module.hasattr(name) {
Ok(contain) => {
if contain {
match py_module.getattr(name) {
Ok(func) => {
if func.is_callable() {
Some(func)
} else {
warn!("function<{}>: {:#?} in {:?} is not callable", name, func, path);
None
}
}
Err(e) => {
warn!("failed to get function<{}> from {:?}: {:?}", name, path, e);
None
}
}
} else {
debug!("no function<{}> in module {:?}", name, path);
None
}
}
Err(e) => {
warn!("failed to check function<{}> from {:?}: {:?}", name, path, e);
None
}
}
}
/// 执行 new message 的 python 插件
pub async fn new_message_py(message: &NewMessage, client: &Client) {
// 验证插件是否改变
verify_plugins();
let plugins = PyStatus::get_files();
for (path, (_, py_module)) in plugins.iter() {
let msg = class::NewMessagePy::new(message);
let client = class::IcaClientPy::new(client);
// 甚至实际上压根不需要await这个spawn, 直接让他自己跑就好了(离谱)
tokio::spawn(async move {
Python::with_gil(|py| {
let args = (msg, client);
if let Some(py_func) = get_func(py_module.as_ref(py), &path, "on_message") {
if let Err(e) = py_func.call1(args) {
warn!("failed to call function<on_message>: {:?}", e);
}
}
})
});
}
}
pub async fn delete_message_py(msg_id: MessageId, client: &Client) {
verify_plugins();
let plugins = PyStatus::get_files();
for (path, (_, py_module)) in plugins.iter() {
let msg_id = msg_id.clone();
let client = class::IcaClientPy::new(client);
tokio::spawn(async move {
Python::with_gil(|py| {
let args = (msg_id.clone(), client);
if let Some(py_func) = get_func(py_module.as_ref(py), &path, "on_delete_message") {
if let Err(e) = py_func.call1(args) {
warn!("failed to call function<on_delete_message>: {:?}", e);
}
}
})
});
}
}

View File

@ -4,7 +4,7 @@ use tokio::runtime::Runtime;
use tracing::{debug, info, warn}; use tracing::{debug, info, warn};
use crate::client::send_message; use crate::client::send_message;
use crate::data_struct::messages::{NewMessage, ReplyMessage, SendMessage}; use crate::data_struct::messages::{MessageTrait, NewMessage, ReplyMessage, SendMessage};
use crate::data_struct::MessageId; use crate::data_struct::MessageId;
use crate::ClientStatus; use crate::ClientStatus;
@ -15,14 +15,10 @@ pub struct IcaStatusPy {}
#[pymethods] #[pymethods]
impl IcaStatusPy { impl IcaStatusPy {
#[new] #[new]
pub fn py_new() -> Self { pub fn py_new() -> Self { Self {} }
Self {}
}
#[getter] #[getter]
pub fn get_login(&self) -> bool { pub fn get_login(&self) -> bool { unsafe { ClientStatus.login } }
unsafe { ClientStatus.login }
}
#[getter] #[getter]
pub fn get_online(&self) -> bool { pub fn get_online(&self) -> bool {
@ -106,9 +102,7 @@ impl IcaStatusPy {
} }
impl IcaStatusPy { impl IcaStatusPy {
pub fn new() -> Self { pub fn new() -> Self { Self {} }
Self {}
}
} }
#[derive(Clone)] #[derive(Clone)]
@ -124,35 +118,21 @@ impl NewMessagePy {
SendMessagePy::new(self.msg.reply_with(&content)) SendMessagePy::new(self.msg.reply_with(&content))
} }
pub fn __str__(&self) -> String { pub fn __str__(&self) -> String { format!("{:?}", self.msg) }
format!("{:?}", self.msg)
}
#[getter] #[getter]
pub fn get_id(&self) -> MessageId { pub fn get_id(&self) -> MessageId { self.msg.msg_id().clone() }
self.msg.msg_id.clone()
}
#[getter] #[getter]
pub fn get_content(&self) -> String { pub fn get_content(&self) -> String { self.msg.content().clone() }
self.msg.content.clone()
}
#[getter] #[getter]
pub fn get_sender_id(&self) -> i64 { pub fn get_sender_id(&self) -> i64 { self.msg.sender_id() }
self.msg.sender_id
}
#[getter] #[getter]
pub fn get_is_from_self(&self) -> bool { pub fn get_is_from_self(&self) -> bool { self.msg.is_from_self() }
self.msg.is_from_self()
}
#[getter] #[getter]
pub fn get_is_reply(&self) -> bool { pub fn get_is_reply(&self) -> bool { self.msg.is_reply() }
self.msg.is_reply()
}
} }
impl NewMessagePy { impl NewMessagePy {
pub fn new(msg: &NewMessage) -> Self { pub fn new(msg: &NewMessage) -> Self { Self { msg: msg.clone() } }
Self { msg: msg.clone() }
}
} }
#[pyclass] #[pyclass]
@ -163,15 +143,11 @@ pub struct ReplyMessagePy {
#[pymethods] #[pymethods]
impl ReplyMessagePy { impl ReplyMessagePy {
pub fn __str__(&self) -> String { pub fn __str__(&self) -> String { format!("{:?}", self.msg) }
format!("{:?}", self.msg)
}
} }
impl ReplyMessagePy { impl ReplyMessagePy {
pub fn new(msg: ReplyMessage) -> Self { pub fn new(msg: ReplyMessage) -> Self { Self { msg } }
Self { msg }
}
} }
#[derive(Clone)] #[derive(Clone)]
@ -183,9 +159,7 @@ pub struct SendMessagePy {
#[pymethods] #[pymethods]
impl SendMessagePy { impl SendMessagePy {
pub fn __str__(&self) -> String { pub fn __str__(&self) -> String { format!("{:?}", self.msg) }
format!("{:?}", self.msg)
}
/// 设置消息内容 /// 设置消息内容
/// 用于链式调用 /// 用于链式调用
pub fn with_content(&mut self, content: String) -> Self { pub fn with_content(&mut self, content: String) -> Self {
@ -193,19 +167,13 @@ impl SendMessagePy {
self.clone() self.clone()
} }
#[getter] #[getter]
pub fn get_content(&self) -> String { pub fn get_content(&self) -> String { self.msg.content.clone() }
self.msg.content.clone()
}
#[setter] #[setter]
pub fn set_content(&mut self, content: String) { pub fn set_content(&mut self, content: String) { self.msg.content = content; }
self.msg.content = content;
}
} }
impl SendMessagePy { impl SendMessagePy {
pub fn new(msg: SendMessage) -> Self { pub fn new(msg: SendMessage) -> Self { Self { msg } }
Self { msg }
}
} }
#[derive(Clone)] #[derive(Clone)]
@ -238,6 +206,9 @@ impl IcaClientPy {
}) })
} }
#[getter]
pub fn get_status(&self) -> IcaStatusPy { IcaStatusPy::new() }
pub fn debug(&self, content: String) { pub fn debug(&self, content: String) {
debug!("{}", content); debug!("{}", content);
} }

View File

@ -1,16 +1,14 @@
pub mod call;
pub mod class; pub mod class;
use std::time::SystemTime; use std::time::SystemTime;
use std::{collections::HashMap, path::PathBuf}; use std::{collections::HashMap, path::PathBuf};
use futures_util::future::join_all;
use pyo3::prelude::*; use pyo3::prelude::*;
use rust_socketio::asynchronous::Client;
use tracing::{debug, info, warn}; use tracing::{debug, info, warn};
use crate::client::IcalinguaStatus; use crate::client::IcalinguaStatus;
use crate::config::IcaConfig; use crate::config::IcaConfig;
use crate::data_struct::messages::NewMessage;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct PyStatus { pub struct PyStatus {
@ -109,11 +107,7 @@ pub fn load_py_plugins(path: &PathBuf) {
pub fn verify_plugins() { pub fn verify_plugins() {
let mut need_reload_files: Vec<PathBuf> = Vec::new(); let mut need_reload_files: Vec<PathBuf> = Vec::new();
let plugin_path = IcalinguaStatus::get_config() let plugin_path = IcalinguaStatus::get_config().py_plugin_path.as_ref().unwrap().to_owned();
.py_plugin_path
.as_ref()
.unwrap()
.to_owned();
for entry in std::fs::read_dir(&plugin_path).unwrap() { for entry in std::fs::read_dir(&plugin_path).unwrap() {
if let Ok(entry) = entry { if let Ok(entry) = entry {
let path = entry.path(); let path = entry.path();
@ -194,124 +188,3 @@ pub fn init_py(config: &IcaConfig) {
info!("python inited") info!("python inited")
} }
/// 执行 new message 的 python 插件
pub async fn new_message_py(message: &NewMessage, client: &Client) {
// 验证插件是否改变
verify_plugins();
let plugins = PyStatus::get_files();
// let tasks: Vec<_> = plugins.iter().map(|(path, (_, py_module))| {
// let msg = class::NewMessagePy::new(message);
// let client = class::IcaClientPy::new(client);
// let (cancel_tx, cancel_rx) = tokio::sync::oneshot::channel();
// let task = tokio::spawn(async move {
// tokio::select! {
// _ = tokio::spawn(async move {Python::with_gil(|py| {
// let args = (msg, client);
// let async_py_func = py_module.getattr(py, "on_message");
// match async_py_func {
// Ok(async_py_func) => match async_py_func.as_ref(py).call1(args) {
// Err(e) => {
// warn!("get a PyErr when call on_message from {:?}: {:?}", path, e);
// }
// _ => (),
// },
// Err(e) => {
// warn!("failed to get on_message function: {:?}", e);
// }
// }
// })}) => (),
// _ = cancel_rx => (),
// }
// });
// (task, cancel_tx)
// }).collect();
// let timeout = tokio::time::sleep(std::time::Duration::from_secs(5));
// tokio::select! {
// _ = join_all(tasks.map(|(task, _)| task)) => (),
// _ = timeout => {
// warn!("timeout when join all tasks");
// for (_, cancel_tx) in &tasks {
// let _ = cancel_tx.send(());
// }
// }
// }
// for (path, (_, py_module)) in plugins.iter() {
// let msg = class::NewMessagePy::new(message);
// let client = class::IcaClientPy::new(client);
// let task = tokio::spawn(async move {
// Python::with_gil(|py| {
// let args = (msg, client);
// let async_py_func = py_module.getattr(py, "on_message");
// match async_py_func {
// Ok(async_py_func) => match async_py_func.as_ref(py).call1(args) {
// Err(e) => {
// warn!("get a PyErr when call on_message from {:?}: {:?}", path, e);
// }
// _ => (),
// },
// Err(e) => {
// warn!("failed to get on_message function: {:?}", e);
// }
// }
// })
// });
// tokio::select! {
// _ = task => (),
// _ = tokio::time::sleep(std::time::Duration::from_secs(1)) => {
// warn!("timeout when join all tasks");
// // task.abort();
// }
// }
// }
let mut tasks = Vec::with_capacity(plugins.len());
for (path, (_, py_module)) in plugins.iter() {
let msg = class::NewMessagePy::new(message);
let client = class::IcaClientPy::new(client);
let task = tokio::spawn(async move {
Python::with_gil(|py| {
let args = (msg, client);
let async_py_func = py_module.getattr(py, "on_message");
match async_py_func {
Ok(async_py_func) => match async_py_func.as_ref(py).call1(args) {
Err(e) => {
warn!("get a PyErr when call on_message from {:?}: {:?}", path, e);
}
_ => (),
},
Err(e) => {
warn!("failed to get on_message function: {:?}", e);
}
}
})
});
tasks.push(task);
}
// 等待所有的插件执行完毕
// 超时时间为 0.1 秒
// ~~ 超时则取消所有的任务 ~~
// 超时就超时了……, 就让他跑着了……
// 主要是, 这玩意是同步的 还没法取消
let wait_time = std::time::Duration::from_millis(100);
let awaits = join_all(tasks);
let timeout = tokio::time::sleep(wait_time.clone());
let await_task = tokio::time::timeout(wait_time.clone(), awaits);
tokio::select! {
_ = await_task => (),
_ = timeout => {
warn!("timeout when join all tasks");
// for task in tasks {
// task.abort();
// }
}
}
// match tokio::time::timeout(wait_time.clone(), awaits).await {
// Ok(_) => (),
// Err(e) => {
// warn!("timeout when join all tasks: {:?}", e);
// }
// }
}

10
news.md
View File

@ -1,5 +1,15 @@
# 更新日志 # 更新日志
## 0.4.10
好家伙, 我感觉都快能叫 0.5 了
修改了一些内部数据结构, 使得插件更加稳定
添加了 `rustfmt.toml` 用于格式化代码
**注意**: 请在提交代码前使用 `cargo +nightly fmt` 格式化代码
修复了 `Message` 解析 `replyMessage` 字段是 如果是 null 则会解析失败的问题
## 0.4.9 ## 0.4.9
修复了 Python 插件运行错误会导致整个程序崩溃的问题 修复了 Python 插件运行错误会导致整个程序崩溃的问题