plivo 0.3.19 → 4.0.0.beta.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +14 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +10 -0
  5. data/AUTHORS.md +4 -0
  6. data/CHANGELOG.md +19 -0
  7. data/Gemfile +3 -0
  8. data/LICENSE.txt +19 -0
  9. data/README.md +105 -24
  10. data/Rakefile +7 -0
  11. data/lib/plivo.rb +9 -815
  12. data/lib/plivo/base.rb +9 -0
  13. data/lib/plivo/base/resource.rb +85 -0
  14. data/lib/plivo/base/resource_interface.rb +93 -0
  15. data/lib/plivo/base/response.rb +29 -0
  16. data/lib/plivo/exceptions.rb +50 -0
  17. data/lib/plivo/resources.rb +14 -0
  18. data/lib/plivo/resources/accounts.rb +174 -0
  19. data/lib/plivo/resources/applications.rb +233 -0
  20. data/lib/plivo/resources/calls.rb +492 -0
  21. data/lib/plivo/resources/conferences.rb +371 -0
  22. data/lib/plivo/resources/endpoints.rb +130 -0
  23. data/lib/plivo/resources/messages.rb +178 -0
  24. data/lib/plivo/resources/numbers.rb +302 -0
  25. data/lib/plivo/resources/pricings.rb +43 -0
  26. data/lib/plivo/resources/recordings.rb +114 -0
  27. data/lib/plivo/rest_client.rb +199 -0
  28. data/lib/plivo/utils.rb +107 -0
  29. data/lib/plivo/version.rb +3 -0
  30. data/lib/plivo/xml.rb +27 -0
  31. data/lib/plivo/xml/conference.rb +20 -0
  32. data/lib/plivo/xml/dial.rb +16 -0
  33. data/lib/plivo/xml/dtmf.rb +13 -0
  34. data/lib/plivo/xml/element.rb +83 -0
  35. data/lib/plivo/xml/get_digits.rb +15 -0
  36. data/lib/plivo/xml/hangup.rb +12 -0
  37. data/lib/plivo/xml/message.rb +13 -0
  38. data/lib/plivo/xml/number.rb +13 -0
  39. data/lib/plivo/xml/play.rb +13 -0
  40. data/lib/plivo/xml/plivo_xml.rb +19 -0
  41. data/lib/plivo/xml/pre_answer.rb +12 -0
  42. data/lib/plivo/xml/record.rb +17 -0
  43. data/lib/plivo/xml/redirect.rb +13 -0
  44. data/lib/plivo/xml/response.rb +21 -0
  45. data/lib/plivo/xml/speak.rb +17 -0
  46. data/lib/plivo/xml/user.rb +13 -0
  47. data/lib/plivo/xml/wait.rb +12 -0
  48. data/plivo.gemspec +44 -0
  49. metadata +134 -45
  50. data/ext/mkrf_conf.rb +0 -9
@@ -0,0 +1,43 @@
1
+ module Plivo
2
+ module Resources
3
+ include Plivo::Utils
4
+ class Pricing < Base::Resource
5
+ def initialize(client, options = nil)
6
+ @_name = 'Pricing'
7
+ @_identifier_string = 'country_iso'
8
+ super
9
+ end
10
+
11
+ def to_s
12
+ {
13
+ api_id: @api_id,
14
+ country: @country,
15
+ country_code: @country_code,
16
+ country_iso: @country_iso,
17
+ message: @message,
18
+ phone_numbers: @phone_numbers,
19
+ voice: @voice
20
+ }.to_s
21
+ end
22
+ end
23
+
24
+ class PricingInterface < Base::ResourceInterface
25
+ def initialize(client, resource_list_json = nil)
26
+ @_name = 'Pricing'
27
+ @_resource_type = Pricing
28
+ @_identifier_string = 'country_iso'
29
+ super
30
+ end
31
+
32
+ # @param [String] country_iso
33
+ def get(country_iso)
34
+ valid_param?(:country_iso, country_iso, [String, Symbol], true)
35
+ unless country_iso.length == 2
36
+ raise_invalid_request('country_iso should be of length 2')
37
+ end
38
+ params = { country_iso: country_iso }
39
+ perform_get_without_identifier(params)
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,114 @@
1
+ module Plivo
2
+ module Resources
3
+ include Plivo::Utils
4
+ class Recording < Base::Resource
5
+ def initialize(client, options = nil)
6
+ @_name = 'Recording'
7
+ @_identifier_string = 'recording_id'
8
+ super
9
+ end
10
+
11
+ def delete
12
+ perform_delete
13
+ end
14
+
15
+ def to_s
16
+ {
17
+ add_time: @add_time,
18
+ api_id: @api_id,
19
+ call_uuid: @call_uuid,
20
+ conference_name: @conference_name,
21
+ recording_duration_ms: @recording_duration_ms,
22
+ recording_end_ms: @recording_end_ms,
23
+ recording_format: @recording_format,
24
+ recording_id: @recording_id,
25
+ recording_start_ms: @recording_start_ms,
26
+ recording_type: @recording_type,
27
+ recording_url: @recording_url,
28
+ resource_uri: @resource_uri
29
+ }.to_s
30
+ end
31
+ end
32
+
33
+ class RecordingInterface < Base::ResourceInterface
34
+ def initialize(client, resource_list_json = nil)
35
+ @_name = 'Recording'
36
+ @_resource_type = Recording
37
+ @_identifier_string = 'recording_id'
38
+ super
39
+ end
40
+
41
+ # @param [Hash] options
42
+ # @option options [String] :subaccount auth_id of the subaccount. Lists only those recordings of the main accounts which are tied to the specified subaccount.
43
+ # @option options [String] :call_uuid Used to filter recordings for a specific call.
44
+ # @option options [String] :add_time Used to filter out recordings according to the time they were added.The add_time filter is a comparative filter that can be used in the following four forms:
45
+ # - add_time\__gt: gt stands for greater than. The format expected is YYYY-MM-DD HH:MM[:ss[.uuuuuu]]. Eg:- To get all recordings that started after 2012-03-21 11:47, use add_time\__gt=2012-03-21 11:47
46
+ # - add_time\__gte: gte stands for greater than or equal. The format expected is YYYY-MM-DD HH:MM[:ss[.uuuuuu]]. Eg:- To get all recordings that started after or exactly at 2012-03-21 11:47[:30], use add_time\__gte=2012-03-21 11:47[:30]
47
+ # - add_time\__lt: lt stands for lesser than. The format expected is YYYY-MM-DD HH:MM[:ss[.uuuuuu]]. Eg:- To get all recordings that started before 2012-03-21 11:47, use add_time\__lt=2012-03-21 11:47
48
+ # - add_time\__gte: lte stands for lesser than or equal. The format expected is YYYY-MM-DD HH:MM[:ss[.uuuuuu]]. Eg:- To get all recordings that started before or exactly at 2012-03-21 11:47[:30], use add_time\__lte=2012-03-21 11:47[:30]
49
+ # - Note: The above filters can be combined to get recordings that started in a particular time range.
50
+ # @option options [Int] :limit Used to display the number of results per page. The maximum number of results that can be fetched is 20.
51
+ # @option options [Int] :offset Denotes the number of value items by which the results should be offset. Eg:- If the result contains a 1000 values and limit is set to 10 and offset is set to 705, then values 706 through 715 are displayed in the results. This parameter is also used for pagination of the results.
52
+ def list(options = nil)
53
+ return perform_list if options.nil?
54
+ valid_param?(:options, options, Hash, true)
55
+
56
+ params = {}
57
+ params_expected = %i[
58
+ call_uuid add_time__gt add_time__gte
59
+ add_time__lt add_time__lte
60
+ ]
61
+
62
+ params_expected.each do |param|
63
+ if options.key?(param) && valid_param?(param, options[param], [String, Symbol], true)
64
+ params[param] = options[param]
65
+ end
66
+ end
67
+
68
+ if options.key?(:subaccount) &&
69
+ valid_subaccount?(options[:subaccount], true)
70
+ params[:subaccount] = options[:subaccount]
71
+ end
72
+
73
+ %i[offset limit].each do |param|
74
+ if options.key?(param) && valid_param?(param, options[param], [Integer, Integer], true)
75
+ params[param] = options[param]
76
+ end
77
+ end
78
+
79
+ if options.key?(:limit) && (options[:limit] > 20 || options[:limit] <= 0)
80
+ raise_invalid_request('The maximum number of results that can be fetched'\
81
+ " is 20. limit can't be more than 20 or less than 1")
82
+ end
83
+
84
+ raise_invalid_request("Offset can't be negative") if options.key?(:offset) && options[:offset] < 0
85
+
86
+ perform_list(params)
87
+ end
88
+
89
+ # @param [String] recording_id
90
+ def get(recording_id)
91
+ valid_param?(:recording_id, recording_id, [String, Symbol], true)
92
+ raise_invalid_request('Invalid recording_id passed') if recording_id.empty?
93
+ perform_get(recording_id)
94
+ end
95
+
96
+ def each
97
+ offset = 0
98
+ loop do
99
+ recording_list = list(offset: offset)
100
+ recording_list[:objects].each { |recording| yield recording }
101
+ offset += 20
102
+ return unless recording_list.length == 20
103
+ end
104
+ end
105
+
106
+ # @param [String] recording_id
107
+ def delete(recording_id)
108
+ valid_param?(:recording_id, recording_id, [String, Symbol], true)
109
+ raise_invalid_request('Invalid recording_id passed') if recording_id.empty?
110
+ Recording.new(@_client, resource_id: recording_id).delete
111
+ end
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,199 @@
1
+ require 'json'
2
+ require 'faraday'
3
+ require 'faraday_middleware'
4
+
5
+ require_relative 'exceptions'
6
+ require_relative 'utils'
7
+ require_relative 'resources'
8
+ require_relative 'base'
9
+
10
+ module Plivo
11
+ # Core client, used for all API requests
12
+ include Utils
13
+ class RestClient
14
+ # Base stuff
15
+ attr_reader :headers, :auth_credentials
16
+
17
+ # Resources
18
+ attr_reader :messages, :account, :subaccounts, :recordings
19
+ attr_reader :pricings, :numbers, :calls, :conferences
20
+ attr_reader :phone_numbers, :applications, :endpoints
21
+
22
+ def initialize(auth_id = nil, auth_token = nil, proxy_options = nil)
23
+ configure_credentials(auth_id, auth_token)
24
+ configure_proxies(proxy_options)
25
+ configure_headers
26
+ configure_connection
27
+ configure_interfaces
28
+ end
29
+
30
+ def auth_id
31
+ @auth_credentials[:auth_id]
32
+ end
33
+
34
+ def process_response(method, response)
35
+ handle_response_exceptions(response)
36
+ if method == 'DELETE'
37
+ if response[:status] != 204
38
+ raise Exceptions::PlivoRESTError, "Resource at #{response[:url]} "\
39
+ 'couldn\'t be deleted'
40
+ end
41
+ elsif !([200, 201, 202].include? response[:status])
42
+ raise Exceptions::PlivoRESTError, "Received #{response[:status]} for #{method}"
43
+ end
44
+
45
+ response[:body]
46
+ end
47
+
48
+ def send_request(resource_path, method = 'GET', data = {}, timeout = nil)
49
+ response = case method
50
+ when 'GET' then send_get(resource_path, data, timeout)
51
+ when 'POST' then send_post(resource_path, data, timeout)
52
+ when 'DELETE' then send_delete(resource_path, timeout)
53
+ else raise_invalid_request("#{method} not supported by Plivo, yet")
54
+ end
55
+
56
+ process_response(method, response.to_hash)
57
+ end
58
+
59
+ private
60
+
61
+ def auth_token
62
+ @auth_credentials[:auth_token]
63
+ end
64
+
65
+ def configure_credentials(auth_id, auth_token)
66
+ # Fetches and sets the right credentials
67
+ auth_id ||= ENV['PLIVO_AUTH_ID']
68
+ auth_token ||= ENV['PLIVO_AUTH_TOKEN']
69
+
70
+ raise Exceptions::AuthenticationError, 'Couldn\'t find auth credentials' unless
71
+ auth_id && auth_token
72
+
73
+ raise Exceptions::AuthenticationError, "Invalid auth_id: '#{auth_id}'" unless
74
+ Utils.valid_account?(auth_id)
75
+
76
+ @auth_credentials = {
77
+ auth_id: auth_id,
78
+ auth_token: auth_token
79
+ }
80
+ end
81
+
82
+ def configure_proxies(proxy_dict)
83
+ @proxy_hash = nil
84
+ return unless proxy_dict
85
+
86
+ @proxy_hash = {
87
+ uri: "#{proxy_dict[:proxy_host]}:#{proxy_dict[:proxy_port]}",
88
+ user: proxy_dict[:proxy_user],
89
+ password: proxy_dict[:proxy_pass]
90
+ }
91
+ end
92
+
93
+ def user_agent
94
+ "plivo-ruby/#{Plivo::VERSION} (Ruby #{RUBY_VERSION})"
95
+ end
96
+
97
+ def configure_headers
98
+ @headers = {
99
+ 'User-Agent' => user_agent,
100
+ 'Content-Type' => 'application/json',
101
+ 'Accept' => 'application/json'
102
+ }
103
+ end
104
+
105
+ def configure_interfaces
106
+ @account = Resources::AccountInterface.new(self)
107
+ @messages = Resources::MessagesInterface.new(self)
108
+ @subaccounts = Resources::SubaccountInterface.new(self)
109
+ @recordings = Resources::RecordingInterface.new(self)
110
+ @pricings = Resources::PricingInterface.new(self)
111
+ @numbers = Resources::NumberInterface.new(self)
112
+ @phone_numbers = Resources::PhoneNumberInterface.new(self)
113
+ @conferences = Resources::ConferenceInterface.new(self)
114
+ @calls = Resources::CallInterface.new(self)
115
+ @endpoints = Resources::EndpointInterface.new(self)
116
+ @applications = Resources::ApplicationInterface.new(self)
117
+ end
118
+
119
+ def configure_connection
120
+ @conn = Faraday.new(Base::PLIVO_API_URL) do |faraday|
121
+ faraday.headers = @headers
122
+
123
+ # DANGER: Basic auth should always come after headers, else
124
+ # The headers will replace the basic_auth
125
+
126
+ faraday.basic_auth(auth_id, auth_token)
127
+
128
+ faraday.proxy(@proxy_hash) if @proxy_hash
129
+ faraday.response :json, content_type: /\bjson$/
130
+ faraday.adapter Faraday.default_adapter
131
+ end
132
+ end
133
+
134
+ def send_get(resource_path, data, timeout)
135
+ response = @conn.get do |req|
136
+ req.url resource_path, data
137
+ req.options.timeout = timeout if timeout
138
+ end
139
+ response
140
+ end
141
+
142
+ def send_post(resource_path, data, timeout)
143
+ response = @conn.post do |req|
144
+ req.url resource_path
145
+ req.options.timeout = timeout if timeout
146
+ req.body = JSON.generate(data) if data
147
+ end
148
+ response
149
+ end
150
+
151
+ def send_delete(resource_path, timeout)
152
+ response = @conn.delete do |req|
153
+ req.url resource_path
154
+ req.options.timeout = timeout if timeout
155
+ end
156
+ response
157
+ end
158
+
159
+ def handle_response_exceptions(response)
160
+ exception_mapping = {
161
+ 400 => [
162
+ Exceptions::ValidationError,
163
+ 'A parameter is missing or is invalid while accessing resource'
164
+ ],
165
+ 401 => [
166
+ Exceptions::AuthenticationError,
167
+ 'Failed to authenticate while accessing resource'
168
+ ],
169
+ 404 => [
170
+ Exceptions::ResourceNotFoundError,
171
+ 'Resource not found'
172
+ ],
173
+ 405 => [
174
+ Exceptions::InvalidRequestError,
175
+ 'HTTP method used is not allowed to access resource'
176
+ ],
177
+ 500 => [
178
+ Exceptions::PlivoServerError,
179
+ 'A server error occurred while accessing resource'
180
+ ]
181
+ }
182
+
183
+ response_json = response[:body]
184
+ return unless exception_mapping.key? response[:status]
185
+
186
+ exception_now = exception_mapping[response[:status]]
187
+ error_message = if (response_json.is_a? Hash) && (response_json.key? 'error')
188
+ response_json['error']
189
+ else
190
+ exception_now[1] + " at: #{response[:url]}"
191
+ end
192
+ if error_message.is_a?(Hash) && error_message.key?('error')
193
+ error_message = error_message['error']
194
+ end
195
+
196
+ raise exception_now[0], error_message.to_s
197
+ end
198
+ end
199
+ end
@@ -0,0 +1,107 @@
1
+ require 'openssl'
2
+ require 'uri'
3
+
4
+ module Plivo
5
+ # Utils module
6
+ module Utils
7
+ module_function
8
+
9
+ def valid_account?(account_id, raise_directly = false)
10
+ valid_subaccount?(account_id, raise_directly) || valid_mainaccount?(account_id, raise_directly)
11
+ end
12
+
13
+ # @param [String] account_id
14
+ # @param [Boolean] raise_directly
15
+ def valid_subaccount?(account_id, raise_directly = false)
16
+ unless account_id.is_a? String
17
+ return false unless raise_directly
18
+ raise_invalid_request('subaccount_id must be a string')
19
+ end
20
+
21
+ if account_id.length != 20
22
+ return false unless raise_directly
23
+ raise_invalid_request('subaccount_id should be of length 20')
24
+ end
25
+
26
+ if account_id[0..1] != 'SA'
27
+ return false unless raise_directly
28
+ raise_invalid_request("subaccount_id should start with 'SA'")
29
+ end
30
+ true
31
+ end
32
+
33
+ def valid_mainaccount?(account_id, raise_directly = false)
34
+ unless account_id.is_a? String
35
+ return false unless raise_directly
36
+ raise_invalid_request('account_id must be a string')
37
+ end
38
+
39
+ if account_id.length != 20
40
+ return false unless raise_directly
41
+ raise_invalid_request('account_id should be of length 20')
42
+ end
43
+
44
+ if account_id[0..1] != 'MA'
45
+ return false unless raise_directly
46
+ raise_invalid_request("account_id should start with 'SA'")
47
+ end
48
+ true
49
+ end
50
+
51
+ def raise_invalid_request(message = '')
52
+ raise Exceptions::InvalidRequestError, message
53
+ end
54
+
55
+ def valid_param?(param_name, param_value, expected_types = nil, mandatory = false, expected_values = nil)
56
+ if mandatory && param_value.nil?
57
+ raise_invalid_request("#{param_name} is a required parameter")
58
+ end
59
+
60
+ return true if param_value.nil?
61
+
62
+ return expected_type?(param_name, expected_types, param_value) unless expected_values
63
+ expected_value?(param_name, expected_values, param_value)
64
+ end
65
+
66
+ def expected_type?(param_name, expected_types, param_value)
67
+ return true if expected_types.nil?
68
+ param_value_class = param_value.class
69
+ param_value_class = Integer if [Fixnum, Bignum].include? param_value_class
70
+ if expected_types.is_a? Array
71
+ return true if expected_types.include? param_value_class
72
+ raise_invalid_request("#{param_name}: Expected one of #{expected_types}"\
73
+ " but received #{param_value.class} instead")
74
+ else
75
+ return true if expected_types == param_value_class
76
+ raise_invalid_request("#{param_name}: Expected a #{expected_types}"\
77
+ " but received #{param_value.class} instead")
78
+ end
79
+ end
80
+
81
+ def expected_value?(param_name, expected_values, param_value)
82
+ return true if expected_values.nil?
83
+ if expected_values.is_a? Array
84
+ return true if expected_values.include? param_value
85
+ raise_invalid_request("#{param_name}: Expected one of #{expected_values}"\
86
+ " but received '#{param_value}' instead")
87
+ else
88
+ return true if expected_values == param_value
89
+ raise_invalid_request("#{param_name}: Expected '#{expected_values}'"\
90
+ " but received '#{param_value}' instead")
91
+ end
92
+ end
93
+
94
+ # @param [String] uri
95
+ # @param [String] nonce
96
+ # @param [String] signature
97
+ # @param [String] auth_token
98
+ def valid_signature?(uri, nonce, signature, auth_token)
99
+ parsed_uri = URI.parse(uri)
100
+ uri_details = { host: parsed_uri.host, path: parsed_uri.path }
101
+ uri_builder_module = parsed_uri.scheme == 'https' ? URI::HTTPS : URI::HTTP
102
+ data_to_sign = uri_builder_module.build(uri_details).to_s + nonce
103
+ sha256_digest = OpenSSL::Digest.new('sha256')
104
+ Base64.encode64(OpenSSL::HMAC.digest(sha256_digest, auth_token, data_to_sign)).strip() == signature
105
+ end
106
+ end
107
+ end