diff --git a/src/Events/onInteractionCreate.ts b/src/Events/onInteractionCreate.ts index 996746b..d7f30d0 100644 --- a/src/Events/onInteractionCreate.ts +++ b/src/Events/onInteractionCreate.ts @@ -3,6 +3,10 @@ import { interactions } from '../interactions'; import { BaseInteraction, Awaitable, ChannelType, GuildMember } from 'discord.js'; export async function onInteractionCreate(interaction: BaseInteraction): Promise> { + if (interaction.isStringSelectMenu()) { + interactions.searchplay(interaction); + return; + } if (!interaction.isChatInputCommand()) return; if (!interaction.guild) { interaction.reply({ content: 'This command can only be used in a server!', ephemeral: true }); @@ -54,6 +58,9 @@ export async function onInteractionCreate(interaction: BaseInteraction): Promise case 'nowplaying': interactions.nowplaying(interaction); break; + case 'search': + interactions.search(interaction); + break; default: interaction.reply(embeds.unknownCommand); break; diff --git a/src/Events/onMessageCreate.ts b/src/Events/onMessageCreate.ts index 2cc2dfe..d1db9b1 100644 --- a/src/Events/onMessageCreate.ts +++ b/src/Events/onMessageCreate.ts @@ -69,6 +69,9 @@ export async function onMessageCreate(message: Message): Promise case 'current': commands.nowplaying(message); break; + case 'search': + commands.search(message); + break; default: message.reply(embeds.unknownCommand); break; diff --git a/src/commands/index.ts b/src/commands/index.ts index 7cff44a..b6d3903 100644 --- a/src/commands/index.ts +++ b/src/commands/index.ts @@ -6,6 +6,7 @@ import { pauseCommand } from './pause'; import { playCommand } from './play'; import { queueCommand } from './queue'; import { resumeCommand } from './resume'; +import { searchCommand } from './search'; import { skipCommand } from './skip'; import { stopCommand } from './stop'; @@ -19,5 +20,6 @@ export const commands = { queue: queueCommand, resume: resumeCommand, skip: skipCommand, - stop: stopCommand + stop: stopCommand, + search: searchCommand }; diff --git a/src/commands/search.ts b/src/commands/search.ts new file mode 100644 index 0000000..a7364a5 --- /dev/null +++ b/src/commands/search.ts @@ -0,0 +1,28 @@ +import { embeds } from '../embeds'; +import { Message, StringSelectMenuBuilder, StringSelectMenuOptionBuilder, ActionRowBuilder } from 'discord.js'; +import ytsr from 'youtube-sr'; + +export async function searchCommand(message: Message) { + const videos = await ytsr.search(message.content.split(' ').slice(1).join(' '), { + type: 'video' + }); + + if (!videos.length) return message.reply(embeds.noResult); + + const menu = new StringSelectMenuBuilder() + .setCustomId('search') + .setPlaceholder('Select a video') + .addOptions( + videos.map((video, index) => + new StringSelectMenuOptionBuilder() + .setLabel(`${index + 1}. ${video.title}`) + .setValue(video.url) + .setDescription(String(video.durationFormatted)) + .setEmoji('๐ŸŽต') + ) + ); + + const row = new ActionRowBuilder().addComponents(menu); + + message.reply({ content: 'Select a video', components: [row] }); +} diff --git a/src/deploy-commands.ts b/src/deploy-commands.ts index e65a53d..da8e66e 100644 --- a/src/deploy-commands.ts +++ b/src/deploy-commands.ts @@ -8,6 +8,10 @@ const commands = [ .setName('play') .setDescription('Play a song') .addStringOption((option) => option.setName('url').setDescription('The song to play').setRequired(true)), + new SlashCommandBuilder() + .setName('search') + .setDescription('Search for a song') + .addStringOption((option) => option.setName('query').setDescription('Enter search words').setRequired(true)), new SlashCommandBuilder().setName('pause').setDescription('Pause the current song'), new SlashCommandBuilder().setName('resume').setDescription('Resume the current song'), new SlashCommandBuilder().setName('skip').setDescription('Skip the current song'), diff --git a/src/embeds/index.ts b/src/embeds/index.ts index 7bf3ab3..b2a56bc 100644 --- a/src/embeds/index.ts +++ b/src/embeds/index.ts @@ -38,6 +38,10 @@ export const embeds = { .addFields({ name: 'Error', value: 'ๆœ‰ๅŠนใชURLใ‚’ๆŒ‡ๅฎšใ—ใฆใใ ใ•ใ„ใ€‚' }) .setColor('Red') .build(), + noResult: new Builder() + .addFields({ name: 'Error', value: 'ๆคœ็ดข็ตๆžœใŒ่ฆ‹ใคใ‹ใ‚Šใพใ›ใ‚“ใงใ—ใŸใ€‚' }) + .setColor('Red') + .build(), voiceChannnelPermission: new Builder() .addFields({ name: 'Error', value: 'ใƒœใ‚คใ‚นใƒใƒฃใƒณใƒใƒซใซๅ‚ๅŠ ใ™ใ‚‹ๆจฉ้™ใŒใ‚ใ‚Šใพใ›ใ‚“ใ€‚' }) .setColor('Red') diff --git a/src/interactions/handleplay.ts b/src/interactions/handleplay.ts new file mode 100644 index 0000000..c89924c --- /dev/null +++ b/src/interactions/handleplay.ts @@ -0,0 +1,68 @@ +import { YTPlayer } from '../classes/player'; +import { Queue, queueManager } from '../classes/queue'; +import { embeds } from '../embeds'; +import { client } from '../index'; +import { StringSelectMenuInteraction, ChannelType, VoiceBasedChannel, GuildMember } from 'discord.js'; +import ytdl from 'ytdl-core'; + +let url: string; + +export async function searchPlayCommand(interaction: StringSelectMenuInteraction) { + let player = client?.player; + if (!queueManager.getQueue(interaction.guild?.id as string)) { + queueManager.setQueue(interaction.guild?.id as string, new Queue()); + } + const queue = queueManager.getQueue(interaction.guild?.id as string) as Queue; + if (!interaction.channel) return; + if (!(interaction.member instanceof GuildMember)) return; + if (!player) { + client.player = new YTPlayer( + interaction.guild?.id as string, + interaction.member?.voice.channel as VoiceBasedChannel, + interaction.channel?.id + ); + player = client.player; + } + + url = interaction.values[0]; + const channel = interaction.member?.voice.channel; + if (!url) return interaction.reply(embeds.noUrl); + if (!ytdl.validateURL(url)) return interaction.reply(embeds.invaildUrl); + if (!channel) return interaction.reply(embeds.voiceChannelJoin); + if (channel.type !== ChannelType.GuildVoice) return; + if (!channel.joinable) return interaction.reply(embeds.voiceChannnelJoined); + if (!channel.speakable) return interaction.reply(embeds.voiceChannnelPermission); + + if (!queue.length || !player.isPlaying) { + queue.addSong(url); + const info = await ytdl.getInfo(url); + interaction.reply( + new embeds.embed() + .setTitle('Success') + .setDescription(`**[${info.videoDetails.title}](${info.videoDetails.video_url})ใ‚’ๅ†็”Ÿใ—ใพใ™ใ€‚**`) + .addFields({ + name: info.videoDetails.title, + value: `ๆŠ•็จฟ่€…: [${info.videoDetails.author.name}](${info.videoDetails.author.channel_url})` + }) + .setImage(info.videoDetails.thumbnails[0].url.split('?')[0]) + .setColor('Green') + .build() + ); + if (queue.length === 1) return player.play(); + } else { + queue.addSong(url); + const info = await ytdl.getInfo(url); + interaction.reply( + new embeds.embed() + .setTitle('Info') + .setDescription(`**[${info.videoDetails.title}](${info.videoDetails.video_url})ใ‚’ใ‚ญใƒฅใƒผใซ่ฟฝๅŠ ใ—ใพใ—ใŸใ€‚**`) + .addFields({ + name: info.videoDetails.title, + value: `ๆŠ•็จฟ่€…: [${info.videoDetails.author.name}](${info.videoDetails.author.channel_url})` + }) + .setImage(info.videoDetails.thumbnails[0].url.split('?')[0]) + .setColor('Yellow') + .build() + ); + } +} diff --git a/src/interactions/index.ts b/src/interactions/index.ts index 2435020..d9edbf4 100644 --- a/src/interactions/index.ts +++ b/src/interactions/index.ts @@ -1,4 +1,5 @@ import { changeVolumeCommand } from './changeVolume'; +import { searchPlayCommand } from './handleplay'; import { helpCommand } from './help'; import { loopCommand } from './loop'; import { nowplayingCommand } from './nowplaying'; @@ -6,6 +7,7 @@ import { pauseCommand } from './pause'; import { playCommand } from './play'; import { queueCommand } from './queue'; import { resumeCommand } from './resume'; +import { searchCommand } from './search'; import { skipCommand } from './skip'; import { stopCommand } from './stop'; @@ -19,5 +21,7 @@ export const interactions = { queue: queueCommand, resume: resumeCommand, skip: skipCommand, - stop: stopCommand + stop: stopCommand, + search: searchCommand, + searchplay: searchPlayCommand }; diff --git a/src/interactions/search.ts b/src/interactions/search.ts new file mode 100644 index 0000000..523dc78 --- /dev/null +++ b/src/interactions/search.ts @@ -0,0 +1,34 @@ +import { embeds } from '../embeds'; +import { + ChatInputCommandInteraction, + StringSelectMenuBuilder, + StringSelectMenuOptionBuilder, + ActionRowBuilder +} from 'discord.js'; +import ytsr from 'youtube-sr'; + +export async function searchCommand(interaction: ChatInputCommandInteraction) { + interaction.deferReply(); + const videos = await ytsr.search(interaction.options.getString('query') as string, { + type: 'video' + }); + + if (!videos.length) return interaction.followUp(embeds.noResult); + + const menu = new StringSelectMenuBuilder() + .setCustomId('search') + .setPlaceholder('Select a video') + .addOptions( + videos.map((video, index) => + new StringSelectMenuOptionBuilder() + .setLabel(`${index + 1}. ${video.title}`) + .setValue(video.url) + .setDescription(String(video.durationFormatted)) + .setEmoji('๐ŸŽต') + ) + ); + + const row = new ActionRowBuilder().addComponents(menu); + + interaction.followUp({ content: 'Select a video', components: [row] }); +}