ZAPKDownloader 1.0.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 +7 -0
- data/bin/ZAPKDownloader +91 -0
- data/lib/GoogleAPI.rb +103 -0
- metadata +74 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: b1b3fdd672542121466f767a3a574cac3c5806368e78d088ed72ab9b4ae20b5c
|
4
|
+
data.tar.gz: df88d1b46bb54ff9fed0dbeb1e0fb00edab59c135d2903876107e8b0561671fd
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: f613f1a1c806d89942b696cdb41a90c63cce71197d5af5656618a71459aa148647019ab3c3c5166e66cf5d77fdf265d88da2cd29a005d65a83866a33e988e5e0
|
7
|
+
data.tar.gz: 430d21c12bba8db930041888b9dfded34dba2d86cfb24f75d1bff86af089d3d5b4496bfb71d388ebafebc510214cc06f2078a0638355ea3536c7c4bd70ab488a
|
data/bin/ZAPKDownloader
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# -*- coding: utf-8 -*-
|
3
|
+
|
4
|
+
$lib = File.expand_path('../lib', File.dirname(__FILE__))
|
5
|
+
$LOAD_PATH.unshift($lib)
|
6
|
+
|
7
|
+
require "GoogleAPI"
|
8
|
+
require "optparse"
|
9
|
+
require "yaml"
|
10
|
+
require "fileutils"
|
11
|
+
|
12
|
+
class Main
|
13
|
+
|
14
|
+
attr_accessor :googleAPI
|
15
|
+
|
16
|
+
def initialize
|
17
|
+
|
18
|
+
ARGV << '-h' if ARGV.empty?
|
19
|
+
|
20
|
+
inputServiceAccount = nil
|
21
|
+
inputOutputFilePath = "."
|
22
|
+
inputOutputFileName = "Universal.apk"
|
23
|
+
inputPackageName = nil
|
24
|
+
inputVersionCode = nil
|
25
|
+
OptionParser.new do |opts|
|
26
|
+
opts.banner = "Usage: ZAPKDownloader [options]"
|
27
|
+
opts.on('-s', '--serviceAccount SERVICEACCOUNT', 'xxxx@xxx.iam.gserviceaccount.com.json') do |serviceAccount|
|
28
|
+
inputServiceAccount = serviceAccount
|
29
|
+
end
|
30
|
+
|
31
|
+
opts.on('-p', '--path FILEPATH', '/Users/zhgchgli/Downloads') do |filePath|
|
32
|
+
inputOutputFilePath = filePath
|
33
|
+
end
|
34
|
+
|
35
|
+
opts.on('-f', '--fileName FILENAME', '/Users/zhgchgli/Downloads') do |fileName|
|
36
|
+
inputOutputFileName = fileName
|
37
|
+
end
|
38
|
+
|
39
|
+
opts.on('-n', '--packageName PACKAGENAME', 'com.zhgchg') do |packageName|
|
40
|
+
inputPackageName = packageName
|
41
|
+
end
|
42
|
+
|
43
|
+
opts.on('-v', '--versionCode VERSIONCODE', '1203') do |versionCode|
|
44
|
+
inputVersionCode = versionCode
|
45
|
+
end
|
46
|
+
end.parse!
|
47
|
+
|
48
|
+
|
49
|
+
if inputServiceAccount.nil?
|
50
|
+
puts "please specify your service account json auth file. e.g. -f xxxx@xxx.iam.gserviceaccount.com.json"
|
51
|
+
return
|
52
|
+
end
|
53
|
+
|
54
|
+
if inputPackageName.nil?
|
55
|
+
puts "please specify your android app package name. e.g. -n com.zhgchg"
|
56
|
+
return
|
57
|
+
end
|
58
|
+
|
59
|
+
if inputVersionCode.nil?
|
60
|
+
puts "please specify your android app's version code that you want to download. e.g. -v 1203"
|
61
|
+
return
|
62
|
+
end
|
63
|
+
|
64
|
+
@googleAPI = GoogleAPI.new(inputServiceAccount, ["https://www.googleapis.com/auth/androidpublisher"])
|
65
|
+
apks = googleAPI.request("https://androidpublisher.googleapis.com/androidpublisher/v3/applications/#{inputPackageName}/generatedApks/#{inputVersionCode}")
|
66
|
+
|
67
|
+
downloadId = apks["generatedApks"][0]["generatedUniversalApk"]["downloadId"]
|
68
|
+
downloadURL = "https://androidpublisher.googleapis.com/androidpublisher/v3/applications/#{inputPackageName}/generatedApks/1327/downloads/#{downloadId}:download?alt=media"
|
69
|
+
|
70
|
+
puts "PackageName:#{inputPackageName}, versionCode:#{inputVersionCode}"
|
71
|
+
puts "#{inputOutputFileName} Downloading..."
|
72
|
+
|
73
|
+
uri = URI(downloadURL)
|
74
|
+
https = Net::HTTP.new(uri.host, uri.port)
|
75
|
+
https.use_ssl = true
|
76
|
+
|
77
|
+
request = Net::HTTP::Get.new(uri)
|
78
|
+
request['Authorization'] = "Bearer #{googleAPI.token}";
|
79
|
+
|
80
|
+
response = https.request(request).read_body
|
81
|
+
|
82
|
+
fileFullPath = "#{inputOutputFilePath}/#{inputOutputFileName}"
|
83
|
+
File.delete(fileFullPath) if File.exist?(fileFullPath)
|
84
|
+
|
85
|
+
open(fileFullPath, "wb") { |file|
|
86
|
+
file.write(response)
|
87
|
+
}
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
Main.new()
|
data/lib/GoogleAPI.rb
ADDED
@@ -0,0 +1,103 @@
|
|
1
|
+
$lib = File.expand_path('../lib', File.dirname(__FILE__))
|
2
|
+
|
3
|
+
require "jwt"
|
4
|
+
require "time"
|
5
|
+
require "net/http"
|
6
|
+
|
7
|
+
class GoogleAPI
|
8
|
+
|
9
|
+
attr_accessor :keyContent, :keyID, :tokenURI, :clientEmail, :scopes, :token
|
10
|
+
|
11
|
+
def initialize(keyFilePath, scopes = [])
|
12
|
+
|
13
|
+
keyFileContent = JSON.parse(File.read(keyFilePath))
|
14
|
+
|
15
|
+
@keyContent = keyFileContent["private_key"]
|
16
|
+
@keyID = keyFileContent["private_key_id"]
|
17
|
+
@clientEmail = keyFileContent["client_email"]
|
18
|
+
@tokenURI = keyFileContent["token_uri"]
|
19
|
+
|
20
|
+
@scopes = scopes
|
21
|
+
|
22
|
+
@token = generateJWT()
|
23
|
+
|
24
|
+
puts "[GoogleAPI] Init Success."
|
25
|
+
end
|
26
|
+
|
27
|
+
def request(url, method = "GET", data = nil, retryCount = 0)
|
28
|
+
uri = URI(url)
|
29
|
+
https = Net::HTTP.new(uri.host, uri.port)
|
30
|
+
https.use_ssl = true
|
31
|
+
|
32
|
+
request = Net::HTTP::Get.new(uri)
|
33
|
+
if method.upcase == "POST"
|
34
|
+
request = Net::HTTP::Post.new(uri)
|
35
|
+
if !data.nil?
|
36
|
+
request['Content-Type'] = 'application/json'
|
37
|
+
request.body = JSON.dump(data)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
request['Authorization'] = "Bearer #{token}";
|
42
|
+
|
43
|
+
response = https.request(request).read_body
|
44
|
+
result = JSON.parse(response)
|
45
|
+
|
46
|
+
if !result["error"].nil?
|
47
|
+
# Quota exceeded for quota metric 'Write requests' and limit 'Write requests per minute per user
|
48
|
+
if result["error"]["code"] == 429
|
49
|
+
puts "[GoogleAPI] Reached Rate Limited, sleep 30 secs..."
|
50
|
+
sleep(30)
|
51
|
+
return request(url, method, data, 0)
|
52
|
+
else
|
53
|
+
if retryCount >= 10
|
54
|
+
raise "Could not connect to #{tokenURI}, key id: #{keyID}, error message: #{response}"
|
55
|
+
else
|
56
|
+
@token = generateJWT()
|
57
|
+
message = "JWT Invalid, retry. (#{retryCount + 1})"
|
58
|
+
puts "[GoogleAPI] #{message}"
|
59
|
+
return request(url, method, data, retryCount + 1)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
else
|
63
|
+
return result
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
def generateJWT(retryCount = 0)
|
70
|
+
payload = {
|
71
|
+
iss: clientEmail,
|
72
|
+
sub: clientEmail,
|
73
|
+
scope: scopes.join(' '),
|
74
|
+
aud: tokenURI,
|
75
|
+
iat: Time.now.to_i,
|
76
|
+
exp: Time.now.to_i + 60*20
|
77
|
+
}
|
78
|
+
|
79
|
+
rsa_private = OpenSSL::PKey::RSA.new(keyContent)
|
80
|
+
token = JWT.encode payload, rsa_private, 'RS256', header_fields = {kid:keyID, typ:"JWT"}
|
81
|
+
|
82
|
+
uri = URI(tokenURI)
|
83
|
+
https = Net::HTTP.new(uri.host, uri.port)
|
84
|
+
https.use_ssl = true
|
85
|
+
request = Net::HTTP::Post.new(uri)
|
86
|
+
request.body = "grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&assertion=#{token}"
|
87
|
+
|
88
|
+
response = https.request(request).read_body
|
89
|
+
result = JSON.parse(response)
|
90
|
+
|
91
|
+
if result["access_token"].nil?
|
92
|
+
if retryCount >= 10
|
93
|
+
raise "Could not generate google api JWT, key id: #{keyID}, error message: #{response}"
|
94
|
+
else
|
95
|
+
message = "Could not generate google api JWT, retry. (#{retryCount + 1})"
|
96
|
+
puts "[GoogleAPI] #{message}"
|
97
|
+
return generateJWT(retryCount + 1)
|
98
|
+
end
|
99
|
+
else
|
100
|
+
return result["access_token"]
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
metadata
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ZAPKDownloader
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- ZhgChgLi
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2023-04-14 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: net-http
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.1.0
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.1.0
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: jwt
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 2.4.1
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 2.4.1
|
41
|
+
description: ZAPKDownloader
|
42
|
+
email:
|
43
|
+
executables:
|
44
|
+
- ZAPKDownloader
|
45
|
+
extensions: []
|
46
|
+
extra_rdoc_files: []
|
47
|
+
files:
|
48
|
+
- bin/ZAPKDownloader
|
49
|
+
- lib/GoogleAPI.rb
|
50
|
+
homepage: https://github.com/ZhgChgLi/ZAPKDownloader
|
51
|
+
licenses:
|
52
|
+
- MIT
|
53
|
+
metadata: {}
|
54
|
+
post_install_message:
|
55
|
+
rdoc_options: []
|
56
|
+
require_paths:
|
57
|
+
- lib
|
58
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - ">="
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '0'
|
63
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - ">="
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '0'
|
68
|
+
requirements: []
|
69
|
+
rubygems_version: 3.0.3
|
70
|
+
signing_key:
|
71
|
+
specification_version: 4
|
72
|
+
summary: A tool that helps developers to download their uploaded APK files from the
|
73
|
+
Google Play Console via CLI.
|
74
|
+
test_files: []
|