PythonとHerokuで謎解きLINEbotを作る　前編

:::note warn
（2022/09/30追記）本記事で利用している**Herokuの無償プランは、2022年11月28日（月）に廃止されることとなりました**。ご注意ください。
https://forest.watch.impress.co.jp/docs/news/1435422.html
:::

1週間前、「27 Letters」というLINE公式アカウントを利用した謎解きゲームをリリースしました。

https://line.me/R/ti/p/@979mvoqz

この記事は、その「27 Letters」で使用したLINE botの作り方についての雑記です。
本記事は前後編に分かれています。
前編では、プログラミングによる謎解きbot制作のメリットデメリットと、LINEbotの基本となるエコーボットの実装に触れます。
後編では、謎解きbotらしい機能を実装していきます。

# 注意
:::note warn
この記事は**LINE謎解き「27 Letters」のネタバレをごくわずかに含みます**（大きなネタバレはありませんが……）。
:::

一期一会なコンテンツなので、まずはプレイしてみてください。
LINEのアカウントがあれば無料でできます。
謎解き初心者には難しいかもしれませんが、「プログラミングでできること」の一例としてはうってつけなのではと思います。

:::note warn
僕はPythonもといプログラミング初心者であるため、**「コードの作法」的なことはいまいちわかっていないことはご了承ください**。
:::

より簡潔、綺麗なコードが書ける人はぜひこの内容をアップデートする記事を書いてください。

# この記事はこんな人向け
- **高性能な**謎解きbotを作るためならプログラミングをいとわない人
- プログラミングやコマンドプロンプトのようなCUIに抵抗がない人
- （謎解きに限らず）PythonでLINE botを作りたいけどよくわからなかった人
- 「27 Letters」の裏側が気になる人
- 作りたいもののためならめげない人

# 動機
「27 Letters」は、「LINEbotを開発したい」という思いで始まったわけではなく、謎の画像や構成はとっくに出来上がっていて、それを一番楽しくプレイできる方法としてLINEbotを開発する、という流れでできました。
「27 Letters」は小謎[^1]が数十問と大謎[^2]が1問で構成される謎です。
通常の謎解きゲームでは「小謎を解かないと大謎には進めない」という構成が基本ですが、今回は大謎は最初から確認できる形に設定することにしました（その方が面白くなりそうなので）。
その結果として、「解いた小謎の数が0問の状態でクリアする」という凄まじいクリアルートが生まれます。
しかし、通常のLINEbotでは小謎の正解数を管理することはできません。
そのため、「0問クリア」という実績の解除をプレイヤー側がSNSで共有するとしたら、それは自己申告に委ねられています。
このような**小謎の正解数管理を自己申告に依存せずシステムとして提供したい**、という思いが始まりでした。

一応、最近しゑひさんという方がGoogleスプレッドシートを用いた謎解きLINE botの作りかたを共有されていました。

https://shiwehi.com/column/seisaku/022.php

小謎の問題数が少なければ、おそらくこの方法で正解数が管理できるかと思います。ですが、数十問となると、この方法での正解数の管理はおそらく不可能です。
そのため、PythonでLINEbotを作る方法を模索することとなりました。Pythonなのは単純に僕がプログラミング言語の中で一番使い慣れているからです（JavaScriptの方がリファレンスが充実しているので、使える人はそちらの方がいいかもしれません）。

また、LINE botを用いずブラウザゲーム化して公開するという方法もあります。ですがWebの場合、アカウント認証などを用いない限り、ブラウザバックで正解数を偽ることもできます。そのためLINE botということにしました。

## プログラミングによるbot開発のメリット
プログラミングによるbot開発のメリットは、**とにかく高機能にできる**ということです。
例えば上で述べたように、**小謎の正解数を管理する**という機能がつけられます。
他にも「27 Letters」では**タイムアタック機能**や**アンサーシート機能**（今まで解いた小謎の答えをまとめてくれる機能）を実装しました。
このような機能を手軽に実装できるのが、プログラミングによるLINE bot開発の強みです。
逆に**そこまでの機能が不要（友だち登録したときのメッセージとキーワードへの返信で十分）なのであれば、あえて難しいプログラミングを使う必要はありません**。↓のページ等を参考にしてLINEbotを作ってください。とても簡単に出来ます。

https://shiwehi.com/column/seisaku/007.php

## デメリット
プログラミングによるbot開発のデメリットは、**とにかく高難度である**ということです。
正直謎自体よりもbot開発の方に僕はめちゃくちゃ時間をかけました。
LINEの公式リファレンスは初心者には難しいですし、参考となる本やサイトも非常に少ないです（このQiitaの半分はそのことへの怒りでできています）。しかもPythonの場合ほとんどの文献が一番初歩的なエコーボットの作り方の解説で、そこから一歩先の解説はほとんどありません。
正直、僕はPythonは少し学んだことがあるとはいえ、最初は「Git？　Heroku？　SQL？　なにそれ？」の状態でした。いろいろな文献の断片をつなぎ合わせてようやく2週間でbotを作り上げました（逆に言えば初心者でもPythonを学んだことさえあれば2週間で可能です）。2週間もあきらめずにできたのは、ひとえに「この謎解きを一番良い状態で提供したい」という思いがあったからです。そういう思いがある人は、ぜひ手を出してみてほしいです。今はこのドキュメントもありますから。
もちろん、プログラミングができて、公式リファレンスがすらすら読める人にはそこまで難しくないと思うので、ぜひチャレンジしてみてください。

# 使ったもの
- Python 3.10.4
- LINE-bot-SDK
- Flask 2.1.2
- Git 2.36.0
- Heroku
- Heroku CLI 7.60.2
- Heroku Postgres
- psycopg2
また、Windows 10の64bitのパソコンを使用しています。

# まずはエコーボットを作る
いきなり完成品のbotを作ろうとするのは無茶です。まずは一番簡単なエコーボット（ユーザーのメッセージをそのまま返すbot）を実装しましょう。
一番初歩的ではありますが、正直これだけでもかなりつまずきポイントがあります。

## LINE Developersでチャネルを作る
まずはLINE Developersでチャネルを作ります。ここはある程度省略します。
あえて言うことがあるとするならば、通常のLINE謎で使う「応答メッセージ」「あいさつメッセージ」は使いません。Messagin API設定から **「応答メッセージ」「あいさつメッセージ」を無効にしてください**。
また、「Messaging API設定 > **チャネルアクセストークン**」と「チャネル基本設定 > **チャネルシークレット**」の2つが発行されているかを確認します。この2つはこのあと使います。なかったら発行してください。

## Herokuに登録
会員登録部分の説明は省略します。
会員登録したら、ホームにある「New > Create New App」から新しいアプリを作ります。App nameは自分の好きなように設定してください。Choose a regionでアメリカかヨーロッパの2択に迫られますが、アメリカでいいです。Add a pipelineは放置します。
### 環境変数を設定
アプリを作ったら、Webでアプリのページを開き、「Setting > Config Vars > Reveal Config Vars」から、新しい環境変数を2つ追加します。
| KEY | VALUE |
|:-:|:-:|
| YOUR_CHANNEL_ACCESS_TOKEN | （ここにチャンネルアクセストークンを入力） |
| YOUR_CHANNEL_SECRET | （ここにチャンネルシークレットを入力） |

### GitとHeroku CLIのインストール
これらがないとHerokuはほとんど操作できません。
Gitは、インストール後にWindowsの環境変数を設定しておきましょう（方法は[このあたり](https://proengineer.internous.co.jp/content/columnfeature/6893 "WindowsにGitをインストールする方法")を参照）。
これらをインストールしたら、コマンドプロンプトで```heroku login```と入力することでブラウザが開き、Herokuを操作できるようになります。
例えば、```Heroku logs --tail```と入力することで、Herokuの現在進行形のログが表示されます。この状態はCtrl+Cで解除されます。

#### GitとHerokuを連携する
ここでGitとHerokuの連携が必要になります。コマンドプロンプトに```heroku git:remote -a （Herokuアプリの名前）```と入力すれば連携できます。

## 環境構築
実行環境はHeroku上なので最悪何もなくてもできますが、flaskとline-bot-sdkは使うかもしれないのでインストールしておきましょう。```pip install flask```や```pip install line-bot-sdk```とコマンドプロンプトに打ち込めばインストールできます。

## ファイル作成
作業フォルダ（C>User>User Nameのところに置くとアクセスしやすい）に次の3つのファイルを置きます。
- **Procfile**：「Herokuでアプリケーションをデプロイした際に最初に実行されるコマンドを記述するためのファイル」[^3]らしいです。
- **requirement.txt**：Herokuでプログラムを動かすために必要なライブラリ一覧です。
- **main.py**：メインプログラム。これ**だけ**は好きな名前でいいです。


### Procfile
```Procfile
web: gunicorn main:app --log-file=-
```
この```main```は、```main.py```の```main```なので、.pyファイルの名前を適当に変えた人はその名前にしてください。

### requirements.txt
```requirements.txt
Flask
gunicorn
line-bot-sdk
```
他のほとんどの文献はここでバージョンも指定していましたね。指定したほうがいいと思いますが、指定しなくても動きます（たぶんバージョンアップで互換性なくなったら詰む）。

### main.py
正直いろんな文献[^3] [^4]のパクリです。
```main.py
from flask import Flask, request, abort
from linebot import LineBotApi, WebhookHandler
from linebot.exceptions import InvalidSignatureError
from linebot.models import MessageEvent, TextMessage, TextSendMessage
import os

app = Flask(__name__)

linebot_api = LineBotApi(os.environ["YOUR_CHANNEL_ACCESS_TOKEN"])
handler = WebhookHandler(os.environ["YOUR_CHANNEL_SECRET"])

@app.route("/callback", methods=['POST'])
def callback():
    signature = request.headers["X-Line-Signature"]
    body = request.get_data(as_text=True)

    try:
        handler.handle(body, signature)
    except InvalidSignatureError:
        abort(400)

    return 'OK'
  
@handler.add(MessageEvent, message=TextMessage)
def handle_message(event):       
    linebot_api.reply_message(event.reply_token, \
                             TextSendMessage(text=event.message.text))
  
if __name__ == '__main__':
    app.run()
```
#### main.pyの解説
```main.py
linebot_api = LineBotApi(os.environ["YOUR_CHANNEL_ACCESS_TOKEN"])
handler = WebhookHandler(os.environ["YOUR_CHANNEL_SECRET"])
```
ここでHerokuに先ほど設定した環境変数を参照します。なので、```YOUR_CHANNEL_ACCESS_TOKEN```や```YOUR_CHANNEL_SECRET```は**上で設定した名前になっていることを確認**してください。
一応、```os.environ["YOUR_CHANNEL_ACCESS_TOKEN"]```の代わりにここに直接チャンネルアクセストークンを入力するやり方もあります（```linebot_api = LineBotApi("（ここにチャンネルアクセストークンを入力）")```のように）。その場合Herokuの環境変数設定は不要になります。ただ、仮にソースコードが流出したときにも大丈夫なので、Herokuに環境変数を設定したほうがセキュリティ上良い気がします（違ったらごめん）。

```main.py
@app.route("/callback", methods=['POST'])
def callback():
    signature = request.headers["X-Line-Signature"]
    body = request.get_data(as_text=True)

    try:
        handler.handle(body, signature)
    except InvalidSignatureError:
        abort(400)

    return 'OK'
```
署名の確認です。よくわかんないですが、**LINE公式が「入れろ」と言っているので入れましょう**。

```main.py
@handler.add(MessageEvent, message=TextMessage)
def handle_message(event):       
    linebot_api.reply_message(event.reply_token, \
                             TextSendMessage(text=event.message.text))
```
ここが中心部です。```@handler.add```で、各イベントに対してどのように反応するかを定義します。今回は「文章のメッセージが来たら」なので、その引数は```(MessageEvent, message=TextMessage)```です。
```linebot_api.reply_message```は返信を返す指示です。```TextSendMessage```は「（画像などではなく）文章で返す」ということを示し、```event.message.text```は、ユーザーの送信したメッセージの文字列を示すので、この部分は「ユーザーの送信した文章そのまま返す」という意味になります。

## デプロイ
上で作ったファイルをHerokuにデプロイします。
ここの説明はある程度省略します。
要は、3つのファイルが格納されているディレクトリに入り、次のコマンドを次々に実行するということです。
```
git add .
git commit -m '適当なコメント'
git push heroku master
```
デプロイはrejectされることもあります。そんなときは、コードがおかしいとか、requirements.txtに欠陥があるとかでした。
これで、上で作った3ファイルがHerokuにアップロードされ、Heroku上で実行されることとなります。

## Webhookの設定
Line DevelopersのページでWebhookを設定します。「LINE Messaging API > Webhook設定 > Webhook URL」を、「```https://***.herokuapp.com（アプリのURL）/callback```」に設定します。ここで「/callback」の部分は、main.pyの@app.routeの最初の引数の部分です。文献によってこの部分が違ったりするので、一致しているかに注意。
検証して「成功」が出たら成功です。
あとその下の **「Webhookの利用」もオン**にしてください。

# H10エラー "App Crashed"
　とはいえ実際はこうもうまくいかないです。エコーボットが完成したと思ってデプロイしてbotにメッセージを送っても無反応だったりします。そういうとき```heroku logs```でログを確認するとこんな感じになっていたりします。

```
at=error code=H10 desc="App crashed" method=POST path="/callback"
```

このエラーについては、検索しても有効な解決策がなかなか出てきません。それもそのはずで、おそらく「プログラムがおかしい」という原因のエラーがここに集約されるからです。

## サーバをローカルサーバに変えてみる
今はherokuのサーバにアップロードする形でLINEbotを動かしていますが、ローカルに一時的なサーバを立ててそこからLINEbotを動かすこともできます。
やり方は意外と簡単でした。

① まず.pyファイルに下のように、はじめの方にngrokに関する記述を加えます。
```main.py
# 以上略
from flask_ngrok import run_with_ngrok

app = Flask(__name__)
run_with_ngrok(app)
 
# 以下略
```
② .pyファイルを実行します。実行すると、「```Running on https://***.ngrok.io```」みたいなのが出てきます。
③ LINE Messaging API設定のWebhook URLを「```https://***.ngrok.io/callback```」にします（http:// だったら、sをつける）。この「/callback」の部分は「@app.route」の最初の引数の部分です。
　これが失敗するということは、.pyファイルにおかしいところがあるということになるでしょう。逆にこれが成功すると、.pyファイルに誤りがなく、それ以外のところがおかしいということがわかり、精神的に楽になります。

## ログとコンソールを見てヒントを探す
しかし、これをherokuにデプロイすると急にうまくいかなくなってしまいました。
ここからは、```heroku restart```で再起動をかけた後LINEbotにメッセージを送って反応を見たり、```heroku logs --tail```や```herok run console```などを叩いてログを確かめたり、プログラムの途中で適当な変数を```print```したりして、ひたすらに原因を究明していくほかありません。
特に、**クラッシュする前のログにヒントがあることが多いです**。
このログを見て原因を探る動作は今後とても大事になるので、今のうちに慣れておきましょう。
結局僕の場合、Procfileに記述したアプリの名前を間違えるという凡ミスでした。

# 後編に続きます
ここではエコーボットの作り方にとどまりました。こうした基本が大事ですが、おそらく読者の関心は「謎解きbot」を作ることにあるでしょう。
後編では、いよいよ謎解きbotらしくなっていきますので、お楽しみに。
▼　後編はこちら

https://qiita.com/TomokiMitsuhama/items/bd9e318e600ed4428729

[^1]: 謎解きゲームにおいて、すぐに解ける問題のこと。アクションゲームでいう「ザコ敵」。
[^2]: 謎解きゲームにおいて、最後の方に立ちはだかる問題のこと。アクションゲームでいう「ラスボス」。
[^3]: ナカノヒトシ, 2019『Python + LINEで作る人工知能開発入門 ─Flask + LINE Messaging APIでの人工知能LINEボットの作り方─』
[^4]: Yoshiki Mogi, 2020『作りながら学ぶPython人工知能　超入門』