amazon translateを使って翻訳機能付きdiscord botをec2上で動かす

概要

discord(slackのようなコミュニケーションツール)のbotをec2上で動かす。botがコマンドに反応して、amazon translate経由で翻訳を実現する。ec2上で動かすことで、botが常駐して翻訳できる。

処理の流れ

  1. discordでユーザーがコマンドを入力する
  2. botがコマンドに反応し、翻訳先言語と文章を取得する
  3. amazon translateに翻訳先言語と文章を渡して翻訳済み文章を取得する
  4. botがコマンドに翻訳済み文章を返信する

環境

  • mfa有効化IAMユーザー
  • ec2 Amazon Linux 2023
  • Python 3.9.16
  • boto3==1.29.0
  • discord.py==2.3.2
  • python-dotenv==1.0.0

下準備

botを動かす前の下準備として、

  • ec2の作成
  • ec2にssh接続
  • ec2にpython,pip,必要ライブラリのインストール
  • discordBotの作成
  • aws cliの設定
  • MFAの一時的認証

以上の設定を行う。

ec2の作成

amazon linux 2023を指定

新しくキーペアを作成 Users/user/.sshとかに保存しておく。

ログインキーペアに作成したキーペアを設定。

パブリック IP 有効化、セキュリティグループはsshを許可する。

ec2にssh接続

qiita.com

vscodessh接続するので上記の記事通り設定していく。 Users/username/.ssh/configに以下の内容を書き込む。

Host discord-ec2
  HostName <ec2のip>
  User ec2-user
  Port 22
  UserKnownHostsFile /dev/null
  PreferredAuthentications publickey
  StrictHostKeyChecking no
  PasswordAuthentication no
  IdentityFile /Users/<username>/.ssh/<保存したキーペア>.pem
  IdentitiesOnly yes
  LogLevel FATAL

ec2にpython,pip,必要ライブラリのインストール

terminalで

sudo yum update
sudo yum install python3
sudo yum install pip
pip install discord
pip install python-dotenv

を順番に実行。

python3 -V
pip freeze

でちゃんとインストールできたか確認する。

mkdir test
cd test

適当な作業ディレクトリも作っておく。

discordBotの作成

note.com

Botをサーバーに入れるところまでは上記の記事通り設定していく。

discord.com

discord dev portalからNew Applicationsでbotを新規作成した後は、

Botタブのreset tokenでbotのtokenをコピーしておく。

intentsの設定でMESSAGE CONTENT INTENTをオンにしておかないと、コマンドの取得ができないのでオンにしておく。

OAuth2のURL GeneratorのScopesで、botをチェック

同画面の下にあるBot Permissionsで、Send MessagesとRead Message HistoryをチェックしてGENERATED URLをコピー

管理者権限持ってるサーバーを選択し、botを追加

これでbotが追加できた。 ec2の作業ディレクトリに.envファイルを作成し、

DISCORD_TOKEN=<コピーしたtoken>

と書き込んでおく。

aws cliの設定

aws consoleでIAMにアクセスし、使用するユーザーのアクセスキーを作成する。 アクセスキーとシークレットアクセスキーをコピーしておく。

aws configure

で指示に従いアクセスキーとシークレットアクセスキーを入力する。

MFAの一時的認証

MFAを有効化している場合、aws configureだけだとamazon translateの権限が足りず、pythonから接続ができない。 そこで、一時的なMFA認証をする。

qiita.com

上記の記事通り設定していく。

aws sts get-session-token \
  --serial-number <mfaデバイスのarn> \
  --token-code <token code>\
  --profile mfa

このコマンドでAccessKeyId,SecretAccessKey,SessionTokenが得られるのでコピーしておく。

export AWS_ACCESS_KEY_ID=<AccessKeyId>
export AWS_SECRET_ACCESS_KEY=<SecretAccessKey>
export AWS_SESSION_TOKEN=<SessionToken>

環境変数に保存しておく。

これでひとまず下準備終わり。

discordBotのテスト起動

discordBotのテストとして、!greetコマンドに対して"hello"と返信するBotを作成してみる。

import discord
from dotenv import load_dotenv
from discord.ext import commands
import os

load_dotenv()
TOKEN = os.getenv('DISCORD_TOKEN')

# Intentsを作成
intents = discord.Intents.default()
intents.message_content = True

bot = commands.Bot(command_prefix='!', intents=intents, help_command=None)

@bot.event
async def on_ready():
    print('on ready')

@commands.command()
async def greet(ctx):
    await ctx.reply("hello")

bot.add_command(greet)
bot.run(TOKEN)

動作確認ができた。

コード解説
load_dotenv()
TOKEN = os.getenv('DISCORD_TOKEN')

# Intentsを作成
intents = discord.Intents.default()
intents.message_content = True

bot = commands.Bot(command_prefix='!', intents=intents, help_command=None)
  • envからtokenを取得
  • intentsの設定
  • botインスタンスの作成(コマンドの接頭辞を'!'に設定、helpコマンドの無効化)

以上の設定をしている。

@bot.event
async def on_ready():
    print('on ready')

@commands.command()
async def greet(ctx):
    await ctx.reply("hello")

bot.add_command(greet)
bot.run(TOKEN)
  • bot起動時にprint出力
  • greetコマンドの登録
  • コマンドの追加

以上の設定をしている。

pythonからamazon translateを動かす

Translate.py

import boto3

class Translate:
    def __init__(self, region_name='ap-northeast-1'):
        self.translate_client = boto3.client('translate', region_name=region_name)

    def translate_text(self, text, target_language_code):
        response = self.translate_client.translate_text(
            Text=text,
            SourceLanguageCode="auto",
            TargetLanguageCode=target_language_code
        )
        return response["TranslatedText"]

t = Translate()

# テキストを翻訳
response = t.translate_text(
    text="こんにちは",
    target_language_code="en"
)

print(response)

あとで使い回すのでクラス化してある。Translate.pyで保存しておく。

よさそうです。

コード解説
response = t.translate_text(
    text="こんにちは",
    target_language_code="en"
)

textとtarget_language_codeを変更すると応じた結果に変わる。

def translate_text(self, text, target_language_code):
        response = self.translate_client.translate_text(
            Text=text,
            SourceLanguageCode="auto",
            TargetLanguageCode=target_language_code
        )
        return response["TranslatedText"]

responseから翻訳済み文章だけを抽出する。SourceLanguageCodeをautoにすると、comprehendで言語推定をして補完してくれる。しかし、その分の使用料がかかるので注意。

discordBotに翻訳処理を追加する

テストとして作成した、!greetコマンドに対して"hello"と返信するBotをベースに、翻訳処理を追加する。

main.py

import discord
from dotenv import load_dotenv
from discord.ext import commands
import os

import Translate

load_dotenv()
TOKEN = os.getenv('DISCORD_TOKEN')

# Intentsを作成
intents = discord.Intents.default()
intents.message_content = True

bot = commands.Bot(command_prefix='!', intents=intents, help_command=None)

language_codes = [
    "af", "sq", "am", "ar", "hy", "az", "bn", "bs", "bg", "ca",
    "zh", "zh-TW", "hr", "cs", "da", "fa-AF", "nl", "en", "et", "fa",
    "tl", "fi", "fr", "fr-CA", "ka", "de", "el", "gu", "ht", "ha",
    "he", "hi", "hu", "is", "id", "ga", "it", "ja", "kn", "kk", "ko",
    "lv", "lt", "mk", "ms", "ml", "mt", "mr", "mn", "no", "ps", "pl",
    "pt", "pt-PT", "pa", "ro", "ru", "sr", "si", "sk", "sl", "so",
    "es", "es-MX", "sw", "sv", "ta", "te", "th", "tr", "uk", "ur",
    "uz", "vi", "cy"
]

# translateのインスタンス作成
t = Translate.Translate()

@bot.event
async def on_ready():
    print('on ready')

@commands.command()
async def help(ctx):
    await send_help(ctx)

@commands.command()
async def tl(ctx, *args):
    print(f"args:{args}")
    # 引数が2個未満のときはhelpメッセージ表示してreturn
    if(len(args) < 2):
        await send_help(ctx)
        return
    language_code = args[0]
    text = args[1]

    # 最初の引数が言語コードじゃない場合はhelpメッセージ表示してreturn
    if(language_code not in language_codes):
        print(f"ng {language_code}")
        await send_help(ctx)
        return
    print(f"言語コード:{language_code}\nテキスト:{text}")

    # テキストを翻訳
    response = t.translate_text(
        text=text,
        target_language_code=language_code
    )
    await ctx.reply(f"{response}")

async def send_help(ctx):
    msg = """
    翻訳コマンド !tl
例:!tl [language_code] [text]
指定した言語に文章を翻訳します

[language_code] 言語コード 
対応コード一覧:https://docs.aws.amazon.com/ja_jp/translate/latest/dg/what-is-languages.html
[text] 翻訳する文章 スペースが含まれる場合はダブルクォーテーション("")で囲う
    """
    await ctx.send(msg)


bot.add_command(tl)
bot.add_command(help)
bot.run(TOKEN)

テスト用のdiscordBotに

  • tlコマンド(翻訳コマンド)の追加
  • helpコマンドの追加
  • amazon translateの言語コードの定義

この3つの処理を追加した。

tlコマンドが実行されたら以下の順番で処理をする。

  1. コマンドの引数が2個未満なら、helpメッセージを表示してreturn
  2. 1つ目をlanguage_code,2つ目をtextとして代入する
  3. language_codeがamazon translateの対象リスト(language_codes)に含まれていない場合は、helpメッセージを表示してreturn
  4. language_codeとtextをTranslate.translate_text()に渡し、翻訳済み文章を取得
  5. ctx.reply()で元コマンドに返信する

実際に動かす

helpコマンドもtlコマンドも問題なく動いてますね。

感想

1日でシステム完成までできて簡単にアプリを作れたのは、サーバーの諸設定について考慮しなくていいawsならではだと思う。 今回は常駐型でサーバー有りでbotを動かしたが、lambdaでサーバーレス構成にすることも可能なので、拡張性が高い。 詰まったところは、

  • discordBotのintents設定
  • mfa有効化した場合のaws cli接続
  • discordBotのデフォルトhelpコマンド無効化

この3つだった。特に、intents設定は新しく追加された設定で、昔個人で使用した際にはなかったため調べるのに時間がかかった。ほか2つは調べるとすぐ解決できた。

改善点

  • lambda+INTERACTIONS ENDPOINT URLで完全サーバーレス化
  • スラッシュコマンドを使ってUIをわかりやすくする
  • 翻訳前文章の言語推定をcomprehend経由ではなく、他のサービスにして言語推定にかかる料金を節約
  • awsの他のサービスと連携して機能拡張

上記の改善点が挙げられる。INTERACTIONS ENDPOINT URLでのサーバーレスは以前に一回試したが、新しい機能で参考にする資料が少なく、うまくいかなかったた。

参考文献

note.com qiita.com qiita.com