From 07f75bbd93d900e4708449d6f27e14551669a1a4 Mon Sep 17 00:00:00 2001 From: megabyte Date: Sat, 21 Jun 2025 15:13:58 -0500 Subject: [PATCH] first commit --- .eslintrc.json | 49 + .gitignore | 4 + commands/misc/pinguser.js | 92 + commands/moderation/ban.js | 92 + commands/moderation/kick.js | 75 + commands/music/bplay.js | 132 ++ commands/music/queue.js | 36 + commands/music/skip.js | 60 + commands/music/stop.js | 20 + commands/utility/auto-welcome.js | 46 + commands/utility/ping.js | 17 + commands/utility/setBluChannel.js | 33 + db.js | 37 + deploy-commands-private.js | 46 + deploy-commands-public.js | 46 + embeds/modLogs.js | 44 + embeds/nowPlaying.js | 19 + embeds/queue.js | 15 + embeds/userStreaming.js | 33 + embeds/welcomeMember.js | 32 + events/InteractionCreate/interactionCreate.js | 26 + events/guildBanAdd/chatlog.js | 33 + events/guildBanAdd/logtodb.js | 48 + events/guildBanRemove/chatlog.js | 33 + events/guildCreate/addGuildToDB.js | 21 + events/guildCreate/createChannel.js | 34 + events/guildMemberAdd/logToDB.js | 26 + events/guildMemberAdd/sendWelcome.js | 34 + events/messageCreate/messageCreate.js | 26 + events/presenceUpdate/checktwitchPres.js | 47 + events/ready/ready.js | 10 + events/ready/setActivity.js | 41 + events/voiceStateUpdate/checkUpdate.js | 0 index.js | 73 + package-lock.json | 1545 +++++++++++++++++ package.json | 29 + playback.js | 171 ++ 37 files changed, 3125 insertions(+) create mode 100644 .eslintrc.json create mode 100644 .gitignore create mode 100644 commands/misc/pinguser.js create mode 100644 commands/moderation/ban.js create mode 100644 commands/moderation/kick.js create mode 100644 commands/music/bplay.js create mode 100644 commands/music/queue.js create mode 100644 commands/music/skip.js create mode 100644 commands/music/stop.js create mode 100644 commands/utility/auto-welcome.js create mode 100644 commands/utility/ping.js create mode 100644 commands/utility/setBluChannel.js create mode 100644 db.js create mode 100644 deploy-commands-private.js create mode 100644 deploy-commands-public.js create mode 100644 embeds/modLogs.js create mode 100644 embeds/nowPlaying.js create mode 100644 embeds/queue.js create mode 100644 embeds/userStreaming.js create mode 100644 embeds/welcomeMember.js create mode 100644 events/InteractionCreate/interactionCreate.js create mode 100644 events/guildBanAdd/chatlog.js create mode 100644 events/guildBanAdd/logtodb.js create mode 100644 events/guildBanRemove/chatlog.js create mode 100644 events/guildCreate/addGuildToDB.js create mode 100644 events/guildCreate/createChannel.js create mode 100644 events/guildMemberAdd/logToDB.js create mode 100644 events/guildMemberAdd/sendWelcome.js create mode 100644 events/messageCreate/messageCreate.js create mode 100644 events/presenceUpdate/checktwitchPres.js create mode 100644 events/ready/ready.js create mode 100644 events/ready/setActivity.js create mode 100644 events/voiceStateUpdate/checkUpdate.js create mode 100644 index.js create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 playback.js diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..4cd9b60 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,49 @@ +{ + "extends": "eslint:recommended", + "env": { + "node": true, + "es6": true + }, + "parserOptions": { + "ecmaVersion": 2021 + }, + "rules": { + "arrow-spacing": ["warn", { "before": true, "after": true }], + "brace-style": ["error", "stroustrup", { "allowSingleLine": true }], + "comma-dangle": ["error", "always-multiline"], + "comma-spacing": "error", + "comma-style": "error", + "curly": ["error", "multi-line", "consistent"], + "dot-location": ["error", "property"], + "handle-callback-err": "off", + "indent": ["error", "tab"], + "keyword-spacing": "error", + "max-nested-callbacks": ["error", { "max": 4 }], + "max-statements-per-line": ["error", { "max": 2 }], + "no-console": "off", + "no-empty-function": "error", + "no-floating-decimal": "error", + "no-inline-comments": "error", + "no-lonely-if": "error", + "no-multi-spaces": "error", + "no-multiple-empty-lines": ["error", { "max": 2, "maxEOF": 1, "maxBOF": 0 }], + "no-shadow": ["error", { "allow": ["err", "resolve", "reject"] }], + "no-trailing-spaces": ["error"], + "no-var": "error", + "object-curly-spacing": ["error", "always"], + "prefer-const": "error", + "quotes": ["error", "single"], + "semi": ["error", "always"], + "space-before-blocks": "error", + "space-before-function-paren": ["error", { + "anonymous": "never", + "named": "never", + "asyncArrow": "always" + }], + "space-in-parens": "error", + "space-infix-ops": "error", + "space-unary-ops": "error", + "spaced-comment": "error", + "yoda": "error" + } +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..00ed5bf --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +node_modules +.env +config.json +dockerfile \ No newline at end of file diff --git a/commands/misc/pinguser.js b/commands/misc/pinguser.js new file mode 100644 index 0000000..758e51b --- /dev/null +++ b/commands/misc/pinguser.js @@ -0,0 +1,92 @@ +const { Client, Interaction, SlashCommandBuilder } = require("discord.js"); + +const cooldowns = new Map(); +var isDisabled = false; + +/** + * Handles the ping user command. + * @param {Interaction} interaction - The interaction object. + */ +async function handlePingCommand(interaction) { + const user = interaction.options.getUser("user"); // Get the user from the interaction + const times = interaction.options.getInteger("times") || 1; // Default to 1 if not provided + + try { + // Ensure the user exists in the guild + const userPing = await interaction.guild.members.fetch(user.id); + if (!userPing) { + await interaction.reply("That user doesn't exist in this server."); + return; + } + + // Prevent the bot from pinging itself + if (userPing.id === interaction.guild.members.me.id) { + await interaction.reply("I can't ping myself."); + return; + } + + // Cooldown logic + const coolDown = 24 * 60 * 60 * 1000; // 24 hours in milliseconds + const now = Date.now(); + const lastExec = cooldowns.get(interaction.user.id); + + if (lastExec && now - lastExec < coolDown) { + const timeLeft = coolDown - (now - lastExec); + + const days = Math.floor(timeLeft / (1000 * 60 * 60 * 24)); + const hours = Math.floor((timeLeft % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)); + const minutes = Math.floor((timeLeft % (1000 * 60 * 60)) / (1000 * 60)); + const seconds = Math.floor((timeLeft % (1000 * 60)) / 1000); + + await interaction.reply( + `You need to wait ${days} days, ${hours} hours, ${minutes} minutes, and ${seconds} seconds before using this command again.`, + ); + return; + } + + // Send the pings + await interaction.reply({ content: "Ok, sending pings.", ephemeral: false }); + + for (let i = 0; i < times; i++) { + await interaction.channel.send(`Pinging ${userPing}`); + await new Promise((resolve) => setTimeout(resolve, 300)); // Wait 300ms between pings + } + + // Update the cooldown + cooldowns.set(interaction.user.id, now); + } catch (error) { + console.error("There was an error in pinguser: ", error); + await interaction.reply({ + content: "An error occurred while executing the command.", + ephemeral: true, + }); + } +} + +module.exports = { + data: new SlashCommandBuilder() + .setName("pinguser") + .setDescription("Ping the mentioned user") + .addUserOption((option) => + option.setName("user").setDescription("The user to ping").setRequired(true), + ) + .addIntegerOption((option) => + option + .setName("times") + .setDescription("Number of times to ping the user") + .setRequired(false), + ), + async execute(interaction) { + try { + if (isDisabled) { + return interaction.reply({ + content: "The command is disabled... Try again later.", + ephemeral: true, + }); + } + await handlePingCommand(interaction); // Ensure we await this function + } catch (error) { + console.error("There was an error executing the command: ", error); + } + }, +}; diff --git a/commands/moderation/ban.js b/commands/moderation/ban.js new file mode 100644 index 0000000..94557cc --- /dev/null +++ b/commands/moderation/ban.js @@ -0,0 +1,92 @@ +const { + Client, + Interaction, + ApplicationCommandOptionType, + PermissionFlagsBits, + SlashCommandBuilder, +} = require("discord.js"); + +async function handleBanCommand(interaction) { + if (!interaction.member.permissions.has(PermissionFlagsBits.BanMembers)) { + await interaction.reply({ + content: "You do not have permission to ban members.", + ephemeral: true, + }); + return; + } + + const targetID = interaction.options.get("user").value; + const reason = + interaction.options.get("reason")?.value || "No reason provided"; + + await interaction.deferReply(); + const targetUser = await interaction.guild.members.fetch(targetID); + + if (!targetUser) { + await interaction.editReply("That user is not in the server"); + return; + } + + if (targetUser.id === interaction.guild.ownerId) { + await interaction.editReply("I can't ban the server owner"); + return; + } + + if (targetUser.id === interaction.guild.members.me) { + await interaction.editReply("I cant't ban myself"); + return; + } + + const targetUserRolePostion = targetUser.roles.highest.postion; // check the user highest role + const requestUserRolePostion = interaction.member.roles.highest.postion; // check the user issuing the command is higher than the target + const botRolePostion = interaction.guild.members.me.roles.highest.postion; // check the bot has permissions + + if (targetUserRolePostion >= requestUserRolePostion) { + await interaction.editReply( + "You can't ban someone that has the same/higher role than you." + ); + return; + } + + if (targetUserRolePostion >= botRolePostion) { + await interaction.editReply( + "I can't ban that user because they have the same/higher role than me." + ); + return; + } + + try { + await targetUser.ban({ reason }); + await interaction.editReply( + `User ${targetUser} was banned. Reason: ${reason}` + ); + } catch (error) { + console.error("There was an error banning the target: ", error); + } +} + +module.exports = { + data: new SlashCommandBuilder() + .setName("ban") + .setDescription("Bans the specified user") + .addUserOption((option) => + option + .setName("user") + .setDescription("Mention the user to ban") + .setRequired(true) + ) + .addStringOption((option) => + option + .setName("reason") + .setDescription("Specify the reason for banning") + .setRequired(false) + ) + .setDefaultMemberPermissions(PermissionFlagsBits.BanMembers), + async execute(interaction) { + try { + handleBanCommand(interaction); + } catch (error) { + console.error("There was an error in banning: ", error); + } + }, +}; diff --git a/commands/moderation/kick.js b/commands/moderation/kick.js new file mode 100644 index 0000000..90afebb --- /dev/null +++ b/commands/moderation/kick.js @@ -0,0 +1,75 @@ +const { + Client, + Interaction, + ApplicationCommandOptionType, + PermissionFlagsBits, + SlashCommandBuilder, +} = require("discord.js"); + +async function handleKickCommand(interaction) { + if (!interaction.member.permissions.has(PermissionFlagsBits.KickMembers)) { + await interaction.reply({ + content: "You do not have permission to kick members.", + ephemeral: true, + }); + return; + } + + const targetID = interaction.options.get('user').value; + const reason = interaction.options.get("reason")?.value || "No reason provided"; + + await interaction.deferReply(); + const targetUser = await interaction.guild.members.fetch(targetID) + + if (!targetUser) { + await interaction.editReply('That user does not exist in this server'); + return; + } + + if (targetUser.id === interaction.guild.ownerId) { + await interaction.editReply("I can't kick the server owner") + return; + } + + if (targetUser.id === interaction.guild.members.me) { + await interaction.editReply("I can't kick myself") + return; + } + + const targetUserRolePostion = targetUser.roles.highest.postion; + const requestUserRolePostion = interaction.member.roles.highest.postion + const botRolePostion = interaction.guild.members.me.roles.highest.postion + + if (targetUserRolePostion >= requestUserRolePostion) { + await interaction.editReply("You can't kick someone that has the same/higher role than you") + return; + } + + if (targetUserRolePostion >= botRolePostion) { + await interaction.editReply("I can't kick that user because they have the same/higher role than me.") + return; + } + + try { + await targetUser.kick({ reason }) + await interaction.editReply(`User ${targetUser} was kicked. Reason: ${reason}`) + } catch (error) { + console.error('There was a problem kicking: ', error) + } +} + +module.exports = { + data: new SlashCommandBuilder() + .setName("kick") + .setDescription("Kicks the specified user") + .addUserOption((option) => option.setName("user").setDescription("Mention the user to kick").setRequired(true)) + .addStringOption((option) => option.setName('reason').setDescription('Specify the reason for kicking').setRequired(false)) + .setDefaultMemberPermissions(PermissionFlagsBits.KickMembers), + async execute(interaction) { + try { + handleKickCommand(interaction) + } catch (error) { + console.error('There was an error in kicking:', error) + } + } +}; diff --git a/commands/music/bplay.js b/commands/music/bplay.js new file mode 100644 index 0000000..c689e0d --- /dev/null +++ b/commands/music/bplay.js @@ -0,0 +1,132 @@ +const { Client, Interaction, SlashCommandBuilder, VoiceChannel } = require('discord.js'); +const { playFromQueue, queue} = require('../../playback'); +const ytdl = require('ytdl-core'); +const { default: axios } = require('axios'); +const { setStopped, cancelTimer } = require('../../playback') +var isDisabled = false + +const API_KEY = process.env.YoutubeToken +/** + * + * @param {Client} client + * @param {Interaction} interaction + */ + + +async function addToQueue(interaction, link, songName) { + const voiceChannel = interaction.member.voice.channel; + const textChannel = interaction.channel; + const getMember = interaction.member.displayName; + + const wasQueueEmpty = queue.length === 0; + queue.push({interaction, getMember, link, songName}) + cancelTimer() + if (wasQueueEmpty) { + await playFromQueue(voiceChannel, textChannel, getMember, link, songName) + } else { + //console.log(queue) + interaction.reply(`Added to queue: ${songName}`) + } +} + +async function searchYoutubeSong(input) { + try { + const response = await axios.get('https://www.googleapis.com/youtube/v3/search', { + params: { + part: 'snippet', + q: input, + key: API_KEY, + maxResults: 1, + type: 'video', + } + }); + + if (response.data.items && response.data.items.length > 0) { + const video = response.data.items[0]; + const videoId = video.id.videoId; + const videoTitle = video.snippet.title; + const videoUrl = `https://www.youtube.com/watch?v=${videoId}`; + return { videoUrl, videoTitle} + } else { + return null; + } + } catch (error) { + console.error('Error searching Youtube: ', error) + return null; + } +} + + +async function handlePlayCommand(interaction, input) { + const voiceChannel = interaction.member.voice.channel; + + if (!voiceChannel) { + return interaction.reply({ + content: 'You need to be in a voice channel to use this command.', + ephemeral: true + }); + } + + try { + let link = ''; + let songName = input; + + if (ytdl.validateURL(input)) { + // If it's a valid YouTube URL, extract the song information + const videoInfo = await ytdl.getBasicInfo(input); + songName = videoInfo.videoDetails.title; + link = input; + } else { + // If it's a search query, find the video + const searchResult = await searchYoutubeSong(input); + if (searchResult) { + link = searchResult.videoUrl; + songName = searchResult.videoTitle; + } else { + return interaction.reply({ + content: 'No results found for your query.', + ephemeral: true, + }); + } + } + + // Add the song to the queue and play if needed + await addToQueue(interaction, link, songName); + + } catch (error) { + console.error('Error handling play command:', error); + await interaction.reply({ + content: 'An error occurred while processing your request.', + ephemeral: true, + }); + } +} + + +module.exports = { + data: new SlashCommandBuilder() + .setName('bplay') + .setDescription('Plays a song') + .addStringOption(option => + option.setName('query') + .setDescription('The song name or URL to play') + .setRequired(true) + ), + + async execute(interaction) { + try { + if (isDisabled) return interaction.reply({ content: 'The command is Disabled... Try again later', ephemeral: true}) + const query = interaction.options.getString('query'); + setStopped(false) + await handlePlayCommand(interaction, query); + } catch (error) { + console.error('Error executing play command:', error); + await interaction.reply({ + content: 'An error occurred while processing your request.', + ephemeral: true + }); + } + } +}; + + diff --git a/commands/music/queue.js b/commands/music/queue.js new file mode 100644 index 0000000..e6d989d --- /dev/null +++ b/commands/music/queue.js @@ -0,0 +1,36 @@ +const { Client, Interaction, SlashCommandBuilder, EmbedBuilder } = require('discord.js') +const { queue } = require('./../../playback') +const { musicQueueEmbed } = require('../../embeds/queue'); + +async function checkQueue(interaction) { + if (!queue || queue.length === 0 ) { + interaction.reply('The queue is empty.') + return + } + + try { + const embed = musicQueueEmbed(queue); + await interaction.reply({ embeds: [embed] }) + } catch (error) { + console.log('There is an error in the queue command: ', error) + } +} + +module.exports = { + data: new SlashCommandBuilder() + .setName('queue') + .setDescription('Check the playback queue'), + async execute(interaction) { + const voiceChannel = interaction.member.voice.channel + const textChannel = interaction.textChannel + + if (!voiceChannel) { + return interaction.reply({ + content: 'You need to be in a voice channel to use that queue', + ephemeral: true + }) + } + + await checkQueue(interaction, voiceChannel, textChannel) + } +} \ No newline at end of file diff --git a/commands/music/skip.js b/commands/music/skip.js new file mode 100644 index 0000000..ed006e2 --- /dev/null +++ b/commands/music/skip.js @@ -0,0 +1,60 @@ +const { Client, Interaction, SlashCommandBuilder } = require('discord.js') + +const { playFromQueue, queue } = require('../../playback'); +const { getVoiceConnection } = require('@discordjs/voice') + +async function skipSong(voiceChannel, textChannel) { + const connection = getVoiceConnection(voiceChannel.guild.id) + + if (!connection) { + console.log('No active voice connections') + textChannel.send('No active voice connections') + return + } + + if (!queue || queue.length === 0) { + console.log('Queue is empty. No songs to skip') + connection.state.subscription.player.stop(); + return; + } + + try { + queue.shift() + + if (queue.length > 0 ) { + const nextSong = queue[0]; + await playFromQueue(voiceChannel, textChannel, nextSong.getMember, nextSong.link, nextSong.songName) + } else { + textChannel.send('No more songs in the queue') + } + } catch (error) { + console.error('Error skipping song: ', error) + } +} + +module.exports = { + data: new SlashCommandBuilder() + .setName('skip') + .setDescription('Skips a song in the queue'), + async execute(interaction) { + const voiceChannel = interaction.member.voice.channel + const textChannel = interaction.channel + if (!voiceChannel) { + return interaction.reply({ + content: 'You need to be in a voice channel to use the command', + ephemeral: true + }); + } + + try { + await skipSong(voiceChannel, textChannel) + interaction.reply('Skipped the current song') + } catch (error) { + console.error('Error in skip command: ', error) + interaction.reply({ + content: 'An error has occurred while processing the command', + ephemeral: true + }); + } + } +} \ No newline at end of file diff --git a/commands/music/stop.js b/commands/music/stop.js new file mode 100644 index 0000000..7d7d446 --- /dev/null +++ b/commands/music/stop.js @@ -0,0 +1,20 @@ +const { SlashCommandBuilder } = require("discord.js"); +const { resetAll, setStopped } = require("../../playback"); + +module.exports = { + data: new SlashCommandBuilder() + .setName("stop") + .setDescription("Stop playback and leave the voice channel"), + + async execute(interaction) { + const voiceChannel = interaction.member.voice.channel; + if (!voiceChannel) { + return interaction.reply({ content: "You need to be in a voice channel to use this command.", ephemeral: true }); + } + + setStopped(true); // Mark playback as stopped + resetAll(voiceChannel.guild.id); // Reset everything for a clean state + + return interaction.reply("Stopped playback and left the voice channel."); + }, +}; diff --git a/commands/utility/auto-welcome.js b/commands/utility/auto-welcome.js new file mode 100644 index 0000000..816bcf2 --- /dev/null +++ b/commands/utility/auto-welcome.js @@ -0,0 +1,46 @@ +const { + Client, + Interaction, + ApplicationCommandOptionType, + PermissionFlagsBits, + SlashCommandBuilder, + } = require("discord.js"); +const db = require("../../db"); + +module.exports = { + data: new SlashCommandBuilder() + .setName("autowelcome") + .setDescription("Enable or disable auto-welcome messages.") + .addChannelOption((option) => + option + .setName("channel") + .setDescription( + "Channel Id to send welcome messages (required when enabling)" + ) + .setRequired(false) + ) + .setDefaultMemberPermissions(PermissionFlagsBits.Administrator), + + async execute(interaction) { + const channelId = interaction.options.getChannel("channel"); + const guildId = interaction.guild.id; + + if (channelId) { + await db.query( + `INSERT INTO auto_welcome (guild_id, channel_id, enabled) + VALUES ($1, $2, $3) + ON CONFLICT (guild_id) DO UPDATE SET channel_id = $2, enabled = $3`, + [guildId, channelId.id, true] + ); + return interaction.reply( + `Auto-welcome enabled! Messages will be sent to <#${channelId.id}>.` + ); + } else { + await db.query( + `UPDATE auto_welcome SET enabled = $1 WHERE guild_id = $2`, + [false, guildId] + ); + return interaction.reply("Auto-welcome has been disabled."); + } + }, +}; diff --git a/commands/utility/ping.js b/commands/utility/ping.js new file mode 100644 index 0000000..3f573da --- /dev/null +++ b/commands/utility/ping.js @@ -0,0 +1,17 @@ +const { SlashCommandBuilder } = require("discord.js"); + +module.exports = { + data: new SlashCommandBuilder() + .setName("ping") + .setDescription("Replies with Pong! and websocket ping"), + async execute(interaction) { + // Send the initial reply and wait for it to be sent + const reply = await interaction.reply({ + content: "Pong!", + fetchReply: true // This ensures we can get the reply object + }); + + const ping = reply.createdTimestamp - interaction.createdTimestamp; // Calculate the ping + await interaction.editReply(`Pong! client ${ping}ms | websocket: ${interaction.client.ws.ping}ms`); + }, +}; diff --git a/commands/utility/setBluChannel.js b/commands/utility/setBluChannel.js new file mode 100644 index 0000000..51f44c0 --- /dev/null +++ b/commands/utility/setBluChannel.js @@ -0,0 +1,33 @@ +const { Client, Interaction, ApplicationCommandOptionType, PermissionFlagsBits, SlashCommandBuilder } = require("discord.js"); +const db = require("../../db"); + +module.exports = { + data: new SlashCommandBuilder() + .setName("setbluchannel") + .setDescription("Set the channel for Blu to send messages.") + .addChannelOption((option) => + option + .setName("channel") + .setDescription("Channel to set for Blu messages") + .setRequired(true) + ) + .setDefaultMemberPermissions(PermissionFlagsBits.Administrator), + async execute(interaction) { + const channelId = interaction.options.getChannel("channel"); + const guildId = interaction.guild.id; + const guildName = interaction.guild.name; + const channelName = channelId.name; + + if (channelId) { + await db.query( + `INSERT INTO bot_channel (guild_id, channel_id, guild_name, channel_name) + VALUES ($1, $2, $3, $4) + ON CONFLICT (guild_id) DO UPDATE SET Channel_id = $2, guild_name = $3, channel_name = $4`, + [guildId, channelId.id, guildName, channelName] + ); + return interaction.reply(`Blu messages will be sent to <#${channelId.id}>.`); + } else { + return interaction.reply("Please provide a valid channel."); + } + }, +} \ No newline at end of file diff --git a/db.js b/db.js new file mode 100644 index 0000000..b2da454 --- /dev/null +++ b/db.js @@ -0,0 +1,37 @@ +const { Pool } = require('pg'); +require('dotenv').config(); + +const pool = new Pool({ + host: process.env.PGHOST, + user: process.env.PGUSER, + database: process.env.PGDATABASE, + password: process.env.PGPASSWORD, + port: process.env.PGPORT, +}); + +async function connectDB() { + try { + await pool.connect(); + console.log('Database connected.') + } catch (error) { + console.error('Failed to connect to the database: ',error) + process.exit(1) + } +} + +function closeDB() { + try { + pool.end(); + console.log('Database connection closed.') + } catch (error) { + console.error('Error closing the database: ', error ) + } +} + + + +module.exports = { + query: (text, params) => pool.query(text, params), + closeDB, + connectDB +}; diff --git a/deploy-commands-private.js b/deploy-commands-private.js new file mode 100644 index 0000000..e66b20b --- /dev/null +++ b/deploy-commands-private.js @@ -0,0 +1,46 @@ +const { REST, Routes } = require('discord.js'); +require('dotenv').config(); +const fs = require('node:fs'); +const path = require('node:path'); + +const commands = []; +// Grab all the command folders from the commands directory you created earlier +const foldersPath = path.join(__dirname, 'commands'); +const commandFolders = fs.readdirSync(foldersPath); + +for (const folder of commandFolders) { + // Grab all the command files from the commands directory you created earlier + const commandsPath = path.join(foldersPath, folder); + const commandFiles = fs.readdirSync(commandsPath).filter(file => file.endsWith('.js')); + // Grab the SlashCommandBuilder#toJSON() output of each command's data for deployment + for (const file of commandFiles) { + const filePath = path.join(commandsPath, file); + const command = require(filePath); + if ('data' in command && 'execute' in command) { + commands.push(command.data.toJSON()); + } else { + console.log(`[WARNING] The command at ${filePath} is missing a required "data" or "execute" property.`); + } + } +} + +// Construct and prepare an instance of the REST module +const rest = new REST().setToken(process.env.TOKEN); + +// and deploy your commands! +(async () => { + try { + console.log(`Started refreshing ${commands.length} application (/) commands.`); + + // The put method is used to fully refresh all commands in the guild with the current set + const data = await rest.put( + Routes.applicationGuildCommands(process.env.ClientID, process.env.GuildID), + { body: commands }, + ); + + console.log(`Successfully reloaded ${data.length} application (/) commands.`); + } catch (error) { + // And of course, make sure you catch and log any errors! + console.error(error); + } +})(); diff --git a/deploy-commands-public.js b/deploy-commands-public.js new file mode 100644 index 0000000..576ad47 --- /dev/null +++ b/deploy-commands-public.js @@ -0,0 +1,46 @@ +const { REST, Routes } = require('discord.js'); +require('dotenv').config(); +const fs = require('node:fs'); +const path = require('node:path'); + +const commands = []; +// Grab all the command folders from the commands directory you created earlier +const foldersPath = path.join(__dirname, 'commands'); +const commandFolders = fs.readdirSync(foldersPath); + +for (const folder of commandFolders) { + // Grab all the command files from the commands directory you created earlier + const commandsPath = path.join(foldersPath, folder); + const commandFiles = fs.readdirSync(commandsPath).filter(file => file.endsWith('.js')); + // Grab the SlashCommandBuilder#toJSON() output of each command's data for deployment + for (const file of commandFiles) { + const filePath = path.join(commandsPath, file); + const command = require(filePath); + if ('data' in command && 'execute' in command) { + commands.push(command.data.toJSON()); + } else { + console.log(`[WARNING] The command at ${filePath} is missing a required "data" or "execute" property.`); + } + } +} + +// Construct and prepare an instance of the REST module +const rest = new REST().setToken(process.env.TOKEN); + +// and deploy your commands! +(async () => { + try { + console.log(`Started refreshing ${commands.length} application (/) commands.`); + + // The put method is used to fully refresh all commands in the guild with the current set + const data = await rest.put( + Routes.applicationCommands(process.env.ClientID), + { body: commands }, + ); + + console.log(`Successfully reloaded ${data.length} application (/) commands.`); + } catch (error) { + // And of course, make sure you catch and log any errors! + console.error(error); + } +})(); diff --git a/embeds/modLogs.js b/embeds/modLogs.js new file mode 100644 index 0000000..694f565 --- /dev/null +++ b/embeds/modLogs.js @@ -0,0 +1,44 @@ +const { EmbedBuilder } = require('discord.js') + +function modLogEmbed(action, member, reason ) { + let title, description, color; + + //console.log(reason); + switch (action) { + case "ban": + title = ":hammer: User Banned"; + description = `**User:** **${member.user.username}** has been banned.`; + reason2 = `**Reason:** ${reason}`; + color = "#FF0000"; + break; + case "unban": + title = ":tada::party_popper: User Unbanned"; + description = `**User:** **${member.user.username}** has been unbanned.`; + reason2 = `**Reason:** ${reason}`; + color = "#00FF00"; + break; + case "mute": + return muteEmbed(member); + case "unmute": + return unmuteEmbed(member); + case "warn": + return warnEmbed(member); + default: + return null; + } + + + + + return new EmbedBuilder() + .setTitle(title) + .setDescription(`${description}\n${reason2}`) + .setColor(color) + .setTimestamp() + .setFooter({ text: `User ID: ${member.user.id}` }) + .setThumbnail(member.user.displayAvatarURL({ dynamic: true })); + +} + + +module.exports = { modLogEmbed }; \ No newline at end of file diff --git a/embeds/nowPlaying.js b/embeds/nowPlaying.js new file mode 100644 index 0000000..2037830 --- /dev/null +++ b/embeds/nowPlaying.js @@ -0,0 +1,19 @@ +const { EmbedBuilder } = require('discord.js') + +function nowPlayingEmbed(queue) { + if (!queue || queue.length === 0) { + return new EmbedBuilder() + .setTitle('**🎵 Now Playing:**') + .setDescription('No song is currently playing.') + .setColor('Grey'); + } + + const currentSong = queue[0] + + return new EmbedBuilder() + .setTitle('**🎵 Now Playing:**') + .setDescription(`${currentSong.songName}\n**Requested By:** ${currentSong.getMember || 'Unknown User'}`) + .setColor('Green') +} + +module.exports = { nowPlayingEmbed } \ No newline at end of file diff --git a/embeds/queue.js b/embeds/queue.js new file mode 100644 index 0000000..9a84832 --- /dev/null +++ b/embeds/queue.js @@ -0,0 +1,15 @@ +const { EmbedBuilder } = require('discord.js') + + +function musicQueueEmbed(queue) { + const songLines = queue.map((item, index) => + index === 0 ? `**🎵 Now Playing:** ${item.songName} **Requested By:** ${item.getMember}\n` : `${index + 1}. ${item.songName} - **Requested By:** ${item.getMember}\n` + ); + + return new EmbedBuilder() + .setTitle('Current Queue') + .setDescription(songLines.join('\n') || 'No Songs in the queue') + .setColor('Blue') +} + +module.exports = { musicQueueEmbed } \ No newline at end of file diff --git a/embeds/userStreaming.js b/embeds/userStreaming.js new file mode 100644 index 0000000..290b906 --- /dev/null +++ b/embeds/userStreaming.js @@ -0,0 +1,33 @@ +const { EmbedBuilder, ButtonBuilder, ActionRowBuilder } = require('discord.js'); + +function userStreamingEmbed(user, status) { + + const username = user?.username || 'Unknown User'; + const avatarURL = user?.displayAvatarURL({ dynamic: true }) || null; + const streamTitle = status?.details || 'No title available'; + const gameName = status?.name || 'No game available'; + const streamURL = status?.url || 'No URL available'; + + + const embed = new EmbedBuilder() + .setTitle('📡 Live Stream Alert!') + .setDescription(`**${username}** Just went Live on Twitch!`) + .addFields( { name: 'Stream Title', value: streamTitle, inline: false }, { name: 'Game / Category', value: gameName, inline: true } ) + .setColor('Green') + .setTimestamp() + .setFooter({ text: 'User Streaming Status' }) + .setThumbnail(avatarURL); + + const streamButton = new ButtonBuilder() + .setLabel('🎥 Watch Stream') + .setURL(streamURL) + .setStyle(5); + + const row = new ActionRowBuilder() + .addComponents(streamButton); + return { embeds: [embed], components: [row] }; +} + +module.exports = { + userStreamingEmbed +}; \ No newline at end of file diff --git a/embeds/welcomeMember.js b/embeds/welcomeMember.js new file mode 100644 index 0000000..80cd018 --- /dev/null +++ b/embeds/welcomeMember.js @@ -0,0 +1,32 @@ +const { GuildMember, EmbedBuilder } = require("discord.js"); + +/** + * @param {Client} client + * @param {GuildMember} member + */ + +function welcomeEmbed(member) { + let welcomeMessages = [ + `Welcome to the server ${member.user.username}. We hope you enjoy your stay.`, + `We hope you brought pizza ${member.user.username}.`, + `Yippe.... Welcome to the server ${member.user.username}.`, + `A wild ${member.user.username} has appeared.`, + ]; + const randomMessage = + welcomeMessages[Math.floor(Math.random() * welcomeMessages.length)]; + + return new EmbedBuilder() + .setTitle("**New Member**") + .setDescription(randomMessage) + .addFields( + { name: "Joined at", value: `${member.joinedAt}`, inline: true }, + { + name: "User Created at", + value: `${member.user.createdAt}`, + inline: true, + } + ) + .setThumbnail(member.user.avatarURL()); +} + +module.exports = { welcomeEmbed }; diff --git a/events/InteractionCreate/interactionCreate.js b/events/InteractionCreate/interactionCreate.js new file mode 100644 index 0000000..a4c3122 --- /dev/null +++ b/events/InteractionCreate/interactionCreate.js @@ -0,0 +1,26 @@ +const { Events } = require('discord.js'); + +module.exports = { + name: Events.InteractionCreate, + async execute(interaction) { + if (!interaction.isChatInputCommand()) return; + + const command = interaction.client.commands.get(interaction.commandName); + + if (!command) { + console.error(`No command matching ${interaction.commandName} was found.`); + return; + } + + try { + await command.execute(interaction); + } catch (error) { + console.error(error); + if (interaction.replied || interaction.deferred) { + await interaction.followUp({ content: 'There was an error while executing this command!', ephemeral: true }); + } else { + await interaction.reply({ content: 'There was an error while executing this command!', ephemeral: true }); + } + } + }, +}; diff --git a/events/guildBanAdd/chatlog.js b/events/guildBanAdd/chatlog.js new file mode 100644 index 0000000..5b6fc71 --- /dev/null +++ b/events/guildBanAdd/chatlog.js @@ -0,0 +1,33 @@ +const { Events, GuildMember } = require("discord.js"); +const { modLogEmbed } = require("../../embeds/modLogs"); + + + +module.exports = { + name: Events.GuildBanAdd, + async execute(member) { + try { + const auditLogs = await member.guild.fetchAuditLogs({ type: 22 }); + const banEntry = auditLogs.entries.first(); + const modLogsChannel = member.guild.channels.cache.find(channel => channel.name === 'mod-logs'); + + if (!banEntry) return; + + const { target, executor, reason } = banEntry; + + if (!modLogsChannel) return; + + // Call modLogEmbed and send the embed + const embed = modLogEmbed("ban", member, reason || "No reason provided"); + + if (embed) { + await modLogsChannel.send({ embeds: [embed] }); + //console.log(`Ban logged: ${target.tag} banned by ${executor.tag}`); + } + } catch (error) { + console.error(error); + } + + + }, + }; \ No newline at end of file diff --git a/events/guildBanAdd/logtodb.js b/events/guildBanAdd/logtodb.js new file mode 100644 index 0000000..fe85082 --- /dev/null +++ b/events/guildBanAdd/logtodb.js @@ -0,0 +1,48 @@ +const { Events, GuildMember } = require("discord.js"); +const db = require("../../db"); + +/** + * @param {Client} client + * @param {GuildMember} member + */ + +module.exports = { + name: Events.GuildBanAdd, + async execute(member) { + const auditLogs = await member.guild.fetchAuditLogs({ type: 22 }); + const banEntry = auditLogs.entries.first(); + + try { + if (banEntry) { + const { reason, executor, target, createdAt } = banEntry; + + const guildID = member.guild.id; + const guildName = member.guild.name; + const userID = target.id; + const userName = target.username; + const userTag = target.tag; + const avatarURL = target.displayAvatarURL({ dynamic: true }); + const banDate = createdAt.toISOString(); + const banReason = reason || "No reason provided"; + const banExecutor = executor.tag; + + await db.query( + "INSERT INTO bans (guild_ID, guild_Name, user_ID, username, user_Tag, avatar_URL, ban_Date, ban_Reason, ban_Executor) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)", + [ + guildID, + guildName, + userID, + userName, + userTag, + avatarURL, + banDate, + banReason, + banExecutor, + ] + ); + } + } catch (error) { + console.error("There was an error", error); + } + }, +}; diff --git a/events/guildBanRemove/chatlog.js b/events/guildBanRemove/chatlog.js new file mode 100644 index 0000000..5c37d95 --- /dev/null +++ b/events/guildBanRemove/chatlog.js @@ -0,0 +1,33 @@ +const { Events, GuildMember } = require("discord.js"); +const { modLogEmbed } = require("../../embeds/modLogs"); + + + +module.exports = { + name: Events.GuildBanRemove, + async execute(member) { + try { + const auditLogs = await member.guild.fetchAuditLogs({ type: 23 }); + const banEntry = auditLogs.entries.first(); + const modLogsChannel = member.guild.channels.cache.find(channel => channel.name === 'mod-logs'); + + if (!banEntry) return; + + const { target, executor, reason } = banEntry; + + if (!modLogsChannel) return; + + // Call modLogEmbed and send the embed + const embed = modLogEmbed("unban", member, reason || "No reason provided"); + + if (embed) { + await modLogsChannel.send({ embeds: [embed] }); + //console.log(`Ban logged: ${target.tag} banned by ${executor.tag}`); + } + } catch (error) { + console.error(error); + } + + + }, + }; \ No newline at end of file diff --git a/events/guildCreate/addGuildToDB.js b/events/guildCreate/addGuildToDB.js new file mode 100644 index 0000000..3ee5202 --- /dev/null +++ b/events/guildCreate/addGuildToDB.js @@ -0,0 +1,21 @@ +const { Guild, Events } = require('discord.js'); + +const db = require('../../db'); + +module.exports = { + name: Events.GuildCreate, + async execute(guild) { + try { + await db.query('CREATE TABLE IF NOT EXISTS guilds (guild_id VARCHAR PRIMARY KEY, guild_name VARCHAR, created_at TIMESTAMP, guild_owner_id VARCHAR)'); + const result = await db.query('SELECT * FROM guilds WHERE guild_id = $1', [guild.id]); + if (result.rows.length === 0) { + await db.query('INSERT INTO guilds (guild_id, guild_name, created_at, guild_owner_id) VALUES ($1, $2, $3, $4)', [guild.id, guild.name, guild.createdAt, guild.ownerId]); + console.log(`Added new guild to the database: ${guild.name}`); + } else { + console.log(`Guild already exists in the database: ${guild.name}`); + } + } catch (error) { + console.error('Error adding guild to the database:', error); + } + }, +}; \ No newline at end of file diff --git a/events/guildCreate/createChannel.js b/events/guildCreate/createChannel.js new file mode 100644 index 0000000..92ef6bd --- /dev/null +++ b/events/guildCreate/createChannel.js @@ -0,0 +1,34 @@ +const { Guild, Events, PermissionFlagsBits } = require('discord.js'); + +const db = require('../../db'); + +module.exports = { + name: Events.GuildCreate, + async execute(guild) { + try { + await db.query('CREATE TABLE IF NOT EXISTS bot_channel (guild_id VARCHAR PRIMARY KEY, guild_name VARCHAR, channel_id VARCHAR, channel_name VARCHAR)'); + const result = await db.query('SELECT * FROM bot_channel WHERE guild_id = $1', [guild.id]); + if (result.rows.length === 0) { + // Create Channel then add to database + const channel = await guild.channels.create({ + name: 'BluBot', + type: 0, + permissionOverwrites: [ + { + id: guild.id, + allow: [PermissionFlagsBits.ViewChannel], + }, + ], + }); + await db.query('INSERT INTO bot_channel (guild_id, guild_name, channel_id, channel_name) VALUES ($1, $2, $3, $4)', [guild.id, guild.name, channel.id, channel.name]); + //await db.query('INSERT INTO bot_channel (guild_id, guild_name, channel_id, channel_name) VALUES ($1, $2, $3, $4)', [guild.id, guild.name, guild.channels.id, guild.channels.name]); + console.log(`Added new guild to the database: ${guild.name}`); + } else { + console.log(`Guild already exists in the database: ${guild.name}`); + } + } + catch (error) { + console.error('Error creating channel or adding to database:', error); + } + } +}; diff --git a/events/guildMemberAdd/logToDB.js b/events/guildMemberAdd/logToDB.js new file mode 100644 index 0000000..a94d1f9 --- /dev/null +++ b/events/guildMemberAdd/logToDB.js @@ -0,0 +1,26 @@ +const { Events, GuildMember } = require("discord.js"); +const db = require("../../db"); + +/** + * @param {Client} client + * @param {GuildMember} member + */ + +module.exports = { + name: Events.GuildMemberAdd, + async execute(member) { + try { + const userID = member.id; + const userName = member.user.username; + const userTag = member.user.tag; + const avatarURL = member.displayAvatarURL({ dynamic: true }); + + await db.query( + "INSERT INTO members (user_ID, username, user_Tag, avatar_URL) VALUES ($1, $2, $3, $4) ON CONFLICT (user_ID) DO NOTHING", + [userID, userName, userTag, avatarURL] + ); + } catch (error) { + console.error("There was an error", error); + } + }, +}; diff --git a/events/guildMemberAdd/sendWelcome.js b/events/guildMemberAdd/sendWelcome.js new file mode 100644 index 0000000..1358e0b --- /dev/null +++ b/events/guildMemberAdd/sendWelcome.js @@ -0,0 +1,34 @@ +const { GuildMember, Events } = require('discord.js') +const db = require('../../db') +const { welcomeEmbed } = require('../../embeds/welcomeMember') + +/** +* @param {Client} client +* @param {GuildMember} member +*/ + +module.exports = { + name: Events.GuildMemberAdd, + async execute(member) { + + try { + const embed = welcomeEmbed(member) + const result = await db.query(`SELECT channel_id, enabled FROM auto_welcome WHERE guild_id = $1`, + [member.guild.id]) + + const welcomeData = result.rows[0] + + if (welcomeData && welcomeData.enabled) { + const welcomeChannel = member.guild.channels.cache.get(welcomeData.channel_id) + + if (welcomeChannel) { + welcomeChannel.send({ embeds: [embed]}) + } else { + console.error('Invalid channel id') + } + } + } catch (error) { + console.error('There was an error in sendWelcome:', error) + } + } +} \ No newline at end of file diff --git a/events/messageCreate/messageCreate.js b/events/messageCreate/messageCreate.js new file mode 100644 index 0000000..afbf4f1 --- /dev/null +++ b/events/messageCreate/messageCreate.js @@ -0,0 +1,26 @@ +const { Events, Message } = require('discord.js'); +const db = require('../../db') + + /** + * @param { Message } message + */ + +module.exports = { + name: Events.MessageCreate, + async execute(message) { + const messageContent = message.content + const author = message.author.tag + const guildID = message.guild.id + if (!message.inGuild() || message.author.bot) return; + + try { + await db.query("INSERT INTO messages (message, author, guild_ID) VALUES ($1, $2, $3)", [ + messageContent, + author, + guildID, + ]); + } catch (error) { + console.error("Error saving message: ", error) + } + } +} \ No newline at end of file diff --git a/events/presenceUpdate/checktwitchPres.js b/events/presenceUpdate/checktwitchPres.js new file mode 100644 index 0000000..bbfe50f --- /dev/null +++ b/events/presenceUpdate/checktwitchPres.js @@ -0,0 +1,47 @@ +const { Guild, GuildMember, Events } = require('discord.js') +const db = require('../../db') + +const { userStreamingEmbed } = require('../../embeds/userStreaming') + + +module.exports = { + name: Events.PresenceUpdate, + async execute(oldPresence, newPresence) { + + //console.log('Presence Update:', newPresence); + + + + // Check if the user is streaming + if (!newPresence.activities) return; + const streamingActivity = newPresence.activities.find(activity => activity.type === 1); + if (!streamingActivity) return; + + const user = newPresence.user; + + if (streamingActivity) + { + try { + for (const [guildId, guild] of newPresence.client.guilds.cache) { + const member = await guild.members.fetch(user.id).catch(() => null); + if (!member) continue; + const result = await db.query("SELECT channel_id FROM bot_channel WHERE guild_id = $1", [guildId]); + if (result.rows.length === 0) continue; + const channelId = result.rows[0].channel_id; + const channel = guild.channels.cache.get(channelId); + + if (channel && channel.isTextBased()) { + const { embeds, components } = userStreamingEmbed(user, streamingActivity); + await channel.send({ + embeds, + components + }); + } + } + } catch (error) { + console.error('Error fetching user:', error); + return; + } + } + }, +}; \ No newline at end of file diff --git a/events/ready/ready.js b/events/ready/ready.js new file mode 100644 index 0000000..f6c3b0b --- /dev/null +++ b/events/ready/ready.js @@ -0,0 +1,10 @@ +const { Events, PresenceUpdateStatus } = require('discord.js') + +module.exports = { + name: Events.ClientReady, + once: true, + execute(client) { + console.log(`Ready! Logged in as ${client.user.tag}`); + client.user.setStatus(PresenceUpdateStatus.Online) + }, +}; \ No newline at end of file diff --git a/events/ready/setActivity.js b/events/ready/setActivity.js new file mode 100644 index 0000000..11bbee9 --- /dev/null +++ b/events/ready/setActivity.js @@ -0,0 +1,41 @@ +const { Client, Guild, Events, ActivityType, PresenceUpdateStatus } = require('discord.js'); + +/** + * + * @param {Client} client + * @param {Guild} guild + */ + +module.exports = { + name: Events.ClientReady, + async execute(client) { + const guildsCount = client.guilds.cache.size; + + let status = [ + { + name: "I am Live", + type: ActivityType.Watching, + }, + { + name: "Choo Choo 🚂", + }, + { + name: 'Hippity Hoppity', + }, + { + name: `I am in ${guildsCount} Server(s)`, + }, + { + name: 'Yippe 🐻' + }, + ]; + + + + + setInterval(() => { + let random = Math.floor(Math.random() * status.length); + client.user.setActivity(status[random]); + }, 30000); + } +}; \ No newline at end of file diff --git a/events/voiceStateUpdate/checkUpdate.js b/events/voiceStateUpdate/checkUpdate.js new file mode 100644 index 0000000..e69de29 diff --git a/index.js b/index.js new file mode 100644 index 0000000..5356973 --- /dev/null +++ b/index.js @@ -0,0 +1,73 @@ +const fs = require('node:fs'); +const path = require('node:path') +const {Client, Events, GatewayIntentBits, Collection, InteractionResponse } = require('discord.js'); +require('dotenv').config(); +const { connectDB, closeDB } = require('./db') + +const client = new Client({ intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMembers, GatewayIntentBits.GuildMessages, GatewayIntentBits.MessageContent, GatewayIntentBits.GuildModeration, GatewayIntentBits.GuildVoiceStates, GatewayIntentBits.GuildPresences ] }); + +client.commands= new Collection(); + +const foldersPath = path.join(__dirname, 'commands'); +const commandFolders = fs.readdirSync(foldersPath); + +for (const folder of commandFolders) { + const commandsPath = path.join(foldersPath, folder); + const commandFiles = fs.readdirSync(commandsPath).filter(file => file.endsWith('.js')); + for (const file of commandFiles) { + const filePath = path.join(commandsPath, file); + const command = require(filePath); + // Set a new item in the Collection with the key as the command name and the value as the exported module + if ('data' in command && 'execute' in command) { + client.commands.set(command.data.name, command); + } else { + console.log(`[WARNING] The command at ${filePath} is missing a required "data" or "execute" property.`); + } + } +} + +function loadEvents(dir, client) { + const files = fs.readdirSync(dir, { withFileTypes: true }); + + for (const file of files) { + const fullPath = path.join(dir, file.name); + + if (file.isDirectory()) { + // Recursively load events from subdirectories + loadEvents(fullPath, client); + } else if (file.isFile() && file.name.endsWith('.js')) { + // Load the event file + const event = require(fullPath); + + if (event.once) { + client.once(event.name, (...args) => event.execute(...args)); + } else { + client.on(event.name, (...args) => event.execute(...args)); + } + + //console.log(`Loaded event: ${event.name}`); + } + } +} + +// Call the function to load all events +const eventsPath = path.join(__dirname, 'events'); +loadEvents(eventsPath, client); + + +connectDB().then(() => { + client.login(process.env.TOKEN).catch(console.error) +}); + + +process.on('SIGINT', async () => { + console.log('Bot is shutting down...') + closeDB(); + process.exit(0) +}); + +process.on('SIGTERM', async () => { + console.log('Bot received termination signal...'); + closeDB(); + process.exit(0) +}) diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..b48f6f1 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1545 @@ +{ + "name": "blubot", + "version": "1.0.9", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "blubot", + "version": "1.0.9", + "license": "ISC", + "dependencies": { + "@discordjs/voice": "^0.17.0", + "@distube/ytdl-core": "^4.14.4", + "axios": "^1.7.7", + "discord-api-types": "^0.37.119", + "discord.js": "^14.18.0", + "dotenv": "^16.4.5", + "ffmpeg": "^0.0.4", + "ffmpeg-static": "^5.2.0", + "libsodium-wrappers": "^0.7.15", + "nodemon": "^3.1.9", + "opusscript": "^0.0.8", + "pg": "^8.13.0", + "play-dl": "^1.9.7", + "undici": "^7.7.0", + "ytdl-core": "^4.11.5" + } + }, + "node_modules/@derhuerst/http-basic": { + "version": "8.2.4", + "resolved": "https://registry.npmjs.org/@derhuerst/http-basic/-/http-basic-8.2.4.tgz", + "integrity": "sha512-F9rL9k9Xjf5blCz8HsJRO4diy111cayL2vkY2XE4r4t3n0yPXVYy3KD3nJ1qbrSn9743UWSXH4IwuCa/HWlGFw==", + "license": "MIT", + "dependencies": { + "caseless": "^0.12.0", + "concat-stream": "^2.0.0", + "http-response-object": "^3.0.1", + "parse-cache-control": "^1.0.1" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@discordjs/builders": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-1.10.1.tgz", + "integrity": "sha512-OWo1fY4ztL1/M/DUyRPShB4d/EzVfuUvPTRRHRIt/YxBrUYSz0a+JicD5F5zHFoNs2oTuWavxCOVFV1UljHTng==", + "license": "Apache-2.0", + "dependencies": { + "@discordjs/formatters": "^0.6.0", + "@discordjs/util": "^1.1.1", + "@sapphire/shapeshift": "^4.0.0", + "discord-api-types": "^0.37.119", + "fast-deep-equal": "^3.1.3", + "ts-mixer": "^6.0.4", + "tslib": "^2.6.3" + }, + "engines": { + "node": ">=16.11.0" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" + } + }, + "node_modules/@discordjs/collection": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-1.5.3.tgz", + "integrity": "sha512-SVb428OMd3WO1paV3rm6tSjM4wC+Kecaa1EUGX7vc6/fddvw/6lg90z4QtCqm21zvVe92vMMDt9+DkIvjXImQQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=16.11.0" + } + }, + "node_modules/@discordjs/formatters": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@discordjs/formatters/-/formatters-0.6.0.tgz", + "integrity": "sha512-YIruKw4UILt/ivO4uISmrGq2GdMY6EkoTtD0oS0GvkJFRZbTSdPhzYiUILbJ/QslsvC9H9nTgGgnarnIl4jMfw==", + "license": "Apache-2.0", + "dependencies": { + "discord-api-types": "^0.37.114" + }, + "engines": { + "node": ">=16.11.0" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" + } + }, + "node_modules/@discordjs/rest": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/@discordjs/rest/-/rest-2.4.3.tgz", + "integrity": "sha512-+SO4RKvWsM+y8uFHgYQrcTl/3+cY02uQOH7/7bKbVZsTfrfpoE62o5p+mmV+s7FVhTX82/kQUGGbu4YlV60RtA==", + "license": "Apache-2.0", + "dependencies": { + "@discordjs/collection": "^2.1.1", + "@discordjs/util": "^1.1.1", + "@sapphire/async-queue": "^1.5.3", + "@sapphire/snowflake": "^3.5.3", + "@vladfrangu/async_event_emitter": "^2.4.6", + "discord-api-types": "^0.37.119", + "magic-bytes.js": "^1.10.0", + "tslib": "^2.6.3", + "undici": "6.21.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" + } + }, + "node_modules/@discordjs/rest/node_modules/@discordjs/collection": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-2.1.1.tgz", + "integrity": "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg==", + "license": "Apache-2.0", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" + } + }, + "node_modules/@discordjs/rest/node_modules/undici": { + "version": "6.21.1", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.21.1.tgz", + "integrity": "sha512-q/1rj5D0/zayJB2FraXdaWxbhWiNKDvu8naDT2dl1yTlvJp4BLtOcp2a5BvgGNQpYYJzau7tf1WgKv3b+7mqpQ==", + "license": "MIT", + "engines": { + "node": ">=18.17" + } + }, + "node_modules/@discordjs/util": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@discordjs/util/-/util-1.1.1.tgz", + "integrity": "sha512-eddz6UnOBEB1oITPinyrB2Pttej49M9FZQY8NxgEvc3tq6ZICZ19m70RsmzRdDHk80O9NoYN/25AqJl8vPVf/g==", + "license": "Apache-2.0", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" + } + }, + "node_modules/@discordjs/voice": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@discordjs/voice/-/voice-0.17.0.tgz", + "integrity": "sha512-hArn9FF5ZYi1IkxdJEVnJi+OxlwLV0NJYWpKXsmNOojtGtAZHxmsELA+MZlu2KW1F/K1/nt7lFOfcMXNYweq9w==", + "deprecated": "This version uses deprecated encryption modes. Please use a newer version.", + "license": "Apache-2.0", + "dependencies": { + "@types/ws": "^8.5.10", + "discord-api-types": "0.37.83", + "prism-media": "^1.3.5", + "tslib": "^2.6.2", + "ws": "^8.16.0" + }, + "engines": { + "node": ">=16.11.0" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" + } + }, + "node_modules/@discordjs/voice/node_modules/discord-api-types": { + "version": "0.37.83", + "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.83.tgz", + "integrity": "sha512-urGGYeWtWNYMKnYlZnOnDHm8fVRffQs3U0SpE8RHeiuLKb/u92APS8HoQnPTFbnXmY1vVnXjXO4dOxcAn3J+DA==", + "license": "MIT" + }, + "node_modules/@discordjs/ws": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@discordjs/ws/-/ws-1.2.1.tgz", + "integrity": "sha512-PBvenhZG56a6tMWF/f4P6f4GxZKJTBG95n7aiGSPTnodmz4N5g60t79rSIAq7ywMbv8A4jFtexMruH+oe51aQQ==", + "license": "Apache-2.0", + "dependencies": { + "@discordjs/collection": "^2.1.0", + "@discordjs/rest": "^2.4.3", + "@discordjs/util": "^1.1.0", + "@sapphire/async-queue": "^1.5.2", + "@types/ws": "^8.5.10", + "@vladfrangu/async_event_emitter": "^2.2.4", + "discord-api-types": "^0.37.119", + "tslib": "^2.6.2", + "ws": "^8.17.0" + }, + "engines": { + "node": ">=16.11.0" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" + } + }, + "node_modules/@discordjs/ws/node_modules/@discordjs/collection": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-2.1.1.tgz", + "integrity": "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg==", + "license": "Apache-2.0", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" + } + }, + "node_modules/@distube/ytdl-core": { + "version": "4.16.4", + "resolved": "https://registry.npmjs.org/@distube/ytdl-core/-/ytdl-core-4.16.4.tgz", + "integrity": "sha512-r0ZPMMB5rbUSQSez//dYDWjPSAEOm6eeV+9gyR+1vngGYFUi953Z/CoF4epTBS40X8dR32gyH3ERlh7NbnCaRg==", + "license": "MIT", + "dependencies": { + "http-cookie-agent": "^6.0.8", + "https-proxy-agent": "^7.0.6", + "m3u8stream": "^0.8.6", + "miniget": "^4.2.3", + "sax": "^1.4.1", + "tough-cookie": "^5.1.0", + "undici": "^7.3.0" + }, + "engines": { + "node": ">=20.18.1" + }, + "funding": { + "url": "https://github.com/distubejs/ytdl-core?sponsor" + } + }, + "node_modules/@sapphire/async-queue": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@sapphire/async-queue/-/async-queue-1.5.5.tgz", + "integrity": "sha512-cvGzxbba6sav2zZkH8GPf2oGk9yYoD5qrNWdu9fRehifgnFZJMV+nuy2nON2roRO4yQQ+v7MK/Pktl/HgfsUXg==", + "license": "MIT", + "engines": { + "node": ">=v14.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@sapphire/shapeshift": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sapphire/shapeshift/-/shapeshift-4.0.0.tgz", + "integrity": "sha512-d9dUmWVA7MMiKobL3VpLF8P2aeanRTu6ypG2OIaEv/ZHH/SUQ2iHOVyi5wAPjQ+HmnMuL0whK9ez8I/raWbtIg==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "lodash": "^4.17.21" + }, + "engines": { + "node": ">=v16" + } + }, + "node_modules/@sapphire/snowflake": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/@sapphire/snowflake/-/snowflake-3.5.3.tgz", + "integrity": "sha512-jjmJywLAFoWeBi1W7994zZyiNWPIiqRRNAmSERxyg93xRGzNYvGjlZ0gR6x0F4gPRi2+0O6S71kOZYyr3cxaIQ==", + "license": "MIT", + "engines": { + "node": ">=v14.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@types/node": { + "version": "22.13.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.5.tgz", + "integrity": "sha512-+lTU0PxZXn0Dr1NBtC7Y8cR21AJr87dLLU953CWA6pMxxv/UDc7jYAY90upcrie1nRcD6XNG5HOYEDtgW5TxAg==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.20.0" + } + }, + "node_modules/@types/ws": { + "version": "8.5.14", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.14.tgz", + "integrity": "sha512-bd/YFLW+URhBzMXurx7lWByOu+xzU9+kb3RboOteXYDfW+tr+JZa99OyNmPINEGB/ahzKrEuc8rcv4gnpJmxTw==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@vladfrangu/async_event_emitter": { + "version": "2.4.6", + "resolved": "https://registry.npmjs.org/@vladfrangu/async_event_emitter/-/async_event_emitter-2.4.6.tgz", + "integrity": "sha512-RaI5qZo6D2CVS6sTHFKg1v5Ohq/+Bo2LZ5gzUEwZ/WkHhwtGTCB/sVLw8ijOkAUxasZ+WshN/Rzj4ywsABJ5ZA==", + "license": "MIT", + "engines": { + "node": ">=v14.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", + "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.8.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.4.tgz", + "integrity": "sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "license": "MIT" + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", + "license": "Apache-2.0" + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "license": "MIT" + }, + "node_modules/concat-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", + "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", + "engines": [ + "node >= 6.0" + ], + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.0.2", + "typedarray": "^0.0.6" + } + }, + "node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/discord-api-types": { + "version": "0.37.119", + "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.119.tgz", + "integrity": "sha512-WasbGFXEB+VQWXlo6IpW3oUv73Yuau1Ig4AZF/m13tXcTKnMpc/mHjpztIlz4+BM9FG9BHQkEXiPto3bKduQUg==", + "license": "MIT" + }, + "node_modules/discord.js": { + "version": "14.18.0", + "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-14.18.0.tgz", + "integrity": "sha512-SvU5kVUvwunQhN2/+0t55QW/1EHfB1lp0TtLZUSXVHDmyHTrdOj5LRKdR0zLcybaA15F+NtdWuWmGOX9lE+CAw==", + "license": "Apache-2.0", + "dependencies": { + "@discordjs/builders": "^1.10.1", + "@discordjs/collection": "1.5.3", + "@discordjs/formatters": "^0.6.0", + "@discordjs/rest": "^2.4.3", + "@discordjs/util": "^1.1.1", + "@discordjs/ws": "^1.2.1", + "@sapphire/snowflake": "3.5.3", + "discord-api-types": "^0.37.119", + "fast-deep-equal": "3.1.3", + "lodash.snakecase": "4.1.1", + "tslib": "^2.6.3", + "undici": "6.21.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" + } + }, + "node_modules/discord.js/node_modules/undici": { + "version": "6.21.1", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.21.1.tgz", + "integrity": "sha512-q/1rj5D0/zayJB2FraXdaWxbhWiNKDvu8naDT2dl1yTlvJp4BLtOcp2a5BvgGNQpYYJzau7tf1WgKv3b+7mqpQ==", + "license": "MIT", + "engines": { + "node": ">=18.17" + } + }, + "node_modules/dotenv": { + "version": "16.4.7", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", + "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/ffmpeg": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/ffmpeg/-/ffmpeg-0.0.4.tgz", + "integrity": "sha512-3TgWUJJlZGQn+crJFyhsO/oNeRRnGTy6GhgS98oUCIfZrOW5haPPV7DUfOm3xJcHr5q3TJpjk2GudPutrNisRA==", + "dependencies": { + "when": ">= 0.0.1" + } + }, + "node_modules/ffmpeg-static": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ffmpeg-static/-/ffmpeg-static-5.2.0.tgz", + "integrity": "sha512-WrM7kLW+do9HLr+H6tk7LzQ7kPqbAgLjdzNE32+u3Ff11gXt9Kkkd2nusGFrlWMIe+XaA97t+I8JS7sZIrvRgA==", + "hasInstallScript": true, + "license": "GPL-3.0-or-later", + "dependencies": { + "@derhuerst/http-basic": "^8.2.0", + "env-paths": "^2.2.0", + "https-proxy-agent": "^5.0.0", + "progress": "^2.0.3" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/ffmpeg-static/node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/ffmpeg-static/node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", + "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-cookie-agent": { + "version": "6.0.8", + "resolved": "https://registry.npmjs.org/http-cookie-agent/-/http-cookie-agent-6.0.8.tgz", + "integrity": "sha512-qnYh3yLSr2jBsTYkw11elq+T361uKAJaZ2dR4cfYZChw1dt9uL5t3zSUwehoqqVb4oldk1BpkXKm2oat8zV+oA==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.3" + }, + "engines": { + "node": ">=18.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/3846masa" + }, + "peerDependencies": { + "tough-cookie": "^4.0.0 || ^5.0.0", + "undici": "^5.11.0 || ^6.0.0" + }, + "peerDependenciesMeta": { + "undici": { + "optional": true + } + } + }, + "node_modules/http-response-object": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/http-response-object/-/http-response-object-3.0.2.tgz", + "integrity": "sha512-bqX0XTF6fnXSQcEJ2Iuyr75yVakyjIDCqroJQ/aHfSdlM743Cwqoi2nDYMzLGWUcuTWGWy8AAvOKXTfiv6q9RA==", + "license": "MIT", + "dependencies": { + "@types/node": "^10.0.3" + } + }, + "node_modules/http-response-object/node_modules/@types/node": { + "version": "10.17.60", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.60.tgz", + "integrity": "sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==", + "license": "MIT" + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "license": "ISC" + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/libsodium": { + "version": "0.7.15", + "resolved": "https://registry.npmjs.org/libsodium/-/libsodium-0.7.15.tgz", + "integrity": "sha512-sZwRknt/tUpE2AwzHq3jEyUU5uvIZHtSssktXq7owd++3CSgn8RGrv6UZJJBpP7+iBghBqe7Z06/2M31rI2NKw==", + "license": "ISC" + }, + "node_modules/libsodium-wrappers": { + "version": "0.7.15", + "resolved": "https://registry.npmjs.org/libsodium-wrappers/-/libsodium-wrappers-0.7.15.tgz", + "integrity": "sha512-E4anqJQwcfiC6+Yrl01C1m8p99wEhLmJSs0VQqST66SbQXXBoaJY0pF4BNjRYa/sOQAxx6lXAaAFIlx+15tXJQ==", + "license": "ISC", + "dependencies": { + "libsodium": "^0.7.15" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" + }, + "node_modules/lodash.snakecase": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz", + "integrity": "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==", + "license": "MIT" + }, + "node_modules/m3u8stream": { + "version": "0.8.6", + "resolved": "https://registry.npmjs.org/m3u8stream/-/m3u8stream-0.8.6.tgz", + "integrity": "sha512-LZj8kIVf9KCphiHmH7sbFQTVe4tOemb202fWwvJwR9W5ENW/1hxJN6ksAWGhQgSBSa3jyWhnjKU1Fw1GaOdbyA==", + "license": "MIT", + "dependencies": { + "miniget": "^4.2.2", + "sax": "^1.2.4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/magic-bytes.js": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/magic-bytes.js/-/magic-bytes.js-1.10.0.tgz", + "integrity": "sha512-/k20Lg2q8LE5xiaaSkMXk4sfvI+9EGEykFS4b0CHHGWqDYU0bGUFSwchNOMA56D7TCs9GwVTkqe9als1/ns8UQ==", + "license": "MIT" + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/miniget": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/miniget/-/miniget-4.2.3.tgz", + "integrity": "sha512-SjbDPDICJ1zT+ZvQwK0hUcRY4wxlhhNpHL9nJOB2MEAXRGagTljsO8MEDzQMTFf0Q8g4QNi8P9lEm/g7e+qgzA==", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nodemon": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.9.tgz", + "integrity": "sha512-hdr1oIb2p6ZSxu3PB2JWWYS7ZQ0qvaZsc3hK8DR8f02kRzc8rjYmxAIvdz+aYC+8F2IjNaB7HMcSDg8nQpJxyg==", + "license": "MIT", + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^4", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/opusscript": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/opusscript/-/opusscript-0.0.8.tgz", + "integrity": "sha512-VSTi1aWFuCkRCVq+tx/BQ5q9fMnQ9pVZ3JU4UHKqTkf0ED3fKEPdr+gKAAl3IA2hj9rrP6iyq3hlcJq3HELtNQ==", + "license": "MIT" + }, + "node_modules/parse-cache-control": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parse-cache-control/-/parse-cache-control-1.0.1.tgz", + "integrity": "sha512-60zvsJReQPX5/QP0Kzfd/VrpjScIQ7SHBW6bFCYfEP+fp0Eppr1SHhIO5nd1PjZtvclzSzES9D/p5nFJurwfWg==" + }, + "node_modules/pg": { + "version": "8.13.3", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.13.3.tgz", + "integrity": "sha512-P6tPt9jXbL9HVu/SSRERNYaYG++MjnscnegFh9pPHihfoBSujsrka0hyuymMzeJKFWrcG8wvCKy8rCe8e5nDUQ==", + "license": "MIT", + "dependencies": { + "pg-connection-string": "^2.7.0", + "pg-pool": "^3.7.1", + "pg-protocol": "^1.7.1", + "pg-types": "^2.1.0", + "pgpass": "1.x" + }, + "engines": { + "node": ">= 8.0.0" + }, + "optionalDependencies": { + "pg-cloudflare": "^1.1.1" + }, + "peerDependencies": { + "pg-native": ">=3.0.1" + }, + "peerDependenciesMeta": { + "pg-native": { + "optional": true + } + } + }, + "node_modules/pg-cloudflare": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.1.1.tgz", + "integrity": "sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==", + "license": "MIT", + "optional": true + }, + "node_modules/pg-connection-string": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.7.0.tgz", + "integrity": "sha512-PI2W9mv53rXJQEOb8xNR8lH7Hr+EKa6oJa38zsK0S/ky2er16ios1wLKhZyxzD7jUReiWokc9WK5nxSnC7W1TA==", + "license": "MIT" + }, + "node_modules/pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", + "license": "ISC", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/pg-pool": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.7.1.tgz", + "integrity": "sha512-xIOsFoh7Vdhojas6q3596mXFsR8nwBQBXX5JiV7p9buEVAGqYL4yFzclON5P9vFrpu1u7Zwl2oriyDa89n0wbw==", + "license": "MIT", + "peerDependencies": { + "pg": ">=8.0" + } + }, + "node_modules/pg-protocol": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.7.1.tgz", + "integrity": "sha512-gjTHWGYWsEgy9MsY0Gp6ZJxV24IjDqdpTW7Eh0x+WfJLFsm/TJx1MzL6T0D88mBvkpxotCQ6TwW6N+Kko7lhgQ==", + "license": "MIT" + }, + "node_modules/pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "license": "MIT", + "dependencies": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pgpass": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", + "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", + "license": "MIT", + "dependencies": { + "split2": "^4.1.0" + } + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/play-audio": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/play-audio/-/play-audio-0.5.2.tgz", + "integrity": "sha512-ZAqHUKkQLix2Iga7pPbsf1LpUoBjcpwU93F1l3qBIfxYddQLhxS6GKmS0d3jV8kSVaUbr6NnOEcEMFvuX93SWQ==", + "license": "GPL-3.0" + }, + "node_modules/play-dl": { + "version": "1.9.7", + "resolved": "https://registry.npmjs.org/play-dl/-/play-dl-1.9.7.tgz", + "integrity": "sha512-KpgerWxUCY4s9Mhze2qdqPhiqd8Ve6HufpH9mBH3FN+vux55qSh6WJKDabfie8IBHN7lnrAlYcT/UdGax58c2A==", + "license": "GPL-3.0", + "dependencies": { + "play-audio": "^0.5.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/postgres-bytea": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", + "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "license": "MIT", + "dependencies": { + "xtend": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/prism-media": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/prism-media/-/prism-media-1.3.5.tgz", + "integrity": "sha512-IQdl0Q01m4LrkN1EGIE9lphov5Hy7WWlH6ulf5QdGePLlPas9p2mhgddTEHrlaXYjjFToM1/rWuwF37VF4taaA==", + "license": "Apache-2.0", + "peerDependencies": { + "@discordjs/opus": ">=0.8.0 <1.0.0", + "ffmpeg-static": "^5.0.2 || ^4.2.7 || ^3.0.0 || ^2.4.0", + "node-opus": "^0.3.3", + "opusscript": "^0.0.8" + }, + "peerDependenciesMeta": { + "@discordjs/opus": { + "optional": true + }, + "ffmpeg-static": { + "optional": true + }, + "node-opus": { + "optional": true + }, + "opusscript": { + "optional": true + } + } + }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "license": "MIT" + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/sax": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", + "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==", + "license": "ISC" + }, + "node_modules/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/tldts": { + "version": "6.1.78", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.78.tgz", + "integrity": "sha512-fSgYrW0ITH0SR/CqKMXIruYIPpNu5aDgUp22UhYoSrnUQwc7SBqifEBFNce7AAcygUPBo6a/gbtcguWdmko4RQ==", + "license": "MIT", + "dependencies": { + "tldts-core": "^6.1.78" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "6.1.78", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.78.tgz", + "integrity": "sha512-jS0svNsB99jR6AJBmfmEWuKIgz91Haya91Z43PATaeHJ24BkMoNRb/jlaD37VYjb0mYf6gRL/HOnvS1zEnYBiw==", + "license": "MIT" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/touch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", + "license": "ISC", + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, + "node_modules/tough-cookie": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.1.tgz", + "integrity": "sha512-Ek7HndSVkp10hmHP9V4qZO1u+pn1RU5sI0Fw+jCU3lyvuMZcgqsNgc6CmJJZyByK4Vm/qotGRJlfgAX8q+4JiA==", + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^6.1.32" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/ts-mixer": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.4.tgz", + "integrity": "sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA==", + "license": "MIT" + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", + "license": "MIT" + }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "license": "MIT" + }, + "node_modules/undici": { + "version": "7.7.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.7.0.tgz", + "integrity": "sha512-tZ6+5NBq4KH35rr46XJ2JPFKxfcBlYNaqLF/wyWIO9RMHqqU/gx/CLB1Y2qMcgB8lWw/bKHa7qzspqCN7mUHvA==", + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, + "node_modules/undici-types": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "license": "MIT" + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/when": { + "version": "3.7.8", + "resolved": "https://registry.npmjs.org/when/-/when-3.7.8.tgz", + "integrity": "sha512-5cZ7mecD3eYcMiCH4wtRPA5iFJZ50BJYDfckI5RRpQiktMiYTcn0ccLTZOvcbBume+1304fQztxeNzNS9Gvrnw==", + "license": "MIT" + }, + "node_modules/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, + "node_modules/ytdl-core": { + "version": "4.11.5", + "resolved": "https://registry.npmjs.org/ytdl-core/-/ytdl-core-4.11.5.tgz", + "integrity": "sha512-27LwsW4n4nyNviRCO1hmr8Wr5J1wLLMawHCQvH8Fk0hiRqrxuIu028WzbJetiYH28K8XDbeinYW4/wcHQD1EXA==", + "license": "MIT", + "dependencies": { + "m3u8stream": "^0.8.6", + "miniget": "^4.2.2", + "sax": "^1.1.3" + }, + "engines": { + "node": ">=12" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..6adb8be --- /dev/null +++ b/package.json @@ -0,0 +1,29 @@ +{ + "name": "blubot", + "version": "1.1.0", + "main": "index.js", + "scripts": { + "run": "node .", + "dev": "nodemon ." + }, + "author": "Megabyte", + "license": "ISC", + "description": "", + "dependencies": { + "@discordjs/voice": "^0.17.0", + "@distube/ytdl-core": "^4.14.4", + "axios": "^1.7.7", + "discord-api-types": "^0.37.119", + "discord.js": "^14.18.0", + "dotenv": "^16.4.5", + "ffmpeg": "^0.0.4", + "ffmpeg-static": "^5.2.0", + "libsodium-wrappers": "^0.7.15", + "nodemon": "^3.1.9", + "opusscript": "^0.0.8", + "pg": "^8.13.0", + "play-dl": "^1.9.7", + "undici": "^7.7.0", + "ytdl-core": "^4.11.5" + } +} diff --git a/playback.js b/playback.js new file mode 100644 index 0000000..a6cd29d --- /dev/null +++ b/playback.js @@ -0,0 +1,171 @@ +const { Client, Interaction, Member } = require("discord.js"); +const { + joinVoiceChannel, + createAudioPlayer, + createAudioResource, + NoSubscriberBehavior, + AudioPlayerStatus, +} = require("@discordjs/voice"); +const ytdl = require("@distube/ytdl-core"); +const { nowPlayingEmbed } = require("./embeds/nowPlaying"); + +const queue = []; +const activeChannels = {}; +let stopped = false; +let timeout; + +/** + * + * @param {Client} client + * @param {Interaction} interaction + */ + +function resetAll(guildId) { + //console.log(`Resetting all: ${guildId}`) + queue.length = 0; + cancelTimer() + const connection = activeChannels[guildId] + if (connection) { + connection.destroy() + // console.log('Conection Destroyed') + } + delete activeChannels[guildId] + stopped = false + //console.log('All States reset') +} + +function startTimer(connection) { + console.log(`Disconnect timer started `); + timeout = setTimeout(() => { + connection.destroy(); + }, 30_000); +} + +function cancelTimer() { + if (timeout) { + clearTimeout(timeout); + console.log("Disconnect timer stopped"); + timeout = null; + } +} + +async function playFromQueue( + voiceChannel, + textChannel, + getMemeber, + videoUrl, + videoTitle +) { + if (queue.length === 0) { + if (textChannel && activeChannels[voiceChannel.id] === textChannel.id) { + textChannel.send("Queue has finished playing."); + delete activeChannels[voiceChannel.id]; + } + return; + } + + //console.log("Current queue: ", queue) + + const { interaction, link, songName } = queue[0]; + + try { + let connection = activeChannels[interaction.guild.id]; + // Join the voice channel + + if (!connection) { + connection = joinVoiceChannel({ + channelId: voiceChannel.id, + guildId: interaction.guild.id, + adapterCreator: interaction.guild.voiceAdapterCreator, + }); + activeChannels[interaction.guild.id] = connection; + } + + // Stream audio using ytdl-core + const stream = ytdl(videoUrl, { + filter: "audioonly", + highWaterMark: 1 << 25, // Larger buffer size + quality: "highestaudio", + requestOptions: { + headers: { Connection: "keep-alive" }, + }, + }); + const resource = createAudioResource(stream, { inlineVolume: true }); + resource.volume.setVolume(1); // Set volume to 1 (max) + + const player = createAudioPlayer({ + behaviors: { + noSubscriber: NoSubscriberBehavior.Play, + }, + }); + + player.play(resource); + connection.subscribe(player); + + player.on(AudioPlayerStatus.Playing, () => { + if (startTimer) { + cancelTimer() + } else { + console.log('No timer running') + } + + }) + + player.on(AudioPlayerStatus.Idle, () => { + //console.log('The audio player is idle.'); + if (stopped) { + console.log("Playback was stopped by user"); + return; + } + + queue.shift(); + if (queue.length > 0) { + const nextsong = queue[0]; + playFromQueue( + voiceChannel, + textChannel, + nextsong.getMember, + nextsong.link, + nextsong.songName + ); + } else { + textChannel.send("The queue is empty."); + if (connection) { + startTimer(connection); + } else { + return; + } + } + }); + + if (interaction.replied || interaction.deferred) { + const embed = nowPlayingEmbed(queue); + await interaction.followUp({ embeds: [embed] }); + } else { + const embed = nowPlayingEmbed(queue); + await interaction.reply({ embeds: [embed] }); + } + + // Handle errors + player.on("error", (error) => { + console.error("Error with the audio player:", error); + interaction.followUp({ + content: "There was an error playing the music.", + ephemeral: true, + }); + }); + } catch (error) { + console.error("Error in playback.js: ", error); + } +} + +module.exports = { + playFromQueue, + queue, + cancelTimer, + setStopped: (value) => { + stopped = value; + }, + getConnection: (guildId) => activeChannels[guildId], // Helper to get connection + resetAll, +};