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.
- checksums.yaml +7 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +87 -0
- data/LICENSE +20 -0
- data/README.md +114 -0
- data/lib/smess.rb +63 -0
- data/lib/smess/country_code_registry.rb +33 -0
- data/lib/smess/logging.rb +16 -0
- data/lib/smess/outputs/auto.rb +26 -0
- data/lib/smess/outputs/clickatell.rb +115 -0
- data/lib/smess/outputs/etisalatdemo.rb +61 -0
- data/lib/smess/outputs/global_mouth.rb +87 -0
- data/lib/smess/outputs/iconectiv.rb +48 -0
- data/lib/smess/outputs/ipx.rb +223 -0
- data/lib/smess/outputs/ipxus.rb +103 -0
- data/lib/smess/outputs/mblox.rb +155 -0
- data/lib/smess/outputs/mm7.rb +122 -0
- data/lib/smess/outputs/smsglobal.rb +70 -0
- data/lib/smess/outputs/test.rb +31 -0
- data/lib/smess/outputs/twilio.rb +69 -0
- data/lib/smess/sms.rb +25 -0
- data/lib/smess/utils.rb +91 -0
- data/lib/smess/version.rb +3 -0
- data/lib/string.rb +122 -0
- metadata +192 -0
@@ -0,0 +1,155 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'httpi'
|
3
|
+
|
4
|
+
module Smess
|
5
|
+
class Mblox
|
6
|
+
include Smess::Logging
|
7
|
+
|
8
|
+
def deliver_sms(sms)
|
9
|
+
return false unless sms.kind_of? Sms
|
10
|
+
|
11
|
+
parts = Smess.split_sms(sms.message.strip_nongsm_chars)
|
12
|
+
return false if parts[0].empty?
|
13
|
+
|
14
|
+
xml_params = {
|
15
|
+
subscriber_number: sms.to,
|
16
|
+
message: ""
|
17
|
+
}
|
18
|
+
# if we have several parts, send them as concatenated sms
|
19
|
+
if parts.length > 1
|
20
|
+
logger.info "Num Parts: #{parts.length.to_s}"
|
21
|
+
# create concat-sms UDH
|
22
|
+
ref_id = Random.new.rand(255).to_s(16).rjust(2,"0")
|
23
|
+
num_parts = parts.length
|
24
|
+
xml_params[:udh] = ":05:00:03:#{ref_id}:#{num_parts.to_s(16).rjust(2,'0')}:01" # {050003}{ff}{02}{01} {concat-command}{id to link all parts}{total num parts}{num of current part}
|
25
|
+
end
|
26
|
+
|
27
|
+
xml_params[:message] = parts.shift
|
28
|
+
# send first SMS... the one we return the result from...
|
29
|
+
result = send_one_sms( xml_params )
|
30
|
+
result[:data][:text] = sms.message.strip_nongsm_chars
|
31
|
+
|
32
|
+
|
33
|
+
# send additional parts if we have them
|
34
|
+
if parts.length > 0 && result[:response_code] != "-1"
|
35
|
+
more_results = []
|
36
|
+
parts.each_with_index do |part, i|
|
37
|
+
xml_params[:message] = part
|
38
|
+
xml_params[:udh] = ":05:00:03:#{ref_id}:#{num_parts.to_s(16).rjust(2,'0')}:#{(i+2).to_s(16).rjust(2,'0')}"
|
39
|
+
more_results << send_one_sms( xml_params )
|
40
|
+
end
|
41
|
+
# we don't actually return the status for any of these which is cheating
|
42
|
+
logger.info more_results
|
43
|
+
end
|
44
|
+
|
45
|
+
result
|
46
|
+
end
|
47
|
+
|
48
|
+
|
49
|
+
def send_one_sms(xml_params)
|
50
|
+
xml = xml_data_for(xml_params)
|
51
|
+
body = "XMLDATA="+URI::encode( xml.encode("ISO-8859-1") ) # escape
|
52
|
+
|
53
|
+
request = HTTPI::Request.new
|
54
|
+
request.url = 'https://xml4.us.mblox.com:443/send'
|
55
|
+
request.headers["Content-Type"] = "application/x-www-form-urlencoded"
|
56
|
+
request.body = body
|
57
|
+
|
58
|
+
begin
|
59
|
+
HTTPI.log_level = :debug
|
60
|
+
response = HTTPI.post request
|
61
|
+
|
62
|
+
rescue Exception => e
|
63
|
+
logger.warn response
|
64
|
+
# connection problem or some error
|
65
|
+
result = {
|
66
|
+
response_code: '-1',
|
67
|
+
response: {
|
68
|
+
temporaryError: 'true',
|
69
|
+
responseCode: e.code,
|
70
|
+
responseText: e.message
|
71
|
+
},
|
72
|
+
data: {
|
73
|
+
to: xml_params[:subscriber_number],
|
74
|
+
text: xml_params[:message],
|
75
|
+
from: ENV["SMESS_MBLOX_SURE_ROUTE_SHORTCODE"]
|
76
|
+
}
|
77
|
+
}
|
78
|
+
else
|
79
|
+
response_data = Nori.parse(response.body)
|
80
|
+
response_code = response_code_for response_data
|
81
|
+
# Successful response
|
82
|
+
result = {
|
83
|
+
message_id: @message_id,
|
84
|
+
response_code: response_code,
|
85
|
+
response: response_data,
|
86
|
+
destination_address: xml_params[:subscriber_number],
|
87
|
+
data: {
|
88
|
+
to: xml_params[:subscriber_number],
|
89
|
+
text: xml_params[:message],
|
90
|
+
from: ENV["SMESS_MBLOX_SURE_ROUTE_SHORTCODE"]
|
91
|
+
}
|
92
|
+
}
|
93
|
+
end
|
94
|
+
result
|
95
|
+
end
|
96
|
+
|
97
|
+
|
98
|
+
def xml_data_for(xml_params)
|
99
|
+
Gyoku.convert_symbols_to :camelcase
|
100
|
+
'<?xml version="1.0"?>'+
|
101
|
+
Gyoku.xml( hash_data_for(xml_params) )
|
102
|
+
end
|
103
|
+
|
104
|
+
def hash_data_for(xml_params)
|
105
|
+
rand = (SecureRandom.random_number*100000000).to_i
|
106
|
+
@message_id = rand # "#{Time.now.to_i}#{xml_params.to}#{rand}"
|
107
|
+
|
108
|
+
xml_hash = {
|
109
|
+
notification_request: {
|
110
|
+
notification_header: {
|
111
|
+
partner_name: ENV["SMESS_MBLOX_SURE_ROUTE_USER"],
|
112
|
+
partner_password: ENV["SMESS_MBLOX_SURE_ROUTE_PASS"]
|
113
|
+
},
|
114
|
+
notification_list: {
|
115
|
+
notification: {
|
116
|
+
message: xml_params[:message],
|
117
|
+
profile: ENV["SMESS_MBLOX_SURE_ROUTE_PROFILE_ID"],
|
118
|
+
udh: xml_params.fetch(:udh,""),
|
119
|
+
sender_i_d: ENV["SMESS_MBLOX_SURE_ROUTE_SHORTCODE"],
|
120
|
+
# expire_date: "",
|
121
|
+
# operator: "",
|
122
|
+
# tariff: "",
|
123
|
+
subscriber: {
|
124
|
+
subscriber_number: xml_params[:subscriber_number],
|
125
|
+
session_id: ""
|
126
|
+
},
|
127
|
+
# tags: '<Tag Name=”Number”>56</Tag><Tag Name=”City”>Paris</Tag>',
|
128
|
+
# service_desc: "",
|
129
|
+
# content_type: "",
|
130
|
+
service_id: ENV["SMESS_MBLOX_SURE_ROUTE_SID"],
|
131
|
+
attributes!: { sender_i_d: { "Type" => "Shortcode" } }
|
132
|
+
},
|
133
|
+
attributes!: { notification: { "SequenceNumber" => "1", "MessageType" => "SMS" } } # FlashSMS
|
134
|
+
},
|
135
|
+
attributes!: { notification_list: { "BatchID" => @message_id } }
|
136
|
+
},
|
137
|
+
attributes!: { notification_request: { "Version" => "3.5" } }
|
138
|
+
}
|
139
|
+
xml_hash[:notification_request][:notification_list][:notification].delete :udh unless xml_params.key? :udh
|
140
|
+
xml_hash
|
141
|
+
end
|
142
|
+
|
143
|
+
def response_code_for(response_data)
|
144
|
+
request_result_code = response_data[:notification_request_result][:notification_result_header][:request_result_code] rescue "-1"
|
145
|
+
return "request:#{request_result_code}" unless request_result_code == "0"
|
146
|
+
|
147
|
+
notification_result_code = response_data[:notification_request_result][:notification_result_list][:notification_result][:notification_result_code] rescue "-1"
|
148
|
+
return "notification:#{notification_result_code}" unless notification_result_code == "0"
|
149
|
+
|
150
|
+
subscriber_result_code = response_data[:notification_request_result][:notification_result_list][:notification_result][:subscriber_result][:subscriber_result_code] rescue "-1"
|
151
|
+
(subscriber_result_code == "0") ? subscriber_result_code : "subscriber:#{subscriber_result_code}"
|
152
|
+
end
|
153
|
+
|
154
|
+
end
|
155
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
module Smess
|
2
|
+
|
3
|
+
class Mm7
|
4
|
+
include Smess::Logging
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
|
8
|
+
# SOAP defaults
|
9
|
+
@endpoint = "http://example.com/mms/mm7"
|
10
|
+
|
11
|
+
# MM7 defaults
|
12
|
+
@mm7ns = "http://www.3gpp.org/ftp/Specs/archive/23_series/23.140/schema/REL-6-MM7-1-2"
|
13
|
+
@mm7header = {
|
14
|
+
"mm7:TransactionID" => Time.now.strftime('%Y%m%d%H%M%S'),
|
15
|
+
:attributes! => {
|
16
|
+
"mm7:TransactionID" => {
|
17
|
+
"xmlns:mm7" => @mm7ns
|
18
|
+
}
|
19
|
+
}
|
20
|
+
}
|
21
|
+
@mm7body = {
|
22
|
+
"mm7:MM7Version" => "6.5.0",
|
23
|
+
"mm7:SenderIdentification" => {
|
24
|
+
"mm7:VASPID" => "",
|
25
|
+
"mm7:SenderAddress" => {}
|
26
|
+
},
|
27
|
+
"mm7:Recipients" => {
|
28
|
+
"mm7:To" => {
|
29
|
+
"mm7:Number" => ""
|
30
|
+
}
|
31
|
+
},
|
32
|
+
"mm7:ServiceCode" => "",
|
33
|
+
"mm7:LinkedID" => "",
|
34
|
+
"mm7:DeliveryReport" => "true",
|
35
|
+
"mm7:Subject" => "",
|
36
|
+
"mm7:Content/" => nil,
|
37
|
+
:attributes! => {"mm7:Content/" => {"href" => "cid:attachment_1"}}
|
38
|
+
}
|
39
|
+
|
40
|
+
@credentials = nil
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def perform_operator_adaptation(msisdn)
|
47
|
+
# implement this method to do modify the message output
|
48
|
+
end
|
49
|
+
|
50
|
+
def soap_client
|
51
|
+
Savon.configure do |config|
|
52
|
+
config.log_level = :info
|
53
|
+
config.raise_errors = false
|
54
|
+
end
|
55
|
+
|
56
|
+
|
57
|
+
endpoint = @endpoint
|
58
|
+
mm7ns = @mm7ns
|
59
|
+
credentials = @credentials
|
60
|
+
|
61
|
+
client = Savon::Client.new do |wsdl, http|
|
62
|
+
wsdl.endpoint = endpoint
|
63
|
+
wsdl.namespace = mm7ns
|
64
|
+
|
65
|
+
http.open_timeout = 15
|
66
|
+
http.read_timeout = 60 # Won't set read timeout to 10 minutes!! (IPX are crazy)
|
67
|
+
http.auth.basic credentials[:name], credentials[:pass] unless credentials.nil?
|
68
|
+
end
|
69
|
+
client
|
70
|
+
end
|
71
|
+
|
72
|
+
def get_response_hash_from(response)
|
73
|
+
response.to_hash[:submit_rsp]
|
74
|
+
end
|
75
|
+
|
76
|
+
def get_message_id_from hash
|
77
|
+
hash[:message_id] rescue ''
|
78
|
+
end
|
79
|
+
|
80
|
+
def parse_response(response)
|
81
|
+
logger.debug ' --- '
|
82
|
+
logger.debug response.http_error.to_hash
|
83
|
+
logger.debug ' --- '
|
84
|
+
# logger.debug response.success?
|
85
|
+
# logger.debug ' --- '
|
86
|
+
# logger.debug "#{response.http_error}, #{response.soap_fault }"
|
87
|
+
# logger.debug ' --- '
|
88
|
+
|
89
|
+
unless response.success?
|
90
|
+
result = {
|
91
|
+
:response_code => "-1",
|
92
|
+
:response => {
|
93
|
+
:responseCode => "-1",
|
94
|
+
:responseText => response.http_error? ? response.http_error.to_hash : response.soap_fault.to_hash
|
95
|
+
}
|
96
|
+
}
|
97
|
+
|
98
|
+
return result
|
99
|
+
end
|
100
|
+
|
101
|
+
hash = get_response_hash_from response
|
102
|
+
message_id = get_message_id_from hash
|
103
|
+
|
104
|
+
# any 1000 range code is a success, anything else is an error.
|
105
|
+
status_code = hash[:status][:status_code] rescue '-1'
|
106
|
+
if (1000..1999) === status_code.to_i
|
107
|
+
response_code = "0"
|
108
|
+
else
|
109
|
+
response_code = status_code
|
110
|
+
# LOG error here?
|
111
|
+
end
|
112
|
+
|
113
|
+
result = {
|
114
|
+
:message_id => message_id,
|
115
|
+
:response_code => response_code,
|
116
|
+
:response => hash
|
117
|
+
}
|
118
|
+
|
119
|
+
end
|
120
|
+
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'httpi'
|
3
|
+
|
4
|
+
module Smess
|
5
|
+
class Smsglobal
|
6
|
+
include Smess::Logging
|
7
|
+
|
8
|
+
def deliver_sms(sms)
|
9
|
+
return false unless sms.kind_of? Sms
|
10
|
+
|
11
|
+
url = "https://www.smsglobal.com/http-api.php"
|
12
|
+
from = sms.originator || ENV["SMESS_SMSGLOBAL_SENDER_ID"]
|
13
|
+
|
14
|
+
params = {
|
15
|
+
action: "sendsms",
|
16
|
+
user: ENV["SMESS_SMSGLOBAL_USER"],
|
17
|
+
password: ENV["SMESS_SMSGLOBAL_PASS"],
|
18
|
+
from: from,
|
19
|
+
to: sms.to,
|
20
|
+
text: sms.message.strip_nongsm_chars,
|
21
|
+
maxsplit: "3"
|
22
|
+
}
|
23
|
+
|
24
|
+
request = HTTPI::Request.new
|
25
|
+
request.url = url
|
26
|
+
request.body = params
|
27
|
+
|
28
|
+
begin
|
29
|
+
HTTPI.log_level = :debug
|
30
|
+
response = HTTPI.post request
|
31
|
+
|
32
|
+
rescue Exception => e
|
33
|
+
logger.warn response
|
34
|
+
# connection problem or some error
|
35
|
+
result = {
|
36
|
+
response_code: '-1',
|
37
|
+
response: {
|
38
|
+
temporaryError: 'true',
|
39
|
+
responseCode: e.code,
|
40
|
+
responseText: e.message
|
41
|
+
},
|
42
|
+
data: {
|
43
|
+
to: sms.to,
|
44
|
+
text: sms.message.strip_nongsm_chars,
|
45
|
+
from: from
|
46
|
+
}
|
47
|
+
}
|
48
|
+
else
|
49
|
+
first_response = response.body.split(/\r\n/).first.split(";")
|
50
|
+
response_code = first_response.first.split(':').last.to_i
|
51
|
+
message_id = first_response.last.split('SMSGlobalMsgID:').last
|
52
|
+
|
53
|
+
# Successful response
|
54
|
+
result = {
|
55
|
+
message_id: message_id,
|
56
|
+
response_code: response_code.to_s,
|
57
|
+
response: response.body,
|
58
|
+
destination_address: sms.to,
|
59
|
+
data: {
|
60
|
+
to: sms.to,
|
61
|
+
text: sms.message.strip_nongsm_chars,
|
62
|
+
from: from
|
63
|
+
}
|
64
|
+
}
|
65
|
+
end
|
66
|
+
result
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Smess
|
2
|
+
class Test
|
3
|
+
|
4
|
+
@@instance = nil
|
5
|
+
@delivery_result = {
|
6
|
+
:response_code => '-1',
|
7
|
+
:response => {
|
8
|
+
:temporaryError =>'true',
|
9
|
+
:responseCode => '-1',
|
10
|
+
:responseText => 'No delivery result set in test output object.'
|
11
|
+
}
|
12
|
+
}
|
13
|
+
class << self; attr_accessor :delivery_result end
|
14
|
+
attr_reader :sms, :mms
|
15
|
+
|
16
|
+
def initialize
|
17
|
+
@@instance = self
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.instance
|
21
|
+
@@instance
|
22
|
+
end
|
23
|
+
|
24
|
+
def deliver_sms(sms)
|
25
|
+
return false unless sms.kind_of? Sms
|
26
|
+
@sms = sms
|
27
|
+
self.class.delivery_result
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'twilio-ruby'
|
2
|
+
|
3
|
+
module Smess
|
4
|
+
class Twilio
|
5
|
+
include Smess::Logging
|
6
|
+
|
7
|
+
def deliver_sms(sms)
|
8
|
+
return false unless sms.kind_of? Sms
|
9
|
+
|
10
|
+
parts = Smess.separate_sms sms.message.strip_nongsm_chars
|
11
|
+
return false if parts[0].empty?
|
12
|
+
|
13
|
+
@client = ::Twilio::REST::Client.new(ENV["SMESS_TWILIO_SID"], ENV["SMESS_TWILIO_AUTH_TOKEN"])
|
14
|
+
|
15
|
+
|
16
|
+
results = []
|
17
|
+
while parts.length > 0
|
18
|
+
results << send_one_sms(sms, parts.shift)
|
19
|
+
end
|
20
|
+
results[0][:data][:text] = sms.message.strip_nongsm_chars
|
21
|
+
results[0]
|
22
|
+
end
|
23
|
+
|
24
|
+
def send_one_sms(sms, message)
|
25
|
+
begin
|
26
|
+
response = @client.account.sms.messages.create({
|
27
|
+
from: ENV["SMESS_TWILIO_FROM"],
|
28
|
+
to: "+#{sms.to}",
|
29
|
+
body: message,
|
30
|
+
status_callback: ENV["SMESS_TWILIO_CALLBACK_URL"]
|
31
|
+
})
|
32
|
+
rescue Exception => e
|
33
|
+
logger.warn response
|
34
|
+
# connection problem or some error
|
35
|
+
result = {
|
36
|
+
response_code: '-1',
|
37
|
+
response: {
|
38
|
+
temporaryError: 'true',
|
39
|
+
responseCode: e.code,
|
40
|
+
responseText: e.message
|
41
|
+
},
|
42
|
+
data: {
|
43
|
+
to: sms.to,
|
44
|
+
text: message,
|
45
|
+
from: ENV["SMESS_TWILIO_FROM"]
|
46
|
+
}
|
47
|
+
}
|
48
|
+
else
|
49
|
+
response_code = response.status
|
50
|
+
response_code = "0" unless response.status == "failed"
|
51
|
+
# Successful response
|
52
|
+
result = {
|
53
|
+
message_id: response.sid,
|
54
|
+
response_code: response_code.to_s,
|
55
|
+
response: MultiJson.load(@client.last_response.body),
|
56
|
+
destination_address: sms.to,
|
57
|
+
data: {
|
58
|
+
to: sms.to,
|
59
|
+
text: message,
|
60
|
+
from: ENV["SMESS_TWILIO_FROM"]
|
61
|
+
}
|
62
|
+
}
|
63
|
+
end
|
64
|
+
result
|
65
|
+
end
|
66
|
+
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|