【オフィスIoT】ICカードで出退勤する。4
リファクタリング回
稟議通ったのに僕が注文依頼出してなかったせいで、カード開発進みませんでした!!
ただ、かなりいい感じに前回のコードの改修が済んだので、せっかくなので解説という形にしたいと思います。
ざっくりソース
//定数を宣言
const puppeteer = require('puppeteer');
const slack_touch_command = '/jobcan_status';
require('dotenv').config();
//dotenvから環境変数を取得
const workspace_url = process.env.WORKSPACE_URL;
const email = process.env.EMAIL_ADDRESS;
const password = process.env.PASSWORD;
(async() => {
const browser = await puppeteer.launch({
headless:false //ヘッドレスモードをオフにする。テスト用。
});
//新しいページを生成
const page = await browser.newPage();
//ページへ移動、JS読み込み完了まで待機
await page.goto(workspace_url,{waitUntil: "networkidle0"});
//フォームが出現するまで待機。無くても動くけど念のため。
await page.waitFor('input#email');
//フォームにアドレス・パスワードを入力して送信。
await page.type('input#email',email);
await page.type('input#password',password);
await page.click('button#signin_btn');
//ロードするまで待つ。
await page.waitForNavigation();
if (await page.$('signin_btn').then(response => !response)) {
browser.close();
process.exit(1);
}
console.log('Login success');
//Slackの(自分)の要素を探す。
await page.waitFor('.p-channel_sidebar__you_label');
await page.click('.p-channel_sidebar__you_label');
//テキストボックスにSlackコマンドを打ち込んで終
await page.waitFor('#msg_input .ql-editor');
await page.type('#msg_input .ql-editor', slack_touch_command);
await page.click('#msg_input_send');
browser.close();
})();
こんな感じと相成りました。
一番の進歩は、sleep()という固定値で待機する処理が消えたことですね。
ポイント解説していきます。
ポイント解説
上の奴から一部分ずつ解説していきます。
//新しいページを生成
const page = await browser.newPage();
//ページへ移動、JS読み込み完了まで待機
await page.goto(workspace_url,{waitUntil: "networkidle0"});
//フォームが出現するまで待機。無くても動くけど念のため。
await page.waitFor('input#email');
page.goto()
にoptionとして{waitUntil: "networkidle0"}
を渡しました。
これを指定しないとJSが読み込まれる前にアドレスを打ち込み始めやがります。そのためにsleep()
を使っていたのですが…どうやらJSが読み込まれるのを待つことで打ち込みが正常に開始できるようです。
この点、もしもロードがめちゃくちゃ長くなったときに固定値だと対応できないので、きちんとイベント処理で対応できるようになったのはいい感じですね。
//フォームにアドレス・パスワードを入力して送信。
await page.type('input#email',email);
await page.type('input#password',password);
await page.click('button#signin_btn');
ここら辺は、CSSと同じようなセレクタを指定しています。
一意でないと事故が起きる可能性があるので、id使ってあげるととってもいい感じです。
ただ、場合によってはidが無くclassのみで要素探さないといけないケースもあると思います。
そういう時はブラウザコンソールで
$$('.className') #Firefox最新版
とかってやると、該当する要素全部出してくれます。これで過不足なくobjectを取得できているか確認しましょう。
ちなみに、$$()
はdocument.querySelectorAll()
と同じで、objectの配列が帰ってきます。
また、$()
はdocument.querySelector()
となり、一番最初のobjectだけ返してくれます。
スクレイピングの必須知識ですね。
//ロードするまで待つ。
await page.waitForNavigation({waitUntil : "domcontentloaded"});
if (await page.$('#signin_btn').then(response => response)) {
console.log("Login missed");
browser.close();
process.exit(1);
}
console.log('Login success');
また、以前はgeneralのチャンネルIDで判定していたのですが、ログインボタンの有無で判定するようにしました。
URLによる分岐にも挑戦したのですが、改変するほどのコードではなかったためこちらを採用しました。ここだけは検討点ですね。
//Slackの(自分)の要素を探す。
await page.waitFor('.p-channel_sidebar__you_label');
await page.click('.p-channel_sidebar__you_label');
また、個人ページへの移動も、ユニークIDを使う必要がなくなりました。
Slackの自分のページにのみ表示される要素を取得して移動すると、Slackページのリロードが入らないので動き自体も素早くなりました。
await page.waitFor('#msg_input .ql-editor');
await page.type('#msg_input .ql-editor', slack_touch_command);
await page.click('#msg_input_send');
browser.close();
最後にこいつらでコマンド打ってボタン押しておしまいですね。
本当は動きを見せられれば一番面白いのですが、いかんせん今回セキュアな情報が多すぎるので諦めます。
とまあ、こんな感じでリファクタリングが完了してかなり満足いく動きになりました。どんな状況であってもきちんと狙った動きができるようなもの作れるとかなり安心できますね。
スピードを重視するとどうしても「動けばいい」という考え方に寄りがちです。こういったインプット→アウトプットで、より効率の良い方法についての情報を常に集めていくべきだなと改めて感じました。
次回以降の話
次回はちゃんとICカードリーダのお話をしていきます。
とはいえ、ICカードでジョブカン打刻するのは公式アプリ使うのが一番良さそうなんですよね。
と言いますのも、現状の方法のまま行くと
- 1.利用者がSlackIDを全員持っていないといけない
- 2.そのSlackにジョブカンを紐づけてもらわないといけない
- 3.さらにSlackIDを全員分保管しておかないといけない
とまあ、かなり問題点が多い完成品となってしまいます。
まあSlackアカウント取得してくれっていうのは問題ないのですがパスワード渡してもらうのはセキュリティ的にどうなんだろうとか、ジョブカンと連携したりするのは我々エンジニアは楽でもきっと難しい人も多くいるだろうとか・・・。
こういった部分考えるとなかなか大型化には向いていないんですよね。今回作った仕組みだと。
ただ、買ってもらったカードリーダは公式アプリにそのまま流用できるようなやつを買ってもらいました。
なので、公式使うとなっても稼働させるマシンがラズパイからWindowsマシンに変わるくらいのもんですね。
まあ、公式使うってことは僕のアプリが無駄になるのですが・・・。
しかし、その後についてももちろん考えておりますのでまだまだ終わりません。やりたいことは無限大です。
また、今回作った動画を社内で共有したら「こんなの作ってよ!」という依頼が飛んできました。
頼られると応えたくなるのが僕ですので、やる気に満ち溢れております。頑張ったことがより頑張れることに繋がるこの感じ、最高。
1