dimiro1-clickatell 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +51 -0
- data/License.txt +21 -0
- data/RDOC_README.txt +33 -0
- data/README.textile +40 -0
- data/bin/sms +62 -0
- data/lib/clickatell.rb +10 -0
- data/lib/clickatell/api.rb +148 -0
- data/lib/clickatell/api/command.rb +31 -0
- data/lib/clickatell/api/command_executor.rb +69 -0
- data/lib/clickatell/api/error.rb +25 -0
- data/lib/clickatell/api/message_status.rb +26 -0
- data/lib/clickatell/response.rb +32 -0
- data/lib/clickatell/utility.rb +6 -0
- data/lib/clickatell/utility/options.rb +110 -0
- data/lib/clickatell/version.rb +13 -0
- data/lib/core-ext/hash.rb +15 -0
- data/spec/api_spec.rb +239 -0
- data/spec/command_executor_spec.rb +75 -0
- data/spec/hash_ext_spec.rb +14 -0
- data/spec/response_spec.rb +48 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +8 -0
- metadata +88 -0
data/History.txt
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
== 0.7.0
|
2
|
+
|
3
|
+
* Added support for sending SMS to multiple recipients.
|
4
|
+
|
5
|
+
== 0.6.0
|
6
|
+
|
7
|
+
* API host can now be customized (John Gibbons)
|
8
|
+
* Fixed bug with the ampersands in messages, they are now escaped properly (John Brice)
|
9
|
+
* Removed reliance on Object#blank? which was causing some compatibility issues (Greg Bell)
|
10
|
+
* Added :callback option support (Greg Bell)
|
11
|
+
* Added new test mode (Pivotal Labs)
|
12
|
+
|
13
|
+
== 0.5.0
|
14
|
+
|
15
|
+
* Added support for mobile originated flag (courtesy Dan Weinand)
|
16
|
+
* Added support for WAP push (experimental, courtesy Zhao Lu)
|
17
|
+
* Updated specs to use Mocha instead of the built-in RSpec mocking
|
18
|
+
* Improved specs in general
|
19
|
+
|
20
|
+
== 0.4.1
|
21
|
+
|
22
|
+
* Custom alphanumeric sender would not always be supported by default unless it was explicitly enabled using the req_feat parameter.
|
23
|
+
|
24
|
+
== 0.4.0
|
25
|
+
|
26
|
+
* Added API debug mode and --debug option to sms utility
|
27
|
+
* Restructured API classes into individual files
|
28
|
+
* Refactored command execution into a separate object (CommandExecutor).
|
29
|
+
* Major refactoring of API module - converted it to a class with API methods implemented as instance methods. Code is much cleaner and Connection class becomes redundant. See updated documentation.
|
30
|
+
|
31
|
+
== 0.3.0
|
32
|
+
|
33
|
+
* Display proper message status, not just the code
|
34
|
+
* Make it possible to specify custom :from number/name using SMS utility
|
35
|
+
* Added support for custom :from number/name when sending a message.
|
36
|
+
* Added support for checking message status to sms utility
|
37
|
+
* sms utility now returns the id of a successfully sent message.
|
38
|
+
* Capture API KEY errors.
|
39
|
+
* Make sure errors are handled when using sms utility to check balance.
|
40
|
+
|
41
|
+
== 0.2.0
|
42
|
+
|
43
|
+
* Added Clickatell API error handling to API and sms utility.
|
44
|
+
* Handle required/optional arguments for sms utility correctly
|
45
|
+
* Added further sms utility usage information to website
|
46
|
+
* Make sure sms utility gracefully handles missing recipient/message
|
47
|
+
* Added balance query support to API and SMS utility (--check-balance).
|
48
|
+
|
49
|
+
== 0.1.0 2007-08-17
|
50
|
+
|
51
|
+
* Initial release.
|
data/License.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
Copyright (c) 2007 Luke Redpath
|
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
|
+
|
data/RDOC_README.txt
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
= Clickatell SMS Library
|
2
|
+
|
3
|
+
To use this gem, you will need sign up for an account at www.clickatell.com. Once you are registered and logged into your account centre, you should add an HTTP API connection to your account. This will give you your API_ID.
|
4
|
+
|
5
|
+
== Basic Usage
|
6
|
+
|
7
|
+
You will need your API_ID as well as your account username and password.
|
8
|
+
|
9
|
+
require 'rubygems'
|
10
|
+
require 'clickatell'
|
11
|
+
|
12
|
+
api = Clickatell::API.authenticate('your_api_id', 'your_username', 'your_password')
|
13
|
+
api.send_message('447771234567', 'Hello from clickatell')
|
14
|
+
|
15
|
+
|
16
|
+
== Command-line SMS Utility
|
17
|
+
|
18
|
+
The Clickatell gem also comes with a command-line utility that will allow you to send an SMS directly from the command-line.
|
19
|
+
|
20
|
+
You will need to create a YAML configuration file in your home directory, in a file called .clickatell that resembles the following:
|
21
|
+
|
22
|
+
# ~/.clickatell
|
23
|
+
api_key: your_api_id
|
24
|
+
username: your_username
|
25
|
+
password: your_password
|
26
|
+
|
27
|
+
You can then use the sms utility to send a message to a single recipient:
|
28
|
+
|
29
|
+
sms 447771234567 'Hello from clickatell'
|
30
|
+
|
31
|
+
Alternatively, you can specify the username and password as a command line option. Run +sms+ without any arguments for a full list of options.
|
32
|
+
|
33
|
+
See http://clickatell.rubyforge.org for further instructions.
|
data/README.textile
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
h1. Clickatell SMS Library
|
2
|
+
|
3
|
+
To use this gem, you will need sign up for an account at www.clickatell.com. Once you are registered and logged into your account centre, you should add an HTTP API connection to your account. This will give you your API_ID.
|
4
|
+
|
5
|
+
h2. Basic Usage
|
6
|
+
|
7
|
+
You will need your API_ID as well as your account username and password.
|
8
|
+
|
9
|
+
require 'rubygems'
|
10
|
+
require 'clickatell'
|
11
|
+
|
12
|
+
api = Clickatell::API.authenticate('your_api_id', 'your_username', 'your_password')
|
13
|
+
api.send_message('447771234567', 'Hello from clickatell')
|
14
|
+
|
15
|
+
To send a message to multiple recipients, simply pass in an array of numbers.
|
16
|
+
|
17
|
+
api.send_message(['447771234567', '447771234568'], 'Hello from clickatell')
|
18
|
+
|
19
|
+
h2. Command-line SMS Utility
|
20
|
+
|
21
|
+
The Clickatell gem also comes with a command-line utility that will allow you to send an SMS directly from the command-line.
|
22
|
+
|
23
|
+
You will need to create a YAML configuration file in your home directory, in a file called .clickatell that resembles the following:
|
24
|
+
|
25
|
+
# ~/.clickatell
|
26
|
+
api_key: your_api_id
|
27
|
+
username: your_username
|
28
|
+
password: your_password
|
29
|
+
|
30
|
+
You can then use the sms utility to send a message to a single recipient:
|
31
|
+
|
32
|
+
sms 447771234567 'Hello from clickatell'
|
33
|
+
|
34
|
+
Alternatively, you can specify the username and password as a command line option. Run +sms+ without any arguments for a full list of options.
|
35
|
+
|
36
|
+
The sms utility also supports multiple, comma-separated recipients (up to 100).
|
37
|
+
|
38
|
+
sms 447771111111,447772222222 "Hello everyone"
|
39
|
+
|
40
|
+
See http://clickatell.rubyforge.org for further instructions.
|
data/bin/sms
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
local_libs = [
|
4
|
+
File.join(File.dirname(__FILE__), *%w[../lib/clickatell]),
|
5
|
+
File.join(File.dirname(__FILE__), *%w[../lib/clickatell/utility])
|
6
|
+
]
|
7
|
+
|
8
|
+
if File.exist?(local_libs.first)
|
9
|
+
local_libs.each { |lib| require lib }
|
10
|
+
else
|
11
|
+
require 'rubygems'
|
12
|
+
require 'clickatell'
|
13
|
+
require 'clickatell/utility'
|
14
|
+
end
|
15
|
+
|
16
|
+
# parse command line options
|
17
|
+
options = Clickatell::Utility::Options.parse(ARGV)
|
18
|
+
|
19
|
+
# authenticate and load the API
|
20
|
+
api = Clickatell::API.authenticate(options.api_key, options.username, options.password)
|
21
|
+
|
22
|
+
begin
|
23
|
+
if options.show_balance
|
24
|
+
puts "Retrieving account balance..."
|
25
|
+
puts "You have #{api.account_balance} credits remaining."
|
26
|
+
exit 0
|
27
|
+
elsif options.show_status
|
28
|
+
puts "Getting status of message ##{options.message_id}."
|
29
|
+
status = api.message_status(options.message_id)
|
30
|
+
puts "Status: #{Clickatell::API::MessageStatus[status]} (##{status})."
|
31
|
+
exit 0
|
32
|
+
else
|
33
|
+
puts "Sending '#{options.message}' to #{[options.recipient].flatten.join(", ")}..."
|
34
|
+
additional_opts = {}
|
35
|
+
additional_opts[:from] = options.from if options.from
|
36
|
+
msg_id = api.send_message(options.recipient, options.message, additional_opts)
|
37
|
+
puts "Message sent successfully (message id: #{[msg_id].flatten.join(", ")})."
|
38
|
+
exit 0
|
39
|
+
end
|
40
|
+
|
41
|
+
rescue Clickatell::API::Error => e
|
42
|
+
case e.code
|
43
|
+
when '001', '002', '003', '005', '108'
|
44
|
+
puts "Authentication failed. Please check your username, password and API key and try again."
|
45
|
+
exit 1
|
46
|
+
when '004'
|
47
|
+
puts "Your account has been frozen. Please contact Clickatell support."
|
48
|
+
exit 1
|
49
|
+
when '007'
|
50
|
+
puts "Requests for this API key are not permitted from this IP address."
|
51
|
+
exit 1
|
52
|
+
else
|
53
|
+
puts "Unexpected error occurred. #{e.message} (error code: #{e.code})."
|
54
|
+
puts "Please contact the author (contact@lukeredpath.co.uk) with the above error."
|
55
|
+
exit 1
|
56
|
+
end
|
57
|
+
|
58
|
+
rescue Timeout::Error
|
59
|
+
puts "The connection the the Clickatell service timed out"
|
60
|
+
puts "Please check your network settings and try again."
|
61
|
+
exit 1
|
62
|
+
end
|
data/lib/clickatell.rb
ADDED
@@ -0,0 +1,148 @@
|
|
1
|
+
module Clickatell
|
2
|
+
# This module provides the core implementation of the Clickatell
|
3
|
+
# HTTP service.
|
4
|
+
class API
|
5
|
+
attr_accessor :auth_options
|
6
|
+
|
7
|
+
class << self
|
8
|
+
# Authenticates using the given credentials and returns an
|
9
|
+
# API instance with the authentication options set to use the
|
10
|
+
# resulting session_id.
|
11
|
+
def authenticate(api_id, username, password)
|
12
|
+
api = self.new
|
13
|
+
session_id = api.authenticate(api_id, username, password)
|
14
|
+
api.auth_options = { :session_id => session_id }
|
15
|
+
api
|
16
|
+
end
|
17
|
+
|
18
|
+
# Set to true to enable debugging (off by default)
|
19
|
+
attr_accessor :debug_mode
|
20
|
+
|
21
|
+
# Enable secure mode (SSL)
|
22
|
+
attr_accessor :secure_mode
|
23
|
+
|
24
|
+
# Allow customizing URL
|
25
|
+
attr_accessor :api_service_host
|
26
|
+
|
27
|
+
# Set to true to test message sending; this will not actually send
|
28
|
+
# messages but will collect sent messages in a testable collection.
|
29
|
+
# (off by default)
|
30
|
+
attr_accessor :test_mode
|
31
|
+
end
|
32
|
+
self.debug_mode = false
|
33
|
+
self.secure_mode = false
|
34
|
+
self.test_mode = false
|
35
|
+
|
36
|
+
# Creates a new API instance using the specified +auth options+.
|
37
|
+
# +auth_options+ is a hash containing either a :session_id or
|
38
|
+
# :username, :password and :api_key.
|
39
|
+
#
|
40
|
+
# Some API calls (authenticate, ping etc.) do not require any
|
41
|
+
# +auth_options+. +auth_options+ can be updated using the accessor methods.
|
42
|
+
def initialize(auth_options={})
|
43
|
+
@auth_options = auth_options
|
44
|
+
end
|
45
|
+
|
46
|
+
# Authenticates using the specified credentials. Returns
|
47
|
+
# a session_id if successful which can be used in subsequent
|
48
|
+
# API calls.
|
49
|
+
def authenticate(api_id, username, password)
|
50
|
+
response = execute_command('auth', 'http',
|
51
|
+
:api_id => api_id,
|
52
|
+
:user => username,
|
53
|
+
:password => password
|
54
|
+
)
|
55
|
+
parse_response(response)['OK']
|
56
|
+
end
|
57
|
+
|
58
|
+
# Pings the service with the specified session_id to keep the
|
59
|
+
# session alive.
|
60
|
+
def ping(session_id)
|
61
|
+
execute_command('ping', 'http', :session_id => session_id)
|
62
|
+
end
|
63
|
+
|
64
|
+
# Sends a message +message_text+ to +recipient+. Recipient
|
65
|
+
# number should have an international dialing prefix and
|
66
|
+
# no leading zeros (unless you have set a default prefix
|
67
|
+
# in your clickatell account centre).
|
68
|
+
#
|
69
|
+
# Additional options:
|
70
|
+
# :from - the from number/name
|
71
|
+
# :set_mobile_originated - mobile originated flag
|
72
|
+
#
|
73
|
+
# Returns a new message ID if successful.
|
74
|
+
def send_message(recipient, message_text, opts={})
|
75
|
+
valid_options = opts.only(:from, :mo, :callback, :cliMsgId)
|
76
|
+
valid_options.merge!(:req_feat => '48') if valid_options[:from]
|
77
|
+
valid_options.merge!(:mo => '1') if opts[:set_mobile_originated]
|
78
|
+
recipient = recipient.join(",")if recipient.is_a?(Array)
|
79
|
+
response = execute_command('sendmsg', 'http',
|
80
|
+
{:to => recipient, :text => message_text}.merge(valid_options)
|
81
|
+
)
|
82
|
+
response = parse_response(response)
|
83
|
+
response.is_a?(Array) ? response.map { |r| r['ID'] } : response['ID']
|
84
|
+
end
|
85
|
+
|
86
|
+
def send_wap_push(recipient, media_url, notification_text='', opts={})
|
87
|
+
valid_options = opts.only(:from)
|
88
|
+
valid_options.merge!(:req_feat => '48') if valid_options[:from]
|
89
|
+
response = execute_command('si_push', 'mms',
|
90
|
+
{:to => recipient, :si_url => media_url, :si_text => notification_text, :si_id => 'foo'}.merge(valid_options)
|
91
|
+
)
|
92
|
+
parse_response(response)['ID']
|
93
|
+
end
|
94
|
+
|
95
|
+
# Returns the status of a message. Use message ID returned
|
96
|
+
# from original send_message call.
|
97
|
+
def message_status(message_id)
|
98
|
+
response = execute_command('querymsg', 'http', :apimsgid => message_id)
|
99
|
+
parse_response(response)['Status']
|
100
|
+
end
|
101
|
+
|
102
|
+
# Returns the number of credits remaining as a float.
|
103
|
+
def account_balance
|
104
|
+
response = execute_command('getbalance', 'http')
|
105
|
+
parse_response(response)['Credit'].to_f
|
106
|
+
end
|
107
|
+
|
108
|
+
def sms_requests
|
109
|
+
@sms_requests ||= []
|
110
|
+
end
|
111
|
+
|
112
|
+
protected
|
113
|
+
def execute_command(command_name, service, parameters={}) #:nodoc:
|
114
|
+
executor = CommandExecutor.new(auth_hash, self.class.secure_mode, self.class.debug_mode, self.class.test_mode)
|
115
|
+
result = executor.execute(command_name, service, parameters)
|
116
|
+
|
117
|
+
(sms_requests << executor.sms_requests).flatten! if self.class.test_mode
|
118
|
+
|
119
|
+
result
|
120
|
+
end
|
121
|
+
|
122
|
+
def parse_response(raw_response) #:nodoc:
|
123
|
+
Clickatell::Response.parse(raw_response)
|
124
|
+
end
|
125
|
+
|
126
|
+
def auth_hash #:nodoc:
|
127
|
+
if @auth_options[:session_id]
|
128
|
+
{ :session_id => @auth_options[:session_id] }
|
129
|
+
elsif @auth_options[:api_id]
|
130
|
+
{ :user => @auth_options[:username],
|
131
|
+
:password => @auth_options[:password],
|
132
|
+
:api_id => @auth_options[:api_key] }
|
133
|
+
else
|
134
|
+
{}
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
%w( api/command
|
142
|
+
api/command_executor
|
143
|
+
api/error
|
144
|
+
api/message_status
|
145
|
+
|
146
|
+
).each do |lib|
|
147
|
+
require File.join(File.dirname(__FILE__), lib)
|
148
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require "cgi"
|
2
|
+
module Clickatell
|
3
|
+
class API
|
4
|
+
|
5
|
+
# Represents a Clickatell HTTP gateway command in the form
|
6
|
+
# of a complete URL (the raw, low-level request).
|
7
|
+
class Command
|
8
|
+
API_SERVICE_HOST = 'api.clickatell.com'
|
9
|
+
|
10
|
+
def initialize(command_name, service = 'http', opts={})
|
11
|
+
@command_name = command_name
|
12
|
+
@service = service
|
13
|
+
@options = { :secure => false }.merge(opts)
|
14
|
+
end
|
15
|
+
|
16
|
+
# Returns a URL for the given parameters (a hash).
|
17
|
+
def with_params(param_hash)
|
18
|
+
param_string = '?' + param_hash.map { |key, value| "#{::CGI.escape(key.to_s)}=#{::CGI.escape(value.to_s)}" }.sort.join('&')
|
19
|
+
return URI.parse(File.join(api_service_uri, @command_name + param_string))
|
20
|
+
end
|
21
|
+
|
22
|
+
protected
|
23
|
+
def api_service_uri
|
24
|
+
protocol = @options[:secure] ? 'https' : 'http'
|
25
|
+
api_service_host = ((Clickatell::API.api_service_host.nil? || Clickatell::API.api_service_host.empty?) ? API_SERVICE_HOST : Clickatell::API.api_service_host)
|
26
|
+
return "#{protocol}://#{api_service_host}/#{@service}/"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'net/https'
|
3
|
+
|
4
|
+
module Clickatell
|
5
|
+
class API
|
6
|
+
|
7
|
+
class FakeHttpResponse
|
8
|
+
def body
|
9
|
+
"test"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
# Used to run commands agains the Clickatell gateway.
|
14
|
+
class CommandExecutor
|
15
|
+
def initialize(authentication_hash, secure=false, debug=false, test_mode=false)
|
16
|
+
@authentication_hash = authentication_hash
|
17
|
+
@debug = debug
|
18
|
+
@secure = secure
|
19
|
+
@test_mode = test_mode
|
20
|
+
|
21
|
+
allow_request_recording if @test_mode
|
22
|
+
end
|
23
|
+
|
24
|
+
def in_test_mode?
|
25
|
+
@test_mode
|
26
|
+
end
|
27
|
+
|
28
|
+
# Builds a command object and sends it using HTTP GET.
|
29
|
+
# Will output URLs as they are requested to stdout when
|
30
|
+
# debugging is enabled.
|
31
|
+
def execute(command_name, service, parameters={})
|
32
|
+
request_uri = command(command_name, service, parameters)
|
33
|
+
puts "[debug] Sending request to #{request_uri}" if @debug
|
34
|
+
get_response(request_uri).first
|
35
|
+
end
|
36
|
+
|
37
|
+
protected
|
38
|
+
def command(command_name, service, parameters) #:nodoc:
|
39
|
+
Command.new(command_name, service, :secure => @secure).with_params(
|
40
|
+
parameters.merge(@authentication_hash)
|
41
|
+
)
|
42
|
+
end
|
43
|
+
|
44
|
+
def get_response(uri)
|
45
|
+
if in_test_mode?
|
46
|
+
sms_requests << uri
|
47
|
+
[FakeHttpResponse.new]
|
48
|
+
else
|
49
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
50
|
+
http.use_ssl = (uri.scheme == 'https')
|
51
|
+
http.start do |http|
|
52
|
+
resp, body = http.get([uri.path, uri.query].join('?'))
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def allow_request_recording
|
60
|
+
class << self
|
61
|
+
define_method :sms_requests do
|
62
|
+
@sms_requests ||= []
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Clickatell
|
2
|
+
class API
|
3
|
+
|
4
|
+
# Clickatell API Error exception.
|
5
|
+
class Error < StandardError
|
6
|
+
attr_reader :code, :message
|
7
|
+
|
8
|
+
def initialize(code, message)
|
9
|
+
@code, @message = code, message
|
10
|
+
end
|
11
|
+
|
12
|
+
# Creates a new Error from a Clickatell HTTP response string
|
13
|
+
# e.g.:
|
14
|
+
#
|
15
|
+
# Error.parse("ERR: 001, Authentication error")
|
16
|
+
# # => #<Clickatell::API::Error code='001' message='Authentication error'>
|
17
|
+
def self.parse(error_string)
|
18
|
+
error_details = error_string.split(':').last.strip
|
19
|
+
code, message = error_details.split(',').map { |s| s.strip }
|
20
|
+
self.new(code, message)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Clickatell
|
2
|
+
class API
|
3
|
+
|
4
|
+
class MessageStatus
|
5
|
+
STATUS_MAP = {
|
6
|
+
1 => 'Message unknown',
|
7
|
+
2 => 'Message queued',
|
8
|
+
3 => 'Delivered to gateway',
|
9
|
+
4 => 'Received by recipient',
|
10
|
+
5 => 'Error with message',
|
11
|
+
6 => 'User cancelled messaged delivery',
|
12
|
+
7 => 'Error delivering message',
|
13
|
+
8 => 'OK',
|
14
|
+
9 => 'Routing error',
|
15
|
+
10 => 'Message expired',
|
16
|
+
11 => 'Message queued for later delivery',
|
17
|
+
12 => 'Out of credit'
|
18
|
+
}
|
19
|
+
|
20
|
+
def self.[](code)
|
21
|
+
STATUS_MAP[code]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
module Clickatell
|
4
|
+
|
5
|
+
# Used to parse HTTP responses returned from Clickatell API calls.
|
6
|
+
class Response
|
7
|
+
|
8
|
+
class << self
|
9
|
+
PARSE_REGEX = /[A-Za-z0-9]+:.*?(?:(?=[A-Za-z0-9]+:)|$)/
|
10
|
+
|
11
|
+
# Returns the HTTP response body data as a hash.
|
12
|
+
def parse(http_response)
|
13
|
+
return { 'OK' => 'session_id' } if API.test_mode
|
14
|
+
|
15
|
+
if http_response.body.scan(/ERR/).any?
|
16
|
+
raise Clickatell::API::Error.parse(http_response.body)
|
17
|
+
end
|
18
|
+
results = http_response.body.split("\n").map do |line|
|
19
|
+
# YAML.load converts integer strings that have leading zeroes into integers
|
20
|
+
# using octal rather than decimal. This isn't what we want, so we'll strip out any
|
21
|
+
# leading zeroes in numbers here.
|
22
|
+
response_fields = line.scan(PARSE_REGEX)
|
23
|
+
response_fields = response_fields.collect { |field| field.gsub(/\b0+(\d+)\b/, '\1') }
|
24
|
+
YAML.load(response_fields.join("\n"))
|
25
|
+
end
|
26
|
+
results.length == 1 ? results.first : results
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
require 'ostruct'
|
3
|
+
|
4
|
+
module Clickatell
|
5
|
+
module Utility
|
6
|
+
class Options #:nodoc:
|
7
|
+
class << self
|
8
|
+
|
9
|
+
def parse(args)
|
10
|
+
@options = self.default_options
|
11
|
+
parser = OptionParser.new do |opts|
|
12
|
+
opts.banner = "Usage: sms [options] recipient(s) message"
|
13
|
+
opts.separator " Recipients can be a comma-separated list, up to 100 max."
|
14
|
+
opts.separator ""
|
15
|
+
opts.separator "Specific options:"
|
16
|
+
|
17
|
+
opts.on('-u', '--username USERNAME',
|
18
|
+
"Specify the clickatell username (overrides ~/.clickatell setting)") do |username|
|
19
|
+
@options.username = username
|
20
|
+
end
|
21
|
+
|
22
|
+
opts.on('-p', '--password PASSWORD',
|
23
|
+
"Specify the clickatell password (overrides ~/.clickatell setting)") do |password|
|
24
|
+
@options.password = password
|
25
|
+
end
|
26
|
+
|
27
|
+
opts.on('-k', '--apikey API_KEY',
|
28
|
+
"Specify the clickatell API key (overrides ~/.clickatell setting)") do |key|
|
29
|
+
@options.api_key = key
|
30
|
+
end
|
31
|
+
|
32
|
+
opts.on('-f', '--from NAME_OR_NUMBER',
|
33
|
+
"Specify the name or number that the SMS will appear from") do |from|
|
34
|
+
@options.from = from
|
35
|
+
end
|
36
|
+
|
37
|
+
opts.on('-b', '--show-balance',
|
38
|
+
"Shows the total number of credits remaining on your account") do
|
39
|
+
@options.show_balance = true
|
40
|
+
end
|
41
|
+
|
42
|
+
opts.on('-s', '--status MESSAGE_ID',
|
43
|
+
"Displays the status of the specified message.") do |message_id|
|
44
|
+
@options.message_id = message_id
|
45
|
+
@options.show_status = true
|
46
|
+
end
|
47
|
+
|
48
|
+
opts.on('-S', '--secure',
|
49
|
+
"Sends request using HTTPS") do
|
50
|
+
Clickatell::API.secure_mode = true
|
51
|
+
end
|
52
|
+
|
53
|
+
opts.on('-d', '--debug') do
|
54
|
+
Clickatell::API.debug_mode = true
|
55
|
+
end
|
56
|
+
|
57
|
+
opts.on_tail('-h', '--help', "Show this message") do
|
58
|
+
puts opts
|
59
|
+
exit
|
60
|
+
end
|
61
|
+
|
62
|
+
opts.on_tail('-v', '--version') do
|
63
|
+
puts "Ruby Clickatell SMS Utility #{Clickatell::VERSION}"
|
64
|
+
exit
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
parser.parse!(args)
|
69
|
+
@options.recipient = ARGV[-2]
|
70
|
+
@options.message = ARGV[-1]
|
71
|
+
|
72
|
+
if (@options.message.nil? || @options.recipient.nil?) && send_message?
|
73
|
+
puts "You must specify a recipient and message!"
|
74
|
+
puts parser
|
75
|
+
exit
|
76
|
+
end
|
77
|
+
|
78
|
+
@options.recipient = @options.recipient.split(",")
|
79
|
+
|
80
|
+
return @options
|
81
|
+
|
82
|
+
rescue OptionParser::MissingArgument => e
|
83
|
+
switch_given = e.message.split(':').last.strip
|
84
|
+
puts "The #{switch_given} option requires an argument."
|
85
|
+
puts parser
|
86
|
+
exit
|
87
|
+
end
|
88
|
+
|
89
|
+
def default_options
|
90
|
+
options = OpenStruct.new
|
91
|
+
config_file = File.open(File.join(ENV['HOME'], '.clickatell'))
|
92
|
+
config = YAML.load(config_file)
|
93
|
+
options.username = config['username']
|
94
|
+
options.password = config['password']
|
95
|
+
options.api_key = config['api_key']
|
96
|
+
options.from = config['from']
|
97
|
+
return options
|
98
|
+
rescue Errno::ENOENT
|
99
|
+
return options
|
100
|
+
end
|
101
|
+
|
102
|
+
def send_message?
|
103
|
+
(@options.show_status.nil? &&
|
104
|
+
@options.show_balance.nil?)
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class Hash
|
2
|
+
# Returns a new hash containing only the keys specified
|
3
|
+
# that exist in the current hash.
|
4
|
+
#
|
5
|
+
# {:a => '1', :b => '2', :c => '3'}.only(:a, :c)
|
6
|
+
# # => {:a => '1', :c => '3'}
|
7
|
+
#
|
8
|
+
# Keys that do not exist in the original hash are ignored.
|
9
|
+
def only(*keys)
|
10
|
+
inject( {} ) do |new_hash, (key, value)|
|
11
|
+
new_hash[key] = value if keys.include?(key)
|
12
|
+
new_hash
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
data/spec/api_spec.rb
ADDED
@@ -0,0 +1,239 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
require File.dirname(__FILE__) + '/../lib/clickatell'
|
3
|
+
|
4
|
+
class FakeHttp
|
5
|
+
def start(&block)
|
6
|
+
yield self
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
module Clickatell
|
11
|
+
|
12
|
+
describe "API Command" do
|
13
|
+
before do
|
14
|
+
@command = API::Command.new('cmdname')
|
15
|
+
end
|
16
|
+
|
17
|
+
after do
|
18
|
+
Clickatell::API.api_service_host = nil
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should return encoded URL for the specified command and parameters" do
|
22
|
+
url = @command.with_params(:param_one => 'abc', :param_two => '123')
|
23
|
+
url.should == URI.parse("http://api.clickatell.com/http/cmdname?param_one=abc¶m_two=123")
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should URL encode any special characters in parameters" do
|
27
|
+
url = @command.with_params(:param_one => 'abc', :param_two => 'hello world & goodbye cruel world <grin>')
|
28
|
+
url.should == URI.parse("http://api.clickatell.com/http/cmdname?param_one=abc¶m_two=hello+world+%26+goodbye+cruel+world+%3Cgrin%3E")
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should use a custom host when constructing command URLs if specified" do
|
32
|
+
Clickatell::API.api_service_host = 'api.clickatell-custom.co.uk'
|
33
|
+
url = @command.with_params(:param_one => 'abc', :param_two => '123')
|
34
|
+
url.should == URI.parse("http://api.clickatell-custom.co.uk/http/cmdname?param_one=abc¶m_two=123")
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should use the default host if specified custom host is nil" do
|
38
|
+
Clickatell::API.api_service_host = nil
|
39
|
+
url = @command.with_params(:param_one => 'abc', :param_two => '123')
|
40
|
+
url.should == URI.parse("http://api.clickatell.com/http/cmdname?param_one=abc¶m_two=123")
|
41
|
+
end
|
42
|
+
|
43
|
+
it "should use the default host if specified custom host is an empty string" do
|
44
|
+
Clickatell::API.api_service_host = ''
|
45
|
+
url = @command.with_params(:param_one => 'abc', :param_two => '123')
|
46
|
+
url.should == URI.parse("http://api.clickatell.com/http/cmdname?param_one=abc¶m_two=123")
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
describe "Secure API Command" do
|
51
|
+
before do
|
52
|
+
@command = API::Command.new('cmdname', 'http', :secure => true)
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should use HTTPS" do
|
56
|
+
url = @command.with_params(:param_one => 'abc', :param_two => '123')
|
57
|
+
url.should == URI.parse("https://api.clickatell.com/http/cmdname?param_one=abc¶m_two=123")
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
describe "Command executor" do
|
62
|
+
it "should create an API command with the given params" do
|
63
|
+
executor = API::CommandExecutor.new(:session_id => '12345')
|
64
|
+
executor.stubs(:get_response).returns([])
|
65
|
+
API::Command.expects(:new).with('cmdname', 'http', :secure => false).returns(command = stub('Command'))
|
66
|
+
command.expects(:with_params).with(:param_one => 'foo', :session_id => '12345').returns(stub_everything('URI'))
|
67
|
+
executor.execute('cmdname', 'http', :param_one => 'foo')
|
68
|
+
end
|
69
|
+
|
70
|
+
it "should send the URI generated by the created command via HTTP get and return the response" do
|
71
|
+
executor = API::CommandExecutor.new(:session_id => '12345')
|
72
|
+
command_uri = URI.parse('http://clickatell.com:8080/path?foo=bar')
|
73
|
+
API::Command.stubs(:new).returns(command = stub('Command', :with_params => command_uri))
|
74
|
+
Net::HTTP.stubs(:new).with(command_uri.host, command_uri.port).returns(http = FakeHttp.new)
|
75
|
+
http.stubs(:use_ssl=)
|
76
|
+
http.stubs(:get).with('/path?foo=bar').returns([response = stub('HTTP Response'), 'response body'])
|
77
|
+
executor.execute('cmdname', 'http').should == response
|
78
|
+
end
|
79
|
+
|
80
|
+
it "should send the command over a secure HTTPS connection if :secure option is set to true" do
|
81
|
+
executor = API::CommandExecutor.new({:session_id => '12345'}, secure = true)
|
82
|
+
Net::HTTP.stubs(:new).returns(http = mock('HTTP'))
|
83
|
+
http.expects(:use_ssl=).with(true)
|
84
|
+
http.stubs(:start).returns([])
|
85
|
+
executor.execute('cmdname', 'http')
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
describe "API" do
|
90
|
+
before do
|
91
|
+
API.debug_mode = false
|
92
|
+
API.secure_mode = false
|
93
|
+
API.test_mode = false
|
94
|
+
|
95
|
+
@executor = mock('command executor')
|
96
|
+
@api = API.new(:session_id => '1234')
|
97
|
+
API::CommandExecutor.stubs(:new).with({:session_id => '1234'}, false, false, false).returns(@executor)
|
98
|
+
end
|
99
|
+
|
100
|
+
it "should use the api_id, username and password to authenticate and return the new session id" do
|
101
|
+
@executor.expects(:execute).with('auth', 'http',
|
102
|
+
:api_id => '1234',
|
103
|
+
:user => 'joebloggs',
|
104
|
+
:password => 'superpass'
|
105
|
+
).returns(response = stub('response'))
|
106
|
+
Response.stubs(:parse).with(response).returns('OK' => 'new_session_id')
|
107
|
+
@api.authenticate('1234', 'joebloggs', 'superpass').should == 'new_session_id'
|
108
|
+
end
|
109
|
+
|
110
|
+
it "should support ping, using the current session_id" do
|
111
|
+
@executor.expects(:execute).with('ping', 'http', :session_id => 'abcdefg').returns(response = stub('response'))
|
112
|
+
@api.ping('abcdefg').should == response
|
113
|
+
end
|
114
|
+
|
115
|
+
it "should support sending messages to a specified number, returning the message id" do
|
116
|
+
@executor.expects(:execute).with('sendmsg', 'http',
|
117
|
+
:to => '4477791234567',
|
118
|
+
:text => 'hello world & goodbye'
|
119
|
+
).returns(response = stub('response'))
|
120
|
+
Response.stubs(:parse).with(response).returns('ID' => 'message_id')
|
121
|
+
@api.send_message('4477791234567', 'hello world & goodbye').should == 'message_id'
|
122
|
+
end
|
123
|
+
|
124
|
+
it "should support sending messages to a multiple numbers, returning the message ids" do
|
125
|
+
@executor.expects(:execute).with('sendmsg', 'http',
|
126
|
+
:to => '4477791234567,447779999999',
|
127
|
+
:text => 'hello world & goodbye'
|
128
|
+
).returns(response = stub('response'))
|
129
|
+
Response.stubs(:parse).with(response).returns([{'ID' => 'message_1_id'}, {'ID' => 'message_2_id'}])
|
130
|
+
@api.send_message(['4477791234567', '447779999999'], 'hello world & goodbye').should == ['message_1_id', 'message_2_id']
|
131
|
+
end
|
132
|
+
|
133
|
+
it "should set the :from parameter and set the :req_feat to 48 when using a custom from string when sending a message" do
|
134
|
+
@executor.expects(:execute).with('sendmsg', 'http', has_entries(:from => 'LUKE', :req_feat => '48')).returns(response = stub('response'))
|
135
|
+
Response.stubs(:parse).with(response).returns('ID' => 'message_id')
|
136
|
+
@api.send_message('4477791234567', 'hello world', :from => 'LUKE')
|
137
|
+
end
|
138
|
+
|
139
|
+
it "should set the :mo flag to 1 when :set_mobile_originated is true when sending a message" do
|
140
|
+
@executor.expects(:execute).with('sendmsg', 'http', has_entry(:mo => '1')).returns(response=mock('response'))
|
141
|
+
Response.stubs(:parse).with(response).returns('ID' => 'message_id')
|
142
|
+
@api.send_message('4477791234567', 'hello world', :set_mobile_originated => true)
|
143
|
+
end
|
144
|
+
|
145
|
+
it "should set the callback flag to the number passed in the options hash" do
|
146
|
+
@executor.expects(:execute).with('sendmsg', 'http', has_entry(:callback => 1)).returns(response=mock('response'))
|
147
|
+
Response.stubs(:parse).with(response).returns('ID' => 'message_id')
|
148
|
+
@api.send_message('4477791234567', 'hello world', :callback => 1)
|
149
|
+
end
|
150
|
+
|
151
|
+
it "should ignore any invalid parameters when sending a message" do
|
152
|
+
@executor.expects(:execute).with('sendmsg', 'http', Not(has_key(:any_old_param))).returns(response = stub('response'))
|
153
|
+
Response.stubs(:parse).returns('ID' => 'foo')
|
154
|
+
@api.send_message('4477791234567', 'hello world', :from => 'LUKE', :any_old_param => 'test')
|
155
|
+
end
|
156
|
+
|
157
|
+
it "should support message status query for a given message id, returning the message status" do
|
158
|
+
@executor.expects(:execute).with('querymsg', 'http', :apimsgid => 'messageid').returns(response = stub('response'))
|
159
|
+
Response.expects(:parse).with(response).returns('ID' => 'message_id', 'Status' => 'message_status')
|
160
|
+
@api.message_status('messageid').should == 'message_status'
|
161
|
+
end
|
162
|
+
|
163
|
+
it "should support balance query, returning number of credits as a float" do
|
164
|
+
@executor.expects(:execute).with('getbalance', 'http', {}).returns(response=mock('response'))
|
165
|
+
Response.stubs(:parse).with(response).returns('Credit' => '10.0')
|
166
|
+
@api.account_balance.should == 10.0
|
167
|
+
end
|
168
|
+
|
169
|
+
it "should raise an API::Error if the response parser raises" do
|
170
|
+
@executor.stubs(:execute)
|
171
|
+
Response.stubs(:parse).raises(Clickatell::API::Error.new('', ''))
|
172
|
+
proc { @api.account_balance }.should raise_error(Clickatell::API::Error)
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
describe API, ' when authenticating' do
|
177
|
+
it "should authenticate to retrieve a session_id and return a new API instance using that session id" do
|
178
|
+
API.stubs(:new).returns(api = mock('api'))
|
179
|
+
api.stubs(:authenticate).with('my_api_key', 'joebloggs', 'mypassword').returns('new_session_id')
|
180
|
+
api.expects(:auth_options=).with(:session_id => 'new_session_id')
|
181
|
+
API.authenticate('my_api_key', 'joebloggs', 'mypassword')
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
describe API, ' with no authentication options set' do
|
186
|
+
it "should build commands with no authentication options" do
|
187
|
+
api = API.new
|
188
|
+
API::CommandExecutor.stubs(:new).with({}, false, false, false).returns(executor = stub('command executor'))
|
189
|
+
executor.stubs(:execute)
|
190
|
+
api.ping('1234')
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
describe API, ' in secure mode' do
|
195
|
+
it "should execute commands securely" do
|
196
|
+
API.secure_mode = true
|
197
|
+
api = API.new
|
198
|
+
API::CommandExecutor.expects(:new).with({}, true, false, false).returns(executor = stub('command executor'))
|
199
|
+
executor.stubs(:execute)
|
200
|
+
api.ping('1234')
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
describe "API Error" do
|
205
|
+
it "should parse http response string to create error" do
|
206
|
+
response_string = "ERR: 001, Authentication error"
|
207
|
+
error = Clickatell::API::Error.parse(response_string)
|
208
|
+
error.code.should == '001'
|
209
|
+
error.message.should == 'Authentication error'
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
describe API, "#test_mode" do
|
214
|
+
before(:each) do
|
215
|
+
API.secure_mode = false
|
216
|
+
API.test_mode = true
|
217
|
+
@api = API.new
|
218
|
+
end
|
219
|
+
|
220
|
+
it "should create a new CommandExecutor with test_mode parameter set to true" do
|
221
|
+
API::CommandExecutor.expects(:new).with({}, false, false, true).once.returns(executor = mock('command executor'))
|
222
|
+
executor.stubs(:execute)
|
223
|
+
executor.stubs(:sms_requests).returns([])
|
224
|
+
@api.ping('1234')
|
225
|
+
end
|
226
|
+
|
227
|
+
it "should record all commands" do
|
228
|
+
@api.ping('1234')
|
229
|
+
@api.sms_requests.should_not be_empty
|
230
|
+
end
|
231
|
+
|
232
|
+
it "should return the recorded commands in a flattened array" do
|
233
|
+
@api.ping('1234')
|
234
|
+
@api.sms_requests.size.should == 1
|
235
|
+
@api.sms_requests.first.should_not be_instance_of(Array)
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
require File.dirname(__FILE__) + '/../lib/clickatell'
|
3
|
+
|
4
|
+
module Clickatell
|
5
|
+
describe "API::CommandExecutor" do
|
6
|
+
it "should have test mode" do
|
7
|
+
executor = API::CommandExecutor.new({}, false, false, :test_mode)
|
8
|
+
executor.should be_in_test_mode
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should default to not test mode" do
|
12
|
+
executor = API::CommandExecutor.new({})
|
13
|
+
executor.should_not be_in_test_mode
|
14
|
+
end
|
15
|
+
|
16
|
+
describe "#execute" do
|
17
|
+
describe "in non-test mode" do
|
18
|
+
before(:each) do
|
19
|
+
@executor = API::CommandExecutor.new({})
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should not record requests" do
|
23
|
+
@executor.should_not respond_to(:sms_requests)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe "in test mode" do
|
28
|
+
before(:each) do
|
29
|
+
@params = {:foo => 1, :bar => 2}
|
30
|
+
@executor = API::CommandExecutor.new(@params, false, false, :test_mode)
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should not make any network calls" do
|
34
|
+
Net::HTTP.expects(:new).never
|
35
|
+
@executor.execute("foo", "http")
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should start with an empty request collection" do
|
39
|
+
@executor.sms_requests.should be_empty
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should record sms requests" do
|
43
|
+
@executor.execute("bar", "http")
|
44
|
+
@executor.sms_requests.should_not be_empty
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should record a request for each call" do
|
48
|
+
@executor.execute("wibble", "http")
|
49
|
+
@executor.sms_requests.size.should == 1
|
50
|
+
@executor.execute("foozle", "http")
|
51
|
+
@executor.sms_requests.size.should == 2
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should return a response that approximates a true Net::HttpResponse" do
|
55
|
+
response = @executor.execute("throat-warbler", "http")
|
56
|
+
response.body.should == "test"
|
57
|
+
end
|
58
|
+
|
59
|
+
describe "each recorded request" do
|
60
|
+
it "should return the command information" do
|
61
|
+
command = "rum_tum_tum"
|
62
|
+
@executor.execute(command, "http")
|
63
|
+
|
64
|
+
uri = @executor.sms_requests.first
|
65
|
+
uri.host.should == "api.clickatell.com"
|
66
|
+
uri.path.should include(command)
|
67
|
+
@params.collect { |k, v| "#{k}=#{v}"}.each do |query_parameter|
|
68
|
+
uri.query.should include(query_parameter)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
require File.dirname(__FILE__) + '/../lib/clickatell'
|
3
|
+
|
4
|
+
describe Hash do
|
5
|
+
it "should return only the keys specified" do
|
6
|
+
hash = {:a => 'foo', :b => 'bar', :c => 'baz'}
|
7
|
+
hash.only(:a, :b).should == {:a => 'foo', :b => 'bar'}
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should return only the keys specified, ignoring keys that do not exist" do
|
11
|
+
hash = {:a => 'foo', :b => 'bar', :c => 'baz'}
|
12
|
+
hash.only(:a, :d).should == {:a => 'foo'}
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
require File.dirname(__FILE__) + '/../lib/clickatell'
|
3
|
+
|
4
|
+
module Clickatell
|
5
|
+
|
6
|
+
describe "Response parser" do
|
7
|
+
before do
|
8
|
+
Clickatell::API::Error.stubs(:parse).returns(Clickatell::API::Error.new('', ''))
|
9
|
+
end
|
10
|
+
|
11
|
+
before(:each) do
|
12
|
+
API.test_mode = false
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should return hash for one-line success response" do
|
16
|
+
Response.parse(stub('response', :body => 'k1: foo k2: bar')).should == {'k1' => 'foo', 'k2' => 'bar'}
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should return array of hashes for multi-line success response" do
|
20
|
+
Response.parse(stub('response', :body => "k1: foo\nk2: bar")).should == [{'k1' => 'foo'}, {'k2' => 'bar'}]
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should raise API::Error if response contains an error message" do
|
24
|
+
proc { Response.parse(stub('response', :body => 'ERR: 001, Authentication failed')) }.should raise_error(Clickatell::API::Error)
|
25
|
+
end
|
26
|
+
|
27
|
+
{
|
28
|
+
'001' => 1, '002' => 2, '003' => 3, '004' => 4,
|
29
|
+
'005' => 5, '006' => 6, '007' => 7, '008' => 8,
|
30
|
+
'009' => 9, '010' => 10, '011' => 11, '012' => 12
|
31
|
+
}.each do |status_str, status_int|
|
32
|
+
it "should parse a message status code of #{status_int} when the response body contains a status code of #{status_str}" do
|
33
|
+
Response.parse(stub('response', :body => "ID: 0d1d7dda17d5a24edf1555dc0b679d0e Status: #{status_str}")).should == {'ID' => '0d1d7dda17d5a24edf1555dc0b679d0e', 'Status' => status_int}
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe "in test mode" do
|
38
|
+
before(:each) do
|
39
|
+
API.test_mode = true
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should return something approximating a session_id" do
|
43
|
+
Response.parse("pretty much anything").should == { 'OK' => 'session_id' }
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
data/spec/spec.opts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--colour
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: dimiro1-clickatell
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.7.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Luke Redpath
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-10-07 00:00:00 -03:00
|
13
|
+
default_executable: sms
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: rspec
|
17
|
+
type: :development
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: "0"
|
24
|
+
version:
|
25
|
+
description:
|
26
|
+
email: luke@lukeredpath.co.uk
|
27
|
+
executables:
|
28
|
+
- sms
|
29
|
+
extensions: []
|
30
|
+
|
31
|
+
extra_rdoc_files:
|
32
|
+
- RDOC_README.txt
|
33
|
+
- History.txt
|
34
|
+
- License.txt
|
35
|
+
files:
|
36
|
+
- History.txt
|
37
|
+
- License.txt
|
38
|
+
- RDOC_README.txt
|
39
|
+
- README.textile
|
40
|
+
- bin/sms
|
41
|
+
- spec/api_spec.rb
|
42
|
+
- spec/command_executor_spec.rb
|
43
|
+
- spec/hash_ext_spec.rb
|
44
|
+
- spec/response_spec.rb
|
45
|
+
- spec/spec.opts
|
46
|
+
- spec/spec_helper.rb
|
47
|
+
- lib/clickatell/api/command.rb
|
48
|
+
- lib/clickatell/api/command_executor.rb
|
49
|
+
- lib/clickatell/api/error.rb
|
50
|
+
- lib/clickatell/api/message_status.rb
|
51
|
+
- lib/clickatell/api.rb
|
52
|
+
- lib/clickatell/response.rb
|
53
|
+
- lib/clickatell/utility/options.rb
|
54
|
+
- lib/clickatell/utility.rb
|
55
|
+
- lib/clickatell/version.rb
|
56
|
+
- lib/clickatell.rb
|
57
|
+
- lib/core-ext/hash.rb
|
58
|
+
has_rdoc: true
|
59
|
+
homepage: http://clickatell.rubyforge.org
|
60
|
+
licenses: []
|
61
|
+
|
62
|
+
post_install_message:
|
63
|
+
rdoc_options:
|
64
|
+
- --main
|
65
|
+
- RDOC_README.txt
|
66
|
+
require_paths:
|
67
|
+
- lib
|
68
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
69
|
+
requirements:
|
70
|
+
- - ">="
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
version: "0"
|
73
|
+
version:
|
74
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
75
|
+
requirements:
|
76
|
+
- - ">="
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
version: "0"
|
79
|
+
version:
|
80
|
+
requirements: []
|
81
|
+
|
82
|
+
rubyforge_project: clickatell
|
83
|
+
rubygems_version: 1.3.5
|
84
|
+
signing_key:
|
85
|
+
specification_version: 2
|
86
|
+
summary: Ruby interface to the Clickatell SMS gateway service.
|
87
|
+
test_files: []
|
88
|
+
|