(2022/09/30追記)本記事で利用しているHerokuの無償プランは、2022年11月28日(月)に廃止されることとなりました。ご注意ください。
https://forest.watch.impress.co.jp/docs/news/1435422.html
1週間前、「27 Letters」というLINE公式アカウントを利用した謎解きゲームをリリースしました。
この記事は、その「27 Letters」で使用したLINE botの作り方についての雑記です。
本記事は前後編に分かれています。
前編では、プログラミングによる謎解きbot制作のメリットデメリットと、LINEbotの基本となるエコーボットの実装に触れます。
後編では、謎解きbotらしい機能を実装していきます。
注意
この記事はLINE謎解き「27 Letters」のネタバレをごくわずかに含みます(大きなネタバレはありませんが……)。
一期一会なコンテンツなので、まずはプレイしてみてください。
LINEのアカウントがあれば無料でできます。
謎解き初心者には難しいかもしれませんが、「プログラミングでできること」の一例としてはうってつけなのではと思います。
僕はPythonもといプログラミング初心者であるため、「コードの作法」的なことはいまいちわかっていないことはご了承ください。
より簡潔、綺麗なコードが書ける人はぜひこの内容をアップデートする記事を書いてください。
この記事はこんな人向け
- 高性能な謎解きbotを作るためならプログラミングをいとわない人
- プログラミングやコマンドプロンプトのようなCUIに抵抗がない人
- (謎解きに限らず)PythonでLINE botを作りたいけどよくわからなかった人
- 「27 Letters」の裏側が気になる人
- 作りたいもののためならめげない人
動機
「27 Letters」は、「LINEbotを開発したい」という思いで始まったわけではなく、謎の画像や構成はとっくに出来上がっていて、それを一番楽しくプレイできる方法としてLINEbotを開発する、という流れでできました。
「27 Letters」は小謎1が数十問と大謎2が1問で構成される謎です。
通常の謎解きゲームでは「小謎を解かないと大謎には進めない」という構成が基本ですが、今回は大謎は最初から確認できる形に設定することにしました(その方が面白くなりそうなので)。
その結果として、「解いた小謎の数が0問の状態でクリアする」という凄まじいクリアルートが生まれます。
しかし、通常のLINEbotでは小謎の正解数を管理することはできません。
そのため、「0問クリア」という実績の解除をプレイヤー側がSNSで共有するとしたら、それは自己申告に委ねられています。
このような小謎の正解数管理を自己申告に依存せずシステムとして提供したい、という思いが始まりでした。
一応、最近しゑひさんという方がGoogleスプレッドシートを用いた謎解きLINE botの作りかたを共有されていました。
小謎の問題数が少なければ、おそらくこの方法で正解数が管理できるかと思います。ですが、数十問となると、この方法での正解数の管理はおそらく不可能です。
そのため、PythonでLINEbotを作る方法を模索することとなりました。Pythonなのは単純に僕がプログラミング言語の中で一番使い慣れているからです(JavaScriptの方がリファレンスが充実しているので、使える人はそちらの方がいいかもしれません)。
また、LINE botを用いずブラウザゲーム化して公開するという方法もあります。ですがWebの場合、アカウント認証などを用いない限り、ブラウザバックで正解数を偽ることもできます。そのためLINE botということにしました。
プログラミングによるbot開発のメリット
プログラミングによるbot開発のメリットは、とにかく高機能にできるということです。
例えば上で述べたように、小謎の正解数を管理するという機能がつけられます。
他にも「27 Letters」ではタイムアタック機能やアンサーシート機能(今まで解いた小謎の答えをまとめてくれる機能)を実装しました。
このような機能を手軽に実装できるのが、プログラミングによるLINE bot開発の強みです。
逆にそこまでの機能が不要(友だち登録したときのメッセージとキーワードへの返信で十分)なのであれば、あえて難しいプログラミングを使う必要はありません。↓のページ等を参考にしてLINEbotを作ってください。とても簡単に出来ます。
デメリット
プログラミングによる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の環境変数を設定しておきましょう(方法はこのあたりを参照)。
これらをインストールしたら、コマンドプロンプトで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
web: gunicorn main:app --log-file=-
このmain
は、main.py
のmain
なので、.pyファイルの名前を適当に変えた人はその名前にしてください。
requirements.txt
Flask
gunicorn
line-bot-sdk
他のほとんどの文献はここでバージョンも指定していましたね。指定したほうがいいと思いますが、指定しなくても動きます(たぶんバージョンアップで互換性なくなったら詰む)。
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の解説
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に環境変数を設定したほうがセキュリティ上良い気がします(違ったらごめん)。
@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公式が「入れろ」と言っているので入れましょう。
@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に関する記述を加えます。
# 以上略
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らしくなっていきますので、お楽しみに。
▼ 後編はこちら