ZReviewTender 1.3.7 → 1.4.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 77c4a5c74b6076acbf3ec3ea98046ab703f1df3485c4590c9f1de62bda77fad9
4
- data.tar.gz: 1b8bc52b07eac4add166d360b648fa5991264d501b72a39c1dc7a412da51413c
3
+ metadata.gz: 767abeb4f548e38a266911a20c15a6307b756542db7317e444e2755c704e437c
4
+ data.tar.gz: 1d15d46a181b16e6ed3969e10e8925e139fe67b20437a6bd82eff34869e147cb
5
5
  SHA512:
6
- metadata.gz: 8bba8ffe64bb4342689ade1b868a4a369e7879641c927466792cfef62f1860b3508efb1d289c5fea8c0bb6741780e2ce491c063814b327e4b85c2b699851dff7
7
- data.tar.gz: 5cea45b9203f52e95ff67169fec881981e501e68b8e7cbc2298798e483963a44684d17e0337a7536b99ed2ba6f08f99bf531b762cf0848d8326c4864ba5bdc99
6
+ metadata.gz: b45ed4176179853c79686f0baa46ba70f8c76d8ed8f340403d27183f7435d9905fcac484965d01690c49005e7b9232ea62e971fc3ca96c9c317702cc508698af
7
+ data.tar.gz: 2835f394a70693c454bd4ce73acd161f7e636f9f62f7b78e340bbcc59197dc5ac21b58854abcc3f639fa60c60ff923b134d4ff81459dbfea0a0dd1272003564d
data/.version CHANGED
@@ -1 +1 @@
1
- 1.3.7
1
+ 1.4.0
data/bin/ZReviewTender CHANGED
@@ -91,12 +91,12 @@ class Main
91
91
  appleDestExampleYML = "#{basePath}/#{defaultConfigDirName}/#{defaultAppleConfigFileName}"
92
92
  androidDestExampleYML = "#{basePath}/#{defaultConfigDirName}/#{defaultAndroidConfigFileName}"
93
93
 
94
- if !File.exists?("#{basePath}/latestCheckTimestamp/.keep")
94
+ if !File.exist?("#{basePath}/latestCheckTimestamp/.keep")
95
95
  File.open("#{basePath}/latestCheckTimestamp/.keep", 'w') { |file| file.write("") }
96
96
  end
97
97
  #
98
- if File.exists?(appleSourceExampleYML)
99
- if File.exists?(appleDestExampleYML)
98
+ if File.exist?(appleSourceExampleYML)
99
+ if File.exist?(appleDestExampleYML)
100
100
  puts "Failed! Config YML File Exists: #{appleDestExampleYML}"
101
101
  else
102
102
  FileUtils.cp(appleSourceExampleYML, appleDestExampleYML)
@@ -104,8 +104,8 @@ class Main
104
104
  end
105
105
  end
106
106
  #
107
- if File.exists?(androidSourceExampleYML)
108
- if File.exists?(androidDestExampleYML)
107
+ if File.exist?(androidSourceExampleYML)
108
+ if File.exist?(androidDestExampleYML)
109
109
  puts "Failed! Config YML File Exists: #{androidDestExampleYML}"
110
110
  else
111
111
  FileUtils.cp(androidSourceExampleYML, androidDestExampleYML)
@@ -129,7 +129,7 @@ class Main
129
129
  opts.on('-d', '--delete', 'delete latest check timestamp log file.(factory reset)') do
130
130
  FileUtils.rm_rf("#{basePath}/latestCheckTimestamp/")
131
131
  Helper.createDirIfNotExist("#{basePath}/latestCheckTimestamp/")
132
- if !File.exists?("#{basePath}/latestCheckTimestamp/.keep")
132
+ if !File.exist?("#{basePath}/latestCheckTimestamp/.keep")
133
133
  File.open("#{basePath}/latestCheckTimestamp/.keep", 'w') { |file| file.write("") }
134
134
  end
135
135
 
@@ -160,7 +160,7 @@ class Main
160
160
 
161
161
  private
162
162
  def configFileCheck(path, command)
163
- if !File.exists? path
163
+ if !File.exist? path
164
164
  puts "Make sure you have vaild config file at #{path}"
165
165
  puts "Or use ZReviewTender #{command} specify config.yml file path"
166
166
  raise "Config file not found: #{path}"
@@ -112,7 +112,7 @@ class AndroidFetcher < ReviewFetcher
112
112
  if !config.accountID.nil? && !config.appID.nil?
113
113
  url = "https://play.google.com/console/developers/#{config.accountID}/app/#{config.appID}/user-feedback/review-details?reviewId=#{customerReviewID}"
114
114
  end
115
- reviews.append(Review.new(customerReviewPlatform, customerReviewID, customerReviewReviewerNickname, customerReviewRating, customerReviewTitle, customerReviewBody, customerReviewCreatedDateTimestamp, url, customerReviewVersionString, customerReviewTerritory))
115
+ reviews.append(Review.new(customerReviewPlatform, customerReviewID, customerReviewReviewerNickname, customerReviewRating, customerReviewTitle, customerReviewBody, customerReviewCreatedDateTimestamp, url, customerReviewVersionString, customerReviewTerritory, {}))
116
116
 
117
117
  # init first time, need first review to set as latestCheckTimestamp
118
118
  if latestCheckTimestamp == 0
data/lib/AppleFetcher.rb CHANGED
@@ -80,7 +80,7 @@ class AppleFetcher < ReviewFetcher
80
80
  break
81
81
  else
82
82
  url = "https://appstoreconnect.apple.com/WebObjects/iTunesConnect.woa/ra/ng/app/#{config.appID}/ios/ratingsResponses"
83
- reviews.append(Review.new(nil, customerReviewID, customerReviewReviewerNickname, customerReviewRating, customerReviewTitle, customerReviewBody, customerReviewCreatedDateTimestamp, url, nil, customerReviewTerritory))
83
+ reviews.append(Review.new(nil, customerReviewID, customerReviewReviewerNickname, customerReviewRating, customerReviewTitle, customerReviewBody, customerReviewCreatedDateTimestamp, url, nil, customerReviewTerritory, {}))
84
84
 
85
85
  # init first time, need first review to set as latestCheckTimestamp
86
86
  if latestCheckTimestamp == 0
data/lib/Helper.rb CHANGED
@@ -19,7 +19,7 @@ class Helper
19
19
  begin
20
20
  dir = dirs.shift
21
21
  currentDir = "#{currentDir}/#{dir}"
22
- Dir.mkdir(currentDir) unless File.exists?(currentDir)
22
+ Dir.mkdir(currentDir) unless File.exist?(currentDir)
23
23
  end while dirs.length > 0
24
24
  end
25
25
  end
data/lib/Models/Review.rb CHANGED
@@ -1,8 +1,8 @@
1
1
  $lib = File.expand_path('../', File.dirname(__FILE__))
2
2
 
3
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)
4
+ attr_accessor :platform, :id, :userName, :rating, :title, :body, :createdDateTimestamp, :url, :appVersion, :territory, :tempData
5
+ def initialize(platform, id, userName, rating, title, body, createdDateTimestamp, url, appVersion, territory, tempData)
6
6
  @platform = platform # 來源平台 android or apple
7
7
  @id = id # review id
8
8
  @userName = userName # reviewer
@@ -13,5 +13,6 @@ class Review
13
13
  @url = url # 前往 review 的連結 (apple 不提供後台指定到某個 review 的連結)
14
14
  @appVersion = appVersion # app 版本號
15
15
  @territory = territory # apple: 地區(TWN/JPN...), android: 語系(zh-tw/en...)
16
+ @tempData = tempData # 程式內部傳遞使用
16
17
  end
17
18
  end
@@ -47,7 +47,7 @@ class ReviewFetcher
47
47
 
48
48
  def isSentWelcomeMessage()
49
49
  filePath = "#{config.baseExecutePath}/latestCheckTimestamp/#{platform}Welcome"
50
- return File.exists?(filePath)
50
+ return File.exist?(filePath)
51
51
  end
52
52
 
53
53
  def setPlatformLatestCheckTimestamp(timestamp)
@@ -58,7 +58,7 @@ class ReviewFetcher
58
58
 
59
59
  def getPlatformLatestCheckTimestamp()
60
60
  filePath = "#{config.baseExecutePath}/latestCheckTimestamp/#{platform}"
61
- if File.exists?(filePath)
61
+ if File.exist?(filePath)
62
62
  return File.read(filePath).to_i
63
63
  else
64
64
  return 0
@@ -84,6 +84,8 @@ class AsanaProcessor < Processor
84
84
 
85
85
  taskData = asanaAPI("/tasks", "POST", requestTaskData)
86
86
  taskData = taskData["data"]
87
+
88
+ review.tempData["asanaTaskGID"] = taskData["gid"]
87
89
 
88
90
  if !sectionID.nil? && !taskData.nil?
89
91
  asanaAPI("/sections/#{sectionID}/addTask", "POST", {"task": taskData["gid"]})
@@ -0,0 +1,159 @@
1
+ $lib = File.expand_path('../lib', File.dirname(__FILE__))
2
+
3
+ require "Models/Review"
4
+ require "Models/Processor"
5
+ require "Helper"
6
+ require "pathname"
7
+ require "GoogleAPI"
8
+
9
+ class SlackAndAsanaConnector < Processor
10
+
11
+ attr_accessor :logger, :slackBotToken, :asanaToken, :projectID, :asanaAPIURL
12
+
13
+ def initialize(config, configFilePath, baseExecutePath)
14
+ @config = config
15
+ @configFilePath = configFilePath
16
+ @baseExecutePath = baseExecutePath
17
+ @logger = ZLogger.new(baseExecutePath)
18
+
19
+ @asanaAPIURL = "https://app.asana.com/api/1.0"
20
+ @asanaToken = Helper.unwrapRequiredParameter(config, "asanaToken")
21
+ @slackBotToken = Helper.unwrapRequiredParameter(config, "slackBotToken")
22
+
23
+ puts "[SlackAndAsanaConnector] Init Success."
24
+ end
25
+
26
+ def processReviews(reviews, platform)
27
+ if reviews.length < 1
28
+ return reviews
29
+ end
30
+
31
+ pendingReviews = reviews.dup
32
+
33
+ loop do
34
+ review = pendingReviews.shift
35
+
36
+ result = retrieveSlackMessage(review)
37
+ if !result["ok"]
38
+ if result["error"] == "ratelimited"
39
+ puts "[SlackAndAsanaConnector] Reached Rate Limited, sleep 1 sec..."
40
+ sleep(1)
41
+ pendingReviews.insert(0, review)
42
+ end
43
+ else
44
+ sendAsanaCommentMessage(review, "ref: #{result["permalink"]}")
45
+ end
46
+
47
+ puts "[SlackAndAsanaConnector] Connect Slack Message to Asana Task, rest: #{pendingReviews.length}"
48
+ break if pendingReviews.length < 1
49
+ end
50
+
51
+ pendingReviews = reviews.dup
52
+
53
+ loop do
54
+ review = pendingReviews.shift
55
+ asanaTaskURL = retrieveAsanaTaskURL(review)
56
+ if !asanaTaskURL.nil?
57
+ result = sendSlackCommnetMessage(review, "<#{asanaTaskURL}| Go To *Asana Task*>")
58
+ if !result["ok"]
59
+ if result["error"] == "ratelimited"
60
+ puts "[SlackAndAsanaConnector] Reached Rate Limited, sleep 1 sec..."
61
+ sleep(1)
62
+ pendingReviews.insert(0, review)
63
+ end
64
+ end
65
+ end
66
+
67
+ puts "[SlackAndAsanaConnector] Connect Asana Task to Slack Message, rest: #{pendingReviews.length}"
68
+ break if pendingReviews.length < 1
69
+ end
70
+
71
+ return reviews
72
+ end
73
+
74
+ private
75
+ def retrieveSlackMessage(review)
76
+ uri = URI("https://slack.com/api/chat.getPermalink?channel=#{review.tempData["slackChannelID"]}&message_ts=#{review.tempData["slackTS"]}")
77
+ headers = {'Content-Type': 'application/json; charset=utf-8', 'Authorization': "Bearer #{slackBotToken}"}
78
+
79
+ http = Net::HTTP.new(uri.host, uri.port)
80
+ http.use_ssl = true
81
+ req = Net::HTTP::Get.new(uri.request_uri, headers)
82
+ res = http.request(req)
83
+
84
+ result = JSON.parse(res.body)
85
+ return result
86
+ end
87
+
88
+ private
89
+ def sendSlackCommnetMessage(review, message)
90
+ payload = Payload.new()
91
+ payload.text = message
92
+ payload.channel = review.tempData["slackChannelID"]
93
+ payload.thread_ts = review.tempData["slackTS"]
94
+ payload.unfurl_links = true
95
+ payload.unfurl_media = true
96
+ uri = URI("https://slack.com/api/chat.postMessage")
97
+ headers = {'Content-Type': 'application/json; charset=utf-8', 'Authorization': "Bearer #{slackBotToken}"}
98
+
99
+ http = Net::HTTP.new(uri.host, uri.port)
100
+ http.use_ssl = true
101
+ req = Net::HTTP::Post.new(uri.request_uri, headers)
102
+ req.body = payload.to_json
103
+ res = http.request(req)
104
+
105
+ result = JSON.parse(res.body)
106
+ return result
107
+ end
108
+
109
+
110
+ private
111
+ def retrieveAsanaTaskURL(review)
112
+ if review.tempData["asanaTaskGID"].nil? || review.tempData["asanaTaskGID"] == ""
113
+ return nil
114
+ end
115
+
116
+ return "https://app.asana.com/0/0/#{review.tempData["asanaTaskGID"]}/f"
117
+ end
118
+
119
+ private
120
+ def sendAsanaCommentMessage(review, message)
121
+ if review.tempData["asanaTaskGID"].nil? || review.tempData["asanaTaskGID"] == ""
122
+ return nil
123
+ end
124
+
125
+ path = "/tasks/#{review.tempData["asanaTaskGID"]}/stories"
126
+ uri = URI(asanaAPIURL+path)
127
+ https = Net::HTTP.new(uri.host, uri.port)
128
+ https.use_ssl = true
129
+
130
+ request = Net::HTTP::Post.new(uri)
131
+ request['Content-Type'] = 'application/json'
132
+ request.body = JSON.dump({"data": {"text": message}})
133
+
134
+ request['Authorization'] = "Bearer #{asanaToken}"
135
+
136
+ response = https.request(request).read_body
137
+
138
+ return result = JSON.parse(response)
139
+ end
140
+
141
+ private
142
+ class Payload
143
+ attr_accessor :channel, :thread_ts, :text, :unfurl_links, :unfurl_media
144
+
145
+ def as_json(options={})
146
+ {
147
+ channel: @channel,
148
+ thread_ts: @thread_ts,
149
+ text: @text,
150
+ unfurl_links: @unfurl_links,
151
+ unfurl_media: @unfurl_media
152
+ }
153
+ end
154
+
155
+ def to_json(*options)
156
+ as_json(*options).to_json(*options)
157
+ end
158
+ end
159
+ end
@@ -42,7 +42,7 @@ class SlackProcessor < Processor
42
42
  return reviews
43
43
  end
44
44
 
45
- pendingPayloads = []
45
+ pendingRequests = []
46
46
 
47
47
  # Slack Message Limit: posting one message per second per channel
48
48
  reviews.each_slice(attachmentGroupByNumber) do |reviewGroup|
@@ -71,12 +71,16 @@ class SlackProcessor < Processor
71
71
  payload.attachments.append(attachment)
72
72
  end
73
73
 
74
- pendingPayloads.append(payload)
74
+ pendingRequests.append({"payload" => payload, "reviewGroup" => reviewGroup})
75
75
  end
76
+
77
+ resultReviews = []
76
78
 
77
79
  loop do
78
- payload = pendingPayloads.shift
79
-
80
+ pendingRequest = pendingRequests.shift
81
+ payload = pendingRequest["payload"]
82
+ reviewGroup = pendingRequest["reviewGroup"]
83
+
80
84
  result = request(payload)
81
85
  if !result[:ok]
82
86
  logger.logError(payload)
@@ -84,12 +88,17 @@ class SlackProcessor < Processor
84
88
  if result[:message] == "ratelimited"
85
89
  puts "[SlackProcessor] Reached Rate Limited, sleep 1 sec..."
86
90
  sleep(1)
87
- pendingPayloads.insert(0, payload)
91
+ pendingRequests.insert(0, pendingRequest)
92
+ end
93
+ elsif !result[:ts].nil?
94
+ reviewGroup.each do |review|
95
+ review.tempData["slackTS"] = result[:ts]
96
+ review.tempData["slackChannelID"] = targetChannel
88
97
  end
89
98
  end
90
99
 
91
- puts "[SlackProcessor] Send new Review messages, rest: #{pendingPayloads.length}"
92
- break if pendingPayloads.length < 1
100
+ puts "[SlackProcessor] Send new Review messages, rest: #{pendingRequests.length}"
101
+ break if pendingRequests.length < 1
93
102
  end
94
103
 
95
104
  return reviews
@@ -140,10 +149,10 @@ class SlackProcessor < Processor
140
149
  res = http.request(req)
141
150
 
142
151
  if isInCommingWebHook
143
- return {"ok":res.body == "ok", "message":nil}
152
+ return {"ok":res.body == "ok", "message":nil, "ts": res['ts']}
144
153
  else
145
154
  result = JSON.parse(res.body)
146
- return {"ok":result["ok"] == true, "message":result['error']}
155
+ return {"ok":result["ok"] == true, "message":result['error'], "ts": result['ts']}
147
156
  end
148
157
 
149
158
  end
@@ -182,4 +191,4 @@ class SlackProcessor < Processor
182
191
  as_json(*options).to_json(*options)
183
192
  end
184
193
  end
185
- end
194
+ end
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ZReviewTender
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.7
4
+ version: 1.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - ZhgChgLi
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2022-12-28 00:00:00.000000000 Z
10
+ date: 2025-03-22 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: net-http
@@ -30,16 +29,15 @@ dependencies:
30
29
  requirements:
31
30
  - - "~>"
32
31
  - !ruby/object:Gem::Version
33
- version: 2.4.1
32
+ version: 2.10.0
34
33
  type: :runtime
35
34
  prerelease: false
36
35
  version_requirements: !ruby/object:Gem::Requirement
37
36
  requirements:
38
37
  - - "~>"
39
38
  - !ruby/object:Gem::Version
40
- version: 2.4.1
39
+ version: 2.10.0
41
40
  description: ZReviewTender - App Reviews Automatic Bot
42
- email:
43
41
  executables:
44
42
  - ZReviewTender
45
43
  extensions: []
@@ -64,13 +62,13 @@ files:
64
62
  - lib/Processors/GoogleSheetProcessor.rb
65
63
  - lib/Processors/GoogleTranslateProcessor.rb
66
64
  - lib/Processors/ProcessorTemplate.rb
65
+ - lib/Processors/SlackAndAsanaConnector.rb
67
66
  - lib/Processors/SlackProcessor.rb
68
67
  - lib/ZLogger.rb
69
68
  homepage: https://github.com/ZhgChgLi/ZReviewTender
70
69
  licenses:
71
70
  - MIT
72
71
  metadata: {}
73
- post_install_message:
74
72
  rdoc_options: []
75
73
  require_paths:
76
74
  - lib
@@ -85,8 +83,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
85
83
  - !ruby/object:Gem::Version
86
84
  version: '0'
87
85
  requirements: []
88
- rubygems_version: 3.0.3
89
- signing_key:
86
+ rubygems_version: 3.6.2
90
87
  specification_version: 4
91
88
  summary: ZReviewTender uses brand new App Store & Google Play API to fetch App reviews
92
89
  and integration your workflow.