adhearsion-asterisk 0.1.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.
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