twilio-lookups 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +1 -0
- data/Gemfile +10 -0
- data/Gemfile.lock +43 -0
- data/Makefile +12 -0
- data/Rakefile +10 -0
- data/conf/cacert.pem +3376 -0
- data/lib/twilio-lookups.rb +40 -0
- data/lib/twilio-lookups/rest/base_client.rb +130 -0
- data/lib/twilio-lookups/rest/errors.rb +14 -0
- data/lib/twilio-lookups/rest/instance_resource.rb +87 -0
- data/lib/twilio-lookups/rest/list_resource.rb +100 -0
- data/lib/twilio-lookups/rest/lookups/phone_numbers.rb +17 -0
- data/lib/twilio-lookups/rest/lookups_client.rb +99 -0
- data/lib/twilio-lookups/rest/next_gen_list_resource.rb +36 -0
- data/lib/twilio-lookups/rest/utils.rb +49 -0
- data/lib/twilio-lookups/util.rb +15 -0
- data/lib/twilio-lookups/util/client_config.rb +29 -0
- data/lib/twilio-lookups/version.rb +3 -0
- data/spec/rest/phone_number_spec.rb +28 -0
- data/spec/spec_helper.rb +13 -0
- data/twilio-lookups.gemspec +34 -0
- metadata +132 -0
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'net/https'
|
3
|
+
require 'builder'
|
4
|
+
require 'multi_json'
|
5
|
+
require 'cgi'
|
6
|
+
require 'openssl'
|
7
|
+
require 'base64'
|
8
|
+
require 'forwardable'
|
9
|
+
require 'jwt'
|
10
|
+
|
11
|
+
require 'twilio-lookups/version' unless defined?(Twilio::VERSION)
|
12
|
+
require 'twilio-lookups/util'
|
13
|
+
require 'twilio-lookups/rest/utils'
|
14
|
+
require 'twilio-lookups/rest/errors'
|
15
|
+
require 'twilio-lookups/util/client_config'
|
16
|
+
require 'twilio-lookups/rest/lookups_client'
|
17
|
+
require 'twilio-lookups/rest/instance_resource'
|
18
|
+
require 'twilio-lookups/rest/list_resource'
|
19
|
+
require 'twilio-lookups/rest/next_gen_list_resource'
|
20
|
+
require 'twilio-lookups/rest/lookups/phone_numbers'
|
21
|
+
|
22
|
+
module TwilioLookups
|
23
|
+
extend SingleForwardable
|
24
|
+
|
25
|
+
def_delegators :configuration, :account_sid, :auth_token
|
26
|
+
|
27
|
+
##
|
28
|
+
# Pre-configure with account SID and auth token so that you don't need to
|
29
|
+
# pass them to various initializers each time.
|
30
|
+
def self.configure(&block)
|
31
|
+
yield configuration
|
32
|
+
end
|
33
|
+
|
34
|
+
##
|
35
|
+
# Returns an existing or instantiates a new configuration object.
|
36
|
+
def self.configuration
|
37
|
+
@configuration ||= Util::Configuration.new
|
38
|
+
end
|
39
|
+
private_class_method :configuration
|
40
|
+
end
|
@@ -0,0 +1,130 @@
|
|
1
|
+
module TwilioLookups
|
2
|
+
module REST
|
3
|
+
class BaseClient
|
4
|
+
include TwilioLookups::Util
|
5
|
+
include TwilioLookups::REST::Utils
|
6
|
+
|
7
|
+
HTTP_HEADERS = {
|
8
|
+
'Accept' => 'application/json',
|
9
|
+
'Accept-Charset' => 'utf-8',
|
10
|
+
'User-Agent' => "twilio-ruby/#{TwilioLookups::VERSION}" \
|
11
|
+
" (#{RUBY_ENGINE}/#{RUBY_PLATFORM}" \
|
12
|
+
" #{RUBY_VERSION}-p#{RUBY_PATCHLEVEL})"
|
13
|
+
}
|
14
|
+
|
15
|
+
##
|
16
|
+
# Override the default host for a REST Client (api.twilio.com)
|
17
|
+
def self.host(host=nil)
|
18
|
+
return @host unless host
|
19
|
+
@host = host
|
20
|
+
end
|
21
|
+
|
22
|
+
attr_reader :account_sid, :last_request, :last_response
|
23
|
+
|
24
|
+
def initialize(*args)
|
25
|
+
options = args.last.is_a?(Hash) ? args.pop : {}
|
26
|
+
options[:host] ||= self.class.host
|
27
|
+
@config = TwilioLookups::Util::ClientConfig.new options
|
28
|
+
|
29
|
+
@account_sid = args[0] || TwilioLookups.account_sid
|
30
|
+
@auth_token = args[1] || TwilioLookups.auth_token
|
31
|
+
if @account_sid.nil? || @auth_token.nil?
|
32
|
+
raise ArgumentError, 'Account SID and auth token are required'
|
33
|
+
end
|
34
|
+
|
35
|
+
set_up_connection
|
36
|
+
set_up_subresources
|
37
|
+
end
|
38
|
+
|
39
|
+
##
|
40
|
+
# Define #get, #put, #post and #delete helper methods for sending HTTP
|
41
|
+
# requests to TwilioLookups. You shouldn't need to use these methods directly,
|
42
|
+
# but they can be useful for debugging. Each method returns a hash
|
43
|
+
# obtained from parsing the JSON object in the response body.
|
44
|
+
[:get, :put, :post, :delete].each do |method|
|
45
|
+
method_class = Net::HTTP.const_get method.to_s.capitalize
|
46
|
+
define_method method do |path, *args|
|
47
|
+
params = twilify(args[0])
|
48
|
+
params = {} if params.empty?
|
49
|
+
# build the full path unless already given
|
50
|
+
path = build_full_path(path, params, method) unless args[1]
|
51
|
+
request = method_class.new(path, HTTP_HEADERS)
|
52
|
+
request.basic_auth(@account_sid, @auth_token)
|
53
|
+
request.form_data = params if [:post, :put].include?(method)
|
54
|
+
connect_and_send(request)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
protected
|
59
|
+
|
60
|
+
##
|
61
|
+
# Builds up full request path
|
62
|
+
# Needs implementation in child classes
|
63
|
+
def build_full_path(path, params, method)
|
64
|
+
raise NotImplementedError
|
65
|
+
end
|
66
|
+
|
67
|
+
##
|
68
|
+
# Set up and cache a Net::HTTP object to use when making requests. This is
|
69
|
+
# a private method documented for completeness.
|
70
|
+
def set_up_connection # :doc:
|
71
|
+
connection_class = Net::HTTP::Proxy @config.proxy_addr,
|
72
|
+
@config.proxy_port, @config.proxy_user, @config.proxy_pass
|
73
|
+
@connection = connection_class.new @config.host, @config.port
|
74
|
+
set_up_ssl
|
75
|
+
@connection.open_timeout = @config.timeout
|
76
|
+
@connection.read_timeout = @config.timeout
|
77
|
+
end
|
78
|
+
|
79
|
+
##
|
80
|
+
# Set up the ssl properties of the <tt>@connection</tt> Net::HTTP object.
|
81
|
+
# This is a private method documented for completeness.
|
82
|
+
def set_up_ssl # :doc:
|
83
|
+
@connection.use_ssl = @config.use_ssl
|
84
|
+
if @config.ssl_verify_peer
|
85
|
+
@connection.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
86
|
+
@connection.ca_file = @config.ssl_ca_file
|
87
|
+
else
|
88
|
+
@connection.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
##
|
93
|
+
# Set up sub resources attributes.
|
94
|
+
def set_up_subresources # :doc:
|
95
|
+
# To be overridden
|
96
|
+
end
|
97
|
+
|
98
|
+
##
|
99
|
+
# Send an HTTP request using the cached <tt>@connection</tt> object and
|
100
|
+
# return the JSON response body parsed into a hash. Also save the raw
|
101
|
+
# Net::HTTP::Request and Net::HTTP::Response objects as
|
102
|
+
# <tt>@last_request</tt> and <tt>@last_response</tt> to allow for
|
103
|
+
# inspection later.
|
104
|
+
def connect_and_send(request) # :doc:
|
105
|
+
@last_request = request
|
106
|
+
retries_left = @config.retry_limit
|
107
|
+
begin
|
108
|
+
response = @connection.request request
|
109
|
+
@last_response = response
|
110
|
+
if response.kind_of? Net::HTTPServerError
|
111
|
+
raise TwilioLookups::REST::ServerError
|
112
|
+
end
|
113
|
+
rescue
|
114
|
+
raise if request.class == Net::HTTP::Post
|
115
|
+
if retries_left > 0 then retries_left -= 1; retry else raise end
|
116
|
+
end
|
117
|
+
if response.body and !response.body.empty?
|
118
|
+
object = MultiJson.load response.body
|
119
|
+
elsif response.kind_of? Net::HTTPBadRequest
|
120
|
+
object = { message: 'Bad request', code: 400 }
|
121
|
+
end
|
122
|
+
|
123
|
+
if response.kind_of? Net::HTTPClientError
|
124
|
+
raise TwilioLookups::REST::RequestError.new object['message'], object['code']
|
125
|
+
end
|
126
|
+
object
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
module TwilioLookups
|
2
|
+
module REST
|
3
|
+
##
|
4
|
+
# A class to wrap an instance resource (like a call or application) within
|
5
|
+
# the Twilio API. All other instance resource classes within this library
|
6
|
+
# inherit from this class. You shouldn't need to instantiate this class
|
7
|
+
# directly. But reviewing the available methods is informative since they
|
8
|
+
# are rarely overridden in the inheriting class.
|
9
|
+
class InstanceResource
|
10
|
+
include Utils
|
11
|
+
|
12
|
+
##
|
13
|
+
# Instantiate a new instance resource object. You must pass the +path+ of
|
14
|
+
# the instance (e.g. /2010-04-01/Accounts/AC123/Calls/CA456) as well as a
|
15
|
+
# +client+ object that responds to #get #post and #delete. This client
|
16
|
+
# is meant to be an instance of Twilio::REST::Client but could just as
|
17
|
+
# well be a mock object if you want to test the interface. The optional
|
18
|
+
# +params+ hash will be converted into attributes on the instantiated
|
19
|
+
# object.
|
20
|
+
def initialize(path, client, params = {})
|
21
|
+
@path, @client = path, client
|
22
|
+
set_up_properties_from params
|
23
|
+
end
|
24
|
+
|
25
|
+
def inspect # :nodoc:
|
26
|
+
"<#{self.class} @path=#{@path}>"
|
27
|
+
end
|
28
|
+
|
29
|
+
##
|
30
|
+
# Update the properties of this instance resource using the key/value
|
31
|
+
# pairs in +params+. This makes an HTTP POST request to <tt>@path</tt>
|
32
|
+
# to handle the update. For example, to update the +VoiceUrl+ of a Twilio
|
33
|
+
# Application you could write:
|
34
|
+
#
|
35
|
+
# @app.update voice_url: 'http://my.other.app.com/handle_voice'
|
36
|
+
#
|
37
|
+
# After returning, the object will contain the most recent state of the
|
38
|
+
# instance resource, including the newly updated properties.
|
39
|
+
def update(params = {})
|
40
|
+
raise "Can't update a resource without a REST Client" unless @client
|
41
|
+
set_up_properties_from(@client.post(@path, params))
|
42
|
+
self
|
43
|
+
end
|
44
|
+
|
45
|
+
##
|
46
|
+
# Refresh the attributes of this instance resource object by fetching it
|
47
|
+
# from Twilio. Calling this makes an HTTP GET request to <tt>@path</tt>.
|
48
|
+
def refresh
|
49
|
+
raise "Can't refresh a resource without a REST Client" unless @client
|
50
|
+
@updated = false
|
51
|
+
set_up_properties_from(@client.get(@path))
|
52
|
+
self
|
53
|
+
end
|
54
|
+
|
55
|
+
##
|
56
|
+
# Delete an instance resource from Twilio. This operation isn't always
|
57
|
+
# supported. For instance, you can't delete an SMS. Calling this method
|
58
|
+
# makes an HTTP DELETE request to <tt>@path</tt>.
|
59
|
+
def delete
|
60
|
+
raise "Can't delete a resource without a REST Client" unless @client
|
61
|
+
@client.delete @path
|
62
|
+
end
|
63
|
+
|
64
|
+
##
|
65
|
+
# Lazily load attributes of the instance resource by waiting to fetch it
|
66
|
+
# until an attempt is made to access an unknown attribute.
|
67
|
+
def method_missing(method, *args)
|
68
|
+
super if @updated
|
69
|
+
set_up_properties_from(@client.get(@path))
|
70
|
+
self.send method, *args
|
71
|
+
end
|
72
|
+
|
73
|
+
protected
|
74
|
+
|
75
|
+
def set_up_properties_from(hash)
|
76
|
+
eigenclass = class << self; self; end
|
77
|
+
hash.each do |p,v|
|
78
|
+
property = detwilify p
|
79
|
+
unless ['client', 'updated'].include? property
|
80
|
+
eigenclass.send :define_method, property.to_sym, &lambda { v }
|
81
|
+
end
|
82
|
+
end
|
83
|
+
@updated = !hash.keys.empty?
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
module TwilioLookups
|
2
|
+
module REST
|
3
|
+
class ListResource
|
4
|
+
include Utils
|
5
|
+
|
6
|
+
def initialize(path, client)
|
7
|
+
custom_names = {
|
8
|
+
'Activities' => 'Activity',
|
9
|
+
'Addresses' => 'Address',
|
10
|
+
'Countries' => 'Country',
|
11
|
+
'Feedback' => 'FeedbackInstance',
|
12
|
+
'IpAddresses' => 'IpAddress',
|
13
|
+
'Media' => 'MediaInstance',
|
14
|
+
}
|
15
|
+
@path, @client = path, client
|
16
|
+
resource_name = self.class.name.split('::')[-1]
|
17
|
+
instance_name = custom_names.fetch(resource_name, resource_name.chop)
|
18
|
+
|
19
|
+
# The next line grabs the enclosing module. Necessary for resources
|
20
|
+
# contained in their own submodule like /SMS/Messages
|
21
|
+
parent_module = self.class.to_s.split('::')[-2]
|
22
|
+
full_module_path = if parent_module == "REST"
|
23
|
+
TwilioLookups::REST
|
24
|
+
else
|
25
|
+
TwilioLookups::REST.const_get parent_module
|
26
|
+
end
|
27
|
+
|
28
|
+
@instance_class = full_module_path.const_get instance_name
|
29
|
+
@list_key, @instance_id_key = detwilify(resource_name), 'sid'
|
30
|
+
end
|
31
|
+
|
32
|
+
def inspect # :nodoc:
|
33
|
+
"<#{self.class} @path=#{@path}>"
|
34
|
+
end
|
35
|
+
|
36
|
+
##
|
37
|
+
# Grab a list of this kind of resource and return it as an array. The
|
38
|
+
# array includes two special methods +previous_page+ and +next_page+
|
39
|
+
# which will return the previous or next page or resources. By default
|
40
|
+
# Twilio will only return 50 resources, and the maximum number of
|
41
|
+
# resources you can request (using the :page_size param) is 1000.
|
42
|
+
#
|
43
|
+
# The optional +params+ hash allows you to filter the list returned. The
|
44
|
+
# filters for each list resource type are defined by Twilio.
|
45
|
+
def list(params={}, full_path=false)
|
46
|
+
raise "Can't get a resource list without a REST Client" unless @client
|
47
|
+
response = @client.get @path, params, full_path
|
48
|
+
resources = response[@list_key]
|
49
|
+
path = full_path ? @path.split('.')[0] : @path
|
50
|
+
resource_list = resources.map do |resource|
|
51
|
+
@instance_class.new "#{path}/#{resource[@instance_id_key]}", @client,
|
52
|
+
resource
|
53
|
+
end
|
54
|
+
# set the +previous_page+ and +next_page+ properties on the array
|
55
|
+
client, list_class = @client, self.class
|
56
|
+
resource_list.instance_eval do
|
57
|
+
eigenclass = class << self; self; end
|
58
|
+
eigenclass.send :define_method, :previous_page, &lambda {
|
59
|
+
if response['previous_page_uri']
|
60
|
+
list_class.new(response['previous_page_uri'], client).list({}, true)
|
61
|
+
else
|
62
|
+
[]
|
63
|
+
end
|
64
|
+
}
|
65
|
+
eigenclass.send :define_method, :next_page, &lambda {
|
66
|
+
if response['next_page_uri']
|
67
|
+
list_class.new(response['next_page_uri'], client).list({}, true)
|
68
|
+
else
|
69
|
+
[]
|
70
|
+
end
|
71
|
+
}
|
72
|
+
end
|
73
|
+
resource_list
|
74
|
+
end
|
75
|
+
|
76
|
+
##
|
77
|
+
# Return an empty instance resource object with the proper path. Note that
|
78
|
+
# this will never raise a Twilio::REST::RequestError on 404 since no HTTP
|
79
|
+
# request is made. The HTTP request is made when attempting to access an
|
80
|
+
# attribute of the returned instance resource object, such as
|
81
|
+
# its #date_created or #voice_url attributes.
|
82
|
+
def get(sid)
|
83
|
+
@instance_class.new "#{@path}/#{sid}", @client
|
84
|
+
end
|
85
|
+
alias :find :get # for the ActiveRecord lovers
|
86
|
+
|
87
|
+
##
|
88
|
+
# Return a newly created resource. Some +params+ may be required. Consult
|
89
|
+
# the Twilio REST API documentation related to the kind of resource you
|
90
|
+
# are attempting to create for details. Calling this method makes an HTTP
|
91
|
+
# POST request to <tt>@path</tt> with the given params
|
92
|
+
def create(params={})
|
93
|
+
raise "Can't create a resource without a REST Client" unless @client
|
94
|
+
response = @client.post @path, params
|
95
|
+
@instance_class.new "#{@path}/#{response[@instance_id_key]}", @client,
|
96
|
+
response
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module TwilioLookups
|
2
|
+
module REST
|
3
|
+
module Lookups
|
4
|
+
class PhoneNumbers < TwilioLookups::REST::NextGenListResource;
|
5
|
+
include TwilioLookups::Util
|
6
|
+
include TwilioLookups::REST::Utils
|
7
|
+
|
8
|
+
def get(number, query={})
|
9
|
+
full_path = "#{@path}/#{URI.encode(number)}"
|
10
|
+
full_path << "?#{url_encode(twilify(query))}" if !query.empty?
|
11
|
+
@instance_class.new full_path, @client
|
12
|
+
end
|
13
|
+
end
|
14
|
+
class PhoneNumber < InstanceResource; end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
require 'twilio-lookups/rest/base_client'
|
2
|
+
module TwilioLookups
|
3
|
+
module REST
|
4
|
+
class LookupsClient < BaseClient
|
5
|
+
API_VERSION = 'v1'
|
6
|
+
|
7
|
+
attr_reader :phone_numbers
|
8
|
+
|
9
|
+
host 'lookups.twilio.com'
|
10
|
+
|
11
|
+
##
|
12
|
+
# Instantiate a new HTTP Lookups client to talk to TwilioLookups. The parameters
|
13
|
+
# +account_sid+, +auth_token+ and +workspace_sid are required, unless you
|
14
|
+
# have configured them already using the block configure syntax, and used
|
15
|
+
# to generate the HTTP basic auth header in each request. The +options+
|
16
|
+
# parameter is a hash of connection configuration options. the following
|
17
|
+
# keys are supported:
|
18
|
+
#
|
19
|
+
# === <tt>host: 'lookups.twilio.com'</tt>
|
20
|
+
#
|
21
|
+
# The domain to which you'd like the client to make HTTP requests. Useful
|
22
|
+
# for testing. Defaults to 'lookups.twilio.com'.
|
23
|
+
#
|
24
|
+
# === <tt>port: 443</tt>
|
25
|
+
#
|
26
|
+
# The port on which to connect to the above domain. Defaults to 443 and
|
27
|
+
# should be left that way except in testing environments.
|
28
|
+
#
|
29
|
+
# === <tt>use_ssl: true</tt>
|
30
|
+
#
|
31
|
+
# Declare whether ssl should be used for connections to the above domain.
|
32
|
+
# Defaults to true and should be left alone except when testing.
|
33
|
+
#
|
34
|
+
# === <tt>ssl_verify_peer: true</tt>
|
35
|
+
#
|
36
|
+
# Declare whether to verify the host's ssl cert when setting up the
|
37
|
+
# connection to the above domain. Defaults to true, but can be turned off
|
38
|
+
# to avoid ssl certificate verification failures in environments without
|
39
|
+
# the necessary ca certificates.
|
40
|
+
#
|
41
|
+
# === <tt>ssl_ca_file: '/path/to/ca/file'</tt>
|
42
|
+
#
|
43
|
+
# Specify the path to the certificate authority bundle you'd like to use
|
44
|
+
# to verify TwilioLookups's SSL certificate on each request. If not specified, a
|
45
|
+
# certificate bundle extraced from Firefox is packaged with the gem and
|
46
|
+
# used by default.
|
47
|
+
#
|
48
|
+
# === <tt>timeout: 30</tt>
|
49
|
+
#
|
50
|
+
# Set the time in seconds to wait before timing out the HTTP request.
|
51
|
+
# Defaults to 30 seconds. If you aren't fetching giant pages of call or
|
52
|
+
# SMS logs you can safely decrease this to something like 3 seconds or
|
53
|
+
# lower. In paricular if you are sending SMS you can set this to 1 second
|
54
|
+
# or less and swallow the exception if you don't care about the response.
|
55
|
+
#
|
56
|
+
# === <tt>proxy_addr: 'proxy.host.domain'</tt>
|
57
|
+
#
|
58
|
+
# The domain of a proxy through which you'd like the client to make HTTP
|
59
|
+
# requests. Defaults to nil.
|
60
|
+
#
|
61
|
+
# === <tt>proxy_port: 3128</tt>
|
62
|
+
#
|
63
|
+
# The port on which to connect to the above proxy. Defaults to nil.
|
64
|
+
#
|
65
|
+
# === <tt>proxy_user: 'username'</tt>
|
66
|
+
#
|
67
|
+
# The user name to use for authentication with the proxy. Defaults to nil.
|
68
|
+
#
|
69
|
+
# === <tt>proxy_pass: 'password'</tt>
|
70
|
+
#
|
71
|
+
# The password to use for authentication with the proxy. Defaults to nil.
|
72
|
+
#
|
73
|
+
# === <tt>retry_limit: 1</tt>
|
74
|
+
#
|
75
|
+
# The number of times to retry a request that has failed before throwing
|
76
|
+
# an exception. Defaults to one.
|
77
|
+
def inspect # :nodoc:
|
78
|
+
"<TwilioLookups::REST::LookupsClient @account_sid=#{@account_sid}>"
|
79
|
+
end
|
80
|
+
|
81
|
+
protected
|
82
|
+
|
83
|
+
##
|
84
|
+
# Set up +phone_numbers+ attribute.
|
85
|
+
def set_up_subresources # :doc:
|
86
|
+
@phone_numbers = TwilioLookups::REST::Lookups::PhoneNumbers.new "/#{API_VERSION}/PhoneNumbers", self
|
87
|
+
end
|
88
|
+
|
89
|
+
##
|
90
|
+
# Builds up full request path
|
91
|
+
def build_full_path(path, params, method)
|
92
|
+
path = path.dup
|
93
|
+
path << "?#{url_encode(params)}" if method == :get && !params.empty?
|
94
|
+
path
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|