ZReviewTender 1.2.7 → 1.2.8
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 +4 -4
- data/.version +1 -1
- data/lib/AndroidFetcher.rb +149 -33
- data/lib/AppleFetcher.rb +2 -1
- data/lib/Models/AndroidConfig.rb +7 -2
- data/lib/Processors/GoogleTranslateProcessor.rb +83 -11
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8bcc9b0eab8e7b7c5aacfbc1add9a591e860222656d4097b3184011e41f8d93b
|
4
|
+
data.tar.gz: cf092f42c2d81f62394713040fea08748810c283e4f0540f019b381042cf305b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7ddf0c24a5120da15392ca8c2b5053d39b6ae48853ed85e16fb8e445ca76ec3039ff2c97ee02e6015d20641ae697245de715951015397c44de4002d3456494f8
|
7
|
+
data.tar.gz: 03afbee02b7543121a52a645258845780e0fbadf42b7b8ab65f8f9553317447500ee004df953c62735ba82cc584c0e1e3193a07b98f396913ce1217db40eb56b
|
data/.version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.2.
|
1
|
+
1.2.8
|
data/lib/AndroidFetcher.rb
CHANGED
@@ -3,20 +3,19 @@ $lib = File.expand_path('../lib', File.dirname(__FILE__))
|
|
3
3
|
require "Models/Review"
|
4
4
|
require "Helper"
|
5
5
|
require "Models/ReviewFetcher"
|
6
|
+
require "jwt"
|
6
7
|
require "time"
|
7
|
-
require "google/apis/androidpublisher_v3"
|
8
8
|
|
9
9
|
class AndroidFetcher < ReviewFetcher
|
10
10
|
|
11
|
-
attr_accessor :token
|
11
|
+
attr_accessor :token
|
12
12
|
|
13
13
|
def initialize(config)
|
14
14
|
@processors = []
|
15
15
|
@config = config
|
16
16
|
@platform = 'Android'
|
17
|
-
|
18
|
-
@
|
19
|
-
@client.authorization = Google::Auth::ServiceAccountCredentials.make_creds(json_key_io: config.keyContent, scope: 'https://www.googleapis.com/auth/androidpublisher')
|
17
|
+
@logger = ZLogger.new(config.baseExecutePath)
|
18
|
+
@token = generateJWT()
|
20
19
|
|
21
20
|
puts "[AndroidFetcher] Init Success."
|
22
21
|
end
|
@@ -28,43 +27,106 @@ class AndroidFetcher < ReviewFetcher
|
|
28
27
|
puts "[AndroidFetcher] Start execute(), latestCheckTimestamp: #{latestCheckTimestamp}"
|
29
28
|
|
30
29
|
reviews = []
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
30
|
+
|
31
|
+
reviewsInfoLink = "https://androidpublisher.googleapis.com/androidpublisher/v3/applications/#{config.packageName}/reviews"
|
32
|
+
|
33
|
+
# Note: You can retrieve only the reviews that users have created or modified within the last week.
|
34
|
+
# https://developers.google.com/android-publisher/reply-to-reviews#retrieving_a_set_of_reviews
|
35
|
+
|
36
|
+
puts "[AndroidFetcher] Fetch reviews in #{config.packageName}"
|
37
|
+
|
38
|
+
loop do
|
39
|
+
reviewsInfo = request(reviewsInfoLink)
|
40
|
+
reviewsInfoLink = reviewsInfo&.dig("tokenPagination", "nextPageToken")
|
41
|
+
|
42
|
+
customerReviews = reviewsInfo["reviews"]
|
43
|
+
puts "[AndroidFetcher] Fetch reviews in #{config.packageName}, count: #{customerReviews.length}"
|
44
|
+
customerReviews.each do |customerReview|
|
45
|
+
|
46
|
+
customerReviewID = customerReview&.dig("reviewId")
|
47
|
+
customerReviewTitle = nil
|
48
|
+
customerReviewReviewerNickname = customerReview&.dig("authorName")
|
49
|
+
customerReviewVersionString = "unknown"
|
50
|
+
customerReviewPlatform = platform
|
51
|
+
customerReviewCreatedDateTimestamp = 0
|
52
|
+
|
53
|
+
comment = customerReview&.dig("comments", 0, "userComment")
|
54
|
+
if !comment.nil?
|
55
|
+
customerReviewBody = comment&.dig("text")
|
56
|
+
customerReviewTerritory = comment&.dig("reviewerLanguage")
|
57
|
+
|
58
|
+
if !customerReviewBody.nil?
|
59
|
+
customerReviewBody = customerReviewBody.strip
|
60
|
+
end
|
61
|
+
|
62
|
+
customerReviewRating = comment&.dig("starRating")
|
63
|
+
if !customerReviewRating.nil?
|
64
|
+
customerReviewRating = customerReviewRating.to_i
|
65
|
+
end
|
66
|
+
|
67
|
+
customerReviewCreatedDateTimestamp = comment&.dig("lastModified", "seconds")
|
68
|
+
if !customerReviewCreatedDateTimestamp.nil?
|
69
|
+
customerReviewCreatedDateTimestamp = customerReviewCreatedDateTimestamp.to_i
|
70
|
+
end
|
71
|
+
|
72
|
+
androidOSVersion = comment&.dig("androidOsVersion")
|
73
|
+
if !androidOSVersion.nil?
|
74
|
+
customerReviewPlatform = "#{platform} #{androidOSVersion}"
|
75
|
+
end
|
76
|
+
|
77
|
+
versionString = comment&.dig("appVersionName")
|
78
|
+
if !versionString.nil?
|
79
|
+
customerReviewVersionString = "#{versionString}"
|
80
|
+
|
81
|
+
versionCode = comment&.dig("appVersionCode")
|
82
|
+
if !versionCode.nil?
|
83
|
+
customerReviewVersionString = "#{customerReviewVersionString}(#{versionCode})"
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
deviceInfo = []
|
88
|
+
manufacturer = comment&.dig("deviceMetadata", "manufacturer")
|
89
|
+
if !manufacturer.nil?
|
90
|
+
deviceInfo.append(manufacturer)
|
91
|
+
end
|
92
|
+
|
93
|
+
productName = comment&.dig("deviceMetadata", "productName")
|
94
|
+
if !productName.nil?
|
95
|
+
deviceInfo.append(productName)
|
96
|
+
end
|
97
|
+
|
98
|
+
if deviceInfo.length > 0
|
99
|
+
customerReviewTitle = "#{deviceInfo.join("/")}"
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
if latestCheckTimestamp >= customerReviewCreatedDateTimestamp
|
104
|
+
reviewsInfoLink = nil
|
105
|
+
break
|
106
|
+
else
|
107
|
+
url = "https://play.google.com/store/apps/details?id=#{config.packageName}&reviewId=#{customerReviewID}"
|
108
|
+
if !config.accountID.nil? && !config.appID.nil?
|
109
|
+
url = "https://play.google.com/console/developers/#{config.accountID}/app/#{config.appID}/user-feedback/review-details?reviewId=#{customerReviewID}"
|
110
|
+
end
|
111
|
+
reviews.append(Review.new(customerReviewPlatform, customerReviewID, customerReviewReviewerNickname, customerReviewRating, customerReviewTitle, customerReviewBody, customerReviewCreatedDateTimestamp, url, customerReviewVersionString, customerReviewTerritory))
|
112
|
+
|
113
|
+
# init first time, need first review to set as latestCheckTimestamp
|
114
|
+
if latestCheckTimestamp == 0
|
115
|
+
reviewsInfoLink = nil
|
116
|
+
break
|
117
|
+
end
|
49
118
|
end
|
50
119
|
end
|
51
|
-
customerReviewPlatform = "Android #{customerReview.comments[0].user_comment.android_os_version}"
|
52
120
|
|
53
|
-
|
54
|
-
if !config.accountID.nil? && !config.appID.nil?
|
55
|
-
url = "https://play.google.com/console/developers/#{config.accountID}/app/#{config.appID}/user-feedback/review-details?reviewId=#{customerReviewID}"
|
56
|
-
end
|
57
|
-
reviews.append(Review.new(customerReviewPlatform, customerReviewID, customerReviewReviewerNickname, customerReviewRating, customerReviewTitle, customerReviewBody, customerReviewCreatedDateTimestamp, url, customerReviewVersionString, customerReviewTerritory))
|
121
|
+
break if reviewsInfoLink.nil?
|
58
122
|
end
|
59
123
|
|
60
|
-
|
61
|
-
|
62
|
-
puts "[AndroidFetcher] latest reviews count: #{reviews.length}"
|
124
|
+
puts "[AndroidFetcher] Fetch reviews in #{config.packageName}, total reviews count: #{reviews.length}"
|
63
125
|
|
64
126
|
if reviews.length > 0
|
127
|
+
reviews = reviews.sort! { |a, b| a.createdDateTimestamp <=> b.createdDateTimestamp }
|
65
128
|
|
66
129
|
puts "[AndroidFetcher] latest review: #{reviews.last.body}, #{reviews.last.createdDateTimestamp}"
|
67
|
-
|
68
130
|
setPlatformLatestCheckTimestamp(reviews.last.createdDateTimestamp)
|
69
131
|
|
70
132
|
# init first time, send welcome message
|
@@ -76,4 +138,58 @@ class AndroidFetcher < ReviewFetcher
|
|
76
138
|
processReviews(reviews, platform)
|
77
139
|
end
|
78
140
|
end
|
141
|
+
|
142
|
+
private
|
143
|
+
def generateJWT()
|
144
|
+
payload = {
|
145
|
+
iss: config.clientEmail,
|
146
|
+
sub: config.clientEmail,
|
147
|
+
scope: "https://www.googleapis.com/auth/androidpublisher",
|
148
|
+
aud: config.tokenURI,
|
149
|
+
iat: Time.now.to_i,
|
150
|
+
exp: Time.now.to_i + 60*20
|
151
|
+
}
|
152
|
+
|
153
|
+
rsa_private = OpenSSL::PKey::RSA.new(config.keyContent)
|
154
|
+
token = JWT.encode payload, rsa_private, 'RS256', header_fields = {kid:config.keyID, typ:"JWT"}
|
155
|
+
|
156
|
+
uri = URI(config.tokenURI)
|
157
|
+
https = Net::HTTP.new(uri.host, uri.port)
|
158
|
+
https.use_ssl = true
|
159
|
+
request = Net::HTTP::Post.new(uri)
|
160
|
+
request.body = "grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&assertion=#{token}"
|
161
|
+
|
162
|
+
response = https.request(request).read_body
|
163
|
+
result = JSON.parse(response)
|
164
|
+
|
165
|
+
return result["access_token"]
|
166
|
+
end
|
167
|
+
|
168
|
+
private
|
169
|
+
def request(url, retryCount = 0)
|
170
|
+
uri = URI(url)
|
171
|
+
https = Net::HTTP.new(uri.host, uri.port)
|
172
|
+
https.use_ssl = true
|
173
|
+
|
174
|
+
request = Net::HTTP::Get.new(uri)
|
175
|
+
request['Authorization'] = "Bearer #{token}";
|
176
|
+
|
177
|
+
response = https.request(request).read_body
|
178
|
+
|
179
|
+
result = JSON.parse(response)
|
180
|
+
if result["reviews"].nil?
|
181
|
+
if retryCount >= 10
|
182
|
+
raise "Could not connect to androidpublisher.googleapis.com, error message: #{response}"
|
183
|
+
else
|
184
|
+
@token = generateJWT()
|
185
|
+
message = "JWT Expired, refresh a new one. (#{retryCount + 1})"
|
186
|
+
logger.logWarn(message)
|
187
|
+
puts "[AndroidFetcher] #{message}"
|
188
|
+
return request(url, retryCount + 1)
|
189
|
+
end
|
190
|
+
else
|
191
|
+
return result
|
192
|
+
end
|
193
|
+
|
194
|
+
end
|
79
195
|
end
|
data/lib/AppleFetcher.rb
CHANGED
@@ -175,7 +175,8 @@ class AppleFetcher < ReviewFetcher
|
|
175
175
|
exp: Time.now.to_i + 60*20,
|
176
176
|
aud: 'appstoreconnect-v1'
|
177
177
|
}
|
178
|
-
|
178
|
+
|
179
|
+
return JWT.encode payload, config.keyContent, 'ES256', header_fields={kid:config.keyID, typ:"JWT"}
|
179
180
|
end
|
180
181
|
|
181
182
|
private
|
data/lib/Models/AndroidConfig.rb
CHANGED
@@ -5,7 +5,7 @@ require "Helper"
|
|
5
5
|
require "time"
|
6
6
|
|
7
7
|
class AndroidConfig
|
8
|
-
attr_accessor :keyContent, :packageName, :accountID, :appID, :baseExecutePath
|
8
|
+
attr_accessor :keyContent, :keyID, :tokenURI, :clientEmail, :packageName, :accountID, :appID, :baseExecutePath
|
9
9
|
def initialize(configYMLObj, configFilePath, baseExecutePath)
|
10
10
|
keyFilePath = Helper.unwrapRequiredParameter(configYMLObj,"keyFilePath")
|
11
11
|
|
@@ -14,9 +14,14 @@ class AndroidConfig
|
|
14
14
|
keyFilePath = "#{configDir}#{keyFilePath}"
|
15
15
|
end
|
16
16
|
|
17
|
+
keyFileContent = JSON.parse(File.read(keyFilePath))
|
18
|
+
|
17
19
|
@accountID = configYMLObj["playConsoleDeveloperAccountID"]
|
18
20
|
@appID = configYMLObj["playConsoleAppID"]
|
19
|
-
@keyContent =
|
21
|
+
@keyContent = Helper.unwrapRequiredParameter(keyFileContent,"private_key")
|
22
|
+
@keyID = Helper.unwrapRequiredParameter(keyFileContent,"private_key_id")
|
23
|
+
@clientEmail = Helper.unwrapRequiredParameter(keyFileContent,"client_email")
|
24
|
+
@tokenURI = Helper.unwrapRequiredParameter(keyFileContent,"token_uri")
|
20
25
|
@baseExecutePath = baseExecutePath
|
21
26
|
@packageName = Helper.unwrapRequiredParameter(configYMLObj,"packageName")
|
22
27
|
end
|
@@ -3,18 +3,19 @@ $lib = File.expand_path('../lib', File.dirname(__FILE__))
|
|
3
3
|
require "Models/Review"
|
4
4
|
require "Models/Processor"
|
5
5
|
require "Helper"
|
6
|
-
|
7
6
|
require "pathname"
|
8
|
-
require "
|
7
|
+
require "jwt"
|
8
|
+
require "time"
|
9
9
|
|
10
10
|
class GoogleTranslateProcessor < Processor
|
11
11
|
|
12
|
-
attr_accessor :
|
12
|
+
attr_accessor :keyContent, :keyID, :tokenURI, :clientEmail, :targetLang, :territoriesExclude, :token, :logger
|
13
13
|
|
14
14
|
def initialize(config, configFilePath, baseExecutePath)
|
15
15
|
@config = config
|
16
16
|
@configFilePath = configFilePath
|
17
17
|
@baseExecutePath = baseExecutePath
|
18
|
+
@logger = ZLogger.new(baseExecutePath)
|
18
19
|
|
19
20
|
keyFilePath = Helper.unwrapRequiredParameter(config, "googleTranslateAPIKeyFilePath")
|
20
21
|
|
@@ -23,14 +24,21 @@ class GoogleTranslateProcessor < Processor
|
|
23
24
|
keyFilePath = "#{configDir}#{keyFilePath}"
|
24
25
|
end
|
25
26
|
|
26
|
-
|
27
|
-
|
27
|
+
keyFileContent = JSON.parse(File.read(keyFilePath))
|
28
|
+
|
29
|
+
@keyContent = Helper.unwrapRequiredParameter(keyFileContent,"private_key")
|
30
|
+
@keyID = Helper.unwrapRequiredParameter(keyFileContent,"private_key_id")
|
31
|
+
@clientEmail = Helper.unwrapRequiredParameter(keyFileContent,"client_email")
|
32
|
+
@tokenURI = Helper.unwrapRequiredParameter(keyFileContent,"token_uri")
|
33
|
+
|
28
34
|
@targetLang = Helper.unwrapRequiredParameter(config, "googleTranslateTargetLang")
|
29
35
|
@territoriesExclude = []
|
30
36
|
if !config['googleTranslateTerritoriesExclude'].nil? && config['googleTranslateTerritoriesExclude'].length > 0
|
31
37
|
@territoriesExclude = config['googleTranslateTerritoriesExclude']
|
32
38
|
end
|
33
39
|
|
40
|
+
@token = generateJWT()
|
41
|
+
|
34
42
|
puts "[GoogleTranslateProcessor] Init Success."
|
35
43
|
end
|
36
44
|
|
@@ -41,21 +49,85 @@ class GoogleTranslateProcessor < Processor
|
|
41
49
|
|
42
50
|
reviews.each_index do |index|
|
43
51
|
if territoriesExclude.include? reviews[index].territory
|
44
|
-
next
|
52
|
+
#next
|
45
53
|
end
|
46
54
|
|
47
55
|
puts "[GoogleTranslateProcessor] translate #{reviews[index].body} from #{reviews[index].territory} to #{targetLang}"
|
48
56
|
|
49
57
|
if !reviews[index].title.nil?
|
50
|
-
|
58
|
+
translateTitle = translate(reviews[index].title)
|
59
|
+
if !translateTitle.nil? && translateTitle != reviews[index].title
|
60
|
+
reviews[index].title = "#{translateTitle} (#{reviews[index].title})"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
if !reviews[index].body.nil?
|
65
|
+
translateBody = translate(reviews[index].body)
|
66
|
+
if !translateBody.nil? && translateBody != reviews[index].body
|
67
|
+
body = "#{translateBody}"
|
68
|
+
body += "\r\n===== Translate by Google =====\r\n"
|
69
|
+
body += reviews[index].body
|
70
|
+
reviews[index].body = body
|
71
|
+
end
|
51
72
|
end
|
52
|
-
body = "#{client.translate reviews[index].body, to: targetLang}"
|
53
|
-
body += "\r\n===== Translate by Google =====\r\n"
|
54
|
-
body += reviews[index].body
|
55
|
-
reviews[index].body = body
|
56
73
|
end
|
57
74
|
|
58
75
|
return reviews
|
59
76
|
end
|
60
77
|
|
78
|
+
private
|
79
|
+
def generateJWT()
|
80
|
+
payload = {
|
81
|
+
iss: clientEmail,
|
82
|
+
sub: clientEmail,
|
83
|
+
scope: ["https://www.googleapis.com/auth/cloud-translation","https://www.googleapis.com/auth/cloud-platform"].join(' '),
|
84
|
+
aud: tokenURI,
|
85
|
+
iat: Time.now.to_i,
|
86
|
+
exp: Time.now.to_i + 60*20
|
87
|
+
}
|
88
|
+
|
89
|
+
rsa_private = OpenSSL::PKey::RSA.new(keyContent)
|
90
|
+
token = JWT.encode payload, rsa_private, 'RS256', header_fields = {kid:keyID, typ:"JWT"}
|
91
|
+
|
92
|
+
uri = URI(tokenURI)
|
93
|
+
https = Net::HTTP.new(uri.host, uri.port)
|
94
|
+
https.use_ssl = true
|
95
|
+
request = Net::HTTP::Post.new(uri)
|
96
|
+
request.body = "grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&assertion=#{token}"
|
97
|
+
|
98
|
+
response = https.request(request).read_body
|
99
|
+
result = JSON.parse(response)
|
100
|
+
|
101
|
+
return result["access_token"]
|
102
|
+
end
|
103
|
+
|
104
|
+
private
|
105
|
+
def translate(text, retryCount = 0)
|
106
|
+
uri = URI("https://translation.googleapis.com/language/translate/v2")
|
107
|
+
https = Net::HTTP.new(uri.host, uri.port)
|
108
|
+
https.use_ssl = true
|
109
|
+
|
110
|
+
request = Net::HTTP::Post.new(uri)
|
111
|
+
request.body = "q=#{text}&target=#{targetLang}"
|
112
|
+
request['Authorization'] = "Bearer #{token}";
|
113
|
+
|
114
|
+
response = https.request(request).read_body
|
115
|
+
|
116
|
+
result = JSON.parse(response)
|
117
|
+
translate = result&.dig("data", "translations", 0, "translatedText")
|
118
|
+
if translate.nil?
|
119
|
+
if retryCount >= 10
|
120
|
+
raise "Could not connect to translation.googleapis.com, error message: #{response}"
|
121
|
+
else
|
122
|
+
@token = generateJWT()
|
123
|
+
message = "JWT Expired, refresh a new one. (#{retryCount + 1})"
|
124
|
+
logger.logWarn(message)
|
125
|
+
puts "[GoogleTranslateProcessor] #{message}"
|
126
|
+
return request(text, retryCount + 1)
|
127
|
+
end
|
128
|
+
else
|
129
|
+
return translate
|
130
|
+
end
|
131
|
+
|
132
|
+
end
|
61
133
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ZReviewTender
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.2.
|
4
|
+
version: 1.2.8
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- ZhgChgLi
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-08-
|
11
|
+
date: 2022-08-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: net-http
|