Skip to main content

rust_tg_bot_ext/handlers/
inline_query.rs

1//! [`InlineQueryHandler`] -- handles updates containing an inline query.
2//!
3//! Ported from `python-telegram-bot`'s `InlineQueryHandler`. Supports
4//! optional regex matching on `inline_query.query` and optional
5//! `chat_types` filtering.
6
7use std::collections::HashMap;
8use std::future::Future;
9use std::pin::Pin;
10use std::sync::Arc;
11
12use regex::Regex;
13use rust_tg_bot_raw::types::update::Update;
14
15use super::base::{Handler, HandlerCallback, HandlerResult, MatchResult};
16use crate::context::CallbackContext;
17
18/// Handler for `Update.inline_query`.
19///
20/// # Matching rules
21///
22/// 1. The update must carry an `inline_query`.
23/// 2. If `chat_types` is set, the inline query's `chat_type` must be in the
24///    list. Queries without `chat_type` (e.g. from secret chats) are rejected.
25/// 3. If `pattern` is set, `re.match(pattern, query)` must succeed.
26/// 4. When all checks pass, captured groups (if any) are returned as
27///    `MatchResult::RegexMatch` or `MatchResult::RegexMatchWithNames` (when
28///    the pattern contains named capture groups).
29pub struct InlineQueryHandler {
30    callback: HandlerCallback,
31    pattern: Option<Regex>,
32    chat_types: Option<Vec<String>>,
33    block: bool,
34}
35
36impl InlineQueryHandler {
37    /// Create a new `InlineQueryHandler`.
38    pub fn new(
39        callback: HandlerCallback,
40        pattern: Option<Regex>,
41        chat_types: Option<Vec<String>>,
42        block: bool,
43    ) -> Self {
44        Self {
45            callback,
46            pattern,
47            chat_types,
48            block,
49        }
50    }
51}
52
53impl Handler for InlineQueryHandler {
54    fn check_update(&self, update: &Update) -> Option<MatchResult> {
55        let iq = update.inline_query()?;
56
57        // chat_types filter
58        if let Some(ref allowed) = self.chat_types {
59            let chat_type = iq.chat_type.as_deref()?;
60            if !allowed.iter().any(|t| t == chat_type) {
61                return None;
62            }
63        }
64
65        // Pattern filter
66        if let Some(ref re) = self.pattern {
67            let query = &iq.query;
68            let caps = re.captures(query)?;
69
70            let positional: Vec<String> = caps
71                .iter()
72                .filter_map(|m| m.map(|m| m.as_str().to_owned()))
73                .collect();
74
75            // Collect named groups (only those that matched).
76            let mut named: HashMap<String, String> = HashMap::new();
77            for name in re.capture_names().flatten() {
78                if let Some(m) = caps.name(name) {
79                    named.insert(name.to_owned(), m.as_str().to_owned());
80                }
81            }
82
83            return if named.is_empty() {
84                Some(MatchResult::RegexMatch(positional))
85            } else {
86                Some(MatchResult::RegexMatchWithNames { positional, named })
87            };
88        }
89
90        Some(MatchResult::Empty)
91    }
92
93    fn handle_update(
94        &self,
95        update: Arc<Update>,
96        match_result: MatchResult,
97    ) -> Pin<Box<dyn Future<Output = HandlerResult> + Send>> {
98        (self.callback)(update, match_result)
99    }
100
101    fn block(&self) -> bool {
102        self.block
103    }
104
105    /// Populate `context.matches` (positional) and `context.named_matches`
106    /// (named groups) from the regex match result.
107    fn collect_additional_context(
108        &self,
109        context: &mut CallbackContext,
110        match_result: &MatchResult,
111    ) {
112        match match_result {
113            MatchResult::RegexMatch(groups) => {
114                context.matches = Some(groups.clone());
115            }
116            MatchResult::RegexMatchWithNames { positional, named } => {
117                context.matches = Some(positional.clone());
118                context.named_matches = Some(named.clone());
119            }
120            _ => {}
121        }
122    }
123}