import { type APIEmbed, ActionRowBuilder, ButtonBuilder, ButtonStyle, ChannelType, Client, EmbedBuilder, GatewayIntentBits, type Message, SlashCommandBuilder, Interaction, } from "discord.js"; import { Connectors, Track } from "shoukaku"; import { Kazagumo, type KazagumoPlayer, type KazagumoTrack, type Payload, } from "kazagumo"; import KazagumoPlugin from "kazagumo-spotify"; import prettyMilliseconds from "pretty-ms"; const { Guilds, GuildVoiceStates, GuildMessages, MessageContent } = GatewayIntentBits; import * as dotenv from "dotenv"; import { readFileSync, writeFileSync } from "fs"; import { channel } from "process"; dotenv.config(); if (!process.env.SPOTIFY_ID || !process.env.SPOTIFY_SECRET) { throw "Spotify Credentials missing"; } let settings: settings; try { settings = JSON.parse(readFileSync("./settings.json", "utf8")); // Load data } catch (e) { settings = { requestChannels: {} }; // Init if no data found } const Nodes = [ { name: "lavalink", url: "localhost:2333", auth: "youshallnotpass", secure: false, }, ]; const client = new Client({ intents: [Guilds, GuildVoiceStates, GuildMessages, MessageContent], }); const kazagumo = new Kazagumo( { defaultSearchEngine: "youtube", send: (guildId: string, payload: Payload) => { const guild = client.guilds.cache.get(guildId); if (guild) guild.shard.send(payload); }, plugins: [ new KazagumoPlugin({ clientId: process.env.SPOTIFY_ID, clientSecret: process.env.SPOTIFY_SECRET, playlistPageLimit: 1, // optional ( 100 tracks per page ) albumPageLimit: 1, // optional ( 50 tracks per page ) searchLimit: 10, // optional ( track search limit. Max 50 ) searchMarket: "US", // optional || default: US ( Enter the country you live in. [ Can only be of 2 letters. For eg: US, IN, EN ] )// }), ], }, new Connectors.DiscordJS(client), Nodes ); interface requestChannels { [guildId: string]: { channelId: string; messageId: string; }; } interface settings { requestChannels: requestChannels; } client.on("ready", () => { // console.log(client) console.log(`${client.user!.tag}·Ready!`); }); kazagumo.shoukaku.on("ready", (name: string) => console.log(`Lavalink ${name}: Ready!`) ); kazagumo.shoukaku.on("error", (name: string, error: Error) => console.error(`Lavalink ${name}: Error Caught,`, error) ); kazagumo.shoukaku.on("close", (name: string, code: unknown, reason: unknown) => console.warn( `Lavalink ${name}: Closed, Code ${code}, Reason ${reason || "No reason"}` ) ); kazagumo.shoukaku.on("debug", (name: string, info: string) => console.debug(`Lavalink ${name}: Debug,`, info) ); kazagumo.shoukaku.on("disconnect", (name: string, count: number) => { // if (moved) return; // players.map((player:any) => player.connection.disconnect()); console.warn(`Lavalink ${name}: Disconnected`); }); kazagumo.on("playerStart", (player: KazagumoPlayer, track: KazagumoTrack) => { if (!player.textId) return; const channel = client.channels.cache.get(player.textId); if (!channel) return; if (channel.type === ChannelType.GuildText) { channel .send({ content: `Now playing **${track.title}** by **${track.author}**`, }) .then((x: any) => { player.data.set("message", x); setTimeout(() => x.delete(), 3000); }); } }); kazagumo.on("playerEnd", (player: KazagumoPlayer) => { player.data.get("message")?.edit({ content: "Finished playing" }); }); kazagumo.on("playerEmpty", (player: KazagumoPlayer) => { if (!player.textId) return; const channel = client.channels.cache.get(player.textId); if (!channel) return; if (channel.type === ChannelType.GuildText) { channel .send({ content: "Destroyed player due to inactivity." }) .then((x: any) => { player.data.set("message", x); setTimeout(() => x.delete(), 3000); }); } player.destroy(); }); client.on("messageCreate", async (msg: Message) => { // console.log(msg.content); if (msg.author.bot) return; if (!msg.guild) return; if (!msg.member) return; if (msg.channel.type !== ChannelType.GuildText) return; if (msg.content.startsWith(".")) { let cmd = msg.content.split(".")[1]; switch (cmd) { case "init": if (msg.guildId && msg.channelId) { if (!settings.requestChannels[msg.guildId]) { await msg.delete(); const play = new ButtonBuilder() .setCustomId("play") .setLabel("Play/Pause") .setStyle(ButtonStyle.Secondary) .setEmoji("889943073793122355"); const stop = new ButtonBuilder() .setCustomId("stop") .setLabel("Stop Playing") .setStyle(ButtonStyle.Secondary) .setEmoji("889943074258694184"); const skip = new ButtonBuilder() .setCustomId("skip") .setLabel("Skip Song") .setStyle(ButtonStyle.Secondary) .setEmoji("889943074233516072"); const loop = new ButtonBuilder() .setCustomId("loop") .setLabel("Loop Song") .setStyle(ButtonStyle.Secondary) .setEmoji("889943073667289099"); const shuffle = new ButtonBuilder() .setCustomId("shuffle") .setLabel("Shuffle Queue") .setStyle(ButtonStyle.Secondary) .setEmoji("890325437962678352"); const seek = new ButtonBuilder() .setCustomId("seek") .setLabel("Seek Forward") .setStyle(ButtonStyle.Secondary) .setEmoji("890325511878889504"); const previous = new ButtonBuilder() .setCustomId("previous") .setLabel("Previous Song") .setStyle(ButtonStyle.Secondary) .setEmoji("890325512071831562"); const row = new ActionRowBuilder().addComponents( play, stop, skip, loop ); const row2 = new ActionRowBuilder().addComponents( shuffle, seek, previous ); let track_author: string; track_author = "None"; let track_duration; track_duration = "NaN:NaN"; const embed = new EmbedBuilder() .setAuthor({ name: "Moe", iconURL: "https://cdn.m3.fyi/MoeLogo.gif", }) .setTitle("Nothing is being played right now") .setDescription("Enter message to search") .addFields( { name: "Author", value: track_author, inline: true, }, { name: "Duration", value: track_duration, inline: true, } ) .setColor("#ff0047"); const response = await msg.channel.send({ embeds: [embed], components: [row, row2], }); settings.requestChannels[msg.guildId] = { channelId: msg.channelId, messageId: response.id, }; console.log(settings.requestChannels[msg.guildId]); writeFileSync("./settings.json", JSON.stringify(settings)); // Save count to file return; } else { let answer = await msg.reply("Already initialized"); console.log(answer); setTimeout(() => msg.delete(), 3000); setTimeout(() => answer.delete(), 3000); } } break; default: break; } return; } if (msg.channel.name === "moe-song-requests") { let controls; if (!settings.requestChannels[msg.guild.id]) { let answer = await msg.reply("Use .init first"); console.log(answer); setTimeout(() => msg.delete(), 3000); setTimeout(() => answer.delete(), 3000); return; } else { let channelId = settings.requestChannels[msg.guild.id].channelId; let messageId = settings.requestChannels[msg.guild.id].messageId; let channel = msg.guild.channels.cache.get(channelId); if (channel && channel.type === ChannelType.GuildText) { controls = await channel.messages.fetch(messageId); } // let controls = msg.channel.guild // get(requestChannels[msg.guild.id]); // client.channels.fetch(requestChannels[msg.guild.id]) } const query = msg.content; const { channel } = msg.member.voice; if (!channel) { let answer = await msg.reply( "You need to be in a voice channel to use this command!" ); setTimeout(() => answer.delete(), 3000); return; } const player = await kazagumo.createPlayer({ guildId: msg.guild.id, textId: msg.channel.id, voiceId: channel.id, volume: 40, }); console.log("Player created"); const result = await kazagumo.search(query, { requester: msg.author }); if (!result.tracks.length) { let answer = await msg.reply("No results found!"); setTimeout(() => answer.delete(), 3000); } if (result.type === "PLAYLIST") for (const track of result.tracks) player.queue.add(track); else player.queue.add(result.tracks[0]); const play = new ButtonBuilder() .setCustomId("play") .setLabel("Play/Pause") .setStyle(ButtonStyle.Secondary) .setEmoji("889943073793122355"); const stop = new ButtonBuilder() .setCustomId("stop") .setLabel("Stop Playing") .setStyle(ButtonStyle.Secondary) .setEmoji("889943074258694184"); const skip = new ButtonBuilder() .setCustomId("skip") .setLabel("Skip Song") .setStyle(ButtonStyle.Secondary) .setEmoji("889943074233516072"); const loop = new ButtonBuilder() .setCustomId("loop") .setLabel("Loop Song") .setStyle(ButtonStyle.Secondary) .setEmoji("889943073667289099"); const shuffle = new ButtonBuilder() .setCustomId("shuffle") .setLabel("Shuffle Queue") .setStyle(ButtonStyle.Secondary) .setEmoji("890325437962678352"); const seek = new ButtonBuilder() .setCustomId("seek") .setLabel("Seek Forward") .setStyle(ButtonStyle.Secondary) .setEmoji("890325511878889504"); const previous = new ButtonBuilder() .setCustomId("previous") .setLabel("Previous Song") .setStyle(ButtonStyle.Secondary) .setEmoji("890325512071831562"); const row = new ActionRowBuilder().addComponents( play, stop, skip, loop ); const row2 = new ActionRowBuilder().addComponents( shuffle, seek, previous ); let track_author: string; if (result.tracks[0].author) { track_author = result.tracks[0].author; } else { track_author = ""; } let track_duration: string; if (result.tracks[0].length) { track_duration = prettyMilliseconds(result.tracks[0].length); } else { track_duration = "NaN:NaN"; } let track_thumbnail: string; if (result.tracks[0].thumbnail) { track_thumbnail = result.tracks[0].thumbnail.toString(); } else { track_thumbnail = "NaN:NaN"; } const embed = new EmbedBuilder() .setAuthor({ name: "Now playing", iconURL: "https://cdn.m3.fyi/MoeLogo.gif", }) .setTitle( result.type === "PLAYLIST" ? `Playing ${result.tracks.length} from ${result.playlistName}` : `Playing ${result.tracks[0].title}` ) .setDescription(`[${result.tracks[0].title}](${result.tracks[0].uri})`) .addFields( { name: "Author", value: track_author, inline: true, }, { name: "Duration", value: track_duration, inline: true, } ) .setThumbnail(track_thumbnail) .setColor("#ff0047"); if (!player.playing && !player.paused) player.play(); if (controls) { const response = controls.edit({ embeds: [embed], components: [row, row2], }); } setTimeout(() => msg.delete(), 3000); return; } }); client.on("interactionCreate", async (interaction: Interaction) => { console.log(interaction.id); if (interaction.isButton()) { const guild = client.guilds.cache.get(interaction.guildId!); const member = guild?.members.cache.get(interaction.member!.user.id); let voiceId = member?.voice.channel!.id; let player = await kazagumo.createPlayer({ guildId: interaction.guild!.id, textId: interaction.channel!.id, voiceId: voiceId!, }); // volume: 40 // }) switch (interaction.customId) { case "play": player.pause(!player.paused); await interaction.reply({ ephemeral: true, content: "Toggled Pause", }); break; case "stop": // player.disconnect(); player.destroy(); await interaction.reply({ ephemeral: true, content: "Stopped Playing", }); break; case "skip": player.skip(); await interaction.reply({ ephemeral: true, content: "Skipped Song", }); break; case "loop": if (player.loop != "none") { player.setLoop("none"); await interaction.reply({ ephemeral: true, content: "Disabled Looping", }); } else { player.setLoop("track"); await interaction.reply({ ephemeral: true, content: "Started Looping", }); } break; case "shuffle": player.queue.shuffle(); await interaction.reply({ ephemeral: true, content: "Shuffled Queue", }); break; case "seek": // let position = player.position / 1000; // position = position | 0; // console.log(position); // player.seek(position + 5); await interaction.reply({ ephemeral: true, content: "Does not work :pensive:", // content: "Skipped 5s forward", }); break; case "previous": let track = player.getPrevious(); await interaction.reply({ ephemeral: true, content: "Queued previous song", }); player.play(track[0]); break; default: break; } } // interaction.id // console.log(interaction.client.application.get(interaction.commandName)) // switch (interaction.customID) { // case value: // break; // default: // break; // } }); client.login(process.env.TOKEN);