TelegramBot в облаке Wolfram

15 ИЮНЯ 2022

------------------------------------------------------------------------

Наш телеграм-канал: t.me/@mailsgun_online

Youtube: youtube.com/@mailsgun_online

------------------------------------------------------------------------

Введение

Прошел тот период, когда каждая вторая статья на Habrahabr была посвящена написанию своего телеграмм-бота. Также прошел период времени, когда бота без трудностей можно было разместить на своем компьютере или хостинге в России. Еще полгода назад мой бот запускался просто на ноутбуке и не испытывал никаких проблем с подключением к API. Но сейчас, когда я задумался над тем, чтобы вернуть его в работу, я понял, что это будет не так легко. Не хотелось искать и настраивать прокси-сервер и тем более за рубежом. Также до этого я писал бота на Wolfram Language и не имел представления о том, как язык работает с прокси-серверами, так как до сих пор их не использовал. И тут появилась замечательная идея! Использовать Wolfram Cloud. В этой статье я хочу показать, как очень просто с регистрацией, но без смс можно запустить своего простого телеграм-бота, написанного на Wolfram Language. Из инструментов понадобится для этого только браузер.

Немного про облако Wolfram

Чтобы получить доступ к облаку необходимо создать аккаунт Wolfram. Для этого нужно перейти по адресу https://account.wolfram.com и следовать инструкциям после нажатия на кнопку Create One.

После всех проделанных манипуляций на главной странице облака по адресу https://www.wolframcloud.com будут отображаться все продукты и их планы использования. Необходимо выбрать Development Platform и создать новый блокнот.

Весь код приведенный в дальнейшем, будет выполняться именно в этом облачном блокноте.

Совсем немного о телеграмм-ботах

Существует огромное множество статей посвященных им. Здесь всего лишь надо сказать, что перед тем как выполнять все дальнейшие действия, бота надо создать стандартным способом. То есть просто начать чат с ботом @BotFather и отправить ему команду:

/newbot

Дальше просто необходимо следовать инструкциям и ввести имя и логин. Пусть его имя будет Wolfram Cloud Bot и логин @WolframCloud5973827Bot.

Реализация API

Воспользуемся рекомендациями @BotFather и бегло осмотрим HTTP API телеграм-ботов. Задачи по реализации всего API целиком пока не стоит. Для написания бота достаточно только небольшой части. Проверим, что API доступно и бот с указанным выше токеном существует. Для этого достаточно выполнить всего одну строчку:

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"}}

Команда выше — самый простой способ выполнить HTTP запрос из Wolfram Language. Но немного усложним его, чтобы было легко реализовать все остальные методы API. Создадим общий метод выполнения запроса к API:

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"]]
]

Проверим работает ли это на уже протестированном выше методе:

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"|>|>

Отлично. Создадим отдельно функцию для выполнения проверки бота:

  • getMe — информация о боте

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"|>|>

Теперь подобным образом осталось добавить основные методы, которые необходимы для создания бота в облаке:

  • getUpdates — получает все последние сообщения написанные боту

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 — устанавлиевает адрес серевера для обработки обновлений

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 — отправка сообщения в чат

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}]]
]

Минимальная версия API готова. Проверим как работает отправка сообщение и получение обновлений. Для этого создадим чат с нашим ботом. При создании боту отправится первое сообщение с тектом /start. Посморим — попало ли оно в список обновлений:

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"|>}|>|>}|>

Получить из списка обновлений данные последнего обновления можно так:

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"|>}|>|>

А вот так можно получить чат, из которого пришло сообщение и сам текст сообщения:

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

Out[..] := …

490138492
/start

Как видно из результат выполнения — все на месте. Теперь отправим сообщение от имени бота используя 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"|>|
 >

В общем-то этого набора функций уже достаточно. Однако, использовать метод getUpdates не очень удобно. Нужно придумать способ, как обрабатывать сообщения с помощью webhook.

Создание webhook

В Wolram Langauge есть специальный вид функций, которые создаются с помощью APIFunction. Вот пример одной из таких:

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

Out[..] := …

Такие функции предназначены специально для развертывания в облаке. Данная функция будет принимать на вход один параметр запроса. Чтобы развернуть ее в облаке достаточно передать саму функцию в CloudDeploy.

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

Out[..] := …

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

Затем можно перейти по полученной ссылке в браузере и добавить параметр запроса:

Функция выше обрабатывала параменты запроса. Значит нужно создать такую же функцию для обработки тела HTTP запроса, прирходящего от телеграм-бота в виде объекта Update. Для генерации адреса используем токен, чтобы получить доступ к облачному объекту было сложнее. Также необходимо указать, что объект имеет публичный доступ, иначе телеграм не сможет попасть на webhook.

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

handler — другая функция обработчик. Пусть обработчик превращает строку тела запроса в ассоциацию, получает оттуда идентификатор чата и высылает обратно слово «hello».

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

Теперь развернем фунцкию в облаке.

webhookObject = deployWebhook[bot, handlerHello[bot]]

Out[..] := …

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

И последний шаг — передадим адрес этого объекта телеграм-боту.

setWebhook[bot, webhookObject[[1]]]

Out[..] := …

<"ok" -> True, "result" -> True, "description" -> "Webhook was set">

Теперь напишем что-нибуд боту и посмотрим что он ответит:

Диалог можно считать состоявшимся. Для того чтобы изменить логику работы уже существующего обработчика — достаточно повторно выполнить развертывание облачного объекта. При этом выполнять установку webhook для бота уже не потребуется.

Логика ответов

Это будет последняя часть в процессе создания бота в облаке Wolfram. Дальше таким же образом можно усложнять логику и добавлять новые методы API. Теперь о самом диалоге. Пусть, после отправки команды /start бот возвращает ответ «Привет» и меняет клавиатуру пользователя. В клавиатуре остается всего две кнопки: «Привет» и «Кто ты?». Реализуем диалог в виде ассоциации. Ключами будут команды, которые высылает пользователь боту. Значения ключей — сам ответ бота и новая клавиатура. При этом множество ключей и кнопок должны полностью совпадать. Иначе может появиться ситуациция, когда бот не знает что ответить. В таких случаях, конечно, можно добавить ответ по умолчанию.

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][/text]

Теперь создадим обработчик:

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][/text]["answer"],
            "_markup" -> answer[text][/text]["keyboard"]];
    ]

И выполним повторно развертывание облачного объекта:

deployWebhook[bot, handlerAbout[bot]];

Приверим, что получилось в чате с ботом. Но для начала очистим историю сообщений:

Расширение функциональности

Пока что принципиальных отличий от огромного множества уже существующих ботов нет. Может и смысла в его напиании тоже нет? Смысл всей проделанной выше работы будет, если понять в чем собственно преимущества такого бота! Ведь он может использовать все возможности Wolfram Language и Wolrfam Cloud. Необходимо, чтобы робот умел решать уравнения? Это очень легко! Надо всего-лишь доопределить ответ!

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

Если у кого-то дополнительно появится интерес к возможностям облака — то хорошее описание его функциональности есть здесь.

Ограничения

Wolfram Cloud — платформа, которая позволяет использовать язык Wolfram бесплатно, в то время как основной продукт компании Wolfram Research — Mathematica стоит денег. Соответственно на использование есть ограничения и на мой взгяд они очень сильные. При использовании бесплатной версии Development Platform пользователю в месяц выдается 1000 облачных кредитов. Каждый облачный кредит дает время на вычисление различного типа. Так как в статье говоиртся про CloudDeploy + APIFunction — то такие объекты, хранящиеся в облаке тратят 1 кредит за 0.1 секунды вычислительного времени. Несложно подсчитать, что пользователю бесплатно выдается всего 1 минута и 40 секунд серверного времени на работу своего приложения (в данном случае бота). Мне здесь нечего добавить — это очень и очень мало. Основной упор на пользователей, которые работают в Development Platform самостоятельно с помощью браузера. Ведь в таком режиме нет никаких ограничений по времени, а только по длительности сессии и выделяемым ресурсам. При таком использовании Development Platform — это почти полноценная Mathematica, но не требующая установки и лицензии.

→ Статья в Wolfram Cloud

Tags:

Похожие публикации

Source: habr1

Ну и напоследок напоминаю вам о нашем новом проекте vkserfing bot.