勤怠管理システムを自動で操作するChrome拡張機能を作って１クリック出社をするようになった話

## はじめに

この記事は、[Supershipグループ Advent Calendar 2021](https://qiita.com/advent-calendar/2021/supership)の9日目の記事になります。 

## 勤怠入力が面倒くさい

勤怠入力は、仕事において実に単調で面白みに欠ける作業の１つです。
毎日特定のサイトに遷移して同じボタンをクリックさせられる気が狂いそうな作業でもあります。
他の操作をやるとしても日付を操作したり、有給休暇の理由を軽く書いたりする程度。
なので自動化したい欲が溢れ出てくる作業の１つになっています。

一方で、勤怠入力を怠ると、出勤日数が減り、給料にも影響するというあまりにもリスクのある作業でもあります。
完全に自動化して裏で入力に失敗されると困るので確実に入力を完了することを可視化する必要もあります。

そんな面倒な作業を軽減するために数年にも渡って試行錯誤してしまったのでここに書き残します。

## 自動化の手段

入社してから様々な手段を試してきました。

1. 手入力時代
2. Node.js ([nightmare](https://github.com/segmentio/nightmare)) 
3. Node.js ([puppeteer](https://github.com/puppeteer/puppeteer))
4. Swift（mac OS アプリ） + Node.js （puppeteer）
5. Chrome 拡張機能

## Node.js （nightmare / puppeteer）

2年間の手入力時代を経て、決心し作りました。

勤怠管理システムにAPIが用意されていないのでChromium制御による自動入力をしていました。
毎回コマンドを叩く感じでしたが、途中から**コマンドファイル**を作成してクリックする方式に変更しました。

```sh:勤怠入力.command
#!/bin/sh

DIR=$(cd $(dirname $0) && pwd)

node $DIR/index.js -c $DIR/config.json
```

例の場合だと`config.json`で入力に必要な情報を渡して記入していました。

あと、パーミッションの変更が必要になります。

```sh
chmod 755 勤怠入力.command
```

このコマンドファイルを**Dockに追加**して毎回クリックする方式にしていました。
通信状況によって記入漏れがあり、月末に確認する作業が必要でした。

## Swift（mac OS アプリ） + Node.js （puppeteer）

これまた2年くらい経つとクリックが面倒になってきたのでPCの起動やスリープに反応して勤怠入力をするソフトウェアを作りました。

Macの状態を監視する常駐アプリをSwiftで書き、既存のpuppeteerで入力していました。
本来ならElectronでも実現できると思うのですが、Swiftの方が書き慣れている人間なのでSwiftで書いています。


Swift（mac OS アプリ）では `NSWorkspace.screensDidSleepNotification` 等の通知イベントから
[Process](https://developer.apple.com/documentation/foundation/process)を使ってNode.jsを実行しています。

```swift
// Sleep
NSWorkspace.shared.notificationCenter.addObserver(self,
                                                  selector: #selector(sleep),
                                                  name: NSWorkspace.screensDidSleepNotification,
                                                  object: nil)
    
// Wake
NSWorkspace.shared.notificationCenter.addObserver(self,
                                                  selector: #selector(wake),
                                                  name: NSWorkspace.screensDidWakeNotification,
                                                  object: nil)
```

入力漏れは前回と同じくらいありました。

そしてここまで作ってようやく気づいたことがあります。
PCの状態に合わせた精度の高い時間の入力は求められていないということを...。

**いくら細かい入力をしても１円も給料が上がらないということを...**。

## Chrome 拡張機能

勤怠入力の負担が軽減した代わりに、Node.js の更新作業、ログイン情報の管理、OSの依存、入力漏れ等のさまざまな問題も新たに発生しました。こちらもまた面倒な問題です。
そこで考えたのが、「細かい入力が求められていないのであれば、諸問題を解決する方法に変更する」です。

Node.js の更新作業・ログイン情報の管理が不要で、OSの依存がなく、入力漏れも気付ける手段はないかと探した結果、

辿り着いたのが**Chrome拡張機能**でした。

- ブラウザを更新すれば最新の状態を保てる
- パスワード管理ツールを使って自動入力ができる [^1]
- OSの依存がない
- ブラウザを眺めていれば万一の失敗にも気づける

と新たに悩んでいた点がほぼすべて解消できます。

[^1]: 実は、タイトルに1クリックと書きましたがLastPass等の`Event.isTrusted`をチェックしている仕組みが導入されている場合は、ユーザーによる2クリック目が必要になる場合があります。適当にクリックすれば、LastPassは自動入力してくれるのでさほど負担にはなっていません。

### Chrome拡張機能について

Manifest V3 が公開 [^2] されて、Manifest V2が段階的に廃止されることが告知されました [^3] 。

[^2]: [Welcome to Manifest V3 \- Chrome Developers](https://developer.chrome.com/docs/extensions/mv3/intro/)

[^3]: [Google、「Manifest V2」準拠のChrome拡張を段階的に廃止 ～2023年には利用不能に \- 窓の杜](https://forest.watch.impress.co.jp/docs/news/1353788.html)

そんなに恐れる必要はなく、大抵の場合、下記のような `manifest.json` で事足りると思います。

```json:manifest.json  
{
  "manifest_version": 3,
  "name": "勤怠入力",
  "description": "クリックすると勤怠入力を自動で行ってくれる",
  "version": "1.0.0",
  "permissions": [
    "tabs",
    "activeTab",
    "scripting"
  ],
  "host_permissions": [
    "<all_urls>"
  ],
  "icons": {
    "128": "img/128.png",
    "48": "img/48.png",
    "16": "img/16.png"
  },
  "background": {
    "service_worker": "background.js"
  },
  "content_scripts": [
    {
      "matches": [
        "https://[勤怠管理システムのログインURL]"
      ],
      "js": [
        "js/login.js"
      ]
    },
    {
      "matches": [
        "https://[勤怠管理システムのURL]"
      ],
      "js": [
        "js/kintai.js"
      ],
      "all_frames": true
    }
  ],
  "action": {
    "default_icon": "img/icon.png"
  }
}
```

## Chrome拡張機能で実際に動かしているソースコードの一部

拡張機能をクリックしたときに勤怠管理システムのURLに遷移して入力を始めます。
勤怠管理システムに情報入力をしてクリックする仕組みは各社それぞれ異なるので割愛します。

```background.js
function openURL() {
  var newURL = 'https://[勤怠管理システムのURL]#auto';
  chrome.tabs.create({ url: newURL });
}

chrome.action.onClicked.addListener((tab) => {
  if (tab.url.includes('https://[勤怠管理システムのURL]')) {
    chrome.scripting.executeScript({
      target: { tabId: tab.id },
      files: ["js/kintai_clicked.js"]
    });
  } else {
    openURL();
  }
});
```

```js/kintai.js
if (window.location.href.includes("#auto")) {
  // 各勤怠管理システムに依存するので簡単な例
  let now = new Date();
  document.querySelector('input[name=hour]') = now.getHours();
  document.querySelector('input[name=minute]') = now.getMinutes();
  document.querySelector('input[name=submit]').click();
}
```

URLに `#auto` 等の情報を付与して自動入力かどうかの判別をすると手動で入力したいときに拡張機能をわざわざ停止する必要がなくなります。`kintai_clicked.js`はその `#auto` の判定がないものになります。

開発した拡張機能は `chrome://extensions/` のデベロッパーモードを有効にしてから「パッケージ化されていない拡張機能を読み込む」ボタンを押してインストールすれば使えるようになります。

### ディレクトリ構成

最後にディレクトリ構成を載せておきます。

```:ディレクトリ構成
app/
  img/
    16.png
    48.png
    128.png
    icon.png
  js/
    kintai.js
    kintai_click.js
    login.js
  background.js
  manifest.json
```

省略していますが、アイコン画像を作成するのが一番の難所です。頑張りましょう。


以上、勤怠入力をサボりたいがためにいろいろ試行錯誤したら、
最後に辿り着いたのが**Chrome拡張機能で1クリック出社**でしたという話でした。

## おわりに

最後に宣伝
Supershipではプロダクト開発やサービス開発に関わる人を絶賛募集しております。
ご興味がある方は以下リンクよりご確認ください。
 [Supershipグループ 採用サイト](https://supership-hd.jp/recruit/)
是非ともよろしくお願いします。