From: Jochem Smit Date: 2010-07-22T19:17:02+09:00 Subject: 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 # 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 # # # 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" => "303@sip.blueface.ie", "1" => "**500@voxalot.com" } Speeddial = { "9901" => "613@fwd.pulver.com", # Echo test USA "9949" => "enum-echo-test@sip.nemox.net", # Echo test Germany "9944" => "sip:904@mouselike.org", # Call quality test UK "303" => "303@sip.blueface.ie", # Calls speaking time @ blueface "612" => "612@fwd.pulver.com" # 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" => "homephone@asip.com&mobilephone@asip.com" # # 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" => "homephone@asip.com!10#mobilephone@asip.com!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" => "mobilephone@asip.com&homephone@asip.com", # "21:30-23:00" => "homephone@asip.com", # "23:00-6:00" => "decline", # "6:00-17:30" => "officephone@asip.com!10#homephone@asip.com!5", # "$unavailable" => "mobilephone@asip.com", "$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 !@enum.to_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 ) 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 -- Posted via http://www.ruby-forum.com/.