voicebase-client-ruby 1.0.14
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/.gitignore +11 -0
- data/.rspec +2 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +4 -0
- data/Gemfile +4 -0
- data/LICENSE +17 -0
- data/README.md +91 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/voicebase.rb +22 -0
- data/lib/voicebase/client.rb +41 -0
- data/lib/voicebase/client/token.rb +16 -0
- data/lib/voicebase/helpers.rb +25 -0
- data/lib/voicebase/json.rb +89 -0
- data/lib/voicebase/json/word.rb +63 -0
- data/lib/voicebase/response.rb +35 -0
- data/lib/voicebase/v1.rb +6 -0
- data/lib/voicebase/v1/client.rb +92 -0
- data/lib/voicebase/v1/response.rb +24 -0
- data/lib/voicebase/v2.rb +6 -0
- data/lib/voicebase/v2/client.rb +233 -0
- data/lib/voicebase/v2/response.rb +38 -0
- data/lib/voicebase/version.rb +5 -0
- data/voicebase-client-ruby.gemspec +29 -0
- metadata +155 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 3dff5518b93777f4361ef5ae09534b24f26b81fd
|
4
|
+
data.tar.gz: a7b02a83c9ec5b4bca5da34213544f7049e45395
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 722c6172937548128010286413d6a684a1ab6507812a6213091246fe2af74d45fb5fe8eaf9468d4865e1e3510365eea5049931b90a91de21f1635dc4faf58c6f
|
7
|
+
data.tar.gz: 4472a71f64fd1bdd655c2396161da6b0afbfa0046cc6a03ad105a4ee99cbfe15ffd62fe67324d6f8b9ac88b0eab6cb3e6033c69de4c075d505221f0b11d72ed0
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.ruby-gemset
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
voicebase-client-ruby
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.2.4
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
Copyright (c) 2016 User Testing, Inc.
|
3
|
+
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software
|
5
|
+
and associated documentation files (the "Software"), to deal in the Software without restriction,
|
6
|
+
including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
7
|
+
and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
|
8
|
+
subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial
|
11
|
+
portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
|
14
|
+
LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
15
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
16
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
|
17
|
+
OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
# Voicebase Client Ruby
|
2
|
+
|
3
|
+
This is a Ruby client to the VoiceBase API Version [1.x](http://www.voicebase.com/developers/), see [API documentation](https://s3.amazonaws.com/vb-developers/VB-api-devguide-v1.1.5.pdf), and [2.x](https://apis.voicebase.com). Some portions of this gem were derived from [voicebase-client-ruby](https://github.com/popuparchive/voicebase-client-ruby).
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
gem 'voicebase-client-ruby', github: "usertesting/voicebase-client-ruby"
|
11
|
+
```
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
## Usage
|
18
|
+
|
19
|
+
### VoiceBase API V1.x:
|
20
|
+
|
21
|
+
An example to authenticate with v1 and upload a video file.
|
22
|
+
|
23
|
+
```ruby
|
24
|
+
require 'voicebase'
|
25
|
+
|
26
|
+
client = VoiceBase::Client.new({
|
27
|
+
api_version: "1.1",
|
28
|
+
auth_key: "my-voicebase-key",
|
29
|
+
auth_secret: "my-voicebase-secret",
|
30
|
+
})
|
31
|
+
|
32
|
+
client.upload_media({
|
33
|
+
media_url: "http://my.media-example.com/media1.mp4",
|
34
|
+
title: "My fancy media",
|
35
|
+
transcription_type: 'machine',
|
36
|
+
external_id: 'abcd1234',
|
37
|
+
machine_ready_callback: "http://my.example.com/success",
|
38
|
+
error_callback: "http://my.example.com/error"
|
39
|
+
})
|
40
|
+
|
41
|
+
response = get_transcript(external_id: 'abcd1234' format: "json")
|
42
|
+
if response.success?
|
43
|
+
transcript_json = JSON.parse(response.transcript)
|
44
|
+
end
|
45
|
+
|
46
|
+
```
|
47
|
+
|
48
|
+
For VoiceBase API V2.x:
|
49
|
+
|
50
|
+
```ruby
|
51
|
+
require 'voicebase'
|
52
|
+
|
53
|
+
client = VoiceBase::Client.new({
|
54
|
+
api_version: "2.0.beta",
|
55
|
+
auth_key: "my-voicebase-key",
|
56
|
+
auth_secret: "my-voicebase-secret",
|
57
|
+
})
|
58
|
+
|
59
|
+
client.upload_media({
|
60
|
+
media_url: "http://my.media-example.com/media1.mp4",
|
61
|
+
configuration: {
|
62
|
+
transcripts: {
|
63
|
+
engine: "premium"
|
64
|
+
},
|
65
|
+
publish: {
|
66
|
+
callbacks: [{
|
67
|
+
url: "https://example.org/callback",
|
68
|
+
method: "POST",
|
69
|
+
include: ["transcripts", "keywords", "topics", "metadata"]
|
70
|
+
}]
|
71
|
+
}
|
72
|
+
}
|
73
|
+
})
|
74
|
+
|
75
|
+
client.get_transcript({
|
76
|
+
media_id: "3b5c78e2-868c-4ce7-a0db-087a02db4042"
|
77
|
+
}, {'Accept' => 'text/srt'})
|
78
|
+
|
79
|
+
...
|
80
|
+
```
|
81
|
+
|
82
|
+
## Development
|
83
|
+
|
84
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
85
|
+
|
86
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
87
|
+
|
88
|
+
## Contributing
|
89
|
+
|
90
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/usertesting/voicebase-client-ruby.
|
91
|
+
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "voicebase/client/ruby"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
data/lib/voicebase.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'uri'
|
3
|
+
require 'httparty'
|
4
|
+
require 'active_support/core_ext/module'
|
5
|
+
|
6
|
+
require "voicebase/version"
|
7
|
+
require "voicebase/helpers"
|
8
|
+
|
9
|
+
require "voicebase/v1"
|
10
|
+
require "voicebase/v2"
|
11
|
+
|
12
|
+
require "voicebase/client"
|
13
|
+
require "voicebase/client/token"
|
14
|
+
require "voicebase/response"
|
15
|
+
|
16
|
+
require "voicebase/json"
|
17
|
+
require "voicebase/json/word"
|
18
|
+
|
19
|
+
module VoiceBase
|
20
|
+
class AuthenticationError < StandardError; end
|
21
|
+
class ArgumentError < StandardError; end
|
22
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module VoiceBase
|
2
|
+
class Client
|
3
|
+
include HTTParty
|
4
|
+
|
5
|
+
attr_accessor :args
|
6
|
+
attr_accessor :api_host
|
7
|
+
attr_accessor :api_endpoint
|
8
|
+
attr_accessor :api_version
|
9
|
+
attr_accessor :debug
|
10
|
+
attr_accessor :user_agent
|
11
|
+
attr_accessor :cookies
|
12
|
+
attr_accessor :locale
|
13
|
+
attr_accessor :token
|
14
|
+
|
15
|
+
# E.g. "request_status" -> "requestStatus"
|
16
|
+
def self.camelize_name(snake_cased_name)
|
17
|
+
snake_cased_name.to_s.camelize(:lower)
|
18
|
+
end
|
19
|
+
|
20
|
+
def initialize(args = {})
|
21
|
+
@args = args
|
22
|
+
@api_version = args[:api_version] || ENV.fetch('VOICEBASE_API_VERSION', '1.1')
|
23
|
+
@auth_key = args[:auth_key] || ENV['VOICEBASE_API_KEY']
|
24
|
+
@auth_secret = args[:auth_secret] || ENV['VOICEBASE_API_SECRET']
|
25
|
+
@debug = !!args[:debug]
|
26
|
+
@user_agent = args[:user_agent] || "usertesting-client/#{VoiceBase::version}"
|
27
|
+
@locale = args[:locale] || 'en' # US English
|
28
|
+
|
29
|
+
if @api_version.to_f < 2.0
|
30
|
+
self.extend(VoiceBase::V1::Client)
|
31
|
+
else
|
32
|
+
self.extend(VoiceBase::V2::Client)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def uri
|
37
|
+
@api_host + @api_endpoint
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module VoiceBase
|
2
|
+
class Client::Token
|
3
|
+
attr_accessor :token, :created_at, :timeout
|
4
|
+
|
5
|
+
def initialize(token, timeout = Float::INFINITY)
|
6
|
+
raise VoiceBase::AuthenticationError, "Authentication token cannot be empty" unless token
|
7
|
+
@token = token
|
8
|
+
@created_at = Time.now
|
9
|
+
@timeout = timeout
|
10
|
+
end
|
11
|
+
|
12
|
+
def expired?
|
13
|
+
Time.now > created_at + (timeout / 1000.to_f)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module VoiceBase
|
2
|
+
module Helpers
|
3
|
+
def self.included(base)
|
4
|
+
base.send :extend, ClassMethods
|
5
|
+
base.send :include, InstanceMethods
|
6
|
+
end
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
|
10
|
+
# E.g. "request_status" -> "requestStatus"
|
11
|
+
def camelize_name(snake_cased_name)
|
12
|
+
snake_cased_name.to_s.camelize(:lower)
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
module InstanceMethods
|
18
|
+
|
19
|
+
def camelize_name(snake_cased_name)
|
20
|
+
self.class.camelize_name(snake_cased_name)
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
module VoiceBase
|
2
|
+
class JSON
|
3
|
+
include Enumerable
|
4
|
+
|
5
|
+
class ParseError < StandardError; end
|
6
|
+
|
7
|
+
class << self
|
8
|
+
|
9
|
+
def parse(input, options = {})
|
10
|
+
@debug = options.fetch(:debug, false)
|
11
|
+
if input.is_a?(String)
|
12
|
+
parse_string(input)
|
13
|
+
elsif input.is_a?(::File)
|
14
|
+
parse_file(input)
|
15
|
+
else
|
16
|
+
raise "Invalid input. Expected a String or File, got #{input.class.name}."
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def parse_file(json_file)
|
23
|
+
parse_string ::File.open(json_file, 'rb') { |f| json_file.read }
|
24
|
+
end
|
25
|
+
|
26
|
+
def parse_string(json_string_data)
|
27
|
+
result = new
|
28
|
+
|
29
|
+
json_hash_data = ::JSON.parse(json_string_data)
|
30
|
+
raise ParseError, "Invalid format" unless json_hash_data.is_a?(Array)
|
31
|
+
json_hash_data.each_with_index do |word_hash, index|
|
32
|
+
word = Word.new(word_hash)
|
33
|
+
result.words << word unless word.empty?
|
34
|
+
|
35
|
+
%w(p c s e w).each do |field|
|
36
|
+
if word.send(field).nil?
|
37
|
+
word.error = "#{index}, Invalid formatting of #{field}, [#{word_hash[field]}]"
|
38
|
+
$stderr.puts word.error if @debug
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
result
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
attr_writer :words
|
47
|
+
|
48
|
+
def initialize(word_array = nil)
|
49
|
+
raise StandardError, "Must be initialized with words." if word_array.is_a?(Array) && !words.all? {|w| w.is_a?(VoiceBase::JSON::Word)}
|
50
|
+
@words = word_array || []
|
51
|
+
end
|
52
|
+
|
53
|
+
def words
|
54
|
+
@words ||= []
|
55
|
+
end
|
56
|
+
|
57
|
+
def errors
|
58
|
+
@words.map {|w| w.error if w.error}.compact
|
59
|
+
end
|
60
|
+
|
61
|
+
def each(&block)
|
62
|
+
@words.each {|word| block.call(word)}
|
63
|
+
end
|
64
|
+
|
65
|
+
def gt(start_time)
|
66
|
+
VoiceBase::JSON.new(select {|w| w.start_time > start_time})
|
67
|
+
end
|
68
|
+
|
69
|
+
def gteq(start_time)
|
70
|
+
VoiceBase::JSON.new(select {|w| w.start_time >= start_time})
|
71
|
+
end
|
72
|
+
|
73
|
+
def lt(start_time)
|
74
|
+
VoiceBase::JSON.new(select {|w| w.start_time < start_time})
|
75
|
+
end
|
76
|
+
|
77
|
+
def lteq(start_time)
|
78
|
+
VoiceBase::JSON.new(select {|w| w.start_time <= start_time})
|
79
|
+
end
|
80
|
+
|
81
|
+
def to_a
|
82
|
+
@words
|
83
|
+
end
|
84
|
+
|
85
|
+
def to_json
|
86
|
+
map {|w| w.to_hash}.to_json
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module VoiceBase
|
2
|
+
class JSON::Word
|
3
|
+
attr_accessor :sequence
|
4
|
+
attr_accessor :start_time
|
5
|
+
attr_accessor :end_time
|
6
|
+
attr_accessor :confidence
|
7
|
+
attr_accessor :word
|
8
|
+
attr_accessor :error
|
9
|
+
attr_accessor :metadata
|
10
|
+
|
11
|
+
alias_method :p, :sequence
|
12
|
+
alias_method :p=, :sequence=
|
13
|
+
alias_method :c, :confidence
|
14
|
+
alias_method :c=, :confidence=
|
15
|
+
alias_method :s, :start_time
|
16
|
+
alias_method :s=, :start_time=
|
17
|
+
alias_method :e, :end_time
|
18
|
+
alias_method :e=, :end_time=
|
19
|
+
alias_method :w, :word
|
20
|
+
alias_method :w=, :word=
|
21
|
+
alias_method :m, :metadata
|
22
|
+
alias_method :m=, :metadata=
|
23
|
+
|
24
|
+
def initialize(options={})
|
25
|
+
options.each do |k,v|
|
26
|
+
self.send("#{k}=",v)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def clone
|
31
|
+
clone = VoiceBase::JSON::Word.new
|
32
|
+
clone.sequence = sequence
|
33
|
+
clone.start_time = start_time
|
34
|
+
clone.end_time = end_time
|
35
|
+
clone.confidence = confidence
|
36
|
+
clone.error = error
|
37
|
+
clone.word = word
|
38
|
+
clone.metadata = metadata
|
39
|
+
clone
|
40
|
+
end
|
41
|
+
|
42
|
+
def ==(word)
|
43
|
+
self.sequence == word.sequence &&
|
44
|
+
self.start_time == word.start_time &&
|
45
|
+
self.end_time == word.end_time &&
|
46
|
+
self.confidence == word.confidence &&
|
47
|
+
self.word == word.word &&
|
48
|
+
self.metadata == word.metadata
|
49
|
+
end
|
50
|
+
|
51
|
+
def empty?
|
52
|
+
sequence.nil? && start_time.nil? && end_time.nil? && (word.nil? || word.empty?)
|
53
|
+
end
|
54
|
+
|
55
|
+
def to_hash
|
56
|
+
{"p": sequence, "c": confidence, "s": start_time, "e": end_time, "w": word}
|
57
|
+
end
|
58
|
+
|
59
|
+
def to_json
|
60
|
+
{"p": sequence, "c": confidence, "s": start_time, "e": end_time, "w": word}.to_json
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module VoiceBase
|
2
|
+
class Response
|
3
|
+
include Helpers
|
4
|
+
attr_accessor :http_response
|
5
|
+
|
6
|
+
delegate :code, :body, :headers, :message, to: :http_response, allow_nil: true
|
7
|
+
|
8
|
+
def initialize(http_response, api_version = "1.1")
|
9
|
+
@http_response = http_response
|
10
|
+
if api_version.to_f < 2
|
11
|
+
self.extend(VoiceBase::V1::Response)
|
12
|
+
else
|
13
|
+
self.extend(VoiceBase::V2::Response)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def ok?
|
18
|
+
http_response.code && http_response.code >= 200 && http_response.code < 300
|
19
|
+
end
|
20
|
+
|
21
|
+
# E.g.
|
22
|
+
#
|
23
|
+
# @response.request_status is derived from the
|
24
|
+
# response hash 'statusMessage' key, or
|
25
|
+
# @response.status_message from 'statusMessage'
|
26
|
+
#
|
27
|
+
def method_missing(method, *args, &block)
|
28
|
+
if result = http_response.parsed_response[camelize_name(method)]
|
29
|
+
result
|
30
|
+
else
|
31
|
+
super
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/lib/voicebase/v1.rb
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
module VoiceBase
|
2
|
+
module V1
|
3
|
+
module Client
|
4
|
+
include Helpers
|
5
|
+
|
6
|
+
TOKEN_TIMEOUT_IN_MS = 1440
|
7
|
+
PARAM_NORMALIZATION = {"Url" => "URL", "Id" => "ID", "Callback" => "CallBack"}
|
8
|
+
ACTIONS = ['uploadMedia', 'getTranscript', 'deleteFile', 'getFileStatus']
|
9
|
+
|
10
|
+
def self.extended(client, args = {})
|
11
|
+
client.api_host = client.args[:api_host] || ENV.fetch('VOICEBASE_V1_API_HOST', 'https://api.voicebase.com')
|
12
|
+
client.api_endpoint = client.args[:api_endpoint] || ENV.fetch('VOICEBASE_V1_API_ENDPOINT', '/services')
|
13
|
+
end
|
14
|
+
|
15
|
+
def authenticate!
|
16
|
+
response = VoiceBase::Response.new(
|
17
|
+
self.class.post(uri,
|
18
|
+
query: {
|
19
|
+
version: @api_version, apiKey: @auth_key,
|
20
|
+
password: @auth_secret, action: 'getToken',
|
21
|
+
timeout: TOKEN_TIMEOUT_IN_MS
|
22
|
+
}), api_version)
|
23
|
+
@token = Token.new(response.token, TOKEN_TIMEOUT_IN_MS)
|
24
|
+
rescue NoMethodError => ex
|
25
|
+
raise VoiceBase::AuthenticationError, response.status_message
|
26
|
+
end
|
27
|
+
|
28
|
+
# E.g. @client.upload_media media_url: "https://ut.aws.amazon.com/..."
|
29
|
+
def method_missing(method, args, &block)
|
30
|
+
if actions.include?(camelize_name(method)) && args.size > 0
|
31
|
+
post camelize_keys(args).merge({action: camelize_name(method)})
|
32
|
+
else
|
33
|
+
super
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def post(query_params, headers = {})
|
40
|
+
query = default_query(query_params)
|
41
|
+
|
42
|
+
puts "post #{uri} #{query.inspect}, #{default_headers(headers).inspect}" if debug
|
43
|
+
VoiceBase::Response.new(self.class.post(uri,
|
44
|
+
query: query, headers: default_headers(headers)), api_version)
|
45
|
+
end
|
46
|
+
|
47
|
+
def actions
|
48
|
+
ACTIONS
|
49
|
+
end
|
50
|
+
|
51
|
+
def default_query(params = {})
|
52
|
+
params = params.reverse_merge({version: @api_version,
|
53
|
+
apiKey: @auth_key, password: @auth_secret,
|
54
|
+
lang: locale})
|
55
|
+
|
56
|
+
# authenticate using token or key/password?
|
57
|
+
if token && !token.expired?
|
58
|
+
params.merge!({token: token.token})
|
59
|
+
else
|
60
|
+
params.merge!({apiKey: @auth_key, password: @auth_secret})
|
61
|
+
end
|
62
|
+
|
63
|
+
params
|
64
|
+
end
|
65
|
+
|
66
|
+
def default_headers(headers = {})
|
67
|
+
{'User-Agent' => @user_agent, 'Accept' => 'application/json',
|
68
|
+
'Cookie' => @cookies}.reject {|k, v| v.blank?}.merge(headers)
|
69
|
+
end
|
70
|
+
|
71
|
+
def camelize_keys(params)
|
72
|
+
params.inject({}) {|r, e| r[camelize_and_normalize_name(e.first)] = e.last; r }
|
73
|
+
end
|
74
|
+
|
75
|
+
# Parameters are camelized and normalized
|
76
|
+
# according to VoiceBase API.
|
77
|
+
#
|
78
|
+
# E.g.
|
79
|
+
#
|
80
|
+
# :media_url -> "mediaURL"
|
81
|
+
# :external_id -> "externalID"
|
82
|
+
# :error_callback -> "errorCallBack"
|
83
|
+
#
|
84
|
+
def camelize_and_normalize_name(snake_cased_name)
|
85
|
+
result = Client.camelize_name(snake_cased_name.to_s)
|
86
|
+
PARAM_NORMALIZATION.each {|k, v| result.gsub!(/#{k}/, v) }
|
87
|
+
result
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module VoiceBase
|
2
|
+
module V1
|
3
|
+
module Response
|
4
|
+
def self.extended(response)
|
5
|
+
end
|
6
|
+
|
7
|
+
def success?
|
8
|
+
ok? && request_status == "SUCCESS"
|
9
|
+
end
|
10
|
+
|
11
|
+
|
12
|
+
def transcript_ready?
|
13
|
+
|
14
|
+
# this was added because with the V1 API, a value in the returned JSON indicates both a
|
15
|
+
# successful HTTP request, but also a ready transcript. With V2, there's no JSON value
|
16
|
+
# to indicate status. Instead, the HTTP status code indicates request status, and
|
17
|
+
# the state becoming "finished" indicates the transcript it ready.
|
18
|
+
|
19
|
+
success?
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/lib/voicebase/v2.rb
ADDED
@@ -0,0 +1,233 @@
|
|
1
|
+
module VoiceBase
|
2
|
+
module V2
|
3
|
+
module Client
|
4
|
+
BOUNDARY = "0123456789ABLEWASIEREISAWELBA9876543210"
|
5
|
+
MULTIPART_CONTENT_TYPE = "multipart/form-data; boundary=#{BOUNDARY}"
|
6
|
+
|
7
|
+
def self.extended(client, args = {})
|
8
|
+
client.api_host = client.args[:host] || ENV.fetch('VOICEBASE_V2_API_HOST', 'https://apis.voicebase.com')
|
9
|
+
client.api_endpoint = client.args[:api_endpoint] || ENV.fetch('VOICEBASE_V2_API_ENDPOINT', '/v2-beta')
|
10
|
+
end
|
11
|
+
|
12
|
+
def authenticate!
|
13
|
+
auth = {:username => @auth_key, :password => @auth_secret}
|
14
|
+
response = VoiceBase::Response.new(
|
15
|
+
self.class.get(
|
16
|
+
uri + '/access/users/admin/tokens',
|
17
|
+
basic_auth: auth,
|
18
|
+
headers: {
|
19
|
+
'User-Agent' => @user_agent,
|
20
|
+
'Accept' => 'application/json'
|
21
|
+
}
|
22
|
+
), api_version)
|
23
|
+
@token = VoiceBase::Client::Token.new(response.tokens.try(:first).try(:[], 'token'))
|
24
|
+
rescue NoMethodError => ex
|
25
|
+
raise VoiceBase::AuthenticationError, response.status_message
|
26
|
+
end
|
27
|
+
|
28
|
+
def upload_media(args = {}, headers = {})
|
29
|
+
|
30
|
+
media_url = require_media_file_or_url(args)
|
31
|
+
|
32
|
+
form_args = {
|
33
|
+
'media' => media_url,
|
34
|
+
'configuration' => {
|
35
|
+
'configuration' => {
|
36
|
+
'executor' => 'v2'
|
37
|
+
}
|
38
|
+
}
|
39
|
+
}
|
40
|
+
|
41
|
+
# external ID is only partially supported in the V2 API (can't get plain text transcripts or delete media)
|
42
|
+
if args[:external_id]
|
43
|
+
form_args.merge!({
|
44
|
+
'metadata' => {
|
45
|
+
'metadata' => {
|
46
|
+
'external' => {
|
47
|
+
'id' => "#{args[:external_id]}"
|
48
|
+
}
|
49
|
+
}
|
50
|
+
}
|
51
|
+
})
|
52
|
+
end
|
53
|
+
|
54
|
+
response = self.class.post(
|
55
|
+
uri + '/media',
|
56
|
+
headers: multipart_headers(headers),
|
57
|
+
body: multipart_query(form_args)
|
58
|
+
)
|
59
|
+
|
60
|
+
VoiceBase::Response.new(response, api_version)
|
61
|
+
end
|
62
|
+
|
63
|
+
# I presume this method exists for parity with the V1 API however it is not used by the Orders app
|
64
|
+
def get_media(args = {}, headers = {})
|
65
|
+
raise ArgumentError, "Missing argument :media_id" unless args[:media_id]
|
66
|
+
url = if args[:media_id]
|
67
|
+
uri + "/media/#{args[:media_id]}"
|
68
|
+
elsif args[:external_id]
|
69
|
+
uri + "/media?externalID=#{args[:external_id]}"
|
70
|
+
else
|
71
|
+
raise ArgumentError, "Missing argument :media_url or :media_file"
|
72
|
+
end
|
73
|
+
if args[:external_id]
|
74
|
+
uri + "/media?externalID=#{args[:external_id]}"
|
75
|
+
else
|
76
|
+
raise ArgumentError, "Missing argument :external_id"
|
77
|
+
end
|
78
|
+
|
79
|
+
VoiceBase::Response.new(self.class.get(
|
80
|
+
url, headers: default_headers(headers)
|
81
|
+
), api_version)
|
82
|
+
end
|
83
|
+
|
84
|
+
def get_json_transcript(args, headers)
|
85
|
+
url = if args[:media_id]
|
86
|
+
uri + "/media/#{args[:media_id]}"
|
87
|
+
else
|
88
|
+
raise ArgumentError, "Missing argument :media_id"
|
89
|
+
end
|
90
|
+
|
91
|
+
response = self.class.get(
|
92
|
+
url,
|
93
|
+
headers: default_headers(headers)
|
94
|
+
)
|
95
|
+
|
96
|
+
VoiceBase::Response.new(response, api_version)
|
97
|
+
end
|
98
|
+
|
99
|
+
def get_text_transcript(args, headers)
|
100
|
+
url = if args[:media_id]
|
101
|
+
uri + "/media/#{args[:media_id]}/transcripts/latest"
|
102
|
+
else
|
103
|
+
raise ArgumentError, "Missing argument :media_id"
|
104
|
+
end
|
105
|
+
|
106
|
+
headers.merge!({ 'Accept' => 'text/plain' })
|
107
|
+
|
108
|
+
response = self.class.get(
|
109
|
+
url,
|
110
|
+
headers: default_headers(headers)
|
111
|
+
)
|
112
|
+
|
113
|
+
response.parsed_response
|
114
|
+
end
|
115
|
+
|
116
|
+
def get_transcript(args = {}, headers = {})
|
117
|
+
if args[:format] == "txt"
|
118
|
+
get_text_transcript(args, headers)
|
119
|
+
else
|
120
|
+
get_json_transcript(args, headers)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
# is this used?
|
125
|
+
def get_media_progress(args = {}, headers = {})
|
126
|
+
raise ArgumentError, "Missing argument :media_id" unless args[:media_id]
|
127
|
+
VoiceBase::Response.new(self.class.get(
|
128
|
+
uri + "/media/#{args[:media_id]}/progress",
|
129
|
+
headers: default_headers(headers)
|
130
|
+
), api_version)
|
131
|
+
end
|
132
|
+
|
133
|
+
def delete_file(args = {}, headers = {})
|
134
|
+
url = if args[:media_id]
|
135
|
+
uri + "/media/#{args[:media_id]}"
|
136
|
+
else
|
137
|
+
raise ArgumentError, "Missing argument :media_id"
|
138
|
+
end
|
139
|
+
|
140
|
+
response = self.class.delete(
|
141
|
+
url,
|
142
|
+
headers: default_headers(headers)
|
143
|
+
)
|
144
|
+
|
145
|
+
VoiceBase::Response.new(response, api_version)
|
146
|
+
end
|
147
|
+
|
148
|
+
private
|
149
|
+
|
150
|
+
def default_headers(headers = {})
|
151
|
+
authenticate! unless token
|
152
|
+
headers = {'Authorization' => "Bearer #{token.token}",
|
153
|
+
'User-Agent' => user_agent}.reject {|k, v| v.blank?}.merge(headers)
|
154
|
+
puts "> headers\n> #{headers}" if debug
|
155
|
+
headers
|
156
|
+
end
|
157
|
+
|
158
|
+
def multipart_headers(headers = {})
|
159
|
+
default_headers(headers.merge({'Content-Type' => MULTIPART_CONTENT_TYPE}))
|
160
|
+
end
|
161
|
+
|
162
|
+
def multipart_query(params)
|
163
|
+
fp = []
|
164
|
+
|
165
|
+
params.each do |k, v|
|
166
|
+
if v.respond_to?(:path) and v.respond_to?(:read) then
|
167
|
+
fp.push(FileParam.new(k, v.path, v.read))
|
168
|
+
elsif v.is_a?(Hash)
|
169
|
+
fp.push(HashParam.new(k, v))
|
170
|
+
else
|
171
|
+
fp.push(StringParam.new(k, v))
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
query = fp.map {|p| "--" + BOUNDARY + "\r\n" + p.to_multipart }.join("") + "--" + BOUNDARY + "--"
|
176
|
+
puts "> multipart-query\n> #{query}" if debug
|
177
|
+
query
|
178
|
+
end
|
179
|
+
|
180
|
+
def require_media_file_or_url(args = {})
|
181
|
+
media = if args[:media_url]
|
182
|
+
args[:media_url]
|
183
|
+
elsif args[:media_file]
|
184
|
+
args[:media_file]
|
185
|
+
else
|
186
|
+
raise ArgumentError, "Missing argument :media_url or :media_file"
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
class StringParam
|
191
|
+
attr_accessor :k, :v
|
192
|
+
|
193
|
+
def initialize(k, v)
|
194
|
+
@k, @v = k, v
|
195
|
+
end
|
196
|
+
|
197
|
+
def to_multipart
|
198
|
+
return "Content-Disposition: form-data; name=\"#{CGI::escape(k.to_s)}\"\r\n\r\n#{v}\r\n"
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
class HashParam
|
203
|
+
attr_accessor :k, :v
|
204
|
+
|
205
|
+
def initialize(k, v)
|
206
|
+
@k, @v = k, v
|
207
|
+
end
|
208
|
+
|
209
|
+
def to_multipart
|
210
|
+
return "Content-Disposition: form-data; name=\"#{CGI::escape(k.to_s)}\"\r\n\r\n#{v.to_json}\r\n"
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
# Formats the contents of a file or string for inclusion with a multipart
|
215
|
+
# form post
|
216
|
+
class FileParam
|
217
|
+
attr_accessor :k, :filename, :content
|
218
|
+
|
219
|
+
def initialize(k, filename, content)
|
220
|
+
@k, @filename, @content = k, filename, content
|
221
|
+
end
|
222
|
+
|
223
|
+
def to_multipart
|
224
|
+
# If we can tell the possible mime-type from the filename, use the
|
225
|
+
# first in the list; otherwise, use "application/octet-stream"
|
226
|
+
mime_type = MIME::Types.type_for(filename)[0] || MIME::Types["application/octet-stream"][0]
|
227
|
+
return "Content-Disposition: form-data; name=\"#{CGI::escape(k.to_s)}\"; filename=\"#{ filename }\"\r\n" +
|
228
|
+
"Content-Type: #{ mime_type.simplified }\r\n\r\n#{ content }\r\n"
|
229
|
+
end
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module VoiceBase
|
2
|
+
module V2
|
3
|
+
module Response
|
4
|
+
|
5
|
+
TRANSCRIPT_READY_STATUS = "finished".freeze
|
6
|
+
|
7
|
+
def success?
|
8
|
+
|
9
|
+
# for the V1 API this indicates both a successful HTTP status code and a values of "SUCCESS" in the
|
10
|
+
# returned JSON. with V2, there is no "SUCCESS" value. The combined use was split, adding
|
11
|
+
# #transcript_ready? to both interfaces.
|
12
|
+
|
13
|
+
ok?
|
14
|
+
end
|
15
|
+
|
16
|
+
def media_id
|
17
|
+
voicebase_response['mediaId']
|
18
|
+
end
|
19
|
+
|
20
|
+
def transcript_ready?
|
21
|
+
voicebase_response['media']['status'].casecmp(TRANSCRIPT_READY_STATUS) == 0
|
22
|
+
end
|
23
|
+
|
24
|
+
def transcript
|
25
|
+
# this retrieves the JSON transcript only
|
26
|
+
# the plain text transcript is a plain text non-JSON response
|
27
|
+
voicebase_response['media']['transcripts']['latest']['words']
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def voicebase_response
|
33
|
+
http_response.parsed_response
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'voicebase/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "voicebase-client-ruby"
|
8
|
+
spec.version = VoiceBase::version
|
9
|
+
spec.authors = ["Jerry Hogsett"]
|
10
|
+
spec.email = ["jerry@usertesting.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{Ruby client for VoiceBase API Version 1.x and 2.x.}
|
13
|
+
spec.description = %q{Ruby client for VoiceBase API Version 1.x and 2.x that will make both API versions available at the same time.}
|
14
|
+
spec.homepage = "https://github.com/usertesting/voicebase-client-ruby"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
18
|
+
spec.bindir = "exe"
|
19
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
20
|
+
spec.require_paths = ["lib"]
|
21
|
+
|
22
|
+
spec.add_development_dependency "bundler", "~> 1.11"
|
23
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
24
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
25
|
+
spec.add_development_dependency "timecop", "~> 0.8"
|
26
|
+
|
27
|
+
spec.add_dependency "httparty"
|
28
|
+
spec.add_dependency "activesupport"
|
29
|
+
end
|
metadata
ADDED
@@ -0,0 +1,155 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: voicebase-client-ruby
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.14
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jerry Hogsett
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-05-04 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.11'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.11'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: timecop
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0.8'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0.8'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: httparty
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: activesupport
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
description: Ruby client for VoiceBase API Version 1.x and 2.x that will make both
|
98
|
+
API versions available at the same time.
|
99
|
+
email:
|
100
|
+
- jerry@usertesting.com
|
101
|
+
executables: []
|
102
|
+
extensions: []
|
103
|
+
extra_rdoc_files: []
|
104
|
+
files:
|
105
|
+
- ".gitignore"
|
106
|
+
- ".rspec"
|
107
|
+
- ".ruby-gemset"
|
108
|
+
- ".ruby-version"
|
109
|
+
- ".travis.yml"
|
110
|
+
- Gemfile
|
111
|
+
- LICENSE
|
112
|
+
- README.md
|
113
|
+
- Rakefile
|
114
|
+
- bin/console
|
115
|
+
- bin/setup
|
116
|
+
- lib/voicebase.rb
|
117
|
+
- lib/voicebase/client.rb
|
118
|
+
- lib/voicebase/client/token.rb
|
119
|
+
- lib/voicebase/helpers.rb
|
120
|
+
- lib/voicebase/json.rb
|
121
|
+
- lib/voicebase/json/word.rb
|
122
|
+
- lib/voicebase/response.rb
|
123
|
+
- lib/voicebase/v1.rb
|
124
|
+
- lib/voicebase/v1/client.rb
|
125
|
+
- lib/voicebase/v1/response.rb
|
126
|
+
- lib/voicebase/v2.rb
|
127
|
+
- lib/voicebase/v2/client.rb
|
128
|
+
- lib/voicebase/v2/response.rb
|
129
|
+
- lib/voicebase/version.rb
|
130
|
+
- voicebase-client-ruby.gemspec
|
131
|
+
homepage: https://github.com/usertesting/voicebase-client-ruby
|
132
|
+
licenses:
|
133
|
+
- MIT
|
134
|
+
metadata: {}
|
135
|
+
post_install_message:
|
136
|
+
rdoc_options: []
|
137
|
+
require_paths:
|
138
|
+
- lib
|
139
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
140
|
+
requirements:
|
141
|
+
- - ">="
|
142
|
+
- !ruby/object:Gem::Version
|
143
|
+
version: '0'
|
144
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
145
|
+
requirements:
|
146
|
+
- - ">="
|
147
|
+
- !ruby/object:Gem::Version
|
148
|
+
version: '0'
|
149
|
+
requirements: []
|
150
|
+
rubyforge_project:
|
151
|
+
rubygems_version: 2.4.8
|
152
|
+
signing_key:
|
153
|
+
specification_version: 4
|
154
|
+
summary: Ruby client for VoiceBase API Version 1.x and 2.x.
|
155
|
+
test_files: []
|