From aad9ab08f698ffbf97dd7266144f22e3ca081737 Mon Sep 17 00:00:00 2001 From: shenjack <3695888@qq.com> Date: Mon, 10 Jun 2024 16:05:24 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B8=80=E4=BA=9B=200.6.7=20=E7=9A=84=E5=B7=A5?= =?UTF-8?q?=E4=BD=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ica-rs/ica_typing.py | 493 ++++++++++---------- ica-rs/src/data_struct/tailchat/messages.rs | 22 +- ica-rs/src/data_struct/tailchat/status.rs | 22 + ica-rs/src/ica.rs | 2 +- ica-rs/src/ica/events.rs | 1 + ica-rs/src/py/class/tailchat.rs | 10 +- ica-rs/src/tailchat.rs | 10 +- ica-rs/src/tailchat/client.rs | 17 +- ica-rs/src/tailchat/events.rs | 18 +- news.md | 15 + 10 files changed, 344 insertions(+), 266 deletions(-) diff --git a/ica-rs/ica_typing.py b/ica-rs/ica_typing.py index c087c51..df5e784 100644 --- a/ica-rs/ica_typing.py +++ b/ica-rs/ica_typing.py @@ -1,6 +1,7 @@ # Python 兼容版本 3.8+ -from typing import Callable, Tuple, NewType, TYPE_CHECKING, TypeVar +from typing import Callable, Tuple +from typing_extensions import Optional """ ica.rs @@ -9,9 +10,9 @@ pub type UserId = i64; pub type MessageId = String; """ class IcaType: - RoomId = NewType('RoomId', int) - UserId = NewType('UserId', int) - MessageId = NewType('MessageId', str) + RoomId = int + UserId = int + MessageId = str """ tailchat.rs @@ -21,266 +22,260 @@ pub type UserId = String; pub type MessageId = String; """ class TailchatType: - GroupId = NewType('GroupId', str) - ConverseId = NewType('ConverseId', str) - UserId = NewType('UserId', str) - MessageId = NewType('MessageId', str) + GroupId = str + ConverseId = str + UserId = str + MessageId = str -if TYPE_CHECKING: - - class IcaStatus: - """ - ica状态信息 - 此类并不存储信息, 所有方法都是实时获取 - """ - @property - def qq_login(self) -> bool: - ... - @property - def online(self) -> bool: - ... - @property - def self_id(self) -> IcaType.UserId: - ... - @property - def nick_name(self) -> str: - ... - @property - def ica_version(self) -> str: - ... - @property - def os_info(self) -> str: - ... - @property - def resident_set_size(self) -> str: - ... - @property - def head_used(self) -> str: - ... - @property - def load(self) -> str: - ... - - - class IcaReplyMessage: +class IcaStatus: + """ + ica状态信息 + 此类并不存储信息, 所有方法都是实时获取 + """ + @property + def qq_login(self) -> bool: + ... + @property + def online(self) -> bool: + ... + @property + def self_id(self) -> IcaType.UserId: + ... + @property + def nick_name(self) -> str: + ... + @property + def ica_version(self) -> str: + ... + @property + def os_info(self) -> str: + ... + @property + def resident_set_size(self) -> str: + ... + @property + def head_used(self) -> str: + ... + @property + def load(self) -> str: ... - class IcaSendMessage: - @property - def content(self) -> str: - ... - @content.setter - def content(self, value: str) -> None: - ... - def with_content(self, content: str) -> "IcaSendMessage": - """ - 为了链式调用, 返回自身 - """ - 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 IcaReplyMessage: + ... - class IcaDeleteMessage: - def __str__(self) -> str: - ... - - - class IcaNewMessage: +class IcaSendMessage: + @property + def content(self) -> str: + ... + @content.setter + def content(self, value: str) -> None: + ... + def with_content(self, content: str) -> "IcaSendMessage": """ - Icalingua 接收到新消息 + 为了链式调用, 返回自身 """ - def reply_with(self, message: str) -> IcaSendMessage: - """回复这条消息""" - ... - def as_deleted(self) -> IcaDeleteMessage: - ... - def __str__(self) -> str: - ... - @property - def id(self) -> IcaType.MessageId: - ... - @property - def content(self) -> str: - ... - @property - def sender_id(self) -> IcaType.UserId: - ... - @property - def is_from_self(self) -> bool: - ... - @property - def is_reply(self) -> bool: - ... - @property - def is_room_msg(self) -> bool: - """是否是群聊消息""" - ... - @property - def is_chat_msg(self) -> bool: - """是否是私聊消息""" - ... - @property - def room_id(self) -> IcaType.RoomId: - """ - 如果是群聊消息, 返回 (-群号) - 如果是私聊消息, 返回 对面qq - """ - ... - - - class IcaClient: + self.content = content + return self + def set_img(self, file: bytes, file_type: str, as_sticker: bool): """ - Icalingua 的客户端 + 设置消息的图片 + @param file: 图片文件 (实际上是 vec) + @param file_type: 图片类型 (MIME) (image/png; image/jpeg) + @param as_sticker: 是否作为贴纸发送 """ - # @staticmethod - # async def send_message_a(client: "IcaClient", message: SendMessage) -> bool: - # """ - # 仅作占位, 不能使用 - # (因为目前来说, rust调用 Python端没法启动一个异步运行时 - # 所以只能 tokio::task::block_in_place 转换成同步调用) - # """ - def send_message(self, message: IcaSendMessage) -> bool: - ... - def send_and_warn(self, message: IcaSendMessage) -> bool: - """发送消息, 并在日志中输出警告信息""" - self.warn(message.content) - return self.send_message(message) - def delete_message(self, message: IcaDeleteMessage) -> bool: - ... - - @property - def status(self) -> IcaStatus: - ... - @property - def version(self) -> str: - ... - @property - def ica_version(self) -> str: - """shenbot ica 的版本号""" - ... - def debug(self, message: str) -> None: - """向日志中输出调试信息""" - ... - def info(self, message: str) -> None: - """向日志中输出信息""" - ... - def warn(self, message: str) -> None: - """向日志中输出警告信息""" - ... - class TailchatReciveMessage: - """ - Tailchat 接收到的新消息 - """ - @property - def id(self) -> TailchatType.MessageId: - ... - @property - def content(self) -> str: - ... - @property - def sender_id(self) -> TailchatType.UserId: - ... - # @property - # def is_from_self(self) -> bool: - # ... - @property - def is_reply(self) -> bool: - ... - @property - def group_id(self) -> TailchatType.GroupId: - ... - @property - def converse_id(self) -> TailchatType.ConverseId: - ... - def reply_with(self, message: str) -> "TailchatSendingMessage": - """回复这条消息""" - ... - def as_reply(self, message: str) -> "TailchatSendingMessage": - """回复这条消息""" - ... +class IcaDeleteMessage: + def __str__(self) -> str: + ... - 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) - @property - def version(self) -> str: - ... - @property - def tailchat_version(self) -> str: - """tailchat 的版本号""" - ... - def debug(self, message: str) -> None: - """向日志中输出调试信息""" - def info(self, message: str) -> None: - """向日志中输出信息""" - def warn(self, message: str) -> None: - """向日志中输出警告信息""" - - - class ConfigData: - def __getitem__(self, key: str): - ... - def have_key(self, key: str) -> bool: - ... - - CONFIG_DATA: ConfigData = ConfigData() -else: +class IcaNewMessage: """ - 正常 Import 的时候使用的类型定义 + Icalingua 接收到新消息 """ - IcaStatus = TypeVar("IcaStatus") - IcaReplyMessage = TypeVar("IcaReplyMessage") - IcaNewMessage = TypeVar("IcaNewMessage") - IcaSendMessage = TypeVar("IcaSendMessage") - IcaDeleteMessage = TypeVar("IcaDeleteMessage") - IcaClient = TypeVar("IcaClient") - TailchatReciveMessage = TypeVar("TailchatReciveMessage") - TailchatSendingMessage = TypeVar("TailchatSendingMessage") - TailchatClient = TypeVar("TailchatClient") - ConfigData = TypeVar("ConfigData") + def reply_with(self, message: str) -> IcaSendMessage: + """回复这条消息""" + ... + def as_deleted(self) -> IcaDeleteMessage: + ... + def __str__(self) -> str: + ... + @property + def id(self) -> IcaType.MessageId: + ... + @property + def content(self) -> str: + ... + @property + def sender_id(self) -> IcaType.UserId: + ... + @property + def is_from_self(self) -> bool: + ... + @property + def is_reply(self) -> bool: + ... + @property + def is_room_msg(self) -> bool: + """是否是群聊消息""" + ... + @property + def is_chat_msg(self) -> bool: + """是否是私聊消息""" + ... + @property + def room_id(self) -> IcaType.RoomId: + """ + 如果是群聊消息, 返回 (-群号) + 如果是私聊消息, 返回 对面qq + """ + ... + + +class IcaClient: + """ + Icalingua 的客户端 + """ + # @staticmethod + # async def send_message_a(client: "IcaClient", message: SendMessage) -> bool: + # """ + # 仅作占位, 不能使用 + # (因为目前来说, rust调用 Python端没法启动一个异步运行时 + # 所以只能 tokio::task::block_in_place 转换成同步调用) + # """ + def send_message(self, message: IcaSendMessage) -> bool: + ... + def send_and_warn(self, message: IcaSendMessage) -> bool: + """发送消息, 并在日志中输出警告信息""" + self.warn(message.content) + return self.send_message(message) + def delete_message(self, message: IcaDeleteMessage) -> bool: + ... + + @property + def status(self) -> IcaStatus: + ... + @property + def version(self) -> str: + ... + @property + def ica_version(self) -> str: + """shenbot ica 的版本号""" + ... + def debug(self, message: str) -> None: + """向日志中输出调试信息""" + ... + def info(self, message: str) -> None: + """向日志中输出信息""" + ... + def warn(self, message: str) -> None: + """向日志中输出警告信息""" + ... + + +class TailchatReciveMessage: + """ + Tailchat 接收到的新消息 + """ + @property + def id(self) -> TailchatType.MessageId: + ... + @property + def content(self) -> str: + ... + @property + def sender_id(self) -> TailchatType.UserId: + ... + # @property + # def is_from_self(self) -> bool: + # ... + @property + def is_reply(self) -> bool: + ... + @property + def group_id(self) -> Optional[TailchatType.GroupId]: + ... + @property + def converse_id(self) -> TailchatType.ConverseId: + ... + def reply_with(self, message: str) -> "TailchatSendingMessage": + """回复这条消息""" + ... + def as_reply(self, message: str) -> "TailchatSendingMessage": + """回复这条消息""" + ... + + +class TailchatSendingMessage: + """ + Tailchat 将要发送的信息 + """ + @property + def content(self) -> str: + ... + @content.setter + def content(self, value: str) -> None: + ... + @property + def group_id(self) -> Optional[TailchatType.GroupId]: + ... + @group_id.setter + def group_id(self, value: Optional[TailchatType.GroupId]) -> None: + ... + @property + def converse_id(self) -> TailchatType.ConverseId: + ... + @converse_id.setter + def converse_id(self, value: TailchatType.ConverseId) -> 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) + @property + def version(self) -> str: + ... + @property + def tailchat_version(self) -> str: + """tailchat 的版本号""" + ... + def debug(self, message: str) -> None: + """向日志中输出调试信息""" + def info(self, message: str) -> None: + """向日志中输出信息""" + def warn(self, message: str) -> None: + """向日志中输出警告信息""" + + +class ConfigData: + def __getitem__(self, key: str): + ... + def have_key(self, key: str) -> bool: + ... on_load = Callable[[IcaClient], None] @@ -300,3 +295,5 @@ on_tailchat_message = Callable[[TailchatClient, TailchatReciveMessage], None] # ... on_config = Callable[[None], Tuple[str, str]] + +CONFIG_DATA: ConfigData = ConfigData() diff --git a/ica-rs/src/data_struct/tailchat/messages.rs b/ica-rs/src/data_struct/tailchat/messages.rs index 3f51390..6ebd169 100644 --- a/ica-rs/src/data_struct/tailchat/messages.rs +++ b/ica-rs/src/data_struct/tailchat/messages.rs @@ -16,8 +16,9 @@ pub struct ReciveMessage { #[serde(rename = "author")] pub sender_id: UserId, /// 服务器ID + /// 在私聊中不存在 #[serde(rename = "groupId")] - pub group_id: GroupId, + pub group_id: Option, /// 会话ID #[serde(rename = "converseId")] pub converse_id: ConverseId, @@ -31,13 +32,10 @@ pub struct ReciveMessage { pub reactions: Vec, /// 创建时间 #[serde(rename = "createdAt")] - pub created_at: JsonValue, + pub created_at: String, /// 更新时间 #[serde(rename = "updatedAt")] - pub updated_at: JsonValue, - /// 未知 - #[serde(rename = "__v")] - pub v: JsonValue, + pub updated_at: String, } impl ReciveMessage { @@ -69,7 +67,7 @@ impl Display for ReciveMessage { // msgid|groupid-converseid|senderid|content write!( f, - "{}|{}-{}|{}|{}", + "{}|{:?}-{}|{}|{}", self.msg_id, self.group_id, self.converse_id, self.sender_id, self.content ) } @@ -98,7 +96,7 @@ pub struct SendingMessage { pub converse_id: ConverseId, /// 服务器ID #[serde(rename = "groupId")] - pub group_id: GroupId, + pub group_id: Option, /// 消息的元数据 pub meta: Option, } @@ -107,7 +105,7 @@ impl SendingMessage { pub fn new( content: String, converse_id: ConverseId, - group_id: GroupId, + group_id: Option, meta: Option, ) -> Self { Self { @@ -117,7 +115,11 @@ impl SendingMessage { meta, } } - pub fn new_without_meta(content: String, converse_id: ConverseId, group_id: GroupId) -> Self { + pub fn new_without_meta( + content: String, + converse_id: ConverseId, + group_id: Option, + ) -> Self { Self { content, converse_id, diff --git a/ica-rs/src/data_struct/tailchat/status.rs b/ica-rs/src/data_struct/tailchat/status.rs index 224e7cd..e33dd23 100644 --- a/ica-rs/src/data_struct/tailchat/status.rs +++ b/ica-rs/src/data_struct/tailchat/status.rs @@ -1,4 +1,5 @@ use serde::{Deserialize, Serialize}; +use serde_json::Value as JsonValue; use crate::data_struct::tailchat::UserId; @@ -11,3 +12,24 @@ pub struct LoginData { pub nickname: String, pub avatar: String, } + +/* +{"__v":0,"_id":"66045ddb5163504389a6f5b1","createdAt":"2024-03-27T17:56:43.528Z","members":["6602e20d7b8d10675758e36b","6604482b5163504389a6f481"],"type":"DM","updatedAt":"2024-03-27T17:56:43.528Z"} +*/ +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct UpdateDMConverse { + /// 会话ID + #[serde(rename = "_id")] + pub id: String, + /// 创建时间 + #[serde(rename = "createdAt")] + pub created_at: String, + /// 成员 + pub members: Vec, + /// 类型 + #[serde(rename = "type")] + pub converse_type: String, + /// 更新时间 + #[serde(rename = "updatedAt")] + pub updated_at: String, +} diff --git a/ica-rs/src/ica.rs b/ica-rs/src/ica.rs index e08b458..ef54192 100644 --- a/ica-rs/src/ica.rs +++ b/ica-rs/src/ica.rs @@ -10,7 +10,7 @@ use crate::config::IcaConfig; use crate::error::{ClientResult, IcaError}; use crate::{wrap_any_callback, wrap_callback, StopGetter}; -const ICA_PROTOCOL_VERSION: &str = "2.12.2"; +const ICA_PROTOCOL_VERSION: &str = "2.12.6"; pub async fn start_ica(config: &IcaConfig, stop_reciver: StopGetter) -> ClientResult<(), IcaError> { let span = span!(Level::INFO, "Icalingua Client"); diff --git a/ica-rs/src/ica/events.rs b/ica-rs/src/ica/events.rs index 5b8aaba..ceffb8e 100644 --- a/ica-rs/src/ica/events.rs +++ b/ica-rs/src/ica/events.rs @@ -123,6 +123,7 @@ pub async fn any_event(event: Event, payload: Payload, _client: Client) { // 忽略的 "notify", "closeLoading", // 发送消息/加载新聊天 有一个 loading + "renewMessage", // 我也不确定到底是啥事件 "updateRoom", "syncRead", // 同步已读 ]; diff --git a/ica-rs/src/py/class/tailchat.rs b/ica-rs/src/py/class/tailchat.rs index fb4f355..978682b 100644 --- a/ica-rs/src/py/class/tailchat.rs +++ b/ica-rs/src/py/class/tailchat.rs @@ -86,7 +86,7 @@ impl TailchatReciveMessagePy { #[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() } + pub fn get_group_id(&self) -> Option { self.message.group_id.clone() } #[getter] pub fn get_converse_id(&self) -> ConverseId { self.message.converse_id.clone() } /// 作为回复 @@ -110,8 +110,14 @@ impl TailchatSendingMessagePy { 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() } + #[setter] + pub fn set_converse_id(&mut self, converse_id: ConverseId) { + self.message.converse_id = converse_id; + } #[getter] - pub fn get_group_id(&self) -> GroupId { self.message.group_id.clone() } + pub fn get_group_id(&self) -> Option { self.message.group_id.clone() } + #[setter] + pub fn set_group_id(&mut self, group_id: Option) { self.message.group_id = group_id; } pub fn with_content(&mut self, content: String) -> Self { self.message.content = content; self.clone() diff --git a/ica-rs/src/tailchat.rs b/ica-rs/src/tailchat.rs index ef5bf44..e60d12d 100644 --- a/ica-rs/src/tailchat.rs +++ b/ica-rs/src/tailchat.rs @@ -1,6 +1,7 @@ pub mod client; pub mod events; +use colored::Colorize; use futures_util::FutureExt; use md5::{Digest, Md5}; use reqwest::ClientBuilder as reqwest_ClientBuilder; @@ -42,6 +43,7 @@ pub async fn start_tailchat( Ok(resp) => { if resp.status().is_success() { let raw_data = resp.text().await?; + let json_data = serde_json::from_str::(&raw_data).unwrap(); let login_data = serde_json::from_value::(json_data["data"].clone()); match login_data { @@ -64,20 +66,24 @@ pub async fn start_tailchat( .on_any(wrap_any_callback!(events::any_event)) .on("notify:chat.message.add", wrap_callback!(events::on_message)) .on("notify:chat.message.delete", wrap_callback!(events::on_msg_delete)) + .on( + "notify:chat.converse.updateDMConverse", + wrap_callback!(events::on_converse_update), + ) // .on("notify:chat.message.update", wrap_callback!(events::on_message)) // .on("notify:chat.message.addReaction", wrap_callback!(events::on_msg_update)) .connect() .await .unwrap(); - event!(Level::INFO, "tailchat connected"); + event!(Level::INFO, "{}", "已经连接到 tailchat!".green()); // sleep for 500ms to wait for the connection to be established tokio::time::sleep(std::time::Duration::from_millis(500)).await; socket.emit("chat.converse.findAndJoinRoom", json!([])).await.unwrap(); - event!(Level::INFO, "tailchat joined room"); + event!(Level::INFO, "{}", "tailchat 已经加入房间".green()); stop_reciver.await.ok(); event!(Level::INFO, "socketio client stopping"); diff --git a/ica-rs/src/tailchat/client.rs b/ica-rs/src/tailchat/client.rs index bba11a3..5fd363a 100644 --- a/ica-rs/src/tailchat/client.rs +++ b/ica-rs/src/tailchat/client.rs @@ -4,8 +4,8 @@ use crate::data_struct::tailchat::messages::SendingMessage; use rust_socketio::asynchronous::Client; use colored::Colorize; -use serde_json::Value; -use tracing::{debug, warn}; +use serde_json::{json, Value}; +use tracing::{debug, info, warn}; pub async fn send_message(client: &Client, message: &SendingMessage) -> bool { let value: Value = message.as_value(); @@ -20,3 +20,16 @@ pub async fn send_message(client: &Client, message: &SendingMessage) -> bool { } } } + +pub async fn emit_join_room(client: &Client) -> bool { + match client.emit("chat.converse.findAndJoinRoom", json!([])).await { + Ok(_) => { + info!("emiting join room"); + true + } + Err(e) => { + warn!("emit_join_room faild:{}", format!("{:#?}", e).red()); + false + } + } +} diff --git a/ica-rs/src/tailchat/events.rs b/ica-rs/src/tailchat/events.rs index b70400b..3f13ca4 100644 --- a/ica-rs/src/tailchat/events.rs +++ b/ica-rs/src/tailchat/events.rs @@ -12,6 +12,7 @@ pub async fn any_event(event: Event, payload: Payload, _client: Client) { // 真正处理过的 "notify:chat.message.add", "notify:chat.message.delete", + "notify:chat.converse.updateDMConverse", // 也许以后会用到 "notify:chat.message.update", "notify:chat.message.addReaction", @@ -60,7 +61,14 @@ pub async fn any_event(event: Event, payload: Payload, _client: Client) { 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(); + let message: ReciveMessage = match serde_json::from_value(value.clone()) { + Ok(v) => v, + Err(e) => { + info!("tailchat_msg {}", value.to_string().red()); + info!("tailchat_msg {}", format!("{:?}", e).red()); + return; + } + }; info!("tailchat_msg {}", message.to_string().cyan()); if !message.is_reply() { @@ -84,3 +92,11 @@ pub async fn on_msg_delete(payload: Payload, _client: Client) { } } } + +pub async fn on_converse_update(payload: Payload, _client: Client) { + if let Payload::Text(values) = payload { + if let Some(value) = values.first() { + info!("更新会话 {}", value.to_string().green()); + } + } +} diff --git a/news.md b/news.md index d4a7c66..b074558 100644 --- a/news.md +++ b/news.md @@ -1,5 +1,20 @@ # 更新日志 +## 0.6.7 + +游学回来啦 + +- 处理了一些 tailchat 的特殊情况 + - 比如 message 里面的 `GroupId` 实际上是可选的, 在私聊中没有这一项 + - 忽略了所有的 `__v` (用于数据库记录信息的, bot不需要管) + - 作者原话 `不用管。数据库记录版本` + - 修复了如果没法解析新的信息, 会 panic 的问题 +- `ica_typing.py` + - 补充了 `TailchatSendingMessage` 的 `group_id` 和 `converse_id` 字段 + - 把 `group_id` 的设置和返回都改成了 `Optional[GroupId]` +- tailchat 的 API 也差点意思就是了(逃) +- 处理了 icalingua 的 `renewMessage` 事件 (其实就是直接忽略掉了) + ## 0.6.6 游学之前最后一次更新