Skip to main content

rustigram_bot/
context.rs

1use std::sync::Arc;
2
3use rustigram_api::BotClient;
4use rustigram_types::inline::InlineQuery;
5use rustigram_types::message::Message;
6use rustigram_types::update::CallbackQuery;
7use rustigram_types::update::{Update, UpdateKind};
8use rustigram_types::user::ChatId;
9
10/// The context object passed to every handler.
11///
12/// Contains the incoming [`Update`], the [`BotClient`] (ready to make API
13/// calls), and a reference to any shared bot-level state.
14#[derive(Clone)]
15/// The context object passed to every handler.
16///
17/// `Context` bundles the incoming [`Update`] with the [`BotClient`] so
18/// handlers have everything they need in a single value.
19///
20/// # Accessing the update
21///
22/// ```rust,ignore
23/// async fn handler(ctx: Context) -> BotResult<()> {
24///     // Convenience accessors
25///     let text    = ctx.text();       // message text or caption
26///     let cmd     = ctx.command();    // "/start" → Some("start")
27///     let chat_id = ctx.chat_id();
28///     let user_id = ctx.from_id();
29///
30///     // Raw update for anything not covered by the helpers
31///     let update = &ctx.update;
32///     Ok(())
33/// }
34/// ```
35///
36/// # Sending replies
37///
38/// `ctx.reply(text)` is a shortcut that sends a message to the current chat
39/// and automatically sets `reply_to_message_id`. It returns `None` when the
40/// update has no associated chat (e.g. inline queries).
41///
42/// ```rust,ignore
43/// if let Some(reply) = ctx.reply("Got it!") {
44///     reply.parse_mode(ParseMode::HTML).await?;
45/// }
46/// ```
47pub struct Context {
48    /// The incoming update that triggered this handler.
49    pub update: Arc<Update>,
50
51    /// The API client — call any Bot API method directly.
52    pub bot: BotClient,
53}
54
55impl Context {
56    /// Creates a new `Context`.
57    #[must_use]
58    pub fn new(update: Update, bot: BotClient) -> Self {
59        Self {
60            update: Arc::new(update),
61            bot,
62        }
63    }
64
65    /// Returns the update ID.
66    #[must_use]
67    pub fn update_id(&self) -> i64 {
68        self.update.update_id
69    }
70
71    /// Returns the [`Message`] from a message, edited message, channel post,
72    /// or callback query update. Returns `None` for all other update types.
73    #[must_use]
74    pub fn message(&self) -> Option<&Message> {
75        match &self.update.kind {
76            UpdateKind::Message(m)
77            | UpdateKind::EditedMessage(m)
78            | UpdateKind::ChannelPost(m)
79            | UpdateKind::EditedChannelPost(m)
80            | UpdateKind::BusinessMessage(m)
81            | UpdateKind::EditedBusinessMessage(m) => Some(m),
82            UpdateKind::CallbackQuery(q) => q.message.as_ref(),
83            _ => None,
84        }
85    }
86
87    /// Returns the chat ID from the current update as a [`ChatId`], if available.
88    #[must_use]
89    pub fn chat_id(&self) -> Option<ChatId> {
90        self.update.chat_id().map(ChatId::Id)
91    }
92
93    /// Returns the sender's user ID, if available.
94    #[must_use]
95    pub fn from_id(&self) -> Option<i64> {
96        self.update.from().map(|u| u.id)
97    }
98
99    /// Returns the [`CallbackQuery`] if this is a callback query update.
100    #[must_use]
101    pub fn callback_query(&self) -> Option<&CallbackQuery> {
102        match &self.update.kind {
103            UpdateKind::CallbackQuery(q) => Some(q),
104            _ => None,
105        }
106    }
107
108    /// Returns the [`InlineQuery`] if this is an inline query update.
109    #[must_use]
110    pub fn inline_query(&self) -> Option<&InlineQuery> {
111        match &self.update.kind {
112            UpdateKind::InlineQuery(q) => Some(q),
113            _ => None,
114        }
115    }
116
117    /// Returns the effective text of the message — [`Message::text`] if present,
118    /// falling back to [`Message::caption`].
119    #[must_use]
120    pub fn text(&self) -> Option<&str> {
121        self.message().and_then(|m| m.effective_text())
122    }
123
124    /// Returns the command name if the message starts with a bot command entity.
125    ///
126    /// The leading `/` and optional `@BotName` suffix are stripped automatically.
127    /// `/start@mybot` returns `Some("start")`.
128    #[must_use]
129    pub fn command(&self) -> Option<&str> {
130        self.message().and_then(|m| m.command())
131    }
132
133    /// Sends a text reply to the current chat, automatically setting
134    /// `reply_to_message_id` to the incoming message.
135    ///
136    /// Returns `None` when the update has no associated chat.
137    pub fn reply(
138        &self,
139        text: impl Into<String>,
140    ) -> Option<rustigram_api::methods::sending::SendMessage> {
141        let chat_id = self.chat_id()?;
142        let mut builder = self.bot.send_message(chat_id, text);
143        if let Some(msg) = self.message() {
144            builder = builder.reply_to(msg.message_id);
145        }
146        Some(builder)
147    }
148}