exacttarget 0.1.2 → 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +3 -2
- data/Gemfile.lock +4 -2
- data/README.rdoc +2 -2
- data/Rakefile +9 -0
- data/VERSION +1 -1
- data/exacttarget.gemspec +17 -9
- data/lib/exacttarget.rb +148 -6
- data/lib/exacttarget/email.rb +152 -122
- data/lib/exacttarget/group.rb +76 -0
- data/lib/exacttarget/image.rb +33 -25
- data/lib/exacttarget/job.rb +58 -0
- data/lib/exacttarget/list.rb +109 -0
- data/lib/exacttarget/templates/group.xml.erb +7 -0
- data/lib/exacttarget/templates/image.xml.erb +1 -1
- data/lib/exacttarget/templates/job.xml.erb +31 -0
- data/lib/exacttarget/templates/list.xml.erb +11 -0
- data/lib/exacttarget/templates/main.xml.erb +2 -2
- metadata +32 -8
- data/lib/exacttarget/client.rb +0 -85
data/Gemfile
CHANGED
@@ -6,9 +6,10 @@ source "http://rubygems.org"
|
|
6
6
|
# Add dependencies to develop your gem here.
|
7
7
|
# Include everything needed to run rake, tests, features, etc.
|
8
8
|
group :development do
|
9
|
-
gem "shoulda", ">=
|
9
|
+
gem "shoulda", ">= 2.11.3"
|
10
10
|
gem "bundler", "~> 1.0.0"
|
11
11
|
gem "jeweler", "~> 1.6.0"
|
12
|
-
gem "rcov", ">= 0"
|
12
|
+
gem "rcov", ">= 0.9.9"
|
13
13
|
gem "nokogiri", ">= 1.4.4"
|
14
|
+
gem "yard", ">= 0.6.8"
|
14
15
|
end
|
data/Gemfile.lock
CHANGED
@@ -10,6 +10,7 @@ GEM
|
|
10
10
|
rake (0.8.7)
|
11
11
|
rcov (0.9.9)
|
12
12
|
shoulda (2.11.3)
|
13
|
+
yard (0.6.8)
|
13
14
|
|
14
15
|
PLATFORMS
|
15
16
|
ruby
|
@@ -18,5 +19,6 @@ DEPENDENCIES
|
|
18
19
|
bundler (~> 1.0.0)
|
19
20
|
jeweler (~> 1.6.0)
|
20
21
|
nokogiri (>= 1.4.4)
|
21
|
-
rcov
|
22
|
-
shoulda
|
22
|
+
rcov (>= 0.9.9)
|
23
|
+
shoulda (>= 2.11.3)
|
24
|
+
yard (>= 0.6.8)
|
data/README.rdoc
CHANGED
@@ -21,6 +21,6 @@ See LICENSE.txt for further details.
|
|
21
21
|
|
22
22
|
== Support
|
23
23
|
|
24
|
-
|
25
|
-
|
24
|
+
Provided through RubyDoc:
|
25
|
+
* http://rubydoc.info/github/msimpson/exacttarget/master/frames
|
26
26
|
|
data/Rakefile
CHANGED
@@ -41,3 +41,12 @@ Rake::RDocTask.new do |rdoc|
|
|
41
41
|
rdoc.rdoc_files.include('README*')
|
42
42
|
rdoc.rdoc_files.include('lib/**/*.rb')
|
43
43
|
end
|
44
|
+
|
45
|
+
task :push do
|
46
|
+
system "yardoc"
|
47
|
+
system "git add ."
|
48
|
+
system "rake gemspec:generate"
|
49
|
+
system "git add ."
|
50
|
+
system "git commit -a"
|
51
|
+
system "rake git:release"
|
52
|
+
end
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.1.
|
1
|
+
0.1.3
|
data/exacttarget.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{exacttarget}
|
8
|
-
s.version = "0.1.
|
8
|
+
s.version = "0.1.3"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Matthew Simpson"]
|
12
|
-
s.date = %q{2011-05-
|
12
|
+
s.date = %q{2011-05-15}
|
13
13
|
s.description = %q{ExactTarget is a client system for communicating with the ExactTarget email system. The client supports the most up-to-date XML API and is capable of uploading email pastes, images and retrieving lists of subscribers, emails and more.}
|
14
14
|
s.email = %q{matt.simpson@alextom.com}
|
15
15
|
s.extra_rdoc_files = [
|
@@ -26,11 +26,16 @@ Gem::Specification.new do |s|
|
|
26
26
|
"VERSION",
|
27
27
|
"exacttarget.gemspec",
|
28
28
|
"lib/exacttarget.rb",
|
29
|
-
"lib/exacttarget/client.rb",
|
30
29
|
"lib/exacttarget/email.rb",
|
30
|
+
"lib/exacttarget/group.rb",
|
31
31
|
"lib/exacttarget/image.rb",
|
32
|
+
"lib/exacttarget/job.rb",
|
33
|
+
"lib/exacttarget/list.rb",
|
32
34
|
"lib/exacttarget/templates/email.xml.erb",
|
35
|
+
"lib/exacttarget/templates/group.xml.erb",
|
33
36
|
"lib/exacttarget/templates/image.xml.erb",
|
37
|
+
"lib/exacttarget/templates/job.xml.erb",
|
38
|
+
"lib/exacttarget/templates/list.xml.erb",
|
34
39
|
"lib/exacttarget/templates/main.xml.erb"
|
35
40
|
]
|
36
41
|
s.homepage = %q{http://github.com/msimpson/exacttarget}
|
@@ -44,24 +49,27 @@ Gem::Specification.new do |s|
|
|
44
49
|
s.specification_version = 3
|
45
50
|
|
46
51
|
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
47
|
-
s.add_development_dependency(%q<shoulda>, [">=
|
52
|
+
s.add_development_dependency(%q<shoulda>, [">= 2.11.3"])
|
48
53
|
s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
|
49
54
|
s.add_development_dependency(%q<jeweler>, ["~> 1.6.0"])
|
50
|
-
s.add_development_dependency(%q<rcov>, [">= 0"])
|
55
|
+
s.add_development_dependency(%q<rcov>, [">= 0.9.9"])
|
51
56
|
s.add_development_dependency(%q<nokogiri>, [">= 1.4.4"])
|
57
|
+
s.add_development_dependency(%q<yard>, [">= 0.6.8"])
|
52
58
|
else
|
53
|
-
s.add_dependency(%q<shoulda>, [">=
|
59
|
+
s.add_dependency(%q<shoulda>, [">= 2.11.3"])
|
54
60
|
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
55
61
|
s.add_dependency(%q<jeweler>, ["~> 1.6.0"])
|
56
|
-
s.add_dependency(%q<rcov>, [">= 0"])
|
62
|
+
s.add_dependency(%q<rcov>, [">= 0.9.9"])
|
57
63
|
s.add_dependency(%q<nokogiri>, [">= 1.4.4"])
|
64
|
+
s.add_dependency(%q<yard>, [">= 0.6.8"])
|
58
65
|
end
|
59
66
|
else
|
60
|
-
s.add_dependency(%q<shoulda>, [">=
|
67
|
+
s.add_dependency(%q<shoulda>, [">= 2.11.3"])
|
61
68
|
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
62
69
|
s.add_dependency(%q<jeweler>, ["~> 1.6.0"])
|
63
|
-
s.add_dependency(%q<rcov>, [">= 0"])
|
70
|
+
s.add_dependency(%q<rcov>, [">= 0.9.9"])
|
64
71
|
s.add_dependency(%q<nokogiri>, [">= 1.4.4"])
|
72
|
+
s.add_dependency(%q<yard>, [">= 0.6.8"])
|
65
73
|
end
|
66
74
|
end
|
67
75
|
|
data/lib/exacttarget.rb
CHANGED
@@ -1,9 +1,5 @@
|
|
1
1
|
# Nokogiri is required for XML parsing:
|
2
|
-
|
3
|
-
require 'nokogiri'
|
4
|
-
rescue LoadError
|
5
|
-
raise '[ExactTarget] Error: Nokogiri is missing, run "bundle install".'
|
6
|
-
end
|
2
|
+
require 'nokogiri'
|
7
3
|
|
8
4
|
# Standard:
|
9
5
|
require 'net/https'
|
@@ -12,5 +8,151 @@ require 'uri'
|
|
12
8
|
require 'erb'
|
13
9
|
|
14
10
|
# Library:
|
15
|
-
require 'exacttarget/
|
11
|
+
require 'exacttarget/job'
|
12
|
+
require 'exacttarget/list'
|
13
|
+
require 'exacttarget/group'
|
16
14
|
require 'exacttarget/email'
|
15
|
+
require 'exacttarget/image'
|
16
|
+
|
17
|
+
# ExactTarget XML API wrapper.
|
18
|
+
#
|
19
|
+
# @author Matthew Simpson (matt.simpson@alextom.com)
|
20
|
+
# @attr_reader [hash] :config Configuration hash
|
21
|
+
#
|
22
|
+
class ExactTarget
|
23
|
+
|
24
|
+
public
|
25
|
+
|
26
|
+
# Error/warning message header:
|
27
|
+
MSG = '[ExactTarget]'
|
28
|
+
# For error messages:
|
29
|
+
ERROR = "#{MSG} Error:"
|
30
|
+
# For warning messages:
|
31
|
+
WARN = "#{MSG} Warning:"
|
32
|
+
# The standard FTP name:
|
33
|
+
FTP_STANDARD_NAME = 'ExactTargetFTP'
|
34
|
+
# The standard FTP URI:
|
35
|
+
FTP_STANDARD_URI = 'ftp.exacttarget.com'
|
36
|
+
# The standard FTP path (directory):
|
37
|
+
FTP_STANDARD_PATH = '/'
|
38
|
+
# The enhanced FTP name:
|
39
|
+
FTP_ENHANCED_NAME = 'ExactTargetEnhancedFTP'
|
40
|
+
# The enhanced FTP URI:
|
41
|
+
FTP_ENHANCED_URI = 'ftp1.exacttarget.com'
|
42
|
+
# The enhanced FTP path (directory):
|
43
|
+
FTP_ENHANCED_PATH = '/import'
|
44
|
+
|
45
|
+
attr_reader :config
|
46
|
+
|
47
|
+
# Create a new ExactTarget API client.
|
48
|
+
#
|
49
|
+
# @example
|
50
|
+
# # Simple:
|
51
|
+
# client = ExactTarget.new :username => 'username', :password => 'password'
|
52
|
+
#
|
53
|
+
# # Using the enhanced FTP:
|
54
|
+
# client = ExactTarget.new(
|
55
|
+
# :username => 'username',
|
56
|
+
# :password => 'password',
|
57
|
+
# :ftp_username => '123456',
|
58
|
+
# :ftp_password => '123456',
|
59
|
+
# :ftp_name => ExactTarget::FTP_ENHANCED_NAME,
|
60
|
+
# :ftp_uri => ExactTarget::FTP_ENHANCED_URI,
|
61
|
+
# :ftp_path => ExactTarget::FTP_ENHANCED_PATH
|
62
|
+
# )
|
63
|
+
#
|
64
|
+
# @param [Hash] config Configuration hash (required)
|
65
|
+
# @option config [String] :username Username (required)
|
66
|
+
# @option config [String] :password Password (required)
|
67
|
+
# @option config [String] :api_uri ExactTarget API URI (needs to be the asp path)
|
68
|
+
# @option config [String] :ftp_username FTP username (default: import)
|
69
|
+
# @option config [String] :ftp_password FTP password (default: import)
|
70
|
+
# @option config [String] :ftp_name FTP name (default: ExactTargetFTP)
|
71
|
+
# @option config [String] :ftp_uri FTP URI (default: ftp.exacttarget.com)
|
72
|
+
# @option config [String] :ftp_path FTP path (defaults to root '/')
|
73
|
+
#
|
74
|
+
def initialize(config)
|
75
|
+
@config = {
|
76
|
+
:username => nil,
|
77
|
+
:password => nil,
|
78
|
+
:api_uri => 'https://api.dc1.exacttarget.com/integrate.asp',
|
79
|
+
:ftp_username => 'import',
|
80
|
+
:ftp_password => 'import',
|
81
|
+
:ftp_name => FTP_STANDARD_NAME,
|
82
|
+
:ftp_uri => FTP_STANDARD_URI,
|
83
|
+
:ftp_path => FTP_STANDARD_PATH
|
84
|
+
}.merge(config)
|
85
|
+
|
86
|
+
# Sanity check:
|
87
|
+
if @config[:username].nil? ||
|
88
|
+
@config[:password].nil?
|
89
|
+
raise "#{ERROR} username and password required!"
|
90
|
+
end
|
91
|
+
|
92
|
+
ftp_connect
|
93
|
+
@uri = URI.parse(@config[:api_uri])
|
94
|
+
@api = Net::HTTP.new(@uri.host, @uri.port)
|
95
|
+
@api.use_ssl = true
|
96
|
+
end
|
97
|
+
|
98
|
+
private
|
99
|
+
|
100
|
+
def ftp_connect
|
101
|
+
begin
|
102
|
+
@ftp = Net::FTP.new(@config[:ftp_uri])
|
103
|
+
@ftp.login @config[:ftp_username], @config[:ftp_password]
|
104
|
+
@ftp.chdir @config[:ftp_path]
|
105
|
+
rescue => msg
|
106
|
+
puts "#{ERROR} FTP access failed!"
|
107
|
+
raise msg
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def ftp_put(file_path)
|
112
|
+
ftp_connect if @ftp.closed?
|
113
|
+
|
114
|
+
begin
|
115
|
+
@ftp.noop
|
116
|
+
@ftp.put(file_path.to_s)
|
117
|
+
rescue => msg
|
118
|
+
puts "#{ERROR} FTP put failed!"
|
119
|
+
raise msg
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def ftp_delete(file_name)
|
124
|
+
ftp_connect if @ftp.closed?
|
125
|
+
|
126
|
+
begin
|
127
|
+
@ftp.noop
|
128
|
+
@ftp.delete(file_name.to_s)
|
129
|
+
rescue => msg
|
130
|
+
puts "#{ERROR} FTP delete failed!"
|
131
|
+
raise msg
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def render(template)
|
136
|
+
path = File.join(File.dirname(__FILE__), "exacttarget/templates/#{template.to_s}.xml.erb")
|
137
|
+
file = File.open(path, 'r').read
|
138
|
+
ERB.new(file, 0, '<>').result(binding)
|
139
|
+
end
|
140
|
+
|
141
|
+
def send(xml)
|
142
|
+
@system = xml
|
143
|
+
post = 'qf=xml&xml=' + URI.escape(render(:main))
|
144
|
+
|
145
|
+
begin
|
146
|
+
@api.post(@uri.path, post,
|
147
|
+
{
|
148
|
+
'Content-Type' => 'application/x-www-form-urlencoded',
|
149
|
+
'Content-length' => post.length.to_s
|
150
|
+
}
|
151
|
+
).body
|
152
|
+
rescue => msg
|
153
|
+
puts "#{ERROR} API request failed."
|
154
|
+
raise msg
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
end
|
data/lib/exacttarget/email.rb
CHANGED
@@ -1,135 +1,165 @@
|
|
1
|
-
|
2
|
-
class Client
|
1
|
+
class ExactTarget
|
3
2
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
3
|
+
public
|
4
|
+
|
5
|
+
# Find all emails.
|
6
|
+
#
|
7
|
+
# @param [bool] body Retrieve HTML body of each email (can cause seriously lag) [dangerous]
|
8
|
+
#
|
9
|
+
def email_find_all(body = false)
|
10
|
+
email_find({ :body => body })
|
11
|
+
end
|
12
|
+
|
13
|
+
# Retrieve an email by its ID.
|
14
|
+
#
|
15
|
+
# @param [int,string] id Email ID
|
16
|
+
# @param [hash] options Other options
|
17
|
+
# @see ExactTarget#email_find
|
18
|
+
#
|
19
|
+
def email_find_by_id(id, options = {})
|
20
|
+
email_find({
|
21
|
+
:id => id,
|
22
|
+
:body => true
|
23
|
+
}.merge(options))
|
24
|
+
end
|
25
|
+
|
26
|
+
# Find all emails who's name includes the given keyword.
|
27
|
+
#
|
28
|
+
# @param [string] name Name of the email (keyword)
|
29
|
+
# @param [hash] options Other options
|
30
|
+
# @see ExactTarget#email_find
|
31
|
+
#
|
32
|
+
def email_find_by_name(name, options = {})
|
33
|
+
email_find({
|
34
|
+
:name => name,
|
35
|
+
}.merge(options))
|
36
|
+
end
|
37
|
+
|
38
|
+
# Find all emails who's subject includes the given keyword.
|
39
|
+
#
|
40
|
+
# @param [string] subject Subject of the email (keyword)
|
41
|
+
# @param [hash] options Other options
|
42
|
+
# @see ExactTarget#email_find
|
43
|
+
#
|
44
|
+
def email_find_by_subject(subject, options = {})
|
45
|
+
email_find({
|
46
|
+
:subject => subject,
|
47
|
+
}.merge(options))
|
48
|
+
end
|
49
|
+
|
50
|
+
# Find all emails who's attributes match the selected options.
|
51
|
+
#
|
52
|
+
# @param [hash] options Options hash
|
53
|
+
# @option options [int,string] :id Email ID
|
54
|
+
# @option options [string] :name Name of the email (keyword search)
|
55
|
+
# @option options [string] :subject Subject of the email (keyword search)
|
56
|
+
# @option options [date] :start The date at which to start the search
|
57
|
+
# @option options [date] :end The date at which to end the search
|
58
|
+
# @option options [bool] :body Whether or not to retrieve the HTML body of the email
|
59
|
+
#
|
60
|
+
def email_find(options = {})
|
61
|
+
@action = 'retrieve'
|
62
|
+
@sub_action = 'all'
|
63
|
+
@type = ''
|
64
|
+
@value = ''
|
22
65
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
end
|
66
|
+
id = options[:id] || false
|
67
|
+
name = options[:name] || false
|
68
|
+
subject = options[:subject] || false
|
69
|
+
start_date = options[:start] || false
|
70
|
+
end_date = options[:end] || false
|
71
|
+
get_body = options[:body] || false
|
72
|
+
list = []
|
28
73
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
.exacttarget
|
54
|
-
.system
|
55
|
-
.email
|
56
|
-
.emaillist.each do |email|
|
57
|
-
(next if email.emailid.content != id.to_s) if id
|
58
|
-
(next if !email.emailname.content.include? name.to_s) if name
|
59
|
-
(next if !email.emailsubject.content.include? subject.to_s) if subject
|
60
|
-
|
61
|
-
date = format.call(email.emailcreateddate.content)
|
74
|
+
Nokogiri::Slop(send(render(:email)))
|
75
|
+
.exacttarget
|
76
|
+
.system
|
77
|
+
.email
|
78
|
+
.emaillist.each do |email|
|
79
|
+
(next if email.emailid.content != id.to_s) if id
|
80
|
+
(next if !email.emailname.content.include? name.to_s) if name
|
81
|
+
(next if !email.emailsubject.content.include? subject.to_s) if subject
|
82
|
+
|
83
|
+
date = Date.strptime(email.emailcreateddate.content, '%m/%d/%Y')
|
84
|
+
|
85
|
+
(next if date < start_date) if start_date && start_date.instance_of?(Date)
|
86
|
+
(next if date > end_date) if end_date && end_date.instance_of?(Date)
|
87
|
+
|
88
|
+
body = email_get_body(email.emailid.content) if get_body
|
89
|
+
|
90
|
+
email.instance_eval do
|
91
|
+
new = {
|
92
|
+
:id => emailid.content,
|
93
|
+
:name => emailname.content,
|
94
|
+
:subject => emailsubject.content,
|
95
|
+
:date => date,
|
96
|
+
:category_id => categoryid.content
|
97
|
+
}
|
62
98
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
body = email_get_body(email.emailid.content) if get_body
|
67
|
-
|
68
|
-
email.instance_eval do
|
69
|
-
new = {
|
70
|
-
:id => emailid.content,
|
71
|
-
:name => emailname.content,
|
72
|
-
:subject => emailsubject.content,
|
73
|
-
:date => email.emailcreateddate.content,
|
74
|
-
:category_id => categoryid.content
|
75
|
-
}
|
76
|
-
|
77
|
-
new[:body] = body if get_body
|
78
|
-
list << new
|
79
|
-
end
|
80
|
-
end
|
81
|
-
|
82
|
-
list
|
99
|
+
new[:body] = body if get_body
|
100
|
+
list << new
|
101
|
+
end
|
83
102
|
end
|
84
103
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
104
|
+
list
|
105
|
+
end
|
106
|
+
|
107
|
+
# Create an email.
|
108
|
+
#
|
109
|
+
# @param [string] name Name of the new email
|
110
|
+
# @param [string] subject The subject line
|
111
|
+
# @param [string] html The HTML source
|
112
|
+
# @param [string] text The text version
|
113
|
+
#
|
114
|
+
# @see ExactTarget#job_send #email_send (job_send) for sending emails.
|
115
|
+
#
|
116
|
+
def email_create(name, subject, html, text = false)
|
117
|
+
id = email_add_html(name, subject, html)
|
118
|
+
email_add_text(id, text) if text
|
119
|
+
id
|
120
|
+
end
|
121
|
+
|
122
|
+
private
|
123
|
+
|
124
|
+
def email_add_html(name, subject, html)
|
125
|
+
@action = 'add'
|
126
|
+
@sub_action = 'HTMLPaste'
|
127
|
+
@name = name.to_s
|
128
|
+
@subject = subject.to_s
|
129
|
+
@body = html
|
90
130
|
|
91
|
-
|
131
|
+
result = Nokogiri::XML(send(render(:email)))
|
132
|
+
id = result.xpath('//emailID').text
|
92
133
|
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
raise '[ExactTarget] Error: Email HTMLPaste failed.' if id.empty?
|
104
|
-
id
|
105
|
-
end
|
134
|
+
raise "#{ERROR} email HTMLPaste failed." if id.empty?
|
135
|
+
id
|
136
|
+
end
|
137
|
+
|
138
|
+
def email_add_text(id, text)
|
139
|
+
@action = 'add'
|
140
|
+
@sub_action = 'text'
|
141
|
+
@id = id
|
142
|
+
@body = text
|
106
143
|
|
107
|
-
|
108
|
-
|
109
|
-
@sub_action = 'text'
|
110
|
-
@id = id
|
111
|
-
@body = text
|
112
|
-
|
113
|
-
result = Nokogiri::XML(send(render(:email)))
|
114
|
-
info = result.xpath('//email_info').text
|
115
|
-
|
116
|
-
raise '[ExactTarget] Error: Email text update failed.' if info.empty?
|
117
|
-
true
|
118
|
-
end
|
144
|
+
result = Nokogiri::XML(send(render(:email)))
|
145
|
+
info = result.xpath('//email_info').text
|
119
146
|
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
rescue
|
130
|
-
nil
|
131
|
-
end
|
132
|
-
end
|
147
|
+
raise "#{ERROR} email text update failed." if info.empty?
|
148
|
+
true
|
149
|
+
end
|
150
|
+
|
151
|
+
def email_get_body(id)
|
152
|
+
@action = 'retrieve'
|
153
|
+
@sub_action = 'htmlemail'
|
154
|
+
@type = 'emailid'
|
155
|
+
@value = id.to_s
|
133
156
|
|
157
|
+
begin
|
158
|
+
Nokogiri::XML(send(render(:email)))
|
159
|
+
.xpath('//htmlbody').text
|
160
|
+
rescue
|
161
|
+
nil
|
162
|
+
end
|
134
163
|
end
|
164
|
+
|
135
165
|
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
class ExactTarget
|
2
|
+
|
3
|
+
public
|
4
|
+
|
5
|
+
# Retrieve a group by its ID.
|
6
|
+
#
|
7
|
+
# @param [int,string] id Group ID
|
8
|
+
# @param [hash] options Other options
|
9
|
+
# @see ExactTarget#group_find
|
10
|
+
#
|
11
|
+
def group_find_by_id(id, options = {})
|
12
|
+
group_find({
|
13
|
+
:id => id
|
14
|
+
}.merge(options))
|
15
|
+
end
|
16
|
+
|
17
|
+
# Retrieve a group by its name.
|
18
|
+
#
|
19
|
+
# @param [string] name Group name
|
20
|
+
# @param [hash] options Other options
|
21
|
+
# @see ExactTarget#group_find
|
22
|
+
#
|
23
|
+
def group_find_by_name(name, options = {})
|
24
|
+
group_find({
|
25
|
+
:name => name
|
26
|
+
}.merge(options))
|
27
|
+
end
|
28
|
+
|
29
|
+
# Retrieve a group by its description.
|
30
|
+
#
|
31
|
+
# @param [string] desc Group description
|
32
|
+
# @param [hash] options Other options
|
33
|
+
# @see ExactTarget#group_find
|
34
|
+
#
|
35
|
+
def group_find_by_desc(desc, options = {})
|
36
|
+
group_find({
|
37
|
+
:desc => desc
|
38
|
+
}.merge(options))
|
39
|
+
end
|
40
|
+
|
41
|
+
# Find all groups who's attributes match the selected options.
|
42
|
+
#
|
43
|
+
# @param [hash] options Options hash
|
44
|
+
# @option options [int,string] :id Group ID
|
45
|
+
# @option options [string] :name Name of the group (keyword search)
|
46
|
+
# @option options [string] :desc Description of the group (keyword search)
|
47
|
+
#
|
48
|
+
def group_find(options = {})
|
49
|
+
id = options[:id] || false
|
50
|
+
name = options[:name] || false
|
51
|
+
desc = options[:desc] || false
|
52
|
+
groups = []
|
53
|
+
|
54
|
+
Nokogiri::Slop(send(render(:group)))
|
55
|
+
.exacttarget
|
56
|
+
.system
|
57
|
+
.list
|
58
|
+
.groups.each do |group|
|
59
|
+
(next if group.groupID.content != id.to_s) if id
|
60
|
+
(next if !group.groupName.content.include? name.to_s) if name
|
61
|
+
(next if !group.description.content.include? desc.to_s) if desc
|
62
|
+
|
63
|
+
group.instance_eval do
|
64
|
+
groups << {
|
65
|
+
:id => groupID.content,
|
66
|
+
:name => groupName.content,
|
67
|
+
:desc => description.content
|
68
|
+
}
|
69
|
+
end
|
70
|
+
end
|
71
|
+
groups
|
72
|
+
end
|
73
|
+
|
74
|
+
alias :group_find_all :group_find
|
75
|
+
|
76
|
+
end
|
data/lib/exacttarget/image.rb
CHANGED
@@ -1,31 +1,39 @@
|
|
1
|
-
|
2
|
-
class Client
|
1
|
+
class ExactTarget
|
3
2
|
|
4
|
-
|
3
|
+
public
|
4
|
+
|
5
|
+
# Upload an image (note: uses the FTP and cleans up afterward).
|
6
|
+
#
|
7
|
+
# @param [string] file_path The file path of the image to upload/import
|
8
|
+
#
|
9
|
+
def image_import(file_path)
|
10
|
+
@name = File.basename(file_path)
|
11
|
+
|
12
|
+
ftp_put(file_path.to_s)
|
13
|
+
|
14
|
+
result = Nokogiri::XML(send(render(:image)))
|
15
|
+
desc = result.xpath('//filemanagement-description').text
|
16
|
+
total = result.xpath('//filemanagement-totalmoved').text.to_i
|
17
|
+
name = result.xpath('//filemanagement-info').text
|
18
|
+
error = desc.include?('not exist') ? 'File not found' : total < 1 ? 'Unsupported file type' : nil
|
5
19
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
end
|
16
|
-
|
17
|
-
desc = result.xpath('//filemanagement-description').text
|
18
|
-
total = result.xpath('//filemanagement-totalmoved').text.to_i
|
19
|
-
name = result.xpath('//filemanagement-info').text
|
20
|
-
error = desc.include?('not exist') ? 'File not found.' : total < 1 ? 'Unsupported file type.' : nil
|
21
|
-
|
22
|
-
{
|
23
|
-
:file_path => file_path,
|
24
|
-
:old_name => @name,
|
25
|
-
:new_name => name,
|
26
|
-
:error => error
|
27
|
-
}
|
20
|
+
count = 0
|
21
|
+
limit = 15
|
22
|
+
|
23
|
+
begin
|
24
|
+
sleep 0.5
|
25
|
+
ftp_delete(@name)
|
26
|
+
rescue
|
27
|
+
count += 1
|
28
|
+
retry if count < limit
|
28
29
|
end
|
29
30
|
|
31
|
+
{
|
32
|
+
:file_path => file_path.to_s,
|
33
|
+
:old_name => @name,
|
34
|
+
:new_name => name,
|
35
|
+
:error => error
|
36
|
+
}
|
30
37
|
end
|
38
|
+
|
31
39
|
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
class ExactTarget
|
2
|
+
|
3
|
+
public
|
4
|
+
|
5
|
+
# Send an email to a collection of lists or groups.
|
6
|
+
#
|
7
|
+
# @param [hash] options Options hash
|
8
|
+
# @option options [int,string] :id Email ID
|
9
|
+
# @option options [array] :include The collection of lists or groups to target
|
10
|
+
# @option options [array] :exclude The collection of lists or groups to skip
|
11
|
+
# @option options [string] :from_name Name of sender (only if supported by your account)
|
12
|
+
# @option options [string] :from_email Email address of sender (only if supported by your account)
|
13
|
+
# @option options [string] :additional Additional information to include
|
14
|
+
# @option options [date,datetime] :when The date and/or time which to send the email(s)
|
15
|
+
# @option options [bool] :multipart Whether or not to send in multiple parts (for MIME compatibility)
|
16
|
+
# @option options [bool] :track Whether or not to track hyperlink clicks
|
17
|
+
# @option options [bool] :test If true, suppress email(s) from Performance Reports
|
18
|
+
#
|
19
|
+
def job_send(options)
|
20
|
+
@options = {
|
21
|
+
:id => nil,
|
22
|
+
:include => [],
|
23
|
+
:exclude => [],
|
24
|
+
:from_name => nil,
|
25
|
+
:from_email => nil,
|
26
|
+
:additional => nil,
|
27
|
+
:when => nil,
|
28
|
+
:multipart => false,
|
29
|
+
:track => true,
|
30
|
+
:test => false
|
31
|
+
}.merge(options)
|
32
|
+
|
33
|
+
# Sanity check:
|
34
|
+
if @options[:id].nil? ||
|
35
|
+
@options[:include].empty?
|
36
|
+
raise "#{ERROR} id and include array/string required!"
|
37
|
+
end
|
38
|
+
|
39
|
+
@date =
|
40
|
+
(@options[:when].strftime('%-m/%-d/%Y') if
|
41
|
+
@options[:when].instance_of?(Date) ||
|
42
|
+
@options[:when].instance_of?(DateTime)) || 'immediate'
|
43
|
+
|
44
|
+
@time =
|
45
|
+
(@options[:when].strftime('%H:%M') if
|
46
|
+
@options[:when].instance_of?(DateTime)) || ''
|
47
|
+
|
48
|
+
result = Nokogiri::XML(send(render(:job)))
|
49
|
+
info = result.xpath('//job_info').text
|
50
|
+
desc = result.xpath('//job_description').text
|
51
|
+
|
52
|
+
raise "#{ERROR} job send failed !" if !info.include? 'success'
|
53
|
+
desc
|
54
|
+
end
|
55
|
+
|
56
|
+
alias :email_send :job_send
|
57
|
+
|
58
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
class ExactTarget
|
2
|
+
|
3
|
+
public
|
4
|
+
|
5
|
+
# Retrieve a list by its ID.
|
6
|
+
#
|
7
|
+
# @param [int,string] id List ID
|
8
|
+
# @param [hash] options Other options
|
9
|
+
# @see ExactTarget#list_find
|
10
|
+
#
|
11
|
+
def list_find_by_id(id, options = {})
|
12
|
+
list_find({
|
13
|
+
:id => id
|
14
|
+
}.merge(options))
|
15
|
+
end
|
16
|
+
|
17
|
+
# Find all lists who's name includes the given keyword.
|
18
|
+
#
|
19
|
+
# @param [string] name Name of the list (keyword)
|
20
|
+
# @param [hash] options Other options
|
21
|
+
# @see ExactTarget#list_find
|
22
|
+
#
|
23
|
+
def list_find_by_name(name, options = {})
|
24
|
+
list_find({
|
25
|
+
:name => name,
|
26
|
+
}.merge(options))
|
27
|
+
end
|
28
|
+
|
29
|
+
# Find all lists who's type includes the given keyword.
|
30
|
+
#
|
31
|
+
# @param [string] subject Type of the list (keyword)
|
32
|
+
# @param [hash] options Other options
|
33
|
+
# @see ExactTarget#list_find
|
34
|
+
#
|
35
|
+
def list_find_by_type(type, options = {})
|
36
|
+
list_find({
|
37
|
+
:type => subject,
|
38
|
+
}.merge(options))
|
39
|
+
end
|
40
|
+
|
41
|
+
# Find all lists who's attributes match the selected options.
|
42
|
+
#
|
43
|
+
# @param [hash] options Options hash
|
44
|
+
# @option options [int,string] :id list ID
|
45
|
+
# @option options [string] :name Name of the list (keyword search)
|
46
|
+
# @option options [string] :type Type of the list (keyword search)
|
47
|
+
# @option options [date] :start The date at which to start the search
|
48
|
+
# @option options [date] :end The date at which to end the search
|
49
|
+
#
|
50
|
+
def list_find(options = {})
|
51
|
+
id = options[:id] || false
|
52
|
+
name = options[:name] || false
|
53
|
+
type = options[:type] || false
|
54
|
+
start_date = options[:start] || false
|
55
|
+
end_date = options[:end] || false
|
56
|
+
list = list_get_all
|
57
|
+
|
58
|
+
list.select do |item|
|
59
|
+
(next if item[:id] != id.to_s) if id
|
60
|
+
(next if !item[:name].include? name.to_s) if name
|
61
|
+
(next if !item[:type].include? type.to_s) if type
|
62
|
+
(next if item[:modified] < start_date) if start_date && start_date.instance_of?(DateTime)
|
63
|
+
(next if item[:modified] > end_date) if end_date && end_date.instance_of?(DateTime)
|
64
|
+
true
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
alias :list_find_all :list_find
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
def list_get_all
|
73
|
+
@action = 'retrieve'
|
74
|
+
@type = 'listname'
|
75
|
+
@value = ''
|
76
|
+
|
77
|
+
result = Nokogiri::XML(send(render(:list)))
|
78
|
+
error = result.xpath('//error_description').text
|
79
|
+
id_list = result.xpath('//listid').map &:text
|
80
|
+
list = []
|
81
|
+
|
82
|
+
id_list.each { |id| list << list_get_details(id) } if error.empty?
|
83
|
+
list
|
84
|
+
end
|
85
|
+
|
86
|
+
def list_get_details(id)
|
87
|
+
@action = 'retrieve'
|
88
|
+
@type = 'listid'
|
89
|
+
@value = id.to_s
|
90
|
+
|
91
|
+
result = Nokogiri::XML(send(render(:list)))
|
92
|
+
error = result.xpath('//error_description').text
|
93
|
+
error = error.empty? ? nil : error
|
94
|
+
|
95
|
+
{
|
96
|
+
:id => id.to_s,
|
97
|
+
:name => result.xpath('//list_name').text,
|
98
|
+
:type => result.xpath('//list_type').text,
|
99
|
+
:modified => (DateTime.strptime(result.xpath('//modified').text, '%m/%d/%Y %I:%M:%S %p') if !error),
|
100
|
+
:total => (result.xpath('//subscriber_count').text.to_i if !error),
|
101
|
+
:subscribed => (result.xpath('//active_total').text.to_i if !error),
|
102
|
+
:unsubscribed => (result.xpath('//unsub_count').text.to_i if !error),
|
103
|
+
:bounce => (result.xpath('//bounce_count').text.to_i if !error),
|
104
|
+
:held => (result.xpath('//held_count').text.to_i if !error),
|
105
|
+
:error => error
|
106
|
+
}
|
107
|
+
end
|
108
|
+
|
109
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
<system>
|
2
|
+
|
3
|
+
<system_name>job</system_name>
|
4
|
+
<action>send</action>
|
5
|
+
<search_type>emailid</search_type>
|
6
|
+
<search_value><%= @options[:id].to_s %></search_value>
|
7
|
+
|
8
|
+
<from_name><%= @options[:from_name].to_s %></from_name>
|
9
|
+
<from_email><%= @options[:from_email].to_s %></from_email>
|
10
|
+
<additional><%= @options[:additional].to_s %></additional>
|
11
|
+
<multipart_mime><%= @options[:multipart].to_s %></multipart_mime>
|
12
|
+
<track_links><%= @options[:track].to_s %></track_links>
|
13
|
+
|
14
|
+
<send_date><%= @date %></send_date>
|
15
|
+
<send_time><%= @time %></send_time>
|
16
|
+
|
17
|
+
<lists>
|
18
|
+
<% @options[:include].each do |id| %>
|
19
|
+
<list><%= id %></list>
|
20
|
+
<% end %>
|
21
|
+
</lists>
|
22
|
+
|
23
|
+
<suppress>
|
24
|
+
<% @options[:exclude].each do |id| %>
|
25
|
+
<list><%= id %></list>
|
26
|
+
<% end %>
|
27
|
+
</suppress>
|
28
|
+
|
29
|
+
<test_send><%= @options[:test].to_s %></test_send>
|
30
|
+
|
31
|
+
</system>
|
@@ -1,8 +1,8 @@
|
|
1
1
|
<?xml version="1.0" ?>
|
2
2
|
<exacttarget>
|
3
3
|
<authorization>
|
4
|
-
<username><%= @username %></username>
|
5
|
-
<password><%= @password %></password>
|
4
|
+
<username><%= @config[:username] %></username>
|
5
|
+
<password><%= @config[:password] %></password>
|
6
6
|
</authorization>
|
7
7
|
<%= @system %>
|
8
8
|
</exacttarget>
|
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 0
|
7
7
|
- 1
|
8
|
-
-
|
9
|
-
version: 0.1.
|
8
|
+
- 3
|
9
|
+
version: 0.1.3
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Matthew Simpson
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2011-05-
|
17
|
+
date: 2011-05-15 00:00:00 -04:00
|
18
18
|
default_executable:
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
@@ -25,8 +25,10 @@ dependencies:
|
|
25
25
|
- - ">="
|
26
26
|
- !ruby/object:Gem::Version
|
27
27
|
segments:
|
28
|
-
-
|
29
|
-
|
28
|
+
- 2
|
29
|
+
- 11
|
30
|
+
- 3
|
31
|
+
version: 2.11.3
|
30
32
|
type: :development
|
31
33
|
prerelease: false
|
32
34
|
version_requirements: *id001
|
@@ -69,7 +71,9 @@ dependencies:
|
|
69
71
|
- !ruby/object:Gem::Version
|
70
72
|
segments:
|
71
73
|
- 0
|
72
|
-
|
74
|
+
- 9
|
75
|
+
- 9
|
76
|
+
version: 0.9.9
|
73
77
|
type: :development
|
74
78
|
prerelease: false
|
75
79
|
version_requirements: *id004
|
@@ -88,6 +92,21 @@ dependencies:
|
|
88
92
|
type: :development
|
89
93
|
prerelease: false
|
90
94
|
version_requirements: *id005
|
95
|
+
- !ruby/object:Gem::Dependency
|
96
|
+
name: yard
|
97
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
98
|
+
none: false
|
99
|
+
requirements:
|
100
|
+
- - ">="
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
segments:
|
103
|
+
- 0
|
104
|
+
- 6
|
105
|
+
- 8
|
106
|
+
version: 0.6.8
|
107
|
+
type: :development
|
108
|
+
prerelease: false
|
109
|
+
version_requirements: *id006
|
91
110
|
description: ExactTarget is a client system for communicating with the ExactTarget email system. The client supports the most up-to-date XML API and is capable of uploading email pastes, images and retrieving lists of subscribers, emails and more.
|
92
111
|
email: matt.simpson@alextom.com
|
93
112
|
executables: []
|
@@ -107,11 +126,16 @@ files:
|
|
107
126
|
- VERSION
|
108
127
|
- exacttarget.gemspec
|
109
128
|
- lib/exacttarget.rb
|
110
|
-
- lib/exacttarget/client.rb
|
111
129
|
- lib/exacttarget/email.rb
|
130
|
+
- lib/exacttarget/group.rb
|
112
131
|
- lib/exacttarget/image.rb
|
132
|
+
- lib/exacttarget/job.rb
|
133
|
+
- lib/exacttarget/list.rb
|
113
134
|
- lib/exacttarget/templates/email.xml.erb
|
135
|
+
- lib/exacttarget/templates/group.xml.erb
|
114
136
|
- lib/exacttarget/templates/image.xml.erb
|
137
|
+
- lib/exacttarget/templates/job.xml.erb
|
138
|
+
- lib/exacttarget/templates/list.xml.erb
|
115
139
|
- lib/exacttarget/templates/main.xml.erb
|
116
140
|
has_rdoc: true
|
117
141
|
homepage: http://github.com/msimpson/exacttarget
|
@@ -127,7 +151,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
127
151
|
requirements:
|
128
152
|
- - ">="
|
129
153
|
- !ruby/object:Gem::Version
|
130
|
-
hash:
|
154
|
+
hash: 1094960179515826332
|
131
155
|
segments:
|
132
156
|
- 0
|
133
157
|
version: "0"
|
data/lib/exacttarget/client.rb
DELETED
@@ -1,85 +0,0 @@
|
|
1
|
-
module ExactTarget
|
2
|
-
FTP_STANDARD = {
|
3
|
-
:location => 'ExactTargetFTP',
|
4
|
-
:uri => 'ftp.exacttarget.com',
|
5
|
-
:path => '/',
|
6
|
-
}
|
7
|
-
FTP_ENHANCED = {
|
8
|
-
:location => 'ExactTargetEnhancedFTP',
|
9
|
-
:uri => 'ftp1.exacttarget.com',
|
10
|
-
:path => '/import',
|
11
|
-
}
|
12
|
-
|
13
|
-
class Client
|
14
|
-
|
15
|
-
attr_reader :username, :password, :ftp
|
16
|
-
|
17
|
-
public
|
18
|
-
|
19
|
-
def initialize(username, password, ftp_username = 'import', ftp_password = 'import', ftp = FTP_STANDARD)
|
20
|
-
@username = username
|
21
|
-
@password = password
|
22
|
-
@ftp = {
|
23
|
-
:username => ftp_username.to_s,
|
24
|
-
:password => ftp_password.to_s,
|
25
|
-
:handle => Net::FTP.new(ftp[:uri])
|
26
|
-
}.merge ftp
|
27
|
-
|
28
|
-
begin
|
29
|
-
@ftp[:handle].login @ftp[:username], @ftp[:password]
|
30
|
-
@ftp[:handle].chdir @ftp[:path]
|
31
|
-
rescue Net::FTPPermError => msg
|
32
|
-
puts '[ExactTarget] Error: FTP access failed.'
|
33
|
-
raise msg
|
34
|
-
end
|
35
|
-
|
36
|
-
@uri = URI.parse('https://api.dc1.exacttarget.com/integrate.asp')
|
37
|
-
@api = Net::HTTP.new(@uri.host, @uri.port)
|
38
|
-
@api.use_ssl = true
|
39
|
-
end
|
40
|
-
|
41
|
-
private
|
42
|
-
|
43
|
-
def put(file_path)
|
44
|
-
begin
|
45
|
-
@ftp[:handle].put(file_path.to_s)
|
46
|
-
rescue Exception => msg
|
47
|
-
puts '[ExactTarget] Error: FTP put failed.'
|
48
|
-
raise msg
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
def delete(file_name)
|
53
|
-
begin
|
54
|
-
@ftp[:handle].delete(file_name.to_s)
|
55
|
-
rescue Exception => msg
|
56
|
-
puts '[ExactTarget] Error: FTP delete failed.'
|
57
|
-
raise msg
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
def render(template)
|
62
|
-
path = File.join(File.dirname(__FILE__), "templates/#{template.to_s}.xml.erb")
|
63
|
-
file = File.open(path, 'r').read
|
64
|
-
ERB.new(file, 0, '<>').result(binding)
|
65
|
-
end
|
66
|
-
|
67
|
-
def send(xml)
|
68
|
-
@system = xml
|
69
|
-
post = 'qf=xml&xml=' + URI.escape(render(:main))
|
70
|
-
|
71
|
-
begin
|
72
|
-
@api.post(@uri.path, post,
|
73
|
-
{
|
74
|
-
'Content-Type' => 'application/x-www-form-urlencoded',
|
75
|
-
'Content-length' => post.length.to_s
|
76
|
-
}
|
77
|
-
).body
|
78
|
-
rescue SocketError => msg
|
79
|
-
puts '[ExactTarget] Error: API request failed.'
|
80
|
-
raise msg
|
81
|
-
end
|
82
|
-
end
|
83
|
-
|
84
|
-
end
|
85
|
-
end
|