初めに
こんにちは、のいたみなです。
今回は今まで使っていたbotを動かしていたGlitchがサ終したのでRenderで作り直してみました。
discord botの登録、renderへのログインについては割愛します。
今回すること(目次)
renderプロジェクトの作成
コマンド機能を作る
コマンドの再読み込み
githubのリポジトリの作成
GitHubのリポジトリを作成します。
ログインしたらここを押して、

適当な名前を付けて作成します。

ファイルの新規作成をクリックして、、、

dockerfileを作成します。
FROM node:22
WORKDIR ./
COPY package*.json ./
RUN npm install
COPY .
EXPOSE 3000
CMD ["npm", "start"]
CommitChanges...→Commit changesをクリックしてファイルを保存します。

今後はこの方法でファイルを作成します。
おなじディレクトリにpackage.jsonを作成します。
{
"name": "nodejs",
"version": "1.0.0",
"description": "",
"type": "module",
"scripts": {
"start": "node ./index.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"@types/node": "^22.13.11",
"discord.js": "^14.25.1",
"dotenv": "^17.2.3",
"express": "^5.2.1",
"nodejs": "file:"
}
}
Renderプロジェクトの作成
Renderを開いて、GitHubアカウントでログインします。
ログイン出来たら、事前に適当なProject NameとEnvironmant nameを設定してください。

Githubからデプロイします。

先ほど作成したリポジトリを選択して、

ランタイムをFreeに設定します。

スクリーンショットには映っていませんが、Start Commandには
npm run start
を設定してください。
Add Environment Variableをクリックして KeyにTOKEN, ValueにDiscord botのトークンを入力して、 Add Secretをクリックします。

同じようにApplication IDも、
Key:ApplicationID
Value:BotのApplicationID
を設定してください。
これで環境変数の設定は終わりです。
TokenはDiscord devのBOTから、
Application IDは同じくDiscord devのGeneral Informationから確認できます。
あとはDeploy web serviceをクリックして作成してください。
index.jsに次のコードを書き込んでください。
import { Client, GatewayIntentBits, Events } from "discord.js";
import express from "express";
var client = new Client({
intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages],
});
client.login(process.env.TOKEN);
client.on("clientReady", () => {
console.log(`サーバーが起動しました!!`);
});
var app = express();
app.get("/", (req, res) => {
res.send(`ok`);
});
var port = 3000;
app.listen(port, () => {
console.log(`Good morning!!`);
});
解説
import { Client, GatewayIntentBits, Events } from "discord.js";
import express from "express";
//必要なファイルのインポート
var client = new Client({
intents: [GatewayIntentBits.Guilds/*Gulid(サーバーの情報)を読み書きする権限*/, GatewayIntentBits.GuildMessages/*Guildのメッセージを読み書きする権限*/],
});
client.login(process.env.TOKEN/*環境変数TOKENを取得*/);
client.on("clientReady", () => {//サーバーが起動したとき
console.log(`サーバーが起動しました!!`);
});
var app = express();
app.get("/", (req, res) => {//httpリクエストのルートを作成
res.send(`ok`);
});
var port = 3000;
app.listen(port, () => {//ポート3000にリクエストが来たとき
console.log(`Good morning!!`);
});
githubのコミットを自動で検知してデプロイも自動で開始します。
サーバーが起動しました!!の文字が表示されていればokです。

コマンド機能を作る
まずはindex.jsのトップに次のコードを書き足します。
+ import fs from "fs";
+ import path from "path";
import { Client, GatewayIntentBits } from "discord.js";
~~~
そして、どこでもいいのでわかりやすい名前のjsファイルを作成します。
ここでは /commands/aisatu.jsとしました。
const { SlashCommandBuilder } = await import("discord.js");
export default {
data: new SlashCommandBuilder()
.setName("aisatu")
.setDescription("あいさつに反応してbotが返事します。"),
execute: async function (interaction) {
await interaction.reply("こんにちは~☆");
},
};
解説
const { SlashCommandBuilder } = await import("discord.js");
export default {//jsファイルをインポートしてそのまま関数を使えるように設定
data: new SlashCommandBuilder()
.setName("aisatu")//コマンド名
.setDescription("あいさつに反応してbotが返事します。"),//コマンドの説明
execute: async/*await(実行完了まで待つ)を使いますよ~*/ function (interaction) {//関数の中身
await interaction.reply("こんにちは~☆");//コマンドに返信します。
},
};
保存したらindex.jsに戻って上の方に先ほど作成したjsファイルを読み込むコードを追加します。
~~~
import express from "express";
+ import aisatu from "./commands/aisatu.js";
~~~
そしてテキストが送信されたとき、認識できるようにするため最下部へ次のコードを追加します。
client.on(Events.InteractionCreate, async interaction => {
if (!interaction.isChatInputCommand()) return;
if (interaction.commandName == aisatu.data.name) {
try {
await aisatu.execute(interaction);
} catch (error) {
console.error(error);
await interaction.reply({content: 'コマンド実行時にエラーになりました。',ephemeral:true});
}
} else {
await interaction.reply(`不明なコマンドが実行されました。`)
}
});
解説
client.on(Events.InteractionCreate, async interaction => {//投稿があった時…
if (!interaction.isChatInputCommand()) return;//投稿がコマンドでないならスキップ
if (interaction.commandName == aisatu.data.name) {//コマンド名がaisatu.data.neme(aisatu)だったら…
try {//とりあえず実行
await aisatu.execute(interaction);//定義した関数に投稿の中身を渡して実行
} catch (error) {//エラーが出たら…
console.error(error);
await interaction.reply({content: 'コマンド実行時にエラーになりました。',ephemeral:true});
}
} else {
await interaction.reply(`不明なコマンドが実行されました。`)
}
});
これでコマンド機能は完成です。
コマンドの再読み込み
コマンドを実際に使用するためにはコマンドを再読み込みさせなければいけません。
index.jsを同じディレクトリにupdate-commands.jsを作成してコードを書いてください。
import { REST, Routes } from "discord.js";
import fs from "fs";
import path from "path";
import "dotenv/config";
const commands = [];
const foldersPath = path.join(process.cwd(), "commands");
const commandFiles = fs
.readdirSync(foldersPath)
.filter((file) => file.endsWith(".js") || file.endsWith(".mjs"));
export default async () => {
for (const file of commandFiles) {
const filePath = path.join(foldersPath, file);
const module = await import(`file://${filePath}`);
const command = module.default || module;
if (command.data) {
commands.push(command.data.toJSON());
}
}
const rest = new REST().setToken(process.env.TOKEN);
try {
console.log(
`[INIT] ${commands.length}つのスラッシュコマンドを更新します。`,
);
await rest.put(Routes.applicationCommands(process.env.ApplicationID), {
body: commands,
});
console.log(
`[INIT] ${commands.length}つのスラッシュコマンドを更新しました。`,
);
} catch (error) {
console.error(error);
}
};
解説
import { REST, Routes } from "discord.js";
import fs from "fs";
import path from "path";
import "dotenv/config";
const commands = [];
const foldersPath = path.join(process.cwd(), "commands");//今のディレクトリにcommandsを書き足します
const commandFiles = fs
.readdirSync(foldersPath)//commandsフォルダ内の、
.filter((file) => file.endsWith(".js") || file.endsWith(".mjs"));//.jsと.mjsファイルを読み込みます。
export default async () => {
for (const file of commandFiles) {//読み込んだファイルを一つずつfile変数に入れて…
const filePath = path.join(foldersPath, file);//絶対パスに変換します。
const module = await import(`file://${filePath}`);//それをimportします。
const command = module.default || module;
if (command.data) {//有効なファイルだったら…
commands.push(command.data.toJSON());//配列commandにjsonとして挿入します。
}
}//繰り返し~
const rest = new REST().setToken(process.env.TOKEN);
try {
console.log(
`[INIT] ${commands.length}つのスラッシュコマンドを更新します。`,
);
await rest.put(Routes.applicationCommands(process.env.ApplicationID), {
body: commands,
});//コマンドの再読み込みをコマンドの一覧と説明とともにやらせます
console.log(
`[INIT] ${commands.length}つのスラッシュコマンドを更新しました。`,
);
} catch (error) {
console.error(error);//エラーだお
}
};
そしてindex.jsに追記します。
~~~
import * as aisatu from "./commands/aisatu.js";
+ import UpdateCommands from "./update-commands.js";
+ UpdateCommands();
~~~
~~~
client.on("clientReady", () => {
console.log(`サーバーが起動しました!!`);
+ console.log(`招待URL:https://discord.com/api/oauth2/authorize?client_id=${process.env.ApplicationID}&permissions=8&scope=applications.commands+bot`)
});
~~~
実行してみる
きちんと動いているみたいですね!!
実行したままdiscordへ移動します。
招待URLから招待できます。
きちんと返ってきました!
応答速度も遅くはありませんでした。
終わりに
かなり駆け足でしたがスラッシュコマンド実装までは完了しました。
次回はコマンドの拡張と通常のメッセージに対しての返信機能などを作っていきます。
前回:なし
次回:なし

