概要
- ウサギを飼うときの体重、チモシー、ペレット(通称カリカリ)の記録をslackでやる
- 体重とチモシーとペレットの平均値と標準偏差を予め入力しておくことで、入力された数値からいずれに該当するか自動的に判定します。
- slackからのメンションをgoogle cloud functionで受けて、google datastoreに保存して、slackにメッセージします。
動作イメージ
手順
google cloud functionを作り、下記のコードをdeployする
slack の Create New APP で BOTを作る。手順はこちらのCloud Functionsで作る選択肢つき Slack BOT がとても詳しいです!
index.js
const PROJECT_ID = 'google datastoreのプロジェクトID';
const BOT_USER_TOKEN = "slackのBot User OAuth Access Token";
const GOOGLE_APPLICATION_CREDENTIALS = "./アプリケーションのデフォルト認証情報.json"; // https://cloud.google.com/docs/authentication?hl=ja
const startOfToday = require('date-fns/start_of_today');
const format = require('date-fns/format')
const request = require('request-promise-native');
// const payloadTest = require('./payloadTest.json'); テスト用
const Datastore = require('@google-cloud/datastore');
const datastore = Datastore({projectId: PROJECT_ID});
// 環境変数を設定する(簡易的です。.envでやりましょうー)
const set_env = () => {
process.env.BOT_USER_TOKEN = BOT_USER_TOKEN;
process.env.GOOGLE_APPLICATION_CREDENTIALS = GOOGLE_APPLICATION_CREDENTIALS;
}
// slackに返信する
const postMessage = (payload) => {
request.post('https://slack.com/api/chat.postMessage', {
headers: {'Authorization': `Bearer ${process.env.BOT_USER_TOKEN}`},
json: payload,
});
}
// いわゆる。zスコア計算する。小さいほど、その(体重、チモシー、カリカリ)分布から得られる値と判断する
// 今回は不要だけど累積分布関数でp値だせばよりそれっぽい。
const zScore = (x, mean, stdev) => {
const z = (x - mean) / stdev;
return z
}
// 入力値を保存する
async function addRecord(kind, value, unit, message) {
const date = startOfToday();
const dateStr = format(date, 'YYYY-MM-DD');
const recordKey = datastore.key([kind, dateStr]);
const data = [
{
name: 'date',
value: date,
},
{
name: 'unit',
value: unit,
},
{
name: 'value',
value: value,
},
{
name: 'message',
value: message,
excludeFromIndexes: true,
},
];
const entity = {
key: recordKey,
data: data,
};
datastore.save(entity);
}
// 体重、チモシー、カリカリのパラメータ(平均値、標準偏差)を取得する
const getEntities = (kind) => {
const query = datastore.createQuery(kind)
.limit(10);
return datastore.runQuery(query)
}
const onRequest = async (req, res) => {
// slackからの入力値を取得する
let payload = req.body;
// let payload = payloadTest;
// APIキーなどを環境変数にセットする
set_env();
// slackからのverificationのため。
// https://qiita.com/castaneai/items/144bb17ec8bb21f306d4
if (payload.type === 'url_verification') {
return res.status(200).json({'challenge': payload.challenge});
}
let kind = '';
let z = 9999;
const message = payload.event.text
const value = message.match(/> [^0-9]*([0-9]+[\.]?[0-9]*)/) && message.match(/> [^0-9]*([0-9]+[\.]?[0-9]*)/)[1]; // 数値を抽出
// valueに数値が入っていない場合は何もせず返す
if (!value || !(value > 0) || !(payload.event.type === 'app_mention')) {
postMessage({
text: `<@${payload.event.user}> 数字入れてね`,
channel: payload.event.channel,
});
return res.status(200).send('OK');
}
// 数値が 体重、チモシー、カリカリのどの平均値に近いか判定して
// slackで返事 & データを保存
getEntities('Parameter')
.then(results => {
const entities = results[0];
let selectedEntity = {};
entities.forEach(entity => {
if (Math.abs(z) > Math.abs(zScore(value, entity.mean, entity.stdev))) {
selectedEntity = entity;
z = zScore(value, entity.mean, entity.stdev);
}
});
addRecord(selectedEntity.kind, value, 'g', message);
const date = startOfToday();
const dateStr = format(date, 'YYYY-MM-DD');
postMessage({
text: `<@${payload.event.user}> 入力ありがと。${dateStr}の ${selectedEntity.name} は ${value} g で記録しておくね。`,
channel: payload.event.channel,
});
});
return res.status(200).send('OK');
}
exports.slackChoicesBot = onRequest;
以上。