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