instedd-pigeon 0.1.3 → 0.2.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.
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