Пишем Telegram бота на Ruby для уведомлений в канале

Ruby wrapper for Telegram’s Bot API.

Add following line to your Gemfile:

gem '
telegram-bot-ruby'

And then execute:

$ bundle

Or install it system-wide:

$ gem install telegram-bot-ruby

First things first, you need to obtain a token for your bot. Then create your Telegram bot like this:

require
'
telegram/bot'

  token =
'
YOUR_TELEGRAM_BOT_API_TOKEN'

Telegram
::Bot
::Client
.run(token) do
 |bot
|   bot.listen do
 |message
|     case
 message.text     when
'
/start'

       bot.api.send_message(chat_id:
 message.chat.id, text:
"
Hello, #{
message.from.first_name
}
"

)     when
'
/stop'

       bot.api.send_message(chat_id:
 message.chat.id, text:
"
Bye, #{
message.from.first_name
}
"

)     end
end
end

Note that bot.api
object implements Telegram Bot API methods as is. So you can invoke any method inside the block without any problems. All methods are available in both snake_case
and camelCase
notations.

Same thing about message
object — it implements Message spec, so you always know what to expect from it.

If you are going to use webhooks instead of long polling, you need to implement your own webhook callbacks server. Take a look at this repo as an example.

As some countries block access to Telegram, you can set up your own proxy and use it to access Telegram API. In this case you need to configure API url:

Telegram
::Bot
::Client
.run(token, url:
'
https://proxy.example.com'

) do
 |bot
|   #
 ...
end

You can use your own custom keyboards. Here is an example:

bot.listen do
 |message
|   case
 message.text   when
'
/start'

     question =
'
London is a capital of which country?'

#
 See more: https://core.telegram.org/bots/api#replykeyboardmarkup
     answers =
Telegram
::Bot
::Types
::ReplyKeyboardMarkup
       .new
(keyboard:
 [%w(A B)
, %w(C D)
], one_time_keyboard:
true
)     bot.api.send_message(chat_id:
 message.chat.id, text:
 question, reply_markup:
 answers)   when
'
/stop'

#
 See more: https://core.telegram.org/bots/api#replykeyboardremove
     kb =
Telegram
::Bot
::Types
::ReplyKeyboardRemove
.new
(remove_keyboard:
true
)     bot.api.send_message(chat_id:
 message.chat.id, text:
'
Sorry to see you go :('

, reply_markup:
 kb)   end
end

Furthermore, you can ask user to share location or phone number using KeyboardButton
:

bot.listen do
 |message
|   kb =
 [     Telegram
::Bot
::Types
::KeyboardButton
.new
(text:
'
Give me your phone number'

, request_contact:
true
),     Telegram
::Bot
::Types
::KeyboardButton
.new
(text:
'
Show me your location'

, request_location:
true
)   ]   markup =
Telegram
::Bot
::Types
::ReplyKeyboardMarkup
.new
(keyboard:
 kb)   bot.api.send_message(chat_id:
 message.chat.id, text:
'
Hey!'

, reply_markup:
 markup) end

Bot API 2.0 brought us new inline keyboards. Example:

bot.listen do
 |message
|   case
 message   when
Telegram
::Bot
::Types
::CallbackQuery
#
 Here you can handle your callbacks from inline buttons
if
 message.data ==
'
touch'

       bot.api.send_message(chat_id:
 message.from.id, text:
"
Don't touch me!"

)     end
when
Telegram
::Bot
::Types
::Message
     kb =
 [       Telegram
::Bot
::Types
::InlineKeyboardButton
.new
(text:
'
Go to Google'

, url:
'
https://google.com'

),       Telegram
::Bot
::Types
::InlineKeyboardButton
.new
(text:
'
Touch me'

, callback_data:
'
touch'

),       Telegram
::Bot
::Types
::InlineKeyboardButton
.new
(text:
'
Switch to inline'

, switch_inline_query:
'
some text'

)     ]     markup =
Telegram
::Bot
::Types
::InlineKeyboardMarkup
.new
(inline_keyboard:
 kb)     bot.api.send_message(chat_id:
 message.chat.id, text:
'
Make a choice'

, reply_markup:
 markup)   end
end

If you are going to create inline bot, check the example below:

bot.listen do
 |message
|   case
 message   when
Telegram
::Bot
::Types
::InlineQuery
     results =
 [       [1
, '
First article'

, '
Very interesting text goes here.'

],       [2
, '
Second article'

, '
Another interesting text here.'

]     ].map do
 |arr
|       Telegram
::Bot
::Types
::InlineQueryResultArticle
.new
(         id:
 arr[],         title:
 arr[1
],         input_message_content:
Telegram
::Bot
::Types
::InputTextMessageContent
.new
(message_text:
 arr[2
])       )     end
      bot.api.answer_inline_query(inline_query_id:
 message.id, results:
 results)   when
Telegram
::Bot
::Types
::Message
     bot.api.send_message(chat_id:
 message.chat.id, text:
"
Hello, #{
message.from.first_name
}
!"

)   end
end

Now, with inline
mode enabled, your message
object can be an instance of Message, InlineQuery or ChosenInlineResult. That’s why you need to check type of each message and decide how to handle it.

Using answer_inline_query
you can send query results to user. results
field must be an array of query result objects.

Your bot can even upload files (photos, audio, documents, stickers, video) to Telegram servers. Just like this:

bot.listen do
 |message
|   case
 message.text   when
'
/photo'

     bot.api.send_photo(chat_id:
 message.chat.id, photo:
Faraday
::UploadIO
.new
('
~/Desktop/jennifer.jpg'

, '
image/jpeg'

))   end
end

By default, bot doesn’t log anything (uses NullLoger
). You can change this behavior and provide your own logger class. See example below:

Telegram
::Bot
::Client
.run(token, logger:
Logger
.new
($stderr
)) do
 |bot
|   bot.logger.info('
Bot has been started'

)   bot.listen do
 |message
|     #
 ...
end
end

Since version 0.5.0
we rely on faraday under the hood. You can use any of supported adapters (for example, net/http/persistent
):

require
'
net/http/persistent'

Telegram
::Bot
.configure do
 |config
|   config.adapter =
:net_http_persistent
end

If you don’t know how to setup database for your bot or how to use it with different languages here are some boilerplates which can help you to start faster:

  1. Fork it
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am ‘Add some feature’)
  4. Push to the branch (git push origin my-new-feature)
  5. Create new Pull Request
После выпуска Telegram Bot Platform многие задумывались о написании своего бота. Этот пост описывает минимальные шаги, необходимые для написания собственного бота на Ruby. Для этого потребуется только аккаунт в Telegram и машина с установленным Ruby на ней. Я выбрал Ruby из за удобного гема для работы с Telegram bot api. Первое, что нужно сделать, это создать .rb файл, в котором будет храниться логика бота, к примеру, start_bot.rb, и добавить туда минимальный код, необходимый для работы бота:
require 'telegram/bot' token = 'YOUR_TELEGRAM_BOT_API_TOKEN' Telegram::Bot::Client.run(token) do |bot|   bot.listen do |message|     case message.text     when '/start'       bot.api.sendMessage(chat_id: message.chat.id, text: "Hello, #{message.from.first_name}")     end   end end 

Для работы сервера не хватает только установки гема. Установку можно совершить двумя способами: 1. Установка гема непосредственно на машину:

gem install telegram-bot-ruby 

2. Используя Gemfile

gem 'telegram-bot-ruby' 

с последующим выполнением

bundle 

Я для простоты примера использовал первый способ. Теперь необходимо получить токен для бота. Заходим в Telegram, добавляем бота @BotFather, и создаем бота: Далее добавляем токен в файл и получаем готовый сервер для бота:

require 'telegram/bot' token = '118997426:AAFVFtYa15Z7ckyDUIHb578NazgepL4kmU8' Telegram::Bot::Client.run(token) do |bot|   bot.listen do |message|     case message.text     when '/start'       bot.api.sendMessage(chat_id: message.chat.id, text: "Hello, #{message.from.first_name}")     end   end end 

Для проверки работы бота в начале запускаем сервер:

ruby start_bot.rb 

А после пишем боту в Telegram: Как видно, все работает. PS: Бота после я удалил, поэтому мой токен, как и бот, недоступны. UPD:

3 строки на Ruby
require 'telegram/bot' token = '' Telegram::Bot::Client.run(token) { |bot| bot.listen { |message| bot.api.sendMessage(chat_id: message.chat.id, text: 'Hi!') if message.text == '/start' } } 

За Python спасибо

3 строки на Python
from twx.botapi import TelegramBot token = '' while True: [lambda update: TelegramBot(token).send_message(update.message.chat.id, 'test message body') if update.message.text.startswith('/start') else None for update in TelegramBot(token).get_updates().wait()] 
154

46,3k


154

РЕСУРСЫ, переменные И МЕТОДЫ
  • https://core.telegram.org/bots/api – основной необходимый MAN. Очень крутой, но не хватает примеров
  • https://github.com/atipugin/telegram-bot-ruby – ruby gem telegram, есть пример кода для разных кейсов
  • https://github.com/mustafababil/Telegram-Weather-Bot/blob/master/responseController.py – пример бота в телеграм на python
METHOD  bot.api.send_message - основной метод, отправка сообщений  bot.api.answer_callback_query- ответ на CallBackQuery, отвечать нужно после обработки сообщения, чтобы ошибки ответа на callback не привели к проблемам с самим ответом  bot.api.deleteMessage - удаление сообщений (своих, исходящих от бота)    ID  rqst.from.id - user info id. Если ботом отвечать на него, то даже если бот добавлен в группу ответ пользователю будет в личный чат, а не в групповой.  rqst.chat.id - id чата из которого сделан запрос (с пользователем, групповой). Если ботом отвечать на него, то если бот добавлен в группу ответ пользователю будет в групповой чат. rqst.message.message_id - id сообщения внутри чата rqst.message.id - id сообщения глобальный  TEXT  rqst.text - текст сообщения в случае типа Telegram::Bot::Types  rqst.data - текст "за кнопкой" InlineKeyBoardButton в случае типа Telegram::Bot::Types  rqst.message.text - текст сообщения, на которое мы отвечаем, в случае типа Telegram::Bot::Types::CallbackQuery    Разное  rqst.from.first_name - имя  rqst.from.last_name - фамилия  rqst.chat.type - тип сообщения в чате
ЗАПУСКАЕМ Простой БОТ
1) BotFather – создаем bot, получаем token
Done! Congratulations on your new bot. You will find it at t.me/xxxxxx_Bot. You can now add a description, about section and profile picture for your bot, see /help for a list of commands. By the way, when you've finished creating your cool bot, ping our Bot Support if you want a better username for it. Just make sure the bot is fully operational before you do this.    Use this token to access the HTTP API:  xxxxxxx
    For a description of the Bot API, see this page: https://core.telegram.org/bots/api
2) Ставим gem telegram-bot-ruby
sudo gem install telegram-bot-ruby
3) Проверяем работу токена (код из Usage)
require 'telegram/bot'    token = 'YOUR_TELEGRAM_BOT_API_TOKEN
'    Telegram::Bot::Client.run(token) do |bot|    bot.listen do |message|    case message.text    when '/start'    bot.api.send_message(chat_id: message.chat.id, text: "Hello, #{message.from.first_name}")    when '/stop'    bot.api.send_message(chat_id: message.chat.id, text: "Bye, #{message.from.first_name}")    end    end  end
Добавляем плюшки

THREAD

Бот надо делать многопоточным, по умолчанию это не так – запросы разных пользователей в одной очереди и один сложный запрос может всю очередь на минуту затормозить. Для фикса заворачиваем обработку в Thread. Чтобы не путаться с конструкциями типа “.message.message”, message выше заменено на rqst (в telegram есть тип сообщения message).

Telegram::Bot::Client.run(token) do |bot|    bot.listen do |rqst|      Thread.start(rqst) do |rqst|              end    end  end
RESCUE
Необходимо завернуть основной код бота в конструкцию loop-do и begin-rescue. Пример уже с многопоточностью.
loop do    begin      Telegram::Bot::Client.run(token) do |bot|        bot.listen do |rqst|          Thread.start(rqst) do |rqst|            begin                          rescue                          end          end        end      end    rescue          end  end
Это самое важное. Без него бот будет падать как из-за ошибок при обработке ваших запросов (в том числе это может быть и нормой – например при работе с callback или message edit, об этом ниже), так и из-за работ на платформе telegram, которые проводятся регулярно.
CALLBACK  
2017-10-02 11:01:45 +0300;out: Telegram API has returned the error. (ok: false, error_code: 400, description: Bad Request: QUERY_ID_INVALID);mess: /var/lib/gems/1.9.1/gems/telegram-bot-ruby-0.8.3/lib/telegram/bot/api.rb:74:in call   /var/lib/gems/1.9.1/gems/telegram-bot-ruby-0.8.3/lib/telegram/bot/api.rb:58:in method_missing   /home/redkin_p/bin/telegram_bot.rb:72:in block (4 levels) in   /strong>
  2017-10-02 06:39:23 +0300;out: Telegram API has returned the error. (error_code: 502, uri: https://api.telegram.org/botxxxxxxx/getUpdates);mess: /var/lib/gems/1.9.1/gems/telegram-bot-ruby-0.8.3/lib/telegram/bot/api.rb:74:in call
/var/lib/gems/1.9.1/gems/telegram-bot-ruby-0.8.3/lib/telegram/bot/api.rb:58:in method_missing
/var/lib/gems/1.9.1/gems/telegram-bot-ruby-0.8.3/lib/telegram/bot/client.rb:30:in fetch_updates
/var/lib/gems/1.9.1/gems/telegram-bot-ruby-0.8.3/lib/telegram/bot/client.rb:25:in listen
/var/lib/gems/1.9.1/gems/telegram-bot-ruby-0.8.3/lib/telegram/bot/client.rb:18:in run
/var/lib/gems/1.9.1/gems/telegram-bot-ruby-0.8.3/lib/telegram/bot/client.rb:8:in run
/home/redkin_p/bin/telegram_bot.rb:12:in loop
/home/redkin_p/bin/telegram_bot.rb:12:in ;

Отправка файлов

Чтобы расшарить файл скидываем в чате с ботом необходимый файл боту и узнаем ботом id файла – в ответе от бота получаем id (типо BQ1DAsSDF@14FSFDSFFDAJ-3diLAg). Осторожно применение метода .file_id не на тот тип данных приведет к ошибке: фотографии windows desktop приложение отсылает как document, а не фото, а в мобильном приложении по умолчанию используется тип именно photo.

Узнаем ID файла    fid = rqst.document.file_id    bot.api.send_message(chat_id: rqst.chat.id, text: "#{fid}")

Отсылка файла требует id пользователя и file_id сообщения, остальные параметры опциональные.

Отсылаем файл   bot.api.send_document(chat_id: rqst.from.id, document: "BQ1DAsSDF@14FSFDSFFDAJ-3diLAg")

Простой пример – отсылаем на основе запросов в inline keyboard файлы.

Создаем кнопки по вызову файлов.
   kb = [   Telegram::Bot::Types::InlineKeyboardButton.new(text: 'some-text-test1', callback_data: "test1"),   Telegram::Bot::Types::InlineKeyboardButton.new(text: 'some-text-test2', callback_data: "test2"),   Telegram::Bot::Types::InlineKeyboardButton.new(text: 'some-text-test3', callback_data: "test3")   ]   markup_retry = Telegram::Bot::Types::InlineKeyboardMarkup.new(inline_keyboard: kb)     Делаем соответствующие кнопкам ответы со ссылками на файлы (вместо send_message используем send_document).
  if rqst.data == "test1"    bot.api.send_document(chat_id: rqst.from.id, document: "BQ1DAsSDF@14FSFDSFFDAJ-3diLAg1")   elsif rqst.data == "test2"    bot.api.send_document(chat_id: rqst.from.id, document: "BQ1DAsSDF@14FSFDSFFDAJ-3diLAg2")   elsif rqst.data == "test3"    bot.api.send_document(chat_id: rqst.from.id, document: "BQ1DAsSDF@14FSFDSFFDAJ-3diLAg3")   else   "тут что то еще"  end    Не забываем отвечать на Callback. Отвечать нужно после обработки сообщения, чтобы ошибки ответа на callback не привели к проблемам с самим ответом.
   bot.api.answer_callback_query(callback_query_id: rqst.id)
Запрос phone_number

Авторизация по номеру очень клевая штука. Тут есть особенность – нужно проверять чтобы ID пользователя совпадал с ID контакта, потому что свой номер передается как контакт и легко передать контакт “левого”, авторизованного, человека.

Для начала запрашиваем контакт (например, если чела с таким ID telegram еще нет в нашей телеграм-базе) через кастом-клавиатуру с одной кнопкой (https://github.com/atipugin/telegram-bot-ruby#Usage раздел Custom keyboards):

kb = Telegram::Bot::Types::KeyboardButton.new(text: 'Отправить номер', request_contact: true)  markup = Telegram::Bot::Types::ReplyKeyboardMarkup.new(keyboard: kb)  bot.api.send_message(chat_id: rqst.chat.id, text: 'Для работы с ботом вам нужно пройти идентификацию посредством отправки вашего номера телефона.', reply_markup: markup)

Далее при приеме контакта (например, через проверку rqst.contact != nil) проверяем что пользователь отправивший контакт имеет такой же ID как указанный в полученном контакте, если это не так – ругаемся.

bot.api.send_message(chat_id: rqst.chat.id, text: 'Нет, так не получится.') if rqst.from.id != rqst.contact.user_id

Потом проверяем уже наличие номера в своих системах. Если есть – добавляем чела с таким ID telegram в нашу телеграм-базу. Если нет – просим не беспокоить. В любом случае удаляем кастом клавиатуру с запросом номера.

number = rqst.contact.phone_number.sub(/+/,"")  check = check_number(number)  if check == "ok"    add_to_telega(number,rqst.contact.user_id)    text = "Спасибо, идентификация пройдена."  else    text = "Вас нет в базе данных."  end  kb = Telegram::Bot::Types::ReplyKeyboardRemove.new(remove_keyboard: true)  bot.api.send_message(chat_id: rqst.chat.id, text: "#{text}", reply_markup: kb)
Inline keyboards

Пример простого статического InlineKeyBoard.

kb = [   Telegram::Bot::Types::InlineKeyboardButton.new(text: 'some-text-test1', callback_data: "test1"),   Telegram::Bot::Types::InlineKeyboardButton.new(text: 'some-text-test2', callback_data: "test2"),   Telegram::Bot::Types::InlineKeyboardButton.new(text: 'some-text-test3', callback_data: "test3")   ]   markup_retry = Telegram::Bot::Types::InlineKeyboardMarkup.new(inline_keyboard: kb)

Пример статического InlineKB с кнопками на разных рядах.

kb =   [     [     Telegram::Bot::Types::InlineKeyboardButton.new(text: 'Text1', callback_data: "1"),     Telegram::Bot::Types::InlineKeyboardButton.new(text: 'Text2', callback_data: "2"),     ],     [     Telegram::Bot::Types::InlineKeyboardButton.new(text: 'Text3', callback_data: "3"),     Telegram::Bot::Types::InlineKeyboardButton.new(text: 'Text4', callback_data: "4"),     ],   Telegram::Bot::Types::InlineKeyboardButton.new(text: 'Text5', callback_data: "5")   ]  markup_retry = Telegram::Bot::Types::InlineKeyboardMarkup.new(inline_keyboard: kb)

Далее отсылаем ответ с созданной клавой.

bot.api.send_message(chat_id: user_id, text: "#{res}", reply_markup: markup_retry)  bot.api.answer_callback_query(callback_query_id: rqst.id)
Inline keyboards on-the-fly

Очень крутая штука, с помощью нее можно создавать многоуровневые меню, я так сделал трехуровневое меню с кнопками туда-сюда. Очень удобно.

Inline keyboards and on-the-fly updating    There are times when you'd prefer to do things without sending any messages to the chat. For example, when your user is changing settings or flipping through search results. In such cases you can use Inline Keyboards that are integrated directly into the messages they belong to.    Unlike with custom reply keyboards, pressing buttons on inline keyboards doesn't result in messages sent to the chat. Instead, inline keyboards support buttons that work behind the scenes: callback buttons, URL buttons and switch to inline buttons.

В интернете кто-то почему то думает, что чтобы поменять свое сообщение нужно сохранить результат операции отправки (в виде ID) в файле. По факту это не нужно. При получении CallBackQuery можно извлечь ID и даже текст сообщения, на которое пришел ответ.

last_bot_message_id_to_user = rqst.message.message_id  last_bot_message_text_to_user = rqst.message.text

В целом для ответа нужен только id и новая клавиатура. Если клавиатура будет такая же как раньше – telegram ругнется (тут нам поможет exception выше) и менять ничего не будет.

bot.api.editMessageReplyMarkup(chat_id: rqst.from.id, message_id: last_bot_message_id_to_user, reply_markup: markup_retry)
Emoji
Отсылаются просто по unicode коду внутри сообщения. Unicode можно взять отсюда. Буква u обязательно должна быть маленькая. Например, используем в инлайн-кнопках.
hammer = "u{1F528}"  Telegram::Bot::Types::InlineKeyboardButton.new(text: "#{hammer} Сделать то-то", callback_data: "test_operations")
Разметка
Отправка форматированных сообщений. Можно размечать через Markdown, можно через HTML. HTML базовый, без каких либо таблиц и прочего. В одном сообщении не должно быть больше 100 URL ссылок (последующие обрезаются) вне зависимости от типа форматирования.
bot.api.send_message(chat_id: "#{id}", text: "[test](https://t.me/TESTBot?start=command-TEST)", parse_mode: "Markdown")    bot.api.send_message(chat_id: "#{id}", text: "inline URL", parse_mode: "HTML")

Markdown симпатичнее HTML, но на практике очень капризный – из-за него могут сыпаться ошибки и не доставляться сообщения пользователю если markdown увидит в сообщении спец. символы типа _, причем даже не в самой ссылке, что можно было бы “понять и простить”, а дальше в тексте.

bot.api.send_message(chat_id: "#{id}", text: "[Test](https://t.me/TestBot?start) My_
Test", parse_mode: "Markdown")    Telegram API has returned the error. (ok: "false", error_code: "400", description: "Bad Request: can't parse entities: Can't find end of the entity starting at byte offset 37") (Telegram::Bot::Exceptions::ResponseError)
Редирект на свой же бот, для исполнения команды  можно сделать двумя способами:
1) Используя встроенные команды, поставив перед числом или неразрывной латиницей знак
start
2) Используя Deep Linking (1,2,3). Единственный найденный способ для редиректа IP. Недостаток – пользователю придется нажать кнопку Start (не во всех реализациях клиента), даже если он уже общался с ботом и запрос от пользователя в чате для самого пользователя будет выглядеть как /start, хотя по факту за ним будут скрываться какие-то данные. В случае не первого общения с ботом выглядит немного странно, но работает. В целом, Deep Linking нужен именно для старта общения – авторизации, связи между ботом и другими системами, реферальных ссылок и прочего через передачу ID при старте общения с ботом.
bot.api.send_message(chat_id: "#{id}", text: "[test](https://t.me/TESTBot?start=command-TEST)", parse_mode: "Markdown")

УВЕДОМЛЕНИЯ

По умолчанию бот отправляет сообщения только в ответ на сообщение пользователя. Чтобы отправлять уведомления нужно вместо Listen использовать New. Можно отправлять в ответ и отправлять уведомления одновременно.

# initialize bot client and store it in constant  # that can be done in any file which is loaded at your application's boot process  BOT = Telegram::Bot::Client.new(TOKEN)    # anywhere in another file  BOT.send_message(chat_id: id, text: "hello world")
КОМАНДЫ

Как установить команды которые видит клиент в боте:

  1. Открыть чат с BotFather
  2. Отправить ему команду /setcommands
  3. Выбрать бота для установки
  4. enjoy

РАБОТА ЧЕРЕЗ PROXY

Gem использует Faraday для подключения. В Faraday встроена возможность работать через HTTP proxy – он читает глобальные переменные http_proxy, ftp_proxy и берет из них адреса серверов. Можно так же задать напрямую в коде.

Faraday will try to automatically infer the proxy settings from your system using URI#find_proxy. This will retrieve them from environment variables such as http_proxy, ftp_proxy, no_proxy, etc. If for any reason you want to disable this behaviour, you can do so by setting the global varibale ignore_env_proxy:
Faraday.ignore_env_proxy = true
You can also specify a custom proxy when initializing the connection
Faraday.new('http://www.example.com', :proxy => 'http://proxy.com')
export http_proxy=http://your.proxy.server:port/ # включаем  unset http_proxy # отключаем
Рейтинг автора
5
Подборку подготовил
Андрей Ульянов
Наш эксперт
Написано статей
168
Ссылка на основную публикацию
Похожие публикации