まだ勤怠処理で消耗してるの？nightmareによる自動化のススメ

こんにちは！ @kumabook です。

みなさん、サラリーマンしてますか？
僕はサラリーマンしてます。

サラリーマンにとって避けて通れないものといえば...

そう、勤怠処理ですね。

僕はこの勤怠処理が嫌いです。

会社に来たら、まず出勤処理をして、仕事が終わったら、工数をつけて退勤処理してって。
本当にめんどくさいですよね。めんどくさがって月末までサボろうものなら、待っているのは

申請hell。地獄です。半日くらい持ってかれます。

弊社では、最近まで勤怠ツールが独自のwindowsアプリベースのものだったのですが、
[team-spirit](https://www.teamspirit.co.jp/) というブラウザベースのサービスに変わりました。

それでwindowsアプリの頃より幾分マシになったのですが(それまでは普段macで勤怠処理の時だけwindowsマシンを立ち上げていた)、ブラウザベースになったとはいえGUIをぽちぽち弄るのには変わりません。
日に日に勤怠処理がバカらしく思えて来ます。
全然[DRY](https://ja.wikipedia.org/wiki/Don't_repeat_yourself)じゃない。

昔同じ処理を３回書いたら関数化しろって先輩が言っていました。
僕は一体何回この不毛なことをするのか。。。
そうだ、コマンド化しよう。
ということで、勤怠処理をコマンド化することにしました。

コマンド化するにあたっては、プログラムからブラウザを操作する必要があります。
最初は[phamtomJS](http://phantomjs.org/) かなと思っていましたが、
[electron](http://electron.atom.io/)ベースの[nightmare](http://www.nightmarejs.org/)がAPIがモダンで良さそうです。こちらを使って勤怠処理をコマンド化していきます。

## 基本的な使い方

[nightmare](http://www.nightmarejs.org/)は以下のような感じでメソッドチェーンで処理をかけます。
insertはテキストを入力するメソッドでuncheckはチェックボックスを外すメソッド、clickはその名の通りクリックするメソッドです。CSSセレクタを使って要素を指定できます。
他にもevaluateメソッドで任意のjavascriptをブラウザ内で実行できます。
ユーザ名とパスワード名を入力してログインするという処理は以下のようになります。

```js

const Nightmare = require('nightmare');
const nightmare = Nightmare({ show: true });
nightmare.goto(loginUrl)
      .wait(0)
      .insert('#username', false)
      .insert('#password', false)
      .insert('#username', userName)
      .insert('#password', password)
      .uncheck('#rememberUn')
      .click('#Login')

```

## [co](https://github.com/tj/co) と組み合わせる

[nightmare](http://www.nightmarejs.org/)のメソッドは[promise](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Promise)を返してくれますが、
それでもそれぞれ非同期な処理を組み合わせて複雑なシナリオを書こうとすると煩雑になってしまいます。そこで今回は[co](https://github.com/tj/co)と組み合わせてみることにしました。[co](https://github.com/tj/co)は[generator](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Statements/function*)使って非同期処理を同期的にネストなしでかけるようにできるライブラリです。


```js

function* login(userName, password) {
  yield nightmare.goto(loginUrl)
    .insert('#username', false)
    .insert('#password', false)
    .insert('#username', userName)
    .insert('#password', password)
    .uncheck('#rememberUn')
    .click('#Login')
}

function* fetchWorkState() {
  let status = yield nightmare.goto(statusUrl)
      ...
  return status;
}

function* startWork() {
   ...
}

co(function *() {
  yield login(userName, password);
  let workStatus = yield fetchWorkStatus();
  if (workStatus !== WorkStatus.BeforeWorking) {
    self.print(`you are already working. (status=${workStatus})`);
    return Promise.resolve(ExitStatus.Failure);
  }
  yield startWork();
  console.log("Succeeded in starting work!! Let's working!! (9｀･ω･)9");
  return Promise.resolve(ExitStatus.Success);
}).then(onSuccess, onError);
```

疑似コードですが、こんな感じでプロミスを返すgenerator関数を用意しておけば
非同期処理がネストなしで書けます。
詳しくは[こちら](https://github.com/aHirokiKumamoto/hack-spirit/blob/master/lib/hack-spirit.js#L63) などを参照してください。


## 良い「待ち」を書く

基本的には今まで紹介したメソッドで、手動でやって来たことをシミュレートしていきます。
chrome developer toolなどのインスペクタでボタンやテキストボックスの要素のセレクタを調べてスクリプト化していきます。
基本的にはそれでOKなのですが、問題となるのは「待ち」です。
普段何気なくページ遷移やローディングが消えるのを待っていますが、スクリプトで表現するとなるとちと面倒です。[nightmare](http://www.nightmarejs.org/)には「待ち」の方法として以下が提供されています。

1. 指定された秒数だけ待つ wait(ms)
2. 指定された要素が存在するまで待つ wait(selector)
3. 指定されたscriptがtrueを返すまで待つ wait(fn[, arg1, arg2,...])

ページ遷移の場合は2をローディングの場合は3を使うと良いです。
具体的にはローディングの要素のstyleのdisplayプロパティや存在をチェックすると良いでしょう。
そこまでこだわりがない場合は長めに秒数をとって1で済ましてしまうという手もありますが、あまり得策ではありません。

## 出来上がったもの

今回出来上がったものが[こちら](https://github.com/aHirokiKumamoto/hack-spirit)です。
説明した機能以外にコマンドライン引数のパースやパスワードの保存・入力の受け付けなどやっています。

こんな感じで使えます。

```shell
$ hack-spirit start_work -u user_name -p password
```

現時点で対応コマンドは以下のような感じです。

- 出勤・退勤
- 遅刻・残業申請
- 休憩（チルアウト）
- 工数割り当て
- 勤務時間集計(週報・月報機能)

## 使い心地

これで日々の勤怠処理の大半はコマンド化されました。実際に使ってみて感想ですが、

- コマンドを実行自体は少し時間がかかる（出勤コマンドで15秒弱くらい）が、その間別のことができるのでGUIに比べて格段に便利
- GUIをいじっていると使われている感があるがCLIだとなぜだかストレスレス
- shell scriptを書けば月末にまとめて申請することもできて便利


## よくある質問

- Q. HTMLの構造変わったら終わりじゃね？
- A. 終わりです。地獄です。hellです。
- Q. windowsは?
- A. electronベースなので動きます
- Q. cronで回したら、自動出勤ツール作れるね。
- A. やめてください

## まとめ

今回は勤怠処理をコマンド化していましたが、[nightmare](http://www.nightmarejs.org/)を使えばブラウザ上で行っているルーティンワークは自動化することができます。
些細なことでも自動化できるとその分自分の本来やりたいことに時間を割くことができます。
仕事をしていると必ずしも本来やりたいコトばかりができるわけではないですが、
プログラマたるものそういったコトはhackして効率化して削られていかないようにしたいですね。

-----

ACCESS Advent Calendar 2016の明日の担当は @sylph01 さんです。