diff --git a/client.js b/client.js index 030708a..b01d1a3 100644 --- a/client.js +++ b/client.js @@ -2,7 +2,7 @@ require("dotenv").config() // Get .env // Init discord.js const Discord = require('discord.js'); -const client = new Discord.Client(); +const client = new Discord.Client({ partials: ['MESSAGE', 'CHANNEL', 'REACTION'] }); // Log when client is ready client.on('ready', () => { @@ -19,104 +19,32 @@ process.on('uncaughtException', function (err) { console.log(err); }) -const { parseMsg, isCmd } = require("./modules/parse.js") // Import command parsing functions - // Ready and parse commands.yml const fs = require("fs") const YAML = require('yaml') const commands = YAML.parse(fs.readFileSync('./commands.yml', 'utf8')) -// Import image module -let { exec, execGM, getFormat } = require("@frogebot/image")({ imageMagick: process.env.USE_IMAGEMAGICK, maxGifSize: process.env.MAX_GIF_SIZE, maxImageSize: process.env.MAX_IMAGE_SIZE }) - -let { findImage, sendImage } = require("./modules/utils.js") // Import image util commands +let { handleCmdMsg, handleReaction, handleMemberJoin, handleMemberLeave } = require("./modules/handler") +// On message handle command client.on('message', async msg => { - if(msg.author.bot || !await isCmd(msg)) return // If not command or called by bot - - let parsed = await parseMsg(msg); // Parses message, returns [0: Prefix, 1: Command, 2: Args string] - - let cmd = commands[parsed[1]] - let args = parsed[2] - let startTime = new Date().getTime() - - if(cmd.type == 'script') { // If command is set as script type - let { cmdFunc } = require('./'+cmd.path) // Gets function of command - setImmediate(async () => { - cmdFunc(msg, args, startTime) // Runs command function - }); - } else if (cmd.type == 'image') { // If command is set as image type - let imageUrl = await findImage(msg); // Find image in channel - try { - procMsg = await msg.channel.send(process.env.MSG_PROCESSING); - msg.channel.startTyping() - - let r; - if(cmd.r) { // Handle replacement of input args in "r" val of command - for(let i = cmd.r.split('||').length-1; i >= 0; i--) { - newR = cmd.r.split('||')[i].trim() // Separate potential r value - if(newR.match(/\(input\)/) && args.length > 0) { // If r accepts input and args are present - newR = newR.replace(/\(input\)/, args) // Replace with input value - if(cmd.r_type == 'int' && Number.isInteger(Number(newR))) r = newR // int type handling - if(cmd.r_type == 'num' && !Number.isNaN(Number(newR))) r = newR // num type handling - } else if(newR.match(/\(input\:[0-9]+\)/) && args.length > 0) { // If r accepts certain word of input and args are present - newR = newR.replace(/\(input:[0-9]+\)/, args.split(' ')[newR.replace(')','').split(':')[1]]) // Replace with input value - if(cmd.r_type.startsWith('int') && Number.isInteger(Number(newR))) r = newR // int type handling - if(cmd.r_type.startsWith('num') && !Number.isNaN(Number(newR))) r = newR // num type handling - } else if(!newR.match(/\(input\)/) && !newR.match(/\(input\:[0-9]+\)/)) { // If r is just a value - r = newR; // Set r with no worries :) - } - } - } - // Parse "r" as a number with constraints if required - if(cmd.r_type == 'int') r = parseInt(r) - if(cmd.r_type == 'num') r = Number(r) - if(cmd.r_type == 'int>0') r = Math.max(0, parseInt(r)) - if(cmd.r_type == 'num>0') r = Math.max(0, Number(r)) - - // Replace "(r)" with the variable r in params - let list = cmd.list.map(l => { if(typeof l == "object") { return [ Object.keys(l)[0], l[Object.keys(l)[0]].params.map(p => { if(p == '(r)') { return r } else { return p } }) ] } else { return [ l, [] ] } }) - - // Execute command - let img; - if(cmd.library == 'jimp') img = await exec(imageUrl, list); // Execute with jimp - if(cmd.library == 'magick') img = await execGM(imageUrl, list); // Execute with magick - - let extension = await getFormat(imageUrl) // Get extension of the output image - - // Send image - sendImage(msg, cmd.title, startTime, img, extension, procMsg) - } catch(e) { - // If error, catch it and let the user know - console.log(e) - msg.channel.stopTyping() - msg.channel.send({ - embed: { - "title": "Error", - "description": `<@${msg.author.id}> - ${ imageUrl != undefined ? process.env.MSG_ERROR : process.env.MSG_NO_IMAGE}`, - "color": Number(process.env.EMBED_COLOUR), - "timestamp": new Date(), - "author": { - "name": process.env.BOT_NAME, - "icon_url": msg.client.user.displayAvatarURL() - } - } - }) - procMsg.delete(); - } - } else if (cmd.type == 'music' && process.env.MUSIC_ENABLED == "true") { // If command is set as music type - musicWorker.postMessage({ msgId: msg.id, channelId: msg.channel.id, args, cmd }) // Post message to music worker - } + handleCmdMsg(msg) +}); +// Handle reactions +client.on('messageReactionAdd', async (reaction, user) => { + handleReaction(reaction, user, false) +}); +client.on('messageReactionRemove', async (reaction, user) => { + handleReaction(reaction, user, true) +}); +// Handle member join +client.on('guildMemberAdd', async member => { + handleMemberJoin(member) +}); +// Handle member leave +client.on('guildMemberRemove', async member => { + handleMemberLeave(member) }); - -// Create music worker -let musicWorker; -if(process.env.MUSIC_ENABLED == "true") { - const { Worker } = require('worker_threads'); - - const musicWorkerPath = '/modules/music-worker.js' - musicWorker = new Worker(__dirname+musicWorkerPath) // Spawn worker -} var path = require('path'); diff --git a/commands/reactions/starboard.js b/commands/reactions/starboard.js new file mode 100644 index 0000000..4f14f69 --- /dev/null +++ b/commands/reactions/starboard.js @@ -0,0 +1,85 @@ +require("dotenv").config() + +const escapeMarkdown = s => s.replace(/([\[\]\(\)])/g, '\\$&') +const attachmentType = a => + ({ + png: 'image', + jpg: 'image', + jpeg: 'image', + gif: 'image', + webp: 'image', + mp4: 'video', + mov: 'video', + webm: 'video', + }[fileExtension(a.url).toLowerCase()]) + +const fileExtension = url => { + if (!url) return + + return url.split('.').pop().split(/\#|\?/)[0] +} + +async function starFunc(reaction, member, data, startTime) { + const channel = await reaction.client.channels.fetch(data.channel.toString()) + if(!channel || reaction.message.channel.id == channel.id) return + const messages = await channel.messages.fetch({ limit: 100 }); + const inBoard = messages.find(m => (m.embeds && m.embeds.length >= 1 && m.embeds[0].description.endsWith(`[Jump to Message](${reaction.message.url})`))); + + if(inBoard) { + inBoard.edit(inBoard.embeds[0].setFooter(`⭐ ${reaction.count}`)) + if(reaction.count < data.count) return + } else { + if(reaction.count < data.count) return + const attachments = [...reaction.message.attachments.values()] + const primaryAttachment = attachments.shift() + let attachmentEmbed = {}, + files = [] + if (primaryAttachment) { + switch (attachmentType(primaryAttachment)) { + case 'image': + attachmentEmbed = { + image: { url: primaryAttachment.proxyURL }, + } + break + case 'video': + files = [{ attachment: primaryAttachment.proxyURL }] + break + default: + // Unknown; we'll handle it with all the other attachments + attachments.unshift(primaryAttachment) + } + } + const fields = [] + // Add reamining attachments to an extra field + if (attachments.length) { + fields.push({ + name: 'Attachments', + value: attachments + .map(a => `[${a.proxyURL.substring(a.proxyURL.lastIndexOf('/') + 1)}](${a.proxyURL})`,) + .join('\n'), + }) + } + channel.send({ + files, + embed: { + color: data.colour, + author: { + name: reaction.message.member.displayName, + icon_url: await reaction.message.author.displayAvatarURL(), + }, + description: `${escapeMarkdown(reaction.message.content)}\n\n[Jump to Message](${reaction.message.url})`, + timestamp: reaction.message.createdTimestamp, + footer: { + text: `⭐ ${reaction.count}` + }, + fields, + ...attachmentEmbed, + }, + }) + } +} + +module.exports = { + reactionAddFunc: starFunc, + reactionRemoveFunc: starFunc, +} \ No newline at end of file diff --git a/modules/handler.js b/modules/handler.js new file mode 100644 index 0000000..22b7de3 --- /dev/null +++ b/modules/handler.js @@ -0,0 +1,205 @@ +require("dotenv").config(); // Get .env + +const fs = require("fs"); +const YAML = require("yaml"); + +const commands = YAML.parse(fs.readFileSync("./commands.yml", "utf8")); +const triggers = YAML.parse(fs.readFileSync("./triggers.yml", "utf8")); + +let { exec, execGM, getFormat } = require("@frogebot/image")({ + imageMagick: process.env.USE_IMAGEMAGICK, + maxGifSize: process.env.MAX_GIF_SIZE, + maxImageSize: process.env.MAX_IMAGE_SIZE, +}); +let { findImage, sendImage } = require("./utils"); + +const { parseMsg, isCmd } = require("./parse"); + +let runMusicCmd; +if (process.env.MUSIC_ENABLED == "true") { + runMusicCmd = require("@frogebot/music")({ + BOT_NAME: process.env.BOT_NAME, + EMBED_COLOUR: process.env.EMBED_COLOUR, + MSG_VIBING: process.env.MSG_VIBING, + MSG_UNVIBING: process.env.MSG_UNVIBING, + SKIP_PERCENT: process.env.SKIP_PERCENT, + USE_MUSIC_ROLE: process.env.USE_MUSIC_ROLE, + MUSIC_ROLE_NAME: process.env.MUSIC_ROLE_NAME, + TOKEN: process.env.TOKEN, + }); +} + +async function handleCmdMsg(msg) { + if (msg.author.bot || !(await isCmd(msg))) return; // If not command or called by bot + + let parsed = await parseMsg(msg); // Parses message, returns [0: Prefix, 1: Command, 2: Args string] + + let cmd = commands[parsed[1]]; + let args = parsed[2]; + + handleCmd(msg, cmd, args); +} + +async function handleCmd(msg, cmd, args) { + let startTime = new Date().getTime(); + + if (cmd.type == "script") { + // If command is set as script type + let { cmdFunc } = require("../" + cmd.path); // Gets function of command + if (cmdFunc) { + setImmediate(async () => { + cmdFunc(msg, args, startTime); // Runs command function + }); + } + } else if (cmd.type == "image") { + // If command is set as image type + let imageUrl = await findImage(msg); // Find image in channel + try { + procMsg = await msg.channel.send(process.env.MSG_PROCESSING); + msg.channel.startTyping(); + + let r; + if (cmd.r) { + // Handle replacement of input args in "r" val of command + for (let i = cmd.r.split("||").length - 1; i >= 0; i--) { + newR = cmd.r.split("||")[i].trim(); + if (newR.match(/\(input\)/) && args.length > 0) { + newR = newR.replace(/\(input\)/, args); + if (cmd.r_type == "int" && Number.isInteger(Number(newR))) r = newR; + if (cmd.r_type == "num" && !Number.isNaN(Number(newR))) r = newR; + } else if (newR.match(/\(input\:[0-9]+\)/) && args.length > 0) { + newR = newR.replace( + /\(input:[0-9]+\)/, + args.split(" ")[newR.replace(")", "").split(":")[1]] + ); + if (cmd.r_type.startsWith("int") && Number.isInteger(Number(newR))) + r = newR; + if (cmd.r_type.startsWith("num") && !Number.isNaN(Number(newR))) + r = newR; + } else if ( + !newR.match(/\(input\)/) && + !newR.match(/\(input\:[0-9]+\)/) + ) { + r = newR; + } + } + } + // Parse "r" as a number if required + if (cmd.r_type == "int") r = parseInt(r); + if (cmd.r_type == "num") r = Number(r); + if (cmd.r_type == "int>0") r = Math.max(0, parseInt(r)); + if (cmd.r_type == "num>0") r = Math.max(0, Number(r)); + + // Replace "(r)" with the variable r in params + let list = cmd.list.map((l) => { + if (typeof l == "object") { + return [ + Object.keys(l)[0], + l[Object.keys(l)[0]].params.map((p) => { + if (p == "(r)") { + return r; + } else { + return p; + } + }), + ]; + } else { + return [l, []]; + } + }); + + // Execute command + let img; + if (cmd.library == "jimp") img = await exec(imageUrl, list); + if (cmd.library == "magick") img = await execGM(imageUrl, list); + + let extension = await getFormat(imageUrl); + + // Send image + sendImage(msg, cmd.title, startTime, img, extension, procMsg); + } catch (e) { + // If error, catch it and let the user know + console.log(e); + msg.channel.stopTyping(); + msg.channel.send({ + embed: { + title: "Error", + description: `<@${msg.author.id}> - ${ + imageUrl != undefined + ? process.env.MSG_ERROR + : process.env.MSG_NO_IMAGE + }`, + color: Number(process.env.EMBED_COLOUR), + timestamp: new Date(), + author: { + name: process.env.BOT_NAME, + icon_url: msg.client.user.displayAvatarURL(), + }, + }, + }); + procMsg.delete(); + } + } else if (cmd.type == "music" && process.env.MUSIC_ENABLED == "true") { + runMusicCmd(msg, args, cmd); + } +} + +const { getShortcode } = require("discord-emoji-converter"); +async function handleReaction(reaction, user, remove) { + if (reaction.partial) { + try { + await reaction.fetch(); + } catch (error) { + return; + } + } + + let startTime = new Date().getTime(); + + let emoji = `<${reaction.emoji.animated ? "a" : ""}:${reaction.emoji.name}:${ + reaction.emoji.id + }>`; + if (reaction.emoji.id == null) + emoji = getShortcode(reaction.emoji.name, true); + let toExec = triggers.reaction.filter( + (t) => + t.emoji == emoji || + (reaction.emoji.id == null && t.emoji == reaction.emoji.id) + ); + if (toExec.length == 0) return; + + let member = reaction.message.guild.members.resolve(user.id); + toExec.forEach((t) => { + if (t.type == "script") { + // If trigger is set as script type + let { reactionAddFunc, reactionRemoveFunc } = require("../" + t.path); // Gets function of trigger + if ( + !remove && + ["add", "both"].indexOf(t.event) != -1 && + reactionAddFunc + ) { + setImmediate(async () => { + reactionAddFunc(reaction, member, t.data, startTime); // Runs reaction function + }); + } + if ( + remove && + ["remove", "both"].indexOf(t.event) != -1 && + reactionRemoveFunc + ) { + setImmediate(async () => { + reactionRemoveFunc(reaction, member, t.data, startTime); // Runs reaction function + }); + } + } + }); +} +async function handleMemberJoin(member) {} +async function handleMemberLeave(member) {} + +module.exports = { + handleCmdMsg, + handleReaction, + handleMemberJoin, + handleMemberLeave, +}; diff --git a/modules/music-worker.js b/modules/music-worker.js deleted file mode 100644 index 6270a56..0000000 --- a/modules/music-worker.js +++ /dev/null @@ -1,41 +0,0 @@ -const { isMainThread, parentPort } = require("worker_threads"); - -// Create discord.js client -const Discord = require("discord.js"); -const client = new Discord.Client(); - -let ready = false; -client.on("ready", () => { - ready = true; - console.log(`Music client ready`); -}); - -client.on("error", (error) => { - console.log(error); -}); - -client.login(process.env.TOKEN); // discord.js connect to discord bot - -const musicCmdPath = "music.js"; -let { cmdFunc } = require("./" + musicCmdPath); // Gets function of music commands - -// On worker message (music command passthrough) -parentPort.on("message", async (data) => { - if (!isMainThread && ready) { - let { msgId, channelId, args, cmd } = data; - try { - let channel = await client.channels.fetch(channelId); // Get channel from ID - let msg = await channel.messages.fetch(msgId); // Get message from ID - setImmediate(async () => { - cmdFunc(msg, args, cmd.action); // Runs command function - }); - } catch (e) { - console.log(e); - } - } -}); - -// Catch process errors -process.on("uncaughtException", function (err) { - console.log(err); -}); diff --git a/modules/music.js b/modules/music.js deleted file mode 100644 index 939dc3a..0000000 --- a/modules/music.js +++ /dev/null @@ -1,54 +0,0 @@ -require("dotenv").config(); - -// Global vars -queue = new Map(); -client = undefined; - -// Action functions -var { - execute, - skip, - stop, - disconnect, - getQueue, - nowPlaying, - remove, - shuffle, -} = require("./music/actions"); - -// Handle recieved function -async function cmdFunc(msg, args, action) { - client = msg.client; - const serverQueue = queue.get(msg.guild.id); - if (action == "play") { - execute(msg, serverQueue, args); // Attempt to play track - } else if (action == "next") { - skip(msg, serverQueue); // Skip track - } else if (action == "stop") { - stop(msg, serverQueue); // Stop playback - } else if (action == "disconnect") { - disconnect(msg, msg.guild.id); // Disconnect from vc - } else if (action == "queue") { - getQueue(msg, serverQueue, args); // Sends queue - } else if (action == "nowPlaying") { - nowPlaying(msg, serverQueue); // Sends current track - } else if (action == "remove") { - remove(msg, serverQueue, args); // Remove from queue - } else if (action == "shuffle") { - shuffle(msg, serverQueue); // Shuffle queue - } -} - -// Global prototype function for required modules -Number.prototype.durationFormat = function () { - if (this >= 3600) { - return new Date(this * 1000).toISOString().substr(11, 8); - } else { - return new Date(this * 1000).toISOString().substr(14, 5); - } -}; - -// Exports -module.exports = { - cmdFunc, -}; diff --git a/modules/music/actions.js b/modules/music/actions.js deleted file mode 100644 index 8f38317..0000000 --- a/modules/music/actions.js +++ /dev/null @@ -1,517 +0,0 @@ -const ytdl = require("ytdl-core"); -const ytsr = require("ytsr"); -var ytpl = require("ytpl"); - -var { makeEmbed, playTrack, getPlaylist } = require("./utils"); // Require music utils - -async function execute(message, serverQueue, args) { - try { - const voiceChannel = message.member.voice.channel; - if (!voiceChannel) // If the user calling the command is not connected to vc - return message.channel.send( - makeEmbed( - "Error", - `<@${message.author.id}> - ${process.env.MSG_UNVIBING} You need to be in a voice channel to play music` - ) - ); - const permissions = voiceChannel.permissionsFor(message.client.user); - if (!permissions.has("CONNECT") || !permissions.has("SPEAK")) { // If the bot lacks either connect or speak perms - return message.channel.send( - makeEmbed( - "Error", - `<@${message.author.id}> - ${process.env.MSG_UNVIBING} ${process.env.BOT_NAME} lacks permissions to join and speak in your voice channel` - ) - ); - } - - let results; - - let isPlaylist = ytpl.validateID(args); // Check if the query is a playlist link/id - let isLinkOrId = ytdl.validateURL(args); // Check if the query is a video link/id - - if (isLinkOrId) { // If video - results = { // This object just mimics the response given by the search module so that the same code can be used for both - items: [ - { - id: ytdl.getVideoID(args), // Get video form id - }, - ], - }; - } else if (!isPlaylist) { // If the query is not a link or id to either video or playlist - let filterUrl = (await ytsr.getFilters(args)).get("Type").get("Video") - .url; - results = await ytsr(filterUrl, { limit: 1 }); - } // Get the first video result for the search query - - if ( - !isPlaylist && - (args.length == 0 || - (results.items && results.items.length == 0) || - (results.data && results.data.pageInfo.totalResults == 0)) - ) { // If no videos could be found - return message.channel.send( - makeEmbed( - "Error", - `<@${message.author.id}> - ${process.env.MSG_UNVIBING} No videos found` - ) - ); - } - - if (isPlaylist) { // If is a playlist - if (!serverQueue) { - const queueConstruct = { - textChannel: message.channel, - voiceChannel: voiceChannel, - connection: null, - songs: [], - volume: 5, - playing: true, - leaveTimeout: null, - }; - - queue.set(message.guild.id, queueConstruct); // Initialise server queue if it doesn't exist - - let playlist = await getPlaylist(args, message.author.id); // Get the playlist as an object - let playlistSongs = playlist.songs; - message.channel.send( - makeEmbed( - "Queued Playlist", - `<@${message.author.id}> - ${process.env.MSG_VIBING} **[${playlist.title}](https://www.youtube.com/playlist?list=${playlist.id})** has been added to the queue` - ) - ); - for (let i = 0; i < playlistSongs.length; i++) { // Loop throught the songs adding them to the queue - queueConstruct.songs.push(playlistSongs[i]); - if (i == 0) { // If it is the first song - try { - var connection = await voiceChannel.join(); // Join vc - queueConstruct.connection = connection; - playTrack(message.guild, queueConstruct.songs[0]); // Attempt to play the song - } catch (err) { - console.log(err); - queue.delete(message.guild.id); - return message.channel.send(err); - } - } - } - } else { - let playlist = await getPlaylist(args, message.author.id); // Get the playlist as an object - let playlistSongs = playlist.songs; - message.channel.send( - makeEmbed( - "Queued Playlist", - `<@${message.author.id}> - ${process.env.MSG_VIBING} **[${playlist.title}](https://www.youtube.com/playlist?list=${playlist.id})** has been added to the queue` - ) - ); - for (let i = 0; i < playlistSongs.length; i++) { // Loop throught the songs adding them to the queue - serverQueue.songs.push(playlistSongs[i]); - if (serverQueue.songs.length == 1) { // If the song being added means that the queue now only has one song (first in queue and nothing playing) - playTrack(message.guild, serverQueue.songs[0]); // Attempt to play the song - } - } - } - return; - } - - // If not a playlist - const songInfo = await ytdl.getInfo(results.items[0].id); // Get video details - const song = { - title: songInfo.videoDetails.title, - url: songInfo.videoDetails.video_url, - duration: Number(songInfo.videoDetails.lengthSeconds), - user: message.author.id, - }; // Map to song object - - if (!serverQueue) { - const queueConstruct = { - textChannel: message.channel, - voiceChannel: voiceChannel, - connection: null, - songs: [], - volume: 5, - playing: true, - leaveTimeout: null, - }; - - queue.set(message.guild.id, queueConstruct); // Initialise server queue if it doesn't exist - - queueConstruct.songs.push(song); // Add song to queue - - try { - var connection = await voiceChannel.join(); // Join vc - queueConstruct.connection = connection; - playTrack(message.guild, queueConstruct.songs[0]); // Attempt to play the song - } catch (err) { - console.log(err); - queue.delete(message.guild.id); - return message.channel.send(err); - } - } else { - serverQueue.songs.push(song); - if (serverQueue.songs.length == 1) { // If the song being added means that the queue now only has one song (first in queue and nothing playing) - playTrack(message.guild, serverQueue.songs[0]); // Attempt to play the song - } - - return message.channel.send( - makeEmbed( - "Queued Track", - `<@${message.author.id}> - ${process.env.MSG_VIBING} **[${song.title}](${song.url})** has been added to the queue`, - song.duration.durationFormat() - ) - ); - } - } catch (e) { - console.log(e); - - return message.channel.send( - makeEmbed( - "Error", - `<@${message.author.id}> - ${process.env.MSG_UNVIBING} Something went wrong` - ) - ); - } -} - -function skip(message, serverQueue) { - if (!message.member.voice.channel) { // If the user calling the command is not connected to vc - return message.channel.send( - makeEmbed( - "Error", - `<@${message.author.id}> - ${process.env.MSG_UNVIBING} You have to be in a voice channel to skip`, - ) - ); - } - if (!serverQueue || serverQueue.songs.length == 0) { // If no music is playing - return message.channel.send( - makeEmbed( - "Error", - `<@${message.author.id}> - ${process.env.MSG_UNVIBING} No music is playing this server`, - ) - ); - } - if (message.member.voice.channel.id != serverQueue.voiceChannel.id) { // If the voice channels don't match - return message.channel.send( - makeEmbed( - "Error", - `<@${message.author.id}> - ${process.env.MSG_UNVIBING} You're not in the same voice channel as the bot` - ) - ); - } - let sPercent = Number(process.env.SKIP_PERCENT); // Get the percent needed to skip - let members = serverQueue.voiceChannel.members.size - 1; // Get the number of people in the vc - let toSkip = Math.max(1, Math.ceil((members * sPercent) / 100)); // Work out the number required to skip (min 1) - if (!serverQueue.skips) serverQueue.skips = 0; // If by some anomaly, the skips value isn't defined, fix it - serverQueue.skips += 1; // Add 1 to skips - if (serverQueue.skips >= toSkip) { // If enough skips votes are cast - message.channel.send( - makeEmbed( - `Skipped (${toSkip}/${toSkip})`, - `<@${message.author.id}> - ${process.env.MSG_VIBING} Skipped song` - ) - ); - serverQueue.connection.dispatcher.end(); // End track playback early, will attempt to play the next song due to the end handler - } else { // If not enough skip votes have been cast - message.channel.send( - makeEmbed( - `Skipping (${serverQueue.skips}/${toSkip})`, - `<@${message.author.id}> - ${process.env.MSG_VIBING} ${toSkip-serverQueue.skips} more skips required` - ) - ); - } -} - -function stop(message, serverQueue) { - if (!message.member.voice.channel) { // If the user calling the command is not connected to vc - return message.channel.send( - makeEmbed( - "Error", - `<@${message.author.id}> - ${process.env.MSG_UNVIBING} You have to be in a voice channel to stop the music` - ) - ); - } - if (!serverQueue || serverQueue.songs.length == 0) { // If no music is playing - return message.channel.send( - makeEmbed( - "Error", - `<@${message.author.id}> - ${process.env.MSG_UNVIBING} No music is playing this server` - ) - ); - } - if (message.member.voice.channel.id != serverQueue.voiceChannel.id) { // If the voice channels don't match - return message.channel.send( - makeEmbed( - "Error", - `<@${message.author.id}> - ${process.env.MSG_UNVIBING} You're not in the same voice channel as the bot` - ) - ); - } - if ( - process.env.USE_MUSIC_ROLE != "true" || - message.member.roles.cache.find( - (r) => r.name == process.env.MUSIC_ROLE_NAME - ) != undefined || - serverQueue.voiceChannel.members.size <= 2 - ) { // Check conditions for disconnecting the bot (DJ role is disabled, has DJ role, no songs in queue, only 1 listener) - message.channel.send( - makeEmbed( - "Stopping", - `<@${message.author.id}> - ${process.env.MSG_VIBING} Stopped music` - ) - ); - serverQueue.songs = []; // Clear queue - serverQueue.connection.dispatcher.end(); // End playback - } else { // If none of the other conditions are met, tell them they lack the DJ role - message.channel.send( - makeEmbed( - "Error", - `<@${message.author.id}> - ${process.env.MSG_UNVIBING} You don't have the **${process.env.MUSIC_ROLE_NAME}** role` - ) - ); - } -} - -function disconnect(message, guildId) { - let serverQueue = queue.get(guildId); - if (!serverQueue) { // If no music is playing - return message.channel.send( - makeEmbed( - "Error", - `<@${message.author.id}> - ${process.env.MSG_UNVIBING} No music is playing this server` - ) - ); - } - if (!message.member.voice.channel) { // If the user calling the command is not connected to vc - return message.channel.send( - makeEmbed( - "Error", - `<@${message.author.id}> - ${process.env.MSG_UNVIBING} You have to be in a voice channel to stop the music` - ) - ); - } - if (message.member.voice.channel.id != serverQueue.voiceChannel.id) { // If the voice channels don't match - return message.channel.send( - makeEmbed( - "Error", - `<@${message.author.id}> - ${process.env.MSG_UNVIBING} You're not in the same voice channel as the bot` - ) - ); - } - if ( - process.env.USE_MUSIC_ROLE != "true" || - message.member.roles.cache.find( - (r) => r.name == process.env.MUSIC_ROLE_NAME - ) != undefined || - serverQueue.songs.length == 0 || - serverQueue.voiceChannel.members.size <= 2 - ) { // Check conditions for disconnecting the bot (DJ role is disabled, has DJ role, no songs in queue, only 1 listener) - clearTimeout(serverQueue.leaveTimeout); // Clear leave timeout - serverQueue.voiceChannel.leave(); // Leave now - queue.delete(guildId); // Delete queue object - message.channel.send( - makeEmbed( - "Disconnected", - `<@${message.author.id}> - ${process.env.MSG_VIBING}` - ) - ); - } else { // If none of the other conditions are met, tell them they lack the DJ role - message.channel.send( - makeEmbed( - "Error", - `<@${message.author.id}> - ${process.env.MSG_UNVIBING} You don't have the **${process.env.MUSIC_ROLE_NAME}** role` - ) - ); - } -} - -function getQueue(message, serverQueue, args) { - if (!serverQueue || serverQueue.songs.length == 0) { // If no music is playing - return message.channel.send( - makeEmbed( - "Error", - `<@${message.author.id}> - ${process.env.MSG_UNVIBING} No music is playing this server` - ) - ); - } else { - let perPage = 12; // Amount of songs to show per page - let pages = Math.floor(serverQueue.songs.length / perPage); // Amount of pages there are - - let page = - args.length > 0 && - Number.isInteger(Number(args)) && - Number(args) >= 1 && - Number(args) <= pages + 1 - ? Number(args) - 1 - : 0; // Calculate page to show - - let songsMapped = serverQueue.songs - .map( - (s, i) => `${i + 1}) ${s.title} | [${s.duration.durationFormat()}]` - ) // Songs to readable format - .slice(perPage * page, perPage * (page + 1)); // Limit to only the page requestd - - // Send the queue - return message.channel.send( - makeEmbed( - "Server Queue", - `<@${message.author.id}> - ${ - process.env.MSG_VIBING - }\n\`\`\`nim\n${songsMapped.join("\n")}\n\`\`\``, - `Page ${page + 1} of ${pages + 1} • ${serverQueue.songs.length} songs` - ) - ); - } -} - -function nowPlaying(message, serverQueue) { - if (!serverQueue || serverQueue.songs.length == 0) { // If no music is playing - return message.channel.send( - makeEmbed( - "Error", - `<@${message.author.id}> - ${process.env.MSG_UNVIBING} No music is playing this server` - ) - ); - } - - try { - let song = serverQueue.songs[0]; // Get currently playing song - - let remaining = - song.startTime + song.duration - Math.round(new Date().getTime() / 1000); // Get time remaining by subtracting current time from expected end time - let elapsed = song.duration - remaining; // Work out elapsed time - - let barLength = 30; // Length of duration bar - - let elapsedBars = Math.max( - 0, - Math.round((elapsed / song.duration) * barLength) - 1 - ); // Number of bar characters before the dot - - let bar = ( - "―".repeat(elapsedBars) + - "⬤" + - "―".repeat(barLength - elapsedBars - 1) - ).substr(0, barLength); // Generate bar string - message.channel.send( - makeEmbed( - "Now Playing", - `<@${message.author.id}> - ${process.env.MSG_VIBING} **[${ - song.title - }](${ - song.url - })**\n \`\`\`nim\n[${bar}] -${remaining.durationFormat()}\n\`\`\`` - ) - ); - } catch (e) { - return message.channel.send( - makeEmbed( - "Error", - `<@${message.author.id}> - ${process.env.MSG_UNVIBING} Something went wrong` - ) - ); - } -} - -function remove(message, serverQueue, args) { - if (!serverQueue || serverQueue.songs.length == 0) { // If no music is playing - return message.channel.send( - makeEmbed( - "Error", - `<@${message.author.id}> - ${process.env.MSG_UNVIBING} No music is playing this server` - ) - ); - } else { - if ( - args.length > 0 && - Number.isInteger(Number(args)) && - serverQueue.songs[Number(args) - 1] != undefined - ) { - if (Number(args) - 1 == 0) { // If attempted to remove currently playing song - return message.channel.send( - makeEmbed( - "Unable to remove", - `<@${message.author.id}> - ${process.env.MSG_UNVIBING} You can't remove the currently playing song` - ) - ); - } - let song = serverQueue.songs[Number(args) - 1]; - if ( - process.env.USE_MUSIC_ROLE != "true" || - message.member.roles.cache.find( - (r) => r.name == process.env.MUSIC_ROLE_NAME - ) != undefined || - song.user == message.author.id || - serverQueue.voiceChannel.members.size <= 2 - ) { // Check conditions for disconnecting the bot (DJ role is disabled, has DJ role, no songs in queue, only 1 listener) - serverQueue.songs.splice(Number(args) - 1, 1); // Remove the song from the queue - return message.channel.send( - makeEmbed( - "Removed", - `<@${message.author.id}> - ${process.env.MSG_VIBING} Removed **[${song.title}](${song.url})** from the queue` - ) - ); - } else { // If none of the other conditions are met, tell them they lack the DJ role - return message.channel.send( - makeEmbed( - "Error", - `<@${message.author.id}> - ${process.env.MSG_UNVIBING} You don't have the **${process.env.MUSIC_ROLE_NAME}** role` - ) - ); - } - } else { // If no song is found at the index supplied, or no index supplied - return message.channel.send( - makeEmbed( - "Error", - `<@${message.author.id}> - ${process.env.MSG_UNVIBING} There is no song at that index` - ) - ); - } - } -} - -function shuffle(message, serverQueue) { - if (!serverQueue || serverQueue.songs.length == 0) { // If no music is playing - return message.channel.send( - makeEmbed( - "Error", - `<@${message.author.id}> - ${process.env.MSG_UNVIBING} No music is playing this server` - ) - ); - } else { - if ( - process.env.USE_MUSIC_ROLE != "true" || - message.member.roles.cache.find( - (r) => r.name == process.env.MUSIC_ROLE_NAME - ) != undefined || - serverQueue.voiceChannel.members.size <= 2 - ) { // Check conditions for disconnecting the bot (DJ role is disabled, has DJ role, no songs in queue, only 1 listener) - let shuffled = [serverQueue.songs[0]].concat( - serverQueue.songs.slice(1).shuffle() - ); // Shuffle the queue but keep the first item in the same place because it's the currently playing track - serverQueue.songs = shuffled; // Apply the shuffled queue - return message.channel.send( - makeEmbed( - "Shuffled", - `<@${message.author.id}> - ${process.env.MSG_VIBING} Shuffled the music` - ) - ); - } else { // If none of the other conditions are met, tell them they lack the DJ role - message.channel.send( - makeEmbed( - "Error", - `<@${message.author.id}> - ${process.env.MSG_UNVIBING} You don't have the **${process.env.MUSIC_ROLE_NAME}** role` - ) - ); - } - } -} - -// Exports -module.exports = { - execute, - skip, - stop, - disconnect, - getQueue, - nowPlaying, - remove, - shuffle -} \ No newline at end of file diff --git a/modules/music/utils.js b/modules/music/utils.js deleted file mode 100644 index ff5418d..0000000 --- a/modules/music/utils.js +++ /dev/null @@ -1,128 +0,0 @@ -const ytdl = require("ytdl-core"); -var ytpl = require("ytpl"); - -// Shorthand embed function -function makeEmbed(title, description, footerText) { - return { - embed: { - title, - description, - color: Number(process.env.EMBED_COLOUR), - timestamp: new Date(), - author: { - name: process.env.BOT_NAME, - icon_url: client.user.displayAvatarURL(), - }, - footer: { - text: footerText, - }, - }, - }; -} - -function playTrack(guild, song) { - let serverQueue = queue.get(guild.id); - if (!song) { // If song is undefined, stop the music in 2 minutes - serverQueue.leaveTimeout = setTimeout(() => { - serverQueue.voiceChannel.leave(); - queue.delete(guild.id); - }, 120000); - return; - } - - // Reset skips when the next song is played - serverQueue.skips = 0; - - clearTimeout(serverQueue.leaveTimeout); // Cancel leave timeout - - try { - const dispatcher = serverQueue.connection - .play(ytdl(song.url, { filter: "audio" }), { // Play YouTube audio in vc - bitrate: "auto", - }) - .on("finish", () => { // When playback finishes, call the function again to try and play the next song - serverQueue.songs.shift(); - playTrack(guild, serverQueue.songs[0]); - }) - .on("error", (error) => { // When playback has an error, call the function again to try and play the next song - console.error(error); - serverQueue.songs.shift(); - playTrack(guild, serverQueue.songs[0]); - }) - .on("failed", (error) => { // When starting playback fails, log the error - console.error(error); - }); - dispatcher.setVolumeLogarithmic(serverQueue.volume / 5); // Set the volume - - serverQueue.textChannel.send( - makeEmbed( - "Now Playing", - `${process.env.MSG_VIBING} **[${song.title}](${song.url})**`, - song.duration.durationFormat() - ) - ); // Send a message saying that the song is now playing - - serverQueue.songs[0].startTime = Math.round(new Date().getTime() / 1000); // Set start time of song (used in nowPlaying) - } catch (e) { - console.log(e); - } -} - -// Function to convert from playlist ID to playlist object with array of parsed song objects -async function getPlaylist(args, userId) { - return new Promise(async (resolve, reject) => { - try { - // Get playlist from YouTube - const results = await ytpl(await ytpl.getPlaylistID(args), { - limit: Infinity, - }); - // Map videos to "songs" - let songs = results.items.map((v) => { - return { - title: v.title, - url: v.url, - duration: v.durationSec, - userId, - }; - }); - // Resolve playlist object - resolve({ - title: results.title, - id: results.id, - songs, - }); - } catch (e) { - reject(e); - } - }); -} - -// Exports -module.exports = { - makeEmbed, - playTrack, - getPlaylist -} - -// Array shuffle function -Array.prototype.shuffle = function () { - let array = this.slice(0); - - var currentIndex = array.length, - temporaryValue, - randomIndex; - - // While there remain elements to shuffle... - while (0 !== currentIndex) { - // Pick a remaining element... - randomIndex = Math.floor(Math.random() * currentIndex); - currentIndex -= 1; - - // And swap it with the current element. - temporaryValue = array[currentIndex]; - array[currentIndex] = array[randomIndex]; - array[randomIndex] = temporaryValue; - } - - return array; -}; \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index dee436f..95eef56 100644 --- a/package-lock.json +++ b/package-lock.json @@ -154,6 +154,26 @@ "twemoji-parser": "^13.0.0" } }, + "@frogebot/music": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@frogebot/music/-/music-1.0.0.tgz", + "integrity": "sha512-fifQ+f1t+kNpjNSVToYNlVawsp0yxiGH4/t2cqN526QfFK4zLgVlH1/TYb/bEscREnPxS7YtKyodd/3IHxI8pA==", + "requires": { + "@discordjs/opus": "^0.4.0", + "discord.js": "^12.5.1", + "opusscript": "0.0.8", + "ytdl": "^1.4.1", + "ytpl": "^2.0.5", + "ytsr": "^3.3.1" + }, + "dependencies": { + "opusscript": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/opusscript/-/opusscript-0.0.8.tgz", + "integrity": "sha512-VSTi1aWFuCkRCVq+tx/BQ5q9fMnQ9pVZ3JU4UHKqTkf0ED3fKEPdr+gKAAl3IA2hj9rrP6iyq3hlcJq3HELtNQ==" + } + } + }, "@jimp/bmp": { "version": "0.16.1", "resolved": "https://registry.npmjs.org/@jimp/bmp/-/bmp-0.16.1.tgz", @@ -515,11 +535,42 @@ "uri-js": "^4.2.2" } }, + "ansi-escape-sequences": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/ansi-escape-sequences/-/ansi-escape-sequences-5.1.2.tgz", + "integrity": "sha512-JcpoVp1W1bl1Qn4cVuiXEhD6+dyXKSOgCn2zlzE8inYgCJCBy1aPnUhlz6I4DFum8D4ovb9Qi/iAjUcGvG2lqw==", + "requires": { + "array-back": "^4.0.0" + } + }, "ansi-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + }, + "dependencies": { + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + } + } + }, "any-base": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/any-base/-/any-base-1.1.0.tgz", @@ -539,6 +590,11 @@ "readable-stream": "^2.0.6" } }, + "array-back": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-4.0.1.tgz", + "integrity": "sha512-Z/JnaVEXv+A9xabHzN43FiiiWEE7gPCRXMrVmRm00tWbjZRul1iHm7ECzlyNq1p4a4ATXz+G9FJ3GqGOkOV3fg==" + }, "array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", @@ -714,11 +770,59 @@ "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, "chownr": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" }, + "cli-progress": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/cli-progress/-/cli-progress-3.9.0.tgz", + "integrity": "sha512-g7rLWfhAo/7pF+a/STFH/xPyosaL1zgADhI0OM83hl3c7S43iGvJWEAV2QuDOnQ8i6EMBj/u4+NTd0d5L+4JfA==", + "requires": { + "colors": "^1.1.2", + "string-width": "^4.2.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + }, + "string-width": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "requires": { + "ansi-regex": "^5.0.0" + } + } + } + }, "code-point-at": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", @@ -755,6 +859,11 @@ "simple-swizzle": "^0.2.2" } }, + "colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==" + }, "combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -763,6 +872,11 @@ "delayed-stream": "~1.0.0" } }, + "commander": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", + "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==" + }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -864,6 +978,11 @@ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=" }, + "discord-emoji-converter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/discord-emoji-converter/-/discord-emoji-converter-1.0.0.tgz", + "integrity": "sha512-HD6k2REPoO8HUa4HrjcWs87P7G2tkqKsA+oBqYx0K43rUlm4J2I4am3A/RAGlHFKUkfCwY/PNToL9hnD0xnbHQ==" + }, "discord.js": { "version": "12.5.1", "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-12.5.1.tgz", @@ -889,6 +1008,11 @@ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz", "integrity": "sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw==" }, + "eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" + }, "ecc-jsbn": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", @@ -903,6 +1027,11 @@ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, "encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", @@ -1184,11 +1313,26 @@ "har-schema": "^2.0.0" } }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, "has-unicode": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" }, + "hash-arg": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/hash-arg/-/hash-arg-1.0.3.tgz", + "integrity": "sha512-BUvgqsnHlEupirIUo/pyaeodQ4gEP4b5aC/HgOvQcy7MMOhXrQjImcM+jupDNwBZDLxXpD04CufZQN32g1LdfQ==" + }, + "homedir": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/homedir/-/homedir-0.6.0.tgz", + "integrity": "sha1-KyHbZr8Ipts4JJo+/1LX0YcGrx4=" + }, "http-errors": { "version": "1.7.2", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", @@ -1349,6 +1493,33 @@ "verror": "1.10.0" } }, + "list-it": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/list-it/-/list-it-1.3.7.tgz", + "integrity": "sha512-a75ZgJGoGfHM6wo+tW8DFoMSm24pq1cJkZkvbzjZ/rpfe3a0djdgOoEd8MlgDeioBiCscZnvfL97gcR22xb+Vw==", + "requires": { + "ansi-escape-sequences": "^5.1.2", + "debug": "^4.2.0", + "eastasianwidth": "^0.2.0", + "hash-arg": "^1.0.3", + "node-getopt": "^0.3.2" + }, + "dependencies": { + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, "load-bmfont": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/load-bmfont/-/load-bmfont-1.4.1.tgz", @@ -1364,6 +1535,11 @@ "xtend": "^4.0.0" } }, + "lodash.throttle": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", + "integrity": "sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ=" + }, "lru-cache": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", @@ -1531,6 +1707,11 @@ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==" }, + "node-getopt": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/node-getopt/-/node-getopt-0.3.2.tgz", + "integrity": "sha512-yqkmYrMbK1wPrfz7mgeYvA4tBperLg9FQ4S3Sau3nSAkpOA0x0zC8nQ1siBwozy1f4SE8vq2n1WKv99r+PCa1Q==" + }, "node-pre-gyp": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.11.0.tgz", @@ -1889,6 +2070,14 @@ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, + "sanitize-filename": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/sanitize-filename/-/sanitize-filename-1.6.3.tgz", + "integrity": "sha512-y/52Mcy7aw3gRm7IrcGDFx/bCk4AhRh2eI9luHOQM86nZsqwiRkkq2GekHXBBD+SmPidc8i2PqtYZl+pWJ8Oeg==", + "requires": { + "truncate-utf8-bytes": "^1.0.0" + } + }, "sax": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", @@ -2091,6 +2280,11 @@ "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" }, + "streamspeed": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/streamspeed/-/streamspeed-2.0.1.tgz", + "integrity": "sha512-j6pFynhO0nZ+1zhyTDqLLlIxM0IyCdLKMj1EUJznDA87xLE/JWSwEg3FlJWERDB72KYt5OD29ZUWxxVkMTnqWg==" + }, "string-width": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", @@ -2122,6 +2316,14 @@ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "^4.0.0" + } + }, "tar": { "version": "4.4.13", "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.13.tgz", @@ -2195,6 +2397,14 @@ "punycode": "^2.1.1" } }, + "truncate-utf8-bytes": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz", + "integrity": "sha1-QFkjkJWS1W94pYGENLC3hInKXys=", + "requires": { + "utf8-byte-length": "^1.0.1" + } + }, "tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", @@ -2235,6 +2445,11 @@ "punycode": "^2.1.0" } }, + "utf8-byte-length": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.4.tgz", + "integrity": "sha1-9F8VDExm7uloGGUFq5P8u4rWv2E=" + }, "utif": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/utif/-/utif-2.0.1.tgz", @@ -2349,6 +2564,22 @@ "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.0.tgz", "integrity": "sha512-yr2icI4glYaNG+KWONODapy2/jDdMSDnrONSjblABjD9B4Z5LgiircSt8m8sRZFNi08kG9Sm0uSHtEmP3zaEGg==" }, + "ytdl": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/ytdl/-/ytdl-1.4.1.tgz", + "integrity": "sha512-YJS9zRRS7Goq3yBx4jt6pk1w0CPjAaTzYX49/BEDtP4aSsF92cQTkDdA6893AjS0adEvpH79gvXUOx6K959sBw==", + "requires": { + "chalk": "^4.0.0", + "cli-progress": "^3.8.2", + "commander": "^6.1.0", + "homedir": "^0.6.0", + "list-it": "^1.3.3", + "lodash.throttle": "^4.1.1", + "sanitize-filename": "^1.6.3", + "streamspeed": "^2.0.1", + "ytdl-core": "^4.1.0" + } + }, "ytdl-core": { "version": "4.4.5", "resolved": "https://registry.npmjs.org/ytdl-core/-/ytdl-core-4.4.5.tgz", diff --git a/package.json b/package.json index dc38cca..a2deb12 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,9 @@ "dependencies": { "@discordjs/opus": "^0.4.0", "@frogebot/image": "^1.1.6", + "@frogebot/music": "^1.0.0", "canvas": "^2.6.1", + "discord-emoji-converter": "^1.0.0", "discord.js": "^12.5.1", "dotenv": "^8.2.0", "express": "^4.17.1", diff --git a/triggers.yml b/triggers.yml new file mode 100644 index 0000000..699a251 --- /dev/null +++ b/triggers.yml @@ -0,0 +1,10 @@ +# Commands: Info can be found here https://github.com/FrogeBot/frogeBot/wiki/Commands +reaction: + - emoji: ":star:" + event: both + data: + count: 3 + channel: "815249192754741318" + colour: 15844367 + type: script + path: commands/reactions/starboard.js \ No newline at end of file