diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index 6ed9f88..0000000 --- a/.eslintignore +++ /dev/null @@ -1,3 +0,0 @@ -build/ -node_modules/ -.env \ No newline at end of file diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000..d3f2759 --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,39 @@ +import { FlatCompat } from '@eslint/eslintrc'; +import js from '@eslint/js'; +import typescriptEslint from '@typescript-eslint/eslint-plugin'; +import tsParser from '@typescript-eslint/parser'; +import globals from 'globals'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const compat = new FlatCompat({ + baseDirectory: __dirname, + recommendedConfig: js.configs.recommended, + allConfig: js.configs.all +}); + +export default [ + { + ignores: ['**/build/', '**/node_modules/', '**/.env'] + }, + ...compat.extends('eslint:recommended', 'plugin:@typescript-eslint/recommended', 'prettier'), + { + plugins: { + '@typescript-eslint': typescriptEslint + }, + + languageOptions: { + globals: { + ...globals.node + }, + + parser: tsParser, + ecmaVersion: 'latest', + sourceType: 'module' + }, + + rules: {} + } +]; diff --git a/package.json b/package.json index 0901993..dbe763e 100644 --- a/package.json +++ b/package.json @@ -23,26 +23,27 @@ }, "license": "MIT", "devDependencies": { + "@eslint/eslintrc": "^3.2.0", + "@eslint/js": "^9.15.0", "@trivago/prettier-plugin-sort-imports": "^4.3.0", - "@typescript-eslint/eslint-plugin": "^6.21.0", - "@typescript-eslint/parser": "^6.21.0", - "eslint": "^8.57.1", + "@typescript-eslint/eslint-plugin": "^8.16.0", + "@typescript-eslint/parser": "^8.16.0", + "eslint": "^9.15.0", "eslint-config-prettier": "^9.1.0", - "prettier": "^3.3.3", + "globals": "^15.12.0", + "prettier": "^3.4.1", "ts-node": "^10.9.2", - "typescript": "^5.6.3" + "typescript": "^5.7.2" }, "dependencies": { "@discordjs/opus": "^0.9.0", - "@discordjs/voice": "^0.16.1", + "@discordjs/voice": "^0.18.0", "@distube/ytdl-core": "^4.15.1", + "@distube/ytpl": "^1.2.1", "discord.js": "^14.16.3", "dotenv": "^16.4.5", - "ffmpeg-static": "^5.2.0", "node-cache": "^5.1.2", - "tweetnacl": "^1.0.3", - "youtube-sr": "^4.3.11", - "ytpl": "^2.3.0" + "youtube-sr": "^4.3.11" }, "repository": { "type": "git", diff --git a/src/Events/onInteractionCreate.ts b/src/Events/onInteractionCreate.ts index 0ce7307..916e0d1 100644 --- a/src/Events/onInteractionCreate.ts +++ b/src/Events/onInteractionCreate.ts @@ -33,10 +33,6 @@ export async function onInteractionCreate(interaction: BaseInteraction): Promise return; } if (channel.type !== ChannelType.GuildVoice) return; - if (!channel.joinable) { - interaction.reply(embeds.voiceChannnelJoined); - return; - } if (!channel.speakable) { interaction.reply(embeds.voiceChannnelPermission); return; diff --git a/src/Events/onMessageCreate.ts b/src/Events/onMessageCreate.ts index d1db9b1..5765dab 100644 --- a/src/Events/onMessageCreate.ts +++ b/src/Events/onMessageCreate.ts @@ -18,10 +18,6 @@ export async function onMessageCreate(message: Message): Promise return; } if (channel.type !== ChannelType.GuildVoice) return; - if (!channel.joinable) { - message.reply(embeds.voiceChannnelJoined); - return; - } if (!channel.speakable) { message.reply(embeds.voiceChannnelPermission); return; diff --git a/src/Events/onReady.ts b/src/Events/onReady.ts index 9da2a23..655e85f 100644 --- a/src/Events/onReady.ts +++ b/src/Events/onReady.ts @@ -1,13 +1,17 @@ -import { Client, version } from 'discord.js'; +import { extendedClient } from '../Utils/extendedClient'; +import { version as voiceVersion } from '@discordjs/voice'; +import { version as djsVersion } from 'discord.js'; -export function onReady(client: Client) { +export function onReady(client: extendedClient): void { console.log(`Logged in as ${client.user?.tag}`); client.user?.setActivity('CatHouse Products'); console.table({ 'Bot User': client.user?.tag, 'Guild(s)': client.guilds.cache.size + ' Servers', Watching: client.guilds.cache.reduce((a, b) => a + b.memberCount, 0) + ' Members', - 'Discord.js': version, + 'Discord.js': djsVersion, + '@discordjs/voice': voiceVersion, + Proxy: client.useProxy ? client.getURI() : 'No Proxy', 'Node.js': process.version, Plattform: process.platform + ' | ' + process.arch, Memory: diff --git a/src/Events/onVoiceStateUpdate.ts b/src/Events/onVoiceStateUpdate.ts index e885b7f..0f2454e 100644 --- a/src/Events/onVoiceStateUpdate.ts +++ b/src/Events/onVoiceStateUpdate.ts @@ -1,17 +1,11 @@ import { Queue, queueManager } from '../classes/queue'; -import { client } from '../index'; import { VoiceState } from 'discord.js'; export async function onVoiceStateUpdate(oldState: VoiceState, newState: VoiceState) { - const botId = oldState.client.user?.id; - const channel = oldState.channel; - if (!botId) return; - - if (channel && channel.members.size === 1 && channel.members.has(botId)) { - newState.guild.members.me?.voice.disconnect(); - client.player = undefined; - queueManager.deleteQueue(newState.guild.id); - } + const oldchannel = oldState.channel; + const newchannel = newState.channel; + console.log(`[INFO] Old Channel: ${oldchannel?.name} | New Channel: ${newchannel?.name} + `); if (!queueManager.getQueue(newState.guild.id)) queueManager.setQueue(newState.guild.id, new Queue()); } diff --git a/src/Utils/extendedClient.ts b/src/Utils/extendedClient.ts index 71c77d4..3e04ff4 100644 --- a/src/Utils/extendedClient.ts +++ b/src/Utils/extendedClient.ts @@ -1,10 +1,40 @@ import { YTPlayer } from '../classes/player'; import { ClientOptions, Client } from 'discord.js'; +interface ProxyOptions { + host: string; + port: number; +} + export class extendedClient extends Client { - player: YTPlayer | undefined; - constructor(options: ClientOptions) { + player: Map; + useProxy: boolean; + proxySettings?: ProxyOptions; + + constructor(options: ClientOptions, proxyOptions?: ProxyOptions) { super(options); - this.player = undefined; + this.player = new Map(); + this.useProxy = Boolean(proxyOptions); + this.proxySettings = proxyOptions ?? undefined; + } + + public setPlayer(serverId: string, player: YTPlayer): void { + this.player.set(serverId, player); + } + + public getPlayer(serverId: string): YTPlayer | undefined { + return this.player.get(serverId); + } + + public deletePlayer(serverId: string): boolean { + return this.player.delete(serverId); + } + + public getURI(): string { + if (this.useProxy && this.proxySettings) { + const { host, port } = this.proxySettings; + return `http://${host}:${port}/`; + } + return ''; } } diff --git a/src/Utils/songResolver.ts b/src/Utils/songResolver.ts index 5a1a86e..9195e8c 100644 --- a/src/Utils/songResolver.ts +++ b/src/Utils/songResolver.ts @@ -1,4 +1,5 @@ import { format_count, seconds_to_time } from '../Utils/NumberUtil'; +import { YTPlayer } from '../classes/player'; import { client } from '../index'; import { Builder } from './Builder'; import ytdl from '@distube/ytdl-core'; @@ -18,28 +19,30 @@ export function songResolver(info: ytdl.videoInfo, requestedBy?: string, request } export function formatProgressBar(current: number, total: number, barLength: number = 20): string { - const progress = Math.round((current / total) * barLength); - const empty = Math.max(barLength - progress, 0); // `empty` が負にならないように修正 - const filled = '█'.repeat(progress); // 進行中部分(█) - const unfilled = '░'.repeat(empty); // 残り部分(░) + const progress = Math.round((current / total) * barLength); + const empty = Math.max(barLength - progress, 0); // `empty` が負にならないように修正 + const filled = '█'.repeat(progress); // 進行中部分(█) + const unfilled = '░'.repeat(empty); // 残り部分(░) - return `${seconds_to_time(current)} [${filled}${unfilled}] ${seconds_to_time(total)} (${Math.round((current / total) * 100)}%)`; + return `${seconds_to_time(current)} [${filled}${unfilled}] ${seconds_to_time(total)} (${Math.round((current / total) * 100)}%)`; } - -export async function getSongInfo(url: string) { +export async function getSongInfo(url: string, serverId: string) { const info = await ytdl.getInfo(url); const song = songResolver(info); + const player = client.getPlayer(serverId) as YTPlayer; // currentDuration が undefined または NaN の場合、デフォルトで 0 を設定 - const currentDuration = Number(client.player?.resource?.playbackDuration) ? Math.round(Number(client.player?.resource?.playbackDuration) / 1000) : 0; + const currentDuration = Number(player.resource?.playbackDuration) + ? Math.round(Number(player.resource?.playbackDuration) / 1000) + : 0; const totalDuration = Number(info.videoDetails.lengthSeconds); const progressBar = formatProgressBar(currentDuration, totalDuration); // 次の曲がキューにあるか確認 - const nextSong = (await ytdl.validateURL(client.player?.queue.store[1] as string)) - ? await ytdl.getInfo(client.player?.queue.store[1] as string) + const nextSong = (await ytdl.validateURL(player?.queue.store[1] as string)) + ? await ytdl.getInfo(player.queue.store[1] as string) : null; let footerText = '次の曲はありません'; diff --git a/src/classes/player.ts b/src/classes/player.ts index 49b7917..f75c437 100644 --- a/src/classes/player.ts +++ b/src/classes/player.ts @@ -29,7 +29,7 @@ export class YTPlayer { }); this.player = createAudioPlayer(); this.queue = queueManager.getQueue(serverId) as Queue; - this.volume = 0.1; + this.volume = 1; this.player .on('subscribe', () => { this.isPlaying = true; @@ -42,17 +42,22 @@ export class YTPlayer { } public play() { + let agent = undefined; + if (client.useProxy) agent = ytdl.createProxyAgent({ uri: client.getURI() }); + const queue = queueManager.getQueue(this.serverId); const stream = ytdl(ytdl.getURLVideoID(queue?.currentSong as string), { + agent: agent ?? undefined, filter: (format) => format.audioCodec === 'opus' && format.container === 'webm', quality: 'highest', highWaterMark: 32 * 1024 * 1024 }); + this.resource = createAudioResource(stream, { inputType: StreamType.WebmOpus, inlineVolume: true }); - this.resource.volume?.setVolume(this.volume); + this.resource.volume?.setVolume(this.volume / 10); this.connection.subscribe(this.player); this.player.play(this.resource); } @@ -68,7 +73,7 @@ export class YTPlayer { public stop(): void { this.connection.destroy(); queueManager.deleteQueue(this.serverId); - client.player = undefined; + client.deletePlayer(this.serverId); } public skip(): void { @@ -94,14 +99,14 @@ export class YTPlayer { if (this.queue.loop === 'queue') this.queue.loopQueue(); if (this.queue.loop === 'track') this.queue.loopTrack(); this.play(); - await this.fetchSongData(); + return await this.fetchSongData(); } private async fetchSongData() { const channel = client.channels.cache.get(this.messageChannelId); if (!channel || !channel.isTextBased()) return; if (channel.isTextBased() && 'send' in channel) { - await channel.send(await getSongInfo(this.queue.currentSong!)); + await channel.send(await getSongInfo(this.queue.currentSong!, this.serverId)); } } } diff --git a/src/commands/changeVolume.ts b/src/commands/changeVolume.ts index 391cc57..bcb0fd4 100644 --- a/src/commands/changeVolume.ts +++ b/src/commands/changeVolume.ts @@ -3,7 +3,7 @@ import { client } from '../index'; import { Message } from 'discord.js'; export async function changeVolumeCommand(message: Message) { - const player = client?.player; + const player = client.getPlayer(message.guildId!); if (!player) return message.reply(embeds.videoNotPlaying); const content = message.content.split(' '); diff --git a/src/commands/loop.ts b/src/commands/loop.ts index 40f63e0..13649f2 100644 --- a/src/commands/loop.ts +++ b/src/commands/loop.ts @@ -4,12 +4,16 @@ import { client } from '../index'; import { Message } from 'discord.js'; export async function loopCommand(message: Message, args: string[]) { - const player = client?.player; + const player = client.getPlayer(message.guildId!); if (!player) return message.reply(embeds.videoNotPlaying); const queue = queueManager.queues.get(message.guildId!) as Queue; args = args.filter((arg) => arg !== ''); if (args.length === 0) { - queue.loop === 'none' ? queue.setLoop('queue') : queue.setLoop('none'); + if (queue.loop === 'none') { + queue.setLoop('queue'); + } else { + queue.setLoop('none'); + } } else if (args.length === 1) { switch (args[0]) { case 'none': diff --git a/src/commands/nowplaying.ts b/src/commands/nowplaying.ts index 03afc05..46d45b6 100644 --- a/src/commands/nowplaying.ts +++ b/src/commands/nowplaying.ts @@ -5,9 +5,9 @@ import { client } from '../index'; import { Message } from 'discord.js'; export async function nowplayingCommand(message: Message) { - const player = client?.player; + const player = client.getPlayer(message.guildId!); if (!player) return message.reply(embeds.videoNotPlaying); const queue = queueManager.getQueue(message.guild?.id as string) as Queue; - const info = await getSongInfo(queue.currentSong); + const info = await getSongInfo(queue.currentSong, message.guildId!); return message.reply(info); } diff --git a/src/commands/pause.ts b/src/commands/pause.ts index 05cb29d..60e3684 100644 --- a/src/commands/pause.ts +++ b/src/commands/pause.ts @@ -4,7 +4,7 @@ import { AudioPlayerStatus } from '@discordjs/voice'; import { Message } from 'discord.js'; export async function pauseCommand(message: Message) { - const player = client?.player; + const player = client.getPlayer(message.guildId!); if (!player) return message.reply(embeds.videoNotPlaying); if (player.player.state.status === AudioPlayerStatus.Playing) { diff --git a/src/commands/play.ts b/src/commands/play.ts index bf968b7..ec5c8ed 100644 --- a/src/commands/play.ts +++ b/src/commands/play.ts @@ -8,18 +8,17 @@ import { Message, VoiceBasedChannel } from 'discord.js'; let url: string; export async function playCommand(message: Message) { - let player = client?.player; - if (!queueManager.getQueue(message.guild?.id as string)) { - queueManager.setQueue(message.guild?.id as string, new Queue()); + let player = client.getPlayer(message.guildId!); + if (!queueManager.getQueue(message.guild!.id)) { + queueManager.setQueue(message.guild!.id, new Queue()); } const queue = queueManager.getQueue(message.guild?.id as string) as Queue; if (!player) { - client.player = new YTPlayer( - message.guild?.id as string, - message.member?.voice.channel as VoiceBasedChannel, - message.channel.id + client.setPlayer( + message.guild!.id, + new YTPlayer(message.guildId!, message.member!.voice.channel as VoiceBasedChannel, message.channelId) ); - player = client.player; + player = client.getPlayer(message.guildId!) as YTPlayer; } url = message.content.split(' ')[1]; if (!url) return message.reply(embeds.noUrl); diff --git a/src/commands/queue.ts b/src/commands/queue.ts index fbdbcca..3f98447 100644 --- a/src/commands/queue.ts +++ b/src/commands/queue.ts @@ -6,7 +6,7 @@ import ytdl from '@distube/ytdl-core'; import { Message } from 'discord.js'; export async function queueCommand(message: Message) { - const player = client?.player; + const player = client.getPlayer(message.guildId!); if (!player) return message.reply(embeds.videoNotPlaying); const queue = queueManager.getQueue(message.guildId!) as Queue; diff --git a/src/commands/resume.ts b/src/commands/resume.ts index 3e514e6..fb7c133 100644 --- a/src/commands/resume.ts +++ b/src/commands/resume.ts @@ -4,7 +4,7 @@ import { AudioPlayerStatus } from '@discordjs/voice'; import { Message } from 'discord.js'; export async function resumeCommand(message: Message) { - const player = client?.player; + const player = client.getPlayer(message.guildId!); if (!player) return message.reply(embeds.videoNotPlaying); if (player.player.state.status === AudioPlayerStatus.Paused) { player.resume(); diff --git a/src/commands/skip.ts b/src/commands/skip.ts index d6aac50..e1915d4 100644 --- a/src/commands/skip.ts +++ b/src/commands/skip.ts @@ -3,7 +3,7 @@ import { client } from '../index'; import { Message } from 'discord.js'; export async function skipCommand(message: Message) { - const player = client?.player; + const player = client.getPlayer(message.guildId!); if (!player) return message.reply(embeds.videoNotPlaying); player.skip(); return message.reply(embeds.videoNext); diff --git a/src/commands/stop.ts b/src/commands/stop.ts index 5b59308..2e7ebd6 100644 --- a/src/commands/stop.ts +++ b/src/commands/stop.ts @@ -1,14 +1,11 @@ -import { queueManager } from '../classes/queue'; import { embeds } from '../embeds'; import { client } from '../index'; import { Message } from 'discord.js'; export async function stopCommand(message: Message): Promise { if (message.guild?.members.me?.voice.channel) { - message.guild.members.me.voice.disconnect(); - client.player = undefined; + const player = client.getPlayer(message.guildId!); message.reply(embeds.videoStopped); + player?.stop(); } else message.reply(embeds.videoNotPlaying); - - queueManager.queues.delete(message.guildId!); } diff --git a/src/interactions/changeVolume.ts b/src/interactions/changeVolume.ts index d6f3621..d711d0a 100644 --- a/src/interactions/changeVolume.ts +++ b/src/interactions/changeVolume.ts @@ -3,7 +3,7 @@ import { client } from '../index'; import { ChatInputCommandInteraction } from 'discord.js'; export async function changeVolumeCommand(interaction: ChatInputCommandInteraction) { - const player = client?.player; + const player = client.getPlayer(interaction.guildId!); if (!player) return interaction.reply(embeds.videoNotPlaying); const vol = interaction.options.getInteger('volume'); diff --git a/src/interactions/handleplay.ts b/src/interactions/handleplay.ts index 18cf9b5..39f9011 100644 --- a/src/interactions/handleplay.ts +++ b/src/interactions/handleplay.ts @@ -8,35 +8,34 @@ import { StringSelectMenuInteraction, ChannelType, VoiceBasedChannel, GuildMembe 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()); + await interaction.deferReply(); + let player = client.getPlayer(interaction.guildId!); + if (!queueManager.getQueue(interaction.guild!.id)) { + queueManager.setQueue(interaction.guild!.id, new Queue()); } - const queue = queueManager.getQueue(interaction.guild?.id as string) as Queue; + const queue = queueManager.getQueue(interaction.guild!.id) 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 + client.setPlayer( + interaction.guildId!, + new YTPlayer(interaction.guild!.id, interaction.member.voice.channel as VoiceBasedChannel, interaction.channel.id) ); - player = client.player; + player = client.getPlayer(interaction.guildId!) as YTPlayer; } 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 (!url) return interaction.editReply(embeds.noUrl); + if (!ytdl.validateURL(url)) return interaction.editReply(embeds.invaildUrl); + if (!channel) return interaction.editReply(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 (!channel.speakable) return interaction.editReply(embeds.voiceChannnelPermission); if (!queue.length || !player.isPlaying) { queue.addSong(url); const info = await ytdl.getInfo(url); - interaction.reply( + interaction.editReply( new embeds.embed() .setTitle('Success') .setDescription(`**[${info.videoDetails.title}](${info.videoDetails.video_url})を再生します。**`) @@ -52,7 +51,7 @@ export async function searchPlayCommand(interaction: StringSelectMenuInteraction } else { queue.addSong(url); const info = await ytdl.getInfo(url); - interaction.reply( + interaction.editReply( new embeds.embed() .setTitle('Info') .setDescription(`**[${info.videoDetails.title}](${info.videoDetails.video_url})をキューに追加しました。**`) diff --git a/src/interactions/loop.ts b/src/interactions/loop.ts index 087b924..a924afe 100644 --- a/src/interactions/loop.ts +++ b/src/interactions/loop.ts @@ -4,12 +4,16 @@ import { client } from '../index'; import { ChatInputCommandInteraction } from 'discord.js'; export async function loopCommand(interaction: ChatInputCommandInteraction) { - const player = client?.player; + const player = client.getPlayer(interaction.guildId!); if (!player) return interaction.reply(embeds.videoNotPlaying); const queue = queueManager.queues.get(interaction.guildId!) as Queue; const settings = interaction.options.getString('mode') as string; if (!settings) { - queue.loop === 'none' ? queue.setLoop('queue') : queue.setLoop('none'); + if (queue.loop === 'none') { + queue.setLoop('queue'); + } else { + queue.setLoop('none'); + } } else if (settings) { switch (settings) { case 'none': diff --git a/src/interactions/nowplaying.ts b/src/interactions/nowplaying.ts index 49f21d5..096b4a4 100644 --- a/src/interactions/nowplaying.ts +++ b/src/interactions/nowplaying.ts @@ -6,9 +6,9 @@ import { ChatInputCommandInteraction } from 'discord.js'; export async function nowplayingCommand(interaction: ChatInputCommandInteraction) { await interaction.deferReply(); - const player = client?.player; + const player = client.getPlayer(interaction.guildId!); if (!player) return interaction.followUp(embeds.videoNotPlaying); - const queue = queueManager.getQueue(interaction.guild?.id as string) as Queue; - const info = await getSongInfo(queue.currentSong); + const queue = queueManager.getQueue(interaction.guild!.id) as Queue; + const info = await getSongInfo(queue.currentSong, interaction.guildId!); return interaction.followUp(info); } diff --git a/src/interactions/pause.ts b/src/interactions/pause.ts index 5d62ab7..40a02c6 100644 --- a/src/interactions/pause.ts +++ b/src/interactions/pause.ts @@ -4,7 +4,7 @@ import { AudioPlayerStatus } from '@discordjs/voice'; import { ChatInputCommandInteraction } from 'discord.js'; export async function pauseCommand(interaction: ChatInputCommandInteraction) { - const player = client?.player; + const player = client.getPlayer(interaction.guildId!); if (!player) return interaction.reply(embeds.videoNotPlaying); if (player.player.state.status === AudioPlayerStatus.Playing) { diff --git a/src/interactions/play.ts b/src/interactions/play.ts index 5d82ed0..f84dde0 100644 --- a/src/interactions/play.ts +++ b/src/interactions/play.ts @@ -3,41 +3,46 @@ import { Queue, queueManager } from '../classes/queue'; import { embeds } from '../embeds'; import { client } from '../index'; import ytdl from '@distube/ytdl-core'; +import ytpl from '@distube/ytpl'; import { ChannelType, VoiceBasedChannel, ChatInputCommandInteraction, GuildMember } from 'discord.js'; -import ytpl from 'ytpl'; let url: string; export async function playCommand(interaction: ChatInputCommandInteraction) { - let player = client?.player; - if (!queueManager.getQueue(interaction.guild?.id as string)) { - queueManager.setQueue(interaction.guild?.id as string, new Queue()); + await interaction.deferReply(); + let player = client.getPlayer(interaction.guildId!); + if (!queueManager.getQueue(interaction.guild!.id)) { + queueManager.setQueue(interaction.guild!.id, new Queue()); } - const queue = queueManager.getQueue(interaction.guild?.id as string) as Queue; + const queue = queueManager.getQueue(interaction.guild!.id) 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 + client.setPlayer( + interaction.guildId!, + new YTPlayer( + interaction.guild!.id as string, + interaction.member.voice.channel as VoiceBasedChannel, + interaction.channel.id + ) ); - player = client.player; + player = client.getPlayer(interaction.guildId!) as YTPlayer; } url = interaction.options.getString('url') as string; const channel = interaction.member?.voice.channel; if (!url) return interaction.reply(embeds.noUrl); 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); // Check if URL is a playlist if (ytpl.validateID(url)) { - const playlist = await ytpl(url); + const playlist = await ytpl(url, { + limit: Infinity + }); for (const video of playlist.items) queue.addSong(video.url); - interaction.reply( + interaction.editReply( new embeds.embed() .setTitle('Playlist Added') .setDescription(`**${playlist.title}** を再生キューに追加しました。`) @@ -49,7 +54,7 @@ export async function playCommand(interaction: ChatInputCommandInteraction) { if (!queue.length || !player.isPlaying) { queue.addSong(url); const info = await ytdl.getInfo(url); - interaction.reply( + interaction.editReply( new embeds.embed() .setTitle('Success') .setDescription(`**[${info.videoDetails.title}](${info.videoDetails.video_url})を再生します。**`) @@ -65,7 +70,7 @@ export async function playCommand(interaction: ChatInputCommandInteraction) { } else { queue.addSong(url); const info = await ytdl.getInfo(url); - interaction.reply( + interaction.editReply( new embeds.embed() .setTitle('Info') .setDescription(`**[${info.videoDetails.title}](${info.videoDetails.video_url})をキューに追加しました。**`) @@ -79,6 +84,6 @@ export async function playCommand(interaction: ChatInputCommandInteraction) { ); } } else { - interaction.reply(embeds.invaildUrl); + interaction.editReply(embeds.invaildUrl); } } diff --git a/src/interactions/queue.ts b/src/interactions/queue.ts index 8ab01f9..c111977 100644 --- a/src/interactions/queue.ts +++ b/src/interactions/queue.ts @@ -27,7 +27,7 @@ async function getCachedInfo(url: string): Promise { } export async function queueCommand(interaction: ChatInputCommandInteraction) { - const player = client?.player; + const player = client.getPlayer(interaction.guildId!); await interaction.deferReply(); if (!player) return interaction.followUp(embeds.videoNotPlaying); diff --git a/src/interactions/resume.ts b/src/interactions/resume.ts index 7af6de3..171719f 100644 --- a/src/interactions/resume.ts +++ b/src/interactions/resume.ts @@ -4,8 +4,8 @@ import { AudioPlayerStatus } from '@discordjs/voice'; import { ChatInputCommandInteraction } from 'discord.js'; export async function resumeCommand(interaction: ChatInputCommandInteraction) { - const player = client?.player; - if (typeof player === 'undefined') return interaction.reply(embeds.videoNotPlaying); + const player = client.getPlayer(interaction.guildId!); + if (!player) return interaction.reply(embeds.videoNotPlaying); if (player.player.state.status === AudioPlayerStatus.Paused) { player.resume(); interaction.reply(embeds.videoResumed); diff --git a/src/interactions/search.ts b/src/interactions/search.ts index 2718c6a..d281f8d 100644 --- a/src/interactions/search.ts +++ b/src/interactions/search.ts @@ -8,7 +8,7 @@ import { import ytsr from 'youtube-sr'; export async function searchCommand(interaction: ChatInputCommandInteraction) { - interaction.deferReply(); + await interaction.deferReply(); const videos = await ytsr.search(interaction.options.getString('query') as string, { type: 'video' }); diff --git a/src/interactions/skip.ts b/src/interactions/skip.ts index 3056b95..9ad3995 100644 --- a/src/interactions/skip.ts +++ b/src/interactions/skip.ts @@ -3,7 +3,7 @@ import { client } from '../index'; import { ChatInputCommandInteraction } from 'discord.js'; export async function skipCommand(interaction: ChatInputCommandInteraction) { - const player = client?.player; + const player = client.getPlayer(interaction.guildId!); if (!player) return interaction.reply(embeds.videoNotPlaying); player.skip(); return interaction.reply(embeds.videoNext); diff --git a/src/interactions/stop.ts b/src/interactions/stop.ts index 3890016..f36c2e0 100644 --- a/src/interactions/stop.ts +++ b/src/interactions/stop.ts @@ -1,14 +1,12 @@ -import { queueManager } from '../classes/queue'; import { embeds } from '../embeds'; import { client } from '../index'; import { ChatInputCommandInteraction } from 'discord.js'; +//TODO: 動画を止めましたではなく終了して退室したのを通知すべきかも export async function stopCommand(interaction: ChatInputCommandInteraction): Promise { if (interaction.guild?.members.me?.voice.channel) { - interaction.guild.members.me.voice.disconnect(); - client.player = undefined; + const player = client.getPlayer(interaction.guildId!); + player?.stop(); interaction.reply(embeds.videoStopped); } else interaction.reply(embeds.videoNotPlaying); - - queueManager.queues.delete(interaction.guildId!); }