mirror of
https://github.com/lingble/chatwoot.git
synced 2025-12-14 17:57:09 +00:00
172 lines
5.1 KiB
Ruby
172 lines
5.1 KiB
Ruby
class Twilio::VoiceController < ActionController::Base
|
|
skip_forgery_protection
|
|
|
|
before_action :set_call_details, only: %i[status_callback simple_twiml]
|
|
before_action :set_inbox, only: %i[status_callback simple_twiml]
|
|
|
|
|
|
def status_callback
|
|
return head :ok unless @inbox
|
|
|
|
conversation = Voice::ConversationFinderService.new(
|
|
account: @inbox.account,
|
|
call_sid: @call_sid,
|
|
phone_number: incoming_number,
|
|
is_outbound: outbound?,
|
|
inbox: @inbox
|
|
).perform
|
|
|
|
Voice::CallStatus::Manager.new(
|
|
conversation: conversation,
|
|
call_sid: @call_sid,
|
|
provider: :twilio
|
|
).process_status_update(params[:CallStatus], params[:CallDuration]&.to_i, first_status_response?)
|
|
|
|
head :ok
|
|
end
|
|
|
|
def simple_twiml
|
|
return fallback_twiml unless @inbox
|
|
|
|
conversation = Voice::ConversationFinderService.new(
|
|
account: @inbox.account,
|
|
call_sid: @call_sid,
|
|
phone_number: incoming_number,
|
|
is_outbound: outbound?,
|
|
inbox: @inbox
|
|
).perform
|
|
|
|
Voice::CallStatus::Manager.new(
|
|
conversation: conversation,
|
|
call_sid: @call_sid,
|
|
provider: :twilio
|
|
).process_status_update('in_progress', nil, true)
|
|
|
|
conference_name = ensure_conference_name(conversation, params[:conference_name])
|
|
|
|
conversation.update!(
|
|
additional_attributes: conversation.additional_attributes.merge(
|
|
'conference_sid' => conference_name,
|
|
'call_direction' => outbound? ? 'outbound' : 'inbound',
|
|
'requires_agent_join' => true
|
|
)
|
|
)
|
|
|
|
render_twiml do |r|
|
|
r.say(message: 'Please wait while we connect you to an agent')
|
|
|
|
|
|
# Set up the conference
|
|
conference_callback_url = "#{base_url}/api/v1/accounts/#{@inbox.account_id}/channels/voice/webhooks/conference_status"
|
|
Rails.logger.info("📞 VoiceController: Setting conference callback to: #{conference_callback_url}")
|
|
|
|
r.dial do |d|
|
|
d.conference(
|
|
conference_name,
|
|
startConferenceOnEnter: outbound? ? true : false, # Start conference for outbound calls
|
|
endConferenceOnExit: true,
|
|
beep: false,
|
|
muted: false,
|
|
waitUrl: '',
|
|
earlyMedia: true,
|
|
statusCallback: conference_callback_url,
|
|
statusCallbackMethod: 'POST',
|
|
statusCallbackEvent: 'start end join leave',
|
|
participantLabel: "caller-#{@call_sid.last(8)}",
|
|
)
|
|
end
|
|
end
|
|
rescue StandardError => e
|
|
Rails.logger.error("Error creating voice conversation: #{e.message}")
|
|
fallback_twiml
|
|
end
|
|
|
|
private
|
|
|
|
def set_call_details
|
|
@call_sid = params[:CallSid]
|
|
@direction = params[:Direction]
|
|
end
|
|
|
|
def set_inbox
|
|
@inbox = find_inbox(outbound? ? params[:From] : params[:To])
|
|
end
|
|
|
|
def outbound?
|
|
@direction == 'outbound-api'
|
|
end
|
|
|
|
def incoming_number
|
|
outbound? ? params[:To] : params[:From]
|
|
end
|
|
|
|
def first_status_response?
|
|
params[:IsFirstResponseForStatus] == 'true'
|
|
end
|
|
|
|
def render_twiml(status: :ok)
|
|
response = Twilio::TwiML::VoiceResponse.new
|
|
yield response
|
|
render xml: response.to_s, status: status
|
|
end
|
|
|
|
def build_message(conversation, content)
|
|
Messages::MessageBuilder.new(
|
|
nil,
|
|
conversation,
|
|
content: content,
|
|
message_type: :activity,
|
|
additional_attributes: { call_sid: @call_sid, call_status: 'in_progress', user_input: true }
|
|
).perform
|
|
end
|
|
|
|
def input_text
|
|
return "Caller pressed #{params[:Digits]}" if params[:Digits].present?
|
|
return "Caller said: \"#{params[:SpeechResult]}\"" if params[:SpeechResult].present?
|
|
|
|
'Caller responded'
|
|
end
|
|
|
|
def ensure_conference_name(conversation, supplied)
|
|
name = supplied.presence ||
|
|
conversation.additional_attributes['conference_sid'] ||
|
|
conversation.additional_attributes['conference_name']
|
|
|
|
return name if name&.match?(/^conf_account_\d+_conv_\d+$/)
|
|
|
|
"conf_account_#{@inbox.account_id}_conv_#{conversation.display_id}"
|
|
end
|
|
|
|
def fallback_twiml
|
|
render_twiml do |r|
|
|
r.say(message: 'Hello from Chatwoot. This is a courtesy call to check on your recent signup.')
|
|
r.pause(length: 1)
|
|
r.say(message: 'We will connect you with an agent shortly.')
|
|
r.hangup
|
|
end
|
|
end
|
|
|
|
def base_url
|
|
ENV.fetch('FRONTEND_URL', 'http://localhost:3000')
|
|
end
|
|
|
|
def find_inbox(phone_number)
|
|
return nil if phone_number.blank?
|
|
|
|
Inbox.joins('INNER JOIN channel_voice ON channel_voice.account_id = inboxes.account_id AND inboxes.channel_id = channel_voice.id')
|
|
.find_by('channel_voice.phone_number = ?', phone_number)
|
|
end
|
|
|
|
def find_or_create_conversation(inbox, phone_number, call_sid)
|
|
Voice::ConversationFinderService.new(
|
|
account: inbox.account,
|
|
call_sid: call_sid,
|
|
phone_number: phone_number,
|
|
is_outbound: false,
|
|
inbox: inbox
|
|
).perform
|
|
rescue StandardError => e
|
|
Rails.logger.error("find_or_create_conversation error: #{e.message}")
|
|
nil
|
|
end
|
|
end |