PythonでLINE Botのリッチメニュー を活用したアプリを開発したので学んだことを記す

## リッチメニューとは何か
リッチメニューはLINE公式アカウントのトーク画面下部に表示されるメニュー機能。
リッチメニューの各領域にリンクを設定することで、LINE公式アカウントの各機能のほか、外部サイトや予約ページなどにユーザーを誘導することができる。

![Screen Shot 2021-05-25 at 11.05.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/411851/fd0298e2-37a3-669b-12a5-f0c3212d6082.png)


## リッチメニューオブジェクト
```json:json
{
  "size": {
    "width": 2500,
    "height": 1686
  },
  "selected": false,
  "name": "Nice richmenu",
  "chatBarText": "Tap to open",
  "areas": [
    {
      "bounds": {
        "x": 0,
        "y": 0,
        "width": 2500,
        "height": 1686
      },
      "action": {
        "type": "postback",
        "data": "action=buy&itemid=123"
      }
    }
  ]
}
```
## リッチメニューの作成(例)
```shell:shell
curl -v -X POST https://api.line.me/v2/bot/richmenu \
-H 'Authorization: Bearer {channel access token}' \
-H 'Content-Type: application/json' \
-d \
'{
    "size": {
      "width": 2500,
      "height": 1686
    },
    "selected": false,
    "name": "Nice richmenu",
    "chatBarText": "Tap here",
    "areas": [
      {
        "bounds": {
          "x": 0,
          "y": 0,
          "width": 2500,
          "height": 1686
        },
        "action": {
          "type": "postback",
          "data": "action=buy&itemid=123"
        }
      },
      {
        "bounds": {
          "x": 0,
          "y": 840,
          "width": 2500,
          "height": 1686
        },
        "action": {
          "type": "postback",
          "data": "action=buy&itemid=123"
        }
      }
   ]
}'
```
### 注意
#### リッチメニューの画像は1枚である
例えば、リッチメニューで6枚のタイルを設置したい場面においても、画像自体は1枚でなければいけない。（6枚アップロードするわけではない。）
なので私は、Figmaで6枚の画像を1枚のFieldに設置してExportして1枚の画像にしました！

#### リッチメニューの各コンテンツはタップ領域を決めなければいけない
上で、**リッチメニューの画像は1枚である**と述べたように、1枚の画像をアップロードしなければいけないため、複数のタイルを表示したいときは、画像のどの領域をタップすればどういったアクションを呼ぶかというものを定義しないといけない。

#### 各領域のタップ領域は、各タイルの左上を基準にして定める
下に添付した画像の通り、画像の赤丸の領域（つまりタイルの角）が、1枚の画像に対してx座標(画像の上端からの距離),y座標(画像の左端からの距離)のどの位置に位置するかを定義する必要がある。

>ちなみに、この例では、1枚の画像のサイズは
>2500:1686で定義している。故に6枚タイルを設置するときは、1枚のタイルの縦は840,横は830程度である。

以下の画像について考えるとき、例えば、左端上のタイルのタップ領域を定義するには以下のようになる。左上のタイルの左上の角は1枚の画像の左端から0距離だからx=0,1枚の画像の上端から0距離なのでy=0で定義しなければいけない。
![Screen Shot 2021-05-24 at 16.01.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/411851/f5338ba7-afa7-dc1f-5fca-9899ddbcfcfa.png)


##### 左上の定義
```shell:shell
curl -v -X POST https://api.line.me/v2/bot/richmenu \
-H 'Authorization: Bearer {channel access token}' \
-H 'Content-Type: application/json' \
-d \
'{
    "size": {
      "width": 2500,
      "height": 1686
    },
    "selected": false,
    "name": "Nice richmenu",
    "chatBarText": "Tap here",
    "areas": [
      {
        "bounds": {
          "x": 0,
          "y": 0,
          "width": 2500,
          "height": 1686
        },
        "action": {
          "type": "postback",
          "data": "action=buy&itemid=123"
        }
      }
   ]
}'
```
同様にすると
##### 右下の定義
```shell:shell
curl -v -X POST https://api.line.me/v2/bot/richmenu \
-H 'Authorization: Bearer {channel access token}' \
-H 'Content-Type: application/json' \
-d \
'{
    "size": {
      "width": 2500,
      "height": 1686
    },
    "selected": false,
    "name": "Nice richmenu",
    "chatBarText": "Tap here",
    "areas": [
      {
        "bounds": {
          "x": 0,
          "y": 840,
          "width": 2500,
          "height": 1686
        },
        "action": {
          "type": "postback",
          "data": "action=buy&itemid=123"
        }
      }
   ]
}'
```
##### 真ん中上の定義
```shell:shell
curl -v -X POST https://api.line.me/v2/bot/richmenu \
-H 'Authorization: Bearer {channel access token}' \
-H 'Content-Type: application/json' \
-d \
'{
    "size": {
      "width": 2500,
      "height": 1686
    },
    "selected": false,
    "name": "Nice richmenu",
    "chatBarText": "Tap here",
    "areas": [
      {
        "bounds": {
          "x": 830,
          "y": 0,
          "width": 2500,
          "height": 1686
        },
        "action": {
          "type": "postback",
          "data": "action=buy&itemid=123"
        }
      }
   ]
}'
```

### 複数のタイルを作りたいときは、areasの中身を配列にして渡せばOKです！
以下は6枚のタイルを作るとき。（実際はxとかyの値を変えてあげてくださいね！）

```shell:shell
curl -v -X POST https://api.line.me/v2/bot/richmenu \
-H 'Authorization: Bearer {channel access token}' \
-H 'Content-Type: application/json' \
-d \
'{
    "size": {
      "width": 2500,
      "height": 1686
    },
    "selected": false,
    "name": "Nice richmenu",
    "chatBarText": "Tap here",
    "areas": [
      {
        "bounds": {
          "x": 830,
          "y": 0,
          "width": 2500,
          "height": 1686
        },
        "action": {
          "type": "postback",
          "data": "action=buy&itemid=123"
        }
      },
      {
        "bounds": {
          "x": 830,
          "y": 0,
          "width": 2500,
          "height": 1686
        },
        "action": {
          "type": "postback",
          "data": "action=buy&itemid=123"
        },
        {
        "bounds": {
          "x": 830,
          "y": 0,
          "width": 2500,
          "height": 1686
        },
        "action": {
          "type": "postback",
          "data": "action=buy&itemid=123"
        },
        {
        "bounds": {
          "x": 830,
          "y": 0,
          "width": 2500,
          "height": 1686
        },
        "action": {
          "type": "postback",
          "data": "action=buy&itemid=123"
        },
        {
        "bounds": {
          "x": 830,
          "y": 0,
          "width": 2500,
          "height": 1686
        },
        "action": {
          "type": "postback",
          "data": "action=buy&itemid=123"
        },
        {
        "bounds": {
          "x": 830,
          "y": 0,
          "width": 2500,
          "height": 1686
        },
        "action": {
          "type": "postback",
          "data": "action=buy&itemid=123"
        }
   ]
}'
```

## リッチメニューの配列を取得
```shell:shell
curl -v -X GET https://api.line.me/v2/bot/richmenu/list \
-H 'Authorization: Bearer {channel access token}'
```

## リッチメニューを削除
```shell:shell
curl -v -X DELETE https://api.line.me/v2/bot/richmenu/{richMenuId} \
-H 'Authorization: Bearer {channel access token}'
```

## リッチメニューの設定(公開)
```shell:shell
curl -v -X POST https://api.line.me/v2/bot/user/all/richmenu/{richMenuId} \
-H "Authorization: Bearer {channel access token}"
```

## リッチメニューの画像をアップロード
```shell:shell
curl -v -X POST https://api-data.line.me/v2/bot/richmenu/{richMenuId}/content \
-H "Authorization: Bearer {channel access token}" \
-H "Content-Type: image/jpeg" \
-T image.jpg
```

>ルール：
>画像フォーマット：JPEGまたはPNG
>画像の幅サイズ（ピクセル）：800以上、2500以下
>画像の高さサイズ（ピクセル）：250以上
>画像のアスペクト比（幅/高さ）：1.45以上
>最大ファイルサイズ：1MB

>ファイルサイズが1MB以下なのにアップロードできない！という方は一度、その画像をWeb上の圧縮サービスで圧縮したのちにアップロードしてみて再度試してみてください！

-----
## PostBackオブジェクトのレスポンスの形式

```json:json
{
   "type":"postback",
   "label":"Buy",
   "data":"action=buy&itemid=111",
   "text":"Buy"
}
```

よって、例えば、リッチメニューに設定したdataを取り出したいときは、

```python:python
from linebot.models import (
    MessageEvent, TextMessage, TextSendMessage,PostbackEvent,PostbackTemplateAction # PostbackEventの取得用
)

@handler.add(PostbackEvent)
def on_postback(event):
    # PostBackを取得
    postback_msg = event.postback.data
```

-----
## Webhookのイベントオブジェクト(何が返ってくるか)

```json:json
{
  "destination": "xxxxxxxxxx",
  "events": [
    {
      "replyToken": "0f3779fba3b349968c5d07db31eab56f",
      "type": "message",
      "mode": "active",
      "timestamp": 1462629479859,
      "source": {
        "type": "user",
        "userId": "U4af4980629..."
      },
      "message": {
        "id": "325708",
        "type": "text",
        "text": "Hello, world"
      }
    },
    {
      "replyToken": "8cf9239d56244f4197887e939187e19e",
      "type": "follow",
      "mode": "active",
      "timestamp": 1462629479859,
      "source": {
        "type": "user",
        "userId": "U4af4980629..."
      }
    }
  ]
}
```

よって、メッセージを送ってきたクライアントUserのidを取得するには

```python:python
from linebot.models import (
    MessageEvent, TextMessage, TextSendMessage,PostbackEvent,PostbackTemplateAction # PostbackEventの取得用
)

@handler.add(MessageEvent, message=TextMessage)
def handle_message(event): # 引数のeventにAPIからのレスポンスが入っている
    returnUserid = event.source.user_id
```

## メッセージをユーザーに返す (MessageEvent)
```python:python
from linebot.models import (
    MessageEvent, TextMessage, TextSendMessage,PostbackEvent,PostbackTemplateAction # PostbackEventの取得用
)

@handler.add(MessageEvent, message=TextMessage)
def handle_message(event): # 引数のeventにAPIからのレスポンスが入っている
    text = event.message.text # 送信されたメッセージを取得
    line_bot_api.reply_message(event.reply_token, TextSendMessage(text)) # トークンと送信テキストをTextSendMessage(text)にて指定。おうむ返しアプリを作りたければ、送信された内容と同じ内容を返すようにすればいいので上のようにすればいい。
```

### 注意
#### ReplyTokenは一回のみしか使えない
通称、応答トークンは一回しか使えないので、例えば以下のようにすると、先に書いた方のメッセージしかBotにてユーザーに返されない。

```python:lambda_function.py
from linebot import LineBotApi
from linebot.exceptions import LineBotApiError
from linebot.models import (
    MessageEvent, TextMessage, TextSendMessage,PostbackEvent,PostbackTemplateAction # PostbackEventの取得用
)

line_bot_api = LineBotApi('<channel access token>')

try:
    line_bot_api.reply_message('<reply_token>', TextSendMessage(text='Hello World first!'))
    line_bot_api.reply_message('<reply_token>', TextSendMessage(text='Hello World second!'))
except LineBotApiError as e:

# => output is below
Hello World first!
```
-----

## 特定のユーザー情報の取得
```python:python
from linebot import LineBotApi
from linebot.exceptions import LineBotApiError

line_bot_api = LineBotApi('<channel access token>')

# UserId = event.source.user_idでユーザーIDを取得する
try:
    profile = line_bot_api.get_profile('<user_id>')
    user_disp_name = profile.display_name # アカウント名
except LineBotApiError as e:
    # error handle
    ...

### 返ってくるレスポンス
```json:json
{
    "displayName":"LINE taro",
    "userId":"U4af4980629...",
    "language":"en",
    "pictureUrl":"https://obs.line-apps.com/...",
    "statusMessage":"Hello, LINE!"
}
```

## Botを友達追加したユーザーのリストを取得する
```shell:shell
curl -v -X GET https://api.line.me/v2/bot/followers/ids?start={continuationToken} \
-H 'Authorization: Bearer {channel access token}'
```
:::message alert
この機能は認証済みアカウントまたはプレミアムアカウントのみ
:::

### 返ってくるレスポンス
```json:json
{
   "userIds":[
      "U4af4980629...",
      "U0c229f96c4...",
      "U95afb1d4df..."
   ],
   "next":"yANU9IA..."
}
```
## 参照
https://developers.line.biz/ja/reference/messaging-api