LINE Botでお菓子好きによる お菓子好きのためのガチャを作成してみた

#お菓子好きですか？
私はお菓子が好きです。
クッキー、チョコレート、ケーキ、お団子・・・あぁ幸せ:two_hearts:
世の中には、期間限定や地域限定など様々なお菓子があり、普段の生活ではお目にかかるのが難しいお菓子もあり、お菓子ラバーとしては残念な状態:disappointed_relieved:
まだ見ぬお菓子に出会うため、無課金で遊べるお菓子ガチャを作成して妄想でのお菓子ライフを充実させましょう！

#お菓子ガチャ（LINE Bot）
LINE Botでお菓子の種類（チョコとか）を入力すると、ランダムで返答してくれるBotを作成しました。
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">お菓子好きには堪らないガチャが出来た<a href="https://twitter.com/hashtag/protoout?src=hash&amp;ref_src=twsrc%5Etfw">#protoout</a> <a href="https://t.co/bcbzgS2CYk">pic.twitter.com/bcbzgS2CYk</a></p>&mdash; momoiku (@momoiku3) <a href="https://twitter.com/momoiku3/status/1450344299791470593?ref_src=twsrc%5Etfw">October 19, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

#作り方
###使用環境
・Node.js
・axios
・JavaScript
・LINE　Messaging API
・[お菓子の虜 Web API](https://sysbird.jp/toriko/webapi/)

###プログラム
####★プログラム全体像

<details><summary>長文なので折りたたみます</summary><div>

```javascript
'use strict';

// ########################################
//               初期設定など
// ########################################

// パッケージを使用します
const express = require('express');
const line = require('@line/bot-sdk');
const axios = require('axios');

// ローカル（自分のPC）でサーバーを公開するときのポート番号です
const PORT = process.env.PORT || 3000;

// Messaging APIで利用するクレデンシャル（秘匿情報）です。
const config = {
    channelSecret: '作成したBotのチャネルシークレット',
    channelAccessToken: '作成したBotのチャネルアクセストークン'
};

// ########## ▼▼▼ 処理関数 ▼▼▼ ##########
const getSweetsTag = async (userId, tag) => {
    let result = '';
    let img ='';

        // axiosでお菓子のAPIを叩きます
        const res = await axios.get('https://www.sysbird.jp/webapi/?apikey=guest&keyword=' + encodeURIComponent(tag) + '&max=1&order=r&format=json');
        //const item = res.data;
        const item = res.data;
        console.log(res.data);

        if(item.count==0){
            //お菓子のヒットがなかった場合
            result = tag + 'でお菓子ガチャは引けませんでした。'+'\n'+'\n'+'キーワードを変更して再チャレンジしてください！'+'\n'+'例：お煎餅⇒煎餅';
            // ターミナルにも検索結果を出しておきます
            console.log(`「${tag}」の検索結果：なし`);
            //任意の画像URL
            img ="任意の画像URLを記述";

        }else{
            //お菓子のヒットがあった場合
            result = tag + 'のお菓子ガチャ結果は……' + '\n'+ item.item.name + 'です！'+'\n'+'\n'+'★お菓子の紹介★'+'\n' + item.item.comment +'\n'+'紹介サイト：'+item.item.url+'\n'+'\n'+'※発売終了の場合があります。';
            // ターミナルにも検索結果を出しておきます
            console.log(`「${tag}」の検索結果：${item.item.name}`);
            //お菓子の画像URL取得
            img =item.item.image;

        }

    //ガチャ結果の返答

    //画像
    await client.pushMessage(userId, {
        type: 'image',
        originalContentUrl: img,
        previewImageUrl: img,
    });
    //お菓子の文言
    return client.pushMessage(userId, {
        type: 'text',
        text: result,
    });

}

const sampleFunction = async (event) => {
    // 検索には少し時間がかかるので、これの結果はリプライの後に送信
    getSweetsTag(event.source.userId, event.message.text);

    // こちらが先に返事を返します
    // ユーザーからのメッセージに対する「リプライ」
    return client.replyMessage(event.replyToken, {
        type: 'text',
        text: 'お菓子ガチャスタート！'
    });
};

// ########## ▲▲▲ 処理関数 ▲▲▲ ##########

// ########################################
//  LINEサーバーからのWebhookデータを処理する部分
// ########################################

// LINE SDKを初期化します
const client = new line.Client(config);

// LINEサーバーからWebhookがあると「サーバー部分」から以下の "handleEvent" という関数が呼び出されます
async function handleEvent(event) {
    // 受信したWebhookが「テキストメッセージ以外」であればnullを返すことで無視します
    if (event.type !== 'message' || event.message.type !== 'text') {
        return Promise.resolve(null);
    }
    // サンプル関数を実行します
    return sampleFunction(event);
}

// ########################################
//          Expressによるサーバー部分
// ########################################

// expressを初期化します
const app = express();

// HTTP POSTによって '/webhook' のパスにアクセスがあったら、POSTされた内容に応じて様々な処理をします
app.post('/webhook', line.middleware(config), (req, res) => {
  
  // 検証ボタンをクリックしたときに飛んできたWebhookを受信したときのみ以下のif文内を実行
  if (req.body.events.length === 0) {
    res.send('Hello LINE BOT! (HTTP POST)'); // LINEサーバーに返答します（なくてもよい）
    console.log('検証イベントを受信しました！'); // ターミナルに表示します
    return; // これより下は実行されません
  } else {
    // 通常のメッセージなど … Webhookの中身を確認用にターミナルに表示します
    console.log('受信しました:', req.body.events);
  }

  // あらかじめ宣言しておいた "handleEvent" 関数にWebhookの中身を渡して処理してもらい、
  // 関数から戻ってきたデータをそのままLINEサーバーに「レスポンス」として返します
  Promise.all(req.body.events.map(handleEvent)).then((result) => res.json(result));
});

// 最初に決めたポート番号でサーバーをPC内だけに公開します
// （環境によってはローカルネットワーク内にも公開されます）
app.listen(PORT);
console.log(`ポート${PORT}番でExpressサーバーを実行中です…`);

```

</div></details>

####★プログラム解説
処理関数の中で、悩んだ部分を中心に解説します。

【１】データが表示されないのはなぜ？

プログラムを実行すると、データは取得できるけれどLINE側に表示される結果が「undefined」となってしまう現象が発生。
どうやら、このAPIはJSON意外にも複数の形式に対応しているため、取得する際に「JSONでデータを取ってね！」という指定を入れないと違う形式で取得してしまうようです。
APIを実行するときに、記述の最後に「format=json」を記載すると、JSON形式でデータを取得できました。

```javascript
// axiosでお菓子のAPIを叩きます
const res = await axios.get('https://www.sysbird.jp/webapi/?apikey=guest&keyword=' + encodeURIComponent(tag) + '&max=1&order=r&format=json');
```


【２】お菓子がヒットしない場合はどうする？

何かしらのお菓子がヒットする素敵なAPIですが、検索方法によってはヒットしない場合もあります。
例えば、「煎餅」ならヒットするけど、「お煎餅」だとヒットしないとか。
ヒットしない場合はエラー値が返ってくるかと思いましたが、その場合はJSONデータの最後にある「count」の値を「0」として返していました。

```
{ status: 'OK', count: '0' }
```

この「count」を使って0件用の返答メッセージを作成しました。

```javascript
if(item.count==0){
   //お菓子のヒットがなかった場合
   result = tag + 'でお菓子ガチャは引けませんでした。'+'\n'+'\n'+'キーワードを変更して再チャレンジしてください！'+'\n'+'例：お煎餅⇒煎餅';
   // ターミナルにも検索結果を出しておきます
   console.log(`「${tag}」の検索結果：なし`);
   //任意の画像URL
   img ="任意の画像URLを記述";

   }else{
      //お菓子のヒットがあった場合
      result = tag + 'のお菓子ガチャ結果は……' + '\n'+ item.item.name + 'です！'+'\n'+'\n'+'★お菓子の紹介★'+'\n' + item.item.comment +'\n'+'紹介サイト：'+item.item.url+'\n'+'\n'+'※発売終了の場合があります。';
      // ターミナルにも検索結果を出しておきます
      console.log(`「${tag}」の検索結果：${item.item.name}`);
      //お菓子の画像URL取得
      img =item.item.image;

        }
```



【３】画像を表示したいのだけど？

メッセージにURLがあれば、そのURLの画像が小さく表示されるけれど、どうせなら画像のみが表示されているメッセージが欲しい。
そんな時は、メッセージのタイプを「image」として作成すると、画像が送信されます。
参考：[画像メッセージ](https://developers.line.biz/ja/reference/messaging-api/#image-message)

```javascript
await client.pushMessage(userId, {
   type: 'image',
   originalContentUrl: img,
   previewImageUrl: img,
    });
```

#最後に
今回作ったLINE Botでは、ガチャポン感覚で色々な期間限定・地域限定のお菓子に出会えます。
たくさんのお菓子のデータが蓄積されているAPIなので、知らないお菓子や、懐かしいお菓子も登場するため、お菓子好きにはわくわくが止まらない物が出来ました！
今日のおやつガチャ（現在発売していないものもあるよ！）とか、このお菓子知っているかいゲームなどなど、色々と遊んでみてください:relaxed: