AWS Cloud Development Kit(CDK) を使用したAWS Lambdaで動くDiscord botの作り方

はじめに

今回は,AWS CDKを使用してDiscord botを作成してみたので,そのやり方についてまとめます.

botを使用した機能を作りたい!でも自宅サーバでは管理したくない!運用費用もそこまで掛けたくない!という人はぜひ

前提

この方法で作成できるのはスラッシュコマンドで呼び出されたときのみ動作する形式のbotです.
そのためサーバを常に監視して特定のテキストが送信された時とか,ボイスチャンネルに参加した時という条件で動作させることはできません.
このような形式のbotを作成するためにはEC2を使用するか自宅サーバを使用してbot本体を動かし,処理を行う関数をAWS Lamba + API Gatewayで公開する方法があります.しかし本記事ではそれの解説はしないのでご了承ください.

またAWS CDKではGitで管理できるように.gitignoreファイルが作成されますが,今回作成するプログラムではトークンの管理などの関係で,GitHubにPushできるようには作成していないのでGitHubで管理する場合には適宜変更してください.

言語はPythonでFlaskを使用します.
環境はWindowsです.

1. Botアカウントを作成する

はじめにApplicationページでBotのアカウントを作成します.

New Applicationを押します.

Botの名前を入力してアカウントを作成します.

General Informationのタブに移動し,Application IDとPublic keyをメモしておきます.
この情報は後ほど使用します.

Botのタブに移動し,Message Content Intentの設定をオンにします.


これはBotがメッセージを受信するために必要です.
またReset Tokenを押して生成されたトークンもメモしておきましょう.

OAuth2 URL Generatorに移動してbotにチェックを付けます.

必要なPermissonsにチェックを付けて下のURLをコピーします.


このリンクがBotの招待リンクになるので,保存しておいてサーバに招待する人に送信できるようにしましょう.

この時点でテスト用のサーバを作成して,Botを招待しておくと正常に動いているか確認するのに便利です.

2. AWS CDKの準備

ローカル環境でAWS CDKを使用するための準備をします.

はじめにDocker Desktopが使用できることを確認してください.
使用できない場合はこちらからセットアップしてください.

次にAWS CLIをインストールします.

AWS CLIのインストールからダウンロードし,インストーラーを実行してください.

Bash
$ aws --version

コマンドプロンプトを起動しこのコマンドを入力して,バージョン情報が返ってきたらOKです.

次にアカウントにアクセスキーを作成します.

  1. WebからAWSのページにアクセスしI AMに移動します.
  2. 左側のアクセス管理からユーザを選択
  3. 設定したいユーザを選択
  4. セキュリティ認証情報のタブに移動
  5. アクセスキーを作成を押す
  6. コマンドラインインターフェースを選択し,下の「上記のレコメンデーションを理解し、アクセスキーを作成します。」にチェック
  7. アクセスキーを作成を押し,アクセスキーの情報を保存

次に以下のコマンドでアカウントを紐づけます.
以下のコマンドを打ち保存しておいた情報を入力すればOKです.

Bash
$ aws configure
AWS Access Key ID [None]: <type key ID here>
AWS Secret Access Key [None]: <type access key>
Default region name [None]: <choose region (e.g. "us-east-1", "eu-west-1")>
Default output format [None]: <leave blank>

次にAWS CDKとTypeScriptをインストールします.

Bash
$ npm install -g aws-cdk
$ npm install -g typescript

3. AWS CDKプロジェクトを作成する

フォルダを作成してその中で作成します.

Bash
$ mkdir "フォルダ名"
$ cd "フォルダ名"
$ cdk init app --language typescript

typescriptで作成しているのは,Flaskで作成したAPIをtypescriptから呼び出すためです.

コマンドの実行が完了するとファイルが複数作成されます.
今回の記事では作成されたファイルの詳細な解説はしません.

4. スラッシュコマンドの登録

discordにコマンドを登録するためのプログラムを作成します.

Bash
$ mkdir commands
$ cd commands
$ touch discord_commands.yaml
$ touch register_commands.py
$ touch requirements.txt

それぞれのファイルに内容を記述します.

commands/discord_commands.yaml

YAML
- name: randompick
  description: get a random hero.
  options:
    - name: position
      description: The position you want.
      type: 3
      required: false

- name: herolist
  description: get all random hero.
  options:
    - name: position
      description: The position you want.
      type: 3
      required: true

このファイルでは作成するコマンドを定義します.
適宜読み替えてコマンドを定義してください.

ここでは/randompickと打つとget a random hero.という説明文が出るコマンドを定義しています.またoptionsをrequiredがfalseの状態で設定してあるので任意でpositionの引数をつけることができます.
もう1つ/herelistも定義していますが,こちらはrequredがtrueのためユーザに必ずpositionの引数を入力させます.

commands/register_commands.py

Python
import requests
import yaml


TOKEN = "トークン"
APPLICATION_ID = "アプリケーションID"
URL = f"https://discord.com/api/v9/applications/{APPLICATION_ID}/commands"


with open("discord_commands.yaml", "r") as file:
    yaml_content = file.read()

commands = yaml.safe_load(yaml_content)
headers = {"Authorization": f"Bot {TOKEN}", "Content-Type": "application/json"}

# Send the POST request for each command
for command in commands:
    response = requests.post(URL, json=command, headers=headers)
    command_name = command["name"]
    print(f"Command {command_name} created: {response.status_code}")

このプログラムでコマンドの作成を行います.
TOKENとAPPLICATION_IDはそれぞれ1. Botアカウントを作成するのときにメモした内容を記入しましょう.

commands/requirements.txt

YAML
requests
pyyaml

最後に必要がライブラリを設定しています.

コマンドを打ってライブラリのインストールとスラッシュコマンドの登録をします.

Bash
$ pip install -r requirements.txt
$ python3 register_commands.py

これでスラッシュコマンドの登録ができたはずです.
しばらくしたらこのbotを追加してあるdiscordサーバ上で/と打つと登録したコマンドが表示されるはずです.

5. プログラムの作成

最後にプログラムを作成します.

Bash
# 今commandsフォルダにいる場合は以下も行う
# $ cd ..
$ mkdir src
$ cd src
$ touch Dockerfile
$ touch requrements.txt
$ mkdir app
$ cd app
$ touch main.py

それぞれのファイルの内容を記述していきます.

src/Dockerfile

Python
FROM public.ecr.aws/lambda/python:3.11
COPY requirements.txt ${LAMBDA_TASK_ROOT}
RUN pip install -r requirements.txt
COPY app/* ${LAMBDA_TASK_ROOT}
CMD [ "main.handler" ]

src/requirements.txt

Python
flask
requests
mangum
asgiref
discord-interactions

app/main.py

Python
import os
from flask import Flask, jsonify, request
from mangum import Mangum
from asgiref.wsgi import WsgiToAsgi
from discord_interactions import verify_key_decorator

import requests
import json
import random

DISCORD_PUBLIC_KEY = os.environ.get("DISCORD_PUBLIC_KEY")

app = Flask(__name__)
asgi_app = WsgiToAsgi(app)
handler = Mangum(asgi_app)


@app.route("/", methods=["POST"])
async def interactions():
    print(f"👉 Request: {request.json}")
    raw_request = request.json
    return interact(raw_request)


@verify_key_decorator(DISCORD_PUBLIC_KEY)
def interact(raw_request):
    if raw_request["type"] == 1:
        response_data = {"type": 1}
    else:
        data = raw_request["data"]
        command_name = data["name"]

        if command_name == "randompick":
            # randompickが呼び出されたときのプログラム
            message_content = "出力内容"
        elif command_name == "herolist":
            # herolistが呼び出されたときのプログラム
            message_content = "出力内容"
        response_data = {
            "type": 4,
            "data": {"content": message_content},
        }

    return jsonify(response_data)

if __name__ == "__main__":
    app.run(debug=True)

コマンドの処理内容の部分は適宜書き換えてください.

次にlib内にあるtsファイルを編集します(最初に作成したフォルダ名がファイル名になってる?)

(私の環境では)lib/dota2-assistant-stack.ts

TypeScript
import * as cdk from "aws-cdk-lib";
import { Construct } from "constructs";
import * as lambda from "aws-cdk-lib/aws-lambda";

export class Dota2AssistantStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const dockerFunction = new lambda.DockerImageFunction(
      this,
      "DockerFunction",
      {
        code: lambda.DockerImageCode.fromImageAsset("./src"),
        memorySize: 1024,
        timeout: cdk.Duration.seconds(10),
        architecture: lambda.Architecture.X86_64,
        environment: {
          DISCORD_PUBLIC_KEY: "パブリックキー",
        },
      }
    );

    const functionUrl = dockerFunction.addFunctionUrl({
      authType: lambda.FunctionUrlAuthType.NONE,
      cors: {
        allowedOrigins: ["*"],
        allowedMethods: [lambda.HttpMethod.ALL],
        allowedHeaders: ["*"],
      },
    });

    new cdk.CfnOutput(this, "FunctionUrl", {
      value: functionUrl.url,
    });
  }
}

export classの部分はDota2AssistantStackとなっていますが,作成する際ははじめから記入されているものを参考に書き換えてください.
DISCORD_PUBLIC_KEYの部分には1. Botアカウントを作成するで保存したパブリックキーを記入してください.

6. デプロイ

最後にデプロイを行います.

Bash
$ cdk bootstrap

しばらく時間がかかると思いますが,気長に待ちましょう.
このコマンドはプロジェクトを作成した最初1回だけ行えば大丈夫です.

Bash
$ cdk deploy

このコマンドでデプロイが行われます.
プログラムを変更した場合は毎回おこないましょう.

デプロイが完了したら,lambdaリソースが作成されているか確認してください.

作成されていれば,Discordでのスラッシュコマンドでbotが返答をするはずです.

おわりに

AWS CDKを使用したDisocord botの作り方の解説は以上です!
自分でもまとめつつ書いていたので,わかりにくい部分やこの内容では動かない部分などあれば連絡していただけると嬉しいです.

返信を残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA