1use rustigram_types::update::UpdateKind;
2
3use crate::context::Context;
4
5pub trait Filter: Send + Sync + 'static {
27 fn check(&self, ctx: &Context) -> bool;
29}
30
31pub trait FilterExt: Filter + Sized + Clone {
35 fn and<F: Filter + Clone>(self, other: F) -> And<Self, F> {
37 And {
38 left: self,
39 right: other,
40 }
41 }
42
43 fn or<F: Filter + Clone>(self, other: F) -> Or<Self, F> {
45 Or {
46 left: self,
47 right: other,
48 }
49 }
50
51 fn not(self) -> Not<Self> {
53 Not { inner: self }
54 }
55}
56
57impl<F: Filter + Clone> FilterExt for F {}
58
59#[derive(Clone)]
63pub struct And<L, R> {
64 left: L,
65 right: R,
66}
67impl<L: Filter, R: Filter> Filter for And<L, R> {
68 fn check(&self, ctx: &Context) -> bool {
69 self.left.check(ctx) && self.right.check(ctx)
70 }
71}
72
73#[derive(Clone)]
75pub struct Or<L, R> {
76 left: L,
77 right: R,
78}
79impl<L: Filter, R: Filter> Filter for Or<L, R> {
80 fn check(&self, ctx: &Context) -> bool {
81 self.left.check(ctx) || self.right.check(ctx)
82 }
83}
84
85#[derive(Clone)]
87pub struct Not<F> {
88 inner: F,
89}
90impl<F: Filter> Filter for Not<F> {
91 fn check(&self, ctx: &Context) -> bool {
92 !self.inner.check(ctx)
93 }
94}
95
96#[derive(Clone)]
99pub struct FnFilter<F>(pub F);
101
102impl<F: Fn(&Context) -> bool + Send + Sync + Clone + 'static> Filter for FnFilter<F> {
103 fn check(&self, ctx: &Context) -> bool {
104 (self.0)(ctx)
105 }
106}
107
108pub fn filter_fn<F>(f: F) -> FnFilter<F>
110where
111 F: Fn(&Context) -> bool + Send + Sync + Clone + 'static,
112{
113 FnFilter(f)
114}
115
116#[derive(Clone, Copy)]
119pub struct MessageFilter;
121impl Filter for MessageFilter {
122 fn check(&self, ctx: &Context) -> bool {
123 matches!(ctx.update.kind, UpdateKind::Message(_))
124 }
125}
126
127#[derive(Clone, Copy)]
129pub struct EditedMessageFilter;
131impl Filter for EditedMessageFilter {
132 fn check(&self, ctx: &Context) -> bool {
133 matches!(ctx.update.kind, UpdateKind::EditedMessage(_))
134 }
135}
136
137#[derive(Clone, Copy)]
139pub struct CallbackQueryFilter;
141impl Filter for CallbackQueryFilter {
142 fn check(&self, ctx: &Context) -> bool {
143 matches!(ctx.update.kind, UpdateKind::CallbackQuery(_))
144 }
145}
146
147#[derive(Clone, Copy)]
149pub struct InlineQueryFilter;
151impl Filter for InlineQueryFilter {
152 fn check(&self, ctx: &Context) -> bool {
153 matches!(ctx.update.kind, UpdateKind::InlineQuery(_))
154 }
155}
156
157#[derive(Clone)]
158pub struct CommandFilter {
163 command: String,
164}
165
166impl CommandFilter {
167 pub fn new(command: impl Into<String>) -> Self {
169 Self {
170 command: command.into(),
171 }
172 }
173}
174
175impl Filter for CommandFilter {
176 fn check(&self, ctx: &Context) -> bool {
177 ctx.command()
178 .is_some_and(|cmd| cmd.eq_ignore_ascii_case(&self.command))
179 }
180}
181
182#[derive(Clone)]
183pub struct TextFilter {
185 text: String,
186}
187
188impl TextFilter {
189 pub fn new(text: impl Into<String>) -> Self {
191 Self { text: text.into() }
192 }
193}
194
195impl Filter for TextFilter {
196 fn check(&self, ctx: &Context) -> bool {
197 ctx.text().is_some_and(|t| t == self.text)
198 }
199}
200
201#[derive(Clone)]
202pub struct TextContainsFilter {
204 needle: String,
205}
206
207impl TextContainsFilter {
208 pub fn new(needle: impl Into<String>) -> Self {
210 Self {
211 needle: needle.into(),
212 }
213 }
214}
215
216impl Filter for TextContainsFilter {
217 fn check(&self, ctx: &Context) -> bool {
218 ctx.text().is_some_and(|t| t.contains(self.needle.as_str()))
219 }
220}
221
222#[derive(Clone)]
223pub struct CallbackDataFilter {
225 data: String,
226}
227
228impl CallbackDataFilter {
229 pub fn new(data: impl Into<String>) -> Self {
231 Self { data: data.into() }
232 }
233}
234
235impl Filter for CallbackDataFilter {
236 fn check(&self, ctx: &Context) -> bool {
237 ctx.callback_query()
238 .and_then(|q| q.data.as_deref())
239 .is_some_and(|d| d == self.data)
240 }
241}
242
243#[derive(Clone)]
244pub struct CallbackDataPrefixFilter {
246 prefix: String,
247}
248
249impl CallbackDataPrefixFilter {
250 pub fn new(prefix: impl Into<String>) -> Self {
252 Self {
253 prefix: prefix.into(),
254 }
255 }
256}
257
258impl Filter for CallbackDataPrefixFilter {
259 fn check(&self, ctx: &Context) -> bool {
260 ctx.callback_query()
261 .and_then(|q| q.data.as_deref())
262 .is_some_and(|d| d.starts_with(self.prefix.as_str()))
263 }
264}
265
266#[derive(Clone, Copy)]
267pub struct PrivateChatFilter;
269impl Filter for PrivateChatFilter {
270 fn check(&self, ctx: &Context) -> bool {
271 ctx.message()
272 .is_some_and(|m| matches!(m.chat.kind, rustigram_types::chat::ChatType::Private))
273 }
274}
275
276#[derive(Clone, Copy)]
277pub struct GroupFilter;
279impl Filter for GroupFilter {
280 fn check(&self, ctx: &Context) -> bool {
281 ctx.message().is_some_and(|m| {
282 matches!(
283 m.chat.kind,
284 rustigram_types::chat::ChatType::Group
285 | rustigram_types::chat::ChatType::Supergroup
286 )
287 })
288 }
289}
290
291#[cfg(feature = "tma")]
298#[derive(Clone, Copy)]
299pub struct WebAppDataFilter;
300
301#[cfg(feature = "tma")]
302impl Filter for WebAppDataFilter {
303 fn check(&self, ctx: &Context) -> bool {
304 ctx.message()
305 .and_then(|m| m.web_app_data.as_ref())
306 .is_some()
307 }
308}
309
310#[cfg(feature = "tma")]
318#[derive(Clone)]
319pub struct WebAppDataMatchingFilter<F> {
320 predicate: F,
321}
322
323#[cfg(feature = "tma")]
324impl<F> Filter for WebAppDataMatchingFilter<F>
325where
326 F: Fn(&str) -> bool + Send + Sync + Clone + 'static,
327{
328 fn check(&self, ctx: &Context) -> bool {
329 ctx.message()
330 .and_then(|m| m.web_app_data.as_ref())
331 .is_some_and(|d| (self.predicate)(d.button_text.as_str()))
332 }
333}
334
335pub mod filters {
347 use super::*;
348
349 pub fn message() -> MessageFilter {
351 MessageFilter
352 }
353 pub fn edited_message() -> EditedMessageFilter {
355 EditedMessageFilter
356 }
357 pub fn callback_query() -> CallbackQueryFilter {
359 CallbackQueryFilter
360 }
361 pub fn inline_query() -> InlineQueryFilter {
363 InlineQueryFilter
364 }
365 pub fn command(cmd: impl Into<String>) -> CommandFilter {
367 CommandFilter::new(cmd)
368 }
369 pub fn text(t: impl Into<String>) -> TextFilter {
371 TextFilter::new(t)
372 }
373 pub fn text_contains(needle: impl Into<String>) -> TextContainsFilter {
375 TextContainsFilter::new(needle)
376 }
377 pub fn callback_data(data: impl Into<String>) -> CallbackDataFilter {
379 CallbackDataFilter::new(data)
380 }
381 pub fn callback_data_prefix(prefix: impl Into<String>) -> CallbackDataPrefixFilter {
383 CallbackDataPrefixFilter::new(prefix)
384 }
385 pub fn private() -> PrivateChatFilter {
387 PrivateChatFilter
388 }
389 pub fn group() -> GroupFilter {
391 GroupFilter
392 }
393 pub fn any() -> FnFilter<fn(&Context) -> bool> {
395 FnFilter(|_| true)
396 }
397 #[cfg(feature = "tma")]
401 pub fn web_app_data() -> WebAppDataFilter {
402 WebAppDataFilter
403 }
404 #[cfg(feature = "tma")]
414 pub fn web_app_data_matching<F>(predicate: F) -> WebAppDataMatchingFilter<F>
415 where
416 F: Fn(&str) -> bool + Send + Sync + Clone + 'static,
417 {
418 WebAppDataMatchingFilter { predicate }
419 }
420}