├── index.js ├── res ├── sharpbot.gif ├── sharpbot-install.gif └── sharpbot-install-old.gif ├── .gitignore ├── .editorconfig ├── src ├── commands │ ├── _base.js │ ├── Utility │ │ ├── restart.js │ │ ├── shutdown.js │ │ ├── avatar.js │ │ ├── image.js │ │ ├── shorturl.js │ │ ├── lmgtfy.js │ │ ├── calculator.js │ │ ├── say.js │ │ ├── ping.js │ │ ├── status.js │ │ ├── text-upload.js │ │ ├── imagedumper.js │ │ ├── timezone.js │ │ ├── dictionary.js │ │ ├── translate.js │ │ ├── tags.js │ │ ├── github.js │ │ ├── weather.js │ │ ├── embed.js │ │ ├── eval.js │ │ ├── shortcuts.js │ │ ├── exec.js │ │ ├── react.js │ │ ├── spotify.js │ │ ├── google.js │ │ └── todo.js │ ├── Fun │ │ ├── reverse.js │ │ ├── initial.js │ │ ├── clap.js │ │ ├── shoot.js │ │ ├── bill.js │ │ ├── space.js │ │ ├── fliptext.js │ │ ├── anim.js │ │ ├── sigh.js │ │ ├── jumbo.js │ │ ├── gif.js │ │ ├── animals.js │ │ ├── fanceh.js │ │ ├── roll.js │ │ ├── 8ball.js │ │ ├── tiny.js │ │ ├── xkcd.js │ │ ├── figlet.js │ │ ├── binary.js │ │ ├── destruct.js │ │ ├── leet.js │ │ └── meme.js │ ├── Settings │ │ ├── prefix.js │ │ └── setgame.js │ ├── Info │ │ ├── undelete.js │ │ ├── guilds.js │ │ ├── mutual.js │ │ ├── playerinfo.js │ │ ├── emoji.js │ │ ├── users.js │ │ ├── color.js │ │ ├── stats.js │ │ ├── serverinfo.js │ │ ├── userinfo.js │ │ └── help.js │ └── Moderation │ │ ├── message-deletion.js │ │ ├── search.js │ │ └── quote.js ├── managers │ ├── stats.js │ ├── index.js │ ├── migrator.js │ ├── notifications.js │ ├── plugins.js │ ├── logger.js │ ├── dynamic-imports.js │ ├── storage.js │ ├── config.js │ └── commands.js ├── scripts │ ├── configure.js │ └── debug.js ├── plugins │ ├── desktop-notifier.js │ └── assistant-bot.js ├── bot.js └── utils.js ├── .codeclimate.yml ├── docs ├── FAQ.md ├── storage-system.md └── COMMANDS.md ├── .eslintrc.json ├── LICENSE ├── package.json ├── README.md └── bin └── sharpbot /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./src/bot'); 2 | -------------------------------------------------------------------------------- /res/sharpbot.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rayzrdev/SharpBot/HEAD/res/sharpbot.gif -------------------------------------------------------------------------------- /res/sharpbot-install.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rayzrdev/SharpBot/HEAD/res/sharpbot-install.gif -------------------------------------------------------------------------------- /res/sharpbot-install-old.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rayzrdev/SharpBot/HEAD/res/sharpbot-install-old.gif -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.log 3 | node_modules/ 4 | 5 | # Data files/folders 6 | data/ 7 | *.sqlite 8 | src/config.json 9 | /config.json 10 | out/ 11 | .idea 12 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | indent_style = space 3 | ident_size = 4 4 | charset = utf-8 5 | trim_trailing_whitespace = true 6 | insert_final_newline = true 7 | 8 | [*.md] 9 | trim_trailing_whitespace = false 10 | -------------------------------------------------------------------------------- /src/commands/_base.js: -------------------------------------------------------------------------------- 1 | exports.run = function (bot, msg, args) { 2 | // insert code here 3 | msg.channel.send(`You typed: ${args.join(' ')}`); 4 | }; 5 | 6 | exports.info = { 7 | name: '', 8 | usage: '', 9 | description: '' 10 | }; 11 | -------------------------------------------------------------------------------- /src/commands/Utility/restart.js: -------------------------------------------------------------------------------- 1 | exports.run = async (bot, msg) => { 2 | await msg.edit(':wave: Restarting. Bye!'); 3 | bot.shutdown(true); 4 | }; 5 | 6 | exports.info = { 7 | name: 'restart', 8 | usage: 'restart', 9 | description: 'Restarts the bot' 10 | }; 11 | -------------------------------------------------------------------------------- /src/commands/Utility/shutdown.js: -------------------------------------------------------------------------------- 1 | exports.run = async (bot, msg) => { 2 | await msg.edit(':wave: Shutting down. Bye!'); 3 | bot.shutdown(false); 4 | }; 5 | 6 | exports.info = { 7 | name: 'shutdown', 8 | usage: 'shutdown', 9 | description: 'Fully shuts the bot down' 10 | }; 11 | -------------------------------------------------------------------------------- /src/commands/Fun/reverse.js: -------------------------------------------------------------------------------- 1 | exports.run = function (bot, msg, args) { 2 | if (args.length < 1) { 3 | throw 'You must input text to be reversed!'; 4 | } 5 | msg.edit(args.join(' ').split('').reverse().join('')); 6 | }; 7 | 8 | exports.info = { 9 | name: 'reverse', 10 | usage: 'reverse ', 11 | description: 'Reverses the text you input' 12 | }; 13 | -------------------------------------------------------------------------------- /src/managers/stats.js: -------------------------------------------------------------------------------- 1 | class Stats { 2 | constructor() { 3 | this._stats = {}; 4 | } 5 | 6 | get(key) { 7 | return this._stats[key]; 8 | } 9 | 10 | set(key, value) { 11 | this._stats[key] = value; 12 | } 13 | 14 | increment(key, amount) { 15 | this.set(key, (this.get(key) || 0) + (amount || 1)); 16 | } 17 | } 18 | 19 | module.exports = Stats; 20 | -------------------------------------------------------------------------------- /src/managers/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | CommandManager: require('./commands'), 3 | Logger: require('./logger'), 4 | Migrator: require('./migrator'), 5 | Stats: require('./stats'), 6 | Config: require('./config'), 7 | Notifications: require('./notifications'), 8 | Storage: require('./storage'), 9 | DynamicImports: require('./dynamic-imports'), 10 | Plugins: require('./plugins') 11 | }; 12 | -------------------------------------------------------------------------------- /src/commands/Fun/initial.js: -------------------------------------------------------------------------------- 1 | exports.run = function(bot, msg, args) { 2 | if (args.length === 0) { 3 | throw 'You must input text to be transformed.'; 4 | } 5 | msg.edit(args.map(arg => arg[0].toUpperCase() + arg.slice(1).toLowerCase()).join(' ')); 6 | }; 7 | 8 | exports.info = { 9 | name: 'initial', 10 | usage: 'initial ', 11 | description: 'Transforms the text you input into Initial Caps' 12 | }; 13 | -------------------------------------------------------------------------------- /src/scripts/configure.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const DynamicImportsManager = require('../managers/dynamic-imports'); 4 | const ConfigManager = require('../managers/config'); 5 | 6 | const importManager = new DynamicImportsManager(null, path.join(__dirname, '..')); 7 | importManager.init(); 8 | const configManager = new ConfigManager(null, path.join(__dirname, '..'), importManager); 9 | 10 | configManager.load(true); 11 | -------------------------------------------------------------------------------- /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | --- 2 | engines: 3 | duplication: 4 | enabled: true 5 | config: 6 | languages: 7 | - ruby 8 | - javascript 9 | - python 10 | - php 11 | eslint: 12 | enabled: true 13 | fixme: 14 | enabled: true 15 | ratings: 16 | paths: 17 | - "**.inc" 18 | - "**.js" 19 | - "**.jsx" 20 | - "**.module" 21 | - "**.php" 22 | - "**.py" 23 | - "**.rb" 24 | exclude_paths: 25 | - "src/commands/_base.js" 26 | -------------------------------------------------------------------------------- /src/commands/Fun/clap.js: -------------------------------------------------------------------------------- 1 | const randomizeCase = word => word.split('').map(c => Math.random() > 0.5 ? c.toUpperCase() : c.toLowerCase()).join(''); 2 | 3 | exports.run = (bot, msg, args) => { 4 | if (args.length < 1) { 5 | throw 'Please provide some text to clapify'; 6 | } 7 | 8 | msg.edit(args.map(randomizeCase).join(':clap:')); 9 | }; 10 | 11 | exports.info = { 12 | name: 'clap', 13 | usage: 'clap ', 14 | description: 'Clapifies your text' 15 | }; 16 | -------------------------------------------------------------------------------- /src/managers/migrator.js: -------------------------------------------------------------------------------- 1 | // const fse = require('fs-extra'); 2 | // const path = require('path'); 3 | 4 | exports.migrate = function ( /* bot */ ) { /* currently unused */ }; 5 | 6 | /*** UTILITIES ***/ 7 | // function moveIfExists(oldPath, newPath, failMessage) { 8 | // if (fse.existsSync(oldPath)) { 9 | // try { 10 | // fse.renameSync(oldPath, newPath); 11 | // } catch (err) { 12 | // console.error(failMessage); 13 | // process.exit(1); 14 | // } 15 | // } 16 | // } 17 | -------------------------------------------------------------------------------- /src/commands/Fun/shoot.js: -------------------------------------------------------------------------------- 1 | exports.run = function (bot, msg) { 2 | if (msg.mentions.users.size < 1) { 3 | throw '@mention some people to shoot!'; 4 | } 5 | 6 | let output = msg.mentions.users.map(m => `**${m}** :gun:`).join('\n'); 7 | 8 | msg.delete(); 9 | msg.channel.send({ 10 | embed: bot.utils.embed(`${bot.user.username} is on a killing spree!`, output) 11 | }); 12 | }; 13 | 14 | exports.info = { 15 | name: 'shoot', 16 | usage: 'shoot ', 17 | description: 'Shoots yer friendz!' 18 | }; 19 | -------------------------------------------------------------------------------- /src/commands/Settings/prefix.js: -------------------------------------------------------------------------------- 1 | exports.run = (bot, msg, args) => { 2 | if (args.length < 1) { 3 | throw 'Please provide a prefix to set!'; 4 | } 5 | 6 | const prefix = args.join(' '); 7 | bot.managers.config.set('prefix', prefix); 8 | // No point trying to delete this message, the bot will be 9 | // rebooting before we have a chance to. 10 | msg.edit('Prefix set, rebooting! :ok_hand:'); 11 | }; 12 | 13 | exports.info = { 14 | name: 'prefix', 15 | usage: 'prefix ', 16 | description: 'Sets the bot prefix' 17 | }; 18 | -------------------------------------------------------------------------------- /src/commands/Fun/bill.js: -------------------------------------------------------------------------------- 1 | const got = require('got'); 2 | 3 | exports.run = async (bot, msg) => { 4 | await msg.edit(':arrows_counterclockwise:'); 5 | const { body } = await got('http://belikebill.azurewebsites.net/billgen-API.php?default=1', { encoding: null }); 6 | 7 | await msg.channel.send({ 8 | file: { 9 | attachment: body, 10 | name: 'bill.jpg' 11 | } 12 | }); 13 | 14 | msg.delete(); 15 | }; 16 | 17 | exports.info = { 18 | name: 'bill', 19 | usage: 'bill', 20 | description: 'Be like Bill!' 21 | }; 22 | -------------------------------------------------------------------------------- /src/commands/Fun/space.js: -------------------------------------------------------------------------------- 1 | exports.run = (bot, msg, args) => { 2 | if (args.length < 1) { 3 | throw 'You must provide text to space out!'; 4 | } 5 | 6 | let amount = 2; 7 | 8 | if (!isNaN(args[0])) { 9 | amount = parseInt(args[0]); 10 | (amount < 1) && (amount = 1); 11 | (amount > 15) && (amount = 15); 12 | args = args.slice(1); 13 | } 14 | 15 | msg.edit(args.join(' '.repeat(amount / 2)).split('').join(' '.repeat(amount))); 16 | }; 17 | 18 | exports.info = { 19 | name: 'space', 20 | usage: 'space [amount] ', 21 | description: 'Spaces out text to look all dramatic n\' stuff' 22 | }; 23 | -------------------------------------------------------------------------------- /src/commands/Info/undelete.js: -------------------------------------------------------------------------------- 1 | exports.run = function (bot, msg) { 2 | const user = msg.mentions.users.first(); 3 | if (!user) { 4 | throw 'Please mention a user.'; 5 | } 6 | 7 | const delmsg = bot.deleted.get(user.id); 8 | if (!delmsg) { 9 | throw 'No recently deleted messages found'; 10 | } 11 | 12 | bot.deleted.delete(user.id); 13 | 14 | msg.edit(`Undeleted message of ${user.username} in ${delmsg.guild.name} | ${delmsg.channel.name}\n\`\`\`${delmsg.content}\`\`\``); 15 | }; 16 | 17 | exports.info = { 18 | name: 'undelete', 19 | usage: 'undelete ', 20 | description: 'Undeletes messages' 21 | }; 22 | -------------------------------------------------------------------------------- /src/commands/Utility/avatar.js: -------------------------------------------------------------------------------- 1 | exports.run = async (bot, msg) => { 2 | const user = msg.mentions.users.first(); 3 | if (!user) { 4 | throw 'Please mention the user who you want the avatar from.'; 5 | } 6 | 7 | if (!user.avatarURL) { 8 | throw 'That user does not have an avatar.'; 9 | } 10 | 11 | msg.delete(); 12 | (await msg.channel.send({ 13 | embed: bot.utils.embed(`${user.username}'s Avatar`, `[Download](${user.avatarURL})`, [], { image: user.avatarURL }) 14 | })).delete(30000); 15 | }; 16 | 17 | exports.info = { 18 | name: 'avatar', 19 | usage: 'avatar ', 20 | description: 'Gives you the avatar of a user' 21 | }; 22 | -------------------------------------------------------------------------------- /src/commands/Fun/fliptext.js: -------------------------------------------------------------------------------- 1 | const mapping = '¡"#$%⅋,)(*+\'-˙/0ƖᄅƐㄣϛ9ㄥ86:;<=>?@∀qƆpƎℲפHIſʞ˥WNOԀQɹS┴∩ΛMX⅄Z[/]^_`ɐqɔpǝɟƃɥᴉɾʞlɯuodbɹsʇnʌʍxʎz{|}~'; 2 | // Start with the character '!' 3 | const OFFSET = '!'.charCodeAt(0); 4 | 5 | exports.run = (bot, msg, args) => { 6 | if (args.length < 1) { 7 | throw 'You must provide text to flip!'; 8 | } 9 | 10 | msg.edit( 11 | args.join(' ').split('') 12 | .map(c => c.charCodeAt(0) - OFFSET) 13 | .map(c => mapping[c] || ' ') 14 | .reverse().join('') 15 | ); 16 | }; 17 | 18 | exports.info = { 19 | name: 'fliptext', 20 | usage: 'fliptext ', 21 | description: 'Flips your text!' 22 | }; 23 | -------------------------------------------------------------------------------- /src/commands/Fun/anim.js: -------------------------------------------------------------------------------- 1 | exports.run = (bot, msg, args) => { 2 | let parsed = bot.utils.parseArgs(args, 'd:'); 3 | 4 | if (parsed.leftover.length < 1) { 5 | throw 'Please provide some emojis to use!'; 6 | } 7 | 8 | let frames = parsed.leftover; 9 | let content = frames.join(' '); 10 | 11 | if (content.indexOf('|') > -1) { 12 | frames = content.split('|'); 13 | } 14 | 15 | let delay = isNaN(parsed.options.d) ? 250 : parsed.options.d; 16 | 17 | bot.utils.playAnimation(msg, delay, frames); 18 | }; 19 | 20 | exports.info = { 21 | name: 'anim', 22 | usage: 'anim [-d ] [emoji2] [...]', 23 | description: '"Animates" a series of emojis' 24 | }; 25 | -------------------------------------------------------------------------------- /src/commands/Fun/sigh.js: -------------------------------------------------------------------------------- 1 | const ascii = ` 2 | \`\`\` 3 | _______ _________ _________ , , 4 | / | / | | 5 | | | | | | 6 | | | | | | 7 | \\_____, | | _______, |________| 8 | \\ | | | | | 9 | | | | | | | 10 | | | | | | | 11 | ______/ ____|____ \\________| | | 12 | \u200b 13 | \`\`\` 14 | `; 15 | 16 | exports.run = function (bot, msg) { 17 | msg.edit(ascii); 18 | }; 19 | 20 | exports.info = { 21 | name: 'sigh', 22 | usage: 'sigh', 23 | description: 'Dramatic sigh text' 24 | }; 25 | -------------------------------------------------------------------------------- /src/managers/notifications.js: -------------------------------------------------------------------------------- 1 | const {stripIndents} = require('common-tags'); 2 | 3 | class NotificationManager { 4 | constructor(bot) { 5 | this._bot = bot; 6 | this.availablePlugins = bot.plugins.getAllOfType(NotificationManager.PLUGIN_TYPE); 7 | } 8 | 9 | notify(title, message) { 10 | this.availablePlugins.forEach(plugin => { 11 | try { 12 | plugin.notify(title, message) 13 | .catch(() => {}); 14 | } catch (e) {} // eslint-disable-line no-empty 15 | }); 16 | console.log(stripIndents` 17 | Notification: 18 | Title: ${title} 19 | Message: ${message} 20 | `); 21 | } 22 | } 23 | 24 | NotificationManager.PLUGIN_TYPE = 'notifications'; 25 | 26 | module.exports = NotificationManager; 27 | -------------------------------------------------------------------------------- /src/plugins/desktop-notifier.js: -------------------------------------------------------------------------------- 1 | const nn = require('node-notifier'); 2 | const opn = require('opn'); 3 | const NotificationManager = require('../managers/notifications'); 4 | 5 | class DesktopNotifier { 6 | constructor(bot) { 7 | this._bot = bot; 8 | 9 | this.init(); 10 | } 11 | 12 | init() { 13 | nn.on('click', () => { 14 | opn('', { app: 'Discord' }); 15 | }); 16 | } 17 | 18 | notify(title, message) { 19 | nn.notify({ 20 | title, 21 | message, 22 | wait: true 23 | }); 24 | } 25 | } 26 | 27 | DesktopNotifier.PLUGIN_NAME = 'desktop-notifier'; 28 | 29 | module.exports = { 30 | name: DesktopNotifier.PLUGIN_NAME, 31 | pluginTypes: [NotificationManager.PLUGIN_TYPE], 32 | run: bot => new DesktopNotifier(bot) 33 | }; 34 | -------------------------------------------------------------------------------- /src/commands/Info/guilds.js: -------------------------------------------------------------------------------- 1 | const oneLine = require('common-tags').oneLine; 2 | 3 | exports.run = (bot, msg) => { 4 | 5 | let servers = bot.guilds.array().sort((a, b) => b.memberCount - a.memberCount).map(guild => { 6 | return { 7 | name: guild.name, 8 | value: oneLine` 9 | ${guild.memberCount} users, 10 | ${guild.channels.size} channels 11 | ` 12 | }; 13 | }); 14 | 15 | for (let i = 0; i < servers.length / 20; i += 20) { 16 | msg.edit('', { 17 | embed: bot.utils.embed(`${bot.user.username}'s Servers`, '\u200b', servers.slice(i, i + 20), { inline: true }) 18 | }); 19 | } 20 | }; 21 | 22 | exports.info = { 23 | name: 'guilds', 24 | usage: 'guilds', 25 | description: 'Lists all guilds that you\'re a member of' 26 | }; 27 | -------------------------------------------------------------------------------- /src/commands/Utility/image.js: -------------------------------------------------------------------------------- 1 | const IMAGE_NAME = /\.(jpe?g|png|gif|webp)$/i; 2 | 3 | exports.run = async (bot, msg, args) => { 4 | if (args.length < 1) { 5 | throw 'Please provide an image URL to send.'; 6 | } 7 | 8 | msg.delete(); 9 | 10 | const url = args[0]; 11 | let name; 12 | 13 | if (!IMAGE_NAME.test(url)) { 14 | name = 'image.png'; 15 | } 16 | 17 | try { 18 | await msg.channel.send({ 19 | file: { 20 | name, 21 | attachment: url 22 | } 23 | }); 24 | } catch (ignore) { 25 | // Using throw inside of a catch doesn't work quite right 26 | return msg.error('Failed to send image.'); 27 | } 28 | }; 29 | 30 | exports.info = { 31 | name: 'image', 32 | usage: 'image ', 33 | description: 'Sends an image from a URL' 34 | }; 35 | -------------------------------------------------------------------------------- /docs/FAQ.md: -------------------------------------------------------------------------------- 1 | # Frequently Asked Questions 2 | If your question/problem is not answered here, please [join my Discord server](https://discord.io/rayzrdevofficial) and ask for help, providing any errors you have. 3 | 4 | - `ERROR: There are no scenarios; must have at least one.` 5 | > This occurs if you install Yarn incorrectly on Linux. Please read the [official Yarn installation instructions for Linux](http://yarnpkg.com/en/docs/install#linux-tab). 6 | 7 | - `Error: Cannot find module './config.json'` 8 | > This means you did not set up the `config.json` for the bot. Please read the [SharpBot installation instructions](https://github.com/Rayzr522/SharpBot#installing). 9 | 10 | - My game isn't showing after using `//setgame`! 11 | > This isn't a bug, it's just how Discord works. It can't be fixed. Don't worry, your game shows for everyone else. If you want to check your game, just use `//stats`. 12 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "es6": true, 4 | "node": true 5 | }, 6 | "extends": "eslint:recommended", 7 | "parserOptions": { 8 | "sourceType": "module", 9 | "ecmaVersion": 8 10 | }, 11 | "rules": { 12 | "indent": [ 13 | "warn", 14 | 4 15 | ], 16 | "linebreak-style": [ 17 | "off" 18 | ], 19 | "quotes": [ 20 | "warn", 21 | "single" 22 | ], 23 | "semi": [ 24 | "warn", 25 | "always" 26 | ], 27 | "no-console": [ 28 | "off" 29 | ], 30 | "no-unused-vars": [ 31 | "warn" 32 | ], 33 | "no-var": [ 34 | "warn" 35 | ], 36 | "eol-last": [ 37 | "warn", 38 | "always" 39 | ] 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/commands/Fun/jumbo.js: -------------------------------------------------------------------------------- 1 | exports.run = function (bot, msg, args) { 2 | if (args.length < 1) { 3 | throw 'Please provide an emoji to enlarge.'; 4 | } 5 | 6 | if (args[0].charCodeAt(0) >= 55296) { 7 | throw 'Cannot enlarge built-in discord emoji.'; 8 | } 9 | 10 | const match = args[0].match(/<:[a-zA-Z0-9_-]+:(\d{18})>/); 11 | 12 | if (!match || !match[1]) { 13 | throw 'Please provide a valid emoji.'; 14 | } 15 | 16 | const emoji = bot.emojis.get(match[1]); 17 | 18 | if (!emoji) { 19 | throw 'That emoji could not be identified!'; 20 | } 21 | 22 | msg.delete(); 23 | msg.channel.send({ 24 | files: [ 25 | emoji.url 26 | ] 27 | }); 28 | }; 29 | 30 | exports.info = { 31 | name: 'jumbo', 32 | usage: 'jumbo ', 33 | description: 'Enlarges emojis!' 34 | }; 35 | -------------------------------------------------------------------------------- /src/commands/Utility/shorturl.js: -------------------------------------------------------------------------------- 1 | const got = require('got'); 2 | 3 | exports.run = async (bot, msg, args) => { 4 | if (args.length < 1) { 5 | throw 'Please provide a url to shorten!'; 6 | } 7 | 8 | const url = args.join(' '); 9 | 10 | msg.delete(); 11 | 12 | const res = await got(`http://tinyurl.com/api-create.php?url=${encodeURIComponent(url)}`); 13 | msg.channel.send({ 14 | embed: bot.utils.embed('', '', [ 15 | { 16 | name: 'Link', 17 | value: url 18 | }, 19 | { 20 | name: 'Short URL', 21 | value: res.body 22 | } 23 | ]) 24 | }); 25 | 26 | }; 27 | 28 | exports.info = { 29 | name: 'shorturl', 30 | aliases: ['short'], 31 | usage: 'shorturl ', 32 | description: 'Shortens a URL', 33 | credits: 'xd <@335490180028956673>' 34 | }; 35 | -------------------------------------------------------------------------------- /src/commands/Fun/gif.js: -------------------------------------------------------------------------------- 1 | const got = require('got'); 2 | 3 | // Public API key provided by Giphy for anyone to use. 4 | const API_KEY = 'dc6zaTOxFJmzC'; 5 | 6 | exports.run = async (bot, msg, args) => { 7 | if (args.length < 1) { 8 | throw 'You must provide something to search for!'; 9 | } 10 | 11 | const res = await got(`http://api.giphy.com/v1/gifs/random?api_key=${API_KEY}&tag=${encodeURIComponent(args.join(' '))}`, { json: true }); 12 | 13 | if (!res || !res.body || !res.body.data) { 14 | throw 'Failed to find a GIF that matched your query!'; 15 | } 16 | 17 | msg.delete(); 18 | msg.channel.send({ 19 | embed: bot.utils.embed('', '', [], { image: res.body.data.image_url }) 20 | }); 21 | }; 22 | 23 | // async function findRandom(query) {} 24 | 25 | exports.info = { 26 | name: 'gif', 27 | usage: 'gif ', 28 | description: 'Searches Giphy for GIFs' 29 | }; 30 | -------------------------------------------------------------------------------- /src/commands/Utility/lmgtfy.js: -------------------------------------------------------------------------------- 1 | exports.run = (bot, msg, args) => { 2 | let parsed = bot.utils.parseArgs(args, ['i']); 3 | 4 | if (parsed.leftover.length < 1) { 5 | throw 'You must provide something to search for!'; 6 | } 7 | 8 | const query = encodeURIComponent(parsed.leftover.join(' ')); 9 | // Are there better ways to do this? Sure. But why not abuse 10 | // JavaScript's craziness, and use a truthy/falsy -> 1/0 converter? 11 | const internetExplainer = !!parsed.options.i / 1; 12 | 13 | msg.edit(`**Wow!** :arrow_right: http://www.lmgtfy.com/?iie=${internetExplainer}&q=${query}`); 14 | }; 15 | 16 | exports.info = { 17 | name: 'lmgtfy', 18 | usage: 'lmgtfy [search text]', 19 | description: 'Links to LMGTFY with the given search text', 20 | options: [ 21 | { 22 | name: '-i', 23 | description: 'Enables Internet Explainer' 24 | } 25 | ] 26 | }; 27 | -------------------------------------------------------------------------------- /src/commands/Fun/animals.js: -------------------------------------------------------------------------------- 1 | const got = require('got'); 2 | 3 | function makeCommand(type, url, transformer) { 4 | return { 5 | run: async (bot, msg) => { 6 | msg.edit(':arrows_counterclockwise:'); 7 | const { body } = await got(url); 8 | 9 | let file; 10 | try { 11 | file = transformer(body); 12 | } catch (ignore) { 13 | return msg.error('Failed to transform image URL!'); 14 | } 15 | 16 | msg.delete(); 17 | msg.channel.send({ files: [file] }); 18 | }, 19 | info: { 20 | name: type, 21 | usage: type, 22 | description: `Sends a random ${type} image` 23 | } 24 | }; 25 | } 26 | 27 | module.exports = [ 28 | makeCommand('cat', 'http://thecatapi.com/api/images/get?format=xml', body => /(.+?)<\/url>/.exec(body)[1]), 29 | makeCommand('dog', 'http://random.dog/woof', body => `http://random.dog/${body}`) 30 | ]; 31 | -------------------------------------------------------------------------------- /src/commands/Utility/calculator.js: -------------------------------------------------------------------------------- 1 | const math = require('math-expression-evaluator'); 2 | const stripIndents = require('common-tags').stripIndents; 3 | 4 | exports.run = (bot, msg, args) => { 5 | if (args.length < 1) { 6 | throw 'You must provide a equation to be solved on the calculator'; 7 | } 8 | 9 | const question = args.join(' '); 10 | 11 | let answer; 12 | try { 13 | answer = math.eval(question); 14 | } catch (err) { 15 | throw `Invalid math equation: ${err}`; 16 | } 17 | 18 | msg.delete(); 19 | msg.channel.send({ 20 | embed: bot.utils.embed('', stripIndents` 21 | **Equation:**\n\`\`\`\n${question}\n\`\`\` 22 | **Answer:**\n\`\`\`\n${answer}\n\`\`\` 23 | `) 24 | }); 25 | }; 26 | 27 | exports.info = { 28 | name: 'calculate', 29 | usage: 'calculate ', 30 | aliases: ['calc', 'math'], 31 | description: 'Calculates any math equation', 32 | credits: 'Carbowix', 33 | }; 34 | -------------------------------------------------------------------------------- /src/commands/Fun/fanceh.js: -------------------------------------------------------------------------------- 1 | const mapping = { 2 | ' ': ' ', 3 | '0': ':zero:', 4 | '1': ':one:', 5 | '2': ':two:', 6 | '3': ':three:', 7 | '4': ':four:', 8 | '5': ':five:', 9 | '6': ':six:', 10 | '7': ':seven:', 11 | '8': ':eight:', 12 | '9': ':nine:', 13 | '!': ':grey_exclamation:', 14 | '?': ':grey_question:', 15 | '#': ':hash:', 16 | '*': ':asterisk:' 17 | }; 18 | 19 | 'abcdefghijklmnopqrstuvwxyz'.split('').forEach(c => { 20 | mapping[c] = mapping[c.toUpperCase()] = ` :regional_indicator_${c}:`; 21 | }); 22 | 23 | exports.run = (bot, msg, args) => { 24 | if (args.length < 1) { 25 | throw 'You must provide some text to fanceh-fy!'; 26 | } 27 | 28 | msg.edit( 29 | args.join(' ') 30 | .split('') 31 | .map(c => mapping[c] || c) 32 | .join('') 33 | ); 34 | }; 35 | 36 | exports.info = { 37 | name: 'fanceh', 38 | usage: 'fanceh ', 39 | description: 'Renders text in big emoji letters' 40 | }; 41 | -------------------------------------------------------------------------------- /src/commands/Utility/say.js: -------------------------------------------------------------------------------- 1 | exports.run = (bot, msg, args) => { 2 | const parsed = bot.utils.parseArgs(args, ['c:']); 3 | 4 | if (parsed.leftover.length < 1) { 5 | throw 'You must put something to say!'; 6 | } 7 | 8 | let channel = msg.channel; 9 | if (parsed.options.c) { 10 | const id = parsed.options.c.match(/\d{18}/)[0]; 11 | 12 | if (!id) { 13 | throw 'Invalid channel!'; 14 | } 15 | 16 | channel = bot.channels.get(id); 17 | if (!channel) { 18 | throw 'That channel could not be found!'; 19 | } 20 | } 21 | 22 | msg.delete(); 23 | channel.send(parsed.leftover.join(' ')); 24 | }; 25 | 26 | exports.info = { 27 | name: 'say', 28 | usage: 'say ', 29 | description: 'Says the message you put. Useful for shortcuts.', 30 | options: [ 31 | { 32 | name: '-c', 33 | usage: '-c ', 34 | description: 'Specifies a specific channel to send the message in' 35 | } 36 | ] 37 | }; 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 RayzrDev 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/commands/Utility/ping.js: -------------------------------------------------------------------------------- 1 | exports.run = async (bot, msg, args) => { 2 | let parsed = bot.utils.parseArgs(args, ['o']); 3 | 4 | if (parsed.options.o) { 5 | return msg.edit(':stopwatch: Ping').then(m => { 6 | let time = msg.editedTimestamp - msg.createdTimestamp; 7 | bot.utils.playAnimation(m, 500, [ 8 | ':stopwatch: __P__ing', 9 | ':stopwatch: __Pi__ng', 10 | ':stopwatch: __Pin__g', 11 | ':stopwatch: __Ping__', 12 | `:stopwatch: ***Pong!*** \`${time}ms\`` 13 | ]); 14 | }); 15 | } 16 | 17 | await msg.edit(':thinking: Ping'); 18 | 19 | const delay = msg.editedTimestamp - msg.createdTimestamp; 20 | 21 | (await msg.edit(`:stopwatch: Pong! \`${delay}ms\``)).delete(5000); 22 | }; 23 | 24 | exports.info = { 25 | name: 'ping', 26 | usage: 'ping [-o]', 27 | description: 'Pings the bot', 28 | options: [ 29 | { 30 | name: '-o', 31 | usage: '-o', 32 | description: 'Shows the old ping message (animated)' 33 | } 34 | ] 35 | }; 36 | -------------------------------------------------------------------------------- /src/commands/Fun/roll.js: -------------------------------------------------------------------------------- 1 | const Roll = require('roll'); 2 | const roller = new Roll(); 3 | 4 | exports.run = function (bot, msg, args) { 5 | if (args.length < 1) { 6 | throw 'You must specify in dice notation (XdY)'; 7 | } 8 | 9 | let reason = ''; 10 | let footer = ''; 11 | 12 | footer += `:game_die: **${args[0]}**`; 13 | if (args.length > 1) { 14 | reason = args.splice(1).join(' '); 15 | footer += ` | ${reason}`; 16 | } 17 | 18 | let results = roller.roll(args[0]); 19 | 20 | msg.delete(); 21 | 22 | let embed = bot.utils.embed( 23 | `Total: ${results.result}`, 24 | `${[].concat.apply([], results.rolled).join(', ').substr(0, 1800)}`, 25 | [ 26 | { 27 | name: '\u200b', 28 | value: footer 29 | } 30 | ] 31 | ); 32 | 33 | msg.channel.send({ embed }); 34 | }; 35 | 36 | exports.info = { 37 | name: 'roll', 38 | usage: 'roll XdY ', 39 | description: 'rolls X dice with Y sides. Supports standard dice notation', 40 | credits: '<@136641861073764352>' // Abyss#0473 41 | }; 42 | -------------------------------------------------------------------------------- /src/commands/Fun/8ball.js: -------------------------------------------------------------------------------- 1 | const responses = [ 2 | 'Unclear, ask again later', 3 | 'Soon', 4 | 'Yes', 5 | 'Absolutely!', 6 | 'Never', 7 | 'Magic 8-ball is currently unavailable, please leave a message after the tone. \\*beep\\*', 8 | 'When you are ready', 9 | 'Hopefully', 10 | 'Hopefully not', 11 | 'Oh my, why would you even ask that?', 12 | 'What kind of a question is that?', 13 | 'Over my dead body!', 14 | 'Haha, funny joke' 15 | ]; 16 | 17 | function randomItem(array) { 18 | return array[Math.floor(Math.random() * array.length)]; 19 | } 20 | 21 | exports.run = (bot, msg, args) => { 22 | if (args.length < 1) { 23 | throw 'Please specify something to ask of the magic 8-ball!'; 24 | } 25 | 26 | let response = randomItem(responses); 27 | 28 | const query = args.join(' '); 29 | 30 | if (query.indexOf('ipodtouch0218') > -1 || query.indexOf('233360087979130882') > -1) { 31 | response = 'HAH'; 32 | } 33 | 34 | msg.edit(`${query} :8ball: | **${response}**`); 35 | }; 36 | 37 | 38 | exports.info = { 39 | name: '8ball', 40 | usage: '8ball ', 41 | description: 'Asks the magic 8-ball a question' 42 | }; 43 | -------------------------------------------------------------------------------- /docs/storage-system.md: -------------------------------------------------------------------------------- 1 | ```javascript 2 | const bot = {}; 3 | 4 | // Get yourself a StorageAdapter 5 | const storage = bot.storage('todo-list'); 6 | 7 | // Get the array of items (default to empty array) 8 | const items = storage.get('items') || []; 9 | 10 | // Add an item to the array 11 | items.push({ 12 | name: 'Make breakfast', 13 | longDesc: 'Make some eggs and toast. Yum!' 14 | }); 15 | 16 | // Set the value with the key 'items' to the updated array 17 | storage.set('items', items); 18 | // Save your changes, this step is probably important :P 19 | storage.save(); 20 | 21 | // === Other StorageAdapter properties/methods 22 | // StorageAdapter.load() 23 | // - Loads data from the disk, called automatically 24 | // when a StorageAdapter is constructed 25 | // StorageAdapter.internal 26 | // - The raw internal storage object 27 | // StorageAdapter.keys 28 | // - The computed keys 29 | // StorageAdapter.values 30 | // - The computed values 31 | // 32 | // === Notes 33 | // StorageAdapter instances are cached, so calling 34 | // bot.storage('someName') from two different locations 35 | // will actually give you the same object. A new 36 | // StorageAdapter is only constructed the first time 37 | // the adapter factory is called for a specific name. 38 | ``` 39 | -------------------------------------------------------------------------------- /src/commands/Moderation/message-deletion.js: -------------------------------------------------------------------------------- 1 | function makeCommand(name, viewName, description, filter) { 2 | return { 3 | run: async (bot, msg, args) => { 4 | let count = parseInt(args[0]) || 1; 5 | 6 | msg.delete(); 7 | 8 | const messages = await msg.channel.fetchMessages({ limit: Math.min(count, 100), before: msg.id }); 9 | const deletable = messages.filter(message => filter(message, bot)); 10 | 11 | await Promise.all( 12 | deletable.map(m => m.delete()) 13 | ); 14 | 15 | const deleted = deletable.size; 16 | 17 | (await msg.channel.send(`:white_check_mark: ${viewName} \`${deletable.size}\` message${deleted === 1 ? '' : 's'}.`)).delete(2000); 18 | }, 19 | info: { 20 | name, 21 | usage: `${name} [amount]`, 22 | description 23 | } 24 | }; 25 | } 26 | 27 | module.exports = [ 28 | makeCommand('purge', 'Purged', 'Deletes a certain number of messages', () => true), 29 | makeCommand('prune', 'Pruned', 'Deletes a certain number of messages sent by you', (msg, bot) => msg.author.id === bot.user.id), 30 | makeCommand('flush', 'Flushed', 'Deletes a certain number of messages sent by bots', msg => msg.author.bot) 31 | ]; 32 | -------------------------------------------------------------------------------- /src/commands/Fun/tiny.js: -------------------------------------------------------------------------------- 1 | const mappings = (function (object) { 2 | let output = []; 3 | 4 | for (let key in object) { 5 | output.push({ 6 | regex: new RegExp(key, 'ig'), 7 | replacement: object[key] 8 | }); 9 | } 10 | 11 | return output; 12 | })({ 13 | a: '\u1D00', 14 | b: '\u0299', 15 | c: '\u1D04', 16 | d: '\u1D05', 17 | e: '\u1D07', 18 | f: '\uA730', 19 | g: '\u0262', 20 | h: '\u029C', 21 | i: '\u026A', 22 | j: '\u1D0A', 23 | k: '\u1D0B', 24 | l: '\u029F', 25 | m: '\u1D0D', 26 | n: '\u0274', 27 | o: '\u1D0F', 28 | p: '\u1D18', 29 | q: '\u0071', 30 | r: '\u0280', 31 | s: '\uA731', 32 | t: '\u1D1B', 33 | u: '\u1D1C', 34 | v: '\u1D20', 35 | w: '\u1D21', 36 | x: '\u0078', 37 | y: '\u028F', 38 | z: '\u1D22' 39 | }); 40 | 41 | exports.run = (bot, msg, args) => { 42 | if (args.length < 1) { 43 | throw 'You must provide some text to shrink!'; 44 | } 45 | 46 | let output = args.join(' '); 47 | mappings.forEach(replacer => output = output.replace(replacer.regex, replacer.replacement)); 48 | 49 | msg.delete(); 50 | msg.channel.send(output); 51 | }; 52 | 53 | exports.info = { 54 | name: 'tiny', 55 | usage: 'tiny ', 56 | description: 'Converts your text to tiny letters!' 57 | }; 58 | -------------------------------------------------------------------------------- /src/commands/Utility/status.js: -------------------------------------------------------------------------------- 1 | const validStatuses = [ 2 | { 3 | internal: 'online', 4 | display: 'online', 5 | emoji: ':zap:' 6 | }, 7 | { 8 | internal: 'idle', 9 | display: 'idle', 10 | emoji: ':beach_umbrella:' 11 | }, 12 | { 13 | internal: 'dnd', 14 | display: 'do-not-disturb', 15 | emoji: ':mute:' 16 | }, 17 | { 18 | internal: 'invisible', 19 | display: 'invisible', 20 | emoji: ':ghost:' 21 | } 22 | ]; 23 | 24 | const validStatusRegex = new RegExp(`^(${validStatuses.map(status => status.internal).join('|')})$`); 25 | const validStatusString = validStatuses.map(status => `\`${status.internal}\``).join(', '); 26 | 27 | exports.run = async (bot, msg, args) => { 28 | if (args.length < 1 || !validStatusRegex.test(args[0])) { 29 | throw `Please provide a status to set: ${validStatusString}`; 30 | } 31 | 32 | const status = validStatuses.find(status => status.internal === args[0].toLowerCase()); 33 | 34 | bot.user.setStatus(status.internal); 35 | 36 | (await msg.edit(`${status.emoji} Set status to ${status.display}.`)).delete(5000); 37 | }; 38 | 39 | exports.info = { 40 | name: 'status', 41 | usage: `status <${validStatuses.map(status => status.internal).join('|')}>`, 42 | description: 'Sets your status' 43 | }; 44 | -------------------------------------------------------------------------------- /src/scripts/debug.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const ConfigManager = require('../managers/config'); 3 | const { textUpload } = require('../utils'); 4 | const packageJson = require('../../package.json'); 5 | 6 | const manager = new ConfigManager(null, path.join(__dirname, '..')); 7 | 8 | const config = manager.load(); 9 | 10 | Object.keys(config).forEach(configName => { 11 | const wordsToHide = ['token', 'password', 'auth']; 12 | if (wordsToHide.some(word => configName.toLowerCase().indexOf(word) !== -1)) { 13 | config[configName] = 'HIDDEN'; 14 | } 15 | }); 16 | 17 | let debugMessage = `SharpBot ${packageJson.version} Debug Output: 18 | 19 | Config: ${JSON.stringify(config, null, 4)} 20 | `; 21 | 22 | const handleError = error => { 23 | console.log('We were unable to upload your debug information to HasteBin. Please copy & paste this output, or take a screenshot of it:'); 24 | if (error) { 25 | debugMessage += 'Debug Error: ' + error.stack; 26 | } 27 | console.log('\n' + debugMessage); 28 | process.exit(); 29 | }; 30 | 31 | textUpload(debugMessage) 32 | .then(({ url }) => { 33 | if (url) { 34 | console.log('Here is the link to your debug output: ' + url); 35 | } else { 36 | handleError(); 37 | } 38 | process.exit(); 39 | }) 40 | .catch(handleError); 41 | 42 | -------------------------------------------------------------------------------- /src/commands/Utility/text-upload.js: -------------------------------------------------------------------------------- 1 | function makeCommand(name, displayName, methodName) { 2 | return { 3 | run: async (bot, msg, args) => { 4 | const parsed = bot.utils.parseArgs(args, 'r'); 5 | 6 | if (parsed.leftover.length < 1) { 7 | throw 'You must have something to upload!'; 8 | } 9 | 10 | await msg.edit(':arrows_counterclockwise: Uploading...'); 11 | const { url, rawUrl } = await bot.utils[methodName](parsed.leftover.join(' ')); 12 | 13 | if (!url) { 14 | throw 'Failed to upload, no key was returned!'; 15 | } 16 | 17 | if (parsed.options.r) { 18 | msg.edit(`:white_check_mark: ${rawUrl}`); 19 | } else { 20 | msg.edit(`:white_check_mark: ${url}`); 21 | } 22 | }, 23 | info: { 24 | name, 25 | usage: `${name} `, 26 | description: `Uploads some text to ${displayName}`, 27 | options: [ 28 | { 29 | name: '-r', 30 | description: 'Returns the URL for a raw version of your upload' 31 | } 32 | ] 33 | } 34 | }; 35 | } 36 | 37 | module.exports = [ 38 | makeCommand('haste', 'Hastebin', 'hastebinUpload'), 39 | makeCommand('ix', 'ix.io', 'ixUpload') 40 | ]; 41 | -------------------------------------------------------------------------------- /src/commands/Fun/xkcd.js: -------------------------------------------------------------------------------- 1 | const got = require('got'); 2 | 3 | exports.run = async (bot, msg, args) => { 4 | let id; 5 | 6 | if (args[0] === 'latest') { 7 | id = (await getLatest()).num; 8 | } else { 9 | id = parseInt(args[0]); 10 | if (isNaN(id)) { 11 | id = await getRandom(); 12 | } 13 | } 14 | 15 | // Avoid the 404 page 16 | while (id === 404) { 17 | id = await getRandom(); 18 | } 19 | 20 | const info = await getInfo(id); 21 | 22 | msg.delete(); 23 | msg.channel.send({ 24 | embed: bot.utils.embed(`[${id}] ${info.title}`, '', [], { 25 | image: info.img, 26 | // Color of the XKCD website. 27 | color: [150, 168, 199], 28 | url: `http://xkcd.com/${id}` 29 | }).setFooter(info.alt) 30 | }); 31 | }; 32 | 33 | async function getInfo(id) { 34 | return (await got(`http://xkcd.com/${id}/info.0.json`, { json: true })).body; 35 | } 36 | 37 | async function getLatest() { 38 | return (await got('http://xkcd.com/info.0.json', { json: true })).body; 39 | } 40 | 41 | async function getRandom() { 42 | const latest = await getLatest(); 43 | const max = latest.num; 44 | 45 | return Math.floor(Math.random() * max); 46 | } 47 | 48 | exports.info = { 49 | name: 'xkcd', 50 | usage: 'xkcd [latest|]', 51 | description: 'Fetches random or specific XKCD comics' 52 | }; 53 | -------------------------------------------------------------------------------- /src/commands/Info/mutual.js: -------------------------------------------------------------------------------- 1 | const limitTo = (array, max, joiner) => array.slice(0, max).join(joiner) + (array.length <= max ? '' : '...'); 2 | 3 | const inGuild = (guild, user) => !!guild.members.find('id', user.id); 4 | 5 | exports.run = async (bot, msg, args) => { 6 | if (args.length < 1) { 7 | throw 'Please provide a name of a Discord server you are in.'; 8 | } 9 | 10 | const query = args.join(' ').toLowerCase(); 11 | // Try to parse by Server Name fist or Server ID 12 | const guild = bot.guilds.find(guild => guild.name.toLowerCase() === query || guild.id === query); 13 | 14 | if (!guild) { 15 | throw 'That guild could not be found!'; 16 | } 17 | 18 | const mutual = bot.users.filter(user => inGuild(msg.guild, user) && inGuild(guild, user)); 19 | 20 | await msg.edit(':arrows_counterclockwise: Searching...'); 21 | 22 | const { url } = await bot.utils.textUpload(mutual.map(user => `- ${user.tag}`).join('\n')); 23 | 24 | msg.delete(); 25 | (await msg.channel.send({ 26 | embed: bot.utils.embed(`Mutual members of ${guild.name}`, limitTo(mutual.array().map(user => user.tag), 25, ', '), [ 27 | { 28 | name: 'Full List', 29 | value: url 30 | } 31 | ]) 32 | })).delete(30000); 33 | }; 34 | 35 | exports.info = { 36 | name: 'mutual', 37 | usage: 'mutual ', 38 | description: 'Finds users who are in a given server that you are in' 39 | }; 40 | -------------------------------------------------------------------------------- /src/commands/Info/playerinfo.js: -------------------------------------------------------------------------------- 1 | const got = require('got'); 2 | const cheerio = require('cheerio'); 3 | 4 | exports.run = async (bot, msg, args) => { 5 | if (args.length < 1) { 6 | throw 'Please provide the username of a player.'; 7 | } 8 | 9 | const username = args[0]; 10 | 11 | const uuid = await getUUID(username); 12 | if (!uuid) { 13 | throw 'That player could not be found.'; 14 | } 15 | 16 | msg.delete(); 17 | return msg.channel.send({ 18 | embed: bot.utils.embed('', '', [ 19 | { 20 | name: 'Username', 21 | value: username 22 | }, 23 | { 24 | name: 'UUID', 25 | value: `\`${uuid}\`` 26 | }, 27 | { 28 | name: 'Skin', 29 | value: `[Download](https://crafatar.com/skins/${uuid}.png)` 30 | } 31 | ], { thumbnail: `https://crafatar.com/avatars/${uuid}.png?size=250&overlay=true` }) 32 | }); 33 | }; 34 | 35 | async function getUUID(username) { 36 | const res = await got(`https://mcuuid.net/?q=${username}`); 37 | const $ = cheerio.load(res.body); 38 | const input = $('input')[1]; 39 | 40 | if (!input) { 41 | return; 42 | } 43 | 44 | return input.attribs['value']; 45 | } 46 | 47 | exports.info = { 48 | name: 'playerinfo', 49 | usage: 'playerinfo ', 50 | description: 'Shows information about a Minecraft player' 51 | }; 52 | -------------------------------------------------------------------------------- /src/commands/Info/emoji.js: -------------------------------------------------------------------------------- 1 | exports.run = async (bot, msg, args) => { 2 | if (args.length < 1) { 3 | throw 'Please provide an emoji to gather info on!'; 4 | } 5 | 6 | if (args[0].charCodeAt(0) >= 55296) { 7 | msg.delete(); 8 | 9 | return (await msg.channel.send({ 10 | embed: bot.utils.embed(args[0], 'Built-in **Discord** emoji.') 11 | })).delete(15000); 12 | } 13 | 14 | const match = args[0].match(/<:[a-zA-Z0-9_-]+:(\d{18})>/); 15 | 16 | if (!match || !match[1]) { 17 | throw 'Please provide a valid emoji!'; 18 | } 19 | 20 | const emoji = bot.emojis.get(match[1]); 21 | 22 | if (!emoji) { 23 | throw 'That emoji could not be identified.'; 24 | } 25 | 26 | msg.delete(); 27 | (await msg.channel.send({ 28 | embed: bot.utils.embed('', '', [ 29 | { 30 | name: 'Name', 31 | value: emoji.name 32 | }, 33 | { 34 | name: 'From Guild', 35 | value: emoji.guild.name 36 | }, 37 | { 38 | name: 'ID', 39 | value: emoji.id 40 | }, 41 | { 42 | name: 'Download URL', 43 | value: emoji.url 44 | } 45 | ], { thumbnail: emoji.url }) 46 | })).delete(15000); 47 | }; 48 | 49 | exports.info = { 50 | name: 'emoji', 51 | usage: 'emoji ', 52 | description: 'Shows information about an emoji' 53 | }; 54 | -------------------------------------------------------------------------------- /src/commands/Utility/imagedumper.js: -------------------------------------------------------------------------------- 1 | const got = require('got'); 2 | const fs = require('fs'); 3 | const path = require('path'); 4 | exports.run = (bot, msg, args) => { 5 | let count = parseInt(args[0]) || 100; 6 | let attachments = []; 7 | 8 | msg.channel.fetchMessages({ limit: Math.min(count, 100), before: msg.id }).then(messages => { 9 | messages.map(m => { 10 | m.attachments.map(attachment => { 11 | if (attachment.height) { 12 | attachments.push(attachment.url); 13 | } 14 | }); 15 | }); 16 | 17 | let dir = __dirname + '/../../../out'; 18 | if (!fs.existsSync(dir)) fs.mkdirSync(dir); 19 | 20 | for (let i = 0; i < attachments.length; i++) download(attachments[i]); 21 | 22 | if (attachments.length === 0) throw 'Couldn\'t find any images.'; 23 | 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); }); 24 | msg.delete(); 25 | }).catch(msg.error); 26 | }; 27 | 28 | exports.info = { 29 | name: 'imagedumper', 30 | usage: 'imagedumper ', 31 | description: 'Grabs all images from the specified amount of messages (max 100)', 32 | credits: '<@149916494183137280>' // Liam Bagge#0550 33 | }; 34 | 35 | function download(url) { 36 | let file = fs.createWriteStream(`${__dirname}/../../../out/attachment_${path.basename(url)}`); 37 | got.stream(url).pipe(file); 38 | } 39 | -------------------------------------------------------------------------------- /src/commands/Fun/figlet.js: -------------------------------------------------------------------------------- 1 | const figlet = require('figlet'); 2 | 3 | exports.run = (bot, msg, args) => { 4 | // -l -- List all fonts 5 | // -f -- Set font 6 | const parsed = bot.utils.parseArgs(args, ['l', 'f:']); 7 | 8 | if (parsed.options.l) { 9 | bot.utils.textUpload(figlet.fontsSync().join('\n')).then(({ url }) => { 10 | if (!url) { 11 | return msg.error('Failed to upload fonts list!'); 12 | } 13 | 14 | msg.edit({ 15 | embed: bot.utils.embed('Available Fonts', `A list of available fonts can be found [here](${url}).`) 16 | }).then(m => m.delete(5000)); 17 | }); 18 | return; 19 | } 20 | 21 | if (parsed.leftover.length < 1) { 22 | throw 'You must provide some text to render!'; 23 | } 24 | 25 | const options = {}; 26 | 27 | if (parsed.options.f) { 28 | options.font = parsed.options.f; 29 | } 30 | 31 | msg.delete(); 32 | 33 | const input = parsed.leftover.join(' '); 34 | msg.channel.send(`\`\`\`\n${figlet.textSync(input, options)}\n\`\`\``); 35 | }; 36 | 37 | exports.info = { 38 | name: 'figlet', 39 | usage: 'figlet ', 40 | description: 'Renders fancy ASCII text', 41 | options: [ 42 | { 43 | name: '-f', 44 | usage: '-f ', 45 | description: 'Sets the font to use' 46 | }, 47 | { 48 | name: '-l', 49 | description: 'Lists all available fonts' 50 | } 51 | ] 52 | }; 53 | -------------------------------------------------------------------------------- /src/commands/Fun/binary.js: -------------------------------------------------------------------------------- 1 | exports.methods = { 2 | encode: input => { 3 | return input.toString().split('') 4 | .map(c => c.charCodeAt(0).toString(2)); 5 | }, 6 | decode: input => { 7 | let _input = typeof input === 'string' ? input.split(' ') : input; 8 | return _input.map(c => parseInt(c, 2)) 9 | .map(c => String.fromCharCode(c)) 10 | .join(''); 11 | } 12 | }; 13 | 14 | exports.run = (bot, msg, args) => { 15 | if (args.length < 2) { 16 | throw `Do \`${bot.config.prefix}help binary\` to see how to use this.`; 17 | } 18 | 19 | let input = args.slice(1).join(' '); 20 | 21 | if (args[0].match(/^enc(ode(Text)?)?$/i)) { 22 | msg.edit(this.methods.encode(input).join(' ')); 23 | } else if (args[0].match(/^dec(ode(Text)?)?$/i)) { 24 | msg.edit(this.methods.decode(input)); 25 | } else if (args[0].match(/^decToBin$/i)) { 26 | if (isNaN(input)) { 27 | throw 'Input must be a number!'; 28 | } 29 | 30 | msg.edit(parseInt(input).toString(2)); 31 | } else if (args[0].match(/^binToDec$/i)) { 32 | if (isNaN(input)) { 33 | throw 'Input must be a number!'; 34 | } 35 | 36 | msg.edit(parseInt(input, 2)); 37 | } else { 38 | throw `Unknown sub command: \`${args[0]}\``; 39 | } 40 | }; 41 | 42 | exports.info = { 43 | name: 'binary', 44 | usage: 'binary ', 45 | description: 'Convert your input to/from binary' 46 | }; 47 | -------------------------------------------------------------------------------- /src/commands/Utility/timezone.js: -------------------------------------------------------------------------------- 1 | const got = require('got'); 2 | 3 | exports.run = async (bot, msg, args) => { 4 | if (args.length < 1) { 5 | throw 'You must specify a time to convert'; 6 | } 7 | 8 | let input = args.join(' '); 9 | let url = `https://api.duckduckgo.com/?q=${encodeURIComponent(input)}&format=json`; 10 | 11 | await msg.edit(':arrows_counterclockwise: Loading conversion...'); 12 | 13 | const res = await got(url, { json: true }); 14 | 15 | if (!res || !res.body) { 16 | throw 'Could not load data from DDG.'; 17 | } 18 | 19 | let data = res.body; 20 | 21 | let answer = data['Answer']; 22 | let message; 23 | 24 | if (data['AnswerType'] === 'timezone_converter') { 25 | msg.delete(); 26 | 27 | let matches = input.match(/(.*?)\s*(to|in)\s*(.*)/); 28 | let prefix; 29 | 30 | if (matches) { 31 | prefix = matches[1]; 32 | } else { 33 | prefix = input; 34 | } 35 | 36 | message = bot.utils.embed('', '', [ 37 | { 38 | name: 'Timezone:', 39 | value: `${prefix} \u2794 ${answer}` 40 | } 41 | ]); 42 | 43 | msg.channel.send({ embed: message }); 44 | } else { 45 | throw `No conversion found for ${input}`; 46 | } 47 | }; 48 | 49 | exports.info = { 50 | name: 'timezone', 51 | usage: 'timezone