exact-target 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,8 @@
1
+ class Net::HTTP
2
+ alias_method :old_initialize, :initialize
3
+ def initialize(*args)
4
+ old_initialize(*args)
5
+ @ssl_context = OpenSSL::SSL::SSLContext.new
6
+ @ssl_context.verify_mode = OpenSSL::SSL::VERIFY_NONE
7
+ end
8
+ end
@@ -0,0 +1,284 @@
1
+ require 'builder'
2
+
3
+ module ExactTarget
4
+ class RequestBuilder
5
+
6
+ def initialize(config)
7
+ @config = config
8
+ end
9
+
10
+ def accountinfo_retrieve_attrbs
11
+ build(:accountinfo, :retrieve_attrbs)
12
+ end
13
+
14
+ def list_add(list_name, list_type=nil)
15
+ list_type = :public unless %w(public private salesforce).include?(list_type.to_s)
16
+ build(:list, :add) do |li|
17
+ li.list_type list_type.to_s
18
+ li.list_name list_name.to_s
19
+ end
20
+ end
21
+
22
+ def list_edit(list_id, new_list_name)
23
+ build(:list, :edit, :listid, list_id) do |li|
24
+ li.list_name new_list_name.to_s
25
+ end
26
+ end
27
+
28
+ def list_retrieve(id_or_name=nil)
29
+ if id_or_name.is_a?(Fixnum) or id_or_name =~ /^\d+$/
30
+ build(:list, :retrieve, :listid, id_or_name.to_i)
31
+ else
32
+ build(:list, :retrieve, :listname, id_or_name)
33
+ end
34
+ end
35
+
36
+ def list_import(list_id, file_name, file_mapping, options={})
37
+ options = list_import_default_options(options, file_name)
38
+ build(:list, :import, :listid, list_id) do |li|
39
+ li.tags_from_options! options, :file_name, :email_address, :file_type, :column_headings
40
+ li.file_mapping do |fm|
41
+ file_mapping.each { |m| fm.field(m) }
42
+ end
43
+ li.tags_from_options! options, :import_type, :returnid, :encrypted, :encrypt_format
44
+ end
45
+ end
46
+
47
+ def list_importstatus(import_id)
48
+ build(
49
+ :list, :import,
50
+ :sub_action => :importstatus,
51
+ :search_type => :omit,
52
+ :search_value => import_id
53
+ )
54
+ end
55
+
56
+ def list_retrieve_sub(list_id, status=nil)
57
+ unless status.nil? or %w(Active Unsubscribed Returned Undeliverable Deleted).include?(status)
58
+ raise "Invalid status: #{status}"
59
+ end
60
+ build(:list, :retrieve_sub, :listid, list_id) do |li|
61
+ li.search_status status unless status.nil?
62
+ end
63
+ end
64
+
65
+ def list_delete(id)
66
+ build(:list, :delete, :listid, id)
67
+ end
68
+
69
+ def list_retrievegroups
70
+ build(:list, :retrievegroups, :groups)
71
+ end
72
+
73
+ def list_refresh_group(group_id)
74
+ build(
75
+ :list, :refresh_group,
76
+ :sub_action => nil,
77
+ :search_type => :omit,
78
+ :search_value => group_id,
79
+ :search_value2 => nil
80
+ )
81
+ end
82
+
83
+ def batch_inquire(batch_id)
84
+ build(
85
+ :batch, :inquire, :batchid, batch_id,
86
+ :sub_action => nil,
87
+ :search_value2 => nil
88
+ )
89
+ end
90
+
91
+ ###################################################################
92
+
93
+ def subscriber_add(list_id, subscriber, options={})
94
+ subscriber_edit(list_id, nil, subscriber, options)
95
+ end
96
+
97
+ def subscriber_edit(list_id, orig_email, subscriber, options={})
98
+ options = subscriber_edit_default_options(options)
99
+ subscriber = subscriber.to_et_hash if subscriber.is_a?(Subscriber)
100
+ action = orig_email.nil? ? :add : :edit
101
+ build(:subscriber, action, :listid, list_id, :search_value2 => orig_email) do |sub|
102
+ sub.values do |vs|
103
+ subscriber.each do |k, v|
104
+ vs.tag!(k.to_s, v) unless k.to_s =~ /status/i
105
+ end
106
+ vs.status options[:status].to_s
107
+ vs.reason options[:reason] if options.has_key? :reason
108
+ vs.ChannelMemberID options[:ChannelMemberID] if options.has_key? :ChannelMemberID
109
+ end
110
+ sub.update options[:update] if orig_email.nil?
111
+ end
112
+ end
113
+
114
+ def subscriber_retrieve(id, email=nil)
115
+ type = email.blank? ? :subid : :listid
116
+ build(:subscriber, :retrieve, type, id, :search_value2 => email)
117
+ end
118
+
119
+ def subscriber_delete(id, email=nil)
120
+ type = email.blank? ? :subid : :listid
121
+ build(:subscriber, :delete, type, id, :search_value2 => email)
122
+ end
123
+
124
+ def subscriber_masterunsub(*email_addresses)
125
+ build(:subscriber, :masterunsub, :emailaddress, :search_value => :omit) do |sub|
126
+ sub.search_value do |sv|
127
+ email_addresses.flatten.each { |a| sv.emailaddress(a) }
128
+ end
129
+ end
130
+ end
131
+
132
+ ###################################################################
133
+
134
+ def email_retrieve(name=nil, options={})
135
+ name, options = nil, name if name.is_a?(Hash)
136
+ start_date, end_date = %w(start_date end_date).map do |n|
137
+ et_date options[n.to_sym]
138
+ end
139
+ type = unless start_date.nil? and end_date.nil?
140
+ name.nil? ? :daterange : :emailnameanddaterange
141
+ else
142
+ name.nil? ? nil : :emailname
143
+ end
144
+ build(:email, :retrieve, type, name, :sub_action => :all, :search_value2 => nil) do |em|
145
+ em.daterange do |r|
146
+ r.startdate start_date if start_date
147
+ r.enddate end_date if end_date
148
+ end
149
+ end
150
+ end
151
+
152
+ def email_add(name, subject, options)
153
+ build(:email, :add, :search_type => :omit, :search_value => :omit, :sub_action => 'HTMLPaste') do |em|
154
+ em.category
155
+ em.email_name name
156
+ em.email_subject subject
157
+ if options.has_key? :body
158
+ em.email_body { |eb| eb.cdata! options[:body] }
159
+ elsif options.has_key? :file
160
+ em.file_name options[:file]
161
+ end
162
+ end
163
+ end
164
+
165
+ def email_add_text(email_id, options)
166
+ build(:email, :add, :search_type => :emailid,
167
+ :search_value => email_id, :sub_action => :text) do |em|
168
+ if options.has_key? :body
169
+ em.email_body { |eb| eb.cdata! options[:body] }
170
+ elsif options.has_key? :file
171
+ em.file_name options[:file]
172
+ end
173
+ end
174
+ end
175
+
176
+ def email_retrieve_body(email_id)
177
+ build(:email, :retrieve, :emailid, email_id, :sub_action => :htmlemail) do |em|
178
+ em.search_value2
179
+ em.search_value3
180
+ end
181
+ end
182
+
183
+ ###################################################################
184
+
185
+ def job_send(email_id, list_ids, options={})
186
+ options = job_send_default_options(options)
187
+
188
+ build(:job, :send, :emailid, email_id) do |job|
189
+ job.tags_from_options! options, :from_name, :from_email, :additional,
190
+ :multipart_mime, :track_links,
191
+ :send_date, :send_time
192
+ job_send_id_list job, :lists, list_ids
193
+ job_send_id_list job, :suppress, options[:suppress_ids]
194
+ job.test_send options[:test_send]
195
+ end
196
+ end
197
+
198
+ ###################################################################
199
+ private
200
+ ###################################################################
201
+
202
+ def list_import_default_options(options, file_name)
203
+ options = options.dup
204
+ options[:file_name] = file_name
205
+ options[:file_type] ||= (file_name =~ /\.txt$/i ? 'tab' : 'csv')
206
+ options[:column_headings] ||= true
207
+ options[:import_type] ||= 0
208
+ options[:returnid] ||= true
209
+ options[:encrypted] ||= false
210
+ options
211
+ end
212
+
213
+ def subscriber_edit_default_options(options)
214
+ options = options.dup
215
+ options[:status] = :active unless options[:status].to_s == 'unsub'
216
+ options[:update] = true unless options[:update] == false
217
+ options
218
+ end
219
+
220
+ def job_send_default_options(options)
221
+ options = options.nil? ? {} : options.dup # Don't munge hash passed in
222
+ options[:multipart_mime] = false unless options.has_key? :multipart_mime
223
+ options[:track_links] = true unless options.has_key? :track_links
224
+ options[:test_send] = false unless options.has_key? :test_send
225
+ options[:send_date] ||= :immediate
226
+ options[:suppress_ids] ||= []
227
+ options
228
+ end
229
+
230
+ def job_send_id_list(job, tag, ids)
231
+ job.tag!(tag.to_s) do |li|
232
+ [ids].flatten.compact.each { |id| li.list id }
233
+ end
234
+ end
235
+
236
+ def et_date(d)
237
+ d = Date.parse(d) if d.is_a?(String)
238
+ [d.month, d.day, d.year] * '/' if d
239
+ end
240
+
241
+ def build(system_name, action, search_type=nil, search_value=nil, options=nil, &block)
242
+ options = parse_options(search_type, search_value, options)
243
+ xml = Builder::XmlMarkup.new
244
+ xml.instruct! :xml, :version => "1.0", :encoding => nil
245
+ xml = xml.exacttarget do |et|
246
+ et.authorization do |a|
247
+ a.username @config.username
248
+ a.password @config.password
249
+ end
250
+ et.system do |s|
251
+ build_system(s, system_name, action, options, &block)
252
+ end
253
+ end
254
+ xml.to_s
255
+ end
256
+
257
+ def parse_options(search_type, search_value, options)
258
+ options, search_type = search_type, nil if search_type.is_a?(Hash)
259
+ options, search_value = search_value, nil if search_value.is_a?(Hash)
260
+ options ||= {}
261
+ { :search_type => search_type, :search_value => search_value }.merge(options)
262
+ end
263
+
264
+ def build_system(s, system_name, action, options, &block)
265
+ s.system_name system_name.to_s
266
+ s.action action.to_s
267
+ [:sub_action, :search_type, :search_value, :search_value2].each do |name|
268
+ build_system_option(s, name, options[name]) if options.has_key? name
269
+ end
270
+ yield(s) if block_given?
271
+ end
272
+
273
+ def build_system_option(s, name, value)
274
+ if name == :search_value and value.is_a?(Array)
275
+ s.search_values do |ss|
276
+ value.each { |v| ss.search_value v }
277
+ end
278
+ elsif value != :omit
279
+ s.tag! name.to_s, value.to_s
280
+ end
281
+ end
282
+
283
+ end
284
+ end
@@ -0,0 +1,30 @@
1
+ class <%= name %>
2
+
3
+ <% attributes.each do |rb_a, et_a| %>
4
+ attr_accessor :<%= rb_a %>
5
+
6
+ <% if rb_a != et_a %>
7
+ alias :<%= et_a %> :<%= rb_a %>
8
+ alias :<%= et_a %>= :<%= rb_a %>=
9
+ <% end %>
10
+ <% end %>
11
+
12
+ def attributes
13
+ [<%= attributes.map { |rb_a, et_a| ":#{rb_a}" } * ', ' %>]
14
+ end
15
+
16
+ def et_attributes
17
+ [<%= attributes.map { |rb_a, et_a| ":#{et_a}" } * ', ' %>]
18
+ end
19
+
20
+ def to_hash
21
+ attributes.map { |a| [a, send(a)] }
22
+ end
23
+
24
+ def to_et_hash
25
+ et_attributes.map { |a| [a, send(a)] }
26
+ end
27
+
28
+ alias :to_s :<%= attributes.first.first %>
29
+
30
+ end
@@ -0,0 +1,61 @@
1
+ require 'erb'
2
+
3
+ module ExactTarget
4
+ #
5
+ # Response classes for ExactTarget. We'll use these rather
6
+ # than just stuff it all in a hash as it allows us to more
7
+ # easily map between ugly ET names (e.g. GroupID) to ruby-friendly
8
+ # names (e.g. group_id).
9
+ #
10
+ module ResponseClasses
11
+ class << self
12
+
13
+ def extended(base)
14
+ class_from_et_attributes base, :ListProfileAttribute,
15
+ :name, :description, :default_value, :data_type, :required,
16
+ :min_size, :max_size, :subscriber_editable, :display, :values
17
+
18
+ class_from_et_attributes base, :ListInformation,
19
+ :list_name, :list_type, :modified, :subscriber_count,
20
+ :active_total, :held_count, :bounce_count, :unsub_count
21
+
22
+ class_from_et_attributes base, :ListGroupInformation,
23
+ :groupName, :groupID, :parentlistID, :description
24
+
25
+ class_from_et_attributes base, :SubscriberInformation,
26
+ :subid, :listid, :list_name, :subscriber
27
+
28
+ class_from_et_attributes base, :EmailInformation,
29
+ :emailname, :emailid, :emailsubject, :emailcreateddate, :categoryid
30
+
31
+ def base.subscriber_class
32
+ @subscriber_class ||= ResponseClasses.class_from_et_attributes(
33
+ self, :Subscriber, accountinfo_retrieve_attrbs.map(&:name)
34
+ )
35
+ end
36
+
37
+ def base.const_missing(name)
38
+ if name.to_sym == :Subscriber
39
+ subscriber_class
40
+ else
41
+ super
42
+ end
43
+ end
44
+ end
45
+
46
+ def class_from_et_attributes(base, name, *attribute_names)
47
+ attributes = attribute_names.flatten.map do |a|
48
+ [a.to_s.underscore.gsub(' ', '_'), a.to_s.gsub(' ', '__')]
49
+ end
50
+ class_def = class_template.result(binding)
51
+ base.module_eval(class_def)
52
+ base.const_get(name)
53
+ end
54
+
55
+ def class_template
56
+ @class_template ||= ERB.new File.read(File.expand_path '../response_class.erb', __FILE__)
57
+ end
58
+
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,167 @@
1
+ require 'builder'
2
+
3
+ module ExactTarget
4
+ #
5
+ # This class is responsible for turning parsed ExactTarget response xml
6
+ # into data that applications can use
7
+ #
8
+ class ResponseHandler
9
+
10
+ def initialize(config)
11
+ @config = config
12
+ end
13
+
14
+ def handle_import_id_result(resp)
15
+ handle_id_result resp, :import_info, :importid, /is being imported/i
16
+ end
17
+
18
+ %w(email job list subscriber).each do |t|
19
+ define_method "handle_#{t}_id_result", lambda { |resp|
20
+ handle_id_result resp, "#{t}_info", "#{t}_description", /success/i
21
+ }
22
+ end
23
+
24
+ def accountinfo_retrieve_attrbs(resp)
25
+ resp.xpath('//attribute').map do |a|
26
+ create_result(ListProfileAttribute, a) do |child|
27
+ child.children.map { |v| cast_value(v.text) } if child.name == 'values'
28
+ end
29
+ end
30
+ end
31
+
32
+ alias :list_add :handle_list_id_result
33
+
34
+ alias :list_edit :handle_list_id_result
35
+
36
+ def list_retrieve(resp)
37
+ if resp.xpath('//list/list_name').size == 1
38
+ resp.xpath('//list[1]').map do |list|
39
+ create_result(ListInformation, list)
40
+ end.first
41
+ else
42
+ resp.xpath('//listid').map do |id|
43
+ id.text.to_i
44
+ end
45
+ end
46
+ end
47
+
48
+ alias :list_import :handle_import_id_result
49
+
50
+ def list_importstatus(resp)
51
+ resp.xpath('//import_info[1]').first.text
52
+ end
53
+
54
+ def list_retrieve_sub(resp)
55
+ resp.xpath('//subscriber').map do |s|
56
+ return [] if s.text =~ /no subscribers found/i
57
+ create_result(Subscriber, s)
58
+ end
59
+ end
60
+
61
+ alias :list_delete :handle_list_id_result
62
+
63
+ def list_retrievegroups(resp)
64
+ resp.xpath('//group').map do |group|
65
+ create_result(ListGroupInformation, group)
66
+ end
67
+ end
68
+
69
+ def list_refresh_group(resp)
70
+ resp.xpath('//groupRefresh/groupAsyncID[1]').first.text.to_i
71
+ end
72
+
73
+ def batch_inquire(resp)
74
+ resp.xpath('//Batch/status[1]').first.text
75
+ end
76
+
77
+ ###################################################################
78
+
79
+ alias :subscriber_add :handle_subscriber_id_result
80
+
81
+ alias :subscriber_edit :handle_subscriber_id_result
82
+
83
+ def subscriber_retrieve(resp)
84
+ resp.xpath('//subscriber').map do |s|
85
+ return [] if s.text =~ /no subscribers found/i
86
+ sri = create_result(SubscriberInformation, s)
87
+ sri.subscriber = create_result(Subscriber, s)
88
+ sri
89
+ end
90
+ end
91
+
92
+ alias :subscriber_delete :handle_subscriber_id_result
93
+
94
+ def subscriber_masterunsub(resp)
95
+ resp = resp.xpath('//subscriberunsub').map do |su|
96
+ %w(emailaddress status).map { |p| su.xpath(".//#{p}[1]").first.text }
97
+ end
98
+ Hash[resp]
99
+ end
100
+
101
+ ###################################################################
102
+
103
+ def email_retrieve(resp)
104
+ resp.xpath('//emaillist').map do |el|
105
+ create_result(EmailInformation, el)
106
+ end
107
+ end
108
+
109
+ def email_add(resp)
110
+ resp.xpath('//emailID[1]').first.text.to_i
111
+ end
112
+
113
+ alias :email_add_text :handle_email_id_result
114
+
115
+ def email_retrieve_body(resp)
116
+ resp.xpath('//htmlbody[1]').first.text
117
+ end
118
+
119
+ ###################################################################
120
+
121
+ alias :job_send :handle_job_id_result
122
+
123
+ ###################################################################
124
+ private
125
+ ###################################################################
126
+
127
+ def handle_id_result(resp, info_tag, id_tag, success_regex)
128
+ info = resp.xpath("//#{info_tag}[1]").first
129
+ if !info.nil? and info.text =~ success_regex
130
+ id = resp.xpath("//#{id_tag}[1]").first
131
+ id.nil? ? true : cast_value(id.text)
132
+ else
133
+ raise Error.new(0, "Unsupported id result: #{resp.to_s}")
134
+ end
135
+ end
136
+
137
+ def create_result(clazz, node, &block)
138
+ ret = clazz.new
139
+ node.children.each do |child|
140
+ if ret.respond_to? "#{child.name}="
141
+ val = yield(child) if block_given?
142
+ val = cast_value(child.text) if val.nil?
143
+ ret.send "#{child.name}=", val
144
+ end
145
+ end
146
+ ret
147
+ end
148
+
149
+ def cast_value(v)
150
+ case v
151
+ when /^[+-]?\d+$/
152
+ v.to_i
153
+ when /^([+-]?)(?=\dâ\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/
154
+ v.to_f
155
+ when /^true$/i
156
+ true
157
+ when /^false$/i
158
+ false
159
+ when %r{^\d+/\d+/\d+ \d+:\d+:\d+ [AP]M$}i, %r{^\d+/\d+/\d+$}i
160
+ DateTime.parse(v)
161
+ else
162
+ v.strip
163
+ end
164
+ end
165
+
166
+ end
167
+ end