instedd-pigeon 0.1.3 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.rspec +2 -0
- data/app/helpers/pigeon/channel_helper.rb +6 -0
- data/app/models/pigeon/channel.rb +4 -0
- data/app/models/pigeon/channel_attribute.rb +1 -1
- data/app/models/pigeon/nuntium_channel.rb +10 -4
- data/app/models/pigeon/verboice_channel.rb +8 -5
- data/config/locales/en.yml +6 -0
- data/config/schemas/nuntium/nuntium.yml +17 -27
- data/config/schemas/nuntium/smpp.yml +71 -0
- data/lib/pigeon.rb +12 -0
- data/lib/pigeon/errors.rb +18 -0
- data/lib/pigeon/nuntium.rb +290 -5
- data/lib/pigeon/utils.rb +70 -0
- data/lib/pigeon/verboice.rb +149 -5
- data/lib/pigeon/version.rb +1 -1
- data/pigeon.gemspec +3 -3
- data/spec/lib/pigeon/nuntium_spec.rb +208 -0
- data/spec/lib/pigeon/verboice_spec.rb +181 -0
- metadata +25 -17
data/lib/pigeon/utils.rb
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
module Pigeon
|
2
|
+
module Utils
|
3
|
+
|
4
|
+
def to_query(hash)
|
5
|
+
hash.map do |key, value|
|
6
|
+
"#{key.to_s}=#{CGI.escape(value.to_s)}"
|
7
|
+
end.join('&')
|
8
|
+
end
|
9
|
+
|
10
|
+
def handle_channel_error(error)
|
11
|
+
if error.is_a? RestClient::BadRequest
|
12
|
+
response = JSON.parse error.response.body
|
13
|
+
raise self.class.error_class.new response['summary'], Hash[response['properties'].map {|e| [e.keys[0], e.values[0]]}]
|
14
|
+
else
|
15
|
+
raise self.class.error_class.new error.message
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def with_indifferent_access(hash)
|
20
|
+
if hash.respond_to? :with_indifferent_access
|
21
|
+
hash.with_indifferent_access
|
22
|
+
else
|
23
|
+
hash
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def get(path)
|
28
|
+
resource = RestClient::Resource.new @url, @options
|
29
|
+
resource = resource[path].get
|
30
|
+
yield resource, nil
|
31
|
+
rescue => ex
|
32
|
+
yield nil, ex
|
33
|
+
end
|
34
|
+
|
35
|
+
def get_json(path)
|
36
|
+
get(path) do |response, error|
|
37
|
+
raise self.class.error_class.new error.message if error
|
38
|
+
|
39
|
+
elem = JSON.parse response.body
|
40
|
+
elem.map! { |x| with_indifferent_access x } if elem.is_a? Array
|
41
|
+
elem
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def post(path, data)
|
46
|
+
resource = RestClient::Resource.new @url, @options
|
47
|
+
resource = resource[path].post(data)
|
48
|
+
yield resource, nil
|
49
|
+
rescue => ex
|
50
|
+
yield nil, ex
|
51
|
+
end
|
52
|
+
|
53
|
+
def put(path, data)
|
54
|
+
resource = RestClient::Resource.new @url, @options
|
55
|
+
resource = resource[path].put(data)
|
56
|
+
yield resource, nil
|
57
|
+
rescue => ex
|
58
|
+
yield nil, ex
|
59
|
+
end
|
60
|
+
|
61
|
+
def delete(path)
|
62
|
+
resource = RestClient::Resource.new @url, @options
|
63
|
+
resource = resource[path].delete
|
64
|
+
yield resource, nil
|
65
|
+
rescue => ex
|
66
|
+
yield nil, ex
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
end
|
data/lib/pigeon/verboice.rb
CHANGED
@@ -1,10 +1,154 @@
|
|
1
|
-
|
1
|
+
# Provides access to the Verboice Public API.
|
2
|
+
# Taken from verboice-api-ruby gem version 0.7.0
|
3
|
+
# See http://bitbucket.org/instedd/verboice-api-ruby
|
4
|
+
#
|
2
5
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
+
require 'net/http'
|
7
|
+
require 'json'
|
8
|
+
require 'rest_client'
|
9
|
+
require 'cgi'
|
10
|
+
require 'time'
|
11
|
+
require 'pigeon/errors'
|
12
|
+
require 'pigeon/utils'
|
13
|
+
|
14
|
+
module Pigeon
|
15
|
+
class Verboice
|
16
|
+
include Pigeon::Utils
|
17
|
+
|
18
|
+
def self.error_class
|
19
|
+
Pigeon::VerboiceException
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.from_config
|
23
|
+
config = Pigeon.config
|
24
|
+
|
25
|
+
self.new config.verboice_host, config.verboice_account, config.verboice_password
|
26
|
+
end
|
27
|
+
|
28
|
+
# Creates an account-authenticated Verboice api access.
|
29
|
+
def initialize(url, account, password, default_channel = nil)
|
30
|
+
@url = url
|
31
|
+
@account = account
|
32
|
+
@password = password
|
33
|
+
@default_channel = default_channel
|
34
|
+
@options = {
|
35
|
+
:user => account,
|
36
|
+
:password => password,
|
37
|
+
:headers => {:content_type => 'application/json'},
|
38
|
+
}
|
39
|
+
end
|
40
|
+
|
41
|
+
def call address, options = {}
|
42
|
+
options = options.dup
|
43
|
+
|
44
|
+
args = {}
|
45
|
+
if channel = options.delete(:channel)
|
46
|
+
args[:channel] = channel
|
47
|
+
else
|
48
|
+
args[:channel] = @default_channel
|
49
|
+
end
|
50
|
+
|
51
|
+
args[:address] = address
|
52
|
+
|
53
|
+
if not_before = options.delete(:not_before)
|
54
|
+
args[:not_before] = not_before.iso8601
|
55
|
+
end
|
56
|
+
|
57
|
+
flow = options.delete(:flow)
|
58
|
+
callback_url = options.delete(:callback_url)
|
59
|
+
|
60
|
+
args.merge!(options)
|
61
|
+
|
62
|
+
if flow
|
63
|
+
post "/api/call?#{to_query args}", flow do |response, error|
|
64
|
+
raise ::Pigeon::VerboiceException.new error.message if error
|
65
|
+
JSON.parse response.body
|
66
|
+
end
|
67
|
+
else
|
68
|
+
args[:callback_url] = callback_url if callback_url
|
69
|
+
get_json "/api/call?#{to_query args}"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def call_state id
|
74
|
+
get_json "/api/calls/#{id}/state"
|
75
|
+
end
|
76
|
+
|
77
|
+
def call_redirect id
|
78
|
+
get_json "/api/calls/#{id}/redirect"
|
79
|
+
end
|
80
|
+
|
81
|
+
def channel(name)
|
82
|
+
get("/api/channels/#{name}.json") do |response, error|
|
83
|
+
handle_channel_error error if error
|
84
|
+
|
85
|
+
channel = JSON.parse response.body
|
86
|
+
with_indifferent_access channel
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def create_channel(channel)
|
91
|
+
post "/api/channels.json", channel.to_json do |response, error|
|
92
|
+
handle_channel_error error if error
|
93
|
+
|
94
|
+
channel = JSON.parse response.body
|
95
|
+
with_indifferent_access channel
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def update_channel(channel, name = channel['name'])
|
100
|
+
put "/api/channels/#{name}.json", channel.to_json do |response, error|
|
101
|
+
handle_channel_error error if error
|
102
|
+
|
103
|
+
channel = JSON.parse response.body
|
104
|
+
with_indifferent_access channel
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
# Deletes a channel given its name.
|
109
|
+
#
|
110
|
+
# Raises Verboice::Exception if something goes wrong.
|
111
|
+
def delete_channel(name)
|
112
|
+
delete "/api/channels/#{name}" do |response, error|
|
113
|
+
raise ::Pigeon::VerboiceException.new error.message if error
|
114
|
+
|
115
|
+
response
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def list_channels()
|
120
|
+
get_json "/api/channels.json"
|
121
|
+
end
|
122
|
+
|
123
|
+
def schedules(project_id)
|
124
|
+
get_json "/api/projects/#{project_id}/schedules.json"
|
125
|
+
end
|
126
|
+
|
127
|
+
def schedule(project_id, name)
|
128
|
+
get_json "/api/projects/#{project_id}/schedules/#{name}.json"
|
129
|
+
end
|
130
|
+
|
131
|
+
def create_schedule(project_id, schedule)
|
132
|
+
post "/api/projects/#{project_id}/schedules", schedule.to_json do |response, error|
|
133
|
+
raise ::Pigeon::VerboiceException.new error.message if error
|
134
|
+
response
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def update_schedule(project_id, name, schedule)
|
139
|
+
put "/api/projects/#{project_id}/schedules/#{name}", schedule.to_json do |response, error|
|
140
|
+
raise ::Pigeon::VerboiceException.new error.message if error
|
141
|
+
response
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def delete_schedule(project_id, name)
|
146
|
+
delete "/api/projects/#{project_id}/schedules/#{name}" do |response, error|
|
147
|
+
raise ::Pigeon::VerboiceException.new error.message if error
|
148
|
+
response
|
149
|
+
end
|
150
|
+
end
|
6
151
|
|
7
|
-
Verboice.new config.verboice_host, config.verboice_account, config.verboice_password
|
8
152
|
end
|
9
153
|
end
|
10
154
|
|
data/lib/pigeon/version.rb
CHANGED
data/pigeon.gemspec
CHANGED
@@ -12,10 +12,10 @@ Gem::Specification.new do |gem|
|
|
12
12
|
gem.summary = %q{This gem handles creating, updating and destroying channels in Nuntium and Verboice for your Rails application.}
|
13
13
|
gem.homepage = "https://bitbucket.org/instedd/pigeon"
|
14
14
|
|
15
|
-
gem.add_dependency 'rails', '~> 3.2
|
16
|
-
gem.add_dependency 'nuntium_api', '~> 0.19'
|
17
|
-
gem.add_dependency 'verboice', '0.7.0'
|
15
|
+
gem.add_dependency 'rails', '~> 3.2'
|
18
16
|
gem.add_dependency 'twitter_oauth'
|
17
|
+
gem.add_dependency 'rest-client'
|
18
|
+
gem.add_dependency 'json'
|
19
19
|
|
20
20
|
gem.files = `git ls-files`.split($/)
|
21
21
|
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
@@ -0,0 +1,208 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Pigeon
|
4
|
+
describe Nuntium do
|
5
|
+
let(:url) { "http://example.com" }
|
6
|
+
let(:options) { {:user => "account/application", :password => "password", :headers => {:content_type => 'application/json'}} }
|
7
|
+
let(:api) { Nuntium.new url, "account", "application", "password" }
|
8
|
+
|
9
|
+
it "gets countries" do
|
10
|
+
should_receive_http_get '/api/countries.json', %([{"name": "Argentina", "iso2": "ar"}])
|
11
|
+
|
12
|
+
api.countries.should eq(['name' => 'Argentina', 'iso2' => 'ar'])
|
13
|
+
end
|
14
|
+
|
15
|
+
it "gets country" do
|
16
|
+
should_receive_http_get '/api/countries/ar.json', %({"name": "Argentina", "iso2": "ar"})
|
17
|
+
|
18
|
+
api.country('ar').should eq({'name' => 'Argentina', 'iso2' => 'ar'})
|
19
|
+
end
|
20
|
+
|
21
|
+
it "gets carriers" do
|
22
|
+
should_receive_http_get '/api/carriers.json', %([{"name": "Argentina", "iso2": "ar"}])
|
23
|
+
|
24
|
+
api.carriers.should eq(['name' => 'Argentina', 'iso2' => 'ar'])
|
25
|
+
end
|
26
|
+
|
27
|
+
it "gets carriers for a country" do
|
28
|
+
should_receive_http_get '/api/carriers.json?country_id=ar', %([{"name": "Argentina", "iso2": "ar"}])
|
29
|
+
|
30
|
+
api.carriers('ar').should eq(['name' => 'Argentina', 'iso2' => 'ar'])
|
31
|
+
end
|
32
|
+
|
33
|
+
it "gets carrier" do
|
34
|
+
should_receive_http_get '/api/carriers/ar.json', %({"name": "Argentina", "iso2": "ar"})
|
35
|
+
|
36
|
+
api.carrier('ar').should eq({'name' => 'Argentina', 'iso2' => 'ar'})
|
37
|
+
end
|
38
|
+
|
39
|
+
it "gets channels" do
|
40
|
+
should_receive_http_get '/api/channels.json', %([{"name": "Argentina", "configuration": [{"name": "foo", "value": "bar"}]}])
|
41
|
+
|
42
|
+
api.channels.should eq([{'name' => 'Argentina', 'configuration' => {'foo' => 'bar'}}])
|
43
|
+
end
|
44
|
+
|
45
|
+
it "gets channel" do
|
46
|
+
should_receive_http_get '/api/channels/Argentina.json', %({"name": "Argentina", "configuration": [{"name": "foo", "value": "bar"}]})
|
47
|
+
|
48
|
+
api.channel('Argentina').should eq({'name' => 'Argentina', 'configuration' => {'foo' => 'bar'}})
|
49
|
+
end
|
50
|
+
|
51
|
+
it "creates channel" do
|
52
|
+
channel_json = %({"name":"Argentina","configuration":[{"name":"foo","value":"bar"}]})
|
53
|
+
should_receive_http_post '/api/channels.json', channel_json, channel_json
|
54
|
+
|
55
|
+
channel = {'name' => 'Argentina', 'configuration' => {'foo' => 'bar'}}
|
56
|
+
|
57
|
+
api.create_channel(channel).should eq(channel)
|
58
|
+
end
|
59
|
+
|
60
|
+
it "updates channel", :focus => true do
|
61
|
+
channel_json = %({"name":"Argentina","configuration":[{"name":"foo","value":"bar"}]})
|
62
|
+
should_receive_http_put '/api/channels/Argentina.json', channel_json, channel_json
|
63
|
+
|
64
|
+
channel = {'name' => 'Argentina', 'configuration' => {'foo' => 'bar'}}
|
65
|
+
|
66
|
+
api.update_channel(channel).should eq(channel)
|
67
|
+
end
|
68
|
+
|
69
|
+
it "deletes channel", :focus => true do
|
70
|
+
should_receive_http_delete '/api/channels/Argentina'
|
71
|
+
|
72
|
+
api.delete_channel('Argentina')
|
73
|
+
end
|
74
|
+
|
75
|
+
it "gets candidate channels for ao", :focus => true do
|
76
|
+
should_receive_http_get "/api/candidate/channels.json?from=#{CGI.escape 'sms://1234'}&body=Hello", %([{"name":"Argentina","configuration":[{"value":"bar","name":"foo"}]}])
|
77
|
+
|
78
|
+
channels = api.candidate_channels_for_ao :from => 'sms://1234', :body => 'Hello'
|
79
|
+
channels.should eq([{'name' => 'Argentina', 'configuration' => {'foo' => 'bar'}}])
|
80
|
+
end
|
81
|
+
|
82
|
+
it "sends single ao" do
|
83
|
+
should_receive_http_get_with_headers "/account/application/send_ao?from=#{CGI.escape 'sms://1234'}&body=Hello", :x_nuntium_id => '1', :x_nuntium_guid => '2', :x_nuntium_token => '3'
|
84
|
+
response = api.send_ao :from => 'sms://1234', :body => 'Hello'
|
85
|
+
response.should eq("id" => '1', "guid" => '2', "token" => '3')
|
86
|
+
end
|
87
|
+
|
88
|
+
it "sends many aos" do
|
89
|
+
post_body = %([{"from":"sms://1234","body":"Hello"}])
|
90
|
+
should_receive_http_post_with_headers "/account/application/send_ao.json", post_body, :x_nuntium_token => '3'
|
91
|
+
response = api.send_ao [{:from => 'sms://1234', :body => 'Hello'}]
|
92
|
+
response.should eq("token" => '3')
|
93
|
+
end
|
94
|
+
|
95
|
+
it "gets ao" do
|
96
|
+
should_receive_http_get '/account/application/get_ao.json?token=foo', %([{"name": "Argentina", "iso2": "ar"}])
|
97
|
+
|
98
|
+
api.get_ao('foo').should eq([{'name' => 'Argentina', 'iso2' => 'ar'}])
|
99
|
+
end
|
100
|
+
|
101
|
+
it "gets custom attributes" do
|
102
|
+
should_receive_http_get '/api/custom_attributes?address=foo', %([{"name": "Argentina", "iso2": "ar"}])
|
103
|
+
|
104
|
+
api.get_custom_attributes('foo').should eq([{'name' => 'Argentina', 'iso2' => 'ar'}])
|
105
|
+
end
|
106
|
+
|
107
|
+
it "sets custom attributes" do
|
108
|
+
should_receive_http_post '/api/custom_attributes?address=foo', %({"application":"bar"}), ''
|
109
|
+
|
110
|
+
api.set_custom_attributes('foo', :application => :bar)
|
111
|
+
end
|
112
|
+
|
113
|
+
it "creates twitter friendship" do
|
114
|
+
should_receive_http_get '/api/channels/twit/twitter/friendships/create?user=foo&follow=true'
|
115
|
+
|
116
|
+
api.twitter_friendship_create 'twit', 'foo', true
|
117
|
+
end
|
118
|
+
|
119
|
+
it "authorizes twitter channel" do
|
120
|
+
should_receive_http_get '/api/channels/twit/twitter/authorize?callback=foo', "http://bar"
|
121
|
+
|
122
|
+
url = api.twitter_authorize 'twit', 'foo'
|
123
|
+
url.should eq("http://bar")
|
124
|
+
end
|
125
|
+
|
126
|
+
it "adds xmpp contact" do
|
127
|
+
should_receive_http_get "/api/channels/chan/xmpp/add_contact?jid=#{CGI.escape 'foo@bar.com'}"
|
128
|
+
|
129
|
+
api.xmpp_add_contact 'chan', 'foo@bar.com'
|
130
|
+
end
|
131
|
+
|
132
|
+
def should_receive_http_get(path, body = nil)
|
133
|
+
resource = mock 'resource'
|
134
|
+
RestClient::Resource.should_receive(:new).with(url, options).and_return(resource)
|
135
|
+
|
136
|
+
resource2 = mock 'resource2'
|
137
|
+
resource.should_receive(:[]).with(path).and_return(resource2)
|
138
|
+
|
139
|
+
resource3 = mock 'resource3'
|
140
|
+
resource2.should_receive(:get).and_return(resource3)
|
141
|
+
|
142
|
+
resource3.should_receive(:body).and_return(body) if body
|
143
|
+
end
|
144
|
+
|
145
|
+
def should_receive_http_get_with_headers(path, headers)
|
146
|
+
resource = mock 'resource'
|
147
|
+
RestClient::Resource.should_receive(:new).with(url, options).and_return(resource)
|
148
|
+
|
149
|
+
resource2 = mock 'resource2'
|
150
|
+
resource.should_receive(:[]).with(path).and_return(resource2)
|
151
|
+
|
152
|
+
resource3 = mock 'resource3'
|
153
|
+
resource2.should_receive(:get).and_return(resource3)
|
154
|
+
|
155
|
+
resource3.stub(:headers) { headers }
|
156
|
+
end
|
157
|
+
|
158
|
+
def should_receive_http_post(path, data, body)
|
159
|
+
resource = mock 'resource'
|
160
|
+
RestClient::Resource.should_receive(:new).with(url, options).and_return(resource)
|
161
|
+
|
162
|
+
resource2 = mock 'resource2'
|
163
|
+
resource.should_receive(:[]).with(path).and_return(resource2)
|
164
|
+
|
165
|
+
resource3 = mock 'resource3'
|
166
|
+
resource2.should_receive(:post).with(data).and_return(resource3)
|
167
|
+
|
168
|
+
resource3.stub(:body) { body }
|
169
|
+
end
|
170
|
+
|
171
|
+
def should_receive_http_post_with_headers(path, data, headers)
|
172
|
+
resource = mock 'resource'
|
173
|
+
RestClient::Resource.should_receive(:new).with(url, options).and_return(resource)
|
174
|
+
|
175
|
+
resource2 = mock 'resource2'
|
176
|
+
resource.should_receive(:[]).with(path).and_return(resource2)
|
177
|
+
|
178
|
+
resource3 = mock 'resource3'
|
179
|
+
resource2.should_receive(:post).with(data).and_return(resource3)
|
180
|
+
|
181
|
+
resource3.stub(:headers) { headers }
|
182
|
+
end
|
183
|
+
|
184
|
+
def should_receive_http_put(path, data, body)
|
185
|
+
resource = mock 'resource'
|
186
|
+
RestClient::Resource.should_receive(:new).with(url, options).and_return(resource)
|
187
|
+
|
188
|
+
resource2 = mock 'resource2'
|
189
|
+
resource.should_receive(:[]).with(path).and_return(resource2)
|
190
|
+
|
191
|
+
resource3 = mock 'resource3'
|
192
|
+
resource2.should_receive(:put).with(data).and_return(resource3)
|
193
|
+
|
194
|
+
resource3.should_receive(:body).and_return(body)
|
195
|
+
end
|
196
|
+
|
197
|
+
def should_receive_http_delete(path)
|
198
|
+
resource = mock 'resource'
|
199
|
+
RestClient::Resource.should_receive(:new).with(url, options).and_return(resource)
|
200
|
+
|
201
|
+
resource2 = mock 'resource2'
|
202
|
+
resource.should_receive(:[]).with(path).and_return(resource2)
|
203
|
+
|
204
|
+
resource3 = mock 'resource3'
|
205
|
+
resource2.should_receive(:delete)
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|