diff --git a/locales/ChineseCN.json b/locales/ChineseCN.json index 322be9fe5..bf16f1eff 100644 --- a/locales/ChineseCN.json +++ b/locales/ChineseCN.json @@ -134,6 +134,9 @@ "messages": { "filter_enabled": "`✅` | 低音增强滤镜已`启用`。\n**请注意,音量过大会损害您的听力!**", "filter_disabled": "`✅` | 低音增强滤镜已`禁用`。" + }, + "options": { + "level": "The bassboost level you want to set" } }, "distorsion": { diff --git a/locales/ChineseTW.json b/locales/ChineseTW.json index e9c3d3c33..affb23d34 100644 --- a/locales/ChineseTW.json +++ b/locales/ChineseTW.json @@ -134,6 +134,9 @@ "messages": { "filter_enabled": "`✅` | 低音增強等化器已`啟用`。\n**請注意,音量過大會損害您的聽力!**", "filter_disabled": "`✅` | 低音增強等化器已`停用`。" + }, + "options": { + "level": "The bassboost level you want to set" } }, "distorsion": { diff --git a/locales/EnglishUS.json b/locales/EnglishUS.json index d671e74eb..bc479f6d4 100644 --- a/locales/EnglishUS.json +++ b/locales/EnglishUS.json @@ -132,9 +132,14 @@ }, "bassboost": { "description": "on/off bassboost filter", + "options": { + "level": "The bassboost level you want to set" + }, "messages": { - "filter_enabled": "`✅` | Bassboost filter has been `ENABLED`. \n**Be careful, listening too loudly can damage your hearing!**", - "filter_disabled": "`✅` | Bassboost filter has been `DISABLED`." + "high": "`✅` | High bassboost filter has been `ENABLED`.", + "low": "`✅` | Low bassboost filter has been `ENABLED`.", + "medium": "`✅` | Medium bassboost filter has been `ENABLED`.", + "off": "`✅` | Bassboost filter has been `DISABLED`." } }, "distorsion": { diff --git a/locales/French.json b/locales/French.json index c9c70a1d2..b0ca2a7bb 100644 --- a/locales/French.json +++ b/locales/French.json @@ -121,6 +121,9 @@ "messages": { "filter_enabled": "`✅` | Le filtre de basses a été `ACTIVÉ`. \n**Attention, écouter trop fort peut endommager votre audition !**", "filter_disabled": "`✅` | Le filtre de basses a été `DÉSACTIVÉ`." + }, + "options": { + "level": "The bassboost level you want to set" } }, "distorsion": { diff --git a/locales/German.json b/locales/German.json index f8e495061..53b894886 100644 --- a/locales/German.json +++ b/locales/German.json @@ -121,6 +121,9 @@ "messages": { "filter_enabled": "`✅` | Bassboost-Filter wurde `AKTIVIERT`. \n**Vorsicht, zu lautes Hören kann deine Ohren schädigen!**", "filter_disabled": "`✅` | Bassboost-Filter wurde `DEAKTIVIERT`." + }, + "options": { + "level": "The bassboost level you want to set" } }, "distorsion": { diff --git a/locales/Hindi.json b/locales/Hindi.json index 21760c3ad..e71d90a66 100644 --- a/locales/Hindi.json +++ b/locales/Hindi.json @@ -134,6 +134,9 @@ "messages": { "filter_enabled": "`✅` | Bassboost filter `SAKRIYA` kar diya gaya hai. \n**Savdhan rahein, tej awaz mein sunne se aapke kaano ko nuksan pahunch sakta hai!**", "filter_disabled": "`✅` | Bassboost filter `NIRAKRIYA` kar diya gaya hai." + }, + "options": { + "level": "The bassboost level you want to set" } }, "distorsion": { diff --git a/locales/Indonesian.json b/locales/Indonesian.json index 4cd3616fe..9ff592c91 100644 --- a/locales/Indonesian.json +++ b/locales/Indonesian.json @@ -134,6 +134,9 @@ "messages": { "filter_enabled": "`✅` | Filter bassboost telah `DIAKTIFKAN`. \n**Hati-hati, mendengarkan terlalu keras dapat merusak pendengaran Anda!**", "filter_disabled": "`✅` | Filter bassboost telah `DINONAKTIFKAN`." + }, + "options": { + "level": "The bassboost level you want to set" } }, "distorsion": { diff --git a/locales/Japanese.json b/locales/Japanese.json index dc8899c8f..3e397571e 100644 --- a/locales/Japanese.json +++ b/locales/Japanese.json @@ -134,6 +134,9 @@ "messages": { "filter_enabled": "`✅` | ベースブーストフィルターが`有効`になりました。\n**音量を上げすぎると聴覚に悪影響を与える可能性があるので注意してください!**", "filter_disabled": "`✅` | ベースブーストフィルターが`無効`になりました。" + }, + "options": { + "level": "The bassboost level you want to set" } }, "distorsion": { diff --git a/locales/Korean.json b/locales/Korean.json index 7f3025359..c448eb89c 100644 --- a/locales/Korean.json +++ b/locales/Korean.json @@ -135,6 +135,9 @@ "messages": { "filter_enabled": "`✅` | 베이스부스트 필터가 `활성화되었어요`. \n**조심하세요, 너무 크게 들으면 귀가 나갈 수도 있어요!**", "filter_disabled": "`✅` | 베이스부스트 필터가 `비활성화되었어요`." + }, + "options": { + "level": "The bassboost level you want to set" } }, "distorsion": { @@ -551,8 +554,8 @@ "error_searching": "노래를 검색하는 도중 오류가 발생했어요.", "no_results": "검색결과가 없어요.", "nothing_playing": "재생 중인 노래 없음", - "queue_too_long": "대기열에 노래가 너무 많아요. 노래는 최대 {maxQueueSize}개까지만 추가할 수 있어요.", - "playlist_too_long": "플레이리스트 또는 대기열에 노래가 너무 많아요. 노래는 최대 {maxPlaylistSize}개까지만 추가할 수 있어요.", + "queue_too_long": "대기열에 노래가 너무 많아요. 노래는 최대 {maxQueueSize}개까지만 추가할 수 있어요.", + "playlist_too_long": "플레이리스트 또는 대기열에 노래가 너무 많아요. 노래는 최대 {maxPlaylistSize}개까지만 추가할 수 있어요.", "added_to_queue": "대기열에 추가되었어요: [{title}]({uri})", "added_playlist_to_queue": "[{length}]개의 노래가 추가되었어요." } diff --git a/locales/Norwegian.json b/locales/Norwegian.json index 65112849e..163d69550 100644 --- a/locales/Norwegian.json +++ b/locales/Norwegian.json @@ -134,6 +134,9 @@ "messages": { "filter_enabled": "`✅` | Bassboost-filteret er `AKTIVERT`. \n**Vær forsiktig, å lytte for høyt kan skade hørselen!**", "filter_disabled": "`✅` | Bassboost-filteret er `DEAKTIVERT`." + }, + "options": { + "level": "The bassboost level you want to set" } }, "distorsion": { diff --git a/locales/Polish.json b/locales/Polish.json index 4014481c7..1c9646971 100644 --- a/locales/Polish.json +++ b/locales/Polish.json @@ -134,6 +134,9 @@ "messages": { "filter_enabled": "`✅` | Filtr wzmocnienia basów został `WŁĄCZONY`. \n**Uwaga, zbyt głośne słuchanie może uszkodzić słuch!**", "filter_disabled": "`✅` | Filtr wzmocnienia basów został `WYŁĄCZONY`." + }, + "options": { + "level": "The bassboost level you want to set" } }, "distorsion": { diff --git a/locales/Russian.json b/locales/Russian.json index 4311e12a3..a1d640041 100644 --- a/locales/Russian.json +++ b/locales/Russian.json @@ -134,6 +134,9 @@ "messages": { "filter_enabled": "`✅` | Фильтр усиления басов был `ВКЛЮЧЕН`. \n**Будьте осторожны, слишком громкое прослушивание может повредить слух!**", "filter_disabled": "`✅` | Фильтр усиления басов был `ОТКЛЮЧЕН`." + }, + "options": { + "level": "The bassboost level you want to set" } }, "distorsion": { diff --git a/locales/SpanishES.json b/locales/SpanishES.json index 9df924bff..567356249 100644 --- a/locales/SpanishES.json +++ b/locales/SpanishES.json @@ -121,6 +121,9 @@ "messages": { "filter_enabled": "`✅` | El filtro de refuerzo de graves se ha `ACTIVADO`. \n**¡Ten cuidado, escuchar demasiado alto puede dañar tu oído!**", "filter_disabled": "`✅` | El filtro de refuerzo de graves se ha `DESACTIVADO`." + }, + "options": { + "level": "The bassboost level you want to set" } }, "distorsion": { diff --git a/locales/Vietnamese.json b/locales/Vietnamese.json index ab5b6b6ea..1db3602ca 100644 --- a/locales/Vietnamese.json +++ b/locales/Vietnamese.json @@ -134,6 +134,9 @@ "messages": { "filter_enabled": "`✅` | Bộ lọc Bassboost đã được `BẬT`. \n**Cẩn thận, nghe quá to có thể gây hại cho thính giác của bạn!**", "filter_disabled": "`✅` | Bộ lọc Bassboost đã được `TẮT`." + }, + "options": { + "level": "The bassboost level you want to set" } }, "distorsion": { diff --git a/src/commands/config/Setup.ts b/src/commands/config/Setup.ts index b2fe14649..c17ca182f 100644 --- a/src/commands/config/Setup.ts +++ b/src/commands/config/Setup.ts @@ -93,7 +93,7 @@ export default class Setup extends Command { const player = this.client.manager.getPlayer(ctx.guild!.id); const image = this.client.config.links.img; const desc = - player.queue.current + player && player.queue.current ? `[${player.queue.current.info.title}](${player.queue.current.info.uri})` : ctx.locale("player.setupStart.nothing_playing"); embed.setDescription(desc).setImage(image); @@ -129,7 +129,7 @@ export default class Setup extends Command { } client.db.deleteSetup(ctx.guild!.id); const textChannel = ctx.guild.channels.cache.get(data2.textId); - if (textChannel) await textChannel.delete().catch(() => { }); + if (textChannel) await textChannel.delete().catch(() => {}); await ctx.sendMessage({ embeds: [ { diff --git a/src/commands/filters/8d.ts b/src/commands/filters/8d.ts new file mode 100644 index 000000000..9990b4655 --- /dev/null +++ b/src/commands/filters/8d.ts @@ -0,0 +1,70 @@ +import { Command, type Context, type Lavamusic } from "../../structures/index.js"; + +export default class _8d extends Command { + constructor(client: Lavamusic) { + super(client, { + name: "8d", + description: { + content: "cmd.8d.description", + examples: ["8d"], + usage: "8d", + }, + category: "filters", + aliases: ["3d"], + cooldown: 3, + args: false, + vote: false, + player: { + voice: true, + dj: true, + active: true, + djPerm: null, + }, + permissions: { + dev: false, + client: ["SendMessages", "ReadMessageHistory", "ViewChannel", "EmbedLinks"], + user: [], + }, + slashCommand: true, + options: [], + }); + } + + public async run(client: Lavamusic, ctx: Context): Promise { + const player = client.manager.getPlayer(ctx.guild!.id); + const filterEnabled = player.filterManager.filters.rotation; + + if (filterEnabled) { + await player.filterManager.toggleRotation(); + await ctx.sendMessage({ + embeds: [ + { + description: ctx.locale("cmd.8d.messages.filter_disabled"), + color: this.client.color.main, + }, + ], + }); + } else { + await player.filterManager.toggleRotation(0.2); + await ctx.sendMessage({ + embeds: [ + { + description: ctx.locale("cmd.8d.messages.filter_enabled"), + color: this.client.color.main, + }, + ], + }); + } + } +} + +/** + * Project: lavamusic + * Author: Appu + * Main Contributor: LucasB25 + * Company: Coders + * Copyright (c) 2024. All rights reserved. + * This code is the property of Coder and may not be reproduced or + * modified without permission. For more information, contact us at + * https://discord.gg/ns8CTk9J3e + */ diff --git a/src/commands/filters/BassBoost.ts b/src/commands/filters/BassBoost.ts new file mode 100644 index 000000000..3b9646805 --- /dev/null +++ b/src/commands/filters/BassBoost.ts @@ -0,0 +1,109 @@ +import { ApplicationCommandOptionType } from "discord.js"; +import { EQList } from "lavalink-client"; +import { Command, type Context, type Lavamusic } from "../../structures/index.js"; + +export default class BassBoost extends Command { + constructor(client: Lavamusic) { + super(client, { + name: "bassboost", + description: { + content: "cmd.bassboost.description", + examples: ["bassboost high", "bassboost medium", "bassboost low", "bassboost off"], + usage: "bassboost [level]", + }, + category: "filters", + aliases: ["bb"], + cooldown: 3, + args: true, + vote: false, + player: { + voice: true, + dj: true, + active: true, + djPerm: null, + }, + permissions: { + dev: false, + client: ["SendMessages", "ReadMessageHistory", "ViewChannel", "EmbedLinks"], + user: [], + }, + slashCommand: true, + options: [ + { + name: "level", + description: "cmd.bassboost.options.level", + type: ApplicationCommandOptionType.String, + required: true, + choices: [ + { name: "high", value: "high" }, + { name: "medium", value: "medium" }, + { name: "low", value: "low" }, + { name: "off", value: "off" }, + ], + }, + ], + }); + } + + public async run(client: Lavamusic, ctx: Context): Promise { + const player = client.manager.getPlayer(ctx.guild!.id); + + switch (ctx.args[0]?.toLowerCase()) { + case "high": + await player.filterManager.setEQ(EQList.BassboostHigh); + await ctx.sendMessage({ + embeds: [ + { + description: ctx.locale("cmd.bassboost.messages.high"), + color: this.client.color.main, + }, + ], + }); + break; + case "medium": + await player.filterManager.setEQ(EQList.BassboostMedium); + await ctx.sendMessage({ + embeds: [ + { + description: ctx.locale("cmd.bassboost.messages.medium"), + color: this.client.color.main, + }, + ], + }); + break; + case "low": + await player.filterManager.setEQ(EQList.BassboostLow); + await ctx.sendMessage({ + embeds: [ + { + description: ctx.locale("cmd.bassboost.messages.low"), + color: this.client.color.main, + }, + ], + }); + break; + case "off": + await player.filterManager.clearEQ(); + await ctx.sendMessage({ + embeds: [ + { + description: ctx.locale("cmd.bassboost.messages.off"), + color: this.client.color.main, + }, + ], + }); + break; + } + } +} + +/** + * Project: lavamusic + * Author: Appu + * Main Contributor: LucasB25 + * Company: Coders + * Copyright (c) 2024. All rights reserved. + * This code is the property of Coder and may not be reproduced or + * modified without permission. For more information, contact us at + * https://discord.gg/ns8CTk9J3e + */ diff --git a/src/commands/filters/Karaoke.ts b/src/commands/filters/Karaoke.ts new file mode 100644 index 000000000..8025f4df8 --- /dev/null +++ b/src/commands/filters/Karaoke.ts @@ -0,0 +1,70 @@ +import { Command, type Context, type Lavamusic } from "../../structures/index.js"; + +export default class Karaoke extends Command { + constructor(client: Lavamusic) { + super(client, { + name: "karaoke", + description: { + content: "cmd.karaoke.description", + examples: ["karaoke"], + usage: "karaoke", + }, + category: "filters", + aliases: ["kk"], + cooldown: 3, + args: false, + vote: false, + player: { + voice: true, + dj: true, + active: true, + djPerm: null, + }, + permissions: { + dev: false, + client: ["SendMessages", "ReadMessageHistory", "ViewChannel", "EmbedLinks"], + user: [], + }, + slashCommand: true, + options: [], + }); + } + + public async run(client: Lavamusic, ctx: Context): Promise { + const player = client.manager.getPlayer(ctx.guild!.id); + const filterEnabled = player.filterManager.filters.karaoke; + + if (filterEnabled) { + await player.filterManager.toggleKaraoke(); + await ctx.sendMessage({ + embeds: [ + { + description: ctx.locale("cmd.karaoke.messages.filter_disabled"), + color: this.client.color.main, + }, + ], + }); + } else { + await player.filterManager.toggleKaraoke(); + await ctx.sendMessage({ + embeds: [ + { + description: ctx.locale("cmd.karaoke.messages.filter_enabled"), + color: this.client.color.main, + }, + ], + }); + } + } +} + +/** + * Project: lavamusic + * Author: Appu + * Main Contributor: LucasB25 + * Company: Coders + * Copyright (c) 2024. All rights reserved. + * This code is the property of Coder and may not be reproduced or + * modified without permission. For more information, contact us at + * https://discord.gg/ns8CTk9J3e + */ diff --git a/src/commands/filters/LowPass.ts b/src/commands/filters/LowPass.ts new file mode 100644 index 000000000..10d32278d --- /dev/null +++ b/src/commands/filters/LowPass.ts @@ -0,0 +1,70 @@ +import { Command, type Context, type Lavamusic } from "../../structures/index.js"; + +export default class LowPass extends Command { + constructor(client: Lavamusic) { + super(client, { + name: "lowpass", + description: { + content: "cmd.lowpass.description", + examples: ["lowpass"], + usage: "lowpass ", + }, + category: "filters", + aliases: ["lp"], + cooldown: 3, + args: false, + vote: false, + player: { + voice: true, + dj: true, + active: true, + djPerm: null, + }, + permissions: { + dev: false, + client: ["SendMessages", "ReadMessageHistory", "ViewChannel", "EmbedLinks"], + user: [], + }, + slashCommand: true, + options: [], + }); + } + + public async run(client: Lavamusic, ctx: Context): Promise { + const player = client.manager.getPlayer(ctx.guild!.id); + const filterEnabled = player.filterManager.filters.lowPass; + + if (filterEnabled) { + await player.filterManager.toggleLowPass(); + await ctx.sendMessage({ + embeds: [ + { + description: ctx.locale("cmd.lowpass.messages.filter_disabled"), + color: this.client.color.main, + }, + ], + }); + } else { + await player.filterManager.toggleLowPass(20); + await ctx.sendMessage({ + embeds: [ + { + description: ctx.locale("cmd.lowpass.messages.filter_enabled"), + color: this.client.color.main, + }, + ], + }); + } + } +} + +/** + * Project: lavamusic + * Author: Appu + * Main Contributor: LucasB25 + * Company: Coders + * Copyright (c) 2024. All rights reserved. + * This code is the property of Coder and may not be reproduced or + * modified without permission. For more information, contact us at + * https://discord.gg/ns8CTk9J3e + */ diff --git a/src/commands/filters/NightCore.ts b/src/commands/filters/NightCore.ts new file mode 100644 index 000000000..b25b63eb9 --- /dev/null +++ b/src/commands/filters/NightCore.ts @@ -0,0 +1,70 @@ +import { Command, type Context, type Lavamusic } from "../../structures/index.js"; + +export default class NightCore extends Command { + constructor(client: Lavamusic) { + super(client, { + name: "nightcore", + description: { + content: "cmd.nightcore.description", + examples: ["nightcore"], + usage: "nightcore", + }, + category: "filters", + aliases: ["nc"], + cooldown: 3, + args: false, + vote: false, + player: { + voice: true, + dj: true, + active: true, + djPerm: null, + }, + permissions: { + dev: false, + client: ["SendMessages", "ReadMessageHistory", "ViewChannel", "EmbedLinks"], + user: [], + }, + slashCommand: true, + options: [], + }); + } + + public async run(client: Lavamusic, ctx: Context): Promise { + const player = client.manager.getPlayer(ctx.guild!.id); + const filterEnabled = player.filterManager.filters.nightcore; + + if (filterEnabled) { + await player.filterManager.toggleNightcore(); + await ctx.sendMessage({ + embeds: [ + { + description: ctx.locale("cmd.nightcore.messages.filter_disabled"), + color: this.client.color.main, + }, + ], + }); + } else { + await player.filterManager.toggleNightcore(); + await ctx.sendMessage({ + embeds: [ + { + description: ctx.locale("cmd.nightcore.messages.filter_enabled"), + color: this.client.color.main, + }, + ], + }); + } + } +} + +/** + * Project: lavamusic + * Author: Appu + * Main Contributor: LucasB25 + * Company: Coders + * Copyright (c) 2024. All rights reserved. + * This code is the property of Coder and may not be reproduced or + * modified without permission. For more information, contact us at + * https://discord.gg/ns8CTk9J3e + */ diff --git a/src/commands/music/Queue.ts b/src/commands/music/Queue.ts index 72ebaf10d..968fdba6f 100644 --- a/src/commands/music/Queue.ts +++ b/src/commands/music/Queue.ts @@ -51,14 +51,14 @@ export default class Queue extends Command { } const songStrings = []; for (let i = 0; i < player.queue.tracks.length; i++) { - const track = player.queue[i]; + const track = player.queue.tracks[i]; songStrings.push( ctx.locale("cmd.queue.track_info", { index: i + 1, title: track.info.title, uri: track.info.uri, - requester: track?.info.requester, - duration: track.info.isStream ? ctx.locale("cmd.queue.live") : client.utils.formatTime(track.info.length), + requester: `<@${(track.requester as any).id}>`, + duration: track.info.isStream ? ctx.locale("cmd.queue.live") : client.utils.formatTime(track.info.duration), }), ); } diff --git a/src/env.ts b/src/env.ts index 75277f8db..21a0599b2 100644 --- a/src/env.ts +++ b/src/env.ts @@ -74,9 +74,17 @@ const envSchema = z.object({ LOG_COMMANDS_ID: z.string().optional(), /** - * The bot status + * The bot status online | idle | dnd | invisible */ - BOT_STATUS: z.string().default("online"), + BOT_STATUS: z.preprocess( + (val) => { + if (typeof val === "string") { + return val.toLowerCase(); + } + return val; + }, + z.enum(["online", "idle", "dnd", "invisible"]).default("online"), + ), /** * The bot activity diff --git a/src/events/client/SetupButtons.ts b/src/events/client/SetupButtons.ts new file mode 100644 index 000000000..9a04a4d4d --- /dev/null +++ b/src/events/client/SetupButtons.ts @@ -0,0 +1,250 @@ +import type { Message } from "discord.js"; +import { T } from "../../structures/I18n"; +import { Event, type Lavamusic } from "../../structures/index"; +import type { Requester } from "../../types"; +import { getButtons } from "../../utils/Buttons"; +import { buttonReply } from "../../utils/SetupSystem"; +import { checkDj } from "../player/TrackStart"; + +export default class SetupButtons extends Event { + constructor(client: Lavamusic, file: string) { + super(client, file, { + name: "setupButtons", + }); + } + + public async run(interaction: any): Promise { + const locale = await this.client.db.getLanguage(interaction.guildId); + + if (!interaction.replied) await interaction.deferReply().catch(() => {}); + if (!interaction.member.voice.channel) { + return await buttonReply(interaction, T(locale, "event.setupButton.no_voice_channel_button"), this.client.color.red); + } + const clientMember = interaction.guild.members.cache.get(this.client.user.id); + if (clientMember.voice.channel && clientMember.voice.channelId !== interaction.member.voice.channelId) { + return await buttonReply( + interaction, + T(locale, "event.setupButton.different_voice_channel_button", { + channel: clientMember.voice.channel, + }), + this.client.color.red, + ); + } + const player = this.client.manager.getPlayer(interaction.guildId); + if (!player) return await buttonReply(interaction, T(locale, "event.setupButton.no_music_playing"), this.client.color.red); + if (!player.queue) return await buttonReply(interaction, T(locale, "event.setupButton.no_music_playing"), this.client.color.red); + if (!player.queue.current) + return await buttonReply(interaction, T(locale, "event.setupButton.no_music_playing"), this.client.color.red); + const data = await this.client.db.getSetup(interaction.guildId); + const { title, uri, duration, artworkUrl, sourceName, isStream } = player.queue.current.info; + let message: Message; + try { + message = await interaction.channel.messages.fetch(data.messageId, { + cache: true, + }); + } catch (_e) { + /* empty */ + } + + const iconUrl = this.client.config.icons[sourceName] || this.client.user.displayAvatarURL({ extension: "png" }); + const embed = this.client + .embed() + .setAuthor({ + name: T(locale, "event.setupButton.now_playing"), + iconURL: iconUrl, + }) + .setColor(this.client.color.main) + .setDescription( + `[${title}](${uri}) - ${isStream ? T(locale, "event.setupButton.live") : this.client.utils.formatTime(duration)} - ${T(locale, "event.setupButton.requested_by", { requester: `<@${(player.queue.current.requester as Requester).id}>` })}`, + ) + .setImage(artworkUrl || this.client.user.displayAvatarURL({ extension: "png" })); + + if (!interaction.isButton()) return; + if (!(await checkDj(this.client, interaction))) { + return await buttonReply(interaction, T(locale, "event.setupButton.no_dj_permission"), this.client.color.red); + } + if (message) { + const handleVolumeChange = async (change: number) => { + const vol = player.volume + change; + player.setVolume(vol); + await buttonReply(interaction, T(locale, "event.setupButton.volume_set", { vol }), this.client.color.main); + await message.edit({ + embeds: [ + embed.setFooter({ + text: T(locale, "event.setupButton.volume_footer", { + vol, + displayName: interaction.member.displayName, + }), + iconURL: interaction.member.displayAvatarURL({}), + }), + ], + }); + }; + switch (interaction.customId) { + case "LOW_VOL_BUT": + await handleVolumeChange(-10); + break; + case "HIGH_VOL_BUT": + await handleVolumeChange(10); + break; + case "PAUSE_BUT": { + const name = player.paused ? T(locale, "event.setupButton.resumed") : T(locale, "event.setupButton.paused"); + player.pause(); + await buttonReply(interaction, T(locale, "event.setupButton.pause_resume", { name }), this.client.color.main); + await message.edit({ + embeds: [ + embed.setFooter({ + text: T(locale, "event.setupButton.pause_resume_footer", { + name, + displayName: interaction.member.displayName, + }), + iconURL: interaction.member.displayAvatarURL({}), + }), + ], + components: getButtons(player, this.client), + }); + break; + } + case "SKIP_BUT": + if (player.queue.tracks.length === 0) { + return await buttonReply(interaction, T(locale, "event.setupButton.no_music_to_skip"), this.client.color.main); + } + player.skip(); + await buttonReply(interaction, T(locale, "event.setupButton.skipped"), this.client.color.main); + await message.edit({ + embeds: [ + embed.setFooter({ + text: T(locale, "event.setupButton.skipped_footer", { + displayName: interaction.member.displayName, + }), + iconURL: interaction.member.displayAvatarURL({}), + }), + ], + }); + break; + case "STOP_BUT": + player.stopPlaying(true, false); + await buttonReply(interaction, T(locale, "event.setupButton.stopped"), this.client.color.main); + await message.edit({ + embeds: [ + embed + .setFooter({ + text: T(locale, "event.setupButton.stopped_footer", { + displayName: interaction.member.displayName, + }), + iconURL: interaction.member.displayAvatarURL({}), + }) + .setDescription(T(locale, "event.setupButton.nothing_playing")) + .setImage(this.client.config.links.img) + .setAuthor({ + name: this.client.user.username, + iconURL: this.client.user.displayAvatarURL({ + extension: "png", + }), + }), + ], + }); + break; + case "LOOP_BUT": { + const loopOptions: Array<"off" | "queue" | "track"> = ["off", "queue", "track"]; + const newLoop = loopOptions[(loopOptions.indexOf(player.repeatMode) + 1) % loopOptions.length]; + player.setRepeatMode(newLoop); + await buttonReply( + interaction, + T(locale, "event.setupButton.loop_set", { + loop: newLoop, + }), + this.client.color.main, + ); + await message.edit({ + embeds: [ + embed.setFooter({ + text: T(locale, "event.setupButton.loop_footer", { + loop: newLoop, + displayName: interaction.member.displayName, + }), + iconURL: interaction.member.displayAvatarURL({}), + }), + ], + }); + break; + } + case "SHUFFLE_BUT": + player.queue.shuffle(); + await buttonReply(interaction, T(locale, "event.setupButton.shuffled"), this.client.color.main); + break; + case "PREV_BUT": + if (!player.queue.previous) { + return await buttonReply(interaction, T(locale, "event.setupButton.no_previous_track"), this.client.color.main); + } + player.play({ + track: player.queue.previous[0], + }); + await buttonReply(interaction, T(locale, "event.setupButton.playing_previous"), this.client.color.main); + await message.edit({ + embeds: [ + embed.setFooter({ + text: T(locale, "event.setupButton.previous_footer", { + displayName: interaction.member.displayName, + }), + iconURL: interaction.member.displayAvatarURL({}), + }), + ], + }); + break; + case "REWIND_BUT": { + const time = player.position - 10000; + if (time < 0) { + return await buttonReply(interaction, T(locale, "event.setupButton.rewind_limit"), this.client.color.main); + } + player.seek(time); + await buttonReply(interaction, T(locale, "event.setupButton.rewinded"), this.client.color.main); + await message.edit({ + embeds: [ + embed.setFooter({ + text: T(locale, "event.setupButton.rewind_footer", { + displayName: interaction.member.displayName, + }), + iconURL: interaction.member.displayAvatarURL({}), + }), + ], + }); + break; + } + case "FORWARD_BUT": { + const time = player.position + 10000; + if (time > player.queue.current.info.duration) { + return await buttonReply(interaction, T(locale, "event.setupButton.forward_limit"), this.client.color.main); + } + player.seek(time); + await buttonReply(interaction, T(locale, "event.setupButton.forwarded"), this.client.color.main); + await message.edit({ + embeds: [ + embed.setFooter({ + text: T(locale, "event.setupButton.forward_footer", { + displayName: interaction.member.displayName, + }), + iconURL: interaction.member.displayAvatarURL({}), + }), + ], + }); + break; + } + default: + await buttonReply(interaction, T(locale, "event.setupButton.button_not_available"), this.client.color.main); + break; + } + } + } +} + +/** + * Project: lavamusic + * Author: Appu + * Main Contributor: LucasB25 + * Company: Coders + * Copyright (c) 2024. All rights reserved. + * This code is the property of Coder and may not be reproduced or + * modified without permission. For more information, contact us at + * https://discord.gg/ns8CTk9J3e + */ diff --git a/src/events/client/SetupSystem.ts b/src/events/client/SetupSystem.ts new file mode 100644 index 000000000..b033c07ce --- /dev/null +++ b/src/events/client/SetupSystem.ts @@ -0,0 +1,77 @@ +import { type Message, PermissionsBitField, TextChannel } from "discord.js"; +import { T } from "../../structures/I18n"; +import { Event, type Lavamusic } from "../../structures/index"; +import { oops, setupStart } from "../../utils/SetupSystem"; + +export default class SetupSystem extends Event { + constructor(client: Lavamusic, file: string) { + super(client, file, { + name: "setupSystem", + }); + } + + public async run(message: Message): Promise { + const locale = await this.client.db.getLanguage(message.guildId); + const channel = message.channel as TextChannel; + if (!(channel instanceof TextChannel)) return; + if (!message.member?.voice.channel) { + await oops(channel, T(locale, "event.message.no_voice_channel_queue")); + await message.delete().catch(() => {}); + return; + } + + const voiceChannel = message.member.voice.channel; + const clientUser = this.client.user; + const clientMember = message.guild.members.cache.get(clientUser.id); + + if (!voiceChannel.permissionsFor(clientUser).has(PermissionsBitField.Flags.Connect | PermissionsBitField.Flags.Speak)) { + await oops( + channel, + T(locale, "event.message.no_permission_connect_speak", { + channel: voiceChannel.id, + }), + ); + await message.delete().catch(() => {}); + return; + } + + if (clientMember?.voice.channel && clientMember.voice.channelId !== voiceChannel.id) { + await oops( + channel, + T(locale, "event.message.different_voice_channel_queue", { + channel: clientMember.voice.channelId, + }), + ); + await message.delete().catch(() => {}); + return; + } + + let player = this.client.manager.getPlayer(message.guildId); + if (!player) { + player = this.client.manager.createPlayer({ + guildId: message.guildId, + voiceChannelId: voiceChannel.id, + textChannelId: message.channelId, + selfMute: false, + selfDeaf: true, + instaUpdateFiltersFix: true, + vcRegion: voiceChannel.rtcRegion, + }); + if (!player.connected) await player.connect(); + } + + await setupStart(this.client, message.content, player, message); + await message.delete().catch(() => {}); + } +} + +/** + * Project: lavamusic + * Author: Appu + * Main Contributor: LucasB25 + * Company: Coders + * Copyright (c) 2024. All rights reserved. + * This code is the property of Coder and may not be reproduced or + * modified without permission. For more information, contact us at + * https://discord.gg/ns8CTk9J3e + */ diff --git a/src/events/node/Connect.ts b/src/events/node/Connect.ts index d26d19b3a..303ae8040 100644 --- a/src/events/node/Connect.ts +++ b/src/events/node/Connect.ts @@ -49,6 +49,6 @@ export default class Connect extends Event { }, index * 1000); }); - BotLog.send(this.client, `Node ${node} is ready!`, "success"); + BotLog.send(this.client, `Node ${node.id} is ready!`, "success"); } } diff --git a/src/events/node/Destroy.ts b/src/events/node/Destroy.ts index 2b0147c0e..768d6f67a 100644 --- a/src/events/node/Destroy.ts +++ b/src/events/node/Destroy.ts @@ -11,6 +11,6 @@ export default class Destroy extends Event { public async run(node: LavalinkNode, destroyReason?: DestroyReasonsType): Promise { this.client.logger.success(`Node ${node.id} is destroyed!`); - BotLog.send(this.client, `Node ${node} is destroyed: ${destroyReason}`, "warn"); + BotLog.send(this.client, `Node ${node.id} is destroyed: ${destroyReason}`, "warn"); } } diff --git a/src/structures/Event.ts b/src/structures/Event.ts index bb3ca41b4..452be054e 100644 --- a/src/structures/Event.ts +++ b/src/structures/Event.ts @@ -1,8 +1,13 @@ -import type { ClientEvents } from "discord.js"; -import type Lavamusic from "./Lavamusic"; +import type { ButtonInteraction, ClientEvents, Message } from "discord.js"; import type { LavalinkManagerEvents, NodeManagerEvents } from "lavalink-client"; +import type Lavamusic from "./Lavamusic"; -export type AllEvents = LavalinkManagerEvents & NodeManagerEvents & ClientEvents; +// custom client events setupSystem and setupButtons +interface CustomClientEvents { + setupSystem: (message: Message) => void; + setupButtons: (interaction: ButtonInteraction) => void; +} +export type AllEvents = LavalinkManagerEvents & NodeManagerEvents & ClientEvents & CustomClientEvents; interface EventOptions { name: keyof AllEvents; diff --git a/src/structures/LavalinkClient.ts b/src/structures/LavalinkClient.ts index 78840e5e6..6e2bc3c2b 100644 --- a/src/structures/LavalinkClient.ts +++ b/src/structures/LavalinkClient.ts @@ -1,5 +1,5 @@ import { LavalinkManager, type LavalinkNodeOptions, type SearchPlatform, type SearchResult } from "lavalink-client"; -import { requesterTransformer } from "../utils/functions/player"; +import { autoPlayFunction, requesterTransformer } from "../utils/functions/player"; import type Lavamusic from "./Lavamusic"; export default class LavalinkClient extends LavalinkManager { @@ -18,7 +18,7 @@ export default class LavalinkClient extends LavalinkManager { }, requesterTransformer: requesterTransformer, onEmptyQueue: { - //autoPlayFunction, + autoPlayFunction, }, }, }); diff --git a/src/utils/SetupSystem.ts b/src/utils/SetupSystem.ts index 20c0c9885..6eb3b23be 100644 --- a/src/utils/SetupSystem.ts +++ b/src/utils/SetupSystem.ts @@ -1,10 +1,10 @@ import { type ColorResolvable, EmbedBuilder, type Message, type TextChannel } from "discord.js"; +import type { Player, Track } from "lavalink-client"; import { T } from "../structures/I18n"; import type { Lavamusic } from "../structures/index"; -import { getButtons } from "./Buttons"; -import type { Player, Track } from "lavalink-client"; import type { Requester } from "../types"; +import { getButtons } from "./Buttons"; /** * A function that will generate an embed based on the player's current track. @@ -63,139 +63,55 @@ async function setupStart(client: Lavamusic, query: string, player: Player, mess if (m) { try { if (message.inGuild()) { - /* const res = await client.queue.search(query); + const res = await player.search(query, message.author); + switch (res.loadType) { - case LoadType.ERROR: + case "empty": + case "error": await message.channel .send({ embeds: [embed.setColor(client.color.red).setDescription(T(locale, "player.setupStart.error_searching"))], }) .then((msg) => setTimeout(() => msg.delete(), 5000)); break; - case LoadType.EMPTY: + case "search": + case "track": + player.queue.add(res.tracks[0]); await message.channel .send({ - embeds: [embed.setColor(client.color.red).setDescription(T(locale, "player.setupStart.no_results"))], - }) - .then((msg) => setTimeout(() => msg.delete(), 5000)); - break; - case LoadType.TRACK: { - const track = player.buildTrack(res.data, message.author); - if (player.queue.length > client.config.maxQueueSize) { - await message.channel - .send({ - embeds: [ - embed.setColor(client.color.red).setDescription( - T(locale, "player.setupStart.queue_too_long", { - maxQueueSize: client.config.maxQueueSize, + embeds: [ + embed + .setColor(client.color.main) + .setDescription( + T(locale, "player.setupStart.added_to_queue", { + title: res.tracks[0].info.title, + uri: res.tracks[0].info.uri, }), ), - ], - }) - .then((msg) => setTimeout(() => msg.delete(), 5000)); - return; - } - player.queue.push(track); - await player.isPlaying(); - await message.channel - .send({ - embeds: [ - embed.setColor(client.color.main).setDescription( - T(locale, "player.setupStart.added_to_queue", { - title: res.data.info.title, - uri: res.data.info.uri, - }), - ), ], }) .then((msg) => setTimeout(() => msg.delete(), 5000)); neb(n, player, client, locale); await m.edit({ embeds: [n] }).catch(() => {}); break; - } - case LoadType.PLAYLIST: - if (res.data.tracks.length > client.config.maxPlaylistSize) { - await message.channel - .send({ - embeds: [ - embed.setColor(client.color.red).setDescription( - T(locale, "player.setupStart.playlist_too_long", { - maxPlaylistSize: client.config.maxPlaylistSize, - }), - ), - ], - }) - .then((msg) => setTimeout(() => msg.delete(), 5000)); - return; - } - for (const track of res.data.tracks) { - const pl = player.buildTrack(track, message.author); - if (player.queue.length > client.config.maxQueueSize) { - await message.channel - .send({ - embeds: [ - embed.setColor(client.color.red).setDescription( - T(locale, "player.setupStart.queue_too_long", { - maxQueueSize: client.config.maxQueueSize, - }), - ), - ], - }) - .then((msg) => setTimeout(() => msg.delete(), 5000)); - return; - } - player.queue.push(pl); - } - await player.isPlaying(); + case "playlist": + player.queue.add(res.tracks); await message.channel .send({ embeds: [ embed .setColor(client.color.main) .setDescription( - T(locale, "player.setupStart.added_playlist_to_queue", { length: res.data.tracks.length }), - ), - ], - }) - .then((msg) => setTimeout(() => msg.delete(), 5000)); - neb(n, player, client, locale); - await m.edit({ embeds: [n] }).catch(() => {}); - break; - case LoadType.SEARCH: { - const track = player.buildTrack(res.data[0], message.author); - if (player.queue.length > client.config.maxQueueSize) { - await message.channel - .send({ - embeds: [ - embed.setColor(client.color.red).setDescription( - T(locale, "player.setupStart.queue_too_long", { - maxQueueSize: client.config.maxQueueSize, - }), + T(locale, "player.setupStart.added_playlist_to_queue", { length: res.tracks.length }), ), - ], - }) - .then((msg) => setTimeout(() => msg.delete(), 5000)); - return; - } - player.queue.push(track); - await player.isPlaying(); - await message.channel - .send({ - embeds: [ - embed.setColor(client.color.main).setDescription( - T(locale, "player.setupStart.added_to_queue", { - title: res.data[0].info.title, - uri: res.data[0].info.uri, - }), - ), ], }) .then((msg) => setTimeout(() => msg.delete(), 5000)); neb(n, player, client, locale); await m.edit({ embeds: [n] }).catch(() => {}); break; - } - } */ + } + if (!player.playing) await player.play(); } } catch (error) { client.logger.error(error); @@ -258,7 +174,7 @@ async function trackStart( return b; }), }) - .catch(() => { }); + .catch(() => {}); } else { await channel .send({ @@ -271,7 +187,7 @@ async function trackStart( .then((msg) => { client.db.setSetup(msg.guild.id, msg.id, msg.channel.id); }) - .catch(() => { }); + .catch(() => {}); } } @@ -319,7 +235,7 @@ async function updateSetup(client: Lavamusic, guild: any, locale: string): Promi return b; }), }) - .catch(() => { }); + .catch(() => {}); } else { const embed = client .embed() @@ -338,7 +254,7 @@ async function updateSetup(client: Lavamusic, guild: any, locale: string): Promi return b; }), }) - .catch(() => { }); + .catch(() => {}); } } } @@ -347,13 +263,13 @@ async function buttonReply(int: any, args: string, color: ColorResolvable): Prom const embed = new EmbedBuilder(); let m: Message; if (int.replied) { - m = await int.editReply({ embeds: [embed.setColor(color).setDescription(args)] }).catch(() => { }); + m = await int.editReply({ embeds: [embed.setColor(color).setDescription(args)] }).catch(() => {}); } else { - m = await int.followUp({ embeds: [embed.setColor(color).setDescription(args)] }).catch(() => { }); + m = await int.followUp({ embeds: [embed.setColor(color).setDescription(args)] }).catch(() => {}); } setTimeout(async () => { if (int && !int.ephemeral) { - await m.delete().catch(() => { }); + await m.delete().catch(() => {}); } }, 2000); } @@ -364,7 +280,7 @@ async function oops(channel: TextChannel, args: string): Promise { const m = await channel.send({ embeds: [embed1], }); - setTimeout(async () => await m.delete().catch(() => { }), 12000); + setTimeout(async () => await m.delete().catch(() => {}), 12000); } catch (e) { return console.error(e); } diff --git a/src/utils/functions/player.ts b/src/utils/functions/player.ts index 5e61171c3..d59f83363 100644 --- a/src/utils/functions/player.ts +++ b/src/utils/functions/player.ts @@ -1,3 +1,4 @@ +import type { Player, Track } from "lavalink-client/dist/types"; import type { Requester } from "../../types"; /** @@ -22,3 +23,80 @@ export const requesterTransformer = (requester: any): Requester => { } return { id: requester!.toString(), username: "unknown" }; }; + +/** + * Function that will be called when the autoplay feature is enabled and the queue + * is empty. It will search for tracks based on the last played track and add them + * to the queue. + * + * @param {Player} player The player instance. + * @param {Track} lastTrack The last played track. + * @returns {Promise} A promise that resolves when the function is done. + */ +export async function autoPlayFunction(player: Player, lastTrack?: Track): Promise { + if (!player.get("autoplay")) return; + if (!lastTrack) return; + + if (lastTrack.info.sourceName === "spotify") { + const filtered = player.queue.previous.filter((v) => v.info.sourceName === "spotify").slice(0, 5); + const ids = filtered.map( + (v) => v.info.identifier || v.info.uri.split("/")?.reverse()?.[0] || v.info.uri.split("/")?.reverse()?.[1], + ); + if (ids.length >= 2) { + const res = await player + .search( + { + query: `seed_tracks=${ids.join(",")}`, //`seed_artists=${artistIds.join(",")}&seed_genres=${genre.join(",")}&seed_tracks=${trackIds.join(",")}`; + source: "sprec", + }, + lastTrack.requester, + ) + .then((response: any) => { + response.tracks = response.tracks.filter((v) => v.info.identifier !== lastTrack.info.identifier); // remove the lastPlayed track if it's in there.. + return response; + }) + .catch(console.warn); + if (res && res.tracks.length > 0) + await player.queue.add( + res.tracks.slice(0, 5).map((track) => { + // transform the track plugininfo so you can figure out if the track is from autoplay or not. + track.pluginInfo.clientData = { ...(track.pluginInfo.clientData || {}), fromAutoplay: true }; + return track; + }), + ); + } + return; + } + if (lastTrack.info.sourceName === "youtube" || lastTrack.info.sourceName === "youtubemusic") { + const res = await player + .search( + { + query: `https://www.youtube.com/watch?v=${lastTrack.info.identifier}&list=RD${lastTrack.info.identifier}`, + source: "youtube", + }, + lastTrack.requester, + ) + .then((response: any) => { + response.tracks = response.tracks.filter((v) => v.info.identifier !== lastTrack.info.identifier); // remove the lastPlayed track if it's in there.. + return response; + }) + .catch(console.warn); + if (res && res.tracks.length > 0) + await player.queue.add( + res.tracks.slice(0, 5).map((track) => { + // transform the track plugininfo so you can figure out if the track is from autoplay or not. + track.pluginInfo.clientData = { ...(track.pluginInfo.clientData || {}), fromAutoplay: true }; + return track; + }), + ); + return; + } + if (lastTrack.info.sourceName === "jiosaavn") { + const res = await player.search({ query: `jsrec:${lastTrack.info.identifier}`, source: "jsrec" }, lastTrack.requester); + if (res.tracks.length > 0) { + const track = res.tracks.filter((v) => v.info.identifier !== lastTrack.info.identifier)[0]; + await player.queue.add(track); + } + } + return; +}