These habits include dialing 7-digit numbers for local calls and 10-digit for long distance (3-digit area code followed by 7-digit number). This dialplan will take care of converting these numbers; it will also accept 11-digit, such as 1-212-555-1212. If you, by chance, try a collect call (0-212-555-1212), it will be replace leading 0 with 1 thus handling it as a regular call.
Needless to say, all domestic calls are routed via GoogleVoice and therefore, they are free.
Should you dial a "long number" (for example, 1-800-CITIBANK), the code will truncate it. This is necessary because, unlike regular ma bell, VoIP operators do not truncate long numbers and in result, can't connect the call.
There is a string attached, though. If you want to place an international call, you must dial 011 before the number (for example, 011-33-6-12-21-33-44). The dialplan will also accept 00 or '+' in lieu of 011.
Okay, let's get to the code:
Code: Select all
#Ruby
Area = '415' # My area code
Tz = -8 # Time zone (GMT-8)
# Speed dial entries. Format: "key" => "number"
Speeddial = {
'0' => '234-5678', # Home
'1' => '1 408 234-1212', # Mom
'411' => '411@Gizmo', # Directory
'911' => '911@F9', # Emergency
'303' => '303@sip.blueface.ie', # Blueface speaking clock
'612' => '612@fwd.pulver.com', # FWD speaking clock
}
# Serviced domains, must be in lowercase!
Domains = ['sipsorcery.com','174.129.236.7']
# Excluded Prefixes. Provides a safeguard against accidentally calling premium
# numbers. Accepts both strings and patterns, spaces ignored
ExcludedPrefixes = [
' 1 (900 | 976 | 809)', # USA Premium
'44 (9 | 55 | 87 (0|1))', # UK Premium
'44 84 (4|5)', # UK Local Premium
'44 70', # UK Personal Premium
'43 (8|9)', # Austria Premium
'32 (7|90)', # Belgium Premium
'45 (1 | 50 (1|2|3) | 70 (1|2))', # Denmark Premium
'45 (8|9) 0', # Denmark Premium (...)
'33 (7|9)', # France Premium
'49 (1 | 900)', # Germany Premium
'39 (1 | 84 | 89)', # Italy Premium (...)
'31 (14 | 6 (3|8|9) | 8 | 9)', # Netherlands Premium (...)
'48 (39 | (2|7|8) 0)', # Poland Premium
'46 9 (00 | 39 | 44)', # Sweden Premium
'41 90 (0|1|6)', # Switzerland Premium
]
# Providers: "key" => "template". Template format: prefix@Provider
VSPtab = {
'0' => '00 @ F9', # Future-nine default route
'2' => '02 @ F9', # Future-nine grey route
'3' => '03 @ F9', # Future-nine white route
'4' => '04 @ F9', # Future-nine premium route
'5' => '@ Gizmo', # Gizmo5
}
# ******************** G o o g l e V o i c e *****************************
# !!!!!!!!!!!! Make sure to enter your credentials below !!!!!!!!!!!!!
def googleVoice
sys.GoogleVoiceCall('mygmail@gmail.com','mygmailpass','17471234567',@num,'.*',7)
end
# ******************** s e l e c t V S P *******************************
def selectVSP # VoIP provider selection
# Reject calls to premium numbers unless VSP was forced
ExcludedPrefixes.each { |p| p.gsub!(/\s*/,''); sys.Respond(503,"Calls to #{$1}* not allowed") if @num =~ /^(#{p})/ }
case @num
when /^1([2-9]\d\d)/ # North America
case $1 # check area code
# when "808", "907", "867" # AK, HI, Canada Yukon
# route(4,"Destination - Alaska, Hawaii, Yukon")
when /(800|866|877|888|747)/ # Toll-free and 1-747 calling
route(5,"Destination - US toll-free or G5")
else
googleVoice
sys.Log("GoogleVoiceCall failed, routing thru F9")
route(4,"Destination - North America")
end
when /^972(5|6)/ # Israel mobile
route(3,"Destination - Israel mobile")
else
route(0,"Default route applied")
end
end
# ************************** C A L L S W I T C H **********************
def callswitch(num,*args)
route # Initialize vars
@num = num unless @num = Speeddial[num] # If there is speed dial entry for it...
@l = "URI dialing: #{@num}" # Assume URI dialing
unless @num =~ /@/ # If we already have URI, skip all number processing
@num.gsub!(/%(..)/) {$1.to_i(16).chr} # Convert %hh into ASCII
if @num =~ /^#(.)(.*)/ # If number starts with '#'
@p = $1; @num = $2 # next char is VSP code
end
@num.gsub!(/[^0-9*+]/,'') # Delete all fancy chars (only digits, '+' and '*' allowed)
# sub! below removes prefixes:
# '+' - international format
# 00 - European style international prefix (00)
# 011 - US style international prefix (011)
unless @num.sub!(/^(\+|00|011)/,'') # If no international prefix, process special cases below
case @num
when /^[2-9]\d{6}$/ # Local call, 7-digit number
@num = "1#{Area}#{@num}" # prefix it with country and area code
when /^[01]?([2-9]\d{9})/ # US number with or without "1" country code
@num = '1' + $1 # add country code and truncate number to 10-digit
when /^\*/ # Voxalot voicemail, echotest & other special numbers
else
sys.Respond(603,'Wrong number, check & dial again')
end
end
sys.Log("Number in ENUM format: #{@num}")
@l = "Forced routing to provider #{@p}, template '#{VSPtab[@p]}'" # Assume user explicitly selected VSP
if @p.empty? # Automatic VSP selection?
# Invoke selectVSP prior to ENUM lookup just in case we need to modify @num
route # re-initialize variables
selectVSP # Pick appropriate provider for the call
if enumuri = sys.ENUMLookup("+#{@num}.e164.org") # Check if NAPTR exists for the number
sys.Log("ENUM entry found: '#{enumuri}'") # If yes, call that URI
sys.Dial(enumuri) # if call failed, call via regular VSP.
status() # If this is not what you want, add "return"
sys.Log("Call to #{enumuri} failed (#{@reason}), will call again via regular VoIP provider")
end
end # @p.empty
end # URI
dial(*args) # dial selected number or URI
end
# ******************************* D I A L ********************************
def dial(*args)
sys.Log(@l) unless @l.empty? # for the record :)
if tpl=VSPtab[@p.to_s] # if provider is in our table
tpl.gsub!(/\s*/,'') # Remove spaces
@num = tpl.gsub(/@/,@num+'@') # Insert number before '@'
end
sys.Dial(@num,*args) # Dial
status() # We shouldn't be here! Get error code...
sys.Log("Call failed: code #{@code}, #{@reason}")
end
# ****************************** R O U T E *******************************
def route(p='', l='')
@p = p; @l = l
end
# ***************************** S T A T U S ******************************
def status
if (ptr = sys.LastDialled[0]).nil?
@code = 487; @reason = 'Cancelled by Sipsorcery'
else
ptr = ptr.TransactionFinalResponse
@code = ptr.StatusCode; @reason = ptr.ReasonPhrase
# sys.Log("#{ptr.ToString()}")
end
end
# ******************************* M A I N ********************************
begin
sys.Log("** Call from #{req.Header.From.ToString()} to #{req.URI.User} **")
t = Time.now + ((Tz+8)*60*60) # Get current time and adjust to local. SS Server is in GMT-8
sys.Log(t.strftime('Local time: %c'))
if sys.In # If incoming call...
name = req.Header.from.FromURI.User.to_s # Get caller ID
# Prepend 10-digit numbers with "1" (US country code) and remove 011 prefix (if present)
name = ('1' + name) if name =~ /^[2-9]\d\d[2-9]\d{6}$/
name.sub!(/^(011|\+)/,'')
sys.Log("FromName: '#{name}'")
# Set FromName for sys.Dial. Change FromURI when forwarding to @local, or
# else Bria won't find contact in its phonebook!
sys.SetFromHeader(name, nil, nil)
if sys.IsAvailable() # If my ATA is registered
callswitch("#{sys.Username}@local[fu=#{name}]") # forward all incoming calls to it
elsif (8..23) === t.hour # else forward calls to my home
sys.Log("#{sys.Username} is not online, forwarding call to home number...")
callswitch("0",35) # Note that '0' is in my speed dial list
end
sys.Respond(480, "#{sys.Username} Not online") # switch to voice mail
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.find {|x| x == 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
... Instructions
1. Change "Area" to your home area code and Tz to your time zone. PST is -8, EST is -6.
2. Create your speed dial entries. Note that all "short" numbers such as 411 and 911 must be listed in this table.
3. Review ExcludedPrefixes table. Each entry contains a regular expression (pattern). If the number you dialed matches the pattern, your call will be rejected with 503 code. This way you won't be charged $$$ for accidental dialing a premium number. I borrowed the list from Myatus' dialplan and reworked it replacing "exact match" with patterns. Hope that all premium numbers are listed and you won't need to change this table but if you do that, please share the change.
4. Review and edit VSPTab. You'll find description of this table and related selectVSP method in my original topic.
5. Enter your GoogleVoice credentials and desired callback number in googleVoice method.
6. Review and edit incoming call handling section of the dialplan. Most certainly you'll need to edit this part:
Code: Select all
if sys.IsAvailable() # If my ATA is registered
callswitch("#{sys.Username}@local[fu=#{name}]") # forward all incoming calls to it
elsif (8..23) === t.hour # else forward calls to my home
sys.Log("#{sys.Username} is not online, forwarding call to home number...")
callswitch("0",35) # Note that '0' is in my speed dial list
end