spaceship 0.7.0 → 0.9.0

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.
@@ -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