Re: Flexible table-controlled dialplan, Version 2
Posted: Tue Jul 27, 2010 3:54 pm
Oh, it explains everything, thank you!...they have allocated hundreds of 1-403-222-xxxx phone numbers
Community discussions for the SIP Sorcery SIP aggregator service
http://forum.sipsorcery.com/
Oh, it explains everything, thank you!...they have allocated hundreds of 1-403-222-xxxx phone numbers
Code: Select all
# Copyright 2010 Mike Telis
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations under
# the License.
require 'mikesgem'
# ************* C O N F I G U R A T I O N S E C T I O N *************** #
Area = '026' # my area code, this will be added to 7-digit dialouts
Tz = +2 # my time zone (GMT format, e.g. Eastern = -5, Central = -6)
# Speed dial entries. Format: "key" => "number"
Speeddial = {
'0' => '31261234567@sip1.budgetphone.nl', # Home
'303' => '303@sip.blueface.ie', # Blueface speaking clock (Ireland time)
'266' => '4153767253@podlinez.net', # CNN Headlines (266 = "CNN")
'73' => '31261234765@sip1.budgetphone.nl' # Piet
}
# CNAM table: number in ENUM format => caller's name
CNAM = {
'31261234567' => 'Pa en ma'
# '(215) 333-2211' => 'Bratty kid',
}
# Uncomment next line and insert your White Pages API key, if you have it
# WP_key = 'Insert your White Pages key here' # White Pages API key
# Uncomment line below to enable misdialing safeguards
EnableSafeguards = 1
# Excluded Prefixes. Provides a safeguard against accidentally calling premium numbers
ExcludedPrefixes = [
' 1 (900 | 809)', # USA Premium
' 1 \d\d\d 555 1212', # USA Directory assistance
'44 (9 | 55 | 70 | 84 | 87)', # UK Premium
'49 (1 [^567] | 900)', # Germany Premium
'39 (1 | 84 | 89)', # Italy Premium
'420 90', # Czech Premium
'32 (70 | 90\d)' # Belgium Premium
]
# Yet another safeguard, list of blessed country codes
Allowed_Country = %w{
1 31 32 33 34 35[2-3] 41 43 44 48 49
}
# My own ENUM database
MyENUM = {
'0261234567' => '31261234567@sip1.budgetphone.nl', # eigen nummer
'0261234765' => '31261234765@sip1.budgetphone.nl' # Piet
}
# Enum database list
EnumDB = [
MyENUM, # look in MyENUM first
'e164.org',
'e164.info',
'e164.arpa',
'e164.televolution.net',
'enum.org'
]
# Serviced domains, must be in lowercase!
Domains = ['sipsorcery.com','sip.sipsorcery.com','sip1.sipsorcery.com','sip2.sipsorcery.com','69.59.142.213']
Host = 'sipsorcery.com' # Replaces "host" on incoming calls
# Google Voice accounts
#
# Myacnt1 = { :usr => 'myname1', :pwd => 'password1' }
# Myacnt2 = { :usr => 'myname2', :pwd => 'password2' }
#
# SJaccount = [
# Myacnt1 + { :cb => '(206) 424-1234' },
# Myacnt1 + { :cb => '(253) 753-4321' },
# Myacnt1 + { :cb => '(401) 648-1234' },
# ]
#
# SFaccount = [
# Myacnt2 + { :cb => '(603) 413-1234' },
# Myacnt2 + { :cb => '(747) 493-4321' },
# ]
# SIP accounts
12voip = VSP.new '#0', '${EXTEN}@12voip', '12voip (default)'
budgetphone = VSP.new '#1', '${EXTEN}@budgetphone', 'budgetphone'
# SanJose = GV.new '*#1', nil, 'GV-408', :account => SJaccount, :repeat => 3, :rand => true
# Frisco = GV.new '*#2', nil, 'GV-415', :account => SFaccount, :repeat => 2
# Chicago = GV.new '*#3', nil, 'GV-773', :usr => 'myname3', :pwd => 'password3',
# :cb => '(305) 760-1234', :repeat => 2
# VSP.new '**', 'DisableSafeGuards' # Disable safeguards prefix is **
# ******************** s e l e c t V S P *******************************
def selectVSP # VoIP provider selection
# case @num
# when /^\*/ # For *500, *600 and other Voxalot services
# route_to Voxalot, "Voxalot Services", false
#
# when /^883/ # iNUM
# route_to INUM, "iNUM"
#
# when /(^1([2-9]\d\d)[2-9]\d{6})/ # North America
# @num = $1 # Truncate to 11 digits
# case $2 # check area code
# when "800", "866", "877", "888" # toll free numbers...
# route_to SanJose, "USA toll-free", false # call from GV/San Jose, disable ENUM search
# when "415" # San Francisco patch, I have GV number there
# route_to Frisco, "San Francisco"
# when "312", "773", "872" # Chicago, I have GV number there
# route_to Chicago, "Chicago"
# else
# route_to SanJose # all other destinations within US & Canada
# end
#
# route_to Ribbit, nil, false # If GV call failed, try one more time with Ribbit
#
case @num
when /^34/
route_to 12voip, "Spanje"
else
rejectCall(603,"Number's too short, check & dial again") if @num.length < 10
route_to 12voip
end
end
# ------------ O P T I O N A L C O N F I G U R A T I O N --------------- #
# ******************** i n c o m i n g C a l l *************************
def incomingCall
sys.SetFromHeader(formatNum(@cname || @cid,true), nil, Host) # Set FromName & FromHost for sys.Dial
# Forward call to the bindings (ATA / softphone)
# Change FromURI when forwarding to @local, or else Bria won't find contact in its phonebook!
callswitch("#{@user}@local[fu=#{@cid}]",45) unless (30..745) === @t.hour*100 + @t.min # reject incoming calls from 0:30a to 7:45a
@code, @reason = 480, "#{@user} is asleep" unless @code # if nothing else, must be the night hour
@code = 486 if @trunk =~ /IPCOMM/i ## *** temporary fix for IPCOMMS ***
end
# ************************** t o E N U M *******************************
def to_ENUM num
num.gsub!(/[^0-9*+]/,'') # Delete all fancy chars (only digits, '+' and '*' allowed)
# Check if the number begins with one of international prefixes:
# '+' - international format
# 00 - European style international prefix (00)
# 011 - US style international prefix (011)
num =~ /^(\+|00|011)/ and return $' # if yes, remove prefix and return
case num # Special cases
when /^[2-9]\d{6}$/ # Local call, 7-digit number
'1' + Area + num # prefix it with country and area code
when /^[01]?([2-9]\d{9})/ # US number with or without "1" country code
'1' + $1 # add country code and truncate number to 10-digit
when /^\*/ # Voxalot voicemail, echotest & other special numbers
num # ... as is
else
rejectCall(603,"Wrong number: '#{num}', check & dial again")
end
end
# ****** E N D O F C O N F I G U R A T I O N S E C T I O N ******** #
# ************************** C A L L S W I T C H **********************
def callswitch(num,*args)
@timeout = args[0]
num.gsub!(/%([0-9A-F]{2})/) {$1.to_i(16).chr} # Convert %hh into ASCII
@num = Speeddial[num] || num # If there is speed dial entry for it...
if @num =~ /@/ # If we already have URI, just dial and return
sys.Log("URI dialing: #@num")
dial(@num,*args)
else # Not URI
rexp = VSP.tab.keys.sort {|a,b| b.length <=> a.length}.map {|x| Regexp.escape(x)}.join('|')
if @num =~ /^(#{rexp})/ # If number starts with VSP selection prefix
@num = $'; @forcedRoute = VSP.tab[$1]
@noSafeGuards = (@forcedRoute.fmt =~ /Disable\s*Safe\s*Guards/i)
end
@num = to_ENUM(@num) # Convert to ENUM
rejectCall(503,"Number's empty") if @num.empty?
sys.Log("Number in ENUM format: #{@num}")
if @forcedRoute && !@noSafeGuards
route_to @forcedRoute, "Forced routing!", false # if forced with prefix, skip ENUM, safeguards & VSP selection
else
checkNum if defined?(EnableSafeguards) && !@noSafeGuards
selectVSP # Pick appropriate provider for the call
end
end # URI
end
# *************************** R O U T E _ T O ****************************
def route_to vsp, dest=nil, enum = EnumDB
enum.to_a.each do |db| # if enum enabled, look in all enum databases
if uri = (db.class == Hash)? db[@num] : sys.ENUMLookup("#{@num}.#{db}")
sys.Log("ENUM entry found: '#{uri}' in #{db.class == Hash ? 'local' : db} database")
dial(uri)
end
end # ENUM not found or failed, call via regular VSP
return unless vsp # No VSP - do nothing
uri = vsp.fmt.gsub(/\s+/,'').gsub(/\$\{EXTEN(:([^:}]+)(:([^}]+))?)?\}/) {@num[$2.to_i,$4? $4.to_i : 100]}
dest &&= " (#{dest})"; with = vsp.name; with &&= " with #{with}"
sys.Log("Calling #{formatNum(@num)}#{dest}#{with}")
if vsp.is_gv?
vsp.repeat.times do |i|
@code, @reason = 200, "OK" # assume OK
sys.GoogleVoiceCall *vsp.getparams(uri, i + (vsp.rand ? @t.to_i : 0))
sys.Log("Google Voice Call failed!")
@code, @reason = 603, 'Service Unavailable'
end
else
vsp.repeat.times do
dial(uri, @timeout || vsp.tmo || 300) # Dial, global time-out overrides account
end
end
end
# ******************************* D I A L ********************************
def dial *args
@code, @reason = nil
sys.Dial *args # dial URI
status() # We shouldn't be here! Get error code...
sys.Log("Call failed: code #{@code}, #{@reason}")
end
# ***************************** S T A T U S ******************************
def status
begin
@code, @reason = 487, 'Cancelled by Sipsorcery'
sys.LastDialled.each do |ptr|
if ptr
ptr = ptr.TransactionFinalResponse
@code = ptr.StatusCode; @reason = ptr.ReasonPhrase; break if @code == 200
# sys.Log("#{ptr.ToString()}")
end
end
rescue
end
end
# ************************ r e j e c t C a l l ***************************
def rejectCall code, reason
@code = code; @reason = reason
sys.Respond code, reason
end
# **************************** C H E C K N U M **************************
def checkNum
return if @num.match(/^\D/) # skip if number doesn't begin with a digit
# Reject calls to not blessed countries and premium numbers
# (unless VSP was forced using #n dial prefix)
rejectCall(503,"Calls to code #{formatNum(@num).split(' ')[0]} not allowed") \
unless @num.match "^(#{Allowed_Country.join('|')})"
rejectCall(503,"Calls to '#{formatNum($&)}' not allowed") if @num.match \
'^(' + ExcludedPrefixes.map { |x| "(:?#{x.gsub(/\s*/,'')})" }.join('|') + ')'
end
# ********************** k e y s t o E N U M *************************
def keys_to_ENUM (table)
Hash[*table.keys.map! {|key| to_ENUM(key.dup)}.zip(table.values).flatten]
end
# ************************** g e t T I M E *******************************
def getTime
Time.now + ((Tz+8)*60*60) # Get current time and adjust to local. SS Server is in GMT-8
end
# ******************************* M A I N ********************************
begin
sys.Log("** Call from #{req.Header.From} to #{req.URI.User} **")
sys.ExtendScriptTimeout(15) # preventing long running dialscript time-out
@t = getTime()
sys.Log(@t.strftime('Local time: %c'))
EnumDB.map! {|x| x.class == Hash ? keys_to_ENUM(x) : x } # rebuild local ENUM table
if sys.In # If incoming call...
@cid = req.Header.from.FromURI.User.to_s # Get caller ID
# Prepend 10-digit numbers with "1" (US country code) and remove int'l prefix (if present)
@cid = ('1' + @cid) if @cid =~ /^[2-9]\d\d[2-9]\d{6}$/
@cid.sub!(/^(\+|00|011)/,'') # Remove international prefixes, if any
prs = req.URI.User.split('.') # parse User into chunks
@trunk = prs[-2] # get trunk name
@user = prs[-1] # called user name
# Check CNAM first. If not found and US number, try to lookup caller's name in Whitepages
if !(@cname = keys_to_ENUM(CNAM)[@cid]) && @cid =~ /^1([2-9]\d\d[2-9]\d{6})$/ && defined?(WP_key)
url = "http://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20xml%20where%20url%3D'http%3A%2F%2Fapi.whitepages.com%2Freverse_phone%2F1.0%2F%3Fphone%3D#{$1}%3Bapi_key%3D#{WP_key}'%20and%20itemPath%3D'wp.listings.listing'&format=json"
if js = sys.WebGet(url,4).to_s
@cname, dname, city, state = %w(businessname displayname city state).map {|x| js =~ /"#{x}":"([^"]+)"/; $1}
@cname ||= dname; @cname ||= "#{city}, #{state}" if city && state
end
end
sys.Log("Caller's number: '#{@cid}'"); sys.Log("Caller's name: '#{@cname}'") if @cname
incomingCall() # forward incoming call
else # Outbound call ...
# check if it's URI or phone number.
# If destination's host is in our domain, it's a phone call
num = req.URI.User.to_s; reqHost = req.URI.Host.to_s # Get User and Host
host = reqHost.downcase.slice(/[^:]+/) # Convert to lowercase and delete optional ":port"
num << '@' << reqHost unless Domains.include?(host) # URI dialing unless host is in our domain list
callswitch(num)
end
sys.Respond(@code,@reason) # Forward error code to ATA
rescue
# Gives a lot more details at what went wrong (borrowed from Myatus' dialplan)
sys.Log("** Error: " + $!) unless $!.to_s =~ /Thread was being aborted./
end
Forwarding ("callback") numbers are selected either at random or in sequential order (refer to :rand option). The only way to change it is to edit your dialplan or force provider with a prefix (see below).TheMountainMan wrote:When using some of the providers such a sipgate, IPComms IPKall with this dialplan, is there an easy way to change the order of the preferred carrier?
Yes, speed dial entries are processed as if you dialed the number manually. It is documented, check out the Configuration example section:Also, when using more than one GV account, can I manually force the call to a second GV account from one the the main speed dials, a speed dial that normally uses the default GC number?
Why don't you read the manual in the 2nd post on first page of this thread? The answer is right there:TheMountainMan wrote:Can you explain what the names GV-408' GV-415' actually do in the section below? I don't see those names anyplace else in your dialplan. I know they are area codes .
You need to create two GV.new descriptors, one for your "main" GV account and the other for your "occasional" GV account. Save the first descriptor to a constant (you'll use it as route_to 1st parameter):TheMountainMan wrote:I have 2 GV numbers, one of which is only used to call a few numbers. After I make what I think are the correct changes to this cool dialplan I always get a busy signal after about 5 seconds when dialing. I think I am getting close, but I am obviously missing something to get it all working.
All I need is to have my main GV number used for just about all calls, and a select few other calls using the other GV number.
Code: Select all
MainGV = GV.new nil, nil, ‘Google Voice / Main’,
:usr => ‘myname’, # Google login name
:pwd => ‘password’, # Google password
:cb => ‘(403) 234-5678’ # Callback number
Code: Select all
GV.new '*1', nil, ‘Google Voice / Occasional’,
:usr => ‘myname1’, # Google login name
:pwd => ‘password1’, # Google password
:cb => ‘(403) 234-7722’ # Callback number
Code: Select all
when /(^1([2-9]\d\d)[2-9]\d{6})/ # North America
@num = $1 # Truncate to 11 digits
route_to MainGV