telegram_agent.rb 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. require 'httmultiparty'
  2. module Agents
  3. class TelegramAgent < Agent
  4. include FormConfigurable
  5. cannot_be_scheduled!
  6. cannot_create_events!
  7. no_bulk_receive!
  8. can_dry_run!
  9. description <<-MD
  10. The Telegram Agent receives and collects events and sends them via [Telegram](https://telegram.org/).
  11. It is assumed that events have either a `text`, `photo`, `audio`, `document` or `video` key. You can use the EventFormattingAgent if your event does not provide these keys.
  12. The value of `text` key is sent as a plain text message. You can also tell Telegram how to parse the message with `parse_mode`, set to either `html` or `markdown`.
  13. The value of `photo`, `audio`, `document` and `video` keys should be a url whose contents will be sent to you.
  14. **Setup**
  15. * Obtain an `auth_token` by [creating a new bot](https://telegram.me/botfather).
  16. * If you would like to send messages to a public channel:
  17. * Add your bot to the channel as an administrator
  18. * If you would like to send messages to a group:
  19. * Add the bot to the group
  20. * If you would like to send messages privately to yourself:
  21. * Open a conservation with the bot by visiting https://telegram.me/YourHuginnBot
  22. * Send a message to the bot, group or channel.
  23. * Select the `chat_id` from the dropdown.
  24. **Options**
  25. * `caption`: caption for a media content (0-200 characters)
  26. * `disable_notification`: send a message silently in a channel
  27. * `disable_web_page_preview`: disable link previews for links in a text message
  28. * `long_message`: truncate (default) or split text messages and captions that exceed Telegram API limits. Markdown and HTML tags can't span across messages and, if not opened or closed properly, will render as plain text.
  29. * `parse_mode`: parse policy of a text message
  30. See the official [Telegram Bot API documentation](https://core.telegram.org/bots/api#available-methods) for detailed info.
  31. MD
  32. def default_options
  33. {
  34. auth_token: 'xxxxxxxxx:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
  35. chat_id: 'xxxxxxxx'
  36. }
  37. end
  38. form_configurable :auth_token, roles: :validatable
  39. form_configurable :chat_id, roles: :completable
  40. form_configurable :caption
  41. form_configurable :disable_notification, type: :array, values: ['', 'true', 'false']
  42. form_configurable :disable_web_page_preview, type: :array, values: ['', 'true', 'false']
  43. form_configurable :long_message, type: :array, values: ['', 'split', 'truncate']
  44. form_configurable :parse_mode, type: :array, values: ['', 'html', 'markdown']
  45. def validate_auth_token
  46. HTTMultiParty.post(telegram_bot_uri('getMe'))['ok']
  47. end
  48. def complete_chat_id
  49. response = HTTMultiParty.post(telegram_bot_uri('getUpdates'))
  50. return [] unless response['ok']
  51. response['result'].map { |update| update_to_complete(update) }.uniq
  52. end
  53. def validate_options
  54. errors.add(:base, 'auth_token is required') unless options['auth_token'].present?
  55. errors.add(:base, 'chat_id is required') unless options['chat_id'].present?
  56. errors.add(:base, 'caption should be 200 characters ol less') if interpolated['caption'].present? && interpolated['caption'].length > 200 && (!interpolated['long_message'].present? || interpolated['long_message'] != 'split')
  57. errors.add(:base, "disable_notification has invalid value: should be 'true' or 'false'") if interpolated['disable_notification'].present? && !%w(true false).include?(interpolated['disable_notification'])
  58. errors.add(:base, "disable_web_page_preview has invalid value: should be 'true' or 'false'") if interpolated['disable_web_page_preview'].present? && !%w(true false).include?(interpolated['disable_web_page_preview'])
  59. errors.add(:base, "long_message has invalid value: should be 'split' or 'truncate'") if interpolated['long_message'].present? && !%w(split truncate).include?(interpolated['long_message'])
  60. errors.add(:base, "parse_mode has invalid value: should be 'html' or 'markdown'") if interpolated['parse_mode'].present? && !%w(html markdown).include?(interpolated['parse_mode'])
  61. end
  62. def working?
  63. received_event_without_error? && !recent_error_logs?
  64. end
  65. def receive(incoming_events)
  66. incoming_events.each do |event|
  67. receive_event event
  68. end
  69. end
  70. private
  71. TELEGRAM_ACTIONS = {
  72. text: :sendMessage,
  73. photo: :sendPhoto,
  74. audio: :sendAudio,
  75. document: :sendDocument,
  76. video: :sendVideo
  77. }.freeze
  78. def configure_params(params)
  79. params[:chat_id] = interpolated['chat_id']
  80. params[:disable_notification] = interpolated['disable_notification'] if interpolated['disable_notification'].present?
  81. if params.has_key?(:text)
  82. params[:disable_web_page_preview] = interpolated['disable_web_page_preview'] if interpolated['disable_web_page_preview'].present?
  83. params[:parse_mode] = interpolated['parse_mode'] if interpolated['parse_mode'].present?
  84. else
  85. params[:caption] = interpolated['caption'] if interpolated['caption'].present?
  86. end
  87. params
  88. end
  89. def receive_event(event)
  90. interpolate_with event do
  91. messages_send = TELEGRAM_ACTIONS.count do |field, _method|
  92. payload = event.payload[field]
  93. next unless payload.present?
  94. send_telegram_messages field, configure_params(field => payload)
  95. true
  96. end
  97. error("No valid key found in event #{event.payload.inspect}") if messages_send.zero?
  98. end
  99. end
  100. def send_message(field, params)
  101. response = HTTMultiParty.post telegram_bot_uri(TELEGRAM_ACTIONS[field]), query: params
  102. unless response['ok']
  103. error(response)
  104. end
  105. end
  106. def send_telegram_messages(field, params)
  107. if interpolated['long_message'] == 'split'
  108. if field == :text
  109. params[:text].scan(/\G(?:\w{4096}|.{1,4096}(?=\b|\z))/m) do |message|
  110. send_message field, configure_params(field => message.strip) unless message.strip.blank?
  111. end
  112. else
  113. caption_array = params[:caption].scan(/\G(?:\w{200}|.{1,200}(?=\b|\z))/m)
  114. params[:caption] = caption_array.first.strip
  115. send_message field, params
  116. caption_array.drop(1).each do |caption|
  117. send_message(:text, configure_params(text: caption.strip)) unless caption.strip.blank?
  118. end
  119. end
  120. else
  121. params[:caption] = params[:caption][0..199] if params[:caption]
  122. params[:text] = params[:text][0..4095] if params[:text]
  123. send_message field, params
  124. end
  125. end
  126. def telegram_bot_uri(method)
  127. "https://api.telegram.org/bot#{interpolated['auth_token']}/#{method}"
  128. end
  129. def update_to_complete(update)
  130. chat = (update['message'] || update.fetch('channel_post', {})).fetch('chat', {})
  131. {id: chat['id'], text: chat['title'] || "#{chat['first_name']} #{chat['last_name']}"}
  132. end
  133. end
  134. end