#!/usr/bin/env ruby require 'bundler' require 'logging' Bundler.require Dir['models/*.rb'].each { |file| require File.join Dir.pwd, file } DataMapper.setup(:default, ENV['DATABASE_URL'] || "sqlite://#{Dir.pwd}/development.db") DataMapper.finalize logger = Logging.logger['CallListener'] logger.level = :info logger.add_appenders( Logging.appenders.stdout(layout: Logging.layouts.pattern(pattern: '[%d] %-5l: %m\n')), Logging.appenders.rolling_file('log/calls.log', age: 'daily', layout: Logging.layouts.pattern(pattern: '[%d] %-5l: %m\n')) ) logger.info "Starting Calls" module CallListener def post_init logger = Logging.logger['CallListener'] Fiber.new do connection_headers = command('connect') operator_uuid = connection_headers['Caller-Unique-ID'] command("events plain all") # command("filter Unique-ID #{operator_uuid}") command("filter Core-UUID #{operator_uuid}") command("filter Event-Name CHANNEL_ANSWER") command("filter Event-Name CHANNEL_HANGUP") rack_sid = connection_headers['Channel-Destination-Number'][8..-1] rack_session = Rack::Session::Redis.new(nil) rack = rack_session.get_session({}, rack_sid).last user = Identity.get rack['user_id'] unless user.nil? company = Company.get rack['calling_to_company_id'] targets = company.targets(status: :in_progress) target = targets[rand(targets.size)] target_contact = target.target_contacts.first(status: :not_called) logger.warn "#{company.name} #{target.name} has not contacts left" if target_contact.nil? unless target_contact.nil? rack['target_contact_id'] = target_contact.id rack_session.set_session({}, rack_sid, rack, {}) logger.info "Bridging #{user.name} with #{target_contact.phone}" bridge("sofia/external/sip:#{target_contact.phone}@sip.zadarma.com") loop do @current_fiber = Fiber.current event_headers = Fiber.yield if event_headers['Content-Type'] == "text/disconnect-notice" break elsif event_headers['Event-Name'] == "CHANNEL_ANSWER" # in case uuid is same as operator_uuid, skip to next CHANNEL_ANSWER target_call_uuid = event_headers['Caller-Unique-ID'] bridged_call operator_uuid, target_call_uuid, rack_sid, company.manager_phone # command "api uuid_kill #{target_call_uuid}" break elsif event_headers['Event-Name'] == "CHANNEL_HANGUP" break end end end end # command "api uuid_kill #{operator_uuid}" close_connection_after_writing end.resume end def bridged_call operator_uuid, target_call_uuid, rack_sid, manager_phone puts "operator_uuid #{operator_uuid}, target_call_uuid #{target_call_uuid}" subscriber = EM::Hiredis.connect subscriber.subscribe "transfer.#{rack_sid}" subscriber.on(:message) do |channel, message| puts "TRANSFER MESSAGE RECV" puts "Bridging with #{manager_phone}" Fiber.new do manager_uuid = command("api create_uuid\nevent-lock:true")['Body'] puts "manager_uuid #{manager_uuid}" command("api originate {origination_uuid=#{manager_uuid}}sofia/external/sip:#{manager_phone}@sip.zadarma.com &park() inline") loop do puts "listen" @current_fiber = Fiber.current event_headers = Fiber.yield break if event_headers.nil? if event_headers['Content-Type'] == "text/disconnect-notice" break elsif event_headers['Event-Name'] == "CHANNEL_ANSWER" intercept manager_uuid, "-bleg #{operator_uuid}" elsif event_headers['Event-Name'] == "CHANNEL_HANGUP" # command "api uuid_kill #{operator_uuid}" break end end end.resume @current_fiber.resume nil if @current_fiber && @current_fiber.alive? end loop do @current_fiber = Fiber.current event_headers = Fiber.yield break if event_headers.nil? if event_headers['Content-Type'] == "text/disconnect-notice" break elsif event_headers['Event-Name'] == "CHANNEL_HANGUP" break end end end def bridge phone command <<-BRIDGE sendmsg call-command: execute execute-app-name: bridge execute-app-arg: #{phone}\n\n BRIDGE end def intercept inbound_uuid, outbound_uuid command <<-INTERCEPT sendmsg #{inbound_uuid} call-command: execute execute-app-name: intercept execute-app-arg: #{outbound_uuid}\n\n INTERCEPT end def command data send_data "#{data}\n\n" @current_fiber = Fiber.current Fiber.yield end def receive_data data headers = data.split(/\n/).inject({}) do |hash, row| ri = row.index(': ') hash[row[0..(ri-1)]] = row[(ri+2)..-1] unless ri.nil? hash end content_length = Integer(headers['Content-Length']) rescue nil headers['Body'] = content_length ? data[(data.size-content_length)..-1] : '' # puts "RECV: [#{headers['Event-Name']}] [#{headers['Content-Type']}]" # puts "BODY: [#{headers['Body'][0..100]}]" rescue nil @current_fiber.resume headers if @current_fiber && @current_fiber.alive? end # def send_data data # puts "SNDING: #{data}" # super # end end begin EM.run do EventMachine.start_server '127.0.0.1', 8084, CallListener end rescue StandardError => e logger.error ["Unhandled exception in EM loop, restarting", e.message, e.backtrace].join('\n') retry rescue Exception => e logger.fatal ["Unhandled FATAL exception in EM loop", e.message, e.backtrace].join('\n') end