Skip to content

Instantly share code, notes, and snippets.

@libryder
Created August 31, 2011 16:27
Show Gist options
  • Save libryder/1183954 to your computer and use it in GitHub Desktop.
Save libryder/1183954 to your computer and use it in GitHub Desktop.
Web component of restful_clicktocall
begin
require 'rack'
require 'json'
rescue LoadError
abort "ERROR: restful_rpc requires the 'rack' and 'json' gems"
end
# Don't you love regular expressions? Matches only 0-255 octets. Recognizes "*" as an octet wildcard.
VALID_IP_ADDRESS = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|\*)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|\*)$/
def ip_allowed?(ip)
raise ArgumentError, "#{ip.inspect} is not a valid IP address!" unless ip.kind_of?(String) && ip =~ VALID_IP_ADDRESS
octets = ip.split "."
case COMPONENTS.restful_rpc["access"]
when "everyone"
true
when "whitelist"
whitelist = COMPONENTS.restful_rpc["whitelist"]
!! whitelist.find do |pattern|
pattern_octets = pattern.split "."
# Traverse both arrays in parallel
octets.zip(pattern_octets).map do |octet, octet_pattern|
octet_pattern == "*" ? true : (octet == octet_pattern)
end == [true, true, true, true]
end
when "blacklist"
blacklist = COMPONENTS.restful_rpc["blacklist"]
! blacklist.find do |pattern|
pattern_octets = pattern.split "."
# Traverse both arrays in parallel
octets.zip(pattern_octets).map do |octet, octet_pattern|
octet_pattern == "*" ? true : (octet == octet_pattern)
end == [true, true, true, true]
end
else
raise Adhearsion::Components::ConfigurationError, 'Unrecognized "access" configuration value!'
end
end
RESTFUL_API_HANDLER = lambda do |env|
json = env["rack.input"].read
# Return "Bad Request" HTTP error if the client forgot
return [400, {}, "You must POST a valid JSON object!"] if json.blank?
json = JSON.parse(json)
nesting = COMPONENTS.restful_rpc["path_nesting"]
path = env["PATH_INFO"]
return [404, {}, "This resource does not respond to #{path.inspect}"] unless path[0...nesting.size] == nesting
path = path[nesting.size..-1]
return [404, {"Content-Type" => "application/json"}, "You cannot nest method names!"] if path.include?("/")
rpc_object = Adhearsion::Components.component_manager.extend_object_with(Object.new, :rpc)
# TODO: set the content-type and other HTTP headers
response_object = rpc_object.send(path, *json)
if response_object.kind_of? String
return [200, {"Content-Type" => "application/json"}, response_object.to_a.to_json]
elsif response_object.kind_of? Hash
return [200, {"Content-Type" => "application/json"}, response_object.to_json]
else
return [200, {"Content-Type" => "application/json"}, response_object.headers.to_json]
end
end
initialization do
config = COMPONENTS.restful_rpc
api = RESTFUL_API_HANDLER
port = config["port"] || 5000
authentication = config["authentication"]
show_exceptions = config["show_exceptions"]
handler = Rack::Handler.const_get(config["handler"] || "Mongrel")
if authentication
api = Rack::Auth::Basic.new(api) do |username, password|
authentication[username] == password
end
api.realm = "Adhearsion API"
end
if show_exceptions
api = Rack::ShowStatus.new(Rack::ShowExceptions.new(api))
end
Thread.new do
handler.run api, :Port => port
end
end
require 'rubygems'
require 'sinatra' # Get with "gem install sinatra"
require 'restful_adhearsion'
require 'pp'
#Create our Adhearsion object connected to the RESTful API of Adhearsion
Adhearsion = RESTfulAdhearsion.new(:host => "localhost",
:port => 5000,
:user => "jicksta",
:password => "roflcopterz")
# You'll need to change this for your own format.
# Note: this will soon be handled by the Call Routing DSL in Adhearsion.
def format_number(number)
if number.length == 10
return "SIP/tsgglobal/1#{number}"
elsif number.length == 11
return "SIP/tsgglobal/#{number}"
else
return "SIP/elastix-trunk/#{number}"
end
end
def call_with_source(source)
#ahn_log.click_to_call "Finding call with source #{source.inspect} in #{Adhearsion.active_calls.size} active calls"
retVal = Hash.new
Adhearsion.active_calls.to_a.find do |call|
if call.variables[:sourcedid].include? source
if (defined? call.variables[:msg_filename] && call.variables[:msg_filename] != nil)
retVal[:msg_filename] = call.variables[:msg_filename]
retVal[:result] = "alive"
end
else
retVal[:msg_filename] = nil
retVal[:result] = "dead"
end
return retVal
end
end
post "/call" do
options = { :channel => format_number(params[:source]),
:context => "adhearsion",
:priority => "1",
:callerid => "8664680900",
:exten => "1000",
:async => 'true',
:variable => "destination=#{format_number(params[:destination])},sourcedid=#{params[:source]}",
}
#Originates a call over the AMI interface
Adhearsion.originate(options)
#puts options.to_json
"ok".to_json
end
post "/hangup" do
#Get the channel of the active call, then hang it up
channel_of_active_call = Adhearsion.hangup_channel_with_destination params[:call_to_hangup]
"ok".to_json
end
get '/status' do
# The line below will return either {result:"alive"} or {result:"dead"} to the browser
Adhearsion.call_with_source(params[:source]).to_json
#{:result => Adhearsion.call_with_source(params[:source])}.to_json
end
get '/' do
#Build our web form that has the JQuery sprinkled in
<<-HTML
<html><head>
<title>Record a greeting</title>
<script src="jquery.js" type="text/javascript"></script>
<script src="call.js" type="text/javascript"></script>
<link href="style.css" media="screen" rel="stylesheet" type="text/css" />
</head><body>
<div id="content">
<h1>Record a greeting</h1>
<div id="call-form">
<p><label for="source">Your number: </label><input type="text" id="source" name="source"/></p>
<p><button onclick="new AhnCall($('#call'), $('#source').val(), $('#destination').val())">Start call</button></p>
<input type="hidden" name="destination" value="1" />
</div>
</div>
<div id="call" class="hidden">
<p>Starting...</p>
</div>
</body></html>
HTML
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment