sfmc-fuelsdk-ruby 1.3.0 → 1.3.1

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.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/.github/ISSUE_TEMPLATE/bug_report.md +39 -39
  3. data/.github/ISSUE_TEMPLATE/feature_request.md +20 -20
  4. data/.gitignore +29 -29
  5. data/Gemfile +3 -3
  6. data/Gemfile.lock +104 -92
  7. data/Guardfile +8 -8
  8. data/LICENSE.md +13 -13
  9. data/README.md +200 -194
  10. data/Rakefile +1 -1
  11. data/lib/marketingcloudsdk.rb +74 -74
  12. data/lib/marketingcloudsdk/client.rb +395 -395
  13. data/lib/marketingcloudsdk/http_request.rb +118 -118
  14. data/lib/marketingcloudsdk/objects.rb +757 -757
  15. data/lib/marketingcloudsdk/rest.rb +118 -118
  16. data/lib/marketingcloudsdk/soap.rb +296 -296
  17. data/lib/marketingcloudsdk/targeting.rb +99 -99
  18. data/lib/marketingcloudsdk/utils.rb +47 -47
  19. data/lib/marketingcloudsdk/version.rb +39 -39
  20. data/lib/new.rb +1240 -1240
  21. data/marketingcloudsdk.gemspec +30 -30
  22. data/samples/sample-AddSubscriberToList.rb +56 -56
  23. data/samples/sample-CreateAndStartDataExtensionImport.rb +29 -29
  24. data/samples/sample-CreateAndStartListImport.rb +27 -27
  25. data/samples/sample-CreateContentAreas.rb +48 -48
  26. data/samples/sample-CreateDataExtensions.rb +54 -54
  27. data/samples/sample-CreateProfileAttributes.rb +48 -48
  28. data/samples/sample-SendEmailToDataExtension.rb +23 -23
  29. data/samples/sample-SendEmailToList.rb +23 -23
  30. data/samples/sample-SendTriggeredSends.rb +30 -30
  31. data/samples/sample-bounceevent.rb +70 -70
  32. data/samples/sample-campaign.rb +211 -211
  33. data/samples/sample-clickevent.rb +71 -71
  34. data/samples/sample-contentarea.rb +122 -122
  35. data/samples/sample-dataextension.rb +209 -209
  36. data/samples/sample-directverb.rb +54 -54
  37. data/samples/sample-email.rb +122 -122
  38. data/samples/sample-email.senddefinition.rb +134 -134
  39. data/samples/sample-folder.rb +143 -143
  40. data/samples/sample-import.rb +103 -103
  41. data/samples/sample-list.rb +105 -105
  42. data/samples/sample-list.subscriber.rb +97 -97
  43. data/samples/sample-openevent.rb +70 -70
  44. data/samples/sample-profileattribute.rb +56 -56
  45. data/samples/sample-sentevent.rb +70 -70
  46. data/samples/sample-subscriber.rb +135 -135
  47. data/samples/sample-triggeredsend.rb +129 -129
  48. data/samples/sample-unsubevent.rb +72 -72
  49. data/samples/sample_helper.rb.template +10 -10
  50. data/spec/client_spec.rb +416 -416
  51. data/spec/default_values_fallback_spec.rb +30 -30
  52. data/spec/helper_funcs_spec.rb +11 -11
  53. data/spec/http_request_spec.rb +61 -61
  54. data/spec/objects_helper_spec.rb +32 -32
  55. data/spec/objects_spec.rb +484 -484
  56. data/spec/public_or_web_integration_credentials.rb.template +11 -11
  57. data/spec/rest_spec.rb +48 -48
  58. data/spec/soap_spec.rb +140 -140
  59. data/spec/spec_helper.rb +14 -14
  60. data/spec/targeting_spec.rb +44 -44
  61. metadata +14 -27
data/Rakefile CHANGED
@@ -1 +1 @@
1
- require "bundler/gem_tasks"
1
+ require "bundler/gem_tasks"
@@ -1,74 +1,74 @@
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 'date'
41
- require 'jwt'
42
-
43
- module MarketingCloudSDK
44
- require 'marketingcloudsdk/utils'
45
- autoload :HTTPRequest, 'marketingcloudsdk/http_request'
46
- autoload :Targeting, 'marketingcloudsdk/targeting'
47
- autoload :Soap, 'marketingcloudsdk/soap'
48
- autoload :Rest, 'marketingcloudsdk/rest'
49
- require 'marketingcloudsdk/client'
50
- require 'marketingcloudsdk/objects'
51
- end
52
-
53
- # backwards compatability
54
- ET_Client = MarketingCloudSDK::Client
55
- ET_BounceEvent = MarketingCloudSDK::BounceEvent
56
- ET_ClickEvent = MarketingCloudSDK::ClickEvent
57
- ET_ContentArea = MarketingCloudSDK::ContentArea
58
- ET_DataExtension = MarketingCloudSDK::DataExtension
59
- ET_DataFolder = MarketingCloudSDK::DataFolder
60
- ET_Folder = MarketingCloudSDK::Folder
61
- ET_Email = MarketingCloudSDK::Email
62
- ET_List = MarketingCloudSDK::List
63
- ET_OpenEvent = MarketingCloudSDK::OpenEvent
64
- ET_SentEvent = MarketingCloudSDK::SentEvent
65
- ET_Subscriber = MarketingCloudSDK::Subscriber
66
- ET_UnsubEvent = MarketingCloudSDK::UnsubEvent
67
- ET_TriggeredSend = MarketingCloudSDK::TriggeredSend
68
- ET_Campaign = MarketingCloudSDK::Campaign
69
- ET_Get = MarketingCloudSDK::Get
70
- ET_Post = MarketingCloudSDK::Post
71
- ET_Delete = MarketingCloudSDK::Delete
72
- ET_Patch = MarketingCloudSDK::Patch
73
- ET_ProfileAttribute = MarketingCloudSDK::ProfileAttribute
74
- ET_Import = MarketingCloudSDK::Import
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 'date'
41
+ require 'jwt'
42
+
43
+ module MarketingCloudSDK
44
+ require 'marketingcloudsdk/utils'
45
+ autoload :HTTPRequest, 'marketingcloudsdk/http_request'
46
+ autoload :Targeting, 'marketingcloudsdk/targeting'
47
+ autoload :Soap, 'marketingcloudsdk/soap'
48
+ autoload :Rest, 'marketingcloudsdk/rest'
49
+ require 'marketingcloudsdk/client'
50
+ require 'marketingcloudsdk/objects'
51
+ end
52
+
53
+ # backwards compatability
54
+ ET_Client = MarketingCloudSDK::Client
55
+ ET_BounceEvent = MarketingCloudSDK::BounceEvent
56
+ ET_ClickEvent = MarketingCloudSDK::ClickEvent
57
+ ET_ContentArea = MarketingCloudSDK::ContentArea
58
+ ET_DataExtension = MarketingCloudSDK::DataExtension
59
+ ET_DataFolder = MarketingCloudSDK::DataFolder
60
+ ET_Folder = MarketingCloudSDK::Folder
61
+ ET_Email = MarketingCloudSDK::Email
62
+ ET_List = MarketingCloudSDK::List
63
+ ET_OpenEvent = MarketingCloudSDK::OpenEvent
64
+ ET_SentEvent = MarketingCloudSDK::SentEvent
65
+ ET_Subscriber = MarketingCloudSDK::Subscriber
66
+ ET_UnsubEvent = MarketingCloudSDK::UnsubEvent
67
+ ET_TriggeredSend = MarketingCloudSDK::TriggeredSend
68
+ ET_Campaign = MarketingCloudSDK::Campaign
69
+ ET_Get = MarketingCloudSDK::Get
70
+ ET_Post = MarketingCloudSDK::Post
71
+ ET_Delete = MarketingCloudSDK::Delete
72
+ ET_Patch = MarketingCloudSDK::Patch
73
+ ET_ProfileAttribute = MarketingCloudSDK::ProfileAttribute
74
+ ET_Import = MarketingCloudSDK::Import
@@ -1,395 +1,395 @@
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
-
36
- =end
37
-
38
- require 'securerandom'
39
- module MarketingCloudSDK
40
- class Response
41
- # not doing accessor so user, can't update these values from response.
42
- # You will see in the code some of these
43
- # items are being updated via back doors and such.
44
- attr_reader :code, :message, :results, :request_id, :body, :raw
45
-
46
- # some defaults
47
- def success
48
- @success ||= false
49
- end
50
- alias :success? :success
51
- alias :status :success # backward compatibility
52
-
53
- def more
54
- @more ||= false
55
- end
56
- alias :more? :more
57
-
58
- def initialize raw, client
59
- @client = client # keep connection with client in case we request more
60
- @results = []
61
- @raw = raw
62
- unpack raw
63
- rescue => ex # all else fails return raw
64
- puts ex.message
65
- raw
66
- end
67
-
68
- def continue
69
- raise NotImplementedError
70
- end
71
-
72
- private
73
- def unpack raw
74
- raise NotImplementedError
75
- end
76
- end
77
-
78
- class Client
79
- attr_accessor :debug, :access_token, :auth_token, :internal_token, :refresh_token,
80
- :id, :secret, :signature, :base_api_url, :package_name, :package_folders, :parent_folders, :auth_token_expiration,
81
- :request_token_url, :soap_endpoint, :use_oAuth2_authentication, :account_id, :scope, :application_type, :authorization_code, :redirect_URI
82
-
83
- include MarketingCloudSDK::Soap
84
- include MarketingCloudSDK::Rest
85
-
86
- def jwt= encoded_jwt
87
- raise 'Require app signature to decode JWT' unless self.signature
88
- decoded_jwt = JWT.decode(encoded_jwt, self.signature, true)
89
- decoded_jwt_first = decoded_jwt.first
90
-
91
- self.auth_token = decoded_jwt_first['request']['user']['oauthToken']
92
- self.internal_token = decoded_jwt_first['request']['user']['internalOauthToken']
93
- self.refresh_token = decoded_jwt_first['request']['user']['refreshToken']
94
- self.auth_token_expiration = Time.new + decoded_jwt_first['request']['user']['expiresIn']
95
- self.package_name = decoded_jwt_first['request']['application']['package']
96
- end
97
-
98
- def initialize(params={}, debug=false)
99
- @refresh_mutex = Mutex.new
100
- self.debug = debug
101
- client_config = params['client']
102
- if client_config
103
- self.id = client_config["id"]
104
- self.secret = client_config["secret"]
105
- self.signature = client_config["signature"]
106
- self.base_api_url = !(client_config["base_api_url"].to_s.strip.empty?) ? client_config["base_api_url"] : 'https://www.exacttargetapis.com'
107
- self.request_token_url = client_config["request_token_url"]
108
- self.soap_endpoint = client_config["soap_endpoint"]
109
- self.use_oAuth2_authentication = client_config["use_oAuth2_authentication"]
110
- self.account_id = client_config["account_id"]
111
- self.scope = client_config["scope"]
112
- self.application_type = client_config["application_type"]
113
- self.authorization_code = client_config["authorization_code"]
114
- self.redirect_URI = client_config["redirect_URI"]
115
- end
116
-
117
- # Set a default value in case no 'client' params is sent
118
- if (!self.base_api_url)
119
- self.base_api_url = 'https://www.exacttargetapis.com'
120
- end
121
-
122
- if (self.request_token_url.to_s.strip.empty?)
123
- if(use_oAuth2_authentication == true)
124
- raise 'request_token_url (Auth TSE) is mandatory when using OAuth2 authentication'
125
- else
126
- self.request_token_url = 'https://auth.exacttargetapis.com/v1/requestToken'
127
- end
128
- end
129
-
130
- if application_type.to_s.strip.empty?
131
- self.application_type = 'server'
132
- end
133
-
134
- if ['web', 'public'].include? application_type
135
- if authorization_code.to_s.strip.empty? or redirect_URI.to_s.strip.empty?
136
- raise 'authorization_code or redirect_URI is null: For Public/Web Apps, the authorization_code and redirect_URI must be passed when instantiating Client'
137
- end
138
- end
139
-
140
- if application_type == 'public'
141
- if id.to_s.strip.empty?
142
- raise 'id is null: id must be passed when instantiating Client'
143
- end
144
- else
145
- if id.to_s.strip.empty? or secret.to_s.strip.empty?
146
- raise 'id and secret must pe passed when instantiating Client'
147
- end
148
- end
149
-
150
- self.jwt = params['jwt'] if params['jwt']
151
- self.refresh_token = params['refresh_token'] if params['refresh_token']
152
-
153
- self.wsdl = params["defaultwsdl"] if params["defaultwsdl"]
154
-
155
- self.refresh
156
- end
157
-
158
- def refresh force=false
159
- @refresh_mutex.synchronize do
160
-
161
- if (self.use_oAuth2_authentication == true)
162
- self.refreshWithOAuth2(force)
163
- return
164
- end
165
-
166
- #If we don't already have a token or the token expires within 5 min(300 seconds)
167
- if (self.access_token.nil? || Time.new + 300 > self.auth_token_expiration || force) then
168
- payload = Hash.new.tap do |h|
169
- h['clientId']= id
170
- h['clientSecret'] = secret
171
- h['refreshToken'] = refresh_token if refresh_token
172
- h['accessType'] = 'offline'
173
- end
174
-
175
- options = Hash.new.tap do |h|
176
- h['data'] = payload
177
- h['content_type'] = 'application/json'
178
- h['params'] = {'legacy' => 1}
179
- end
180
- response = post(request_token_url, options)
181
- raise "Unable to refresh token: #{response['message']}" unless response.has_key?('accessToken')
182
-
183
- self.access_token = response['accessToken']
184
- self.internal_token = response['legacyToken']
185
- self.auth_token_expiration = Time.new + response['expiresIn']
186
- self.refresh_token = response['refreshToken'] if response.has_key?("refreshToken")
187
- return true
188
- else
189
- return false
190
- end
191
- end
192
- end
193
-
194
- def refreshWithOAuth2 force=false
195
- #If we don't already have a token or the token expires within 5 min(300 seconds)
196
- if (self.access_token.nil? || Time.new + 300 > self.auth_token_expiration || force) then
197
-
198
- payload = createPayload
199
-
200
- options = Hash.new.tap do |h|
201
- h['data'] = payload
202
- h['content_type'] = 'application/json'
203
- end
204
-
205
- auth_endpoint = request_token_url + '/v2/token'
206
-
207
- response = post(auth_endpoint, options)
208
- raise "Unable to refresh token: #{response['message']}" unless response.has_key?('access_token')
209
-
210
- self.access_token = response['access_token']
211
- self.auth_token_expiration = Time.new + response['expires_in']
212
- self.soap_endpoint = response['soap_instance_url'] + 'service.asmx'
213
- self.base_api_url = response['rest_instance_url']
214
-
215
- if response.has_key?('refresh_token')
216
- self.refresh_token = response['refresh_token']
217
- end
218
-
219
- return true
220
- else
221
- return false
222
- end
223
- end
224
-
225
- def createPayload
226
- payload = Hash.new.tap do |h|
227
- h['client_id'] = id
228
-
229
- if application_type != 'public'
230
- h['client_secret'] = secret
231
- end
232
-
233
- if !refresh_token.to_s.strip.empty?
234
- h['grant_type'] = 'refresh_token'
235
- h['refresh_token'] = refresh_token
236
- elsif ['web', 'public'].include? application_type
237
- h['grant_type'] = 'authorization_code'
238
- h['code'] = authorization_code
239
- h['redirect_uri'] = redirect_URI
240
- else
241
- h['grant_type'] = 'client_credentials'
242
- end
243
-
244
- unless account_id.to_s.strip.empty?
245
- h['account_id'] = account_id
246
- end
247
-
248
- unless scope.to_s.strip.empty?
249
- h['scope'] = scope
250
- end
251
- end
252
-
253
- payload
254
- end
255
-
256
- def refresh!
257
- refresh true
258
- end
259
-
260
- def AddSubscriberToList(email, ids, subscriber_key = nil)
261
- s = MarketingCloudSDK::Subscriber.new
262
- s.client = self
263
- lists = ids.collect{|id| {'ID' => id}}
264
- s.properties = {"EmailAddress" => email, "Lists" => lists}
265
- p s.properties
266
- s.properties['SubscriberKey'] = subscriber_key if subscriber_key
267
-
268
- # Try to add the subscriber
269
- if(rsp = s.post and rsp.results.first[:error_code] == '12014')
270
- # subscriber already exists we need to update.
271
- rsp = s.patch
272
- end
273
- rsp
274
- end
275
-
276
- def CreateDataExtensions(definitions)
277
- de = MarketingCloudSDK::DataExtension.new
278
- de.client = self
279
- de.properties = definitions
280
- de.post
281
- end
282
- def SendTriggeredSends(arrayOfTriggeredRecords)
283
- sendTS = ET_TriggeredSend.new
284
- sendTS.authStub = self
285
-
286
- sendTS.properties = arrayOfTriggeredRecords
287
- sendResponse = sendTS.send
288
-
289
- return sendResponse
290
- end
291
- def SendEmailToList(emailID, listID, sendClassificationCustomerKey)
292
- email = ET_Email::SendDefinition.new
293
- email.properties = {"Name"=>SecureRandom.uuid, "CustomerKey"=>SecureRandom.uuid, "Description"=>"Created with RubySDK"}
294
- email.properties["SendClassification"] = {"CustomerKey"=>sendClassificationCustomerKey}
295
- email.properties["SendDefinitionList"] = {"List"=> {"ID"=>listID}, "DataSourceTypeID"=>"List"}
296
- email.properties["Email"] = {"ID"=>emailID}
297
- email.authStub = self
298
- result = email.post
299
- if result.status then
300
- sendresult = email.send
301
- if sendresult.status then
302
- deleteresult = email.delete
303
- return sendresult
304
- else
305
- raise "Unable to send using send definition due to: #{result.results[0][:status_message]}"
306
- end
307
- else
308
- raise "Unable to create send definition due to: #{result.results[0][:status_message]}"
309
- end
310
- end
311
-
312
- def SendEmailToDataExtension(emailID, sendableDataExtensionCustomerKey, sendClassificationCustomerKey)
313
- email = ET_Email::SendDefinition.new
314
- email.properties = {"Name"=>SecureRandom.uuid, "CustomerKey"=>SecureRandom.uuid, "Description"=>"Created with RubySDK"}
315
- email.properties["SendClassification"] = {"CustomerKey"=> sendClassificationCustomerKey}
316
- email.properties["SendDefinitionList"] = {"CustomerKey"=> sendableDataExtensionCustomerKey, "DataSourceTypeID"=>"CustomObject"}
317
- email.properties["Email"] = {"ID"=>emailID}
318
- email.authStub = self
319
- result = email.post
320
- if result.status then
321
- sendresult = email.send
322
- if sendresult.status then
323
- deleteresult = email.delete
324
- return sendresult
325
- else
326
- raise "Unable to send using send definition due to: #{result.results[0][:status_message]}"
327
- end
328
- else
329
- raise "Unable to create send definition due to: #{result.results[0][:status_message]}"
330
- end
331
- end
332
- def CreateAndStartListImport(listId,fileName)
333
- import = ET_Import.new
334
- import.authStub = self
335
- import.properties = {"Name"=> "SDK Generated Import #{DateTime.now.to_s}"}
336
- import.properties["CustomerKey"] = SecureRandom.uuid
337
- import.properties["Description"] = "SDK Generated Import"
338
- import.properties["AllowErrors"] = "true"
339
- import.properties["DestinationObject"] = {"ID"=>listId}
340
- import.properties["FieldMappingType"] = "InferFromColumnHeadings"
341
- import.properties["FileSpec"] = fileName
342
- import.properties["FileType"] = "CSV"
343
- import.properties["RetrieveFileTransferLocation"] = {"CustomerKey"=>"ExactTarget Enhanced FTP"}
344
- import.properties["UpdateType"] = "AddAndUpdate"
345
- result = import.post
346
-
347
- if result.status then
348
- return import.start
349
- else
350
- raise "Unable to create import definition due to: #{result.results[0][:status_message]}"
351
- end
352
- end
353
-
354
- def CreateAndStartDataExtensionImport(dataExtensionCustomerKey, fileName, overwrite)
355
- import = ET_Import.new
356
- import.authStub = self
357
- import.properties = {"Name"=> "SDK Generated Import #{DateTime.now.to_s}"}
358
- import.properties["CustomerKey"] = SecureRandom.uuid
359
- import.properties["Description"] = "SDK Generated Import"
360
- import.properties["AllowErrors"] = "true"
361
- import.properties["DestinationObject"] = {"CustomerKey"=>dataExtensionCustomerKey}
362
- import.properties["FieldMappingType"] = "InferFromColumnHeadings"
363
- import.properties["FileSpec"] = fileName
364
- import.properties["FileType"] = "CSV"
365
- import.properties["RetrieveFileTransferLocation"] = {"CustomerKey"=>"ExactTarget Enhanced FTP"}
366
- if overwrite then
367
- import.properties["UpdateType"] = "Overwrite"
368
- else
369
- import.properties["UpdateType"] = "AddAndUpdate"
370
- end
371
- result = import.post
372
-
373
- if result.status then
374
- return import.start
375
- else
376
- raise "Unable to create import definition due to: #{result.results[0][:status_message]}"
377
- end
378
- end
379
-
380
- def CreateProfileAttributes(allAttributes)
381
- attrs = ET_ProfileAttribute.new
382
- attrs.authStub = self
383
- attrs.properties = allAttributes
384
- return attrs.post
385
- end
386
-
387
- def CreateContentAreas(arrayOfContentAreas)
388
- postC = ET_ContentArea.new
389
- postC.authStub = self
390
- postC.properties = arrayOfContentAreas
391
- sendResponse = postC.post
392
- return sendResponse
393
- end
394
- end
395
- end
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
+
36
+ =end
37
+
38
+ require 'securerandom'
39
+ module MarketingCloudSDK
40
+ class Response
41
+ # not doing accessor so user, can't update these values from response.
42
+ # You will see in the code some of these
43
+ # items are being updated via back doors and such.
44
+ attr_reader :code, :message, :results, :request_id, :body, :raw
45
+
46
+ # some defaults
47
+ def success
48
+ @success ||= false
49
+ end
50
+ alias :success? :success
51
+ alias :status :success # backward compatibility
52
+
53
+ def more
54
+ @more ||= false
55
+ end
56
+ alias :more? :more
57
+
58
+ def initialize raw, client
59
+ @client = client # keep connection with client in case we request more
60
+ @results = []
61
+ @raw = raw
62
+ unpack raw
63
+ rescue => ex # all else fails return raw
64
+ puts ex.message
65
+ raw
66
+ end
67
+
68
+ def continue
69
+ raise NotImplementedError
70
+ end
71
+
72
+ private
73
+ def unpack raw
74
+ raise NotImplementedError
75
+ end
76
+ end
77
+
78
+ class Client
79
+ attr_accessor :debug, :access_token, :auth_token, :internal_token, :refresh_token,
80
+ :id, :secret, :signature, :base_api_url, :package_name, :package_folders, :parent_folders, :auth_token_expiration,
81
+ :request_token_url, :soap_endpoint, :use_oAuth2_authentication, :account_id, :scope, :application_type, :authorization_code, :redirect_URI
82
+
83
+ include MarketingCloudSDK::Soap
84
+ include MarketingCloudSDK::Rest
85
+
86
+ def jwt= encoded_jwt
87
+ raise 'Require app signature to decode JWT' unless self.signature
88
+ decoded_jwt = JWT.decode(encoded_jwt, self.signature, true)
89
+ decoded_jwt_first = decoded_jwt.first
90
+
91
+ self.auth_token = decoded_jwt_first['request']['user']['oauthToken']
92
+ self.internal_token = decoded_jwt_first['request']['user']['internalOauthToken']
93
+ self.refresh_token = decoded_jwt_first['request']['user']['refreshToken']
94
+ self.auth_token_expiration = Time.new + decoded_jwt_first['request']['user']['expiresIn']
95
+ self.package_name = decoded_jwt_first['request']['application']['package']
96
+ end
97
+
98
+ def initialize(params={}, debug=false)
99
+ @refresh_mutex = Mutex.new
100
+ self.debug = debug
101
+ client_config = params['client']
102
+ if client_config
103
+ self.id = client_config["id"]
104
+ self.secret = client_config["secret"]
105
+ self.signature = client_config["signature"]
106
+ self.base_api_url = !(client_config["base_api_url"].to_s.strip.empty?) ? client_config["base_api_url"] : 'https://www.exacttargetapis.com'
107
+ self.request_token_url = client_config["request_token_url"]
108
+ self.soap_endpoint = client_config["soap_endpoint"]
109
+ self.use_oAuth2_authentication = client_config["use_oAuth2_authentication"]
110
+ self.account_id = client_config["account_id"]
111
+ self.scope = client_config["scope"]
112
+ self.application_type = client_config["application_type"]
113
+ self.authorization_code = client_config["authorization_code"]
114
+ self.redirect_URI = client_config["redirect_URI"]
115
+ end
116
+
117
+ # Set a default value in case no 'client' params is sent
118
+ if (!self.base_api_url)
119
+ self.base_api_url = 'https://www.exacttargetapis.com'
120
+ end
121
+
122
+ if (self.request_token_url.to_s.strip.empty?)
123
+ if(use_oAuth2_authentication == true)
124
+ raise 'request_token_url (Auth TSE) is mandatory when using OAuth2 authentication'
125
+ else
126
+ self.request_token_url = 'https://auth.exacttargetapis.com/v1/requestToken'
127
+ end
128
+ end
129
+
130
+ if application_type.to_s.strip.empty?
131
+ self.application_type = 'server'
132
+ end
133
+
134
+ if ['web', 'public'].include? application_type
135
+ if authorization_code.to_s.strip.empty? or redirect_URI.to_s.strip.empty?
136
+ raise 'authorization_code or redirect_URI is null: For Public/Web Apps, the authorization_code and redirect_URI must be passed when instantiating Client'
137
+ end
138
+ end
139
+
140
+ if application_type == 'public'
141
+ if id.to_s.strip.empty?
142
+ raise 'id is null: id must be passed when instantiating Client'
143
+ end
144
+ else
145
+ if id.to_s.strip.empty? or secret.to_s.strip.empty?
146
+ raise 'id and secret must pe passed when instantiating Client'
147
+ end
148
+ end
149
+
150
+ self.jwt = params['jwt'] if params['jwt']
151
+ self.refresh_token = params['refresh_token'] if params['refresh_token']
152
+
153
+ self.wsdl = params["defaultwsdl"] if params["defaultwsdl"]
154
+
155
+ self.refresh
156
+ end
157
+
158
+ def refresh force=false
159
+ @refresh_mutex.synchronize do
160
+
161
+ if (self.use_oAuth2_authentication == true)
162
+ self.refreshWithOAuth2(force)
163
+ return
164
+ end
165
+
166
+ #If we don't already have a token or the token expires within 5 min(300 seconds)
167
+ if (self.access_token.nil? || Time.new + 300 > self.auth_token_expiration || force) then
168
+ payload = Hash.new.tap do |h|
169
+ h['clientId']= id
170
+ h['clientSecret'] = secret
171
+ h['refreshToken'] = refresh_token if refresh_token
172
+ h['accessType'] = 'offline'
173
+ end
174
+
175
+ options = Hash.new.tap do |h|
176
+ h['data'] = payload
177
+ h['content_type'] = 'application/json'
178
+ h['params'] = {'legacy' => 1}
179
+ end
180
+ response = post(request_token_url, options)
181
+ raise "Unable to refresh token: #{response['message']}" unless response.has_key?('accessToken')
182
+
183
+ self.access_token = response['accessToken']
184
+ self.internal_token = response['legacyToken']
185
+ self.auth_token_expiration = Time.new + response['expiresIn']
186
+ self.refresh_token = response['refreshToken'] if response.has_key?("refreshToken")
187
+ return true
188
+ else
189
+ return false
190
+ end
191
+ end
192
+ end
193
+
194
+ def refreshWithOAuth2 force=false
195
+ #If we don't already have a token or the token expires within 5 min(300 seconds)
196
+ if (self.access_token.nil? || Time.new + 300 > self.auth_token_expiration || force) then
197
+
198
+ payload = createPayload
199
+
200
+ options = Hash.new.tap do |h|
201
+ h['data'] = payload
202
+ h['content_type'] = 'application/json'
203
+ end
204
+
205
+ auth_endpoint = request_token_url + '/v2/token'
206
+
207
+ response = post(auth_endpoint, options)
208
+ raise "Unable to refresh token: #{response['message']}" unless response.has_key?('access_token')
209
+
210
+ self.access_token = response['access_token']
211
+ self.auth_token_expiration = Time.new + response['expires_in']
212
+ self.soap_endpoint = response['soap_instance_url'] + 'service.asmx'
213
+ self.base_api_url = response['rest_instance_url']
214
+
215
+ if response.has_key?('refresh_token')
216
+ self.refresh_token = response['refresh_token']
217
+ end
218
+
219
+ return true
220
+ else
221
+ return false
222
+ end
223
+ end
224
+
225
+ def createPayload
226
+ payload = Hash.new.tap do |h|
227
+ h['client_id'] = id
228
+
229
+ if application_type != 'public'
230
+ h['client_secret'] = secret
231
+ end
232
+
233
+ if !refresh_token.to_s.strip.empty?
234
+ h['grant_type'] = 'refresh_token'
235
+ h['refresh_token'] = refresh_token
236
+ elsif ['web', 'public'].include? application_type
237
+ h['grant_type'] = 'authorization_code'
238
+ h['code'] = authorization_code
239
+ h['redirect_uri'] = redirect_URI
240
+ else
241
+ h['grant_type'] = 'client_credentials'
242
+ end
243
+
244
+ unless account_id.to_s.strip.empty?
245
+ h['account_id'] = account_id
246
+ end
247
+
248
+ unless scope.to_s.strip.empty?
249
+ h['scope'] = scope
250
+ end
251
+ end
252
+
253
+ payload
254
+ end
255
+
256
+ def refresh!
257
+ refresh true
258
+ end
259
+
260
+ def AddSubscriberToList(email, ids, subscriber_key = nil)
261
+ s = MarketingCloudSDK::Subscriber.new
262
+ s.client = self
263
+ lists = ids.collect{|id| {'ID' => id}}
264
+ s.properties = {"EmailAddress" => email, "Lists" => lists}
265
+ p s.properties
266
+ s.properties['SubscriberKey'] = subscriber_key if subscriber_key
267
+
268
+ # Try to add the subscriber
269
+ if(rsp = s.post and rsp.results.first[:error_code] == '12014')
270
+ # subscriber already exists we need to update.
271
+ rsp = s.patch
272
+ end
273
+ rsp
274
+ end
275
+
276
+ def CreateDataExtensions(definitions)
277
+ de = MarketingCloudSDK::DataExtension.new
278
+ de.client = self
279
+ de.properties = definitions
280
+ de.post
281
+ end
282
+ def SendTriggeredSends(arrayOfTriggeredRecords)
283
+ sendTS = ET_TriggeredSend.new
284
+ sendTS.authStub = self
285
+
286
+ sendTS.properties = arrayOfTriggeredRecords
287
+ sendResponse = sendTS.send
288
+
289
+ return sendResponse
290
+ end
291
+ def SendEmailToList(emailID, listID, sendClassificationCustomerKey)
292
+ email = ET_Email::SendDefinition.new
293
+ email.properties = {"Name"=>SecureRandom.uuid, "CustomerKey"=>SecureRandom.uuid, "Description"=>"Created with RubySDK"}
294
+ email.properties["SendClassification"] = {"CustomerKey"=>sendClassificationCustomerKey}
295
+ email.properties["SendDefinitionList"] = {"List"=> {"ID"=>listID}, "DataSourceTypeID"=>"List"}
296
+ email.properties["Email"] = {"ID"=>emailID}
297
+ email.authStub = self
298
+ result = email.post
299
+ if result.status then
300
+ sendresult = email.send
301
+ if sendresult.status then
302
+ deleteresult = email.delete
303
+ return sendresult
304
+ else
305
+ raise "Unable to send using send definition due to: #{result.results[0][:status_message]}"
306
+ end
307
+ else
308
+ raise "Unable to create send definition due to: #{result.results[0][:status_message]}"
309
+ end
310
+ end
311
+
312
+ def SendEmailToDataExtension(emailID, sendableDataExtensionCustomerKey, sendClassificationCustomerKey)
313
+ email = ET_Email::SendDefinition.new
314
+ email.properties = {"Name"=>SecureRandom.uuid, "CustomerKey"=>SecureRandom.uuid, "Description"=>"Created with RubySDK"}
315
+ email.properties["SendClassification"] = {"CustomerKey"=> sendClassificationCustomerKey}
316
+ email.properties["SendDefinitionList"] = {"CustomerKey"=> sendableDataExtensionCustomerKey, "DataSourceTypeID"=>"CustomObject"}
317
+ email.properties["Email"] = {"ID"=>emailID}
318
+ email.authStub = self
319
+ result = email.post
320
+ if result.status then
321
+ sendresult = email.send
322
+ if sendresult.status then
323
+ deleteresult = email.delete
324
+ return sendresult
325
+ else
326
+ raise "Unable to send using send definition due to: #{result.results[0][:status_message]}"
327
+ end
328
+ else
329
+ raise "Unable to create send definition due to: #{result.results[0][:status_message]}"
330
+ end
331
+ end
332
+ def CreateAndStartListImport(listId,fileName)
333
+ import = ET_Import.new
334
+ import.authStub = self
335
+ import.properties = {"Name"=> "SDK Generated Import #{DateTime.now.to_s}"}
336
+ import.properties["CustomerKey"] = SecureRandom.uuid
337
+ import.properties["Description"] = "SDK Generated Import"
338
+ import.properties["AllowErrors"] = "true"
339
+ import.properties["DestinationObject"] = {"ID"=>listId}
340
+ import.properties["FieldMappingType"] = "InferFromColumnHeadings"
341
+ import.properties["FileSpec"] = fileName
342
+ import.properties["FileType"] = "CSV"
343
+ import.properties["RetrieveFileTransferLocation"] = {"CustomerKey"=>"ExactTarget Enhanced FTP"}
344
+ import.properties["UpdateType"] = "AddAndUpdate"
345
+ result = import.post
346
+
347
+ if result.status then
348
+ return import.start
349
+ else
350
+ raise "Unable to create import definition due to: #{result.results[0][:status_message]}"
351
+ end
352
+ end
353
+
354
+ def CreateAndStartDataExtensionImport(dataExtensionCustomerKey, fileName, overwrite)
355
+ import = ET_Import.new
356
+ import.authStub = self
357
+ import.properties = {"Name"=> "SDK Generated Import #{DateTime.now.to_s}"}
358
+ import.properties["CustomerKey"] = SecureRandom.uuid
359
+ import.properties["Description"] = "SDK Generated Import"
360
+ import.properties["AllowErrors"] = "true"
361
+ import.properties["DestinationObject"] = {"CustomerKey"=>dataExtensionCustomerKey}
362
+ import.properties["FieldMappingType"] = "InferFromColumnHeadings"
363
+ import.properties["FileSpec"] = fileName
364
+ import.properties["FileType"] = "CSV"
365
+ import.properties["RetrieveFileTransferLocation"] = {"CustomerKey"=>"ExactTarget Enhanced FTP"}
366
+ if overwrite then
367
+ import.properties["UpdateType"] = "Overwrite"
368
+ else
369
+ import.properties["UpdateType"] = "AddAndUpdate"
370
+ end
371
+ result = import.post
372
+
373
+ if result.status then
374
+ return import.start
375
+ else
376
+ raise "Unable to create import definition due to: #{result.results[0][:status_message]}"
377
+ end
378
+ end
379
+
380
+ def CreateProfileAttributes(allAttributes)
381
+ attrs = ET_ProfileAttribute.new
382
+ attrs.authStub = self
383
+ attrs.properties = allAttributes
384
+ return attrs.post
385
+ end
386
+
387
+ def CreateContentAreas(arrayOfContentAreas)
388
+ postC = ET_ContentArea.new
389
+ postC.authStub = self
390
+ postC.properties = arrayOfContentAreas
391
+ sendResponse = postC.post
392
+ return sendResponse
393
+ end
394
+ end
395
+ end