JavaScript
gas
spreadsheet
Slack
ランチ

いい感じにランチを決める Slack ボットを作った

ランチどこいく問題

皆さん、ランチどこ行くかってどうやって決めてますか。
毎日のことなのに結構悩みますよね。

弊社でもだいたいいつも

「ランチ行きましょう!」
「いきましょうか!」
「どこいきます?」
:thinking: …逆にどこがいいですか?」
:thinking: なんでもいいですよ」
「「:thinking::thinking: 」」

みたいな感じになって、すっぱりお店が決まらないです。

会社が飯田橋にあるので、選択肢は山のようにあるのですが、いざ行くとなると候補が多すぎて迷ってしまいます。

そこで Slack にボットを作って選択肢を自動的に提示してくれるボットを作ってみました。

いい感じに選択肢を提示したい

ランチ系のボット、Web を漁ると結構出てきます(やっぱりみんなランチ困ってるんですね)。
基本、仕組みとしてはお店のリストからランダムにチョイスして Slack などに投げるだけなので、別段難しいところはないのですが、「ランダムでいい感じの選択肢を出せるか」「お店のリストの管理をどうするか」の 2点はいい感じにしたいなと思いました。

遠かったりそもそも昼やっていないみたいなトンチンカンな提示ばかりでは意味がないですし、毎日「おっ、ここか」みたいになるようなボットを作りたいです。

また運用についても出来るだけメンテナンス不要な形に持っていきたいです。お店が開店・閉店するたびに担当者がポチポチして更新するというのはナンセンスですし、ほっておいたらどんどん要らないボットになってしまいます。

最終的に、次のような仕様を考えてみました。

  • 「サクッと」「近場」「たまには」という3つのリストを作っておく
    • サクッと: 時間、値段的にお手軽。お弁当とかUberEatsなどオフィス内で食べる候補に含める。
    • 近場: あまり歩かなくてもいけるお店。歩くの面倒くさいときや天気悪いときなど。
    • たまには: 遠かったりちょっとお値段が張るところ。気分転換などに。
    • どのリストに入るかは厳密に考えない。重複も可。
  • 定時になると各リストから 1 つづつランダムにピックアップして投稿。
    • 「サクッと」と「近場」 : 「たまには」2:1 になるのでお手軽さを重視。
  • リストは社内の誰でもが自由に編集できる。
    • 投稿時に編集リンクもつけておく。
      • 気づいた人がサクッとリスト編集できる。
  • 3つの選択肢に番号を振っておく。
    • 簡単に Slack のアクションで「ここ行きたい!」を宣言できるように。

実際にはこんな形のものを Slack に投げます。

image.png

食べログとかをスクレイピングしてリストを作ることも考えたのですが、止まってしまったときのメンテが必要だったり、ランチの有無・オフィスからの距離・網羅性などを検討した結果あまりうまく行かなそうなのでやめました。

機械的にピックアップするより、会社の誰かが「この店いいよ」ってピックアップしてくれた方が信頼性が高く楽しいです。また入力が自由なので「ステーキ」みたいなアバウトな指定や「 :sushi: 」 みたいに絵文字とかで遊べるメリットもあります。

問題は肝心のリストをどうやって管理するかですね。
データベース設計してきっちり作ってもいいのですが、今回はみんな大好きなあれを使うことにしました。

Excel Spreadsheets 方眼紙

みんな大好きな方眼紙を管理に採用しました。
エンジニアでなくても抵抗なく編集できるのがポイント高いです。

G Suite を利用しているので、社員のみが読み書き出来るシートを作ることが出来ます。

そこに上記のリストを管理するものを作って、付属する GoogleAppScript(GAS) で Slack に投げる仕組みにしました。
サーバーレスなのでメンテも簡単です。しかも無料(たぶん)。
そこに「サクッと」「近場」「たまには」の列をセットして、チュートリアル的にデータを入れてあげればリストは完成です。
あとはみんなが思い思いに編集してくれます。

実際のデータをチラ見せ。すでに遊ばれた形跡があります。
芊品香 何個あるんだ…。

Slack に投稿する

データが整えばあとはここからランダムに拾って、Slack に貼り付けるだけです。
とくに面白い処理をしているわけではないのでさくっとソース置いときます。

var SLACK_WEBHOOK = 'https://hooks.slack.com/services/XXXXXXXXXXXXXXXXXXXXXX';
var SLACK_CHANNEL = '#gourmet';
var SPREADSHEET_URL = '';
var EMOJI_ICON = ':monaca_chan:';
var BOT_NAME = 'もなかちゃん';
var LUNCH_HOUR = 12;
var LUNCH_MINUTE = 30;


// 分指定でトリガー出来ないので、一旦ここでトリガーをセット
// cf. https://qiita.com/sumi-engraphia/items/465dd027e17f44da4d6a
var setTrigger = function() {
  var triggerDay = new Date();
  triggerDay.setHours(LUNCH_HOUR);
  triggerDay.setMinutes(LUNCH_MINUTE);
  ScriptApp.newTrigger("mai").timeBased().at(triggerDay).create();
}

var deleteTrigger = function() {
  var triggers = ScriptApp.getProjectTriggers();
  for(var i=0; i < triggers.length; i++) {
    if (triggers[i].getHandlerFunction() == "choice") {
      ScriptApp.deleteTrigger(triggers[i]);
    }
  }
}

var getRows = function (range) {
  return range.getValues().map(function(x) { return x[0]; }).filter(function(x) { return x });
}

Array.prototype.random = function () {
    return this[Math.floor(Math.random() * this.length)]
}

function postMessage(message, hookPoint) {
  var payload = {
    "text": message,
    "icon_emoji": ICON_EMOJI,
    "username": BOT_NAME,
    "channel": SLACK_CHANNEL
  }
  var options = {
    "method" : "POST",
    "payload" : JSON.stringify(payload),
    "headers": {
      "Content-type": "application/json",
    }
  }
  var response = UrlFetchApp.fetch(hookPoint, options);

  if (response.getResponseCode() == 200) {
    return response;
  }
  Logger.log(response);
  return false;
}

function main() {
  deleteTrigger();
  var sheet = SpreadsheetApp.getActiveSheet();
  // A列、B列、C列
  var as = getRows(sheet.getRange(2, 1, sheet.getLastRow()));
  var bs = getRows(sheet.getRange(2, 2, sheet.getLastRow()));
  var cs = getRows(sheet.getRange(2, 3, sheet.getLastRow()));
  var a = as.random();
  var b = bs.random();
  var c = cs.random();

  var message = [
      "そろそろランチにしませんか?",
      "ランチの時間ですよ!",
      "今日のおすすめはこちらです。",
      "お腹すきましたね",
      "お昼です!",
      "お昼の時間です!",
      "ご飯行きましょう!!",
  ].random();

  message += "\n:one: " + a + "\n:two: " + b + "\n:three: " + c;
  message += "\n\n候補を編集する → <" + SPREADSHEET_URL + "|:memo:>";
  Logger.log(message);
  postMessage(message, SLACK_WEBHOOK);
  return message;
}

main の関数を呼んであげると Slack に投稿されます。
ちなみにもなかちゃんは弊社のニュースアプリ 「NewsDigest」 のマスコットキャラになるとかならないとか。


GAS には定期実行するトリガーも用意されているので、毎日叩くようにセットしてあげます。

不便なことに分単位でトリガーを走らせられないので、

https://qiita.com/sumi-engraphia/items/465dd027e17f44da4d6a

の Hack を使って 12 時までにトリガーをセットするトリガーをセットしています。

これでお昼時にいい感じの選択肢を提示するボットが出来ました。
これからはもなかちゃんがいい感じのランチに導いてくれることでしょう。

!?

image.png

image.png
リスト全消しHack…! ( 先日オープン したみたいです。)