kobot 1.1.0 → 1.2.4
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/CHANGELOG.md +20 -0
- data/README.md +7 -5
- data/bin/console +1 -0
- data/exe/kobot +10 -1
- data/lib/kobot.rb +1 -2
- data/lib/kobot/config.rb +2 -2
- data/lib/kobot/credential.rb +2 -7
- data/lib/kobot/engine.rb +59 -25
- data/lib/kobot/exception.rb +3 -10
- data/lib/kobot/mailer.rb +0 -2
- data/lib/kobot/option.rb +17 -8
- data/lib/kobot/version.rb +1 -1
- 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: 0e8cedf9053f994144d21b233b2c68fb4e03aff011192f1273e1ad24d347c4f9
|
4
|
+
data.tar.gz: 8daab86f87c5ffe5e0588c9029ccd062954ca7ad3557769861ef19fd07347c79
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4aaaa2f0e8eebe98487536158b0f571e794b76db95dec5cd2eb268037dfd0888045d6cebbeb46876162ed9944813f7429e8a4466da20ca9010ecacfd552cd302
|
7
|
+
data.tar.gz: 9ede3410f3ad664982db308f382140eb19bc8140970fc4991f68829a0c63fe664a6978b99ef5753036954a0bbabb9731b3f7e5fe08aff11c589798898ce0ec59
|
data/CHANGELOG.md
CHANGED
@@ -5,3 +5,23 @@
|
|
5
5
|
- Deprecated lower-case environment variables for credentials configuration
|
6
6
|
- Updated option help doc to indicate weekends and public holidays are skipped by default
|
7
7
|
- Updated required_ruby_version to be >= 2.4.0 to align with the webdrivers dependency
|
8
|
+
|
9
|
+
### v1.2.0
|
10
|
+
- Added an option to allow forcibly running regardless of weekends or public holidays
|
11
|
+
- Added an ENV flag to allow requiring lib in local workspace for development purpose
|
12
|
+
|
13
|
+
### v1.2.1
|
14
|
+
- Improved logging for better readability in logs
|
15
|
+
- Switched to builtin Logger#deprecate from Logger#warn for deprecations
|
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
|
data/README.md
CHANGED
@@ -57,15 +57,17 @@ Get help doc:
|
|
57
57
|
```
|
58
58
|
$ kobot -h
|
59
59
|
Usage: kobot [options]
|
60
|
-
-c, --clock CLOCK
|
61
|
-
-l, --loglevel [LEVEL] Specify log level: debug, info, warn, error
|
60
|
+
-c, --clock CLOCK Required; the clock action option: in, out
|
61
|
+
-l, --loglevel [LEVEL] Specify log level: debug, info, warn, error; default is info
|
62
62
|
-s, --skip [D1,D2,D3] Specify dates to skip clock in/out with date format YYYY-MM-DD and
|
63
|
-
multiple values separated by comma, such as: 2020-05-01,2020-12-31
|
64
|
-
|
65
|
-
-t, --to [TO] Email address to send notification to
|
63
|
+
multiple values separated by comma, such as: 2020-05-01,2020-12-31;
|
64
|
+
weekends and public holidays in Japan will be skipped by default
|
65
|
+
-t, --to [TO] Email address to send notification to; by default it is sent to
|
66
66
|
the same self email account used in SMTP config as the sender
|
67
67
|
-n, --notify Enable email notification
|
68
68
|
-d, --dryrun Run the process without actual clock in/out
|
69
|
+
-f, --force Run the process forcibly regardless of weekends or public holidays;
|
70
|
+
must be used together with --dryrun to prevent mistaken operations
|
69
71
|
-x, --headless Start browser in headless mode
|
70
72
|
-g, --geolocation Allow browser to use geolocation
|
71
73
|
-h, --help Show this help message
|
data/bin/console
CHANGED
data/exe/kobot
CHANGED
@@ -1,6 +1,15 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
-
|
4
|
+
begin
|
5
|
+
# For quickly tryout in local development
|
6
|
+
# without installing to system gems path
|
7
|
+
raise LoadError if ENV['KOBOT_DEV']
|
8
|
+
|
9
|
+
require 'kobot'
|
10
|
+
rescue LoadError
|
11
|
+
$LOAD_PATH.unshift File.expand_path('../lib', __dir__)
|
12
|
+
require 'kobot'
|
13
|
+
end
|
5
14
|
|
6
15
|
Kobot.run
|
data/lib/kobot.rb
CHANGED
@@ -14,8 +14,6 @@ require 'kobot/engine'
|
|
14
14
|
# and with Google Gmail service email notification can also be sent to notify the results.
|
15
15
|
module Kobot
|
16
16
|
class << self
|
17
|
-
|
18
|
-
# The entrance to run Kobot.
|
19
17
|
def run
|
20
18
|
configure
|
21
19
|
Engine.new.start
|
@@ -33,6 +31,7 @@ module Kobot
|
|
33
31
|
config.clock = options[:clock].to_sym
|
34
32
|
config.loglevel = options[:loglevel]&.to_sym || :info
|
35
33
|
config.dryrun = options[:dryrun]
|
34
|
+
config.force = options[:force]
|
36
35
|
config.skip = options[:skip] || []
|
37
36
|
|
38
37
|
config.kot_url = 'https://s2.kingtime.jp/independent/recorder/personal/'
|
data/lib/kobot/config.rb
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Kobot
|
4
|
-
|
5
4
|
# Configuration definition includes static ones hardcoded and
|
6
5
|
# dynamic ones that can be specified by command line options.
|
7
6
|
class Config
|
@@ -9,7 +8,8 @@ module Kobot
|
|
9
8
|
attr_accessor :clock,
|
10
9
|
:loglevel,
|
11
10
|
:skip,
|
12
|
-
:dryrun
|
11
|
+
:dryrun,
|
12
|
+
:force
|
13
13
|
|
14
14
|
attr_accessor :kot_url,
|
15
15
|
:kot_timezone_offset,
|
data/lib/kobot/credential.rb
CHANGED
@@ -4,7 +4,6 @@ module Kobot
|
|
4
4
|
# Credentials include id and password to login to KOT and
|
5
5
|
# Gmail SMTP id and password to send email notifications.
|
6
6
|
class Credential
|
7
|
-
|
8
7
|
class << self
|
9
8
|
attr_accessor :kot_id,
|
10
9
|
:kot_password,
|
@@ -30,7 +29,7 @@ module Kobot
|
|
30
29
|
@credentials.each do |attr, value|
|
31
30
|
send("#{attr}=".to_sym, value)
|
32
31
|
end
|
33
|
-
Kobot.logger.info('
|
32
|
+
Kobot.logger.info('Load credentials successful')
|
34
33
|
Kobot.logger.debug(@credentials)
|
35
34
|
end
|
36
35
|
|
@@ -49,11 +48,7 @@ module Kobot
|
|
49
48
|
required_credentials = %w[kot_id kot_password]
|
50
49
|
required_credentials.concat %w[gmail_id gmail_password] if Config.gmail_notify_enabled
|
51
50
|
required_credentials.each do |attr|
|
52
|
-
if ENV[attr]
|
53
|
-
Kobot.logger.warn(
|
54
|
-
"[DEPRECATION] lower-case ENV variable is deprecated, please use #{attr.upcase} instead."
|
55
|
-
)
|
56
|
-
end
|
51
|
+
Kobot.logger.deprecate(attr, attr.upcase) if ENV[attr]
|
57
52
|
env_attr_value = ENV[attr.upcase] || ENV[attr]
|
58
53
|
@credentials[attr] = env_attr_value if env_attr_value
|
59
54
|
end
|
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,22 +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
|
-
return
|
31
|
-
end
|
32
|
-
if holiday?
|
33
|
-
Kobot.logger.info("Today=#{@today} is holiday.")
|
34
|
-
return
|
35
|
-
end
|
36
|
-
unless %i[in out].include? Config.clock
|
37
|
-
Kobot.logger.warn("Invalid clock operation: #{Config.clock}")
|
38
|
-
return
|
39
|
-
end
|
27
|
+
return unless should_run_today?
|
28
|
+
|
40
29
|
launch_browser
|
41
30
|
login
|
42
31
|
read_today_record
|
43
|
-
|
32
|
+
validate_today_record!
|
44
33
|
if Config.clock == :in
|
45
34
|
clock_in!
|
46
35
|
else
|
@@ -65,11 +54,23 @@ module Kobot
|
|
65
54
|
Mailer.send(clock_notify_message(status: e.message))
|
66
55
|
logout
|
67
56
|
ensure
|
68
|
-
|
57
|
+
close_browser
|
69
58
|
end
|
70
59
|
|
71
60
|
private
|
72
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
|
+
|
73
74
|
def launch_browser
|
74
75
|
prefs = {
|
75
76
|
profile: {
|
@@ -85,9 +86,18 @@ module Kobot
|
|
85
86
|
Kobot.logger.info('Launch browser successful')
|
86
87
|
end
|
87
88
|
|
89
|
+
def close_browser
|
90
|
+
return unless @browser
|
91
|
+
|
92
|
+
Kobot.logger.info('Close browser')
|
93
|
+
@browser.quit
|
94
|
+
end
|
95
|
+
|
88
96
|
def login
|
89
|
-
@browser.get @top_url
|
90
97
|
Kobot.logger.info("Navigate to: #{@top_url}")
|
98
|
+
@browser.get @top_url
|
99
|
+
@wait.until { @browser.find_element(id: 'modal_window') }
|
100
|
+
Kobot.logger.info "Page title: #{@browser.title}"
|
91
101
|
Kobot.logger.debug do
|
92
102
|
"Login with id=#{Credential.kot_id} and password=#{Credential.kot_password}"
|
93
103
|
end
|
@@ -104,13 +114,15 @@ module Kobot
|
|
104
114
|
Kobot.logger.warn "Get geolocation failed: #{e.message}"
|
105
115
|
end
|
106
116
|
end
|
107
|
-
Kobot.logger.info @browser.title
|
117
|
+
Kobot.logger.info "Page title: #{@browser.title}"
|
108
118
|
end
|
109
119
|
|
110
120
|
def logout
|
111
121
|
if @browser.current_url.include? 'admin'
|
122
|
+
Kobot.logger.info('Logout from タイムカード page')
|
112
123
|
@browser.find_element(css: 'div.htBlock-header_logoutButton').click
|
113
124
|
else
|
125
|
+
Kobot.logger.info('Logout from Myレコーダー page')
|
114
126
|
@wait.until { @browser.find_element(id: 'menu_icon') }.click
|
115
127
|
@wait.until { @browser.find_element(link: 'ログアウト') }.click
|
116
128
|
@browser.switch_to.alert.accept
|
@@ -119,6 +131,7 @@ module Kobot
|
|
119
131
|
end
|
120
132
|
|
121
133
|
def read_today_record
|
134
|
+
Kobot.logger.info('Navigate to タイムカード page')
|
122
135
|
@wait.until { @browser.find_element(id: 'menu_icon') }.click
|
123
136
|
@wait.until { @browser.find_element(link: 'タイムカード') }.click
|
124
137
|
|
@@ -131,8 +144,12 @@ module Kobot
|
|
131
144
|
@kot_today = date_cell.text
|
132
145
|
@kot_today_css_class = date_cell.attribute('class')
|
133
146
|
@kot_today_type = tr.find_element(css: 'td.work_day_type').text
|
134
|
-
@kot_today_clock_in = tr.find_element(
|
135
|
-
|
147
|
+
@kot_today_clock_in = tr.find_element(
|
148
|
+
css: 'td.start_end_timerecord[data-ht-sort-index="START_TIMERECORD"]'
|
149
|
+
).text
|
150
|
+
@kot_today_clock_out = tr.find_element(
|
151
|
+
css: 'td.start_end_timerecord[data-ht-sort-index="END_TIMERECORD"]'
|
152
|
+
).text
|
136
153
|
Kobot.logger.debug do
|
137
154
|
{
|
138
155
|
kot_toay: @kot_today,
|
@@ -146,10 +163,23 @@ module Kobot
|
|
146
163
|
end
|
147
164
|
end
|
148
165
|
|
149
|
-
def
|
150
|
-
raise KotRecordError, "Today=#{@today} is not found on kot
|
151
|
-
|
152
|
-
|
166
|
+
def validate_today_record!
|
167
|
+
raise KotRecordError, "Today=#{@today} is not found on kot" if @kot_today.strip.empty?
|
168
|
+
|
169
|
+
if kot_weekend?
|
170
|
+
raise KotRecordError, "Today=#{@today} is marked as weekend on kot: #{@kot_today}" unless Config.force
|
171
|
+
|
172
|
+
Kobot.logger.info(
|
173
|
+
"[Force] should have exited: today=#{@today} is marked as weekend on kot: #{@kot_today}"
|
174
|
+
)
|
175
|
+
end
|
176
|
+
|
177
|
+
return unless kot_public_holiday?
|
178
|
+
raise KotRecordError, "Today=#{@today} is marked as public holiday on kot: #{@kot_today}" unless Config.force
|
179
|
+
|
180
|
+
Kobot.logger.info(
|
181
|
+
"[Force] should have exited: today=#{@today} is marked as public holiday on kot: #{@kot_today}"
|
182
|
+
)
|
153
183
|
end
|
154
184
|
|
155
185
|
def clock_in!
|
@@ -192,21 +222,25 @@ module Kobot
|
|
192
222
|
end
|
193
223
|
|
194
224
|
def click_clock_in_button
|
225
|
+
Kobot.logger.info("Navigate to: #{@top_url}")
|
195
226
|
@browser.get @top_url
|
196
227
|
clock_in_button = @wait.until { @browser.find_element(css: 'div.record-clock-in') }
|
197
228
|
if Config.dryrun
|
198
229
|
Kobot.logger.info('[Dryrun] clock in button (出勤) would have been clicked')
|
199
230
|
else
|
231
|
+
Kobot.logger.info('Clicking the clock in button (出勤)')
|
200
232
|
clock_in_button.click
|
201
233
|
end
|
202
234
|
end
|
203
235
|
|
204
236
|
def click_clock_out_button
|
237
|
+
Kobot.logger.info("Navigate to: #{@top_url}")
|
205
238
|
@browser.get @top_url
|
206
239
|
clock_out_button = @wait.until { @browser.find_element(css: 'div.record-clock-out') }
|
207
240
|
if Config.dryrun
|
208
241
|
Kobot.logger.info('[Dryrun] clock out button (退勤) would have been clicked')
|
209
242
|
else
|
243
|
+
Kobot.logger.info('Clicking the clock in button (退勤)')
|
210
244
|
clock_out_button.click
|
211
245
|
end
|
212
246
|
end
|
@@ -215,7 +249,7 @@ module Kobot
|
|
215
249
|
@now.saturday? || @now.sunday?
|
216
250
|
end
|
217
251
|
|
218
|
-
def
|
252
|
+
def skip?
|
219
253
|
return false unless Config.skip
|
220
254
|
return false unless Config.skip.respond_to? :include?
|
221
255
|
|
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
@@ -3,34 +3,32 @@
|
|
3
3
|
require 'optparse'
|
4
4
|
|
5
5
|
module Kobot
|
6
|
-
|
7
6
|
# Responsible for parsing the command line options for custom execution.
|
8
7
|
class Option
|
9
8
|
class << self
|
10
|
-
|
11
9
|
# Parses command line options and returns a hash containing the options.
|
12
10
|
def parse!
|
13
11
|
options = {}
|
14
12
|
opt_parser = OptionParser.new do |opt|
|
15
13
|
opt.banner = "Usage: #{$PROGRAM_NAME} [options]"
|
16
14
|
|
17
|
-
opt.on('-c', '--clock CLOCK', '
|
15
|
+
opt.on('-c', '--clock CLOCK', 'Required; the clock action option: in, out') do |clock|
|
18
16
|
options[:clock] = clock
|
19
17
|
end
|
20
18
|
|
21
|
-
opt.on('-l', '--loglevel [LEVEL]', 'Specify log level: debug, info, warn, error
|
19
|
+
opt.on('-l', '--loglevel [LEVEL]', 'Specify log level: debug, info, warn, error; default is info') do |level|
|
22
20
|
options[:loglevel] = level
|
23
21
|
end
|
24
22
|
|
25
23
|
opt.on('-s', '--skip [D1,D2,D3]', Array,
|
26
24
|
'Specify dates to skip clock in/out with date format YYYY-MM-DD and',
|
27
|
-
'multiple values separated by comma, such as: 2020-05-01,2020-12-31',
|
28
|
-
'
|
25
|
+
'multiple values separated by comma, such as: 2020-05-01,2020-12-31;',
|
26
|
+
'weekends and public holidays in Japan will be skipped by default') do |skip|
|
29
27
|
options[:skip] = skip
|
30
28
|
end
|
31
29
|
|
32
30
|
opt.on('-t', '--to [TO]',
|
33
|
-
'Email address to send notification to
|
31
|
+
'Email address to send notification to; by default it is sent to',
|
34
32
|
'the same self email account used in SMTP config as the sender') do |to|
|
35
33
|
options[:to] = to
|
36
34
|
end
|
@@ -43,6 +41,12 @@ module Kobot
|
|
43
41
|
options[:dryrun] = dryrun
|
44
42
|
end
|
45
43
|
|
44
|
+
opt.on('-f', '--force',
|
45
|
+
'Run the process forcibly regardless of weekends or public holidays;',
|
46
|
+
'must be used together with --dryrun to prevent mistaken operations') do |force|
|
47
|
+
options[:force] = force
|
48
|
+
end
|
49
|
+
|
46
50
|
opt.on('-x', '--headless', 'Start browser in headless mode') do |headless|
|
47
51
|
options[:headless] = headless
|
48
52
|
end
|
@@ -63,7 +67,12 @@ module Kobot
|
|
63
67
|
end
|
64
68
|
opt_parser.parse! ARGV
|
65
69
|
raise OptionParser::MissingArgument, 'The clock option is required' if options[:clock].nil?
|
66
|
-
|
70
|
+
unless %w[in out].include? options[:clock]
|
71
|
+
raise OptionParser::InvalidArgument, 'The clock option must be either: in, out'
|
72
|
+
end
|
73
|
+
if options[:force] && !options[:dryrun]
|
74
|
+
raise OptionParser::InvalidArgument, 'Must specify --dryrun for forcibly running'
|
75
|
+
end
|
67
76
|
|
68
77
|
options
|
69
78
|
rescue OptionParser::MissingArgument, OptionParser::InvalidArgument => e
|
data/lib/kobot/version.rb
CHANGED
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.
|
4
|
+
version: 1.2.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andy Jiang
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-09-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: webdrivers
|