今シンガポールにいますLineBotを作成し、記憶に残る仕事をしたい物語

# 「ごめん、同級会にはいけません」

強烈なインパクトを持つこのCM。
これが大好きなので、同級会に誘うと
「**今、シンガポールにいます**」と
返事を返してくれる**LineBot**を作ってしまいました。
さらに、**ドヤァ感**をよりいっそう高める仕様をいろいろモリこみ、
地図には残らなくても、使った人の**記憶に残る仕事**にしたいと思います！

[クソアプリ Advent Calendar 2019](https://qiita.com/advent-calendar/2019/kuso-app) の４日目です。
~~と、書くまでもなくタイトルから漂うクソアプリ感~~


### 使い方：
　① **同級会を開く**
　② **おもむろにLineを立ち上げ「綾乃、いまどこ？」と聞く**
　③ **「ごめん、同級会にはいけません～～～以下略」と返信が来る**
　④ **「え、シンガポールだって」という感じでみんなでのぞきこむ**

### 実行した時の様子：
![キャプチャ1.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/193512/54a95d4d-01e6-5921-4654-b8f6fbab5b16.png)
※親切に、シンガポールの地図を示してくれる（地図に残る仕事）
　他にも形態素解析などの~~無駄な~~機能を満載。
　LineBot作成のノウハウもいろいろ、後述します。

### お手元での実行は、こちらから友達登録をどうぞ
<a href="https://lin.ee/rdjOev6"><img src="https://scdn.line-apps.com/n/line_add_friends/btn/ja.png" alt="友だち追加" height="36" border="0"></a>
![QR_min.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/193512/4c41aa31-ba4d-3b63-cbbb-725a5c19a246.png)

# 「今、シンガポールにいます」

まだ見たことが無い人、CM本体の閲覧は以下よりどうぞ。

[![大成建設CM：「シンガポール」篇(youtube)](https://img.youtube.com/vi/HQYQ3Me2KLw/0.jpg)](https://www.youtube.com/watch?v=HQYQ3Me2KLw)
https://www.youtube.com/watch?v=HQYQ3Me2KLw


# 「地図に残る仕事」
さまざまな~~余計な~~機能を実装しています。
まず最初は「**地図に残る仕事**」です。

**シンガポール！！**をより強くアピールするため、
親切にもシンガポールの位置を示す地図を示すようにしました。
これで**地図に残る感**もバッチリです☆

さらに、国際派のAYANOは
**２００を超える国や地域を飛び回ります（スゴイ）**
定型文以外で問いかけた場合、
彼女はシンガポールに居るとは限りません。
さまざまな場所からの返事が聞けるでしょう！
![キャプチャ1-2.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/193512/a88f8a9e-189a-9e4c-e68e-3672f4ac2175.png)


見慣れない国名でも場所が分かるため、
**世界地理のお勉強にもなるアプリですね！**
~~クソアプリではなかったかもしれません。~~


# 飲み会、送別会、など、様々なお誘いに対応
おっと、よく見ると上の画面キャプチャでは
「クラス会」に誘っているようです。
**誘った会の内容に合わせた即レス**をしてくれます！

「**合コン**」でも「**桜を見る会**」でもなんでも誘ってみてください。


# ラピュタの作成（~~地図に残らない~~）

AYANOは地下鉄以外のものも作っています。
![キャプチャ2-1.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/193512/65b4fd4f-8e5b-dc07-6fe6-17d7b3770268.png)

ラピュタ以外にも様々なものを作っていますよ！
他のいろいろな作成物は、ぜひご自身でお確かめくださいませ。
~~誰かの青春を乗せる ＝ シータ＆パズー。君を乗せて。~~


# あなたのキーワードに反応する機能（形態素解析）

二次会などの、お誘い内容に応じた回答は行うものの、
ここまでの機能では「ランダムメッセージ表示感」があり、
もしかしたら、一緒にのぞきこんだ友人に
「**あれ、このAYANOってBotじゃね？**」
とバレてしまうかもしれません。

そこで、あなたの投げた会話のキーワードに反応して、
なんか**意味深な言葉を返す機能**を追加しました。（赤枠部）
![キャプチャ_ラーメン.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/193512/76f015bc-424f-ead4-f245-19d66b9283aa.png)
~~意識高い系の会話。ラーメンが懐かしい模様。~~

AYANOはもちろん日本語を理解出来るので、
**会話の中にあがってきた「重要単語」を認識することが出来ます。**
今回は、「名詞/一般」を重要だと認識していて、
それが無い場合は、「地図に残る仕事」と返信します。
上の例だと、「ラーメン」を認識しているようです。
**これでAYANOさんにリアリティが生じ、記憶に残りますね！**

形態素解析自体はもはや凄くも何ともない技術です。
とはいえGAE（サーバレス/AWSのLambdaのようなもの）に組み込む実装は
あまり見慣れない（少し実現が難しい）のがポイントです。
**外部APIコールも無く、爆速です。超即レス☆**

**これで、同級会や飲み会など様々な場で、**
**世界中のAYANOと会話が出来るようになりました☆**

**仲間と一緒に使うと、生暖かい白い目で見られ、**
**「記憶に残る仕事」間違いなしです☆**

ということで、内容の紹介はここまでにして、
実装上の工夫ポイントや、コード、
LineBot作成の様々なノウハウを以降に書きます。
＃長いです。

# 実装上のポイント：目次/まとめ

1. 全体方式のポイント
    1. 全体のベースとなる参考資料
    1. LineBotはHTTPSが前提。GAEで作ろう
    1. LineBotのReplyは無料。Pushは有料。Replyのみで作ろう
1. Line Messaging API の裏ワザ
    1. 「アクセストークン」を永続的に使う裏ワザ
    1. 「接続確認」を騙す裏ワザ
    1. 「地図表示」をするちょっと複雑な裏ワザ
    1.  「wait」を実現出来なかった話 
1. GAE(Google App Engine)の制約からの逃げ方
    1. 自然言語処理は重すぎて、普通は無料では動かせない話
    1. ローカルでは動くけど、GAEでは動かない時の話 

# 1. 全体方式のポイント

## 1-1. 全体のベースとなる参考資料

まず、LineBotってそもそもどんな感じに作るの？
ということで、下記のページをご参照ください。
（Lineの中の人の開発ブログの記事です。）

[イメージマップメッセージを使って終電に乗り遅れないボットを作りました](https://engineering.linecorp.com/ja/blog/imezimatsupumetsuseziwoshi-tsutezhong-dian-nicheng-richi-renaibotsutowozuo-rimashita/)

ベースとなる構成はこの記事と同様に作ります。
**Python  + Flask** （ただしPythonのバージョンは、3.6⇒3.7に変更）
そして、地図画像も記事と同様に**Google Static Maps API** を使います。

まずは「オウム返しボット」をデプロイするのが良いでしょう。
しかし、**このコードをただ書いて実行するではLineBotになりません**。
どのように動かせば良いのでしょうか？


## 1-2. LineBotはHTTPSが前提。GAEで作ろう

LineBotを作る系の情報のほとんどは、**Heroku**にデプロイしているようです。
なぜHerokuを使うかというと、**HTTPSを簡単に使えるため**、です。

LineBotを作成するためには、まず[LINE Developers](https://developers.line.biz/ja/)にログインします。
（自身のLineアカウントなどがそのまま使えます）

ログイン後「Webhook URL」を指定する箇所があり、
そのURLの指定には、**HTTPSしか指定することが出来ません**。
また、コード内で送信用の画像ファイルを指定する際などにも、
 LineMessagingAPIでは**HTTPS-URLで指定することが必須**です。
（ローカルファイルを送ることも出来ません。必ずURLで指定します）

つまり、**LineBotの開発には、HTTPSサーバの作成がほぼ必須**となります。

ここで大きく三つの選択肢があります。
　①**自作サーバを立てて、HTTPS化**する
　②**サーバレス/PaaSサービスを使う**（Heroku、GAE、Lambdaなど）
　③**Twilio**等の専用サービスを使い、サーバ部分を担ってもらう

簡単な応答botなら③が一番オススメです。
今回は自分で多少複雑なコードを書くため、①か②ですね。
①も無料で実現可能とはいえ、サーバ準備/運用が少々手間です。
そこで②が最適な選択肢となります。

恐らくLine公式で例にされていることと、上記の理由から、
ほとんどのLineBotの作り方紹介情報が
Heroku前提になっているようです。
が、**本質的にはHTTPSが簡単に実現出来れば何でもいい**ので、
Herokuに依存する要素は一切ありません。

今回はちょっと趣向を変えて、
**GAE（GoogleAppEngine）**を使ってみたいと思います。
Heroku向けの記事のコードもほぼそのまま動きます。
＃GAEのPython3ランライムはPython 3.7のみなのでその点は要変更です

**GAE（スタンダード環境）には1 日あたり 28 時間の常時無料枠があります。**
（2019年11月現在）
おいおい、１日＝24時間だろうが、って思いますよね？
負荷状況に応じて複数のインスタンスが同時に立ち上がる場合や、
性能的により良いインスタンスを立ち上げた場合、
その倍数分時間がカウントされる仕組みです。
つまり、最小構成のインスタンス１台で処理できる負荷なら、
ピーク時に多少２台になっていることがあったとしても、
常時無料の枠で収まる、というイメージ。

＃常時無料＝GCPで初回１年間で使える３万円分の無料クーポンと別に、
　１年後以降でもずっと無料で使える利用範囲のこと。
　例えば「[Qiitaの殿堂](https://qiita.com/youwht/items/9851c2ac9024633fc04e)」では、IaaSにおけるこの常時無料枠を利用している。

GAEのPython3.7のFlaskチュートリアルを実行して
（`git clone`して`gcloud app deploy`するだけ、すぐ終わります）
`main.py`を参考元のコードに変えればそれでほぼLineBotの完成です。
（あと、`requirements.txt`に、`line-bot-sdk==1.14.0`を追記）

## 1-3. LineBotのReplyは無料。Pushは有料。Replyのみで作ろう

個人開発におけるLineMessagingAPIの使い方の最大のコツは、
**Replyでサービスを設計すること**だと思われます。

LineMessagingAPIは、Push型のメッセージ送信には従量制限があります。
無料枠では、月に1000通しか送信できません。
課金をしても、一定数を超えると一通3円~5円程度で課金されてしまいます。
（詳細は公式サイト/最新情報等をご確認ください）

しかしなんと、**Replyなら無料で使えます**。

サービスの全体像の最初のデザインとして、
**「ユーザからの発話に返信することが自然となるようなサービスデザイン」**
にして、Push型の送信に依存しないような形が望ましいでしょう。

ただし、Replyはユーザの発話後一定時間のみしか使えず、
１発話に対し１回返信のみ＆１回に吹き出し５つ分のみ、
しか使えないことにも注意が必要です。

例えば、「**素数を数えて落ち着くアプリ**」を作ろうと思った場合、
３秒ごとに１３、１７、１９・・・などと一方的にbotが話すようなものは、
Push型になってしまいます。
次は？次は？とユーザに問いかけさせてそのたびに
２３、２９、などと答えさせることが自然となるように、
サービス全体のデザインを考えた方が良い、というわけです。

今回のAYANOは、「同級会に行こう！」との
誘いに対してReplyすることが極自然であるように、
サービスデザインの中におさまっていますね！

# 2. Line Messaging API の裏ワザ

## 2-1. 「アクセストークン」を永続的に使う裏ワザ

参考元記事の以下の記載の場所、
「YOUR_CHANNEL_ACCESS_TOKEN」には、

```py
line_bot_api = LineBotApi('YOUR_CHANNEL_ACCESS_TOKEN')
```

LINE Developers上で、無料/何回でも発行可能な
「チャネルアクセストークン（ロングターム） 」を設定します。
「Messaging API設定」の最下部にあります。

しかし、このアクセストークンは
発行時に有効期限を設定する必要があり、
「現在のチャネルアクセストークンが無効になるまでの時間」の
**プルダウンで、最大２４時間までしか設定できません**。

通常は２４時間で設定しておき、２３時間ごとくらいのペースで
有効期限延長/書き換え的な処理を作る必要があるのですが、
ここで（書いて良いのか迷うレベルの）**本当の裏技**があります。

なんと**時間＝０で設定すると永続的に有効なトークンになる**のです。
（2019年11月現在）

**おそろしく速い~~手刀~~有効時間、オレでなきゃ見逃しちゃうね**
とでも言う気持ちで、ありがたく使わせていただきましょう。
（このセリフは完全に死亡フラグ）

Lineのバグではなく、どうもアクセストークンの有効期限は、
当初は無期限だったようです。時間制限をつけるように変更中で、
その変更の過渡期？反対も多く変更に時間を要している？ようで、
そのために、基本は有効時間を設定してね、
知っている人は無限で使えちゃうけど、という状況のようです。
（ウワサ＆邪推を含む。真偽不明）

このトークンの期限更新自動化の一番良い方法が分からず、
（サーバレスは基本はステートレスで作りたいし）
コイツの期限があるだけでLineBot開発はやめようか、
と思うくらいクリティカルなポイントだったため、
**このまま無期限で使わせて欲しい、と切に願います**。

## 2-2. 「接続確認」を騙す裏ワザ

こっちは普通にLINE Developersさんの、
半分バグ的な内容かな、という話です。(2019年11月現在)

前述の通りLINE Developesの「Webhook URL」のところに、
HTTPSのURLとして、GAEのURLを記載することになります。
が「接続確認」のボタンを押下しても、OKになりません。

そこで、元のコードのhandle定義のところに、下記のように
特殊なreply_tokenでの処理分岐を追記します。

```py
#以下がhandle定義
@handler.add(MessageEvent, message=TextMessage)
def handle_message(event):
    #Lineの「接続確認」をOKにするための特殊コード
    #LineのDeveloperコンソール上で「接続確認」を押下した場合に、
    #普通に作ると、reply_tokenの不正のため、500リターンになる。
    #接続確認側で期待している200が返ってこないと怒られる。
    #2019年11月時点でのLineのコンソール上の問題。おそらくいずれ解決する。
    if event.reply_token == "00000000000000000000000000000000":
        return
    #以下省略
```

「接続確認」が通らないと動かない、ということはありません。
ただ、やっぱり気分の問題で、接続確認＝OKにしておきたいですよね。
詳しくは、下記の素晴らしい先駆者様のQiita記事をご参照ください。

[LINE DevelopesのWebhook URLの接続確認でエラーが出る件について](https://qiita.com/q_masa/items/c9db3e8396fb62cc64ed)

## 2-3. 「地図表示」をするちょっと複雑な裏ワザ

今回の開発の中で、最も分かりにくい処理が、
地図表示を行う処理、すなわち、
**Google Static Maps API** の結果をLineに送る処理です。

まず、Google Static Maps APIとは、
Googleの提供している地図APIで、
GoogleMAPを静的画像にしたモノを
単純なパラメータ指定で得ることが出来ます。

凄いのは、緯度経度による指定だけでなく、
シンガポール、とか、横浜、とか
地名で指定して座標を返すことにも対応している点です。
AYANOが飛び回っている世界の国と地域の一覧は、
単純なLIST型で情報を持っています。
そのLISTにGPS座標を用意しておかなくても、
Google Static Maps APIに国名だけ投げて、
座標を教えてもらっているわけですね。楽ちん。
その他細かいオプションは適宜ググってみてください。

で、**そのGoogle Static Maps APIの画像をLineに送るには？**

LineMessagingAPIには、画像送信用のAPIがありまして、
送信用の画像は、httpsのURLで指定してね、
とリファレンスに書いてあります。

そして、Google Static Maps API は、
HTTPSで静的地図画像にアクセスします。

**Google Static Maps APIのURLを**
**LineMessagingAPIの引数に指定すればいいじゃん！**
**⇒ハズレ。**

Google Static Maps APIのURLはHTTPSです。
でも、画像ファイルがそのパスに直接「存在する」わけではなく、
Googleの中で計算されて表示されているため、
**Line側から見ると、画像じゃなくね？と見えるようで、**
**この方法は上手くいかないようです。**
（※上手くいかない正確な理由は中の人に聞かないと分かりません。
　たぶん、APIキーもURLのパラメータに含んでいるために、
 　その認証とかがあって無理なんだろうと思いました）

そこで、Google Static Maps APIで作った画像だけを、
自作のサーバで（今回はGAE上で）疑似的に表示するような
コードを用意しておく必要があります。
後述のコード部の、この部分です。

```py
@app.route("/imagemap/<path:url>/<size>")
def imagemap(url, size):
#以下略
```

参考元のベース記事でも、同じことを実施しています。
初見でコードの意図が良く分からなかったので、解説を追加してみました。

さらに任意の日本語指定地点を表示出来るように処理を加減した結果、
ポイントとなる部分だけ抜粋＆コメント付与すると、
以下のようになります。

```py
#■GoogleMap関連の設定
#GoogleのStaticMapsAPIのキーを記入する。
google_staticmaps_api_key="YOUR GOOGLE STATICMAPS API KEY"
#Googleと送信時に使う大きさ指定（※Googles側では最大640*640まで）
IMAGE_SIZE = 640

#markers=国名を入れて自動的に検索表示する
#https://maps.googleapis.com/maps/api/staticmap?markers=color:blue|%22%E3%82%B7%E3%83%B3%E3%82%AC%E3%83%9D%E3%83%BC%E3%83%AB%22&size=300x300&zoom=3&language=jp&key=YOUR GOOGLE STATICMAPS API KEY

#input_basyoには日本語で入るため、URLエンコードを実施してから返す
def makeMapUlr(input_basyo):
    map_image_url = 'https://maps.googleapis.com/maps/api/staticmap?markers=color:{}|{}&center={}&zoom=3&language=jp&size={}x{}&key={}'.format('blue', urllib.parse.quote(input_basyo), urllib.parse.quote(input_basyo),IMAGE_SIZE, IMAGE_SIZE, google_staticmaps_api_key)
    return map_image_url

#Lineの地図表示用のメッセージに加工する関数
def makeImagemapSendMessage(map_image_url):
    #Flaskのホスト名が入る模様：
    request_host_name = request.host
    print(request_host_name)
    
    base_url = 'https://{}/imagemap/{}'.format(request_host_name, urllib.parse.quote_plus(map_image_url))
    print(base_url)
    message = ImagemapSendMessage(
        base_url = base_url,
        alt_text = '世界地図',
        base_size = BaseSize(height=IMAGE_SIZE, width=IMAGE_SIZE),
    )
    return message
    
#受け付けたリクエストを元にGoogleから取得した画像を返す処理を行う。
@app.route("/imagemap/<path:url>/<size>")
def imagemap(url, size):
    print("imagemap-get-called")
    #デバッグ用
    print(url)
    print(size)
    map_image_url = urllib.parse.unquote(url)
    response = requests.get(map_image_url)
    img = Image.open(BytesIO(response.content))
    img_resize = img.resize((int(size), int(size)))
    byte_io = BytesIO()
    img_resize.save(byte_io, 'PNG')
    byte_io.seek(0)
    return send_file(byte_io, mimetype='image/png')

#　上記までのコードを使い、
#　line_bot_api.reply_message
#　の引数の、reply_messages相当を作って渡せば良い
#   （makeImagemapSendMessageのreturn値がそれ）
#
#    line_bot_api.reply_message(
#        event.reply_token,
#        reply_messages
#    )

```

**画像の指定方法はHTTPSのURLで指定、**
**でもGoogleStaticMapsのURLをただ入れるだけではダメ。**
これ、**超ハマりどころだと思います**。

## 2-4. 「wait」を実現出来なかった話

さて、今回AYANOさんは、
**同級会の誘いがあることを待っていたように、**
**そしてシンガポールの地図まで用意して、**
**超即レスでババっと返信を返してきます。**
そこまで同級会の誘いに**食いつき過ぎずに**、
１０秒置きなどで自然に返すことは出来ないのでしょうか？

結論としては、出来ませんでした。（分かりませんでした）

Replyを、waitかけて２回以上実施すれば？
⇒　reply_tokenは１回しか使えないようでダメ

LineMessagingAPIに、間隔やwaitを指定出来そう？
⇒ [リファレンス](https://developers.line.biz/ja/reference/messaging-api/#wh-text)を見ても無さそう

さらに、もしwaitをかけてしまうと、
GAE：サーバレスと構造上相性が悪い気がします。
（課金額が大幅に上がるようなことは無いと思います。気分の問題）

一応最終手段としては、最初はReplyして以降はwait後にPushで返せば、
実現不可能ではないです。前述の通りPushはお高いので却下。

今回は、**めっちゃ同級会の誘いを待っていたAYANOさん**
という設定で許してください。
**超即レスで、シンガポールをアピールします**。



# 3. GAE(Google App Engine)の制約からの逃げ方

## 3-1. 自然言語処理は重すぎて、普通は無料では動かせない話

自然言語処理×Python というと、Mecab,Janome、
また、Word2VecでGensimなどのライブラリをよく使っていました。

チャットボットを作るということで、
これらのライブラリ/機能も使いたいなー、と思います。

が、ここで大きな壁が立ちはだかります。
**GAEではインストールが軽量に済むライブラリしか使えない（説明雑）**

Mecabは最初から諦めていたものの、JanomeやGensimについても、
インスタンスのメモリサイズを最大に調整しても、
メモリオーバーによりGAEで使うことは出来ませんでした。
多少の工夫をした程度では、無料では全く見込み無いでしょう。

**そこで、GAEの最小インスタンスでも使える形態素解析ツールとして、**
**「[igo-python](https://pypi.org/project/igo-python/)」を使ってみました。**
軽量でサクサク動いて素晴らしいです。

使い方はMecabやJanomeと同じで、下記のコードの通りです。
ただし今回は辞書等も拡張していないし、
形態素解析的な精度はかなり低い点、留意が必要です。

```py:igo-pythonの使い方

from igo.Tagger import Tagger

#形態素解析：igo-pythonの初期化
t = Tagger()

#形態素解析結果を文字列で返す関数
def extract_str(input_str):
    parsed_list = t.parse(input_str)
    result_str = ""
    for m in parsed_list:
        result_str += m.surface + " / " + m.feature + " \n"
    return result_str


#名詞-一般のみを抽出してリストにして返す関数
def meisi_tyuusyutu(input_str):
    result_list = []
    parsed_list = t.parse(input_str)
    for m in parsed_list:
        feature_list = m.feature.split(',')
        #名詞-一般、助詞-連体化、助詞-係助詞などのようになる。
        hinsi_info = feature_list[0] + "-" + feature_list[1]
        if hinsi_info == "名詞-一般":
            result_list.append(m.surface)
    return result_list

```

GAEにデプロイする時の設定方法、
「requirements.txt」 の書き方は以下の通りです。

```
Flask==1.1.1
line-bot-sdk==1.14.0
igo-python==1.0.0
```

ということで、
**GAEの最小構成のままで形態素解析まで実現出来ました！**

今回は一旦精度度外視でigo-python利用としました。
もし、LineBot内で自然言語処理＆精度を求めるならば、
　案① 自作HTTPSサーバを立てて自前で組み込む
　案② GAEやHerokuでやるならば[COTOHA](https://qiita.com/tags/cotoha)を使う
などが正しい進め方だと思われます。

また、Word2Vec/Gensim相当をサーバレスで動かす実装については、
道半ばなので見送ります。いつか別な機会に。
（簡単なものを動かすところまでは、驚きの方法で実現。
　modelファイル自体が重いので精度とのバランスが・・・。）


## 3-2. ローカルでは動くけど、GAEでは動かない時の話 

さて、上述の自然言語処理組み込みの実験のように、
GAEのメモリ依存で処理が落ちる場合、

**ローカル開発時は動いているけど、**
**サーバにアップすると動かない**

というケースが多発します。

また、LineBotはHTTPSサーバが必須、これも、
ローカル開発環境ではテストしにくい観点ですので、
LineMessagingAPI～GoogleStaticMapsAPIの連携近辺においては、
**GAEへのデプロイ後しか確認出来ない処理**が多発しました。

これらの場合、AYANOにチャットを送ると、
**既読無視**、を連発してきます。
デプロイのたびに既読無視されると**心を折られます**。

これについては、あまり良い解決方法を見つけられていません。

一応、多少マシになった方法としては、
　①print等標準出力のログは、
　　GCP内の「Logging(Stackdriver)」で集約して見れるので、
　　デバック時は適当な場所にprintを書いておく
　②Line関連の問題と、それ以外の問題とを切り分けられるように、
　　Lineを経由しなくても、サーバーへのGETアクセスから、
　　作成した関数を叩けるように作っておく
という２点でしょうか。

下記がFlask-LineBotの主要処理です。
①②に相当する箇所に★付けでコメント記載しました。

```py:

#LineのWebHookはPOST型であり、通常のGETでのURLアクセスの場合は使われない
#以下はサーバの疎通確認用
#★このように、LineのWebHook以外で確認出来る場所を作っておき、
#  これらの場所で、確認したい関数を呼び出せば、
#  Line関連との問題の切り分けが可能。
@app.route("/", methods=['GET'])
def sayhello_root():
    """Return a friendly HTTP greeting."""
    return '[200] It works!'

#LineのWebHookに設定し、メッセージの受け取り＋リプライをする箇所
#ボットでメッセージを送付する場合、LineのDeveloperコンソール上で、
#「Webhook URL ※SSLのみ対応」の所に記載したURL宛に、POSTでメッセージが送付される。
#参考：https://www.casleyconsulting.co.jp/blog/engineer/3028/
@app.route("/", methods=['POST'])
def callback():
    # リクエストヘッダーから署名検証のための値を取得
    signature = request.headers['X-Line-Signature']
    # リクエストボディを取得
    body = request.get_data(as_text=True)
    app.logger.info("Request body: " + body)

    # 署名を検証し、問題なければhandleに定義されている関数を呼び出す。
    try:
        handler.handle(body, signature)
    # 署名検証で失敗した場合、例外を出す。
    except InvalidSignatureError:
        abort(400)
    return 'OK'

#以下がhandle定義
@handler.add(MessageEvent, message=TextMessage)
def handle_message(event):
    #Lineの「接続確認」をOKにするための特殊コード
    #LineのDeveloperコンソール上で「接続確認」を押下した場合に、
    #普通に作ると、reply_tokenの不正のため、500リターンになる。
    #接続確認側で期待している200が返ってこないと怒られる。
    #2019年11月時点でのLineのコンソール上の問題。おそらくいずれ解決する。
    #参考：https://qiita.com/q_masa/items/c9db3e8396fb62cc64ed
    if event.reply_token == "00000000000000000000000000000000":
        return
    
    #イベント内のメッセージを取得する
    input_text = event.message.text
    #★標準出力は、GCP内の「Logging」に集約表示されるので、
    #  要所でprint出力をしておくとデバッグしやすい
    print(input_text)
    
    #メッセージを解析し、返信用の「lineのmessages」を作成する
    #★Webhookやhandlerに全処理を書かずに、
    #  主要処理は外部の関数で作成しておき、
    #  Webhookやhandlerを通さずとも、
    #  直接呼び出し＆確認が出来るように作る
    reply_messages = makeLineMessages(input_text)

    #reply_messageは、原則無料だが、
    #reply_tokenは有効期間があり、あまり長時間の処理は実施出来ない。
    #下記が基本形：
    #line_bot_api.reply_message(
    #    event.reply_token,
    #    [
    #        TextSendMessage(text= reply_text),
    #    ]
    #)

    #replyして終了。
    line_bot_api.reply_message(
        event.reply_token,
        reply_messages
    )


#FlaskをGAE上（python3.7）で動かすためのサンプル：
#「gae_python37_app」をベースに作成
if __name__ == '__main__':
    # This is used when running locally only. When deploying to Google App
    # Engine, a webserver process such as Gunicorn will serve the app. This
    # can be configured by adding an `entrypoint` to app.yaml.
    app.run(host='127.0.0.1', port=8080, debug=True)

```

以上で、技術的な話題は終了です。

# **あとがき（ポエム）**

## AYANOLineBot爆誕の背景

過日、とある活動(業務外)のご縁で、Line社を見学する機会をいただきました。
とても親切にご対応/ご紹介いただきまして、大変楽しいひと時でした。
この場をかりて、再度お礼申し上げます。

その際に、LineBotに関する技術的なQAにも、詳しく答えていただきました。
本記事は、そのQAで得た知見/ノウハウの総まとめ的な記事です。
（Qiitaなどに書いて良いよ~~(むしろ書くことを推奨)~~、とのお話だったため）
**LineBot開発は初めて！という時にとても悩む要点をまとめたつもりです**。

でもまさかQAの結果が「ごめん、同級会には行けませんbot」になるとは
全く思わなかったでしょう！~~ほんとうに「ごめん」です。~~

## LineBot作成の感想

LineBotもGAEも今回初めてまともに触りました。

LineBotの作成においては「Line(bot)で返信しなければならない理由」を
どうデザインするのか？が最大のポイントと感じています。
LIFF（LINE Front-end Framework）という、
Line内にウェブアプリを組み込める機能もあるとはいえ、
Lineでもアプリを実現出来る、ではなく、Line内での実現が必須、な
デザインにしていくべきでしょう。
最もシンプルにそのデザインを達成するのは「**キャラクター**」です。
今回は、AYANOの存在感を増すために、Lineでの実現が必須でした。

LineBotの作り方を解説するページは多くあれど、このへんの、
**なぜLineBotとして作るべきか？どうBotをデザインするべきか？**は
あまり語られていない印象があります。

例えば他に考えられるのは「**グループチャットや友達同士での対話目的**」です。
仮に「オセロ(リバーシ)のアプリ」を作る場合、
Webアプリやスマホアプリでは、友達同士での対戦がちょっと実装が大変そう。
スケジュールの共有アプリなども同様です。
Line/LIFF上で実装することによって、グループチャットにbotを呼べば、
このような課題が解決出来そうです。
AIとの対戦や、知らない人とのマッチング対戦機能を作りたいなら
Webアプリやスマホアプリで作り、友人との対戦目的ならLine上で、
など、最初にデザインを考えた方が良いでしょう。
他にも、適宜人が介入するサポート目的でbotを作る、なども方針の一つです。

こうしたLineBotの**「特性」**を意識して考えると、
次のアイデアが出しやすい気がしますね！！

## GAE利用/自然言語処理の感想

GAE（サーバレス）で自然言語処理を扱うのは、
正直予想以上に大変でした。
自然言語処理はもともと「辞書」や「単語ごとデータ」等が必要になるので、
どこで実装するにしても「重い」ことが問題です。

例えば以前下記の記事で、思い切って
フロントエンド（JavaScript/PWA）側で実装してみたこともありました。
https://qiita.com/youwht/items/6c7712bfc7fd088223a2

フロント側に寄せると処理は最速になりましたが、
パッケージに形態素解析ライブラリが同梱されるため、
初回起動時の配布ファイルサイズが少々重いことを
課題として感じました。（あと精度もいまいち）

GAE（サーバレス）においても、結局は重さによって、
まともな精度×処理内容を組めていません。

GAE自体は、軽い処理を作る際には
インフラ不要でPythonコードだけでHTTPSサーバになり、
かなり便利です。ただやはりクセがあり、
Pythonで出来ること何でも出来る、という感じではないため、
使う際には留意する必要があるでしょう。

今回は、自然言語処理は重いとはいえ、
このレベルまでならGAE（サーバレス）で出来るのね、
という感覚をつかめたのは良かったと思います！

＃つぎは COTOHA API で作るべきかな


## 友達が残らない仕事

**「今、シンガポールにいます。」**
**と、あらゆるお誘いを断っていたら、**
**Line友達がAYANOだけになってしまいました。**

**本当は、AYANOはbotだと分かっているけど、**
**でも今はもう少しだけ、知らないふりをします。**

**私の作るクソアプリも、**
**きっといつか誰かの孤独を癒やすから**

シンガポールからは以上です。