はじめに
テキストだけでダメ出しされると凹みます。絵文字があれば大分雰囲気は変わります。Slackならもっと何かできるんじゃない?と思っていると「安西先生ジェネレータやりたいっす」 の後輩の一言。それ、いいね!
以下、ブラックジャックによろしく(タイトル:ブラックジャックによろしく、著作者名:佐藤秀峰)のデータを利用して解説していきます(二次利用フリー、ありがとうございます)。
やりたいこと
SlackからHubotに対してセリフをカタカタすると、事前に用意しておいた画像のフキダシへ文字を入れてアップロードしてくれる。利用する画像も指定できるように。
実際にできたものはこんな感じです。
どうやったか
前提として、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/ 内へ配置しておきます。
Hubotスクリプト
指定された画像をCanvasへ書き込み、フキダシへ文字を入れます。そしてこれをPNGファイルとして一時保存し、Slackへアップロードします。1
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を指定しておきます。
用意した画像の情報は、以下のようにまとめておきました。
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はフキダシ内の文字の書き込み領域です。矩形領域として左上、右下の座標を指定しました。
最後に、フキダシへの文字入れ処理です。
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
全角、縦書きで文字を書いていきます。書き込み領域を超える場合は、書き込み可能な最大文字数を返して終了するので、以下のように怒られます。
どうなったか
-
一時ファイル経由ではなくcanvas.pngStream()を利用したかったのですが、何故かうまく行きませんでした... ↩