Skip to main content

flow_bot/base/
extract.rs

1use std::sync::Arc;
2
3use async_trait::async_trait;
4
5use crate::{
6    api::api_ext::ApiExt,
7    event::{
8        BotEvent, TypedEvent,
9        message::{GroupSenderInfo, PrivateSenderInfo, SenderSex},
10    },
11    message::{self, message_ext::MessageExt, segments::Segment},
12};
13
14use super::context::BotContext;
15
16#[async_trait]
17/// Extractor trait for extracting information from BotEvent and BotContext.
18pub trait FromEvent {
19    async fn from_event(context: BotContext, event: BotEvent) -> Option<Self>
20    where
21        Self: Sized;
22}
23
24/// State extractor, extract the state from BotContext.
25/// If the required state is not found, the handler will be skipped.
26pub struct State<S> {
27    pub state: Arc<S>,
28}
29
30#[async_trait]
31impl<S> FromEvent for State<S>
32where
33    S: 'static + Send + Sync,
34{
35    async fn from_event(context: BotContext, _: BotEvent) -> Option<Self> {
36        let state = context.state.get::<S>()?;
37        Some(Self { state })
38    }
39}
40
41/// Extractor for message event.
42pub struct MessageBody {
43    pub message: message::Message,
44}
45
46#[async_trait]
47impl FromEvent for MessageBody {
48    async fn from_event(_: BotContext, event: BotEvent) -> Option<MessageBody> {
49        match event.event {
50            crate::event::TypedEvent::Message(ref msg) => Some(Self {
51                message: msg.message.clone(),
52            }),
53            _ => None,
54        }
55    }
56}
57
58unsafe impl Send for MessageBody {}
59unsafe impl Sync for MessageBody {}
60
61pub struct BasicSenderInfo {
62    pub user_id: Option<i64>,
63    pub nickname: Option<String>,
64    pub sex: Option<SenderSex>,
65    pub age: Option<i32>,
66}
67
68impl From<PrivateSenderInfo> for BasicSenderInfo {
69    fn from(info: PrivateSenderInfo) -> Self {
70        Self {
71            user_id: info.user_id,
72            nickname: info.nickname,
73            sex: info.sex,
74            age: info.age,
75        }
76    }
77}
78
79impl From<GroupSenderInfo> for BasicSenderInfo {
80    fn from(info: GroupSenderInfo) -> Self {
81        Self {
82            user_id: info.user_id,
83            nickname: info.nickname,
84            sex: info.sex,
85            age: info.age,
86        }
87    }
88}
89
90pub struct Sender {
91    pub info: BasicSenderInfo,
92}
93
94#[async_trait]
95impl FromEvent for Sender {
96    async fn from_event(_: BotContext, event: BotEvent) -> Option<Self> {
97        match event.event {
98            TypedEvent::Message(ref msg) => {
99                let info = match &msg.info {
100                    crate::event::message::TypedMessageInfo::Private(info) => {
101                        info.sender.clone().into()
102                    }
103                    crate::event::message::TypedMessageInfo::Group(info) => {
104                        info.sender.clone().into()
105                    }
106                };
107                Some(Self { info })
108            }
109            _ => None,
110        }
111    }
112}
113
114unsafe impl Send for Sender {}
115unsafe impl Sync for Sender {}
116
117pub struct At {
118    pub user_id: String,
119}
120
121#[async_trait]
122impl FromEvent for At {
123    async fn from_event(_: BotContext, event: BotEvent) -> Option<Self> {
124        if let TypedEvent::Message(ref msg) = event.event {
125            msg.message.iter().find_map(|seg| match seg {
126                Segment::At(at) => Some(Self {
127                    user_id: at.qq.clone(),
128                }),
129                _ => None,
130            })
131        } else {
132            None
133        }
134    }
135}
136
137unsafe impl Send for At {}
138unsafe impl Sync for At {}
139
140pub struct GroupId {
141    pub group_id: i64,
142}
143
144#[async_trait]
145impl FromEvent for GroupId {
146    async fn from_event(_: BotContext, event: BotEvent) -> Option<Self>
147    where
148        Self: Sized,
149    {
150        if let TypedEvent::Message(ref msg) = event.event {
151            match &msg.info {
152                crate::event::message::TypedMessageInfo::Group(info) => Some(Self {
153                    group_id: info.group_id,
154                }),
155                _ => None,
156            }
157        } else {
158            None
159        }
160    }
161}
162
163unsafe impl Send for GroupId {}
164unsafe impl Sync for GroupId {}
165
166pub struct SenderId {
167    pub user_id: i64,
168}
169
170#[async_trait]
171impl FromEvent for SenderId {
172    async fn from_event(context: BotContext, event: BotEvent) -> Option<Self>
173    where
174        Self: Sized,
175    {
176        let sender_info = Sender::from_event(context, event).await?;
177        Some(Self {
178            user_id: sender_info.info.user_id?,
179        })
180    }
181}
182
183unsafe impl Send for SenderId {}
184unsafe impl Sync for SenderId {}
185
186pub struct MatchGroupId<const ID: i64>;
187
188#[async_trait]
189impl<const ID: i64> FromEvent for MatchGroupId<ID> {
190    async fn from_event(context: BotContext, event: BotEvent) -> Option<Self>
191    where
192        Self: Sized,
193    {
194        let group_id = GroupId::from_event(context, event).await?.group_id;
195        if group_id == ID { Some(Self) } else { None }
196    }
197}
198
199unsafe impl<const ID: i64> Send for MatchGroupId<ID> {}
200unsafe impl<const ID: i64> Sync for MatchGroupId<ID> {}
201
202pub struct Reply {
203    pub reply: message::Message,
204}
205
206#[async_trait]
207impl FromEvent for Reply {
208    async fn from_event(context: BotContext, event: BotEvent) -> Option<Self>
209    where
210        Self: Sized,
211    {
212        if let TypedEvent::Message(ref msg) = event.event {
213            for segment in msg.message.iter() {
214                if let Segment::Reply(reply) = segment {
215                    let id = reply.id.parse::<i64>().ok()?;
216                    let message = context
217                        .get_message(id)
218                        .await
219                        .map(|msg| msg.message.clone())
220                        .ok()?;
221                    return Some(Self { reply: message });
222                }
223            }
224        }
225        None
226    }
227}
228
229pub struct Command<const PREFIX: &'static str, Cmd> {
230    pub command: Cmd,
231}
232
233#[async_trait]
234impl<const PREFIX: &'static str, Cmd> FromEvent for Command<PREFIX, Cmd>
235where
236    Cmd: clap::Parser,
237{
238    async fn from_event(context: BotContext, event: BotEvent) -> Option<Self>
239    where
240        Self: Sized,
241    {
242        let message_body = MessageBody::from_event(context, event).await?;
243        let plain_text = message_body.message.extract_if_plain_text()?;
244        let trimmed = plain_text.trim();
245
246        if trimmed.split_whitespace().next() != Some(PREFIX) {
247            return None;
248        }
249
250        let cmd = Cmd::try_parse_from(trimmed.split_whitespace()).ok()?;
251        Some(Self { command: cmd })
252    }
253}
254
255#[async_trait]
256impl<T> FromEvent for Option<T>
257where
258    T: FromEvent,
259{
260    async fn from_event(context: BotContext, event: BotEvent) -> Option<Self>
261    where
262        Self: Sized,
263    {
264        Some(T::from_event(context, event).await)
265    }
266}
267
268/// A helper macro for matching one of the variants.
269/// The macro will generate an enum with the given name and variants.
270/// The enum will implement the FromEvent trait, and will try to match the event with the given matchers.
271///
272/// # Example
273/// ```no_run
274/// match_one!(MatchOne, A: AMatcher, B: BMatcher);
275/// ```
276/// The above code will generate an enum like this:
277/// ```no_run
278/// pub enum MatchOne {
279///    A(AMatcher),
280///    B(BMatcher),
281/// }
282/// ```
283#[macro_export]
284macro_rules! match_one {
285    ($name:ident,$($variant:ident : $matcher:ty),*) => {
286        pub enum $name {
287            $(
288                $variant($matcher),
289            )*
290        }
291
292        #[async_trait::async_trait]
293        impl $crate::base::extract::FromEvent for $name {
294            async fn from_event(context: $crate::base::context::BotContext, event: $crate::event::BotEvent) -> Option<Self> {
295                $(
296                    if let Some(matcher) = <$matcher>::from_event(context.clone(), event.clone()).await {
297                        return Some(Self::$variant(matcher));
298                    }
299                )*
300                None
301            }
302        }
303    };
304}