sms_broker 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +21 -0
- data/.rspec +3 -0
- data/Gemfile +9 -0
- data/README.md +56 -0
- data/Rakefile +5 -0
- data/lib/sms_broker/client/base.rb +24 -0
- data/lib/sms_broker/client/nexmo.rb +44 -0
- data/lib/sms_broker/client/response/error.rb +32 -0
- data/lib/sms_broker/client/response/nexmo_error.rb +35 -0
- data/lib/sms_broker/client/response/nexmo_success.rb +37 -0
- data/lib/sms_broker/client/response/success.rb +40 -0
- data/lib/sms_broker/client/response/twilio_error.rb +51 -0
- data/lib/sms_broker/client/response/twilio_success.rb +41 -0
- data/lib/sms_broker/client/twilio.rb +49 -0
- data/lib/sms_broker/configuration.rb +56 -0
- data/lib/sms_broker/exceptions/invalid_service.rb +9 -0
- data/lib/sms_broker/exceptions/invalid_setup.rb +11 -0
- data/lib/sms_broker/message_sender.rb +81 -0
- data/lib/sms_broker/service.rb +50 -0
- data/lib/sms_broker/setup.rb +82 -0
- data/lib/sms_broker/version.rb +5 -0
- data/lib/sms_broker.rb +25 -0
- data/sms_broker.gemspec +29 -0
- data/spec/sms_broker/nexmo_spec.rb +92 -0
- data/spec/sms_broker/setup_spec.rb +161 -0
- data/spec/sms_broker/sms_broker_spec.rb +145 -0
- data/spec/sms_broker/twilio_spec.rb +122 -0
- data/spec/spec_helper.rb +29 -0
- data/spec/support/nexmo_helpers.rb +150 -0
- metadata +176 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 0b28741c2cf164576894b6b876444ae070f7303f
|
4
|
+
data.tar.gz: f0549e93ac7bbe3b87fff76d101a6f06152c192a
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: fcd96dffdafb77800af5a3798be97beff7ec3a921fcdbc061c0ad1f069f5ea127a976e0dace58624742c5ad510b400fadd866a3d6eba884d22a179d7577ae3c2
|
7
|
+
data.tar.gz: c6b9c850c13c0c43e2cc324b12fb6bf884bda46a3f0ab709a6ca13a06df0412a82341fd7e4e87d07aa0d47dfd100352906f9a685f6bf9a464c342a1c0cdccf10
|
data/.gitignore
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
.bundle
|
4
|
+
.config
|
5
|
+
.yardoc
|
6
|
+
.rvmrc
|
7
|
+
.ruby-version
|
8
|
+
.ruby-gemset
|
9
|
+
Gemfile.lock
|
10
|
+
InstalledFiles
|
11
|
+
_yardoc
|
12
|
+
coverage
|
13
|
+
doc/
|
14
|
+
lib/bundler/man
|
15
|
+
pkg
|
16
|
+
rdoc
|
17
|
+
spec/reports
|
18
|
+
spec/support/services_keys.yml
|
19
|
+
test/tmp
|
20
|
+
test/version_tmp
|
21
|
+
tmp
|
data/.rspec
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
Sms broker
|
2
|
+
==========================
|
3
|
+
|
4
|
+
### Usage
|
5
|
+
|
6
|
+
```ruby
|
7
|
+
SmsBroker.setup do |config|
|
8
|
+
config.services ['nexmo', 'twilio']
|
9
|
+
|
10
|
+
config.default_service 'nexmo'
|
11
|
+
|
12
|
+
config.nexmo_setup \
|
13
|
+
key: 'NEXMO_API_KEY',
|
14
|
+
secret: 'NEXMO_API_SECRET',
|
15
|
+
sender_id: 'NEXMO_SENDER_ID',
|
16
|
+
phone_number: 'NEXMO_PHONE_NUMBER'
|
17
|
+
|
18
|
+
config.twilio_setup \
|
19
|
+
auth_token: 'TWILIO_AUTH_TOKEN',
|
20
|
+
account_sid: 'TWILIO_ACCOUNT_SID',
|
21
|
+
sender_id: 'TWILIO_SENDER_ID',
|
22
|
+
phone_number: 'TWILIO_PHONE_NUMBER'
|
23
|
+
end
|
24
|
+
```
|
25
|
+
|
26
|
+
#### Basic usage
|
27
|
+
```ruby
|
28
|
+
message = SmsBroker.message('Get paid doing small tasks!').to('441234567890')
|
29
|
+
|
30
|
+
if message.valid?
|
31
|
+
response = message.deliver
|
32
|
+
|
33
|
+
# response.success?
|
34
|
+
# response.serialized
|
35
|
+
end
|
36
|
+
```
|
37
|
+
|
38
|
+
#### Specifying the provider
|
39
|
+
```ruby
|
40
|
+
SmsBroker.service(:twilio).message('Get paid doing small tasks!').to('441234567890')
|
41
|
+
```
|
42
|
+
|
43
|
+
###Installation
|
44
|
+
|
45
|
+
Add this line to your application's Gemfile:
|
46
|
+
|
47
|
+
gem 'sms_broker'
|
48
|
+
|
49
|
+
And then execute:
|
50
|
+
|
51
|
+
$ bundle
|
52
|
+
|
53
|
+
### Get in touch
|
54
|
+
|
55
|
+
If you have any questions, write an issue or get in touch dev@streetbees.com
|
56
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
module SmsBroker
|
2
|
+
module Client
|
3
|
+
|
4
|
+
class Base
|
5
|
+
|
6
|
+
attr_reader :name,
|
7
|
+
:client,
|
8
|
+
:options,
|
9
|
+
:sender_id,
|
10
|
+
:phone_number
|
11
|
+
|
12
|
+
def initialize(name, client)
|
13
|
+
@name = name
|
14
|
+
@client = client
|
15
|
+
end
|
16
|
+
|
17
|
+
def serialize_number(number)
|
18
|
+
"#{number}".delete("+")
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module SmsBroker
|
2
|
+
module Client
|
3
|
+
|
4
|
+
class Nexmo < Base
|
5
|
+
|
6
|
+
def initialize(options)
|
7
|
+
nexmo_options = options.dup
|
8
|
+
|
9
|
+
auth_options = {
|
10
|
+
key: nexmo_options.delete(:key),
|
11
|
+
secret: nexmo_options.delete(:secret)
|
12
|
+
}
|
13
|
+
|
14
|
+
@sender_id = nexmo_options.delete(:sender_id)
|
15
|
+
@phone_number = nexmo_options.delete(:phone_number)
|
16
|
+
|
17
|
+
super(:nexmo, ::Nexmo::Client.new(auth_options))
|
18
|
+
end
|
19
|
+
|
20
|
+
def send_message(message)
|
21
|
+
response = client.send_message \
|
22
|
+
text: message[:text],
|
23
|
+
from: serialize_number(message[:from]),
|
24
|
+
to: serialize_number(message[:to])
|
25
|
+
|
26
|
+
if success_response?(response)
|
27
|
+
Response::NexmoSuccess.new(response)
|
28
|
+
else
|
29
|
+
Response::NexmoError.new(response)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def success_response?(response)
|
36
|
+
# just looking for the first message,
|
37
|
+
# right now only one message per call
|
38
|
+
response['messages'].length > 0 && response['messages'][0]['status'] == '0'
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module SmsBroker
|
2
|
+
module Client
|
3
|
+
module Response
|
4
|
+
|
5
|
+
class Error
|
6
|
+
|
7
|
+
attr_reader :service,
|
8
|
+
:response,
|
9
|
+
:serialized
|
10
|
+
|
11
|
+
def initialize(service, response, serialized = {})
|
12
|
+
@service = service
|
13
|
+
@response = response
|
14
|
+
@serialized = { errors: serialized }
|
15
|
+
end
|
16
|
+
|
17
|
+
def success?
|
18
|
+
false
|
19
|
+
end
|
20
|
+
|
21
|
+
def invalid_sender_id?
|
22
|
+
(@serialized[:errors]['sender_id'] || {}).include?('is invalid')
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
require 'sms_broker/client/response/nexmo_error'
|
32
|
+
require 'sms_broker/client/response/twilio_error'
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module SmsBroker
|
2
|
+
module Client
|
3
|
+
module Response
|
4
|
+
|
5
|
+
class NexmoError < Error
|
6
|
+
|
7
|
+
SENDER_ID_NOT_SUPPORTED = '15'
|
8
|
+
|
9
|
+
def initialize(nexmo_response)
|
10
|
+
super :nexmo, nexmo_response, serialize_error_response(nexmo_response)
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def serialize_error_response(nexmo_response)
|
16
|
+
errors = {}.tap do |hash|
|
17
|
+
nexmo_response['messages'].each do |message|
|
18
|
+
hash[message['status']] = [message['error-text']]
|
19
|
+
end
|
20
|
+
|
21
|
+
hash
|
22
|
+
end
|
23
|
+
|
24
|
+
if errors.keys.include?(SENDER_ID_NOT_SUPPORTED)
|
25
|
+
errors['sender_id'] = ['is invalid']
|
26
|
+
end
|
27
|
+
|
28
|
+
errors
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module SmsBroker
|
2
|
+
module Client
|
3
|
+
module Response
|
4
|
+
|
5
|
+
class NexmoSuccess < Success
|
6
|
+
|
7
|
+
def initialize(nexmo_response)
|
8
|
+
super :nexmo, nexmo_response, serialize(nexmo_response)
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def serialize(response)
|
14
|
+
single_response = response['messages'][0]
|
15
|
+
|
16
|
+
{
|
17
|
+
to: single_response['to'],
|
18
|
+
from: single_response['from'],
|
19
|
+
message_id: single_response['message-id'],
|
20
|
+
raw: {
|
21
|
+
to: single_response['to'],
|
22
|
+
from: single_response['from'],
|
23
|
+
status: single_response['status'],
|
24
|
+
network: single_response['network'],
|
25
|
+
message_id: single_response['message-id'],
|
26
|
+
client_ref: single_response['client-ref'],
|
27
|
+
remaining_balance: single_response['remaining-balance'],
|
28
|
+
message_price: single_response['message-price']
|
29
|
+
}
|
30
|
+
}
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module SmsBroker
|
2
|
+
module Client
|
3
|
+
module Response
|
4
|
+
|
5
|
+
class Success
|
6
|
+
|
7
|
+
attr_reader :raw,
|
8
|
+
:service,
|
9
|
+
:serialized
|
10
|
+
|
11
|
+
def initialize(service, response, serialized)
|
12
|
+
@raw = response
|
13
|
+
@service = service
|
14
|
+
@serialized = serialized
|
15
|
+
end
|
16
|
+
|
17
|
+
def success?
|
18
|
+
true
|
19
|
+
end
|
20
|
+
|
21
|
+
def to
|
22
|
+
serialized[:to]
|
23
|
+
end
|
24
|
+
|
25
|
+
def from
|
26
|
+
serialized[:from]
|
27
|
+
end
|
28
|
+
|
29
|
+
def message_id
|
30
|
+
serialized[:message_id]
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
require 'sms_broker/client/response/nexmo_success'
|
40
|
+
require 'sms_broker/client/response/twilio_success'
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module SmsBroker
|
2
|
+
module Client
|
3
|
+
module Response
|
4
|
+
|
5
|
+
class TwilioError < Error
|
6
|
+
|
7
|
+
SENDER_ID_NOT_SUPPORTED = '21212'
|
8
|
+
|
9
|
+
def initialize(twilio_response)
|
10
|
+
super :twilio, twilio_response, serialize(twilio_response)
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def serialize(twilio_response)
|
16
|
+
if twilio_response.is_a?(::Twilio::REST::RequestError)
|
17
|
+
serialize_exeception_errors(twilio_response)
|
18
|
+
else
|
19
|
+
serialize_response_error(twilio_response)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def serialize_response_error(response)
|
24
|
+
errors = {
|
25
|
+
"#{response.error_code}" => [response.error_message]
|
26
|
+
}
|
27
|
+
|
28
|
+
if "#{response.error_code}" == SENDER_ID_NOT_SUPPORTED
|
29
|
+
errors['sender_id'] = ['is invalid']
|
30
|
+
end
|
31
|
+
|
32
|
+
errors
|
33
|
+
end
|
34
|
+
|
35
|
+
def serialize_exeception_errors(exception)
|
36
|
+
errors = {
|
37
|
+
"#{exception.code}" => [exception.message]
|
38
|
+
}
|
39
|
+
|
40
|
+
if "#{exception.code}" == SENDER_ID_NOT_SUPPORTED
|
41
|
+
errors['sender_id'] = ['is invalid']
|
42
|
+
end
|
43
|
+
|
44
|
+
errors
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module SmsBroker
|
2
|
+
module Client
|
3
|
+
module Response
|
4
|
+
|
5
|
+
class TwilioSuccess < Success
|
6
|
+
|
7
|
+
def initialize(twilio_response)
|
8
|
+
super :twilio, twilio_response, serialize(twilio_response)
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def serialize(response)
|
14
|
+
{
|
15
|
+
to: response.to,
|
16
|
+
from: response.from,
|
17
|
+
message_id: response.sid,
|
18
|
+
raw: {
|
19
|
+
to: response.to,
|
20
|
+
sid: response.sid,
|
21
|
+
uri: response.uri,
|
22
|
+
from: response.from,
|
23
|
+
body: response.body,
|
24
|
+
price: response.price,
|
25
|
+
status: response.status,
|
26
|
+
price_unit: response.price_unit,
|
27
|
+
error_code: response.error_code,
|
28
|
+
account_sid: response.account_sid,
|
29
|
+
api_version: response.api_version,
|
30
|
+
date_created: response.date_created,
|
31
|
+
error_message: response.error_message,
|
32
|
+
messaging_service_sid: response.messaging_service_sid
|
33
|
+
}
|
34
|
+
}
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module SmsBroker
|
2
|
+
module Client
|
3
|
+
|
4
|
+
class Twilio < Base
|
5
|
+
|
6
|
+
def initialize(options)
|
7
|
+
twilio_options = options.dup
|
8
|
+
|
9
|
+
auth_options = {
|
10
|
+
account_sid: twilio_options.delete(:account_sid),
|
11
|
+
auth_token: twilio_options.delete(:auth_token)
|
12
|
+
}
|
13
|
+
|
14
|
+
@sender_id = twilio_options.delete(:sender_id)
|
15
|
+
@phone_number = twilio_options.delete(:phone_number)
|
16
|
+
|
17
|
+
super \
|
18
|
+
:twilio,
|
19
|
+
::Twilio::REST::Client.new(auth_options[:account_sid], auth_options[:auth_token])
|
20
|
+
end
|
21
|
+
|
22
|
+
def send_message(message)
|
23
|
+
begin
|
24
|
+
response = client.messages.create \
|
25
|
+
body: message[:text],
|
26
|
+
from: serialize_number(message[:from]),
|
27
|
+
to: serialize_number(message[:to])
|
28
|
+
|
29
|
+
if failed_response?(response)
|
30
|
+
Response::TwilioError.new(response)
|
31
|
+
else
|
32
|
+
Response::TwilioSuccess.new(response)
|
33
|
+
end
|
34
|
+
|
35
|
+
rescue ::Twilio::REST::RequestError => exception
|
36
|
+
Response::TwilioError.new(exception)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def failed_response?(response)
|
43
|
+
['undelivered', 'failed'].include?(response.status)
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'sms_broker/setup'
|
2
|
+
require 'sms_broker/exceptions/invalid_setup'
|
3
|
+
|
4
|
+
module SmsBroker
|
5
|
+
|
6
|
+
module Configuration
|
7
|
+
|
8
|
+
@@configuration = nil
|
9
|
+
|
10
|
+
def default_service
|
11
|
+
configuration[:default_service]
|
12
|
+
end
|
13
|
+
|
14
|
+
def clear_setup
|
15
|
+
@@configuration = nil
|
16
|
+
end
|
17
|
+
|
18
|
+
def configuration
|
19
|
+
exception = \
|
20
|
+
Exceptions::InvalidSetup.new('setup does not exists')
|
21
|
+
|
22
|
+
@@configuration || (raise exception)
|
23
|
+
end
|
24
|
+
|
25
|
+
def setup(&block)
|
26
|
+
setup = Setup.new
|
27
|
+
yield setup if block_given?
|
28
|
+
|
29
|
+
@@configuration = setup.options
|
30
|
+
|
31
|
+
setup
|
32
|
+
end
|
33
|
+
|
34
|
+
def setup!(&block)
|
35
|
+
setup = Setup.new
|
36
|
+
yield setup if block_given?
|
37
|
+
|
38
|
+
unless setup.valid?
|
39
|
+
exception = \
|
40
|
+
Exceptions::InvalidSetup.new('setup is invalid, check exception.errors')
|
41
|
+
|
42
|
+
exception.errors = setup.errors
|
43
|
+
|
44
|
+
raise exception
|
45
|
+
end
|
46
|
+
|
47
|
+
@@configuration = setup.options
|
48
|
+
|
49
|
+
setup
|
50
|
+
end
|
51
|
+
|
52
|
+
extend self
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
module SmsBroker
|
2
|
+
|
3
|
+
class MessageSender
|
4
|
+
|
5
|
+
attr_reader :client,
|
6
|
+
:errors
|
7
|
+
|
8
|
+
def initialize(client)
|
9
|
+
@client = client
|
10
|
+
end
|
11
|
+
|
12
|
+
def to(number)
|
13
|
+
@message_to = number
|
14
|
+
|
15
|
+
self
|
16
|
+
end
|
17
|
+
|
18
|
+
def message(text)
|
19
|
+
@message_text = text
|
20
|
+
|
21
|
+
self
|
22
|
+
end
|
23
|
+
|
24
|
+
def deliver
|
25
|
+
unless valid?
|
26
|
+
return Client::Response::Error.new(client.name, errors, errors)
|
27
|
+
end
|
28
|
+
|
29
|
+
response = client.send_message(build_message)
|
30
|
+
|
31
|
+
if should_try_again_with_phone_number?(response)
|
32
|
+
return client.send_message(build_message(:phone_number))
|
33
|
+
end
|
34
|
+
|
35
|
+
response
|
36
|
+
end
|
37
|
+
|
38
|
+
def valid?
|
39
|
+
schema = {
|
40
|
+
message: Compel.string.required.max_length(140),
|
41
|
+
to: Compel.string.required
|
42
|
+
}
|
43
|
+
|
44
|
+
object = {
|
45
|
+
message: @message_text,
|
46
|
+
to: @message_to
|
47
|
+
}
|
48
|
+
|
49
|
+
result = Compel.hash.keys(schema).validate(object)
|
50
|
+
|
51
|
+
@errors = result.errors
|
52
|
+
|
53
|
+
result.valid?
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def build_message(from = :sender_id)
|
59
|
+
sender = \
|
60
|
+
if client.sender_id && from == :sender_id
|
61
|
+
client.sender_id
|
62
|
+
else
|
63
|
+
client.phone_number
|
64
|
+
end
|
65
|
+
|
66
|
+
{
|
67
|
+
text: @message_text,
|
68
|
+
from: sender,
|
69
|
+
to: @message_to
|
70
|
+
}
|
71
|
+
end
|
72
|
+
|
73
|
+
def should_try_again_with_phone_number?(response)
|
74
|
+
response.is_a?(Client::Response::Error) &&
|
75
|
+
response.invalid_sender_id? &&
|
76
|
+
!!client.sender_id
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'sms_broker/message_sender'
|
2
|
+
|
3
|
+
require 'sms_broker/client/base'
|
4
|
+
require 'sms_broker/client/nexmo'
|
5
|
+
require 'sms_broker/client/twilio'
|
6
|
+
|
7
|
+
require 'sms_broker/exceptions/invalid_service'
|
8
|
+
|
9
|
+
module SmsBroker
|
10
|
+
|
11
|
+
CLIENTS = {
|
12
|
+
nexmo: Client::Nexmo,
|
13
|
+
twilio: Client::Twilio
|
14
|
+
}
|
15
|
+
|
16
|
+
class Service
|
17
|
+
|
18
|
+
def self.get(name)
|
19
|
+
options = service_configuration(name)
|
20
|
+
|
21
|
+
result = Service.validate(name, options)
|
22
|
+
|
23
|
+
unless result.valid?
|
24
|
+
raise Exceptions::InvalidService, { name.to_sym => result.errors }
|
25
|
+
end
|
26
|
+
|
27
|
+
new CLIENTS[name.to_sym].new(options)
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.service_configuration(name)
|
31
|
+
SmsBroker.configuration[:services_setups][name.to_sym]
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.validate(name, options)
|
35
|
+
Setup.service_validation_schemas[name.to_sym].validate(options)
|
36
|
+
end
|
37
|
+
|
38
|
+
attr_reader :client
|
39
|
+
|
40
|
+
def initialize(client)
|
41
|
+
@client = client
|
42
|
+
end
|
43
|
+
|
44
|
+
def message(message)
|
45
|
+
MessageSender.new(client).message(message)
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|