- 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 - тип сообщения в чате
ЗАПУСКАЕМ Простой БОТ
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
sudo gem install telegram-bot-ruby
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 Telegram::Bot::Client.run(token) do |bot| bot.listen do |rqst| Thread.start(rqst) do |rqst| begin <ОСНОВНОЙ КОД БОТА> rescue <RESCUE, можно сделать лог в файл типа "RESCUE PRCSNG"> end end end end rescue <RESCUE, можно сделать лог в файл типа "RESCUE API"> end end
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 <main> API 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 /home/redkin_p/bin/telegram_bot.rb:16:in block (2 levels) in <main> /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:14:in block in <main> /home/redkin_p/bin/telegram_bot.rb:12:in loop /home/redkin_p/bin/telegram_bot.rb:12:in <main>;
Отправка файлов
Чтобы расшарить файл скидываем в чате с ботом необходимый файл боту и узнаем ботом 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
hammer = "\u{1F528}" Telegram::Bot::Types::InlineKeyboardButton.new(text: "#{hammer} Сделать то-то", callback_data: "test_operations")
Разметка
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: "<a href=\"http://www.example.com/\">inline URL</a>", 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)
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")
КОМАНДЫ
Как установить команды которые видит клиент в боте:
- Открыть чат с BotFather
- Отправить ему команду /setcommands
- Выбрать бота для установки
- Составить лист команд
/help Помощь
/settings Настройки - 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 # отключаем