📜 ⬆️ ⬇️

We write Telegram-bot on Rust, which will run the code on ... Rust?

Goodnight! Today I would like to briefly describe how to write a Telegram bot on Rust, which will run the code on Rust. The article does not have a goal to make a full immersion in the API telegram_bot, Serde, Telegram or in the nuances of development on Rust. Rather, it is exploratory in nature. Peano numbers using the type system will not add.
Preview of the article



Creating a bot in Telegram


A temporary link to the bot, so that you can see the result


First, create a bot and get an HTTP API token.


We go to this guy and write the following:


We initiate the creation of a new bot: /newbot .
The answer of the godfather:


Alright, a new bot. How are we going to call it? Please choose a name for your bot.

In the answer we write the name of the bot we want to create: rust .
The answer of the godfather:


Good Now let's choose a username for your bot. It must end in bot . Like this, for example: TetrisBot or tetris_bot.

Following the directions, enter another name: rustlanguage_bot .
The answer of the godfather:


Done! Congratulations on your new bot. You will find it at t.me/rustlanguage_bot. You can now add a list of commands. By the way, you’ll be better than the username for it. Just make sure the bot is fully functional before you do this. Use this token to access the HTTP. API:% TOKEN% For a description of the Bot API, see this page: https://core.telegram.org/bots/api

Fine. Bot created. %TOKEN% - this is actually the token.


Rust playground


Now a little about how and where to run the code that the user will send to the bot as a message.


There is a Rust Playground service that allows you to run the simplest Rust code online. We will use it. It is located at this address: https://play.rust-lang.org/


Following the link, we introduce a simple hello-world program:


 fn main() { println!("Hello world!"); } 

Open the Network tab from DevTools to see what and in what format it sends to get the results of the compilation:


Rust Playground Request


It seems to be all transparent and clear. Let's try to reproduce from the console:


 [loomaclin@localhost ~]$ curl -X POST -d '{"code":"fn main() {\n println!(\"Hello world!\");\n}","version":"stable","optimize":"0","test":false,"separate_output":true,"color":true,"backtrace":"0"}' https://play.rust-lang.org/evaluate.json {"program":"Hello world!\n","rustc":"rustc 1.16.0 (30cf806ef 2017-03-10)\n"} 

Great, let's go further.


Write bot


Create a project:


 cargo new rust_telegram_bot --bin 

Add the following dependencies to Cargo.toml :


 [dependencies] telegram-bot = { git = "https://github.com/White-Oak/telegram-bot.git" } hyper = "0.10.8" hyper-rustls = "0.3.2" serde_json = "0.9.10" serde = "0.9.14" serde_derive = "0.9.14" 

I will briefly describe why they are needed:



In src/main.rs include all the necessary libraries and modules:


 extern crate telegram_bot; extern crate hyper; extern crate hyper_rustls; extern crate serde_json; extern crate serde; #[macro_use] extern crate serde_derive; use serde_json::Value; use telegram_bot::{Api, MessageType, ListeningMethod, ListeningAction}; use std::io::Read; use hyper::client::Client; use hyper::net::HttpsConnector; use hyper_rustls::TlsClient; 

Note: #[macro_use] used to include in the scope of the current program macros from the library to which this attribute was applied.


In this line, we import modules from the library root to determine the type of message, the "wiretapping" method, and the structure representing the Telegram API:


 use telegram_bot::{Api, MessageType, ListeningMethod, ListeningAction}; 

Using enum , we describe the possible types of responses from the server, and there are 2 of them in this case, when the program was compiled successfully, and when a compilation error occurred:


 #[derive(Serialize, Deserialize, Debug)] #[serde(untagged)] pub enum ResponseType { ProgramCompiled { program: String, rustc: String }, ProgramCompileError { rustc: String } } 

Noticed the attribute #[serde(untagged)] , which was applied to the enumeration? He says that when (de) serializing, for the enumeration options, no tag will be searched for that clearly indicates which one it is. So how does Serde determine which response option from the server we received? In fact, it will try to deserialize into each of the options until it reaches the first successful result. More details about this can be found in the official documentation: https://serde.rs/enum-representations.html .


Define the structure for our query in the Rust Playground:


 #[derive(Serialize)] pub struct PlaygroundRequest { code: String, version: String, optimize: String, test: bool, separate_output: bool, color: bool, backtrace: String } 

From the user input in this structure only the code field will go. The rest is hard-coded, because we always do this :) (no)


In the main function of the main program, we will create the Telegram API instance and make it print everything that came to the bot in the message:


 fn main() { let api = Api::from_env("TOKEN").unwrap(); println!("getMe: {:?}", api.get_me()); let mut listener = api.listener(ListeningMethod::LongPoll(None)); let res = listener.listen(|u| if let Some(m) = u.message { let name = m.from.first_name; match m.msg { MessageType::Text(t) => { println!("<{}> {}", name, t); } _ => {} } }); } 

To check the performance of this code, run the program, not forgetting to pass the real token received earlier as the environment variable:


 TOKEN=%TOKEN% cargo run 

Let us analyze a bit what we wrote above.


  let api = Api::from_env("TOKEN").unwrap(); println!("getMe: {:?}", api.get_me()); 

Here we create an instance of the Api structure imported from telegram_bot , then create a bot listener in long-polling mode:


 let mut listener = api.listener(ListeningMethod::LongPoll(None)); 

Finally, we create a message processing loop using the listen function and matching by the message type pattern:


  let res = listener.listen(|u| if let Some(m) = u.message { let name = m.from.first_name; match m.msg { MessageType::Text(t) => { println!("<{}> {}", name, t); } _ => {} } }); 

We agree that we will transmit the code only in text form. Files, etc. exclude. For this, as you can see, all the other enumeration options for MessageType simply ignored.


We process the /rust command by sending a request to the Rust Playground, and read the answer:


  if t.starts_with("/rust ") { let program = t.split("/rust ").collect(); let mut result = String::new(); let tls = hyper_rustls::TlsClient::new(); let connector = HttpsConnector::new(tls); let client = Client::with_connector(connector); let playground_request = serde_json::to_string(&PlaygroundRequest { code: program, version: String::from("stable"), optimize: String::from("0"), test: false, separate_output: true, color: false, backtrace: String::from("0"), }) .unwrap(); let mut response = client .post("https://play.rust-lang.org/evaluate.json") .body(&playground_request) .send() .unwrap(); response.read_to_string(&mut result); println!("Result : {:?}", result); } 

We process the request only if the message starts with a certain command ( /rust ):


 if t.starts_with("/rust ") { 

And also pull out the program code that needs to be compiled:


  let program = t.split("/rust ").collect(); 

The serde_json::to_string(&PlaygroundReques { ... }) function serializes our query structure to a string. The rest of the code refers to the initialization of the HTTPS client, sending and reading the request, more about this can be found here: https://hyper.rs/hyper/v0.10.7/hyper/index.html .


We process the come answer:


  let result : ResponseType = serde_json::from_str(&result) .unwrap_or(ResponseType::ProgramCompileError { rustc: String::from("     ") }); let mut result = match result { ResponseType::ProgramCompiled { program, .. } => { format!("  : {}", program) } ResponseType::ProgramCompileError { rustc, .. } => { format!("  : {}", rustc) } }; 

The serde::from_str deserializes the reply to one of our enum variants. In case the answer could not be deserialized, for simplicity we wrap it in a compilation error variant with the appropriate text. Next, we form our resulting message, which will be sent to the user, based on which of the enum options was presented. Perhaps, for the first time, you see the pattern type { program, .. } in the pattern matching, I will explain - this ignores the structure, which we do not need during the processing of this variant, when the structure is restructured.


Sending compilation results to chat:


  if result.len() > 500 { result.truncate(500); } try!(api.send_message(m.chat.id(), result, None, None, Some(m.message_id), None)); 

At the end, we check the length of the message to exclude the results of the compilation with a large amount of output, and cut the case. After we send the message, specifying the identifier of the chat from which the request for compilation came, and transmit the final result of the compilation. We also send the id of the message you need to reply to. The rest of the transmitted parameters are optional and are responsible for displaying the preview, the type of response, and the like.


We check the performance


Chat with a rust telegram bot


Output to console:


  Finished dev [unoptimized + debuginfo] target(s) in 2.38 secs Running `target/debug/rust_telegram_bot` getMe: Ok(User { id: 334562900, first_name: "rust", last_name: None, username: Some("rustlanguage_bot") }) <Arsen> /rust abc Result : "{\"rustc\":\"rustc 1.16.0 (30cf806ef 2017-03-10)\\nerror: expected one of `!` or `::`, found `<eof>`\\n --> <anon>:1:1\\n |\\n1 | abc\\n | ^^^\\n\\nerror: aborting due to previous error\\n\\n\"}" <Arsen> /rust fn main() { println!("Hello habrahabr!"); } Result : "{\"program\":\"Hello habrahabr!\\n\",\"rustc\":\"rustc 1.16.0 (30cf806ef 2017-03-10)\\n\" }" 

Conclusion


I think that's all. Thanks to WhiteOak for the telegram_bot fork.
By the way, he has a project for binding to QML from Rust: https://github.com/White-Oak/qml-rust . Perhaps someone will be interesting.


Any constructive criticism is welcome.
The repository with the full code of this bot is located here .


I almost forgot to leave a link to the chat of the Russian-speaking community Rust, where you will always be helped to cope with the language: https://gitter.im/ruRust/general


')

Source: https://habr.com/ru/post/326830/


All Articles