Flexible table-controlled dialplan, Version 2

Catalog of dial plans
MikeTelis
Posts: 1582
Joined: Wed Jul 30, 2008 6:48 am

Re: Flexible table-controlled dialplan, Version 2

Post by MikeTelis » Tue Jul 27, 2010 3:54 pm

...they have allocated hundreds of 1-403-222-xxxx phone numbers
Oh, it explains everything, thank you!

Jochem
Posts: 5
Joined: Sun Jul 25, 2010 7:39 am

Re: Flexible table-controlled dialplan, Version 2

Post by Jochem » Sun Aug 08, 2010 12:59 pm

Ok, I want to use this dialplan, so I did some minor changes to add my providers and stuff. Now I get a 500 Dial plan syntax error in my trace.

Can someone tell me how I properly debug ruby code, because a syntax error without a line number is a bit hard to discover when you have close to no knowledge of ruby.

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

MikeTelis
Posts: 1582
Joined: Wed Jul 30, 2008 6:48 am

Re: Flexible table-controlled dialplan, Version 2

Post by MikeTelis » Sun Aug 08, 2010 1:32 pm

Usually Console trace shows line number but of course, You'd be better off installing Ruby on your computer.

Anyway, the problem is here:

12voip = VSP.new '#0', '${EXTEN}@12voip', '12voip (default)'

Ruby constant must begin with uppercase letter, not with a digit. So, it should be something line OneTwoVoip instead of 12voip, and You'd better use constant Budgetphone instead of budgetphone (variable). You need to make corresponding changes to your code in selectVSP() method, too.

Jochem
Posts: 5
Joined: Sun Jul 25, 2010 7:39 am

Re: Flexible table-controlled dialplan, Version 2

Post by Jochem » Sun Aug 08, 2010 2:07 pm

Thanx, seems to work.

MikeTelis
Posts: 1582
Joined: Wed Jul 30, 2008 6:48 am

Re: Flexible table-controlled dialplan, Version 2

Post by MikeTelis » Sun Aug 08, 2010 2:30 pm

You're welcome! I'm thinking of adding a special note about constant names...

TheMountainMan
Posts: 24
Joined: Tue May 04, 2010 3:22 pm

Re: Flexible table-controlled dialplan, Version 2

Post by TheMountainMan » Mon Aug 16, 2010 6:19 pm

Hi Mike,

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?

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?

Thanks

MikeTelis
Posts: 1582
Joined: Wed Jul 30, 2008 6:48 am

Re: Flexible table-controlled dialplan, Version 2

Post by MikeTelis » Mon Aug 16, 2010 6:50 pm

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?
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).
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?
Yes, speed dial entries are processed as if you dialed the number manually. It is documented, check out the Configuration example section:

Please note that GV accounts can be selected by prefixes, too (*#1, *#2, *#3) and I use this in Speeddial table. There are 3 entries for calling GV Voice Mail. As you probably know, it’s impossible to call your own Google Voice number using GV account to which it belongs. That’s why I use GV account with San Francisco number to call GV number in San Jose, and I select this account using *#2 prefix.

Back to the first question, it's possible to create two (or more) different GV.new descriptors for the same Google Voice account which would differ by callback number(s) and/or the order in which callback numbers are picked (random/seq). Then you could select different callback numbers or their order by a prefix.

TheMountainMan
Posts: 24
Joined: Tue May 04, 2010 3:22 pm

Re: Flexible table-controlled dialplan, Version 2

Post by TheMountainMan » Tue Aug 17, 2010 3:35 pm

HI Mike,

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 .


SanJose = GV.new '*#1', nil, 'GV-408', :account => SJaccount, :repeat => 3, :rand => true
Frisco = GV.new '*#2', nil, 'GV-415', :account => SFaccount, :repeat => 2

I know that these lines are calling for a speed dial, but I am not sure how to change them for my situation. 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.

Thanks

MikeTelis
Posts: 1582
Joined: Wed Jul 30, 2008 6:48 am

Re: Flexible table-controlled dialplan, Version 2

Post by MikeTelis » Tue Aug 17, 2010 8:32 pm

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 .
Why don't you read the manual in the 2nd post on first page of this thread? The answer is right there:

Optional Name parameter is merely a comment; it will appear in the Console trace.
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.
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):

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
Note that I did not assign any prefix to this GV account. This is because you'll route all the calls without a prefix to it.

The "occasional" GV account will be selected by a prefix (say, *1). You will not use it in selectVSP and therefore, you don't need to save descriptor into a constant:

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
In your selectVSP() method route all the calls with country code 1 to your main GV account:

Code: Select all

    when /(^1([2-9]\d\d)[2-9]\d{6})/                 # North America
      @num = $1                                      # Truncate to 11 digits
      route_to MainGV
In result, when you dial a number without any prefix, your calls will be routed thru main GV account. If you want to use the other GV account, dial *1 before the number.

Busy signal shortly after dialing usually means a syntax error in your dialplan. Go to the console; you should see the line number there. Check it and you'll find the error :)

TheMountainMan
Posts: 24
Joined: Tue May 04, 2010 3:22 pm

Re: Flexible table-controlled dialplan, Version 2

Post by TheMountainMan » Thu Aug 19, 2010 11:03 pm

Hi Mike,

I finally have your Flexible table-controlled dialplan working pretty good for me for the most part. The one thing I am still working on is using the other GV line with it. I am able to get the phone that's connected to the other GV line to ring, but then it stops ringing and goes dead, then starts ringing again but it never connects. Thanks to the use of the console, I have found and corrected most of my errors. I have a Linksys PAP2t, and for reasons unknown to me, line one of that device will never bind with SS no matter what. Line 2 binds fine, so that's the big reason I am wanting to be able to get both lines working via SS on line #2 of my ATA. Line 1 of the ata works fine with Sipgate without SS in the mix.

BTW, I notice a BIG improvement in the callback working with this dialplan versus the simple dialplan I have been using. You certainly are a genius with this stuff!

Thanks for all the help.

Post Reply