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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 00f2c1151ffcb202b20d4b3f9881c9570b1075ea3684f7ab3ea2cfc65baf4b33
4
- data.tar.gz: a6f3da0260abbbfce192557ea580526698b828ed19ec7175dd64080fa437ba35
3
+ metadata.gz: 0e8cedf9053f994144d21b233b2c68fb4e03aff011192f1273e1ad24d347c4f9
4
+ data.tar.gz: 8daab86f87c5ffe5e0588c9029ccd062954ca7ad3557769861ef19fd07347c79
5
5
  SHA512:
6
- metadata.gz: 4fb7e982db990bdbf659009501f3eb028d02fdddf7e8d6ea48762495867196f5dfc748c6d14926d5239b57c4433dad3d2568c8d6ca9bf42d6de401b91391e7a3
7
- data.tar.gz: '06482f411bfbf8f0083d0750e7d8ea4074f6740de3e9b1d6ba27c3e850356cd28c39d83d3a27f5d99bf7e21589cce65d64396793ffd018180ec621f926db7327'
6
+ metadata.gz: 4aaaa2f0e8eebe98487536158b0f571e794b76db95dec5cd2eb268037dfd0888045d6cebbeb46876162ed9944813f7429e8a4466da20ca9010ecacfd552cd302
7
+ data.tar.gz: 9ede3410f3ad664982db308f382140eb19bc8140970fc4991f68829a0c63fe664a6978b99ef5753036954a0bbabb9731b3f7e5fe08aff11c589798898ce0ec59
@@ -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 The clock action: in, out
61
- -l, --loglevel [LEVEL] Specify log level: debug, info, warn, error. Default is info
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
- Weekends and public holidays in Japan are skipped by default.
65
- -t, --to [TO] Email address to send notification to. By default it is sent 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
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
4
  require 'bundler/setup'
4
5
  require 'kobot'
data/exe/kobot CHANGED
@@ -1,6 +1,15 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
- require 'kobot'
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
@@ -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/'
@@ -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,
@@ -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('Credentials load successful')
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
@@ -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 treated as holiday by
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
- if weekend?
29
- Kobot.logger.info("Today=#{@today} is weekend.")
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
- verify_today_record!
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
- @browser&.quit
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(css: 'td.start_end_timerecord[data-ht-sort-index="START_TIMERECORD"]').text
135
- @kot_today_clock_out = tr.find_element(css: 'td.start_end_timerecord[data-ht-sort-index="END_TIMERECORD"]').text
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 verify_today_record!
150
- raise KotRecordError, "Today=#{@today} is not found on kot." if @kot_today.strip.empty?
151
- raise KotRecordError, "Today=#{@today} is marked as weekend on kot: #{@kot_today}" if kot_weekend?
152
- raise KotRecordError, "Today=#{@today} is marked as public holiday on kot: #{@kot_today}" if kot_public_holiday?
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 holiday?
252
+ def skip?
219
253
  return false unless Config.skip
220
254
  return false unless Config.skip.respond_to? :include?
221
255
 
@@ -1,14 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Kobot
4
-
5
- class KotRecordError < StandardError
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
@@ -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}.
@@ -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', 'The clock action: in, out') do |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. Default is info') do |level|
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
- 'Weekends and public holidays in Japan are skipped by default.') do |skip|
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. By default it is sent 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
- raise OptionParser::InvalidArgument, 'The clock option must be either: in, out' unless %w[in out].include? options[:clock]
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Kobot
4
- VERSION = '1.1.0'
4
+ VERSION = '1.2.4'
5
5
  end
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.1.0
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-08-22 00:00:00.000000000 Z
11
+ date: 2020-09-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: webdrivers