freeswitcher 0.4.10 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|