spaceship 0.31.10 → 0.32.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/spaceship/client.rb +11 -1
- data/lib/spaceship/portal/portal_client.rb +37 -37
- data/lib/spaceship/tunes/app_ratings.rb +107 -0
- data/lib/spaceship/tunes/application.rb +6 -0
- data/lib/spaceship/tunes/tunes.rb +1 -0
- data/lib/spaceship/tunes/tunes_client.rb +10 -0
- data/lib/spaceship/version.rb +1 -1
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 446e6df28c4691d7dea2ecb1d0e9ef8543c418a7
|
4
|
+
data.tar.gz: 7ea38ddea78ce1183853b9e619b724ada951a8da
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 27fc2dd359daf036dc9e0d22274ccf99ff9336239a765d9ba1c0ff18a23f7d2062498e345f4d74cc08d29a9c52ed24ad82ebe7a09c72df7fb7635f90d4031f1c
|
7
|
+
data.tar.gz: e28fd65e2a39cac2d175a3f615bde469ba0f30b00c0af1700345fd9046ec93bba459c157dbbfe39ea323e2f16d9c6dec58ff2818274d1dc35e94e5cc3800d0e7
|
data/lib/spaceship/client.rb
CHANGED
@@ -310,6 +310,11 @@ module Spaceship
|
|
310
310
|
|
311
311
|
def itc_service_key
|
312
312
|
return @service_key if @service_key
|
313
|
+
|
314
|
+
# Check if we have a local cache of the key
|
315
|
+
itc_service_key_path = File.expand_path("~/Library/Caches/spaceship_itc_service_key.txt")
|
316
|
+
return File.read(itc_service_key_path) if File.exist?(itc_service_key_path)
|
317
|
+
|
313
318
|
# Some customers in Asia have had trouble with the CDNs there that cache and serve this content, leading
|
314
319
|
# to "buffer error (Zlib::BufError)" from deep in the Ruby HTTP stack. Setting this header requests that
|
315
320
|
# the content be served only as plain-text, which seems to work around their problem, while not affecting
|
@@ -319,7 +324,12 @@ module Spaceship
|
|
319
324
|
headers = { 'Accept-Encoding' => 'identity' }
|
320
325
|
# We need a service key from a JS file to properly auth
|
321
326
|
js = request(:get, "https://itunesconnect.apple.com/itc/static-resources/controllers/login_cntrl.js", nil, headers)
|
322
|
-
@service_key
|
327
|
+
@service_key = js.body.match(/itcServiceKey = '(.*)'/)[1]
|
328
|
+
|
329
|
+
# Cache the key locally
|
330
|
+
File.write(itc_service_key_path, @service_key)
|
331
|
+
|
332
|
+
return @service_key
|
323
333
|
end
|
324
334
|
|
325
335
|
#####################################################
|
@@ -9,29 +9,6 @@ module Spaceship
|
|
9
9
|
"https://developer.apple.com/services-account/#{PROTOCOL_VERSION}/"
|
10
10
|
end
|
11
11
|
|
12
|
-
# Fetches the latest API Key from the Apple Dev Portal
|
13
|
-
def api_key
|
14
|
-
cache_path = File.expand_path("~/Library/Caches/spaceship_api_key.txt")
|
15
|
-
begin
|
16
|
-
cached = File.read(cache_path)
|
17
|
-
rescue Errno::ENOENT
|
18
|
-
end
|
19
|
-
return cached if cached
|
20
|
-
|
21
|
-
landing_url = "https://developer.apple.com/account/"
|
22
|
-
logger.info("GET: " + landing_url)
|
23
|
-
headers = @client.get(landing_url).headers
|
24
|
-
results = headers['location'].match(/.*appIdKey=(\h+)/)
|
25
|
-
if (results || []).length > 1
|
26
|
-
api_key = results[1]
|
27
|
-
FileUtils.mkdir_p(File.dirname(cache_path))
|
28
|
-
File.write(cache_path, api_key) if api_key.length == 64
|
29
|
-
return api_key
|
30
|
-
else
|
31
|
-
raise "Could not find latest API Key from the Dev Portal - the server might be slow right now"
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
12
|
def send_login_request(user, password)
|
36
13
|
response = send_shared_login_request(user, password)
|
37
14
|
return response if self.cookie.include?("myacinfo")
|
@@ -353,9 +330,9 @@ module Spaceship
|
|
353
330
|
end
|
354
331
|
|
355
332
|
def create_provisioning_profile!(name, distribution_method, app_id, certificate_ids, device_ids, mac: false, sub_platform: nil)
|
356
|
-
|
357
|
-
|
358
|
-
|
333
|
+
ensure_csrf(Spaceship::ProvisioningProfile) do
|
334
|
+
fetch_csrf_token_for_provisioning
|
335
|
+
end
|
359
336
|
|
360
337
|
params = {
|
361
338
|
teamId: team_id,
|
@@ -372,9 +349,9 @@ module Spaceship
|
|
372
349
|
end
|
373
350
|
|
374
351
|
def download_provisioning_profile(profile_id, mac: false)
|
375
|
-
|
376
|
-
|
377
|
-
|
352
|
+
ensure_csrf(Spaceship::ProvisioningProfile) do
|
353
|
+
fetch_csrf_token_for_provisioning
|
354
|
+
end
|
378
355
|
|
379
356
|
r = request(:get, "account/#{platform_slug(mac)}/profile/downloadProfileContent", {
|
380
357
|
teamId: team_id,
|
@@ -389,9 +366,9 @@ module Spaceship
|
|
389
366
|
end
|
390
367
|
|
391
368
|
def delete_provisioning_profile!(profile_id, mac: false)
|
392
|
-
|
393
|
-
|
394
|
-
|
369
|
+
ensure_csrf(Spaceship::ProvisioningProfile) do
|
370
|
+
fetch_csrf_token_for_provisioning
|
371
|
+
end
|
395
372
|
|
396
373
|
r = request(:post, "account/#{platform_slug(mac)}/profile/deleteProvisioningProfile.action", {
|
397
374
|
teamId: team_id,
|
@@ -401,9 +378,9 @@ module Spaceship
|
|
401
378
|
end
|
402
379
|
|
403
380
|
def repair_provisioning_profile!(profile_id, name, distribution_method, app_id, certificate_ids, device_ids, mac: false)
|
404
|
-
|
405
|
-
|
406
|
-
|
381
|
+
ensure_csrf(Spaceship::ProvisioningProfile) do
|
382
|
+
fetch_csrf_token_for_provisioning
|
383
|
+
end
|
407
384
|
|
408
385
|
r = request(:post, "account/#{platform_slug(mac)}/profile/regenProvisioningProfile.action", {
|
409
386
|
teamId: team_id,
|
@@ -418,6 +395,27 @@ module Spaceship
|
|
418
395
|
parse_response(r, 'provisioningProfile')
|
419
396
|
end
|
420
397
|
|
398
|
+
# We need a custom way to fetch the csrf token for the provisioning profile requests, since
|
399
|
+
# we use a separate API endpoint (host of Xcode API) to fetch the provisioning profiles
|
400
|
+
# All we do is fetch one profile (if exists) to get a valid csrf token with its time stamp
|
401
|
+
# This method is being called from all requests that modify, create or downloading provisioning
|
402
|
+
# profiles.
|
403
|
+
# Source https://github.com/fastlane/fastlane/issues/5903
|
404
|
+
def fetch_csrf_token_for_provisioning(mac: false)
|
405
|
+
req = request(:post) do |r|
|
406
|
+
r.url "https://developer.apple.com/services-account/#{PROTOCOL_VERSION}/account/#{platform_slug(mac)}/profile/listProvisioningProfiles.action"
|
407
|
+
r.params = {
|
408
|
+
teamId: team_id,
|
409
|
+
pageSize: 1,
|
410
|
+
pageNumber: 1,
|
411
|
+
sort: "name=asc"
|
412
|
+
}
|
413
|
+
end
|
414
|
+
|
415
|
+
parse_response(req, 'provisioningProfiles')
|
416
|
+
return nil
|
417
|
+
end
|
418
|
+
|
421
419
|
private
|
422
420
|
|
423
421
|
# This is a cache of entity type (App, AppGroup, Certificate, Device) to csrf_tokens
|
@@ -428,6 +426,8 @@ module Spaceship
|
|
428
426
|
# Ensures that there are csrf tokens for the appropriate entity type
|
429
427
|
# Relies on store_csrf_tokens to set csrf_tokens to the appropriate value
|
430
428
|
# then stores that in the correct place in cache
|
429
|
+
# This method also takes a block, if you want to send a custom request, instead of
|
430
|
+
# calling `.all` on the given klass. This is used for provisioning profiles.
|
431
431
|
def ensure_csrf(klass)
|
432
432
|
if csrf_cache[klass]
|
433
433
|
self.csrf_tokens = csrf_cache[klass]
|
@@ -438,13 +438,13 @@ module Spaceship
|
|
438
438
|
|
439
439
|
# If we directly create a new resource (e.g. app) without querying anything before
|
440
440
|
# we don't have a valid csrf token, that's why we have to do at least one request
|
441
|
-
klass.all
|
441
|
+
block_given? ? yield : klass.all
|
442
442
|
|
443
443
|
# Update 18th August 2016
|
444
444
|
# For some reason, we have to query the resource twice to actually get a valid csrf_token
|
445
445
|
# I couldn't find out why, the first response does have a valid Set-Cookie header
|
446
446
|
# But it still needs this second request
|
447
|
-
klass.all
|
447
|
+
block_given? ? yield : klass.all
|
448
448
|
|
449
449
|
csrf_cache[klass] = self.csrf_tokens
|
450
450
|
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
module Spaceship
|
2
|
+
module Tunes
|
3
|
+
# Represents app ratings from iTunesConnect
|
4
|
+
class AppRatings < TunesBase
|
5
|
+
# @return (Spaceship::Tunes::Application) A reference to the application
|
6
|
+
# this version is for
|
7
|
+
attr_accessor :application
|
8
|
+
|
9
|
+
# @return (Spaceship::Tunes::AppRatingSummary) A summary of the overall ratings for the application
|
10
|
+
attr_accessor :rating_summary
|
11
|
+
|
12
|
+
# @return (Hash) mapping country codes to a (Spaceship::Tunes::AppRatingSummary) summary of ratings for that country
|
13
|
+
attr_reader :store_fronts
|
14
|
+
|
15
|
+
# @return (Hash) of iTunesConnect version id's to readable version numbers
|
16
|
+
attr_reader :versions
|
17
|
+
|
18
|
+
attr_mapping({
|
19
|
+
'versions' => :versions
|
20
|
+
})
|
21
|
+
|
22
|
+
class << self
|
23
|
+
# Create a new object based on a hash.
|
24
|
+
# This is used to create a new object based on the server response.
|
25
|
+
def factory(attrs)
|
26
|
+
obj = self.new(attrs)
|
27
|
+
|
28
|
+
obj.unfold_rating_summary(attrs['ratings'])
|
29
|
+
obj.unfold_store_fronts(attrs['storeFronts'])
|
30
|
+
|
31
|
+
return obj
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def unfold_rating_summary(attrs)
|
36
|
+
unfolded_rating_summary = AppRatingSummary.new(attrs)
|
37
|
+
instance_variable_set(:@rating_summary, unfolded_rating_summary)
|
38
|
+
end
|
39
|
+
|
40
|
+
def unfold_store_fronts(attrs)
|
41
|
+
unfolded_store_fronts = {}
|
42
|
+
|
43
|
+
attrs.each do |info|
|
44
|
+
unfolded_store_fronts[info['countryCode']] = AppRatingSummary.new(info['ratings'])
|
45
|
+
end
|
46
|
+
|
47
|
+
instance_variable_set(:@store_fronts, unfolded_store_fronts)
|
48
|
+
end
|
49
|
+
|
50
|
+
# @return (Array) of raw hashes representing user reviews for the given store front (and optional versionId)
|
51
|
+
def reviews(store_front, versionId = '')
|
52
|
+
client.get_reviews(application.apple_id, application.platform, store_front, versionId)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
class AppRatingSummary < TunesBase
|
57
|
+
# @return (Integer) total number of reviews recevied
|
58
|
+
attr_reader :review_count
|
59
|
+
|
60
|
+
# @return (Integer) total number of ratings recevied
|
61
|
+
attr_reader :rating_count
|
62
|
+
|
63
|
+
# @return (Integer) total number of one star ratings recevied
|
64
|
+
attr_reader :one_star_rating_count
|
65
|
+
|
66
|
+
# @return (Integer) total number of two star ratings recevied
|
67
|
+
attr_reader :two_star_rating_count
|
68
|
+
|
69
|
+
# @return (Integer) total number of three star ratings recevied
|
70
|
+
attr_reader :three_star_rating_count
|
71
|
+
|
72
|
+
# @return (Integer) total number of four star ratings recevied
|
73
|
+
attr_reader :four_star_rating_count
|
74
|
+
|
75
|
+
# @return (Integer) total number of five star ratings recevied
|
76
|
+
attr_reader :five_star_rating_count
|
77
|
+
|
78
|
+
attr_mapping({
|
79
|
+
'reviewCount' => :review_count,
|
80
|
+
'ratingCount' => :rating_count,
|
81
|
+
'ratingOneCount' => :one_star_rating_count,
|
82
|
+
'ratingTwoCount' => :two_star_rating_count,
|
83
|
+
'ratingThreeCount' => :three_star_rating_count,
|
84
|
+
'ratingFourCount' => :four_star_rating_count,
|
85
|
+
'ratingFiveCount' => :five_star_rating_count
|
86
|
+
})
|
87
|
+
|
88
|
+
class << self
|
89
|
+
# Create a new object based on a hash.
|
90
|
+
# This is used to create a new object based on the server response.
|
91
|
+
def factory(attrs)
|
92
|
+
obj = self.new(attrs)
|
93
|
+
return obj
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# @return (Float) the average rating for this summary (rounded to 2 decimal places)
|
98
|
+
def average_rating
|
99
|
+
((self.one_star_rating_count +
|
100
|
+
(self.two_star_rating_count * 2) +
|
101
|
+
(self.three_star_rating_count * 3) +
|
102
|
+
(self.four_star_rating_count * 4) +
|
103
|
+
(self.five_star_rating_count * 5)) / self.rating_count.to_f).round(2)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -120,6 +120,12 @@ module Spaceship
|
|
120
120
|
client.get_resolution_center(apple_id, platform)
|
121
121
|
end
|
122
122
|
|
123
|
+
def ratings
|
124
|
+
attrs = client.get_rating_summary(apple_id, platform)
|
125
|
+
attrs[:application] = self
|
126
|
+
Tunes::AppRatings.factory(attrs)
|
127
|
+
end
|
128
|
+
|
123
129
|
# kept for backward compatibility
|
124
130
|
# tries to guess the platform of the currently submitted apps
|
125
131
|
# note that as ITC now supports multiple app types, this might break
|
@@ -7,6 +7,7 @@ require 'spaceship/tunes/tunes_client'
|
|
7
7
|
require 'spaceship/tunes/language_item'
|
8
8
|
require 'spaceship/tunes/app_status'
|
9
9
|
require 'spaceship/tunes/app_image'
|
10
|
+
require 'spaceship/tunes/app_ratings'
|
10
11
|
require 'spaceship/tunes/app_version_ref'
|
11
12
|
require 'spaceship/tunes/app_version_history'
|
12
13
|
require 'spaceship/tunes/app_version_states_history'
|
@@ -271,6 +271,16 @@ module Spaceship
|
|
271
271
|
parse_response(r, 'data')
|
272
272
|
end
|
273
273
|
|
274
|
+
def get_rating_summary(app_id, platform, versionId = '')
|
275
|
+
r = request(:get, "ra/apps/#{app_id}/reviews/summary?platform=#{platform}&versionId=#{versionId}")
|
276
|
+
parse_response(r, 'data')
|
277
|
+
end
|
278
|
+
|
279
|
+
def get_reviews(app_id, platform, storefront, versionId = '')
|
280
|
+
r = request(:get, "ra/apps/#{app_id}/reviews?platform=#{platform}&storefront=#{storefront}&versionId=#{versionId}")
|
281
|
+
parse_response(r, 'data')['reviews']
|
282
|
+
end
|
283
|
+
|
274
284
|
#####################################################
|
275
285
|
# @!group AppVersions
|
276
286
|
#####################################################
|
data/lib/spaceship/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: spaceship
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.32.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Felix Krause
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2016-08-
|
12
|
+
date: 2016-08-29 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: credentials_manager
|
@@ -341,6 +341,7 @@ files:
|
|
341
341
|
- lib/spaceship/portal/ui/select_team.rb
|
342
342
|
- lib/spaceship/tunes/app_details.rb
|
343
343
|
- lib/spaceship/tunes/app_image.rb
|
344
|
+
- lib/spaceship/tunes/app_ratings.rb
|
344
345
|
- lib/spaceship/tunes/app_screenshot.rb
|
345
346
|
- lib/spaceship/tunes/app_status.rb
|
346
347
|
- lib/spaceship/tunes/app_submission.rb
|
@@ -391,7 +392,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
391
392
|
version: '0'
|
392
393
|
requirements: []
|
393
394
|
rubyforge_project:
|
394
|
-
rubygems_version: 2.
|
395
|
+
rubygems_version: 2.2.2
|
395
396
|
signing_key:
|
396
397
|
specification_version: 4
|
397
398
|
summary: Ruby library to access the Apple Dev Center and iTunes Connect
|