sms-rb 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.md +24 -0
- data/bin/sms +32 -0
- data/lib/sms.rb +27 -0
- data/lib/sms/.gitignore +0 -0
- data/lib/sms/twiliolib.rb +392 -0
- metadata +71 -0
data/README.md
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# sms-rb
|
2
|
+
|
3
|
+
(c) Copyright 2010 Pat Nakajima
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person
|
6
|
+
obtaining a copy of this software and associated documentation
|
7
|
+
files (the "Software"), to deal in the Software without
|
8
|
+
restriction, including without limitation the rights to use,
|
9
|
+
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
10
|
+
copies of the Software, and to permit persons to whom the
|
11
|
+
Software is furnished to do so, subject to the following
|
12
|
+
conditions:
|
13
|
+
|
14
|
+
The above copyright notice and this permission notice shall be
|
15
|
+
included in all copies or substantial portions of the Software.
|
16
|
+
|
17
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
18
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
19
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
20
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
21
|
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
22
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
23
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
24
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
data/bin/sms
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require File.join(File.dirname(__FILE__), '..', 'lib', 'sms')
|
4
|
+
require 'optparse'
|
5
|
+
|
6
|
+
from = to = nil
|
7
|
+
OptionParser.new do |opts|
|
8
|
+
opts.banner = 'Usage: sms "some message" [options]'
|
9
|
+
opts.separator "Specific options:"
|
10
|
+
opts.on('-t', '--to [PHONE NUMBER]', "Recipient's number") { |v| to ||= v }
|
11
|
+
opts.on('-f', '--from [PHONE NUMBER]', "Your Twilio number") { |v| from ||= v }
|
12
|
+
end.parse!
|
13
|
+
|
14
|
+
ENV['TWILIO_ID'] || abort("Please set ENV['TWILIO_ID'] to your Twilio API id.")
|
15
|
+
ENV['TWILIO_SECRET'] || abort("Please set ENV['TWILIO_SECRET] to your Twilio API secret key.")
|
16
|
+
|
17
|
+
if from.nil? and ENV['TWILIO_PHONE'].nil?
|
18
|
+
abort <<-ERR
|
19
|
+
Twilio number blank.
|
20
|
+
Please set ENV['TWILIO_PHONE'] or pass the --from option.
|
21
|
+
ERR
|
22
|
+
end
|
23
|
+
|
24
|
+
if to.nil?
|
25
|
+
abort 'Recipient number blank.'
|
26
|
+
end
|
27
|
+
|
28
|
+
begin
|
29
|
+
SMS.text! ARGV.join(' '), :from => from, :to => to
|
30
|
+
rescue => e
|
31
|
+
abort('Failure: ' + e.message)
|
32
|
+
end
|
data/lib/sms.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require File.join(File.dirname(__FILE__), 'sms', 'twiliolib')
|
3
|
+
|
4
|
+
module SMS
|
5
|
+
TWILIO_SEND = "/2008-08-01/Accounts/#{ENV['TWILIO_ID']}/SMS/Messages"
|
6
|
+
|
7
|
+
def self.text(message, options={})
|
8
|
+
twilio = Twilio::RestAccount.new ENV['TWILIO_ID'], ENV['TWILIO_SECRET']
|
9
|
+
res = twilio.request TWILIO_SEND, 'POST',
|
10
|
+
'To' => options[:to],
|
11
|
+
'From' => options[:from] || ENV['TWILIO_PHONE'],
|
12
|
+
'Body' => message
|
13
|
+
if res.code == 201
|
14
|
+
true
|
15
|
+
else
|
16
|
+
if options[:raise]
|
17
|
+
raise(res.body.match(/<Message>(Message body is required)<\/Message>/)[1])
|
18
|
+
else
|
19
|
+
false
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.text!(message, options={})
|
25
|
+
text(message, options.merge(:raise => true))
|
26
|
+
end
|
27
|
+
end
|
data/lib/sms/.gitignore
ADDED
File without changes
|
@@ -0,0 +1,392 @@
|
|
1
|
+
=begin
|
2
|
+
Copyright (c) 2009 Twilio, Inc.
|
3
|
+
|
4
|
+
Permission is hereby granted, free of charge, to any person
|
5
|
+
obtaining a copy of this software and associated documentation
|
6
|
+
files (the "Software"), to deal in the Software without
|
7
|
+
restriction, including without limitation the rights to use,
|
8
|
+
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the
|
10
|
+
Software is furnished to do so, subject to the following
|
11
|
+
conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
18
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
20
|
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
21
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
22
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
23
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
24
|
+
=end
|
25
|
+
|
26
|
+
# @author Twilio
|
27
|
+
module Twilio
|
28
|
+
require 'net/http'
|
29
|
+
require 'net/https'
|
30
|
+
require 'uri'
|
31
|
+
require 'cgi'
|
32
|
+
require 'rubygems'
|
33
|
+
require 'builder'
|
34
|
+
require 'openssl'
|
35
|
+
require 'base64'
|
36
|
+
|
37
|
+
TWILIO_API_URL = 'https://api.twilio.com'
|
38
|
+
|
39
|
+
# Twilio REST Helpers
|
40
|
+
class RestAccount
|
41
|
+
|
42
|
+
#initialize a twilio account object
|
43
|
+
#
|
44
|
+
#@param [String, String] Your Twilio Acount SID/ID and Auth Token
|
45
|
+
#@return [Object] Twilio account object
|
46
|
+
def initialize(id, token)
|
47
|
+
@id = id
|
48
|
+
@token = token
|
49
|
+
end
|
50
|
+
|
51
|
+
#sends a request and gets a response from the Twilio REST API
|
52
|
+
#
|
53
|
+
#@param [String, String, Hash]
|
54
|
+
#path, the URL (relative to the endpoint URL, after the /v1
|
55
|
+
#method, the HTTP method to use, defaults to POST
|
56
|
+
#vars, for POST or PUT, a dict of data to send
|
57
|
+
#
|
58
|
+
#@return Twilio response XML
|
59
|
+
#@raises [ArgumentError] Invalid path parameter
|
60
|
+
#@raises [NotImplementedError] Method given is not implemented
|
61
|
+
def request(path, method=nil, vars={})
|
62
|
+
if !path || path.length < 1
|
63
|
+
raise ArgumentError, 'Invalid path parameter'
|
64
|
+
end
|
65
|
+
if method && !['GET', 'POST', 'DELETE', 'PUT'].include?(method)
|
66
|
+
raise NotImplementedError, 'HTTP %s not implemented' % method
|
67
|
+
end
|
68
|
+
|
69
|
+
if path[0, 1] == '/'
|
70
|
+
uri = TWILIO_API_URL + path
|
71
|
+
else
|
72
|
+
uri = TWILIO_API_URL + '/' + path
|
73
|
+
end
|
74
|
+
|
75
|
+
return fetch(uri, vars, method)
|
76
|
+
end
|
77
|
+
|
78
|
+
#enocde the parameters into a URL friendly string
|
79
|
+
#
|
80
|
+
#@param [Hash] URL key / values
|
81
|
+
#@return [String] Encoded URL
|
82
|
+
protected
|
83
|
+
def urlencode(params)
|
84
|
+
params.to_a.collect! \
|
85
|
+
{ |k, v| "#{k}=#{CGI.escape(v.to_s)}" }.join("&")
|
86
|
+
end
|
87
|
+
|
88
|
+
# Create the uri for the REST call
|
89
|
+
#
|
90
|
+
#@param [String, Hash] Base URL and URL parameters
|
91
|
+
#@return [String] URI for the REST call
|
92
|
+
def build_get_uri(uri, params)
|
93
|
+
if params && params.length > 0
|
94
|
+
if uri.include?('?')
|
95
|
+
if uri[-1, 1] != '&'
|
96
|
+
uri += '&'
|
97
|
+
end
|
98
|
+
uri += urlencode(params)
|
99
|
+
else
|
100
|
+
uri += '?' + urlencode(params)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
return uri
|
104
|
+
end
|
105
|
+
|
106
|
+
# Returns a http request for the given url and parameters
|
107
|
+
#
|
108
|
+
#@param [String, Hash, String] Base URL, URL parameters, optional METHOD
|
109
|
+
#@return [String] URI for the REST call
|
110
|
+
def fetch(url, params, method=nil)
|
111
|
+
if method && method == 'GET'
|
112
|
+
url = build_get_uri(url, params)
|
113
|
+
end
|
114
|
+
uri = URI.parse(url)
|
115
|
+
|
116
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
117
|
+
http.use_ssl = true
|
118
|
+
|
119
|
+
if method && method == 'GET'
|
120
|
+
req = Net::HTTP::Get.new(uri.request_uri)
|
121
|
+
elsif method && method == 'DELETE'
|
122
|
+
req = Net::HTTP::Delete.new(uri.request_uri)
|
123
|
+
elsif method && method == 'PUT'
|
124
|
+
req = Net::HTTP::Put.new(uri.request_uri)
|
125
|
+
req.set_form_data(params)
|
126
|
+
else
|
127
|
+
http.use_ssl = true
|
128
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
129
|
+
req = Net::HTTP::Post.new(uri.request_uri)
|
130
|
+
req.set_form_data(params)
|
131
|
+
end
|
132
|
+
req.basic_auth(@id, @token)
|
133
|
+
|
134
|
+
return http.request(req)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
# Twiml Response Helpers
|
139
|
+
module Verb
|
140
|
+
module ClassMethods
|
141
|
+
@attributes = []
|
142
|
+
@allowed_verbs = []
|
143
|
+
attr_accessor :attributes
|
144
|
+
|
145
|
+
def allowed_verbs(*verbs)
|
146
|
+
return @allowed_verbs if verbs == []
|
147
|
+
@allowed_verbs = [] if @allowed_verbs.nil?
|
148
|
+
verbs.each do |verb|
|
149
|
+
@allowed_verbs << verb.to_s.capitalize
|
150
|
+
end
|
151
|
+
@allowed_verbs = @allowed_verbs.uniq
|
152
|
+
end
|
153
|
+
|
154
|
+
def attributes(*attrs)
|
155
|
+
return @attributes if attrs == []
|
156
|
+
@attributes = [] if @attributes.nil?
|
157
|
+
@attributes = (@attributes + attrs).uniq
|
158
|
+
attr_accessor(*@attributes)
|
159
|
+
@attributes
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
def attributes
|
164
|
+
self.class.attributes
|
165
|
+
end
|
166
|
+
|
167
|
+
#test if a given verb is allowed to be nested
|
168
|
+
#
|
169
|
+
#@param [Object] Verb to be appended
|
170
|
+
#@return [true, false]
|
171
|
+
def allowed?(verb)
|
172
|
+
self.class.allowed_verbs.nil? ? false : self.class.allowed_verbs.include?(verb.class.name.split('::')[1])
|
173
|
+
end
|
174
|
+
|
175
|
+
#initialize a twilio response object
|
176
|
+
#
|
177
|
+
#@param [String, Hash] Body of the verb, and a hash of the attributes
|
178
|
+
#@return [Object] Twilio Verb object
|
179
|
+
#
|
180
|
+
#@raises [ArgumentError] Invalid Argument
|
181
|
+
def initialize(body = nil, params = {})
|
182
|
+
@children = []
|
183
|
+
if body.class == String
|
184
|
+
@body = body
|
185
|
+
else
|
186
|
+
@body = nil
|
187
|
+
params = body || {}
|
188
|
+
end
|
189
|
+
params.each do |k,v|
|
190
|
+
if !self.class.attributes.nil? && self.class.attributes.include?(k)
|
191
|
+
send(k.to_s+"=",v)
|
192
|
+
else
|
193
|
+
raise ArgumentError, "Attribute Not Supported"
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
#set an attribute key / value
|
199
|
+
#no error checking
|
200
|
+
#
|
201
|
+
#@param [Hash] Hash of options
|
202
|
+
#@return void
|
203
|
+
def set(params = {})
|
204
|
+
params.each do |k,v|
|
205
|
+
self.class.attributes k.to_s
|
206
|
+
send(k.to_s+"=",v)
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
#output valid Twilio markup
|
211
|
+
#
|
212
|
+
#@param [Hash] Hash of options
|
213
|
+
#@return [String] Twilio Markup (in XML)
|
214
|
+
def respond(opts = {})
|
215
|
+
opts[:builder] ||= Builder::XmlMarkup.new(:indent => opts[:indent])
|
216
|
+
b = opts[:builder]
|
217
|
+
attrs = {}
|
218
|
+
attributes.each {|a| attrs[a] = send(a) unless send(a).nil? } unless attributes.nil?
|
219
|
+
|
220
|
+
if @children and @body.nil?
|
221
|
+
b.__send__(self.class.to_s.split(/::/)[-1], attrs) do
|
222
|
+
@children.each {|e|e.respond( opts.merge(:skip_instruct => true) )}
|
223
|
+
end
|
224
|
+
elsif @body and @children == []
|
225
|
+
b.__send__(self.class.to_s.split(/::/)[-1], @body, attrs)
|
226
|
+
else
|
227
|
+
raise ArgumentError, "Cannot have children and a body at the same time"
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
#output valid Twilio markup encoded for inclusion in a URL
|
232
|
+
#
|
233
|
+
#@param []
|
234
|
+
#@return [String] URL encoded Twilio Markup (XML)
|
235
|
+
def asURL()
|
236
|
+
CGI::escape(self.respond)
|
237
|
+
end
|
238
|
+
|
239
|
+
def append(verb)
|
240
|
+
if(allowed?(verb))
|
241
|
+
@children << verb
|
242
|
+
@children[-1]
|
243
|
+
else
|
244
|
+
raise ArgumentError, "Verb Not Supported"
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
# Verb Convenience Methods
|
249
|
+
def addSay(string_to_say = nil, opts = {})
|
250
|
+
append Twilio::Say.new(string_to_say, opts)
|
251
|
+
end
|
252
|
+
|
253
|
+
def addPlay(file_to_play = nil, opts = {})
|
254
|
+
append Twilio::Play.new(file_to_play, opts)
|
255
|
+
end
|
256
|
+
|
257
|
+
def addGather(opts = {})
|
258
|
+
append Twilio::Gather.new(opts)
|
259
|
+
end
|
260
|
+
|
261
|
+
def addRecord(opts = {})
|
262
|
+
append Twilio::Record.new(opts)
|
263
|
+
end
|
264
|
+
|
265
|
+
def addDial(number = nil, opts = {})
|
266
|
+
append Twilio::Dial.new(number, opts)
|
267
|
+
end
|
268
|
+
|
269
|
+
def addRedirect(url = nil, opts = {})
|
270
|
+
append Twilio::Redirect.new(url, opts)
|
271
|
+
end
|
272
|
+
|
273
|
+
def addPause(opts = {})
|
274
|
+
append Twilio::Pause.new(opts)
|
275
|
+
end
|
276
|
+
|
277
|
+
def addHangup
|
278
|
+
append Twilio::Hangup.new
|
279
|
+
end
|
280
|
+
|
281
|
+
def addNumber(number, opts = {})
|
282
|
+
append Twilio::Number.new(number, opts)
|
283
|
+
end
|
284
|
+
|
285
|
+
def addConference(room, opts = {})
|
286
|
+
append Twilio::Conference.new(room, opts)
|
287
|
+
end
|
288
|
+
|
289
|
+
def addSms(msg, opts = {})
|
290
|
+
append Twilio::Sms.new(msg, opts)
|
291
|
+
end
|
292
|
+
|
293
|
+
end
|
294
|
+
|
295
|
+
class Say
|
296
|
+
extend Twilio::Verb::ClassMethods
|
297
|
+
include Twilio::Verb
|
298
|
+
attributes :voice, :language, :loop
|
299
|
+
end
|
300
|
+
|
301
|
+
class Play
|
302
|
+
extend Twilio::Verb::ClassMethods
|
303
|
+
include Twilio::Verb
|
304
|
+
attributes :loop
|
305
|
+
end
|
306
|
+
|
307
|
+
class Gather
|
308
|
+
extend Twilio::Verb::ClassMethods
|
309
|
+
include Twilio::Verb
|
310
|
+
attributes :action, :method, :timeout, :finishOnKey, :numDigits
|
311
|
+
allowed_verbs :play, :say, :pause
|
312
|
+
end
|
313
|
+
|
314
|
+
class Record
|
315
|
+
extend Twilio::Verb::ClassMethods
|
316
|
+
include Twilio::Verb
|
317
|
+
attributes :action, :method, :timeout, :finishOnKey, :maxLength, :transcribe, :transcribeCallback
|
318
|
+
end
|
319
|
+
|
320
|
+
class Dial
|
321
|
+
extend Twilio::Verb::ClassMethods
|
322
|
+
include Twilio::Verb
|
323
|
+
attributes :action, :method, :timeout, :hangupOnStar, :timeLimit, :callerId
|
324
|
+
allowed_verbs :number, :conference
|
325
|
+
end
|
326
|
+
|
327
|
+
class Redirect
|
328
|
+
extend Twilio::Verb::ClassMethods
|
329
|
+
include Twilio::Verb
|
330
|
+
attributes :method
|
331
|
+
end
|
332
|
+
|
333
|
+
class Pause
|
334
|
+
extend Twilio::Verb::ClassMethods
|
335
|
+
include Twilio::Verb
|
336
|
+
attributes :length
|
337
|
+
end
|
338
|
+
|
339
|
+
class Hangup
|
340
|
+
extend Twilio::Verb::ClassMethods
|
341
|
+
include Twilio::Verb
|
342
|
+
end
|
343
|
+
|
344
|
+
class Number
|
345
|
+
extend Twilio::Verb::ClassMethods
|
346
|
+
include Twilio::Verb
|
347
|
+
attributes :sendDigits, :url
|
348
|
+
end
|
349
|
+
|
350
|
+
class Conference
|
351
|
+
extend Twilio::Verb::ClassMethods
|
352
|
+
include Twilio::Verb
|
353
|
+
attributes :muted, :beep, :startConferenceOnEnter, :endConferenceOnExit, :waitUrl, :waitMethod
|
354
|
+
end
|
355
|
+
|
356
|
+
class Sms
|
357
|
+
extend Twilio::Verb::ClassMethods
|
358
|
+
include Twilio::Verb
|
359
|
+
attributes :to, :from, :statusCallback, :action, :method
|
360
|
+
end
|
361
|
+
|
362
|
+
class Response
|
363
|
+
extend Twilio::Verb::ClassMethods
|
364
|
+
include Twilio::Verb
|
365
|
+
allowed_verbs :say, :play, :gather, :record, :dial, :redirect, :pause, :hangup, :sms
|
366
|
+
end
|
367
|
+
|
368
|
+
# Twilio Utility function and Request Validation class
|
369
|
+
class Utils
|
370
|
+
|
371
|
+
#initialize a twilio utils abject
|
372
|
+
#
|
373
|
+
#@param [String, String] Your Twilio Acount SID/ID and Auth Token
|
374
|
+
#@return [Object] Twilio account object
|
375
|
+
def initialize(id, token)
|
376
|
+
@id = id
|
377
|
+
@token = token
|
378
|
+
end
|
379
|
+
|
380
|
+
def validateRequest(signature, url, params = {})
|
381
|
+
sorted_post_params = params.sort
|
382
|
+
data = url
|
383
|
+
sorted_post_params.each do |pkey|
|
384
|
+
data = data + pkey[0]+pkey[1]
|
385
|
+
end
|
386
|
+
digest = OpenSSL::Digest::Digest.new('sha1')
|
387
|
+
expected = Base64.encode64(OpenSSL::HMAC.digest(digest, @token, data)).strip
|
388
|
+
return expected == signature
|
389
|
+
end
|
390
|
+
end
|
391
|
+
|
392
|
+
end
|
metadata
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: sms-rb
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 29
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 0.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Pat Nakajima
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2010-07-29 00:00:00 -04:00
|
19
|
+
default_executable: sms
|
20
|
+
dependencies: []
|
21
|
+
|
22
|
+
description:
|
23
|
+
email: patnakajima@gmail.com
|
24
|
+
executables:
|
25
|
+
- sms
|
26
|
+
extensions: []
|
27
|
+
|
28
|
+
extra_rdoc_files: []
|
29
|
+
|
30
|
+
files:
|
31
|
+
- README.md
|
32
|
+
- bin/sms
|
33
|
+
- lib/sms.rb
|
34
|
+
- lib/sms/.gitignore
|
35
|
+
- lib/sms/twiliolib.rb
|
36
|
+
has_rdoc: true
|
37
|
+
homepage:
|
38
|
+
licenses: []
|
39
|
+
|
40
|
+
post_install_message:
|
41
|
+
rdoc_options: []
|
42
|
+
|
43
|
+
require_paths:
|
44
|
+
- lib
|
45
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
46
|
+
none: false
|
47
|
+
requirements:
|
48
|
+
- - ">="
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
hash: 3
|
51
|
+
segments:
|
52
|
+
- 0
|
53
|
+
version: "0"
|
54
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
55
|
+
none: false
|
56
|
+
requirements:
|
57
|
+
- - ">="
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
hash: 3
|
60
|
+
segments:
|
61
|
+
- 0
|
62
|
+
version: "0"
|
63
|
+
requirements: []
|
64
|
+
|
65
|
+
rubyforge_project:
|
66
|
+
rubygems_version: 1.3.7
|
67
|
+
signing_key:
|
68
|
+
specification_version: 3
|
69
|
+
summary: Send text messages with Twilio.
|
70
|
+
test_files: []
|
71
|
+
|