グループトークに招待したLINEBOTにお帰りいただく処理

#はじめに
昨日あたりに、LINE@からLINE Official Account Managerに切り替わったので戸惑い。
確かに、統合するって話題は見かけてたけど・・・。

グループトークにLINEBOTを招待できる。
しかし、勝手には退出してくれないし、間違えて招待した場合とかずっと残り続けるのも気持ち悪い。
というか、オウム返しBOTをグループトークに召喚した日にはテロもいいところ。
ということで、グループトークの参加を想定したLINEBOTには、この機能絶対必要じゃんとか思った。

グループトークと書いているが、複数人チャット（ルーム）も対象としている。

#前提
* Windows
* Python
* Heroku
* オウム返しBOTの作成に成功している。
    * この記事では[line-bot-sdk-python](https://github.com/line/line-bot-sdk-python)のREADME.rst内のソースをもとにしている。

正しくオウム返しされるBOTを変更する想定で書いていきます。
* 変更前にオウム返しができていること
* バックアップを取っていること（任意）
を確認してください。

#この処理を通して学んだこと
* sourceクラス（グループIDとかルームIDとかを取得できるクラス）
* 属性の存在チェック（存在しない属性にアクセスしようとするとその瞬間クラッシュする）

Pythonってクラスって呼んでいいのかな。

#実装
##BOTの設定変更
LINE Developersからプロバイダー選択＞チャネル選択＞チャネル基本設定の中にある
「Botのグループトーク参加」を利用するに変更する。
![setting1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/401158/6b4f8354-82b0-da1b-4151-311d3fb53975.jpeg)

設定はこちらとあるので、押下すると、LINE Official Account Managerに繋がりそこで変更できる。
![WS000053.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/401158/f74696ea-64b8-14ea-298a-9bb04db14c42.jpeg)

##モジュールの追加
人によってはもうあるかもしれないけど、LINEBOTのモジュールを全部追加しているか確認してほしい。
[line-bot-sdk-python](https://github.com/line/line-bot-sdk-python)
linebot配下をディレクトリごと、main.pyのあるディレクトリにおいておけばよい。

##コードの追加

```python:main.py(変更前)
@handler.add(MessageEvent, message=TextMessage)
def handle_message(event):
    line_bot_api.reply_message(
        event.reply_token,
        TextSendMessage(text=event.message.text))
```
```python:main.py(変更後)
@handler.add(MessageEvent, message=TextMessage)
def handle_message(event):

    # 退出処理
    if event.message.text == "帰って":
        line_bot_api.reply_message(event.reply_token, TextSendMessage("うっうっ"))

        #グループトークからの退出処理
        if hasattr(event.source,"group_id"):
            line_bot_api.leave_group(event.source.group_id)

        #ルームからの退出処理
        if hasattr(event.source,"room_id"):
            line_bot_api.leave_room(event.source.room_id)
            
        return

    # オウム返し
    line_bot_api.reply_message(
        event.reply_token,
        TextSendMessage(text=event.message.text))
```

#結果
<img width="375" alt="退出.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/401158/877ab2fd-2d50-0ae7-31fb-7bde3ee17c21.png">

無事、お帰りいただくことに成功。
もし、動かなかった人も、そのまま解説を読み進めてほしい。
オウム返しには必要のなかったAPIを用意していない可能性がある。

#解説
##グループとルーム
まず、LINEにはグループとルームという概念がある。
細かい違いは本筋から外れるのでググってもらうとして、
グループが招待＞承認を経て始まるのに、
ルームは強制的に一斉送信していきなり始まるもの。
「グループ・複数人チャットへの参加を許可する」にすると、どちらも招待できるようになってしまうので、
どちらにも対応した処理を追加する必要がある。

##インポート
今回、インポートで追加するものはなかった。
と思ったが、念のため、linebot/models/sources.pyを削除したら動かなくなった。

```console:コマンドプロンプト
heroku logs --tail

heroku[web.1]: Starting process with command `python main.py`
heroku[web.1]: State changed from starting to crashed
heroku[web.1]: Process exited with status 1
app[web.1]: Traceback (most recent call last):
app[web.1]: File "main.py", line 3, in <module>
app[web.1]: from linebot import (
app[web.1]: File "/app/linebot/__init__.py", line 22, in <module>
app[web.1]: from .api import (  # noqa
app[web.1]: File "/app/linebot/api.py", line 24, in <module>
app[web.1]: from .models import (
app[web.1]: File "/app/linebot/models/__init__.py", line 39, in <module>
app[web.1]: from .events import (  # noqa
app[web.1]: File "/app/linebot/models/events.py", line 33, in <module>
app[web.1]: from .sources import SourceUser, SourceGroup, SourceRoom
app[web.1]: ModuleNotFoundError: No module named 'linebot.models.sources'
heroku[router]: at=error code=H10 desc="App crashed" method=POST path="/callback" host=samplebot20190423.herokuapp.com request_id=6ea05c69-7d9f-42ee-96ac-109fd230faf5 fwd="147.92.149.169" dyno= connect= service= status=503 bytes= protocol=https
heroku[router]: at=error code=H10 desc="App crashed" method=POST path="/callback" host=samplebot20190423.herokuapp.com request_id=ed6cd4ae-da03-4f25-8d81-44aa4efb03d6 fwd="147.92.149.169" dyno= connect= service= status=503 bytes= protocol=https

```

最終的にevents.pyの33行目でインポートしてるモジュールがねーよって怒られてる。

```python:events.py
from .sources import SourceUser, SourceGroup, SourceRoom
```
今回使おうとしている
SourceGroup
SourceRoom
のインポートがここで行われていた。

もし、動かない人がいれば、このあたりのソースが足りないので、
linebot配下のソースを確認してほしい。

##退出処理

```python:main.py
        #グループトークからの退出処理
        if hasattr(event.source,"group_id"):
            line_bot_api.leave_group(event.source.group_id)
```

hasattr(第1引数,第2引数)は第1引数のオブジェクトに、第2引数に指定した属性が存在するかどうかを確認してくれる。
存在しないときに、その属性を取得しようとすると実行時エラーとなる。
例えば、グループトークに参加していないボットがグループIDを取得しようとするとエラーが発生する。

BOTが退出するには、leave_group(グループID)にグループIDを渡して実行すればよい。

##グループIDについて
そのグループIDってどうやって取得すればいいんだよってところにLINEBOT&Python初心者な私はかなりの調査を要したのだが、
結果的には、events.pyのEventクラスに実装されていて、簡単に取得できるようだった。
簡単すぎて誰も解説してくれてなかった（情報が見つけられなかった）というかなしみ。

get_group_idみたいなメソッドがあるだろうと思って、grep検索したものの見つからず、
どうやらsourceクラスに情報はあるぞってことはAPIリファレンスで分かったのだが・・・。

![api.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/401158/2b7f51af-4a19-00b4-5437-7a2a08a7e67b.jpeg)

「Webhookイベントオブジェクトのsourceオブジェクトで返されます。」
これで理解しなければだめだったらしい。
日本語でお願いします。

ちなみに英語リファレンスだと次の記載。
「Group ID. Found in the source object of webhook event objects.」


最終的に次の記事みて、event.source.user_idという記述を見て、
お、source内のuser_idはこうやってとるのか！と気づいて、
event.source.group_idの記述方法にたどり着けた。

[PythonでLINE Bot APIを使ってLINEユーザー情報を取得する](https://qiita.com/kotamatsuoka/items/472b455e5f9a6315d499)

言語違いだけど、次の記事を解析してpythonに書き換えれば行けるとか思ったのだけど、
eがeventのことだと気付かずに素通りしてしまった。

[LINE BOTでuserId, groupId, roomIdを取得したい](http://www.pre-practice.net/2018/11/line-botuserid-groupid-roomid.html)

今になって、改めて検索すると、この記事はわかりやすかったかな。
[【Messaging API #1】 クライアントのユーザID(user_id)とアカウント名(display_name)の取得方法 (python)](http://motojapan.hateblo.jp/entry/2017/07/08/010435)

#余談
##グループトークにBOTを召喚した後で、「グループ・複数人チャットへの参加を許可しない」に変更した場合どうなるか。
試してみたところ、変更時点では何も起きていないが、
グループ参加不可となったBOTがいるグループトークで発言を行うと、そのタイミングでBOTが退出する。
webhookイベントがないと退出できないのかなと予想していたので、
どうやらwebhookイベントが発生しないとアクションが起こせないらしい。

##メッセージ配信するとどうなるか
個別のチャットには配信されるが、グループトークには配信されない。

##BOTが所属しているグループIDの一覧みたいなのは取得できないのかもしれない
上の2点のことからの予想。
もし、所属しているグループ全体に何らかのアクションを起こしたいと思うのであれば、
どこかのタイミングでグループIDをどこかに退避しておいて、
独自で一覧取得できるようにする必要があるのかな。

まあ、グループトークに対して、BOT側から発言しに行く必要性はない気はするが。


#まとめ
##group_idの取得方法

```python
#eventはWebhookイベントオブジェクト
event.source.group_id
```


##room_idの取得方法

```python
#eventはWebhookイベントオブジェクト
event.source.room_id
```


##オウム返しBOTをグループトークに召喚するテロはやめよう
テロダメ！ゼッタイ！