aftalk 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.circleci/config.yml +45 -0
- data/.gitignore +35 -0
- data/.rubocop.yml +630 -0
- data/Gemfile +3 -0
- data/README.md +69 -0
- data/Rakefile +1 -0
- data/africas_talking_api.gemspec +27 -0
- data/lib/aftalk.rb +13 -0
- data/lib/aftalk/client.rb +20 -0
- data/lib/aftalk/configuration.rb +37 -0
- data/lib/aftalk/connection.rb +37 -0
- data/lib/aftalk/exceptions.rb +5 -0
- data/lib/aftalk/request.rb +41 -0
- data/lib/aftalk/response.rb +26 -0
- data/lib/aftalk/responses/send_message_response.rb +21 -0
- data/lib/aftalk/sms_message_status.rb +12 -0
- data/lib/aftalk/version.rb +3 -0
- data/spec/aftalk_spec.rb +7 -0
- data/spec/integration/send_message_spec.rb +25 -0
- data/spec/spec_helper.rb +20 -0
- data/spec/support/env_vars.rb +13 -0
- data/spec/unit/client_spec.rb +46 -0
- data/spec/unit/configuration_spec.rb +73 -0
- data/spec/unit/connection_spec.rb +67 -0
- data/spec/unit/request_spec.rb +59 -0
- data/spec/unit/response_spec.rb +27 -0
- data/spec/unit/responses/send_message_response_spec.rb +43 -0
- data/spec/unit/sms_message_status_spec.rb +23 -0
- metadata +195 -0
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
[![Build](https://circleci.com/gh/thriveglobal/aftalk.svg?&style=shield&circle-token=ade67468e07185e650dc6ba100127bce34bd10b5)](https://circleci.com/gh/thriveglobal/aftalk)
|
2
|
+
[![Maintainability](https://api.codeclimate.com/v1/badges/8d84a0d4fc06f1c49cd8/maintainability)](https://codeclimate.com/github/thriveglobal/aftalk/maintainability)
|
3
|
+
|
4
|
+
# aftalk
|
5
|
+
|
6
|
+
A Ruby wrapper for [Africa's Talking](https://africastalking.com) telephony services.
|
7
|
+
|
8
|
+
Currently the only endpoint supported is the `/messaging` POST endpoint that allows you to send SMS messages. Support for other endpoints is planned for future releases.
|
9
|
+
|
10
|
+
## Installation
|
11
|
+
|
12
|
+
Add
|
13
|
+
|
14
|
+
> `gem "aftalk"`
|
15
|
+
|
16
|
+
to your `Gemfile` and run
|
17
|
+
|
18
|
+
> `$ bundle`
|
19
|
+
|
20
|
+
or install manually by running
|
21
|
+
|
22
|
+
> `gem install aftalk`.
|
23
|
+
|
24
|
+
## Configuration
|
25
|
+
|
26
|
+
Africa's Talking needs a username and an API key to authenticate your requests. You can also toggle sandbox mode, which causes the gem to hit AF's sandbox host instead of the live API. (**Note: Use of sandbox mode requires that you provide the username `sandbox`.** This is not well documented.)
|
27
|
+
|
28
|
+
There are two ways to configure these options.
|
29
|
+
|
30
|
+
### Set environment variables
|
31
|
+
|
32
|
+
The first way to configure `aftalk` is by setting environment variables. You can do this by creating a file called `.env` in your project root and adding content like this to it:
|
33
|
+
|
34
|
+
```txt
|
35
|
+
AFRICAS_TALKING_API_KEY=abc123
|
36
|
+
AFRICAS_TALKING_USER_NAME=sandbox
|
37
|
+
AFRICAS_TALKING_SANDBOX=true
|
38
|
+
```
|
39
|
+
|
40
|
+
Replace the values with your own data. Sandbox mode will be activated if any value whatsoever is provided for `AFRICAS_TALKING_SANDBOX`. If you don't want to use sandbox mode, don't set this variable at all.
|
41
|
+
|
42
|
+
### Use `AfTalk::Configuration`
|
43
|
+
|
44
|
+
You can also configure this data in Ruby code as follows:
|
45
|
+
|
46
|
+
```ruby
|
47
|
+
AfTalk::Configuration.configure do |config|
|
48
|
+
config.api_key = "abc123"
|
49
|
+
config.sandbox = true
|
50
|
+
config.user_name = "sandbox"
|
51
|
+
end
|
52
|
+
```
|
53
|
+
|
54
|
+
Make sure this code runs before you try to use the API.
|
55
|
+
|
56
|
+
## Usage
|
57
|
+
|
58
|
+
To send an SMS:
|
59
|
+
|
60
|
+
```ruby
|
61
|
+
phone_number = "+15555555555"
|
62
|
+
message = "Hello, world!"
|
63
|
+
|
64
|
+
AfTalk.send_message(
|
65
|
+
to: phone_number,
|
66
|
+
message: message,
|
67
|
+
)
|
68
|
+
```
|
69
|
+
The `send_message` method requires `to` and `message` parameters, but also supports all optional parameters supported by the API. For a full list, see [the API docs](http://docs.africastalking.com/sms/sending/).
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,27 @@
|
|
1
|
+
lib = File.expand_path("../lib", __FILE__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
require "aftalk/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "aftalk"
|
7
|
+
spec.version = AfTalk::VERSION
|
8
|
+
spec.email = ["techadmin@thriveglobal.com"]
|
9
|
+
spec.summary = "API wrapper for Africa's Talking services"
|
10
|
+
spec.description = <<-DESC
|
11
|
+
A Ruby wrapper for Africa's Talking telephony services (https://africastalking.com)
|
12
|
+
DESC
|
13
|
+
spec.homepage = "https://github.com/thriveglobal/africas_talking_api"
|
14
|
+
spec.license = "MIT"
|
15
|
+
spec.authors = ["Thrive Global Engineering Team"]
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
18
|
+
|
19
|
+
spec.add_development_dependency "bundler", "~> 1.15"
|
20
|
+
spec.add_dependency "dotenv", "~>2.2"
|
21
|
+
spec.add_dependency "faraday", "~> 0.13"
|
22
|
+
spec.add_dependency "faraday_middleware", "~> 0.12"
|
23
|
+
spec.add_development_dependency "rake", "~> 12"
|
24
|
+
spec.add_development_dependency "rspec", "~> 3.7"
|
25
|
+
spec.add_development_dependency "rspec_junit_formatter", "~> 0.3"
|
26
|
+
spec.add_development_dependency "vcr", "~> 3.0"
|
27
|
+
end
|
data/lib/aftalk.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require "dotenv"
|
2
|
+
Dotenv.load
|
3
|
+
|
4
|
+
APP_PATH = File.expand_path("../", __FILE__) + "/aftalk"
|
5
|
+
|
6
|
+
module AfTalk
|
7
|
+
autoload(:Response, "#{APP_PATH}/response.rb")
|
8
|
+
autoload(:Client, "#{APP_PATH}/client.rb")
|
9
|
+
|
10
|
+
extend AfTalk::Client
|
11
|
+
end
|
12
|
+
|
13
|
+
Dir["#{APP_PATH}/**/*.rb"].each { |file| require file }
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module AfTalk
|
2
|
+
module Client
|
3
|
+
def send_message(to:, message:, **options)
|
4
|
+
messaging_params = { to: to, message: message }.merge(options)
|
5
|
+
response = post("/messaging", messaging_params)
|
6
|
+
|
7
|
+
AfTalk::SendMessageResponse.new(response)
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def post(endpoint, **options)
|
13
|
+
request.post(endpoint, options)
|
14
|
+
end
|
15
|
+
|
16
|
+
def request
|
17
|
+
AfTalk::Request
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module AfTalk
|
2
|
+
class Configuration
|
3
|
+
DEFAULT_VERSION = "version1".freeze
|
4
|
+
|
5
|
+
class << self
|
6
|
+
attr_writer :api_key, :sandbox, :user_name
|
7
|
+
|
8
|
+
def configure
|
9
|
+
yield self
|
10
|
+
connection.clear
|
11
|
+
self
|
12
|
+
end
|
13
|
+
|
14
|
+
def version
|
15
|
+
DEFAULT_VERSION
|
16
|
+
end
|
17
|
+
|
18
|
+
def api_key
|
19
|
+
@api_key || ENV["AFRICAS_TALKING_API_KEY"]
|
20
|
+
end
|
21
|
+
|
22
|
+
def sandbox
|
23
|
+
@sandbox || ENV["AFRICAS_TALKING_SANDBOX"]
|
24
|
+
end
|
25
|
+
|
26
|
+
def user_name
|
27
|
+
@user_name || ENV["AFRICAS_TALKING_USER_NAME"]
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def connection
|
33
|
+
AfTalk::Connection
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require "faraday"
|
2
|
+
require "faraday_middleware"
|
3
|
+
|
4
|
+
module AfTalk
|
5
|
+
class Connection
|
6
|
+
URL = "https://api.africastalking.com".freeze
|
7
|
+
SANDBOX_URL = "https://api.sandbox.africastalking.com".freeze
|
8
|
+
|
9
|
+
def self.build
|
10
|
+
@_connection ||= new.build
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.clear
|
14
|
+
@_connection = nil
|
15
|
+
end
|
16
|
+
|
17
|
+
def build
|
18
|
+
Faraday.new(
|
19
|
+
url: api_url,
|
20
|
+
headers: { apiKey: AfTalk::Configuration.api_key,
|
21
|
+
accept: "application/json" },
|
22
|
+
) do |connection|
|
23
|
+
connection.request :url_encoded
|
24
|
+
connection.response :json,
|
25
|
+
content_type: /\bjson$/,
|
26
|
+
parser_options: { symbolize_names: true }
|
27
|
+
connection.adapter Faraday.default_adapter
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def api_url
|
34
|
+
ENV["AFRICAS_TALKING_SANDBOX"] ? SANDBOX_URL : URL
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module AfTalk
|
2
|
+
class Request
|
3
|
+
def self.get(path, **options)
|
4
|
+
new.get(path, options)
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.post(path, **options)
|
8
|
+
new.post(path, options)
|
9
|
+
end
|
10
|
+
|
11
|
+
def get(path, **options)
|
12
|
+
connection.get(complete_path(path), complete_options(options))
|
13
|
+
end
|
14
|
+
|
15
|
+
def post(path, **options)
|
16
|
+
connection.post(complete_path(path), complete_options(options))
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def complete_options(options)
|
22
|
+
default_options.merge(options)
|
23
|
+
end
|
24
|
+
|
25
|
+
def complete_path(path)
|
26
|
+
"/#{configuration.version}#{path}"
|
27
|
+
end
|
28
|
+
|
29
|
+
def configuration
|
30
|
+
AfTalk::Configuration
|
31
|
+
end
|
32
|
+
|
33
|
+
def connection
|
34
|
+
AfTalk::Connection.build
|
35
|
+
end
|
36
|
+
|
37
|
+
def default_options
|
38
|
+
{ username: configuration.user_name }
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module AfTalk
|
2
|
+
class Response
|
3
|
+
SUCCESS_STATUSES = [200, 201].freeze
|
4
|
+
JSON_CONTENT = /\bjson$/
|
5
|
+
|
6
|
+
attr_reader :body, :error_message, :status
|
7
|
+
|
8
|
+
def initialize(response)
|
9
|
+
@status = response.status
|
10
|
+
body = response.body
|
11
|
+
|
12
|
+
if success?
|
13
|
+
@body = body
|
14
|
+
else
|
15
|
+
@error_message = body
|
16
|
+
@body = { error: body }
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def success?
|
23
|
+
SUCCESS_STATUSES.include?(status)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module AfTalk
|
2
|
+
class SendMessageResponse < Response
|
3
|
+
attr_reader :message, :recipients
|
4
|
+
|
5
|
+
def initialize(response)
|
6
|
+
super(response)
|
7
|
+
if success?
|
8
|
+
@message = body_data[:Message]
|
9
|
+
@recipients = body_data[:Recipients].map do |recipient|
|
10
|
+
SmsMessageStatus.new(recipient)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def body_data
|
18
|
+
body[:SMSMessageData]
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/spec/aftalk_spec.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe AfTalk do
|
4
|
+
describe "#send_message" do
|
5
|
+
let(:options) do
|
6
|
+
{ to: "+14125554563", message: "Hello World!", foo: "bar" }
|
7
|
+
end
|
8
|
+
|
9
|
+
it "produces a send message response object" do
|
10
|
+
VCR.use_cassette("aftalk/messaging/send_message") do
|
11
|
+
response = described_class.send_message(options)
|
12
|
+
recipient = response.recipients.first
|
13
|
+
|
14
|
+
expect(response.error_message).to be_nil
|
15
|
+
expect(response.status).to eq(201)
|
16
|
+
expect(response.message).to be
|
17
|
+
|
18
|
+
expect(recipient.cost).to be
|
19
|
+
expect(recipient.message_id).to be
|
20
|
+
expect(recipient.number).to eq(options[:to])
|
21
|
+
expect(recipient.status).to eq("Success")
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require "rspec"
|
2
|
+
require "aftalk"
|
3
|
+
require "vcr"
|
4
|
+
|
5
|
+
current_path = File.expand_path("../", __FILE__)
|
6
|
+
Dir["#{current_path}/support/**/*.rb"].each { |file| require file }
|
7
|
+
|
8
|
+
VCR.configure do |config|
|
9
|
+
config.cassette_library_dir = "spec/support/vcr_cassettes"
|
10
|
+
config.hook_into :faraday
|
11
|
+
config.configure_rspec_metadata!
|
12
|
+
config.allow_http_connections_when_no_cassette = false
|
13
|
+
config.default_cassette_options = { record: :new_episodes }
|
14
|
+
end
|
15
|
+
|
16
|
+
RSpec.configure do |config|
|
17
|
+
config.before :each do
|
18
|
+
AfTalk::Connection.clear
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module EnvVars
|
2
|
+
def stub_env_vars(vars)
|
3
|
+
allow(ENV).to receive(:[]).and_call_original
|
4
|
+
|
5
|
+
vars.each do |key, val|
|
6
|
+
allow(ENV).to receive(:[]).with(key).and_return(val)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
RSpec.configure do |config|
|
12
|
+
config.include EnvVars
|
13
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe AfTalk::Client do
|
4
|
+
subject do
|
5
|
+
class ExampleKlass
|
6
|
+
extend AfTalk::Client
|
7
|
+
end
|
8
|
+
ExampleKlass
|
9
|
+
end
|
10
|
+
|
11
|
+
before(:all) { reset_config }
|
12
|
+
|
13
|
+
describe "#send_message" do
|
14
|
+
let(:options) {
|
15
|
+
{ to: "+14125554563", message: "Hello World!", foo: "bar" }
|
16
|
+
}
|
17
|
+
let(:response) { double(headers: { "content-type" => "application/json" }) }
|
18
|
+
|
19
|
+
context "with all required options" do
|
20
|
+
it "it instantiates a send message response" do
|
21
|
+
allow(AfTalk::Request).to receive(:post).
|
22
|
+
with("/messaging", options).
|
23
|
+
and_return(response)
|
24
|
+
allow(AfTalk::SendMessageResponse).to receive(:new).with(response)
|
25
|
+
|
26
|
+
subject.send_message(options)
|
27
|
+
|
28
|
+
expect(AfTalk::SendMessageResponse).to have_received(:new).with(response)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
context "without some required options" do
|
33
|
+
let(:options) { {} }
|
34
|
+
|
35
|
+
it "raises a missing options error" do
|
36
|
+
expect { subject.send_message(options) }.to raise_error(ArgumentError)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def reset_config
|
42
|
+
config = AfTalk::Configuration
|
43
|
+
config.api_key = nil
|
44
|
+
config.user_name = nil
|
45
|
+
end
|
46
|
+
end
|