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 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