spaceship 0.7.0 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,19 @@
1
+ module Spaceship
2
+ module Tunes
3
+ class AppVersionRef < TunesBase
4
+ attr_accessor :sso_token_for_image
5
+ attr_accessor :sso_token_for_video
6
+
7
+ attr_mapping(
8
+ 'ssoTokenForImage' => :sso_token_for_image,
9
+ 'ssoTokenForVideo' => :sso_token_for_video
10
+ )
11
+
12
+ class << self
13
+ def factory(attrs)
14
+ self.new(attrs)
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -1,7 +1,6 @@
1
1
  module Spaceship
2
2
  module Tunes
3
3
  class Application < TunesBase
4
-
5
4
  # @return (String) The App identifier of this app, provided by iTunes Connect
6
5
  # @example
7
6
  # "1013943394"
@@ -116,7 +115,8 @@ module Spaceship
116
115
  if raw_data['versions'].count == 1
117
116
  v = raw_data['versions'].last
118
117
 
119
- unless ['Prepare for Upload', 'prepareForUpload'].include?(v['state']) # this only applies for the initial version
118
+ # this only applies for the initial version
119
+ unless ['Prepare for Upload', 'Developer Rejected', 'Rejected', 'prepareForUpload'].include?(v['state'])
120
120
  return nil # only live version, user should create a new version
121
121
  end
122
122
  end
@@ -165,6 +165,16 @@ module Spaceship
165
165
  # Future: implemented -reload method
166
166
  end
167
167
 
168
+ # set the price tier. This method doesn't require `save` to be called
169
+ def update_price_tier!(price_tier)
170
+ client.update_price_tier!(self.apple_id, price_tier)
171
+ end
172
+
173
+ # The current price tier
174
+ def price_tier
175
+ client.price_tier(self.apple_id)
176
+ end
177
+
168
178
  #####################################################
169
179
  # @!group Builds
170
180
  #####################################################
@@ -2,7 +2,6 @@ module Spaceship
2
2
  module Tunes
3
3
  # Represents a build which is inside the build train
4
4
  class Build < TunesBase
5
-
6
5
  #####################################################
7
6
  # @!group General metadata
8
7
  #####################################################
@@ -22,6 +21,9 @@ module Spaceship
22
21
  # @return (String) The version number (e.g. 1.3)
23
22
  attr_accessor :train_version
24
23
 
24
+ # @return (Boolean) Is this build currently processing?
25
+ attr_accessor :processing
26
+
25
27
  # @return (Integer) The number of ticks since 1970 (e.g. 1413966436000)
26
28
  attr_accessor :upload_date
27
29
 
@@ -80,6 +82,7 @@ module Spaceship
80
82
  'platform' => :platform,
81
83
  'id' => :id,
82
84
  'valid' => :valid,
85
+ 'processing' => :processing,
83
86
 
84
87
  'installCount' => :install_count,
85
88
  'internalInstallCount' => :internal_install_count,
@@ -3,7 +3,6 @@ module Spaceship
3
3
  # Represents a build train of builds from iTunes Connect
4
4
  # A build train is all builds for a given version number with different build numbers
5
5
  class BuildTrain < TunesBase
6
-
7
6
  # @return (Spaceship::Tunes::Application) A reference to the application
8
7
  # this train is for
9
8
  attr_accessor :application
@@ -0,0 +1,14 @@
1
+ module Spaceship
2
+ module Tunes
3
+ class DeviceType
4
+ @types = ['iphone4', 'iphone35', 'iphone6', 'iphone6Plus', 'ipad', 'watch']
5
+ class << self
6
+ attr_accessor :types
7
+
8
+ def exists?(type)
9
+ types.include? type
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -2,7 +2,6 @@ module Spaceship
2
2
  module Tunes
3
3
  # Represents a build which doesn't have a version number yet and is either processing or is stuck
4
4
  class ProcessingBuild < Build
5
-
6
5
  # @return [String] The state of this build
7
6
  # @example
8
7
  # ITC.apps.betaProcessingStatus.InvalidBinary
@@ -1,7 +1,6 @@
1
1
  module Spaceship
2
2
  module Tunes
3
3
  class Tester < TunesBase
4
-
5
4
  # @return (String) The identifier of this tester, provided by iTunes Connect
6
5
  # @example
7
6
  # "60f858b4-60a8-428a-963a-f943a3d68d17"
@@ -59,7 +58,6 @@ module Spaceship
59
58
  )
60
59
 
61
60
  class << self
62
-
63
61
  # @return (Hash) All urls for the ITC used for web requests
64
62
  def url
65
63
  raise "You have to use a subclass: Internal or External"
@@ -0,0 +1,27 @@
1
+ module Spaceship
2
+ module Tunes
3
+ # Represents a geo json
4
+ class TransitAppFile < TunesBase
5
+ attr_accessor :asset_token
6
+
7
+ attr_accessor :name
8
+
9
+ attr_accessor :time_stamp
10
+
11
+ attr_accessor :url
12
+
13
+ attr_mapping(
14
+ 'assetToken' => :asset_token,
15
+ 'timeStemp' => :time_stamp,
16
+ 'url' => :url,
17
+ 'name' => :name
18
+ )
19
+
20
+ class << self
21
+ def factory(attrs)
22
+ self.new(attrs)
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -5,10 +5,21 @@ require 'spaceship/tunes/app_submission'
5
5
  require 'spaceship/tunes/tunes_client'
6
6
  require 'spaceship/tunes/language_item'
7
7
  require 'spaceship/tunes/app_status'
8
+ require 'spaceship/tunes/app_image'
9
+ require 'spaceship/tunes/app_version_ref'
10
+ require 'spaceship/tunes/transit_app_file'
11
+ require 'spaceship/tunes/user_detail'
8
12
  require 'spaceship/tunes/app_screenshot'
9
13
  require 'spaceship/tunes/language_converter'
10
14
  require 'spaceship/tunes/build'
11
15
  require 'spaceship/tunes/processing_build'
12
16
  require 'spaceship/tunes/build_train'
17
+ require 'spaceship/tunes/device_type'
18
+ require 'spaceship/tunes/app_trailer'
13
19
  require 'spaceship/tunes/tester'
14
20
  require 'spaceship/tunes/app_details'
21
+
22
+ # File Uploads
23
+ require 'spaceship/du/utilities'
24
+ require 'spaceship/du/upload_file'
25
+ require 'spaceship/du/du_client'
@@ -1,15 +1,32 @@
1
- # rubocop:disable Metrics/ClassLength
2
1
  module Spaceship
2
+ # rubocop:disable Metrics/ClassLength
3
3
  class TunesClient < Spaceship::Client
4
-
5
4
  # ITunesConnectError is only thrown when iTunes Connect raises an exception
6
5
  class ITunesConnectError < StandardError
7
6
  end
8
7
 
9
- # ITunesConnectNoChangesError is thrown when the only error is that there were no changes
10
- # usually those errors are irrelevant
11
- class ITunesConnectNoChangesError < ITunesConnectError
8
+ attr_reader :du_client
9
+
10
+ def initialize
11
+ super
12
+
13
+ @du_client = DUClient.new
14
+ end
15
+
16
+ class << self
17
+ # trailer preview screenshots are required to have a specific size
18
+ def video_preview_resolution_for(device, is_portrait)
19
+ resolutions = {
20
+ 'iphone4' => [1136, 640],
21
+ 'iphone6' => [1334, 750],
22
+ 'iphone6Plus' => [2208, 1242],
23
+ 'ipad' => [1024, 768]
24
+ }
12
25
 
26
+ r = resolutions[device]
27
+ r = [r[1], r[0]] if is_portrait
28
+ r
29
+ end
13
30
  end
14
31
 
15
32
  #####################################################
@@ -90,8 +107,8 @@ module Spaceship
90
107
  data = raw['data'] || raw # sometimes it's with data, sometimes it isn't
91
108
 
92
109
  if data.fetch('sectionErrorKeys', []).count == 0 and
93
- data.fetch('sectionInfoKeys', []).count == 0 and
94
- data.fetch('sectionWarningKeys', []).count == 0
110
+ data.fetch('sectionInfoKeys', []).count == 0 and
111
+ data.fetch('sectionWarningKeys', []).count == 0
95
112
 
96
113
  logger.debug("Request was successful")
97
114
  end
@@ -125,8 +142,7 @@ module Spaceship
125
142
 
126
143
  if errors.count > 0 # they are separated by `.` by default
127
144
  if errors.count == 1 and errors.first == "You haven't made any changes."
128
- # This is a special error for which we throw a separate exception
129
- raise ITunesConnectNoChangesError.new, errors.first
145
+ # This is a special error which we really don't care about
130
146
  else
131
147
  raise ITunesConnectError.new, errors.join(' ')
132
148
  end
@@ -186,7 +202,7 @@ module Spaceship
186
202
  data['newApp']['name'] = { value: name }
187
203
  data['newApp']['bundleId']['value'] = bundle_id
188
204
  data['newApp']['primaryLanguage']['value'] = primary_language || 'English'
189
- data['newApp']['vendorId'] = {value: sku }
205
+ data['newApp']['vendorId'] = { value: sku }
190
206
  data['newApp']['bundleIdSuffix']['value'] = bundle_id_suffix
191
207
  data['companyName']['value'] = company_name if company_name
192
208
  data['newApp']['appType'] = app_type
@@ -255,6 +271,160 @@ module Spaceship
255
271
  handle_itc_response(r.body)
256
272
  end
257
273
 
274
+ #####################################################
275
+ # @!group Pricing
276
+ #####################################################
277
+
278
+ def update_price_tier!(app_id, price_tier)
279
+ r = request(:get, "ra/apps/#{app_id}/pricing/intervals")
280
+ data = parse_response(r, 'data')
281
+
282
+ first_price = (data["pricingIntervalsFieldTO"]["value"] || []).count == 0 # first price
283
+ data["pricingIntervalsFieldTO"]["value"] ||= []
284
+ data["pricingIntervalsFieldTO"]["value"] << {} if data["pricingIntervalsFieldTO"]["value"].count == 0
285
+ data["pricingIntervalsFieldTO"]["value"].first["tierStem"] = price_tier.to_s
286
+
287
+ effective_date = (first_price ? nil : Time.now.to_i * 1000)
288
+ data["pricingIntervalsFieldTO"]["value"].first["priceTierEffectiveDate"] = effective_date
289
+ data["pricingIntervalsFieldTO"]["value"].first["priceTierEndDate"] = nil
290
+ data["countriesChanged"] = first_price
291
+ data["theWorld"] = true
292
+
293
+ if first_price # first price, need to set all countries
294
+ data["countries"] = supported_countries.collect do |c|
295
+ c.delete('region') # we don't care about le region
296
+ c
297
+ end
298
+ end
299
+
300
+ # send the changes back to Apple
301
+ r = request(:post) do |req|
302
+ req.url "ra/apps/#{app_id}/pricing/intervals"
303
+ req.body = data.to_json
304
+ req.headers['Content-Type'] = 'application/json'
305
+ end
306
+ handle_itc_response(r.body)
307
+ end
308
+
309
+ def price_tier(app_id)
310
+ r = request(:get, "ra/apps/#{app_id}/pricing/intervals")
311
+ data = parse_response(r, 'data')
312
+
313
+ begin
314
+ data["pricingIntervalsFieldTO"]["value"].first["tierStem"]
315
+ rescue
316
+ nil
317
+ end
318
+ end
319
+
320
+ # An array of supported countries
321
+ # [{
322
+ # "code": "AL",
323
+ # "name": "Albania",
324
+ # "region": "Europe"
325
+ # }, {
326
+ # ...
327
+ def supported_countries
328
+ r = request(:get, "ra/apps/pricing/supportedCountries")
329
+ parse_response(r, 'data')
330
+ end
331
+
332
+ #####################################################
333
+ # @!group App Icons
334
+ #####################################################
335
+ # Uploads a large icon
336
+ # @param app_version (AppVersion): The version of your app
337
+ # @param upload_image (UploadFile): The icon to upload
338
+ # @return [JSON] the response
339
+ def upload_large_icon(app_version, upload_image)
340
+ raise "app_version is required" unless app_version
341
+ raise "upload_image is required" unless upload_image
342
+
343
+ du_client.upload_large_icon(app_version, upload_image, content_provider_id, sso_token_for_image)
344
+ end
345
+
346
+ # Uploads a watch icon
347
+ # @param app_version (AppVersion): The version of your app
348
+ # @param upload_image (UploadFile): The icon to upload
349
+ # @return [JSON] the response
350
+ def upload_watch_icon(app_version, upload_image)
351
+ raise "app_version is required" unless app_version
352
+ raise "upload_image is required" unless upload_image
353
+
354
+ du_client.upload_watch_icon(app_version, upload_image, content_provider_id, sso_token_for_image)
355
+ end
356
+
357
+ # Uploads a screenshot
358
+ # @param app_version (AppVersion): The version of your app
359
+ # @param upload_image (UploadFile): The image to upload
360
+ # @param device (string): The target device
361
+ # @return [JSON] the response
362
+ def upload_screenshot(app_version, upload_image, device)
363
+ raise "app_version is required" unless app_version
364
+ raise "upload_image is required" unless upload_image
365
+ raise "device is required" unless device
366
+
367
+ du_client.upload_screenshot(app_version, upload_image, content_provider_id, sso_token_for_image, device)
368
+ end
369
+
370
+ # Uploads the transit app file
371
+ # @param app_version (AppVersion): The version of your app
372
+ # @param upload_file (UploadFile): The image to upload
373
+ # @return [JSON] the response
374
+ def upload_geojson(app_version, upload_file)
375
+ raise "app_version is required" unless app_version
376
+ raise "upload_file is required" unless upload_file
377
+
378
+ du_client.upload_geojson(app_version, upload_file, content_provider_id, sso_token_for_image)
379
+ end
380
+
381
+ # Uploads the transit app file
382
+ # @param app_version (AppVersion): The version of your app
383
+ # @param upload_trailer (UploadFile): The trailer to upload
384
+ # @return [JSON] the response
385
+ def upload_trailer(app_version, upload_trailer)
386
+ raise "app_version is required" unless app_version
387
+ raise "upload_trailer is required" unless upload_trailer
388
+
389
+ du_client.upload_trailer(app_version, upload_trailer, content_provider_id, sso_token_for_video)
390
+ end
391
+
392
+ # Uploads the trailer preview
393
+ # @param app_version (AppVersion): The version of your app
394
+ # @param upload_trailer_preview (UploadFile): The trailer preview to upload
395
+ # @return [JSON] the response
396
+ def upload_trailer_preview(app_version, upload_trailer_preview)
397
+ raise "app_version is required" unless app_version
398
+ raise "upload_trailer_preview is required" unless upload_trailer_preview
399
+
400
+ du_client.upload_trailer_preview(app_version, upload_trailer_preview, content_provider_id, sso_token_for_image)
401
+ end
402
+
403
+ # Fetches the App Version Reference information from ITC
404
+ # @return [AppVersionRef] the response
405
+ def ref_data
406
+ r = request(:get, '/WebObjects/iTunesConnect.woa/ra/apps/version/ref')
407
+ data = parse_response(r, 'data')
408
+ Spaceship::Tunes::AppVersionRef.factory(data)
409
+ end
410
+
411
+ # Fetches the User Detail information from ITC
412
+ # @return [UserDetail] the response
413
+ def user_detail_data
414
+ r = request(:get, '/WebObjects/iTunesConnect.woa/ra/user/detail')
415
+ data = parse_response(r, 'data')
416
+ Spaceship::Tunes::UserDetail.factory(data)
417
+ end
418
+
419
+ #####################################################
420
+ # @!group CandiateBuilds
421
+ #####################################################
422
+
423
+ def candidate_builds(app_id, version_id)
424
+ r = request(:get, "ra/apps/#{app_id}/versions/#{version_id}/candidateBuilds")
425
+ parse_response(r, 'data')['builds']
426
+ end
427
+
258
428
  #####################################################
259
429
  # @!group Build Trains
260
430
  #####################################################
@@ -287,6 +457,7 @@ module Spaceship
287
457
  handle_itc_response(r.body)
288
458
  end
289
459
 
460
+ # rubocop:disable Metrics/AbcSize
290
461
  def submit_testflight_build_for_review!( # Required:
291
462
  app_id: nil,
292
463
  train: nil,
@@ -356,16 +527,31 @@ module Spaceship
356
527
  handle_itc_response(r.body)
357
528
  end
358
529
  end
530
+ # rubocop:enable Metrics/AbcSize
359
531
 
360
532
  #####################################################
361
533
  # @!group Submit for Review
362
534
  #####################################################
363
535
 
364
- def send_app_submission(app_id, data, stage)
536
+ def prepare_app_submissions(app_id, version)
365
537
  raise "app_id is required" unless app_id
538
+ raise "version is required" unless version
539
+
540
+ r = request(:get) do |req|
541
+ req.url "ra/apps/#{app_id}/versions/#{version}/submit/summary"
542
+ req.headers['Content-Type'] = 'application/json'
543
+ end
366
544
 
545
+ handle_itc_response(r.body)
546
+ parse_response(r, 'data')
547
+ end
548
+
549
+ def send_app_submission(app_id, data)
550
+ raise "app_id is required" unless app_id
551
+
552
+ # ra/apps/1039164429/version/submit/complete
367
553
  r = request(:post) do |req|
368
- req.url "ra/apps/#{app_id}/version/submit/#{stage}"
554
+ req.url "ra/apps/#{app_id}/version/submit/complete"
369
555
  req.body = data.to_json
370
556
  req.headers['Content-Type'] = 'application/json'
371
557
  end
@@ -463,6 +649,21 @@ module Spaceship
463
649
 
464
650
  private
465
651
 
652
+ # the contentProviderIr found in the UserDetail instance
653
+ def content_provider_id
654
+ user_detail_data.content_provider_id
655
+ end
656
+
657
+ # the ssoTokenForImage found in the AppVersionRef instance
658
+ def sso_token_for_image
659
+ ref_data.sso_token_for_image
660
+ end
661
+
662
+ # the ssoTokenForVideo found in the AppVersionRef instance
663
+ def sso_token_for_video
664
+ ref_data.sso_token_for_video
665
+ end
666
+
466
667
  def update_tester_from_app!(tester, app_id, testing)
467
668
  url = tester.class.url(app_id)[:update_by_app]
468
669
  data = {
@@ -494,5 +695,5 @@ module Spaceship
494
695
  handle_itc_response(data)
495
696
  end
496
697
  end
698
+ # rubocop:enable Metrics/ClassLength
497
699
  end
498
- # rubocop:enable Metrics/ClassLength