feat(channel): add support for Telegram Business bots (#10181) (#11663)

Added support for Telegram Business bots. Telegram webhooks from such bots include the business_message field, which we transform into a standard message for Chatwoot. This PR also modifies how we handle replies, attachments, and image uploads when working with Telegram Business bots.

demo: https://drive.google.com/file/d/1Yz82wXBVRtb-mxjXogkUju4hlJbt3qyh/view?usp=sharing&t=4

Fixes #10181
This commit is contained in:
ruslan
2025-06-17 06:35:23 +03:00
committed by GitHub
parent 149dab239a
commit b87b7972c1
10 changed files with 203 additions and 11 deletions

View File

@@ -114,6 +114,24 @@ RSpec.describe Channel::Telegram do
expect(telegram_channel.send_message_on_telegram(message)).to eq('telegram_123')
end
it 'sends message with business_connection_id' do
additional_attributes = { 'chat_id' => '123', 'business_connection_id' => 'eooW3KF5WB5HxTD7T826' }
message = create(:message, message_type: :outgoing, content: 'test',
conversation: create(:conversation, inbox: telegram_channel.inbox, additional_attributes: additional_attributes))
stub_request(:post, "https://api.telegram.org/bot#{telegram_channel.bot_token}/sendMessage")
.with(
body: 'chat_id=123&text=test&reply_markup=&parse_mode=HTML&reply_to_message_id=&business_connection_id=eooW3KF5WB5HxTD7T826'
)
.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 text message failed' do
message = create(:message, message_type: :outgoing, content: 'test',
conversation: create(:conversation, inbox: telegram_channel.inbox, additional_attributes: { 'chat_id' => '123' }))

View File

@@ -89,6 +89,61 @@ describe Telegram::IncomingMessageService do
end
end
context 'when business connection messages' do
subject do
described_class.new(inbox: telegram_channel.inbox, params: params).perform
end
let(:business_message_params) { message_params.merge('business_connection_id' => 'eooW3KF5WB5HxTD7T826') }
let(:params) do
{
'update_id' => 2_342_342_343_242,
'business_message' => { 'text' => 'test' }.deep_merge(business_message_params)
}.with_indifferent_access
end
it 'creates appropriate conversations, message and contacts' do
subject
expect(telegram_channel.inbox.conversations.count).not_to eq(0)
expect(telegram_channel.inbox.conversations.last.additional_attributes).to include({ 'chat_id' => 23,
'business_connection_id' => 'eooW3KF5WB5HxTD7T826' })
contact = Contact.all.first
expect(contact.name).to eq('Sojan Jose')
expect(contact.additional_attributes['language_code']).to eq('en')
message = telegram_channel.inbox.messages.first
expect(message.content).to eq('test')
expect(message.message_type).to eq('incoming')
expect(message.sender).to eq(contact)
end
context 'when sender is your business account' do
let(:business_message_params) do
message_params.merge(
'business_connection_id' => 'eooW3KF5WB5HxTD7T826',
'from' => {
'id' => 42, 'is_bot' => false, 'first_name' => 'John', 'last_name' => 'Doe', 'username' => 'johndoe', 'language_code' => 'en'
}
)
end
it 'creates appropriate conversations, message and contacts' do
subject
expect(telegram_channel.inbox.conversations.count).not_to eq(0)
expect(telegram_channel.inbox.conversations.last.additional_attributes).to include({ 'chat_id' => 23,
'business_connection_id' => 'eooW3KF5WB5HxTD7T826' })
contact = Contact.all.first
expect(contact.name).to eq('Sojan Jose')
# TODO: The language code is not present when we send the first message to the client.
# Should we update it when the user replies?
expect(contact.additional_attributes['language_code']).to be_nil
message = telegram_channel.inbox.messages.first
expect(message.content).to eq('test')
expect(message.message_type).to eq('outgoing')
expect(message.sender).to be_nil
end
end
end
context 'when valid audio messages params' do
it 'creates appropriate conversations, message and contacts' do
allow(telegram_channel.inbox.channel).to receive(:get_telegram_file_path).and_return('https://chatwoot-assets.local/sample.mp3')

View File

@@ -40,6 +40,22 @@ RSpec.describe Telegram::SendAttachmentsService do
end
end
context 'when this is business chat' do
before { allow(channel).to receive(:business_connection_id).and_return('eooW3KF5WB5HxTD7T826') }
it 'sends all types of attachments in seperate groups and returns the last successful message ID from the batch' do
attach_files(message)
service.perform
expect(a_request(:post, "#{telegram_api_url}/sendMediaGroup")
.with { |req| req.body =~ /business_connection_id.+eooW3KF5WB5HxTD7T826/m })
.to have_been_made.times(2)
expect(a_request(:post, "#{telegram_api_url}/sendDocument")
.with { |req| req.body =~ /business_connection_id.+eooW3KF5WB5HxTD7T826/m })
.to have_been_made.once
end
end
context 'when all attachments are photo and video' do
before do
2.times { attach_file_to_message(message, 'image', 'sample.png', 'image/png') }

View File

@@ -53,6 +53,24 @@ describe Telegram::UpdateMessageService do
described_class.new(inbox: telegram_channel.inbox, params: caption_update_params.with_indifferent_access).perform
expect(message.reload.content).to eq('updated caption')
end
context 'when business message' do
let(:text_update_params) do
{
'update_id': 1,
'edited_business_message': common_message_params.merge(
'message_id': 48,
'text': 'updated message'
)
}
end
it 'updates the message text when text is present' do
message = create(:message, conversation: conversation, source_id: text_update_params[:edited_business_message][:message_id])
described_class.new(inbox: telegram_channel.inbox, params: text_update_params.with_indifferent_access).perform
expect(message.reload.content).to eq('updated message')
end
end
end
context 'when invalid update message params' do