123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186 |
- require 'httmultiparty'
- module Agents
- class TelegramAgent < Agent
- include FormConfigurable
- cannot_be_scheduled!
- cannot_create_events!
- no_bulk_receive!
- can_dry_run!
- description <<~MD
- The Telegram Agent receives and collects events and sends them via [Telegram](https://telegram.org/).
- 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.
- 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`.
- The value of `photo`, `audio`, `document` and `video` keys should be a url whose contents will be sent to you.
- 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.#{' '}
- **Setup**
- * Obtain an `auth_token` by [creating a new bot](https://telegram.me/botfather).
- * If you would like to send messages to a public channel:
- * Add your bot to the channel as an administrator
- * If you would like to send messages to a group:
- * Add the bot to the group
- * If you would like to send messages privately to yourself:
- * Open a conservation with the bot by visiting https://telegram.me/YourHuginnBot
- * Send a message to the bot, group or channel.
- * Select the `chat_id` from the dropdown.
- **Options**
- * `caption`: caption for a media content (0-1024 characters), applied only for `photo`, `audio`, `document`, or `video`
- * `disable_notification`: send a message silently in a channel
- * `disable_web_page_preview`: disable link previews for links in a text message
- * `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.
- * `parse_mode`: parse policy of a text message
- See the official [Telegram Bot API documentation](https://core.telegram.org/bots/api#available-methods) for detailed info.
- MD
- def default_options
- {
- auth_token: 'xxxxxxxxx:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
- chat_id: 'xxxxxxxx'
- }
- end
- form_configurable :auth_token, roles: :validatable
- form_configurable :chat_id, roles: :completable
- form_configurable :caption
- form_configurable :disable_notification, type: :array, values: ['', 'true', 'false']
- form_configurable :disable_web_page_preview, type: :array, values: ['', 'true', 'false']
- form_configurable :long_message, type: :array, values: ['', 'split', 'truncate']
- form_configurable :parse_mode, type: :array, values: ['', 'html', 'markdown', 'markdownv2']
- def validate_auth_token
- HTTMultiParty.post(telegram_bot_uri('getMe'))['ok']
- end
- def complete_chat_id
- response = HTTMultiParty.post(telegram_bot_uri('getUpdates'))
- return [] unless response['ok']
- response['result'].map { |update| update_to_complete(update) }.uniq
- end
- def validate_options
- errors.add(:base, 'auth_token is required') unless options['auth_token'].present?
- errors.add(:base, 'chat_id is required') unless options['chat_id'].present?
- errors.add(:base,
- 'caption should be 1024 characters or less') if interpolated['caption'].present? && interpolated['caption'].length > 1024 && (!interpolated['long_message'].present? || interpolated['long_message'] != 'split')
- 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'])
- 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'])
- 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'])
- errors.add(:base,
- "parse_mode has invalid value: should be 'html', 'markdown' or 'markdownv2'") if interpolated['parse_mode'].present? && !%w[
- html markdown markdownv2
- ].include?(interpolated['parse_mode'])
- end
- def working?
- received_event_without_error? && !recent_error_logs?
- end
- def receive(incoming_events)
- incoming_events.each do |event|
- receive_event event
- end
- end
- private
- TELEGRAM_ACTIONS = {
- text: :sendMessage,
- photo: :sendPhoto,
- audio: :sendAudio,
- document: :sendDocument,
- video: :sendVideo,
- group: :sendMediaGroup,
- }.freeze
- def configure_params(params)
- params[:chat_id] = interpolated['chat_id']
- params[:disable_notification] =
- interpolated['disable_notification'] if interpolated['disable_notification'].present?
- if params.has_key?(:text)
- params[:disable_web_page_preview] =
- interpolated['disable_web_page_preview'] if interpolated['disable_web_page_preview'].present?
- params[:parse_mode] = interpolated['parse_mode'] if interpolated['parse_mode'].present?
- elsif !params.has_key?(:media)
- params[:caption] = interpolated['caption'] if interpolated['caption'].present?
- end
- params
- end
- def receive_event(event)
- interpolate_with event do
- messages_send = TELEGRAM_ACTIONS.count do |field, _method|
- payload = event.payload[field]
- next unless payload.present?
- if field == :group
- send_telegram_messages field, configure_params(media: payload)
- else
- send_telegram_messages field, configure_params(field => payload)
- end
- true
- end
- error("No valid key found in event #{event.payload.inspect}") if messages_send.zero?
- end
- end
- def send_message(field, params)
- response = HTTMultiParty.post telegram_bot_uri(TELEGRAM_ACTIONS[field]),
- body: params.to_json,
- headers: { 'Content-Type' => 'application/json' }
- unless response['ok']
- error(response)
- end
- end
- def send_telegram_messages(field, params)
- if interpolated['long_message'] == 'split'
- if field == :text
- params[:text].scan(/\G\s*(?:\w{4096}|.{1,4096}(?=\b|\z))/m) do |message|
- message.strip!
- send_message field, configure_params(field => message) unless message.blank?
- end
- else
- caption_array = (params[:caption].presence || '').scan(/\G\s*\K(?:\w{1024}|.{1,1024}(?=\b|\z))/m).map(&:strip)
- params[:caption] = caption_array.shift
- send_message field, params
- caption_array.each do |caption|
- send_message(:text, configure_params(text: caption)) unless caption.blank?
- end
- end
- else
- params[:caption] = params[:caption][0..1023] if params[:caption]
- params[:text] = params[:text][0..4095] if params[:text]
- send_message field, params
- end
- end
- def telegram_bot_uri(method)
- "https://api.telegram.org/bot#{interpolated['auth_token']}/#{method}"
- end
- def update_to_complete(update)
- chat = (update['message'] || update.fetch('channel_post', {})).fetch('chat', {})
- { id: chat['id'], text: chat['title'] || "#{chat['first_name']} #{chat['last_name']}" }
- end
- end
- end
|