class TelegramBotMiddleware
Constants
- VERSION
Public Class Methods
new(app) { |config| ... }
click to toggle source
# File lib/telegram_bot_middleware.rb, line 13 def initialize(app, &_block) # save the app var @app = app # local cookies hash @cookies = Hash.new # create the config and populate passing do the block function @config = OpenStruct.new yield(@config) if block_given? # validate required input params raise ArgumentError.new("Config error: host can't be null || empty.") if @config.host.nil? || @config.host.empty? raise ArgumentError.new("Config error: token can't be null || empty.") if @config.token.nil? || @config.token.empty? # initialize persistent connection to telegram self.class.persistent_connection_adapter pool_size: (@config.connection_pool_size || 2), keep_alive: (@config.connection_keep_alive || 30), force_retry: (@config.connection_force_retry || true) # if get_updates is empty set to :polling by default @config.get_updates ||= :polling # setup webhook if @config.webhook.nil? @config.host = "#{@config.host}/" unless @config.host.end_with?('/') @config.webhook = "#{@config.host}#{@config.token}" end # setup telegram messages input case @config.get_updates # setup polling when :polling # clear the webhook in case was set in the past send_to_telegram('setWebhook', {url: ''}) # setup a thread with get_updates function start_get_updates_thread # setup webhook when :webhook send_to_telegram('setWebhook', {url: @config.webhook}) # in this case get_updates is a non valid value else raise ArgumentError.new('Config error: get_updates must be :webhook || :polling.') end end
Public Instance Methods
_call(env)
click to toggle source
# File lib/telegram_bot_middleware.rb, line 92 def _call(env) # retrieve the request object request = Rack::Request.new(env) # if the request is a post to bot webhhok if request.post? and request.path == "/#{@config.token}" # in case someone already read it request.body.rewind # build an openstruct based on post params params = OpenStruct.from_json(request.body.read) log_debug("Message from chat: #{params}") # build command based on message command = get_command(params) # transform the POST in GET env['PATH_INFO'] = command env['QUERY_STRING'] = Rack::Utils.build_nested_query(params.message.to_h_nested) env['REQUEST_METHOD'] = 'GET' env['REQUEST_URI'] = "https://#{request.host}#{command}" # if in cache a cookie for this chat was present add to the header env['HTTP_COOKIE'] = @cookies[params.message.chat.id] if @cookies.include?(params.message.chat.id) # call the rack stack status, headers, body = @app.call(env) # try to send to telegram only if no errors if status == 200 || status == '200' # if the call setted a cookie save to local cache @cookies[params.message.chat.id] = headers['Set-Cookie'] if headers.include?('Set-Cookie') case headers['Content-Type'].split(';').first when 'text/html', 'application/json' if body.is_a? Hash process_hash_message(body.clone, params) body = Array.new(1) { '' } else body.each do |data| begin #TODO: add better json parsing to support symbols too process_hash_message(JSON.parse(data.gsub('=>', ':')), params) rescue send_to_telegram('sendMessage', {chat_id: params.message.chat.id, text: data}) end end end when /(^image\/)/ send_to_telegram('sendPhoto', {chat_id: params.message.chat.id, photo: File.new(body)}) when /(^audio\/)/ send_to_telegram('sendAudio', {chat_id: params.message.chat.id, audio: File.new(body)}) when /(^video\/)/ send_to_telegram('sendVideo', {chat_id: params.message.chat.id, video: File.new(body)}) end end # return result [status, headers, body] else # normal rack flow - not a bot call @app.call(env) end end
call(env)
click to toggle source
necessary for thread safe
# File lib/telegram_bot_middleware.rb, line 88 def call(env) dup._call(env) end
get_command(params)
click to toggle source
build command based on message
# File lib/telegram_bot_middleware.rb, line 163 def get_command(params) unless params.message['text'].nil? # build path based on message # - get only message part of post params # - remove empty chars from beginning || end (strip) # - replace first sequence of spaces with / # - encode as uri command = URI.escape(params.message.text.strip.sub(/\s+/, '/')) # - add first / if not present command = "/#{command}" unless command.start_with?('/') return command else %w(audio document photo sticker video voice contact location new_chat_participant left_chat_participant new_chat_title new_chat_photo delete_chat_photo group_chat_created).each do |type| unless params.message[type].nil? return "/#{type}" end end end end
log(level, message)
click to toggle source
TODO: to fix env
# File lib/telegram_bot_middleware.rb, line 239 def log(level, message) #return if @env.nil? #if @env['rack.logger'] # @env['rack.logger'].send(level, message) #else # @env['rack.errors'].write(message) #end end
log_debug(message)
click to toggle source
# File lib/telegram_bot_middleware.rb, line 234 def log_debug(message) log(:debug, message) end
log_error(exception)
click to toggle source
# File lib/telegram_bot_middleware.rb, line 225 def log_error(exception) message = "Error: #{exception.message}\n#{exception.backtrace.join("\n")}\n" log(:error, message) end
log_info(message)
click to toggle source
# File lib/telegram_bot_middleware.rb, line 230 def log_info(message) log(:info, message) end
process_hash_message(message, params)
click to toggle source
# File lib/telegram_bot_middleware.rb, line 183 def process_hash_message(message, params) if (message.include?(:multiple) && message[:multiple].is_a?(Array)) message[:multiple].each { |item| process_json_message(item, params) } elsif (message.include?('multiple') && message['multiple'].is_a?(Array)) message['multiple'].each { |item| process_json_message(item, params) } else process_json_message(message, params) end end
process_json_message(message, params)
click to toggle source
# File lib/telegram_bot_middleware.rb, line 193 def process_json_message(message, params) message[:chat_id] = params.message.chat.id unless message.include?(:chat_id) || message.include?('chat_id') ['reply_markup', :reply_markup].each do |item| message[item] = message[item].to_json if message.include?(item) end ['photo', :photo, 'audio', :audio, 'video', :video].each do |item| message[item] = File.new(message[item]) if message.include?(item) end if message.include?(:text) || message.include?('text') send_to_telegram('sendMessage', message) elsif (message.include?(:latitude) and message.include?(:longitude)) || (message.include?('latitude') and message.include?('longitude')) send_to_telegram('sendLocation', message) elsif message.include?(:photo) || message.include?('photo') send_to_telegram('sendPhoto', message) elsif message.include?(:audio) || message.include?('audio') send_to_telegram('sendAudio', message) elsif message.include?(:video) || message.include?('video') send_to_telegram('sendVideo', message) else # TODO: invalid query end end
send_to_telegram(path, query)
click to toggle source
# File lib/telegram_bot_middleware.rb, line 219 def send_to_telegram(path, query) log_debug("Sending to chat: #{path} - #{query}") self.class.post("/bot#{@config.token}/#{path}", query: query) # TODO check response error and return response end
start_get_updates_thread()
click to toggle source
# File lib/telegram_bot_middleware.rb, line 63 def start_get_updates_thread # start a new thread Thread.new do # the initial offset is always 0 @offset = 0 # wait 5 seconds to don't risk to post message too early when the app is not still up sleep 5 # loop forever loop do # call the getUpdates telegram function response = send_to_telegram('getUpdates', {offset: @offset}) # enumerate the results response.to_hash['result'].each do |data| # create an update message from the post data update = OpenStruct.new(data) # store the last offset +1 but ensure that is not lower than the already stored @offset = (update.update_id + 1) if update.update_id + 1 > @offset # simulate a post to itself HTTP.post @config.webhook, json: update.to_h_nested end end end end