Skip to main content

flow_bot/base/
extract.rs

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