ZReviewTender 1.2.7 → 1.2.8

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d2943cc5872e966ed140f7300a31570056b8d11a08c24ef3f3e7af07612ebfa1
4
- data.tar.gz: 7f6c610e0f1277d71f89d7678e754e9ae543865dd0942f1a2b77d40606f35538
3
+ metadata.gz: 8bcc9b0eab8e7b7c5aacfbc1add9a591e860222656d4097b3184011e41f8d93b
4
+ data.tar.gz: cf092f42c2d81f62394713040fea08748810c283e4f0540f019b381042cf305b
5
5
  SHA512:
6
- metadata.gz: 5ef30f02a3719a92277a2b4014f0cc074927aad1e3fe0466a4e1e2352baed8244a160d1800a7e188740357219e566389befb8f088d9deae6d02a933914ee7871
7
- data.tar.gz: 5605793e165c0fc01c8ff1aa635eb7dda2c5894f75e590b03e44e6194d4ce5b237e50506925bc1a100dea887adc3369e95bc36f920b02d1c315e530acb507c8e
6
+ metadata.gz: 7ddf0c24a5120da15392ca8c2b5053d39b6ae48853ed85e16fb8e445ca76ec3039ff2c97ee02e6015d20641ae697245de715951015397c44de4002d3456494f8
7
+ data.tar.gz: 03afbee02b7543121a52a645258845780e0fbadf42b7b8ab65f8f9553317447500ee004df953c62735ba82cc584c0e1e3193a07b98f396913ce1217db40eb56b
data/.version CHANGED
@@ -1 +1 @@
1
- 1.2.7
1
+ 1.2.8
@@ -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, :client
11
+ attr_accessor :token
12
12
 
13
13
  def initialize(config)
14
14
  @processors = []
15
15
  @config = config
16
16
  @platform = 'Android'
17
-
18
- @client = Google::Apis::AndroidpublisherV3::AndroidPublisherService.new
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
- # Google API Bug, couldn't specify limit/offse/pagination, google only return a few recent reviews.
33
- customerReviews = client.list_reviews(config.packageName).reviews
34
- puts "[AndroidFetcher] Fetch reviews in #{config.packageName}, count: #{customerReviews.length}"
35
- customerReviews.each do |customerReview|
36
-
37
- customerReviewID = customerReview.review_id
38
- customerReviewTitle = nil
39
- customerReviewBody = customerReview.comments[0].user_comment.text.strip
40
- customerReviewRating = customerReview.comments[0].user_comment.star_rating.to_i
41
- customerReviewReviewerNickname = customerReview.author_name
42
- customerReviewCreatedDateTimestamp = customerReview.comments[0].user_comment.last_modified.seconds.to_i
43
- customerReviewTerritory = customerReview.comments[0].user_comment.reviewer_language
44
- customerReviewVersionString = "unknown"
45
- if !customerReview.comments[0].user_comment.app_version_name.nil?
46
- customerReviewVersionString = "#{customerReview.comments[0].user_comment.app_version_name}"
47
- if !customerReview.comments[0].user_comment.app_version_code.nil?
48
- customerReviewVersionString = "#{customerReviewVersionString}(#{customerReview.comments[0].user_comment.app_version_code})"
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
- url = "https://play.google.com/store/apps/details?id=#{config.packageName}&reviewId=#{customerReviewID}"
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
- reviews = reviews.reject{ |review| latestCheckTimestamp >= review.createdDateTimestamp }.sort! { |a, b| a.createdDateTimestamp <=> b.createdDateTimestamp }
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
- token = JWT.encode payload, config.keyContent, 'ES256', header_fields={kid:config.keyID, typ:"JWT"}
178
+
179
+ return JWT.encode payload, config.keyContent, 'ES256', header_fields={kid:config.keyID, typ:"JWT"}
179
180
  end
180
181
 
181
182
  private
@@ -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 = File.open(keyFilePath)
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 "google/cloud/translate/v2"
7
+ require "jwt"
8
+ require "time"
9
9
 
10
10
  class GoogleTranslateProcessor < Processor
11
11
 
12
- attr_accessor :client, :targetLang, :territoriesExclude
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
- ENV["TRANSLATE_CREDENTIALS"] = keyFilePath
27
- @client = Google::Cloud::Translate::V2.new
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
- reviews[index].title = "#{client.translate reviews[index].title, to: targetLang} (#{reviews[index].title})"
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.7
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-16 00:00:00.000000000 Z
11
+ date: 2022-08-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: net-http