diff --git a/cmds_SharpBot/Fun/8ball.js b/cmds_SharpBot/Fun/8ball.js new file mode 100644 index 0000000..83a2119 --- /dev/null +++ b/cmds_SharpBot/Fun/8ball.js @@ -0,0 +1,42 @@ +const responses = [ + 'Unclear, ask again later', + 'Soon', + 'Yes', + 'Absolutely!', + 'Never', + 'Magic 8-ball is currently unavailable, please leave a message after the tone. \\*beep\\*', + 'When you are ready', + 'Hopefully', + 'Hopefully not', + 'Oh my, why would you even ask that?', + 'What kind of a question is that?', + 'Over my dead body!', + 'Haha, funny joke' +]; + +function randomItem(array) { + return array[Math.floor(Math.random() * array.length)]; +} + +exports.run = (bot, msg, args) => { + if (args.length < 1) { + throw 'Please specify something to ask of the magic 8-ball!'; + } + + let response = randomItem(responses); + + const query = args.join(' '); + + if (query.indexOf('ipodtouch0218') > -1 || query.indexOf('233360087979130882') > -1) { + response = 'HAH'; + } + + msg.edit(`${query} :8ball: | **${response}**`); +}; + + +exports.info = { + name: '8ball', + usage: '8ball ', + description: 'Asks the magic 8-ball a question' +}; diff --git a/cmds_SharpBot/Fun/anim.js b/cmds_SharpBot/Fun/anim.js new file mode 100644 index 0000000..f787630 --- /dev/null +++ b/cmds_SharpBot/Fun/anim.js @@ -0,0 +1,24 @@ +exports.run = (bot, msg, args) => { + let parsed = bot.utils.parseArgs(args, 'd:'); + + if (parsed.leftover.length < 1) { + throw 'Please provide some emojis to use!'; + } + + let frames = parsed.leftover; + let content = frames.join(' '); + + if (content.indexOf('|') > -1) { + frames = content.split('|'); + } + + let delay = isNaN(parsed.options.d) ? 250 : parsed.options.d; + + bot.utils.playAnimation(msg, delay, frames); +}; + +exports.info = { + name: 'anim', + usage: 'anim [-d ] [emoji2] [...]', + description: '"Animates" a series of emojis' +}; diff --git a/cmds_SharpBot/Fun/animals.js b/cmds_SharpBot/Fun/animals.js new file mode 100644 index 0000000..f38a2a6 --- /dev/null +++ b/cmds_SharpBot/Fun/animals.js @@ -0,0 +1,30 @@ +const got = require('got'); + +function makeCommand(type, url, transformer) { + return { + run: async (bot, msg) => { + msg.edit(':arrows_counterclockwise:'); + const { body } = await got(url); + + let file; + try { + file = transformer(body); + } catch (ignore) { + return msg.error('Failed to transform image URL!'); + } + + msg.delete(); + msg.channel.send({ files: [file] }); + }, + info: { + name: type, + usage: type, + description: `Sends a random ${type} image` + } + }; +} + +module.exports = [ + makeCommand('cat', 'http://thecatapi.com/api/images/get?format=xml', body => /(.+?)<\/url>/.exec(body)[1]), + makeCommand('dog', 'http://random.dog/woof', body => `http://random.dog/${body}`) +]; diff --git a/cmds_SharpBot/Fun/bill.js b/cmds_SharpBot/Fun/bill.js new file mode 100644 index 0000000..5f53faf --- /dev/null +++ b/cmds_SharpBot/Fun/bill.js @@ -0,0 +1,21 @@ +const got = require('got'); + +exports.run = async (bot, msg) => { + await msg.edit(':arrows_counterclockwise:'); + const { body } = await got('http://belikebill.azurewebsites.net/billgen-API.php?default=1', { encoding: null }); + + await msg.channel.send({ + file: { + attachment: body, + name: 'bill.jpg' + } + }); + + msg.delete(); +}; + +exports.info = { + name: 'bill', + usage: 'bill', + description: 'Be like Bill!' +}; diff --git a/cmds_SharpBot/Fun/binary.js b/cmds_SharpBot/Fun/binary.js new file mode 100644 index 0000000..b777798 --- /dev/null +++ b/cmds_SharpBot/Fun/binary.js @@ -0,0 +1,46 @@ +exports.methods = { + encode: input => { + return input.toString().split('') + .map(c => c.charCodeAt(0).toString(2)); + }, + decode: input => { + let _input = typeof input === 'string' ? input.split(' ') : input; + return _input.map(c => parseInt(c, 2)) + .map(c => String.fromCharCode(c)) + .join(''); + } +}; + +exports.run = (bot, msg, args) => { + if (args.length < 2) { + throw `Do \`${bot.config.prefix}help binary\` to see how to use this.`; + } + + let input = args.slice(1).join(' '); + + if (args[0].match(/^enc(ode(Text)?)?$/i)) { + msg.edit(this.methods.encode(input).join(' ')); + } else if (args[0].match(/^dec(ode(Text)?)?$/i)) { + msg.edit(this.methods.decode(input)); + } else if (args[0].match(/^decToBin$/i)) { + if (isNaN(input)) { + throw 'Input must be a number!'; + } + + msg.edit(parseInt(input).toString(2)); + } else if (args[0].match(/^binToDec$/i)) { + if (isNaN(input)) { + throw 'Input must be a number!'; + } + + msg.edit(parseInt(input, 2)); + } else { + throw `Unknown sub command: \`${args[0]}\``; + } +}; + +exports.info = { + name: 'binary', + usage: 'binary ', + description: 'Convert your input to/from binary' +}; diff --git a/cmds_SharpBot/Fun/clap.js b/cmds_SharpBot/Fun/clap.js new file mode 100644 index 0000000..9cf3e38 --- /dev/null +++ b/cmds_SharpBot/Fun/clap.js @@ -0,0 +1,15 @@ +const randomizeCase = word => word.split('').map(c => Math.random() > 0.5 ? c.toUpperCase() : c.toLowerCase()).join(''); + +exports.run = (bot, msg, args) => { + if (args.length < 1) { + throw 'Please provide some text to clapify'; + } + + msg.edit(args.map(randomizeCase).join(':clap:')); +}; + +exports.info = { + name: 'clap', + usage: 'clap ', + description: 'Clapifies your text' +}; diff --git a/cmds_SharpBot/Fun/destruct.js b/cmds_SharpBot/Fun/destruct.js new file mode 100644 index 0000000..d60906d --- /dev/null +++ b/cmds_SharpBot/Fun/destruct.js @@ -0,0 +1,59 @@ +exports.run = async (bot, msg, args) => { + + const parsedArgs = bot.utils.parseArgs(args, ['d:', 's:']); + + if (parsedArgs.leftover.length < 1) { + throw 'Please provide a secret message'; + } + + let message = parsedArgs.leftover.join(' '); + let delay = isNaN(parsedArgs.options.d) ? 5000 : parseInt(parsedArgs.options.d); + delay = (delay < 100) ? 100 : delay; + const style = (typeof parsedArgs.options.s === 'string') ? parsedArgs.options.s : 'plain'; + + msg.delete(); + + switch (style) { + case 'embed': + message = { + embed: bot.utils.embed( + `This message self-destructs in ${delay / 1000} seconds.`, + message, + [], + { + inline: true, + footer: 'Secret Message' + } + ) + }; + break; + case 'inline': + message = `*This message self-destructs in ${delay / 1000} seconds.*\n${message}`; + break; + case 'code': + message = `*This message self-destructs in ${delay / 1000} seconds.*\n\`\`\`${message}\`\`\``; + break; + } + + (await msg.channel.send(message)).delete(delay); +}; + + +exports.info = { + name: 'destruct', + usage: 'destruct [-d delay in ms] [-s ] ', + description: 'creates a self-destructing message', + options: [ + { + name: '-d', + usage: '-d ', + description: 'Sets the time (in ms) for the message to be deleted. (Default: 5 seconds)' + }, + { + name: '-s', + usage: '-s ', + description: 'Sets the message style (default: plain)' + } + ], + credits: '<@140541588915879936>' // Doxylamin#4539 +}; diff --git a/cmds_SharpBot/Fun/fanceh.js b/cmds_SharpBot/Fun/fanceh.js new file mode 100644 index 0000000..2da6198 --- /dev/null +++ b/cmds_SharpBot/Fun/fanceh.js @@ -0,0 +1,40 @@ +const mapping = { + ' ': ' ', + '0': ':zero:', + '1': ':one:', + '2': ':two:', + '3': ':three:', + '4': ':four:', + '5': ':five:', + '6': ':six:', + '7': ':seven:', + '8': ':eight:', + '9': ':nine:', + '!': ':grey_exclamation:', + '?': ':grey_question:', + '#': ':hash:', + '*': ':asterisk:' +}; + +'abcdefghijklmnopqrstuvwxyz'.split('').forEach(c => { + mapping[c] = mapping[c.toUpperCase()] = ` :regional_indicator_${c}:`; +}); + +exports.run = (bot, msg, args) => { + if (args.length < 1) { + throw 'You must provide some text to fanceh-fy!'; + } + + msg.edit( + args.join(' ') + .split('') + .map(c => mapping[c] || c) + .join('') + ); +}; + +exports.info = { + name: 'fanceh', + usage: 'fanceh ', + description: 'Renders text in big emoji letters' +}; diff --git a/cmds_SharpBot/Fun/figlet.js b/cmds_SharpBot/Fun/figlet.js new file mode 100644 index 0000000..d12b934 --- /dev/null +++ b/cmds_SharpBot/Fun/figlet.js @@ -0,0 +1,52 @@ +const figlet = require('figlet'); + +exports.run = (bot, msg, args) => { + // -l -- List all fonts + // -f -- Set font + const parsed = bot.utils.parseArgs(args, ['l', 'f:']); + + if (parsed.options.l) { + bot.utils.textUpload(figlet.fontsSync().join('\n')).then(({ url }) => { + if (!url) { + return msg.error('Failed to upload fonts list!'); + } + + msg.edit({ + embed: bot.utils.embed('Available Fonts', `A list of available fonts can be found [here](${url}).`) + }).then(m => m.delete(5000)); + }); + return; + } + + if (parsed.leftover.length < 1) { + throw 'You must provide some text to render!'; + } + + const options = {}; + + if (parsed.options.f) { + options.font = parsed.options.f; + } + + msg.delete(); + + const input = parsed.leftover.join(' '); + msg.channel.send(`\`\`\`\n${figlet.textSync(input, options)}\n\`\`\``); +}; + +exports.info = { + name: 'figlet', + usage: 'figlet ', + description: 'Renders fancy ASCII text', + options: [ + { + name: '-f', + usage: '-f ', + description: 'Sets the font to use' + }, + { + name: '-l', + description: 'Lists all available fonts' + } + ] +}; diff --git a/cmds_SharpBot/Fun/fliptext.js b/cmds_SharpBot/Fun/fliptext.js new file mode 100644 index 0000000..a45122f --- /dev/null +++ b/cmds_SharpBot/Fun/fliptext.js @@ -0,0 +1,22 @@ +const mapping = '¡"#$%⅋,)(*+\'-˙/0ƖᄅƐㄣϛ9ㄥ86:;<=>?@∀qƆpƎℲפHIſʞ˥WNOԀQɹS┴∩ΛMX⅄Z[/]^_`ɐqɔpǝɟƃɥᴉɾʞlɯuodbɹsʇnʌʍxʎz{|}~'; +// Start with the character '!' +const OFFSET = '!'.charCodeAt(0); + +exports.run = (bot, msg, args) => { + if (args.length < 1) { + throw 'You must provide text to flip!'; + } + + msg.edit( + args.join(' ').split('') + .map(c => c.charCodeAt(0) - OFFSET) + .map(c => mapping[c] || ' ') + .reverse().join('') + ); +}; + +exports.info = { + name: 'fliptext', + usage: 'fliptext ', + description: 'Flips your text!' +}; diff --git a/cmds_SharpBot/Fun/gif.js b/cmds_SharpBot/Fun/gif.js new file mode 100644 index 0000000..c3f19af --- /dev/null +++ b/cmds_SharpBot/Fun/gif.js @@ -0,0 +1,29 @@ +const got = require('got'); + +// Public API key provided by Giphy for anyone to use. +const API_KEY = 'dc6zaTOxFJmzC'; + +exports.run = async (bot, msg, args) => { + if (args.length < 1) { + throw 'You must provide something to search for!'; + } + + const res = await got(`http://api.giphy.com/v1/gifs/random?api_key=${API_KEY}&tag=${encodeURIComponent(args.join(' '))}`, { json: true }); + + if (!res || !res.body || !res.body.data) { + throw 'Failed to find a GIF that matched your query!'; + } + + msg.delete(); + msg.channel.send({ + embed: bot.utils.embed('', '', [], { image: res.body.data.image_url }) + }); +}; + +// async function findRandom(query) {} + +exports.info = { + name: 'gif', + usage: 'gif ', + description: 'Searches Giphy for GIFs' +}; diff --git a/cmds_SharpBot/Fun/initial.js b/cmds_SharpBot/Fun/initial.js new file mode 100644 index 0000000..60b1e32 --- /dev/null +++ b/cmds_SharpBot/Fun/initial.js @@ -0,0 +1,12 @@ +exports.run = function(bot, msg, args) { + if (args.length === 0) { + throw 'You must input text to be transformed.'; + } + msg.edit(args.map(arg => arg[0].toUpperCase() + arg.slice(1).toLowerCase()).join(' ')); +}; + +exports.info = { + name: 'initial', + usage: 'initial ', + description: 'Transforms the text you input into Initial Caps' +}; diff --git a/cmds_SharpBot/Fun/jumbo.js b/cmds_SharpBot/Fun/jumbo.js new file mode 100644 index 0000000..1059abb --- /dev/null +++ b/cmds_SharpBot/Fun/jumbo.js @@ -0,0 +1,34 @@ +exports.run = function (bot, msg, args) { + if (args.length < 1) { + throw 'Please provide an emoji to enlarge.'; + } + + if (args[0].charCodeAt(0) >= 55296) { + throw 'Cannot enlarge built-in discord emoji.'; + } + + const match = args[0].match(/<:[a-zA-Z0-9_-]+:(\d{18})>/); + + if (!match || !match[1]) { + throw 'Please provide a valid emoji.'; + } + + const emoji = bot.emojis.get(match[1]); + + if (!emoji) { + throw 'That emoji could not be identified!'; + } + + msg.delete(); + msg.channel.send({ + files: [ + emoji.url + ] + }); +}; + +exports.info = { + name: 'jumbo', + usage: 'jumbo ', + description: 'Enlarges emojis!' +}; diff --git a/cmds_SharpBot/Fun/leet.js b/cmds_SharpBot/Fun/leet.js new file mode 100644 index 0000000..0419431 --- /dev/null +++ b/cmds_SharpBot/Fun/leet.js @@ -0,0 +1,107 @@ +/* +Generating the inverse replacements isnt insanely expensive, + but I feel bad repeating an O(n) operation on every call of run + */ +let _inverseReplacementsCached = null; +const getInverseReplacements = replacements => { + if (_inverseReplacementsCached) { + return _inverseReplacementsCached; + } + const inverseReplacements = new Map(); + Object.keys(replacements) + .map(letter => { + replacements[letter].forEach(replacement => { + inverseReplacements.set(new RegExp(global.utils.quoteRegex(replacement), 'gi'), letter); + }); + }); + + _inverseReplacementsCached = inverseReplacements; + + return inverseReplacements; +}; + +exports.run = function (bot, message, args) { + const parsedArgs = bot.utils.parseArgs(args, ['e', 't']); + + if (parsedArgs.leftover.length < 1) { + throw 'Provide text to be leeted.'; + } + + let parsed; + + if (parsedArgs.options.e) { + const extendedLeetReplacements = { + 'a': ['4', '@', '/-\\', 'Д'], + 'b': ['ß'], + 'c': ['¢', '©'], + 'e': ['3', '€'], + 'f': ['ph', 'ƒ'], + 'g': ['6'], + 'i': ['1', '!'], + 'l': ['7'], + 'n': ['И', 'ท'], + 'q': ['Ø'], + 'r': ['®', 'Я'], + 's': ['5', '$', '§'], + 't': ['†'], + 'u': ['|_|', 'µ', 'บ'], + 'v': ['\\/'], + 'w': ['\\/\\/', 'VV', 'Ш', 'พ'], + 'x': ['Ж', '×'], + 'y': ['¥'] + }; + + const inverseReplacements = getInverseReplacements(extendedLeetReplacements); + if (parsedArgs.options.t) { + parsed = parsedArgs.leftover.join(' '); + + for (let [replacement, origValue] of inverseReplacements) { + parsed = parsed.replace(replacement, origValue); + } + } else { + parsed = parsedArgs.leftover + .join(' ') + .replace(/[a-z]/gi, str => { + let selection = bot.utils.randomSelection(extendedLeetReplacements[str.toLowerCase()] || [str]); + selection = bot.utils.quoteRegex(selection); + return selection; + }); + } + } else { + const simpleLeetReplacements = '4BCD3F6H1JKLMN0PQR57'; + if (parsedArgs.options.t) { + parsed = parsedArgs.leftover.join(' ').replace(/[a-z0-9]/g, function (a) { + let foundInReplacements = simpleLeetReplacements.indexOf(a); + if (foundInReplacements === -1) { + return a; + } + return String.fromCharCode(97 + foundInReplacements); + }); + } else { + parsed = parsedArgs.leftover.join(' ').replace(/[a-z]/g, function f(a) { + return simpleLeetReplacements[parseInt(a, 36) - 10] || a.replace(/[a-t]/gi, f); + }).toLowerCase(); + } + } + + message.delete(); + message.channel.send(parsed); +}; + +exports.info = { + name: 'leet', + usage: 'leet ', + description: 'Talk like true gamers', + options: [ + { + name: '-e', + usage: '-e', + description: 'Use extended l33t $p3@k' + }, + { + name: '-t', + usage: '-t', + description: 'Translate from leet speak into English' + } + ] +}; diff --git a/cmds_SharpBot/Fun/meme.js b/cmds_SharpBot/Fun/meme.js new file mode 100644 index 0000000..1d1a9c4 --- /dev/null +++ b/cmds_SharpBot/Fun/meme.js @@ -0,0 +1,120 @@ +const got = require('got'); +let templates = []; + +function getMeme(name) { + return templates.find(m => m.name.toLowerCase() === name.toLowerCase()); +} + +function cleanInput(input) { + if (!input) return ''; + return input.replace(/"/g, '\'\'').replace(/#/g, '~h') + .replace(/-/g, '--').replace(/_/g, '__') + .replace(/ /g, '_').replace(/\?/g, '~q') + .replace(/%/g, '~p').replace(/\//g, '~s'); +} + +exports.init = () => { + got('https://memegen.link/templates/').then(res => { + let data = JSON.parse(res.body); + templates = []; + let promises = []; + for (let key in data) { + promises.push(_loadMeme(data[key])); + } + + Promise.all(promises).then(() => { + templates = templates.filter(e => !!e); + templates.sort((a, b) => a.name.localeCompare(b.name)); + }).catch(console.error); + }).catch(console.error); + + function _loadMeme(url) { + return got(url).then(res => { + let singleData = JSON.parse(res.body); + + templates.push({ + name: url.replace(/https:\/\/memegen\.link\/api\/templates\/(.*)/, '$1'), + url: url.replace('/api/templates', ''), + styles: singleData.styles + }); + }); + } +}; + +exports.run = async (bot, msg, args) => { + if (templates.length < 1) { + throw 'The memes haven\'t loaded yet!'; + } + + if (/^(h(elp)?|\?)$/i.test(args[0])) { + return bot.commands.get('help').run(bot, msg, 'meme'); + } + + if (/^(ls|list|s(earch)?)$/i.test(args[0])) { + msg.delete(); + return (await msg.channel.send({ + embed: bot.utils.embed('Available Memes', '*This message will vanish in 30 seconds*\n\n' + templates.map(meme => `- \`${meme.name}\``).join('\n')) + })).delete(30000); + } + + if (/^(i(nf(o)?)?)$/i.test(args[0])) { + if (args.length < 2) { + throw 'You must provide a meme to get info about!'; + } + + let info = getMeme(args[1]); + if (!info) { + throw `That is not a valid meme! Do \`${bot.config.prefix}${this.info.name} list\` to see available memes.`; + } + + msg.delete(); + return (await msg.channel.send({ + embed: bot.utils.embed(`\`${info.name}\``, `Styles: ${info.styles && info.styles.length > 1 ? info.styles.map(s => `\n- \`${s}\``).join('') : 'None'}`) + })).delete(15000); + } + + let input = args.join(' '); + let parts = input.split('|').map(p => p.trim()); + + if (parts.length < 3) { + throw `No message was provided! Do \`${bot.config.prefix}help ${this.info.name}\` for info on how to use this.`; + } + + let meme = getMeme(args[0]); + if (!meme) { + throw `That is not a valid meme! Do \`${bot.config.prefix}${this.info.name} list\` to see available memes.`; + } + + let topText = cleanInput(parts[1]); + let bottomText = cleanInput(parts[2]); + + if (!topText || !bottomText) { + throw 'Empty message!'; + } + + let url = `${meme.url}/${cleanInput(parts[1])}/${cleanInput(parts[2])}.jpg`; + if (parts[3]) url += `?alt=${encodeURIComponent(parts[3])}`; + + await msg.edit(':arrows_counterclockwise:'); + await msg.channel.send({ + files: [ + { + attachment: url, + name: parts[0] + '.jpg' + } + ] + }); + + msg.delete(); +}; + +exports.info = { + name: 'meme', + usage: 'meme list | info | [ | | | [style]]', + description: 'Helps you generate meme images with custom text', + examples: [ + 'meme info sad-biden', + 'meme facepalm | please, oh please | rtfm', + 'meme sad-biden | sad joe biden | doesn\'t have discord | scowl' + ] +}; diff --git a/cmds_SharpBot/Fun/reverse.js b/cmds_SharpBot/Fun/reverse.js new file mode 100644 index 0000000..c8e490d --- /dev/null +++ b/cmds_SharpBot/Fun/reverse.js @@ -0,0 +1,12 @@ +exports.run = function (bot, msg, args) { + if (args.length < 1) { + throw 'You must input text to be reversed!'; + } + msg.edit(args.join(' ').split('').reverse().join('')); +}; + +exports.info = { + name: 'reverse', + usage: 'reverse ', + description: 'Reverses the text you input' +}; diff --git a/cmds_SharpBot/Fun/roll.js b/cmds_SharpBot/Fun/roll.js new file mode 100644 index 0000000..e929ab6 --- /dev/null +++ b/cmds_SharpBot/Fun/roll.js @@ -0,0 +1,41 @@ +const Roll = require('roll'); +const roller = new Roll(); + +exports.run = function (bot, msg, args) { + if (args.length < 1) { + throw 'You must specify in dice notation (XdY)'; + } + + let reason = ''; + let footer = ''; + + footer += `:game_die: **${args[0]}**`; + if (args.length > 1) { + reason = args.splice(1).join(' '); + footer += ` | ${reason}`; + } + + let results = roller.roll(args[0]); + + msg.delete(); + + let embed = bot.utils.embed( + `Total: ${results.result}`, + `${[].concat.apply([], results.rolled).join(', ').substr(0, 1800)}`, + [ + { + name: '\u200b', + value: footer + } + ] + ); + + msg.channel.send({ embed }); +}; + +exports.info = { + name: 'roll', + usage: 'roll XdY ', + description: 'rolls X dice with Y sides. Supports standard dice notation', + credits: '<@136641861073764352>' // Abyss#0473 +}; diff --git a/cmds_SharpBot/Fun/shoot.js b/cmds_SharpBot/Fun/shoot.js new file mode 100644 index 0000000..0edd9b3 --- /dev/null +++ b/cmds_SharpBot/Fun/shoot.js @@ -0,0 +1,18 @@ +exports.run = function (bot, msg) { + if (msg.mentions.users.size < 1) { + throw '@mention some people to shoot!'; + } + + let output = msg.mentions.users.map(m => `**${m}** :gun:`).join('\n'); + + msg.delete(); + msg.channel.send({ + embed: bot.utils.embed(`${bot.user.username} is on a killing spree!`, output) + }); +}; + +exports.info = { + name: 'shoot', + usage: 'shoot ', + description: 'Shoots yer friendz!' +}; diff --git a/cmds_SharpBot/Fun/sigh.js b/cmds_SharpBot/Fun/sigh.js new file mode 100644 index 0000000..56f963c --- /dev/null +++ b/cmds_SharpBot/Fun/sigh.js @@ -0,0 +1,24 @@ +const ascii = ` +\`\`\` + _______ _________ _________ , , + / | / | | +| | | | | +| | | | | + \\_____, | | _______, |________| + \\ | | | | | + | | | | | | + | | | | | | + ______/ ____|____ \\________| | | +\u200b +\`\`\` +`; + +exports.run = function (bot, msg) { + msg.edit(ascii); +}; + +exports.info = { + name: 'sigh', + usage: 'sigh', + description: 'Dramatic sigh text' +}; diff --git a/cmds_SharpBot/Fun/space.js b/cmds_SharpBot/Fun/space.js new file mode 100644 index 0000000..1c29f02 --- /dev/null +++ b/cmds_SharpBot/Fun/space.js @@ -0,0 +1,22 @@ +exports.run = (bot, msg, args) => { + if (args.length < 1) { + throw 'You must provide text to space out!'; + } + + let amount = 2; + + if (!isNaN(args[0])) { + amount = parseInt(args[0]); + (amount < 1) && (amount = 1); + (amount > 15) && (amount = 15); + args = args.slice(1); + } + + msg.edit(args.join(' '.repeat(amount / 2)).split('').join(' '.repeat(amount))); +}; + +exports.info = { + name: 'space', + usage: 'space [amount] ', + description: 'Spaces out text to look all dramatic n\' stuff' +}; diff --git a/cmds_SharpBot/Fun/tiny.js b/cmds_SharpBot/Fun/tiny.js new file mode 100644 index 0000000..2b0cedf --- /dev/null +++ b/cmds_SharpBot/Fun/tiny.js @@ -0,0 +1,57 @@ +const mappings = (function (object) { + let output = []; + + for (let key in object) { + output.push({ + regex: new RegExp(key, 'ig'), + replacement: object[key] + }); + } + + return output; +})({ + a: '\u1D00', + b: '\u0299', + c: '\u1D04', + d: '\u1D05', + e: '\u1D07', + f: '\uA730', + g: '\u0262', + h: '\u029C', + i: '\u026A', + j: '\u1D0A', + k: '\u1D0B', + l: '\u029F', + m: '\u1D0D', + n: '\u0274', + o: '\u1D0F', + p: '\u1D18', + q: '\u0071', + r: '\u0280', + s: '\uA731', + t: '\u1D1B', + u: '\u1D1C', + v: '\u1D20', + w: '\u1D21', + x: '\u0078', + y: '\u028F', + z: '\u1D22' +}); + +exports.run = (bot, msg, args) => { + if (args.length < 1) { + throw 'You must provide some text to shrink!'; + } + + let output = args.join(' '); + mappings.forEach(replacer => output = output.replace(replacer.regex, replacer.replacement)); + + msg.delete(); + msg.channel.send(output); +}; + +exports.info = { + name: 'tiny', + usage: 'tiny ', + description: 'Converts your text to tiny letters!' +}; diff --git a/cmds_SharpBot/Fun/xkcd.js b/cmds_SharpBot/Fun/xkcd.js new file mode 100644 index 0000000..6d13279 --- /dev/null +++ b/cmds_SharpBot/Fun/xkcd.js @@ -0,0 +1,52 @@ +const got = require('got'); + +exports.run = async (bot, msg, args) => { + let id; + + if (args[0] === 'latest') { + id = (await getLatest()).num; + } else { + id = parseInt(args[0]); + if (isNaN(id)) { + id = await getRandom(); + } + } + + // Avoid the 404 page + while (id === 404) { + id = await getRandom(); + } + + const info = await getInfo(id); + + msg.delete(); + msg.channel.send({ + embed: bot.utils.embed(`[${id}] ${info.title}`, '', [], { + image: info.img, + // Color of the XKCD website. + color: [150, 168, 199], + url: `http://xkcd.com/${id}` + }).setFooter(info.alt) + }); +}; + +async function getInfo(id) { + return (await got(`http://xkcd.com/${id}/info.0.json`, { json: true })).body; +} + +async function getLatest() { + return (await got('http://xkcd.com/info.0.json', { json: true })).body; +} + +async function getRandom() { + const latest = await getLatest(); + const max = latest.num; + + return Math.floor(Math.random() * max); +} + +exports.info = { + name: 'xkcd', + usage: 'xkcd [latest|]', + description: 'Fetches random or specific XKCD comics' +}; diff --git a/cmds_SharpBot/Info/color.js b/cmds_SharpBot/Info/color.js new file mode 100644 index 0000000..fa76aa1 --- /dev/null +++ b/cmds_SharpBot/Info/color.js @@ -0,0 +1,67 @@ +const { RichEmbed } = require('discord.js'); + +const rgbToHSL = (red, green, blue) => { + let r = red / 255; + let g = green / 255; + let b = blue / 255; + + let max = Math.max(r, g, b), min = Math.min(r, g, b); + let h, s, l = (max + min) / 2; + + if (max == min) { + h = s = 0; // achromatic + } else { + let d = max - min; + s = l > 0.5 ? d / (2 - max - min) : d / (max + min); + switch (max) { + case r: h = (g - b) / d + (g < b ? 6 : 0); break; + case g: h = (b - r) / d + 2; break; + case b: h = (r - g) / d + 4; break; + } + h /= 6; + } + + h = Math.round(h * 360); + s = Math.round(s * 100); + l = Math.round(l * 100); + + return { hue: h, saturation: s, lightness: l }; +}; + +const resolveColor = input => { + if (input.startsWith('#')) input = input.substr(1); + if (input.length === 3) input = input.split('').map(c => c + c).join(''); + + let hex = input; + let [red, green, blue] = [hex.substr(0, 2), hex.substr(2, 2), hex.substr(4, 2)] + .map(value => parseInt(value, 16)); + let { hue, saturation, lightness } = rgbToHSL(red, green, blue); + + return { hex, red, green, blue, hue, saturation, lightness }; +}; + +exports.run = (bot, msg, args) => { + if (args.length < 1) { + throw 'Please provide a color!'; + } + + if (!/^#?([a-fA-F0-9]{6}|[a-fA-F0-9]{3})$/.test(args[0])) { + throw 'The color must be in the format of `#RRGGBB` or `#RGB`!'; + } + + let color = resolveColor(args[0]); + + msg.delete(); + msg.channel.send({ + embed: new RichEmbed() + .setDescription(`Hex: \`#${color.hex}\`\nRGB: \`${color.red}, ${color.green}, ${color.blue}\`\nHSL: \`${color.hue}, ${color.saturation}, ${color.lightness}\``) + .setImage(`http://placehold.it/500/${color.hex}/${color.hex}`) + .setColor(`${color.hex}`) + }); +}; + +exports.info = { + name: 'color', + usage: 'color ', + description: 'Shows information and a preview of a hex color' +}; diff --git a/cmds_SharpBot/Info/emoji.js b/cmds_SharpBot/Info/emoji.js new file mode 100644 index 0000000..6f294ed --- /dev/null +++ b/cmds_SharpBot/Info/emoji.js @@ -0,0 +1,53 @@ +exports.run = async (bot, msg, args) => { + if (args.length < 1) { + throw 'Please provide an emoji to gather info on!'; + } + + if (args[0].charCodeAt(0) >= 55296) { + msg.delete(); + + return (await msg.channel.send({ + embed: bot.utils.embed(args[0], 'Built-in **Discord** emoji.') + })).delete(15000); + } + + const match = args[0].match(/<:[a-zA-Z0-9_-]+:(\d{18})>/); + + if (!match || !match[1]) { + throw 'Please provide a valid emoji!'; + } + + const emoji = bot.emojis.get(match[1]); + + if (!emoji) { + throw 'That emoji could not be identified.'; + } + + msg.delete(); + (await msg.channel.send({ + embed: bot.utils.embed('', '', [ + { + name: 'Name', + value: emoji.name + }, + { + name: 'From Guild', + value: emoji.guild.name + }, + { + name: 'ID', + value: emoji.id + }, + { + name: 'Download URL', + value: emoji.url + } + ], { thumbnail: emoji.url }) + })).delete(15000); +}; + +exports.info = { + name: 'emoji', + usage: 'emoji ', + description: 'Shows information about an emoji' +}; diff --git a/cmds_SharpBot/Info/guilds.js b/cmds_SharpBot/Info/guilds.js new file mode 100644 index 0000000..d7c2b1e --- /dev/null +++ b/cmds_SharpBot/Info/guilds.js @@ -0,0 +1,26 @@ +const oneLine = require('common-tags').oneLine; + +exports.run = (bot, msg) => { + + let servers = bot.guilds.array().sort((a, b) => b.memberCount - a.memberCount).map(guild => { + return { + name: guild.name, + value: oneLine` + ${guild.memberCount} users, + ${guild.channels.size} channels + ` + }; + }); + + for (let i = 0; i < servers.length / 20; i += 20) { + msg.edit('', { + embed: bot.utils.embed(`${bot.user.username}'s Servers`, '\u200b', servers.slice(i, i + 20), { inline: true }) + }); + } +}; + +exports.info = { + name: 'guilds', + usage: 'guilds', + description: 'Lists all guilds that you\'re a member of' +}; diff --git a/cmds_SharpBot/Info/help.js b/cmds_SharpBot/Info/help.js new file mode 100644 index 0000000..d83d66d --- /dev/null +++ b/cmds_SharpBot/Info/help.js @@ -0,0 +1,112 @@ +const stripIndents = require('common-tags').stripIndents; + +exports.run = async (bot, msg, args) => { + + let commands = []; + let title = 'Categories'; + + if (args.length > 0) { + if (/^category|type$/i.test(args[0])) { + if (args.length < 2) { + throw 'You must specify a category!'; + } + + commands = bot.commands.all(args[1]); + title = `${args[1]} Commands`; + } else if (/^all|full|every$/i.test(args[0])) { + commands = bot.commands.all(); + title = 'All Commands'; + } else { + let command = bot.commands.get(args[0]); + if (!command) { + throw `The command '${args[0]}' does not exist!`; + } + + commands = [command]; + title = `Help for \`${command.info.name}\``; + } + } + + if (commands.length > 0) { + let fields = commands.filter(c => !c.info.hidden) + .sort((a, b) => a.info.name.localeCompare(b.info.name)) + .map(c => getHelp(bot, c, commands.length === 1)); + + // Temporary workaround for the 2k char limit + let maxLength = 1900; + let messages = []; + + while (fields.length > 0) { + let len = 0; + let i = 0; + while (len < maxLength) { + if (i >= fields.length) { + break; + } + let field = fields[i]; + len += field.name.length + field.value.length; + if (len >= maxLength) { + break; + } + i++; + } + + messages.push({ fields: fields.splice(0, i) }); + } + + msg.delete().catch(() => { }); + messages.map(m => m.fields).forEach(async fields => { + (await msg.channel.send({ + embed: bot.utils.embed(title, '_This message will self-destruct in 90 seconds._ :boom:', fields) + })).delete(90000); + }); + } else { + let categories = bot.commands.categories().sort(); + (await msg.edit({ + embed: bot.utils.embed(title, stripIndents` + **Available categories:** + ${categories.map(c => `- __${c}__`).join('\n')} + + **Usage:** + Do \`${bot.config.prefix}help category \` for a list of commands in a specific category. + Do \`${bot.config.prefix}help all\` for a list of every command available in this bot. + Do \`${bot.config.prefix}help \` for **extended** command help and command options.`) + })).delete(15000); + } +}; + +const getHelp = (bot, command, single) => { + + let description = stripIndents` + **Usage:** \`${bot.config.prefix}${command.info.usage || command.info.name}\` + **Description:** ${command.info.description || ''} + **Category:** __${command.info.category}__`; + + if (command.info.credits) + description += `\n**Credits:** *${command.info.credits}*`; + + if (single && command.info.examples) + description += `\n**Examples:**\n${command.info.examples.map(example => `\`${bot.config.prefix}${example}\``).join('\n')}`; + + if (single && command.info.options instanceof Array) { + let options = command.info.options.map(option => { + return stripIndents` + **${option.name}** + *Usage:* \`${option.usage || option.name}\` + *Description:* ${option.description} + `; + }); + description += `\n**Options:**\n\n${options.join('\n\n')}`; + } + + return { + name: single ? '\u200b' : command.info.name, + value: description + }; +}; + +exports.info = { + name: 'help', + usage: 'help all|[command]|[category ]', + description: 'Shows you help for all commands or just a single command' +}; diff --git a/cmds_SharpBot/Info/mutual.js b/cmds_SharpBot/Info/mutual.js new file mode 100644 index 0000000..0c9a415 --- /dev/null +++ b/cmds_SharpBot/Info/mutual.js @@ -0,0 +1,39 @@ +const limitTo = (array, max, joiner) => array.slice(0, max).join(joiner) + (array.length <= max ? '' : '...'); + +const inGuild = (guild, user) => !!guild.members.find('id', user.id); + +exports.run = async (bot, msg, args) => { + if (args.length < 1) { + throw 'Please provide a name of a Discord server you are in.'; + } + + const query = args.join(' ').toLowerCase(); + // Try to parse by Server Name fist or Server ID + const guild = bot.guilds.find(guild => guild.name.toLowerCase() === query || guild.id === query); + + if (!guild) { + throw 'That guild could not be found!'; + } + + const mutual = bot.users.filter(user => inGuild(msg.guild, user) && inGuild(guild, user)); + + await msg.edit(':arrows_counterclockwise: Searching...'); + + const { url } = await bot.utils.textUpload(mutual.map(user => `- ${user.tag}`).join('\n')); + + msg.delete(); + (await msg.channel.send({ + embed: bot.utils.embed(`Mutual members of ${guild.name}`, limitTo(mutual.array().map(user => user.tag), 25, ', '), [ + { + name: 'Full List', + value: url + } + ]) + })).delete(30000); +}; + +exports.info = { + name: 'mutual', + usage: 'mutual ', + description: 'Finds users who are in a given server that you are in' +}; diff --git a/cmds_SharpBot/Info/playerinfo.js b/cmds_SharpBot/Info/playerinfo.js new file mode 100644 index 0000000..76a8ce0 --- /dev/null +++ b/cmds_SharpBot/Info/playerinfo.js @@ -0,0 +1,51 @@ +const got = require('got'); +const cheerio = require('cheerio'); + +exports.run = async (bot, msg, args) => { + if (args.length < 1) { + throw 'Please provide the username of a player.'; + } + + const username = args[0]; + + const uuid = await getUUID(username); + if (!uuid) { + throw 'That player could not be found.'; + } + + msg.delete(); + return msg.channel.send({ + embed: bot.utils.embed('', '', [ + { + name: 'Username', + value: username + }, + { + name: 'UUID', + value: `\`${uuid}\`` + }, + { + name: 'Skin', + value: `[Download](https://crafatar.com/skins/${uuid}.png)` + } + ], { thumbnail: `https://crafatar.com/avatars/${uuid}.png?size=250&overlay=true` }) + }); +}; + +async function getUUID(username) { + const res = await got(`https://mcuuid.net/?q=${username}`); + const $ = cheerio.load(res.body); + const input = $('input')[1]; + + if (!input) { + return; + } + + return input.attribs['value']; +} + +exports.info = { + name: 'playerinfo', + usage: 'playerinfo ', + description: 'Shows information about a Minecraft player' +}; diff --git a/cmds_SharpBot/Info/serverinfo.js b/cmds_SharpBot/Info/serverinfo.js new file mode 100644 index 0000000..052df23 --- /dev/null +++ b/cmds_SharpBot/Info/serverinfo.js @@ -0,0 +1,80 @@ +const dateFormat = require('dateformat'); + +const now = new Date(); +dateFormat(now, 'dddd, mmmm dS, yyyy, h:MM:ss TT'); + +exports.run = async (bot, msg) => { + if (!msg.guild) { + throw 'This can only be used in a guild!'; + } + + const millis = new Date().getTime() - msg.guild.createdAt.getTime(); + const days = millis / 1000 / 60 / 60 / 24; + + const owner = msg.guild.owner.user || {}; + + const verificationLevels = ['None', 'Low', 'Medium', 'Insane', 'Extreme']; + + let embed = bot.utils.embed( + `${msg.guild.name}`, + '***This message will dissappear in 60 seconds.***', + [ + { + name: 'Created On', + value: `${dateFormat(msg.guild.createdAt)}`, + }, + { + name: 'Days Since Creation', + value: `${days.toFixed(0)}`, + }, + { + name: 'Default Channel', + value: `${msg.guild.defaultChannel}`, + }, + { + name: 'Region', + value: `${msg.guild.region}`, + }, + { + name: 'Member Count', + value: `${msg.guild.members.filter(m => m.presence.status !== 'offline').size} / ${msg.guild.memberCount}`, + }, + { + name: 'Owner', + value: `${owner.username || 'None'}`, + }, + { + name: 'Text Channels', + value: `${msg.guild.channels.filter(m => m.type === 'text').size}`, + }, + { + name: 'Voice Channels', + value: `${msg.guild.channels.filter(m => m.type === 'voice').size}`, + }, + { + name: 'Verification Level', + value: `${verificationLevels[msg.guild.verificationLevel]}`, + }, + { + name: 'Roles', + value: `${msg.guild.roles.size}`, + }, + ], + { + inline: true, + footer: `Guild ID: ${msg.guild.id}` + } + ); + + if (msg.guild.iconURL != null) { + embed.setThumbnail(`${msg.guild.iconURL}`); + } + + (await msg.edit({ embed })).delete(60000); +}; + +exports.info = { + name: 'serverinfo', + usage: 'serverinfo', + description: 'Shows info of the server you are in' +}; diff --git a/cmds_SharpBot/Info/stats.js b/cmds_SharpBot/Info/stats.js new file mode 100644 index 0000000..d153fbd --- /dev/null +++ b/cmds_SharpBot/Info/stats.js @@ -0,0 +1,68 @@ +const formatTime = (time) => { + let seconds = time[0] + time[1] / 1e9; + + let minutes = Math.floor(seconds / 60); + seconds = seconds % 60; + + let hours = Math.floor(minutes / 60); + minutes = minutes % 60; + return `${hours.toFixed(0)}h ${minutes.toFixed(0)}m ${seconds.toFixed(0)}s`; +}; + +const activityTypes = [ + 'playing', + 'streaming', + 'listening to', + 'watching', +]; + +exports.run = async (bot, msg) => { + const game = bot.user.presence.game || {}; + + (await msg.edit({ + embed: bot.utils.embed('SharpBot Stats', '***This message will dissappear in 30 seconds.***', [ + { + name: ':outbox_tray: Messages sent', + value: bot.managers.stats.get('messages-sent') || 0, + }, + { + name: ':inbox_tray: Messages received', + value: bot.managers.stats.get('messages-received') || 0, + }, + { + name: ':mailbox: Mentions', + value: bot.managers.stats.get('mentions') || 0 + }, + { + name: ':baby: Users', + value: `${bot.guilds.reduce((mem, g) => mem += g.memberCount, 0)}`, + }, + { + name: ':desktop: Servers', + value: `${bot.guilds.size.toLocaleString()}`, + }, + { + name: ':keyboard: Channels', + value: `${bot.channels.size.toLocaleString()}`, + }, + { + name: ':thinking: RAM usage', + value: `${(process.memoryUsage().heapUsed / 1024 / 1024).toFixed(2)} MB`, + }, + { + name: ':stopwatch: Uptime', + value: formatTime(process.hrtime(bot.managers.stats.get('start-time'))) + }, + { + name: ':video_game: Game', + value: (game.name) ? `*${activityTypes[game.type]}* ${game.name} ${game.streaming ? `[(Streaming)](${game.url})` : ''}` : 'none' + } + ], { inline: true }) + })).delete(30000); +}; + +exports.info = { + name: 'stats', + usage: 'stats', + description: 'Shows you stats about SharpBot' +}; diff --git a/cmds_SharpBot/Info/undelete.js b/cmds_SharpBot/Info/undelete.js new file mode 100644 index 0000000..0e2b9c9 --- /dev/null +++ b/cmds_SharpBot/Info/undelete.js @@ -0,0 +1,21 @@ +exports.run = function (bot, msg) { + const user = msg.mentions.users.first(); + if (!user) { + throw 'Please mention a user.'; + } + + const delmsg = bot.deleted.get(user.id); + if (!delmsg) { + throw 'No recently deleted messages found'; + } + + bot.deleted.delete(user.id); + + msg.edit(`Undeleted message of ${user.username} in ${delmsg.guild.name} | ${delmsg.channel.name}\n\`\`\`${delmsg.content}\`\`\``); +}; + +exports.info = { + name: 'undelete', + usage: 'undelete ', + description: 'Undeletes messages' +}; diff --git a/cmds_SharpBot/Info/userinfo.js b/cmds_SharpBot/Info/userinfo.js new file mode 100644 index 0000000..66655c7 --- /dev/null +++ b/cmds_SharpBot/Info/userinfo.js @@ -0,0 +1,84 @@ +const dateFormat = require('dateformat'); + +dateFormat('dddd, mmmm dS, yyyy, h:MM:ss TT'); + +exports.run = async (bot, msg) => { + //Makes sure command is sent in a guild + if (!msg.guild) { + throw 'This can only be used in a guild!'; + } + + //Makes sure user mentions a user to get info for + if (msg.mentions.users.size < 1) { + throw '@mention someone to find the info'; + } + + let user = msg.mentions.users.first(); + let member = msg.guild.member(user); + + if (!member) { + throw 'That member could not be found!'; + } + + //How long ago the account was created + const millisCreated = new Date().getTime() - user.createdAt.getTime(); + const daysCreated = millisCreated / 1000 / 60 / 60 / 24; + + //How long about the user joined the server + const millisJoined = new Date().getTime() - member.joinedAt.getTime(); + const daysJoined = millisJoined / 1000 / 60 / 60 / 24; + + // Slice off the first item (the @everyone) + let roles = member.roles.array().slice(1).sort((a, b) => a.comparePositionTo(b)).reverse().map(role => role.name); + if (roles.length < 1) roles = ['None']; + + let embed = bot.utils.embed( + `${user.username}#${msg.mentions.users.first().discriminator}`, + '***This message will dissappear in 60 seconds.***', + [ + { + name: 'Status', + value: `${user.presence.status[0].toUpperCase() + user.presence.status.slice(1)}`, + }, + { + name: 'Game', + value: `${(user.presence.game && user.presence.game && user.presence.game.name) || 'Not playing a game.'}`, + }, + { + name: 'Created On', + value: `${dateFormat(user.createdAt)}`, + }, + { + name: 'Days Since Creation', + value: `${daysCreated.toFixed(0)}`, + }, + { + name: 'Joined On', + value: `${dateFormat(member.joinedAt)}`, + }, + { + name: 'Days Since Joining', + value: `${daysJoined.toFixed(0)}`, + }, + { + name: 'Roles', + value: `${roles.join(', ')}`, + inline: false, + }, + ], + { + inline: true, + footer: `User ID: ${user.id}`, + thumbnail: user.displayAvatarURL + } + ); + + (await msg.edit({ embed })).delete(60000); +}; + +exports.info = { + name: 'userinfo', + usage: 'userinfo ', + description: 'Shows info about a user' +}; + diff --git a/cmds_SharpBot/Info/users.js b/cmds_SharpBot/Info/users.js new file mode 100644 index 0000000..8c4caa6 --- /dev/null +++ b/cmds_SharpBot/Info/users.js @@ -0,0 +1,47 @@ +function hasRole(member, roleName) { + return member.roles.map(role => role.name.toLowerCase()).indexOf(roleName.toLowerCase()) > -1; +} + +exports.run = async (bot, msg, args) => { + if (!msg.guild || !msg.guild.members) { + throw 'You must run this command from within a server.'; + } + + let members = msg.guild.members.array().sort((a, b) => a.user.username.localeCompare(b.user.username)); + + if (args.length > 0) { + members = members.filter(member => hasRole(member, args[0])); + } + + if (members.length < 1) { + throw 'No members could be found.'; + } + + msg.delete(); + + let users = members.map(m => `${m.user}${(m.user.bot ? ' [BOT]' : '')}`); + const body = users.join('\n'); + + if (body.length < 2000) { + (await msg.channel.send({ + embed: bot.utils.embed('', body) + })).delete(60000); + } else { + let raw = members.map(m => `${m.user.username}${m.user.bot ? ' [BOT]' : ''}`).join('\n'); + + const { url } = await bot.utils.textUpload(raw); + + let trimmed = body.substr(0, 1500); + trimmed = trimmed.slice(0, trimmed.lastIndexOf('\n')); + + msg.channel.send({ + embed: bot.utils.embed('', trimmed, [{ name: 'Full list', value: url }]) + }); + } +}; + +exports.info = { + name: 'users', + usage: 'users [role]', + description: 'Lists all users on your current server' +}; diff --git a/cmds_SharpBot/Moderation/message-deletion.js b/cmds_SharpBot/Moderation/message-deletion.js new file mode 100644 index 0000000..0f1e2d0 --- /dev/null +++ b/cmds_SharpBot/Moderation/message-deletion.js @@ -0,0 +1,31 @@ +function makeCommand(name, viewName, description, filter) { + return { + run: async (bot, msg, args) => { + let count = parseInt(args[0]) || 1; + + msg.delete(); + + const messages = await msg.channel.fetchMessages({ limit: Math.min(count, 100), before: msg.id }); + const deletable = messages.filter(message => filter(message, bot)); + + await Promise.all( + deletable.map(m => m.delete()) + ); + + const deleted = deletable.size; + + (await msg.channel.send(`:white_check_mark: ${viewName} \`${deletable.size}\` message${deleted === 1 ? '' : 's'}.`)).delete(2000); + }, + info: { + name, + usage: `${name} [amount]`, + description + } + }; +} + +module.exports = [ + makeCommand('purge', 'Purged', 'Deletes a certain number of messages', () => true), + makeCommand('prune', 'Pruned', 'Deletes a certain number of messages sent by you', (msg, bot) => msg.author.id === bot.user.id), + makeCommand('flush', 'Flushed', 'Deletes a certain number of messages sent by bots', msg => msg.author.bot) +]; diff --git a/cmds_SharpBot/Moderation/quote.js b/cmds_SharpBot/Moderation/quote.js new file mode 100644 index 0000000..50c7290 --- /dev/null +++ b/cmds_SharpBot/Moderation/quote.js @@ -0,0 +1,58 @@ +exports.run = async (bot, msg, args) => { + let channel = msg.channel; + + if (args.length < 1) { + throw 'You must provide a message ID'; + } + + if (!/^\d{18}$/.test(args[0])) { + throw 'You must provide a valid message ID.'; + } + + if (args[1] && /^<#\d{18}>$|^\d{18}$/.test(args[1])) { + channel = bot.channels.get(args[1].replace(/[<#>]/g, '')); + } + + if (!channel) { + throw 'That channel could not be found!'; + } + + const messages = await channel.fetchMessages({ around: args[0], limit: 1 }); + + if (!messages || messages.size < 1) { + throw 'That message could not be found!'; + } + + let message = messages.first(); + + let options = { + timestamp: message.editedTimestamp || message.createdTimestamp, + footer: false + }; + + let attachment = message.attachments.first(); + + if (attachment && (attachment.width || attachment.height)) { + options.image = attachment.url; + } + + let field = ''; + + if ((msg.guild || {}).id !== (channel.guild || {}).id) { + field = `**in ${(channel.guild || { name: 'DMs' }).name} <#${channel.id}>:**`; + } else if (channel.id !== msg.channel.id) { + field = `**in <#${channel.id}>:**`; + } + + msg.delete(); + msg.channel.send({ + embed: bot.utils.embed('', field + '\n\n' + message.toString(), [], options) + .setAuthor(message.author.username, message.author.avatarURL) + }); +}; + +exports.info = { + name: 'quote', + usage: 'quote [#channel | channel ID]', + description: 'Quotes the message with the given ID and channel ID.' +}; diff --git a/cmds_SharpBot/Moderation/search.js b/cmds_SharpBot/Moderation/search.js new file mode 100644 index 0000000..0108cca --- /dev/null +++ b/cmds_SharpBot/Moderation/search.js @@ -0,0 +1,48 @@ +exports.run = async (bot, msg, args) => { + if (args.length < 1) { + throw 'You must specify what to search for!'; + } + + let query = args.join(' '); + + await msg.edit(`:arrows_counterclockwise: Searching the last \`100\` messages for \`${query}\``); + + const messages = await msg.channel.fetchMessages({ limit: 100, before: msg.id }); + + const results = messages.filter(it => it.cleanContent.toLowerCase().indexOf(query.toLowerCase()) != -1) + .sort((a, b) => (a.editedTimestamp || a.createdTimestamp) - (b.editedTimestamp || b.createdTimestamp)); + + if (results.length < 1) { + throw 'No results found.'; + } + + const output = results + .map(message => `${formatDate(message.createdAt)} ${message.author.username}: ${message.content}`) + .join('\n'); + + msg.delete(); + bot.utils.sendLarge( + msg.channel, + output.replace(/`/g, '\u200b`'), + { + prefix: '```log\n', + suffix: '\n```', + delay: 10, + cutOn: '\n' + } + ); +}; + +function formatDate(date) { + return `[${_(date.getDay())}/${_(date.getMonth())}/${_(date.getYear() - 100)}] [${_(date.getHours())}:${_(date.getMinutes())}:${_(date.getSeconds())}]`; +} + +function _(number) { + return number < 10 ? '0' + number : '' + number; +} + +exports.info = { + name: 'search', + usage: 'search <#> ', + description: 'Searches a number of messages for some text' +}; diff --git a/cmds_SharpBot/Settings/prefix.js b/cmds_SharpBot/Settings/prefix.js new file mode 100644 index 0000000..c515c4e --- /dev/null +++ b/cmds_SharpBot/Settings/prefix.js @@ -0,0 +1,17 @@ +exports.run = (bot, msg, args) => { + if (args.length < 1) { + throw 'Please provide a prefix to set!'; + } + + const prefix = args.join(' '); + bot.managers.config.set('prefix', prefix); + // No point trying to delete this message, the bot will be + // rebooting before we have a chance to. + msg.edit('Prefix set, rebooting! :ok_hand:'); +}; + +exports.info = { + name: 'prefix', + usage: 'prefix ', + description: 'Sets the bot prefix' +}; diff --git a/cmds_SharpBot/Settings/setgame.js b/cmds_SharpBot/Settings/setgame.js new file mode 100644 index 0000000..73cc9f9 --- /dev/null +++ b/cmds_SharpBot/Settings/setgame.js @@ -0,0 +1,68 @@ +const normalizeUrl = require('normalize-url'); + +exports.run = async (bot, msg, args) => { + let { leftover, options } = bot.utils.parseArgs(args, ['s:', 'w', 'l']); + + if (leftover.length < 1) { + if (options.s) { + throw 'You must provide a game as well as a stream URL.'; + } + + bot.user.setActivity(null, {}); + return msg.edit('Cleared your game! :ok_hand:').then(m => m.delete(3000)); + } + + let game = leftover.join(' '); + let stream = options.s; + + let fields = []; + + let activityOptions = { type: 'PLAYING' }; + let activityFieldTitle = ':video_game: Game'; + + if (stream) { + stream = normalizeUrl(`twitch.tv/${stream}`); + + activityOptions.url = stream; + activityOptions.type = 'STREAMING'; + + fields.push({ name: ':headphones: Stream URL', value: stream }); + } else if (options.w) { + activityOptions.type = 'WATCHING'; + activityFieldTitle = ':eyes: Watching'; + } else if (options.l) { + activityOptions.type = 'LISTENING'; + activityFieldTitle = ':sound: Listening to'; + } + + fields.unshift({ name: activityFieldTitle, value: game }); + + bot.user.setActivity(game, activityOptions); + + msg.delete(); + + (await msg.channel.send({ + embed: bot.utils.embed(':ok_hand: Game changed!', '', fields) + })).delete(5000); +}; + +exports.info = { + name: 'setgame', + usage: 'setgame ', + description: 'Sets your game (shows for other people)', + options: [ + { + name: '-s', + usage: '-s ', + description: 'Sets your streaming URL to http://twitch.tv/' + }, + { + name: '-w', + description: 'Sets your game prefix to **Watching**' + }, + { + name: '-l', + description: 'Sets your game prefix to **Listening to**' + } + ] +}; diff --git a/cmds_SharpBot/Utility/avatar.js b/cmds_SharpBot/Utility/avatar.js new file mode 100644 index 0000000..3115cb9 --- /dev/null +++ b/cmds_SharpBot/Utility/avatar.js @@ -0,0 +1,21 @@ +exports.run = async (bot, msg) => { + const user = msg.mentions.users.first(); + if (!user) { + throw 'Please mention the user who you want the avatar from.'; + } + + if (!user.avatarURL) { + throw 'That user does not have an avatar.'; + } + + msg.delete(); + (await msg.channel.send({ + embed: bot.utils.embed(`${user.username}'s Avatar`, `[Download](${user.avatarURL})`, [], { image: user.avatarURL }) + })).delete(30000); +}; + +exports.info = { + name: 'avatar', + usage: 'avatar ', + description: 'Gives you the avatar of a user' +}; diff --git a/cmds_SharpBot/Utility/calculator.js b/cmds_SharpBot/Utility/calculator.js new file mode 100644 index 0000000..4c2f3b5 --- /dev/null +++ b/cmds_SharpBot/Utility/calculator.js @@ -0,0 +1,33 @@ +const math = require('math-expression-evaluator'); +const stripIndents = require('common-tags').stripIndents; + +exports.run = (bot, msg, args) => { + if (args.length < 1) { + throw 'You must provide a equation to be solved on the calculator'; + } + + const question = args.join(' '); + + let answer; + try { + answer = math.eval(question); + } catch (err) { + throw `Invalid math equation: ${err}`; + } + + msg.delete(); + msg.channel.send({ + embed: bot.utils.embed('', stripIndents` + **Equation:**\n\`\`\`\n${question}\n\`\`\` + **Answer:**\n\`\`\`\n${answer}\n\`\`\` + `) + }); +}; + +exports.info = { + name: 'calculate', + usage: 'calculate ', + aliases: ['calc', 'math'], + description: 'Calculates any math equation', + credits: 'Carbowix', +}; diff --git a/cmds_SharpBot/Utility/dictionary.js b/cmds_SharpBot/Utility/dictionary.js new file mode 100644 index 0000000..e1930f9 --- /dev/null +++ b/cmds_SharpBot/Utility/dictionary.js @@ -0,0 +1,53 @@ +const webdict = require('webdict'); + +const makeCommand = method => { + return (bot, msg, args) => { + if (args.length < 1) { + throw 'Please provide a word to search!'; + } + + const parsed = bot.utils.parseArgs(args, ['e']); + const word = parsed.leftover.join(' '); + + webdict(method, word).then(res => { + let result; + if (!res || !res.definition || !res.definition[0]) { + result = 'No results found.'; + } else { + result = res.definition[0]; + } + + if (parsed.options.e) { + msg.edit(result); + return; + } + + msg.delete(); + msg.channel.send({ + embed: bot.utils.embed(`:book: ${word}`, result) + }); + }); + }; +}; + +module.exports = [ + { + run: makeCommand('dictionary'), + info: { + name: 'dictionary', + aliases: ['dict'], + usage: 'dictionary ', + description: 'Looks a word up in the dictionary.', + credits: 'NITEHAWK' + } + }, + { + run: makeCommand('urbandictionary'), + info: { + name: 'urban', + usage: 'urban ', + description: 'Looks a word up in the urban dictionary.', + credits: 'NITEHAWK' + } + } +]; diff --git a/cmds_SharpBot/Utility/embed.js b/cmds_SharpBot/Utility/embed.js new file mode 100644 index 0000000..beca30e --- /dev/null +++ b/cmds_SharpBot/Utility/embed.js @@ -0,0 +1,93 @@ +exports.run = (bot, msg, args) => { + if (args.length < 1) { + throw 'You must specify something to embed!'; + } + + let parsed = bot.utils.parseArgs(args, ['f', 'ft:', 'd', 't:', 'c:', 'r', 'i:', 'a:', 'th:']); + + let color = parsed.options.c; + if (parsed.options.r && msg.guild && msg.guild.members) { + let member = msg.guild.members.get(msg.author.id); + if (member) { + color = member.highestRole.hexColor; + } + } + + if (color) { + if (!color.startsWith('#')) { + color = `#${color}`; + } + + if (!/^#[a-fA-F0-9]{6}$/.test(color)) { + throw `Invalid color: \`${color}\`. Please use valid hex format (\`#RRGGBB\`)`; + } + } + + msg.delete(); + msg.channel.send({ + embed: bot.utils.embed(parsed.options.t || '', parsed.leftover.join(' '), [], { + footer: parsed.options.f || parsed.options.ft, + timestamp: parsed.options.d, + color, + image: parsed.options.i, + author: parsed.options.a, + thumbnail: parsed.options.th + }) + }); +}; + +exports.info = { + name: 'embed', + usage: 'embed [text]', + description: 'Sends a message via embeds', + examples: [ + 'embed Hello world!', + 'embed -c #ff0000 Red is my favorite color :heart:', + 'embed -r -t "Welcome to my server!" I hope you enjoy your time here.' + ], + options: [ + { + name: '-f', + description: 'Shows the footer' + }, + { + name: '-ft', + usage: '-ft ', + description: 'Sets the footer text (use quotes for multiple words)' + }, + { + name: '-d', + usage: '-d', + description: 'Enables the timestamp (date) in the footer' + }, + { + name: '-t', + usage: '-t ', + description: 'Sets the embed title (use quotes for multiple words)' + }, + { + name: '-r', + description: 'Uses your role color for the embed color' + }, + { + name: '-c', + usage: '-c ', + description: 'Sets a hex color for the embed in the format of `#RRGGBB`' + }, + { + name: '-i', + usage: '-i ', + description: 'Sets an image for the embed' + }, + { + name: '-a', + usage: '-a ', + description: 'Sets the author of the embed' + }, + { + name: '-th', + usage: '-th ', + description: 'Sets a thumbnail for the embed' + } + ] +}; diff --git a/cmds_SharpBot/Utility/eval.js b/cmds_SharpBot/Utility/eval.js new file mode 100644 index 0000000..36c5078 --- /dev/null +++ b/cmds_SharpBot/Utility/eval.js @@ -0,0 +1,99 @@ +const stripIndents = require('common-tags').stripIndents; + +exports.run = async (bot, msg, args) => { + let parsed = bot.utils.parseArgs(args, ['l:', 'i', 'q']); + let lang = parsed.options.l || ''; + + let code = parsed.leftover.join(' '); + let output; + + try { + output = await eval(code); + } catch (err) { + let message = err; + if (err && err.response && err.response.body && err.response.body.message) { + message = err.response.body.message; + } + + return errorHandler(msg, bot, code, `${message}`); + } + + msg.delete(); + + if (parsed.options.q) { + return; + } + + if (typeof output !== 'string') { + output = require('util').inspect(output); + } + + if (!lang) { + lang = 'javascript'; + } + + output = clean(output).replace(new RegExp(bot.utils.quoteRegex(bot.token), 'g'), 'BOT_TOKEN'); + + const displayedOutput = output.length < 1500 + ? `\n\`\`\`${lang}\n${output}\n\`\`\`` + : `\n${await tryUpload(bot, output)}\n`; + + msg.channel.send({ + embed: bot.utils.embed('', stripIndents` + **Input:**\n\`\`\`js\n${code}\n\`\`\` + **Output:**${displayedOutput} + `) + }); + + if (output.length > 1500 && parsed.options.i) { + bot.utils.sendLarge(msg.channel, output, { + prefix: '```' + lang + '\n', + suffix: '```', + cutOn: ',', + cutAfter: true + }); + } +}; + +const tryUpload = async (bot, content) => { + const { url } = await bot.utils.textUpload(content); + if (!url) { + throw 'Failed to upload!'; + } + return url; +}; + +const errorHandler = (msg, bot, code, err) => { + msg.delete(); + msg.channel.send({ + embed: bot.utils.embed('', `**Input:**\n\`\`\`js\n${code}\n\`\`\`\n:x: **Error!**\n\`\`\`xl\n${clean(err)}\n\`\`\``, [], { + color: '#ff0000' + }) + }).then(m => m.delete(15000)); +}; + +// Prevent @mentions, #channels or code blocks inside code blocks. +const clean = text => text.replace(/([`@#])/g, '$1\u200b'); + +exports.info = { + name: 'eval', + usage: 'eval ', + description: 'Evaluates arbitrary JavaScript', + options: [ + { + name: '-l', + usage: '-l ', + description: 'Sets the output code-block syntax language' + }, + { + name: '-i', + usage: '-i', + description: 'Inline extra-long output in addition to uploading to hastebin' + }, + { + name: '-q', + usage: '-q', + description: 'Does not print any output' + } + ] +}; diff --git a/cmds_SharpBot/Utility/exec.js b/cmds_SharpBot/Utility/exec.js new file mode 100644 index 0000000..85d6237 --- /dev/null +++ b/cmds_SharpBot/Utility/exec.js @@ -0,0 +1,130 @@ +const { exec } = require('child_process'); +const username = require('os').userInfo().username; + +exports.run = async (bot, msg, args) => { + let parsed = bot.utils.parseArgs(args, ['r', 'd', 's', 'f', 'w', 'fn:', 'l:']); + + if (parsed.length < 1) { + throw 'You must provide a command to run!'; + } + + if (parsed.options.d) { + msg.delete(); + } + + let ps = exec(parsed.leftover.join(' ')); + if (!ps) { + throw 'Failed to start process!'; + } + + if (parsed.options.s) { + return; + } + + let opts = { + delay: 10, + cutOn: '\n' + }; + + if (!parsed.options.r) { + opts.prefix = `\`\`\`${parsed.options.l || 'bash'}\n`; + opts.suffix = '\n```'; + } + + if (parsed.options.f) { + let output = ''; + + ps.stdout.on('data', data => output += data.toString()); + await new Promise(resolve => { + ps.once('exit', async () => { + if (!output) { + return resolve(); + } + + try { + await msg.channel.send({ + files: [ + { + attachment: output.replace(/^file:\/\//, ''), + name: parsed.options.fn + } + ] + }); + } catch (err) { + msg.error('Invalid URL/path!'); + } + + resolve(); + }); + }); + } else { + if (parsed.options.w) { + let output = ''; + let handler = data => output += data.toString(); + + [ps.stdout, ps.stderr].forEach(stream => stream.on('data', handler)); + + await new Promise(resolve => { + ps.once('exit', async () => { + if (!output) { + return resolve(); + } + + await bot.utils.sendLarge(msg.channel, clean(output), opts); + + resolve(); + }); + }); + } else { + ps.stdout.on('data', data => bot.utils.sendLarge(msg.channel, clean(data), opts)); + ps.stderr.on('data', data => bot.utils.sendLarge(msg.channel, clean(data), opts)); + + await new Promise(resolve => ps.once('exit', resolve)); + } + } +}; + +const clean = function (data) { + return `${data}` + .replace(/`/g, '\u200b$&') + .replace(new RegExp(username, 'g'), '') + .replace(/\[[0-9]*m/g, ''); +}; + +exports.info = { + name: 'exec', + usage: 'exec ', + description: 'Executes a command in the console', + options: [ + { + name: '-s', + description: 'Runs in silent mode, not showing any console output' + }, + { + name: '-l', + usage: '-l ', + description: 'Sets the language of the outputted code block' + }, + { + name: '-r', + description: 'Sends the output raw, without any code blocks' + }, + { + name: '-d', + description: 'Deletes the command message' + }, + { + name: '-f', + description: 'Interperts the response as a file URL/path to send' + }, + { + name: '-fn', + usage: '-fn ', + description: 'Sets the name for the sent file' + }, + { + name: '-w', + description: 'Wait for the program to finish before sending the output' + } + ] +}; diff --git a/cmds_SharpBot/Utility/github.js b/cmds_SharpBot/Utility/github.js new file mode 100644 index 0000000..a158aea --- /dev/null +++ b/cmds_SharpBot/Utility/github.js @@ -0,0 +1,75 @@ +const got = require('got'); + +exports.run = async (bot, msg, args) => { + if (args.length < 1) { + throw 'You must specify a repository or search term!'; + } + + const input = args.join(' '); + + if (input.indexOf('/') !== -1) { + let repo = safeRepo(input); + + msg.edit(`:arrows_counterclockwise: Loading info for '${repo}'...`); + + const res = await got(`https://api.github.com/repos/${repo}`, { json: true }); + const json = res.body; + if (json.message === 'Not Found') { + msg.error('That repository could not be found!'); + } + + msg.edit({ + embed: bot.utils.embed('', getInfo(json)) + }); + } else { + msg.edit(`:arrows_counterclockwise: Searching for '${input}'...`); + + const res = await got(`https://api.github.com/search/repositories?q=${args.join('+')}`, { json: true }); + const json = res.body; + if (json.total_count < 1) { + throw `No results found for '${args.join(' ')}'`; + } + + msg.delete(); + msg.channel.send(':white_check_mark: Top 3 results:'); + + json.items.slice(0, 3).forEach(item => { + msg.channel.send({ + embed: bot.utils.embed('', getInfo(item)) + }); + }); + } +}; + +function safeRepo(input) { + if (input.indexOf('/') === -1) { + return; + } + + let user = input.substr(0, input.indexOf('/')); + input = input.substr(input.indexOf('/') + 1); + let repo = input.indexOf('/') === -1 ? input : input.substr(0, input.indexOf('/')); + return `${user}/${repo}`; +} + +function getInfo(json) { + return `**${json.full_name}** + +\t**Description:** _${json.description || 'None provided'}_ +\t**Owner:** [${json.owner.login}](${json.owner.html_url}) +\t**Primary Language:** \`${json.language}\` + +\t:house: [Home page](${json.html_url}) :small_red_triangle_down: [Downloads](${json.html_url}/releases) :exclamation: [Issues](${json.html_url}/issues) + +\t:negative_squared_cross_mark: \`${json.open_issues_count}\` open issues :star: \`${json.stargazers_count}\` stargazers :eyes: \`${json.subscribers_count || json.watchers_count}\` watchers + + +\tDo \`git clone ${json.clone_url}\` to clone this repository +`; +} + +exports.info = { + name: 'github', + usage: 'github ', + description: 'Links to a GitHub repository' +}; diff --git a/cmds_SharpBot/Utility/google.js b/cmds_SharpBot/Utility/google.js new file mode 100644 index 0000000..2122a0a --- /dev/null +++ b/cmds_SharpBot/Utility/google.js @@ -0,0 +1,207 @@ +const cheerio = require('cheerio'); +const got = require('got'); +const { stringify } = require('querystring'); +const { RichEmbed } = require('discord.js'); + +exports.run = async (bot, msg, args) => { + if(args.length < 1) throw 'I need to know what to search...'; + + await msg.edit(':arrows_counterclockwise: Googling....'); + + const params = { + q: args.join(' '), + safe: 'on', + lr: 'lang_en', + hl: 'en' + }; + + let resp = await got('https://google.com/search?' + stringify(params), { headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 6.3; Win64; x64) Gecko/20100101 Firefox/53.0' }}); + + if(resp.statusCode !== 200) throw 'Google didn\'t want to respond somehow'; + + const $ = cheerio.load(resp.body); + + const results = []; + + let card = null; + + const cardNode = $('div#rso > div._NId').find('div.vk_c, div.g.mnr-c.g-blk, div.kp-blk'); + + if(cardNode && cardNode.length !== 0) { + card = this.parseCards($, cardNode); + } + + $('.rc > h3 > a').each((i, e) => { + const link = $(e).attr('href'); + const text = $(e).text(); + if(link) { + results.push({ text, link }); + } + }); + + if(card) { + const value = results.slice(0, 3).map(r => `[${r.text}](${r.link})`).join('\n'); + if(value) { + card.addField(`This is what I also found for: "${params.q}" `, value) + .setColor(bot.utils.randomColor()) + .setURL(`https://google.com/search?q=${encodeURIComponent(params.q)}`); + } + return await msg.edit({ embed: card }); + } + + if(results.length === 0) { + return await msg.channel.send('Sorry, we didn\'t found any results'); + } + + const firstentry = `[${results[0].text}](${results[0].link})`; + const nexttwo = results.slice(1, 3).map(r => `[${r.text}](${r.link})`).join('\n'); + + + const embed = bot.utils.embed(`Search results for ${args.join(' ')}`, firstentry, [ + { + name: 'More links', + value: nexttwo + } + ], { url: `https://google.com/search?q=${encodeURIComponent(params.q)}`}); + await msg.edit('', {embed: embed}); + +}; + +exports.parseCards = ($, node) => { + const e = new RichEmbed(); + + + //calculator card + if(node.find('span.cwclet').first().length > 0) { + e.setTitle(':nerd: Calculator'); + const calculation = node.find('span.cwclet').first().text(); + let result = node.find('.cwcot').first().text(); + if(result) + result = `${calculation.trim()} ${result.trim()}`; + else result = `${calculation} ...`; + e.setDescription(result); + return e; + } + + //unit conversion card + if(node.find('input[class*=_eif]').length > 0 && node.find('input[class*=_eif]').first().attr('value').length > 0) { + if(node.find('input[class*=_eif]').length === 2) { + e.setTitle(':scales: Unit conversion'); + + try { + const firstvalue = node.find('input[class*=_eif]').eq(0).attr('value'); + const firstunit = node.find('input[class*=_eif]').eq(0).parent().find('select :selected').text().toLowerCase(); + const secondvalue = node.find('input[class*=_eif]').eq(1).attr('value'); + const secondunit = node.find('input[class*=_eif]').eq(1).parent().find('select :selected').text().toLowerCase(); + e.setDescription(`${firstvalue} ${firstunit} = ${secondvalue} ${secondunit}`); + } catch(e) { + return; + } + + return e; + } + + } + + //currency converter card + if(node.find('.ct-cs').first().length > 0) { + e.setTitle(':moneybag: Currency converter'); + + try { + let firsttext = node.find('.vk_sh.vk_gy.cursrc').text(); + let secondtext = node.find('.vk_ans.vk_bk.curtgt > span').first().text() + node.find('.vk_ans.vk_bk.curtgt > span').eq(1).text(); + + let result = `${firsttext} ${secondtext}`; + e.setDescription(result); + } catch(e) { + return; + } + + return e; + } + + //generic info card + if(node.find('._oDd > ._Tgc').first().length > 0) { + e.setDescription(node.find('._oDd > ._Tgc').first().text()); + + return e; + } + + //translation card + if(node.find('div#tw-ob').first().length > 0) { + const srctext = node.find('pre#tw-source-text > span').first().text(); + let srclang = node.find('select#tw-sl > option:selected').first().text(); + + const desttext = node.find('pre#tw-target-text > span').first().text(); + const destlang = node.find('select#tw-tl > option:selected').first().text(); + + if(srclang.includes('detected')) { + srclang = srclang.replace(' - detected', ''); + } + + e.setTitle(':man_with_turban: Translation card'); + try { + e.addField(srclang, srctext, true); + e.addField(destlang, desttext, true); + } catch(e) { + return; + } + + return e; + } + + //time in card + if(node.find('div.vk_bk.vk_ans').first().length > 0) { + let time = node.find('div.vk_bk.vk_ans').text(); + try { + e.setTitle(`:alarm_clock: ${node.find('span').text()}`) + .setDescription(`${time}`); + } catch(e) { + return; + } + return e; + } + + //defenition cards + const words = node.find('span[data-dobid=hdw]'); + if(words.length > 0) { + node.find('span[data-dobid=hdw]').each((i, word) => { + const root = node.find('span[data-dobid=hdw]').parent().parent(); + + const pronunciation = $(root).find('span.lr_dct_ph > span'); + if(!pronunciation) return true; + + const select = (selector) => $(selector).parent().parent().parent().find('ol.lr_dct_sf_sens') + .find('div.lr_dct_sf_sen.vk_txt') + .find('div._Jig > div[data-dobid=dfn] > span'); + + const lexicalCategory = $(root).find('div.lr_dct_sf_h > i > span'); + let defenitions = select(root); + + $(lexicalCategory).each((i, category) => { + defenitions = select(category); + try { + let descrip = [`${$(category).text()}`]; + $(defenitions).each((i, e) => { + descrip.push(`${i + 1}. ${$(e).text()}`); + }); + + if(i === 0) e.addField(`${$(word).text()} / ${$(pronunciation).text()}`, descrip.join('\n')); + else e.addField(`${$(word).text()} / ${$(pronunciation).text()}`, descrip.join('\n')); + } catch(e) { + return true; + } + }); + }); + + return e; + } + +}; + + +exports.info = { + name: 'google', + usage: 'google ', + description: 'Searches Google using magic' +}; diff --git a/cmds_SharpBot/Utility/image.js b/cmds_SharpBot/Utility/image.js new file mode 100644 index 0000000..b262ad1 --- /dev/null +++ b/cmds_SharpBot/Utility/image.js @@ -0,0 +1,34 @@ +const IMAGE_NAME = /\.(jpe?g|png|gif|webp)$/i; + +exports.run = async (bot, msg, args) => { + if (args.length < 1) { + throw 'Please provide an image URL to send.'; + } + + msg.delete(); + + const url = args[0]; + let name; + + if (!IMAGE_NAME.test(url)) { + name = 'image.png'; + } + + try { + await msg.channel.send({ + file: { + name, + attachment: url + } + }); + } catch (ignore) { + // Using throw inside of a catch doesn't work quite right + return msg.error('Failed to send image.'); + } +}; + +exports.info = { + name: 'image', + usage: 'image ', + description: 'Sends an image from a URL' +}; diff --git a/cmds_SharpBot/Utility/imagedumper.js b/cmds_SharpBot/Utility/imagedumper.js new file mode 100644 index 0000000..98d36e3 --- /dev/null +++ b/cmds_SharpBot/Utility/imagedumper.js @@ -0,0 +1,38 @@ +const got = require('got'); +const fs = require('fs'); +const path = require('path'); +exports.run = (bot, msg, args) => { + let count = parseInt(args[0]) || 100; + let attachments = []; + + msg.channel.fetchMessages({ limit: Math.min(count, 100), before: msg.id }).then(messages => { + messages.map(m => { + m.attachments.map(attachment => { + if (attachment.height) { + attachments.push(attachment.url); + } + }); + }); + + let dir = __dirname + '/../../../out'; + if (!fs.existsSync(dir)) fs.mkdirSync(dir); + + for (let i = 0; i < attachments.length; i++) download(attachments[i]); + + if (attachments.length === 0) throw 'Couldn\'t find any images.'; + msg.channel.send(`:white_check_mark: ${attachments.length} images scraped and saved to the "out" folder in the SharpBot folder.`).then(m => { m.delete(10000); }); + msg.delete(); + }).catch(msg.error); +}; + +exports.info = { + name: 'imagedumper', + usage: 'imagedumper ', + description: 'Grabs all images from the specified amount of messages (max 100)', + credits: '<@149916494183137280>' // Liam Bagge#0550 +}; + +function download(url) { + let file = fs.createWriteStream(`${__dirname}/../../../out/attachment_${path.basename(url)}`); + got.stream(url).pipe(file); +} diff --git a/cmds_SharpBot/Utility/lmgtfy.js b/cmds_SharpBot/Utility/lmgtfy.js new file mode 100644 index 0000000..47d3b4c --- /dev/null +++ b/cmds_SharpBot/Utility/lmgtfy.js @@ -0,0 +1,26 @@ +exports.run = (bot, msg, args) => { + let parsed = bot.utils.parseArgs(args, ['i']); + + if (parsed.leftover.length < 1) { + throw 'You must provide something to search for!'; + } + + const query = encodeURIComponent(parsed.leftover.join(' ')); + // Are there better ways to do this? Sure. But why not abuse + // JavaScript's craziness, and use a truthy/falsy -> 1/0 converter? + const internetExplainer = !!parsed.options.i / 1; + + msg.edit(`**Wow!** :arrow_right: http://www.lmgtfy.com/?iie=${internetExplainer}&q=${query}`); +}; + +exports.info = { + name: 'lmgtfy', + usage: 'lmgtfy [search text]', + description: 'Links to LMGTFY with the given search text', + options: [ + { + name: '-i', + description: 'Enables Internet Explainer' + } + ] +}; diff --git a/cmds_SharpBot/Utility/ping.js b/cmds_SharpBot/Utility/ping.js new file mode 100644 index 0000000..3c5be5d --- /dev/null +++ b/cmds_SharpBot/Utility/ping.js @@ -0,0 +1,35 @@ +exports.run = async (bot, msg, args) => { + let parsed = bot.utils.parseArgs(args, ['o']); + + if (parsed.options.o) { + return msg.edit(':stopwatch: Ping').then(m => { + let time = msg.editedTimestamp - msg.createdTimestamp; + bot.utils.playAnimation(m, 500, [ + ':stopwatch: __P__ing', + ':stopwatch: __Pi__ng', + ':stopwatch: __Pin__g', + ':stopwatch: __Ping__', + `:stopwatch: ***Pong!*** \`${time}ms\`` + ]); + }); + } + + await msg.edit(':thinking: Ping'); + + const delay = msg.editedTimestamp - msg.createdTimestamp; + + (await msg.edit(`:stopwatch: Pong! \`${delay}ms\``)).delete(5000); +}; + +exports.info = { + name: 'ping', + usage: 'ping [-o]', + description: 'Pings the bot', + options: [ + { + name: '-o', + usage: '-o', + description: 'Shows the old ping message (animated)' + } + ] +}; diff --git a/cmds_SharpBot/Utility/react.js b/cmds_SharpBot/Utility/react.js new file mode 100644 index 0000000..203021d --- /dev/null +++ b/cmds_SharpBot/Utility/react.js @@ -0,0 +1,148 @@ +const discordEmoji = require('discord-emoji'); +const emoji = {}; + +Object.values(discordEmoji).forEach(value => { + Object.keys(value).forEach(key => { + emoji[key] = value[key]; + }); +}); + +const mappings = { + 'a': [':regional_indicator_a:', ':a:'], + 'b': [':regional_indicator_b:', ':b:'], + 'c': [':regional_indicator_c:'], + 'd': [':regional_indicator_d:'], + 'e': [':regional_indicator_e:'], + 'f': [':regional_indicator_f:'], + 'g': [':regional_indicator_g:', ':compression:'], + 'h': [':regional_indicator_h:'], + 'i': [':regional_indicator_i:', ':information_source:'], + 'j': [':regional_indicator_j:'], + 'k': [':regional_indicator_k:'], + 'l': [':regional_indicator_l:'], + 'm': [':regional_indicator_m:', ':m:'], + 'n': [':regional_indicator_n:'], + 'o': [':regional_indicator_o:', ':o2:', ':o:'], + 'p': [':regional_indicator_p:', ':parking:'], + 'q': [':regional_indicator_q:'], + 'r': [':regional_indicator_r:'], + 's': [':regional_indicator_s:'], + 't': [':regional_indicator_t:', ':cross:'], + 'u': [':regional_indicator_u:'], + 'v': [':regional_indicator_v:'], + 'w': [':regional_indicator_w:'], + 'x': [':regional_indicator_x:', ':heavy_multiplication_x:', ':x:', ':negative_squared_cross_mark:'], + 'y': [':regional_indicator_y:'], + 'z': [':regional_indicator_z:'], + '0': [':zero:'], + '1': [':one:'], + '2': [':two:'], + '3': [':three:'], + '4': [':four:'], + '5': [':five:'], + '6': [':six:'], + '7': [':seven:'], + '8': [':eight:'], + '9': [':nine:'], + '!': [':exclamation:', ':grey_exclamation:'], + '?': [':question:', ':grey_question:'], + '*': [':asterisk:', ':eight_spoked_asterisk:'], + '#': [':hash:'], + '$': [':heavy_dollar_sign:'] +}; + +function clone(object) { + const newObject = {}; + + Object.keys(object).forEach(key => { + if (object[key] instanceof Array) { + newObject[key] = new Array(...object[key]); + } else { + newObject[key] = object[key]; + } + }); + + return newObject; +} + +function emojiToUnicode(input) { + if (/^:regional_indicator_[a-z]:$/.test(input)) { + return String.fromCharCode(55356) + String.fromCharCode(56806 + input.substr(20, 1).charCodeAt(0) - 97); + } + return emoji[input.slice(1, -1)]; +} + +function react(message, remaining, allowedMappings) { + if (remaining.length < 1) { + // We're out of stuff + return; + } + + const char = remaining.shift().toLowerCase(); + + if (!char) { + return; + } + + if (!allowedMappings[char]) { + // Not a usable char + return; + } + + const next = allowedMappings[char].shift(); + if (!next) { + // We have no more mappings available + return; + } + + message.react(emojiToUnicode(next)).then(() => { + react(message, remaining, allowedMappings); + }); +} + +exports.run = (bot, msg, args) => { + if (args.length < 1) { + throw 'You must provide some text to react with.'; + } + + const fetchOptions = { limit: 1 }; + if (args[1]) { + if (!/\d{18}/.test(args[1])) { + throw `${args[1]} is not a valid message ID!`; + } + + fetchOptions.around = args[1]; + } else { + fetchOptions.before = msg.id; + } + + msg.channel.fetchMessages(fetchOptions).then(messages => { + if (messages.length < 1) { + return msg.error('Failed to find the message.'); + } + + const target = messages.first(); + const allowedMappings = clone(mappings); + + // Remove current reactions from allowed emojis + target.reactions.forEach(reaction => { + const emoji = reaction.toString(); + for (const key in allowedMappings) { + const index = allowedMappings[key].indexOf(emoji); + if (index > -1) { + allowedMappings[key].splice(index, 1); + } + } + }); + + msg.delete(); + + react(target, args[0].split(''), allowedMappings); + }).catch(msg.error); +}; + +exports.info = { + name: 'react', + usage: 'react [message ID]', + description: 'Reacts to the last sent message (or another message) with a given text (cannot contain spaces)' +}; diff --git a/cmds_SharpBot/Utility/restart.js b/cmds_SharpBot/Utility/restart.js new file mode 100644 index 0000000..c391e26 --- /dev/null +++ b/cmds_SharpBot/Utility/restart.js @@ -0,0 +1,10 @@ +exports.run = async (bot, msg) => { + await msg.edit(':wave: Restarting. Bye!'); + bot.shutdown(true); +}; + +exports.info = { + name: 'restart', + usage: 'restart', + description: 'Restarts the bot' +}; diff --git a/cmds_SharpBot/Utility/say.js b/cmds_SharpBot/Utility/say.js new file mode 100644 index 0000000..864b6ba --- /dev/null +++ b/cmds_SharpBot/Utility/say.js @@ -0,0 +1,37 @@ +exports.run = (bot, msg, args) => { + const parsed = bot.utils.parseArgs(args, ['c:']); + + if (parsed.leftover.length < 1) { + throw 'You must put something to say!'; + } + + let channel = msg.channel; + if (parsed.options.c) { + const id = parsed.options.c.match(/\d{18}/)[0]; + + if (!id) { + throw 'Invalid channel!'; + } + + channel = bot.channels.get(id); + if (!channel) { + throw 'That channel could not be found!'; + } + } + + msg.delete(); + channel.send(parsed.leftover.join(' ')); +}; + +exports.info = { + name: 'say', + usage: 'say ', + description: 'Says the message you put. Useful for shortcuts.', + options: [ + { + name: '-c', + usage: '-c ', + description: 'Specifies a specific channel to send the message in' + } + ] +}; diff --git a/cmds_SharpBot/Utility/shortcuts.js b/cmds_SharpBot/Utility/shortcuts.js new file mode 100644 index 0000000..cd4e9e5 --- /dev/null +++ b/cmds_SharpBot/Utility/shortcuts.js @@ -0,0 +1,94 @@ +exports.init = bot => { + this.storage = bot.storage('shortcuts'); +}; + +const ADD = /^a(dd)?|c(reate)?$/i; +const EDIT = /^e(dit)?$/i; +const DELETE = /^d(el(ete)?)?|r(em(ove)?)?$/i; +const INFO = /^i(nfo)?$/i; + +exports.run = (bot, msg, args) => { + if (args.length < 1) { + const shortcuts = this.storage.values; + + if (shortcuts.length < 1) { + throw 'You have no shortcuts!'; + } + + const list = shortcuts.map(shortcut => `- \`${shortcut.name}\``).join('\n'); + + msg.delete(); + return bot.utils.sendLarge(msg.channel, `**Shortcuts:**\n${list}`, { cutOn: '\n' }); + } + + if (ADD.test(args[0]) || EDIT.test(args[0])) { + if (args.length < 3) { + throw `Usage: \`${bot.config.prefix}shortcut add|edit \``; + } + + let id = args[1].toLowerCase(); + let command = args.slice(2).join(' '); + + // People keep accidentally putting their command prefix in + if (command.startsWith(bot.config.prefix)) { + command = command.substr(bot.config.prefix.length); + } + + const shortcut = this.storage.get(id); + if (shortcut && !EDIT.test(args[0])) { + throw `The shortcut \`${id}\` already exists!`; + } + + this.storage.set(id, { name: id, command }); + this.storage.save(); + + msg.edit(`:white_check_mark: Created shortcut \`${id}\`.`) + .then(m => m.delete(5000)); + } else if (DELETE.test(args[0])) { + if (args.length < 2) { + throw `Usage: \`${bot.config.prefix}shortcut delete \``; + } + + let id = args[1]; + + const shortcut = this.storage.get(id); + if (!shortcut) { + throw `The shortcut \`${id}\` doesn't exist!`; + } + + this.storage.set(id); + this.storage.save(); + + msg.edit(`:white_check_mark: Removed the shortcut \`${id}\`.`) + .then(m => m.delete(5000)); + } else if (INFO.test(args[0])) { + if (args.length < 2) { + throw `Usage: \`${bot.config.prefix}shortcut info \``; + } + + const id = args[1]; + const shortcut = this.storage.get(id); + + if (!shortcut) { + throw `The shortcut \`${id}\` doesn't exist!`; + } + + msg.edit(`**Name:** \`${shortcut.name}\`\n\`\`\`xl\n${shortcut.command}\n\`\`\``) + .then(m => m.delete(30000)); + } else { + bot.commands.get('help').run(bot, msg, ['shortcuts']); + } +}; + +exports.info = { + name: 'shortcuts', + usage: 'shortcuts [add |edit |delete |info ]', + description: 'Controls or lists your shortcuts', + examples: [ + 'shortcuts add love embed -c #ff0000 <3', + 'shortcuts edit drpg say #!mine;; say #!forage;; say #!chop;; say #!fish', + 'shortcuts delete invite', + 'shortcuts info love', + 'shortcuts' + ] +}; diff --git a/cmds_SharpBot/Utility/shorturl.js b/cmds_SharpBot/Utility/shorturl.js new file mode 100644 index 0000000..d8cdbb3 --- /dev/null +++ b/cmds_SharpBot/Utility/shorturl.js @@ -0,0 +1,34 @@ +const got = require('got'); + +exports.run = async (bot, msg, args) => { + if (args.length < 1) { + throw 'Please provide a url to shorten!'; + } + + const url = args.join(' '); + + msg.delete(); + + const res = await got(`http://tinyurl.com/api-create.php?url=${encodeURIComponent(url)}`); + msg.channel.send({ + embed: bot.utils.embed('', '', [ + { + name: 'Link', + value: url + }, + { + name: 'Short URL', + value: res.body + } + ]) + }); + +}; + +exports.info = { + name: 'shorturl', + aliases: ['short'], + usage: 'shorturl ', + description: 'Shortens a URL', + credits: 'xd <@335490180028956673>' +}; diff --git a/cmds_SharpBot/Utility/shutdown.js b/cmds_SharpBot/Utility/shutdown.js new file mode 100644 index 0000000..0602a52 --- /dev/null +++ b/cmds_SharpBot/Utility/shutdown.js @@ -0,0 +1,10 @@ +exports.run = async (bot, msg) => { + await msg.edit(':wave: Shutting down. Bye!'); + bot.shutdown(false); +}; + +exports.info = { + name: 'shutdown', + usage: 'shutdown', + description: 'Fully shuts the bot down' +}; diff --git a/cmds_SharpBot/Utility/spotify.js b/cmds_SharpBot/Utility/spotify.js new file mode 100644 index 0000000..0a44da3 --- /dev/null +++ b/cmds_SharpBot/Utility/spotify.js @@ -0,0 +1,135 @@ +const RichEmbed = require('discord.js').RichEmbed; +const SpotifyWebApi = require('spotify-web-api-node'); +const SpotifyUriParser = require('spotify-uri'); +const got = require('got'); +const spotifyApi = new SpotifyWebApi(); + +exports.run = async (bot, msg, args) => { + if (args.length < 1) { + throw 'You must specify a Spotify URI at least!'; + } + + const response = await got('https://spotify-auth.doxylamin.pw/', { json: true }); + + if (response.body.success == true) { + spotifyApi.setAccessToken(response.body.access_token); + } + + const parsed = bot.utils.parseArgs(args, ['preview']); + const spotifyUri = SpotifyUriParser.parse(parsed.leftover.join(' ')); + + switch (spotifyUri.type) { + case 'track': + getTrackEmbed(msg, spotifyUri.id, parsed.options.preview); + break; + case 'artist': + getArtistEmbed(msg, spotifyUri.id, parsed.options.preview); + break; + case 'playlist': + getPlaylistEmbed(msg, spotifyUri.user, spotifyUri.id, parsed.options.preview); + break; + default: + throw 'Sorry, I can\'t parse that type of URI yet.'; + } + +}; + +function getPlaylistEmbed(msg, user, spotifyId, withPreview) { + spotifyApi.getPlaylist(user, spotifyId) + .then(data => { + const apiResponse = data.body; + + const embed = new RichEmbed() + .setColor([30, 215, 96]) + .setThumbnail(apiResponse.images[0].url) + .setAuthor('Click to listen on Spotify', 'https://image.flaticon.com/icons/png/512/174/174872.png', apiResponse.external_urls.spotify) + .addField('Playlist Name', apiResponse.name, true) + .addField('Created by', apiResponse.owner.display_name, true) + .addField('Description', apiResponse.description, true) + .addField('Public Playlist', ((apiResponse.public) ? 'Yes' : 'No'), true) + .addField('Collaborative', ((apiResponse.collaborative) ? 'Yes' : 'No'), true) + .addField('Followers', apiResponse.followers.total, true) + .addField('Track Count', apiResponse.tracks.total, true); + + msg.edit({ embed }); + + if (withPreview) msg.channel.send(`Got spotify? Click below to play! ${apiResponse.external_urls.spotify}`); + + }).catch(err => { + throw `Something went wrong! ${err}`; + }); +} + +function getArtistEmbed(msg, spotifyId, withPreview) { + spotifyApi.getArtist(spotifyId) + .then(data => { + const apiResponse = data.body; + + const embed = new RichEmbed() + .setColor([30, 215, 96]) + .setThumbnail(apiResponse.images[0].url) + .setAuthor('Click to listen on Spotify', 'https://image.flaticon.com/icons/png/512/174/174872.png', apiResponse.external_urls.spotify) + .addField('Artist Name', apiResponse.name, true) + .addField('Followers', apiResponse.followers.total.toLocaleString(), true) + .addField('Popularity', apiResponse.popularity + '%', true) + .addField('Genres', ((apiResponse.genres.length > 0) ? apiResponse.genres.join(', ') : 'unknown'), true); + + msg.edit({ embed }); + + if (withPreview) msg.channel.send(`Got spotify? Click below to play! ${apiResponse.external_urls.spotify}`); + + }).catch(err => { + throw `Something went wrong! ${err}`; + }); +} + +function getTrackEmbed(msg, spotifyId, withPreview) { + spotifyApi.getTrack(spotifyId) + .then(data => { + const apiResponse = data.body; + let artists = apiResponse.artists.map(artist => artist.name); + + const embed = new RichEmbed() + .setColor([30, 215, 96]) + .setThumbnail(apiResponse.album.images[0].url) + .setAuthor('Click to listen on Spotify', 'https://image.flaticon.com/icons/png/512/174/174872.png', apiResponse.external_urls.spotify) + .addField('Artist', artists.join(', '), true) + .addField('Title', apiResponse.name, true) + .addField('Length', calculateDuration(apiResponse.duration_ms), true) + .addField('Album', apiResponse.album.name, true) + .addField('Parental Advisory', ((apiResponse.explicit) ? 'contains explicit lyrics' : 'none applicable'), true) + .addField('Availability', `Available in ${apiResponse.available_markets.length} countrys`, true) + .addField('Popularity', `${apiResponse.popularity}%`, true); + + msg.edit({ embed }); + + if (withPreview) msg.channel.send(`Got spotify? Click below to play! ${apiResponse.external_urls.spotify}`); + + }).catch(err => { + throw `Something went wrong! ${err}`; + }); +} + +function calculateDuration(ms) { + const minutes = Math.floor(ms / 60000); + const seconds = ((ms % 60000) / 1000).toFixed(0); + return (seconds == 60 ? (minutes + 1) + ':00' : minutes + ':' + (seconds < 10 ? '0' : '') + seconds); +} + +exports.info = { + name: 'spotify', + usage: 'spotify ', + description: 'Parses a spotify-uri and outputs its information.', + examples: [ + 'spotify spotify:track:5DkCAVqn09WAPOPiphKOUD', + 'spotify -preview spotify:track:5DkCAVqn09WAPOPiphKOUD', + 'spotify https://open.spotify.com/track/5DkCAVqn09WAPOPiphKOUD' + ], + options: [ + { + name: '-preview', + description: 'Sends another message where discord places it\'s own embedded player' + } + ], + credits: '<@140541588915879936>' // Doxylamin#3377 +}; diff --git a/cmds_SharpBot/Utility/status.js b/cmds_SharpBot/Utility/status.js new file mode 100644 index 0000000..dda8914 --- /dev/null +++ b/cmds_SharpBot/Utility/status.js @@ -0,0 +1,43 @@ +const validStatuses = [ + { + internal: 'online', + display: 'online', + emoji: ':zap:' + }, + { + internal: 'idle', + display: 'idle', + emoji: ':beach_umbrella:' + }, + { + internal: 'dnd', + display: 'do-not-disturb', + emoji: ':mute:' + }, + { + internal: 'invisible', + display: 'invisible', + emoji: ':ghost:' + } +]; + +const validStatusRegex = new RegExp(`^(${validStatuses.map(status => status.internal).join('|')})$`); +const validStatusString = validStatuses.map(status => `\`${status.internal}\``).join(', '); + +exports.run = async (bot, msg, args) => { + if (args.length < 1 || !validStatusRegex.test(args[0])) { + throw `Please provide a status to set: ${validStatusString}`; + } + + const status = validStatuses.find(status => status.internal === args[0].toLowerCase()); + + bot.user.setStatus(status.internal); + + (await msg.edit(`${status.emoji} Set status to ${status.display}.`)).delete(5000); +}; + +exports.info = { + name: 'status', + usage: `status <${validStatuses.map(status => status.internal).join('|')}>`, + description: 'Sets your status' +}; diff --git a/cmds_SharpBot/Utility/tags.js b/cmds_SharpBot/Utility/tags.js new file mode 100644 index 0000000..fb668fe --- /dev/null +++ b/cmds_SharpBot/Utility/tags.js @@ -0,0 +1,70 @@ +exports.init = bot => { + this.storage = bot.storage('tags'); +}; + +exports.run = async (bot, message, args) => { + if (args.length < 1) { + throw `Do \`${bot.config.prefix}help tag\` for info on how to use this command.`; + } + + const sub = args[0].toLowerCase(); + args = args.slice(1); + + if (sub === 'list') { + const tags = this.storage.values + .sort((a, b) => b.used - a.used); + + message.edit(`${tags.map(tag => `**${tag.name}** (${tag.used})`).join('\n')}`, { split: true }); + } else if (sub === 'add') { + if (args.length < 2) { + throw 'You must provide a tag name and the tag contents!'; + } + + let name = args[0]; + let contents = args.slice(1).join(' '); + + const tag = this.storage.get(name); + if (tag) { + throw 'That tag already exists!'; + } + + this.storage.set(name, { name, contents, used: 0 }); + this.storage.save(); + + (await message.edit(`:white_check_mark: The tag \`${name}\` was added.`)).delete(5000); + } else if (sub === 'delete') { + if (args.length < 1) { + throw 'You must provide a tag name to delete!'; + } + + const name = args[0]; + + if (!this.storage.get(name)) { + throw 'That tag does not exist!'; + } + + this.storage.set(name); + this.storage.save(); + + (await message.edit(`:white_check_mark: The tag \`${name}\` was deleted.`)).delete(5000); + } else { + const tag = this.storage.get(sub); + + if (!tag) { + throw 'That tag does not exist!'; + } + + message.edit(args.join(' ') + tag.contents); + + tag.used++; + + this.storage.set(sub, tag); + this.storage.save(); + } +}; + +exports.info = { + name: 'tag', + usage: 'tag |list|add |delete ', + description: 'Manages your tags' +}; diff --git a/cmds_SharpBot/Utility/text-upload.js b/cmds_SharpBot/Utility/text-upload.js new file mode 100644 index 0000000..dd59f15 --- /dev/null +++ b/cmds_SharpBot/Utility/text-upload.js @@ -0,0 +1,40 @@ +function makeCommand(name, displayName, methodName) { + return { + run: async (bot, msg, args) => { + const parsed = bot.utils.parseArgs(args, 'r'); + + if (parsed.leftover.length < 1) { + throw 'You must have something to upload!'; + } + + await msg.edit(':arrows_counterclockwise: Uploading...'); + const { url, rawUrl } = await bot.utils[methodName](parsed.leftover.join(' ')); + + if (!url) { + throw 'Failed to upload, no key was returned!'; + } + + if (parsed.options.r) { + msg.edit(`:white_check_mark: ${rawUrl}`); + } else { + msg.edit(`:white_check_mark: ${url}`); + } + }, + info: { + name, + usage: `${name} `, + description: `Uploads some text to ${displayName}`, + options: [ + { + name: '-r', + description: 'Returns the URL for a raw version of your upload' + } + ] + } + }; +} + +module.exports = [ + makeCommand('haste', 'Hastebin', 'hastebinUpload'), + makeCommand('ix', 'ix.io', 'ixUpload') +]; diff --git a/cmds_SharpBot/Utility/timezone.js b/cmds_SharpBot/Utility/timezone.js new file mode 100644 index 0000000..d4ea759 --- /dev/null +++ b/cmds_SharpBot/Utility/timezone.js @@ -0,0 +1,54 @@ +const got = require('got'); + +exports.run = async (bot, msg, args) => { + if (args.length < 1) { + throw 'You must specify a time to convert'; + } + + let input = args.join(' '); + let url = `https://api.duckduckgo.com/?q=${encodeURIComponent(input)}&format=json`; + + await msg.edit(':arrows_counterclockwise: Loading conversion...'); + + const res = await got(url, { json: true }); + + if (!res || !res.body) { + throw 'Could not load data from DDG.'; + } + + let data = res.body; + + let answer = data['Answer']; + let message; + + if (data['AnswerType'] === 'timezone_converter') { + msg.delete(); + + let matches = input.match(/(.*?)\s*(to|in)\s*(.*)/); + let prefix; + + if (matches) { + prefix = matches[1]; + } else { + prefix = input; + } + + message = bot.utils.embed('', '', [ + { + name: 'Timezone:', + value: `${prefix} \u2794 ${answer}` + } + ]); + + msg.channel.send({ embed: message }); + } else { + throw `No conversion found for ${input}`; + } +}; + +exports.info = { + name: 'timezone', + usage: 'timezone