いらすとやの画像付きのメッセージをSlackに投稿する(Puppeteerでランダムに画像を取得)

# 概要

1. 定刻になったら、いらすとやの画像付きのメッセージをSlackのチャンネルに投稿する。
2. トリガーワードがSlackに投稿されたら、いらすとやの画像付きのメッセージで返信する。

なお、画像はランダムで指定されるため、何が出てくるかはお楽しみです:relaxed:

※本記事は、すでにSlack Botを運用されている前提で書いていますので、Node環境の準備やSlackのAPI Tokenの払い出しなどの情報は割愛していますので、ご了承ください:bow:


# Slackへの投稿イメージ

1のパターン: 毎時00分でスケジュール指定した場合
<img width="596" alt="スクリーンショット 2018-09-24 22.12.15.png" src="https://qiita-image-store.s3.amazonaws.com/0/148986/9310425f-e2ad-bc3c-6631-41d1ecaf8fc7.png">

2のパターン: "いらすとや"というワードに反応した場合
<img width="550" alt="スクリーンショット 2018-09-24 0.35.41.png" src="https://qiita-image-store.s3.amazonaws.com/0/148986/bf8ed5dc-48dd-b70e-b059-43cbbf9bd86c.png">

# 必要なノードモジュールは3つ

```
npm install -S botkit
npm install -S puppeteer
npm install -S node-cron
```

# メインのスクリプト

```index.js
const botkit = require('botkit');
const puppeteer = require('puppeteer');
const cron = require('node-cron');

if (!process.env.SLACK_TOKEN) {
  console.log('Error: Specify token in environment');
  process.exit(1);
}

const controller = botkit.slackbot({
  debug: true,
});

const bot = controller.spawn({
  token: process.env.SLACK_TOKEN,
}).startRTM();

// Bot起動時のSlack疎通確認用
controller.hears(['ping'], 'direct_message,direct_mention,mention,ambient', function (bot, message) {
  bot.reply(message, 'PONG');
});

// --------------------------------------------------
//  いらすとや画像をSlackに投稿する
// --------------------------------------------------

function postIrasutoya(channel){

  (async () => {
    const browser = await puppeteer.launch({ headless: true });
    const page = await browser.newPage();
    await page.goto('https://www.irasutoya.com/2016/04/blog-post_890.html');
    await page.click('a[href="#random"]');
    await page.waitForNavigation({ waitUntil: 'domcontentloaded' });

    const image = await page.$('#post .entry img');
    const src = await image.getProperty('src');
    const url = await src.jsonValue();
    const title = await page.$eval('#post h2', item => {
      return item.innerText;
    });
    
    bot.say({
      channel: channel,
      text: '休憩しようぜ！<' + url + '|' + title + '>',
      as_user: false,
      username : 'いらすとや',
      icon_emoji: ':+1:',
    }, function(err, res) {
      if (err) {
        bot.botkit.log('Failed to postMessage', err);
      }
    });
    await browser.close();
  })();
}

// トリガーワードで実行する
controller.hears(['いらすとや'], 'direct_message,direct_mention,mention,ambient', function(bot, message) {
  postIrasutoya(message.channel);
});

// cronでスケジュール実行する（月曜〜金曜の12時と15時）
cron.schedule('0 0 12,15 * * 1-5', () => {
  postIrasutoya('#general');  // #generalチャンネルに投稿
});
```

## 解説1: いらすとやの画像をランダムに取得する

<img width="596" alt="スクリーンショット_2018-09-24_0_16_34.png" src="https://qiita-image-store.s3.amazonaws.com/0/148986/d8712dfe-fe3c-c972-0da7-03bddb06e81e.png">

1. いらすとやの画像ページを開き、下部にあるランダムボタンをクリックする。
2. クリック後に表示されたページの画像のURLとタイトルを取得する。
3. Slackに画像のタイトルとURLを投稿する。


## 解説2: トリガーワードで実行する

controller.hearsの第一引数が、トリガーワードになります。
複数指定や正規表現指定も可能です。

controller.hearsの第二引数は、反応するメッセージイベントの種類を指定します。

| イベントの種類     | 説明                                          |
|:---------------|:----------------------------------------------|
| direct_message | ユーザーからのダイレクトメッセージに反応します。           |
| direct_mention | チャンネル内でのダイレクトメンションのメッセージに反応します。 |
| mention        | チャンネル内でのメンション付きのメッセージに反応します。     |
| ambient        | チャンネル内でのメンションがないメッセージに反応します。     |

※@メンションがメッセージの先頭についているのがダイレクトメンションです。（direct_mention）
<img width="225" alt="スクリーンショット 2018-09-24 0.32.42.png" src="https://qiita-image-store.s3.amazonaws.com/0/148986/e404e251-ae69-19e6-60e1-c54b45e8eeb7.png">

※それ以外のメンション付きメッセージ（mention）
<img width="213" alt="スクリーンショット 2018-09-24 0.33.25.png" src="https://qiita-image-store.s3.amazonaws.com/0/148986/bfc07fbd-e579-8f26-00bd-67e3a25db1cd.png">

※メンションがないメッセージ（ambient）
<img width="158" alt="スクリーンショット 2018-09-24 0.34.33.png" src="https://qiita-image-store.s3.amazonaws.com/0/148986/a390adb0-6e74-30ed-e1c0-9b43d1f16977.png">


## 解説3: cronでスケジュール実行する

cron.scheduleの引数で、スケジュールを指定します。
引数の中の左から「秒、分、時、日、月、曜日」で指定することができます。
`'0 0 12,15 * * 1-5'`の場合は、1-5が月曜〜金曜を意味しており、月曜〜金曜の間の12時0分0秒と15時0分0秒を意味しています。

詳細は、merencia/node-cronのREADME.mdの[Cron Syntax](https://github.com/merencia/node-cron#cron-syntax)をご参照ください。


# どうしてこれを作ったか

* 作業に集中していると、休憩も忘れて没頭しがちなので、休憩を喚起（歓喜）したかった。
* 最初はSlackの/remindで休憩メッセージを出していたが、テキストだけだと無機質で事務的に感じるようになってしまった。
* 休憩する、という空気感（和む、安らぐ）を出したかったので画像付きを試みました。
* 最初は[lgtm.in](http://lgtm.in/)の画像を出そうと思ったが、ランダムに出てくる画像のほとんどに、LGTMさを感じられず、当初の目的の休憩する空気感に沿わないと判断して、いらすとやの画像を採用しました。

※この記事を書いている時に知ったのですが、[lgtm.in](http://lgtm.in/)のサイトは閉じるようですね。（2018年9月23日に確認した時点）
<img width="1168" alt="スクリーンショット 2018-09-24 0.46.02.png" src="https://qiita-image-store.s3.amazonaws.com/0/148986/711e27b5-94d7-8de3-b804-fa9d3f67019c.png">


# 参考

* [かわいいフリー素材集 いらすとや](https://www.irasutoya.com/)
* [Puppeteer入門 スクレイピング+Web操作自動処理プログラミング](https://www.amazon.co.jp/dp/4798055204)
* GitHub
  * [GoogleChrome/puppeteer](https://github.com/GoogleChrome/puppeteer)
  * [howdyai/botkit](https://github.com/howdyai/botkit)
     * [Botkit and Slack](https://botkit.ai/docs/readme-slack.html) 
  * [merencia/node-cron](https://github.com/merencia/node-cron)
  * [will-meaning/slack-irasutoya](https://github.com/will-meaning/slack-irasutoya)  ※本記事のリポジトリ