adhearsion-asterisk 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (33) hide show
  1. data/.gitignore +10 -0
  2. data/.rspec +3 -0
  3. data/CHANGELOG.md +14 -0
  4. data/Gemfile +6 -0
  5. data/Guardfile +5 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +143 -0
  8. data/Rakefile +23 -0
  9. data/adhearsion-asterisk.gemspec +35 -0
  10. data/lib/adhearsion-asterisk.rb +1 -0
  11. data/lib/adhearsion/asterisk.rb +12 -0
  12. data/lib/adhearsion/asterisk/config_generator.rb +103 -0
  13. data/lib/adhearsion/asterisk/config_generator/agents.rb +138 -0
  14. data/lib/adhearsion/asterisk/config_generator/queues.rb +247 -0
  15. data/lib/adhearsion/asterisk/config_generator/voicemail.rb +238 -0
  16. data/lib/adhearsion/asterisk/config_manager.rb +60 -0
  17. data/lib/adhearsion/asterisk/plugin.rb +464 -0
  18. data/lib/adhearsion/asterisk/queue_proxy.rb +177 -0
  19. data/lib/adhearsion/asterisk/queue_proxy/agent_proxy.rb +81 -0
  20. data/lib/adhearsion/asterisk/queue_proxy/queue_agents_list_proxy.rb +132 -0
  21. data/lib/adhearsion/asterisk/version.rb +5 -0
  22. data/spec/adhearsion/asterisk/config_generators/agents_spec.rb +258 -0
  23. data/spec/adhearsion/asterisk/config_generators/queues_spec.rb +322 -0
  24. data/spec/adhearsion/asterisk/config_generators/voicemail_spec.rb +306 -0
  25. data/spec/adhearsion/asterisk/config_manager_spec.rb +125 -0
  26. data/spec/adhearsion/asterisk/plugin_spec.rb +618 -0
  27. data/spec/adhearsion/asterisk/queue_proxy/agent_proxy_spec.rb +90 -0
  28. data/spec/adhearsion/asterisk/queue_proxy/queue_agents_list_proxy_spec.rb +145 -0
  29. data/spec/adhearsion/asterisk/queue_proxy_spec.rb +156 -0
  30. data/spec/adhearsion/asterisk_spec.rb +9 -0
  31. data/spec/spec_helper.rb +23 -0
  32. data/spec/support/the_following_code.rb +3 -0
  33. metadata +229 -0
@@ -0,0 +1,10 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ vendor/ruby
6
+ coverage
7
+ .*.swp
8
+ vendor/bundle
9
+ tags
10
+ .rbenv-version
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --colour
3
+ --tty
@@ -0,0 +1,14 @@
1
+ # develop
2
+
3
+ # v0.1.0 - 2012-01-17
4
+ * A whole bunch of new stuff
5
+
6
+ ## Config Generators
7
+ * Asterisk config generators moved from Adhearsion
8
+ * Namespace cleanup
9
+
10
+ ## Plugin Rename
11
+ * ahn-asterisk to adhearsion-asterisk
12
+
13
+ # v0.0.1
14
+ * First release!
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "http://rubygems.org"
2
+
3
+ gem 'adhearsion', :git => 'git://github.com/adhearsion/adhearsion.git', :branch => :develop
4
+
5
+ # Specify your gem's dependencies in ahn-asterisk.gemspec
6
+ gemspec
@@ -0,0 +1,5 @@
1
+ guard 'rspec', :version => 2, :cli => '--format documentation' do
2
+ watch(%r{^spec/.+_spec\.rb$})
3
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
4
+ watch('spec/spec_helper.rb') { "spec/" }
5
+ end
@@ -0,0 +1,22 @@
1
+ The MIT License
2
+
3
+ Copyright (c) 2011 Ben Langfeld
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,143 @@
1
+ adhearsion-asterisk
2
+ ===========
3
+
4
+ adhearsion-asterisk is an Adhearsion Plugin providing Asterisk-specific dialplan methods, AMI access, and access to Asterisk configuration.
5
+
6
+ Features
7
+ --------
8
+
9
+ Dialplan methods
10
+
11
+ * agi
12
+ * execute
13
+ * verbose
14
+ * get_variable
15
+ * set_variable
16
+ * sip_add_header
17
+ * sip_get_header
18
+ * variable
19
+ * meetme
20
+ * voicemail
21
+ * voicemail_main
22
+ * queue
23
+ * play
24
+ * play!
25
+ * play_time
26
+ * play_numeric
27
+ * play_soundfile
28
+ * enable_feature
29
+ * disable_feature
30
+
31
+ Asterisk configuration generators
32
+
33
+ * agents.conf
34
+ * queues.conf
35
+ * voicemail.conf
36
+
37
+ Requirements
38
+ ------------
39
+
40
+ * Adhearsion 2.0+
41
+ * Asterisk 1.8+
42
+
43
+ Install
44
+ -------
45
+
46
+ Add `adhearsion-asterisk` to your Adhearsion app's Gemfile.
47
+
48
+ Examples
49
+ --------
50
+
51
+ ### Dialplan
52
+
53
+
54
+ ```ruby
55
+ vm {
56
+ voicemail "8000"
57
+ }
58
+
59
+ echotest {
60
+ play 'demo-echotest'
61
+ execute 'Echo'
62
+ play 'demo-echodone'
63
+ }
64
+
65
+ saytime {
66
+ t = Time.now
67
+ date = t.to_date
68
+ date_format = 'ABdY'
69
+ execute "SayUnixTime", t.to_i, date_format
70
+ play_time date, :format => date_format
71
+ }
72
+
73
+ callqueue {
74
+ case extension
75
+ when 5001
76
+ queue 'sales'
77
+ when 5002
78
+ queue 'support'
79
+ end
80
+ }
81
+
82
+ salesagent {
83
+ queue('sales').join!
84
+ }
85
+
86
+ supportagent {
87
+ queue('support').join!
88
+ }
89
+
90
+ operator {
91
+ enable_feature :blind_transfer
92
+ dial extension, :options => "Tt"
93
+ }
94
+
95
+
96
+ ```
97
+
98
+ ### Config generation
99
+
100
+ Stand-alone example
101
+
102
+ ```ruby
103
+ require 'adhearsion/asterisk'
104
+ require 'adhearsion/asterisk/config_generator/voicemail'
105
+
106
+ config_generator = Adhearsion::Asterisk::ConfigGenerator::Voicemail.new
107
+ asterisk_config_file = "voicemail.conf"
108
+
109
+ File.open(asterisk_config_file, "w") do |file|
110
+ file.write config_generator
111
+ end
112
+ ```
113
+
114
+ agents.conf, and queue.conf can be done similarly.
115
+
116
+ Author
117
+ ------
118
+
119
+ Original author: [Ben Langfeld](https://github.com/benlangfeld)
120
+
121
+ Contributors:
122
+ * [Taylor Carpenter](https://github.com/taylor)
123
+
124
+ Links
125
+ -----
126
+ * [Source](https://github.com/adhearsion/adhearsion-asterisk)
127
+ * [Documentation](http://rdoc.info/github/adhearsion/adhearsion-asterisk/master/frames)
128
+ * [Bug Tracker](https://github.com/adhearsion/adhearsion-asterisk/issues)
129
+
130
+ Note on Patches/Pull Requests
131
+ -----------------------------
132
+
133
+ * Fork the project.
134
+ * Make your feature addition or bug fix.
135
+ * Add tests for it. This is important so I don't break it in a future version unintentionally.
136
+ * Commit, do not mess with rakefile, version, or history.
137
+ * If you want to have your own version, that is fine but bump version in a commit by itself so I can ignore when I pull
138
+ * Send me a pull request. Bonus points for topic branches.
139
+
140
+ Copyright
141
+ ---------
142
+
143
+ Copyright (c) 2011 Ben Langfeld. MIT licence (see LICENSE for details).
@@ -0,0 +1,23 @@
1
+ begin
2
+ require 'bones'
3
+ rescue LoadError
4
+ abort '### Please install the "bones" gem ###'
5
+ end
6
+
7
+ require 'bundler/gem_tasks'
8
+
9
+ task :default => :spec
10
+
11
+ require 'rspec/core'
12
+ require 'rspec/core/rake_task'
13
+ require 'ci/reporter/rake/rspec'
14
+ RSpec::Core::RakeTask.new(:spec) do |spec|
15
+ spec.pattern = 'spec/**/*_spec.rb'
16
+ spec.rspec_opts = '--color'
17
+ end
18
+
19
+ task :default => :spec
20
+ task :ci => ['ci:setup:rspec', :spec]
21
+
22
+ require 'yard'
23
+ YARD::Rake::YardocTask.new
@@ -0,0 +1,35 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "adhearsion/asterisk/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "adhearsion-asterisk"
7
+ s.version = Adhearsion::Asterisk::VERSION
8
+ s.authors = ["Ben Langfeld", "Taylor Carpenter"]
9
+ s.email = ["blangfeld@adhearsion.com", "taylor@codecafe.com"]
10
+ s.homepage = "http://adhearsion.com"
11
+ s.summary = %q{Asterisk specific features for Adhearsion}
12
+ s.description = %q{An Adhearsion Plugin providing Asterisk-specific dialplan methods, AMI access, and access to Asterisk configuration}
13
+
14
+ s.rubyforge_project = "adhearsion-asterisk"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ # s.add_runtime_dependency %q<adhearsion>, [">= 2.0.0"]
22
+ s.add_runtime_dependency %q<activesupport>, [">= 3.0.10"]
23
+
24
+ s.add_development_dependency %q<bundler>, ["~> 1.0.0"]
25
+ s.add_development_dependency %q<rspec>, [">= 2.5.0"]
26
+ s.add_development_dependency %q<ci_reporter>, [">= 1.6.3"]
27
+ s.add_development_dependency %q<simplecov>, [">= 0"]
28
+ s.add_development_dependency %q<simplecov-rcov>, [">= 0"]
29
+ s.add_development_dependency %q<yard>, ["~> 0.6.0"]
30
+ s.add_development_dependency %q<rake>, [">= 0"]
31
+ s.add_development_dependency %q<mocha>, [">= 0"]
32
+ s.add_development_dependency %q<bones>
33
+ s.add_development_dependency %q<guard-rspec>
34
+ s.add_development_dependency 'ruby_gntp'
35
+ end
@@ -0,0 +1 @@
1
+ require 'adhearsion/asterisk'
@@ -0,0 +1,12 @@
1
+ require 'adhearsion'
2
+ require 'active_support/dependencies/autoload'
3
+ require 'adhearsion/asterisk/version'
4
+ require 'adhearsion/asterisk/plugin'
5
+
6
+ module Adhearsion
7
+ module Asterisk
8
+ extend ActiveSupport::Autoload
9
+
10
+ autoload :QueueProxy
11
+ end
12
+ end
@@ -0,0 +1,103 @@
1
+ module Adhearsion
2
+ module Asterisk
3
+ class ConfigGenerator
4
+ extend ActiveSupport::Autoload
5
+
6
+ autoload :Agents
7
+ autoload :Queues
8
+ autoload :Voicemail
9
+
10
+ SECTION_TITLE = /(\[[\w_-]+\])/
11
+
12
+ class << self
13
+
14
+ # Converts a config file into a Hash of contexts mapping to two dimensional array of pairs
15
+ def create_sanitary_hash_from(config_file_content)
16
+ almost_sanitized = Hash[*config_file_content.
17
+ split("\n"). # Split the lines into an Array
18
+ grep(/^\s*[^;\s]/). # Grep lines that aren't commented out
19
+ join("\n"). # Convert them into one String again
20
+ split(SECTION_TITLE). # Separate them into sections
21
+ map(&:strip). # Remove all whitespace
22
+ reject(&:empty?). # Get rid of indices that were only whitespace
23
+ # Lastly, separate the keys/value pairs for the Hash
24
+ map { |token| token =~ /^#{SECTION_TITLE}$/ ? token : token.split(/\n+/).sort }
25
+ ]
26
+ end
27
+
28
+ def warning_message
29
+ %{;; THIS FILE WAS GENERATED BY ADHEARSION ON #{Time.now.ctime}!\n} +
30
+ %{;; ANY CHANGES MADE BELOW WILL BE BLOWN AWAY WHEN THE FILE IS REGENERATED!\n\n}
31
+ end
32
+
33
+ end
34
+
35
+ def initialize
36
+ yield self if block_given?
37
+ end
38
+
39
+ def to_sanitary_hash
40
+ self.class.create_sanitary_hash_from to_s
41
+ end
42
+
43
+ protected
44
+
45
+ def boolean(options)
46
+ cache = options.delete(:with) || properties
47
+ options.each_pair do |key, value|
48
+ cache[key] = boolean_to_yes_no value
49
+ end
50
+ end
51
+
52
+ def string(options)
53
+ cache = options.delete(:with) || properties
54
+ options.each_pair do |key, value|
55
+ cache[key] = value
56
+ end
57
+ end
58
+
59
+ def int(options)
60
+ cache = options.delete(:with) || properties
61
+ options.each_pair do |property,number|
62
+ number = number.to_i if (number.kind_of?(String) && number =~ /^\d+$/) || number.kind_of?(Numeric)
63
+ raise ArgumentError, "#{number.inspect} must be an integer" unless number.kind_of?(Fixnum)
64
+ cache[property] = number.to_i
65
+ end
66
+ end
67
+
68
+ def one_of(criteria, options)
69
+ cache = options.delete(:with) || properties
70
+ options.each_pair do |key, value|
71
+ search = !criteria.find { |criterion| criterion === value }.nil?
72
+ unless search
73
+ msg = "Didn't recognize #{value.inspect}! Must be one of " + criteria.map(&:inspect).to_sentence
74
+ raise ArgumentError, msg
75
+ end
76
+ cache[key] = [true, false].include?(value) ? boolean_to_yes_no(value) : value
77
+ end
78
+ end
79
+
80
+ def one_of_and_translate(criteria, options)
81
+ cache = options.delete(:with) || properties
82
+ options.each_pair do |key, value|
83
+ search = criteria.keys.find { |criterion| criterion === value }
84
+ unless search
85
+ msg = "Didn't recognize #{value.inspect}! Must be one of " + criteria.keys.map(&:inspect).to_sentence
86
+ raise ArgumentError, msg
87
+ end
88
+ cache[key] = criteria[value]
89
+ end
90
+ end
91
+
92
+ private
93
+
94
+ def boolean_to_yes_no(boolean)
95
+ unless boolean.equal?(boolean) || boolean.equal?(boolean)
96
+ raise "#{boolean.inspect} is not true/false!"
97
+ end
98
+ boolean ? 'yes' : 'no'
99
+ end
100
+
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,138 @@
1
+ require File.join(File.dirname(__FILE__), '../config_generator')
2
+
3
+ module Adhearsion
4
+ module Asterisk
5
+ class ConfigGenerator
6
+ class Agents < ConfigGenerator
7
+
8
+ attr_accessor :general_section, :agent_section, :agent_definitions, :agent_section_special
9
+ def initialize
10
+ @general_section = {}
11
+ @agent_section = {}
12
+ @agent_section_special = {} # Uses => separator
13
+ @agent_definitions = []
14
+
15
+ super
16
+ end
17
+
18
+ def to_s
19
+ ConfigGenerator.warning_message +
20
+ general_section.inject("[general]") { |section,(key,value)| section + "\n#{key}=#{value}" } +
21
+ agent_section.inject("\n[agents]") { |section,(key,value)| section + "\n#{key}=#{value}" } +
22
+ agent_section_special.inject("") { |section,(key,value)| section + "\n#{key} => #{value}" } +
23
+ agent_definitions.inject("\n") do |section,properties|
24
+ section + "\nagent => #{properties[:id]},#{properties[:password]},#{properties[:name]}"
25
+ end
26
+ end
27
+ alias conf to_s # Allows "agents.conf" if agents.kind_of?(Agents)
28
+
29
+ def agent(id, properties)
30
+ agent_definitions << {:id => id}.merge(properties)
31
+ end
32
+
33
+ # Group memberships for agents (may change in mid-file)
34
+ # WHAT DOES GROUPING ACCOMPLISH?
35
+ def groups(*args)
36
+ agent_section[:group] = args.join(",")
37
+ end
38
+
39
+ # Define whether callbacklogins should be stored in astdb for
40
+ # persistence. Persistent logins will be reloaded after
41
+ # Asterisk restarts.
42
+ def persistent_agents(yes_or_no)
43
+ general_section[:persistentagents] = boolean_to_yes_no yes_or_no
44
+ end
45
+
46
+ # enable or disable a single extension from longing in as multiple
47
+ # agents, defaults to enabled
48
+ def allow_multiple_logins_per_extension(yes_or_no)
49
+ general_section[:multiplelogin] = boolean_to_yes_no yes_or_no
50
+ end
51
+
52
+ # Define maxlogintries to allow agent to try max logins before
53
+ # failed. Default to 3
54
+ def max_login_tries(number_of_tries)
55
+ agent_section[:maxlogintries] = number_of_tries
56
+ end
57
+
58
+ # Define autologoff times if appropriate. This is how long
59
+ # the phone has to ring with no answer before the agent is
60
+ # automatically logged off (in seconds)
61
+ def log_off_after_duration(time_in_seconds)
62
+ agent_section[:autologoff] = time_in_seconds
63
+ end
64
+
65
+ # Define autologoffunavail to have agents automatically logged
66
+ # out when the extension that they are at returns a CHANUNAVAIL
67
+ # status when a call is attempted to be sent there.
68
+ # Default is "no".
69
+ def log_off_if_unavailable(yes_or_no)
70
+ agent_section[:autologoffunavail] = boolean_to_yes_no yes_or_no
71
+ end
72
+
73
+ # Define ackcall to require an acknowledgement by '#' when
74
+ # an agent logs in using agentcallbacklogin. Default is "no".
75
+ def require_hash_to_acknowledge(yes_or_no)
76
+ agent_section[:ackcall] = boolean_to_yes_no yes_or_no
77
+ end
78
+
79
+ # Define endcall to allow an agent to hangup a call by '*'.
80
+ # Default is "yes". Set this to "no" to ignore '*'.
81
+ def allow_star_to_hangup(yes_or_no)
82
+ agent_section[:endcall] = boolean_to_yes_no yes_or_no
83
+ end
84
+
85
+ # Define wrapuptime. This is the minimum amount of time when
86
+ # after disconnecting before the caller can receive a new call
87
+ # note this is in milliseconds.
88
+ def time_between_calls(time_in_seconds)
89
+ agent_section[:wrapuptime] = (time_in_seconds * 1000).to_i
90
+ end
91
+
92
+ # Define the default musiconhold for agents
93
+ # musiconhold => music_class
94
+ def hold_music_class(music_class)
95
+ agent_section_special[:musiconhold] = music_class.to_s
96
+ end
97
+
98
+ # TODO: I'm not exactly sure what this even does....
99
+
100
+ # Define the default good bye sound file for agents
101
+ # default to vm-goodbye
102
+ def play_on_agent_goodbye(sound_file_name)
103
+ agent_section_special[:agentgoodbye] = sound_file_name
104
+ end
105
+
106
+ # Define updatecdr. This is whether or not to change the source
107
+ # channel in the CDR record for this call to agent/agent_id so
108
+ # that we know which agent generates the call
109
+ def change_cdr_source(yes_or_no)
110
+ agent_section[:updatecdr] = boolean_to_yes_no yes_or_no
111
+ end
112
+
113
+ # An optional custom beep sound file to play to always-connected agents.
114
+ def play_for_waiting_keep_alive(sound_file)
115
+ agent_section[:custom_beep] = sound_file
116
+ end
117
+
118
+ def record_agent_calls(yes_or_no)
119
+ agent_section[:recordagentcalls] = boolean_to_yes_no yes_or_no
120
+ end
121
+
122
+ def recording_format(symbol)
123
+ raise ArgumentError, "Unrecognized format #{symbol}" unless [:wav, :wav49, :gsm].include? symbol
124
+ agent_section[:recordformat] = symbol
125
+ end
126
+
127
+ def recording_prefix(string)
128
+ agent_section[:urlprefix] = string
129
+ end
130
+
131
+ def save_recordings_in(path_to_directory)
132
+ agent_section[:savecallsin] = path_to_directory
133
+ end
134
+
135
+ end
136
+ end
137
+ end
138
+ end