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 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", ">= 0"
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
@@ -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)
@@ -21,6 +21,6 @@ See LICENSE.txt for further details.
21
21
 
22
22
  == Support
23
23
 
24
- API documentaiton will be available soon. But until then,
25
- email all bugs, concerns, etc. to matt.simpson@alextom.com.
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.2
1
+ 0.1.3
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{exacttarget}
8
- s.version = "0.1.2"
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-08}
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>, [">= 0"])
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>, [">= 0"])
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>, [">= 0"])
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
 
@@ -1,9 +1,5 @@
1
1
  # Nokogiri is required for XML parsing:
2
- begin
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/client'
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
@@ -1,135 +1,165 @@
1
- module ExactTarget
2
- class Client
1
+ class ExactTarget
3
2
 
4
- public
5
-
6
- def email_find_all(body = false)
7
- email_find({ :body => body })
8
- end
9
-
10
- def email_find_by_id(id, options = {})
11
- email_find({
12
- :id => id,
13
- :body => true
14
- }.merge(options))
15
- end
16
-
17
- def email_find_by_name(name, options = {})
18
- email_find({
19
- :name => name,
20
- }.merge(options))
21
- end
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
- def email_find_by_subject(subject, options = {})
24
- email_find({
25
- :subject => subject,
26
- }.merge(options))
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
- def email_find(options = {})
30
- @action = 'retrieve'
31
- @sub_action = 'all'
32
- @type = ''
33
- @value = ''
34
-
35
- id = options[:id] || false
36
- name = options[:name] || false
37
- subject = options[:subject] || false
38
- start_date = options[:start] || false
39
- end_date = options[:end] || false
40
- get_body = options[:body] || false
41
- list = []
42
-
43
- format = Proc.new { |date|
44
- begin
45
- date if date.instance_of? Date
46
- Date.strptime(date, '%m/%d/%Y')
47
- rescue
48
- raise '[ExactTarget] Error: Invalid date.'
49
- end
50
- }
51
-
52
- Nokogiri::Slop(send(render(:email)))
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
- (next if date < format.call(start_date)) if start_date
64
- (next if date > format.call(end_date)) if end_date
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
- def email_create(name, subject, html, text = false)
86
- id = email_add_html(name, subject, html)
87
- email_add_text(id, text) if text
88
- id
89
- end
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
- private
131
+ result = Nokogiri::XML(send(render(:email)))
132
+ id = result.xpath('//emailID').text
92
133
 
93
- def email_add_html(name, subject, html)
94
- @action = 'add'
95
- @sub_action = 'HTMLPaste'
96
- @name = name.to_s
97
- @subject = subject.to_s
98
- @body = html
99
-
100
- result = Nokogiri::XML(send(render(:email)))
101
- id = result.xpath('//emailID').text
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
- def email_add_text(id, text)
108
- @action = 'add'
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
- def email_get_body(id)
121
- @action = 'retrieve'
122
- @sub_action = 'htmlemail'
123
- @type = 'emailid'
124
- @value = id.to_s
125
-
126
- begin
127
- Nokogiri::XML(send(render(:email)))
128
- .xpath('//htmlbody').text
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
@@ -1,31 +1,39 @@
1
- module ExactTarget
2
- class Client
1
+ class ExactTarget
3
2
 
4
- public
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
- def image_import(file_path)
7
- @name = File.basename(file_path)
8
-
9
- begin
10
- put(file_path.to_s)
11
- result = Nokogiri::XML(send(render(:image)))
12
- delete(@name)
13
- rescue Exception => msg
14
- puts msg
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,7 @@
1
+ <system>
2
+
3
+ <system_name>list</system_name>
4
+ <action>retrievegroups</action>
5
+ <search_type>groups</search_type>
6
+
7
+ </system>
@@ -3,7 +3,7 @@
3
3
  <system_name>filemanagement</system_name>
4
4
  <action>filemove</action>
5
5
  <source>
6
- <location><%= @ftp[:location] %></location>
6
+ <location><%= @config[:ftp_name] %></location>
7
7
  <files>
8
8
  <filename><%= @name %></filename>
9
9
  </files>
@@ -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>
@@ -0,0 +1,11 @@
1
+ <system>
2
+
3
+ <system_name>list</system_name>
4
+ <action><%= @action %></action>
5
+
6
+ <% if @action == 'retrieve' %>
7
+ <search_type><%= @type %></search_type>
8
+ <search_value><%= @value %></search_value>
9
+ <% end %>
10
+
11
+ </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
- - 2
9
- version: 0.1.2
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-08 00:00:00 -04:00
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
- - 0
29
- version: "0"
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
- version: "0"
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: 1328632892447175540
154
+ hash: 1094960179515826332
131
155
  segments:
132
156
  - 0
133
157
  version: "0"
@@ -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