freeswitcher 0.4.10 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README +69 -13
- data/Rakefile +1 -1
- data/examples/play_and_get_digits.rb +34 -0
- data/lib/fsr.rb +4 -1
- data/lib/fsr/app/play_and_get_digits.rb +8 -0
- data/lib/fsr/app/playback.rb +8 -2
- data/lib/fsr/cmd/call_center.rb +156 -0
- data/lib/fsr/cmd/calls.rb +4 -4
- data/lib/fsr/cmd/channels.rb +47 -0
- data/lib/fsr/cmd/enum.rb +40 -0
- data/lib/fsr/cmd/sofia_contact.rb +2 -1
- data/lib/fsr/file_methods.rb +14 -0
- data/lib/fsr/listener/inbound.rb +27 -3
- data/lib/fsr/model.rb +11 -0
- data/lib/fsr/model/agent.rb +18 -0
- data/lib/fsr/model/call.rb +6 -41
- data/lib/fsr/model/channel.rb +13 -0
- data/lib/fsr/model/enum.rb +26 -0
- data/lib/fsr/model/queue.rb +15 -0
- data/lib/fsr/model/tier.rb +15 -0
- data/lib/rack/middleware.rb +83 -0
- data/spec/fsr/app.rb +2 -0
- data/spec/fsr/app/play_and_get_digits.rb +15 -0
- data/spec/fsr/app/playback.rb +15 -0
- data/spec/fsr/cmd/call_center.rb +58 -0
- data/spec/fsr/cmd/calls.rb +2 -2
- data/spec/fsr/cmd/channels.rb +18 -0
- data/spec/fsr/cmd/enum.rb +19 -0
- data/spec/fsr/file_methods.rb +36 -0
- data/spec/fsr/listener/inbound.rb +6 -6
- data/spec/fsr/loading.rb +1 -1
- metadata +139 -24
data/README
CHANGED
@@ -56,27 +56,83 @@ An Inbound Event Socket Listener example using FreeSWITCHeR's hook system:
|
|
56
56
|
--------------------------------------------------------------------------
|
57
57
|
|
58
58
|
#!/usr/bin/ruby
|
59
|
-
require 'pp'
|
60
59
|
require 'fsr'
|
61
60
|
require "fsr/listener/inbound"
|
62
61
|
|
63
|
-
|
64
|
-
|
65
|
-
|
62
|
+
class MyEventListener < FSR::Listener::Inbound
|
63
|
+
def before_session
|
64
|
+
# This adds a hook on CHANNEL_CREATE events. You can also create a method to handle the event you're after. See the next example
|
65
|
+
add_event(:CHANNEL_CREATE) { |e| p e }
|
66
66
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
FSR::Log.info "*** [#{event.content[:unique_id]}] Channel hangup. The event:"
|
71
|
-
pp event
|
72
|
-
end
|
67
|
+
# This adds a hook on CHANNEL_HANGUP events with a callback method.
|
68
|
+
add_event(:CHANNEL_HANGUP) { |e| channel_hangup(e) }
|
69
|
+
end
|
73
70
|
|
74
|
-
|
75
|
-
|
71
|
+
def channel_hangup(event)
|
72
|
+
p event
|
73
|
+
end
|
74
|
+
|
75
|
+
def on_event(event)
|
76
|
+
# This gets called for _every_ event that's subscribed (through add_event)
|
77
|
+
p event
|
78
|
+
end
|
79
|
+
end
|
76
80
|
|
77
81
|
|
78
82
|
# Start FSR Inbound Listener
|
79
|
-
FSR.start_ies!(
|
83
|
+
FSR.start_ies!(MyEventListener, :host => "localhost", :port => 8021)
|
84
|
+
|
85
|
+
A More Advanced Example, Publishing Events To A Web Socket:
|
86
|
+
-----------------------------------------------------------
|
87
|
+
|
88
|
+
class MyWebSocketClient < Struct.new(:reporter, :socket, :channel_id)
|
89
|
+
Channel = EM::Channel.new
|
90
|
+
|
91
|
+
def initialize(reporter, socket)
|
92
|
+
self.reporter, self.socket = reporter, socket
|
93
|
+
socket.onopen(&method(:on_open))
|
94
|
+
socket.onmessage(&method(:on_message))
|
95
|
+
socket.onclose(&method(:on_close))
|
96
|
+
end
|
97
|
+
|
98
|
+
def on_message(json)
|
99
|
+
msg = JSON.parse(json)
|
100
|
+
FSR::Log.info "Websocket got #{msg}"
|
101
|
+
end
|
102
|
+
|
103
|
+
def send(msg)
|
104
|
+
socket.send(msg.to_json)
|
105
|
+
end
|
106
|
+
|
107
|
+
def on_open
|
108
|
+
FSR::Log.info("Subscribed listener")
|
109
|
+
self.channel_id = Channel.subscribe { |message| send(message) }
|
110
|
+
end
|
111
|
+
|
112
|
+
def on_close
|
113
|
+
Channel.unsubscribe(channel_id)
|
114
|
+
FSR::Log.info("Unsubscribed listener")
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# Add the Channel to your event listener
|
119
|
+
class MyEventListener
|
120
|
+
def on_event(event)
|
121
|
+
MyWebSocketClient::Channel << event.content
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
# Start Listener within and EM.run
|
126
|
+
EM.epoll
|
127
|
+
EM.run do
|
128
|
+
server, port = '127.0.0.1', 8021
|
129
|
+
EventMachine.connect(server, port, MyEventListener, auth: 'MyPassword') do |listener|
|
130
|
+
FSR::Log.info "MyEventListener connected to #{server} on #{port}"
|
131
|
+
EventMachine.start_server('0.0.0.0'), 8080, EventSocket::WebSocket::Connection, {}) do |websocket|
|
132
|
+
MyWebSocketClient.new(reporter, websocket)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
80
136
|
|
81
137
|
|
82
138
|
An Inbound Event Socket Listener example using the on_event callback method instead of hooks:
|
data/Rakefile
CHANGED
@@ -15,7 +15,7 @@ GEM_FILES << "spec/fsr_listener_helper.rb" if Pathname("spec/helper.rb").file?
|
|
15
15
|
|
16
16
|
GEMSPEC = Gem::Specification.new do |spec|
|
17
17
|
spec.name = "freeswitcher"
|
18
|
-
spec.version = ENV["FSR_VERSION"] || FSR::VERSION
|
18
|
+
spec.version = (ENV["FSR_VERSION"] || FSR::VERSION).dup
|
19
19
|
spec.summary = 'A library for interacting with the "FreeSWITCH":http://freeswitch.org telephony platform'
|
20
20
|
spec.authors = ["Jayson Vaughn", "Michael Fellinger", "Kevin Berry", "TJ Vanderpoel"]
|
21
21
|
spec.email = "FreeSWITCHeR@rubyists.com"
|
@@ -0,0 +1,34 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require File.join(File.dirname(__FILE__), "..", 'lib', 'fsr')
|
4
|
+
require "fsr/listener/outbound"
|
5
|
+
$stdout.flush
|
6
|
+
|
7
|
+
class PlayAndGetDigitsDemo < FSR::Listener::Outbound
|
8
|
+
|
9
|
+
def session_initiated
|
10
|
+
exten = @session.headers[:caller_caller_id_number]
|
11
|
+
FSR::Log.info "*** Answering incoming call from #{exten}"
|
12
|
+
|
13
|
+
answer do
|
14
|
+
FSR::Log.info "***Reading DTMF from #{exten}"
|
15
|
+
#######################################################
|
16
|
+
## NOTE YOU MUST MAKE SURE YOU PASS A VALID WAV FILE ##
|
17
|
+
#######################################################
|
18
|
+
play_and_get_digits("/usr/local/freeswitch/sounds/music/8000/sweet.wav","/usr/local/freeswitch/sounds/music/8000/not.wav", :chan_var => rand(10000).to_s) do |read_var|
|
19
|
+
FSR::Log.info "***Success, grabbed #{read_var.to_s.strip} from #{exten}"
|
20
|
+
# Tell the caller what they entered
|
21
|
+
# If you have mod_flite installed you should hear speech
|
22
|
+
speak("Got the DTMF of: #{read_var.to_s.strip}") { hangup }
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def receive_reply(reply)
|
27
|
+
FSR::Log.info "Received #{reply.inspect}"
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
FSR.start_oes! PlayAndGetDigitsDemo, :port => 8084, :host => "0.0.0.0"
|
data/lib/fsr.rb
CHANGED
@@ -34,7 +34,10 @@ module FSR
|
|
34
34
|
end
|
35
35
|
|
36
36
|
ROOT = Pathname(__FILE__).dirname.expand_path.freeze
|
37
|
-
$LOAD_PATH.unshift(FSR::ROOT)
|
37
|
+
$LOAD_PATH.unshift(FSR::ROOT.to_s)
|
38
|
+
|
39
|
+
FSR_ROOT = Pathname(__FILE__).join("..").dirname.expand_path.freeze
|
40
|
+
$LOAD_PATH.unshift(FSR::FSR_ROOT.to_s)
|
38
41
|
|
39
42
|
# Load all FSR::Cmd classes
|
40
43
|
def self.load_all_commands(retrying = false)
|
@@ -1,8 +1,13 @@
|
|
1
1
|
require "fsr/app"
|
2
|
+
require 'fsr/file_methods'
|
3
|
+
|
2
4
|
module FSR
|
3
5
|
#http://wiki.freeswitch.org/wiki/Misc._Dialplan_Tools_play_and_get_digits
|
4
6
|
module App
|
5
7
|
class PlayAndGetDigits < Application
|
8
|
+
|
9
|
+
include ::FSR::App::FileMethods
|
10
|
+
|
6
11
|
attr_reader :chan_var
|
7
12
|
DEFAULT_ARGS = {:min => 0, :max => 10, :tries => 3, :timeout => 7000, :terminators => ["#"], :chan_var => "fsr_read_dtmf", :regexp => '\d'}
|
8
13
|
|
@@ -30,6 +35,9 @@ module FSR
|
|
30
35
|
@chan_var = arg_hash[:chan_var]
|
31
36
|
@terminators = arg_hash[:terminators]
|
32
37
|
@regexp = arg_hash[:regexp]
|
38
|
+
|
39
|
+
raise unless test_files(@sound_file, @invalid_file)
|
40
|
+
|
33
41
|
end
|
34
42
|
|
35
43
|
def arguments
|
data/lib/fsr/app/playback.rb
CHANGED
@@ -1,19 +1,25 @@
|
|
1
1
|
require "fsr/app"
|
2
|
+
require 'fsr/file_methods'
|
2
3
|
module FSR
|
3
4
|
module App
|
4
5
|
class Playback < Application
|
6
|
+
|
7
|
+
include ::FSR::App::FileMethods
|
8
|
+
|
5
9
|
attr_reader :wavfile
|
6
10
|
|
7
11
|
def initialize(wavfile)
|
8
|
-
# wav file you wish to play, full path
|
12
|
+
# wav file you wish to play, full path
|
13
|
+
test_files wavfile
|
9
14
|
@wavfile = wavfile
|
10
15
|
end
|
16
|
+
|
11
17
|
def arguments
|
12
18
|
@wavfile
|
13
19
|
end
|
14
20
|
|
15
21
|
def sendmsg
|
16
|
-
"call-command: execute\nexecute-app-name: %s\nexecute-app-arg: %s\nevent-lock:true\n\n" % [app_name, arguments]
|
22
|
+
"call-command: execute\nexecute-app-name: %s\nexecute-app-arg: %s\nevent-lock:true\n\n" % [app_name, arguments]
|
17
23
|
end
|
18
24
|
end
|
19
25
|
|
@@ -0,0 +1,156 @@
|
|
1
|
+
require "fsr/app"
|
2
|
+
module FSR
|
3
|
+
module Cmd
|
4
|
+
class CallCenter < Command
|
5
|
+
attr_accessor :cmd
|
6
|
+
def initialize(fs_socket = nil, config_type = :agent)
|
7
|
+
@fs_socket = fs_socket # FSR::CommandSocket obj
|
8
|
+
@config_type = config_type
|
9
|
+
@last_repsonse = nil
|
10
|
+
@cmd = []
|
11
|
+
end
|
12
|
+
|
13
|
+
def list_agent
|
14
|
+
["list"]
|
15
|
+
end
|
16
|
+
|
17
|
+
def last_response
|
18
|
+
responses.last
|
19
|
+
end
|
20
|
+
|
21
|
+
def responses
|
22
|
+
@responses ||= []
|
23
|
+
end
|
24
|
+
|
25
|
+
def list_tier(queue)
|
26
|
+
["list", queue]
|
27
|
+
end
|
28
|
+
|
29
|
+
def list_queue(queue=nil)
|
30
|
+
["list", queue].compact
|
31
|
+
end
|
32
|
+
|
33
|
+
def list(*args)
|
34
|
+
@listing = true
|
35
|
+
@cmd = case @config_type
|
36
|
+
when :agent
|
37
|
+
list_agent(*args)
|
38
|
+
when :tier
|
39
|
+
list_tier(*args)
|
40
|
+
when :queue
|
41
|
+
list_queue(*args)
|
42
|
+
else
|
43
|
+
raise "no such config_type #{@config_type}"
|
44
|
+
end
|
45
|
+
self
|
46
|
+
end
|
47
|
+
|
48
|
+
|
49
|
+
def set_agent(agent, field, value)
|
50
|
+
value = value.respond_to?(:each_char) ? "'#{value}'" : value
|
51
|
+
["set", field.to_s, "'#{agent}'", value].compact
|
52
|
+
end
|
53
|
+
|
54
|
+
def set_tier(agent, queue, field, value)
|
55
|
+
["set", field.to_s, queue, agent, "'#{value}'"].compact
|
56
|
+
end
|
57
|
+
|
58
|
+
def set(agent, *args)
|
59
|
+
@listing = false
|
60
|
+
@cmd = case @config_type
|
61
|
+
when :agent
|
62
|
+
set_agent(agent, *args)
|
63
|
+
when :tier
|
64
|
+
set_tier(agent, *args)
|
65
|
+
else
|
66
|
+
raise "Cannot run set on #{@config_type}"
|
67
|
+
end
|
68
|
+
self
|
69
|
+
end
|
70
|
+
|
71
|
+
def add_tier(agent, queue, level = 1, position = nil)
|
72
|
+
["add", queue, agent, level, position].compact
|
73
|
+
end
|
74
|
+
|
75
|
+
def add_agent(agent, callback = nil)
|
76
|
+
callback ||= 'callback'
|
77
|
+
["add", "'#{agent}'", callback].compact
|
78
|
+
end
|
79
|
+
|
80
|
+
def add(agent, *args)
|
81
|
+
@listing = false
|
82
|
+
@cmd = case @config_type
|
83
|
+
when :agent
|
84
|
+
add_agent(agent, *args)
|
85
|
+
when :tier
|
86
|
+
add_tier(agent, *args)
|
87
|
+
else
|
88
|
+
raise "Cannot add to #{@config_type}"
|
89
|
+
end
|
90
|
+
self
|
91
|
+
end
|
92
|
+
|
93
|
+
protected :list_agent, :list_tier, :list_queue, :set_agent, :set_tier, :add_tier, :add_agent
|
94
|
+
def del(agent, queue = nil)
|
95
|
+
@listing = false
|
96
|
+
@cmd = ["del", queue, agent].compact
|
97
|
+
self
|
98
|
+
end
|
99
|
+
|
100
|
+
def run(api_method = :api)
|
101
|
+
orig_command = "%s %s" % [api_method, raw]
|
102
|
+
resp = @fs_socket.say(orig_command)
|
103
|
+
responses << resp
|
104
|
+
if @listing
|
105
|
+
if resp["body"].match(/^([^|]*\|[^|]*)+/)
|
106
|
+
require "csv"
|
107
|
+
csv = CSV.parse(resp["body"], :col_sep => '|', :headers => true)
|
108
|
+
case @config_type
|
109
|
+
when :tier
|
110
|
+
require "fsr/model/tier"
|
111
|
+
csv.to_a[1 .. -2].map { |c| FSR::Model::Tier.new(csv.headers, *c) }
|
112
|
+
when :queue
|
113
|
+
require "fsr/model/queue"
|
114
|
+
csv.to_a[1 .. -2].map { |c| FSR::Model::Queue.new(csv.headers, *c) }
|
115
|
+
when :agent
|
116
|
+
require "fsr/model/agent"
|
117
|
+
csv.to_a[1 .. -2].map { |c| FSR::Model::Agent.new(csv.headers, *c) }
|
118
|
+
end
|
119
|
+
else
|
120
|
+
[]
|
121
|
+
end
|
122
|
+
else
|
123
|
+
resp["body"].to_s =~ /\+OK$/
|
124
|
+
end
|
125
|
+
end
|
126
|
+
=begin
|
127
|
+
# Send the command to the event socket, using bgapi by default.
|
128
|
+
def run(api_method = :api)
|
129
|
+
orig_command = "%s %s" % [api_method, raw]
|
130
|
+
Log.debug "saying #{orig_command}"
|
131
|
+
resp = @fs_socket.say(orig_command)
|
132
|
+
unless resp["body"] == "0 total."
|
133
|
+
call_info, count = resp["body"].split("\n\n")
|
134
|
+
require "fsr/model/call"
|
135
|
+
begin
|
136
|
+
require "fastercsv"
|
137
|
+
@calls = FCSV.parse(call_info)
|
138
|
+
rescue LoadError
|
139
|
+
require "csv"
|
140
|
+
@calls = CSV.parse(call_info)
|
141
|
+
end
|
142
|
+
return @calls[1 .. -1].map { |c| FSR::Model::Call.new(@calls[0],*c) }
|
143
|
+
end
|
144
|
+
[]
|
145
|
+
end
|
146
|
+
=end
|
147
|
+
|
148
|
+
# This method builds the API command to send to the freeswitch event socket
|
149
|
+
def raw
|
150
|
+
["callcenter_config", @config_type, *@cmd].join(" ")
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
register(:call_center, CallCenter)
|
155
|
+
end
|
156
|
+
end
|
data/lib/fsr/cmd/calls.rb
CHANGED
@@ -25,14 +25,14 @@ module FSR
|
|
25
25
|
require "fsr/model/call"
|
26
26
|
begin
|
27
27
|
require "fastercsv"
|
28
|
-
calls = FCSV.parse(call_info)
|
28
|
+
@calls = FCSV.parse(call_info)
|
29
29
|
rescue LoadError
|
30
30
|
require "csv"
|
31
|
-
calls = CSV.parse(call_info)
|
31
|
+
@calls = CSV.parse(call_info)
|
32
32
|
end
|
33
|
-
return calls[1 .. -1].map { |c| FSR::Model::Call.new(
|
33
|
+
return @calls[1 .. -1].map { |c| FSR::Model::Call.new(@calls[0],*c) }
|
34
34
|
end
|
35
|
-
|
35
|
+
[]
|
36
36
|
end
|
37
37
|
|
38
38
|
# This method builds the API command to send to the freeswitch event socket
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require "fsr/app"
|
2
|
+
module FSR
|
3
|
+
module Cmd
|
4
|
+
class Channels < Command
|
5
|
+
|
6
|
+
include Enumerable
|
7
|
+
def each(&block)
|
8
|
+
@channels ||= run
|
9
|
+
if @channels
|
10
|
+
@channels.each { |call| yield call }
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize(fs_socket = nil, distinct = true)
|
15
|
+
@distinct = distinct
|
16
|
+
@fs_socket = fs_socket # FSR::CommandSocket obj
|
17
|
+
end
|
18
|
+
|
19
|
+
# Send the command to the event socket, using bgapi by default.
|
20
|
+
def run(api_method = :api)
|
21
|
+
orig_command = "%s %s" % [api_method, raw]
|
22
|
+
Log.debug "saying #{orig_command}"
|
23
|
+
resp = @fs_socket.say(orig_command)
|
24
|
+
unless resp["body"] == "0 total."
|
25
|
+
call_info, count = resp["body"].split("\n\n")
|
26
|
+
require "fsr/model/channel"
|
27
|
+
begin
|
28
|
+
require "fastercsv"
|
29
|
+
@channels = FCSV.parse(call_info)
|
30
|
+
rescue LoadError
|
31
|
+
require "csv"
|
32
|
+
@channels = CSV.parse(call_info)
|
33
|
+
end
|
34
|
+
return @channels[1 .. -1].map { |c| FSR::Model::Channel.new(@channels[0],*c) }
|
35
|
+
end
|
36
|
+
[]
|
37
|
+
end
|
38
|
+
|
39
|
+
# This method builds the API command to send to the freeswitch event socket
|
40
|
+
def raw
|
41
|
+
orig_command = @distinct ? "show distinct_channels" : "show channels"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
register(:channels, Channels)
|
46
|
+
end
|
47
|
+
end
|
data/lib/fsr/cmd/enum.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
require "fsr/app"
|
2
|
+
module FSR
|
3
|
+
module Cmd
|
4
|
+
class Enum < Command
|
5
|
+
attr_reader :ph_nbr
|
6
|
+
|
7
|
+
def initialize(fs_socket = nil, phone_number = nil)
|
8
|
+
@fs_socket = fs_socket # FSR::CommandSocket obj
|
9
|
+
@ph_nbr = phone_number # phone number to look up, up to 15 digits
|
10
|
+
end
|
11
|
+
|
12
|
+
# Send the command to the event socket, using api by default.
|
13
|
+
def run(api_method = :api)
|
14
|
+
orig_command = "%s %s" % [api_method, raw]
|
15
|
+
Log.debug "saying #{orig_command}"
|
16
|
+
resp = @fs_socket.say(orig_command)
|
17
|
+
parse(resp)
|
18
|
+
end
|
19
|
+
|
20
|
+
# This method builds the API command to send to the freeswitch event socket
|
21
|
+
def raw
|
22
|
+
orig_command = "enum #{ph_nbr}"
|
23
|
+
end
|
24
|
+
|
25
|
+
def parse(response)
|
26
|
+
body = response["body"]
|
27
|
+
offered_routes,supported_routes = body.match(/Offered\ Routes:(.*?)Supported\ Routes:(.*?)/mx)[1 .. 2]
|
28
|
+
order, pref, service, route = offered_routes.match(/==+\n(.*)/m)[1].split
|
29
|
+
unless body == "No Match!"
|
30
|
+
require "fsr/model/enum"
|
31
|
+
return FSR::Model::Enum.new(offered_routes, supported_routes, order, pref, service, route)
|
32
|
+
end
|
33
|
+
nil
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
register(:enum, Enum)
|
39
|
+
end
|
40
|
+
end
|