diff options
| author | Andrew Lee <alee14498@protonmail.com> | 2024-02-17 00:07:31 -0500 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-02-17 00:07:31 -0500 |
| commit | 214a83c0f696ac731c54b00bf7503f87e497afa6 (patch) | |
| tree | 15d57b08e69d19fb4c2f3effb9937aec8d042bdc | |
| parent | b29ab06623fd24cfc2a611bdd658b4d2ef934335 (diff) | |
| parent | c848f1d90fef40ffa81915d7dd875a2ee6d6c8d5 (diff) | |
| download | DLAP-214a83c0f696ac731c54b00bf7503f87e497afa6.tar.gz DLAP-214a83c0f696ac731c54b00bf7503f87e497afa6.tar.bz2 DLAP-214a83c0f696ac731c54b00bf7503f87e497afa6.zip | |
Merge pull request #17 from Alee14/testing
Merging testing branch
| -rw-r--r-- | .dockerignore | 4 | ||||
| -rw-r--r-- | .parlance.json | 11 | ||||
| -rw-r--r-- | AudioBackend/AudioControl.js | 20 | ||||
| -rw-r--r-- | AudioBackend/PlayAudio.js | 52 | ||||
| -rw-r--r-- | AudioBackend/QueueSystem.js | 8 | ||||
| -rw-r--r-- | AudioBackend/Shutdown.js | 11 | ||||
| -rw-r--r-- | AudioBackend/VoiceInitialization.js | 14 | ||||
| -rw-r--r-- | Commands/about.js | 23 | ||||
| -rw-r--r-- | Commands/join.js | 14 | ||||
| -rw-r--r-- | Commands/leave.js | 13 | ||||
| -rw-r--r-- | Commands/list.js | 11 | ||||
| -rw-r--r-- | Commands/next.js | 9 | ||||
| -rw-r--r-- | Commands/pause.js | 20 | ||||
| -rw-r--r-- | Commands/ping.js | 5 | ||||
| -rw-r--r-- | Commands/play.js | 24 | ||||
| -rw-r--r-- | Commands/previous.js | 8 | ||||
| -rw-r--r-- | Commands/reshuffle.js | 16 | ||||
| -rw-r--r-- | Commands/shutdown.js | 7 | ||||
| -rw-r--r-- | Commands/status.js | 44 | ||||
| -rw-r--r-- | Dockerfile | 23 | ||||
| -rw-r--r-- | Locales/en/translation.json | 82 | ||||
| -rw-r--r-- | README.md | 37 | ||||
| -rw-r--r-- | Utilities/Voting.js | 40 | ||||
| -rw-r--r-- | Utilities/i18n.js | 10 | ||||
| -rw-r--r-- | bot.js | 28 | ||||
| -rw-r--r-- | deploy-command.js | 26 | ||||
| -rw-r--r-- | package.json | 16 | ||||
| -rw-r--r-- | yarn.lock | 350 |
28 files changed, 603 insertions, 323 deletions
diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..698128e --- /dev/null +++ b/.dockerignore @@ -0,0 +1,4 @@ +music +.git +node_modules +config.json diff --git a/.parlance.json b/.parlance.json new file mode 100644 index 0000000..d25db98 --- /dev/null +++ b/.parlance.json @@ -0,0 +1,11 @@ +{ + "name": "Discord Local Audio Player", + "subprojects": [ + { + "name": "DLAP Bot", + "type": "i18next", + "path": "/Locales/{lang}/translation.json", + "baseLang": "en" + } + ] +} 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 e8a0f3a..d671e5a 100644 --- a/AudioBackend/PlayAudio.js +++ b/AudioBackend/PlayAudio.js @@ -21,11 +21,14 @@ import { createAudioResource } from '@discordjs/voice'; import { parseFile } from 'music-metadata'; import { readdirSync, readFileSync, writeFile } from 'node:fs'; -import { EmbedBuilder } from 'discord.js'; +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; @@ -38,6 +41,7 @@ export let audioTitle; export let audioArtist; export let audioYear; export let audioAlbum; +export let audioPicture; export let duration; const inputFiles = readdirSync('music'); @@ -45,9 +49,10 @@ 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); + audioPicture = null; const audioFile = audio; @@ -59,6 +64,13 @@ export async function playAudio(bot) { audioArtist = common.artist; audioYear = common.year; audioAlbum = common.album; + 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, { name: 'albumArt.png' }); + } } else { metadataEmpty = true; } @@ -70,34 +82,44 @@ 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); } }); } 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}` } ); - 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] }); + if (!channel) return console.error(t('statusChannelError')); + if (audioPicture) { + return await channel.send({ embeds: [statusEmbed], files: [audioPicture] }); + } else { + return await channel.send({ embeds: [statusEmbed] }); + } } export function updatePlaylist(option) { @@ -118,7 +140,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 23c750e..d74a9e9 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: 'Victor Moraes (Vicktor#7232) (Improving README)' }, + // { name: t('aboutForked'), value: '[your name] (username)' }, + { name: t('aboutFrameworks'), value: `Discord.JS ${version}\nmusic-metadata\ni18next` }, + { name: t('aboutLicense'), value: 'GNU General Public License v3.0' } ) - .setFooter({ text: '© Copyright 2020-2022 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 2649784..dd49969 100644 --- a/Commands/join.js +++ b/Commands/join.js @@ -23,17 +23,23 @@ 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; 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.member.permission.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 }); + 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 5d01822..c78cfe0 100644 --- a/Commands/leave.js +++ b/Commands/leave.js @@ -23,18 +23,21 @@ 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'; +import { playerStatus } from '../AudioBackend/AudioControl.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.member.permission.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 (playerStatus === 2) return await interaction.reply({ content: t('alreadyLeave'), 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 082bf25..c99fd6b 100644 --- a/Commands/next.js +++ b/Commands/next.js @@ -21,20 +21,21 @@ 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') .setDescription('Goes to next music') - /* .addSubcommand(subcommand => + .addSubcommand(subcommand => subcommand.setName('vote') - .setDescription('Voting to skip this audio track')) */ + .setDescription('Voting to skip this audio track')) .addSubcommand(subcommand => 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/pause.js b/Commands/pause.js index 9e6026c..1d04404 100644 --- a/Commands/pause.js +++ b/Commands/pause.js @@ -20,23 +20,29 @@ ***************************************************************************/ 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'; +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.member.permission.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 }); + if (playerStatus === 2) { + return await interaction.reply({ content: t('alreadyLeave'), ephemeral: true }); } else { - return await interaction.reply({ content: 'Music is already paused', 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/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 49f0d3e..d514cfe 100644 --- a/Commands/play.js +++ b/Commands/play.js @@ -21,12 +21,14 @@ 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'; 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,28 @@ 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.member.permission.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 }); + if (playerStatus === 2) { + return await interaction.reply({ content: t('statusStopped'), ephemeral: true }); } else { - return await interaction.reply({ content: 'Music is already playing', 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/Commands/previous.js b/Commands/previous.js index 5acca47..c5a45b5 100644 --- a/Commands/previous.js +++ b/Commands/previous.js @@ -21,19 +21,21 @@ 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') .setDescription('Goes to previous music') - /* .addSubcommand(subcommand => + .addSubcommand(subcommand => subcommand.setName('vote') - .setDescription('Voting to skip this audio track')) */ + .setDescription('Voting to skip this audio track')) .addSubcommand(subcommand => 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 340c91b..459575f 100644 --- a/Commands/reshuffle.js +++ b/Commands/reshuffle.js @@ -23,23 +23,25 @@ import { SlashCommandBuilder } from 'discord.js'; 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 { audioState, playerStatus } 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.member.permission.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 (playerStatus === 2) return await interaction.reply({ content: t('playerStopped'), 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..ee02126 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') @@ -38,19 +40,19 @@ export default { // Get the members of the voice channel who have not voted yet const voiceChannel = interaction.member.voice.channel; - const members = voiceChannel.members.filter(m => !votes.has(m.id)); + const members = voiceChannel.members.filter(m => !votes.has(m.id) && !m.user.bot); // 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); if (audioID >= files.length) { - audioName = 'Playlist Finished'; + audioName = t('playlistDone'); } else { audioName = files[audioID]; if (!metadataEmpty) { try { const { common } = await parseFile('music/' + audioName); - audioName = common.title; + audioName = common.title ? common.title : audioName.split('.').slice(0, -1).join('.'); } catch (error) { console.error(error.message); } @@ -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/Dockerfile b/Dockerfile new file mode 100644 index 0000000..be11515 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,23 @@ +FROM node:alpine AS build + +WORKDIR /usr/src/bot + +RUN apk add --update alpine-sdk libtool autoconf automake python3 + +COPY package.json ./ + +COPY yarn.lock ./ + +RUN yarn global add node-gyp + +RUN yarn install + +FROM node:alpine + +WORKDIR /usr/src/bot + +COPY --from=build /usr/src/bot/node_modules ./node_modules + +COPY . ./ + +CMD ["node", "bot.js"] diff --git a/Locales/en/translation.json b/Locales/en/translation.json index f86d2ad..68cd260 100644 --- a/Locales/en/translation.json +++ b/Locales/en/translation.json @@ -1,3 +1,83 @@ { - "botReady": "Bot is ready!" + "botReady": "Bot is ready!", + "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 using `/about`\n\nDetails:```{{e}}```", + "musicRepeatingFinished": "All music tracks in the playlist has finished, going back to the beginning...", + "musicPlaylistFinished": "All music tracks in the playlist has finished.", + "previousBeginningPlaylist": "You are at the beginning of the playlist, cannot go further", + "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 track...", + "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", + "statusStopped": "Music is currently not playing", + "alreadyLeave": "Already left the voice chat", + "alreadyJoin": "Already joined the voice chat", + "playerStopped": "Music player is currently not running", + "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.", + "forceSkip": "Force skipping this audio track..." } @@ -15,10 +15,38 @@ If you want to become a maintainer, you must at least know this source code, Jav Also you must join my discord server (Support Server) to communicate with me. # Recommended Software Requirements -- Latest version of NodeJS (v16.9.0+) +- Latest version of NodeJS (v16.11.0+) - Linux (or WSL for Windows users) - 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 -v <path to config>:/usr/src/bot/config.json -v <path to music>:/usr/src/bot/music --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 +- -v flag in Docker is used for volume mounting. It creates a bind mount, which allows you to specify a host path (on your local machine) and a container path (inside the Docker container). When the Docker container is run, the specified host path is mounted into the container at the specified container path. +- --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 dlap +``` + +- -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)], } ``` @@ -112,7 +141,7 @@ shutdown - Powers off the bot. # Forking When forking the project, you can make your own version of DLAP or help contribute to the project (See the "Contributing" section). -You need to edit `/commands/about.js` to uncomment the `{ name: 'Forked by', value: '[your name] (discord#0000)' }` section. +You need to edit `/commands/about.js` to uncomment the `{ name: 'Forked by', value: '[your name] (username)' }` section. Be sure to replace that with your name. diff --git a/Utilities/Voting.js b/Utilities/Voting.js index ad9b656..fe38ab0 100644 --- a/Utilities/Voting.js +++ b/Utilities/Voting.js @@ -18,18 +18,20 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. * ***************************************************************************/ -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 { @@ -37,7 +39,7 @@ async function commandCheck(interaction, bot) { } } -export async function voteSkip(interaction, bot) { /* +export async function voteSkip(interaction, bot) { if (interaction.commandName === 'next') { if (nextCheck !== true) { // Reset the votes if the current value of nextCheck is different from the command being executed @@ -55,44 +57,44 @@ export async function voteSkip(interaction, bot) { /* if (interaction.options.getSubcommand() === 'vote') { // Get the members of the voice channel who have not voted yet const voiceChannel = interaction.member.voice.channel; - const members = voiceChannel.members.filter(m => !votes.has(m.id)); + const members = voiceChannel.members.filter(m => !votes.has(m.id) && !m.user.bot); // 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)) { - 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('playerStopped'), ephemeral: true }); } } -*/ + if (interaction.options.getSubcommand() === 'force') { - 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 }); - 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('playerStopped'), 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); } }; @@ -21,9 +21,11 @@ import { Client, Events, GatewayIntentBits, EmbedBuilder, Collection, version, InteractionType } from 'discord.js'; import { voiceInit } from './AudioBackend/VoiceInitialization.js'; import { readdirSync, readFileSync } from 'node:fs'; +import i18next from './Utilities/i18n.js'; // import config from './config.json' assert { type: 'json' } Not supported by ESLint yet const { token, statusChannel, voiceChannel, djRole, ownerID, shuffle, repeat, presenceActivity, activityType } = JSON.parse(readFileSync('./config.json', 'utf-8')); const bot = new Client({ intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages, GatewayIntentBits.GuildVoiceStates] }); +const t = i18next.t; bot.login(token); // Slash Command Handler @@ -37,15 +39,15 @@ for (const file of commandFiles) { } bot.once(Events.ClientReady, async() => { - console.log('Bot is ready!'); - console.log(`Logged in as ${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('botReady')); + console.log(t('loggedIn', { bot: bot.user.tag })); + 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({ @@ -57,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); @@ -83,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/deploy-command.js b/deploy-command.js index fd9de7a..f464825 100644 --- a/deploy-command.js +++ b/deploy-command.js @@ -1,7 +1,5 @@ import fs, { readFileSync } from 'node:fs'; -import { REST } from '@discordjs/rest'; -import { Routes } from 'discord-api-types/v10'; -// import config from './config.json' assert {type: 'json'} +import { REST, Routes } from 'discord.js'; const { clientID, token } = JSON.parse(readFileSync('./config.json')); const commands = []; @@ -12,8 +10,22 @@ for (const file of commandFiles) { commands.push(command.data.toJSON()); } -const rest = new REST({ version: '10' }).setToken(token); +const rest = new REST().setToken(token); -rest.put(Routes.applicationCommands(clientID), { body: commands }) - .then(() => console.log('Successfully registered application commands.')) - .catch(console.error); +// and deploy your commands! +(async() => { + try { + console.log(`Started refreshing ${commands.length} application (/) commands.`); + + // The put method is used to fully refresh all commands in the guild with the current set + const data = await rest.put( + Routes.applicationCommands(clientID), + { body: commands } + ); + + console.log(`Successfully reloaded ${data.length} application (/) commands.`); + } catch (error) { + // And of course, make sure you catch and log any errors! + console.error(error); + } +})(); diff --git a/package.json b/package.json index a8f7379..4c309cb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "dlap", - "version": "1.5.1", + "version": "1.6.0", "type": "module", "main": "bot.js", "license": "GPL-3.0", @@ -10,14 +10,12 @@ }, "dependencies": { "@discordjs/opus": "^0.9.0", - "@discordjs/rest": "^1.6.0", - "@discordjs/voice": "^0.15.0", - "discord-api-types": "^0.37.35", - "discord.js": "^14.8.0", - "ffmpeg-static": "^5.1.0", - "i18next": "^22.4.5", - "i18next-fs-backend": "^2.1.0", - "music-metadata": "^8.1.0", + "@discordjs/voice": "^0.16.1", + "discord.js": "^14.14.1", + "ffmpeg-static": "^5.2.0", + "i18next": "^23.8.2", + "i18next-fs-backend": "^2.3.1", + "music-metadata": "^7.14.0", "sodium": "^3.0.2" }, "devDependencies": { @@ -2,12 +2,12 @@ # yarn lockfile v1 -"@babel/runtime@^7.20.6": - version "7.21.0" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.21.0.tgz#5b55c9d394e5fcf304909a8b00c07dc217b56673" - integrity sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw== +"@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.13.11" + regenerator-runtime "^0.14.0" "@derhuerst/http-basic@^8.2.0": version "8.2.4" @@ -19,22 +19,35 @@ http-response-object "^3.0.1" parse-cache-control "^1.0.1" -"@discordjs/builders@^1.4.0": - version "1.4.0" - resolved "https://registry.yarnpkg.com/@discordjs/builders/-/builders-1.4.0.tgz#b951b5e6ce4e459cd06174ce50dbd51c254c1d47" - integrity sha512-nEeTCheTTDw5kO93faM1j8ZJPonAX86qpq/QVoznnSa8WWcCgJpjlu6GylfINTDW6o7zZY0my2SYdxx2mfNwGA== +"@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/util" "^0.1.0" - "@sapphire/shapeshift" "^3.7.1" - discord-api-types "^0.37.20" + "@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.2" - tslib "^2.4.1" + ts-mixer "^6.0.3" + tslib "^2.6.2" -"@discordjs/collection@^1.3.0": - version "1.3.0" - resolved "https://registry.yarnpkg.com/@discordjs/collection/-/collection-1.3.0.tgz#65bf9674db72f38c25212be562bb28fa0dba6aa3" - integrity sha512-ylt2NyZ77bJbRij4h9u/wVy7qYw/aDqQLWnadjvDqW/WoWCxrsX6M3CIw9GVP5xcGCDxsrKj5e0r5evuFYwrKg== +"@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/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.61" "@discordjs/node-pre-gyp@^0.4.5": version "0.4.5" @@ -59,35 +72,51 @@ "@discordjs/node-pre-gyp" "^0.4.5" node-addon-api "^5.0.0" -"@discordjs/rest@^1.3.0", "@discordjs/rest@^1.4.0": - version "1.5.0" - resolved "https://registry.yarnpkg.com/@discordjs/rest/-/rest-1.5.0.tgz#dc15474ab98cf6f31291bf61bbc72bcf4f30cea2" - integrity sha512-lXgNFqHnbmzp5u81W0+frdXN6Etf4EUi8FAPcWpSykKd8hmlWh1xy6BmE0bsJypU1pxohaA8lQCgp70NUI3uzA== +"@discordjs/rest@^2.1.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.3.0" - "@discordjs/util" "^0.1.0" + "@discordjs/collection" "^2.0.0" + "@discordjs/util" "^1.0.2" "@sapphire/async-queue" "^1.5.0" - "@sapphire/snowflake" "^3.2.2" - discord-api-types "^0.37.23" - file-type "^18.0.0" - tslib "^2.4.1" - undici "^5.13.0" + "@sapphire/snowflake" "^3.5.1" + "@vladfrangu/async_event_emitter" "^2.2.2" + discord-api-types "0.37.61" + magic-bytes.js "^1.5.0" + tslib "^2.6.2" + undici "5.27.2" + +"@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/util@^0.1.0": - version "0.1.0" - resolved "https://registry.yarnpkg.com/@discordjs/util/-/util-0.1.0.tgz#e42ca1bf407bc6d9adf252877d1b206e32ba369a" - integrity sha512-e7d+PaTLVQav6rOc2tojh2y6FE8S7REkqLldq1XF4soCx74XB/DIjbVbVLtBemf0nLW77ntz0v+o5DytKwFNLQ== +"@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.9" + discord-api-types "0.37.61" + prism-media "^1.3.5" + tslib "^2.6.2" + ws "^8.14.2" -"@discordjs/voice@^0.14.0": - version "0.14.0" - resolved "https://registry.yarnpkg.com/@discordjs/voice/-/voice-0.14.0.tgz#8e5be1a76bb6c1e6e75236237f00241c763b2833" - integrity sha512-/LV8LSFuJ1c4OEW1ubPg3al2QNpUpwX8ZL+KL+LORmnUFVCtehSaEh+38uDfWg1O/TgiGI5vOLj4ZKql43drcw== +"@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: - "@types/ws" "^8.5.3" - discord-api-types "^0.37.20" - prism-media "^1.3.4" - tslib "^2.4.1" - ws "^8.11.0" + "@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.9" + "@vladfrangu/async_event_emitter" "^2.2.2" + 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" @@ -121,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" @@ -166,18 +200,18 @@ resolved "https://registry.yarnpkg.com/@sapphire/async-queue/-/async-queue-1.5.0.tgz#2f255a3f186635c4fb5a2381e375d3dfbc5312d8" integrity sha512-JkLdIsP8fPAdh9ZZjrbHWR/+mZj0wvKS5ICibcLrRI1j84UmLMshx5n9QmL8b95d4onJ2xxiyugTgSAX7AalmA== -"@sapphire/shapeshift@^3.7.1": - version "3.8.1" - resolved "https://registry.yarnpkg.com/@sapphire/shapeshift/-/shapeshift-3.8.1.tgz#b98dc6a7180f9b38219267917b2e6fa33f9ec656" - integrity sha512-xG1oXXBhCjPKbxrRTlox9ddaZTvVpOhYLmKmApD/vIWOV1xEYXnpoFs68zHIZBGbqztq6FrUPNPerIrO1Hqeaw== +"@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.2.2": - version "3.4.0" - resolved "https://registry.yarnpkg.com/@sapphire/snowflake/-/snowflake-3.4.0.tgz#25c012158a9feea2256c718985dbd6c1859a5022" - integrity sha512-zZxymtVO6zeXVMPds+6d7gv/OfnCc25M1Z+7ZLB0oPmeMTPeRWVPQSS16oDJy5ZsyCOLj7M6mbZml5gWXcVRNw== +"@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== "@tokenizer/token@^0.3.0": version "0.3.0" @@ -199,13 +233,25 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.60.tgz#35f3d6213daed95da7f0f73e75bcc6980e90597b" integrity sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw== -"@types/ws@^8.5.3": - 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.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" "*" + +"@vladfrangu/async_event_emitter@^2.2.2": + version "2.2.2" + resolved "https://registry.yarnpkg.com/@vladfrangu/async_event_emitter/-/async_event_emitter-2.2.2.tgz#84c5a3f8d648842cec5cc649b88df599af32ed88" + integrity sha512-HIzRG7sy88UZjBJamssEczH5q7t5+axva19UbZLO6u0ySbYPrwzWiXBcC0WuHyhKKoeCyneH+FvYzKQq/zTtkQ== + abbrev@1: version "1.1.1" resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" @@ -349,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" @@ -439,7 +478,7 @@ console-control-strings@^1.0.0, console-control-strings@^1.1.0: resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" integrity sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ== -content-type@^1.0.4: +content-type@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918" integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== @@ -490,28 +529,30 @@ 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.15, discord-api-types@^0.37.20, discord-api-types@^0.37.23: - version "0.37.35" - resolved "https://registry.yarnpkg.com/discord-api-types/-/discord-api-types-0.37.35.tgz#d0dad0bce7ce5bffc633030f17464e45148d9f28" - integrity sha512-iyKZ/82k7FX3lcmHiAvvWu5TmyfVo78RtghBV/YsehK6CID83k5SI03DKKopBcln+TiEIYw5MGgq7SJXSpNzMg== - -discord.js@^14.7.1: - version "14.7.1" - resolved "https://registry.yarnpkg.com/discord.js/-/discord.js-14.7.1.tgz#26079d0ff4d27daf02480a403c456121f0682bd9" - integrity sha512-1FECvqJJjjeYcjSm0IGMnPxLqja/pmG1B0W2l3lUY2Gi4KXiyTeQmU1IxWcbXHn2k+ytP587mMWqva2IA87EbA== - dependencies: - "@discordjs/builders" "^1.4.0" - "@discordjs/collection" "^1.3.0" - "@discordjs/rest" "^1.4.0" - "@discordjs/util" "^0.1.0" - "@sapphire/snowflake" "^3.2.2" - "@types/ws" "^8.5.3" - discord-api-types "^0.37.20" - fast-deep-equal "^3.1.3" - lodash.snakecase "^4.1.1" - tslib "^2.4.1" - undici "^5.13.0" - ws "^8.11.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.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" @@ -791,7 +832,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== @@ -813,10 +854,10 @@ fastq@^1.6.0: dependencies: reusify "^1.0.4" -ffmpeg-static@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/ffmpeg-static/-/ffmpeg-static-5.1.0.tgz#133500f4566570c5a0e96795152b0526d8c936ad" - integrity sha512-eEWOiGdbf7HKPeJI5PoJ0oCwkL0hckL2JdS4JOuB/gUETppwkEpq8nF0+e6VEQnDCo/iuoipbTUsn9QJmtpNkg== +ffmpeg-static@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/ffmpeg-static/-/ffmpeg-static-5.2.0.tgz#6ca64a5ed6e69ec4896d175c1f69dd575db7c5ef" + integrity sha512-WrM7kLW+do9HLr+H6tk7LzQ7kPqbAgLjdzNE32+u3Ff11gXt9Kkkd2nusGFrlWMIe+XaA97t+I8JS7sZIrvRgA== dependencies: "@derhuerst/http-basic" "^8.2.0" env-paths "^2.2.0" @@ -830,14 +871,14 @@ file-entry-cache@^6.0.1: dependencies: flat-cache "^3.0.4" -file-type@^18.0.0: - 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" @@ -1061,17 +1102,17 @@ https-proxy-agent@^5.0.0: agent-base "6" debug "4" -i18next-fs-backend@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/i18next-fs-backend/-/i18next-fs-backend-2.1.1.tgz#07c6393be856c5a398e3dfc1257bf8439841cd89" - integrity sha512-FTnj+UmNgT3YRml5ruRv0jMZDG7odOL/OP5PF5mOqvXud2vHrPOOs68Zdk6iqzL47cnnM0ZVkK2BAvpFeDJToA== +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@^22.4.5: - version "22.4.11" - resolved "https://registry.yarnpkg.com/i18next/-/i18next-22.4.11.tgz#8b6c9be95176de90d3f10a78af125d95d3a3258d" - integrity sha512-ShfTzXVMjXdF2iPiT/wbizOrssLh9Ab6VpuVROihLCAu+u25KbZiEYVgsA0W6g0SgjPa/JmGWcUEV/g6cKzEjQ== +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.20.6" + "@babel/runtime" "^7.23.2" ieee754@^1.2.1: version "1.2.1" @@ -1313,7 +1354,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== @@ -1330,6 +1371,11 @@ lru-cache@^6.0.0: dependencies: yallist "^4.0.0" +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" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" @@ -1389,18 +1435,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.0: - version "8.1.3" - resolved "https://registry.yarnpkg.com/music-metadata/-/music-metadata-8.1.3.tgz#bd2d6c35b9f4bb881d7349edae2cdbe18e3fcbba" - integrity sha512-Wo+gR2tt8vbricyaK/sMt/4o/YEjStJOD806iZzmLtM/9GrBvhkIDOijt9J8vdRkH4rgYDI4xNu8dp0O0vDryw== +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.4" + content-type "^1.0.5" debug "^4.3.4" - file-type "^18.0.0" + 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" @@ -1568,10 +1614,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" @@ -1583,7 +1629,7 @@ prelude-ls@^1.2.1: resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== -prism-media@^1.3.4: +prism-media@^1.3.5: version "1.3.5" resolved "https://registry.yarnpkg.com/prism-media/-/prism-media-1.3.5.tgz#ea1533229f304a1b774b158de40e98c765db0aa6" integrity sha512-IQdl0Q01m4LrkN1EGIE9lphov5Hy7WWlH6ulf5QdGePLlPas9p2mhgddTEHrlaXYjjFToM1/rWuwF37VF4taaA== @@ -1617,7 +1663,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== @@ -1631,10 +1677,10 @@ readdirp@~3.6.0: dependencies: picomatch "^2.2.1" -regenerator-runtime@^0.13.11: - version "0.13.11" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9" - integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg== +regenerator-runtime@^0.14.0: + version "0.14.0" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz#5e19d68eb12d486f797e15a3c6a918f7cec5eb45" + integrity sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA== regexp.prototype.flags@^1.4.3: version "1.4.3" @@ -1764,11 +1810,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" @@ -1820,13 +1861,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" @@ -1871,10 +1912,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" @@ -1891,7 +1932,7 @@ tr46@~0.0.3: resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== -ts-mixer@^6.0.2: +ts-mixer@^6.0.3: version "6.0.3" resolved "https://registry.yarnpkg.com/ts-mixer/-/ts-mixer-6.0.3.tgz#69bd50f406ff39daa369885b16c77a6194c7cae6" integrity sha512-k43M7uCG1AkTyxgnmI5MPwKoUvS/bRvLvUb7+Pgpdlmok8AoqmUaZxUUw8zKM5B1lqZrt41GjYgnvAi0fppqgQ== @@ -1906,10 +1947,10 @@ tsconfig-paths@^3.14.1: minimist "^1.2.6" strip-bom "^3.0.0" -tslib@^2.4.1: - version "2.5.0" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.5.0.tgz#42bfed86f5787aeb41d031866c8f402429e0fddf" - integrity sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg== +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== type-check@^0.4.0, type-check@~0.4.0: version "0.4.0" @@ -1952,12 +1993,12 @@ undefsafe@^2.0.5: resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.5.tgz#38733b9327bdcd226db889fb723a6efd162e6e2c" integrity sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA== -undici@^5.13.0: - version "5.20.0" - resolved "https://registry.yarnpkg.com/undici/-/undici-5.20.0.tgz#6327462f5ce1d3646bcdac99da7317f455bcc263" - integrity sha512-J3j60dYzuo6Eevbawwp1sdg16k5Tf768bxYK4TUJRH7cBM4kFCbf3mOnM/0E3vQYXvpxITbbWmBafaDbxLDz3g== +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" @@ -2031,10 +2072,15 @@ wrappy@1: resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== -ws@^8.11.0: - version "8.13.0" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.13.0.tgz#9a9fb92f93cf41512a0735c8f4dd09b8a1211cd0" - integrity sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA== +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" |
