mirror of
http://shenjack.top:5100/shenjack/icalingua-python-bot.git
synced 2025-04-20 17:09:54 +08:00
Compare commits
17 Commits
3ed0f5af1e
...
9f5956e77a
Author | SHA1 | Date | |
---|---|---|---|
9f5956e77a | |||
f2624dbcca | |||
b41617bb06 | |||
29f6b2efaf | |||
5381ef598a | |||
8448b03d83 | |||
4b3da3b85f | |||
8b2a8ee8d2 | |||
fe06356bea | |||
03fdcc300b | |||
95c2cc377a | |||
63e18e8eab | |||
16ff8f534e | |||
4bad0c95c5 | |||
559de2e2f6 | |||
85608570bf | |||
0420cf36b2 |
|
@ -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
|
||||||
|
|
|
@ -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:
|
||||||
|
# ...
|
||||||
|
|
|
@ -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
23
ica-rs/rustfmt.toml
Normal 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"
|
|
@ -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");
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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());
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
126
ica-rs/src/data_struct/messages/msg_trait.rs
Normal file
126
ica-rs/src/data_struct/messages/msg_trait.rs
Normal 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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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() }
|
||||||
|
}
|
||||||
|
|
|
@ -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");
|
||||||
|
|
|
@ -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) => {
|
||||||
|
|
|
@ -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
85
ica-rs/src/py/call.rs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
10
news.md
|
@ -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 插件运行错误会导致整个程序崩溃的问题
|
||||||
|
|
Loading…
Reference in New Issue
Block a user