marketingcloudsdk 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +26 -0
  3. data/Gemfile +3 -0
  4. data/Gemfile.lock +92 -0
  5. data/Guardfile +8 -0
  6. data/LICENSE.md +13 -0
  7. data/README.md +130 -0
  8. data/Rakefile +1 -0
  9. data/lib/marketingcloudsdk.rb +74 -0
  10. data/lib/marketingcloudsdk/client.rb +283 -0
  11. data/lib/marketingcloudsdk/http_request.rb +113 -0
  12. data/lib/marketingcloudsdk/objects.rb +757 -0
  13. data/lib/marketingcloudsdk/rest.rb +122 -0
  14. data/lib/marketingcloudsdk/soap.rb +288 -0
  15. data/lib/marketingcloudsdk/targeting.rb +58 -0
  16. data/lib/marketingcloudsdk/utils.rb +47 -0
  17. data/lib/marketingcloudsdk/version.rb +39 -0
  18. data/lib/new.rb +1240 -0
  19. data/marketingcloudsdk.gemspec +30 -0
  20. data/samples/sample-AddSubscriberToList.rb +56 -0
  21. data/samples/sample-CreateAndStartDataExtensionImport.rb +29 -0
  22. data/samples/sample-CreateAndStartListImport.rb +27 -0
  23. data/samples/sample-CreateContentAreas.rb +48 -0
  24. data/samples/sample-CreateDataExtensions.rb +54 -0
  25. data/samples/sample-CreateProfileAttributes.rb +48 -0
  26. data/samples/sample-SendEmailToDataExtension.rb +23 -0
  27. data/samples/sample-SendEmailToList.rb +23 -0
  28. data/samples/sample-SendTriggeredSends.rb +30 -0
  29. data/samples/sample-bounceevent.rb +70 -0
  30. data/samples/sample-campaign.rb +211 -0
  31. data/samples/sample-clickevent.rb +71 -0
  32. data/samples/sample-contentarea.rb +122 -0
  33. data/samples/sample-dataextension.rb +209 -0
  34. data/samples/sample-directverb.rb +55 -0
  35. data/samples/sample-email.rb +122 -0
  36. data/samples/sample-email.senddefinition.rb +134 -0
  37. data/samples/sample-folder.rb +143 -0
  38. data/samples/sample-import.rb +104 -0
  39. data/samples/sample-list.rb +105 -0
  40. data/samples/sample-list.subscriber.rb +97 -0
  41. data/samples/sample-openevent.rb +70 -0
  42. data/samples/sample-profileattribute.rb +57 -0
  43. data/samples/sample-sentevent.rb +70 -0
  44. data/samples/sample-subscriber.rb +136 -0
  45. data/samples/sample-triggeredsend.rb +130 -0
  46. data/samples/sample-unsubevent.rb +72 -0
  47. data/samples/sample_helper.rb.template +8 -0
  48. data/spec/client_spec.rb +210 -0
  49. data/spec/helper_funcs_spec.rb +11 -0
  50. data/spec/http_request_spec.rb +36 -0
  51. data/spec/objects_helper_spec.rb +32 -0
  52. data/spec/objects_spec.rb +484 -0
  53. data/spec/rest_spec.rb +48 -0
  54. data/spec/soap_spec.rb +140 -0
  55. data/spec/spec_helper.rb +14 -0
  56. data/spec/targeting_spec.rb +39 -0
  57. metadata +260 -0
@@ -0,0 +1,47 @@
1
+ =begin
2
+ Copyright (c) 2013 ExactTarget, Inc.
3
+
4
+ All rights reserved.
5
+
6
+ Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
7
+
8
+ following conditions are met:
9
+
10
+ 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the
11
+
12
+ following disclaimer.
13
+
14
+ 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
15
+
16
+ following disclaimer in the documentation and/or other materials provided with the distribution.
17
+
18
+ 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote
19
+
20
+ products derived from this software without specific prior written permission.
21
+
22
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
23
+
24
+ INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
25
+
26
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
27
+
28
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
29
+
30
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
31
+
32
+ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
33
+
34
+ USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
35
+ =end
36
+
37
+ module MarketingCloudSDK
38
+ module_function
39
+ def format_name_value_pairs attributes
40
+ attrs = []
41
+ attributes.each do |name, value|
42
+ attrs.push 'Name' => name, 'Value' => value
43
+ end
44
+
45
+ attrs
46
+ end
47
+ end
@@ -0,0 +1,39 @@
1
+ =begin
2
+ Copyright (c) 2013 ExactTarget, Inc.
3
+
4
+ All rights reserved.
5
+
6
+ Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
7
+
8
+ following conditions are met:
9
+
10
+ 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the
11
+
12
+ following disclaimer.
13
+
14
+ 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
15
+
16
+ following disclaimer in the documentation and/or other materials provided with the distribution.
17
+
18
+ 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote
19
+
20
+ products derived from this software without specific prior written permission.
21
+
22
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
23
+
24
+ INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
25
+
26
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
27
+
28
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
29
+
30
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
31
+
32
+ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
33
+
34
+ USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
35
+ =end
36
+
37
+ module MarketingCloudSDK
38
+ VERSION = "1.0.0"
39
+ end
@@ -0,0 +1,1240 @@
1
+ =begin
2
+ Copyright (c) 2013 ExactTarget, Inc.
3
+
4
+ All rights reserved.
5
+
6
+ Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
7
+
8
+ following conditions are met:
9
+
10
+ 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the
11
+
12
+ following disclaimer.
13
+
14
+ 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
15
+
16
+ following disclaimer in the documentation and/or other materials provided with the distribution.
17
+
18
+ 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote
19
+
20
+ products derived from this software without specific prior written permission.
21
+
22
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
23
+
24
+ INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
25
+
26
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
27
+
28
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
29
+
30
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
31
+
32
+ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
33
+
34
+ USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
35
+ =end
36
+
37
+ require "marketingcloudsdk/version"
38
+
39
+ require 'rubygems'
40
+ require 'open-uri'
41
+ require 'savon'
42
+ require 'date'
43
+ require 'json'
44
+ require 'yaml'
45
+ require 'jwt'
46
+
47
+
48
+ def indifferent_access key, hash
49
+ hash[key.to_sym] || hash[key.to_s]
50
+ end
51
+
52
+ module MarketingCloudSDK
53
+
54
+ class Soap
55
+ def client
56
+ 'soap'
57
+ end
58
+ end
59
+
60
+ class Rest
61
+ def client
62
+ 'rest'
63
+ end
64
+ end
65
+
66
+ class Client
67
+ attr_reader :id, :secret, :signature
68
+
69
+ def initialize params={}, debug=false
70
+ @debug = debug
71
+ @id = indifferent_access :clientid, params
72
+ @secret = indifferent_access :clientsecret, params
73
+ @signature = indifferent_access :appsignature, params
74
+ end
75
+ end
76
+
77
+ class SoapClient < Client
78
+ def initialize getWSDL=true, params={}, debug=false
79
+ super params, debug
80
+ @wsdl = params["defaultwsdl"]
81
+ end
82
+ end
83
+
84
+ class RestClient < Client
85
+ end
86
+
87
+
88
+ # parse response
89
+ class ET_Constructor
90
+ attr_accessor :status, :code, :message, :results, :request_id, :moreResults
91
+
92
+ def initialize(response = nil, rest = false)
93
+ @results = []
94
+ if !response.nil? && !rest then
95
+ envelope = response.hash[:envelope]
96
+ @@body = envelope[:body]
97
+
98
+ if ((!response.soap_fault?) or (!response.http_error?)) then
99
+ @code = response.http.code
100
+ @status = true
101
+ elsif (response.soap_fault?) then
102
+ @code = response.http.code
103
+ @message = @@body[:fault][:faultstring]
104
+ @status = false
105
+ elsif (response.http_error?) then
106
+ @code = response.http.code
107
+ @status = false
108
+ end
109
+ elsif
110
+ @code = response.code
111
+ @status = true
112
+ if @code != "200" then
113
+ @status = false
114
+ end
115
+
116
+ begin
117
+ @results = JSON.parse(response.body)
118
+ rescue
119
+ @message = response.body
120
+ end
121
+
122
+ end
123
+ end
124
+ end
125
+
126
+ class ET_CreateWSDL
127
+
128
+ def initialize(path)
129
+ # Get the header info for the correct wsdl
130
+ response = HTTPI.head(@wsdl)
131
+ if response and (response.code >= 200 and response.code <= 400) then
132
+ header = response.headers
133
+ # Check when the WSDL was last modified
134
+ modifiedTime = Date.parse(header['last-modified'])
135
+ p = path + '/ExactTargetWSDL.xml'
136
+ # Check if a local file already exists
137
+ if (File.file?(p) and File.readable?(p) and !File.zero?(p)) then
138
+ createdTime = File.new(p).mtime.to_date
139
+
140
+ # Check if the locally created WSDL older than the production WSDL
141
+ if createdTime < modifiedTime then
142
+ createIt = true
143
+ else
144
+ createIt = false
145
+ end
146
+ else
147
+ createIt = true
148
+ end
149
+
150
+ if createIt then
151
+ res = open(@wsdl).read
152
+ File.open(p, 'w+') { |f|
153
+ f.write(res)
154
+ }
155
+ end
156
+ @status = response.code
157
+ else
158
+ @status = response.code
159
+ end
160
+ end
161
+ end
162
+
163
+ class ET_Client < ET_CreateWSDL
164
+ attr_accessor :auth, :ready, :status, :debug, :authToken
165
+ attr_reader :authTokenExpiration,:internalAuthToken, :wsdlLoc, :clientId,
166
+ :clientSecret, :soapHeader, :authObj, :path, :appsignature, :stackID, :refreshKey
167
+
168
+ def initialize(getWSDL = true, debug = false, params = nil)
169
+ config = YAML.load_file("config.yaml")
170
+ @clientId = config["clientid"]
171
+ @clientSecret = config["clientsecret"]
172
+ @appsignature = config["appsignature"]
173
+ @wsdl = config["defaultwsdl"]
174
+ @debug = debug
175
+
176
+ begin
177
+ @path = File.dirname(__FILE__)
178
+
179
+ #make a new WSDL
180
+ if getWSDL then
181
+ super(@path)
182
+ end
183
+
184
+ if params && params.has_key?("jwt") then
185
+ jwt = JWT.decode(params["jwt"], @appsignature, true);
186
+ @authToken = jwt['request']['user']['oauthToken']
187
+ @authTokenExpiration = Time.new + jwt['request']['user']['expiresIn']
188
+ @internalAuthToken = jwt['request']['user']['internalOauthToken']
189
+ @refreshKey = jwt['request']['user']['refreshToken']
190
+
191
+ self.determineStack
192
+
193
+ @authObj = {'oAuth' => {'oAuthToken' => @internalAuthToken}}
194
+ @authObj[:attributes!] = { 'oAuth' => { 'xmlns' => 'http://exacttarget.com' }}
195
+
196
+ myWSDL = File.read(@path + '/ExactTargetWSDL.xml')
197
+ @auth = Savon.client(
198
+ soap_header: @authObj,
199
+ wsdl: myWSDL,
200
+ endpoint: @endpoint,
201
+ wsse_auth: ["*", "*"],
202
+ raise_errors: false,
203
+ log: @debug,
204
+ open_timeout:180,
205
+ read_timeout: 180
206
+ )
207
+ else
208
+ self.refreshToken
209
+ end
210
+
211
+ rescue
212
+ raise
213
+ end
214
+
215
+ if ((@auth.operations.length > 0) and (@status >= 200 and @status <= 400)) then
216
+ @ready = true
217
+ else
218
+ @ready = false
219
+ end
220
+ end
221
+
222
+ def debug=(value)
223
+ @debug = value
224
+ end
225
+
226
+ def refreshToken(force = nil)
227
+ #If we don't already have a token or the token expires within 5 min(300 seconds), get one
228
+ if ((@authToken.nil? || Time.new + 300 > @authTokenExpiration) || force) then
229
+ begin
230
+ uri = URI.parse("https://auth.exacttargetapis.com/v1/requestToken?legacy=1")
231
+ http = Net::HTTP.new(uri.host, uri.port)
232
+ http.use_ssl = true
233
+ request = Net::HTTP::Post.new(uri.request_uri)
234
+ jsonPayload = {'clientId' => @clientId, 'clientSecret' => @clientSecret}
235
+
236
+ #Pass in the refreshKey if we have it
237
+ if @refreshKey then
238
+ jsonPayload['refreshToken'] = @refreshKey
239
+ end
240
+
241
+ request.body = jsonPayload.to_json
242
+ request.add_field "Content-Type", "application/json"
243
+ tokenResponse = JSON.parse(http.request(request).body)
244
+
245
+ if !tokenResponse.has_key?('accessToken') then
246
+ raise 'Unable to validate App Keys(ClientID/ClientSecret) provided: ' + http.request(request).body
247
+ end
248
+
249
+ @authToken = tokenResponse['accessToken']
250
+ @authTokenExpiration = Time.new + tokenResponse['expiresIn']
251
+ @internalAuthToken = tokenResponse['legacyToken']
252
+ if tokenResponse.has_key?("refreshToken") then
253
+ @refreshKey = tokenResponse['refreshToken']
254
+ end
255
+
256
+ if @endpoint.nil? then
257
+ self.determineStack
258
+ end
259
+
260
+ @authObj = {'oAuth' => {'oAuthToken' => @internalAuthToken}}
261
+ @authObj[:attributes!] = { 'oAuth' => { 'xmlns' => 'http://exacttarget.com' }}
262
+
263
+ myWSDL = File.read(@path + '/ExactTargetWSDL.xml')
264
+ @auth = Savon.client(
265
+ soap_header: @authObj,
266
+ wsdl: myWSDL,
267
+ endpoint: @endpoint,
268
+ wsse_auth: ["*", "*"],
269
+ raise_errors: false,
270
+ log: @debug
271
+ )
272
+
273
+ rescue Exception => e
274
+ raise 'Unable to validate App Keys(ClientID/ClientSecret) provided: ' + e.message
275
+ end
276
+ end
277
+ end
278
+
279
+ def AddSubscriberToList(emailAddress, listIDs, subscriberKey = nil)
280
+ newSub = ET_Subscriber.new
281
+ newSub.authStub = self
282
+ lists = []
283
+
284
+ listIDs.each{ |p|
285
+ lists.push({"ID"=> p})
286
+ }
287
+
288
+ newSub.props = {"EmailAddress" => emailAddress, "Lists" => lists}
289
+ if !subscriberKey.nil? then
290
+ newSub.props['SubscriberKey'] = subscriberKey;
291
+ end
292
+
293
+ # Try to add the subscriber
294
+ postResponse = newSub.post
295
+
296
+ if postResponse.status == false then
297
+ # If the subscriber already exists in the account then we need to do an update.
298
+ # Update Subscriber On List
299
+ if postResponse.results[0][:error_code] == "12014" then
300
+ patchResponse = newSub.patch
301
+ return patchResponse
302
+ end
303
+ end
304
+ return postResponse
305
+ end
306
+
307
+ def CreateDataExtensions(dataExtensionDefinitions)
308
+ newDEs = ET_DataExtension.new
309
+ newDEs.authStub = self
310
+
311
+ newDEs.props = dataExtensionDefinitions
312
+ postResponse = newDEs.post
313
+
314
+ return postResponse
315
+ end
316
+
317
+
318
+ protected
319
+ def determineStack()
320
+ begin
321
+ uri = URI.parse("https://www.exacttargetapis.com/platform/v1/endpoints/soap?access_token=" + @authToken)
322
+ http = Net::HTTP.new(uri.host, uri.port)
323
+
324
+ http.use_ssl = true
325
+
326
+ request = Net::HTTP::Get.new(uri.request_uri)
327
+
328
+ contextResponse = JSON.parse(http.request(request).body)
329
+ @endpoint = contextResponse['url']
330
+
331
+ rescue StandardError => e
332
+ raise 'Unable to determine stack using /platform/v1/tokenContext: ' + e.message
333
+ end
334
+ end
335
+ end
336
+
337
+ class ET_Describe < ET_Constructor
338
+ def initialize(authStub = nil, objType = nil)
339
+ begin
340
+ authStub.refreshToken
341
+ response = authStub.auth.call(:describe, :message => {
342
+ 'DescribeRequests' =>
343
+ {'ObjectDefinitionRequest' =>
344
+ {'ObjectType' => objType}
345
+ }
346
+ })
347
+ ensure
348
+ super(response)
349
+
350
+ if @status then
351
+ objDef = @@body[:definition_response_msg][:object_definition]
352
+
353
+ if objDef then
354
+ @overallStatus = true
355
+ else
356
+ @overallStatus = false
357
+ end
358
+ @results = @@body[:definition_response_msg][:object_definition][:properties]
359
+ end
360
+ end
361
+ end
362
+ end
363
+
364
+ class ET_Post < ET_Constructor
365
+ def initialize(authStub, objType, props = nil)
366
+ @results = []
367
+
368
+ begin
369
+ authStub.refreshToken
370
+ if props.is_a? Array then
371
+ obj = {
372
+ 'Objects' => [],
373
+ :attributes! => { 'Objects' => { 'xsi:type' => ('tns:' + objType) } }
374
+ }
375
+ props.each{ |p|
376
+ obj['Objects'] << p
377
+ }
378
+ else
379
+ obj = {
380
+ 'Objects' => props,
381
+ :attributes! => { 'Objects' => { 'xsi:type' => ('tns:' + objType) } }
382
+ }
383
+ end
384
+
385
+ response = authStub.auth.call(:create, :message => obj)
386
+
387
+ ensure
388
+ super(response)
389
+ if @status then
390
+ if @@body[:create_response][:overall_status] != "OK"
391
+ @status = false
392
+ end
393
+ #@results = @@body[:create_response][:results]
394
+ if !@@body[:create_response][:results].nil? then
395
+ if !@@body[:create_response][:results].is_a? Hash then
396
+ @results = @results + @@body[:create_response][:results]
397
+ else
398
+ @results.push(@@body[:create_response][:results])
399
+ end
400
+ end
401
+ end
402
+ end
403
+ end
404
+ end
405
+
406
+ class ET_Delete < ET_Constructor
407
+
408
+ def initialize(authStub, objType, props = nil)
409
+ @results = []
410
+ begin
411
+ authStub.refreshToken
412
+ if props.is_a? Array then
413
+ obj = {
414
+ 'Objects' => [],
415
+ :attributes! => { 'Objects' => { 'xsi:type' => ('tns:' + objType) } }
416
+ }
417
+ props.each{ |p|
418
+ obj['Objects'] << p
419
+ }
420
+ else
421
+ obj = {
422
+ 'Objects' => props,
423
+ :attributes! => { 'Objects' => { 'xsi:type' => ('tns:' + objType) } }
424
+ }
425
+ end
426
+
427
+ response = authStub.auth.call(:delete, :message => obj)
428
+ ensure
429
+ super(response)
430
+ if @status then
431
+ if @@body[:delete_response][:overall_status] != "OK"
432
+ @status = false
433
+ end
434
+ if !@@body[:delete_response][:results].is_a? Hash then
435
+ @results = @results + @@body[:delete_response][:results]
436
+ else
437
+ @results.push(@@body[:delete_response][:results])
438
+ end
439
+ end
440
+ end
441
+ end
442
+ end
443
+
444
+ class ET_Patch < ET_Constructor
445
+ def initialize(authStub, objType, props = nil)
446
+ @results = []
447
+ begin
448
+ authStub.refreshToken
449
+ if props.is_a? Array then
450
+ obj = {
451
+ 'Objects' => [],
452
+ :attributes! => { 'Objects' => { 'xsi:type' => ('tns:' + objType) } }
453
+ }
454
+ props.each{ |p|
455
+ obj['Objects'] << p
456
+ }
457
+ else
458
+ obj = {
459
+ 'Objects' => props,
460
+ :attributes! => { 'Objects' => { 'xsi:type' => ('tns:' + objType) } }
461
+ }
462
+ end
463
+
464
+ response = authStub.auth.call(:update, :message => obj)
465
+
466
+ ensure
467
+ super(response)
468
+ if @status then
469
+ if @@body[:update_response][:overall_status] != "OK"
470
+ @status = false
471
+ end
472
+ if !@@body[:update_response][:results].is_a? Hash then
473
+ @results = @results + @@body[:update_response][:results]
474
+ else
475
+ @results.push(@@body[:update_response][:results])
476
+ end
477
+ end
478
+ end
479
+ end
480
+ end
481
+
482
+ class ET_Continue < ET_Constructor
483
+ def initialize(authStub, request_id)
484
+ @results = []
485
+ authStub.refreshToken
486
+ obj = {'ContinueRequest' => request_id}
487
+ response = authStub.auth.call(:retrieve, :message => {'RetrieveRequest' => obj})
488
+
489
+ super(response)
490
+
491
+ if @status then
492
+ if @@body[:retrieve_response_msg][:overall_status] != "OK" && @@body[:retrieve_response_msg][:overall_status] != "MoreDataAvailable" then
493
+ @status = false
494
+ @message = @@body[:retrieve_response_msg][:overall_status]
495
+ end
496
+
497
+ @moreResults = false
498
+ if @@body[:retrieve_response_msg][:overall_status] == "MoreDataAvailable" then
499
+ @moreResults = true
500
+ end
501
+
502
+ if (!@@body[:retrieve_response_msg][:results].is_a? Hash) && (!@@body[:retrieve_response_msg][:results].nil?) then
503
+ @results = @results + @@body[:retrieve_response_msg][:results]
504
+ elsif (!@@body[:retrieve_response_msg][:results].nil?)
505
+ @results.push(@@body[:retrieve_response_msg][:results])
506
+ end
507
+
508
+ # Store the Last Request ID for use with continue
509
+ @request_id = @@body[:retrieve_response_msg][:request_id]
510
+ end
511
+ end
512
+ end
513
+
514
+ class ET_Get < ET_Constructor
515
+ def initialize(authStub, objType, props = nil, filter = nil)
516
+ @results = []
517
+ authStub.refreshToken
518
+ if !props then
519
+ resp = ET_Describe.new(authStub, objType)
520
+ if resp then
521
+ props = []
522
+ resp.results.map { |p|
523
+ if p[:is_retrievable] then
524
+ props << p[:name]
525
+ end
526
+ }
527
+ end
528
+ end
529
+
530
+ # If the properties is a hash, then we just want to use the keys
531
+ if props.is_a? Hash then
532
+ obj = {'ObjectType' => objType,'Properties' => props.keys}
533
+ else
534
+ obj = {'ObjectType' => objType,'Properties' => props}
535
+ end
536
+
537
+ if filter then
538
+ if filter.has_key?('LogicalOperator') then
539
+ obj['Filter'] = filter
540
+ obj[:attributes!] = { 'Filter' => { 'xsi:type' => 'tns:ComplexFilterPart' }}
541
+ obj['Filter'][:attributes!] = { 'LeftOperand' => { 'xsi:type' => 'tns:SimpleFilterPart' }, 'RightOperand' => { 'xsi:type' => 'tns:SimpleFilterPart' }}
542
+ else
543
+ obj['Filter'] = filter
544
+ obj[:attributes!] = { 'Filter' => { 'xsi:type' => 'tns:SimpleFilterPart' } }
545
+ end
546
+ end
547
+
548
+ response = authStub.auth.call(:retrieve, :message => {
549
+ 'RetrieveRequest' => obj
550
+ })
551
+
552
+ super(response)
553
+
554
+ if @status then
555
+ if @@body[:retrieve_response_msg][:overall_status] != "OK" && @@body[:retrieve_response_msg][:overall_status] != "MoreDataAvailable" then
556
+ @status = false
557
+ @message = @@body[:retrieve_response_msg][:overall_status]
558
+ end
559
+
560
+ @moreResults = false
561
+ if @@body[:retrieve_response_msg][:overall_status] == "MoreDataAvailable" then
562
+ @moreResults = true
563
+ end
564
+
565
+ if (!@@body[:retrieve_response_msg][:results].is_a? Hash) && (!@@body[:retrieve_response_msg][:results].nil?) then
566
+ @results = @results + @@body[:retrieve_response_msg][:results]
567
+ elsif (!@@body[:retrieve_response_msg][:results].nil?)
568
+ @results.push(@@body[:retrieve_response_msg][:results])
569
+ end
570
+
571
+ # Store the Last Request ID for use with continue
572
+ @request_id = @@body[:retrieve_response_msg][:request_id]
573
+ end
574
+ end
575
+ end
576
+
577
+ class ET_BaseObject
578
+ attr_accessor :authStub, :props
579
+ attr_reader :obj, :lastRequestID, :endpoint
580
+
581
+ def initialize
582
+ @authStub = nil
583
+ @props = nil
584
+ @filter = nil
585
+ @lastRequestID = nil
586
+ @endpoint = nil
587
+ end
588
+ end
589
+
590
+ class ET_GetSupport < ET_BaseObject
591
+ attr_accessor :filter
592
+
593
+ def get(props = nil, filter = nil)
594
+ if props and props.is_a? Array then
595
+ @props = props
596
+ end
597
+
598
+ if @props and @props.is_a? Hash then
599
+ @props = @props.keys
600
+ end
601
+
602
+ if filter and filter.is_a? Hash then
603
+ @filter = filter
604
+ end
605
+ obj = ET_Get.new(@authStub, @obj, @props, @filter)
606
+
607
+ @lastRequestID = obj.request_id
608
+
609
+ return obj
610
+ end
611
+
612
+ def info()
613
+ ET_Describe.new(@authStub, @obj)
614
+ end
615
+
616
+ def getMoreResults()
617
+ ET_Continue.new(@authStub, @lastRequestID)
618
+ end
619
+ end
620
+
621
+ class ET_CUDSupport < ET_GetSupport
622
+
623
+ def post()
624
+ if props and props.is_a? Hash then
625
+ @props = props
626
+ end
627
+
628
+ if @extProps then
629
+ @extProps.each { |key, value|
630
+ @props[key.capitalize] = value
631
+ }
632
+ end
633
+
634
+ ET_Post.new(@authStub, @obj, @props)
635
+ end
636
+
637
+ def patch()
638
+ if props and props.is_a? Hash then
639
+ @props = props
640
+ end
641
+
642
+ ET_Patch.new(@authStub, @obj, @props)
643
+ end
644
+
645
+ def delete()
646
+ if props and props.is_a? Hash then
647
+ @props = props
648
+ end
649
+
650
+ ET_Delete.new(@authStub, @obj, @props)
651
+ end
652
+ end
653
+
654
+ class ET_GetSupportRest < ET_BaseObject
655
+ attr_reader :urlProps, :urlPropsRequired, :lastPageNumber
656
+
657
+ def get(props = nil)
658
+ if props and props.is_a? Hash then
659
+ @props = props
660
+ end
661
+
662
+ completeURL = @endpoint
663
+ additionalQS = {}
664
+
665
+ if @props and @props.is_a? Hash then
666
+ @props.each do |k,v|
667
+ if @urlProps.include?(k) then
668
+ completeURL.sub!("{#{k}}", v)
669
+ else
670
+ additionalQS[k] = v
671
+ end
672
+ end
673
+ end
674
+
675
+ @urlPropsRequired.each do |value|
676
+ if !@props || !@props.has_key?(value) then
677
+ raise "Unable to process request due to missing required prop: #{value}"
678
+ end
679
+ end
680
+
681
+ @urlProps.each do |value|
682
+ completeURL.sub!("/{#{value}}", "")
683
+ end
684
+
685
+ obj = ET_GetRest.new(@authStub, completeURL,additionalQS)
686
+
687
+ if obj.results.has_key?('page') then
688
+ @lastPageNumber = obj.results['page']
689
+ pageSize = obj.results['pageSize']
690
+ if obj.results.has_key?('count') then
691
+ count = obj.results['count']
692
+ elsif obj.results.has_key?('totalCount') then
693
+ count = obj.results['totalCount']
694
+ end
695
+
696
+ if !count.nil? && count > (@lastPageNumber * pageSize) then
697
+ obj.moreResults = true
698
+ end
699
+ end
700
+ return obj
701
+ end
702
+
703
+ def getMoreResults()
704
+ if props and props.is_a? Hash then
705
+ @props = props
706
+ end
707
+
708
+ originalPageValue = "1"
709
+ removePageFromProps = false
710
+
711
+ if !@props.nil? && @props.has_key?('$page') then
712
+ originalPageValue = @props['page']
713
+ else
714
+ removePageFromProps = true
715
+ end
716
+
717
+ if @props.nil?
718
+ @props = {}
719
+ end
720
+
721
+ @props['$page'] = @lastPageNumber + 1
722
+
723
+ obj = self.get
724
+
725
+ if removePageFromProps then
726
+ @props.delete('$page')
727
+ else
728
+ @props['$page'] = originalPageValue
729
+ end
730
+
731
+ return obj
732
+ end
733
+ end
734
+
735
+ class ET_CUDSupportRest < ET_GetSupportRest
736
+
737
+ def post()
738
+ completeURL = @endpoint
739
+
740
+ if @props and @props.is_a? Hash then
741
+ @props.each do |k,v|
742
+ if @urlProps.include?(k) then
743
+ completeURL.sub!("{#{k}}", v)
744
+ end
745
+ end
746
+ end
747
+
748
+ @urlPropsRequired.each do |value|
749
+ if !@props || !@props.has_key?(value) then
750
+ raise "Unable to process request due to missing required prop: #{value}"
751
+ end
752
+ end
753
+
754
+ # Clean Optional Parameters from Endpoint URL first
755
+ @urlProps.each do |value|
756
+ completeURL.sub!("/{#{value}}", "")
757
+ end
758
+
759
+ ET_PostRest.new(@authStub, completeURL, @props)
760
+ end
761
+
762
+ def patch()
763
+ completeURL = @endpoint
764
+ # All URL Props are required when doing Patch
765
+ @urlProps.each do |value|
766
+ if !@props || !@props.has_key?(value) then
767
+ raise "Unable to process request due to missing required prop: #{value}"
768
+ end
769
+ end
770
+
771
+ if @props and @props.is_a? Hash then
772
+ @props.each do |k,v|
773
+ if @urlProps.include?(k) then
774
+ completeURL.sub!("{#{k}}", v)
775
+ end
776
+ end
777
+ end
778
+
779
+ obj = ET_PatchRest.new(@authStub, completeURL, @props)
780
+ end
781
+
782
+ def delete()
783
+ completeURL = @endpoint
784
+ # All URL Props are required when doing Patch
785
+ @urlProps.each do |value|
786
+ if !@props || !@props.has_key?(value) then
787
+ raise "Unable to process request due to missing required prop: #{value}"
788
+ end
789
+ end
790
+
791
+ if @props and @props.is_a? Hash then
792
+ @props.each do |k,v|
793
+ if @urlProps.include?(k) then
794
+ completeURL.sub!("{#{k}}", v)
795
+ end
796
+ end
797
+ end
798
+
799
+ ET_DeleteRest.new(@authStub, completeURL)
800
+ end
801
+
802
+ end
803
+
804
+
805
+ class ET_GetRest < ET_Constructor
806
+ def initialize(authStub, endpoint, qs = nil)
807
+ authStub.refreshToken
808
+
809
+ if qs then
810
+ qs['access_token'] = authStub.authToken
811
+ else
812
+ qs = {"access_token" => authStub.authToken}
813
+ end
814
+
815
+ uri = URI.parse(endpoint)
816
+ uri.query = URI.encode_www_form(qs)
817
+ http = Net::HTTP.new(uri.host, uri.port)
818
+ http.use_ssl = true
819
+ request = Net::HTTP::Get.new(uri.request_uri)
820
+ requestResponse = http.request(request)
821
+
822
+ @moreResults = false
823
+
824
+ obj = super(requestResponse, true)
825
+ return obj
826
+ end
827
+ end
828
+
829
+
830
+ class ET_ContinueRest < ET_Constructor
831
+ def initialize(authStub, endpoint, qs = nil)
832
+ authStub.refreshToken
833
+
834
+ if qs then
835
+ qs['access_token'] = authStub.authToken
836
+ else
837
+ qs = {"access_token" => authStub.authToken}
838
+ end
839
+
840
+ uri = URI.parse(endpoint)
841
+ uri.query = URI.encode_www_form(qs)
842
+ http = Net::HTTP.new(uri.host, uri.port)
843
+ http.use_ssl = true
844
+ request = Net::HTTP::Get.new(uri.request_uri)
845
+ requestResponse = http.request(request)
846
+
847
+ @moreResults = false
848
+
849
+ super(requestResponse, true)
850
+ end
851
+ end
852
+
853
+
854
+ class ET_PostRest < ET_Constructor
855
+ def initialize(authStub, endpoint, payload)
856
+ authStub.refreshToken
857
+
858
+ qs = {"access_token" => authStub.authToken}
859
+ uri = URI.parse(endpoint)
860
+ uri.query = URI.encode_www_form(qs)
861
+ http = Net::HTTP.new(uri.host, uri.port)
862
+ http.use_ssl = true
863
+ request = Net::HTTP::Post.new(uri.request_uri)
864
+ request.body = payload.to_json
865
+ request.add_field "Content-Type", "application/json"
866
+ requestResponse = http.request(request)
867
+
868
+ super(requestResponse, true)
869
+
870
+ end
871
+ end
872
+
873
+ class ET_PatchRest < ET_Constructor
874
+ def initialize(authStub, endpoint, payload)
875
+ authStub.refreshToken
876
+
877
+ qs = {"access_token" => authStub.authToken}
878
+ uri = URI.parse(endpoint)
879
+ uri.query = URI.encode_www_form(qs)
880
+ http = Net::HTTP.new(uri.host, uri.port)
881
+ http.use_ssl = true
882
+ request = Net::HTTP::Patch.new(uri.request_uri)
883
+ request.body = payload.to_json
884
+ request.add_field "Content-Type", "application/json"
885
+ requestResponse = http.request(request)
886
+ super(requestResponse, true)
887
+
888
+ end
889
+ end
890
+
891
+ class ET_DeleteRest < ET_Constructor
892
+ def initialize(authStub, endpoint)
893
+ authStub.refreshToken
894
+
895
+ qs = {"access_token" => authStub.authToken}
896
+
897
+ uri = URI.parse(endpoint)
898
+ uri.query = URI.encode_www_form(qs)
899
+ http = Net::HTTP.new(uri.host, uri.port)
900
+ http.use_ssl = true
901
+ request = Net::HTTP::Delete.new(uri.request_uri)
902
+ requestResponse = http.request(request)
903
+ super(requestResponse, true)
904
+
905
+ end
906
+ end
907
+
908
+ class ET_Campaign < ET_CUDSupportRest
909
+ def initialize
910
+ super
911
+ @endpoint = 'https://www.exacttargetapis.com/hub/v1/campaigns/{id}'
912
+ @urlProps = ["id"]
913
+ @urlPropsRequired = []
914
+ end
915
+
916
+ class Asset < ET_CUDSupportRest
917
+ def initialize
918
+ super
919
+ @endpoint = 'https://www.exacttargetapis.com/hub/v1/campaigns/{id}/assets/{assetId}'
920
+ @urlProps = ["id", "assetId"]
921
+ @urlPropsRequired = ["id"]
922
+ end
923
+ end
924
+ end
925
+
926
+ class ET_Subscriber < ET_CUDSupport
927
+ def initialize
928
+ super
929
+ @obj = 'Subscriber'
930
+ end
931
+ end
932
+
933
+ class ET_DataExtension < ET_CUDSupport
934
+ attr_accessor :columns
935
+
936
+ def initialize
937
+ super
938
+ @obj = 'DataExtension'
939
+ end
940
+
941
+ def post
942
+ originalProps = @props
943
+
944
+ if @props.is_a? Array then
945
+ multiDE = []
946
+ @props.each { |currentDE|
947
+ currentDE['Fields'] = {}
948
+ currentDE['Fields']['Field'] = []
949
+ currentDE['columns'].each { |key|
950
+ currentDE['Fields']['Field'].push(key)
951
+ }
952
+ currentDE.delete('columns')
953
+ multiDE.push(currentDE.dup)
954
+ }
955
+
956
+ @props = multiDE
957
+ else
958
+ @props['Fields'] = {}
959
+ @props['Fields']['Field'] = []
960
+
961
+ @columns.each { |key|
962
+ @props['Fields']['Field'].push(key)
963
+ }
964
+ end
965
+
966
+ obj = super
967
+ @props = originalProps
968
+ return obj
969
+ end
970
+
971
+ def patch
972
+ @props['Fields'] = {}
973
+ @props['Fields']['Field'] = []
974
+ @columns.each { |key|
975
+ @props['Fields']['Field'].push(key)
976
+ }
977
+ obj = super
978
+ @props.delete("Fields")
979
+ return obj
980
+ end
981
+
982
+ class Column < ET_GetSupport
983
+ def initialize
984
+ super
985
+ @obj = 'DataExtensionField'
986
+ end
987
+
988
+ def get
989
+
990
+ if props and props.is_a? Array then
991
+ @props = props
992
+ end
993
+
994
+ if @props and @props.is_a? Hash then
995
+ @props = @props.keys
996
+ end
997
+
998
+ if filter and filter.is_a? Hash then
999
+ @filter = filter
1000
+ end
1001
+
1002
+ fixCustomerKey = false
1003
+ if filter and filter.is_a? Hash then
1004
+ @filter = filter
1005
+ if @filter.has_key?("Property") && @filter["Property"] == "CustomerKey" then
1006
+ @filter["Property"] = "DataExtension.CustomerKey"
1007
+ fixCustomerKey = true
1008
+ end
1009
+ end
1010
+
1011
+ obj = ET_Get.new(@authStub, @obj, @props, @filter)
1012
+ @lastRequestID = obj.request_id
1013
+
1014
+ if fixCustomerKey then
1015
+ @filter["Property"] = "CustomerKey"
1016
+ end
1017
+
1018
+ return obj
1019
+ end
1020
+ end
1021
+
1022
+ class Row < ET_CUDSupport
1023
+ attr_accessor :Name, :CustomerKey
1024
+
1025
+ def initialize()
1026
+ super
1027
+ @obj = "DataExtensionObject"
1028
+ end
1029
+
1030
+ def get
1031
+ getName
1032
+ if props and props.is_a? Array then
1033
+ @props = props
1034
+ end
1035
+
1036
+ if @props and @props.is_a? Hash then
1037
+ @props = @props.keys
1038
+ end
1039
+
1040
+ if filter and filter.is_a? Hash then
1041
+ @filter = filter
1042
+ end
1043
+
1044
+ obj = ET_Get.new(@authStub, "DataExtensionObject[#{@Name}]", @props, @filter)
1045
+ @lastRequestID = obj.request_id
1046
+
1047
+ return obj
1048
+ end
1049
+
1050
+ def post
1051
+ getCustomerKey
1052
+ originalProps = @props
1053
+ ## FIX THIS
1054
+ if @props.is_a? Array then
1055
+ =begin
1056
+ multiRow = []
1057
+ @props.each { |currentDE|
1058
+
1059
+ currentDE['columns'].each { |key|
1060
+ currentDE['Fields'] = {}
1061
+ currentDE['Fields']['Field'] = []
1062
+ currentDE['Fields']['Field'].push(key)
1063
+ }
1064
+ currentDE.delete('columns')
1065
+ multiRow.push(currentDE.dup)
1066
+ }
1067
+
1068
+ @props = multiRow
1069
+ =end
1070
+ else
1071
+ currentFields = []
1072
+ currentProp = {}
1073
+
1074
+ @props.each { |key,value|
1075
+ currentFields.push({"Name" => key, "Value" => value})
1076
+ }
1077
+ currentProp['CustomerKey'] = @CustomerKey
1078
+ currentProp['Properties'] = {}
1079
+ currentProp['Properties']['Property'] = currentFields
1080
+ end
1081
+
1082
+ obj = ET_Post.new(@authStub, @obj, currentProp)
1083
+ @props = originalProps
1084
+ obj
1085
+ end
1086
+
1087
+ def patch
1088
+ getCustomerKey
1089
+ currentFields = []
1090
+ currentProp = {}
1091
+
1092
+ @props.each { |key,value|
1093
+ currentFields.push({"Name" => key, "Value" => value})
1094
+ }
1095
+ currentProp['CustomerKey'] = @CustomerKey
1096
+ currentProp['Properties'] = {}
1097
+ currentProp['Properties']['Property'] = currentFields
1098
+
1099
+ ET_Patch.new(@authStub, @obj, currentProp)
1100
+ end
1101
+ def delete
1102
+ getCustomerKey
1103
+ currentFields = []
1104
+ currentProp = {}
1105
+
1106
+ @props.each { |key,value|
1107
+ currentFields.push({"Name" => key, "Value" => value})
1108
+ }
1109
+ currentProp['CustomerKey'] = @CustomerKey
1110
+ currentProp['Keys'] = {}
1111
+ currentProp['Keys']['Key'] = currentFields
1112
+
1113
+ ET_Delete.new(@authStub, @obj, currentProp)
1114
+ end
1115
+
1116
+ private
1117
+ def getCustomerKey
1118
+ if @CustomerKey.nil? then
1119
+ if @CustomerKey.nil? && @Name.nil? then
1120
+ raise 'Unable to process DataExtension::Row request due to CustomerKey and Name not being defined on ET_DatExtension::row'
1121
+ else
1122
+ de = ET_DataExtension.new
1123
+ de.authStub = @authStub
1124
+ de.props = ["Name","CustomerKey"]
1125
+ de.filter = {'Property' => 'CustomerKey','SimpleOperator' => 'equals','Value' => @Name}
1126
+ getResponse = de.get
1127
+ if getResponse.status && (getResponse.results.length == 1) then
1128
+ @CustomerKey = getResponse.results[0][:customer_key]
1129
+ else
1130
+ raise 'Unable to process DataExtension::Row request due to unable to find DataExtension based on Name'
1131
+ end
1132
+ end
1133
+ end
1134
+ end
1135
+
1136
+ def getName
1137
+ if @Name.nil? then
1138
+ if @CustomerKey.nil? && @Name.nil? then
1139
+ raise 'Unable to process DataExtension::Row request due to CustomerKey and Name not being defined on ET_DatExtension::row'
1140
+ else
1141
+ de = ET_DataExtension.new
1142
+ de.authStub = @authStub
1143
+ de.props = ["Name","CustomerKey"]
1144
+ de.filter = {'Property' => 'CustomerKey','SimpleOperator' => 'equals','Value' => @CustomerKey}
1145
+ getResponse = de.get
1146
+ if getResponse.status && (getResponse.results.length == 1) then
1147
+ @Name = getResponse.results[0][:name]
1148
+ else
1149
+ raise 'Unable to process DataExtension::Row request due to unable to find DataExtension based on CustomerKey'
1150
+ end
1151
+ end
1152
+ end
1153
+ end
1154
+ end
1155
+ end
1156
+
1157
+ class ET_List < ET_CUDSupport
1158
+ def initialize
1159
+ super
1160
+ @obj = 'List'
1161
+ end
1162
+
1163
+ class Subscriber < ET_GetSupport
1164
+ def initialize
1165
+ super
1166
+ @obj = 'ListSubscriber'
1167
+ end
1168
+ end
1169
+ end
1170
+
1171
+ class ET_Email < ET_CUDSupport
1172
+ def initialize
1173
+ super
1174
+ @obj = 'Email'
1175
+ end
1176
+ end
1177
+
1178
+ class ET_TriggeredSend < ET_CUDSupport
1179
+ attr_accessor :subscribers, :attributes
1180
+ def initialize
1181
+ super
1182
+ @obj = 'TriggeredSendDefinition'
1183
+ end
1184
+
1185
+ def send
1186
+ @tscall = {"TriggeredSendDefinition" => @props, "Subscribers" => @subscribers, "Attributes" => @attributes}
1187
+ ET_Post.new(@authStub, "TriggeredSend", @tscall)
1188
+ end
1189
+ end
1190
+
1191
+ class ET_ContentArea < ET_CUDSupport
1192
+ def initialize
1193
+ super
1194
+ @obj = 'ContentArea'
1195
+ end
1196
+ end
1197
+
1198
+ class ET_Folder < ET_CUDSupport
1199
+ def initialize
1200
+ super
1201
+ @obj = 'DataFolder'
1202
+ end
1203
+ end
1204
+
1205
+ class ET_SentEvent < ET_GetSupport
1206
+ def initialize
1207
+ super
1208
+ @obj = 'SentEvent'
1209
+ end
1210
+ end
1211
+
1212
+ class ET_OpenEvent < ET_GetSupport
1213
+ def initialize
1214
+ super
1215
+ @obj = 'OpenEvent'
1216
+ end
1217
+ end
1218
+
1219
+ class ET_BounceEvent < ET_GetSupport
1220
+ def initialize
1221
+ super
1222
+ @obj = 'BounceEvent'
1223
+ end
1224
+ end
1225
+
1226
+ class ET_UnsubEvent < ET_GetSupport
1227
+ def initialize
1228
+ super
1229
+ @obj = 'UnsubEvent'
1230
+ end
1231
+ end
1232
+
1233
+ class ET_ClickEvent < ET_GetSupport
1234
+ def initialize
1235
+ super
1236
+ @obj = 'ClickEvent'
1237
+ end
1238
+ end
1239
+
1240
+ end