ibm_watson 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.
- checksums.yaml +7 -0
 - data/README.md +258 -0
 - data/bin/console +14 -0
 - data/bin/setup +8 -0
 - data/lib/ibm_watson.rb +16 -0
 - data/lib/ibm_watson/assistant_v1.rb +1997 -0
 - data/lib/ibm_watson/detailed_response.rb +21 -0
 - data/lib/ibm_watson/discovery_v1.rb +2039 -0
 - data/lib/ibm_watson/iam_token_manager.rb +166 -0
 - data/lib/ibm_watson/language_translator_v3.rb +411 -0
 - data/lib/ibm_watson/natural_language_classifier_v1.rb +309 -0
 - data/lib/ibm_watson/natural_language_understanding_v1.rb +297 -0
 - data/lib/ibm_watson/personality_insights_v3.rb +260 -0
 - data/lib/ibm_watson/speech_to_text_v1.rb +2153 -0
 - data/lib/ibm_watson/text_to_speech_v1.rb +716 -0
 - data/lib/ibm_watson/tone_analyzer_v3.rb +287 -0
 - data/lib/ibm_watson/version.rb +3 -0
 - data/lib/ibm_watson/visual_recognition_v3.rb +579 -0
 - data/lib/ibm_watson/watson_api_exception.rb +41 -0
 - data/lib/ibm_watson/watson_service.rb +180 -0
 - data/lib/ibm_watson/websocket/recognize_callback.rb +32 -0
 - data/lib/ibm_watson/websocket/speech_to_text_websocket_listener.rb +162 -0
 - data/rakefile +45 -0
 - data/test/integration/test_assistant_v1.rb +645 -0
 - data/test/integration/test_discovery_v1.rb +200 -0
 - data/test/integration/test_iam_assistant_v1.rb +707 -0
 - data/test/integration/test_language_translator_v3.rb +81 -0
 - data/test/integration/test_natural_language_classifier_v1.rb +69 -0
 - data/test/integration/test_natural_language_understanding_v1.rb +98 -0
 - data/test/integration/test_personality_insights_v3.rb +95 -0
 - data/test/integration/test_speech_to_text_v1.rb +187 -0
 - data/test/integration/test_text_to_speech_v1.rb +81 -0
 - data/test/integration/test_tone_analyzer_v3.rb +72 -0
 - data/test/integration/test_visual_recognition_v3.rb +64 -0
 - data/test/test_helper.rb +22 -0
 - data/test/unit/test_assistant_v1.rb +1598 -0
 - data/test/unit/test_discovery_v1.rb +1144 -0
 - data/test/unit/test_iam_token_manager.rb +165 -0
 - data/test/unit/test_language_translator_v3.rb +461 -0
 - data/test/unit/test_natural_language_classifier_v1.rb +187 -0
 - data/test/unit/test_natural_language_understanding_v1.rb +132 -0
 - data/test/unit/test_personality_insights_v3.rb +172 -0
 - data/test/unit/test_speech_to_text_v1.rb +755 -0
 - data/test/unit/test_text_to_speech_v1.rb +336 -0
 - data/test/unit/test_tone_analyzer_v3.rb +200 -0
 - data/test/unit/test_vcap_using_personality_insights.rb +150 -0
 - data/test/unit/test_visual_recognition_v3.rb +345 -0
 - metadata +302 -0
 
| 
         @@ -0,0 +1,41 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            require("json")
         
     | 
| 
      
 4 
     | 
    
         
            +
            # Custom exception class for errors returned from Watson APIs
         
     | 
| 
      
 5 
     | 
    
         
            +
            class WatsonApiException < StandardError
         
     | 
| 
      
 6 
     | 
    
         
            +
              attr_reader :code, :error, :info, :transaction_id, :global_transaction_id
         
     | 
| 
      
 7 
     | 
    
         
            +
              # :param HTTP::Response response: The response object from the Watson API
         
     | 
| 
      
 8 
     | 
    
         
            +
              def initialize(code: nil, error: nil, info: nil, transaction_id: nil, global_transaction_id: nil, response: nil)
         
     | 
| 
      
 9 
     | 
    
         
            +
                if code.nil? || error.nil?
         
     | 
| 
      
 10 
     | 
    
         
            +
                  @code = response.code
         
     | 
| 
      
 11 
     | 
    
         
            +
                  @error = response.reason
         
     | 
| 
      
 12 
     | 
    
         
            +
                  unless response.body.empty?
         
     | 
| 
      
 13 
     | 
    
         
            +
                    body_hash = JSON.parse(response.body.to_s)
         
     | 
| 
      
 14 
     | 
    
         
            +
                    @code = body_hash["code"] || body_hash["error_code"]
         
     | 
| 
      
 15 
     | 
    
         
            +
                    @error = body_hash["error"] || body_hash["error_message"]
         
     | 
| 
      
 16 
     | 
    
         
            +
                    %w[code error_code error error_message].each { |k| body_hash.delete(k) }
         
     | 
| 
      
 17 
     | 
    
         
            +
                    @info = body_hash
         
     | 
| 
      
 18 
     | 
    
         
            +
                  end
         
     | 
| 
      
 19 
     | 
    
         
            +
                  @transaction_id = transaction_id
         
     | 
| 
      
 20 
     | 
    
         
            +
                  @global_transaction_id = global_transaction_id
         
     | 
| 
      
 21 
     | 
    
         
            +
                  @transaction_id = response.headers["X-DP-Watson-Tran-ID"] if response.headers.include?("X-DP-Watson-Tran-ID")
         
     | 
| 
      
 22 
     | 
    
         
            +
                  @global_transaction_id = response.headers["X-Global-Transaction-ID"] if response.headers.include?("X-Global-Transaction-ID")
         
     | 
| 
      
 23 
     | 
    
         
            +
                else
         
     | 
| 
      
 24 
     | 
    
         
            +
                  # :nocov:
         
     | 
| 
      
 25 
     | 
    
         
            +
                  @code = code
         
     | 
| 
      
 26 
     | 
    
         
            +
                  @error = error
         
     | 
| 
      
 27 
     | 
    
         
            +
                  @info = info
         
     | 
| 
      
 28 
     | 
    
         
            +
                  @transaction_id = transaction_id
         
     | 
| 
      
 29 
     | 
    
         
            +
                  @global_transaction_id = global_transaction_id
         
     | 
| 
      
 30 
     | 
    
         
            +
                  # :nocov:
         
     | 
| 
      
 31 
     | 
    
         
            +
                end
         
     | 
| 
      
 32 
     | 
    
         
            +
              end
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
              def to_s
         
     | 
| 
      
 35 
     | 
    
         
            +
                msg = "Error: #{@error}, Code: #{@code}"
         
     | 
| 
      
 36 
     | 
    
         
            +
                msg += ", Information: #{@info}" unless @info.nil?
         
     | 
| 
      
 37 
     | 
    
         
            +
                msg += ", X-dp-watson-tran-id: #{@transaction_id}" unless @transaction_id.nil?
         
     | 
| 
      
 38 
     | 
    
         
            +
                msg += ", X-global-transaction-id: #{@global_transaction_id}" unless @global_transaction_id.nil?
         
     | 
| 
      
 39 
     | 
    
         
            +
                msg
         
     | 
| 
      
 40 
     | 
    
         
            +
              end
         
     | 
| 
      
 41 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,180 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            require("http")
         
     | 
| 
      
 4 
     | 
    
         
            +
            require("rbconfig")
         
     | 
| 
      
 5 
     | 
    
         
            +
            require("stringio")
         
     | 
| 
      
 6 
     | 
    
         
            +
            require("json")
         
     | 
| 
      
 7 
     | 
    
         
            +
            require_relative("./detailed_response.rb")
         
     | 
| 
      
 8 
     | 
    
         
            +
            require_relative("./watson_api_exception.rb")
         
     | 
| 
      
 9 
     | 
    
         
            +
            require_relative("./iam_token_manager.rb")
         
     | 
| 
      
 10 
     | 
    
         
            +
            require_relative("./version.rb")
         
     | 
| 
      
 11 
     | 
    
         
            +
            # require("httplog")
         
     | 
| 
      
 12 
     | 
    
         
            +
            # HttpLog.configure do |config|
         
     | 
| 
      
 13 
     | 
    
         
            +
            #   config.log_connect   = true
         
     | 
| 
      
 14 
     | 
    
         
            +
            #   config.log_request   = true
         
     | 
| 
      
 15 
     | 
    
         
            +
            #   config.log_headers   = true
         
     | 
| 
      
 16 
     | 
    
         
            +
            #   config.log_data      = true
         
     | 
| 
      
 17 
     | 
    
         
            +
            #   config.log_status    = true
         
     | 
| 
      
 18 
     | 
    
         
            +
            #   config.log_response  = true
         
     | 
| 
      
 19 
     | 
    
         
            +
            # end
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
            # Class for interacting with the Watson API
         
     | 
| 
      
 22 
     | 
    
         
            +
            class WatsonService
         
     | 
| 
      
 23 
     | 
    
         
            +
              attr_accessor :password, :url, :username
         
     | 
| 
      
 24 
     | 
    
         
            +
              attr_reader :conn
         
     | 
| 
      
 25 
     | 
    
         
            +
              def initialize(vars)
         
     | 
| 
      
 26 
     | 
    
         
            +
                defaults = {
         
     | 
| 
      
 27 
     | 
    
         
            +
                  vcap_services_name: nil,
         
     | 
| 
      
 28 
     | 
    
         
            +
                  username: nil,
         
     | 
| 
      
 29 
     | 
    
         
            +
                  password: nil,
         
     | 
| 
      
 30 
     | 
    
         
            +
                  use_vcap_services: true,
         
     | 
| 
      
 31 
     | 
    
         
            +
                  api_key: nil,
         
     | 
| 
      
 32 
     | 
    
         
            +
                  x_watson_learning_opt_out: false,
         
     | 
| 
      
 33 
     | 
    
         
            +
                  iam_api_key: nil,
         
     | 
| 
      
 34 
     | 
    
         
            +
                  iam_access_token: nil,
         
     | 
| 
      
 35 
     | 
    
         
            +
                  iam_url: nil
         
     | 
| 
      
 36 
     | 
    
         
            +
                }
         
     | 
| 
      
 37 
     | 
    
         
            +
                vars = defaults.merge(vars)
         
     | 
| 
      
 38 
     | 
    
         
            +
                @url = vars[:url]
         
     | 
| 
      
 39 
     | 
    
         
            +
                @username = nil
         
     | 
| 
      
 40 
     | 
    
         
            +
                @password = nil
         
     | 
| 
      
 41 
     | 
    
         
            +
                @token_manager = nil
         
     | 
| 
      
 42 
     | 
    
         
            +
                @temp_headers = nil
         
     | 
| 
      
 43 
     | 
    
         
            +
             
     | 
| 
      
 44 
     | 
    
         
            +
                user_agent_string = "watson-apis-ruby-sdk-" + IBMWatson::VERSION
         
     | 
| 
      
 45 
     | 
    
         
            +
                user_agent_string += " #{RbConfig::CONFIG["host"]}"
         
     | 
| 
      
 46 
     | 
    
         
            +
                user_agent_string += " #{RbConfig::CONFIG["RUBY_BASE_NAME"]}-#{RbConfig::CONFIG["RUBY_PROGRAM_VERSION"]}"
         
     | 
| 
      
 47 
     | 
    
         
            +
             
     | 
| 
      
 48 
     | 
    
         
            +
                headers = {
         
     | 
| 
      
 49 
     | 
    
         
            +
                  "User-Agent" => user_agent_string
         
     | 
| 
      
 50 
     | 
    
         
            +
                }
         
     | 
| 
      
 51 
     | 
    
         
            +
                headers["x-watson-learning-opt-out"] = true if vars[:x_watson_learning_opt_out]
         
     | 
| 
      
 52 
     | 
    
         
            +
                if vars[:use_vcap_services]
         
     | 
| 
      
 53 
     | 
    
         
            +
                  @vcap_service_credentials = load_from_vcap_services(service_name: vars[:vcap_services_name])
         
     | 
| 
      
 54 
     | 
    
         
            +
                  if !@vcap_service_credentials.nil? && @vcap_service_credentials.instance_of?(Hash)
         
     | 
| 
      
 55 
     | 
    
         
            +
                    @url = @vcap_service_credentials["url"]
         
     | 
| 
      
 56 
     | 
    
         
            +
                    @username = @vcap_service_credentials["username"] if @vcap_service_credentials.key?("username")
         
     | 
| 
      
 57 
     | 
    
         
            +
                    @password = @vcap_service_credentials["password"] if @vcap_service_credentials.key?("password")
         
     | 
| 
      
 58 
     | 
    
         
            +
                    @iam_api_key = @vcap_service_credentials["iam_api_key"] if @vcap_service_credentials.key?("iam_api_key")
         
     | 
| 
      
 59 
     | 
    
         
            +
                    @iam_access_token = @vcap_service_credentials["iam_access_token"] if @vcap_service_credentials.key?("iam_access_token")
         
     | 
| 
      
 60 
     | 
    
         
            +
                    @iam_url = @vcap_service_credentials["iam_url"] if @vcap_service_credentials.key?("iam_url")
         
     | 
| 
      
 61 
     | 
    
         
            +
                  end
         
     | 
| 
      
 62 
     | 
    
         
            +
                end
         
     | 
| 
      
 63 
     | 
    
         
            +
             
     | 
| 
      
 64 
     | 
    
         
            +
                if !vars[:iam_access_token].nil? || !vars[:iam_api_key].nil?
         
     | 
| 
      
 65 
     | 
    
         
            +
                  _token_manager(iam_api_key: vars[:iam_api_key], iam_access_token: vars[:iam_access_token], iam_url: vars[:iam_url])
         
     | 
| 
      
 66 
     | 
    
         
            +
                elsif !vars[:username].nil? && !vars[:password].nil?
         
     | 
| 
      
 67 
     | 
    
         
            +
                  if vars[:username] == "apikey"
         
     | 
| 
      
 68 
     | 
    
         
            +
                    _iam_api_key(iam_api_key: vars[:password])
         
     | 
| 
      
 69 
     | 
    
         
            +
                  else
         
     | 
| 
      
 70 
     | 
    
         
            +
                    @username = vars[:username]
         
     | 
| 
      
 71 
     | 
    
         
            +
                    @password = vars[:password]
         
     | 
| 
      
 72 
     | 
    
         
            +
                  end
         
     | 
| 
      
 73 
     | 
    
         
            +
                end
         
     | 
| 
      
 74 
     | 
    
         
            +
             
     | 
| 
      
 75 
     | 
    
         
            +
                @conn = HTTP::Client.new(
         
     | 
| 
      
 76 
     | 
    
         
            +
                  headers: headers
         
     | 
| 
      
 77 
     | 
    
         
            +
                ).timeout(
         
     | 
| 
      
 78 
     | 
    
         
            +
                  :per_operation,
         
     | 
| 
      
 79 
     | 
    
         
            +
                  read: 60,
         
     | 
| 
      
 80 
     | 
    
         
            +
                  write: 60,
         
     | 
| 
      
 81 
     | 
    
         
            +
                  connect: 60
         
     | 
| 
      
 82 
     | 
    
         
            +
                )
         
     | 
| 
      
 83 
     | 
    
         
            +
              end
         
     | 
| 
      
 84 
     | 
    
         
            +
             
     | 
| 
      
 85 
     | 
    
         
            +
              def load_from_vcap_services(service_name:)
         
     | 
| 
      
 86 
     | 
    
         
            +
                vcap_services = ENV["VCAP_SERVICES"]
         
     | 
| 
      
 87 
     | 
    
         
            +
                unless vcap_services.nil?
         
     | 
| 
      
 88 
     | 
    
         
            +
                  services = JSON.parse(vcap_services)
         
     | 
| 
      
 89 
     | 
    
         
            +
                  return services[service_name][0]["credentials"] if services.key?(service_name)
         
     | 
| 
      
 90 
     | 
    
         
            +
                end
         
     | 
| 
      
 91 
     | 
    
         
            +
                nil
         
     | 
| 
      
 92 
     | 
    
         
            +
              end
         
     | 
| 
      
 93 
     | 
    
         
            +
             
     | 
| 
      
 94 
     | 
    
         
            +
              def add_default_headers(headers: {})
         
     | 
| 
      
 95 
     | 
    
         
            +
                raise TypeError unless headers.instance_of?(Hash)
         
     | 
| 
      
 96 
     | 
    
         
            +
                headers.each_pair { |k, v| @conn.default_options.headers.add(k, v) }
         
     | 
| 
      
 97 
     | 
    
         
            +
              end
         
     | 
| 
      
 98 
     | 
    
         
            +
             
     | 
| 
      
 99 
     | 
    
         
            +
              def _token_manager(iam_api_key: nil, iam_access_token: nil, iam_url: nil)
         
     | 
| 
      
 100 
     | 
    
         
            +
                @iam_api_key = iam_api_key
         
     | 
| 
      
 101 
     | 
    
         
            +
                @iam_access_token = iam_access_token
         
     | 
| 
      
 102 
     | 
    
         
            +
                @iam_url = iam_url
         
     | 
| 
      
 103 
     | 
    
         
            +
                @token_manager = IAMTokenManager.new(iam_api_key: iam_api_key, iam_access_token: iam_access_token, iam_url: iam_url)
         
     | 
| 
      
 104 
     | 
    
         
            +
              end
         
     | 
| 
      
 105 
     | 
    
         
            +
             
     | 
| 
      
 106 
     | 
    
         
            +
              def _iam_access_token(iam_access_token:)
         
     | 
| 
      
 107 
     | 
    
         
            +
                @token_manager._access_token(iam_access_token: iam_access_token) unless @token_manager.nil?
         
     | 
| 
      
 108 
     | 
    
         
            +
                @token_manager = IAMTokenManager.new(iam_access_token: iam_access_token) if @token_manager.nil?
         
     | 
| 
      
 109 
     | 
    
         
            +
                @iam_access_token = iam_access_token
         
     | 
| 
      
 110 
     | 
    
         
            +
              end
         
     | 
| 
      
 111 
     | 
    
         
            +
             
     | 
| 
      
 112 
     | 
    
         
            +
              def _iam_api_key(iam_api_key:)
         
     | 
| 
      
 113 
     | 
    
         
            +
                @token_manager._iam_api_key(iam_api_key: iam_api_key) unless @token_manager.nil?
         
     | 
| 
      
 114 
     | 
    
         
            +
                @token_manager = IAMTokenManager.new(iam_api_key: iam_api_key) if @token_manager.nil?
         
     | 
| 
      
 115 
     | 
    
         
            +
                @iam_api_key = iam_api_key
         
     | 
| 
      
 116 
     | 
    
         
            +
              end
         
     | 
| 
      
 117 
     | 
    
         
            +
             
     | 
| 
      
 118 
     | 
    
         
            +
              # @return [DetailedResponse]
         
     | 
| 
      
 119 
     | 
    
         
            +
              def request(args)
         
     | 
| 
      
 120 
     | 
    
         
            +
                defaults = { method: nil, url: nil, accept_json: false, headers: nil, params: nil, json: {}, data: nil }
         
     | 
| 
      
 121 
     | 
    
         
            +
                args = defaults.merge(args)
         
     | 
| 
      
 122 
     | 
    
         
            +
                args[:data].delete_if { |_k, v| v.nil? } if args[:data].instance_of?(Hash)
         
     | 
| 
      
 123 
     | 
    
         
            +
                args[:json] = args[:data].merge(args[:json]) if args[:data].respond_to?(:merge)
         
     | 
| 
      
 124 
     | 
    
         
            +
                args[:json] = args[:data] if args[:json].empty? || (args[:data].instance_of?(String) && !args[:data].empty?)
         
     | 
| 
      
 125 
     | 
    
         
            +
                args[:json].delete_if { |_k, v| v.nil? } if args[:json].instance_of?(Hash)
         
     | 
| 
      
 126 
     | 
    
         
            +
                args[:headers]["Accept"] = "application/json" if args[:accept_json]
         
     | 
| 
      
 127 
     | 
    
         
            +
                args[:headers]["Content-Type"] = "application/json" unless args[:headers].key?("Content-Type")
         
     | 
| 
      
 128 
     | 
    
         
            +
                args[:json] = args[:json].to_json if args[:json].instance_of?(Hash)
         
     | 
| 
      
 129 
     | 
    
         
            +
                args[:headers].delete_if { |_k, v| v.nil? } if args[:headers].instance_of?(Hash)
         
     | 
| 
      
 130 
     | 
    
         
            +
                args[:params].delete_if { |_k, v| v.nil? } if args[:params].instance_of?(Hash)
         
     | 
| 
      
 131 
     | 
    
         
            +
                args[:form].delete_if { |_k, v| v.nil? } if args.key?(:form)
         
     | 
| 
      
 132 
     | 
    
         
            +
                args.delete_if { |_, v| v.nil? }
         
     | 
| 
      
 133 
     | 
    
         
            +
                args[:headers].delete("Content-Type") if args.key?(:form) || args[:json].nil?
         
     | 
| 
      
 134 
     | 
    
         
            +
             
     | 
| 
      
 135 
     | 
    
         
            +
                if @username == "apikey"
         
     | 
| 
      
 136 
     | 
    
         
            +
                  _iam_api_key(iam_api_key: @password)
         
     | 
| 
      
 137 
     | 
    
         
            +
                  @username = nil
         
     | 
| 
      
 138 
     | 
    
         
            +
                end
         
     | 
| 
      
 139 
     | 
    
         
            +
             
     | 
| 
      
 140 
     | 
    
         
            +
                conn = @conn
         
     | 
| 
      
 141 
     | 
    
         
            +
                if !@token_manager.nil?
         
     | 
| 
      
 142 
     | 
    
         
            +
                  access_token = @token_manager._token
         
     | 
| 
      
 143 
     | 
    
         
            +
                  args[:headers]["Authorization"] = "Bearer #{access_token}"
         
     | 
| 
      
 144 
     | 
    
         
            +
                elsif !@username.nil? && !@password.nil?
         
     | 
| 
      
 145 
     | 
    
         
            +
                  conn = @conn.basic_auth(user: @username, pass: @password)
         
     | 
| 
      
 146 
     | 
    
         
            +
                end
         
     | 
| 
      
 147 
     | 
    
         
            +
             
     | 
| 
      
 148 
     | 
    
         
            +
                args[:headers] = args[:headers].merge(@temp_headers) unless @temp_headers.nil?
         
     | 
| 
      
 149 
     | 
    
         
            +
                @temp_headers = nil unless @temp_headers.nil?
         
     | 
| 
      
 150 
     | 
    
         
            +
             
     | 
| 
      
 151 
     | 
    
         
            +
                if args.key?(:form)
         
     | 
| 
      
 152 
     | 
    
         
            +
                  response = conn.follow.request(
         
     | 
| 
      
 153 
     | 
    
         
            +
                    args[:method],
         
     | 
| 
      
 154 
     | 
    
         
            +
                    HTTP::URI.parse(@url + args[:url]),
         
     | 
| 
      
 155 
     | 
    
         
            +
                    headers: conn.default_options.headers.merge(HTTP::Headers.coerce(args[:headers])),
         
     | 
| 
      
 156 
     | 
    
         
            +
                    params: args[:params],
         
     | 
| 
      
 157 
     | 
    
         
            +
                    form: args[:form]
         
     | 
| 
      
 158 
     | 
    
         
            +
                  )
         
     | 
| 
      
 159 
     | 
    
         
            +
                else
         
     | 
| 
      
 160 
     | 
    
         
            +
                  response = conn.follow.request(
         
     | 
| 
      
 161 
     | 
    
         
            +
                    args[:method],
         
     | 
| 
      
 162 
     | 
    
         
            +
                    HTTP::URI.parse(@url + args[:url]),
         
     | 
| 
      
 163 
     | 
    
         
            +
                    headers: conn.default_options.headers.merge(HTTP::Headers.coerce(args[:headers])),
         
     | 
| 
      
 164 
     | 
    
         
            +
                    body: args[:json],
         
     | 
| 
      
 165 
     | 
    
         
            +
                    params: args[:params]
         
     | 
| 
      
 166 
     | 
    
         
            +
                  )
         
     | 
| 
      
 167 
     | 
    
         
            +
                end
         
     | 
| 
      
 168 
     | 
    
         
            +
                return DetailedResponse.new(response: response) if (200..299).cover?(response.code)
         
     | 
| 
      
 169 
     | 
    
         
            +
                raise WatsonApiException.new(response: response)
         
     | 
| 
      
 170 
     | 
    
         
            +
              end
         
     | 
| 
      
 171 
     | 
    
         
            +
             
     | 
| 
      
 172 
     | 
    
         
            +
              # @note Chainable
         
     | 
| 
      
 173 
     | 
    
         
            +
              # @param headers [Hash] Custom headers to be sent with the request
         
     | 
| 
      
 174 
     | 
    
         
            +
              # @return [self]
         
     | 
| 
      
 175 
     | 
    
         
            +
              def headers(headers)
         
     | 
| 
      
 176 
     | 
    
         
            +
                raise TypeError("Expected Hash type, received #{headers.class}") unless headers.instance_of?(Hash)
         
     | 
| 
      
 177 
     | 
    
         
            +
                @temp_headers = headers
         
     | 
| 
      
 178 
     | 
    
         
            +
                self
         
     | 
| 
      
 179 
     | 
    
         
            +
              end
         
     | 
| 
      
 180 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,32 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module IBMWatson
         
     | 
| 
      
 4 
     | 
    
         
            +
              # Abstract class for Recognize Callbacks
         
     | 
| 
      
 5 
     | 
    
         
            +
              class RecognizeCallback
         
     | 
| 
      
 6 
     | 
    
         
            +
                def initialize(*); end
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
                # Called when an interim result is received
         
     | 
| 
      
 9 
     | 
    
         
            +
                def on_transcription(transcript:); end
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
                # Called when a WebSocket connection is made
         
     | 
| 
      
 12 
     | 
    
         
            +
                def on_connected; end
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
                # Called when there is an error in the WebSocket connection
         
     | 
| 
      
 15 
     | 
    
         
            +
                def on_error(error:); end
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                # Called when there is an inactivity timeout
         
     | 
| 
      
 18 
     | 
    
         
            +
                def on_inactivity_timeout(error:); end
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
                # Called when the service is listening for audio
         
     | 
| 
      
 21 
     | 
    
         
            +
                def on_listening; end
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                # Called after the service returns the final result for the transcription
         
     | 
| 
      
 24 
     | 
    
         
            +
                def on_transcription_complete; end
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
                # Called when the service returns the final hypothesis
         
     | 
| 
      
 27 
     | 
    
         
            +
                def on_hypothesis(hypothesis:); end
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
                # Called when the service returns results. The data is returned unparsed
         
     | 
| 
      
 30 
     | 
    
         
            +
                def on_data(data:); end
         
     | 
| 
      
 31 
     | 
    
         
            +
              end
         
     | 
| 
      
 32 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,162 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            require("eventmachine")
         
     | 
| 
      
 4 
     | 
    
         
            +
            require("faye/websocket")
         
     | 
| 
      
 5 
     | 
    
         
            +
            require("json")
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
            ONE_KB = 1024
         
     | 
| 
      
 8 
     | 
    
         
            +
            TIMEOUT_PREFIX = "No speech detected for".freeze
         
     | 
| 
      
 9 
     | 
    
         
            +
            CLOSE_SIGNAL = 1000
         
     | 
| 
      
 10 
     | 
    
         
            +
            TEN_MILLISECONDS = 0.01
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
            # Class for interacting with the WebSocket API
         
     | 
| 
      
 13 
     | 
    
         
            +
            class WebSocketClient
         
     | 
| 
      
 14 
     | 
    
         
            +
              def initialize(audio: nil, chunk_data:, options:, recognize_callback:, url:, headers:)
         
     | 
| 
      
 15 
     | 
    
         
            +
                @audio = audio
         
     | 
| 
      
 16 
     | 
    
         
            +
                @options = options
         
     | 
| 
      
 17 
     | 
    
         
            +
                @callback = recognize_callback
         
     | 
| 
      
 18 
     | 
    
         
            +
                @bytes_sent = 0
         
     | 
| 
      
 19 
     | 
    
         
            +
                @headers = headers
         
     | 
| 
      
 20 
     | 
    
         
            +
                @is_listening = false
         
     | 
| 
      
 21 
     | 
    
         
            +
                @url = url
         
     | 
| 
      
 22 
     | 
    
         
            +
                @timer = nil
         
     | 
| 
      
 23 
     | 
    
         
            +
                @chunk_data = chunk_data
         
     | 
| 
      
 24 
     | 
    
         
            +
                @mic_running = false
         
     | 
| 
      
 25 
     | 
    
         
            +
                @data_size = audio.nil? ? 0 : @audio.size
         
     | 
| 
      
 26 
     | 
    
         
            +
                @queue = Queue.new
         
     | 
| 
      
 27 
     | 
    
         
            +
              end
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
              def start
         
     | 
| 
      
 30 
     | 
    
         
            +
                on_open = lambda do |event|
         
     | 
| 
      
 31 
     | 
    
         
            +
                  on_connect(event)
         
     | 
| 
      
 32 
     | 
    
         
            +
                  @client.send(build_start_message(options: @options))
         
     | 
| 
      
 33 
     | 
    
         
            +
                  @mic_running = true if @chunk_data
         
     | 
| 
      
 34 
     | 
    
         
            +
                  send_audio(data: @audio)
         
     | 
| 
      
 35 
     | 
    
         
            +
                end
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
                on_message = lambda do |event|
         
     | 
| 
      
 38 
     | 
    
         
            +
                  json_object = JSON.parse(event.data)
         
     | 
| 
      
 39 
     | 
    
         
            +
                  if json_object.key?("error")
         
     | 
| 
      
 40 
     | 
    
         
            +
                    error = json_object["error"]
         
     | 
| 
      
 41 
     | 
    
         
            +
                    if error.start_with?(TIMEOUT_PREFIX)
         
     | 
| 
      
 42 
     | 
    
         
            +
                      @callback.on_inactivity_timeout(error: error)
         
     | 
| 
      
 43 
     | 
    
         
            +
                    else
         
     | 
| 
      
 44 
     | 
    
         
            +
                      @callback.on_error(error: error)
         
     | 
| 
      
 45 
     | 
    
         
            +
                    end
         
     | 
| 
      
 46 
     | 
    
         
            +
                  elsif json_object.key?("state")
         
     | 
| 
      
 47 
     | 
    
         
            +
                    if !@is_listening
         
     | 
| 
      
 48 
     | 
    
         
            +
                      @is_listening = true
         
     | 
| 
      
 49 
     | 
    
         
            +
                    else
         
     | 
| 
      
 50 
     | 
    
         
            +
                      @client.send(build_close_message)
         
     | 
| 
      
 51 
     | 
    
         
            +
                      @callback.on_transcription_complete
         
     | 
| 
      
 52 
     | 
    
         
            +
                      @client.close(CLOSE_SIGNAL)
         
     | 
| 
      
 53 
     | 
    
         
            +
                    end
         
     | 
| 
      
 54 
     | 
    
         
            +
                  elsif json_object.key?("results") || json_object.key?("speaker_labels")
         
     | 
| 
      
 55 
     | 
    
         
            +
                    hypothesis = ""
         
     | 
| 
      
 56 
     | 
    
         
            +
                    unless json_object["results"].nil?
         
     | 
| 
      
 57 
     | 
    
         
            +
                      hypothesis = json_object.dig("results", 0, "alternatives", 0, "transcript")
         
     | 
| 
      
 58 
     | 
    
         
            +
                      b_final = json_object.dig("results", 0, "final")
         
     | 
| 
      
 59 
     | 
    
         
            +
                      transcripts = extract_transcripts(alternatives: json_object.dig("results", 0, "alternatives"))
         
     | 
| 
      
 60 
     | 
    
         
            +
             
     | 
| 
      
 61 
     | 
    
         
            +
                      @callback.on_hypothesis(hypothesis: hypothesis) if b_final
         
     | 
| 
      
 62 
     | 
    
         
            +
             
     | 
| 
      
 63 
     | 
    
         
            +
                      @callback.on_transcription(transcript: transcripts)
         
     | 
| 
      
 64 
     | 
    
         
            +
                      @callback.on_data(data: json_object)
         
     | 
| 
      
 65 
     | 
    
         
            +
                    end
         
     | 
| 
      
 66 
     | 
    
         
            +
                  end
         
     | 
| 
      
 67 
     | 
    
         
            +
                end
         
     | 
| 
      
 68 
     | 
    
         
            +
             
     | 
| 
      
 69 
     | 
    
         
            +
                on_close = lambda do |_event|
         
     | 
| 
      
 70 
     | 
    
         
            +
                  @client = nil
         
     | 
| 
      
 71 
     | 
    
         
            +
                  EM.stop_event_loop
         
     | 
| 
      
 72 
     | 
    
         
            +
                end
         
     | 
| 
      
 73 
     | 
    
         
            +
             
     | 
| 
      
 74 
     | 
    
         
            +
                on_error = lambda do |event|
         
     | 
| 
      
 75 
     | 
    
         
            +
                  p event.message
         
     | 
| 
      
 76 
     | 
    
         
            +
                end
         
     | 
| 
      
 77 
     | 
    
         
            +
             
     | 
| 
      
 78 
     | 
    
         
            +
                EM.reactor_thread.join unless EM.reactor_thread.nil?
         
     | 
| 
      
 79 
     | 
    
         
            +
                EM.run do
         
     | 
| 
      
 80 
     | 
    
         
            +
                  @client = Faye::WebSocket::Client.new(@url, nil, headers: @headers)
         
     | 
| 
      
 81 
     | 
    
         
            +
                  @client.onclose = on_close
         
     | 
| 
      
 82 
     | 
    
         
            +
                  @client.onerror = on_error
         
     | 
| 
      
 83 
     | 
    
         
            +
                  @client.onmessage = on_message
         
     | 
| 
      
 84 
     | 
    
         
            +
                  @client.onopen = on_open
         
     | 
| 
      
 85 
     | 
    
         
            +
                  @client.add_listener(Faye::WebSocket::API::Event.create("open"))
         
     | 
| 
      
 86 
     | 
    
         
            +
                  @client.add_listener(Faye::WebSocket::API::Event.create("message"))
         
     | 
| 
      
 87 
     | 
    
         
            +
                  @client.add_listener(Faye::WebSocket::API::Event.create("close"))
         
     | 
| 
      
 88 
     | 
    
         
            +
                  @client.add_listener(Faye::WebSocket::API::Event.create("error"))
         
     | 
| 
      
 89 
     | 
    
         
            +
                end
         
     | 
| 
      
 90 
     | 
    
         
            +
              end
         
     | 
| 
      
 91 
     | 
    
         
            +
             
     | 
| 
      
 92 
     | 
    
         
            +
              def add_audio_chunk(chunk:)
         
     | 
| 
      
 93 
     | 
    
         
            +
                @data_size += chunk.size
         
     | 
| 
      
 94 
     | 
    
         
            +
                @queue << chunk
         
     | 
| 
      
 95 
     | 
    
         
            +
              end
         
     | 
| 
      
 96 
     | 
    
         
            +
             
     | 
| 
      
 97 
     | 
    
         
            +
              def stop_audio
         
     | 
| 
      
 98 
     | 
    
         
            +
                @mic_running = false
         
     | 
| 
      
 99 
     | 
    
         
            +
              end
         
     | 
| 
      
 100 
     | 
    
         
            +
             
     | 
| 
      
 101 
     | 
    
         
            +
              private
         
     | 
| 
      
 102 
     | 
    
         
            +
             
     | 
| 
      
 103 
     | 
    
         
            +
              def on_connect(_response)
         
     | 
| 
      
 104 
     | 
    
         
            +
                @callback.on_connected
         
     | 
| 
      
 105 
     | 
    
         
            +
              end
         
     | 
| 
      
 106 
     | 
    
         
            +
             
     | 
| 
      
 107 
     | 
    
         
            +
              def build_start_message(options:)
         
     | 
| 
      
 108 
     | 
    
         
            +
                options["action"] = "start"
         
     | 
| 
      
 109 
     | 
    
         
            +
                options.to_json
         
     | 
| 
      
 110 
     | 
    
         
            +
              end
         
     | 
| 
      
 111 
     | 
    
         
            +
             
     | 
| 
      
 112 
     | 
    
         
            +
              def build_close_message
         
     | 
| 
      
 113 
     | 
    
         
            +
                { "action" => "close" }.to_json
         
     | 
| 
      
 114 
     | 
    
         
            +
              end
         
     | 
| 
      
 115 
     | 
    
         
            +
             
     | 
| 
      
 116 
     | 
    
         
            +
              def send_audio(data:)
         
     | 
| 
      
 117 
     | 
    
         
            +
                if @chunk_data
         
     | 
| 
      
 118 
     | 
    
         
            +
                  if @mic_running
         
     | 
| 
      
 119 
     | 
    
         
            +
                    @queue.empty? ? send_chunk(chunk: nil, final: false) : send_chunk(chunk: @queue.pop(true), final: false)
         
     | 
| 
      
 120 
     | 
    
         
            +
                  elsif @queue.length == 1
         
     | 
| 
      
 121 
     | 
    
         
            +
                    send_chunk(chunk: @queue.pop(true), final: true)
         
     | 
| 
      
 122 
     | 
    
         
            +
                    @queue.close
         
     | 
| 
      
 123 
     | 
    
         
            +
                    @timer.cancel if @timer.respond_to?(:cancel)
         
     | 
| 
      
 124 
     | 
    
         
            +
                    return
         
     | 
| 
      
 125 
     | 
    
         
            +
                  else
         
     | 
| 
      
 126 
     | 
    
         
            +
                    send_chunk(chunk: @queue.pop(true), final: false) unless @queue.empty?
         
     | 
| 
      
 127 
     | 
    
         
            +
                  end
         
     | 
| 
      
 128 
     | 
    
         
            +
                else
         
     | 
| 
      
 129 
     | 
    
         
            +
                  if @bytes_sent + ONE_KB >= @data_size
         
     | 
| 
      
 130 
     | 
    
         
            +
                    if @data_size > @bytes_sent
         
     | 
| 
      
 131 
     | 
    
         
            +
                      send_chunk(chunk: data.read(ONE_KB), final: true)
         
     | 
| 
      
 132 
     | 
    
         
            +
                      @timer.cancel if @timer.respond_to?(:cancel)
         
     | 
| 
      
 133 
     | 
    
         
            +
                      return
         
     | 
| 
      
 134 
     | 
    
         
            +
                    end
         
     | 
| 
      
 135 
     | 
    
         
            +
                    @timer.cancel if @timer.respond_to?(:cancel)
         
     | 
| 
      
 136 
     | 
    
         
            +
                  end
         
     | 
| 
      
 137 
     | 
    
         
            +
                  send_chunk(chunk: data.read(ONE_KB), final: false)
         
     | 
| 
      
 138 
     | 
    
         
            +
                end
         
     | 
| 
      
 139 
     | 
    
         
            +
                @timer = EventMachine::Timer.new(TEN_MILLISECONDS) { send_audio(data: data) }
         
     | 
| 
      
 140 
     | 
    
         
            +
              end
         
     | 
| 
      
 141 
     | 
    
         
            +
             
     | 
| 
      
 142 
     | 
    
         
            +
              def extract_transcripts(alternatives:)
         
     | 
| 
      
 143 
     | 
    
         
            +
                transcripts = []
         
     | 
| 
      
 144 
     | 
    
         
            +
                unless alternatives.nil?
         
     | 
| 
      
 145 
     | 
    
         
            +
                  alternatives.each do |alternative|
         
     | 
| 
      
 146 
     | 
    
         
            +
                    transcript = {}
         
     | 
| 
      
 147 
     | 
    
         
            +
                    transcript["confidence"] = alternative["confidence"] if alternative.key?("confidence")
         
     | 
| 
      
 148 
     | 
    
         
            +
                    transcript["transcript"] = alternative["transcript"]
         
     | 
| 
      
 149 
     | 
    
         
            +
                    transcripts << transcript
         
     | 
| 
      
 150 
     | 
    
         
            +
                  end
         
     | 
| 
      
 151 
     | 
    
         
            +
                end
         
     | 
| 
      
 152 
     | 
    
         
            +
                transcripts
         
     | 
| 
      
 153 
     | 
    
         
            +
              end
         
     | 
| 
      
 154 
     | 
    
         
            +
             
     | 
| 
      
 155 
     | 
    
         
            +
              def send_chunk(chunk:, final: false)
         
     | 
| 
      
 156 
     | 
    
         
            +
                return if chunk.nil?
         
     | 
| 
      
 157 
     | 
    
         
            +
                @bytes_sent += chunk.size
         
     | 
| 
      
 158 
     | 
    
         
            +
                @client.send(chunk.bytes)
         
     | 
| 
      
 159 
     | 
    
         
            +
                @client.send({ "action" => "stop" }.to_json) if final
         
     | 
| 
      
 160 
     | 
    
         
            +
                @timer.cancel if @timer.respond_to?(:cancel) && final
         
     | 
| 
      
 161 
     | 
    
         
            +
              end
         
     | 
| 
      
 162 
     | 
    
         
            +
            end
         
     | 
    
        data/rakefile
    ADDED
    
    | 
         @@ -0,0 +1,45 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            require "dotenv/tasks"
         
     | 
| 
      
 4 
     | 
    
         
            +
            require "rake/testtask"
         
     | 
| 
      
 5 
     | 
    
         
            +
            require "rubocop/rake_task"
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
            task default: %w[def]
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
            RuboCop::RakeTask.new
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
            namespace :test do
         
     | 
| 
      
 12 
     | 
    
         
            +
              Rake::TestTask.new do |t|
         
     | 
| 
      
 13 
     | 
    
         
            +
                t.name = "unit"
         
     | 
| 
      
 14 
     | 
    
         
            +
                t.description = "Run unit tests"
         
     | 
| 
      
 15 
     | 
    
         
            +
                t.libs << "test"
         
     | 
| 
      
 16 
     | 
    
         
            +
                t.test_files = FileList["test/unit/*.rb"]
         
     | 
| 
      
 17 
     | 
    
         
            +
                t.verbose = true
         
     | 
| 
      
 18 
     | 
    
         
            +
                t.warning = true
         
     | 
| 
      
 19 
     | 
    
         
            +
              end
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
              Rake::TestTask.new do |t|
         
     | 
| 
      
 22 
     | 
    
         
            +
                t.name = "integration"
         
     | 
| 
      
 23 
     | 
    
         
            +
                t.description = "Run integration tests (put credentials in a .env file)"
         
     | 
| 
      
 24 
     | 
    
         
            +
                t.libs << "test"
         
     | 
| 
      
 25 
     | 
    
         
            +
                t.test_files = FileList["test/integration/*.rb"]
         
     | 
| 
      
 26 
     | 
    
         
            +
                t.verbose = true
         
     | 
| 
      
 27 
     | 
    
         
            +
                t.warning = true
         
     | 
| 
      
 28 
     | 
    
         
            +
                t.deps = [:dotenv]
         
     | 
| 
      
 29 
     | 
    
         
            +
              end
         
     | 
| 
      
 30 
     | 
    
         
            +
            end
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
            desc "Run unit & integration tests"
         
     | 
| 
      
 33 
     | 
    
         
            +
            task :test do
         
     | 
| 
      
 34 
     | 
    
         
            +
              Rake::Task["test:unit"].invoke
         
     | 
| 
      
 35 
     | 
    
         
            +
              Rake::Task["test:integration"].invoke
         
     | 
| 
      
 36 
     | 
    
         
            +
            end
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
            desc "Run tests and generate a code coverage report"
         
     | 
| 
      
 39 
     | 
    
         
            +
            task :coverage do
         
     | 
| 
      
 40 
     | 
    
         
            +
              ENV["COVERAGE"] = "true"
         
     | 
| 
      
 41 
     | 
    
         
            +
              Rake::Task["test"].execute
         
     | 
| 
      
 42 
     | 
    
         
            +
            end
         
     | 
| 
      
 43 
     | 
    
         
            +
             
     | 
| 
      
 44 
     | 
    
         
            +
            task def: %i[rubocop coverage] do
         
     | 
| 
      
 45 
     | 
    
         
            +
            end
         
     |