From 6002394fcf7d0202cd9568fe3ecf94e8e89d03ee Mon Sep 17 00:00:00 2001 From: "Chamath K.B. Attanayaka" <84182411+ChamathKB@users.noreply.github.com> Date: Tue, 28 Mar 2023 22:50:07 +0530 Subject: [PATCH] feat: Support input_select messages on telegram (#5887) - Adding interactive button support for telegram for outgoing and incoming messages. Co-authored-by: Sojan Jose --- app/models/channel/telegram.rb | 21 +++++- .../telegram/incoming_message_service.rb | 32 +++++---- app/services/telegram/param_helpers.rb | 68 +++++++++++++++++++ spec/models/channel/telegram_spec.rb | 35 ++++++++-- .../telegram/incoming_message_service_spec.rb | 28 ++++++++ 5 files changed, 162 insertions(+), 22 deletions(-) create mode 100644 app/services/telegram/param_helpers.rb diff --git a/app/models/channel/telegram.rb b/app/models/channel/telegram.rb index 26d70c854..ffc6433b2 100644 --- a/app/models/channel/telegram.rb +++ b/app/models/channel/telegram.rb @@ -78,10 +78,24 @@ class Channel::Telegram < ApplicationRecord end def send_message(message) - response = message_request(message.conversation[:additional_attributes]['chat_id'], message.content) + response = message_request(message.conversation[:additional_attributes]['chat_id'], message.content, reply_markup(message)) response.parsed_response['result']['message_id'] if response.success? end + def reply_markup(message) + return unless message.content_type == 'input_select' + + { + one_time_keyboard: true, + inline_keyboard: message.content_attributes['items'].map do |item| + [{ + text: item['title'], + callback_data: item['value'] + }] + end + }.to_json + end + def send_attachments(message) send_message(message) unless message.content.nil? @@ -113,11 +127,12 @@ class Channel::Telegram < ApplicationRecord }) end - def message_request(chat_id, text) + def message_request(chat_id, text, reply_markup = nil) HTTParty.post("#{telegram_api_url}/sendMessage", body: { chat_id: chat_id, - text: text + text: text, + reply_markup: reply_markup }) end end diff --git a/app/services/telegram/incoming_message_service.rb b/app/services/telegram/incoming_message_service.rb index 2391921bd..51a99b4fb 100644 --- a/app/services/telegram/incoming_message_service.rb +++ b/app/services/telegram/incoming_message_service.rb @@ -3,6 +3,7 @@ class Telegram::IncomingMessageService include ::FileTypeHelper + include ::Telegram::ParamHelpers pattr_initialize [:inbox!, :params!] def perform @@ -13,27 +14,23 @@ class Telegram::IncomingMessageService update_contact_avatar set_conversation @message = @conversation.messages.build( - content: params[:message][:text].presence || params[:message][:caption], + content: telegram_params_message_content, account_id: @inbox.account_id, inbox_id: @inbox.id, message_type: :incoming, sender: @contact, - source_id: (params[:message][:message_id]).to_s + source_id: telegram_params_message_id.to_s ) - attach_location - attach_files + + process_message_attachments if message_params? @message.save! end private - def private_message? - params.dig(:message, :chat, :type) == 'private' - end - def set_contact contact_inbox = ::ContactInboxWithContactBuilder.new( - source_id: params[:message][:from][:id], + source_id: telegram_params_from_id, inbox: inbox, contact_attributes: contact_attributes ).perform @@ -42,10 +39,15 @@ class Telegram::IncomingMessageService @contact = contact_inbox.contact end + def process_message_attachments + attach_location + attach_files + end + def update_contact_avatar return if @contact.avatar.attached? - avatar_url = inbox.channel.get_telegram_profile_image(params[:message][:from][:id]) + avatar_url = inbox.channel.get_telegram_profile_image(telegram_params_from_id) ::Avatar::AvatarFromUrlJob.perform_later(@contact, avatar_url) if avatar_url end @@ -68,21 +70,21 @@ class Telegram::IncomingMessageService def contact_attributes { - name: "#{params[:message][:from][:first_name]} #{params[:message][:from][:last_name]}", + name: "#{telegram_params_first_name} #{telegram_params_last_name}", additional_attributes: additional_attributes } end def additional_attributes { - username: params[:message][:from][:username], - language_code: params[:message][:from][:language_code] + username: telegram_params_username, + language_code: telegram_params_language_code } end def conversation_additional_attributes { - chat_id: params[:message][:chat][:id] + chat_id: telegram_params_chat_id } end @@ -128,7 +130,7 @@ class Telegram::IncomingMessageService end def location - @location ||= params[:message][:location].presence + @location ||= params.dig(:message, :location).presence end def visual_media_params diff --git a/app/services/telegram/param_helpers.rb b/app/services/telegram/param_helpers.rb new file mode 100644 index 000000000..5accae1f1 --- /dev/null +++ b/app/services/telegram/param_helpers.rb @@ -0,0 +1,68 @@ +module Telegram::ParamHelpers + # ensures that message is from a private chat and not a group chat + def private_message? + return true if callback_query_params? + + params.dig(:message, :chat, :type) == 'private' + end + + def message_params? + params[:message].present? + end + + def callback_query_params? + params[:callback_query].present? + end + + def telegram_params_base_object + if callback_query_params? + params[:callback_query] + else + params[:message] + end + end + + def telegram_params_from_id + telegram_params_base_object[:from][:id] + end + + def telegram_params_first_name + telegram_params_base_object[:from][:first_name] + end + + def telegram_params_last_name + telegram_params_base_object[:from][:last_name] + end + + def telegram_params_username + telegram_params_base_object[:from][:username] + end + + def telegram_params_language_code + telegram_params_base_object[:from][:language_code] + end + + def telegram_params_chat_id + if callback_query_params? + params[:callback_query][:message][:chat][:id] + else + telegram_params_base_object[:chat][:id] + end + end + + def telegram_params_message_content + if callback_query_params? + params[:callback_query][:data] + else + params[:message][:text].presence || params[:message][:caption] + end + end + + def telegram_params_message_id + if callback_query_params? + params[:callback_query][:id] + else + params[:message][:message_id] + end + end +end diff --git a/spec/models/channel/telegram_spec.rb b/spec/models/channel/telegram_spec.rb index de174d89c..8186a1149 100644 --- a/spec/models/channel/telegram_spec.rb +++ b/spec/models/channel/telegram_spec.rb @@ -8,11 +8,38 @@ RSpec.describe Channel::Telegram do message = create(:message, message_type: :outgoing, content: 'test', conversation: create(:conversation, inbox: telegram_channel.inbox, additional_attributes: { 'chat_id' => '123' })) - telegram_message_response = double + stub_request(:post, "https://api.telegram.org/bot#{telegram_channel.bot_token}/sendMessage") + .with( + body: 'chat_id=123&text=test&reply_markup=' + ) + .to_return( + status: 200, + body: { result: { message_id: 'telegram_123' } }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + + expect(telegram_channel.send_message_on_telegram(message)).to eq('telegram_123') + end + + it 'send message with reply_markup' do + message = create( + :message, message_type: :outgoing, content: 'test', content_type: 'input_select', + content_attributes: { 'items' => [{ 'title' => 'test', 'value' => 'test' }] }, + conversation: create(:conversation, inbox: telegram_channel.inbox, additional_attributes: { 'chat_id' => '123' }) + ) + + stub_request(:post, "https://api.telegram.org/bot#{telegram_channel.bot_token}/sendMessage") + .with( + body: 'chat_id=123&text=test' \ + '&reply_markup=%7B%22one_time_keyboard%22%3Atrue%2C%22inline_keyboard%22%3A%5B%5B%7B%22text%22%3A%22test%22%2C%22' \ + 'callback_data%22%3A%22test%22%7D%5D%5D%7D' + ) + .to_return( + status: 200, + body: { result: { message_id: 'telegram_123' } }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) - allow(telegram_message_response).to receive(:success?).and_return(true) - allow(telegram_message_response).to receive(:parsed_response).and_return({ 'result' => { 'message_id' => 'telegram_123' } }) - allow(telegram_channel).to receive(:message_request).and_return(telegram_message_response) expect(telegram_channel.send_message_on_telegram(message)).to eq('telegram_123') end end diff --git a/spec/services/telegram/incoming_message_service_spec.rb b/spec/services/telegram/incoming_message_service_spec.rb index b651e6fc4..0792f911c 100644 --- a/spec/services/telegram/incoming_message_service_spec.rb +++ b/spec/services/telegram/incoming_message_service_spec.rb @@ -232,5 +232,33 @@ describe Telegram::IncomingMessageService do expect(telegram_channel.inbox.messages.first.attachments.first.file_type).to eq('location') end end + + context 'when valid callbac_query params' do + it 'creates appropriate conversations, message and contacts' do + params = { + 'update_id' => 2_342_342_343_242, + 'callback_query' => { + 'id' => '2342342309929423', + 'from' => { + 'id' => 5_171_248, + 'is_bot' => false, + 'first_name' => 'Sojan', + 'last_name' => 'Jose', + 'username' => 'sojan', + 'language_code' => 'en', + 'is_premium' => true + }, + 'message' => message_params, + 'chat_instance' => '-89923842384923492', + 'data' => 'Option 1' + } + }.with_indifferent_access + + described_class.new(inbox: telegram_channel.inbox, params: params).perform + expect(telegram_channel.inbox.conversations.count).not_to eq(0) + expect(Contact.all.first.name).to eq('Sojan Jose') + expect(telegram_channel.inbox.messages.first.content).to eq('Option 1') + end + end end end