signal_api 0.0.1
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/.gitignore +19 -0
- data/.travis.yml +5 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +43 -0
- data/Rakefile +49 -0
- data/lib/signal_api/carrier.rb +43 -0
- data/lib/signal_api/contact.rb +60 -0
- data/lib/signal_api/core_ext/array.rb +7 -0
- data/lib/signal_api/core_ext/hash.rb +7 -0
- data/lib/signal_api/core_ext/nil_class.rb +7 -0
- data/lib/signal_api/core_ext/string.rb +7 -0
- data/lib/signal_api/coupon_group.rb +47 -0
- data/lib/signal_api/deliver_sms.rb +54 -0
- data/lib/signal_api/exceptions.rb +19 -0
- data/lib/signal_api/list.rb +224 -0
- data/lib/signal_api/mocks/api_mock.rb +70 -0
- data/lib/signal_api/mocks/contact.rb +13 -0
- data/lib/signal_api/mocks/deliver_sms.rb +14 -0
- data/lib/signal_api/mocks/list.rb +19 -0
- data/lib/signal_api/mocks/short_url.rb +9 -0
- data/lib/signal_api/segment.rb +161 -0
- data/lib/signal_api/short_url.rb +56 -0
- data/lib/signal_api/signal_http_api.rb +49 -0
- data/lib/signal_api/util/email_address.rb +10 -0
- data/lib/signal_api/util/phone.rb +37 -0
- data/lib/signal_api/version.rb +3 -0
- data/lib/signal_api.rb +114 -0
- data/signal_api.gemspec +27 -0
- data/test/api/carrier_test.rb +43 -0
- data/test/api/contact_test.rb +93 -0
- data/test/api/coupon_group_test.rb +36 -0
- data/test/api/deliver_sms_test.rb +66 -0
- data/test/api/general_test.rb +26 -0
- data/test/api/list_test.rb +261 -0
- data/test/api/segment_test.rb +144 -0
- data/test/api/short_url_test.rb +50 -0
- data/test/mocks/contact_mock_test.rb +24 -0
- data/test/mocks/deliver_sms_mock_test.rb +21 -0
- data/test/mocks/list_mock_test.rb +33 -0
- data/test/mocks/short_url_mock_test.rb +17 -0
- data/test/test_helper.rb +20 -0
- metadata +248 -0
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Signal
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
# SignalApi
|
2
|
+
|
3
|
+
A simple library for working with the Signal API (see [http://dev.signalhq.com](http://dev.signalhq.com))
|
4
|
+
|
5
|
+
[](http://travis-ci.org/signal/signal-ruby)
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
------------
|
9
|
+
|
10
|
+
### RubyGems ###
|
11
|
+
Signal can be installed using RubyGems
|
12
|
+
|
13
|
+
gem install signal_api
|
14
|
+
|
15
|
+
Inside your script, be sure to
|
16
|
+
|
17
|
+
require "rubygems"
|
18
|
+
require "signal_api"
|
19
|
+
|
20
|
+
### Bundler ###
|
21
|
+
If you're using Bundler, add the following to your Gemfile
|
22
|
+
|
23
|
+
gem "signal_api"
|
24
|
+
|
25
|
+
and then run
|
26
|
+
|
27
|
+
bundle install
|
28
|
+
|
29
|
+
Usage
|
30
|
+
------------
|
31
|
+
|
32
|
+
Before using any of the APIs, you will need to set your API key:
|
33
|
+
|
34
|
+
SignalApi.api_key = 'foobar123456abcxyz77'
|
35
|
+
|
36
|
+
You can find your Signal API key while logged into Signal and looking at your account settings.
|
37
|
+
|
38
|
+
You may also specify where Signal should log messages (optional):
|
39
|
+
|
40
|
+
SignalApi.logger = Rails.logger
|
41
|
+
SignalApi.logger = Logger.new(STDERR)
|
42
|
+
|
43
|
+
After SignalApi has been configured, you may use any of the API classes to interact with the Signal platform.
|
data/Rakefile
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
|
4
|
+
Bundler::GemHelper.install_tasks
|
5
|
+
|
6
|
+
begin
|
7
|
+
Bundler.setup(:default, :development)
|
8
|
+
rescue Bundler::BundlerError => e
|
9
|
+
$stderr.puts e.message
|
10
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
11
|
+
exit e.status_code
|
12
|
+
end
|
13
|
+
|
14
|
+
require 'rake'
|
15
|
+
require 'rake/testtask'
|
16
|
+
require 'yard'
|
17
|
+
|
18
|
+
task :default => :test
|
19
|
+
|
20
|
+
namespace :test do
|
21
|
+
Rake::TestTask.new(:api) do |test|
|
22
|
+
test.libs << 'lib' << 'test'
|
23
|
+
test.pattern = ENV['TEST'] || "test/api/**/*_test.rb"
|
24
|
+
test.verbose = true
|
25
|
+
end
|
26
|
+
|
27
|
+
Rake::TestTask.new(:mocks) do |test|
|
28
|
+
test.libs << 'lib' << 'test'
|
29
|
+
test.pattern = ENV['TEST'] || "test/mocks/**/*_test.rb"
|
30
|
+
test.verbose = true
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
desc 'Run all of the tests'
|
35
|
+
task :test => ["test:api", "test:mocks"]
|
36
|
+
|
37
|
+
YARD::Rake::YardocTask.new do |t|
|
38
|
+
t.files = ['lib/**/*.rb']
|
39
|
+
end
|
40
|
+
|
41
|
+
desc 'Delete yard, and other generated files'
|
42
|
+
task :clobber => [:clobber_yard]
|
43
|
+
|
44
|
+
desc 'Delete yard generated files'
|
45
|
+
task :clobber_yard do
|
46
|
+
puts 'rm -rf doc .yardoc'
|
47
|
+
FileUtils.rm_rf ['doc', '.yardoc']
|
48
|
+
end
|
49
|
+
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module SignalApi
|
2
|
+
|
3
|
+
# Managed carrier from signal api
|
4
|
+
class Carrier < SignalHttpApi
|
5
|
+
|
6
|
+
# The Carrier id from Signal
|
7
|
+
attr_reader :id
|
8
|
+
|
9
|
+
# The Carrier name from Signal
|
10
|
+
attr_reader :name
|
11
|
+
|
12
|
+
def initialize(id, name)
|
13
|
+
@id = id
|
14
|
+
@name = name
|
15
|
+
end
|
16
|
+
|
17
|
+
# Lookup a carrier on textme
|
18
|
+
#
|
19
|
+
# @param [String] mobile_phone The mobile phone to lookup
|
20
|
+
#
|
21
|
+
# @return [carrier] A Carrier object representing the Carrier on the Signal platform
|
22
|
+
def self.lookup(mobile_phone)
|
23
|
+
raise InvalidParameterException.new("mobile_phone cannot be blank") if mobile_phone.blank?
|
24
|
+
|
25
|
+
SignalApi.logger.info "Attempting to lookup carrier for mobile phone #{mobile_phone}"
|
26
|
+
|
27
|
+
with_retries do
|
28
|
+
response = get("/app/carriers/lookup/#{mobile_phone}.xml",
|
29
|
+
:format => :xml,
|
30
|
+
:headers => common_headers)
|
31
|
+
|
32
|
+
if response.code == 200 && response.parsed_response['carrier']
|
33
|
+
Carrier.new(response.parsed_response['carrier']['id'], response.parsed_response['carrier']['name'])
|
34
|
+
elsif response.code == 404
|
35
|
+
raise InvalidMobilePhoneException.new("carrier for mobile phone #{mobile_phone} could not be found")
|
36
|
+
else
|
37
|
+
handle_api_failure(response)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module SignalApi
|
2
|
+
|
3
|
+
# A Contact (person, subscriber, etc) on the Signal platform
|
4
|
+
class Contact
|
5
|
+
|
6
|
+
# The user attributes associated with the contact
|
7
|
+
attr_accessor :attributes
|
8
|
+
|
9
|
+
def initialize(attributes={})
|
10
|
+
@attributes = attributes
|
11
|
+
end
|
12
|
+
|
13
|
+
# Convenience accessor for the contact's mobile phone
|
14
|
+
def mobile_phone
|
15
|
+
@attributes['mobile-phone']
|
16
|
+
end
|
17
|
+
|
18
|
+
# Convenience accessor for the contact's email address
|
19
|
+
def email_address
|
20
|
+
@attributes['email-address']
|
21
|
+
end
|
22
|
+
|
23
|
+
# Update the contact's data on the Signal platform.
|
24
|
+
#
|
25
|
+
# @return true If the contact's data was saved successfully.
|
26
|
+
def save
|
27
|
+
validate_contact_update
|
28
|
+
|
29
|
+
xml = Builder::XmlMarkup.new
|
30
|
+
xml.user_attributes do
|
31
|
+
attributes.each do |key, value|
|
32
|
+
xml.tag!(key, value)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
contact_identifier = mobile_phone.blank? ? email_address : mobile_phone
|
37
|
+
|
38
|
+
with_retries do
|
39
|
+
response = put("/api/contacts/#{contact_identifier}.xml",
|
40
|
+
:body => xml.target!,
|
41
|
+
:format => :xml,
|
42
|
+
:headers => common_headers)
|
43
|
+
|
44
|
+
if response.code == 200
|
45
|
+
true
|
46
|
+
else
|
47
|
+
handle_api_failure(response)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def validate_contact_update
|
55
|
+
raise InvalidParameterException.new("mobile_phone or email is required") if mobile_phone.blank? && email_address.blank?
|
56
|
+
raise InvalidParameterException.new("nothing to update, only identifier provided") if attributes.count < 2
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module SignalApi
|
2
|
+
|
3
|
+
# manage a copuon group using signals api
|
4
|
+
class CouponGroup < SignalHttpApi
|
5
|
+
|
6
|
+
# Consume a coupon
|
7
|
+
#
|
8
|
+
# @param [String] coupon_group_tag The tag for the coupon group in Textme to consume this coupon from.]
|
9
|
+
# @param [String] mobile_phone The mobile phone to consume this coupon for
|
10
|
+
#
|
11
|
+
# @return a coupon code
|
12
|
+
def self.consume_coupon(coupon_group_tag, mobile_phone)
|
13
|
+
validate_consume_coupon_parameters(coupon_group_tag, mobile_phone)
|
14
|
+
|
15
|
+
SignalApi.logger.info "Attempting to consume coupon from group #{coupon_group_tag} #{mobile_phone}"
|
16
|
+
|
17
|
+
xml = Builder::XmlMarkup.new
|
18
|
+
xml.request do
|
19
|
+
xml.user do
|
20
|
+
xml.tag!('mobile_phone',mobile_phone)
|
21
|
+
end
|
22
|
+
xml.tag!('coupon_group', coupon_group_tag)
|
23
|
+
end
|
24
|
+
|
25
|
+
with_retries do
|
26
|
+
response = get('/api/coupon_groups/consume_coupon.xml',
|
27
|
+
:body => xml.target!,
|
28
|
+
:format => :xml,
|
29
|
+
:headers => common_headers)
|
30
|
+
|
31
|
+
if response.code == 200
|
32
|
+
response.parsed_response['coupon_code']
|
33
|
+
else
|
34
|
+
handle_api_failure(response)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def self.validate_consume_coupon_parameters(coupon_group_tag, mobile_phone)
|
42
|
+
raise InvalidParameterException.new("Coupon group tag cannot be blank") if coupon_group_tag.blank?
|
43
|
+
raise InvalidParameterException.new("Mobile_phone cannot be blank") if mobile_phone.blank?
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module SignalApi
|
2
|
+
|
3
|
+
# Deliver a SMS message using Signal's messaging API
|
4
|
+
class DeliverSms < SignalHttpApi
|
5
|
+
|
6
|
+
base_uri "https://api.imws.us"
|
7
|
+
|
8
|
+
# Create an instance of this class, with your messaging API credentials.
|
9
|
+
# These credentials are separate from the api_key that is used by the
|
10
|
+
# other APIs, and can be found in the API campaign configuration.
|
11
|
+
def initialize(username, password)
|
12
|
+
@username = username
|
13
|
+
@password = password
|
14
|
+
|
15
|
+
if @username.nil? || @password.nil?
|
16
|
+
raise InvalidParameterException.new("username and password must be provided")
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Deliver a SMS message to a mobile phone. Messages exceeding the 160 character
|
21
|
+
# limit will be split into multiple messages.
|
22
|
+
#
|
23
|
+
# @param [String] mobile_phone The mobile phone to send the message to
|
24
|
+
# @param [String] message The message to send
|
25
|
+
#
|
26
|
+
# @return [String] The unique message ID
|
27
|
+
def deliver(mobile_phone, message)
|
28
|
+
sanitized_mobile_phone = Phone.sanitize(mobile_phone)
|
29
|
+
unless Phone.valid?(sanitized_mobile_phone)
|
30
|
+
raise InvalidParameterException.new("An invalid mobile phone was specified: #{mobile_phone}")
|
31
|
+
end
|
32
|
+
|
33
|
+
if message.nil? || message.strip.empty?
|
34
|
+
raise InvalidParameterException.new("A message must be provided")
|
35
|
+
end
|
36
|
+
|
37
|
+
SignalApi.logger.info "Delivering the following message to #{sanitized_mobile_phone}: #{message}"
|
38
|
+
self.class.with_retries do
|
39
|
+
response = self.class.post('/messages/send',
|
40
|
+
:basic_auth => { :username => @username, :password => @password },
|
41
|
+
:query => { :mobile_phone => sanitized_mobile_phone, :message => message })
|
42
|
+
|
43
|
+
if response.code == 200
|
44
|
+
response.parsed_response =~ /^Message ID: (.*)$/
|
45
|
+
$1
|
46
|
+
else
|
47
|
+
self.class.handle_api_failure(response)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module SignalApi
|
2
|
+
# Base class for exceptions that should not be retried
|
3
|
+
class NonRetryableException < StandardError; end
|
4
|
+
|
5
|
+
# Exception raised when a request to the Signal API fails
|
6
|
+
class ApiException < StandardError; end
|
7
|
+
|
8
|
+
# Exception raised when the api key is not properly set
|
9
|
+
class InvalidApiKeyException < StandardError; end
|
10
|
+
|
11
|
+
# Authentication to the Signal platform failed. Make sure your API key is correct.
|
12
|
+
class AuthFailedException < StandardError; end
|
13
|
+
|
14
|
+
# An invalid parameter was passed to the given method
|
15
|
+
class InvalidParameterException < StandardError; end
|
16
|
+
|
17
|
+
# An invalid mobile phone number was passed
|
18
|
+
class InvalidMobilePhoneException < NonRetryableException; end
|
19
|
+
end
|
@@ -0,0 +1,224 @@
|
|
1
|
+
module SignalApi
|
2
|
+
|
3
|
+
# The type of subscription
|
4
|
+
class SubscriptionType
|
5
|
+
SMS = "SMS"
|
6
|
+
EMAIL = "EMAIL"
|
7
|
+
end
|
8
|
+
|
9
|
+
# Represents a message to be sent to users on a particular carrier
|
10
|
+
class CarrierOverrideMessage
|
11
|
+
attr_accessor :carrier_id, :text
|
12
|
+
|
13
|
+
def initialize(carrier_id, text)
|
14
|
+
@carrier_id = carrier_id
|
15
|
+
@text = text
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# Manage subscriptions to, and send messages to subscribers of a List.
|
20
|
+
class List < SignalHttpApi
|
21
|
+
|
22
|
+
# Create a new List object
|
23
|
+
#
|
24
|
+
# @param [Fixnum] list_id The ID of the list in the Signal platform
|
25
|
+
def initialize(list_id)
|
26
|
+
@list_id = list_id
|
27
|
+
raise InvalidParameterException.new("list_id cannot be nil") if @list_id.nil?
|
28
|
+
end
|
29
|
+
|
30
|
+
# Create a new subscription to the list.
|
31
|
+
#
|
32
|
+
# @param [SubscriptionType] subscription_type The type of subscription to create
|
33
|
+
# @param [Contact] contact The contact to create the subscription for. The contact must contain a valid
|
34
|
+
# mobile phone number for SMS subscriptions, and a valid email address for
|
35
|
+
# EMAIL subscriptions. Any other attributes stored with the contact will also
|
36
|
+
# be stored on the Signal platform.
|
37
|
+
# @param [Hash] options <b>Optional</b> The options used to create the subscription
|
38
|
+
# @option options [String] :source_keyword The source keyword to use when creating the subscription (for SMS subscriptions)
|
39
|
+
#
|
40
|
+
# @return [Bool] True if a subscription was created, false if the subscription already existed.
|
41
|
+
def create_subscription(subscription_type, contact, options={})
|
42
|
+
validate_create_subscription_request(subscription_type, contact, options)
|
43
|
+
|
44
|
+
builder = Builder::XmlMarkup.new
|
45
|
+
body = builder.subscription do |subscription|
|
46
|
+
subscription.tag!('subscription-type', subscription_type)
|
47
|
+
subscription.tag!('source-keyword', options[:source_keyword]) if options[:source_keyword]
|
48
|
+
subscription.user do |user|
|
49
|
+
contact.attributes.each do |attribute_name, attribute_value|
|
50
|
+
user.__send__(attribute_name, attribute_value)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
SignalApi.logger.info "Attempting to create a subscription to list #{@list_id}"
|
56
|
+
SignalApi.logger.debug "Subscription data: #{body}"
|
57
|
+
self.class.with_retries do
|
58
|
+
response = self.class.post("/api/subscription_campaigns/#{@list_id}/subscriptions.xml",
|
59
|
+
:body => body,
|
60
|
+
:format => :xml,
|
61
|
+
:headers => self.class.common_headers)
|
62
|
+
|
63
|
+
if response.code == 200
|
64
|
+
return true
|
65
|
+
else
|
66
|
+
if response.body.include?("Could not find the carrier for mobile phone")
|
67
|
+
raise SignalApi::InvalidMobilePhoneException.new(response.body)
|
68
|
+
elsif response.body.include?("already signed up")
|
69
|
+
SignalApi.logger.info response.body
|
70
|
+
return false
|
71
|
+
elsif response.body.include?("Subscriber cannot be re-added since they have unsubscribed within the past")
|
72
|
+
SignalApi.logger.info response.body
|
73
|
+
return false
|
74
|
+
elsif response.body.include?("User already subscribed, resending confirmation message")
|
75
|
+
SignalApi.logger.info response.body
|
76
|
+
return false
|
77
|
+
else
|
78
|
+
self.class.handle_api_failure(response)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# Destroy a subscription which exists in this list.
|
85
|
+
#
|
86
|
+
# @param [SubscriptionType] subscription_type The type of subscription to destroy
|
87
|
+
# @param [Contact] contact The contact to destroy the subscription for. The contact must contain a valid
|
88
|
+
# mobile phone number for SMS subscriptions, and a valid email address for
|
89
|
+
# EMAIL subscriptions.
|
90
|
+
#
|
91
|
+
# @return [Bool] True if a subscription was desctroyed, false if the subscription did not exist.
|
92
|
+
def destroy_subscription(subscription_type, contact)
|
93
|
+
validate_destroy_subscription_request(subscription_type, contact)
|
94
|
+
|
95
|
+
SignalApi.logger.info "Attempting to destroy a subscription to list #{@list_id}"
|
96
|
+
SignalApi.logger.debug "Contact data: #{contact.inspect}"
|
97
|
+
|
98
|
+
if subscription_type == SubscriptionType::SMS
|
99
|
+
contact_id = contact.mobile_phone
|
100
|
+
else
|
101
|
+
contact_id = contact.email_address
|
102
|
+
end
|
103
|
+
|
104
|
+
self.class.with_retries do
|
105
|
+
response = self.class.delete("/api/subscription_campaigns/#{@list_id}/subscriptions/#{contact_id}.xml",
|
106
|
+
:headers => self.class.common_headers)
|
107
|
+
|
108
|
+
if response.code == 200
|
109
|
+
return true
|
110
|
+
else
|
111
|
+
if response.body.include?("is not subscribed to campaign")
|
112
|
+
SignalApi.logger.info response.body
|
113
|
+
return false
|
114
|
+
elsif response.body.include?("Invalid user ID")
|
115
|
+
SignalApi.logger.info response.body
|
116
|
+
return false
|
117
|
+
else
|
118
|
+
self.class.handle_api_failure(response)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
# Sends an SMS message to the subscribers of the subscription list.
|
125
|
+
#
|
126
|
+
# @param [String] description A description of the message.
|
127
|
+
# @param [String] text The message to send. Must not be greater than 160 characters.
|
128
|
+
# @param [Hash] options <b>Optional</b> The options used when sending the message.
|
129
|
+
# @option options [Time] :send_at The date and time to send the message. The message will be
|
130
|
+
# sent immediately if not provided.
|
131
|
+
# @option options [Fixnum] :segment_id The id of the segment to send the message to. If not
|
132
|
+
# specified, the message will be sent to all subscribers in the list.
|
133
|
+
# @option options [Array<Fixnum>] :tags An array of tag ids to tag the scheduled message with.
|
134
|
+
# @option options [Array<CarrierOverrideMessage>] :carrier_overrides An alternate text message to send to
|
135
|
+
# users on a particular carrier.
|
136
|
+
# @return [Fixnum] The ID of the scheduled message on the Signal platform.
|
137
|
+
def send_message(description, text, options={})
|
138
|
+
raise InvalidParameterException.new("A description must be provided") if description.blank?
|
139
|
+
raise InvalidParameterException.new("A text message must be provided") if text.blank?
|
140
|
+
raise InvalidParameterException.new("The text message must not be greater than 160 characters") if text.size > 160
|
141
|
+
|
142
|
+
builder = Builder::XmlMarkup.new
|
143
|
+
body = builder.message do |message|
|
144
|
+
message.description(description)
|
145
|
+
message.text(text)
|
146
|
+
message.send_at(options[:send_at].strftime("%Y-%m-%d %H:%M:%S")) if options[:send_at]
|
147
|
+
message.segment_id(options[:segment_id]) if options[:segment_id]
|
148
|
+
|
149
|
+
if options[:tags]
|
150
|
+
message.tags(:type => :array) do |tags|
|
151
|
+
options[:tags].each { |tag_id| tags.tag(tag_id) }
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
if options[:carrier_overrides]
|
156
|
+
message.carrier_overrides(:type => :array) do |carrier_overrides|
|
157
|
+
options[:carrier_overrides].each do |carrier_override_message|
|
158
|
+
carrier_overrides.carrier_override do |carrier_override|
|
159
|
+
carrier_override.carrier_id(carrier_override_message.carrier_id)
|
160
|
+
carrier_override.text(carrier_override_message.text)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
SignalApi.logger.info "Attempting to send a message to list #{@list_id}"
|
168
|
+
SignalApi.logger.debug "Message data: #{body}"
|
169
|
+
self.class.with_retries do
|
170
|
+
response = self.class.post("/api/subscription_campaigns/#{@list_id}/send_message.xml",
|
171
|
+
:body => body,
|
172
|
+
:format => :xml,
|
173
|
+
:headers => self.class.common_headers)
|
174
|
+
|
175
|
+
if response.code == 200
|
176
|
+
data = response.parsed_response['scheduled_message']
|
177
|
+
data['id']
|
178
|
+
else
|
179
|
+
self.class.handle_api_failure(response)
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
private
|
185
|
+
|
186
|
+
def validate_create_subscription_request(subscription_type, contact, options)
|
187
|
+
unless [SubscriptionType::SMS, SubscriptionType::EMAIL].include?(subscription_type)
|
188
|
+
raise InvalidParameterException.new("Invalid subscription type")
|
189
|
+
end
|
190
|
+
|
191
|
+
if contact.nil?
|
192
|
+
raise InvalidParameterException.new("A contact must be provided")
|
193
|
+
end
|
194
|
+
|
195
|
+
if subscription_type == SubscriptionType::SMS && !Phone.valid?(contact.mobile_phone)
|
196
|
+
raise InvalidMobilePhoneException.new("A valid mobile phone number required for SMS subscriptions")
|
197
|
+
end
|
198
|
+
|
199
|
+
if subscription_type == SubscriptionType::EMAIL && !EmailAddress.valid?(contact.email_address)
|
200
|
+
raise InvalidParameterException.new("A valid email address required for EMAIL subscriptions")
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
def validate_destroy_subscription_request(subscription_type, contact)
|
205
|
+
unless [SubscriptionType::SMS, SubscriptionType::EMAIL].include?(subscription_type)
|
206
|
+
raise InvalidParameterException.new("Invalid subscription type")
|
207
|
+
end
|
208
|
+
|
209
|
+
if contact.nil?
|
210
|
+
raise InvalidParameterException.new("A contact must be provided")
|
211
|
+
end
|
212
|
+
|
213
|
+
if subscription_type == SubscriptionType::SMS && !Phone.valid?(contact.mobile_phone)
|
214
|
+
raise InvalidMobilePhoneException.new("A valid mobile phone number required for SMS subscriptions")
|
215
|
+
end
|
216
|
+
|
217
|
+
if subscription_type == SubscriptionType::EMAIL && !EmailAddress.valid?(contact.email_address)
|
218
|
+
raise InvalidParameterException.new("A valid email address required for EMAIL subscriptions")
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|