kobot 1.2.2 → 1.3.0

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: c5280569d35d3db17de90830bc1b6b2fa7fd4ec17c1b3efae0772a2329288fbe
4
- data.tar.gz: 51a08ceb2e07e577a503e6c5bbd17352e664612a9b6ae6fd6e3ca9aedc56a71e
3
+ metadata.gz: '08261437cd35a8b09dfad79740f2f6ec52e87fea47713aba5e00133aa570ac08'
4
+ data.tar.gz: a7941208f88d46d40fb8d5366851f5b466bb85e975b3d5ff95e7753bf9f8032c
5
5
  SHA512:
6
- metadata.gz: 9ce2200116a9c36f384edbc2a0a2b47b37819ddb44fb8f590a1a1af1afd0859ea4d56c2022c4990f720f9bbd42eb4aaaf26986333298ed61580a4ad37b41bbed
7
- data.tar.gz: 783cd8e9c729404108119fc2c26b49729852c68e40e415dad7d5ab92066021a4c2496fe3aaeab355911ad174b848af1cda99510496a53ff282f5d4fa42f8d244
6
+ metadata.gz: f14ebacda3eaec90ae6f6e9f995821d3116599e2fdff2e19afcd546c43404f6453055d256828f4f549e07d5f3e08ac0ba4cbc1c6ee0e4b3739a02277b0285258
7
+ data.tar.gz: d42d8f45187cc512eb08b13d5c891cd7b0bf567a76bf55b49ffc089c240946f0345208b7586fecbd2a32c8e3f3855f22086913bed5c79152e3248e5db0692d75
data/CHANGELOG.md CHANGED
@@ -18,3 +18,18 @@
18
18
  ### v1.2.2
19
19
  - Improved login screen wait and logging
20
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
31
+
32
+ ### v1.3.0
33
+ - Added config option (-a, --auto-skip-without) to enable automatic skipping based on schedule
34
+ - Replaced deprecated options with capabilities option in Selenium driver initialization
35
+ - Refactored scattered attr_accessors and reduced instance variables count reported by Rubocop
data/README.md CHANGED
@@ -62,6 +62,9 @@ Usage: kobot [options]
62
62
  -s, --skip [D1,D2,D3] Specify dates to skip clock in/out with date format YYYY-MM-DD and
63
63
  multiple values separated by comma, such as: 2020-05-01,2020-12-31;
64
64
  weekends and public holidays in Japan will be skipped by default
65
+ -a [SCHEDULE], Enable automatic skipping by reading schedule from the Time Card page;
66
+ --auto-skip-without skip today if the provided text is not contained in the schedule column
67
+ on the Time Card page; by default 裁量労働 is used
65
68
  -t, --to [TO] Email address to send notification to; by default it is sent to
66
69
  the same self email account used in SMTP config as the sender
67
70
  -n, --notify Enable email notification
data/lib/kobot/config.rb CHANGED
@@ -8,24 +8,21 @@ module Kobot
8
8
  attr_accessor :clock,
9
9
  :loglevel,
10
10
  :skip,
11
+ :auto_skip_without,
11
12
  :dryrun,
12
- :force
13
-
14
- attr_accessor :kot_url,
13
+ :force,
14
+ :kot_url,
15
15
  :kot_timezone_offset,
16
- :kot_date_format
17
-
18
- attr_accessor :gmail_notify_enabled,
16
+ :kot_date_format,
17
+ :gmail_notify_enabled,
19
18
  :gmail_notify_subject,
20
19
  :gmail_notify_to,
21
20
  :gmail_smtp_address,
22
- :gmail_smtp_port
23
-
24
- attr_accessor :browser_headless,
21
+ :gmail_smtp_port,
22
+ :browser_headless,
25
23
  :browser_geolocation,
26
- :browser_wait_timeout
27
-
28
- attr_accessor :credentials_file
24
+ :browser_wait_timeout,
25
+ :credentials_file
29
26
 
30
27
  def configure
31
28
  yield self
data/lib/kobot/engine.rb CHANGED
@@ -14,7 +14,7 @@ module Kobot
14
14
 
15
15
  # The entrance where the whole flow starts.
16
16
  #
17
- # 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
18
18
  # the #{Config.skip} specified from command line option --skip.
19
19
  #
20
20
  # Unexpected behavior such as record appearing as holiday on
@@ -24,26 +24,12 @@ module Kobot
24
24
  # System errors or any unknown exceptions occurred if any are
25
25
  # to be popped up and should be handled by the outside caller.
26
26
  def start
27
- if weekend?
28
- if Config.force
29
- Kobot.logger.info("[Force] should have exited: today=#{@today} is weekend")
30
- else
31
- Kobot.logger.info("Today=#{@today} is weekend")
32
- return
33
- end
34
- end
35
- if skip?
36
- Kobot.logger.info("Today=#{@today} is skipped as per: --skip=#{Config.skip}")
37
- return
38
- end
39
- unless %i[in out].include? Config.clock
40
- Kobot.logger.warn("Invalid clock operation: #{Config.clock}")
41
- return
42
- end
27
+ return unless should_run_today?
28
+
43
29
  launch_browser
44
30
  login
45
31
  read_today_record
46
- verify_today_record!
32
+ validate_today_record!
47
33
  if Config.clock == :in
48
34
  clock_in!
49
35
  else
@@ -68,12 +54,23 @@ module Kobot
68
54
  Mailer.send(clock_notify_message(status: e.message))
69
55
  logout
70
56
  ensure
71
- Kobot.logger.info('Close browser')
72
- @browser&.quit
57
+ close_browser
73
58
  end
74
59
 
75
60
  private
76
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
+
77
74
  def launch_browser
78
75
  prefs = {
79
76
  profile: {
@@ -84,15 +81,31 @@ module Kobot
84
81
  }
85
82
  options = Selenium::WebDriver::Chrome::Options.new(prefs: prefs)
86
83
  options.headless! if Config.browser_headless
87
- @browser = Selenium::WebDriver.for(:chrome, options: options)
84
+ caps = [
85
+ options
86
+ ]
87
+ @browser = Selenium::WebDriver.for(:chrome, capabilities: caps)
88
88
  @wait = Selenium::WebDriver::Wait.new(timeout: Config.browser_wait_timeout)
89
89
  Kobot.logger.info('Launch browser successful')
90
90
  end
91
91
 
92
+ def close_browser
93
+ return unless @browser
94
+
95
+ Kobot.logger.info('Close browser')
96
+ @browser.quit
97
+ end
98
+
92
99
  def login
93
100
  Kobot.logger.info("Navigate to: #{@top_url}")
94
101
  @browser.get @top_url
95
102
  @wait.until { @browser.find_element(id: 'modal_window') }
103
+ modal_title_element = @wait.until { @browser.find_element(css: '.modal-title') }
104
+ @selector = if modal_title_element.text.downcase.include? 'password'
105
+ Selector.en
106
+ else
107
+ Selector.ja
108
+ end
96
109
  Kobot.logger.info "Page title: #{@browser.title}"
97
110
  Kobot.logger.debug do
98
111
  "Login with id=#{Credential.kot_id} and password=#{Credential.kot_password}"
@@ -102,10 +115,14 @@ module Kobot
102
115
  @browser.find_element(css: 'div.btn-control-message').click
103
116
 
104
117
  Kobot.logger.info 'Login successful'
105
- @wait.until { @browser.find_element(id: 'notification_content').text.include?('データを取得しました') }
118
+ @wait.until do
119
+ @browser.find_element(id: 'notification_content').text.include?(@selector.login_success_notification_text)
120
+ end
106
121
  if Config.browser_geolocation
107
122
  begin
108
- @wait.until { @browser.find_element(id: 'location_area').text.include?('位置情報取得済み') }
123
+ @wait.until do
124
+ @browser.find_element(id: 'location_area').text.include?(@selector.location_area_notification_text)
125
+ end
109
126
  rescue StandardError => e
110
127
  Kobot.logger.warn "Get geolocation failed: #{e.message}"
111
128
  end
@@ -115,21 +132,21 @@ module Kobot
115
132
 
116
133
  def logout
117
134
  if @browser.current_url.include? 'admin'
118
- Kobot.logger.info('Logout from タイムカード page')
135
+ Kobot.logger.info('Logout from Time Card (タイムカード) page')
119
136
  @browser.find_element(css: 'div.htBlock-header_logoutButton').click
120
137
  else
121
- Kobot.logger.info('Logout from Myレコーダー page')
138
+ Kobot.logger.info('Logout from My Recorder (Myレコーダー) page')
122
139
  @wait.until { @browser.find_element(id: 'menu_icon') }.click
123
- @wait.until { @browser.find_element(link: 'ログアウト') }.click
140
+ @wait.until { @browser.find_element(link: @selector.logout_menu_link_text) }.click
124
141
  @browser.switch_to.alert.accept
125
142
  end
126
143
  Kobot.logger.info 'Logout successful'
127
144
  end
128
145
 
129
146
  def read_today_record
130
- Kobot.logger.info('Navigate to タイムカード page')
147
+ Kobot.logger.info('Navigate to Time Card (タイムカード) page')
131
148
  @wait.until { @browser.find_element(id: 'menu_icon') }.click
132
- @wait.until { @browser.find_element(link: 'タイムカード') }.click
149
+ @wait.until { @browser.find_element(link: @selector.time_card_menu_link_text) }.click
133
150
 
134
151
  time_table = @wait.until { @browser.find_element(css: 'div.htBlock-adjastableTableF_inner > table') }
135
152
  time_table.find_elements(css: 'tbody > tr').each do |tr|
@@ -137,83 +154,94 @@ module Kobot
137
154
  next unless date_cell.text.include? @today
138
155
 
139
156
  Kobot.logger.info('Reading today record')
140
- @kot_today = date_cell.text
141
- @kot_today_css_class = date_cell.attribute('class')
142
- @kot_today_type = tr.find_element(css: 'td.work_day_type').text
143
- @kot_today_clock_in = tr.find_element(
157
+ @kot_data = {}
158
+ @kot_data[:today] = date_cell.text
159
+ @kot_data[:today_css_class] = date_cell.attribute('class')
160
+ @kot_data[:today_type] = tr.find_element(css: 'td.work_day_type').text
161
+ @kot_data[:today_schedule] = tr.find_element(css: 'td.schedule').text
162
+ @kot_data[:today_clock_in] = tr.find_element(
144
163
  css: 'td.start_end_timerecord[data-ht-sort-index="START_TIMERECORD"]'
145
164
  ).text
146
- @kot_today_clock_out = tr.find_element(
165
+ @kot_data[:today_clock_out] = tr.find_element(
147
166
  css: 'td.start_end_timerecord[data-ht-sort-index="END_TIMERECORD"]'
148
167
  ).text
149
168
  Kobot.logger.debug do
150
169
  {
151
- kot_toay: @kot_today,
152
- kot_today_css_class: @kot_today_css_class,
153
- kot_today_type: @kot_today_type,
154
- kot_today_clock_in: @kot_today_clock_in,
155
- kot_today_clock_out: @kot_today_clock_out
170
+ kot_toay: @kot_data[:today],
171
+ kot_today_css_class: @kot_data[:today_css_class],
172
+ kot_today_type: @kot_data[:today_type],
173
+ kot_today_clock_in: @kot_data[:today_clock_in],
174
+ kot_today_clock_out: @kot_data[:today_clock_out]
156
175
  }
157
176
  end
158
177
  break
159
178
  end
160
179
  end
161
180
 
162
- def verify_today_record!
163
- raise KotRecordError, "Today=#{@today} is not found on kot" if @kot_today.strip.empty?
181
+ def validate_today_record!
182
+ raise KotRecordError, "Today=#{@today} is not found on kot" if @kot_data[:today].strip.empty?
164
183
 
165
184
  if kot_weekend?
166
- raise KotRecordError, "Today=#{@today} is marked as weekend on kot: #{@kot_today}" unless Config.force
185
+ raise KotRecordError, "Today=#{@today} is marked as weekend: #{@kot_data[:today]}" unless Config.force
186
+
187
+ Kobot.logger.info(
188
+ "[Force] should have exited: today=#{@today} is marked as weekend: #{@kot_data[:today]}"
189
+ )
190
+ end
191
+
192
+ if kot_public_holiday?
193
+ raise KotRecordError, "Today=#{@today} is marked as public holiday: #{@kot_data[:today]}" unless Config.force
167
194
 
168
195
  Kobot.logger.info(
169
- "[Force] should have exited: today=#{@today} is marked as weekend on kot: #{@kot_today}"
196
+ "[Force] should have exited: today=#{@today} is marked as public holiday: #{@kot_data[:today]}"
170
197
  )
171
198
  end
172
199
 
173
- return unless kot_public_holiday?
174
- raise KotRecordError, "Today=#{@today} is marked as public holiday on kot: #{@kot_today}" unless Config.force
200
+ if kot_non_work_schedule?
201
+ raise KotRecordError, "Today=#{@today} is non-work schedule: #{@kot_data[:today_schedule]}" unless Config.force
175
202
 
176
- Kobot.logger.info(
177
- "[Force] should have exited: today=#{@today} is marked as public holiday on kot: #{@kot_today}"
178
- )
203
+ Kobot.logger.info(
204
+ "[Force] should have exited: today=#{@today} is non-work schedule: #{@kot_data[:today_schedule]}"
205
+ )
206
+ end
179
207
  end
180
208
 
181
209
  def clock_in!
182
210
  Kobot.logger.warn("Clock in during the afternoon: #{@now}") if @now.hour > 12
183
- if @kot_today_clock_in.strip.empty?
211
+ if @kot_data[:today_clock_in].strip.empty?
184
212
  click_clock_in_button
185
213
  return if Config.dryrun
186
214
 
187
215
  read_today_record
188
- raise KotClockInError, 'Clock in operation seems to have failed' if @kot_today_clock_in.strip.empty?
216
+ raise KotClockInError, 'Clock in operation seems to have failed' if @kot_data[:today_clock_in].strip.empty?
189
217
 
190
- Kobot.logger.info("Clock in successful: #{@kot_today_clock_in}")
218
+ Kobot.logger.info("Clock in successful: #{@kot_data[:today_clock_in]}")
191
219
  Mailer.send(clock_notify_message(clock: :in))
192
220
  else
193
- Kobot.logger.warn("Clock in done already: #{@kot_today_clock_in}")
221
+ Kobot.logger.warn("Clock in done already: #{@kot_data[:today_clock_in]}")
194
222
  end
195
223
  end
196
224
 
197
225
  def clock_out!
198
226
  Kobot.logger.warn("Clock out during the morning: #{@now}") if @now.hour <= 12
199
227
  unless Config.dryrun
200
- if @kot_today_clock_in.strip.empty?
228
+ if @kot_data[:today_clock_in].strip.empty?
201
229
  raise KotClockOutError,
202
- "!!!No clock in record for today=#{@kot_today}!!!"
230
+ "!!!No clock in record for today=#{@kot_data[:today]}!!!"
203
231
  end
204
232
  end
205
233
 
206
- if @kot_today_clock_out.strip.empty?
234
+ if @kot_data[:today_clock_out].strip.empty?
207
235
  click_clock_out_button
208
236
  return if Config.dryrun
209
237
 
210
238
  read_today_record
211
- raise KotClockOutError, 'Clock out operation seems to have failed' if @kot_today_clock_out.strip.empty?
239
+ raise KotClockOutError, 'Clock out operation seems to have failed' if @kot_data[:today_clock_out].strip.empty?
212
240
 
213
- Kobot.logger.info("Clock out successful: #{@kot_today_clock_out}")
241
+ Kobot.logger.info("Clock out successful: #{@kot_data[:today_clock_out]}")
214
242
  Mailer.send(clock_notify_message(clock: :out))
215
243
  else
216
- Kobot.logger.warn("Clock out done already: #{@kot_today_clock_out}")
244
+ Kobot.logger.warn("Clock out done already: #{@kot_data[:today_clock_out]}")
217
245
  end
218
246
  end
219
247
 
@@ -222,9 +250,9 @@ module Kobot
222
250
  @browser.get @top_url
223
251
  clock_in_button = @wait.until { @browser.find_element(css: 'div.record-clock-in') }
224
252
  if Config.dryrun
225
- Kobot.logger.info('[Dryrun] clock in button (出勤) would have been clicked')
253
+ Kobot.logger.info('[Dryrun] Clock-in button (出勤) would have been clicked')
226
254
  else
227
- Kobot.logger.info('Clicking the clock in button (出勤)')
255
+ Kobot.logger.info('Clicking the Clock-in button (出勤)')
228
256
  clock_in_button.click
229
257
  end
230
258
  end
@@ -234,9 +262,9 @@ module Kobot
234
262
  @browser.get @top_url
235
263
  clock_out_button = @wait.until { @browser.find_element(css: 'div.record-clock-out') }
236
264
  if Config.dryrun
237
- Kobot.logger.info('[Dryrun] clock out button (退勤) would have been clicked')
265
+ Kobot.logger.info('[Dryrun] Clock-out button (退勤) would have been clicked')
238
266
  else
239
- Kobot.logger.info('Clicking the clock in button (退勤)')
267
+ Kobot.logger.info('Clicking the Clock-out button (退勤)')
240
268
  clock_out_button.click
241
269
  end
242
270
  end
@@ -253,18 +281,25 @@ module Kobot
253
281
  end
254
282
 
255
283
  def kot_weekend?
256
- %w[土 日].any? { |kanji| @kot_today&.include? kanji }
284
+ [
285
+ @selector.kot_date_saturday,
286
+ @selector.kot_date_sunday
287
+ ].any? { |weekend| @kot_data[:today]&.include? weekend }
288
+ end
289
+
290
+ def kot_non_work_schedule?
291
+ !@kot_data[:today_schedule]&.include?(Config.auto_skip_without)
257
292
  end
258
293
 
259
294
  def kot_public_holiday?
260
- return true if @kot_today_type&.include? '休日'
295
+ return true if @kot_data[:today_type]&.include? @selector.kot_workday_time_off_text
261
296
 
262
297
  kot_today_highlighted = %w[sunday saturday].any? do |css|
263
- @kot_today_css_class&.include? css
298
+ @kot_data[:today_css_class]&.include? css
264
299
  end
265
300
  if kot_today_highlighted
266
301
  Kobot.logger.warn(
267
- "Today=#{@kot_today} is highlighted (holiday) but not marked as 休日"
302
+ "Today=#{@kot_data[:today]} is highlighted (holiday) but not marked as #{@selector.kot_workday_time_off_text}"
268
303
  )
269
304
  end
270
305
  kot_today_highlighted
@@ -276,8 +311,8 @@ module Kobot
276
311
  "<b>Date:</b> #{@today}",
277
312
  "<b>Status:</b> <span style='color:#{color}'>#{status}</span>"
278
313
  ]
279
- message << "<b>Clock_in:</b> #{@kot_today_clock_in}" if clock
280
- message << "<b>Clock_out:</b> #{@kot_today_clock_out}" if clock == :out
314
+ message << "<b>Clock_in:</b> #{@kot_data[:today_clock_in]}" if clock
315
+ message << "<b>Clock_out:</b> #{@kot_data[:today_clock_out]}" if clock == :out
281
316
  message.join('<br>')
282
317
  end
283
318
  end
@@ -1,12 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Kobot
4
- class KotRecordError < StandardError
5
- end
6
-
7
- class KotClockInError < StandardError
8
- end
9
-
10
- class KotClockOutError < StandardError
11
- end
4
+ class KotRecordError < StandardError; end
5
+ class KotClockInError < StandardError; end
6
+ class KotClockOutError < StandardError; end
12
7
  end
data/lib/kobot/option.rb CHANGED
@@ -27,6 +27,13 @@ module Kobot
27
27
  options[:skip] = skip
28
28
  end
29
29
 
30
+ opt.on('-a', '--auto-skip-without [SCHEDULE]',
31
+ 'Enable automatic skipping by reading schedule from the Time Card page;',
32
+ 'skip today if the provided text is not contained in the schedule column',
33
+ 'on the Time Card page; by default 裁量労働 is used') do |schedule|
34
+ options[:auto_skip_without] = schedule
35
+ end
36
+
30
37
  opt.on('-t', '--to [TO]',
31
38
  'Email address to send notification to; by default it is sent to',
32
39
  'the same self email account used in SMTP config as the sender') do |to|
@@ -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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Kobot
4
- VERSION = '1.2.2'
4
+ VERSION = '1.3.0'
5
5
  end
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'
@@ -33,6 +34,7 @@ module Kobot
33
34
  config.dryrun = options[:dryrun]
34
35
  config.force = options[:force]
35
36
  config.skip = options[:skip] || []
37
+ config.auto_skip_without = options[:auto_skip_without] || '裁量労働'
36
38
 
37
39
  config.kot_url = 'https://s2.kingtime.jp/independent/recorder/personal/'
38
40
  config.kot_timezone_offset = '+09:00'
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.2
4
+ version: 1.3.0
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-09-03 00:00:00.000000000 Z
11
+ date: 2021-12-08 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.0.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.