2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Slackに安西先生ジェネレータを召喚するレシピ

Last updated at Posted at 2018-10-21

はじめに

テキストだけでダメ出しされると凹みます。絵文字があれば大分雰囲気は変わります。Slackならもっと何かできるんじゃない?と思っていると「安西先生ジェネレータやりたいっす」 の後輩の一言。それ、いいね!

以下、ブラックジャックによろしく(タイトル:ブラックジャックによろしく、著作者名:佐藤秀峰)のデータを利用して解説していきます(二次利用フリー、ありがとうございます)。

やりたいこと

SlackからHubotに対してセリフをカタカタすると、事前に用意しておいた画像のフキダシへ文字を入れてアップロードしてくれる。利用する画像も指定できるように。

実際にできたものはこんな感じです。

sample.png

どうやったか

前提として、Slackと連携できているHubotがすでにあるものとします。

準備

画像の加工(フキダシへの文字入れ)にはCanvasを利用しました。Hubotの動作環境としてUbuntu14.04を利用していたので、Cairoなどを導入してからCanvas(node-canvas)をインストールしました。

$ sudo apt-get install libcairo2-dev libjpeg8-dev libpango1.0-dev libgif-dev build-essential g++
$ npm install --save canvas

画像

利用したい画像を用意し、hubot/scripts/meme/ 内へ配置しておきます。

phone.jpg

Hubotスクリプト

指定された画像をCanvasへ書き込み、フキダシへ文字を入れます。そしてこれをPNGファイルとして一時保存し、Slackへアップロードします。1

hubot/scripts/meme.coffee
Fs = require 'fs'
Canvas = require 'canvas'
Request = require 'request'

module.exports = (robot) ->
  robot.respond /meme (\S+) (\S.*)$/i, (msg) ->
    name = msg.match[1]
    text = msg.match[2]

    # 画像の情報を取得
    if collage = findCollage(name)
      path = './scripts/meme/'

      # 画像の読み込み
      Fs.readFile path + collage.image, (err, data) ->
        if err
          msg.send '指定された画像の読み込みに失敗しました...'
        else
          # 画像をCanvasへ書き込み
          image = new Canvas.Image
          image.src = data

          canvas = new Canvas(image.width, image.height)
          context = canvas.getContext('2d')
          context.drawImage(image, 0, 0, image.width, image.height)

          # フキダシへ文字入れ
          context.font = '15px "TakaoGothic"'
          result = fillTextVertically(context, text, collage.lower, collage.upper)

          if result > 0
            msg.send "#{collage.name}のセリフは#{result}文字以内です..."
          else
            # PNGファイルとして一時保存
            file = msg.message.user.name + '.png'
            buf = canvas.toBuffer()
            Fs.writeFile path + file, buf, (err) ->
              if err
                msg.send '指定された画像の書き込みに失敗しました...'
              else
                # 一時保存したファイルをアップロード
                url = 'https://slack.com/api/files.upload'
                data =
                  token:    process.env.HUBOT_SLACK_TOKEN
                  filename: collage.name + '.png'
                  file:     Fs.createReadStream(path + file)
                  channels: msg.envelope.room
                Request.post {url: url, formData: data}

                # 一時保存したファイルを消去
                Fs.unlink path + file
    else
      msg.send '不明な画像が指定されました...'

環境変数HUBOT_SLACK_TOKENへはHubotのAPI Tokenを指定しておきます。

用意した画像の情報は、以下のようにまとめておきました。

hubot/scripts/meme.coffee
findCollage = (name) ->
  collages =
    phone: {name: 'ブラックジャックによろしく 佐藤秀峰 - 電話', image: 'phone.jpg', lower: {x: 107, y: 9}, upper: {x: 207, y: 154}}
#    xxxx: {name: 'テンプレート', image: 'xxxx.jpg', lower: {x: 0, y: 0}, upper: {x: 100, y: 100}}

  if name of collages
    return collages[name]
  else
    return null

lower、upperはフキダシ内の文字の書き込み領域です。矩形領域として左上、右下の座標を指定しました。

box.png

最後に、フキダシへの文字入れ処理です。

hubot/scripts/meme.coffee
fillTextVertically = (context, text, lower, upper) ->
  size = context.measureText('あ').width # 代表文字
  count =
    x: Math.floor((upper.x - lower.x) / size)
    y: Math.floor((upper.y - lower.y) / size)

  if text.length > count.x * count.y
    return count.x * count.y

  text = text.replace /[A-Za-z0-9]/g, (s) ->
    String.fromCharCode(s.charCodeAt(0) + 0xFEE0) # 半角->全角変換

  lines = []
  for i in [0...Math.ceil(text.length / count.y)]
    j = i * count.y
    line = text.slice(j, j + count.y)
    lines.push(line)

  x = upper.x - (upper.x - lower.x - lines   .length * size) / 2 - size
  y = lower.y + (upper.y - lower.y - lines[0].length * size) / 2 + size

  for line, i in lines
    for s, j in line
      context.fillText(s, x - size * i, y + size * j)

  return 0

全角、縦書きで文字を書いていきます。書き込み領域を超える場合は、書き込み可能な最大文字数を返して終了するので、以下のように怒られます。

error.png

どうなったか

result.png

  1. 一時ファイル経由ではなくcanvas.pngStream()を利用したかったのですが、何故かうまく行きませんでした...

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?