From 45d6ffa1798bcbe532d4319f46a9839e607311c6 Mon Sep 17 00:00:00 2001 From: Marius Begby Date: Sun, 27 Aug 2023 17:52:49 +0200 Subject: [PATCH] fix: annotated the rest of commands --- config/default.js | 2 +- src/commands/info/help.ts | 11 +- src/commands/info/status.ts | 24 +- src/commands/player/filters.ts | 183 +++++++------ src/commands/player/leave.ts | 22 +- src/commands/player/loop.ts | 38 +-- src/commands/player/lyrics.ts | 22 +- src/commands/player/nowplaying.ts | 123 +++++---- src/commands/player/pause.ts | 36 ++- src/commands/player/play.ts | 240 ++++++++++-------- src/commands/player/queue.ts | 36 +-- src/commands/player/remove.ts | 32 ++- src/commands/player/seek.ts | 74 +++--- src/commands/player/shuffle.ts | 22 +- src/commands/player/skip.ts | 47 +++- src/commands/player/stop.ts | 22 +- src/commands/player/volume.ts | 14 +- src/commands/system/guilds.ts | 2 +- src/types/clientTypes.ts | 5 +- src/types/commandTypes.ts | 11 + src/types/configTypes.ts | 2 +- .../validation/systemCommandValidator.ts | 2 +- 22 files changed, 592 insertions(+), 378 deletions(-) diff --git a/config/default.js b/config/default.js index d2a566ba..739450f7 100644 --- a/config/default.js +++ b/config/default.js @@ -114,7 +114,7 @@ module.exports.playerOptions = { leaveOnEmpty: true, leaveOnEmptyCooldown: 1_800_000, leaveOnEnd: true, - leaveonEndCooldown: 1_800_000, + leaveOnEndCooldown: 1_800_000, leaveOnStop: true, leaveOnStopCooldown: 1_800_000, defaultVolume: 50, diff --git a/src/commands/info/help.ts b/src/commands/info/help.ts index 3ba7a01f..7e988010 100644 --- a/src/commands/info/help.ts +++ b/src/commands/info/help.ts @@ -8,6 +8,8 @@ import { BotOptions, EmbedOptions } from '../../types/configTypes'; const embedOptions: EmbedOptions = config.get('embedOptions'); const botOptions: BotOptions = config.get('botOptions'); module.exports = { + isNew: false, + isBeta: false, data: new SlashCommandBuilder() .setName('help') .setDescription('Show a list of commands and their usage.') @@ -19,10 +21,15 @@ module.exports = { module: 'slashCommand', name: '/help', executionId: executionId, - shardId: interaction.guild.shardId, - guildId: interaction.guild.id + shardId: interaction.guild?.shardId, + guildId: interaction.guild?.id }); + if (!client || !client.commands) { + logger.error('Client is undefined or does not have commands property.'); + return; + } + const commandList = client.commands .filter((command) => { // don't include system commands diff --git a/src/commands/info/status.ts b/src/commands/info/status.ts index d425a0c3..afced7a1 100644 --- a/src/commands/info/status.ts +++ b/src/commands/info/status.ts @@ -1,5 +1,5 @@ import config from 'config'; -import { EmbedBuilder, SlashCommandBuilder } from 'discord.js'; +import { Collection, EmbedBuilder, Guild, SlashCommandBuilder } from 'discord.js'; import osu from 'node-os-utils'; // @ts-ignore @@ -24,8 +24,8 @@ module.exports = { module: 'slashCommand', name: '/status', executionId: executionId, - shardId: interaction.guild.shardId, - guildId: interaction.guild.id + shardId: interaction.guild?.shardId, + guildId: interaction.guild?.id }); const uptimeString = await getUptimeFormatted({ executionId }); @@ -38,15 +38,20 @@ module.exports = { let totalTracks = 0; let totalListeners = 0; + if (!client || !client.shard) { + logger.error('Client is undefined or does not have shard property.'); + return; + } + await client.shard .broadcastEval(() => { /* eslint-disable no-undef */ return player.generateStatistics(); }) .then((results) => { - const queueCountList = []; - const trackCountList = []; - const listenerCountList = []; + const queueCountList: number[] = []; + const trackCountList: number[] = []; + const listenerCountList: number[] = []; results.map((result) => { queueCountList.push(result.queues.length); if (result.queues.length > 0) { @@ -70,10 +75,11 @@ module.exports = { await client.shard .fetchClientValues('guilds.cache') .then((results) => { - results.map((guildCache) => { + const guildCaches = results as Collection[]; + guildCaches.map((guildCache) => { if (guildCache) { - guildCount += guildCache.length; - memberCount += guildCache.reduce((acc, guildCache) => acc + guildCache.memberCount, 0); + guildCount += guildCache.size; + memberCount += guildCache.reduce((acc, guild) => acc + guild.memberCount, 0); } }); diff --git a/src/commands/player/filters.ts b/src/commands/player/filters.ts index 25c1948e..fb25c8e8 100644 --- a/src/commands/player/filters.ts +++ b/src/commands/player/filters.ts @@ -1,17 +1,22 @@ import config from 'config'; -import { useQueue } from 'discord-player'; +import { NodeResolvable, QueueFilters, useQueue } from 'discord-player'; import { - ActionRowBuilder, + APIActionRowComponent, + APIMessageActionRowComponent, ButtonBuilder, + ButtonStyle, + ComponentType, EmbedBuilder, + GuildMember, + Interaction, SlashCommandBuilder, StringSelectMenuBuilder, StringSelectMenuOptionBuilder } from 'discord.js'; import loggerModule from '../../services/logger'; -import { CommandParams } from '../../types/commandTypes'; -import { EmbedOptions, FFmpegFilterOptions } from '../../types/configTypes'; +import { CommandParams, CustomError } from '../../types/commandTypes'; +import { EmbedOptions, FFmpegFilterOption, FFmpegFilterOptions } from '../../types/configTypes'; import { queueDoesNotExist, queueNoCurrentTrack } from '../../utils/validation/queueValidator'; import { notInSameVoiceChannel, notInVoiceChannel } from '../../utils/validation/voiceChannelValidator'; @@ -31,15 +36,15 @@ module.exports = { module: 'slashCommand', name: '/filters', executionId: executionId, - shardId: interaction.guild.shardId, - guildId: interaction.guild.id + shardId: interaction.guild?.shardId, + guildId: interaction.guild?.id }); if (await notInVoiceChannel({ interaction, executionId })) { return; } - const queue = useQueue(interaction.guild.id); + const queue: NodeResolvable = useQueue(interaction.guild!.id)!; if (await queueDoesNotExist({ interaction, queue, executionId })) { return; @@ -53,12 +58,12 @@ module.exports = { return; } - const filterOptions = []; + const filterOptions: StringSelectMenuOptionBuilder[] = []; - ffmpegFilterOptions.availableFilters.forEach((filter) => { + ffmpegFilterOptions.availableFilters.forEach((filter: FFmpegFilterOption) => { let isEnabled = false; - if (queue.filters.ffmpeg.filters.includes(filter.value)) { + if (queue.filters.ffmpeg.filters.includes(filter.value as keyof QueueFilters)) { isEnabled = true; } @@ -77,31 +82,37 @@ module.exports = { .setPlaceholder('Select multiple options.') .setMinValues(0) .setMaxValues(filterOptions.length) - .addOptions(filterOptions); - - const filterActionRow = new ActionRowBuilder().addComponents(filterSelect); - - const disableFiltersActionRow = new ActionRowBuilder().addComponents( - new ButtonBuilder() - .setCustomId('disable-filters') - .setLabel('Disable all filters') - .setStyle('Secondary') - .setEmoji(embedOptions.icons.disable) - ); + .addOptions(filterOptions) + .toJSON(); + + const filterActionRow: APIActionRowComponent = { + type: ComponentType.ActionRow, + components: [filterSelect] + }; + + const disableButton = new ButtonBuilder() + .setCustomId('disable-filters') + .setLabel('Disable all filters') + .setStyle(ButtonStyle.Secondary) + .setEmoji(embedOptions.icons.disable) + .toJSON(); + + const disableFiltersActionRow: APIActionRowComponent = { + type: ComponentType.ActionRow, + components: [disableButton] + }; logger.debug('Sending info embed with action row components.'); const response = await interaction.editReply({ embeds: [ new EmbedBuilder() - .setDescription( - `**Toggle filters** ${embedOptions.icons.beta}\nEnable or disable audio filters for playback from the menu.` - ) + .setDescription('**Toggle filters**\nEnable or disable audio filters for playback from the menu.') .setColor(embedOptions.colors.info) ], components: [filterActionRow, disableFiltersActionRow] }); - const collectorFilter = (i) => i.user.id === interaction.user.id; + const collectorFilter = (i: Interaction) => i.user.id === interaction.user.id; try { const confirmation = await response.awaitMessageComponent({ filter: collectorFilter, @@ -130,14 +141,64 @@ module.exports = { logger.debug('Reset queue filters.'); } - if (confirmation.customId === 'disable-filters' || confirmation.values.length === 0) { + let authorName: string; + + if (interaction.member instanceof GuildMember) { + authorName = interaction.member.nickname || interaction.user.username; + } else { + authorName = interaction.user.username; + } + + if ('values' in confirmation) { + // if bassboost is enabled and not normalizer, also enable normalizer to avoid distrorion + if ( + (confirmation.values.includes('bassboost_low') || confirmation.values.includes('bassboost')) && + !confirmation.values.includes('normalizer') + ) { + confirmation.values.push('normalizer'); + } + + // Enable provided filters + queue.filters.ffmpeg.toggle(confirmation.values as (keyof QueueFilters)[]); + logger.debug(`Enabled filters ${confirmation.values.join(', ')}.`); + logger.debug('Responding with success embed.'); return await interaction.editReply({ embeds: [ new EmbedBuilder() .setAuthor({ - name: interaction.member.nickname || interaction.user.username, - iconURL: interaction.user.avatarURL() + name: authorName, + iconURL: interaction.user.avatarURL() || '' + }) + .setDescription( + `**${ + embedOptions.icons.success + } Filters toggled**\nNow using these filters:\n${confirmation.values + .map((enabledFilter: string) => { + const filter = ffmpegFilterOptions.availableFilters.find( + (filter) => enabledFilter == filter.value + ); + + if (!filter) { + return enabledFilter; + } + + return `- **${filter.emoji} ${filter.label}**`; + }) + .join('\n')}` + ) + .setColor(embedOptions.colors.success) + ], + components: [] + }); + } else if (confirmation.customId === 'disable-filters') { + logger.debug('Responding with success embed.'); + return await interaction.editReply({ + embeds: [ + new EmbedBuilder() + .setAuthor({ + name: authorName, + iconURL: interaction.user.avatarURL() || '' }) .setDescription( `**${embedOptions.icons.success} Disabled filters**\nAll audio filters have been disabled.` @@ -146,53 +207,33 @@ module.exports = { ], components: [] }); + } else { + logger.warn('Unhandled component interaction response.'); } - - // if bassboost is enabled and not normalizer, also enable normalizer to avoid distrorion - if ( - (confirmation.values.includes('bassboost_low') || confirmation.values.includes('bassboost')) && - !confirmation.values.includes('normalizer') - ) { - confirmation.values.push('normalizer'); - } - - // Enable provided filters - queue.filters.ffmpeg.toggle(confirmation.values); - logger.debug(`Enabled filters ${confirmation.values.join(', ')}.`); - - logger.debug('Responding with success embed.'); - return await interaction.editReply({ - embeds: [ - new EmbedBuilder() - .setAuthor({ - name: interaction.member.nickname || interaction.user.username, - iconURL: interaction.user.avatarURL() - }) - .setDescription( - `**${ - embedOptions.icons.success - } Filters toggled**\nNow using these filters:\n${confirmation.values - .map((enabledFilter) => { - const filter = ffmpegFilterOptions.availableFilters.find( - (filter) => enabledFilter == filter.value - ); - - return `- **${filter.emoji} ${filter.label}**`; - }) - .join('\n')}` - ) - .setColor(embedOptions.colors.success) - ], - components: [] - }); } catch (error) { - if (error.code === 'InteractionCollectorError') { - logger.debug('Interaction response timed out.'); + if (error instanceof CustomError) { + if (error.code === 'InteractionCollectorError') { + logger.debug('Interaction response timed out.'); + return; + } + + if (error.message === 'Collector received no interactions before ending with reason: time') { + logger.debug('Interaction response timed out.'); + return; + } + + logger.error(error, 'Unhandled error while awaiting or handling component interaction.'); return; + } else { + if ( + error instanceof Error && + error.message === 'Collector received no interactions before ending with reason: time' + ) { + logger.debug('Interaction response timed out.'); + return; + } + throw error; } - - logger.error(error, 'Unhandled error while awaiting or handling component interaction.'); - return; } } }; diff --git a/src/commands/player/leave.ts b/src/commands/player/leave.ts index d0193278..9b5308e1 100644 --- a/src/commands/player/leave.ts +++ b/src/commands/player/leave.ts @@ -1,6 +1,6 @@ import config from 'config'; -import { useQueue } from 'discord-player'; -import { EmbedBuilder, SlashCommandBuilder } from 'discord.js'; +import { NodeResolvable, useQueue } from 'discord-player'; +import { EmbedBuilder, GuildMember, SlashCommandBuilder } from 'discord.js'; import loggerModule from '../../services/logger'; import { CommandParams } from '../../types/commandTypes'; @@ -22,15 +22,15 @@ module.exports = { module: 'slashCommand', name: '/leave', executionId: executionId, - shardId: interaction.guild.shardId, - guildId: interaction.guild.id + shardId: interaction.guild?.shardId, + guildId: interaction.guild?.id }); if (await notInVoiceChannel({ interaction, executionId })) { return; } - const queue = useQueue(interaction.guild.id); + const queue: NodeResolvable = useQueue(interaction.guild!.id)!; if (!queue) { logger.debug('There is already no queue.'); @@ -56,13 +56,21 @@ module.exports = { logger.debug('Deleted the queue.'); } + let authorName: string; + + if (interaction.member instanceof GuildMember) { + authorName = interaction.member.nickname || interaction.user.username; + } else { + authorName = interaction.user.username; + } + logger.debug('Responding with success embed.'); return await interaction.editReply({ embeds: [ new EmbedBuilder() .setAuthor({ - name: interaction.member.nickname || interaction.user.username, - iconURL: interaction.user.avatarURL() + name: authorName, + iconURL: interaction.user.avatarURL() || '' }) .setDescription( `**${embedOptions.icons.success} Leaving channel**\nCleared the track queue and left voice channel.\n\nTo play more music, use the **\`/play\`** command!` diff --git a/src/commands/player/loop.ts b/src/commands/player/loop.ts index 623fcd49..1424691c 100644 --- a/src/commands/player/loop.ts +++ b/src/commands/player/loop.ts @@ -1,6 +1,6 @@ import config from 'config'; -import { useQueue } from 'discord-player'; -import { EmbedBuilder, SlashCommandBuilder } from 'discord.js'; +import { NodeResolvable, useQueue } from 'discord-player'; +import { EmbedBuilder, GuildMember, SlashCommandBuilder } from 'discord.js'; import loggerModule from '../../services/logger'; import { CommandParams } from '../../types/commandTypes'; @@ -36,15 +36,15 @@ module.exports = { module: 'slashCommand', name: '/loop', executionId: executionId, - shardId: interaction.guild.shardId, - guildId: interaction.guild.id + shardId: interaction.guild?.shardId, + guildId: interaction.guild?.id }); if (await notInVoiceChannel({ interaction, executionId })) { return; } - const queue = useQueue(interaction.guild.id); + const queue: NodeResolvable = useQueue(interaction.guild!.id)!; if (await queueDoesNotExist({ interaction, queue, executionId })) { return; @@ -61,7 +61,7 @@ module.exports = { [3, 'autoplay'] ]); - const mode = parseInt(interaction.options.getString('mode')); + const mode = parseInt(interaction.options.getString('mode')!); const modeUserString = loopModesFormatted.get(mode); const currentMode = queue.repeatMode; const currentModeUserString = loopModesFormatted.get(currentMode); @@ -76,7 +76,7 @@ module.exports = { .setDescription( `**${ currentMode === 3 ? embedOptions.icons.autoplay : embedOptions.icons.loop - } Current loop mode**\nThe looping mode is currently set to \`${currentModeUserString}\`.` + } Current loop mode**\nThe looping mode is currently set to **\`${currentModeUserString}\`**.` ) .setColor(embedOptions.colors.info) ] @@ -91,7 +91,7 @@ module.exports = { embeds: [ new EmbedBuilder() .setDescription( - `**${embedOptions.icons.warning} Oops!**\nLoop mode is already \`${modeUserString}\`.` + `**${embedOptions.icons.warning} Oops!**\nLoop mode is already **\`${modeUserString}\`**.` ) .setColor(embedOptions.colors.warning) ] @@ -112,7 +112,7 @@ module.exports = { embeds: [ new EmbedBuilder() .setDescription( - `**${embedOptions.icons.error} Uh-oh... Failed to change loop mode!**\nI tried to change the loop mode to \`${modeUserString}\`, but something went wrong.\n\nYou can try to perform the command again.\n\n_If you think this message is incorrect or the issue persists, please submit a bug report in the **[support server](${botOptions.serverInviteUrl})**._` + `**${embedOptions.icons.error} Uh-oh... Failed to change loop mode!**\nI tried to change the loop mode to **\`${modeUserString}\`**, but something went wrong.\n\nYou can try to perform the command again.\n\n_If you think this message is incorrect or the issue persists, please submit a bug report in the **[support server](${botOptions.serverInviteUrl})**._` ) .setColor(embedOptions.colors.error) .setFooter({ text: `Execution ID: ${executionId}` }) @@ -120,6 +120,14 @@ module.exports = { }); } + let authorName: string; + + if (interaction.member instanceof GuildMember) { + authorName = interaction.member.nickname || interaction.user.username; + } else { + authorName = interaction.user.username; + } + if (queue.repeatMode === 0) { logger.debug('Disabled loop mode.'); @@ -128,8 +136,8 @@ module.exports = { embeds: [ new EmbedBuilder() .setAuthor({ - name: interaction.member.nickname || interaction.user.username, - iconURL: interaction.user.avatarURL() + name: authorName, + iconURL: interaction.user.avatarURL() || '' }) .setDescription( `**${embedOptions.icons.success} Loop mode disabled**\nChanging loop mode from **\`${currentModeUserString}\`** to **\`${modeUserString}\`**.\n\nThe ${currentModeUserString} will no longer play on repeat!` @@ -147,8 +155,8 @@ module.exports = { embeds: [ new EmbedBuilder() .setAuthor({ - name: interaction.member.nickname || interaction.user.username, - iconURL: interaction.user.avatarURL() + name: authorName, + iconURL: interaction.user.avatarURL() || '' }) .setDescription( `**${embedOptions.icons.autoplaying} Loop mode changed**\nChanging loop mode from **\`${currentModeUserString}\`** to **\`${modeUserString}\`**.\n\nWhen the queue is empty, similar tracks will start playing!` @@ -165,8 +173,8 @@ module.exports = { embeds: [ new EmbedBuilder() .setAuthor({ - name: interaction.member.nickname || interaction.user.username, - iconURL: interaction.user.avatarURL() + name: authorName, + iconURL: interaction.user.avatarURL() || '' }) .setDescription( `**${embedOptions.icons.looping} Loop mode changed**\nChanging loop mode from **\`${currentModeUserString}\`** to **\`${modeUserString}\`**.\n\nThe ${modeUserString} will now play on repeat!` diff --git a/src/commands/player/lyrics.ts b/src/commands/player/lyrics.ts index 39868757..332fa836 100644 --- a/src/commands/player/lyrics.ts +++ b/src/commands/player/lyrics.ts @@ -1,5 +1,5 @@ import config from 'config'; -import { QueryType, useMainPlayer, useQueue } from 'discord-player'; +import { NodeResolvable, QueryType, useMainPlayer, useQueue } from 'discord-player'; import { EmbedBuilder, SlashCommandBuilder } from 'discord.js'; import { lyricsExtractor } from '@discord-player/extractor'; @@ -39,8 +39,8 @@ module.exports = { autocomplete: async ({ interaction, executionId }: CommandAutocompleteParams) => { const logger = loggerTemplate.child({ executionId: executionId, - shardId: interaction.guild.shardId, - guildId: interaction.guild.id + shardId: interaction.guild?.shardId, + guildId: interaction.guild?.id }); const query = interaction.options.getString('query', true); @@ -63,7 +63,7 @@ module.exports = { if (!lyricsResult) { logger.debug(`No Genius lyrics found for query '${query}', using player.search() as fallback.`); - const player = useMainPlayer(); + const player = useMainPlayer()!; const searchResults = await player.search(query); response = searchResults.tracks.slice(0, 1).map((track) => ({ name: @@ -99,12 +99,12 @@ module.exports = { execute: async ({ interaction, executionId }: CommandParams) => { const logger = loggerTemplate.child({ executionId: executionId, - shardId: interaction.guild.shardId, - guildId: interaction.guild.id + shardId: interaction.guild?.shardId, + guildId: interaction.guild?.id }); const query = interaction.options.getString('query'); - const queue = useQueue(interaction.guild.id); + const queue: NodeResolvable = useQueue(interaction.guild!.id)!; let geniusSearchQuery = ''; if (!query) { @@ -123,7 +123,7 @@ module.exports = { if (await queueNoCurrentTrack({ interaction, queue, executionId })) { return; } - geniusSearchQuery = queue.currentTrack.title.slice(0, 50); + geniusSearchQuery = queue.currentTrack!.title.slice(0, 50); logger.debug( `No input query provided, using current track. Using query for genius: '${geniusSearchQuery}'` @@ -133,7 +133,7 @@ module.exports = { let searchResult; if (query) { logger.debug(`Query input provided, using query '${query}' for player.search().`); - const player = useMainPlayer(); + const player = useMainPlayer()!; const searchResults = await player.search(query, { searchEngine: QueryType.SPOTIFY_SEARCH }); @@ -220,7 +220,7 @@ module.exports = { logger.debug('Lyrics text too long, splitting into multiple messages.'); const messageCount = Math.ceil(lyricsResult.lyrics.length / 3800); for (let i = 0; i < messageCount; i++) { - logger.debuf(`Lyrics, sending message ${i + 1} of ${messageCount}.`); + logger.debug(`Lyrics, sending message ${i + 1} of ${messageCount}.`); const message = lyricsResult.lyrics.slice(i * 3800, (i + 1) * 3800); if (i === 0) { logger.debug('Responding with info embed for first message with lyrics.'); @@ -239,7 +239,7 @@ module.exports = { continue; } else { logger.debug('Sending consecutive message with lyrics.'); - await interaction.channel.send({ + await interaction.channel!.send({ embeds: [ new EmbedBuilder() .setDescription(`\`\`\`fix\n${message}\`\`\``) diff --git a/src/commands/player/nowplaying.ts b/src/commands/player/nowplaying.ts index 423f89bd..f56a8d90 100644 --- a/src/commands/player/nowplaying.ts +++ b/src/commands/player/nowplaying.ts @@ -1,9 +1,19 @@ import config from 'config'; -import { useQueue } from 'discord-player'; -import { ActionRowBuilder, ButtonBuilder, EmbedBuilder, SlashCommandBuilder } from 'discord.js'; +import { NodeResolvable, Track, useQueue } from 'discord-player'; +import { + APIActionRowComponent, + APIMessageActionRowComponent, + ButtonBuilder, + ButtonStyle, + ComponentType, + EmbedBuilder, + GuildMember, + Interaction, + SlashCommandBuilder +} from 'discord.js'; import loggerModule from '../../services/logger'; -import { CommandParams } from '../../types/commandTypes'; +import { CommandParams, TrackMetadata, CustomError } from '../../types/commandTypes'; import { EmbedOptions, PlayerOptions } from '../../types/configTypes'; import { queueDoesNotExist, queueNoCurrentTrack } from '../../utils/validation/queueValidator'; import { notInSameVoiceChannel, notInVoiceChannel } from '../../utils/validation/voiceChannelValidator'; @@ -24,15 +34,15 @@ module.exports = { module: 'slashCommand', name: '/nowplaying', executionId: executionId, - shardId: interaction.guild.shardId, - guildId: interaction.guild.id + shardId: interaction.guild?.shardId, + guildId: interaction.guild?.id }); if (await notInVoiceChannel({ interaction, executionId })) { return; } - const queue = useQueue(interaction.guild.id); + const queue: NodeResolvable = useQueue(interaction.guild!.id)!; if (await queueDoesNotExist({ interaction, queue, executionId })) { return; @@ -62,28 +72,27 @@ module.exports = { ['arbitrary', embedOptions.icons.sourceArbitrary] ]); - const currentTrack = queue.currentTrack; + const currentTrack: Track = queue.currentTrack!; let author = currentTrack.author ? currentTrack.author : 'Unavailable'; if (author === 'cdn.discordapp.com') { author = 'Unavailable'; } - let plays = currentTrack.views !== 0 ? currentTrack.views : 0; - - if ( - plays === 0 && - currentTrack.metadata.bridge && - currentTrack.metadata.bridge.views !== 0 && - currentTrack.metadata.bridge.views !== undefined - ) { - plays = currentTrack.metadata.bridge.views; + const plays = currentTrack.views !== 0 ? currentTrack.views : 0; + + let displayPlays: string = plays.toLocaleString('en-US'); + + const metadata = currentTrack.metadata as TrackMetadata; + + if (plays === 0 && metadata.bridge && metadata.bridge.views !== 0 && metadata.bridge.views !== undefined) { + displayPlays = metadata.bridge.views.toLocaleString('en-US'); } else if (plays === 0) { - plays = 'Unavailable'; + displayPlays = 'Unavailable'; } - const source = sourceStringsFormatted.get(currentTrack.raw.source) ?? 'Unavailable'; + const source = sourceStringsFormatted.get(currentTrack.raw.source!) ?? 'Unavailable'; const queueLength = queue.tracks.data.length; - const timestamp = queue.node.getTimestamp(); + const timestamp = queue.node.getTimestamp()!; let bar = `**\`${timestamp.current.label}\`** ${queue.node.createProgressBar({ queue: false, length: playerOptions.progressBar.length ?? 12, @@ -93,7 +102,7 @@ module.exports = { rightChar: playerOptions.progressBar.rightChar ?? '▬' })} **\`${timestamp.total.label}\`**`; - if (currentTrack.raw.duration === 0 || currentTrack.duration === '0:00') { + if (Number(currentTrack.raw.duration) === 0 || currentTrack.duration === '0:00') { bar = '_No duration available._'; } @@ -101,13 +110,17 @@ module.exports = { bar = `${embedOptions.icons.liveTrack} **\`LIVE\`** - Playing continuously from live source.`; } - const nowPlayingActionRow = new ActionRowBuilder().addComponents( - new ButtonBuilder() - .setCustomId('nowplaying-skip') - .setLabel('Skip track') - .setStyle('Secondary') - .setEmoji(embedOptions.icons.nextTrack) - ); + const nowPlayingButton = new ButtonBuilder() + .setCustomId('nowplaying-skip') + .setLabel('Skip track') + .setStyle(ButtonStyle.Secondary) + .setEmoji(embedOptions.icons.nextTrack) + .toJSON(); // Convert the builder to a raw object + + const nowPlayingActionRow: APIActionRowComponent = { + type: ComponentType.ActionRow, // This might vary based on your discord.js version + components: [nowPlayingButton] + }; const loopModesFormatted = new Map([ [0, 'disabled'], @@ -125,15 +138,15 @@ module.exports = { embeds: [ new EmbedBuilder() .setAuthor({ - name: `Channel: ${queue.channel.name} (${queue.channel.bitrate / 1000}kbps)`, - iconURL: interaction.guild.iconURL() + name: `Channel: ${queue.channel!.name} (${queue.channel!.bitrate / 1000}kbps)`, + iconURL: interaction.guild!.iconURL() || '' }) .setDescription( (queue.node.isPaused() ? '**Currently Paused**\n' : `**${embedOptions.icons.audioPlaying} Now Playing**\n`) + `**[${currentTrack.title}](${currentTrack.raw.url ?? currentTrack.url})**` + - `\nRequested by: <@${currentTrack.requestedBy.id}>` + + `\nRequested by: <@${currentTrack.requestedBy?.id}>` + `\n ${bar}\n\n` + `${ queue.repeatMode === 0 @@ -151,12 +164,12 @@ module.exports = { }, { name: '**Plays**', - value: plays.toLocaleString('en-US'), + value: displayPlays, inline: true }, { name: '**Track source**', - value: `**${sourceIcons.get(currentTrack.raw.source)} [${source}](${ + value: `**${sourceIcons.get(currentTrack.raw.source!)} [${source}](${ currentTrack.raw.url ?? currentTrack.url })**`, inline: true @@ -165,15 +178,15 @@ module.exports = { .setFooter({ text: queueLength ? `${queueLength} other tracks in the queue...` : ' ' }) - .setThumbnail(queue.currentTrack.thumbnail) + .setThumbnail(queue.currentTrack!.thumbnail) .setColor(embedOptions.colors.info) ], - components: [nowPlayingActionRow] + components: [nowPlayingActionRow as APIActionRowComponent] }); logger.debug('Finished sending response.'); - const collectorFilter = (i) => i.user.id === interaction.user.id; + const collectorFilter = (i: Interaction) => i.user.id === interaction.user.id; try { const confirmation = await response.awaitMessageComponent({ filter: collectorFilter, @@ -222,7 +235,7 @@ module.exports = { const skippedTrack = queue.currentTrack; let durationFormat = - skippedTrack.raw.duration === 0 || skippedTrack.duration === '0:00' + Number(skippedTrack.raw.duration) === 0 || skippedTrack.duration === '0:00' ? '' : `\`${skippedTrack.duration}\``; @@ -234,13 +247,21 @@ module.exports = { const repeatModeUserString = loopModesFormatted.get(queue.repeatMode); + let authorName: string; + + if (interaction.member instanceof GuildMember) { + authorName = interaction.member.nickname || interaction.user.username; + } else { + authorName = interaction.user.username; + } + logger.debug('Responding with success embed.'); return await interaction.followUp({ embeds: [ new EmbedBuilder() .setAuthor({ - name: interaction.member.nickname || interaction.user.username, - iconURL: interaction.user.avatarURL() + name: authorName, + iconURL: interaction.user.avatarURL() || '' }) .setDescription( `**${embedOptions.icons.skipped} Skipped track**\n**${durationFormat} [${ @@ -263,13 +284,29 @@ module.exports = { }); } } catch (error) { - if (error.code === 'InteractionCollectorError') { - logger.debug('Interaction response timed out.'); + if (error instanceof CustomError) { + if (error.code === 'InteractionCollectorError') { + logger.debug('Interaction response timed out.'); + return; + } + + if (error.message === 'Collector received no interactions before ending with reason: time') { + logger.debug('Interaction response timed out.'); + return; + } + + logger.error(error, 'Unhandled error while awaiting or handling component interaction.'); return; + } else { + if ( + error instanceof Error && + error.message === 'Collector received no interactions before ending with reason: time' + ) { + logger.debug('Interaction response timed out.'); + return; + } + throw error; } - - logger.error(error, 'Unhandled error while awaiting or handling component interaction.'); - return; } } }; diff --git a/src/commands/player/pause.ts b/src/commands/player/pause.ts index 9c92e9d3..029cf318 100644 --- a/src/commands/player/pause.ts +++ b/src/commands/player/pause.ts @@ -1,6 +1,6 @@ import config from 'config'; -import { useQueue } from 'discord-player'; -import { EmbedBuilder, SlashCommandBuilder } from 'discord.js'; +import { NodeResolvable, useQueue } from 'discord-player'; +import { EmbedBuilder, GuildMember, SlashCommandBuilder } from 'discord.js'; import loggerModule from '../../services/logger'; import { CommandParams } from '../../types/commandTypes'; @@ -23,15 +23,15 @@ module.exports = { module: 'slashCommand', name: '/pause', executionId: executionId, - shardId: interaction.guild.shardId, - guildId: interaction.guild.id + shardId: interaction.guild?.shardId, + guildId: interaction.guild?.id }); if (await notInVoiceChannel({ interaction, executionId })) { return; } - const queue = useQueue(interaction.guild.id); + const queue: NodeResolvable = useQueue(interaction.guild!.id)!; if (await queueDoesNotExist({ interaction, queue, executionId })) { return; @@ -45,12 +45,14 @@ module.exports = { return; } + const currentTrack = queue.currentTrack!; + let durationFormat = - queue.currentTrack.raw.duration === 0 || queue.currentTrack.duration === '0:00' + Number(currentTrack.raw.duration) === 0 || currentTrack.duration === '0:00' ? '' - : `\`${queue.currentTrack.duration}\``; + : `\`${currentTrack.duration}\``; - if (queue.currentTrack.raw.live) { + if (currentTrack.raw.live) { durationFormat = `${embedOptions.icons.liveTrack} \`LIVE\``; } @@ -58,22 +60,28 @@ module.exports = { queue.node.setPaused(!queue.node.isPaused()); logger.debug(`Set paused state to ${queue.node.isPaused()}.`); + let authorName: string; + + if (interaction.member instanceof GuildMember) { + authorName = interaction.member.nickname || interaction.user.username; + } else { + authorName = interaction.user.username; + } + logger.debug('Responding with success embed.'); return await interaction.editReply({ embeds: [ new EmbedBuilder() .setAuthor({ - name: interaction.member.nickname || interaction.user.username, - iconURL: interaction.user.avatarURL() + name: authorName, + iconURL: interaction.user.avatarURL() || '' }) .setDescription( `**${embedOptions.icons.pauseResumed} ${ queue.node.isPaused() ? 'Paused Track' : 'Resumed track' - }**\n**${durationFormat} [${queue.currentTrack.title}](${ - queue.currentTrack.raw.url ?? queue.currentTrack.url - })**` + }**\n**${durationFormat} [${currentTrack.title}](${currentTrack.raw.url ?? currentTrack.url})**` ) - .setThumbnail(queue.currentTrack.thumbnail) + .setThumbnail(currentTrack.thumbnail) .setColor(embedOptions.colors.success) ] }); diff --git a/src/commands/player/play.ts b/src/commands/player/play.ts index 14ebb144..371689ce 100644 --- a/src/commands/player/play.ts +++ b/src/commands/player/play.ts @@ -1,6 +1,6 @@ import config from 'config'; import { useMainPlayer, useQueue } from 'discord-player'; -import { EmbedBuilder, SlashCommandBuilder } from 'discord.js'; +import { EmbedBuilder, GuildMember, SlashCommandBuilder } from 'discord.js'; import loggerModule from '../../services/logger'; import { CommandAutocompleteParams, CommandParams } from '../../types/commandTypes'; @@ -40,11 +40,11 @@ module.exports = { autocomplete: async ({ interaction, executionId }: CommandAutocompleteParams) => { const logger = loggerTemplate.child({ executionId: executionId, - shardId: interaction.guild.shardId, - guildId: interaction.guild.id + shardId: interaction.guild?.shardId, + guildId: interaction.guild?.id }); - const player = useMainPlayer(); + const player = useMainPlayer()!; const query = interaction.options.getString('query', true); const { lastQuery, results, timestamp } = recentQueries.get(interaction.user.id) || {}; @@ -92,8 +92,8 @@ module.exports = { execute: async ({ interaction, executionId }: CommandParams) => { const logger = loggerTemplate.child({ executionId: executionId, - shardId: interaction.guild.shardId, - guildId: interaction.guild.id + shardId: interaction.guild?.shardId, + guildId: interaction.guild?.id }); if (await notInVoiceChannel({ interaction, executionId })) { @@ -104,13 +104,13 @@ module.exports = { return; } - let queue = useQueue(interaction.guild.id); + let queue = useQueue(interaction.guild!.id); if (queue && (await notInSameVoiceChannel({ interaction, queue, executionId }))) { return; } - const player = useMainPlayer(); - const query = interaction.options.getString('query'); + const player = useMainPlayer()!; + const query = interaction.options.getString('query')!; const transformedQuery = await transformQuery({ query, executionId }); @@ -132,17 +132,17 @@ module.exports = { embeds: [ new EmbedBuilder() .setDescription( - `**${embedOptions.icons.warning} No track found**\nNo results found for \`${transformedQuery}\`.\n\nIf you specified a URL, please make sure it is valid and public.` + `**${embedOptions.icons.warning} No track found**\nNo results found for **\`${transformedQuery}\`**.\n\nIf you specified a URL, please make sure it is valid and public.` ) .setColor(embedOptions.colors.warning) ] }); } - queue = useQueue(interaction.guild.id); + queue = useQueue(interaction.guild!.id); const queueSize = queue?.size ?? 0; - if ((searchResult.playlist && searchResult.tracks.length) > playerOptions.maxQueueSize - queueSize) { + if ((searchResult.playlist! && searchResult.tracks.length) > playerOptions.maxQueueSize - queueSize) { logger.debug(`Playlist found but would exceed max queue size. Query: '${query}'.`); logger.debug('Responding with warning embed.'); @@ -162,7 +162,7 @@ module.exports = { try { logger.debug(`Attempting to add track with player.play(). Query: '${query}'.`); - ({ track } = await player.play(interaction.member.voice.channel, searchResult, { + ({ track } = await player.play((interaction.member as GuildMember).voice.channel!, searchResult, { requestedBy: interaction.user, nodeOptions: { leaveOnEmpty: playerOptions.leaveOnEmpty ?? true, @@ -185,98 +185,106 @@ module.exports = { } })); } catch (error) { - if (error.message.includes('Sign in to confirm your age')) { - logger.debug('Found track but failed to retrieve audio due to age confirmation warning.'); - - logger.debug('Responding with warning embed.'); - return await interaction.editReply({ - embeds: [ - new EmbedBuilder() - .setDescription( - `**${embedOptions.icons.warning} Cannot retrieve audio for track**\nThis audio source is age restricted and requires login to access. Because of this I cannot retrieve the audio for the track.\n\n_If you think this message is incorrect, please submit a bug report in the **[support server](${botOptions.serverInviteUrl})**._` - ) - .setColor(embedOptions.colors.warning) - ] - }); - } + if (error instanceof Error) { + if (error.message.includes('Sign in to confirm your age')) { + logger.debug('Found track but failed to retrieve audio due to age confirmation warning.'); + + logger.debug('Responding with warning embed.'); + return await interaction.editReply({ + embeds: [ + new EmbedBuilder() + .setDescription( + `**${embedOptions.icons.warning} Cannot retrieve audio for track**\nThis audio source is age restricted and requires login to access. Because of this I cannot retrieve the audio for the track.\n\n_If you think this message is incorrect, please submit a bug report in the **[support server](${botOptions.serverInviteUrl})**._` + ) + .setColor(embedOptions.colors.warning) + ] + }); + } - if (error.message.includes('The following content may contain')) { - logger.debug('Found track but failed to retrieve audio due to graphic/mature/sensitive topic warning.'); - - logger.debug('Responding with warning embed.'); - return await interaction.editReply({ - embeds: [ - new EmbedBuilder() - .setDescription( - `**${embedOptions.icons.warning} Cannot retrieve audio for track**\nThis audio source cannot be played as the video source has a warning for graphic or sensistive topics. It requires a manual confirmation to to play the video, and because of this I am unable to extract the audio for this source.\n\n_If you think this message is incorrect, please submit a bug report in the **[support server](${botOptions.serverInviteUrl})**._` - ) - .setColor(embedOptions.colors.warning) - ] - }); - } + if (error.message.includes('The following content may contain')) { + logger.debug( + 'Found track but failed to retrieve audio due to graphic/mature/sensitive topic warning.' + ); + + logger.debug('Responding with warning embed.'); + return await interaction.editReply({ + embeds: [ + new EmbedBuilder() + .setDescription( + `**${embedOptions.icons.warning} Cannot retrieve audio for track**\nThis audio source cannot be played as the video source has a warning for graphic or sensistive topics. It requires a manual confirmation to to play the video, and because of this I am unable to extract the audio for this source.\n\n_If you think this message is incorrect, please submit a bug report in the **[support server](${botOptions.serverInviteUrl})**._` + ) + .setColor(embedOptions.colors.warning) + ] + }); + } - if ( - (error.type === 'TypeError' && - (error.message.includes("Cannot read properties of null (reading 'createStream')") || - error.message.includes('Failed to fetch resources for ytdl streaming'))) || - error.message.includes('Could not extract stream for this track') - ) { - logger.debug(error, `Found track but failed to retrieve audio. Query: ${query}.`); - - logger.debug('Responding with error embed.'); - return await interaction.editReply({ - embeds: [ - new EmbedBuilder() - .setDescription( - `**${embedOptions.icons.error} Uh-oh... Failed to add track!**\nAfter finding a result, I was unable to retrieve audio for the track.\n\nYou can try to perform the command again.\n\n_If you think this message is incorrect, please submit a bug report in the **[support server](${botOptions.serverInviteUrl})**._` - ) - .setColor(embedOptions.colors.error) - .setFooter({ text: `Execution ID: ${executionId}` }) - ] - }); - } + if ( + error.message.includes("Cannot read properties of null (reading 'createStream')") || + error.message.includes('Failed to fetch resources for ytdl streaming') || + error.message.includes('Could not extract stream for this track') + ) { + logger.debug(error, `Found track but failed to retrieve audio. Query: ${query}.`); + + logger.debug('Responding with error embed.'); + return await interaction.editReply({ + embeds: [ + new EmbedBuilder() + .setDescription( + `**${embedOptions.icons.error} Uh-oh... Failed to add track!**\nAfter finding a result, I was unable to retrieve audio for the track.\n\nYou can try to perform the command again.\n\n_If you think this message is incorrect, please submit a bug report in the **[support server](${botOptions.serverInviteUrl})**._` + ) + .setColor(embedOptions.colors.error) + .setFooter({ text: `Execution ID: ${executionId}` }) + ] + }); + } - if (error.message === 'Cancelled') { - logger.debug(error, `Operation cancelled. Query: ${query}.`); - - logger.debug('Responding with error embed.'); - return await interaction.editReply({ - embeds: [ - new EmbedBuilder() - .setDescription( - `**${embedOptions.icons.error} Uh-oh... Failed to add track!**\nSomething unexpected happened and the operation was cancelled.\n\nYou can try to perform the command again.\n\n_If you think this message is incorrect, please submit a bug report in the **[support server](${botOptions.serverInviteUrl})**._` - ) - .setColor(embedOptions.colors.error) - .setFooter({ text: `Execution ID: ${executionId}` }) - ] - }); - } + if (error.message === 'Cancelled') { + logger.debug(error, `Operation cancelled. Query: ${query}.`); + + logger.debug('Responding with error embed.'); + return await interaction.editReply({ + embeds: [ + new EmbedBuilder() + .setDescription( + `**${embedOptions.icons.error} Uh-oh... Failed to add track!**\nSomething unexpected happened and the operation was cancelled.\n\nYou can try to perform the command again.\n\n_If you think this message is incorrect, please submit a bug report in the **[support server](${botOptions.serverInviteUrl})**._` + ) + .setColor(embedOptions.colors.error) + .setFooter({ text: `Execution ID: ${executionId}` }) + ] + }); + } - if (error.message === "Cannot read properties of null (reading 'createStream')") { - // Can happen if /play then /leave before track starts playing - logger.warn(error, 'Found track but failed to play back audio. Voice connection might be unavailable.'); - - logger.debug('Responding with error embed.'); - return await interaction.editReply({ - embeds: [ - new EmbedBuilder() - .setDescription( - `**${embedOptions.icons.error} Uh-oh... Failed to add track!**\nSomething unexpected happened and it was not possible to start playing the track. This could happen if the voice connection is lost or queue is destroyed while adding the track.\n\nYou can try to perform the command again.\n\n_If you think this message is incorrect, please submit a bug report in the **[support server](${botOptions.serverInviteUrl})**._` - ) - .setColor(embedOptions.colors.error) - .setFooter({ text: `Execution ID: ${executionId}` }) - ] - }); - } + if (error.message === "Cannot read properties of null (reading 'createStream')") { + // Can happen if /play then /leave before track starts playing + logger.warn( + error, + 'Found track but failed to play back audio. Voice connection might be unavailable.' + ); + + logger.debug('Responding with error embed.'); + return await interaction.editReply({ + embeds: [ + new EmbedBuilder() + .setDescription( + `**${embedOptions.icons.error} Uh-oh... Failed to add track!**\nSomething unexpected happened and it was not possible to start playing the track. This could happen if the voice connection is lost or queue is destroyed while adding the track.\n\nYou can try to perform the command again.\n\n_If you think this message is incorrect, please submit a bug report in the **[support server](${botOptions.serverInviteUrl})**._` + ) + .setColor(embedOptions.colors.error) + .setFooter({ text: `Execution ID: ${executionId}` }) + ] + }); + } - logger.error(error, 'Failed to play track with player.play(), unhandled error.'); + logger.error(error, 'Failed to play track with player.play(), unhandled error.'); + } else { + throw error; + } } logger.debug(`Successfully added track with player.play(). Query: '${query}'.`); - queue = useQueue(interaction.guild.id); + queue = useQueue(interaction.guild!.id); - if (!queue) { + if (!queue || !track) { logger.warn(`After player.play(), queue is undefined. Query: '${query}'.`); logger.debug('Responding with error embed.'); @@ -295,7 +303,7 @@ module.exports = { if ( track.source.length === 0 || track.source === 'arbitrary' || - track.thumnail === null || + track.thumbnail === null || track.thumbnail === undefined || track.thumbnail === '' ) { @@ -305,7 +313,8 @@ module.exports = { track.thumbnail = embedOptions.info.fallbackThumbnailUrl; } - let durationFormat = track.raw.duration === 0 || track.duration === '0:00' ? '' : `\`${track.duration}\``; + let durationFormat = + Number(track.raw.duration) === 0 || track.duration === '0:00' ? '' : `\`${track.duration}\``; if (track.raw.live) { durationFormat = `${embedOptions.icons.liveTrack} \`LIVE\``; @@ -314,13 +323,21 @@ module.exports = { if (searchResult.playlist && searchResult.tracks.length > 1) { logger.debug(`Playlist found and added with player.play(). Query: '${query}'`); + let authorName: string; + + if (interaction.member instanceof GuildMember) { + authorName = interaction.member.nickname || interaction.user.username; + } else { + authorName = interaction.user.username; + } + logger.debug('Responding with success embed.'); return await interaction.editReply({ embeds: [ new EmbedBuilder() .setAuthor({ - name: interaction.member.nickname || interaction.user.username, - iconURL: interaction.user.avatarURL() + name: authorName, + iconURL: interaction.user.avatarURL() || '' }) .setDescription( `**${embedOptions.icons.success} Added playlist to queue**\n**${durationFormat} [${ @@ -338,14 +355,21 @@ module.exports = { if (queue.currentTrack === track && queue.tracks.data.length === 0) { logger.debug(`Track found and added with player.play(), started playing. Query: '${query}'.`); + let authorName: string; + + if (interaction.member instanceof GuildMember) { + authorName = interaction.member.nickname || interaction.user.username; + } else { + authorName = interaction.user.username; + } + logger.debug('Responding with success embed.'); return await interaction.editReply({ embeds: [ new EmbedBuilder() .setAuthor({ - name: - interaction.member.nickname || interaction.member.nickname || interaction.user.username, - iconURL: interaction.user.avatarURL() + name: authorName, + iconURL: interaction.user.avatarURL() || '' }) .setDescription( `**${embedOptions.icons.audioStartedPlaying} Started playing**\n**${durationFormat} [${ @@ -360,13 +384,21 @@ module.exports = { logger.debug(`Track found and added with player.play(), added to queue. Query: '${query}'.`); + let authorName: string; + + if (interaction.member instanceof GuildMember) { + authorName = interaction.member.nickname || interaction.user.username; + } else { + authorName = interaction.user.username; + } + logger.debug('Responding with success embed.'); return await interaction.editReply({ embeds: [ new EmbedBuilder() .setAuthor({ - name: interaction.member.nickname || interaction.user.username, - iconURL: interaction.user.avatarURL() + name: authorName, + iconURL: interaction.user.avatarURL() || '' }) .setDescription( `${embedOptions.icons.success} **Added to queue**\n**${durationFormat} [${track.title}](${ diff --git a/src/commands/player/queue.ts b/src/commands/player/queue.ts index deb58c06..02208fd4 100644 --- a/src/commands/player/queue.ts +++ b/src/commands/player/queue.ts @@ -1,5 +1,5 @@ import config from 'config'; -import { useQueue } from 'discord-player'; +import { NodeResolvable, useQueue } from 'discord-player'; import { EmbedBuilder, SlashCommandBuilder } from 'discord.js'; import loggerModule from '../../services/logger'; @@ -24,15 +24,15 @@ module.exports = { module: 'slashCommand', name: '/queue', executionId: executionId, - shardId: interaction.guild.shardId, - guildId: interaction.guild.id + shardId: interaction.guild?.shardId, + guildId: interaction.guild?.id }); if (await notInVoiceChannel({ interaction, executionId })) { return; } - const queue = useQueue(interaction.guild.id); + const queue: NodeResolvable = useQueue(interaction.guild!.id)!; if (await notInSameVoiceChannel({ interaction, queue, executionId })) { return; @@ -50,9 +50,9 @@ module.exports = { embeds: [ new EmbedBuilder() .setDescription( - `**${embedOptions.icons.warning} Oops!**\nPage \`${ + `**${embedOptions.icons.warning} Oops!**\nPage **\`${ pageIndex + 1 - }\` is not a valid page number.\n\nThe queue is currently empty, first add some tracks with **\`/play\`**!` + }\`** is not a valid page number.\n\nThe queue is currently empty, first add some tracks with **\`/play\`**!` ) .setColor(embedOptions.colors.warning) ] @@ -67,8 +67,8 @@ module.exports = { embeds: [ new EmbedBuilder() .setAuthor({ - name: interaction.guild.name, - iconURL: interaction.guild.iconURL() + name: interaction.guild!.name, + iconURL: interaction.guild!.iconURL() || '' }) .setDescription(`**${embedOptions.icons.queue} Tracks in queue**\n${queueString}`) .setColor(embedOptions.colors.info) @@ -90,9 +90,9 @@ module.exports = { embeds: [ new EmbedBuilder() .setDescription( - `**${embedOptions.icons.warning} Oops!**\nPage \`${ + `**${embedOptions.icons.warning} Oops!**\nPage **\`${ pageIndex + 1 - }\` is not a valid page number.\n\nThere are only a total of \`${totalPages}\` pages in the queue.` + }\`** is not a valid page number.\n\nThere are only a total of **\`${totalPages}\`** pages in the queue.` ) .setColor(embedOptions.colors.warning) ] @@ -107,7 +107,7 @@ module.exports = { .slice(pageIndex * 10, pageIndex * 10 + 10) .map((track, index) => { let durationFormat = - track.raw.duration === 0 || track.duration === '0:00' ? '' : `\`${track.duration}\``; + Number(track.raw.duration) === 0 || track.duration === '0:00' ? '' : `\`${track.duration}\``; if (track.raw.live) { durationFormat = `${embedOptions.icons.liveTrack} \`LIVE\``; @@ -147,8 +147,8 @@ module.exports = { embeds: [ new EmbedBuilder() .setAuthor({ - name: `Channel: ${queue.channel.name} (${queue.channel.bitrate / 1000}kbps)`, - iconURL: interaction.guild.iconURL() + name: `Channel: ${queue.channel!.name} (${queue.channel!.bitrate / 1000}kbps)`, + iconURL: interaction.guild!.iconURL() || '' }) .setDescription( `${repeatModeString}` + `**${embedOptions.icons.queue} Tracks in queue**\n${queueString}` @@ -161,7 +161,7 @@ module.exports = { }); } else { logger.debug('Queue exists with current track, gathering information.'); - const timestamp = queue.node.getTimestamp(); + const timestamp = queue.node.getTimestamp()!; let bar = `**\`${timestamp.current.label}\`** ${queue.node.createProgressBar({ queue: false, length: playerOptions.progressBar.length ?? 12, @@ -171,7 +171,7 @@ module.exports = { rightChar: playerOptions.progressBar.rightChar ?? '▬' })} **\`${timestamp.total.label}\`**`; - if (currentTrack.raw.duration === 0 || currentTrack.duration === '0:00') { + if (Number(currentTrack.raw.duration) === 0 || currentTrack.duration === '0:00') { bar = '_No duration available._'; } @@ -184,15 +184,15 @@ module.exports = { embeds: [ new EmbedBuilder() .setAuthor({ - name: `Channel: ${queue.channel.name} (${queue.channel.bitrate / 1000}kbps)`, - iconURL: interaction.guild.iconURL() + name: `Channel: ${queue.channel!.name} (${queue.channel!.bitrate / 1000}kbps)`, + iconURL: interaction.guild!.iconURL() || '' }) .setDescription( `**${embedOptions.icons.audioPlaying} Now playing**\n` + (currentTrack ? `**[${currentTrack.title}](${currentTrack.raw.url ?? currentTrack.url})**` : 'None') + - `\nRequested by: <@${currentTrack.requestedBy.id}>` + + `\nRequested by: <@${currentTrack.requestedBy?.id}>` + `\n ${bar}\n\n` + `${repeatModeString}` + `**${embedOptions.icons.queue} Tracks in queue**\n${queueString}` diff --git a/src/commands/player/remove.ts b/src/commands/player/remove.ts index 324dc927..de23dc24 100644 --- a/src/commands/player/remove.ts +++ b/src/commands/player/remove.ts @@ -1,6 +1,6 @@ import config from 'config'; -import { useQueue } from 'discord-player'; -import { EmbedBuilder, SlashCommandBuilder } from 'discord.js'; +import { NodeResolvable, useQueue } from 'discord-player'; +import { EmbedBuilder, GuildMember, SlashCommandBuilder } from 'discord.js'; import loggerModule from '../../services/logger'; import { CommandParams } from '../../types/commandTypes'; @@ -30,15 +30,15 @@ module.exports = { module: 'slashCommand', name: '/remove', executionId: executionId, - shardId: interaction.guild.shardId, - guildId: interaction.guild.id + shardId: interaction.guild?.shardId, + guildId: interaction.guild?.id }); if (await notInVoiceChannel({ interaction, executionId })) { return; } - const queue = useQueue(interaction.guild.id); + const queue: NodeResolvable = useQueue(interaction.guild!.id)!; if (await queueDoesNotExist({ interaction, queue, executionId })) { return; @@ -48,7 +48,7 @@ module.exports = { return; } - const removeTrackNumber = interaction.options.getNumber('tracknumber'); + const removeTrackNumber = interaction.options.getNumber('tracknumber')!; if (removeTrackNumber > queue.tracks.data.length) { logger.debug('Specified track number is higher than total tracks.'); @@ -58,7 +58,7 @@ module.exports = { embeds: [ new EmbedBuilder() .setDescription( - `**${embedOptions.icons.warning} Oops!**\nTrack \`${removeTrackNumber}\` is not a valid track number. There are a total of\`${queue.tracks.data.length}\` tracks in the queue.\n\nView tracks added to the queue with **\`/queue\`**.` + `**${embedOptions.icons.warning} Oops!**\nTrack **\`${removeTrackNumber}\`** is not a valid track number. There are a total of **\`${queue.tracks.data.length}\`** tracks in the queue.\n\nView tracks added to the queue with **\`/queue\`**.` ) .setColor(embedOptions.colors.warning) ] @@ -66,22 +66,32 @@ module.exports = { } // Remove specified track number from queue - const removedTrack = queue.node.remove(removeTrackNumber - 1); + const removedTrack = queue.node.remove(removeTrackNumber - 1)!; logger.debug(`Removed track '${removedTrack.url}' from queue.`); let durationFormat = - removedTrack.raw.duration === 0 || removedTrack.duration === '0:00' ? '' : `\`${removedTrack.duration}\``; + Number(removedTrack.raw.duration) === 0 || removedTrack.duration === '0:00' + ? '' + : `\`${removedTrack.duration}\``; if (removedTrack.raw.live) { durationFormat = `${embedOptions.icons.liveTrack} \`LIVE\``; } + let authorName: string; + + if (interaction.member instanceof GuildMember) { + authorName = interaction.member.nickname || interaction.user.username; + } else { + authorName = interaction.user.username; + } + logger.debug('Responding with success embed.'); return await interaction.editReply({ embeds: [ new EmbedBuilder() .setAuthor({ - name: interaction.member.nickname || interaction.user.username, - iconURL: interaction.user.avatarURL() + name: authorName, + iconURL: interaction.user.avatarURL() || '' }) .setDescription( `**${embedOptions.icons.success} Removed track**\n**${durationFormat} [${removedTrack.title}](${ diff --git a/src/commands/player/seek.ts b/src/commands/player/seek.ts index 88a03390..2d188510 100644 --- a/src/commands/player/seek.ts +++ b/src/commands/player/seek.ts @@ -1,6 +1,6 @@ import config from 'config'; -import { useQueue } from 'discord-player'; -import { EmbedBuilder, SlashCommandBuilder } from 'discord.js'; +import { NodeResolvable, useQueue } from 'discord-player'; +import { EmbedBuilder, GuildMember, SlashCommandBuilder } from 'discord.js'; import loggerModule from '../../services/logger'; import { CommandParams } from '../../types/commandTypes'; @@ -29,15 +29,15 @@ module.exports = { module: 'slashCommand', name: '/seek', executionId: executionId, - shardId: interaction.guild.shardId, - guildId: interaction.guild.id + shardId: interaction.guild?.shardId, + guildId: interaction.guild?.id }); if (await notInVoiceChannel({ interaction, executionId })) { return; } - const queue = useQueue(interaction.guild.id); + const queue: NodeResolvable = useQueue(interaction.guild!.id)!; if (await queueDoesNotExist({ interaction, queue, executionId })) { return; @@ -52,7 +52,8 @@ module.exports = { } const durationInput = interaction.options.getString('duration'); - let durationArray = durationInput.split(':'); + + let durationArray = durationInput!.split(':'); switch (durationArray.length) { case 1: @@ -65,6 +66,12 @@ module.exports = { break; } + durationArray = durationArray.map((value) => { + return value.padStart(2, '0'); + }); + + const durationString = durationArray.join(':'); + if (durationArray.length === 0 || durationArray.length > 3) { logger.debug('Invalid duration format input.'); @@ -73,22 +80,16 @@ module.exports = { embeds: [ new EmbedBuilder() .setDescription( - `**${embedOptions.icons.warning} Oops!**\nYou entered an invalid duration format, \`${durationString}\`.\n\nPlease use the format \`HH:mm:ss\`, \`mm:ss\` or \`ss\`, where \`HH\` is hours, \`mm\` is minutes and \`ss\` is seconds.\n\n**Examples:**\n` + - '- `/seek` **`1:24:12`** - Seek to 1 hour, 24 minutes and 12 seconds.\n' + - '- `/seek` **`3:27`** - Seek to 3 minutes and 27 seconds.\n' + - '- `/seek` **`42`** - Seek to 42 seconds.' + `**${embedOptions.icons.warning} Oops!**\nYou entered an invalid duration format, **\`${durationString}\`**.\n\nPlease use the format **\`HH:mm:ss\`**, **\`mm:ss\`** or **\`ss\`**, where **\`HH\`** is hours, **\`mm\`** is minutes and **\`ss\`** is seconds.\n\n**Examples:**\n` + + '- **`/seek`** **`1:24:12`** - Seek to 1 hour, 24 minutes and 12 seconds.\n' + + '- **`/seek`** **`3:27`** - Seek to 3 minutes and 27 seconds.\n' + + '- **`/seek`** **`42`** - Seek to 42 seconds.' ) .setColor(embedOptions.colors.warning) ] }); } - durationArray = durationArray.map((value) => { - return value.padStart(2, '0'); - }); - - const durationString = durationArray.join(':'); - const validElements = durationArray.every((value) => { return value.length === 2; }); @@ -101,10 +102,10 @@ module.exports = { embeds: [ new EmbedBuilder() .setDescription( - `**${embedOptions.icons.warning} Oops!**\nYou entered an invalid duration format, \`${durationString}\`.\n\nPlease use the format \`HH:mm:ss\`, \`mm:ss\` or \`ss\`, where \`HH\` is hours, \`mm\` is minutes and \`ss\` is seconds.\n\n**Examples:**\n` + - '- `/seek` **`1:24:12`** - Seek to 1 hour, 24 minutes and 12 seconds.\n' + - '- `/seek` **`3:27`** - Seek to 3 minutes and 27 seconds.\n' + - '- `/seek` **`42`** - Seek to 42 seconds.' + `**${embedOptions.icons.warning} Oops!**\nYou entered an invalid duration format, **\`${durationString}\`**.\n\nPlease use the format **\`HH:mm:ss\`**, **\`mm:ss\`** or **\`ss\`**, where **\`HH\`** is hours, **\`mm\`** is minutes and **\`ss\`** is seconds.\n\n**Examples:**\n` + + '- **`/seek`** **`1:24:12`** - Seek to 1 hour, 24 minutes and 12 seconds.\n' + + '- **`/seek`** **`3:27`** - Seek to 3 minutes and 27 seconds.\n' + + '- **`/seek`** **`42`** - Seek to 42 seconds.' ) .setColor(embedOptions.colors.warning) ] @@ -124,18 +125,19 @@ module.exports = { embeds: [ new EmbedBuilder() .setDescription( - `**${embedOptions.icons.warning} Oops!**\nYou entered an invalid duration format, \`${durationString}\`.\n\nPlease use the format \`HH:mm:ss\`, \`mm:ss\` or \`ss\`, where \`HH\` is hours, \`mm\` is minutes and \`ss\` is seconds.\n\n**Examples:**\n` + - '- `/seek` **`1:24:12`** - Seek to 1 hour, 24 minutes and 12 seconds.\n' + - '- `/seek` **`3:27`** - Seek to 3 minutes and 27 seconds.\n' + - '- `/seek` **`42`** - Seek to 42 seconds.' + `**${embedOptions.icons.warning} Oops!**\nYou entered an invalid duration format, **\`${durationString}\`**.\n\nPlease use the format **\`HH:mm:ss\`**, **\`mm:ss\`** or **\`ss\`**, where **\`HH\`** is hours, **\`mm\`** is minutes and **\`ss\`** is seconds.\n\n**Examples:**\n` + + '- **`/seek`** **`1:24:12`** - Seek to 1 hour, 24 minutes and 12 seconds.\n' + + '- **`/seek`** **`3:27`** - Seek to 3 minutes and 27 seconds.\n' + + '- **`/seek`** **`42`** - Seek to 42 seconds.' ) .setColor(embedOptions.colors.warning) ] }); } - const currentTrackMaxDurationInMs = queue.currentTrack.durationMS; - const durationInMilliseconds = durationArray[0] * 3600000 + durationArray[1] * 60000 + durationArray[2] * 1000; + const currentTrackMaxDurationInMs = queue.currentTrack!.durationMS; + const durationInMilliseconds: number = + Number(durationArray[0]) * 3600000 + Number(durationArray[1]) * 60000 + Number(durationArray[2]) * 1000; if (durationInMilliseconds > currentTrackMaxDurationInMs - 1000) { logger.debug('Duration specified is longer than the track duration.'); @@ -145,7 +147,11 @@ module.exports = { embeds: [ new EmbedBuilder() .setDescription( - `**${embedOptions.icons.warning} Oops!**\nYou entered **\`${durationString}\`**, which is a duration that is longer than the duration for the current track.\n\nPlease try a duration that is less than the duration of the track (**\`${queue.currentTrack.duration}\`**).` + `**${ + embedOptions.icons.warning + } Oops!**\nYou entered **\`${durationString}\`**, which is a duration that is longer than the duration for the current track.\n\nPlease try a duration that is less than the duration of the track (**\`${ + queue.currentTrack!.duration + }\`**).` ) .setColor(embedOptions.colors.warning) ] @@ -155,18 +161,26 @@ module.exports = { queue.node.seek(durationInMilliseconds); logger.debug(`Seeked to '${durationString}' in current track.`); + let authorName: string; + + if (interaction.member instanceof GuildMember) { + authorName = interaction.member.nickname || interaction.user.username; + } else { + authorName = interaction.user.username; + } + logger.debug('Responding with success embed.'); return await interaction.editReply({ embeds: [ new EmbedBuilder() .setAuthor({ - name: interaction.member.nickname || interaction.user.username, - iconURL: interaction.user.avatarURL() + name: authorName, + iconURL: interaction.user.avatarURL() || '' }) .setDescription( `**${embedOptions.icons.success} Seeking to duration**\nSeeking to **\`${durationString}\`** in current track.` ) - .setThumbnail(queue.currentTrack.thumbnail) + .setThumbnail(queue.currentTrack!.thumbnail) .setColor(embedOptions.colors.success) ] }); diff --git a/src/commands/player/shuffle.ts b/src/commands/player/shuffle.ts index 990a3fef..e5692e8d 100644 --- a/src/commands/player/shuffle.ts +++ b/src/commands/player/shuffle.ts @@ -1,6 +1,6 @@ import config from 'config'; -import { useQueue } from 'discord-player'; -import { EmbedBuilder, SlashCommandBuilder } from 'discord.js'; +import { NodeResolvable, useQueue } from 'discord-player'; +import { EmbedBuilder, GuildMember, SlashCommandBuilder } from 'discord.js'; import loggerModule from '../../services/logger'; import { CommandParams } from '../../types/commandTypes'; @@ -23,15 +23,15 @@ module.exports = { module: 'slashCommand', name: '/shuffle', executionId: executionId, - shardId: interaction.guild.shardId, - guildId: interaction.guild.id + shardId: interaction.guild?.shardId, + guildId: interaction.guild?.id }); if (await notInVoiceChannel({ interaction, executionId })) { return; } - const queue = useQueue(interaction.guild.id); + const queue: NodeResolvable = useQueue(interaction.guild!.id)!; if (await queueDoesNotExist({ interaction, queue, executionId })) { return; @@ -48,13 +48,21 @@ module.exports = { queue.tracks.shuffle(); logger.debug('Shuffled queue tracks.'); + let authorName: string; + + if (interaction.member instanceof GuildMember) { + authorName = interaction.member.nickname || interaction.user.username; + } else { + authorName = interaction.user.username; + } + logger.debug('Responding with success embed.'); return await interaction.editReply({ embeds: [ new EmbedBuilder() .setAuthor({ - name: interaction.member.nickname || interaction.user.username, - iconURL: interaction.user.avatarURL() + name: authorName, + iconURL: interaction.user.avatarURL() || '' }) .setDescription( `**${embedOptions.icons.shuffled} Shuffled queue tracks**\nThe **${queue.tracks.data.length}** tracks in the queue has been shuffled.\n\nView the new queue order with **\`/queue\`**.` diff --git a/src/commands/player/skip.ts b/src/commands/player/skip.ts index 2942fb31..77248523 100644 --- a/src/commands/player/skip.ts +++ b/src/commands/player/skip.ts @@ -1,6 +1,6 @@ import config from 'config'; -import { useQueue } from 'discord-player'; -import { EmbedBuilder, SlashCommandBuilder } from 'discord.js'; +import { NodeResolvable, useQueue } from 'discord-player'; +import { EmbedBuilder, GuildMember, SlashCommandBuilder } from 'discord.js'; import loggerModule from '../../services/logger'; import { CommandParams } from '../../types/commandTypes'; @@ -26,15 +26,15 @@ module.exports = { module: 'slashCommand', name: '/skip', executionId: executionId, - shardId: interaction.guild.shardId, - guildId: interaction.guild.id + shardId: interaction.guild?.shardId, + guildId: interaction.guild?.id }); if (await notInVoiceChannel({ interaction, executionId })) { return; } - const queue = useQueue(interaction.guild.id); + const queue: NodeResolvable = useQueue(interaction.guild!.id)!; if (await queueDoesNotExist({ interaction, queue, executionId })) { return; @@ -59,15 +59,17 @@ module.exports = { embeds: [ new EmbedBuilder() .setDescription( - `**${embedOptions.icons.warning} Oops!**\nThere are only \`${queue.tracks.data.length}\` tracks in the queue. You cannot skip to track \`${skipToTrack}\`.\n\nView tracks added to the queue with **\`/queue\`**.` + `**${embedOptions.icons.warning} Oops!**\nThere are only **\`${queue.tracks.data.length}\`** tracks in the queue. You cannot skip to track **\`${skipToTrack}\`**.\n\nView tracks added to the queue with **\`/queue\`**.` ) .setColor(embedOptions.colors.warning) ] }); } else { - const skippedTrack = queue.currentTrack; + const skippedTrack = queue.currentTrack!; + logger.debug('Responding with warning embed.'); + let durationFormat = - skippedTrack.raw.duration === 0 || skippedTrack.duration === '0:00' + Number(skippedTrack.raw.duration) === 0 || skippedTrack.duration === '0:00' ? '' : `\`${skippedTrack.duration}\``; @@ -78,13 +80,21 @@ module.exports = { queue.node.skipTo(skipToTrack - 1); logger.debug('Skipped to specified track number.'); + let authorName: string; + + if (interaction.member instanceof GuildMember) { + authorName = interaction.member.nickname || interaction.user.username; + } else { + authorName = interaction.user.username; + } + logger.debug('Responding with success embed.'); return await interaction.editReply({ embeds: [ new EmbedBuilder() .setAuthor({ - name: interaction.member.nickname || interaction.user.username, - iconURL: interaction.user.avatarURL() + name: authorName, + iconURL: interaction.user.avatarURL() || '' }) .setDescription( `**${embedOptions.icons.skipped} Skipped track**\n**${durationFormat} [${ @@ -112,9 +122,10 @@ module.exports = { }); } - const skippedTrack = queue.currentTrack; + const skippedTrack = queue.currentTrack!; + let durationFormat = - skippedTrack.raw.duration === 0 || skippedTrack.duration === '0:00' + Number(skippedTrack.raw.duration) === 0 || skippedTrack.duration === '0:00' ? '' : `\`${skippedTrack.duration}\``; @@ -133,13 +144,21 @@ module.exports = { const loopModeUserString = loopModesFormatted.get(queue.repeatMode); + let authorName: string; + + if (interaction.member instanceof GuildMember) { + authorName = interaction.member.nickname || interaction.user.username; + } else { + authorName = interaction.user.username; + } + logger.debug('Responding with success embed.'); return await interaction.editReply({ embeds: [ new EmbedBuilder() .setAuthor({ - name: interaction.member.nickname || interaction.user.username, - iconURL: interaction.user.avatarURL() + name: authorName, + iconURL: interaction.user.avatarURL() || '' }) .setDescription( `**${embedOptions.icons.skipped} Skipped track**\n**${durationFormat} [${ diff --git a/src/commands/player/stop.ts b/src/commands/player/stop.ts index 4cb5d9da..c3c15c77 100644 --- a/src/commands/player/stop.ts +++ b/src/commands/player/stop.ts @@ -1,6 +1,6 @@ import config from 'config'; -import { useQueue } from 'discord-player'; -import { EmbedBuilder, SlashCommandBuilder } from 'discord.js'; +import { NodeResolvable, useQueue } from 'discord-player'; +import { EmbedBuilder, GuildMember, SlashCommandBuilder } from 'discord.js'; import loggerModule from '../../services/logger'; import { CommandParams } from '../../types/commandTypes'; @@ -22,15 +22,15 @@ module.exports = { module: 'slashCommand', name: '/stop', executionId: executionId, - shardId: interaction.guild.shardId, - guildId: interaction.guild.id + shardId: interaction.guild?.shardId, + guildId: interaction.guild?.id }); if (await notInVoiceChannel({ interaction, executionId })) { return; } - const queue = useQueue(interaction.guild.id); + const queue: NodeResolvable = useQueue(interaction.guild!.id)!; if (!queue) { logger.debug('There is no queue.'); @@ -58,13 +58,21 @@ module.exports = { logger.debug('Cleared and stopped the queue.'); } + let authorName: string; + + if (interaction.member instanceof GuildMember) { + authorName = interaction.member.nickname || interaction.user.username; + } else { + authorName = interaction.user.username; + } + logger.debug('Responding with success embed.'); return await interaction.editReply({ embeds: [ new EmbedBuilder() .setAuthor({ - name: interaction.member.nickname || interaction.user.username, - iconURL: interaction.user.avatarURL() + name: authorName, + iconURL: interaction.user.avatarURL() || '' }) .setDescription( `**${embedOptions.icons.success} Stopped playing**\nStopped playing audio and cleared the track queue.\n\nTo play more music, use the **\`/play\`** command!` diff --git a/src/commands/player/volume.ts b/src/commands/player/volume.ts index 4849c4c7..989e652e 100644 --- a/src/commands/player/volume.ts +++ b/src/commands/player/volume.ts @@ -38,11 +38,7 @@ module.exports = { return; } - if (!interaction.guild) { - return; - } - - const queue: NodeResolvable = useQueue(interaction.guild.id)!; + const queue: NodeResolvable = useQueue(interaction.guild!.id)!; if (await queueDoesNotExist({ interaction, queue, executionId })) { return; @@ -66,7 +62,7 @@ module.exports = { .setDescription( `**${ currentVolume === 0 ? embedOptions.icons.volumeIsMuted : embedOptions.icons.volume - } Playback volume**\nThe playback volume is currently set to \`${currentVolume}%\`.` + } Playback volume**\nThe playback volume is currently set to **\`${currentVolume}%\`**.` ) .setColor(embedOptions.colors.info) ] @@ -79,7 +75,7 @@ module.exports = { embeds: [ new EmbedBuilder() .setDescription( - `**${embedOptions.icons.warning} Oops!**\nYou cannot set the volume to \`${volume}\`, please pick a value betwen \`1\`% and \`100\`%.` + `**${embedOptions.icons.warning} Oops!**\nYou cannot set the volume to **\`${volume}%\`**, please pick a value betwen **\`1%\`** and **\`100%\`**.` ) .setColor(embedOptions.colors.warning) ] @@ -106,7 +102,7 @@ module.exports = { iconURL: interaction.user.avatarURL() || '' }) .setDescription( - `**${embedOptions.icons.volumeMuted} Audio muted**\nPlayback audio has been muted, because volume was set to \`${volume}%\`.` + `**${embedOptions.icons.volumeMuted} Audio muted**\nPlayback audio has been muted, because volume was set to **\`${volume}%\`**.` ) .setColor(embedOptions.colors.success) ] @@ -122,7 +118,7 @@ module.exports = { iconURL: interaction.user.avatarURL() || '' }) .setDescription( - `**${embedOptions.icons.volumeChanged} Volume changed**\nPlayback volume has been changed to \`${volume}%\`.` + `**${embedOptions.icons.volumeChanged} Volume changed**\nPlayback volume has been changed to **\`${volume}%\`**.` ) .setColor(embedOptions.colors.success) ] diff --git a/src/commands/system/guilds.ts b/src/commands/system/guilds.ts index fc76acdd..5f45a359 100644 --- a/src/commands/system/guilds.ts +++ b/src/commands/system/guilds.ts @@ -71,7 +71,7 @@ module.exports = { `**${embedOptions.icons.bot} ${ totalGuildCount < 25 ? `Top ${totalGuildCount} guilds` : 'Top 25 guilds' } by member count (${totalGuildCount} total)**\n${guildListFormatted}` + - `\n\n**Total members:** \`${totalMemberCount}\``; + `\n\n**Total members:** **\`${totalMemberCount}\`**`; logger.debug('Transformed guild into into embed description.'); diff --git a/src/types/clientTypes.ts b/src/types/clientTypes.ts index 9b38d2fb..921106ba 100644 --- a/src/types/clientTypes.ts +++ b/src/types/clientTypes.ts @@ -3,8 +3,9 @@ import { Client, Collection, SharedNameAndDescription } from 'discord.js'; type RegisterClientCommandsFunction = (params: { client: Client; executionId: string }) => void; export interface Command { - isNew: boolean; - isBeta: boolean; + isNew?: boolean; + isBeta?: boolean; + isSystemCommand?: boolean; data: { name: string; description: string; diff --git a/src/types/commandTypes.ts b/src/types/commandTypes.ts index c122b7de..d5a13167 100644 --- a/src/types/commandTypes.ts +++ b/src/types/commandTypes.ts @@ -25,3 +25,14 @@ export interface ShardInfo { totalListeners: number; }; } + +export interface TrackMetadata { + bridge: { + views: number; + }; +} + +export class CustomError extends Error { + type?: string; + code?: string; +} diff --git a/src/types/configTypes.ts b/src/types/configTypes.ts index f5c18fce..d5e4f7e7 100644 --- a/src/types/configTypes.ts +++ b/src/types/configTypes.ts @@ -91,7 +91,7 @@ export interface PlayerOptions { leaveOnEmpty: boolean; leaveOnEmptyCooldown: number; leaveOnEnd: boolean; - leaveonEndCooldown: number; + leaveOnEndCooldown: number; leaveOnStop: boolean; leaveOnStopCooldown: number; defaultVolume: number; diff --git a/src/utils/validation/systemCommandValidator.ts b/src/utils/validation/systemCommandValidator.ts index d659b7b0..cdcb6475 100644 --- a/src/utils/validation/systemCommandValidator.ts +++ b/src/utils/validation/systemCommandValidator.ts @@ -22,7 +22,7 @@ export const notValidGuildId = async ({ interaction, executionId }: NotValidGuil embeds: [ new EmbedBuilder() .setDescription( - `**${embedOptions.icons.warning} Oops!**\nNo permission to execute this command.\n\nThe command \`${interaction.commandName}\` cannot be executed in this server.` + `**${embedOptions.icons.warning} Oops!**\nNo permission to execute this command.\n\nThe command **\`/${interaction.commandName}\`** cannot be executed in this server.` ) .setColor(embedOptions.colors.warning) ]