From 8b2c262c37128797d71fdf13de07e30df41c3b60 Mon Sep 17 00:00:00 2001 From: MrTech999 <48654513+MrTech999@users.noreply.github.com> Date: Mon, 9 Mar 2020 22:20:45 +0000 Subject: [PATCH 01/78] Added a dogfact and carfact command, while also improving index.js so it actually gives reasons for it shitting itself. --- index.js | 86 ++++++++++++++++++++++++++++++++++++++--- src/commands/catfact.js | 23 +++++++++++ src/commands/dogfact.js | 23 +++++++++++ 3 files changed, 127 insertions(+), 5 deletions(-) create mode 100644 src/commands/catfact.js create mode 100644 src/commands/dogfact.js diff --git a/index.js b/index.js index 9b559ff..a6db338 100644 --- a/index.js +++ b/index.js @@ -6,11 +6,40 @@ const chalk = require('chalk'); const DBL = require("dblapi.js"); const client = new Discord.Client(); +try { client.config = require('./config'); +} catch (err) { + console.log('Unable to load config.js \n', err); + process.exit(); +} + +try{ client.version = require('./version.json'); +} catch (err) { + console.log('Unable to load the version file. \n', err); + process.exit(); +} + +try{ client.logger = require('./src/modules/Logger'); +} catch (err) { + console.log('Unable to load the logger. \n', err); + process.exit(); +} + +try{ require("./src/modules/functions")(client); +} catch (err) { + console.log('Unable to load the functions. \n', err); + process.exit(); +} + +try{ client.logger.setClient(client); +} catch (err) { + console.log('Unable to initiate the logger. \n', err); + process.exit(1); +} if(process.env['USER'] != 'container') { client.devmode = true; @@ -19,12 +48,37 @@ if(process.env['USER'] != 'container') { const dblapi = new DBL(client.config.dblkey, client); } +try{ client.commands = new Enmap(); -client.aliases = new Enmap(); -client.settings = new Enmap({name: 'settings'}); -client.blacklist = new Enmap({name: 'blacklist'}); +} catch (err) { + console.log('Failed to create the commands database. \n', err); + process.exit(); +} +try{ +client.aliases = new Enmap(); +} catch (err) { + console.log('Unable to create the aliases database. \n', err); + process.exit(); +} + +try{ +client.settings = new Enmap({name: 'settings'}); +} catch (err) { + console.log('Unable to make the settings database. \n', err); + process.exit(); +} + +try{ +client.blacklist = new Enmap({name: 'blacklist'}); +} catch (err) { + console.log('Unable to create the blacklist database. \n', err); + process.exit(1); +} + +try{ const init = async () => { + try{ const cmdFiles = await readdir("./src/commands/"); client.logger.info(`Loading ${cmdFiles.length} commands.`); cmdFiles.forEach(file => { @@ -36,7 +90,12 @@ const init = async () => { console.log(response); }; }); +} catch (err) { + console.log('Unable to load Woomys commands. \n', err); + process.exit(1); +} + try{ const evtFiles = await readdir("./src/events/"); client.logger.info(`Loading ${evtFiles.length} events.`); evtFiles.forEach(file => { @@ -47,18 +106,35 @@ const init = async () => { const event = require(`./src/events/${file}`); client.on(eventName, event.bind(null, client)); }); +} catch (err) { + console.log('Unable to load Woomy events. \n', err); + process.exit(); +} + try{ client.levelCache = {}; for (let i = 0; i < client.config.permLevels.length; i++) { const thisLevel = client.config.permLevels[i]; client.levelCache[thisLevel.name] = thisLevel.level; }; +} catch (err) { + console.log('Unable to enable the levelCache. \n', err); + process.exit(); +} + try{ if(client.devmode === true) { client.login(client.config.devtoken); } else { client.login(client.config.token); }; +} catch (err) { + console.log('Unable to login to Discord. \n', err); + process.exit(1); +} }; - -init(); \ No newline at end of file +init(); +} catch (err) { + console.log('Failed to initiate Woomy. \n', err); + process.exit(1); +} \ No newline at end of file diff --git a/src/commands/catfact.js b/src/commands/catfact.js new file mode 100644 index 0000000..7505a1b --- /dev/null +++ b/src/commands/catfact.js @@ -0,0 +1,23 @@ +const request = require("request"); + +exports.run = async (bot, message, args) => { + request({ uri: "https://catfact.ninja/facts", json: true }, (error, response, body) => { + if (error) throw new Error(error); + message.channel.send(`**Did you know?**\n ${body.facts[0]}`); + }); +}; + +exports.conf = { + enabled: true, + guildOnly: false, + aliases: ["kittenfact"], + permLevel: "User", + requiredPerms: [] + }; + + exports.help = { + name: "catfact", + category: "User", + description: "Sends a fun fact about a cat.", + usage: "catfact/kittenfact" + }; diff --git a/src/commands/dogfact.js b/src/commands/dogfact.js new file mode 100644 index 0000000..ae0ef94 --- /dev/null +++ b/src/commands/dogfact.js @@ -0,0 +1,23 @@ +const request = require("request"); + +exports.run = async (bot, message, args) => { + request({ uri: "https://dog-api.kinduff.com/api/facts", json: true }, (error, response, body) => { + if (error) throw new Error(error); + message.channel.send(`**Did you know?**\n ${body.facts[0]}`); + }); +}; + +exports.conf = { + enabled: true, + guildOnly: false, + aliases: ["pupfact"], + permLevel: "User", + requiredPerms: [] + }; + + exports.help = { + name: "dogfact", + category: "User", + description: "Sends a fun fact about a doggo.", + usage: "dogfact/pupfact" + }; From 1c21161500f2b733e6ee2555385ec2bf43fa4096 Mon Sep 17 00:00:00 2001 From: mudkipscience Date: Tue, 10 Mar 2020 09:37:48 +1100 Subject: [PATCH 02/78] Bots have a bot badge, avatars are larger --- changes.txt | 2 ++ src/commands/about.js | 2 +- src/commands/avatar.js | 2 +- src/commands/ban.js | 2 +- src/commands/giverole.js | 2 +- src/commands/hackban.js | 2 +- src/commands/kick.js | 2 +- src/commands/nowplaying.js | 2 +- src/commands/purge.js | 2 +- src/commands/servericon.js | 2 +- src/commands/serverinfo.js | 2 +- src/commands/softban.js | 2 +- src/commands/takerole.js | 2 +- src/commands/userinfo.js | 14 +++++++++++--- 14 files changed, 25 insertions(+), 15 deletions(-) create mode 100644 changes.txt diff --git a/changes.txt b/changes.txt new file mode 100644 index 0000000..daa5b88 --- /dev/null +++ b/changes.txt @@ -0,0 +1,2 @@ +Links to avatars now lead to the original file size +Bots now get a bot badge in the userinfo command diff --git a/src/commands/about.js b/src/commands/about.js index 02b2c2c..5c1538f 100644 --- a/src/commands/about.js +++ b/src/commands/about.js @@ -28,7 +28,7 @@ exports.run = (client, message) => { embed = new Discord.MessageEmbed(); embed.setTitle(`Woomy`); embed.setColor(client.embedColour(message)); - embed.setThumbnail(client.user.avatarURL({format: "png", dynamic: true})) + embed.setThumbnail(client.user.avatarURL({format: "png", dynamic: true, size: 2048})) embed.addField( "General:", `• users: \`${client.users.cache.size}\`\n• channels: \`${client.channels.cache.size}\`\n• servers: \`${client.guilds.cache.size}\`\n• commands: \`${client.commands.size}\`\n• uptime: \`${duration}\``,true ); diff --git a/src/commands/avatar.js b/src/commands/avatar.js index 1bbb9e4..5832be2 100644 --- a/src/commands/avatar.js +++ b/src/commands/avatar.js @@ -19,7 +19,7 @@ exports.run = (client, message, args) => { user = users[0]; user = user.user; } - message.channel.send(`**${user.tag}'s** avatar is: ${user.avatarURL({format: "png", dynamic: true})}`); + message.channel.send(`**${user.tag}'s** avatar is: ${user.avatarURL({format: "png", dynamic: true, size: 2048})}`); }; exports.conf = { diff --git a/src/commands/ban.js b/src/commands/ban.js index fe403c2..affa882 100644 --- a/src/commands/ban.js +++ b/src/commands/ban.js @@ -58,7 +58,7 @@ exports.run = async (client, message, args) => { if (channel) { let embed = new Discord.MessageEmbed(); embed.setColor("#BC0057"); - embed.setAuthor("User banned!", user.user.avatarURL({format: "png", dynamic: true})); + embed.setAuthor("User banned!", user.user.avatarURL({format: "png", dynamic: true, size: 2048})); embed.setDescription( `• User: ${user.user.tag} (${user.user.id})\n• Mod: ${message.author} (${message.author.id})\n• Reason: ${reason}` ); diff --git a/src/commands/giverole.js b/src/commands/giverole.js index 356cd74..f62a055 100644 --- a/src/commands/giverole.js +++ b/src/commands/giverole.js @@ -59,7 +59,7 @@ exports.run = async (client, message, [member, ...role2add], query) => { if (channel) { let embed = new Discord.MessageEmbed(); embed.setColor("#00c09a"); - embed.setAuthor("Role given:", user.user.avatarURL({format: "png", dynamic: true})); + embed.setAuthor("Role given:", user.user.avatarURL({format: "png", dynamic: true, size: 2048})); embed.setDescription(`‏‏‎• User: ${user} (${user.user.id})\n‏‏‎• Mod: ${message.author} (${message.author.id})\n‏‏‎• Role: ${gRole}`) try { channel.send({ embed }); diff --git a/src/commands/hackban.js b/src/commands/hackban.js index c18b880..ff4d1cb 100644 --- a/src/commands/hackban.js +++ b/src/commands/hackban.js @@ -31,7 +31,7 @@ exports.run = async (client, message, args) => { if (channel) { let embed = new Discord.MessageEmbed(); embed.setColor("#BC0057"); - embed.setAuthor("User banned!", user.avatarURL({format: "png", dynamic: true})); + embed.setAuthor("User banned!", user.avatarURL({format: "png", dynamic: true, size: 2048})); embed.setDescription( `• User: ${user.tag} (${user.id})\n• Mod: ${message.author} (${message.author.id})\n• Reason: ${reason}` ); diff --git a/src/commands/kick.js b/src/commands/kick.js index 0002f44..9070a98 100644 --- a/src/commands/kick.js +++ b/src/commands/kick.js @@ -58,7 +58,7 @@ if (settings.modlogsChannel !== "off") { if (channel) { let embed = new Discord.MessageEmbed(); embed.setColor("#fd0061"); - embed.setAuthor("User kicked!", user.user.avatarURL({format: "png", dynamic: true})); + embed.setAuthor("User kicked!", user.user.avatarURL({format: "png", dynamic: true, size: 2048})); embed.setDescription( `• User: ${user.user.tag} (${user.user.id})\n• Mod: ${message.author} (${message.author.id})\n• Reason: ${reason}` ); diff --git a/src/commands/nowplaying.js b/src/commands/nowplaying.js index 9aa0487..e94f71a 100644 --- a/src/commands/nowplaying.js +++ b/src/commands/nowplaying.js @@ -23,7 +23,7 @@ exports.run = async (client, message) => { embed.setDescription(`**[${song.title}](https://www.youtube.com/watch?v=${song.id})**`) embed.addField("Channel:", song.author, true) embed.addField("Time:", timestamp, true) - embed.setFooter("Requested by " + song.requestedBy.tag, song.requestedBy.avatarURL({format: "png", dynamic: true})) + embed.setFooter("Requested by " + song.requestedBy.tag, song.requestedBy.avatarURL({format: "png", dynamic: true, size: 2048})) message.channel.send(embed) }; diff --git a/src/commands/purge.js b/src/commands/purge.js index 406bf64..79cf320 100644 --- a/src/commands/purge.js +++ b/src/commands/purge.js @@ -34,7 +34,7 @@ exports.run = async (client, message, args, level) => { if (channel) { let embed = new Discord.MessageEmbed(); embed.setColor("#a62019"); - embed.setAuthor(`${amount} messages purged!`, message.author.avatarURL({format: "png", dynamic: true})); + embed.setAuthor(`${amount} messages purged!`, message.author.avatarURL({format: "png", dynamic: true, size: 2048})); embed.setDescription(`• Channel: ${message.channel.name} (${message.channel.id})\n• Mod: ${message.author} (${message.author.id})\n• Amount: \`${amount}\``) try { channel.send({ embed }); diff --git a/src/commands/servericon.js b/src/commands/servericon.js index 5a22748..2eb44c4 100644 --- a/src/commands/servericon.js +++ b/src/commands/servericon.js @@ -1,5 +1,5 @@ exports.run = (client, message) => { - message.channel.send(`**${message.guild}'s** icon is:\n${message.guild.iconURL({format: "png", dynamic: true})}`) + message.channel.send(`**${message.guild}'s** icon is:\n${message.guild.iconURL({format: "png", dynamic: true, size: 2048})}`) }; exports.conf = { diff --git a/src/commands/serverinfo.js b/src/commands/serverinfo.js index 41c8b0c..89c2230 100644 --- a/src/commands/serverinfo.js +++ b/src/commands/serverinfo.js @@ -78,7 +78,7 @@ exports.run = (client, message) => { .setColor(message.guild.member(client.user).displayHexColor) .setTitle(guild.name) .setDescription(`${badges}• **ID:** ${guild.id}\n• **Owner:** ${guild.owner}\n• **Region:** ${guild.region.toProperCase()}\n• **Boosts:** ${boosts}\n• **Members:** ${members}\n• **Channels:** ${channelString}\n• **Roles:** ${roles}\n• **Emojis:** ${emojiString}\n• **Creation date:** ${guild.createdAt}`) - .setThumbnail(message.guild.iconURL({format: "png", dynamic: true})); + .setThumbnail(message.guild.iconURL({format: "png", dynamic: true, size: 2048})); message.channel.send(embed); }; diff --git a/src/commands/softban.js b/src/commands/softban.js index ca95e60..1b602c5 100644 --- a/src/commands/softban.js +++ b/src/commands/softban.js @@ -71,7 +71,7 @@ exports.run = async (client, message, args) => { if (channel) { let embed = new Discord.MessageEmbed(); embed.setColor("#F38159"); - embed.setAuthor("User softbanned!", user.user.avatarURL({format: "png", dynamic: true})); + embed.setAuthor("User softbanned!", user.user.avatarURL({format: "png", dynamic: true, size: 2048})); embed.setDescription( `• User: ${user.user.tag} (${user.user.id})\n• Mod: ${message.author} (${message.author.id})\n• Days cleared: ${days}` ); diff --git a/src/commands/takerole.js b/src/commands/takerole.js index 4a11fa6..408d0e3 100644 --- a/src/commands/takerole.js +++ b/src/commands/takerole.js @@ -59,7 +59,7 @@ exports.run = async (client, message, [member, ...role2add], query) => { if (channel) { let embed = new Discord.MessageEmbed(); embed.setColor("#008369"); - embed.setAuthor("Role taken:", user.user.avatarURL({format: "png", dynamic: true})); + embed.setAuthor("Role taken:", user.user.avatarURL({format: "png", dynamic: true, size: 2048})); embed.setDescription(`‏‏‎• User: ${user} (${user.user.id})\n‏‏‎• Mod: ${message.author} (${message.author.id})\n‏‏‎• Role: ${gRole}`) try { channel.send({ embed }); diff --git a/src/commands/userinfo.js b/src/commands/userinfo.js index 1ad7ec0..f84b40a 100644 --- a/src/commands/userinfo.js +++ b/src/commands/userinfo.js @@ -40,7 +40,15 @@ exports.run = (client, message, args) => { }; if(user.user.id == message.guild.ownerID) { - badges = "<:owner:685703193694306331>\n" + badges += "<:owner:685703193694306331>" + } + + if(user.user.bot) { + badges += "<:bot:686489601678114859>" + } + + if(badges.length > 0) { + badges += "\n" } createdTimestamp = user.user.createdTimestamp; @@ -61,13 +69,13 @@ exports.run = (client, message, args) => { id = user.user.id; tag = user.user.tag; colour = user.displayHexColor; - avurl = user.user.avatarURL({format: "png", dynamic: true}); + avurl = user.user.avatarURL({format: "png", dynamic: true, size: 2048}); createdAt = user.user.createdAt; } else { id = user.id; tag = user.tag; colour = ["#ff9d68", "#ff97cb", "#d789ff", "#74FFFF"].random(); - avurl = user.avatarURL({format: "png", dynamic: true}); + avurl = user.avatarURL({format: "png", dynamic: true, size: 2048}); createdAt = user.createdAt; }; From a91d4774eb5057583b7259b5042821f1df0dc945 Mon Sep 17 00:00:00 2001 From: mudkipscience Date: Mon, 9 Mar 2020 22:58:41 +0000 Subject: [PATCH 03/78] Update index.js - modified code a bit --- index.js | 34 ++++++++++++---------------------- 1 file changed, 12 insertions(+), 22 deletions(-) diff --git a/index.js b/index.js index a6db338..6adb8b7 100644 --- a/index.js +++ b/index.js @@ -9,35 +9,35 @@ const client = new Discord.Client(); try { client.config = require('./config'); } catch (err) { - console.log('Unable to load config.js \n', err); + console.log('Could not load config.js. \n', err); process.exit(); } try{ client.version = require('./version.json'); } catch (err) { - console.log('Unable to load the version file. \n', err); + console.log('Could not load version.json. \n', err); process.exit(); } try{ client.logger = require('./src/modules/Logger'); } catch (err) { - console.log('Unable to load the logger. \n', err); + console.log('Could not load Logger.js. \n', err); process.exit(); } try{ require("./src/modules/functions")(client); } catch (err) { - console.log('Unable to load the functions. \n', err); + console.log('Could not load functions.js. \n', err); process.exit(); } try{ client.logger.setClient(client); } catch (err) { - console.log('Unable to initiate the logger. \n', err); + console.log('Logger failed to initialize. \n', err); process.exit(1); } @@ -51,34 +51,33 @@ if(process.env['USER'] != 'container') { try{ client.commands = new Enmap(); } catch (err) { - console.log('Failed to create the commands database. \n', err); + console.log('Failed to create the commands map. \n', err); process.exit(); } try{ client.aliases = new Enmap(); } catch (err) { - console.log('Unable to create the aliases database. \n', err); + console.log('Failed to create the aliases map. \n', err); process.exit(); } try{ client.settings = new Enmap({name: 'settings'}); } catch (err) { - console.log('Unable to make the settings database. \n', err); + console.log('Failed to initialize the settings database. \n', err); process.exit(); } try{ client.blacklist = new Enmap({name: 'blacklist'}); } catch (err) { - console.log('Unable to create the blacklist database. \n', err); + console.log('Failed to initialize the blacklist database. \n', err); process.exit(1); } try{ const init = async () => { - try{ const cmdFiles = await readdir("./src/commands/"); client.logger.info(`Loading ${cmdFiles.length} commands.`); cmdFiles.forEach(file => { @@ -90,12 +89,7 @@ const init = async () => { console.log(response); }; }); -} catch (err) { - console.log('Unable to load Woomys commands. \n', err); - process.exit(1); -} - try{ const evtFiles = await readdir("./src/events/"); client.logger.info(`Loading ${evtFiles.length} events.`); evtFiles.forEach(file => { @@ -106,10 +100,6 @@ const init = async () => { const event = require(`./src/events/${file}`); client.on(eventName, event.bind(null, client)); }); -} catch (err) { - console.log('Unable to load Woomy events. \n', err); - process.exit(); -} try{ client.levelCache = {}; @@ -118,7 +108,7 @@ const init = async () => { client.levelCache[thisLevel.name] = thisLevel.level; }; } catch (err) { - console.log('Unable to enable the levelCache. \n', err); + console.log('Level cache failed to initialize. \n', err); process.exit(); } @@ -135,6 +125,6 @@ const init = async () => { }; init(); } catch (err) { - console.log('Failed to initiate Woomy. \n', err); + console.log('Initialization failed. \n', err); process.exit(1); -} \ No newline at end of file +} From 3af8fdb2b090912564aceae480c00de93621e8aa Mon Sep 17 00:00:00 2001 From: mudkipscience Date: Tue, 10 Mar 2020 10:11:53 +1100 Subject: [PATCH 04/78] Update README.md --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 693f553..d0fde98 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,16 @@ # Woomy -Woomy is a all-purpose discord bot built off the [guidebot](https://github.com/AnIdiotsGuide/guidebot) base and coded in node.js using discord.js. +Woomy is a all-purpose discord bot built off the [guidebot](https://github.com/AnIdiotsGuide/guidebot) base and coded in node.js using discord.js. # How to use The easiest way to use Woomy is to invite it to your server with [this link.](https://discordapp.com/oauth2/authorize?client_id=435961704145485835&permissions=2134240503&scope=bot) It is hosted 24/7 and automatically updates itself when a new release is made available, making sure you always get the newest features. -Self hosting is generally not recommended, but instructions are provided below if you still wish to do so. Woomy's code will need to be modified before it will run on your machine. +You can also self-host! Some modificatiomns to the code will need to be made before Woomy will run on your machine, but anyone who can read errors will figure out what needs to be changed pretty quickly :P # Requirements - git - node.js v12.0.0 or higher - node-gyp build tools -- ffmpeg +- ffmpeg (or ffmpeg-static) # Installation - Clone Woomy to your machine @@ -18,4 +18,4 @@ Self hosting is generally not recommended, but instructions are provided below i - Open config.js in your code editor and insert all the required information # Contributing -If you wish to contribute to Woomy, please fork the repository and open a pull request. +If you wish to contribute to Woomy, please fork the repository and open a pull request. Any contribution is appreciated <3 From 85be37097d0da55656d746e0e6ee2271edf7a7b6 Mon Sep 17 00:00:00 2001 From: mudkipscience Date: Tue, 10 Mar 2020 10:17:32 +1100 Subject: [PATCH 05/78] Update configTemplate.js --- configTemplate.js | 129 +++++++++++++++++++++++----------------------- 1 file changed, 65 insertions(+), 64 deletions(-) diff --git a/configTemplate.js b/configTemplate.js index a8adf42..3828ed8 100644 --- a/configTemplate.js +++ b/configTemplate.js @@ -1,68 +1,69 @@ const config = { - // ID's - "owners": ["433790467830972417", "324937993972350976"], - - // Tokens - "token": "", - "devtoken": "", - "ytkey": "", - "dblkey": "", - - // Default per-server settings - "defaultSettings" : { - "prefix": "~", - "devprefix": "!", - "modRole": "None set", - "adminRole": "None set", - "mutedRole": "None set", - "autorole": "off", - "welcomeChannel": "off", - "welcomeMessage": "off", - "leaveMessage": "off", - "chatlogsChannel": "off", - "modlogsChannel": "off", - "raidMode": "off", - "raidModeStrict": "off", - "blacklisted": "ARRAY", - "botChannels": "ARRAY", - "AFK": "ARRAY", - "SAR": "ARRAY" + // ID's + "owners": [], // Adding your ID here will give you access to dangerous commands like eval. Please be careful with who you add here! Eval can be used to modify the host machine. + + // Tokens + "token": "", // Your bot's token. + "devtoken": "", // (optional) another token, meant for a bot used for development + "ytkey": "", // Youtube API key, needed for music searching to work + "dblkey": "", // top.gg key, sends bot statistics to top.gg. You do not need this. + + // Default per-server settings + "defaultSettings" : { + "prefix": "~", + "devprefix": "!", + "modRole": "None set", + "adminRole": "None set", + "mutedRole": "None set", + "autorole": "off", + "welcomeChannel": "off", + "welcomeMessage": "off", + "leaveMessage": "off", + "chatlogsChannel": "off", + "modlogsChannel": "off", + "raidMode": "off", + "raidModeStrict": "off", + "blacklisted": "ARRAY", + "botChannels": "ARRAY", + "AFK": "ARRAY", + "SAR": "ARRAY", + "customCommands": "ARRAY", + }, + + // Perm levels + permLevels: [ + { level: 0, + name: "User", + check: () => true }, - - // Perm levels - permLevels: [ - { level: 0, - name: "User", - check: () => true - }, - - { level: 1, - name: "Moderator", - check: (message) => { - try { - if (message.member.roles.has(message.settings.modRole)) return true; - } catch (e) { - return false; - } + + { level: 1, + name: "Moderator", + check: (message) => { + try { + if (message.member.roles.cache.has(message.settings.modRole)) return true; + } catch (e) { + return false; } - }, - - { level: 2, - name: "Administrator", - check: (message) => { - try { - if (message.member.roles.has(message.settings.adminRole)) return true; - } catch (e) { - return false; - } + } + }, + + { level: 2, + name: "Administrator", + check: (message) => { + try { + if (message.member.roles.cache.has(message.settings.adminRole) || message.member.permissions.has("ADMINISTRATOR")) return true; + } catch (e) { + return false; } - }, - - { level: 3, - name: "Server Owner", - check: (message) => message.channel.type === "text" ? (message.guild.ownerID === message.author.id ? true : false) : false - }, - ] - }; - - module.exports = config; \ No newline at end of file + } + }, + + { level: 3, + name: "Server Owner", + check: (message) => message.channel.type === "text" ? (message.guild.ownerID === message.author.id ? true : false) : false + }, + ] +}; + +module.exports = config; From 1bf10ccc0c859df819e7571cb8e0d5cc20d872ab Mon Sep 17 00:00:00 2001 From: mudkipscience Date: Tue, 10 Mar 2020 10:57:54 +1100 Subject: [PATCH 06/78] Fixed catfact --- src/commands/catfact.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/catfact.js b/src/commands/catfact.js index 7505a1b..97063fc 100644 --- a/src/commands/catfact.js +++ b/src/commands/catfact.js @@ -3,7 +3,7 @@ const request = require("request"); exports.run = async (bot, message, args) => { request({ uri: "https://catfact.ninja/facts", json: true }, (error, response, body) => { if (error) throw new Error(error); - message.channel.send(`**Did you know?**\n ${body.facts[0]}`); + message.channel.send(`**Did you know?**\n ${body.data[0].fact}`); }); }; From e2403b6cbec1a347aaf3eb18552c4e6616e3bad9 Mon Sep 17 00:00:00 2001 From: Terryiscool160 <48654513+Terryiscool160@users.noreply.github.com> Date: Tue, 10 Mar 2020 00:12:00 +0000 Subject: [PATCH 07/78] Fixes. Put CatFact and DogFact into the "Fun" category while also adding a discord disconnect and reconnect logger. --- src/commands/catfact.js | 4 ++-- src/commands/dicerole.js | 27 +++++++++++++++++++++++++++ src/commands/dogfact.js | 2 +- src/events/disconnect.js | 3 +++ src/events/reconnection.js | 3 +++ 5 files changed, 36 insertions(+), 3 deletions(-) create mode 100644 src/commands/dicerole.js create mode 100644 src/events/disconnect.js create mode 100644 src/events/reconnection.js diff --git a/src/commands/catfact.js b/src/commands/catfact.js index 7505a1b..18fc87e 100644 --- a/src/commands/catfact.js +++ b/src/commands/catfact.js @@ -3,7 +3,7 @@ const request = require("request"); exports.run = async (bot, message, args) => { request({ uri: "https://catfact.ninja/facts", json: true }, (error, response, body) => { if (error) throw new Error(error); - message.channel.send(`**Did you know?**\n ${body.facts[0]}`); + message.channel.send(`**Did you know?**\n ${body.data[0].facts}`); }); }; @@ -17,7 +17,7 @@ exports.conf = { exports.help = { name: "catfact", - category: "User", + category: "Fun", description: "Sends a fun fact about a cat.", usage: "catfact/kittenfact" }; diff --git a/src/commands/dicerole.js b/src/commands/dicerole.js new file mode 100644 index 0000000..569f381 --- /dev/null +++ b/src/commands/dicerole.js @@ -0,0 +1,27 @@ +exports.run = async (bot, message, args) => { + if (args.length === 0) { + message.channel.send(`🎲 The dice landed on ${Array.from(Array(6).keys()).random() + 1}.`); + } else { + if (args[0].match(/^\d+$/)) { + message.channel.send(`🎲 The dice landed on ${Array.from(Array(parseInt(args[0])).keys()).random() + 1}.`); + } else { + message.channel.send(`🎲 The dice landed on ${Array.from(Array(6).keys()).random() + 1}.`); + } + } + }; + + exports.conf = { + enabled: true, + guildOnly: false, + aliases: ["diceroll"], + permLevel: "User", + requiredPerms: [] + }; + + exports.help = { + name: "dice", + category: "Fun", + description: "Rolls a dice.", + usage: "dice" + }; + \ No newline at end of file diff --git a/src/commands/dogfact.js b/src/commands/dogfact.js index ae0ef94..dc6bf7c 100644 --- a/src/commands/dogfact.js +++ b/src/commands/dogfact.js @@ -17,7 +17,7 @@ exports.conf = { exports.help = { name: "dogfact", - category: "User", + category: "Fun", description: "Sends a fun fact about a doggo.", usage: "dogfact/pupfact" }; diff --git a/src/events/disconnect.js b/src/events/disconnect.js new file mode 100644 index 0000000..932b5fd --- /dev/null +++ b/src/events/disconnect.js @@ -0,0 +1,3 @@ +module.exports = client => { // logs if the bot gets disconnected from discord. + client.logger.warn(`Bot disconnected at ${new Date()}`); +}; \ No newline at end of file diff --git a/src/events/reconnection.js b/src/events/reconnection.js new file mode 100644 index 0000000..d0a6fb2 --- /dev/null +++ b/src/events/reconnection.js @@ -0,0 +1,3 @@ +module.exports = client => { // logs when the bot reconnects to discord + client.logger.info(`Reconnecting at ${new Date()}`); +}; \ No newline at end of file From f032e0c1e9d1be82a263460603a6a6c4959f0c80 Mon Sep 17 00:00:00 2001 From: Terryiscool160 <48654513+Terryiscool160@users.noreply.github.com> Date: Tue, 10 Mar 2020 00:18:05 +0000 Subject: [PATCH 08/78] More stuff. Added a dice roll command. --- README.md | 8 ++++---- src/commands/diceroll.js | 27 +++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 4 deletions(-) create mode 100644 src/commands/diceroll.js diff --git a/README.md b/README.md index 693f553..c95b417 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,16 @@ # Woomy -Woomy is a all-purpose discord bot built off the [guidebot](https://github.com/AnIdiotsGuide/guidebot) base and coded in node.js using discord.js. +Woomy is a all-purpose discord bot built off the [guidebot](https://github.com/AnIdiotsGuide/guidebot) base and coded in node.js using discord.js. # How to use The easiest way to use Woomy is to invite it to your server with [this link.](https://discordapp.com/oauth2/authorize?client_id=435961704145485835&permissions=2134240503&scope=bot) It is hosted 24/7 and automatically updates itself when a new release is made available, making sure you always get the newest features. -Self hosting is generally not recommended, but instructions are provided below if you still wish to do so. Woomy's code will need to be modified before it will run on your machine. +You can also self-host! Some modificatiomns to the code will need to be made before Woomy will run on your machine, but anyone who can read errors will figure out what needs to be changed pretty quickly :P # Requirements - git - node.js v12.0.0 or higher - node-gyp build tools -- ffmpeg +- ffmpeg (or ffmpeg-static) # Installation - Clone Woomy to your machine @@ -18,4 +18,4 @@ Self hosting is generally not recommended, but instructions are provided below i - Open config.js in your code editor and insert all the required information # Contributing -If you wish to contribute to Woomy, please fork the repository and open a pull request. +If you wish to contribute to Woomy, please fork the repository and open a pull request. Any contribution is appreciated <3 \ No newline at end of file diff --git a/src/commands/diceroll.js b/src/commands/diceroll.js new file mode 100644 index 0000000..569f381 --- /dev/null +++ b/src/commands/diceroll.js @@ -0,0 +1,27 @@ +exports.run = async (bot, message, args) => { + if (args.length === 0) { + message.channel.send(`🎲 The dice landed on ${Array.from(Array(6).keys()).random() + 1}.`); + } else { + if (args[0].match(/^\d+$/)) { + message.channel.send(`🎲 The dice landed on ${Array.from(Array(parseInt(args[0])).keys()).random() + 1}.`); + } else { + message.channel.send(`🎲 The dice landed on ${Array.from(Array(6).keys()).random() + 1}.`); + } + } + }; + + exports.conf = { + enabled: true, + guildOnly: false, + aliases: ["diceroll"], + permLevel: "User", + requiredPerms: [] + }; + + exports.help = { + name: "dice", + category: "Fun", + description: "Rolls a dice.", + usage: "dice" + }; + \ No newline at end of file From 0c377f6cce942925a45398d379259c3efce0d4ef Mon Sep 17 00:00:00 2001 From: mudkipscience Date: Tue, 10 Mar 2020 00:35:38 +0000 Subject: [PATCH 09/78] Update dicerole.js --- src/commands/dicerole.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/commands/dicerole.js b/src/commands/dicerole.js index 569f381..b307d4e 100644 --- a/src/commands/dicerole.js +++ b/src/commands/dicerole.js @@ -1,11 +1,11 @@ exports.run = async (bot, message, args) => { if (args.length === 0) { - message.channel.send(`🎲 The dice landed on ${Array.from(Array(6).keys()).random() + 1}.`); + message.channel.send(`🎲 You rolled a ${Array.from(Array(6).keys()).random() + 1}!`); } else { if (args[0].match(/^\d+$/)) { - message.channel.send(`🎲 The dice landed on ${Array.from(Array(parseInt(args[0])).keys()).random() + 1}.`); + message.channel.send(`🎲 You rolled a ${Array.from(Array(parseInt(args[0])).keys()).random() + 1}!`); } else { - message.channel.send(`🎲 The dice landed on ${Array.from(Array(6).keys()).random() + 1}.`); + message.channel.send(`🎲 You rolled a ${Array.from(Array(6).keys()).random() + 1}!`); } } }; @@ -24,4 +24,4 @@ exports.run = async (bot, message, args) => { description: "Rolls a dice.", usage: "dice" }; - \ No newline at end of file + From 3fa96ca6f81dfad299d0ca1f8456109d83296fe6 Mon Sep 17 00:00:00 2001 From: mudkipscience Date: Tue, 10 Mar 2020 00:41:59 +0000 Subject: [PATCH 10/78] Delete dicerole.js --- src/commands/dicerole.js | 27 --------------------------- 1 file changed, 27 deletions(-) delete mode 100644 src/commands/dicerole.js diff --git a/src/commands/dicerole.js b/src/commands/dicerole.js deleted file mode 100644 index b307d4e..0000000 --- a/src/commands/dicerole.js +++ /dev/null @@ -1,27 +0,0 @@ -exports.run = async (bot, message, args) => { - if (args.length === 0) { - message.channel.send(`🎲 You rolled a ${Array.from(Array(6).keys()).random() + 1}!`); - } else { - if (args[0].match(/^\d+$/)) { - message.channel.send(`🎲 You rolled a ${Array.from(Array(parseInt(args[0])).keys()).random() + 1}!`); - } else { - message.channel.send(`🎲 You rolled a ${Array.from(Array(6).keys()).random() + 1}!`); - } - } - }; - - exports.conf = { - enabled: true, - guildOnly: false, - aliases: ["diceroll"], - permLevel: "User", - requiredPerms: [] - }; - - exports.help = { - name: "dice", - category: "Fun", - description: "Rolls a dice.", - usage: "dice" - }; - From 76d6230e97777e6b00dc33ba2e8ea6da957bc1a0 Mon Sep 17 00:00:00 2001 From: mudkipscience Date: Tue, 10 Mar 2020 00:42:20 +0000 Subject: [PATCH 11/78] Update diceroll.js --- src/commands/diceroll.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/commands/diceroll.js b/src/commands/diceroll.js index 569f381..b307d4e 100644 --- a/src/commands/diceroll.js +++ b/src/commands/diceroll.js @@ -1,11 +1,11 @@ exports.run = async (bot, message, args) => { if (args.length === 0) { - message.channel.send(`🎲 The dice landed on ${Array.from(Array(6).keys()).random() + 1}.`); + message.channel.send(`🎲 You rolled a ${Array.from(Array(6).keys()).random() + 1}!`); } else { if (args[0].match(/^\d+$/)) { - message.channel.send(`🎲 The dice landed on ${Array.from(Array(parseInt(args[0])).keys()).random() + 1}.`); + message.channel.send(`🎲 You rolled a ${Array.from(Array(parseInt(args[0])).keys()).random() + 1}!`); } else { - message.channel.send(`🎲 The dice landed on ${Array.from(Array(6).keys()).random() + 1}.`); + message.channel.send(`🎲 You rolled a ${Array.from(Array(6).keys()).random() + 1}!`); } } }; @@ -24,4 +24,4 @@ exports.run = async (bot, message, args) => { description: "Rolls a dice.", usage: "dice" }; - \ No newline at end of file + From c4ca29f56de773b047083d3ca51d0f003de416b9 Mon Sep 17 00:00:00 2001 From: mudkipscience Date: Tue, 10 Mar 2020 11:46:27 +1100 Subject: [PATCH 12/78] Update changes.txt --- changes.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/changes.txt b/changes.txt index daa5b88..25e245c 100644 --- a/changes.txt +++ b/changes.txt @@ -1,2 +1,6 @@ Links to avatars now lead to the original file size Bots now get a bot badge in the userinfo command +Added dogfact and catfact command (terry) +index.js now has better logging of when things fail to load/initialize (terry) +added `dice`, rolls a 6 sided die (terry) +Added two new events, disconnect.js and reconnection.js. Should log when the bot loses connection/reconnects to discord (terry) \ No newline at end of file From b9b14a45d2662911b449e22200e4fc1c2dd04e07 Mon Sep 17 00:00:00 2001 From: mudkipscience Date: Tue, 10 Mar 2020 12:01:44 +1100 Subject: [PATCH 13/78] Delete disconnect.js, reconnection.js --- src/events/disconnect.js | 3 --- src/events/reconnection.js | 3 --- 2 files changed, 6 deletions(-) delete mode 100644 src/events/disconnect.js delete mode 100644 src/events/reconnection.js diff --git a/src/events/disconnect.js b/src/events/disconnect.js deleted file mode 100644 index 932b5fd..0000000 --- a/src/events/disconnect.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = client => { // logs if the bot gets disconnected from discord. - client.logger.warn(`Bot disconnected at ${new Date()}`); -}; \ No newline at end of file diff --git a/src/events/reconnection.js b/src/events/reconnection.js deleted file mode 100644 index d0a6fb2..0000000 --- a/src/events/reconnection.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = client => { // logs when the bot reconnects to discord - client.logger.info(`Reconnecting at ${new Date()}`); -}; \ No newline at end of file From b62f5aeef1c5e0b3853c1c7239aa1a0119f13059 Mon Sep 17 00:00:00 2001 From: mudkipscience Date: Tue, 10 Mar 2020 12:20:28 +1100 Subject: [PATCH 14/78] help is now cooler n stuff --- changes.txt | 2 +- src/commands/help.js | 19 ++++++++++++------- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/changes.txt b/changes.txt index 25e245c..e3ed03b 100644 --- a/changes.txt +++ b/changes.txt @@ -3,4 +3,4 @@ Bots now get a bot badge in the userinfo command Added dogfact and catfact command (terry) index.js now has better logging of when things fail to load/initialize (terry) added `dice`, rolls a 6 sided die (terry) -Added two new events, disconnect.js and reconnection.js. Should log when the bot loses connection/reconnects to discord (terry) \ No newline at end of file +Help command changed, the amount of commands in each category and overall is now displayed \ No newline at end of file diff --git a/src/commands/help.js b/src/commands/help.js index 9ccb578..c9744a2 100644 --- a/src/commands/help.js +++ b/src/commands/help.js @@ -4,6 +4,7 @@ exports.run = (client, message, args, level) => { var ran = false; var output = ""; + var commands = 0; var prefix; var currentCategory; @@ -14,7 +15,7 @@ exports.run = (client, message, args, level) => { }; if(!args[0]) { - embed.setTitle("Command list"); + embed.setTitle(`Commands [${client.commands.size}]`); embed.setDescription(`⁣For more information on a specific command use \`${prefix}help \`\nFor the full command list use \`${prefix}help all\`\n`); const myCommands = message.guild ? client.commands.filter( @@ -37,16 +38,18 @@ exports.run = (client, message, args, level) => { const cat = c.help.category.toProperCase(); if (currentCategory !== cat) { if(ran == true) { - embed.addField(currentCategory + ":", output.slice(0, -6)) + embed.addField(currentCategory + ` [${commands}]`, output.slice(0, -2)) output = ""; + commands = 0; } currentCategory = cat; ran = true } - output += `\`${prefix}${c.help.name}\`**,** `; + output += `\`${prefix}${c.help.name}\`, `; + commands = commands + 1; }); - embed.addField(currentCategory + ":", output.slice(0, -6)); + embed.addField(currentCategory + ` [${commands}]`, output.slice(0, -2)); embed.addField( "Invite me", @@ -84,16 +87,18 @@ exports.run = (client, message, args, level) => { const cat = c.help.category.toProperCase(); if (currentCategory !== cat) { if(ran == true) { - embed.addField(currentCategory + ":", output.slice(0, -6)) + embed.addField(currentCategory + ` [${commands}]`, output.slice(0, -2)) output = ""; + commands = 0; } currentCategory = cat; ran = true } - output += `\`${prefix}${c.help.name}\`**,** `; + output += `\`${prefix}${c.help.name}\`, `; + commands = commands + 1; }); - embed.addField(currentCategory + ":", output.slice(0, -6)); + embed.addField(currentCategory + ` [${commands}]`, output.slice(0, -2)); embed.addField( "Invite me", From 155eac0688414124b8c875d93966eb5c08f174d3 Mon Sep 17 00:00:00 2001 From: mudkipscience Date: Tue, 10 Mar 2020 12:38:42 +1100 Subject: [PATCH 15/78] Update version.json --- version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.json b/version.json index b6a4488..3e289f1 100644 --- a/version.json +++ b/version.json @@ -1,4 +1,4 @@ { - "number": "1.1.0", + "number": "1.2.0", "changelog": "**1.1.0 CHANGELOG:**\n> • Added `~softban`, bans and unbans a user to clear messages\n> • Added `~emoji`, enlarges custom emojis\n> • Added `~inspirobot`, generates an inspirational quote\n> • `~serverinfo` has been changed to be more consistent, and also now displays boosts and if the server is partnered and stuff\n> • `~userinfo` has been changed to be more consistent, also added some stuff\n> • `~about` has been changed, added a thumbnail and removed the description\n> • `~colour` has been changed, it can now generate colours from text\n> • `~hackban` no longer has its own embed\n> • `~eval` now logs to hastebin if output is too large\n> • role names are no longer case sensitive\n> • `~echo` renamed `~say`\n> • Users with the ADMINISTRATOR permission now automatically recieve woomy admin\n> • Fixed `~flip`, `~purge`, `~bohemian_rhapsody` and `~creeper`\n> • Guild join/leave messages no longer include the guild name\n> • Some emojis have been changed\n> • Woomy now supports discord.js v12\n> • Files have been restructured\n> • Logger now logs error stack\n> • Restart now exits with code 0" } From b11f18b1a98ba94c0211cef10129a9acbdcda8c1 Mon Sep 17 00:00:00 2001 From: mudkipscience Date: Tue, 10 Mar 2020 20:35:16 +1100 Subject: [PATCH 16/78] Update index.js --- index.js | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/index.js b/index.js index 6adb8b7..4e535ee 100644 --- a/index.js +++ b/index.js @@ -9,35 +9,35 @@ const client = new Discord.Client(); try { client.config = require('./config'); } catch (err) { - console.log('Could not load config.js. \n', err); + console.log('Could not load config.js: \n', err); process.exit(); } try{ client.version = require('./version.json'); } catch (err) { - console.log('Could not load version.json. \n', err); + console.log('Could not load version.json: \n', err); process.exit(); } try{ client.logger = require('./src/modules/Logger'); } catch (err) { - console.log('Could not load Logger.js. \n', err); + console.log('Could not load Logger.js: \n', err); process.exit(); } try{ require("./src/modules/functions")(client); } catch (err) { - console.log('Could not load functions.js. \n', err); + console.log('Could not load functions.js: \n', err); process.exit(); } try{ client.logger.setClient(client); } catch (err) { - console.log('Logger failed to initialize. \n', err); + console.log('Logger failed to initialize: \n', err); process.exit(1); } @@ -45,34 +45,36 @@ if(process.env['USER'] != 'container') { client.devmode = true; } else { client.devmode = false; - const dblapi = new DBL(client.config.dblkey, client); + if(client.config.dblkey.length == 0) { + const dblapi = new DBL(client.config.dblkey, client); + } } try{ client.commands = new Enmap(); } catch (err) { - console.log('Failed to create the commands map. \n', err); + console.log('Failed to create the commands database: \n', err); process.exit(); } try{ client.aliases = new Enmap(); } catch (err) { - console.log('Failed to create the aliases map. \n', err); + console.log('Failed to create the aliases database: \n', err); process.exit(); } try{ client.settings = new Enmap({name: 'settings'}); } catch (err) { - console.log('Failed to initialize the settings database. \n', err); + console.log('Failed to initialize the settings database: \n', err); process.exit(); } try{ client.blacklist = new Enmap({name: 'blacklist'}); } catch (err) { - console.log('Failed to initialize the blacklist database. \n', err); + console.log('Failed to initialize the blacklist database: \n', err); process.exit(1); } @@ -108,7 +110,7 @@ const init = async () => { client.levelCache[thisLevel.name] = thisLevel.level; }; } catch (err) { - console.log('Level cache failed to initialize. \n', err); + console.log('Level cache failed to initialize: \n', err); process.exit(); } @@ -119,12 +121,12 @@ const init = async () => { client.login(client.config.token); }; } catch (err) { - console.log('Unable to login to Discord. \n', err); + console.log('Could not login to Discord: \n', err); process.exit(1); } }; init(); } catch (err) { - console.log('Initialization failed. \n', err); + console.log('Initialization failed: \n', err); process.exit(1); } From 1e66c75982c9efc2538157f1c5bb905e6acc989b Mon Sep 17 00:00:00 2001 From: mudkipscience Date: Wed, 11 Mar 2020 13:09:40 +1100 Subject: [PATCH 17/78] Update help.js --- src/commands/help.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/help.js b/src/commands/help.js index c9744a2..beeda6d 100644 --- a/src/commands/help.js +++ b/src/commands/help.js @@ -68,7 +68,7 @@ exports.run = (client, message, args, level) => { }; if(args[0].toLowerCase() == "all") { - embed.setTitle("Command list"); + embed.setTitle(`Commands [${client.commands.size}]`); embed.setDescription(`⁣For more information on a specific command use \`${prefix}help \`\nFor the full command list use \`${prefix}help all\`\n`); const myCommands = client.commands From 42f3fd10ff0072c6834f7d7744f720210d6aaa0d Mon Sep 17 00:00:00 2001 From: mudkipscience Date: Wed, 11 Mar 2020 14:33:03 +1100 Subject: [PATCH 18/78] why was this logging a unix timestamp --- src/commands/userinfo.js | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/commands/userinfo.js b/src/commands/userinfo.js index f84b40a..8d7008b 100644 --- a/src/commands/userinfo.js +++ b/src/commands/userinfo.js @@ -50,13 +50,6 @@ exports.run = (client, message, args) => { if(badges.length > 0) { badges += "\n" } - - createdTimestamp = user.user.createdTimestamp; - var date = new Date(createdTimestamp * 1000); - var hours = date.getHours(); - var minutes = "0" + date.getMinutes(); - var seconds = "o" + date.getSeconds(); - console.log(date) user.roles.cache.forEach((role) => { roles = roles + role.name + "`, `" From a82d6cff820f945e870bc2ab28791bacda8e4af7 Mon Sep 17 00:00:00 2001 From: mudkipscience Date: Wed, 11 Mar 2020 21:46:13 +1100 Subject: [PATCH 19/78] Synced changes --- changes.txt | 5 ++++- src/commands/inspirobot.js | 2 +- src/commands/ship.js | 37 +++++++++++++++++++++++++++++++++++++ src/modules/functions.js | 16 ++++++++++++++++ 4 files changed, 58 insertions(+), 2 deletions(-) create mode 100644 src/commands/ship.js diff --git a/changes.txt b/changes.txt index e3ed03b..20d4a91 100644 --- a/changes.txt +++ b/changes.txt @@ -3,4 +3,7 @@ Bots now get a bot badge in the userinfo command Added dogfact and catfact command (terry) index.js now has better logging of when things fail to load/initialize (terry) added `dice`, rolls a 6 sided die (terry) -Help command changed, the amount of commands in each category and overall is now displayed \ No newline at end of file +Help command changed, the amount of commands in each category and overall is now displayed +added `inspire` as an alias for inspirobot +ship command +added find by mention to functions \ No newline at end of file diff --git a/src/commands/inspirobot.js b/src/commands/inspirobot.js index 6a26ee7..5ba4e0a 100644 --- a/src/commands/inspirobot.js +++ b/src/commands/inspirobot.js @@ -20,7 +20,7 @@ exports.run = async (client, message) => { exports.conf = { enabled: true, guildOnly: false, - aliases: [], + aliases: ["inspire"], permLevel: "User", requiredPerms: [] }; diff --git a/src/commands/ship.js b/src/commands/ship.js new file mode 100644 index 0000000..cd187b9 --- /dev/null +++ b/src/commands/ship.js @@ -0,0 +1,37 @@ +const request = require('request') +exports.run = async (client, message, args) => { + message.channel.startTyping(); + + var user = client.getUserFromMention(args[0]) + var user2 = client.getUserFromMention(args[1]) + + var secondLength = Math.floor(user2.username.length / 2); + + var first = user.username.substr(0, user.username.length / 2) + var second = user2.username.substr(secondLength, user2.username.length / 2) + + try { + var attachment = new Discord.MessageAttachment(`https://api.alexflipnote.dev/ship?user=${user.avatarURL({format: "png"})}&user2=${user2.avatarURL({format: "png"})}`) + message.channel.send(`Your ship name is **${first+second}!**`, attachment) + message.channel.stopTyping(); + } catch(err) { + message.channel.send(`<:error:466995152976871434> API error: ${err}`); + message.channel.stopTyping(); + }; +}; + +exports.conf = { + enabled: true, + guildOnly: false, + aliases: [], + permLevel: "User", + requiredPerms: [] +}; + +exports.help = { + name: "ship", + category: "Fun", + description: "Ship two people together <3", + usage: "ship name name2" +}; + diff --git a/src/modules/functions.js b/src/modules/functions.js index 85ec48d..98cf223 100644 --- a/src/modules/functions.js +++ b/src/modules/functions.js @@ -133,6 +133,22 @@ module.exports = client => { return a; }; + // USER OBJECT FROM MENTION + client.getUserFromMention = mention => { + if (!mention) return; + + if (mention.startsWith('<@') && mention.endsWith('>')) { + mention = mention.slice(2, -1); + + if (mention.startsWith('!')) { + mention = mention.slice(1); + } + + return client.users.cache.get(mention); + } + } + + // MUSIC client.music = {guilds: {}}; From 5d8a81523f658d8fab2d8f1e8044cbb8d9e97831 Mon Sep 17 00:00:00 2001 From: MrTech999 <48654513+MrTech999@users.noreply.github.com> Date: Wed, 11 Mar 2020 20:47:06 +0000 Subject: [PATCH 20/78] Added yodish. --- changes.txt | 8 ++++++++ index.js | 28 +++++++++++++++------------- src/commands/catfact.js | 8 +++++++- src/commands/dogfact.js | 8 +++++++- src/commands/help.js | 21 +++++++++++++-------- src/commands/inspirobot.js | 2 +- src/commands/ship.js | 37 +++++++++++++++++++++++++++++++++++++ src/commands/userinfo.js | 7 ------- src/commands/yoda.js | 31 +++++++++++++++++++++++++++++++ src/modules/functions.js | 16 ++++++++++++++++ version.json | 2 +- 11 files changed, 136 insertions(+), 32 deletions(-) create mode 100644 src/commands/ship.js create mode 100644 src/commands/yoda.js diff --git a/changes.txt b/changes.txt index daa5b88..f7f1849 100644 --- a/changes.txt +++ b/changes.txt @@ -1,2 +1,10 @@ +Added a yodish command (terry) Links to avatars now lead to the original file size Bots now get a bot badge in the userinfo command +Added dogfact and catfact command (terry) +index.js now has better logging of when things fail to load/initialize (terry) +added `dice`, rolls a 6 sided die (terry) +Help command changed, the amount of commands in each category and overall is now displayed +added `inspire` as an alias for inspirobot +ship command +added find by mention to functions \ No newline at end of file diff --git a/index.js b/index.js index 6adb8b7..4e535ee 100644 --- a/index.js +++ b/index.js @@ -9,35 +9,35 @@ const client = new Discord.Client(); try { client.config = require('./config'); } catch (err) { - console.log('Could not load config.js. \n', err); + console.log('Could not load config.js: \n', err); process.exit(); } try{ client.version = require('./version.json'); } catch (err) { - console.log('Could not load version.json. \n', err); + console.log('Could not load version.json: \n', err); process.exit(); } try{ client.logger = require('./src/modules/Logger'); } catch (err) { - console.log('Could not load Logger.js. \n', err); + console.log('Could not load Logger.js: \n', err); process.exit(); } try{ require("./src/modules/functions")(client); } catch (err) { - console.log('Could not load functions.js. \n', err); + console.log('Could not load functions.js: \n', err); process.exit(); } try{ client.logger.setClient(client); } catch (err) { - console.log('Logger failed to initialize. \n', err); + console.log('Logger failed to initialize: \n', err); process.exit(1); } @@ -45,34 +45,36 @@ if(process.env['USER'] != 'container') { client.devmode = true; } else { client.devmode = false; - const dblapi = new DBL(client.config.dblkey, client); + if(client.config.dblkey.length == 0) { + const dblapi = new DBL(client.config.dblkey, client); + } } try{ client.commands = new Enmap(); } catch (err) { - console.log('Failed to create the commands map. \n', err); + console.log('Failed to create the commands database: \n', err); process.exit(); } try{ client.aliases = new Enmap(); } catch (err) { - console.log('Failed to create the aliases map. \n', err); + console.log('Failed to create the aliases database: \n', err); process.exit(); } try{ client.settings = new Enmap({name: 'settings'}); } catch (err) { - console.log('Failed to initialize the settings database. \n', err); + console.log('Failed to initialize the settings database: \n', err); process.exit(); } try{ client.blacklist = new Enmap({name: 'blacklist'}); } catch (err) { - console.log('Failed to initialize the blacklist database. \n', err); + console.log('Failed to initialize the blacklist database: \n', err); process.exit(1); } @@ -108,7 +110,7 @@ const init = async () => { client.levelCache[thisLevel.name] = thisLevel.level; }; } catch (err) { - console.log('Level cache failed to initialize. \n', err); + console.log('Level cache failed to initialize: \n', err); process.exit(); } @@ -119,12 +121,12 @@ const init = async () => { client.login(client.config.token); }; } catch (err) { - console.log('Unable to login to Discord. \n', err); + console.log('Could not login to Discord: \n', err); process.exit(1); } }; init(); } catch (err) { - console.log('Initialization failed. \n', err); + console.log('Initialization failed: \n', err); process.exit(1); } diff --git a/src/commands/catfact.js b/src/commands/catfact.js index 4944b43..8beb770 100644 --- a/src/commands/catfact.js +++ b/src/commands/catfact.js @@ -1,10 +1,16 @@ const request = require("request"); exports.run = async (bot, message, args) => { + message.channel.startTyping(); + try{ request({ uri: "https://catfact.ninja/facts", json: true }, (error, response, body) => { - if (error) throw new Error(error); message.channel.send(`**Did you know?**\n ${body.data[0].fact}`); + message.channel.startTyping(); }); +} catch(err) { + message.channel.send(`<:error:466995152976871434> API error: ${err}`); + message.channel.stopTyping(); +}; }; exports.conf = { diff --git a/src/commands/dogfact.js b/src/commands/dogfact.js index dc6bf7c..cd89858 100644 --- a/src/commands/dogfact.js +++ b/src/commands/dogfact.js @@ -1,10 +1,16 @@ const request = require("request"); exports.run = async (bot, message, args) => { + message.channel.startTyping(); + try{ request({ uri: "https://dog-api.kinduff.com/api/facts", json: true }, (error, response, body) => { - if (error) throw new Error(error); + message.channel.stopTyping(); message.channel.send(`**Did you know?**\n ${body.facts[0]}`); }); +} catch(err) { + message.channel.send(`<:error:466995152976871434> API error: ${err}`); + message.channel.stopTyping(); +}; }; exports.conf = { diff --git a/src/commands/help.js b/src/commands/help.js index 9ccb578..beeda6d 100644 --- a/src/commands/help.js +++ b/src/commands/help.js @@ -4,6 +4,7 @@ exports.run = (client, message, args, level) => { var ran = false; var output = ""; + var commands = 0; var prefix; var currentCategory; @@ -14,7 +15,7 @@ exports.run = (client, message, args, level) => { }; if(!args[0]) { - embed.setTitle("Command list"); + embed.setTitle(`Commands [${client.commands.size}]`); embed.setDescription(`⁣For more information on a specific command use \`${prefix}help \`\nFor the full command list use \`${prefix}help all\`\n`); const myCommands = message.guild ? client.commands.filter( @@ -37,16 +38,18 @@ exports.run = (client, message, args, level) => { const cat = c.help.category.toProperCase(); if (currentCategory !== cat) { if(ran == true) { - embed.addField(currentCategory + ":", output.slice(0, -6)) + embed.addField(currentCategory + ` [${commands}]`, output.slice(0, -2)) output = ""; + commands = 0; } currentCategory = cat; ran = true } - output += `\`${prefix}${c.help.name}\`**,** `; + output += `\`${prefix}${c.help.name}\`, `; + commands = commands + 1; }); - embed.addField(currentCategory + ":", output.slice(0, -6)); + embed.addField(currentCategory + ` [${commands}]`, output.slice(0, -2)); embed.addField( "Invite me", @@ -65,7 +68,7 @@ exports.run = (client, message, args, level) => { }; if(args[0].toLowerCase() == "all") { - embed.setTitle("Command list"); + embed.setTitle(`Commands [${client.commands.size}]`); embed.setDescription(`⁣For more information on a specific command use \`${prefix}help \`\nFor the full command list use \`${prefix}help all\`\n`); const myCommands = client.commands @@ -84,16 +87,18 @@ exports.run = (client, message, args, level) => { const cat = c.help.category.toProperCase(); if (currentCategory !== cat) { if(ran == true) { - embed.addField(currentCategory + ":", output.slice(0, -6)) + embed.addField(currentCategory + ` [${commands}]`, output.slice(0, -2)) output = ""; + commands = 0; } currentCategory = cat; ran = true } - output += `\`${prefix}${c.help.name}\`**,** `; + output += `\`${prefix}${c.help.name}\`, `; + commands = commands + 1; }); - embed.addField(currentCategory + ":", output.slice(0, -6)); + embed.addField(currentCategory + ` [${commands}]`, output.slice(0, -2)); embed.addField( "Invite me", diff --git a/src/commands/inspirobot.js b/src/commands/inspirobot.js index 6a26ee7..5ba4e0a 100644 --- a/src/commands/inspirobot.js +++ b/src/commands/inspirobot.js @@ -20,7 +20,7 @@ exports.run = async (client, message) => { exports.conf = { enabled: true, guildOnly: false, - aliases: [], + aliases: ["inspire"], permLevel: "User", requiredPerms: [] }; diff --git a/src/commands/ship.js b/src/commands/ship.js new file mode 100644 index 0000000..cd187b9 --- /dev/null +++ b/src/commands/ship.js @@ -0,0 +1,37 @@ +const request = require('request') +exports.run = async (client, message, args) => { + message.channel.startTyping(); + + var user = client.getUserFromMention(args[0]) + var user2 = client.getUserFromMention(args[1]) + + var secondLength = Math.floor(user2.username.length / 2); + + var first = user.username.substr(0, user.username.length / 2) + var second = user2.username.substr(secondLength, user2.username.length / 2) + + try { + var attachment = new Discord.MessageAttachment(`https://api.alexflipnote.dev/ship?user=${user.avatarURL({format: "png"})}&user2=${user2.avatarURL({format: "png"})}`) + message.channel.send(`Your ship name is **${first+second}!**`, attachment) + message.channel.stopTyping(); + } catch(err) { + message.channel.send(`<:error:466995152976871434> API error: ${err}`); + message.channel.stopTyping(); + }; +}; + +exports.conf = { + enabled: true, + guildOnly: false, + aliases: [], + permLevel: "User", + requiredPerms: [] +}; + +exports.help = { + name: "ship", + category: "Fun", + description: "Ship two people together <3", + usage: "ship name name2" +}; + diff --git a/src/commands/userinfo.js b/src/commands/userinfo.js index f84b40a..8d7008b 100644 --- a/src/commands/userinfo.js +++ b/src/commands/userinfo.js @@ -50,13 +50,6 @@ exports.run = (client, message, args) => { if(badges.length > 0) { badges += "\n" } - - createdTimestamp = user.user.createdTimestamp; - var date = new Date(createdTimestamp * 1000); - var hours = date.getHours(); - var minutes = "0" + date.getMinutes(); - var seconds = "o" + date.getSeconds(); - console.log(date) user.roles.cache.forEach((role) => { roles = roles + role.name + "`, `" diff --git a/src/commands/yoda.js b/src/commands/yoda.js new file mode 100644 index 0000000..aae6af0 --- /dev/null +++ b/src/commands/yoda.js @@ -0,0 +1,31 @@ +const request = require('request') +exports.run = async (client, message, args) => { + const speech = args.join(' '); + if (!args[0]) { + return message.channel.send(`<:error:466995152976871434> Please include text for me to convert to yodish. Yes.`) + } + try { + const { text } = request({ uri: `http://yoda-api.appspot.com/api/v1/yodish?text=${encodeURIComponent(speech.toLowerCase())}`, json: true }, (error, response, body) => { + message.channel.send(JSON.parse(text).yodish) + }); + } catch(err) { + message.channel.send(`<:error:466995152976871434> API error: ${err}`); + message.channel.stopTyping(); + } + +} + +exports.conf = { + enabled: true, + guildOnly: false, + aliases: ["yoda","yodasay"], + permLevel: "User", + requiredPerms: [] + }; + + exports.help = { + name: "yodish", + category: "Fun", + description: "Turns any text you input into yodish. Yes.", + usage: "yodish " + }; \ No newline at end of file diff --git a/src/modules/functions.js b/src/modules/functions.js index 85ec48d..98cf223 100644 --- a/src/modules/functions.js +++ b/src/modules/functions.js @@ -133,6 +133,22 @@ module.exports = client => { return a; }; + // USER OBJECT FROM MENTION + client.getUserFromMention = mention => { + if (!mention) return; + + if (mention.startsWith('<@') && mention.endsWith('>')) { + mention = mention.slice(2, -1); + + if (mention.startsWith('!')) { + mention = mention.slice(1); + } + + return client.users.cache.get(mention); + } + } + + // MUSIC client.music = {guilds: {}}; diff --git a/version.json b/version.json index b6a4488..3e289f1 100644 --- a/version.json +++ b/version.json @@ -1,4 +1,4 @@ { - "number": "1.1.0", + "number": "1.2.0", "changelog": "**1.1.0 CHANGELOG:**\n> • Added `~softban`, bans and unbans a user to clear messages\n> • Added `~emoji`, enlarges custom emojis\n> • Added `~inspirobot`, generates an inspirational quote\n> • `~serverinfo` has been changed to be more consistent, and also now displays boosts and if the server is partnered and stuff\n> • `~userinfo` has been changed to be more consistent, also added some stuff\n> • `~about` has been changed, added a thumbnail and removed the description\n> • `~colour` has been changed, it can now generate colours from text\n> • `~hackban` no longer has its own embed\n> • `~eval` now logs to hastebin if output is too large\n> • role names are no longer case sensitive\n> • `~echo` renamed `~say`\n> • Users with the ADMINISTRATOR permission now automatically recieve woomy admin\n> • Fixed `~flip`, `~purge`, `~bohemian_rhapsody` and `~creeper`\n> • Guild join/leave messages no longer include the guild name\n> • Some emojis have been changed\n> • Woomy now supports discord.js v12\n> • Files have been restructured\n> • Logger now logs error stack\n> • Restart now exits with code 0" } From cab212cecc136c206097020a93fe88e870a6e1b4 Mon Sep 17 00:00:00 2001 From: Terryiscool160 <48654513+Terryiscool160@users.noreply.github.com> Date: Wed, 11 Mar 2020 20:52:43 +0000 Subject: [PATCH 21/78] Add files via upload --- changes.txt | 8 ++++++++ src/commands/catfact.js | 8 +++++++- src/commands/dogfact.js | 8 +++++++- src/commands/yoda.js | 31 +++++++++++++++++++++++++++++++ 4 files changed, 53 insertions(+), 2 deletions(-) create mode 100644 src/commands/yoda.js diff --git a/changes.txt b/changes.txt index daa5b88..e91c096 100644 --- a/changes.txt +++ b/changes.txt @@ -1,2 +1,10 @@ +Added a yodish command and made catfact and dogfact say if the api errors (terry) Links to avatars now lead to the original file size Bots now get a bot badge in the userinfo command +Added dogfact and catfact command (terry) +index.js now has better logging of when things fail to load/initialize (terry) +added `dice`, rolls a 6 sided die (terry) +Help command changed, the amount of commands in each category and overall is now displayed +added `inspire` as an alias for inspirobot +ship command +added find by mention to functions \ No newline at end of file diff --git a/src/commands/catfact.js b/src/commands/catfact.js index 4944b43..8beb770 100644 --- a/src/commands/catfact.js +++ b/src/commands/catfact.js @@ -1,10 +1,16 @@ const request = require("request"); exports.run = async (bot, message, args) => { + message.channel.startTyping(); + try{ request({ uri: "https://catfact.ninja/facts", json: true }, (error, response, body) => { - if (error) throw new Error(error); message.channel.send(`**Did you know?**\n ${body.data[0].fact}`); + message.channel.startTyping(); }); +} catch(err) { + message.channel.send(`<:error:466995152976871434> API error: ${err}`); + message.channel.stopTyping(); +}; }; exports.conf = { diff --git a/src/commands/dogfact.js b/src/commands/dogfact.js index dc6bf7c..cd89858 100644 --- a/src/commands/dogfact.js +++ b/src/commands/dogfact.js @@ -1,10 +1,16 @@ const request = require("request"); exports.run = async (bot, message, args) => { + message.channel.startTyping(); + try{ request({ uri: "https://dog-api.kinduff.com/api/facts", json: true }, (error, response, body) => { - if (error) throw new Error(error); + message.channel.stopTyping(); message.channel.send(`**Did you know?**\n ${body.facts[0]}`); }); +} catch(err) { + message.channel.send(`<:error:466995152976871434> API error: ${err}`); + message.channel.stopTyping(); +}; }; exports.conf = { diff --git a/src/commands/yoda.js b/src/commands/yoda.js new file mode 100644 index 0000000..2d1645d --- /dev/null +++ b/src/commands/yoda.js @@ -0,0 +1,31 @@ +const request = require('request') +exports.run = async (client, message, args) => { + const speech = args.join(' '); + if (!args[0]) { + return message.channel.send(`<:error:466995152976871434> Please include text for me to convert to yodish. Yes.`) + } + try { + const { text } = request({ uri: `http://yoda-api.appspot.com/api/v1/yodish?text=${encodeURIComponent(speech.toLowerCase())}`, json: true }, (error, response, body) => { + message.channel.send(JSON.parse(text).yodish) + }); + } catch(err) { + message.channel.send(`<:error:466995152976871434> API error: ${err}`); + message.channel.stopTyping(); + } + +} + +exports.conf = { + enabled: true, + guildOnly: false, + aliases: ["yoda","yodasay"], + permLevel: "User", + requiredPerms: [] + }; + + exports.help = { + name: "yodish", + category: "Fun", + description: "Turns any text you input into yodish. Yes.", + usage: "yodish " + }; \ No newline at end of file From cc02ccf89d584ff5328222a66407594c99fd742a Mon Sep 17 00:00:00 2001 From: MrTech999 <48654513+MrTech999@users.noreply.github.com> Date: Wed, 11 Mar 2020 20:52:55 +0000 Subject: [PATCH 22/78] Update changes.txt --- changes.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changes.txt b/changes.txt index f7f1849..e91c096 100644 --- a/changes.txt +++ b/changes.txt @@ -1,4 +1,4 @@ -Added a yodish command (terry) +Added a yodish command and made catfact and dogfact say if the api errors (terry) Links to avatars now lead to the original file size Bots now get a bot badge in the userinfo command Added dogfact and catfact command (terry) From 9948976cf7faacf6fe5a167ff880a35b9aef6313 Mon Sep 17 00:00:00 2001 From: MrTech999 <48654513+MrTech999@users.noreply.github.com> Date: Wed, 11 Mar 2020 20:58:58 +0000 Subject: [PATCH 23/78] Update dogfact.js --- src/commands/dogfact.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/dogfact.js b/src/commands/dogfact.js index cd89858..31d1639 100644 --- a/src/commands/dogfact.js +++ b/src/commands/dogfact.js @@ -4,8 +4,8 @@ exports.run = async (bot, message, args) => { message.channel.startTyping(); try{ request({ uri: "https://dog-api.kinduff.com/api/facts", json: true }, (error, response, body) => { - message.channel.stopTyping(); message.channel.send(`**Did you know?**\n ${body.facts[0]}`); + message.channel.stopTyping(); }); } catch(err) { message.channel.send(`<:error:466995152976871434> API error: ${err}`); From 652b03136e70d74387817755758d26ac7f2d1a19 Mon Sep 17 00:00:00 2001 From: mudkipscience Date: Mon, 16 Mar 2020 12:14:33 +1100 Subject: [PATCH 24/78] synced changes --- package.json | 4 +- resources/other/coolpeople.json | 19 ++++++++++ src/commands/catfact.js | 16 ++++---- src/commands/colourify.js | 53 +++++++++++++++++++++++++++ src/commands/ship.js | 41 +++++++++++++++++++-- src/commands/ship1.js | 65 +++++++++++++++++++++++++++++++++ src/commands/userinfo.js | 14 ++++++- src/commands/yoda.js | 28 +++++++------- src/modules/functions.js | 2 +- 9 files changed, 215 insertions(+), 27 deletions(-) create mode 100644 resources/other/coolpeople.json create mode 100644 src/commands/colourify.js create mode 100644 src/commands/ship1.js diff --git a/package.json b/package.json index 7210610..1c03415 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "description": "Woomy is a all-purpose discord bot built off the guidebot base and coded in node.js using discord.js.", "main": "index.js", "dependencies": { + "@discordjs/opus": "^0.1.0", "better-sqlite3": "^5.4.1", "chalk": "^3.0.0", "dblapi.js": "^2.3.1", @@ -12,12 +13,13 @@ "garfield": "^1.1.2", "get-youtube-id": "^1.0.1", "hastebin-gen": "^2.0.5", + "is-url": "^1.2.4", "moment": "^2.24.0", "moment-duration-format": "^2.3.2", "prism-media": "^1.2.1", "randomcolor": "^0.5.4", - "request": "^2.88.2", "relevant-urban": "^2.0.0", + "request": "^2.88.2", "urban": "^0.3.2", "url-unshort": "^5.0.0", "url-unshorten": "^1.0.6", diff --git a/resources/other/coolpeople.json b/resources/other/coolpeople.json new file mode 100644 index 0000000..f0de195 --- /dev/null +++ b/resources/other/coolpeople.json @@ -0,0 +1,19 @@ +{ + "coolPeople": [ + "448354605617643520", + "433790467830972417", + "231777839576252417", + "285992938314661899", + "231704701433937931", + "324937993972350976", + "336492042299637771", + "273867501006225419", + "331870539897372672", + "304000458144481280", + "239787232666451980", + "264970229514371072", + "254310746450690048", + "358390849807319040", + "211011138656272386" + ] +} \ No newline at end of file diff --git a/src/commands/catfact.js b/src/commands/catfact.js index 8beb770..9fef875 100644 --- a/src/commands/catfact.js +++ b/src/commands/catfact.js @@ -3,14 +3,14 @@ const request = require("request"); exports.run = async (bot, message, args) => { message.channel.startTyping(); try{ - request({ uri: "https://catfact.ninja/facts", json: true }, (error, response, body) => { - message.channel.send(`**Did you know?**\n ${body.data[0].fact}`); - message.channel.startTyping(); - }); -} catch(err) { - message.channel.send(`<:error:466995152976871434> API error: ${err}`); - message.channel.stopTyping(); -}; + request({ uri: "https://catfact.ninja/facts", json: true }, (error, response, body) => { + message.channel.send(`**Did you know?**\n ${body.data[0].fact}`); + message.channel.stopTyping(); + }); + } catch(err) { + message.channel.send(`<:error:466995152976871434> API error: ${err}`); + message.channel.stopTyping(); + }; }; exports.conf = { diff --git a/src/commands/colourify.js b/src/commands/colourify.js new file mode 100644 index 0000000..f13720d --- /dev/null +++ b/src/commands/colourify.js @@ -0,0 +1,53 @@ +const isURL = require("is-url") +exports.run = async (client, message, args) => { + var img; + if(!args[0]) { + if(!message.attachments.first()) { + return message.channel.send("No attachment") + }; + img = message.attachments.first().attachment; + } else { + if(isURL(args[0]) == true ) { + img = args[0]; + } else { + user = message.mentions.members.first(); + + if (!user) { + let users; + users = client.searchForMembers(message.guild, args[0]); + if (users.length > 1) + return message.channel.send( + "<:error:466995152976871434> Found multiple users! Please be more specific or mention the user instead." + ); + else if (users.length == 0) + return message.channel.send( + "<:error:466995152976871434> That user doesn't seem to exist. Try again!" + ); + user = users[0]; + }; + + if(user) { + img = user.user.avatarURL({format: "png", dynamic: true}) + } + } + }; + + var attachment = new Discord.MessageAttachment(`https://api.alexflipnote.dev/filter/magik?image=${img}`) + message.channel.send(attachment) +}; + + exports.conf = { + enabled: true, + guildOnly: false, + aliases: [], + permLevel: "User", + requiredPerms: [] + }; + + exports.help = { + name: "magikt", + category: "Fun", + description: "Colourifies an image", + usage: "colourify user hex1 hex2" + }; + \ No newline at end of file diff --git a/src/commands/ship.js b/src/commands/ship.js index cd187b9..2ef0477 100644 --- a/src/commands/ship.js +++ b/src/commands/ship.js @@ -1,14 +1,49 @@ const request = require('request') exports.run = async (client, message, args) => { + + + //NOT FINISHED + + if(!args[0] || !args[1]) { + return message.channel.send(`<:error:466995152976871434> Please include two users`) + } message.channel.startTyping(); var user = client.getUserFromMention(args[0]) var user2 = client.getUserFromMention(args[1]) + + if (!user) { + let users; + users = client.searchForMembers(message.guild, args[0]); + if (users.length > 1) + return message.channel.send( + "<:error:466995152976871434> Found multiple users! Please be more specific or mention the user instead." + ); + else if (users.length == 0) + return message.channel.send( + "<:error:466995152976871434> That user doesn't seem to exist. Try again!" + ); + user = users[0]; + }; + + if (!user2) { + let users; + users = client.searchForMembers(message.guild, args[0]); + if (users.length > 1) + return message.channel.send( + "<:error:466995152976871434> Found multiple users! Please be more specific or mention the user instead." + ); + else if (users.length == 0) + return message.channel.send( + "<:error:466995152976871434> That user doesn't seem to exist. Try again!" + ); + user2 = users[0]; + }; var secondLength = Math.floor(user2.username.length / 2); - var first = user.username.substr(0, user.username.length / 2) - var second = user2.username.substr(secondLength, user2.username.length / 2) + var first = user.username.slice(0, user.username.length / 2) + var second = user2.username.slice(secondLength, user2.username.length / 2) try { var attachment = new Discord.MessageAttachment(`https://api.alexflipnote.dev/ship?user=${user.avatarURL({format: "png"})}&user2=${user2.avatarURL({format: "png"})}`) @@ -22,7 +57,7 @@ exports.run = async (client, message, args) => { exports.conf = { enabled: true, - guildOnly: false, + guildOnly: true, aliases: [], permLevel: "User", requiredPerms: [] diff --git a/src/commands/ship1.js b/src/commands/ship1.js new file mode 100644 index 0000000..ca96edd --- /dev/null +++ b/src/commands/ship1.js @@ -0,0 +1,65 @@ +const request = require('request') +exports.run = async (client, message, args) => { + + + //NOT FINISHED + + if(!args[0] || !args[1]) { + return message.channel.send(`<:error:466995152976871434> Please include two to five users`) + } + message.channel.startTyping(); + + var user = client.getUserFromMention(args[0]) + var user2 = client.getUserFromMention(args[1]) + + if (!user) { + let users; + users = client.searchForMembers(message.guild, args[0]); + if (users.length > 1) + return message.channel.send( + "<:error:466995152976871434> Found multiple users! Please be more specific or mention the user instead." + ); + else if (users.length == 0) + return message.channel.send( + "<:error:466995152976871434> That user doesn't seem to exist. Try again!" + ); + user = users[0]; + }; + + if (!user2) { + let users; + users = client.searchForMembers(message.guild, args[0]); + if (users.length > 1) + return message.channel.send( + "<:error:466995152976871434> Found multiple users! Please be more specific or mention the user instead." + ); + else if (users.length == 0) + return message.channel.send( + "<:error:466995152976871434> That user doesn't seem to exist. Try again!" + ); + user2 = users[0]; + }; + + var secondLength = Math.floor(user2.username.length / 2); + + var first = user.username.slice(0, user.username.length / 2) + var second = user2.username.slice(secondLength, user2.username.length / 2) + + message.channel.send(`Your ship name is **${shipname}!**`) +}; + +exports.conf = { + enabled: true, + guildOnly: true, + aliases: [], + permLevel: "User", + requiredPerms: [] +}; + +exports.help = { + name: "ship1", + category: "Fun", + description: "Ship two people together <3", + usage: "ship name name2" +}; + diff --git a/src/commands/userinfo.js b/src/commands/userinfo.js index 8d7008b..ddec46f 100644 --- a/src/commands/userinfo.js +++ b/src/commands/userinfo.js @@ -1,5 +1,5 @@ const Discord = require("discord.js"); - +const coolPeople = require('../../resources/other/coolpeople.json') exports.run = (client, message, args) => { var user; var guild; @@ -13,6 +13,8 @@ exports.run = (client, message, args) => { var tag; var id; var bot; + var coolPerson = false; + var friendos = coolPeople.coolPeople; if(message.guild) { user = message.mentions.members.first(); @@ -39,6 +41,15 @@ exports.run = (client, message, args) => { nick = `\n• **Nickname:** ${user.nickname}`; }; + for (var i = 0; i < friendos.length; i++) { + if (user.user.id == friendos[i]) + coolPerson = true; + }; + + if(coolPerson == true) { + badges += "🌟" + } + if(user.user.id == message.guild.ownerID) { badges += "<:owner:685703193694306331>" } @@ -46,6 +57,7 @@ exports.run = (client, message, args) => { if(user.user.bot) { badges += "<:bot:686489601678114859>" } + if(badges.length > 0) { badges += "\n" diff --git a/src/commands/yoda.js b/src/commands/yoda.js index 293d786..8b61784 100644 --- a/src/commands/yoda.js +++ b/src/commands/yoda.js @@ -1,20 +1,22 @@ const request = require('request') exports.run = async (client, message, args) => { - const speech = args.join(' '); - if (!args[0]) { - return message.channel.send(`<:error:466995152976871434> Please include text for me to convert to yodish. Yes.`) - } - try { - const { text } = request({ uri: `http://yoda-api.appspot.com/api/v1/yodish?text=${encodeURIComponent(speech.toLowerCase())}`, json: true }, (error, response, body) => { - message.channel.send(JSON.parse(text).yodish) - }); - } catch(err) { - message.channel.send(`<:error:466995152976871434> API error: ${err}`); - message.channel.stopTyping(); - } + const speech = args.join(' '); + if (!speech) { + return message.channel.send(`<:error:466995152976871434> Please include text for me to convert to yodish. Yes.`) + }; -} + message.channel.startTyping(); + try{ + request({ uri: `http://yoda-api.appspot.com/api/v1/yodish?text=${encodeURIComponent(speech.toLowerCase())}`, json: true }, (error, response, body) => { + message.channel.send(body.yodish); + message.channel.stopTyping(); + }); + } catch(err) { + message.channel.send(`<:error:466995152976871434> API error: ${err}`); + message.channel.stopTyping(); + }; +}; exports.conf = { enabled: true, diff --git a/src/modules/functions.js b/src/modules/functions.js index 98cf223..bd30579 100644 --- a/src/modules/functions.js +++ b/src/modules/functions.js @@ -368,6 +368,6 @@ module.exports = client => { }); process.on("unhandledRejection", err => { - client.logger.error(`Unhandled rejection: ${err.stack}`); + client.logger.error(`Unhandled rejection: ${err}`); }); }; From a411fb3af49c9c5c2d02a9bf23844c299223bc18 Mon Sep 17 00:00:00 2001 From: mudkipscience Date: Mon, 16 Mar 2020 16:30:43 +1100 Subject: [PATCH 25/78] Update about.js --- src/commands/about.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/about.js b/src/commands/about.js index 5c1538f..ad1666b 100644 --- a/src/commands/about.js +++ b/src/commands/about.js @@ -45,7 +45,7 @@ exports.run = (client, message) => { exports.conf = { enabled: true, guildOnly: false, - aliases: ["stats"], + aliases: ["stats", "botinfo"], permLevel: "User", requiredPerms: [] }; From e5aad749f5be27ce7d515a2474b7b819a75516ac Mon Sep 17 00:00:00 2001 From: FLGX Date: Thu, 19 Mar 2020 10:46:53 +0100 Subject: [PATCH 26/78] make weather command return if an error occured --- src/commands/weather.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/weather.js b/src/commands/weather.js index f5fc362..4af8b86 100644 --- a/src/commands/weather.js +++ b/src/commands/weather.js @@ -13,7 +13,7 @@ exports.run = async (client, message, args, error) => { message.channel.startTyping(); weather.find({search: args.join(" "), degreeType: 'C'}, function(err, result) { - if(err) client.logger.log(`weather.js error: ${JSON.stringify(error)}`, "error") + if(err) return client.logger.log(`weather.js error: ${JSON.stringify(error)}`, "error") if(result.length < 2 || !result) { message.channel.stopTyping(); return message.channel.send("<:error:466995152976871434> City not found!"); From ea52ce2fcf77d92ca6e85f308876f86a27839262 Mon Sep 17 00:00:00 2001 From: FLGX Date: Thu, 19 Mar 2020 11:20:18 +0100 Subject: [PATCH 27/78] woomy mention prefix support --- src/events/message.js | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/events/message.js b/src/events/message.js index 3505c1c..f91dd7e 100644 --- a/src/events/message.js +++ b/src/events/message.js @@ -131,12 +131,20 @@ module.exports = async (client, message) => { }; }; - const prefixMention = new RegExp(`^<@!?${client.user.id}>( |)$`); + //const prefixMention = new RegExp(`^<@!?${client.user.id}>( |)$`); + const myMention = `<@&${client.user.id}>`; + const myMention2 = `<@!${client.user.id}>`; - if (message.content.match(prefixMention)) { - return message.channel.send(`Current prefix: \`${prefix}\``); - } + console.log(message.content); + if (message.content.startsWith(myMention) || message.content.startsWith(myMention2)) { + if(message.content.length > myMention.length + 1 && (message.content.substr(0, myMention.length + 1) == myMention + ' ' || message.content.substr(0, myMention2.length + 1) == myMention2 + ' ')) { + prefix = message.content.substr(0, myMention.length) + ' '; + } else { + return message.channel.send(`Current prefix: \`${prefix}\``); + }; + }; + if (message.content.indexOf(prefix) !== 0) return; const args = message.content.slice(prefix.length).trim().split(/ +/g); From 0360a5c581a4426fadd0f71aa45973e4be498532 Mon Sep 17 00:00:00 2001 From: FLGX Date: Thu, 19 Mar 2020 11:20:45 +0100 Subject: [PATCH 28/78] remove message content printing --- src/events/message.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/events/message.js b/src/events/message.js index f91dd7e..457706b 100644 --- a/src/events/message.js +++ b/src/events/message.js @@ -134,9 +134,7 @@ module.exports = async (client, message) => { //const prefixMention = new RegExp(`^<@!?${client.user.id}>( |)$`); const myMention = `<@&${client.user.id}>`; const myMention2 = `<@!${client.user.id}>`; - - console.log(message.content); - + if (message.content.startsWith(myMention) || message.content.startsWith(myMention2)) { if(message.content.length > myMention.length + 1 && (message.content.substr(0, myMention.length + 1) == myMention + ' ' || message.content.substr(0, myMention2.length + 1) == myMention2 + ' ')) { prefix = message.content.substr(0, myMention.length) + ' '; @@ -144,7 +142,7 @@ module.exports = async (client, message) => { return message.channel.send(`Current prefix: \`${prefix}\``); }; }; - + if (message.content.indexOf(prefix) !== 0) return; const args = message.content.slice(prefix.length).trim().split(/ +/g); From 9853c9032be6272d0f3d78d36495263f6de0cea2 Mon Sep 17 00:00:00 2001 From: mudkipscience Date: Thu, 19 Mar 2020 21:37:34 +1100 Subject: [PATCH 29/78] synced changes --- LICENSE.md | 3 +- changes.txt | 2 +- resources/other/coolpeople.json | 3 +- resources/other/genders.json | 13 ++++++- src/commands/colourify.js | 53 --------------------------- src/commands/help.js | 24 +++++++----- src/commands/ship.js | 4 +- src/commands/ship1.js | 65 --------------------------------- src/commands/userinfo.js | 14 +------ 9 files changed, 35 insertions(+), 146 deletions(-) delete mode 100644 src/commands/colourify.js delete mode 100644 src/commands/ship1.js diff --git a/LICENSE.md b/LICENSE.md index f82dfa5..4e230a1 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,6 +1,7 @@ MIT License -Copyright (c) 2020 mudkipscience +Copyright (c) 2018 YorkAARGH +Copyright (c) 2018-2020 mudkipscience Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/changes.txt b/changes.txt index e91c096..529e5aa 100644 --- a/changes.txt +++ b/changes.txt @@ -4,7 +4,7 @@ Bots now get a bot badge in the userinfo command Added dogfact and catfact command (terry) index.js now has better logging of when things fail to load/initialize (terry) added `dice`, rolls a 6 sided die (terry) -Help command changed, the amount of commands in each category and overall is now displayed +Help command changed, the amount of commands in each category and overall is now displayed and formatting changed added `inspire` as an alias for inspirobot ship command added find by mention to functions \ No newline at end of file diff --git a/resources/other/coolpeople.json b/resources/other/coolpeople.json index f0de195..a2ec0bf 100644 --- a/resources/other/coolpeople.json +++ b/resources/other/coolpeople.json @@ -14,6 +14,7 @@ "264970229514371072", "254310746450690048", "358390849807319040", - "211011138656272386" + "211011138656272386", + "266472557740425216" ] } \ No newline at end of file diff --git a/resources/other/genders.json b/resources/other/genders.json index 3b50663..e09ea2d 100644 --- a/resources/other/genders.json +++ b/resources/other/genders.json @@ -1,4 +1,15 @@ { + "agender": "A gender identity used by someone who has no gender, or sometimes by someone whose gender is neutral", + "aporagender": "A gender that is not male, female, or anything on the spectrum between male and female, but that still has a distinct gendered feeling.", + "bigender": "Bigender individuals have two gender identities, either simultaneously or varying between the two.", "cisgender": "Someone who identifies with their assigned gender at birth.", - "transgender": "Someone who identifies with a gender that is not their assigned gender at birth." + "demiboy": "Someone who identifies as partially male and partially another gender.", + "demiflux": "A gender where one part of someone’s gender is static, and the other part fluctuates in intensity.", + "demigender": "Someone who identifies as partially one gender, and partially another.", + "demigirl": "Someone who identifies as partially female and partially another gender.", + "enby": "Shortened term for “nonbinary”. Used as a noun, like “boy” or “girl” but for nonbinary people.", + "genderfluid": "Someone whos gender varies over time. This might be fluctuating between different genders, or expressing multiple aspects of various genders at the same time.", + "genderflux": "Someone whose gender fluctuates, usually between agender and something else.", + "" + "transgender": "Someone whos gender is different from what they were assigned at birth." } \ No newline at end of file diff --git a/src/commands/colourify.js b/src/commands/colourify.js deleted file mode 100644 index f13720d..0000000 --- a/src/commands/colourify.js +++ /dev/null @@ -1,53 +0,0 @@ -const isURL = require("is-url") -exports.run = async (client, message, args) => { - var img; - if(!args[0]) { - if(!message.attachments.first()) { - return message.channel.send("No attachment") - }; - img = message.attachments.first().attachment; - } else { - if(isURL(args[0]) == true ) { - img = args[0]; - } else { - user = message.mentions.members.first(); - - if (!user) { - let users; - users = client.searchForMembers(message.guild, args[0]); - if (users.length > 1) - return message.channel.send( - "<:error:466995152976871434> Found multiple users! Please be more specific or mention the user instead." - ); - else if (users.length == 0) - return message.channel.send( - "<:error:466995152976871434> That user doesn't seem to exist. Try again!" - ); - user = users[0]; - }; - - if(user) { - img = user.user.avatarURL({format: "png", dynamic: true}) - } - } - }; - - var attachment = new Discord.MessageAttachment(`https://api.alexflipnote.dev/filter/magik?image=${img}`) - message.channel.send(attachment) -}; - - exports.conf = { - enabled: true, - guildOnly: false, - aliases: [], - permLevel: "User", - requiredPerms: [] - }; - - exports.help = { - name: "magikt", - category: "Fun", - description: "Colourifies an image", - usage: "colourify user hex1 hex2" - }; - \ No newline at end of file diff --git a/src/commands/help.js b/src/commands/help.js index beeda6d..0dd8acf 100644 --- a/src/commands/help.js +++ b/src/commands/help.js @@ -3,7 +3,7 @@ exports.run = (client, message, args, level) => { embed.setColor(client.embedColour(message)); var ran = false; - var output = ""; + var output = "```"; var commands = 0; var prefix; var currentCategory; @@ -38,18 +38,22 @@ exports.run = (client, message, args, level) => { const cat = c.help.category.toProperCase(); if (currentCategory !== cat) { if(ran == true) { - embed.addField(currentCategory + ` [${commands}]`, output.slice(0, -2)) - output = ""; + output = output.slice(0, -2) + "```"; + embed.addField(currentCategory + ` [${commands}]`, output) + output = "```"; commands = 0; } currentCategory = cat; ran = true } - output += `\`${prefix}${c.help.name}\`, `; + output += `${prefix}${c.help.name}, `; commands = commands + 1; }); - embed.addField(currentCategory + ` [${commands}]`, output.slice(0, -2)); + output = output.slice(0, -2); + output = output + "```" + + embed.addField(currentCategory + ` [${commands}]`, output); embed.addField( "Invite me", @@ -87,18 +91,20 @@ exports.run = (client, message, args, level) => { const cat = c.help.category.toProperCase(); if (currentCategory !== cat) { if(ran == true) { - embed.addField(currentCategory + ` [${commands}]`, output.slice(0, -2)) - output = ""; + output = output.slice(0, -2) + "```"; + embed.addField(currentCategory + ` [${commands}]`, output) + output = "```"; commands = 0; } currentCategory = cat; ran = true } - output += `\`${prefix}${c.help.name}\`, `; + output += `${prefix}${c.help.name}, `; commands = commands + 1; }); - embed.addField(currentCategory + ` [${commands}]`, output.slice(0, -2)); + output = output.slice(0, -2) + "```"; + embed.addField(currentCategory + ` [${commands}]`, output); embed.addField( "Invite me", diff --git a/src/commands/ship.js b/src/commands/ship.js index 2ef0477..6264131 100644 --- a/src/commands/ship.js +++ b/src/commands/ship.js @@ -39,11 +39,9 @@ exports.run = async (client, message, args) => { ); user2 = users[0]; }; - - var secondLength = Math.floor(user2.username.length / 2); var first = user.username.slice(0, user.username.length / 2) - var second = user2.username.slice(secondLength, user2.username.length / 2) + var second = user2.username.slice(Math.floor(user2.username.length / 2), user2.username.length) try { var attachment = new Discord.MessageAttachment(`https://api.alexflipnote.dev/ship?user=${user.avatarURL({format: "png"})}&user2=${user2.avatarURL({format: "png"})}`) diff --git a/src/commands/ship1.js b/src/commands/ship1.js deleted file mode 100644 index ca96edd..0000000 --- a/src/commands/ship1.js +++ /dev/null @@ -1,65 +0,0 @@ -const request = require('request') -exports.run = async (client, message, args) => { - - - //NOT FINISHED - - if(!args[0] || !args[1]) { - return message.channel.send(`<:error:466995152976871434> Please include two to five users`) - } - message.channel.startTyping(); - - var user = client.getUserFromMention(args[0]) - var user2 = client.getUserFromMention(args[1]) - - if (!user) { - let users; - users = client.searchForMembers(message.guild, args[0]); - if (users.length > 1) - return message.channel.send( - "<:error:466995152976871434> Found multiple users! Please be more specific or mention the user instead." - ); - else if (users.length == 0) - return message.channel.send( - "<:error:466995152976871434> That user doesn't seem to exist. Try again!" - ); - user = users[0]; - }; - - if (!user2) { - let users; - users = client.searchForMembers(message.guild, args[0]); - if (users.length > 1) - return message.channel.send( - "<:error:466995152976871434> Found multiple users! Please be more specific or mention the user instead." - ); - else if (users.length == 0) - return message.channel.send( - "<:error:466995152976871434> That user doesn't seem to exist. Try again!" - ); - user2 = users[0]; - }; - - var secondLength = Math.floor(user2.username.length / 2); - - var first = user.username.slice(0, user.username.length / 2) - var second = user2.username.slice(secondLength, user2.username.length / 2) - - message.channel.send(`Your ship name is **${shipname}!**`) -}; - -exports.conf = { - enabled: true, - guildOnly: true, - aliases: [], - permLevel: "User", - requiredPerms: [] -}; - -exports.help = { - name: "ship1", - category: "Fun", - description: "Ship two people together <3", - usage: "ship name name2" -}; - diff --git a/src/commands/userinfo.js b/src/commands/userinfo.js index ddec46f..c5dcbac 100644 --- a/src/commands/userinfo.js +++ b/src/commands/userinfo.js @@ -1,18 +1,8 @@ const Discord = require("discord.js"); const coolPeople = require('../../resources/other/coolpeople.json') exports.run = (client, message, args) => { - var user; - var guild; - var nick = ""; - var roles = ""; - var presence = ""; - var badges = ""; - var status; - var createdAt; - var avurl; - var tag; - var id; - var bot; + var user, guild, status, createdAt, avurl, tag, id; + var nick, roles, presence, badges = ""; var coolPerson = false; var friendos = coolPeople.coolPeople; From 4948a5d1f5c8ed5bf9982a0297331312fad10346 Mon Sep 17 00:00:00 2001 From: FLGX Date: Thu, 19 Mar 2020 11:46:10 +0100 Subject: [PATCH 30/78] fix ship command --- src/commands/ship.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/commands/ship.js b/src/commands/ship.js index 2ef0477..a3207e2 100644 --- a/src/commands/ship.js +++ b/src/commands/ship.js @@ -23,12 +23,12 @@ exports.run = async (client, message, args) => { return message.channel.send( "<:error:466995152976871434> That user doesn't seem to exist. Try again!" ); - user = users[0]; + user = users[0].user; }; if (!user2) { let users; - users = client.searchForMembers(message.guild, args[0]); + users = client.searchForMembers(message.guild, args[1]); if (users.length > 1) return message.channel.send( "<:error:466995152976871434> Found multiple users! Please be more specific or mention the user instead." @@ -37,13 +37,13 @@ exports.run = async (client, message, args) => { return message.channel.send( "<:error:466995152976871434> That user doesn't seem to exist. Try again!" ); - user2 = users[0]; + user2 = users[0].user; }; var secondLength = Math.floor(user2.username.length / 2); - var first = user.username.slice(0, user.username.length / 2) - var second = user2.username.slice(secondLength, user2.username.length / 2) + var first = user.username.slice(0, secondLength - 1) + var second = user2.username.slice(secondLength) try { var attachment = new Discord.MessageAttachment(`https://api.alexflipnote.dev/ship?user=${user.avatarURL({format: "png"})}&user2=${user2.avatarURL({format: "png"})}`) From f87c2834665f5f836ecbbdb5fd7645d95499f4f5 Mon Sep 17 00:00:00 2001 From: FLGX Date: Thu, 19 Mar 2020 11:50:30 +0100 Subject: [PATCH 31/78] a --- src/commands/ship1.js | 70 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 src/commands/ship1.js diff --git a/src/commands/ship1.js b/src/commands/ship1.js new file mode 100644 index 0000000..39d2621 --- /dev/null +++ b/src/commands/ship1.js @@ -0,0 +1,70 @@ +const request = require('request') +exports.run = async (client, message, args) => { + + + //NOT FINISHED + + if(!args[0] || !args[1]) { + return message.channel.send(`<:error:466995152976871434> Please include two users`) + } + message.channel.startTyping(); + + var user = client.getUserFromMention(args[0]) + var user2 = client.getUserFromMention(args[1]) + + if (!user) { + let users; + users = client.searchForMembers(message.guild, args[0]); + if (users.length > 1) + return message.channel.send( + "<:error:466995152976871434> Found multiple users! Please be more specific or mention the user instead." + ); + else if (users.length == 0) + return message.channel.send( + "<:error:466995152976871434> That user doesn't seem to exist. Try again!" + ); + user = users[0].user; + }; + + if (!user2) { + let users; + users = client.searchForMembers(message.guild, args[1]); + if (users.length > 1) + return message.channel.send( + "<:error:466995152976871434> Found multiple users! Please be more specific or mention the user instead." + ); + else if (users.length == 0) + return message.channel.send( + "<:error:466995152976871434> That user doesn't seem to exist. Try again!" + ); + user2 = users[0].user; + }; + + var first = user.username.slice(0, secondLength - 1) + var second = user2.username.slice(secondLength) + + try { + var attachment = new Discord.MessageAttachment(`https://api.alexflipnote.dev/ship?user=${user.avatarURL({format: "png"})}&user2=${user2.avatarURL({format: "png"})}`) + message.channel.send(`Your ship name is **${first+second}!**`, attachment) + message.channel.stopTyping(); + } catch(err) { + message.channel.send(`<:error:466995152976871434> API error: ${err}`); + message.channel.stopTyping(); + }; +}; + +exports.conf = { + enabled: true, + guildOnly: true, + aliases: [], + permLevel: "User", + requiredPerms: [] +}; + +exports.help = { + name: "ship1", + category: "Fun", + description: "Ship two people together <3", + usage: "ship1 name name2" +}; + From 8a1f87b993f8e99a6cf9872e32152e06b48a9afd Mon Sep 17 00:00:00 2001 From: mudkipscience Date: Fri, 20 Mar 2020 12:08:52 +1100 Subject: [PATCH 32/78] Renamed ~gender to ~identity --- resources/other/genders.json | 15 ----- resources/other/identities.json | 110 ++++++++++++++++++++++++++++++++ src/commands/identity.js | 31 +++++++++ 3 files changed, 141 insertions(+), 15 deletions(-) delete mode 100644 resources/other/genders.json create mode 100644 resources/other/identities.json create mode 100644 src/commands/identity.js diff --git a/resources/other/genders.json b/resources/other/genders.json deleted file mode 100644 index e09ea2d..0000000 --- a/resources/other/genders.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "agender": "A gender identity used by someone who has no gender, or sometimes by someone whose gender is neutral", - "aporagender": "A gender that is not male, female, or anything on the spectrum between male and female, but that still has a distinct gendered feeling.", - "bigender": "Bigender individuals have two gender identities, either simultaneously or varying between the two.", - "cisgender": "Someone who identifies with their assigned gender at birth.", - "demiboy": "Someone who identifies as partially male and partially another gender.", - "demiflux": "A gender where one part of someone’s gender is static, and the other part fluctuates in intensity.", - "demigender": "Someone who identifies as partially one gender, and partially another.", - "demigirl": "Someone who identifies as partially female and partially another gender.", - "enby": "Shortened term for “nonbinary”. Used as a noun, like “boy” or “girl” but for nonbinary people.", - "genderfluid": "Someone whos gender varies over time. This might be fluctuating between different genders, or expressing multiple aspects of various genders at the same time.", - "genderflux": "Someone whose gender fluctuates, usually between agender and something else.", - "" - "transgender": "Someone whos gender is different from what they were assigned at birth." -} \ No newline at end of file diff --git a/resources/other/identities.json b/resources/other/identities.json new file mode 100644 index 0000000..c37d5d9 --- /dev/null +++ b/resources/other/identities.json @@ -0,0 +1,110 @@ +{ + "agender": { + "name": "agender", + "description": "A gender identity used by someone who has no gender, or sometimes by someone whose gender is neutral." + }, + "androgyne": { + "name": "androgyne", + "description": "A gender identity associated with androgyny. Androgynes have a gender which is simultaneously feminine and masculine, although not necessarily in equal amounts." + }, + "androgynous": { + "name": "androgynous", + "description": "A term used to refer to people who have both feminine and masculine characteristics." + }, + "aporagender": { + "name": "aporagender", + "description": "A gender that is not male, female, or anything in between that still has a distinct gendered feeling." + }, + "bigender": { + "name": "bigender", + "description": "Having two gender identities, either simultaneously or varying between the two." + }, + "cisgender": { + "name": "cisgender", + "description": "Someone who identifies with their assigned gender at birth." + }, + "demiboy": { + "name": "demiboy", + "description": "Someone who identifies as partially male and partially another gender." + }, + "demiflux": { + "name": "demiflux", + "description": "A gender where one part of someone’s gender is static, and the other part fluctuates in intensity." + }, + "demigender": { + "name": "demigender", + "description": "Someone who identifies as partially one gender, and partially another." + }, + "demigirl": { + "name": "demigirl", + "description": "Someone who identifies as partially female and partially another gender." + }, + "dyadic": { + "name": "dyadic", + "description": "A word used to refer to people who are not intersex" + }, + "enby": { + "name": "enby", + "description": "Shortened term for “nonbinary”. Used as a noun, like “boy” or “girl” but for nonbinary people." + }, + "fluidflux": { + "name": "fluidflux", + "description": "A gender identity which refers to someone with a gender that moves between two or more genders and also fluctuates in intensity." + }, + "genderfluid": { + "name": "genderfluid", + "description": "Someone whose gender varies over time. This might be fluctuating between different genders, or expressing multiple aspects of various genders at the same time." + }, + "genderflux": { + "name": "genderflux", + "description": "Someone whose gender fluctuates, usually between agender and something else." + }, + "genderqueer": { + "name": "genderqueer", + "description": "An umbrella term for all the nonbinary genders. Genderqueer can be a standalone identity, or can refer to a more specific gender identity." + }, + "gendervoid": { + "name": "gendervoid", + "description": "Someone who does not experience gender, or who feels an absence or void in the place of gender." + }, + "intersex": { + "name": "intersex", + "description": "An intersex person is someone with sex characteristics (sexual anatomy, reproductive organs, chromosomal patterns, etc.) that do not align with the typical descriptions of male and female." + }, + "libragender": { + "name": "libragender", + "description": "A gender identity that is mostly agender, but has a connection to masculinity and/or femininity and/or other gendered feelings. That connection may be static (libragender, librafeminine, libramasculine, etc) or fluid, where one feels that the gender one experiences changes (librafluid)." + }, + "neutrois": { + "name": "neutrois", + "description": "Having a null or neutral gender." + }, + "nonbinary": { + "name": "nonbinary", + "description": "An umbrella term for all the gender identities that aren't male or female (the binary genders)" + }, + "non-gendered": { + "name": "non-gendered", + "description": "Having no gender." + }, + "polygender": { + "name": "polygender", + "description": "Having more than one gender, either at the same time or at different times. A polygender person may identify with any combination of binary and nonbinary genders." + }, + "pangender": { + "name": "pangender", + "description": "Having more than one gender, especially someone who identifies as all genders." + }, + "transfeminine": { + "name": "transfeminine", + "description": "A transgender person who identifies with femininity, either as a binary female or a fem-leaning nonbinary identity." + }, + "transgender": { + "name": "transgender", + "description": "An umbrella term that refers to people whose identity differs from their assigned gender at birth." + }, + "transmasculine": { + "name": "transmasculine", + "description": "A transgender person who identifies with masculinity, either as a binary male or a masc-leaning nonbinary identity." + } +} \ No newline at end of file diff --git a/src/commands/identity.js b/src/commands/identity.js new file mode 100644 index 0000000..9be25a4 --- /dev/null +++ b/src/commands/identity.js @@ -0,0 +1,31 @@ +const identities = require ("../../resources/other/identities.json"); +exports.run = async (client, message, args) => { + var output = ""; + if(!args[0]) { + for (var key of Object.keys(identities)) { + output += `${key}, ` + }; + return message.channel.send(`__**Identities**__\n${output.slice(0, -2)}`); + } else { + output = identities[args[0].toLowerCase()]; + if(!output) { + return message.channel.send("<:error:466995152976871434> No results for that query."); + }; + return message.channel.send(`__**${output.name}**__\n${output.description}`); + }; +}; + +exports.conf = { + enabled: true, + guildOnly: false, + aliases: [], + permLevel: "User", + requiredPerms: [] +}; + +exports.help = { + name: "identity", + category: "Fun", + description: "Gives you information about the specified identity.", + usage: "identity [identity]" +}; From 29e8198126ed6c82ef6881604c7015b582e13296 Mon Sep 17 00:00:00 2001 From: FLGX Date: Sat, 21 Mar 2020 10:46:51 +0100 Subject: [PATCH 33/78] aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa --- src/commands/ship.js | 80 ++++++++++++++++++++++++++----------------- src/commands/ship1.js | 70 ------------------------------------- 2 files changed, 48 insertions(+), 102 deletions(-) delete mode 100644 src/commands/ship1.js diff --git a/src/commands/ship.js b/src/commands/ship.js index c10855f..1fe3c57 100644 --- a/src/commands/ship.js +++ b/src/commands/ship.js @@ -9,43 +9,59 @@ exports.run = async (client, message, args) => { } message.channel.startTyping(); - var user = client.getUserFromMention(args[0]) - var user2 = client.getUserFromMention(args[1]) + let users = []; + let totalLength = 0; - if (!user) { - let users; - users = client.searchForMembers(message.guild, args[0]); - if (users.length > 1) - return message.channel.send( - "<:error:466995152976871434> Found multiple users! Please be more specific or mention the user instead." - ); - else if (users.length == 0) - return message.channel.send( - "<:error:466995152976871434> That user doesn't seem to exist. Try again!" - ); - user = users[0].user; + for(let i = 0; i < args.length; i++) { + let arg = args[i]; + + let user = client.getUserFromMention(arg); + + if(!user) { + let usersFound; + usersFound = client.searchForMembers(message.guild, arg); + if (usersFound.length > 1) + return message.channel.send( + "<:error:466995152976871434> Found multiple users! Please be more specific or mention the user instead." + ); + else if (usersFound.length == 0) + return message.channel.send( + "<:error:466995152976871434> That user doesn't seem to exist. Try again!" + ); + user = usersFound[0].user; + } + + users.push(user); + totalLength += user.username.length; + } + + let lengthPerUser = Math.floor(totalLength / users.length); + + let finalName = ''; + + let last = -1; + + for(let i = 0; i < users.length; i++) { + let user = users[i]; + let l = Math.min(lengthPerUser, user.username.length); + + let p = user.username.substr(last + 1, last + l); + + console.log(p); + + finalName = finalName + p; + + last = last + l; }; - if (!user2) { - let users; - users = client.searchForMembers(message.guild, args[1]); - if (users.length > 1) - return message.channel.send( - "<:error:466995152976871434> Found multiple users! Please be more specific or mention the user instead." - ); - else if (users.length == 0) - return message.channel.send( - "<:error:466995152976871434> That user doesn't seem to exist. Try again!" - ); - user2 = users[0].user; - }; - - var first = user.username.slice(0, user.username.length / 2) - var second = user2.username.slice(Math.floor(user2.username.length / 2), user2.username.length) + console.log(totalLength); + console.log(users.length); + console.log(lengthPerUser); + console.log(finalName); try { - var attachment = new Discord.MessageAttachment(`https://api.alexflipnote.dev/ship?user=${user.avatarURL({format: "png"})}&user2=${user2.avatarURL({format: "png"})}`) - message.channel.send(`Your ship name is **${first+second}!**`, attachment) + //var attachment = new Discord.MessageAttachment(`https://api.alexflipnote.dev/ship?user=${user.avatarURL({format: "png"})}&user2=${user2.avatarURL({format: "png"})}`) + message.channel.send(`Your ship name is **${finalName}!**`) message.channel.stopTyping(); } catch(err) { message.channel.send(`<:error:466995152976871434> API error: ${err}`); diff --git a/src/commands/ship1.js b/src/commands/ship1.js deleted file mode 100644 index 39d2621..0000000 --- a/src/commands/ship1.js +++ /dev/null @@ -1,70 +0,0 @@ -const request = require('request') -exports.run = async (client, message, args) => { - - - //NOT FINISHED - - if(!args[0] || !args[1]) { - return message.channel.send(`<:error:466995152976871434> Please include two users`) - } - message.channel.startTyping(); - - var user = client.getUserFromMention(args[0]) - var user2 = client.getUserFromMention(args[1]) - - if (!user) { - let users; - users = client.searchForMembers(message.guild, args[0]); - if (users.length > 1) - return message.channel.send( - "<:error:466995152976871434> Found multiple users! Please be more specific or mention the user instead." - ); - else if (users.length == 0) - return message.channel.send( - "<:error:466995152976871434> That user doesn't seem to exist. Try again!" - ); - user = users[0].user; - }; - - if (!user2) { - let users; - users = client.searchForMembers(message.guild, args[1]); - if (users.length > 1) - return message.channel.send( - "<:error:466995152976871434> Found multiple users! Please be more specific or mention the user instead." - ); - else if (users.length == 0) - return message.channel.send( - "<:error:466995152976871434> That user doesn't seem to exist. Try again!" - ); - user2 = users[0].user; - }; - - var first = user.username.slice(0, secondLength - 1) - var second = user2.username.slice(secondLength) - - try { - var attachment = new Discord.MessageAttachment(`https://api.alexflipnote.dev/ship?user=${user.avatarURL({format: "png"})}&user2=${user2.avatarURL({format: "png"})}`) - message.channel.send(`Your ship name is **${first+second}!**`, attachment) - message.channel.stopTyping(); - } catch(err) { - message.channel.send(`<:error:466995152976871434> API error: ${err}`); - message.channel.stopTyping(); - }; -}; - -exports.conf = { - enabled: true, - guildOnly: true, - aliases: [], - permLevel: "User", - requiredPerms: [] -}; - -exports.help = { - name: "ship1", - category: "Fun", - description: "Ship two people together <3", - usage: "ship1 name name2" -}; - From 282c61261dfa288d2d1d91997e149b42f720c74b Mon Sep 17 00:00:00 2001 From: mudkipscience Date: Sun, 22 Mar 2020 22:13:41 +1100 Subject: [PATCH 34/78] Synced changes --- changes.txt | 5 +- package.json | 2 +- resources/images/attackhelicopter.jpg | Bin 0 -> 101789 bytes resources/other/coolpeople.json | 3 +- resources/other/identities.json | 2 +- resources/other/pronouns.json | 153 ++++++++++++++++++++++++- resources/other/sexualities.json | 101 +++++++++++++++- src/commands/{math.js => calculate.js} | 6 +- src/commands/emoji.js | 1 - src/commands/identity.js | 11 +- src/commands/pronoun.js | 36 ++++++ src/commands/sexuality.js | 36 ++++++ src/commands/ship.js | 64 ++++------- 13 files changed, 367 insertions(+), 53 deletions(-) create mode 100644 resources/images/attackhelicopter.jpg rename src/commands/{math.js => calculate.js} (92%) create mode 100644 src/commands/pronoun.js create mode 100644 src/commands/sexuality.js diff --git a/changes.txt b/changes.txt index 529e5aa..2648987 100644 --- a/changes.txt +++ b/changes.txt @@ -7,4 +7,7 @@ added `dice`, rolls a 6 sided die (terry) Help command changed, the amount of commands in each category and overall is now displayed and formatting changed added `inspire` as an alias for inspirobot ship command -added find by mention to functions \ No newline at end of file +added find by mention to functions +you can now @mention the bot to run commands +added identity command, has definitions of gender identities and stuff +renamed math calculate diff --git a/package.json b/package.json index 1c03415..901ffbf 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "better-sqlite3": "^5.4.1", "chalk": "^3.0.0", "dblapi.js": "^2.3.1", - "discord.js": "^12.0.1", + "discord.js": "^12.0.2", "enmap": "^5.2.4", "garfield": "^1.1.2", "get-youtube-id": "^1.0.1", diff --git a/resources/images/attackhelicopter.jpg b/resources/images/attackhelicopter.jpg new file mode 100644 index 0000000000000000000000000000000000000000..c077f0d0f97251402063f0982e20d381590fc427 GIT binary patch literal 101789 zcmb5V1yo$kvIaUxkPsLMu7eYTyF&=UEkJ?=4Fq?0LP&5J26vKRA-F?ucXxuj!{9c< z8aOaqs_$p+mjSp6GV(G2BqRU;3GoNG{|%4=V4$I) zqdmevM@PrR#CZIK0Q(6R))UgF_&@?mG7uFd83hG3Jv$RMEh`-b#WMkBR!%NnUS1HB zkeDF1C_4`?_d_E{n3$MPu%3`$W0P>xP|$Gy|Nh*!0Prx74^c}|kZ1wOct|LCNcSB8 z3dA^3Q4j1RaCBuIZg|LGMm4n#kQ5_&uY^N>*hC=V3?C=WG|knx_f1xfgL^ zigMXc9;!!kL*&oGUi2ZVGNU{^_aE{^!b9N%qHy+q(En4N@b9Ax+3Ki8}g7iaw+3K(H^HG=y z%YA9%o*=O?sWT(fu+c)4vei8>5U)^~E|-m+t?q@(9sSZ3F_an=i}T>B=Q3cOCVt!( z$5#^1CPH1|YFS^QPN_M-_N!0B(%%WHd5rn;997A*ylShNX{2Vd7R}9&jJr68b&c>8ec}bK1Fcgf> z>u~{9lMD|`e*G}Cm##D@>Y6)esd7&t@|FYv%$N^f{0|ahCP;w%p$dl~dVhx5LR+&# zxSsj$d%{ScwA~-UA{Zm28TkA`a*|;Ue+4Ls`5*Q@Fb?+*`w0Frk;PQ~feU}c@<$+l zr3`se^zg7WPy8PN7mfZSPntht3Hz%wAs^^-VqzY6AzmX*`-eRUb^5n9A?Pfoe-!M$ zD9qxqmdFU+dH%z^2R{Cz3+l`$mIUeYJzQhVf0QPBSK5;`XiVV`$8}W@d>>;LhRBi2 zx6sB$^F!o&nGq_h@)AK6!XW#5gJAreG(zkIbALoj5Qlx#0?J24$FZAVTYCnQyq(y1 z=0`>hPVzCom1tDX2ZviCBr5;zc`t54jY2?vR~p~Jm~s#9i%4t$ok!?0%CHFChCuU<70FITsbrH zgq*pjkRC53YdDO_#HgDJX zk4Pz}NVC~@rT8EB_*ddxjjO^*fft|HQ+dNqbeki2?*S_g_W)bLU2V34{?CRdpBPgK z!v3p3=vM;PJlTA$yhK07FB^4cmb9*l^dY1srtcHN832h5#KV?&RdP864=ob|FUIOM zT1=$U6EQ50HW6JbBMSDE;FRbW<5`oO3?8PQ(RJOmLWE^<*{ix!r%zFQ6vo|3a;rKh zW{^Prl8A=#ueScwpPC@WU9ZU?)uD!3R`TjfGl}hoemQcmmQ~jbnCAUzvDNB7ELktl zZ0RR9)*Rdl%gF>u09mKRm7G9mb=U|_izifGUwI5|WHJgaEN@W{%F$T)361*)OTuzu zC>7CZ?jRptU3#EX5C3afPGCNDbacz46&wysS43Y+7r3$k%}FWuNwZm0iDG7uQt7cf z9^$-nL8zIw`A|ovJL@%Gmk-58Fmx#Ojvsiyx0F=+zV6m+m^J#E67WG+EQOW!)uML9)j%A~#DnV^ z?|FjIucD+H%?e<{MDOI&dnurGi%9)~SJf3R(o993 z&QKb6)?}`##Yg>06*SgNPZxn0ebX)-1~V9>8LN zmf(uZ$I8Pc@jTo~b?Ee+@`Q60uHQ^tNX#Xig;5F{$FIFhzZzP1D;4+6sOUoEHuu%NP@GFSJ@a|?qB7u z7QHg`xOs}V{}$b9#J}0Rlh^8{YBIwuMcTKVu9&f0N(D`<&2O7!k*2`&_IV9R<MG z*|fBxT^Z;WdgS;{&FZD3Z4B&-kd)$E0=2hOo3Yp3s}%ygaL83b<(N~9dWfAdR3|mT z$1iS<-tzgFof=dpNcroaTz1z0ktCg}7ZL*#GSEvGv2^{l&kv^h<-hE;R?J`9{ekJP z1-Fa){9wX;sUM**BWyQ7P5n)nqQ0797CnN1J{hwuYUD_@nF6MvOmj_?t z!LP9;i2JwG()1tS-ZguCGziwhV968=KGWk^hzPwWo)62i%;?5%j1+V0o>W zpWArSVetqV!{FnzN4UaDk?7{`EM!g61#9R~9N_*#3}p2mN|gyA>8khTf? zh2h(H&(siK3aC38D9&_MaYg`^MLIu7HMd;}B-xbD>J4SSDcmoVgD?F?j<~Rbx2WHStF@EWNpgb6qyMp z%(?>!cAu3t>lI4`YF(EY!c#bI^yGO1)#a0y_CR#@f+mTE3WnA!?umcoUYtMB(+-Wpz_)*UAy>TUcB{M zDoLJesgOggS+a!YY!StNiwP(uX9Ln)&6xsJoZsfgFMD7>=ZUivlL(ba$TMN{D{)6~ z6!Bi1?9&_S)hMFT>`7-*)Fde0@Jw@3%}@hz7L3xdkixbdcN3XB5f^X0`-UBQL1;eQ z8mQiqiNz3bJkZu%dHE@@J!Vg*j?6|I2hznam{Fg};57zAgSr*unrxLNjdHu8SH<+I z0M1JDYVAih-x$O*bqq$N(>=NeJbQ{)8QJ`{`FgpLeu^*hi3QTv*C=)9@p)&Mok=6| zqGQY4f^&LX!nPmZU^7W&>a4Qb5|hadj1SI~t>4tF?d$&@Z_dUK2Hz5mo8{+F%-S0- z6v04V>r@fVKE)t-Z3i*OvHNPtiU;WT^PH|GFrOXDHC+|ySI-`%*fQ5i4s&*tD|z_V zcYmR6Ab?g8Us zUINE{umjoiUcXLTxy`43Kp!;%RmiLd!BmW|(~o%$)+w!9OYoC5EwSHxb=>$+W-*RX1L28<-_m&3lmp5(XHUC4)3Nh&GN zxn9q@k8jxo(EK>o(tHF19Q%5(FZeHSh%1jBX;tlbFax6C@l$Hk*%~R}I(L zBvy~=oMHYiCu)IVIA|JkPBa@H-s09NS~bKNceJ$@ih+S0oE6ibFYxge$47qr>~}_; zQ|+fa3QzO4bY7$!am5i><=?p)=lq~NaA>L0=l`VwuF)=(RP0`YLMe4k&z32<)MN)b z1)Z~PxaYoqcSm_uaAkmg(UgQ%$q2>*FdhWrQdA17K8|5j|4${Y6Oo?4O5U%woH*n5!mf~PM01?CGyl{-j$xW0a{sw=^c zB6^DMoj#8o*h697D%1c>MPv6f$(eOFpz2H{ywG z*{n0VAgR8jOWMjkAaDI1@aic4h1V0%1?yvjJ4#z@Cr8 z`2Fg)Xv6`DvG0CkWkdg!jDN1K|GDaI0-5t6{tBg5l&enZ*=5&$5KpCJ@_G5Me=n5@ z)60ACR5OIxlF&Wd1+pN_*cQpvieH`OmKXL~1V%*Js+1t*$N*Lo*2FbgQ+ha8Fr5e$ zGrDHei^ns8j8A^t$}yNmKurp%p*)+bRK)%J&NRK<4kDc*%7i?Loi>9jI}3RG)LWX-=19(afZha^dFlv|xcx~?-HcwLrQ==Ar%lNFL4IdW zU0-*_*^eso(~7@!Zs1V|BG0f@abxi>4&E(mMO3Z=iPj*sLV@r~;tM$N&gg<~W#-L_ zfBo|6D)Fjc^{W0*_DTrK0Zqt+y3aQXR%K6cu0)sX z{m_O^7JHylpR(I8!#cDDNlBfoNFGJM+Mz&ljUM&GN*G9pT+I4d^cDxlO8Z5ITw8{8`JrIuLqj>ir?@s^n z+0hG$CR6`OhFju-*Ph`2`+e{-JG}VgAv5N?Nws4TpO_vpODp(bt;yA`D$M(}=hB-q z^bTRo#Mn&!Z=bfIYutjozj~-Pu-d4?TrMC%?sZu)tb`lLE*n=ZtqNQ6#=|(6uO8+gWvAO z(>q6C)YFL8qhO8 zyQ5H5JAu?D79X9tN01MG0at~CW~#RpP>sQ^Qwc&y?V|Z=VbF-J*^7ICrXLH>tF=>L zr*a73NJ4f#gN}-6`!;^e7H3pon=rod$6YtGxbD$S%&3;!2%6!DQ{S}W4bBx?io*AU zfMUa(+f&Bng4cs7wgx=D*R!KfsstztE}52UrGyc~az`??1q%u{)?fY7Zcb zO&Fqzf&6*7kQ#1-io}h*#0_mZ97kfuE)S*wUrz2=y9eY}AhrU`|7#z?RJ67>w%043 z=I<-oyq#rGZ6JTPcDu8250EBnSL9EVIGeZMPCaK!iS(wRn8reNFTsW~nv%}#pRSgc)o9(kaxX60 zz8%l1v=ygh3HcV|K}%7#-os_SDOT<1$$@i0a3Y8vL-X;^gdO-TGbR?jX5P2 z&MF;L=bOzccbK8yKbY*@*eC}*$#YW0mqu$o3$A(;cx-hK;M+s^mD2YB#Y8tfrvF8d z+$v@{BkT~~BG?tq6}Q7O?oI0HY8)yRy?A{OSl;Obb&bm~OCjQ76R$hx(k`Bns_u7D5(C+Ur!;i;Y&U_SUIyw;M(C<&>(MlSg9a65i z?UA|3C~o9o*%{r+ z)=*nyvirs#0JXCRIC`5#H<$=5B)nnDN?kiNXlok?eBBnzK_=6SIsi18bE7UHSX(uN zHWSN6h7u0OQ(M0P@ij4clLzsVc$f!sRz|oB?RQ7;)^+uYZLOB)dl9?-%D;KJCTjP1 zCA1>jK}eM%Y1F;$9xz{2kl{*)?O#Z`|m|_56K-ZXU?{V?sX_SuJP*D<`h)Mk#X3vkp{Q!)!HzH9 zB2pp)2QGtUJ|Qqi#S0d7VLS6{f_uPd{jLGQFl5x4G|HIzVhjs3#IoR-xh@2HD0n&{ z%U_g$wmx(NA63&2KXzX^ir-8AwO&{Y)tQx1-KoD#%yMG1+qWdo0uwfQ5}pkAck2u6R{SY$ekesR znnqkOn$O13Z}R3E1sl4r5SpEvBrz#ILE>TRe|AWGN)Wepr)#(g%70ra{%p+QOi$1A z1nWa&+)9t;S%cwsVdBaU~R z=CVQsI#w0JUc;{@p{@rYVvY7_@h3s(m4V~E*1PjOIeVU0q{&xpI;ExuG)%SE#gDeh zqg0X${JNa8C71@>uNvVt8C2&xZ+pw10*;Y9oyYB|sA#ACpBl-jpyBUy=2_CiH>Q2L z9P4Wpzv$yI7v3$Kgup)@Ih}$rEQTh|(alXiZGU%-IT~^*x6!#$eMM%L!5H7XhL7cb zBKxT{=W9ZuCGY0s5a`BF^-Sd^-h0ISC^-~H4$pZ%{BN53KeULs&b<$thHX4BZUj*N z(|^8^_rF~5ME;07O8;@2rQP5?gLH%U|Mf$1JyXTM$;|M~<|cvTRhs^fZ9VJ25N=a9 zqIVtYZCmf$w>1s_T_N5)U}mTGROSh4Y+MJ`ydKsuWGsnh^jx!Mn%xD5uC0*$Mwp%D zN_L>@xHu8-`lyd@A6xRY(b|uEADQA&q2` z>ALwA&;4)yt>a-55xhqSB8U20Jm6YI)qzBOo)DKSZd!L%NH6E3yp}?VYAw`z04z%h z<{`N$E^wxgaL&-g&9Y*{S(u``MltCFwHANQ5ntZ}RN-YHn55gV%AM9%>pQXB_H3)G zNaJ-mCs1g>`p=q`g}6PRzR4ZR2SuELFO;;ln!f1?Kp z1}ctM;m@}JaOSJXh;VoPI^3m47tp1PI%j7Ma3p?`RcLoeG(JG_kdkV{WFx?O<9<_VVPHzr{W0kC>eAv@7K9ILFIqe-$J3-(eQp? zfk`=*iQ>i7X=PMFy5IP`^PM!u#i#SLx}*`21e=g7_rMG!rMnLZ3>`RyO2xcN^<=tM zr?gq8#gD~|p0hU4if8>PrJxy)Td+p(r^DvYcADZcRN*tj6mgeJbODEYOF>*ZvELHJ zs;7lo3K++x-<{!g=e-#RV_V@bYO>f<4v92L0G$q=%JiDJIPnTa5iEPRL2>=u(V?uQ zNhPcan5Ym@KZoO6;?i1)P{-PeAkK3ilggsJnll4_hpYS?^A$M7xQ98?jF%~8oC;)2 zq@y+-Ylkk9dbyZ5l{MO#0Zr86PJeCx@d9f^5L4f&l8IuP&nM_*b`xjsS}~Wu$>52c z;mm6HhOh57^;raK6z$Y{U!QZP{Q;xr-Rdt2k8efT))qJc#v^A*XX(O%DE`lUEh$&u z#Iw%~%CP;$_=tKq_|-*+#nh>YCM~P=f5o$BQ7~o+GITM~ev$908KVA)sQnFA>ipOJ zzg7_)4R)|Z90JLDcdL7?YVL2XJ=pS+@e-ORerMx5!( zU78jBVsMsGSc&+LY)m>as--af`jE?KPai_CCl8I&XJg7&X3D?jkG1?875#rlRUvYS z5F{d!NSiEQSw7X1^R-VoCjp}PEO0ddVUC4^&!jn)*Wd}e{*w}inDgoqHzu#)Aw?1| zgmcjnJINxFuN~I7W*UlIO0ZE+frQ}LDJXOo5nNW0Xv#1=Q!7!14+z>{ouZr{80eq% zricHyk?-wk?*Y>i%aQ;i=UZ;4$n+{l5$5YX!YyCQH%m_&ky|2)PDa=x4&s{!tUK}B z;D9g4CkQC7>m!`a)f;vfi2wUp^-=ZS?=Qq z*9|Bw@K~29y7q)}&f{ci9?Z>ZqRXgv3tlMrAucvopOb#&(hrDXF<8E1pD5L3n~g@= zqJC}5&T%EZ)lU4_$S4tK&L+xe@do8*og+t%3gJqcdvevYa;F#Pj?jgnRiC}@Cr1>} z1b1>MYD(1Ga>gia8gz@fz%oBIEqY5aF0IsZq*{ z-!o@bbIoQH0(YJPHy2-5MYXmCLS2WRPgIH8q!G2oazO%)1Pr{N*&~pv;T13UJ<3ZC zsef26yWbvk4*+vY7)(&P`!An&I(BWPH43L-!bX>zHXdxziWaDq^g2x2{*yQVN~ z4<>ufH-M}Nyt%mI5bzQj;f*wjaynO}3chVq~n?WjtV>Pl&iA1TRAByjUS!&67kaFFY6QvEIlOj_m7Xe zf`6Jjbkzj@YynX~1m%Vf%`Qs}E#T1}_HRd_&dua2Z97>wE=~%Fu8v>gpzP#*1U}iz z(-~i-Qyz4UUWCXmitS|{Pvu=@h4C&(cV9jUYRL3D@5bHr0d=HMX(px@S`G)f5gm#6 zYRtbpA*##82I=;-;CG7{dtxUDIP)DGQh&YVXMCem8@=&zc{|6*TTi6*&5?Wqs^IAh zJ6;Wbig|bEmP_o=W0puSuCt*-(oVC;;%fP`i_Bi7g(ckMQ>*sOppix!e_B)1rDtCzs&QnoEf;8Q86(AuNSyewBp=2eR*WYHE?b>&femfDISpGUH@k^COsG-N@G z`Hb>*)7YO2VOP)i>pB`hMcc_KHg}S_38t?$?V}f=<)_`>JtvsW)3m=os~49oP1P!y4aP>qYK{CFf54I)mY|#Hdvk#y z{(%PmP`JQuTHqDGc>vtM3#O51|4(@|G#DGPf+1q9i0P$$W*g?<%f6HJrM2rx_bWY- zYk900>+X5-)Nt@l%#*ruIxvY9w?vimM_XqRZ-d-_TKV*CM`AQe7PP+HkCsKm(VMav zeL<{$ODu5@Kx?=Mtlim93c1|_{=dgpNAxXF0>@EY28C%ek;nvOu~3)q&2wSg?%$mR zDzSndk(o+MQ0~s+_RP|{X{1e8&d{J8WyPrGuwi7}ZNduXusRLEC&nFT;P30EeLdg6 zT*cmt7QCJ%`hNdj#wEM%0hlT&OjUd7$JGV=MDY~9X-+ToY<#AH#vltV|2ClZxT{(g zoMWiFZ=GXvKO1M`irp}K^Oq=&+@b`NohxU3;=q3DXwfej_PpQ}DU-w)4S2C~`r)DY zYR$92x4$-VCes;k=C!~KNJmA9Qlqzq9W!EOj*yY6Ar7?5a`ww#Inxx?T^h1mV3-Kg z4U?}z)i%%NKnFY$p$8TJ5VN+;MTk|-Lc<}+5DMIoMXYuAXXMN&tLM9?94n;oWW;6@ zVbKt?=^yT#mgFs{qQ6-kAa&qg>5Z&y$QczCM6pg)2DeVb_y^%X{Hk^)H~e3uB;X`= zkveT{v`3Pch2X@md`&$yi2O&8fw4DoXJRE#caq_s%cjN^R5E;D6;5y#H<-59Mt-a) z>!dUlB~q#AnVhI0qO>4#ec}rfgnUx`_vR{{8|jYy(((LcahDIS3*&^kXOOWJPJt^( z9c5MxYT)d(9!e?{pcrLcUn#U?1qn4N*4Uweo4A4Ku2`+Ve{-ss;*NEHKq*qw&z&c8 z3Ia1&4g3Yi_FwnfI*YhT^fYk41M#KvD0EE8e9Huf8PW;8Dn~oD-K@9&kAa|24TqsS zvNv|lW#os)ewyLVlb;|ECOPQ zmPjp+7D-(GWIocI!x3N3PCB(kSug#5==8LEJCkoA@soBhbe2d@g^gxvTA)DAAmL1B z#~r9M7L;DHAsv|+F(P7V*vDuOu0l65U|w5V!qMt$6?Lr?)G0gTA(yzG}W} zejjrteY>?|I3}{I9FBFTmD0>ppyv3wzM-sNW-6M74oYBzDo)9}UTJABB-kk(JfCBxQMUoIt*G8Q z)<{#q^twNo2q}`zTV9WB#O=r+0haENcMq6xS%S5{_PiaxJC*Got%_;PS|6M(rg7fp zwe_YYQpPuTree#h+&e2s%wy9)(OL^KwY-krkUDXzY+?$=Qoq4}F>-u?h$>AMnvE?8 z{r1o@==FX_&rw23KjZuSHfslata2M#SC=N>2z{;#(I=LVi1=Lv977=+50cfN_B_M9 zoi_2;PKD>R`O<(-B(4)p&KNp``BS%-Kp=rP9ocf7!?xaq@ta&^l2WK*K6qE!p7!!vGaaJ6#^kT*Dc;t zm1)mZ2R$zdf5HiSKzxT+h;W}`q58oz6s*4FG-Fp4b_j*O4R$z0gt>*UgZfo z6vXJh)xOHP2c#igsLz<=SKe(8*Ru36k(K7`ek1Kj>cBCB9F$g*mlzA)ZMb8Fs3NreQF3eQ8&EX0ySa-e&7TYdHGF)Mo1Tk-&zHpG#ReQ7TPPuReb? z5{mN$a~H?+E<|R=T-Rf3X+Q!Zp{O#_Cr#UF*@YCZT5h4t`cu|!Mij|4C1X0IXB=Lv za^$s5@P&1@h9)+kHjs3hKyq@4;wwD`UB}5yagYN1&2L}p5*igePa!*|taJq}VB8=9 z+fmlQ+oCkQTX&prKzrx9e4dNODgR}ETi5qb z^SdYt;{2vlrtg3U*|3tT%q!4ueFKQ19!+nbg!O_o9C$I}OSLD?3A&!vq9Mq`aB>3A z-RdB6fM8WMd0wJ}LP~BfkZPh-2jg-rr6P#Jaq#mZDDmU=g5^$%N2P;+AA`0Ak2HW; z2S$_;=y2D`9fCwsMFas zen21kj@;Bz#)fN}=)C4h)$D7Z&0hn9{D66%mACuvoHtg*pH6SJ$TNO~L=hf}t<{;t zIum1L%Jp?YisCq(c?F4T1+^+|%G=uUDY{0itNA8In|`i!j-l_4)`6qcW&nCE3zuRq z<|d8ziIml#Sn3s;;!nDQly;U_ZKAu0c|2PjtV5AIFDJpCS|7zXRF)m#e2FzT2)yC7 z7AL=cSvptzes>2|H`njr$1Lf);@}ED%}Zl>@^6l%6c%5`m_@|r3VIJ!sM6sOBilp!YNCaE}>k5Op_%&IOsFaNz1K7G@T5LI!(J68MTh{n!> zHj08ozZO35mBepV^Q;4<)EFB#f$OPX6m>0+QMF=5GBvEx4j(%Pok%V_h~v$=aAOi$18S5+GJMyJJhn@3DahHFix^ zoKfVGv4d9&*&#>b6=5%tJ0qp-q3mifV3@?y4S%N9LRr%sE!cqIZ#sx+`@t zoT(44+G+FNlEbw16i<2aQ@WYRqStFEixZphV6^-8Vt>d5Nt%6pSBO4CDPPlMNm}a*&50_<*_vnWSi9KtxWfl* z5$w2weaS^Wkg~*A3k80Po(7uAZ1WX(n`K<7`UOJsD2Y$<)@SjW*jEq2x^E44I}9zs zu$)n+blcsEHqWn-i<0)ARo)Jj!a!yeL1}g0ZK!YN5i2m-GaXdc&`(LNeaM4yIi?VS ziigl_&MOd}1=}=dDv3Y+XsStlV_2LAmKM4RCY9*kk1=MmwdV)@%|^P-h!PpsVJq=8ZIcmz3E9Aodp)lOydf$N1-b%U>|fB3{ zin<$-Jo@q>D?j?-NDLxv^kLB)_-V|VOp$9v=5u1pKeJThYJRjZk7^+0Kak-uppBHs2;MfgwRND5B0 z4e=BD69j2o9}x(!?NaAA%ih)`4#t$FFD%NmakcHP%CP|}d*t(CU=|gXC%v{8&b?>2 zCG(*Gjn6vq&WXoE`GhA*${uCLezAoeUVLjaFLzw5^CgKVfL;#40_-lrg@z(eVNpsv zTQa^j0^s+%?4iL0WWHZn?K%S5J#_O>Qj_3(Js~%vDoe&Jbb_B`QVz`QD`9)zB^2BJ zwPkCIJO%w#6AKMrs1SNebEPRiD}g3@x6OrzZ4q9(f$Wy7(qljKZow^Se7T){d8AcdM} zj-s+#YYjl^@`_N-kn@y_>uA3XHGeFtQA3u6nkwsNrKxXXl!qmCDxORvjTQRiOYmQz zP=bmIjW++mUh{+%XY(4Zh#P3*;uNemlV$LObBox)a|9B(yembE4%9RwKh;69uEDWv zMkZ#q%V$wJ20DkZ@VM60oSNB}ZRng(P|AR7X2QfDIlDR17owb8V_$sq>#P6~DFLr7 zo6)&+(gfea9>;=+Rco#Wa4tR(t^4TE?9RL2QYs9rIu^X8jrUAbp9a3ez12O|G;`$V zx}G_$8Nkx~Ksu>RBV7|xIbM8BK}tG5lz%Zr=6VZ5Uv z(yWWRE-}6kB$!Kp$a|{lim$6t_Mv7oDE!G{V6X{z$cV;89D12}IN`$Z-3$1soAr1t zJVjGR_;(KyNs2)xGotH;HiUL&aQ=ieafh&9`=atpf6;4!XWH+s9XM|p_Da>MRd>tEM@k@|b zE>5j{I3o6%zahpcuQsS`=)0$+BT9bJ_Ok`1w`Qfi=>$07a%x;X3guh0zC$TP+Z%ZW z7L8RsY!-1P)&L26a+G*@+5oHG<;tqjES@Aq|3R;-*-$98iH zdI?gtw=8Ry=J4P}2PmmC(rOo<@K<6)LjukbmO(ak$$g&T^CZ(EwUzn-(?$5p_|U4fn)(DfEIHJZq2m$X&$YlR zrd|%~wxTF!ozsv@HhE_qk{Pg7(faNz<=4mxW4V(tPou=NN+9JAZ`L2P<=hNR8LF;% zJYl`Dgfs zyw^PU0OoWA zOxUg*@cbjy#<+@GrAZsg532L@Qm*v=_c7GneP}H<2<=rz%_&OwjnZnX-pS*cdMK$o z6csr()#lEw1zAuq@3eL6BP`k9tXNSn)bz+qz8mFTn0YtjS50r;f_L^K$@GA0&b}$G zDYJ>qLpUKvp5+Ev2)X%t0@|G7G&IN{YOmbpYA~9%<|Uv^=OE3Y@>a ziJvFujkln*SG2#i29|>AOx$sIOkm%-ughXm!+*wPtB1Nj-HF`!T|Z0d`+;j-qwf4jbarACnGQ2zQq=eFFC}`^dB{`Qt6;-wIvVI)JT1Z;9wS zNxL`Oqr^J$`^a?j34XG#>dQ~7d}-?P_v$+E*KkA#0%fKJKL)DL;^Gu9<5rO`Qf!*B z3wm!nQIS5)ZhQCGDVXpy?uv0b=`yl|*=t?{OKSKt%l`h%!2N%dzyRJgfI zg@LpX2jRe861Aa74|M(aj<#V33HFia33A^Li0?gsAYQ>K_xso(V>^hSH2o0iy*ojN zxE&qrFh}pDQy%d0bXU!3&Y>#fVmIeuos`6_?XwX0k>UpJJw_LrTyg08t%*RcfNC9AnkC(3CPjR3ahIc-9~H0NcL zeTb5%JOFM-$+Q0ukS@HOro`vJk+Y$de6wo`~*GU)a_B&@Ks zp>+{p?#G!!z7+HR)x#lpzm*n`bn*_Buidmo`nt88Q-}@SD1g_9H+RscALV2~&qU+# z+mJ}NPK_5(PxINPc7WTmW@&n>Tz&N{e@QGI`}r)H{rCmbnVu%`v1Sac17BcfHSbBY zikb}Az*(eR=WbS|-%gx4KG0^q3vXT*L(@`_Sjj=O+_dx>*RQJh5UE2#K-lHf@nt z%6{8@QSI(dGY^{Tjuls>lSca`AYgCR<-vOtDF1LTn4je(x94OGs-HQ1GhXpTH%cVWRomM}@HSm`U)_fd5}qe5 z?iZOX;v`r1O4Y>;-Lbgoxa!pD#8vMu-M}NtC^1F|O_4r9hA+>pwE82{%6eH})sM&^ zJ)2FTeLF=I?30iD7oRkTSYOF;)0$~~_zCn0%&XYfwTbf46D>?wQ4D@E#BLFv?Zj*A zYLq^Ok(o}1Iy1gzT3#~gJ@*1i9V_2Rraha#>zE>Byr^Q~3IkcSrtO^O1=G>&AJ9-& z#HW3~>-Hb{rsC#&;fnLePa@Pa_QNIkH#ly9J9(q!;j<(F6ciSemaKZ zPV)(91g>C<0N#^dpCdB9yLFRy=&82v0gal!OQgp%g}E0REz-n(>n?_iV}7+M9V8E> zM+>LNTQgGI_F@utlo!#O$bQuP+*)NhsAFztWQM6xrZqA-s=FJUU}73HPja$ovLz1{ zbUWcIMnwy(o6q=M6!VTGq)8^0njNWH8Aw^Jq8amwsPJMZ#WO*x?xgYVTaEFefcL(5 zXGzYcV$#KW6ZI37+6^3<^#j{-<;MgV$c1^kGo->1$4%P$GiA-Zf{*D_DS?uT)~X$y z-Qi`CU=Q+yU>l&X?yHKLmur-lkpu{TuTI&NejtbwEv$z!$h zbl=@s^l9jY2cND<)(7aj&%XRcB-brFqe5~pKO%`kmovIpc&C7@`tmSa*YruQ z+)VcgiwjB8%JoZAaa3^G`-LhdxwVAroXA)+%q;OAmJ#)FIDucqMcC@ND?PAQZDu@8 z;?U;No>F*y!lZ|+8JmR@;}sKu*hx1wsqS0Oxf1! zbDF7Un@Hm?1X6I+Z#@+ZTzS13y)DEu_+(Tyr(id z74Pu%=q(7I^h;#igv2r7s_%H_$i>}TB;^EL#jQ?7W}AO*rdj2S>8PoXQP~n zG_c(J75+;sZzd|Wsyo*6=Z3th=XG>6Pl=R)EUBjfcFXSPx0(^Q!CKw!ypkhQG#fb8 znU8y(@0%2FTbIi}X@>JRo;B?L}{|9UK|8ZbD%mOM%_cRIk9jeX;(8#dbcCF%2p z;IQ@l96KOJHUL#?q}=qqYK-o2SM?VvvmpFqbti?HIfKiaro{qO9T@|2NIys>tlVeZ z#fn0<_HFs?L5sTPYU8I4&W6aN%B8(`qrDim=!foKyYbJN#4S<;9DZ#}`?^&1MN@l9DlR^r>aYO)-j? z1~1OZQ(oAsJUZ#53OchLaO6@iwJ%yiYMDX1ib*?ICI}x|lyK+#ew{O^(U4yd^fQ4X z-%l0qJP)$aTC%tC%Za!Bs)k{iBxUz#%bv|gWhbU(27QCcL{C)i=1bg+&W|l@Xis?O zxKQ^p|2q#a_T=BdgyD4hn%h7FvL(N%v`D_y_6&=T!cUfTBEejZvLTI4@n71s`rhFX zv=%WXkKsuzl~<^WRNlRql0VUwpW&?RX8^ zV8KoPX9xRQ6yNuEK~2H%KGh-QC^Y(j^V<@4~(B=l${haF%Nru3E#HN0@-YE3M+*tF%O=epZ3& z4aYZKaxY0aqrZNw>+GPj)V)B;QE)-^wZ*9{KwlqG2>YFPjey*9Bp^@9%cUx2MvJ~c z8MSqY%4=5~Pb?jd@%iVSN^?&XdK_~Rt`ItGQcdD?IAm?f5A8k{e8vB1!ElltucJU% zZRld7+2NuibT@L*E^CVO%PVSgo@$S@b*H(tFmaRHT(187d4WZ|JlmXlZ0E>^&a9Cy zaIDGuL`SQQz3%3~@WWMNr^lgI#4E;T6^Y;Z`^gv1^5)bB=#SS!+IutFx9>E^Q!6^U zl5O>V6H0bBt;D<1XeCQ%A&Oz??PWA^U(90^GI0jV42F(-?w{zfv)unoB#ASYrS;#* z$s&%2u)HCBp6FG(&Hq2iB~(W{V&O?t-F@z!3p5yNw$ zSSCqApp%Tu>t_I&p8|0kQ`;Eqtm<3i|8j(TU-C0OButcg;IbqCJP*HP+4dNo)J=}# zrf9&neehI9d8Jbam?ql3mulp@#kdFI7ll{^L|tIy{!8`)fj<9 z_K-Tne2m9zhhgx(lKyGv%l&i7=iT@}4MOMeCb23?IHm~Mx7WT#6g+b7M+q065SoCR z3y#yUoOJI+)Svm#72d-_Pwy*VkK}xnkt3G?m*6B1)4_}Hn;rw_gyYTta`f}_w(CY2 zYnVEFnLscb{)qtY#vpQC8N+tD=8fZ(S7r9O%8DpOWJ%O^q{}3O3%>i%KLAVPQ|KB? zpKf|~kav9NKcIdcE(MefgX^E9O^%^mtL(@wQXLD01Oxz8Sa>~-f2#5uQKK=ZaOPKJ zW5nfb`l9wW2HzRo{Yl|-K!VI7Wt~r3y6OZhOIkSXabrb%f&66kK-$`D0 zCB0I7^ey`BsQA-z>~WUUZ3Bz&t}xS71K%P2qR;jpP}ot!#f%KQ$6^~~_UNnb(I2kt z-B&H=(ZJ2H>3={Lwx_|$e{jPu229sl^dqBAaFOc+&(+q0!~OwfhC^I6_4qRW{u5>U zOf=7PeL2~P9egUjyDt3?sG^&H&BS~32M>5Q`=*L%^2J}@7x@U8=jby0va0C1^pzy^ z;a=f#cKvL?bSZk!Bif7G{%-f3qc6N!_)EXZDJoh=Y!11l{KOk0Jdo}#7=IqoGLGg4g^xfD|bgl&m+#|KD zse9yl!YC|(KeUAFv$!S%Ps@$K(#pj_+o$g@eC30S(g}}skZHizCN9!B@HL*eb9_X= zVT3aTs?NQ4Ii`-V(87L`KRu5254M29q8B#P%L~`)S?H!i`p^RPkA4Q*r*YA|5kbX4 z3|`-Q3#gTa?0VHuqNizIPYs{Fi3NrmotaABC?-=py{kW~$-vFg3s`u%_3yqm=vr0> z{JiP&{DYpf%eT)p8=(n{!}DP1d(i(JKZx`3&pdJ`*6YX*X}*18O981Jv`pELj<&e5 ze`X^u{CAJxiFRYBMuTkoqd&x`P0kQPyI2ck@PD`|!fKW1e`rRe%cAExBfFXFx>p=n@oxr>S=JL-Ds5;l z>kZd(E~RPqo9H;FDHDtgw!<3>O!>FBLZ8lw75Sy*4(f>qC~qJ$DSnQVrOt3wxYR?9 z3YsIE+mUYU&Yf2Y6*)+-?2QcAC$7zP;Y{xcb`+>iP`^T)OI+K}CQwS&M!=xX{OR9r z^&j7!+6+_wY`+P|1g%J<*ws-m} z#p($zN*j^))uiA|i}nm1Z2ZDmv-$Z21(-b}i1;MWRQy<(WY4Axt~1=VYQG=TYz(=$ zJ5@2u9SzxJfZ!*OexBzWuekS;G>r7IuQXRN&sxk*Jhnz3t|qm;#=59WOs}5vzG&h;vL=P zNvhUbEk~W{uVftl6?ok}N0!&b^J=}Qgpl-MVpKs<2)(m&RkM_E^%z@m zrdYw^3b~JQ0c&Kw=tuI4P8LYL=Wis>5{+gv?#v;ra;nWD9R0{uj@OdZRd2otORwgkx@`#T^ut$yO=>tKjBh0w)2|8fD` zr+|fEmAM-u69#s={$4$(P+T_4pzV41`;%sbkEu5JXu?L+Jv z%qC2VdeH=^9-Xzg-tbNaCN=3W@WQ;hj8qSlcwQR{6oGQF*}0~mXm^@3CvA2<3a0V& zpDpSZui!|i*B@1P^uJbf_N2F*)lwFwA-yQUay40?JDXCRApUGzsa6)ee6I!R#qfK2 z1bR8fcNM25j&wejqOUY&8V(L@u!o(5(sPCB2+qXq;T~>Kf}SJcW8oy!p^s7;*RG&A zxkNu}Q68nS@u35+n&!GzJ?O zWu$E=j4EDgb*o$SAUnDWmMf1k8uW)4F348CzbKJbzxU&GhNR(NrF2fm6}Q?;bRQ>vqgec`qU7iNI!4E^bW2;ayFL9r3q6waU^7sIr#V zq*k;AGk^Y}885VJa49G>JQys6>l;Jbi^EjAQ(tqB467k8Cy~m~60B{9sJf$=$-u)c zg=i=Zxv-Lj<0UOazH0-N9PVicGICL(1FWe#mkMj2)F_u}gO^g=3;}G6W0=>fzy|uf zOdlUfLiKjLo^!wcAFJ|bbK>oFE#IovbyEJ{^V+!~qSU%e=ass946lYhx0U?K=@ATO z?w}J}+a*P@6{RpS+91n~F*~gT9OqfV^9z#K5mijViBVTtBpyWV4&ghBodOEB)lG+Iu1l3THZ;Ej*i{xTT{sR8Wqs2IqAM0mE~7Fw zGubuUE6kn(+RTTg^W`sT&Nnb-G#9wm&r+gGjPbbgIecSpe7VI5>p5rRg2k88tqC*u z_80++Ml)d;6I(VwSTwozbP?4D+ba5)DS`KWy~1V9KwfuF1Z z(vxj~_T>M71QK=r8O5Yd*Hk&*-r6LkFeQWh`n^nW>EC0}I{ix|VvZfDQH_%n9FZ8U z86F8h$9nuY+A<8z$8uL}c9Gi?CF&oEQU)XcUapE2|bL?WqwQU7} zE*g-g$RqMk@lRE^;=sPVnBn^tp!w|AO0*TTW)~dI=<;U=^pMu}xp8nre4Zk>t3^u~ek zE)FPOom8D%EP2yEKWiu##j+OzIJTd~Zm<&PBXh7Xrvi6{f4W#_d`BJIzg4}4tpe8# zfTxtwyKwYQl2g13@{Zr1ZOM=qjy2odb#xS1?29uTUje|i#sDXk+bGAggtEx3vv>7> zs1AL(zm(w>@2f^|Dbc#A3IW9{Huhbz_X(|=qlnq6}R0U*#00Mi(Xi?-4Zgqy^c%eqbeQnypCV>BJ_cf*3T)`YIpCzFdNC9 zRJ>_%p*)}HyIX%!^MH8X=>aO!O?9E!5unVLZPN#J@s&F$kU#yii3@G+!DAHCEun7i zN#5ykD{eT6zUMeA@+Z+9x|dh=iJpEvA13D{&M4K7Q`5)dVTVb~9Fg^6F6`6?dqHdO z(m8Y``#_P4cnG9@Xm{@nd@b`ugzfVpI?b_{As79h1Oq-Kye5jqN5F`M=gSI$>Us>@ zOKlW|ZOJFhEppe6x}dUSkhVJ)lB9=q;GFUU7w2KmRIF_1r|gY*U#Rh9VTOVng!rL9 zPW}8A9pEjTU59l!N}dFgGJw9&mH>H92F&**)gWX^AzIq@MJ|XP)L9*Lv=w`Y8$a<= zXIAgp!=%RG9V7AO`&{EqOIh*+I`iU*@5>|>!f7z37}v$|1}k_a>{w&5RQu%&zu2b# z)Y;&TOvTIeXc>QjochN729DaEuVTe7CtV1q?gVx4b^#Cv2J$`PWShFV1op|*)RR-$bY_2D= ze?T--o?>8)%SW9Hm1sM3+T!!$;*Xd{MqHpYAGUpdgF!ft2N zSj@J}WiRw?p{k|O=ivLx)O&*4a*ctV90+@!I4=oJreo}>s0q5|C7!g!xW>A50zOsQ zks<7A!%jTMBuN>rcJ?3MUb7FbwB(O61HB&&nzz=<$>O+C-`8id(JI%Rpf#6SHDB`$ z1Pbd&@M}_IHED)kx9k{K5j-9sJdGSL*^ftu4ebqiRg^N2-QUw??|VYMugFvc$Vklrs1unFL`el@kx!L_uZ`P4T~JC#i(YG*U$Nm3n_2O1`X} zPVq$u7<4E8Iwk=`W*m_{Yu>$ZeQ9^dOVnZFQbr^6b1;DZ!^hOxTI2W1EP}`mGKIx=Fw*Eyaq%*9TVSbCdsN1YJ|dulbO?E&lBbj%Dn z5y!N`bE>FoAHsJ*j9{EKUjNjBAO78KTUVJsRMlbI`y zn5^N@VIPJ!jjIV06Vz~oD#ZNWpHuWS9`Q=hxp|23IOe2hCvl}b1WN01en7fdx##c6VD1ejSgwQylmpGX-MlM#Ti4uUGK`1e9R*D zu7e=z{TMyTuKqJ^@8 zTGiP?Z#kK<#99_pzPC_ax#I}2ohU|biX#6sH5!_5yyk*Wk@Dos=oPm>_dHKxkdam{ z!%H?a(v)~9%Jx2O5evAYi|5M6NgN|W0q_6l47zL9+T$RK`Z~3OmpksRzOM10NF%|_ zw2DEizRWe@A`D9p=>53AT#I$#=BU_@)>qG%(-Y_O{TP4DSCcE49_v9>c+0qtXznT& z5<`KX-b7$%-t;EFAUK7|k#_wnWgHcP3x=T4c+(*U`Mn{wRsjuY^cn>qcjqe%L2`M; z819K`3`_h)5vs0*2}MK`l$Z;|(i061g~^qQlK}WbpaSyG=y)UZ{(=B15scHC8Mr$t zGQTelC~W8%aS3oqaei+~t`2~(<`;#JfH1UX!C$@Nzsj1FUMK)+4AAm^!#V=e-KC;* zq5onQ3&gnJ>Jk|4Yw#C9xkhkg{oncpfJwvvn%Msdf5*izH!f4ZANqg7ne%&b|C76Z zQ*0)FUti;G6@c<4oufhhbwWwOmj(b>tmCyG{jcm03Qw$NmfDeJ z;Kh$#wcS-@P7QHB-{pJ8((npJS)&S*s$l=3f1Rl7Q_yA5`5#>O0x~M-CgRXn2t@G(!$gFcXlYl!ai1^^iCI~-7u#*WnI_uJ z7969r2~%VyEM^93!C(Z*2=F|2v*4f>I!X{f3Bk)l}u9Z8z6}u=yH71M3!BW*0 z)w9rX#Vj-DCEk)j6o8LH^sk@oNlq7tmwni-m(uK`pBxHNhsEpva#c8Xdha6nyEXIp$xJ&FLE1#>`YMA4I9fN zWZ+fea?_=iPkfj*5yy(SzTu08Sl^mzKQekPXWph~vUuo=Jeq2Q+F@@`3 zz$402g)J&sn%LMQgwVl-IQ@S&xC12wo>et4o%Ld=AUC=TCX!4R{S}3{l)G z47j5Gs*qg;KgE;Ka|q7mMq{WTQ0I`S_Olcf1zR1WF3d-5p$ES|JmPGLqP@i;Ny2au zJ$1BA%d|N^s$z9v(>^J>T135!vV|WH4@P3W8D-I!8tRoYTee@a7L4|dYlu@NoWJ_5 z)uzI2Eh%f)yOiNlzugymlLjUN@s+T+1xN2r_K5ullvI1nOU<61FQAYuIr>=wlUE@%ZJ~<0Sq3D5zlYOWp5%(D9E_%z zyuT#t_mRi20VA9&v-r_r{o$RRN$vq>gLH%Dy%}i!L(68rh#kU{Y;xbyGWP<}@b}B+ z^2QiapVipuo-+9(*R>Xya!N>xS(&({)sRzO101Z&g(3Wo-ad@m8;#T}1|DV245n)> z-MAxJAxNxXMQ!cVo2z%$q7S{)vtwP@k&*BRDQUMF)22UKc_4Nqe;jim+SE9t3=9oS zSvl~=EJOtw{-&kKEH|C}VKEW?B?!x8;53t00V53wNr3w?X!XLg#70*n_C14nx@bCU zfCICGv^7*4D+qMox>Ig{q(SBtG4%a%uZIilBM;4KC4*b-S>eTOoQ6PW0Pl&}zTb6z z$hE-t&8mffzY*YTBhmU|nD<8xo{HRv871yFz=!)o4xewdDaF&OI&gKO81{yjPh#+d z=iX|)w_}&Rufl&F^$jNEiMSGkFOLM-2->?+@p#&Me*3`KwcP*#@!?NAY= z2QT_n5)#O15)!}iu|mXYyoth;K7UKB`8Q+iy@@kZ^EbRP>XIa{CH~W5GbVr2>@O&v z0cdrA4MPkG>I}7DVyDQ8TzTA2Y#j-6E^;b>q6c)t|B0-LQbN=K8BJ=m#ttqY6+{yE z15(ui@K30ElgKZD!vzCCIduQ}7D+k4O#v4y83!CPCJ~C;6RiY26O0SaFQIjURF20WJri2>d_q3}wn0lz(q#?(fm_NAk|^KPAPxfa1&$ zhxxi;`F67iL-q{D{h^{vL^9gbIgrXnBDW{V#%B((7(sx7-{lBm-MV#qbb+-!C7N9F zPeUVf!K$Nk2E#QW2A8cY#d_u9;p+Nw7uDltZm&cAaa!UW&tFqH&Bo4aP6L|wV$yA9 zKdhRuQ3UZAvWcpHZ%*YoJfutaf1k9fkrkHEj}lOdK~yKRc3x`^62=ba^&L>zHAv=^vXS~OK64nV`_<{J@ zKV-e88#yYa!HZr<&yhYW8!F9GhH>y+$+?OyGbN>8f2G8+bY&qc&6TO|lzC@A($`d( z%Ih8qw4V&|NrNb2%O7L-zklp{3JT9Rkz>fC3R4{(M_b2sGdrdW&pTIMswzqx2NnW} z=S%DqH|aoZoU8L9??V+`X#{7m!WSjg4=&9J3B0RN=Irkcble#mTG%!8cdS|Gf%Qyr zIe;o5^r)ZNTTuxu`@=I3p=Z21GOY1u`l{JK5@wjpkGA-xeqmj--)Z1ab;2p&i;)is zvDToO1!8a}3TxyPB;YM#4*i$BqVTXtQjY+3y(K-UI2&o1Ib5^D+aM0_p86aeN~n># zkxcm2fxuj%qb|yBz+LZXT=js;Gb!Ig49<>WP81Tt%&hhDu6WsjYu!cETCXrv&NEu`IIP-*69L3Q zT~Y35#twc$_sJtAjJv@i&a*>Q>okZs14P}HYU;bMVtDxzrq+8NHLr=n2?(ZdKH@@L zxPFCA{h+8g^AL;--#F}-lD4g%aNGT%^^&y^{FdKMb%qSIp4UpV{?xE*F{`;7Ij^mhJpHsmDtg*4uHyYqb z!1hGD-Dphj8E4o>Jmy;=l3@i#Q(v_o={n2cNH=3`$1Makgwn5W&nw)T;eL-fxchNf zLWGixub`b*h+w&7Q9K`YmN@CJKkUOUtak5}wk%ScT5gYFX(#R+b*p5Po*svz8F4)b zf8biGI#oIeV_>{{DMZbm@|0-&|SKrDfL4LJFoEbMvisPzZdbRm;45ai2aeQbz zb#~Z0qpHczGXoF6zG}H@lW$rnC@A^U5W{@|0)KrCH5a;nM#xV&z-~t(9~j=od>4M> zm6cC1HvkO~RnGqz)%+!aPiq2iyd^H}W9U7ZeYx^jFqSAu{E#@!kHn2Ix$zgmkn@<)| z%K=96-!$`$$eQRI@GEau|EUGW`HMHjBA_CO+7_!xP7H0^De@kiU-!S+GjP9}x#fSU zYJZRTw_AH_MYDm{6G(%A-_XAq5O^TgM3OQ@xJ#;%d;n2S;RAcYuR~lK*U|6Ao#=f1 z>lraOLGg2taBOGl0*1Wep}0(!w@XHY&W_2!Oyf}2L0h+19}dL(sXu6uTP7-0w-e~s zVVWLusWTq)a7S>Qbb1unV2Mr8;m;PyBku7G$&(T#AEvWnB7l_O3&+y3Y%5 z-IngqKzhhgn~-#sh&*m8$I50AqJA3Y`=T;~KsdAgFf(Ojp9^nqpU$zONah7jcqX>K z4Q&YS@eM}uZ)=$_MY}jRtLFbeX-x8I&#C8Qh$IKcTJp_QPy)@){vkF@_x6$VXHz4ux68G ze`BQ^pHhCQm?@scOi*Y6S^diy67Q2+o8$&RGUSW$ze-g#~VDQuyH0ok>A(Q3oC- zv->yWvXDNi+X_X&$mirQ^mwaoY4xK{NVq$A_`0%lzUbgzyNPjs6LNSa&L<=fjZ zmFYn-h*5qI6-#H6j2@83WsBcW0pZ(Eo-{!-v_=3m(cFUpyAw|EjjtvuW7 zugPCMI4e_d6$)3AW8DX1Y6uR+bZ)AJ3L z(#X-Jk{#Nn6jWZD)&(BN?3iV>I?ERpkzRDJG}s%u$CeXK^DJr7?F~ZQi0Lr+nJRjI zP*r=NRkF%q$8)K^3cl&E*ds$$@_UbjzY_xN4VY`Ri!$neO?$RdM7WE9#Ff#18Xe6fK?SGe0XNR zp~)j$c4km`$9Q)LSBxFw*R5fk^jkNH!AaHci&0K?RB zN?L^4#m5uJp)D$x>PBC42&JMRt4N7hejoR!^B7i@b?#eL{4WY5t~%dU>QW+BMl;0! zX!K_um3XKHCHKqgcYS8#sc?+jnAhKUF^-qez6Une=HA z!=~17B%W<_uUa=^aDQL0Y;CHLALJCqRq_7~@`U5xD9s+e8g*xj(v=c`9^33wD+fU? z4!y@Wi}QG%??@KdTIS)kNBkY1)}BLH?F5r$61cn9Vz|pC2&o|pf&LRH$^VORK(GVC z-W!<_zzG6DRZ8M38(?XsnFF}N0363}bzxu0NgCxK(EW4%R@KF_<#B$0gPsBmN|e%* z1Q>S&D6|t>fZCfMa2=cf!5uf_-#+}t^5uA&RlVtR*!-Qh{ci*XB?xTV8U;7@5?kKd zl=zajW`ImKpXjYGA+i9Ph+?K0rMK&I2SlTVApT#dlzbcB{f*>rMf`tZ637C84Vb)y zC1B_E|19U;`kvUo{mx(B@W1~BBD77E_P$SZe@!(oaA- z`s>g(AEj>ajcry6PsXuXL+2R!b}8!#BgZX1q~^D_Fd;4uoDu35GV(Kfd-A>yP_4EF zDr|g?JnBBY*s*;>>WrL`)j`wyM5y5Q?q4!=`&sEJXVWBU2vIgYZpQnpA`Xy3_(FO5 zf>%kcsl6>C*lu5Y>EevwGRXDqKy{hLVeCBfL`JO=^Hud)E*@icjw#)(Y3z2Z{rS-W zjUGZH{po=woXcOw7LCL-NE0wWj_gk1uJoVP=a1SmaF>i@vTI9;_Kv5PS=H92jz{$h z)v(vd1#Ipwo0+F_#{62bBuovNy#^l@xCGm?ynC`1F zI&4AQe@{;2@?#fk3zXsQLn>?(8gJ4Ro4IY*4E53dHr%J4BSKeIh58JY5C#+6Z9CcX zgn=Pef;?=5`1z(sJ?RiR^#&1D2pI#aI7<(vTf|PK%IVBfap2V0X}Q_j4ge(^Nt?w^ zFpy30QQ5HbqbEywrUz(2IFg&&o5h@4-Bw)6`G)N$CKF^*Do>g`YBBD4=|`JEjCq1d zSE^l>1%42AmBKVaN5vT3*_;MOi0%bIwFJexIva~z!?%bUL{7Rxba!k4`z5ihcFM2Y zX7$tsV~^6YV0TSYVJLqhAM1tQb_J{{Ja%o}z%g~aPmz(UJ><>lLq|Ax$V?>N9HLsL zCtTDSmc30lNMe{n2A|=1qD;&q`9#CLs51Q}L{sA$f9O>0PUz|x{@I=3tq};}*U%b(0%6eVd*{qiFuPJ)s z89rmBD^qYC96EsN;?-T4y;7xFc1kYd;bD(21XR;*fY|<42lkJ?OXd;@fOUllQ(VY1T$G$(EsE52&AiSm1%yEd$WM!7CGWiFV zJ$Bv@O(I~|Snj0n-`|CcvBUQj5eMIMr0%LL7{~hEjI5~YqoAv46#=_oy>fL?eXRv&wL^f9A{J7WMFyR! z!29)?%6}1p00WfEf5PFW2^~x{S(hThgif;3Y9n^^=kAlmxf@b2z zg@#V}%_D6Vyc-crs;S{XmB=jU@AIGW$FT^ArD^tYS%HA7P?jp8Sei=>Me|0Y{2L$_x$S{ z2D|`d_)mKo-w**+267v}{Dg#W#W-Q>T!1U%e4U#W!(u)<=_v8Oki7gp{^-$+YXvnW9CED+~Yhp@5i zo-^9oTIbeyQM{as$)AkZu+h6eKyIpq5$=kCj2jv&byoHzMC&fNh66m)W1W}Y2Px{# zV9K8zi|4?>fvaHEbF?%a+$ZjG+?`h^9*%~qv=A9A$GVrHO=pjkBE-AQ#YzH?v}B_C)jazpZ`k_$=;Q?Bcl((cdcOJ$c=St zjFYK_k3%#YNx~)Z>IP}18{@<7n3(Duu&4H$<2XXAVE_c>axRzn5CFa;SvSe6=_qU<=uNZY6Fh#WPsnDh# zc|16_J)!V9Ehf|E`)qU0mF=px{s>Fgtks2g92;dL1!23t`4rGjwnk^}@@`SEngkxg)rpT_w z3Y7DBSb1h3;wNXd+ z?QAmfnZBsXa&^0=Qu)C?*!>_$R$LyalO@^P3ysuT`#Z$0w6o|kg-)F!!fVY8p>W>CPgxtKlWVeV5RWf7nb)m_RObb$vNm5r zO}NYWHd%QFw8tFVVxaGeW`nmmk46Fx!qkB$k?G2QYQ4*b+Gs8{coQwCT%lgXxcW7~VuFY`CBqwD60m!{naB^P!} z+;4}2uZIAROcs3bYSD~qS$#FNjh)(Z5~25&`d4+)ATMATDN9Y)uDVX_SaB)&KY|6#;u|r zSYJ`vA0@R1_pN*lcyUXz>Oi|ZZHo_-Lb z1EpKj_nEq8u3UX3d>w*Gwn`|MHe6L194Y_cQDb1GSEVvkKD)yA)yq|!W|l(v0m#Om zYTjzw7@(|`RHr6}1NPj1L=^s(yF>op0GR_=1Gt-a|IQ_Vw`p_#T?O2>{GU1wu;T@A zZec*34J-lj+W>iMFt7?hAt$DSyA%vHhst%PdK=lB!|eYZ)01?(g`52U-UZ39iJyKEr5! zf^da1fITxYV4mCvq?A%%$Pa|@ztulT7Dyw&=)NGBlmbD8?GbGdUgvIL5qvqNby66< zGVvRQKUSg-<(O!xO6dIhug5UayH$uw!H}R zTZP14afW@52B*ee`ja6|oYj4hnudmA{ZV!4mf{P)fP2Meh5joe2uz`z|6L=z-5Xa0 zohy;KH^J8(LCPjnZb*xf$mv@yXBDHN7feG@g!XW$4fw1({|VYA)>Fju^mI#Ia~vQ& zqC})iXdv@==bcDN7@D7{2_&F6c;x|<6$>87jTKGUHg&C4O8g(^`{(wnJ1wkznYvtE zR>m=}FzPRxR@1hS+h~ylPzMf~B0STL zSXq+S8|}UdOHK?E#`b2uZUy1otQ*hPmHn#SPSfV0>P7dTcuB=a4$VoDlqyJO)U@G^ z?l+186n)ChWg?RfvX5annn2uc^a$;w(vP2qT@;ZlwE^H3L?_6ii%@d2H^^L^RX=FZ z@jMAf*05tm=z`2o&e^K5D(@`f+XybM=ylpftqYh>2v9mOVP&b4r^km_bAF)PMU_Ys z1~&xISAx=y5l_WsI&#C-CU~nw(M(_GE0FatggwVM7R;1wO?$0f!B{iw6;DgPmsNON zyLJrbYhH)EoN4GVs~~QOM#UW!ypThprzrjrw%;W`%K6n$`9qNCK}lybPwrGD1&lnI z0bW<5W4p_3giLqe%SrKu)^vE-=aZXIU{}v^6H7>`*2+|+Vx>W-^b-_mQ{3@ekX_3; z{en21TnR!nlbv$Evj)e`UZ>+y#0~ixnIRQ+!$b>fZ!7NJ+Fx|VOZp-w ze~fSL8(LvqfPDU>WSMhwM-+D zI09i`z5vdN#!)+#HQO`iu{l@_Illn#J9X0pm0^FDvlHm-tZ}_@RxKV%Ex?)SSO$aW zkUu$B5Ut){2%!HGXEC&n;Tk5jzPr%mj+e7JYr|-u<}1bB0ho^&DiO&ap3r$uo#L(= zyW-CUHVP>*$Lc<75S9#L3-Cpu%e8BIkXh&(H6qh+v?X0PaW>)id-9(6k~dN)xvNh% z$GK$e|M0sGM=+1*Ryt+uKW09*wf0h zobtP?x%j>oFQOMJsAi1DT_DS+ZbVxvvNDXC7g_-#o0AZ6k!o&DHRX>bR2mP!IBl0h{FtL1LJBVs4fIPL@3#`ji>B@H!<2_`tQ7xxbCMb_|B^%vwQ>k* z5vAYVyu1m3~( zL|Sd_)}LNe!#*wGE%#=K*kI2_$pnYrV-S)(Evrc9E4ahGmCSGJBl0&)GuOwVEXcn> zUb7GwY`+bR|2G8Lr+%BgNa;un362Qa4DSK)pC#EzHSTX}7|90z6rE0-2FCCr%yA0C zqU6ND!F7}%z#o7fqUG@D~w!lA|j$s$Kml>7_y8&Dd33b>|x;jh4p;@ivL zwu9|0Kg_+Q10WH^LJ|Wl1kT%d+~TiGJUG8tFa-FQ5ElTTqD20)AO7+-yIF_B`qG_Q zv}@6%c(fcFyt~I*J^oAFp8y(%=pLn^gq%RX-G<}xMreZ5G3t84Y?du$`}At*K!AQ? zKWWKb{4*IWZjJ{)q9V0+MgK+TnC2|)-9QHJfhav0m-H_;OSzHg#}iFD)dF8SGfd** zsKkw>zT-NzJ30A~!!z6hxu69<98DQjQ|0u+$AUlJ+dH!zz`?9z8q~zyy#|YBCG{OfF z+05>^ewr#&Bu`>DaS=c`O5yZ?{U{WV-hGh-wok>2BARyG5ISshRQ|m$?TT{9%z;+k zKG)+K+S(Ge$uZCE(ZIdZ^Bi7cBl@8GeCDw!$j9LqoLT49E<&o>i$3*zq|zc3Q!DQ`o2$24 zcEGdVMGq3={T?9KXsf6B52$`2uo?9@vJ;`lr>dTd@}&_O^C)N0IQa|_5CU(e+F57X zJig)m2Sl;n`J5DdW}mD5QmWiZm=JjTEqHn$#>d8|Y<8bTZeiZ{*AWG0sq}>nKyrMM z5l~D>!pI9QhGa5Db`)*lfLyF|i*c=FG8|X&wTxswwOHxm=e@v?NQl4?S$)8Z(G(!ZiQL|BEVtTT648vrpPkGO&?^U% zq%QaM1q_NjxZOXVxV@fc!7hI9gI%iaSj}QcNRvIPWHr^e}+>Bf!}IvF|>fB&#P`=<2mYP&*y{qyI>k7KCuTa_g#1A=Dv6vyK^3br%#Oz zO=ceYaf}1%43>J~Zo+FI`_6U5dr0<p^4Q* z8;27mf_vgUYO=hiTyb^h3z*X0w=#sE8p}=1(2R%K>nkQpB(;6XdyyR9Ia!~=K}~v( zg!$qcY&F(Q)nVBW3O@RIwlzSVXw`e=vY931 z(K=Z^zW5oi1uyOqeTH5JXb8WbTdXRFs+z+_)lPVXcPn2*;TcMPHHA+jDP;Ie+E?mdp@VuSo>3&=xFTpq*aY3)AI9{1zgk+cxuqOA}g@5WtJm4pJm%RQQy&YR95(phnDEeouD4bL=s2VEJ3BP^_GLEe&1g(bh)S8e;(zD>_>T- zR>AXnkGs5t3}s}z_#2!%dsSncy|3U1xdmq>n0Z%i1O4G(2|x0a5qpVwDvlKMsOIV@ zLYJ6&($0hw&+LljhPfmX*7oDN+Km)?_N9Y}pVyZ@vi|<;EM8Tyt4)L))Y8ng$`gVKlB3&ki#Tm&>Kiu!di;XdKXN6K&qof!lO^a`i>063IEoD2ROP z@kZgFx`uw`cPhz~1wfQn`0>!fQ|2I^%Fq|eilWG_1{5CUBmJPoyS{6#0IW$C5xb$C z--++?3&j=!~T3DrKP>>C&*xp z;w!oV)81$E{sSPH7?;7*Vw0C+7p>X1X=A`R6o4^mj0C+g_pTfvDcPhdP`N$oc-`V* zO!EOvxNqSWUrOL>Z!jeW=PF8+CjA+nus^!kb~%UXO~5ZG!D*35-yLgSzQUD*E(mB8 zQB^mhPl{XKhPX1*6J*sNoC;O>KTDO*!G3O8&Vi`npLZJ=85lR^XFZK^=54^^D`ypN zqu=E=Ig42hr7Yp_AxBR*4b#vg%gKr={=DB>+db^+7CQKgHo@X!=vXXn5l2!t(Zx(h z{oJkTod>&SMZ4tYM8JMpU^q5U_j;Lq=ZANuG;3<@I3mRT%*m*l(9w>Qs%YMF%?h_1 z$;Zq*hNf+9aTQrn8L)`QNIxDqjjW?ur=OK6Sr*29wWB?D1%5BixJv3j@{U6X{DSL* zT2n%N??CRVtKRdP@F`fbo`?85 z+C@fD)!)7#o6vL)uT&%5cn#QhCIokP8{FO9Jvan+ zC%C)Y0Kwf|lJj-Wd7p2snO;3Z13#j6rpP@b7M@BK=5EO2=KjF7f z2D;IJx66U?g!AE14iF?rAq)hfFZ2hUHLdn*4YgH^{B#s(WivMBtqD`=`6boXGN$K= z^{F#S+XjCsL5}ZSo@+;Ew+))KsiX?1C8_aK`ZT~EGLwHDHSf1$e6vnv4(>~ua}kOC zYeWo2=I}4HkNQb@z5fBRon9bFTw~PFf2=L^I5QTgce+p5t+4NLzU3;{ByPyQ8PT2) zDcJFm{DIy=KjJl_bK!Hs&GuTMnyvJM@-1288rGGxc&dh7!3Rd{WUa7a6_=ASKc$YR zD|!#aIc(%woi|r*mASiXZcf@c^>%`X@9`2n*P1Ac5r)|Qho5loiPtU=B-g=^qi`T7 z-Eq2gQ1>PJMrvxo2}TH~+SjRMe`A55s7<(a_TW}zmVT$R66)a?TgYQlwdd}fN;=07 z$lnVe5~i5x&Ggn8EhwrXDQv+MvY!tvG&*mA9W53yv$VA-uDy>{hT$klnKfR#T^nk- zjukuY?8NbU?fX`7kYvtfd`a$9VRXW_Cp~M>{8NsdE^9C85TkSnv~li(>DHwL-{I0F zZu9v7?25V4bPHj3;jU+z$e@q?=wR&+I|(=Ae0`(Rooo_gstE}av8+{Ey?a&7qO#Un zil0-wa3nN&wBcW}t)NUlgNRB0ns|4D#GP>j0)dbmw7Z(cc5KKxtAn|dSvz?He&H#= z?3KT1mo<>%w`=Q&x4~sPftoq&kQx)N-X(xple|E>Za>+6RaN&*xT#YZ>_a@5*ixj* zPHQPm;}H56O$2ftM*PXK)Jfi7(*0Uw4vQ3q-S|`ZSS8Puog=f7G(ED+F6G3X(l`dv zT^fpgJV-wynx}vMn_WT`fyG^wP#2~xz0=Zpipt{G|7Bi7C zb2DIy29vm9OGN3=PfeY`6)NqS%)1!6V)gVc6TPRBs- z9!a&gI=h}a&M_#7S9^z)RnL;%l2U1ylV8|U`QYk=W)CWowyiSS+~pTIR)G=Bmu>_I1pJX<`g*|z7d1TYe}L9(=>T6( z?DbgK+LP%$D=CyKRf@@9<~_KA)Fw~>h9dIVMfV#MAJoir4XiZELl~B18a&InyRYvF zG2`}nc4Iyl|C~kmfv%}?G$=jvT+y=BrGs0ROCcs|t0r*oWZeYXV}IEWtfeqB79s7b zkYZq=K!Wwe@dUXcwLSyNot)J8iRLHcWj|bK<&hM+z#v-`WeHcuLFep!h{oEmte_fc zHUZShnT0gA^qu9y6WQSNM-~=2Ftyz`@%Uft!w?n9^(ds)WxveGMAkKg0B9V|@}d3m zTDufiqo&=w)B773s?38G%E|oS7UwR%RsDCorT5Mqe~+0|ZPXaA&GAn?&W{%aOhk7z z38P*Cf#6Igw&+M1)7JJQIm0uJe7W>w+x+<5Ov5A3(ggCh=9GfVr|*O~kT1cgmf7}& z@pA|JP4}(XjsD&+2U~*>x{i#Zw>gD5hflZ2seBf@x|n|24C)>c9sdD6()&(2e|gN1 zDqBN+Rp*UJ<;@kLc0gIbWSq`b*+Fn#!Sq+i)NAtnFniOOK@n;h90#$^CFD@1NHSIC zw1FCjB#eI6(dk?AN2bZ?B;W-l6vg`-md}q2fC zaLqJ|A0la6T)@Ej5i6hf{h)&|QE_u5Ni|u}Llw%>e|wSn)-=a$wXaxjxhZrUp~yt9 z-DqXt)ftYyXF7n8&TTr_U`e3AS$;R1=VX9NjT~(GsUrD^R|c8~co#)pxNT-b=ZSu4 zk{qnP;!1AF1vpL!&_*xM(*n^E6OJ%FY0AKU(>%=D3hbC3P0Jemft^LMnAw=m)JgnuHiCKc8d3oiNnTi>%i<62QKX!M zD;}ihwxNx+t5qS1AKomLpR(B65A@_`Z;Uh83d09KW=+hk9~5q~w5w2;ePe3Z#PR%u z>E!ToSASnL$7r{bunU21Vb} zI-SsGGQ8Hh+V`7c5e6`Mfm{_UajeBoM7EY!E)_I$+TUG01-HZ4r|8uF5+&N+m2}Kb zqyM<)?R#V2c&M-jp^Lmbp<6`q4=$M;jn%QkG^Lke(jXB%V7b4RSokT2BT8To==hly|tgL5+fe4;)lIjdFRqJZqtaZwG{kO>OJ*NO9K=vZnW&@$LVvunSvtd#DYr7w5d%f zlpQe$kV$NW1$X9ExR!c?Fg~Nrw%wX3d>ptP17%F3-}et61f2NK*5d2hjLj_#^_U6V znEX=TGaXxMmf1sxZ*+#K0&!qcns;WR+NCCD{Ueq>{OjI;USv*`TTVtrp-4T^Nme*H z_9rl=DO3C}?gXq!2*f>g{x|;tNJFZBUPuY5jDL_*O}$&lX&X|VO0}B?g8U#Ir=4-^ zUT>o+YuSZJxC?oQptV0(d|y{%w!4a^x-q)?J9e$e*?F6Ep0*7SA!W9+PId)(Vf0_9 z>jOfjKKLqv&Emo*DqB!!U3WNVoG(cFwgoBMh)iQg60#S(QUvz6^>(mmy)q(*NCyk^ zv`kgfaK8x7$xc|Jl615YHHa?07nrQyo&4SL&|QA@heXV`YfO1w0Y2u&_yK)GO?ckk$ZeipCON24#ZkU+?d&r-yL+RKaOQun~os@ zz56;d04J5X9LP`I-)0A5aJkW?F={~+-{C?OtOO~;I!mQL`l56JgWOe zHHu2A^w}$LKno@h#~AT0vsycw#cV{$2U{Puz&(sruEh0QJ5O|CBZj#;FT4$tOQDZf z>Y``A-97$*Q#(3ujfEwZ|U0qV-hYP+>OtBN0>ReoUYVcEDPS-S4+(1q%iEZ=F(b6l=M?Cm_VY5A$UTA$S z3A$Ta0-jWYGE^k?%+m4R6-m|J29nxd6`VL{4KX0NyC7(;Z9wEQY{3{)^WTNK)%pw{ z3CT5cA4k1xuF+$Q>t=q${Y@N7^F-r&iES;Ajoxxx6Dgk|4XHVFR_sg3DYC(^$Q@tX zynz8N#~%Reauqv_HebGLPkuq4m_trd8nYg^>WLNBlDUHU?X9X)=!InWqEbgbvMN2RXXCO_ueW6h`a&g=5&Y?0%o$WhbIJc1c6 z5yTO&FN#8#4>^baLQd`4&>sZbOI1Ug{e-3t45J-yCO=0Feg3xcd0NWBf{K)3LziE} zKZQ_nN8uN%hTh*}81+awR&`Wax+)%qS>M-`Dl|7yu0maJMGVuFhl5u@ZTdy#u@pV1 z?e0t^l6MA3wqrp_>efXEkeU%@O_#7Rtf9O{wu7*bT(4@bV!)oZ#Z@7%6uCDDPx-~Q z=I#ztNxYAW9x|UKHzs#HpR~u8&ak7{?p(*1!FE7GvewMf^2uam%76U_yje2~+SEZoAud}Tos3&+_th!UHjMQJUMursYXn|@BI#KhxtUwj2apsb@MKizm{xpV{xu#5EbScvJsmI#F09cmJ00WLna1b?Jgl zTO05C@oRB(!h{HhbzC@g_oOCAj0#{!-srIEy}Vvu`B~{0vmbQuw!O!{P(oTdby?)Z5D0KiAwySUbj6@Cu!Mh>86>SV*8NKtks2 zGoJINp@%)qr`M4D$SN2IaHKKWAa^c-;lN{&vrNQM+@M`AvGnbkz&4H=fcvAdgyjvR z?u4Nmp2I&3{v7@EtYOE!l0Xe}VI6@1?hzKcX$g5@|6xsL1Ize

LP?s}>_8ENxMl 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 { exports.conf = { enabled: true, guildOnly: false, - aliases: ["calculate", "calc"], + aliases: ["calc", "math"], permLevel: "User", requiredPerms: [] }; exports.help = { - name: "math", + name: "calculate", category: "Utility", description: "Solves basic mathematical equations.", - usage: "math [equation]" + usage: "calculate [equation]" }; \ No newline at end of file diff --git a/src/commands/emoji.js b/src/commands/emoji.js index 795686c..ab6907b 100644 --- a/src/commands/emoji.js +++ b/src/commands/emoji.js @@ -11,7 +11,6 @@ exports.run = async (client, message, args) => { format = ".gif" }; - console.log(string.length) if(string.length > 18) { ID = string.slice(string.length - 18); } else { diff --git a/src/commands/identity.js b/src/commands/identity.js index 9be25a4..22e8693 100644 --- a/src/commands/identity.js +++ b/src/commands/identity.js @@ -6,8 +6,13 @@ exports.run = async (client, message, args) => { output += `${key}, ` }; return message.channel.send(`__**Identities**__\n${output.slice(0, -2)}`); - } else { - output = identities[args[0].toLowerCase()]; + } else { + if(args.join(" ").toLowerCase() == "attack helicopter" || args.join(" ").toLowerCase() == "apache attack helicopter" || args.join(" ").toLowerCase() == "apache") { + return message.channel.send({ + files: [new Discord.MessageAttachment("./resources/images/attackhelicopter.jpg")] + }); + } + output = identities[args.join(" ").toLowerCase()]; if(!output) { return message.channel.send("<:error:466995152976871434> No results for that query."); }; @@ -18,7 +23,7 @@ exports.run = async (client, message, args) => { exports.conf = { enabled: true, guildOnly: false, - aliases: [], + aliases: ["identities"], permLevel: "User", requiredPerms: [] }; diff --git a/src/commands/pronoun.js b/src/commands/pronoun.js new file mode 100644 index 0000000..a535647 --- /dev/null +++ b/src/commands/pronoun.js @@ -0,0 +1,36 @@ +const pronouns = require ("../../resources/other/pronouns.json"); +exports.run = async (client, message, args) => { + var output = ""; + if(!args[0]) { + for (var key of Object.keys(pronouns)) { + output += `${key}, ` + }; + return message.channel.send(`__**Pronouns**__\n${output.slice(0, -2)}`); + } else { + if(args.join(" ").toLowerCase() == "attack helicopter" || args.join(" ").toLowerCase() == "apache attack helicopter" || args.join(" ").toLowerCase() == "apache") { + return message.channel.send({ + files: [new Discord.MessageAttachment("./resources/images/attackhelicopter.jpg")] + }); + }; + output = pronouns[args.join(" ").toLowerCase()]; + if(!output) { + return message.channel.send("<:error:466995152976871434> No results for that query."); + }; + return message.channel.send(`__**Example sentences using ${output.name}**__\n${output.examples}`); + }; +}; + +exports.conf = { + enabled: true, + guildOnly: false, + aliases: ["pronouns"], + permLevel: "User", + requiredPerms: [] +}; + +exports.help = { + name: "pronoun", + category: "Fun", + description: "Gives you information on how to use the specified pronoun.", + usage: "pronoun [pronoun]" +}; diff --git a/src/commands/sexuality.js b/src/commands/sexuality.js new file mode 100644 index 0000000..8507dac --- /dev/null +++ b/src/commands/sexuality.js @@ -0,0 +1,36 @@ +const sexualities = require ("../../resources/other/sexualities.json"); +exports.run = async (client, message, args) => { + var output = ""; + if(!args[0]) { + for (var key of Object.keys(sexualities)) { + output += `${key}, ` + }; + return message.channel.send(`__**sexualities**__\n${output.slice(0, -2)}`); + } else { + if(args.join(" ").toLowerCase() == "attack helicopter" || args.join(" ").toLowerCase() == "apache attack helicopter" || args.join(" ").toLowerCase() == "apache") { + return message.channel.send({ + files: [new Discord.MessageAttachment("./resources/images/attackhelicopter.jpg")] + }); + } + output = sexualities[args.join(" ").toLowerCase()]; + if(!output) { + return message.channel.send("<:error:466995152976871434> No results for that query."); + }; + return message.channel.send(`__**${output.name}**__\n${output.description}`); + }; +}; + +exports.conf = { + enabled: true, + guildOnly: false, + aliases: ["sexualities"], + permLevel: "User", + requiredPerms: [] +}; + +exports.help = { + name: "sexuality", + category: "Fun", + description: "Gives you information about the specified sexuality.", + usage: "sexuality [sexuality]" +}; diff --git a/src/commands/ship.js b/src/commands/ship.js index 1fe3c57..8d8fef3 100644 --- a/src/commands/ship.js +++ b/src/commands/ship.js @@ -1,51 +1,42 @@ -const request = require('request') exports.run = async (client, message, args) => { - - //NOT FINISHED + var hearts = [ + "❤️", + "🧡", + "💛", + "💚", + "💙", + "💜" + ]; if(!args[0] || !args[1]) { - return message.channel.send(`<:error:466995152976871434> Please include two users`) + return message.channel.send(`<:error:466995152976871434> Please include at least two names.`) } - message.channel.startTyping(); - let users = []; + let names = []; let totalLength = 0; for(let i = 0; i < args.length; i++) { let arg = args[i]; - - let user = client.getUserFromMention(arg); - - if(!user) { - let usersFound; - usersFound = client.searchForMembers(message.guild, arg); - if (usersFound.length > 1) - return message.channel.send( - "<:error:466995152976871434> Found multiple users! Please be more specific or mention the user instead." - ); - else if (usersFound.length == 0) - return message.channel.send( - "<:error:466995152976871434> That user doesn't seem to exist. Try again!" - ); - user = usersFound[0].user; - } - - users.push(user); - totalLength += user.username.length; + let name = client.getUserFromMention(arg).username; + if(!name) { + name = arg; + }; + names.push(name); + totalLength += arg.length; } - let lengthPerUser = Math.floor(totalLength / users.length); + let lengthPerName = Math.floor(totalLength / names.length); let finalName = ''; let last = -1; - for(let i = 0; i < users.length; i++) { - let user = users[i]; - let l = Math.min(lengthPerUser, user.username.length); + for(let i = 0; i < names.length; i++) { + let name = names[i]; + let l = Math.min(lengthPerName, name.length); - let p = user.username.substr(last + 1, last + l); + let p = name.substr(last + 1, last + l); console.log(p); @@ -55,18 +46,11 @@ exports.run = async (client, message, args) => { }; console.log(totalLength); - console.log(users.length); - console.log(lengthPerUser); + console.log(names.length); + console.log(lengthPerName); console.log(finalName); - try { - //var attachment = new Discord.MessageAttachment(`https://api.alexflipnote.dev/ship?user=${user.avatarURL({format: "png"})}&user2=${user2.avatarURL({format: "png"})}`) - message.channel.send(`Your ship name is **${finalName}!**`) - message.channel.stopTyping(); - } catch(err) { - message.channel.send(`<:error:466995152976871434> API error: ${err}`); - message.channel.stopTyping(); - }; + message.channel.send(`**Ship generator:**\n${hearts.random()} Ship name: \`${finalName}\`\n${hearts.random()} Compatibility rating:`) }; exports.conf = { From 063a5ab4659577cc7088ded6f84c86d7b4ba516b Mon Sep 17 00:00:00 2001 From: mudkipscience Date: Mon, 23 Mar 2020 00:01:54 +1100 Subject: [PATCH 35/78] Synced changes --- changes.txt | 6 ++- src/commands/{creeper.js => dice.js} | 0 src/commands/diceroll.js | 2 +- src/commands/roleinfo.js | 45 +++++++++++++++++---- src/commands/ship.js | 59 ++++++++++------------------ src/events/message.js | 2 +- src/modules/functions.js | 6 +++ 7 files changed, 72 insertions(+), 48 deletions(-) rename src/commands/{creeper.js => dice.js} (100%) diff --git a/changes.txt b/changes.txt index 2648987..f121500 100644 --- a/changes.txt +++ b/changes.txt @@ -3,11 +3,15 @@ Links to avatars now lead to the original file size Bots now get a bot badge in the userinfo command Added dogfact and catfact command (terry) index.js now has better logging of when things fail to load/initialize (terry) -added `dice`, rolls a 6 sided die (terry) +added `dice`, rolls a sided die (terry) Help command changed, the amount of commands in each category and overall is now displayed and formatting changed added `inspire` as an alias for inspirobot ship command added find by mention to functions you can now @mention the bot to run commands added identity command, has definitions of gender identities and stuff +added pronouns command, tells you how to use pronouns +added sexuality command, has definitions of sexualities renamed math calculate +fixed woomy in dms +roleinfo now displays what permissions a role has and also the role colour in the thumbnail diff --git a/src/commands/creeper.js b/src/commands/dice.js similarity index 100% rename from src/commands/creeper.js rename to src/commands/dice.js diff --git a/src/commands/diceroll.js b/src/commands/diceroll.js index b307d4e..65900c8 100644 --- a/src/commands/diceroll.js +++ b/src/commands/diceroll.js @@ -22,6 +22,6 @@ exports.run = async (bot, message, args) => { name: "dice", category: "Fun", description: "Rolls a dice.", - usage: "dice" + usage: "dice **OR** dice " }; diff --git a/src/commands/roleinfo.js b/src/commands/roleinfo.js index f6fd0e0..e6c95a4 100644 --- a/src/commands/roleinfo.js +++ b/src/commands/roleinfo.js @@ -15,17 +15,48 @@ exports.run = async (client, message, args, level) => { return message.channel.send(`<:error:466995152976871434> Role not found.`) } - if(role.hoist === true) { - var hoist = `Yes` - } else { - var hoist = `No` - } + var permissions = "```"; + if(role.permissions.has("ADMINISTRATOR")) permissions += "ADMINISTRATOR, "; + if(role.permissions.has("CREATE_INSTANT_INVITE")) permissions += "CREATE_INSTANT_INVITE, "; + if(role.permissions.has("KICK_MEMBERS")) permissions += "KICK_MEMBERS, "; + if(role.permissions.has("BAN_MEMBERS")) permissions += "BAN_MEMBERS, "; + if(role.permissions.has("MANAGE_CHANNELS")) permissions += "MANAGE_CHANNELS, "; + if(role.permissions.has("MANAGE_GUILD")) permissions += "MANAGE_GUILD, "; + if(role.permissions.has("ADD_REACTIONS")) permissions += "ADD_REACTIONS, "; + if(role.permissions.has("VIEW_AUDIT_LOG")) permissions += "VIEW_AUDIT_LOG, "; + if(role.permissions.has("PRIORITY_SPEAKER")) permissions += "PRIORITY_SPEAKER, "; + if(role.permissions.has("STREAM")) permissions += "STREAM, "; + if(role.permissions.has("VIEW_CHANNEL")) permissions += "VIEW_CHANNEL, "; + if(role.permissions.has("SEND_MESSAGES")) permissions += "SEND_MESSAGES, "; + if(role.permissions.has("SEND_TTS_MESSAGES")) permissions += "SEND_TTS_MESSAGES, "; + if(role.permissions.has("MANAGE_MESSAGES")) permissions += "MANAGE_MESSAGES, "; + if(role.permissions.has("EMBED_LINKS")) permissions += "EMBED_LINKS, "; + if(role.permissions.has("ATTACH_FILES")) permissions += "ATTACH_FILES, "; + if(role.permissions.has("READ_MESSAGE_HISTORY")) permissions += "READ_MESSAGE_HISTORY, "; + if(role.permissions.has("MENTION_EVERYONE")) permissions += "MENTION_EVERYONE, "; + if(role.permissions.has("USE_EXTERNAL_EMOJIS")) permissions += "USE_EXTERNAL_EMOJIS, "; + if(role.permissions.has("CONNECT")) permissions += "CONNECT, "; + if(role.permissions.has("SPEAK")) permissions += "SPEAK, "; + if(role.permissions.has("MUTE_MEMBERS")) permissions += "MUTE_MEMBERS, "; + if(role.permissions.has("DEAFEN_MEMBERS")) permissions += "DEAFEN_MEMBERS, "; + if(role.permissions.has("MOVE_MEMBERS")) permissions += "MOVE_MEMBERS, "; + if(role.permissions.has("USE_VAD")) permissions += "USE_VAD, "; + if(role.permissions.has("CHANGE_NICKNAME")) permissions += "CHANGE_NICKNAME, "; + if(role.permissions.has("MANAGE_NICKNAMES")) permissions += "MANAGE_NICKNAMES, "; + if(role.permissions.has("MANAGE_ROLES")) permissions += "MANAGE_ROLES, "; + if(role.permissions.has("MANAGE_WEBHOOKS")) permissions += "MANAGE_WEBHOOKS, "; + if(role.permissions.has("MANAGE_EMOJIS")) permissions += "MANAGE_EMOJIS, "; + permissions = permissions.slice(0, -2); + permissions += "```"; var embed = new Discord.MessageEmbed(); - embed.setColor(role.color) + embed.setColor(role.color); + embed.setTitle(role.name); + embed.setThumbnail("https://api.alexflipnote.xyz/colour/image/" + role.hexColor.replace("#", "")); embed.setDescription( - `• **Name:** ${role.name}\n• **ID:** ${role.id}\n• **Hex:** ${role.hexColor}\n• **Members:** ${role.members.size}\n• **Position:** ${role.position}\n• **Hoisted:** ${hoist}` + `• **ID:** ${role.id}\n• **Hex:** ${role.hexColor}\n• **Members:** ${role.members.size}\n• **Position:** ${role.position}\n• **Hoisted:** ${role.hoist}` ); + embed.addField(`**Permissions:**`, permissions) message.channel.send(embed) }; diff --git a/src/commands/ship.js b/src/commands/ship.js index 8d8fef3..7a68e00 100644 --- a/src/commands/ship.js +++ b/src/commands/ship.js @@ -1,5 +1,7 @@ exports.run = async (client, message, args) => { + var name, name1; + var rating = Math.floor(Math.random() * 100) + 1; var hearts = [ "❤️", "🧡", @@ -9,53 +11,34 @@ exports.run = async (client, message, args) => { "💜" ]; - if(!args[0] || !args[1]) { - return message.channel.send(`<:error:466995152976871434> Please include at least two names.`) + if(args.length < 2) { + return message.channel.send(`<:error:466995152976871434> Please include two names/users.`) } - let names = []; - let totalLength = 0; - - for(let i = 0; i < args.length; i++) { - let arg = args[i]; - let name = client.getUserFromMention(arg).username; - if(!name) { - name = arg; - }; - names.push(name); - totalLength += arg.length; - } - - let lengthPerName = Math.floor(totalLength / names.length); - - let finalName = ''; - - let last = -1; - - for(let i = 0; i < names.length; i++) { - let name = names[i]; - let l = Math.min(lengthPerName, name.length); - - let p = name.substr(last + 1, last + l); - - console.log(p); - - finalName = finalName + p; - - last = last + l; + if(message.guild && message.mentions.members && message.mentions.members.size > 0) { + name = message.mentions.members.first().displayName; }; - console.log(totalLength); - console.log(names.length); - console.log(lengthPerName); - console.log(finalName); + if(message.guild && message.mentions.members && message.mentions.members.size > 1) { + name1 = message.mentions.members.last().displayName; + }; - message.channel.send(`**Ship generator:**\n${hearts.random()} Ship name: \`${finalName}\`\n${hearts.random()} Compatibility rating:`) + if(!name) { + name = args[0]; + }; + + if(!name1) { + name1 = args[1]; + }; + + shipName = name.substring(0, client.intBetween(1,name.length))+name1.substring(client.intBetween(0,name1.length)); + + message.channel.send(`__**Ship Generator:**__\n${hearts.random()} Ship Name: \`${shipName}\`\n${hearts.random()} Compatibility rating: \`${rating}%\``) }; exports.conf = { enabled: true, - guildOnly: true, + guildOnly: false, aliases: [], permLevel: "User", requiredPerms: [] diff --git a/src/events/message.js b/src/events/message.js index 457706b..0dfc019 100644 --- a/src/events/message.js +++ b/src/events/message.js @@ -158,7 +158,7 @@ module.exports = async (client, message) => { .then(m => m.delete(2000)); }; - if (!perms.has('SEND_MESSAGES')) { + if (message.guild && !perms.has('SEND_MESSAGES')) { return message.author.send(`<:error:466995152976871434> I don't have permission to speak in **#${message.channel.name}**, Please ask a moderator to give me the send messages permission!`); }; diff --git a/src/modules/functions.js b/src/modules/functions.js index bd30579..f51a34d 100644 --- a/src/modules/functions.js +++ b/src/modules/functions.js @@ -340,6 +340,12 @@ module.exports = client => { }; }; + // FIND RANDOM INT BETWEEN TWO INTEGERS + client.intBetween = function(min, max){ + return Math.round((Math.random() * (max - min))+min); + }; + + // .toPropercase() returns a proper-cased string Object.defineProperty(String.prototype, "toProperCase", { value: function() { From 0fc9659b9ca6231bdfe6c3169c7d528a400b9516 Mon Sep 17 00:00:00 2001 From: mudkipscience Date: Mon, 23 Mar 2020 00:02:30 +1100 Subject: [PATCH 36/78] oosp forgot a thing --- src/commands/ship.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/ship.js b/src/commands/ship.js index 7a68e00..b11a988 100644 --- a/src/commands/ship.js +++ b/src/commands/ship.js @@ -48,6 +48,6 @@ exports.help = { name: "ship", category: "Fun", description: "Ship two people together <3", - usage: "ship name name2" + usage: "ship [name/user] [name/user]" }; From 55d66f348d423decc5ddf29357b6c455cb896799 Mon Sep 17 00:00:00 2001 From: mudkipscience Date: Tue, 24 Mar 2020 05:47:26 +0000 Subject: [PATCH 37/78] Fixed woomy in DM's --- src/events/message.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/events/message.js b/src/events/message.js index 3505c1c..8904399 100644 --- a/src/events/message.js +++ b/src/events/message.js @@ -152,7 +152,7 @@ module.exports = async (client, message) => { .then(m => m.delete(2000)); }; - if (!perms.has('SEND_MESSAGES')) { + if (message.guild && !perms.has('SEND_MESSAGES')) { return message.author.send(`<:error:466995152976871434> I don't have permission to speak in **#${message.channel.name}**, Please ask a moderator to give me the send messages permission!`); }; @@ -224,4 +224,4 @@ module.exports = async (client, message) => { client.logger.cmd(`${client.config.permLevels.find(l => l.level === level).name} ${message.author.username} (${message.author.id}) ran command ${cmd.help.name}`); cmd.run(client, message, args, level); -}; \ No newline at end of file +}; From 51e307b3d63689646e9f40a52fe66a837882f5b0 Mon Sep 17 00:00:00 2001 From: mudkipscience Date: Tue, 24 Mar 2020 05:47:41 +0000 Subject: [PATCH 38/78] Update version.json --- version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.json b/version.json index edc38f2..b7c8929 100644 --- a/version.json +++ b/version.json @@ -1,4 +1,4 @@ { - "number": "1.1.1", + "number": "1.1.2", "changelog": "**1.1.0 CHANGELOG:**\n> • Added `~softban`, bans and unbans a user to clear messages\n> • Added `~emoji`, enlarges custom emojis\n> • Added `~inspirobot`, generates an inspirational quote\n> • `~serverinfo` has been changed to be more consistent, and also now displays boosts and if the server is partnered and stuff\n> • `~userinfo` has been changed to be more consistent, also added some stuff\n> • `~about` has been changed, added a thumbnail and removed the description\n> • `~colour` has been changed, it can now generate colours from text\n> • `~hackban` no longer has its own embed\n> • `~eval` now logs to hastebin if output is too large\n> • role names are no longer case sensitive\n> • `~echo` renamed `~say`\n> • Users with the ADMINISTRATOR permission now automatically recieve woomy admin\n> • Fixed `~flip`, `~purge`, `~bohemian_rhapsody` and `~creeper`\n> • Guild join/leave messages no longer include the guild name\n> • Some emojis have been changed\n> • Woomy now supports discord.js v12\n> • Files have been restructured\n> • Logger now logs error stack\n> • Restart now exits with code 0" } From 7fdeceee670d0d495f74810908f7ce415aacc67c Mon Sep 17 00:00:00 2001 From: mudkipscience Date: Tue, 24 Mar 2020 09:05:32 +0000 Subject: [PATCH 39/78] Made readme friendlier --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 693f553..d0fde98 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,16 @@ # Woomy -Woomy is a all-purpose discord bot built off the [guidebot](https://github.com/AnIdiotsGuide/guidebot) base and coded in node.js using discord.js. +Woomy is a all-purpose discord bot built off the [guidebot](https://github.com/AnIdiotsGuide/guidebot) base and coded in node.js using discord.js. # How to use The easiest way to use Woomy is to invite it to your server with [this link.](https://discordapp.com/oauth2/authorize?client_id=435961704145485835&permissions=2134240503&scope=bot) It is hosted 24/7 and automatically updates itself when a new release is made available, making sure you always get the newest features. -Self hosting is generally not recommended, but instructions are provided below if you still wish to do so. Woomy's code will need to be modified before it will run on your machine. +You can also self-host! Some modificatiomns to the code will need to be made before Woomy will run on your machine, but anyone who can read errors will figure out what needs to be changed pretty quickly :P # Requirements - git - node.js v12.0.0 or higher - node-gyp build tools -- ffmpeg +- ffmpeg (or ffmpeg-static) # Installation - Clone Woomy to your machine @@ -18,4 +18,4 @@ Self hosting is generally not recommended, but instructions are provided below i - Open config.js in your code editor and insert all the required information # Contributing -If you wish to contribute to Woomy, please fork the repository and open a pull request. +If you wish to contribute to Woomy, please fork the repository and open a pull request. Any contribution is appreciated <3 From 3ec4d08763874f06912cb8d1d31615a90e9df260 Mon Sep 17 00:00:00 2001 From: mudkipscience Date: Thu, 26 Mar 2020 15:31:32 +1100 Subject: [PATCH 40/78] Update time woo --- changes.txt | 22 ++++++ index.js | 98 ++++++++------------------- package.json | 9 +-- src/commands/about.js | 2 +- src/commands/cat.js | 30 ++++++++ src/commands/catfact.js | 12 ++-- src/commands/{flip.js => coinflip.js} | 6 +- src/commands/credits.js | 4 +- src/commands/creeper.js | 45 ++++++++++++ src/commands/cuddle.js | 69 +++++++++++++++++++ src/commands/dice.js | 70 +++++++------------ src/commands/diceroll.js | 27 -------- src/commands/dog.js | 30 ++++++++ src/commands/dogfact.js | 17 +++-- src/commands/emojify.js | 2 +- src/commands/fact.js | 30 ++++++++ src/commands/feed.js | 69 +++++++++++++++++++ src/commands/feedback.js | 2 +- src/commands/foxgirl.js | 30 ++++++++ src/commands/help.js | 25 +++---- src/commands/hug.js | 69 +++++++++++++++++++ src/commands/identity.js | 2 +- src/commands/inspirobot.js | 25 +++---- src/commands/kemonomimi.js | 30 ++++++++ src/commands/kiss.js | 69 +++++++++++++++++++ src/commands/lizard.js | 30 ++++++++ src/commands/neko.js | 30 ++++++++ src/commands/nekogif.js | 30 ++++++++ src/commands/owoify.js | 4 +- src/commands/pat.js | 69 +++++++++++++++++++ src/commands/poke.js | 69 +++++++++++++++++++ src/commands/pronoun.js | 4 +- src/commands/roleinfo.js | 1 - src/commands/sexuality.js | 4 +- src/commands/ship.js | 2 +- src/commands/slap.js | 69 +++++++++++++++++++ src/commands/smug.js | 33 +++++++++ src/commands/spoilerise.js | 28 ++++++++ src/commands/support.js | 2 +- src/commands/tickle.js | 69 +++++++++++++++++++ src/commands/weather.js | 75 ++++++++++---------- src/commands/yoda.js | 37 +++++----- src/commands/zalgo.js | 30 ++++++++ src/events/ready.js | 19 ++++-- src/modules/functions.js | 19 +++--- 45 files changed, 1139 insertions(+), 279 deletions(-) create mode 100644 src/commands/cat.js rename src/commands/{flip.js => coinflip.js} (90%) create mode 100644 src/commands/creeper.js create mode 100644 src/commands/cuddle.js delete mode 100644 src/commands/diceroll.js create mode 100644 src/commands/dog.js create mode 100644 src/commands/fact.js create mode 100644 src/commands/feed.js create mode 100644 src/commands/foxgirl.js create mode 100644 src/commands/hug.js create mode 100644 src/commands/kemonomimi.js create mode 100644 src/commands/kiss.js create mode 100644 src/commands/lizard.js create mode 100644 src/commands/neko.js create mode 100644 src/commands/nekogif.js create mode 100644 src/commands/pat.js create mode 100644 src/commands/poke.js create mode 100644 src/commands/slap.js create mode 100644 src/commands/smug.js create mode 100644 src/commands/spoilerise.js create mode 100644 src/commands/tickle.js create mode 100644 src/commands/zalgo.js diff --git a/changes.txt b/changes.txt index f121500..8ca32e2 100644 --- a/changes.txt +++ b/changes.txt @@ -15,3 +15,25 @@ added sexuality command, has definitions of sexualities renamed math calculate fixed woomy in dms roleinfo now displays what permissions a role has and also the role colour in the thumbnail +request has been replaced with node-fetch +added neko +added fact +added nekogif +added dog +added cat +added hug +added kiss +added pat +added poke +added slap +added tickle +added cuddle +added feed +added smug +added lizard +added kemonomimi +added spoilerise +added zalgo +renamed flip coinflip +small changes to weather +recategorised some commands diff --git a/index.js b/index.js index 4e535ee..395e910 100644 --- a/index.js +++ b/index.js @@ -1,84 +1,55 @@ +if (Number(process.version.slice(1).split(".")[0]) < 12) { + throw new Error("Node 12.0.0 or higher is required. Please update Node on your system."); +}; + const Discord = require('discord.js'); const { promisify } = require('util'); const readdir = promisify(require('fs').readdir); const Enmap = require('enmap'); const chalk = require('chalk'); -const DBL = require("dblapi.js"); const client = new Discord.Client(); try { -client.config = require('./config'); + client.config = require('./config'); } catch (err) { - console.log('Could not load config.js: \n', err); - process.exit(); -} - + console.log('Failed to load config.js:', err); + process.exit(); +}; try{ -client.version = require('./version.json'); + client.version = require('./version.json'); } catch (err) { - console.log('Could not load version.json: \n', err); - process.exit(); -} - + console.log('Failed to load version.json:', err); + process.exit(); +}; try{ -client.logger = require('./src/modules/Logger'); + client.logger = require('./src/modules/Logger'); } catch (err) { - console.log('Could not load Logger.js: \n', err); - process.exit(); -} - -try{ -require("./src/modules/functions")(client); -} catch (err) { - console.log('Could not load functions.js: \n', err); - process.exit(); -} - -try{ + console.log('Failed to load Logger.js:', err); + process.exit(); +}; client.logger.setClient(client); + +try{ + require("./src/modules/functions")(client); } catch (err) { - console.log('Logger failed to initialize: \n', err); - process.exit(1); -} + console.log('Failed to load functions.js:', err); + process.exit(); +}; if(process.env['USER'] != 'container') { client.devmode = true; } else { client.devmode = false; - if(client.config.dblkey.length == 0) { + if(client.config.dblkey.length > 0) { + const DBL = require("dblapi.js"); const dblapi = new DBL(client.config.dblkey, client); - } -} + }; +}; -try{ client.commands = new Enmap(); -} catch (err) { - console.log('Failed to create the commands database: \n', err); - process.exit(); -} - -try{ client.aliases = new Enmap(); -} catch (err) { - console.log('Failed to create the aliases database: \n', err); - process.exit(); -} - -try{ client.settings = new Enmap({name: 'settings'}); -} catch (err) { - console.log('Failed to initialize the settings database: \n', err); - process.exit(); -} -try{ -client.blacklist = new Enmap({name: 'blacklist'}); -} catch (err) { - console.log('Failed to initialize the blacklist database: \n', err); - process.exit(1); -} - -try{ const init = async () => { const cmdFiles = await readdir("./src/commands/"); client.logger.info(`Loading ${cmdFiles.length} commands.`); @@ -103,30 +74,17 @@ const init = async () => { client.on(eventName, event.bind(null, client)); }); - try{ client.levelCache = {}; for (let i = 0; i < client.config.permLevels.length; i++) { const thisLevel = client.config.permLevels[i]; client.levelCache[thisLevel.name] = thisLevel.level; }; -} catch (err) { - console.log('Level cache failed to initialize: \n', err); - process.exit(); -} - try{ if(client.devmode === true) { client.login(client.config.devtoken); } else { client.login(client.config.token); }; -} catch (err) { - console.log('Could not login to Discord: \n', err); - process.exit(1); -} }; -init(); -} catch (err) { - console.log('Initialization failed: \n', err); - process.exit(1); -} + +init(); \ No newline at end of file diff --git a/package.json b/package.json index 901ffbf..4109849 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "main": "index.js", "dependencies": { "@discordjs/opus": "^0.1.0", - "better-sqlite3": "^5.4.1", + "better-sqlite3": "^5.4.3", "chalk": "^3.0.0", "dblapi.js": "^2.3.1", "discord.js": "^12.0.2", @@ -13,16 +13,17 @@ "garfield": "^1.1.2", "get-youtube-id": "^1.0.1", "hastebin-gen": "^2.0.5", - "is-url": "^1.2.4", "moment": "^2.24.0", "moment-duration-format": "^2.3.2", + "nekos.life": "^2.0.5", + "node-fetch": "^2.6.0", + "openweather-apis": "^4.2.0", "prism-media": "^1.2.1", "randomcolor": "^0.5.4", "relevant-urban": "^2.0.0", "request": "^2.88.2", + "to-zalgo": "^1.0.1", "urban": "^0.3.2", - "url-unshort": "^5.0.0", - "url-unshorten": "^1.0.6", "weather-js": "^2.0.0", "youtube-info": "^1.3.2", "ytdl-core-discord": "^1.1.0" diff --git a/src/commands/about.js b/src/commands/about.js index ad1666b..2049bfa 100644 --- a/src/commands/about.js +++ b/src/commands/about.js @@ -26,9 +26,9 @@ exports.run = (client, message) => { } embed = new Discord.MessageEmbed(); - embed.setTitle(`Woomy`); embed.setColor(client.embedColour(message)); embed.setThumbnail(client.user.avatarURL({format: "png", dynamic: true, size: 2048})) + embed.setTitle("About Woomy") embed.addField( "General:", `• users: \`${client.users.cache.size}\`\n• channels: \`${client.channels.cache.size}\`\n• servers: \`${client.guilds.cache.size}\`\n• commands: \`${client.commands.size}\`\n• uptime: \`${duration}\``,true ); diff --git a/src/commands/cat.js b/src/commands/cat.js new file mode 100644 index 0000000..afd627f --- /dev/null +++ b/src/commands/cat.js @@ -0,0 +1,30 @@ +const API = require('nekos.life'); +const {sfw} = new API(); +exports.run = async (client, message) => { + message.channel.startTyping(); + try { + sfw.meow().then((json) => { + message.channel.send(json.url) + message.channel.stopTyping(); + }); + } catch (err) { + client.logger.error("cat.js: " + err); + message.channel.send(`<:error:466995152976871434> An error has occurred: ${err}`) + message.channel.stopTyping(); + }; +}; + +exports.conf = { + enabled: true, + guildOnly: false, + aliases: [], + permLevel: "User", + requiredPerms: ["EMBED_LINKS"] +}; + +exports.help = { + name: "cat", + category: "Image", + description: "Sends you cat pics.", + usage: "cat" +}; diff --git a/src/commands/catfact.js b/src/commands/catfact.js index 9fef875..a58aae4 100644 --- a/src/commands/catfact.js +++ b/src/commands/catfact.js @@ -1,14 +1,12 @@ -const request = require("request"); - +const fetch = require("node-fetch") exports.run = async (bot, message, args) => { message.channel.startTyping(); try{ - request({ uri: "https://catfact.ninja/facts", json: true }, (error, response, body) => { - message.channel.send(`**Did you know?**\n ${body.data[0].fact}`); - message.channel.stopTyping(); - }); + fetch('https://catfact.ninja/facts') + .then(res => res.json()) + .then(json => message.channel.send(`__**Did you know?**__\n${json.data[0].fact}`)) } catch(err) { - message.channel.send(`<:error:466995152976871434> API error: ${err}`); + message.channel.send(`<:error:466995152976871434> An error has occurred: ${err}`); message.channel.stopTyping(); }; }; diff --git a/src/commands/flip.js b/src/commands/coinflip.js similarity index 90% rename from src/commands/flip.js rename to src/commands/coinflip.js index 99ba778..418a996 100644 --- a/src/commands/flip.js +++ b/src/commands/coinflip.js @@ -22,14 +22,14 @@ exports.run = (client, message, args) => { exports.conf = { enabled: true, guildOnly: false, - aliases: [], + aliases: ["flip"], permLevel: "User", requiredPerms: [] }; exports.help = { - name: "flip", + name: "coinflip", category: "Fun", description: "Flips a coin!", - usage: "flip [heads/tails]" + usage: "coinflip [heads/tails]" }; diff --git a/src/commands/credits.js b/src/commands/credits.js index b3f8516..5eb48ce 100644 --- a/src/commands/credits.js +++ b/src/commands/credits.js @@ -1,6 +1,6 @@ exports.run = async (client, message, args) => { message.channel.send( - `**Credits:**\n• \`mudkipscience#8904\` and \`FLGX#9896\`for developing the bot\n• \`An Idiots Guide\` for the Guidebot bot base\n• \`dellannie#6057\` for helping with the music commands\n• \`TheCakeChicken#9088\` and \`Tina the Cyclops girl#0064\` for helping me not suck at coding\n• \`AirVentTrent\` for the icon, find him on Instagram` + `__**Credits:**__\n• \`mudkipscience#8904\`, \`FLGX#9896\` and \`TheCakeChicken#9088\` for developing the bot\n• \`An Idiots Guide\` for the Guidebot bot base\n• \`Tina the Cyclops girl#0064\` for helping me not suck at coding\n• \`AirVentTrent\` for the icon, find him on Instagram\n• \`Terryiscool160\` for contributing to Woomy.` ); }; @@ -14,7 +14,7 @@ exports.conf = { exports.help = { name: "credits", - category: "Miscellaneous", + category: "Utility", description: "Cool people", usage: "credits" }; diff --git a/src/commands/creeper.js b/src/commands/creeper.js new file mode 100644 index 0000000..80ac90b --- /dev/null +++ b/src/commands/creeper.js @@ -0,0 +1,45 @@ + +const lyric = require('../../resources/other/lyrics.json') +exports.run = async (client, message, args, level) => { + var lyrics = lyric.creeper; + + var runtop = true; + var runbottom = false; + for(var br = 0; br < lyrics.length; br++) { + { + if (runtop === true) { + var response = await client.awaitReply(message, lyrics[br]); + runbottom = false; + }; + + if (runbottom === true) { + if (response !== lyrics[br]) { + return message.channel.send("Those aren't the lyrics!") + } + runtop = false + }; + } if (runtop === true) { + runtop = false + runbottom = true + } else if (runbottom === true) { + runtop = true + runbottom = false + } + } + message.channel.send("What a lovely duet!") +}; + +exports.conf = { + enabled: true, + guildOnly: false, + aliases: [], + permLevel: "User", + requiredPerms: [] +}; + +exports.help = { + name: "creeper", + category: "Fun", + description: "Aww man", + usage: "creeper" +}; diff --git a/src/commands/cuddle.js b/src/commands/cuddle.js new file mode 100644 index 0000000..63263ae --- /dev/null +++ b/src/commands/cuddle.js @@ -0,0 +1,69 @@ +const API = require('nekos.life'); +const {sfw} = new API(); +exports.run = async (client, message, args) => { + if(!args[0]) { + return message.channel.send(`<:error:466995152976871434> You didn't say who you wanted to cuddle! Usage: \`${client.commands.get(`cuddle`).help.usage}\``) + }; + + var people = ""; + + for (var i = 0; i < args.length; i++) { + var user = client.getUserFromMention(args[i]) + if (user) { + user = message.guild.members.cache.get(user.id).displayName; + } else { + users = client.searchForMembers(message.guild, args[i]); + if (users.length > 1) + return message.channel.send( + "<:error:466995152976871434> Found multiple users for `" + args[i] + "`, Please be more specific or mention the user instead." + ); + else if (users.length == 0) + return message.channel.send( + "<:error:466995152976871434> That user doesn't seem to exist. Try again!" + ); + user = users[0].displayName; + }; + if(i+1 == args.length && args.length > 1) { + people += `**and** ${user}!` + } else if(args.length < 2) { + people += `${user}!`; + } else if(args.length == 2 && i == 0) { + people += `${user} `; + } else { + people += `${user}, `; + }; + }; + + + + message.channel.startTyping(); + try { + sfw.cuddle().then((json) => { + embed = new Discord.MessageEmbed(); + embed.setImage(json.url) + embed.setColor(client.embedColour(message)); + embed.setDescription(`**${message.guild.members.cache.get(message.author.id).displayName}** cuddled **${people}**`) + message.channel.send(embed) + message.channel.stopTyping(); + }); + } catch (err) { + client.logger.error("cuddle.js: " + err); + message.channel.send(`<:error:466995152976871434> An error has occurred: ${err}`) + message.channel.stopTyping(); + }; +}; + +exports.conf = { + enabled: true, + guildOnly: true, + aliases: [], + permLevel: "User", + requiredPerms: ["EMBED_LINKS"] +}; + +exports.help = { + name: "cuddle", + category: "Action", + description: "cuddle someone!", + usage: "cuddle [@user/user] (you can cuddle as many people as you want!)" +}; diff --git a/src/commands/dice.js b/src/commands/dice.js index 80ac90b..be9edcf 100644 --- a/src/commands/dice.js +++ b/src/commands/dice.js @@ -1,45 +1,27 @@ - -const lyric = require('../../resources/other/lyrics.json') -exports.run = async (client, message, args, level) => { - var lyrics = lyric.creeper; - - var runtop = true; - var runbottom = false; - for(var br = 0; br < lyrics.length; br++) { - { - if (runtop === true) { - var response = await client.awaitReply(message, lyrics[br]); - runbottom = false; - }; - - if (runbottom === true) { - if (response !== lyrics[br]) { - return message.channel.send("Those aren't the lyrics!") +exports.run = async (bot, message, args) => { + if (args.length === 0) { + message.channel.send(`🎲 You rolled a ${Array.from(Array(6).keys()).random() + 1}!`); + } else { + if (args[0].match(/^\d+$/)) { + message.channel.send(`🎲 You rolled a ${Array.from(Array(parseInt(args[0])).keys()).random() + 1}!`); + } else { + message.channel.send(`🎲 You rolled a ${Array.from(Array(6).keys()).random() + 1}!`); } - runtop = false - }; - } if (runtop === true) { - runtop = false - runbottom = true - } else if (runbottom === true) { - runtop = true - runbottom = false - } - } - message.channel.send("What a lovely duet!") -}; - -exports.conf = { - enabled: true, - guildOnly: false, - aliases: [], - permLevel: "User", - requiredPerms: [] -}; - -exports.help = { - name: "creeper", - category: "Fun", - description: "Aww man", - usage: "creeper" -}; + } + }; + + exports.conf = { + enabled: true, + guildOnly: false, + aliases: ["diceroll", "roll"], + permLevel: "User", + requiredPerms: [] + }; + + exports.help = { + name: "dice", + category: "Fun", + description: "Rolls a dice.", + usage: "dice " + }; + diff --git a/src/commands/diceroll.js b/src/commands/diceroll.js deleted file mode 100644 index 65900c8..0000000 --- a/src/commands/diceroll.js +++ /dev/null @@ -1,27 +0,0 @@ -exports.run = async (bot, message, args) => { - if (args.length === 0) { - message.channel.send(`🎲 You rolled a ${Array.from(Array(6).keys()).random() + 1}!`); - } else { - if (args[0].match(/^\d+$/)) { - message.channel.send(`🎲 You rolled a ${Array.from(Array(parseInt(args[0])).keys()).random() + 1}!`); - } else { - message.channel.send(`🎲 You rolled a ${Array.from(Array(6).keys()).random() + 1}!`); - } - } - }; - - exports.conf = { - enabled: true, - guildOnly: false, - aliases: ["diceroll"], - permLevel: "User", - requiredPerms: [] - }; - - exports.help = { - name: "dice", - category: "Fun", - description: "Rolls a dice.", - usage: "dice **OR** dice " - }; - diff --git a/src/commands/dog.js b/src/commands/dog.js new file mode 100644 index 0000000..39ce25c --- /dev/null +++ b/src/commands/dog.js @@ -0,0 +1,30 @@ +const API = require('nekos.life'); +const {sfw} = new API(); +exports.run = async (client, message) => { + message.channel.startTyping(); + try { + sfw.woof().then((json) => { + message.channel.send(json.url) + message.channel.stopTyping(); + }); + } catch (err) { + client.logger.error("dog.js: " + err); + message.channel.send(`<:error:466995152976871434> An error has occurred: ${err}`) + message.channel.stopTyping(); + }; +}; + +exports.conf = { + enabled: true, + guildOnly: false, + aliases: [], + permLevel: "User", + requiredPerms: ["EMBED_LINKS"] +}; + +exports.help = { + name: "dog", + category: "Image", + description: "Sends you dog pics.", + usage: "dog" +}; diff --git a/src/commands/dogfact.js b/src/commands/dogfact.js index 31d1639..d0b80ed 100644 --- a/src/commands/dogfact.js +++ b/src/commands/dogfact.js @@ -1,16 +1,15 @@ -const request = require("request"); - +const fetch = require("node-fetch"); exports.run = async (bot, message, args) => { message.channel.startTyping(); try{ - request({ uri: "https://dog-api.kinduff.com/api/facts", json: true }, (error, response, body) => { - message.channel.send(`**Did you know?**\n ${body.facts[0]}`); + fetch('https://dog-api.kinduff.com/api/facts') + .then(res => res.json()) + .then(json => message.channel.send(`__**Did you know?**__\n ${json.facts[0]}`)); message.channel.stopTyping(); - }); -} catch(err) { - message.channel.send(`<:error:466995152976871434> API error: ${err}`); - message.channel.stopTyping(); -}; + } catch(err) { + message.channel.send(`<:error:466995152976871434> An error has occurred: ${err}`); + message.channel.stopTyping(); + }; }; exports.conf = { diff --git a/src/commands/emojify.js b/src/commands/emojify.js index 058faa3..46dfa35 100644 --- a/src/commands/emojify.js +++ b/src/commands/emojify.js @@ -34,7 +34,7 @@ exports.run = (client, message, args) => { if(emojified.length > 2000) { return message.channel.send("<:error:466995152976871434> The emojified message exceeds 2000 characters.") - } + }; message.channel.send(emojified); }; diff --git a/src/commands/fact.js b/src/commands/fact.js new file mode 100644 index 0000000..0e351a6 --- /dev/null +++ b/src/commands/fact.js @@ -0,0 +1,30 @@ +const API = require('nekos.life'); +const {sfw} = new API(); +exports.run = async (client, message) => { + message.channel.startTyping(); + try { + sfw.fact().then((json) => { + message.channel.send("__**Did you know?**__\n" + json.fact + "."); + message.channel.stopTyping(); + }); + } catch (err) { + client.logger.error("fact.js: " + err); + message.channel.send(`<:error:466995152976871434> An error has occurred: ${err}`) + message.channel.stopTyping(); + }; +}; + +exports.conf = { + enabled: true, + guildOnly: false, + aliases: ["randomfact"], + permLevel: "User", + requiredPerms: [] +}; + +exports.help = { + name: "fact", + category: "Fun", + description: "Sends you a random fact.", + usage: "fact" +}; diff --git a/src/commands/feed.js b/src/commands/feed.js new file mode 100644 index 0000000..9749f45 --- /dev/null +++ b/src/commands/feed.js @@ -0,0 +1,69 @@ +const API = require('nekos.life'); +const {sfw} = new API(); +exports.run = async (client, message, args) => { + if(!args[0]) { + return message.channel.send(`<:error:466995152976871434> You didn't say who you wanted to feed! Usage: \`${client.commands.get(`feed`).help.usage}\``) + }; + + var people = ""; + + for (var i = 0; i < args.length; i++) { + var user = client.getUserFromMention(args[i]) + if (user) { + user = message.guild.members.cache.get(user.id).displayName; + } else { + users = client.searchForMembers(message.guild, args[i]); + if (users.length > 1) + return message.channel.send( + "<:error:466995152976871434> Found multiple users for `" + args[i] + "`, Please be more specific or mention the user instead." + ); + else if (users.length == 0) + return message.channel.send( + "<:error:466995152976871434> That user doesn't seem to exist. Try again!" + ); + user = users[0].displayName; + }; + if(i+1 == args.length && args.length > 1) { + people += `**and** ${user}!` + } else if(args.length < 2) { + people += `${user}!`; + } else if(args.length == 2 && i == 0) { + people += `${user} `; + } else { + people += `${user}, `; + }; + }; + + + + message.channel.startTyping(); + try { + sfw.feed().then((json) => { + embed = new Discord.MessageEmbed(); + embed.setImage(json.url) + embed.setColor(client.embedColour(message)); + embed.setDescription(`**${message.guild.members.cache.get(message.author.id).displayName}** fed **${people}**`) + message.channel.send(embed) + message.channel.stopTyping(); + }); + } catch (err) { + client.logger.error("feed.js: " + err); + message.channel.send(`<:error:466995152976871434> An error has occurred: ${err}`) + message.channel.stopTyping(); + }; +}; + +exports.conf = { + enabled: true, + guildOnly: true, + aliases: [], + permLevel: "User", + requiredPerms: ["EMBED_LINKS"] +}; + +exports.help = { + name: "feed", + category: "Action", + description: "feed someone!", + usage: "feed [@user/user] (you can feed as many people as you want!)" +}; diff --git a/src/commands/feedback.js b/src/commands/feedback.js index 9f4e436..6b4512b 100644 --- a/src/commands/feedback.js +++ b/src/commands/feedback.js @@ -22,7 +22,7 @@ exports.conf = { exports.help = { name: "feedback", - category: "Miscellaneous", + category: "Utility", description: "Send feedback to my developer.", usage: "feedback [message]" }; diff --git a/src/commands/foxgirl.js b/src/commands/foxgirl.js new file mode 100644 index 0000000..f3f3312 --- /dev/null +++ b/src/commands/foxgirl.js @@ -0,0 +1,30 @@ +const API = require('nekos.life'); +const {sfw} = new API(); +exports.run = async (client, message) => { + message.channel.startTyping(); + try { + sfw.foxGirl().then((json) => { + message.channel.send(json.url) + message.channel.stopTyping(); + }); + } catch (err) { + client.logger.error("foxgirl.js: " + err); + message.channel.send(`<:error:466995152976871434> An error has occurred: ${err}`) + message.channel.stopTyping(); + }; +}; + +exports.conf = { + enabled: true, + guildOnly: false, + aliases: [], + permLevel: "User", + requiredPerms: ["EMBED_LINKS"] +}; + +exports.help = { + name: "foxgirl", + category: "Image", + description: "Sends you pictures of fox girls.", + usage: "foxgirl" +}; diff --git a/src/commands/help.js b/src/commands/help.js index 0dd8acf..5c07963 100644 --- a/src/commands/help.js +++ b/src/commands/help.js @@ -3,7 +3,7 @@ exports.run = (client, message, args, level) => { embed.setColor(client.embedColour(message)); var ran = false; - var output = "```"; + var output = ""; var commands = 0; var prefix; var currentCategory; @@ -15,7 +15,7 @@ exports.run = (client, message, args, level) => { }; if(!args[0]) { - embed.setTitle(`Commands [${client.commands.size}]`); + embed.setTitle(`Command list`); embed.setDescription(`⁣For more information on a specific command use \`${prefix}help \`\nFor the full command list use \`${prefix}help all\`\n`); const myCommands = message.guild ? client.commands.filter( @@ -35,24 +35,20 @@ exports.run = (client, message, args, level) => { ); sorted.forEach( c => { - const cat = c.help.category.toProperCase(); + const cat = c.help.category; if (currentCategory !== cat) { if(ran == true) { - output = output.slice(0, -2) + "```"; embed.addField(currentCategory + ` [${commands}]`, output) - output = "```"; + output = ""; commands = 0; } currentCategory = cat; ran = true } - output += `${prefix}${c.help.name}, `; + output += `\`${c.help.name}\` `; commands = commands + 1; }); - output = output.slice(0, -2); - output = output + "```" - embed.addField(currentCategory + ` [${commands}]`, output); embed.addField( @@ -72,7 +68,7 @@ exports.run = (client, message, args, level) => { }; if(args[0].toLowerCase() == "all") { - embed.setTitle(`Commands [${client.commands.size}]`); + embed.setTitle(`Command list`); embed.setDescription(`⁣For more information on a specific command use \`${prefix}help \`\nFor the full command list use \`${prefix}help all\`\n`); const myCommands = client.commands @@ -88,22 +84,21 @@ exports.run = (client, message, args, level) => { ); sorted.forEach( c => { - const cat = c.help.category.toProperCase(); + const cat = c.help.category; if (currentCategory !== cat) { if(ran == true) { - output = output.slice(0, -2) + "```"; embed.addField(currentCategory + ` [${commands}]`, output) - output = "```"; + output = ""; commands = 0; } currentCategory = cat; ran = true } - output += `${prefix}${c.help.name}, `; + output += `\`${c.help.name}\` `; commands = commands + 1; }); - output = output.slice(0, -2) + "```"; + embed.addField(currentCategory + ` [${commands}]`, output); embed.addField( diff --git a/src/commands/hug.js b/src/commands/hug.js new file mode 100644 index 0000000..30ef214 --- /dev/null +++ b/src/commands/hug.js @@ -0,0 +1,69 @@ +const API = require('nekos.life'); +const {sfw} = new API(); +exports.run = async (client, message, args) => { + if(!args[0]) { + return message.channel.send(`<:error:466995152976871434> You didn't say who you wanted to hug! Usage: \`${client.commands.get(`hug`).help.usage}\``) + }; + + var people = ""; + + for (var i = 0; i < args.length; i++) { + var user = client.getUserFromMention(args[i]) + if (user) { + user = message.guild.members.cache.get(user.id).displayName; + } else { + users = client.searchForMembers(message.guild, args[i]); + if (users.length > 1) + return message.channel.send( + "<:error:466995152976871434> Found multiple users for `" + args[i] + "`, Please be more specific or mention the user instead." + ); + else if (users.length == 0) + return message.channel.send( + "<:error:466995152976871434> That user doesn't seem to exist. Try again!" + ); + user = users[0].displayName; + }; + if(i+1 == args.length && args.length > 1) { + people += `**and** ${user}!` + } else if(args.length < 2) { + people += `${user}!`; + } else if(args.length == 2 && i == 0) { + people += `${user} `; + } else { + people += `${user}, `; + }; + }; + + + + message.channel.startTyping(); + try { + sfw.hug().then((json) => { + embed = new Discord.MessageEmbed(); + embed.setImage(json.url) + embed.setColor(client.embedColour(message)); + embed.setDescription(`**${message.guild.members.cache.get(message.author.id).displayName}** hugged **${people}**`) + message.channel.send(embed) + message.channel.stopTyping(); + }); + } catch (err) { + client.logger.error("hug.js: " + err); + message.channel.send(`<:error:466995152976871434> An error has occurred: ${err}`) + message.channel.stopTyping(); + }; +}; + +exports.conf = { + enabled: true, + guildOnly: true, + aliases: [], + permLevel: "User", + requiredPerms: ["EMBED_LINKS"] +}; + +exports.help = { + name: "hug", + category: "Action", + description: "Hug someone!", + usage: "hug [@user/user] (you can hug as many people as you want!)" +}; diff --git a/src/commands/identity.js b/src/commands/identity.js index 22e8693..b7f3809 100644 --- a/src/commands/identity.js +++ b/src/commands/identity.js @@ -16,7 +16,7 @@ exports.run = async (client, message, args) => { if(!output) { return message.channel.send("<:error:466995152976871434> No results for that query."); }; - return message.channel.send(`__**${output.name}**__\n${output.description}`); + return message.channel.send(`__**${output.name.toProperCase()}**__\n${output.description}`); }; }; diff --git a/src/commands/inspirobot.js b/src/commands/inspirobot.js index 5ba4e0a..17a028f 100644 --- a/src/commands/inspirobot.js +++ b/src/commands/inspirobot.js @@ -1,20 +1,15 @@ -const request = require('request') +const fetch = require("node-fetch") exports.run = async (client, message) => { message.channel.startTyping(); - request({ - url: "http://inspirobot.me/api?generate=true" - }, - function(error, res, body) { - if(body.length > 0) { - message.channel.send({ - files: [new Discord.MessageAttachment(body)] - }); - message.channel.stopTyping(); - } else { - message.channel.send('<:error:466995152976871434> API error, please retry.') - message.channel.stopTyping(); - }; - }); + try { + fetch('http://inspirobot.me/api?generate=true') + .then(res => res.text()) + .then(body => message.channel.send({files: [new Discord.MessageAttachment(body)]})); + message.channel.stopTyping(); + } catch (err) { + message.channel.send(`<:error:466995152976871434> An error has occurred: ${err}`) + message.channel.stopTyping(); + }; }; exports.conf = { diff --git a/src/commands/kemonomimi.js b/src/commands/kemonomimi.js new file mode 100644 index 0000000..3c4b70e --- /dev/null +++ b/src/commands/kemonomimi.js @@ -0,0 +1,30 @@ +const API = require('nekos.life'); +const {sfw} = new API(); +exports.run = async (client, message) => { + message.channel.startTyping(); + try { + sfw.kemonomimi().then((json) => { + message.channel.send(json.url) + message.channel.stopTyping(); + }); + } catch (err) { + client.logger.error("kemonomimi.js: " + err); + message.channel.send(`<:error:466995152976871434> An error has occurred: ${err}`) + message.channel.stopTyping(); + }; +}; + +exports.conf = { + enabled: true, + guildOnly: false, + aliases: [], + permLevel: "User", + requiredPerms: ["EMBED_LINKS"] +}; + +exports.help = { + name: "kemonomimi", + category: "Image", + description: "Sends you pictures of people with animal characteristics.", + usage: "kemonomimi" +}; diff --git a/src/commands/kiss.js b/src/commands/kiss.js new file mode 100644 index 0000000..495ee62 --- /dev/null +++ b/src/commands/kiss.js @@ -0,0 +1,69 @@ +const API = require('nekos.life'); +const {sfw} = new API(); +exports.run = async (client, message, args) => { + if(!args[0]) { + return message.channel.send(`<:error:466995152976871434> You didn't say who you wanted to kiss! Usage: \`${client.commands.get(`kiss`).help.usage}\``) + }; + + var people = ""; + + for (var i = 0; i < args.length; i++) { + var user = client.getUserFromMention(args[i]) + if (user) { + user = message.guild.members.cache.get(user.id).displayName; + } else { + users = client.searchForMembers(message.guild, args[i]); + if (users.length > 1) + return message.channel.send( + "<:error:466995152976871434> Found multiple users for `" + args[i] + "`, Please be more specific or mention the user instead." + ); + else if (users.length == 0) + return message.channel.send( + "<:error:466995152976871434> That user doesn't seem to exist. Try again!" + ); + user = users[0].displayName; + }; + if(i+1 == args.length && args.length > 1) { + people += `**and** ${user}!` + } else if(args.length < 2) { + people += `${user}!`; + } else if(args.length == 2 && i == 0) { + people += `${user} `; + } else { + people += `${user}, `; + }; + }; + + + + message.channel.startTyping(); + try { + sfw.kiss().then((json) => { + embed = new Discord.MessageEmbed(); + embed.setImage(json.url) + embed.setColor(client.embedColour(message)); + embed.setDescription(`**${message.guild.members.cache.get(message.author.id).displayName}** kissed **${people}**`) + message.channel.send(embed) + message.channel.stopTyping(); + }); + } catch (err) { + client.logger.error("kiss.js: " + err); + message.channel.send(`<:error:466995152976871434> An error has occurred: ${err}`) + message.channel.stopTyping(); + }; +}; + +exports.conf = { + enabled: true, + guildOnly: true, + aliases: [], + permLevel: "User", + requiredPerms: ["EMBED_LINKS"] +}; + +exports.help = { + name: "kiss", + category: "Action", + description: "Kiss someone!", + usage: "kiss [@user/user] (you can kiss as many people as you want!)" +}; diff --git a/src/commands/lizard.js b/src/commands/lizard.js new file mode 100644 index 0000000..f476704 --- /dev/null +++ b/src/commands/lizard.js @@ -0,0 +1,30 @@ +const API = require('nekos.life'); +const {sfw} = new API(); +exports.run = async (client, message) => { + message.channel.startTyping(); + try { + sfw.lizard().then((json) => { + message.channel.send(json.url) + message.channel.stopTyping(); + }); + } catch (err) { + client.logger.error("lizard.js: " + err); + message.channel.send(`<:error:466995152976871434> An error has occurred: ${err}`) + message.channel.stopTyping(); + }; +}; + +exports.conf = { + enabled: true, + guildOnly: true, + aliases: [], + permLevel: "User", + requiredPerms: ["EMBED_LINKS"] +}; + +exports.help = { + name: "lizard", + category: "Image", + description: "Sends pictures of lizards.", + usage: "lizard" +}; diff --git a/src/commands/neko.js b/src/commands/neko.js new file mode 100644 index 0000000..7ce0d6b --- /dev/null +++ b/src/commands/neko.js @@ -0,0 +1,30 @@ +const API = require('nekos.life'); +const {sfw} = new API(); +exports.run = async (client, message) => { + message.channel.startTyping(); + try { + sfw.neko().then((json) => { + message.channel.send(json.url); + message.channel.stopTyping(); + }); + } catch (err) { + client.logger.error("neko.js: " + err); + message.channel.send(`<:error:466995152976871434> An error has occurred: ${err}`) + message.channel.stopTyping(); + }; +}; + +exports.conf = { + enabled: true, + guildOnly: false, + aliases: ["catgirl"], + permLevel: "User", + requiredPerms: ["EMBED_LINKS"] +}; + +exports.help = { + name: "neko", + category: "Image", + description: "Sends you pictures of catgirls.", + usage: "neko" +}; diff --git a/src/commands/nekogif.js b/src/commands/nekogif.js new file mode 100644 index 0000000..0df6917 --- /dev/null +++ b/src/commands/nekogif.js @@ -0,0 +1,30 @@ +const API = require('nekos.life'); +const {sfw} = new API(); +exports.run = async (client, message) => { + message.channel.startTyping(); + try { + sfw.nekoGif().then((json) => { + message.channel.send(json.url); + message.channel.stopTyping(); + }); + } catch (err) { + client.logger.error("nekogif.js: " + err); + message.channel.send(`<:error:466995152976871434> An error has occurred: ${err}`) + message.channel.stopTyping(); + }; +}; + +exports.conf = { + enabled: true, + guildOnly: false, + aliases: ["catgirlgif"], + permLevel: "User", + requiredPerms: ["EMBED_LINKS"] +}; + +exports.help = { + name: "nekogif", + category: "Image", + description: "Sends you gifs of catgirls.", + usage: "nekogif" +}; diff --git a/src/commands/owoify.js b/src/commands/owoify.js index ae8b9a5..9587a6f 100644 --- a/src/commands/owoify.js +++ b/src/commands/owoify.js @@ -13,8 +13,8 @@ exports.run = (client, message, args) => { owoified = owoified.replace(/!+/g, ' ' + faces[~~(Math.random() * faces.length)] + ' ') if(owoified.length > 2000) { - return message.channel.send("<:error:466995152976871434> The owoified message exceeds 2000 characters.") - } + owoified = owoified.slice(0, -Math.abs(owoified.length - 2000)) + }; message.channel.send(owoified) }; diff --git a/src/commands/pat.js b/src/commands/pat.js new file mode 100644 index 0000000..fee469b --- /dev/null +++ b/src/commands/pat.js @@ -0,0 +1,69 @@ +const API = require('nekos.life'); +const {sfw} = new API(); +exports.run = async (client, message, args) => { + if(!args[0]) { + return message.channel.send(`<:error:466995152976871434> You didn't say who you wanted to pat! Usage: \`${client.commands.get(`pat`).help.usage}\``) + }; + + var people = ""; + + for (var i = 0; i < args.length; i++) { + var user = client.getUserFromMention(args[i]) + if (user) { + user = message.guild.members.cache.get(user.id).displayName; + } else { + users = client.searchForMembers(message.guild, args[i]); + if (users.length > 1) + return message.channel.send( + "<:error:466995152976871434> Found multiple users for `" + args[i] + "`, Please be more specific or mention the user instead." + ); + else if (users.length == 0) + return message.channel.send( + "<:error:466995152976871434> That user doesn't seem to exist. Try again!" + ); + user = users[0].displayName; + }; + if(i+1 == args.length && args.length > 1) { + people += `**and** ${user}!` + } else if(args.length < 2) { + people += `${user}!`; + } else if(args.length == 2 && i == 0) { + people += `${user} `; + } else { + people += `${user}, `; + }; + }; + + + + message.channel.startTyping(); + try { + sfw.pat().then((json) => { + embed = new Discord.MessageEmbed(); + embed.setImage(json.url) + embed.setColor(client.embedColour(message)); + embed.setDescription(`**${message.guild.members.cache.get(message.author.id).displayName}** patted **${people}**`) + message.channel.send(embed) + message.channel.stopTyping(); + }); + } catch (err) { + client.logger.error("pat.js: " + err); + message.channel.send(`<:error:466995152976871434> An error has occurred: ${err}`) + message.channel.stopTyping(); + }; +}; + +exports.conf = { + enabled: true, + guildOnly: true, + aliases: ["headpat"], + permLevel: "User", + requiredPerms: ["EMBED_LINKS"] +}; + +exports.help = { + name: "pat", + category: "Action", + description: "pat someone!", + usage: "pat [@user/user] (you can pat as many people as you want!)" +}; diff --git a/src/commands/poke.js b/src/commands/poke.js new file mode 100644 index 0000000..07c48bc --- /dev/null +++ b/src/commands/poke.js @@ -0,0 +1,69 @@ +const API = require('nekos.life'); +const {sfw} = new API(); +exports.run = async (client, message, args) => { + if(!args[0]) { + return message.channel.send(`<:error:466995152976871434> You didn't say who you wanted to poke! Usage: \`${client.commands.get(`poke`).help.usage}\``) + }; + + var people = ""; + + for (var i = 0; i < args.length; i++) { + var user = client.getUserFromMention(args[i]) + if (user) { + user = message.guild.members.cache.get(user.id).displayName; + } else { + users = client.searchForMembers(message.guild, args[i]); + if (users.length > 1) + return message.channel.send( + "<:error:466995152976871434> Found multiple users for `" + args[i] + "`, Please be more specific or mention the user instead." + ); + else if (users.length == 0) + return message.channel.send( + "<:error:466995152976871434> That user doesn't seem to exist. Try again!" + ); + user = users[0].displayName; + }; + if(i+1 == args.length && args.length > 1) { + people += `**and** ${user}!` + } else if(args.length < 2) { + people += `${user}!`; + } else if(args.length == 2 && i == 0) { + people += `${user} `; + } else { + people += `${user}, `; + }; + }; + + + + message.channel.startTyping(); + try { + sfw.poke().then((json) => { + embed = new Discord.MessageEmbed(); + embed.setImage(json.url) + embed.setColor(client.embedColour(message)); + embed.setDescription(`**${message.guild.members.cache.get(message.author.id).displayName}** poked **${people}**`) + message.channel.send(embed) + message.channel.stopTyping(); + }); + } catch (err) { + client.logger.error("poke.js: " + err); + message.channel.send(`<:error:466995152976871434> An error has occurred: ${err}`) + message.channel.stopTyping(); + }; +}; + +exports.conf = { + enabled: true, + guildOnly: true, + aliases: [], + permLevel: "User", + requiredPerms: ["EMBED_LINKS"] +}; + +exports.help = { + name: "poke", + category: "Action", + description: "poke someone!", + usage: "poke [@user/user] (you can poke as many people as you want!)" +}; diff --git a/src/commands/pronoun.js b/src/commands/pronoun.js index a535647..0d4b36a 100644 --- a/src/commands/pronoun.js +++ b/src/commands/pronoun.js @@ -5,7 +5,7 @@ exports.run = async (client, message, args) => { for (var key of Object.keys(pronouns)) { output += `${key}, ` }; - return message.channel.send(`__**Pronouns**__\n${output.slice(0, -2)}`); + return message.channel.send(`__**Pronouns:**__\n${output.slice(0, -2)}`); } else { if(args.join(" ").toLowerCase() == "attack helicopter" || args.join(" ").toLowerCase() == "apache attack helicopter" || args.join(" ").toLowerCase() == "apache") { return message.channel.send({ @@ -16,7 +16,7 @@ exports.run = async (client, message, args) => { if(!output) { return message.channel.send("<:error:466995152976871434> No results for that query."); }; - return message.channel.send(`__**Example sentences using ${output.name}**__\n${output.examples}`); + return message.channel.send(`__**Example sentences using ${output.name}:**__\n${output.examples}`); }; }; diff --git a/src/commands/roleinfo.js b/src/commands/roleinfo.js index e6c95a4..10d8c4d 100644 --- a/src/commands/roleinfo.js +++ b/src/commands/roleinfo.js @@ -52,7 +52,6 @@ exports.run = async (client, message, args, level) => { var embed = new Discord.MessageEmbed(); embed.setColor(role.color); embed.setTitle(role.name); - embed.setThumbnail("https://api.alexflipnote.xyz/colour/image/" + role.hexColor.replace("#", "")); embed.setDescription( `• **ID:** ${role.id}\n• **Hex:** ${role.hexColor}\n• **Members:** ${role.members.size}\n• **Position:** ${role.position}\n• **Hoisted:** ${role.hoist}` ); diff --git a/src/commands/sexuality.js b/src/commands/sexuality.js index 8507dac..c41930b 100644 --- a/src/commands/sexuality.js +++ b/src/commands/sexuality.js @@ -5,7 +5,7 @@ exports.run = async (client, message, args) => { for (var key of Object.keys(sexualities)) { output += `${key}, ` }; - return message.channel.send(`__**sexualities**__\n${output.slice(0, -2)}`); + return message.channel.send(`__**Sexualities:**__\n${output.slice(0, -2)}`); } else { if(args.join(" ").toLowerCase() == "attack helicopter" || args.join(" ").toLowerCase() == "apache attack helicopter" || args.join(" ").toLowerCase() == "apache") { return message.channel.send({ @@ -16,7 +16,7 @@ exports.run = async (client, message, args) => { if(!output) { return message.channel.send("<:error:466995152976871434> No results for that query."); }; - return message.channel.send(`__**${output.name}**__\n${output.description}`); + return message.channel.send(`__**${output.name.toProperCase()}:**__\n${output.description}`); }; }; diff --git a/src/commands/ship.js b/src/commands/ship.js index b11a988..3850d87 100644 --- a/src/commands/ship.js +++ b/src/commands/ship.js @@ -31,7 +31,7 @@ exports.run = async (client, message, args) => { name1 = args[1]; }; - shipName = name.substring(0, client.intBetween(1,name.length))+name1.substring(client.intBetween(0,name1.length)); + shipName = name.substr(0, client.intBetween(1,name.length))+name1.substr(client.intBetween(0,name1.length)); message.channel.send(`__**Ship Generator:**__\n${hearts.random()} Ship Name: \`${shipName}\`\n${hearts.random()} Compatibility rating: \`${rating}%\``) }; diff --git a/src/commands/slap.js b/src/commands/slap.js new file mode 100644 index 0000000..2e5189c --- /dev/null +++ b/src/commands/slap.js @@ -0,0 +1,69 @@ +const API = require('nekos.life'); +const {sfw} = new API(); +exports.run = async (client, message, args) => { + if(!args[0]) { + return message.channel.send(`<:error:466995152976871434> You didn't say who you wanted to slap! Usage: \`${client.commands.get(`kiss`).help.usage}\``) + }; + + var people = ""; + + for (var i = 0; i < args.length; i++) { + var user = client.getUserFromMention(args[i]) + if (user) { + user = message.guild.members.cache.get(user.id).displayName; + } else { + users = client.searchForMembers(message.guild, args[i]); + if (users.length > 1) + return message.channel.send( + "<:error:466995152976871434> Found multiple users for `" + args[i] + "`, Please be more specific or mention the user instead." + ); + else if (users.length == 0) + return message.channel.send( + "<:error:466995152976871434> That user doesn't seem to exist. Try again!" + ); + user = users[0].displayName; + }; + if(i+1 == args.length && args.length > 1) { + people += `**and** ${user}!` + } else if(args.length < 2) { + people += `${user}!`; + } else if(args.length == 2 && i == 0) { + people += `${user} `; + } else { + people += `${user}, `; + }; + }; + + + + message.channel.startTyping(); + try { + sfw.slap().then((json) => { + embed = new Discord.MessageEmbed(); + embed.setImage(json.url) + embed.setColor(client.embedColour(message)); + embed.setDescription(`**${message.guild.members.cache.get(message.author.id).displayName}** slapped **${people}**`) + message.channel.send(embed) + message.channel.stopTyping(); + }); + } catch (err) { + client.logger.error("slap.js: " + err); + message.channel.send(`<:error:466995152976871434> An error has occurred: ${err}`) + message.channel.stopTyping(); + }; +}; + +exports.conf = { + enabled: true, + guildOnly: true, + aliases: [], + permLevel: "User", + requiredPerms: ["EMBED_LINKS"] +}; + +exports.help = { + name: "slap", + category: "Action", + description: "Slap someone >:3", + usage: "slap [@user/user] (you can slap as many people as you want!)" +}; diff --git a/src/commands/smug.js b/src/commands/smug.js new file mode 100644 index 0000000..011f2bd --- /dev/null +++ b/src/commands/smug.js @@ -0,0 +1,33 @@ +const API = require('nekos.life'); +const {sfw} = new API(); +exports.run = async (client, message) => { + message.channel.startTyping(); + try { + sfw.smug().then((json) => { + embed = new Discord.MessageEmbed(); + embed.setImage(json.url) + embed.setColor(client.embedColour(message)); + message.channel.send(embed) + message.channel.stopTyping(); + }); + } catch (err) { + client.logger.error("smug.js: " + err); + message.channel.send(`<:error:466995152976871434> An error has occurred: ${err}`) + message.channel.stopTyping(); + }; +}; + +exports.conf = { + enabled: true, + guildOnly: true, + aliases: [], + permLevel: "User", + requiredPerms: ["EMBED_LINKS"] +}; + +exports.help = { + name: "smug", + category: "Action", + description: "Sends a smug gif.", + usage: "smug" +}; diff --git a/src/commands/spoilerise.js b/src/commands/spoilerise.js new file mode 100644 index 0000000..25ab221 --- /dev/null +++ b/src/commands/spoilerise.js @@ -0,0 +1,28 @@ +exports.run = async (client, message, args) => { + if(!args[0]) { + return message.channel.send(`<:error:466995152976871434> You didn't provide any text! Usage: \`${client.commands.get(`spoiler`).help.usage}\``) + }; + + var output = `||${[...message.cleanContent.substring(9)].join("||||")}||`; + + if(output.length > 2000) { + output = output.slice(0, -Math.abs(output.length - 2000)) + }; + + message.channel.send(output) +}; + +exports.conf = { + enabled: true, + guildOnly: false, + aliases: ["spoilerize", "spoiler"], + permLevel: "User", + requiredPerms: [] +}; + +exports.help = { + name: "spoilerise", + category: "Fun", + description: "Spoilers every letter in the provided text.", + usage: "spoiler [text]" +}; diff --git a/src/commands/support.js b/src/commands/support.js index 8fad6b2..c15de88 100644 --- a/src/commands/support.js +++ b/src/commands/support.js @@ -12,7 +12,7 @@ exports.conf = { exports.help = { name: "support", - category: "utility", + category: "Utility", description: "Sends a link to Woomy's support/development server.", usage: "support" }; diff --git a/src/commands/tickle.js b/src/commands/tickle.js new file mode 100644 index 0000000..e75a771 --- /dev/null +++ b/src/commands/tickle.js @@ -0,0 +1,69 @@ +const API = require('nekos.life'); +const {sfw} = new API(); +exports.run = async (client, message, args) => { + if(!args[0]) { + return message.channel.send(`<:error:466995152976871434> You didn't say who you wanted to tickle! Usage: \`${client.commands.get(`tickle`).help.usage}\``) + }; + + var people = ""; + + for (var i = 0; i < args.length; i++) { + var user = client.getUserFromMention(args[i]) + if (user) { + user = message.guild.members.cache.get(user.id).displayName; + } else { + users = client.searchForMembers(message.guild, args[i]); + if (users.length > 1) + return message.channel.send( + "<:error:466995152976871434> Found multiple users for `" + args[i] + "`, Please be more specific or mention the user instead." + ); + else if (users.length == 0) + return message.channel.send( + "<:error:466995152976871434> That user doesn't seem to exist. Try again!" + ); + user = users[0].displayName; + }; + if(i+1 == args.length && args.length > 1) { + people += `**and** ${user}!` + } else if(args.length < 2) { + people += `${user}!`; + } else if(args.length == 2 && i == 0) { + people += `${user} `; + } else { + people += `${user}, `; + }; + }; + + + + message.channel.startTyping(); + try { + sfw.tickle().then((json) => { + embed = new Discord.MessageEmbed(); + embed.setImage(json.url) + embed.setColor(client.embedColour(message)); + embed.setDescription(`**${message.guild.members.cache.get(message.author.id).displayName}** tickled **${people}**`) + message.channel.send(embed) + message.channel.stopTyping(); + }); + } catch (err) { + client.logger.error("tickle.js: " + err); + message.channel.send(`<:error:466995152976871434> An error has occurred: ${err}`) + message.channel.stopTyping(); + }; +}; + +exports.conf = { + enabled: true, + guildOnly: true, + aliases: [], + permLevel: "User", + requiredPerms: ["EMBED_LINKS"] +}; + +exports.help = { + name: "tickle", + category: "Action", + description: "Tickle someone!", + usage: "tickle [@user/user] (you can tickle as many people as you want!)" +}; diff --git a/src/commands/weather.js b/src/commands/weather.js index 4af8b86..ef01bc8 100644 --- a/src/commands/weather.js +++ b/src/commands/weather.js @@ -5,46 +5,47 @@ exports.run = async (client, message, args, error) => { `<:error:466995152976871434> You didn't give me a location. Usage: \`${client.commands.get(`weather`).help.usage}\`` ); }; - - if(args.join(" ").toLowerCase() == "antarctica") { - return; - } message.channel.startTyping(); - - weather.find({search: args.join(" "), degreeType: 'C'}, function(err, result) { - if(err) return client.logger.log(`weather.js error: ${JSON.stringify(error)}`, "error") - if(result.length < 2 || !result) { + + try { + weather.find({search: args.join(" "), degreeType: 'C'}, function(err, result) { + if(err) return message.channel.send(`<:error:466995152976871434> API error: \`${error}\``) + if(result.length < 2 || !result) { + message.channel.stopTyping(); + return message.channel.send("<:error:466995152976871434> City not found!"); + }; + + var location = result[0].location; + var current = result[0].current; + + var warning = (`${location.alert}` || "No warnings"); + + var embedColour; + if (current.temperature < 0) { + embedColour = "#addeff"; + }else if (current.temperature < 20) { + embedColour = "#4fb8ff"; + }else if (current.temperature < 26) { + embedColour = "#ffea4f"; + }else if (current.temperature < 31) { + embedColour = "#ffa14f" + } else { + embedColour = "#ff614f" + }; + + embed = new Discord.MessageEmbed(); + embed.setAuthor(`Weather for ${location.name}:`) + embed.setDescription(`• **Condition:** ${current.skytext}\n• **Temperature:** ${current.temperature}°C\n• **Feels like:** ${current.feelslike}°C\n• **Humidity:** ${current.humidity}%\n• **Wind:** ${current.winddisplay}\n• **Warnings:** ${warning}`) + embed.setThumbnail(current.imageUrl) + embed.setFooter(`Last updated at ${current.observationtime} ${current.date}`) + embed.setColor(embedColour) message.channel.stopTyping(); - return message.channel.send("<:error:466995152976871434> City not found!"); - }; - - var location = result[0].location; - var current = result[0].current; - - var warning = (`${location.alert}` || "No warnings"); - - var embedColour; - if (current.temperature < 0) { - embedColour = "#addeff"; - }else if (current.temperature < 20) { - embedColour = "#4fb8ff"; - }else if (current.temperature < 26) { - embedColour = "#ffea4f"; - }else if (current.temperature < 31) { - embedColour = "#ffa14f" - } else { - embedColour = "#ff614f" - }; - - embed = new Discord.MessageEmbed(); - embed.addField(`Weather for ${location.name}:`, `**Condition:** ${current.skytext}\n**Temperature:** ${current.temperature}C°\n**Feels like:** ${current.feelslike}C°\n**Humidity:** ${current.humidity}%\n**Wind:** ${current.winddisplay}\n**Warnings:** ${warning}`) - embed.setThumbnail(current.imageUrl) - embed.setFooter(`Last updated at ${current.observationtime} ${current.date}`) - embed.setColor(embedColour) - message.channel.stopTyping(); - message.channel.send(embed) - }); + message.channel.send(embed) + }); + } catch(err) { + return message.channel.send(`<:error:466995152976871434> API error: \`${err}\``) + }; }; exports.conf = { diff --git a/src/commands/yoda.js b/src/commands/yoda.js index 8b61784..496b929 100644 --- a/src/commands/yoda.js +++ b/src/commands/yoda.js @@ -1,34 +1,33 @@ -const request = require('request') +const fetch = require("node-fetch") exports.run = async (client, message, args) => { const speech = args.join(' '); if (!speech) { return message.channel.send(`<:error:466995152976871434> Please include text for me to convert to yodish. Yes.`) }; - message.channel.startTyping(); try{ - request({ uri: `http://yoda-api.appspot.com/api/v1/yodish?text=${encodeURIComponent(speech.toLowerCase())}`, json: true }, (error, response, body) => { - message.channel.send(body.yodish); - message.channel.stopTyping(); - }); + fetch(`http://yoda-api.appspot.com/api/v1/yodish?text=${encodeURIComponent(speech.toLowerCase())}`) + .then(res => res.json()) + .then(json => message.channel.send(json.yodish)); + message.channel.stopTyping(); } catch(err) { - message.channel.send(`<:error:466995152976871434> API error: ${err}`); + message.channel.send(`<:error:466995152976871434> An error has occurred: ${err}`); message.channel.stopTyping(); }; }; exports.conf = { - enabled: true, - guildOnly: false, - aliases: ["yoda","yodasay"], - permLevel: "User", - requiredPerms: [] - }; - + enabled: true, + guildOnly: false, + aliases: [], + permLevel: "User", + requiredPerms: [] +}; + exports.help = { - name: "yodish", - category: "Fun", - description: "Turns any text you input into yodish. Yes.", - usage: "yodish " - }; \ No newline at end of file + name: "yoda", + category: "Fun", + description: "Turns any text you input into yodish. Yes.", + usage: "yoda " +}; \ No newline at end of file diff --git a/src/commands/zalgo.js b/src/commands/zalgo.js new file mode 100644 index 0000000..bf2e082 --- /dev/null +++ b/src/commands/zalgo.js @@ -0,0 +1,30 @@ +const zalgo = require("to-zalgo") +exports.run = async (client, message, args) => { + if(!args[0]) { + return message.channel.send(`<:error:466995152976871434> You didn't provide any text! Usage: \`${client.commands.get(`zalgo`).help.usage}\``) + }; + + var output = zalgo(args.join(" ")) + + if(output.length > 2000) { + output = output.slice(0, -Math.abs(output.length - 2000)) + }; + + message.channel.send(output) + }; + + exports.conf = { + enabled: true, + guildOnly: false, + aliases: [], + permLevel: "User", + requiredPerms: [] + }; + + exports.help = { + name: "zalgo", + category: "Fun", + description: "Spoilers every letter in the provided text.", + usage: "zalgo [text]" + }; + \ No newline at end of file diff --git a/src/events/ready.js b/src/events/ready.js index 4a3ce44..f37a7d9 100644 --- a/src/events/ready.js +++ b/src/events/ready.js @@ -8,11 +8,15 @@ module.exports = client => { client.logger.log(`Connected to Discord as ${client.user.tag} | v${client.version.number}`, 'ready'); - let channel; - let channel1; + let channel, channel1; - try { channel = client.guilds.cache.get('410990517841690625').channels.cache.get('570963998342643732'); } catch(err) {}; - try { channel1 = client.guilds.cache.get('410990517841690625').channels.cache.get('570963481189154822'); } catch(err) {}; + try { + channel = client.guilds.cache.get('410990517841690625').channels.cache.get('570963998342643732'); + } catch(err) {}; + + try { + channel1 = client.guilds.cache.get('410990517841690625').channels.cache.get('570963481189154822'); + } catch(err) {}; if(client.devmode == true) { client.logger.warn("Running in development mode.") @@ -21,13 +25,16 @@ module.exports = client => { prefix = client.config.defaultSettings.prefix; channel.send(`\`${timestamp}\`: Ready event fired! Connected to ${client.users.cache.size} users in ${client.guilds.cache.size} guilds.`); channel1.send(`\`${timestamp}\`: **Ready event fired**`); - } + }; let randomActivity = activityArray.random(); client.user.setActivity(`${prefix + randomActivity} | v${client.version.number}`, {type: "PLAYING"}); + setInterval(() => { randomActivity = activityArray.random(); - if(client.lockActivity == false) client.user.setActivity(`${prefix + randomActivity} | v${client.version.number}`, {type: "PLAYING"}); + if(client.lockActivity == false) { + client.user.setActivity(`${prefix + randomActivity} | v${client.version.number}`, {type: "PLAYING"}); + }; }, 30000); }; diff --git a/src/modules/functions.js b/src/modules/functions.js index f51a34d..a30dd4b 100644 --- a/src/modules/functions.js +++ b/src/modules/functions.js @@ -1,7 +1,7 @@ const ytdl = require('ytdl-core-discord'); const youtubeInfo = require('youtube-info'); const getYoutubeId = require('get-youtube-id'); -const request = require('request'); +const fetch = require('node-fetch'); module.exports = client => { // Permission level function @@ -160,14 +160,17 @@ module.exports = client => { { return new Promise(function(resolve, reject) { - request("https://www.googleapis.com/youtube/v3/search?part=id&type=video&q=" + encodeURIComponent(query) + "&key=" + client.config.ytkey, function(error, response, body) - { - if(error) throw error; - - var json = JSON.parse(body); + try{ + fetch("https://www.googleapis.com/youtube/v3/search?part=id&type=video&q=" + encodeURIComponent(query) + "&key=" + client.config.ytkey) + .then(res => res.json()) + .then(json => { if(!json.items) { reject(); return; } resolve(json.items[0]); - }); + }); + } catch (err) { + client.logger.error("Music search err: ", err); + throw err; + }; }); } @@ -374,6 +377,6 @@ module.exports = client => { }); process.on("unhandledRejection", err => { - client.logger.error(`Unhandled rejection: ${err}`); + client.logger.error(`Unhandled rejection: ${err.stack}`); }); }; From da8e23e43466744bf910dd6a9d7db3e39e234a7a Mon Sep 17 00:00:00 2001 From: mudkipscience Date: Thu, 26 Mar 2020 15:38:00 +1100 Subject: [PATCH 41/78] Forgot some things oosp --- changes.txt | 39 --------------------------------------- version.json | 2 +- 2 files changed, 1 insertion(+), 40 deletions(-) delete mode 100644 changes.txt diff --git a/changes.txt b/changes.txt deleted file mode 100644 index 8ca32e2..0000000 --- a/changes.txt +++ /dev/null @@ -1,39 +0,0 @@ -Added a yodish command and made catfact and dogfact say if the api errors (terry) -Links to avatars now lead to the original file size -Bots now get a bot badge in the userinfo command -Added dogfact and catfact command (terry) -index.js now has better logging of when things fail to load/initialize (terry) -added `dice`, rolls a sided die (terry) -Help command changed, the amount of commands in each category and overall is now displayed and formatting changed -added `inspire` as an alias for inspirobot -ship command -added find by mention to functions -you can now @mention the bot to run commands -added identity command, has definitions of gender identities and stuff -added pronouns command, tells you how to use pronouns -added sexuality command, has definitions of sexualities -renamed math calculate -fixed woomy in dms -roleinfo now displays what permissions a role has and also the role colour in the thumbnail -request has been replaced with node-fetch -added neko -added fact -added nekogif -added dog -added cat -added hug -added kiss -added pat -added poke -added slap -added tickle -added cuddle -added feed -added smug -added lizard -added kemonomimi -added spoilerise -added zalgo -renamed flip coinflip -small changes to weather -recategorised some commands diff --git a/version.json b/version.json index 3e289f1..f89b995 100644 --- a/version.json +++ b/version.json @@ -1,4 +1,4 @@ { "number": "1.2.0", - "changelog": "**1.1.0 CHANGELOG:**\n> • Added `~softban`, bans and unbans a user to clear messages\n> • Added `~emoji`, enlarges custom emojis\n> • Added `~inspirobot`, generates an inspirational quote\n> • `~serverinfo` has been changed to be more consistent, and also now displays boosts and if the server is partnered and stuff\n> • `~userinfo` has been changed to be more consistent, also added some stuff\n> • `~about` has been changed, added a thumbnail and removed the description\n> • `~colour` has been changed, it can now generate colours from text\n> • `~hackban` no longer has its own embed\n> • `~eval` now logs to hastebin if output is too large\n> • role names are no longer case sensitive\n> • `~echo` renamed `~say`\n> • Users with the ADMINISTRATOR permission now automatically recieve woomy admin\n> • Fixed `~flip`, `~purge`, `~bohemian_rhapsody` and `~creeper`\n> • Guild join/leave messages no longer include the guild name\n> • Some emojis have been changed\n> • Woomy now supports discord.js v12\n> • Files have been restructured\n> • Logger now logs error stack\n> • Restart now exits with code 0" + "changelog": "**1.2.0 CHANGELOG:**\n> • Added action commands! (`cuddle`, `feed`, `hug`, `kiss`, `pat`, `poke`, `slap`, `smug`, `tickle`)\n> • Added `fact`\n> • Added `catfact`\n> • Added `dogfact`\n> • Added `yoda`\n> • Added `dice`\n> • Added `spoilerise`\n> • Added `zalgo`\n> • Added `dog`\n> • Added `cat`\n> • Added `lizard`\n> • Added `neko`\n> • Added `nekogif`\n> • Added `kemonomimi`\n> • Added `foxgirl`\n> • Added `identity`\n> • Added `pronouns`\n> • Added `sexuality`\n> • Added `ship`\n> • Renamed `flip to `coinflip` (flip remains as an alias)\n> • Renamed `math` to `calculate` (math is an alias)\n> • @Woomy is now a prefix\n> • Added the `inspire` alias to `inspirobot`\n> • Help now displays the amount of commands in each category\n> • Bots now get a badge in `userinfo`\n> • `roleinfo` now displays what permissions a role has\n> • small changes to `weather`\n> • Woomy now has clear logging of issues that prevent her from starting\n> • request npm module has been swapped out for node-fetch\n**NOTES:**\n> Thank you to Terryiscool160 for creating multiple commands used in this update" } From 566c049b10e778e6c86327889922940130b6f2f8 Mon Sep 17 00:00:00 2001 From: mudkipscience Date: Thu, 26 Mar 2020 15:42:33 +1100 Subject: [PATCH 42/78] downgrade better-sqlite3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4109849..891ab34 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "main": "index.js", "dependencies": { "@discordjs/opus": "^0.1.0", - "better-sqlite3": "^5.4.3", + "better-sqlite3": "^5.4.1", "chalk": "^3.0.0", "dblapi.js": "^2.3.1", "discord.js": "^12.0.2", From 3589373ce922774e701aa1427be7a0a1725ccc9f Mon Sep 17 00:00:00 2001 From: mudkipscience Date: Thu, 26 Mar 2020 15:57:53 +1100 Subject: [PATCH 43/78] Fixed cooldown --- src/events/message.js | 13 +++++++------ version.json | 2 +- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/events/message.js b/src/events/message.js index dc5611e..fab2583 100644 --- a/src/events/message.js +++ b/src/events/message.js @@ -1,4 +1,4 @@ -const commandRanRecently = new Set(); +const cooldown = new Set(); module.exports = async (client, message) => { if (message.author.bot) return; @@ -151,11 +151,11 @@ module.exports = async (client, message) => { if (!cmd) return; - if (commandRanRecently.has(message.author.id)) { + if (cooldown.has(message.author.id)) { return message.channel.send( `⏱️ You are being ratelimited. Please try again in 2 seconds.` ) - .then(m => m.delete(2000)); + .then(m => m.delete(5000)); }; if (message.guild && !perms.has('SEND_MESSAGES')) { @@ -222,10 +222,11 @@ module.exports = async (client, message) => { message.flags.push(args.shift().slice(1)); }; - commandRanRecently.add(message.author.id); + cooldown.add(message.author.id); + setTimeout(() => { - commandRanRecently.delete(message.author.id); - }, {timeout: 2000}); + cooldown.delete(message.author.id); + }, 2000); client.logger.cmd(`${client.config.permLevels.find(l => l.level === level).name} ${message.author.username} (${message.author.id}) ran command ${cmd.help.name}`); diff --git a/version.json b/version.json index f89b995..92c7223 100644 --- a/version.json +++ b/version.json @@ -1,4 +1,4 @@ { - "number": "1.2.0", + "number": "1.2.1", "changelog": "**1.2.0 CHANGELOG:**\n> • Added action commands! (`cuddle`, `feed`, `hug`, `kiss`, `pat`, `poke`, `slap`, `smug`, `tickle`)\n> • Added `fact`\n> • Added `catfact`\n> • Added `dogfact`\n> • Added `yoda`\n> • Added `dice`\n> • Added `spoilerise`\n> • Added `zalgo`\n> • Added `dog`\n> • Added `cat`\n> • Added `lizard`\n> • Added `neko`\n> • Added `nekogif`\n> • Added `kemonomimi`\n> • Added `foxgirl`\n> • Added `identity`\n> • Added `pronouns`\n> • Added `sexuality`\n> • Added `ship`\n> • Renamed `flip to `coinflip` (flip remains as an alias)\n> • Renamed `math` to `calculate` (math is an alias)\n> • @Woomy is now a prefix\n> • Added the `inspire` alias to `inspirobot`\n> • Help now displays the amount of commands in each category\n> • Bots now get a badge in `userinfo`\n> • `roleinfo` now displays what permissions a role has\n> • small changes to `weather`\n> • Woomy now has clear logging of issues that prevent her from starting\n> • request npm module has been swapped out for node-fetch\n**NOTES:**\n> Thank you to Terryiscool160 for creating multiple commands used in this update" } From 2408257b4471d6c911f08464f9272928cf8eca90 Mon Sep 17 00:00:00 2001 From: mudkipscience Date: Thu, 26 Mar 2020 16:01:27 +1100 Subject: [PATCH 44/78] ACTUALLY fixed cooldown --- src/events/message.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/events/message.js b/src/events/message.js index fab2583..7c399a8 100644 --- a/src/events/message.js +++ b/src/events/message.js @@ -153,9 +153,10 @@ module.exports = async (client, message) => { if (cooldown.has(message.author.id)) { return message.channel.send( - `⏱️ You are being ratelimited. Please try again in 2 seconds.` - ) - .then(m => m.delete(5000)); + `⏱️ You are being ratelimited. Please try again in 2 seconds.` + ).then(msg => { + msg.delete({timeout: 2000}); + }); }; if (message.guild && !perms.has('SEND_MESSAGES')) { From 177f4e2b529c8e1c4c4a13e8142b624fa8e5a8fb Mon Sep 17 00:00:00 2001 From: mudkipscience Date: Thu, 26 Mar 2020 18:26:22 +1100 Subject: [PATCH 45/78] 1.2.2 --- configTemplate.js | 6 ++++++ index.js | 5 ++++- src/commands/prefix.js | 6 ------ src/commands/zalgo.js | 2 +- src/events/ready.js | 44 +++++++++++++++++++++++++++++++----------- src/modules/Logger.js | 37 +++++++++++++++++++++++------------ version.json | 2 +- 7 files changed, 70 insertions(+), 32 deletions(-) diff --git a/configTemplate.js b/configTemplate.js index 3828ed8..b6e96c3 100644 --- a/configTemplate.js +++ b/configTemplate.js @@ -2,6 +2,12 @@ const config = { // ID's "owners": [], // Adding your ID here will give you access to dangerous commands like eval. Please be careful with who you add here! Eval can be used to modify the host machine. + // Host options + "devmodeEnabled": false, // true or false + "loggingServer": "", // server ID, or blank to disable + "startupLogs": "", // Channel ID, or blank to disable + "consoleLogs": "", // Channel ID, or blank to disable + // Tokens "token": "", // Your bot's token. "devtoken": "", // (optional) another token, meant for a bot used for development diff --git a/index.js b/index.js index 395e910..49d5326 100644 --- a/index.js +++ b/index.js @@ -15,18 +15,21 @@ try { console.log('Failed to load config.js:', err); process.exit(); }; + try{ client.version = require('./version.json'); } catch (err) { console.log('Failed to load version.json:', err); process.exit(); }; + try{ client.logger = require('./src/modules/Logger'); } catch (err) { console.log('Failed to load Logger.js:', err); process.exit(); }; + client.logger.setClient(client); try{ @@ -36,7 +39,7 @@ try{ process.exit(); }; -if(process.env['USER'] != 'container') { +if(client.config.devmodeEnabled == true && process.env['USER'] != 'container') { client.devmode = true; } else { client.devmode = false; diff --git a/src/commands/prefix.js b/src/commands/prefix.js index 774a22b..1d72e74 100644 --- a/src/commands/prefix.js +++ b/src/commands/prefix.js @@ -1,10 +1,4 @@ exports.run = async (client, message, args) => { - if(client.devmode === true) { - return message.channel.send( - "<:error:466995152976871434> This command has been disabled because Woomy is in development mode." - ); - }; - const settings = message.settings; if (!client.settings.has(message.guild.id)) client.settings.set(message.guild.id, {}); diff --git a/src/commands/zalgo.js b/src/commands/zalgo.js index bf2e082..e34d404 100644 --- a/src/commands/zalgo.js +++ b/src/commands/zalgo.js @@ -14,7 +14,7 @@ exports.run = async (client, message, args) => { }; exports.conf = { - enabled: true, + enabled: false, guildOnly: false, aliases: [], permLevel: "User", diff --git a/src/events/ready.js b/src/events/ready.js index f37a7d9..be5792e 100644 --- a/src/events/ready.js +++ b/src/events/ready.js @@ -6,25 +6,45 @@ module.exports = client => { client.lockActivity = false; - client.logger.log(`Connected to Discord as ${client.user.tag} | v${client.version.number}`, 'ready'); + let guild, channel, channel1; + + if(client.config.loggingServer.length > 0) { + try { + guild = client.guilds.cache.get(client.config.loggingServer) + } catch(err) { + client.logger.error("Could not find loggingServer server (is the ID valid?):\n" + err); + process.exit(1); + }; + + if(client.config.consoleLogs.length > 0) { + try { + channel1 = client.guilds.cache.get(client.config.consoleLogs) + } catch(err) { + client.logger.error("Could not find consoleLogs channel (is the ID valid?):\n" + err); + process.exit(1); + }; + }; + + if(client.config.startupLogs.length > 0) { + try { + channel = guild.channels.cache.get(client.config.startupLogs) + } catch(err) { + client.logger.error("Could not find startupLogs channel (is the ID valid?):\n" + err); + process.exit(1); + }; + }; + }; - let channel, channel1; - try { - channel = client.guilds.cache.get('410990517841690625').channels.cache.get('570963998342643732'); - } catch(err) {}; - - try { - channel1 = client.guilds.cache.get('410990517841690625').channels.cache.get('570963481189154822'); - } catch(err) {}; if(client.devmode == true) { client.logger.warn("Running in development mode.") prefix = client.config.defaultSettings.devprefix; } else { prefix = client.config.defaultSettings.prefix; - channel.send(`\`${timestamp}\`: Ready event fired! Connected to ${client.users.cache.size} users in ${client.guilds.cache.size} guilds.`); - channel1.send(`\`${timestamp}\`: **Ready event fired**`); + if(channel1) { + channel1.send(`Bot started at \`${timestamp}\``); + }; }; let randomActivity = activityArray.random(); @@ -37,4 +57,6 @@ module.exports = client => { client.user.setActivity(`${prefix + randomActivity} | v${client.version.number}`, {type: "PLAYING"}); }; }, 30000); + + client.logger.log(`Connected to Discord as ${client.user.tag} | v${client.version.number}`, 'ready'); }; diff --git a/src/modules/Logger.js b/src/modules/Logger.js index 0c8614f..c1fed4b 100644 --- a/src/modules/Logger.js +++ b/src/modules/Logger.js @@ -2,17 +2,25 @@ const chalk = require("chalk"); const moment = require("moment"); exports.log = (content, type = "log") => { - const timestamp = chalk.grey(`[${moment().format("YYYY-MM-DD HH:mm:ss")}]`); + const timestamp = `[${moment().format("YYYY-MM-DD HH:mm:ss")}]`; let channel; + + try { + channel = client.guilds.cache.get(client.config.loggingServer).channels.cache.get(client.config.consoleLogs); + } catch(err) {}; - try { channel = client.guilds.cache.get('410990517841690625').channels.cache.get('570963481189154822'); } catch(err) {} + var logToServer = false; + + if(client.devmode === false && channel && guild.available) { + logToServer = true; + }; switch (type) { case "info": { try { - if (client.devmode == false) { - channel.send(`\`${timestamp}\`: ` + content); + if (logToServer == true) { + channel.send(`\`${timestamp}\` \`[${type.toUpperCase()}]\` ` + content); }; } catch(err) {}; return console.log(`${timestamp} ${chalk.cyanBright(`[${type.toUpperCase()}]`)} ${content} `); @@ -20,8 +28,8 @@ exports.log = (content, type = "log") => { case "warn": { try { - if (client.devmode == false) { - channel.send(`\`${timestamp}\`: ` + content); + if (logToServer == true) { + channel.send(`\`${timestamp}\` \`[${type.toUpperCase()}]\` ` + content); }; } catch(err) {}; return console.log(`${timestamp} ${chalk.yellowBright(`[${type.toUpperCase()}]`)} ${content} `); @@ -29,8 +37,8 @@ exports.log = (content, type = "log") => { case "error": { try { - if (client.devmode == false) { - channel.send(`\`${timestamp}\`: ` + content); + if (logToServer == true) { + channel.send(`\`${timestamp}\` \`[${type.toUpperCase()}]\` ` + content); }; } catch(err) {} return console.log(`${timestamp} ${chalk.redBright(`[${type.toUpperCase()}]`)} ${content} `); @@ -38,8 +46,8 @@ exports.log = (content, type = "log") => { case "debug": { try { - if (client.devmode == false) { - channel.send(`\`${timestamp}\`: ` + content); + if (logToServer == true) { + channel.send(`\`${timestamp}\` \`[${type.toUpperCase()}]\` ` + content); }; } catch(err) {}; return console.log(`${timestamp} ${chalk.magentaBright(`[${type.toUpperCase()}]`)} ${content} `); @@ -47,14 +55,19 @@ exports.log = (content, type = "log") => { case "cmd": { try { - if (client.devmode == false) { - channel.send(`\`${timestamp}\` ` + content); + if (logToServer == true) { + channel.send(`\`${timestamp}\` \`[${type.toUpperCase()}]\` ` + content); }; } catch(err) {}; return console.log(`${timestamp} ${chalk.whiteBright(`[${type.toUpperCase()}]`)} ${content}`); }; case "ready": { + try { + if (logToServer == true) { + channel.send(`\`${timestamp}\` \`[${type.toUpperCase()}]\` ` + content); + }; + } catch(err) {}; return console.log(`${timestamp} ${chalk.greenBright (`[${type.toUpperCase()}]`)} ${content}`); }; diff --git a/version.json b/version.json index 92c7223..19b07db 100644 --- a/version.json +++ b/version.json @@ -1,4 +1,4 @@ { - "number": "1.2.1", + "number": "1.2.2", "changelog": "**1.2.0 CHANGELOG:**\n> • Added action commands! (`cuddle`, `feed`, `hug`, `kiss`, `pat`, `poke`, `slap`, `smug`, `tickle`)\n> • Added `fact`\n> • Added `catfact`\n> • Added `dogfact`\n> • Added `yoda`\n> • Added `dice`\n> • Added `spoilerise`\n> • Added `zalgo`\n> • Added `dog`\n> • Added `cat`\n> • Added `lizard`\n> • Added `neko`\n> • Added `nekogif`\n> • Added `kemonomimi`\n> • Added `foxgirl`\n> • Added `identity`\n> • Added `pronouns`\n> • Added `sexuality`\n> • Added `ship`\n> • Renamed `flip to `coinflip` (flip remains as an alias)\n> • Renamed `math` to `calculate` (math is an alias)\n> • @Woomy is now a prefix\n> • Added the `inspire` alias to `inspirobot`\n> • Help now displays the amount of commands in each category\n> • Bots now get a badge in `userinfo`\n> • `roleinfo` now displays what permissions a role has\n> • small changes to `weather`\n> • Woomy now has clear logging of issues that prevent her from starting\n> • request npm module has been swapped out for node-fetch\n**NOTES:**\n> Thank you to Terryiscool160 for creating multiple commands used in this update" } From 26da45afdc6067333fb9487de6c0bdeb0012a93f Mon Sep 17 00:00:00 2001 From: mudkipscience Date: Thu, 26 Mar 2020 18:28:36 +1100 Subject: [PATCH 46/78] oops --- src/modules/Logger.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/Logger.js b/src/modules/Logger.js index c1fed4b..e924961 100644 --- a/src/modules/Logger.js +++ b/src/modules/Logger.js @@ -12,7 +12,7 @@ exports.log = (content, type = "log") => { var logToServer = false; - if(client.devmode === false && channel && guild.available) { + if(client.devmode === false && channel && client.guilds.cache.get(client.config.loggingServer).available) { logToServer = true; }; From 2df5337e2d3105d38b1be6fac27eabb5d68490e0 Mon Sep 17 00:00:00 2001 From: mudkipscience Date: Thu, 26 Mar 2020 18:47:48 +1100 Subject: [PATCH 47/78] final fix (hopefully) --- src/events/ready.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/events/ready.js b/src/events/ready.js index be5792e..9818463 100644 --- a/src/events/ready.js +++ b/src/events/ready.js @@ -18,7 +18,7 @@ module.exports = client => { if(client.config.consoleLogs.length > 0) { try { - channel1 = client.guilds.cache.get(client.config.consoleLogs) + channel1 = guild.channels.cache.get(client.config.consoleLogs) } catch(err) { client.logger.error("Could not find consoleLogs channel (is the ID valid?):\n" + err); process.exit(1); @@ -35,15 +35,13 @@ module.exports = client => { }; }; - - - if(client.devmode == true) { + if(client.devmode !== true) { client.logger.warn("Running in development mode.") prefix = client.config.defaultSettings.devprefix; } else { prefix = client.config.defaultSettings.prefix; - if(channel1) { - channel1.send(`Bot started at \`${timestamp}\``); + if(channel) { + channel.send(`Bot started at \`${timestamp}\``); }; }; From 2623d6035c51d105c4514a16eb99ea0c76f78227 Mon Sep 17 00:00:00 2001 From: mudkipscience Date: Thu, 26 Mar 2020 18:48:03 +1100 Subject: [PATCH 48/78] Update ready.js --- src/events/ready.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/events/ready.js b/src/events/ready.js index 9818463..532cd8c 100644 --- a/src/events/ready.js +++ b/src/events/ready.js @@ -35,7 +35,7 @@ module.exports = client => { }; }; - if(client.devmode !== true) { + if(client.devmode == true) { client.logger.warn("Running in development mode.") prefix = client.config.defaultSettings.devprefix; } else { From 1d7214f95a50516d13ab4b95ddea926f66df9e88 Mon Sep 17 00:00:00 2001 From: TheCakeChicken Date: Thu, 26 Mar 2020 14:04:34 +0000 Subject: [PATCH 49/78] make the bot stop typing --- src/commands/catfact.js | 2 +- src/commands/dogfact.js | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/commands/catfact.js b/src/commands/catfact.js index a58aae4..366999e 100644 --- a/src/commands/catfact.js +++ b/src/commands/catfact.js @@ -7,8 +7,8 @@ exports.run = async (bot, message, args) => { .then(json => message.channel.send(`__**Did you know?**__\n${json.data[0].fact}`)) } catch(err) { message.channel.send(`<:error:466995152976871434> An error has occurred: ${err}`); - message.channel.stopTyping(); }; + message.channel.stopTyping(); }; exports.conf = { diff --git a/src/commands/dogfact.js b/src/commands/dogfact.js index d0b80ed..fca8a62 100644 --- a/src/commands/dogfact.js +++ b/src/commands/dogfact.js @@ -5,11 +5,10 @@ exports.run = async (bot, message, args) => { fetch('https://dog-api.kinduff.com/api/facts') .then(res => res.json()) .then(json => message.channel.send(`__**Did you know?**__\n ${json.facts[0]}`)); - message.channel.stopTyping(); } catch(err) { message.channel.send(`<:error:466995152976871434> An error has occurred: ${err}`); - message.channel.stopTyping(); }; + message.channel.stopTyping(); }; exports.conf = { From 5d211fcc68d66e5f79ea9f88692be30e7e703e79 Mon Sep 17 00:00:00 2001 From: TheCakeChicken Date: Thu, 26 Mar 2020 14:05:48 +0000 Subject: [PATCH 50/78] make the bot stop typing here as well --- src/commands/catfact.js | 14 +++++++------- src/commands/dogfact.js | 13 ++++++------- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/src/commands/catfact.js b/src/commands/catfact.js index 8beb770..437541f 100644 --- a/src/commands/catfact.js +++ b/src/commands/catfact.js @@ -3,15 +3,15 @@ const request = require("request"); exports.run = async (bot, message, args) => { message.channel.startTyping(); try{ - request({ uri: "https://catfact.ninja/facts", json: true }, (error, response, body) => { - message.channel.send(`**Did you know?**\n ${body.data[0].fact}`); - message.channel.startTyping(); - }); -} catch(err) { - message.channel.send(`<:error:466995152976871434> API error: ${err}`); + request({ uri: "https://catfact.ninja/facts", json: true }, (error, response, body) => { + message.channel.send(`**Did you know?**\n ${body.data[0].fact}`); + message.channel.startTyping(); + }); + } catch(err) { + message.channel.send(`<:error:466995152976871434> API error: ${err}`); + }; message.channel.stopTyping(); }; -}; exports.conf = { enabled: true, diff --git a/src/commands/dogfact.js b/src/commands/dogfact.js index 31d1639..586f323 100644 --- a/src/commands/dogfact.js +++ b/src/commands/dogfact.js @@ -3,15 +3,14 @@ const request = require("request"); exports.run = async (bot, message, args) => { message.channel.startTyping(); try{ - request({ uri: "https://dog-api.kinduff.com/api/facts", json: true }, (error, response, body) => { - message.channel.send(`**Did you know?**\n ${body.facts[0]}`); - message.channel.stopTyping(); - }); -} catch(err) { - message.channel.send(`<:error:466995152976871434> API error: ${err}`); + request({ uri: "https://dog-api.kinduff.com/api/facts", json: true }, (error, response, body) => { + message.channel.send(`**Did you know?**\n ${body.facts[0]}`); + }); + } catch(err) { + message.channel.send(`<:error:466995152976871434> API error: ${err}`); + }; message.channel.stopTyping(); }; -}; exports.conf = { enabled: true, From f7d649d6b1b9b36cfba0e32c352f71628b139df6 Mon Sep 17 00:00:00 2001 From: TheCakeChicken Date: Thu, 26 Mar 2020 14:09:53 +0000 Subject: [PATCH 51/78] fix usage for slap --- src/commands/slap.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/slap.js b/src/commands/slap.js index 2e5189c..c87c783 100644 --- a/src/commands/slap.js +++ b/src/commands/slap.js @@ -2,7 +2,7 @@ const API = require('nekos.life'); const {sfw} = new API(); exports.run = async (client, message, args) => { if(!args[0]) { - return message.channel.send(`<:error:466995152976871434> You didn't say who you wanted to slap! Usage: \`${client.commands.get(`kiss`).help.usage}\``) + return message.channel.send(`<:error:466995152976871434> You didn't say who you wanted to slap! Usage: \`${client.commands.get(`slap`).help.usage}\``) }; var people = ""; From e6ab5bb101b1282556e7073154ed5abb7378407e Mon Sep 17 00:00:00 2001 From: TheCakeChicken Date: Thu, 26 Mar 2020 23:01:10 +0000 Subject: [PATCH 52/78] fix undefined stuff --- src/commands/userinfo.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/userinfo.js b/src/commands/userinfo.js index c5dcbac..98d6738 100644 --- a/src/commands/userinfo.js +++ b/src/commands/userinfo.js @@ -2,7 +2,7 @@ const Discord = require("discord.js"); const coolPeople = require('../../resources/other/coolpeople.json') exports.run = (client, message, args) => { var user, guild, status, createdAt, avurl, tag, id; - var nick, roles, presence, badges = ""; + var nick = "", roles = "", presence = "", badges = ""; var coolPerson = false; var friendos = coolPeople.coolPeople; From 72b0f6479379cbb88e782804fe690891ac03ee4d Mon Sep 17 00:00:00 2001 From: mudkipscience Date: Sun, 29 Mar 2020 06:14:30 +0000 Subject: [PATCH 53/78] Update version.json --- version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.json b/version.json index 19b07db..6d6b7a3 100644 --- a/version.json +++ b/version.json @@ -1,4 +1,4 @@ { - "number": "1.2.2", + "number": "1.2.3", "changelog": "**1.2.0 CHANGELOG:**\n> • Added action commands! (`cuddle`, `feed`, `hug`, `kiss`, `pat`, `poke`, `slap`, `smug`, `tickle`)\n> • Added `fact`\n> • Added `catfact`\n> • Added `dogfact`\n> • Added `yoda`\n> • Added `dice`\n> • Added `spoilerise`\n> • Added `zalgo`\n> • Added `dog`\n> • Added `cat`\n> • Added `lizard`\n> • Added `neko`\n> • Added `nekogif`\n> • Added `kemonomimi`\n> • Added `foxgirl`\n> • Added `identity`\n> • Added `pronouns`\n> • Added `sexuality`\n> • Added `ship`\n> • Renamed `flip to `coinflip` (flip remains as an alias)\n> • Renamed `math` to `calculate` (math is an alias)\n> • @Woomy is now a prefix\n> • Added the `inspire` alias to `inspirobot`\n> • Help now displays the amount of commands in each category\n> • Bots now get a badge in `userinfo`\n> • `roleinfo` now displays what permissions a role has\n> • small changes to `weather`\n> • Woomy now has clear logging of issues that prevent her from starting\n> • request npm module has been swapped out for node-fetch\n**NOTES:**\n> Thank you to Terryiscool160 for creating multiple commands used in this update" } From b81db48f7b26b3906fbd7ddd94ca106b095818ac Mon Sep 17 00:00:00 2001 From: TheCakeChicken Date: Mon, 30 Mar 2020 23:28:16 +0100 Subject: [PATCH 54/78] typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d0fde98..8d86032 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Woomy is a all-purpose discord bot built off the [guidebot](https://github.com/A # How to use The easiest way to use Woomy is to invite it to your server with [this link.](https://discordapp.com/oauth2/authorize?client_id=435961704145485835&permissions=2134240503&scope=bot) It is hosted 24/7 and automatically updates itself when a new release is made available, making sure you always get the newest features. -You can also self-host! Some modificatiomns to the code will need to be made before Woomy will run on your machine, but anyone who can read errors will figure out what needs to be changed pretty quickly :P +You can also self-host! Some modifications to the code will need to be made before Woomy will run on your machine, but anyone who can read errors will figure out what needs to be changed pretty quickly :P # Requirements - git From c7d3ecd6da26e16fefd4b87e5c97d1bc3a0423b2 Mon Sep 17 00:00:00 2001 From: FLGX Date: Wed, 8 Apr 2020 11:04:52 +0200 Subject: [PATCH 55/78] Fix music in v1 woomy --- src/modules/functions.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/modules/functions.js b/src/modules/functions.js index a30dd4b..f8c552c 100644 --- a/src/modules/functions.js +++ b/src/modules/functions.js @@ -229,7 +229,16 @@ module.exports = client => { }; id = item.id.videoId; } - + + // music "playing", nothing in queue + if((client.music.getGuild(message.guild.id).playing || client.music.getGuild(message.guild.id).dispatcher) && client.music.getGuild(message.guild.id).queue.length == 0) { + client.music.getGuild(message.guild.id).playing = false; + client.music.getGuild(message.guild.id).dispatcher = null; + // music not playing, something is in queue + } else if(!client.music.getGuild(message.guild.id).playing && !client.music.getGuild(message.guild.id).dispatcher && client.music.getGuild(message.guild.id).queue.length > 0) { + client.music.getGuild(message.guild.id).queue = []; + }; + if(client.music.getGuild(message.guild.id).queue.length == 0 || bypassQueue) { let meta = await client.music.getMeta(id); From 276c26d3c7d4e3bf6ffbf28152f3ea4998b9a34a Mon Sep 17 00:00:00 2001 From: FLGX Date: Wed, 8 Apr 2020 11:10:44 +0200 Subject: [PATCH 56/78] remove member username/id print on command --- src/events/message.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/events/message.js b/src/events/message.js index 7c399a8..447bd5b 100644 --- a/src/events/message.js +++ b/src/events/message.js @@ -229,7 +229,7 @@ module.exports = async (client, message) => { cooldown.delete(message.author.id); }, 2000); - client.logger.cmd(`${client.config.permLevels.find(l => l.level === level).name} ${message.author.username} (${message.author.id}) ran command ${cmd.help.name}`); + client.logger.cmd(`${client.config.permLevels.find(l => l.level === level).name} ran command ${cmd.help.name}`); cmd.run(client, message, args, level); }; From c188356030c7178e975c09bce42b72bff733e92c Mon Sep 17 00:00:00 2001 From: mudkipscience Date: Fri, 17 Apr 2020 17:14:42 +1000 Subject: [PATCH 57/78] update ytdl-core --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 891ab34..fa5d116 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "dependencies": { "@discordjs/opus": "^0.1.0", "better-sqlite3": "^5.4.1", - "chalk": "^3.0.0", + "chalk": "^4.0.0", "dblapi.js": "^2.3.1", "discord.js": "^12.0.2", "enmap": "^5.2.4", @@ -26,7 +26,7 @@ "urban": "^0.3.2", "weather-js": "^2.0.0", "youtube-info": "^1.3.2", - "ytdl-core-discord": "^1.1.0" + "ytdl-core-discord": "^1.2.0" }, "devDependencies": {}, "scripts": { From 0662be72d110e95e4af94c530fd0cef7bbd310f8 Mon Sep 17 00:00:00 2001 From: Emily Date: Sat, 18 Apr 2020 04:11:13 +0000 Subject: [PATCH 58/78] why are unhandled rejections trying to log a stack --- src/modules/functions.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/functions.js b/src/modules/functions.js index f8c552c..46ad1e4 100644 --- a/src/modules/functions.js +++ b/src/modules/functions.js @@ -386,6 +386,6 @@ module.exports = client => { }); process.on("unhandledRejection", err => { - client.logger.error(`Unhandled rejection: ${err.stack}`); + client.logger.error(`Unhandled rejection: ${err}`); }); }; From 57ad4914f86f447303c5d1d9c85667fb878146e5 Mon Sep 17 00:00:00 2001 From: FLGX Date: Sat, 18 Apr 2020 21:34:41 +0200 Subject: [PATCH 59/78] set volume to 25% by default for now before v2 woomy is released --- src/modules/functions.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/modules/functions.js b/src/modules/functions.js index 46ad1e4..eb759a1 100644 --- a/src/modules/functions.js +++ b/src/modules/functions.js @@ -280,6 +280,8 @@ module.exports = client => { { let dispatcher = client.music.getGuild(message.guild.id).dispatcher = connection.play(await ytdl("https://www.youtube.com/watch?v=" + id, {highWaterMark: 1024 * 1024 * 32}), {type: 'opus'}); + dispatcher.setVolume(0.25) + dispatcher.on('finish', (a, b) => { end(a == "silent"); From 79b14f60aa745d00da5978874ec2d3ae5888fbcd Mon Sep 17 00:00:00 2001 From: TheCakeChicken Date: Sun, 19 Apr 2020 00:22:57 +0100 Subject: [PATCH 60/78] Remove potentially NSFW content --- .gitignore | 3 ++- src/commands/foxgirl.js | 2 +- src/commands/kemonomimi.js | 2 +- src/commands/neko.js | 2 +- src/commands/nekogif.js | 2 +- 5 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index 656811d..1f83f4c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ node_modules data config.js -package-lock.json \ No newline at end of file +package-lock.json +.env \ No newline at end of file diff --git a/src/commands/foxgirl.js b/src/commands/foxgirl.js index f3f3312..3d8a0e6 100644 --- a/src/commands/foxgirl.js +++ b/src/commands/foxgirl.js @@ -15,7 +15,7 @@ exports.run = async (client, message) => { }; exports.conf = { - enabled: true, + enabled: false, guildOnly: false, aliases: [], permLevel: "User", diff --git a/src/commands/kemonomimi.js b/src/commands/kemonomimi.js index 3c4b70e..7d16938 100644 --- a/src/commands/kemonomimi.js +++ b/src/commands/kemonomimi.js @@ -15,7 +15,7 @@ exports.run = async (client, message) => { }; exports.conf = { - enabled: true, + enabled: false, guildOnly: false, aliases: [], permLevel: "User", diff --git a/src/commands/neko.js b/src/commands/neko.js index 7ce0d6b..6e62f19 100644 --- a/src/commands/neko.js +++ b/src/commands/neko.js @@ -15,7 +15,7 @@ exports.run = async (client, message) => { }; exports.conf = { - enabled: true, + enabled: false, guildOnly: false, aliases: ["catgirl"], permLevel: "User", diff --git a/src/commands/nekogif.js b/src/commands/nekogif.js index 0df6917..cc878e8 100644 --- a/src/commands/nekogif.js +++ b/src/commands/nekogif.js @@ -15,7 +15,7 @@ exports.run = async (client, message) => { }; exports.conf = { - enabled: true, + enabled: false, guildOnly: false, aliases: ["catgirlgif"], permLevel: "User", From f702254603d2e3d3e01e734bbfaf85d5956ad9f9 Mon Sep 17 00:00:00 2001 From: TheCakeChicken Date: Sun, 19 Apr 2020 00:40:05 +0100 Subject: [PATCH 61/78] actually disable potentially NSFW commands --- src/commands/foxgirl.js | 3 ++- src/commands/kemonomimi.js | 4 ++-- src/commands/neko.js | 4 ++-- src/commands/nekogif.js | 4 ++-- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/commands/foxgirl.js b/src/commands/foxgirl.js index 3d8a0e6..a2a2d40 100644 --- a/src/commands/foxgirl.js +++ b/src/commands/foxgirl.js @@ -1,4 +1,4 @@ -const API = require('nekos.life'); +/*const API = require('nekos.life'); const {sfw} = new API(); exports.run = async (client, message) => { message.channel.startTyping(); @@ -28,3 +28,4 @@ exports.help = { description: "Sends you pictures of fox girls.", usage: "foxgirl" }; +*/ \ No newline at end of file diff --git a/src/commands/kemonomimi.js b/src/commands/kemonomimi.js index 7d16938..dbf4d6d 100644 --- a/src/commands/kemonomimi.js +++ b/src/commands/kemonomimi.js @@ -1,4 +1,4 @@ -const API = require('nekos.life'); +/*const API = require('nekos.life'); const {sfw} = new API(); exports.run = async (client, message) => { message.channel.startTyping(); @@ -27,4 +27,4 @@ exports.help = { category: "Image", description: "Sends you pictures of people with animal characteristics.", usage: "kemonomimi" -}; +};*/ diff --git a/src/commands/neko.js b/src/commands/neko.js index 6e62f19..301e850 100644 --- a/src/commands/neko.js +++ b/src/commands/neko.js @@ -1,4 +1,4 @@ -const API = require('nekos.life'); +/*const API = require('nekos.life'); const {sfw} = new API(); exports.run = async (client, message) => { message.channel.startTyping(); @@ -27,4 +27,4 @@ exports.help = { category: "Image", description: "Sends you pictures of catgirls.", usage: "neko" -}; +};*/ diff --git a/src/commands/nekogif.js b/src/commands/nekogif.js index cc878e8..c3405d2 100644 --- a/src/commands/nekogif.js +++ b/src/commands/nekogif.js @@ -1,4 +1,4 @@ -const API = require('nekos.life'); +/*const API = require('nekos.life'); const {sfw} = new API(); exports.run = async (client, message) => { message.channel.startTyping(); @@ -27,4 +27,4 @@ exports.help = { category: "Image", description: "Sends you gifs of catgirls.", usage: "nekogif" -}; +};*/ From 536c6f2b2aa9b8f0a4d1686447792fd288393540 Mon Sep 17 00:00:00 2001 From: Emily Date: Mon, 20 Apr 2020 02:49:36 +0000 Subject: [PATCH 62/78] Update package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index fa5d116..0baac56 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "woomy", - "version": "1.1.0", + "version": "1.2.3", "description": "Woomy is a all-purpose discord bot built off the guidebot base and coded in node.js using discord.js.", "main": "index.js", "dependencies": { From 5fd8da270277b58eae68c5da25a9aff734aa076e Mon Sep 17 00:00:00 2001 From: mudkipscience Date: Tue, 21 Apr 2020 17:05:40 +1000 Subject: [PATCH 63/78] fixed skip command --- src/commands/skip.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/commands/skip.js b/src/commands/skip.js index c997c18..e62db40 100644 --- a/src/commands/skip.js +++ b/src/commands/skip.js @@ -6,9 +6,9 @@ exports.run = (client, message, args, level) => { "<:error:466995152976871434> Nothing is playing." ); - let vc = message.guild.members.cache.get(client.user.id).voiceChannel; + let vc = message.guild.members.cache.get(client.user.id).voice.channel; - if(vc != message.member.voiceChannel) return message.channel.send( + if(vc != message.member.voice.channel) return message.channel.send( '<:error:466995152976871434> You need to be in my voice channel to use this command!' ); From 7e4b3556273b38299bb066f49cda71919d6218ac Mon Sep 17 00:00:00 2001 From: mudkipscience Date: Tue, 21 Apr 2020 17:06:03 +1000 Subject: [PATCH 64/78] update version --- version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.json b/version.json index 6d6b7a3..a28546d 100644 --- a/version.json +++ b/version.json @@ -1,4 +1,4 @@ { - "number": "1.2.3", + "number": "1.2.4", "changelog": "**1.2.0 CHANGELOG:**\n> • Added action commands! (`cuddle`, `feed`, `hug`, `kiss`, `pat`, `poke`, `slap`, `smug`, `tickle`)\n> • Added `fact`\n> • Added `catfact`\n> • Added `dogfact`\n> • Added `yoda`\n> • Added `dice`\n> • Added `spoilerise`\n> • Added `zalgo`\n> • Added `dog`\n> • Added `cat`\n> • Added `lizard`\n> • Added `neko`\n> • Added `nekogif`\n> • Added `kemonomimi`\n> • Added `foxgirl`\n> • Added `identity`\n> • Added `pronouns`\n> • Added `sexuality`\n> • Added `ship`\n> • Renamed `flip to `coinflip` (flip remains as an alias)\n> • Renamed `math` to `calculate` (math is an alias)\n> • @Woomy is now a prefix\n> • Added the `inspire` alias to `inspirobot`\n> • Help now displays the amount of commands in each category\n> • Bots now get a badge in `userinfo`\n> • `roleinfo` now displays what permissions a role has\n> • small changes to `weather`\n> • Woomy now has clear logging of issues that prevent her from starting\n> • request npm module has been swapped out for node-fetch\n**NOTES:**\n> Thank you to Terryiscool160 for creating multiple commands used in this update" } From debd47fe0277b89c75142ded0885ba4b9b200cd4 Mon Sep 17 00:00:00 2001 From: mudkipscience Date: Tue, 21 Apr 2020 17:09:27 +1000 Subject: [PATCH 65/78] cmd.conf.enabled actually does something now --- src/events/message.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/events/message.js b/src/events/message.js index 447bd5b..465ce3f 100644 --- a/src/events/message.js +++ b/src/events/message.js @@ -163,6 +163,10 @@ module.exports = async (client, message) => { return message.author.send(`<:error:466995152976871434> I don't have permission to speak in **#${message.channel.name}**, Please ask a moderator to give me the send messages permission!`); }; + if (!cmd.conf.enabled) { + return message.channel.send('<:error:466995152976871434> This command has been disabled by my developers.') + } + if(message.guild && blacklisted == true) { try { return message.author.send( From 7eab55b98136879f1bc9ef76bd23a917c427998b Mon Sep 17 00:00:00 2001 From: mudkipscience Date: Tue, 21 Apr 2020 17:33:18 +1000 Subject: [PATCH 66/78] disabled problematic commands --- src/commands/foxgirl.js | 3 +-- src/commands/kemonomimi.js | 4 ++-- src/commands/neko.js | 4 ++-- src/commands/nekogif.js | 4 ++-- 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/commands/foxgirl.js b/src/commands/foxgirl.js index a2a2d40..3d8a0e6 100644 --- a/src/commands/foxgirl.js +++ b/src/commands/foxgirl.js @@ -1,4 +1,4 @@ -/*const API = require('nekos.life'); +const API = require('nekos.life'); const {sfw} = new API(); exports.run = async (client, message) => { message.channel.startTyping(); @@ -28,4 +28,3 @@ exports.help = { description: "Sends you pictures of fox girls.", usage: "foxgirl" }; -*/ \ No newline at end of file diff --git a/src/commands/kemonomimi.js b/src/commands/kemonomimi.js index dbf4d6d..7d16938 100644 --- a/src/commands/kemonomimi.js +++ b/src/commands/kemonomimi.js @@ -1,4 +1,4 @@ -/*const API = require('nekos.life'); +const API = require('nekos.life'); const {sfw} = new API(); exports.run = async (client, message) => { message.channel.startTyping(); @@ -27,4 +27,4 @@ exports.help = { category: "Image", description: "Sends you pictures of people with animal characteristics.", usage: "kemonomimi" -};*/ +}; diff --git a/src/commands/neko.js b/src/commands/neko.js index 301e850..6e62f19 100644 --- a/src/commands/neko.js +++ b/src/commands/neko.js @@ -1,4 +1,4 @@ -/*const API = require('nekos.life'); +const API = require('nekos.life'); const {sfw} = new API(); exports.run = async (client, message) => { message.channel.startTyping(); @@ -27,4 +27,4 @@ exports.help = { category: "Image", description: "Sends you pictures of catgirls.", usage: "neko" -};*/ +}; diff --git a/src/commands/nekogif.js b/src/commands/nekogif.js index c3405d2..cc878e8 100644 --- a/src/commands/nekogif.js +++ b/src/commands/nekogif.js @@ -1,4 +1,4 @@ -/*const API = require('nekos.life'); +const API = require('nekos.life'); const {sfw} = new API(); exports.run = async (client, message) => { message.channel.startTyping(); @@ -27,4 +27,4 @@ exports.help = { category: "Image", description: "Sends you gifs of catgirls.", usage: "nekogif" -};*/ +}; From 0cb1d15f12e4dfd10144019f0296776b3eec4816 Mon Sep 17 00:00:00 2001 From: mudkipscience Date: Thu, 30 Apr 2020 13:10:53 +1000 Subject: [PATCH 67/78] remove music functions from functions.js --- src/modules/functions.js | 182 --------------------------------------- 1 file changed, 182 deletions(-) diff --git a/src/modules/functions.js b/src/modules/functions.js index eb759a1..0d62623 100644 --- a/src/modules/functions.js +++ b/src/modules/functions.js @@ -147,188 +147,6 @@ module.exports = client => { return client.users.cache.get(mention); } } - - - // MUSIC - client.music = {guilds: {}}; - - client.music.isYoutubeLink = function(input) { - return input.startsWith('https://www.youtube.com/') || input.startsWith('http://www.youtube.com/') || input.startsWith('https://youtube.com/') || input.startsWith('http://youtube.com/') || input.startsWith('https://youtu.be/') || input.startsWith('http://youtu.be/') || input.startsWith('http://m.youtube.com/') || input.startsWith('https://m.youtube.com/'); - } - - client.music.search = async function(query) - { - return new Promise(function(resolve, reject) - { - try{ - fetch("https://www.googleapis.com/youtube/v3/search?part=id&type=video&q=" + encodeURIComponent(query) + "&key=" + client.config.ytkey) - .then(res => res.json()) - .then(json => { - if(!json.items) { reject(); return; } - resolve(json.items[0]); - }); - } catch (err) { - client.logger.error("Music search err: ", err); - throw err; - }; - }); - } - - client.music.getGuild = function(id) - { - if(client.music.guilds[id]) return client.music.guilds[id]; - - return client.music.guilds[id] = - { - queue: [], - playing: false, - paused: false, - dispatcher: null, - skippers: [] - } - } - - client.music.getMeta = async function(id) - { - return new Promise(function(resolve, reject) - { - youtubeInfo(id, function(err, videoInfo) - { - if(err) throw err; - - resolve(videoInfo); - }); - }); - } - - client.music.play = async function(message, input, bypassQueue) - { - let voiceChannel = message.member.voice.channel; - if(!voiceChannel) return message.channel.send('<:error:466995152976871434> You need to be in a voice channel to use this command!'); - - let permissions = voiceChannel.permissionsFor(client.user); - if (!permissions.has('CONNECT')) { - return message.channel.send('<:error:466995152976871434> I do not have permission to join your voice channel.'); - } - if (!permissions.has('SPEAK')) { - return message.channel.send('<:error:466995152976871434> I do not have permission to join your voice channel.'); - } - if (voiceChannel.joinable != true) { - return message.channel.send("<:error:466995152976871434> I do not have permission to join your voice channel.") - } - - let id = undefined; - - if(client.music.isYoutubeLink(input)) - { - id = await getYoutubeId(input) - } else { - let item = await client.music.search(input); - if(!item) { - return message.channel.send(`<:error:466995152976871434> No results found.`); - }; - id = item.id.videoId; - } - - // music "playing", nothing in queue - if((client.music.getGuild(message.guild.id).playing || client.music.getGuild(message.guild.id).dispatcher) && client.music.getGuild(message.guild.id).queue.length == 0) { - client.music.getGuild(message.guild.id).playing = false; - client.music.getGuild(message.guild.id).dispatcher = null; - // music not playing, something is in queue - } else if(!client.music.getGuild(message.guild.id).playing && !client.music.getGuild(message.guild.id).dispatcher && client.music.getGuild(message.guild.id).queue.length > 0) { - client.music.getGuild(message.guild.id).queue = []; - }; - - if(client.music.getGuild(message.guild.id).queue.length == 0 || bypassQueue) - { - let meta = await client.music.getMeta(id); - - if(!bypassQueue) client.music.getGuild(message.guild.id).queue.push({input: input, id: id, requestedBy: message.author, title: meta.title, author: meta.owner, thumbnail: meta.thumbnailUrl, duration: meta.duration}); - - let connection = await new Promise((resolve, reject) => - { - voiceChannel.join().then((connection) => - { - resolve(connection); - }); - }); - - function end(silent) - { - client.music.getGuild(message.guild.id).queue.shift(); - client.music.getGuild(message.guild.id).dispatcher = null; - - if(client.music.getGuild(message.guild.id).queue.length > 0) - { - client.music.play(message, client.music.getGuild(message.guild.id).queue[0].input, true); - } else { - client.music.getGuild(message.guild.id).playing = false; - - if(!silent) { - message.channel.send("<:play:467216788187512832> Queue is empty! Disconnecting from the voice channel."); - } - - connection.disconnect(); - } - } - - client.music.getGuild(message.guild.id).playing = true; - - let song = client.music.getGuild(message.guild.id).queue[0]; - - try - { - let dispatcher = client.music.getGuild(message.guild.id).dispatcher = connection.play(await ytdl("https://www.youtube.com/watch?v=" + id, {highWaterMark: 1024 * 1024 * 32}), {type: 'opus'}); - - dispatcher.setVolume(0.25) - - dispatcher.on('finish', (a, b) => - { - end(a == "silent"); - }); - } catch(err) { - message.channel.send('<:error:466995152976871434> Failed to play **' + song.title + '** ' + err); - - end(); - } - - client.music.getGuild(message.guild.id).skippers = []; - message.channel.send(`<:play:467216788187512832> Now playing: **${song.title}**`); - } else { - let meta = await client.music.getMeta(id); - let song = {input: input, id: id, requestedBy: message.author, title: meta.title, author: meta.owner, thumbnail: meta.thumbnailUrl, duration: meta.duration}; - - client.music.getGuild(message.guild.id).queue.push(song); - - message.channel.send(`<:success:466995111885144095> Added to queue: **${song.title}**`); - } - } - - // MUSIC - TIMESTAMP - client.createTimestamp = function(duration){ - hrs = ~~(duration / 60 / 60), - min = ~~(duration / 60) % 60, - sec = ~~(duration - min * 60); - - if(String(hrs).length < 2) { - hrs = "0" + String(hrs) + ":"; - }; - - if(String(min).length < 2) { - min = "0" + String(min); - }; - - if(String(sec).length < 2) { - sec = "0" + String(sec); - }; - - if(hrs == "00:") { - hrs = ""; - } - - var time = hrs + min + ":" + sec; - return time; - }; //FIND ROLE client.findRole = function(input, message) { From a5ee79476f1e8bcf99f9e60894ff93c62acd1ea6 Mon Sep 17 00:00:00 2001 From: mudkipscience Date: Thu, 30 Apr 2020 13:16:49 +1000 Subject: [PATCH 68/78] update/remove dependencies --- package.json | 5 +---- src/modules/functions.js | 5 ----- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/package.json b/package.json index 0baac56..73e312c 100644 --- a/package.json +++ b/package.json @@ -4,20 +4,18 @@ "description": "Woomy is a all-purpose discord bot built off the guidebot base and coded in node.js using discord.js.", "main": "index.js", "dependencies": { - "@discordjs/opus": "^0.1.0", + "@discordjs/opus": "^0.2.1", "better-sqlite3": "^5.4.1", "chalk": "^4.0.0", "dblapi.js": "^2.3.1", "discord.js": "^12.0.2", "enmap": "^5.2.4", "garfield": "^1.1.2", - "get-youtube-id": "^1.0.1", "hastebin-gen": "^2.0.5", "moment": "^2.24.0", "moment-duration-format": "^2.3.2", "nekos.life": "^2.0.5", "node-fetch": "^2.6.0", - "openweather-apis": "^4.2.0", "prism-media": "^1.2.1", "randomcolor": "^0.5.4", "relevant-urban": "^2.0.0", @@ -25,7 +23,6 @@ "to-zalgo": "^1.0.1", "urban": "^0.3.2", "weather-js": "^2.0.0", - "youtube-info": "^1.3.2", "ytdl-core-discord": "^1.2.0" }, "devDependencies": {}, diff --git a/src/modules/functions.js b/src/modules/functions.js index 0d62623..6a3a9b7 100644 --- a/src/modules/functions.js +++ b/src/modules/functions.js @@ -1,8 +1,3 @@ -const ytdl = require('ytdl-core-discord'); -const youtubeInfo = require('youtube-info'); -const getYoutubeId = require('get-youtube-id'); -const fetch = require('node-fetch'); - module.exports = client => { // Permission level function client.permlevel = message => { From d07a680b7724adb772a6d967ae579c66cc957c9e Mon Sep 17 00:00:00 2001 From: mudkipscience Date: Thu, 30 Apr 2020 13:18:44 +1000 Subject: [PATCH 69/78] update configTemplate --- configTemplate.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/configTemplate.js b/configTemplate.js index b6e96c3..2c516a0 100644 --- a/configTemplate.js +++ b/configTemplate.js @@ -11,8 +11,12 @@ const config = { // Tokens "token": "", // Your bot's token. "devtoken": "", // (optional) another token, meant for a bot used for development - "ytkey": "", // Youtube API key, needed for music searching to work - "dblkey": "", // top.gg key, sends bot statistics to top.gg. You do not need this. + "dblkey": "", // (optional) top.gg key, sends bot statistics to top.gg. You do not need this. + + // Configurable API endpoints + endpoints: { + invidious: 'https://invidio.us/api/' + }, // Default per-server settings "defaultSettings" : { From def26feab35054802c806bc84fde0f358cee7430 Mon Sep 17 00:00:00 2001 From: mudkipscience Date: Thu, 30 Apr 2020 13:32:19 +1000 Subject: [PATCH 70/78] update rip.js to use node-fetch instead of request --- src/commands/rip.js | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/commands/rip.js b/src/commands/rip.js index d677655..968e08a 100644 --- a/src/commands/rip.js +++ b/src/commands/rip.js @@ -1,12 +1,15 @@ -var request = require('request'); +const fetch = require('node-fetch'); const Discord = require("discord.js") exports.run = (client, message) => { message.channel.startTyping(); - var r = request.get('http://mityurl.com/y/yKsQ/r', function (err, res, body) { - var rip = r.uri.href - message.channel.send(`>:] ${rip}`) - message.channel.stopTyping(); - }); + try{ + fetch('http://mityurl.com/y/yKsQ/r', { redirect: 'follow' }) + .then(res => res) + .then(res => message.channel.send(`>:] ${res.url}`)) + } catch(err) { + message.channel.send(`<:error:466995152976871434> An error has occurred: ${err}`); + }; + message.channel.stopTyping(); } exports.conf = { From d1acf688c9643c0e9266edea3caf1c1f0b4811b8 Mon Sep 17 00:00:00 2001 From: mudkipscience Date: Thu, 30 Apr 2020 14:06:09 +1000 Subject: [PATCH 71/78] update music commands to support new module --- src/commands/forceskip.js | 23 ++-- src/commands/nowplaying.js | 43 ++++--- src/commands/pause.js | 23 ++-- src/commands/play.js | 24 ++-- src/commands/queue.js | 245 ++++++++++++++++++------------------- src/commands/removesong.js | 42 +++---- src/commands/resume.js | 23 ++-- src/commands/skip.js | 69 ++++++----- src/commands/stop.js | 20 +-- 9 files changed, 250 insertions(+), 262 deletions(-) diff --git a/src/commands/forceskip.js b/src/commands/forceskip.js index 7e341d6..0271d8e 100644 --- a/src/commands/forceskip.js +++ b/src/commands/forceskip.js @@ -1,11 +1,16 @@ +const { skip, getGuild } = require('../modules/music') exports.run = (client, message) => { - let guild = client.music.getGuild(message.guild.id); + const guild = getGuild(message.guild.id) - if(guild.queue.length < 1) return message.channel.send( - `<:error:466995152976871434> There is nothing for me to skip!` - ); - skip_song(guild); - message.channel.send("<:skip:467216735356059660> Skipped the song!") + if (guild.queue.length < 1 || !guild.playing || !guild.dispatcher) { + return message.channel.send( + '<:error:466995152976871434> Nothing is playing.' + ) + } + + skip(message.guild, 'skip') + + message.channel.send('<:success:466995111885144095> Song skipped.') }; exports.conf = { @@ -13,7 +18,7 @@ exports.conf = { guildOnly: true, aliases: [], permLevel: "Moderator", - requiredPerms: ["SPEAK"] + requiredPerms: [] }; exports.help = { @@ -22,7 +27,3 @@ exports.help = { description: "Skips the currently playing song without requiring a vote.", usage: "forceskip" }; - -function skip_song(guild) { - guild.dispatcher.end(); -} diff --git a/src/commands/nowplaying.js b/src/commands/nowplaying.js index e94f71a..c26222d 100644 --- a/src/commands/nowplaying.js +++ b/src/commands/nowplaying.js @@ -1,31 +1,30 @@ -const Discord = require("discord.js"); +const { getGuild, createTimestamp } = require('../modules/music') +const { MessageEmbed } = require('discord.js') exports.run = async (client, message) => { - let guild = client.music.getGuild(message.guild.id); - - if(guild.queue.length < 1) { - return message.channel.send("<:error:466995152976871434> Nothing is playing."); + const guild = getGuild(message.guild.id) + + if (guild.queue.length < 1) { + return message.channel.send(client.config.emojis.error + ' Nothing is in the queue!') } - var song = guild.queue[0]; - var elapsedTime = client.createTimestamp(guild.dispatcher.streamTime / 1000); - var timestamp; + const s = guild.queue[0] + const elapsedTime = createTimestamp(guild.dispatcher.streamTime / 1000) + let timestamp = `\`[${createTimestamp(s.video.lengthSeconds)}]\`` - if(song.duration == 0) { - timestamp = "`[LIVE]`"; - } else { - timestamp = `\`[${elapsedTime + "/" + client.createTimestamp(song.duration)}]\``; - }; + if (timestamp !== '`[LIVE]`') { + timestamp = `\`[${elapsedTime + '/' + createTimestamp(s.video.lengthSeconds)}]\`` + } - embed = new Discord.MessageEmbed(); - embed.setTitle("Now playing:") - embed.setThumbnail(song.thumbnail) - embed.setColor(client.embedColour(message)); - embed.setDescription(`**[${song.title}](https://www.youtube.com/watch?v=${song.id})**`) - embed.addField("Channel:", song.author, true) - embed.addField("Time:", timestamp, true) - embed.setFooter("Requested by " + song.requestedBy.tag, song.requestedBy.avatarURL({format: "png", dynamic: true, size: 2048})) + const embed = new MessageEmbed() + embed.setTitle('Now playing') + embed.setThumbnail(s.video.videoThumbnails[1].url) + embed.setColor(client.embedColour(message)) + embed.setDescription(`**[${s.video.title}](https://www.youtube.com/watch?v=${s.video.videoId})**`) + embed.addField('Channel:', s.video.author, true) + embed.addField('Time:', timestamp, true) + embed.setFooter('Requested by ' + s.requestedBy.tag, s.requestedBy.avatarURL({ format: 'png', dynamic: true, size: 2048 })) - message.channel.send(embed) + message.channel.send(embed) }; exports.conf = { diff --git a/src/commands/pause.js b/src/commands/pause.js index 2816baf..e1500e2 100644 --- a/src/commands/pause.js +++ b/src/commands/pause.js @@ -1,15 +1,20 @@ +const { getGuild } = require('../modules/music') exports.run = (client, message, args, level) => { - let guild = client.music.getGuild(message.guild.id); - if(guild.queue.length < 1) { - return message.channel.send("<:error:466995152976871434> Nothing is playing."); - }; + const guild = getGuild(message.guild.id) - guild.playing = false; - guild.paused = true; - guild.dispatcher.pause(); - message.channel.send("<:pause:467639357961142273> Playback paused!"); + if (guild.paused === true) { + return message.channel.send('<:error:466995152976871434> The music has already been paused! Run resume to start the music again.') + } + if (guild.queue.length < 1 || guild.playing === false) { + return message.channel.send('<:error:466995152976871434> Nothing is playing!') + } + guild.playing = false + guild.paused = true + guild.dispatcher.pause() + + message.channel.send('<:pause:467639357961142273> Music playback has been paused.') }; exports.conf = { @@ -17,7 +22,7 @@ exports.conf = { guildOnly: true, aliases: [], permLevel: "Moderator", - requiredPerms: ["CONNECT", "SPEAK"] + requiredPerms: [] }; exports.help = { diff --git a/src/commands/play.js b/src/commands/play.js index d9c8866..882aedd 100644 --- a/src/commands/play.js +++ b/src/commands/play.js @@ -1,20 +1,12 @@ -const util = require("util") +const { play } = require('../modules/music') const Discord = require("discord.js") -module.exports.run = (client, message, args, level) =>{ - if(!args[0]) - { - message.channel.send(`<:error:466995152976871434> You didn't give me a song to play! Usage: \`${client.commands.get(`play`).help.usage}\``); - - return; - } +module.exports.run = async (client, message, args, level) =>{ + if (!args[0]) { + return message.channel.send(`<:error:466995152976871434> You didn't give me a song name or YouTube URL! Usage: \`${client.commands.get('play').help.usage}\``) + } - let voiceChannel = message.member.voice.channel; - if(!voiceChannel) return message.channel.send('<:error:466995152976871434> You need to be in a voice channel to use this command!'); - - message.channel.send(`🔎 searching YouTube for \`${args.join(" ")}\``); - - client.music.play(message, args.join(" ")); + await play(client, message, args.join(' '), false) } exports.conf = { @@ -28,6 +20,6 @@ exports.conf = { exports.help = { name: "play", category: "Music", - description: "Plays a song.", - usage: "play [youtube-url] **OR** play [song-name]" + description: 'Plays the song you request, or adds it to the queue.', + usage: 'playnext [song]', }; diff --git a/src/commands/queue.js b/src/commands/queue.js index 0bdc87e..7ac4d79 100644 --- a/src/commands/queue.js +++ b/src/commands/queue.js @@ -1,171 +1,162 @@ 'use strict'; -const Discord = require("discord.js"); +const { getGuild, createTimestamp } = require('../modules/music') +const Discord = require('discord.js') exports.run = (client, message, args) => { - var queue = client.music.getGuild(message.guild.id).queue; + var queue = getGuild(message.guild.id).queue - if(queue.length < 1) { - return message.channel.send("<:error:466995152976871434> Nothing is playing."); + if (queue.length < 1) { + return message.channel.send('<:error:466995152976871434> Nothing is playing.') } - let lists = []; + const lists = [] - function generateList(start, number) { - var list = ""; - var timestamp; - var livestream; + function generateList (start, number) { + let list = '' + let timestamp - if(start == 1 && queue.length == 1) { - return ["There's nothing else waiting to be played!", 1]; + if (start === 1 && queue.length === 1) { + return ['There\'s nothing else waiting to be played!', 1] } - if(number == 1 && queue.length + 1 < start) { - return false; - }; - - let q = queue.slice(start); - - let i = 0; - - for(i = 0; i < q.length; i++) { - let song = q[i]; - - if(song.duration == 0) { - timestamp = "LIVE"; - livestream = true; - } else { - timestamp = client.createTimestamp(song.duration); - }; - - let aaa = list + `\`${(i + 1) + start - 1}:\` **[${song.title}](https://www.youtube.com/watch?v=${song.id})** added by ${song.requestedBy} \`[${timestamp}]\`\n`; - - if(aaa.length > 1024) { - return [list, start + i - 1]; - } else { - list = aaa; - } - - //totalDuration = totalDuration + song.duration; - }; - - return [list, start + i + 1]; - }; - - let songsInQueue = queue.length - 1; - let songsInQueueEnglish = "song"; - let timeRemaining = 0; - - function generatePage(list, page) { - if(!list || list == "") { - return false; + if (number === 1 && queue.length + 1 < start) { + return false } - var embed = new Discord.MessageEmbed(); - embed.setTitle(`Queue for: ${message.guild.name}`); - embed.setColor(client.embedColour(message)); - - var elapsedTime = client.music.getGuild(message.guild.id).dispatcher.streamTime / 1000 - var totalDuration = queue[0].duration - elapsedTime; + const q = queue.slice(start) - let timeRemaining = ""; - - for(let i = 1; i < queue.length; i++) { - let b = queue[i]; + let i = 0 - if(b.duration == 0) { - timeRemaining = "∞"; + for (i = 0; i < q.length; i++) { + const song = q[i] - break; - } + timestamp = createTimestamp(song.video.lengthSeconds) - totalDuration += b.duration; - } - - if(timeRemaining == "") { - let queueDuration = client.createTimestamp(totalDuration); + const aaa = list + `\`${(i + 1) + start - 1}:\` **[${song.video.title}](https://www.youtube.com/watch?v=${song.video.videoId})** added by ${song.requestedBy} \`[${timestamp}]\`\n` - timeRemaining = queueDuration; - } - - let timestamp; - - if(queue[0].duration == 0) { - timestamp = "LIVE"; - livestream = true; - } else { - timestamp = client.createTimestamp(elapsedTime) + '/' + client.createTimestamp(queue[0].duration); - }; - - embed.addField(`Now playing:`, `**[${queue[0].title}](https://www.youtube.com/watch?v=${queue[0].id})** added by ${queue[0].requestedBy} \`[${timestamp}]\``) - - embed.addField(`Up next:`, list); - - if(songsInQueue > 1 || songsInQueue == 0) { - songsInQueueEnglish = "songs"; - } - - embed.setFooter(`Page ${page}/${lists.length} | ${songsInQueue + " " + songsInQueueEnglish} in queue | ${timeRemaining} time remaining`); - - return embed; - }; - - var myMessage = null; - - function displayPage(number) { - let page = generatePage(lists[number - 1], number); - - if(page) { - if(myMessage) { - myMessage.edit(page); + if (aaa.length > 1024) { + return [list, start + i - 1] } else { - myMessage = message.channel.send(page); + list = aaa } - return true; - } else { - return false; + // totalDuration = totalDuration + song.duration } - }; - function aFunction(start) { + return [list, start + i + 1] + } + + const songsInQueue = queue.length - 1 + let songsInQueueEnglish = 'song' + + function generatePage (list, page) { + if (!list || list === '') { + return false + } + + var embed = new Discord.MessageEmbed() + embed.setTitle(`Queue for: ${message.guild.name}`) + embed.setColor(client.embedColour(message)) + + var elapsedTime = getGuild(message.guild.id).dispatcher.streamTime / 1000 + var totalDuration = queue[0].video.lengthSeconds - elapsedTime + + let timeRemaining = '' + + for (let i = 1; i < queue.length; i++) { + const b = queue[i] + + if (b.video.lengthSeconds === 0) { + timeRemaining = '∞' + + break + } + + totalDuration += b.video.lengthSeconds + } + + if (timeRemaining === '') { + const queueDuration = createTimestamp(totalDuration) + + timeRemaining = queueDuration + } + + let timestamp = `\`${createTimestamp(queue[0].video.lengthSeconds)}\`` + + if (timestamp !== '`[LIVE]`') { + timestamp = `\`[${createTimestamp(elapsedTime) + '/' + createTimestamp(queue[0].video.lengthSeconds)}]\`` + } + + embed.addField('Now playing:', `**[${queue[0].video.title}](https://www.youtube.com/watch?v=${queue[0].video.videoId})** added by ${queue[0].requestedBy} ${timestamp}`) + + embed.addField('Up next:', list) + + if (songsInQueue > 1 || songsInQueue === 0) { + songsInQueueEnglish = 'songs' + } + + embed.setFooter(`Page ${page}/${lists.length} | ${songsInQueue + ' ' + songsInQueueEnglish} in queue | ${timeRemaining} time remaining`) + + return embed + } + + var myMessage = null + + function displayPage (number) { + const page = generatePage(lists[number - 1], number) + + if (page) { + if (myMessage) { + myMessage.edit(page) + } else { + myMessage = message.channel.send(page) + } + + return true + } else { + return false + } + } + + function aFunction (start) { // start - index of song, which we should start with // end - index of song, which we ended with - let [list, end] = generateList(start, lists.length + 1); + const [list, end] = generateList(start, lists.length + 1) - if(list && list != "") { - lists.push(list); - - if(queue[end + 1]) { - aFunction(end + 1); + if (list && list !== '') { + lists.push(list) + + if (queue[end + 1]) { + aFunction(end + 1) } } - }; + } - aFunction(1); + aFunction(1) - let page = 1; + let page = 1 - if(args[0]) { - let userPage = Number(args[0]); + if (args[0]) { + const userPage = Number(args[0]) - if(userPage) { - page = userPage; + if (userPage) { + page = userPage } else { return message.channel.send( - `<:error:466995152976871434> Invalid page. Usage: \`${client.commands.get(`queue`).help.usage}\`` - ); + `<:error:466995152976871434> Invalid page number. Usage: \`${client.commands.get('queue').help.usage}\`` + ) } - }; + } - if(displayPage(page)) { + if (displayPage(page)) { } else { return message.channel.send( `<:error:466995152976871434> Page ${page} doesn't exist!` - ); + ) } -}; +} exports.conf = { enabled: true, diff --git a/src/commands/removesong.js b/src/commands/removesong.js index d576c7d..357720f 100644 --- a/src/commands/removesong.js +++ b/src/commands/removesong.js @@ -1,36 +1,30 @@ -const util = require("util") -const Discord = require("discord.js") - +const { getGuild } = require('../modules/music') module.exports.run = (client, message, args, level) =>{ - var queue = client.music.getGuild(message.guild.id).queue; + var queue = getGuild(message.guild.id).queue - if(queue.length < 2) { - return message.channel.send(`<:error:466995152976871434> Not enough songs are in the queue for this command to work!`); + if (queue.length < 2) { + return message.channel.send('<:error:466995152976871434> Not enough songs are in the queue for this command to work!') } - if(!args[0]) { - return message.channel.send(`<:error:466995152976871434> You didn't tell me what song to remove! Usage: \`${client.commands.get(`removesong`).help.usage}\``); - }; + if (!args[0]) { + return message.channel.send(`<:error:466995152976871434> You didn't tell me what song to remove! Usage: \`${client.commands.get('removesong').help.usage}\``) + } - var input = +args[0]; + var input = +args[0] - if(isNaN(input) == true) { - return message.channel.send(`<:error:466995152976871434> That isn't a number! You need to tell me the songs position in the queue (1, 2, etc.)`); - }; + if (isNaN(input) === true) { + return message.channel.send('<:error:466995152976871434> That isn\'t a number! You need to tell me the songs position in the queue (1, 2, etc.)') + } - if(input >= queue.length) { - return message.channel.send("Invalid (too large)"); - }; + if (input >= queue.length || input < 1) { + return message.channel.send('<:error:466995152976871434> Input is not a valid song ID.') + } - if(input < 1) { - return message.channel.send("Invalid (too small)"); - }; + var songName = queue[input].video.title - var songName = queue[input].title; + queue.splice(input, 1) - queue.splice(input, 1); - - message.channel.send(`<:success:466995111885144095> Removed from queue: **${songName}**`); + message.channel.send(`<:success:466995111885144095> Removed from queue: **${songName}**`) }; exports.conf = { @@ -38,7 +32,7 @@ exports.conf = { guildOnly: true, aliases: ["rmsong"], permLevel: "Moderator", - requiredPerms: ["SPEAK"] + requiredPerms: [] }; exports.help = { diff --git a/src/commands/resume.js b/src/commands/resume.js index 9c63f87..051cdfc 100644 --- a/src/commands/resume.js +++ b/src/commands/resume.js @@ -1,15 +1,20 @@ -const Discord = require("discord.js") +const { getGuild } = require('../modules/music') exports.run = (client, message, args, level) => { - let guild = client.music.getGuild(message.guild.id); - if(guild.queue.length < 1) { - return message.channel.send("<:error:466995152976871434> Nothing is playing."); - }; - guild.playing = true; - guild.paused = false; - guild.dispatcher.resume(); - message.channel.send("<:play:467216788187512832> Playback resumed!"); + const guild = getGuild(message.guild.id) + if (guild.paused === false) { + return message.channel.send('<:error:466995152976871434> The music is already playing, use pause to pause the music first!') + } + if (guild.queue.length < 1) { + return message.channel.send('<:error:466995152976871434> Nothing is playing!') + } + + guild.playing = true + guild.paused = false + guild.dispatcher.resume() + + message.channel.send('<:success:466995111885144095> Music playback has been resumed.') }; exports.conf = { diff --git a/src/commands/skip.js b/src/commands/skip.js index e62db40..35ee02f 100644 --- a/src/commands/skip.js +++ b/src/commands/skip.js @@ -1,49 +1,50 @@ -const Discord = require("discord.js") +const { skip, getGuild } = require('../modules/music') exports.run = (client, message, args, level) => { - let guild = client.music.getGuild(message.guild.id); + const guild = getGuild(message.guild.id) - if(guild.queue.length < 1 || !guild.playing || !guild.dispatcher) return message.channel.send( - "<:error:466995152976871434> Nothing is playing." - ); - - let vc = message.guild.members.cache.get(client.user.id).voice.channel; - - if(vc != message.member.voice.channel) return message.channel.send( - '<:error:466995152976871434> You need to be in my voice channel to use this command!' - ); - - if(guild.queue[0].requestedBy.id == message.author.id) { - skip_song(guild); - - message.channel.send( - `<:skip:467216735356059660> Song has been skipped by the user who requested it.` - ); - - return; + if (guild.queue.length < 1 || !guild.playing || !guild.dispatcher) { + return message.channel.send( + '<:error:466995152976871434> Nothing is playing.' + ) } - if (guild.skippers.indexOf(message.author.id) == -1) { - guild.skippers.push(message.author.id); + const vc = message.guild.members.cache.get(client.user.id).voice.channel + + if (vc !== message.member.voice.channel) { + return message.channel.send( + '<:error:466995152976871434> You need to be in my voice channel to use this command!' + ) + } + + if (guild.queue[0].requestedBy.id === message.author.id) { + skip(message.guild, 'skip') + + message.channel.send( + '<:success:466995111885144095> Song has been skipped by the user who requested it.' + ) + + return + } + + if (guild.skippers.indexOf(message.author.id) === -1) { + guild.skippers.push(message.author.id) if (guild.skippers.length >= Math.ceil(vc.members.filter(member => !member.user.bot).size / 2)) { - - skip_song(guild); + skip(message.guild, 'skip') message.channel.send( - `<:skip:467216735356059660> Song has been skipped.` - ); - + '<:skip:467216735356059660> Song skipped.' + ) } else { message.channel.send( - `<:success:466995111885144095> Your vote has been acknowledged! **${guild.skippers.length + "/" + Math.ceil(vc.members.filter(member => !member.user.bot).size / 2)}**` - ); + `<:success:466995111885144095> Your vote has been acknowledged! **${guild.skippers.length + '/' + Math.ceil(vc.members.filter(member => !member.user.bot).size / 2)}**` + ) }; - } else { message.channel.send( - "<:denied:466995195150336020> You cannot vote twice!" - ); - }; + '<:denied:466995195150336020> You cannot vote twice!' + ) + } }; exports.conf = { @@ -51,7 +52,7 @@ exports.conf = { guildOnly: true, aliases: ["voteskip"], permLevel: "User", - requiredPerms: ["SPEAK"] + requiredPerms: [] }; exports.help = { diff --git a/src/commands/stop.js b/src/commands/stop.js index 695dd04..b36b2a1 100644 --- a/src/commands/stop.js +++ b/src/commands/stop.js @@ -1,18 +1,18 @@ -const Discord = require("discord.js"); - +const { getGuild } = require('../modules/music') exports.run = async (client, message) => { - let guild = client.music.getGuild(message.guild.id); + const guild = getGuild(message.guild.id) - if(guild.queue.length < 1 || !guild.playing || !guild.dispatcher) return message.channel.send("<:error:466995152976871434> Nothing is playing."); - if(!message.member.voice.channel) return message.channel.send('<:error:466995152976871434> You need to be in voice channel to use this command!'); + if (guild.queue.length < 1 || !guild.playing || !guild.dispatcher) return message.channel.send('Nothing is playing.') + if (!message.member.voice.channel) return message.channel.send('You need to be in voice channel to use this command!') - guild.playing = false; - guild.paused = false; - guild.queue = []; + guild.dispatcher.end('silent') - guild.dispatcher.end("silent"); + guild.queue = [] + guild.playing = false + guild.paused = false + guild.skippers = [] - message.channel.send("<:stop:467639381390262284> Playback stopped!"); + message.channel.send('<:success:466995111885144095> Playback stopped!') }; exports.conf = { From 6d608455464b130cbe947b96bbc7d30584da55e4 Mon Sep 17 00:00:00 2001 From: mudkipscience Date: Thu, 30 Apr 2020 14:06:15 +1000 Subject: [PATCH 72/78] new music module --- src/modules/music.js | 246 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 246 insertions(+) create mode 100644 src/modules/music.js diff --git a/src/modules/music.js b/src/modules/music.js new file mode 100644 index 0000000..a3bb99e --- /dev/null +++ b/src/modules/music.js @@ -0,0 +1,246 @@ +// Copyright 2020 Emily J. / mudkipscience and contributors. Subject to the AGPLv3 license. + +const ytdl = require('ytdl-core-discord') +const fetch = require('node-fetch') +const { MessageEmbed } = require('discord.js') +const { utc } = require('moment') + +exports.queue = {} + +exports.createTimestamp = function (s) { + if (s < 1) { + return 'LIVE' + } else if (s >= 3600) { + return utc(s * 1000).format('HH:mm:ss') + } else { + return utc(s * 1000).format('mm:ss') + } +} + +exports.getGuild = function (id) { + let guild = exports.queue[id] + + if (!guild) { + guild = {} + + guild.queue = [] + guild.playing = false + guild.paused = false + guild.dispatcher = null + guild.skippers = [] + + exports.queue[id] = guild + } + + return guild +} + +exports.getLinkFromID = function (id) { + return 'https://www.youtube.com/watch?v=' + id +} + +exports.getVideoByQuery = async function (client, query, message) { + let res + + try { + const id = await ytdl.getURLVideoID(query) + res = await fetch(`${client.config.endpoints.invidious}v1/videos/${id}`) + } catch (err) { + res = await fetch(`${client.config.endpoints.invidious}v1/search?q=${encodeURIComponent(query)}`) + } + + const parsed = await res.json().catch(function (e) { + return message.channel.send('<:error:466995152976871434> An error has occured: ' + e) + }) + + if (parsed) { + const videos = parsed + if (videos) { + return videos + } else { + return false + } + } else { + return false + } +} + +exports.play = async function (client, message, query, playNext, ignoreQueue) { + const guild = exports.getGuild(message.guild.id) + guild.message = message + + message.channel.startTyping() + + if (!message.member.voice.channel && !guild.voiceChannel) { + message.channel.stopTyping() + return message.channel.send('<:error:466995152976871434> You have to be connected to a voice channel to use this command!') + } + + const vc = message.member.voice.channel + + let video + let videos + + if (!ignoreQueue) { + videos = await exports.getVideoByQuery(client, query, message) + if (!videos[1]) { + if (!videos[0]) { + video = videos + message.channel.stopTyping() + } else { + video = videos[0] + } + } + } + + if (videos || ignoreQueue) { + if (!ignoreQueue) { + // Fix the bot if somehow broken + // music "playing", nothing in queue + if ((guild.playing || guild.dispatcher) && guild.queue.length === 0) { + guild.queue = [] + guild.playing = false + guild.paused = false + guild.skippers = [] + // music not playing, something is in queue + } else if (!guild.playing && !guild.dispatcher && guild.queue.length > 0) { + guild.queue = [] + } + + if (!video) { + let output = '' + let i = 0 + for (i = 0; i < 5; i++) { + if (!videos[i]) break + output += `\`${i + 1}:\` **[${videos[i].title}](https://www.youtube.com/watch?v=${videos[i].videoId})** \`[${exports.createTimestamp(videos[i].lengthSeconds)}]\`\n` + } + + message.channel.stopTyping() + const embed = new MessageEmbed() + embed.setTitle('Please reply with a number `1-' + i + '` to select which song you want to add to the queue.') + embed.setColor(client.embedColour(message)) + embed.setDescription(output) + + let selection = await client.awaitReply(message, embed) + selection = Number(selection) + + switch (selection) { + case 1: + video = videos[0] + break + case 2: + if (videos[1]) { + video = videos[1] + } else { + return message.channel.send('<:error:466995152976871434> Invalid choice.') + } + break + case 3: + if (videos[2]) { + video = videos[2] + } else { + return message.channel.send('<:error:466995152976871434> Invalid choice.') + } + break + case 4: + if (videos[3]) { + video = videos[3] + } else { + return message.channel.send('<:error:466995152976871434> Invalid choice.') + } + break + case 5: + if (videos[4]) { + video = videos[4] + } else { + return message.channel.send('<:error:466995152976871434> Invalid choice.') + } + break + default: + return message.channel.send('<:error:466995152976871434> Invalid choice.') + } + } + + if (!video && videos[0]) { + video = videos[0] + } else if (!video) { + video = videos + } + + // Add video to queue + if (playNext === true) { + guild.queue.splice(1, 0, { video: video, requestedBy: message.author }) + } else { + guild.queue.push({ video: video, requestedBy: message.author }) + } + } + + // Figure out if the bot should add it to queue or play it right now + if (guild.playing) { + message.channel.send('<:success:466995111885144095> Queued **' + video.title + '** `[' + exports.createTimestamp(video.lengthSeconds) + ']`') + } else { + guild.playing = true + + guild.voiceChannel = vc + + if (!guild.channel) { + guild.channel = message.channel + } + + const connection = await vc.join() + + const v = guild.queue[0] + + try { + guild.dispatcher = connection.play(await ytdl(exports.getLinkFromID(v.video.videoId), { highWaterMark: 1024 * 1024 * 32 }), { type: 'opus' }) + } catch (err) { + if (playNext && playNext === true) { + guild.queue.splice(1, 1) + } else { + guild.queue.pop() + } + + client.logger.error(err) + return message.channel.send(`<:error:466995152976871434> An error has occured! If this issue persists, please contact my developers with this:\n\`${err}\``) + } + guild.dispatcher.setVolume(0.25) + + guild.channel.send('<:player:467216674622537748> Now playing: **' + v.video.title + '** `[' + exports.createTimestamp(v.video.lengthSeconds) + ']`') + + // play next in queue on end + guild.dispatcher.once('finish', () => { + guild.queue.shift() + guild.playing = false + + if (guild.queue.length > 0) { + exports.play(client, message, null, false, true) + } else { + guild.queue = [] + guild.playing = false + guild.paused = false + guild.skippers = [] + + connection.disconnect() + } + }) + } + } else { + return message.channel.channelsend('failed to find the video!') + } +} + +exports.setVolume = function (guild, target) { + const g = exports.getGuild(guild.id) + + if (g.dispatcher) { + g.dispatcher.setVolume(target) + } +} + +exports.skip = function (guild, reason) { + const g = exports.getGuild(guild.id) + + if (g.dispatcher) { + g.dispatcher.end(reason) + } +} From 0b8c4ec38dd8e26f388b822486e54c2ab5293eb5 Mon Sep 17 00:00:00 2001 From: mudkipscience Date: Thu, 30 Apr 2020 14:06:41 +1000 Subject: [PATCH 73/78] player monitor to hopefully help music break less --- src/events/voiceStateUpdate.js | 42 ++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 src/events/voiceStateUpdate.js diff --git a/src/events/voiceStateUpdate.js b/src/events/voiceStateUpdate.js new file mode 100644 index 0000000..5b40a54 --- /dev/null +++ b/src/events/voiceStateUpdate.js @@ -0,0 +1,42 @@ +// Copyright 2020 Emily J. / mudkipscience and contributors. Subject to the AGPLv3 license. + +const music = require('../modules/music') + +module.exports = (client, oldState, newState) => { + if (newState.channelID !== oldState.channelID) { + const guild = music.getGuild(newState.guild.id) + + // Reset queue, dispatcher, etc if Woomy is forcibly disconnected from the queue + if (guild.voiceChannel && !guild.voiceChannel.members.get(client.user.id) && guild.queue.length > 0) { + guild.queue = [] + guild.playing = false + guild.paused = false + guild.skippers = [] + } + + // Auto-disconnect feature + if (guild.playing && guild.channel && guild.voiceChannel.id === oldState.channelID) { + if (guild.voiceChannel.members.filter(member => !member.user.bot).size < 1) { + guild.message.channel.send('Everyone has left my voice channel, the music will end in two minutes if no one rejoins.') + .then(msg => { + msg.delete({ timeout: 120000 }) + }) + + setTimeout(() => { + if (guild.dispatcher !== null && guild.voiceChannel.members.filter(member => !member.user.bot).size < 1) { + // Probably should be async? But no need here I think + guild.dispatcher.end('silent') + + guild.queue = [] + guild.playing = false + guild.paused = false + guild.dispatcher = null + guild.skippers = [] + + guild.message.channel.send('The music has ended because no one was listening to me ;~;') + } + }, 120000) + } + } + } +} From 42df6b71c7f9c0a86aba5a9be221a8687cd14446 Mon Sep 17 00:00:00 2001 From: mudkipscience Date: Thu, 30 Apr 2020 14:11:02 +1000 Subject: [PATCH 74/78] backport new ship command --- src/commands/ship.js | 72 ++++++++++++++++++++++++++------------------ 1 file changed, 43 insertions(+), 29 deletions(-) diff --git a/src/commands/ship.js b/src/commands/ship.js index 3850d87..cd62b5d 100644 --- a/src/commands/ship.js +++ b/src/commands/ship.js @@ -1,40 +1,54 @@ +const Discord = require('discord.js') exports.run = async (client, message, args) => { - - var name, name1; - var rating = Math.floor(Math.random() * 100) + 1; + var rating = Math.floor(Math.random() * 100) + 1 + var meter = ['▬', '▬', '▬', '▬', '▬', '▬', '▬', '▬', '▬'] var hearts = [ - "❤️", - "🧡", - "💛", - "💚", - "💙", - "💜" - ]; - - if(args.length < 2) { - return message.channel.send(`<:error:466995152976871434> Please include two names/users.`) + '❤️', + '🧡', + '💛', + '💚', + '💙', + '💜' + ] + + if (!args[0]) { + return message.channel.send(client.userError(exports, 'Missing argument, the `name1` argument is required!')) } - if(message.guild && message.mentions.members && message.mentions.members.size > 0) { - name = message.mentions.members.first().displayName; - }; + if (!args[1]) { + return message.channel.send(client.userError(exports, 'Missing argument, the `name2` argument is required!')) + } - if(message.guild && message.mentions.members && message.mentions.members.size > 1) { - name1 = message.mentions.members.last().displayName; - }; + const firstName = args[0] + const secondName = args[1] - if(!name) { - name = args[0]; - }; + const shipName = firstName.substr(0, firstName.length * 0.5) + secondName.substr(secondName.length * 0.5) - if(!name1) { - name1 = args[1]; - }; + if (shipName.toLowerCase() === 'teily' || shipName.toLowerCase() === 'emrra') { + rating = '100' + } - shipName = name.substr(0, client.intBetween(1,name.length))+name1.substr(client.intBetween(0,name1.length)); + var pos = 0 + var under = 9 + while (pos < 10) { + if (rating < under) { + meter.splice(pos, 0, hearts.random()) + break + } + pos++ + under += 10 + } - message.channel.send(`__**Ship Generator:**__\n${hearts.random()} Ship Name: \`${shipName}\`\n${hearts.random()} Compatibility rating: \`${rating}%\``) -}; + if (rating >= 99) { + meter.splice(9, 0, hearts.random()) + } + + const embed = new Discord.MessageEmbed() + embed.setTitle(`Original Names: ${firstName}, ${secondName}`) + embed.setColor(client.embedColour(message.guild)) + embed.setDescription(`Ship Name: **${shipName}**\nCompatibility: **${rating}%**\n**[**${meter.join('')}**]**`) + message.channel.send(embed) +} exports.conf = { enabled: true, @@ -48,6 +62,6 @@ exports.help = { name: "ship", category: "Fun", description: "Ship two people together <3", - usage: "ship [name/user] [name/user]" + usage: "ship [name1] [name2]" }; From 59084b07cb553788d045473db0f1bfa2bcf2b7a5 Mon Sep 17 00:00:00 2001 From: mudkipscience Date: Thu, 30 Apr 2020 14:44:23 +1000 Subject: [PATCH 75/78] Add new music commands --- src/commands/fixmusic.js | 30 +++++++++++++++++++++ src/commands/movehere.js | 33 +++++++++++++++++++++++ src/commands/movesong.js | 56 +++++++++++++++++++++++++++++++++++++++ src/commands/playnext.js | 23 ++++++++++++++++ src/commands/shuffle.js | 34 ++++++++++++++++++++++++ src/commands/songinfo.js | 42 +++++++++++++++++++++++++++++ src/commands/volume.js | 57 ++++++++++++++++++++++++++++++++++++++++ 7 files changed, 275 insertions(+) create mode 100644 src/commands/fixmusic.js create mode 100644 src/commands/movehere.js create mode 100644 src/commands/movesong.js create mode 100644 src/commands/playnext.js create mode 100644 src/commands/shuffle.js create mode 100644 src/commands/songinfo.js create mode 100644 src/commands/volume.js diff --git a/src/commands/fixmusic.js b/src/commands/fixmusic.js new file mode 100644 index 0000000..387aa9f --- /dev/null +++ b/src/commands/fixmusic.js @@ -0,0 +1,30 @@ +const { getGuild } = require('../modules/music') +module.exports.run = async (client, message, args, level) =>{ + guild = getGuild(message.guild.id) + + guild.queue = [] + guild.playing = false + guild.paused = false + guild.skippers = [] + + if (guild.dispatcher) { + guild.dispatcher.end('silent') + } + + message.channel.send('<:success:466995111885144095> Music has been fixed (hopefully)') +} + +exports.conf = { + enabled: true, + guildOnly: true, + aliases: [], + permLevel: "Moderator", + requiredPerms: [] +}; + +exports.help = { + name: "fixmusic", + category: "Music", + description: 'Fixes music if it breaks.', + usage: 'fixmusic', +}; diff --git a/src/commands/movehere.js b/src/commands/movehere.js new file mode 100644 index 0000000..61ea0ee --- /dev/null +++ b/src/commands/movehere.js @@ -0,0 +1,33 @@ +const { getGuild } = require('../modules/music') +const Discord = require("discord.js") + +module.exports.run = async (client, message, args, level) =>{ + const guild = getGuild(message.guild.id) + + if (!guild.playing) { + return message.channel.send('<:error:466995152976871434> Nothing is playing.') + } + + if (guild.channel.id === message.channel.id) { + return message.channel.send('<:error:466995152976871434> Music messages are already being sent to this channel.') + } + + guild.channel = message.channel + + message.channel.send('<:success:466995111885144095> Music messages will now be sent to this channel.') +} + +exports.conf = { + enabled: true, + guildOnly: true, + aliases: [], + permLevel: "Moderator", + requiredPerms: [] +}; + +exports.help = { + name: 'movehere', + category: 'Music', + description: 'Moves music related messages to the channel the this command is ran in.', + usage: 'movehere', +}; diff --git a/src/commands/movesong.js b/src/commands/movesong.js new file mode 100644 index 0000000..83c6a7c --- /dev/null +++ b/src/commands/movesong.js @@ -0,0 +1,56 @@ +const { getGuild } = require('../modules/music') +exports.run = async (client, message, args) => { + const queue = getGuild(message.guild.id).queue + + if (queue.length < 3) { + return message.channel.send('<:error:466995152976871434> Not enough songs are in the queue for this command to work!') + } + + if (!args[0]) { + return client.userError(message, exports, 'Missing argument, the `current position` argument is required!') + } + + if (!args[1]) { + return client.userError(message, exports, 'Missing argument, the `new position` argument is required!') + } + + const oldPosition = +args[0] + const newPosition = +args[1] + + if (isNaN(oldPosition) === true) { + return message.channel.send('<:error:466995152976871434> That isn\'t a number! You need to tell me the songs position in the queue (1, 2, etc.)') + } + + if (isNaN(newPosition) === true) { + return message.channel.send('<:error:466995152976871434> That isn\'t a number! You need to tell me the songs position in the queue (1, 2, etc.)') + } + + if (oldPosition < 1 || oldPosition >= queue.length) { + return message.channel.send('<:error:466995152976871434> Old position is not a valid song ID.') + } + + if (newPosition < 1 || newPosition >= queue.length) { + return message.channel.send('<:error:466995152976871434> New position is not a valid song ID.') + } + + const songName = queue[oldPosition].video.title + + queue.splice(newPosition, 0, queue.splice(oldPosition, 1)[0]) + + message.channel.send(`<:success:466995111885144095> Moved **${songName}** from position \`${oldPosition}\` to \`${newPosition}\``) +} + +exports.conf = { + enabled: true, + guildOnly: true, + aliases: [], + permLevel: "Moderator", + requiredPerms: [] +} + +exports.help = { + name: 'movesong', + category: 'Music', + description: 'Moves a song to a new position in the queue.', + usage: 'movesong [current position] [new position]' +} \ No newline at end of file diff --git a/src/commands/playnext.js b/src/commands/playnext.js new file mode 100644 index 0000000..6314283 --- /dev/null +++ b/src/commands/playnext.js @@ -0,0 +1,23 @@ +const { play } = require('../modules/music') +exports.run = async (client, message, args) => { + if (!args[0]) { + return message.channel.send(`<:error:466995152976871434> You didn't give me a song name or YouTube URL! Usage: \`${client.commands.get('play').help.usage}\``) + } + + await play(client, message, args.join(' '), true) +} + +exports.conf = { + enabled: true, + guildOnly: true, + aliases: [], + permLevel: "Moderator", + requiredPerms: [] +} + +exports.help = { + name: 'playnext', + category: 'Music', + description: 'Similar to play, but adds it to the start of the queue instead of the end.', + usage: 'playnext [song]' +} \ No newline at end of file diff --git a/src/commands/shuffle.js b/src/commands/shuffle.js new file mode 100644 index 0000000..23270e5 --- /dev/null +++ b/src/commands/shuffle.js @@ -0,0 +1,34 @@ +const { getGuild } = require('../modules/music') +exports.run = async (client, message) => { + var queue = getGuild(message.guild.id).queue + + if (queue.length < 4) { + return message.channel.send('<:error:466995152976871434> There aren\'t enough songs are in the queue for this command to work!') + } + + const max = queue.length - 1 + const min = 1 + for (let i = max; i >= min; i--) { + const randomIndex = Math.floor(Math.random() * (max - min + 1)) + min + const itemAtIndex = queue[randomIndex] + queue[randomIndex] = queue[i] + queue[i] = itemAtIndex + } + + message.channel.send('<:success:466995111885144095> Queue shuffled!') +} + +exports.conf = { + enabled: true, + guildOnly: true, + aliases: [], + permLevel: "Moderator", + requiredPerms: [] +} + +exports.help = { + name: 'shuffle', + category: 'Music', + description: 'Mixes up the songs in the queue', + usage: 'shuffle' +} \ No newline at end of file diff --git a/src/commands/songinfo.js b/src/commands/songinfo.js new file mode 100644 index 0000000..cd7bb0b --- /dev/null +++ b/src/commands/songinfo.js @@ -0,0 +1,42 @@ +const { getGuild, createTimestamp } = require('../modules/music') +const { MessageEmbed } = require('discord.js') +exports.run = async (client, message, args) => { + const guild = getGuild(message.guild.id) + + if (guild.queue.length < 1) { + return message.channel.send(client.config.emojis.error + ' Nothing is in the queue!') + } + + const songID = +args[0] + + if (isNaN(songID) === true) { + return message.channel.send('<:error:466995152976871434> That isn\'t a number! You need to tell me the songs position in the queue (1, 2, etc.)') + } + + const s = guild.queue[songID] + + const embed = new MessageEmbed() + embed.setThumbnail(s.video.videoThumbnails[1].url) + embed.setColor(client.embedColour(message)) + embed.setDescription(`**[${s.video.title}](https://www.youtube.com/watch?v=${s.video.videoId})**`) + embed.addField('Channel:', s.video.author, true) + embed.addField('Length:', '`[' + createTimestamp(s.video.lengthSeconds) + ']`', true) + embed.setFooter('Requested by ' + s.requestedBy.tag, s.requestedBy.avatarURL({ format: 'png', dynamic: true, size: 2048 })) + + message.channel.send(embed) +} + +exports.conf = { + enabled: true, + guildOnly: true, + aliases: [], + permLevel: "User", + requiredPerms: [] +} + +exports.help = { + name: "songinfo", + category: "Music", + description: "Sends you information about a song in the queue. Song ID is the song's position in the queue.", + usage: "songinfo [songID]" +} \ No newline at end of file diff --git a/src/commands/volume.js b/src/commands/volume.js new file mode 100644 index 0000000..6711947 --- /dev/null +++ b/src/commands/volume.js @@ -0,0 +1,57 @@ +const { getGuild, setVolume } = require('../modules/music') +exports.run = async (client, message, args) => { + if (!args[0]) { + return message.channel.send(`<:error:466995152976871434> No input! Usage: \`${client.commands.get('volume').help.usage}\``) + } + + const guild = getGuild(message.guild.id) + + if (guild.queue.length < 1 || !guild.playing || !guild.dispatcher) { + return message.channel.send( + '<:error:466995152976871434> Nothing is playing.' + ) + } + + let userVolume = args[0] + + if (userVolume.includes('%')) { + userVolume = userVolume.replace('%', '') + } + + userVolume = +userVolume + + if (isNaN(userVolume) === true) { + return message.channel.send('<:error:466995152976871434> Input must be a number!') + } + + if (userVolume > 100 || userVolume < 1) { + return message.channel.send('<:error:466995152976871434> Invalid input, input must be between 1-100') + } + + if (userVolume) { + userVolume = Number(userVolume) + + userVolume = userVolume / 100 + + if (userVolume <= 1) { + setVolume(message.guild, userVolume) + + message.channel.send('<:success:466995111885144095> Set volume to ' + userVolume * 100 + '%') + } + } +} + +exports.conf = { + enabled: true, + guildOnly: true, + aliases: [], + permLevel: "Moderator", + requiredPerms: [] +} + +exports.help = { + name: 'volume', + category: 'Music', + description: 'Sets volume of currently playing music. (100% = 25% of the actual volume)', + usage: 'volume [volume]' +} \ No newline at end of file From fef8c0e5b0ef72c8b5613e4d657f126627509014 Mon Sep 17 00:00:00 2001 From: mudkipscience Date: Thu, 30 Apr 2020 14:48:56 +1000 Subject: [PATCH 76/78] update changelog --- version.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/version.json b/version.json index a28546d..67e2097 100644 --- a/version.json +++ b/version.json @@ -1,4 +1,4 @@ { - "number": "1.2.4", - "changelog": "**1.2.0 CHANGELOG:**\n> • Added action commands! (`cuddle`, `feed`, `hug`, `kiss`, `pat`, `poke`, `slap`, `smug`, `tickle`)\n> • Added `fact`\n> • Added `catfact`\n> • Added `dogfact`\n> • Added `yoda`\n> • Added `dice`\n> • Added `spoilerise`\n> • Added `zalgo`\n> • Added `dog`\n> • Added `cat`\n> • Added `lizard`\n> • Added `neko`\n> • Added `nekogif`\n> • Added `kemonomimi`\n> • Added `foxgirl`\n> • Added `identity`\n> • Added `pronouns`\n> • Added `sexuality`\n> • Added `ship`\n> • Renamed `flip to `coinflip` (flip remains as an alias)\n> • Renamed `math` to `calculate` (math is an alias)\n> • @Woomy is now a prefix\n> • Added the `inspire` alias to `inspirobot`\n> • Help now displays the amount of commands in each category\n> • Bots now get a badge in `userinfo`\n> • `roleinfo` now displays what permissions a role has\n> • small changes to `weather`\n> • Woomy now has clear logging of issues that prevent her from starting\n> • request npm module has been swapped out for node-fetch\n**NOTES:**\n> Thank you to Terryiscool160 for creating multiple commands used in this update" + "number": "1.3.0", + "changelog": "**1.3.0 Changelog:**\n> • Music module has been rewritten for better stability and lots more features\n> • Force disconnecting Woomy from a voice channel no longer breaks music\n> • Music should (hopefully) break less in general\n> • Existing music commands have been rewritten\n> • Added the following new commands: fixmusic, movehere, movesong, playnext, shuffle, songinfo, volume\n> • Updated ship command\n**Notes:**\n> • This will be the final major update to Woomy V1, as we are shifting our focus to Woomy V2, which is a complete rewrite." } From bdbea2abb95e542f230a15b3119df0996a3edc95 Mon Sep 17 00:00:00 2001 From: IFcoltransG <47414286+IFcoltransG@users.noreply.github.com> Date: Sat, 2 May 2020 13:14:28 +1200 Subject: [PATCH 77/78] Spelling mistake in Bohemian Rhapsody description --- src/commands/bohemian_rhapsody.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/bohemian_rhapsody.js b/src/commands/bohemian_rhapsody.js index c9bb3ef..878b8e9 100644 --- a/src/commands/bohemian_rhapsody.js +++ b/src/commands/bohemian_rhapsody.js @@ -40,6 +40,6 @@ exports.conf = { exports.help = { name: "bohemian_rhapsody", category: "Fun", - description: "Queen kareoke", + description: "Queen karaoke", usage: "bohemian_rhapsody" }; From b2b5431ee4d36a623eb805c30b7c2e14afb2bcd7 Mon Sep 17 00:00:00 2001 From: mudkipscience Date: Sat, 2 May 2020 18:48:58 +1000 Subject: [PATCH 78/78] fixed bugs --- src/commands/say.js | 4 ++-- src/events/voiceStateUpdate.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/commands/say.js b/src/commands/say.js index e196847..e63fc48 100644 --- a/src/commands/say.js +++ b/src/commands/say.js @@ -5,7 +5,7 @@ exports.run = (client, message, args, level) => { ); }; if (message.content.includes("@everyone")) { - return message.channel.send(`<@${message.author.id}>`); + return message.channel.send(message.author); }; message.delete().catch(O_o => {}); @@ -24,5 +24,5 @@ exports.help = { name: "say", category: "Fun", description: "Makes Woomy copy what the user says.", - usage: "echo <-hide> [message]" + usage: "echo [message]" }; diff --git a/src/events/voiceStateUpdate.js b/src/events/voiceStateUpdate.js index 5b40a54..34fd833 100644 --- a/src/events/voiceStateUpdate.js +++ b/src/events/voiceStateUpdate.js @@ -15,7 +15,7 @@ module.exports = (client, oldState, newState) => { } // Auto-disconnect feature - if (guild.playing && guild.channel && guild.voiceChannel.id === oldState.channelID) { + if (guild.playing && guild.voiceChannel && guild.voiceChannel.id === oldState.channelID) { if (guild.voiceChannel.members.filter(member => !member.user.bot).size < 1) { guild.message.channel.send('Everyone has left my voice channel, the music will end in two minutes if no one rejoins.') .then(msg => {