Posted at

晩ごはんの献立を一緒に考えてくれるbotを作ってみた

More than 3 years have passed since last update.


はじめに

この記事はリブセンスアドベントカレンダー2015(その2)の10日目です。

10日目担当の @nwatanabe です。普段は、治療ノートという医療情報系サービスのエンジニアとして、コード書いたり施策出したりと色々やってます。

突然ですが、みなさんはちゃんとご飯を作っていますか?良いコードを書くためには不健康ではダメですよね。

自分は今まで、朝食抜きの2食で生活していたのですが、そろそろいい年になってきたので、健康のために3食ちゃんとご飯を作るようにしました。ただ、1ヶ月ぐらいやってみて、いくつか問題が出てきました


  • 夕飯の献立が定型化してきている(好きなもの/思いつくものしか作らない

  • 通常、夫婦間で献立の話をする際にコミュニケーションツールとして使っているLINEでレシピ情報を展開したり、引っ張ってきたりできないので、都度外部サービスで調べるのが面倒

  • そもそも毎日新しい献立を積極的に探すほどのモチベーションはない

このままだと○○の塩焼きと○○の野菜炒め(かろうじて○○に入る食材は違う)のローテーションが続いてしまうので、そんな献立を大きく変えてくれるようなbotが欲しい、、ということで作ってみることにしました


やったこと

食べたいものがあってもなくても夕飯の献立を提案してくれるようなbotを、今回はslack+heroku+ruboty+楽天レシピAPIを利用して作りました


1. 楽天レシピAPIを登録

ざっと調べた感じでは、レシピAPIを提供しているのは楽天ウェブサービスだけだったので楽天ウェブサービス: 新規アプリ登録から登録して使わせていただきました。登録後のアプリケーションIDは後ほどherokuのconfigに設定します。


2. slackにbot用アカウントを作成する

botには、会話の流れを読んで勝手に割り込んできてほしくなかったため、RubotyをHerokuにデプロイしてSlackで使う辺りを参考に、slackでGatewayの設定(Team Settings > Permissions > Gateways から「Enable XMPP gateway (SSL only)」を選択)をしてbot用のアカウントを用意しました


3. botを用意

今回はrubyベースのrubotyを利用しました。rubotyは以前から利用しており、今利用しているslackに別のbotを入れるよりはruboty のプラグインを追加するほうがいいかなぁぐらいの理由です


Gemfile

source "https://rubygems.org"

ruby '2.2.3'
gem "ruboty"
gem "ruboty-slack"
...


Procfile

bot: bundle exec ruboty


bundle exec rubotyとかでローカル環境でも動作させることができるので、そこらへんを試したあとherokuにデプロイします


4. herokuにデプロイ

herokuへのデプロイ方法は他の記事等々でも良く紹介されているので割愛します。

成功するとこんな感じ

デプロイ後、1.で取得したアプリケーションIDもheroku側に設定しておきます

$ heroku config:set APPLICATION_ID=XXXXXXXXXXXXX --app=bot-application-name

ここまできたら準備完了。早速本題のものを開発します


5. 何を食べたいのか伝える

食べたいものを伝える上で、以下の処理を考えました


  • 該当するものがある場合、ランダムでレシピを返す


  • 該当するものがない場合、聞き返す


  • (実は一番個人的にニーズがある)なんでもいい場合は、勝手に選んでくれる

コードだとこんな感じです。

プラグインの作り方はRuboty の Plugin のつくりかた #rubotyを参考にしてます

(ざっくり作ってしまっているのでコードが汚いのは目をつぶっていただけると、、


lib/ruboty/handlers/bangohan/recommend_recipe.rb

require "ruboty/bangohan/actions/recommend_recipe"

module Ruboty
module Handlers
class Bangohan < Base
on /(.*)?今日は(?<food>.+)かな\z/, name: 'recommend_recipe', description: 'output recommend recipe'

def recommend_recipe(message)
food = message[:food]
Ruboty::Bangohan::Actions::RecommendRecipe.new(message).call(food)
end
end
end
end



lib/ruboty/bangohan/actions/recommend_recipe.rb

require 'faraday'

require 'json'

module Ruboty
module Bangohan
module Actions
class RecommendRecipe < Ruboty::Actions::Base
RECIPE_CATEGORY_URL = "/services/api/Recipe/CategoryList/20121121?format=json&elements=categoryName%2CcategoryId%2CparentCategoryId&categoryType=medium&applicationId=#{ENV['APPLICATION_ID']}"
RECIPES_URL = "/services/api/Recipe/CategoryRanking/20121121?format=json&formatVersion=2&applicationId=#{ENV['APPLICATION_ID']}&categoryId="

def call(food)
message.reply(hearing(food))
end

private

def hearing(food)
results = recipe_categories
if food == 'なんでもいい'
recipe_category_id = results[results.keys.sample(1)]
recipe = choose_recipe(recipe_category_id)
"#{food}なら、#{recipe['recipeTitle']} とかはどう?\n #{recipe['recipeUrl']}\n" +
' <http://webservice.rakuten.co.jp/|Supported by 楽天ウェブサービス> '
else
recipe_category_id = results.fetch(food, nil)
if recipe_category_id.nil?
"#{food}だとわからないからもうちょっと詳しく教えて"
else
recipe = choose_recipe(recipe_category_id)
"#{food}だと、#{recipe['recipeTitle']} とかはどう?\n #{recipe['recipeUrl']}\n" +
' <http://webservice.rakuten.co.jp/|Supported by 楽天ウェブサービス> '
end
end
end

def recipe_service_api(api_path)
@conn ||= Faraday.new(:url => 'https://app.rakuten.co.jp') do |faraday|
faraday.request :url_encoded
faraday.adapter Faraday.default_adapter
end
end

def recipe_categories
response = recipe_service_api.get(RECIPE_CATEGORY_URL)
results = JSON.parse(response.body.force_encoding('UTF-8'))
results["result"]["medium"].map{|result| [result['categoryName'], "#{result['parentCategoryId']}-#{result['categoryId']}"]}.to_h
end

def choose_recipe(recipe_category_id)
response = recipe_service_api.get("#{RECIPES_URL}#{recipe_category_id}")
results = JSON.parse(response.body.force_encoding('UTF-8'))
results['result'][Random.rand(1 .. results['result'].count)]
end
end
end
end
end



ちなみに

開発時には、以下のようにgemのパスをローカル環境の開発中のものにしてheroku localでbotを立ち上げてslackで確認することができたので、いちいちherokuにデプロイしなくても動作検証できて楽でした。


Gemfile

...

gem 'ruboty-bangohan', path: '../ruboty-bangohan'
...

ここまでできたら作成したgemを含めたbotのソースをherokuにデプロイしてあげます

(今回のやつはRubyGemsにはアップせずに個人のgithubにアップして動作確認をしました


使ってみる

うまくr7kamura/ruboty-talkあたりと合わせて利用するとbotの会話も入り乱れながら普段の夫婦の晩ごはんの献立決めがこんなに賑やかになりました


雑感


  • rubotyプラグインはいくつか作っているけど、簡単に作れるしローカル環境でもデバッグできるので作っていて楽しい

  • 今までレパートリーになかったレシピも作るようになったし、コミュニケーションも活性化したので良かった

  • ただ一番大事なのは会社から帰ってからでもご飯を作ろうとする強い意志

本当は提案されたものが食べたい気分じゃない時に、似たような別の献立をオススメしたり、献立決めたら買い物に行くために共用のカレンダーに記載したりする機能も作っていたんですが、途中で力尽きてしまったので、それは年末までの宿題としようかと思います。ではー