LambdaにLine BOTを作ったメモ

Cloud9環境でLambda(Python)にLine Botを作る手順をまとめておきます。
例によって自分用。

ちなみに自動応答や一斉配信、リッチメニュー等は公式アカウントの機能で実現できます。今回はSlackとの連携やデータ収集機能を追加する予定なのでLambdaで実装しました。

## 前提
- OS：Amazon Linux 2023
- Pythonバージョン：3.10
- [LINE Messaging API](https://developers.line.biz/ja/services/messaging-api/)

## LINE側準備
### LINE公式アカウント作成
まずは[LINE公式アカウント](https://www.lycbiz.com/jp/service/line-official-account/)を作成します。複数人での管理を前提とするならメールアドレスで登録しておきましょう。
<img width=200 title="line_signup.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/62668/67fcd7d7-e967-c6f7-49a7-8d9ff74196a0.png">

送付されてくる登録用リンクにアクセスして公式アカウントの情報を入力します。

:::note warn
ここで入力した内容はあとからいくらでも変更できますが、うっかり検索に引っかかっても困るので実在する他社情報とかは入力しないように。
:::
<img width=300 title="line_account.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/62668/2bdc6357-b6ef-3c6d-3922-9bb84b0be3c8.png">

LINE公式アカウントは[コミュニケーションプランなら無料](https://www.lycbiz.com/jp/service/line-official-account/plan/)です。2024年2月時点では「月の無料メッセージ通数200通」という記載がありますが、これは登録者への一斉配信のことを指すので普通にやり取りする分にはコミュニケーションプランで十分。

### LINE公式アカウント設定
#### 自動応答設定
公式アカウント作成後は[LINE Official Account Manager](https://manager.line.biz/)からアカウントの追加・編集・管理をすることができます。
アカウント作成直後は以下2種類の自動応答が設定されています。とりあえず#2の方は邪魔なのでオフにしておきました。
| # | 応答タイミング | 設定画面 |
|--:|:--------------|:-----------|
|1|友達追加直後|\[トークルーム管理\]→\[あいさつメッセージ\]|
|2|メッセージ受信時|\[自動応答メッセージ\]→\[応答メッセージ\]|

#### チャット画面表示
\[設定\]→\[応答設定]→\[チャット]をオンにして管理画面上でトークルームを確認できるようにします。ここをオンにしていないと誰からどんなメッセージが送られてきたかが確認できないし手動応答もできません。

#### Messaging APIでプロバイダー作成
\[設定]→\[Messaging API]画面で「Messaging APIを利用する」を選択します。APIの利用にはLINE Developersへの登録が必要になります。公式アカウント作成済みであれば[LINE Developersコンソール](https://developers.line.biz/console/)にログインすることでも同じ作業が可能です。

Developer登録後は新規プロバイダーを作成します。プロバイダー名については[LINE Debelopersドキュメント](https://developers.line.biz/ja/docs/line-developers-console/overview/#creating-a-provider)を参照してください。基本的にはユーザーに公開されても問題ないサービス提供者名を指定します。

:::note warn
プロバイダー名は後から変更可能です。
ただし一度プロバイダーに紐づけた公式アカウント(チャネル)はアカウントごと削除しないと紐づけを解除できないので、複数アカウントを1プロバイダーに紐づける場合は注意が必要です。
:::

作成したプロバイダーページを開いてMessage API設定の「チャネルアクセストークン」を発行し、控えておきます。

## AWS側作業
### Lambda関数作成
基本手順は[Cloud9でLambda(Python)を作り始めるメモ](https://qiita.com/_saino/items/016bbc6c8a72caa4a669)に記載したので、それ以外の手順をまとめます。

#### HelloWorldから名称変更
チュートリアル通りにHelloWorldを作成しているため、各所のパスや名称がHelloWorldになっています。実体と異なる名称・パスにするのもあれなので、ちゃんと直しておきましょう。
template.yamlにデプロイ時の設定があるので、HelloWorldを置き換えておきます。
```diff_yaml:template.yaml
Resources:
- HelloWorldFunction:
+ LinetoslackFunction:
    Type: AWS::Serverless::Function
    Properties:
-     CodeUri: hello_world/
+     CodeUri: linetoslack/
      Handler: app.lambda_handler
      Runtime: python3.10
      Architectures:
        - x86_64
      Events:
-       HelloWorld:
+       Linetoslack:
          Type: Api
          Properties:
-           Path: /hello
-           Method: get
+           Path: /linetoslack
+           Method: post

Outputs:
-  HelloWorldApi:
+  LinetoslackApi:
    Description: "API Gateway endpoint URL for Prod stage for Line to Slack function"
-   Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/"
+   Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/linetoslack/"
- HelloWorldFunction:
+ LinetoslackFunction:
    Description: "Line to Slack Lambda Function ARN"
-   Value: !GetAtt HelloWorldFunction.Arn
+   Value: !GetAtt LinetoslackFunction.Arn
- HelloWorldFunctionIamRole:
+ LinetoslackFunctionIamRole:
    Description: "Implicit IAM Role created for Line to Slack function"
-   Value: !GetAtt HelloWorldFunctionRole.Arn
+   Value: !GetAtt LinetoslackFunctionRole.Arn
```
　※厳密にはDescriptionもいじってるけど動作には影響しない説明欄なので強調していません。

CodeUriを変更したので、Cloud9のリソース内に生成されている「hello_world」というフォルダも「linetoslack」にリネームしました。

#### Lambda環境変数追加
本来アクセストークンとか接続情報系はAWS Secrets Managerを使った方がいいんだろうけど、Secrets Managerはお金がかかるので無料で試したい今回の目的とはそぐわないんですよね。
というわけでひとまずは環境変数に突っ込むことでハードコーディングやらなんやらを回避しておきます。

Lambdaの環境変数はtemplate.yamlから追加できます。
```diff_yaml:template.yaml
Resources:
  LinetoslackFunction:
    Properties:
      # 省略
+     Environment:
+       Variables:
+         token: '<チャネルアクセストークン>'
```

#### 実装
[AWS Lambda + Python + LINE Botで傘が必要か教えてもらう](https://qiita.com/taku-0728/items/c80bcf65aba318ac6db0#1-line-messaging-api-%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%A6%E3%81%8A%E3%81%86%E3%82%80%E8%BF%94%E3%81%97%E3%81%99%E3%82%8Bbot%E3%82%92%E4%BD%9C%E3%82%8Bdef) を参考にオウム返しするコードを実装しました。ログ部分は一旦printで実装していますが実際はpythonなのでlogging使ってキレイにした方がいいですね。docstringは記事上長くなるので省略。
```python:app.py
import json
import urllib.request
import os

# Lambda環境変数からトークンを取得
TOKEN = os.environ['token']

def lambda_handler(event, context):

    for message_event in json.loads(event['body'])['events']:
        url = 'https://api.line.me/v2/bot/message/reply'
        headers = {
            'Content-Type': 'application/json',
            'Authorization': 'Bearer ' + TOKEN
        }
        body = {
            'replyToken': message_event['replyToken'],
            'messages': [
                {
                    "type": "text",
                    "text": message_event['message']['text'],
                }
            ]
        }

        req = urllib.request.Request(url, data=json.dumps(body).encode('utf-8'), method='POST', headers=headers)
        
        # LINEへの送信結果をCloudWatchで確認できるよう出力 
        with urllib.request.urlopen(req) as res:
            print(res.read().decode("utf-8"))

    # ステータスコードとbodyを返却しないとAPI Gatewayで502扱いされる
    return {
        "statusCode": 200,
        "body": json.dumps({
            "message": "success",
        }),
    }
```

#### デプロイ
とりあえずデプロイ！！と思ったら前回手順では見なかったエラーが発生したので対処。
```
[ERROR]: [qfe [SamCliError]: Cannot use both --resolve-s3 and --s3-bucket parameters. Please use only one.
```
これは初期値で作成されたsamconfig.tomlでresolve_s3がtrueになっているため、デプロイコマンドで競合が起こっているようです。
いや生成直後のデフォルト設定とデプロイボタンぽちっただけの動作で競合するなよ、という気持ち。
　参考：https://stackoverflow.com/a/77097963

というわけで設定を修正して再デプロイ。関数その他が生成できた！

```diff_toml:samconfig.toml
[default.deploy.parameters]
capabilities = "CAPABILITY_IAM"
confirm_changeset = true
- resolve_s3 = true
+ resolve_s3 = false

[default.package.parameters]
- resolve_s3 = true
+ resolve_s3 = false
```

### Webhook設定
最後に生成されたAPI GatewayのエンドポイントURLをコピーしてLINE Developersの「Messaging API設定」⇒「Webhook設定」に追加し、「Webhookの利用」をONにする。
LINEでメッセージを送ってみてそのままテキストが返ってきたら成功！！！！おめでとう！！！！！

## おわりに
AWS側の手順がわかりにくいことを除けばLINE連携は結構簡単なので助かりますね。気を付ければRPA系のサービス使うより基本安いし。
先人の皆さんの情報公開に感謝。