mirror of
https://github.com/lingble/chatwoot.git
synced 2025-11-02 12:08:01 +00:00
feat: support reply to for Telegram (#8105)
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
This commit is contained in:
@@ -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?
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user