diff --git a/src/bot.ts b/src/bot.ts index a18e344..c51caf9 100644 --- a/src/bot.ts +++ b/src/bot.ts @@ -87,27 +87,40 @@ export default class { }); this.client.on('interactionCreate', async interaction => { - if (!interaction.isButton()) { - return; - } - - const command = this.commandsByButtonId.get(interaction.customId); - - if (!command) { - return; - } - try { - if (command.handleButtonInteraction) { - await command.handleButtonInteraction(interaction); + if (interaction.isButton()) { + const command = this.commandsByButtonId.get(interaction.customId); + + if (!command) { + return; + } + + if (command.handleButtonInteraction) { + await command.handleButtonInteraction(interaction); + } + } + + if (interaction.isAutocomplete()) { + const command = this.commandsByName.get(interaction.commandName); + + if (!command) { + return; + } + + if (command.handleAutocompleteInteraction) { + await command.handleAutocompleteInteraction(interaction); + } } } catch (error: unknown) { debug(error); - if (interaction.replied || interaction.deferred) { - await interaction.editReply(errorMsg('something went wrong')); - } else { - await interaction.reply({content: errorMsg(error as Error), ephemeral: true}); + // Can't reply with errors for autocomplete queries + if (interaction.isButton()) { + if (interaction.replied || interaction.deferred) { + await interaction.editReply(errorMsg('something went wrong')); + } else { + await interaction.reply({content: errorMsg(error as Error), ephemeral: true}); + } } } }); diff --git a/src/commands/index.ts b/src/commands/index.ts index b9f5877..1d1646f 100644 --- a/src/commands/index.ts +++ b/src/commands/index.ts @@ -1,5 +1,5 @@ import {SlashCommandBuilder} from '@discordjs/builders'; -import {ButtonInteraction, CommandInteraction} from 'discord.js'; +import {AutocompleteInteraction, ButtonInteraction, CommandInteraction} from 'discord.js'; export default interface Command { readonly slashCommand: Partial & Pick; @@ -7,4 +7,5 @@ export default interface Command { readonly requiresVC?: boolean; execute: (interaction: CommandInteraction) => Promise; handleButtonInteraction?: (interaction: ButtonInteraction) => Promise; + handleAutocompleteInteraction?: (interaction: AutocompleteInteraction) => Promise; } diff --git a/src/commands/play.ts b/src/commands/play.ts index 619c18f..7e5dee1 100644 --- a/src/commands/play.ts +++ b/src/commands/play.ts @@ -1,4 +1,4 @@ -import {CommandInteraction, GuildMember} from 'discord.js'; +import {AutocompleteInteraction, CommandInteraction, GuildMember} from 'discord.js'; import {URL} from 'url'; import {Except} from 'type-fest'; import {SlashCommandBuilder} from '@discordjs/builders'; @@ -12,6 +12,7 @@ import {getMostPopularVoiceChannel, getMemberVoiceChannel} from '../utils/channe import errorMsg from '../utils/error-msg.js'; import GetSongs from '../services/get-songs.js'; import {prisma} from '../utils/db.js'; +import getYouTubeSuggestionsFor from '../utils/get-youtube-suggestions-for.js'; @injectable() export default class implements Command { @@ -21,7 +22,8 @@ export default class implements Command { .setDescription('play a song or resume playback') .addStringOption(option => option .setName('query') - .setDescription('YouTube URL, Spotify URL, or search query')) + .setDescription('YouTube URL, Spotify URL, or search query') + .setAutocomplete(true)) .addBooleanOption(option => option .setName('immediate') .setDescription('adds track to the front of the queue')) @@ -191,4 +193,17 @@ export default class implements Command { await interaction.editReply(`u betcha, **${firstSong.title}** and ${newSongs.length - 1} other songs were added to the queue${extraMsg}`); } } + + public async handleAutocompleteInteraction(interaction: AutocompleteInteraction): Promise { + const query = interaction.options.getString('query')?.trim(); + + if (!query || query.length === 0) { + return interaction.respond([]); + } + + await interaction.respond((await getYouTubeSuggestionsFor(query)).map(s => ({ + name: s, + value: s, + }))); + } } diff --git a/src/utils/get-youtube-suggestions-for.ts b/src/utils/get-youtube-suggestions-for.ts new file mode 100644 index 0000000..11977e4 --- /dev/null +++ b/src/utils/get-youtube-suggestions-for.ts @@ -0,0 +1,15 @@ +import got from 'got'; + +const getYouTubeSuggestionsFor = async (query: string): Promise => { + const [_, suggestions] = await got('https://suggestqueries.google.com/complete/search?client=firefox&ds=yt&q=', { + searchParams: { + client: 'firefox', + ds: 'yt', + q: query, + }, + }).json<[string, string[]]>(); + + return suggestions; +}; + +export default getYouTubeSuggestionsFor;