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
291pub mod filters {
303 use super::*;
304
305 pub fn message() -> MessageFilter {
307 MessageFilter
308 }
309 pub fn edited_message() -> EditedMessageFilter {
311 EditedMessageFilter
312 }
313 pub fn callback_query() -> CallbackQueryFilter {
315 CallbackQueryFilter
316 }
317 pub fn inline_query() -> InlineQueryFilter {
319 InlineQueryFilter
320 }
321 pub fn command(cmd: impl Into<String>) -> CommandFilter {
323 CommandFilter::new(cmd)
324 }
325 pub fn text(t: impl Into<String>) -> TextFilter {
327 TextFilter::new(t)
328 }
329 pub fn text_contains(needle: impl Into<String>) -> TextContainsFilter {
331 TextContainsFilter::new(needle)
332 }
333 pub fn callback_data(data: impl Into<String>) -> CallbackDataFilter {
335 CallbackDataFilter::new(data)
336 }
337 pub fn callback_data_prefix(prefix: impl Into<String>) -> CallbackDataPrefixFilter {
339 CallbackDataPrefixFilter::new(prefix)
340 }
341 pub fn private() -> PrivateChatFilter {
343 PrivateChatFilter
344 }
345 pub fn group() -> GroupFilter {
347 GroupFilter
348 }
349 pub fn any() -> FnFilter<fn(&Context) -> bool> {
351 FnFilter(|_| true)
352 }
353}