├── lib ├── twilio │ ├── version.rb │ ├── account.rb │ ├── twilio_object.rb │ ├── notification.rb │ ├── recording.rb │ ├── sms.rb │ ├── outgoing_caller_id.rb │ ├── incoming_phone_number.rb │ ├── call.rb │ ├── conference.rb │ ├── available_phone_numbers.rb │ └── verb.rb └── twilio.rb ├── Gemfile ├── .gitignore ├── Rakefile ├── spec ├── fixtures │ ├── xml │ │ ├── outgoing_caller_id_new.xml │ │ ├── conference.xml │ │ ├── account_renamed.xml │ │ ├── recording.xml │ │ ├── outgoing_caller_id.xml │ │ ├── available_phone_numbers_toll_free_search.xml │ │ ├── call_new.xml │ │ ├── conferences.xml │ │ ├── transcription.xml │ │ ├── call_redirected.xml │ │ ├── incoming_phone_number.xml │ │ ├── conference_participant.xml │ │ ├── conference_participant_muted.xml │ │ ├── sms_new.xml │ │ ├── sms.xml │ │ ├── call.xml │ │ ├── available_phone_numbers_toll_free.xml │ │ ├── sms_new_with_callback.xml │ │ ├── available_phone_numbers_local_search.xml │ │ ├── recordings.xml │ │ ├── notification.xml │ │ ├── outgoing_caller_ids.xml │ │ ├── transcriptions.xml │ │ ├── incoming_phone_numbers.xml │ │ ├── conference_participants.xml │ │ ├── available_phone_numbers_local.xml │ │ ├── sms_messages.xml │ │ ├── calls.xml │ │ ├── notifications.xml │ │ └── account.xml │ └── yml │ │ └── verb_responses.yml ├── spec_helper.rb ├── twilio │ ├── live_connection_spec.rb │ ├── account_spec.rb │ ├── notification_spec.rb │ ├── sms_spec.rb │ ├── incoming_phone_number_spec.rb │ ├── outgoing_caller_id_spec.rb │ ├── recording_spec.rb │ ├── call_spec.rb │ ├── conference_spec.rb │ ├── available_phone_numbers_spec.rb │ └── verb_spec.rb └── support │ └── twilio_helpers.rb ├── LICENSE ├── twilio.gemspec ├── CHANGELOG.rdoc └── README.rdoc /lib/twilio/version.rb: -------------------------------------------------------------------------------- 1 | module Twilio 2 | VERSION = "3.1.1" 3 | end 4 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "http://rubygems.org" 2 | gemspec 3 | 4 | platforms :jruby do 5 | gem 'jruby-openssl' 6 | end 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.sw? 2 | .DS_Store 3 | bin 4 | coverage 5 | rdoc 6 | *.gem 7 | .bundle 8 | Gemfile.lock 9 | pkg/* 10 | .rvmrc 11 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler/setup' 2 | Bundler::GemHelper.install_tasks 3 | 4 | require 'rake' 5 | require 'rspec/core/rake_task' 6 | 7 | RSpec::Core::RakeTask.new 8 | 9 | task :default => :spec -------------------------------------------------------------------------------- /spec/fixtures/xml/outgoing_caller_id_new.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | mysid 4 | 4158675309 5 | 123456 6 | 7 | -------------------------------------------------------------------------------- /lib/twilio/account.rb: -------------------------------------------------------------------------------- 1 | module Twilio 2 | # The Account resource represents your Twilio Account. 3 | class Account < TwilioObject 4 | def get 5 | Twilio.get('') 6 | end 7 | 8 | def update_name(name) 9 | Twilio.put('', :body => {:FriendlyName => name}) 10 | end 11 | end 12 | end -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'bundler' 2 | Bundler.setup 3 | 4 | require 'twilio' 5 | 6 | require 'support/twilio_helpers' 7 | require 'webmock/rspec' 8 | 9 | RSpec.configure do |config| 10 | config.include TwilioHelpers 11 | config.before(:suite) do 12 | WebMock.disable_net_connect! 13 | end 14 | config.after(:suite) do 15 | WebMock.allow_net_connect! 16 | end 17 | end -------------------------------------------------------------------------------- /spec/fixtures/xml/conference.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | CF9f2ead1ae43cdabeab102fa30d938378 4 | mysid 5 | 1234 6 | 2 7 | Thu, 03 Sep 2009 23:37:53 -0700 8 | Fri, 04 Sep 2009 00:35:02 -0700 9 | 10 | -------------------------------------------------------------------------------- /spec/fixtures/xml/account_renamed.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | mysid 4 | Bubba 5 | 2 6 | Active 7 | Wed, 02 Apr 2008 17:33:38 -0700 8 | Wed, 02 Apr 2008 17:34:18 -0700 9 | mytoken 10 | 11 | -------------------------------------------------------------------------------- /spec/fixtures/xml/recording.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | RE41331862605f3d662488fdafda2e175f 4 | mysid 5 | CAcd420fcb3c4b86e360ea0cc27ebc8698 6 | 15 7 | Tue, 01 Apr 2008 01:07:15 -0400 8 | Tue, 01 Apr 2008 01:07:15 -0400 9 | 10 | -------------------------------------------------------------------------------- /spec/fixtures/xml/outgoing_caller_id.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | PNe536dfda7c6184afab78d980cb8cdf43 4 | mysid 5 | My Home Phone Number 6 | 4158675309 7 | Tue, 01 Apr 2008 11:26:32 -0700 8 | Tue, 01 Apr 2008 11:26:32 -0700 9 | 10 | -------------------------------------------------------------------------------- /spec/fixtures/xml/available_phone_numbers_toll_free_search.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | (866) 557-8676 5 | +18665578676 6 | US 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /spec/fixtures/xml/call_new.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | CA42ed11f93dc08b952027ffbc406d0868 4 | AC309475e5fede1b49e100272a8640f438 5 | 4155551212 6 | 4158675309 7 | PN01234567890123456789012345678900 8 | 0 9 | Thu, 03 Apr 2008 04:36:33 -0400 10 | 11 | 12 | 1 13 | 14 | -------------------------------------------------------------------------------- /spec/twilio/live_connection_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | # uncomment and add your own tests here 4 | =begin 5 | describe "testing with a live connection" do 6 | before(:all) do 7 | WebMock.allow_net_connect! 8 | @sid = 'abc123' 9 | @token = '123' 10 | Twilio.connect(@sid, @token) 11 | end 12 | 13 | after(:all) do 14 | WebMock.disable_net_connect! 15 | end 16 | 17 | it "gets real account" do 18 | Twilio::Account.get.should include("TwilioResponse") 19 | end 20 | end 21 | =end -------------------------------------------------------------------------------- /spec/fixtures/xml/conferences.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CFd0a50bbe038c437e87f6c82db8f37f21 5 | mysid 6 | 1234 7 | 2 8 | Thu, 03 Sep 2009 23:37:53 -0700 9 | Fri, 04 Sep 2009 00:35:02 -0700 10 | 11 | 12 | -------------------------------------------------------------------------------- /lib/twilio/twilio_object.rb: -------------------------------------------------------------------------------- 1 | module Twilio 2 | class TwilioObject #:nodoc: all 3 | def initialize 4 | end 5 | 6 | class << self 7 | def method_missing(method_id, *args) #:nodoc: 8 | o = self.new 9 | o.send(method_id, *args) 10 | rescue HTTParty::UnsupportedURIScheme 11 | raise "You must set Twilio.connect before calling #{self.inspect}##{method_id}" 12 | end 13 | end 14 | 15 | def connected? 16 | self.class.base_uri 17 | end 18 | end 19 | end -------------------------------------------------------------------------------- /spec/fixtures/xml/transcription.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | TRbdece5b75f2cd8f6ef38e0a10f5c4447 4 | 1235986685 5 | 1235957924 6 | mysid 7 | completed 8 | RE3870404da563592ef6a72136438a879c 9 | 9 10 | This is the body a transcribed recording 11 | -0.03000 12 | 13 | -------------------------------------------------------------------------------- /spec/fixtures/xml/call_redirected.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | CA42ed11f93dc08b952027ffbc406d0868 4 | 5 | AC309475e5fede1b49e100272a8640f438 6 | 4155551234 7 | 4158675309 8 | PN01234567890123456789012345678900 9 | 1 10 | Thu, 03 Apr 2008 04:36:33 -0400 11 | 12 | 13 | 1 14 | 15 | -------------------------------------------------------------------------------- /spec/fixtures/xml/incoming_phone_number.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | PNe536dfda7c6184afab78d980cb8cdf43 4 | mysid 5 | My Home Phone Number 6 | 4158675309 7 | http://mycompany.com/handleMainLineCall.asp 8 | GET 9 | Tue, 01 Apr 2008 11:26:32 -0700 10 | Tue, 01 Apr 2008 11:26:32 -0700 11 | 12 | -------------------------------------------------------------------------------- /spec/fixtures/xml/conference_participant.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | CF9f2ead1ae43cdabeab102fa30d938378 4 | mysid 5 | CA9ae8e040497c0598481c2031a154919e 6 | false 7 | true 8 | false 9 | Wed, 31 Dec 1969 16:33:29 -0800 10 | Wed, 31 Dec 1969 16:33:29 -0800 11 | 12 | -------------------------------------------------------------------------------- /spec/fixtures/xml/conference_participant_muted.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | CF9f2ead1ae43cdabeab102fa30d938378 4 | mysid 5 | CA9ae8e040497c0598481c2031a154919e 6 | true 7 | true 8 | false 9 | Wed, 31 Dec 1969 16:33:29 -0800 10 | Wed, 31 Dec 1969 16:33:29 -0800 11 | 12 | -------------------------------------------------------------------------------- /spec/twilio/account_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'Account' do 4 | before(:all) do 5 | Twilio.connect('mysid', 'mytoken') 6 | end 7 | 8 | it "gets an account" do 9 | response, url = stub_get(:account) 10 | 11 | Twilio::Account.get.should eql response 12 | WebMock.should have_requested(:get, twilio_url) 13 | end 14 | 15 | it "updates name" do 16 | response, url = stub_put(:account_renamed) 17 | 18 | Twilio::Account.update_name('Bubba').should eql response 19 | WebMock.should have_requested(:put, twilio_url).with(:body => {:FriendlyName => 'Bubba'}) 20 | end 21 | end -------------------------------------------------------------------------------- /spec/fixtures/xml/sms_new.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | SM872fb94e3b358913777cdb313f25b46f 4 | Sun, 04 Oct 2009 03:48:08 -0700 5 | Sun, 04 Oct 2009 03:48:10 -0700 6 | Sun, 04 Oct 2009 03:48:10 -0700 7 | AC5ea872f6da5a21de157d80997a64bd33 8 | 5558675309 9 | 4155551212 10 | Hi Jenny! Want to grab dinner? 11 | sent 12 | 2 13 | 14 | 15 | -------------------------------------------------------------------------------- /spec/fixtures/xml/sms.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | SM872fb94e3b358913777cdb313f25b46f 4 | Sun, 04 Oct 2009 03:48:08 -0700 5 | Sun, 04 Oct 2009 03:48:10 -0700 6 | Sun, 04 Oct 2009 03:48:10 -0700 7 | AC5ea872f6da5a21de157d80997a64bd33 8 | 4158675309 9 | 6505551212 10 | Hi there Jenny! Why didn't you call me back? 11 | sent 12 | 2 13 | 14 | 15 | -------------------------------------------------------------------------------- /lib/twilio/notification.rb: -------------------------------------------------------------------------------- 1 | module Twilio 2 | # A Notification represenents a log entry made by Twilio in the course of handling 3 | # your calls or using the REST API. 4 | # Example: 5 | # Twilio.connect('my_twilio_sid', 'my_auth_token') 6 | # Twilio::Notification.list 7 | class Notification < TwilioObject 8 | def list(opts = {}) 9 | Twilio.get('/Notifications', :query => (opts.empty? ? nil : opts)) 10 | end 11 | 12 | def get(notification_sid) 13 | Twilio.get("/Notifications/#{notification_sid}") 14 | end 15 | 16 | def delete(notification_sid) 17 | Twilio.delete("/Notifications/#{notification_sid}") 18 | end 19 | end 20 | end -------------------------------------------------------------------------------- /spec/fixtures/xml/call.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | CA42ed11f93dc08b952027ffbc406d0868 4 | Sat, 07 Feb 2009 13:15:19 -0800 5 | Sat, 07 Feb 2009 13:15:19 -0800 6 | 7 | mysid 8 | 4159633717 9 | 4156767925 10 | PN01234567890123456789012345678900 11 | 2 12 | Thu, 03 Apr 2008 04:36:33 -0400 13 | Thu, 03 Apr 2008 04:36:47 -0400 14 | 14 15 | 16 | 1 17 | 18 | -------------------------------------------------------------------------------- /spec/fixtures/xml/available_phone_numbers_toll_free.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | (866) 583-8815 5 | +18665838815 6 | US 7 | 8 | 9 | (866) 583-0795 10 | +18665830795 11 | US 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /spec/fixtures/xml/sms_new_with_callback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | SM872fb94e3b358913777cdb313f25b46f 4 | Sun, 04 Oct 2009 03:48:08 -0700 5 | Sun, 04 Oct 2009 03:48:10 -0700 6 | Sun, 04 Oct 2009 03:48:10 -0700 7 | AC5ea872f6da5a21de157d80997a64bd33 8 | 5558675309 9 | 4155551212 10 | Hi Jenny! Want to grab dinner? 11 | http://example.com/callback 12 | sent 13 | 2 14 | 15 | 16 | -------------------------------------------------------------------------------- /spec/fixtures/xml/available_phone_numbers_local_search.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | (510) 555-1214 5 | +15105551214 6 | 722 7 | OKLD0349T 8 | 37.806940 9 | -122.270360 10 | CA 11 | 94612 12 | US 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /lib/twilio/recording.rb: -------------------------------------------------------------------------------- 1 | module Twilio 2 | # Recordings are generated when you use the Record Verb. Those recordings are 3 | # hosted on Twilio's REST API for you to access. 4 | # Example: 5 | # Twilio.connect('my_twilio_sid', 'my_auth_token') 6 | # Twilio::Recording.list 7 | class Recording < TwilioObject 8 | def list(opts = {}) 9 | Twilio.get("/Recordings", :query => (opts.empty? ? nil : opts)) 10 | end 11 | 12 | def get(recording_sid) 13 | Twilio.get("/Recordings/#{recording_sid}") 14 | end 15 | 16 | def delete(recording_sid) 17 | Twilio.delete("/Recordings/#{recording_sid}") 18 | end 19 | 20 | def transcriptions(recording_sid, transcription_sid = nil) 21 | Twilio.get("/Recordings/#{recording_sid}/Transcriptions#{ '/' + transcription_sid if transcription_sid }") 22 | end 23 | end 24 | end -------------------------------------------------------------------------------- /spec/fixtures/xml/recordings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | RE41331862605f3d662488fdafda2e175f 5 | mysid 6 | CAcd420fcb3c4b86e360ea0cc27ebc8698 7 | 123 8 | Tue, 01 Apr 2008 01:07:15 -0400 9 | Tue, 01 Apr 2008 01:07:15 -0400 10 | 11 | 12 | RE50358f2565ad3c542e004161c3aecfd2 13 | mysid 14 | CAcd420fcb3c4b86e360ea0cc27ebc8698 15 | 45 16 | Tue, 01 Apr 2008 01:07:10 -0400 17 | Tue, 01 Apr 2008 01:07:10 -0400 18 | 19 | 20 | -------------------------------------------------------------------------------- /spec/fixtures/xml/notification.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | NO1fb7086ceb85caed2265f17d7bf7981c 4 | Sat, 07 Feb 2009 13:15:19 -0800 5 | Sat, 07 Feb 2009 13:15:19 -0800 6 | mysid 7 | CA42ed11f93dc08b952027ffbc406d0868 8 | 0 9 | 12345 10 | http://www.twilio.com/docs/errors/12345 11 | Unable to parse XML response 12 | Thu, 03 Apr 2008 04:36:32 -0400 13 | http://yourserver.com/handleCall.php 14 | POST 15 | From=4158675309&To=4155551212... 16 | Content-Length: 500 17 | <h1>Error parsing PHP script</h1> 18 | 19 | -------------------------------------------------------------------------------- /spec/fixtures/xml/outgoing_caller_ids.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | PNe536dfda7c6184afab78d980cb8cdf43 5 | mysid 6 | Bob Cell Phone 7 | 4158675309 8 | Tue, 01 Apr 2008 11:26:32 -0700 9 | Tue, 01 Apr 2008 11:26:32 -0700 10 | 11 | 12 | PNe536dfda7c6DDd455fed980cb83345FF 13 | mysid 14 | Company Main Line 15 | 4158675310 16 | Tue, 01 Apr 2008 11:26:32 -0700 17 | Tue, 01 Apr 2008 11:26:32 -0700 18 | 19 | 20 | -------------------------------------------------------------------------------- /lib/twilio/sms.rb: -------------------------------------------------------------------------------- 1 | module Twilio 2 | # An SMS message resource represents an Inbound or Outbound SMS message. When someone sends a text message from 3 | # or to your application, either via the REST API, or during a call via the verb, an SMS message resource is created. 4 | class Sms < TwilioObject 5 | # Example: 6 | # Twilio.connect('my_twilio_sid', 'my_auth_token') 7 | # Twilio::Sms.message(CALLER_ID, user_number, 'This is my simple SMS message', 'http://example.com/sms_callback') 8 | def message(from, to, body, callback_url=nil) 9 | callback = callback_url ? {:StatusCallback => callback_url} : {} 10 | Twilio.post("/SMS/Messages", :body => {:From => from, :To => to, :Body => body}.merge(callback)) 11 | end 12 | 13 | def list(opts = {}) 14 | Twilio.get("/SMS/Messages", :query => (opts.empty? ? nil : opts)) 15 | end 16 | 17 | def get(sms_message_sid) 18 | Twilio.get("/SMS/Messages/#{sms_message_sid}") 19 | end 20 | 21 | end 22 | end -------------------------------------------------------------------------------- /spec/twilio/notification_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe "Notification" do 4 | before(:all) do 5 | Twilio.connect('mysid', 'mytoken') 6 | @notification_sid = 'NO1fb7086ceb85caed2265f17d7bf7981c' 7 | end 8 | 9 | it "gets a list of notifications" do 10 | response, url = stub_get(:notifications, 'Notifications') 11 | 12 | Twilio::Notification.list.should eql response 13 | WebMock.should have_requested(:get, url) 14 | end 15 | 16 | it "gets a specific notification" do 17 | response, url = stub_get(:notification, "Notifications/#{@notification_sid}") 18 | 19 | Twilio::Notification.get(@notification_sid).should eql response 20 | WebMock.should have_requested(:get, url) 21 | end 22 | 23 | it "is deleted" do 24 | response, url = stub_delete(:notification, "Notifications/#{@notification_sid}") 25 | 26 | Twilio::Notification.delete(@notification_sid).should eql response 27 | WebMock.should have_requested(:delete, url) 28 | end 29 | end -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright Phil Misiowiec, Webficient LLC 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /twilio.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | $:.push File.expand_path("../lib", __FILE__) 3 | require "twilio/version" 4 | 5 | Gem::Specification.new do |s| 6 | s.name = "twilio" 7 | s.version = Twilio::VERSION 8 | s.platform = Gem::Platform::RUBY 9 | s.authors = ["Phil Misiowiec", "Jonathan Rudenberg", "Alex K Wolfe", "Kyle Daigle", "Jeff Wigal", "Yuri Gadow", "Vlad Moskovets"] 10 | s.email = ["github@webficient.com"] 11 | s.homepage = "" 12 | s.summary = %q{Twilio API Client} 13 | s.description = %q{Twilio API wrapper} 14 | 15 | s.rubyforge_project = "twilio" 16 | 17 | s.files = `git ls-files`.split("\n") 18 | s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") 19 | s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } 20 | s.require_paths = ["lib"] 21 | 22 | s.add_dependency "builder", ">= 2.1.2" 23 | s.add_dependency "httparty", ">= 0.8" 24 | 25 | { 26 | 'rake' => '~> 0.8.7', 27 | 'rspec' => '~> 2.12', 28 | 'webmock' => '~> 1.6.2' 29 | }.each { |l, v| s. add_development_dependency l, v } 30 | end 31 | -------------------------------------------------------------------------------- /spec/fixtures/xml/transcriptions.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | TR685e9a2bdf89b978491b1afada63f078 5 | 1235986685 6 | 1235957975 7 | mysid 8 | completed 9 | RE3870404da563592ef6a72136438a879c 10 | 9 11 | This is the body of one transcribed recording 12 | -0.25000 13 | 14 | 15 | TRbdece5b75f2cd8f6ef38e0a10f5c4447 16 | 1235986685 17 | 1235957924 18 | mysid 19 | completed 20 | RE3870404da563592ef6a72136438a879c 21 | 9 22 | This is the body of another transcribed recording 23 | -0.03000 24 | 25 | 26 | -------------------------------------------------------------------------------- /spec/fixtures/xml/incoming_phone_numbers.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | PNe536dfda7c6184afab78d980cb8cdf43 5 | mysid 6 | Company Main Line 7 | 4158675309 8 | http://mycompany.com/handleMainLineCall.asp 9 | GET 10 | Tue, 01 Apr 2008 11:26:32 -0700 11 | Tue, 01 Apr 2008 11:26:32 -0700 12 | 13 | 14 | PNe536dfda7c6DDd455fed980cb83345FF 15 | mysid 16 | Company Support Line 17 | 4158675310 18 | http://mycompany.com/handleSupportCall.php 19 | POST 20 | Tue, 01 Apr 2008 11:26:32 -0700 21 | Tue, 01 Apr 2008 11:26:32 -0700 22 | 23 | 24 | -------------------------------------------------------------------------------- /spec/fixtures/xml/conference_participants.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CF9f2ead1ae43cdabeab102fa30d938378 5 | mysid 6 | CA9ae8e040497c0598481c2031a154919e 7 | false 8 | true 9 | false 10 | Wed, 31 Dec 1969 16:33:29 -0800 11 | Wed, 31 Dec 1969 16:33:29 -0800 12 | 13 | 14 | CF9f2ead1ae43cdabeab102fa30d938378 15 | mysid 16 | CA3c3c002e06c4cdaa5367e814ade05c76 17 | false 18 | true 19 | false 20 | Wed, 31 Dec 1969 16:33:29 -0800 21 | Wed, 31 Dec 1969 16:33:29 -0800 22 | 23 | 24 | -------------------------------------------------------------------------------- /CHANGELOG.rdoc: -------------------------------------------------------------------------------- 1 | = Twilio Gem Changelog 2 | 3 | == 3.1.1 4 | 5 | * Loosens dependency on httparty gem to address a potential vulnerability 6 | 7 | == 3.1.0 8 | 9 | * Better handling of Builder version for greater compat 10 | * Reject verb 11 | * Dependency on recent, improved version of HTTParty 12 | 13 | == 3.0.1 14 | 15 | * Bumped down Builder version requirement for Rails compat 16 | 17 | == 3.0 18 | 19 | * SSL validation (thanks Kyle Humberto) 20 | * Available phone numbers search support (thanks Mark Turner) 21 | * Deprecated Twilio::Connection method has been removed, use Twilio.connect(...) 22 | * LocalPhoneNumber and TollFreeNumber have been removed to reflect API changes 23 | * New phone numbers are now provisioned via the IncomingPhoneNumber class 24 | * Several classes improved to avoid adding a stray "?" when no params were set 25 | * Compatibility with 1.9.2 (and tested on 1.8.7 MRI and REE) 26 | * Now uses Bundler, Rspec 2, and WebMock 27 | 28 | == 2.9 29 | 30 | * Compatibility with Twilio's 2010-04-01 API publish 31 | 32 | == 2.8 33 | 34 | * SMS callback URLs 35 | * Ability to delete phone numbers 36 | 37 | == 2.7 38 | 39 | * SMS functionality 40 | 41 | For earlier versions, see https://github.com/webficient/twilio/commits/master -------------------------------------------------------------------------------- /lib/twilio/outgoing_caller_id.rb: -------------------------------------------------------------------------------- 1 | module Twilio 2 | # An OutgoingCallerId resource represents an outgoing Caller ID that you have 3 | # registered with Twilio for use when making an outgoing call or using the Dial Verb. 4 | # Example: 5 | # Twilio.connect('my_twilio_sid', 'my_auth_token') 6 | # Twilio::OutgoingCallerId.list 7 | class OutgoingCallerId < TwilioObject 8 | def create(phone_number, friendly_name = phone_number, call_delay = 0, extension = nil) 9 | Twilio.post("/OutgoingCallerIds", :body => { 10 | :PhoneNumber => phone_number, 11 | :FriendlyName => friendly_name, 12 | :CallDelay => call_delay, 13 | :Extension => extension 14 | }) 15 | end 16 | 17 | def list(opts = {}) 18 | Twilio.get("/OutgoingCallerIds", :query => (opts.empty? ? nil : opts)) 19 | end 20 | 21 | def get(callerid_sid) 22 | Twilio.get("/OutgoingCallerIds/#{callerid_sid}") 23 | end 24 | 25 | def update_name(callerid_sid, name) 26 | Twilio.put("/OutgoingCallerIds/#{callerid_sid}", :body => {:FriendlyName => name}) 27 | end 28 | 29 | def delete(callerid_sid) 30 | Twilio.delete("/OutgoingCallerIds/#{callerid_sid}") 31 | end 32 | end 33 | end -------------------------------------------------------------------------------- /spec/fixtures/xml/available_phone_numbers_local.xml: -------------------------------------------------------------------------------- 1 | TwilioResponse> 2 | 3 | 4 | (510) 564-7903 5 | +15105647903 6 | 722 7 | OKLD TRNID 8 | 37.780000 9 | -122.380000 10 | CA 11 | 94703 12 | US 13 | 14 | 15 | (510) 488-4379 16 | +15104884379 17 | 722 18 | OKLD FRTVL 19 | 37.780000 20 | -122.380000 21 | CA 22 | 94602 23 | US 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /spec/twilio/sms_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe "SMS" do 4 | before(:all) do 5 | Twilio.connect('mysid', 'mytoken') 6 | @sms_sid = 'SM872fb94e3b358913777cdb313f25b46f' 7 | end 8 | 9 | it "gets a list of SMS messages" do 10 | response, url = stub_get(:sms_messages, 'SMS/Messages') 11 | 12 | Twilio::Sms.list.should eql response 13 | WebMock.should have_requested(:get, url) 14 | end 15 | 16 | it "gets a specific SMS message" do 17 | response, url = stub_get(:sms, "SMS/Messages/#{@sms_sid}") 18 | 19 | Twilio::Sms.get(@sms_sid).should eql response 20 | WebMock.should have_requested(:get, url) 21 | end 22 | 23 | it "is created" do 24 | response, url = stub_post(:sms_new, "SMS/Messages") 25 | 26 | Twilio::Sms.message('4155551212', '5558675309', 'Hi Jenny! Want to grab dinner?').should eql response 27 | WebMock.should have_requested(:post, url) 28 | end 29 | 30 | it "is created with a callback URL" do 31 | response, url = stub_post(:sms_new_with_callback, "SMS/Messages") 32 | 33 | Twilio::Sms.message('4155551212', '5558675309', 'Hi Jenny! Want to grab dinner?', 'http://example.com/callback').should eql response 34 | WebMock.should have_requested(:post, url) 35 | end 36 | end -------------------------------------------------------------------------------- /spec/fixtures/xml/sms_messages.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | SM872fb94e3b358913777cdb313f25b46f 6 | Sun, 04 Oct 2009 03:48:08 -0700 7 | Sun, 04 Oct 2009 03:48:10 -0700 8 | Sun, 04 Oct 2009 03:48:10 -0700 9 | AC5ea872f6da5a21de157d80997a64bd33 10 | 4158675309 11 | 6505551212 12 | Hi there Jenny! Want to grab dinner? 13 | sent 14 | 2 15 | 16 | 17 | SM47dfd824add482e1fee25ee3ce216113 18 | Sun, 04 Oct 2009 03:48:07 -0700 19 | Sun, 04 Oct 2009 03:48:07 -0700 20 | Sun, 04 Oct 2009 03:48:07 -0700 21 | AC5ea872f6da5a21de157d80997a64bd33 22 | 6505551212 23 | 4158675309 24 | The judge said you can't text me anymore. 25 | sent 26 | 1 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /lib/twilio/incoming_phone_number.rb: -------------------------------------------------------------------------------- 1 | module Twilio 2 | # An IncomingPhoneNumber resource represents a phone number given to you by 3 | # Twilio to receive incoming phone calls. 4 | # Example: 5 | # Twilio.connect('my_twilio_sid', 'my_auth_token') 6 | # Twilio::IncomingPhoneNumber.list 7 | class IncomingPhoneNumber < TwilioObject 8 | def list(opts = {}) 9 | Twilio.get("/IncomingPhoneNumbers", :query => (opts.empty? ? nil : opts)) 10 | end 11 | 12 | def get(incoming_sid) 13 | Twilio.get("/IncomingPhoneNumbers/#{incoming_sid}") 14 | end 15 | 16 | # Creates a phone number in Twilio. You must first find an existing number using 17 | # the AvailablePhoneNumber class before creating one here. 18 | # 19 | # Required: you must either set PhoneNumber or AreaCode as a required option 20 | # For additional options, see http://www.twilio.com/docs/api/rest/incoming-phone-numbers 21 | def create(opts) 22 | raise "You must set either :PhoneNumber or :AreaCode" if !opts.include?(:AreaCode) && !opts.include?(:PhoneNumber) 23 | Twilio.post("/IncomingPhoneNumbers", :body => opts) 24 | end 25 | 26 | def update(incoming_sid, opts) 27 | Twilio.post("/IncomingPhoneNumbers/#{incoming_sid}", :body => opts) 28 | end 29 | 30 | def delete(incoming_sid) 31 | Twilio.delete("/IncomingPhoneNumbers/#{incoming_sid}") 32 | end 33 | end 34 | end -------------------------------------------------------------------------------- /spec/fixtures/xml/calls.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CA42ed11f93dc08b952027ffbc406d0868 5 | Sat, 07 Feb 2009 13:15:19 -0800 6 | Sat, 07 Feb 2009 13:15:19 -0800 7 | 8 | mysid 9 | 4159633717 10 | 4156767925 11 | PN01234567890123456789012345678900 12 | 2 13 | Thu, 03 Apr 2008 04:36:33 -0400 14 | Thu, 03 Apr 2008 04:36:47 -0400 15 | 14 16 | 17 | 1 18 | 19 | 20 | CA751e8fa0a0105cf26a0d7a9775fb4bfb 21 | Sat, 07 Feb 2009 13:15:19 -0800 22 | Sat, 07 Feb 2009 13:15:19 -0800 23 | 24 | mysid 25 | 2064287985 26 | 4156767925 27 | PNd59c2ba27ef48264773edb90476d1674 28 | 2 29 | Thu, 03 Apr 2008 01:37:05 -0400 30 | Thu, 03 Apr 2008 01:37:40 -0400 31 | 35 32 | 33 | 1 34 | 35 | 36 | -------------------------------------------------------------------------------- /lib/twilio/call.rb: -------------------------------------------------------------------------------- 1 | module Twilio 2 | # A Call represenents a connection between a telephone and Twilio. This may be 3 | # inbound, when a person calls your application, or outbound when your application 4 | # initiates the call, either via the REST API, or during a call via the Dial Verb. 5 | class Call < TwilioObject 6 | # Example: 7 | # Twilio.connect('my_twilio_sid', 'my_auth_token') 8 | # Twilio::Call.make(CALLER_ID, user_number, 'http://myapp.com/twilio_response_handler') 9 | def make(from, to, url, opts = {}) 10 | Twilio.post("/Calls", :body => {:From => from, :To => to, :Url => url}.merge(opts)) 11 | end 12 | 13 | def list(opts = {}) 14 | Twilio.get("/Calls", :query => (opts.empty? ? nil : opts)) 15 | end 16 | 17 | def get(call_sid) 18 | Twilio.get("/Calls/#{call_sid}") 19 | end 20 | 21 | def redirect(call_sid, new_url, url_action = 'POST') 22 | Twilio.post("/Calls/#{call_sid}", :body => {:CurrentUrl => new_url, :CurrentMethod => url_action}) 23 | end 24 | 25 | def segments(call_sid, call_segment_sid = nil) 26 | Twilio.get("/Calls/#{call_sid}/Segments#{ '/' + call_segment_sid if call_segment_sid }") 27 | end 28 | 29 | def recordings(call_sid) 30 | Twilio.get("/Calls/#{call_sid}/Recordings") 31 | end 32 | 33 | def notifications(call_sid) 34 | Twilio.get("/Calls/#{call_sid}/Notifications") 35 | end 36 | end 37 | end -------------------------------------------------------------------------------- /lib/twilio/conference.rb: -------------------------------------------------------------------------------- 1 | module Twilio 2 | # The Conference REST resource allows you to query and manage the state of conferences. 3 | # When a caller joins a conference via the Dial verb and Conference noun, 4 | # a Conference Instance Resource is created to represent the conference room 5 | # and a Participant Instance Resource is created to represent the caller who joined. 6 | class Conference < TwilioObject 7 | def list(opts = {}) 8 | Twilio.get("/Conferences", :query => (opts.empty? ? nil : opts)) 9 | end 10 | 11 | def get(conference_sid) 12 | Twilio.get("/Conferences/#{conference_sid}") 13 | end 14 | 15 | def participants(conference_sid, opts = {}) 16 | Twilio.get("/Conferences/#{conference_sid}/Participants", :query => (opts.empty? ? nil : opts)) 17 | end 18 | 19 | def participant(conference_sid, call_sid) 20 | Twilio.get("/Conferences/#{conference_sid}/Participants/#{call_sid}") 21 | end 22 | 23 | def mute_participant(conference_sid, call_sid) 24 | Twilio.post("/Conferences/#{conference_sid}/Participants/#{call_sid}", :body => {:Muted => true}) 25 | end 26 | 27 | def unmute_participant(conference_sid, call_sid) 28 | Twilio.post("/Conferences/#{conference_sid}/Participants/#{call_sid}", :body => {:Muted => false}) 29 | end 30 | 31 | def kick_participant(conference_sid, call_sid) 32 | Twilio.delete("/Conferences/#{conference_sid}/Participants/#{call_sid}") 33 | end 34 | end 35 | end -------------------------------------------------------------------------------- /spec/support/twilio_helpers.rb: -------------------------------------------------------------------------------- 1 | module TwilioHelpers #:nodoc: 2 | 3 | def stub_http_request(http_method, fixture_name, *opts) 4 | if opts 5 | request_options = opts.pop if opts.last.is_a?(Hash) 6 | resource = opts.pop 7 | end 8 | 9 | fake_response = fixture(fixture_name) 10 | url = twilio_url(resource) 11 | 12 | if request_options 13 | stub_request(http_method, url).with(request_options).to_return(:body => fake_response) 14 | else 15 | stub_request(http_method, url).to_return(:body => fake_response) 16 | end 17 | 18 | return fake_response, url 19 | end 20 | 21 | def stub_get(fixture, *opts) 22 | stub_http_request(:get, fixture, *opts) 23 | end 24 | 25 | def stub_post(fixture, *opts) 26 | stub_http_request(:post, fixture, *opts) 27 | end 28 | 29 | def stub_put(fixture, *opts) 30 | stub_http_request(:put, fixture, *opts) 31 | end 32 | 33 | def stub_delete(fixture, *opts) 34 | stub_http_request(:delete, fixture, *opts) 35 | end 36 | 37 | def verb_response(verb) 38 | path = File.join(File.dirname(__FILE__), "../fixtures/yml/verb_responses.yml") 39 | YAML.load_file(path)[verb.to_s]['response'] 40 | end 41 | 42 | private 43 | 44 | def twilio_url(url=nil) 45 | "https://mysid:mytoken@api.twilio.com:443/2010-04-01/Accounts/mysid#{'/' + url if url}" 46 | end 47 | 48 | def fixture(filename) 49 | path = File.join(File.dirname(__FILE__), "../fixtures/xml/#{filename}.xml") 50 | File.read path 51 | end 52 | end -------------------------------------------------------------------------------- /spec/twilio/incoming_phone_number_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe "Incoming Phone Number" do 4 | before(:all) do 5 | Twilio.connect('mysid', 'mytoken') 6 | @incoming_sid = 'PNe536dfda7c6184afab78d980cb8cdf43' 7 | end 8 | 9 | it "gets a specific phone number" do 10 | response, url = stub_get(:incoming_phone_number, "IncomingPhoneNumbers/#{@incoming_sid}") 11 | 12 | Twilio::IncomingPhoneNumber.get(@incoming_sid).should eql response 13 | WebMock.should have_requested(:get, url) 14 | end 15 | 16 | it "gets a list of phone numbers" do 17 | response, url = stub_get(:incoming_phone_numbers, 'IncomingPhoneNumbers') 18 | 19 | Twilio::IncomingPhoneNumber.list.should eql response 20 | WebMock.should have_requested(:get, url) 21 | end 22 | 23 | context "creating" do 24 | it "is created" do 25 | response, url = stub_post(:incoming_phone_number, 'IncomingPhoneNumbers') 26 | 27 | Twilio::IncomingPhoneNumber.create(:PhoneNumber => '8055551212').should eql response 28 | WebMock.should have_requested(:post, url) 29 | end 30 | 31 | it "raises an exception if PhoneNumber or AreaCode are not set" do 32 | expect { Twilio::IncomingPhoneNumber.create(:FriendlyName => 'Booyah') }.to raise_exception 33 | end 34 | end 35 | 36 | it "is deleted" do 37 | response, url = stub_delete(:incoming_phone_number, "IncomingPhoneNumbers/#{@incoming_sid}") 38 | 39 | Twilio::IncomingPhoneNumber.delete(@incoming_sid).should eql response 40 | WebMock.should have_requested(:delete, url) 41 | end 42 | end -------------------------------------------------------------------------------- /spec/twilio/outgoing_caller_id_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe "Outgoing Caller ID" do 4 | before(:all) do 5 | Twilio.connect('mysid', 'mytoken') 6 | @callerid_sid = 'PNe536dfda7c6184afab78d980cb8cdf43' 7 | end 8 | 9 | it "gets a list of caller id's" do 10 | response, url = stub_get(:outgoing_caller_ids, 'OutgoingCallerIds') 11 | 12 | Twilio::OutgoingCallerId.list.should eql response 13 | WebMock.should have_requested(:get, url) 14 | end 15 | 16 | it "gets a specific caller id" do 17 | response, url = stub_get(:outgoing_caller_id, "OutgoingCallerIds/#{@callerid_sid}") 18 | 19 | Twilio::OutgoingCallerId.get(@callerid_sid).should eql response 20 | WebMock.should have_requested(:get, url) 21 | end 22 | 23 | it "is created" do 24 | response, url = stub_post(:outgoing_caller_id_new, 'OutgoingCallerIds') 25 | 26 | Twilio::OutgoingCallerId.create('4158675309', 'My Home Phone').should eql response 27 | WebMock.should have_requested(:post, url) 28 | end 29 | 30 | it "is deleted" do 31 | response, url = stub_delete(:outgoing_caller_id, "OutgoingCallerIds/#{@callerid_sid}") 32 | 33 | Twilio::OutgoingCallerId.delete(@callerid_sid).should eql response 34 | WebMock.should have_requested(:delete, url) 35 | end 36 | 37 | it "updates name" do 38 | response, url = stub_put(:outgoing_caller_id, "OutgoingCallerIds/#{@callerid_sid}") 39 | 40 | Twilio::OutgoingCallerId.update_name(@callerid_sid, 'My office line').should eql response 41 | WebMock.should have_requested(:put, url) 42 | end 43 | end -------------------------------------------------------------------------------- /spec/fixtures/xml/notifications.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | NO1fb7086ceb85caed2265f17d7bf7981c 5 | Sat, 07 Feb 2009 13:15:19 -0800 6 | Sat, 07 Feb 2009 13:15:19 -0800 7 | mysid 8 | CA42ed11f93dc08b952027ffbc406d0868 9 | 0 10 | 12345 11 | http://www.twilio.com/docs/errors/12345 12 | http://yourserver.com/handleCall.php 13 | POST 14 | Unable to parse XML response 15 | Thu, 03 Apr 2008 04:36:32 -0400 16 | 17 | 18 | NOe936fdac57d238e56fd346b89820d342 19 | Sat, 07 Feb 2009 13:15:19 -0800 20 | Sat, 07 Feb 2009 13:15:19 -0800 21 | mysid 22 | 23 | 1 24 | 67890 25 | http://www.twilio.com/docs/errors/67890 26 | http://api.twilio.com/2008-08-01/Accounts/AC309475e5fede1b49e100272a8640f438/Calls 27 | POST 28 | Unknown parameter received by the REST API: foo=bar 29 | Thu, 03 Apr 2008 04:36:32 -0400 30 | 31 | 32 | -------------------------------------------------------------------------------- /spec/fixtures/xml/account.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | mysid 4 | Do you like my friendly name? 5 | active 6 | Wed, 04 Aug 2010 21:37:41 +0000 7 | Fri, 06 Aug 2010 01:15:02 +0000 8 | mytoken 9 | /2010-04-01/Accounts/ACba8bc05eacf94afdae398e642c9cc32d 10 | 11 | /2010-04-01/Accounts/ACba8bc05eacf94afdae398e642c9cc32d/AvailablePhoneNumbers 12 | /2010-04-01/Accounts/ACba8bc05eacf94afdae398e642c9cc32d/Calls 13 | /2010-04-01/Accounts/ACba8bc05eacf94afdae398e642c9cc32d/Conferences 14 | /2010-04-01/Accounts/ACba8bc05eacf94afdae398e642c9cc32d/IncomingPhoneNumbers 15 | /2010-04-01/Accounts/ACba8bc05eacf94afdae398e642c9cc32d/Notifications 16 | /2010-04-01/Accounts/ACba8bc05eacf94afdae398e642c9cc32d/OutgoingCallerIds 17 | /2010-04-01/Accounts/ACba8bc05eacf94afdae398e642c9cc32d/Recordings 18 | /2010-04-01/Accounts/ACba8bc05eacf94afdae398e642c9cc32d/Sandbox 19 | /2010-04-01/Accounts/ACba8bc05eacf94afdae398e642c9cc32d/SMS/Messages 20 | /2010-04-01/Accounts/ACba8bc05eacf94afdae398e642c9cc32d/Transcriptions 21 | 22 | 23 | -------------------------------------------------------------------------------- /spec/twilio/recording_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe "Recording" do 4 | before(:all) do 5 | Twilio.connect('mysid', 'mytoken') 6 | @recording_sid = 'RE41331862605f3d662488fdafda2e175f' 7 | @transcription_sid = 'TRbdece5b75f2cd8f6ef38e0a10f5c4447' 8 | end 9 | 10 | it "gets a list of recordings" do 11 | response, url = stub_get(:recordings, 'Recordings') 12 | 13 | Twilio::Recording.list.should eql response 14 | WebMock.should have_requested(:get, url) 15 | end 16 | 17 | it "gets a specific recording" do 18 | response, url = stub_get(:recording, "Recordings/#{@recording_sid}") 19 | 20 | Twilio::Recording.get(@recording_sid).should eql response 21 | WebMock.should have_requested(:get, url) 22 | end 23 | 24 | it "is deleted" do 25 | response, url = stub_delete(:recording, "Recordings/#{@recording_sid}") 26 | 27 | Twilio::Recording.delete(@recording_sid).should eql response 28 | WebMock.should have_requested(:delete, url) 29 | end 30 | 31 | it "gets a list of transcriptions" do 32 | response, url = stub_get(:transcriptions, "Recordings/#{@recording_sid}/Transcriptions") 33 | 34 | Twilio::Recording.transcriptions(@recording_sid).should eql response 35 | WebMock.should have_requested(:get, url) 36 | end 37 | 38 | it "gets a specific transcription" do 39 | response, url = stub_get(:transcriptions, "Recordings/#{@recording_sid}/Transcriptions/#{@transcription_sid}") 40 | 41 | Twilio::Recording.transcriptions(@recording_sid, @transcription_sid).should eql response 42 | WebMock.should have_requested(:get, url) 43 | end 44 | end -------------------------------------------------------------------------------- /README.rdoc: -------------------------------------------------------------------------------- 1 | = Twilio Gem 2 | 3 | The Twilio gem provides two major pieces of functionality: (1) a Ruby wrapper for the Twilio REST API and (2) response handlers based on the Twilio Markup XML (TwiML). 4 | 5 | See http://www.twilio.com/docs/index for Twilio's API documentation. 6 | 7 | == Calling the Twilio REST API 8 | 9 | First set your credentials by calling the connect method: 10 | 11 | Twilio.connect('my_twilio_sid', 'my_auth_token') 12 | 13 | Now call any of the Twilio classes: 14 | 15 | Twilio::Call.make('1234567890', '9876543210', 'http://mysite.com/connected_call') 16 | Twilio::Recording.list 17 | 18 | == Responding to Twilio 19 | 20 | When Twilio calls your application URL, your response must use the Twilio Markup XML (http://www.twilio.com/docs/api_reference/TwiML/). The Twilio gem makes this very easy 21 | by providing a Twilio Verb class. 22 | 23 | For example, in a Ruby on Rails application, you could do the following inside a controller class: 24 | 25 | Twilio::Verb.dial '415-123-4567' 26 | 27 | and you can nest multiple verbs inside a block: 28 | 29 | verb = Twilio::Verb.new { |v| 30 | v.say "The time is #{Time.now}" 31 | v.hangup 32 | } 33 | verb.response 34 | 35 | == Installation 36 | 37 | gem install twilio 38 | 39 | == Contributing 40 | 41 | 1. Run 'bundle' from the command line to install dependencies 42 | 2. Write test(s) for your patch 43 | 3. Submit a pull request 44 | 45 | Note: don't require 'rubygems' in any file (http://www.rubyinside.com/why-using-require-rubygems-is-wrong-1478.html) 46 | 47 | == Testing 48 | 49 | * Currently using RSpec 50 | * Tested with REE 1.8.7, MRI 1.9.2, MRI 1.9.3, and JRuby 1.6.5 51 | 52 | == Copyright 53 | 54 | Copyright Phil Misiowiec, Webficient LLC. See LICENSE for details. 55 | 56 | == Contributors 57 | 58 | * Kyle Daigle 59 | * Yuri Gadow 60 | * Kyle Humberto 61 | * Vlad Moskovets 62 | * Jonathan Rudenberg 63 | * Mark Turner 64 | * Jeff Wigal 65 | * Alex K Wolfe -------------------------------------------------------------------------------- /spec/twilio/call_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe "Call" do 4 | before(:all) do 5 | Twilio.connect('mysid', 'mytoken') 6 | @call_sid = 'CA42ed11f93dc08b952027ffbc406d0868' 7 | end 8 | 9 | it "can be made" do 10 | response, url = stub_post(:call_new, 'Calls') 11 | 12 | Twilio::Call.make('4158675309', '4155551212', 'http://localhost:3000/call_handler').should eql response 13 | WebMock.should have_requested(:post, url) 14 | end 15 | 16 | it "can be redirected" do 17 | response, url = stub_post(:call_redirected, "Calls/#{@call_sid}") 18 | 19 | Twilio::Call.redirect(@call_sid, 'http://localhost:3000/redirect_handler').should eql response 20 | WebMock.should have_requested(:post, url) 21 | end 22 | 23 | it "gets a list of calls" do 24 | response, url = stub_get(:calls, 'Calls') 25 | 26 | Twilio::Call.list.should eql response 27 | WebMock.should have_requested(:get, url) 28 | end 29 | 30 | it "gets a specific call" do 31 | response, url = stub_get(:calls, "Calls/#{@call_sid}") 32 | 33 | Twilio::Call.get(@call_sid).should eql response 34 | WebMock.should have_requested(:get, url) 35 | end 36 | 37 | it "gets a list of call segments" do 38 | response, url = stub_get(:calls, "Calls/#{@call_sid}/Segments") 39 | 40 | Twilio::Call.segments(@call_sid).should eql response 41 | WebMock.should have_requested(:get, url) 42 | end 43 | 44 | it "gets a specific call segment" do 45 | response, url = stub_get(:calls, "Calls/#{@call_sid}/Segments/abc123") 46 | 47 | Twilio::Call.segments(@call_sid, 'abc123').should eql response 48 | WebMock.should have_requested(:get, url) 49 | end 50 | 51 | it "gets a list of call recordings" do 52 | response, url = stub_get(:recordings, "Calls/#{@call_sid}/Recordings") 53 | 54 | Twilio::Call.recordings(@call_sid).should eql response 55 | WebMock.should have_requested(:get, url) 56 | end 57 | 58 | it "gets a list of call notifications" do 59 | response, url = stub_get(:notifications, "Calls/#{@call_sid}/Notifications") 60 | 61 | Twilio::Call.notifications(@call_sid).should eql response 62 | WebMock.should have_requested(:get, url) 63 | end 64 | end -------------------------------------------------------------------------------- /lib/twilio.rb: -------------------------------------------------------------------------------- 1 | #-- 2 | # Copyright (c) 2009 Phil Misiowiec, phil@webficient.com 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining 5 | # a copy of this software and associated documentation files (the 6 | # "Software"), to deal in the Software without restriction, including 7 | # without limitation the rights to use, copy, modify, merge, publish, 8 | # distribute, sublicense, and/or sell copies of the Software, and to 9 | # permit persons to whom the Software is furnished to do so, subject to 10 | # the following conditions: 11 | # 12 | # The above copyright notice and this permission notice shall be 13 | # included in all copies or substantial portions of the Software. 14 | # 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | #++ 23 | 24 | require 'httparty' 25 | 26 | require 'twilio/twilio_object' 27 | 28 | require 'twilio/account' 29 | require 'twilio/available_phone_numbers' 30 | require 'twilio/call' 31 | require 'twilio/conference' 32 | require 'twilio/incoming_phone_number' 33 | require 'twilio/notification' 34 | require 'twilio/outgoing_caller_id' 35 | require 'twilio/recording' 36 | require 'twilio/sms' 37 | require 'twilio/verb' 38 | 39 | module Twilio 40 | include HTTParty 41 | TWILIO_URL = "https://api.twilio.com/2010-04-01/Accounts" 42 | SSL_CA_PATH = "/etc/ssl/certs" 43 | 44 | # The connect method caches your Twilio account id and authentication token 45 | # Example: 46 | # Twilio.connect('AC309475e5fede1b49e100272a8640f438', '3a2630a909aadbf60266234756fb15a0') 47 | def self.connect(account_sid, auth_token) 48 | self.base_uri "#{TWILIO_URL}/#{account_sid}" 49 | self.basic_auth account_sid, auth_token 50 | self.default_options[:ssl_ca_path] ||= SSL_CA_PATH unless self.default_options[:ssl_ca_file] 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /lib/twilio/available_phone_numbers.rb: -------------------------------------------------------------------------------- 1 | module Twilio 2 | # An AvailablePhoneNumbers resources represents the available phone numbers 3 | # that Twilio can provide you based on your search criteria. The valid 4 | # query terms are outlined in the search methods. 5 | # Example: 6 | # Twilio.connect('my_twilio_sid', 'my_auth_token') 7 | # Twilio.AvailablePhoneNumbers.search_local(:area_code => 541) 8 | class AvailablePhoneNumbers < TwilioObject 9 | 10 | # The Search method handles the searching of both local and toll-free 11 | # numbers. 12 | def search(opts={}) 13 | iso_country_code = opts[:iso_country_code] || 'US' 14 | resource = opts.delete(:resource) 15 | 16 | params = { 17 | :AreaCode => opts[:area_code], 18 | :InPostalCode => opts[:postal_code], 19 | :InRegion => opts[:in_region], 20 | :Contains => opts[:contains], 21 | :NearLatLong => opts[:near_lat_long], 22 | :NearNumber => opts[:near_number], 23 | :InLata => opts[:in_lata], 24 | :InRateCenter => opts[:in_rate_center], 25 | :Distance => opts[:distance], 26 | :Page => opts[:page], 27 | :PageSize => opts[:page_size] 28 | }.reject {|k,v| v == nil} unless opts.empty? 29 | 30 | Twilio.get("/AvailablePhoneNumbers/#{iso_country_code}/#{resource}", :query => params) 31 | end 32 | 33 | # The search_local method searches for numbers in local areas (i.e. state, zip, etc..) 34 | # Search Options: 35 | # :area_code 36 | # :postal_code 37 | # :in_region 38 | # :contains 39 | # :near_lat_long 40 | # :near_number 41 | # :in_lata 42 | # :in_rate_center 43 | # :distance 44 | # :page 45 | # :page_size 46 | def search_local(opts ={}) 47 | opts = {:resource => 'Local'}.merge(opts) 48 | search(opts) 49 | end 50 | 51 | # The search_toll_free method searches for available toll-free numbers 52 | # Search Options 53 | # :area_code 54 | # :contains 55 | # :page 56 | # :page_size 57 | def search_toll_free(opts ={}) 58 | opts = {:resource => 'TollFree'}.merge(opts) 59 | search(opts) 60 | end 61 | end 62 | end 63 | 64 | -------------------------------------------------------------------------------- /spec/twilio/conference_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe "Conference" do 4 | before(:all) do 5 | Twilio.connect('mysid', 'mytoken') 6 | @conference_sid = 'CF9f2ead1ae43cdabeab102fa30d938378' 7 | @call_sid = 'CA9ae8e040497c0598481c2031a154919e' 8 | end 9 | 10 | it "gets a list of conferences" do 11 | response, url = stub_get(:conferences, 'Conferences') 12 | 13 | Twilio::Conference.list.should eql response 14 | WebMock.should have_requested(:get, url) 15 | end 16 | 17 | it "gets a specific conference" do 18 | response, url = stub_get(:conference, "Conferences/#{@conference_sid}") 19 | 20 | Twilio::Conference.get(@conference_sid).should eql response 21 | WebMock.should have_requested(:get, url) 22 | end 23 | 24 | it "gets a list of participants" do 25 | response, url = stub_get(:conference_participants, "Conferences/#{@conference_sid}/Participants") 26 | 27 | Twilio::Conference.participants(@conference_sid).should eql response 28 | WebMock.should have_requested(:get, url) 29 | end 30 | 31 | it "gets a specific participant" do 32 | response, url = stub_get(:conference_participant, "Conferences/#{@conference_sid}/Participants/#{@call_sid}") 33 | 34 | Twilio::Conference.participant(@conference_sid, @call_sid).should eql response 35 | WebMock.should have_requested(:get, url) 36 | end 37 | 38 | it "can mute a particant" do 39 | response, url = stub_post(:conference_participant_muted, "Conferences/#{@conference_sid}/Participants/#{@call_sid}", :body => 'Muted=true') 40 | 41 | Twilio::Conference.mute_participant(@conference_sid, @call_sid).should eql response 42 | WebMock.should have_requested(:post, url) 43 | end 44 | 45 | it "can unmute a participant" do 46 | response, url = stub_post(:conference_participant, "Conferences/#{@conference_sid}/Participants/#{@call_sid}", :body => 'Muted=false') 47 | 48 | Twilio::Conference.unmute_participant(@conference_sid, @call_sid).should eql response 49 | WebMock.should have_requested(:post, url) 50 | end 51 | 52 | it "can be kicked" do 53 | response, url = stub_delete(:conference_participant, "Conferences/#{@conference_sid}/Participants/#{@call_sid}") 54 | 55 | Twilio::Conference.kick_participant(@conference_sid, @call_sid).should eql response 56 | WebMock.should have_requested(:delete, url) 57 | end 58 | end -------------------------------------------------------------------------------- /spec/twilio/available_phone_numbers_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe "Available Phone Numbers" do 4 | before(:all) do 5 | Twilio.connect('mysid', 'mytoken') 6 | end 7 | 8 | context "Local Number" do 9 | it "is searchable" do 10 | response, url = stub_get(:available_phone_numbers_local, 'AvailablePhoneNumbers/US/Local') 11 | 12 | Twilio::AvailablePhoneNumbers.search_local.should eql response 13 | WebMock.should have_requested(:get, url) 14 | end 15 | 16 | it "is searchable by area code" do 17 | response, url = stub_get(:available_phone_numbers_local_search, 'AvailablePhoneNumbers/US/Local?AreaCode=510') 18 | 19 | Twilio::AvailablePhoneNumbers.search_local(:area_code => 510).should eql response 20 | WebMock.should have_requested(:get, url) 21 | end 22 | 23 | it "is searchable by postal code" do 24 | response, url = stub_get(:available_phone_numbers_local_search, 'AvailablePhoneNumbers/US/Local?InPostalCode=94612') 25 | 26 | Twilio::AvailablePhoneNumbers.search_local(:postal_code => 94612).should eql response 27 | WebMock.should have_requested(:get, url) 28 | end 29 | 30 | it "is searchable using multiple parameters" do 31 | response, url = stub_get(:available_phone_numbers_local_search, 'AvailablePhoneNumbers/US/Local?NearLatLong=37.806940%2C-122.270360&InRateCenter=OKLD0349T&NearNumber=15105551213&Distance=50&InRegion=CA&InLata=722&Contains=510555****&Page=2&PageSize=30') 32 | 33 | Twilio::AvailablePhoneNumbers.search_local(:in_region => 'CA', :contains => '510555****', :near_lat_long => '37.806940,-122.270360', :near_number => '15105551213', :in_lata => 722, :in_rate_center => 'OKLD0349T', :distance => 50, :page => 2, :page_size => 30).should eql response 34 | WebMock.should have_requested(:get, url) 35 | end 36 | end 37 | 38 | context "Toll-free Number" do 39 | it "is searchable" do 40 | response, url = stub_get(:available_phone_numbers_toll_free, 'AvailablePhoneNumbers/US/TollFree') 41 | 42 | Twilio::AvailablePhoneNumbers.search_toll_free.should eql response 43 | WebMock.should have_requested(:get, url) 44 | end 45 | 46 | it "is searchable as a vanity number" do 47 | response, url = stub_get(:available_phone_numbers_toll_free_search, 'AvailablePhoneNumbers/US/TollFree?Contains=STORM') 48 | 49 | Twilio::AvailablePhoneNumbers.search_toll_free(:contains => 'STORM').should eql response 50 | WebMock.should have_requested(:get, url) 51 | end 52 | end 53 | end -------------------------------------------------------------------------------- /spec/fixtures/yml/verb_responses.yml: -------------------------------------------------------------------------------- 1 | play_mp3: 2 | response: http://foo.com/cowbell.mp3 3 | 4 | play_mp3_two_times: 5 | response: http://foo.com/cowbell.mp3 6 | 7 | play_mp3_two_times_with_pause: 8 | response: http://foo.com/cowbell.mp3http://foo.com/cowbell.mp3 9 | 10 | gather: 11 | response: 12 | 13 | gather_with_action: 14 | response: 15 | 16 | gather_with_get_method: 17 | response: 18 | 19 | gather_with_timeout: 20 | response: 21 | 22 | gather_with_finish_key: 23 | response: 24 | 25 | gather_with_num_digits: 26 | response: 27 | 28 | record: 29 | response: 30 | 31 | record_with_action: 32 | response: 33 | 34 | record_with_get_method: 35 | response: 36 | 37 | record_with_timeout: 38 | response: 39 | 40 | record_with_finish_key: 41 | response: 42 | 43 | record_with_max_length: 44 | response: 45 | 46 | dial: 47 | response: 415-123-4567 48 | 49 | dial_with_action: 50 | response: 415-123-4567 51 | 52 | dial_with_get_method: 53 | response: 415-123-4567 54 | 55 | dial_with_timeout: 56 | response: 415-123-4567 57 | 58 | dial_with_hangup_on_star: 59 | response: 415-123-4567 60 | 61 | dial_with_time_limit: 62 | response: 415-123-4567 63 | 64 | dial_with_caller_id: 65 | response: 415-123-4567 66 | 67 | dial_with_redirect: 68 | response: 415-123-4567http://www.foo.com/nextInstructions 69 | 70 | dial_with_number_and_send_digits: 71 | response: 415-123-4567 72 | 73 | dial_multiple_numbers: 74 | response: 415-123-4567415-123-4568415-123-4569 75 | 76 | dial_conference: 77 | response: MyRoom 78 | 79 | dial_muted_conference: 80 | response: MyRoom 81 | 82 | hangup: 83 | response: 84 | 85 | reject: 86 | response: 87 | -------------------------------------------------------------------------------- /spec/twilio/verb_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe "Verb" do 4 | 5 | context "Say" do 6 | it "says 'hi'" do 7 | Twilio::Verb.say('hi').should match %r{hi} 8 | end 9 | 10 | it "says 'hi' with female voice" do 11 | Twilio::Verb.say('hi', :voice => 'woman').should match %r{hi} 12 | end 13 | 14 | it "says 'hola' in Spanish with female voice" do 15 | Twilio::Verb.say('hola', :voice => 'woman', :language => 'es').should match %r{hola} 16 | end 17 | 18 | it "says 'hi' three times" do 19 | Twilio::Verb.say('hi', :loop => 3).should match %r{hi} 20 | end 21 | 22 | it "says 'hi' three times with pause" do 23 | Twilio::Verb.say('hi', :loop => 3, :pause => true).should match %r{hihihi} 24 | end 25 | 26 | it "says 'hi' with pause and say 'bye'" do 27 | verb = Twilio::Verb.new { |v| 28 | v.say 'hi', :loop => 1 29 | v.pause 30 | v.say 'bye' 31 | }.response.should match %r{hibye} 32 | end 33 | 34 | it "says 'hi' with 2 second pause and say 'bye'" do 35 | verb = Twilio::Verb.new { |v| 36 | v.say 'hi' 37 | v.pause :length => 2 38 | v.say 'bye' 39 | }.response.should match %r{hibye} 40 | end 41 | end 42 | 43 | context "Play" do 44 | it "plays mp3 response" do 45 | Twilio::Verb.play('http://foo.com/cowbell.mp3').should == verb_response(:play_mp3) 46 | end 47 | 48 | it "plays mp3 response two times" do 49 | Twilio::Verb.play('http://foo.com/cowbell.mp3', :loop => 2).should == verb_response(:play_mp3_two_times) 50 | end 51 | 52 | it "plays mp3 response two times with pause" do 53 | Twilio::Verb.play('http://foo.com/cowbell.mp3', :loop => 2, :pause => true).should == verb_response(:play_mp3_two_times_with_pause) 54 | end 55 | end 56 | 57 | context "Gather" do 58 | it "gathers" do 59 | Twilio::Verb.gather.should == verb_response(:gather) 60 | end 61 | 62 | it "gathers with action" do 63 | Twilio::Verb.gather(:action => 'http://foobar.com').should == verb_response(:gather_with_action) 64 | end 65 | 66 | it "gathers with GET method" do 67 | Twilio::Verb.gather(:method => 'GET').should == verb_response(:gather_with_get_method) 68 | end 69 | 70 | it "gathers with timeout" do 71 | Twilio::Verb.gather(:timeout => 10).should == verb_response(:gather_with_timeout) 72 | end 73 | 74 | it "gathers with finish key" do 75 | Twilio::Verb.gather(:finishOnKey => '*').should == verb_response(:gather_with_finish_key) 76 | end 77 | 78 | it "gathers with num digits" do 79 | Twilio::Verb.gather(:numDigits => 5).should == verb_response(:gather_with_num_digits) 80 | end 81 | 82 | it "gathers with all options set" do 83 | Twilio::Verb.gather(:action => 'http://foobar.com', 84 | :finishOnKey => '*', 85 | :method => 'GET', 86 | :numDigits => 5, 87 | :timeout => 10).should match %r{} 88 | end 89 | 90 | it "gathers and says instructions" do 91 | verb = Twilio::Verb.new { |v| 92 | v.gather { 93 | v.say 'Please enter your account number followed by the pound sign' 94 | } 95 | v.say "We didn't receive any input. Goodbye!" 96 | }.response.should match %r{Please enter your account number followed by the pound signWe didn't receive any input. Goodbye!} 97 | end 98 | 99 | it "gathers with timeout and says instructions" do 100 | verb = Twilio::Verb.new { |v| 101 | v.gather(:timeout => 10) { 102 | v.say 'Please enter your account number followed by the pound sign' 103 | } 104 | v.say "We didn't receive any input. Goodbye!" 105 | }.response.should match %r{Please enter your account number followed by the pound signWe didn't receive any input. Goodbye!} 106 | end 107 | end 108 | 109 | context "Record" do 110 | it "records" do 111 | Twilio::Verb.record.should == verb_response(:record) 112 | end 113 | 114 | it "records with action" do 115 | Twilio::Verb.record(:action => 'http://foobar.com').should == verb_response(:record_with_action) 116 | end 117 | 118 | it "records with GET method" do 119 | Twilio::Verb.record(:method => 'GET').should == verb_response(:record_with_get_method) 120 | end 121 | 122 | it "records with timeout" do 123 | Twilio::Verb.record(:timeout => 10).should == verb_response(:record_with_timeout) 124 | end 125 | 126 | it "records with finish key" do 127 | Twilio::Verb.record(:finishOnKey => '*').should == verb_response(:record_with_finish_key) 128 | end 129 | 130 | it "records with max length" do 131 | Twilio::Verb.record(:maxLength => 1800).should == verb_response(:record_with_max_length) 132 | end 133 | 134 | it "records with transcribe" do 135 | Twilio::Verb.record(:transcribe => true, :transcribeCallback => '/handle_transcribe').should match %r{} 136 | end 137 | end 138 | 139 | context "Dial" do 140 | it "dials" do 141 | Twilio::Verb.dial('415-123-4567').should == verb_response(:dial) 142 | end 143 | 144 | it "dials with action" do 145 | Twilio::Verb.dial('415-123-4567', :action => 'http://foobar.com').should == verb_response(:dial_with_action) 146 | end 147 | 148 | it "dials with GET method" do 149 | Twilio::Verb.dial('415-123-4567', :method => 'GET').should == verb_response(:dial_with_get_method) 150 | end 151 | 152 | it "dials with timeout" do 153 | Twilio::Verb.dial('415-123-4567', :timeout => 10).should == verb_response(:dial_with_timeout) 154 | end 155 | 156 | it "dials with hangup on star" do 157 | Twilio::Verb.dial('415-123-4567', :hangupOnStar => true).should == verb_response(:dial_with_hangup_on_star) 158 | end 159 | 160 | it "dials with time limit" do 161 | Twilio::Verb.dial('415-123-4567', :timeLimit => 3600).should == verb_response(:dial_with_time_limit) 162 | end 163 | 164 | it "dials with caller id" do 165 | Twilio::Verb.dial('415-123-4567', :callerId => '858-987-6543').should == verb_response(:dial_with_caller_id) 166 | end 167 | 168 | it "dials with timeout and caller id" do 169 | Twilio::Verb.dial('415-123-4567', :timeout => 10, :callerId => '858-987-6543').should match %r{415-123-4567} 170 | end 171 | 172 | it "dials with redirect" do 173 | verb = Twilio::Verb.new { |v| 174 | v.dial '415-123-4567' 175 | v.redirect 'http://www.foo.com/nextInstructions' 176 | }.response.should == verb_response(:dial_with_redirect) 177 | end 178 | 179 | it "dials with number and send digits" do 180 | verb = Twilio::Verb.new { |v| 181 | v.dial { 182 | v.number('415-123-4567', :sendDigits => 'wwww1928') 183 | } 184 | }.response.should == verb_response(:dial_with_number_and_send_digits) 185 | end 186 | 187 | it "dials multiple numbers" do 188 | verb = Twilio::Verb.new { |v| 189 | v.dial { 190 | v.number '415-123-4567' 191 | v.number '415-123-4568' 192 | v.number '415-123-4569' 193 | } 194 | }.response.should == verb_response(:dial_multiple_numbers) 195 | end 196 | 197 | it "dials a conference" do 198 | verb = Twilio::Verb.new { |v| 199 | v.dial { 200 | v.conference 'MyRoom' 201 | } 202 | }.response.should == verb_response(:dial_conference) 203 | end 204 | 205 | it "dials a muted conference" do 206 | verb = Twilio::Verb.new { |v| 207 | v.dial { 208 | v.conference 'MyRoom', :mute => :true 209 | } 210 | }.response.should == verb_response(:dial_muted_conference) 211 | end 212 | end 213 | 214 | context "Hang Up" do 215 | it "hangs up" do 216 | Twilio::Verb.hangup.should == verb_response(:hangup) 217 | end 218 | 219 | it "says hi and hangs up" do 220 | verb = Twilio::Verb.new { |v| 221 | v.say 'hi' 222 | v.hangup 223 | }.response.should match %r{hi} 224 | end 225 | end 226 | 227 | context "Reject" do 228 | it "rejects" do 229 | Twilio::Verb.reject.should == verb_response(:reject) 230 | end 231 | 232 | it "just rejects incoming call" do 233 | verb = Twilio::Verb.new { |v| 234 | v.reject 235 | }.response.should match %r{} 236 | end 237 | 238 | it "just rejects incoming call with 'busy' status" do 239 | verb = Twilio::Verb.new { |v| 240 | v.reject :reason => 'busy' 241 | }.response.should match %r{} 242 | end 243 | end 244 | 245 | context "SMS" do 246 | it "sends a simple SMS message" do 247 | verb = Twilio::Verb.new { |v| 248 | v.sms 'Join us at the bar', :to => "8005554321", :from => "9006661111", :action => "/smsService", :method => "GET" 249 | }.response.should match %r{Join us at the bar} 250 | end 251 | end 252 | 253 | end 254 | -------------------------------------------------------------------------------- /lib/twilio/verb.rb: -------------------------------------------------------------------------------- 1 | require 'builder' 2 | 3 | module Twilio 4 | # Twilio Verbs enable your application to respond to Twilio requests (to your app) with XML responses. 5 | # There are 5 primary verbs (say, play, gather, record, dial) and 3 secondary (hangup, pause, redirect). 6 | # Verbs can be chained and, in some cases, nested. 7 | # 8 | # If your response consists of a single verb, you can call a Verb class method: 9 | # 10 | # Twilio::Verb.say 'The time is 9:35 PM.' 11 | # 12 | # But if you need to chain several verbs together, just wrap them in an instance block and call the 'response' attribute: 13 | # 14 | # verb = Twilio::Verb.new { |v| 15 | # v.dial '415-123-4567' 16 | # v.redirect 'http://www.foo.com/nextInstructions' 17 | # } 18 | # verb.response 19 | class Verb 20 | 21 | attr_reader :response 22 | 23 | class << self 24 | def method_missing(method_id, *args) #:nodoc: 25 | v = Verb.new 26 | v.send(method_id, *args) 27 | end 28 | end 29 | 30 | def initialize(&block) 31 | @xml = Builder::XmlMarkup.new 32 | @xml.instruct! 33 | 34 | if block_given? 35 | @chain = true 36 | @response = @xml.Response { block.call(self) } 37 | end 38 | end 39 | 40 | # The Say verb converts text to speech that is read back to the caller. 41 | # Say is useful for dynamic text that is difficult to prerecord. 42 | # 43 | # Examples: 44 | # Twilio::Verb.say 'The time is 9:35 PM.' 45 | # Twilio::Verb.say 'The time is 9:35 PM.', :loop => 3 46 | # 47 | # With numbers, 12345 will be spoken as "twelve thousand three hundred forty five" while 48 | # 1 2 3 4 5 will be spoken as "one two three four five." 49 | # 50 | # Twilio::Verb.say 'Your PIN is 1234', :loop => 4 51 | # Twilio::Verb.say 'Your PIN is 1 2 3 4', :loop => 4 52 | # 53 | # If you need a longer pause between each loop, instead of explicitly calling the Pause 54 | # verb within a block, you can set the convenient pause option: 55 | # 56 | # Twilio::Verb.say 'Your PIN is 1 2 3 4', :loop => 4, :pause => true 57 | # 58 | # Options (see http://www.twilio.com/docs/api_reference/TwiML/say) are passed in as a hash: 59 | # 60 | # Twilio::Verb.say 'The time is 9:35 PM.', :voice => 'woman' 61 | # Twilio::Verb.say 'The time is 9:35 PM.', :voice => 'woman', :language => 'es' 62 | def say(*args) 63 | options = {:voice => 'man', :language => 'en', :loop => 1} 64 | args.each do |arg| 65 | case arg 66 | when String 67 | options[:text_to_speak] = arg 68 | when Hash 69 | options.merge!(arg) 70 | else 71 | raise ArgumentError, 'say expects String or Hash argument' 72 | end 73 | end 74 | 75 | output { 76 | if options[:pause] 77 | loop_with_pause(options[:loop], @xml) do 78 | @xml.Say(options[:text_to_speak], :voice => options[:voice], :language => options[:language]) 79 | end 80 | else 81 | @xml.Say(options[:text_to_speak], :voice => options[:voice], :language => options[:language], :loop => options[:loop]) 82 | end 83 | } 84 | end 85 | 86 | # The Play verb plays an audio URL back to the caller. 87 | # Examples: 88 | # Twilio::Verb.play 'http://foo.com/cowbell.mp3' 89 | # Twilio::Verb.play 'http://foo.com/cowbell.mp3', :loop => 3 90 | # 91 | # If you need a longer pause between each loop, instead of explicitly calling the Pause 92 | # verb within a block, you can set the convenient pause option: 93 | # 94 | # Twilio::Verb.play 'http://foo.com/cowbell.mp3', :loop => 3, :pause => true 95 | # 96 | # Options (see http://www.twilio.com/docs/api_reference/TwiML/play) are passed in as a hash, 97 | # but only 'loop' is currently supported. 98 | def play(*args) 99 | options = {:loop => 1} 100 | args.each do |arg| 101 | case arg 102 | when String 103 | options[:audio_url] = arg 104 | when Hash 105 | options.merge!(arg) 106 | else 107 | raise ArgumentError, 'play expects String or Hash argument' 108 | end 109 | end 110 | 111 | output { 112 | if options[:pause] 113 | loop_with_pause(options[:loop], @xml) do 114 | @xml.Play(options[:audio_url]) 115 | end 116 | else 117 | @xml.Play(options[:audio_url], :loop => options[:loop]) 118 | end 119 | } 120 | end 121 | 122 | # The Gather verb collects digits entered by a caller into their telephone keypad. 123 | # When the caller is done entering data, Twilio submits that data to a provided URL, 124 | # as either a HTTP GET or POST request, just like a web browser submits data from an HTML form. 125 | # 126 | # Options (see http://www.twilio.com/docs/api_reference/TwiML/gather) are passed in as a hash 127 | # 128 | # Examples: 129 | # Twilio::Verb.gather 130 | # Twilio::Verb.gather :action => 'http://foobar.com' 131 | # Twilio::Verb.gather :finishOnKey => '*' 132 | # Twilio::Verb.gather :action => 'http://foobar.com', :finishOnKey => '*' 133 | # 134 | # Gather also lets you nest the Play, Say, and Pause verbs: 135 | # 136 | # verb = Twilio::Verb.new { |v| 137 | # v.gather(:action => '/process_gather', :method => 'GET) { 138 | # v.say 'Please enter your account number followed by the pound sign' 139 | # } 140 | # v.say "We didn't receive any input. Goodbye!" 141 | # } 142 | # verb.response # represents the final xml output 143 | def gather(*args, &block) 144 | options = args.shift || {} 145 | output { 146 | if block_given? 147 | @xml.Gather(options) { block.call} 148 | else 149 | @xml.Gather(options) 150 | end 151 | } 152 | end 153 | 154 | #play, say, pause 155 | 156 | # The Record verb records the caller's voice and returns a URL that links to a file 157 | # containing the audio recording. 158 | # 159 | # Options (see http://www.twilio.com/docs/api_reference/TwiML/record) are passed in as a hash 160 | # 161 | # Examples: 162 | # Twilio::Verb.record 163 | # Twilio::Verb.record :action => 'http://foobar.com' 164 | # Twilio::Verb.record :finishOnKey => '*' 165 | # Twilio::Verb.record :transcribe => true, :transcribeCallback => '/handle_transcribe' 166 | def record(*args) 167 | options = args.shift 168 | output { @xml.Record(options) } 169 | end 170 | 171 | # The Dial verb connects the current caller to an another phone. If the called party picks up, 172 | # the two parties are connected and can communicate until one hangs up. If the called party does 173 | # not pick up, if a busy signal is received, or the number doesn't exist, the dial verb will finish. 174 | # 175 | # If an action verb is provided, Twilio will submit the outcome of the call attempt to the action URL. 176 | # If no action is provided, Dial will fall through to the next verb in the document. 177 | # 178 | # Note: this is different than the behavior of Record and Gather. Dial does not submit back to the 179 | # current document URL if no action is provided. 180 | # 181 | # Options (see http://www.twilio.com/docs/api_reference/TwiML/dial) are passed in as a hash 182 | # 183 | # Examples: 184 | # Twilio::Verb.dial '415-123-4567' 185 | # Twilio::Verb.dial '415-123-4567', :action => 'http://foobar.com' 186 | # Twilio::Verb.dial '415-123-4567', :timeout => 10, :callerId => '858-987-6543' 187 | # 188 | # Twilio also supports an alternate form in which a Number object is nested inside Dial: 189 | # 190 | # verb = Twilio::Verb.new { |v| 191 | # v.dial { |v| 192 | # v.number '415-123-4567' 193 | # v.number '415-123-4568' 194 | # v.number '415-123-4569' 195 | # } 196 | # } 197 | # verb.response # represents the final xml output 198 | def dial(*args, &block) 199 | number_to_dial = '' 200 | options = {} 201 | args.each do |arg| 202 | case arg 203 | when String 204 | number_to_dial = arg 205 | when Hash 206 | options.merge!(arg) 207 | else 208 | raise ArgumentError, 'dial expects String or Hash argument' 209 | end 210 | end 211 | 212 | output { 213 | if block_given? 214 | @xml.Dial(options) { block.call } 215 | else 216 | @xml.Dial(number_to_dial, options) 217 | end 218 | } 219 | end 220 | 221 | # The verb's noun allows you to connect to a conference room. 222 | # Much like how the noun allows you to connect to another phone number, 223 | # the noun allows you to connect to a named conference room and talk 224 | # with the other callers who have also connected to that room. 225 | # 226 | # Options (see http://www.twilio.com/docs/api_reference/TwiML/conference) are passed in as a hash 227 | # 228 | # Examples: 229 | # verb = Twilio::Verb.new { |v| 230 | # v.dial { 231 | # v.conference 'MyRoom', :muted => true 232 | # } 233 | # } 234 | # verb.response 235 | def conference(*args) 236 | conference_name = '' 237 | options = {} 238 | args.each do |arg| 239 | case arg 240 | when String 241 | conference_name = arg 242 | when Hash 243 | options.merge!(arg) 244 | else 245 | raise ArgumentError, 'conference expects String or Hash argument' 246 | end 247 | end 248 | 249 | output { @xml.Conference(conference_name, options)} 250 | end 251 | 252 | # The Pause (secondary) verb waits silently for a number of seconds. 253 | # It is normally chained with other verbs. 254 | # 255 | # Options (see http://www.twilio.com/docs/api_reference/TwiML/pause) are passed in as a hash 256 | # 257 | # Examples: 258 | # verb = Twilio::Verb.new { |v| 259 | # v.say 'greetings' 260 | # v.pause :length => 2 261 | # v.say 'have a nice day' 262 | # } 263 | # verb.response 264 | def pause(*args) 265 | options = args.shift 266 | output { @xml.Pause(options) } 267 | end 268 | 269 | # The Redirect (secondary) verb transfers control to a different URL. 270 | # It is normally chained with other verbs. 271 | # 272 | # Options (see http://www.twilio.com/docs/api_reference/TwiML/redirect) are passed in as a hash 273 | # 274 | # Examples: 275 | # verb = Twilio::Verb.new { |v| 276 | # v.dial '415-123-4567' 277 | # v.redirect 'http://www.foo.com/nextInstructions' 278 | # } 279 | # verb.response 280 | def redirect(*args) 281 | redirect_to_url = '' 282 | options = {} 283 | args.each do |arg| 284 | case arg 285 | when String 286 | redirect_to_url = arg 287 | when Hash 288 | options.merge!(arg) 289 | else 290 | raise ArgumentError, 'dial expects String or Hash argument' 291 | end 292 | end 293 | 294 | output { @xml.Redirect(redirect_to_url, options) } 295 | end 296 | 297 | # The verb sends a SMS message to a phone number. 298 | # 299 | # Options (see http://www.twilio.com/docs/api/2008-08-01/twiml/sms/sms) ars passed in as a hash 300 | # 301 | # Examples: 302 | # verb = Twilio::Verb.new { |v| 303 | # v.sms 'Meet at South Street' 304 | # } 305 | # 306 | def sms(*args) 307 | message = '' 308 | options = {} 309 | args.each do |arg| 310 | case arg 311 | when String 312 | message = arg 313 | when Hash 314 | options.merge!(arg) 315 | else 316 | raise ArgumentError, 'sms expects STring or Hash argument' 317 | end 318 | end 319 | 320 | output { @xml.Sms(message, options) } 321 | end 322 | 323 | # The Hangup (secondary) verb ends the call. 324 | # 325 | # Examples: 326 | # If your response is only a hangup: 327 | # 328 | # Twilio::Verb.hangup 329 | # 330 | # If your response is chained: 331 | # 332 | # verb = Twilio::Verb.new { |v| 333 | # v.say "The time is #{Time.now}" 334 | # v.hangup 335 | # } 336 | # verb.response 337 | def hangup 338 | output { @xml.Hangup } 339 | end 340 | 341 | # The Number element specifies a phone number. The number element has two optional attributes: sendDigits and url. 342 | # Number elements can only be nested in Dial verbs 343 | def number(*args) 344 | number_to_dial = '' 345 | options = {} 346 | args.each do |arg| 347 | case arg 348 | when String 349 | number_to_dial = arg 350 | when Hash 351 | options.merge!(arg) 352 | else 353 | raise ArgumentError, 'dial expects String or Hash argument' 354 | end 355 | end 356 | 357 | output { @xml.Number(number_to_dial, options) } 358 | end 359 | 360 | 361 | # The Reject verb rejects an incoming call to your Twilio number without billing you 362 | # (see http://www.twilio.com/docs/api/twiml/reject) 363 | # Examples: 364 | # 365 | # Twilio::Verb.reject 366 | # 367 | # If reject is called with an argument: 368 | # 369 | # Twilio::Verb.reject :reason => "busy" 370 | # 371 | def reject options = {} 372 | output { @xml.Reject(options) } 373 | end 374 | 375 | private 376 | 377 | def output 378 | @chain ? yield : @xml.Response { yield } 379 | end 380 | 381 | def loop_with_pause(loop_count, xml, &verb_action) 382 | last_iteration = loop_count-1 383 | loop_count.times do |i| 384 | yield verb_action 385 | xml.Pause unless i == last_iteration 386 | end 387 | end 388 | end 389 | end 390 | --------------------------------------------------------------------------------