kobot 1.2.1 → 1.2.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +14 -0
- data/bin/console +1 -0
- data/lib/kobot/config.rb +0 -1
- data/lib/kobot/credential.rb +0 -1
- data/lib/kobot/engine.rb +66 -51
- data/lib/kobot/exception.rb +3 -10
- data/lib/kobot/mailer.rb +0 -2
- data/lib/kobot/option.rb +0 -2
- data/lib/kobot/selector.rb +40 -0
- data/lib/kobot/version.rb +1 -1
- data/lib/kobot.rb +1 -2
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f35251b3ce8549b6b268a9741ecc88398f4fca4e1fbe27c343e2a1c35d456ca0
|
4
|
+
data.tar.gz: 1af276cc34607f075650b5be4df65c9666449c761b410fb5cc63629fe0392ff7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a1a76b64eb1aa1019a12b4f1d4a2d34c84d79cf020b0ebc7cee7c1bd4902b180aec70790ee236883100bfd2501bbee9846f61b5b40dd8fb3996159e58b94b0df
|
7
|
+
data.tar.gz: a81c35396d3dde1a74ab9aad3c3535e92699268f49a133f9f43514b192bae808a84f1974c9f9c537690ce3561c942251c044591fc363b54c619b44586ede7670
|
data/CHANGELOG.md
CHANGED
@@ -14,3 +14,17 @@
|
|
14
14
|
- Improved logging for better readability in logs
|
15
15
|
- Switched to builtin Logger#deprecate from Logger#warn for deprecations
|
16
16
|
- Renamed internal method to skip? from holiday? as it was meant for skipping any specified date
|
17
|
+
|
18
|
+
### v1.2.2
|
19
|
+
- Improved login screen wait and logging
|
20
|
+
- Applied fix for offenses about empty lines and long lines reported by Rubocop
|
21
|
+
|
22
|
+
### v1.2.3
|
23
|
+
- Improved validation logic to skip running due to weekend or intentional skips
|
24
|
+
- Refactored engine by reducing methods length based on reports by Rubocop
|
25
|
+
|
26
|
+
### v1.2.4
|
27
|
+
- Changed to use boolean values for validation instead of raising exceptions
|
28
|
+
|
29
|
+
### v1.2.5
|
30
|
+
- Added I18n support for both English and Japanese KOT UI
|
data/bin/console
CHANGED
data/lib/kobot/config.rb
CHANGED
data/lib/kobot/credential.rb
CHANGED
data/lib/kobot/engine.rb
CHANGED
@@ -6,7 +6,6 @@ module Kobot
|
|
6
6
|
# The core class that launches browser, logins to KOT, reads today
|
7
7
|
# record, and conducts clock in or clock out action based on config.
|
8
8
|
class Engine
|
9
|
-
|
10
9
|
def initialize
|
11
10
|
@now = Time.now.getlocal(Config.kot_timezone_offset)
|
12
11
|
@today = @now.strftime(Config.kot_date_format)
|
@@ -15,7 +14,7 @@ module Kobot
|
|
15
14
|
|
16
15
|
# The entrance where the whole flow starts.
|
17
16
|
#
|
18
|
-
# It exits early if today is weekend or
|
17
|
+
# It exits early if today is weekend or marked as to skip by
|
19
18
|
# the #{Config.skip} specified from command line option --skip.
|
20
19
|
#
|
21
20
|
# Unexpected behavior such as record appearing as holiday on
|
@@ -25,26 +24,12 @@ module Kobot
|
|
25
24
|
# System errors or any unknown exceptions occurred if any are
|
26
25
|
# to be popped up and should be handled by the outside caller.
|
27
26
|
def start
|
28
|
-
|
29
|
-
|
30
|
-
Kobot.logger.info("[Force] should have exited: today=#{@today} is weekend")
|
31
|
-
else
|
32
|
-
Kobot.logger.info("Today=#{@today} is weekend")
|
33
|
-
return
|
34
|
-
end
|
35
|
-
end
|
36
|
-
if skip?
|
37
|
-
Kobot.logger.info("Today=#{@today} is skipped as per: --skip=#{Config.skip}")
|
38
|
-
return
|
39
|
-
end
|
40
|
-
unless %i[in out].include? Config.clock
|
41
|
-
Kobot.logger.warn("Invalid clock operation: #{Config.clock}")
|
42
|
-
return
|
43
|
-
end
|
27
|
+
return unless should_run_today?
|
28
|
+
|
44
29
|
launch_browser
|
45
30
|
login
|
46
31
|
read_today_record
|
47
|
-
|
32
|
+
validate_today_record!
|
48
33
|
if Config.clock == :in
|
49
34
|
clock_in!
|
50
35
|
else
|
@@ -69,12 +54,23 @@ module Kobot
|
|
69
54
|
Mailer.send(clock_notify_message(status: e.message))
|
70
55
|
logout
|
71
56
|
ensure
|
72
|
-
|
73
|
-
@browser&.quit
|
57
|
+
close_browser
|
74
58
|
end
|
75
59
|
|
76
60
|
private
|
77
61
|
|
62
|
+
def should_run_today?
|
63
|
+
if skip?
|
64
|
+
Kobot.logger.warn("Today=#{@today} is skipped as per: --skip=#{Config.skip}")
|
65
|
+
return false
|
66
|
+
end
|
67
|
+
return true unless weekend?
|
68
|
+
|
69
|
+
Kobot.logger.info("[Force] should have exited: today=#{@today} is weekend") if Config.force
|
70
|
+
Kobot.logger.warn("Today=#{@today} is weekend") unless Config.force
|
71
|
+
Config.force
|
72
|
+
end
|
73
|
+
|
78
74
|
def launch_browser
|
79
75
|
prefs = {
|
80
76
|
profile: {
|
@@ -90,9 +86,24 @@ module Kobot
|
|
90
86
|
Kobot.logger.info('Launch browser successful')
|
91
87
|
end
|
92
88
|
|
89
|
+
def close_browser
|
90
|
+
return unless @browser
|
91
|
+
|
92
|
+
Kobot.logger.info('Close browser')
|
93
|
+
@browser.quit
|
94
|
+
end
|
95
|
+
|
93
96
|
def login
|
94
97
|
Kobot.logger.info("Navigate to: #{@top_url}")
|
95
98
|
@browser.get @top_url
|
99
|
+
@wait.until { @browser.find_element(id: 'modal_window') }
|
100
|
+
modal_title_element = @wait.until { @browser.find_element(css: '.modal-title') }
|
101
|
+
@selector = if modal_title_element.text.downcase.include? 'password'
|
102
|
+
Selector.en
|
103
|
+
else
|
104
|
+
Selector.ja
|
105
|
+
end
|
106
|
+
Kobot.logger.info "Page title: #{@browser.title}"
|
96
107
|
Kobot.logger.debug do
|
97
108
|
"Login with id=#{Credential.kot_id} and password=#{Credential.kot_password}"
|
98
109
|
end
|
@@ -101,10 +112,14 @@ module Kobot
|
|
101
112
|
@browser.find_element(css: 'div.btn-control-message').click
|
102
113
|
|
103
114
|
Kobot.logger.info 'Login successful'
|
104
|
-
@wait.until
|
115
|
+
@wait.until do
|
116
|
+
@browser.find_element(id: 'notification_content').text.include?(@selector.login_success_notification_text)
|
117
|
+
end
|
105
118
|
if Config.browser_geolocation
|
106
119
|
begin
|
107
|
-
@wait.until
|
120
|
+
@wait.until do
|
121
|
+
@browser.find_element(id: 'location_area').text.include?(@selector.location_area_notification_text)
|
122
|
+
end
|
108
123
|
rescue StandardError => e
|
109
124
|
Kobot.logger.warn "Get geolocation failed: #{e.message}"
|
110
125
|
end
|
@@ -114,21 +129,21 @@ module Kobot
|
|
114
129
|
|
115
130
|
def logout
|
116
131
|
if @browser.current_url.include? 'admin'
|
117
|
-
Kobot.logger.info('Logout from タイムカード page')
|
132
|
+
Kobot.logger.info('Logout from Time Card (タイムカード) page')
|
118
133
|
@browser.find_element(css: 'div.htBlock-header_logoutButton').click
|
119
134
|
else
|
120
|
-
Kobot.logger.info('Logout from Myレコーダー page')
|
135
|
+
Kobot.logger.info('Logout from My Recorder (Myレコーダー) page')
|
121
136
|
@wait.until { @browser.find_element(id: 'menu_icon') }.click
|
122
|
-
@wait.until { @browser.find_element(link:
|
137
|
+
@wait.until { @browser.find_element(link: @selector.logout_menu_link_text) }.click
|
123
138
|
@browser.switch_to.alert.accept
|
124
139
|
end
|
125
140
|
Kobot.logger.info 'Logout successful'
|
126
141
|
end
|
127
142
|
|
128
143
|
def read_today_record
|
129
|
-
Kobot.logger.info('Navigate to タイムカード page')
|
144
|
+
Kobot.logger.info('Navigate to Time Card (タイムカード) page')
|
130
145
|
@wait.until { @browser.find_element(id: 'menu_icon') }.click
|
131
|
-
@wait.until { @browser.find_element(link:
|
146
|
+
@wait.until { @browser.find_element(link: @selector.time_card_menu_link_text) }.click
|
132
147
|
|
133
148
|
time_table = @wait.until { @browser.find_element(css: 'div.htBlock-adjastableTableF_inner > table') }
|
134
149
|
time_table.find_elements(css: 'tbody > tr').each do |tr|
|
@@ -139,8 +154,12 @@ module Kobot
|
|
139
154
|
@kot_today = date_cell.text
|
140
155
|
@kot_today_css_class = date_cell.attribute('class')
|
141
156
|
@kot_today_type = tr.find_element(css: 'td.work_day_type').text
|
142
|
-
@kot_today_clock_in = tr.find_element(
|
143
|
-
|
157
|
+
@kot_today_clock_in = tr.find_element(
|
158
|
+
css: 'td.start_end_timerecord[data-ht-sort-index="START_TIMERECORD"]'
|
159
|
+
).text
|
160
|
+
@kot_today_clock_out = tr.find_element(
|
161
|
+
css: 'td.start_end_timerecord[data-ht-sort-index="END_TIMERECORD"]'
|
162
|
+
).text
|
144
163
|
Kobot.logger.debug do
|
145
164
|
{
|
146
165
|
kot_toay: @kot_today,
|
@@ -154,30 +173,23 @@ module Kobot
|
|
154
173
|
end
|
155
174
|
end
|
156
175
|
|
157
|
-
def
|
176
|
+
def validate_today_record!
|
158
177
|
raise KotRecordError, "Today=#{@today} is not found on kot" if @kot_today.strip.empty?
|
159
178
|
|
160
179
|
if kot_weekend?
|
161
|
-
unless Config.force
|
162
|
-
raise KotRecordError,
|
163
|
-
"Today=#{@today} is marked as weekend on kot: #{@kot_today}"
|
164
|
-
end
|
180
|
+
raise KotRecordError, "Today=#{@today} is marked as weekend on kot: #{@kot_today}" unless Config.force
|
165
181
|
|
166
182
|
Kobot.logger.info(
|
167
183
|
"[Force] should have exited: today=#{@today} is marked as weekend on kot: #{@kot_today}"
|
168
184
|
)
|
169
185
|
end
|
170
186
|
|
171
|
-
|
172
|
-
|
173
|
-
raise KotRecordError,
|
174
|
-
"Today=#{@today} is marked as public holiday on kot: #{@kot_today}"
|
175
|
-
end
|
187
|
+
return unless kot_public_holiday?
|
188
|
+
raise KotRecordError, "Today=#{@today} is marked as public holiday on kot: #{@kot_today}" unless Config.force
|
176
189
|
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
end
|
190
|
+
Kobot.logger.info(
|
191
|
+
"[Force] should have exited: today=#{@today} is marked as public holiday on kot: #{@kot_today}"
|
192
|
+
)
|
181
193
|
end
|
182
194
|
|
183
195
|
def clock_in!
|
@@ -224,9 +236,9 @@ module Kobot
|
|
224
236
|
@browser.get @top_url
|
225
237
|
clock_in_button = @wait.until { @browser.find_element(css: 'div.record-clock-in') }
|
226
238
|
if Config.dryrun
|
227
|
-
Kobot.logger.info('[Dryrun]
|
239
|
+
Kobot.logger.info('[Dryrun] Clock-in button (出勤) would have been clicked')
|
228
240
|
else
|
229
|
-
Kobot.logger.info('Clicking the
|
241
|
+
Kobot.logger.info('Clicking the Clock-in button (出勤)')
|
230
242
|
clock_in_button.click
|
231
243
|
end
|
232
244
|
end
|
@@ -236,9 +248,9 @@ module Kobot
|
|
236
248
|
@browser.get @top_url
|
237
249
|
clock_out_button = @wait.until { @browser.find_element(css: 'div.record-clock-out') }
|
238
250
|
if Config.dryrun
|
239
|
-
Kobot.logger.info('[Dryrun]
|
251
|
+
Kobot.logger.info('[Dryrun] Clock-out button (退勤) would have been clicked')
|
240
252
|
else
|
241
|
-
Kobot.logger.info('Clicking the
|
253
|
+
Kobot.logger.info('Clicking the Clock-out button (退勤)')
|
242
254
|
clock_out_button.click
|
243
255
|
end
|
244
256
|
end
|
@@ -255,18 +267,21 @@ module Kobot
|
|
255
267
|
end
|
256
268
|
|
257
269
|
def kot_weekend?
|
258
|
-
|
270
|
+
[
|
271
|
+
@selector.kot_date_saturday,
|
272
|
+
@selector.kot_date_sunday
|
273
|
+
].any? { |weekend| @kot_today&.include? weekend }
|
259
274
|
end
|
260
275
|
|
261
276
|
def kot_public_holiday?
|
262
|
-
return true if @kot_today_type&.include?
|
277
|
+
return true if @kot_today_type&.include? @selector.kot_workday_time_off_text
|
263
278
|
|
264
279
|
kot_today_highlighted = %w[sunday saturday].any? do |css|
|
265
280
|
@kot_today_css_class&.include? css
|
266
281
|
end
|
267
282
|
if kot_today_highlighted
|
268
283
|
Kobot.logger.warn(
|
269
|
-
"Today=#{@kot_today} is highlighted (holiday) but not marked as
|
284
|
+
"Today=#{@kot_today} is highlighted (holiday) but not marked as #{@selector.kot_workday_time_off_text}"
|
270
285
|
)
|
271
286
|
end
|
272
287
|
kot_today_highlighted
|
data/lib/kobot/exception.rb
CHANGED
@@ -1,14 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Kobot
|
4
|
-
|
5
|
-
class
|
6
|
-
end
|
7
|
-
|
8
|
-
class KotClockInError < StandardError
|
9
|
-
end
|
10
|
-
|
11
|
-
class KotClockOutError < StandardError
|
12
|
-
end
|
13
|
-
|
4
|
+
class KotRecordError < StandardError; end
|
5
|
+
class KotClockInError < StandardError; end
|
6
|
+
class KotClockOutError < StandardError; end
|
14
7
|
end
|
data/lib/kobot/mailer.rb
CHANGED
@@ -3,11 +3,9 @@
|
|
3
3
|
require 'net/smtp'
|
4
4
|
|
5
5
|
module Kobot
|
6
|
-
|
7
6
|
# Responsible for sending email notifications in SMTP with Gmail
|
8
7
|
class Mailer
|
9
8
|
class << self
|
10
|
-
|
11
9
|
# Sends email in preconfigured Gmail SMTP credential and to the recipient
|
12
10
|
# configured by #{Config.gmail_notify_to} or self if not configured, with
|
13
11
|
# email subject set by #{Config.gmail_notify_subject}.
|
data/lib/kobot/option.rb
CHANGED
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kobot
|
4
|
+
# The texts used in selectors to identify the elements on KOT UI.
|
5
|
+
class Selector
|
6
|
+
attr_accessor :login_success_notification_text,
|
7
|
+
:location_area_notification_text,
|
8
|
+
:time_card_menu_link_text,
|
9
|
+
:kot_date_saturday,
|
10
|
+
:kot_date_sunday,
|
11
|
+
:kot_workday_time_off_text,
|
12
|
+
:logout_menu_link_text
|
13
|
+
|
14
|
+
class << self
|
15
|
+
def en
|
16
|
+
selector = Selector.new
|
17
|
+
selector.login_success_notification_text = 'Data has been obtained'
|
18
|
+
selector.location_area_notification_text = 'Obtained location'
|
19
|
+
selector.time_card_menu_link_text = 'Time Card'
|
20
|
+
selector.kot_date_saturday = 'Sat'
|
21
|
+
selector.kot_date_sunday = 'Sun'
|
22
|
+
selector.kot_workday_time_off_text = 'time-off'
|
23
|
+
selector.logout_menu_link_text = 'Sign out'
|
24
|
+
selector
|
25
|
+
end
|
26
|
+
|
27
|
+
def ja
|
28
|
+
selector = Selector.new
|
29
|
+
selector.login_success_notification_text = 'データを取得しました'
|
30
|
+
selector.location_area_notification_text = '位置情報取得済み'
|
31
|
+
selector.time_card_menu_link_text = 'タイムカード'
|
32
|
+
selector.kot_date_saturday = '土'
|
33
|
+
selector.kot_date_sunday = '日'
|
34
|
+
selector.kot_workday_time_off_text = '休日'
|
35
|
+
selector.logout_menu_link_text = 'ログアウト'
|
36
|
+
selector
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
data/lib/kobot/version.rb
CHANGED
data/lib/kobot.rb
CHANGED
@@ -4,6 +4,7 @@ require 'kobot/version'
|
|
4
4
|
require 'kobot/exception'
|
5
5
|
require 'kobot/option'
|
6
6
|
require 'kobot/config'
|
7
|
+
require 'kobot/selector'
|
7
8
|
require 'kobot/credential'
|
8
9
|
require 'kobot/logger'
|
9
10
|
require 'kobot/mailer'
|
@@ -14,8 +15,6 @@ require 'kobot/engine'
|
|
14
15
|
# and with Google Gmail service email notification can also be sent to notify the results.
|
15
16
|
module Kobot
|
16
17
|
class << self
|
17
|
-
|
18
|
-
# The entrance to run Kobot.
|
19
18
|
def run
|
20
19
|
configure
|
21
20
|
Engine.new.start
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: kobot
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.2.
|
4
|
+
version: 1.2.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andy Jiang
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-12-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: webdrivers
|
@@ -83,6 +83,7 @@ files:
|
|
83
83
|
- lib/kobot/logger.rb
|
84
84
|
- lib/kobot/mailer.rb
|
85
85
|
- lib/kobot/option.rb
|
86
|
+
- lib/kobot/selector.rb
|
86
87
|
- lib/kobot/version.rb
|
87
88
|
homepage: https://github.com/yuan-jiang/kobot
|
88
89
|
licenses:
|
@@ -107,7 +108,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
107
108
|
- !ruby/object:Gem::Version
|
108
109
|
version: '0'
|
109
110
|
requirements: []
|
110
|
-
rubygems_version: 3.
|
111
|
+
rubygems_version: 3.1.2
|
111
112
|
signing_key:
|
112
113
|
specification_version: 4
|
113
114
|
summary: Kobot automates the clock in/out of KING OF TIME.
|