├── commands ├── index.md ├── Fun │ ├── coinflip.js │ ├── shameBell.js │ ├── cat.js │ ├── banner.js │ ├── dog.js │ ├── catfacts.js │ ├── yomomma.js │ ├── choice.js │ ├── fml.js │ ├── card.js │ ├── 8ball.js │ ├── urban.js │ ├── quiz.js │ ├── insult.js │ └── dogfacts.js ├── Moderation │ ├── ban.js │ ├── kick.js │ ├── softban.js │ └── check.js ├── Music │ ├── skip.js │ ├── pause.js │ ├── resume.js │ ├── time.js │ ├── queue.js │ ├── join.js │ ├── add.js │ ├── play.js │ └── volume.js ├── Self │ ├── nick.js │ ├── presence.js │ └── friend.js ├── Misc │ ├── spy.js │ └── starboard.js ├── System │ ├── prune.js │ ├── purge.js │ └── exec.js └── Tools │ ├── price.js │ ├── twitch.js │ ├── tags.js │ ├── debug.js │ └── guide.js ├── functions ├── index.md ├── addCommas.js ├── splitText.js ├── hierarchyCheck.js └── points.js ├── packages └── index.md ├── providers ├── index.md ├── redis.js ├── localStorage.js ├── json.js ├── levelup.js ├── mongodb.js ├── nedb.js ├── mysql.js ├── sqlite.js └── rethinkdb.js ├── monitors ├── index.md ├── nomention.js └── didyoumean.js ├── .travis.yml ├── inhibitors ├── nsfw.js ├── index.md ├── deleteCommand.js ├── requiredProviders.js ├── commandSlowMode.js ├── cooldown.js └── commandCooldown.js ├── events └── guildCreate.js ├── package.json ├── .gitignore ├── LICENSE ├── .eslintrc.json └── README.md /commands/index.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /functions/index.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/index.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /providers/index.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /monitors/index.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "8" 4 | install: npm install 5 | -------------------------------------------------------------------------------- /inhibitors/nsfw.js: -------------------------------------------------------------------------------- 1 | exports.run = (client, msg, cmd) => { 2 | if (!cmd.conf.nsfw || msg.channel.nsfw) return false; 3 | return "This command is only available in NSFW channels."; 4 | }; 5 | 6 | exports.conf = { 7 | enabled: true, 8 | priority: 5, 9 | }; 10 | 11 | exports.help = { 12 | name: "nsfw", 13 | type: "inhibitors", 14 | description: "Commands marked as NSFW are only allowed in NSFW channels.", 15 | }; 16 | -------------------------------------------------------------------------------- /inhibitors/index.md: -------------------------------------------------------------------------------- 1 | Inhibitor | Description 2 | ----------|------------- 3 | deleteCommand.js | Deletes every message that is a command. Keeps your spam down to a minimum. 4 | commandSlowMode.js | Ratelimits commands from being executed if spammed. 5 | commandCooldown.js | Ability to put a cooldown on specific commands per player, channel or guild, disabled by default. 6 | nsfw.js | Commands marked as NSFW are only allowed in NSFW channels. 7 | requiredProviders.js | Checks if specific providers are loaded. 8 | -------------------------------------------------------------------------------- /commands/Fun/coinflip.js: -------------------------------------------------------------------------------- 1 | exports.run = (client, msg) => msg.reply(`You flipped ${Math.random() > 0.5 ? "Heads" : "Tails"}.`); 2 | 3 | exports.conf = { 4 | enabled: true, 5 | selfbot: false, 6 | runIn: ["text", "dm", "group"], 7 | aliases: ["coin"], 8 | permLevel: 0, 9 | botPerms: [], 10 | requiredFuncs: [], 11 | requiredModules: [], 12 | }; 13 | 14 | exports.help = { 15 | name: "coinflip", 16 | description: "Flips a (pseudo) fair coin.", 17 | usage: "", 18 | usageDelim: "", 19 | type: "commands", 20 | }; 21 | -------------------------------------------------------------------------------- /commands/Fun/shameBell.js: -------------------------------------------------------------------------------- 1 | exports.run = (client, msg, [user]) => msg.channel.send(`🔔 SHAME 🔔 ${user} 🔔 SHAME 🔔`); 2 | 3 | exports.conf = { 4 | enabled: true, 5 | selfbot: false, 6 | runIn: ["text", "dm", "group"], 7 | aliases: [], 8 | permLevel: 0, 9 | botPerms: [], 10 | requiredFuncs: [], 11 | requiredModules: [], 12 | }; 13 | 14 | exports.help = { 15 | name: "shame", 16 | description: "Rings a bell on the server shaming the mentioned person", 17 | usage: "", 18 | usageDelim: "", 19 | type: "commands", 20 | }; 21 | -------------------------------------------------------------------------------- /commands/Moderation/ban.js: -------------------------------------------------------------------------------- 1 | exports.run = async (client, msg, [member]) => { 2 | await msg.guild.ban(member); 3 | return msg.channel.send(`${member.user.tag} was banned.`); 4 | }; 5 | 6 | exports.conf = { 7 | enabled: true, 8 | selfbot: false, 9 | runIn: ["text"], 10 | aliases: ["b"], 11 | permLevel: 3, 12 | botPerms: ["BAN_MEMBERS"], 13 | requiredFuncs: [], 14 | requiredModules: [], 15 | }; 16 | 17 | exports.help = { 18 | name: "ban", 19 | description: "Bans a mentioned user. Currently does not require reason (no mod-log)", 20 | usage: "", 21 | usageDelim: "", 22 | type: "commands", 23 | }; 24 | -------------------------------------------------------------------------------- /commands/Moderation/kick.js: -------------------------------------------------------------------------------- 1 | exports.run = async (client, msg, [member]) => { 2 | await member.kick(); 3 | return msg.channel.send(`${member.user.tag} was kicked.`); 4 | }; 5 | 6 | exports.conf = { 7 | enabled: true, 8 | selfbot: false, 9 | runIn: ["text"], 10 | aliases: ["k"], 11 | permLevel: 2, 12 | botPerms: ["KICK_MEMBERS"], 13 | requiredFuncs: [], 14 | requiredModules: [], 15 | }; 16 | 17 | exports.help = { 18 | name: "kick", 19 | description: "Kicks a mentioned user. Currently does not require reason (no mod-log)", 20 | usage: "", 21 | usageDelim: "", 22 | type: "commands", 23 | }; 24 | -------------------------------------------------------------------------------- /commands/Fun/cat.js: -------------------------------------------------------------------------------- 1 | const snek = require("snekfetch"); 2 | 3 | exports.run = async (client, msg) => { 4 | const { body: { file } } = await snek.get("http://random.cat/meow"); 5 | return msg.channel.sendFile(file, `cat.${file.split(".")[2]}`); 6 | }; 7 | 8 | exports.conf = { 9 | enabled: true, 10 | runIn: ["text", "dm", "group"], 11 | aliases: ["meow", "nyan", "kitty", "kitten"], 12 | permLevel: 0, 13 | botPerms: ["ATTACH_FILES"], 14 | requiredFuncs: [], 15 | requiredSettings: [], 16 | }; 17 | 18 | exports.help = { 19 | name: "cat", 20 | description: "Gathers a cat image from random.cat website", 21 | usage: "", 22 | usageDelim: "", 23 | }; 24 | -------------------------------------------------------------------------------- /commands/Music/skip.js: -------------------------------------------------------------------------------- 1 | exports.run = async (client, msg) => { 2 | if (!msg.guild.voiceConnection) { 3 | throw `I am not connected in a voice channel, please add some songs to the queue first with ${msg.guild.settings.prefix}add`; 4 | } 5 | 6 | msg.guild.voiceConnection.dispatcher.end(); 7 | return msg.send("⏭ Skipped"); 8 | }; 9 | 10 | exports.conf = { 11 | enabled: true, 12 | runIn: ["text"], 13 | aliases: [], 14 | permLevel: 0, 15 | botPerms: [], 16 | requiredFuncs: [], 17 | }; 18 | 19 | exports.help = { 20 | name: "skip", 21 | description: "Skips the current song.", 22 | usage: "", 23 | usageDelim: "", 24 | extendedHelp: "", 25 | }; 26 | -------------------------------------------------------------------------------- /events/guildCreate.js: -------------------------------------------------------------------------------- 1 | const MIN_MEMBERS = 3; // Minimum amount of MEMBERS that must be in a guild. 2 | const BOT_RATIO = 70; // The percentage of Members that are Bots in a guild must be less than this. 3 | 4 | exports.run = async (client, guild) => { 5 | if (guild.memberCount < MIN_MEMBERS || guild.members.filter(u => u.user.bot).size * 100 / guild.memberCount >= BOT_RATIO) { 6 | await guild.owner.send(`I have left your server \`${guild.name}\` because it looks like a bot farm, or a server with only me and you.`).catch(() => null); 7 | guild.leave(); 8 | client.emit('verbose', `Left server that didn't meet Anti-Botfarm requirements. (${guild.id})`); 9 | } 10 | }; 11 | -------------------------------------------------------------------------------- /inhibitors/deleteCommand.js: -------------------------------------------------------------------------------- 1 | exports.run = (client, msg) => { 2 | if (msg.guildConf.deleteCommand === true) msg.delete(); 3 | return false; 4 | }; 5 | 6 | exports.conf = { 7 | enabled: true, 8 | requiredModules: [], 9 | }; 10 | 11 | exports.help = { 12 | name: "deleteCommand", 13 | type: "inhibitors", 14 | description: "Enables the ability for Guild/Bot owners to decide if they want all messages that initiate a command to be deleted.", 15 | }; 16 | 17 | // Uncompatible with Komada SettingGateway 18 | exports.init = (client) => { 19 | if (!client.funcs.confs.hasKey("deleteCommand")) { 20 | client.funcs.confs.addKey("deleteCommand", false); 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /commands/Self/nick.js: -------------------------------------------------------------------------------- 1 | exports.run = async (client, msg, [nick = ""]) => { 2 | await msg.member.setNickname(nick); 3 | const text = nick.length > 0 ? `Nickname changed to ${nick}` : "Nickname Cleared"; 4 | return msg.channel.send(text).then(m => m.delete(5000)); 5 | }; 6 | 7 | exports.conf = { 8 | enabled: true, 9 | selfbot: false, 10 | runIn: ["text"], 11 | aliases: [], 12 | permLevel: 10, 13 | botPerms: ["CHANGE_NICKNAME"], 14 | requiredFuncs: [], 15 | requiredModules: [], 16 | }; 17 | 18 | exports.help = { 19 | name: "nick", 20 | description: "Set's the bot's nickname", 21 | usage: "[nick:str]", 22 | usageDelim: "", 23 | type: "commands", 24 | }; 25 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "komada-pieces", 3 | "description": "This repository contains the various *Pieces* submitted by users and collaborators.", 4 | "scripts": { 5 | "test": "npx eslint commands providers functions inhibitors monitors packages", 6 | "lint": "npm run test -- --fix" 7 | }, 8 | "repository": { 9 | "type": "git", 10 | "url": "git+https://github.com/dirigeants/komada-pieces.git" 11 | }, 12 | "license": "ISC", 13 | "homepage": "https://github.com/dirigeants/komada-pieces#readme", 14 | "devDependencies": { 15 | "eslint": "^4.9.0", 16 | "eslint-config-airbnb-base": "^12.1.0", 17 | "eslint-plugin-import": "^2.7.0" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /commands/Fun/banner.js: -------------------------------------------------------------------------------- 1 | const figletAsync = require("util").promisify(require("figlet")); 2 | 3 | exports.run = async (client, msg, [banner]) => { 4 | const data = await figletAsync(banner); 5 | return msg.channel.send(data, { code: true }); 6 | }; 7 | 8 | exports.conf = { 9 | enabled: true, 10 | selfbot: false, 11 | runIn: ["text", "dm", "group"], 12 | aliases: [], 13 | permLevel: 0, 14 | botPerms: [], 15 | requiredFuncs: [], 16 | requiredModules: ["figlet"], 17 | }; 18 | 19 | exports.help = { 20 | name: "banner", 21 | description: "Creates an ASCII banner from the string you supply", 22 | usage: "", 23 | usageDelim: "", 24 | type: "commands", 25 | }; 26 | -------------------------------------------------------------------------------- /commands/Fun/dog.js: -------------------------------------------------------------------------------- 1 | const snek = require("snekfetch"); 2 | 3 | exports.run = async (client, msg) => { 4 | const { body: { message } } = await snek.get("https://dog.ceo/api/breeds/image/random"); 5 | return msg.channel.sendFile(message); 6 | }; 7 | 8 | exports.conf = { 9 | enabled: true, 10 | runIn: ["text", "dm", "group"], 11 | aliases: ["dawg", "duwg", "woof", "bork", "bark", "duggo", "doggo", "dug"], 12 | permLevel: 0, 13 | botPerms: ["ATTACH_FILES"], 14 | requiredFuncs: [], 15 | requiredSettings: [], 16 | }; 17 | 18 | exports.help = { 19 | name: "dog", 20 | description: "Gathers a dog image from random.dog website", 21 | usage: "", 22 | usageDelim: "", 23 | }; 24 | -------------------------------------------------------------------------------- /commands/Misc/spy.js: -------------------------------------------------------------------------------- 1 | const { inspect } = require("util"); 2 | 3 | exports.run = (client, msg, [ugc]) => { 4 | ugc = inspect(ugc, { depth: 0 }); 5 | return msg.channel.send(client.funcs.clean(client, ugc), { code: "js" }); 6 | }; 7 | 8 | exports.conf = { 9 | enabled: true, 10 | selfbot: false, 11 | runIn: ["text", "dm", "group"], 12 | aliases: [], 13 | permLevel: 3, 14 | botPerms: [], 15 | requiredFuncs: [], 16 | requiredModules: [], 17 | }; 18 | 19 | exports.help = { 20 | name: "spy", 21 | description: "Spies on a user, guild, or channel", 22 | usage: "", 23 | usageDelim: "", 24 | type: "commands", 25 | }; 26 | -------------------------------------------------------------------------------- /commands/Fun/catfacts.js: -------------------------------------------------------------------------------- 1 | const snekfetch = require("snekfetch"); 2 | 3 | exports.run = async (client, msg) => snekfetch.get("https://catfact.ninja/fact") 4 | .then(res => msg.channel.send(`📢 **Catfact:** *${res.body.fact}*`)); 5 | 6 | exports.conf = { 7 | enabled: true, 8 | selfbot: false, 9 | runIn: ["text", "dm", "group"], 10 | aliases: ["catfact", "kittenfact"], 11 | permLevel: 0, 12 | botPerms: [], 13 | requiredFuncs: [], 14 | requiredModules: ["snekfetch"], 15 | }; 16 | 17 | exports.help = { 18 | name: "catfacts", 19 | description: "Let me tell you a misterious cat fact.", 20 | usage: "", 21 | usageDelim: "", 22 | type: "commands", 23 | }; 24 | -------------------------------------------------------------------------------- /commands/Fun/yomomma.js: -------------------------------------------------------------------------------- 1 | const request = require("snekfetch"); 2 | 3 | exports.run = async (client, msg) => { 4 | const res = await request.get("http://api.yomomma.info").then(data => JSON.parse(data.text)); 5 | return msg.channel.send(`📢 **Yomomma joke:** *${res.joke}*`); 6 | }; 7 | 8 | exports.conf = { 9 | enabled: true, 10 | selfbot: false, 11 | runIn: ["text", "dm", "group"], 12 | aliases: ["yomama"], 13 | permLevel: 0, 14 | botPerms: [], 15 | requiredFuncs: [], 16 | requiredModules: ["snekfetch"], 17 | }; 18 | 19 | exports.help = { 20 | name: "yomomma", 21 | description: "Yo momma is so fat, yo.", 22 | usage: "", 23 | usageDelim: "", 24 | type: "commands", 25 | }; 26 | -------------------------------------------------------------------------------- /commands/Moderation/softban.js: -------------------------------------------------------------------------------- 1 | exports.run = async (client, msg, [member, days = 1]) => { 2 | await msg.guild.ban(member, days); 3 | await msg.guild.unban(member); 4 | return msg.channel.send(`${member.tag} was softbanned.`); 5 | }; 6 | 7 | exports.conf = { 8 | enabled: true, 9 | selfbot: false, 10 | runIn: ["text"], 11 | aliases: ["sb"], 12 | permLevel: 3, 13 | botPerms: ["BAN_MEMBERS"], 14 | requiredFuncs: [], 15 | requiredModules: [], 16 | }; 17 | 18 | exports.help = { 19 | name: "softban", 20 | description: "Bans a mentioned user. Currently does not require reason (no mod-log)", 21 | usage: " [days:int{1,7}]", 22 | usageDelim: "", 23 | type: "commands", 24 | }; 25 | -------------------------------------------------------------------------------- /functions/addCommas.js: -------------------------------------------------------------------------------- 1 | /* 2 | This piece keeps here for compatibility. However, Number.toLocaleString() does pretty much the same. 3 | Check it here: https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Number/toLocaleString 4 | */ 5 | module.exports = (nStr) => { 6 | nStr += ""; 7 | const x = nStr.split("."); 8 | let x1 = x[0]; 9 | const x2 = x.length > 1 ? `.${x[1]}` : ""; 10 | const rgx = /(\d+)(\d{3})/; 11 | while (rgx.test(x1)) { 12 | x1 = x1.replace(rgx, "$1,$2"); 13 | } 14 | return x1 + x2; 15 | }; 16 | 17 | module.exports.conf = { requiredModules: [] }; 18 | module.exports.help = { 19 | name: "addCommas", 20 | type: "functions", 21 | description: "Add commas in every three characters.", 22 | }; 23 | -------------------------------------------------------------------------------- /inhibitors/requiredProviders.js: -------------------------------------------------------------------------------- 1 | exports.conf = { 2 | enabled: true, 3 | spamProtection: false, 4 | priority: 6, 5 | }; 6 | 7 | exports.help = { 8 | name: "requiredProviders", 9 | type: "inhibitors", 10 | description: "Checks if specific providers are loaded.", 11 | }; 12 | 13 | /* eslint-disable no-prototype-builtins */ 14 | exports.run = (client, msg, cmd) => { 15 | if (!cmd.conf.requiredProviders || cmd.conf.requiredProviders.length === 0) return false; 16 | const providers = cmd.conf.requiredProviders.filter(provider => !client.providers.has(provider)); 17 | if (providers.length > 0) return `The client is missing the **${providers.join(", ")}** provider${providers.length > 1 ? "s" : ""} and cannot run.`; 18 | return false; 19 | }; 20 | -------------------------------------------------------------------------------- /monitors/nomention.js: -------------------------------------------------------------------------------- 1 | exports.run = (client, msg) => { 2 | if (!msg.guild || msg.author.id === client.user.id) return; 3 | const user = `${msg.author.username}#${msg.author.discriminator} (${msg.author.id})`; 4 | const channel = `#${msg.channel.name} (${msg.channel.id}) from ${msg.guild.name}`; 5 | if (msg.mentions.everyone) client.emit("log", `${user} mentioned everyone in ${channel}`); 6 | else if (msg.mentions.users.has(client.user.id)) client.emit("log", `${user} mentioned you in ${channel}`); 7 | }; 8 | 9 | exports.conf = { 10 | enabled: true, 11 | requiredModules: [], 12 | }; 13 | 14 | exports.help = { 15 | name: "nomention", 16 | type: "monitors", 17 | description: "Did you get ghost-mentioned? This monitor is for you.", 18 | }; 19 | -------------------------------------------------------------------------------- /commands/Music/pause.js: -------------------------------------------------------------------------------- 1 | exports.run = async (client, msg) => { 2 | if (!msg.guild.voiceConnection) { 3 | throw `I am not connected in a voice channel, please add some songs to the queue first with ${msg.guild.settings.prefix}add`; 4 | } 5 | if (msg.guild.voiceConnection.dispatcher.paused) { 6 | return msg.send("The stream is already paused."); 7 | } 8 | msg.guild.voiceConnection.dispatcher.pause(); 9 | return msg.send("⏸ Paused"); 10 | }; 11 | 12 | exports.conf = { 13 | enabled: true, 14 | runIn: ["text"], 15 | aliases: [], 16 | permLevel: 0, 17 | botPerms: [], 18 | requiredFuncs: [], 19 | }; 20 | 21 | exports.help = { 22 | name: "pause", 23 | description: "Pauses the current song.", 24 | usage: "", 25 | usageDelim: "", 26 | extendedHelp: "", 27 | }; 28 | -------------------------------------------------------------------------------- /commands/Fun/choice.js: -------------------------------------------------------------------------------- 1 | exports.run = async (client, msg, [...choices]) => { 2 | const validChoices = choices.filter(x => x); 3 | return msg.reply(validChoices.length === 1 ? 4 | "You only gave me one choice, dummy." : 5 | `I think you should go with "${choices[Math.floor(Math.random() * choices.length)]}"`); 6 | }; 7 | 8 | exports.conf = { 9 | enabled: true, 10 | selfbot: false, 11 | runIn: ["text", "dm", "group"], 12 | aliases: ["choose", "decide"], 13 | permLevel: 0, 14 | botPerms: [], 15 | requiredFuncs: [], 16 | requiredModules: [], 17 | }; 18 | 19 | exports.help = { 20 | name: "choice", 21 | description: "Makes a decision for you given some choices.", 22 | usage: " [...]", 23 | usageDelim: "|", 24 | type: "commands", 25 | }; 26 | -------------------------------------------------------------------------------- /commands/Music/resume.js: -------------------------------------------------------------------------------- 1 | exports.run = async (client, msg) => { 2 | if (!msg.guild.voiceConnection) { 3 | throw `I am not connected in a voice channel, please add some songs to the queue first with ${msg.guild.settings.prefix}add`; 4 | } 5 | if (msg.guild.voiceConnection.dispatcher.paused === false) { 6 | return msg.send("The stream is not paused."); 7 | } 8 | 9 | msg.guild.voiceConnection.dispatcher.resume(); 10 | return msg.send("▶ Resumed"); 11 | }; 12 | 13 | exports.conf = { 14 | enabled: true, 15 | runIn: ["text"], 16 | aliases: [], 17 | permLevel: 0, 18 | botPerms: [], 19 | requiredFuncs: [], 20 | }; 21 | 22 | exports.help = { 23 | name: "resume", 24 | description: "Resumes the current song.", 25 | usage: "", 26 | usageDelim: "", 27 | extendedHelp: "", 28 | }; 29 | -------------------------------------------------------------------------------- /inhibitors/commandSlowMode.js: -------------------------------------------------------------------------------- 1 | const slowmode = new Map(); 2 | const timers = []; 3 | const ratelimit = 1250; 4 | 5 | exports.run = (client, msg) => { 6 | const slowmodeLevel = msg.author.id; 7 | const entry = slowmode.get(slowmodeLevel); 8 | if (!entry) { slowmode.set(slowmodeLevel, true); } 9 | if (timers[slowmodeLevel]) clearTimeout(timers[slowmodeLevel]); 10 | timers[slowmodeLevel] = setTimeout(() => { 11 | slowmode.delete(slowmodeLevel); 12 | delete timers[slowmodeLevel]; 13 | }, ratelimit); 14 | 15 | return !!entry; 16 | }; 17 | 18 | exports.conf = { 19 | enabled: true, 20 | requiredModules: [], 21 | }; 22 | 23 | exports.help = { 24 | name: "commandSlowMode", 25 | type: "inhibitors", 26 | description: "Slows down the usage of commands, which defaults to per user.", 27 | }; 28 | -------------------------------------------------------------------------------- /commands/Fun/fml.js: -------------------------------------------------------------------------------- 1 | const request = require("snekfetch"); 2 | const HTMLParser = require("fast-html-parser"); 3 | 4 | exports.run = async (client, msg) => { 5 | const { text: html } = await request.get("http://www.fmylife.com/random"); 6 | const root = HTMLParser.parse(html); 7 | const article = root.querySelector(".block a"); 8 | return msg.channel.send(article.text); 9 | }; 10 | 11 | exports.conf = { 12 | enabled: true, 13 | selfbot: false, 14 | runIn: ["text", "dm", "group"], 15 | aliases: [], 16 | permLevel: 0, 17 | botPerms: [], 18 | requiredFuncs: [], 19 | requiredModules: ["snekfetch", "fast-html-parser"], 20 | }; 21 | 22 | exports.help = { 23 | name: "fml", 24 | description: "Grabs random 'Fuck My Life' quote from the web.", 25 | usage: "", 26 | usageDelim: "", 27 | type: "commands", 28 | }; 29 | -------------------------------------------------------------------------------- /commands/System/prune.js: -------------------------------------------------------------------------------- 1 | exports.run = async (client, msg, [amount]) => { 2 | let messages = await msg.channel.messages.fetch({ limit: amount }); 3 | messages = messages.filter(m => m.author.id === client.user.id); 4 | if (client.config.selfbot) return messages.forEach(m => m.delete().catch((e) => { throw new Error(e); })); 5 | return msg.channel.bulkDelete(messages); 6 | }; 7 | 8 | exports.conf = { 9 | enabled: true, 10 | selfbot: true, 11 | runIn: ["text", "dm", "group"], 12 | aliases: [], 13 | permLevel: 0, 14 | botPerms: [], 15 | requiredFuncs: [], 16 | requiredModules: [], 17 | }; 18 | 19 | exports.help = { 20 | name: "prune", 21 | description: "This will remove X amount of messages sent in a channel sent by yourself.", 22 | usage: "", 23 | usageDelim: " ", 24 | type: "commands", 25 | }; 26 | -------------------------------------------------------------------------------- /functions/splitText.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Find the last space of a string and cuts it down to a manageable size for use in Discord. 3 | * @param {string} string The string to truncate. 4 | * @param {number} length The desired length for the string. 5 | * @param {string} [endBy=" "] The character in which the resolved string should end with. 6 | * @return {string} 7 | */ 8 | module.exports = (string, length, endBy = " ") => { 9 | const x = string.substring(0, length).lastIndexOf(endBy); 10 | const pos = x === -1 ? length : x; 11 | return string.substring(0, pos); 12 | }; 13 | 14 | module.exports.conf = { requiredModules: [] }; 15 | module.exports.help = { 16 | name: "splitText", 17 | type: "functions", 18 | description: "Find the last space of a string and cuts it down to a manageable size for use in Discord.", 19 | }; 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | *.pid.lock 11 | 12 | # Directory for instrumented libs generated by jscoverage/JSCover 13 | lib-cov 14 | 15 | # Coverage directory used by tools like istanbul 16 | coverage 17 | 18 | # nyc test coverage 19 | .nyc_output 20 | 21 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 22 | .grunt 23 | 24 | # node-waf configuration 25 | .lock-wscript 26 | 27 | # Compiled binary addons (http://nodejs.org/api/addons.html) 28 | build/Release 29 | 30 | # Dependency directories 31 | node_modules 32 | jspm_packages 33 | 34 | # Optional npm cache directory 35 | .npm 36 | 37 | # Optional eslint cache 38 | .eslintcache 39 | 40 | # Optional REPL history 41 | .node_repl_history 42 | 43 | # Output of 'npm pack' 44 | *.tgz 45 | -------------------------------------------------------------------------------- /commands/System/purge.js: -------------------------------------------------------------------------------- 1 | exports.run = async (client, msg, [user = client.user, amount]) => { 2 | let messages = await msg.channel.messages.fetch({ limit: amount }); 3 | messages = messages.filter(m => m.author.id === user.id); 4 | if (client.config.selfbot) return messages.forEach(m => m.delete().catch((e) => { throw new Error(e); })); 5 | return msg.channel.bulkDelete(messages); 6 | }; 7 | 8 | exports.conf = { 9 | enabled: true, 10 | runIn: ["text", "dm", "group"], 11 | selfbot: false, 12 | aliases: [], 13 | permLevel: 0, 14 | botPerms: ["MANAGE_MESSAGES"], 15 | requiredFuncs: [], 16 | requiredModules: [], 17 | }; 18 | 19 | exports.help = { 20 | name: "purge", 21 | description: "This will remove X amount of messages sent in a channel, or by Y user.", 22 | usage: "[user:mention] ", 23 | usageDelim: " ", 24 | type: "commands", 25 | }; 26 | -------------------------------------------------------------------------------- /commands/Fun/card.js: -------------------------------------------------------------------------------- 1 | const suits = ["♠️", "♦", "♥️", "♠️"]; 2 | const ranks = ["A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K"]; 3 | 4 | exports.run = (client, msg, [num]) => { 5 | const numCards = num; 6 | const lines = []; 7 | 8 | for (let i = 0; i < numCards; ++i) { 9 | lines.push(`**${ranks[Math.floor(Math.random() * ranks.length)]}**${suits[Math.floor(Math.random() * suits.length)]}`); 10 | } 11 | 12 | return msg.channel.send(lines.join(", ")); 13 | }; 14 | 15 | exports.conf = { 16 | enabled: true, 17 | selfbot: false, 18 | runIn: ["text", "dm", "group"], 19 | aliases: [], 20 | permLevel: 0, 21 | botPerms: [], 22 | requiredFuncs: [], 23 | requiredModules: [], 24 | }; 25 | 26 | exports.help = { 27 | name: "card", 28 | description: "Draws some random cards from a deck.", 29 | usage: "", 30 | usageDelim: "", 31 | type: "commands", 32 | }; 33 | -------------------------------------------------------------------------------- /commands/System/exec.js: -------------------------------------------------------------------------------- 1 | const exec = require("util").promisify(require("child_process").exec); 2 | 3 | exports.run = async (client, msg, [input]) => { 4 | const result = await exec(input).catch((err) => { throw err; }); 5 | 6 | const output = result.stdout ? `**\`OUTPUT\`**${"```sh"}\n${result.stdout}\n${"```"}` : ""; 7 | const outerr = result.stderr ? `**\`ERROR\`**${"```sh"}\n${result.stderr}\n${"```"}` : ""; 8 | return msg.channel.send([output, outerr].join("\n")); 9 | }; 10 | 11 | exports.conf = { 12 | enabled: true, 13 | selfbot: false, 14 | runIn: ["text", "dm", "group"], 15 | aliases: [], 16 | permLevel: 10, 17 | botPerms: [], 18 | requiredFuncs: [], 19 | requiredModules: [], 20 | }; 21 | 22 | exports.help = { 23 | name: "exec", 24 | description: "Execute commands in the terminal, use with EXTREME CAUTION.", 25 | usage: "", 26 | usageDelim: "", 27 | extendedHelp: "", 28 | type: "commands", 29 | }; 30 | -------------------------------------------------------------------------------- /commands/Music/time.js: -------------------------------------------------------------------------------- 1 | const moment = require("moment"); 2 | require("moment-duration-format"); 3 | 4 | exports.run = async (client, msg) => { 5 | if (!msg.guild.voiceConnection) { 6 | throw `I am not connected in a voice channel, please add some songs to the queue first with ${msg.guild.settings.prefix}add`; 7 | } 8 | 9 | const handler = client.queue.get(msg.guild.id); 10 | if (!handler || handler.playing === false) throw "I am not playing music."; 11 | return msg.send(`🕰 Time remaining: ${moment.duration((handler.songs[0].seconds * 1000) - msg.guild.voiceConnection.dispatcher.time).format("h:mm:ss", { trim: false })}`); 12 | }; 13 | 14 | exports.conf = { 15 | enabled: true, 16 | runIn: ["text"], 17 | aliases: [], 18 | permLevel: 0, 19 | botPerms: [], 20 | requiredFuncs: [], 21 | }; 22 | 23 | exports.help = { 24 | name: "time", 25 | description: "Check when is the song going to end.", 26 | usage: "", 27 | usageDelim: "", 28 | extendedHelp: "", 29 | }; 30 | -------------------------------------------------------------------------------- /commands/Self/presence.js: -------------------------------------------------------------------------------- 1 | // Big thanks to AoDude, Faith and cyberiumshadow 2 | // And kyra#2490 for rewritting this. 3 | exports.run = async (client, msg, [type, status = "online", ...game]) => { 4 | game = game.length ? game.join(" ") : null; 5 | if (type === "status") { 6 | await client.user.setStatus(status); 7 | return msg.channel.send(`Status changed to ***${status}***`); 8 | } 9 | await client.user.setGame(game); 10 | return msg.channel.send(`${game ? `Game changed to ***${game}***` : "Game cleared"}`); 11 | }; 12 | 13 | exports.conf = { 14 | enabled: true, 15 | selfbot: false, 16 | runIn: ["text", "dm", "group"], 17 | aliases: [], 18 | permLevel: 10, 19 | botPerms: [], 20 | requiredFuncs: [], 21 | requiredModules: [], 22 | }; 23 | 24 | exports.help = { 25 | name: "presence", 26 | description: "Set either your 'status' or your 'game' by using this command", 27 | usage: " [online|idle|invisible|dnd] [game:str]", 28 | usageDelim: " ", 29 | type: "commands", 30 | }; 31 | -------------------------------------------------------------------------------- /commands/Music/queue.js: -------------------------------------------------------------------------------- 1 | exports.run = (client, msg) => { 2 | const handler = client.queue.get(msg.guild.id); 3 | if (!handler) throw `Add some songs to the queue first with ${msg.guild.settings.prefix}add`; 4 | 5 | const output = []; 6 | for (let i = 0; i < Math.min(handler.songs.length, 15); i++) { 7 | output.push(`${i + 1}. ${handler.songs[i].title} - Requested by: ${handler.songs[i].requester}`); 8 | } 9 | 10 | return msg.channel.send([ 11 | `🗒 __**${msg.guild.name}'s Music Queue:**__ Currently **${output.length}** songs queued ${(handler.songs.length > 15 ? "*[Only next 15 shown]*" : "")}`, 12 | `${"```"}${output.join("\n")}${"```"}`, 13 | ].join("\n")); 14 | }; 15 | 16 | exports.conf = { 17 | enabled: true, 18 | runIn: ["text"], 19 | aliases: [], 20 | permLevel: 0, 21 | botPerms: [], 22 | requiredFuncs: [], 23 | }; 24 | 25 | exports.help = { 26 | name: "queue", 27 | description: "Displays the music queue.", 28 | usage: "", 29 | usageDelim: "", 30 | extendedHelp: "", 31 | }; 32 | -------------------------------------------------------------------------------- /commands/Tools/price.js: -------------------------------------------------------------------------------- 1 | const request = require("snekfetch"); 2 | 3 | exports.run = async (client, msg, [coin, currency]) => { 4 | const c1 = coin.toUpperCase(); 5 | const c2 = currency.toUpperCase(); 6 | try { 7 | const res = await request.get(`https://min-api.cryptocompare.com/data/price?fsym=${c1}&tsyms=${c2}`); 8 | await msg.reply(!res.body[c2] ? 9 | "There was an error, please make sure you specified an appropriate coin and currency." : 10 | `Current ${c1} price is ${res.body[c2]} ${c2}`); 11 | } catch (e) { 12 | msg.reply("There was an error, please make sure you specified an appropriate coin and currency."); 13 | } 14 | }; 15 | exports.conf = { 16 | enabled: true, 17 | runIn: ["text", "dm", "group"], 18 | aliases: [], 19 | permLevel: 0, 20 | botPerms: [], 21 | requiredFuncs: [], 22 | }; 23 | 24 | exports.help = { 25 | name: "price", 26 | description: "Returns the current price of a Cryptocurrency Coin.", 27 | usage: " ", 28 | usageDelim: " ", 29 | type: "commands", 30 | }; 31 | -------------------------------------------------------------------------------- /commands/Music/join.js: -------------------------------------------------------------------------------- 1 | exports.run = async (client, msg) => { 2 | const { voiceChannel } = msg.member; 3 | if (!voiceChannel) { 4 | throw "You are not conected in a voice channel."; 5 | } 6 | 7 | const permissions = voiceChannel.permissionsFor(msg.guild.me); 8 | if (permissions.has("CONNECT") === false) { 9 | throw "I do not have enough permissions to connect to your voice channel. I am missing the CONNECT permission."; 10 | } 11 | if (permissions.has("SPEAK") === false) { 12 | throw "I can connect... but not speak. Please turn on this permission so I can emit music."; 13 | } 14 | 15 | await voiceChannel.join(); 16 | return msg.send(`Successfully connected to the voice channel ${voiceChannel}`); 17 | }; 18 | 19 | exports.conf = { 20 | enabled: true, 21 | runIn: ["text"], 22 | aliases: [], 23 | permLevel: 0, 24 | botPerms: [], 25 | requiredFuncs: [], 26 | }; 27 | 28 | exports.help = { 29 | name: "join", 30 | description: "Joins the message author's voice channel.", 31 | usage: "", 32 | usageDelim: "", 33 | extendedHelp: "", 34 | }; 35 | -------------------------------------------------------------------------------- /functions/hierarchyCheck.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Check if user is higher in guild's role hierarchy 3 | * @param {Client} client The Client object. 4 | * @param {User} executor The User instance of the user who is executing the command. 5 | * @param {User} target The User instance of the user targetted. 6 | * @param {Guild} guild The Guild instance where the command is being executed. 7 | * @returns {Promise} 8 | */ 9 | module.exports = async (client, executor, target, guild = null) => { 10 | if (guild) { 11 | const executorMember = await guild.members.resolve(await client.users.resolve(executor.id)); 12 | const targetMember = await guild.members.resolve(await client.users.resolve(target.id)); 13 | 14 | return executorMember.roles.highest.position > targetMember.roles.highest.position; 15 | } 16 | 17 | return false; 18 | }; 19 | 20 | 21 | module.exports.conf = { requiredModules: [] }; 22 | module.exports.help = { 23 | name: "hierarchyCheck", 24 | type: "functions", 25 | description: "Checks to see that command executor is higher on guild's hierarchy than command target.", 26 | }; 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Évelyne Lachance 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 | -------------------------------------------------------------------------------- /commands/Fun/8ball.js: -------------------------------------------------------------------------------- 1 | const answers = ["Maybe.", "Certainly not.", "I hope so.", "Not in your wildest dreams.", "There is a good chance.", "Quite likely.", "I think so.", "I hope not.", "I hope so.", "Never!", "Fuhgeddaboudit.", "Ahaha! Really?!?", "Pfft.", "Sorry, bucko.", "Hell, yes.", "Hell to the no.", "The future is bleak.", "The future is uncertain.", "I would rather not say.", "Who cares?", "Possibly.", "Never, ever, ever.", "There is a small chance.", "Yes!"]; 2 | 3 | exports.run = (client, msg) => msg.reply(msg.content.endsWith("?") ? 4 | `🎱 ${answers[Math.floor(Math.random() * answers.length)]}` : 5 | "🎱 That doesn't look like a question, try again please."); 6 | 7 | exports.conf = { 8 | enabled: true, 9 | selfbot: false, 10 | runIn: ["text", "dm", "group"], 11 | aliases: ["8", "magic", "8ball", "mirror"], 12 | permLevel: 0, 13 | botPerms: [], 14 | requiredFuncs: [], 15 | requiredModules: [], 16 | }; 17 | 18 | exports.help = { 19 | name: "magic8", 20 | description: "Magic 8-Ball, does exactly what the toy does. (Results may vary)", 21 | usage: "", 22 | usageDelim: "", 23 | type: "commands", 24 | }; 25 | -------------------------------------------------------------------------------- /functions/points.js: -------------------------------------------------------------------------------- 1 | const provider = "json"; 2 | 3 | module.exports = async (client, user, action) => { 4 | let row = await this.provider.get("quiz", user); 5 | if (!row) { 6 | await this.provider.create("quiz", user, { points: 0 }); 7 | row = { id: user, points: 0 }; 8 | } 9 | let { points } = row; 10 | switch (action) { 11 | case "add": 12 | points++; 13 | break; 14 | case "remove": 15 | Math.max(0, points--); 16 | break; 17 | case "reset": 18 | points = 0; 19 | break; 20 | // no default 21 | } 22 | await this.provider.update("quiz", user, { points }); 23 | }; 24 | 25 | module.exports.conf = { requiredModules: [] }; 26 | module.exports.help = { 27 | name: "points", 28 | type: "functions", 29 | description: "Adds a point system for users accesible through a database.", 30 | }; 31 | 32 | module.exports.init = async (client) => { 33 | if (client.providers.has(provider)) this.provider = client.providers.get(provider); 34 | else throw new Error(`The Provider ${provider} does not seem to exist.`); 35 | if (!(await this.provider.hasTable("quiz"))) { 36 | const SQLCreate = ["id TEXT NOT NULL UNIQUE", "points INTEGER NOT NULL DEFAULT 0"]; 37 | await this.provider.createTable("quiz", SQLCreate); 38 | } 39 | }; 40 | -------------------------------------------------------------------------------- /commands/Moderation/check.js: -------------------------------------------------------------------------------- 1 | exports.init = (client) => { 2 | // Uncompatible with Komada SettingGateway. 3 | const keycheck = client.funcs.confs.hasKey("minAccAge"); 4 | if (!keycheck) client.funcs.confs.addKey("minAccAge", 1800000); 5 | }; 6 | 7 | exports.run = (client, msg) => { 8 | const accAge = msg.guildConf.minAccAge; 9 | const mtime = msg.createdTimestamp; 10 | const check = msg.guild.members.filter(m => (mtime - m.user.createdTimestamp) <= accAge); 11 | if (check.size > 0) { 12 | const result = check.map(u => `${u.user.tag}, Created:${((mtime - u.user.createdTimestamp) / 1000 / 60).toFixed(0)} min(s) ago`).join("\n"); 13 | return msg.reply(`The following users are less than the Minimum Account Age: \n \`\`\`js\n${result}\n\`\`\` `); 14 | } 15 | return msg.reply("No users less than Minimum Account Age were found in the guild."); 16 | }; 17 | 18 | exports.conf = { 19 | enabled: true, 20 | selfbot: false, 21 | runIn: ["text"], 22 | aliases: [], 23 | permLevel: 3, 24 | botPerms: [], 25 | requiredFuncs: [], 26 | requiredModules: [], 27 | }; 28 | 29 | exports.help = { 30 | name: "check", 31 | description: "Checks the guild for any user accounts younger than the minimum account age.", 32 | usage: "", 33 | usageDelim: "", 34 | type: "commands", 35 | }; 36 | -------------------------------------------------------------------------------- /commands/Fun/urban.js: -------------------------------------------------------------------------------- 1 | const request = require("snekfetch"); 2 | 3 | exports.run = async (client, msg, [search, resultNum = 0]) => { 4 | const url = `http://api.urbandictionary.com/v0/define?term=${search}`; 5 | const body = await request.get(url).then(data => JSON.parse(data.text)); 6 | if (resultNum > 1) resultNum--; 7 | 8 | const result = body.list[resultNum]; 9 | if (!result) return msg.channel.send("No entry found."); 10 | const wdef = result.definition.length > 1000 11 | ? `${client.funcs.splitText(result.definition, 1000)}...` 12 | : result.definition; 13 | const definition = [ 14 | `**Word:** ${search}`, 15 | "", 16 | `**Definition:** ${resultNum + 1} out of ${body.list.length}\n_${wdef}_`, 17 | "", 18 | `**Example:**\n${result.example}`, 19 | `<${result.permalink}>`, 20 | ].join("\n"); 21 | 22 | return msg.channel.send(definition); 23 | }; 24 | 25 | exports.conf = { 26 | enabled: true, 27 | selfbot: false, 28 | runIn: ["text", "dm", "group"], 29 | aliases: [], 30 | permLevel: 0, 31 | botPerms: [], 32 | requiredFuncs: ["splitText"], 33 | requiredModules: ["snekfetch"], 34 | }; 35 | 36 | exports.help = { 37 | name: "urban", 38 | description: "Searches the Urban Dictionary library for a definition to the search term.", 39 | usage: " [resultNum:int]", 40 | usageDelim: ", ", 41 | type: "commands", 42 | }; 43 | -------------------------------------------------------------------------------- /commands/Music/add.js: -------------------------------------------------------------------------------- 1 | const yt = require("ytdl-core"); 2 | const getInfoAsync = require("util").promisify(yt.getInfo); 3 | 4 | const YouTubeRegExp = /^(?:https?:\/\/)?(?:www\.)?(?:youtube\.com\/\S*(?:(?:\/e(?:mbed)?)?\/|watch\/?\?(?:\S*?&?v=))|youtu\.be\/)([\w-]{11})(?:[^\w-]|$)/; 5 | 6 | exports.run = async (client, msg, [song]) => { 7 | const id = YouTubeRegExp.exec(song); 8 | if (id === null) throw "You must provide a valid YouTube URL."; 9 | const info = await getInfoAsync(`https://youtu.be/${id[1]}`); 10 | 11 | if (client.queue.has(msg.guild.id) === false) { 12 | client.queue.set(msg.guild.id, { 13 | playing: false, 14 | songs: [], 15 | }); 16 | } 17 | 18 | client.queue.get(msg.guild.id).songs.push({ 19 | url: song, 20 | title: info.title, 21 | seconds: info.length_seconds, 22 | requester: msg.author.username, 23 | }); 24 | 25 | return msg.send(`🎵 Added **${info.title}** to the queue 🎶`); 26 | }; 27 | 28 | exports.conf = { 29 | enabled: true, 30 | runIn: ["text"], 31 | aliases: [], 32 | permLevel: 0, 33 | botPerms: [], 34 | requiredFuncs: [], 35 | }; 36 | 37 | exports.help = { 38 | name: "add", 39 | description: "Adds a song the the queue.", 40 | usage: "", 41 | usageDelim: "", 42 | extendedHelp: "", 43 | }; 44 | 45 | exports.init = (client) => { 46 | client.queue = new client.methods.Collection(); 47 | }; 48 | -------------------------------------------------------------------------------- /inhibitors/cooldown.js: -------------------------------------------------------------------------------- 1 | exports.run = (client, msg, cmd) => { 2 | if (cmd.conf.cooldown === 0) return false; 3 | const cooldown = cmd.conf.cooldown || 1000; 4 | if (!cmd.cooldown) cmd.cooldown = new Map(); 5 | const remaining = (cooldown - (Date.now() - cmd.cooldown.get(msg.author.id))) / 1000; 6 | if (cmd.cooldown.has(msg.author.id)) return `You are being ratelimited. Wait until ${remaining}s`; 7 | cmd.cooldown.set(msg.author.id, Date.now()); 8 | setTimeout(() => cmd.cooldown.delete(msg.author.id), cooldown); 9 | return false; 10 | }; 11 | 12 | exports.conf = { 13 | enabled: true, 14 | priority: 5, 15 | }; 16 | 17 | exports.help = { 18 | name: "cooldown", 19 | type: "inhibitors", 20 | description: "[v2] Add per-command cooldown to single users.", 21 | }; 22 | 23 | /* 24 | Implemented in Komada Indev as a Core Inhibitor. 25 | 26 | *Usage* 27 | In https://komada.js.org/creating-commands.html, you have this object in every command: 28 | exports.conf = { 29 | enabled: true, 30 | runIn: ["text"], 31 | aliases: [], 32 | permLevel: 0, 33 | botPerms: [], 34 | requiredFuncs: [], 35 | }; 36 | Then, you add: cooldown: {time} 37 | In which, {time} is the amount of time in milliseconds you want to ratelimit the command. 38 | To disable the cooldown in a certain command, set {cooldown} as 0. 39 | */ 40 | -------------------------------------------------------------------------------- /commands/Tools/twitch.js: -------------------------------------------------------------------------------- 1 | const request = require("snekfetch"); 2 | const moment = require("moment"); 3 | 4 | const clientID = "CLIENT_ID_HERE"; // https://dev.twitch.tv/docs/v5/guides/authentication/ 5 | 6 | /* eslint-disable no-underscore-dangle */ 7 | exports.run = async (client, msg, [twitchName]) => { 8 | try { 9 | const { body } = await request.get(`https://api.twitch.tv/kraken/channels/${twitchName}?client_id=${clientID}`); 10 | const creationDate = moment(body.created_at).format("DD-MM-YYYY"); 11 | const embed = new client.methods.Embed() 12 | .setColor(6570406) 13 | .setThumbnail(body.logo) 14 | .setAuthor(body.display_name, "https://i.imgur.com/OQwQ8z0.jpg", body.url) 15 | .addField("Account ID", body._id, true) 16 | .addField("Followers", body.followers, true) 17 | .addField("Created On", creationDate, true) 18 | .addField("Channel Views", body.views, true); 19 | return msg.channel.send({ embed }); 20 | } catch (e) { 21 | return msg.reply("Unable to find account. Did you spell it correctly?"); 22 | } 23 | }; 24 | 25 | exports.conf = { 26 | enabled: true, 27 | selfbot: false, 28 | runIn: ["text", "dm", "group"], 29 | aliases: [], 30 | permLevel: 0, 31 | botPerms: [], 32 | requiredFuncs: [], 33 | requiredModules: [], 34 | }; 35 | 36 | exports.help = { 37 | name: "twitch", 38 | description: "Returns information on a Twitch.tv Account", 39 | usage: "", 40 | usageDelim: "", 41 | type: "commands", 42 | }; 43 | -------------------------------------------------------------------------------- /commands/Fun/quiz.js: -------------------------------------------------------------------------------- 1 | const quiz = [ 2 | { q: "What colour is the sky?", a: ["blue"] }, 3 | { q: "Name a soft drink brand", a: ["pepsi", "coke", "rc", "7up", "sprite", "mountain dew"] }, 4 | { q: "Name a programming language", a: ["actionscript", "coffeescript", "c", "c++", "basic", "python", "perl", "javascript", "dotnet", "lua", "crystal", "go", "d", "php", "ruby", "rust", "dart"] }, 5 | { q: "Who's your favourite server owner?", a: ["root", "luckyevie", "evie", "eslachance"] }, 6 | { q: "Who's a good boy? **Who's a good boy???**", a: ["you are", "i am"] }, 7 | ]; 8 | 9 | const options = { 10 | max: 1, 11 | time: 30000, 12 | errors: ["time"], 13 | }; 14 | 15 | exports.run = async (client, msg) => { 16 | const item = quiz[Math.floor(Math.random() * quiz.length)]; 17 | await msg.channel.send(item.q); 18 | try { 19 | const collected = await msg.channel.awaitMessages(answer => item.a.includes(answer.content.toLowerCase()), options); 20 | const winnerMessage = collected.first(); 21 | await client.funcs.points(client, winnerMessage.author.id, "add"); 22 | return msg.channel.send(`We have a winner! *${winnerMessage.author.username}* had a right answer with \`${winnerMessage.content}\`!`); 23 | } catch (_) { 24 | return msg.channel.send("Seems no one got it! Oh well."); 25 | } 26 | }; 27 | 28 | exports.conf = { 29 | enabled: true, 30 | selfbot: false, 31 | runIn: ["text", "dm", "group"], 32 | aliases: [], 33 | permLevel: 0, 34 | botPerms: [], 35 | requiredFuncs: ["points"], 36 | requiredModules: [], 37 | }; 38 | 39 | exports.help = { 40 | name: "quiz", 41 | description: "Sends a quiz and expects a correct answer.", 42 | usage: "", 43 | usageDelim: "", 44 | type: "commands", 45 | }; 46 | -------------------------------------------------------------------------------- /commands/Music/play.js: -------------------------------------------------------------------------------- 1 | const yt = require("ytdl-core"); 2 | 3 | exports.run = async (client, msg) => { 4 | if (client.queue.has(msg.guild.id) === false) { 5 | throw `Add some songs to the queue first with ${msg.guild.settings.prefix}add`; 6 | } 7 | if (!msg.guild.voiceConnection) { 8 | await client.commands.get("join").run(client, msg); 9 | return this.run(client, msg); 10 | } 11 | 12 | const handler = client.queue.get(msg.guild.id); 13 | if (handler.playing) throw "Already Playing"; 14 | handler.playing = true; 15 | 16 | (function play(song) { 17 | if (song === undefined) { 18 | return msg.channel.send("⏹ Queue is empty").then(() => { 19 | handler.playing = false; 20 | return msg.member.voiceChannel.leave(); 21 | }); 22 | } 23 | 24 | msg.channel.send(`🎧 Playing: **${song.title}** as requested by: **${song.requester}**`) 25 | .catch(err => client.emit("log", err, "error")); 26 | 27 | return msg.guild.voiceConnection.playStream(yt(song.url, { audioonly: true }), { passes: 2 }) 28 | .on("end", () => { 29 | setTimeout(() => { 30 | handler.songs.shift(); 31 | play(handler.songs[0]); 32 | }, 100); 33 | }) 34 | .on("error", err => msg.channel.send(`error: ${err}`).then(() => { 35 | handler.songs.shift(); 36 | play(handler.songs[0]); 37 | })); 38 | }(handler.songs[0])); 39 | 40 | return null; 41 | }; 42 | 43 | exports.conf = { 44 | enabled: true, 45 | runIn: ["text"], 46 | aliases: [], 47 | permLevel: 0, 48 | botPerms: [], 49 | requiredFuncs: [], 50 | }; 51 | 52 | exports.help = { 53 | name: "play", 54 | description: "Plays the queue.", 55 | usage: "", 56 | usageDelim: "", 57 | extendedHelp: "", 58 | }; 59 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parserOptions": { 3 | "ecmaVersion": 8 4 | }, 5 | "env": { 6 | "es6": true, 7 | "node": true 8 | }, 9 | "extends": "airbnb-base", 10 | "rules": { 11 | "no-console": "off", 12 | "object-curly-newline": "off", 13 | "function-paren-newline": "off", 14 | "indent": ["error", 2, { "SwitchCase": 1 }], 15 | "linebreak-style": "off", 16 | "quotes": ["warn", "double"], 17 | "semi": ["warn", "always"], 18 | "no-param-reassign": "off", 19 | "no-shadow": "warn", 20 | "no-plusplus": "off", 21 | "no-continue": "off", 22 | "radix": ["error", "as-needed"], 23 | "import/no-extraneous-dependencies": "off", 24 | "import/no-unresolved": "off", 25 | "import/no-dynamic-require": "warn", 26 | "no-prototype-builtins": "warn", 27 | "no-restricted-syntax": "warn", 28 | "no-throw-literal": "off", 29 | "guard-for-in": "warn", 30 | "consistent-return": ["warn", { "treatUndefinedAsUnspecified": true }], 31 | "no-use-before-define": ["warn", { "functions": true, "classes": true }], 32 | "no-eval": "warn", 33 | "max-len": "off", 34 | "no-underscore-dangle": ["error", { "allow": ["_events"] }], 35 | "global-require": "off", 36 | "no-nested-ternary": "warn", 37 | "padded-blocks": ["error", { "classes": "always", "blocks": "never", "switches": "never" }], 38 | "valid-jsdoc": ["warn", { 39 | "requireReturn": false, 40 | "requireReturnDescription": false, 41 | "preferType": { 42 | "String": "string", 43 | "Number": "number", 44 | "Boolean": "boolean", 45 | "Symbol": "symbol", 46 | "function": "Function", 47 | "object": "Object", 48 | "date": "Date", 49 | "error": "Error" 50 | } 51 | }] 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /commands/Music/volume.js: -------------------------------------------------------------------------------- 1 | exports.run = async (client, msg, [vol = null]) => { 2 | if (!msg.guild.voiceConnection) { 3 | throw `I am not connected in a voice channel, please add some songs to the queue first with ${msg.guild.settings.prefix}add`; 4 | } 5 | const handler = client.queue.get(msg.guild.id); 6 | if (!handler || handler.playing === false) throw "I am not playing music."; 7 | 8 | const { dispatcher } = msg.guild.voiceConnection; 9 | 10 | if (!vol) return msg.send(`📢 Volume: ${Math.round(dispatcher.volume * 50)}%`); 11 | if (/^[+]+$/.test(vol)) { 12 | if (Math.round(dispatcher.volume * 50) >= 100) return msg.send(`📢 Volume: ${Math.round(dispatcher.volume * 50)}%`); 13 | dispatcher.setVolume(Math.min(((dispatcher.volume * 50) + (2 * (vol.split("+").length - 1))) / 50, 2)); 14 | return msg.send(`${dispatcher.volume === 2 ? "📢" : "🔊"} Volume: ${Math.round(dispatcher.volume * 50)}%`); 15 | } 16 | if (/^[-]+$/.test(vol)) { 17 | if (Math.round(dispatcher.volume * 50) <= 0) return msg.send(`🔇 Volume: ${Math.round(dispatcher.volume * 50)}%`); 18 | dispatcher.setVolume(Math.max(((dispatcher.volume * 50) - (2 * (vol.split("-").length - 1))) / 50, 0)); 19 | return msg.send(`${dispatcher.volume === 0 ? "🔇" : "🔉"} Volume: ${Math.round(dispatcher.volume * 50)}%`); 20 | } 21 | 22 | return msg.send("The usage of this command is either `volume +++` or `volume ---`"); 23 | }; 24 | 25 | exports.conf = { 26 | enabled: true, 27 | runIn: ["text"], 28 | aliases: [], 29 | permLevel: 0, 30 | botPerms: [], 31 | requiredFuncs: [], 32 | }; 33 | 34 | exports.help = { 35 | name: "volume", 36 | description: "Manage the volume for current song.", 37 | usage: "[control:string]", 38 | usageDelim: "", 39 | extendedHelp: [ 40 | "Let's break it down!", 41 | "", 42 | "Listen carefully, you use this command by doing either 'volume ++++' or 'volume ----'.", 43 | "The more '+' you write, the more the volume will increment.", 44 | "The more '-' you write, the more the volume will decrease.", 45 | ].join("\n"), 46 | }; 47 | -------------------------------------------------------------------------------- /monitors/didyoumean.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-use-before-define, no-restricted-syntax, consistent-return, guard-for-in */ 2 | 3 | const levenshtein = require("fast-levenshtein"); 4 | 5 | exports.minDist = null; 6 | 7 | exports.init = (client) => { 8 | this.minDist = client.config.minDist && Number.isInteger(client.config.minDist) && client.config.minDist >= 1 ? client.config.minDist : 1; 9 | }; 10 | 11 | exports.run = async (client, msg) => { 12 | if (msg.author.bot || (!client.user.bot && msg.author.id !== client.user.id)) return; 13 | 14 | const { prefixLength, command } = await parseCommand(client, msg); 15 | if (!prefixLength) return; 16 | if (!command.length) return; 17 | if (client.commands.has(command) || client.aliases.has(command)) return; 18 | 19 | const distances = []; 20 | 21 | for (const cmd of msg.usableCommands) { 22 | distances.push({ 23 | dist: levenshtein.get(cmd[0], command), 24 | cmd, 25 | }); 26 | } 27 | 28 | if (distances.length === 0) return; 29 | distances.sort((a, b) => a.dist - b.dist); 30 | 31 | if (distances[0].dist > 0 && distances[0].dist <= this.minDist) { 32 | return msg.send(`|\`❔\`| Did you mean \`${msg.guildSettings.prefix + distances[0].cmd[0]}\`?`).catch((err) => { 33 | client.console.log(err, "error"); 34 | }); 35 | } 36 | }; 37 | 38 | exports.conf = { 39 | enabled: true, 40 | requiredModules: ["fast-levenshtein"], 41 | }; 42 | 43 | exports.help = { 44 | name: "didyoumean", 45 | type: "monitors", 46 | description: "Helps users that type in commands incorrectly.", 47 | }; 48 | 49 | const parseCommand = async (client, msg) => { 50 | const prefix = client.funcs.getPrefix(client, msg); 51 | if (!prefix) return false; 52 | const prefixLength = getLength(client, msg, prefix); 53 | return { 54 | command: msg.content.slice(prefixLength).trim().split(" ")[0].toLowerCase(), 55 | prefixLength, 56 | }; 57 | }; 58 | 59 | const getLength = (client, msg, prefix) => { 60 | if (client.config.prefixMention === prefix) return prefix.exec(msg.content)[0].length + 1; 61 | return prefix.exec(msg.content)[0].length; 62 | }; 63 | -------------------------------------------------------------------------------- /inhibitors/commandCooldown.js: -------------------------------------------------------------------------------- 1 | /* 2 | Add this to the exports.conf of any command to implement a cooldown per command 3 | 4 | exports.conf.cooldowns: { 5 | // Is the cooldown per user or guild 6 | scope: "guild", 7 | time: 15000 8 | } 9 | */ 10 | 11 | const cooldowns = new Map(); 12 | 13 | const createTimeOut = (commandCooldown, commandName, id) => setTimeout(() => { 14 | const userCooldownBooleans = cooldowns.get(id); 15 | delete userCooldownBooleans[commandName]; 16 | 17 | // delete key in map when its value is empty, just to keep the map clear 18 | if (Object.keys(userCooldownBooleans).length === 0) { 19 | cooldowns.delete(id); 20 | } 21 | }, commandCooldown); 22 | 23 | exports.run = (client, msg, cmd) => { 24 | const commandName = cmd.help.name; 25 | 26 | // default behaviour 27 | // change this to msg.channel.id or msg.guild.id to override default behaviour 28 | let { id } = msg.author; 29 | 30 | const standardCooldown = 1000; 31 | let commandCooldown = standardCooldown; 32 | 33 | // Override default behaviour if command.conf has cooldown variable 34 | if (cmd.conf.cooldown) { 35 | if (cmd.conf.cooldown.scope === "guild") { 36 | id = msg.guild.id; // eslint-disable-line prefer-destructuring 37 | } 38 | 39 | commandCooldown = cmd.conf.cooldown.time; 40 | } 41 | let entry = false; 42 | 43 | if (!cooldowns.get(id)) cooldowns.set(id, {}); 44 | 45 | if (!cooldowns.get(id)[commandName]) cooldowns.get(id)[commandName] = createTimeOut(commandCooldown, commandName, id); 46 | else entry = true; 47 | 48 | if (entry && commandCooldown !== standardCooldown) { 49 | let message = `Please wait ${commandCooldown / 1000} second(s) between !${ 50 | commandName} commands.`; 51 | 52 | if (id === msg.guild.id) message += "\nThe cooldown for this command is shared across the guild."; 53 | 54 | return message; 55 | } 56 | 57 | return !!entry; 58 | }; 59 | 60 | exports.conf = { 61 | enabled: false, 62 | requiredModules: [], 63 | }; 64 | 65 | exports.help = { 66 | name: "commandCooldown", 67 | type: "inhibitors", 68 | description: "Puts commands that have the valid configuration on cooldown, by default per user.", 69 | }; 70 | -------------------------------------------------------------------------------- /commands/Self/friend.js: -------------------------------------------------------------------------------- 1 | exports.run = async (client, msg, [type, user]) => { 2 | const added = client.user.friends.get(user.id); 3 | const blocked = client.user.blocked.get(user.id); 4 | let message; 5 | try { 6 | switch (type) { 7 | case "add": 8 | if (user.bot) { 9 | message = await msg.edit("You can't add a bot as your friend."); 10 | } else if (!added) { 11 | await client.user.addFriend(user); 12 | message = await msg.edit(`Sent friend request to ${user.username}.`); 13 | } else { 14 | message = await msg.edit(`${user.username} is already your friend.`); 15 | } 16 | break; 17 | case "remove": 18 | if (added) { 19 | await client.user.removeFriend(user); 20 | message = await msg.edit(`Removed ${user.username} from friends list.`); 21 | } else { 22 | message = await msg.edit(`Could not find ${user.username} on your friends list.`); 23 | } 24 | break; 25 | case "block": 26 | if (!blocked) { 27 | await client.users.get(user.id).block(); 28 | message = await msg.edit(`Blocked ${user.username}.`); 29 | } else { 30 | message = await msg.edit(`You have already blocked ${user.username}.`); 31 | } 32 | break; 33 | case "unblock": 34 | if (blocked) { 35 | await client.users.get(user.id).unblock(); 36 | message = await msg.edit(`Removed ${user.username} from your blocked list.`); 37 | } else { 38 | message = await msg.edit(`Could not find ${user.username} on your blocked list.`); 39 | } 40 | break; 41 | // no default 42 | } 43 | } catch (e) { 44 | message = await msg.edit(e); 45 | } finally { 46 | message.delete(5000); 47 | } 48 | }; 49 | 50 | exports.conf = { 51 | enabled: true, 52 | selfbot: true, 53 | runIn: ["text", "dm", "group"], 54 | aliases: [], 55 | permLevel: 10, 56 | botPerms: [], 57 | requiredFuncs: [], 58 | requiredModules: [], 59 | }; 60 | 61 | exports.help = { 62 | name: "friend", 63 | description: "You can 'add', 'remove', 'block' and 'unblock' other users.", 64 | usage: " ", 65 | usageDelim: " ", 66 | type: "commands", 67 | }; 68 | -------------------------------------------------------------------------------- /commands/Misc/starboard.js: -------------------------------------------------------------------------------- 1 | const moment = require("moment"); 2 | require("moment-duration-format"); 3 | 4 | exports.providerEngine = "json"; 5 | 6 | exports.init = async (client) => { 7 | if (client.providers.has(this.providerEngine)) this.provider = client.providers.get(this.providerEngine); 8 | else throw new Error(`The Provider ${this.providerEngine} does not seem to exist.`); 9 | if (!(await this.provider.hasTable("starboard"))) { 10 | const SQLCreate = ["id TEXT NOT NULL UNIQUE", "messages TEXT NOT NULL DEFAULT '[]'"]; 11 | await this.provider.createTable("starboard", SQLCreate); 12 | } 13 | }; 14 | 15 | const generateMessage = (message) => { 16 | const starTime = moment(message.createdTimestamp).format("D[/]M[/]Y [@] HH:mm:ss"); 17 | const starFooter = `${message.author.tag} in #${message.channel.name} (ID: ${message.id})`; 18 | return `⭐ ${message.cleanContent}\n\n- ${starTime} by ${starFooter}`; 19 | }; 20 | 21 | exports.run = async (client, msg, [message]) => { 22 | const channel = msg.guild.channels.find("name", "starboard"); 23 | if (!channel) return msg.channel.send("Please create the _starboard_ channel and try again."); 24 | if (channel.postable === false) return msg.channel.send(`I require the permission SEND_MESSAGES to post messages in ${channel} channel.`); 25 | if (!(await this.provider.has("starboard", message.guild.id))) await this.provider.set("starboard", message.guild.id, JSON.stringify([])); 26 | const msgArray = JSON.parse(await this.provider.get("starboard", message.guild.id)); 27 | if (msgArray.includes(message.id)) return message.channel.send("This message has already been starred."); 28 | else if (msg.author === message.author) return message.channel.send("You cannot star yourself."); 29 | const options = {}; 30 | if (message.attachments.first()) options.files = message.attachments.map(a => ({ name: a.filename, attachment: a.url })); 31 | 32 | await channel.send(generateMessage(message), options); 33 | msgArray.push(message.id); 34 | await this.provider.update("starboard", message.guild.id, { messages: JSON.stringify(msgArray) }); 35 | await message.react("⭐").catch(() => null); 36 | return msg.channel.send("Successfully starred!"); 37 | }; 38 | 39 | exports.conf = { 40 | enabled: true, 41 | selfbot: false, 42 | runIn: ["text", "dm", "group"], 43 | aliases: ["star"], 44 | permLevel: 2, 45 | botPerms: [], 46 | requiredFuncs: [], 47 | requiredModules: ["moment", "moment-duration-format"], 48 | }; 49 | 50 | exports.help = { 51 | name: "starboard", 52 | description: "Stars a message", 53 | usage: "", 54 | usageDelim: "", 55 | type: "commands", 56 | }; 57 | -------------------------------------------------------------------------------- /providers/redis.js: -------------------------------------------------------------------------------- 1 | const Redis = require("redis-nextra"); 2 | 3 | const redis = new Redis() 4 | .on("ready", () => console.log("Redis Connected")) 5 | .on("serverReconnect", server => console.warn(`Redis server ${server.host.string} is reconnecting`)) 6 | .on("error", err => console.error("Redis error:", err)); 7 | 8 | /* Table methods */ 9 | 10 | /** 11 | * Checks if the table exists. 12 | * @param {string} table the name of the table you want to check. 13 | * @returns {boolean} 14 | */ 15 | exports.hasTable = table => redis.tables.has(table); 16 | 17 | /** 18 | * Creates a new table. 19 | * @param {string} table the name for the new table. 20 | * @returns {Object} 21 | */ 22 | exports.createTable = table => redis.createTable(table); 23 | 24 | /** 25 | * Deletes a table. 26 | * @param {string} table the name of the table you want to drop. 27 | * @returns {Object} 28 | */ 29 | exports.deleteTable = table => redis.deleteTable(table); 30 | 31 | /* Document methods */ 32 | 33 | /** 34 | * Get all entries from a table. 35 | * @param {string} table the name of the table you want to get the data from. 36 | * @returns {?array} 37 | */ 38 | exports.getAll = table => redis.table(table).valuesJson("*"); 39 | 40 | /** 41 | * Get an entry from a table. 42 | * @param {string} table the name of the table. 43 | * @param {string|number} id the entry's ID. 44 | * @returns {?Object} 45 | */ 46 | exports.get = (table, id) => redis.table(table).getJson(id); 47 | 48 | /** 49 | * Check if an entry exists from a table. 50 | * @param {string} table the name of the table. 51 | * @param {string|number} id the entry's ID. 52 | * @returns {boolean} 53 | */ 54 | exports.has = (table, id) => redis.table(table).has(id); 55 | 56 | /** 57 | * Insert a new document into a table. 58 | * @param {string} table the name of the table. 59 | * @param {string} id the id of the record. 60 | * @param {Object} value the object you want to insert in the table. 61 | * @param {?number} timer Amount of time in milliseconds for the value to expirate. 62 | * @returns {Object} 63 | */ 64 | exports.set = (table, id, value, timer) => redis.table(table).setJson(id, value, timer); 65 | 66 | /** 67 | * Delete an entry from the table. 68 | * @param {string} table the name of the table. 69 | * @param {string|number} id the entry's ID. 70 | * @returns {Object} 71 | */ 72 | exports.delete = (table, id) => redis.table(table).del(id); 73 | 74 | exports.conf = { 75 | enabled: false, 76 | cache: true, 77 | moduleName: "redis", 78 | requiredModules: ["redis-nextra"], 79 | }; 80 | 81 | exports.help = { 82 | name: "redis", 83 | type: "providers", 84 | description: "Allows you use Redis functionality throughout Komada.", 85 | }; 86 | -------------------------------------------------------------------------------- /commands/Tools/tags.js: -------------------------------------------------------------------------------- 1 | exports.providerEngine = "json"; 2 | 3 | exports.init = async (client) => { 4 | if (client.providers.has(this.providerEngine)) this.provider = client.providers.get(this.providerEngine); 5 | else throw new Error(`The Provider ${this.providerEngine} does not seem to exist.`); 6 | if (!(await this.provider.hasTable("tags"))) { 7 | const SQLCreate = ["count INTEGER NOT NULL DEFAULT 0", "id TEXT NOT NULL UNIQUE", "contents TEXT NOT NULL"]; 8 | await this.provider.createTable("tags", SQLCreate); 9 | } 10 | }; 11 | 12 | exports.run = async (client, msg, [action, ...contents]) => { 13 | contents = contents[0] ? contents.join(" ") : null; 14 | switch (action) { 15 | case "add": { 16 | const row = await this.provider.get("tags", contents); 17 | if (row) return msg.reply("this tag already exists."); 18 | await this.provider.insert("tags", contents.split(" ")[0], { count: 0, contents: contents.split(" ").slice(1).join(" ") }); 19 | return msg.reply(`The tag \`${contents.split(" ")[0]}\` has been added.`); 20 | } 21 | case "delete": { 22 | const row = await this.provider.get("tags", contents); 23 | if (!row) return msg.reply("this tag doesn't seem to exist."); 24 | await this.provider.delete("tags", row.id).catch(e => msg.reply(`I wasn't able to delete the tag because of the following reason: \n${e}`)); 25 | return msg.reply("the tag has been deleted. It's gone. For real, it no longer exists. It's pushing up the daisies."); 26 | } 27 | case "random": { 28 | const row = await this.provider.getRandom("tags"); 29 | await this.provider.update("tags", row.id, { count: row.count + 1 }); 30 | return msg.channel.send(row.contents); 31 | } 32 | default: { 33 | if (!contents) { 34 | const rows = await this.provider.getAll("tags"); 35 | if (rows[0] === undefined) return msg.channel.send("There is no tag available."); 36 | return msg.channel.send(`**List of tags**: \`\`\`${rows.map(r => r.id).join(" | ")}\`\`\``); 37 | } 38 | const row = await this.provider.get("tags", contents); 39 | if (!row) return msg.reply("tag name not found."); 40 | await this.provider.update("tags", row.id, { count: row.count + 1 }); 41 | return msg.channel.send(row.contents); 42 | } 43 | } 44 | }; 45 | 46 | exports.conf = { 47 | enabled: true, 48 | selfbot: false, 49 | runIn: ["text", "dm", "group"], 50 | aliases: ["tag"], 51 | permLevel: 0, 52 | botPerms: [], 53 | requiredFuncs: [], 54 | requiredModules: [], 55 | }; 56 | 57 | exports.help = { 58 | name: "tags", 59 | description: "Server-Specific tags", 60 | usage: "[add|delete|random] [contents:string] [...]", 61 | usageDelim: " ", 62 | type: "commands", 63 | }; 64 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Komada Pieces repository 2 | 3 | This repository contains the various *Pieces* submitted by users and collaborators. 4 | 5 | ## What are Pieces? 6 | 7 | *Pieces* are simply parts of code that can be downloaded and installed straight 8 | into your [Komada](https://github.com/dirigeants/komada) bot installation. 9 | 10 | Pieces can include: 11 | 12 | - **Commands**: Chat commands that generally respond with a message after 13 | taking some actions. 14 | - **Events**: Pieces that get executed when a Discord event triggers. 15 | - **Functions**: Functions that are made available to other Pieces. These 16 | functions can range from utility functions to blocks of code repeated enough 17 | to justify making a function out of it. They are not seen by the members. 18 | - **Inhibitors**: Inhibitors are pieces that run before a command is 19 | executed and may take action on the message received, and block a command from 20 | running in certain cases (thus *inhibit* a command). 21 | - **Monitors**: Monitors are pieces that can run on every message, whether or 22 | not it triggers a command. Useful for spam monitoring, swear filters, etc. 23 | - **Providers**: Support for a specific database type. By default a very 24 | small amount of DBs are supported, but you can extend the support by adding a 25 | provider for whatever database you choose, and configure it to point to your 26 | own database. 27 | - **Finalizers**: (New in v0.20.0) Pieces that run on messages after a successful 28 | command. 29 | - **Extendables**: (New in v0.20.0) Pieces that act passively, attaching 30 | new [getters](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get), 31 | [setters](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/set) 32 | or [methods](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Method_definitions) 33 | to the current Discord.js classes. They're executed at startup before any 34 | other piece. 35 | - **Packages**: A *Pieces Package* containing one or more of other pieces. 36 | Packages are presumed to work as a single entity, meaning, custom functions 37 | are used by commands, so are data providers, etc. 38 | 39 | ## Submitting Pieces 40 | 41 | Check out the documentation: 42 | 43 | - **[Creating your pieces](https://komada.js.org/creating-your-pieces.html)** 44 | - **[Downloading pieces](https://komada.js.org/download-pieces.html)** 45 | 46 | To submit your own pieces for approval (quick steps): 47 | 48 | - Fork this repository 49 | - Create a new piece in the appropriate folder 50 | - Create a Pull Request to the repository. 51 | - Be patient. Someone will approve/deny it as soon as they can. 52 | 53 | We will automatically deny PRs that: 54 | 55 | - Have identical functionality to an existing *Piece* 56 | - Have code that breaks/does not catch errors/etc 57 | - Contain NSFW, NSFL contents or contents we deem to be unacceptable. 58 | - Contain hacks/exploits/etc 59 | - Have code that might cause a bot to break the TOS or Ratelimits 60 | - Any reason **WE** feel is valid. 61 | 62 | > WE RESERVE THE RIGHT TO REFUSE ANY CONTENTS FOR ANY REASON WHETHER YOU 63 | ACCEPT THEM OR NOT. 64 | -------------------------------------------------------------------------------- /providers/localStorage.js: -------------------------------------------------------------------------------- 1 | const { LocalStorage } = require("node-localstorage"); 2 | const { resolve } = require("path"); 3 | const fs = require("fs-nextra"); 4 | 5 | /* 6 | This provider requires Node.js 8.1.0 7 | */ 8 | 9 | let baseDir; 10 | const tables = {}; 11 | 12 | exports.init = async (client) => { 13 | baseDir = resolve(client.clientBaseDir, "bwd", "provider", "localStorage"); 14 | await fs.ensureDir(baseDir); 15 | const files = await fs.readdir(baseDir); 16 | for (let i = 0; i < files.length; i++) { 17 | const table = files[i].split(".")[0]; 18 | tables[table] = new LocalStorage(resolve(baseDir, table)); 19 | } 20 | }; 21 | 22 | /* Table methods */ 23 | 24 | /** 25 | * Checks if a LocalStorage table exists. 26 | * @param {string} table The name of the table you want to check. 27 | * @returns {boolean} 28 | */ 29 | exports.hasTable = table => !!tables[table]; 30 | 31 | /** 32 | * Creates a new LocalStorage table. 33 | * @param {string} table The name for the new LocalStorage table. 34 | * @returns {Void} 35 | */ 36 | exports.createTable = (table) => { 37 | tables[table] = new LocalStorage(resolve(baseDir, table)); 38 | return true; 39 | }; 40 | 41 | /** 42 | * Deletes a LocalStorage table. 43 | * @param {string} table The name of the LocalStorage table to delete. 44 | * @returns {boolean} 45 | */ 46 | exports.deleteTable = (table) => { 47 | const index = tables.indexOf(table); 48 | if (index > -1) { 49 | tables[table].clear(); 50 | tables.splice(index, 1); 51 | return true; 52 | } 53 | throw "Table name not found in list of available tables."; 54 | }; 55 | 56 | /* Document methods */ 57 | 58 | /** 59 | * Get a document from a LocalStorage. 60 | * @param {string} table The name of the LocalStorage table. 61 | * @param {string} id The document ID. 62 | * @returns {?Object} 63 | */ 64 | exports.get = (table, id) => tables[table].getItem(id); 65 | 66 | /** 67 | * Check if a row exists. 68 | * @param {string} table The name of the LocalStorage table. 69 | * @param {string} id The document ID. 70 | * @returns {boolean} 71 | */ 72 | exports.has = (table, id) => !!tables[table].getItem(id); 73 | 74 | /** 75 | * Insert a new document into a table. 76 | * @param {string} table The name of the LocalStorage table. 77 | * @param {string} id The document ID. 78 | * @param {Object} value The object with all properties you want to insert into the document. 79 | * @returns {Object} 80 | */ 81 | exports.create = (table, id, value) => tables[table].setItem(id, value); 82 | exports.set = (...args) => this.create(...args); 83 | exports.insert = (...args) => this.create(...args); 84 | 85 | /** 86 | * Delete a document from the table. 87 | * @param {string} table The name of the LocalStorage table. 88 | * @param {string} id The document ID. 89 | * @returns {Void} 90 | */ 91 | exports.delete = (table, id) => tables[table].removeItem(id); 92 | 93 | exports.conf = { 94 | moduleName: "localStorage", 95 | enabled: true, 96 | requiredModules: ["fs-nextra", "node-localstorage"], 97 | }; 98 | 99 | exports.help = { 100 | name: "localstorage", 101 | type: "providers", 102 | description: "Allows you to create a \"local storage\" equivalent from a browser for use in Node.js", 103 | }; 104 | -------------------------------------------------------------------------------- /commands/Tools/debug.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs-nextra"); 2 | const { sep } = require("path"); 3 | 4 | const sendHelpMessage = (client, msg, cmd) => client.commands.get("help").run(client, msg, [cmd]); 5 | 6 | /* Returns an array of dir (including trailing slash) and filename */ 7 | const getPiecePath = async (client, type, name, obj) => { 8 | // Komada base dirs already end with sep 9 | const category = (obj.help ? obj.help.category : null) || "General"; 10 | const subCategory = (obj.help ? obj.help.subCategory : null) || "General"; 11 | const catDir = (category === "General" ? "" : sep + category) + 12 | (subCategory === "General" ? "" : sep + subCategory); 13 | const clientDir = `${client.clientBaseDir}${type}s${catDir}`; 14 | 15 | // See if it's a client piece (or overrides a core piece) 16 | return fs.access(`${clientDir}${sep}${name}.js`) 17 | .then(() => [`${clientDir}${sep}`, `${name}.js`]) 18 | .catch(() => { 19 | const coreDir = `${client.coreBaseDir}${type}s${catDir}`; 20 | return fs.access(`${coreDir}${sep}${name}.js`) 21 | .then(() => [`${coreDir}${sep}`, `${name}.js`]) 22 | .catch(() => [null, null]); 23 | }); 24 | }; 25 | 26 | const sendDebugMessage = async (client, msg, type, name, obj) => { 27 | const [dir, filename] = await getPiecePath(client, type, name, obj); 28 | return msg.channel.send(`location :: ${dir}\n${filename}\nType \`${msg.guildConf.prefix}debug ${type} ${name} src\` to see source`, { code: "asciidoc" }); 29 | }; 30 | 31 | const sendSrcMessage = async (client, chan, type, name, obj) => { 32 | const [dir, filename] = await getPiecePath(client, type, name, obj); 33 | if (dir && filename) { 34 | const src = await fs.readFile(dir + filename); 35 | if (src) return chan.send("js", src, { code: "js", split: true }); 36 | return chan.send("Something went wrong; could not load source", { code: true }); 37 | } 38 | return chan.send("Could not find piece", { code: true }); 39 | }; 40 | 41 | const runShowPiece = async (client, msg, type, name, obj) => { 42 | if (type === "command") await sendHelpMessage(client, msg, name); 43 | return sendDebugMessage(client, msg, type, name, obj); 44 | }; 45 | 46 | const runShowPieceSrc = (client, msg, type, name, obj) => sendSrcMessage(client, msg.channel, type, name, obj); 47 | 48 | const runListPieces = (client, chan, type, pieces) => { 49 | const piecesNames = pieces instanceof Map ? 50 | pieces.keyArray() : 51 | Object.keys(pieces); 52 | const piecesMsg = piecesNames.length > 0 ? 53 | piecesNames.reduce((acc, name) => `${acc}, ${name}`) : 54 | `No ${type}s loaded`; 55 | return chan.send(piecesMsg, { code: true }).catch(console.error); 56 | }; 57 | 58 | exports.run = async (client, msg, [type, name, src]) => { 59 | const pieces = client[{ 60 | command: "commands", 61 | inhibitor: "commandInhibitors", 62 | monitor: "messageMonitors", 63 | function: "funcs", 64 | provider: "providers", 65 | }[type]]; 66 | if (name === "*") { 67 | return runListPieces(client, msg.channel, type, pieces); 68 | } 69 | const obj = pieces instanceof Map ? 70 | pieces.get(name) : 71 | pieces[name]; 72 | if (!obj) { 73 | return msg.channel.send(`That ${type} doesn't exist`, { code: true }) 74 | .catch(console.error); 75 | } 76 | if (src) { 77 | return runShowPieceSrc(client, msg, type, name, obj); 78 | } 79 | return runShowPiece(client, msg, type, name, obj); 80 | }; 81 | 82 | exports.conf = { 83 | enabled: true, 84 | selfbot: false, 85 | runIn: ["text", "dm", "group"], 86 | aliases: [], 87 | permLevel: 10, 88 | botPerms: [], 89 | requiredFuncs: [], 90 | }; 91 | 92 | exports.help = { 93 | name: "debug", 94 | description: "Show debugging info on some thing or list all things.", 95 | usage: " [src]", 96 | usageDelim: " ", 97 | extendedHelp: "Use `*` as name to list all pieces of that type.", 98 | type: "commands", 99 | }; 100 | -------------------------------------------------------------------------------- /commands/Fun/insult.js: -------------------------------------------------------------------------------- 1 | const start = ["a lazy", "a stupid", "an insecure", "an idiotic", "a slimy", "a slutty", "a smelly", "a pompous", "a communist", "a dicknose", "a pie-eating", "a racist", "an elitist", "a white trash", "a drug-loving", "a butterface", "a tone deaf", "a ugly", "a creepy", "an artless", "a bawdy", "a beslubbering", "a bootless", "a churlish", "a cockered", "a clouted", "a craven", "a currish", "a dankish", "a dissembling", "a droning", "an errant", "a fawning", "a fobbing", "a frothy", "a gleeking", "a goatfish", "a gorbellied", "an impertinent", "an infectious", "a jarring", "a loggerheaded", "a lumpish", "a mammering", "a mangled", "a mewling", "a paunchy", "a pribbling", "a puking", "a puny", "a qualling", "a rank", "a reeky", "a roguish", "a ruttish", "a saucy", "a spleeny", "a spongy", "a surly", "a tottering", "an unmuzzled", "a vain", "a venomed", "a villainous", "a warped", "a wayward", "a weedy", "a yeasty", "a lilly-livered", "a rotten", "a stinky", "a lame", "a dim-witted", "a funky", "a crusty", "a steamy", "a drizzly", "a grizzly", "a squirty", "an uptight", "a hairy", "a husky", "an arrogant", "a nippy", "a chunky", "a smelly", "a drooling", "a crusty", "a decrepic", "a stupid", "a moronic", "a greasy", "a poxy", "an ugly", "a smelly", "a putrid", "a shitty", "an assinine", "a sickening"]; 2 | 3 | const middle = ["douche", "ass", "turd", "rectum", "butt", "cock", "shit", "crotch", "bitch", "turd", "prick", "slut", "taint", "fuck", "dick", "boner", "shart", "nut", "sphincter", "base-court", "bat-fowling", "beef-witted", "beetle-headed", "boil-brained", "clapper-clawed", "clay-brained", "common-kissing", "crook-pated", "dismal-dreaming", "dizzy-eyed", "doghearted", "dread-bolted", "earth-vexing", "elf-skinned", "fat-kidneyed", "fen-sucked", "flap-mouthed", "fly-bitten", "folly-fallen", "fool-born", "full-gorged", "guts-gripping", "half-faced", "hasty-witted", "hedge-born", "hell-hated", "idle-headed", "ill-breeding", "ill-nurtured", "knotty-pated", "milk-livered", "motly-minded", "onion-eyed", "plume-plucked", "pottle-deep", "pox-marked", "reeling-ripe", "rough-hewn", "rude-growing", "rump-red", "shard-borne", "sheep-biting", "spur-galled", "swag-bellied", "tardy-gaited", "tickle-brained", "toad-spotted", "unchin-snouted", "weather-bitten", "hiney", "poop", "toot", "wedgie", "stool", "fudge", "bum", "potty", "dookie", "pudding", "sphincter", "booger", "feces", "snot", "crust", "badonk-a", "crud", "sludge", "tool", "shit-kicking", "monkey-licking", "butt-munching", "crotch-sniffing", "donkey-spanking", "fashion-illiterate", "worm-ridden", "grub-fucking", "lathered-up", "pasty-waisted", "snot-flicking", "fart-eating"]; 4 | 5 | const end = ["pilot", "canoe", "captain", "pirate", "hammer", "knob", "box", "jockey", "nazi", "waffle", "goblin", "blossom", "biscuit", "clown", "socket", "monster", "hound", "dragon", "balloon", "apple-john", "baggage", "barnacle", "bladder", "boar-pig", "bugbear", "bum-bailey", "canker-blossom", "clack-dish", "clotpole", "coxcomb", "codpiece", "death-token", "dewberry", "flap-dragon", "flax-wench", "flirt-gill", "foot-licker", "fustilarian", "giglet", "gudgeon", "haggard", "harpy", "hedge-pig", "horn-beast", "hugger-mugger", "joithead", "lewdster", "lout", "maggot-pie", "malt-worm", "mammet", "measle", "minnow", "miscreant", "moldwarp", "mumble-news", "nut-hook", "pigeon-egg", "pignut", "puttock", "pumpion", "ratsbane", "scut", "skinsmate", "strumpet", "varlot", "vassal", "whey-face", "wagtail", "squeegee", "turtle", "cabbage", "bomb", "sniffer", "binkie", "stump", "nugget", "whistle", "twig", "knuckle", "burger", "hotdog", "loaf", "freckle", "soldier", "kernal", "shingle", "warrior", "hemorrhoid", "fuckface", "asshole", "scumbucket", "toerag", "hackwack", "imbecile", "stunodigan", "maggot", "hipster", "gargabe", "jerkstore"]; 6 | 7 | const roll = type => type[Math.floor(Math.random() * type.length)]; 8 | 9 | exports.run = (client, msg, [user]) => msg.channel.send(`${user}, you know what? you're nothing but ${roll(start)} ${roll(middle)} ${roll(end)}.`); 10 | 11 | exports.conf = { 12 | enabled: true, 13 | selfbot: false, 14 | runIn: ["text", "dm", "group"], 15 | aliases: [], 16 | permLevel: 0, 17 | botPerms: [], 18 | requiredFuncs: [], 19 | requiredModules: [], 20 | }; 21 | 22 | exports.help = { 23 | name: "insult", 24 | description: "Insults who you mention", 25 | usage: "", 26 | usageDelim: "", 27 | type: "commands", 28 | }; 29 | -------------------------------------------------------------------------------- /providers/json.js: -------------------------------------------------------------------------------- 1 | const { resolve } = require("path"); 2 | const fs = require("fs-nextra"); 3 | 4 | let baseDir; 5 | 6 | exports.init = (client) => { 7 | if (baseDir) return null; 8 | baseDir = resolve(client.clientBaseDir, "bwd", "provider", "json"); 9 | return fs.ensureDir(baseDir).catch(err => client.emit("log", err, "error")); 10 | }; 11 | 12 | /* Table methods */ 13 | 14 | /** 15 | * Checks if a directory exists. 16 | * @param {string} table The name of the table you want to check. 17 | * @returns {Promise} 18 | */ 19 | exports.hasTable = table => fs.pathExists(resolve(baseDir, table)); 20 | 21 | /** 22 | * Creates a new directory. 23 | * @param {string} table The name for the new directory. 24 | * @returns {Promise} 25 | */ 26 | exports.createTable = table => fs.mkdir(resolve(baseDir, table)); 27 | 28 | /** 29 | * Recursively deletes a directory. 30 | * @param {string} table The directory's name to delete. 31 | * @returns {Promise} 32 | */ 33 | exports.deleteTable = table => this.hasTable(table) 34 | .then(exists => (exists ? fs.emptyDir(resolve(baseDir, table)).then(() => fs.remove(resolve(baseDir, table))) : null)); 35 | 36 | /* Document methods */ 37 | 38 | /** 39 | * Get all documents from a directory. 40 | * @param {string} table The name of the directory to fetch from. 41 | * @returns {Promise} 42 | */ 43 | exports.getAll = (table) => { 44 | const dir = resolve(baseDir, table); 45 | return fs.readdir(dir) 46 | .then(files => Promise.all(files.map(file => fs.readJSON(resolve(dir, file))))); 47 | }; 48 | 49 | /** 50 | * Get a document from a directory. 51 | * @param {string} table The name of the directory. 52 | * @param {string} document The document name. 53 | * @returns {Promise} 54 | */ 55 | exports.get = (table, document) => fs.readJSON(resolve(baseDir, table, `${document}.json`)).catch(() => null); 56 | 57 | /** 58 | * Check if the document exists. 59 | * @param {string} table The name of the directory. 60 | * @param {string} document The document name. 61 | * @returns {Promise} 62 | */ 63 | exports.has = (table, document) => fs.pathExists(resolve(baseDir, table, `${document}.json`)); 64 | 65 | /** 66 | * Get a random document from a directory. 67 | * @param {string} table The name of the directory. 68 | * @returns {Promise} 69 | */ 70 | exports.getRandom = table => this.getAll(table).then(data => data[Math.floor(Math.random() * data.length)]); 71 | 72 | /** 73 | * Insert a new document into a directory. 74 | * @param {string} table The name of the directory. 75 | * @param {string} document The document name. 76 | * @param {Object} data The object with all properties you want to insert into the document. 77 | * @returns {Promise} 78 | */ 79 | exports.create = (table, document, data) => fs.outputJSONAtomic(resolve(baseDir, table, `${document}.json`), Object.assign(data, { id: document })); 80 | exports.set = (...args) => this.create(...args); 81 | exports.insert = (...args) => this.create(...args); 82 | 83 | /** 84 | * Update a document from a directory. 85 | * @param {string} table The name of the directory. 86 | * @param {string} document The document name. 87 | * @param {Object} data The object with all the properties you want to update. 88 | * @returns {Promise} 89 | */ 90 | exports.update = (table, document, data) => this.get(table, document) 91 | .then(current => fs.outputJSONAtomic(resolve(baseDir, table, `${document}.json`), Object.assign(current, data))); 92 | 93 | /** 94 | * Replace all the data from a document. 95 | * @param {string} table The name of the directory. 96 | * @param {string} document The document name. 97 | * @param {Object} data The new data for the document. 98 | * @returns {Promise} 99 | */ 100 | exports.replace = (table, document, data) => fs.outputJSONAtomic(resolve(baseDir, table, `${document}.json`), data); 101 | 102 | /** 103 | * Delete a document from the table. 104 | * @param {string} table The name of the directory. 105 | * @param {string} document The document name. 106 | * @returns {Promise} 107 | */ 108 | exports.delete = (table, document) => fs.unlink(resolve(baseDir, table, `${document}.json`)); 109 | 110 | exports.conf = { 111 | moduleName: "json", 112 | enabled: true, 113 | requiredModules: ["fs-nextra"], 114 | }; 115 | 116 | exports.help = { 117 | name: "json", 118 | type: "providers", 119 | description: "Allows you to use JSON functionality throught Komada", 120 | }; 121 | -------------------------------------------------------------------------------- /providers/levelup.js: -------------------------------------------------------------------------------- 1 | const { sep, resolve } = require("path"); 2 | const fs = require("fs-nextra"); 3 | const level = require("level"); 4 | const promisify = require("then-levelup"); 5 | const { Collection } = require("discord.js"); 6 | 7 | let baseDir; 8 | const loaded = new Collection(); 9 | 10 | const throwError = (err) => { throw err; }; 11 | 12 | exports.init = async (client) => { 13 | baseDir = resolve(client.clientBaseDir, "bwd", "provider", "level"); 14 | await fs.ensureDir(baseDir).catch(throwError); 15 | const files = await fs.readdir(baseDir).catch(throwError); 16 | const fn = file => promisify(level(baseDir + sep + file)) 17 | .then(db => loaded.set(file, db)); 18 | Promise.all(files.map(fn)); 19 | }; 20 | 21 | /* Table methods */ 22 | 23 | /** 24 | * Checks if a directory exists. 25 | * @param {string} table The name of the table you want to check. 26 | * @returns {Promise} 27 | */ 28 | exports.hasTable = async table => loaded.has(table); 29 | 30 | /** 31 | * Creates a new directory. 32 | * @param {string} table The name for the new directory. 33 | * @returns {Promise} 34 | */ 35 | exports.createTable = table => promisify(level(baseDir + sep + table)) 36 | .then(db => loaded.set(table, db)); 37 | 38 | /** 39 | * Recursively deletes a directory. 40 | * @param {string} table The directory's name to delete. 41 | * @returns {Promise} 42 | */ 43 | exports.deleteTable = table => this.hasTable(table) 44 | .then(exists => (exists ? level.destroy(baseDir + sep + table).then(() => fs.remove(baseDir + sep + table)) : null)); 45 | 46 | /* Document methods */ 47 | 48 | /** 49 | * Get all documents from a directory. 50 | * @param {string} table The name of the directory to fetch from. 51 | * @returns {Promise} 52 | */ 53 | exports.getAll = table => new Promise((res) => { 54 | const db = loaded.get(table); 55 | const output = []; 56 | if (!db) res(output); 57 | db.keyStream() 58 | .on("data", key => db.get(key).then(d => output.push(JSON.parse(d)))) 59 | .on("end", () => res(output)); 60 | }); 61 | 62 | 63 | /** 64 | * Get a document from a directory. 65 | * @param {string} table The name of the directory. 66 | * @param {string} document The document name. 67 | * @returns {Promise} 68 | */ 69 | exports.get = async (table, document) => loaded.get(table)[document]; 70 | 71 | /** 72 | * Insert a new document into a directory. Aliases: set, insert. 73 | * @param {string} table The name of the directory. 74 | * @param {string} document The document name. 75 | * @param {Object} data The object with all properties you want to insert into the document. 76 | * @returns {Promise} 77 | */ 78 | exports.create = (table, document, data) => loaded.get(table).set(document, JSON.stringify(Object.assign(data, { id: document }))); 79 | exports.set = (...args) => this.create(...args); 80 | exports.insert = (...args) => this.create(...args); 81 | 82 | /** 83 | * Update a document from a directory. 84 | * @param {string} table The name of the directory. 85 | * @param {string} document The document name. 86 | * @param {Object} data The object with all the properties you want to update. 87 | * @returns {Promise} 88 | */ 89 | exports.update = (table, document, data) => this.get(table, document) 90 | .then(current => this.set(table, document, Object.assign(current, data))); 91 | 92 | /** 93 | * Replace all the data from a document. 94 | * @param {string} table The name of the directory. 95 | * @param {string} document The document name. 96 | * @param {Object} data The new data for the document. 97 | * @returns {Promise} 98 | */ 99 | exports.replace = (table, document, data) => this.set(table, document, data); 100 | 101 | /** 102 | * Delete a document from the table. 103 | * @param {string} table The name of the directory. 104 | * @param {string} document The document name. 105 | * @returns {Promise} 106 | */ 107 | exports.delete = (table, document) => this.get(table, document) 108 | .then(db => db.delete(document)); 109 | 110 | /** 111 | * Closes all open database connections. 112 | * @returns {Promise} 113 | */ 114 | exports.shutdown = () => loaded.forEach(db => db.close()); 115 | 116 | exports.conf = { 117 | moduleName: "level", 118 | enabled: true, 119 | requiredModules: ["fs-nextra", "level", "then-level"], 120 | }; 121 | 122 | exports.help = { 123 | name: "level", 124 | type: "providers", 125 | description: "Allows you to use LevelDB functionality throught Komada", 126 | }; 127 | -------------------------------------------------------------------------------- /providers/mongodb.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-underscore-dangle */ 2 | 3 | const Mongo = require("mongodb").MongoClient; 4 | 5 | let db; 6 | 7 | const config = { 8 | moduleName: "mongo", 9 | enabled: true, 10 | dbName: "Komada", 11 | dbURL: "mongodb://localhost:27017/", 12 | }; 13 | 14 | exports.init = async () => { 15 | try { 16 | const client = await Mongo.connect(config.dbURL); 17 | db = client.db(config.dbName); 18 | } catch (err) { console.log(err); } 19 | }; 20 | 21 | const resolveQuery = query => (query instanceof Object ? query : { id: query }); 22 | 23 | // Collection Methods. Collections are implicitly created with document methods regardess. 24 | 25 | /** 26 | * Create a collection within a DB. Options may be specfied, refer to MongoDB docs. 27 | * @param {string} name Name of the Collection to crate 28 | * @param {Object} [options] Object containing various options for the created Collection 29 | * @returns {Promise} Returns a promise containing the created Collection. 30 | */ 31 | exports.createCollection = (name, options = {}) => db.createCollection(name, options); 32 | exports.createTable = (...args) => this.createCollection(...args); 33 | exports.hasTable = table => db.listCollections().toArray().then(collections => collections.some(col => col.name === table)); 34 | 35 | /** 36 | * Drops a collection within a DB. 37 | * @param {string} name Name of the collection to drop. 38 | * @returns {Promise} 39 | */ 40 | exports.dropCollection = name => db.dropCollection(name); 41 | exports.deleteTable = (...args) => this.dropCollection(...args); 42 | 43 | // Document Methods. Update requires MongoDB Update Operators. Be sure to refer to MongoDB documentation. 44 | /** 45 | * Retrieves all Documents in a collection. 46 | * @param {string} collection Name of the Collection 47 | * @returns {Promise} 48 | */ 49 | exports.getAll = collection => this.db.collection(collection).find({}, { _id: 0 }).toArray(); 50 | 51 | /** 52 | * Retrieves a single Document from a Collection that matches a user determined ID 53 | * @param {string} collection Name of the Collection 54 | * @param {string} id ID of the document 55 | * @returns {Promise} 56 | */ 57 | exports.get = (collection, id) => db.collection(collection).findOne(resolveQuery(id)); // eslint-disable-line quote-props 58 | 59 | exports.has = (collection, id) => this.get(collection, id).then(res => !!res); 60 | 61 | exports.getRandom = collection => this.getAll(collection).then(results => results[Math.floor(Math.random() * results.length)]); 62 | 63 | /** 64 | * Inserts a Document into a Collection using a user provided object. 65 | * @param {string} collection Name of the Collection 66 | * @param {(string|Object)} id ID of the document 67 | * @param {Object} docObj Document Object to insert 68 | * @returns {Promise} 69 | */ 70 | exports.insert = (collection, id, docObj) => db.collection(collection).insertOne(Object.assign(docObj, resolveQuery(id))); 71 | exports.create = (...args) => this.insert(...args); 72 | exports.set = (...args) => this.insert(...args); 73 | 74 | /** 75 | * Deletes a Document from a Collection that matches a user determined ID * 76 | * @param {string} collection Name of the Collection 77 | * @param {string} id ID of the document 78 | * @returns {Promise} 79 | */ 80 | exports.delete = (collection, id) => db.collection(collection).deleteOne(resolveQuery(id)); 81 | 82 | /** 83 | * Updates a Document using MongoDB Update Operators. * 84 | * @param {string} collection Name of the Collection 85 | * @param {Object} filter The Filter used to select the document to update 86 | * @param {Object} updateObj The update operations to be applied to the document 87 | */ 88 | exports.update = (collection, filter, updateObj) => this.db.collection(collection).updateOne(resolveQuery(filter), { $set: updateObj }); 89 | 90 | /** 91 | * Replaces a Document with a new Document specified by the user * 92 | * @param {string} collection Name of the Collection 93 | * @param {Object} filter The Filter used to select the document to update 94 | * @param {Object} repDoc The Document that replaces the matching document 95 | * @returns {Promise} 96 | */ 97 | exports.replace = (collection, filter, repDoc) => db.collection(collection).replaceOne(resolveQuery(filter), repDoc); 98 | 99 | exports.eval = () => db; 100 | 101 | exports.help = { 102 | name: "mongodb", 103 | type: "providers", 104 | description: "Allows use of MongoDB functionality throughout Komada.", 105 | }; 106 | 107 | exports.conf = config; 108 | exports.conf.requiredModules = ["mongodb"]; 109 | -------------------------------------------------------------------------------- /providers/nedb.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-underscore-dangle */ 2 | 3 | const { resolve } = require("path"); 4 | const Datastore = require("nedb-core"); 5 | require("tsubaki").promisifyAll(Datastore.prototype); 6 | 7 | let dataStores; 8 | const fs = require("fs-nextra"); 9 | 10 | let baseDir; 11 | 12 | exports.init = (client) => { 13 | dataStores = new client.methods.Collection(); 14 | baseDir = resolve(client.clientBaseDir, "bwd", "provider", "nedb"); 15 | return fs.ensureDir(baseDir).catch(err => client.emit("log", err, "error")); 16 | }; 17 | 18 | const resolveQuery = query => (query instanceof Object ? query : { id: query }); 19 | 20 | /* Table methods */ 21 | 22 | /** 23 | * Check if a table exists. 24 | * @param {string} table The name of the table you want to check. 25 | * @returns {boolean} 26 | */ 27 | exports.hasTable = table => dataStores.has(table); 28 | 29 | /** 30 | * Create a new table. 31 | * @param {string} table The name for the new table. 32 | * @param {boolean} [persistent=true] Whether the DB should be persistent. 33 | * @returns {Promise} 34 | */ 35 | exports.createTable = async (table, persistent = true) => { 36 | if (dataStores.has(table)) return null; 37 | const db = new Datastore(persistent ? { filename: resolve(baseDir, `${table}.db`), autoload: true } : {}); 38 | dataStores.set(table, db); 39 | return db; 40 | }; 41 | 42 | /** 43 | * Delete a table. 44 | * @param {string} table The name of the table to delete. 45 | * @returns {Promise} 46 | */ 47 | exports.deleteTable = async (table) => { 48 | if (dataStores.has(table)) { 49 | await this.deleteAll(table); 50 | dataStores.delete(table); 51 | return true; 52 | } 53 | return false; 54 | }; 55 | 56 | /* Document methods */ 57 | 58 | /** 59 | * Get all entries from a table. 60 | * @param {string} table The name of the table to get all entries from. 61 | * @returns {Promise} 62 | */ 63 | exports.getAll = async (table) => { 64 | const data = await dataStores.get(table).findAsync({}); 65 | for (let i = 0; i < data.length; i++) { delete data[i]._id; } 66 | return data; 67 | }; 68 | 69 | /** 70 | * Get a single entry from a table by a query. 71 | * @param {string} table The name of the table to get the entry from. 72 | * @param {string|Object} query The query object. If it is a string, it will search by 'id'. 73 | * @returns {Promise} 74 | */ 75 | exports.get = async (table, query) => { 76 | const data = await dataStores.get(table).findOneAsync(resolveQuery(query)); 77 | delete data._id; 78 | return data; 79 | }; 80 | 81 | /** 82 | * Check if a entry exists from a table by a query. 83 | * @param {string} table The name of the table to check the entry from. 84 | * @param {string|Object} query The query object. If it is a string, it will search by 'id'. 85 | * @returns {Promise} 86 | */ 87 | exports.has = (table, query) => this.get(table, query).then(result => !!result); 88 | 89 | /** 90 | * Insert a new entry into a table. 91 | * @param {string} table The name of the table to insert the entry. 92 | * @param {string|Object} query The query object. If it is a string, it will be keyed by 'id'. 93 | * @param {Object} data The data you want the entry to contain. 94 | * @returns {Promise} 95 | */ 96 | exports.create = (table, query, data) => dataStores.get(table).insertAsync(Object.assign(data, resolveQuery(query))); 97 | exports.set = (...args) => this.create(...args); 98 | exports.insert = (...args) => this.create(...args); 99 | 100 | /** 101 | * Update an entry from a table. 102 | * @param {string} table The name of the table to update the entry from. 103 | * @param {string|Object} query The query object. If it is a string, it will search by 'id'. 104 | * @param {Object} data The data you want to update. 105 | * @returns {Promise} Returns a Promise containing the number of Documents Updated. (Either 0 or 1). 106 | */ 107 | exports.update = async (table, query, data) => { 108 | const res = await this.get(table, query); 109 | await dataStores.get(table).updateAsync(resolveQuery(query), Object.assign(res, data)); 110 | await dataStores.get(table).persistence.compactDatafile(); 111 | return true; 112 | }; 113 | exports.replace = async (table, query, data) => { 114 | await dataStores.get(table).updateAsync(resolveQuery(query), data); 115 | await dataStores.get(table).persistence.compactDatafile(); 116 | return true; 117 | }; 118 | 119 | /** 120 | * Delete a single or all entries from a table that matches the query. 121 | * @param {string} table The name of the table to delete the entry from. 122 | * @param {string|Object} query The query object. If it is a string, it will search by 'id'. 123 | * @param {boolean} [all=false] Option to delete all documents that match the query. 124 | * @returns {Promise} Returns a Promise with the number of documents deleted. 125 | */ 126 | exports.delete = (table, query, all = false) => dataStores.get(table).removeAsync(resolveQuery(query), { multi: all }); 127 | 128 | /** 129 | * Delete all entries from a table. 130 | * @param {string} table The name of the table to delete the entries from. 131 | * @returns {Promise} Returns a Promise with the number of documents deleted. 132 | */ 133 | exports.deleteAll = table => this.delete(table, {}, true); 134 | 135 | /** 136 | * Count the amount of entries from a table based on the query. 137 | * @param {string} table The name of the table to count the entries from. 138 | * @param {string|Object} [query={}] The query object. If it is a string, it will search by 'id'. 139 | * @returns {Promise} The amount of entries that matches the query. 140 | */ 141 | exports.count = (table, query = {}) => dataStores.get(table).countAsync(resolveQuery(query)); 142 | 143 | exports.conf = { 144 | moduleName: "nedb", 145 | enabled: true, 146 | requiredModules: ["tsubaki", "nedb-core"], 147 | }; 148 | 149 | exports.help = { 150 | name: "nedb", 151 | type: "providers", 152 | description: "Allows you to use NeDB functionality throught Komada", 153 | }; 154 | -------------------------------------------------------------------------------- /commands/Tools/guide.js: -------------------------------------------------------------------------------- 1 | const baseUrl = "https://anidiotsguide.gitbooks.io/discord-js-bot-guide"; 2 | const guides = { 3 | home: { url: "/", snippet: "Some technical details about the bot guide, and a donation link if you're inclined to be as generous with your petty cash as I was with my time writing this!" }, 4 | faq: { url: "/frequently-asked-questions.html", snippet: "In this page, some very basic, frequently-asked questions are answered. It's important to understand that **these examples are generic** and will most likely not work if you just copy/paste them in your code." }, 5 | // Getting Started 6 | editor: { url: "/getting-started/installing_and_using_a_proper_editor.html", snippet: "Let's take a moment to appreciate the fact that the best code, is not just code that _works_ but also code that is _readable._" }, 7 | gslong: { url: "/getting-started/the-long-version.html", snippet: "So, you want to write a bot and you know some JavaScript, or maybe even node.js. You want to do cool things like a music bot, tag commands, random image searches, the whole shebang. Well you're at the right place!" }, 8 | gswin: { url: "/getting-started/windows-tldr.html", snippet: "**Windows TL;DR** version of the getting started guide. When you have exactly 0 time to lose and no desire to fuck around." }, 9 | gslinux: { url: "/getting-started/linux-tldr.html", snippet: "**Linux TL;DR** version of the getting started guide. When you have exactly 0 time to lose and no desire to fuck around." }, 10 | firstbot: { url: "/coding-walkthroughs/your_basic_bot.html", snippet: "In this chapter I'll guide you through the development of a simple bot with some useful commands. We'll start with the example we created in the first chapter and expand upon it." }, 11 | // Understanding / Information 12 | roles: { url: "/information/understanding_roles.html", snippet: "Roles are a powerful feature in Discord, and admittedly have been one of the hardest parts to master in discord.js. This walkthrough aims at explaining how roles and permissions work. We'll also explore how to use roles to protect your commands." }, 13 | events: { url: "/information/understanding_events_and_handlers.html", snippet: "We already explored one event handler in Your Basic Bot, the `message` handler. Now let's take a look at some of the most important handlers that you will use, along with an example." }, 14 | collection: { url: "/information/understanding_collections.html", snippet: "In this page we will explore Collections, and how to use them to grab data from various part of the API." }, 15 | async: { url: "/information/understanding_asyncawait.html", snippet: "When an async function is called, it returns a Promise. When the async function returns a value, the Promise will be resolved with the returned value." }, 16 | // Coding Guides 17 | stjson: { url: "/coding-guides/storing-data-in-a-json-file.html", snippet: "In this example we're going to read and write data to and from a JSON file. We'll keep is simple by using this JSON file for a points system." }, 18 | stsqlite: { url: "/coding-guides/storing-data-in-an-sqlite-file.html", snippet: "As mentioned in the Storing Data in a JSON file guide, JSON files could get corrupted due to race conditions. However SQLite doesn't suffer from that and is a better method of storing data between boot ups than JSON." }, 19 | webhp1: { url: "/coding-guides/discord-webhooks-part-1.html", snippet: "This has been a rather demanded topic recently, everyone wants to know how to use the webhooks, so here I am with this guide to explain the basic coverage of the webhooks." }, 20 | cmdhndlr: { url: "/coding-guides/a-basic-command-handler.html", snippet: "A Command Handler is essentially a way to separate your commands into different files, instead of having a bunch of `if/else` conditions inside your code (or a `switch/case` if you're being fancy)." }, 21 | musicbot: { url: "/coding-guides/coding_a_music_bot.html", snippet: "Everyone including their grandparents want to create a music bot, I myself have created a music bot and believe me it's not as easy as you would think." }, 22 | cleverbot: { url: "/coding-guides/cleverbot-integration.html", snippet: "I've had this request since I started my Idiot's Guide, in fact it was one of the very first requests I had, but I had a feeling it would be a disappointing short episode, maybe a 5 minute long episode. But for a written guide it'd be perfect!" }, 23 | // Examples 24 | msgxusr: { url: "/examples/welcome_message_every_x_users.html", snippet: "This example will show how to keep an array/object of new users coming into a server. Then, when this array reaches a certain number of users, it shows a message welcoming those users as a group." }, 25 | msgra: { url: "/examples/message_reply_array.html", snippet: "This sample shows the use of a simple string array to reply specific strings when triggered." }, 26 | args: { url: "/examples/command_with_arguments.html", snippet: "In Your First Bot, we explored how to make more than one command. These commands all started with a prefix, but didn't have any *arguments* : extra parameters used to vary what the command actually does." }, 27 | selfbot: { url: "/examples/selfbots_are_awesome.html", snippet: "So, my friend, you are wondering what is a selfbot? Well let me tell you a little story. Stay a while, and listen!" }, 28 | eval: { url: "/examples/making-an-eval-command.html", snippet: "Eval bots are AWESOME. But eval bots are DANGEROUS. Read up on them here." }, 29 | embeds: { url: "/examples/using-embeds-in-messages.html", snippet: "You've seen them all around - these sexy dark grey boxes with a nice color on the left, images, and **tables** oh my god. So nice-looking, right? Well, let me show you how to make them!" }, 30 | // Other-Guides 31 | github: { url: "/other-guides/using-git-to-share-and-update-code.html", snippet: "Have you ever come to a point where you're editing code, removing and adding and changing stuff and all of a sudden you realize, _Shit, I deleted this piece and I need to rewrite or re-use it now. Damn!_" }, 32 | }; 33 | 34 | exports.run = (client, msg, [keyword]) => { 35 | if (keyword === "list") { 36 | return msg.channel.send(`Available keywords for this command:\n\`${Object.keys(guides).join("`, `")}\``); 37 | } else if (guides[keyword]) { 38 | return msg.channel.send(`${guides[keyword].snippet}\n**Read More**: <${baseUrl}${guides[keyword].url}>`); 39 | } 40 | return msg.channel.send(`${guides.home.snippet}\n**Read More**: <${baseUrl}${guides.home.url}>`); 41 | }; 42 | 43 | exports.conf = { 44 | enabled: true, 45 | selfbot: false, 46 | runIn: ["text", "dm", "group"], 47 | aliases: [], 48 | permLevel: 0, 49 | botPerms: [], 50 | requiredFuncs: [], 51 | requiredModules: [], 52 | }; 53 | 54 | exports.help = { 55 | name: "guide", 56 | description: "Returns page details from root's awesome bot guide.", 57 | usage: "[list|keyword:string]", 58 | usageDelim: "", 59 | type: "commands", 60 | }; 61 | -------------------------------------------------------------------------------- /providers/mysql.js: -------------------------------------------------------------------------------- 1 | const mysql = require("mysql2/promise"); 2 | 3 | let db; 4 | 5 | const throwError = (err) => { throw err; }; 6 | 7 | exports.init = async () => { 8 | db = await mysql.createConnection({ 9 | host: "localhost", 10 | port: "3306", 11 | user: "root", 12 | password: "", 13 | database: "Komada", 14 | }); 15 | }; 16 | 17 | /* Table methods */ 18 | 19 | /** 20 | * Checks if a table exists. 21 | * @param {string} table The name of the table you want to check. 22 | * @returns {Promise} 23 | */ 24 | exports.hasTable = table => this.exec(`SELECT 1 FROM ${table} LIMIT 1`) 25 | .then(() => true) 26 | .catch(() => false); 27 | 28 | /** 29 | * Creates a new table. 30 | * @param {string} table The name for the new table. 31 | * @param {string} rows The rows for the table. 32 | * @returns {Promise} 33 | */ 34 | exports.createTable = (table, rows) => this.exec(`CREATE TABLE '${table}' (${rows.join(", ")})`); 35 | 36 | /** 37 | * Drops a table. 38 | * @param {string} table The name of the table to drop. 39 | * @returns {Promise} 40 | */ 41 | exports.deleteTable = table => this.exec(`DROP TABLE '${table}'`); 42 | 43 | /* Document methods */ 44 | 45 | /** 46 | * Get all documents from a table. 47 | * @param {string} table The name of the table to fetch from. 48 | * @param {Object} options Options. 49 | * @returns {Promise} 50 | */ 51 | exports.getAll = async (table, options = {}) => this.runAll(options.key && options.value ? 52 | `SELECT * FROM \`${table}\` WHERE \`${options.key}\` = ${this.sanitize(options.value)}` : 53 | `SELECT * FROM \`${table}\``).then(([rows]) => rows); 54 | 55 | /** 56 | * Get a row from a table. 57 | * @param {string} table The name of the table. 58 | * @param {string} key The row id or the key to find by. If value is undefined, it'll search by 'id'. 59 | * @param {string} [value=null] The desired value to find. 60 | * @returns {Promise} 61 | */ 62 | exports.get = (table, key, value = null) => this.run(!value ? 63 | `SELECT * FROM \`${table}\` WHERE \`id\` = ${this.sanitize(key)} LIMIT 1` : 64 | `SELECT * FROM \`${table}\` WHERE \`${key}\` = ${this.sanitize(value)} LIMIT 1`) 65 | .then(([rows]) => rows[0]).catch(() => null); 66 | 67 | /** 68 | * Check if a row exists. 69 | * @param {string} table The name of the table 70 | * @param {string} value The value to search by 'id'. 71 | * @returns {Promise} 72 | */ 73 | exports.has = (table, value) => this.runAll(`SELECT \`id\` FROM \`${table}\` WHERE \`id\` = ${this.sanitize(value)} LIMIT 1`) 74 | .then(([rows]) => rows.length > 0); 75 | 76 | /** 77 | * Get a random row from a table. 78 | * @param {string} table The name of the table. 79 | * @returns {Promise} 80 | */ 81 | exports.getRandom = table => this.run(`SELECT * FROM \`${table}\` ORDER BY RAND() LIMIT 1`); 82 | 83 | /** 84 | * Insert a new document into a table. 85 | * @param {string} table The name of the table. 86 | * @param {string} row The row id. 87 | * @param {Object} data The object with all properties you want to insert into the document. 88 | * @returns {Promise} 89 | */ 90 | exports.create = (table, row, data) => { 91 | const { keys, values } = this.serialize(Object.assign(data, { id: row })); 92 | return this.exec(`INSERT INTO \`${table}\` (\`${keys.join("`, `")}\`) VALUES (${values.map(this.sanitize).join(", ")})`); 93 | }; 94 | exports.set = (...args) => this.create(...args); 95 | exports.insert = (...args) => this.create(...args); 96 | 97 | /** 98 | * Update a row from a table. 99 | * @param {string} table The name of the table. 100 | * @param {string} row The row id. 101 | * @param {Object} data The object with all the properties you want to update. 102 | * @returns {Promise} 103 | */ 104 | exports.update = (table, row, data) => { 105 | const inserts = Object.entries(data).map(value => `\`${value[0]}\` = ${this.sanitize(value[1])}`).join(", "); 106 | return this.exec(`UPDATE \`${table}\` SET ${inserts} WHERE id = '${row}'`); 107 | }; 108 | exports.replace = (...args) => this.update(...args); 109 | 110 | /** 111 | * Delete a document from the table. 112 | * @param {string} table The name of the directory. 113 | * @param {string} row The row id. 114 | * @returns {Promise} 115 | */ 116 | exports.delete = (table, row) => this.exec(`DELETE FROM \`${table}\` WHERE id = ${this.sanitize(row)}`); 117 | 118 | /** 119 | * Update the columns from a table. 120 | * @param {string} table The name of the table. 121 | * @param {string[]} columns Array of columns. 122 | * @param {array[]} schema Tuples of keys/values from the schema. 123 | * @returns {boolean} 124 | */ 125 | exports.updateColumns = async (table, columns, schema) => { 126 | await this.exec(`CREATE TABLE \`temp_table\` (\n${schema.map(s => `\`${s[0]}\` ${s[1]}`).join(",\n")}\n);`); 127 | await this.exec(`INSERT INTO \`temp_table\` (\`${columns.join("`, `")}\`) SELECT \`${columns.join("`, `")}\` FROM \`${table}\`;`); 128 | await this.exec(`DROP TABLE \`${table}\`;`); 129 | await this.exec(`ALTER TABLE \`temp_table\` RENAME TO \`${table}\`;`); 130 | return true; 131 | }; 132 | 133 | /** 134 | * Get a row from an arbitrary SQL query. 135 | * @param {string} sql The query to execute. 136 | * @returns {Promise} 137 | */ 138 | exports.run = sql => db.query(sql).then(([rows]) => rows[0]).catch(throwError); 139 | 140 | /** 141 | * Get all rows from an arbitrary SQL query. 142 | * @param {string} sql The query to execute. 143 | * @returns {Promise} 144 | */ 145 | exports.runAll = sql => db.query(sql).catch(throwError); 146 | exports.exec = (...args) => this.runAll(...args); 147 | 148 | /** 149 | * Transform NoSQL queries into SQL. 150 | * @param {Object} data The object. 151 | * @returns {Object} 152 | */ 153 | exports.serialize = (data) => { 154 | const keys = []; 155 | const values = []; 156 | const entries = Object.entries(data); 157 | for (let i = 0; i < entries.length; i++) { 158 | keys[i] = entries[i][0]; // eslint-disable-line prefer-destructuring 159 | values[i] = entries[i][1]; // eslint-disable-line prefer-destructuring 160 | } 161 | 162 | return { keys, values }; 163 | }; 164 | 165 | exports.sanitize = (string) => { 166 | if (typeof string === "string") return `'${string.replace(/'/g, "''")}'`; 167 | else if (string instanceof Object) return `'${JSON.stringify(string).replace(/'/g, "''")}'`; 168 | return JSON.stringify(string); 169 | }; 170 | 171 | exports.CONSTANTS = { 172 | String: "TEXT", 173 | Integer: "INT", 174 | Float: "INT", 175 | AutoID: "INT PRIMARY KEY AUTOINCREMENT UNIQUE", 176 | Timestamp: "DATETIME", 177 | AutoTS: "DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL", 178 | }; 179 | 180 | exports.conf = { 181 | moduleName: "mysql", 182 | enabled: true, 183 | requiredModules: ["mysql2"], 184 | sql: true, 185 | }; 186 | 187 | exports.help = { 188 | name: "mysql", 189 | type: "providers", 190 | description: "Allows you use MySQL functionality throughout Komada.", 191 | }; 192 | -------------------------------------------------------------------------------- /providers/sqlite.js: -------------------------------------------------------------------------------- 1 | const { resolve } = require("path"); 2 | const db = require("sqlite"); 3 | const fs = require("fs-nextra"); 4 | 5 | /* 6 | This provider requires Node.js 8.1.0 7 | */ 8 | 9 | const throwError = (err) => { throw err; }; 10 | 11 | exports.init = async (client) => { 12 | const baseDir = resolve(client.clientBaseDir, "bwd", "provider", "sqlite"); 13 | await fs.ensureDir(baseDir).catch(throwError); 14 | await fs.ensureFile(resolve(baseDir, "db.sqlite")).catch(throwError); 15 | return db.open(resolve(baseDir, "db.sqlite")).catch(throwError); 16 | }; 17 | 18 | /* Table methods */ 19 | 20 | /** 21 | * Checks if a table exists. 22 | * @param {string} table The name of the table you want to check. 23 | * @returns {Promise} 24 | */ 25 | exports.hasTable = table => this.runGet(`SELECT name FROM sqlite_master WHERE type='table' AND name='${table}'`) 26 | .then(row => !!row) 27 | .catch(throwError); 28 | 29 | /** 30 | * Creates a new table. 31 | * @param {string} table The name for the new table. 32 | * @param {string} rows The rows for the table. 33 | * @returns {Promise} 34 | */ 35 | exports.createTable = (table, rows) => this.run(`CREATE TABLE '${table}' (${rows.join(", ")});`); 36 | 37 | /** 38 | * Drops a table. 39 | * @param {string} table The name of the table to drop. 40 | * @returns {Promise} 41 | */ 42 | exports.deleteTable = table => this.run(`DROP TABLE '${table}'`); 43 | 44 | /* Document methods */ 45 | 46 | /** 47 | * Get all documents from a table. 48 | * @param {string} table The name of the table to fetch from. 49 | * @param {Object} options key and value. 50 | * @returns {Promise} 51 | */ 52 | exports.getAll = (table, options = {}) => this.runAll(options.key && options.value ? 53 | `SELECT * FROM '${table}' WHERE ${options.key} = ${this.sanitize(options.value)}` : 54 | `SELECT * FROM '${table}'`); 55 | 56 | /** 57 | * Get a row from a table. 58 | * @param {string} table The name of the table. 59 | * @param {string} key The row id or the key to find by. If value is undefined, it'll search by 'id'. 60 | * @param {string} [value=null] The desired value to find. 61 | * @returns {Promise} 62 | */ 63 | exports.get = (table, key, value = null) => this.runGet(!value ? 64 | `SELECT * FROM ${table} WHERE id = ${this.sanitize(key)}` : 65 | `SELECT * FROM ${table} WHERE ${key} = ${this.sanitize(value)}`).catch(() => null); 66 | 67 | /** 68 | * Check if a row exists. 69 | * @param {string} table The name of the table 70 | * @param {string} value The value to search by 'id'. 71 | * @returns {Promise} 72 | */ 73 | exports.has = (table, value) => this.runGet(`SELECT id FROM '${table}' WHERE id = ${this.sanitize(value)}`) 74 | .then(() => true) 75 | .catch(() => false); 76 | 77 | /** 78 | * Get a random row from a table. 79 | * @param {string} table The name of the table. 80 | * @returns {Promise} 81 | */ 82 | exports.getRandom = table => this.runGet(`SELECT * FROM '${table}' ORDER BY RANDOM() LIMIT 1`).catch(() => null); 83 | 84 | /** 85 | * Insert a new document into a table. 86 | * @param {string} table The name of the table. 87 | * @param {string} row The row id. 88 | * @param {Object} data The object with all properties you want to insert into the document. 89 | * @returns {Promise} 90 | */ 91 | exports.create = (table, row, data) => { 92 | const { keys, values } = this.serialize(Object.assign(data, { id: row })); 93 | return this.run(`INSERT INTO '${table}' (${keys.join(", ")}) VALUES(${values.map(this.sanitize).join(", ")})`); 94 | }; 95 | exports.set = (...args) => this.create(...args); 96 | exports.insert = (...args) => this.create(...args); 97 | 98 | /** 99 | * Update a row from a table. 100 | * @param {string} table The name of the table. 101 | * @param {string} row The row id. 102 | * @param {Object} data The object with all the properties you want to update. 103 | * @returns {Promise} 104 | */ 105 | exports.update = (table, row, data) => { 106 | const inserts = Object.entries(data).map(value => `${value[0]} = ${this.sanitize(value[1])}`).join(", "); 107 | return this.run(`UPDATE '${table}' SET ${inserts} WHERE id = '${row}'`); 108 | }; 109 | exports.replace = (...args) => this.update(...args); 110 | 111 | /** 112 | * Delete a document from the table. 113 | * @param {string} table The name of the table. 114 | * @param {string} row The row id. 115 | * @returns {Promise} 116 | */ 117 | exports.delete = (table, row) => this.run(`DELETE FROM '${table}' WHERE id = ${this.sanitize(row)}`); 118 | 119 | /** 120 | * Update the columns from a table. 121 | * @param {string} table The name of the table. 122 | * @param {string[]} columns Array of columns. 123 | * @param {array[]} schema Tuples of keys/values from the schema. 124 | * @returns {boolean} 125 | */ 126 | exports.updateColumns = async (table, columns, schema) => { 127 | await this.run(`CREATE TABLE \`temp_table\` (\n${schema.map(s => `\`${s[0]}\` ${s[1]}`).join(",\n")}\n);`); 128 | await this.run(`INSERT INTO \`temp_table\` (\`${columns.join("`, `")}\`) SELECT \`${columns.join("`, `")}\` FROM \`${table}\`;`); 129 | await this.run(`DROP TABLE \`${table}\`;`); 130 | await this.run(`ALTER TABLE \`temp_table\` RENAME TO \`${table}\`;`); 131 | return true; 132 | }; 133 | 134 | /** 135 | * Get a row from an arbitrary SQL query. 136 | * @param {string} sql The query to execute. 137 | * @returns {Promise} 138 | */ 139 | exports.runGet = sql => db.get(sql).catch(throwError); 140 | 141 | /** 142 | * Get all rows from an arbitrary SQL query. 143 | * @param {string} sql The query to execute. 144 | * @returns {Promise} 145 | */ 146 | exports.runAll = sql => db.all(sql).catch(throwError); 147 | 148 | /** 149 | * Run arbitrary SQL query. 150 | * @param {string} sql The query to execute. 151 | * @returns {Promise} 152 | */ 153 | exports.run = sql => db.run(sql).catch(throwError); 154 | 155 | /** 156 | * Execute arbitrary SQL query. 157 | * @param {string} sql The query to execute. 158 | * @returns {Promise} 159 | */ 160 | exports.exec = sql => db.exec(sql).catch(throwError); 161 | 162 | /** 163 | * Transform NoSQL queries into SQL. 164 | * @param {Object} data The object. 165 | * @returns {Object} 166 | */ 167 | exports.serialize = (data) => { 168 | const keys = []; 169 | const values = []; 170 | const entries = Object.entries(data); 171 | for (let i = 0; i < entries.length; i++) { 172 | keys[i] = entries[i][0]; // eslint-disable-line prefer-destructuring 173 | values[i] = entries[i][1]; // eslint-disable-line prefer-destructuring 174 | } 175 | 176 | return { keys, values }; 177 | }; 178 | 179 | exports.sanitize = (string) => { 180 | if (typeof string === "string") return `'${string.replace(/'/g, "''")}'`; 181 | else if (string instanceof Object) return `'${JSON.stringify(string).replace(/'/g, "''")}'`; 182 | return JSON.stringify(string); 183 | }; 184 | 185 | exports.CONSTANTS = { 186 | String: "TEXT", 187 | Integer: "INTEGER", 188 | Float: "INTEGER", 189 | AutoID: "INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE", 190 | Timestamp: "DATETIME", 191 | AutoTS: "DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL", 192 | }; 193 | 194 | exports.conf = { 195 | moduleName: "sqlite", 196 | enabled: true, 197 | requiredModules: ["sqlite", "fs-nextra"], 198 | sql: true, 199 | }; 200 | 201 | exports.help = { 202 | name: "sqlite", 203 | type: "providers", 204 | description: "Allows you use SQLite functionality throughout Komada.", 205 | }; 206 | -------------------------------------------------------------------------------- /providers/rethinkdb.js: -------------------------------------------------------------------------------- 1 | const config = { 2 | moduleName: "rethinkdb", 3 | enabled: true, 4 | requiredModules: ["rethinkdbdash"], 5 | }; 6 | 7 | const r = require("rethinkdbdash")({ db: "test" }); 8 | 9 | exports.exec = r; 10 | 11 | /* Table methods */ 12 | 13 | /** 14 | * Checks if the table exists. 15 | * @param {string} table the name of the table you want to check. 16 | * @returns {boolean} 17 | */ 18 | exports.hasTable = table => r.tableList().run().then(data => data.includes(table)); 19 | 20 | /** 21 | * Creates a new table. 22 | * @param {string} table the name for the new table. 23 | * @returns {Object} 24 | */ 25 | exports.createTable = table => r.tableCreate(table).run(); 26 | 27 | /** 28 | * Deletes a table. 29 | * @param {string} table the name of the table you want to drop. 30 | * @returns {Object} 31 | */ 32 | exports.deleteTable = table => r.tableDrop(table).run(); 33 | 34 | /** 35 | * Sync the database. 36 | * @param {string} table the name of the table you want to sync. 37 | * @returns {Object} 38 | */ 39 | exports.sync = table => r.table(table).sync().run(); 40 | 41 | /* Document methods */ 42 | 43 | /** 44 | * Get all entries from a table. 45 | * @param {string} table the name of the table you want to get the data from. 46 | * @returns {?array} 47 | */ 48 | exports.getAll = table => r.table(table) || null; 49 | 50 | /** 51 | * Get an entry from a table. 52 | * @param {string} table the name of the table. 53 | * @param {string|number} id the entry's ID. 54 | * @returns {?Object} 55 | */ 56 | exports.get = (table, id) => r.table(table).get(id) || null; 57 | 58 | /** 59 | * Check if an entry exists from a table. 60 | * @param {string} table the name of the table. 61 | * @param {string|number} id the entry's ID. 62 | * @returns {boolean} 63 | */ 64 | exports.has = (table, id) => this.get(table, id) 65 | .then(data => !!data) 66 | .catch(() => false); 67 | 68 | /** 69 | * Get a random entry from a table. 70 | * @param {string} table the name of the table. 71 | * @returns {Object} 72 | */ 73 | exports.getRandom = table => this.all(table).then(data => data[Math.floor(Math.random() * data.length)]); 74 | 75 | /** 76 | * Insert a new document into a table. 77 | * @param {string} table the name of the table. 78 | * @param {string} id the id of the record. 79 | * @param {Object} doc the object you want to insert in the table. 80 | * @returns {Object} 81 | */ 82 | exports.create = (table, id, doc) => r.table(table).insert(Object.assign(doc, { id })).run(); 83 | exports.set = (...args) => this.create(...args); 84 | exports.insert = (...args) => this.create(...args); 85 | 86 | /** 87 | * Update a document from a table given its ID. 88 | * @param {string} table the name of the table. 89 | * @param {string|number} id the entry's ID. 90 | * @param {Object} doc the object you want to insert in the table. 91 | * @returns {Object} 92 | */ 93 | exports.update = (table, id, doc) => r.table(table).get(id).update(doc).run(); 94 | 95 | /** 96 | * Replace the object from an entry with another. 97 | * @param {string} table the name of the table. 98 | * @param {string|number} id the entry's ID. 99 | * @param {Object} doc the document in question to replace the current entry's properties. 100 | * @returns {Object} 101 | */ 102 | exports.replace = (table, id, doc) => r.table(table).get(id).replace(doc).run(); 103 | 104 | /** 105 | * Delete an entry from the table. 106 | * @param {string} table the name of the table. 107 | * @param {string|number} id the entry's ID. 108 | * @returns {Object} 109 | */ 110 | exports.delete = (table, id) => r.table(table).get(id).delete().run(); 111 | 112 | /** 113 | * Insert an object into an array given the name of the array, entry ID and table. 114 | * @param {string} table the name of the table. 115 | * @param {string|number} id the entry's ID. 116 | * @param {string} uArray the name of the array you want to update. 117 | * @param {Object} doc the object you want to insert in the table. 118 | * @returns {Object} 119 | */ 120 | exports.append = (table, id, uArray, doc) => r.table(table).get(id).update(object => ({ [uArray]: object(uArray).default([]).append(doc) })).run(); 121 | 122 | /** 123 | * Update an object into an array given the position of the array, entry ID and table. 124 | * @param {string} table the name of the table. 125 | * @param {string|number} id the entry's ID. 126 | * @param {string} uArray the name of the array you want to update. 127 | * @param {number} index the position of the object inside the array. 128 | * @param {Object} doc the object you want to insert in the table. 129 | * @returns {Object} 130 | */ 131 | exports.updateArrayByIndex = (table, id, uArray, index, doc) => r.table(table).get(id).update({ [uArray]: r.row(uArray).changeAt(index, r.row(uArray).nth(index).merge(doc)) }).run(); 132 | 133 | /** 134 | * Update an object into an array given the ID, the name of the array, entry ID and table. 135 | * @param {string} table the name of the table. 136 | * @param {string|number} id the entry's ID. 137 | * @param {string} uArray the name of the array you want to update. 138 | * @param {string} index the ID of the object inside the array. 139 | * @param {Object} doc the object you want to insert in the table. 140 | * @returns {Object} 141 | */ 142 | exports.updateArrayByID = (table, id, uArray, index, doc) => r.table(table).get(id).update({ [uArray]: r.row(uArray).map(d => r.branch(d("id").eq(index), d.merge(doc), d)) }).run(); 143 | 144 | /** 145 | * Remove an object from an array given the position of the array, entry ID and table. 146 | * @param {string} table the name of the table. 147 | * @param {string|number} id the entry's ID. 148 | * @param {string} uArray the name of the array you want to update. 149 | * @param {number} index the position of the object inside the array. 150 | * @returns {Object} 151 | */ 152 | exports.removeFromArrayByIndex = (table, id, uArray, index) => r.table(table).get(id).update({ [uArray]: r.row(uArray).deleteAt(index) }).run(); 153 | 154 | /** 155 | * Remove an object from an array given the position of the array, entry ID and table. 156 | * @param {string} table the name of the table. 157 | * @param {string|number} id the entry's ID. 158 | * @param {string} uArray the name of the array you want to update. 159 | * @param {string} index the ID of the object inside the array. 160 | * @returns {Object} 161 | */ 162 | exports.removeFromArrayByID = (table, id, uArray, index) => r.table(table).get(id).update({ [uArray]: r.row(uArray).filter(it => it("id").ne(index)) }).run(); 163 | 164 | /** 165 | * Get an object from an array given the position of the array, entry ID and table. 166 | * @param {string} table the name of the table. 167 | * @param {string|number} id the entry's ID. 168 | * @param {string} uArray the name of the array you want to update. 169 | * @param {number} index the position of the object inside the array. 170 | * @returns {Object} 171 | */ 172 | exports.getFromArrayByIndex = (table, id, uArray, index) => r.table(table).get(id)(uArray).nth(index).run(); 173 | 174 | /** 175 | * Get an object into an array given the ID, the name of the array, entry ID and table. 176 | * @param {string} table the name of the table. 177 | * @param {string|number} id the entry's ID. 178 | * @param {string} uArray the name of the array you want to update. 179 | * @param {string} index the ID of the object inside the array. 180 | * @returns {?Object} 181 | */ 182 | exports.getFromArrayByID = (table, id, uArray, index) => r.table(table).get(id)(uArray).filter(r.row("id").eq(index)).run().then(res => (res.length ? res[0] : null)); 183 | 184 | /* Exports for the Download command */ 185 | 186 | exports.conf = config; 187 | 188 | exports.help = { 189 | name: "rethinkdb", 190 | type: "providers", 191 | description: "Allows you use rethinkDB functionality throughout Komada.", 192 | }; 193 | -------------------------------------------------------------------------------- /commands/Fun/dogfacts.js: -------------------------------------------------------------------------------- 1 | const facts = [ 2 | "Puppies have 28 teeth and adult dogs have 42.", 3 | "Hollywood’s first and arguably best canine superstar was Rin Tin Tin, a five-day-old German Shepherd found wounded in battle in WWI France and adopted by an American soldier, Lee Duncan. He would sign his own contracts with his paw print.", 4 | "Spiked dog collars were used to protect dogs' throats from wolf attacks in ancient Greece.", 5 | "Chocolate contains a substance known as theobromine (similar to caffeine) which can kill dogs or, at the very least, make them violently ill.", 6 | "A dog's whiskers are used as sensing devices.", 7 | "Dogs judge objects first by their movement, then by their brightness, and lastly by their shape.", 8 | "n ancient China, an emperor's last line of defense was a small Pekingese dog literally hidden up his sleeve.", 9 | "Greyhounds appear to be the most ancient dog breed. \"Greyhound\" comes from a mistake in translating the early German name Greishund, which means \"old (or ancient) dog,\" not from the color gray.", 10 | "There are about 400 million dogs in the world.", 11 | "Dogs with little human contact in the first three months typically don’t make good pets.", 12 | "Endal was the first dog to ride on the London Eye (the characteristic ferris wheel in London, England), and was also the first known dog to successfully use a ATM machine.", 13 | "Dogs’ only sweat glands are between their paw pads.", 14 | "Dogs can count up to five and can perform simple mathematical calculations.", 15 | "Dogs can see best at dawn and dusk.", 16 | "Zorba, an English mastiff, is the biggest dog ever recorded. He weighed 343 pounds and measured 8’ 3\" from his nose to his tail.", 17 | "Dogs have a wet nose to collect more of the tiny droplets of smelling chemicals in the air.", 18 | "It pays to be a lap dog. Three dogs (from First Class cabins!) survived the sinking of the Titanic – two Pomeranians and one Pekingese.", 19 | "A dog’s vision is not fully developed until after the first month.", 20 | "The smallest dog on record was a matchbox-size Yorkshire Terrier. It was 2.5\" tall at the shoulder, 3.5\" from nose tip to tail, and weighed only 4 ounces.", 21 | "The Beagle came into prominence in the 1300s and 1400s during the days of King Henry VII of England. Elizabeth I was fond of Pocket Beagles, which were only 9\" high.", 22 | "Chase that tail! Dogs chase their tails for a variety of reasons: curiosity, exercise, anxiety, predatory instinct or, they might have fleas! If your dog is chasing his tail excessively, talk with your vet.", 23 | "Irish Wolfhounds, the tallest breed, are 30 to 35 inches tall.", 24 | "Growing up. While the Chow Chow dogs are well known for their distinctive blue-black tongues, they’re actually born with pink tongues. They turn blue-black at 8-10 weeks of age.", 25 | "Greyhounds are the fastest dogs on earth, with speeds of up to 45 miles per hour.", 26 | "Rock star Ozzy Osborne saved his wife Sharon’s Pomeranian from a coyote by tackling and wresting the coyote until it released the dog.", 27 | "In addition to \"formal\" forms of dog training (operant conditioning, reinforcement, or classical conditioning), dogs are able to learn merely from observation.", 28 | "Some dogs can smell dead bodies under water, where termites are hiding, and natural gas buried under 40 feet of dirt. They can even detect cancer that is too small to be detected by a doctor and can find lung cancer by sniffing a person’s breath.", 29 | "U.S. Customs dogs \"Rocky\" and \"Barco\" were so good at patrolling the border that Mexican drug lords put a $300,000 bounty on their heads.", 30 | "Different smells in the a dog’s urine can tell other dogs whether the dog leaving the message is female or male, old or young, sick or healthy, happy or angry.", 31 | "Dogs can see in color, though they most likely see colors similar to a color-blind human. They can see better when the light is low.", 32 | "One of Shakespeare’s most mischievous characters is Crab, the dog belonging to Launce in the Two Gentlemen of Verona. The word \"watchdog\" is first found in The Tempest.", 33 | "The oldest dog on record – a Queensland Heeler named Bluey – was 29 years, 5 months old.", 34 | "Teddy Roosevelt’s dog, Pete, ripped a French ambassador’s pants off at the White House.", 35 | "Obesity is the top health problem among dogs.", 36 | "The Mayans and Aztecs symbolized every tenth day with the dog, and those born under this sign were believed to have outstanding leadership skills.", 37 | "The Labrador Retriever has been the most popular dog breed since 1991.", 38 | "Dogs’ nose prints are as unique as a human’s finger prints, and can be used to accurately identify them.", 39 | "When \"Bobbie,\" a Collie/Shepherd mix, was accidentally abandoned on a family vacation, he traveled 2,551 miles over six months to return to his home.", 40 | "Analysis of the dog genome demonstrate only 4 major types of dogs: \"Old Lineage Dogs,\" \"Mastiff-type Dogs,\" \"Herding Dogs,\" and \"Modern Hunting Dogs.\"", 41 | "Davy Crockett had a dog named Sport.", 42 | "Dogs live an average of 15 years.", 43 | "The dog was frequently depicted in Greek art, including Cerberus, the three-headed hound guarding the entrance to the underworld, and the hunting dogs which accompanied the virgin goddess of the chase, Diana.", 44 | "Puppies sleep 90% of the day for their first few weeks.", 45 | "Many foot disorders in dogs are simply an issue of too-long toenails.", 46 | "A Russian dog named Laika was the first animal in space, traveling around Earth in 1957.", 47 | "Move over Rover! 45% of dogs sleep in their owner’s bed (we’re pretty sure a large percentage also hog the blankets!)", 48 | "Dogs with a flesh colored nose is said to have a \"Dudley Nose.\"", 49 | "Fifty-eight percent of people put pets in family and holiday portraits.", 50 | "The U.S. has the highest dog population in the world.", 51 | "Newfoundlands are great swimmers because of their webbed feet.", 52 | "The Berger Picard, Miniature American Shepherd and Lagotto Romagnolo are the newest dog breeds recognized by the American Kennel Club in 2015.", 53 | "During the Middle Ages, mixed breeds of peasants’ dogs were required to wear blocks around their necks to keep them from breeding with noble hunting dogs. Purebred dogs were very expensive and hunting became the province of the rich.", 54 | "The Chihuahua was named after the state in Mexico where they were discovered.", 55 | "Teams of dogs compete for the fastest time without errors in Flyball races.", 56 | "Three of the 12 dogs on the Titanic survived.", 57 | "Bichons, Portuguese Water Dogs, Kerry Blue Terriers, Maltese and Poodles are all good choices if you have allergies since they shed less than other breeds.", 58 | "Petting a dog can lower your blood pressure.", 59 | "One survey reports that 33% of dog owners admit they talk to their dogs on the phone or leave messages on answering machines while they are away..", 60 | "Dogs are naturally submissive to any creature with a higher pack status.", 61 | "Dogs are naturally submissive to any creature with higher pack status, human or canine.", 62 | "President Lyndon Johnson had two beagles named Him and Her.", 63 | "Humans can detect sounds at 20,000 times per second, while dogs can sense frequencies of 30,000 times per second.", 64 | "The most dogs ever owned by one person were 5,000 Mastiffs owned by Kublai Khan.", 65 | "There are an estimated 400 million dogs in the world.", 66 | "More than one in three U.S. families owns a dog.", 67 | "Dogs don't enjoy being hugged as much as humans and other primates. Canines interpret putting a limb over another animal as a sign of dominance.", 68 | "The phrase \"raining cats and dogs\" originated in 17th century England when it is believed that many cats and dogs drowned during heavy periods of rain.", 69 | "The ancient religion Zoroastrianism includes in its religious text titled the Zend Avesta a section devoted to the care and breeding of dogs.", 70 | "The bible mentions dogs 14 times.", 71 | "Boxers are so named because of their manner of playing using their front paws.", 72 | "During the Renaissance, detailed portraits of the dog as a symbol of fidelity and loyalty appeared in mythological, allegorical, and religious art throughout Europe, including works by Leonardo da Vinci, Diego Velázquez, Jan van Eyck, and Albrecht Durer.", 73 | "Petting dogs is proven to lower blood pressure of dog owners.", 74 | "Smaller breeds mature faster than larger breeds.", 75 | "Dogs have no sense of time.", 76 | "In 2001, it was estimated that there are approximately 400 million dogs in the world.", 77 | "At the end of WWI, the German government trained the first guide dogs for war-blinded soldiers.", 78 | "Dogs sleep for an average of 10 hours per day.", 79 | "Dogs have been used since the 1700’s for therapy.", 80 | "Smaller breeds of dogs mature faster than larger breeds.", 81 | "A dog’s pregnancy lasts for approximately 60 days.", 82 | "Seventy percent of people sign their pet’s name on greeting and holiday cards.", 83 | "French poodles did not originate in France but in Germany (\"poodle\" comes from the German pudel or pudelhund, meaning \"splashing dog\"). Some scholars speculate the poodle’s puffs of hair evolved when hunters shaved the poodle for more efficient swimming, while leaving the pom-poms around the major joints to keep them warm.", 84 | "In 1957, Laika became the first living being in space via an earth satellite and JFK’s terrier, Charlie, fathered 4 puppies with Laika’s daughter.", 85 | "The most popular dog breed in Canada, U.S., and Great Britain is the Labrador retriever.", 86 | "Although it was once illegal to keep dogs as pets in Iceland's capital city, the laws have been relaxed.", 87 | "The Basenji is the only barkless dog.", 88 | "Dogs are capable of understanding up to 250 words and gestures, can count up to five and can perform simple mathematical calculations. The average dog is as intelligent as a two-year-old child.", 89 | "Basset Hounds cannot swim.", 90 | "The phrase \"raining cats and dogs\" originated in seventeenth-century England. During heavy rainstorms, many homeless animals would drown and float down the streets, giving the appearance that it had actually rained cats and dogs.", 91 | "The Labrador is so popular, in 2006 there were approximately 3-5 times more Labs as there were German Shepherds or Golden Retrievers.", 92 | "Male dogs will raise their legs while urinating to aim higher on a tree or lamppost because they want to leave a message that they are tall and intimidating. Some wild dogs in Africa try to run up tree trunks while they are urinating to appear to be very large.", 93 | "Even with their poor eyesight, dogs can still see better at night than humans do.", 94 | "An elderly woman was saved by her 12-pound Yorkshire Terrier, who fought off an 80- pound Akita, and survived with only 9 stitches.", 95 | "Unlike cats, dogs are not exclusively carnivores: they can adapt to different foods, and a percentage of their diets can be fruits and vegetables.", 96 | "Female wolves have been known to travel great distances to regurgitate full meals for their hungry pups.", 97 | "There are hundreds of breeds of dogs.", 98 | "In Egypt, a person bitten by a rabid dog was encouraged to eat the roasted liver of a dog infected with rabies to avoid contracting the disease. The tooth of a dog infected with rabies would also be put in a band tied to the arm of the person bitten. The menstrual blood of a female dog was used for hair removal, while dog genitals were used for preventing the whitening of hair.", 99 | "Dogs can get jealous when their humans display affection toward someone or something else.", 100 | "Weird dog laws include allowing police offers in Palding, Ohio, to bite a dog to quiet it. In Ventura County, California, cats and dogs are not allowed to have sex without a permit.", 101 | "The largest dog was an English Mastiff who weighed 343 pounds.", 102 | ]; 103 | 104 | exports.run = (client, msg) => msg.channel.send(facts[Math.floor(Math.random() * facts.length)]); 105 | 106 | exports.conf = { 107 | enabled: true, 108 | selfbot: false, 109 | runIn: ["text", "dm", "group"], 110 | aliases: [], 111 | permLevel: 0, 112 | botPerms: [], 113 | requiredFuncs: [], 114 | requiredModules: [], 115 | }; 116 | 117 | exports.help = { 118 | name: "dogfact", 119 | description: "Gives you a random dog fact.", 120 | usage: "", 121 | usageDelim: "", 122 | type: "commands", 123 | }; 124 | --------------------------------------------------------------------------------