mote_sms 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +18 -0
- data/.travis.yml +6 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +59 -0
- data/Rakefile +6 -0
- data/lib/mote_sms/message.rb +102 -0
- data/lib/mote_sms/number.rb +53 -0
- data/lib/mote_sms/number_list.rb +84 -0
- data/lib/mote_sms/transports/.gitkeep +0 -0
- data/lib/mote_sms/transports/mobile_technics_transport.rb +185 -0
- data/lib/mote_sms/transports/test_transport.rb +40 -0
- data/lib/mote_sms/transports.rb +7 -0
- data/lib/mote_sms/version.rb +3 -0
- data/lib/mote_sms.rb +45 -0
- data/mote_sms.gemspec +27 -0
- data/spec/mote_sms/message_spec.rb +60 -0
- data/spec/mote_sms/number_list_spec.rb +33 -0
- data/spec/mote_sms/number_spec.rb +69 -0
- data/spec/mote_sms/transports/mobile_technics_transport_spec.rb +124 -0
- data/spec/mote_sms/transports/test_transport_spec.rb +17 -0
- data/spec/mote_sms_spec.rb +37 -0
- data/spec/spec_helper.rb +5 -0
- metadata +144 -0
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 at-point ag, Lukas Westermann, http://at-point.ch/
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following 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 OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
# MobileTechnics SMS API Client
|
2
|
+
|
3
|
+
Unofficial ruby adapter for MobileTechnics HTTP Bulk SMS API. Tries to mimick
|
4
|
+
mail API, so users can switch e.g. ActionMailer with this SMS provider. Requires
|
5
|
+
Ruby 1.9.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
gem 'mote_sms'
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install mote_sms
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
```ruby
|
24
|
+
# Quick and dirty
|
25
|
+
MoteSMS.transport = MoteSMS::MobileTechnicsTransport.new 'https://endpoint.com:1234', 'username', 'password'
|
26
|
+
MoteSMS.deliver do
|
27
|
+
body 'Quick hello world'
|
28
|
+
to '+41 79 111 22 33'
|
29
|
+
from 'ARUBYGEM'
|
30
|
+
end
|
31
|
+
```
|
32
|
+
|
33
|
+
```ruby
|
34
|
+
# Using global transport
|
35
|
+
MoteSMS.transport = MoteSMS::MobileTechnicsTransport.new 'https://endpoint.com:1234', 'username', 'password'
|
36
|
+
sms = MoteSMS.Message.new do
|
37
|
+
to '+41 79 111 22 33'
|
38
|
+
from 'ARUBYGEM'
|
39
|
+
body 'Hello world, you know.'
|
40
|
+
end
|
41
|
+
sms.deliver
|
42
|
+
```
|
43
|
+
|
44
|
+
```ruby
|
45
|
+
# Using client instance
|
46
|
+
transport = MoteSMS::MobileTechnicsTransport.new 'https://endpoint.com:1234', 'username', 'password'
|
47
|
+
sms = MoteSMS.Message.new do
|
48
|
+
# create message
|
49
|
+
end
|
50
|
+
sms.deliver(transport: transport)
|
51
|
+
```
|
52
|
+
|
53
|
+
## Contributing
|
54
|
+
|
55
|
+
1. Fork it
|
56
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
57
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
58
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
59
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
require 'mote_sms/number'
|
2
|
+
require 'mote_sms/number_list'
|
3
|
+
|
4
|
+
module MoteSMS
|
5
|
+
|
6
|
+
# Represents an SMS message, currently only provides the
|
7
|
+
# tools to build new messages, not parse incoming messages or
|
8
|
+
# similar stuff.
|
9
|
+
#
|
10
|
+
class Message
|
11
|
+
|
12
|
+
# The transport instance to use, if not defined
|
13
|
+
# falls back to use global MoteSMS.transport instance.
|
14
|
+
attr_accessor :transport
|
15
|
+
|
16
|
+
# Public: Create a new SMS message instance.
|
17
|
+
#
|
18
|
+
# Examples:
|
19
|
+
#
|
20
|
+
# sms = MoteSMS::Message.new do
|
21
|
+
# from '41791112233'
|
22
|
+
# to '41797776655'
|
23
|
+
# body 'Hi there.'
|
24
|
+
# end
|
25
|
+
# sms.from # => '41791112233'
|
26
|
+
# sms.to # => ['41797776655']
|
27
|
+
# sms.body # => 'Hi there.'
|
28
|
+
#
|
29
|
+
# Returns a new instance.
|
30
|
+
def initialize(transport = nil, &block)
|
31
|
+
@transport = transport
|
32
|
+
@to = MoteSMS::NumberList.new
|
33
|
+
instance_eval(&block) if block_given?
|
34
|
+
end
|
35
|
+
|
36
|
+
# Public: Returns current SMS message body, which should
|
37
|
+
# be something stringish.
|
38
|
+
#
|
39
|
+
# Returns value of body.
|
40
|
+
attr_writer :body
|
41
|
+
def body(val = nil)
|
42
|
+
@body = val if val
|
43
|
+
@body
|
44
|
+
end
|
45
|
+
|
46
|
+
# Public: Returns string of sender, the sender should
|
47
|
+
# either be 11 alphanumeric characters or 20 numbers.
|
48
|
+
#
|
49
|
+
# Examples:
|
50
|
+
#
|
51
|
+
# sms.from = '41791231234'
|
52
|
+
# sms.from # => '41791231234'
|
53
|
+
#
|
54
|
+
# Returns value of sender.
|
55
|
+
def from(val = nil)
|
56
|
+
self.from = val if val
|
57
|
+
@from
|
58
|
+
end
|
59
|
+
|
60
|
+
# Public: Asign an instance of Number instead of the direct
|
61
|
+
# string, so only vanity numbers are allowed.
|
62
|
+
def from=(val)
|
63
|
+
@from = val ? Number.new(val, :vanity => true) : nil
|
64
|
+
end
|
65
|
+
|
66
|
+
# Public: Set to multiple arguments or array, or whatever.
|
67
|
+
#
|
68
|
+
# Examples:
|
69
|
+
#
|
70
|
+
# sms.to = '41791231212'
|
71
|
+
# sms.to # => ['41791231212']
|
72
|
+
#
|
73
|
+
# sms.to = ['41791231212', '41791231212']
|
74
|
+
# sms.to # => ['41791231212', '41791231212']
|
75
|
+
#
|
76
|
+
# Returns nothing.
|
77
|
+
def to=(*args)
|
78
|
+
@to = MoteSMS::NumberList.new.push(*args)
|
79
|
+
end
|
80
|
+
|
81
|
+
# Public: Returns NumberList for this message.
|
82
|
+
#
|
83
|
+
# Returns NumberList instance.
|
84
|
+
def to(*numbers)
|
85
|
+
@to.push(*numbers) unless numbers.empty?
|
86
|
+
@to
|
87
|
+
end
|
88
|
+
|
89
|
+
# Public: Deliver message using defined transport, to select the correct
|
90
|
+
# transport method uses any of these values:
|
91
|
+
#
|
92
|
+
# 1. if options[:transport] is defined
|
93
|
+
# 2. falls back to self.transport
|
94
|
+
# 3. falls back to use MoteSMS.transport (global transport)
|
95
|
+
#
|
96
|
+
# Returns result of transport#deliver.
|
97
|
+
def deliver(options = {})
|
98
|
+
transport = options.delete(:transport) || self.transport || MoteSMS.transport
|
99
|
+
transport.deliver(self, options)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'phony'
|
2
|
+
|
3
|
+
module MoteSMS
|
4
|
+
|
5
|
+
# MoteSMS::Number handles all the number parsing and formatting
|
6
|
+
# issues, also a number is immutable.
|
7
|
+
class Number
|
8
|
+
|
9
|
+
# Access the E164 normalized value of the number.
|
10
|
+
attr_reader :number
|
11
|
+
alias :to_number :number
|
12
|
+
|
13
|
+
def initialize(value, options = {})
|
14
|
+
@options = options || {}
|
15
|
+
@raw_number = value.to_s
|
16
|
+
parse_raw_number
|
17
|
+
end
|
18
|
+
|
19
|
+
# Public: Returns true if this **could** represent a vanity number.
|
20
|
+
#
|
21
|
+
# Returns Boolean, true if this is a vanity number, else false.
|
22
|
+
def vanity?
|
23
|
+
!!@options[:vanity]
|
24
|
+
end
|
25
|
+
|
26
|
+
# Public: Prints formatted number, i.e. the human readable
|
27
|
+
# variant.
|
28
|
+
#
|
29
|
+
# Returns formatted number.
|
30
|
+
def to_s
|
31
|
+
@formatted_number ||= vanity? ? number : Phony.formatted(number)
|
32
|
+
end
|
33
|
+
|
34
|
+
protected
|
35
|
+
|
36
|
+
# Internal: Parse raw number with the help of Phony. Automatically
|
37
|
+
# adds the country code if missing.
|
38
|
+
#
|
39
|
+
def parse_raw_number
|
40
|
+
unless vanity?
|
41
|
+
raise ArgumentError, "Unable to parse #{@raw_number} as number" unless Phony.plausible?(@raw_number)
|
42
|
+
normalized = Phony.normalize(@raw_number)
|
43
|
+
normalized = "#{@options[:cc]}#{normalized}" unless @options[:cc] && normalized.start_with?(@options[:cc])
|
44
|
+
raise ArgumentError, "Wrong national destination code #{@raw_number}" unless Phony.plausible?(normalized, @options)
|
45
|
+
|
46
|
+
@number = Phony.normalize normalized
|
47
|
+
else
|
48
|
+
@number = @raw_number.gsub(/[^A-Z0-9]/i, '').upcase.strip
|
49
|
+
raise ArgumentError, "Invalid vanity number #{@raw_number}" if @number.length == 0 || @number.length > 11
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'mote_sms/number'
|
2
|
+
|
3
|
+
module MoteSMS
|
4
|
+
|
5
|
+
# List of Number instances, which transparantly is able to add
|
6
|
+
# new Number instances from strings, or whatever.
|
7
|
+
#
|
8
|
+
# Implements Enumerable, thus can be used like any regular array.
|
9
|
+
#
|
10
|
+
# Examples:
|
11
|
+
#
|
12
|
+
# list << '+41 79 123 12 12'
|
13
|
+
# list.push '044 123 12 12', cc: '41'
|
14
|
+
# list.push Number.new('0800 123 12 12')
|
15
|
+
# list.normalized_numbers
|
16
|
+
# # => ['41791231212', '41441231212', '08001231212']
|
17
|
+
#
|
18
|
+
class NumberList
|
19
|
+
|
20
|
+
# Load ruby enumerable support.
|
21
|
+
include ::Enumerable
|
22
|
+
|
23
|
+
# Internal numbers array.
|
24
|
+
attr_reader :numbers
|
25
|
+
|
26
|
+
# Public: Create a new number list instance.
|
27
|
+
def initialize
|
28
|
+
@numbers = ::Array.new
|
29
|
+
end
|
30
|
+
|
31
|
+
# Public: Count of numbers in the list.
|
32
|
+
def length
|
33
|
+
numbers.length
|
34
|
+
end
|
35
|
+
alias :size :length
|
36
|
+
|
37
|
+
# Public: Conform to arrayish behavior.
|
38
|
+
def empty?
|
39
|
+
numbers.empty?
|
40
|
+
end
|
41
|
+
alias :blank? :empty?
|
42
|
+
|
43
|
+
# Public: Add number to internal list, use duck typing to detect if
|
44
|
+
# it appears to be a number instance or not. So everything which does
|
45
|
+
# not respond to `to_number` is converted into a Number instance.
|
46
|
+
#
|
47
|
+
# number - The Number or String to add.
|
48
|
+
#
|
49
|
+
# Returns nothing.
|
50
|
+
def <<(number)
|
51
|
+
self.push(number)
|
52
|
+
end
|
53
|
+
|
54
|
+
# Public: Add multiple numbers, with optional options hash which can
|
55
|
+
# be used to set country options etc.
|
56
|
+
#
|
57
|
+
# Returns self.
|
58
|
+
def push(*numbers)
|
59
|
+
options = numbers.last.is_a?(Hash) ? numbers.pop : {}
|
60
|
+
numbers.flatten.each do |number|
|
61
|
+
number = Number.new(number, options) unless number.respond_to?(:to_number)
|
62
|
+
self.numbers << number
|
63
|
+
end
|
64
|
+
self
|
65
|
+
end
|
66
|
+
|
67
|
+
# Public: Yields each Number instance from this number list
|
68
|
+
# to the provided block. This interface is also required to be
|
69
|
+
# implemeneted for Enumerable support.
|
70
|
+
#
|
71
|
+
# Returns self.
|
72
|
+
def each(&block)
|
73
|
+
numbers.each(&block)
|
74
|
+
self
|
75
|
+
end
|
76
|
+
|
77
|
+
# Public: Fetch numbers using to_number.
|
78
|
+
#
|
79
|
+
# Returns Array of E164 normalized numbers.
|
80
|
+
def normalized_numbers
|
81
|
+
numbers.map(&:to_number)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
File without changes
|
@@ -0,0 +1,185 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'net/http'
|
3
|
+
require 'phony'
|
4
|
+
require 'logger'
|
5
|
+
|
6
|
+
module MoteSMS
|
7
|
+
|
8
|
+
# MoteSMS::MobileTechnicsTransport provides the implementation to
|
9
|
+
# send messages using nth.ch bulk SMS HTTP/S API. Each customer has
|
10
|
+
# custom endpoint (with port) and username/password.
|
11
|
+
#
|
12
|
+
# Examples:
|
13
|
+
#
|
14
|
+
# transport = MoteSMS::MobileTechnicsTransport.new 'https://mygateway.nth.ch', 'username', 'password'
|
15
|
+
# transport.deliver message
|
16
|
+
# # => ['000-791234', '001-7987324']
|
17
|
+
#
|
18
|
+
class MobileTechnicsTransport
|
19
|
+
|
20
|
+
# Maximum recipients allowed by API
|
21
|
+
MAX_RECIPIENT = 100
|
22
|
+
|
23
|
+
# Custom exception subclass.
|
24
|
+
ServiceError = Class.new(::Exception)
|
25
|
+
|
26
|
+
# Accessible attributes
|
27
|
+
attr_accessor :endpoint, :username, :password, :logger
|
28
|
+
|
29
|
+
# Options are readable as hash
|
30
|
+
attr_reader :options
|
31
|
+
|
32
|
+
# Public: Global default parameters for sending messages, Procs/lambdas
|
33
|
+
# are evaluated on #deliver. Ensure to use only symbols as keys. Contains
|
34
|
+
# `allow_adaption: true` as default.
|
35
|
+
#
|
36
|
+
# Examples:
|
37
|
+
#
|
38
|
+
# MoteSMS::MobileTechnicsTransports.defaults[:messageid] = ->(msg) { "#{msg.from}-#{SecureRandom.hex}" }
|
39
|
+
#
|
40
|
+
# Returns Hash with options.
|
41
|
+
def self.defaults
|
42
|
+
@@options ||= {
|
43
|
+
allow_adaption: true
|
44
|
+
}
|
45
|
+
end
|
46
|
+
|
47
|
+
# Public: Logger used to log HTTP requests to mobile
|
48
|
+
# technics API endpoint.
|
49
|
+
#
|
50
|
+
# Returns Logger instance.
|
51
|
+
def self.logger
|
52
|
+
@@logger ||= ::Logger.new($stdout)
|
53
|
+
end
|
54
|
+
|
55
|
+
# Public: Change the logger used to log all HTTP requests to
|
56
|
+
# the endpoint.
|
57
|
+
#
|
58
|
+
# logger - The Logger instance, should at least respond to #debug, #error.
|
59
|
+
#
|
60
|
+
# Returns nothing.
|
61
|
+
def self.logger=(logger)
|
62
|
+
@@logger = logger
|
63
|
+
end
|
64
|
+
|
65
|
+
# Public: Create a new instance using specified endpoint, username
|
66
|
+
# and password.
|
67
|
+
#
|
68
|
+
# endpoint - The String with the URL (with protocol et all) to nth gateway.
|
69
|
+
# username - The String with username.
|
70
|
+
# password - The String with password.
|
71
|
+
# options - The Hash with additional options.
|
72
|
+
#
|
73
|
+
# Returns a new instance.
|
74
|
+
def initialize(endpoint, username, password, options = nil)
|
75
|
+
self.endpoint = endpoint
|
76
|
+
self.username = username
|
77
|
+
self.password = password
|
78
|
+
@options = options || {}
|
79
|
+
end
|
80
|
+
|
81
|
+
# Public: Delivers message using mobile technics HTTP/S API.
|
82
|
+
#
|
83
|
+
# message - The MoteSMS::Message instance to send.
|
84
|
+
# options - The Hash with service specific options.
|
85
|
+
#
|
86
|
+
# Returns Array with sender ids.
|
87
|
+
def deliver(message, options = {})
|
88
|
+
raise ArgumentError, "Too many recipients, max. is #{MAX_RECIPIENT} (current: #{message.to.length})" if message.to.length > MAX_RECIPIENT
|
89
|
+
|
90
|
+
# Prepare request
|
91
|
+
uri = URI.parse endpoint
|
92
|
+
http = http_client uri
|
93
|
+
request = http_request uri, post_params(message, options)
|
94
|
+
|
95
|
+
# Log
|
96
|
+
self.class.logger.debug "curl -X#{request.method} #{http.use_ssl? ? '-k ' : ''}'#{endpoint}' -d '#{request.body}'"
|
97
|
+
|
98
|
+
# Perform request
|
99
|
+
resp = http.request request
|
100
|
+
|
101
|
+
# Handle errors
|
102
|
+
raise ServiceError, "Endpoint did respond with #{resp.code}" unless resp.code.to_i == 200
|
103
|
+
raise ServiceError, "Endpoint was unable to deliver message to all recipients" unless resp.body.split("\n").all? { |l| l =~ /Result_code: 00/ }
|
104
|
+
|
105
|
+
# extract Nth-SmsIds
|
106
|
+
resp['X-Nth-SmsId'].split(',')
|
107
|
+
end
|
108
|
+
|
109
|
+
protected
|
110
|
+
|
111
|
+
# Internal: Prepare request including body, headers etc.
|
112
|
+
#
|
113
|
+
# uri - The URI from the endpoint.
|
114
|
+
# params - The Array with the attributes.
|
115
|
+
#
|
116
|
+
# Returns Net::HTTP::Post instance.
|
117
|
+
def http_request(uri, params)
|
118
|
+
Net::HTTP::Post.new(uri.request_uri).tap do |request|
|
119
|
+
request.body = URI.encode_www_form params
|
120
|
+
request.content_type = 'application/x-www-form-urlencoded; charset=utf-8'
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
# Internal: Build new Net::HTTP instance, enables SSL if requested.
|
125
|
+
# FIXME: Add ability to change verify_mode, so e.g. certificates can be
|
126
|
+
# verified!
|
127
|
+
#
|
128
|
+
# uri - The URI from the endpoint.
|
129
|
+
#
|
130
|
+
# Returns Net::HTTP client instance.
|
131
|
+
def http_client(uri)
|
132
|
+
Net::HTTP.new(uri.host, uri.port).tap do |http|
|
133
|
+
# SSL support
|
134
|
+
if uri.instance_of?(URI::HTTPS)
|
135
|
+
http.use_ssl = true
|
136
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
# Internal: Merge defaults from class and instance with options
|
142
|
+
# supplied to #deliver.
|
143
|
+
#
|
144
|
+
# options - The Hash to merge with #defaults and #options.
|
145
|
+
#
|
146
|
+
# Returns Hash.
|
147
|
+
def prepare_options(options)
|
148
|
+
self.class.defaults.merge(self.options).merge(options)
|
149
|
+
end
|
150
|
+
|
151
|
+
# Internal: Convert NumberList instance to ; separated string with international
|
152
|
+
# relative formatted numbers. Formatting is done using phony.
|
153
|
+
#
|
154
|
+
# number_list - The NumberList instance.
|
155
|
+
#
|
156
|
+
# Returns String with numbers separated by ;.
|
157
|
+
def prepare_numbers(number_list)
|
158
|
+
number_list.normalized_numbers.map { |n| Phony.formatted(n, format: :international_relative, spaces: '') }.join(';')
|
159
|
+
end
|
160
|
+
|
161
|
+
# Internal: Prepare parameters for sending POST to endpoint, merges defaults,
|
162
|
+
# local and per call options, adds message related informations etc etc.
|
163
|
+
#
|
164
|
+
# message - The MoteSMS::Message to create the POST body for.
|
165
|
+
# options - The Hash with additional, per delivery options.
|
166
|
+
#
|
167
|
+
# Returns Array with params.
|
168
|
+
def post_params(message, options)
|
169
|
+
params = prepare_options options
|
170
|
+
params.merge! username: self.username,
|
171
|
+
password: self.password,
|
172
|
+
origin: message.from ? message.from.to_number : params[:origin],
|
173
|
+
text: message.body,
|
174
|
+
:'call-number' => prepare_numbers(message.to)
|
175
|
+
|
176
|
+
# Post process params (Procs & allow_adaption)
|
177
|
+
params.map do |param, value|
|
178
|
+
value = value.call(message) if value.respond_to?(:call)
|
179
|
+
value = value ? 1 : 0 if param == :allow_adaption
|
180
|
+
|
181
|
+
[param.to_s, value.to_s]
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module MoteSMS
|
2
|
+
|
3
|
+
# Public: Provide access to global array of delivered
|
4
|
+
# messages, this can be used in testing to assert sent
|
5
|
+
# SMS messages, test their contents, recipients etc.
|
6
|
+
#
|
7
|
+
# Must be cleared manually (!)
|
8
|
+
@@deliveries = []
|
9
|
+
def self.deliveries
|
10
|
+
@@deliveries
|
11
|
+
end
|
12
|
+
|
13
|
+
# MoteSMS::TestTransport provides a transport implementation which
|
14
|
+
# can be used in test cases. This allows to test sending SMSes
|
15
|
+
# et all without depending on an API or accidentally sending out
|
16
|
+
# messages to real people.
|
17
|
+
#
|
18
|
+
# It works similar to testing ActionMailers, all delivered messages
|
19
|
+
# will be appended to `MoteSMS.deliveries`. This array must be
|
20
|
+
# cleared manually, so it makes sense to add a before hook to
|
21
|
+
# your favorite testing framework:
|
22
|
+
#
|
23
|
+
# before do
|
24
|
+
# MoteSMS.transport = MoteSMS::TestTransport
|
25
|
+
# MoteSMS.deliveries.clear
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
module TestTransport
|
29
|
+
|
30
|
+
# Public: Appends supplied message to global deliveries array.
|
31
|
+
#
|
32
|
+
# message - The MoteSMS::Message instance to deliver.
|
33
|
+
# options - The Hash with additional, transport specific options.
|
34
|
+
#
|
35
|
+
# Returns nothing.
|
36
|
+
def self.deliver(message, options = {})
|
37
|
+
MoteSMS.deliveries << message
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,7 @@
|
|
1
|
+
module MoteSMS
|
2
|
+
|
3
|
+
# All transports live within mote_sms/transports, though should be
|
4
|
+
# available in ruby as `MoteSMS::<Some>Transport`.
|
5
|
+
autoload :TestTransport, 'mote_sms/transports/test_transport'
|
6
|
+
autoload :MobileTechnicsTransport, 'mote_sms/transports/mobile_technics_transport'
|
7
|
+
end
|
data/lib/mote_sms.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'mote_sms/transports'
|
2
|
+
|
3
|
+
module MoteSMS
|
4
|
+
autoload :Number, 'mote_sms/number'
|
5
|
+
autoload :NumberList, 'mote_sms/number_list'
|
6
|
+
autoload :Message, 'mote_sms/message'
|
7
|
+
|
8
|
+
autoload :VERSION, 'mote_sms/version'
|
9
|
+
|
10
|
+
# No default transport.
|
11
|
+
@@transport = nil
|
12
|
+
|
13
|
+
# Public: Get globally defined transport method, if any.
|
14
|
+
# Defaults to `nil`.
|
15
|
+
#
|
16
|
+
# Returns global SMS transport method.
|
17
|
+
def self.transport
|
18
|
+
@@transport
|
19
|
+
end
|
20
|
+
|
21
|
+
# Public: Set global transport method to use.
|
22
|
+
#
|
23
|
+
# transport - Any object which implements `#deliver(message, options)`.
|
24
|
+
#
|
25
|
+
# Returns nothing.
|
26
|
+
def self.transport=(transport)
|
27
|
+
@@transport = transport
|
28
|
+
end
|
29
|
+
|
30
|
+
# Public: Directly deliver a message using global transport.
|
31
|
+
#
|
32
|
+
# Examples:
|
33
|
+
#
|
34
|
+
# MoteSMS.deliver do
|
35
|
+
# to '0041 79 123 12 12'
|
36
|
+
# from 'SENDER'
|
37
|
+
# body 'Hello world'
|
38
|
+
# end
|
39
|
+
#
|
40
|
+
# Returns result of #deliver.
|
41
|
+
def self.deliver(&block)
|
42
|
+
raise ArgumentError, 'Block missing' unless block_given?
|
43
|
+
Message.new(&block).deliver
|
44
|
+
end
|
45
|
+
end
|
data/mote_sms.gemspec
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/mote_sms/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.name = 'mote_sms'
|
6
|
+
gem.authors = ['Lukas Westermann']
|
7
|
+
gem.email = ['lukas.westermann@at-point.ch']
|
8
|
+
gem.summary = %q{Deliver SMS using MobileTechnics HTTP API.}
|
9
|
+
gem.description = %q{Unofficial ruby adapter for MobileTechnics HTTP Bulk SMS API.
|
10
|
+
Tries to mimick mail API, so users can switch e.g. ActionMailer
|
11
|
+
with this SMS provider.}
|
12
|
+
gem.homepage = 'https://at-point.ch/opensource'
|
13
|
+
|
14
|
+
gem.files = `git ls-files`.split($\)
|
15
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
16
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
17
|
+
gem.require_paths = %w{lib}
|
18
|
+
gem.version = MoteSMS::VERSION
|
19
|
+
|
20
|
+
gem.required_ruby_version = '>= 1.9'
|
21
|
+
|
22
|
+
gem.add_dependency 'phony', ['~> 1.7.0']
|
23
|
+
|
24
|
+
gem.add_development_dependency 'rake'
|
25
|
+
gem.add_development_dependency 'rspec', ['~> 2.10']
|
26
|
+
gem.add_development_dependency 'webmock', ['~> 1.8.0']
|
27
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'mote_sms/message'
|
3
|
+
|
4
|
+
describe MoteSMS::Message do
|
5
|
+
it 'can be constructed using a block' do
|
6
|
+
msg = described_class.new do
|
7
|
+
from 'SENDER'
|
8
|
+
to '+41 79 123 12 12'
|
9
|
+
body 'This is the SMS content'
|
10
|
+
end
|
11
|
+
msg.from.number.should == 'SENDER'
|
12
|
+
msg.to.normalized_numbers.should == %w{41791231212}
|
13
|
+
msg.body.should == 'This is the SMS content'
|
14
|
+
end
|
15
|
+
|
16
|
+
context '#to' do
|
17
|
+
it 'behaves as accessor' do
|
18
|
+
subject.to = '41791231212'
|
19
|
+
subject.to.normalized_numbers.should == %w{41791231212}
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'behaves as array' do
|
23
|
+
subject.to << '41791231212'
|
24
|
+
subject.to << '41797775544'
|
25
|
+
subject.to.normalized_numbers.should == %w{41791231212 41797775544}
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'normalizes numbers' do
|
29
|
+
subject.to = '+41 79 123 12 12'
|
30
|
+
subject.to.normalized_numbers.should == %w{41791231212}
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
context "#deliver" do
|
35
|
+
let(:transport) { double("Some Transport") }
|
36
|
+
subject { described_class.new(transport) }
|
37
|
+
|
38
|
+
it "sends messages to transport" do
|
39
|
+
transport.should_receive(:deliver).with(subject, {})
|
40
|
+
subject.deliver
|
41
|
+
end
|
42
|
+
|
43
|
+
it "can pass additional attributes to transport" do
|
44
|
+
transport.should_receive(:deliver).with(subject, serviceid: "myapplication")
|
45
|
+
subject.deliver serviceid: "myapplication"
|
46
|
+
end
|
47
|
+
|
48
|
+
it "can override per message transport using :transport option" do
|
49
|
+
transport.should_not_receive(:deliver)
|
50
|
+
subject.deliver transport: double(deliver: true)
|
51
|
+
end
|
52
|
+
|
53
|
+
it "uses global MoteSMS.transport if no per message transport defined" do
|
54
|
+
message = described_class.new
|
55
|
+
transport.should_receive(:deliver).with(message, {})
|
56
|
+
MoteSMS.should_receive(:transport) { transport }
|
57
|
+
message.deliver
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'mote_sms/number_list'
|
3
|
+
|
4
|
+
describe MoteSMS::NumberList do
|
5
|
+
it 'has length' do
|
6
|
+
subject.length.should == 0
|
7
|
+
subject << '+41 79 111 22 33'
|
8
|
+
subject.length.should == 1
|
9
|
+
5.times { subject << '+41 79 222 33 44' }
|
10
|
+
subject.length.should == 6
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'has empty?' do
|
14
|
+
subject.empty?.should be_true
|
15
|
+
subject << '+41 79 111 22 33'
|
16
|
+
subject.empty?.should be_false
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'can add numbers by string' do
|
20
|
+
subject << '+41 79 111 22 33'
|
21
|
+
subject.normalized_numbers.should == %w{41791112233}
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'can multiple numbers using push' do
|
25
|
+
subject.push '+41 79 111 22 33', '+41 44 111 22 33'
|
26
|
+
subject.normalized_numbers.should == %w{41791112233 41441112233}
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'can push multiple numbers with adding country codes' do
|
30
|
+
subject.push '079 111 22 33', '0041 44 111 22 33', cc: '41', ndc: /(44|79)/
|
31
|
+
subject.normalized_numbers.should == %w{41791112233 41441112233}
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'mote_sms/number'
|
3
|
+
|
4
|
+
describe MoteSMS::Number do
|
5
|
+
context 'normalized number' do
|
6
|
+
subject { described_class.new('41443643533') }
|
7
|
+
|
8
|
+
its(:to_s) { should == '+41 44 364 35 33' }
|
9
|
+
its(:number) { should == '41443643533' }
|
10
|
+
its(:to_number) { should == '41443643533' }
|
11
|
+
end
|
12
|
+
|
13
|
+
context 'E164 conforming number' do
|
14
|
+
subject { described_class.new('+41 44 3643533') }
|
15
|
+
|
16
|
+
its(:to_s) { should == '+41 44 364 35 33' }
|
17
|
+
its(:number) { should == '41443643533' }
|
18
|
+
its(:to_number) { should == '41443643533' }
|
19
|
+
end
|
20
|
+
|
21
|
+
context 'E164 conforming number with name' do
|
22
|
+
subject { described_class.new('Frank: +41 44 3643533', cc: '41') }
|
23
|
+
|
24
|
+
its(:to_s) { should == '+41 44 364 35 33' }
|
25
|
+
its(:number) { should == '41443643533' }
|
26
|
+
its(:to_number) { should == '41443643533' }
|
27
|
+
end
|
28
|
+
|
29
|
+
context 'handles local numbers' do
|
30
|
+
subject { described_class.new('079 700 50 90', cc: '41') }
|
31
|
+
|
32
|
+
its(:to_s) { should == '+41 79 700 50 90' }
|
33
|
+
its(:number) { should == '41797005090'}
|
34
|
+
its(:to_number) { should == '41797005090' }
|
35
|
+
end
|
36
|
+
|
37
|
+
context 'non conforming number' do
|
38
|
+
it 'raises error when creating' do
|
39
|
+
Proc.new { described_class.new('what ever?') }.should raise_error(ArgumentError, /unable to parse/i)
|
40
|
+
Proc.new { described_class.new('0000') }.should raise_error(ArgumentError, /unable to parse/i)
|
41
|
+
Proc.new { described_class.new('123456789012345678901') }.should raise_error(ArgumentError, /unable to parse/i)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
context 'wrong cc/ndc' do
|
46
|
+
it 'raises error when creating instance with wrong ndc' do
|
47
|
+
Proc.new { described_class.new('+41 44 364 35 33', cc: '41', ndc: '43') }.should raise_error(ArgumentError, /national destination/i)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
context 'vanity numbers' do
|
52
|
+
subject { described_class.new('0800-vanity', vanity: true) }
|
53
|
+
|
54
|
+
its(:to_s) { should == '0800VANITY' }
|
55
|
+
its(:number) { should == '0800VANITY' }
|
56
|
+
its(:to_number) { should == '0800VANITY' }
|
57
|
+
its(:vanity?) { should be_true }
|
58
|
+
|
59
|
+
it 'raises error if more than 11 alpha numeric chars' do
|
60
|
+
Proc.new { described_class.new('1234567890AB', vanity: true) }.should raise_error(ArgumentError, /invalid vanity/i)
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'can also be normal phone number' do
|
64
|
+
num = described_class.new('0800 123 12 12', vanity: true)
|
65
|
+
num.to_s.should == '08001231212'
|
66
|
+
num.to_number.should == '08001231212'
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'spec_helper'
|
3
|
+
require 'cgi'
|
4
|
+
require 'mote_sms/message'
|
5
|
+
require 'mote_sms/transports/mobile_technics_transport'
|
6
|
+
|
7
|
+
describe MoteSMS::MobileTechnicsTransport do
|
8
|
+
before do
|
9
|
+
@logger = described_class.logger
|
10
|
+
described_class.logger = stub(debug: true, info: true, error: true)
|
11
|
+
end
|
12
|
+
|
13
|
+
after do
|
14
|
+
described_class.logger = @logger
|
15
|
+
end
|
16
|
+
|
17
|
+
subject { described_class.new(endpoint, "example", "123456") }
|
18
|
+
|
19
|
+
let(:endpoint) { "http://test.nth.ch" }
|
20
|
+
let(:message) {
|
21
|
+
MoteSMS::Message.new do
|
22
|
+
to '0041 079 123 12 12'
|
23
|
+
from 'SENDER'
|
24
|
+
body 'Hello World, with äöü.'
|
25
|
+
end
|
26
|
+
}
|
27
|
+
|
28
|
+
let(:success) {
|
29
|
+
{ body: "Result_code: 00, Message OK", status: 200, headers: { 'X-Nth-SmsId' => '43797917' } }
|
30
|
+
}
|
31
|
+
|
32
|
+
context "#deliver" do
|
33
|
+
it "sends POST to endpoint with URL encoded body" do
|
34
|
+
stub_request(:post, endpoint).with do |req|
|
35
|
+
params = CGI.parse(req.body)
|
36
|
+
params['username'].should == %w{example}
|
37
|
+
params['password'].should == %w{123456}
|
38
|
+
params['text'].should == ['Hello World, with äöü.']
|
39
|
+
params['call-number'].should == ['0041791231212']
|
40
|
+
end.to_return(success)
|
41
|
+
subject.deliver message
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'sends message in single request to multiple recipients' do
|
45
|
+
message.to << '+41 79 333 44 55'
|
46
|
+
message.to << '+41 78 111 22 33'
|
47
|
+
|
48
|
+
stub_request(:post, endpoint).with(body: hash_including('call-number' => '0041791231212;0041793334455;0041781112233')).to_return(success)
|
49
|
+
subject.deliver message
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'raises exception if required parameter is missing' do
|
53
|
+
stub_request(:post, endpoint).to_return(body: 'Result_code: 02, call-number')
|
54
|
+
Proc.new { subject.deliver message }.should raise_error(MoteSMS::MobileTechnicsTransport::ServiceError)
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'raises exception if status code is not 200' do
|
58
|
+
stub_request(:post, endpoint).to_return(status: 500)
|
59
|
+
Proc.new { subject.deliver message }.should raise_error(MoteSMS::MobileTechnicsTransport::ServiceError)
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'returns message id' do
|
63
|
+
stub_request(:post, endpoint).to_return(success)
|
64
|
+
subject.deliver(message).should == %w{43797917}
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'logs curl compatible output' do
|
68
|
+
io = StringIO.new
|
69
|
+
described_class.logger = Logger.new(io)
|
70
|
+
stub_request(:post, endpoint).to_return(success)
|
71
|
+
subject.deliver message
|
72
|
+
io.rewind
|
73
|
+
io.read.should =~ %r{curl -XPOST 'http://test.nth.ch' -d 'allow_adaption=1&}
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
context "#options" do
|
78
|
+
it 'can be passed in as last item in the constructor' do
|
79
|
+
transport = described_class.new endpoint, 'user', 'pass', allow_adaption: false, validity: 30
|
80
|
+
transport.options[:allow_adaption].should be_false
|
81
|
+
transport.options[:validity].should == 30
|
82
|
+
transport.options[:something].should be_nil
|
83
|
+
end
|
84
|
+
|
85
|
+
it 'should be exposed as hash' do
|
86
|
+
subject.options[:messageid] = "test"
|
87
|
+
subject.options[:messageid].should == "test"
|
88
|
+
end
|
89
|
+
|
90
|
+
it 'overrides settings from #defaults' do
|
91
|
+
described_class.defaults[:something] = 'global'
|
92
|
+
subject.options[:something] = 'local'
|
93
|
+
|
94
|
+
stub_request(:post, endpoint).with(body: hash_including('something' => 'local')).to_return(success)
|
95
|
+
subject.deliver message
|
96
|
+
end
|
97
|
+
|
98
|
+
it 'is overriden by options passed to #deliver' do
|
99
|
+
subject.options[:something] = 'local'
|
100
|
+
|
101
|
+
stub_request(:post, endpoint).with(body: hash_including('something' => 'deliver')).to_return(success)
|
102
|
+
subject.deliver message, something: 'deliver'
|
103
|
+
end
|
104
|
+
|
105
|
+
it 'evaluates Procs & lambdas' do
|
106
|
+
subject.options[:messageid] = Proc.new { "test" }
|
107
|
+
|
108
|
+
stub_request(:post, endpoint).with(body: hash_including('messageid' => 'test')).to_return(success)
|
109
|
+
subject.deliver message
|
110
|
+
end
|
111
|
+
|
112
|
+
it 'converts allow_adaption to 1 when true' do
|
113
|
+
subject.options[:allow_adaption] = true
|
114
|
+
stub_request(:post, endpoint).with(body: hash_including('allow_adaption' => '1')).to_return(success)
|
115
|
+
subject.deliver message
|
116
|
+
end
|
117
|
+
|
118
|
+
it 'converts allow_adaption to 0 when false' do
|
119
|
+
subject.options[:allow_adaption] = nil
|
120
|
+
stub_request(:post, endpoint).with(body: hash_including('allow_adaption' => '0')).to_return(success)
|
121
|
+
subject.deliver message
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'mote_sms/transports/test_transport'
|
3
|
+
|
4
|
+
describe MoteSMS::TestTransport do
|
5
|
+
subject { described_class }
|
6
|
+
before { MoteSMS.deliveries.clear }
|
7
|
+
|
8
|
+
it 'defines global #deliveries' do
|
9
|
+
MoteSMS.should respond_to(:deliveries)
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'appends deliveries' do
|
13
|
+
subject.deliver "firstMessage"
|
14
|
+
subject.deliver "secondMessage"
|
15
|
+
MoteSMS.deliveries.should == %w{firstMessage secondMessage}
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'mote_sms'
|
3
|
+
|
4
|
+
describe MoteSMS do
|
5
|
+
subject { described_class }
|
6
|
+
|
7
|
+
it 'has a version' do
|
8
|
+
subject::VERSION.should =~ /\d/
|
9
|
+
end
|
10
|
+
|
11
|
+
context "transport" do
|
12
|
+
before { @current_transport = subject.transport }
|
13
|
+
after { subject.transport = @current_transport }
|
14
|
+
let(:transport) { double("transport") }
|
15
|
+
|
16
|
+
it "has no default transport" do
|
17
|
+
subject.transport.should be_nil
|
18
|
+
end
|
19
|
+
|
20
|
+
it "can change global transport" do
|
21
|
+
subject.transport = transport
|
22
|
+
subject.transport.should == transport
|
23
|
+
end
|
24
|
+
|
25
|
+
context "#deliver" do
|
26
|
+
it 'delivers quick and dirty using global transport' do
|
27
|
+
transport.should_receive(:deliver).with(kind_of(MoteSMS::Message), {})
|
28
|
+
subject.transport = transport
|
29
|
+
subject.deliver { body 'Hello World' }
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'raises error if block is missing' do
|
33
|
+
Proc.new { subject.deliver }.should raise_error(ArgumentError, /block missing/i)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,144 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mote_sms
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Lukas Westermann
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-07-31 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: phony
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 1.7.0
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 1.7.0
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rake
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: rspec
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ~>
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '2.10'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '2.10'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: webmock
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ~>
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: 1.8.0
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ~>
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: 1.8.0
|
78
|
+
description: ! "Unofficial ruby adapter for MobileTechnics HTTP Bulk SMS API.\n Tries
|
79
|
+
to mimick mail API, so users can switch e.g. ActionMailer\n with
|
80
|
+
this SMS provider."
|
81
|
+
email:
|
82
|
+
- lukas.westermann@at-point.ch
|
83
|
+
executables: []
|
84
|
+
extensions: []
|
85
|
+
extra_rdoc_files: []
|
86
|
+
files:
|
87
|
+
- .gitignore
|
88
|
+
- .travis.yml
|
89
|
+
- Gemfile
|
90
|
+
- LICENSE
|
91
|
+
- README.md
|
92
|
+
- Rakefile
|
93
|
+
- lib/mote_sms.rb
|
94
|
+
- lib/mote_sms/message.rb
|
95
|
+
- lib/mote_sms/number.rb
|
96
|
+
- lib/mote_sms/number_list.rb
|
97
|
+
- lib/mote_sms/transports.rb
|
98
|
+
- lib/mote_sms/transports/.gitkeep
|
99
|
+
- lib/mote_sms/transports/mobile_technics_transport.rb
|
100
|
+
- lib/mote_sms/transports/test_transport.rb
|
101
|
+
- lib/mote_sms/version.rb
|
102
|
+
- mote_sms.gemspec
|
103
|
+
- spec/mote_sms/message_spec.rb
|
104
|
+
- spec/mote_sms/number_list_spec.rb
|
105
|
+
- spec/mote_sms/number_spec.rb
|
106
|
+
- spec/mote_sms/transports/mobile_technics_transport_spec.rb
|
107
|
+
- spec/mote_sms/transports/test_transport_spec.rb
|
108
|
+
- spec/mote_sms_spec.rb
|
109
|
+
- spec/spec_helper.rb
|
110
|
+
homepage: https://at-point.ch/opensource
|
111
|
+
licenses: []
|
112
|
+
post_install_message:
|
113
|
+
rdoc_options: []
|
114
|
+
require_paths:
|
115
|
+
- lib
|
116
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
117
|
+
none: false
|
118
|
+
requirements:
|
119
|
+
- - ! '>='
|
120
|
+
- !ruby/object:Gem::Version
|
121
|
+
version: '1.9'
|
122
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
123
|
+
none: false
|
124
|
+
requirements:
|
125
|
+
- - ! '>='
|
126
|
+
- !ruby/object:Gem::Version
|
127
|
+
version: '0'
|
128
|
+
segments:
|
129
|
+
- 0
|
130
|
+
hash: 3261538551323079383
|
131
|
+
requirements: []
|
132
|
+
rubyforge_project:
|
133
|
+
rubygems_version: 1.8.23
|
134
|
+
signing_key:
|
135
|
+
specification_version: 3
|
136
|
+
summary: Deliver SMS using MobileTechnics HTTP API.
|
137
|
+
test_files:
|
138
|
+
- spec/mote_sms/message_spec.rb
|
139
|
+
- spec/mote_sms/number_list_spec.rb
|
140
|
+
- spec/mote_sms/number_spec.rb
|
141
|
+
- spec/mote_sms/transports/mobile_technics_transport_spec.rb
|
142
|
+
- spec/mote_sms/transports/test_transport_spec.rb
|
143
|
+
- spec/mote_sms_spec.rb
|
144
|
+
- spec/spec_helper.rb
|