ZReviewTender 0.0.1

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: f28c083172d2863eac2225b5dcd2f282252dc1ba549a367db473f9e6c0674550
4
+ data.tar.gz: bb244ed194bf859f24f7952cae809ca9dac12048062c53acafb95e1baab71ca4
5
+ SHA512:
6
+ metadata.gz: 4baa4d768ee7c2c1e848a6ddec740cce709a94972c1157069fcd4601f364e09b32b75622b2f392facff383a14c775e3685b3226917e4c9322ebe8ebf8674c44e
7
+ data.tar.gz: 678a9d03b6c4ca05a9d6d0555a50c4a3f3387ea9d2556f7b1a081ffafc4e335ea9251e09fd8714233ebd534ac06125a025197ae00547bb8ed3852471ec3c1bbc
data/bin/ZReviewTender ADDED
@@ -0,0 +1,96 @@
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 "Models/AppleConfig"
8
+ require "Models/AndroidConfig"
9
+ require "Models/Processor"
10
+ require "Helper"
11
+ require "AppleFetcher"
12
+ require "AndroidFetcher"
13
+ require "optparse"
14
+ require "yaml"
15
+
16
+ class Main
17
+ def initialize
18
+
19
+ ARGV << '-r' if ARGV.empty?
20
+
21
+ OptionParser.new do |opts|
22
+ opts.banner = "Usage: ZReviewTender [options]"
23
+
24
+ basePath = ENV['PWD'] || ::Dir.pwd
25
+
26
+ opts.on('-a', '--apple[=CONFIGYMLFILEPATH]', 'execute apple platform with config yml file') do |configYMLFilePath|
27
+ if configYMLFilePath.nil?
28
+ configYMLFilePath = "#{basePath}/config/apple.yml"
29
+ end
30
+ fetcher = parseConfigYMLFile(configYMLFilePath)
31
+ fetcher.execute()
32
+ end
33
+
34
+ opts.on('-g', '--googleAndroid[=CONFIGYMLFILEPATH]', 'execute apple platform with config yml file') do |configYMLFilePath|
35
+ if configYMLFilePath.nil?
36
+ configYMLFilePath = "#{basePath}/config/android.yml"
37
+ end
38
+ fetcher = parseConfigYMLFile(configYMLFilePath)
39
+ fetcher.execute()
40
+ end
41
+
42
+ opts.on('-r', '--run', 'execute with config yml file') do
43
+ appleConfigFilePath = "#{basePath}/config/android.yml"
44
+ fetcher = parseConfigYMLFile(appleConfigFilePath)
45
+ fetcher.execute()
46
+
47
+ appleConfigFilePath = "#{basePath}/config/apple.yml"
48
+ fetcher = parseConfigYMLFile(appleConfigFilePath)
49
+ fetcher.execute()
50
+ end
51
+ end.parse!
52
+ end
53
+
54
+ private
55
+ def parseConfigYMLFile(configFilePath)
56
+ configYMLObj = YAML.load_file(configFilePath)
57
+ begin
58
+ platform = Helper.unwrapRequiredParameter(configYMLObj, 'platform')
59
+
60
+ fetcher = nil
61
+ if platform.downcase == "apple"
62
+ fetcher = AppleFetcher.new(AppleConfig.new(configYMLObj, configFilePath, ENV['PWD'] || ::Dir.pwd))
63
+ elsif platform.downcase == "android"
64
+ fetcher = AndroidFetcher.new(AndroidConfig.new(configYMLObj, configFilePath, ENV['PWD'] || ::Dir.pwd))
65
+ else
66
+ raise "unknow platform #{platform} in yml file #{configFilePath}."
67
+ end
68
+
69
+ processors = Helper.unwrapRequiredParameter(configYMLObj, 'processors')
70
+
71
+ if processors.length < 1
72
+ raise "must specify at least one processor."
73
+ end
74
+
75
+ processors.each do |processor|
76
+ processor.each do |key, value|
77
+ processorClass = Helper.unwrapRequiredParameter(value, "class")
78
+ require "Processors/#{processorClass}"
79
+ fetcher.registerProcessor(eval("#{processorClass}.new(#{value}, '#{configFilePath}', '#{ENV['PWD'] || ::Dir.pwd}')"))
80
+ end
81
+ end
82
+
83
+
84
+ return fetcher
85
+ rescue => e
86
+ raise "#{e.message} in yml file #{configFilePath}."
87
+ end
88
+ end
89
+ end
90
+
91
+ begin
92
+ Main.new()
93
+ rescue => e
94
+ puts "#Error: #{e.class} #{e.message}\n"
95
+ puts e.backtrace
96
+ end
@@ -0,0 +1,75 @@
1
+ $lib = File.expand_path('../lib', File.dirname(__FILE__))
2
+
3
+ require "Models/Review"
4
+ require "Helper"
5
+ require "Models/ReviewFetcher"
6
+ require "time"
7
+ require "google/apis/androidpublisher_v3"
8
+ require "google/apis/content_v2"
9
+
10
+ class AndroidFetcher < ReviewFetcher
11
+
12
+ attr_accessor :token, :client
13
+
14
+ def initialize(config)
15
+ @processors = []
16
+ @config = config
17
+ @platform = 'Android'
18
+
19
+ @client = Google::Apis::AndroidpublisherV3::AndroidPublisherService.new
20
+ @client.authorization = Google::Auth::ServiceAccountCredentials.make_creds(json_key_io: config.keyContent, scope: 'https://www.googleapis.com/auth/androidpublisher')
21
+ end
22
+
23
+ def execute()
24
+
25
+ latestCheckTimestamp = getPlatformLatestCheckTimestamp()
26
+
27
+ # init first time, send welcome message
28
+ if latestCheckTimestamp == 0
29
+ sendWelcomMessage()
30
+ setPlatformLatestCheckTimestamp()
31
+ return
32
+ end
33
+
34
+ reviews = []
35
+
36
+ # Google API Bug, couldn't specify limit/offse/pagination, google only return a few recent reviews.
37
+ customerReviews = client.list_reviews(config.packageName).reviews
38
+ customerReviews.each do |customerReview|
39
+
40
+ customerReviewID = customerReview.review_id
41
+ customerReviewTitle = nil
42
+ customerReviewBody = customerReview.comments[0].user_comment.text.strip
43
+ customerReviewRating = customerReview.comments[0].user_comment.star_rating.to_i
44
+ customerReviewReviewerNickname = customerReview.author_name
45
+ customerReviewCreatedDateTimestamp = customerReview.comments[0].user_comment.last_modified.seconds.to_i
46
+ customerReviewTerritory = customerReview.comments[0].user_comment.reviewer_language
47
+ customerReviewVersionString = "unknown"
48
+ if !customerReview.comments[0].user_comment.app_version_name.nil?
49
+ customerReviewVersionString = "#{customerReview.comments[0].user_comment.app_version_name}"
50
+ if !customerReview.comments[0].user_comment.app_version_code.nil?
51
+ customerReviewVersionString = "#{customerReviewVersionString}(#{customerReview.comments[0].user_comment.app_version_code})"
52
+ end
53
+ end
54
+ customerReviewPlatform = "Android #{customerReview.comments[0].user_comment.android_os_version}"
55
+
56
+ if latestCheckTimestamp > customerReviewCreatedDateTimestamp
57
+ break
58
+ else
59
+ url = "https://play.google.com/store/apps/details?id=#{config.packageName}&reviewId=#{customerReviewID}"
60
+ if !config.accountID.nil? && !config.appID.nil?
61
+ url = "https://play.google.com/console/developers/#{config.accountID}/app/#{config.appID}/user-feedback/review-details?reviewId=#{customerReviewID}"
62
+ end
63
+ reviews.append(Review.new(customerReviewPlatform, customerReviewID, customerReviewReviewerNickname, customerReviewRating, customerReviewTitle, customerReviewBody, customerReviewCreatedDateTimestamp, url, customerReviewVersionString, customerReviewTerritory))
64
+ end
65
+
66
+ end
67
+
68
+ if reviews.length > 0
69
+ reviews.sort! { |a, b| a.createdDateTimestamp <=> b.createdDateTimestamp }
70
+ processReviews(reviews, platform)
71
+ end
72
+
73
+ setPlatformLatestCheckTimestamp()
74
+ end
75
+ end
@@ -0,0 +1,178 @@
1
+ $lib = File.expand_path('../lib', File.dirname(__FILE__))
2
+
3
+ require "Models/Review"
4
+ require "Helper"
5
+ require "Models/ReviewFetcher"
6
+ require "jwt"
7
+ require "time"
8
+ require "net/http"
9
+
10
+ class AppleFetcher < ReviewFetcher
11
+
12
+ attr_accessor :token
13
+
14
+ def initialize(config)
15
+ @processors = []
16
+ @config = config
17
+ @platform = 'Apple'
18
+ @token = generateJWT()
19
+ end
20
+
21
+ def execute()
22
+
23
+ latestCheckTimestamp = getPlatformLatestCheckTimestamp()
24
+
25
+
26
+ # init first time, send welcome message
27
+ if latestCheckTimestamp == 0
28
+ sendWelcomMessage()
29
+ setPlatformLatestCheckTimestamp()
30
+ return;
31
+ end
32
+
33
+ reviews = fetchReviews(latestCheckTimestamp)
34
+
35
+ if reviews.length > 0
36
+ reviews.sort! { |a, b| a.createdDateTimestamp <=> b.createdDateTimestamp }
37
+ reviews = fullfillAppInfo(reviews)
38
+
39
+ processReviews(reviews, platform)
40
+ end
41
+
42
+ setPlatformLatestCheckTimestamp()
43
+ end
44
+
45
+ private
46
+ def fetchReviews(latestCheckTimestamp)
47
+ customerReviewsLink = "https://api.appstoreconnect.apple.com/v1/apps/#{config.appID}/customerReviews?sort=-createdDate"
48
+ reviews = []
49
+
50
+ loop do
51
+ customerReviews = request(customerReviewsLink)
52
+ customerReviewsLink = customerReviews&.dig("links", "next")
53
+
54
+ customerReviews&.dig("data").each do |customerReview|
55
+
56
+ customerReviewID = customerReview&.dig("id")
57
+ customerReviewTitle = customerReview&.dig("attributes","title")
58
+ customerReviewBody= customerReview&.dig("attributes","body")
59
+ customerReviewRating = customerReview&.dig("attributes","rating").to_i
60
+ customerReviewReviewerNickname = customerReview&.dig("attributes","reviewerNickname")
61
+ customerReviewCreatedDate = customerReview&.dig("attributes","createdDate")
62
+ customerReviewTerritory = customerReview&.dig("attributes","territory")
63
+
64
+ customerReviewCreatedDateTimestamp = 0
65
+ if !customerReviewCreatedDate.nil?
66
+ customerReviewCreatedDateTimestamp = Time.parse(customerReviewCreatedDate).to_i
67
+ end
68
+
69
+ if latestCheckTimestamp > customerReviewCreatedDateTimestamp
70
+ customerReviewsLink = nil
71
+ break
72
+ else
73
+ url = "https://appstoreconnect.apple.com/apps/#{config.appID}/appstore/activity/ios/ratingsResponses"
74
+ reviews.append(Review.new(nil, customerReviewID, customerReviewReviewerNickname, customerReviewRating, customerReviewTitle, customerReviewBody, customerReviewCreatedDateTimestamp, url, nil, customerReviewTerritory))
75
+ end
76
+ end
77
+
78
+ break if customerReviewsLink.nil?
79
+ end
80
+
81
+ return reviews
82
+ end
83
+
84
+ private
85
+ def fullfillAppInfo(reviews)
86
+ customerReviewWhichAppVersionIsNil = reviews.select{ |review| review.appVersion.nil? }.map.with_index { |review, index| {"id":review.id, "index": index} }
87
+
88
+ appStoreVersionsLink = "https://api.appstoreconnect.apple.com/v1/apps/#{config.appID}/appStoreVersions"
89
+
90
+ loop do
91
+ appStoreVersions = request(appStoreVersionsLink)
92
+ appStoreVersionsLink = appStoreVersions&.dig("links", "next")
93
+
94
+ appStoreVersions&.dig("data").each do |appStoreVersion|
95
+ applePlatform = appStoreVersion&.dig("attributes","platform")
96
+ versionString = appStoreVersion&.dig("attributes","versionString")
97
+ customerReviewsLink = appStoreVersion&.dig("relationships","customerReviews","links","related")
98
+
99
+ if !customerReviewsLink.nil?
100
+ customerReviewsLink = "#{customerReviewsLink}?sort=-createdDate&limit=200"
101
+
102
+ loop do
103
+ customerReviews = request(customerReviewsLink)
104
+ customerReviewsLink = customerReviews&.dig("links", "next")
105
+
106
+ customerReviews&.dig("data").each do |customerReview|
107
+ customerReviewID = customerReview&.dig("id")
108
+ if customerReviewID.nil?
109
+ next
110
+ end
111
+ findIndex = customerReviewWhichAppVersionIsNil.find_index { |value| value[:id] == customerReviewID }
112
+ if !findIndex.nil?
113
+ findResult = customerReviewWhichAppVersionIsNil[findIndex]
114
+ reviews[findResult[:index]].appVersion = versionString
115
+ reviews[findResult[:index]].platform = applePlatform
116
+
117
+ customerReviewWhichAppVersionIsNil.delete_at(findIndex)
118
+
119
+ if customerReviewWhichAppVersionIsNil.length < 1
120
+ customerReviewsLink = nil
121
+ break
122
+ end
123
+ end
124
+ end
125
+
126
+ break if customerReviewsLink.nil?
127
+ end
128
+ end
129
+
130
+ if customerReviewWhichAppVersionIsNil.length < 1
131
+ appStoreVersionsLink = nil
132
+ break
133
+ end
134
+ end
135
+
136
+ break if appStoreVersionsLink.nil?
137
+ end
138
+
139
+ return reviews
140
+ end
141
+
142
+ private
143
+ def generateJWT()
144
+ payload = {
145
+ iss: config.issueID,
146
+ iat: Time.now.to_i,
147
+ exp: Time.now.to_i + 60*20,
148
+ aud: 'appstoreconnect-v1'
149
+ }
150
+ token = JWT.encode payload, config.keyContent, 'ES256', header_fields={kid:config.keyID, typ:"JWT"}
151
+ end
152
+
153
+ private
154
+ def request(url, retryCount = 0)
155
+ uri = URI(url)
156
+ https = Net::HTTP.new(uri.host, uri.port)
157
+ https.use_ssl = true
158
+
159
+ request = Net::HTTP::Get.new(uri)
160
+ request['Authorization'] = "Bearer #{token}";
161
+
162
+ response = https.request(request).read_body
163
+
164
+ result = JSON.parse(response)
165
+ if !result["errors"].nil?
166
+ if retryCount >= 10
167
+ raise "Could not connect to api.appstoreconnect.apple.com, error message: #{response}"
168
+ else
169
+ @token = generateJWT()
170
+ Helper.logWarn("JWT Expired, refresh a new one. (#{retryCount + 1})")
171
+ return request(url, retryCount + 1)
172
+ end
173
+ else
174
+ return result
175
+ end
176
+
177
+ end
178
+ end
data/lib/Helper.rb ADDED
@@ -0,0 +1,42 @@
1
+ $lib = File.expand_path('../', File.dirname(__FILE__))
2
+
3
+ require "logger"
4
+
5
+ class Helper
6
+ def self.unwrapRequiredParameter(obj, key)
7
+ if obj[key].nil?
8
+ raise "Required Parameter Not Found: #{key}"
9
+ else
10
+ if obj[key] == ''
11
+ raise "Required Parameter Is Empty: #{key}"
12
+ else
13
+ return obj[key]
14
+ end
15
+ end
16
+ end
17
+
18
+ def self.createDirIfNotExist(dirPath)
19
+ dirs = dirPath.split("/")
20
+ currentDir = ""
21
+ begin
22
+ dir = dirs.shift
23
+ currentDir = "#{currentDir}/#{dir}"
24
+ Dir.mkdir(currentDir) unless File.exists?(currentDir)
25
+ end while dirs.length > 0
26
+ end
27
+
28
+ def self.logError(message)
29
+ logger = Logger.new(STDOUT)
30
+ logger.error("#{caller[0]}: #{message}")
31
+ end
32
+
33
+ def self.logWarn(message)
34
+ logger = Logger.new(STDOUT)
35
+ logger.warning("#{caller[0]}: #{message}")
36
+ end
37
+
38
+ def self.logInfo(message)
39
+ logger = Logger.new(STDOUT)
40
+ logger.info("#{caller[0]}: #{message}")
41
+ end
42
+ end
@@ -0,0 +1,23 @@
1
+ $lib = File.expand_path('../', File.dirname(__FILE__))
2
+
3
+ require "pathname"
4
+ require "Helper"
5
+ require "time"
6
+
7
+ class AndroidConfig
8
+ attr_accessor :keyContent, :packageName, :accountID, :appID, :baseExecutePath
9
+ def initialize(configYMLObj, configFilePath, baseExecutePath)
10
+ keyFilePath = Helper.unwrapRequiredParameter(configYMLObj,"keyFilePath")
11
+
12
+ if Pathname.new(keyFilePath).absolute?
13
+ configDir = File.dirname(configFilePath)
14
+ keyFilePath = "#{configDir}#{keyFilePath}"
15
+ end
16
+
17
+ @accountID = configYMLObj["playConsoleDeveloperAccountID"]
18
+ @appID = configYMLObj["playConsoleAppID"]
19
+ @keyContent = File.open(keyFilePath)
20
+ @baseExecutePath = baseExecutePath
21
+ @packageName = Helper.unwrapRequiredParameter(configYMLObj,"packageName")
22
+ end
23
+ end
@@ -0,0 +1,25 @@
1
+ $lib = File.expand_path('../', File.dirname(__FILE__))
2
+
3
+ require "pathname"
4
+ require "Helper"
5
+ require "openssl"
6
+
7
+ class AppleConfig
8
+ attr_accessor :keyContent, :keyID, :issueID, :appID, :baseExecutePath
9
+ def initialize(configYMLObj, configFilePath, baseExecutePath)
10
+ keyFilePath = Helper.unwrapRequiredParameter(configYMLObj,"appStoreConnectP8PrivateKeyFilePath")
11
+
12
+ if Pathname.new(keyFilePath).absolute?
13
+ configDir = File.dirname(configFilePath)
14
+ keyFilePath = "#{configDir}#{keyFilePath}"
15
+ end
16
+
17
+ keyFile = File.read(keyFilePath)
18
+ @keyContent = OpenSSL::PKey::EC.new(keyFile)
19
+
20
+ @baseExecutePath = baseExecutePath
21
+ @keyID = Helper.unwrapRequiredParameter(configYMLObj,"appStoreConnectP8PrivateKeyID")
22
+ @issueID = Helper.unwrapRequiredParameter(configYMLObj,"appStoreConnectIssueID")
23
+ @appID = Helper.unwrapRequiredParameter(configYMLObj,"appID")
24
+ end
25
+ end
@@ -0,0 +1,16 @@
1
+ $lib = File.expand_path('../', File.dirname(__FILE__))
2
+
3
+ class Processor
4
+
5
+ attr_accessor :config, :configFilePath, :baseExecutePath
6
+
7
+ def initialize(config, configFilePath, baseExecutePath)
8
+ @config = config
9
+ @configFilePath = configFilePath
10
+ @baseExecutePath = baseExecutePath
11
+ end
12
+
13
+ def processReviews(reviews, platform)
14
+
15
+ end
16
+ end
@@ -0,0 +1,17 @@
1
+ $lib = File.expand_path('../', File.dirname(__FILE__))
2
+
3
+ class Review
4
+ attr_accessor :platform, :id, :userName, :rating, :title, :body, :createdDateTimestamp, :url, :appVersion, :territory
5
+ def initialize(platform, id, userName, rating, title, body, createdDateTimestamp, url, appVersion, territory)
6
+ @platform = platform
7
+ @id = id
8
+ @userName = userName
9
+ @rating = rating
10
+ @title = title
11
+ @body = body
12
+ @createdDateTimestamp = createdDateTimestamp
13
+ @url = url
14
+ @appVersion = appVersion
15
+ @territory = territory
16
+ end
17
+ end
@@ -0,0 +1,48 @@
1
+
2
+ $lib = File.expand_path('../lib', File.dirname(__FILE__))
3
+
4
+
5
+ require "Processors/SlackProcessor"
6
+ require "Models/Processor"
7
+ require "time"
8
+
9
+ class ReviewFetcher
10
+
11
+ attr_accessor :config, :platform, :processors
12
+
13
+ def execute()
14
+
15
+ end
16
+
17
+ def registerProcessor(processor)
18
+ processors.append(processor)
19
+ end
20
+
21
+ def processReviews(reviews, platform)
22
+ processors.each do |processor|
23
+ reviews = processor.processReviews(reviews, platform)
24
+ end
25
+ end
26
+
27
+ def sendWelcomMessage()
28
+ slackProcessor = processors.find { |processor| processor.is_a?(SlackProcessor) }
29
+ if !slackProcessor.nil?
30
+ slackProcessor.sendWelcomMessage(platform)
31
+ end
32
+ end
33
+
34
+ def setPlatformLatestCheckTimestamp()
35
+ basePath = "#{config.baseExecutePath}/.cache"
36
+ Helper.createDirIfNotExist(basePath)
37
+ File.open("#{basePath}/#{platform}-latestCheckTimestamp", 'w') { |file| file.write(Time.now().to_i) }
38
+ end
39
+
40
+ def getPlatformLatestCheckTimestamp()
41
+ filePath = "#{config.baseExecutePath}/.cache/#{platform}-latestCheckTimestamp"
42
+ if File.exists?(filePath)
43
+ return File.read(filePath).to_i
44
+ else
45
+ return 0
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,53 @@
1
+ $lib = File.expand_path('../lib', File.dirname(__FILE__))
2
+
3
+ require "Models/Review"
4
+ require "Models/Processor"
5
+ require "Helper"
6
+
7
+ require "pathname"
8
+ require "google/cloud/translate/v2"
9
+
10
+ class GoogleTranslateProcessor < Processor
11
+
12
+ attr_accessor :client, :targetLang, :whiteListTerritories
13
+
14
+ def initialize(config, configFilePath, baseExecutePath)
15
+ @config = config
16
+ @configFilePath = configFilePath
17
+ @baseExecutePath = baseExecutePath
18
+
19
+ keyFilePath = Helper.unwrapRequiredParameter(config, "googleTranslateAPIKeyFilePath")
20
+
21
+ if Pathname.new(keyFilePath).absolute?
22
+ configDir = File.dirname(configFilePath)
23
+ keyFilePath = "#{configDir}#{keyFilePath}"
24
+ end
25
+
26
+ ENV["TRANSLATE_CREDENTIALS"] = keyFilePath
27
+ @client = Google::Cloud::Translate::V2.new
28
+ @targetLang = Helper.unwrapRequiredParameter(config, "googleTranslateTargetLang")
29
+ @whiteListTerritories = []
30
+ if !config['googleTranslateWhiteListTerritories'].nil? && config['googleTranslateWhiteListTerritories'].length > 0
31
+ @whiteListTerritories = config['googleTranslateWhiteListTerritories']
32
+ end
33
+ end
34
+
35
+ def processReviews(reviews, platform)
36
+ reviews.each_index do |index|
37
+ if whiteListTerritories.include? reviews[index].territory
38
+ next
39
+ end
40
+
41
+ if !reviews[index].title.nil?
42
+ reviews[index].title = "#{client.translate reviews[index].title, to: targetLang} (#{reviews[index].title})"
43
+ end
44
+ body = "#{client.translate reviews[index].body, to: targetLang}"
45
+ body += "\r\n===== Translate by Google =====\r\n"
46
+ body += reviews[index].body
47
+ reviews[index].body = body
48
+ end
49
+
50
+ return reviews
51
+ end
52
+
53
+ end
@@ -0,0 +1,142 @@
1
+ $lib = File.expand_path('../lib', File.dirname(__FILE__))
2
+
3
+ require "Models/Review"
4
+ require "Models/Processor"
5
+ require "Helper"
6
+ require "net/http"
7
+ require "json"
8
+ require "time"
9
+
10
+ class SlackProcessor < Processor
11
+
12
+ attr_accessor :botToken, :inCommingWebHookURL, :targetChannel, :timeZoneOffset
13
+
14
+ def initialize(config, configFilePath, baseExecutePath)
15
+ @config = config
16
+ @configFilePath = configFilePath
17
+ @baseExecutePath = baseExecutePath
18
+
19
+ @botToken = config["slackBotToken"]
20
+ @inCommingWebHookURL = config["slackInCommingWebHookURL"]
21
+ @targetChannel = config["slackBotTargetChannel"]
22
+ @timeZoneOffset = Helper.unwrapRequiredParameter(config, "slackTimeZoneOffset")
23
+
24
+ if (botToken.nil? && inCommingWebHookURL.nil?) || (botToken == "" && inCommingWebHookURL == "")
25
+ raise "must specify slackBotToken or slackInCommingWebHookURL in SlackProcessor."
26
+ elsif !botToken.nil? && botToken != "" && (targetChannel.nil? || targetChannel == "")
27
+ raise "must specify slackBotTargetChannel in SlackProcessor."
28
+ end
29
+ end
30
+
31
+ def processReviews(reviews, platform)
32
+ reviews.each_slice(50) do |reviewGroup|
33
+ payload = Payload.new()
34
+ payload.attachments = []
35
+
36
+ reviewGroup.each do |review|
37
+ attachment = Payload::Attachment.new()
38
+
39
+ stars = "★" * review.rating + "☆" * (5 - review.rating)
40
+ color = review.rating >= 4 ? "good" : (review.rating > 2 ? "warning" : "danger")
41
+ date = Time.at(review.createdDateTimestamp).getlocal(timeZoneOffset)
42
+
43
+ title = "#{stars}"
44
+ if !review.title.nil?
45
+ title = "#{review.title} - #{stars}"
46
+ end
47
+
48
+ attachment.color = color
49
+ attachment.author_name = review.userName
50
+ attachment.fallback = title
51
+ attachment.title = title
52
+ attachment.text = review.body
53
+ attachment.footer = "#{platform} - #{review.platform} - #{review.appVersion} - #{review.territory} - <#{review.url}|#{date}>"
54
+
55
+ payload.attachments.append(attachment)
56
+ end
57
+
58
+ result = request(payload)
59
+ if result["ok"] != true
60
+ Helper.logError(result)
61
+ end
62
+ end
63
+
64
+ return reviews
65
+ end
66
+
67
+ def sendWelcomMessage(platform)
68
+ payload = Payload.new()
69
+ payload.attachments = []
70
+
71
+ attachment = Payload::Attachment.new()
72
+
73
+ title = "ZReviewTender Standing By :astronaut:"
74
+ body = "#{platform} Init Success!, will resend App Review to this channel automatically."
75
+
76
+ attachment.color = "good"
77
+ attachment.author_name = "<https://zhgchg.li|ZhgChgLi>"
78
+ attachment.fallback = title
79
+ attachment.title = title
80
+ attachment.text = body
81
+ attachment.footer = "Powered by "
82
+
83
+ payload.attachments.append(attachment)
84
+
85
+ request(payload)
86
+ end
87
+
88
+ private
89
+ def request(payload)
90
+ if !botToken.nil? && botToken != ""
91
+ uri = URI("https://slack.com/api/chat.postMessage")
92
+ payload.channel = targetChannel
93
+ headers = {'Content-Type': 'application/json; charset=utf-8', 'Authorization': "Bearer #{botToken}"}
94
+ else
95
+ uri = URI(inCommingWebHookURL)
96
+ payload.channel = nil
97
+ headers = {'Content-Type': 'application/json; charset=utf-8'}
98
+ end
99
+
100
+ http = Net::HTTP.new(uri.host, uri.port)
101
+ http.use_ssl = true
102
+ req = Net::HTTP::Post.new(uri.request_uri, headers)
103
+ req.body = payload.to_json
104
+ res = http.request(req)
105
+ JSON.parse(res.body)
106
+ end
107
+
108
+ private
109
+ class Payload
110
+ attr_accessor :channel, :attachments
111
+ class Attachment
112
+ attr_accessor :pretext, :color, :fallback, :title, :text, :author_name, :footer
113
+
114
+ def as_json(options={})
115
+ {
116
+ pretext: @pretext,
117
+ color: @color,
118
+ fallback: @fallback,
119
+ title: @title,
120
+ text: @text,
121
+ author_name: @author_name,
122
+ footer: @footer
123
+ }
124
+ end
125
+
126
+ def to_json(*options)
127
+ as_json(*options).to_json(*options)
128
+ end
129
+ end
130
+
131
+ def as_json(options={})
132
+ {
133
+ channel: @channel,
134
+ attachments: @attachments
135
+ }
136
+ end
137
+
138
+ def to_json(*options)
139
+ as_json(*options).to_json(*options)
140
+ end
141
+ end
142
+ end
metadata ADDED
@@ -0,0 +1,111 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ZReviewTender
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - ZhgChgLi
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2022-08-07 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
+ - !ruby/object:Gem::Dependency
42
+ name: google-cloud-translate-v2
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 0.3.0
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 0.3.0
55
+ - !ruby/object:Gem::Dependency
56
+ name: google-apis-androidpublisher_v3
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 0.25.0
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 0.25.0
69
+ description: ZReviewTender - Monitor your App reviews in your Slack channel
70
+ email:
71
+ executables:
72
+ - ZReviewTender
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - bin/ZReviewTender
77
+ - lib/AndroidFetcher.rb
78
+ - lib/AppleFetcher.rb
79
+ - lib/Helper.rb
80
+ - lib/Models/AndroidConfig.rb
81
+ - lib/Models/AppleConfig.rb
82
+ - lib/Models/Processor.rb
83
+ - lib/Models/Review.rb
84
+ - lib/Models/ReviewFetcher.rb
85
+ - lib/Processors/GoogleTranslateProcessor.rb
86
+ - lib/Processors/SlackProcessor.rb
87
+ homepage: https://github.com/ZhgChgLi/ZReviewTender
88
+ licenses:
89
+ - MIT
90
+ metadata: {}
91
+ post_install_message:
92
+ rdoc_options: []
93
+ require_paths:
94
+ - lib
95
+ required_ruby_version: !ruby/object:Gem::Requirement
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ version: '0'
100
+ required_rubygems_version: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ version: '0'
105
+ requirements: []
106
+ rubygems_version: 3.0.3
107
+ signing_key:
108
+ specification_version: 4
109
+ summary: ZReviewTender uses brand new App Store & Google Play API to resend App reviews
110
+ to your Slack channel automatically.
111
+ test_files: []