From 3d4f5061d402b44218cdfd351f39317d5f8ecd11 Mon Sep 17 00:00:00 2001 From: Andrew Lee Date: Sun, 15 Oct 2023 23:54:53 -0400 Subject: New feature (embed thumbnails); Docker; Package update --- Commands/pause.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Commands/pause.js') diff --git a/Commands/pause.js b/Commands/pause.js index 9e6026c..ab5a609 100644 --- a/Commands/pause.js +++ b/Commands/pause.js @@ -30,7 +30,7 @@ export default { .setDescription('Pauses music'), async execute(interaction, bot) { if (!interaction.member.voice.channel) return await interaction.reply({ content: 'You need to be in a voice channel to use this command.', ephemeral: true }); - if (!interaction.member.roles.cache.has(djRole) && interaction.user.id !== ownerID && !interaction.member.permission.has(PermissionFlagsBits.ManageGuild)) return interaction.reply({ content: 'You need a specific role to execute this command', ephemeral: true }); + if (!interaction.member.roles.cache.has(djRole) && interaction.user.id !== ownerID && !interaction.memberPermissions.has(PermissionFlagsBits.ManageGuild)) return interaction.reply({ content: 'You need a specific role to execute this command', ephemeral: true }); if (!isAudioStatePaused) { toggleAudioState(); -- cgit v1.2.3 From 72ea1090753ccca3c5573801ae0b0a4439e1b736 Mon Sep 17 00:00:00 2001 From: Andrew Lee Date: Tue, 13 Feb 2024 22:35:50 -0500 Subject: Fully working i18n (hopefully); Docker on walkthrough --- .parlance.json | 0 AudioBackend/AudioControl.js | 20 ++- AudioBackend/PlayAudio.js | 46 +++--- AudioBackend/QueueSystem.js | 8 +- AudioBackend/Shutdown.js | 11 +- AudioBackend/VoiceInitialization.js | 14 +- Commands/about.js | 23 +-- Commands/join.js | 9 +- Commands/leave.js | 11 +- Commands/list.js | 11 +- Commands/next.js | 5 +- Commands/pause.js | 10 +- Commands/ping.js | 5 +- Commands/play.js | 14 +- Commands/previous.js | 4 +- Commands/reshuffle.js | 12 +- Commands/shutdown.js | 7 +- Commands/status.js | 38 +++-- Locales/en/translation.json | 78 ++++++++- README.md | 33 +++- Utilities/Voting.js | 32 ++-- Utilities/i18n.js | 10 +- bot.js | 22 +-- package.json | 14 +- yarn.lock | 320 ++++++++++++++++++------------------ 25 files changed, 453 insertions(+), 304 deletions(-) create mode 100644 .parlance.json (limited to 'Commands/pause.js') diff --git a/.parlance.json b/.parlance.json new file mode 100644 index 0000000..e69de29 diff --git a/AudioBackend/AudioControl.js b/AudioBackend/AudioControl.js index 3a9c7a5..57d8ed7 100644 --- a/AudioBackend/AudioControl.js +++ b/AudioBackend/AudioControl.js @@ -22,21 +22,24 @@ import { readdirSync, readFileSync } from 'node:fs'; import { shufflePlaylist, orderPlaylist } from './QueueSystem.js'; import { playAudio, currentTrack, updatePlaylist } from './PlayAudio.js'; import { player } from './VoiceInitialization.js'; +import i18next from '../Utilities/i18n.js'; +const t = i18next.t; const { shuffle, repeat } = JSON.parse(readFileSync('./config.json', 'utf-8')); export const files = readdirSync('music'); export let playerState; +export let playerStatus; export let isAudioStatePaused; let totalTrack = files.length; async function repeatCheck(bot) { if (repeat) { - console.log('All beats in the playlist has finished, repeating beats...'); + console.log(t('musicRepeatingFinished')); totalTrack = files.length; return (shuffle) ? await shufflePlaylist(bot) : await orderPlaylist(bot); } else { - console.log('All beats in the playlist has finished.'); + console.log(t('musicPlaylistFinished')); updatePlaylist('stop'); audioState(2); } @@ -53,9 +56,9 @@ export async function nextAudio(bot) { export async function previousAudio(bot, interaction) { if (currentTrack <= 0) { - return await interaction.reply({ content: 'You are at the beginning of the playlist, cannot go further than this', ephemeral: true }); + return await interaction.reply({ content: t('previousBeginningPlaylist'), ephemeral: true }); } else { - await interaction.reply({ content: 'Playing previous music' }); + await interaction.reply({ content: t('musicPrevious') }); player.stop(); updatePlaylist('back'); return await playAudio(bot); @@ -73,17 +76,20 @@ export function toggleAudioState() { export function audioState(state) { switch (state) { case 0: - playerState = 'Playing'; + playerState = t('playPlayerState'); + playerStatus = 0; isAudioStatePaused = false; player.unpause(); break; case 1: - playerState = 'Paused'; + playerState = t('pausePlayerState'); + playerStatus = 1; isAudioStatePaused = true; player.pause(); break; case 2: - playerState = 'Stopped'; + playerState = t('stopPlayerState'); + playerStatus = 2; totalTrack = files.length; isAudioStatePaused = true; player.stop(); diff --git a/AudioBackend/PlayAudio.js b/AudioBackend/PlayAudio.js index 3d9f13a..5c45418 100644 --- a/AudioBackend/PlayAudio.js +++ b/AudioBackend/PlayAudio.js @@ -25,7 +25,10 @@ import { EmbedBuilder, AttachmentBuilder } from 'discord.js'; import { player } from './VoiceInitialization.js'; import { audioState, files } from './AudioControl.js'; import { integer } from '../Commands/play.js'; +import i18next from '../Utilities/i18n.js'; + const { statusChannel, txtFile } = JSON.parse(readFileSync('./config.json', 'utf-8')); +const t = i18next.t; let fileData; @@ -46,7 +49,7 @@ export async function playAudio(bot) { const resource = createAudioResource('music/' + audio); player.play(resource); - console.log(`Now playing: ${audio}`); + console.log(t('nowPlayingFile', { audio })); audioState(0); @@ -60,7 +63,13 @@ export async function playAudio(bot) { audioArtist = common.artist; audioYear = common.year; audioAlbum = common.album; - if (common.picture) audioPicture = new AttachmentBuilder(common.picture[0].data, { name: 'albumArt.png', description: 'Album Art' }); + if (common.picture) { + // Convert base64 image to a buffer + const imageBuffer = Buffer.from(common.picture[0].data, 'base64'); + + // Create a new attachment using the buffer + audioPicture = new AttachmentBuilder(imageBuffer, 'albumArt.png'); + } } else { metadataEmpty = true; } @@ -72,7 +81,7 @@ export async function playAudio(bot) { audio = audio.split('.').slice(0, -1).join('.'); if (txtFile) { - fileData = `Now Playing: ${audio}`; + fileData = t('nowPlayingFile', { audio }); writeFile('./now-playing.txt', fileData, (err) => { if (err) { console.log(err); } }); @@ -81,31 +90,30 @@ export async function playAudio(bot) { const statusEmbed = new EmbedBuilder(); if (metadataEmpty) { - statusEmbed.setTitle('Now Playing'); + statusEmbed.setTitle(t('nowPlaying')); statusEmbed.addFields( - { name: 'Title', value: `${audio}` }, - { name: 'Duration', value: `${duration}` } + { name: t('musicTitle'), value: `${audio}` }, + { name: t('musicDuration'), value: `${duration}` } ); statusEmbed.setColor('#0066ff'); } else { - statusEmbed.setTitle('Now Playing'); + statusEmbed.setTitle(t('nowPlaying')); statusEmbed.addFields( - { name: 'Title', value: `${audioTitle}`, inline: true }, - { name: 'Artist', value: `${audioArtist}`, inline: true }, - { name: 'Year', value: `${audioYear}` }, - { name: 'Duration', value: `${duration}` } + { name: t('musicTitle'), value: `${audioTitle}`, inline: true }, + { name: t('musicArtist'), value: `${audioArtist}`, inline: true }, + { name: t('musicYear'), value: `${audioYear}` }, + { name: t('musicDuration'), value: `${duration}` } ); - // console.log(audioPicture); - if (audioPicture) { - // statusEmbed.setThumbnail({ url: 'attachment://albumArt.png' }); - } - statusEmbed.setFooter({ text: `Album: ${audioAlbum}\nFilename: ${audioFile}` }); + // if (audioPicture) { + // statusEmbed.setThumbnail('attachment://albumArt.png'); + // } + statusEmbed.setFooter({ text: t('playerFooter', { audioAlbum, audioFile }) }); statusEmbed.setColor('#0066ff'); } const channel = bot.channels.cache.get(statusChannel); - if (!channel) return console.error('The status channel does not exist! Skipping.'); - return await channel.send({ embeds: [statusEmbed], files: [audioPicture] }); + if (!channel) return console.error(t('statusChannelError')); + return await channel.send({ embeds: [statusEmbed] }); } export function updatePlaylist(option) { @@ -126,7 +134,7 @@ export function updatePlaylist(option) { audio = inputFiles[integer]; break; case 'stop': - audio = 'Not Playing'; + audio = t('notPlaying'); break; } } diff --git a/AudioBackend/QueueSystem.js b/AudioBackend/QueueSystem.js index 0cd6ded..2bab8eb 100644 --- a/AudioBackend/QueueSystem.js +++ b/AudioBackend/QueueSystem.js @@ -20,6 +20,8 @@ ***************************************************************************/ import { playAudio, updatePlaylist } from './PlayAudio.js'; import { files } from './AudioControl.js'; +import i18next from '../Utilities/i18n.js'; +const t = i18next.t; function shuffleArray(array) { // Durstenfeld Shuffle @@ -29,16 +31,16 @@ function shuffleArray(array) { } } export async function orderPlaylist(bot) { - console.log('Playing beats by order...'); + console.log(t('musicPlayOrder')); updatePlaylist('reset'); console.log(files); return await playAudio(bot); } export async function shufflePlaylist(bot) { - console.log('Shuffling beats...'); + console.log(t('musicShuffling')); shuffleArray(files); - console.log('Playing beats by shuffle...'); + console.log(t('musicPlayShuffle')); updatePlaylist('reset'); console.log(files); return await playAudio(bot); diff --git a/AudioBackend/Shutdown.js b/AudioBackend/Shutdown.js index fb89505..dcc57f7 100644 --- a/AudioBackend/Shutdown.js +++ b/AudioBackend/Shutdown.js @@ -24,12 +24,15 @@ import { updatePlaylist } from './PlayAudio.js'; import { audioState } from './AudioControl.js'; import { readFileSync, writeFile } from 'node:fs'; import { getVoiceConnection, VoiceConnectionStatus } from '@discordjs/voice'; +import i18next from '../Utilities/i18n.js'; + const { statusChannel, txtFile } = JSON.parse(readFileSync('./config.json', 'utf-8')); let fileData; +const t = i18next.t; export async function destroyAudio(interaction) { if (txtFile) { - fileData = 'Now Playing: Nothing'; + fileData = t('txtNothing'); writeFile('now-playing.txt', fileData, (err) => { if (err) { console.log(err); } }); @@ -47,13 +50,13 @@ export async function destroyAudio(interaction) { export async function stopBot(bot, interaction) { const statusEmbed = new EmbedBuilder() .setAuthor({ name: bot.user.username, iconURL: bot.user.avatarURL() }) - .setDescription(`That's all folks! Powering down ${bot.user.username}...`) + .setDescription(t('statusShutdown', { bot: bot.user.username })) .setColor('#0066ff'); const channel = bot.channels.cache.get(statusChannel); - if (!channel) return console.error('The status channel does not exist! Skipping.'); + if (!channel) return console.error(t('statusChannelError')); await channel.send({ embeds: [statusEmbed] }); - console.log(`Powering off ${bot.user.username}...`); + console.log(t('powerOff', { bot: bot.user.username })); await destroyAudio(interaction); await bot.destroy(); return process.exit(0); diff --git a/AudioBackend/VoiceInitialization.js b/AudioBackend/VoiceInitialization.js index ae1241b..0088fbd 100644 --- a/AudioBackend/VoiceInitialization.js +++ b/AudioBackend/VoiceInitialization.js @@ -19,13 +19,15 @@ * ***************************************************************************/ import { readFileSync } from 'node:fs'; -import { createAudioPlayer, joinVoiceChannel, VoiceConnectionStatus } from '@discordjs/voice'; +import { createAudioPlayer, joinVoiceChannel, VoiceConnectionStatus, AudioPlayerStatus } from '@discordjs/voice'; import { nextAudio } from './AudioControl.js'; import { shufflePlaylist, orderPlaylist } from './QueueSystem.js'; import { votes } from '../Utilities/Voting.js'; +import i18next from '../Utilities/i18n.js'; const { voiceChannel, shuffle } = JSON.parse(readFileSync('./config.json', 'utf-8')); export const player = createAudioPlayer(); +const t = i18next.t; export async function voiceInit(bot) { bot.channels.fetch(voiceChannel).then(async channel => { const connection = joinVoiceChannel({ @@ -35,16 +37,16 @@ export async function voiceInit(bot) { }); connection.on(VoiceConnectionStatus.Connecting, () => { - console.log(`Connecting to ${channel.name}...`); + console.log(t('voiceConnecting', { channel: channel.name })); }); connection.on(VoiceConnectionStatus.Ready, async() => { - console.log('Ready to blast some beats!'); + console.log(t('voiceReady')); return (shuffle) ? await shufflePlaylist(bot) : await orderPlaylist(bot); }); connection.on(VoiceConnectionStatus.Destroyed, () => { - console.log('Destroyed the beats...'); + console.log(t('voiceDestroyed')); }); player.on('error', error => { @@ -52,8 +54,8 @@ export async function voiceInit(bot) { nextAudio(bot); }); - player.on('idle', () => { - console.log('Beat has finished playing, now playing next beat...'); + player.on(AudioPlayerStatus.Idle, () => { + console.log(t('musicsFinished')); votes.clear(); nextAudio(bot); }); diff --git a/Commands/about.js b/Commands/about.js index e027d02..d8afa52 100644 --- a/Commands/about.js +++ b/Commands/about.js @@ -21,33 +21,34 @@ import { EmbedBuilder, version, ActionRowBuilder, ButtonBuilder, ButtonStyle, SlashCommandBuilder } from 'discord.js'; // import npmPackage from '../package.json' assert { type:'json' } +import i18next from '../Utilities/i18n.js'; import { readFileSync } from 'node:fs'; const npmPackage = JSON.parse(readFileSync('./package.json', 'utf-8')); - +const t = i18next.t; export default { data: new SlashCommandBuilder() .setName('about') .setDescription('Information about the bot'), async execute(interaction, bot) { const aboutEmbed = new EmbedBuilder() - .setAuthor({ name: `About ${bot.user.username}`, iconURL: bot.user.avatarURL() }) + .setAuthor({ name: t('aboutBot', { bot: bot.user.username }), iconURL: bot.user.avatarURL() }) .addFields( - { name: 'Information', value: 'A Discord bot that plays local audio tracks.' }, - { name: 'Version', value: `DLAP ${npmPackage.version}` }, - { name: 'Original Creator', value: 'Andrew Lee (Alee#4277)' }, // Do not remove this since I created this :) - // { name: 'Contributors', value: '[your name] (discord#0000)' }, - // { name: 'Forked by', value: '[your name] (discord#0000)' }, - { name: 'Frameworks', value: `Discord.JS ${version} + Voice` }, - { name: 'License', value: 'GNU General Public License v3.0' } + { name: t('aboutInfo'), value: t('aboutInfoValue') }, + { name: t('aboutBotVersion'), value: `DLAP ${npmPackage.version}` }, + { name: t('aboutCreator'), value: 'Andrew Lee (alee)' }, // Do not remove this since I created this :) + // { name: t('aboutContributors'), value: '[your name] (username)' }, + // { name: t('aboutForked'), value: '[your name] (username)' }, + { name: t('aboutFrameworks'), value: `Discord.JS ${version} + Voice` }, + { name: t('aboutLicense'), value: 'GNU General Public License v3.0' } ) - .setFooter({ text: '© Copyright 2020-2023 Andrew Lee' }) + .setFooter({ text: '© Copyright 2020-2024 Andrew Lee' }) .setColor('#0066ff'); const srcOrig = new ActionRowBuilder() .addComponents( new ButtonBuilder() .setStyle(ButtonStyle.Link) - .setLabel('Original Source Code') + .setLabel(t('aboutSrc')) .setURL('https://github.com/Alee14/DLAP') ); diff --git a/Commands/join.js b/Commands/join.js index e9bac9b..2f513cf 100644 --- a/Commands/join.js +++ b/Commands/join.js @@ -23,17 +23,18 @@ import { SlashCommandBuilder } from 'discord.js'; import { voiceInit } from '../AudioBackend/VoiceInitialization.js'; import { PermissionFlagsBits } from 'discord-api-types/v10'; import { readFileSync } from 'node:fs'; +import i18next from '../Utilities/i18n.js'; const { djRole, ownerID } = JSON.parse(readFileSync('./config.json', 'utf-8')); - +const t = i18next.t; export default { data: new SlashCommandBuilder() .setName('join') .setDescription('Joins voice chat'), async execute(interaction, bot) { - if (!interaction.member.voice.channel) return await interaction.reply({ content: 'You need to be in a voice channel to use this command.', ephemeral: true }); - if (!interaction.member.roles.cache.has(djRole) && interaction.user.id !== ownerID && !interaction.memberPermissions.has(PermissionFlagsBits.ManageGuild)) return interaction.reply({ content: 'You need a specific role to execute this command', ephemeral: true }); + if (!interaction.member.voice.channel) return await interaction.reply({ content: t('voicePermission'), ephemeral: true }); + if (!interaction.member.roles.cache.has(djRole) && interaction.user.id !== ownerID && !interaction.memberPermissions.has(PermissionFlagsBits.ManageGuild)) return interaction.reply({ content: t('rolePermission'), ephemeral: true }); - await interaction.reply({ content: 'Joining voice channel', ephemeral: true }); + await interaction.reply({ content: t('joinVoice'), ephemeral: true }); return await voiceInit(bot); } }; diff --git a/Commands/leave.js b/Commands/leave.js index c90231c..e6559ca 100644 --- a/Commands/leave.js +++ b/Commands/leave.js @@ -23,18 +23,19 @@ import { SlashCommandBuilder } from 'discord.js'; import { destroyAudio } from '../AudioBackend/Shutdown.js'; import { PermissionFlagsBits } from 'discord-api-types/v10'; import { readFileSync } from 'node:fs'; +import i18next from '../Utilities/i18n.js'; const { djRole, ownerID } = JSON.parse(readFileSync('./config.json', 'utf-8')); - +const t = i18next.t; export default { data: new SlashCommandBuilder() .setName('leave') .setDescription('Leaves the voice chat'), async execute(interaction, bot) { - if (!interaction.member.voice.channel) return await interaction.reply({ content: 'You need to be in a voice channel to use this command.', ephemeral: true }); - if (!interaction.member.roles.cache.has(djRole) && interaction.user.id !== ownerID && !interaction.memberPermissions.has(PermissionFlagsBits.ManageGuild)) return interaction.reply({ content: 'You need a specific role to execute this command', ephemeral: true }); + if (!interaction.member.voice.channel) return await interaction.reply({ content: t('voicePermission'), ephemeral: true }); + if (!interaction.member.roles.cache.has(djRole) && interaction.user.id !== ownerID && !interaction.memberPermissions.has(PermissionFlagsBits.ManageGuild)) return interaction.reply({ content: t('rolePermission'), ephemeral: true }); - console.log('Leaving voice channel...'); + console.log(t('leaveVoice')); await destroyAudio(interaction); - return await interaction.reply({ content: 'Leaving voice channel', ephemeral: true }); + return await interaction.reply({ content: t('leaveVoice'), ephemeral: true }); } }; diff --git a/Commands/list.js b/Commands/list.js index 90d3cc6..4713a63 100644 --- a/Commands/list.js +++ b/Commands/list.js @@ -21,9 +21,10 @@ import { EmbedBuilder, SlashCommandBuilder } from 'discord.js'; import { readdir } from 'node:fs'; +import i18next from '../Utilities/i18n.js'; const musicFolder = './music'; - +const t = i18next.t; export default { data: new SlashCommandBuilder() .setName('list') @@ -42,7 +43,7 @@ export default { const pageSize = 20; // Number of tracks per page const numPages = Math.ceil(trackList.length / pageSize); // Total number of pages if (page < 1 || page > numPages) { // Check if the page number is valid - return await interaction.reply({ content: `Invalid page number. Please specify a number between 1 and ${numPages}.`, ephemeral: true }); + return await interaction.reply({ content: t('invalidPage', { numPages }), ephemeral: true }); } // Split the track list into pages const pages = []; @@ -53,9 +54,9 @@ export default { } // Send the specified page with the page number and total number of pages const listEmbed = new EmbedBuilder(); - listEmbed.setAuthor({ name: `${bot.user.username} List`, iconURL: bot.user.avatarURL() }); - listEmbed.addFields({ name: `Listing ${trackList.length} audio tracks...`, value: `\`\`\`\n${pages[page - 1].join('\n')}\n\`\`\`` }); - listEmbed.setFooter({ text: `Page ${page}/${numPages}` }); + listEmbed.setAuthor({ name: t('listTitle', { bot: bot.user.username }), iconURL: bot.user.avatarURL() }); + listEmbed.addFields({ name: t('listTracks', { trackList: trackList.length }), value: `\`\`\`\n${pages[page - 1].join('\n')}\n\`\`\`` }); + listEmbed.setFooter({ text: t('listPage') + ` ${page}/${numPages}` }); listEmbed.setColor('#0066ff'); await interaction.reply({ embeds: [listEmbed] }); } diff --git a/Commands/next.js b/Commands/next.js index f082c89..c99fd6b 100644 --- a/Commands/next.js +++ b/Commands/next.js @@ -21,7 +21,8 @@ import { SlashCommandBuilder } from 'discord.js'; import { voteSkip } from '../Utilities/Voting.js'; - +import i18next from '../Utilities/i18n.js'; +const t = i18next.t; export default { data: new SlashCommandBuilder() .setName('next') @@ -34,7 +35,7 @@ export default { .setDescription('Forces skip this audio track')), async execute(interaction, bot) { - if (!interaction.member.voice.channel) return await interaction.reply({ content: 'You need to be in a voice channel to use this command.', ephemeral: true }); + if (!interaction.member.voice.channel) return await interaction.reply({ content: t('voicePermission'), ephemeral: true }); await voteSkip(interaction, bot); } }; diff --git a/Commands/pause.js b/Commands/pause.js index ab5a609..bc649a6 100644 --- a/Commands/pause.js +++ b/Commands/pause.js @@ -23,20 +23,22 @@ import { SlashCommandBuilder } from 'discord.js'; import { toggleAudioState, isAudioStatePaused } from '../AudioBackend/AudioControl.js'; import { PermissionFlagsBits } from 'discord-api-types/v10'; import { readFileSync } from 'node:fs'; +import i18next from '../Utilities/i18n.js'; +const t = i18next.t; const { djRole, ownerID } = JSON.parse(readFileSync('./config.json', 'utf-8')); export default { data: new SlashCommandBuilder() .setName('pause') .setDescription('Pauses music'), async execute(interaction, bot) { - if (!interaction.member.voice.channel) return await interaction.reply({ content: 'You need to be in a voice channel to use this command.', ephemeral: true }); - if (!interaction.member.roles.cache.has(djRole) && interaction.user.id !== ownerID && !interaction.memberPermissions.has(PermissionFlagsBits.ManageGuild)) return interaction.reply({ content: 'You need a specific role to execute this command', ephemeral: true }); + if (!interaction.member.voice.channel) return await interaction.reply({ content: t('voicePermission'), ephemeral: true }); + if (!interaction.member.roles.cache.has(djRole) && interaction.user.id !== ownerID && !interaction.memberPermissions.has(PermissionFlagsBits.ManageGuild)) return interaction.reply({ content: t('rolePermission'), ephemeral: true }); if (!isAudioStatePaused) { toggleAudioState(); - return await interaction.reply({ content: 'Pausing music', ephemeral: true }); + return await interaction.reply({ content: t('pausingMusic'), ephemeral: true }); } else { - return await interaction.reply({ content: 'Music is already paused', ephemeral: true }); + return await interaction.reply({ content: t('pausedAlready'), ephemeral: true }); } } }; diff --git a/Commands/ping.js b/Commands/ping.js index 72ef024..3ee61e0 100644 --- a/Commands/ping.js +++ b/Commands/ping.js @@ -20,12 +20,13 @@ ***************************************************************************/ import { SlashCommandBuilder } from 'discord.js'; - +import i18next from '../Utilities/i18n.js'; +const t = i18next.t; export default { data: new SlashCommandBuilder() .setName('ping') .setDescription('Pong!'), async execute(interaction, bot) { - return await interaction.reply(`Pong! ${Math.round(bot.ws.ping)}ms`); + return await interaction.reply(`${t('pong')} ${Math.round(bot.ws.ping)}ms`); } }; diff --git a/Commands/play.js b/Commands/play.js index f6a7fd3..7cfe36b 100644 --- a/Commands/play.js +++ b/Commands/play.js @@ -26,7 +26,9 @@ import { audio } from '../AudioBackend/PlayAudio.js'; import { PermissionFlagsBits } from 'discord-api-types/v10'; import { readFileSync } from 'node:fs'; import { votes } from '../Utilities/Voting.js'; +import i18next from '../Utilities/i18n.js'; +const t = i18next.t; const { djRole, ownerID } = JSON.parse(readFileSync('./config.json', 'utf-8')); export let integer; @@ -41,24 +43,24 @@ export default { ), async execute(interaction, bot) { - if (!interaction.member.voice.channel) return await interaction.reply({ content: 'You need to be in a voice channel to use this command.', ephemeral: true }); - if (!interaction.member.roles.cache.has(djRole) && interaction.user.id !== ownerID && !interaction.memberPermissions.has(PermissionFlagsBits.ManageGuild)) return interaction.reply({ content: 'You need a specific role to execute this command', ephemeral: true }); + if (!interaction.member.voice.channel) return await interaction.reply({ content: t('voicePermission'), ephemeral: true }); + if (!interaction.member.roles.cache.has(djRole) && interaction.user.id !== ownerID && !interaction.memberPermissions.has(PermissionFlagsBits.ManageGuild)) return interaction.reply({ content: t('rolePermission'), ephemeral: true }); integer = interaction.options.getInteger('int'); if (integer) { if (integer < files.length) { await inputAudio(bot, integer); await votes.clear(); - return await interaction.reply({ content: `Now playing: ${audio}`, ephemeral: true }); + return await interaction.reply({ content: t('nowPlayingFile', audio), ephemeral: true }); } else { - return await interaction.reply({ content: 'Number is too big, choose a number that\'s less than ' + files.length + '.', ephemeral: true }); + return await interaction.reply({ content: t('numBig', { files: files.length }), ephemeral: true }); } } if (isAudioStatePaused) { toggleAudioState(); - return await interaction.reply({ content: 'Resuming music', ephemeral: true }); + return await interaction.reply({ content: t('resumingMusic'), ephemeral: true }); } else { - return await interaction.reply({ content: 'Music is already playing', ephemeral: true }); + return await interaction.reply({ content: t('resumedAlready'), ephemeral: true }); } } }; diff --git a/Commands/previous.js b/Commands/previous.js index aa732b2..c5a45b5 100644 --- a/Commands/previous.js +++ b/Commands/previous.js @@ -21,7 +21,9 @@ import { SlashCommandBuilder } from 'discord.js'; import { voteSkip } from '../Utilities/Voting.js'; +import i18next from '../Utilities/i18n.js'; +const t = i18next.t; export default { data: new SlashCommandBuilder() .setName('previous') @@ -33,7 +35,7 @@ export default { subcommand.setName('force') .setDescription('Forces skip this audio track')), async execute(interaction, bot) { - if (!interaction.member.voice.channel) return await interaction.reply({ content: 'You need to be in a voice channel to use this command.', ephemeral: true }); + if (!interaction.member.voice.channel) return await interaction.reply({ content: t('voicePermission'), ephemeral: true }); await voteSkip(interaction, bot); } }; diff --git a/Commands/reshuffle.js b/Commands/reshuffle.js index c16314e..d4ecfc3 100644 --- a/Commands/reshuffle.js +++ b/Commands/reshuffle.js @@ -24,22 +24,24 @@ import { shufflePlaylist } from '../AudioBackend/QueueSystem.js'; import { PermissionFlagsBits } from 'discord-api-types/v10'; import { readFileSync } from 'node:fs'; import { audioState } from '../AudioBackend/AudioControl.js'; +import i18next from '../Utilities/i18n.js'; + // import config from './config.json' assert {type: 'json'} const { shuffle, djRole, ownerID } = JSON.parse(readFileSync('./config.json', 'utf-8')); - +const t = i18next.t; export default { data: new SlashCommandBuilder() .setName('reshuffle') .setDescription('Reshuffles the playlist'), async execute(interaction, bot) { - if (!interaction.member.voice.channel) return await interaction.reply({ content: 'You need to be in a voice channel to use this command.', ephemeral: true }); - if (!interaction.member.roles.cache.has(djRole) && interaction.user.id !== ownerID && !interaction.memberPermissions.has(PermissionFlagsBits.ManageGuild)) return interaction.reply({ content: 'You need a specific role to execute this command', ephemeral: true }); + if (!interaction.member.voice.channel) return await interaction.reply({ content: t('voicePermission'), ephemeral: true }); + if (!interaction.member.roles.cache.has(djRole) && interaction.user.id !== ownerID && !interaction.memberPermissions.has(PermissionFlagsBits.ManageGuild)) return interaction.reply({ content: t('rolePermission'), ephemeral: true }); async function shuffleDetected(bot) { - await interaction.reply({ content: 'Reshuffling the playlist...', ephemeral: true }); + await interaction.reply({ content: t('reshufflePlaylist'), ephemeral: true }); await audioState(2); await shufflePlaylist(bot); } - return (shuffle) ? await shuffleDetected(bot) : await interaction.reply({ content: 'Shuffle mode is disabled, enable it in the configuration file to access this command.', ephemeral: true }); + return (shuffle) ? await shuffleDetected(bot) : await interaction.reply({ content: t('reShuffleDisabled'), ephemeral: true }); } }; diff --git a/Commands/shutdown.js b/Commands/shutdown.js index eefbab3..6e26f83 100644 --- a/Commands/shutdown.js +++ b/Commands/shutdown.js @@ -22,14 +22,17 @@ import { SlashCommandBuilder } from 'discord.js'; import { stopBot } from '../AudioBackend/Shutdown.js'; import { readFileSync } from 'node:fs'; +import i18next from '../Utilities/i18n.js'; + +const t = i18next.t; const { ownerID } = JSON.parse(readFileSync('./config.json', 'utf-8')); export default { data: new SlashCommandBuilder() .setName('shutdown') .setDescription('Powers off the bot'), async execute(interaction, bot) { - if (interaction.user.id !== ownerID) return interaction.reply({ content: 'You need to be the creator of this bot to execute this command', ephemeral: true }); - await interaction.reply({ content: 'Powering off...', ephemeral: true }); + if (interaction.user.id !== ownerID) return interaction.reply({ content: t('creatorPermission'), ephemeral: true }); + await interaction.reply({ content: t('powerOff', { bot: bot.user.username }), ephemeral: true }); return await stopBot(bot, interaction); } }; diff --git a/Commands/status.js b/Commands/status.js index aeb909d..d762187 100644 --- a/Commands/status.js +++ b/Commands/status.js @@ -22,9 +22,11 @@ import { EmbedBuilder, SlashCommandBuilder } from 'discord.js'; import { parseFile } from 'music-metadata'; import { audio, metadataEmpty, duration, audioTitle, currentTrack } from '../AudioBackend/PlayAudio.js'; -import { files, playerState } from '../AudioBackend/AudioControl.js'; +import { files, playerState, playerStatus } from '../AudioBackend/AudioControl.js'; import { votes } from '../Utilities/Voting.js'; +import i18next from '../Utilities/i18n.js'; +const t = i18next.t; export default { data: new SlashCommandBuilder() .setName('status') @@ -44,7 +46,7 @@ export default { const votesRequired = Math.ceil((members.size - votes.size) / 2); if (audioID >= files.length) { - audioName = 'Playlist Finished'; + audioName = t('playlistDone'); } else { audioName = files[audioID]; if (!metadataEmpty) { @@ -60,25 +62,27 @@ export default { } const controlEmbed = new EmbedBuilder() - .setAuthor({ name: `${bot.user.username} Status`, iconURL: bot.user.avatarURL() }) + .setAuthor({ name: t('statusTitle', { bot: bot.user.username }), iconURL: bot.user.avatarURL() }) .addFields( - { name: 'State', value: `${playerState}` }, - { name: 'Tracks', value: `${audioID}/${files.length}` }, - { name: 'Duration', value: `${duration}` }, - { name: 'Votes Needed', value: `${votesRequired}` } + { name: t('statusState'), value: `${playerState}` }, + { name: t('statusTracks'), value: `${audioID}/${files.length}` }, + { name: t('musicDuration'), value: `${duration}` }, + { name: t('statusVotesNeeded'), value: `${votesRequired}` } ) .setColor('#0066ff'); - if (metadataEmpty) { - controlEmbed.addFields( - { name: 'Currently Playing', value: `${audio}` }, - { name: 'Up Next', value: `${audioName}` } - ); - } else { - controlEmbed.addFields( - { name: 'Currently Playing', value: `${audioTitle}` }, - { name: 'Up Next', value: `${audioName}` } - ); + if (playerStatus === 0 || playerStatus === 1) { + if (metadataEmpty) { + controlEmbed.addFields( + { name: t('currentlyPlaying'), value: `${audio}` }, + { name: t('upNext'), value: `${audioName}` } + ); + } else { + controlEmbed.addFields( + { name: t('currentlyPlaying'), value: `${audioTitle}` }, + { name: t('upNext'), value: `${audioName}` } + ); + } } interaction.reply({ embeds: [controlEmbed] }); } diff --git a/Locales/en/translation.json b/Locales/en/translation.json index 3e13246..c96f74f 100644 --- a/Locales/en/translation.json +++ b/Locales/en/translation.json @@ -1,4 +1,80 @@ { "botReady": "Bot is ready!", - "loggedIn": "Logged in as {{bot}}!" + "loggedIn": "Logged in as {{bot}}!", + "discordVersion": "Running on Discord.JS {{version}}", + "voiceChannel": "Voice Channel: {{voiceChannel}}", + "statusChannel": "Status Channel: {{statusChannel}}", + "djRole": "DJ Role: {{djRole}}", + "ownerID": "Owner ID: {{ownerID}}", + "shuffleEnabled": "Shuffle Enabled: {{shuffle}}", + "repeatEnabled": "Repeat Enabled: {{repeat}}", + "presenceSet": "Updated bot presence to {{activity}}", + "startingBot": "Starting bot...", + "statusChannelError": "The status channel does not exist! Skipping.", + "exception": "There was an error while executing this command...\nShare this to the bot owner or report it to the git repository in `/about`\n\nDetails:```{{e}}```", + "musicRepeatingFinished": "All music in the playlist has finished, repeating...", + "musicPlaylistFinished": "All music in the playlist has finished.", + "previousBeginningPlaylist": "You are at the beginning of the playlist, cannot go further than this", + "musicPrevious": "Playing previous music", + "playPlayerState": "Playing", + "pausePlayerState": "Paused", + "stopPlayerState": "Stopped", + "nowPlayingFile": "Now playing: {{audio}}", + "nowPlaying": "Now Playing", + "musicTitle": "Title", + "musicArtist": "Artist", + "musicYear": "Year", + "musicDuration": "Duration", + "notPlaying": "Not Playing", + "playerFooter": "Album: {{audioAlbum}}\nFilename: {{audioFile}}", + "musicPlayOrder": "Playing music by order...", + "musicPlayShuffle": "Playing music by shuffle...", + "musicShuffling": "Shuffling music...", + "txtNothing": "Now Playing: Nothing", + "statusShutdown": "That's all folks! Powering down {{bot}}...", + "powerOff": "Powering off {{bot}}...", + "voiceConnecting": "Connecting to {{channel}}...", + "voiceReady": "Ready to blast some beats!", + "voiceDestroyed": "Destroyed the music player...", + "musicsFinished": "Music has finished playing, now playing next music...", + "aboutBot": "About {{bot}}", + "aboutInfo": "Information", + "aboutInfoValue": "A Discord bot that plays local audio tracks.", + "aboutBotVersion": "Version", + "aboutCreator": "Original Creator", + "aboutContributors": "Contributors", + "aboutForked": "Forked by", + "aboutFrameworks": "Frameworks", + "aboutLicense": "License", + "aboutSrc": "Original Source Code", + "voicePermission": "You need to be in a voice channel to use this command.", + "rolePermission": "You need a specific role to execute this command", + "creatorPermission": "You need to be the creator of this bot to execute this command", + "joinVoice": "Joining voice channel", + "leaveVoice": "Leaving voice channel", + "invalidPage": "Invalid page number. Please specify a number between 1 and {{numPages}}.", + "listTitle": "{{bot}} List", + "listTracks": "Listing {{trackList}} audio tracks...", + "listPage": "Page", + "pausingMusic": "Pausing music", + "pausedAlready": "Music is already paused", + "pong": "Pong!", + "numBig": "Number is too big, choose a number that's less than {{files}}.", + "resumingMusic": "Resuming music", + "resumedAlready": "Music is already playing", + "reshufflePlaylist": "Reshuffling the playlist...", + "reshuffleDisabled": "Shuffle mode is disabled, enable it in the configuration file to access this command.", + "playlistDone": "Playlist Finished", + "statusTitle": "{{bot}} Status", + "statusState": "State", + "statusTracks": "Tracks", + "statusVotesNeeded": "Votes Needed", + "currentlyPlaying": "Currently Playing", + "upNext": "Up Next", + "musicNext": "Playing next music", + "alreadyVoted": "You have already voted, wait {{votesRequired}} more vote(s) to skip the audio track", + "enoughVotes": "Enough votes has passed, skipping audio file...", + "votesNeeded": "{{votesRequired}} more vote(s) needed to skip the audio track.", + "cannotPlay": "Cannot play next music. Player is currently stopped...", + "forceSkip": "Force skipping this audio track..." } diff --git a/README.md b/README.md index efa90ac..b625e76 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # DLAP Bot (Discord.JS Local Audio Player) -DLAP is a Discord bot that lets you play local audio tracks in your server. With DLAP, you can access your music files, and share your tunes with your friends and community. DLAP offers seamless integration with Discord, so you can enjoy your music without missing a beat. +DLAP is a Discord bot that lets you play local audio tracks in your server. With DLAP, you can access your music files, and share your tunes with your friends and community. DLAP offers seamless integration with Discord, so you can enjoy your music without missing a music. [Video Tutorial](https://youtu.be/Gvva8LHjOOo) | [Support Server](https://discord.gg/EFhRDqG) @@ -20,6 +20,34 @@ Also you must join my discord server (Support Server) to communicate with me. - Yarn Package Manager - NodeJS v18.5.0+ +# Docker +First install Docker then using CMD or a terminal change directory to DLAP root folder and type the following: +``` +docker build -t dlap . +``` + +- the -t flag specifies a tag that will be assigned to the image. With it, we can easily run the image that the tag was assigned to. +- the dot at the end is basically the path to search for Dockerfile. The dot means current directory (./) +- Run the container + +Follow the guide below and when ready type the following +``` +docker run -d --name dlap dlap:latest +``` + +- -d flag tells Docker to run the container in detached mode, meaning it will run the container in the background of your terminal and not give us any output from it. If we don't provide it, the run will be giving us the output until the application exits. Discord bots aren't supposed to exit after certain time, so we do need this flag +- --name assigns a name to the container. By default, container is identified by id that is not human-readable. To conveniently refer to container when needed, we can assign it a name +- dlap:latest means "latest version of dlap image" + +View logs (optional) +``` +docker logs -f mybot +``` + +- -f flag tells the docker to keep reading logs as they appear in container and is called "follow mode". To exit press CTRL + C. + +*Docker guide partially taken from [PythonDiscord](https://www.pythondiscord.com/pages/guides/python-guides/docker-hosting-guide/#creating-dockerfile)* + # Configuration Make a new file called `config.json` inside the root of your project. ``` @@ -33,8 +61,9 @@ Make a new file called `config.json` inside the root of your project. "clientID": "client_id", "ownerID": "your_user_id", "djRole": "role_id", + "locale": "en", "presenceActivity": "activity_here", - "activityType": [0 (Playing)/1 (Streaming)/2 (Listening)/3 (Watching)/4 (Custom)/5 (Competing)] + "activityType": [0 (Playing)/1 (Streaming)/2 (Listening)/3 (Watching)/4 (Custom)/5 (Competing)], } ``` diff --git a/Utilities/Voting.js b/Utilities/Voting.js index dc2f153..5bea29c 100644 --- a/Utilities/Voting.js +++ b/Utilities/Voting.js @@ -18,18 +18,20 @@ * along with this program. If not, see . * ***************************************************************************/ -import { nextAudio, playerState, previousAudio } from '../AudioBackend/AudioControl.js'; +import { nextAudio, playerStatus, previousAudio } from '../AudioBackend/AudioControl.js'; import { PermissionFlagsBits } from 'discord-api-types/v10'; import { readFileSync } from 'node:fs'; import { player } from '../AudioBackend/VoiceInitialization.js'; -const { djRole, ownerID } = JSON.parse(readFileSync('./config.json', 'utf-8')); +import i18next from '../Utilities/i18n.js'; +const { djRole, ownerID } = JSON.parse(readFileSync('./config.json', 'utf-8')); +const t = i18next.t; export const votes = new Set(); let nextCheck; async function commandCheck(interaction, bot) { if (nextCheck) { - await interaction.reply({ content: 'Playing next music' }); + await interaction.reply({ content: t('musicNext') }); player.stop(); return await nextAudio(bot); } else { @@ -62,37 +64,37 @@ export async function voteSkip(interaction, bot) { // Check if the message author has already voted if (votes.has(interaction.user.id)) { - return interaction.reply({ content: `You have already voted, wait ${votesRequired} more vote(s) to skip the audio track`, ephemeral: true }); + return interaction.reply({ content: t('alreadyVoted', votesRequired), ephemeral: true }); } - if (playerState === 'Playing' || playerState === 'Paused') { + if (playerStatus === 0 || playerStatus === 1) { // Add the message author to the set of members who have voted votes.add(interaction.user.id); if (votes.size >= votesRequired) { - console.log('Enough votes has passed, skipping audio file...'); + console.log(t('enoughVotes')); // Reset the number of votes votes.clear(); // Do something to skip the audio track here (e.g. player.stop()) await commandCheck(interaction, bot); } else { // Send a message with the number of votes needed to skip the audio track - console.log(`${votesRequired - 1} more vote(s) needed to skip the audio track.`); - await interaction.reply({ content: `${votesRequired - 1} more vote(s) needed to skip the audio track.` }); + console.log(t('votesNeeded', { votesRequired: votesRequired - 1 })); + await interaction.reply({ content: t('votesNeeded', { votesRequired: votesRequired - 1 }) }); } - } else if (playerState === 'Stopped') { - return await interaction.reply({ content: 'Cannot play next music. Player is currently stopped...', ephemeral: true }); + } else if (playerStatus === 2) { + return await interaction.reply({ content: t('cannotPlay'), ephemeral: true }); } } if (interaction.options.getSubcommand() === 'force') { - if (!interaction.member.roles.cache.has(djRole) && interaction.user.id !== ownerID && !interaction.memberPermissions.has(PermissionFlagsBits.ManageGuild)) return interaction.reply({ content: 'You need a specific role to execute this command', ephemeral: true }); - console.log('Force skipping this audio track...'); - if (playerState === 'Playing' || playerState === 'Paused') { + if (!interaction.member.roles.cache.has(djRole) && interaction.user.id !== ownerID && !interaction.memberPermissions.has(PermissionFlagsBits.ManageGuild)) return interaction.reply({ content: t('rolePermission'), ephemeral: true }); + console.log(t('forceSkip')); + if (playerStatus === 0 || playerStatus === 1) { votes.clear(); // Do something to skip the audio track here (e.g. player.stop()) await commandCheck(interaction, bot); - } else if (playerState === 'Stopped') { - return await interaction.reply({ content: 'Cannot play next music. Player is currently stopped...', ephemeral: true }); + } else if (playerStatus === 2) { + return await interaction.reply({ content: t('cannotPlay'), ephemeral: true }); } } } diff --git a/Utilities/i18n.js b/Utilities/i18n.js index c667c4c..9b431a1 100644 --- a/Utilities/i18n.js +++ b/Utilities/i18n.js @@ -20,10 +20,12 @@ ***************************************************************************/ import i18next from 'i18next'; import fsBackend from 'i18next-fs-backend'; +import { readFileSync } from 'node:fs'; +const { locale } = JSON.parse(readFileSync('./config.json', 'utf-8')); i18next.use(fsBackend).init({ - lng: 'en', // if you're using a language detector, do not define the lng option - debug: true, + lng: locale, // if you're using a language detector, do not define the lng option + debug: false, fallbackLng: 'en', backend: { loadPath: './Locales/{{lng}}/{{ns}}.json' @@ -32,7 +34,7 @@ i18next.use(fsBackend).init({ export default { i18next, - t(key) { - return i18next.t(key); + t(key, option) { + return i18next.t(key, option); } }; diff --git a/bot.js b/bot.js index e6bd3c0..9376c2a 100644 --- a/bot.js +++ b/bot.js @@ -41,13 +41,13 @@ for (const file of commandFiles) { bot.once(Events.ClientReady, async() => { console.log(t('botReady')); console.log(t('loggedIn', { bot: bot.user.tag })); - console.log(`Running on Discord.JS ${version}`); - console.log(`Voice Channel: ${voiceChannel}`); - console.log(`Status Channel: ${statusChannel}`); - console.log(`DJ Role: ${djRole}`); - console.log(`Owner ID: ${ownerID}`); - console.log(`Shuffle Enabled: ${shuffle}`); - console.log(`Repeat Enabled: ${repeat}`); + console.log(t('discordVersion', { version })); + console.log(t('voiceChannel', { voiceChannel })); + console.log(t('statusChannel', { statusChannel })); + console.log(t('djRole', { djRole })); + console.log(t('ownerID', { ownerID })); + console.log(t('shuffleEnabled', { shuffle })); + console.log(t('repeatEnabled', { repeat })); // Set bots' presence bot.user.setPresence({ @@ -59,16 +59,16 @@ bot.once(Events.ClientReady, async() => { }); const activity = bot.presence.activities[0]; - console.log(`Updated bot presence to "${activity.name}"`); + console.log(t('presenceSet', { activity: activity.name })); // Send bots' status to channel const readyEmbed = new EmbedBuilder() .setAuthor({ name: bot.user.username, iconURL: bot.user.avatarURL() }) - .setDescription('Starting bot...') + .setDescription(t('startingBot')) .setColor('#0066ff'); const channel = bot.channels.cache.get(statusChannel); - if (!channel) return console.error('The status channel does not exist! Skipping.'); + if (!channel) return console.error(t('statusChannelError')); await channel.send({ embeds: [readyEmbed] }); return await voiceInit(bot); @@ -85,6 +85,6 @@ bot.on(Events.InteractionCreate, async interaction => { await command.execute(interaction, bot); } catch (e) { console.error(e); - await interaction.reply({ content: `There was an error while executing this command...\nShare this to the bot owner or report it to the git repository in \`/about\`\n\nDetails:\`\`\`${e}\`\`\``, ephemeral: true }); + await interaction.reply({ content: t('exception', { e }), ephemeral: true }); } }); diff --git a/package.json b/package.json index c13b265..1383003 100644 --- a/package.json +++ b/package.json @@ -10,14 +10,14 @@ }, "dependencies": { "@discordjs/opus": "^0.9.0", - "@discordjs/rest": "^2.0.1", - "@discordjs/voice": "^0.16.0", - "discord-api-types": "^0.37.60", - "discord.js": "^14.13.0", + "@discordjs/rest": "^2.2.0", + "@discordjs/voice": "^0.16.1", + "discord-api-types": "^0.37.69", + "discord.js": "^14.14.1", "ffmpeg-static": "^5.2.0", - "i18next": "^23.5.1", - "i18next-fs-backend": "^2.2.0", - "music-metadata": "^8.1.4", + "i18next": "^23.8.2", + "i18next-fs-backend": "^2.3.1", + "music-metadata": "^7.14.0", "sodium": "^3.0.2" }, "devDependencies": { diff --git a/yarn.lock b/yarn.lock index 731705a..7e3a0e1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,10 +2,10 @@ # yarn lockfile v1 -"@babel/runtime@^7.22.5": - version "7.23.2" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.2.tgz#062b0ac103261d68a966c4c7baf2ae3e62ec3885" - integrity sha512-mM8eg4yl5D6i3lu2QKPuPH4FArvJ8KhTofbE7jwMUv9KX5mBvwPAqnV3MlyBNqdp9RyRKP6Yck8TrfYrPvX3bg== +"@babel/runtime@^7.23.2": + version "7.23.9" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.9.tgz#47791a15e4603bb5f905bc0753801cf21d6345f7" + integrity sha512-0CX6F+BI2s9dkUqr08KFrAIZgNFj75rdBU/DjCyYLIaV/quFjkk6T+EJ2LkZHyZTbEV4L5p97mNkUsHl2wLFAw== dependencies: regenerator-runtime "^0.14.0" @@ -19,30 +19,35 @@ http-response-object "^3.0.1" parse-cache-control "^1.0.1" -"@discordjs/builders@^1.6.5": - version "1.6.5" - resolved "https://registry.yarnpkg.com/@discordjs/builders/-/builders-1.6.5.tgz#3e23912eaab1d542b61ca0fa7202e5aaef2b7200" - integrity sha512-SdweyCs/+mHj+PNhGLLle7RrRFX9ZAhzynHahMCLqp5Zeq7np7XC6/mgzHc79QoVlQ1zZtOkTTiJpOZu5V8Ufg== +"@discordjs/builders@^1.7.0": + version "1.7.0" + resolved "https://registry.yarnpkg.com/@discordjs/builders/-/builders-1.7.0.tgz#e2478c7e55b0f4c40837edb8f102bce977323a37" + integrity sha512-GDtbKMkg433cOZur8Dv6c25EHxduNIBsxeHrsRoIM8+AwmEZ8r0tEpckx/sHwTLwQPOF3e2JWloZh9ofCaMfAw== dependencies: - "@discordjs/formatters" "^0.3.2" - "@discordjs/util" "^1.0.1" - "@sapphire/shapeshift" "^3.9.2" - discord-api-types "0.37.50" + "@discordjs/formatters" "^0.3.3" + "@discordjs/util" "^1.0.2" + "@sapphire/shapeshift" "^3.9.3" + discord-api-types "0.37.61" fast-deep-equal "^3.1.3" ts-mixer "^6.0.3" - tslib "^2.6.1" + tslib "^2.6.2" -"@discordjs/collection@^1.5.3": +"@discordjs/collection@1.5.3": version "1.5.3" resolved "https://registry.yarnpkg.com/@discordjs/collection/-/collection-1.5.3.tgz#5a1250159ebfff9efa4f963cfa7e97f1b291be18" integrity sha512-SVb428OMd3WO1paV3rm6tSjM4wC+Kecaa1EUGX7vc6/fddvw/6lg90z4QtCqm21zvVe92vMMDt9+DkIvjXImQQ== -"@discordjs/formatters@^0.3.2": - version "0.3.2" - resolved "https://registry.yarnpkg.com/@discordjs/formatters/-/formatters-0.3.2.tgz#3ae054f7b3097cc0dc7645fade37a3f20fa1fb4b" - integrity sha512-lE++JZK8LSSDRM5nLjhuvWhGuKiXqu+JZ/DsOR89DVVia3z9fdCJVcHF2W/1Zxgq0re7kCzmAJlCMMX3tetKpA== +"@discordjs/collection@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@discordjs/collection/-/collection-2.0.0.tgz#409b80c74eb8486cc4ee6a9b83426aaff1380f8c" + integrity sha512-YTWIXLrf5FsrLMycpMM9Q6vnZoR/lN2AWX23/Cuo8uOOtS8eHB2dyQaaGnaF8aZPYnttf2bkLMcXn/j6JUOi3w== + +"@discordjs/formatters@^0.3.3": + version "0.3.3" + resolved "https://registry.yarnpkg.com/@discordjs/formatters/-/formatters-0.3.3.tgz#b16fdd79bb819680ab7e519193004e9dc124a749" + integrity sha512-wTcI1Q5cps1eSGhl6+6AzzZkBBlVrBdc9IUhJbijRgVjCNIIIZPgqnUj3ntFODsHrdbGU8BEG9XmDQmgEEYn3w== dependencies: - discord-api-types "0.37.50" + discord-api-types "0.37.61" "@discordjs/node-pre-gyp@^0.4.5": version "0.4.5" @@ -67,51 +72,51 @@ "@discordjs/node-pre-gyp" "^0.4.5" node-addon-api "^5.0.0" -"@discordjs/rest@^2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@discordjs/rest/-/rest-2.0.1.tgz#100c208a964e54b8d7cd418bbaed279c816b8ec5" - integrity sha512-/eWAdDRvwX/rIE2tuQUmKaxmWeHmGealttIzGzlYfI4+a7y9b6ZoMp8BG/jaohs8D8iEnCNYaZiOFLVFLQb8Zg== +"@discordjs/rest@^2.1.0", "@discordjs/rest@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@discordjs/rest/-/rest-2.2.0.tgz#f4ec00d3faff965c00a879b7e87bb4b6f4446966" + integrity sha512-nXm9wT8oqrYFRMEqTXQx9DUTeEtXUDMmnUKIhZn6O2EeDY9VCdwj23XCPq7fkqMPKdF7ldAfeVKyxxFdbZl59A== dependencies: - "@discordjs/collection" "^1.5.3" - "@discordjs/util" "^1.0.1" + "@discordjs/collection" "^2.0.0" + "@discordjs/util" "^1.0.2" "@sapphire/async-queue" "^1.5.0" "@sapphire/snowflake" "^3.5.1" "@vladfrangu/async_event_emitter" "^2.2.2" - discord-api-types "0.37.50" - magic-bytes.js "^1.0.15" - tslib "^2.6.1" - undici "5.22.1" + discord-api-types "0.37.61" + magic-bytes.js "^1.5.0" + tslib "^2.6.2" + undici "5.27.2" -"@discordjs/util@^1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@discordjs/util/-/util-1.0.1.tgz#7d6f97b65425d3a8b46ea1180150dee6991a88cf" - integrity sha512-d0N2yCxB8r4bn00/hvFZwM7goDcUhtViC5un4hPj73Ba4yrChLSJD8fy7Ps5jpTLg1fE9n4K0xBLc1y9WGwSsA== +"@discordjs/util@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@discordjs/util/-/util-1.0.2.tgz#dc1896d764452b1bd9707eb9aa99ccfbb30bd1c0" + integrity sha512-IRNbimrmfb75GMNEjyznqM1tkI7HrZOf14njX7tCAAUetyZM1Pr8hX/EK2lxBCOgWDRmigbp24fD1hdMfQK5lw== -"@discordjs/voice@^0.16.0": - version "0.16.0" - resolved "https://registry.yarnpkg.com/@discordjs/voice/-/voice-0.16.0.tgz#9df1e492c8fea95113236a3de3ac52702c587729" - integrity sha512-ToGCvHD1cBscuW3p+C7zOF5+L7MJmU4GjdOARfNk9mkHyFFZq4grK+Sxr3QXKbp27DtfDBc9uqD4GUOYgxngfA== +"@discordjs/voice@^0.16.1": + version "0.16.1" + resolved "https://registry.yarnpkg.com/@discordjs/voice/-/voice-0.16.1.tgz#c4ad52b9308875d0462e22681060aecb97675941" + integrity sha512-uiWiW0Ta6K473yf8zs13RfKuPqm/xU4m4dAidMkIdwqgy1CztbbZBtPLfDkVSKzpW7s6m072C+uQcs4LwF3FhA== dependencies: - "@types/ws" "^8.5.4" - discord-api-types "^0.37.37" + "@types/ws" "^8.5.9" + discord-api-types "0.37.61" prism-media "^1.3.5" - tslib "^2.5.0" - ws "^8.13.0" + tslib "^2.6.2" + ws "^8.14.2" -"@discordjs/ws@^1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@discordjs/ws/-/ws-1.0.1.tgz#fab8aa4c1667040a95b5268a2875add27353d323" - integrity sha512-avvAolBqN3yrSvdBPcJ/0j2g42ABzrv3PEL76e3YTp2WYMGH7cuspkjfSyNWaqYl1J+669dlLp+YFMxSVQyS5g== +"@discordjs/ws@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@discordjs/ws/-/ws-1.0.2.tgz#3933b12d4686aabf6a95dfe5fb6e744342a661d1" + integrity sha512-+XI82Rm2hKnFwAySXEep4A7Kfoowt6weO6381jgW+wVdTpMS/56qCvoXyFRY0slcv7c/U8My2PwIB2/wEaAh7Q== dependencies: - "@discordjs/collection" "^1.5.3" - "@discordjs/rest" "^2.0.1" - "@discordjs/util" "^1.0.1" + "@discordjs/collection" "^2.0.0" + "@discordjs/rest" "^2.1.0" + "@discordjs/util" "^1.0.2" "@sapphire/async-queue" "^1.5.0" - "@types/ws" "^8.5.5" + "@types/ws" "^8.5.9" "@vladfrangu/async_event_emitter" "^2.2.2" - discord-api-types "0.37.50" - tslib "^2.6.1" - ws "^8.13.0" + discord-api-types "0.37.61" + tslib "^2.6.2" + ws "^8.14.2" "@eslint-community/eslint-utils@^4.2.0": version "4.2.0" @@ -145,6 +150,11 @@ resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.36.0.tgz#9837f768c03a1e4a30bd304a64fb8844f0e72efe" integrity sha512-lxJ9R5ygVm8ZWgYdUweoq5ownDlJ4upvoWmO4eLxBYHdMo+vZ/Rx0EN6MbKWDJOSUGrqJy2Gt+Dyv/VKml0fjg== +"@fastify/busboy@^2.0.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@fastify/busboy/-/busboy-2.1.0.tgz#0709e9f4cb252351c609c6e6d8d6779a8d25edff" + integrity sha512-+KpH+QxZU7O4675t3mnkQKcZZg56u+K/Ct2K+N2AZYNVK8kyeo/bI18tI8aPm3tvNNRyTWfj6s5tnGNlcbQRsA== + "@humanwhocodes/config-array@^0.11.8": version "0.11.8" resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.8.tgz#03595ac2075a4dc0f191cc2131de14fbd7d410b9" @@ -190,15 +200,15 @@ resolved "https://registry.yarnpkg.com/@sapphire/async-queue/-/async-queue-1.5.0.tgz#2f255a3f186635c4fb5a2381e375d3dfbc5312d8" integrity sha512-JkLdIsP8fPAdh9ZZjrbHWR/+mZj0wvKS5ICibcLrRI1j84UmLMshx5n9QmL8b95d4onJ2xxiyugTgSAX7AalmA== -"@sapphire/shapeshift@^3.9.2": - version "3.9.3" - resolved "https://registry.yarnpkg.com/@sapphire/shapeshift/-/shapeshift-3.9.3.tgz#89d26713044bc21cc5e0845e61a8a328ca3c1a84" - integrity sha512-WzKJSwDYloSkHoBbE8rkRW8UNKJiSRJ/P8NqJ5iVq7U2Yr/kriIBx2hW+wj2Z5e5EnXL1hgYomgaFsdK6b+zqQ== +"@sapphire/shapeshift@^3.9.3": + version "3.9.6" + resolved "https://registry.yarnpkg.com/@sapphire/shapeshift/-/shapeshift-3.9.6.tgz#bd9629c08641f5b94ae094e23f092187a3ed9a7d" + integrity sha512-4+Na/fxu2SEepZRb9z0dbsVh59QtwPuBg/UVaDib3av7ZY14b14+z09z6QVn0P6Dv6eOU2NDTsjIi0mbtgP56g== dependencies: fast-deep-equal "^3.1.3" lodash "^4.17.21" -"@sapphire/snowflake@^3.5.1": +"@sapphire/snowflake@3.5.1", "@sapphire/snowflake@^3.5.1": version "3.5.1" resolved "https://registry.yarnpkg.com/@sapphire/snowflake/-/snowflake-3.5.1.tgz#254521c188b49e8b2d4cc048b475fb2b38737fec" integrity sha512-BxcYGzgEsdlG0dKAyOm0ehLGm2CafIrfQTZGWgkfKYbj+pNNsorZ7EotuZukc2MT70E0UbppVbtpBrqpzVzjNA== @@ -223,17 +233,17 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.60.tgz#35f3d6213daed95da7f0f73e75bcc6980e90597b" integrity sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw== -"@types/ws@^8.5.4": - version "8.5.4" - resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.4.tgz#bb10e36116d6e570dd943735f86c933c1587b8a5" - integrity sha512-zdQDHKUgcX/zBc4GrwsE/7dVdAD8JR4EuiAXiiUhhfyIJXXb2+PrGshFyeXWQPMmmZ2XxgaqclgpIC7eTXc1mg== +"@types/ws@8.5.9": + version "8.5.9" + resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.9.tgz#384c489f99c83225a53f01ebc3eddf3b8e202a8c" + integrity sha512-jbdrY0a8lxfdTp/+r7Z4CkycbOFN8WX+IOchLJr3juT/xzbJ8URyTVSJ/hvNdadTgM1mnedb47n+Y31GsFnQlg== dependencies: "@types/node" "*" -"@types/ws@^8.5.5": - version "8.5.7" - resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.7.tgz#1ca585074fe5d2c81dec7a3d451f244a2a6d83cb" - integrity sha512-6UrLjiDUvn40CMrAubXuIVtj2PEfKDffJS7ychvnPU44j+KVeXmdHHTgqcM/dxLUTHxlXHiFM8Skmb8ozGdTnQ== +"@types/ws@^8.5.9": + version "8.5.10" + resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.10.tgz#4acfb517970853fa6574a3a6886791d04a396787" + integrity sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A== dependencies: "@types/node" "*" @@ -385,13 +395,6 @@ builtins@^5.0.1: dependencies: semver "^7.0.0" -busboy@^1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/busboy/-/busboy-1.6.0.tgz#966ea36a9502e43cdb9146962523b92f531f6893" - integrity sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA== - dependencies: - streamsearch "^1.1.0" - call-bind@^1.0.0, call-bind@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" @@ -526,35 +529,35 @@ detect-libc@^2.0.0: resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.1.tgz#e1897aa88fa6ad197862937fbc0441ef352ee0cd" integrity sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w== -discord-api-types@0.37.50: - version "0.37.50" - resolved "https://registry.yarnpkg.com/discord-api-types/-/discord-api-types-0.37.50.tgz#6059eb8c0b784ad8194655a8b8b7f540fcfac428" - integrity sha512-X4CDiMnDbA3s3RaUXWXmgAIbY1uxab3fqe3qwzg5XutR3wjqi7M3IkgQbsIBzpqBN2YWr/Qdv7JrFRqSgb4TFg== - -discord-api-types@^0.37.37, discord-api-types@^0.37.60: - version "0.37.60" - resolved "https://registry.yarnpkg.com/discord-api-types/-/discord-api-types-0.37.60.tgz#435855217afcf867e6ea20d0a750b23550b734ed" - integrity sha512-5BELXTsv7becqVHrD81nZrqT4oEyXXWBwbsO/kwDDu6X3u19VV1tYDB5I5vaVAK+c1chcDeheI9zACBLm41LiQ== - -discord.js@^14.13.0: - version "14.13.0" - resolved "https://registry.yarnpkg.com/discord.js/-/discord.js-14.13.0.tgz#e7a00bdba70adb9e266a06884ca1acaf9a0b5c20" - integrity sha512-Kufdvg7fpyTEwANGy9x7i4od4yu5c6gVddGi5CKm4Y5a6sF0VBODObI3o0Bh7TGCj0LfNT8Qp8z04wnLFzgnbA== - dependencies: - "@discordjs/builders" "^1.6.5" - "@discordjs/collection" "^1.5.3" - "@discordjs/formatters" "^0.3.2" - "@discordjs/rest" "^2.0.1" - "@discordjs/util" "^1.0.1" - "@discordjs/ws" "^1.0.1" - "@sapphire/snowflake" "^3.5.1" - "@types/ws" "^8.5.5" - discord-api-types "0.37.50" - fast-deep-equal "^3.1.3" - lodash.snakecase "^4.1.1" - tslib "^2.6.1" - undici "5.22.1" - ws "^8.13.0" +discord-api-types@0.37.61: + version "0.37.61" + resolved "https://registry.yarnpkg.com/discord-api-types/-/discord-api-types-0.37.61.tgz#9dd8e58c624237e6f1b23be2d29579af268b8c5b" + integrity sha512-o/dXNFfhBpYHpQFdT6FWzeO7pKc838QeeZ9d91CfVAtpr5XLK4B/zYxQbYgPdoMiTDvJfzcsLW5naXgmHGDNXw== + +discord-api-types@^0.37.69: + version "0.37.69" + resolved "https://registry.yarnpkg.com/discord-api-types/-/discord-api-types-0.37.69.tgz#4c78a03ef247dd8d6b95e4eaf199ed8d000a3955" + integrity sha512-c0rHc5YGNIXQkI+V7QwP8y77wxo74ITNeZmMwxtKC/l01aIF/gKBG/U2MKhUt2iaeRH9XwAt9PT3AI9JQVvKVA== + +discord.js@^14.14.1: + version "14.14.1" + resolved "https://registry.yarnpkg.com/discord.js/-/discord.js-14.14.1.tgz#9a2bea23bba13819705ab87612837610abce9ee3" + integrity sha512-/hUVzkIerxKHyRKopJy5xejp4MYKDPTszAnpYxzVVv4qJYf+Tkt+jnT2N29PIPschicaEEpXwF2ARrTYHYwQ5w== + dependencies: + "@discordjs/builders" "^1.7.0" + "@discordjs/collection" "1.5.3" + "@discordjs/formatters" "^0.3.3" + "@discordjs/rest" "^2.1.0" + "@discordjs/util" "^1.0.2" + "@discordjs/ws" "^1.0.2" + "@sapphire/snowflake" "3.5.1" + "@types/ws" "8.5.9" + discord-api-types "0.37.61" + fast-deep-equal "3.1.3" + lodash.snakecase "4.1.1" + tslib "2.6.2" + undici "5.27.2" + ws "8.14.2" doctrine@^2.1.0: version "2.1.0" @@ -834,7 +837,7 @@ esutils@^2.0.2: resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== -fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: +fast-deep-equal@3.1.3, fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== @@ -873,14 +876,14 @@ file-entry-cache@^6.0.1: dependencies: flat-cache "^3.0.4" -file-type@^18.2.1: - version "18.2.1" - resolved "https://registry.yarnpkg.com/file-type/-/file-type-18.2.1.tgz#6d8f1fa3b079606f6ecf89483346f55fcd2c671b" - integrity sha512-Yw5MtnMv7vgD2/6Bjmmuegc8bQEVA9GmAyaR18bMYWKqsWDG9wgYZ1j4I6gNMF5Y5JBDcUcjRQqNQx7Y8uotcg== +file-type@^16.5.4: + version "16.5.4" + resolved "https://registry.yarnpkg.com/file-type/-/file-type-16.5.4.tgz#474fb4f704bee427681f98dd390058a172a6c2fd" + integrity sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw== dependencies: - readable-web-to-node-stream "^3.0.2" - strtok3 "^7.0.0" - token-types "^5.0.1" + readable-web-to-node-stream "^3.0.0" + strtok3 "^6.2.4" + token-types "^4.1.1" fill-range@^7.0.1: version "7.0.1" @@ -1104,17 +1107,17 @@ https-proxy-agent@^5.0.0: agent-base "6" debug "4" -i18next-fs-backend@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/i18next-fs-backend/-/i18next-fs-backend-2.2.0.tgz#016c865344632a666ea80653deae466fbfa6042c" - integrity sha512-VOPHhdDX0M/csRqEw+9Ectpf6wvTIg1MZDfAHxc3JKnAlJz7fcZSAKAeyDohOq0xuLx57esYpJopIvBaRb0Bag== +i18next-fs-backend@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/i18next-fs-backend/-/i18next-fs-backend-2.3.1.tgz#0c7d2459ff4a039e2b3228131809fbc0e74ff1a8" + integrity sha512-tvfXskmG/9o+TJ5Fxu54sSO5OkY6d+uMn+K6JiUGLJrwxAVfer+8V3nU8jq3ts9Pe5lXJv4b1N7foIjJ8Iy2Gg== -i18next@^23.5.1: - version "23.5.1" - resolved "https://registry.yarnpkg.com/i18next/-/i18next-23.5.1.tgz#7f7c35ffaa907618d9489f106d5006b09fbca3d3" - integrity sha512-JelYzcaCoFDaa+Ysbfz2JsGAKkrHiMG6S61+HLBUEIPaF40WMwW9hCPymlQGrP+wWawKxKPuSuD71WZscCsWHg== +i18next@^23.8.2: + version "23.8.2" + resolved "https://registry.yarnpkg.com/i18next/-/i18next-23.8.2.tgz#f3ff6ea929e0927d9717f0ed195ae46d05919900" + integrity sha512-Z84zyEangrlERm0ZugVy4bIt485e/H8VecGUZkZWrH7BDePG6jT73QdL9EA1tRTTVVMpry/MgWIP1FjEn0DRXA== dependencies: - "@babel/runtime" "^7.22.5" + "@babel/runtime" "^7.23.2" ieee754@^1.2.1: version "1.2.1" @@ -1356,7 +1359,7 @@ lodash.merge@^4.6.2: resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== -lodash.snakecase@^4.1.1: +lodash.snakecase@4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz#39d714a35357147837aefd64b5dcbb16becd8f8d" integrity sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw== @@ -1373,10 +1376,10 @@ lru-cache@^6.0.0: dependencies: yallist "^4.0.0" -magic-bytes.js@^1.0.15: - version "1.5.0" - resolved "https://registry.yarnpkg.com/magic-bytes.js/-/magic-bytes.js-1.5.0.tgz#f5531ca53e1c8dab5692b8dcfb360f7ca6c6b6bc" - integrity sha512-wJkXvutRbNWcc37tt5j1HyOK1nosspdh3dj6LUYYAvF6JYNqs53IfRvK9oEpcwiDA1NdoIi64yAMfdivPeVAyw== +magic-bytes.js@^1.5.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/magic-bytes.js/-/magic-bytes.js-1.8.0.tgz#8362793c60cd77c2dd77db6420be727192df68e2" + integrity sha512-lyWpfvNGVb5lu8YUAbER0+UMBTdR63w2mcSUlhhBTyVbxJvjgqwyAf3AZD6MprgK0uHuBoWXSDAMWLupX83o3Q== make-dir@^3.1.0: version "3.1.0" @@ -1437,18 +1440,18 @@ ms@^2.1.1: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== -music-metadata@^8.1.4: - version "8.1.4" - resolved "https://registry.yarnpkg.com/music-metadata/-/music-metadata-8.1.4.tgz#a65e59366bf347821ae2ea4fb0661bf202e37b7c" - integrity sha512-q9mw2qeESeJY69cXtdaum/YJstDimpP+mwZnb801iq20JpyY75v6uzcp6VfVXZDixpD2f9yWneJtA0TgSEypxA== +music-metadata@^7.14.0: + version "7.14.0" + resolved "https://registry.yarnpkg.com/music-metadata/-/music-metadata-7.14.0.tgz#74e3e5fc8e09b86d1a3e791fb5ce9ccdc4347ad9" + integrity sha512-xrm3w7SV0Wk+OythZcSbaI8mcr/KHd0knJieu8bVpaPfMv/Agz5EooCAPz3OR5hbYMiUG6dgAPKZKnMzV+3amA== dependencies: "@tokenizer/token" "^0.3.0" content-type "^1.0.5" debug "^4.3.4" - file-type "^18.2.1" + file-type "^16.5.4" media-typer "^1.1.0" - strtok3 "^7.0.0" - token-types "^5.0.1" + strtok3 "^6.3.0" + token-types "^4.2.1" natural-compare@^1.4.0: version "1.4.0" @@ -1616,10 +1619,10 @@ path-parse@^1.0.7: resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== -peek-readable@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/peek-readable/-/peek-readable-5.0.0.tgz#7ead2aff25dc40458c60347ea76cfdfd63efdfec" - integrity sha512-YtCKvLUOvwtMGmrniQPdO7MwPjgkFBtFIrmfSbYmYuq3tKDV/mcfAhBth1+C3ru7uXIZasc/pHnb+YDYNkkj4A== +peek-readable@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/peek-readable/-/peek-readable-4.1.0.tgz#4ece1111bf5c2ad8867c314c81356847e8a62e72" + integrity sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg== picomatch@^2.0.4, picomatch@^2.2.1: version "2.3.1" @@ -1665,7 +1668,7 @@ readable-stream@^3.0.2, readable-stream@^3.6.0: string_decoder "^1.1.1" util-deprecate "^1.0.1" -readable-web-to-node-stream@^3.0.2: +readable-web-to-node-stream@^3.0.0: version "3.0.2" resolved "https://registry.yarnpkg.com/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.2.tgz#5d52bb5df7b54861fd48d015e93a2cb87b3ee0bb" integrity sha512-ePeK6cc1EcKLEhJFt/AebMCLL+GgSKhuygrZ/GLaKZYEecIgIECf4UaUuaByiGtzckwR4ain9VzUh95T1exYGw== @@ -1812,11 +1815,6 @@ sodium@^3.0.2: dependencies: node-addon-api "*" -streamsearch@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-1.1.0.tgz#404dd1e2247ca94af554e841a8ef0eaa238da764" - integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg== - "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" @@ -1868,13 +1866,13 @@ strip-json-comments@^3.1.0, strip-json-comments@^3.1.1: resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== -strtok3@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/strtok3/-/strtok3-7.0.0.tgz#868c428b4ade64a8fd8fee7364256001c1a4cbe5" - integrity sha512-pQ+V+nYQdC5H3Q7qBZAz/MO6lwGhoC2gOAjuouGf/VO0m7vQRh8QNMl2Uf6SwAtzZ9bOw3UIeBukEGNJl5dtXQ== +strtok3@^6.2.4, strtok3@^6.3.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/strtok3/-/strtok3-6.3.0.tgz#358b80ffe6d5d5620e19a073aa78ce947a90f9a0" + integrity sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw== dependencies: "@tokenizer/token" "^0.3.0" - peek-readable "^5.0.0" + peek-readable "^4.1.0" supports-color@^5.5.0: version "5.5.0" @@ -1919,10 +1917,10 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" -token-types@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/token-types/-/token-types-5.0.1.tgz#aa9d9e6b23c420a675e55413b180635b86a093b4" - integrity sha512-Y2fmSnZjQdDb9W4w4r1tswlMHylzWIeOKpx0aZH9BgGtACHhrk3OkT52AzwcuqTRBZtvvnTjDBh8eynMulu8Vg== +token-types@^4.1.1, token-types@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/token-types/-/token-types-4.2.1.tgz#0f897f03665846982806e138977dbe72d44df753" + integrity sha512-6udB24Q737UD/SDsKAHI9FCRP7Bqc9D/MQUV02ORQg5iskjtLJlZJNdN4kKtcdtwCeWIwIHDGaUsTsCCAa8sFQ== dependencies: "@tokenizer/token" "^0.3.0" ieee754 "^1.2.1" @@ -1954,12 +1952,7 @@ tsconfig-paths@^3.14.1: minimist "^1.2.6" strip-bom "^3.0.0" -tslib@^2.5.0: - version "2.5.0" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.5.0.tgz#42bfed86f5787aeb41d031866c8f402429e0fddf" - integrity sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg== - -tslib@^2.6.1: +tslib@2.6.2, tslib@^2.6.2: version "2.6.2" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== @@ -2005,12 +1998,12 @@ undefsafe@^2.0.5: resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.5.tgz#38733b9327bdcd226db889fb723a6efd162e6e2c" integrity sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA== -undici@5.22.1: - version "5.22.1" - resolved "https://registry.yarnpkg.com/undici/-/undici-5.22.1.tgz#877d512effef2ac8be65e695f3586922e1a57d7b" - integrity sha512-Ji2IJhFXZY0x/0tVBXeQwgPlLWw13GVzpsWPQ3rV50IFMMof2I55PZZxtm4P6iNq+L5znYN9nSTAq0ZyE6lSJw== +undici@5.27.2: + version "5.27.2" + resolved "https://registry.yarnpkg.com/undici/-/undici-5.27.2.tgz#a270c563aea5b46cc0df2550523638c95c5d4411" + integrity sha512-iS857PdOEy/y3wlM3yRp+6SNQQ6xU0mmZcwRSriqk+et/cwWAtwmIGf6WkoDN2EK/AMdCO/dfXzIwi+rFMrjjQ== dependencies: - busboy "^1.6.0" + "@fastify/busboy" "^2.0.0" uri-js@^4.2.2: version "4.4.1" @@ -2084,11 +2077,16 @@ wrappy@1: resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== -ws@^8.13.0: +ws@8.14.2: version "8.14.2" resolved "https://registry.yarnpkg.com/ws/-/ws-8.14.2.tgz#6c249a806eb2db7a20d26d51e7709eab7b2e6c7f" integrity sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g== +ws@^8.14.2: + version "8.16.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.16.0.tgz#d1cd774f36fbc07165066a60e40323eab6446fd4" + integrity sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ== + yallist@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" -- cgit v1.2.3 From d98937b34bbb7582f4c13e0d18bcc30febe1d174 Mon Sep 17 00:00:00 2001 From: Andrew Lee Date: Thu, 15 Feb 2024 17:18:15 -0500 Subject: Finally fixed some issues - Voting should work properly? - Now the bot tells the user if they are already in vc - Thumbnails will be null when switching tracks --- AudioBackend/PlayAudio.js | 1 + Commands/join.js | 5 +++++ Commands/leave.js | 2 ++ Commands/pause.js | 14 +++++++++----- Commands/play.js | 16 ++++++++++------ Locales/en/translation.json | 3 +++ Utilities/Voting.js | 2 +- 7 files changed, 31 insertions(+), 12 deletions(-) (limited to 'Commands/pause.js') diff --git a/AudioBackend/PlayAudio.js b/AudioBackend/PlayAudio.js index a0cfb39..d671e5a 100644 --- a/AudioBackend/PlayAudio.js +++ b/AudioBackend/PlayAudio.js @@ -52,6 +52,7 @@ export async function playAudio(bot) { console.log(t('nowPlayingFile', { audio })); audioState(0); + audioPicture = null; const audioFile = audio; diff --git a/Commands/join.js b/Commands/join.js index 2f513cf..dd49969 100644 --- a/Commands/join.js +++ b/Commands/join.js @@ -23,6 +23,7 @@ import { SlashCommandBuilder } from 'discord.js'; import { voiceInit } from '../AudioBackend/VoiceInitialization.js'; import { PermissionFlagsBits } from 'discord-api-types/v10'; import { readFileSync } from 'node:fs'; +import { getVoiceConnection } from '@discordjs/voice'; import i18next from '../Utilities/i18n.js'; const { djRole, ownerID } = JSON.parse(readFileSync('./config.json', 'utf-8')); const t = i18next.t; @@ -34,6 +35,10 @@ export default { if (!interaction.member.voice.channel) return await interaction.reply({ content: t('voicePermission'), ephemeral: true }); if (!interaction.member.roles.cache.has(djRole) && interaction.user.id !== ownerID && !interaction.memberPermissions.has(PermissionFlagsBits.ManageGuild)) return interaction.reply({ content: t('rolePermission'), ephemeral: true }); + const connection = getVoiceConnection(interaction.guild.id); + if (connection) { + return await interaction.reply({ content: t('alreadyJoin'), ephemeral: true }); + } await interaction.reply({ content: t('joinVoice'), ephemeral: true }); return await voiceInit(bot); } diff --git a/Commands/leave.js b/Commands/leave.js index e6559ca..c78cfe0 100644 --- a/Commands/leave.js +++ b/Commands/leave.js @@ -24,6 +24,7 @@ import { destroyAudio } from '../AudioBackend/Shutdown.js'; import { PermissionFlagsBits } from 'discord-api-types/v10'; import { readFileSync } from 'node:fs'; import i18next from '../Utilities/i18n.js'; +import { playerStatus } from '../AudioBackend/AudioControl.js'; const { djRole, ownerID } = JSON.parse(readFileSync('./config.json', 'utf-8')); const t = i18next.t; export default { @@ -33,6 +34,7 @@ export default { async execute(interaction, bot) { if (!interaction.member.voice.channel) return await interaction.reply({ content: t('voicePermission'), ephemeral: true }); if (!interaction.member.roles.cache.has(djRole) && interaction.user.id !== ownerID && !interaction.memberPermissions.has(PermissionFlagsBits.ManageGuild)) return interaction.reply({ content: t('rolePermission'), ephemeral: true }); + if (playerStatus === 2) return await interaction.reply({ content: t('alreadyLeave'), ephemeral: true }); console.log(t('leaveVoice')); await destroyAudio(interaction); diff --git a/Commands/pause.js b/Commands/pause.js index bc649a6..1d04404 100644 --- a/Commands/pause.js +++ b/Commands/pause.js @@ -20,7 +20,7 @@ ***************************************************************************/ import { SlashCommandBuilder } from 'discord.js'; -import { toggleAudioState, isAudioStatePaused } from '../AudioBackend/AudioControl.js'; +import { toggleAudioState, isAudioStatePaused, playerStatus } from '../AudioBackend/AudioControl.js'; import { PermissionFlagsBits } from 'discord-api-types/v10'; import { readFileSync } from 'node:fs'; import i18next from '../Utilities/i18n.js'; @@ -34,11 +34,15 @@ export default { if (!interaction.member.voice.channel) return await interaction.reply({ content: t('voicePermission'), ephemeral: true }); if (!interaction.member.roles.cache.has(djRole) && interaction.user.id !== ownerID && !interaction.memberPermissions.has(PermissionFlagsBits.ManageGuild)) return interaction.reply({ content: t('rolePermission'), ephemeral: true }); - if (!isAudioStatePaused) { - toggleAudioState(); - return await interaction.reply({ content: t('pausingMusic'), ephemeral: true }); + if (playerStatus === 2) { + return await interaction.reply({ content: t('alreadyLeave'), ephemeral: true }); } else { - return await interaction.reply({ content: t('pausedAlready'), ephemeral: true }); + if (!isAudioStatePaused) { + toggleAudioState(); + return await interaction.reply({ content: t('pausingMusic'), ephemeral: true }); + } else { + return await interaction.reply({ content: t('pausedAlready'), ephemeral: true }); + } } } }; diff --git a/Commands/play.js b/Commands/play.js index 7cfe36b..d514cfe 100644 --- a/Commands/play.js +++ b/Commands/play.js @@ -21,7 +21,7 @@ import { SlashCommandBuilder } from 'discord.js'; import { inputAudio } from '../AudioBackend/QueueSystem.js'; -import { files, isAudioStatePaused, toggleAudioState } from '../AudioBackend/AudioControl.js'; +import { files, isAudioStatePaused, playerStatus, toggleAudioState } from '../AudioBackend/AudioControl.js'; import { audio } from '../AudioBackend/PlayAudio.js'; import { PermissionFlagsBits } from 'discord-api-types/v10'; import { readFileSync } from 'node:fs'; @@ -51,16 +51,20 @@ export default { if (integer < files.length) { await inputAudio(bot, integer); await votes.clear(); - return await interaction.reply({ content: t('nowPlayingFile', audio), ephemeral: true }); + return await interaction.reply({ content: t('nowPlayingFile', { audio }), ephemeral: true }); } else { return await interaction.reply({ content: t('numBig', { files: files.length }), ephemeral: true }); } } - if (isAudioStatePaused) { - toggleAudioState(); - return await interaction.reply({ content: t('resumingMusic'), ephemeral: true }); + if (playerStatus === 2) { + return await interaction.reply({ content: t('statusStopped'), ephemeral: true }); } else { - return await interaction.reply({ content: t('resumedAlready'), ephemeral: true }); + if (isAudioStatePaused) { + toggleAudioState(); + return await interaction.reply({ content: t('resumingMusic'), ephemeral: true }); + } else { + return await interaction.reply({ content: t('resumedAlready'), ephemeral: true }); + } } } }; diff --git a/Locales/en/translation.json b/Locales/en/translation.json index 04b6acc..aa42b78 100644 --- a/Locales/en/translation.json +++ b/Locales/en/translation.json @@ -62,6 +62,9 @@ "numBig": "Number is too big, choose a number that's less than {{files}}.", "resumingMusic": "Resuming music", "resumedAlready": "Music is already playing", + "statusStopped": "Music is currently not playing", + "alreadyLeave": "Already left the voice chat", + "alreadyJoin": "Already joined the voice chat", "reshufflePlaylist": "Reshuffling the playlist...", "reshuffleDisabled": "Shuffle mode is disabled, enable it in the configuration file to access this command.", "playlistDone": "Playlist Finished", diff --git a/Utilities/Voting.js b/Utilities/Voting.js index 5bea29c..0d986b8 100644 --- a/Utilities/Voting.js +++ b/Utilities/Voting.js @@ -60,7 +60,7 @@ export async function voteSkip(interaction, bot) { const members = voiceChannel.members.filter(m => !votes.has(m.id)); // Calculate the number of votes required to skip the audio track - const votesRequired = Math.ceil((members.size - votes.size) / 2); + const votesRequired = Math.ceil(members.size / 2); // Check if the message author has already voted if (votes.has(interaction.user.id)) { -- cgit v1.2.3