reidab-campaign_monitor 1.3.3
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/MIT-LICENSE +20 -0
- data/README.rdoc +63 -0
- data/Rakefile +31 -0
- data/TODO +6 -0
- data/campaign_monitor.gemspec +53 -0
- data/init.rb +1 -0
- data/install.rb +0 -0
- data/lib/campaign_monitor.rb +270 -0
- data/lib/campaign_monitor/base.rb +55 -0
- data/lib/campaign_monitor/campaign.rb +240 -0
- data/lib/campaign_monitor/client.rb +228 -0
- data/lib/campaign_monitor/helpers.rb +27 -0
- data/lib/campaign_monitor/list.rb +217 -0
- data/lib/campaign_monitor/misc.rb +46 -0
- data/lib/campaign_monitor/result.rb +31 -0
- data/lib/campaign_monitor/subscriber.rb +43 -0
- data/support/class_enhancements.rb +35 -0
- data/support/faster-xml-simple/lib/faster_xml_simple.rb +187 -0
- data/support/faster-xml-simple/test/regression_test.rb +47 -0
- data/support/faster-xml-simple/test/test_helper.rb +17 -0
- data/support/faster-xml-simple/test/xml_simple_comparison_test.rb +46 -0
- data/test/campaign_monitor_test.rb +90 -0
- data/test/campaign_test.rb +110 -0
- data/test/client_test.rb +129 -0
- data/test/list_test.rb +115 -0
- data/test/test_helper.rb +27 -0
- metadata +97 -0
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2006 Jordan Brock
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
= campaign_monitor
|
2
|
+
|
3
|
+
This RubyGem provides access to the Campaign Monitor API (http://www.campaignmonitor.com/api).
|
4
|
+
|
5
|
+
Campaign Monitor recently made some changes to their API.
|
6
|
+
|
7
|
+
patientslikeme's fork makes the following changes:
|
8
|
+
|
9
|
+
* host changed from http://app.campaignmonitor.com to http://api.createsend.com
|
10
|
+
* ID values are no longer sent #to_i because they are hex strings
|
11
|
+
* added support for subscribers with custom fields using SOAP API
|
12
|
+
* refactored gemspec to build on github
|
13
|
+
* misc. cleanup and refactoring
|
14
|
+
|
15
|
+
This fork integrates the following additional changes:
|
16
|
+
|
17
|
+
* updates Subscriber#is_subscribed? to work with the latest API. (from amiel)
|
18
|
+
* adds support for array values in custom_field_hash (from oferlin)
|
19
|
+
* API key cleanup (from yyyc514)
|
20
|
+
* added ability to get fetch lists by id using List#GetDetails or List#[] (from yyyc514)
|
21
|
+
* added support for adding and deleting clients (from yyyc514)
|
22
|
+
* added support for querying CM for countries and timezones (from yyyc514)
|
23
|
+
* added support for creating campaigns (from yyyc514)
|
24
|
+
* general test and api cleanup (from yyyc514)
|
25
|
+
|
26
|
+
|
27
|
+
== Pre-requisites
|
28
|
+
|
29
|
+
An account with Campaign Monitor and the API Key. Accounts are free and can be created at
|
30
|
+
http://www.campaignmonitor.com.
|
31
|
+
|
32
|
+
== Resources
|
33
|
+
|
34
|
+
=== Install
|
35
|
+
gem install patientslikeme-campaign_monitor
|
36
|
+
|
37
|
+
=== Git Repository
|
38
|
+
http://github.com/patientslikeme/campaign_monitor
|
39
|
+
|
40
|
+
|
41
|
+
== Usage
|
42
|
+
|
43
|
+
cm = CampaignMonitor.new # assumes you've set CAMPAIGN_MONITOR_API_KEY in your project
|
44
|
+
|
45
|
+
for client in cm.clients
|
46
|
+
for list in client.lists
|
47
|
+
client.name # => returns the name
|
48
|
+
|
49
|
+
# modify a subscriber list
|
50
|
+
list.add_subscriber(email, name, custom_fields_hash)
|
51
|
+
list.remove_subscriber(email)
|
52
|
+
list.add_and_resubscribe(email, name, custom_fields_hash)
|
53
|
+
|
54
|
+
# get subscriber list details
|
55
|
+
subscribers = list.active_subscribers(since_time)
|
56
|
+
unsubscribed = list.unsubscribed(since_time)
|
57
|
+
bounced = list.bounced(since_time)
|
58
|
+
end
|
59
|
+
|
60
|
+
for campaign in client.campaigns
|
61
|
+
|
62
|
+
end
|
63
|
+
end
|
data/Rakefile
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake/gempackagetask'
|
3
|
+
require 'rake/testtask'
|
4
|
+
require 'rake/rdoctask'
|
5
|
+
|
6
|
+
# read the contents of the gemspec, eval it, and assign it to 'spec'
|
7
|
+
# this lets us maintain all gemspec info in one place. Nice and DRY.
|
8
|
+
spec = eval(IO.read("campaign_monitor.gemspec"))
|
9
|
+
|
10
|
+
Rake::GemPackageTask.new(spec) do |pkg|
|
11
|
+
pkg.gem_spec = spec
|
12
|
+
end
|
13
|
+
|
14
|
+
task :install => [:package] do
|
15
|
+
sh %{sudo gem install pkg/#{spec.name}-#{spec.version}}
|
16
|
+
end
|
17
|
+
|
18
|
+
task :default => [:test]
|
19
|
+
|
20
|
+
Rake::TestTask.new do |t|
|
21
|
+
t.libs << "test"
|
22
|
+
t.test_files = FileList['test/*_test.rb']
|
23
|
+
t.verbose = true
|
24
|
+
end
|
25
|
+
|
26
|
+
Rake::RDocTask.new do |rd|
|
27
|
+
rd.main = "README.rdoc"
|
28
|
+
rd.rdoc_files.include("README.rdoc", "lib/**/*.rb")
|
29
|
+
rd.rdoc_dir = 'doc'
|
30
|
+
rd.options = spec.rdoc_options
|
31
|
+
end
|
data/TODO
ADDED
@@ -0,0 +1,6 @@
|
|
1
|
+
* Decide on convents about when to raise and when to return true/false
|
2
|
+
|
3
|
+
So far I've kind of taken the stance if we're retrieving a list and there
|
4
|
+
are only two possible failures (APIKey or primary key) to raise if there
|
5
|
+
is an error but for update/save/create operations to return true/false
|
6
|
+
and then let the user probe @object.result for more details
|
@@ -0,0 +1,53 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.platform = Gem::Platform::RUBY
|
3
|
+
s.name = 'campaign_monitor'
|
4
|
+
s.version = "1.3.3"
|
5
|
+
s.summary = 'Provides access to the Campaign Monitor API.'
|
6
|
+
s.description = <<-EOF
|
7
|
+
A simple wrapper class that provides basic access to the Campaign Monitor API.
|
8
|
+
EOF
|
9
|
+
s.author = 'Jeremy Weiskotten'
|
10
|
+
s.email = 'jweiskotten@patientslikeme.com'
|
11
|
+
s.homepage = 'http://github.com/reidab/campaign_monitor/'
|
12
|
+
s.has_rdoc = true
|
13
|
+
|
14
|
+
s.requirements << 'none'
|
15
|
+
s.require_path = 'lib'
|
16
|
+
|
17
|
+
s.add_dependency 'xml-simple', ['>= 1.0.11']
|
18
|
+
s.add_dependency 'soap4r', ['>= 1.5.8']
|
19
|
+
|
20
|
+
s.files = [
|
21
|
+
'campaign_monitor.gemspec',
|
22
|
+
'init.rb',
|
23
|
+
'install.rb',
|
24
|
+
'MIT-LICENSE',
|
25
|
+
'Rakefile',
|
26
|
+
'README.rdoc',
|
27
|
+
'TODO',
|
28
|
+
|
29
|
+
'lib/campaign_monitor.rb',
|
30
|
+
'lib/campaign_monitor/base.rb',
|
31
|
+
'lib/campaign_monitor/misc.rb',
|
32
|
+
'lib/campaign_monitor/campaign.rb',
|
33
|
+
'lib/campaign_monitor/client.rb',
|
34
|
+
'lib/campaign_monitor/helpers.rb',
|
35
|
+
'lib/campaign_monitor/list.rb',
|
36
|
+
'lib/campaign_monitor/result.rb',
|
37
|
+
'lib/campaign_monitor/subscriber.rb',
|
38
|
+
|
39
|
+
'support/class_enhancements.rb',
|
40
|
+
'support/faster-xml-simple/lib/faster_xml_simple.rb',
|
41
|
+
'support/faster-xml-simple/test/regression_test.rb',
|
42
|
+
'support/faster-xml-simple/test/test_helper.rb',
|
43
|
+
'support/faster-xml-simple/test/xml_simple_comparison_test.rb',
|
44
|
+
|
45
|
+
'test/campaign_monitor_test.rb',
|
46
|
+
'test/campaign_test.rb',
|
47
|
+
'test/client_test.rb',
|
48
|
+
'test/list_test.rb',
|
49
|
+
'test/test_helper.rb'
|
50
|
+
]
|
51
|
+
|
52
|
+
s.test_file = 'test/campaign_monitor_test.rb'
|
53
|
+
end
|
data/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'campaign_monitor'
|
data/install.rb
ADDED
File without changes
|
@@ -0,0 +1,270 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'cgi'
|
3
|
+
require 'net/http'
|
4
|
+
require 'xmlsimple'
|
5
|
+
require 'date'
|
6
|
+
gem 'soap4r'
|
7
|
+
|
8
|
+
require File.join(File.dirname(__FILE__), '../support/class_enhancements.rb')
|
9
|
+
require File.join(File.dirname(__FILE__), 'campaign_monitor/helpers.rb')
|
10
|
+
require File.join(File.dirname(__FILE__), 'campaign_monitor/misc.rb')
|
11
|
+
require File.join(File.dirname(__FILE__), 'campaign_monitor/base.rb')
|
12
|
+
require File.join(File.dirname(__FILE__), 'campaign_monitor/client.rb')
|
13
|
+
require File.join(File.dirname(__FILE__), 'campaign_monitor/list.rb')
|
14
|
+
require File.join(File.dirname(__FILE__), 'campaign_monitor/subscriber.rb')
|
15
|
+
require File.join(File.dirname(__FILE__), 'campaign_monitor/result.rb')
|
16
|
+
require File.join(File.dirname(__FILE__), 'campaign_monitor/campaign.rb')
|
17
|
+
|
18
|
+
# A wrapper class to access the Campaign Monitor API. Written using the wonderful
|
19
|
+
# Flickr interface by Scott Raymond as a guide on how to access remote web services
|
20
|
+
#
|
21
|
+
# For more information on the Campaign Monitor API, visit http://campaignmonitor.com/api
|
22
|
+
#
|
23
|
+
# Author:: Jordan Brock <jordan@spintech.com.au>
|
24
|
+
# Copyright:: Copyright (c) 2006 Jordan Brock <jordan@spintech.com.au>
|
25
|
+
# License:: MIT <http://www.opensource.org/licenses/mit-license.php>
|
26
|
+
#
|
27
|
+
# USAGE:
|
28
|
+
# require 'campaign_monitor'
|
29
|
+
# cm = CampaignMonitor.new(API_KEY) # creates a CampaignMonitor object
|
30
|
+
# # Can set CAMPAIGN_MONITOR_API_KEY in environment.rb
|
31
|
+
# cm.clients # Returns an array of clients associated with
|
32
|
+
# # the user account
|
33
|
+
# cm.campaigns(client_id)
|
34
|
+
# cm.lists(client_id)
|
35
|
+
# cm.add_subscriber(list_id, email, name)
|
36
|
+
#
|
37
|
+
# == CLIENT
|
38
|
+
# client = Client[client_id] # find an existing client
|
39
|
+
# client = Client.new(attributes)
|
40
|
+
# client.Create
|
41
|
+
# client.Delete
|
42
|
+
# client.GetDetail
|
43
|
+
# client.UpdateAccessAndBilling
|
44
|
+
# client.UpdateBasics
|
45
|
+
# client.update # update basics, access, and billing
|
46
|
+
# client.lists # OR
|
47
|
+
# client.GetLists
|
48
|
+
# client.lists.build # to create a new unsaved list for a client
|
49
|
+
# client.campaigns # OR
|
50
|
+
# client.GetCampaigns
|
51
|
+
#
|
52
|
+
# == LIST
|
53
|
+
# list = List[list_id] # find an existing list
|
54
|
+
# list = List.new(attributes)
|
55
|
+
# list.Create
|
56
|
+
# list.Delete
|
57
|
+
# list.Update
|
58
|
+
# list.add_subscriber(email, name)
|
59
|
+
# list.remove_subscriber(email)
|
60
|
+
# list.active_subscribers(date)
|
61
|
+
# list.unsubscribed(date)
|
62
|
+
# list.bounced(date)
|
63
|
+
#
|
64
|
+
# == CAMPAIGN
|
65
|
+
# campaign = Campaign.new(campaign_id)
|
66
|
+
# campaign.clicks
|
67
|
+
# campaign.opens
|
68
|
+
# campaign.bounces
|
69
|
+
# campaign.unsubscribes
|
70
|
+
# campaign.number_recipients
|
71
|
+
# campaign.number_clicks
|
72
|
+
# campaign.number_opens
|
73
|
+
# campaign.number_bounces
|
74
|
+
# campaign.number_unsubscribes
|
75
|
+
#
|
76
|
+
#
|
77
|
+
# == SUBSCRIBER
|
78
|
+
# subscriber = Subscriber.new(email)
|
79
|
+
# subscriber.add(list_id)
|
80
|
+
# subscriber.unsubscribe(list_id)
|
81
|
+
#
|
82
|
+
# == Data Types
|
83
|
+
# SubscriberBounce
|
84
|
+
# SubscriberClick
|
85
|
+
# SubscriberOpen
|
86
|
+
# SubscriberUnsubscribe
|
87
|
+
# Result
|
88
|
+
#
|
89
|
+
class CampaignMonitor
|
90
|
+
include CampaignMonitor::Helpers
|
91
|
+
|
92
|
+
class InvalidAPIKey < StandardError
|
93
|
+
end
|
94
|
+
|
95
|
+
class ApiError < StandardError
|
96
|
+
end
|
97
|
+
|
98
|
+
attr_reader :api_key, :api_url
|
99
|
+
|
100
|
+
# Replace this API key with your own (http://www.campaignmonitor.com/api/)
|
101
|
+
def initialize(api_key=CAMPAIGN_MONITOR_API_KEY)
|
102
|
+
@api_key = api_key
|
103
|
+
@api_url = 'http://api.createsend.com/api/api.asmx'
|
104
|
+
CampaignMonitor::Base.client=self
|
105
|
+
end
|
106
|
+
|
107
|
+
# Takes a CampaignMonitor API method name and set of parameters;
|
108
|
+
# returns an XmlSimple object with the response
|
109
|
+
def request(method, params)
|
110
|
+
request_xml=http_get(request_url(method, params))
|
111
|
+
begin
|
112
|
+
response = PARSER.xml_in(request_xml, { 'keeproot' => false,
|
113
|
+
'forcearray' => %w[List Campaign Subscriber Client SubscriberOpen SubscriberUnsubscribe SubscriberClick SubscriberBounce],
|
114
|
+
'noattr' => true })
|
115
|
+
response.delete('d1p1:type')
|
116
|
+
response.delete("d1p1:http://www.w3.org/2001/XMLSchema-instance:type")
|
117
|
+
response
|
118
|
+
# rescue XML::Parser::ParseError
|
119
|
+
rescue XML::Error
|
120
|
+
{ "Code" => 500, "Message" => request_xml.split(/\r?\n/).first, "FullError" => request_xml }
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
# Takes a CampaignMonitor API method name and set of parameters; returns the correct URL for the REST API.
|
125
|
+
def request_url(method, params={})
|
126
|
+
params.merge!('ApiKey' => api_key)
|
127
|
+
|
128
|
+
query = params.collect do |key, value|
|
129
|
+
"#{CGI.escape(key.to_s)}=#{CGI.escape(value.to_s)}"
|
130
|
+
end.sort * '&'
|
131
|
+
|
132
|
+
"#{api_url}/#{method}?#{query}"
|
133
|
+
end
|
134
|
+
|
135
|
+
# Does an HTTP GET on a given URL and returns the response body
|
136
|
+
def http_get(url)
|
137
|
+
response=Net::HTTP.get_response(URI.parse(url))
|
138
|
+
response.body.to_s
|
139
|
+
end
|
140
|
+
|
141
|
+
# By overriding the method_missing method, it is possible to easily support all of the methods
|
142
|
+
# available in the API
|
143
|
+
def method_missing(method_id, params = {})
|
144
|
+
puts " CM: #{method_id} (#{params.inspect})" if $debug
|
145
|
+
res=request(method_id.id2name.gsub(/_/, '.'), params)
|
146
|
+
puts " returning: #{res.inspect}" if $debug
|
147
|
+
res
|
148
|
+
end
|
149
|
+
|
150
|
+
# Returns an array of Client objects associated with the API Key
|
151
|
+
#
|
152
|
+
# Example
|
153
|
+
# @cm = CampaignMonitor.new()
|
154
|
+
# @clients = @cm.clients
|
155
|
+
#
|
156
|
+
# for client in @clients
|
157
|
+
# puts client.name
|
158
|
+
# end
|
159
|
+
def clients
|
160
|
+
handle_response(User_GetClients()) do |response|
|
161
|
+
response["Client"].collect{|c| Client.new({"ClientID" => c["ClientID"], "CompanyName" => c["Name"]})}
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def new_client
|
166
|
+
Client.new(nil)
|
167
|
+
end
|
168
|
+
|
169
|
+
def system_date
|
170
|
+
User_GetSystemDate()
|
171
|
+
end
|
172
|
+
|
173
|
+
def parsed_system_date
|
174
|
+
DateTime.strptime(system_date, timestamp_format)
|
175
|
+
end
|
176
|
+
|
177
|
+
def countries
|
178
|
+
handle_response(User_GetCountries()) do | response |
|
179
|
+
response["string"]
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
def timezones
|
184
|
+
handle_response(User_GetTimezones()) do | response |
|
185
|
+
response["string"]
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
# Returns an array of Campaign objects associated with the specified Client ID
|
190
|
+
#
|
191
|
+
# Example
|
192
|
+
# @cm = CampaignMonitor.new()
|
193
|
+
# @campaigns = @cm.campaigns(12345)
|
194
|
+
#
|
195
|
+
# for campaign in @campaigns
|
196
|
+
# puts campaign.subject
|
197
|
+
# end
|
198
|
+
def campaigns(client_id)
|
199
|
+
handle_response(Client_GetCampaigns("ClientID" => client_id)) do |response|
|
200
|
+
response["Campaign"].collect{|c| Campaign.new(c) }
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
# Returns an array of Subscriber Lists for the specified Client ID
|
205
|
+
#
|
206
|
+
# Example
|
207
|
+
# @cm = CampaignMonitor.new()
|
208
|
+
# @lists = @cm.lists(12345)
|
209
|
+
#
|
210
|
+
# for list in @lists
|
211
|
+
# puts list.name
|
212
|
+
# end
|
213
|
+
def lists(client_id)
|
214
|
+
handle_response(Client_GetLists("ClientID" => client_id)) do |response|
|
215
|
+
response["List"].collect{|l| List.new({"ListID" => l["ListID"], "Title" => l["Name"]})}
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
# A quick method of adding a subscriber to a list. Returns a Result object
|
220
|
+
#
|
221
|
+
# Example
|
222
|
+
# @cm = CampaignMonitor.new()
|
223
|
+
# result = @cm.add_subscriber(12345, "ralph.wiggum@simpsons.net", "Ralph Wiggum")
|
224
|
+
#
|
225
|
+
# if result.succeeded?
|
226
|
+
# puts "Subscriber Added to List"
|
227
|
+
# end
|
228
|
+
def add_subscriber(list_id, email, name)
|
229
|
+
Result.new(Subscriber_Add("ListID" => list_id, "Email" => email, "Name" => name))
|
230
|
+
end
|
231
|
+
|
232
|
+
def using_soap
|
233
|
+
driver = wsdl_driver_factory.create_rpc_driver
|
234
|
+
driver.wiredump_dev = STDERR if $debug
|
235
|
+
response = yield(driver)
|
236
|
+
driver.reset_stream
|
237
|
+
|
238
|
+
response
|
239
|
+
end
|
240
|
+
|
241
|
+
protected
|
242
|
+
|
243
|
+
def wsdl_driver_factory
|
244
|
+
SOAP::WSDLDriverFactory.new("#{api_url}?WSDL")
|
245
|
+
end
|
246
|
+
|
247
|
+
end
|
248
|
+
|
249
|
+
# If libxml is installed, we use the FasterXmlSimple library, that provides most of the functionality of XmlSimple
|
250
|
+
# except it uses the xml/libxml library for xml parsing (rather than REXML).
|
251
|
+
# If libxml isn't installed, we just fall back on XmlSimple.
|
252
|
+
|
253
|
+
PARSER =
|
254
|
+
begin
|
255
|
+
require 'xml/libxml'
|
256
|
+
# Older version of libxml aren't stable (bus error when requesting attributes that don't exist) so we
|
257
|
+
# have to use a version greater than '0.3.8.2'.
|
258
|
+
raise LoadError unless XML::Parser::VERSION > '0.3.8.2'
|
259
|
+
$:.push(File.join(File.dirname(__FILE__), '..', 'support', 'faster-xml-simple', 'lib'))
|
260
|
+
require 'faster_xml_simple'
|
261
|
+
p 'Using libxml-ruby'
|
262
|
+
FasterXmlSimple
|
263
|
+
rescue LoadError
|
264
|
+
begin
|
265
|
+
require 'rexml-expansion-fix'
|
266
|
+
rescue LoadError => e
|
267
|
+
p 'Cannot load rexml security patch'
|
268
|
+
end
|
269
|
+
XmlSimple
|
270
|
+
end
|