LoginSignup
5
3

【スレッド返信対応版】ChatGPTのDiscordBotを作ってデプロイまでやっちゃうの巻(discord.js & ChatGPT API & fly.io)

Last updated at Posted at 2023-10-28

はじめに

お疲れ様です! @Keichan_15 です!

半年ほど前に以下の記事でChatGPT APIを使用したSlackBotの作成方法について公開しました。

で、最近そのSlackBotで事件が起きたんですよね~~ 僕のせいなんですが

とあるSlackのチャンネルにBotを導入して半年くらいずーっと運用していたのですが、諸事情あってプライベートチャンネルに変更した瞬間に返信がピタリと止んでしまったのです。

なんで~って思って調べてみたら、普通にSlack公式が以下のページで 「パブリックチャンネルだけしか使えんで!」 って書いてたみたいで、完全に見落としてました。嘘やん…。

Outgoing Webhooks are a legacy method of sending notifications to an app about two specific activities:
1. A message was posted in a particular public Slack channel.

ChatGPT APIのクレジットも持て余しちゃってる & 最近遂にGPT-4が使用できるようになったので、何とか活かせないかと考慮した結果、Discord Botとして第2の人生を歩み始めるようにしてあげるのが良いんじゃないかという結論に…!:relaxed:

そこで今回は

  • 特定のチャンネルにChatGPTのBotが自動返信
  • 複数メンバーのメッセージ送信に対応するスレッド返信
  • 継続運用のためのデプロイ

の豪華三本立てでお送りしていきます!

スレッド返信の日本語資料が少なすぎて本当に困難を極めました…マジツラカッタ~~

それではやっていきましょう!!:v:

環境

  • WSL2(Ubuntu 20.04.5)
  • Node.js(v16.18.0)
  • npm(v8.19.2)
  • Visual Studio Code(v1.83.1)

当初はおもちゃを動かすくらいの想定だったので、昔にRails環境を構築した時に一緒に入れたNode.jsが入ってたWSL2を選びました。

ぶっちゃけNode.jsが動かせる環境なら何でも良いと思います。

環境準備

まず任意のディレクトリを作成します。本記事ではchat_gptにしました。何でも良いです。

作成したディレクトリに移動後、以下のコマンドを入力してpackage.jsonを生成します。

terminal
$ npm init -y

以下のようなpackage.jsonが生成されます。

package.json
{
  "name": "chat_gpt",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

今回はindex.jsbotというフォルダ内で管理するようにするため、package.jsonを以下のように書き換えましょう。(ついでにモジュール環境でimportステートメントを使えるようにもしておきます)

package.json
{
  "name": "chat_gpt",
  "version": "1.0.0",
  "description": "",
- "main": "index.js",
+ "main": "bot/index.js",
+ "type": "module",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

同時にbotディレクトリを作成し、index.jsも作成します。
この時点でディレクトリ構成が以下のようになっていれば問題ありません。

image.png

次に以下の3つをインストールするため、以下のコマンドを入力します。

  • discord.js
  • dotenv(.env 環境変数読み込み用のモジュール)
  • openai
terminal
$ npm install discord.js dotenv openai

次に環境変数設定用の.envファイルを作成します。

terminal
$ touch .env

これで準備は一旦完了です。現時点のディレクトリ構成が以下のようになっていれば問題ありません!

image.png

package.jsonも以下のように更新されていると思います。

package.json
{
  "name": "chat_gpt",
  "version": "1.0.0",
  "description": "",
  "main": "bot/index.js",
  "type": "module",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "discord.js": "^14.13.0",
    "dotenv": "^16.3.1",
    "openai": "^4.14.1"
  }
}

次にBot側の設定をしていきましょう!

Bot準備

Discord Developer Portalにアクセスします。(ログインしてない場合はログインしてくださいね!)

画面右上のNew Applicationをクリックします。

image.png

NAMEの欄にBot名を入力し、チェックボックスにチェックを入れてCreateをクリックします。

image.png

サイドバーのsettingsからbotをクリックし、各設定を以下のように変更しましょう。
変更後はSave Changesをお忘れなく!

image.png

次にBuild-A-BotTOKEN欄にあるReset Tokenをクリックしましょう。

image.png

Yes, do it!をクリック。

image.png

表示されたTOKENをコピーしてください。後ほど使用しますのでどこかにメモしておいてくださいね!

次にサイドバーのOAuth2 > URL Generatorをクリックします。
SCOPESBOT PERMISSIONSの設定項目があるので、以下のようにチェックを入れてください。

image.png

image.png

チェックが終わったら画面の一番下に表示されているURLをコピーしてブラウザに貼り付けましょう。
以下のような画面が表示されるので、Botを導入したいサーバーを選択してはいをクリックします。

スクリーンショット 2023-10-28 163345.png

認証をクリック。

スクリーンショット 2023-10-28 163644.png

Botがサーバーに入ったことを確認しましょう!

image.png

OpenAI APIキー取得

Open AIのAPIキー取得方法については、自身が過去に執筆した以下の記事にて紹介しています。

本記事では取得方法の詳細については割愛させて頂きます。
こちらもDiscordBotと同様、取得したAPIキーは忘れないようメモしておいてくださいね。

.envの編集

環境変数を設定します。.envに以下の記述を追加してください。

.env
TOKEN=DiscordBotのTOKEN
OPENAI_API_KEY=OpenAIのAPIキー
CHANNEL_ID=Botが返信を送信するチャンネルのID

CHANNEL_IDの確認方法ですが、導入したいテキストチャンネルを右クリックするとチャンネルIDをコピーのボタンが出てきます。

image.png

index.jsの編集

まず、特定のチャンネルにメッセージが送信された場合に、そのチャンネルにBotがそのまま返信するようにします。

index.jsに以下のコードを設定してください。

bot/index.js
import { Client, GatewayIntentBits } from 'discord.js';
import dotenv from 'dotenv';
import OpenAI from 'openai';

// dotenv適用(これで.envに記載の環境変数を使用できる)
dotenv.config()

const openai = new OpenAI({
  apiKey: process.env.OPENAI_API_KEY,
});

const client = new Client({ intents: [
  GatewayIntentBits.Guilds,
  GatewayIntentBits.MessageContent,
  GatewayIntentBits.GuildMessages,
  GatewayIntentBits.GuildPresences,
  GatewayIntentBits.GuildMembers,
] });

client.once('ready', async () => {  

  // 開始ログ
  console.log('ready!');
});

client.on('messageCreate', async (message) => {

  // メッセージの送信者がBotの場合 or 特定のチャンネル以外からのメッセージ送信の場合はreturn
  if (message.author.bot || message.channel.id != process.env.CHANNEL_ID) return;

  try {
    // 送信されたメッセージをpromptに設定
    const completion = await openai.chat.completions.create({
      messages: [{ role: 'user', content: `${message.content}`}],
      model: 'gpt-4',
    });

    if (completion.choices[0].message.content === undefined) throw new Error();
    
    // メッセージが送信されたチャンネルに、GPT-4の返信を送信
    await message.channel.send(completion.choices[0].message.content);
  } catch (err) {
    console.log(err);
  };
});

client.login(process.env.TOKEN);

コードの内容は基本的にコメント通りの内容になっています。

何せ日本語資料が少なすぎるので、ドキュメントやGitHubと睨めっこする時間が多かったな~という感じです。

注意点としては今回の記事で使用しているdiscord.jsのバージョンは14になっています。
13との大きな変更点にIntentsの書き方が変わっている所が挙げられるみたいです。 ぶっちゃけ追えてない

13まではFLAGS.GUILD_MESSAGESのような書き方だったのが、14でGatewayIntentBits.MessageContentのような書き方に変わっているようです。

ただこれから導入するのであれば基本的にv14になると思うので、あまり気にしなくても良い所かなと思います。
13→14へのバージョンアップが伴う場合は要注意ポイントになりますね。

動作確認

index.jsの編集が終わったらローカルで一度Botを動作させてみましょう。

ターミナルに以下のコマンドを入力します。

terminal
$ node bot/index.js

Botの返信先に設定したチャンネル内でメッセージを入力すると、Botから返信が返ってくることが確認できました!:clap:

スクリーンショット 2023-10-28 165724.png

スレッド返信に対応する

現段階でもChatGPTのBotとして十分な働きをしてくれているのですが、当初の想定ではこのチャンネルに参加しているメンバーなら誰でも使用できるようにしたいと考えていました。

案の定、Botを解禁した瞬間、治安がドエライことに…:innocent:

そこで各メッセージに対してスレッドを作成することで、どのメッセージに対する返信なのかを分かりやすくするようにしてみたいと考えたわけです。

これが魔境・地獄の始まりなのですが 完成版から見ていきましょう。:relaxed:
index.jsを以下のコードに書き換えてください。

bot/index.js
import { Client, GatewayIntentBits } from 'discord.js';
import dotenv from 'dotenv';
import OpenAI from 'openai';

dotenv.config()

const openai = new OpenAI({
  apiKey: process.env.OPENAI_API_KEY,
});

const client = new Client({ intents: [
  GatewayIntentBits.Guilds,
  GatewayIntentBits.MessageContent,
  GatewayIntentBits.GuildMessages,
  GatewayIntentBits.GuildPresences,
  GatewayIntentBits.GuildMembers,
] });

client.once('ready', async () => {  
  console.log('ready!');
});

client.on('messageCreate', async (message) => {

  // メッセージの送信者がBotの場合 or 特定のチャンネル以外からのメッセージ送信の場合はreturn
  if (message.author.bot || message.channel.id != process.env.CHANNEL_ID) return;

  try {
    // 送信されたメッセージをpromptに設定
    const completion = await openai.chat.completions.create({
      messages: [{ role: 'user', content: `${message.content}`}],
      model: 'gpt-4',
    });

    if (completion.choices[0].message.content === undefined) throw new Error();

+   const thread = await message.startThread({
+     name: message.content,
+     autoArchiveDuration: 60,
+   })

+   await thread.send(completion.choices[0].message.content);
-   await message.channel.send(completion.choices[0].message.content);
    
  } catch (err) {
    console.log(err);
  };
});

client.login(process.env.TOKEN);

該当箇所にコードを追加・削除して再度Botを起動すると、以下のように送信したメッセージに対してスレッドを作成し、スレッド内でBotが返信していることを確認できると思います!:raised_hands:

スクリーンショット 2023-10-28 173344.png

解説

追加を行った箇所について補足します。

    const thread = await message.startThread({
      ...
    })

ThreadsMessage#startThread()は既存メッセージに対してスレッドを作成することができます。
discordのスレッド作成方法は主に以下の2点です。

  • チャンネルにスレッドを新規作成する
  • メッセージに対してスレッドを新規作成する

今回は送信されたメッセージに対してスレッドを作成したかったため、Message#startThread()を使用しました。

      name: message.content,
      autoArchiveDuration: 60,

name:はスレッドのタイトルを設定することができます。今回は送信されたメッセージをタイトルに設定するようにしました。

autoArchiveDuration: は自動アーカイブを行う時間を設定します。

その他詳しい内容についてはdiscord.js Guideに細かい記載があるので、ご興味ある方はご覧になられることをおススメします。

このスレッド返信ですが、本当に魔境で日本語資料が少なすぎてしんどかった…。ツライ。
公式ドキュメント(英語)って正義なんやなって。:thinking:

fly.ioにデプロイして継続運用する

ちなみにここまで作成したBotですが、現状段階ではローカルで起動している間のみBotが動作するため、サーバーを停止してしまうとBotも一緒に停止してしまいます。

そのためBotを継続運用するためにはデプロイが必要になるんですね。

Slack Botの際はGASでデプロイするだけで動作したのですが、DiscordBotはGASでは自動返信を行うことができないんですね…。面倒だな…。

なるべくお金を掛けたくない乞食マンなので、今herokuの代替先としてアツいfly.ioを今回採用しました。:fire:

fly.ioの個人的に良いポイントは、CLIで基本的にデプロイまで完結してしまうという点と東京リージョンが標準搭載されている点です。いいねえ。:sunglasses:

まずflyctlをインストールします。

terminal
$ curl -L https://fly.io/install.sh | sh

OSによってインストールコマンドが変わるため、以下のページでご自身のOSに沿ったコマンドを入力してください。

次にサインアップ・サインインを行います。自動で画面が開くので、案内に沿って進めていってください。

terminal
$ flyctl auth signup
$ flyctl auth login

ログインが完了したら以下のコマンドを入力し、質問内容に沿ってコマンドを使用して回答していきます。

fly.launchは対話形式でデプロイを自動化するためのコマンドです。

terminal
$ fly launch

...

# アプリケーション名を入力(お好きな名前でOK!)
? Choose an app name (leave blank to generate one): keichan-gpt-bot

...

# リージョンを選択(東京リージョンでOK)
? Choose a region for deployment: Tokyo, Japan (nrt)
App will use 'nrt' region as primary

...

Created app 'keichan-gpt-bot' in organization 'personal'

...

If you need custom packages installed, or have problems with your deployment
build, you may need to edit the Dockerfile for app-specific changes. If you
need help, please post on https://community.fly.io.

Now: run 'fly deploy' to deploy your Node.js app.

Now: run 'fly deploy' to deploy your Node.js app. の表示が出ていればOKです。
ちなみに実行後のディレクトリを確認すると、以下の3つのファイルが追加されていると思います。

  • .dockerignore
  • Dockerfile
  • fly.toml

image.png

次にfly.tomlを開き、以下のように編集します。

デフォルトではhttp serviceを使用するようになっているため、今回のBot用に変更を加える必要があります。

fly.toml
# fly.toml app configuration file generated for keichan-gpt-bot on 2023-10-28T18:20:20+09:00
#
# See https://fly.io/docs/reference/configuration/ for information about how to use this file.
#

app = "keichan-gpt-bot"
+ primary_region = "nrt"
+ kill_signal = "SIGINT"
+ kill_timeout = 5
+ processes = []

+ [env]

- primary_region = "nrt"

- [build]

- [http_service]
-  internal_port = 3000
-  force_https = true
-  auto_stop_machines = true
-  auto_start_machines = true
-  min_machines_running = 0
-  processes = ["app"]

最後に環境変数を設定します。fly.ioは環境変数の登録がCLIのみでしかできません。

terminal
$ flyctl secrets set TOKEN=DiscordBotのTOKEN OPENAI_API_KEY=OpenAIのAPIキー CHANNEL_ID=Botが返信を送信するチャンネルのID

環境変数が登録できているかは以下のコマンドで確認できます。

terminal
$ flyctl secrets list
NAME            DIGEST                  CREATED AT
CHANNEL_ID      7e98152c44c44309        21s ago
OPENAI_API_KEY  6080ebcf755537aa        21s ago
TOKEN           5eee98f021cbafb8        21s ago

最後にデプロイをしましょう。以下コマンドを入力します。

terminal
$ fly deploy
...

No machines in group app, launching a new machine
Creating a standby machine for 32874ddec54d78
Finished launching new machines
-------
 ✔ Machine 91857559c53178 [app] was created
-------

上記のような表記が出ていればデプロイは成功です!

ダッシュボードにアクセスするとデプロイしたbotの名前が表示されていることが確認できます。

image.png

デプロイが問題無く成功しているかはMonitoringの画面で確認しましょう。

デプロイ後にアプリケーションが上手く動作しない場合は、このMonitoringを確認することでエラーの特定につながります。

image.png

参考

おわりに

いかがでしたでしょうか。
今回はdiscord.jsとChatGPT APIを使用したDiscordBotを作成し、fly.ioを使用してデプロイまで行いました。

プログラミングを学ぶ上で個人的におススメなのは、こういうしょうもないおもちゃを興味本位で作り始めるのが一番良いのかなと思います。何事も興味が無いとやる気って出ないですし。:zipper_mouth:

そういえばそろそろQiita Advent Calendar 2023の季節がやってきますね。楽しみ~~。

ではまた!:wave:

5
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
3