なんで作ろうと思った?
前回作ったSlack bot にセリフと一緒にスライムの画像も表示したいと思ったから。
使った技術は?
Node.js
前提
・Node.js インストール済
・Yarn インストール済
・Slack アカウント作成済
どうやって作ったの?
一つの画像を表示する
1. images ディレクトリ作成
mkdir scripts/images
2. サンプル画像をダウンロードして保存する
サンプル画像
command
+ s
ダウンロードフォルダにいったん保存する
3. ダウンロードした画像を images ディレクトリに移動させる
4. 参考にした記事の coffeescript のコードを JavaScript に変換する
変換前
module.exports = (robot) ->
robot.hear /(猫)$/i, (msg) ->
room = msg.envelope.room
if room == "image"
text = msg.match[1]
msg.send "#{text} の画像アップするよ"
# 下記を追加
filename = 'image/nya-o.png'
channel = msg.message.rawMessage.channel
exec "curl -F file=@#{filename} -F channels=#{channel} -F token=#{process.env.HUBOT_SLACK_TOKEN} https://slack.com/api/files.upload", (err, stdout, stderr) ->
変換後
module.exports = function(robot) {
return robot.hear(/(猫)$/i, function(msg) {
var channel, filename, room, text;
room = msg.envelope.room;
if (room === "image") {
text = msg.match[1];
msg.send(text + " の画像アップするよ");
filename = 'image/nya-o.png';
channel = msg.message.rawMessage.channel;
return exec("curl -F file=@" + filename + " -F channels=" + channel + " -F token=" + process.env.HUBOT_SLACK_TOKEN + " https://slack.com/api/files.upload", function(err, stdout, stderr) {});
}
});
};
5. JavaScript に変換後のコードを既存のコードに合うように編集する
編集前
'use strict';
const data = require('./data.json');
const words = data.slimelife;
module.exports = (robot) => {
robot.hear(/スライム/i, (msg) => {
const user_id = msg.message.user.id;
const word = words[Math.floor(Math.random() * words.length)];
msg.send(`<@${user_id}> ${word}`);
});
・var
を const
にする。
・room
は使用しないので消す。(チャンネルはすでにSlackの設定の時にひとつ指定しているから)
・text
は使用しないので消す。(セリフのJSONファイルがあるから)
編集後
'use strict';
const data = require('./data.json');
const words = data.slimelife;
module.exports = (robot) => {
robot.hear(/スライム/i, (msg) => {
const user_id = msg.message.user.id;
const word = words[Math.floor(Math.random() * words.length)];
+ msg.send(`<@${user_id}> ${word}`);
+ const filename = `images/pose_pien_uruuru_woman.png`;
+ const channel = msg.message.rawMessage.channel;
+ return exec("curl -F file=@" + filename + " -F channels=" + channel + " -F token=" + process.env.HUBOT_SLACK_TOKEN + " https://slack.com/api/files.upload", function(err, stdout, stderr) {
+ if (err) {
+ console.error(err);
+ }
+ });
});
};
実行すると以下のエラーメッセージがでた。
ERROR ReferenceError: exec is not defined`
こちらの記事によると、exec
の読み込みできてないのが原因だったので、以下のコードを1行目に追記した。
const { exec } = require('child_process');
実行すると今度は別のエラーメッセージがでた。
Failed to open/read local data from file/application
画像ファイルのパスが合ってないのが原因だった。
$ pwd
/Users/maiamea/git/maiamea/slimelife-bot
現在位置が slimelife-bot ディレクトリ。images ディレクトリは scripts ディレクトリ配下だから、slimelife-bot ディレクトリ直下ではないのでエラーになった。
ファイルパスを以下に修正
const filename = `scripts/images/pose_pien_uruuru_woman.png`;
6. ボットを Slack のアダプターなしで動かす
bin/hubot
実行すると以下のエラーメッセージがでた。
ERROR TypeError: Cannot read property 'channel' of undefined
<検証1>
・bin/hubot
の時、message
に何が入ってるのか。channel
のオブジェクトであるrawMessage
に何が入ってるのか確認
'use strict';
const { exec } = require('child_process');
const data = require('./data.json');
const words = data.slimelife;
module.exports = (robot) => {
robot.hear(/スライム/i, (msg) => {
const user_id = msg.message.user.id;
const word = words[Math.floor(Math.random() * words.length)];
msg.send(`<@${user_id}> ${word}`);
const filename = `scripts/images/pose_pien_uruuru_woman.png`;
console.log('ーーーーーーーーーーー');
console.log(msg.message);
console.log('ーーーーーーーーーーー');
console.log(msg.message.rawMessage);
console.log('ーーーーーーーーーーー');
const channel = msg.message.rawMessage.channel;
return exec("curl -F file=@" + filename + " -F channels=" + channel + " -F token=" + process.env.HUBOT_SLACK_TOKEN + " https://slack.com/api/files.upload", function(err, stdout, stderr) {
if (err) {
console.error(err);
}
});
});
};
bin/hubot
TextMessage {
user:
User { id: '1', _getRobot: [Function], name: 'Shell', room: 'Shell' },
done: false,
room: 'Shell',
text: 'スライム',
id: 'messageId' }
ーーーーーーーーーーー
undefined
ーーーーーーーーーーー
ERROR TypeError: Cannot read property 'channel' of undefined
・bin/hubot
で実行する場合、message
にはTextMessage
が入ってる。
・rawMessage
がundefined
のためchannel
に何も入ってない状態になりエラーとなった。
<検証2>
・Slackに繋がないで実行した時と、繋げて実行した時とで何が違うか確認
・参考にした記事で元々const room = msg.envelope.room;
があったので、room
のオブジェクトのmsg.envelope
に何が入っているのか確認
'use strict';
const { exec } = require('child_process');
const data = require('./data.json');
const words = data.slimelife;
module.exports = (robot) => {
robot.hear(/スライム/i, (msg) => {
const user_id = msg.message.user.id;
const word = words[Math.floor(Math.random() * words.length)];
msg.send(`<@${user_id}> ${word}`);
const filename = `scripts/images/pose_pien_uruuru_woman.png`;
console.log('ーーーーーーーーーーー');
console.log(msg.message);
console.log('ーーーーーーーーーーー');
console.log(msg.message.rawMessage);
console.log('ーーーーーーーーーーー');
console.log(msg.envelope);
console.log('ーーーーーーーーーーー');
const channel = msg.message.rawMessage.channel;
return exec("curl -F file=@" + filename + " -F channels=" + channel + " -F token=" + process.env.HUBOT_SLACK_TOKEN + " https://slack.com/api/files.upload", function(err, stdout, stderr) {
if (err){
console.error(err);
}
});
});
};
【Slackに繋がないで実行した時】
bin/hubot
ーーーーーーーーーーー
TextMessage {
user:
User { id: '1', _getRobot: [Function], name: 'Shell', room: 'Shell' },
done: false,
room: 'Shell',
text: 'スライム',
id: 'messageId' }
ーーーーーーーーーーー
undefined
ーーーーーーーーーーー
{ room: 'Shell',
user:
User { ... },
message:
TextMessage { ... }
}
ーーーーーーーーーーー
ERROR TypeError: Cannot read property 'channel' of undefined
・message
にはTextMessage
が入ってる。
・rawMessage
はundefined
・room
がShell
【Slackに繋げて実行した時】
env HUBOT_SLACK_TOKEN=控えておいた文字列 bin/hubot --adapter slack
ーーーーーーーーーーー
SlackTextMessage {
user:
User { ... },
text: 'スライム',
rawMessage:
{ client_msg_id: 'ee2210fa-39ee-4ffc-bfdb-5337d946b1ce',
...
channel: 'CHCUY01CK',
...
},
_channel_id: 'CHCUY01CK',
_robot_name: 'slimelife_bot',
_robot_alias: false,
rawText: 'スライム',
mentions: [],
done: false,
room: 'CHCUY01CK',
id: '1597567494.002100',
constructor:
{ ... },
buildText: [Function],
replaceLinks: [Function],
replaceUser: [Function],
replaceConversation: [Function] }
ーーーーーーーーーーー
{ client_msg_id: 'ee2210fa-39ee-4ffc-bfdb-5337d946b1ce',
...
channel: 'CHCUY01CK',
...
}
ーーーーーーーーーーー
{ room: 'CHCUY01CK',
user:
User { ... },
message:
SlackTextMessage { ... }
ーーーーーーーーーーー
・message
にはSlackTextMessage
が入ってる。
・rawMessage
はちゃんと存在していて、channel
が入ってる。
・room
がchannel
Slack に繋げて実行すると、ちゃんと Slack の方でセリフと画像が返ってきた。
【解決策】
・bin/hubot
でエラーにならずにセリフと同様に画像もちゃんと返ってくることを確認するために、room
がShell
かどうかで場合分けする。room
がShell
の時はrawMessage
がundefined
なので、channel
の定義はせずそのまま結果を返す。
・ターミナルでは画像表示はできないので、画像のファイル名を返すことで確認する。
'use strict';
const { exec } = require('child_process');
const data = require('./data.json');
const words = data.slimelife;
module.exports = (robot) => {
robot.hear(/スライム/i, (msg) => {
const user_id = msg.message.user.id;
const word = words[Math.floor(Math.random() * words.length)];
msg.send(`<@${user_id}> ${word}`);
const filename = `scripts/images/pose_pien_uruuru_woman.png`;
console.log('ーーーーーーーーーーー');
console.log(msg.message);
console.log('ーーーーーーーーーーー');
console.log(msg.message.rawMessage);
console.log('ーーーーーーーーーーー');
console.log(msg.envelope);
console.log('ーーーーーーーーーーー');
const room = msg.envelope.room;
if (room === "Shell") {
msg.send(`表示される画像ファイルは、pose_pien_uruuru_woman.png です。`);
} else {
const channel = msg.message.rawMessage.channel;
return exec("curl -F file=@" + filename + " -F channels=" + channel + " -F token=" + process.env.HUBOT_SLACK_TOKEN + " https://slack.com/api/files.upload", function (err, stdout, stderr) {
if (err) {
console.error(err);
}
});
}
});
};
bin/hubot
ーーーーーーーーーーー
TextMessage {
user:
User { id: '1', _getRobot: [Function], name: 'Shell', room: 'Shell' },
done: false,
room: 'Shell',
text: 'スライム',
id: 'messageId' }
ーーーーーーーーーーー
undefined
ーーーーーーーーーーー
{ room: 'Shell',
user:
User { ... },
message:
TextMessage { ... }
}
ーーーーーーーーーーー
<@1> 一行読めた!やったぁ!!
表示される画像ファイルは、pose_pien_uruuru_woman.png です。
ターミナル上で、セリフと画像ファイル名どちらも返ってきた。
複数の画像をランダムに表示する
1. 画像をリサイズする
画像のファイルサイズが大きいとアップロードに時間がかかってしまう。
こちらの記事を参考にしました。
2. 画像ファイルを scripts/images に移動
3. 画像ファイルのパスをまとめる json ファイルを作成
touch scripts/path.json
{
"imagePaths": [
"IMG_8952.jpeg",
"IMG_8953.jpeg",
"IMG_8955.jpeg"
]
}
4. 画像がランダム表示されるようにコードを追記した。
'use strict';
const { exec } = require('child_process');
const data = require('./data.json');
const words = data.slimelife;
+ const paths = require('./path.json');
+ const imagePaths = paths.imagePaths;
module.exports = (robot) => {
robot.hear(/スライム/i, (msg) => {
const user_id = msg.message.user.id;
const word = words[Math.floor(Math.random() * words.length)];
+ const imagePath = imagePaths[Math.floor(Math.random() * imagePaths.length)];
msg.send(`<@${user_id}> ${word}`);
+ const filename = `scripts/images/${imagePath}`;
const room = msg.envelope.room;
if (room === "Shell") {
+ msg.send(`表示される画像ファイルは、${filename}です。`);
} else {
const channel = msg.message.rawMessage.channel;
return exec("curl -F file=@" + filename + " -F channels=" + channel + " -F token=" + process.env.HUBOT_SLACK_TOKEN + " https://slack.com/api/files.upload", function(err, stdout, stderr) {
if (err) {
console.error(err);
}
});
}
});
};
5. 実装
【Slackに繋がないで実行した時】
bin/hubot
ーーーーーーーーーーー
TextMessage {
user:
User { id: '1', _getRobot: [Function], name: 'Shell', room: 'Shell' },
done: false,
room: 'Shell',
text: 'スライム',
id: 'messageId' }
ーーーーーーーーーーー
undefined
ーーーーーーーーーーー
{ room: 'Shell',
user:
User { ... },
message:
TextMessage { ... }
}
ーーーーーーーーーーー
<@1> 今回のダンジョンでなにをしたか思い出しなさい。まず美味しいカレーを作ったでしょ?お化けも倒して闇の試練も乗り越えてフェンリルとも友達になったでしょ。この経験が今後に活きない訳ないじゃん。近づいてるよなんでも技師
表示される画像ファイルは、scripts/images/IMG_8955.jpegです。
【Slackに繋げて実行した時】
env HUBOT_SLACK_TOKEN=控えておいた文字列 bin/hubot --adapter slack
無事 Slack でランダムに可愛いスライムの画像を表示できた。
参考記事
・Hubotからslackに画像を投稿してみる - Qiita
・javascriptのcoffeescript、cssとscssの変換ならこのサービスで - Qiita
・Node.js|シェルコマンドを実行する方法(child_process) - わくわくBank
・Macだけでできる!画像をリサイズして、ファイルサイズを軽量化しよう![プレビュー.app編] - アイデアマンズブログ