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.
- checksums.yaml +4 -4
- data/README.md +2 -0
- data/lib/spaceship/base.rb +3 -1
- data/lib/spaceship/client.rb +7 -3
- data/lib/spaceship/du/du_client.rb +100 -0
- data/lib/spaceship/du/upload_file.rb +45 -0
- data/lib/spaceship/du/utilities.rb +75 -0
- data/lib/spaceship/helper/net_http_generic_request.rb +5 -3
- data/lib/spaceship/portal/app.rb +0 -1
- data/lib/spaceship/portal/app_service.rb +3 -3
- data/lib/spaceship/portal/portal_client.rb +1 -2
- data/lib/spaceship/portal/provisioning_profile.rb +1 -2
- data/lib/spaceship/tunes/app_details.rb +6 -0
- data/lib/spaceship/tunes/app_image.rb +53 -0
- data/lib/spaceship/tunes/app_screenshot.rb +6 -17
- data/lib/spaceship/tunes/app_submission.rb +2 -14
- data/lib/spaceship/tunes/app_trailer.rb +70 -0
- data/lib/spaceship/tunes/app_version.rb +293 -30
- data/lib/spaceship/tunes/app_version_ref.rb +19 -0
- data/lib/spaceship/tunes/application.rb +12 -2
- data/lib/spaceship/tunes/build.rb +4 -1
- data/lib/spaceship/tunes/build_train.rb +0 -1
- data/lib/spaceship/tunes/device_type.rb +14 -0
- data/lib/spaceship/tunes/processing_build.rb +0 -1
- data/lib/spaceship/tunes/tester.rb +0 -2
- data/lib/spaceship/tunes/transit_app_file.rb +27 -0
- data/lib/spaceship/tunes/tunes.rb +11 -0
- data/lib/spaceship/tunes/tunes_client.rb +214 -13
- data/lib/spaceship/tunes/user_detail.rb +17 -0
- data/lib/spaceship/version.rb +1 -1
- metadata +25 -2
@@ -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
|
-
|
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
|
@@ -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
|
-
|
10
|
-
|
11
|
-
|
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
|
-
|
94
|
-
|
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
|
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
|
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
|
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
|