-
Notifications
You must be signed in to change notification settings - Fork 2
Tutorial
We're going to write a bot that can evaluate math expressions and send the result in chats and inlinely.
Create a new crate:
cargo new tbot-example --bin
cd tbot-example
In your Cargo.toml
, add this:
[dependencies]
tbot = "0.1.0"
meval = "0.2"
We'll use meval
to evaluate math expressions. To work with Telegram Bots API,
you'll only need tbot
. This is one of our design principles.
We assume you're using Rust 2018. If you don't know about it, read the Rust team's introduction on their blog.
First of all, you'll need your bot's token. If you don't have one already,
create one with BotFather and it will give you the token. We'll assume you
set it in the environment as BOT_TOKEN
.
Once you're done with Cargo.toml
and the token, open src/main.rs
. First, in
main
, we'll get our token from the envionment:
let token = std::env::var("BOT_TOKEN").unwrap();
Next, we'll create a new Bot
. We'll add a use
so the code looks a little
better:
use tbot::Bot;
// ...
let mut bot = Bot::new(&token);
Our bot is going to listen to text messages. So, let's add a listener:
bot.on_message(|context| {
println!("Someone sent me a message!");
});
That's why we need mut
on bot
: under the hood, on_message
will push the
handler in bot
's internal Vec
. That's a trade-off for a really convenient
update subscription mechanism; see our design principles for more.
We'll learn about context
a bit later.
Though we added a listener, one thing is missing: we need to actually listen to updates. This can be done in two ways, and polling is the simplest one, so let's use it:
bot.polling().start();
Now run cargo run
and try to send your bot a message (and ignore the
compiler's warnings for now). It won't reply you yet, but you'll see a message
in your terminal that it received a message!
We can already receive updates, but how do we process them? It's easy if you
try: remember context
? That's where updates are coming to. To get the text of
the message, we need to use context.message
. Let's print it:
bot.on_message(|context| {
println!("What is {}?", context.message);
});
Now try it out. You will see the reply in your terminal.
Let's send a reply. First, we'll construct it with
context.send_message_in_reply
:
use tbot::prelude::*;
// ...
let reply = context
.send_message_in_reply(&format!("You sent me {}", context.message))
.into_future()
.map_err(|error| println!("Whops, got an error: {:#?}", error));
Note that we brought the prelude into the scope — calling map_err
requires
Future
to be in the scope, and tbot
has it in its prelude.
context.send_message_in_reply
will construct a message with the given text
with a reply to that message. Because methods have optional fields, they are
set with chained methods. Once you finish building a message, you must call
into_future
on it. Then you must handle erros that may happen during
sending the method. After that, we need to run the future. We're going to call
tbot::spawn
:
// ...
tbot::spawn(reply);
Though tbot::spawn
calls tokio::spawn
, tbot
's function lets a future's
Item
be anything: it will map it to ()
under the hood. This behavior is
pretty convinient because you won't need to process the response of many
methods, like in our case — we might map
the response and do something
with the returned message, but we don't care about it in this case.
Run your bot again. Now it will reply you.
But we actually want a math bot, not an echo one! Let's implement it now.
We're going to use meval
so we won't need to do math ourselves.
use tbot::types::ParseMode::Markdown;
bot.on_message(|context| {
// context is actually a reference, and anything in it is a reference too
let message = match meval::eval_str(context.message) {
Ok(result) => format!("= `{}`", result),
Err(_) => "Whops, I couldn't evaluate your expression :(".to_string(),
};
let reply = context.send_message_in_reply(&message)
.parse_mode(Markdown)
.into_future()
.map_err(|error| println!("Whops, got an error: {:#?}", error))
tbot::spawn(reply);
});
We wrap the result in Markdown's backticks, so it may be easier to copy
the result in some clients (e.g. on Android). So we're calling
.parse_mode(Markdown)
before turning the method into a Future
.
Now the bot will evaluate experessions it receives. Try it out!
Now we're going to implement the inline mode. It isn't hard to do.
First, ensure that your bot can accept inline updates. Go to BotFather, choose
your bot, click Bot Settings
→ Inline Mode
. It's off by default, so turn it
on if you haven't done it yet.
Next, we'll add another handler:
bot.on_inline(|context| ());
Note that for inline handlers, context
is completely different, but we'll get
through this. Instead of context.message
, we need to use context.query
.
Instead of context.send_message_in_reply
, we need to use
context.answer_inline_query
. Through replacing the first one is easy,
the second isn't.
use tbot::types::{
InlineQueryResult::Article,
InputMessageContent::Text,
};
// ...
let mut id: u32 = 0;
bot.on_inline(|context| {
let (title, message) = match meval::eval_str(context.message) {
Ok(result) => (
result.to_string(),
format!("`{} = {}`", context.message, result),
),
Err(_) => (
"Whops...".to_string(),
"I couldn't evaluate your expression :(".to_string(),
),
};
let reply = context.answer_inline_query([
Article::new(
&id.to_string(),
title,
Text::new(&message).parse_mode(Markdown),
),
])
.into_future()
.map_err(|err| eprintln!("Whops, got an error: {:#?}", err));
id += 1;
tbot::spawn(reply);
});
We pass a slice to context.answer_inline_query
of InineQueryResult
s. It
splits into 20 (at the time of writing this tutorial) variants, but we only need
one — Article
. We need to pass an ID, so we created the id
variable with
the initial value of 0u32
. When we answer, we stringify id
to pass it as an
ID (as Telegram requires), and later we increment it. If you simply send a
constant ID, shown results may fail to update as the user types.
It also needs InputMessageContent
which is divided into four types, and of all
of them we only need Text
.
Now we calculate the result, then send it. And voila! Your bot now can work inlinely.
Once you've read this tutorial, you're familiar with tbot
's design, and you
can start writing your own bots. You may want to check our
How-to
guides if you need or refer to our documentation to look up
how to use several methods or construct some types. If you get stuck, feel free
to fill an issue on our GitLab repository.