monkeywrench 0.1.5
Sign up to get free protection for your applications and to get access to all the features.
- data/README.mdown +78 -0
- data/Rakefile +22 -0
- data/lib/monkey_wrench.rb +1 -0
- data/lib/monkeywrench.rb +10 -0
- data/lib/monkeywrench/array.rb +9 -0
- data/lib/monkeywrench/base.rb +97 -0
- data/lib/monkeywrench/campaign.rb +0 -0
- data/lib/monkeywrench/campaign_aim.rb +0 -0
- data/lib/monkeywrench/campaign_stats.rb +0 -0
- data/lib/monkeywrench/config.rb +35 -0
- data/lib/monkeywrench/error.rb +104 -0
- data/lib/monkeywrench/hash.rb +69 -0
- data/lib/monkeywrench/helper.rb +0 -0
- data/lib/monkeywrench/list.rb +353 -0
- data/lib/monkeywrench/member.rb +33 -0
- data/lib/monkeywrench/security.rb +0 -0
- data/test/fixtures/api_fail.json +1 -0
- data/test/fixtures/campaigns_success.json +24 -0
- data/test/fixtures/listBatchSubscribe1000_success.json +1 -0
- data/test/fixtures/listBatchSubscribe10_success.json +1 -0
- data/test/fixtures/listBatchSubscribe4_success.json +1 -0
- data/test/fixtures/listBatchSubscribe5_success.json +1 -0
- data/test/fixtures/listBatchSubscribe_success.json +1 -0
- data/test/fixtures/listBatchSubscribe_with_error_success.json +1 -0
- data/test/fixtures/listBatchUnsubscribe_success.json +1 -0
- data/test/fixtures/listMemberInfo_fail.json +1 -0
- data/test/fixtures/listMemberInfo_success.json +1 -0
- data/test/fixtures/listMembers_none_success.json +1 -0
- data/test/fixtures/listMembers_success.json +8 -0
- data/test/fixtures/listSubscribe_success.json +1 -0
- data/test/fixtures/listUnsubscribe_success.json +0 -0
- data/test/fixtures/listUpdateMember_success.json +1 -0
- data/test/fixtures/listsEmpty_success.json +2 -0
- data/test/fixtures/lists_success.json +14 -0
- data/test/monkey_wrench/base_test.rb +55 -0
- data/test/monkey_wrench/base_test.rbc +1758 -0
- data/test/monkey_wrench/campaign_aim_test.rb +0 -0
- data/test/monkey_wrench/campaign_aim_test.rbc +40 -0
- data/test/monkey_wrench/campaign_stats_test.rb +0 -0
- data/test/monkey_wrench/campaign_stats_test.rbc +40 -0
- data/test/monkey_wrench/campaign_test.rb +0 -0
- data/test/monkey_wrench/campaign_test.rbc +40 -0
- data/test/monkey_wrench/hash_test.rb +57 -0
- data/test/monkey_wrench/hash_test.rbc +1636 -0
- data/test/monkey_wrench/helper_test.rb +0 -0
- data/test/monkey_wrench/helper_test.rbc +40 -0
- data/test/monkey_wrench/list_test.rb +405 -0
- data/test/monkey_wrench/list_test.rbc +10798 -0
- data/test/monkey_wrench/security_test.rb +0 -0
- data/test/monkey_wrench/security_test.rbc +40 -0
- data/test/test_helper.rb +66 -0
- data/test/test_helper.rbc +2045 -0
- metadata +169 -0
data/README.mdown
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
# MonkeyWrench
|
2
|
+
MonkeyWrench is a rubyesque API for interfacing with [Mailchimp](http://www.mailchimp.com) (or show me some love by registering [via my affiliate link](http://eepurl.com/Ge71)). It makes managing a mailing list, adding/removing subscribers, and setting up autoresponders so easy that even a monkey could do it.
|
3
|
+
|
4
|
+
The API provided by MonkeyWrench takes an idiomatic ruby approach where possible, so supported parameters and options may differ slightly to what is documented in the official Mailchimp API documentation. Where possible, refer to the MonkeyWrench docs.
|
5
|
+
|
6
|
+
## Getting Started
|
7
|
+
|
8
|
+
### Installation
|
9
|
+
|
10
|
+
The easiest way to get started is to install the gem:
|
11
|
+
|
12
|
+
gem install monkeywrench
|
13
|
+
|
14
|
+
### Usage
|
15
|
+
|
16
|
+
To get started, you need to first connect to the appropriate datacenter with your API key:
|
17
|
+
|
18
|
+
MonkeyWrench::Config.new(:datacenter => "us1",
|
19
|
+
:apikey => "your-api-key-goes-here")
|
20
|
+
|
21
|
+
From there you've got a rich API for managing Lists and Members. To subscribe a new user to a list simply do the following:
|
22
|
+
|
23
|
+
list = MonkeyWrench::List.find_by_name("My Example List")
|
24
|
+
list.subscribe("foo@bar.com")
|
25
|
+
|
26
|
+
## Further Reading
|
27
|
+
|
28
|
+
For more information, [check the documentation](http://rdoc.info/projects/rubypond/monkeywrench)
|
29
|
+
|
30
|
+
## Compatibility
|
31
|
+
|
32
|
+
Tested on the following versions:
|
33
|
+
|
34
|
+
* 1.8.7
|
35
|
+
* 1.9.2
|
36
|
+
* Ruby Enterprise Edition 1.8.7
|
37
|
+
* Rubinius 1.1.1
|
38
|
+
* JRuby 1.5.5
|
39
|
+
|
40
|
+
It may work in 1.8.6 too, but a test dependency (WebMock) won't work so tests aren't currently automated against that version.
|
41
|
+
|
42
|
+
## Status
|
43
|
+
|
44
|
+
The library is currently under development, but in production use for many clients. It's still currently using the 1.2 version of the Mailchimp API (Mailchimp haven't made any deprecation announcements yet, and they still support 1.1 so it shouldn't be an issue any time soon). I'm working towards a switch to 1.3.
|
45
|
+
|
46
|
+
## Contributing
|
47
|
+
|
48
|
+
Patches and pull requests are gladly accepted. Please make sure that you include associated tests and documentation and that your commit messages are brief but descriptive.
|
49
|
+
|
50
|
+
## Credits & Contributions
|
51
|
+
|
52
|
+
* [David Heath](https://davidheath.org/)
|
53
|
+
* [Maxime Guilbot](https://github.com/maxime)
|
54
|
+
* [Keith Marcum](https://github.com/kamarcum)
|
55
|
+
|
56
|
+
## License
|
57
|
+
|
58
|
+
MonkeyWrench is released under the MIT license.
|
59
|
+
|
60
|
+
Copyright (c) 2010 Glenn Gillen
|
61
|
+
|
62
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
63
|
+
of this software and associated documentation files (the "Software"), to deal
|
64
|
+
in the Software without restriction, including without limitation the rights
|
65
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
66
|
+
copies of the Software, and to permit persons to whom the Software is
|
67
|
+
furnished to do so, subject to the following conditions:
|
68
|
+
|
69
|
+
The above copyright notice and this permission notice shall be included in
|
70
|
+
all copies or substantial portions of the Software.
|
71
|
+
|
72
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
73
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
74
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
75
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
76
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
77
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
78
|
+
THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'rake/gempackagetask'
|
4
|
+
require 'rake/rdoctask'
|
5
|
+
|
6
|
+
spec_data = File.open('monkeywrench.gemspec').read
|
7
|
+
spec = nil
|
8
|
+
Thread.new do
|
9
|
+
spec = eval("#{spec_data}")
|
10
|
+
end.join
|
11
|
+
|
12
|
+
Rake::GemPackageTask.new(spec) do |pkg|
|
13
|
+
pkg.need_zip = false
|
14
|
+
pkg.need_tar = false
|
15
|
+
end
|
16
|
+
|
17
|
+
require 'rake/testtask'
|
18
|
+
Rake::TestTask.new(:test) do |test|
|
19
|
+
test.libs << 'lib' << 'test'
|
20
|
+
test.pattern = 'test/**/*_test.rb'
|
21
|
+
test.verbose = false
|
22
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require "#{File.dirname(__FILE__)}/monkeywrench"
|
data/lib/monkeywrench.rb
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
require "httparty"
|
2
|
+
base_dir = File.join(File.dirname(__FILE__), "monkeywrench")
|
3
|
+
["base", "config", "hash", "list", "error", "member"].each do |lib|
|
4
|
+
require File.join(base_dir, lib)
|
5
|
+
end
|
6
|
+
|
7
|
+
begin
|
8
|
+
OpenStruct.class_eval { undef :id, :type }
|
9
|
+
rescue NameError
|
10
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
module MonkeyWrench
|
3
|
+
class Base < OpenStruct
|
4
|
+
include HTTParty
|
5
|
+
|
6
|
+
@@apikey = nil
|
7
|
+
@@datacenter = nil
|
8
|
+
@@dryrun = false
|
9
|
+
|
10
|
+
class << self
|
11
|
+
def default_query_params
|
12
|
+
{ :output => "json", :apikey=> @@apikey}
|
13
|
+
end
|
14
|
+
|
15
|
+
def base_uri
|
16
|
+
"http://#{datacenter}.api.mailchimp.com/1.2/"
|
17
|
+
end
|
18
|
+
|
19
|
+
def default_retry_limit
|
20
|
+
3
|
21
|
+
end
|
22
|
+
|
23
|
+
def get(params, http_options = {})
|
24
|
+
if @@dryrun
|
25
|
+
puts "GET #{base_uri} #{params.merge(default_query_params).inspect}"
|
26
|
+
return {}
|
27
|
+
else
|
28
|
+
robustly(http_options) do
|
29
|
+
response = super(base_uri, http_options.merge(:query => params.merge(default_query_params)))
|
30
|
+
handle_errors(response.parsed_response)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def post(params, http_options = {})
|
36
|
+
if @@dryrun
|
37
|
+
puts "POST #{base_uri} #{params.merge(default_query_params).inspect}"
|
38
|
+
return {}
|
39
|
+
else
|
40
|
+
robustly(http_options) do
|
41
|
+
post_params = params.dup
|
42
|
+
get_params = default_query_params.merge(:method => post_params.delete(:method))
|
43
|
+
response = super(base_uri, http_options.merge(:body => post_params, :query => get_params))
|
44
|
+
handle_errors(response.parsed_response)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def handle_errors(objects)
|
50
|
+
return objects unless objects.respond_to?(:has_key?)
|
51
|
+
|
52
|
+
if objects.has_key?("error")
|
53
|
+
objects.replace({ "error" => MonkeyWrench::Error.new(objects['error'], objects['code']) })
|
54
|
+
elsif objects.has_key?("errors")
|
55
|
+
objects["errors"] = objects["errors"].map do |err|
|
56
|
+
message = err.delete('message')
|
57
|
+
code = err.delete('code')
|
58
|
+
MonkeyWrench::Error.new(message, code, err)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
objects
|
62
|
+
end
|
63
|
+
|
64
|
+
def apikey
|
65
|
+
@@apikey
|
66
|
+
end
|
67
|
+
|
68
|
+
def datacenter
|
69
|
+
@@datacenter
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
def self.robustly(http_options, &block)
|
75
|
+
retry_limit = http_options[:retry_limit] || default_retry_limit
|
76
|
+
attempts = 0
|
77
|
+
while attempts < retry_limit
|
78
|
+
begin
|
79
|
+
attempts += 1
|
80
|
+
return yield
|
81
|
+
rescue Timeout::Error => e
|
82
|
+
if attempts == retry_limit
|
83
|
+
raise e
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def get(*args)
|
90
|
+
self.class.get(*args)
|
91
|
+
end
|
92
|
+
|
93
|
+
def post(*args)
|
94
|
+
self.class.post(*args)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
File without changes
|
File without changes
|
File without changes
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
module MonkeyWrench
|
3
|
+
class Config < MonkeyWrench::Base
|
4
|
+
# Establishes a connection to the Mailchimp API.
|
5
|
+
#
|
6
|
+
# Can be called with either a path to a file containing credentials, or
|
7
|
+
# a hash of credentials. The formats of each are:
|
8
|
+
#
|
9
|
+
# File:
|
10
|
+
# mailchimp:
|
11
|
+
# datacenter: us1 # Or whatever DC you use
|
12
|
+
# apikey: your-api-key-goes-here
|
13
|
+
# Hash:
|
14
|
+
# { :datacenter => "us1", :apikey => "your-api-key-goes-here"}
|
15
|
+
#
|
16
|
+
# @example
|
17
|
+
# MonkeyWrench::Config.new(:datacenter => "us1",
|
18
|
+
# :apikey => "your-api-key-goes-here")
|
19
|
+
#
|
20
|
+
# @param [String, Hash] credentials accepts either a String pointing to a file
|
21
|
+
# containing the credentials, or a hash of credentials.
|
22
|
+
def initialize(credentials)
|
23
|
+
if credentials.is_a?(String)
|
24
|
+
config = YAML.load_file(credentials)["mailchimp"]
|
25
|
+
config.collect_kv!{|k,v| [k.to_sym, v]}
|
26
|
+
else
|
27
|
+
config = credentials
|
28
|
+
end
|
29
|
+
@@apikey = config[:apikey]
|
30
|
+
@@datacenter = config[:datacenter]
|
31
|
+
@@dryrun = config[:dryrun] || false
|
32
|
+
super({})
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
module MonkeyWrench
|
2
|
+
class Error < StandardError
|
3
|
+
attr_reader :code
|
4
|
+
|
5
|
+
def initialize(message, code, extra_fields = {})
|
6
|
+
super(message)
|
7
|
+
@code = code
|
8
|
+
@extra_fields = extra_fields
|
9
|
+
end
|
10
|
+
|
11
|
+
def type
|
12
|
+
types[@code]
|
13
|
+
end
|
14
|
+
|
15
|
+
def method_missing(sym, *args, &block)
|
16
|
+
return @extra_fields[sym.to_s] if @extra_fields.has_key?(sym.to_s)
|
17
|
+
end
|
18
|
+
|
19
|
+
def types
|
20
|
+
{
|
21
|
+
-32601 => 'ServerError_MethodUnknown',
|
22
|
+
-32602 => 'ServerError_InvalidParameters',
|
23
|
+
-99 => 'Unknown_Exception',
|
24
|
+
-98 => 'Request_TimedOut',
|
25
|
+
-92 => 'Zend_Uri_Exception',
|
26
|
+
-91 => 'PDOException',
|
27
|
+
-91 => 'Avesta_Db_Exception',
|
28
|
+
-90 => 'XML_RPC2_Exception',
|
29
|
+
-90 => 'XML_RPC2_FaultException',
|
30
|
+
-50 => 'Too_Many_Connections',
|
31
|
+
0 => 'Parse_Exception',
|
32
|
+
100 => 'User_Unknown',
|
33
|
+
101 => 'User_Disabled',
|
34
|
+
102 => 'User_DoesNotExist',
|
35
|
+
103 => 'User_NotApproved',
|
36
|
+
104 => 'Invalid_ApiKey',
|
37
|
+
105 => 'User_UnderMaintenance',
|
38
|
+
120 => 'User_InvalidAction',
|
39
|
+
121 => 'User_MissingEmail',
|
40
|
+
122 => 'User_CannotSendCampaign',
|
41
|
+
123 => 'User_MissingModuleOutbox',
|
42
|
+
124 => 'User_ModuleAlreadyPurchased',
|
43
|
+
125 => 'User_ModuleNotPurchased',
|
44
|
+
126 => 'User_NotEnoughCredit',
|
45
|
+
127 => 'MC_InvalidPayment',
|
46
|
+
200 => 'List_DoesNotExist',
|
47
|
+
210 => 'List_InvalidInterestFieldType',
|
48
|
+
211 => 'List_InvalidOption',
|
49
|
+
212 => 'List_InvalidUnsubMember',
|
50
|
+
213 => 'List_InvalidBounceMember',
|
51
|
+
214 => 'List_AlreadySubscribed',
|
52
|
+
215 => 'List_NotSubscribed',
|
53
|
+
220 => 'List_InvalidImport',
|
54
|
+
221 => 'MC_PastedList_Duplicate',
|
55
|
+
222 => 'MC_PastedList_InvalidImport',
|
56
|
+
230 => 'Email_AlreadySubscribed',
|
57
|
+
231 => 'Email_AlreadyUnsubscribed',
|
58
|
+
232 => 'Email_NotExists',
|
59
|
+
233 => 'Email_NotSubscribed',
|
60
|
+
250 => 'List_MergeFieldRequired',
|
61
|
+
251 => 'List_CannotRemoveEmailMerge',
|
62
|
+
252 => 'List_Merge_InvalidMergeID',
|
63
|
+
253 => 'List_TooManyMergeFields',
|
64
|
+
254 => 'List_InvalidMergeField',
|
65
|
+
270 => 'List_InvalidInterestGroup',
|
66
|
+
271 => 'List_TooManyInterestGroups',
|
67
|
+
300 => 'Campaign_DoesNotExist',
|
68
|
+
301 => 'Campaign_StatsNotAvailable',
|
69
|
+
310 => 'Campaign_InvalidAbsplit',
|
70
|
+
311 => 'Campaign_InvalidContent',
|
71
|
+
312 => 'Campaign_InvalidOption',
|
72
|
+
313 => 'Campaign_InvalidStatus',
|
73
|
+
314 => 'Campaign_NotSaved',
|
74
|
+
315 => 'Campaign_InvalidSegment',
|
75
|
+
316 => 'Campaign_InvalidRss',
|
76
|
+
317 => 'Campaign_InvalidAuto',
|
77
|
+
318 => 'MC_ContentImport_InvalidArchive',
|
78
|
+
330 => 'Invalid_EcommOrder',
|
79
|
+
350 => 'Absplit_UnknownError',
|
80
|
+
351 => 'Absplit_UnknownSplitTest',
|
81
|
+
352 => 'Absplit_UnknownTestType',
|
82
|
+
353 => 'Absplit_UnknownWaitUnit',
|
83
|
+
354 => 'Absplit_UnknownWinnerType',
|
84
|
+
355 => 'Absplit_WinnerNotSelected',
|
85
|
+
500 => 'Invalid_Analytics',
|
86
|
+
501 => 'Invalid_DateTime',
|
87
|
+
502 => 'Invalid_Email',
|
88
|
+
503 => 'Invalid_SendType',
|
89
|
+
504 => 'Invalid_Template',
|
90
|
+
505 => 'Invalid_TrackingOptions',
|
91
|
+
506 => 'Invalid_Options',
|
92
|
+
507 => 'Invalid_Folder',
|
93
|
+
508 => 'Invalid_URL',
|
94
|
+
550 => 'Module_Unknown',
|
95
|
+
551 => 'MonthlyPlan_Unknown',
|
96
|
+
552 => 'Order_TypeUnknown',
|
97
|
+
553 => 'Invalid_PagingLimit',
|
98
|
+
554 => 'Invalid_PagingStart'
|
99
|
+
}
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'cgi'
|
2
|
+
module MonkeyWrench
|
3
|
+
module Hash
|
4
|
+
|
5
|
+
# Takes a block that returns a [key, value] pair
|
6
|
+
# and builds a new hash based on those pairs
|
7
|
+
# Courtesy of http://snuxoll.com/post/2009/02/13/ruby-better-hashcollect
|
8
|
+
def collect_kv
|
9
|
+
result = {}
|
10
|
+
each do |k,v|
|
11
|
+
new_k, new_v = yield k, v
|
12
|
+
result[new_k] = new_v
|
13
|
+
end
|
14
|
+
result
|
15
|
+
end
|
16
|
+
|
17
|
+
def collect_kv!(&blk)
|
18
|
+
replace(self.collect_kv(&blk))
|
19
|
+
end
|
20
|
+
|
21
|
+
def escape_keys!
|
22
|
+
collect_kv!{|k,v| [CGI.escape(k.to_s), v]}
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_mailchimp(index = nil, parent_name = nil)
|
26
|
+
result = self.collect_kv do |k,v|
|
27
|
+
if v.is_a?(Array) && v.first.is_a?(Hash)
|
28
|
+
i = 0
|
29
|
+
v = v.inject({}) do |acc,hash|
|
30
|
+
acc.merge!(hash.to_mailchimp(i, k))
|
31
|
+
i += 1
|
32
|
+
acc
|
33
|
+
end
|
34
|
+
elsif v.is_a?(Hash)
|
35
|
+
if parent_name
|
36
|
+
v = v.collect_kv do |key,val|
|
37
|
+
keyname = CGI.escape("#{parent_name.to_s}[#{key.to_s.upcase}]")
|
38
|
+
[keyname, val.to_s]
|
39
|
+
end
|
40
|
+
else
|
41
|
+
v = { k => v }.to_mailchimp(nil, k)
|
42
|
+
end
|
43
|
+
elsif v.is_a?(Array)
|
44
|
+
results = {}
|
45
|
+
i = 0
|
46
|
+
v.each do |val|
|
47
|
+
keyname = CGI.escape("#{k}[#{i}]")
|
48
|
+
results[keyname] = val.to_s
|
49
|
+
i += 1
|
50
|
+
end
|
51
|
+
v = results
|
52
|
+
end
|
53
|
+
k = k.to_s
|
54
|
+
k = "[#{index}][#{k.upcase}]" if index
|
55
|
+
k = [parent_name, k].join if k != parent_name.to_s
|
56
|
+
[CGI.escape(k), v]
|
57
|
+
end
|
58
|
+
if result.detect{|k,v| v.is_a?(Hash)}
|
59
|
+
result.values.first
|
60
|
+
else
|
61
|
+
result
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
class Hash
|
68
|
+
include MonkeyWrench::Hash
|
69
|
+
end
|
File without changes
|
@@ -0,0 +1,353 @@
|
|
1
|
+
require 'cgi'
|
2
|
+
|
3
|
+
module MonkeyWrench
|
4
|
+
class List < MonkeyWrench::Base
|
5
|
+
# Finds a given list by name
|
6
|
+
#
|
7
|
+
# @example
|
8
|
+
# MonkeyWrench::List.find_by_name("My Example List")
|
9
|
+
#
|
10
|
+
# @param [String] list_name the list name
|
11
|
+
# @return [MonkeyWrench::List] the first list found with a matching name
|
12
|
+
def self.find_by_name(list_name)
|
13
|
+
lists = find_all.detect{|list| list.name == list_name}
|
14
|
+
end
|
15
|
+
|
16
|
+
# Will compare another list against the current one and return true if
|
17
|
+
# they are the same (based on list ID)
|
18
|
+
#
|
19
|
+
# @example
|
20
|
+
# list1 = MonkeyWrench::List.find("0a649eafc3")
|
21
|
+
# list2 = MonkeyWrench::List.find("9f9d54a0c4")
|
22
|
+
# list3 = MonkeyWrench::List.find("0a649eafc3") # Same as list1!!
|
23
|
+
# list1 == list2 # false
|
24
|
+
# list1 == list3 # true
|
25
|
+
# @param [MonkeyWrench::List] other_list Other list to compare against
|
26
|
+
# @return [Boolean]
|
27
|
+
def ==(other_list)
|
28
|
+
other_list.is_a?(self.class) && self.id == other_list.id
|
29
|
+
end
|
30
|
+
|
31
|
+
# Finds a given list by ID
|
32
|
+
#
|
33
|
+
# @example
|
34
|
+
# MonkeyWrench::List.find("0a649eafc3")
|
35
|
+
#
|
36
|
+
# @param [String] id the unique Mailchimp list ID
|
37
|
+
# @return [MonkeyWrench::List] the list
|
38
|
+
def self.find(id)
|
39
|
+
find_all.find{|e| e.id == id}
|
40
|
+
end
|
41
|
+
|
42
|
+
# Finds all lists
|
43
|
+
#
|
44
|
+
# @example
|
45
|
+
# MonkeyWrench::List.find_all
|
46
|
+
#
|
47
|
+
# @return [Array<MonkeyWrench::List>]
|
48
|
+
def self.find_all
|
49
|
+
@@lists ||= post({ :method => "lists" }).map do |list|
|
50
|
+
List.new(list)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
class << self
|
55
|
+
alias :all :find_all
|
56
|
+
end
|
57
|
+
|
58
|
+
# Clears the List cache (results from List.find_all are cached for
|
59
|
+
# performance reasons)
|
60
|
+
#
|
61
|
+
# @example
|
62
|
+
# MonkeyWrench::List.clear!
|
63
|
+
#
|
64
|
+
# @return nil
|
65
|
+
def self.clear!
|
66
|
+
@@lists = nil
|
67
|
+
end
|
68
|
+
|
69
|
+
# Returns all members for this list
|
70
|
+
#
|
71
|
+
# @example Find all members that have unsubscribed in the last 24 hours:
|
72
|
+
# MonkeyWrench.members(:status => "unsubscribed",
|
73
|
+
# :since => Time.now - 86400)
|
74
|
+
#
|
75
|
+
# @param [Hash] options additional option to include when searching.
|
76
|
+
# @option options [String] :status ('subscribed') Filter the list members by status. Can be one of the following: "subscribed", "unsubscribed", "cleaned", "updated".
|
77
|
+
# @option options [DateTime] :since Return all members whose status has changed or whose profile has changed since this date/time (in GMT).
|
78
|
+
# @option options [Integer] :start (0) For large datasets, the page number to start at.
|
79
|
+
# @option options [Integer] :limit (100) For large datasets, the number of results to return. Upper limit is set at 15000.
|
80
|
+
# @option options [Boolean] :full_details (true) Return full member details and not just email address and timestamp.
|
81
|
+
# @return [Array<MonkeyWrench::Member>]
|
82
|
+
def members(options = {})
|
83
|
+
if options[:since]
|
84
|
+
options[:since] = options[:since].strftime("%Y-%m-%d %H:%M:%S")
|
85
|
+
end
|
86
|
+
options.merge!(:id => self.id, :method => "listMembers")
|
87
|
+
response = post(options)
|
88
|
+
if options[:full_details]
|
89
|
+
response.map do |response_user|
|
90
|
+
member(response_user["email"])
|
91
|
+
end
|
92
|
+
else
|
93
|
+
response.map do |response_user|
|
94
|
+
MonkeyWrench::Member.new(response_user)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# Enumerates over each member and executes the provided block. Will
|
100
|
+
# automatically page and batch requests for members.
|
101
|
+
#
|
102
|
+
# @example
|
103
|
+
# list = MonkeyWrench::List.find("0a649eafc3")
|
104
|
+
# emails = []
|
105
|
+
# list.each_member do |member|
|
106
|
+
# emails << member.email
|
107
|
+
# end
|
108
|
+
#
|
109
|
+
# @param [Proc] &block code to execute for each member
|
110
|
+
def each_member(&block)
|
111
|
+
page = 0
|
112
|
+
loop do
|
113
|
+
batch = members(:start => page, :limit => 15000)
|
114
|
+
break if batch.empty?
|
115
|
+
batch.each do |member|
|
116
|
+
yield member
|
117
|
+
end
|
118
|
+
page += 1
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
# Updates details of list members
|
123
|
+
#
|
124
|
+
# @example Update a single member's email address
|
125
|
+
# list = MonkeyWrench::List.find("0a649eafc3")
|
126
|
+
# member = {:email => "foo@bar.com", :new_email => "bar@foo.com"}
|
127
|
+
# list.update_members(member)
|
128
|
+
#
|
129
|
+
# @example Update multiple members' email addresses
|
130
|
+
# list = MonkeyWrench::List.find("0a649eafc3")
|
131
|
+
# members = [{:email => "foo@bar.com", :new_email => "bar@foo.com"},
|
132
|
+
# {:email => "bob@bar.com", :new_email => "bob@foo.com"}]
|
133
|
+
# list.update_members(members)
|
134
|
+
#
|
135
|
+
# @param [Hash, Array<Hash>] members details of member(s) to update details
|
136
|
+
# of. Members are matched based on the value of :email, to update the
|
137
|
+
# email address assign the new address to :new_email. All other field
|
138
|
+
# names are lowercase symbols representing the MERGEVAR name in
|
139
|
+
# Mailchimp (e.g., FNAME is :fname)
|
140
|
+
# @param [Hash] options additional options when updating members.
|
141
|
+
# @option options [String] :email_type Change the email type preference for the member ('html', 'text', or 'mobile').
|
142
|
+
# @option options [Boolean] :replace_interests (true) replace the interest groups provided (will append interest groups to existing values when false).
|
143
|
+
def update_members(members, options = {})
|
144
|
+
members = members.is_a?(Array) ? members : [members]
|
145
|
+
options.merge!(:id => self.id, :method => "listUpdateMember")
|
146
|
+
members.each do |member|
|
147
|
+
mailchimp_args = {:email_address => member[:email]}
|
148
|
+
member[:email] = member[:new_email]
|
149
|
+
member.delete(:new_email)
|
150
|
+
mailchimp_args.merge!({ :merge_vars => member }.to_mailchimp)
|
151
|
+
post(options.merge(mailchimp_args))
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
# Find a member in this list with the given email address
|
156
|
+
#
|
157
|
+
# @example
|
158
|
+
# list = MonkeyWrench::List.find("0a649eafc3")
|
159
|
+
# list.member("glenn@rubypond.com")
|
160
|
+
#
|
161
|
+
# @param [String] email members email address
|
162
|
+
# @return [MonkeyWrench::Member]
|
163
|
+
def member(email)
|
164
|
+
response = post(:id => self.id, :method => "listMemberInfo", :email_address => email)
|
165
|
+
if response['error']
|
166
|
+
raise response['error']
|
167
|
+
else
|
168
|
+
MonkeyWrench::Member.new(response)
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
# Check if an email has subscribed to the list
|
173
|
+
#
|
174
|
+
# @example
|
175
|
+
# list = MonkeyWrench::List.find("0a649eafc3")
|
176
|
+
# list.member?("glenn@rubypond.com")
|
177
|
+
#
|
178
|
+
# @param [String] email members email address
|
179
|
+
# @return [Boolean]
|
180
|
+
def member?(email)
|
181
|
+
response = post(:id => self.id, :method => "listMemberInfo", :email_address => email)
|
182
|
+
if response['error']
|
183
|
+
false
|
184
|
+
else
|
185
|
+
true
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
# Subscribes a new member to the list
|
190
|
+
#
|
191
|
+
# @example Subscribe a new email address
|
192
|
+
# list.subscribe("foo@bar.com")
|
193
|
+
#
|
194
|
+
# @example Subscribe a new member with extended details
|
195
|
+
# list.subscribe({:email => "foo@bar.com", :type => :html})
|
196
|
+
#
|
197
|
+
# @example Subscribe multiple new members
|
198
|
+
# subscribers = [{:email => "foo@bar.com", :type => :html},
|
199
|
+
# {:email => "bar@foo.com", :type => :html}]
|
200
|
+
# list.subscribe(subscribe, :send_welcome => true, :double_optin => false)
|
201
|
+
#
|
202
|
+
# @param [String, Hash, Array<Hash>] contact_details the email address or hash of values for the new member
|
203
|
+
# @param [Hash] opts options when adding new member
|
204
|
+
# @option opts [Boolean] :send_welcome (false) if :double_optin if false and this is
|
205
|
+
# true, send the lists 'Welcome Email' to the member(s). Will not send email if
|
206
|
+
# updating an existing member.
|
207
|
+
# @option opts [Boolean] :double_optin (true) send an opt-in confirmation email
|
208
|
+
# @option opts [Boolean] :update_existing (false) update members that are already subscribed to the list or to return an error (false returns error)
|
209
|
+
# @option opts [Boolean] :replace_interests (true) replace interest groups or append to existing interest groups (false appends to groups)
|
210
|
+
def subscribe(contact_details, opts = {})
|
211
|
+
if contact_details.is_a?(Array)
|
212
|
+
return subscribe_many(contact_details, opts)
|
213
|
+
else
|
214
|
+
if contact_details.is_a?(Hash)
|
215
|
+
email_address = contact_details.delete(:email)
|
216
|
+
opts = opts.merge(contact_details)
|
217
|
+
else
|
218
|
+
email_address = contact_details
|
219
|
+
end
|
220
|
+
subscribe_one(email_address, opts)
|
221
|
+
return { :success => 1, :errors => []}
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
# Unsubscribes a person (or list of people) from the list
|
226
|
+
#
|
227
|
+
# @example Unsubscribe a single user
|
228
|
+
# list = MonkeyWrench::List.find("0a649eafc3")
|
229
|
+
# list.unsubscribe("glenn@rubypond.com", :send_goodbye => true) # Unsubscribe a single person
|
230
|
+
#
|
231
|
+
# @example Unsubscribe a list of users
|
232
|
+
# emails = ["glenn@rubypond.com", "me@glenngillen.com"]
|
233
|
+
# list.unsubscribe(emails, :send_goodbye => true) # Unsubscribe multiple people at once
|
234
|
+
#
|
235
|
+
# @param [String, Array<String>] email address(es) of people to unsubscribe.
|
236
|
+
# @param [Hash] opts additional option to include when unsubscribing.
|
237
|
+
# @option opts [Boolean] :delete_member (false) completely delete the member from your list instead of just unsubscribing.
|
238
|
+
# @option opts [Boolean] :send_goodbye (true) send the goodbye email to the email addresses.
|
239
|
+
# @option opts [Boolean] :send_notify (false) send the unsubscribe notification email to the address defined in the list email notification settings.
|
240
|
+
# @return [Hash] contains 2 keys. :success contains the number of successful actions, :error a list of all errors.
|
241
|
+
def unsubscribe(emails, opts = {})
|
242
|
+
emails = [*emails]
|
243
|
+
params = { :method => "listBatchUnsubscribe",
|
244
|
+
:id => self.id }
|
245
|
+
params[:delete_member] = opts[:delete_member] if opts.has_key?(:delete_member)
|
246
|
+
params[:send_goodbye] = opts[:send_goodbye] if opts.has_key?(:send_goodbye)
|
247
|
+
params[:send_notify] = opts[:send_notify] if opts.has_key?(:send_notify)
|
248
|
+
params.merge!({ :emails => emails }.to_mailchimp)
|
249
|
+
response = post(params)
|
250
|
+
return { :success => response["success_count"],
|
251
|
+
:errors => response["errors"] }
|
252
|
+
end
|
253
|
+
|
254
|
+
# Will flag the email(s) as opted-out for all future mailing for this list
|
255
|
+
#
|
256
|
+
# @example Opt-out a single user
|
257
|
+
# list = MonkeyWrench::List.find("0a649eafc3")
|
258
|
+
# list.opt_out("glenn@rubypond.com") # Opt-out a single person
|
259
|
+
#
|
260
|
+
# @example Opt-out a list of users
|
261
|
+
# emails = ["glenn@rubypond.com", "me@glenngillen.com"]
|
262
|
+
# list.opt_out(emails) # Opt-out multiple people at once
|
263
|
+
#
|
264
|
+
# @param [String, Array<String>] email address(es) of people to opt-out.
|
265
|
+
# @return [Hash] contains 2 keys. :success contains the number of successful actions, :error a list of all errors.
|
266
|
+
def opt_out(emails)
|
267
|
+
emails = [*emails]
|
268
|
+
subscribe(emails.map{|email| { :email => email }})
|
269
|
+
unsubscribe(emails, :send_goodbye => false, :send_notify => false)
|
270
|
+
end
|
271
|
+
|
272
|
+
private
|
273
|
+
def self.reserved_keys
|
274
|
+
[:email, :type, :double_optin, :update_existing, :replace_interests,
|
275
|
+
:send_welcome, :emails, :send_notify, :send_goodbye, :delete_member]
|
276
|
+
end
|
277
|
+
|
278
|
+
def subscribe_many(subscribers, opts = {})
|
279
|
+
if opts[:send_welcome]
|
280
|
+
subscribe_one_at_a_time(subscribers, opts)
|
281
|
+
else
|
282
|
+
subscribe_in_batches(subscribers, opts)
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
def batch_size
|
287
|
+
1000
|
288
|
+
end
|
289
|
+
|
290
|
+
def each_batch(subscribers, batch_size, &block)
|
291
|
+
i = 0
|
292
|
+
while i < subscribers.size
|
293
|
+
start = Time.now
|
294
|
+
yield subscribers[i..(i+batch_size-1)], i
|
295
|
+
i += batch_size
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
def subscribe_in_batches(subscribers, opts)
|
300
|
+
cumulative_response = { :success => 0, :errors => [] }
|
301
|
+
each_batch(subscribers, batch_size) do |batch, i|
|
302
|
+
response = subscribe_one_batch(batch, opts)
|
303
|
+
cumulative_response[:success] += (response["success_count"] || 0)
|
304
|
+
cumulative_response[:errors] += (response["errors"] || [])
|
305
|
+
end
|
306
|
+
cumulative_response
|
307
|
+
end
|
308
|
+
|
309
|
+
def subscribe_one_batch(subscribers, opts)
|
310
|
+
params = { :id => self.id, :method => "listBatchSubscribe" }
|
311
|
+
params[:double_optin] = opts[:double_optin] if opts.has_key?(:double_optin)
|
312
|
+
params[:update_existing] = opts[:update_existing] if opts.has_key?(:update_existing)
|
313
|
+
params[:replace_interests] = opts[:replace_interests] if opts.has_key?(:replace_interests)
|
314
|
+
params.merge!({ :batch => subscribers }.to_mailchimp)
|
315
|
+
post(params, :timeout => timeout_for_batch(subscribers))
|
316
|
+
end
|
317
|
+
|
318
|
+
def timeout_for_batch(batch)
|
319
|
+
# 5 mins for a batch of 1000
|
320
|
+
((batch.size.to_f / 1000) * (5 * 60)).to_i
|
321
|
+
end
|
322
|
+
|
323
|
+
def subscribe_one_at_a_time(subscribers, opts)
|
324
|
+
cumulative_response = { :success => 0, :errors => [] }
|
325
|
+
subscribers.each do |subscriber|
|
326
|
+
params = opts.merge(subscriber)
|
327
|
+
params.delete(:email)
|
328
|
+
if subscribe_one(subscriber[:email], params) == true
|
329
|
+
cumulative_response[:success] += 1
|
330
|
+
else
|
331
|
+
cumulative_response[:errors] << response["error"]
|
332
|
+
end
|
333
|
+
end
|
334
|
+
cumulative_response
|
335
|
+
end
|
336
|
+
|
337
|
+
def subscribe_one(email_address, opts = {})
|
338
|
+
params = {
|
339
|
+
:type => opts.delete(:type),
|
340
|
+
:double_optin => opts.delete(:double_optin),
|
341
|
+
:update_existing => opts.delete(:update_existing),
|
342
|
+
:replace_interests => opts.delete(:replace_interests),
|
343
|
+
:send_welcome => opts.delete(:send_welcome),
|
344
|
+
:email_address => email_address
|
345
|
+
}
|
346
|
+
params.reject!{ |k,v| v.nil? }
|
347
|
+
merge_vars = { :merge_vars => opts }.to_mailchimp
|
348
|
+
params.merge!(merge_vars)
|
349
|
+
params.merge!(:method => "listSubscribe", :id => self.id)
|
350
|
+
post(params)
|
351
|
+
end
|
352
|
+
end
|
353
|
+
end
|