twilio-rb 0.4 → 1.0beta
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +209 -40
- data/lib/railtie.rb +22 -0
- data/lib/twilio/account.rb +9 -4
- data/lib/twilio/available_phone_number.rb +26 -0
- data/lib/twilio/call.rb +33 -46
- data/lib/twilio/conference.rb +18 -0
- data/lib/twilio/deletable.rb +7 -0
- data/lib/twilio/finder.rb +60 -0
- data/lib/twilio/incoming_phone_number.rb +14 -0
- data/lib/twilio/notification.rb +23 -0
- data/lib/twilio/outgoing_caller_id.rb +12 -0
- data/lib/twilio/participant.rb +24 -0
- data/lib/twilio/persistable.rb +24 -0
- data/lib/twilio/recording.rb +11 -0
- data/lib/twilio/resource.rb +36 -29
- data/lib/twilio/sandbox.rb +26 -0
- data/lib/twilio/sms.rb +17 -8
- data/lib/twilio/transcription.rb +6 -0
- data/lib/twilio.rb +7 -2
- metadata +38 -8
data/README.md
CHANGED
@@ -1,7 +1,11 @@
|
|
1
1
|
|
2
2
|
# Twilio.rb
|
3
3
|
|
4
|
-
Interact with the Twilio API in a nice Ruby way
|
4
|
+
Interact with the Twilio API in a nice Ruby way.
|
5
|
+
|
6
|
+
Twilio.rb is the only library that encapsulates Twilio resources as Ruby objects, has 100% test coverage, and supports the whole API.
|
7
|
+
|
8
|
+
It offers an ActiveRecord style API, i.e. one that most Ruby developers are familiar using to manipulate Ruby objects with.
|
5
9
|
|
6
10
|
## Installation
|
7
11
|
|
@@ -15,6 +19,10 @@ Require the library in your script as
|
|
15
19
|
|
16
20
|
<pre>require 'twilio'</pre>
|
17
21
|
|
22
|
+
or using bundler:
|
23
|
+
|
24
|
+
<pre>gem 'twilio-rb', '1.0pre', :require => 'twilio'</pre>
|
25
|
+
|
18
26
|
## Configuration
|
19
27
|
|
20
28
|
Configuration for this library is encapsulated within `Twilio::Config`. One needs to setup with an Account SID and an Auth Token, e.g.
|
@@ -27,50 +35,19 @@ end</pre>
|
|
27
35
|
|
28
36
|
Any method that calls the Twilio API will raise `Twilio::ConfigurationError` if either Account SID or Auth Token are not configured.
|
29
37
|
|
30
|
-
|
31
|
-
|
32
|
-
The Twilio API in its current incarnation supports one Twilio account per Account SID, and so the Twilio::Account object correspondingly is a singleton object.
|
33
|
-
|
34
|
-
To access properties of the account the property name should be called as a method on the object itself, e.g.
|
35
|
-
|
36
|
-
<pre>Twilio::Account.friendly_name</pre>
|
37
|
-
|
38
|
-
The first time a method is invoked on the object an API call is made to retrieve the data. The methods themselves are not defined until they are called, i.e. lazy evaluation. This strategy means that addtional properties added to subsequent versions of the API should not break the library.
|
39
|
-
|
40
|
-
To reload the data when needed `Twilio::Account.reload!` will make another API call and update its own internal state.
|
41
|
-
|
42
|
-
Predicate methods i.e. those ending in `?` map directly to the status of the account, e.g. `Twilio::Account.suspended?` returns true if Twilio have suspended your account. Again, all of these methods are defined on the fly.
|
43
|
-
|
44
|
-
The only account property that can be modified via the REST API is the friendly name, e.g.
|
45
|
-
|
46
|
-
<pre>Twilio::Account.friendly_name = "I'm so vain, I had to change my name!"</pre>
|
47
|
-
|
48
|
-
This will update the API immediately with a PUT request.
|
49
|
-
|
50
|
-
Please refer to the Twilio REST API documentation for an up to date list of properties.
|
38
|
+
# Getting started
|
51
39
|
|
52
40
|
## Making a telephone call
|
53
41
|
|
54
42
|
The API used to make a telephone call is similar to interacting with an ActiveRecord model object.
|
55
43
|
<pre>Twilio::Call.create :to => '+16465551234', :from => '+19175550000',
|
56
44
|
:url => "http://example.com/call_handler"</pre>
|
57
|
-
or
|
58
|
-
<pre>call = Twilio::Call.new :to => '+16465551234', :from => '+19175550000',
|
59
|
-
:url => "http://example.com/call_handler"
|
60
|
-
|
61
|
-
call.save</pre>
|
62
45
|
|
63
46
|
The parameter keys should be given as underscored symbols. They will be converted internally to camelized strings prior to an API call being made.
|
64
47
|
|
65
48
|
Please see the Twilio REST API documentation for an up to date list of supported parameters.
|
66
49
|
|
67
|
-
|
68
|
-
|
69
|
-
To retrieve an earlier created call, there is the `Twilio::Call.find` method, which accepts a call SID, e.g.
|
70
|
-
|
71
|
-
<pre>call = Twilio::Call.find 'CAa346467ca321c71dbd5e12f627deb854'</pre>
|
72
|
-
|
73
|
-
This returns an instance of `Twilio::Call` if a call with the given SID was found, otherwise nil is returned
|
50
|
+
If the request was successful, an instance of `Twilio::Call` wil be returned
|
74
51
|
|
75
52
|
### Modifying a live telephone call
|
76
53
|
|
@@ -83,21 +60,26 @@ Once a call has been been created it can be modified with the following methods:
|
|
83
60
|
`Twilio::Call#cancel!` and `Twilio::Call#complete!` will raise `Twilio::InvalidStateError` if the call has not been "saved".
|
84
61
|
`Twilio::Call#url=` will updated its state with the new URL ready for when `Twilio::Call#save` is called.
|
85
62
|
|
63
|
+
## Finding an existing telephone call
|
64
|
+
|
65
|
+
To retrieve an earlier created call, there is the `Twilio::Call.find` method, which accepts a call SID, e.g.
|
66
|
+
|
67
|
+
<pre>call = Twilio::Call.find 'CAa346467ca321c71dbd5e12f627deb854'</pre>
|
68
|
+
|
69
|
+
This returns an instance of `Twilio::Call` if a call with the given SID was found, otherwise nil is returned
|
70
|
+
|
86
71
|
## Sending an SMS message
|
87
72
|
|
88
73
|
The API used to send an SMS message is similar to interacting with an ActiveRecord model object.
|
89
74
|
<pre>Twilio::SMS.create :to => '+16465551234', :from => '+19175550000',
|
90
75
|
:body => "Hey baby, how was your day? x"</pre>
|
91
|
-
or
|
92
|
-
<pre>sms = Twilio::SMS.new :to => '+16465551234', :from => '+19175550000',
|
93
|
-
:body => "Hey baby, how was your day? x"
|
94
|
-
|
95
|
-
sms.save</pre>
|
96
76
|
|
97
77
|
The parameter keys should be given as underscored symbols. They will be converted internally to camelized strings prior to an API call being made.
|
98
78
|
|
99
79
|
Please see the Twilio REST API documentation for an up to date list of supported parameters.
|
100
80
|
|
81
|
+
If the request was successful, an instance of `Twilio::SMS` wil be returned
|
82
|
+
|
101
83
|
## Finding an existing telephone SMS message
|
102
84
|
|
103
85
|
To retrieve an earlier created SMS message, there is the `Twilio::SMS.find` method, which accepts a SMS message SID, e.g.
|
@@ -106,7 +88,7 @@ To retrieve an earlier created SMS message, there is the `Twilio::SMS.find` meth
|
|
106
88
|
|
107
89
|
This returns an instance of `Twilio::SMS` if a SMS message with the given SID was found, otherwise nil is returned
|
108
90
|
|
109
|
-
|
91
|
+
# Building TwiML documents
|
110
92
|
|
111
93
|
A TwiML document is an XML document. The best way to build XML in Ruby is with Builder, and so it follows that we should use builder for TwiML. `Twilio::TwiML.build` behaves like builder except element names are capitalised for you and attributes are camelized for you as well. This is so you may continue to write beautiful code.
|
112
94
|
|
@@ -143,3 +125,190 @@ Therefore emits the following TwiML document:
|
|
143
125
|
</pre>
|
144
126
|
|
145
127
|
This specialised behaviour only affects `Twilio::TwiML.build` and does not affect Builder in general.
|
128
|
+
|
129
|
+
# Rails 3 integration
|
130
|
+
|
131
|
+
Twilio.rb has Rails integration out of the box. It adds a new mime type :voice and a template handler for TwiML views.
|
132
|
+
So now your Rails app can respond_to :voice. Insane!
|
133
|
+
|
134
|
+
<pre>
|
135
|
+
class FooController < ApplicationController
|
136
|
+
responds_to :html, :voice
|
137
|
+
|
138
|
+
def index
|
139
|
+
...
|
140
|
+
end
|
141
|
+
end
|
142
|
+
</pre>
|
143
|
+
|
144
|
+
coupled with the following view file `app/views/foo/index.voice`
|
145
|
+
|
146
|
+
<pre>
|
147
|
+
res.say 'Damn this library is so ill dude!'
|
148
|
+
</pre>
|
149
|
+
|
150
|
+
It's now easier than ever to integrate Twilio in your Rails app cleanly and easily.
|
151
|
+
|
152
|
+
# Basics
|
153
|
+
|
154
|
+
Each super-resource, e.g. Calls, OutgoingCallerIds, etc has a Ruby object in the Twilio namespace representing it, Twilio::Call, Twilio::OutgoingCallerId, etc.
|
155
|
+
|
156
|
+
## Summary
|
157
|
+
|
158
|
+
Resources that can be created via the API, using the HTTP POST verb can be done so in the library using the `.create` class method, e.g.
|
159
|
+
|
160
|
+
<pre>Twilio::Call.create :to => '+16465551234', :from => '+19175550000',
|
161
|
+
:url => "http://example.com/call_handler"</pre>
|
162
|
+
|
163
|
+
Resources that can be removed via the API, using the HTTP DELETE verb can be done so in the library using the `#destroy` instance method, e.g.
|
164
|
+
|
165
|
+
<pre>
|
166
|
+
# Delete all log entries
|
167
|
+
Twilio::Notification.all.each &:destroy
|
168
|
+
</pre>
|
169
|
+
|
170
|
+
Object representations instantiated by the library respond to all methods that match attributes on its corresponding resource. The method names are those of the parameters in snake case (underscored), i.e. as they are returned in the JSON representation.
|
171
|
+
|
172
|
+
The Twilio API documentation itself is the canonical reference for which resources have what properties, and which of those can be updated by the API. Please refer to the Twilio REST API documentation for thos information.
|
173
|
+
|
174
|
+
## Accessing resource instances
|
175
|
+
|
176
|
+
Resource instances can be accessed ad hoc passsing the resource sid to the `.find` class method on the resource class, e.g.
|
177
|
+
|
178
|
+
<pre>call = Twilio::Call.find 'CAe1644a7eed5088b159577c5802d8be38'</pre>
|
179
|
+
|
180
|
+
This will return an instance of the resource class, in this case `Twilio::Call`, with the attributes of the resource. These attributes are accessed using dynamically defined getter methods, where the method name is the attribute name underscored, i.e. as they are returned in a JSON response from the API.
|
181
|
+
|
182
|
+
Sometimes these method name might collide with native Ruby methods, one such example is the `method` parameter colliding with `Object#method`. Native Ruby methods are never overridden by the library as they are lazily defined using `method_missing`. To access these otherwise unreachable attributes, there is another syntax for accessing resource attributes:
|
183
|
+
|
184
|
+
|
185
|
+
<pre>
|
186
|
+
call = Twilio::Call.find 'CAe1644a7eed5088b159577c5802d8be38'
|
187
|
+
call[:method] # With a symbol or
|
188
|
+
call['method'] # or with a string. Access is indifferent.
|
189
|
+
</pre>
|
190
|
+
|
191
|
+
## Querying list resources
|
192
|
+
|
193
|
+
List resources can be accessed ad hoc by calling the `.all` class method on the resource class, e.g.
|
194
|
+
|
195
|
+
<pre>calls = Twilio::Call.all</pre>
|
196
|
+
|
197
|
+
This will return a collection of objects, each a representation of the corresponding resource.
|
198
|
+
|
199
|
+
### Using filter parameters to refine a query
|
200
|
+
|
201
|
+
The `.all` class method will ask Twilio for all resource instances on that list resource, this can easily result in a useless response if there are numerous resource instances on a given resource. The `.all` class method accepts a hash of options for parameters to filter the response, e.g.
|
202
|
+
|
203
|
+
<pre>Twilio::Call.all :to => '+19175550000', :status => 'complete'</pre>
|
204
|
+
|
205
|
+
Twilio does some fancy stuff to implement date ranges, consider the API request:
|
206
|
+
|
207
|
+
<pre>GET /2010-04-01/Accounts/AC5ef87.../Calls?StartTime>=2009-07-06&EndTime<=2009-07-10</pre>
|
208
|
+
|
209
|
+
This will return all calls started after midnight July 06th 2009 and completed before July 10th 2009. Any call started and ended within that time range matches those criteria and will be returned. To make the same reqest using this library:
|
210
|
+
|
211
|
+
<pre>
|
212
|
+
require 'date'
|
213
|
+
start_date, end_date = Date.parse('2009-07-06'), Date.parse('2009-07-10')
|
214
|
+
|
215
|
+
Twilio::Call.all :started_after => start_date, :ended_before => end_date
|
216
|
+
</pre>
|
217
|
+
|
218
|
+
All option parameters pertaining to dates accept a string or any object that returns a RFC 2822 date when `#to_s` is called on it, e.g. an instance of `Date`. If a date parameter is not a rnage but absolute, one can of course use the normal option, e.g.
|
219
|
+
|
220
|
+
<pre>Twilio::Call.all :start_time => start_date</pre>
|
221
|
+
|
222
|
+
The key names for these Date filters are inconsistent across resources, in the library they are:
|
223
|
+
|
224
|
+
<pre>
|
225
|
+
Twilio::SMS.all :created_before => date, :created_after => date, :sent_before => date, :sent_after # :"created_#{when}" and :"sent_#{when}" are synonomous
|
226
|
+
Twilio::Notification.all :created_before => date, :created_after => date
|
227
|
+
Twilio::Call.all :started_before => date, :started_after => date, :ended_before => date, :ended_after => date
|
228
|
+
</pre>
|
229
|
+
|
230
|
+
### Pagination
|
231
|
+
|
232
|
+
The Twilio API paginates API responses and by default it will return 30 objects in one response, this can be overridden to return up to a maximum of 1000 per response using the `:page_size` option, If more than 1000 resources instances exist, the `:page` option is available, e.g.
|
233
|
+
|
234
|
+
<pre>Twilio::Call.all :started_after => start_date, :ended_before => end_date, :page_size => 1000, :page => 7</pre>
|
235
|
+
|
236
|
+
To determine how many resources exist, the `.count` class method exists, which accepts the same options as `.all` in order to constrain the query e.g.
|
237
|
+
|
238
|
+
<pre>Twilio::Call.count :started_after => start_date, :ended_before => end_date</pre>
|
239
|
+
|
240
|
+
It returns an integer corresponding to how many resource instances exist with those conditions.
|
241
|
+
|
242
|
+
## Updating resource attributes
|
243
|
+
|
244
|
+
Certain resources have attributes that can be updated with the REST API. Instances of those resources can be updated using either a setter method with a name that corresponds to the attribute, or by using the `#update_attributes`.
|
245
|
+
|
246
|
+
<pre>
|
247
|
+
call = Twilio::Call.all(:status => 'in-progress').first
|
248
|
+
|
249
|
+
call.url = 'http://example.com/in_ur_apiz_hijackin_ur_callz.xml'
|
250
|
+
call.update_attributes :url => 'http://example.com/in_ur_apiz_hijackin_ur_callz.xml'
|
251
|
+
</pre>
|
252
|
+
|
253
|
+
These are both equivalent, i.e. they immediately make an API request and update the state of the object with the API response. The first one in fact uses the second one internally and is just a shortcut. Use the second when there is more than one attribute to be updated in the same HTTP request.
|
254
|
+
|
255
|
+
## Manipulating conference participants
|
256
|
+
|
257
|
+
The participants list resource is a subresource of a conference resource instance:
|
258
|
+
|
259
|
+
<pre>conference = Twilio::Conference.find 'CFbbe46ff1274e283f7e3ac1df0072ab39'</pre>
|
260
|
+
|
261
|
+
Conference participants are accessed via the `#particpants` instance method, e.g.
|
262
|
+
|
263
|
+
<pre>participants = conference.participants</pre>
|
264
|
+
|
265
|
+
The muted state can be toggled using the `#mute!` instance method, e.g. toggle the mute state off all participants:
|
266
|
+
|
267
|
+
<pre>participants.each &:mute!</pre>
|
268
|
+
|
269
|
+
Participants can be removed from the conference using the '#destroy instance method'
|
270
|
+
|
271
|
+
# Singleton resources
|
272
|
+
|
273
|
+
The Twilio API in its current incarnation has two singleton (scoped per account) resources, correspondingly there are the Twilio::Account, and Twilio::Sandbox singleton objects.
|
274
|
+
|
275
|
+
To access properties of a singleton object the property name should be called as a method on the object itself, e.g.
|
276
|
+
|
277
|
+
<pre>Twilio::Account.friendly_name</pre>
|
278
|
+
|
279
|
+
The first time a method is invoked on the object an API call is made to retrieve the data. The methods themselves are not defined until they are called, i.e. lazy evaluation. This strategy means that addtional properties added to subsequent versions of the API should not break the library.
|
280
|
+
|
281
|
+
To reload the data when needed `Twilio::Account.reload!` will make another API call and update its own internal state.
|
282
|
+
|
283
|
+
The account has a predicate method i.e. ending in `?`, it maps directly to the status of the account, e.g. `Twilio::Account.suspended?` returns true if Twilio have suspended your account. Again, this is defined on the fly.
|
284
|
+
|
285
|
+
The only account property that can be modified via the REST API is the friendly name, e.g.
|
286
|
+
|
287
|
+
<pre>Twilio::Account.friendly_name = "I'm so vain, I had to change my name!"</pre>
|
288
|
+
<pre>Twilio::Account.update_attributes :friendly_name => "I'm so vain, I had to change my name!"</pre>
|
289
|
+
|
290
|
+
This will update the API immediately with a POST request.
|
291
|
+
|
292
|
+
Twilio::Sandbox supports more options. Please refer to the Twilio REST API documentation for an up to date list of properties.
|
293
|
+
|
294
|
+
# Searching for and purchasing available phone numbers
|
295
|
+
|
296
|
+
The Twilio API allows a user to search for available phone numbers in Twilio's inventory. e.g. to find a number in the 917 area code containing '7777':
|
297
|
+
|
298
|
+
<pre>Twilio::AvailablePhoneNumber.all :area_code => '917', :contains => '7777'</pre>
|
299
|
+
|
300
|
+
A collection of Twilio::AvailablePhoneNumber objects will be returned. e.g. To purchase the first one:
|
301
|
+
|
302
|
+
<pre>numbers = Twilio::AvailablePhoneNumber.all :area_code => '917', :contains => '7777'
|
303
|
+
numbers.first.purchase! :voice_url => 'http://example.com/twiml.xml'</pre>
|
304
|
+
|
305
|
+
Which is a shortcut for:
|
306
|
+
|
307
|
+
<pre>numbers = Twilio::AvailablePhoneNumber.all :area_code => '917', :contains => '7777'
|
308
|
+
Twilio::IncomingPhoneNumber.create :phone_number => numbers.first.phone_number, :voice_url => 'http://example.com/twiml.xml'</pre>
|
309
|
+
|
310
|
+
# Recordings
|
311
|
+
|
312
|
+
A recording resource instance has an extra instance method: `#mp3` this returns a publicly accessible URL for the MP3 representation of the resource instance.
|
313
|
+
|
314
|
+
|
data/lib/railtie.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
module Twilio
|
2
|
+
class Railtie < Rails::Railtie
|
3
|
+
initializer 'twilio.initialize' do |app|
|
4
|
+
module ActionView
|
5
|
+
class Template
|
6
|
+
module Handlers
|
7
|
+
class TwiML < ::ActionView::Template::Handler
|
8
|
+
include ::ActionView::Template::Handlers::Compilable
|
9
|
+
|
10
|
+
def compile(template)
|
11
|
+
%<Twilio::TwiML.build { |res| #{template.source} }>
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
::ActionView::Template.register_template_handler(:voice, ActionView::Template::Handlers::TwiML)
|
19
|
+
::Mime::Type.register_alias 'text/xml', :voice
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/lib/twilio/account.rb
CHANGED
@@ -1,20 +1,25 @@
|
|
1
1
|
module Twilio
|
2
2
|
module Account
|
3
3
|
include Twilio::Resource
|
4
|
-
@attributes = {}
|
4
|
+
@attributes = {}.with_indifferent_access
|
5
5
|
|
6
6
|
def attributes
|
7
7
|
@attributes.empty? ? reload! : @attributes
|
8
8
|
end
|
9
9
|
|
10
10
|
def reload!
|
11
|
-
handle_response get
|
11
|
+
handle_response get path
|
12
12
|
end
|
13
13
|
|
14
14
|
def friendly_name=(name)
|
15
|
-
|
15
|
+
update_attributes :friendly_name => name
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
def path
|
20
|
+
"/Accounts/#{Twilio::ACCOUNT_SID}.json"
|
16
21
|
end
|
17
22
|
|
18
23
|
extend self
|
19
24
|
end
|
20
|
-
end
|
25
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Twilio
|
2
|
+
class AvailablePhoneNumber
|
3
|
+
include Twilio::Resource
|
4
|
+
extend Twilio::Finder
|
5
|
+
|
6
|
+
class << self
|
7
|
+
def all(opts={})
|
8
|
+
opts = Hash[opts.map { |k,v| [k.to_s.camelize, v]}]
|
9
|
+
country_code = opts['IsoCountryCode'] ? opts.delete('IsoCountryCode') : 'US'
|
10
|
+
number_type = opts.delete('TollFree') ? 'TollFree' : 'Local'
|
11
|
+
params = { :query => opts } if opts.any?
|
12
|
+
|
13
|
+
handle_response get "/Accounts/#{Twilio::ACCOUNT_SID}/AvailablePhoneNumbers/#{country_code}/#{number_type}.json", params
|
14
|
+
end
|
15
|
+
|
16
|
+
private :new
|
17
|
+
undef_method :count
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
# Shortcut for creating a new incoming phone number. Delegates to Twilio::IncomingPhoneNumber.create accepting the same options as that method does.
|
22
|
+
def purchase!(opts={})
|
23
|
+
Twilio::IncomingPhoneNumber.create opts.update :phone_number => phone_number
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/lib/twilio/call.rb
CHANGED
@@ -1,62 +1,49 @@
|
|
1
1
|
module Twilio
|
2
2
|
class Call
|
3
3
|
include Twilio::Resource
|
4
|
+
include Twilio::Persistable
|
5
|
+
extend Twilio::Finder
|
6
|
+
|
7
|
+
class << self
|
8
|
+
alias old_create create
|
9
|
+
def create(attrs={})
|
10
|
+
attrs = attrs.with_indifferent_access
|
11
|
+
attrs.each { |k,v| v.upcase! if k.to_s =~ /method$/ }
|
12
|
+
attrs[:send_digits] = CGI.escape(attrs[:send_digits]) if attrs[:send_digits]
|
13
|
+
attrs['if_machine'].try :capitalize
|
14
|
+
old_create attrs
|
15
|
+
end
|
4
16
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
17
|
+
private
|
18
|
+
def prepare_dates(opts)
|
19
|
+
opts.map do |k,v|
|
20
|
+
if [:started_before, :started_after, :ended_before, :ended_after].include? k
|
21
|
+
k = k.to_s
|
22
|
+
# Fancy schmancy-ness to handle Twilio <= URI operator for dates
|
23
|
+
comparator = k =~ /before$/ ? '<=' : '>='
|
24
|
+
delineator = k =~ /started/ ? 'Start' : 'End'
|
25
|
+
delineator << "Time" << comparator << v.to_s
|
26
|
+
else
|
27
|
+
"#{k.to_s.camelize}=#{v}"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
15
31
|
end
|
16
32
|
|
17
|
-
# Cancels a call if its state is 'queued' or 'ringing'
|
33
|
+
# Cancels a call if its state is 'queued' or 'ringing'
|
18
34
|
def cancel!
|
19
|
-
|
35
|
+
update_attributes :status => 'cancelled'
|
20
36
|
end
|
21
|
-
|
37
|
+
|
22
38
|
def complete!
|
23
|
-
|
39
|
+
update_attributes :status => 'completed'
|
24
40
|
end
|
25
|
-
|
41
|
+
|
26
42
|
# Update Handler URL
|
27
43
|
def url=(url)
|
28
44
|
# If this attribute exists it is assumed the API call to create a call has been made, so we need to tell Twilio.
|
29
|
-
|
45
|
+
update_attributes :url => url if self[:status]
|
30
46
|
self[:url] = url
|
31
47
|
end
|
32
|
-
|
33
|
-
private
|
34
|
-
|
35
|
-
def normalize_http_verbs! #:nodoc:
|
36
|
-
# Twilio accepts a HTTP method for use with various callbacks. The API documentation
|
37
|
-
# indicates that the HTTP verbs are to be passed as upcase.
|
38
|
-
attributes.each { |k,v| v.upcase! if k =~ /Method$/ }
|
39
|
-
end
|
40
|
-
|
41
|
-
def escape_send_digits! #:nodoc:
|
42
|
-
# A pound, i.e. "#" has special meaning in a URL so it must be escaped
|
43
|
-
attributes.update 'SendDigits' => CGI.escape(attributes['SendDigits'])
|
44
|
-
end
|
45
|
-
|
46
|
-
def normalize_if_machine_parameter! #:nodoc:
|
47
|
-
attributes['IfMachine'].capitalize! if attributes['IfMachine']
|
48
|
-
end
|
49
|
-
|
50
|
-
def state_guard(&blk)
|
51
|
-
if self[:status] # If this attribute exists it is assumed the API call to create a call has been made, and the object is in the correct state to make request.
|
52
|
-
blk.call
|
53
|
-
else
|
54
|
-
raise Twilio::InvalidStateError.new 'Call is in invalid state to perform this action.'
|
55
|
-
end
|
56
|
-
end
|
57
|
-
|
58
|
-
def modify_call(params)
|
59
|
-
handle_response self.class.post "/Accounts/#{Twilio::ACCOUNT_SID}/Calls/#{self[:sid]}.json", :body => params
|
60
|
-
end
|
61
48
|
end
|
62
|
-
end
|
49
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Twilio
|
2
|
+
class Conference
|
3
|
+
include Twilio::Resource
|
4
|
+
extend Twilio::Finder
|
5
|
+
|
6
|
+
def participants
|
7
|
+
res = self.class.get "/Accounts/#{Twilio::ACCOUNT_SID}/Conferences/#{sid}/Participants.json"
|
8
|
+
if (400..599).include? res.code
|
9
|
+
raise Twilio::APIError.new "Error ##{res.parsed_response['code']}: #{res.parsed_response['message']}"
|
10
|
+
else
|
11
|
+
res.parsed_response['participants'].map do |p|
|
12
|
+
p['api_version'] = p['api_version'].to_s # api_version parsed as a date by http_party
|
13
|
+
Twilio::Participant.new p
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module Twilio
|
2
|
+
module Finder
|
3
|
+
def find(id)
|
4
|
+
res = get "/Accounts/#{Twilio::ACCOUNT_SID}/#{resource_fragment}/#{id}.json"
|
5
|
+
hash = res.parsed_response
|
6
|
+
if (200..299).include? res.code
|
7
|
+
hash['api_version'] = hash['api_version'].to_s # api_version parsed as a date by http_party
|
8
|
+
new hash
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def count(opts={})
|
13
|
+
opts = prepare_dates opts
|
14
|
+
params = "?#{URI.encode(opts.join '&')}" unless opts.empty?
|
15
|
+
|
16
|
+
get("/Accounts/#{Twilio::ACCOUNT_SID}/#{resource_fragment}.json#{params}").parsed_response['total']
|
17
|
+
end
|
18
|
+
|
19
|
+
def all(opts={})
|
20
|
+
opts = prepare_dates opts
|
21
|
+
params = "?#{URI.encode(opts.join '&')}" unless opts.empty?
|
22
|
+
|
23
|
+
handle_response get "/Accounts/#{Twilio::ACCOUNT_SID}/#{resource_fragment}.json#{params}"
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def resource_fragment # :nodoc:
|
29
|
+
# All Twilio resources follow a convention, except SMS :(
|
30
|
+
klass_name = name.demodulize
|
31
|
+
resource = klass_name == 'SMS' ? "#{klass_name}/Messages" : klass_name.pluralize
|
32
|
+
end
|
33
|
+
|
34
|
+
def prepare_dates(opts) # :nodoc:
|
35
|
+
opts.map do |k,v|
|
36
|
+
if [:updated_before, :created_before, :updated_after, :created_after].include? k
|
37
|
+
k = k.to_s
|
38
|
+
# Fancy schmancy-ness to handle Twilio <= URI operator for dates
|
39
|
+
comparator = k =~ /before$/ ? '<=' : '>='
|
40
|
+
"Date" << k.gsub(/\_\w+$/, '').capitalize << comparator << v.to_s
|
41
|
+
else
|
42
|
+
"#{k.to_s.camelize}=#{v}"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def handle_response(res) # :nodoc:
|
48
|
+
if (400..599).include? res.code
|
49
|
+
raise Twilio::APIError.new "Error ##{res.parsed_response['code']}: #{res.parsed_response['message']}"
|
50
|
+
else
|
51
|
+
key = name.demodulize == 'SMS' ? 'sms_messages' : name.demodulize.underscore.pluralize
|
52
|
+
res.parsed_response[key].map do |p|
|
53
|
+
p['api_version'] = p['api_version'].to_s # api_version parsed as a date by http_party
|
54
|
+
new p
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Twilio
|
2
|
+
class IncomingPhoneNumber
|
3
|
+
extend Twilio::Finder
|
4
|
+
include Twilio::Resource
|
5
|
+
include Twilio::Persistable
|
6
|
+
include Twilio::Deletable
|
7
|
+
|
8
|
+
%w<friendly_name api_version voice_url voice_method voice_fallback_url voice_fallback_method status_callback status_callback_method sms_url sms_method sms_fallback_url sms_fallback_method voice_caller_id_lookup>.each do |meth|
|
9
|
+
define_method "#{meth}=" do |arg|
|
10
|
+
update_attributes meth => arg
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Twilio
|
2
|
+
class Notification
|
3
|
+
include Twilio::Resource
|
4
|
+
include Twilio::Deletable
|
5
|
+
extend Twilio::Finder
|
6
|
+
|
7
|
+
class << self
|
8
|
+
private
|
9
|
+
def prepare_dates(opts)
|
10
|
+
opts.map do |k,v|
|
11
|
+
if [:created_before, :created_after].include? k
|
12
|
+
k = k.to_s
|
13
|
+
# Fancy schmancy-ness to handle Twilio <= URI operator for dates
|
14
|
+
comparator = k =~ /before$/ ? '<=' : '>='
|
15
|
+
"MessageDate" << comparator << v.to_s
|
16
|
+
else
|
17
|
+
"#{k.to_s.camelize}=#{v}"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Twilio
|
2
|
+
class Participant
|
3
|
+
include Twilio::Resource
|
4
|
+
include Twilio::Deletable
|
5
|
+
|
6
|
+
alias kick! destroy
|
7
|
+
|
8
|
+
def mute!
|
9
|
+
state_guard do
|
10
|
+
update_attributes muted? ? { :muted => false } : { :muted => true }
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def muted?
|
15
|
+
muted
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def path
|
21
|
+
"/Accounts/#{Twilio::ACCOUNT_SID}/Conferences/#{conference_sid}/Participants/#{call_sid}.json"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Twilio
|
2
|
+
module Persistable
|
3
|
+
|
4
|
+
def self.included(base)
|
5
|
+
base.class_eval do
|
6
|
+
class << base
|
7
|
+
def create(attrs={})
|
8
|
+
# All Twilio resources follow a convention, except SMS :(
|
9
|
+
resource = name.demodulize
|
10
|
+
resource = name == 'Twilio::SMS' ? "SMS/Messages" : resource + 's'
|
11
|
+
res = post "/Accounts/#{Twilio::ACCOUNT_SID}/#{resource}.json", :body => Hash[attrs.map { |k,v| [k.to_s.camelize, v]}]
|
12
|
+
if (400..599).include? res.code
|
13
|
+
raise Twilio::APIError.new "Error ##{res.parsed_response['code']}: #{res.parsed_response['message']}"
|
14
|
+
else
|
15
|
+
res.parsed_response['api_version'] = res.parsed_response['api_version'].to_s
|
16
|
+
new res.parsed_response
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
data/lib/twilio/resource.rb
CHANGED
@@ -1,28 +1,49 @@
|
|
1
1
|
require 'active_support/core_ext/string' # Chill! we only use the bits of AS we need!
|
2
|
+
require 'active_support/core_ext/hash'
|
2
3
|
|
3
4
|
module Twilio
|
4
5
|
module Resource
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
6
|
+
def initialize(attrs ={}) #:nodoc:
|
7
|
+
@attributes = attrs.with_indifferent_access
|
8
|
+
end
|
9
|
+
|
9
10
|
def [](key)
|
10
|
-
|
11
|
-
|
11
|
+
attributes[key]
|
12
|
+
end
|
13
|
+
|
14
|
+
def []=(key,value) # :nodoc:
|
15
|
+
attributes[key] = value
|
12
16
|
end
|
13
17
|
|
14
|
-
def
|
15
|
-
|
16
|
-
|
18
|
+
def update_attributes(attrs={})
|
19
|
+
state_guard do
|
20
|
+
handle_response klass.post path, :body => Hash[attrs.map { |k,v| [k.to_s.camelize, v]}]
|
21
|
+
end
|
17
22
|
end
|
18
23
|
|
19
24
|
private
|
25
|
+
def klass
|
26
|
+
self.class == Module ? self : self.class
|
27
|
+
end
|
28
|
+
|
29
|
+
def state_guard
|
30
|
+
if frozen?
|
31
|
+
raise RuntimeError, "#{self.class.name.demodulize} has already been destroyed"
|
32
|
+
else
|
33
|
+
yield
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def path # :nodoc:
|
38
|
+
"/Accounts/#{Twilio::ACCOUNT_SID}/#{klass.name.demodulize.pluralize}/#{self[:sid]}.json"
|
39
|
+
end
|
20
40
|
|
21
41
|
def handle_response(res) # :nodoc:
|
22
|
-
if res.code
|
42
|
+
if (400..599).include? res.code
|
23
43
|
raise Twilio::APIError.new "Error ##{res.parsed_response['code']}: #{res.parsed_response['message']}"
|
24
44
|
else
|
25
|
-
|
45
|
+
res.parsed_response['api_version'] = res.parsed_response['api_version'].to_s
|
46
|
+
@attributes.update(res.parsed_response)
|
26
47
|
end
|
27
48
|
end
|
28
49
|
|
@@ -31,10 +52,10 @@ module Twilio
|
|
31
52
|
if meth =~ /\=$/
|
32
53
|
add_attr_writer meth
|
33
54
|
send meth, args.first
|
34
|
-
elsif meth =~ /^#{meth}
|
55
|
+
elsif meth =~ /^#{meth}\?$/i
|
35
56
|
add_predicate meth
|
36
57
|
send meth
|
37
|
-
elsif
|
58
|
+
elsif attributes.keys.include? meth
|
38
59
|
add_attr_reader meth
|
39
60
|
send meth
|
40
61
|
else
|
@@ -44,7 +65,7 @@ module Twilio
|
|
44
65
|
|
45
66
|
def add_predicate(attribute)
|
46
67
|
metaclass.class_eval do
|
47
|
-
define_method(attribute) { self[
|
68
|
+
define_method(attribute) { self['status'] =~ /^#{attribute.gsub '?', ''}/i ? true : false }
|
48
69
|
end
|
49
70
|
end
|
50
71
|
|
@@ -56,7 +77,7 @@ module Twilio
|
|
56
77
|
|
57
78
|
def add_attr_reader(attribute) #:nodoc
|
58
79
|
metaclass.class_eval do
|
59
|
-
define_method(attribute) { self[attribute
|
80
|
+
define_method(attribute) { self[attribute] } unless respond_to? attribute
|
60
81
|
end
|
61
82
|
end
|
62
83
|
|
@@ -73,20 +94,6 @@ module Twilio
|
|
73
94
|
end
|
74
95
|
|
75
96
|
class << base
|
76
|
-
if instance_of? Class # Don't want this mixed into singleton objects e.g. Twilio::Account
|
77
|
-
def find(id)
|
78
|
-
# All Twilio resources follow a convention, except SMS :(
|
79
|
-
klass_name = name.demodulize
|
80
|
-
resource = klass_name == 'SMS' ? "#{klass_name}/Messages" : klass_name.pluralize
|
81
|
-
res = get "/Accounts/#{Twilio::ACCOUNT_SID}/#{resource}/#{id}.json"
|
82
|
-
new Hash[res.parsed_response.map { |k,v| [k.camelize, v] }] if (200..299).include? res.code
|
83
|
-
end
|
84
|
-
|
85
|
-
def create(attrs={})
|
86
|
-
new(attrs).tap { |c| c.save }
|
87
|
-
end
|
88
|
-
end
|
89
|
-
|
90
97
|
# decorate http methods with authentication
|
91
98
|
%w<post get put delete>.each do |meth|
|
92
99
|
define_method(meth) do |*args| # splatted args necessary hack since <= 1.8.7 does not support optional block args
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Twilio
|
2
|
+
module Sandbox
|
3
|
+
include Twilio::Resource
|
4
|
+
@attributes = {}.with_indifferent_access
|
5
|
+
|
6
|
+
def attributes
|
7
|
+
@attributes.empty? ? reload! : @attributes
|
8
|
+
end
|
9
|
+
|
10
|
+
def reload!
|
11
|
+
handle_response get path
|
12
|
+
end
|
13
|
+
|
14
|
+
%w<voice_url voice_method sms_url sms_method>.each do |meth|
|
15
|
+
define_method "#{meth}=" do |arg|
|
16
|
+
update_attributes meth => arg
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
def path
|
22
|
+
"/Accounts/#{Twilio::ACCOUNT_SID}/Sandbox.json"
|
23
|
+
end
|
24
|
+
extend self
|
25
|
+
end
|
26
|
+
end
|
data/lib/twilio/sms.rb
CHANGED
@@ -1,14 +1,23 @@
|
|
1
1
|
module Twilio
|
2
2
|
class SMS
|
3
3
|
include Twilio::Resource
|
4
|
+
include Twilio::Persistable
|
5
|
+
extend Twilio::Finder
|
4
6
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
7
|
+
class << self
|
8
|
+
private
|
9
|
+
def prepare_dates(opts)
|
10
|
+
opts.map do |k,v|
|
11
|
+
if [:created_before, :created_after, :sent_before, :sent_after].include? k
|
12
|
+
k = k.to_s
|
13
|
+
# Fancy schmancy-ness to handle Twilio <= URI operator for dates
|
14
|
+
comparator = k =~ /before$/ ? '<=' : '>='
|
15
|
+
"DateSent" << comparator << v.to_s
|
16
|
+
else
|
17
|
+
"#{k.to_s.camelize}=#{v}"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
12
21
|
end
|
13
22
|
end
|
14
|
-
end
|
23
|
+
end
|
data/lib/twilio.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
%w<rubygems active_support cgi yajl yajl/json_gem httparty builder>.each { |lib| require lib }
|
2
|
-
require File.join(File.dirname(__FILE__), 'twilio',
|
2
|
+
%w<resource finder persistable deletable>.each { |lib| require File.join(File.dirname(__FILE__), 'twilio', "#{lib}.rb") }
|
3
3
|
|
4
4
|
module Twilio
|
5
5
|
API_ENDPOINT = 'https://api.twilio.com/2010-04-01'
|
@@ -9,10 +9,15 @@ module Twilio
|
|
9
9
|
|
10
10
|
class << self
|
11
11
|
def const_missing(const_name)
|
12
|
-
|
12
|
+
if [:ACCOUNT_SID, :AUTH_TOKEN].include? const_name
|
13
|
+
raise Twilio::ConfigurationError.new "Cannot complete request. Please set #{const_name.to_s.downcase} with Twilio::Config.setup first!"
|
14
|
+
else
|
15
|
+
super
|
16
|
+
end
|
13
17
|
end
|
14
18
|
end
|
15
19
|
end
|
16
20
|
|
17
21
|
Dir[File.join(File.dirname(__FILE__), 'twilio', '*.rb')].each { |lib| require lib }
|
18
22
|
|
23
|
+
require File.join(File.dirname(__FILE__), 'railtie.rb') if Object.const_defined?(:Rails) && Rails.version =~ /^3/
|
metadata
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: twilio-rb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
prerelease:
|
4
|
+
prerelease: true
|
5
5
|
segments:
|
6
|
-
-
|
7
|
-
-
|
8
|
-
version:
|
6
|
+
- 1
|
7
|
+
- 0beta
|
8
|
+
version: 1.0beta
|
9
9
|
platform: ruby
|
10
10
|
authors:
|
11
11
|
- Stevie Graham
|
@@ -13,7 +13,7 @@ autorequire:
|
|
13
13
|
bindir: bin
|
14
14
|
cert_chain: []
|
15
15
|
|
16
|
-
date: 2010-12-
|
16
|
+
date: 2010-12-26 00:00:00 +00:00
|
17
17
|
default_executable:
|
18
18
|
dependencies:
|
19
19
|
- !ruby/object:Gem::Dependency
|
@@ -106,6 +106,21 @@ dependencies:
|
|
106
106
|
version: 2.2.0
|
107
107
|
type: :development
|
108
108
|
version_requirements: *id006
|
109
|
+
- !ruby/object:Gem::Dependency
|
110
|
+
name: mocha
|
111
|
+
prerelease: false
|
112
|
+
requirement: &id007 !ruby/object:Gem::Requirement
|
113
|
+
none: false
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
segments:
|
118
|
+
- 0
|
119
|
+
- 9
|
120
|
+
- 10
|
121
|
+
version: 0.9.10
|
122
|
+
type: :development
|
123
|
+
version_requirements: *id007
|
109
124
|
description: A nice Ruby wrapper for the Twilio REST API
|
110
125
|
email: sjtgraham@mac.com
|
111
126
|
executables: []
|
@@ -116,11 +131,24 @@ extra_rdoc_files: []
|
|
116
131
|
|
117
132
|
files:
|
118
133
|
- README.md
|
134
|
+
- lib/railtie.rb
|
119
135
|
- lib/twilio/account.rb
|
136
|
+
- lib/twilio/available_phone_number.rb
|
120
137
|
- lib/twilio/call.rb
|
138
|
+
- lib/twilio/conference.rb
|
121
139
|
- lib/twilio/config.rb
|
140
|
+
- lib/twilio/deletable.rb
|
141
|
+
- lib/twilio/finder.rb
|
142
|
+
- lib/twilio/incoming_phone_number.rb
|
143
|
+
- lib/twilio/notification.rb
|
144
|
+
- lib/twilio/outgoing_caller_id.rb
|
145
|
+
- lib/twilio/participant.rb
|
146
|
+
- lib/twilio/persistable.rb
|
147
|
+
- lib/twilio/recording.rb
|
122
148
|
- lib/twilio/resource.rb
|
149
|
+
- lib/twilio/sandbox.rb
|
123
150
|
- lib/twilio/sms.rb
|
151
|
+
- lib/twilio/transcription.rb
|
124
152
|
- lib/twilio/twiml.rb
|
125
153
|
- lib/twilio.rb
|
126
154
|
has_rdoc: false
|
@@ -145,11 +173,13 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
145
173
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
146
174
|
none: false
|
147
175
|
requirements:
|
148
|
-
- - "
|
176
|
+
- - ">"
|
149
177
|
- !ruby/object:Gem::Version
|
150
178
|
segments:
|
151
|
-
-
|
152
|
-
|
179
|
+
- 1
|
180
|
+
- 3
|
181
|
+
- 1
|
182
|
+
version: 1.3.1
|
153
183
|
requirements: []
|
154
184
|
|
155
185
|
rubyforge_project:
|