僕が運営しているスキーチーム用のbotをdiscord.js V14で作ったので、その概要と作成方法を共有。
前提
チームの入会フォーム(Google Form)が提出されると、discordサーバーへの招待リンクがメールで届くようになっている。discordサーバーは大まかにコーチ&現役
フォルダーと現役
フォルダーに分かれ、セキュリティーの観点から、○期
ロールが付与されるまでの間は前者しか閲覧できないようになっている。discordのユーザーネームは本名と無関係に設定している人が多いので、僕がこんな感じでやっていた:
新入生が
コーチ&現役
フォルダー内にある自己紹介チャンネルに投稿 → 管理者が個人情報シート(入会フォームの回答をまとめたspreadsheet)を見て何期生かを確認 → 新入生のニックネームを本名(○期)
に変更 &○期
ロールを付与 → 個人情報シートに新入生のdiscord IDを入力
5分もあれば終わる処理だけど、まぁまぁ面倒くさいので、勉強を兼ねてbotを作ってみることにした。
目標
- 上記フローを自動化する。
- 新メンバーと管理者のみが閲覧できる
新入生ウェルカムチャンネル
を作り、いわゆる ようこそ画面 の代わりとする。新たにサーバーに入った新入生は、まずこのチャンネルと自己紹介
チャンネルのみ閲覧できるようになっており、新入生ウェルカムチャンネル
に自分の名前を入力すると他のチャンネルも閲覧できるようになる。その後、ウェルカムチャンネルはアクセス出来なくなる。
実行環境
- マシン:Ventura 13.4 のMac Mini
- エディター:VS Code
- インストールが必要なパッケージ
- Node.js:今回はv18.15.0を使った
- discord.js:v14を推奨
- dotenv
- axios
- nodemon
コード
全体構成
今回の機能に関係するものを抜き出した。(こういうツリー構造をスムーズに書くにはどうすれば良いんでしょう。ターミナルでtree
コマンドを打ち、その出力を<pre>
タグ内に貼り付けているけど、そのままだと表示がズレる。)
discord_bot ├── .env ├── .gitignore ├── events │ ├── guildMemberAdd.js │ ├── messageCreate.js │ └── ready.js ├── index.js ├── newMember │ ├── indexForNewMember.js │ └── modifyDiscord.js └── node_modules
全体の流れは次の通り:
サーバーに新メンバーが入る
↪︎(1) guildMemberAdd.js が実行され、NewMember
ロールを付与する
↪︎新メンバーが新入生ウェルカムチャンネル
に名前を送信する
↪︎(2) messageCreate.js が実行され、データのチェック, discordの各種設定(ニックネームの変更, ロールの変更など), 個人情報シートの更新を行う。
↪︎新メンバーが他のチャンネルを閲覧できるようになり、新入生ウェルカムチャンネル
へのアクセスは失う。
各機能の解説
(0)根幹ファイル
nodemonで常時動かしているのはindex.js。なお、セキュリティー上公開できない情報(トークン, サーバーIDなど)は全て.envファイルに保存してある。それを別ファイルで用いたいときは、process.env.TOKEN
のように書く。
require('dotenv').config() const { Client, Collection, Events, IntentsBitField } = require("discord.js") const fs = require('node:fs') const path = require('node:path') //今回のbotに必要な権限を与える const client = new Client({ intents: [ IntentsBitField.Flags.Guilds, IntentsBitField.Flags.GuildMembers, IntentsBitField.Flags.GuildMessages, IntentsBitField.Flags.MessageContent, ] }) //各種イベントはeventsフォルダーに格納しているので、それを全て読み込む const eventsPath = path.join(__dirname, 'events') const eventFiles = fs.readdirSync(eventsPath).filter(file => file.endsWith('.js')) //readyイベントはbotの起動時のみ、その他のイベントには常時反応できるようにする for (const file of eventFiles){ const filePath = path.join(eventsPath, file) const event = require(filePath) if(event.once){ client.once(event.name, (...args) => event.excute(...args)) }else{ client.on(event.name, (...args) => event.excute(...args)) } } //botを起動 client.login(process.env.TOKEN)
(1)guildMemberAddイベント
サーバーに新メンバーが入ると、以下のコードが実行される。NewMember
ロールのIDは書き換えてください。
guildMemberAdd.js
const {Events} = require('discord.js') module.exports = { name: Events.GuildMemberAdd, async excute(member){ //臨時のロールNewMemberを付与する。 await member.roles.add('1116738049548755044') } }
(2)messageCreateイベント
サーバー内のいずれかのチャンネルにメッセージが送信されると、以下のコードが実行される。そのチャンネルが新入生ウェルカムチャンネル
であり、送信者がbot以外のときのみ、indexForNewMember.js ファイルに処理を引き渡す。
messageCreate.js
const {Events} = require('discord.js') module.exports = { name: Events.MessageCreate, async excute(message){ //新入生ウェルカムチャンネルにのみ反応する if(message.channelId==process.env.WELCOME_CHANNEL_ID && !message.author.bot){ const indexForNewMember = require('../newMember/indexForNewMember.js') indexForNewMember(message) } } }
indexForNewMember.js ファイルは以下の動作をまとめたもので、①の動作は同ディレクトリーの modifyDiscord.js ファイルで行っている。
- 入力された名前が個人情報シートに記録されているかをチェック
- ①新入生のロールとニックネームを変更する
- 個人情報シートに新入生のdiscord IDを追記する
新入生ウェルカムチャンネル
の不要な投稿を削除する
indexForNewMember.js
const {Events} = require('discord.js') const axios = require('axios') module.exports = async (message)=>{ const name = message.content const requestToPersonalInfoSheet = await axios.get(`${process.env.PERSONAL_INFO_SHEET}/search?sheet=名簿&氏名=${name}`) const record = requestToPersonalInfoSheet.data if(record.length===0){ message.reply(`❌ ${name}さんのデータは個人情報シートに記録されていません。表記を確認して、もう一度お試しください。`) }else{ const memberID = message.author.id const gen = record[0]['代'] //integer //新入生のDiscord設定を変更(ロールの変更, ニックネームの変更)。 const modifyDiscord = require('./modifyDiscord.js') await modifyDiscord(message, name, gen) //個人情報シートを更新 const discordInfoSheet = axios.post(`${process.env.PERSONAL_INFO_SHEET}?sheet=discord`, {名前: name, 代: gen, ID: memberID}) //ウェルカムchの不要なメッセージを削除する const messagesToDelete = await message.channel.messages.fetch({after: '1116774827383066684'}) message.channel.bulkDelete(messagesToDelete) } }
①modifyDiscord.js
const {Events} = require('discord.js') module.exports = async (message, name, gen) => { //gen期のロールを付与する。ToDo管理botロールのランクを○期より上に設定しておく必要がある。 const role = message.guild.roles.cache.find(role => role.name===`${gen}期`) if (!role) { console.log(`${gen}期ロールが存在しません。`); return; } if(!message.member.roles.cache.has(role.id)){ try { await message.member.roles.add(role) } catch (error) { console.log(`Error adding role to new member: ${error}`) } } //ニックネームを変更。これもToDo管理botより高いロールが既に付与されている人に対してはエラーを吐く。 try { await message.member.setNickname(`${name}(${gen}期)`) } catch (error) { console.log(`Unable to change the nickname. Error: ${error}`) } await message.reply(`${name}さんのDiscord設定が完了しました。他のチャンネルも覗いてみてください!`) //NewMemberロールを削除 await message.member.roles.remove('1116738049548755044') }
ソースコード
GitHubにアップロード済み。元々はdiscordサーバーで完結するtodo管理機能を作ろうと思っていたので、discord_todo_bot
というフォルダー名になっている。この記事に書かれていないGitHub上のコードは、大体todo機能関連。
クラウドサーバーで常時起動
別の記事を参照。