Message Buttons 楽しそう…
面白そうなのにあまり流行っていないでおなじみ、SlackのMessage Buttons。
slack新機能!message buttonを使ってbotkitをレベルアップさせる!
にならって、herokuのFreeプランで構築してみました。
基本的には上記のリンクの手順で進めれば、ボタンの表示→お返事まではすぐだと思います。
僕は途中でSlackAppへの各種URL登録を飛ばして若干はまりました…。
…あれ?
一度認証しているはずなのに、Dyno再起動のたびに再認証が必要になっちゃう…。
元のサンプルにあったコードがないからかな? と思い、追加しましたがやっぱりだめ。
どうやら、Botkitはローカルのdb_slackbutton_bot
なるフォルダに、OAuthのトークンを保存していて、herokuのFreeプランだとこれが消えちゃうんですね。
(追記)
お世話になった記事の@LOUIS_ruiさんにコメントしていただきました。
HerokuのmLabというプラグインを使えば簡単に永続化できるようです。
この記事の存在意義とは。
もういい、EC2でたてよう
ここでhttpsサーバ必須が立ちはだかります
nginxたててhttpsをローカルのNodeへ流してみたのですが、オレオレ証明書だとだめ。(Appページの Interactive Messages でURLを登録できません)
証明書をlet's encryptで取得しようとしたのですが、デフォルトのドメイン(xxxxx.ap-northeast-1.compute.amazonaws.com的なやつ)だと作成に失敗しました。
S3をストレージにしよう!
呆然とBotkitのソースを眺めていたところ、こんなコードを発見。
この辺のプロパティを実装すれば、カスタムストレージが使えるってことですね!
ということでbotはheroku上で動かし、ストレージとしてS3を使うことにしたのでした。
ストレージクラスのソースはこんな感じです。
デフォルトのsimple_storage.jsをパク参考にしました。
S3用APIのAccessKeyとSecretKeyは事前に取得しておいてください。
/*
Storage module for bots.
Using AWS S3 storage.
Configuration:
accessKey: s3 access key
secretKey: s3 secret key
bucket: target bucket
path: path to s3 folder
region(optional): s3 region (default: us-east-1)
*/
var aws = require('aws-sdk');
var async = require('async');
module.exports = function (config) {
if (!config) {
return {};
}
var teams_db = config.path + '/teams/';
var users_db = config.path + '/users/';
var channels_db = config.path + '/channels/';
var bucket = config.bucket;
aws.config.update({
accessKeyId: config.accessKey,
secretAccessKey: config.secretKey,
region: config.region || 'us-east-1'
});
var objectsToList = function (cb) {
return function (err, data) {
if (err) {
cb(err, data);
} else {
cb(err, Object.keys(data).map(function (key) {
return data[key];
}));
}
};
};
var s3 = new aws.S3();
var put = function (id, data, cb) {
var param = {
Bucket: bucket,
Key: id,
Body: JSON.stringify(data)
};
// http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#putObject-property
s3.putObject(param, function (err, data) {
if (err) {
return cb ? cb(err) : err;
}
return cb ? cb(null, data) : data;
});
};
var get = function (id, cb) {
var param = {
Bucket: bucket,
Key: id
};
// http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#getObject-property
s3.getObject(param, function (err, data) {
if (err) {
return cb ? cb(err) : err;
}
var obj = JSON.parse(data.Body.toString());
return cb ? cb(null, obj) : obj;
});
};
var list = function (prefix, cb) {
var param = {
Bucket: bucket,
Prefix: prefix
};
// http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#listObjects-property
s3.listObjects(param, function (err, data) {
if (err) {
return cb ? cb(err) : err;
}
var all = {};
var funcs = [];
data.Contents.forEach(function (content) {
funcs.push((function (_key) {
return function (callback) {
get(_key, function (e, obj) {
if (!e) {
all[obj.id] = obj;
}
callback(e);
});
};
})(content.Key));
});
return async.parallel(funcs, function (error) {
return cb ? cb(error, all) : all;
});
});
};
// teams, users, channels と
// それぞれに get, save, all があればStorageとして使える
var storage = {
teams: {
get: function (team_id, cb) {
get(teams_db + team_id, cb);
},
save: function (team_data, cb) {
put(teams_db + team_data.id, team_data, cb);
},
all: function (cb) {
list(teams_db, objectsToList(cb));
}
},
users: {
get: function (user_id, cb) {
get(users_db + user_id, cb);
},
save: function (user, cb) {
put(users_db + user.id, user, cb);
},
all: function (cb) {
list(users_db, objectsToList(cb));
}
},
channels: {
get: function (channel_id, cb) {
get(channels_db + channel_id, cb);
},
save: function (channel, cb) {
put(channels_db + channel.id, channel, cb);
},
all: function (cb) {
list(channels_db, objectsToList(cb));
}
}
};
return storage;
};
エントリポイントのjsはこんな感じに修正します。
var s3Storage = require('./s3_storage');
...
var controller = Botkit.slackbot({conversations
// json_file_store: './db_slackbutton_bot/',
storage: new s3Storage({
path: process.env.s3Path,
bucket: process.env.s3Bucket,
accessKey: process.env.s3AccessKey,
secretKey: process.env.s3SecretKey
})
}).configureSlackApp({
clientId: process.env.clientId,
clientSecret: process.env.clientSecret,
scopes: ['bot'],
});
...
これで、Dyno再起動時も認証情報をもとにbotが再接続してくれます。
めでたしめでたし。皆さんも Message Buttons 試しましょう!
あ、30分でSleepしちゃうのも対策しなきゃ…。