Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

new twilio-ruby finally :)

  • Loading branch information...
commit c8a763e3da484eb8217d84dc9c3ed2283e662ce9 1 parent 1448b7f
@andrewmbenton andrewmbenton authored
Showing with 989 additions and 0 deletions.
  1. +19 −0 LICENSE
  2. +87 −0 README.md
  3. +16 −0 Rakefile
  4. +91 −0 examples.rb
  5. +50 −0 lib/twilio-ruby.rb
  6. +12 −0 lib/twilio-ruby/rest/accounts/account.rb
  7. +5 −0 lib/twilio-ruby/rest/accounts/accounts.rb
  8. +5 −0 lib/twilio-ruby/rest/applications/application.rb
  9. +5 −0 lib/twilio-ruby/rest/applications/applications.rb
  10. +5 −0 lib/twilio-ruby/rest/available_phone_numbers/available_phone_number.rb
  11. +11 −0 lib/twilio-ruby/rest/available_phone_numbers/available_phone_numbers.rb
  12. +10 −0 lib/twilio-ruby/rest/available_phone_numbers/country.rb
  13. +11 −0 lib/twilio-ruby/rest/available_phone_numbers/local.rb
  14. +11 −0 lib/twilio-ruby/rest/available_phone_numbers/toll_free.rb
  15. +22 −0 lib/twilio-ruby/rest/calls/call.rb
  16. +10 −0 lib/twilio-ruby/rest/calls/calls.rb
  17. +92 −0 lib/twilio-ruby/rest/client.rb
  18. +10 −0 lib/twilio-ruby/rest/conferences/conference.rb
  19. +5 −0 lib/twilio-ruby/rest/conferences/conferences.rb
  20. +17 −0 lib/twilio-ruby/rest/conferences/participant.rb
  21. +5 −0 lib/twilio-ruby/rest/conferences/participants.rb
  22. +6 −0 lib/twilio-ruby/rest/errors.rb
  23. +5 −0 lib/twilio-ruby/rest/incoming_phone_numbers/incoming_phone_number.rb
  24. +9 −0 lib/twilio-ruby/rest/incoming_phone_numbers/incoming_phone_numbers.rb
  25. +56 −0 lib/twilio-ruby/rest/instance_resource.rb
  26. +35 −0 lib/twilio-ruby/rest/list_resource.rb
  27. +5 −0 lib/twilio-ruby/rest/notifications/notification.rb
  28. +5 −0 lib/twilio-ruby/rest/notifications/notifications.rb
  29. +5 −0 lib/twilio-ruby/rest/outgoing_caller_ids/outgoing_caller_id.rb
  30. +9 −0 lib/twilio-ruby/rest/outgoing_caller_ids/outgoing_caller_ids.rb
  31. +12 −0 lib/twilio-ruby/rest/recordings/recording.rb
  32. +5 −0 lib/twilio-ruby/rest/recordings/recordings.rb
  33. +5 −0 lib/twilio-ruby/rest/sandbox/sandbox.rb
  34. +5 −0 lib/twilio-ruby/rest/sms/message.rb
  35. +5 −0 lib/twilio-ruby/rest/sms/messages.rb
  36. +5 −0 lib/twilio-ruby/rest/sms/short_code.rb
  37. +5 −0 lib/twilio-ruby/rest/sms/short_codes.rb
  38. +10 −0 lib/twilio-ruby/rest/sms/sms.rb
  39. +5 −0 lib/twilio-ruby/rest/transcriptions/transcription.rb
  40. +5 −0 lib/twilio-ruby/rest/transcriptions/transcriptions.rb
  41. +25 −0 lib/twilio-ruby/rest/utils.rb
  42. +15 −0 lib/twilio-ruby/twiml/response.rb
  43. +7 −0 lib/twilio-ruby/util.rb
  44. +22 −0 lib/twilio-ruby/util/request_validator.rb
  45. +210 −0 test/twilio_spec.rb
  46. +14 −0 twilio-ruby.gemspec
View
19 LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2010 Andrew Benton.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
View
87 README.md
@@ -0,0 +1,87 @@
+## Get Started
+
+To install:
+
+Via rubygems.org:
+
+```
+$ sudo gem install twilio-ruby
+```
+
+To build and install yourself from the latest source:
+
+```
+$ git clone git@github.com:andrewmbenton/twilio-ruby.git
+$ cd twilio-ruby; rake gem
+$ sudo gem install pkg/twilio-ruby-{version}
+```
+
+## Some Code To Get You Started
+
+### Setup Work
+
+``` ruby
+require 'rubygems'
+require 'twilio-ruby'
+
+# put your own credentials here
+@account_sid = 'AC043dcf9844e04758bc3a36a84c29761'
+@auth_token = '62ea81de3a5b414154eb263595357c69'
+
+# set up a client to talk to the Twilio REST API
+@client = Twilio::REST::Client.new(@account_sid, @auth_token)
+```
+
+### Send an SMS
+
+``` ruby
+# send an sms
+@client.account.sms.messages.create(
+ :from => '+14159341234',
+ :to => '+16105557069',
+ :body => 'Hey there!'
+)
+```
+
+### Do Some Stuff With Calls
+
+``` ruby
+# make a new outgoing call
+@call = @client.account.calls.create(
+ :from => '+14159341234',
+ :to => '+18004567890',
+ :url => 'http://myapp.com/call-handler'
+)
+
+# hangup a ringing call, but don't touch it if it's connected
+@call.cancel
+
+# if you have the call sid, you can fetch a call object via:
+@call = @client.account.calls.get('CA386025c9bf5d6052a1d1ea42b4d16662')
+
+# redirect an in-progress call
+@call.redirect_to('http://myapp.com/call-redirect')
+
+# hangup a call, no matter whether it is ringing or connected
+@call.hangup
+```
+
+### Buy a Phone Number
+
+``` ruby
+# print some available numbers
+@numbers = @client.account.available_phone_numbers.get('US').local.list(
+ :contains => 'AWESOME'
+)
+@numbers.each {|num| puts num.phone_number}
+
+# buy the first one
+@number = @numbers[0].phone_number
+@account.incoming_phone_numbers.create(:phone_number => @number)
+```
+
+## More Information
+
+There are more detailed examples in the included [examples.rb](twilio-ruby/blob/master/examples.rb).
+
+Full [API documentation](twilio-ruby/wiki/Documentation), as well as an [upgrade guide](twilio-ruby/wiki/UpgradeGuide) for users of the old twiliolib gem, is available in the [twilio-ruby github wiki](twilio-ruby/wiki).
View
16 Rakefile
@@ -0,0 +1,16 @@
+require 'rubygems'
+require 'rake/gempackagetask'
+require 'rspec/core/rake_task'
+
+spec = eval(File.read('twilio-ruby.gemspec'))
+
+Rake::GemPackageTask.new(spec) do |p|
+ p.gem_spec = spec
+end
+
+RSpec::Core::RakeTask.new do |t|
+ t.pattern = 'test/*_spec.rb'
+ t.rspec_opts = ['-c']
+end
+
+task :default => :spec
View
91 examples.rb
@@ -0,0 +1,91 @@
+# examples version 3
+
+@account_sid = 'AC043dcf9844e04758bc3a36a84c29761'
+@auth_token = '62ea81de3a5b414154eb263595357c69'
+# set up a client, without any http requests
+@client = Twilio::REST::Client.new(@account_sid, @auth_token)
+
+################ ACCOUNTS ################
+
+# grab an account instance resource (no http request)
+@account = @client.accounts.get(@account_sid)
+# http round trip happens here
+puts @account.friendly_name
+
+# update an account's friendly name (only one http request, for the POST)
+@client.accounts.get(@account_sid).update(:friendly_name => 'A Fabulous Friendly Name')
+
+# shortcut to grab your account object (account_sid is inferred from the client's auth credentials)
+@account = @client.account
+
+################ CALLS ################
+
+# print a list of calls (all parameters optional, single http req)
+@account.calls.list({:page => 0, :page_size => 1000, :start_time => '2010-09-01'}).each do |call|
+ puts call.sid
+end
+
+# get a particular call and list its recordings (one http req, for each())
+@account.calls.get('CAXXXXXXX').recordings.list.each do {|r| puts r.sid}
+
+# make a new outgoing call (this is the same type of object we get
+# from calls.get, except this has attributes since we made an http request)
+@call = @account.calls.create({:from => '+14159341234', :to => '+18004567890', :url => 'http://myapp.com/call-handler'})
+
+# cancel the call if not already in progress (single http request)
+@account.calls.get(@call.sid).update({:status => 'canceled'})
+# or equivalently (single http request)
+@call.update({:status => 'canceled'})
+# or simply
+@call.cancel
+
+# redirect and then terminate a call (each one http request)
+@account.calls.get('CA386025c9bf5d6052a1d1ea42b4d16662').update({:url => 'http://myapp.com/call-redirect'})
+@account.calls.get('CA386025c9bf5d6052a1d1ea42b4d16662').update({:status => 'completed'}) # formerly @call.hangup
+# or, use the aliases...
+@call.redirect_to('http://myapp.com/call-redirect')
+@call.hangup
+
+################ SMS MESSAGES ################
+
+# print a list of sms messages (one http request)
+@account.sms.messages.list({:date_sent => '2010-09-01'}).each do |sms|
+ puts sms.body
+end
+
+# print a particular sms message (one http request)
+puts @account.sms.messages.get('SMXXXXXXXX').body
+
+# send an sms
+@account.sms.messages.create(:from => '+14159341234', :to => '+16105557069', :body => 'Hey there!')
+
+################ PHONE NUMBERS ################
+
+# get a list of supported country codes (api currently doesn't support this)
+@account.available_phone_numbers.list
+
+# print some available numbers (only one http request)
+@numbers = @account.available_phone_numbers.get('US').local.list({:contains => 'AWESOME'})
+@numbers.each {|num| puts num.phone_number}
+
+# buy the first one (one http request)
+@account.incoming_phone_numbers.create(:phone_number => @numbers[0].phone_number)
+
+# update an existing phone number's voice url (one http request)
+@account.incoming_phone_numbers.get('PNdba508c5616a7f5e141789f44f022cc3').update({:voice_url => 'http://myapp.com/voice'})
+
+################ CONFERENCES ################
+
+# get a particular conference's participants object and stash it (should be zero http requests)
+@participants = @account.conferences.get('CFbbe46ff1274e283f7e3ac1df0072ab39').participants
+
+# list participants (http request here)
+@participants.list.each do {|p| puts p.sid}
+
+# update a conference participant
+@participants.get('CA386025c9bf5d6052a1d1ea42b4d16662').update({:muted => 'true'})
+# or an easier way
+@participants.get('CA386025c9bf5d6052a1d1ea42b4d16662').mute
+
+# and, since we're lazy loading, this would only incur one http request
+@account.conferences.get('CFbbe46ff1274e283f7e3ac1df0072ab39').participants.get('CA386025c9bf5d6052a1d1ea42b4d16662').update({:muted => 'true'})
View
50 lib/twilio-ruby.rb
@@ -0,0 +1,50 @@
+TWILIO_RUBY_ROOT = File.expand_path(File.dirname(__FILE__))
+
+require 'net/http'
+require 'net/https'
+require 'builder'
+require 'crack'
+require 'cgi'
+require 'openssl'
+require 'base64'
+
+
+require "#{TWILIO_RUBY_ROOT}/twilio-ruby/util"
+require "#{TWILIO_RUBY_ROOT}/twilio-ruby/util/request_validator"
+require "#{TWILIO_RUBY_ROOT}/twilio-ruby/rest/errors"
+require "#{TWILIO_RUBY_ROOT}/twilio-ruby/rest/utils"
+require "#{TWILIO_RUBY_ROOT}/twilio-ruby/rest/list_resource"
+require "#{TWILIO_RUBY_ROOT}/twilio-ruby/rest/instance_resource"
+require "#{TWILIO_RUBY_ROOT}/twilio-ruby/rest/sandbox/sandbox"
+require "#{TWILIO_RUBY_ROOT}/twilio-ruby/rest/accounts/account"
+require "#{TWILIO_RUBY_ROOT}/twilio-ruby/rest/accounts/accounts"
+require "#{TWILIO_RUBY_ROOT}/twilio-ruby/rest/calls/call"
+require "#{TWILIO_RUBY_ROOT}/twilio-ruby/rest/calls/calls"
+require "#{TWILIO_RUBY_ROOT}/twilio-ruby/rest/sms/sms"
+require "#{TWILIO_RUBY_ROOT}/twilio-ruby/rest/sms/message"
+require "#{TWILIO_RUBY_ROOT}/twilio-ruby/rest/sms/messages"
+require "#{TWILIO_RUBY_ROOT}/twilio-ruby/rest/sms/short_code"
+require "#{TWILIO_RUBY_ROOT}/twilio-ruby/rest/sms/short_codes"
+require "#{TWILIO_RUBY_ROOT}/twilio-ruby/rest/applications/application"
+require "#{TWILIO_RUBY_ROOT}/twilio-ruby/rest/applications/applications"
+require "#{TWILIO_RUBY_ROOT}/twilio-ruby/rest/outgoing_caller_ids/outgoing_caller_id"
+require "#{TWILIO_RUBY_ROOT}/twilio-ruby/rest/outgoing_caller_ids/outgoing_caller_ids"
+require "#{TWILIO_RUBY_ROOT}/twilio-ruby/rest/incoming_phone_numbers/incoming_phone_number"
+require "#{TWILIO_RUBY_ROOT}/twilio-ruby/rest/incoming_phone_numbers/incoming_phone_numbers"
+require "#{TWILIO_RUBY_ROOT}/twilio-ruby/rest/available_phone_numbers/available_phone_number"
+require "#{TWILIO_RUBY_ROOT}/twilio-ruby/rest/available_phone_numbers/available_phone_numbers"
+require "#{TWILIO_RUBY_ROOT}/twilio-ruby/rest/available_phone_numbers/country"
+require "#{TWILIO_RUBY_ROOT}/twilio-ruby/rest/available_phone_numbers/local"
+require "#{TWILIO_RUBY_ROOT}/twilio-ruby/rest/available_phone_numbers/toll_free"
+require "#{TWILIO_RUBY_ROOT}/twilio-ruby/rest/conferences/conference"
+require "#{TWILIO_RUBY_ROOT}/twilio-ruby/rest/conferences/conferences"
+require "#{TWILIO_RUBY_ROOT}/twilio-ruby/rest/conferences/participant"
+require "#{TWILIO_RUBY_ROOT}/twilio-ruby/rest/conferences/participants"
+require "#{TWILIO_RUBY_ROOT}/twilio-ruby/rest/recordings/recording"
+require "#{TWILIO_RUBY_ROOT}/twilio-ruby/rest/recordings/recordings"
+require "#{TWILIO_RUBY_ROOT}/twilio-ruby/rest/transcriptions/transcription"
+require "#{TWILIO_RUBY_ROOT}/twilio-ruby/rest/transcriptions/transcriptions"
+require "#{TWILIO_RUBY_ROOT}/twilio-ruby/rest/notifications/notification"
+require "#{TWILIO_RUBY_ROOT}/twilio-ruby/rest/notifications/notifications"
+require "#{TWILIO_RUBY_ROOT}/twilio-ruby/rest/client"
+require "#{TWILIO_RUBY_ROOT}/twilio-ruby/twiml/response"
View
12 lib/twilio-ruby/rest/accounts/account.rb
@@ -0,0 +1,12 @@
+module Twilio
+ module REST
+ class Account < InstanceResource
+ def initialize(uri, client, params={})
+ super uri, client, params
+ resource :sandbox, :available_phone_numbers, :incoming_phone_numbers,
+ :calls, :outgoing_caller_ids, :conferences, :sms, :recordings,
+ :transcriptions, :notifications, :applications
+ end
+ end
+ end
+end
View
5 lib/twilio-ruby/rest/accounts/accounts.rb
@@ -0,0 +1,5 @@
+module Twilio
+ module REST
+ class Accounts < ListResource; end
+ end
+end
View
5 lib/twilio-ruby/rest/applications/application.rb
@@ -0,0 +1,5 @@
+module Twilio
+ module REST
+ class Application < InstanceResource; end
+ end
+end
View
5 lib/twilio-ruby/rest/applications/applications.rb
@@ -0,0 +1,5 @@
+module Twilio
+ module REST
+ class Applications < ListResource; end
+ end
+end
View
5 lib/twilio-ruby/rest/available_phone_numbers/available_phone_number.rb
@@ -0,0 +1,5 @@
+module Twilio
+ module REST
+ class AvailablePhoneNumber < InstanceResource; end
+ end
+end
View
11 lib/twilio-ruby/rest/available_phone_numbers/available_phone_numbers.rb
@@ -0,0 +1,11 @@
+module Twilio
+ module REST
+ class AvailablePhoneNumbers < ListResource
+ def initialize(uri, client)
+ @resource_name = 'countries'
+ @instance_class = Twilio::REST.const_get 'Country'
+ @uri, @client = uri, client
+ end
+ end
+ end
+end
View
10 lib/twilio-ruby/rest/available_phone_numbers/country.rb
@@ -0,0 +1,10 @@
+module Twilio
+ module REST
+ class Country < InstanceResource
+ def initialize(uri, client, params={})
+ super uri, client, params
+ resource :local, :toll_free
+ end
+ end
+ end
+end
View
11 lib/twilio-ruby/rest/available_phone_numbers/local.rb
@@ -0,0 +1,11 @@
+module Twilio
+ module REST
+ class Local < ListResource
+ def initialize(uri, client)
+ @resource_name = 'available_phone_numbers'
+ @instance_class = Twilio::REST.const_get 'AvailablePhoneNumber'
+ @uri, @client = uri, client
+ end
+ end
+ end
+end
View
11 lib/twilio-ruby/rest/available_phone_numbers/toll_free.rb
@@ -0,0 +1,11 @@
+module Twilio
+ module REST
+ class TollFree < ListResource
+ def initialize(uri, client)
+ @resource_name = 'available_phone_numbers'
+ @instance_class = Twilio::REST.const_get 'AvailablePhoneNumber'
+ @uri, @client = uri, client
+ end
+ end
+ end
+end
View
22 lib/twilio-ruby/rest/calls/call.rb
@@ -0,0 +1,22 @@
+module Twilio
+ module REST
+ class Call < InstanceResource
+ def initialize(uri, client, params={})
+ super uri, client, params
+ resource :recordings, :transcriptions
+ end
+
+ def redirect_to(url)
+ update :url => url
+ end
+
+ def cancel
+ update :status => 'canceled'
+ end
+
+ def hangup
+ update :status => 'completed'
+ end
+ end
+ end
+end
View
10 lib/twilio-ruby/rest/calls/calls.rb
@@ -0,0 +1,10 @@
+module Twilio
+ module REST
+ class Calls < ListResource
+ def make(from, to, url)
+ create :from => from, :to => to, :url => url
+ end
+ end
+ end
+end
+
View
92 lib/twilio-ruby/rest/client.rb
@@ -0,0 +1,92 @@
+module Twilio
+ module REST
+ class Client
+
+ include Twilio::Util
+ include Twilio::REST::Utils
+
+ attr_reader :account_sid, :account, :accounts
+
+ def initialize(account_sid, auth_token, domain = 'api.twilio.com',
+ proxy_host = nil, proxy_port = nil)
+ @account_sid = account_sid
+ @auth_token = auth_token
+ set_up_connection_to domain, proxy_host, proxy_port
+ set_up_subresources
+ end
+
+ # Define some helper methods for sending HTTP requests
+ [:get, :put, :post, :delete].each do |method|
+ method_class = Net::HTTP.const_get method.to_s.capitalize
+ define_method method do |uri, *args|
+ uri += '.json'
+ params = twilify(args[0]); params = {} if params.empty?
+ uri += "?#{url_encode(params)}" if !params.empty? && method == :get
+ headers = {
+ 'Accept' => 'application/json',
+ 'User-Agent' => 'twilio-ruby/1.0.0'
+ }
+ request = method_class.new uri, headers
+ request.basic_auth @account_sid, @auth_token
+ request.form_data = params if [:post, :put].include? method
+ http_response = @connection.request request
+ object = Crack::JSON.parse http_response.body if http_response.body
+ if http_response.kind_of? Net::HTTPClientError
+ raise Twilio::REST::RequestError, object['message']
+ elsif http_response.kind_of? Net::HTTPServerError
+ raise Twilio::REST::ServerError, object['message']
+ end
+ object
+ end
+ end
+
+ # Mimic the old (deprecated) interface
+ def request(uri, method = 'POST', params = {})
+ raise ArgumentError, 'Invalid path parameter' if uri.empty?
+
+ uri = "/#{uri}" unless uri.start_with? '/'
+
+ case method
+ when 'GET'
+ uri += "?#{url_encode(params)}" if params
+ req = Net::HTTP::Get.new uri
+ when 'DELETE'
+ req = Net::HTTP::Delete.new uri
+ when 'PUT'
+ req = Net::HTTP::Put.new uri
+ req.form_data = params
+ when 'POST'
+ req = Net::HTTP::Post.new uri
+ req.form_data = params
+ else
+ raise NotImplementedError, "HTTP #{method} not implemented"
+ end
+
+ req.basic_auth @account_sid, @auth_token
+ @connection.request req
+ end
+
+ private
+
+ def set_up_connection_to(domain, proxy_host = nil, proxy_port = nil)
+ connection_class = Net::HTTP::Proxy proxy_host, proxy_port
+ @connection = connection_class.new domain, 443
+ @connection.use_ssl = true
+ # Don't check the server cert. Ideally this is configurable in case an
+ # app wants to verify that it's actually talking to the real Twilio.
+ # But cert validation is usually a nightmare, so we skip it for now.
+ @connection.verify_mode = OpenSSL::SSL::VERIFY_NONE
+ end
+
+ def set_up_subresources
+ accounts_uri = '/2010-04-01/Accounts'
+ account_uri = "#{accounts_uri}/#{@account_sid}"
+ # Set up a special handle to grab the account.
+ @account = Twilio::REST::Account.new account_uri, self
+ # Set up the accounts subresource.
+ @accounts = Twilio::REST::Accounts.new accounts_uri, self
+ end
+
+ end
+ end
+end
View
10 lib/twilio-ruby/rest/conferences/conference.rb
@@ -0,0 +1,10 @@
+module Twilio
+ module REST
+ class Conference < InstanceResource
+ def initialize(uri, client, params={})
+ super uri, client, params
+ resource :participants
+ end
+ end
+ end
+end
View
5 lib/twilio-ruby/rest/conferences/conferences.rb
@@ -0,0 +1,5 @@
+module Twilio
+ module REST
+ class Conferences < ListResource; end
+ end
+end
View
17 lib/twilio-ruby/rest/conferences/participant.rb
@@ -0,0 +1,17 @@
+module Twilio
+ module REST
+ class Participant < InstanceResource
+
+ def mute
+ update :muted => 'true'
+ end
+
+ def unmute
+ update :muted => 'false'
+ end
+
+ alias :kick :delete
+
+ end
+ end
+end
View
5 lib/twilio-ruby/rest/conferences/participants.rb
@@ -0,0 +1,5 @@
+module Twilio
+ module REST
+ class Participants < ListResource; end
+ end
+end
View
6 lib/twilio-ruby/rest/errors.rb
@@ -0,0 +1,6 @@
+module Twilio
+ module REST
+ class RequestError < StandardError; end
+ class ServerError < StandardError; end
+ end
+end
View
5 lib/twilio-ruby/rest/incoming_phone_numbers/incoming_phone_number.rb
@@ -0,0 +1,5 @@
+module Twilio
+ module REST
+ class IncomingPhoneNumber < InstanceResource; end
+ end
+end
View
9 lib/twilio-ruby/rest/incoming_phone_numbers/incoming_phone_numbers.rb
@@ -0,0 +1,9 @@
+module Twilio
+ module REST
+ class IncomingPhoneNumbers < ListResource
+ def buy(phone_number)
+ create :phone_number => phone_number
+ end
+ end
+ end
+end
View
56 lib/twilio-ruby/rest/instance_resource.rb
@@ -0,0 +1,56 @@
+module Twilio
+ module REST
+ class InstanceResource
+ include Utils
+
+ def initialize(uri, client, params = {})
+ @uri, @client = uri, client
+ set_up_properties_from params
+ end
+
+ def update(params = {})
+ raise "Can't update a resource without a REST Client" unless @client
+ response = @client.post @uri, params
+ set_up_properties_from response
+ self
+ end
+
+ def delete
+ raise "Can't delete a resource without a REST Client" unless @client
+ @client.delete @uri
+ end
+
+ def method_missing(method, *args)
+ super if @updated
+ response = @client.get @uri
+ set_up_properties_from response
+ self.send method, *args
+ end
+
+ protected
+
+ def set_up_properties_from(hash)
+ eigenclass = class << self; self; end
+ hash.each do |p,v|
+ property = detwilify p
+ unless ['uri', 'client', 'updated'].include? property
+ eigenclass.send :define_method, property.to_sym, &lambda {v}
+ end
+ end
+ @updated = !hash.keys.empty?
+ end
+
+ def resource(*resources)
+ resources.each do |r|
+ resource = twilify r
+ relative_uri = r == :sms ? 'SMS' : resource
+ uri = "#{@uri}/#{relative_uri}"
+ resource_class = Twilio::REST.const_get resource
+ instance_variable_set("@#{r}", resource_class.new(uri, @client))
+ end
+ self.class.instance_eval {attr_reader *resources}
+ end
+
+ end
+ end
+end
View
35 lib/twilio-ruby/rest/list_resource.rb
@@ -0,0 +1,35 @@
+module Twilio
+ module REST
+ class ListResource
+ include Utils
+
+ def initialize(uri, client)
+ @resource_name = self.class.name.split('::')[-1]
+ @instance_class = Twilio::REST.const_get @resource_name.chop
+ @uri, @client = uri, client
+ end
+
+ # Grab a list of this kind of resource and return it as an array.
+ def list(params = {})
+ raise "Can't get a resource list without a REST Client" unless @client
+ response = @client.get @uri, params
+ resources = response[detwilify(@resource_name)]
+ resources.map do |resource|
+ @instance_class.new "#{@uri}/#{resource['sid']}", @client, resource
+ end
+ end
+
+ # Return an empty instance resource object with the proper URI.
+ def get(sid)
+ @instance_class.new "#{@uri}/#{sid}", @client
+ end
+
+ # Return a newly created resource.
+ def create(params = {})
+ raise "Can't create a resource without a REST Client" unless @client
+ response = @client.post @uri, params
+ @instance_class.new "#{@uri}/#{response['sid']}", @client, response
+ end
+ end
+ end
+end
View
5 lib/twilio-ruby/rest/notifications/notification.rb
@@ -0,0 +1,5 @@
+module Twilio
+ module REST
+ class Notification < InstanceResource; end
+ end
+end
View
5 lib/twilio-ruby/rest/notifications/notifications.rb
@@ -0,0 +1,5 @@
+module Twilio
+ module REST
+ class Notifications < ListResource; end
+ end
+end
View
5 lib/twilio-ruby/rest/outgoing_caller_ids/outgoing_caller_id.rb
@@ -0,0 +1,5 @@
+module Twilio
+ module REST
+ class OutgoingCallerId < InstanceResource; end
+ end
+end
View
9 lib/twilio-ruby/rest/outgoing_caller_ids/outgoing_caller_ids.rb
@@ -0,0 +1,9 @@
+module Twilio
+ module REST
+ class OutgoingCallerIds < ListResource
+ def add(phone_number)
+ create :phone_number => phone_number
+ end
+ end
+ end
+end
View
12 lib/twilio-ruby/rest/recordings/recording.rb
@@ -0,0 +1,12 @@
+module Twilio
+ module REST
+ class Recording < InstanceResource
+
+ def initialize(uri, client, params={})
+ super uri, client, params
+ resource :transcriptions
+ end
+
+ end
+ end
+end
View
5 lib/twilio-ruby/rest/recordings/recordings.rb
@@ -0,0 +1,5 @@
+module Twilio
+ module REST
+ class Recordings < ListResource; end
+ end
+end
View
5 lib/twilio-ruby/rest/sandbox/sandbox.rb
@@ -0,0 +1,5 @@
+module Twilio
+ module REST
+ class Sandbox < InstanceResource; end
+ end
+end
View
5 lib/twilio-ruby/rest/sms/message.rb
@@ -0,0 +1,5 @@
+module Twilio
+ module REST
+ class Message < InstanceResource; end
+ end
+end
View
5 lib/twilio-ruby/rest/sms/messages.rb
@@ -0,0 +1,5 @@
+module Twilio
+ module REST
+ class Messages < ListResource; end
+ end
+end
View
5 lib/twilio-ruby/rest/sms/short_code.rb
@@ -0,0 +1,5 @@
+module Twilio
+ module REST
+ class ShortCode < InstanceResource; end
+ end
+end
View
5 lib/twilio-ruby/rest/sms/short_codes.rb
@@ -0,0 +1,5 @@
+module Twilio
+ module REST
+ class ShortCodes < ListResource; end
+ end
+end
View
10 lib/twilio-ruby/rest/sms/sms.rb
@@ -0,0 +1,10 @@
+module Twilio
+ module REST
+ class Sms < InstanceResource
+ def initialize(uri, client, params={})
+ super uri, client, params
+ resource :messages, :short_codes
+ end
+ end
+ end
+end
View
5 lib/twilio-ruby/rest/transcriptions/transcription.rb
@@ -0,0 +1,5 @@
+module Twilio
+ module REST
+ class Transcription < InstanceResource; end
+ end
+end
View
5 lib/twilio-ruby/rest/transcriptions/transcriptions.rb
@@ -0,0 +1,5 @@
+module Twilio
+ module REST
+ class Transcriptions < ListResource; end
+ end
+end
View
25 lib/twilio-ruby/rest/utils.rb
@@ -0,0 +1,25 @@
+module Twilio
+ module REST
+ module Utils
+
+ def twilify(something)
+ if something.is_a? Hash
+ Hash[*something.to_a.map {|a| [twilify(a[0]).to_sym, a[1]]}.flatten]
+ else
+ something.to_s.split('_').map do |s|
+ [s[0,1].capitalize, s[1..-1]].join
+ end.join
+ end
+ end
+
+ def detwilify(something)
+ if something.is_a? Hash
+ Hash[*something.to_a.map {|pair| [detwilify(pair[0]).to_sym, pair[1]]}.flatten]
+ else
+ something.to_s.gsub(/[A-Z][a-z]*/) {|s| "_#{s.downcase}"}.gsub(/^_/, '')
+ end
+ end
+
+ end
+ end
+end
View
15 lib/twilio-ruby/twiml/response.rb
@@ -0,0 +1,15 @@
+module Twilio
+ module TwiML
+ class Response
+
+ attr_reader :text
+
+ def initialize(&block)
+ xml = Builder::XmlMarkup.new
+ xml.instruct!
+ @text = xml.Response &block
+ end
+
+ end
+ end
+end
View
7 lib/twilio-ruby/util.rb
@@ -0,0 +1,7 @@
+module Twilio
+ module Util
+ def url_encode(hash)
+ hash.to_a.map {|p| p.map {|e| CGI.escape e.to_s}.join '='}.join '&'
+ end
+ end
+end
View
22 lib/twilio-ruby/util/request_validator.rb
@@ -0,0 +1,22 @@
+module Twilio
+ module Util
+ class RequestValidator
+
+ def initialize(auth_token)
+ @auth_token = auth_token
+ end
+
+ def validate(url, params, signature)
+ expected = build_signature_for url, params
+ expected == signature
+ end
+
+ def build_signature_for(url, params)
+ data = url + params.sort.to_s
+ digest = OpenSSL::Digest::Digest.new('sha1')
+ Base64.encode64(OpenSSL::HMAC.digest(digest, @auth_token, data)).strip
+ end
+
+ end
+ end
+end
View
210 test/twilio_spec.rb
@@ -0,0 +1,210 @@
+require 'rubygems'
+require 'twilio-ruby'
+require 'fakeweb'
+
+describe Twilio::REST::Client do
+ before :all do
+ FakeWeb.register_uri(:any, %r/http:\/\/api.twilio.com\//, :body => '{"message": "You tried to reach Twilio"}')
+ end
+
+ it 'should set up a new client instance with the given sid and token' do
+ twilio = Twilio::REST::Client.new('someSid', 'someToken')
+ twilio.account_sid.should == 'someSid'
+ twilio.instance_variable_get('@auth_token').should == 'someToken'
+ end
+
+ it 'should set up the proper default http ssl connection' do
+ twilio = Twilio::REST::Client.new('someSid', 'someToken')
+ twilio.instance_variable_get('@connection').address.should == 'api.twilio.com'
+ twilio.instance_variable_get('@connection').port.should == 443
+ twilio.instance_variable_get('@connection').use_ssl?.should == true
+ end
+
+ it 'should set up the proper http ssl connection when a different domain is given' do
+ twilio = Twilio::REST::Client.new('someSid', 'someToken', 'api.faketwilio.com')
+ twilio.instance_variable_get('@connection').address.should == 'api.faketwilio.com'
+ twilio.instance_variable_get('@connection').port.should == 443
+ twilio.instance_variable_get('@connection').use_ssl?.should == true
+ end
+
+ it 'should set up the proper http ssl connection when a proxy_host is given' do
+ twilio = Twilio::REST::Client.new('someSid', 'someToken', 'api.faketwilio.com', 'localhost')
+ twilio.instance_variable_get('@connection').proxy?.should == true
+ twilio.instance_variable_get('@connection').proxy_address.should == 'localhost'
+ twilio.instance_variable_get('@connection').proxy_port.should == 80
+ twilio.instance_variable_get('@connection').address.should == 'api.faketwilio.com'
+ twilio.instance_variable_get('@connection').port.should == 443
+ twilio.instance_variable_get('@connection').use_ssl?.should == true
+ end
+
+ it 'should set up the proper http ssl connection when a proxy_host and proxy_port are given' do
+ twilio = Twilio::REST::Client.new('someSid', 'someToken', 'api.faketwilio.com', 'localhost', 13128)
+ twilio.instance_variable_get('@connection').proxy?.should == true
+ twilio.instance_variable_get('@connection').proxy_address.should == 'localhost'
+ twilio.instance_variable_get('@connection').proxy_port.should == 13128
+ twilio.instance_variable_get('@connection').address.should == 'api.faketwilio.com'
+ twilio.instance_variable_get('@connection').port.should == 443
+ twilio.instance_variable_get('@connection').use_ssl?.should == true
+ end
+
+ it 'should set up an accounts resources object' do
+ twilio = Twilio::REST::Client.new('someSid', 'someToken')
+ twilio.respond_to?(:accounts).should == true
+ twilio.accounts.instance_variable_get('@uri').should == '/2010-04-01/Accounts'
+ end
+
+ it 'should set up an account object with the given sid' do
+ twilio = Twilio::REST::Client.new('someSid', 'someToken')
+ twilio.respond_to?(:account).should == true
+ twilio.account.instance_variable_get('@uri').should == '/2010-04-01/Accounts/someSid'
+ end
+
+ it 'should convert all parameter names to Twilio-style names' do
+ twilio = Twilio::REST::Client.new('someSid', 'someToken')
+ untwilified = {:sms_url => 'someUrl', 'voiceFallbackUrl' => 'anotherUrl',
+ 'Status_callback' => 'yetAnotherUrl'}
+ twilified = {:SmsUrl => 'someUrl', :VoiceFallbackUrl => 'anotherUrl',
+ :StatusCallback => 'yetAnotherUrl'}
+ twilio.instance_eval do
+ twilify(untwilified).should == twilified
+ end
+ end
+end
+
+describe Twilio::REST::InstanceResource do
+ it 'should set up an internal reference to the uri and client' do
+ resource = Twilio::REST::InstanceResource.new('some/uri', 'someClient')
+ resource.instance_variable_get('@uri').should == 'some/uri'
+ resource.instance_variable_get('@client').should == 'someClient'
+ end
+
+ it 'should set up object properties if passed' do
+ params = {'SomeKey' => 'someValue'}
+ resource = Twilio::REST::InstanceResource.new('uri', 'client', params)
+ resource.some_key.should == 'someValue'
+ end
+end
+
+describe Twilio::REST::Account do
+ it 'should set up an incoming phone numbers resources object' do
+ account = Twilio::REST::Account.new('someUri', 'someClient')
+ account.respond_to?(:incoming_phone_numbers).should == true
+ account.incoming_phone_numbers.instance_variable_get('@uri').should == 'someUri/IncomingPhoneNumbers'
+ end
+
+ it 'should set up an available phone numbers resources object' do
+ account = Twilio::REST::Account.new('someUri', 'someClient')
+ account.respond_to?(:available_phone_numbers).should == true
+ account.available_phone_numbers.instance_variable_get('@uri').should == 'someUri/AvailablePhoneNumbers'
+ end
+
+ it 'should set up an outgoing caller ids resources object' do
+ account = Twilio::REST::Account.new('someUri', 'someClient')
+ account.respond_to?(:outgoing_caller_ids).should == true
+ account.outgoing_caller_ids.instance_variable_get('@uri').should == 'someUri/OutgoingCallerIds'
+ end
+
+ it 'should set up a calls resources object' do
+ account = Twilio::REST::Account.new('someUri', 'someClient')
+ account.respond_to?(:calls).should == true
+ account.calls.instance_variable_get('@uri').should == 'someUri/Calls'
+ end
+
+ it 'should set up a conferences resources object' do
+ account = Twilio::REST::Account.new('someUri', 'someClient')
+ account.respond_to?(:conferences).should == true
+ account.conferences.instance_variable_get('@uri').should == 'someUri/Conferences'
+ end
+
+ it 'should set up a sms resource object' do
+ account = Twilio::REST::Account.new('someUri', 'someClient')
+ account.respond_to?(:sms).should == true
+ account.sms.instance_variable_get('@uri').should == 'someUri/SMS'
+ end
+
+ it 'should set up a recordings resources object' do
+ account = Twilio::REST::Account.new('someUri', 'someClient')
+ account.respond_to?(:recordings).should == true
+ account.recordings.instance_variable_get('@uri').should == 'someUri/Recordings'
+ end
+
+ it 'should set up a transcriptions resources object' do
+ account = Twilio::REST::Account.new('someUri', 'someClient')
+ account.respond_to?(:transcriptions).should == true
+ account.transcriptions.instance_variable_get('@uri').should == 'someUri/Transcriptions'
+ end
+
+ it 'should set up a notifications resources object' do
+ account = Twilio::REST::Account.new('someUri', 'someClient')
+ account.respond_to?(:notifications).should == true
+ account.notifications.instance_variable_get('@uri').should == 'someUri/Notifications'
+ end
+end
+
+describe Twilio::REST::Call do
+ it 'should set up a recordings resources object' do
+ call = Twilio::REST::Call.new('someUri', 'someClient')
+ call.respond_to?(:recordings).should == true
+ call.recordings.instance_variable_get('@uri').should == 'someUri/Recordings'
+ end
+
+ it 'should set up a recordings resources object' do
+ call = Twilio::REST::Call.new('someUri', 'someClient')
+ call.respond_to?(:transcriptions).should == true
+ call.transcriptions.instance_variable_get('@uri').should == 'someUri/Transcriptions'
+ end
+end
+
+describe Twilio::REST::Conference do
+ it 'should set up a participants resources object' do
+ call = Twilio::REST::Conference.new('someUri', 'someClient')
+ call.respond_to?(:participants).should == true
+ call.participants.instance_variable_get('@uri').should == 'someUri/Participants'
+ end
+end
+
+describe Twilio::REST::Recording do
+ it 'should set up a transcriptions resources object' do
+ call = Twilio::REST::Recording.new('someUri', 'someClient')
+ call.respond_to?(:transcriptions).should == true
+ call.transcriptions.instance_variable_get('@uri').should == 'someUri/Transcriptions'
+ end
+end
+
+describe Twilio::Util::RequestValidator do
+ it 'should properly validate a Twilio request based on its signature' do
+ token = '1c892n40nd03kdnc0112slzkl3091j20'
+ validator = Twilio::Util::RequestValidator.new token
+ url = 'http://www.postbin.org/1ed898x'
+ params = {
+ 'AccountSid' => 'AC9a9f9392lad99kla0sklakjs90j092j3',
+ 'ApiVersion' => '2010-04-01',
+ 'CallSid' => 'CAd800bb12c0426a7ea4230e492fef2a4f',
+ 'CallStatus' => 'ringing',
+ 'Called' => '+15306384866',
+ 'CalledCity' => 'OAKLAND',
+ 'CalledCountry' => 'US',
+ 'CalledState' => 'CA',
+ 'CalledZip' => '94612',
+ 'Caller' => '+15306666666',
+ 'CallerCity' => 'SOUTH LAKE TAHOE',
+ 'CallerCountry' => 'US',
+ 'CallerName' => 'CA Wireless Call',
+ 'CallerState' => 'CA',
+ 'CallerZip' => '89449',
+ 'Direction' => 'inbound',
+ 'From' => '+15306666666',
+ 'FromCity' => 'SOUTH LAKE TAHOE',
+ 'FromCountry' => 'US',
+ 'FromState' => 'CA',
+ 'FromZip' => '89449',
+ 'To' => '+15306384866',
+ 'ToCity' => 'OAKLAND',
+ 'ToCountry' => 'US',
+ 'ToState' => 'CA',
+ 'ToZip' => '94612'
+ }
+ signature = 'fF+xx6dTinOaCdZ0aIeNkHr/ZAA='
+ validator.validate(url, params, signature).should == true
+ end
+end
View
14 twilio-ruby.gemspec
@@ -0,0 +1,14 @@
+Gem::Specification.new do |s|
+ s.name = "twilio-ruby"
+ s.version = "1.0.0"
+ s.author = "Andrew Benton"
+ s.email = "andrew@twilio.com"
+ s.description = "A simple library for communicating with the Twilio REST API"
+ s.summary = "A simple library for communicating with the Twilio REST API"
+ s.homepage = "http://github.com/twilio/twilio-ruby"
+ s.platform = Gem::Platform::RUBY
+ s.files = Dir['lib/**/*.rb'] + Dir['test/**/*.rb'] + ['examples.rb', 'Rakefile', 'LICENSE', 'README.md', 'twilio-ruby.gemspec']
+ s.test_files = Dir['test/**/*.rb']
+ s.add_dependency("builder", ">= 2.1.2")
+ s.add_dependency("crack", ">= 0.1.8")
+end
Please sign in to comment.
Something went wrong with that request. Please try again.