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

29 Ноября, 2016 19 мин чтения

Вступление

Заикнулся в присутствии нового коллеги о своем блоге, первый его вопрос был “что за блог?”, а второй “а телеграм канал у тебя есть?”. Поймал себя на мысли неужели я стал ретроградом. Когда начался хайп вокруг влогов и Youtube каналов я остался верен теплому ламповому формату текстовых статей, так и сейчас, считаю что нет необходимости иметь свой Telegram канал, но задача меня заинтересовала. Вокруг Telegram ботов сейчас много шумихи, как завести своего бота или канал написано в официальной документации. Не хотелось делать что-то надуманное, только ради “попробовать” и я решил что более менее полезной задачей будет уведомление о новых статьях в этом же блоге и только потом понял, что в принципе, мою реализацию можно использовать для любой RSS ленты c небольшими правками под себя.

Наивная реализация или тупой бот

Первое что нужно сделать это создать бота и получить API токен.

Недолго думая, я взял самую популярную либу-обертку для ботов Telegram – gem telegram-bot-ruby и наваял следующий код:

require 'rss' require 'telegram/bot'  token = ENV['TELEGRAM_BOT_API_KEY']  rss = RSS::Parser.parse('https://doam.ru/feed.xml', false)  Telegram::Bot::Client.run(token) do |bot|   rss.items.each do |item|     bot.api.sendMessage(chat_id: "@doam_ru", text: item.link.href)   end end

Что же тут происходит? Все очень просто, вот здесь:

require 'rss' require 'telegram/bot'

подключаем библиотеки для работы с RSS и Telegram, а вот здесь:

token = ENV['TELEGRAM_BOT_API_KEY']

сетим в переменную с именем token наш API токен, который получили от @BotFather.

Здесь я использую переменные окружения (Environment variables), чтобы это работало, перед запуском скрипта нужно выполнить в командной строке export TELEGRAM_BOT_API_KEY=123456789, где вместо 123456789 нужно вставить собственно токен. Использование переменных окружения один из двенадцати факторов приложения согласно – Adam Wiggins

Затем, с помощью строчки:

rss = RSS::Parser.parse('https://doam.ru/feed.xml', false)

сохраняем в объект rss всю RSS ленту моего блога (я точно знаю что у меня там только 10 записей, поэтому не боюсь никаких переполнений или задержек).

Telegram::Bot::Client.run(token) do |bot|   rss.items.each do |item|     bot.api.sendMessage(chat_id: "@doam_ru", text: item.link.href)   end end

После этого мы создаем бот клиента, обходим каждый item внутри rss и отправляем его ссылку в канал Telegram. Таким образом, при каждом запуске скрипта в канал будет отправляться 10 сообщений с одинаковыми ссылками. Я использую бота, хотя для такого же функционала, например, в Slack я бы использовал Incoming Hooks.

Кстати, чтобы бот мог слать сообщения в канал, его нужно добавить в администраторы этого канала.

На этом этапе я понял что нужно хранить состояние постов, т.е. запоминать информацию какие записи уже отправлены в канал, а какие еще нет.

Используем простую БД или чуть более умный бот

На самом деле новая версия бота растянулась на 55 строк кода, и вот он целиком:

require 'telegram/bot' require 'rss' require 'sdbm' require 'json' require 'logger'  logger = Logger.new(STDOUT)  if ENV['TELEGRAM_BOT_API_KEY'].nil?   logger.fatal "Environment variable TELEGRAM_BOT_API_KEY not set!"   exit 0 else   token = ENV['TELEGRAM_BOT_API_KEY'] end  rss = RSS::Parser.parse('https://doam.ru/feed.xml', false)  SDBM.open 'doam_posts.db' do |posts|   rss.items.each do |item|     key       = item.link.href     title     = item.title.content     published = item.published.content     # next if posts[key]     if posts.has_key?(key)       logger.info "Post exist in DB will not rewrite"     else       posts[key] = JSON.dump(         title: title,         published: published,         sended: 0       )     end   end    hash = {}   posts.each do |k,v|     hash[k] = JSON.parse(v)     if hash[k]["sended"] == 0       text = "Новая запись в блоге: #{hash[k]["title"]} - #{k}"       Telegram::Bot::Client.run(token) do |bot|         if bot.api.sendMessage(chat_id: "@doam_ru", text: text)           posts[k] = JSON.dump(             title: hash[k]["title"],             published: hash[k]["published"],             sended: 1           )           logger.info "Successfuly send #{hash[k]} to telegram!"         else           logger.error "Can not send #{hash[k]} to telegram!"         end       end     end   end end

Опять же приведу разбор этого кода по кусочкам далее по тексту.

require 'telegram/bot' require 'rss' require 'sdbm' require 'json' require 'logger'

Я использую теже две библиотеки что и раньше rss и telegram, затем подключаю sdbm это встроенная либа Ruby, которая предоставляет простое хранилище типа ключ-значение (key-value), в качестве ключей или значений могут выступать только строки. Далее я подключаю json, чтобы легко кодировать объекты в строки и декодировать обратно. И еще одной библиотекой является logger, который предоставляет простой способ для отладки.

logger = Logger.new(STDOUT)  if ENV['TELEGRAM_BOT_API_KEY'].nil?   logger.fatal "Environment variable TELEGRAM_BOT_API_KEY not set!"   exit 1 else   token = ENV['TELEGRAM_BOT_API_KEY'] end  rss = RSS::Parser.parse('https://doam.ru/feed.xml', false)

Сперва делаем простые вещи, создаем объект для логирования и сетим переменную окружения.

Устанавливаем переменную окружения в этот раз мы чуть сложнее. Вначале мы проверяем что ENV пустая и если это так то выбрасываем ошибку через логгер и выходим из программы c использованием exit кода 1, в обратном случае, если переменная не пустая, записываем её значение в переменную, которую мы будем использовать в дальнейшем.

Парсинг RSS ленты происходит таким же образом как и ранее.

SDBM.open 'doam_posts.db' do |posts|

Далее, этой строчкой мы открываем нашу базу данных, которая является просто файлами в нашей файловой системе, в случае если базы не существует она будет создана с нуля.

  rss.items.each do |item|     key       = item.link.href     title     = item.title.content     published = item.published.content     # next if posts[key]     if posts.has_key?(key)       logger.info "Post exist in DB will not rewrite"     else       posts[key] = JSON.dump(         title: title,         published: published,         sended: 0       )     end   end

И после этого мы начинаем первый цикл: обходим все полученные rss итемы, записываем ссылки, заголовок и дату публикации соответственно в переменные key, title и published. Проверяем есть ли в нашей базе ключ с таким же значением как наш и если есть просто выводим в лог текст, что не будем ничего перезаписывать (это нужно только на этапе отладки, но вообще не обязательно), в обратном случае, если в базе нет записи с таким ключом мы генерируем json строку и записываем её в базу. В качестве ключа я выбрал URL, так как они обеспечивают уникальность, всегда написаны в одном регистре и латиницей.

  hash = {}   posts.each do |k,v|   hash[k] = JSON.parse(v)   if hash[k]["sended"] == 0     text = "Новая запись в блоге: #{hash[k]["title"]} - #{k}"     Telegram::Bot::Client.run(token) do |bot|       if bot.api.sendMessage(chat_id: "@doam_ru", text: text)         posts[k] = JSON.dump(           title: hash[k]["title"],           published: hash[k]["published"],           sended: 1         )         logger.info "Successfuly send #{hash[k]} to telegram!"       else         logger.error "Can not send #{hash[k]} to telegram!"       end     end   end end

Следующим шагом я создаю объект типа Hash и прохожусь по всем записям которые есть в базе. С помощью hash[k] = JSON.parse(v) я делаю парсинг строки значения и создаю вложенные хеши. Затем проверяю значение поля sended, если там 0 то генерирую текст и отправляю его в канал, после чего перезаписываю объект cо значением 1, чтобы не отправить эту же запись при следующем запуске.

На самом деле это не совсем рабочая версия кода, я почему-то не закоммитил тот момент когда довел работу с SDBM до ума. Можете попробовать сами понять что здесь не так.

Теперь уже я решил попробовать задеплоить мой код на Heroku и выполнить его там. С использоавнием раннера задач все получилось, да только вот Heroku не сохраняет файлы между запусками задачи (да и вообще). Так что мое решение с SDBM является быстрым и простым, но может быть использовано только как selfhosted.

Прикручиваем РСУБД или умеренно сообразительный бот

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

require 'telegram/bot' require 'rss' require 'sdbm' require 'json' require 'logger' require 'pg'  class DoamTelegramBot    def initialize(url, channel)     @logger   = Logger.new(STDOUT)      if ENV['TELEGRAM_BOT_API_KEY'].nil?       @logger.fatal "Environment variable TELEGRAM_BOT_API_KEY not set!"       exit 0     else       @token = ENV['TELEGRAM_BOT_API_KEY']     end      @url      = url     @channel  = channel     uri       = URI.parse(ENV['DATABASE_URL'])     @db       = PG.connect(uri.hostname, uri.port, nil, nil, uri.path[1..-1], uri.user, uri.password)     @rss      = RSS::Parser.parse(url, false)      @db.exec("CREATE TABLE IF NOT EXISTS posts (id serial, url varchar(450) NOT NULL, sended bool DEFAULT false)")   end    def sync     @rss.items.each do |item|       url = item.link.href       if @db.exec("SELECT exists (SELECT 1 FROM posts WHERE url = '#{url}' LIMIT 1)::int").values[0][0].to_i == 1         @logger.info "Post exist in DB will not rewrite"       else         if @db.exec("INSERT INTO posts (url) VALUES ('#{url}')")           @logger.info "Write post to DB #{url}"         end       end     end   end    def send     urls = @db.exec("SELECT url FROM posts WHERE sended = false")     urls.each do |url|       text = "Новая запись в блоге - #{url['url']}"       if telegram_send(text)         @db.exec("UPDATE posts SET sended = true WHERE url = '#{url['url']}'")       end     end   end    private    def telegram_send(message)     Telegram::Bot::Client.run(@token) do |bot|       if bot.api.sendMessage(chat_id: "#{@channel}", text: message)         @logger.info "Successfuly send #{message} to telegram!"         true       else         @logger.error "Can not send #{message} to telegram!"         false       end     end   end end

Здесь проделывается точно такая же работа: иницилизируются необходимые компоннеты, синхронизируется rss лента и локальная база данных и отправляются ссылки на записи которые числятся в базе как “неотправленные”. Только в качестве базы используется PostgreSQL и все это обернуто в класс и методы. Метод initialize выполняется при вызове метода new на объекте класса DoamTelegramBot, остальные методы нужно вызывать отдельно. И чтобы выполнить всё правильно, я создал каталог bin в который положил файл с именем doam_bot и содержимым:

#!/usr/bin/env ruby require_relative '../app'  telegram = DoamTelegramBot.new("https://doam.ru/feed.xml", "@doam_ru") telegram.sync telegram.send

В котором я указываю что для выполнения скрипта нужно использовать язык Ruby, подключаю созданный мной класс через файл app.rb, создаю объект telegram и передаю в него урл для парсинга и id канала, вызываю методы sync и send. После чего я заливаю код на Heroku и создаю периодическую задачу, например раз в сутки. И если за сутки появились новые записи то в мой канал придет уведомление, уже без моего содействия в автоматическом режиме.

Заключение

Полностью это маленькое приложение можно посмотреть у меня в gitlab (для этого нужно залогиниться в gitlab.com). Лицензия пока не указана, но она MIT, т.е. можете править и использовать в своих целях. Если будет время я хотел бы прикрутить еще несколько функций, например отправку не только в телеграм но и другие сервисы, а также решить проблему первого запуска, когда база наполняется новыми записями с флагом “не отправлено” но неизвестно точно какие записи уже были отправлены в канал. В любом случае готов рассмотреть ваши Merge Requests.

#Ruby#Telegram#Heroku Ruby, Telegram, Heroku –> –>

1*lMnLf3rqbKXvDfpKScf0jw.jpeg

Виталий Емельянцев

</span>

Jul 8, 2017·2 min read

</span>

Письма идут долго — до 7 минут. СМСки стоят по 2–5 руб. за штуку. Подключать CRM — иногда перебор. Выход? Бот на руби, отправляющий заявки в телеграм-чат менеджерам.

Как настроить? Читайте в статье.

UPD 25/08/2017: Поправил код в примерах.

Для создания нужно выбрать хотя бы одного участника кроме вас. Можете выбрать любого из знакомых, создать чат и удалить знакомого. Далее:

  1. Конвертируем чат в супергруппу;
  2. Добавляем бота @get_id_bot;
  3. Пишем в чат /my_id ;
  4. Читаем сообщение от бота, находим в нем ID чата, сохраняем ID;
  5. Удаляем бота.

Создаем через @botfather. Как — описано здесь .

Один такой бот может обслуживать много чатов с разных проектов. Имя и аватар выберите по вкусу, у меня бот такой:

Давать боту права администратора не нужно. Никаких дополнительных прав — тоже.

Суть такая:

  • На сервере живет нативный телеграм-клиент;
  • В руби проекте — тонкий руби гем;
  • Командуем в руби проекте гему отправить сообщение — он дает команду телеграм-клиенту, а тот — отправляет сообщение в чат через бота.

Инструкция по установке: https://github.com/atipugin/telegram-bot-ruby

Копируем туда же, где у вас лежат все credentials. В ENV-переменные, например. Я использую figaro.

Где брать значения ключей:

  • TELEGRAM_BOT_TOKEN – токен бота с шага 2 (получаем через @botfather);
  • TELEGRAM_CHAT_ID – ID чата с шага 1.

На самом деле вы уже можете отправлять сообщения командами на руби. Но чтобы делать удобной отправку заявок (с форматируемым текстом, DRY) — мы напишем пару классов. Пример учитывает, что у вас — Ruby on Rails, но не обязывает сидеть на нем. Если у вас не RoR — адаптируйте решение под ваш фреймворк.

Базовый класс. Обращается к телеграм-гему и просит отправить сообщение. Какое именно — за это отвечает метод render_message. Кладем файл в папку app/notifiers, и рельса подгружает его автоматически.

Пример нотификатора по заявкам с сайта (модель SiteRequest ). Наследуемся от TelegramNotifier. При создании передаем внутрь запись модели. В render_message — рендерим сообщение. В итоге в телеграм-чат придет подобное сообщение:

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

  • Tutorial

После выпуска 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()] 

Telegram может быть новым именем для вас, но он набирает популярность в последние несколько лет, в последних новостях Telegram сообщал о 100 000 000 активных пользователей в месяц. Так о чем это все?

Telegram — это облачное приложение для обмена сообщениями, которое хорошо известно благодаря множеству функций, и его безопасная передача сообщений является одной из ключевых функций. Он поддерживает различные платформы, такие как Windows, OSX, GNU / Linux, Android, iOS и Windows Phone. Стоит также упомянуть, что клиентская сторона Telegram имеет открытый исходный код, поэтому мы будем использовать любое другое приложение для обмена сообщениями, например Pidgin, для связи с Telegram.

Некоторые особенности Telegram:

  • шифрование
  • Супер группы (1000 участников или больше)
  • Передача файлов / фотографий
  • Открытая сторона клиента
  • Синхронизирует ваши сообщения на всех ваших устройствах
  • Ограничение общего размера файла 1,5 ГБ
  • Боты

О последнем поговорим конкретно. Telegram предлагает два API: API разработчика и API бота

Во-первых, нам нужно зарегистрировать аккаунт. Либо загрузите одного из клиентов, либо перейдите непосредственно к веб- версии Telegram. Оттуда вам будет предложено указать номер телефона, и Telegram отправит вам SMS с подтверждением.

Познакомьтесь с BotFather

Боты — это специальные учетные записи Telegram, предназначенные для автоматической обработки сообщений. Пользователи могут взаимодействовать с ботами, отправляя им команды в приватных или групповых чатах. Мы контролируем наших ботов, используя HTTPS-запросы к API ботов.

Что мы можем сделать с ботами?

  • Делайте заметки в группах Telegram
  • Получайте обновления погоды
  • Играть в игры
  • Взаимодействовать с другими сервисами, такими как социальные сети, IRC и т. Д.

Мы можем делать много вещей с ботами. Первый шаг в создании нашего бота — поговорить с BotFather .

Введите в форме поиска BotFather .

Нажмите на BotFather, и контейнер чата будет открыт, чтобы мы могли пообщаться с ним. Нажмите кнопку «Пуск», чтобы отобразить список команд, которые мы можем использовать для взаимодействия с BotFather.

Давайте создадим нашего бота с помощью команды /newbot Он запросит имя для нашего бота, поэтому мы будем называть нашего бота sitepbot .

Теперь мы должны зарегистрироваться для имени пользователя, обратите внимание: оно должно заканчиваться ботом , если наш бот называется TetrisBot, имя пользователя должно быть ботом tetris . Пусть это будет sitepointbot .

BotFather поздравит нас и даст ссылку на нашего бота telegram.me/sitepoint_bot .

Мы можем добавить описание, а также добавить фотографию профиля для нашего бота. Команда /help

/newbot - create a new bot /token - generate authorization token /revoke - revoke bot access token /setname - change a bot's name /setdescription - change bot description /setabouttext - change bot about info /setuserpic - change bot profile photo /setinline - change inline settings /setinlinefeedback - change inline feedback settings /setcommands - change bot commands list /setjoingroups - can your bot be added to groups? /setprivacy - what messages does your bot see in groups? /deletebot - delete a bot /cancel - cancel the current operation 

Используя одну из приведенных выше команд, мы можем внести различные изменения в нашего бота. Но сейчас наиболее важной командой является /token Идите вперед и введите это сейчас:

/token 

Возвращаемое значение будет выглядеть примерно так: 197372558:AAEtvechentOstoPmVyb1_aF2Dbe7k

После того, как мы создадим нашего бота и послушаем его, пришло время для Ruby. Есть несколько драгоценных камней для взаимодействия с API бота. Мы будем использовать telegram-bot-ruby .

Установка

Мы можем установить его прямо с вашего терминала:

gem install telegram-bot-ruby 

Или добавьте его в наш Gemfile :

gem 'telegram-bot-ruby' 

Если мы используем его с Gemfile, нам нужно ввести bundle install Это установит гем и необходимые ему зависимости.

Привет Пример SitePoint

Создайте файл с именем sitepointbot.rb , мы создадим содержимое этого файла здесь.

Во-первых, нам нужно импортировать наш драгоценный камень:

require 'telegram/bot' 

Далее нам нужно добавить наш токен, который мы сгенерировали ранее:

token = 'YOUR API TOKEN' 

Используя объект bot.api

Telegram::Bot::Client.run(token) do |bot|   bot.listen do |message|     case message.text     when '/sitepoint'       bot.api.send_message(chat_id: message.chat.id, text: "Welcome to http://sitepoint.com")     end   end end 

send_messagechat_idchat_id Конечно, text Всякий раз, когда бот видит /sitepoint

Примечание. Настоятельно рекомендуется использовать косую черту (/) перед нашими командами, мы не хотим, чтобы наши боты мешали нашим разговорам.

Полный код

require 'telegram/bot'  token = 'YOUR API TOKEN' Telegram::Bot::Client.run(token) do |bot|   bot.listen do |message|     case message.text     when '/sitepoint'       bot.api.send_message(chat_id: message.chat.id, text: "Welcome to http://sitepoint.com")     end   end end 

Теперь пришло время запустить наш бот:

ruby sitepointbot.rb 

Мы должны искать бота. Он появится в области результатов поиска.

Как видите, есть кнопка «Пуск». Если мы нажмем кнопку, команда /start Это должно всегда содержать приветственное сообщение от нашего бота, это лучшее место для добавления общей информации о боте, например, что он делает и какие команды он понимает.

Это эффект кнопки «Пуск» или команды /start Если вы хотите запустить бот внутри чата, вы также можете набрать

start@SitepBot 

что необходимо, если в чате больше одного бота.

Теперь давайте попробуем /sitepoint

Результатом является приветственное сообщение со ссылкой на сайт SitePoint.

Еще один пример

Давайте использовать метод sendLocation Этот метод сделает точку на карте. Мы будем использовать тот же объект bot.apisend_location

bot.api.send_location(chat_id: message.chat.id, latitude: -37.807416, longitude: 144.985339) 

Полный код

require 'telegram/bot'  token = 'YOUR API KEY' 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: "I am the SitePoint bot, My commands are /sitepoint /map")     when '/sitepoint'       bot.api.send_message(chat_id: message.chat.id, text: "Welcome to http://sitepoint.com")     when '/map'       bot.api.send_location(chat_id: message.chat.id, latitude: -37.807416, longitude: 144.985339)    end   end end 

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

Вывод

Telegram предлагает надежную и безопасную службу обмена сообщениями. Как и Slack, Telegram предлагает возможность создавать ботов, которые могут взаимодействовать с пользователями. Мы видели некоторые основные функции, которые могут делать боты, но мы можем сделать их умнее и функциональнее, взглянуть на API Telegram и проявить креативность. Если вы делаете что-то великое, поделитесь этим с нами

Используемые источники:

  • https://doam.ru/telegram_bot_for_push_messages_when_new_post_published/
  • https://medium.com/@gambala/ruby-telegram-a4223c654fef
  • https://habr.com/ru/post/264707/
  • https://coderlessons.com/articles/ruby/bystro-sozdat-bot-telegram-v-ruby

Оцените статью
Рейтинг автора
5
Материал подготовил
Илья Коршунов
Наш эксперт
Написано статей
134
Добавить комментарий