26
21

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 5 years have passed since last update.

ディズニーの待ち時間を教えてくれるLINE botを作ってみた

Last updated at Posted at 2018-02-14

はじめに

ディズニーの待ち時間をリアルタイムで教えてくれるサイトやアプリはよく見かけていたのですが、
LINEでのBotはあまり聞かないなーと思い、今回作ってみました。

memo.jpg

個人的意見ですが、LINEの利用側として嬉しいのは、スマホが速度制限にかかっていても強いところかなと思います。
ですので、今回はカルーセルタイプ等は使わず文字列操作のみ、とにかく手軽に使えるよう機能は最小限に抑えてみました。

ユーザ操作は以下の通り。

  1. botを開くと、リッチメニューが表示される
  2. 「ランド」か「シー」か選ぶ
  3. 選んだ方の現在の待ち時間が一覧表示される

シンプルですね。ユーザが行うことは2つのボタンのどっちかをポチるのみです。

今回は以下のサイトで待ち時間の情報がまとめられていたので、
こちらをスクレイピングして情報を頂かせてもらいました。

ディズニーランドのリアルタイム待ち時間

サーバ側の処理は以下の通り。

  1. リッチメニュータップ時に固定メッセージを送信(ランドorシー)
  2. 上記URLをスクレイピングし、情報取得
  3. 文字列操作・必要な情報の整形
  4. LINEへのメッセージ返却

作成したLINE Bot

先に作成したLINE Botを掲載しておきます。
よかったら友達登録して、速度制限時のディズニーなどで使ってみてください。

d-suke-botQR.png

LINE友達追加

開発環境

  • npm 5.6.0
  • Node.js v8.1.3
  • LINE Messaging API
  • Heroku

作成過程

LINEbotアカウントの作成(Messaging API)

まずはLINE DevelopersでBotアカウントを作成します。
LINE Developers
初回の方は新規プロバイダー作成からプロバイダーの設定を先に行ってください。

Messaging APIには、pushタイプとreplyタイプの2種類のメッセージ送信方法がありますが、
今回はreplyのみですので、フリーのプランにしています。

一通り登録が終わると、以下のような画面になるかと思います。

linebot.png

この際、Webhookの利用は「利用する」にし、Webhook先を今から作成するアプリのURLにしておきましょう。
また、

  • Channel ID
  • Channel Secret
  • アクセストークン

は後ほど使うのでメモしておいて下さい。

LINE@の設定変更

このBotでは、デフォルトのリッチメニューも利用したいため、LINE@の管理画面から
それらの設定も行っていきます。

LINE@管理画面

こちらも登録がまだの場合は先に済ませて下さい。

lineatto.png

作成し、LINE Developersで作成したアカウントに飛ぶと、上記のような画面になります。
ここから、「リッチコンテンツ作成」→「新規作成」と進みます。

表示設定を「反映する」にし、タイトル、表示期間等を設定します。
このBotでは、
初期表示メニュー:表示あり
テンプレート選択:画像で作成
としています。

linecontent.png

下側にあるコンテンツ設定でエリアタップ時の動作を

  • URL
  • テキスト
  • キーワード
    から選択できます。

今回はテキストで、左半分だと「>Dランド」、右半分だと「>Dシー」と
テキストを返すよう設定しました。

Herokuの設定

Herokuとは

Paasの一つで、クレジットカードなどの登録がなくとも利用可能なお安いサービスです。
DBやcronなどのアドオンを利用したい場合にはクレジットカードの登録が必要ですが、今回は無しで大丈夫です。

Herokuへのデプロイ

Herokuのリポジトリはgitでの管理となるため、CUIから簡単にデプロイできます。
https://git.heroku.com/{リポジトリ名}.git といったgit URLになります。

このURLをそのまま使うのであれば、

git clone https://git.heroku.com/{リポジトリ名}.git

アプリ作成後、

git add {変更したファイル}
git commit -m 'コメント'
git push https://git.heroku.com/{リポジトリ名}.git

でデプロイできます。

では、実際に作っていきましょう。

botの作成

ディレクトリ構成

構成は以下のようになっています。

┠ node_modules/
┠ routes/
┃  ┗ lineBot.js
┠ index.js
┠ package.json
┗ package-lock.json

まずは必要なものをインストールします。
LINEが出しているNode.jsのSDKがあるのですが、
元々慣れていたライブラリがあったので今回はそちらを利用してBotを作成します。

npm init
npm install --save express linebot body-parser cheerio-httpcli

cheerio-httpcli というのは後ほどスクレイピングで利用するライブラリです。

package.jsonのscriptsには node index.js を追記しておいて下さい。

ルーティング設定

まずはindex.jsから書いていきます。

index.js
const express = require('express');
const app = express();
const bodyParser = require('body-parser');
const lineBot = require('./routes/lineBot');

app.set('port', (process.env.PORT || 8000));
app.use(bodyParser.urlencoded({
    extended: true
}));
app.use(bodyParser.json({
    verify(req, res, buf) {
        req.rawBody = buf
    }
}));
app.use(bodyParser.urlencoded({ extended: false }));

app.use('/',lineBot);

app.listen(app.get('port'), function() {
    console.log('Node app is running');
});

app.use('/',lineBot); の部分と、LINE Developersのwebhookが一致するように
webhook URLを設定しましょう。

この例では、800番ポートでlistenし、
このアプリのドメインのルートディレクトリにリクエストが来た際、
routesディレクトリ配下のlineBot.jsにイベントを渡しています。

各種イベントの反映

次に、lineBot.jsを見ていきます。

lineBot.js
const express = require('express');
const linebot = require('linebot');
const router = express.Router();
const bodyParser = require('body-parser');
const bot = linebot({
    channelId: process.env.LINE_CHANNEL_ID,
    channelSecret: process.env.LINE_CHANNEL_SECRET,
    channelAccessToken: process.env.LINE_CHANNEL_ACCESS_TOKEN
});

const parser = bodyParser.json({
    verify: (req, res, buf, encoding) => {
        req.rawBody = buf.toString(encoding);
    }
});

router.post('/', parser, (req, res, next) => {
    if (req.body.events === '') {
        return;
    }
    if (!bot.verify(req.rawBody, req.get('X-Line-Signature'))) {
        return res.sendStatus(400);
    }
    bot.parse(req.body);
    res.set('Content-Type', 'text/plain');
    res.status(200).end();
});

// 友達追加
bot.on('follow', (event) => {
    console.log('follow event');
});

// ブロック
bot.on('unfollow', (event) => {
    console.log('unfollow event');
});

//メッセージイベント
bot.on('message', (event) => {
    console.log('message event');
});

module.exports = router;

ここでは、
はじめにbody-parserを用いてリクエストボディのパースを行い、
linebot ライブラリを用いて、LINEからの各イベントを非同期にタイプごとで
分けて受け止めるようにしています。

上記の例では、フォロー・ブロック・メッセージ時の
イベントのみ記載しました。

WEBスクレイピング

次に、最初にお伝えしたWEBサイトから待ち時間の情報を拾っていきます。
まずは最上部にcheerio-httpcliを追加します。

const express = require('express');
const linebot = require('linebot');
const router = express.Router();
const bodyParser = require('body-parser');
const cheerio = require('cheerio-httpcli');

先ほどのメッセージイベントを以下のように変更しましょう。

bot.on('message', async (event) => {
    console.log('message event');

    if(event.message.type !== 'text') {
        return;
    }
    let replyMessage;
    if(event.message.text.indexOf('Dランド') !== -1) {
        replyMessage = await getWaitingTime("land");
    }else if(event.message.text.indexOf('Dシー') !== -1){
        replyMessage = await getWaitingTime("sea");
    }else {
        replyMessage = "待ち時間を取得するには、メニューからボタンをお選び下さい。";
    }
    event.reply(replyMessage);
});

スクレイピングするためのfunctionを1つ追加します。

async function getWaitingTime(name) {
    const cheerioObject= await cheerio.fetch('http://tokyodisneyresort.info/smartPhone/realtime.php', {park: name, order: "wait"});
    let replyMessage = "";
    let lists = cheerioObject.$('li').text();

    lists = lists.trim().replace(/\t/g, "").replace(/\n+/g, ",").split(",");
    lists.forEach((list) => {
        if (list.indexOf("更新") !== -1){
            replyMessage += list;
        } else if (/FP|中|分|情報なし|案内/.test(list)){
            replyMessage += "\n" + list;
        } else {
            replyMessage += "\n\n" + list;
        }
    });
    return replyMessage;
}

最初のcheerio.fetchでGETのリクエストパラメータを渡せます。
その結果から、jQueryのように要素を指定して値を拾うことができます。

URLのDOM構造や拾い方次第でやり方は色々あると思うのですが、
自分にはいい取り方が思いつかなかったので、すべてのアトラクション名・待ち時間だけでなく
間のタブや改行まで全部ひっくるめて取る強引な形で取得しました。

力技感が否めないですが、その後の文字列操作で無理くり整形し、配列として格納しています。
そして最後に一つの文字列として改行を入れて調整しました。

ここまで行うと、以下のような形になっているかと思います。

19時52分現在のディズニーランド
スペース・マウンテン

60分[20:42更新]
(FP: 発券終了 )
ビッグサンダー・マウンテン

60分[20:52更新]
(FP: 発券終了 )
スプラッシュ・マウンテン
ウッドチャック・グリーティングトレイル(ドナルド)
30分[19:48更新]
ウッドチャック・グリーティングトレイル(デイジー)
:
:

あとはデプロイして完了です。

github

Botのソースは以下に置いています。
https://github.com/daitasu/d-suke-bot

まとめ

・無理くりでも文字列操作でスクレイピングはできる
・機能がシンプルでもBotはできる

今回はとにかくシンプルを目標にしましたが、
そのうちリッチメニューAPIかボタンタイプ等でエリア別の取得等はできるようにしたいですね。

あと、Herokuだと30分間アクセスがないとインスタンスがスリープしてしまい、
LINEのリッチメニュータップから返信まで5-8秒程度かかってしまいます。
もしEC2やLambda等が使える環境であればそちらの方が良さそうな印象です。

このBotでは、待ち時間の取り方がゴリ押しになってしまったので、
なにか良い案があればご教授お願い致します。

26
21
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
26
21

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?