ZAPKDownloader 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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: []