Method error

I use sipsorcery for my SIP-calls, which uses a ruby dialplan.

The dialplan i got from
http://forum.sipsorcery.com/viewtopic.php?f=15&t=826 raises an exception
when I make an outgoing call.

Unfortunately, my ruby knowledge is very limited. Can someone tell
what’s wrong?

Exception:

DialPlan=> ** Error: undefined method `fixupNumber’ for
#TNumber:0x0000370

Thanks in advance.

ruby code:

#Ruby
# Original by MikeTelis

(http://www.mysipswitch.com/forum/viewtopic.php?t=706)
#
# Adapted by Mike Green as following:
#

# ******************************* Configuration

# This section is where you can change the behavior of all the

functions.
# Change these according to your needs.
#


# Specifies the host name required to place a local (MSS to MSS)

call.
#
# LocalDomain = “sip.mysipswitch.com
LocalDomain = “sip.mysipswitch.com

# Specifies the canonical / IP host name(s) of the local MSS server.

Used to
# determine if a call is MSS to MSS, or MSS to PSTN/URI
#
# Domains = [LocalDomain, “213.200.94.182”]
Domains = [LocalDomain, “213.200.94.182”]

# It is possible to define your own "local" area code for making MSS

to MSS
# calls. Dialing any number preceeded by the value define will route
a call
# internally, instead of making a call to a PSTN. If you do not wish
to use
# this feature, leave it blank.
#
# LocalAreaCode = “”
LocalAreaCode = “026”

# Specifify if a 9 needs to be dialed to make a call outside of the

local
# MSS work. If enabled, a call to 5551212 would be routed to
[email protected](LocalDomain)
# and a call to 95551212 would be routed to
[email protected](a_sip_provider).
#
# This function cannot be combined with the LocalAreaCode option.
#
# DialNineOut = true
DialNineOut = false

# Enabling this option forces the User ID to be used the caller ID

(if supported
# by the provider).
#
# UserIDAsOutgoingCallerID = false
UserIDAsOutgoingCallerID = false

# Your local country code. This is used to replace the leading 0.

For example,
# a number dialied as 0203112233 in the Netherlands would be
converted to
# 31203112233 if MyCountryCode = “31”.
#
# For North America (Country Code 1), the number will be prepended
to the
# dialed number, unless it is already present. Ie., 2125551212 will
become
# 12125551212.
#
# Use MyCountryCode = “” if no conversion is desired.
#
# MyCountryCode = “”
MyCountryCode = “31”

# The Time Zone offset based from GMT. Ie., London is 0, Paris is

+1, New York
# is -5 and Kolkata is +5:30.
#
# !NOTE: It does not account for Daylight Savings, as it depends per
country
# (and the server on which MySipSwitch is deployed)
#
# If unsure, visit
http://www.timeanddate.com/worldclock/difference.html
#
# MyTimeZone = “-5”
# MyTimeZone = “+5:30”
MyTimeZone = “+1”

# Specify whether ENUM lookup should be used as the preferred method

of calling.
#
# You can override this function by dialing the number with *9
#
# UseENUM = false
UseENUM = true

# Default Callback Number. This number will be used in case the

bridge/callback
# option is used, but no 2nd number was dialed.
#
# If the entry is empty, the default will be “yourself”, as in
# sip:[email protected]
#
# DefaultCallbackNumber = “”
DefaultCallbackNumber = “”

# Specify how long one particular number should ring until the next

number in
# the list is dialed.
#
# DefaultDelay = 10
DefaultDelay = 10

# Speed Dial numbers.
#
#   Syntax: "(number)" => "(actual number or URI)"
#
# NOTE: It is valid to use special functions in Speed Dial!
#
# Speeddial = { "0" => "[email protected]", "1" =>

“**[email protected]” }
Speeddial = {
“9901” => “[email protected]”, # Echo test USA
“9949” => “[email protected]”, # Echo test Germany
“9944” => “sip:[email protected]”, # Call quality test UK
“303” => “[email protected]”, # Calls speaking time @
blueface
“612” => “[email protected]” # Calls speaking time @ pulver
}

# Providers table.
#
#   Syntax: "Key" => "[Prefix]@Provider"
#
#   Where:
#      Key         A single digit, 0 being the default.
#      Prefix      Optional prefix that needs to be added to the

dialed number.
# Provider Provider by name, as listed in MySipSwitch
configuration.
#
# You can override the provider by dialing a number with the prefix
*1 and then
# the key. For example:
#
# *1412125553456 dials 12125553456 using the provider at key 4
#
# VSPtable = { “0” => “@ provider1”, “1” => “@ provider2” }
VSPtable = {
“0” => “@12voip”, # default provider (prefix numbers with 00)
“1” => “@budgetphone” # budgetphone
}

# Provider rules.
#
#   Syntax: "Rule" => "Provider"
#
#   Where:
#      Rule      A regular expression (Regexp) to match against a

number
# Provider Provider by key, as listed in VSPtable above
#
# NOTE: Use a double backslash for a single one!
#
# VSPRules { “^\*1” => “0”, “^\*2” => “1” }
VSPRules = {
“^#{MyCountryCode}|^0” => “0” # A national (local)
number

“^[1]800|866|877|888\d{7,7}$” => “4”, # North America Toll

Free numbers

“^1[2-9]\d{9,9}$” => “2” # North America

}

# Answer rules.
#
#   Syntax: "Rule"   => "Options"
#
#   Where (CaSe Senitive!):
#      Rule      * Times between which calls will be answered, OR
#               * "unavailable" : the rule when the user is

unavailable, OR
# * “default” : the default rule, for which none of
the other
# rules match.
# Options * “decline” : to decline the call with this
rule, OR
# * One or more numbers to call.
#
# Multiple numbers seperated by a ‘&’ sign will be called
simultaneously. All #
# phones will ring, until one of them is answered.
#
# Example: “12:00-14:00” =>
[email protected]&[email protected]
#
# Multiple numbers seperated by a ‘#’ will be called in the order
# listed. The numbers can be limited to a maximum “wait for answer”
time by
# adding a ‘!’ sign followed by the amount of seconds (15 seconds is
the
# internal limit).
#
# Example: “12:00-14:00” =>
[email protected]!10#[email protected]!10”
# This will call “homephone”, ringing up to 10 seconds and if not
answered will
# proceed to call “mobilephone”, also ringing for up to 10 seconds
AnswerRules = {

“17:30-21:30” => “[email protected]&[email protected]”,

“21:30-23:00” => “[email protected]”,

“23:00-6:00” => “decline”,

“6:00-17:30” =>

[email protected]!10#[email protected]!5”,

“$unavailable” => “[email protected]”,

   "$default"      =>   "#{sys.Username}@local"

“18885551212” => “*3callback1#callback2”

}

# Excluded Prefixes. Provides a safeguard against accidentally

calling premium
# numbers.
#
# Syntax: “(number)”
#
# Where:
# (number) Any number(s) at the start of a dialed number
#
# The numbers need to start with the country code. You can also use
this to
# block entire numbers from being dialed. To override this function,
dial the
# number with *2.
#
# *219005553456 dials 19005553456, bypassing the safeguard
#
# ExcludedPrefixes = [ “118118”, “411”, “1900” ]
ExcludedPrefixes = [
“1900”, “1976”, # USA Premium
“449”, “4455”, “44870”, “44871”, # UK Premium
“44844”, “44845”, # UK Local Premium
“4470”, # UK Personal Premium
“438”, “439”, # Austria Premium
“327”, “3290”, # Belgium Premium
“451”, “45501”, “45502”, “45503”, # Denmark Premium (…)
“45701”, “45702”, “4580”, “4590”, # Denmark Premium
“337”, “339”, # France Premium
“491”, “49900”, # Germany Premium
“391”, “392”, “394”, “395”, # Italy Premium (…)
“396”, “397”, “398”, “399”, # Italy Premium
“3114”, “3163”, “3168”, “3169”, # Netherlands Premium (…)
“318”, “319”, # Netherlands Premium
“4839”, “4820”, “4870”, “4880”, # Poland Premium
“46900”, “46939”, “46944”, # Sweden Premium
“41900”, “41901”, “41906” # Switzerland Premium
]

# ********************************** DEFINES

# For basic functionality of this plan, it should not be neccesary

to
# edit the code below.
#


# DO NOT CHANGE THE TWO LINES BELOW!
Sys = sys
Req = req

def fixupNumber(aNumber)
   number = String.new(aNumber =~ /^(.+)@(.+)$/ ? $1.to_s : aNumber)
   host = $2.to_s.downcase
   port = host.sub!(/\:(\d+)/, "") ? $1 : ""

   if (host == "local")
      # It's a MSS user -> MSS user call
      number << "@" << LocalDomain

   elsif (host == "") or (Domains.find{|x| x.downcase == host})
      # It's a MSS user -> PSTN call, unless LocalAreaCode is

matched.
# A LocalAreaCode is not valid if a 9 needs to be dialed for
outgoing calls.

      if (!LocalAreaCode.empty?) and (!DialNineOut)
         # We have a LocalAreaCode, but we'll also check in case it

was
# dialed with the country code (if specified)
LACC = LocalAreaCode.gsub(/^0/, MyCountryCode)

         if number.sub!(/^#{LocalAreaCode}/,"") or

(number.sub!(/^#{LACC}/, “”) unless MyCountryCode.empty?)
number << “@” << LocalDomain
end
elsif DialNineOut
number << “@” << LocalDomain unless number.sub!(/^9/, “”)
end

   else
      # It's a MSS -> URI call

      number << "@" << host
   end

   number << ":" << port unless port.empty?

   return number
end

# TNumber class
#
# Container for each particular number, with a seperate handler
#
class TNumber
   attr_reader :number, :provider, :override, :enum, :useenum,

:timeout
attr_writer :number, :provider, :override, :enum, :useenum,
:timeout

   def initialize(aNumber)
      @number = fixupNumber(aNumber)
#      @number = Speeddial[@number] if (Speeddial[@number])

      @provider = ""
      @override = false   # Used for overriding excluded prefixes
      @useenum = UseENUM
      @enum = ""         # Initialize ENUM var

      @timeout = extractDialTimeout(@number)
   end

   # Prepares the number for dialing.
   #
   def prepare
      return 0,"" if is_URI?

      # sub! below removes prefixes:
      #  '+' - international format
      #   00 - European style international prefix (00)
      #  011 - US style international prefix (011)
      #  810 - Russian style international prefix (810)

       unless @number.sub!(/^(\+|00|011|810)/,"")
         # Convert a national number to an international number
         case MyCountryCode
            # North America
            when "1"
               # Prepend a North American number with the country

code,
# unless it already starts with the country code
@number = MyCountryCode + @number if @number =~
/^[^1]/

            # Russia (From MikeTelis' original code)
            when "7"
               case @number
                  when /^82\d{7,7}$/
                     @number = "7496" + @number[2..-1]
                  when /^8/
                     @number[0] = "7"
                  when /^[1-9]\d{6,6}$/
                     @number = "7495" + @number
               end

          else
            # Apply default rule (replace 0 with country code),

unless
# MyCountryCode is not specified.

            @number.sub!(/^0/, MyCountryCode) unless

MyCountryCode.empty?

          end
        end

      # Check if we can override the prefix, and if not, see if it's

in
# the list of exluded prefixes.
if !@override and hasExludedPrefix = ExcludedPrefixes.find
{|prefix| @number =~ /^#{prefix}/ }
return 403,“Numbers starting with #{hasExludedPrefix} are
not permitted”
end

        # If the provider was not specified by the user, we will

decide it here
if @provider.empty?
getEnum if @useenum
selectProvider
end

        return 0,""
   end

   # Selects the provider based on the number.
   #
   def selectProvider
      # Fix North American numbers that are longer than usual
      @number = $1 if @number =~ /(^1([2-9]\d\d)\d{7,7})/

        @provider = "0" # Set the default provider first

      VSPRules.each { |aRule|
         if @number =~ /#{aRule[0]}/
            @provider = aRule[1]
            break
         end
      }
   end

   # Extracts the timeout specified with the number
   def extractDialTimeout(aNumber)
      return aNumber.sub!(/\!(\d{1,2})/, '') ? $1.to_i : 0
   end

   # Are we using the ENUM to call?
   #
   def use_ENUM?
      return [email protected]_s.empty?
   end

   # Is it a full URI instead of a number?
   #
   def is_URI?
      return @number =~ /@/
   end

   # Expands the number based on the providers' template, to be used
   # in Sys.Dial
   #
   def expandNumber
      if tpl = VSPtable[@provider]
         return tpl.sub(/\s*@\s*/) {|x| @number+"@"}
      else
         return @number
      end
   end

   # Sets the enum variable, if an ENUM is available
   #
   def getEnum
      Sys.Log("Attempting to retrieve ENUM for #{@number}")
      @enum = "" unless @enum =

Sys.ENUMLookup("+#{@number}.e164.org").to_s
Sys.Log(“Result ENUM lookup: ‘#{@enum}’”)
end

   private :getEnum
end

# TDialList class
#
# Controls the numbers and call methods
#
class TDialList
   attr_reader :bridgenumbers

   MaxItems = 10

   def initialize
      @numbers = Array.new

      @bridgenumbers = false
   end

   # Add a number to the list
   #
   def append(aNumber)
      return 500,"Exceeded limit or class error" if (@numbers.length

== MaxItems) or (aNumber.class.name != “TNumber”)

      if !aNumber.is_URI?
         Code,Reason = processSpecialFunctions(aNumber)
         return Code,Reason if Code > 0
      end

      @numbers.push(aNumber)

      return aNumber.prepare
   end

   # How many numbers do we have?
   #
   def length
      @numbers.length
   end

   def deleteFirst
      @numbers.shift
   end

   def deleteLast
      @numbers.pop
   end

   # Access to the numbers, by index (key) or phone number
   #
   def [](key)
      return @numbers[key] if key.kind_of?(Integer)
      return @numbers.find { |aNumber| key == aNumber.number }
   end

   # Handles any special function present in the number
   #
   # Special Functions:
   #
   #   *1(key)      Override the provider with the one specified in

(key)
# *2 Bypass premium number safeguard
# *3 Bridge (callback) the numbers
# *9 Override ENUM setting (enable if disabled, and
vice versa)
# *0 Trace the phone call
# ** Dial a *
#
def processSpecialFunctions(aNumber)
while aNumber.number.sub!(/^*([^*])/, “”)
case $1
# Special function 1 - Override provider
when “1”
aNumber.number.sub!(/^(.)/, “”)
aNumber.provider = $1;

                return 400,"Invalid provider selected #{$1}" if

VSPtable[aNumber.provider].to_s.empty?

                Sys.Log("! Overriding service provider for

#{aNumber.number} to #{aNumber.provider}, template
#{VSPtable[aNumber.provider]}")

            # Special function 2 - Bypass safeguard
            when "2"
               Sys.Log("! Bypassing premium number safeguard for

#{aNumber.number}")
aNumber.override = true

            # Special function 3 - Bridge the numbers in the list

(will take only first two)
when “3”
Sys.Log("! Request to bridge #{aNumber.number}")
@bridgenumbers = true

            # Special function 0 - Trace this particular call
            when "0"
               Sys.Log("! Requested a trace for this call.")
               Sys.Trace = true

            # Special function 9 - Override the use of ENUM
            when "9"
               Sys.Log("! Override use of ENUM")
               aNumber.useenum = !aNumber.useenum

            # Unknown function
            else
               Sys.Log("! Unknown function in dial sequence found:

#{$1} - Ignoring")
end
end

       # A double ** gets treated as a single * to dial
       aNumber.number.sub!(/^\*/,"")

       return 0,""
   end

   # Make the call
   #
   def dial(*args)
      if Sys.Out and UserIDAsOutgoingCallerID
         Req.Header.From.FromName =

Req.Header.From.FromURI.User.to_s
end

      if @bridgenumbers
         # If no default callback number is set, use local user
         DefaultCallbackNumber = "#{Sys.Username}@local" if

DefaultCallbackNumber.empty?

         number1 = @numbers[0].use_ENUM? ? @numbers[0].enum :

@numbers[0].expandNumber
number2 = (length < 2) ? DefaultCallbackNumber :
@numbers[1].use_ENUM? ? @numbers[1].enum : @numbers[1].expandNumber

         Sys.Log("* Initiating bridge between #{number2} and

#{number1}")
Sys.Callback(number2, number1, 3) # 3 seconds before
dialing
else
# If there’s more than one number in the list, try each one
until
# one is picked up. (No ring timeout for single number)

         ringtimeout = 0
         @numbers.each { |n|
               numbertd = n.use_ENUM? ? n.enum : n.expandNumber

               # If there's more than 1 number to call, set the

timeout if present.
ringtimeout = n.timeout == 0 ? DefaultDelay :
n.timeout if (length > 1)

               if ringtimeout == 0
                  Sys.Log("* Dialing #{numbertd}")
                  Sys.Dial(numbertd)
               else
                  Sys.Log("* Dialing #{numbertd} with timeout

#{ringtimeout}")
Sys.Dial(numbertd, ringtimeout)
end
}
end

      return status
   end

   # Grab the status from the LastDialied list (Another great piece

by MikeTelis)
#
def status(li=0)
if (ptr = Sys.LastDialled[li]).nil?
return 487,“Cancelled by MSS”
else
ptr = ptr.TransactionFinalResponse # SIPTransaction &
SIPResponse
return ptr.StatusCode,ptr.ReasonPhrase
end

      return 0,""
     end
end

# Inbound Call Manager. Uses a rule-based table to determine where
# an incomming call should be routed to, based on time of day and
# availability.
#
class TInboundMgr
   attr_reader :time

   @@localuser = "#{Sys.Username}@local"

   def initialize
      @time = Time.now.gmtime   # In case this SIP Switch is

deployed outside Dublin

      # Some timezones have half hour and 45 minute differences.

Account for this.
# It does NOT account for daylight savings!
mtz = MyTimeZone.split(":")
mtz[0] = ‘0’ if mtz[0].to_s.empty?

      @time += mtz[0].to_i * 3600
      @time += mtz[0][0].chr == "-" ? ("-#{mtz[1]}".to_i * 60) :

(mtz[1].to_i * 60) unless mtz[1].to_s.empty?

      Sys.Log("Using #{@time} to determine Answer Rule")

      callerid_name = Req.Header.From.FromName.to_s

      # Some SIPs simply leave the name blank, which some softphones

don’t
# appreciate
callerid_name = Req.Header.from.FromURI.User.to_s if
callerid_name =~ /^$|\D/

      # Replace any characters other than A..Z or numbers. This

could happen
# with SIPBroker for instance ("Calgary,AB
sip:[email protected])
callerid_name.gsub!(/[^a-zA-Z0-9]/, " ");

      # Add a 1 if it's a US 9-digit number.
      callerid_name = ("1" + callerid_name) if callerid_name =~

/^[2-9]\d{9,9}$/

      # Be careful with changing headers. You could end up spending

a lot of
# time debugging, checking why a call isn’t going through!
(Been there!)
Req.Header.from.FromURI.User = callerid_name
Req.Header.From.FromName = callerid_name

      @NumbersToCall = TDialList.new
   end

   def Answer
      # Grab the rules
      tr = getTimeRule
      ur = getUnavailableRule

      # Insert unavailable rule, if needed
      unless ur.empty?
         tr.gsub!(/#{@@localuser}/, ur) if !Sys.IsAvailable
      end

      Sys.Log("Answer Rule used: #{tr}")

      # Start dialing each number in the rule, unless rule is set to

decline
unless tr == “decline”
tr.split("#").each { |number|
Code,Reason = @NumbersToCall.append(TNumber.new(number))

            # If there was an error adding the number, don't let it

break
# the answer rules, just remove it from the numbers to
call.
@NumbersToCall.deleteLast if Code > 0
}

         @NumbersToCall.dial
      end

      # If we end up here, that means the numbers dialed weren't

answered or
# the rule is set to decline the call.
Sys.Log(“Exhausted answer options (or decline rule).”)
return 408, “#{@@localuser} is not available”
end

   # Returns the time rule, based on the current sys time. Will

return
# a default rule if no time rule exists.
#
def getTimeRule
result = ‘’

      AnswerRules.each { |rule|
         # If the rule contains a from-to time format
         if

rule[0].match(/^(\d{1,2}):(\d{2,2})-(\d{1,2}):(\d{2,2})$/)
# From what time do we start to check?
t1 = Time.gm(@time.year, @time.month, @time.day,
$1.to_i, $2.to_i)

            # And until what time? (We decrease the time by a

second)
t2 = Time.gm(@time.year, @time.month, @time.day,
$3.to_i, $4.to_i)
t2 -= 1

            # If the start time is beyonthe end time, decrease start

time
# by 24 hours
t1 -= (3600 * 24) if t1 > t2

            result = rule[1] if @time.between?(t1,t2)
         else
            unless rule[0] =~ /^\$/
               result = rule[1] if Req.Header.From.FromName.to_s =~

/#{rule[0]}/
end
end
}

      return result if !result.to_s.empty?

      # If we end up here, it means we couldn't find a matching time

rule.
# So we look for a default rule. If none, we make a default
rule.
if AnswerRules["$default"]
return AnswerRules["$default"]
else
return @@localuser
end
end

   # Returns the rule for handeling unavailable user. Will return
   # empty if no rule available.
   #
   def getUnavailableRule
      return AnswerRules["$unavailable"].to_s
   end
end

# A helper for figuring out if the number is:
#   1) a local call (MSS user -> MSS user)
#   2) an URI call (MSS user -> SIP Provider user)
#   3) a dialed phone call (MSS user -> PSTN)
#
def fixupNumber(aNumber)
   number = String.new(aNumber =~ /^(.+)@(.+)$/ ? $1.to_s : aNumber)
   host = $2.to_s.downcase
   port = host.sub!(/\:(\d+)/, "") ? $1 : ""

   if (host == "local")
      # It's a MSS user -> MSS user call
      number << "@" << LocalDomain

   elsif (host == "") or (Domains.find{|x| x.downcase == host})
      # It's a MSS user -> PSTN call, unless LocalAreaCode is

matched.
# A LocalAreaCode is not valid if a 9 needs to be dialed for
outgoing calls.

      if (!LocalAreaCode.empty?) and (!DialNineOut)
         # We have a LocalAreaCode, but we'll also check in case it

was
# dialed with the country code (if specified)
LACC = LocalAreaCode.gsub(/^0/, MyCountryCode)

         if number.sub!(/^#{LocalAreaCode}/,"") or

(number.sub!(/^#{LACC}/, “”) unless MyCountryCode.empty?)
number << “@” << LocalDomain
end
elsif DialNineOut
number << “@” << LocalDomain unless number.sub!(/^9/, “”)
end

   else
      # It's a MSS -> URI call

      number << "@" << host
   end

   number << ":" << port unless port.empty?

   return number
end

# *********************************** MAIN

# This is where all the action happens, depending on incomming or

outgoing calls
#


begin
   timeit = Time.now

   Sys.Log("** Call from #{Req.Header.From.ToString} to

#{Req.URI.ToString} **")

   Code = 500
   Reason = 'Internal error'

   if Sys.In   # Incoming call...
      InboundManager = TInboundMgr.new
      Code,Reason = InboundManager.Answer

   else # Outbound call ...
      dest = String.new(Req.URI.ToString.to_s)
      dest.sub!(/^sip:/, "") # strip "sip:"
      dest.gsub!(/%../) {|x| x[1,2].to_i(16).chr}   # Convert %hh

into ASCII

      # Is it a Speeddial? (Nested speeddial no longer supported,

“adest” is
# simply a temporart placeholder.
adest = fixupNumber(dest)
dest = Speeddial[adest] if (Speeddial[adest])

      # Create a new Dial List
      NumbersToCall = TDialList.new

      # Grab the phone number(s), seperated by a '#'
      dest.split("#").each { |number|
         # Add the number.
         Code,Reason = NumbersToCall.append(TNumber.new(number))

         # Did anything bad happen while adding this number?
         break if Code > 0
      }

      # Will stop script here if Code >= 200, ie error adding number
      Sys.Respond(Code, Reason) if Code > 0

      Code,Reason = NumbersToCall.dial
   end

   Sys.Respond(Code, Reason)

rescue
   # Gives a lot more details at what went wrong. Don't worry about

the Thread Exit.
Sys.Log("** Error: " + $!) unless $!.to_s =~ /Thread was being
aborted./

ensure
   Sys.Log("Time to complete Dial Plan: " + (Time.Now -

timeit).to_s)
end

This forum is not affiliated to the Ruby language, Ruby on Rails framework, nor any Ruby applications discussed here.

| Privacy Policy | Terms of Service | Remote Ruby Jobs