spaceship 0.7.0 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|