AWS CDK(Python)で BlueSky Botを作成してみた

## 動機

Blueskyで[日本各地の花粉情報を教えてくれるbot](https://bsky.app/profile/uso-kahun-bot.bsky.social)を作成しました。

作成するに当たり、AWS CDKを使ってbot作成している記事がほとんど無かったので共有します。

## 概要

![AWS.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3761646/54304295-54ae-9599-5a72-8f5794d4477b.png)

EventBridge SchedulerでLambdaを定期的に呼び出し、Lambdaから[atproto](https://github.com/bluesky-social/atproto)を使って投稿するというシンプルなもの


## 1.アカウントIDのパスワードを払い出す

[こちらの記事](https://www.zenryoku-kun.com/new-post/bluesky-api)を参考にアカウントID，パスワードを生成します。

https://www.zenryoku-kun.com/new-post/bluesky-api

**パスワードは1度しか表示されないのでメモを忘れないように**
## 2.投稿するためのLambdaを書く

[atproto](https://github.com/bluesky-social/atproto)というライブラリを使います。

https://github.com/bluesky-social/atproto

```python:app.py
from atproto import Client
from city_name_list import place
import os
import random
import logging


def lambda_handler(event,context):

    client = Client(base_url='https://bsky.social')
    client.login(login=os.environ.get("LOGIN"),password=os.environ.get("PASSWORD"))

    place_len = 1892
    place_num = random.randint(0, place_len-1)
    kahun_num = random.randint(0, 1000000)
    
    message = f'{place()[place_num]}の花粉は例年の{kahun_num}倍です'

    client.send_post(message)
```

``` python:
client = Client(base_url='https://bsky.social')
client.login(login=os.environ.get("LOGIN"),password=os.environ.get("PASSWORD"))
```
で自アカウントにログイン
```python
client.send_post(message)
```
で投稿しています。

ログインIDとパスワードはcdkのスタックの環境変数で定義したものを取得しています。

※```city_name_list```は都道府県市町村のリストです。

## 3.スタックの定義

atprotoは標準のライブラリではないため、普通にLambdaでimportするとエラーがでます。
そこでDockerImageを使ってLambdaの定義をしていきます。
※最初はLambda Layerを使って作成しようとしたのですが、何故かimport errorが出るのでDockerにしました。

```python:cdk.py
from aws_cdk import aws_lambda as _lambda
from aws_cdk import aws_events as events
from aws_cdk import aws_events_targets as targets
from aws_cdk import Duration, Stack
from constructs import Construct

class BlueSkyStack(Stack):

    def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
        super().__init__(scope, construct_id, **kwargs)

        #Dockerfileがあるディレクトリを指定
        my_function_image = _lambda.DockerImageCode.from_image_asset("Bluesky_app")

        my_function = _lambda.DockerImageFunction(
            self, "FunctionName",
            code=my_function_image,
            memory_size = 128,
            environment={
                "LOGIN": "your_login_id",
                "PASSWORD": "your_password",
            },
        )

        rule = events.Rule(
            self,
            "my-bot-trigger",
            schedule=events.Schedule.rate(Duration.hours(1))
        )

        rule.add_target(targets.LambdaFunction(my_function))
```

## 4.Dockerfileの定義

```Dockerfile
# 基本イメージとしてPythonのDockerイメージを使用
FROM public.ecr.aws/lambda/python:3.11

# Pythonの依存関係のインストール
COPY requirements.txt .
RUN python3.11 -m pip install -r requirements.txt

# Lambda関数のコードをコピー
COPY lambda_function/app.py .
COPY lambda_function/city_name_list.py .

# Lambdaのエントリポイントを設定
CMD ["app.lambda_handler"]
```

requirements.txtには```atproto```のみを書いています。

## 5.デプロイ
あとは```cdk deploy```でデプロイするだけです。

### 参考情報

デプロイする際に以下のようなエラーメッセージが出ました

```terminal
The stack named BlueSkyStack failed to deploy:
UPDATE_ROLLBACK_COMPLETE: Resource handler returned message:
"Invalid request provided: Updating PackageType is not supported"
(RequestToken: 92f7c6c5-26dd-00c8-df2d-149f0aab09ea, HandlerErrorCode: InvalidRequest)
```
Dockerを使用する前にLambda Layerで頑張っていたのですが、その時のスタック名(selfの次の引数)が被っていたことによるエラーでした。
スタック名を変えることでエラーを回避できました。
[この記事](https://zenn.dev/ncdc/articles/77637c681f4239#%E3%83%87%E3%83%97%E3%83%AD%E3%82%A4%E6%96%B9%E6%B3%95%E3%81%AF%E5%A4%89%E6%9B%B4%E3%81%A7%E3%81%8D%E3%81%AA%E3%81%84%E3%80%82)を参照にしました。本当にありがとう

https://zenn.dev/ncdc/articles/77637c681f4239#%E3%83%87%E3%83%97%E3%83%AD%E3%82%A4%E6%96%B9%E6%B3%95%E3%81%AF%E5%A4%89%E6%9B%B4%E3%81%A7%E3%81%8D%E3%81%AA%E3%81%84%E3%80%82

### 参考文献

https://www.zenryoku-kun.com/new-post/bluesky-api

https://qiita.com/ynack/items/85ccf951727f8ac48c6c