Method error

J

Jochem Smit

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
5551212@(LocalDomain)
# and a call to 95551212 would be routed to
5551212@(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" => "(e-mail address removed)", "1" =>
"**[email protected]" }
Speeddial = {
"9901" => "(e-mail address removed)", # Echo test USA
"9949" => "(e-mail address removed)", # Echo test Germany
"9944" => "sip:[email protected]", # Call quality test UK
"303" => "(e-mail address removed)", # Calls speaking time @
blueface
"612" => "(e-mail address removed)" # 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" =>
"(e-mail address removed)&[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" =>
"(e-mail address removed)!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" => "(e-mail address removed)&[email protected]",
# "21:30-23:00" => "(e-mail address removed)",
# "23:00-6:00" => "decline",
# "6:00-17:30" =>
"(e-mail address removed)!10#[email protected]!5",
# "$unavailable" => "(e-mail address removed)",
"$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, :eek:verride, :enum, :useenum,
:timeout
attr_writer :number, :provider, :eek:verride, :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
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,580
Members
45,054
Latest member
TrimKetoBoost

Latest Threads

Top