diff --git a/Cargo.lock b/Cargo.lock index a97ceaa..215bec9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -659,7 +659,7 @@ dependencies = [ [[package]] name = "ica-rs" -version = "0.6.5" +version = "0.6.6" dependencies = [ "anyhow", "base64 0.22.1", diff --git a/ica-rs/Cargo.toml b/ica-rs/Cargo.toml index 03441ce..f228099 100644 --- a/ica-rs/Cargo.toml +++ b/ica-rs/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ica-rs" -version = "0.6.5" +version = "0.6.6" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/ica-rs/ica_typing.py b/ica-rs/ica_typing.py index b355ec1..a772881 100644 --- a/ica-rs/ica_typing.py +++ b/ica-rs/ica_typing.py @@ -188,9 +188,9 @@ class TailchatReciveMessage: @property def sender_id(self) -> TailchatType.UserId: ... - @property - def is_from_self(self) -> bool: - ... + # @property + # def is_from_self(self) -> bool: + # ... @property def is_reply(self) -> bool: ... @@ -202,11 +202,41 @@ class TailchatReciveMessage: ... +class TailchatSendingMessage: + """ + Tailchat 将要发送的信息 + """ + @property + def content(self) -> str: + ... + @content.setter + def content(self, value: str) -> None: + ... + def with_content(self, content: str) -> "TailchatSendingMessage": + """ + 为了链式调用, 返回自身 + """ + self.content = content + return self + # def set_img(self, file: bytes, file_type: str, as_sticker: bool): + # """ + # 设置消息的图片 + # @param file: 图片文件 (实际上是 vec) + # @param file_type: 图片类型 (MIME) (image/png; image/jpeg) + # @param as_sticker: 是否作为贴纸发送 + # """ + class TailchatClient: """ Tailchat 的客户端 """ + def send_message(self, message: TailchatSendingMessage) -> bool: + ... + def send_and_warn(self, message: TailchatSendingMessage) -> bool: + """发送消息, 并在日志中输出警告信息""" + self.warn(message.content) + return self.send_message(message) def debug(self, message: str) -> None: """向日志中输出调试信息""" @@ -235,8 +265,9 @@ on_ica_delete_message = Callable[[IcaType.MessageId, IcaClient], None] # def on_delete_message(msg_id: MessageId, client: IcaClient) -> None: # ... -# TODO: Tailchat adapter -on_tailchat_message = Callable[[], None] +on_tailchat_message = Callable[[TailchatClient, TailchatReciveMessage], None] +# def on_tailchat_message(client: TailchatClient, msg: TailchatReciveMessage) -> None: +# ... on_config = Callable[[None], Tuple[str, str]] diff --git a/ica-rs/plugins/base.py b/ica-rs/plugins/base.py index e923155..efbce69 100644 --- a/ica-rs/plugins/base.py +++ b/ica-rs/plugins/base.py @@ -11,3 +11,5 @@ def on_ica_message(msg: IcaNewMessage, client: IcaClient) -> None: if msg.content == "/bot": reply = msg.reply_with(f"ica-async-rs({client.version})-sync-py {client.ica_version}") client.send_message(reply) + + diff --git a/ica-rs/src/data_struct/tailchat/messages.rs b/ica-rs/src/data_struct/tailchat/messages.rs index 56fd06b..d35f782 100644 --- a/ica-rs/src/data_struct/tailchat/messages.rs +++ b/ica-rs/src/data_struct/tailchat/messages.rs @@ -3,18 +3,6 @@ use serde_json::{json, Value as JsonValue}; use crate::data_struct::tailchat::{ConverseId, GroupId, MessageId, UserId}; -/*{'_id': '6606b3075163504389a6fc47', -'content': '光速!(', -'author': '6602e20d7b8d10675758e36b', -'groupId': '6602e331d31fd31b04aa0693', -'converseId': '6602f785928c4254f17726b2', -'hasRecall': False, -'meta': {'mentions': []}, -'reactions': [], -'createdAt': ExtType(code=0, data=b'\x00\x00\x01\x8e\x8a+TJ'), -'updatedAt': ExtType(code=0, data=b'\x00\x00\x01\x8e\x8a+TJ'), -'__v': 0} */ - #[derive(Serialize, Deserialize, Debug, Clone)] pub struct ReciveMessage { /// 消息ID @@ -35,6 +23,7 @@ pub struct ReciveMessage { #[serde(rename = "hasRecall")] pub has_recall: bool, /// 暂时懒得解析这玩意 + /// 准确来说是不确定内容, 毕竟没细看 API pub meta: JsonValue, /// 也懒得解析这玩意 pub reactions: Vec, @@ -50,19 +39,46 @@ pub struct ReciveMessage { } impl ReciveMessage { - pub fn as_reply(&self) -> ReplyMessage { - ReplyMessage { - content: self.content.clone(), - converse_id: self.converse_id.clone(), - group_id: self.group_id.clone(), - reply_id: self.msg_id.clone(), - } + pub fn is_reply(&self) -> bool { self.meta.get("reply").is_some() } + + /// 创建一个对这条消息的回复 + pub fn as_reply(&self) -> SendingMessage { + SendingMessage::new( + "".to_string(), + self.converse_id.clone(), + self.group_id.clone(), + Some(ReplyMeta::from_recive_message(self)), + ) + } + + /// 回复这条消息 + pub fn reply_with(&self, content: String) -> SendingMessage { + SendingMessage::new( + content, + self.converse_id.clone(), + self.group_id.clone(), + Some(ReplyMeta::from_recive_message(self)), + ) } } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize)] +/// 将要发送的消息 +/// +/// 发送时: +/// - `content`: 回复的消息内容 +/// - `converseId`: 会话ID +/// - `groupId`: 服务器ID +/// - `meta`: 回复的消息的元数据 ( 可能为空 ) +/// - `mentions`: 被回复的人的ID (可以是多个) +/// - `reply`: 被回复的消息 +/// - `_id`: 被回复的消息ID +/// - `author`: 被回复的消息的发送者ID +/// - `content`: 被回复的消息内容 pub struct SendingMessage { /// 消息内容 + /// + /// 其实还有个 plain, 就是不知道干啥的 pub content: String, /// 会话ID #[serde(rename = "converseId")] @@ -70,11 +86,38 @@ pub struct SendingMessage { /// 服务器ID #[serde(rename = "groupId")] pub group_id: GroupId, + /// 消息的元数据 + pub meta: Option, +} + +impl SendingMessage { + pub fn new( + content: String, + converse_id: ConverseId, + group_id: GroupId, + meta: Option, + ) -> Self { + Self { + content, + converse_id, + group_id, + meta, + } + } + pub fn new_without_meta(content: String, converse_id: ConverseId, group_id: GroupId) -> Self { + Self { + content, + converse_id, + group_id, + meta: None, + } + } + pub fn as_value(&self) -> JsonValue { serde_json::to_value(self).unwrap() } } #[derive(Debug, Clone)] pub struct ReplyMeta { - /// 被回复的人的ID (可以是多个?) + /// 被回复的人的ID (可以是多个) pub mentions: Vec, /// 被回复的消息ID pub reply_id: MessageId, @@ -84,6 +127,19 @@ pub struct ReplyMeta { pub reply_content: String, } +impl ReplyMeta { + pub fn from_recive_message(msg: &ReciveMessage) -> Self { + Self { + mentions: vec![msg.sender_id.clone()], + reply_id: msg.msg_id.clone(), + reply_author: msg.sender_id.clone(), + reply_content: msg.content.clone(), + } + } + pub fn add_mention(&mut self, user_id: UserId) { self.mentions.push(user_id); } + pub fn replace_content(&mut self, content: String) { self.reply_content = content; } +} + impl Serialize for ReplyMeta { fn serialize(&self, serializer: S) -> Result where @@ -91,9 +147,9 @@ impl Serialize for ReplyMeta { { let reply = json! { { - "replyId": self.reply_id, - "replyAuthor": self.reply_author, - "replyContent": self.reply_content, + "_id": self.reply_id, + "author": self.reply_author, + "content": self.reply_content, } }; let mut map = serde_json::Map::new(); @@ -102,18 +158,3 @@ impl Serialize for ReplyMeta { map.serialize(serializer) } } - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct ReplyMessage { - /// 消息内容 - pub content: String, - /// 会话ID - #[serde(rename = "converseId")] - pub converse_id: ConverseId, - /// 服务器ID - #[serde(rename = "groupId")] - pub group_id: GroupId, - /// 回复的消息ID - #[serde(rename = "replyId")] - pub reply_id: MessageId, -} diff --git a/ica-rs/src/main.rs b/ica-rs/src/main.rs index ab391e2..b91711e 100644 --- a/ica-rs/src/main.rs +++ b/ica-rs/src/main.rs @@ -25,8 +25,8 @@ pub type MainStatus = status::BotStatus; pub type StopGetter = tokio::sync::oneshot::Receiver<()>; pub const VERSION: &str = env!("CARGO_PKG_VERSION"); -pub const ICA_VERSION: &str = "1.4.1"; -pub const TAILCHAT_VERSION: &str = "0.1.0"; +pub const ICA_VERSION: &str = "1.5.0"; +pub const TAILCHAT_VERSION: &str = "0.2.0"; #[macro_export] macro_rules! wrap_callback { diff --git a/ica-rs/src/py/call.rs b/ica-rs/src/py/call.rs index 95e8eb8..f9f1021 100644 --- a/ica-rs/src/py/call.rs +++ b/ica-rs/src/py/call.rs @@ -174,8 +174,9 @@ pub async fn tailchat_new_message_py(message: &tailchat::messages::ReciveMessage let plugins = PyStatus::get_files(); for (path, plugin) in plugins.iter() { - // let msg = class::tailchat:: - let args = (); + let msg = class::tailchat::TailchatReciveMessagePy::from_recive_message(&message); + let client = class::tailchat::TailchatClientPy::new(client); + let args = (msg, client); call_py_func!(args, plugin, path, TAILCHAT_NEW_MESSAGE_FUNC, client); } } diff --git a/ica-rs/src/py/class.rs b/ica-rs/src/py/class.rs index 81b956d..41fe76b 100644 --- a/ica-rs/src/py/class.rs +++ b/ica-rs/src/py/class.rs @@ -1,4 +1,5 @@ pub mod ica; +pub mod tailchat; use pyo3::prelude::*; use toml::Value as TomlValue; diff --git a/ica-rs/src/py/class/ica.rs b/ica-rs/src/py/class/ica.rs index 8477ac2..0df2b8c 100644 --- a/ica-rs/src/py/class/ica.rs +++ b/ica-rs/src/py/class/ica.rs @@ -185,10 +185,7 @@ impl IcaClientPy { pub fn send_and_warn(&self, message: SendMessagePy) -> bool { warn!(message.msg.content); - tokio::task::block_in_place(|| { - let rt = Runtime::new().unwrap(); - rt.block_on(send_message(&self.client, &message.msg)) - }) + self.send_message(message) } pub fn delete_message(&self, message: DeleteMessagePy) -> bool { @@ -222,11 +219,9 @@ impl IcaClientPy { pub fn debug(&self, content: String) { debug!("{}", content); } - pub fn info(&self, content: String) { info!("{}", content); } - pub fn warn(&self, content: String) { warn!("{}", content); } diff --git a/ica-rs/src/py/class/tailchat.rs b/ica-rs/src/py/class/tailchat.rs new file mode 100644 index 0000000..1e85511 --- /dev/null +++ b/ica-rs/src/py/class/tailchat.rs @@ -0,0 +1,116 @@ +use pyo3::prelude::*; + +use rust_socketio::asynchronous::Client; +use tracing::{debug, info, warn}; + +use crate::data_struct::tailchat::messages::{ReciveMessage, ReplyMeta, SendingMessage}; +use crate::data_struct::tailchat::{ConverseId, GroupId, MessageId, UserId}; +use crate::tailchat::client::send_message; + +#[pyclass] +#[pyo3(name = "TailchatClient")] +pub struct TailchatClientPy { + pub client: Client, +} + +impl TailchatClientPy { + pub fn new(client: &Client) -> Self { + Self { + client: client.clone(), + } + } +} + +#[pyclass] +#[pyo3(name = "TailchatStatus")] +/// 预留? +pub struct TailchatStatusPy {} + +#[pyclass] +#[pyo3(name = "TailchatReciveMessage")] +pub struct TailchatReciveMessagePy { + pub message: ReciveMessage, +} + +impl TailchatReciveMessagePy { + pub fn from_recive_message(msg: &ReciveMessage) -> Self { + Self { + message: msg.clone(), + } + } +} + +#[derive(Clone)] +#[pyclass] +#[pyo3(name = "TailchatSendingMessage")] +pub struct TailchatSendingMessagePy { + pub message: SendingMessage, +} + +#[pymethods] +impl TailchatClientPy { + pub fn send_message(&self, message: TailchatSendingMessagePy) -> bool { + tokio::task::block_in_place(|| { + let rt = tokio::runtime::Runtime::new().unwrap(); + rt.block_on(send_message(&self.client, &message.message)) + }) + } + + pub fn send_and_warn(&self, message: TailchatSendingMessagePy) -> bool { + warn!("{}", message.message.content); + self.send_message(message) + } + + pub fn debug(&self, content: String) { + debug!("{}", content); + } + pub fn info(&self, content: String) { + info!("{}", content); + } + pub fn warn(&self, content: String) { + warn!("{}", content); + } +} + +#[pymethods] +impl TailchatReciveMessagePy { + #[getter] + pub fn get_is_reply(&self) -> bool { self.message.is_reply() } + #[getter] + pub fn get_msg_id(&self) -> MessageId { self.message.msg_id.clone() } + #[getter] + pub fn get_content(&self) -> String { self.message.content.clone() } + #[getter] + pub fn get_sender_id(&self) -> UserId { self.message.sender_id.clone() } + #[getter] + pub fn get_group_id(&self) -> GroupId { self.message.group_id.clone() } + #[getter] + pub fn get_converse_id(&self) -> ConverseId { self.message.converse_id.clone() } + /// 作为回复 + pub fn as_reply(&self) -> TailchatSendingMessagePy { + TailchatSendingMessagePy { + message: self.message.as_reply(), + } + } + pub fn reply_with(&self, content: String) -> TailchatSendingMessagePy { + TailchatSendingMessagePy { + message: self.message.reply_with(content), + } + } +} + +#[pymethods] +impl TailchatSendingMessagePy { + #[getter] + pub fn get_content(&self) -> String { self.message.content.clone() } + #[setter] + pub fn set_content(&mut self, content: String) { self.message.content = content; } + #[getter] + pub fn get_converse_id(&self) -> ConverseId { self.message.converse_id.clone() } + #[getter] + pub fn get_group_id(&self) -> GroupId { self.message.group_id.clone() } + pub fn with_content(&mut self, content: String) -> Self { + self.message.content = content; + self.clone() + } +} diff --git a/ica-rs/src/tailchat/client.rs b/ica-rs/src/tailchat/client.rs index 8b13789..4437130 100644 --- a/ica-rs/src/tailchat/client.rs +++ b/ica-rs/src/tailchat/client.rs @@ -1 +1,22 @@ +use crate::data_struct::tailchat::messages::SendingMessage; +// use crate::data_struct::tailchat::{ConverseId, GroupId, MessageId, UserId}; +use rust_socketio::asynchronous::Client; + +use colored::Colorize; +use serde_json::Value; +use tracing::{debug, info, span, warn, Level}; + +pub async fn send_message(client: &Client, message: &SendingMessage) -> bool { + let value: Value = message.as_value(); + match client.emit("chat.message.sendMessage", value).await { + Ok(_) => { + debug!("send_message {}", format!("{:#?}", message).cyan()); + true + } + Err(e) => { + warn!("send_message faild:{}", format!("{:#?}", e).red()); + false + } + } +} diff --git a/ica-rs/src/tailchat/events.rs b/ica-rs/src/tailchat/events.rs index e0b1aa0..51b00e6 100644 --- a/ica-rs/src/tailchat/events.rs +++ b/ica-rs/src/tailchat/events.rs @@ -56,24 +56,19 @@ pub async fn any_event(event: Event, payload: Payload, _client: Client) { } } -pub async fn on_message(payload: Payload, _client: Client) { - match payload { - Payload::Text(values) => { - if let Some(value) = values.first() { - let message: ReciveMessage = serde_json::from_value(value.clone()).unwrap(); - info!("收到消息 {:?}", message); - } +pub async fn on_message(payload: Payload, client: Client) { + if let Payload::Text(values) = payload { + if let Some(value) = values.first() { + let message: ReciveMessage = serde_json::from_value(value.clone()).unwrap(); + info!("收到消息 {:?}", message); + crate::py::call::tailchat_new_message_py(&message, &client).await; } - _ => (), } } pub async fn on_msg_delete(payload: Payload, _client: Client) { - match payload { - Payload::Text(values) => { - if let Some(value) = values.first() { - info!("删除消息 {}", value.to_string().red()); - } + if let Payload::Text(values) = payload { + if let Some(value) = values.first() { + info!("删除消息 {}", value.to_string().red()); } - _ => (), } }