exacttarget 0.1.2 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/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