diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..b58b603 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,5 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/.idea/discord_read_bot.iml b/.idea/discord_read_bot.iml new file mode 100644 index 0000000..24643cc --- /dev/null +++ b/.idea/discord_read_bot.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..83a7844 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/config/development._env b/config/development._env index 8c9d5b5..fd3b08d 100644 --- a/config/development._env +++ b/config/development._env @@ -1,3 +1,4 @@ NODE_ENV=development LOG_LEVEL=debug BOT_TOKEN=YOUR_DEVELOPMENT_BOT_TOKEN +APPLICATION_ID=YOUR_DEVELOPMENT_APPLICATION_ID diff --git a/config/production._env b/config/production._env index 32228dc..578c07b 100644 --- a/config/production._env +++ b/config/production._env @@ -1,3 +1,4 @@ NODE_ENV=production LOG_LEVEL=error BOT_TOKEN=YOUR_PRODUCTION_BOT_TOKEN +APPLICATION_ID=YOUR_PRODUCTION_APPLICATION_ID diff --git a/package-lock.json b/package-lock.json index 6817c0b..f336124 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,9 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "discord.js": "^14.16.3" + "@discordjs/voice": "^0.17.0", + "discord.js": "^14.16.3", + "dotenv": "^16.4.5" }, "devDependencies": { "nodemon": "^3.1.7" @@ -125,6 +127,31 @@ "url": "https://github.com/discordjs/discord.js?sponsor" } }, + "node_modules/@discordjs/voice": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@discordjs/voice/-/voice-0.17.0.tgz", + "integrity": "sha512-hArn9FF5ZYi1IkxdJEVnJi+OxlwLV0NJYWpKXsmNOojtGtAZHxmsELA+MZlu2KW1F/K1/nt7lFOfcMXNYweq9w==", + "license": "Apache-2.0", + "dependencies": { + "@types/ws": "^8.5.10", + "discord-api-types": "0.37.83", + "prism-media": "^1.3.5", + "tslib": "^2.6.2", + "ws": "^8.16.0" + }, + "engines": { + "node": ">=16.11.0" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" + } + }, + "node_modules/@discordjs/voice/node_modules/discord-api-types": { + "version": "0.37.83", + "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.83.tgz", + "integrity": "sha512-urGGYeWtWNYMKnYlZnOnDHm8fVRffQs3U0SpE8RHeiuLKb/u92APS8HoQnPTFbnXmY1vVnXjXO4dOxcAn3J+DA==", + "license": "MIT" + }, "node_modules/@discordjs/ws": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@discordjs/ws/-/ws-1.1.1.tgz", @@ -367,6 +394,18 @@ "url": "https://github.com/discordjs/discord.js?sponsor" } }, + "node_modules/dotenv": { + "version": "16.4.5", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", + "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -567,6 +606,32 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/prism-media": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/prism-media/-/prism-media-1.3.5.tgz", + "integrity": "sha512-IQdl0Q01m4LrkN1EGIE9lphov5Hy7WWlH6ulf5QdGePLlPas9p2mhgddTEHrlaXYjjFToM1/rWuwF37VF4taaA==", + "license": "Apache-2.0", + "peerDependencies": { + "@discordjs/opus": ">=0.8.0 <1.0.0", + "ffmpeg-static": "^5.0.2 || ^4.2.7 || ^3.0.0 || ^2.4.0", + "node-opus": "^0.3.3", + "opusscript": "^0.0.8" + }, + "peerDependenciesMeta": { + "@discordjs/opus": { + "optional": true + }, + "ffmpeg-static": { + "optional": true + }, + "node-opus": { + "optional": true + }, + "opusscript": { + "optional": true + } + } + }, "node_modules/pstree.remy": { "version": "1.1.8", "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", diff --git a/package.json b/package.json index a9cf89e..78e482d 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,9 @@ "author": "", "license": "ISC", "dependencies": { - "discord.js": "^14.16.3" + "@discordjs/voice": "^0.17.0", + "discord.js": "^14.16.3", + "dotenv": "^16.4.5" }, "devDependencies": { "nodemon": "^3.1.7" diff --git a/src/commands/disconnect.js b/src/commands/disconnect.js new file mode 100644 index 0000000..954261f --- /dev/null +++ b/src/commands/disconnect.js @@ -0,0 +1,17 @@ +const { SlashCommandBuilder } = require('discord.js'); +const { getVoiceConnection } = require('@discordjs/voice'); + +module.exports = { + data: new SlashCommandBuilder() + .setName('disconnect') + .setDescription('Disconnect from the voice channel'), + async execute(interaction) { + const connection = getVoiceConnection(interaction.guild.id); + if (connection) { + connection.destroy(); + await interaction.reply('ボイスチャンネルから切断しました。'); + } else { + await interaction.reply('ボイスチャンネルに接続していません。'); + } + }, +}; diff --git a/src/commands/join.js b/src/commands/join.js new file mode 100644 index 0000000..58b1ba2 --- /dev/null +++ b/src/commands/join.js @@ -0,0 +1,23 @@ +const { SlashCommandBuilder } = require('discord.js'); +const { joinVoiceChannel } = require('@discordjs/voice'); + +module.exports = { + data: new SlashCommandBuilder() + .setName('join') + .setDescription('Join the voice channel you are in'), + async execute(interaction) { + const voiceChannel = interaction.member.voice.channel; + + if (!voiceChannel) { + return interaction.reply('ボイスチャンネルに接続されていません。'); + } + + joinVoiceChannel({ + channelId: voiceChannel.id, + guildId: voiceChannel.guild.id, + adapterCreator: voiceChannel.guild.voiceAdapterCreator, + }); + + await interaction.reply('ボイスチャンネルに接続しました。'); + }, +}; diff --git a/src/events/messageCreate.js b/src/events/messageCreate.js new file mode 100644 index 0000000..202da56 --- /dev/null +++ b/src/events/messageCreate.js @@ -0,0 +1,11 @@ +module.exports = { + name: 'messageCreate', + execute(message) { + if (message.author.bot) return; + + if (message.mentions.has(message.client.user)) { + const response = message.content.replace(`<@${message.client.user.id}>`, '').trim(); + message.channel.send(response || "メッセージを受け取りました!"); + } + }, +}; diff --git a/src/events/ready.js b/src/events/ready.js new file mode 100644 index 0000000..23b32fb --- /dev/null +++ b/src/events/ready.js @@ -0,0 +1,7 @@ +module.exports = { + name: 'ready', + once: true, + execute(client) { + console.log(`Logged in as ${client.user.tag}!`); + }, +}; diff --git a/src/index.js b/src/index.js index 62e110b..8d3de4d 100644 --- a/src/index.js +++ b/src/index.js @@ -1,22 +1,73 @@ -const { Client, GatewayIntentBits } = require('discord.js'); -const client = new Client({ intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages, GatewayIntentBits.MessageContent] }); +require('dotenv').config(); +const { Client, GatewayIntentBits, Collection, REST, Routes } = require('discord.js'); +const fs = require('fs'); +const path = require('path'); -// ボットが起動したときのメッセージ -client.once('ready', () => { - console.log(`Logged in as ${client.user.tag}!`); +const client = new Client({ + intents: [ + GatewayIntentBits.Guilds, + GatewayIntentBits.GuildMessages, + GatewayIntentBits.MessageContent, + GatewayIntentBits.GuildVoiceStates + ] }); -// メッセージが送信されたときに呼び出されるイベント -client.on('messageCreate', message => { - // 自分のメッセージやBotからのメッセージには反応しない - if (message.author.bot) return; +// コマンドの読み込み +client.commands = new Collection(); +const commands = []; +const commandsPath = path.join(__dirname, 'commands'); +const commandFiles = fs.readdirSync(commandsPath).filter(file => file.endsWith('.js')); - // ボットがメンションされた場合に反応 - if (message.mentions.has(client.user)) { - const response = message.content.replace(`<@${client.user.id}>`, '').trim(); +for (const file of commandFiles) { + const filePath = path.join(commandsPath, file); + const command = require(filePath); + client.commands.set(command.data.name, command); + commands.push(command.data.toJSON()); +} - message.channel.send(response || "メッセージを受け取りました!"); +const rest = new REST({ version: '10' }).setToken(process.env.BOT_TOKEN); + +// スラッシュコマンドのグローバル登録 +(async () => { + try { + console.log('Registering global slash commands...'); + await rest.put( + Routes.applicationCommands(process.env.APPLICATION_ID), + { body: commands }, + ); + console.log('Global slash commands registered successfully.'); + } catch (error) { + console.error('Error registering global slash commands:', error); + } +})(); + +// インタラクションの処理 +client.on('interactionCreate', async (interaction) => { + if (!interaction.isCommand()) return; + + const command = client.commands.get(interaction.commandName); + if (!command) return; + + try { + await command.execute(interaction); + } catch (error) { + console.error(error); + await interaction.reply({ content: 'コマンド実行中にエラーが発生しました。', ephemeral: true }); } }); +// イベントの読み込み +const eventsPath = path.join(__dirname, 'events'); +const eventFiles = fs.readdirSync(eventsPath).filter(file => file.endsWith('.js')); + +for (const file of eventFiles) { + const filePath = path.join(eventsPath, file); + const event = require(filePath); + if (event.once) { + client.once(event.name, (...args) => event.execute(...args, client)); + } else { + client.on(event.name, (...args) => event.execute(...args, client)); + } +} + client.login(process.env.BOT_TOKEN);