├── .eslintrc.json ├── .gitignore ├── LICENSE ├── README.md ├── discordfm ├── Chill-Corner.json ├── Classical.json ├── Coffee-House-Jazz.json ├── Electro-Hub.json ├── Electro-Swing.json ├── Hip-Hop.json ├── Japaneese-Lounge.json ├── Korean-Madness.json ├── Metal-Mix.json ├── Purely-Pop.json ├── Retro-Renegade.json └── Rock-n-Roll.json ├── locales ├── de.json ├── en.json ├── es.json └── ru.json ├── package-lock.json ├── package.json └── src ├── bot ├── commands │ ├── admin │ │ ├── autoRole.js │ │ ├── editCommand.js │ │ ├── ignore.js │ │ ├── rolePersist.js │ │ └── settings.js │ ├── creator │ │ ├── eval.js │ │ ├── exec.js │ │ └── update.js │ ├── default │ │ ├── byemom.js │ │ ├── cat.js │ │ ├── channel.js │ │ ├── dog.js │ │ ├── donate.js │ │ ├── google.js │ │ ├── help.js │ │ ├── invite.js │ │ ├── locale.js │ │ ├── needsmorejpeg.js │ │ ├── ping.js │ │ ├── remind.js │ │ ├── roleme.js │ │ ├── serverinfo.js │ │ ├── stats.js │ │ ├── tags.js │ │ ├── userinfo.js │ │ ├── workerinfo.js │ │ └── xkcd.js │ ├── moderator │ │ ├── ban.js │ │ ├── censor.js │ │ ├── giveaway.js │ │ ├── guildlocale.js │ │ ├── hackban.js │ │ ├── kick.js │ │ ├── mute.js │ │ ├── pardon.js │ │ ├── purge.js │ │ ├── reason.js │ │ ├── softban.js │ │ ├── tempmute.js │ │ └── warn.js │ └── music │ │ ├── autoplay.js │ │ ├── end.js │ │ ├── jump.js │ │ ├── movehere.js │ │ ├── pause.js │ │ ├── play.js │ │ ├── queue.js │ │ ├── removequeue.js │ │ ├── repeat.js │ │ ├── resume.js │ │ ├── savequeue.js │ │ ├── seek.js │ │ ├── shuffle.js │ │ ├── skip.js │ │ └── volume.js ├── index.js ├── listeners │ ├── on │ │ ├── channelCreate.js │ │ ├── channelDelete.js │ │ ├── guildBanAdd.js │ │ ├── guildBanRemove.js │ │ ├── guildCreate.js │ │ ├── guildDelete.js │ │ ├── guildMemberAdd.js │ │ ├── guildMemberRemove.js │ │ ├── guildMemberUpdate.js │ │ ├── messageCreate.js │ │ ├── voiceChannelJoin.js │ │ ├── voiceChannelLeave.js │ │ └── voiceChannelSwitch.js │ └── once │ │ └── ready.js ├── modules │ ├── args.js │ ├── audio │ │ ├── autoplay.js │ │ └── main.js │ ├── censors.js │ ├── channels.js │ ├── commands.js │ ├── images │ │ ├── byemom.js │ │ ├── byemom.png │ │ ├── main.js │ │ └── needsmorejpeg.js │ ├── locales.js │ ├── modLog.js │ ├── statPoster.js │ └── tags.js ├── structures │ ├── command.js │ └── player.js └── utils │ ├── autoRole.js │ ├── codeBlock.js │ ├── escapeRegex.js │ ├── formatDate.js │ ├── getMutedRole.js │ ├── parseMs.js │ ├── redis.js │ ├── resolver.js │ ├── rolePersistHandler.js │ ├── secondsToDuration.js │ ├── timedEvents.js │ ├── userLog.js │ └── warnMember.js ├── index.js ├── master.js ├── misc ├── logger.js ├── outputHandler.js ├── rethink.js ├── snowflake.js └── webhookStatus.js ├── website ├── index.js ├── public │ └── assets │ │ ├── css │ │ ├── loading.css │ │ └── main.css │ │ ├── js │ │ ├── lazyload.js │ │ └── settings.js │ │ └── other │ │ ├── lazy.gif │ │ └── whitney.woff ├── routes │ ├── accounts.js │ ├── callback.js │ ├── commands.js │ ├── dashboard.js │ ├── emojis.js │ ├── faq.js │ ├── index.js │ ├── logout.js │ ├── stats.js │ └── update.js └── views │ ├── 404.hbs │ ├── accounts.hbs │ ├── commands.hbs │ ├── dashboard.hbs │ ├── emojis.hbs │ ├── faq.hbs │ ├── index.hbs │ ├── invite.hbs │ ├── modlog.hbs │ ├── settings.hbs │ └── stats.hbs ├── worker.js └── worker_handling ├── bot.js ├── messages.js └── site.js /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "eslint:recommended", 3 | "env": { 4 | "node": true, 5 | "es6": true 6 | }, 7 | "globals": { 8 | "bot": false, 9 | "cluster": false, 10 | "r": false, 11 | "__": false 12 | }, 13 | "parserOptions": { 14 | "ecmaVersion": 2017 15 | }, 16 | "rules": { 17 | "no-extra-parens": ["warn", "all", { 18 | "nestedBinaryExpressions": false 19 | }], 20 | "valid-jsdoc": ["warn", { 21 | "requireReturn": false, 22 | "requireReturnDescription": false, 23 | "preferType": { 24 | "String": "string", 25 | "Number": "number", 26 | "Boolean": "boolean", 27 | "Function": "function", 28 | "object": "Object", 29 | "date": "Date", 30 | "error": "Error" 31 | } 32 | }], 33 | 34 | "accessor-pairs": "warn", 35 | "array-callback-return": "error", 36 | "consistent-return": "error", 37 | "curly": ["error", "multi-line", "consistent"], 38 | "dot-location": ["error", "property"], 39 | "dot-notation": "error", 40 | "eqeqeq": "error", 41 | "no-empty-function": "error", 42 | "no-floating-decimal": "error", 43 | "no-implied-eval": "error", 44 | "no-invalid-this": "error", 45 | "no-lone-blocks": "error", 46 | "no-multi-spaces": "error", 47 | "no-new-func": "error", 48 | "no-new-wrappers": "error", 49 | "no-new": "error", 50 | "no-octal-escape": "error", 51 | "no-self-compare": "error", 52 | "no-sequences": "error", 53 | "no-throw-literal": "error", 54 | "no-unmodified-loop-condition": "error", 55 | "no-unused-expressions": "error", 56 | "no-unused-vars": ["error", { "args": "none" }], 57 | "no-console": "off", 58 | "no-useless-call": "error", 59 | "no-useless-concat": "error", 60 | "no-useless-escape": "off", 61 | "no-void": "error", 62 | "no-warning-comments": "warn", 63 | "wrap-iife": "error", 64 | "yoda": "error", 65 | 66 | "no-label-var": "error", 67 | "no-shadow": "error", 68 | "no-undef-init": "error", 69 | 70 | "callback-return": "error", 71 | "handle-callback-err": "error", 72 | "no-mixed-requires": "error", 73 | "no-new-require": "error", 74 | "no-path-concat": "error", 75 | 76 | "array-bracket-spacing": "error", 77 | "block-spacing": "error", 78 | "brace-style": ["error", "1tbs", { "allowSingleLine": true }], 79 | "camelcase": "error", 80 | "comma-dangle": "error", 81 | "comma-spacing": "error", 82 | "comma-style": "error", 83 | "computed-property-spacing": "error", 84 | "consistent-this": "error", 85 | "eol-last": "error", 86 | "func-style": ["error", "declaration", { "allowArrowFunctions": true }], 87 | "id-length": ["error", { "exceptions": ["i", "j", "a", "b"] }], 88 | "indent": ["error", "tab", { "SwitchCase": 1 }], 89 | "key-spacing": "error", 90 | "keyword-spacing": ["error", { 91 | "overrides": { 92 | "if": { "after": false }, 93 | "for": { "after": false }, 94 | "while": { "after": false }, 95 | "catch": { "after": false }, 96 | "switch": { "after": false } 97 | } 98 | }], 99 | "max-len": ["error", 120, 2], 100 | "max-nested-callbacks": ["error", { "max": 3 }], 101 | "max-statements-per-line": ["error", { "max": 2 }], 102 | "new-cap": "error", 103 | "no-array-constructor": "error", 104 | "no-lonely-if": "error", 105 | "no-mixed-operators": "error", 106 | "no-multiple-empty-lines": ["error", { "max": 2, "maxEOF": 1, "maxBOF": 0 }], 107 | "no-new-object": "error", 108 | "no-spaced-func": "error", 109 | "no-trailing-spaces": "error", 110 | "no-unneeded-ternary": "error", 111 | "no-whitespace-before-property": "error", 112 | "object-curly-newline": "error", 113 | "object-curly-spacing": ["error", "always"], 114 | "operator-assignment": "error", 115 | "operator-linebreak": ["error", "after"], 116 | "padded-blocks": ["error", "never"], 117 | "quotes": ["error", "double", { "allowTemplateLiterals": true, "avoidEscape": true }], 118 | "quote-props": ["error", "as-needed"], 119 | "semi-spacing": "error", 120 | "semi": "error", 121 | "space-before-blocks": "error", 122 | "space-in-parens": "error", 123 | "space-infix-ops": "error", 124 | "space-unary-ops": "error", 125 | "spaced-comment": "error", 126 | "unicode-bom": "error", 127 | 128 | "arrow-body-style": "error", 129 | "arrow-spacing": "error", 130 | "no-duplicate-imports": "error", 131 | "no-useless-computed-key": "error", 132 | "no-useless-constructor": "error", 133 | "prefer-arrow-callback": "error", 134 | "prefer-rest-params": "error", 135 | "prefer-spread": "error", 136 | "prefer-template": "error", 137 | "rest-spread-spacing": "error", 138 | "template-curly-spacing": "error", 139 | "yield-star-spacing": "error" 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | config.json 2 | 3 | # Logs 4 | logs 5 | *.log 6 | npm-debug.log* 7 | yarn-debug.log* 8 | yarn-error.log* 9 | 10 | # Runtime data 11 | pids 12 | *.pid 13 | *.seed 14 | *.pid.lock 15 | 16 | # Directory for instrumented libs generated by jscoverage/JSCover 17 | lib-cov 18 | 19 | # Coverage directory used by tools like istanbul 20 | coverage 21 | 22 | # nyc test coverage 23 | .nyc_output 24 | 25 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 26 | .grunt 27 | 28 | # Bower dependency directory (https://bower.io/) 29 | bower_components 30 | 31 | # node-waf configuration 32 | .lock-wscript 33 | 34 | # Compiled binary addons (http://nodejs.org/api/addons.html) 35 | build/Release 36 | 37 | # Dependency directories 38 | node_modules/ 39 | jspm_packages/ 40 | 41 | # Typescript v1 declaration files 42 | typings/ 43 | 44 | # Optional npm cache directory 45 | .npm 46 | 47 | # Optional eslint cache 48 | .eslintcache 49 | 50 | # Optional REPL history 51 | .node_repl_history 52 | 53 | # Output of 'npm pack' 54 | *.tgz 55 | 56 | # Yarn Integrity file 57 | .yarn-integrity 58 | 59 | # dotenv environment variables file 60 | .env 61 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 John Silva 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ARCHIVED 2 | ## moved to [oxylbot](https://github.com/oxylbot) 3 | 4 | # oxyl 5 | 6 | Discord bot using Eris library 7 | 8 | Example `public-config.json`: 9 | ``` 10 | { 11 | "creators": ["155112606661607425"], 12 | "datadog": { "prefix": "oxyl." }, 13 | "defaultInvitePermissionNumber": 298183686, 14 | "donator": { 15 | "channel": "254770965018443776", 16 | "role": "292657073987125249" 17 | }, 18 | "prefixes": ["o!", "oxyl", "mention"], 19 | "postStats": true, 20 | "serverChannel": "265998475831934977", 21 | "shardsPerWorker": 2, 22 | "updates": { 23 | "channel": "270576454117359617", 24 | "guild": "254768930223161344", 25 | "role": "265293123821895680" 26 | }, 27 | "websocketServer": true 28 | } 29 | ``` 30 | beta is optional stating whether or not the bot is in beta, if true then some stuff is disabled 31 | 32 | creators is required and is an array of user ids 33 | 34 | datadog is optional if you want to send stats to datadog 35 | 36 | databaseName is optional, if you want a different database name besides Oxyl, set it there 37 | 38 | defaultInvitePermissionNumber is required and is used for the invite command 39 | 40 | donator is optional to say thank you for donating and add a role to a member in a server 41 | 42 | prefixes is an array of prefixes, mention is replaced with a bot mention 43 | 44 | postStats is not required, but if true see private-config to set the site tokens 45 | 46 | serverChannel is not required, sends server joins/leaves to a channel 47 | 48 | shardsPerWorker specifies how many shards should be put on each worker 49 | 50 | updates is all optional which sets up the update command 51 | 52 | websocketServer is optional and states whether or not to start a web socket server on 7025. should only be used if done with oxyl-site too 53 | 54 | 55 | Example `private-config.json`: 56 | ``` 57 | { 58 | "database": { "password": "cool fam" }, 59 | "dbotsKey": "very long key", 60 | "dbotsOrgKey": "very long key" 61 | "carbonKey": "shorter key", 62 | "googleKeys": ["a key", "Another"], 63 | "secret": "thing from bot page", 64 | "sentryLink": "url from sentry", 65 | "token": "thing from bot page", 66 | "webhook": "webhook url" 67 | } 68 | ``` 69 | database is optional but bot will most likely break without it. this is passed to rethinkdbdash login 70 | 71 | *Posting stuff - if a key is not present it will just not post to the site it corresponds to, if you have postStats off there is no reason for any of the keys* 72 | 73 | dbotsKey is to post to https://bots.discord.pw 74 | 75 | dbotsOrgKey is to post to http://discordbots.org 76 | 77 | carbonKey is to post to https://www.carbonitex.net/discord/bots 78 | 79 | googleKeys is an array of keys with the youtube data api v3 enabled and required for the bot to work correctly 80 | 81 | secret is only required if you use the web socket server 82 | 83 | sentryLink is optional, but if you want to use sentry.io put the link there 84 | 85 | token is required for the bot to log in 86 | 87 | webhook is optional for status about restarts and stuff 88 | 89 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "oxyl", 3 | "description": "Discord Bot, built with a website and command line tool hosted on a node.js cluster", 4 | "main": "./src/index.js", 5 | "scripts": { 6 | "test": "echo \"Error: no test specified\" && exit 1" 7 | }, 8 | "repository": { 9 | "type": "git", 10 | "url": "git+https://github.com/minemidnight/oxyl.git" 11 | }, 12 | "author": "minemidnight", 13 | "license": "MIT", 14 | "bugs": { 15 | "url": "https://github.com/minemidnight/oxyl/issues" 16 | }, 17 | "homepage": "https://github.com/minemidnight/oxyl#readme", 18 | "dependencies": { 19 | "babel-plugin-es6-promise": "^1.1.1", 20 | "babel-plugin-transform-async-to-generator": "^6.24.1", 21 | "babel-preset-es2015": "^6.24.1", 22 | "big-number": "^0.4.0", 23 | "bluebird": "^3.5.0", 24 | "body-parser": "^1.18.1", 25 | "chalk": "^2.1.0", 26 | "cheerio": "^1.0.0-rc.2", 27 | "cookie-parser": "^1.4.3", 28 | "duration-js": "^4.0.0", 29 | "eris": "github:abalabahaha/eris#dev", 30 | "eris-additions": "^1.2.5", 31 | "eris-lavalink": "git+https://github.com/briantanner/eris-lavalink.git", 32 | "es6-promise": "^4.1.1", 33 | "express": "^4.15.4", 34 | "express-babelify-middleware": "^0.2.1", 35 | "handlebars": "^4.0.10", 36 | "ioredis": "^3.1.4", 37 | "jimp": "^0.2.28", 38 | "raven": "^2.1.2", 39 | "rethinkdbdash": "^2.3.31", 40 | "superagent": "^3.6.0" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/bot/commands/admin/autoRole.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | process: async message => { 3 | let roleData; 4 | if(message.args[1]) { 5 | roleData = await r.table("autoRole").get(message.args[1].id).run(); 6 | } else { 7 | roleData = await r.table("autoRole").getAll(message.channel.guild.id, { index: "guildID" }).run(); 8 | } 9 | 10 | if(message.args[0] === "add") { 11 | if(!message.args[1]) { 12 | return __("commands.admin.autoRole.add.noArg", message); 13 | } else if(roleData) { 14 | return __("commands.admin.autoRole.add.alreadyAutorole", message, { role: message.args[1].name }); 15 | } 16 | 17 | await r.table("autoRole").insert({ 18 | guildID: message.channel.guild.id, 19 | roleID: message.args[1].id 20 | }).run(); 21 | return __("commands.admin.autoRole.add.success", message, { role: message.args[1].name }); 22 | } else if(message.args[0] === "remove") { 23 | if(!message.args[1]) { 24 | return __("commands.admin.autoRole.remove.noArg", message); 25 | } else if(!roleData) { 26 | return __("commands.admin.autoRole.remove.notAutorole", message, { role: message.args[1].name }); 27 | } 28 | 29 | await r.table("autoRole").get(roleData.roleID).delete().run(); 30 | return __("commands.admin.autoRole.remove.success", message, { role: message.args[1].name }); 31 | } else if(message.args[0] === "list") { 32 | if(roleData.length === 0) return __("commands.admin.autoRole.list.noAutoroles", message); 33 | 34 | let roles = roleData 35 | .filter(data => message.channel.guild.roles.has(data.roleID)) 36 | .map(data => message.channel.guild.roles.get(data.roleID).name); 37 | return __("commands.admin.autoRole.list.success", message, { roles: `\`${roles.join("`, `")}\`` }); 38 | } else { 39 | return __("commands.admin.autoRole.invalidSubcommand", message); 40 | } 41 | }, 42 | guildOnly: true, 43 | description: "Set up roles to automatically give to a user when they join", 44 | args: [{ 45 | type: "text", 46 | label: "add|remove|list" 47 | }, { 48 | type: "role", 49 | optional: true 50 | }] 51 | }; 52 | -------------------------------------------------------------------------------- /src/bot/commands/admin/editCommand.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | process: async message => { 3 | let command = Object.keys(bot.commands) 4 | .map(key => bot.commands[key]) 5 | .find(cmd => message.args[0] === cmd.name || ~cmd.aliases.indexOf(message.args[0])); 6 | if(!command) { 7 | return __("commands.admin.editCommand.commandNotFound", message); 8 | } else if(command.name === "editcommand" || command.type === "creator") { 9 | return __("commands.admin.editCommand.cantEdit", message); 10 | } 11 | 12 | if(message.args[1] === "info") { 13 | let editedInfo = await r.table("editedCommands").get([command.name, message.channel.guild.id]).run(); 14 | 15 | if(!editedInfo) { 16 | return __("commands.admin.editCommand.info.noEdits", message); 17 | } else { 18 | let roleNames = editedInfo.roles.map(roleID => message.channel.guild.roles.has(roleID) ? 19 | message.channel.guild.roles.get(roleID).name : roleID); 20 | 21 | return __("commands.admin.editCommand.success", message, { 22 | command: command.name, 23 | enabled: editedInfo.enabled, 24 | roles: editedInfo.roles.length === 0 ? __("phrases.noneRequired", message) : `\`${roleNames.join("`, `")}\`` 25 | }); 26 | } 27 | } else if(message.args[1] === "reset") { 28 | await r.table("editedCommands").get([command.name, message.channel.guild.id]).delete().run(); 29 | return __("commands.admin.editCommand.reset.success", message, { command: command.name }); 30 | } else if(message.args[1] === "toggle") { 31 | let editedInfo = await r.table("editedCommands").get([command.name, message.channel.guild.id]).run(); 32 | 33 | if(!editedInfo) { 34 | await r.table("editedCommands").insert({ 35 | id: [command.name, message.channel.guild.id], 36 | command: command.name, 37 | enabled: false, 38 | guildID: message.channel.guild.id, 39 | roles: [] 40 | }).run(); 41 | 42 | return __("commands.admin.editCommand.toggle.disabled", message, { command: command.name }); 43 | } else { 44 | await r.table("editedCommands").get(editedInfo.id).update({ enabled: !editedInfo.enabled }).run(); 45 | 46 | return __(`commands.admin.editCommand.toggle.${!editedInfo.enabled ? "en" : "dis"}abled`, 47 | message, { command: command.name }); 48 | } 49 | } else if(message.args[1] === "roles") { 50 | if(!message.args[2]) return __("commands.admin.editCommand.roles.noArg", message); 51 | 52 | let roles; 53 | try { 54 | roles = message.args[2].split(",").map(input => bot.utils.resolver.role(message, input.trim())); 55 | } catch(err) { 56 | return __("commands.admin.editCommand.roles.invalidRoles", message); 57 | } 58 | 59 | let editedInfo = await r.table("editedCommands").get([command.name, message.channel.guild.id]).run(); 60 | 61 | if(!editedInfo) { 62 | await r.table("editedCommands").insert({ 63 | id: [command.name, message.channel.guild.id], 64 | command: command.name, 65 | enabled: true, 66 | guildID: message.channel.guild.id, 67 | roles: roles.map(role => role.id) 68 | }).run(); 69 | } else { 70 | await r.table("editedCommands") 71 | .get([command.name, message.channel.guild.id]) 72 | .update({ roles: roles.map(role => role.id) }) 73 | .run(); 74 | } 75 | 76 | return __("commands.admin.editCommand.roles.success", message, { 77 | command: command.name, 78 | roles: `\`${roles.map(role => role.name).join("`, `")}\`` 79 | }); 80 | } else { 81 | return __("commands.admin.editCommand.invalidSubcommand", message); 82 | } 83 | }, 84 | description: "Edit a command by toggling it or requiring roles", 85 | args: [{ 86 | type: "text", 87 | label: "command name" 88 | }, { 89 | type: "text", 90 | label: "info|reset|toggle|roles" 91 | }, { 92 | type: "text", 93 | label: "roles (split with ,)", 94 | optional: true 95 | }] 96 | }; 97 | -------------------------------------------------------------------------------- /src/bot/commands/admin/ignore.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | process: async message => { 3 | let ignored = bot.ignoredChannels.has(message.channel.id); 4 | if(ignored) { 5 | bot.ignoredChannels.delete(message.channel.id); 6 | await r.table("ignoredChannels").get(message.channel.id).delete().run(); 7 | 8 | return __("commands.admin.ignore.enabled", message, { channel: message.channel.mention }); 9 | } else { 10 | bot.ignoredChannels.set(message.channel.id, message.channel.guild.id); 11 | await r.table("ignoredChannels").insert({ 12 | channelID: message.channel.id, 13 | guildID: message.channel.guild.id 14 | }).run(); 15 | 16 | return __("commands.admin.ignore.disabled", message, { channel: message.channel.mention }); 17 | } 18 | }, 19 | guildOnly: true, 20 | description: "Toggle Oxyl in a certain channel" 21 | }; 22 | -------------------------------------------------------------------------------- /src/bot/commands/admin/rolePersist.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | process: async message => { 3 | let roleData; 4 | if(message.args[1]) { 5 | roleData = await r.table("rolePersistRules").get(message.args[1].id).run(); 6 | } else { 7 | roleData = await r.table("rolePersistRules").getAll(message.channel.guild.id, { index: "guildID" }).run(); 8 | } 9 | 10 | if(message.args[0] === "add") { 11 | if(!message.args[1]) { 12 | return __("commands.admin.rolePersist.add.noArg", message); 13 | } else if(roleData) { 14 | return __("commands.admin.rolePersist.add.alreadyPersisted", message, { role: message.args[1].name }); 15 | } 16 | 17 | await r.table("rolePersistRules").insert({ 18 | guildID: message.channel.guild.id, 19 | roleID: message.args[1].id 20 | }).run(); 21 | return __("commands.admin.rolePersist.add.success", message, { role: message.args[1].name }); 22 | } else if(message.args[0] === "remove") { 23 | if(!message.args[1]) { 24 | return __("commands.admin.rolePersist.remove.noArg", message); 25 | } else if(!roleData) { 26 | return __("commands.admin.rolePersist.remove.notPersisted", message, { role: message.args[1].name }); 27 | } 28 | 29 | await r.table("rolePersistRules").get(roleData.roleID).delete().run(); 30 | return __("commands.admin.rolePersist.remove.success", message, { role: message.args[1].name }); 31 | } else if(message.args[0] === "list") { 32 | if(roleData.length === 0) return __("commands.admin.rolePersist.list.noPersistedRoles", message); 33 | 34 | let roles = roleData 35 | .filter(data => message.channel.guild.roles.has(data.roleID)) 36 | .map(data => message.channel.guild.roles.get(data.roleID).name); 37 | return __("commands.admin.rolePersist.list.success", message, { roles: `\`${roles.join("`, `")}\`` }); 38 | } else { 39 | return __("commands.admin.rolePersist.invalidSubcommand", message); 40 | } 41 | }, 42 | guildOnly: true, 43 | description: "Edit the roles which users keep when they rejoin the server", 44 | args: [{ 45 | type: "text", 46 | label: "add|remove|list" 47 | }, { 48 | type: "role", 49 | optional: true 50 | }] 51 | }; 52 | -------------------------------------------------------------------------------- /src/bot/commands/admin/settings.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | process: async message => { 3 | if(!message.args[0]) { 4 | return __("commands.admin.settings.noArgs", message, { settings: Object.keys(settings).join(", ") }); 5 | } 6 | 7 | let settingKey = Object.keys(settings).find(key => key.toLowerCase() === message.args[0].toLowerCase()); 8 | if(settings[settingKey]) { 9 | var setting = settings[settingKey]; 10 | setting.name = settingKey; 11 | } 12 | 13 | if(!setting) { 14 | return __("commands.admin.settings.invalidSetting", message); 15 | } else if(!message.args[1]) { 16 | const guild = message.channel.guild; 17 | let currentValue = await r.table("settings").get([setting.name, guild.id]).run(); 18 | 19 | return __("commands.admin.settings.settingInfo", message, { 20 | setting: setting.name, 21 | description: setting.description, 22 | accepted: `${setting.label || `<${setting.arg}>`}|reset`, 23 | current: currentValue ? currentValue.value : __("words.noValue", message) 24 | }); 25 | } else { 26 | let reset = false; 27 | 28 | if(message.args[1] === "reset") { 29 | reset = true; 30 | } else { 31 | try { 32 | var resolvedInput = bot.utils.resolver[setting.arg](message, message.args[1], setting.extra); 33 | } catch(err) { 34 | return err.message; 35 | } 36 | } 37 | 38 | const guild = message.channel.guild; 39 | let currentValue = await r.table("settings").get([setting.name, guild.id]).run(); 40 | 41 | if(!currentValue && reset) { 42 | return __("commands.admin.settings.cantReset", message, { setting: setting.name }); 43 | } else if(reset || (setting.type === "boolean")) { 44 | await r.table("settings").get([setting.name, guild.id]).delete().run(); 45 | if(setting.name === "prefix") bot.prefixes.delete(message.channel.guild.id); 46 | return __("commands.admin.settings.resetSuccess", message, { setting: setting.name }); 47 | } 48 | 49 | let insertData = { 50 | id: [setting.name, guild.id], 51 | name: setting.name, 52 | guildID: guild.id 53 | }; 54 | 55 | if(setting.name === "userlog" || setting.name === "modLog.channel") insertData.value = resolvedInput.id; 56 | else insertData.value = resolvedInput; 57 | 58 | if(setting.name === "modLog.track") { 59 | let addedRole = true; 60 | if(!currentValue) { 61 | insertData.value = [resolvedInput.id]; 62 | await r.table("settings").insert(insertData).run(); 63 | } else { 64 | let alreadyTracked = currentValue.value.indexOf(resolvedInput.id); 65 | if(~alreadyTracked) { 66 | currentValue.value.splice(alreadyTracked, 1); 67 | addedRole = false; 68 | } else { 69 | currentValue.value.push(resolvedInput.id); 70 | } 71 | 72 | if(currentValue.value.length === 0) { 73 | await r.table("settings").get([setting.name, guild.id]).delete().run(); 74 | } else { 75 | await r.table("settings").get([setting.name, guild.id]).update({ value: currentValue.value }).run(); 76 | } 77 | } 78 | 79 | return addedRole ? 80 | `Added \`${resolvedInput.name}\` to tracked roles` : 81 | `Removed \`${resolvedInput.name}\` from tracked roles`; 82 | } else if(setting.type === "boolean" && !resolvedInput) { 83 | if(currentValue) await r.table("settings").get([setting.name, guild.id]).delete().run(); 84 | } else if(currentValue) { 85 | await r.table("settings").get([setting.name, guild.id]).update({ value: insertData.value }).run(); 86 | } else { 87 | await r.table("settings").insert(insertData).run(); 88 | } 89 | 90 | if(setting.name === "prefix") bot.prefixes.set(message.channel.guild.id, insertData.value); 91 | return __("commands.admin.settings.setSuccess", message, { 92 | setting: setting.name, 93 | value: insertData.value 94 | }); 95 | } 96 | }, 97 | caseSensitive: true, 98 | guildOnly: true, 99 | aliases: ["setting"], 100 | description: "Configurate Oxyl's settings (per guild)", 101 | args: [{ 102 | type: "text", 103 | label: "option", 104 | optional: true 105 | }, { 106 | type: "text", 107 | label: "value", 108 | optional: true 109 | }] 110 | }; 111 | 112 | let settings = module.exports.settings = { 113 | channels: { 114 | arg: "boolean", 115 | description: "Toggle whether or not the `channel` comamnd is usable" 116 | }, 117 | farewell: { 118 | arg: "text", 119 | description: "Message sent in user log when a user leaves " + 120 | "(placeholders: {{mention}}, {{username}}, {{id}}, {{discrim}})" 121 | }, 122 | greeting: { 123 | arg: "text", 124 | description: "Message sent in user log when a user joins " + 125 | "(placeholders: {{mention}}, {{username}}, {{id}}, {{discrim}})" 126 | }, 127 | "greeting-dm": { 128 | arg: "boolean", 129 | description: "Toggle if Oxyl dm's join messages or sends them in a channel" 130 | }, 131 | "disable-music-messages": { 132 | arg: "boolean", 133 | description: "Toggle if Oxyl sends now playing and error messages" 134 | }, 135 | prefix: { 136 | arg: "text", 137 | description: "Set the prefix for the server" 138 | }, 139 | userlog: { 140 | arg: "textChannel", 141 | description: "Set the channel which greeting and farewell messages are announced" 142 | }, 143 | "modLog.channel": { 144 | arg: "textChannel", 145 | description: "Set the mod log channel to log moderator actions" 146 | }, 147 | "modLog.track": { 148 | arg: "role", 149 | description: "Toggle a roles to make mod log entries for (on role add/remove)" 150 | }, 151 | "modLog.kickat": { 152 | arg: "num", 153 | description: "Amount of warnings before a user will get kicked", 154 | extra: { min: 1 } 155 | }, 156 | "modLog.banat": { 157 | arg: "num", 158 | description: "Amount of warnings before a user will get banned", 159 | extra: { min: 1 } 160 | } 161 | }; 162 | -------------------------------------------------------------------------------- /src/bot/commands/creator/eval.js: -------------------------------------------------------------------------------- 1 | const util = require("util"); 2 | module.exports = { 3 | process: async message => { 4 | let guild = message.channel.guild, channel = message.channel, author = message.author, member = message.member; // eslint-disable-line 5 | 6 | try { 7 | let output = await eval(`(async function(){${message.args[0].replace(/“|”/g, "\"")}}).call()`); 8 | output = util.inspect(output, { depth: 0 }).substring(0, 1900); 9 | return `:white_check_mark: **Output:** ${bot.utils.codeBlock(output, "js")}`; 10 | } catch(error) { 11 | return `:x: **Error:** ${bot.utils.codeBlock(error)}`; 12 | } 13 | }, 14 | caseSensitive: true, 15 | description: "Execute code", 16 | args: [{ 17 | type: "text", 18 | label: "code" 19 | }] 20 | }; 21 | -------------------------------------------------------------------------------- /src/bot/commands/creator/exec.js: -------------------------------------------------------------------------------- 1 | const exec = Promise.promisify(require("child_process").exec); 2 | module.exports = { 3 | process: async message => { 4 | try { 5 | let stdout = await exec(message.args[0], { maxBuffer: Infinity }); 6 | return bot.utils.codeBlock(stdout.substring(0, 1950)); 7 | } catch(err) { 8 | return `Error executing command: ${bot.utils.codeBlock(err)}`; 9 | } 10 | }, 11 | caseSensitive: true, 12 | description: "Execute code", 13 | args: [{ 14 | type: "text", 15 | label: "command" 16 | }] 17 | }; 18 | -------------------------------------------------------------------------------- /src/bot/commands/creator/update.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | process: async message => { 3 | let updateInfo = bot.config.bot.updates; 4 | if(bot.config.beta) return "This is the beta bot!"; 5 | else if(!updateInfo) return "No update object was set in the config"; 6 | else if(!updateInfo.channel) return "A channel id to release the updates in has not been set in the config"; 7 | else if(!updateInfo.guild) return "A guild id which the updates role belongs to has not been set in the config"; 8 | else if(!updateInfo.role) return "An updates role has not been set in the config"; 9 | 10 | try { 11 | await bot.editRole(updateInfo.guild, updateInfo.role, { mentionable: true }); 12 | await bot.createMessage(updateInfo.channel, 13 | `<@&${updateInfo.role}>\n${bot.utils.codeBlock(message.args[0], "diff")}`); 14 | await bot.editRole(updateInfo.guild, updateInfo.role, { mentionable: false }); 15 | return "Update released"; 16 | } catch(err) { 17 | return `Error during releasing update: ${err.message}`; 18 | } 19 | }, 20 | caseSensitive: true, 21 | description: "Release an update", 22 | args: [{ 23 | type: "text", 24 | label: "update" 25 | }] 26 | }; 27 | -------------------------------------------------------------------------------- /src/bot/commands/default/byemom.js: -------------------------------------------------------------------------------- 1 | const images = require("../../modules/images/main"); 2 | module.exports = { 3 | process: async message => { 4 | let { buffer, ext } = await images.byeMom(message.args[0]); 5 | return ["", { 6 | file: buffer, 7 | name: `byemom.${ext}` 8 | }]; 9 | }, 10 | description: "Create a \"ok bye mom\" meme", 11 | args: [{ type: "text" }] 12 | }; 13 | -------------------------------------------------------------------------------- /src/bot/commands/default/cat.js: -------------------------------------------------------------------------------- 1 | const superagent = require("superagent"); 2 | module.exports = { 3 | process: async message => { 4 | let { body: { file } } = await superagent.get("http://random.cat/meow"); 5 | let { body: buffer } = await superagent.get(file); 6 | return ["", { 7 | file: buffer, 8 | name: file.substring(file.lastIndexOf("/") + 1) 9 | }]; 10 | }, 11 | description: "Grab a cat picture from random.cat" 12 | }; 13 | -------------------------------------------------------------------------------- /src/bot/commands/default/channel.js: -------------------------------------------------------------------------------- 1 | const channels = require("../../modules/channels.js"); 2 | module.exports = { 3 | process: async message => { 4 | let enabled = await channels.enabled(message.channel.guild.id); 5 | if(!enabled) return __("commands.default.channel.notEnabled", message); 6 | 7 | let botPerms = message.channel.guild.members.get(bot.user.id).permission; 8 | if(!botPerms.has("manageChannels")) return __("commands.default.channel.noPerms", message); 9 | if(message.args[0] === "create") { 10 | let current = await channels.get(message.member); 11 | if(current) return __("commands.default.channel.hasChannelAlready", message, { name: current.name }); 12 | 13 | let channel = await channels.create(message.member, message.args[1]); 14 | return __("commands.default.channel.created", message, { name: message.args[1] }); 15 | } else if(message.args[0] === "kick") { 16 | let current = await channels.get(message.member); 17 | if(!current) return __("commands.default.channel.needsChannel", message); 18 | 19 | try { 20 | message.args[1] = await bot.utils.resolver.user(message, message.args[1]); 21 | } catch(err) { 22 | return err.message; 23 | } 24 | 25 | if(!current.voiceMembers.has(message.args[1].id)) return __("commands.default.channel.cantKick", message); 26 | await bot.editChannelPermission(current.id, message.args[1].id, 0, 1048576, "member"); 27 | let tempChannel = await bot.createChannel(message.channel.guild.id, "VOICEKICK", 2); 28 | await current.voiceMembers.get(message.args[1].id).edit({ channelID: tempChannel.id }); 29 | await tempChannel.delete(); 30 | 31 | return __("commands.default.channel.kicked", message, 32 | { user: `${message.args[1].username}#${message.args[1].discriminator}` }); 33 | } else if(message.args[0] === "bitrate") { 34 | let current = await channels.get(message.member); 35 | if(!current) return __("commands.default.channel.needsChannel", message); 36 | 37 | try { 38 | message.args[1] = await bot.utils.resolver.num(message, message.args[1], { min: 8, max: 96 }); 39 | } catch(err) { 40 | return err.message; 41 | } 42 | 43 | await current.edit({ bitrate: message.args[1] * 1000 }); 44 | return __("commands.default.channel.changedBitrate", message, { bitrate: message.args[1] }); 45 | } else if(message.args[0] === "userlimit") { 46 | let current = await channels.get(message.member); 47 | if(!current) return __("commands.default.channel.needsChannel", message); 48 | 49 | try { 50 | message.args[1] = await bot.utils.resolver.num(message, message.args[1], { min: 0, max: 99 }); 51 | } catch(err) { 52 | return err.message; 53 | } 54 | 55 | await current.edit({ userLimit: message.args[1] }); 56 | return __("commands.default.channel.changedUserLimit", message, { limit: message.args[1] }); 57 | } else if(message.args[0] === "privacy") { 58 | let current = await channels.get(message.member); 59 | if(!current) return __("commands.default.channel.needsChannel", message); 60 | 61 | if(message.args[1] === "private") { 62 | await bot.editChannelPermission(current.id, message.channel.guild.id, 0, 1048576, "role"); 63 | return __("commands.default.channel.nowPrivate", message); 64 | } else if(message.args[1] === "public") { 65 | await bot.editChannelPermission(current.id, message.channel.guild.id, 1048576, 0, "role"); 66 | return __("commands.default.channel.nowPublic", message); 67 | } else { 68 | return __("commands.default.channel.invalidPrivacy", message); 69 | } 70 | } else if(message.args[0] === "whitelist" || message.args[0] === "wl") { 71 | let current = await channels.get(message.member); 72 | if(!current) return __("commands.default.channel.needsChannel", message); 73 | 74 | try { 75 | message.args[1] = await bot.utils.resolver.user(message, message.args[1]); 76 | } catch(err) { 77 | return err.message; 78 | } 79 | 80 | await bot.editChannelPermission(current.id, message.args[1].id, 1048576, 0, "member") 81 | .catch(err => {}); // eslint-disable-line 82 | return __("commands.default.channel.whitelisted", message, 83 | { user: `${message.args[1].username}#${message.args[1].discriminator}` }); 84 | } else { 85 | return __("commands.default.channel.invalidSubcommand", message); 86 | } 87 | }, 88 | guildOnly: true, 89 | description: "Have users create their own channels, with options to make it private, kick users, and more", 90 | args: [{ 91 | type: "text", 92 | label: "create|kick|bitrate|userlimit|privacy|whitelist" 93 | }, { 94 | type: "text", 95 | label: "name|user|number|private/public|user" 96 | }] 97 | }; 98 | -------------------------------------------------------------------------------- /src/bot/commands/default/dog.js: -------------------------------------------------------------------------------- 1 | const superagent = require("superagent"); 2 | module.exports = { 3 | process: async message => { 4 | let { body: [file] } = await superagent.get("http://shibe.online/api/shibes?count=1"); 5 | let { body: buffer } = await superagent.get(file); 6 | return ["", { 7 | file: buffer, 8 | name: file.substring(file.lastIndexOf("/") + 1) 9 | }]; 10 | }, 11 | description: "Grab a dog image from shibe.online" 12 | }; 13 | -------------------------------------------------------------------------------- /src/bot/commands/default/donate.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | process: async message => __("commands.default.donate.success", message), 3 | description: "Get an link to Oxyl's patreon" 4 | }; 5 | -------------------------------------------------------------------------------- /src/bot/commands/default/google.js: -------------------------------------------------------------------------------- 1 | const cheerio = require("cheerio"); 2 | const superagent = require("superagent"); 3 | 4 | module.exports = { 5 | process: async message => { 6 | let { text: body } = await superagent 7 | .get(`https://www.google.com/search?q=${encodeURIComponent(message.args[0])}&hl=en`); 8 | let $ = cheerio.load(body); // eslint-disable-line id-length 9 | 10 | let resultmsg = ""; 11 | let results = $(".g .r a"); 12 | if(!results.length) return __("commands.default.google.noResults", message); 13 | 14 | let dictionary = $("#ires ol").children().eq(0).has("table").has("ol").eq(0); 15 | if(dictionary.length) { 16 | resultmsg += `\n\n**${__("phrases.dictionary", message)}**`; 17 | 18 | let [word, pronunciation] = dictionary.find(".r").eq(0).find("div span") 19 | .map((index, element) => $(element).text()).get(); 20 | 21 | let tableData = dictionary.find("table tbody").eq(0).find("tr td"); 22 | resultmsg += `\n${word} (${pronunciation})`; 23 | for(let i = 0; i < tableData.length; i++) { 24 | let partOfSpeech = tableData.eq(i).find("div").text(); 25 | if(!partOfSpeech) continue; 26 | resultmsg += `\n\n_${partOfSpeech}_`; 27 | 28 | let definitions = tableData.eq(i).find("ol li").map((index, element) => $(element).text()).get(); 29 | definitions.forEach((definition, index) => resultmsg += `\n**${index + 1}**. ${definition}`); 30 | } 31 | } 32 | 33 | let translate = $(".g div table.ts tbody tr td h3.r"); 34 | if(translate.length) { 35 | resultmsg += `\n\n**${__("phrases.translation", message)}**`; 36 | 37 | let [input, output] = translate.find("span").map((index, element) => $(element).text()).get(); 38 | resultmsg += `\n${input} => ${output}`; 39 | } 40 | 41 | let calculator = $("#topstuff ._tLi tbody tr td").eq(2).find("span.nobr h2.r"); 42 | if(calculator.length) resultmsg += `\n\n**${__("phrases.calculator", message)}**\n${calculator.text()}`; 43 | 44 | let infoCard = $("#rhs_block ol .g"); 45 | if(infoCard.length) { 46 | resultmsg += `\n\n**${__("phrases.infoCard", message)}**`; 47 | 48 | let title = infoCard.find("._o0d div ._B5d").text(); 49 | let info = infoCard.find("._o0d div ._zdb._Pxg").text(); 50 | let description = infoCard.find("._o0d ._tXc span").clone().children().remove().end().text(); 51 | 52 | if(info) resultmsg += `\n${title} (${info})\n${description}`; 53 | else resultmsg += `\n${title}\n${description}`; 54 | } 55 | 56 | resultmsg += `\n\n**${__("phrases.searchResults", message)}**`; 57 | for(let i = 0; i < 3; i++) { 58 | let ele = results.eq(i); 59 | 60 | let link = ele.attr("href"); 61 | if(!link) continue; 62 | else if(~link.indexOf("/url?q=")) link = link.substring(link.indexOf("/url?q=") + 7, link.indexOf("&sa=")); 63 | 64 | 65 | if(i) link = `<${link}>`; 66 | resultmsg += `\n${link}`; 67 | } 68 | 69 | return resultmsg; 70 | }, 71 | description: "Search google for a query", 72 | aliases: ["g"], 73 | args: [{ 74 | type: "text", 75 | label: "query" 76 | }] 77 | }; 78 | -------------------------------------------------------------------------------- /src/bot/commands/default/help.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | process: async message => { 3 | if(message.args[0]) { 4 | let command = Object.keys(bot.commands) 5 | .map(key => bot.commands[key]) 6 | .find(cmd => message.args[0] === cmd.name || ~cmd.aliases.indexOf(message.args[0])); 7 | if(!command) return __("commands.default.help.commandNotFound", message); 8 | 9 | let helpMsg = `__**Command Name**__: ${command.name}\n`; 10 | helpMsg += `Category: ${command.type}\n`; 11 | helpMsg += `Usage: ${command.usage}\n`; 12 | helpMsg += `Aliases: ${command.aliases.length ? command.aliases.join(", ") : "None"}\n`; 13 | helpMsg += `Description: ${command.description || "None provided"}\n`; 14 | if(command.perm) helpMsg += `Required Permission: ${command.perm}\n`; 15 | else if(command.guildOnly) helpMsg += `This command will only work in guilds\n`; 16 | helpMsg += `Uses: ${command.uses}`; 17 | 18 | return __("commands.default.help.commandInfo", message, { 19 | command: command.name, 20 | category: command.type, 21 | usage: command.usage, 22 | aliases: command.aliases.length ? command.aliases.join(", ") : __("words.none", message, {}, true), 23 | description: command.description || __("phrases.noneProvided", message), 24 | perm: command.perm || __("phrases.noneRequired", message) 25 | }); 26 | } else { 27 | let disabled; 28 | if(message.channel.guild) { 29 | disabled = await r.table("editedCommands") 30 | .getAll(message.channel.guild.id, { index: "guildID" }) 31 | .filter({ enabled: false }).getField("command").run(); 32 | } else { 33 | disabled = []; 34 | } 35 | 36 | let commandMsg = "", commandTypes = {}; 37 | for(let cmd in bot.commands) { 38 | cmd = bot.commands[cmd]; 39 | if(cmd.type === "creator") continue; 40 | else if(~disabled.indexOf(cmd.name)) continue; 41 | else if(!commandTypes[cmd.type]) commandTypes[cmd.type] = []; 42 | commandTypes[cmd.type].push(cmd.name); 43 | commandTypes[cmd.type].concat(cmd.aliases); 44 | } 45 | 46 | for(let category in commandTypes) { 47 | commandTypes[category].sort(); 48 | commandMsg += `\n__${category.charAt(0).toUpperCase() + category.substring(1)}__\n`; 49 | commandMsg += commandTypes[category].join(", "); 50 | commandMsg += `\n`; 51 | } 52 | 53 | return __("commands.default.help.commandList", message, { 54 | count: Object.keys(bot.commands).length, 55 | commands: commandMsg 56 | }); 57 | } 58 | }, 59 | description: "List commands, or get info on one", 60 | args: [{ 61 | type: "text", 62 | label: "command", 63 | optional: true 64 | }] 65 | }; 66 | -------------------------------------------------------------------------------- /src/bot/commands/default/invite.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | process: async message => { 3 | let invite = `https://discordapp.com/oauth2/authorize?client_id=${bot.user.id}&scope=bot`; 4 | if(bot.config.bot.defaultInvitePermissionNumber) { 5 | invite += `&permissions=${bot.config.bot.defaultInvitePermissionNumber}`; 6 | } 7 | 8 | return __("commands.default.invite.success", message, { 9 | invite: `<${invite}>`, 10 | server: "https://discord.gg/9wkTDcE" 11 | }); 12 | }, 13 | description: "Get an invite link for Oxyl and the Support Server" 14 | }; 15 | -------------------------------------------------------------------------------- /src/bot/commands/default/locale.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | process: async message => { 3 | if(!message.args[0]) { 4 | let currentLocale = await r.table("locales").get(message.author.id).run(); 5 | currentLocale = currentLocale ? currentLocale.locale : "en"; 6 | 7 | return __("commands.default.locale.noArg", message, { 8 | locales: bot.locales.map(locale => { 9 | let format = `${locale}: `; 10 | format += __("commands.default.locale.nativeName", { locale }); 11 | if(locale !== "en") format += ` (${__("commands.default.locale.englishName", { locale })})`; 12 | 13 | return format; 14 | }).join("\n"), 15 | locale: currentLocale 16 | }); 17 | } else if(!~bot.locales.indexOf(message.args[0])) { 18 | return __("commands.default.locale.invalidLocale", message); 19 | } else { 20 | let currentLocale = await r.table("locales").get(message.author.id).run(); 21 | if(!currentLocale) { 22 | await r.table("locales").insert({ id: message.author.id, locale: message.args[0] }).run(); 23 | } else { 24 | await r.table("locales").get(message.author.id).update({ locale: message.args[0] }).run(); 25 | } 26 | 27 | bot.localeCache.set(message.author.id, message.args[0]); 28 | message.locale = message.args[0]; 29 | 30 | let name = __("commands.default.locale.nativeName", message); 31 | return __("commands.default.locale.changedSuccess", message, { locale: `${message.args[0]} (${name})` }); 32 | } 33 | }, 34 | description: "Set your language", 35 | aliases: ["lang"], 36 | args: [{ 37 | type: "text", 38 | label: "language", 39 | optional: true 40 | }] 41 | }; 42 | -------------------------------------------------------------------------------- /src/bot/commands/default/needsmorejpeg.js: -------------------------------------------------------------------------------- 1 | const images = require("../../modules/images/main"); 2 | module.exports = { 3 | process: async message => { 4 | let { buffer, ext } = await images.needsMoreJpeg(message.args[0]); 5 | return ["", { 6 | file: buffer, 7 | name: `needsmorejpeg.${ext}` 8 | }]; 9 | }, 10 | description: "Make the quality of an image worse", 11 | args: [{ type: "image" }] 12 | }; 13 | -------------------------------------------------------------------------------- /src/bot/commands/default/ping.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | process: async message => { 3 | let latency = message.channel.guild ? 4 | message.channel.guild.shard.latency : 5 | bot.shards.get(0).latency; 6 | return __("commands.default.ping.success", message, { latency }); 7 | }, 8 | description: "Test Oxyl's responsiveness" 9 | }; 10 | -------------------------------------------------------------------------------- /src/bot/commands/default/remind.js: -------------------------------------------------------------------------------- 1 | const Duration = require("duration-js"); 2 | module.exports = { 3 | process: async message => { 4 | if(!~message.args[0].indexOf(" in ")) { 5 | return __("commands.default.remind.noTime", message); 6 | } 7 | 8 | let action = message.args[0].substring(0, message.args[0].lastIndexOf("in")).trim(); 9 | let time = message.args[0].substring(message.args[0].lastIndexOf("in") + 2).trim() 10 | .replace(/weeks?/g, "w") 11 | .replace(/days?/g, "d") 12 | .replace(/hours?/g, "h") 13 | .replace(/minutes?/g, "m") 14 | .replace(/seconds?/g, "s") 15 | .replace(/milliseconds?/g, "ms") 16 | .replace(/ /g, ""); 17 | 18 | try { 19 | var duration = new Duration(time); 20 | duration = duration.milliseconds(); 21 | if(duration < 30000 || duration > 2419200000) { 22 | return __("commands.default.remind.invalidTime", message); 23 | } 24 | } catch(err) { 25 | return err.message; 26 | } 27 | 28 | let date = Date.now(); 29 | let dmChannel = message.channel.guild ? await message.author.getDMChannel() : message.channel.id; 30 | await r.table("timedEvents").insert({ 31 | action, 32 | createdAt: date, 33 | channelID: dmChannel.id, 34 | date: date + duration, 35 | type: "reminder", 36 | userID: message.author.id 37 | }).run(); 38 | 39 | return __("commands.default.remind.success", message, { action, time }); 40 | }, 41 | description: "Create a reminder", 42 | args: [{ 43 | type: "text", 44 | label: " in " 45 | }] 46 | }; 47 | -------------------------------------------------------------------------------- /src/bot/commands/default/roleme.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | process: async message => { 3 | if(message.args[0] === "list") { 4 | let roleList = await r.table("roleMe").getAll(message.channel.guild.id, { index: "guildID" }).run(); 5 | if(roleList.length === 0) return __("commands.default.roleMe.list.noneAvailable", message); 6 | 7 | let roles = roleList 8 | .filter(data => message.channel.guild.roles.has(data.roleID)) 9 | .map(data => message.channel.guild.roles.get(data.roleID).name); 10 | return __("commands.default.roleMe.list.success", message, { roles: `\`${roles.join("`, `")}\`` }); 11 | } else if(message.args[0].startsWith("add ")) { 12 | if(!(message.member.permission.has("administrator") || message.author.id === message.channel.guild.ownerID)) { 13 | return __("commands.default.roleMe.noPerms", message); 14 | } 15 | 16 | let role; 17 | try { 18 | role = bot.utils.resolver.role(message, message.args[0].substring(4)); 19 | } catch(err) { 20 | return err.message; 21 | } 22 | 23 | let roleAvailable = await r.table("roleMe").get(role.id).run(); 24 | if(roleAvailable) return __("commands.default.roleMe.add.alreadyAvailable", message); 25 | 26 | await r.table("roleMe").insert({ 27 | guildID: message.channel.guild.id, 28 | roleID: role.id 29 | }).run(); 30 | return __("commands.default.roleMe.add.success", message, { role: role.name }); 31 | } else if(message.args[0].startsWith("remove ")) { 32 | if(!(message.member.permission.has("administrator") || message.author.id === message.channel.guild.ownerID)) { 33 | return __("commands.default.roleMe.noPerms", message); 34 | } 35 | 36 | let role; 37 | try { 38 | role = bot.utils.resolver.role(message, message.args[0].substring(7)); 39 | } catch(err) { 40 | return err.message; 41 | } 42 | 43 | let roleAvailable = await r.table("roleMe").get(role.id).run(); 44 | if(!roleAvailable) return __("commands.default.roleMe.notAvailable", message); 45 | 46 | await r.table("roleMe").get(role.id).delete().run(); 47 | return __("commands.default.roleMe.remove.success", message, { role: role.name }); 48 | } else { 49 | let role; 50 | try { 51 | role = bot.utils.resolver.role(message, message.args[0]); 52 | } catch(err) { 53 | return err.message; 54 | } 55 | 56 | let roleAvailable = await r.table("roleMe").get(role.id).run(); 57 | if(!roleAvailable) { 58 | return __("commands.default.roleMe.notAvailable", message); 59 | } else if(!role.addable) { 60 | return __("commands.default.roleMe.invalidBotPerms", message); 61 | } else { 62 | let hasRole = ~message.member.roles.indexOf(role.id); 63 | if(hasRole) { 64 | await message.member.removeRole(role.id, "RoleMe command"); 65 | return __("commands.default.roleMe.removedRole", message, { role: role.name }); 66 | } else { 67 | await message.member.addRole(role.id, "RoleMe command"); 68 | return __("commands.default.roleMe.gaveRole", message, { role: role.name }); 69 | } 70 | } 71 | } 72 | }, 73 | guildOnly: true, 74 | description: "Recieve a role or edit roles that are recievable", 75 | args: [{ 76 | type: "text", 77 | label: "|list|add/remove " 78 | }] 79 | }; 80 | -------------------------------------------------------------------------------- /src/bot/commands/default/serverinfo.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | process: async message => { 3 | let guild = message.channel.guild; 4 | let botCount = guild.members.filter(member => member.user.bot).length; 5 | let owner = guild.members.get(guild.ownerID).user; 6 | 7 | return __("commands.default.serverInfo.success", message, { 8 | guild: guild.name, 9 | channels: guild.channels.size, 10 | members: guild.memberCount, 11 | users: guild.memberCount - botCount, 12 | userPercent: (((guild.memberCount - botCount) / guild.memberCount) * 100).toFixed(2), 13 | bots: botCount, 14 | botPercent: ((botCount / guild.memberCount) * 100).toFixed(2), 15 | creationDate: bot.utils.formatDate(guild.createdAt), 16 | owner: `${owner.username}#${owner.discriminator}`, 17 | ownerID: owner.id, 18 | icon: guild.iconURL ? `<${guild.iconURL}>` : __("words.none", message, {}, true) 19 | }); 20 | }, 21 | guildOnly: true, 22 | aliases: ["guildinfo"], 23 | description: "Get info about the guild this command was executed in" 24 | }; 25 | -------------------------------------------------------------------------------- /src/bot/commands/default/stats.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | process: async message => { 3 | let results = (await process.output({ 4 | type: "all_shards", 5 | input: () => [ 6 | bot.guilds.filter(guild => guild.large).length, 7 | bot.guilds.size, 8 | process.memoryUsage().heapUsed, 9 | Array.from(bot.players.values()).filter(player => player.connection).length 10 | ] 11 | })).results; 12 | let largeGuilds = results.map(res => res[0]).reduce((a, b) => a + b); 13 | let totalGuilds = results.map(res => res[1]).reduce((a, b) => a + b); 14 | let streams = results.map(res => res[3]).reduce((a, b) => a + b); 15 | 16 | let workerUsage = process.memoryUsage().heapUsed; 17 | let masterUsage = (await process.output({ 18 | type: "master", 19 | input: () => process.memoryUsage().heapUsed 20 | })).result; 21 | let totalUsage = masterUsage + results.map(res => res[2]).reduce((a, b) => a + b); 22 | 23 | return __("commands.default.stats.success", message, { 24 | largeGuilds, 25 | smallGuilds: totalGuilds - largeGuilds, 26 | totalGuilds, 27 | streamCount: streams, 28 | workerUsage: (workerUsage / Math.pow(1024, 2)).toFixed(2), 29 | totalUsage: (totalUsage / Math.pow(1024, 3)).toFixed(2), 30 | worker: cluster.worker.id, 31 | shardRange: `${cluster.worker.shardRange}`, 32 | uptime: bot.utils.parseMs(Date.now() - bot.startTime), 33 | website: "http://minemidnight.work" 34 | }); 35 | }, 36 | description: "View information about Oxyl", 37 | aliases: ["info"] 38 | }; 39 | -------------------------------------------------------------------------------- /src/bot/commands/default/tags.js: -------------------------------------------------------------------------------- 1 | const tags = require("../../modules/tags.js"); 2 | module.exports = { 3 | process: async message => { 4 | let arg = message.args[0]; 5 | if(arg.toLowerCase() === "list") { 6 | let page = 1; 7 | if(~arg.indexOf(" ")) page = parseInt(arg.substring(arg.indexOf(" ") + 1)); 8 | if(isNaN(page) || page < 1) page = 1; 9 | 10 | let tagList = await tags.list(page); 11 | if(!tagList.length) { 12 | return __("commands.default.tags.list.noTags", message); 13 | } else { 14 | tagList = tagList.map(tag => tag.name).join(", "); 15 | return __("commands.default.tags.list.success", message, { page, tags: tagList }); 16 | } 17 | } else if(arg.toLowerCase().startsWith("raw")) { 18 | if(!~arg.indexOf(" ")) return __("commands.default.tags.raw.noTag", message); 19 | 20 | let tagName = arg.substring(arg.indexOf(" ") + 1).toLowerCase(); 21 | let tag = await tags.get(tagName); 22 | if(!tag) return __("commands.default.tags.noTagFound", message, { name: tagName }); 23 | 24 | let content = tag.content.replace(/```/g, "\\`\\`\\`"); 25 | return __("commands.default.tags.raw.success", message, { content, name: tagName }); 26 | } else if(arg.toLowerCase().startsWith("delete")) { 27 | if(!~arg.indexOf(" ")) return __("commands.default.tags.delete.noTag", message); 28 | 29 | let tagName = arg.substring(arg.indexOf(" ") + 1).toLowerCase(); 30 | let tag = await tags.get(tagName); 31 | if(!tag) return __("commands.default.tags.noTagFound", message, { name: tagName }); 32 | else if(tag.ownerID !== message.author.id) return __("commands.default.tags.delete.notOwner"); 33 | 34 | await tags.delete(tagName); 35 | return __("commands.default.tags.delete.success", message, { name: tagName }); 36 | } else if(arg.toLowerCase().startsWith("create")) { 37 | if(!~arg.indexOf(" ")) return __("commands.default.tags.create.noArgs", message); 38 | 39 | let tagName = arg.substring(arg.indexOf(" ") + 1); 40 | let content = tagName.substring(tagName.indexOf(" ") + 1); 41 | tagName = tagName.substring(0, tagName.indexOf(" ")).toLowerCase(); 42 | if(!tagName || !content || !content.length) return __("commands.default.tags.create.noContent", message); 43 | 44 | let tag = await tags.get(tagName); 45 | if(tag) return __("commands.default.tags.create.alreadyExists", message, { name: tagName }); 46 | 47 | await tags.create(tagName, message.author.id, content); 48 | return __("commands.default.tags.create.success", message, { name: tagName }); 49 | } else { 50 | let tagName = arg.toLowerCase(); 51 | let tag = await tags.get(tagName); 52 | if(!tag) return __("commands.default.tags.noTagFound", message, { name: tagName }); 53 | 54 | return tag.content; 55 | } 56 | }, 57 | caseSensitive: true, 58 | description: "Create, delete, display, and use tags", 59 | aliases: ["t", "tag"], 60 | args: [{ 61 | type: "text", 62 | label: "|list []|raw |delete |create " 63 | }] 64 | }; 65 | -------------------------------------------------------------------------------- /src/bot/commands/default/userinfo.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | process: async message => { 3 | let user = message.args[0] || message.author; 4 | let member = message.channel.guild.members.get(user.id); 5 | 6 | return __("commands.default.userInfo.success", message, { 7 | user: `${user.username}#${user.discriminator}`, 8 | id: user.id, 9 | avatar: `<${user.avatarURL}>`, 10 | game: member.game ? member.game.name : __("words.none", message, {}, true), 11 | joinDate: bot.utils.formatDate(user.createdAt), 12 | guildJoin: bot.utils.formatDate(member.joinedAt), 13 | status: member.status.toUpperCase() 14 | }); 15 | }, 16 | guildOnly: true, 17 | description: "View information about a user", 18 | args: [{ 19 | type: "user", 20 | optional: "true" 21 | }] 22 | }; 23 | -------------------------------------------------------------------------------- /src/bot/commands/default/workerinfo.js: -------------------------------------------------------------------------------- 1 | const left = (string, length, spacing = " ") => { 2 | string = string.toString(); 3 | if(string.length >= length) return string; 4 | else return spacing.repeat(length - string.length) + string; 5 | }; 6 | 7 | module.exports = { 8 | process: async message => { 9 | const { results: info } = await process.output({ 10 | type: "all_shards", 11 | input: () => ({ 12 | id: cluster.worker.id, 13 | guilds: bot.guilds.size, 14 | memoryUsed: process.memoryUsage().heapUsed, 15 | streams: Array.from(bot.players.values()).filter(player => player.connection).length, 16 | shards: cluster.worker.shardRange.substring(cluster.worker.shardRange.indexOf(" ") + 1), 17 | uptime: Date.now() - bot.startTime 18 | }) 19 | }); 20 | 21 | const maxLen = {}; 22 | maxLen.id = Math.max(...info.map(data => data.id.toString().length)); 23 | 24 | const totalGuilds = info.reduce((a, b) => a + b.guilds, 0); 25 | maxLen.guilds = totalGuilds.toString().length; 26 | 27 | const totalMemory = (info.reduce((a, b) => a + b.memoryUsed, 0) / Math.pow(1024, 3)).toFixed(2); 28 | maxLen.memory = Math.max( 29 | totalMemory.length, 30 | ...info.map(data => (data.memoryUsed / Math.pow(1024, 3)).toFixed(2).length) 31 | ); 32 | 33 | const totalStreams = info.reduce((a, b) => a + b.streams, 0); 34 | maxLen.streams = totalStreams.toString().length; 35 | 36 | const totalShards = bot.options.maxShards; 37 | maxLen.shards = Math.max(...info.map(data => data.shards.length)); 38 | 39 | maxLen.uptime = Math.max(...info.map(data => bot.utils.parseMs(data.uptime).length)); 40 | 41 | const workerInfo = []; 42 | info.sort((a, b) => a.id - b.id).forEach(data => { 43 | let line = ""; 44 | line += cluster.worker.id === data.id ? "* " : " "; 45 | line += left(data.id, maxLen.id); 46 | line += ": Guilds "; 47 | line += left(data.guilds, maxLen.guilds); 48 | line += ", Streams "; 49 | line += left(data.streams, maxLen.streams); 50 | line += ", Shards "; 51 | line += left(data.shards, maxLen.shards); 52 | line += ", RAM "; 53 | line += left((data.memoryUsed / Math.pow(1024, 3)).toFixed(2), maxLen.memory); 54 | line += "GiB, Uptime "; 55 | line += left(bot.utils.parseMs(data.uptime), maxLen.uptime); 56 | 57 | workerInfo.push(line); 58 | }); 59 | workerInfo.push(` T: Guilds ${totalGuilds}, Streams ${totalStreams}, ` + 60 | `Shards ${left(totalShards, maxLen.shards)}, RAM ${left(totalMemory, maxLen.memory)}GiB`); 61 | 62 | return bot.utils.codeBlock(workerInfo.join("\n"), "prolog"); 63 | }, 64 | description: "Get info about each worker that hosts a bot" 65 | }; 66 | -------------------------------------------------------------------------------- /src/bot/commands/default/xkcd.js: -------------------------------------------------------------------------------- 1 | const superagent = require("superagent"); 2 | let maxComic = 0; 3 | module.exports = { 4 | process: async message => { 5 | if(message.args[0] && message.args[0] > maxComic) { 6 | return __("commands.default.xkcd.invalidComic", message, { comicRange: `1-${maxComic}` }); 7 | } 8 | 9 | let comic = message.args[0] || Math.floor(Math.random() * maxComic) + 1; 10 | let { body } = await superagent.get(`http://xkcd.com/${comic}/info.0.json`); 11 | 12 | let { body: buffer } = await superagent.get(body.img); 13 | return [`\n**${body.title}** (#${comic})`, { 14 | file: buffer, 15 | name: body.img.substring(body.img.lastIndexOf("/") + 1) 16 | }]; 17 | }, 18 | updateComic: async () => maxComic = (await superagent.get("https://xkcd.com/info.0.json")).body.num, 19 | description: "Grab a xkcd from xkcd.com", 20 | args: [{ 21 | label: "comic number", 22 | type: "num", 23 | min: 1, 24 | optional: true 25 | }] 26 | }; 27 | 28 | module.exports.updateComic(); 29 | setInterval(module.exports.updateComic, 10800000); 30 | -------------------------------------------------------------------------------- /src/bot/commands/moderator/ban.js: -------------------------------------------------------------------------------- 1 | const modLog = require("../../modules/modLog.js"); 2 | module.exports = { 3 | process: async message => { 4 | let banPerms = message.channel.guild.members.get(bot.user.id).permission.has("banMembers"); 5 | if(!banPerms) return __("commands.moderator.ban.noPerms", message); 6 | 7 | let member = message.channel.guild.members.get(message.args[0].id); 8 | if(!member) return __("phrases.notInGuild", message); 9 | 10 | if(!member.bannable) { 11 | return __("commands.moderator.ban.botCantBan", message, { user: member.user.username }); 12 | } else if(!member.punishable(message.member)) { 13 | return __("commands.moderator.ban.youCantBan", message, { user: member.user.username }); 14 | } else { 15 | if(message.args[1]) { 16 | let guild = message.channel.guild; 17 | let channel = await modLog.channel(guild); 18 | if(channel) { 19 | modLog.presetReasons[guild.id] = { mod: message.author, reason: message.args[1] }; 20 | } 21 | } 22 | 23 | member.ban(7, message.args[1]); 24 | return __("commands.moderator.ban.success", message, { user: member.user.username }); 25 | } 26 | }, 27 | guildOnly: true, 28 | perm: "banMembers", 29 | description: "Ban a user from the guild", 30 | args: [{ type: "user" }, { 31 | type: "text", 32 | label: "reason", 33 | optional: true 34 | }] 35 | }; 36 | -------------------------------------------------------------------------------- /src/bot/commands/moderator/censor.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | process: async message => { 3 | message.args[0] = message.args[0].toLowerCase(); 4 | if(message.args[1]) message.args[1] = message.args[1].toLowerCase(); 5 | if(message.args[0] === "add") { 6 | if(!message.args[1]) { 7 | return __("commands.moderator.censor.add.noAction", message); 8 | } else if(!~["none", "warn", "kick", "softban", "ban"].indexOf(message.args[1])) { 9 | return __("commands.moderator.censor.add.invalidAction", message); 10 | } else if(!message.args[2]) { 11 | return __("commands.moderator.censor.add.noRegex", message); 12 | } else { 13 | try { 14 | RegExp(message.args[2]); 15 | } catch(err) { 16 | return __("commands.moderator.censor.add.invalidRegex", message, { error: err.message }); 17 | } 18 | 19 | let id = 1, censors = await r.table("censors").getAll(message.channel.guild.id, { index: "guildID" }).run(); 20 | if(censors.length) id = Math.max(...censors.map(censor => censor.censorID)) + 1; 21 | 22 | let censorsCache = bot.censors.get(message.channel.guild.id); 23 | if(censorsCache) { 24 | censorsCache.set(id, { action: message.args[1], regex: message.args[2] }); 25 | } else { 26 | bot.censors.set(message.channel.guild.id, new Map()) 27 | .get(message.channel.guild.id) 28 | .set(id, { action: message.args[1], regex: message.args[2] }); 29 | } 30 | 31 | await r.table("censors").insert({ 32 | action: message.args[1], 33 | censorID: id, 34 | guildID: message.channel.guild.id, 35 | regex: message.args[2], 36 | id: [id, message.channel.guild.id] 37 | }).run(); 38 | return __("commands.moderator.censor.add.success", message); 39 | } 40 | } else if(message.args[0] === "message") { 41 | if(!message.args[1]) return __("commands.moderator.censor.message.noID", message); 42 | 43 | let id = parseInt(message.args[1]); 44 | if(isNaN(id) || id < 1) { 45 | return __("commands.moderator.censor.message.invalidID", message); 46 | } else if(!bot.censors.has(message.channel.guild.id) || !bot.censors.get(message.channel.guild.id).has(id)) { 47 | return __("commands.moderator.message.noCensorFound", message); 48 | } 49 | 50 | await r.table("censors").get([id, message.channel.guild.id]).update({ message: message.args[2] }).run(); 51 | return __("commands.moderator.message.success", message, { id, message: message.args[2] }); 52 | } else if(message.args[0] === "delete" || message.args[0] === "remove") { 53 | if(!message.args[1]) return __("commands.moderator.censor.delete.noID", message); 54 | 55 | let id = parseInt(message.args[1]); 56 | if(isNaN(id) || id < 1) return __("commands.moderator.censor.delete.invalidID", message); 57 | 58 | let { deleted } = await r.table("censors").get([id, message.channel.guild.id]).delete().run(); 59 | if(deleted) { 60 | let censorsCache = bot.censors.get(message.channel.guild.id); 61 | if(!censorsCache) return __("commands.moderator.censor.delete.success", message); 62 | if(censorsCache.size === 1) bot.censors.delete(message.channel.guild.id); 63 | else censorsCache.delete(id); 64 | 65 | return __("commands.moderator.censor.delete.success", message); 66 | } else { 67 | return __("commands.moderator.censor.delete.noCensorFound", message); 68 | } 69 | } else if(message.args[0] === "list") { 70 | let censors = await r.table("censors").getAll(message.channel.guild.id, { index: "guildID" }).run(); 71 | if(!censors.length) { 72 | return __("commands.moderator.censor.list.noCensors", message); 73 | } else { 74 | return __("commands.moderator.censor.list.success", message, { 75 | censors: censors 76 | .sort((a, b) => a.censorID - b.censorID) 77 | .map(cen => `${cen.censorID}. ${cen.regex.replace(/\\/g, "\\\\")} (${cen.action})`) 78 | .join("\n") 79 | }); 80 | } 81 | } else { 82 | return __("commands.moderator.censor.invalidSubcommand", message); 83 | } 84 | }, 85 | caseSensitive: true, 86 | guildOnly: true, 87 | perm: "manageGuild", 88 | description: "Add or remove censors", 89 | args: [{ 90 | type: "text", 91 | label: "add|delete|message|list" 92 | }, { 93 | type: "text", 94 | label: "action|id", 95 | optional: true 96 | }, { 97 | type: "text", 98 | label: "regex|message", 99 | optional: true 100 | }] 101 | }; 102 | -------------------------------------------------------------------------------- /src/bot/commands/moderator/giveaway.js: -------------------------------------------------------------------------------- 1 | const Duration = require("duration-js"); 2 | module.exports = { 3 | process: async message => { 4 | if(!~message.args[0].indexOf(" in ")) { 5 | return __("commands.moderator.giveaway.noTime", message); 6 | } 7 | 8 | let item = message.args[0].substring(0, message.args[0].lastIndexOf("in")).trim(); 9 | let time = message.args[0].substring(message.args[0].lastIndexOf("in") + 2).trim() 10 | .replace(/weeks?/g, "w") 11 | .replace(/days?/g, "d") 12 | .replace(/hours?/g, "h") 13 | .replace(/minutes?/g, "m") 14 | .replace(/seconds?/g, "s") 15 | .replace(/milliseconds?/g, "ms") 16 | .replace(/ /g, ""); 17 | 18 | try { 19 | var duration = new Duration(time); 20 | duration = duration.milliseconds(); 21 | if(duration < 30000 || duration > 2419200000) { 22 | return __("commands.moderator.giveaway.invalidTime", message); 23 | } 24 | } catch(err) { 25 | return err.message; 26 | } 27 | 28 | let date = Date.now(); 29 | let msg = await message.channel.createMessage(__("commands.moderator.giveaway.message", message, { 30 | item, 31 | date: bot.utils.formatDate(date + duration) 32 | })); 33 | await msg.addReaction("🎉"); 34 | 35 | await r.table("timedEvents").insert({ 36 | channelID: message.channel.id, 37 | date: date + duration, 38 | item, 39 | messageID: msg.id, 40 | type: "giveaway", 41 | guildID: message.channel.guild.id 42 | }).run(); 43 | return false; 44 | }, 45 | guildOnly: true, 46 | perm: "manageChannels", 47 | description: "Create a giveaway", 48 | args: [{ 49 | type: "text", 50 | label: " in " 51 | }] 52 | }; 53 | -------------------------------------------------------------------------------- /src/bot/commands/moderator/guildlocale.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | process: async message => { 3 | if(!message.args[0]) { 4 | let currentLocale = await r.table("locales").get(message.channel.guild.id).run(); 5 | currentLocale = currentLocale ? currentLocale.locale : "en"; 6 | 7 | return __("commands.default.locale.noArg", message, { 8 | locales: bot.locales.map(locale => { 9 | let format = `${locale}: ${__("commands.default.locale.nativeName", { locale })}`; 10 | if(locale !== "en") format += ` (${__("commands.default.locale.englishName", { locale })})`; 11 | 12 | return format; 13 | }).join("\n"), 14 | locale: currentLocale 15 | }); 16 | } else if(!~bot.locales.indexOf(message.args[0])) { 17 | return __("commands.default.locale.invalidLocale", message); 18 | } else { 19 | let currentLocale = await r.table("locales").get(message.channel.guild.id).run(); 20 | if(!currentLocale) { 21 | await r.table("locales").insert({ id: message.channel.guild.id, locale: message.args[0] }).run(); 22 | } else { 23 | await r.table("locales").get(message.channel.guild.id).update({ locale: message.args[0] }).run(); 24 | } 25 | 26 | bot.localeCache.set(message.channel.guild.id, message.args[0]); 27 | message.locale = message.args[0]; 28 | 29 | let name = __("commands.default.locale.nativeName", message); 30 | return __("commands.default.locale.changedSuccess", message, { locale: `${message.args[0]} (${name})` }); 31 | } 32 | }, 33 | guildOnly: true, 34 | perm: "manageGuild", 35 | description: "Set the server's default language", 36 | aliases: ["guildlang"], 37 | args: [{ 38 | type: "text", 39 | label: "language", 40 | optional: true 41 | }] 42 | }; 43 | -------------------------------------------------------------------------------- /src/bot/commands/moderator/hackban.js: -------------------------------------------------------------------------------- 1 | const modLog = require("../../modules/modLog.js"); 2 | module.exports = { 3 | process: async message => { 4 | let banPerms = message.channel.guild.members.get(bot.user.id).permission.has("banMembers"); 5 | if(!banPerms) return __("commands.moderator.hackban.noPerms", message); 6 | 7 | if(!message.args[0].match(/^\d{17,21}$/)) return __("commands.moderator.hackban.invalidID", message); 8 | let member = message.channel.guild.members.get(message.args[0]); 9 | if(member) return __("commands.moderator.hackban.inGuild", message); 10 | 11 | 12 | if(message.args[1]) { 13 | let guild = message.channel.guild; 14 | let channel = await modLog.channel(guild); 15 | if(channel) { 16 | modLog.presetReasons[guild.id] = { mod: message.author, reason: message.args[1] }; 17 | } 18 | } 19 | 20 | message.channel.guild.banMember(message.args[0], 7, message.args[1]); 21 | let display = bot.users.has(message.args[0]) ? bot.users.get(message.args[0]).username : `\`${message.args[0]}\``; 22 | return __("commands.moderator.hackban.success", message, { user: display }); 23 | }, 24 | guildOnly: true, 25 | perm: "banMembers", 26 | description: "Ban a user from not in the guild by their ID", 27 | args: [{ 28 | type: "text", 29 | label: "user id" 30 | }, { 31 | type: "text", 32 | label: "reason", 33 | optional: true 34 | }] 35 | }; 36 | -------------------------------------------------------------------------------- /src/bot/commands/moderator/kick.js: -------------------------------------------------------------------------------- 1 | const modLog = require("../../modules/modLog.js"); 2 | module.exports = { 3 | process: async message => { 4 | let kickPerms = message.channel.guild.members.get(bot.user.id).permission.has("kickMembers"); 5 | if(!kickPerms) return __("commands.moderator.kick.noPerms", message); 6 | 7 | let member = message.channel.guild.members.get(message.args[0].id); 8 | if(!member) return __("phrases.notInGuild", message); 9 | 10 | if(!member.kickable) { 11 | return __("commands.moderator.kick.botCantKick", message, { user: member.user.username }); 12 | } else if(!member.punishable(message.member)) { 13 | return __("commands.moderator.kick.youCantKick", message, { user: member.user.username }); 14 | } else { 15 | if(message.args[1]) { 16 | let guild = message.channel.guild; 17 | let channel = await modLog.channel(guild); 18 | if(channel) { 19 | modLog.presetReasons[guild.id] = { mod: message.author, reason: message.args[1] }; 20 | } 21 | } 22 | 23 | member.kick(message.args[1]); 24 | modLog.create(message.channel.guild, "kick", message.args[0]); 25 | return __("commands.moderator.kick.success", message, { user: member.user.username }); 26 | } 27 | }, 28 | caseSensitive: true, 29 | guildOnly: true, 30 | perm: "kickMembers", 31 | description: "Kick a user from the guild", 32 | args: [{ type: "user" }, { 33 | type: "text", 34 | label: "reason", 35 | optional: true 36 | }] 37 | }; 38 | -------------------------------------------------------------------------------- /src/bot/commands/moderator/mute.js: -------------------------------------------------------------------------------- 1 | const modLog = require("../../modules/modLog.js"); 2 | 3 | module.exports = { 4 | process: async message => { 5 | let mutedRole = await bot.utils.getMutedRole(message); 6 | if(typeof mutedRole === "string") return mutedRole; 7 | 8 | if(message.args[1]) { 9 | let guild = message.channel.guild; 10 | let channel = await modLog.channel(guild); 11 | let trackedList = await r.table("settings").get(["modLog.track", guild.id]).run(); 12 | if(channel && trackedList && ~trackedList.value.indexOf(mutedRole.id)) { 13 | modLog.presetReasons[guild.id] = { mod: message.author, reason: message.args[1] }; 14 | } 15 | } 16 | 17 | let member = message.channel.guild.members.get(message.args[0].id); 18 | if(!member) return __("phrases.notInGuild", message); 19 | let isMuted = ~member.roles.indexOf(mutedRole.id); 20 | if(isMuted) { 21 | await member.removeRole(mutedRole.id, message.args[1]); 22 | return __("commands.moderator.mute.unmuted", message, { user: member.user.username }); 23 | } else { 24 | await member.addRole(mutedRole.id, message.args[1]); 25 | return __("commands.moderator.mute.muted", message, { user: member.user.username }); 26 | } 27 | }, 28 | guildOnly: true, 29 | perm: "manageRoles", 30 | description: "Toggle a person's mute state in the guild (for text chat)", 31 | args: [{ type: "user" }, { 32 | type: "text", 33 | label: "reason", 34 | optional: true 35 | }] 36 | }; 37 | -------------------------------------------------------------------------------- /src/bot/commands/moderator/pardon.js: -------------------------------------------------------------------------------- 1 | const modLog = require("../../modules/modLog.js"); 2 | module.exports = { 3 | process: async message => { 4 | let member = message.channel.guild.members.get(message.args[0].id); 5 | if(!member) return __("phrases.notInGuild", message); 6 | 7 | if(!member.punishable(message.member)) { 8 | return __("commands.moderator.pardon.noPerms", message); 9 | } else { 10 | let warnings = await r.table("warnings") 11 | .getAll(member.id, { index: "userID" }) 12 | .filter({ guildID: message.channel.guild.id }).run(); 13 | let warnCount = warnings.length - 1; 14 | if(warnCount < 0) return __("commands.moderator.pardon.noWarnings", message); 15 | 16 | let channel = await modLog.channel(message.channel.guild); 17 | if(channel) { 18 | if(message.args[1]) { 19 | modLog.presetReasons[message.channel.guild.id] = { reason: message.args[1], mod: message.author }; 20 | } 21 | await modLog.create(message.channel.guild, "pardon", member.user, { warnCount }); 22 | } 23 | 24 | await r.table("warnings").get(warnings[warnings.length - 1].uuid).delete().run(); 25 | return __("commands.moderator.pardon.success", message, { user: member.user.username, warnCount }); 26 | } 27 | }, 28 | caseSensitive: true, 29 | guildOnly: true, 30 | perm: "banMembers", 31 | description: "Remove a warning from a member", 32 | args: [{ type: "user" }, { 33 | type: "text", 34 | label: "reason", 35 | optional: true 36 | }] 37 | }; 38 | -------------------------------------------------------------------------------- /src/bot/commands/moderator/purge.js: -------------------------------------------------------------------------------- 1 | let linkFilter = /^((https|http|ftp|rtsp|mms)?:\/\/)?(([0-9a-z_!~*'().&=+$%-]+:)?[0-9a-z_!~*'().&=+$%-]+@)?(([0-9]{1,3}\.){3}[0-9]{1,3}|([0-9a-z_!~*'()-]+\.)*([0-9a-z][0-9a-z-]{0,61})?[0-9a-z]\.[a-z]{2,6})(:[0-9]{1,4})?((\/?)|(\/[0-9a-z_!~*'().;?:@&=+$,%#-]+)+\/?)$/im; // eslint-disable-line max-len 2 | module.exports = { 3 | process: async message => { 4 | let deletePerms = message.channel.guild.members.get(bot.user.id).permission.has("manageMessages"); 5 | if(!deletePerms) { 6 | let msg = await message.channel.createMessage(__("commands.moderator.purge.noPerms", message)); 7 | setTimeout(() => msg.delete(), 3000); 8 | } else if(message.args[1]) { 9 | await message.delete(); 10 | let filterList = ["bots", "images", "files", "embeds", "includes", "contains", 11 | "links", "users", "from", "matches"]; 12 | let filtersActive = {}; 13 | let filters = message.args[1].split(",").map(filter => filter.toLowerCase().trim()); 14 | for(let filter of filters) { 15 | let filterName = !~filter.indexOf(" ") ? filter : filter.split(" ")[0]; 16 | if(!filterList.includes(filterName)) { 17 | delete filters[filters.indexOf(filter)]; 18 | continue; 19 | } 20 | 21 | if(filterName === "from" || filterName === "users") { 22 | let users = filter.substring(filterName.length).trim().split(" "); 23 | for(let i = 0; i < users.length; i++) { 24 | try { 25 | users[i] = (await bot.utils.resolver.user(message, users[i])).id; 26 | } catch(err) { 27 | let msg = await message.channel.createMessage(`${err.message} (user #${i})`); 28 | setTimeout(() => msg.delete(), 3000); 29 | return; 30 | } 31 | } 32 | filtersActive.users = users; 33 | } else if(filterName === "includes" || filterName === "contains") { 34 | filtersActive.includes = filter.substring(filterName.length).trim(); 35 | } else if(filterName === "matches") { 36 | filtersActive.matches = filter.substring(filterName.length).trim(); 37 | } else { 38 | filtersActive[filterName] = true; 39 | } 40 | } 41 | 42 | await message.channel.purge(message.args[0], msg => { 43 | if(filtersActive.bots && msg.author.bot) return true; 44 | else if(filtersActive.images && msg.attachments[0] && msg.attachments[0].width) return true; 45 | else if(filtersActive.files && msg.attachments[0] && !msg.attachments[0].width) return true; 46 | else if(filtersActive.embeds && msg.embeds.length >= 1) return true; 47 | else if(filtersActive.users && filtersActive.users.includes(msg.author.id)) return true; 48 | else if(filtersActive.includes && ~msg.content.toLowerCase().indexOf(filtersActive.includes)) return true; 49 | else if(filtersActive.links && linkFilter.test(msg.content)) return true; 50 | else if(filtersActive.matches && msg.content.match(new RegExp(filtersActive.matches, "im"))) return true; 51 | else return false; 52 | }); 53 | } else { 54 | await message.delete(); 55 | await message.channel.purge(message.args[0]); 56 | } 57 | 58 | let msg = await message.channel.createMessage("👌"); 59 | setTimeout(() => msg.delete(), 3000); 60 | }, 61 | guildOnly: true, 62 | perm: "manageMessages", 63 | aliases: ["prune"], 64 | description: "Delete up to 2500 with optional filters", 65 | args: [{ 66 | type: "num", 67 | min: 1, 68 | max: 2500, 69 | label: "limit" 70 | }, { 71 | type: "text", 72 | optional: true, 73 | label: "filters (bots, images, files, embeds, links, from , includes , matches )" 74 | }] 75 | }; 76 | -------------------------------------------------------------------------------- /src/bot/commands/moderator/reason.js: -------------------------------------------------------------------------------- 1 | const modLog = require("../../modules/modLog.js"); 2 | module.exports = { 3 | process: async message => { 4 | let cases = [], casecount = (await modLog.cases(message.channel.guild)).length; 5 | if(casecount.length === 0) return __("commands.moderator.reason.noCases", message); 6 | 7 | if(message.args[0].indexOf("-") !== -1) { 8 | let match = message.args[0].match(/((?:\d|l(?:atest)?)+)\s?-\s?((?:\d|l(?:atest)?)+)/); 9 | if(match[1] === "l" || match[1] === "latest") match[1] = casecount; 10 | else match[1] = parseInt(match[1]); 11 | if(match[2] === "l" || match[1] === "latest") match[2] = casecount; 12 | else match[2] = parseInt(match[2]); 13 | 14 | if(match[2] < match[1]) return __("phrases.invalidRange", message); 15 | for(let i = match[1]; i <= match[2]; i++) cases.push(i); 16 | } else if(message.args[0].indexOf(",") !== -1) { 17 | message.args[0].split(",").forEach(part => cases.push(part)); 18 | } else { 19 | cases.push(message.args[0]); 20 | } 21 | 22 | cases = cases.map(int => { 23 | if(int === "l" || int === "latest") return casecount; 24 | else return parseInt(int); 25 | }); 26 | if(cases.some(int => isNaN(int))) return __("commands.moderator.reason.invalidInput", message); 27 | 28 | let errMsg, casesSet = 0; 29 | for(let caseNum of cases) { 30 | if(errMsg) break; 31 | let resp = await modLog.set(message.channel.guild, caseNum, message.args[1], message.author); 32 | 33 | if(resp === "SUCCESS") casesSet++; 34 | else if(resp === "NO_DATA") errMsg = __("commands.moderator.reason.invalidCase", message, { caseNum }); 35 | else if(resp === "NO_CHANNEL") errMsg = __("commands.moderator.reason.noChannel", message); 36 | else if(resp === "NO_MSG") errMsg = __("commands.moderator.reason.noMessage", message, { caseNum }); 37 | else errMsg = resp; 38 | } 39 | 40 | let returnMsg = ""; 41 | if(cases.length === 1 && !errMsg) { 42 | returnMsg = __("commands.moderator.reason.successOneCase", message, { caseNum: cases[0] }); 43 | } else if(casesSet) { 44 | returnMsg = __("commands.moderator.reason.successMultipleCases", message, { casesCount: cases.length }); 45 | } 46 | if(errMsg) returnMsg += errMsg; 47 | return returnMsg; 48 | }, 49 | caseSensitive: true, 50 | guildOnly: true, 51 | perm: "kickMembers", 52 | description: "Set a reason of a case (or multiple) on the mod log", 53 | args: [{ 54 | type: "text", 55 | label: "cases" 56 | }, { 57 | type: "text", 58 | label: "reason" 59 | }] 60 | }; 61 | -------------------------------------------------------------------------------- /src/bot/commands/moderator/softban.js: -------------------------------------------------------------------------------- 1 | const modLog = require("../../modules/modLog.js"); 2 | module.exports = { 3 | process: async message => { 4 | let banPerms = message.channel.guild.members.get(bot.user.id).permission.has("banMembers"); 5 | if(!banPerms) return __("commands.moderator.softban.noPerms", message); 6 | 7 | let member = message.channel.guild.members.get(message.args[0].id); 8 | if(!member) return __("phrases.notInGuild", message); 9 | 10 | if(!member.bannable) { 11 | return __("commands.moderator.softban.botCantBan", message); 12 | } else if(!member.punishable(message.member)) { 13 | return __("commands.moderator.softban.youCantBan", message); 14 | } else { 15 | if(message.args[1]) { 16 | let guild = message.channel.guild; 17 | let channel = await modLog.channel(guild); 18 | if(channel) { 19 | modLog.presetReasons[guild.id] = { mod: message.author, reason: message.args[1] }; 20 | } 21 | } 22 | 23 | await member.ban(7, message.args[1]); 24 | member.unban("Softban"); 25 | return __("commands.moderator.softban.success", message, { user: member.user.username }); 26 | } 27 | }, 28 | guildOnly: true, 29 | perm: "banMembers", 30 | description: "Softban a user from the guild (kick with message deletion)", 31 | args: [{ type: "user" }, { 32 | type: "text", 33 | label: "reason", 34 | optional: true 35 | }] 36 | }; 37 | -------------------------------------------------------------------------------- /src/bot/commands/moderator/tempmute.js: -------------------------------------------------------------------------------- 1 | const Duration = require("duration-js"); 2 | const modLog = require("../../modules/modLog.js"); 3 | 4 | module.exports = { 5 | process: async message => { 6 | let mutedRole = await bot.utils.getMutedRole(message); 7 | if(typeof mutedRole === "string") return mutedRole; 8 | 9 | if(message.args[1]) { 10 | let guild = message.channel.guild; 11 | let channel = await modLog.channel(guild); 12 | let trackedList = await r.table("settings").get(["modLog.track", guild.id]).run(); 13 | if(channel && trackedList && ~trackedList.value.indexOf(mutedRole.id)) { 14 | modLog.presetReasons[guild.id] = { mod: message.author, reason: message.args[2] }; 15 | } 16 | } 17 | 18 | let time = message.args[1].trim() 19 | .replace(/weeks?/g, "w") 20 | .replace(/days?/g, "d") 21 | .replace(/hours?/g, "h") 22 | .replace(/minutes?/g, "m") 23 | .replace(/seconds?/g, "s") 24 | .replace(/milliseconds?/g, "ms") 25 | .replace(/ /g, ""); 26 | 27 | try { 28 | var duration = new Duration(time); 29 | duration = duration.milliseconds(); 30 | } catch(err) { 31 | return err.message; 32 | } 33 | 34 | let member = message.channel.guild.members.get(message.args[0].id); 35 | if(!member) return __("phrases.notInGuild", message); 36 | let isMuted = ~member.roles.indexOf(mutedRole.id); 37 | if(!~member.roles.indexOf(mutedRole.id)) await member.addRole(mutedRole.id, message.args[1]); 38 | 39 | let date = Date.now(); 40 | await r.table("timedEvents").insert({ 41 | type: "tempmute", 42 | date: date + duration, 43 | guildID: message.channel.guild.id, 44 | memberID: member.id, 45 | mutedRole: mutedRole.id 46 | }).run(); 47 | 48 | return __("commands.moderator.mute.tempMuted", message, { 49 | user: member.user.username, 50 | date: bot.utils.formatDate(date + duration) 51 | }); 52 | }, 53 | guildOnly: true, 54 | perm: "manageRoles", 55 | description: "Mute someone in the server for a said amount of time", 56 | args: [{ type: "user" }, { 57 | type: "text", 58 | label: "duration" 59 | }, { 60 | type: "text", 61 | label: "reason", 62 | optional: true 63 | }] 64 | }; 65 | -------------------------------------------------------------------------------- /src/bot/commands/moderator/warn.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | process: async message => { 3 | let member = message.channel.guild.members.get(message.args[0].id); 4 | if(!member) return __("phrases.notInGuild", message); 5 | 6 | if(!member.punishable(message.member)) { 7 | return __("commands.moderator.warn.noPerms", message); 8 | } else { 9 | let warnCount = await bot.utils.warnMember(member, message.author, message.args[1]); 10 | return __("commands.moderator.warn.success", message, { user: member.user.username, warnCount }); 11 | } 12 | }, 13 | caseSensitive: true, 14 | guildOnly: true, 15 | perm: "banMembers", 16 | description: "Give a member a warning", 17 | args: [{ type: "user" }, { 18 | type: "text", 19 | label: "reason", 20 | optional: true 21 | }] 22 | }; 23 | -------------------------------------------------------------------------------- /src/bot/commands/music/autoplay.js: -------------------------------------------------------------------------------- 1 | const autoplay = require("../../modules/audio/autoplay.js"); 2 | module.exports = { 3 | process: async message => { 4 | let donator = await r.db("Oxyl").table("donators").get(message.channel.guild.ownerID).run(); 5 | if(!donator) return __("commands.music.autoplay.donatorOnly", message); 6 | 7 | let player = bot.players.get(message.channel.guild.id); 8 | let current = await player.getCurrent(); 9 | if(!player || !player.connection) { 10 | return __("phrases.noMusic", message); 11 | } else if(!player.voiceCheck(message.member)) { 12 | return __("phrases.notListening", message); 13 | } else if(!current) { 14 | return __("commands.music.autoplay.noMusicPlaying", message); 15 | } else { 16 | let options = await player.getOptions(); 17 | options.autoplay = !player.options; 18 | if(!options.repeat && options.autoplay && current.uri.startsWith("https://www.youtube.com/")) { 19 | let queue = await player.getQueue(); 20 | queue.unshift(await autoplay(current.identifier)); 21 | await player.setQueue(queue); 22 | } 23 | 24 | await player.setOptions(options); 25 | return __("commands.music.autoplay.success", message, 26 | { value: __(`words.${options.autoplay ? "on" : "off"}`, message) }); 27 | } 28 | }, 29 | guildOnly: true, 30 | description: "Toggles the usage of autoplay (grab the autoplay video from Youtube and add to start of queue)" 31 | }; 32 | -------------------------------------------------------------------------------- /src/bot/commands/music/end.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | process: async message => { 3 | let player = bot.players.get(message.channel.guild.id); 4 | if(!player) { 5 | return __("phrases.noMusic", message); 6 | } else if(!player.voiceCheck(message.member)) { 7 | return __("phrases.notListening", message); 8 | } else { 9 | player.destroy(); 10 | return __("commands.music.end.success", message); 11 | } 12 | }, 13 | guildOnly: true, 14 | aliases: ["music"], 15 | description: "Stop music in your channel" 16 | }; 17 | -------------------------------------------------------------------------------- /src/bot/commands/music/jump.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | process: async message => { 3 | let player = bot.players.get(message.channel.guild.id); 4 | if(!player || !player.connection) { 5 | return __("phrases.noMusic", message); 6 | } else if(!player.voiceCheck(message.member)) { 7 | return __("phrases.notListening", message); 8 | } else { 9 | let queue = await player.getQueue(); 10 | if(queue.length === 0) return __("phrases.noQueue", message); 11 | if(message.args[0] > queue.length) return __("commands.music.jump.invalidQueue", message); 12 | queue = queue.slice(message.args[0] - 1).concat(queue.slice(0, message.args[0] - 1)); 13 | player.connection.stop(); 14 | 15 | await player.setQueue(queue); 16 | return __("commands.music.jump.success", message, { queue: message.args[0] }); 17 | } 18 | }, 19 | guildOnly: true, 20 | description: "Jump to a song in the queue", 21 | args: [{ 22 | type: "num", 23 | label: "queue #", 24 | min: 1 25 | }] 26 | }; 27 | -------------------------------------------------------------------------------- /src/bot/commands/music/movehere.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | process: async message => { 3 | let player = bot.players.get(message.channel.guild.id); 4 | if(!player || !player.connection) { 5 | return __("phrases.noMusic", message); 6 | } else { 7 | let moves = []; 8 | let connectionChannel = player.connection.channelId; 9 | if(message.member && message.member.voiceState && message.member.voiceState.channelID) { 10 | let memberChannel = message.channel.guild.channels.get(message.member.voiceState.channelID); 11 | if(memberChannel.id !== connectionChannel) { 12 | if(!memberChannel.permissionsOf(bot.user.id).has("voiceConnect")) { 13 | return __("phrases.cantJoin", message); 14 | } else if(!memberChannel.permissionsOf(bot.user.id).has("voiceSpeak")) { 15 | return __("phrases.cantSpeak", message); 16 | } else { 17 | moves.push(memberChannel.name); 18 | player.connection.switchChannel(memberChannel.id); 19 | player.setConnection(memberChannel.id); 20 | } 21 | } 22 | } else { 23 | return __("commands.music.movehere.notInVoice", message); 24 | } 25 | 26 | let channel = await player.getChannel(); 27 | if(!channel || channel.id !== message.channel.id) { 28 | await player.setChannel(message.channel.id); 29 | moves.push(message.channel.mention); 30 | } 31 | 32 | if(moves.length === 0) return __("commands.music.movehere.nothingToMove", message); 33 | else return __("commands.music.movehere.success", message, { locations: moves.join(" and ") }); 34 | } 35 | }, 36 | guildOnly: true, 37 | description: "Change Oxyl's voice channel and/or text channel" 38 | }; 39 | -------------------------------------------------------------------------------- /src/bot/commands/music/pause.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | process: async message => { 3 | let player = bot.players.get(message.channel.guild.id); 4 | if(!player || !player.connection) { 5 | return __("phrases.noMusic", message); 6 | } else if(!player.voiceCheck(message.member)) { 7 | return __("phrases.notListening", message); 8 | } else { 9 | let options = await player.getOptions(); 10 | if(options.paused) { 11 | return __("commands.music.pause.alreadyPaused", message); 12 | } else { 13 | options.paused = true; 14 | await player.setOptions(options); 15 | } 16 | 17 | player.connection.setPause(options.paused); 18 | return __("commands.music.pause.success", message); 19 | } 20 | }, 21 | guildOnly: true, 22 | description: "Pause music in your channel" 23 | }; 24 | -------------------------------------------------------------------------------- /src/bot/commands/music/play.js: -------------------------------------------------------------------------------- 1 | const Player = require("../../structures/player.js"); 2 | const resolver = require("../../modules/audio/main.js"); 3 | 4 | const cheerio = require("cheerio"); 5 | const superagent = require("superagent"); 6 | const fs = require("fs"); 7 | let dfm = fs.readdirSync(`${__dirname}/../../../../discordfm`).reduce((all, playlist) => { 8 | let listname = playlist.substring(0, playlist.lastIndexOf(".")).replace(/-/g, " "); 9 | all[listname] = `${__dirname}/../../../../discordfm/${playlist}`; 10 | require(all[listname]); 11 | 12 | return all; 13 | }, {}); 14 | 15 | module.exports = { 16 | process: async message => { 17 | let voiceChannel, player = bot.players.get(message.channel.guild.id); 18 | if(message.member && message.member.voiceState && message.member.voiceState.channelID) { 19 | voiceChannel = message.channel.guild.channels.get(message.member.voiceState.channelID); 20 | } else { 21 | voiceChannel = undefined; 22 | } 23 | 24 | if(!voiceChannel) return __("commands.music.play.notInVoice", message); 25 | else if(!player) player = new Player(message.channel.guild, { channelID: message.channel.id }); 26 | 27 | if(player && player.connection && !player.voiceCheck(message.member)) { 28 | return __("phrases.notListening", message); 29 | } else if(voiceChannel && !voiceChannel.permissionsOf(bot.user.id).has("voiceConnect")) { 30 | return __("phrases.cantJoin", message); 31 | } else if(voiceChannel && !voiceChannel.permissionsOf(bot.user.id).has("voiceSpeak")) { 32 | return __("phrases.cantSpeak", message); 33 | } else if(message.args[0].startsWith("dfm:")) { 34 | message.args[0] = message.args[0].substring(4).trim(); 35 | let key = Object.keys(dfm).find(loopKey => loopKey.toLowerCase() === message.args[0]); 36 | if(message.args[0] === "list") { 37 | return __("commands.music.play.dfmPlaylists", message, { genres: Object.keys(dfm).join(", ") }); 38 | } else if(key) { 39 | if(!player.connection) await player.connect(voiceChannel.id); 40 | let res = await player.addQueue(require(dfm[key])); 41 | 42 | if(typeof res === "string") return res; 43 | return __("commands.music.play.addedDFM", message, { genre: key }); 44 | } else { 45 | return __("commands.music.play.invalidDFM", message); 46 | } 47 | } else if(message.args[0].startsWith("sq:")) { 48 | message.args[0] = message.args[0].substring(3).trim(); 49 | let donator = await r.db("Oxyl").table("donators").get(message.author.id).run(); 50 | if(!donator) return __("commands.music.play.donatorOnly", message); 51 | 52 | let queueNumber = parseInt(message.args[0]); 53 | if(!queueNumber || queueNumber < 1 || queueNumber > 3) { 54 | return __("commands.music.play.invalidSavedQueue", message); 55 | } 56 | 57 | let savedQueue = await r.table("savedQueues").get([queueNumber, message.author.id]).run(); 58 | if(!savedQueue) return __("commands.music.play.noSavedQueue", message, { save: queueNumber }); 59 | 60 | if(!player.connection) await player.connect(voiceChannel.id); 61 | await player.addQueue(savedQueue.queue); 62 | return __("commands.music.play.loadedSavedQueue", message, { 63 | save: queueNumber, 64 | itemCount: savedQueue.queue.length 65 | }); 66 | } else { 67 | let result = await resolver(message.args[0]); 68 | if(result === "NO_VIDEO") return __("commands.music.play.noVideo", message); 69 | 70 | if(!player.connection) await player.connect(voiceChannel.id); 71 | let res2 = await player.addQueue(result); 72 | if(typeof res2 === "string") return res2; 73 | else if(result.title) return __("commands.music.play.addedItem", message, { title: result.title }); 74 | else return __("commands.music.play.addedPlaylist", message, { items: result.length }); 75 | } 76 | }, 77 | caseSensitive: true, 78 | guildOnly: true, 79 | description: "Add items to the music queue", 80 | args: [{ 81 | type: "text", 82 | label: "link|search query|dfm:/list" 83 | }] 84 | }; 85 | -------------------------------------------------------------------------------- /src/bot/commands/music/queue.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | process: async message => { 3 | let player = bot.players.get(message.channel.guild.id); 4 | if(!player || !player.connection) return __("phrases.noMusic", message); 5 | 6 | let current = await player.getCurrent(); 7 | let queue = await player.getQueue(); 8 | let options = await player.getOptions(); 9 | 10 | let queueMsg = ""; 11 | let page = message.args[0] || 1; 12 | let pageAmount = Math.ceil(queue.length / 15); 13 | if(page > pageAmount) page = pageAmount; 14 | 15 | if(queue.length > 0) { 16 | queueMsg += queue.slice((page - 1) * 15, ((page - 1) * 15) + 15) 17 | .map((song, i) => `[${((page - 1) * 15) + i + 1}] ${song.title}`) 18 | .join("\n"); 19 | 20 | queueMsg += `\n**${__("words.page", message, {}, true)} ${page}/${pageAmount}**`; 21 | } else { 22 | queueMsg += `N/A`; 23 | } 24 | 25 | if(!current || !player.connection) { 26 | queueMsg += "\n\n"; 27 | queueMsg += __("phrases.queueError", message); 28 | } else { 29 | if(current.length && current.length < 900000000000000) { 30 | var videoDuration = bot.utils.secondsToDuration(current.length / 1000); 31 | } 32 | 33 | let playTime = bot.utils.secondsToDuration(Math.floor(player.connection.state.position / 1000)); 34 | 35 | queueMsg += "\n\n"; 36 | queueMsg += __("phrases.currentPlaying", message, { 37 | title: current.title, 38 | duration: videoDuration ? `${playTime}/${videoDuration}` : playTime 39 | }); 40 | } 41 | 42 | queueMsg += "\n"; 43 | queueMsg += __("phrases.autoplay", message, { autoplay: __(`words.${options.autoplay ? "on" : "off"}`, message) }); 44 | queueMsg += "\n"; 45 | queueMsg += __("phrases.repeat", message, { repeat: __(`words.${options.repeat ? "on" : "off"}`, message) }); 46 | return __("commands.music.queue.success", message, { 47 | itemCount: queue.length, 48 | message: queueMsg 49 | }); 50 | }, 51 | guildOnly: true, 52 | description: "List the music queue", 53 | args: [{ 54 | type: "num", 55 | label: "page", 56 | optional: true, 57 | min: 1 58 | }] 59 | }; 60 | -------------------------------------------------------------------------------- /src/bot/commands/music/removequeue.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | process: async message => { 3 | let player = bot.players.get(message.channel.guild.id); 4 | if(!player || !player.connection) { 5 | return __("phrases.noMusic", message); 6 | } else if(!player.voiceCheck(message.member)) { 7 | return __("phrases.notListening", message); 8 | } else { 9 | let queue = await player.getQueue(); 10 | if(queue.length === 0) return __("phrases.noQueue", message); 11 | 12 | let toRemove = []; 13 | if(message.args[0].indexOf("-") !== -1) { 14 | let match = message.args[0].match(/((?:\d|l(?:atest)?)+)\s?-\s?((?:\d|l(?:atest)?)+)/); 15 | if(match[1] === "l" || match[1] === "latest") match[1] = queue.length; 16 | else match[1] = parseInt(match[1]); 17 | if(match[2] === "l" || match[1] === "latest") match[2] = queue.length; 18 | else match[2] = parseInt(match[2]); 19 | 20 | if(match[2] < match[1]) return "Invalid range given!"; 21 | for(let i = match[1]; i <= match[2]; i++) toRemove.push(i); 22 | } else if(message.args[0].indexOf(",") !== -1) { 23 | message.args[0].split(",").forEach(part => toRemove.push(part)); 24 | } else { 25 | toRemove.push(message.args[0]); 26 | } 27 | 28 | toRemove = toRemove.map(int => { 29 | if(int === "l" || int === "latest") return queue.length; 30 | else return parseInt(int); 31 | }); 32 | if(toRemove.some(int => isNaN(int))) return __("commands.music.removequeue.invalidQueue", message); 33 | 34 | toRemove.forEach(int => delete queue[int]); 35 | queue = queue.filter(item => item !== undefined); 36 | 37 | player.setQueue(queue); 38 | return __("commands.music.removequeue.success", message, { itemCount: toRemove.length }); 39 | } 40 | }, 41 | guildOnly: true, 42 | aliases: ["music"], 43 | description: "Stop music in your channel" 44 | }; 45 | -------------------------------------------------------------------------------- /src/bot/commands/music/repeat.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | process: async message => { 3 | let player = bot.players.get(message.channel.guild.id); 4 | if(!player || !player.connection) { 5 | return __("phrases.noMusic", message); 6 | } else if(!player.voiceCheck(message.member)) { 7 | return __("phrases.notListening", message); 8 | } else { 9 | let options = await player.getOptions(); 10 | options.repeat = !options.repeat; 11 | await player.setOptions(options); 12 | 13 | return __("commands.music.repeat.success", message, 14 | { value: __(`words.${options.repeat ? "on" : "off"}`, message) }); 15 | } 16 | }, 17 | guildOnly: true, 18 | description: "Toggle repeating of the queue" 19 | }; 20 | -------------------------------------------------------------------------------- /src/bot/commands/music/resume.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | process: async message => { 3 | let player = bot.players.get(message.channel.guild.id); 4 | if(!player || !player.connection) { 5 | return __("phrases.noMusic", message); 6 | } else if(!player.voiceCheck(message.member)) { 7 | return __("phrases.notListening", message); 8 | } else { 9 | let options = await player.getOptions(); 10 | if(!options.paused) { 11 | return __("commands.music.resume.notPaused", message); 12 | } else { 13 | options.paused = false; 14 | await player.setOptions(options); 15 | } 16 | 17 | player.connection.setPause(options.paused); 18 | return __("commands.music.resume.success", message); 19 | } 20 | }, 21 | guildOnly: true, 22 | description: "Resume the music in your channel" 23 | }; 24 | -------------------------------------------------------------------------------- /src/bot/commands/music/savequeue.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | process: async message => { 3 | let donator = await r.db("Oxyl").table("donators").get(message.author.id).run(); 4 | if(!donator) return __("commands.music.savequeue.donatorOnly", message); 5 | 6 | let player = bot.players.get(message.channel.guild.id); 7 | let queue = await player.getQueue(); 8 | if(!player || !player.connection) { 9 | return __("phrases.noMusic", message); 10 | } else if(!player.voiceCheck(message.member)) { 11 | return __("phrases.notListening", message); 12 | } else if(queue.length === 0) { 13 | return __("phrases.noQueue", message); 14 | } else { 15 | let alreadySaved = await r.table("savedQueues").get([message.args[0], message.author.id]).run(); 16 | if(alreadySaved) { 17 | await r.table("savedQueues").get([message.args[0], message.author.id]) 18 | .update({ queue }).run(); 19 | } else { 20 | await r.table("savedQueues").insert({ 21 | id: [message.args[0], message.author.id], 22 | queue, 23 | userID: message.author.id 24 | }).run(); 25 | } 26 | 27 | return __("commands.music.savequeue.success", message, { 28 | itemCount: queue.length, 29 | queueNumber: message.args[0] 30 | }); 31 | } 32 | }, 33 | guildOnly: true, 34 | description: "Save your queue (donators only)", 35 | args: [{ 36 | type: "num", 37 | label: "save #", 38 | min: 1, 39 | max: 3 40 | }] 41 | }; 42 | -------------------------------------------------------------------------------- /src/bot/commands/music/seek.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | process: async message => { 3 | let player = bot.players.get(message.channel.guild.id); 4 | if(!player || !player.connection) { 5 | return __("phrases.noMusic", message); 6 | } else if(!player.voiceCheck(message.member)) { 7 | return __("phrases.notListening", message); 8 | } else { 9 | const current = await player.getCurrent(); 10 | if(message.args[0] > current.length / 1000) { 11 | return __("commands.music.seek.error", message, { max: current.length / 1000 }); 12 | } 13 | 14 | await player.connection.seek(message.args[0] * 1000); 15 | return __("commands.music.seek.success", message, { time: message.args[0] }); 16 | } 17 | }, 18 | guildOnly: true, 19 | description: "Seek to a position in current playing song", 20 | args: [{ 21 | type: "num", 22 | label: "seconds", 23 | min: 0 24 | }] 25 | }; 26 | -------------------------------------------------------------------------------- /src/bot/commands/music/shuffle.js: -------------------------------------------------------------------------------- 1 | function shuffle(array) { 2 | for(let i = array.length; i; i--) { 3 | let index = Math.floor(Math.random() * i); 4 | [array[i - 1], array[index]] = [array[index], array[i - 1]]; 5 | } 6 | } 7 | 8 | module.exports = { 9 | process: async message => { 10 | let player = bot.players.get(message.channel.guild.id); 11 | if(!player || !player.connection) { 12 | return __("phrases.noMusic", message); 13 | } else if(!player.voiceCheck(message.member)) { 14 | return __("phrases.notListening", message); 15 | } else { 16 | let queue = await player.getQueue(); 17 | shuffle(queue); 18 | await player.setQueue(queue); 19 | 20 | return __("commands.music.shuffle.success", message); 21 | } 22 | }, 23 | guildOnly: true, 24 | description: "Shuffle the queue" 25 | }; 26 | -------------------------------------------------------------------------------- /src/bot/commands/music/skip.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | process: async message => { 3 | let player = bot.players.get(message.channel.guild.id); 4 | if(!player || !player.connection) { 5 | return __("phrases.noMusic", message); 6 | } else if(!player.voiceCheck(message.member)) { 7 | return __("phrases.notListening", message); 8 | } else { 9 | let queue = await player.getQueue(); 10 | let next = queue[0]; 11 | player.connection.stop(); 12 | 13 | if(next) return __("commands.music.skip.successNextSong", message, { title: next.title }); 14 | else return __("commands.music.skip.successNoneLeft", message); 15 | } 16 | }, 17 | guildOnly: true, 18 | description: "Skip a song" 19 | }; 20 | -------------------------------------------------------------------------------- /src/bot/commands/music/volume.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | process: async message => { 3 | let donator = await r.db("Oxyl").table("donators").get(message.channel.guild.ownerID).run(); 4 | if(!donator) return __("commands.music.volume.donatorOnly", message); 5 | 6 | let player = bot.players.get(message.channel.guild.id); 7 | let current = await player.getCurrent(); 8 | if(!player || !player.connection) { 9 | return __("phrases.noMusic", message); 10 | } else if(!player.voiceCheck(message.member)) { 11 | return __("phrases.notListening", message); 12 | } else if(!current) { 13 | return __("commands.music.volume.noMusicPlaying", message); 14 | } else { 15 | let options = await player.getOptions(); 16 | options.volume = message.args[0]; 17 | player.connection.setVolume(options.volume); 18 | 19 | await player.setOptions(options); 20 | return __("commands.music.volume.success", message, { volume: options.volume }); 21 | } 22 | }, 23 | guildOnly: true, 24 | description: "Changes the volume of the music", 25 | args: [{ 26 | type: "num", 27 | label: "volume", 28 | min: 0, 29 | max: 100 30 | }] 31 | }; 32 | -------------------------------------------------------------------------------- /src/bot/index.js: -------------------------------------------------------------------------------- 1 | const Eris = require("eris-additions")(require("eris"), 2 | { enabled: ["Channel.awaitMessages", "Member.bannable", "Member.kickable", "Member.punishable", "Role.addable"] } 3 | ); 4 | 5 | const path = require("path"); 6 | const fs = Promise.promisifyAll(require("fs")); 7 | const config = require(path.resolve("config.json")); 8 | 9 | let raven = require("raven"); 10 | if(config.bot.sentryLink) raven.config(config.bot.sentryLink).install(); 11 | 12 | async function init() { 13 | if(!config.bot.token) { 14 | console.error("No token found in config.json"); 15 | process.exit(0); 16 | } else if(!config.bot.prefixes) { 17 | console.error("No prefix(es) found in config.json"); 18 | process.exit(0); 19 | } 20 | 21 | global.bot = new Eris(config.bot.token, { 22 | firstShardID: cluster.worker.shardStart, 23 | lastShardID: cluster.worker.shardEnd, 24 | maxShards: cluster.worker.totalShards, 25 | disableEvents: { TYPING_START: true }, 26 | messageLimit: 0, 27 | defaultImageFormat: "png", 28 | defaultImageSize: 256 29 | }); 30 | 31 | bot.config = config; 32 | bot.ignoredChannels = new Map(); 33 | bot.players = new Map(); 34 | bot.prefixes = new Map(); 35 | bot.censors = new Map(); 36 | 37 | let locales = await getFiles(path.resolve("locales"), file => file.endsWith(".json")); 38 | bot.locales = locales.map(file => file.substring(file.lastIndexOf("/") + 1, file.lastIndexOf("."))); 39 | bot.localeCache = new Map(); 40 | require(path.resolve("src", "misc", "rethink")); 41 | require(path.resolve("src", "misc", "outputHandler")); 42 | require(path.resolve("src", "bot", "modules", "locales")); 43 | 44 | bot.utils = {}; 45 | let utils = await loadScripts(path.resolve("src", "bot", "utils")); 46 | utils.forEach(script => bot.utils[script.name] = script.exports); 47 | 48 | let onceListeners = await loadScripts(path.resolve("src", "bot", "listeners", "once")); 49 | let onListeners = await loadScripts(path.resolve("src", "bot", "listeners", "on")); 50 | onceListeners.forEach(script => bot.once(script.name, script.exports)); 51 | onListeners.forEach(script => bot.on(script.name, script.exports)); 52 | 53 | bot.commands = {}; 54 | const Command = require(path.resolve("src", "bot", "structures", "command")); 55 | let commands = await loadScripts(path.resolve("src", "bot", "commands"), true); 56 | commands.forEach(script => { 57 | let finalPath = script.path.dir.substring(script.path.dir.lastIndexOf("/") + 1); 58 | script.exports.name = script.name.toLowerCase(); 59 | script.exports.type = finalPath; 60 | 61 | let command = new Command(script.exports); 62 | }); 63 | 64 | bot.connect(); 65 | } 66 | 67 | async function loadScripts(filepath, deep = false) { 68 | if(!fs.existsSync(filepath)) return []; 69 | 70 | let scripts = []; 71 | let files = await getFiles(filepath, file => file.endsWith(".js"), deep); 72 | 73 | files.forEach(file => { 74 | scripts.push({ 75 | name: file.substring(file.lastIndexOf("/") + 1, file.length - 3), 76 | exports: require(file), 77 | path: path.parse(file) 78 | }); 79 | }); 80 | 81 | return scripts; 82 | } 83 | 84 | async function getFiles(filepath, filter = () => true, deep = false) { 85 | let files = await fs.readdirAsync(filepath); 86 | let validFiles = []; 87 | 88 | for(let file of files) { 89 | if(deep) { 90 | let stats = await fs.lstatAsync(`${filepath}/${file}`); 91 | if(stats.isDirectory()) validFiles = validFiles.concat(await getFiles(`${filepath}/${file}`, filter, deep)); 92 | } 93 | 94 | if(filter(file)) validFiles.push(`${filepath}/${file}`); 95 | } 96 | 97 | return validFiles; 98 | } 99 | 100 | const statPoster = require(path.resolve("src", "bot", "modules", "statPoster")); 101 | setInterval(statPoster, 1800000); 102 | 103 | process.on("unhandledRejection", err => { 104 | if(err.message && err.message.startsWith("Request timed out")) return; 105 | try { 106 | let resp = JSON.parse(err.response); 107 | // these codes mean someone bamboozled perms 108 | if(~[0, 10003, 10008, 40005, 50001, 50013].indexOf(resp.code)) return; 109 | else throw err; 110 | } catch(err2) { 111 | if(raven.installed) raven.captureException(err); 112 | else console.error(err.stack); 113 | } 114 | }); 115 | 116 | cluster.worker.on("message", async msg => { 117 | if(msg.type === "eval") { 118 | try { 119 | let result = await eval(msg.input); 120 | process.send({ type: "output", result, id: msg.id }); 121 | } catch(err) { 122 | process.send({ type: "output", error: err.stack, id: msg.id }); 123 | } 124 | } else if(msg.type === "output") { 125 | cluster.worker.emit("outputMessage", msg); 126 | } 127 | }); 128 | 129 | init(); 130 | -------------------------------------------------------------------------------- /src/bot/listeners/on/channelCreate.js: -------------------------------------------------------------------------------- 1 | module.exports = async channel => { 2 | if(channel.type !== 0) return; 3 | 4 | let manageChannels = channel.guild.members.get(bot.user.id).permission.has("manageChannels"); 5 | if(!manageChannels) return; 6 | 7 | let mutedRole = channel.guild.roles.find(role => role.name.toLowerCase() === __("words.muted", channel.guild)); 8 | if(mutedRole) bot.editChannelPermission(channel.id, mutedRole.id, 0, 2048, "role", "Set Muted Role Permissions"); 9 | }; 10 | -------------------------------------------------------------------------------- /src/bot/listeners/on/channelDelete.js: -------------------------------------------------------------------------------- 1 | const channels = require("../../modules/channels.js"); 2 | module.exports = async channel => { 3 | let channelOwner = await channels.memberFromChannel(channel); 4 | if(!channelOwner || typeof channelOwner !== "object") return; 5 | 6 | channels.delete(channelOwner); 7 | }; 8 | -------------------------------------------------------------------------------- /src/bot/listeners/on/guildBanAdd.js: -------------------------------------------------------------------------------- 1 | const modLog = require("../../modules/modLog.js"); 2 | module.exports = async (guild, user) => { 3 | modLog.create(guild, "ban", user); 4 | }; 5 | -------------------------------------------------------------------------------- /src/bot/listeners/on/guildBanRemove.js: -------------------------------------------------------------------------------- 1 | const modLog = require("../../modules/modLog.js"); 2 | module.exports = async (guild, user) => { 3 | modLog.create(guild, "unban", user); 4 | }; 5 | -------------------------------------------------------------------------------- /src/bot/listeners/on/guildCreate.js: -------------------------------------------------------------------------------- 1 | const statPoster = require("../../modules/statPoster.js"); 2 | module.exports = async guild => { 3 | let defaultChannel = guild.channels.filter(channel => channel.type === 0) 4 | .sort((a, b) => a.position - b.position) 5 | .find(channel => channel.permissionsOf(bot.user.id).has("readMessages") && 6 | channel.permissionsOf(bot.user.id).has("sendMessages")); 7 | 8 | if(bot.config.beta) { 9 | let donator = await r.db("Oxyl").table("donators").get(guild.ownerID).run(); 10 | if(!donator) { 11 | if(defaultChannel) { 12 | await defaultChannel.createMessage("You are not a donator, you cannot use Oxyl Beta!") 13 | .catch(err => {}); // eslint-disable-line 14 | } 15 | 16 | guild.leave(); 17 | } 18 | return; 19 | } 20 | 21 | let joinMessage = "Thanks for adding Oxyl to your server! Here is how to get started:\n"; 22 | joinMessage += "❓ Run `o!help` to get a list of commands."; 23 | joinMessage += `Oxyl also supports ${bot.user.mention} or \`oxyl\` as prefixes.\n`; 24 | joinMessage += "⚙ Feeling like configuring the bot? Use the dashboard at .\n"; 25 | joinMessage += "🎵 Like music? Use the `play` command to start to queue music!\n"; 26 | joinMessage += "💰 Feeling generous? Donate at .\n"; 27 | joinMessage += "📎 Need support, or want to be notified about updates? "; 28 | joinMessage += "Join Oxyl's Server at http://discord.gg/9wkTDcE"; 29 | if(defaultChannel) defaultChannel.createMessage(joinMessage); 30 | 31 | if(bot.config.bot.serverChannel) { 32 | let owner = bot.users.get(guild.ownerID); 33 | let botCount = guild.members.filter(member => member.bot).length; 34 | let botPercent = ((botCount / guild.memberCount) * 100).toFixed(2); 35 | let userCount = guild.memberCount - botCount; 36 | let userPercent = ((userCount / guild.memberCount) * 100).toFixed(2); 37 | 38 | let content = "✅ JOINED GUILD ✅\n"; 39 | content += `Guild: ${guild.name} (${guild.id})\n`; 40 | content += `Owner: ${owner.username}#${owner.discriminator} (${owner.id})\n`; 41 | content += `Members: ${guild.memberCount} **|** `; 42 | content += `Users: ${userCount} (${userPercent}%) **|** `; 43 | content += `Bots: ${botCount} (${botPercent}%)`; 44 | 45 | try { 46 | await bot.createMessage(bot.config.bot.serverChannel, content); 47 | } catch(err) { 48 | console.error(`Failed to send message to server log: ${err.message}`); 49 | } 50 | } 51 | 52 | statPoster(); 53 | }; 54 | -------------------------------------------------------------------------------- /src/bot/listeners/on/guildDelete.js: -------------------------------------------------------------------------------- 1 | const excludedTables = ["blacklist", "musicCache", "timedEvents"]; 2 | const statPoster = require("../../modules/statPoster.js"); 3 | module.exports = async guild => { 4 | let tables = await r.tableList().run(); 5 | for(let table of tables) { 6 | let indexes = await r.table(table).indexList().run(); 7 | if(~indexes.indexOf("guildID")) r.table(table).getAll(guild.id, { index: "guildID" }).delete().run(); 8 | else r.table(table).filter({ guildID: guild.id }).delete().run(); 9 | } 10 | 11 | if(bot.config.bot.serverChannel) { 12 | let owner = bot.users.get(guild.ownerID); 13 | let botCount = guild.members.filter(member => member.bot).length; 14 | let botPercent = ((botCount / guild.memberCount) * 100).toFixed(2); 15 | let userCount = guild.memberCount - botCount; 16 | let userPercent = ((userCount / guild.memberCount) * 100).toFixed(2); 17 | 18 | let content = "❌ LEFT GUILD ❌\n"; 19 | content += `Guild: ${guild.name} (${guild.id})\n`; 20 | content += `Owner: ${owner.username}#${owner.discriminator} (${owner.id})\n`; 21 | content += `Members: ${guild.memberCount} **|** `; 22 | content += `Users: ${userCount} (${userPercent}%) **|** `; 23 | content += `Bots: ${botCount} (${botPercent}%)`; 24 | 25 | try { 26 | await bot.createMessage(bot.config.bot.serverChannel, content); 27 | } catch(err) { 28 | console.error(`Failed to send message to server log: ${err.message}`); 29 | } 30 | } 31 | 32 | statPoster(); 33 | }; 34 | -------------------------------------------------------------------------------- /src/bot/listeners/on/guildMemberAdd.js: -------------------------------------------------------------------------------- 1 | module.exports = (guild, member) => { 2 | bot.utils.autoRole(guild, member); 3 | bot.utils.rolePersistHandler(guild, member, "join"); 4 | bot.utils.userLog(guild, member, "join"); 5 | }; 6 | -------------------------------------------------------------------------------- /src/bot/listeners/on/guildMemberRemove.js: -------------------------------------------------------------------------------- 1 | module.exports = (guild, member) => { 2 | bot.utils.rolePersistHandler(guild, member, "leave"); 3 | bot.utils.userLog(guild, member, "leave"); 4 | }; 5 | -------------------------------------------------------------------------------- /src/bot/listeners/on/guildMemberUpdate.js: -------------------------------------------------------------------------------- 1 | const modLog = require("../../modules/modLog.js"); 2 | module.exports = async (guild, member, oldMember) => { 3 | if(!member || !oldMember) return; 4 | else if(member.roles === oldMember.roles) return; 5 | 6 | let addedRoles = member.roles.filter(role => !~oldMember.roles.indexOf(role)); 7 | let removedRoles = oldMember.roles.filter(role => !~member.roles.indexOf(role)); 8 | if(bot.config.bot.donator && ~addedRoles.indexOf(bot.config.bot.donator.role)) { 9 | r.table("donators").insert({ userID: member.id }).run(); 10 | if(bot.config.bot.donator.channel && !bot.config.beta) { 11 | bot.createMessage(bot.config.bot.donator.channel, `Thank you <@${member.id}> for donating to Oxyl!`); 12 | } 13 | } else if(bot.config.bot.donator && ~removedRoles.indexOf(bot.config.bot.donator.role)) { 14 | r.table("donators").get(member.id).delete().run(); 15 | } 16 | 17 | let trackedRoles = await r.table("settings").get(["modLog.track", guild.id]).run(); 18 | if(!trackedRoles || !trackedRoles.value) return; 19 | else trackedRoles = trackedRoles.value; 20 | 21 | trackedRoles.forEach(tracked => { 22 | if(~addedRoles.indexOf(tracked)) { 23 | modLog.create(guild, "specialRoleAdd", member.user || member, { role: tracked }); 24 | } else if(~removedRoles.indexOf(tracked)) { 25 | modLog.create(guild, "specialRoleRemove", member.user || member, { role: tracked }); 26 | } 27 | }); 28 | }; 29 | -------------------------------------------------------------------------------- /src/bot/listeners/on/messageCreate.js: -------------------------------------------------------------------------------- 1 | const commands = require("../../modules/commands.js"); 2 | const handleCensor = require("../../modules/censors.js"); 3 | 4 | module.exports = async message => { 5 | if(message.author.bot || !message.member) return; 6 | if(message.channel.guild) { 7 | await handleCensor(message); 8 | message.locale = bot.localeCache.get(message.author.id) || bot.localeCache.get(message.channel.guild.id) || "en"; 9 | } else { 10 | message.locale = bot.localeCache.get(message.author.id) || "en"; 11 | } 12 | 13 | if(bot.ignoredChannels.has(message.channel.id) && 14 | !(message.member.permission.has("administrator") || message.author.id === message.channel.guild.ownerID)) return; 15 | else commands(message); 16 | }; 17 | -------------------------------------------------------------------------------- /src/bot/listeners/on/voiceChannelJoin.js: -------------------------------------------------------------------------------- 1 | const channels = require("../../modules/channels.js"); 2 | module.exports = async (member, channel) => { 3 | let channelOwner = await channels.memberFromChannel(channel); 4 | if(!channelOwner || typeof channelOwner !== "object" || member.bot) return; 5 | 6 | if(channel.deleteTimeout) { 7 | clearTimeout(channel.deleteTimeout); 8 | delete channel.deleteTimeout; 9 | } 10 | }; 11 | -------------------------------------------------------------------------------- /src/bot/listeners/on/voiceChannelLeave.js: -------------------------------------------------------------------------------- 1 | const channels = require("../../modules/channels.js"); 2 | module.exports = async (member, channel) => { 3 | let channelOwner = await channels.memberFromChannel(channel); 4 | if(!channelOwner || typeof channelOwner !== "object") return; 5 | 6 | if(!channel.voiceMembers.filter(voiceMember => !voiceMember.bot).length) { 7 | channel.deleteTimeout = setTimeout(() => channels.delete(channelOwner), 300000); 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /src/bot/listeners/on/voiceChannelSwitch.js: -------------------------------------------------------------------------------- 1 | const channels = require("../../modules/channels.js"); 2 | module.exports = async (member, newChannel, oldChannel) => { 3 | let channelOwnerNew = await channels.memberFromChannel(newChannel); 4 | let channelOwnerOld = await channels.memberFromChannel(oldChannel); 5 | if(channelOwnerNew && typeof channelOwnerNew === "object" && !member.bot) { 6 | if(newChannel.deleteTimeout) { 7 | clearTimeout(newChannel.deleteTimeout); 8 | delete newChannel.deleteTimeout; 9 | } 10 | } 11 | 12 | if(channelOwnerOld && typeof channelOwnerOld === "object" && 13 | !oldChannel.voiceMembers.filter(voiceMember => !voiceMember.bot).length) { 14 | oldChannel.deleteTimeout = setTimeout(() => channels.delete(channelOwnerOld), 300000); 15 | } 16 | }; 17 | -------------------------------------------------------------------------------- /src/bot/listeners/once/ready.js: -------------------------------------------------------------------------------- 1 | const lavalink = require("eris-lavalink"); 2 | lavalink.Player.prototype.stateUpdate = function(state) { 3 | this.state = state; 4 | if(bot.players.has(this.guildId)) bot.players.get(this.guildId).setTime(state.position); 5 | }; 6 | 7 | const Player = require("../../structures/player"); 8 | const channels = require("../../modules/channels"); 9 | module.exports = () => { 10 | console.startup(`Worker ${cluster.worker.id} bot ready (took ${bot.utils.parseMs(process.uptime() * 1000)})`); 11 | 12 | bot.voiceConnections = new lavalink.PlayerManager(bot, bot.config.lavalink.nodes, { 13 | numShards: cluster.worker.totalShards, 14 | userId: bot.user.id 15 | }); 16 | 17 | let mentionIndex = bot.config.bot.prefixes.indexOf("mention"); 18 | if(~mentionIndex) bot.config.bot.prefixes[mentionIndex] = `<@!?${bot.user.id}>`; 19 | 20 | let name = `${bot.config.bot.prefixes[0]}help | ${cluster.worker.shardRange}`; 21 | bot.editStatus("online", { name }); 22 | cluster.worker.send({ type: "startup" }); 23 | 24 | Player.resumeQueues(); 25 | channels.load(); 26 | }; 27 | -------------------------------------------------------------------------------- /src/bot/modules/args.js: -------------------------------------------------------------------------------- 1 | const resolver = bot.utils.resolver; 2 | module.exports = async (message) => { 3 | let command = message.command; 4 | message.content = message.content.trim(); 5 | if(command.args.length === 0) return [message.content.trim()]; 6 | 7 | let args = [], currentQuoted = false, startIndex = 0; 8 | for(let i = 0; i < message.content.length; i++) { 9 | if(command.args.length === 1) { 10 | if((message.content.startsWith(`"`) && message.content.endsWith(`"`)) || 11 | (message.content.startsWith("'") && message.content.endsWith("'")) || 12 | (message.content.startsWith("`") && message.content.endsWith("`"))) { 13 | args = [message.content.substring(1, message.content.length - 1).trim()]; 14 | } else { 15 | args = [message.content.trim()]; 16 | } 17 | break; 18 | } 19 | 20 | let char = message.content.charAt(i); 21 | if(char === `"` || char === "'" || char === "`") { 22 | if(currentQuoted === false) { 23 | startIndex = i + 1; 24 | } else { 25 | args.push(message.content.substring(startIndex, i)); 26 | 27 | if(command.args.length - 1 === args.length) { 28 | args.push(message.content.substring(i + 1).trim()); 29 | break; 30 | } 31 | startIndex = 0; 32 | } 33 | currentQuoted = !currentQuoted; 34 | } else if(char === " " && !currentQuoted) { 35 | if((startIndex === 0 && args.length === 0) || startIndex !== 0) { 36 | args.push(message.content.substring(startIndex, i)); 37 | 38 | if(command.args.length - 1 === args.length) { 39 | args.push(message.content.substring(i + 1).trim()); 40 | startIndex = 0; 41 | break; 42 | } 43 | } 44 | startIndex = i + 1; 45 | } 46 | } 47 | if((startIndex !== 0 && args.length < command.args.length) || 48 | (startIndex === 0 && args.length === 0 && message.content.length !== 0)) { 49 | args.push(message.content.substring(startIndex)); 50 | } 51 | 52 | if(args.length < command.args.filter(arg => !arg.optional).length) { 53 | return __("modules.args.usageError", message, { command: command.name, usage: command.usage }); 54 | } 55 | 56 | try { 57 | args = await Promise.all(args.map((arg, i) => resolver[command.args[i].type](message, arg, command.args[i]))); 58 | return args; 59 | } catch(err) { 60 | return err.message; 61 | } 62 | }; 63 | -------------------------------------------------------------------------------- /src/bot/modules/audio/autoplay.js: -------------------------------------------------------------------------------- 1 | const cheerio = require("cheerio"); 2 | const superagent = require("superagent"); 3 | const main = require(`${__dirname}/main.js`); 4 | 5 | module.exports = async videoID => { 6 | const { text: html } = await superagent.get(`https://www.youtube.com/watch?v=${videoID}`); 7 | const $ = cheerio.load(html); // eslint-disable-line id-length 8 | 9 | let id = $(".autoplay-bar .content-wrapper .content-link").attr("href"); 10 | id = id.substring(id.indexOf("=") + 1); 11 | return main(`https://www.youtube.com/watch?v=${id}`); 12 | }; 13 | -------------------------------------------------------------------------------- /src/bot/modules/audio/main.js: -------------------------------------------------------------------------------- 1 | const superagent = require("superagent"); 2 | module.exports = async query => { 3 | if(!/^http|(sc|yt)search/.test(query)) query = `ytsearch:${query}`; 4 | let { body } = await superagent.get(bot.config.lavalink.url) 5 | .set("Authorization", bot.config.lavalink.auth) 6 | .query({ identifier: query }); 7 | 8 | if(body && Array.isArray(body) && body.length) { 9 | if(body.length === 1 || /^(sc|yt)search/.test(query)) { 10 | let [data] = body; 11 | return Object.assign(data.info, { track: data.track }); 12 | } else { 13 | return body.map(video => Object.assign({}, video.info, { track: video.track })); 14 | } 15 | } else { 16 | return "NO_VIDEO"; 17 | } 18 | }; 19 | -------------------------------------------------------------------------------- /src/bot/modules/censors.js: -------------------------------------------------------------------------------- 1 | const modLog = require("../modules/modLog.js"); 2 | module.exports = async message => { 3 | let censors = bot.censors.get(message.channel.guild.id); 4 | if(!censors || !message.member) return true; 5 | else if(message.member.permission.has("manageMessages")) return true; 6 | 7 | for(let censor of Array.from(censors.values())) { 8 | if(!(new RegExp(censor.regex, "i")).test(message.content)) continue; 9 | message.delete(); 10 | message.channel.createMessage(censor.message ? 11 | censor.message.replace(/{{mention}}/g, message.author.mention) 12 | .replace(/{{username}}/g, message.author.username) 13 | .replace(/{{discrim(inator)?}}/g, message.author.discriminator) 14 | .replace(/{{id}}/g, message.author.id) : 15 | __("modules.censor", message.channel.guild, { mention: message.author.mention })); 16 | 17 | if(censor.action === "warn") { 18 | await bot.utils.warnMember(message.member, bot.user, "Said a censored phrase"); 19 | } else if(censor.action === "kick") { 20 | let channel = await modLog.channel(message.channel.guild); 21 | if(channel) { 22 | modLog.presetReasons[message.channel.guild.id] = { mod: bot.user, reason: "Said a censored phrase" }; 23 | } 24 | 25 | await message.member.kick("Said a censored phrase"); 26 | modLog.create(message.channel.guild, "kick", message.author); 27 | } else if(censor.action === "softban") { 28 | let channel = await modLog.channel(message.channel.guild); 29 | if(channel) { 30 | modLog.presetReasons[message.channel.guild.id] = { mod: bot.user, reason: "Said a censored phrase" }; 31 | } 32 | 33 | await message.channel.guild.banMember(message.author.id, 7, "Said a censored phrase"); 34 | await message.channel.guild.unbanMember(message.author.id, "Softban"); 35 | } else if(censor.action === "ban") { 36 | let channel = await modLog.channel(message.channel.guild); 37 | if(channel) { 38 | modLog.presetReasons[message.channel.guild.id] = { mod: bot.user, reason: "Said a censored phrase" }; 39 | } 40 | 41 | await message.member.ban(7, "Said a censored phrase"); 42 | } 43 | 44 | break; 45 | } 46 | return true; 47 | }; 48 | -------------------------------------------------------------------------------- /src/bot/modules/channels.js: -------------------------------------------------------------------------------- 1 | const redis = bot.utils.redis; 2 | module.exports = { 3 | enabled: async guildID => { 4 | let enabled = await r.table("settings").get(["channels", guildID]).run(); 5 | return enabled ? enabled.value : false; 6 | }, 7 | get: async member => { 8 | let channel = await redis.get(`channels:${member.guild.id}:${member.id}`); 9 | if(channel && member.guild.channels.has(channel)) channel = member.guild.channels.get(channel); 10 | return channel; 11 | }, 12 | memberFromChannel: async channel => { 13 | let member = await redis.get(`channelMemberMap:${channel.id}`); 14 | if(member && channel.guild.members.has(member)) member = channel.guild.members.get(member); 15 | return member; 16 | }, 17 | create: async (member, name) => { 18 | let channel = await bot.createChannel(member.guild.id, name, 2, 19 | `Channel command (owned by ${member.user.username}#${member.user.discriminator})`); 20 | await bot.editChannelPermission(channel.id, member.id, 1048592, 0, "member", 21 | `Channel command (owned by ${member.user.username}#${member.user.discriminator})`); 22 | 23 | await redis.set(`channels:${member.guild.id}:${member.id}`, channel.id, "EX", 86400); 24 | await redis.set(`channelMemberMap:${channel.id}`, member.id, "EX", 86400); 25 | channel.deleteTimeout = setTimeout(() => module.exports.delete(member), 300000); 26 | return channel; 27 | }, 28 | delete: async member => { 29 | let channel = await module.exports.get(member); 30 | await redis.del(`channels:${member.guild.id}:${member.id}`); 31 | await redis.del(`channelMemberMap:${channel.id || channel}`); 32 | 33 | if(channel && typeof channel === "object") await channel.delete(); 34 | }, 35 | load: async () => { 36 | let keys = await redis.keys(`${redis.options.keyPrefix}channels:*`); 37 | keys.forEach(async key => { 38 | let guildID = key.substring(key.indexOf(":", redis.options.keyPrefix.length) + 1, key.lastIndexOf(":")); 39 | let memberID = key.substring(key.lastIndexOf(":") + 1); 40 | if(!bot.guilds.has(guildID)) return; 41 | let guild = bot.guilds.get(guildID); 42 | 43 | let channelID = await redis.get(key.substring(redis.options.keyPrefix.length)); 44 | if(!guild.channels.has(channelID)) { 45 | module.exports.delete(guild.members.get(memberID)); 46 | } else { 47 | let channel = guild.channels.get(channelID); 48 | if(!channel.voiceMembers.filter(voiceMember => !voiceMember.bot).length) { 49 | channel.deleteTimeout = setTimeout(() => module.exports.delete(guild.members.get(memberID)), 300000); 50 | } 51 | } 52 | }); 53 | } 54 | }; 55 | -------------------------------------------------------------------------------- /src/bot/modules/commands.js: -------------------------------------------------------------------------------- 1 | const argHandler = require("./args.js"); 2 | 3 | module.exports = async message => { 4 | const guild = message.channel.guild; 5 | 6 | let prefix = `^(?:${bot.config.bot.prefixes.join("|")}),?(?:\\s+)?([\\s\\S]+)`; 7 | if(guild && bot.prefixes.has(guild.id)) { 8 | let insertIndex = prefix.indexOf("),?("); 9 | let guildPrefix = `|${bot.utils.escapeRegex(bot.prefixes.get(guild.id))}`; 10 | prefix = prefix.substring(0, insertIndex) + guildPrefix + prefix.substring(insertIndex); 11 | } 12 | prefix = new RegExp(prefix, "i"); 13 | 14 | let match = message.content.match(prefix); 15 | if(!match) return; 16 | else if(match) message.content = match[1]; 17 | 18 | let command; 19 | if(!~message.content.indexOf(" ")) { 20 | command = message.content; 21 | message.content = ""; 22 | } else { 23 | command = message.content.substring(0, message.content.indexOf(" ")); 24 | message.content = message.content.substring(message.content.indexOf(" ")); 25 | } 26 | command = command.toLowerCase().trim(); 27 | 28 | command = Object.keys(bot.commands) 29 | .map(key => bot.commands[key]) 30 | .find(cmd => command === cmd.name || ~cmd.aliases.indexOf(command)); 31 | if(!command) return; 32 | else if(!command.caseSensitive) message.content = message.content.toLowerCase(); 33 | 34 | let editedInfo = {}; 35 | if(guild) { 36 | editedInfo = await r.table("editedCommands").get([command.name, message.channel.guild.id]).run() || {}; 37 | } 38 | 39 | if(editedInfo.enabled === false) { 40 | return; 41 | } else if((command.guildOnly || command.perm || command.type === "admin") && !guild) { 42 | message.channel.createMessage(__("modules.commands.guildOnly", message)); 43 | return; 44 | } else if(command.type === "creator" && !~bot.config.bot.creators.indexOf(message.author.id)) { 45 | message.channel.createMessage(__("modules.commands.creatorOnly", message)); 46 | return; 47 | } else if(command.type === "admin" && !editedInfo.roles && 48 | !(message.member.permission.has("administrator") || message.author.id === guild.ownerID)) { 49 | message.channel.createMessage(__("modules.commands.adminOnly", message)); 50 | return; 51 | } else if(command.perm && !editedInfo.roles && 52 | !(message.member.permission.has(command.perm) || message.author.id === guild.ownerID)) { 53 | message.channel.createMessage(__("modules.commands.noPerms", message, { perm: command.perm })); 54 | return; 55 | } else if(editedInfo.roles && editedInfo.roles.length && 56 | !editedInfo.roles.some(roleID => ~message.member.roles.indexOf(roleID))) { 57 | let roleNames = editedInfo.roles.map(roleID => guild.roles.has(roleID) ? guild.roles.get(roleID).name : roleID); 58 | message.channel.createMessage(__("modules.commands.noRoles", message, { roles: `\`${roleNames.join("`, `")}\`` })); 59 | return; 60 | } 61 | 62 | message.command = command; 63 | let args = message.args = await argHandler(message, command); 64 | if(typeof args === "string") { 65 | message.channel.createMessage(args); 66 | return; 67 | } 68 | 69 | try { 70 | await message.channel.sendTyping(); 71 | let result = await command.run(message); 72 | 73 | let output = { content: "" }, file; 74 | if(Array.isArray(result)) { 75 | output.content = result[0]; 76 | file = result[1]; 77 | } else if(typeof result === "object") { 78 | output = result; 79 | } else if(result) { 80 | output.content = result.toString(); 81 | } else { 82 | output = false; 83 | } 84 | 85 | if(output) { 86 | if(output.content) output.content = output.content.substring(0, 2000); 87 | await message.channel.createMessage(output, file); 88 | } 89 | } catch(err) { 90 | try { 91 | let resp = JSON.parse(err.response); 92 | let permCodes = [10003, 10008, 40005, 50001, 50013]; 93 | if(~permCodes.indexOf(resp.code)) { 94 | message.channel.createMessage(__("modules.commands.permissionFail", message)); 95 | } else { 96 | throw err; 97 | } 98 | } catch(error) { 99 | message.channel.createMessage(__("modules.commands.errorRunning", message, 100 | { stack: bot.utils.codeBlock(err.stack) })); 101 | } 102 | } 103 | }; 104 | -------------------------------------------------------------------------------- /src/bot/modules/images/byemom.js: -------------------------------------------------------------------------------- 1 | const Jimp = require("jimp"); 2 | 3 | async function generate(text) { 4 | const textImage = await new Jimp(500, 500); 5 | const font = await Jimp.loadFont(Jimp.FONT_SANS_16_BLACK); 6 | textImage.print(font, 0, 250, text); 7 | if(text !== "how to rotate text in ms paint") textImage.rotate(-25, false); 8 | 9 | const image = await Jimp.read(`${__dirname}/byemom.png`); 10 | if(text === "how to rotate text in ms paint") image.composite(textImage, 340, 250); 11 | else image.composite(textImage, 325, 235); 12 | image.getBase64(Jimp.MIME_PNG, (err, data) => { 13 | if(err) throw err; 14 | process.stdout.write(data); 15 | }); 16 | } 17 | 18 | generate(process.env.TEXT); 19 | -------------------------------------------------------------------------------- /src/bot/modules/images/byemom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minemidnight/oxyl/d234f5f440c6771091d04108c9361b91ad4a2d7c/src/bot/modules/images/byemom.png -------------------------------------------------------------------------------- /src/bot/modules/images/main.js: -------------------------------------------------------------------------------- 1 | const exec = Promise.promisify(require("child_process").exec); 2 | const path = require("path"); 3 | 4 | module.exports = { 5 | byeMom: async text => { 6 | const stdout = await exec(`node ${path.resolve(`${__dirname}/byemom.js`)}`, { 7 | env: { TEXT: text }, 8 | maxBuffer: Infinity 9 | }); 10 | 11 | return module.exports.imageFromURI(stdout); 12 | }, 13 | needsMoreJpeg: async url => { 14 | const stdout = await exec(`node ${path.resolve(`${__dirname}/needsmorejpeg.js`)}`, { 15 | env: { IMAGE_URL: url }, 16 | maxBuffer: Infinity 17 | }); 18 | 19 | return module.exports.imageFromURI(stdout); 20 | }, 21 | imageFromURI: data => { 22 | let [, ext, buffer] = data.match(/^data:image\/([A-Za-z-+\/]+);base64,(.+)$/); 23 | buffer = Buffer.from(buffer, "base64"); 24 | 25 | return { buffer, ext }; 26 | } 27 | }; 28 | -------------------------------------------------------------------------------- /src/bot/modules/images/needsmorejpeg.js: -------------------------------------------------------------------------------- 1 | const Jimp = require("jimp"); 2 | 3 | async function generate(url) { 4 | const image = await Jimp.read(url); 5 | await image.quality(5); 6 | 7 | image.getBase64(Jimp.MIME_JPEG, (err, data) => { 8 | if(err) throw err; 9 | process.stdout.write(data); 10 | }); 11 | } 12 | 13 | generate(process.env.IMAGE_URL); 14 | -------------------------------------------------------------------------------- /src/bot/modules/locales.js: -------------------------------------------------------------------------------- 1 | const locales = {}; 2 | const fs = Promise.promisifyAll(require("fs")); 3 | const path = require("path"); 4 | 5 | async function loadLocales() { 6 | for(let locale of bot.locales) { 7 | let strings = await fs.readFileAsync(path.resolve("locales", `${locale}.json`)); 8 | locales[locale] = JSON.parse(strings); 9 | } 10 | } 11 | 12 | const __ = global.__ = (context, object = { locale: "en" }, values = {}, capitializeFirst = false) => { 13 | if(object.ownerID) object.locale = bot.localeCache.get(object.id) || "en"; 14 | let string = locales[object.locale]; 15 | for(let part of context.split(".")) { 16 | if(!string[part] && object.locale !== "en") return __(context, undefined, values, capitializeFirst); 17 | else if(!string[part]) return __("modules.locales.invalidContext", object, { context, part }); 18 | else string = string[part]; 19 | } 20 | 21 | if(typeof string !== "string") return __("modules.locales.incompleteContext", object, { context }); 22 | 23 | let placeholders = string.match(/{{[^{}]+}}/g); 24 | if(placeholders) { 25 | placeholders.forEach(placeholder => { 26 | placeholder = placeholder.substring(2, placeholder.length - 2); 27 | string = string.replace(`{{${placeholder}}}`, 28 | values[placeholder] === undefined ? __("modules.locales.invalidPlaceholder", object) : values[placeholder]); 29 | }); 30 | } 31 | 32 | if(capitializeFirst) return string.charAt(0).toUpperCase() + string.substring(1); 33 | else return string; 34 | }; 35 | 36 | module.exports = { loadLocales, __ }; 37 | loadLocales(); 38 | -------------------------------------------------------------------------------- /src/bot/modules/modLog.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | cases: guild => r.table("modLog").getAll(guild.id, { index: "guildID" }).run(), 3 | channel: async guild => { 4 | let data = await r.table("settings").get(["modLog.channel", guild.id]).run(); 5 | return data ? data.value : false; 6 | }, 7 | info: async (guild, caseNum) => await r.table("modLog").get([caseNum, guild.id]).run(), 8 | create: async (guild, action, user, extraData = {}) => { 9 | let caseNum = ((await module.exports.cases(guild)).length || 0) + 1; 10 | let channelID = await module.exports.channel(guild); 11 | if(!channelID) return false; 12 | 13 | let parseData = { 14 | action, 15 | caseNum, 16 | user: `${user.username}#${user.discriminator}` 17 | }; 18 | 19 | let insertData = { 20 | id: [caseNum, guild.id], 21 | action, 22 | caseNum, 23 | guildID: guild.id, 24 | userID: user.id, 25 | userDisplay: `${user.username}#${user.discriminator}` 26 | }; 27 | 28 | for(let key in extraData) { 29 | parseData[key] = extraData[key]; 30 | insertData[key] = extraData[key]; 31 | } 32 | 33 | if(module.exports.presetReasons[guild.id]) { 34 | let preset = module.exports.presetReasons[guild.id]; 35 | delete module.exports.presetReasons[guild.id]; 36 | 37 | parseData.reason = preset.reason; 38 | parseData.mod = insertData.modDisplay = `${preset.mod.username}#${preset.mod.discriminator}`; 39 | 40 | insertData.reason = preset.reason; 41 | insertData.modID = preset.mod.id; 42 | } 43 | 44 | if(action === "ban") { 45 | module.exports.possibleSoftbans[`${guild.id}.${user.id}`] = caseNum; 46 | setTimeout(() => delete module.exports.possibleSoftbans[`${guild.id}.${user.id}`], 120000); 47 | } else if(action === "unban" && module.exports.possibleSoftbans[`${guild.id}.${user.id}`]) { 48 | insertData.action = parseData.action = "softban"; 49 | parseData.caseNum = caseNum = module.exports.possibleSoftbans[`${guild.id}.${user.id}`]; 50 | delete module.exports.possibleSoftbans[`${guild.id}.${user.id}`]; 51 | } 52 | 53 | let parsed = module.exports.parse(guild, parseData); 54 | if(insertData.action !== "softban") { 55 | let message = await bot.createMessage(channelID, parsed); 56 | insertData.messageID = message.id; 57 | 58 | await r.table("modLog").insert(insertData).run(); 59 | } else { 60 | let caseData = await module.exports.info(guild, caseNum); 61 | if(!caseData) return false; 62 | if(caseData.reason) { 63 | parsed = module.exports.parse(guild, { 64 | action: "softban", 65 | caseNum, 66 | mod: caseData.modDisplay, 67 | reason: caseData.reason, 68 | role: caseData.role, 69 | user: caseData.userDisplay 70 | }); 71 | } 72 | 73 | await bot.editMessage(channelID, caseData.messageID, parsed); 74 | await r.table("modLog").get([caseNum, guild.id]).update({ action: "softban" }).run(); 75 | } 76 | 77 | return true; 78 | }, 79 | set: async (guild, caseNum, reason, mod) => { 80 | let channelID = await module.exports.channel(guild); 81 | if(!channelID) return "NO_CHANNEL"; 82 | 83 | let caseData = await module.exports.info(guild, caseNum); 84 | if(!caseData) return "NO_DATA"; 85 | 86 | try { 87 | var message = await bot.getMessage(channelID, caseData.messageID); 88 | } catch(err) { 89 | return "NO_MSG"; 90 | } 91 | 92 | let user = bot.users.has(caseData.userID) ? 93 | `${bot.users.get(caseData.userID).username}#${bot.users.get(caseData.userID).discriminator}` : 94 | caseData.userDisplay; 95 | let modDisplay = `${mod.username}#${mod.discriminator}`; 96 | 97 | let parsed = module.exports.parse(guild, { 98 | action: caseData.action, 99 | caseNum, 100 | mod: modDisplay, 101 | reason, 102 | role: caseData.role, 103 | user, 104 | warnCount: caseData.warnCount 105 | }); 106 | await bot.editMessage(channelID, caseData.messageID, parsed); 107 | 108 | await r.table("modLog").get([caseNum, guild.id]).update({ 109 | userDisplay: user, 110 | modID: mod.id, 111 | modDisplay, 112 | reason 113 | }).run(); 114 | return "SUCCESS"; 115 | }, 116 | parse: (guild, { action, caseNum, user, reason, mod, role, warnCount }) => { 117 | action = __(`modules.modLog.actions.${action}`, guild); 118 | action = action.substring(0, 1).toUpperCase() + action.substring(1); 119 | 120 | let parsed = `__**${__("modules.modLog.words.case", guild).toUpperCase()} #${caseNum}**__` + 121 | `\n**${__("modules.modLog.words.action", guild).toUpperCase()}**: ${action}`; 122 | if(role) { 123 | parsed += ` (${guild.roles.get(role).name})`; 124 | } else if(warnCount !== undefined) { 125 | parsed += `\n**${__("modules.modLog.words.totalWarnings", guild).toUpperCase()}**: ${warnCount}`; 126 | } 127 | 128 | parsed += `\n**${__("modules.modLog.words.user", guild).toUpperCase()}**: ${user}` + 129 | `\n**${__("modules.modLog.words.reason", guild).toUpperCase()}**: `; 130 | 131 | if(reason) parsed += `${reason}\n**${__("modules.modLog.words.mod", guild).toUpperCase()}**: ${mod}`; 132 | else parsed += __("modules.modLog.words.noReason", guild, { caseNum }); 133 | return parsed; 134 | }, 135 | presetReasons: {}, 136 | possibleSoftbans: [] 137 | }; 138 | -------------------------------------------------------------------------------- /src/bot/modules/statPoster.js: -------------------------------------------------------------------------------- 1 | const superagent = require("superagent"); 2 | module.exports = async () => { 3 | let guilds = (await process.output({ 4 | type: "all_shards", 5 | input: () => bot.guilds.size 6 | })).results.reduce((a, b) => a + b); 7 | 8 | if(!bot.config.bot.postStats) return; 9 | if(bot.config.bot.dbotsKey) { 10 | await superagent.post(`https://bots.discord.pw/api/bots/${bot.user.id}/stats`) 11 | .set("Authorization", bot.config.bot.dbotsKey) 12 | .send({ server_count: guilds }).catch(err => {}); // eslint-disable-line 13 | } 14 | 15 | if(bot.config.bot.dbotsOrgKey) { 16 | await superagent.post(`https://discordbots.org/api/bots/${bot.user.id}/stats`) 17 | .set("Authorization", bot.config.bot.dbotsKey) 18 | .send({ server_count: guilds }).catch(err => {}); // eslint-disable-line 19 | } 20 | 21 | if(bot.config.bot.carbonKey) { 22 | await superagent.post("https://www.carbonitex.net/discord/data/botdata.php") 23 | .send({ key: bot.config.bot.carbonKey, servercount: guilds }).catch(err => {}); // eslint-disable-line 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /src/bot/modules/tags.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | get: async name => r.table("tags").get(name).run(), 3 | getByUser: async userID => r.table("tags").getAll(userID, { index: "ownerID" }).run(), 4 | create: async (name, ownerID, content) => await r.table("tags").insert({ name, ownerID, content }).run(), 5 | update: async (name, content) => await r.table("tags").get(name).update({ content }).run(), 6 | delete: async name => await r.table("tags").get(name).delete().run(), 7 | list: async page => await r.table("tags").skip(page).limit(100).run() 8 | }; 9 | -------------------------------------------------------------------------------- /src/bot/structures/command.js: -------------------------------------------------------------------------------- 1 | class Command { 2 | constructor(data) { 3 | if(data.disabled) return; 4 | if(!data.name) throw new Error("Command has no name"); 5 | else if(!data.process) throw new Error(`Command ${data.name} must have code`); 6 | else if(!data.description) console.warn(`Command ${data.name} has no description`); 7 | 8 | this.name = data.name; 9 | this.process = data.process; 10 | this.aliases = data.aliases || []; 11 | this.description = data.description; 12 | this.caseSensitive = !!data.caseSensitive; 13 | this.args = data.args || []; 14 | this.guildOnly = !!data.guildOnly; 15 | this.perm = data.perm; 16 | this.type = data.type; 17 | 18 | if(this.args.length === 0) { 19 | this.usage = "[]"; 20 | } else { 21 | let usage = []; 22 | for(let arg of this.args) { 23 | arg.label = arg.label || arg.type; 24 | 25 | if(arg.optional) usage.push(`[${arg.label}]`); 26 | else usage.push(`<${arg.label}>`); 27 | } 28 | this.usage = usage.join(" "); 29 | } 30 | 31 | bot.commands[this.name] = this; 32 | } 33 | 34 | run(message) { 35 | return this.process(message); 36 | } 37 | } 38 | 39 | module.exports = Command; 40 | -------------------------------------------------------------------------------- /src/bot/utils/autoRole.js: -------------------------------------------------------------------------------- 1 | module.exports = async (guild, member) => { 2 | let autoRoles = await r.table("autoRole").getAll(guild.id, { index: "guildID" }).run(); 3 | if(autoRoles.length === 0) return; 4 | else autoRoles = autoRoles.map(data => data.roleID); 5 | 6 | try { 7 | await Promise.all(autoRoles.map(roleID => member.addRole(roleID, "Auto role"))); 8 | } catch(err) {} // eslint-disable-line no-empty 9 | }; 10 | -------------------------------------------------------------------------------- /src/bot/utils/codeBlock.js: -------------------------------------------------------------------------------- 1 | module.exports = (content, lang) => { 2 | if(!lang) lang = ""; 3 | return `\n\`\`\`${lang}\n${content}\n\`\`\``; 4 | }; 5 | -------------------------------------------------------------------------------- /src/bot/utils/escapeRegex.js: -------------------------------------------------------------------------------- 1 | module.exports = string => string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&", "g"); 2 | -------------------------------------------------------------------------------- /src/bot/utils/formatDate.js: -------------------------------------------------------------------------------- 1 | const months = ["January", "February", "March", "April", "May", "June", "July", 2 | "August", "September", "October", "November", "December"]; 3 | const weekdays = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]; 4 | 5 | module.exports = (toFormat) => { 6 | let date = new Date(toFormat); 7 | 8 | let month = months[date.getMonth()]; 9 | let day = date.getDate(); 10 | let weekday = weekdays[date.getDay()]; 11 | let hour = date.getHours(); 12 | let min = date.getMinutes(); 13 | let sec = date.getSeconds(); 14 | let year = date.getFullYear(); 15 | 16 | day = (day < 10 ? "0" : "") + day; 17 | hour = (hour < 10 ? "0" : "") + hour; 18 | min = (min < 10 ? "0" : "") + min; 19 | sec = (sec < 10 ? "0" : "") + sec; 20 | 21 | return `${weekday.substring(0, 3)}, ${month.substring(0, 3)} ${day} ${year}, ${hour}:${min}:${sec}`; 22 | }; 23 | -------------------------------------------------------------------------------- /src/bot/utils/getMutedRole.js: -------------------------------------------------------------------------------- 1 | module.exports = async message => { 2 | const guild = message.channel.guild; 3 | let botMember = guild.members.get(bot.user.id); 4 | let mutedRole = guild.roles.find(role => role.name.toLowerCase() === __("words.muted", guild)); 5 | 6 | if(mutedRole && !mutedRole.addable) { 7 | return __("commands.moderator.mute.roleError", message); 8 | } else if(!mutedRole) { 9 | let rolePerms = botMember.permission.has("manageRoles"); 10 | let channelPerms = botMember.permission.has("manageChannels"); 11 | if(!rolePerms && !channelPerms) { 12 | return __("commands.moderator.mute.missingBothPerms", message); 13 | } else if(!rolePerms) { 14 | return __("commands.moderator.mute.missingRolePerms", message); 15 | } else if(!channelPerms) { 16 | return __("commands.moderator.mute.missingChannelPerms", message); 17 | } else { 18 | mutedRole = await guild.createRole({ 19 | name: __("words.muted", guild, {}, true), 20 | permissions: 0, 21 | color: 0xDF4242 22 | }, "Create Muted Role"); 23 | // mutedRole.editPosition(0); 24 | 25 | guild.channels 26 | .filter(ch => ch.type === 0) 27 | .forEach(ch => ch.editPermission(mutedRole.id, 0, 2048, "role", "Configure Muted Role")); 28 | return mutedRole; 29 | } 30 | } else { 31 | return mutedRole; 32 | } 33 | }; 34 | -------------------------------------------------------------------------------- /src/bot/utils/parseMs.js: -------------------------------------------------------------------------------- 1 | module.exports = ms => { 2 | let days = Math.floor(ms / 1000 / 60 / 60 / 24); 3 | ms -= days * 1000 * 60 * 60 * 24; 4 | let hours = Math.floor(ms / 1000 / 60 / 60); 5 | ms -= hours * 1000 * 60 * 60; 6 | let mins = Math.floor(ms / 1000 / 60); 7 | ms -= mins * 1000 * 60; 8 | let secs = Math.floor(ms / 1000); 9 | 10 | let timestr = ""; 11 | if(days > 0) timestr += `${days}d `; 12 | if(hours > 0) timestr += `${hours}h `; 13 | if(mins > 0) timestr += `${mins}m `; 14 | if(secs > 0) timestr += `${secs}s`; 15 | return timestr.trim(); 16 | }; 17 | -------------------------------------------------------------------------------- /src/bot/utils/redis.js: -------------------------------------------------------------------------------- 1 | const Redis = require("ioredis"); 2 | module.exports = new Redis({ keyPrefix: bot.config.beta ? "oxylbeta:" : "oxyl:" }); 3 | -------------------------------------------------------------------------------- /src/bot/utils/rolePersistHandler.js: -------------------------------------------------------------------------------- 1 | const modLog = require("../modules/modLog.js"); 2 | module.exports = async (guild, member, type) => { 3 | let persists = await r.table("rolePersistRules").getAll(guild.id, { index: "guildID" }).run(); 4 | if(!persists.length) return; 5 | else persists = persists.map(data => data.roleID); 6 | 7 | if(type === "leave") { 8 | let toPersist = member.roles.filter(roleID => ~persists.indexOf(roleID)); 9 | if(!member.roles.length || !toPersist.length) return; 10 | 11 | await r.table("rolePersistStorage").insert({ 12 | id: [member.id, guild.id], 13 | guildID: guild.id, 14 | roles: toPersist, 15 | memberID: member.id 16 | }).run(); 17 | } else if(type === "join") { 18 | let persistData = await r.table("rolePersistStorage").get([member.id, guild.id]).run(); 19 | if(!persistData || !persistData.roles.length) return; 20 | 21 | await r.table("rolePersistStorage").get([member.id, guild.id]).delete().run(); 22 | let channel = await modLog.channel(guild); 23 | let trackedRoles = await r.table("settings").get(["modLog.track", guild.id]).run(); 24 | 25 | for(let roleID of persistData.roles) { 26 | if(trackedRoles && ~trackedRoles.value.indexOf(roleID) && channel) { 27 | modLog.presetReasons[guild.id] = { reason: __("phrases.rolePersist", guild), mod: bot.user }; 28 | } 29 | 30 | member.addRole(roleID, "Role Persist").catch(() => {}); // eslint-disable-line no-empty-function 31 | } 32 | } 33 | }; 34 | -------------------------------------------------------------------------------- /src/bot/utils/secondsToDuration.js: -------------------------------------------------------------------------------- 1 | module.exports = dur => { 2 | let hours = Math.floor(dur / 3600); 3 | let mins = Math.floor(dur % 3600 / 60); 4 | let secs = Math.floor(dur % 3600 % 60); 5 | return `${(hours > 0 ? `${hours}:${mins < 10 ? "0" : ""}` : "") + mins}:${secs < 10 ? "0" : ""}${secs}`; 6 | }; 7 | -------------------------------------------------------------------------------- /src/bot/utils/timedEvents.js: -------------------------------------------------------------------------------- 1 | const modLog = require("../modules/modLog.js"); 2 | module.exports = { 3 | update: async () => { 4 | let waitingEvents = await r.table("timedEvents").between(r.minval, Date.now(), { index: "date" }).run(); 5 | 6 | waitingEvents.forEach(event => { 7 | if(!module.exports[event.type] || !event || !event.type) return; 8 | else module.exports[event.type](event); 9 | r.table("timedEvents").get(event.uuid).delete().run(); 10 | }); 11 | }, 12 | reminder: data => { 13 | let content = __("modules.timedEvents.reminder", { locale: bot.localeCache.get(data.userID) || "en" }, { 14 | date: bot.utils.formatDate(data.createdAt), 15 | action: data.action 16 | }); 17 | 18 | bot.createMessage(data.channelID, content); 19 | }, 20 | giveaway: async data => { 21 | let entrees = await bot.getMessageReaction(data.channelID, data.messageID, "🎉") 22 | .filter(user => !user.bot); 23 | 24 | if(entrees.length === 0) { 25 | let content = __("modules.timedEvents.noGiveawayEntries", 26 | { locale: bot.localeCache.get(data.guildID) || "en" }, 27 | { item: data.item }); 28 | 29 | bot.createMessage(data.channelID, content); 30 | } else { 31 | let winner = entrees[Math.floor(Math.random() * entrees.length)]; 32 | let content = __("modules.timedEvents.giveawayWinner", 33 | { locale: bot.localeCache.get(data.guildID) || "en" }, 34 | { winner: winner.mention, item: data.item }); 35 | 36 | bot.createMessage(data.channelID, content); 37 | } 38 | }, 39 | tempmute: async data => { 40 | let muteExpired = __("phrases.muteExpired", { locale: bot.localeCache.get(data.guildID) || "en" }); 41 | let channel = await modLog.channel({ id: data.guildID }); 42 | let trackedList = await r.table("settings").get(["modLog.track", data.guildID]).run(); 43 | 44 | if(channel && trackedList && ~trackedList.value.indexOf(data.mutedRole)) { 45 | modLog.presetReasons[data.guildID] = { 46 | mod: bot.user, 47 | reason: muteExpired 48 | }; 49 | } 50 | 51 | await bot.removeGuildMemberRole(data.guildID, data.memberID, data.mutedRole, muteExpired) 52 | .catch(err => {}); // eslint-disable-line 53 | } 54 | }; 55 | setInterval(module.exports.update, 15000); 56 | -------------------------------------------------------------------------------- /src/bot/utils/userLog.js: -------------------------------------------------------------------------------- 1 | module.exports = async (guild, member, type) => { 2 | type = type === "join" ? "greeting" : "farewell"; 3 | if(type === "greeting") { 4 | var dm = await r.table("settings").get(["greeting-dm", guild.id]).run(); 5 | dm = dm ? dm.value : false; 6 | } 7 | 8 | let userlog = await r.table("settings").get(["userlog", guild.id]).run(); 9 | if(!userlog && !dm) return; 10 | else if(userlog) userlog = userlog.value; 11 | 12 | let message = await r.table("settings").get([type, guild.id]).run(); 13 | if(!message) return; 14 | else message = message.value; 15 | 16 | let user = member.user ? member.user : member; 17 | message = message.replace(/{{mention}}/g, user.mention) 18 | .replace(/{{username}}/g, user.username) 19 | .replace(/{{discrim(inator)?}}/g, user.discriminator) 20 | .replace(/{{id}}/g, user.id); 21 | 22 | try { 23 | if(dm) { 24 | let channel = await user.getDMChannel(); 25 | await channel.createMessage(message); 26 | } else { 27 | await bot.createMessage(userlog, message); 28 | } 29 | } catch(err) { 30 | return; 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /src/bot/utils/warnMember.js: -------------------------------------------------------------------------------- 1 | const modLog = require("../modules/modLog.js"); 2 | module.exports = async (member, mod, reason) => { 3 | let { length: warnCount } = await r.table("warnings") 4 | .getAll(member.id, { index: "userID" }) 5 | .filter({ guildID: member.guild.id }).run(); 6 | warnCount++; 7 | 8 | let kick, ban; 9 | let kickAt = await r.table("settings").get(["modLog.kickat", member.guild.id]).run(); 10 | let banAt = await r.table("settings").get(["modLog.banAt", member.guild.id]).run(); 11 | if(banAt && warnCount >= banAt.value) ban = true; 12 | if(kickAt && warnCount >= kickAt.value) kick = true; 13 | 14 | let channel = await modLog.channel(member.guild); 15 | if(channel) { 16 | if(reason) modLog.presetReasons[member.guild.id] = { reason, mod }; 17 | await modLog.create(member.guild, "warn", member.user, { warnCount }); 18 | 19 | if(ban || kick) { 20 | modLog.presetReasons[member.guild.id] = { reason: "Warning Threshold", mod }; 21 | } 22 | } 23 | 24 | if(ban) { 25 | member.ban(7, "Warning Threshold"); 26 | } else if(kick) { 27 | member.kick("Warning Threshold"); 28 | modLog.create(member.guild, "kick", member.user); 29 | } 30 | 31 | await r.table("warnings").insert({ guildID: member.guild.id, userID: member.id }).run(); 32 | return warnCount; 33 | }; 34 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | global.Promise = require("bluebird"); 2 | global.cluster = require("cluster"); 3 | require("./misc/logger.js"); 4 | if(cluster.isMaster) require("./master.js"); 5 | else require("./worker.js"); 6 | -------------------------------------------------------------------------------- /src/master.js: -------------------------------------------------------------------------------- 1 | const superagent = require("superagent"); 2 | const webhook = require("./misc/webhookStatus"); 3 | const botHandler = require("./worker_handling/bot"); 4 | const siteHandler = require("./worker_handling/site"); 5 | 6 | function handleWorker(worker) { 7 | if(worker.type === "bot") botHandler(worker); 8 | else if(worker.type === "website") siteHandler(worker); 9 | } 10 | 11 | Object.defineProperty(cluster, "onlineWorkers", { 12 | get: () => Object.keys(cluster.workers) 13 | .map(id => cluster.workers[id]) 14 | .filter(work => work.isConnected()) 15 | }); 16 | 17 | const config = require(`${__dirname}/../config.json`); 18 | async function init() { 19 | await (require("./misc/rethink")).init(); 20 | const { body: { shards: totalShards } } = await superagent.get("https://discordapp.com/api/gateway/bot") 21 | .set("Authorization", config.bot.token); 22 | process.totalShards = totalShards; 23 | 24 | let shardsPerWorker, fields = []; 25 | fields.push({ name: "Total Shards", value: totalShards }); 26 | 27 | const coreCount = require("os").cpus().length; 28 | if(coreCount >= totalShards) shardsPerWorker = 1; 29 | else shardsPerWorker = Math.ceil(totalShards / coreCount); 30 | fields.push({ name: "Shards per Worker", value: shardsPerWorker }); 31 | 32 | const workerCount = Math.ceil(totalShards / shardsPerWorker); 33 | fields.push({ name: "Total Workers", value: workerCount }); 34 | for(let i = 0; i < workerCount; i++) { 35 | let shardStart = i * shardsPerWorker, shardEnd = ((i + 1) * shardsPerWorker) - 1; 36 | if(shardEnd > totalShards - 1) shardEnd = totalShards - 1; 37 | let shardRange = shardStart === shardEnd ? `shard ${shardStart}` : `shards ${shardStart}-${shardEnd}`; 38 | 39 | const worker = cluster.fork(); 40 | Object.assign(worker, { type: "bot", shardStart, shardEnd, shardRange, totalShards }); 41 | handleWorker(worker); 42 | } 43 | 44 | if(!config.beta && config.website) { 45 | const website = cluster.fork(); 46 | Object.assign(website, { type: "website" }); 47 | handleWorker(website); 48 | fields.push({ name: "Website", value: `Website hosted on worker ${website.id}` }); 49 | } 50 | 51 | webhook({ 52 | title: `Master Started up`, 53 | color: 0x00FF00, 54 | fields, 55 | timestamp: new Date() 56 | }); 57 | } 58 | init(); 59 | -------------------------------------------------------------------------------- /src/misc/logger.js: -------------------------------------------------------------------------------- 1 | const chalk = require("chalk"); 2 | module.exports = { 3 | startup: (info) => console.log(`${chalk.black.bgGreen("STARTUP")} ${chalk.green(info)}`), 4 | error: (info) => console.log(`${chalk.bgRed("ERROR")} ${chalk.red(info)}`), 5 | warn: (info) => console.log(`${chalk.black.bgYellow("WARN")} ${chalk.yellow(info)}`), 6 | info: (info) => console.log(`${chalk.bgBlue("INFO")} ${chalk.magenta(info)}`) 7 | }; 8 | 9 | Object.keys(module.exports).forEach(i => console[i] = module.exports[i]); 10 | -------------------------------------------------------------------------------- /src/misc/outputHandler.js: -------------------------------------------------------------------------------- 1 | const Snowflake = require("./snowflake.js"); 2 | const EventEmitter = require("events").EventEmitter; 3 | 4 | class OutputHandler extends EventEmitter { 5 | constructor(msg) { 6 | super(); 7 | if(typeof msg.input === "function") { 8 | msg.input = `(${msg.input.toString().replace(/\\(t|r|n)/gi, "")}).call()`; 9 | } else if(typeof msg.input === "string") { 10 | msg.input = `(async function(){${msg.input}}).call()`; 11 | } 12 | 13 | this.msg = msg; 14 | this.ended = false; 15 | this.result = undefined; 16 | 17 | this.msg.id = new Snowflake().id; 18 | this.msg.output = true; 19 | process.send(msg); 20 | 21 | this.listener = message => this.verify(message); 22 | cluster.worker.on("outputMessage", this.listener); 23 | setTimeout(() => this.stop("time"), 60000); 24 | } 25 | 26 | verify(msg) { 27 | if(msg.id !== this.msg.id) return; 28 | 29 | this.result = msg; 30 | this.stop("finished"); 31 | } 32 | 33 | stop(reason) { 34 | if(this.ended) return; 35 | else this.ended = true; 36 | cluster.worker.removeListener("outputMessage", this.listener); 37 | 38 | this.emit("end", this.result, reason); 39 | } 40 | } 41 | 42 | module.exports = process.output = msg => { 43 | const handler = new OutputHandler(msg); 44 | return new Promise(resolve => handler.on("end", resolve)); 45 | }; 46 | -------------------------------------------------------------------------------- /src/misc/rethink.js: -------------------------------------------------------------------------------- 1 | const rethinkdbdash = require("rethinkdbdash"); 2 | module.exports = { 3 | init: async () => { 4 | const config = require(require("path").resolve("config.json")); 5 | if(!config.other.database) { 6 | console.warn("No RethinkDB connection info in config.json, Oxyl won't work as expected"); 7 | return false; 8 | } 9 | 10 | let dbName = config.other.databaseName || "Oxyl"; 11 | let connectionInfo = config.other.database; 12 | connectionInfo.silent = true; 13 | connectionInfo.db = dbName; 14 | const r = rethinkdbdash(connectionInfo); // eslint-disable-line id-length 15 | 16 | let dbs = await r.dbList().run(); 17 | if(!~dbs.indexOf(dbName)) { 18 | console.info(`Creating database ${dbName}...`); 19 | await r.dbCreate(dbName).run(); 20 | } 21 | 22 | let tableList = await r.tableList().run(); 23 | let tablesExpected = [{ 24 | name: "autoRole", 25 | primary: "roleID", 26 | indexes: ["guildID"] 27 | }, { 28 | name: "censors", 29 | primary: "id", // [censorID, guildID] 30 | indexes: ["guildID"] 31 | }, { 32 | name: "donators", 33 | primary: "userID" 34 | }, { 35 | name: "editedCommands", 36 | primary: "id", // [command, guildID] 37 | indexes: ["guildID"] 38 | }, { 39 | name: "ignoredChannels", 40 | primary: "channelID" 41 | }, { 42 | name: "locales", 43 | primary: "id" 44 | }, { 45 | name: "modLog", 46 | primary: "id", // [caseNum, guildID] 47 | indexes: ["guildID"] 48 | }, { 49 | name: "roleMe", 50 | primary: "roleID", 51 | indexes: ["guildID"] 52 | }, { 53 | name: "rolePersistRules", 54 | primary: "roleID", 55 | indexes: ["guildID"] 56 | }, { 57 | name: "rolePersistStorage", 58 | primary: "id" // [memberID, guildID] 59 | }, { 60 | name: "savedQueues", 61 | primary: "id" // [savedID, userID] 62 | }, { 63 | name: "settings", 64 | primary: "id", // [name, guildID] 65 | indexes: ["guildID"] 66 | }, { 67 | name: "tags", 68 | primary: "name", 69 | indexes: ["ownerID"] 70 | }, { 71 | name: "timedEvents", 72 | primary: "uuid", 73 | indexes: ["date"] 74 | }, { 75 | name: "warnings", 76 | primary: "uuid", 77 | indexes: ["userID"] 78 | }]; 79 | 80 | for(let table of tablesExpected) { 81 | if(~tableList.indexOf(table.name)) continue; 82 | 83 | console.info(`Creating "${table.name}" table...`); 84 | await r.tableCreate(table.name, { primaryKey: table.primary }).run(); 85 | 86 | if(table.indexes) { 87 | for(let index of table.indexes) await r.table(table.name).indexCreate(index).run(); 88 | } 89 | if(table.insertions) { 90 | for(let insertion of table.insertions) await r.table(table.name).insert(insertion).run(); 91 | } 92 | } 93 | 94 | console.startup(`RethinkDB initated on master`); 95 | await r.getPoolMaster().drain(); 96 | return true; 97 | }, 98 | connect: async () => { 99 | const config = require(require("path").resolve("config.json")); 100 | if(!config.other.database) return; 101 | 102 | let dbName = config.other.databaseName || "Oxyl"; 103 | let connectionInfo = config.other.database; 104 | connectionInfo.silent = true; 105 | connectionInfo.db = dbName; 106 | global.r = rethinkdbdash(connectionInfo); // eslint-disable-line id-length 107 | 108 | if(typeof bot !== "undefined") module.exports.botStuff(); 109 | }, 110 | botStuff: async () => { 111 | let prefixes = await r.table("settings").filter({ name: "prefix" }).run(); 112 | prefixes.forEach(setting => { 113 | let shard = ~~((setting.guildID / 4194304) % cluster.worker.totalShards); 114 | if(shard >= cluster.worker.shardStart && shard <= cluster.worker.shardEnd) { 115 | bot.prefixes.set(setting.guildID, setting.value); 116 | } 117 | }); 118 | 119 | let censors = await r.table("censors").run(); 120 | censors.forEach(censor => { 121 | let shard = ~~((censor.guildID / 4194304) % cluster.worker.totalShards); 122 | if(shard >= cluster.worker.shardStart && shard <= cluster.worker.shardEnd) { 123 | let censorsCache = bot.censors.get(censor.guildID); 124 | let censorObject = { action: censor.action, regex: censor.regex }; 125 | if(censor.message) censorObject.message = censor.message; 126 | 127 | if(censorsCache) { 128 | censorsCache.set(censor.censorID, censorObject); 129 | } else { 130 | bot.censors.set(censor.guildID, new Map()) 131 | .get(censor.guildID) 132 | .set(censor.censorID, censorObject); 133 | } 134 | } 135 | }); 136 | 137 | let channels = await r.table("ignoredChannels").run(); 138 | channels.forEach(ignored => { 139 | let shard = ~~((ignored.guildID / 4194304) % cluster.worker.totalShards); 140 | if(shard >= cluster.worker.shardStart && shard <= cluster.worker.shardEnd) { 141 | bot.ignoredChannels.set(ignored.channelID, ignored.guildID); 142 | } 143 | }); 144 | 145 | let locales = await r.table("locales").run(); 146 | locales.forEach(locale => bot.localeCache.set(locale.id, locale.locale)); 147 | } 148 | }; 149 | 150 | if(!cluster.isMaster) module.exports.connect(); 151 | -------------------------------------------------------------------------------- /src/misc/snowflake.js: -------------------------------------------------------------------------------- 1 | const bigNumber = require("big-number"); 2 | class Snowflake { 3 | constructor() { 4 | this.timestamp = Date.now(); 5 | this.id = bigNumber(this.timestamp) 6 | .subtract(1420070400000) 7 | .multiply(4194304) 8 | .add(cluster.isWorker ? cluster.worker.id : 0) 9 | .toString(); 10 | } 11 | } 12 | module.exports = Snowflake; 13 | -------------------------------------------------------------------------------- /src/misc/webhookStatus.js: -------------------------------------------------------------------------------- 1 | const superagent = require("superagent"); 2 | const webhook = require(require("path").resolve("config.json")).other.webhook; 3 | module.exports = async embed => { 4 | if(!webhook) return false; 5 | return await superagent.post(webhook).send({ embeds: [embed] }); 6 | }; 7 | -------------------------------------------------------------------------------- /src/website/public/assets/css/loading.css: -------------------------------------------------------------------------------- 1 | .pace { 2 | -webkit-pointer-events: none; 3 | pointer-events: none; 4 | -webkit-user-select: none; 5 | -moz-user-select: none; 6 | user-select: none; 7 | z-index: 2000; 8 | } 9 | 10 | .pace-inactive { 11 | display: none; 12 | } 13 | 14 | .pace .pace-progress { 15 | background: #29d; 16 | position: fixed; 17 | z-index: 2000; 18 | top: 0; 19 | right: 100%; 20 | width: 100%; 21 | height: 3px; 22 | } 23 | 24 | .pace .pace-progress-inner { 25 | display: block; 26 | position: absolute; 27 | right: 0px; 28 | width: 100px; 29 | height: 100%; 30 | box-shadow: 0 0 10px #29d, 0 0 5px #29d; 31 | opacity: 1.0; 32 | -webkit-transform: rotate(3deg) translate(0px, -4px); 33 | -moz-transform: rotate(3deg) translate(0px, -4px); 34 | -ms-transform: rotate(3deg) translate(0px, -4px); 35 | -o-transform: rotate(3deg) translate(0px, -4px); 36 | transform: rotate(3deg) translate(0px, -4px); 37 | z-index: 2000; 38 | } 39 | 40 | .pace .pace-activity { 41 | display: block; 42 | position: fixed; 43 | z-index: 2000; 44 | top: 20px; 45 | right: 20px; 46 | width: 19px; 47 | height: 19px; 48 | border: solid 2px transparent; 49 | border-top-color: #29d; 50 | border-left-color: #29d; 51 | border-radius: 50%; 52 | -webkit-animation: pace-spinner 400ms linear infinite; 53 | -moz-animation: pace-spinner 400ms linear infinite; 54 | -ms-animation: pace-spinner 400ms linear infinite; 55 | -o-animation: pace-spinner 400ms linear infinite; 56 | animation: pace-spinner 400ms linear infinite; 57 | z-index: 2000; 58 | } 59 | 60 | @-webkit-keyframes pace-spinner { 61 | 0% { -webkit-transform: rotate(0deg); transform: rotate(0deg); } 62 | 100% { -webkit-transform: rotate(360deg); transform: rotate(360deg); } 63 | } 64 | @-moz-keyframes pace-spinner { 65 | 0% { -moz-transform: rotate(0deg); transform: rotate(0deg); } 66 | 100% { -moz-transform: rotate(360deg); transform: rotate(360deg); } 67 | } 68 | @-o-keyframes pace-spinner { 69 | 0% { -o-transform: rotate(0deg); transform: rotate(0deg); } 70 | 100% { -o-transform: rotate(360deg); transform: rotate(360deg); } 71 | } 72 | @-ms-keyframes pace-spinner { 73 | 0% { -ms-transform: rotate(0deg); transform: rotate(0deg); } 74 | 100% { -ms-transform: rotate(360deg); transform: rotate(360deg); } 75 | } 76 | @keyframes pace-spinner { 77 | 0% { transform: rotate(0deg); transform: rotate(0deg); } 78 | 100% { transform: rotate(360deg); transform: rotate(360deg); } 79 | } 80 | -------------------------------------------------------------------------------- /src/website/public/assets/js/settings.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-undef */ 2 | function setting(action, element) { 3 | element = $(element); 4 | element.parent().children().attr("disabled", ""); 5 | let form = element.parent().parent().find("form"); 6 | 7 | if(action === "set") { 8 | let formData = {}; 9 | form.serializeArray().concat( 10 | form.find("input[type=checkbox]:not(:checked)").map((i, ele) => ({ name: ele.name, value: false })).get() 11 | ).forEach(input => formData[input.name] = input.value); 12 | 13 | $.ajax({ 14 | complete: data => { 15 | element.parent().children().removeAttr("disabled"); 16 | let resp = JSON.parse(data.responseText); 17 | 18 | let type, text; 19 | if(resp.success) { 20 | type = "Success"; 21 | text = "The setting was successfully saved"; 22 | } else if(resp.error) { 23 | type = "Error"; 24 | text = `Error saving setting: ${resp.error}`; 25 | } else { 26 | type = "Unknown"; 27 | text = "No success nor error"; 28 | } 29 | 30 | let id = Date.now(); 31 | $(".notifications").append(`
${type}
` + 32 | `×

${text}

`); 33 | setTimeout(() => $(`#${id}`).css("margin-bottom", ""), 1); 34 | setTimeout(() => removeNotification(`#${id}`), 5000); 35 | }, 36 | data: formData, 37 | dataType: "json", 38 | type: "POST", 39 | url: window.location.href.replace("dashboard/settings", "update/set") 40 | }); 41 | } else { 42 | let name = form.find("[name]").attr("name"); 43 | if(name.startsWith("track")) name = "modLog.track"; 44 | form.find("input:text, input:password, input:file, select, textarea").val(""); 45 | form.find("input:checkbox").removeAttr("checked").removeAttr("selected"); 46 | 47 | $.ajax({ 48 | complete: data => { 49 | element.parent().children().removeAttr("disabled"); 50 | let resp = JSON.parse(data.responseText); 51 | 52 | let type, text; 53 | if(resp.success) { 54 | type = "Success"; 55 | text = "The setting was successfully reset"; 56 | } else if(resp.error) { 57 | type = "Error"; 58 | text = `Error resetting setting: ${resp.error}`; 59 | } else { 60 | type = "Unknown"; 61 | text = "No success nor error"; 62 | } 63 | 64 | 65 | let id = Date.now(); 66 | $(".notifications").append(`
${type}
` + 67 | `×

${text}

`); 68 | setTimeout(() => $(`#${id}`).css("margin-bottom", ""), 1); 69 | setTimeout(() => removeNotification(`#${id}`), 5000); 70 | }, 71 | data: { name }, 72 | dataType: "json", 73 | type: "POST", 74 | url: window.location.href.replace("dashboard/settings", "update/reset") 75 | }); 76 | } 77 | } 78 | global.setting = setting; 79 | -------------------------------------------------------------------------------- /src/website/public/assets/other/lazy.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minemidnight/oxyl/d234f5f440c6771091d04108c9361b91ad4a2d7c/src/website/public/assets/other/lazy.gif -------------------------------------------------------------------------------- /src/website/public/assets/other/whitney.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minemidnight/oxyl/d234f5f440c6771091d04108c9361b91ad4a2d7c/src/website/public/assets/other/whitney.woff -------------------------------------------------------------------------------- /src/website/routes/accounts.js: -------------------------------------------------------------------------------- 1 | const router = module.exports = require("express").Router(); // eslint-disable-line new-cap 2 | 3 | router.get("/", async (req, res) => { 4 | let accounts = []; 5 | for(let key in req.cookies) { 6 | if(!key.startsWith("token_")) continue; 7 | try { 8 | let cookie = JSON.parse(req.cookies[key]); 9 | let info = await req.app.discordInfo(cookie, "users/@me", req); 10 | if(!info) continue; 11 | info.tokenid = cookie.id; 12 | 13 | accounts.push(info); 14 | } catch(err) { 15 | continue; 16 | } 17 | } 18 | 19 | res.status(200).send(await req.app.page(req, "accounts", { accounts })).end(); 20 | }); 21 | -------------------------------------------------------------------------------- /src/website/routes/callback.js: -------------------------------------------------------------------------------- 1 | const router = module.exports = require("express").Router(); // eslint-disable-line new-cap 2 | const superagent = require("superagent"); 3 | 4 | router.get("/", async (req, res) => { 5 | if(req.query.code) { 6 | try { 7 | var { body } = await superagent 8 | .post("https://discordapp.com/api/oauth2/token") 9 | .type("form") 10 | .send({ 11 | client_id: req.app.config.website.botID, // eslint-disable-line camelcase 12 | client_secret: req.app.config.website.secret, // eslint-disable-line camelcase 13 | code: req.query.code, 14 | grant_type: "authorization_code", // eslint-disable-line camelcase 15 | redirect_uri: `${req.app.config.website.baseURL}/callback` // eslint-disable-line camelcase 16 | }); 17 | } catch(err) { 18 | res.redirect(`https://discordapp.com/oauth2/authorize?response_type=code&redirect_uri=` + 19 | `${req.app.config.website.baseURL}/callback&scope=identify+guilds&client_id=${req.app.config.website.botID}`); 20 | return; 21 | } 22 | 23 | let stringified = JSON.stringify({ 24 | token: body.access_token, 25 | time: Date.now(), 26 | refresh: body.refresh_token, 27 | id: Date.now() 28 | }); 29 | 30 | res.status(200).send(``); 35 | } else if(req.query.timestamp) { 36 | res.status(200).send(``); 41 | } else { 42 | res.status(200).redirect(req.app.config.website.baseURL); 43 | } 44 | res.end(); 45 | }); 46 | -------------------------------------------------------------------------------- /src/website/routes/commands.js: -------------------------------------------------------------------------------- 1 | const router = module.exports = require("express").Router(); // eslint-disable-line new-cap 2 | 3 | let commands = {}; 4 | router.get("/", async (req, res) => { 5 | let categories = new Set(); 6 | Object.keys(commands).forEach(key => categories.add(commands[key].type)); 7 | res.status(200).send(await req.app.page(req, "commands", { commands, categories: Array.from(categories) })).end(); 8 | }); 9 | 10 | async function updateCommands() { 11 | let backup = commands; 12 | commands = (await process.output({ 13 | target: 1, 14 | input: `return bot.commands`, 15 | type: "shard" 16 | })).result; 17 | 18 | if(!commands) commands = backup; 19 | } 20 | setTimeout(updateCommands, 100); 21 | setInterval(updateCommands, 3600000); 22 | -------------------------------------------------------------------------------- /src/website/routes/dashboard.js: -------------------------------------------------------------------------------- 1 | const router = module.exports = require("express").Router(); // eslint-disable-line new-cap 2 | 3 | router.get("/overview/*", async (req, res) => { 4 | let id = req.path.substring(9).replace(/\//g, ""); 5 | if(!id.match(/\d+/)) { 6 | res.status(404).send(await req.app.page(req, "404", { guild: id })).end(); 7 | return; 8 | } 9 | 10 | let hasServer = (await process.output({ 11 | target: id, 12 | input: `return bot.guilds.has("${id}")`, 13 | type: "guild" 14 | })).result; 15 | 16 | if(hasServer) { 17 | let modLog = (await r.table("modLog").getAll(id, { index: "guildID" }).run()).length; 18 | let { botCount, channelCount, memberCount, onlineMembers, owner, region } = (await process.output({ 19 | target: id, 20 | input: `let guild = bot.guilds.get("${id}");` + 21 | `let owner = bot.users.get(guild.ownerID);` + 22 | `return {` + 23 | `botCount: guild.members.filter(m => m.bot).length,` + 24 | `channelCount: guild.channels.size,` + 25 | `memberCount: guild.memberCount,` + 26 | `onlineMembers: guild.members.filter(m => m.status !== "offline").length,` + 27 | `owner: owner.username + "#" + owner.discriminator,` + 28 | `region: guild.region,` + 29 | `}`, 30 | type: "guild" 31 | })).result; 32 | 33 | res.status(200).send(await req.app.page(req, "dashboard", { 34 | botCount, 35 | botPercent: (botCount / memberCount * 100).toFixed(2), 36 | channelCount, 37 | guild: id, 38 | memberCount, 39 | modLog, 40 | onlineMembers, 41 | owner, 42 | region, 43 | userCount: memberCount - botCount, 44 | userPercent: ((memberCount - botCount) / memberCount * 100).toFixed(2) 45 | })).end(); 46 | } else { 47 | res.status(200).send(await req.app.page(req, "invite", { guild: id })).end(); 48 | } 49 | }); 50 | 51 | router.get("/settings/*", async (req, res) => { 52 | let id = req.path.substring(9).replace(/\//g, ""); 53 | if(!id.match(/\d+/)) { 54 | res.status(404).send(await req.app.page(req, "404", { guild: id })).end(); 55 | return; 56 | } 57 | 58 | let hasServer = (await process.output({ 59 | target: id, 60 | input: `return bot.guilds.has("${id}")`, 61 | type: "guild" 62 | })).result; 63 | 64 | if(hasServer) { 65 | let modLog = (await r.table("modLog").getAll(id, { index: "guildID" }).run()).length; 66 | let guildSettings = JSON.parse(JSON.stringify(settings)); 67 | (await r.table("settings").getAll(id, { index: "guildID" }).run()).forEach(data => { 68 | let index = guildSettings.indexOf(guildSettings.find(gset => gset.name === data.name)); 69 | guildSettings[index].value = data.value; 70 | }); 71 | 72 | let [textChannels, roles] = (await process.output({ 73 | target: id, 74 | input: `let guild = bot.guilds.get("${id}");` + 75 | `return [guild.channels.filter(c => c.type === 0).map(c => ({ id: c.id, name: c.name })),` + 76 | `guild.roles.map(r => ({ id: r.id, name: r.name }))]`, 77 | type: "guild" 78 | })).result; 79 | 80 | res.status(200).send(await req.app.page(req, "settings", { 81 | textChannels, 82 | guild: id, 83 | modLog, 84 | settings: guildSettings, 85 | roles 86 | })).end(); 87 | } else { 88 | res.status(200).send(await req.app.page(req, "invite", { guild: id })).end(); 89 | } 90 | }); 91 | 92 | const readableActions = { 93 | specialRoleAdd: "Special Role Added", 94 | specialRoleRemove: "Special Role Removed" 95 | }; 96 | 97 | router.get("/modlog/*", async (req, res) => { 98 | let id = req.path.substring(7).replace(/\//g, ""); 99 | if(!id.match(/\d+/)) { 100 | res.status(404).send(await req.app.page(req, "404", { guild: id })).end(); 101 | return; 102 | } 103 | 104 | let hasServer = (await process.output({ 105 | target: id, 106 | input: `return bot.guilds.has("${id}")`, 107 | type: "guild" 108 | })).result; 109 | 110 | if(hasServer) { 111 | let cases = await r.table("modLog").getAll(id, { index: "guildID" }).run(); 112 | let roles = (await process.output({ 113 | target: id, 114 | input: `return bot.guilds.get("${id}").roles.map(data => ({ id: data.id, name: data.name }))`, 115 | type: "guild" 116 | })).result; 117 | cases = cases.sort((a, b) => b.caseNum - a.caseNum); 118 | cases.forEach(data => { 119 | data.action = readableActions[data.action] || 120 | data.action.substring(0, 1).toUpperCase() + data.action.substring(1); 121 | if(data.role) data.role = roles.find(role => role.id === data.role).name || data.role; 122 | }); 123 | if(!cases.length) { 124 | res.status(200).send(await req.app.page(req, "modlog", { guild: id })).end(); 125 | } else { 126 | res.status(200).send(await req.app.page(req, "modlog", { guild: id, cases })).end(); 127 | } 128 | } else { 129 | res.status(200).send(await req.app.page(req, "invite", { guild: id })).end(); 130 | } 131 | }); 132 | 133 | let settings = require(require("path").resolve("src", "bot", "commands", "admin", "settings")).settings; 134 | settings = Object.keys(settings).map(key => Object.assign({ name: key }, settings[key])); 135 | -------------------------------------------------------------------------------- /src/website/routes/emojis.js: -------------------------------------------------------------------------------- 1 | const router = module.exports = require("express").Router(); // eslint-disable-line new-cap 2 | 3 | let emojis = {}; 4 | router.get("/", async (req, res) => { 5 | let displayEmojis = emojis; 6 | if(req.query.search) displayEmojis = displayEmojis.filter(emoji => ~emoji.name.indexOf(req.query.search)); 7 | 8 | let page = req.query.page ? parseInt(req.query.page) : 1; 9 | let totalPages = Math.ceil(displayEmojis.length / 1500); 10 | if(page > totalPages) page = totalPages; 11 | 12 | res.status(200).send(await req.app.page(req, "emojis", { 13 | emojis: displayEmojis.slice((page - 1) * 750, page * 750), 14 | page, 15 | totalPages, 16 | prevPage: page - 1, 17 | nextPage: page + 1 > totalPages ? false : page + 1 18 | })).end(); 19 | }); 20 | 21 | async function updateEmojis() { 22 | let backup = emojis; 23 | emojis = (await process.output({ 24 | input: `return Array.from(bot.guilds.values())` + 25 | `.map(g => g.emojis.map(e => ({ id: e.id, name: e.name })))` + 26 | `.reduce((a, b) => a.concat(b))`, 27 | type: "all_shards" 28 | })).results.reduce((a, b) => a.concat(b), []); 29 | 30 | if(!emojis) emojis = backup; 31 | } 32 | setTimeout(updateEmojis, 100); 33 | setInterval(updateEmojis, 3600000); 34 | -------------------------------------------------------------------------------- /src/website/routes/faq.js: -------------------------------------------------------------------------------- 1 | const router = module.exports = require("express").Router(); // eslint-disable-line new-cap 2 | 3 | router.get("/", async (req, res) => { 4 | res.status(200).send(await req.app.page(req, "faq")).end(); 5 | }); 6 | -------------------------------------------------------------------------------- /src/website/routes/index.js: -------------------------------------------------------------------------------- 1 | const router = module.exports = require("express").Router(); // eslint-disable-line new-cap 2 | 3 | router.get("/", async (req, res) => { 4 | res.status(200).send(await req.app.page(req, "index")).end(); 5 | }); 6 | -------------------------------------------------------------------------------- /src/website/routes/logout.js: -------------------------------------------------------------------------------- 1 | const router = module.exports = require("express").Router(); // eslint-disable-line new-cap 2 | 3 | router.get("/", async (req, res) => { 4 | res.status(200).send(``).end(); 8 | }); 9 | -------------------------------------------------------------------------------- /src/website/routes/stats.js: -------------------------------------------------------------------------------- 1 | const router = module.exports = require("express").Router(); // eslint-disable-line new-cap 2 | 3 | router.get("/", async (req, res) => { 4 | res.status(200).send(await req.app.page(req, "stats")).end(); 5 | }); 6 | -------------------------------------------------------------------------------- /src/website/routes/update.js: -------------------------------------------------------------------------------- 1 | const router = module.exports = require("express").Router(); // eslint-disable-line new-cap 2 | 3 | router.post("/reset/*", async (req, res) => { 4 | let id = req.path.substring(6).replace(/\//g, ""); 5 | if(!id.match(/\d+/)) { 6 | res.status(400).end(); 7 | return; 8 | } 9 | 10 | let { result: hasServer } = await process.output({ 11 | target: id, 12 | input: `return bot.guilds.has("${id}")`, 13 | type: "guild" 14 | }); 15 | if(!hasServer) { 16 | res.status(404).json({ error: "Bot not in Server" }).end(); 17 | return; 18 | } 19 | 20 | if(!req.cookies.currentToken) { 21 | res.status(401).json({ error: "Not logged on" }).end(); 22 | return; 23 | } else { 24 | req.currentToken = JSON.parse(req.cookies[`token_${req.cookies.currentToken}`]); 25 | } 26 | 27 | let guilds = await req.app.discordInfo(null, "users/@me/guilds", req); 28 | let isAdmin = guilds.find(guild => guild.id === id).permissions & (1 << 3); 29 | if(!isAdmin) { 30 | res.status(401).json({ error: "Insufficient permissions" }).end(); 31 | return; 32 | } 33 | 34 | if(req.body.name === "prefix") { 35 | process.output({ 36 | target: id, 37 | input: `bot.prefixes.delete("${id}")`, 38 | type: "guildEval" 39 | }); 40 | } 41 | 42 | await r.table("settings").get([req.body.name, id]).delete().run(); 43 | res.status(200).json({ success: true }).end(); 44 | }); 45 | 46 | router.post("/set/*", async (req, res) => { 47 | let id = req.path.substring(4).replace(/\//g, ""); 48 | if(!id.match(/\d+/)) { 49 | res.status(400).json({ error: "Invalid Guild ID" }).end(); 50 | return; 51 | } 52 | 53 | let { result: hasServer } = await process.output({ 54 | target: id, 55 | input: `return bot.guilds.has("${id}")`, 56 | type: "guild" 57 | }); 58 | if(!hasServer) { 59 | res.status(404).json({ error: "Bot not in Server" }).end(); 60 | return; 61 | } 62 | 63 | if(!req.cookies.currentToken) { 64 | res.status(401).json({ error: "Not logged on" }).end(); 65 | return; 66 | } else { 67 | req.currentToken = JSON.parse(req.cookies[`token_${req.cookies.currentToken}`]); 68 | } 69 | 70 | let guilds = await req.app.discordInfo(null, "users/@me/guilds", req); 71 | let isAdmin = guilds.find(guild => guild.id === id).permissions & (1 << 3); 72 | if(!isAdmin) { 73 | res.status(401).json({ error: "Insufficient permissions" }).end(); 74 | return; 75 | } 76 | 77 | Object.keys(req.body).forEach(key => { 78 | if(req.body[key] === "true") req.body[key] = true; 79 | else if(req.body[key] === "false") req.body[key] = false; 80 | }); 81 | 82 | if(Object.keys(req.body).length === 1) { 83 | let settingName = Object.keys(req.body)[0]; 84 | let current = await r.table("settings").get([settingName, id]).run(); 85 | if(current) { 86 | await r.table("settings").get(current.id).update({ value: req.body[settingName] }).run(); 87 | } else { 88 | await r.table("settings") 89 | .insert({ id: [settingName, id], guildID: id, name: settingName, value: req.body[settingName] }).run(); 90 | } 91 | 92 | if(settingName === "prefix") { 93 | process.output({ 94 | target: id, 95 | input: `bot.prefixes.set("${id}", "${req.body.prefix}")`, 96 | type: "guild" 97 | }); 98 | } 99 | 100 | res.status(200).json({ success: true }).end(); 101 | } else if(Object.keys(req.body)[0].startsWith("track")) { 102 | let value = [], settingName = "modLog.track"; 103 | Object.keys(req.body) 104 | .filter(key => req.body[key]) 105 | .forEach(key => value.push(key.substring(key.indexOf("[") + 1, key.indexOf("]")))); 106 | 107 | let current = await r.table("settings").get([settingName, id]).run(); 108 | if(current && value.length) { 109 | await r.table("settings").get(current.id).update({ value }).run(); 110 | } else if(current && !value.length) { 111 | await r.table("settings").get(current.id).delete().run(); 112 | } else { 113 | await r.table("settings") 114 | .insert({ id: [settingName, id], guildID: id, name: settingName, value: req.body[settingName] }).run(); 115 | } 116 | 117 | res.status(200).json({ success: true }).end(); 118 | } 119 | }); 120 | -------------------------------------------------------------------------------- /src/website/views/404.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Oxyl - 404 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 69 |
70 |
71 | 72 | 404 73 |
74 |
75 |

Page not found

76 |
77 |
78 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /src/website/views/accounts.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Oxyl - Accounts 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 |
17 | Login 18 |
19 | 20 |
21 |

Select account

22 |
23 |
24 |
25 | {{#if accounts}} 26 | {{#each accounts}} 27 |
28 |
29 | {{this.username}}#{{this.discriminator}} 30 |
31 | {{/each}} 32 | {{/if}} 33 |
34 | Add account 35 |
36 |
37 |
38 |
39 |
40 | 41 | 42 | -------------------------------------------------------------------------------- /src/website/views/index.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Oxyl - Home 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 83 |
84 |
85 | 86 | Information 87 |
88 |
89 |

Oxyl is a multipurpose Discord Bot coded in Node.js 90 | using the library Eris with features such as 91 | moderation and music.

92 |

Oxyl was created and founded by minemidnight#0001

93 |
94 |
95 | 107 | 108 | 109 | -------------------------------------------------------------------------------- /src/website/views/invite.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Oxyl - Invite 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 73 |
74 |
75 | 76 | {{guild.name}} 77 |
78 |
79 |

Oxyl is not in {{guild.name}}, please invite him by clicking the invite below.

80 |
81 | 82 | 84 |
85 |
86 |
87 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /src/website/views/modlog.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Oxyl - ModLog 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 79 |
80 |
81 | 82 | {{guild.name}} 83 |
84 |
85 | {{#if cases}} 86 |
87 | {{#each cases}} 88 |
89 |
90 |
Case {{this.caseNum}}
91 |
92 |
Action
93 | {{this.action}} {{#if this.role}}({{this.role}}){{/if}} 94 |
95 | {{#if this.warnCount}} 96 |
97 |
Total Warnings
98 | {{this.warnCount}} 99 |
100 | {{/if}} 101 |
102 |
User
103 | {{this.userDisplay}} 104 |
105 |
106 |
Reason
107 | {{this.reason}} 108 |
109 |
110 |
Mod
111 | {{this.modDisplay}} 112 |
113 |
114 |
115 | {{/each}} 116 |
117 | {{else}} 118 |

No cases found

119 | {{/if}} 120 |
121 |
122 | 134 | 135 | 136 | -------------------------------------------------------------------------------- /src/website/views/stats.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Oxyl - Stats 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 78 |
79 |
80 | 81 | Bot Stats 82 |
83 |
84 | 85 |
86 |
87 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /src/worker.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | async function startupMessage(msg) { 3 | if(msg.type !== "startup") { 4 | cluster.worker.once("message", startupMessage); 5 | return; 6 | } 7 | 8 | if(msg.processType === "bot") { 9 | delete msg.type; 10 | delete msg.processType; 11 | 12 | Object.assign(cluster.worker, msg); 13 | require(path.resolve("src", "bot", "index")); 14 | } else { 15 | setTimeout(() => require(path.resolve("src", "website", "index")), 120000); 16 | } 17 | } 18 | 19 | cluster.worker.once("message", startupMessage); 20 | -------------------------------------------------------------------------------- /src/worker_handling/bot.js: -------------------------------------------------------------------------------- 1 | const messageHandler = require("../worker_handling/messages.js"); 2 | const webhook = require("../misc/webhookStatus"); 3 | const workerCrashes = {}; 4 | 5 | module.exports = async worker => { 6 | worker.on("online", () => { 7 | console.startup(`Worker ${worker.id} started (hosting ${worker.shardRange})`); 8 | worker.send({ 9 | type: "startup", 10 | shardRange: worker.shardRange, 11 | shardStart: worker.shardStart, 12 | shardEnd: worker.shardEnd, 13 | totalShards: worker.totalShards, 14 | processType: "bot" 15 | }); 16 | 17 | if(process.uptime() >= 50) { 18 | webhook({ 19 | title: `Worker ${worker.id} started`, 20 | color: 0x00FF00, 21 | description: `Hosting ${worker.shardRange}`, 22 | timestamp: new Date() 23 | }); 24 | } 25 | }); 26 | 27 | worker.on("exit", (code, signal) => { 28 | if(signal) { 29 | return; 30 | } else if(code === 0) { 31 | console.info(`Worker ${worker.id} killed successfully ` + 32 | `(hosted shards ${worker.shardStart}-${worker.shardEnd}).`); 33 | 34 | webhook({ 35 | title: `Worker ${worker.id} killed`, 36 | color: 0xFFFF00, 37 | description: `Exit code: ${code} (success)\nHosted ${worker.shardRange}`, 38 | timestamp: new Date() 39 | }); 40 | } else if(workerCrashes[worker.shardRange] >= 2) { 41 | console.error(`Worker ${worker.id} killed due to restart loop with ` + 42 | `exit code: ${code} (hosted ${worker.shardRange}).`); 43 | 44 | webhook({ 45 | title: `Worker ${worker.id} killed (restart loop)`, 46 | color: 0xFF0000, 47 | description: `Exit code: ${code}\nHosted ${worker.shardRange}`, 48 | timestamp: new Date() 49 | }); 50 | } else { 51 | console.warn(`Worker ${worker.id} died with exit code ${code} ` + 52 | `(hosted ${worker.shardRange}). Respawning new process...`); 53 | const newWorker = cluster.fork(); 54 | Object.assign(newWorker, { 55 | type: "bot", 56 | shardStart: worker.shardStart, 57 | shardEnd: worker.shardEnd, 58 | shardRange: worker.shardRange, 59 | totalShards: worker.totalShards 60 | }); 61 | module.exports(newWorker); 62 | 63 | webhook({ 64 | title: `Worker ${worker.id} died`, 65 | color: 0xFFFF00, 66 | description: `Exit code: ${code}\nHosted ${worker.shardRange}\n` + 67 | `Respawned as ${newWorker.id}`, 68 | timestamp: new Date() 69 | }); 70 | 71 | if(!workerCrashes[worker.shardRange]) workerCrashes[worker.shardRange] = 1; 72 | else workerCrashes[worker.shardRange]++; 73 | setTimeout(() => { 74 | if(workerCrashes[worker.shardRange] === 1) delete workerCrashes[worker.shardRange]; 75 | else workerCrashes[worker.shardRange]--; 76 | }, 120000); 77 | } 78 | }); 79 | 80 | worker.on("message", msg => messageHandler(msg, worker)); 81 | }; 82 | -------------------------------------------------------------------------------- /src/worker_handling/messages.js: -------------------------------------------------------------------------------- 1 | const waitingResults = {}; 2 | module.exports = async (msg, worker) => { 3 | if(msg.type === "master") { 4 | try { 5 | let result = await eval(msg.input); 6 | worker.send({ type: "output", result, id: msg.id }); 7 | } catch(err) { 8 | worker.send({ type: "output", error: err.stack, id: msg.id }); 9 | } 10 | } else if(msg.type === "site") { 11 | let targetWorker = cluster.onlineWorkers.find(work => work.type === "site"); 12 | if(!targetWorker) { 13 | worker.send({ type: "output", error: "No website worker", id: msg.id }); 14 | } else { 15 | waitingResults[msg.id] = { 16 | expected: 1, 17 | results: [], 18 | worker 19 | }; 20 | targetWorker.send({ type: "eval", input: msg.input, id: msg.id }); 21 | } 22 | } else if(msg.type === "shard") { 23 | let targetShard = msg.target; 24 | if(!targetShard) { 25 | worker.send({ type: "output", error: "Invalid target", id: msg.id }); 26 | } else { 27 | let targetWorker = cluster.onlineWorkers.find(work => work.type === "bot" && 28 | targetShard >= work.shardStart && 29 | targetShard <= work.shardEnd); 30 | 31 | if(!targetWorker) { 32 | worker.send({ type: "output", error: "Invalid target (not found)", id: msg.id }); 33 | } else { 34 | waitingResults[msg.id] = { 35 | expected: 1, 36 | results: [], 37 | worker 38 | }; 39 | targetWorker.send({ type: "eval", input: msg.input, id: msg.id }); 40 | } 41 | } 42 | } else if(msg.type === "guild") { 43 | if(!msg.target) { 44 | worker.send({ type: "output", error: "Invalid target", id: msg.id }); 45 | } else { 46 | let targetShard = ~~((msg.target / 4194304) % process.totalShards); 47 | let targetWorker = cluster.onlineWorkers.find(work => work.type === "bot" && 48 | targetShard >= work.shardStart && 49 | targetShard <= work.shardEnd); 50 | 51 | if(!targetWorker) { 52 | worker.send({ type: "output", error: "Invalid target (not found)", id: msg.id }); 53 | } else { 54 | waitingResults[msg.id] = { 55 | expected: 1, 56 | results: [], 57 | worker 58 | }; 59 | targetWorker.send({ type: "eval", input: msg.input, id: msg.id }); 60 | } 61 | } 62 | } else if(msg.type === "all_shards") { 63 | let workers = cluster.onlineWorkers.filter(work => work.type === "bot"); 64 | waitingResults[msg.id] = { 65 | alwaysPlural: true, 66 | expected: workers.length, 67 | results: [], 68 | worker 69 | }; 70 | workers.forEach(work => work.send({ type: "eval", input: msg.input, id: msg.id })); 71 | } else if(msg.type === "output") { 72 | if(!waitingResults[msg.id]) return; 73 | let waiting = waitingResults[msg.id]; 74 | 75 | if(msg.error) { 76 | waiting.worker.send({ type: "output", error: msg.error, id: msg.id }); 77 | delete waitingResults[msg.id]; 78 | return; 79 | } 80 | 81 | waiting.results.push(msg.result); 82 | if(waiting.results.length === waiting.expected) { 83 | if(waiting.expected > 1 || waiting.alwaysPlural) { 84 | waiting.worker.send({ type: "output", results: waiting.results, id: msg.id }); 85 | } else { 86 | waiting.worker.send({ type: "output", result: waiting.results[0], id: msg.id }); 87 | } 88 | 89 | delete waitingResults[msg.id]; 90 | } 91 | } 92 | }; 93 | -------------------------------------------------------------------------------- /src/worker_handling/site.js: -------------------------------------------------------------------------------- 1 | const messageHandler = require("../worker_handling/messages.js"); 2 | const webhook = require("../misc/webhookStatus"); 3 | let crashes = 0; 4 | 5 | module.exports = async worker => { 6 | worker.on("online", () => { 7 | worker.send({ type: "startup", processType: "site" }); 8 | if(process.uptime() >= 50) { 9 | webhook({ 10 | title: `Worker ${worker.id} started`, 11 | color: 0x00FF00, 12 | description: `Hosting website`, 13 | timestamp: new Date() 14 | }); 15 | } 16 | }); 17 | 18 | worker.on("exit", (code, signal) => { 19 | if(signal) { 20 | return; 21 | } else if(code === 0) { 22 | console.info(`Worker ${worker.id} killed successfully (website).`); 23 | 24 | webhook({ 25 | title: `Worker ${worker.id} killed`, 26 | color: 0xFFFF00, 27 | description: `Exit code: ${code} (success)\nHosted website`, 28 | timestamp: new Date() 29 | }); 30 | } else if(crashes >= 3) { 31 | console.error(`Worker ${worker.id} killed due to restart loop with ` + 32 | `exit code: ${code} (hosted website).`); 33 | 34 | webhook({ 35 | title: `Worker ${worker.id} killed (restart loop)`, 36 | color: 0xFF0000, 37 | description: `Exit code: ${code}\nHosted ${worker.shardRange}`, 38 | timestamp: new Date() 39 | }); 40 | } else { 41 | console.warn(`Worker ${worker.id} died with exit code ${code} ` + 42 | `(hosted website). Respawning new process...`); 43 | const newWorker = cluster.fork(); 44 | Object.assign(newWorker, { type: "website" }); 45 | module.exports(newWorker); 46 | 47 | webhook({ 48 | title: `Worker ${worker.id} died`, 49 | color: 0xFFFF00, 50 | description: `Exit code: ${code}\nHosted website\nRespawned as ${newWorker.id}`, 51 | timestamp: new Date() 52 | }); 53 | 54 | crashes++; 55 | setTimeout(() => crashes--, 120000); 56 | } 57 | }); 58 | 59 | worker.on("message", msg => messageHandler(msg, worker)); 60 | }; 61 | --------------------------------------------------------------------------------