mirror of
https://github.com/aydenjahola/discord-multipurpose-bot.git
synced 2025-09-21 06:41:35 +01:00
add simple music functionality
This commit is contained in:
parent
51dcdc7406
commit
f0a936690b
7 changed files with 140 additions and 36 deletions
11
Dockerfile
11
Dockerfile
|
@ -1,5 +1,16 @@
|
|||
FROM jrottenberg/ffmpeg:8-alpine AS ffmpeg
|
||||
|
||||
FROM node:current-alpine
|
||||
|
||||
COPY --from=ffmpeg /usr/local/bin/ffmpeg /usr/local/bin/
|
||||
COPY --from=ffmpeg /usr/local/bin/ffprobe /usr/local/bin/
|
||||
|
||||
RUN apk add --no-cache \
|
||||
python3 \
|
||||
make \
|
||||
g++ \
|
||||
&& npm install -g npm@latest
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
COPY package*.json ./
|
||||
|
|
31
commands/music/play.js
Normal file
31
commands/music/play.js
Normal file
|
@ -0,0 +1,31 @@
|
|||
const { SlashCommandBuilder } = require("discord.js");
|
||||
|
||||
module.exports = {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName("play")
|
||||
.setDescription("Plays a song from YouTube, Spotify, or SoundCloud.")
|
||||
.addStringOption((option) =>
|
||||
option
|
||||
.setName("query")
|
||||
.setDescription("Song name or URL")
|
||||
.setRequired(true)
|
||||
),
|
||||
async execute(interaction, client) {
|
||||
await interaction.deferReply();
|
||||
const query = interaction.options.getString("query");
|
||||
const voiceChannel = interaction.member.voice.channel;
|
||||
if (!voiceChannel) {
|
||||
return interaction.followUp("❌ You need to be in a voice channel!");
|
||||
}
|
||||
try {
|
||||
await client.distube.play(voiceChannel, query, {
|
||||
textChannel: interaction.channel,
|
||||
member: interaction.member,
|
||||
});
|
||||
await interaction.followUp(`🔍 Searching: \`${query}\``);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
interaction.followUp("❌ Failed to play the song.");
|
||||
}
|
||||
},
|
||||
};
|
18
commands/music/queue.js
Normal file
18
commands/music/queue.js
Normal file
|
@ -0,0 +1,18 @@
|
|||
const { SlashCommandBuilder } = require("discord.js");
|
||||
|
||||
module.exports = {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName("queue")
|
||||
.setDescription("Shows the current music queue."),
|
||||
async execute(interaction, client) {
|
||||
const queue = client.distube.getQueue(interaction.guildId);
|
||||
if (!queue || queue.songs.length === 0) {
|
||||
return interaction.reply("❌ The queue is empty!");
|
||||
}
|
||||
const tracks = queue.songs.map(
|
||||
(song, index) =>
|
||||
`${index + 1}. ${song.name} - \`${song.formattedDuration}\``
|
||||
);
|
||||
interaction.reply(`**Current Queue:**\n${tracks.join("\n")}`);
|
||||
},
|
||||
};
|
17
commands/music/skip.js
Normal file
17
commands/music/skip.js
Normal file
|
@ -0,0 +1,17 @@
|
|||
const { SlashCommandBuilder } = require("discord.js");
|
||||
|
||||
module.exports = {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName("skip")
|
||||
.setDescription("Skips the current song."),
|
||||
async execute(interaction, client) {
|
||||
const queue = client.distube.getQueue(interaction.guildId);
|
||||
if (!queue) return interaction.reply("❌ No songs in queue!");
|
||||
try {
|
||||
await queue.skip();
|
||||
interaction.reply("⏭️ Skipped the current song!");
|
||||
} catch (error) {
|
||||
interaction.reply("❌ Failed to skip.");
|
||||
}
|
||||
},
|
||||
};
|
17
commands/music/stop.js
Normal file
17
commands/music/stop.js
Normal file
|
@ -0,0 +1,17 @@
|
|||
const { SlashCommandBuilder } = require("discord.js");
|
||||
|
||||
module.exports = {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName("stop")
|
||||
.setDescription("Stops the music and clears the queue."),
|
||||
async execute(interaction, client) {
|
||||
const queue = client.distube.getQueue(interaction.guildId);
|
||||
if (!queue) return interaction.reply("❌ No music is playing!");
|
||||
try {
|
||||
await client.distube.stop(interaction.guildId);
|
||||
interaction.reply("🛑 Stopped the player and cleared the queue!");
|
||||
} catch (error) {
|
||||
interaction.reply("❌ Failed to stop.");
|
||||
}
|
||||
},
|
||||
};
|
74
index.js
74
index.js
|
@ -7,6 +7,9 @@ const {
|
|||
Routes,
|
||||
PresenceUpdateStatus,
|
||||
} = require("discord.js");
|
||||
const { DisTube } = require("distube");
|
||||
const { SpotifyPlugin } = require("@distube/spotify");
|
||||
const { SoundCloudPlugin } = require("@distube/soundcloud");
|
||||
const mongoose = require("mongoose");
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
|
@ -19,41 +22,62 @@ const client = new Client({
|
|||
GatewayIntentBits.Guilds,
|
||||
GatewayIntentBits.GuildMessages,
|
||||
GatewayIntentBits.MessageContent,
|
||||
GatewayIntentBits.GuildVoiceStates,
|
||||
],
|
||||
});
|
||||
|
||||
client.commands = new Collection();
|
||||
|
||||
// Initialize DisTube
|
||||
client.distube = new DisTube(client, {
|
||||
emitNewSongOnly: true,
|
||||
plugins: [new SpotifyPlugin(), new SoundCloudPlugin()],
|
||||
});
|
||||
|
||||
// DisTube event listeners
|
||||
client.distube
|
||||
.on("playSong", (queue, song) => {
|
||||
queue.textChannel.send(
|
||||
`🎶 Playing: **${song.name}** - \`${song.formattedDuration}\``
|
||||
);
|
||||
})
|
||||
.on("addSong", (queue, song) => {
|
||||
queue.textChannel.send(
|
||||
`✅ Added: **${song.name}** - \`${song.formattedDuration}\``
|
||||
);
|
||||
})
|
||||
.on("error", (channel, error) => {
|
||||
console.error("DisTube error:", error);
|
||||
channel.send("❌ An error occurred: " + error.message);
|
||||
})
|
||||
.on("finish", (queue) => {
|
||||
queue.textChannel.send("🎵 Queue finished!");
|
||||
});
|
||||
|
||||
// Function to recursively read commands from subdirectories
|
||||
function loadCommands(dir) {
|
||||
const files = fs.readdirSync(dir);
|
||||
|
||||
for (const file of files) {
|
||||
const filePath = path.join(dir, file);
|
||||
|
||||
if (fs.statSync(filePath).isDirectory()) {
|
||||
// If it's a directory, recurse into it
|
||||
loadCommands(filePath);
|
||||
} else if (file.endsWith(".js")) {
|
||||
// If it's a JavaScript file, load the command
|
||||
const command = require(filePath);
|
||||
client.commands.set(command.data.name, command);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Load all commands from the commands directory and its subdirectories
|
||||
loadCommands(path.join(__dirname, "commands"));
|
||||
|
||||
async function registerCommands(guildId) {
|
||||
const commands = client.commands.map((cmd) => cmd.data.toJSON());
|
||||
const rest = new REST({ version: "10" }).setToken(process.env.BOT_TOKEN);
|
||||
|
||||
try {
|
||||
await rest.put(Routes.applicationGuildCommands(client.user.id, guildId), {
|
||||
body: commands,
|
||||
});
|
||||
console.log(`🔄 Successfully registered commands for guild: ${guildId}`);
|
||||
console.log(`🔄 Registered commands for guild: ${guildId}`);
|
||||
} catch (error) {
|
||||
console.error("Error registering commands:", error);
|
||||
}
|
||||
|
@ -64,9 +88,7 @@ client.once("ready", async () => {
|
|||
console.log(`🤖 Logged in as ${client.user.tag}`);
|
||||
console.log(`==============================`);
|
||||
|
||||
// Register commands for all existing guilds
|
||||
const guilds = client.guilds.cache.map((guild) => guild.id);
|
||||
|
||||
await Promise.all(
|
||||
guilds.map(async (guildId) => {
|
||||
await seedShopItems(guildId);
|
||||
|
@ -75,29 +97,19 @@ client.once("ready", async () => {
|
|||
})
|
||||
);
|
||||
|
||||
// Set bot status and activity
|
||||
client.user.setPresence({
|
||||
activities: [{ name: "Degenerate Gamers!", type: 3 }],
|
||||
status: PresenceUpdateStatus.Online,
|
||||
});
|
||||
|
||||
console.log(`\n==============================\n`);
|
||||
});
|
||||
|
||||
// Listen for new guild joins and register the guild ID in the database
|
||||
client.on("guildCreate", async (guild) => {
|
||||
try {
|
||||
// Create a new entry in the ServerSettings collection with just the guildId
|
||||
await ServerSettings.create({ guildId: guild.id });
|
||||
console.log(`✅ Registered new server: ${guild.name} (ID: ${guild.id})`);
|
||||
|
||||
// seed items for new guild with guildId
|
||||
await seedShopItems(guild.id);
|
||||
|
||||
// Seed spyfall locations for the new guild
|
||||
await seedSpyfallLocations(guild.id);
|
||||
|
||||
// Register slash commands for the new guild
|
||||
await registerCommands(guild.id);
|
||||
} catch (error) {
|
||||
console.error("Error registering new server or commands:", error);
|
||||
|
@ -112,31 +124,23 @@ mongoose
|
|||
|
||||
client.on("interactionCreate", async (interaction) => {
|
||||
if (!interaction.isCommand()) return;
|
||||
|
||||
const command = client.commands.get(interaction.commandName);
|
||||
|
||||
if (!command) return;
|
||||
|
||||
try {
|
||||
await command.execute(interaction, client);
|
||||
} catch (err) {
|
||||
console.error("Error executing command:", err);
|
||||
if (interaction.deferred || interaction.ephemeral) {
|
||||
await interaction.followUp({
|
||||
content: "There was an error while executing this command!",
|
||||
ephemeral: true,
|
||||
});
|
||||
const replyOptions = {
|
||||
content: "Error executing command!",
|
||||
ephemeral: true,
|
||||
};
|
||||
if (interaction.deferred || interaction.replied) {
|
||||
await interaction.followUp(replyOptions);
|
||||
} else {
|
||||
await interaction.reply({
|
||||
content: "There was an error while executing this command!",
|
||||
ephemeral: true,
|
||||
});
|
||||
await interaction.reply(replyOptions);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
client.on("error", (err) => {
|
||||
console.error("Client error:", err);
|
||||
});
|
||||
|
||||
client.on("error", (err) => console.error("Client error:", err));
|
||||
client.login(process.env.BOT_TOKEN);
|
||||
|
|
|
@ -11,8 +11,14 @@
|
|||
"keywords": [],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@discordjs/opus": "^0.10.0",
|
||||
"@discordjs/voice": "^0.19.0",
|
||||
"@distube/soundcloud": "^2.0.4",
|
||||
"@distube/spotify": "^2.0.2",
|
||||
"@snazzah/davey": "^0.1.6",
|
||||
"axios": "^1.7.7",
|
||||
"discord.js": "^14.15.3",
|
||||
"distube": "^5.0.7",
|
||||
"dotenv": "^16.4.5",
|
||||
"express": "^4.19.2",
|
||||
"html-entities": "^2.5.2",
|
||||
|
|
Loading…
Reference in a new issue