kobot 1.1.0 → 1.2.4
Sign up to get free protection for your applications and to get access to all the features.
- 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
|