diff --git a/CHANGES.md b/CHANGES.md index a17ec5db..ed04834d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,7 @@ +# 7.11.0 + +* Updates Voice API functionality. [#270](https://github.com/Vonage/vonage-ruby-sdk/pull/270) + # 7.10.0 * Adds Verify2. [#261](https://github.com/Vonage/vonage-ruby-sdk/pull/261) diff --git a/lib/vonage/version.rb b/lib/vonage/version.rb index 6b047c81..5ecfeec4 100644 --- a/lib/vonage/version.rb +++ b/lib/vonage/version.rb @@ -1,5 +1,5 @@ # typed: strong module Vonage - VERSION = "7.10.0" + VERSION = "7.11.0" end diff --git a/lib/vonage/voice.rb b/lib/vonage/voice.rb index 8b13f867..9bb111c3 100644 --- a/lib/vonage/voice.rb +++ b/lib/vonage/voice.rb @@ -48,6 +48,17 @@ class Voice < Namespace # @option params [String] :machine_detection # Configure the behavior when Vonage detects that the call is answered by voicemail. # + # @option params [Hash] :advanced_machine_detection + # Configure the behavior of Vonage's advanced machine detection. Overrides machine_detection if both are set. + # Hash with three possible properties: + # - :behavior [String]: Must be one of `continue` or `hangup`. When hangup is used, the call will be terminated if a + # machine is detected. When continue is used, the call will continue even if a machine is detected. + # - :mode [String]: Must be one of `detect` or `detect_beep`. Detect if machine answered and sends a human or + # machine status in the webhook payload. When set to `detect_beep`, the system also attempts to detect + # voice mail beep and sends an additional parameter `sub_state` in the webhook with the value `beep_start`. + # - :beep_timeout [Integer]: Min: 45, Max: 120. Maximum time in seconds Vonage should wait for a machine beep + # to be detected. A machine event with `sub_state` set to `beep_timeout` will be sent if the timeout is exceeded. + # # @option params [Integer] :length_timer # Set the number of seconds that elapse before Vonage hangs up after the call state changes to in_progress. # diff --git a/lib/vonage/voice/actions/connect.rb b/lib/vonage/voice/actions/connect.rb index de413b13..cbb718b1 100644 --- a/lib/vonage/voice/actions/connect.rb +++ b/lib/vonage/voice/actions/connect.rb @@ -2,9 +2,9 @@ # frozen_string_literal: true require 'phonelib' -module Vonage +module Vonage class Voice::Actions::Connect - attr_accessor :endpoint, :from, :eventType, :timeout, :limit, :machineDetection, :eventUrl, :eventMethod, :ringbackTone + attr_accessor :endpoint, :from, :eventType, :timeout, :limit, :machineDetection, :advanced_machine_detection, :eventUrl, :eventMethod, :ringbackTone def initialize(attributes = {}) @endpoint = attributes.fetch(:endpoint) @@ -13,6 +13,7 @@ def initialize(attributes = {}) @timeout = attributes.fetch(:timeout, nil) @limit = attributes.fetch(:limit, nil) @machineDetection = attributes.fetch(:machineDetection, nil) + @advanced_machine_detection = attributes.fetch(:advanced_machine_detection, nil) @eventUrl = attributes.fetch(:eventUrl, nil) @eventMethod = attributes.fetch(:eventMethod, nil) @ringbackTone = attributes.fetch(:ringbackTone, nil) @@ -39,6 +40,10 @@ def after_initialize! verify_machine_detection end + if self.advanced_machine_detection + verify_advanced_machine_detection + end + if self.eventUrl verify_event_url end @@ -82,6 +87,25 @@ def verify_machine_detection raise ClientError.new("Invalid 'machineDetection' value, must be either: 'continue' or 'hangup' if defined") unless self.machineDetection == 'continue' || self.machineDetection == 'hangup' end + def verify_advanced_machine_detection + raise ClientError.new("Invalid 'advanced_machine_detection' value, must be a Hash") unless self.advanced_machine_detection.is_a?(Hash) + verify_advanced_machine_detection_behavior if self.advanced_machine_detection[:behavior] + verify_advanced_machine_detection_mode if self.advanced_machine_detection[:mode] + verify_advanced_machine_detection_beep_timeout if self.advanced_machine_detection[:beep_timeout] + end + + def verify_advanced_machine_detection_behavior + raise ClientError.new("Invalid 'advanced_machine_detection[:behavior]' value, must be a `continue` or `hangup`") unless ['continue', 'hangup'].include?(self.advanced_machine_detection[:behavior]) + end + + def verify_advanced_machine_detection_mode + raise ClientError.new("Invalid 'advanced_machine_detection[:mode]' value, must be a `detect` or `detect_beep`") unless ['detect', 'detect_beep'].include?(self.advanced_machine_detection[:mode]) + end + + def verify_advanced_machine_detection_beep_timeout + raise ClientError.new("Invalid 'advanced_machine_detection[:beep_timeout]' value, must be between 45 and 120") unless self.advanced_machine_detection[:beep_timeout].between?(45, 120) + end + def verify_event_url uri = URI.parse(self.eventUrl) @@ -196,4 +220,4 @@ def vbc_endpoint(endpoint_attrs) } end end -end \ No newline at end of file +end diff --git a/lib/vonage/voice/actions/pay.rb b/lib/vonage/voice/actions/pay.rb deleted file mode 100644 index efbeb3aa..00000000 --- a/lib/vonage/voice/actions/pay.rb +++ /dev/null @@ -1,107 +0,0 @@ -# typed: true -# frozen_string_literal: true -module Vonage - class Voice::Actions::Pay - attr_accessor :amount, :currency, :eventUrl, :prompts, :voice - - def initialize(attributes= {}) - @amount = attributes.fetch(:amount) - @currency = attributes.fetch(:currency, nil) - @eventUrl = attributes.fetch(:eventUrl, nil) - @prompts = attributes.fetch(:prompts, nil) - @voice = attributes.fetch(:voice, nil) - - after_initialize! - end - - def action - create_pay!(self) - end - - def create_pay!(builder) - ncco = [ - { - action: 'pay', - amount: builder.amount - } - ] - - ncco[0].merge!(currency: builder.currency) if builder.currency - ncco[0].merge!(eventUrl: builder.eventUrl) if builder.eventUrl - ncco[0].merge!(prompts: builder.prompts) if builder.prompts - ncco[0].merge!(voice: builder.voice) if builder.voice - - ncco - end - - private - - def after_initialize! - verify_amount - - if self.eventUrl - verify_event_url - end - - if self.prompts - verify_prompts - end - - if self.voice - verify_voice - end - end - - def verify_amount - verify_amount_class - verify_amount_value - end - - def verify_event_url - raise ClientError.new("Expected 'eventUrl' parameter to be an Array containing a single string item") unless self.eventUrl.is_a?(Array) - - uri = URI.parse(self.eventUrl[0]) - - raise ClientError.new("Invalid 'eventUrl' value, must be a valid URL") unless uri.kind_of?(URI::HTTP) || uri.kind_of?(URI::HTTPS) - end - - def verify_prompts - verify_prompts_structure - verify_prompts_values - end - - def verify_voice - verify_voice_structure - verify_voice_style if self.voice[:style] - end - - def verify_amount_class - raise ClientError.new("Invalid 'amount' value, must be a float") unless self.amount.is_a?(Float) - end - - def verify_amount_value - raise ClientError.new("Invalid 'amount' value, must be greater than 0") unless self.amount > 0 - end - - def verify_prompts_structure - raise ClientError.new("Invalid 'prompt', must be an array of at least one hash") unless self.prompts.is_a?(Array) && !self.prompts.empty? && self.prompts.all?(Hash) - end - - def verify_prompts_values - self.prompts.each do |prompt| - prompt_keys = prompt.keys - [:type, :text, :errors].each do |key| - raise ClientError.new("Invalid 'prompt', '#{key}' is required") unless prompt_keys.include?(key) - end - end - end - - def verify_voice_structure - raise ClientError.new("Expected 'voice' value to be a Hash") unless self.voice.is_a?(Hash) - end - - def verify_voice_style - raise ClientError.new("Expected 'style' value to be an Integer") unless self.voice[:style].is_a?(Integer) - end - end -end diff --git a/lib/vonage/voice/actions/talk.rb b/lib/vonage/voice/actions/talk.rb index 514fb094..43e08412 100644 --- a/lib/vonage/voice/actions/talk.rb +++ b/lib/vonage/voice/actions/talk.rb @@ -2,7 +2,7 @@ # frozen_string_literal: true module Vonage class Voice::Actions::Talk - attr_accessor :text, :bargeIn, :loop, :level, :language, :style + attr_accessor :text, :bargeIn, :loop, :level, :language, :style, :premium def initialize(attributes= {}) @text = attributes.fetch(:text) @@ -11,6 +11,7 @@ def initialize(attributes= {}) @level = attributes.fetch(:level, nil) @language = attributes.fetch(:language, nil) @style = attributes.fetch(:style, nil) + @premium = attributes.fetch(:premium, nil) after_initialize! end @@ -31,6 +32,10 @@ def after_initialize! if self.style verify_style end + + if self.premium + verify_premium + end end def verify_barge_in @@ -49,6 +54,10 @@ def verify_style raise ClientError.new("Expected 'style' value to be an Integer") unless self.style.is_a?(Integer) end + def verify_premium + raise ClientError.new("Expected 'premium' value to be a Boolean") unless self.premium == true || self.premium == false + end + def action create_talk!(self) end @@ -70,4 +79,4 @@ def create_talk!(builder) ncco end end -end \ No newline at end of file +end diff --git a/lib/vonage/voice/talk.rb b/lib/vonage/voice/talk.rb index 0eb2ac25..9515a219 100644 --- a/lib/vonage/voice/talk.rb +++ b/lib/vonage/voice/talk.rb @@ -12,8 +12,18 @@ class Voice::Talk < Namespace # @option params [required, String] :text # The text to read. # + # @option params [String] :language + # The language to use. See {https://developer.vonage.com/en/api/voice#startTalk-req-body} for a list of valid language codes. + # + # @option params [Integer] :style + # The vocal style, as identified by an assigned integer. + # See {https://developer.vonage.com/en/voice/voice-api/concepts/text-to-speech#supported-languages} for a list of available styles. + # + # @option params [Boolean] :premium + # Set to `true` to use the premium version of the specified style if available, otherwise the standard version will be used. + # # @option params [String] :voice_name - # The voice & language to use. + # The voice & language to use. [DEPRECATED: use `language` and `style` instead]. # # @option params [Integer] :loop # The number of times to repeat the text the file, 0 for infinite. diff --git a/test/vonage/voice/actions/connect_test.rb b/test/vonage/voice/actions/connect_test.rb index 3452adbc..80d9adfa 100644 --- a/test/vonage/voice/actions/connect_test.rb +++ b/test/vonage/voice/actions/connect_test.rb @@ -34,13 +34,13 @@ def test_verify_endpoint_with_invalid_phone_number endpoint = { type: 'phone', number: 'abcd' } exception = assert_raises { Vonage::Voice::Actions::Connect.new(endpoint: endpoint) } - + assert_match "Expected 'number' value to be in E.164 format", exception.message end def test_verify_with_invalid_from_number exception = assert_raises { Vonage::Voice::Actions::Connect.new(endpoint: { type: 'phone', number: '12122222222' }, from: 'abcd') } - + assert_match "Invalid 'from' value, must be in E.164 format", exception.message end @@ -62,6 +62,30 @@ def test_verify_with_invalid_machine_detection assert_match "Invalid 'machineDetection' value, must be either: 'continue' or 'hangup' if defined", exception.message end + def test_verify_with_invalid_advanced_machine_detection_data_type + exception = assert_raises { Vonage::Voice::Actions::Connect.new(endpoint: { type: 'phone', number: '12122222222' }, advanced_machine_detection: 'foo') } + + assert_match "Invalid 'advanced_machine_detection' value, must be a Hash", exception.message + end + + def test_verify_with_invalid_advanced_machine_detection_behavior + exception = assert_raises { Vonage::Voice::Actions::Connect.new(endpoint: { type: 'phone', number: '12122222222' }, advanced_machine_detection: {behavior: 'bar'}) } + + assert_match "Invalid 'advanced_machine_detection[:behavior]' value, must be a `continue` or `hangup`", exception.message + end + + def test_verify_with_invalid_advanced_machine_detection_mode + exception = assert_raises { Vonage::Voice::Actions::Connect.new(endpoint: { type: 'phone', number: '12122222222' }, advanced_machine_detection: {mode: 'qux'}) } + + assert_match "Invalid 'advanced_machine_detection[:mode]' value, must be a `detect` or `detect_beep`", exception.message + end + + def test_verify_with_invalid_advanced_machine_detection_beep_timeout + exception = assert_raises { Vonage::Voice::Actions::Connect.new(endpoint: { type: 'phone', number: '12122222222' }, advanced_machine_detection: {beep_timeout: 1}) } + + assert_match "Invalid 'advanced_machine_detection[:beep_timeout]' value, must be between 45 and 120", exception.message + end + def test_verify_with_invalid_event_url exception = assert_raises { Vonage::Voice::Actions::Connect.new(endpoint: { type: 'phone', number: '12122222222' }, eventUrl: 'invalid') } @@ -80,4 +104,4 @@ def test_verify_with_invalid_ringback_tone assert_match "Invalid 'ringbackTone' value, must be a valid URL", exception.message end -end \ No newline at end of file +end diff --git a/test/vonage/voice/actions/pay_test.rb b/test/vonage/voice/actions/pay_test.rb deleted file mode 100644 index 3ca67ef5..00000000 --- a/test/vonage/voice/actions/pay_test.rb +++ /dev/null @@ -1,155 +0,0 @@ -# typed: false - -class Vonage::Voice::Actions::PayTest < Vonage::Test - def test_pay_initialize - pay = Vonage::Voice::Actions::Pay.new(amount: 9.99) - - assert_kind_of Vonage::Voice::Actions::Pay, pay - assert_equal 9.99, pay.amount - end - - def test_create_pay - expected = [{ action: 'pay', amount: 9.99 }] - pay = Vonage::Voice::Actions::Pay.new(amount: 9.99) - - assert_equal expected, pay.create_pay!(pay) - end - - def test_create_pay_with_optional_params - expected = [{ action: 'pay', amount: 9.99, currency: "eur" }] - pay = Vonage::Voice::Actions::Pay.new(amount: 9.99, currency: "eur") - - assert_equal expected, pay.create_pay!(pay) - end - - def test_create_pay_with_prompts_voice_settings - expected = [{ action: 'pay', amount: 9.99, voice: { language: 'en-GB', style: 9 } }] - pay = Vonage::Voice::Actions::Pay.new(amount: 9.99, voice: { language: 'en-GB', style: 9 }) - - assert_equal expected, pay.create_pay!(pay) - end - - def test_create_pay_with_language_no_style - expected = [{ action: 'pay', amount: 9.99, voice: { language: 'en-GB' } }] - pay = Vonage::Voice::Actions::Pay.new(amount: 9.99, voice: { language: 'en-GB' }) - - assert_equal expected, pay.create_pay!(pay) - end - - def test_create_pay_with_style_no_language - expected = [{ action: 'pay', amount: 9.99, voice: { style: 9 } }] - pay = Vonage::Voice::Actions::Pay.new(amount: 9.99, voice: { style: 9 }) - - assert_equal expected, pay.create_pay!(pay) - end - - def test_create_pay_with_prompts_text_settings - expected = [{ - action: 'pay', - amount: 9.99, - prompts: [{ - type: 'ExpirationDate', - text: 'Please enter expiration date', - errors: { - InvalidExpirationDate: { - text: 'Invalid expiration date. Please try again' - } - } - }] - }] - pay = Vonage::Voice::Actions::Pay.new( - amount: 9.99, - prompts: [{ - type: 'ExpirationDate', - text: 'Please enter expiration date', - errors: { - InvalidExpirationDate: { - text: 'Invalid expiration date. Please try again' - } - } - }] - ) - - assert_equal expected, pay.create_pay!(pay) - end - - def test_pay_with_invalid_amount_value_zero - exception = assert_raises { Vonage::Voice::Actions::Pay.new(amount: 0.00) } - - assert_match "Invalid 'amount' value, must be greater than 0", exception.message - end - - def test_pay_with_invalid_amount_value_non_decimal - exception = assert_raises { Vonage::Voice::Actions::Pay.new(amount: 1) } - - assert_match "Invalid 'amount' value, must be a float", exception.message - end - - def test_pay_with_invalid_event_url_structure - exception = assert_raises { Vonage::Voice::Actions::Pay.new(amount: 9.99, eventUrl: "https://example.com/pay") } - - assert_match "Expected 'eventUrl' parameter to be an Array containing a single string item", exception.message - end - - def test_pay_with_invalid_event_url_value - exception = assert_raises { Vonage::Voice::Actions::Pay.new(amount: 9.99, eventUrl: ["example.com/pay"]) } - - assert_match "Invalid 'eventUrl' value, must be a valid URL", exception.message - end - - def test_create_pay_with_invalid_voice_structure - exception = assert_raises { Vonage::Voice::Actions::Pay.new(amount: 9.99, voice: 'en-GB') } - - assert_match "Expected 'voice' value to be a Hash", exception.message - end - - def test_create_pay_with_invalid_style_value - exception = assert_raises { Vonage::Voice::Actions::Pay.new(amount: 9.99, voice: { language: 'en-GB', style: 'baritone' }) } - - assert_match "Expected 'style' value to be an Integer", exception.message - end - - def test_create_pay_with_prompts_type_missing - exception = assert_raises { Vonage::Voice::Actions::Pay.new( - amount: 9.99, - prompts: [{ - text: 'Please enter expiration date', - errors: { - InvalidExpirationDate: { - text: 'Invalid expiration date. Please try again' - } - } - }] - )} - - assert_match "Invalid 'prompt', 'type' is required", exception.message - end - - def test_create_pay_with_prompts_text_missing - exception = assert_raises { Vonage::Voice::Actions::Pay.new( - amount: 9.99, - prompts: [{ - type: 'ExpirationDate', - errors: { - InvalidExpirationDate: { - text: 'Invalid expiration date. Please try again' - } - } - }] - )} - - assert_match "Invalid 'prompt', 'text' is required", exception.message - end - - def test_create_pay_with_prompts_errors_missing - exception = assert_raises { Vonage::Voice::Actions::Pay.new( - amount: 9.99, - prompts: [{ - type: 'ExpirationDate', - text: 'Please enter expiration date' - }] - )} - - assert_match "Invalid 'prompt', 'errors' is required", exception.message - end -end diff --git a/test/vonage/voice/actions/talk_test.rb b/test/vonage/voice/actions/talk_test.rb index 69ed4065..e9539cf9 100644 --- a/test/vonage/voice/actions/talk_test.rb +++ b/test/vonage/voice/actions/talk_test.rb @@ -38,12 +38,18 @@ def test_talk_with_invalid_loop_value def test_talk_with_invalid_level exception = assert_raises { Vonage::Voice::Actions::Talk.new({ text: 'Sample Text', level: -2 }) } - assert_match "Expected 'level' value to be a number between -1 and 1", exception.message + assert_match "Expected 'level' value to be a number between -1 and 1", exception.message end def test_talk_with_invalid_style_value exception = assert_raises { Vonage::Voice::Actions::Talk.new({ text: 'Sample Text', style: 'baritone' }) } - assert_match "Expected 'style' value to be an Integer", exception.message + assert_match "Expected 'style' value to be an Integer", exception.message end -end \ No newline at end of file + + def test_talk_with_invalid_premium_value + exception = assert_raises { Vonage::Voice::Actions::Talk.new({ text: 'Sample Text', premium: 'foo' }) } + + assert_match "Expected 'premium' value to be a Boolean", exception.message + end +end