8
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

位置情報を送ると最寄りのラーメン店を教えてくれるLINE botをつくってみた🍜(2020年版)

Last updated at Posted at 2020-03-29

#概要
 
 この記事はLINE bot・javascrptの初心者が、「LINE bot 面白い!」という熱意で作ったものの制作記です。

 何をつくったか...。
「LINE botに位置情報を送ると、その近辺のラーメン屋さんを教えてくれるbot」を作りました🍜!

【デモ】

 本記事ではこのLINE botのサーバプログラムと、ラーメン店情報を検索するためにぐるなび APIを解説しています。基礎的な機能のみ実装しているので、店舗の星の多さ、複数件表示などは未実装です。

 位置情報を送ると、その最寄りのラーメン屋さんを一軒返すところまで動かしました。
以下、2020年3月29日時点の情報です。

#開発環境
OS:Windows 10
Node.js:v10.15.3

【ライブラリバージョン】
@line/bot-sdk:6.8.4
express:4.17.1
axios:0.19.2

#構成
 構成は次のようになっています。ローカルPC上でNode.jsを動かしているので、グローバルネットワークとのつながりをngrokでトンネリングしています。

構成図.jpg

 位置情報をNode.jsサーバが受け取ると、ぐるなび APIを利用して最寄りののラーメン店情報を取得します。このとき、ユーザから送られた位置情報をぐるなび APIのリクエストURLに含めています。

#実装
##ぐるなび APIの調査
 ぐるなび APIはAPIの使用説明だけでなく、APIテストツールまで用意されていました。手始めに、freewordクエリを追加してリクエストしてみると、レスポンスが下の方に表示されます。
(アクセスキーはテスト用になっています)

image.png

 どんなクエリがあるかは、API仕様のレストラン検索 APIで公開されています。

##プログラムからアクセスしてみる

 LINE botとのやり取りと、APIアクセスはこちらのソースコードをもとに進めていきます。
APIテストツールを使うと、リクエストURLが生成されるのでコピーしてaxisos.getに引数に与えます。
こんな感じです。

axios.get('https://api.gnavi.co.jp/RestSearchAPI/v3/?keyid=b17d2f36873123b0d0f09e9139556c79&freeword=ラーメン');

 この状態でアクセスしたら、次のようなエラーが発生しました。

Request path contains unescaped characters

 コーディングミスというよりは、URLとして認識されていないようなエラーメッセージですね。試しにフリーワードをローマ字(ra-men)にしたらエラーが解消されて、レスポンスが返るようになりました!
↓レスポンスのログ

ローマ字なら動いた.JPG

 一先ず、これでレスポンスが返るので先に進みます。日本語対応は後述の「後回しにしていた日本語対応」で解説しています。

##最寄りの店に絞る
 フリーワード検索のみだと全国の店が検索されてしまうので、最寄りの店に絞ります。これはぐるなび APIのクエリに経緯を追加することで絞ることが可能です。

 最初は経緯を固定で動かします。経緯を調べるにはGoogleマップが便利です。今回はProtoOut Studioの本拠地であるgranicaにしてみました。

【リクエストURL」

https://api.gnavi.co.jp/RestSearchAPI/v3/?keyid=アクセスキー&latitude=35.704607&longitude=139.772416&range=2&freeword=ラーメン

 このURLで問題なくレスポンスが来ればOKです。

が、居酒屋が引っかかる!!!!!!
image.png

##ラーメン店に絞る

 ラーメンに絞りたいので、レスポンスデータの中のcategoryでフィルタリングしました。

for(var num = 0; num <= response.data.rest.length; num++){
    if(response.data.rest[num].category === 'ラーメン'){
      console.log(response.data.rest[num]);
      ramen_url = response.data.rest[num].url_mobile;
      break;
    }
  }

 これで最寄りのラーメン店に絞ることができました!と、思いきや最寄りにラーメン店が見つからなかった場合、エラーが発生します。ちなみに、最寄りとは半径500m圏内です。今回は正常系のみ作ることを目的とし、エラーハンドリングをスルーします...。

後回しにしていた日本語対応

 フリーワードに日本語が入っていることが原因なので、URLにエンコードしてくれるencodeURI関数がありました。こちらの記事の通りになおすと、フリーワードに日本語が入っていても通るようになりました!
こんなコードです。

url = 'https://api.gnavi.co.jp/RestSearchAPI/v3/?keyid=アクセスキー&latitude=35.704607&longitude=139.772416&range=2&freeword=ラーメン';
  const encodeUrl = encodeURI(url);
  const response = await axios.get(encodeUrl);

LINE botへのレスポンスを位置情報にする

 このままテキストで返してもよいのですが、LINE botには位置情報を返す機能がありまので、位置情報で返してみました。ちなみに、テキストでURLを返すと次のようになります。
Screenshot_20200328-230452_LINE.jpg

これを位置情報にすると、

Screenshot_20200328-231744_LINE.jpg

こっちの方が見栄え良さそうですね イェーイ(/・ω・)/

LINE botで位置情報を返すコードは次の通りです。経緯だけではなく住所も返す必要があります。
コード中のhitnumは、レスポンスデータの中で、category”ラーメン”にヒットした位置が格納されているとします。ぐるなび APIの良いところはこれらの情報が全てそろっていることです👍

return client.replyMessage(event.replyToken, {
  type: 'location',
  title: response.data.rest[hitnum].name,
  address: response.data.rest[hitnum].address,
  latitude: response.data.rest[hitnum].latitude,
  longitude: response.data.rest[hitnum].longitude
});

##位置情報入力を追加

 最後に位置情報入力に対応させます。LINEから位置情報を受け取ってログに出し、URLに加えるコードは次の通りです。入力が位置情報のみとなるようにtypeで判定しています。

 if (event.type !== 'message' || event.message.type !== 'location') {
    return Promise.resolve(null);
  }
  console.log(event.message.latitude + ' : ' + event.message.longitude);
 url = 'https://api.gnavi.co.jp/RestSearchAPI/v3/?keyid=アクセスキー&latitude=' + event.message.latitude + '&longitude=' + event.message.longitude + '&range=2&freeword=ラーメン';

#余談
 
 今回、正常系で動くところまで作れたのですが、ラーメン店が見つからないかった場合のエラーハンドリングを入れたかったなと思います。例えば、かわいい動物画像を出すとか。

 もう一つ思ったのは、jsonデータのコンソールログデバッグは時間がかかったなと。APIの解説がされているサイトをみて一気にコーディングできたらよいのですが...。まずは経験して慣れることが大切ですね。

 ちなみに、ProtoOut Studioの授業後から制作を開始し、Qiita公開まで6時間程度かかりました。
javascriptのデバッグ不慣れなのと、Qiita記事執筆をもう少し効率化できそうなので、もう少し頑張ってプロトアウトのサイクルを短くしてきたいなと思っています。

 ほんとに余談ですが、ProtoOut Studioの一環で進めているので、よくわからないエラーが発生して困っていると講師の方々に相談できます。校長である菅原のびすけさんに直接コーディング指南していただいてものすごく緊張していました(;'∀')

 今回はぐるなびAPIを利用しましたが、他にも猫の種類検索APIこの猫なに猫?Harry Potter APIなど気になるAPIが沢山あります(^^♪

 今後はこのLINE botをベースに、connpass APIやGoogle MAP APIなどと連携して、勉強会やイベントのお知らせと夕食を提案してくれるようなサービスにしていけたら面白そうかなと思っています。

#参考

#ソースコード


'use strict';

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

const config = {
    channelSecret: 'LINE botのチャンネルシークレット',
    channelAccessToken: 'LINE botのアクセストークン'
};

const app = express();

app.get('/', (req, res) => res.send('Hello LINE BOT!(GET)')); //ブラウザ確認用(無くても問題ない)
app.post('/webhook', line.middleware(config), (req, res) => {
    console.log(req.body.events);

    //ここの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) {
  var url
  var ramen_url;
  var hitnum;

  // 位置情報のみに入力制限
  if (event.type !== 'message' || event.message.type !== 'location') {
    return Promise.resolve(null);
  }

  // 取得した位置情報をログに表示
  console.log(event.message.latitude + ' : ' + event.message.longitude);

  // ぐるなびAPIを使うためのURLに経緯を加える
  url = 'https://api.gnavi.co.j
p/RestSearchAPI/v3/?keyid=af2b4862cbe801c5e2e1b87ef52881f6&latitude=' + event.message.latitude + '&longitude=' + event.message.longitude + '&range=2&freeword=ラーメン';
  const encodeUrl = encodeURI(url);

  // ぐるなびAPIに問い合わせ
  const response = await axios.get(encodeUrl);

  //レスポンスの中からcategory"ラーメン"を探索
  for(var num = 0; num <= response.data.rest.length; num++){
    if(response.data.rest[num].category === 'ラーメン'){
//      console.log(response.data.rest[num]);
      hitnum = num;
      ramen_url = response.data.rest[num].url_mobile;
      break;
    }
  }
 
// ヒットしたラーメン店の住所をLINE botに返す
return client.replyMessage(event.replyToken, {
  type: 'location',
  title: response.data.rest[hitnum].name,
  address: response.data.rest[hitnum].address,
  latitude: response.data.rest[hitnum].latitude,
  longitude: response.data.rest[hitnum].longitude
});
}
app.listen(PORT);
console.log(`Server running at ${PORT}`);
8
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?