telegram_agent.rb 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  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`, `video` or `group` 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`, `markdown` or `markdownv2`.
  13. The value of `photo`, `audio`, `document` and `video` keys should be a url whose contents will be sent to you.
  14. The value of `group` key should be a list and must consist of 2-10 objects representing an [InputMedia](https://core.telegram.org/bots/api#inputmedia) from the [Telegram Bot API](https://core.telegram.org/bots/api#inputmedia). Be careful: the `caption` field is not covered by the "long message" setting.#{' '}
  15. **Setup**
  16. * Obtain an `auth_token` by [creating a new bot](https://telegram.me/botfather).
  17. * If you would like to send messages to a public channel:
  18. * Add your bot to the channel as an administrator
  19. * If you would like to send messages to a group:
  20. * Add the bot to the group
  21. * If you would like to send messages privately to yourself:
  22. * Open a conservation with the bot by visiting https://telegram.me/YourHuginnBot
  23. * Send a message to the bot, group or channel.
  24. * Select the `chat_id` from the dropdown.
  25. **Options**
  26. * `caption`: caption for a media content (0-1024 characters), applied only for `photo`, `audio`, `document`, or `video`
  27. * `disable_notification`: send a message silently in a channel
  28. * `disable_web_page_preview`: disable link previews for links in a text message
  29. * `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.
  30. * `parse_mode`: parse policy of a text message
  31. See the official [Telegram Bot API documentation](https://core.telegram.org/bots/api#available-methods) for detailed info.
  32. MD
  33. def default_options
  34. {
  35. auth_token: 'xxxxxxxxx:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
  36. chat_id: 'xxxxxxxx'
  37. }
  38. end
  39. form_configurable :auth_token, roles: :validatable
  40. form_configurable :chat_id, roles: :completable
  41. form_configurable :caption
  42. form_configurable :disable_notification, type: :array, values: ['', 'true', 'false']
  43. form_configurable :disable_web_page_preview, type: :array, values: ['', 'true', 'false']
  44. form_configurable :long_message, type: :array, values: ['', 'split', 'truncate']
  45. form_configurable :parse_mode, type: :array, values: ['', 'html', 'markdown', 'markdownv2']
  46. def validate_auth_token
  47. HTTMultiParty.post(telegram_bot_uri('getMe'))['ok']
  48. end
  49. def complete_chat_id
  50. response = HTTMultiParty.post(telegram_bot_uri('getUpdates'))
  51. return [] unless response['ok']
  52. response['result'].map { |update| update_to_complete(update) }.uniq
  53. end
  54. def validate_options
  55. errors.add(:base, 'auth_token is required') unless options['auth_token'].present?
  56. errors.add(:base, 'chat_id is required') unless options['chat_id'].present?
  57. errors.add(:base,
  58. 'caption should be 1024 characters or less') if interpolated['caption'].present? && interpolated['caption'].length > 1024 && (!interpolated['long_message'].present? || interpolated['long_message'] != 'split')
  59. errors.add(:base,
  60. "disable_notification has invalid value: should be 'true' or 'false'") if interpolated['disable_notification'].present? && !%w[
  61. true false
  62. ].include?(interpolated['disable_notification'])
  63. errors.add(:base,
  64. "disable_web_page_preview has invalid value: should be 'true' or 'false'") if interpolated['disable_web_page_preview'].present? && !%w[
  65. true false
  66. ].include?(interpolated['disable_web_page_preview'])
  67. errors.add(:base,
  68. "long_message has invalid value: should be 'split' or 'truncate'") if interpolated['long_message'].present? && !%w[
  69. split truncate
  70. ].include?(interpolated['long_message'])
  71. errors.add(:base,
  72. "parse_mode has invalid value: should be 'html', 'markdown' or 'markdownv2'") if interpolated['parse_mode'].present? && !%w[
  73. html markdown markdownv2
  74. ].include?(interpolated['parse_mode'])
  75. end
  76. def working?
  77. received_event_without_error? && !recent_error_logs?
  78. end
  79. def receive(incoming_events)
  80. incoming_events.each do |event|
  81. receive_event event
  82. end
  83. end
  84. private
  85. TELEGRAM_ACTIONS = {
  86. text: :sendMessage,
  87. photo: :sendPhoto,
  88. audio: :sendAudio,
  89. document: :sendDocument,
  90. video: :sendVideo,
  91. group: :sendMediaGroup,
  92. }.freeze
  93. def configure_params(params)
  94. params[:chat_id] = interpolated['chat_id']
  95. params[:disable_notification] =
  96. interpolated['disable_notification'] if interpolated['disable_notification'].present?
  97. if params.has_key?(:text)
  98. params[:disable_web_page_preview] =
  99. interpolated['disable_web_page_preview'] if interpolated['disable_web_page_preview'].present?
  100. params[:parse_mode] = interpolated['parse_mode'] if interpolated['parse_mode'].present?
  101. elsif !params.has_key?(:media)
  102. params[:caption] = interpolated['caption'] if interpolated['caption'].present?
  103. end
  104. params
  105. end
  106. def receive_event(event)
  107. interpolate_with event do
  108. messages_send = TELEGRAM_ACTIONS.count do |field, _method|
  109. payload = event.payload[field]
  110. next unless payload.present?
  111. if field == :group
  112. send_telegram_messages field, configure_params(media: payload)
  113. else
  114. send_telegram_messages field, configure_params(field => payload)
  115. end
  116. true
  117. end
  118. error("No valid key found in event #{event.payload.inspect}") if messages_send.zero?
  119. end
  120. end
  121. def send_message(field, params)
  122. response = HTTMultiParty.post telegram_bot_uri(TELEGRAM_ACTIONS[field]),
  123. body: params.to_json,
  124. headers: { 'Content-Type' => 'application/json' }
  125. unless response['ok']
  126. error(response)
  127. end
  128. end
  129. def send_telegram_messages(field, params)
  130. if interpolated['long_message'] == 'split'
  131. if field == :text
  132. params[:text].scan(/\G\s*(?:\w{4096}|.{1,4096}(?=\b|\z))/m) do |message|
  133. message.strip!
  134. send_message field, configure_params(field => message) unless message.blank?
  135. end
  136. else
  137. caption_array = (params[:caption].presence || '').scan(/\G\s*\K(?:\w{1024}|.{1,1024}(?=\b|\z))/m).map(&:strip)
  138. params[:caption] = caption_array.shift
  139. send_message field, params
  140. caption_array.each do |caption|
  141. send_message(:text, configure_params(text: caption)) unless caption.blank?
  142. end
  143. end
  144. else
  145. params[:caption] = params[:caption][0..1023] if params[:caption]
  146. params[:text] = params[:text][0..4095] if params[:text]
  147. send_message field, params
  148. end
  149. end
  150. def telegram_bot_uri(method)
  151. "https://api.telegram.org/bot#{interpolated['auth_token']}/#{method}"
  152. end
  153. def update_to_complete(update)
  154. chat = (update['message'] || update.fetch('channel_post', {})).fetch('chat', {})
  155. { id: chat['id'], text: chat['title'] || "#{chat['first_name']} #{chat['last_name']}" }
  156. end
  157. end
  158. end