Skip to main content

botkit_core/
extractor.rs

1use std::future::Future;
2
3use crate::action::ChatActionGuard;
4use crate::context::Context;
5
6#[cfg(not(target_arch = "wasm32"))]
7pub trait FromContextBounds: Send {}
8#[cfg(not(target_arch = "wasm32"))]
9impl<T: Send> FromContextBounds for T {}
10
11#[cfg(target_arch = "wasm32")]
12pub trait FromContextBounds {}
13#[cfg(target_arch = "wasm32")]
14impl<T> FromContextBounds for T {}
15
16#[cfg(not(target_arch = "wasm32"))]
17pub trait ContextFutureBounds: Future + Send {}
18#[cfg(not(target_arch = "wasm32"))]
19impl<T: Future + Send + ?Sized> ContextFutureBounds for T {}
20
21#[cfg(target_arch = "wasm32")]
22pub trait ContextFutureBounds: Future {}
23#[cfg(target_arch = "wasm32")]
24impl<T: Future + ?Sized> ContextFutureBounds for T {}
25
26/// Trait for extracting typed data from bot context
27///
28/// Similar to skyzen's `Extractor` trait, this allows handlers to
29/// declare what data they need as function parameters.
30///
31/// # Example
32/// ```ignore
33/// // Built-in extractors
34/// async fn greet(user: User) -> String {
35///     format!("Hello, {}!", user.name)
36/// }
37///
38/// async fn echo(args: CommandArgs) -> String {
39///     args.0
40/// }
41/// ```
42pub trait FromContext: Sized + FromContextBounds {
43    /// Extract data from the context
44    fn from_context(ctx: &Context) -> impl ContextFutureBounds<Output = Self>;
45}
46
47/// Extract the full context (for advanced use cases)
48impl FromContext for Context {
49    async fn from_context(ctx: &Context) -> Self {
50        ctx.clone()
51    }
52}
53
54/// User information extractor
55#[derive(Debug, Clone)]
56pub struct User {
57    pub id: String,
58    pub name: String,
59}
60
61impl FromContext for User {
62    async fn from_context(ctx: &Context) -> Self {
63        Self {
64            id: ctx.user_id().to_string(),
65            name: ctx.user_name().to_string(),
66        }
67    }
68}
69
70/// Channel information extractor
71#[derive(Debug, Clone)]
72pub struct Channel {
73    pub id: String,
74}
75
76impl FromContext for Channel {
77    async fn from_context(ctx: &Context) -> Self {
78        Self {
79            id: ctx.channel_id().to_string(),
80        }
81    }
82}
83
84/// Command name extractor
85#[derive(Debug, Clone)]
86pub struct CommandName(pub String);
87
88impl FromContext for CommandName {
89    async fn from_context(ctx: &Context) -> Self {
90        Self(ctx.command_name().unwrap_or_default().to_string())
91    }
92}
93
94/// Command arguments extractor (Telegram-style string args)
95#[derive(Debug, Clone)]
96pub struct CommandArgs(pub String);
97
98impl FromContext for CommandArgs {
99    async fn from_context(ctx: &Context) -> Self {
100        Self(ctx.command_args().unwrap_or_default().to_string())
101    }
102}
103
104/// Button/callback ID extractor
105#[derive(Debug, Clone)]
106pub struct ButtonId(pub String);
107
108impl FromContext for ButtonId {
109    async fn from_context(ctx: &Context) -> Self {
110        Self(ctx.button_id().unwrap_or_default().to_string())
111    }
112}
113
114/// Message content extractor
115#[derive(Debug, Clone)]
116pub struct MessageContent(pub String);
117
118impl FromContext for MessageContent {
119    async fn from_context(ctx: &Context) -> Self {
120        Self(ctx.message_content().unwrap_or_default().to_string())
121    }
122}
123
124// Implement FromContext for tuples (for multiple extractors)
125impl FromContext for () {
126    async fn from_context(_ctx: &Context) -> Self {}
127}
128
129impl<T1: FromContext> FromContext for (T1,) {
130    async fn from_context(ctx: &Context) -> Self {
131        (T1::from_context(ctx).await,)
132    }
133}
134
135impl<T1: FromContext, T2: FromContext> FromContext for (T1, T2) {
136    async fn from_context(ctx: &Context) -> Self {
137        let t1 = T1::from_context(ctx).await;
138        let t2 = T2::from_context(ctx).await;
139        (t1, t2)
140    }
141}
142
143impl<T1: FromContext, T2: FromContext, T3: FromContext> FromContext for (T1, T2, T3) {
144    async fn from_context(ctx: &Context) -> Self {
145        let t1 = T1::from_context(ctx).await;
146        let t2 = T2::from_context(ctx).await;
147        let t3 = T3::from_context(ctx).await;
148        (t1, t2, t3)
149    }
150}
151
152impl<T1: FromContext, T2: FromContext, T3: FromContext, T4: FromContext> FromContext
153    for (T1, T2, T3, T4)
154{
155    async fn from_context(ctx: &Context) -> Self {
156        let t1 = T1::from_context(ctx).await;
157        let t2 = T2::from_context(ctx).await;
158        let t3 = T3::from_context(ctx).await;
159        let t4 = T4::from_context(ctx).await;
160        (t1, t2, t3, t4)
161    }
162}
163
164/// Typing indicator extractor
165///
166/// Automatically starts a typing indicator when extracted.
167/// The indicator stops when the handler completes (guard is dropped).
168///
169/// # Example
170/// ```ignore
171/// async fn slow_handler(_typing: Typing) -> String {
172///     expensive_work().await;
173///     "Done!"
174/// }
175/// ```
176pub struct Typing(pub Option<ChatActionGuard>);
177
178impl FromContext for Typing {
179    async fn from_context(ctx: &Context) -> Self {
180        Self(ctx.typing())
181    }
182}