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.
Files changed (4) hide show
  1. checksums.yaml +7 -0
  2. data/bin/ZAPKDownloader +91 -0
  3. data/lib/GoogleAPI.rb +103 -0
  4. 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
@@ -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: []