📜 ⬆️ ⬇️

TelegramBot in the Wolfram Cloud

Introduction


The period passed when every second article on Habrahabr was devoted to writing its telegram bot. Also, a period of time passed when the bot could easily be placed on your computer or hosting in Russia. Just six months ago, my bot ran just on a laptop and did not experience any problems connecting to the API. But now, when I thought about getting him back to work, I realized that it would not be so easy. I did not want to search and configure a proxy server, and even more so abroad. Also, before that, I wrote a bot on Wolfram Language and had no idea how the language works with proxy servers, since I have not used them yet. And then there was a great idea! Use the Wolfram Cloud. In this article I want to show how simple it is to register, but without SMS you can run your simple telegram bot written in Wolfram Language. From the tools you need for this only the browser.


A bit about the Wolfram cloud


To access the cloud you need to create a Wolfram account. To do this, go to https://account.wolfram.com and follow the instructions after clicking the Create One button.



After all the manipulations done on the main cloud page at https://www.wolframcloud.com all products and their usage plans will be displayed. You must select the Development Platform and create a new notepad.



All the code given below will be executed in this cloud notebook.


Just a little bit about telegram bots


There are many articles devoted to them. Here you just have to say that before you perform all further actions, you need to create a bot in a standard way. That is, just start a chat with the @BotFather bot and send it the command:


/newbot 

Then you just need to follow the instructions and enter the name and login. Let his name be Wolfram Cloud Bot and login @ WolframCloud5973827Bot.



API implementation


We will use the @BotFather recommendations and briefly examine the HTTP API of telegram bots. Tasks for the implementation of the entire API is not yet worth it. For writing a bot, only a small part is enough. Check that the API is available and the bot with the above token exists. To do this, just run one line:


 URLExecute["https://api.telegram.org/bot753681357:AAFqdRFN_QoODJxsBy3VN2sVwWTPKJEqteY/getMe"] 

Out [..]: = ...
 {"ok" -> True, "result" -> {"id" -> 753681357, "is_bot" -> True, "first_name" -> "Wolfram Cloud Bot", "username" -> "WolframCloud5973827Bot"}} 

The command above is the easiest way to perform an HTTP request from Wolfram Language. But let's complicate it a bit so that it is easy to implement all other API methods. Create a common method for executing an API request:


 TelegramBot::usage = "TelegramBot[token]"; $telegramAPI = "https://api.telegram.org"; telegramExecute[ TelegramBot[token_String], method_String, parameters: {(_String -> _)...}: {} ] := Module[{ request, requestURL, requestRules, requestBody, response, responseBody }, requestURL = URLBuild[{$telegramAPI, "bot" <> token, method}]; requestRules = DeleteCases[parameters, _[_String, Automatic | Null | None]]; requestBody = ImportString[ExportString[requestRules, "JSON"], "Text"]; request = HTTPRequest[requestURL, <| Method -> "POST", "ContentType" -> "application/json; charset=utf-8", "Body" -> requestBody |>]; response = URLRead[request]; responseBody = response["Body"]; Return[ImportString[responseBody, "RawJSON"]] ] 

Check if this works on the method already tested above:


 token = "753681357:AAFqdRFN_QoODJxsBy3VN2sVwWTPKJEqteY"; bot = TelegramBot[token]; telegramExecute[bot, "getMe"] 

Out [..]: = ...
 <|"ok" -> True, "result" -> <|"id" -> 753681357, "is_bot" -> True, "first_name" -> "Wolfram Cloud Bot", "username" -> "WolframCloud5973827Bot"|>|> 

Fine. Let's create a separate function to perform a bot check:



 getMe::usage="getMe[bot]"; TelegramBot /: getMe[bot_TelegramBot] := telegramExecute[bot, "getMe"] getMe[bot] 

Out [..]: = ...
 <|"ok" -> True, "result" -> <|"id" -> 753681357, "is_bot" -> True, "first_name" -> "Wolfram Cloud Bot", "username" -> "WolframCloud5973827Bot"|>|> 

Now, in this way, it remains to add the basic methods that are needed to create a bot in the cloud:



 getUpdates::usage = "getUpdates[bot, opts]"; Options[getUpdates] = { "offset" -> Automatic, "limit" -> Automatic, "timeout" -> Automatic, "allowed_updates" -> Automatic }; TelegramBot /: getUpdates[bot_TelegramBot, opts: OptionsPattern[getUpdates]] := telegramExecute[bot, "getUpdates", Flatten[{opts}]] 


 setWebhook::usage = "setWebhook[bot, url, opts]"; Options[setWebhook] = { "certificate" -> Automatic, "max_connections" -> Automatic, "allowed_updates" -> Automatic }; TelegramBot /: setWebhook[bot_TelegramBot, url_String, opts: OptionsPattern[setWebhook]] := telegramExecute[bot, "setWebhook", Join[{"url" -> url}, Flatten[{opts}]]] 


 deleteWebhook::usage = "deleteWebhook[bot]"; TelegramBot /: deleteWebhook[bot_TelegramBot] := telegramExecute[bot, "deleteWebhook"] 


 getWebhookInfo::usage = "getWebhookInfo[bot]"; TelegramBot /: getWebhookInfo[bot_TelegramBot] := telegramExecute[bot, "getWebhookInfo"] 


 sendMessage::usage = "sendMessage[bot, chat, text]"; Options[sendMessage] = { "parse_mode" -> Automatic, "disable_web_page_preview" -> Automatic, "disable_notification" -> Automatic, "reply_to_message_id" -> Automatic, "reply_markup" -> Automatic }; TelegramBot /: sendMessage[bot_TelegramBot, chat_Integer, text_String, opts: OptionsPattern[sendMessage]] := telegramExecute[ bot, "sendMessage", Join[{"chat_id" -> chat, "text" -> text}, Flatten[{opts}]] ] 

Minimal API version is ready. Let's check how sending a message and receiving updates work. To do this, create a chat with our bot. When creating a bot, the first message with the / start text will be sent. Let's see whether it got into the list of updates:


 updates = getUpdates[bot] 

Out [..]: = ...
 <|"ok" -> True, "result" -> {<|"update_id" -> 570790461, "message" -> <|"message_id" -> 1, "from" -> <|"id" -> 490138492, "is_bot" -> False, "first_name" -> "Kirill", "last_name" -> "Belov", "username" -> "KirillBelovTest"|>, "chat" -> <|"id" -> 490138492, "first_name" -> "Kirill", "last_name" -> "Belov", "username" -> "KirillBelovTest", "type" -> "private"|>, "date" -> 1542182547, "text" -> "/start", "entities" -> {<|"offset" -> 0, "length" -> 6, "type" -> "bot_command"|>}|>|>}|> 

You can get the latest update data from the list of updates:


 lastUpdate = updates["result"][[-1]] 

Out [..]: = ...
 <|"update_id" -> 570790461, "message" -> <|"message_id" -> 1, "from" -> <|"id" -> 490138492, "is_bot" -> False, "first_name" -> "Kirill", "last_name" -> "Belov", "username" -> "KirillBelovTest"|>, "chat" -> <|"id" -> 490138492, "first_name" -> "Kirill", "last_name" -> "Belov", "username" -> "KirillBelovTest", "type" -> "private"|>, "date" -> 1542182547, "text" -> "/start", "entities" -> {<|"offset" -> 0, "length" -> 6, "type" -> "bot_command"|>}|>|> 

And this is how you can get the chat from which the message came and the message text itself:


 chat = lastUpdate["message", "chat", "id"] text = lastUpdate["message", "text"] 

Out [..]: = ...
 490138492 /start 

As can be seen from the result of the execution - everything is in place. Now we will send a message on behalf of the bot using sendMessage.


 sendMessage[bot, chat, "hello"] 

Out [..]: = ...
 <|"ok" -> True, "result" -> <|"message_id" -> 2, "from" -> <|"id" -> 753681357, "is_bot" -> True, "first_name" -> "Wolfram Cloud Bot", "username" -> "WolframCloud5973827Bot"|>, "chat" -> <|"id" -> 490138492, "first_name" -> "Kirill", "last_name" -> "Belov", "username" -> "KirillBelovTest", "type" -> "private"|>, "date" -> 1542182601, "text" -> "hello"|>| > 


In general, this set of functions is already enough. However, using the getUpdates method is not very convenient. You need to think of a way to handle messages using a webhook.


Create webhook


Wolram Langauge has a special kind of function that is created using the APIFunction. Here is an example of one of these:


 apiFunc = APIFunction[{"n" -> "Integer"}, Plot[Sin[#n * x], {x, -2Pi, 2Pi}]&, "PNG"]; apiFunc[{"n"->3}] 

Out [..]: = ...


These features are specifically designed for deployment in the cloud. This function will accept one request parameter as input. To deploy it in the cloud, it is enough to transfer the function itself to CloudDeploy.


 apiObject = CloudDeploy[apiFunc, "Deploy/apiObject"] 

Out [..]: = ...
 CloudObject[https://www.wolframcloud.com/objects/kirillbelovtest/apiObject] 

Then you can follow the received link in the browser and add a query parameter:



The function above handled the request parameters. So you need to create the same function to process the HTTP request body, received from the telegram bot in the form of an Update object. To generate an address, we use a token to access the cloud object was more difficult. It is also necessary to indicate that the object has public access, otherwise the telegrams will not be able to get on the webhook.


 deployWebhook[bot_TelegramBot, handler_] := CloudDeploy[APIFunction[{}, handler[HTTPRequestData["Body"]] &], "Deploy/Webhooks/" <> Hash[bot, "SHA", "HexString"], Permissions -> "Public" ] 

handler is another handler function. Let the handler turn the request body string into an association, get the chat identifier from there and send back the word "hello".


 handlerHello[bot_TelegramBot][body_String] := Block[{json = ImportString[body, "RawJSON"], chat}, chat = json["message", "chat", "id"]; sendMessage[bot, chat, "hello"]; ] 

Now we will deploy a function in the cloud.


 webhookObject = deployWebhook[bot, handlerHello[bot]] 

Out [..]: = ...
 CloudObject[https://www.wolframcloud.com/objects/kirillbelovtest/Deploy/Webhooks/b9bd74f89348faecd6b683ba02637dd4d4028a28] 

And the last step - give the address of this object telegram-bot.


 setWebhook[bot, webhookObject[[1]]] 

Out [..]: = ...
 <|"ok" -> True, "result" -> True, "description" -> "Webhook was set"|> 

Now we’ll write something to the bot and see what it says:



The dialogue can be considered valid. In order to change the logic of an already existing handler, it is sufficient to redeploy the cloud object. At the same time, installing the webhook for the bot is no longer required.


Response logic


This will be the last part in the process of creating a bot in the Wolfram cloud. Further in the same way you can complicate the logic and add new API methods. Now about the dialogue itself. Let, after sending the command / start, the bot returns the answer "Hello" and changes the user's keyboard. The keyboard remains only two buttons: "Hello" and "Who are you?". We realize the dialogue in the form of an association. The keys are the commands that the user sends to the bot. Values ​​of keys - the answer of the bot and the new keyboard. At the same time, the set of keys and buttons must completely coincide. Otherwise, a situation may appear when the bot does not know what to answer. In such cases, of course, you can add a default answer.


 keyboard[buttons : {__String}] := {"keyboard" -> {Table[{"text" -> button}, {button, buttons}]}, "resize_keyboard" -> True} $answers = <| (*user_text-><|"answer"->bot_text,"keyboard"->next_text|>*) "/start"-><|"answer"->"","keyboard"-> keyboard[{""," ?"}]|>, ""-><|"answer"->" ?", "keyboard" -> keyboard[{" ?"}]|> , " ?"-><|"answer"->"", "keyboard" -> keyboard[{""}]|> , " ?"-><|"answer"->"   Wolfram Language   ", "keyboard"->keyboard[{" ?"," ?"}]|> , " ?"-><|"answer"->"   :\nhttps://habr.com/post/422517/", "keyboard"->keyboard[{""," ?"}]|> , " ?"-><|"answer"->"  :\n@KirillBelovTest", "keyboard"->keyboard[{" ?",""}]|> , ""-><|"answer"->"", "keyboard"->keyboard[{""," ?"}]|> |>; answer[text_String] /; KeyExistsQ[$answers, text] := $answers[text] 

Now create a handler:


 handlerAbout[bot_TelegramBot][body_String] := Block[{json = ImportString[body, "RawJSON"], chat, text}, chat = json["message", "chat", "id"]; text = json["message", "text"]; sendMessage[bot, chat, answer[text]["answer"], "reply_markup" -> answer[text]["keyboard"]]; ] 

And redeploy the cloud object:


 deployWebhook[bot, handlerAbout[bot]]; 

Gain that happened in the chat with the bot. But first, let's clear the message history:



Expansion of functionality


So far, there are no fundamental differences from the huge number of existing bots. Maybe there is no point in his drinking either? The meaning of all the work done above will be, if you understand the actual benefits of such a bot! After all, he can use all the features of Wolfram Language and Wolrfam Cloud. It is necessary that the robot was able to solve equations? It is very easy! It is only necessary to determine the answer!


 answer[text_String]["keyboard"] /; StringContainsQ[text, "  "] := Automatic answer[text_String]["answer"] /; StringContainsQ[text, "  "] := ToString[Flatten[Block[{args = StringSplit[text, "  "]}, Solve[ToExpression[args[[1]]], ToExpression[args[[2]]]] ]]] deployWebhook[bot, handlerAbout[bot]]; 


If someone additionally has an interest in the capabilities of the cloud, then a good description of its functionality is here .


Restrictions


Wolfram Cloud is a platform that allows you to use the Wolfram language for free, while the main product of Wolfram Research is Mathematica worth the money. Accordingly, there are restrictions on the use and in my opinion they are very strong. When using the free version of the Development Platform, a user is given 1000 cloud credits per month. Each cloud loan gives time to calculate a different type. Since the article talks about CloudDeploy + APIFunction, such objects stored in the cloud spend 1 credit in 0.1 seconds of computing time. It is easy to calculate that the user is given out just 1 minute and 40 seconds of server time for the operation of his application (in this case, the bot). I have nothing to add here - it is very, very small. The main focus is on users who work in the Development Platform independently using a browser. Indeed, in this mode there are no time limits, but only on the duration of the session and the resources allocated. With this use, the Development Platform is almost a complete Mathematica, but does not require installation and a license.


Article in the Wolfram Cloud


')

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


All Articles