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 ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+
@@ -24,6 +24,12 @@ module Pigeon
24
24
  choices = choices.map { |h| [h["display"], h["value"]] }
25
25
  end
26
26
  select_tag(field_name, options_for_select(choices, field_value), options)
27
+ when "multi"
28
+ choices = attribute.options
29
+ if choices.length > 0 && choices[0].is_a?(Hash)
30
+ choices = choices.map { |h| [h["display"], h["value"]] }
31
+ end
32
+ select_tag(field_name, options_for_select(choices, field_value), { :multiple => true }.merge(options))
27
33
  when "timezone"
28
34
  select_tag(field_name, time_zone_options_for_select(field_value), options)
29
35
  when "boolean"
@@ -7,6 +7,10 @@ module Pigeon
7
7
  include NestedScopes
8
8
 
9
9
  class << self
10
+ def i18n_scope
11
+ :pigeon
12
+ end
13
+
10
14
  def type() nil end
11
15
 
12
16
  def schemas
@@ -30,7 +30,7 @@ module Pigeon
30
30
  end
31
31
 
32
32
  def self.valid_type?(type)
33
- %w(string boolean password enum integer timezone hidden).include?(type.to_s)
33
+ %w(string boolean password enum multi integer timezone hidden).include?(type.to_s)
34
34
  end
35
35
 
36
36
  def self.build_default(name, hint_value = nil)
@@ -15,7 +15,7 @@ module Pigeon
15
15
  end
16
16
 
17
17
  def nuntium
18
- @nuntium ||= Nuntium.from_config
18
+ @nuntium ||= ::Pigeon::Nuntium.from_config
19
19
  end
20
20
 
21
21
  private
@@ -25,7 +25,13 @@ module Pigeon
25
25
  end
26
26
 
27
27
  def load_schemas
28
- Pigeon::ChannelSchema.list_from_hash(:nuntium, PigeonConfig::NuntiumChannelSchemas)
28
+ if Pigeon.config.nuntium_configured?
29
+ Pigeon::ChannelSchema.list_from_hash(:nuntium, PigeonConfig::NuntiumChannelSchemas).reject do |schema|
30
+ schema.kind == 'twitter' && !Pigeon.config.twitter_configured?
31
+ end
32
+ else
33
+ []
34
+ end
29
35
  end
30
36
  end
31
37
 
@@ -49,7 +55,7 @@ module Pigeon
49
55
  self.class.nuntium.update_channel attributes
50
56
  end
51
57
  true
52
- rescue Nuntium::Exception => e
58
+ rescue Pigeon::NuntiumException => e
53
59
  Rails.logger.warn "error saving Nuntium channel: #{e.message}"
54
60
  e.properties.each do |name, message|
55
61
  if attributes.include? name
@@ -71,7 +77,7 @@ module Pigeon
71
77
  self.class.nuntium.delete_channel(name)
72
78
  end
73
79
  @destroyed = true
74
- rescue Nuntium::Exception => e
80
+ rescue Pigeon::NuntiumException => e
75
81
  Rails.logger.warn "error deleting Nuntium channel: #{e.message}"
76
82
  end
77
83
  end
@@ -13,7 +13,7 @@ module Pigeon
13
13
  end
14
14
 
15
15
  def verboice
16
- @verboice ||= Verboice.from_config
16
+ @verboice ||= ::Pigeon::Verboice.from_config
17
17
  end
18
18
 
19
19
  private
@@ -23,7 +23,11 @@ module Pigeon
23
23
  end
24
24
 
25
25
  def load_schemas
26
- Pigeon::ChannelSchema.list_from_hash(:verboice, PigeonConfig::VerboiceChannelSchemas)
26
+ if Pigeon.config.verboice_configured?
27
+ Pigeon::ChannelSchema.list_from_hash(:verboice, PigeonConfig::VerboiceChannelSchemas)
28
+ else
29
+ []
30
+ end
27
31
  end
28
32
  end
29
33
 
@@ -39,7 +43,6 @@ module Pigeon
39
43
  return false unless valid?
40
44
 
41
45
  begin
42
- puts attributes
43
46
  if !persisted?
44
47
  self.class.verboice.create_channel attributes
45
48
  @persisted = true
@@ -47,7 +50,7 @@ module Pigeon
47
50
  self.class.verboice.update_channel attributes
48
51
  end
49
52
  true
50
- rescue Verboice::Exception => e
53
+ rescue Pigeon::VerboiceException => e
51
54
  Rails.logger.warn "error saving Verboice channel: #{e.message}"
52
55
  e.properties.each do |name, message|
53
56
  if attributes.include? name
@@ -69,7 +72,7 @@ module Pigeon
69
72
  self.class.verboice.delete_channel(name)
70
73
  end
71
74
  @destroyed = true
72
- rescue Verboice::Exception => e
75
+ rescue Pigeon::VerboiceException => e
73
76
  Rails.logger.warn "error deleting Verboice channel: #{e.message}"
74
77
  end
75
78
  end
@@ -0,0 +1,6 @@
1
+ en:
2
+ pigeon:
3
+ errors:
4
+ messages:
5
+ channel_invalid: "Validation failed: %{errors}"
6
+
@@ -34,6 +34,9 @@ clickatell:
34
34
  type: password
35
35
  humanized_name: Incoming password
36
36
  label: Incoming password (to use for the callback URLs)
37
+ - name: cost_per_credit
38
+ label: "Cost per credit (messages will get a 'cost' custom attribute with the value charge * cost_per_credit)"
39
+ default_value: 1
37
40
 
38
41
  dtac:
39
42
  humanized_name: 'DTAC channel'
@@ -43,7 +46,7 @@ dtac:
43
46
  user: false
44
47
  - name: configuration
45
48
  attributes:
46
- - name: username
49
+ - name: user
47
50
  - name: password
48
51
  type: password
49
52
 
@@ -53,14 +56,20 @@ ipop:
53
56
  - name: protocol
54
57
  value: sms
55
58
  user: false
59
+ - name: address
60
+ humanized_name: From address
61
+ label: 'Address (this is usually the "from" number)'
56
62
  - name: configuration
57
63
  attributes:
58
- - name: url
59
- label: URL
60
- - name: username
64
+ - name: mt_post_url
65
+ humanized_name: Post URL
66
+ label: Post URL
67
+ - name: mt_post_user
68
+ humanized_name: Username
61
69
  label: Username (optional)
62
- - name: password
70
+ - name: mt_post_password
63
71
  type: password
72
+ humanized_name: Password
64
73
  label: Password (optional)
65
74
  - name: cid
66
75
  label: Connection ID (cid)
@@ -85,6 +94,9 @@ isms:
85
94
  - name: protocol
86
95
  value: sms
87
96
  user: false
97
+ - name: address
98
+ humanized_name: From address
99
+ label: 'Address (this is usually the "from" number)'
88
100
  - name: configuration
89
101
  attributes:
90
102
  - name: host
@@ -131,28 +143,6 @@ qst:
131
143
  - name: password
132
144
  type: password
133
145
 
134
- smpp:
135
- humanized_name: 'SMPP channel'
136
- attributes:
137
- - name: protocol
138
- value: sms
139
- user: false
140
- - name: configuration
141
- attributes:
142
- - name: host
143
- - name: port
144
- type: integer
145
- - name: user
146
- - name: password
147
- type: password
148
- - name: system_type
149
- default_value: vma
150
- - name: source_ton
151
- - name: source_npi
152
- - name: destination_ton
153
- - name: destination_npi
154
- - name: service_type
155
-
156
146
  smtp:
157
147
  humanized_name: 'SMTP channel'
158
148
  attributes:
@@ -0,0 +1,71 @@
1
+ smpp:
2
+ humanized_name: 'SMPP channel'
3
+ attributes:
4
+ - name: protocol
5
+ value: sms
6
+ user: false
7
+ - name: configuration
8
+ attributes:
9
+ - name: host
10
+ - name: port
11
+ type: integer
12
+ - name: user
13
+ - name: password
14
+ type: password
15
+ - name: system_type
16
+ default_value: vma
17
+ - name: source_ton
18
+ - name: source_npi
19
+ - name: destination_ton
20
+ - name: destination_npi
21
+ - name: service_type
22
+ - name: endianness_mo
23
+ type: boolean
24
+ label: 'Use little endian for UCS-2 MO messages'
25
+ - name: endianness_mt
26
+ type: boolean
27
+ label: 'Use little endian for UCS-2 MT messages'
28
+ - name: accept_mo_hex_string
29
+ type: boolean
30
+ humanized_name: 'Hex strings'
31
+ - name: default_mo_encoding
32
+ type: enum
33
+ options:
34
+ - value: 'ascii'
35
+ display: 'ASCII'
36
+ - value: 'latin1'
37
+ display: 'Latin1'
38
+ - value: 'ucs-2'
39
+ display: 'UCS-2'
40
+ - value: 'gsm'
41
+ display: 'GSM 03.38'
42
+ - name: mt_encodings
43
+ type: multi
44
+ options:
45
+ - value: 'ascii'
46
+ display: 'ASCII'
47
+ - value: 'latin1'
48
+ display: 'Latin1'
49
+ - value: 'ucs-2'
50
+ display: 'UCS-2'
51
+ - name: mt_max_length
52
+ type: enum
53
+ options:
54
+ - 140
55
+ - 160
56
+ - 254
57
+ - name: mt_csms_method
58
+ type: enum
59
+ options:
60
+ - value: 'udh'
61
+ display: 'UDH'
62
+ - value: 'optional_parameters'
63
+ display: 'Optional parameters'
64
+ - value: 'message_payload'
65
+ display: 'Message payload'
66
+ default_value: 'optional_parameters'
67
+ - name: suspension_codes
68
+ label: 'Suspension codes (comma separated)'
69
+ - name: rejection_codes
70
+ label: 'Rejection codes (comma separated)'
71
+
data/lib/pigeon.rb CHANGED
@@ -9,6 +9,18 @@ module Pigeon
9
9
  attr_accessor :verboice_default_call_flow
10
10
 
11
11
  attr_accessor :twitter_consumer_key, :twitter_consumer_secret
12
+
13
+ def nuntium_configured?
14
+ nuntium_host.present? && nuntium_account.present? && nuntium_app.present? && nuntium_app_password.present?
15
+ end
16
+
17
+ def verboice_configured?
18
+ verboice_host.present? && verboice_account.present? && verboice_password.present?
19
+ end
20
+
21
+ def twitter_configured?
22
+ twitter_consumer_key.present? && twitter_consumer_secret.present?
23
+ end
12
24
  end
13
25
 
14
26
  def self.config
data/lib/pigeon/errors.rb CHANGED
@@ -7,6 +7,8 @@ module Pigeon
7
7
 
8
8
  def initialize(channel)
9
9
  @channel = channel
10
+ errors = @channel.errors.full_messages.join(", ")
11
+ super(I18n.t(:"#{@channel.class.i18n_scope}.errors.messages.channel_invalid", :errors => errors, :default => :"errors.messages.channel_invalid"))
10
12
  end
11
13
  end
12
14
 
@@ -24,5 +26,21 @@ module Pigeon
24
26
  })
25
27
  end
26
28
  end
29
+
30
+ class ApiException < PigeonError
31
+ attr_accessor :properties
32
+
33
+ def initialize(msg, properties = {})
34
+ super msg
35
+ @properties = properties
36
+ end
37
+ end
38
+
39
+ class VerboiceException < ApiException
40
+ end
41
+
42
+ class NuntiumException < ApiException
43
+ end
44
+
27
45
  end
28
46
 
@@ -1,10 +1,295 @@
1
- require 'nuntium'
1
+ # Provides access to the Nuntium Public API.
2
+ # Taken from the nuntium-api-ruby gem version 0.21
3
+ # See http://bitbucket.org/instedd/nuntium-api-ruby
2
4
 
3
- class Nuntium
4
- def self.from_config
5
- config = Pigeon.config
5
+ require 'net/http'
6
+ require 'json'
7
+ require 'rest_client'
8
+ require 'cgi'
9
+ require 'pigeon/errors'
10
+ require 'pigeon/utils'
11
+
12
+ module Pigeon
13
+ class Nuntium
14
+ include Pigeon::Utils
15
+
16
+ def self.error_class
17
+ Pigeon::NuntiumException
18
+ end
19
+
20
+ def self.from_config
21
+ config = Pigeon.config
22
+
23
+ Nuntium.new config.nuntium_host, config.nuntium_account, config.nuntium_app, config.nuntium_app_password
24
+ end
25
+
26
+ # Creates an application-authenticated Nuntium api access.
27
+ def initialize(url, account, application, password)
28
+ @url = url
29
+ @account = account
30
+ @application = application
31
+ @options = {
32
+ :user => "#{account}/#{application}",
33
+ :password => password,
34
+ :headers => {:content_type => 'application/json'},
35
+ }
36
+ end
37
+
38
+ # Gets the list of countries known to Nuntium as an array of hashes.
39
+ #
40
+ # Raises ::Pigeon::NuntiumException if something goes wrong.
41
+ def countries
42
+ get_json "/api/countries.json"
43
+ end
44
+
45
+ # Gets a country as a hash given its iso2 or iso3 code, or nil if a country with that iso does not exist.
46
+ #
47
+ # Raises ::Pigeon::NuntiumException if something goes wrong.
48
+ def country(iso)
49
+ get_json "/api/countries/#{iso}.json"
50
+ end
51
+
52
+ # Gets the list of carriers known to Nuntium that belong to a country as an array of hashes, given its
53
+ # iso2 or iso3 code. Gets all carriers as an array of hashes if no country is specified.
54
+ #
55
+ # Raises ::Pigeon::NuntiumException if something goes wrong.
56
+ def carriers(country_id = nil)
57
+ if country_id
58
+ get_json "/api/carriers.json?country_id=#{country_id}"
59
+ else
60
+ get_json "/api/carriers.json"
61
+ end
62
+ end
63
+
64
+ # Gets a carrier as a hash given its guid, or nil if a carrier with that guid does not exist.
65
+ #
66
+ # Raises ::Pigeon::NuntiumException if something goes wrong.
67
+ def carrier(guid)
68
+ get_json "/api/carriers/#{guid}.json"
69
+ end
70
+
71
+ # Returns the list of channels belonging to the application or that don't
72
+ # belong to any application, as an array of hashes.
73
+ #
74
+ # Raises ::Pigeon::NuntiumException if something goes wrong.
75
+ def channels
76
+ get "/api/channels.json" do |response, error|
77
+ raise ::Pigeon::NuntiumException.new error.message if error
78
+
79
+ channels = JSON.parse response.body
80
+ channels.map! do |channel|
81
+ read_configuration channel
82
+ with_indifferent_access channel
83
+ end
84
+ channels
85
+ end
86
+ end
87
+
88
+ # Returns a channel given its name. Raises when the channel does not exist.
89
+ #
90
+ # Raises ::Pigeon::NuntiumException if something goes wrong.
91
+ def channel(name)
92
+ get("/api/channels/#{name}.json") do |response, error|
93
+ raise ::Pigeon::NuntiumException.new error.message if error
94
+
95
+ channel = JSON.parse response.body
96
+ read_configuration channel
97
+ with_indifferent_access channel
98
+ end
99
+ end
100
+
101
+ # Creates a channel.
102
+ #
103
+ # create_channel :name => 'foo', :kind => 'qst_server', :protocol => 'sms', :configuration => {:password => 'bar'}
104
+ #
105
+ # Raises ::Pigeon::NuntiumException if something goes wrong. You can access specific errors on properties via the properties
106
+ # accessor of the exception.
107
+ def create_channel(channel)
108
+ channel = channel.dup
109
+
110
+ write_configuration channel
111
+ post "/api/channels.json", channel.to_json do |response, error|
112
+ handle_channel_error error if error
113
+
114
+ channel = JSON.parse response.body
115
+ read_configuration channel
116
+ with_indifferent_access channel
117
+ end
118
+ end
119
+
120
+ # Updates a channel.
121
+ #
122
+ # update_channel :name => 'foo', :kind => 'qst_server', :protocol => 'sms', :configuration => {:password => 'bar'}
123
+ #
124
+ # Raises ::Pigeon::NuntiumException if something goes wrong. You can access specific errors on properties via the properties
125
+ # accessor of the exception.
126
+ def update_channel(channel)
127
+ channel = channel.dup
128
+
129
+ write_configuration channel
130
+ channel_name = channel['name'] || channel[:name]
131
+
132
+ put "/api/channels/#{channel_name}.json", channel.to_json do |response, error|
133
+ handle_channel_error error if error
134
+
135
+ channel = JSON.parse response.body
136
+ read_configuration channel
137
+ with_indifferent_access channel
138
+ end
139
+ end
140
+
141
+ # Deletes a chnanel given its name.
142
+ #
143
+ # Raises ::Pigeon::NuntiumException if something goes wrong.
144
+ def delete_channel(name)
145
+ delete "/api/channels/#{name}" do |response, error|
146
+ raise ::Pigeon::NuntiumException.new error.message if error
147
+
148
+ response
149
+ end
150
+ end
151
+
152
+ # Returns the list of candidate channels when simulating routing the given AO message.
153
+ #
154
+ # candidate_channels_for_ao :from => 'sms://1', :to => 'sms://2', :subject => 'hello', :body => 'hi!'
155
+ #
156
+ # Raises ::Pigeon::NuntiumException if something goes wrong.
157
+ def candidate_channels_for_ao(message)
158
+ get_channels "/api/candidate/channels.json?#{to_query message}"
159
+ end
160
+
161
+ # Sends one or many AO messages.
162
+ #
163
+ # To send a token, just include it in the message as :token => 'my_token'
164
+ #
165
+ # send_ao :from => 'sms://1', :to => 'sms://2', :subject => 'hello', :body => 'hi!'
166
+ # send_ao [{:from => 'sms://1', :to => 'sms://2', :subject => 'hello', :body => 'hi!'}, {...}]
167
+ #
168
+ # Returns a hash with :id, :guid and :token keys if a single message was sent, otherwise
169
+ # returns a hash with a :token key.
170
+ #
171
+ # Raises ::Pigeon::NuntiumException if something goes wrong.
172
+ def send_ao(messages)
173
+ if messages.is_a? Array
174
+ post "/#{@account}/#{@application}/send_ao.json", messages.to_json do |response, error|
175
+ raise ::Pigeon::NuntiumException.new error.message if error
176
+
177
+ with_indifferent_access({:token => response.headers[:x_nuntium_token]})
178
+ end
179
+ else
180
+ get "/#{@account}/#{@application}/send_ao?#{to_query messages}" do |response, error|
181
+ raise ::Pigeon::NuntiumException.new error.message if error
182
+
183
+ with_indifferent_access(
184
+ {
185
+ :id => response.headers[:x_nuntium_id],
186
+ :guid => response.headers[:x_nuntium_guid],
187
+ :token => response.headers[:x_nuntium_token],
188
+ }
189
+ )
190
+ end
191
+ end
192
+ end
193
+
194
+ # Gets AO messages that have the given token. The response is an array of hashes with the messages' attributes.
195
+ #
196
+ # Raises ::Pigeon::NuntiumException if something goes wrong.
197
+ def get_ao(token)
198
+ get_json "/#{@account}/#{@application}/get_ao.json?token=#{token}"
199
+ end
200
+
201
+ # Gets the custom attributes specified for a given address. Returns a hash with the attributes
202
+ #
203
+ # Raises ::Pigeon::NuntiumException if something goes wrong.
204
+ def get_custom_attributes(address)
205
+ get_json "/api/custom_attributes?address=#{address}"
206
+ end
207
+
208
+ # Sets custom attributes of a given address.
209
+ #
210
+ # Raises ::Pigeon::NuntiumException if something goes wrong.
211
+ def set_custom_attributes(address, attributes)
212
+ post "/api/custom_attributes?address=#{address}", attributes.to_json do |response, error|
213
+ raise ::Pigeon::NuntiumException.new error.message if error
214
+
215
+ nil
216
+ end
217
+ end
218
+
219
+
220
+ # Creates a friendship between the channel's twitter account and the given user.
221
+ # Returns the response from twitter.
222
+ # Refer to Twitter's documentation: https://dev.twitter.com/docs/api/1/post/friendships/create
223
+ #
224
+ # Raises ::Pigeon::NuntiumException if something goes wrong.
225
+ def twitter_friendship_create(channel_name, user, follow = true)
226
+ get("/api/channels/#{channel_name}/twitter/friendships/create?user=#{CGI.escape user}&follow=#{follow}") do |response, error|
227
+ raise ::Pigeon::NuntiumException.new error.message if error
228
+
229
+ response
230
+ end
231
+ end
232
+
233
+ # Returns a URL to authorize the given twitter channel, which will eventually redirect
234
+ # to the given callback URL.
235
+ #
236
+ # Raises ::Pigeon::NuntiumException if something goes wrong.
237
+ def twitter_authorize(channel_name, callback)
238
+ get_text("/api/channels/#{channel_name}/twitter/authorize?callback=#{CGI.escape callback}")
239
+ end
240
+
241
+ # Adds an xmpp conact to the xmpp account associated to the given channel.
242
+ #
243
+ # Raises ::Pigeon::NuntiumException if something goes wrong.
244
+ def xmpp_add_contact(channel_name, jid)
245
+ get("/api/channels/#{channel_name}/xmpp/add_contact?jid=#{CGI.escape jid}") do |response, error|
246
+ raise ::Pigeon::NuntiumException.new error.message if error
247
+
248
+ response
249
+ end
250
+ end
251
+
252
+ private
253
+
254
+ def write_configuration(channel)
255
+ return unless channel[:configuration] || channel['configuration']
256
+
257
+ configuration = []
258
+ (channel[:configuration] || channel['configuration']).each do |name, value|
259
+ configuration << {:name => name, :value => value}
260
+ end
261
+ if channel[:configuration]
262
+ channel[:configuration] = configuration
263
+ else
264
+ channel['configuration'] = configuration
265
+ end
266
+ end
267
+
268
+ def read_configuration(channel)
269
+ channel['configuration'] = Hash[channel['configuration'].map { |e| [e['name'], e['value']] }]
270
+ end
271
+
272
+ def get_text(path)
273
+ get(path) do |response, error|
274
+ raise ::Pigeon::NuntiumException.new error.message if error
275
+
276
+ response.body
277
+ end
278
+ end
279
+
280
+ def get_channels(path)
281
+ get(path) do |response, error|
282
+ raise ::Pigeon::NuntiumException.new error.message if error
283
+
284
+ channels = JSON.parse response.body
285
+ channels.map! do |channel|
286
+ read_configuration channel
287
+ with_indifferent_access channel
288
+ end
289
+ channels
290
+ end
291
+ end
6
292
 
7
- Nuntium.new config.nuntium_host, config.nuntium_account, config.nuntium_app, config.nuntium_app_password
8
293
  end
9
294
  end
10
295