feat: support reply to for Telegram (#8105)

Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
This commit is contained in:
Shivam Mishra
2023-10-20 13:14:20 +05:30
committed by GitHub
parent 7416bbb25e
commit b9694a0818
10 changed files with 146 additions and 12 deletions

View File

@@ -9,11 +9,11 @@ class Messages::MessageBuilder
@user = user
@message_type = params[:message_type] || 'outgoing'
@attachments = params[:attachments]
@automation_rule = @params&.dig(:content_attributes, :automation_rule_id)
@automation_rule = content_attributes&.dig(:automation_rule_id)
return unless params.instance_of?(ActionController::Parameters)
@in_reply_to = params.to_unsafe_h&.dig(:content_attributes, :in_reply_to)
@items = params.to_unsafe_h&.dig(:content_attributes, :items)
@in_reply_to = content_attributes&.dig(:in_reply_to)
@items = content_attributes&.dig(:items)
end
def perform
@@ -26,6 +26,38 @@ class Messages::MessageBuilder
private
# Extracts content attributes from the given params.
# - Converts ActionController::Parameters to a regular hash if needed.
# - Attempts to parse a JSON string if content is a string.
# - Returns an empty hash if content is not present, if there's a parsing error, or if it's an unexpected type.
def content_attributes
params = convert_to_hash(@params)
content_attributes = params.fetch(:content_attributes, {})
return parse_json(content_attributes) if content_attributes.is_a?(String)
return content_attributes if content_attributes.is_a?(Hash)
{}
end
# Converts the given object to a hash.
# If it's an instance of ActionController::Parameters, converts it to an unsafe hash.
# Otherwise, returns the object as-is.
def convert_to_hash(obj)
return obj.to_unsafe_h if obj.instance_of?(ActionController::Parameters)
obj
end
# Attempts to parse a string as JSON.
# If successful, returns the parsed hash with symbolized names.
# If unsuccessful, returns nil.
def parse_json(content)
JSON.parse(content, symbolize_names: true)
rescue JSON::ParserError
{}
end
def process_attachments
return if @attachments.blank?

View File

@@ -26,9 +26,13 @@ export const buildCreatePayload = ({
payload.append('echo_id', echoId);
payload.append('cc_emails', ccEmails);
payload.append('bcc_emails', bccEmails);
if (toEmails) {
payload.append('to_emails', toEmails);
}
if (contentAttributes) {
payload.append('content_attributes', JSON.stringify(contentAttributes));
}
} else {
payload = {
content: message,

View File

@@ -50,6 +50,7 @@ describe('#ConversationAPI', () => {
message: 'test content',
echoId: 12,
isPrivate: true,
contentAttributes: { in_reply_to: 12 },
files: [new Blob(['test-content'], { type: 'application/pdf' })],
});
expect(formPayload).toBeInstanceOf(FormData);
@@ -57,6 +58,10 @@ describe('#ConversationAPI', () => {
expect(formPayload.get('echo_id')).toEqual('12');
expect(formPayload.get('private')).toEqual('true');
expect(formPayload.get('cc_emails')).toEqual('');
expect(formPayload.get('bcc_emails')).toEqual('');
expect(formPayload.get('content_attributes')).toEqual(
'{"in_reply_to":12}'
);
});
it('builds object payload if file is not available', () => {

View File

@@ -38,6 +38,7 @@
v-if="inReplyToMessageId && inboxSupportsReplyTo"
:message="inReplyTo"
:message-type="data.message_type"
:parent-has-attachments="hasAttachments"
/>
<bubble-text
v-if="data.content"

View File

@@ -5,7 +5,7 @@
'bg-slate-100 dark:bg-slate-600 dark:text-slate-50':
messageType === MESSAGE_TYPE.INCOMING,
'bg-woot-600 text-woot-50': messageType === MESSAGE_TYPE.OUTGOING,
'-mx-2': message.content,
'-mx-2': !parentHasAttachments,
}"
>
<message-preview
@@ -34,6 +34,10 @@ export default {
type: Number,
required: true,
},
parentHasAttachments: {
type: Boolean,
required: true,
},
},
data() {
return { MESSAGE_TYPE };

View File

@@ -77,8 +77,16 @@ class Channel::Telegram < ApplicationRecord
errors.add(:bot_token, 'error setting up the webook') unless response.success?
end
def chat_id(message)
message.conversation[:additional_attributes]['chat_id']
end
def reply_to_message_id(message)
message.content_attributes['in_reply_to_external_id']
end
def send_message(message)
response = message_request(message.conversation[:additional_attributes]['chat_id'], message.content, reply_markup(message))
response = message_request(chat_id(message), message.content, reply_markup(message), reply_to_message_id(message))
response.parsed_response['result']['message_id'] if response.success?
end
@@ -115,24 +123,26 @@ class Channel::Telegram < ApplicationRecord
telegram_attachments << telegram_attachment
end
response = attachments_request(message.conversation[:additional_attributes]['chat_id'], telegram_attachments)
response = attachments_request(chat_id(message), telegram_attachments, reply_to_message_id(message))
response.parsed_response['result'].first['message_id'] if response.success?
end
def attachments_request(chat_id, attachments)
def attachments_request(chat_id, attachments, reply_to_message_id)
HTTParty.post("#{telegram_api_url}/sendMediaGroup",
body: {
chat_id: chat_id,
media: attachments.to_json
media: attachments.to_json,
reply_to_message_id: reply_to_message_id
})
end
def message_request(chat_id, text, reply_markup = nil)
def message_request(chat_id, text, reply_markup = nil, reply_to_message_id = nil)
HTTParty.post("#{telegram_api_url}/sendMessage",
body: {
chat_id: chat_id,
text: text,
reply_markup: reply_markup
reply_markup: reply_markup,
reply_to_message_id: reply_to_message_id
})
end
end

View File

@@ -19,6 +19,7 @@ class Telegram::IncomingMessageService
inbox_id: @inbox.id,
message_type: :incoming,
sender: @contact,
content_attributes: telegram_params_content_attributes,
source_id: telegram_params_message_id.to_s
)

View File

@@ -6,6 +6,13 @@ module Telegram::ParamHelpers
params.dig(:message, :chat, :type) == 'private'
end
def telegram_params_content_attributes
reply_to = params.dig(:message, :reply_to_message, :message_id)
return { 'in_reply_to_external_id' => reply_to } if reply_to
{}
end
def message_params?
params[:message].present?
end

View File

@@ -8,6 +8,7 @@ describe Messages::MessageBuilder do
let(:inbox) { create(:inbox, account: account) }
let(:inbox_member) { create(:inbox_member, inbox: inbox, account: account) }
let(:conversation) { create(:conversation, inbox: inbox, account: account) }
let(:message_for_reply) { create(:message, conversation: conversation) }
let(:params) do
ActionController::Parameters.new({
content: 'test'
@@ -21,6 +22,75 @@ describe Messages::MessageBuilder do
end
end
describe '#content_attributes' do
context 'when content_attributes is a JSON string' do
let(:params) do
ActionController::Parameters.new({
content: 'test',
content_attributes: "{\"in_reply_to\":#{message_for_reply.id}}"
})
end
it 'parses content_attributes from JSON string' do
message = described_class.new(user, conversation, params).perform
expect(message.content_attributes).to include(in_reply_to: message_for_reply.id)
end
end
context 'when content_attributes is a hash' do
let(:params) do
ActionController::Parameters.new({
content: 'test',
content_attributes: { in_reply_to: message_for_reply.id }
})
end
it 'uses content_attributes as provided' do
message = described_class.new(user, conversation, params).perform
expect(message.content_attributes).to include(in_reply_to: message_for_reply.id)
end
end
context 'when content_attributes is absent' do
let(:params) do
ActionController::Parameters.new({ content: 'test' })
end
it 'defaults to an empty hash' do
message = message_builder
expect(message.content_attributes).to eq({})
end
end
context 'when content_attributes is nil' do
let(:params) do
ActionController::Parameters.new({
content: 'test',
content_attributes: nil
})
end
it 'defaults to an empty hash' do
message = message_builder
expect(message.content_attributes).to eq({})
end
end
context 'when content_attributes is an invalid JSON string' do
let(:params) do
ActionController::Parameters.new({
content: 'test',
content_attributes: 'invalid_json'
})
end
it 'defaults to an empty hash' do
message = message_builder
expect(message.content_attributes).to eq({})
end
end
end
describe '#perform when message_type is incoming' do
context 'when channel is not api' do
let(:params) do

View File

@@ -10,7 +10,7 @@ RSpec.describe Channel::Telegram do
stub_request(:post, "https://api.telegram.org/bot#{telegram_channel.bot_token}/sendMessage")
.with(
body: 'chat_id=123&text=test&reply_markup='
body: 'chat_id=123&text=test&reply_markup=&reply_to_message_id='
)
.to_return(
status: 200,
@@ -32,7 +32,7 @@ RSpec.describe Channel::Telegram do
.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'
'callback_data%22%3A%22test%22%7D%5D%5D%7D&reply_to_message_id='
)
.to_return(
status: 200,