家庭内フードロスを減らそう。LINEbotを使ったレシピの提案

夏が近づき暖かくなってきました。
食品も傷みやすい季節になってきましたね！
また、在宅ワークも進み家でのお昼ご飯などのレシピにも困っているのではないでしょうか？

ということで、今回は家庭内でのフードロスが減るよう、余っている食材をメッセージするとレシピのリンクを返してくれるボットを作って見ました。

１　材料に関連したレシピがある場合：
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">とりあえずできた。レシピを教えてくれる。<a href="https://twitter.com/hashtag/%E3%83%AC%E3%82%B7%E3%83%94?src=hash&amp;ref_src=twsrc%5Etfw">#レシピ</a> <a href="https://t.co/ZRWcB88FyA">pic.twitter.com/ZRWcB88FyA</a></p>&mdash; shima-07 (@y_kawashima_) <a href="https://twitter.com/y_kawashima_/status/1257720368090853377?ref_src=twsrc%5Etfw">May 5, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>


２　材料に関連したレシピがない場合（ランダムで料理のカテゴリを提案します）：
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">ゴメスを使ってレシピは知らない。<br>ラミレスも。<a href="https://twitter.com/hashtag/%E3%83%AC%E3%82%B7%E3%83%94?src=hash&amp;ref_src=twsrc%5Etfw">#レシピ</a> <a href="https://t.co/MR6EvBx9aP">pic.twitter.com/MR6EvBx9aP</a></p>&mdash; shima-07 (@y_kawashima_) <a href="https://twitter.com/y_kawashima_/status/1257720828050829312?ref_src=twsrc%5Etfw">May 5, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>


※ カルーセルやボタンテンプレートのサムネは[protoout studio](https://protoout.studio/)さんの可愛いアイコンをかりました。

では説明していきます。

## 前提
基本的なLINEbotの作り方はここではあまり詳しく書かないので、初めての方は下記ページを参考にしてみてください。
[1時間でLINE BOTを作るハンズオン (資料+レポート) in Node学園祭2017 #nodefest](https://qiita.com/n0bisuke/items/ceaa09ef8898bee8369d)

## 楽天レシピ系API
今回、レシピに関する情報は[楽天レシピカテゴリーAPI](https://webservice.rakuten.co.jp/api/recipecategorylist/)や[楽天レシピカテゴリ別ランキングAPI](https://webservice.rakuten.co.jp/api/recipecategoryranking/)を利用しました。
これらのAPIはドキュメントにあるように、基本的に料理のカテゴリーや、特定のカテゴリーでの上位４位までにランクインされているレシピを返してくれます。

## 準備
楽天レシピ系のAPIを利用するにはアプリIDの発行が必要なので、まず発行します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/121887/821ddf4e-fd5c-b03c-0af7-9e7c26843876.png)

発行が完了すると、APIを使うときに必要な`アプリID/デベロッパーID`などの情報が表示されるのでそれはメモしておきます。

## ソースコード
早速ですが、ソースコードです。

```recipe.js
'use strict';

const express = require('express');
const line = require('@line/bot-sdk');
const PORT = process.env.PORT || 3000;
// 追加
const axios = require('axios');

const config = {
    channelSecret: 'xxxxxx',
    channelAccessToken: 'xxxxxx'
};

const app = express();

app.get('/', (req, res) => res.send('Hello LINE BOT!(GET)')); //ブラウザ確認用(無くても問題ない)
app.post('/webhook', line.middleware(config), (req, res) => {
    // LINE上で入力された
    console.log(req.body.events[0].message.text);
    
    //ここのif文はdeveloper consoleの"接続確認"用なので後で削除して問題ないです。
    if(req.body.events[0].replyToken === '00000000000000000000000000000000' && req.body.events[1].replyToken === 'ffffffffffffffffffffffffffffffff'){
        res.send('Hello LINE BOT!(POST)');
        console.log('疎通確認用');
        return; 
    }
    Promise
      .all(req.body.events.map(handleEvent))
      .then((result) => res.json(result));
});

const client = new line.Client(config);

//////////////// ここ以降が処理を書く場所 ///////////////
async function handleEvent(event) {
  if (event.type !== 'message' || event.message.type !== 'text') {
    return Promise.resolve(null);
  }

  let mes = ''

    var result = event.message.text;
    mes = result + 'を使ったレシピ'; //wikiのurlの前の一言
    await client.replyMessage(event.replyToken, {
      type: 'text',
      // text: event.message.text //実際に返信の言葉を入れる箇所
      text : mes 
    });
    return getMenu(event.source.userId,result);
 
} //handleevent 


const getMenu = async (userId,food) =>{

    const res = await axios.get('https://app.rakuten.co.jp/services/api/Recipe/CategoryList/20170426?format=json&formatVersion=2&categoryType=small&applicationId='+ {楽天のアプリID/デベロッパーID});

    var name = [];
    var catgid = [];
    var url = [];

    var len = res.data.result.small.length;
    var a = 0;

    for(var i = 0; i < len ; i++){
        if(res.data.result.small[i].categoryName.indexOf(food) > -1 ){
            name[a] = res.data.result.small[i].categoryName;
            catgid[a] = res.data.result.small[i].parentCategoryId;
            url[a] = res.data.result.small[i].categoryUrl;
            a = a + 1;
        }
    }

    console.log(name);
    console.log(catgid);
    console.log(url);

    let col= [];
    
    // 何も引っかからなかった時の処理
    if(name.length <= 0){
        // とりあえず返事する
        await client.pushMessage(userId, {
            type: 'text',
            text: 'ごめん、その食材の使い方は知らない。代わりのおすすめメニュー↓↓',
        });

        // ランダムでこの中の物を返すためのリスト
        let category = [
            {
                categoryName: "人気メニュー",
                categoryId: "30",
                categoryUrl: "https://recipe.rakuten.co.jp/category/30/"
            },
            {
                categoryName: "定番の肉料理",
                categoryId: "31",
                categoryUrl: "https://recipe.rakuten.co.jp/category/31/"
            },
            {
                categoryName: "定番の魚料理",
                categoryId: "32",
                categoryUrl: "https://recipe.rakuten.co.jp/category/32/"
            },
            {
                categoryName: "卵料理",
                categoryId: "33",
                categoryUrl: "https://recipe.rakuten.co.jp/category/33/"
            },
            {
                categoryName: "パスタ",
                categoryId: "15",
                categoryUrl: "https://recipe.rakuten.co.jp/category/15/"
            },
            {
                categoryName: "鍋料理",
                categoryId: "23",
                categoryUrl: "https://recipe.rakuten.co.jp/category/23/"
            },
            {
                categoryName: "簡単料理・時短",
                categoryId: "36",
                categoryUrl: "https://recipe.rakuten.co.jp/category/36/"
            },
            {
                categoryName: "節約料理",
                categoryId: "37",
                categoryUrl: "https://recipe.rakuten.co.jp/category/37/"
            },
            {
                categoryName: "今日の献立",
                categoryId: "38",
                categoryUrl: "https://recipe.rakuten.co.jp/category/38/"
            },
            {
                categoryName: "健康料理",
                categoryId: "39",
                categoryUrl: "https://recipe.rakuten.co.jp/category/39/"
            },
        ]

        var len = category.length;
        var rand = Math.floor(Math.random()*(len+1));

        // 毎度ランダムに変わるようにする
        var categoryName = category[rand].categoryName + 'のレシピだよ！';
        var url = category[rand].categoryUrl;
    
        // ボタンテンプレート
        return client.pushMessage(userId,{
            
                "type": "template",
                "altText": "This is a buttons template",
                "template": {
                    "type": "buttons",
                    "thumbnailImageUrl": "{サムネにしたい画像のURL}",
                    "imageAspectRatio": "rectangle",
                    "imageSize": "cover",
                    "imageBackgroundColor": "#FFFFFF",
                    "title": "おすすめMenu",
                    "text": categoryName,
                    "defaultAction": {
                        "type": "uri",
                        "label": "View detail",
                        "uri": url
                    },
                    "actions": [
                        {
                          "type": "uri",
                          "label": "レシピへGO",
                          "uri": url
                        }
                    ]
                }
        });
    
     
    }else{
    
    var len2 = name.length;
    if(len2 > 10){len2 = 10;} // カルーセルの上限が10個なので10以上ある場合は10にする

    // "columns" の中身を作っておく  
    for(var k = 0 ; k < len2 ; k++){
        const title = name[k] ;
        const text = name[k] + 'のレシピを紹介するよ！';
        const uri = url[k] ;

        let carousel =
        {
          "thumbnailImageUrl": "{サムネにしたい画像のURL}",
          "imageBackgroundColor": "#FFFFFF",
          "title": title,
          "text": text,
          "defaultAction": {
            "type": "uri",
            "label": "View detail",
            "uri": uri
          },
          "actions": [

            {
              "type": "uri",
              "label": "レシピへGO",
              "uri": uri
            }
          ]
        };
        col.push(carousel);

    }

    return client.pushMessage(userId, {
        "type": "template",
        "altText": "this is a carousel template",
        "template": {
          "type": "carousel",
          "columns": col,
          "imageAspectRatio": "rectangle",
          "imageSize": "cover"
        }
      });

    }
}


(process.env.NOW_REGION) ? module.exports = app : app.listen(PORT);
console.log(`Server running at ${PORT}`);

```


## 説明
本当は材料をメッセージしたらその材料を使った料理を返すようにしたかったのですが、それは楽天レシピAPIをそのまま使うだけではできなかったので、まず使ったAPIは`/Recipe/CategoryList`のほうを`categoryType=small`で取ることで、入力した値をヒットしやすくなります。

入力値がAPIで返ってきた値とヒットした場合は、それを配列に入れてカルーセルで出すための準備をします。
もし、「ゴメス」のようなヒットしない値だった場合は、`categoryType=large`の分類のボタンテンプレートでランダムに出すようにしています。

LINEのカルーセルやボタンテンプレートなどに関しては下記が参考になります。
[■楽天のAPIを使ってマスクを買えるBotを作ってみた[LINEBotリッチメニュー]](https://qiita.com/3yaka4/items/87fe165e03cd8735f321)
[■LINEの公式ドキュメント](https://developers.line.biz/ja/docs/messaging-api/message-types/#text-messages)

## 作ってみて
厳密に食材に対してのレシピを返してくれるわけじゃないけど、まあまあ使えると思います。
ぜひお試しください！