勉強会( #IoTLT )の登壇者管理でHeadless Chromeを利用している話


この記事はIoTLTアドベントカレンダーと1日目の記事です。

今年のIoTLTのアドベントカレンダーは

* https://qiita.com/advent-calendar/2017/iotlt
* https://qiita.com/advent-calendar/2017/iotlt_neo

この二つがあるのでどちらもよろしくです！ 

もう一個のは@ukk0
さんの[当Qiita記事がいいねされたら電飾が光りまくるX'mas Internet of Tree をつくる](https://qiita.com/ukk0/items/566899520520f442ef4f)

みなさん、こんばんわ。12月になるの早いですね。

僕は普段IoTLTというLTイベントを主催しているのですが、登壇者の管理に手間を感じることがあります。 今日はその辺の話です。

いつかのIoTLTで話した内容の掘り下げです。

## 登壇者のタイトル管理ってどうしてます？

**登壇者の発表タイトル回収とイベントページへの反映って結構手間ありませんか？**

少し前にこの辺をテーマに話したことがあります。

![](https://i.gyazo.com/c754bfabc629cbe014490f9cbfea21b0.png)

[IoTLTの管理をIoT化っぽくしている話](https://speakerdeck.com/n0bisuke/iotltfalseguan-li-woiothua-tupokusiteiruhua-number-iotlt)

## IoTLTの運用

基本的にはこんな感じで、参加者から登壇希望があり、発表タイトルを収集するスタイルです。

![](https://i.gyazo.com/94c921bb971fb92d023b67d2aba13f25.png)

![](https://i.gyazo.com/dc5bd3cb22ac3664e4b568e4d93e8352.png)

このやり方は結構LT系イベントだとオーソドックスな気もしているのですが、以下の問題点があります。

* 情報集約の手間
* コミュニケーションコスト
* 手動でページを更新する手間
* 発表タイトルに更新があった場合の変更の手間

**LTタイトルなどは収集しないで当日まで分からない状態にすれば手間がないのでは?**って話もあるのですが、 **どんな発表があるかを見て参加するかどうかを決めてる参加者も一定層いるのが現実**です。

主催の気持ちとしては出来るだけ情報は外に出して参加したいって思ってもらいたいと思うので、なるべく情報はアップデートしていきたい気持ちがあります。

### メッセンジャーとスプレッドシートで少しスマートになった感あるけど...

まだハック要素は無いのですが、現状では個別のやりとりではなくFacebookのメッセンジャーグループを作り、スプレッドシートに登壇者たちに情報を記載してもらうことで作業コストの削減をしています。（ました）

![](https://i.gyazo.com/9a6ad59c0d69de4ad6230d0350aea444.jpg)

とりあえずこれで少しは運営側は楽できるようになりました。

ただ、いまだに`アップデートがあった際に手動更新しないといけない問題は変わりません。`


放っておくとこうなります苦笑

![](https://i.gyazo.com/299372a566bbf65e13b8d26632119148.png)

## 話題のHeadless Chromeで自動化検討

connpassにAPIが無いならこの辺ですよね。

![](https://i.gyazo.com/a0f62c0d6f370a32645f494a66e7e4c0.jpg)

過去にPhantomJSやnightmareなどを利用して来ましたが、ここに来て本命が登場した感じです。

[GoogleChrome/puppeteer](https://github.com/GoogleChrome/puppeteer)

他にも色々Headless Chromeを制御するライブラリがありますが、このライブラリがGoogle Chrome公式ということで注目されています。

Node.jsでスプレッドシートへのアクセスも可能なので

![](https://i.gyazo.com/d84c6a5a10698d7824f83471b732c507.png)

**Node.js経由でスプレッドシートの情報を抜き出してpuppeteerでconnpassに書き込んでいきます。**

### スプレッドシートとイベントの紐付け問題

イベントごとに登壇者にスプレッドシートに記入してもらう運用なのですが、ここの紐付けも検討する必要があります。

![](https://i.gyazo.com/dbef1b8438e6effcad7bc2b84f437a21.png)


## connpassのイベント情報の最新を取得

ここの部分は[Node.jsでconnpassの自分が管理しているイベントの情報を取得](https://qiita.com/n0bisuke/items/d081d0990acd89c078f8)に切り出しました。

こちらの記事を参照しましょう。

## Node.jsでスプレッドシートへのアクセス

ここの部分は[Node.jsでGoogle SpreadSheetsを制御](https://dotstud.io/blog/google-spreadsheets-from-nodejs/)に切り出しました。

こちらの記事を参照しましょう。

## connpassの画面をスマホビューでエミュレートした方が良い

Headless Chromeで自動化をする際に、一個一個の動作にある程度時間がかかってしまうことがあったり、時間がかかり過ぎるとタイムアウトになったりします。

なので **出来るだけ操作ステップを少なくする意識が必要です。**

connpassのイベントページの編集画面はこんな感じです。

![](https://i.gyazo.com/653f573bc7ca31c3626542ce52a1b7a1.png)

そのときのURLは 

```
https://connpass.com/event/イベントID/edit/
```

になります。

![](https://i.gyazo.com/bb06dbaab25b99756750ee31727b9612.png)

手作業でやると

* エリアをクリック(これで編集エリアが利用可能に)
* Markdownを編集
* 保存をクリック

の **3ステップの操作が発生します。**

これをスマホで見ると

```
https://connpass.com/event/イベントID/edit/basic/
```

というURLにアクセスでき、 

* Markdownを編集 (最初から編集可能)
* 保存をクリック

この **2ステップの操作で編集できます。**

![](https://i.gyazo.com/f06d4df72dc5626347f66b90db4e4a90.png)

## 実装

```
npm init -y
npm i --save puppeteer
```

`const iPhone = devices['iPhone 6'];`の部分が前述したスマホビューでのエミュレートになります。

別デバイスで表示しているようにエミュレートするためには[page.emulateの項目](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#pageemulateoptions)を参照しましょう

```autoconnpass.js
'use strict';

const puppeteer = require('puppeteer');
const devices = require('puppeteer/DeviceDescriptors');
const iPhone = devices['iPhone 6'];

const CONNPASS_USER = '';
const CONNPASS_PASS = '';
const createTargetURL = require('./libs/createTargetURL'); //ターゲットとなるURLを生成

const main = async () => {
    console.log('connecting connpass...');
    const browser = await puppeteer.launch({headless: false});
    const page = await browser.newPage();
    await page.emulate(iPhone);

    //イベントURLの特定
    const EVENT_URL = await createTargetURL(); //connpass APIから最新イベントを取得 (別記事参照1)

    //ログイン
    await page.goto(EVENT_URL);
    await page.type(`input[name=username]`,CONNPASS_USER);
    await page.type(`input[name=password]`,CONNPASS_PASS);
    await page.click(`#login_form button`);

    //概要を取得
    await page.waitFor(`#id_description_input`);
    const result = await page.evaluate(() => Promise.resolve(document.getElementById(`id_description_input`).value));
    console.log(result);
    console.log(`概要取得done`);

    //概要を編集 スプレットシートにアクセス (別記事参照2)
    const text = await contentRewrite(result, CONFIG.SS_ID, CONFIG.SS_RANGE);
    console.log(`スプレットシートアクセスdone`);
    console.log(`概要編集done`);

    //変更を保存
    await page.evaluate((text) => document.getElementById(`id_description_input`).value = text, text);
    await page.click(`input[value=保存]`);
    console.log(`保存done`);

    //撮影
    await page.waitFor(1000);
    await page.close();
    browser.close();
};

main();
```

実行する (上記コードはシートやconnpassAPI部分が抜けてるのでこのままでは動かないですが)

![](https://i.gyazo.com/6443f59b96c18dc380a91202eec9b64e.gif)

こんな感じで自動化することができました！

* スプレットシートの自動追加
* GASとの連携

なども今後やれたらなぁという感じです！

## おわりに

IoTLTは回数も参加者もおかげさまで増えて来ていますが、運営は僕と土屋さんの二人だけでやっていますので、今後もこのような自動化の仕組みはどんどん取り入れて行こうと思ってます。

**こういった話ができるのも盛り上げてくれる皆さんのおかげです！**
引き続きよろしくお願いします！

**アドベントカレンダー盛り上げていきましょう〜！**

2日目は新潟IoTLTから@snowk2さん！