Slackで送った文字を画像で返すbot作った

# ここでは
Slackで `@textchan create 絵文字!` と送ると、

<img width="64" alt="絵文字！" src="https://qiita-image-store.s3.amazonaws.com/0/99161/05e1dd90-233a-7bec-f373-65cbb451227e.png">

という画像をアップしてくれるbotを作ってみる。

# つくる理由
Slackで [カスタム絵文字が多用されているようなチーム](http://ascii.jp/elem/000/001/177/1177479/) では、
文字だけを収めたカスタム絵文字が多く存在する。
またそれは、ユーザーの手作りであることが多い。

特にエンジニアが大活躍するようなところでは、
各エンジニアがPhotoshop等のデザインツールを持っていないことも珍しくないだろう。
カスタム絵文字の製作時間で職務に影響がでることは間違いない。効率化すべきである。

例）
<img width="64" alt="" src="https://qiita-image-store.s3.amazonaws.com/0/99161/aef084e7-cf37-0720-0b92-e0eb4bc4e9cd.png">
**嬉** / 嬉しいときに

<img width="64" alt="" src="https://qiita-image-store.s3.amazonaws.com/0/99161/6e591ac8-f24b-8ab0-aad0-d98d2def3aeb.png">
**登校** / 登校したときに

# 何故、文字だけの絵文字が必要か
投稿に対して、すぐ読み取れるReactionを送ることができる
<img width="214" alt="Reactionの例" src="https://qiita-image-store.s3.amazonaws.com/0/99161/2bc82e29-71f3-29e8-9202-0c82efa3da5f.png">

今の気持ちとして、IDの横にStatusとして表示しておくことができる
<img width="192" alt="Statusの例" src="https://qiita-image-store.s3.amazonaws.com/0/99161/4b2bde77-2e5e-9d14-32be-36fcf6aa03b8.png">

# つくる
テキストから画像を起こすという処理をするのあたり、
Node.js 上で canvas を生成できる `node-canvas`というのがいいと思った。
ウェブ上のJavaScriptで \<canvas\> を生成するのと同じ要領で使える。
のちのち、文字位置調整や、色、フォントの変更をする際も、比較的楽にできる。

また、Slack上で、Mentionを検知、画像をアップロードするために `Botkit` を使用する。

```shell
# node-canvas を動かすためのライブラリのインストール
# こちらは MacでHomebrewでインストールする場合
# その他 https://github.com/Automattic/node-canvas#installation
brew install pkg-config cairo pango libpng jpeg giflib

# node packageのインストール
npm install --save node-canvas botkit
```

**参考にするもの**

- [BotKitを利用したSlack Botの開発環境を5分～15分位で構築する(Windows)](https://qiita.com/NagaokaKenichi/items/e0715b6bdde8459a705c)
    - Slack Bots Configurationの追加方法などもこちら
- [SlackのBotkitでファイルをアップロードする方法](https://qiita.com/nobukatsu/items/e6ba8d9299816e1a503e)
- [Node.jsでCanvas(ImageData)を使った簡単な画像処理](https://qiita.com/redshoga/items/d5afef65081b7fdf60cc)
- その他、いろいろなリポジトリ

**[GitHub Repository](https://github.com/amotarao/textchan) 有り**
Qiita上のコードは一部簡略化している

```js:index.js
const Botkit = require('botkit')
const canvas = require('./canvas')

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

const controller = Botkit.slackbot({
  debug: false
})

controller.spawn({
  token: process.env.token
}).startRTM(function (err) {
  if (err) {
    throw new Error(err)
  }
})

controller.hears('create(.*)', ['direct_message', 'direct_mention', 'mention'], function (bot, message) {
  var setting = {
    text: '',
    color: '#000',
    fontFamily: 'YuGothic'
  }

  var args = message.match[1]
  var reg = /\s+(["“”][^"“”]+["“”]|[^ ]+)/g
  var arg, i = 0

  while (arg = reg.exec(args)) {
    arg = arg[1].replace(/^["“”](.*)["“”]$/, '$1')

    switch (i) {
      case 0:
        setting.text = arg
        break
      case 1:
        setting.color = arg
        break
      case 2:
        setting.fontFamily = arg
        break
    }
    i++
  }

  canvas(setting).then(function (fileObj) {
    var messageObj = fileObj
    messageObj.channels = message.channel

    bot.api.files.upload(messageObj, function (err, res) {
      if (err) console.log(err)
    })
  })
})
```
▲Slack上でのMentionの検知や画像アップロードなどの処理などを記述

```js:canvas.js
const Canvas = require('canvas')
const fs = require('fs')

var insertStr = function (str, index, insert) {
  return str.slice(0, index) + insert + str.slice(index, str.length);
}
var canvas_to_base64 = function (c) {
  return c.toDataURL().split(',')[1]
}
var decode_and_copy = function (string, filename) {
  return new Promise(function (resolve, reject) {
    var buffer = new Buffer(string, 'base64')
    fs.writeFile(filename, buffer, function (err) {
      if (err) {
        reject(err)
        return
      }
      resolve()
    })
  })
}

async function canvas(setting, next) {

  setting = setting || {
    text: 'えもじ！',
    color: '#000',
    fontFamily: 'YuGothic'
  }

  const text_n = insertStr(setting.text, 2, '\n')
  const filename = './' + setting.text + '.png'

  const c = new Canvas(128, 128)
  const ctx = c.getContext('2d')

  ctx.font = 'bold 60px ' + setting.fontFamily
  ctx.textAlign = 'center'
  ctx.fillStyle = setting.color
  ctx.fillText(text_n, 64, 56)

  await decode_and_copy(canvas_to_base64(c), filename)

  const fileObj = {
    file: fs.createReadStream(filename),
    filename: setting.text + '.png',
    title: setting.text
  }

  return fileObj
}

module.exports = canvas
```
▲テキストを画像にする処理を記述
今回は、全角４文字を ２文字・２文字の２行にする処理のみ

# うごかす

```shell
# [Slack API Token] は適宜置き換え
token=[Slack API Token] node index.js
```

```:Slack
@textchan create 絵文字！
@textchan create 赤・明朝 red "YuMincho"
```

<img width="64" alt="絵文字！" src="https://qiita-image-store.s3.amazonaws.com/0/99161/fa83ec48-c92c-44d2-5fc5-a8e23c8ba323.png"> <img width="64" alt="赤・明朝" src="https://qiita-image-store.s3.amazonaws.com/0/99161/f186a220-858c-c435-5c9a-2f106c2457ef.png">

恐らくこれらが返ってくるので
一旦ダウンロードし [カスタム絵文字](https://slack.com/customize/emoji) として追加する

# 課題
- 全角４文字以外でもキレイな画像を生成させる
- canvasを一度画像ファイルで保存し、その画像ファイルを読み込んでいるが、恐らく不要な作業
- 使用できるフォントが、マシンなりサーバーなりに依存する
- 自動でカスタム絵文字追加させたい

# おわり
楽しいカスタム絵文字ライフを。