気になった飲食店を探すシンプルなLINE Botを作ってみた

## 飲食店をさくっと調べて、後から見返したい:fork_and_knife:

### 気になるお店が見つかった時、どうしますか？

いつもと違う道を散歩中にお好みの飲食店を発見したり、グルメな友達が行きつけのお店を紹介してくれたのを聞いて、**気になるお店が見つかった時、皆さんどうされていますか？**

### メモアプリは二度手間だし、ググると後から見返すのに不便:cold_sweat:

私はiPhoneの**メモアプリに店名を書き留めておくか、店名をググって、グルメ情報サイトを見るかどっちかだと思います。** 特に休日の朝は、カフェで大好きなカプチーノを飲みながらのんびりするのが好きなので、朝早くから営業している居心地の良さそうなカフェを見つけた時は宝物を探し当てたかのような、ほっこりした気持ちになります。更に、カプチーノがメニューにあれば、そりゃーもう言うことないですよ！そのまま入店できない時は、店名を絶対忘れないようにメモっておきます。
メモアプリに入力した場合は、**あとから店を探すのは意外にめんどくさくて二度手間です**。メモアプリからえぇーと、店名をコピーして、Google開いて貼り付けて、はぃ、検索ってな感じで**時間がかかります** (お前どこまで遅いねん！）。
一方で、直接検索サイトで探してみると、複数の検索結果に多少目移りしてしまうものの、それなりにさくっとお店の情報にいきつきます。但し、ググって見つけたのは良いけど、**後から見返すことができない**。整理整頓が苦手な私は、お気に入りに登録したつもりが、分からなくなってしまった、、あーまた検索みたいなことが度々発生します。

## ホットペッパーAPI X LINE Botが救世主に
### さくっとお店を探せて、その情報に戻れるのがベスト:smiley:
大好きなカフェを見つけた時も、お店の検索と情報の記録にフラストレーションを感じていた私は、**①さくっと探せる**　**②後から容易に情報に戻れる**　これらの２つのニーズを満たせたらいいなと思い、LINE Botの制作を行いました。
LINE Botの特徴として、友達登録するだけで簡単に使ってもらえて、普段から使い慣れているLINEアプリを開くだけで、多くの人にすぐに使っていただける早さと手軽さは大きなメリットです。

### まずはAPI探し
課題解決したいことが決まったら、次は**利用可能なAPI探し**！
APIとは、アプリケーションの開発を容易にするために、あらかじめ備えられたソフトウエア資源。共通して用いられる機能を一定の規約に基づいて利用します。会社によって、無料版があったり、有料版だけの場合もあります。
プログラミングを始めた時にはそもそもAPIという用語も知らなかったけど、こんなに便利なものがあるんだなー！困っていることを解決する**エンジニア特有のシェア文化に改めて感心しました。**

### 無料版で使えそうなAPIはホットペッパーだけ
馴染みがある飲食店検索サイトは、４つ。ホットペッパー以外にも、ぐるなび、食べログ、Rettyを検討しましたが、2022年３月現在**無料版が使えるのはホットペッパーだけ**でした。ということで、**ホットペッパーAPI**を使わせていただくことにしました！
[公式ホットペーパーAPI](https://webservice.recruit.co.jp/doc/hotpepper/reference.html)
[【参考】飲食店検索アプリのAPI](https://zenn.dev/kohhee/articles/d4f96624b82e50)

## いざLINE Botを制作しよう
### LINE公式アカウントの作成
LINE Botを作成するには、**LINE Developers**にアクセスし**公式アカウントの作成します**。チャネルシークレットとチャネルアクセストークンが取得できたら準備完了です。

[LINE公式アカウントの作成手順](https://zenn.dev/protoout/articles/16-line-bot-setup)
### ngrokの利用準備
次に**ngrokを使うためのアカウント登録**が必要になります。こちらも事前準備が必要になります。

[ngrokの利用準備手順](https://zenn.dev/protoout/articles/47-ngrok-setup-2022)

### 実行環境の準備
1.  VScodeのインストール
2.  node.jsとnpmのインストール
3.  VScode内で、jsファイルを作成
4.  ターミナルでフォルダをnpm管理できるように初期化

### コード
取得したホットペッパーAPIを入れて実行します。
```js 
const sampleFunction = async (event) => {
    // ホットペッパーAPIのURLとAPIキーを入れる。Event.message.textでメッセージ内容を拾いEncodeURIで日本語変換されたキーワードを返す。キーワード情報からホットペッパーのURLを表示。
  
    const res = await axios.get('http://webservice.recruit.co.jp/hotpepper/shop/v1/?key=ここにAPIキーを入れる&format=json&keyword='+encodeURI(event.message.text));
          console.log(res.data.results.shop[0].urls.pc);
    
      return client.replyMessage(event.replyToken, {
        type: 'text',
        text: res.data.results.shop[0].urls.pc
      });
    }
```
続いてLINEサーバーからのWebhookデータを処理する部分です。
``` javascript
// 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);
}
```
## 結果こんな感じになります
余計な部分も多いですが、、イメージをご理解いただくためのデモ動画をツイッターにあげたので、良かったらご覧ください。

https://twitter.com/NORI30363592/status/1506621500178464769


## コード全文
```js
'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: 'チャンネルシークレットをコピペ',
    channelAccessToken: 'アクセストークンをコピペ'
};

const sampleFunction = async (event) => {
    // ホットペッパーAPIのURLとAPIキーを入れる。Event.message.textでメッセージ内容を拾いEncodeURIで日本語変換されたキーワードを返す。キーワード情報からホットペッパーのURLを表示。
  
    const res = await axios.get('http://webservice.recruit.co.jp/hotpepper/shop/v1/?key=APIキーをコピペ&format=json&keyword='+encodeURI(event.message.text));
          console.log(res.data.results.shop[0].urls.pc);
   
 
      return client.replyMessage(event.replyToken, {
        type: 'text',
        text: res.data.results.shop[0].urls.pc
      });
    }

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を初期化します
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サーバーを実行中です…`);
```
## 実際使ってみての改善ポイント
### 検索に引っかからない時、エラーが出てシステムが止まってしまう。でもどうしてよいか分からない。。（涙）
ネットで調べてみると、検索にかからない時に、エラー情報を取得してシステムが止まらないようにするには「//try{ } catch (error)」を使用するとあったので試してみたものの、これでもエラーは回避できずに今回は断念。前述のコードから変えたのはここだけなので、//try{ } catch (error)の挟み方が間違っているのだろうか？はたまた、別の対処方法が抜け漏れているのか？[Qiita Q&A](https://qiita.com/Nori777/questions/2235c3ae69aac3aa58d4)でも質問させていただきました。


```js
const sampleFunction = async (event) => {
    // ホットペッパーAPIのURLとAPIキーを入れる。Event.message.textでメッセージ内容を拾いEncodeURIで日本語変換されたキーワードを返す。キーワード情報からホットペッパーのURLを表示。
  try {
    const res = await axios.get('http://webservice.recruit.co.jp/hotpepper/shop/v1/?key=ここにAPIキーをコピペ&format=json&keyword='+encodeURI(event.message.text));
          console.log(res.data.results.shop[0].urls.pc);
    //try{ } catch (error)で検索にかからない時に、エラー情報を取得してシステムが止まらないようにする
  }catch (error) {
            console.error(error)};
      return client.replyMessage(event.replyToken, {
        type: 'text',
        text: res.data.results.shop[0].urls.pc
      });
    }
```
### 名前を直接入力じゃなくて、場所から飲食店を探せないか
フィードバックをお願いしたら、次は**場所から検索できる**のも作ってもらえると便利だねという意見を妻にもらいました。店名で探してライン上で見るのもそれなりに使えると思うけど、確かに場所で検索出来たら更にいいよね～。知らない場所を訪れた時も、場所を送るか、**GPSから近くの飲食店を５つ位、距離かランキングのパラメータを使って取り出せたら面白そう。**
何を隠そう、実はこれ実現したかったんですが、ホットペッパーの個別のパラメータを組み合わせていくのは、私の力不足でどうにもなりませんでした。プログラミング経験者からみればシンプルとお感じになるんでしょうが、今回のBot制作も、周りにサポートしていただいて、その後エラーとも格闘しながら苦しんだ果てに、どうにかここまでたどり着いた状況です、とほほ。。私にとって**プログラミングは、暗闇の中のトンネルをとぼとぼ進んでいく感じ**、まだまだそんな感覚が続きそうです、、光は見えてくるのだろうか？:rolling_eyes:

至らぬ点が多い記事ですが、**最後までご覧いただき、ありがとうございました！！**