From d81a5cbe7d95807576044adb024c6040a2a8f37d Mon Sep 17 00:00:00 2001 From: BlueGlassBlock Date: Thu, 16 Mar 2023 18:23:51 +0800 Subject: [PATCH] feat: support recall event Resolve #22 See #17 --- CHANGELOG.md | 2 +- news/22.added.md | 1 + python/ichika/core/events/__init__.pyi | 31 ++++- python/ichika/core/events/structs.pyi | 5 + src/client/friend.rs | 24 ++-- src/events/converter.rs | 177 +++++++++++++++++-------- src/events/mod.rs | 17 +++ src/events/structs.rs | 22 ++- src/lib.rs | 5 +- src/utils.rs | 2 +- 10 files changed, 208 insertions(+), 78 deletions(-) create mode 100644 news/22.added.md diff --git a/CHANGELOG.md b/CHANGELOG.md index aea2ee1..03ae4fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,4 +43,4 @@ ### 其他 -- 使用 [`towncrier`](https://towncrier.readthedocs.io) 和 GitHub Release 来管理项目。 [#18](https://github.com/BlueGlassBlock/ichika/issues/18) +- 使用 [`towncrier`](https://towncrier.readthedocs.io) 和 GitHub Release 来管理项目。 ([#18](https://github.com/BlueGlassBlock/ichika/issues/18)) diff --git a/news/22.added.md b/news/22.added.md new file mode 100644 index 0000000..f009394 --- /dev/null +++ b/news/22.added.md @@ -0,0 +1 @@ +支持处理群聊和好友撤回消息事件 diff --git a/python/ichika/core/events/__init__.pyi b/python/ichika/core/events/__init__.pyi index 602d55c..1436b65 100644 --- a/python/ichika/core/events/__init__.pyi +++ b/python/ichika/core/events/__init__.pyi @@ -1,9 +1,10 @@ from dataclasses import dataclass +from datetime import datetime from graia.amnesia.message import MessageChain from . import structs as structs -from .structs import MemberInfo, MessageSource +from .structs import FriendInfo, MemberInfo, MessageSource internal_repr = dataclass(frozen=True, init=False) @@ -16,3 +17,31 @@ class GroupMessage: source: MessageSource content: MessageChain sender: MemberInfo + +@internal_repr +class GroupRecallMessage: + time: datetime + author: MemberInfo + operator: MemberInfo + seq: int + +@internal_repr +class FriendMessage: + source: MessageSource + content: MessageChain + sender: FriendInfo + +@internal_repr +class FriendRecallMessage: + time: datetime + author: FriendInfo + seq: int + +@internal_repr +class TempMessage: + source: MessageSource + content: MessageChain + sender: MemberInfo + +@internal_repr +class UnknownEvent: ... diff --git a/python/ichika/core/events/structs.pyi b/python/ichika/core/events/structs.pyi index 6f78d48..3aea708 100644 --- a/python/ichika/core/events/structs.pyi +++ b/python/ichika/core/events/structs.pyi @@ -16,3 +16,8 @@ class MemberInfo: uin: int name: str group: Group + +@internal_repr +class FriendInfo: + uin: int + nickname: str diff --git a/src/client/friend.rs b/src/client/friend.rs index a780c7c..bc2b52a 100644 --- a/src/client/friend.rs +++ b/src/client/friend.rs @@ -9,11 +9,11 @@ use ricq_core::command::friendlist::FriendListResponse; #[pyclass(get_all)] #[derive(PyRepr, Clone)] pub struct Friend { - uin: i64, - nick: String, - remark: String, - face_id: i16, - group_id: u8, + pub uin: i64, + pub nick: String, + pub remark: String, + pub face_id: i16, + pub group_id: u8, } impl From for Friend { @@ -31,11 +31,11 @@ impl From for Friend { #[pyclass(get_all)] #[derive(PyRepr, Clone)] pub struct FriendGroup { - group_id: u8, - name: String, - total_count: i32, - online_count: i32, - seq_id: u8, + pub group_id: u8, + pub name: String, + pub total_count: i32, + pub online_count: i32, + pub seq_id: u8, } impl From for FriendGroup { @@ -64,9 +64,9 @@ pub struct FriendList { entries: Vec, friend_groups: HashMap, #[pyo3(get)] - total_count: i16, + pub total_count: i16, #[pyo3(get)] - online_count: i16, + pub online_count: i16, } #[pymethods] diff --git a/src/events/converter.rs b/src/events/converter.rs index 2589919..140f9dd 100644 --- a/src/events/converter.rs +++ b/src/events/converter.rs @@ -1,14 +1,23 @@ +use pyo3::exceptions::PyValueError; use pyo3::prelude::*; use ricq::client::event as rce; use ricq::handler::QEvent; use super::structs::{FriendInfo, MemberInfo, MessageSource}; -use super::{FriendMessage, GroupMessage, LoginEvent, TempMessage, UnknownEvent}; +use super::{ + FriendMessage, + FriendRecallMessage, + GroupMessage, + GroupRecallMessage, + LoginEvent, + TempMessage, + UnknownEvent, +}; use crate::client::cache; use crate::exc::MapPyErr; use crate::message::convert::{serialize_as_py_chain, serialize_audio}; -use crate::utils::{py_try, py_use}; -use crate::PyRet; +use crate::utils::{datetime_from_ts, py_try, AsPython}; +use crate::{call_static_py, PyRet}; pub async fn convert(event: QEvent) -> PyRet { match event { @@ -18,20 +27,14 @@ pub async fn convert(event: QEvent) -> PyRet { QEvent::FriendMessage(event) => handle_friend_message(event).await, QEvent::FriendAudioMessage(event) => handle_friend_audio(event).await, QEvent::GroupTempMessage(event) => handle_temp_message(event).await, - unknown => obj(|_| UnknownEvent { inner: unknown }), + QEvent::GroupMessageRecall(event) => handle_group_recall(event).await, + QEvent::FriendMessageRecall(event) => handle_friend_recall(event).await, + unknown => Ok(UnknownEvent { inner: unknown }.obj()), } } -fn obj(f: F) -> PyResult -where - F: for<'py> FnOnce(Python<'py>) -> R, - R: IntoPy, -{ - py_use(|py| Ok(f(py).into_py(py))) -} - async fn handle_login(uin: i64) -> PyRet { - obj(|py| LoginEvent { uin }.into_py(py)) + Ok(LoginEvent { uin }.obj()) } async fn handle_group_message(event: rce::GroupMessageEvent) -> PyRet { @@ -45,19 +48,44 @@ async fn handle_group_message(event: rce::GroupMessageEvent) -> PyRet { .py_res()?; let content = py_try(|py| serialize_as_py_chain(py, msg.elements))?; - obj(|py| GroupMessage { - source: MessageSource::new(py, &msg.seqs, &msg.rands, msg.time), - content, - sender: MemberInfo { - uin: msg.from_uin, - name: sender_info.card_name.clone(), - nickname: sender_info.nickname.clone(), - group: (*group_info).clone(), - permission: sender_info.permission, - }, + py_try(|py| { + Ok(GroupMessage { + source: MessageSource::new(py, &msg.seqs, &msg.rands, msg.time)?, + content, + sender: MemberInfo { + uin: msg.from_uin, + name: sender_info.card_name.clone(), + nickname: sender_info.nickname.clone(), + group: (*group_info).clone(), + permission: sender_info.permission, + }, + } + .obj()) }) } +async fn handle_group_recall(event: rce::GroupMessageRecallEvent) -> PyRet { + let msg = event.inner; + let mut cache = cache(event.client).await; + let group_info = cache.fetch_group(msg.group_code).await.py_res()?; + let author = cache + .fetch_member(msg.group_code, msg.author_uin) + .await + .py_res()?; + let operator = cache + .fetch_member(msg.group_code, msg.operator_uin) + .await + .py_res()?; + let time = py_try(|py| Ok(call_static_py!(datetime_from_ts, py, (msg.time))?.into_py(py)))?; + Ok(GroupRecallMessage { + time, + author: MemberInfo::new(&author, (*group_info).clone()), + operator: MemberInfo::new(&operator, (*group_info).clone()), + seq: msg.msg_seq, + } + .obj()) +} + async fn handle_group_audio(event: rce::GroupAudioMessageEvent) -> PyRet { let url = event.url().await.py_res()?; let msg = event.inner; @@ -69,43 +97,75 @@ async fn handle_group_audio(event: rce::GroupAudioMessageEvent) -> PyRet { .await .py_res()?; - obj(|py| GroupMessage { - source: MessageSource::new(py, &msg.seqs, &msg.rands, msg.time), - content, - sender: MemberInfo { - uin: msg.from_uin, - name: sender_info.card_name.clone(), - nickname: sender_info.nickname.clone(), - group: (*group_info).clone(), - permission: sender_info.permission, - }, + py_try(|py| { + Ok(GroupMessage { + source: MessageSource::new(py, &msg.seqs, &msg.rands, msg.time)?, + content, + sender: MemberInfo { + uin: msg.from_uin, + name: sender_info.card_name.clone(), + nickname: sender_info.nickname.clone(), + group: (*group_info).clone(), + permission: sender_info.permission, + }, + } + .obj()) }) } async fn handle_friend_message(event: rce::FriendMessageEvent) -> PyRet { let msg = event.inner; let content = py_try(|py| serialize_as_py_chain(py, msg.elements))?; - obj(|py| FriendMessage { - source: MessageSource::new(py, &msg.seqs, &msg.rands, msg.time), - content, - sender: FriendInfo { - uin: msg.from_uin, - nickname: msg.from_nick, - }, + py_try(|py| { + Ok(FriendMessage { + source: MessageSource::new(py, &msg.seqs, &msg.rands, msg.time)?, + content, + sender: FriendInfo { + uin: msg.from_uin, + nickname: msg.from_nick, + }, + } + .obj()) }) } +async fn handle_friend_recall(event: rce::FriendMessageRecallEvent) -> PyRet { + let msg = event.inner; + let mut cache = cache(event.client).await; + let friend = cache + .fetch_friend_list() + .await + .py_res()? + .find_friend(msg.friend_uin) + .ok_or_else(|| { + PyValueError::new_err(format!("Unable to find friend {}", msg.friend_uin)) + })?; + let time = py_try(|py| Ok(call_static_py!(datetime_from_ts, py, (msg.time))?.into_py(py)))?; + Ok(FriendRecallMessage { + time, + author: FriendInfo { + uin: friend.uin, + nickname: friend.nick, + }, + seq: msg.msg_seq, + } + .obj()) +} + async fn handle_friend_audio(event: rce::FriendAudioMessageEvent) -> PyRet { let url = event.url().await.py_res()?; let msg = event.inner; let content = py_try(|py| serialize_audio(py, url, &msg.audio.0))?; - obj(|py| FriendMessage { - source: MessageSource::new(py, &msg.seqs, &msg.rands, msg.time), - content, - sender: FriendInfo { - uin: msg.from_uin, - nickname: msg.from_nick, - }, + py_try(|py| { + Ok(FriendMessage { + source: MessageSource::new(py, &msg.seqs, &msg.rands, msg.time)?, + content, + sender: FriendInfo { + uin: msg.from_uin, + nickname: msg.from_nick, + }, + } + .obj()) }) } @@ -120,15 +180,18 @@ async fn handle_temp_message(event: rce::GroupTempMessageEvent) -> PyRet { .await .py_res()?; - obj(|py| TempMessage { - source: MessageSource::new(py, &msg.seqs, &msg.rands, msg.time), - content, - sender: MemberInfo { - uin: msg.from_uin, - name: sender_info.card_name.clone(), - nickname: sender_info.nickname.clone(), - group: (*group_info).clone(), - permission: sender_info.permission, - }, + py_try(|py| { + Ok(TempMessage { + source: MessageSource::new(py, &msg.seqs, &msg.rands, msg.time)?, + content, + sender: MemberInfo { + uin: msg.from_uin, + name: sender_info.card_name.clone(), + nickname: sender_info.nickname.clone(), + group: (*group_info).clone(), + permission: sender_info.permission, + }, + } + .obj()) }) } diff --git a/src/events/mod.rs b/src/events/mod.rs index dfbcbaf..1fdb8c3 100644 --- a/src/events/mod.rs +++ b/src/events/mod.rs @@ -24,6 +24,15 @@ pub struct GroupMessage { sender: MemberInfo, } +#[pyclass(get_all)] +#[derive(PyRepr, Clone)] +pub struct GroupRecallMessage { + time: PyObject, // PyDatetime + author: MemberInfo, + operator: MemberInfo, + seq: i32, +} + #[pyclass(get_all)] #[derive(PyRepr, Clone)] pub struct FriendMessage { @@ -32,6 +41,14 @@ pub struct FriendMessage { sender: FriendInfo, } +#[pyclass(get_all)] +#[derive(PyRepr, Clone)] +pub struct FriendRecallMessage { + time: PyObject, // PyDatetime + author: FriendInfo, + seq: i32, +} + #[pyclass(get_all)] #[derive(PyRepr, Clone)] pub struct TempMessage { diff --git a/src/events/structs.rs b/src/events/structs.rs index 7c7929b..1c94f1f 100644 --- a/src/events/structs.rs +++ b/src/events/structs.rs @@ -3,7 +3,7 @@ use pyo3::types::PyTuple; use pyo3_repr::PyRepr; use crate::call_static_py; -use crate::client::group::Group; +use crate::client::group::{Group, Member}; use crate::utils::datetime_from_ts; #[pyclass(get_all)] #[derive(PyRepr, Clone)] @@ -14,12 +14,12 @@ pub struct MessageSource { } impl MessageSource { - pub fn new(py: Python, seqs: &[i32], rands: &[i32], time: i32) -> Self { - Self { + pub fn new(py: Python, seqs: &[i32], rands: &[i32], time: i32) -> PyResult { + Ok(Self { seqs: PyTuple::new(py, seqs).into_py(py), rands: PyTuple::new(py, rands).into_py(py), - time: call_static_py!(datetime_from_ts, py, (time)! "Unable to convert time"), - } + time: call_static_py!(datetime_from_ts, py, (time))?.into(), + }) } } @@ -33,6 +33,18 @@ pub struct MemberInfo { pub permission: u8, } +impl MemberInfo { + pub fn new(member: &Member, group: Group) -> Self { + Self { + uin: member.uin, + name: member.card_name.clone(), + nickname: member.nickname.clone(), + group, + permission: member.permission, + } + } +} + #[pyclass(get_all)] #[derive(PyRepr, Clone)] pub struct FriendInfo { diff --git a/src/lib.rs b/src/lib.rs index 69db932..34d984f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -62,8 +62,10 @@ fn register_event_module(py: Python, parent: &PyModule) -> PyResult<()> { let m = PyModule::new(py, "ichika.core.events")?; add_batch!(@cls m, crate::events::GroupMessage, + crate::events::GroupRecallMessage, crate::events::TempMessage, crate::events::FriendMessage, + crate::events::FriendRecallMessage, crate::events::UnknownEvent ); parent.add_submodule(m)?; @@ -80,7 +82,8 @@ fn register_event_structs_module(py: Python, parent: &PyModule) -> PyResult<()> let m = PyModule::new(py, "ichika.core.events.structs")?; add_batch!(@cls m, crate::events::structs::MessageSource, - crate::events::structs::MemberInfo + crate::events::structs::MemberInfo, + crate::events::structs::FriendInfo ); parent.add_submodule(m)?; parent.add("structs", m)?; diff --git a/src/utils.rs b/src/utils.rs index 7144e3d..77bef18 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -150,7 +150,7 @@ macro_rules! static_py_fn { macro_rules! call_static_py { ($pth:expr, $py:expr, ($($arg:expr),*)) => { $pth($py).call1( - ($($arg),*) + ($($arg,)*) ) }; ($pth:expr, $py:expr, ($($arg:expr),*) ! $reason:expr) => {