1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
/*!
`mobot` is a Telegram Bot framework for Rust.
It supports the full Telegram Bot API, and provides a simple framework
around managing routing and state for your bot.
# Framework
The key components of the framework are:
- [`Client`] is the main entry point to the Telegram API. It is used to send
requests to the Telegram API.
- [`Router`] is the main entry point to the bot. It is used to register
handlers for different types of events, and keeps track of the state of
the bot, passing it to the right handler.
- [`API`] is used to make direct calls to the Telegram API. An instance of `API` is
passed to all handlers within the [`Event`] argument.
- [`Handler`]s are functions that handle events. They are registered with
the [`Router`], and are called when an event is received.
Each [`Handler`] is passed an [`Event`] and a [`State`], and returns an
[`Action`].
- [`Action`]s are the result of [`Handler`] calls. They are used to send
responses to the Telegram API.
- [`Event`]s are the events that the bot receives. They are passed to
[`Handler`]s, and can be used to determine what action to take.
- [`State`] is the user-defined state of the bot. It is passed to `Handler`s, as
a generic parameter and can be used to store information about the bot. `State`
must implement the [`Default`] and [`Clone`] traits. [`Default`] is used to
initialize the state of a new chat session, and [`Clone`] is used while passing
the state to the handlers. `State`s are typically wrapped in an [`std::sync::Arc`], so
that they can be shared between threads.
## Example
In the example below we create a bot that replies to every message with the
text "Hello world!".
```no_run
use mobot::*;
#[tokio::main]
async fn main() {
let client = Client::new(std::env::var("TELEGRAM_TOKEN").unwrap());
let mut router = Router::new(client);
router.add_route(Route::Default, |_, _: State<()>| async move {
Ok(Action::ReplyText("Hello world!".into()))
});
router.start().await;
}
```
# Working with state
Every handler is passed a [`State`] object, which can be used to store
information about the bot. The `State` object is generic, and can be
any type that implements [`Default`], [`Clone`] and [`BotState`] traits.
[`State`]s are typically wrapped in an [`std::sync::Arc`], so that they can be shared between threads.
## Example
In the example below we create a bot that counts the number of messages
sent to it.
```no_run
use mobot::*;
#[derive(Clone, Default, BotState)]
struct App {
counter: usize,
}
async fn handle_chat_event(e: Event, state: State<App>) -> Result<Action, anyhow::Error> {
let message = e.update.get_message()?.clone();
let mut state = state.get().write().await;
state.counter += 1;
Ok(Action::ReplyText(format!("Pong {}: {}", state.counter, message.text.unwrap())))
}
#[tokio::main]
async fn main() {
let client = Client::new(std::env::var("TELEGRAM_TOKEN").unwrap());
Router::new(client).add_route(Route::Default, handle_chat_event).start().await;
}
```
You can initialize different handlers for different chats, with the `with_state` method:
```no_run
# use mobot::*;
#
# #[derive(Clone, Default, BotState)]
# struct App {}
# impl App {
# fn new() -> Self {
# Self {}
# }
# }
#
# async fn handle_chat_event(e: Event, state: State<App>) -> Result<Action, anyhow::Error> {
# unreachable!()
# }
# #[tokio::main]
# async fn main() {
# let client = Client::new(std::env::var("TELEGRAM_TOKEN").unwrap());
# let mut router = Router::new(client);
#
router
.with_state(App::new())
.add_route(Route::Default, handle_chat_event)
.start().await;
# }
```
# Working with routes
[`Route`]s are used to determine which handler should be called for a given event. Every
`Route` is paired with a [`Matcher`] which is tested against the incoming event. If the
matcher matches, the handler is called. If no matcher matches, the [`Route::Default`] handler
is called. If there are multiple handlers for a route/match pair, then they're executed in
the order they were added.
All routes are passed in the same [`State`] object, so they can share the same state with
each other.
## Example
```no_run
use mobot::*;
async fn handle_ping(e: Event, state: State<()>) -> Result<Action, anyhow::Error> {
Ok(Action::ReplyText("Pong".into()))
}
async fn handle_any(e: Event, state: State<()>) -> Result<Action, anyhow::Error> {
match e.update {
Update::Message(message) => {
Ok(Action::ReplyText(format!("Got new message: {}", message.text.unwrap())))
}
Update::EditedMessage(message) => {
Ok(Action::ReplyText(format!("Edited message: {}", message.text.unwrap())))
}
_ => { unreachable!() }
}
}
#[tokio::main]
async fn main() {
let client = Client::new(std::env::var("TELEGRAM_TOKEN").unwrap());
Router::new(client)
.add_route(Route::Message(Matcher::Exact("ping".into())), handle_ping)
.add_route(Route::Message(Matcher::Any), handle_any)
.add_route(Route::EditedMessage(Matcher::Any), handle_any)
.add_route(Route::Default, handlers::log_handler)
.start().await;
}
```
# Working with the Telegram API
## Easy API helpers
Many API calls have helper methods which you can call directly via the handler's [`Event`] parameter, for
example, see how [`Event::send_message`] is used below to send a message to the chat:
```no_run
# use mobot::*;
#
async fn handle_chat_event(e: Event, state: State<()>) -> Result<Action, anyhow::Error> {
// Get a new direct message or channel post
let message = e.update.get_message_or_post()?.clone();
// Reply back to the chat with the same message
e.send_message(format!("Your message: {}", message.text.unwrap())).await?;
Ok(Action::Done)
}
```
## Using the Telegram API directly
You can use the [`API`] struct to make calls to the Telegram API. An instance of `API` is
passed to all handlers within the `Event` argument (See [`Event`] and [`Event`]).
## Example
```no_run
# use mobot::*;
#
async fn handle_chat_event(e: Event, state: State<()>) -> Result<Action, anyhow::Error> {
match e.update {
Update::Message(message) => {
e.api
.send_message(&api::SendMessageRequest::new(
message.chat.id, format!("Message: {}", message.text.unwrap())
)).await?;
}
Update::ChannelPost(message) => {
e.api
.send_message(&api::SendMessageRequest::new(
message.chat.id, format!("Channel post: {}", message.text.unwrap())
)).await?;
}
_ => anyhow::bail!("Unhandled update"),
}
Ok(Action::Done)
}
```
MOBOT supports most of the major API calls, however if you need to add more structures or calls, you
can do it by adding a file to `lib/api`. See `lib/api/sticker.rs` for an example of how `sendSticker` was
supported.
*/
extern crate log;
pub use Action;
pub use *;
pub use ;
pub use Event;
pub use ;
pub use ProgressBar;
pub use ;
pub use Text;
pub use Update;
/// Expose mobot_derive macros
pub use BotRequest;
pub use BotState;
/// This method initializes [`env_logger`] from the environment, defaulting to `info` level logging.