Skip to main content

botkit_core/
context.rs

1use std::any::Any;
2use std::sync::Arc;
3
4use crate::action::{ChatAction, ChatActionGuard, ChatActionSender};
5
6#[cfg(not(target_arch = "wasm32"))]
7pub trait ContextDataBounds: Send + Sync {}
8#[cfg(not(target_arch = "wasm32"))]
9impl<T: Send + Sync + ?Sized> ContextDataBounds for T {}
10
11#[cfg(target_arch = "wasm32")]
12pub trait ContextDataBounds {}
13#[cfg(target_arch = "wasm32")]
14impl<T: ?Sized> ContextDataBounds for T {}
15
16/// Context for handling bot events
17///
18/// Provides access to event data and platform client. Platform-specific
19/// details are abstracted away - handlers work with a unified API.
20#[derive(Clone)]
21pub struct Context {
22    inner: Arc<dyn ContextData>,
23}
24
25impl Context {
26    /// Create a new context (called by platform implementations)
27    pub fn new<T: ContextData>(data: T) -> Self {
28        Self {
29            inner: Arc::new(data),
30        }
31    }
32
33    /// Get the channel/chat ID
34    pub fn channel_id(&self) -> &str {
35        self.inner.channel_id()
36    }
37
38    /// Get the user ID who triggered the event
39    pub fn user_id(&self) -> &str {
40        self.inner.user_id()
41    }
42
43    /// Get the user's display name
44    pub fn user_name(&self) -> &str {
45        self.inner.user_name()
46    }
47
48    /// Get the command name if this is a command event
49    pub fn command_name(&self) -> Option<&str> {
50        self.inner.command_name()
51    }
52
53    /// Get command arguments as a string (for Telegram-style commands)
54    pub fn command_args(&self) -> Option<&str> {
55        self.inner.command_args()
56    }
57
58    /// Get a command option value by name
59    pub fn option(&self, name: &str) -> Option<OptionValue> {
60        self.inner.option(name)
61    }
62
63    /// Get the button/callback custom_id if this is a button event
64    pub fn button_id(&self) -> Option<&str> {
65        self.inner.button_id()
66    }
67
68    /// Get the message content if available
69    pub fn message_content(&self) -> Option<&str> {
70        self.inner.message_content()
71    }
72
73    /// Access platform-specific raw data (for advanced use cases)
74    ///
75    /// Returns `Some(&T)` if the underlying platform data is of type `T`.
76    pub fn platform<T: 'static>(&self) -> Option<&T> {
77        self.inner.as_any().downcast_ref::<T>()
78    }
79
80    /// Get the inner context data for platform implementations
81    pub fn data(&self) -> &dyn ContextData {
82        &*self.inner
83    }
84
85    /// Start a typing indicator that auto-renews until the guard is dropped
86    ///
87    /// Returns `None` if the platform doesn't support typing indicators
88    /// or if the context doesn't have the necessary client.
89    ///
90    /// # Example
91    /// ```ignore
92    /// async fn slow_command(ctx: Context) -> String {
93    ///     let _typing = ctx.typing();
94    ///     // Do slow work...
95    ///     expensive_computation().await;
96    ///     "Done!"
97    /// }
98    /// ```
99    pub fn typing(&self) -> Option<ChatActionGuard> {
100        self.send_action(ChatAction::Typing)
101    }
102
103    /// Start a chat action indicator that auto-renews until the guard is dropped
104    ///
105    /// This is internal - use `typing()` for the public API.
106    pub(crate) fn send_action(&self, action: ChatAction) -> Option<ChatActionGuard> {
107        let sender = self.inner.action_sender()?;
108        Some(ChatActionGuard::start(sender, action))
109    }
110}
111
112/// Command option value
113#[derive(Debug, Clone)]
114pub enum OptionValue {
115    String(String),
116    Integer(i64),
117    Boolean(bool),
118    Number(f64),
119}
120
121impl OptionValue {
122    pub fn as_str(&self) -> Option<&str> {
123        match self {
124            Self::String(s) => Some(s),
125            _ => None,
126        }
127    }
128
129    pub fn as_i64(&self) -> Option<i64> {
130        match self {
131            Self::Integer(i) => Some(*i),
132            _ => None,
133        }
134    }
135
136    pub fn as_bool(&self) -> Option<bool> {
137        match self {
138            Self::Boolean(b) => Some(*b),
139            _ => None,
140        }
141    }
142
143    pub fn as_f64(&self) -> Option<f64> {
144        match self {
145            Self::Number(n) => Some(*n),
146            _ => None,
147        }
148    }
149}
150
151/// Trait implemented by platform-specific context data
152///
153/// Platform implementations provide this to expose event data
154/// through the unified Context API.
155pub trait ContextData: ContextDataBounds + 'static {
156    fn channel_id(&self) -> &str;
157    fn user_id(&self) -> &str;
158    fn user_name(&self) -> &str;
159    fn command_name(&self) -> Option<&str>;
160    fn command_args(&self) -> Option<&str>;
161    fn option(&self, name: &str) -> Option<OptionValue>;
162    fn button_id(&self) -> Option<&str>;
163    fn message_content(&self) -> Option<&str>;
164    fn as_any(&self) -> &dyn Any;
165
166    /// Create a chat action sender for the current channel
167    ///
168    /// Returns `None` if the platform doesn't support chat actions
169    /// or if the necessary client is not available.
170    fn action_sender(&self) -> Option<Box<dyn ChatActionSender>> {
171        None
172    }
173}