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
13
Dockerfile
13
Dockerfile
|
@ -1,5 +1,16 @@
|
||||||
|
FROM jrottenberg/ffmpeg:8-alpine AS ffmpeg
|
||||||
|
|
||||||
FROM node:current-alpine
|
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
|
WORKDIR /usr/src/app
|
||||||
|
|
||||||
COPY package*.json ./
|
COPY package*.json ./
|
||||||
|
@ -8,4 +19,4 @@ RUN npm install
|
||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
CMD ["node", "."]
|
CMD ["node", "."]
|
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,
|
Routes,
|
||||||
PresenceUpdateStatus,
|
PresenceUpdateStatus,
|
||||||
} = require("discord.js");
|
} = require("discord.js");
|
||||||
|
const { DisTube } = require("distube");
|
||||||
|
const { SpotifyPlugin } = require("@distube/spotify");
|
||||||
|
const { SoundCloudPlugin } = require("@distube/soundcloud");
|
||||||
const mongoose = require("mongoose");
|
const mongoose = require("mongoose");
|
||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
|
@ -19,41 +22,62 @@ const client = new Client({
|
||||||
GatewayIntentBits.Guilds,
|
GatewayIntentBits.Guilds,
|
||||||
GatewayIntentBits.GuildMessages,
|
GatewayIntentBits.GuildMessages,
|
||||||
GatewayIntentBits.MessageContent,
|
GatewayIntentBits.MessageContent,
|
||||||
|
GatewayIntentBits.GuildVoiceStates,
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
client.commands = new Collection();
|
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 to recursively read commands from subdirectories
|
||||||
function loadCommands(dir) {
|
function loadCommands(dir) {
|
||||||
const files = fs.readdirSync(dir);
|
const files = fs.readdirSync(dir);
|
||||||
|
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
const filePath = path.join(dir, file);
|
const filePath = path.join(dir, file);
|
||||||
|
|
||||||
if (fs.statSync(filePath).isDirectory()) {
|
if (fs.statSync(filePath).isDirectory()) {
|
||||||
// If it's a directory, recurse into it
|
|
||||||
loadCommands(filePath);
|
loadCommands(filePath);
|
||||||
} else if (file.endsWith(".js")) {
|
} else if (file.endsWith(".js")) {
|
||||||
// If it's a JavaScript file, load the command
|
|
||||||
const command = require(filePath);
|
const command = require(filePath);
|
||||||
client.commands.set(command.data.name, command);
|
client.commands.set(command.data.name, command);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load all commands from the commands directory and its subdirectories
|
|
||||||
loadCommands(path.join(__dirname, "commands"));
|
loadCommands(path.join(__dirname, "commands"));
|
||||||
|
|
||||||
async function registerCommands(guildId) {
|
async function registerCommands(guildId) {
|
||||||
const commands = client.commands.map((cmd) => cmd.data.toJSON());
|
const commands = client.commands.map((cmd) => cmd.data.toJSON());
|
||||||
const rest = new REST({ version: "10" }).setToken(process.env.BOT_TOKEN);
|
const rest = new REST({ version: "10" }).setToken(process.env.BOT_TOKEN);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await rest.put(Routes.applicationGuildCommands(client.user.id, guildId), {
|
await rest.put(Routes.applicationGuildCommands(client.user.id, guildId), {
|
||||||
body: commands,
|
body: commands,
|
||||||
});
|
});
|
||||||
console.log(`🔄 Successfully registered commands for guild: ${guildId}`);
|
console.log(`🔄 Registered commands for guild: ${guildId}`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error registering commands:", 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(`🤖 Logged in as ${client.user.tag}`);
|
||||||
console.log(`==============================`);
|
console.log(`==============================`);
|
||||||
|
|
||||||
// Register commands for all existing guilds
|
|
||||||
const guilds = client.guilds.cache.map((guild) => guild.id);
|
const guilds = client.guilds.cache.map((guild) => guild.id);
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
guilds.map(async (guildId) => {
|
guilds.map(async (guildId) => {
|
||||||
await seedShopItems(guildId);
|
await seedShopItems(guildId);
|
||||||
|
@ -75,29 +97,19 @@ client.once("ready", async () => {
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
// Set bot status and activity
|
|
||||||
client.user.setPresence({
|
client.user.setPresence({
|
||||||
activities: [{ name: "Degenerate Gamers!", type: 3 }],
|
activities: [{ name: "Degenerate Gamers!", type: 3 }],
|
||||||
status: PresenceUpdateStatus.Online,
|
status: PresenceUpdateStatus.Online,
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(`\n==============================\n`);
|
console.log(`\n==============================\n`);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Listen for new guild joins and register the guild ID in the database
|
|
||||||
client.on("guildCreate", async (guild) => {
|
client.on("guildCreate", async (guild) => {
|
||||||
try {
|
try {
|
||||||
// Create a new entry in the ServerSettings collection with just the guildId
|
|
||||||
await ServerSettings.create({ guildId: guild.id });
|
await ServerSettings.create({ guildId: guild.id });
|
||||||
console.log(`✅ Registered new server: ${guild.name} (ID: ${guild.id})`);
|
console.log(`✅ Registered new server: ${guild.name} (ID: ${guild.id})`);
|
||||||
|
|
||||||
// seed items for new guild with guildId
|
|
||||||
await seedShopItems(guild.id);
|
await seedShopItems(guild.id);
|
||||||
|
|
||||||
// Seed spyfall locations for the new guild
|
|
||||||
await seedSpyfallLocations(guild.id);
|
await seedSpyfallLocations(guild.id);
|
||||||
|
|
||||||
// Register slash commands for the new guild
|
|
||||||
await registerCommands(guild.id);
|
await registerCommands(guild.id);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error registering new server or commands:", error);
|
console.error("Error registering new server or commands:", error);
|
||||||
|
@ -112,31 +124,23 @@ mongoose
|
||||||
|
|
||||||
client.on("interactionCreate", async (interaction) => {
|
client.on("interactionCreate", async (interaction) => {
|
||||||
if (!interaction.isCommand()) return;
|
if (!interaction.isCommand()) return;
|
||||||
|
|
||||||
const command = client.commands.get(interaction.commandName);
|
const command = client.commands.get(interaction.commandName);
|
||||||
|
|
||||||
if (!command) return;
|
if (!command) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await command.execute(interaction, client);
|
await command.execute(interaction, client);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Error executing command:", err);
|
console.error("Error executing command:", err);
|
||||||
if (interaction.deferred || interaction.ephemeral) {
|
const replyOptions = {
|
||||||
await interaction.followUp({
|
content: "Error executing command!",
|
||||||
content: "There was an error while executing this command!",
|
ephemeral: true,
|
||||||
ephemeral: true,
|
};
|
||||||
});
|
if (interaction.deferred || interaction.replied) {
|
||||||
|
await interaction.followUp(replyOptions);
|
||||||
} else {
|
} else {
|
||||||
await interaction.reply({
|
await interaction.reply(replyOptions);
|
||||||
content: "There was an error while executing this command!",
|
|
||||||
ephemeral: true,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
client.on("error", (err) => {
|
client.on("error", (err) => console.error("Client error:", err));
|
||||||
console.error("Client error:", err);
|
|
||||||
});
|
|
||||||
|
|
||||||
client.login(process.env.BOT_TOKEN);
|
client.login(process.env.BOT_TOKEN);
|
||||||
|
|
|
@ -11,8 +11,14 @@
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"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",
|
"axios": "^1.7.7",
|
||||||
"discord.js": "^14.15.3",
|
"discord.js": "^14.15.3",
|
||||||
|
"distube": "^5.0.7",
|
||||||
"dotenv": "^16.4.5",
|
"dotenv": "^16.4.5",
|
||||||
"express": "^4.19.2",
|
"express": "^4.19.2",
|
||||||
"html-entities": "^2.5.2",
|
"html-entities": "^2.5.2",
|
||||||
|
|
Loading…
Reference in a new issue