smess 1.0.0

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: c788fb1adca56a19e9415362cdf44e74c8064e89
4
+ data.tar.gz: 4778a898c0d307f1b7e45606e9942b9a81456a3e
5
+ SHA512:
6
+ metadata.gz: 64dcc2a833300d0d55a55c38329bd7c5562556a4b1189c2e587380ab4a5d94af45a0ecb98e5080ae6d5f6ff72e85ae903cca7c63a324c32833fd615c182eda43
7
+ data.tar.gz: 50ea00e53fda127b453cf3975dd936f9459025b1fe1f02fdfcc32566b9f81f8a0eeebbb48d89793e02496d8adb3e869e7a2761496ab47c1caf558ec9a90bc05c
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source "http://rubygems.org"
2
+ gemspec
@@ -0,0 +1,87 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ smess (1.0.0)
5
+ activesupport (>= 3.0)
6
+ clickatell
7
+ httpi
8
+ mail
9
+ savon (= 1.2.0)
10
+ twilio-ruby
11
+
12
+ GEM
13
+ remote: http://rubygems.org/
14
+ specs:
15
+ activesupport (4.0.0)
16
+ i18n (~> 0.6, >= 0.6.4)
17
+ minitest (~> 4.2)
18
+ multi_json (~> 1.3)
19
+ thread_safe (~> 0.1)
20
+ tzinfo (~> 0.3.37)
21
+ akami (1.2.0)
22
+ gyoku (>= 0.4.0)
23
+ nokogiri (>= 1.4.0)
24
+ atomic (1.1.13)
25
+ builder (3.2.2)
26
+ clickatell (0.8.2)
27
+ diff-lcs (1.2.4)
28
+ dotenv (0.9.0)
29
+ gyoku (0.4.6)
30
+ builder (>= 2.1.2)
31
+ httpi (1.1.1)
32
+ rack
33
+ i18n (0.6.5)
34
+ jahtml_formatter (1.0.5)
35
+ rspec (~> 2.14)
36
+ jwt (0.1.8)
37
+ multi_json (>= 1.5)
38
+ mail (2.5.4)
39
+ mime-types (~> 1.16)
40
+ treetop (~> 1.4.8)
41
+ mime-types (1.25)
42
+ mini_portile (0.5.1)
43
+ minitest (4.7.5)
44
+ multi_json (1.7.9)
45
+ nokogiri (1.6.0)
46
+ mini_portile (~> 0.5.0)
47
+ nori (1.1.5)
48
+ polyglot (0.3.3)
49
+ rack (1.5.2)
50
+ rspec (2.14.1)
51
+ rspec-core (~> 2.14.0)
52
+ rspec-expectations (~> 2.14.0)
53
+ rspec-mocks (~> 2.14.0)
54
+ rspec-core (2.14.5)
55
+ rspec-expectations (2.14.2)
56
+ diff-lcs (>= 1.1.3, < 2.0)
57
+ rspec-mocks (2.14.3)
58
+ savon (1.2.0)
59
+ akami (~> 1.2.0)
60
+ builder (>= 2.1.2)
61
+ gyoku (~> 0.4.5)
62
+ httpi (~> 1.1.0)
63
+ nokogiri (>= 1.4.0)
64
+ nori (~> 1.1.0)
65
+ wasabi (~> 2.5.0)
66
+ thread_safe (0.1.2)
67
+ atomic
68
+ treetop (1.4.15)
69
+ polyglot
70
+ polyglot (>= 0.3.1)
71
+ twilio-ruby (3.10.1)
72
+ builder (>= 2.1.2)
73
+ jwt (>= 0.1.2)
74
+ multi_json (>= 1.3.0)
75
+ tzinfo (0.3.37)
76
+ wasabi (2.5.1)
77
+ httpi (~> 1.0)
78
+ nokogiri (>= 1.4.0)
79
+
80
+ PLATFORMS
81
+ ruby
82
+
83
+ DEPENDENCIES
84
+ dotenv
85
+ jahtml_formatter
86
+ rspec (>= 2.4.0)
87
+ smess!
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2013 Martin Westin
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
+ the Software, and to permit persons to whom the Software is furnished to do so,
10
+ subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,114 @@
1
+ # Smess - A mess of SMS messaging
2
+
3
+ This is a messy SMS messenger supporting every aggregator I have gotten my hands on.
4
+
5
+ In 2008 I started working on SMS and MMS messaging applications. Within the first 2 months it was apparent that I needed an abstraction that could route messages to different aggregators (messaging providers) to support different countries. No one API supports the entire word in any useful way.
6
+
7
+ This is that abstraction finally cleaned up (a little) enabling it to be public. Everyone from mBlox to Clickatell. The following 7 aggregators are supported.
8
+
9
+ * Auto - will automatically select the best option for each country. Most often using Global Mouth.
10
+ * Clickatell - http://www.clickatell.com
11
+ * Global Mouth - http://www.globalmouth.com
12
+ * Iconectiv - http://www.iconectiv.com
13
+ * IPX - http://www.ipx.com (who knows what'll happen to them)
14
+ * mBlox - http://www.mblox.com
15
+ * SMS Global - https://www.smsglobal.com
16
+ * Twilio - http://www.twilio.com
17
+
18
+ There is also a _test_ aggregator that you should set evrything to use when running tests in your code.
19
+
20
+
21
+ ## Who should use this?
22
+
23
+ * You want to send SMS messages from Ruby code.
24
+ * You want one API to call regardless of which aggregator you switch to.
25
+ * You may even want to do it to people in multiple countries using the "best" aggregator for each.
26
+
27
+ ## Why?
28
+
29
+ It may be old and crappy but it has served many millions of SMS messages in production across all continents of the globe. Being in production it has some nice touches that may serve you well.
30
+
31
+ There is automatic fallback delivery in places where it really makes a difference. Delivery reliability, particularly in the US, is appalling and being able to reduce non-deliveries by more than half is a big deal when sending transactional messages.
32
+
33
+ The aggregator outputs are a very simple plugin system so you can subclass, modify and write your own. The protocol is just one method accepting a single argument.
34
+
35
+ ```ruby
36
+ module Smess
37
+ class Example
38
+
39
+ def deliver_sms(sms)
40
+ # Do work and return a hash like this one
41
+ {
42
+ :response_code => '-1',
43
+ :response => {
44
+ :temporaryError =>'true',
45
+ :responseCode => '-1',
46
+ :responseText => 'Delivery not implemented.'
47
+ }
48
+ }
49
+ end
50
+
51
+ end
52
+ end
53
+ ```
54
+
55
+
56
+
57
+ ## Installation
58
+
59
+ ### Get the gem going
60
+
61
+ ```
62
+ gem "smess"
63
+ ```
64
+ or
65
+ ```
66
+ gem install smess
67
+ ```
68
+
69
+ ### Set a hundred ENV vars
70
+ All credentials, and other account-sepcific settings, have been extracted into ENV vars allowing you to set these any way you like. I use Dotenv but Figaro or other env manager should work equally well. As should setting them any other way.
71
+
72
+ All ENV variables used by Smess are listed in the file .env. You only need to define the aggregator credentials you actually use though.
73
+
74
+ ## Usage
75
+
76
+ ```ruby
77
+ sms = Smess.new(
78
+ to: '46701234567', # phone number in normalized msisdn format
79
+ message: 'Test SMS', # Message text
80
+ originator: 'TestSuite', # originator, sender id. This has many names. Outside the US this can usually be set to whatever you like.
81
+ output: "test" # Name of the output plugin to use. Defaults to auto select.
82
+ )
83
+ results = sms.deliver
84
+ puts result
85
+ ```
86
+
87
+ Look in the project test folder for end-to-end test files showcasing normal usage.
88
+
89
+ There are also convenience methods patched onto String to normalize phone numbers.
90
+ ```ruby
91
+ "\r\n +(070-123)\n45 67\n".msisdn(46)
92
+ #=> 46701234567
93
+ ```
94
+ US numbers cannot be banged into shape as much as most international numbers can. This is due to area codes not conforming to leading 0 as most other countries do. Tt is actually not terribly simple to see the difference between the US area code 850 and the North Korean country code 850. :)
95
+
96
+ More usage is pretty clear from the specs.
97
+
98
+ ## Disclaimers
99
+
100
+ Being Swedish, disclaimers are required.
101
+
102
+ * Much of the code is old and crappy. It started in 2008 in PHP, ported to Ruby in 2010.
103
+ * There are OK specs for the simple stuff. No specs for the more error-prone API calls. There are live tests that can be run to verify end-to-end messaging, though.
104
+ * Does not handle the other part of messaging... accepting and processing Delivery Reports. (I have the code... it is just not extracted yet.)
105
+
106
+
107
+
108
+ ## Contributing
109
+
110
+ 1. Fork it
111
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
112
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
113
+ 4. Push to the branch (`git push origin my-new-feature`)
114
+ 5. Create new Pull Request
@@ -0,0 +1,63 @@
1
+ # encoding: UTF-8
2
+ smess_path = File.expand_path('.', File.dirname(__FILE__))
3
+ $:.unshift(smess_path) if File.directory?(smess_path) && !$:.include?(smess_path)
4
+
5
+ require 'mail'
6
+ require 'savon'
7
+ require 'active_support/core_ext'
8
+
9
+ require "smess/version"
10
+ require 'smess/logging'
11
+ require 'smess/country_code_registry'
12
+ require 'smess/utils'
13
+ require 'smess/sms'
14
+ require 'smess/outputs/auto'
15
+ require 'smess/outputs/mm7'
16
+ require 'smess/outputs/ipx'
17
+ require 'smess/outputs/ipxus'
18
+ require 'smess/outputs/clickatell'
19
+ require 'smess/outputs/etisalatdemo'
20
+ require 'smess/outputs/smsglobal'
21
+ require 'smess/outputs/global_mouth'
22
+ require 'smess/outputs/mblox'
23
+ require 'smess/outputs/twilio'
24
+ require 'smess/outputs/iconectiv'
25
+ require 'smess/outputs/test'
26
+
27
+ require 'string'
28
+
29
+ module Smess
30
+
31
+ # Move to config?
32
+ OUTPUTS = %w{auto clickatell etisalatdemo global_mouth iconectiv mblox ipxus smsglobal twilio}
33
+ COUNTRY_CODES = [1, 20, 34, 46, 49, 966, 971]
34
+
35
+ def self.new(*args)
36
+ Sms.new(*args)
37
+ end
38
+
39
+ def self.config
40
+ @config ||=Config.new
41
+ end
42
+
43
+ class Config
44
+ def initialize
45
+ @config=Hash.new
46
+ end
47
+ def method_missing(method,*args,&block)
48
+ method = method.to_s.gsub(/[=]/,'')
49
+ if args.length>0
50
+ @config[method] = args.first
51
+ end
52
+ @config[method]
53
+ end
54
+ end
55
+
56
+ end
57
+
58
+ # httpclient does not send basic auth correctly, or at all.
59
+ HTTPI.adapter = :net_http
60
+
61
+ # Setting config defaults
62
+ # there is probably a better way and better place
63
+ Smess.config.debug = Smess.booleanize(ENV["SMESS_DEBUG"])
@@ -0,0 +1,33 @@
1
+ module Smess
2
+ OUTPUT_BY_COUNTRY_CODE = {
3
+ "0" => :global_mouth, # default for any undefined code
4
+ "1" => :iconectiv,
5
+ "1242"=> :global_mouth,
6
+ "1246"=> :global_mouth,
7
+ "1264"=> :global_mouth,
8
+ "1268"=> :global_mouth,
9
+ "1284"=> :global_mouth,
10
+ "1345"=> :global_mouth,
11
+ "1441"=> :global_mouth,
12
+ "1473"=> :global_mouth,
13
+ "1649"=> :global_mouth,
14
+ "1664"=> :global_mouth,
15
+ "1670"=> :global_mouth,
16
+ "1671"=> :global_mouth,
17
+ "1684"=> :global_mouth,
18
+ "1758"=> :global_mouth,
19
+ "1767"=> :global_mouth,
20
+ "1784"=> :global_mouth,
21
+ "1787"=> :global_mouth,
22
+ "1809"=> :global_mouth,
23
+ "1868"=> :global_mouth,
24
+ "1869"=> :global_mouth,
25
+ "1876"=> :global_mouth,
26
+ "20" => :global_mouth,
27
+ "34" => :global_mouth,
28
+ "46" => :global_mouth,
29
+ "49" => :global_mouth,
30
+ "966" => :global_mouth,
31
+ "971" => :etisalatdemo
32
+ }
33
+ end
@@ -0,0 +1,16 @@
1
+ module Smess
2
+ module Logging
3
+
4
+ def logger
5
+ # use the Rails logger if it's defined
6
+ @logger ||= if defined?(Rails)
7
+ Rails.logger
8
+ else
9
+ l = ::Logger.new(STDOUT)
10
+ l.level = Logger::WARN
11
+ l
12
+ end
13
+ end
14
+
15
+ end
16
+ end
@@ -0,0 +1,26 @@
1
+ module Smess
2
+ class Auto
3
+
4
+ def get_output_name_for_msisdn(msisdn)
5
+ 3.downto(0).each do |index|
6
+ return OUTPUT_BY_COUNTRY_CODE[msisdn[0..index]] if OUTPUT_BY_COUNTRY_CODE.key? msisdn[0..index]
7
+ end
8
+ OUTPUT_BY_COUNTRY_CODE["0"]
9
+ end
10
+
11
+ def output_for(msisdn)
12
+ out_class = get_output_name_for_msisdn msisdn
13
+ ("Smess::#{out_class.to_s.camelize}").constantize.new
14
+ end
15
+
16
+ def deliver_sms(sms)
17
+ out = output_for sms.to
18
+ if out.respond_to? :deliver_sms
19
+ out.deliver_sms sms
20
+ else
21
+ nil
22
+ end
23
+ end
24
+
25
+ end
26
+ end
@@ -0,0 +1,115 @@
1
+ require 'clickatell'
2
+
3
+ # This hack should be removed soon
4
+ module Clickatell
5
+ class API
6
+
7
+ def send_message(recipient, message_text, opts={})
8
+ valid_options = opts.only(:from, :mo, :callback, :climsgid, :concat)
9
+ valid_options.merge!(:req_feat => '48') if valid_options[:from]
10
+ valid_options.merge!(:mo => '1') if opts[:set_mobile_originated]
11
+ valid_options.merge!(:climsgid => opts[:client_message_id]) if opts[:client_message_id]
12
+ valid_options[:deliv_ack] = 1 if opts[:callback]
13
+ recipient = recipient.join(",")if recipient.is_a?(Array)
14
+ response = execute_command('sendmsg', 'http',
15
+ {:to => recipient, :text => message_text}.merge(valid_options)
16
+ )
17
+ response = parse_response(response)
18
+ #response.is_a?(Array) ? response.map { |r| r['ID'] } : response['ID']
19
+ end
20
+
21
+ end
22
+ end
23
+
24
+
25
+ module Smess
26
+ class Clickatell
27
+ include Smess::Logging
28
+
29
+ def initialize
30
+ ::Clickatell::API.debug_mode = true
31
+ ::Clickatell::API.secure_mode = true
32
+ end
33
+
34
+ def split_sms(text)
35
+ return [text] unless text.sms_length > 160
36
+ logger.debug "message text is long"
37
+
38
+ result = []
39
+ while text.sms_length > 160
40
+ logger.debug end_char = text.rindex(/[\n\r]/, 160)
41
+ part = text[0..end_char]
42
+ result << part
43
+ text = text[(end_char+1)..text.length]
44
+ end
45
+ result << text
46
+ result
47
+ end
48
+
49
+ def sender_not_supported(sms)
50
+ sms.to[0] == "1" || # USA
51
+ sms.to[0..2] == "962" || # Jordan
52
+ sms.to[0..2] == "971" # UAE
53
+ end
54
+ def concat_not_supported(sms)
55
+ sms.to[0] == "1" # USA
56
+ end
57
+
58
+
59
+
60
+ def deliver_sms(sms)
61
+ return false unless sms.kind_of? Sms
62
+ @sms = sms
63
+
64
+ api = ::Clickatell::API.authenticate(
65
+ ENV["SMESS_CLICKATELL_API_ID"],
66
+ ENV["SMESS_CLICKATELL_USER"],
67
+ ENV["SMESS_CLICKATELL_PASS"]
68
+ )
69
+ message = sms.message.strip_nongsm_chars.encode('ISO-8859-1')
70
+ from = ENV["SMESS_CLICKATELL_SENDER_IDS"].split(",").include?(sms.originator) ? sms.originator : ENV["SMESS_CLICKATELL_SENDER_ID"]
71
+
72
+ # Pretty pretty "feature detection"
73
+ if sender_not_supported sms
74
+ from = nil
75
+ end
76
+ if concat_not_supported sms
77
+ message_array = split_sms(message)
78
+ end
79
+
80
+ begin
81
+ if concat_not_supported sms
82
+ response = nil
83
+ message_array.each do |msg|
84
+ rsp = api.send_message(sms.to, msg, {:from => from, :concat => 3, :callback => 7})
85
+ response = rsp if response.nil?
86
+ end
87
+ else
88
+ response = api.send_message(sms.to, message, {:from => from, :concat => 3, :callback => 7})
89
+ end
90
+ rescue Exception => e
91
+ logger.warn response
92
+ # connection problem or some error
93
+ result = {
94
+ :response_code => '-1',
95
+ :response => {
96
+ :temporaryError =>'true',
97
+ :responseCode => e.code,
98
+ :responseText => e.message
99
+ },
100
+ :data => {:to => sms.to, :text => sms.message.strip_nongsm_chars, :from => from}
101
+ }
102
+ return result
103
+ end
104
+ # Successful response
105
+ result = {
106
+ :message_id => response['ID'],
107
+ :response_code => '0',
108
+ :response => response,
109
+ :destination_address => sms.to,
110
+ :data => {:to => sms.to, :text => sms.message.strip_nongsm_chars, :from => from}
111
+ }
112
+ end
113
+
114
+ end
115
+ end